/*
 * Copyright (c) 1980 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. All advertising materials mentioning features or use of this software
 *    must display the following acknowledgement:
 *	This product includes software developed by the University of
 *	California, Berkeley and its contributors.
 * 4. 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.
 */

/*
 * C-shell glob for random programs.
 */
#include "ftp_var.h"

#ifdef HAVE_SYS_STAT_H
#include <sys/stat.h>
#endif

#ifdef HAVE_DIRENT_H
# include <dirent.h>
# define NLENGTH(direct) (strlen((direct)->d_name))
#else /* not HAVE_DIRENT_H */
# define dirent direct
# define NLENGTH(direct) ((direct)->d_namlen)
# ifdef HAVE_SYS_NDIR_H
#  include <sys/ndir.h>
# endif /* HAVE_SYS_NDIR_H */
# ifdef HAVE_SYS_DIR_H
#  include <sys/dir.h>
# endif /* HAVE_SYS_DIR_H */
# ifdef HAVE_NDIR_H
#  include <ndir.h>
# endif /* HAVE_NDIR_H */
#endif /* HAVE_DIRENT_H */

#include <pwd.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define	QUOTE 0200
#define	TRIM 0177
#define	eq(a,b)		(strcmp(a, b)==0)

#ifndef NCARGS
#define NCARGS 256
#endif

#define	GAVSIZ		(NCARGS/6)
#define	isdir(d)	((d.st_mode & S_IFMT) == S_IFDIR)

static	char **gargv;		/* Pointer to the (stack) arglist */
static	int gargc;		/* Number args in gargv */
static	int gnleft;
static	short gflag;

static	char *gpath, *gpathp, *lastgpathp;
static	int globbed, globcnt;
static	char **sortbas;
static	char *entp;

char	*globchars = "`{[*?";
char	*globerr;
char	*home;

extern	int errno;

static int match P((char *, char *));
static int execbrc P((char *, char *));

int any(register int c, register char *s) {
    while (*s) if (*s++ == c) return(1);
    return(0);
}

static int tglob(register char c) {
    if (any(c, globchars)) gflag |= c == '{' ? 2 : 1;
    return (c);
}


int letter(register char c) {
    return ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == '_');
}

int digit(register char c) {
    return (c >= '0' && c <= '9');
}

int blklen(register char **av) {
    register int i = 0;
    
    while (*av++) i++;
    return (i);
}

char ** blkcpy(char **oav, register char **bv) {
    register char **av = oav;
    
    while ((*av++ = *bv++));
    return (oav);
}

void blkfree(char **av0) {
    register char **av = av0;
    
    /* if gflag is 0, we did not malloc() the space! */
    if (!av || !gflag) return;

    while (*av)	free(*av++);
}

static void ginit(char **agargv) {
    agargv[0] = 0; gargv = agargv; sortbas = agargv; gargc = 0;
    gnleft = NCARGS - 4;
}

static void sort() {
    register char **p1, **p2, *c;
    char **Gvp = &gargv[gargc];
    
    p1 = sortbas;
    while (p1 < Gvp-1) {
	p2 = p1;
	while (++p2 < Gvp)
	    if (strcmp(*p1, *p2) > 0)
		c = *p1, *p1 = *p2, *p2 = c;
	p1++;
    }
    sortbas = Gvp;
}

static void addpath(char c) {
    if (gpathp >= lastgpathp)
	globerr = "Pathname too long";
    else {
	*gpathp++ = c;
	*gpathp = 0;
    }
}

static char *strspl(register char *cp, register char *dp) {
    register char *ep = (char *)malloc((unsigned)(strlen(cp) + strlen(dp) + 1));

    if (ep == NULL) fatal("Out of memory");
    strcpy(ep, cp);
    strcat(ep, dp);
    return (ep);
}

static char *strend(register char *cp) {
    while (*cp) cp++;
    return (cp);
}

static void Gcat(register char *s1, register char *s2) {
    register int len = strlen(s1) + strlen(s2) + 1;
    
    if (len >= gnleft || gargc >= GAVSIZ - 1)
	globerr = "Arguments too long";
    else {
	gargc++;
	gnleft -= len;
	gargv[gargc] = 0;
	gargv[gargc - 1] = strspl(s1, s2);
    }
}

