Home | History | Annotate | Line # | Download | only in ftpd
      1 /*	$NetBSD: cmds.c,v 1.35 2016/01/17 14:46:07 christos Exp $	*/
      2 
      3 /*
      4  * Copyright (c) 1999-2009 The NetBSD Foundation, Inc.
      5  * All rights reserved.
      6  *
      7  * This code is derived from software contributed to The NetBSD Foundation
      8  * by Luke Mewburn.
      9  *
     10  * Redistribution and use in source and binary forms, with or without
     11  * modification, are permitted provided that the following conditions
     12  * are met:
     13  * 1. Redistributions of source code must retain the above copyright
     14  *    notice, this list of conditions and the following disclaimer.
     15  * 2. Redistributions in binary form must reproduce the above copyright
     16  *    notice, this list of conditions and the following disclaimer in the
     17  *    documentation and/or other materials provided with the distribution.
     18  *
     19  * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
     20  * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
     21  * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
     22  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
     23  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
     24  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
     25  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
     26  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
     27  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
     28  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
     29  * POSSIBILITY OF SUCH DAMAGE.
     30  */
     31 
     32 /*
     33  * Copyright (c) 1985, 1988, 1990, 1992, 1993, 1994
     34  *	The Regents of the University of California.  All rights reserved.
     35  *
     36  * Redistribution and use in source and binary forms, with or without
     37  * modification, are permitted provided that the following conditions
     38  * are met:
     39  * 1. Redistributions of source code must retain the above copyright
     40  *    notice, this list of conditions and the following disclaimer.
     41  * 2. Redistributions in binary form must reproduce the above copyright
     42  *    notice, this list of conditions and the following disclaimer in the
     43  *    documentation and/or other materials provided with the distribution.
     44  * 3. Neither the name of the University nor the names of its contributors
     45  *    may be used to endorse or promote products derived from this software
     46  *    without specific prior written permission.
     47  *
     48  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
     49  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
     50  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
     51  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
     52  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
     53  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
     54  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
     55  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
     56  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
     57  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
     58  * SUCH DAMAGE.
     59  */
     60 
     61 /*
     62  * Copyright (C) 1997 and 1998 WIDE Project.
     63  * All rights reserved.
     64  *
     65  * Redistribution and use in source and binary forms, with or without
     66  * modification, are permitted provided that the following conditions
     67  * are met:
     68  * 1. Redistributions of source code must retain the above copyright
     69  *    notice, this list of conditions and the following disclaimer.
     70  * 2. Redistributions in binary form must reproduce the above copyright
     71  *    notice, this list of conditions and the following disclaimer in the
     72  *    documentation and/or other materials provided with the distribution.
     73  * 3. Neither the name of the project nor the names of its contributors
     74  *    may be used to endorse or promote products derived from this software
     75  *    without specific prior written permission.
     76  *
     77  * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``AS IS'' AND
     78  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
     79  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
     80  * ARE DISCLAIMED.  IN NO EVENT SHALL THE PROJECT OR CONTRIBUTORS BE LIABLE
     81  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
     82  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
     83  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
     84  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
     85  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
     86  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
     87  * SUCH DAMAGE.
     88  */
     89 
     90 
     91 #include <sys/cdefs.h>
     92 #ifndef lint
     93 __RCSID("$NetBSD: cmds.c,v 1.35 2016/01/17 14:46:07 christos Exp $");
     94 #endif /* not lint */
     95 
     96 #include <sys/param.h>
     97 #include <sys/stat.h>
     98 
     99 #include <arpa/ftp.h>
    100 
    101 #include <dirent.h>
    102 #include <errno.h>
    103 #include <stdio.h>
    104 #include <stdlib.h>
    105 #include <string.h>
    106 #include <tzfile.h>
    107 #include <unistd.h>
    108 #include <ctype.h>
    109 
    110 #ifdef KERBEROS5
    111 #include <krb5/krb5.h>
    112 #endif
    113 
    114 #include "extern.h"
    115 
    116 typedef enum {
    117 	FE_MLSD		= 1<<0,		/* if op is MLSD (MLST otherwise ) */
    118 	FE_ISCURDIR	= 1<<1,		/* if name is the current directory */
    119 } factflag_t;
    120 
    121 typedef struct {
    122 	const char	*path;		/* full pathname */
    123 	const char	*display;	/* name to display */
    124 	struct stat	*stat;		/* stat of path */
    125 	struct stat	*pdirstat;	/* stat of path's parent dir */
    126 	factflag_t	 flags;		/* flags */
    127 } factelem;
    128 
    129 static void	ack(const char *);
    130 static void	base64_encode(const char *, size_t, char *, int);
    131 static void	fact_type(const char *, FILE *, factelem *);
    132 static void	fact_size(const char *, FILE *, factelem *);
    133 static void	fact_modify(const char *, FILE *, factelem *);
    134 static void	fact_perm(const char *, FILE *, factelem *);
    135 static void	fact_unique(const char *, FILE *, factelem *);
    136 static int	matchgroup(gid_t);
    137 static void	mlsname(FILE *, factelem *);
    138 static void	replydirname(const char *, const char *);
    139 
    140 struct ftpfact {
    141 	const char	 *name;		/* name of fact */
    142 	int		  enabled;	/* if fact is enabled */
    143 	void		(*display)(const char *, FILE *, factelem *);
    144 					/* function to display fact */
    145 };
    146 
    147 struct ftpfact facttab[] = {
    148 	{ "Type",	1, fact_type },
    149 #define	FACT_TYPE 0
    150 	{ "Size",	1, fact_size },
    151 	{ "Modify",	1, fact_modify },
    152 	{ "Perm",	1, fact_perm },
    153 	{ "Unique",	1, fact_unique },
    154 	/* "Create" */
    155 	/* "Lang" */
    156 	/* "Media-Type" */
    157 	/* "CharSet" */
    158 };
    159 
    160 #define FACTTABSIZE	(sizeof(facttab) / sizeof(struct ftpfact))
    161 
    162 static char cached_path[MAXPATHLEN + 1] = "/";
    163 static void discover_path(char *, const char *);
    164 
    165 void
    166 cwd(const char *path)
    167 {
    168 
    169 	if (chdir(path) < 0)
    170 		perror_reply(550, path);
    171 	else {
    172 		show_chdir_messages(250);
    173 		ack("CWD");
    174 		if (getcwd(cached_path, MAXPATHLEN) == NULL) {
    175 			discover_path(cached_path, path);
    176 		}
    177 	}
    178 }
    179 
    180 void
    181 delete(const char *name)
    182 {
    183 	char *p = NULL;
    184 
    185 	if (remove(name) < 0) {
    186 		p = strerror(errno);
    187 		perror_reply(550, name);
    188 	} else
    189 		ack("DELE");
    190 	logxfer("delete", -1, name, NULL, NULL, p);
    191 }
    192 
    193 void
    194 feat(void)
    195 {
    196 	size_t i;
    197 
    198 	reply(-211, "Features supported");
    199 	cprintf(stdout, " MDTM\r\n");
    200 	cprintf(stdout, " MLST ");
    201 	for (i = 0; i < FACTTABSIZE; i++)
    202 		cprintf(stdout, "%s%s;", facttab[i].name,
    203 		    facttab[i].enabled ? "*" : "");
    204 	cprintf(stdout, "\r\n");
    205 	cprintf(stdout, " REST STREAM\r\n");
    206 	cprintf(stdout, " SIZE\r\n");
    207 	cprintf(stdout, " TVFS\r\n");
    208 	reply(211,  "End");
    209 }
    210 
    211 void
    212 makedir(const char *name)
    213 {
    214 	char *p = NULL;
    215 
    216 	if (mkdir(name, 0777) < 0) {
    217 		p = strerror(errno);
    218 		perror_reply(550, name);
    219 	} else
    220 		replydirname(name, "directory created.");
    221 	logxfer("mkdir", -1, name, NULL, NULL, p);
    222 }
    223 
    224 void
    225 mlsd(const char *path)
    226 {
    227 	struct dirent	*dp;
    228 	struct stat	 sb, pdirstat;
    229 	factelem f;
    230 	FILE	*dout;
    231 	DIR	*dirp;
    232 	char	name[MAXPATHLEN];
    233 	int	hastypefact;
    234 
    235 	hastypefact = facttab[FACT_TYPE].enabled;
    236 	if (path == NULL)
    237 		path = ".";
    238 	if (stat(path, &pdirstat) == -1) {
    239  mlsdperror:
    240 		perror_reply(550, path);
    241 		return;
    242 	}
    243 	if (! S_ISDIR(pdirstat.st_mode)) {
    244 		errno = ENOTDIR;
    245 		perror_reply(501, path);
    246 		return;
    247 	}
    248 	if ((dirp = opendir(path)) == NULL)
    249 		goto mlsdperror;
    250 
    251 	dout = dataconn("MLSD", (off_t)-1, "w");
    252 	if (dout == NULL) {
    253 		(void) closedir(dirp);
    254 		return;
    255 	}
    256 
    257 	memset(&f, 0, sizeof(f));
    258 	f.stat = &sb;
    259 	f.flags |= FE_MLSD;
    260 	while ((dp = readdir(dirp)) != NULL) {
    261 		snprintf(name, sizeof(name), "%s/%s", path, dp->d_name);
    262 		if (ISDOTDIR(dp->d_name)) {	/* special case curdir: */
    263 			if (! hastypefact)
    264 				continue;
    265 			f.pdirstat = NULL;	/*   require stat of parent */
    266 			f.display = path;	/*   set name to real name */
    267 			f.flags |= FE_ISCURDIR; /*   flag name is curdir */
    268 		} else {
    269 			if (ISDOTDOTDIR(dp->d_name)) {
    270 				if (! hastypefact)
    271 					continue;
    272 				f.pdirstat = NULL;
    273 			} else
    274 				f.pdirstat = &pdirstat;	/* cache parent stat */
    275 			f.display = dp->d_name;
    276 			f.flags &= ~FE_ISCURDIR;
    277 		}
    278 		if (stat(name, &sb) == -1)
    279 			continue;
    280 		f.path = name;
    281 		mlsname(dout, &f);
    282 	}
    283 	(void)closedir(dirp);
    284 
    285 	if (ferror(dout) != 0)
    286 		perror_reply(550, "Data connection");
    287 	else
    288 		reply(226, "MLSD complete.");
    289 	closedataconn(dout);
    290 	total_xfers_out++;
    291 	total_xfers++;
    292 }
    293 
    294 void
    295 mlst(const char *path)
    296 {
    297 	struct stat sb;
    298 	factelem f;
    299 
    300 	if (path == NULL)
    301 		path = ".";
    302 	if (stat(path, &sb) == -1) {
    303 		perror_reply(550, path);
    304 		return;
    305 	}
    306 	reply(-250, "MLST %s", path);
    307 	memset(&f, 0, sizeof(f));
    308 	f.path = path;
    309 	f.display = path;
    310 	f.stat = &sb;
    311 	f.pdirstat = NULL;
    312 	CPUTC(' ', stdout);
    313 	mlsname(stdout, &f);
    314 	reply(250, "End");
    315 }
    316 
    317 
    318 void
    319 opts(const char *command)
    320 {
    321 	struct tab *c;
    322 	char *ep;
    323 
    324 	if ((ep = strchr(command, ' ')) != NULL)
    325 		*ep++ = '\0';
    326 	c = lookup(cmdtab, command);
    327 	if (c == NULL) {
    328 		reply(502, "Unknown command '%s'.", command);
    329 		return;
    330 	}
    331 	if (! CMD_IMPLEMENTED(c)) {
    332 		reply(502, "%s command not implemented.", c->name);
    333 		return;
    334 	}
    335 	if (! CMD_HAS_OPTIONS(c)) {
    336 		reply(501, "%s command does not support persistent options.",
    337 		    c->name);
    338 		return;
    339 	}
    340 
    341 			/* special case: MLST */
    342 	if (strcasecmp(command, "MLST") == 0) {
    343 		int	 enabled[FACTTABSIZE];
    344 		size_t	 i, onedone;
    345 		size_t	 len;
    346 		char	*p;
    347 
    348 		for (i = 0; i < sizeof(enabled) / sizeof(int); i++)
    349 			enabled[i] = 0;
    350 		if (ep == NULL || *ep == '\0')
    351 			goto displaymlstopts;
    352 
    353 				/* don't like spaces, and need trailing ; */
    354 		len = strlen(ep);
    355 		if (strchr(ep, ' ') != NULL || ep[len - 1] != ';') {
    356  badmlstopt:
    357 			reply(501, "Invalid MLST options");
    358 			return;
    359 		}
    360 		ep[len - 1] = '\0';
    361 		while ((p = strsep(&ep, ";")) != NULL) {
    362 			if (*p == '\0')
    363 				goto badmlstopt;
    364 			for (i = 0; i < FACTTABSIZE; i++)
    365 				if (strcasecmp(p, facttab[i].name) == 0) {
    366 					enabled[i] = 1;
    367 					break;
    368 				}
    369 		}
    370 
    371  displaymlstopts:
    372 		for (i = 0; i < FACTTABSIZE; i++)
    373 			facttab[i].enabled = enabled[i];
    374 		cprintf(stdout, "200 MLST OPTS");
    375 		for (i = onedone = 0; i < FACTTABSIZE; i++) {
    376 			if (facttab[i].enabled) {
    377 				cprintf(stdout, "%s%s;", onedone ? "" : " ",
    378 				    facttab[i].name);
    379 				onedone++;
    380 			}
    381 		}
    382 		cprintf(stdout, "\r\n");
    383 		fflush(stdout);
    384 		return;
    385 	}
    386 
    387 			/* default cases */
    388 	if (ep != NULL && *ep != '\0')
    389 		REASSIGN(c->options, ftpd_strdup(ep));
    390 	if (c->options != NULL)
    391 		reply(200, "Options for %s are '%s'.", c->name,
    392 		    c->options);
    393 	else
    394 		reply(200, "No options defined for %s.", c->name);
    395 }
    396 
    397 void
    398 pwd(void)
    399 {
    400 	char path[MAXPATHLEN];
    401 
    402 	if (getcwd(path, sizeof(path) - 1) == NULL) {
    403 		if (chdir(cached_path) < 0) {
    404 			reply(550, "Can't get the current directory: %s.",
    405 			    strerror(errno));
    406 			return;
    407 		}
    408 		(void)strlcpy(path, cached_path, MAXPATHLEN);
    409 	}
    410 	replydirname(path, "is the current directory.");
    411 }
    412 
    413 void
    414 removedir(const char *name)
    415 {
    416 	char *p = NULL;
    417 
    418 	if (rmdir(name) < 0) {
    419 		p = strerror(errno);
    420 		perror_reply(550, name);
    421 	} else
    422 		ack("RMD");
    423 	logxfer("rmdir", -1, name, NULL, NULL, p);
    424 }
    425 
    426 char *
    427 renamefrom(const char *name)
    428 {
    429 	struct stat st;
    430 
    431 	if (stat(name, &st) < 0) {
    432 		perror_reply(550, name);
    433 		return (NULL);
    434 	}
    435 	reply(350, "File exists, ready for destination name");
    436 	return (ftpd_strdup(name));
    437 }
    438 
    439 void
    440 renamecmd(const char *from, const char *to)
    441 {
    442 	char *p = NULL;
    443 
    444 	if (rename(from, to) < 0) {
    445 		p = strerror(errno);
    446 		perror_reply(550, "rename");
    447 	} else
    448 		ack("RNTO");
    449 	logxfer("rename", -1, from, to, NULL, p);
    450 }
    451 
    452 void
    453 sizecmd(const char *filename)
    454 {
    455 	switch (type) {
    456 	case TYPE_L:
    457 	case TYPE_I:
    458 	    {
    459 		struct stat stbuf;
    460 		if (stat(filename, &stbuf) < 0 || !S_ISREG(stbuf.st_mode))
    461 			reply(550, "%s: not a plain file.", filename);
    462 		else
    463 			reply(213, ULLF, (ULLT)stbuf.st_size);
    464 		break;
    465 	    }
    466 	case TYPE_A:
    467 	    {
    468 		FILE *fin;
    469 		int c;
    470 		off_t count;
    471 		struct stat stbuf;
    472 		fin = fopen(filename, "r");
    473 		if (fin == NULL) {
    474 			perror_reply(550, filename);
    475 			return;
    476 		}
    477 		if (fstat(fileno(fin), &stbuf) < 0 || !S_ISREG(stbuf.st_mode)) {
    478 			reply(550, "%s: not a plain file.", filename);
    479 			(void) fclose(fin);
    480 			return;
    481 		}
    482 		if (stbuf.st_size > 10240) {
    483 			reply(550, "%s: file too large for SIZE.", filename);
    484 			(void) fclose(fin);
    485 			return;
    486 		}
    487 
    488 		count = 0;
    489 		while((c = getc(fin)) != EOF) {
    490 			if (c == '\n')	/* will get expanded to \r\n */
    491 				count++;
    492 			count++;
    493 		}
    494 		(void) fclose(fin);
    495 
    496 		reply(213, LLF, (LLT)count);
    497 		break;
    498 	    }
    499 	default:
    500 		reply(504, "SIZE not implemented for Type %c.", "?AEIL"[type]);
    501 	}
    502 }
    503 
    504 void
    505 statfilecmd(const char *filename)
    506 {
    507 	FILE *fin;
    508 	int c;
    509 	int atstart;
    510 	const char *argv[] = { INTERNAL_LS, "-lgA", "", NULL };
    511 
    512 	argv[2] = filename;
    513 	fin = ftpd_popen(argv, "r", STDOUT_FILENO);
    514 	reply(-211, "status of %s:", filename);
    515 /* XXX: use fgetln() or fparseln() here? */
    516 	atstart = 1;
    517 	while ((c = getc(fin)) != EOF) {
    518 		if (c == '\n') {
    519 			if (ferror(stdout)){
    520 				perror_reply(421, "control connection");
    521 				(void) ftpd_pclose(fin);
    522 				dologout(1);
    523 				/* NOTREACHED */
    524 			}
    525 			if (ferror(fin)) {
    526 				perror_reply(551, filename);
    527 				(void) ftpd_pclose(fin);
    528 				return;
    529 			}
    530 			CPUTC('\r', stdout);
    531 		}
    532 		if (atstart && isdigit(c))
    533 			CPUTC(' ', stdout);
    534 		CPUTC(c, stdout);
    535 		atstart = (c == '\n');
    536 	}
    537 	(void) ftpd_pclose(fin);
    538 	reply(211, "End of Status");
    539 }
    540 
    541 /* -- */
    542 
    543 static void
    544 ack(const char *s)
    545 {
    546 
    547 	reply(250, "%s command successful.", s);
    548 }
    549 
    550 /*
    551  * Encode len bytes starting at clear using base64 encoding into encoded,
    552  * which should be at least ((len + 2) * 4 / 3 + 1) in size.
    553  * If nulterm is non-zero, terminate with \0 otherwise pad to 3 byte boundary
    554  * with `='.
    555  */
    556 static void
    557 base64_encode(const char *clear, size_t len, char *encoded, int nulterm)
    558 {
    559 	static const char base64[] =
    560 	    "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
    561 	const char *c;
    562 	char	*e, termchar;
    563 	int	 i;
    564 
    565 			/* determine whether to pad with '=' or NUL terminate */
    566 	termchar = nulterm ? '\0' : '=';
    567 	c = clear;
    568 	e = encoded;
    569 			/* convert all but last 2 bytes */
    570 	for (i = len; i > 2; i -= 3, c += 3) {
    571 		*e++ = base64[(c[0] >> 2) & 0x3f];
    572 		*e++ = base64[((c[0] << 4) & 0x30) | ((c[1] >> 4) & 0x0f)];
    573 		*e++ = base64[((c[1] << 2) & 0x3c) | ((c[2] >> 6) & 0x03)];
    574 		*e++ = base64[(c[2]) & 0x3f];
    575 	}
    576 			/* handle slop at end */
    577 	if (i > 0) {
    578 		*e++ = base64[(c[0] >> 2) & 0x3f];
    579 		*e++ = base64[((c[0] << 4) & 0x30) |
    580 		     (i > 1 ? ((c[1] >> 4) & 0x0f) : 0)];
    581 		*e++ = (i > 1) ? base64[(c[1] << 2) & 0x3c] : termchar;
    582 		*e++ = termchar;
    583 	}
    584 	*e = '\0';
    585 }
    586 
    587 static void
    588 fact_modify(const char *fact, FILE *fd, factelem *fe)
    589 {
    590 	struct tm *t;
    591 
    592 	t = gmtime(&(fe->stat->st_mtime));
    593 	cprintf(fd, "%s=%04d%02d%02d%02d%02d%02d;", fact,
    594 	    TM_YEAR_BASE + t->tm_year,
    595 	    t->tm_mon+1, t->tm_mday,
    596 	    t->tm_hour, t->tm_min, t->tm_sec);
    597 }
    598 
    599 static void
    600 fact_perm(const char *fact, FILE *fd, factelem *fe)
    601 {
    602 	int		rok, wok, xok, pdirwok;
    603 	struct stat	*pdir, dir;
    604 
    605 	if (fe->stat->st_uid == geteuid()) {
    606 		rok = ((fe->stat->st_mode & S_IRUSR) != 0);
    607 		wok = ((fe->stat->st_mode & S_IWUSR) != 0);
    608 		xok = ((fe->stat->st_mode & S_IXUSR) != 0);
    609 	} else if (matchgroup(fe->stat->st_gid)) {
    610 		rok = ((fe->stat->st_mode & S_IRGRP) != 0);
    611 		wok = ((fe->stat->st_mode & S_IWGRP) != 0);
    612 		xok = ((fe->stat->st_mode & S_IXGRP) != 0);
    613 	} else {
    614 		rok = ((fe->stat->st_mode & S_IROTH) != 0);
    615 		wok = ((fe->stat->st_mode & S_IWOTH) != 0);
    616 		xok = ((fe->stat->st_mode & S_IXOTH) != 0);
    617 	}
    618 
    619 	cprintf(fd, "%s=", fact);
    620 
    621 			/*
    622 			 * if parent info not provided, look it up, but
    623 			 * only if the current class has modify rights,
    624 			 * since we only need this info in such a case.
    625 			 */
    626 	pdir = fe->pdirstat;
    627 	if (pdir == NULL && CURCLASS_FLAGS_ISSET(modify)) {
    628 		size_t		len;
    629 		char		realdir[MAXPATHLEN], *p;
    630 
    631 		len = strlcpy(realdir, fe->path, sizeof(realdir));
    632 		if (len < sizeof(realdir) - 4) {
    633 			if (S_ISDIR(fe->stat->st_mode))
    634 				strlcat(realdir, "/..", sizeof(realdir));
    635 			else {
    636 					/* if has a /, move back to it */
    637 					/* otherwise use '..' */
    638 				if ((p = strrchr(realdir, '/')) != NULL) {
    639 					if (p == realdir)
    640 						p++;
    641 					*p = '\0';
    642 				} else
    643 					strlcpy(realdir, "..", sizeof(realdir));
    644 			}
    645 			if (stat(realdir, &dir) == 0)
    646 				pdir = &dir;
    647 		}
    648 	}
    649 	pdirwok = 0;
    650 	if (pdir != NULL) {
    651 		if (pdir->st_uid == geteuid())
    652 			pdirwok = ((pdir->st_mode & S_IWUSR) != 0);
    653 		else if (matchgroup(pdir->st_gid))
    654 			pdirwok = ((pdir->st_mode & S_IWGRP) != 0);
    655 		else
    656 			pdirwok = ((pdir->st_mode & S_IWOTH) != 0);
    657 	}
    658 
    659 			/* 'a': can APPE to file */
    660 	if (wok && CURCLASS_FLAGS_ISSET(upload) && S_ISREG(fe->stat->st_mode))
    661 		CPUTC('a', fd);
    662 
    663 			/* 'c': can create or append to files in directory */
    664 	if (wok && CURCLASS_FLAGS_ISSET(modify) && S_ISDIR(fe->stat->st_mode))
    665 		CPUTC('c', fd);
    666 
    667 			/* 'd': can delete file or directory */
    668 	if (pdirwok && CURCLASS_FLAGS_ISSET(modify)) {
    669 		int candel;
    670 
    671 		candel = 1;
    672 		if (S_ISDIR(fe->stat->st_mode)) {
    673 			DIR *dirp;
    674 			struct dirent *dp;
    675 
    676 			if ((dirp = opendir(fe->display)) == NULL)
    677 				candel = 0;
    678 			else {
    679 				while ((dp = readdir(dirp)) != NULL) {
    680 					if (ISDOTDIR(dp->d_name) ||
    681 					    ISDOTDOTDIR(dp->d_name))
    682 						continue;
    683 					candel = 0;
    684 					break;
    685 				}
    686 				closedir(dirp);
    687 			}
    688 		}
    689 		if (candel)
    690 			CPUTC('d', fd);
    691 	}
    692 
    693 			/* 'e': can enter directory */
    694 	if (xok && S_ISDIR(fe->stat->st_mode))
    695 		CPUTC('e', fd);
    696 
    697 			/* 'f': can rename file or directory */
    698 	if (pdirwok && CURCLASS_FLAGS_ISSET(modify))
    699 		CPUTC('f', fd);
    700 
    701 			/* 'l': can list directory */
    702 	if (rok && xok && S_ISDIR(fe->stat->st_mode))
    703 		CPUTC('l', fd);
    704 
    705 			/* 'm': can create directory */
    706 	if (wok && CURCLASS_FLAGS_ISSET(modify) && S_ISDIR(fe->stat->st_mode))
    707 		CPUTC('m', fd);
    708 
    709 			/* 'p': can remove files in directory */
    710 	if (wok && CURCLASS_FLAGS_ISSET(modify) && S_ISDIR(fe->stat->st_mode))
    711 		CPUTC('p', fd);
    712 
    713 			/* 'r': can RETR file */
    714 	if (rok && S_ISREG(fe->stat->st_mode))
    715 		CPUTC('r', fd);
    716 
    717 			/* 'w': can STOR file */
    718 	if (wok && CURCLASS_FLAGS_ISSET(upload) && S_ISREG(fe->stat->st_mode))
    719 		CPUTC('w', fd);
    720 
    721 	CPUTC(';', fd);
    722 }
    723 
    724 static void
    725 fact_size(const char *fact, FILE *fd, factelem *fe)
    726 {
    727 
    728 	if (S_ISREG(fe->stat->st_mode))
    729 		cprintf(fd, "%s=" LLF ";", fact, (LLT)fe->stat->st_size);
    730 }
    731 
    732 static void
    733 fact_type(const char *fact, FILE *fd, factelem *fe)
    734 {
    735 
    736 	cprintf(fd, "%s=", fact);
    737 	switch (fe->stat->st_mode & S_IFMT) {
    738 	case S_IFDIR:
    739 		if (fe->flags & FE_MLSD) {
    740 			if ((fe->flags & FE_ISCURDIR) || ISDOTDIR(fe->display))
    741 				cprintf(fd, "cdir");
    742 			else if (ISDOTDOTDIR(fe->display))
    743 				cprintf(fd, "pdir");
    744 			else
    745 				cprintf(fd, "dir");
    746 		} else {
    747 			cprintf(fd, "dir");
    748 		}
    749 		break;
    750 	case S_IFREG:
    751 		cprintf(fd, "file");
    752 		break;
    753 	case S_IFIFO:
    754 		cprintf(fd, "OS.unix=fifo");
    755 		break;
    756 	case S_IFLNK:		/* XXX: probably a NO-OP with stat() */
    757 		cprintf(fd, "OS.unix=slink");
    758 		break;
    759 	case S_IFSOCK:
    760 		cprintf(fd, "OS.unix=socket");
    761 		break;
    762 	case S_IFBLK:
    763 	case S_IFCHR:
    764 		cprintf(fd, "OS.unix=%s-" ULLF "/" ULLF,
    765 		    S_ISBLK(fe->stat->st_mode) ? "blk" : "chr",
    766 		    (ULLT)major(fe->stat->st_rdev),
    767 		    (ULLT)minor(fe->stat->st_rdev));
    768 		break;
    769 	default:
    770 		cprintf(fd, "OS.unix=UNKNOWN(0%o)", fe->stat->st_mode & S_IFMT);
    771 		break;
    772 	}
    773 	CPUTC(';', fd);
    774 }
    775 
    776 static void
    777 fact_unique(const char *fact, FILE *fd, factelem *fe)
    778 {
    779 	char obuf[(sizeof(dev_t) + sizeof(ino_t) + 2) * 4 / 3 + 2];
    780 	char tbuf[sizeof(dev_t) + sizeof(ino_t)];
    781 
    782 	memcpy(tbuf,
    783 	    (char *)&(fe->stat->st_dev), sizeof(dev_t));
    784 	memcpy(tbuf + sizeof(dev_t),
    785 	    (char *)&(fe->stat->st_ino), sizeof(ino_t));
    786 	base64_encode(tbuf, sizeof(dev_t) + sizeof(ino_t), obuf, 1);
    787 	cprintf(fd, "%s=%s;", fact, obuf);
    788 }
    789 
    790 static int
    791 matchgroup(gid_t gid)
    792 {
    793 	int	i;
    794 
    795 	for (i = 0; i < gidcount; i++)
    796 		if (gid == gidlist[i])
    797 			return(1);
    798 	return (0);
    799 }
    800 
    801 static void
    802 mlsname(FILE *fp, factelem *fe)
    803 {
    804 	char realfile[MAXPATHLEN];
    805 	int userf = 0;
    806 	size_t i;
    807 
    808 	for (i = 0; i < FACTTABSIZE; i++) {
    809 		if (facttab[i].enabled)
    810 			(facttab[i].display)(facttab[i].name, fp, fe);
    811 	}
    812 	if ((fe->flags & FE_MLSD) &&
    813 	    !(fe->flags & FE_ISCURDIR) && !ISDOTDIR(fe->display)) {
    814 			/* if MLSD and not "." entry, display as-is */
    815 		userf = 0;
    816 	} else {
    817 			/* if MLST, or MLSD and "." entry, realpath(3) it */
    818 		if (realpath(fe->display, realfile) != NULL)
    819 			userf = 1;
    820 	}
    821 	cprintf(fp, " %s\r\n", userf ? realfile : fe->display);
    822 }
    823 
    824 static void
    825 replydirname(const char *name, const char *message)
    826 {
    827 	char *p, *ep;
    828 	char npath[MAXPATHLEN * 2];
    829 
    830 	p = npath;
    831 	ep = &npath[sizeof(npath) - 1];
    832 	while (*name) {
    833 		if (*name == '"') {
    834 			if (ep - p < 2)
    835 				break;
    836 			*p++ = *name++;
    837 			*p++ = '"';
    838 		} else {
    839 			if (ep - p < 1)
    840 				break;
    841 			*p++ = *name++;
    842 		}
    843 	}
    844 	*p = '\0';
    845 	reply(257, "\"%s\" %s", npath, message);
    846 }
    847 
    848 static void
    849 discover_path(char *last_path, const char *new_path)
    850 {
    851 	char tp[MAXPATHLEN + 1] = "";
    852 	char tq[MAXPATHLEN + 1] = "";
    853 	char *cp;
    854 	char *cq;
    855 	int sz1, sz2;
    856 	int nomorelink;
    857 	struct stat st1, st2;
    858 
    859 	if (new_path[0] != '/') {
    860 		(void)strlcpy(tp, last_path, MAXPATHLEN);
    861 		(void)strlcat(tp, "/", MAXPATHLEN);
    862 	}
    863 	(void)strlcat(tp, new_path, MAXPATHLEN);
    864 	(void)strlcat(tp, "/", MAXPATHLEN);
    865 
    866 	/*
    867 	 * resolve symlinks. A symlink may introduce another symlink, so we
    868 	 * loop trying to resolve symlinks until we don't find any of them.
    869 	 */
    870 	do {
    871 		/* Collapse any // into / */
    872 		while ((cp = strstr(tp, "//")) != NULL)
    873 			(void)memmove(cp, cp + 1, strlen(cp) - 1 + 1);
    874 
    875 		/* Collapse any /./ into / */
    876 		while ((cp = strstr(tp, "/./")) != NULL)
    877 			(void)memmove(cp, cp + 2, strlen(cp) - 2 + 1);
    878 
    879 		cp = tp;
    880 		nomorelink = 1;
    881 
    882 		while ((cp = strstr(cp + 1, "/")) != NULL) {
    883 			sz1 = (unsigned long)cp - (unsigned long)tp;
    884 			if (sz1 > MAXPATHLEN)
    885 				goto bad;
    886 			*cp = 0;
    887 			sz2 = readlink(tp, tq, MAXPATHLEN);
    888 			*cp = '/';
    889 
    890 			/* If this is not a symlink, move to next / */
    891 			if (sz2 <= 0)
    892 				continue;
    893 
    894 			/*
    895 			 * We found a symlink, so we will have to
    896 			 * do one more pass to check there is no
    897 			 * more symlink in the path
    898 			 */
    899 			nomorelink = 0;
    900 
    901 			/*
    902 			 * Null terminate the string and remove trailing /
    903 			 */
    904 			tq[sz2] = 0;
    905 			sz2 = strlen(tq);
    906 			if (tq[sz2 - 1] == '/')
    907 				tq[--sz2] = 0;
    908 
    909 			/*
    910 			 * Is this an absolute link or a relative link?
    911 			 */
    912 			if (tq[0] == '/') {
    913 				/* absolute link */
    914 				if (strlen(cp) + sz2 > MAXPATHLEN)
    915 					goto bad;
    916 				memmove(tp + sz2, cp, strlen(cp) + 1);
    917 				memcpy(tp, tq, sz2);
    918 			} else {
    919 				/* relative link */
    920 				for (cq = cp - 1; *cq != '/'; cq--);
    921 				if (strlen(tp) -
    922 				    ((unsigned long)cq - (unsigned long)cp)
    923 				    + 1 + sz2 > MAXPATHLEN)
    924 					goto bad;
    925 				(void)memmove(cq + 1 + sz2,
    926 				    cp, strlen(cp) + 1);
    927 				(void)memcpy(cq + 1, tq, sz2);
    928 			}
    929 
    930 			/*
    931 			 * start over, looking for new symlinks
    932 			 */
    933 			break;
    934 		}
    935 	} while (nomorelink == 0);
    936 
    937 	/* Collapse any /foo/../ into /foo/ */
    938 	while ((cp = strstr(tp, "/../")) != NULL) {
    939 		/* ^/../foo/ becomes ^/foo/ */
    940 		if (cp == tp) {
    941 			(void)memmove(cp, cp + 3,
    942 			    strlen(cp) - 3 + 1);
    943 		} else {
    944 			for (cq = cp - 1; *cq != '/'; cq--);
    945 			(void)memmove(cq, cp + 3,
    946 			    strlen(cp) - 3 + 1);
    947 		}
    948 	}
    949 
    950 	/* strip strailing / */
    951 	if (strlen(tp) != 1)
    952 		tp[strlen(tp) - 1] = '\0';
    953 
    954 	/* check that the path is correct */
    955 	if (stat(tp, &st1) == -1 || stat(".", &st2) == -1)
    956 		goto bad;
    957 	if ((st1.st_dev != st2.st_dev) || (st1.st_ino != st2.st_ino))
    958 		goto bad;
    959 
    960 	(void)strlcpy(last_path, tp, MAXPATHLEN);
    961 	return;
    962 
    963 bad:
    964 	(void)strlcat(last_path, "/", MAXPATHLEN);
    965 	(void)strlcat(last_path, new_path, MAXPATHLEN);
    966 	return;
    967 }
    968 
    969