root/usr.bin/rdist/expand.c
/*      $OpenBSD: expand.c,v 1.18 2019/06/28 13:35:03 deraadt Exp $     */

/*
 * Copyright (c) 1983 Regents of the University of California.
 * All rights reserved.
 *
 * 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.
 */

#include <dirent.h>
#include <errno.h>
#include <fcntl.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#include "client.h"

#define MAXEARGS        2048
#define LC              '{'
#define RC              '}'

static char     shchars[] = "${[*?";

int             which;          /* bit mask of types to expand */
int             eargc;          /* expanded arg count */
char          **eargv;          /* expanded arg vectors */
char           *path;
char           *pathp;
char           *lastpathp;
char           *tilde;          /* "~user" if not expanding tilde, else "" */
char           *tpathp;
char            pathbuf[BUFSIZ];

int             expany;         /* any expansions done? */
char           *entp;
char          **sortbase;
char           *argvbuf[MAXEARGS];

#define sort()  qsort((char *)sortbase, &eargv[eargc] - sortbase, \
                      sizeof(*sortbase), argcmp), sortbase = &eargv[eargc]

static void Cat(u_char *, u_char *);
static void addpath(int);
static int argcmp(const void *, const void *);

static void
Cat(u_char *s1, u_char *s2)             /* quote in s1 and s2 */
{
        char *cp;
        int len = strlen((char *)s1) + strlen((char *)s2) + 2;

        if ((eargc + 1) >= MAXEARGS) {
                yyerror("Too many names");
                return;
        }

        eargv[++eargc] = NULL;
        eargv[eargc - 1] = cp = xmalloc(len);

        do { 
                if (*s1 == QUOTECHAR) 
                        s1++; 
        } while ((*cp++ = *s1++) != '\0');
        cp--;
        do { 
                if (*s2 == QUOTECHAR) 
                        s2++; 
        } while ((*cp++ = *s2++) != '\0');
}

static void
addpath(int c)
{
        if (pathp >= lastpathp) {
                yyerror("Pathname too long");
                return;
        } else {
                *pathp++ = c;
                *pathp = CNULL;
        }
}

/*
 * Take a list of names and expand any macros, etc.
 * wh = E_VARS if expanding variables.
 * wh = E_SHELL if expanding shell characters.
 * wh = E_TILDE if expanding `~'.
 * or any of these or'ed together.
 *
 * Major portions of this were snarfed from csh/sh.glob.c.
 */
struct namelist *
expand(struct namelist *list, int wh)           /* quote in list->n_name */
{
        struct namelist *nl, *prev;
        int n;

        if (debug)
                debugmsg(DM_CALL, "expand(%p, %d) start, list = %s", 
                         list, wh, getnlstr(list));

        if (wh == 0)
                fatalerr("expand() contains invalid 'wh' argument.");

        which = wh;
        path = tpathp = pathp = pathbuf;
        *pathp = CNULL;
        lastpathp = &pathbuf[sizeof pathbuf - 2];
        tilde = "";
        eargc = 0;
        eargv = sortbase = argvbuf;
        *eargv = NULL;

        /*
         * Walk the name list and expand names into eargv[];
         */
        for (nl = list; nl != NULL; nl = nl->n_next)
                expstr((u_char *)nl->n_name);
        /*
         * Take expanded list of names from eargv[] and build a new list.
         */
        list = prev = NULL;
        for (n = 0; n < eargc; n++) {
                nl = makenl(NULL);
                nl->n_name = eargv[n];
                if (prev == NULL)
                        list = prev = nl;
                else {
                        prev->n_next = nl;
                        prev = nl;
                }
        }

        return(list);
}

/*
 * xstrchr() is a version of strchr() that
 * handles u_char buffers.
 */
u_char *
xstrchr(u_char *str, int ch)
{
        u_char *cp;

        for (cp = str; cp && *cp != CNULL; ++cp)
                if (ch == *cp)
                        return(cp);

        return(NULL);
}

