
/*
 * Copyright (c) 1989, 1993
 *    The Regents of the University of California.  All rights reserved.
 *
 * This code is derived from software contributed to Berkeley by
 * Guido van Rossum.
 *
 * 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. Neither the name of the University nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE REGENTS 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 REGENTS 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.
 */

/* Recursion and matches limiting by Frank Denis <j at pureftpd dot org> */

/*
 * glob(3) -- a superset of the one defined in POSIX 1003.2.
 *
 * The [!...] convention to negate a range is supported (SysV, Posix, ksh).
 *
 * Optional extra services, controlled by flags not defined by POSIX:
 *
 * GLOB_QUOTE:
 *    Escaping convention: \ inhibits any special meaning the following
 *    character might have (except \ at end of string is retained).
 * GLOB_MAGCHAR:
 *    Set in gl_flags if pattern contained a globbing character.
 * GLOB_NOMAGIC:
 *    Same as GLOB_NOCHECK, but it will only append pattern if it did
 *    not contain any magic characters.  [Used in csh style globbing]
 * GLOB_BRACE:
 *    expand {1,2}{a,b} to 1a 1b 2a 2b
 * gl_matchc:
 *    Number of matches in the current invocation of glob.
 */

#include <config.h>

#ifndef DISABLE_GLOBBING
#include "ftpd.h"
#ifdef HAVE_DIRENT_H
# include <dirent.h>
#endif
#ifdef HAVE_SYS_NDIR_H
# include <sys/ndir.h>
#endif
#ifdef HAVE_NDIR_H
# include <ndir.h>
#endif
#include "bsd-glob.h"

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

#define    DOLLAR        '$'
#define    DOT        '.'
#define    EOS        '\0'
#define    LBRACKET    '['
#define    NOT        '!'
#define    QUESTION    '?'
#define    QUOTE        '\\'
#define    RANGE        '-'
#define    RBRACKET    ']'
#define    SEP        '/'
#define    STAR        '*'
#ifdef USELESS_FOR_PUREFTPD
# define    TILDE        '~'
#endif
#define    UNDERSCORE    '_'
#define    LBRACE        '{'
#define    RBRACE        '}'
#define    SLASH        '/'
#define    COMMA        ','

#define    M_QUOTE        0x8000
#define    M_PROTECT    0x4000
#define    M_MASK        0xffff
#define    M_ASCII        0x00ff

typedef unsigned short Char;

#define    CHAR(c)        ((Char)((c)&M_ASCII))
#define    META(c)        ((Char)((c)|M_QUOTE))
#define    M_ALL        META('*')
#define    M_END        META(']')
#define    M_NOT        META('!')
#define    M_ONE        META('?')
#define    M_RNG        META('-')
#define    M_SET        META('[')
#define    ismeta(c)    (((c)&M_QUOTE) != 0)

static int compare(const void *, const void *);
static int g_Ctoc(const Char *, char *, unsigned int);
static int g_lstat(Char *, struct stat *);
static DIR *g_opendir(Char *);
static const Char *g_strchr(const Char *, int);
static int glob0(const Char *, glob_t *);
static int glob1(Char *, Char *, glob_t *, size_t *);
static int glob2(Char *, Char *, Char *, Char *, Char *, Char *,
                 glob_t *, size_t *, int);
static int glob3(Char *, Char *, Char *, Char *, Char *,
                 Char *, Char *, glob_t *, size_t *, int);
static int globextend(const Char *, glob_t *, size_t *);
#ifdef USELESS_FOR_PUREFTPD
static const Char *globtilde(const Char *, Char *, size_t, glob_t *);
#endif
static int globexp1(const Char *, glob_t *, int);
static int globexp2(const Char *, const Char *, glob_t *, int *, int);
static int match(Char *, Char *, Char *);
static int glob_(const char *pattern,
                 int flags,
                 int (*errfunc) (const char *, int),
                 glob_t * pglob, unsigned long maxfiles, int maxdepth);