/*
 * Extract a home directory from the password file
 * The argument points to a buffer where the name of the
 * user whose home directory is sought is currently.
 * We write the home directory of the user back there.
 */
int gethdir(char *home) {
    register struct passwd *pp = getpwnam(home);
    
    if (!pp || home + strlen(pp->pw_dir) >= lastgpathp) return (1);
    strcpy(home, pp->pw_dir);
    return (0);
}

static void matchdir(char *pattern) {
    register struct dirent *dp;
    DIR *dirp;
    
    /* 
     * opendir() does not accept a null directory string
     * to mean the current directory.  Must use ".". But leave
     *  the gpath prefix alone
     */  
    if (*gpath == 0)
	dirp = opendir(".");
    else
	dirp = opendir(gpath);
    if (dirp == NULL) {
	if (globbed) return;
	goto patherr2;
    }
#ifndef linux
    {
	struct stat stb;

	if (fstat(dirp->dd_fd, &stb) < 0)
	    goto patherr1;
	if (!isdir(stb)) {
	    errno = ENOTDIR;
	    goto patherr1;
	}
    }
#endif /* linux */
    while ((dp = readdir(dirp)) != NULL) {
	if (dp->d_ino == 0)
	    continue;
	if (match(dp->d_name, pattern)) {
	    Gcat(gpath, dp->d_name);
	    globcnt++;
	}
    }

    closedir(dirp);
    return;

#ifndef linux
  patherr1:
#endif
    closedir(dirp);
  patherr2:
    globerr = "Bad directory components";
}

static void expand(char *as) {
    register char *cs;
    register char *sgpathp, *oldcs;
    struct stat stb;
    
    sgpathp = gpathp;
    cs = as;
    if (*cs == '~' && gpathp == gpath) {
	addpath('~');
	for (cs++; letter(*cs) || digit(*cs) || *cs == '-';)
	    addpath(*cs++);
	if (!*cs || *cs == '/') {
	    if (gpathp != gpath + 1) {
		*gpathp = 0;
		if (gethdir(gpath + 1))
		    globerr = "Unknown user name after ~";
		strcpy(gpath, gpath + 1);
	    } else
		strcpy(gpath, home);
	    gpathp = strend(gpath);
	}
    }
    while (!any(*cs, globchars)) {
	if (*cs == 0) {
	    if (!globbed)
		Gcat(gpath, "");
	    else if (stat(gpath, &stb) >= 0) {
		Gcat(gpath, "");
		globcnt++;
	    }
	    goto endit;
	}
	addpath(*cs++);
    }
    oldcs = cs;
    while (cs > as && *cs != '/')
	cs--, gpathp--;
    if (*cs == '/')
	cs++, gpathp++;
    *gpathp = 0;
    if (*oldcs == '{') {
	execbrc(cs, ((char *)0));
	return;
    }
    matchdir(cs);
  endit:
    gpathp = sgpathp;
    *gpathp = 0;
}

static int amatch(register char *s, register char *p) {
	register int scc;
	int ok, lc;
	char *sgpathp;
	struct stat stb;
	int c, cc;

	globbed = 1;
	for (;;) {
		scc = *s++ & TRIM;
		switch (c = *p++) {
		    case '{':
			return (execbrc(p - 1, s - 1));
			
		    case '[':
			ok = 0;
			lc = 077777;
			while ((cc = *p++)) {
			    if (cc == ']') {
				if (ok) break;
				return (0);
			    }
			    if (cc == '-') {
				if (lc <= scc && scc <= *p++) ok++;
			    } else 
			    if (scc == (lc = cc)) ok++;
			}
			
			if (cc == 0) {
			    if (!ok) { return 0; }
			    else { p--; }
			}
			continue;
		    case '*':
			if (!*p) return (1);
			
			if (*p == '/') {
			    p++;
			    goto slash;
			}
			
			s--;
			
			do {
			    if (amatch(s, p)) return (1);
			} while (*s++);
			return (0);
			
		    case 0:
			return (scc == 0);
			
		    default:
			if (c != scc) return (0);
			continue;
			
		    case '?':
			if (scc == 0) return (0);
			continue;
			
		    case '/':
			if (scc) return (0);

		  slash:
			s = entp;
			sgpathp = gpathp;
			while (*s) addpath(*s++);
			addpath('/');
			if (stat(gpath, &stb) == 0 && isdir(stb)) {
			    if (*p == 0) {
				Gcat(gpath, "");
				globcnt++;
			    } 
			    else { expand(p); }
			}
			gpathp = sgpathp;
			*gpathp = 0;
			return (0);
		}
	}
}