void
expstr(u_char *s)
{
        u_char *cp, *cp1;
        struct namelist *tp;
        u_char *tail;
        u_char ebuf[BUFSIZ];
        u_char varbuff[BUFSIZ];
        int savec, oeargc;

        if (s == NULL || *s == CNULL)
                return;

        /*
         * Remove quoted characters
         */
        if (IS_ON(which, E_VARS)) {
                if (strlen((char *)s) > sizeof(varbuff)) {
                        yyerror("Variable is too large.");
                        return;
                }
                for (cp = s, cp1 = varbuff; cp && *cp; ++cp) {
                        /* 
                         * remove quoted character if the next
                         * character is not $
                         */
                        if (*cp == QUOTECHAR && *(cp+1) != '$')
                                ++cp;
                        else
                                *cp1++ = *cp;
                }
                *cp1 = CNULL;
                s = varbuff;
        }

        /*
         * Consider string 's' a variable that should be expanded if
         * there is a '$' in 's' that is not quoted.
         */
        if (IS_ON(which, E_VARS) && 
            ((cp = xstrchr(s, '$')) && !(cp > s && *(cp-1) == QUOTECHAR))) {
                *cp++ = CNULL;
                if (*cp == CNULL) {
                        yyerror("no variable name after '$'");
                        return;
                }
                if (*cp == LC) {
                        cp++;
                        for (cp1 = cp; ; cp1 = tail + 1) {
                                if ((tail = xstrchr(cp1, RC)) == NULL) {
                                        yyerror("unmatched '{'");
                                        return;
                                }
                                if (tail[-1] != QUOTECHAR) break;
                        }
                        *tail++ = savec = CNULL;
                        if (*cp == CNULL) {
                                yyerror("no variable name after '$'");
                                return;
                        }
                } else {
                        tail = cp + 1;
                        savec = *tail;
                        *tail = CNULL;
                }
                tp = lookup((char *)cp, LOOKUP, NULL);
                if (savec != CNULL)
                        *tail = savec;
                if (tp != NULL) {
                        for (; tp != NULL; tp = tp->n_next) {
                                (void) snprintf((char *)ebuf, sizeof(ebuf),
                                                "%s%s%s", s, tp->n_name, tail);
                                expstr(ebuf);
                        }
                        return;
                }
                (void) snprintf((char *)ebuf, sizeof(ebuf), "%s%s", s, tail);
                expstr(ebuf);
                return;
        }
        if ((which & ~E_VARS) == 0 || !strcmp((char *)s, "{") || 
            !strcmp((char *)s, "{}")) {
                Cat(s, (u_char *)"");
                sort();
                return;
        }
        if (*s == '~') {
                if ((cp = strchr(s, '/')) == NULL) {
                        tilde = "~";
                        s++;
                } else {
                        tilde = memcpy(ebuf, s, (cp - s));
                        ebuf[cp - s] = '\0';
                        s = cp;
                }
                cp = exptilde(path, tilde, sizeof(pathbuf));
                tpathp = pathp = (char *)cp;
        } else {
                tpathp = pathp = path;
                tilde = "";
        }
        *pathp = CNULL;
        if (!(which & E_SHELL)) {
                if (which & E_TILDE)
                        Cat((u_char *)path, s);
                else
                        Cat((u_char *)tilde, s);
                sort();
                return;
        }
        oeargc = eargc;
        expany = 0;
        expsh(s);
        if (eargc == oeargc)
                Cat(s, (u_char *)"");           /* "nonomatch" is set */
        sort();
}

static int
argcmp(const void *v1, const void *v2)
{
        const char *const *a1 = v1, *const *a2 = v2;

        return (strcmp(*a1, *a2));
}

/*
 * If there are any Shell meta characters in the name,
 * expand into a list, after searching directory
 */
void
expsh(u_char *s)                        /* quote in s */
{
        u_char *cp, *oldcp;
        char *spathp;
        struct stat stb;

        spathp = pathp;
        cp = s;
        while (!any(*cp, shchars)) {
                if (*cp == CNULL) {
                        if (!expany || stat(path, &stb) >= 0) {
                                if (which & E_TILDE)
                                        Cat((u_char *)path, (u_char *)"");
                                else
                                        Cat((u_char *)tilde, (u_char *)tpathp);
                        }
                        goto endit;
                }
                if (*cp == QUOTECHAR) cp++;
                addpath(*cp++);
        }
        oldcp = cp;
        while (cp > s && *cp != '/')
                cp--, pathp--;
        if (*cp == '/')
                cp++, pathp++;
        *pathp = CNULL;
        if (*oldcp == '{') {
                (void) execbrc(cp, NULL);
                return;
        }
        matchdir((char *)cp);
endit:
        pathp = spathp;
        *pathp = CNULL;
}

void
matchdir(char *pattern)                 /* quote in pattern */
{
        struct stat stb;
        struct dirent *dp;
        DIR *dirp;

        dirp = opendir(path);
        if (dirp == NULL) {
                if (expany)
                        return;
                goto patherr2;
        }
        if (fstat(dirfd(dirp), &stb) == -1)
                goto patherr1;
        if (!S_ISDIR(stb.st_mode)) {
                errno = ENOTDIR;
                goto patherr1;
        }
        while ((dp = readdir(dirp)) != NULL)
                if (match(dp->d_name, pattern)) {
                        if (which & E_TILDE)
                                Cat((u_char *)path, (u_char *)dp->d_name);
                        else {
                                (void) strlcpy(pathp, dp->d_name,
                                    lastpathp - pathp + 2);
                                Cat((u_char *)tilde, (u_char *)tpathp);
                                *pathp = CNULL;
                        }
                }
        closedir(dirp);
        return;

patherr1:
        closedir(dirp);
patherr2:
        (void) strlcat(path, ": ", lastpathp - path + 2);
        (void) strlcat(path, SYSERR, lastpathp - path + 2);
        yyerror(path);
}