static int glob_(const char *pattern,
                 int flags,
                 int (*errfunc) (const char *, int),
                 glob_t * pglob, unsigned long maxfiles, int maxdepth)
{
    const unsigned char *patnext;
    int c;
    Char *bufnext, *bufend, patbuf[MAXPATHLEN + 1];

    pglob->gl_maxdepth = maxdepth;
    pglob->gl_maxfiles = maxfiles;
    patnext = (const unsigned char *) pattern;
    pglob->gl_flags = flags & ~GLOB_MAGCHAR;
    pglob->gl_errfunc = errfunc;
    pglob->gl_matchc = 0;
    pglob->gl_pathc = 0;
    pglob->gl_pathv = NULL;

    bufnext = patbuf;
    bufend = bufnext + MAXPATHLEN;
    if (flags & GLOB_NOESCAPE) {
        while (bufnext < bufend && (c = *patnext++) != EOS) {
            *bufnext++ = (Char) c;
	}
    } else {
        /* Protect the quoted characters. */
        while (bufnext < bufend && (c = *patnext++) != EOS) {
            if (c == QUOTE) {
                if ((c = *patnext++) == EOS) {
                    c = QUOTE;
                    --patnext;
                }
                *bufnext++ = (Char) (c | M_PROTECT);
            } else {
                *bufnext++ = (Char) c;
        }
        }
    }
    *bufnext = EOS;

    if (flags & GLOB_BRACE) {
        return globexp1(patbuf, pglob, 0);
    }
    return glob0(patbuf, pglob);    
}

int
glob(const char *pattern,
     int flags, int (*errfunc) (const char *, int), glob_t * pglob)
{
    return glob_(pattern, flags, errfunc, pglob, (unsigned long) -1, 0);
}

int
sglob(char *pattern,
      int flags,
      int (*errfunc) (const char *, int),
      glob_t * pglob, unsigned long maxfiles, int maxdepth)
{
    simplify(pattern);
    return glob_(pattern, flags, errfunc, pglob, maxfiles, maxdepth);
}

/*
 * Expand recursively a glob {} pattern. When there is no more expansion
 * invoke the standard globbing routine to glob the rest of the magic
 * characters
 */
static int globexp1(const Char * pattern, glob_t * pglob, int recursion)
{
    const Char *ptr = pattern;
    int rv = 0;

    if (pglob->gl_maxdepth > 0 && recursion > pglob->gl_maxdepth) {
        errno = 0;
        return 0;
    }
    /* Protect a single {}, for find(1), like csh */
    if (pattern[0] == LBRACE && pattern[1] == RBRACE && pattern[2] == EOS)
        return glob0(pattern, pglob);

    while ((ptr =
            (const Char *) g_strchr((const Char *) ptr, LBRACE)) != NULL) {
        if (!globexp2(ptr, pattern, pglob, &rv, recursion + 1))
            return rv;
    }

    return glob0(pattern, pglob);
}


/*
 * Recursive brace globbing helper. Tries to expand a single brace.
 * If it succeeds then it invokes globexp1 with the new pattern.
 * If it fails then it tries to glob the rest of the pattern and returns.
 */