static int match(char *s, char *p) {
	register int c;
	register char *sentp;
	char sglobbed = globbed;

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

static int execbrc(char *p, char *s) {
    char restbuf[BUFSIZ + 2];
    register char *pe, *pm, *pl;
    int brclev = 0;
    char *lm, savec, *sgpathp;
    
    for (lm = restbuf; *p != '{'; *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++);
		continue;
	}
pend:
    brclev = 0;

    for (pl = pm = p; pm <= pe; pm++)
	switch (*pm & (QUOTE|TRIM)) {
	    case '{':
		brclev++;
		continue;
	    case '}':
		if (brclev) {
		    brclev--;
		    continue;
		}
		goto doit;
	    case ','|QUOTE:
	    case ',':
		if (brclev) continue;
doit:
		savec = *pm;
		*pm = 0;
		strcpy(lm, pl);
		strcat(restbuf, pe + 1);
		*pm = savec;

		if (s == 0) {
		    sgpathp = gpathp;
		    expand(restbuf);
		    gpathp = sgpathp;
		    *gpathp = 0;
		} else if (amatch(s, restbuf)) {
		    return (1);
		}

		sort();
		pl = pm + 1;
		if (brclev) return (0);
		continue;
	    case '[':
		for (pm++; *pm && *pm != ']'; pm++);
		if (!*pm) pm--;
		continue;
	}

    if (brclev) goto doit;
    return (0);
}

static void acollect(register char *as) {
    register int ogargc = gargc;
    
    gpathp = gpath; *gpathp = 0; globbed = 0;
    expand(as);

    if (gargc != ogargc) sort();
}

static void collect(register char *as) {
    if (eq(as, "{") || eq(as, "{}")) {
	Gcat(as, "");
	sort();
    } else
	acollect(as);
}

static int Gmatch(register char *s, register char *p) {
    register int scc;
    int ok, lc;
    int c, cc;
    
    for (;;) {
	scc = *s++ & TRIM;
	switch (c = *p++) {
	    case '[':
		ok = 0;
		lc = 077777;

		while ((cc = *p++)) {
		    if (cc == ']') {
			if (ok) break;
			return (0);
		    }
		    if (cc == '-') {
			if (lc <= scc && scc <= *p++) ok++;
		    } else
			if (scc == (lc = cc)) ok++;
		}

		if (cc == 0) {
		    if (!ok) { return 0; }
		    else { p--; }
		}
		continue;
	    case '*':
		if (!*p) return (1);

		for (s--; *s; s++)
		    if (Gmatch(s, p))
			return (1);

		return (0);

	    case 0:
		return (scc == 0);
		
	    default:
		if ((c & TRIM) != scc) return (0);

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

static void rscan(register char **t, int (*f)P((char))) {
    register char *p, c;
    
    while ((p = *t++)) {
	if (f == tglob) {
	    if (*p == '~') { gflag |= 2; }
	    else { if (eq(p, "{") || eq(p, "{}"))
		continue;
	    }
	}
	while ((c = *p++)) (*f)(c);
    }
}

char **copyblk(register char **v) {
    register char **nv = (char **)malloc((unsigned)((blklen(v) + 1) * sizeof(char **)));

    if (nv == (char **)0) fatal("Out of memory");
    return (blkcpy(nv, v));
}

char **ftpglob(register char *v) {
	char agpath[BUFSIZ];
	char *agargv[GAVSIZ];
	char *vv[2];

	vv[0] = v;
	vv[1] = 0;

	gflag = 0;
	rscan(vv, tglob);

	if (gflag == 0)
		return (copyblk(vv));

	globerr = 0;
	gpath = agpath; gpathp = gpath; *gpathp = 0;
	lastgpathp = &gpath[sizeof agpath - 2];
	ginit(agargv); globcnt = 0;
	collect(v);

	if (globcnt == 0 && (gflag&1)) {
	    blkfree(gargv), gargv = 0;
	    return (0);
	} else {
	    return (gargv = copyblk(gargv));
	}
}