int
execbrc(u_char *p, u_char *s)           /* quote in p */
{
        u_char restbuf[BUFSIZ + 2];
        u_char *pe, *pm, *pl;
        int brclev = 0;
        u_char *lm, savec;
        char *spathp;

        for (lm = restbuf; *p != '{'; *lm++ = *p++)
                if (*p == QUOTECHAR) *lm++ = *p++;

        for (pe = ++p; *pe; pe++)
                switch (*pe) {

                case '{':
                        brclev++;
                        continue;

                case '}':
                        if (brclev == 0)
                                goto pend;
                        brclev--;
                        continue;

                case '[':
                        for (pe++; *pe && *pe != ']'; pe++)
                                if (*p == QUOTECHAR) pe++;
                        if (!*pe)
                                yyerror("Missing ']'");
                        continue;

                case QUOTECHAR:         /* skip this character */
                        pe++;
                        continue;
                }
pend:
        if (brclev || !*pe) {
                yyerror("Missing '}'");
                return (0);
        }
        for (pl = pm = p; pm <= pe; pm++)
                /* the strip code was a noop */
                switch (*pm) {

                case '{':
                        brclev++;
                        continue;

                case '}':
                        if (brclev) {
                                brclev--;
                                continue;
                        }
                        goto doit;

                case ',':
                        if (brclev)
                                continue;
doit:
                        savec = *pm;
                        *pm = 0;
                        *lm = 0;
                        (void) strlcat((char *)restbuf, (char *)pl,
                            sizeof(restbuf));
                        (void) strlcat((char *)restbuf, (char *)pe + 1,
                            sizeof(restbuf));
                        *pm = savec;
                        if (s == 0) {
                                spathp = pathp;
                                expsh(restbuf);
                                pathp = spathp;
                                *pathp = 0;
                        } else if (amatch((char *)s, restbuf))
                                return (1);
                        sort();
                        pl = pm + 1;
                        continue;

                case '[':
                        for (pm++; *pm && *pm != ']'; pm++)
                                if (*pm == QUOTECHAR) pm++;
                        if (!*pm)
                                yyerror("Missing ']'");
                        continue;

                case QUOTECHAR:                 /* skip one character */
                        pm++;
                        continue;
                }
        return (0);
}

int
match(char *s, char *p)                         /* quote in p */
{
        int c;
        char *sentp;
        char sexpany = expany;

        if (*s == '.' && *p != '.')
                return (0);
        sentp = entp;
        entp = s;
        c = amatch(s, p);
        entp = sentp;
        expany = sexpany;
        return (c);
}

int
amatch(char *s, u_char *p)                      /* quote in p */
{
        int scc;
        int ok, lc;
        char *spathp;
        struct stat stb;
        int c, cc;

        expany = 1;
        for (;;) {
                scc = *s++;
                switch (c = *p++) {

                case '{':
                        return (execbrc((u_char *)p - 1, (u_char *)s - 1));

                case '[':
                        ok = 0;
                        lc = 077777;
                        while ((cc = *p++) != '\0') {
                                if (cc == ']') {
                                        if (ok)
                                                break;
                                        return (0);
                                }
                                if (cc == QUOTECHAR) cc = *p++;
                                if (cc == '-') {
                                        if (lc <= scc && scc <= (int)*p++)
                                                ok++;
                                } else
                                        if (scc == (lc = cc))
                                                ok++;
                        }
                        if (cc == 0) {
                                yyerror("Missing ']'");
                                return (0);
                        }
                        continue;

                case '*':
                        if (!*p)
                                return (1);
                        if (*p == '/') {
                                p++;
                                goto slash;
                        }
                        for (s--; *s; s++)
                                if (amatch(s, p))
                                        return (1);
                        return (0);

                case CNULL:
                        return (scc == CNULL);

                default:
                        if (c != scc)
                                return (0);
                        continue;

                case '?':
                        if (scc == CNULL)
                                return (0);
                        continue;

                case '/':
                        if (scc)
                                return (0);
slash:
                        s = entp;
                        spathp = pathp;
                        while (*s)
                                addpath(*s++);
                        addpath('/');
                        if (stat(path, &stb) == 0 && S_ISDIR(stb.st_mode)) {
                                if (*p == CNULL) {
                                        if (which & E_TILDE) {
                                                Cat((u_char *)path, 
                                                    (u_char *)"");
                                        } else {
                                                Cat((u_char *)tilde, 
                                                    (u_char *)tpathp);
                                        }
                                } else
                                        expsh(p);
                        }
                        pathp = spathp;
                        *pathp = CNULL;
                        return (0);
                }
        }
}