static int
globexp2(const Char * ptr,
         const Char * pattern, glob_t * pglob, int *rv, int recursion)
{
    int i;
    Char *lm, *ls;
    const Char *pe, *pm, *pl;
    Char patbuf[MAXPATHLEN + 1];

    if (pglob->gl_maxdepth > 0 && recursion > pglob->gl_maxdepth) {
        errno = 0;
        return 0;
    }
    /* copy part up to the brace */
    for (lm = patbuf, pm = pattern; pm != ptr; *lm++ = *pm++);
    *lm = EOS;
    ls = lm;

    /* Find the balanced brace */
    for (i = 0, pe = ++ptr; *pe; pe++)
        if (*pe == LBRACKET) {
            /* Ignore everything between [] */
            for (pm = pe++; *pe != RBRACKET && *pe != EOS; pe++);
            if (*pe == EOS) {
                /*
                 * We could not find a matching RBRACKET.
                 * Ignore and just look for RBRACE
                 */
                pe = pm;
            }
        } else if (*pe == LBRACE) {
            i++;
        } else if (*pe == RBRACE) {
            if (i == 0)
                break;
            i--;
        }

    /* Non matching braces; just glob the pattern */
    if (i != 0 || *pe == EOS) {
        *rv = glob0(patbuf, pglob);
        return 0;
    }

    for (i = 0, pl = pm = ptr; pm <= pe; pm++) {
        switch (*pm) {
        case LBRACKET:
            /* Ignore everything between [] */
            for (pl = pm++; *pm != RBRACKET && *pm != EOS; pm++);
            if (*pm == EOS) {
                /*
                 * We could not find a matching RBRACKET.
                 * Ignore and just look for RBRACE
                 */
                pm = pl;
            }
            break;

        case LBRACE:
            i++;
            break;

        case RBRACE:
            if (i) {
                i--;
                break;
            }
            /* FALLTHROUGH */
        case COMMA:
            if (i && *pm == COMMA)
                break;
            else {
                /* Append the current string */
                for (lm = ls; (pl < pm); *lm++ = *pl++);

                /*
                 * Append the rest of the pattern after the
                 * closing brace
                 */
                for (pl = pe + 1; (*lm++ = *pl++) != EOS;);

                /* Expand the current pattern */
                *rv = globexp1(patbuf, pglob, recursion + 1);

                /* move after the comma, to the next string */
                pl = pm + 1;
            }
            break;

        default:
            break;
        }
    }
    *rv = 0;
    return 0;
}


#ifdef USELESS_FOR_PUREFTPD

/*
 * expand tilde from the passwd file.
 */
static const Char *globtilde(const Char * pattern,
                             Char * patbuf,
                             size_t patbuf_len, glob_t * pglob)
{
    struct passwd *pwd;
    char *h;
    const Char *p;
    Char *b, *eb;

    if (*pattern != TILDE || !(pglob->gl_flags & GLOB_TILDE))
        return pattern;

    /* Copy up to the end of the string or / */
    eb = &patbuf[patbuf_len - 1];
    for (p = pattern + 1, h = (char *) patbuf;
         h < (char *) eb && *p && *p != SLASH; *h++ = *p++);

    *h = EOS;

#if 0
    if (h == (char *) eb)
        return what;
#endif

    if (((char *) patbuf)[0] == EOS) {
        /*
         * handle a plain ~ or ~/ by expanding $HOME
         * first and then trying the password file
         */
        if (issetugid() != 0 || (h = getenv("HOME")) == NULL) {
            if ((pwd = getpwuid(getuid())) == NULL) {
                return pattern;
            } else {
                h = pwd->pw_dir;
            }
        }
    } else {
        /*
         * Expand a ~user
         */
        if ((pwd = getpwnam((char *) patbuf)) == NULL) {
            return pattern;
        } else {
            h = pwd->pw_dir;
        }
    }

    /* Copy the home directory */
    for (b = patbuf; b < eb && *h; *b++ = *h++);

    /* Append the rest of the pattern */
    while (b < eb && (*b++ = *p++) != EOS);
    *b = EOS;

    return patbuf;
}

#endif


/*
 * The main glob() routine: compiles the pattern (optionally processing
 * quotes), calls glob1() to do the real pattern matching, and finally
 * sorts the list (unless unsorted operation is requested).  Returns 0
 * if things went well, nonzero if errors occurred.  It is not an error
 * to find no matches.
 */
