Home | History | Annotate | Line # | Download | only in ftpd
cmds.c revision 1.34
      1 /*	$NetBSD: cmds.c,v 1.34 2015/08/10 07:32:49 shm 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.34 2015/08/10 07:32:49 shm 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;
    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 		struct stat	dir;
    631 
    632 		len = strlcpy(realdir, fe->path, sizeof(realdir));
    633 		if (len < sizeof(realdir) - 4) {
    634 			if (S_ISDIR(fe->stat->st_mode))
    635 				strlcat(realdir, "/..", sizeof(realdir));
    636 			else {
    637 					/* if has a /, move back to it */
    638 					/* otherwise use '..' */
    639 				if ((p = strrchr(realdir, '/')) != NULL) {
    640 					if (p == realdir)
    641 						p++;
    642 					*p = '\0';
    643 				} else
    644 					strlcpy(realdir, "..", sizeof(realdir));
    645 			}
    646 			if (stat(realdir, &dir) == 0)
    647 				pdir = &dir;
    648 		}
    649 	}
    650 	pdirwok = 0;
    651 	if (pdir != NULL) {
    652 		if (pdir->st_uid == geteuid())
    653 			pdirwok = ((pdir->st_mode & S_IWUSR) != 0);
    654 		else if (matchgroup(pdir->st_gid))
    655 			pdirwok = ((pdir->st_mode & S_IWGRP) != 0);
    656 		else
    657 			pdirwok = ((pdir->st_mode & S_IWOTH) != 0);
    658 	}
    659 
    660 			/* 'a': can APPE to file */
    661 	if (wok && CURCLASS_FLAGS_ISSET(upload) && S_ISREG(fe->stat->st_mode))
    662 		CPUTC('a', fd);
    663 
    664 			/* 'c': can create or append to files in directory */
    665 	if (wok && CURCLASS_FLAGS_ISSET(modify) && S_ISDIR(fe->stat->st_mode))
    666 		CPUTC('c', fd);
    667 
    668 			/* 'd': can delete file or directory */
    669 	if (pdirwok && CURCLASS_FLAGS_ISSET(modify)) {
    670 		int candel;
    671 
    672 		candel = 1;
    673 		if (S_ISDIR(fe->stat->st_mode)) {
    674 			DIR *dirp;
    675 			struct dirent *dp;
    676 
    677 			if ((dirp = opendir(fe->display)) == NULL)
    678 				candel = 0;
    679 			else {
    680 				while ((dp = readdir(dirp)) != NULL) {
    681 					if (ISDOTDIR(dp->d_name) ||
    682 					    ISDOTDOTDIR(dp->d_name))
    683 						continue;
    684 					candel = 0;
    685 					break;
    686 				}
    687 				closedir(dirp);
    688 			}
    689 		}
    690 		if (candel)
    691 			CPUTC('d', fd);
    692 	}
    693 
    694 			/* 'e': can enter directory */
    695 	if (xok && S_ISDIR(fe->stat->st_mode))
    696 		CPUTC('e', fd);
    697 
    698 			/* 'f': can rename file or directory */
    699 	if (pdirwok && CURCLASS_FLAGS_ISSET(modify))
    700 		CPUTC('f', fd);
    701 
    702 			/* 'l': can list directory */
    703 	if (rok && xok && S_ISDIR(fe->stat->st_mode))
    704 		CPUTC('l', fd);
    705 
    706 			/* 'm': can create directory */
    707 	if (wok && CURCLASS_FLAGS_ISSET(modify) && S_ISDIR(fe->stat->st_mode))
    708 		CPUTC('m', fd);
    709 
    710 			/* 'p': can remove files in directory */
    711 	if (wok && CURCLASS_FLAGS_ISSET(modify) && S_ISDIR(fe->stat->st_mode))
    712 		CPUTC('p', fd);
    713 
    714 			/* 'r': can RETR file */
    715 	if (rok && S_ISREG(fe->stat->st_mode))
    716 		CPUTC('r', fd);
    717 
    718 			/* 'w': can STOR file */
    719 	if (wok && CURCLASS_FLAGS_ISSET(upload) && S_ISREG(fe->stat->st_mode))
    720 		CPUTC('w', fd);
    721 
    722 	CPUTC(';', fd);
    723 }
    724 
    725 static void
    726 fact_size(const char *fact, FILE *fd, factelem *fe)
    727 {
    728 
    729 	if (S_ISREG(fe->stat->st_mode))
    730 		cprintf(fd, "%s=" LLF ";", fact, (LLT)fe->stat->st_size);
    731 }
    732 
    733 static void
    734 fact_type(const char *fact, FILE *fd, factelem *fe)
    735 {
    736 
    737 	cprintf(fd, "%s=", fact);
    738 	switch (fe->stat->st_mode & S_IFMT) {
    739 	case S_IFDIR:
    740 		if (fe->flags & FE_MLSD) {
    741 			if ((fe->flags & FE_ISCURDIR) || ISDOTDIR(fe->display))
    742 				cprintf(fd, "cdir");
    743 			else if (ISDOTDOTDIR(fe->display))
    744 				cprintf(fd, "pdir");
    745 			else
    746 				cprintf(fd, "dir");
    747 		} else {
    748 			cprintf(fd, "dir");
    749 		}
    750 		break;
    751 	case S_IFREG:
    752 		cprintf(fd, "file");
    753 		break;
    754 	case S_IFIFO:
    755 		cprintf(fd, "OS.unix=fifo");
    756 		break;
    757 	case S_IFLNK:		/* XXX: probably a NO-OP with stat() */
    758 		cprintf(fd, "OS.unix=slink");
    759 		break;
    760 	case S_IFSOCK:
    761 		cprintf(fd, "OS.unix=socket");
    762 		break;
    763 	case S_IFBLK:
    764 	case S_IFCHR:
    765 		cprintf(fd, "OS.unix=%s-" ULLF "/" ULLF,
    766 		    S_ISBLK(fe->stat->st_mode) ? "blk" : "chr",
    767 		    (ULLT)major(fe->stat->st_rdev),
    768 		    (ULLT)minor(fe->stat->st_rdev));
    769 		break;
    770 	default:
    771 		cprintf(fd, "OS.unix=UNKNOWN(0%o)", fe->stat->st_mode & S_IFMT);
    772 		break;
    773 	}
    774 	CPUTC(';', fd);
    775 }
    776 
    777 static void
    778 fact_unique(const char *fact, FILE *fd, factelem *fe)
    779 {
    780 	char obuf[(sizeof(dev_t) + sizeof(ino_t) + 2) * 4 / 3 + 2];
    781 	char tbuf[sizeof(dev_t) + sizeof(ino_t)];
    782 
    783 	memcpy(tbuf,
    784 	    (char *)&(fe->stat->st_dev), sizeof(dev_t));
    785 	memcpy(tbuf + sizeof(dev_t),
    786 	    (char *)&(fe->stat->st_ino), sizeof(ino_t));
    787 	base64_encode(tbuf, sizeof(dev_t) + sizeof(ino_t), obuf, 1);
    788 	cprintf(fd, "%s=%s;", fact, obuf);
    789 }
    790 
    791 static int
    792 matchgroup(gid_t gid)
    793 {
    794 	int	i;
    795 
    796 	for (i = 0; i < gidcount; i++)
    797 		if (gid == gidlist[i])
    798 			return(1);
    799 	return (0);
    800 }
    801 
    802 static void
    803 mlsname(FILE *fp, factelem *fe)
    804 {
    805 	char realfile[MAXPATHLEN];
    806 	int userf = 0;
    807 	size_t i;
    808 
    809 	for (i = 0; i < FACTTABSIZE; i++) {
    810 		if (facttab[i].enabled)
    811 			(facttab[i].display)(facttab[i].name, fp, fe);
    812 	}
    813 	if ((fe->flags & FE_MLSD) &&
    814 	    !(fe->flags & FE_ISCURDIR) && !ISDOTDIR(fe->display)) {
    815 			/* if MLSD and not "." entry, display as-is */
    816 		userf = 0;
    817 	} else {
    818 			/* if MLST, or MLSD and "." entry, realpath(3) it */
    819 		if (realpath(fe->display, realfile) != NULL)
    820 			userf = 1;
    821 	}
    822 	cprintf(fp, " %s\r\n", userf ? realfile : fe->display);
    823 }
    824 
    825 static void
    826 replydirname(const char *name, const char *message)
    827 {
    828 	char *p, *ep;
    829 	char npath[MAXPATHLEN * 2];
    830 
    831 	p = npath;
    832 	ep = &npath[sizeof(npath) - 1];
    833 	while (*name) {
    834 		if (*name == '"') {
    835 			if (ep - p < 2)
    836 				break;
    837 			*p++ = *name++;
    838 			*p++ = '"';
    839 		} else {
    840 			if (ep - p < 1)
    841 				break;
    842 			*p++ = *name++;
    843 		}
    844 	}
    845 	*p = '\0';
    846 	reply(257, "\"%s\" %s", npath, message);
    847 }
    848 
    849 static void
    850 discover_path(char *last_path, const char *new_path)
    851 {
    852 	char tp[MAXPATHLEN + 1] = "";
    853 	char tq[MAXPATHLEN + 1] = "";
    854 	char *cp;
    855 	char *cq;
    856 	int sz1, sz2;
    857 	int nomorelink;
    858 	struct stat st1, st2;
    859 
    860 	if (new_path[0] != '/') {
    861 		(void)strlcpy(tp, last_path, MAXPATHLEN);
    862 		(void)strlcat(tp, "/", MAXPATHLEN);
    863 	}
    864 	(void)strlcat(tp, new_path, MAXPATHLEN);
    865 	(void)strlcat(tp, "/", MAXPATHLEN);
    866 
    867 	/*
    868 	 * resolve symlinks. A symlink may introduce another symlink, so we
    869 	 * loop trying to resolve symlinks until we don't find any of them.
    870 	 */
    871 	do {
    872 		/* Collapse any // into / */
    873 		while ((cp = strstr(tp, "//")) != NULL)
    874 			(void)memmove(cp, cp + 1, strlen(cp) - 1 + 1);
    875 
    876 		/* Collapse any /./ into / */
    877 		while ((cp = strstr(tp, "/./")) != NULL)
    878 			(void)memmove(cp, cp + 2, strlen(cp) - 2 + 1);
    879 
    880 		cp = tp;
    881 		nomorelink = 1;
    882 
    883 		while ((cp = strstr(cp + 1, "/")) != NULL) {
    884 			sz1 = (unsigned long)cp - (unsigned long)tp;
    885 			if (sz1 > MAXPATHLEN)
    886 				goto bad;
    887 			*cp = 0;
    888 			sz2 = readlink(tp, tq, MAXPATHLEN);
    889 			*cp = '/';
    890 
    891 			/* If this is not a symlink, move to next / */
    892 			if (sz2 <= 0)
    893 				continue;
    894 
    895 			/*
    896 			 * We found a symlink, so we will have to
    897 			 * do one more pass to check there is no
    898 			 * more symlink in the path
    899 			 */
    900 			nomorelink = 0;
    901 
    902 			/*
    903 			 * Null terminate the string and remove trailing /
    904 			 */
    905 			tq[sz2] = 0;
    906 			sz2 = strlen(tq);
    907 			if (tq[sz2 - 1] == '/')
    908 				tq[--sz2] = 0;
    909 
    910 			/*
    911 			 * Is this an absolute link or a relative link?
    912 			 */
    913 			if (tq[0] == '/') {
    914 				/* absolute link */
    915 				if (strlen(cp) + sz2 > MAXPATHLEN)
    916 					goto bad;
    917 				memmove(tp + sz2, cp, strlen(cp) + 1);
    918 				memcpy(tp, tq, sz2);
    919 			} else {
    920 				/* relative link */
    921 				for (cq = cp - 1; *cq != '/'; cq--);
    922 				if (strlen(tp) -
    923 				    ((unsigned long)cq - (unsigned long)cp)
    924 				    + 1 + sz2 > MAXPATHLEN)
    925 					goto bad;
    926 				(void)memmove(cq + 1 + sz2,
    927 				    cp, strlen(cp) + 1);
    928 				(void)memcpy(cq + 1, tq, sz2);
    929 			}
    930 
    931 			/*
    932 			 * start over, looking for new symlinks
    933 			 */
    934 			break;
    935 		}
    936 	} while (nomorelink == 0);
    937 
    938 	/* Collapse any /foo/../ into /foo/ */
    939 	while ((cp = strstr(tp, "/../")) != NULL) {
    940 		/* ^/../foo/ becomes ^/foo/ */
    941 		if (cp == tp) {
    942 			(void)memmove(cp, cp + 3,
    943 			    strlen(cp) - 3 + 1);
    944 		} else {
    945 			for (cq = cp - 1; *cq != '/'; cq--);
    946 			(void)memmove(cq, cp + 3,
    947 			    strlen(cp) - 3 + 1);
    948 		}
    949 	}
    950 
    951 	/* strip strailing / */
    952 	if (strlen(tp) != 1)
    953 		tp[strlen(tp) - 1] = '\0';
    954 
    955 	/* check that the path is correct */
    956 	if (stat(tp, &st1) == -1 || stat(".", &st2) == -1)
    957 		goto bad;
    958 	if ((st1.st_dev != st2.st_dev) || (st1.st_ino != st2.st_ino))
    959 		goto bad;
    960 
    961 	(void)strlcpy(last_path, tp, MAXPATHLEN);
    962 	return;
    963 
    964 bad:
    965 	(void)strlcat(last_path, "/", MAXPATHLEN);
    966 	(void)strlcat(last_path, new_path, MAXPATHLEN);
    967 	return;
    968 }
    969 
    970