/*      $OpenBSD: realpath.c,v 1.13 2005/08/08 08:05:37 espie Exp $ */
/*
 * realpath:
 * Copyright (c) 2003 Constantin S. Svintsoff <kostik@iclub.nsu.ru>
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. The names of the authors may not be used to endorse or promote
 *    products derived from this software without specific prior written
 *    permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

/*
 * strlcpy() / strlcat():
 * Copyright (c) 1998 Todd C. Miller <Todd.Miller@courtesan.com>
 *
 * Permission to use, copy, modify, and distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */


#include <config.h>
#define FAKECHROOT_EXCEPTION 1

#include "ftpd.h"
#include "bsd-realpath.h"
#if !defined(HAVE_REALPATH) || defined(USE_BUILTIN_REALPATH)

# ifdef WITH_DMALLOC
#  include <dmalloc.h>
# endif

# ifndef MAXSYMLINKS
#  define MAXSYMLINKS 5
# endif
# if MAXSYMLINKS > UINT_MAX
#  undef MAXSYMLINKS
#  define MAXSYMLINKS UINT_MAX
# endif

#ifndef HAVE_STRLCPY
/*
 * Copy src to string dst of size siz.  At most siz-1 characters
 * will be copied.  Always NUL terminates (unless siz == 0).
 * Returns strlen(src); if retval >= siz, truncation occurred.
 */
static size_t strlcpy(char *dst, const char * const src, const size_t siz)
{
    char *d = dst;
    const char *s = src;
    size_t n = siz;

    /* Copy as many bytes as will fit */
    if (n != 0 && --n != 0) {
        do {
            if ((*d++ = *s++) == 0) {
                break;
            }
        } while (--n != 0);
    }

    /* Not enough room in dst, add NUL and traverse rest of src */
    if (n == 0) {
        if (siz != 0) {
            *d = '\0';          /* NUL-terminate dst */
        }
        while (*s++) {
        }
    }
    return s - src - 1; /* count does not include NUL */
}
#endif

#ifndef HAVE_STRLCAT
/*
 * Appends src to string dst of size siz (unlike strncat, siz is the
 * full size of dst, not space left).  At most siz-1 characters
 * will be copied.  Always NUL terminates (unless siz <= strlen(dst)).
 * Returns strlen(src) + MIN(siz, strlen(initial dst)).
 * If retval >= siz, truncation occurred.
 */
static size_t strlcat(char *dst, const char * const src, const size_t siz)
{
    char *d = dst;
    const char *s = src;
    size_t n = siz;
    size_t dlen;

    /* Find the end of dst and adjust bytes left but don't go past end */
    while (n-- != 0 && *d != '\0') {
        d++;
    }
    dlen = d - dst;
    n = siz - dlen;
    if (n == 0) {
        return dlen + strlen(s);
    }
    while (*s != '\0') {
        if (n != 1) {
            *d++ = *s;
            n--;
        }
        s++;
    }
    *d = '\0';

    return dlen + (s - src);    /* count does not include NUL */
}
#endif

/*
 * Find the real name of path, by removing all ".", ".." and symlink
 * components.  Returns resolved on success, or NULL on failure,
 * in which case the path which caused trouble is left in (resolved).
 */
char *bsd_realpath(const char *path, char resolved[PATH_MAX])
{
    struct stat sb;
    char *p, *q;
    size_t left_len, resolved_len, next_token_len;
    unsigned symlinks;
    int serrno;
    ssize_t slen;
    char left[PATH_MAX], next_token[PATH_MAX], symlink[PATH_MAX];

    serrno = errno;
    symlinks = 0;
    if (path[0] == '/') {
        resolved[0] = '/';
        resolved[1] = '\0';
        if (path[1] == '\0') {
            return resolved;
        }
        resolved_len = 1;
        left_len = strlcpy(left, path + 1, sizeof left);
    } else {
        if (getcwd(resolved, PATH_MAX) == NULL) {
            strlcpy(resolved, ".", PATH_MAX);
            return NULL;
        }
        resolved_len = strlen(resolved);
        left_len = strlcpy(left, path, sizeof left);
    }
    if (left_len >= sizeof left || resolved_len >= PATH_MAX) {
        errno = ENAMETOOLONG;
        return NULL;
    }

    /*
     * Iterate over path components in `left'.
     */
    while (left_len != 0) {
        /*
         * Extract the next path component and adjust `left'
         * and its length.
         */
        p = strchr(left, '/');
        next_token_len = p ? p - left : left_len;
        memcpy(next_token, left, next_token_len);
        next_token[next_token_len] = '\0';
        if (p != NULL) {
            left_len -= next_token_len + 1;
            memmove(left, p + 1, left_len + 1);
        } else {
            left[0] = '\0';
            left_len = 0;
        }
        if (resolved[resolved_len - 1] != '/') {
            if (resolved_len + 1 >= PATH_MAX) {
                errno = ENAMETOOLONG;
                return NULL;
            }
            resolved[resolved_len++] = '/';
            resolved[resolved_len] = '\0';
        }
        if (next_token[0] == '\0' || strcmp(next_token, ".") == 0) {
            continue;
        } else if (strcmp(next_token, "..") == 0) {
            /*
             * Strip the last path component except when we have
             * single "/"
             */
            if (resolved_len > 1) {
                resolved[resolved_len - 1] = '\0';
                q = strrchr(resolved, '/') + 1;
                *q = '\0';
                resolved_len = q - resolved;
            }
            continue;
        }

        /*
         * Append the next path component and lstat() it.
         */
        resolved_len = strlcat(resolved, next_token, PATH_MAX);
        if (resolved_len >= PATH_MAX) {
            errno = ENAMETOOLONG;
            return NULL;
        }
        if (lstat(resolved, &sb) != 0) {
            return NULL;
        }
        if (S_ISLNK(sb.st_mode)) {
            if (symlinks++ > MAXSYMLINKS) {
                errno = ELOOP;
                return NULL;
            }
            slen = readlink(resolved, symlink, sizeof symlink);
            if (slen < 0 || slen >= sizeof symlink) {
                if (slen < 0) {
                    /* keep errno from readlink(2) call */
                } else if (slen == 0) {
                    errno = ENOENT;
                } else {
                    errno = ENAMETOOLONG;
                }
                return NULL;
            }
            symlink[slen] = '\0';
            if (symlink[0] == '/') {
                resolved[1] = 0;
                resolved_len = 1;
            } else {
                /* Strip the last path component. */
                q = strrchr(resolved, '/') + 1;
                *q = '\0';
                resolved_len = q - resolved;
            }

            /*
             * If there are any path components left, then
             * append them to symlink. The result is placed
             * in `left'.
             */
            if (p != NULL) {
                if (symlink[slen - 1] != '/') {
                    if ((size_t) slen >= (sizeof symlink) - 1U) {
                        errno = ENAMETOOLONG;
                        return NULL;
                    }
                    symlink[slen] = '/';
                    symlink[slen + 1] = 0;
                }
                left_len = strlcat(symlink, left, sizeof symlink);
                if (left_len >= sizeof symlink) {
                    errno = ENAMETOOLONG;
                    return NULL;
                }
            }
            left_len = strlcpy(left, symlink, sizeof left);
        } else if (!S_ISDIR(sb.st_mode) && p != NULL) {
            errno = ENOTDIR;
            return NULL;
        }
    }

    /*
     * Remove trailing slash except when the resolved pathname
     * is a single "/".
     */
    if (resolved_len > 1 && resolved[resolved_len - 1] == '/') {
        resolved[resolved_len - 1] = '\0';
    }
    return resolved;
}

#endif