static int glob0(const Char * pattern, glob_t * pglob)
{
    const Char *qpatnext;
    int c, err, oldpathc;
    Char *bufnext, patbuf[MAXPATHLEN + 1];
    size_t limit = 0;

#ifdef USELESS_FOR_PUREFTPD
    qpatnext = globtilde(pattern, patbuf, MAXPATHLEN + 1, pglob);
#else
    qpatnext = pattern;
#endif
    oldpathc = pglob->gl_pathc;
    bufnext = patbuf;

    /* We don't need to check for buffer overflow any more. */
    while ((c = *qpatnext++) != EOS) {
        switch (c) {
        case LBRACKET:
            c = *qpatnext;
            if (c == NOT)
                ++qpatnext;
            if (*qpatnext == EOS ||
                g_strchr((const Char *) qpatnext + 1, RBRACKET) == NULL) {
                *bufnext++ = LBRACKET;
                if (c == NOT)
                    --qpatnext;
                break;
            }
            *bufnext++ = M_SET;
            if (c == NOT)
                *bufnext++ = M_NOT;
            c = *qpatnext++;
            do {
                *bufnext++ = CHAR(c);
                if (*qpatnext == RANGE && (c = qpatnext[1]) != RBRACKET) {
                    *bufnext++ = M_RNG;
                    *bufnext++ = CHAR(c);
                    qpatnext += 2;
                }
            } while ((c = *qpatnext++) != RBRACKET);
            pglob->gl_flags |= GLOB_MAGCHAR;
            *bufnext++ = M_END;
            break;
        case QUESTION:
            pglob->gl_flags |= GLOB_MAGCHAR;
            *bufnext++ = M_ONE;
            break;
        case STAR:
            pglob->gl_flags |= GLOB_MAGCHAR;
            /* collapse adjacent stars to one,
             * to avoid exponential behavior
             */
            if (bufnext == patbuf || bufnext[-1] != M_ALL) {
                *bufnext++ = M_ALL;
            }
            break;
        default:
            *bufnext++ = CHAR(c);
            break;
        }
    }
    *bufnext = EOS;

    if ((err = glob1(patbuf, patbuf + MAXPATHLEN, pglob, &limit)) != 0) {
        return (err);
    }

    /*
     * If there was no match we are going to append the pattern
     * if GLOB_NOCHECK was specified or if GLOB_NOMAGIC was specified
     * and the pattern did not contain any magic characters
     * GLOB_NOMAGIC is there just for compatibility with csh.
     */
    if (pglob->gl_pathc == oldpathc) {
        return (GLOB_NOMATCH);
    }
    if (!(pglob->gl_flags & GLOB_NOSORT)) {
        qsort(pglob->gl_pathv + oldpathc,
              (size_t) (pglob->gl_pathc - oldpathc), sizeof (char *), compare);
    }
    return 0;
}

static int compare(const void *p, const void *q)
{
    return (strcmp(*(const char **) p, *(const char **) q));
}

static int
glob1(Char * pattern, Char * pattern_last, glob_t * pglob, size_t * limitp)
{
    Char pathbuf[MAXPATHLEN + 1];

    /* A null pathname is invalid -- POSIX 1003.1 sect. 2.4. */
    if (*pattern == EOS) {
        return 0;
    }
    return (glob2(pathbuf, pathbuf + MAXPATHLEN,
                  pathbuf, pathbuf + MAXPATHLEN,
                  pattern, pattern_last, pglob, limitp, 0));
}

/*
 * The functions glob2 and glob3 are mutually recursive; there is one level
 * of recursion for each segment in the pattern that contains one or more
 * meta characters.
 */
static int
glob2(Char * pathbuf,
      Char * pathbuf_last,
      Char * pathend,
      Char * pathend_last,
      Char * pattern,
      Char * pattern_last, glob_t * pglob, size_t * limitp, int recursion)
{
    struct stat sb;
    Char *p, *q;
    int anymeta;

    if (pglob->gl_maxdepth > 0 && recursion > pglob->gl_maxdepth) {
        errno = 0;
        return (GLOB_NOSPACE);
    }
    /*
     * Loop over pattern segments until end of pattern or until
     * segment with meta character found.
     */
    for (anymeta = 0;;) {
        if (*pattern == EOS) {  /* End of pattern? */
            *pathend = EOS;
            if (g_lstat(pathbuf, &sb))
                return 0;

            ++pglob->gl_matchc;
            return (globextend(pathbuf, pglob, limitp));
        }

        /* Find end of next segment, copy tentatively to pathend. */
        q = pathend;
        p = pattern;
        while (*p != EOS && *p != SEP) {
            if (ismeta(*p))
                anymeta = 1;
            if (q + 1 > pathend_last)
                return 1;
            *q++ = *p++;
        }

        if (!anymeta) {         /* No expansion, do next segment. */
            pathend = q;
            pattern = p;
            while (*pattern == SEP) {
                if (pathend + 1 > pathend_last)
                    return 1;
                *pathend++ = *pattern++;
            }
        } else
            /* Need expansion, recurse. */
            return (glob3(pathbuf, pathbuf_last, pathend,
                          pathend_last, pattern,
                          p, pattern_last, pglob, limitp, recursion + 1));
    }
    /* NOTREACHED */
}

static int
glob3(Char * pathbuf, Char * pathbuf_last, Char * pathend,
      Char * pathend_last, Char * pattern, Char * restpattern,
      Char * restpattern_last, glob_t * pglob, size_t * limitp,
      int recursion)
{
    struct dirent *dp;
    DIR *dirp;
    int err;
    char buf[MAXPATHLEN + 1];

    if (pathend > pathend_last)
        return 1;

    *pathend = EOS;
    errno = 0;

    if (pglob->gl_maxdepth > 0 && recursion > pglob->gl_maxdepth) {
        return (GLOB_NOSPACE);
    }

    if ((dirp = g_opendir(pathbuf)) == NULL) {
        /* TODO: don't call for ENOENT or ENOTDIR? */
        if (pglob->gl_errfunc) {
            if (g_Ctoc(pathbuf, buf, sizeof (buf))) {
                return (GLOB_ABORTED);
            }
            if (pglob->gl_errfunc(buf, errno) ||
                pglob->gl_flags & GLOB_ERR) {
                return (GLOB_ABORTED);
            }
        }
        return 0;
    }

    err = 0;

    while ((dp = (*readdir) (dirp))) {
        unsigned char *sc;
        Char *dc;

        /* Initial DOT must be matched literally. */
        if (dp->d_name[0] == DOT && *pattern != DOT) {
            continue;
        }
#ifndef GLOB_UNPRINTABLE
        if (checkprintable(dp->d_name) != 0) {
            continue;
        }
#endif
        dc = pathend;
        sc = (unsigned char *) dp->d_name;
        while (dc < pathend_last && (*dc++ = *sc++) != EOS);
        if (dc >= pathend_last) {
            *dc = EOS;
            err = 1;
            break;
        }

        if (!match(pathend, pattern, restpattern)) {
            *pathend = EOS;
            continue;
        }
        err = glob2(pathbuf, pathbuf_last, --dc, pathend_last,
                    restpattern, restpattern_last, pglob, limitp,
                    recursion + 1);
        if (err)
            break;
    }

    closedir(dirp);
    return (err);
}


/*
 * Extend the gl_pathv member of a glob_t structure to accommodate a new item,
 * add the new item, and update gl_pathc.
 *
 * This assumes the BSD realloc, which only copies the block when its size
 * crosses a power-of-two boundary; for v7 realloc, this would cause quadratic
 * behavior.
 *
 * Return 0 if new item added, error code if memory couldn't be allocated.
 *
 * Invariant of the glob_t structure:
 *    Either gl_pathc is zero and gl_pathv is NULL; or gl_pathc > 0 and
 *    gl_pathv points to (gl_offs + gl_pathc + 1) items.
 */
static int globextend(const Char * path, glob_t * pglob, size_t * limitp)
{
    char **pathv;
    unsigned int newsize;
    size_t len;
    char *copy;
    const Char *p;

    if (pglob->gl_maxfiles != (unsigned long) -1) {
        if (pglob->gl_maxfiles == 0) {
            return 0;
        }
        pglob->gl_maxfiles--;
    }
    newsize = sizeof (*pathv) * (2 + pglob->gl_pathc);
    pathv = realloc((char *)pglob->gl_pathv, newsize);
    if (pathv == NULL) {
        free(pglob->gl_pathv);
        pglob->gl_pathv = NULL;
        return (GLOB_NOSPACE);
    }

    pglob->gl_pathv = pathv;

    for (p = path; *p++;);
    len = (size_t) (p - path);
    *limitp += len;
    if ((copy = malloc(len)) != NULL) {
        if (g_Ctoc(path, copy, len)) {
            free(copy);
            return (GLOB_NOSPACE);
        }
        pathv[pglob->gl_pathc++] = copy;
    }
    pathv[pglob->gl_pathc] = NULL;

#ifdef USELESS_FOR_PUREFTPD
    if ((pglob->gl_flags & GLOB_LIMIT) && newsize + *limitp >= ARG_MAX) {
        errno = 0;
        return (GLOB_NOSPACE);
    }
#endif

    return (copy == NULL ? GLOB_NOSPACE : 0);
}


/*
 * pattern matching function for filenames.  Each occurrence of the *
 * pattern causes a recursion level.
 */
static int
match(Char * name, Char * pat, Char * patend)
{
    int ok, negate_range;
    Char c, k;

    while (pat < patend) {
        c = *pat++;
        switch (c & M_MASK) {
        case M_ALL:
            if (pat == patend)
                return 1;
            do {
                if (match(name, pat, patend)) {
                    return 1;
                }
            } while (*name++ != EOS);
            return 0;
        case M_ONE:
            if (*name++ == EOS) {
                return 0;
            }
            break;
        case M_SET:
            ok = 0;
            if ((k = *name++) == EOS)
                return 0;
            if ((negate_range = ((*pat & M_MASK) == M_NOT)) != EOS) {
                ++pat;
            }
            while (((c = *pat++) & M_MASK) != M_END) {
                if ((*pat & M_MASK) == M_RNG) {
                    if (c <= k && k <= pat[1])
                        ok = 1;
                    pat += 2;
                } else if (c == k) {
                    ok = 1;
                }
            }
            if (ok == negate_range) {
                return 0;
            }
            break;
        default:
            if (*name++ != c) {
                return 0;
            }
            break;
        }
    }
    return (*name == EOS);
}

/* Free allocated data belonging to a glob_t structure. */
void globfree(glob_t * pglob)
{
    int i;
    char **pp;

    if (pglob->gl_pathv != NULL) {
        pp = pglob->gl_pathv;
        for (i = pglob->gl_pathc; i--; ++pp) {
            free(*pp);
        }
        free(pglob->gl_pathv);
        pglob->gl_pathv = NULL;
    }
}

static DIR *g_opendir(Char * str)
{
    char buf[MAXPATHLEN + 1];

    if (!*str) {
    buf[0] = '.';
    buf[1] = 0;         /* safe : sizeof buf > 2 */
    } else {
        if (g_Ctoc(str, buf, sizeof (buf)))
            return NULL;
    }
    return (opendir(buf));
}

static int g_lstat(Char * fn, struct stat *sb)
{
    char buf[MAXPATHLEN + 1];

    if (g_Ctoc(fn, buf, sizeof (buf)))
        return -1;
    return (lstat(buf, sb));
}

static const Char *g_strchr(const Char * str, int ch)
{
    do {
        if (*str == (Char) ch)
            return (str);
    } while (*str++);
    return NULL;
}

static int g_Ctoc(const Char * str, char *buf, unsigned int len)
{
    while (len--) {
        if ((*buf++ = (char) *str++) == EOS)
            return 0;
    }
    return 1;
}
#else
extern signed char v6ready;
#endif
