Home | History | Annotate | Line # | Download | only in ftpd
cmds.c revision 1.33
      1 /*	$NetBSD: cmds.c,v 1.33 2013/07/03 14:15:47 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.33 2013/07/03 14:15:47 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 		return;
    254 
    255 	memset(&f, 0, sizeof(f));
    256 	f.stat = &sb;
    257 	f.flags |= FE_MLSD;
    258 	while ((dp = readdir(dirp)) != NULL) {
    259 		snprintf(name, sizeof(name), "%s/%s", path, dp->d_name);
    260 		if (ISDOTDIR(dp->d_name)) {	/* special case curdir: */
    261 			if (! hastypefact)
    262 				continue;
    263 			f.pdirstat = NULL;	/*   require stat of parent */
    264 			f.display = path;	/*   set name to real name */
    265 			f.flags |= FE_ISCURDIR; /*   flag name is curdir */
    266 		} else {
    267 			if (ISDOTDOTDIR(dp->d_name)) {
    268 				if (! hastypefact)
    269 					continue;
    270 				f.pdirstat = NULL;
    271 			} else
    272 				f.pdirstat = &pdirstat;	/* cache parent stat */
    273 			f.display = dp->d_name;
    274 			f.flags &= ~FE_ISCURDIR;
    275 		}
    276 		if (stat(name, &sb) == -1)
    277 			continue;
    278 		f.path = name;
    279 		mlsname(dout, &f);
    280 	}
    281 	(void)closedir(dirp);
    282 
    283 	if (ferror(dout) != 0)
    284 		perror_reply(550, "Data connection");
    285 	else
    286 		reply(226, "MLSD complete.");
    287 	closedataconn(dout);
    288 	total_xfers_out++;
    289 	total_xfers++;
    290 }
    291 
    292 void
    293 mlst(const char *path)
    294 {
    295 	struct stat sb;
    296 	factelem f;
    297 
    298 	if (path == NULL)
    299 		path = ".";
    300 	if (stat(path, &sb) == -1) {
    301 		perror_reply(550, path);
    302 		return;
    303 	}
    304 	reply(-250, "MLST %s", path);
    305 	memset(&f, 0, sizeof(f));
    306 	f.path = path;
    307 	f.display = path;
    308 	f.stat = &sb;
    309 	f.pdirstat = NULL;
    310 	CPUTC(' ', stdout);
    311 	mlsname(stdout, &f);
    312 	reply(250, "End");
    313 }
    314 
    315 
    316 void
    317 opts(const char *command)
    318 {
    319 	struct tab *c;
    320 	char *ep;
    321 
    322 	if ((ep = strchr(command, ' ')) != NULL)
    323 		*ep++ = '\0';
    324 	c = lookup(cmdtab, command);
    325 	if (c == NULL) {
    326 		reply(502, "Unknown command '%s'.", command);
    327 		return;
    328 	}
    329 	if (! CMD_IMPLEMENTED(c)) {
    330 		reply(502, "%s command not implemented.", c->name);
    331 		return;
    332 	}
    333 	if (! CMD_HAS_OPTIONS(c)) {
    334 		reply(501, "%s command does not support persistent options.",
    335 		    c->name);
    336 		return;
    337 	}
    338 
    339 			/* special case: MLST */
    340 	if (strcasecmp(command, "MLST") == 0) {
    341 		int	 enabled[FACTTABSIZE];
    342 		size_t	 i, onedone;
    343 		size_t	 len;
    344 		char	*p;
    345 
    346 		for (i = 0; i < sizeof(enabled) / sizeof(int); i++)
    347 			enabled[i] = 0;
    348 		if (ep == NULL || *ep == '\0')
    349 			goto displaymlstopts;
    350 
    351 				/* don't like spaces, and need trailing ; */
    352 		len = strlen(ep);
    353 		if (strchr(ep, ' ') != NULL || ep[len - 1] != ';') {
    354  badmlstopt:
    355 			reply(501, "Invalid MLST options");
    356 			return;
    357 		}
    358 		ep[len - 1] = '\0';
    359 		while ((p = strsep(&ep, ";")) != NULL) {
    360 			if (*p == '\0')
    361 				goto badmlstopt;
    362 			for (i = 0; i < FACTTABSIZE; i++)
    363 				if (strcasecmp(p, facttab[i].name) == 0) {
    364 					enabled[i] = 1;
    365 					break;
    366 				}
    367 		}
    368 
    369  displaymlstopts:
    370 		for (i = 0; i < FACTTABSIZE; i++)
    371 			facttab[i].enabled = enabled[i];
    372 		cprintf(stdout, "200 MLST OPTS");
    373 		for (i = onedone = 0; i < FACTTABSIZE; i++) {
    374 			if (facttab[i].enabled) {
    375 				cprintf(stdout, "%s%s;", onedone ? "" : " ",
    376 				    facttab[i].name);
    377 				onedone++;
    378 			}
    379 		}
    380 		cprintf(stdout, "\r\n");
    381 		fflush(stdout);
    382 		return;
    383 	}
    384 
    385 			/* default cases */
    386 	if (ep != NULL && *ep != '\0')
    387 		REASSIGN(c->options, ftpd_strdup(ep));
    388 	if (c->options != NULL)
    389 		reply(200, "Options for %s are '%s'.", c->name,
    390 		    c->options);
    391 	else
    392 		reply(200, "No options defined for %s.", c->name);
    393 }
    394 
    395 void
    396 pwd(void)
    397 {
    398 	char path[MAXPATHLEN];
    399 
    400 	if (getcwd(path, sizeof(path) - 1) == NULL) {
    401 		if (chdir(cached_path) < 0) {
    402 			reply(550, "Can't get the current directory: %s.",
    403 			    strerror(errno));
    404 			return;
    405 		}
    406 		(void)strlcpy(path, cached_path, MAXPATHLEN);
    407 	}
    408 	replydirname(path, "is the current directory.");
    409 }
    410 
    411 void
    412 removedir(const char *name)
    413 {
    414 	char *p = NULL;
    415 
    416 	if (rmdir(name) < 0) {
    417 		p = strerror(errno);
    418 		perror_reply(550, name);
    419 	} else
    420 		ack("RMD");
    421 	logxfer("rmdir", -1, name, NULL, NULL, p);
    422 }
    423 
    424 char *
    425 renamefrom(const char *name)
    426 {
    427 	struct stat st;
    428 
    429 	if (stat(name, &st) < 0) {
    430 		perror_reply(550, name);
    431 		return (NULL);
    432 	}
    433 	reply(350, "File exists, ready for destination name");
    434 	return (ftpd_strdup(name));
    435 }
    436 
    437 void
    438 renamecmd(const char *from, const char *to)
    439 {
    440 	char *p = NULL;
    441 
    442 	if (rename(from, to) < 0) {
    443 		p = strerror(errno);
    444 		perror_reply(550, "rename");
    445 	} else
    446 		ack("RNTO");
    447 	logxfer("rename", -1, from, to, NULL, p);
    448 }
    449 
    450 void
    451 sizecmd(const char *filename)
    452 {
    453 	switch (type) {
    454 	case TYPE_L:
    455 	case TYPE_I:
    456 	    {
    457 		struct stat stbuf;
    458 		if (stat(filename, &stbuf) < 0 || !S_ISREG(stbuf.st_mode))
    459 			reply(550, "%s: not a plain file.", filename);
    460 		else
    461 			reply(213, ULLF, (ULLT)stbuf.st_size);
    462 		break;
    463 	    }
    464 	case TYPE_A:
    465 	    {
    466 		FILE *fin;
    467 		int c;
    468 		off_t count;
    469 		struct stat stbuf;
    470 		fin = fopen(filename, "r");
    471 		if (fin == NULL) {
    472 			perror_reply(550, filename);
    473 			return;
    474 		}
    475 		if (fstat(fileno(fin), &stbuf) < 0 || !S_ISREG(stbuf.st_mode)) {
    476 			reply(550, "%s: not a plain file.", filename);
    477 			(void) fclose(fin);
    478 			return;
    479 		}
    480 		if (stbuf.st_size > 10240) {
    481 			reply(550, "%s: file too large for SIZE.", filename);
    482 			(void) fclose(fin);
    483 			return;
    484 		}
    485 
    486 		count = 0;
    487 		while((c = getc(fin)) != EOF) {
    488 			if (c == '\n')	/* will get expanded to \r\n */
    489 				count++;
    490 			count++;
    491 		}
    492 		(void) fclose(fin);
    493 
    494 		reply(213, LLF, (LLT)count);
    495 		break;
    496 	    }
    497 	default:
    498 		reply(504, "SIZE not implemented for Type %c.", "?AEIL"[type]);
    499 	}
    500 }
    501 
    502 void
    503 statfilecmd(const char *filename)
    504 {
    505 	FILE *fin;
    506 	int c;
    507 	int atstart;
    508 	const char *argv[] = { INTERNAL_LS, "-lgA", "", NULL };
    509 
    510 	argv[2] = filename;
    511 	fin = ftpd_popen(argv, "r", STDOUT_FILENO);
    512 	reply(-211, "status of %s:", filename);
    513 /* XXX: use fgetln() or fparseln() here? */
    514 	atstart = 1;
    515 	while ((c = getc(fin)) != EOF) {
    516 		if (c == '\n') {
    517 			if (ferror(stdout)){
    518 				perror_reply(421, "control connection");
    519 				(void) ftpd_pclose(fin);
    520 				dologout(1);
    521 				/* NOTREACHED */
    522 			}
    523 			if (ferror(fin)) {
    524 				perror_reply(551, filename);
    525 				(void) ftpd_pclose(fin);
    526 				return;
    527 			}
    528 			CPUTC('\r', stdout);
    529 		}
    530 		if (atstart && isdigit(c))
    531 			CPUTC(' ', stdout);
    532 		CPUTC(c, stdout);
    533 		atstart = (c == '\n');
    534 	}
    535 	(void) ftpd_pclose(fin);
    536 	reply(211, "End of Status");
    537 }
    538 
    539 /* -- */
    540 
    541 static void
    542 ack(const char *s)
    543 {
    544 
    545 	reply(250, "%s command successful.", s);
    546 }
    547 
    548 /*
    549  * Encode len bytes starting at clear using base64 encoding into encoded,
    550  * which should be at least ((len + 2) * 4 / 3 + 1) in size.
    551  * If nulterm is non-zero, terminate with \0 otherwise pad to 3 byte boundary
    552  * with `='.
    553  */
    554 static void
    555 base64_encode(const char *clear, size_t len, char *encoded, int nulterm)
    556 {
    557 	static const char base64[] =
    558 	    "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
    559 	const char *c;
    560 	char	*e, termchar;
    561 	int	 i;
    562 
    563 			/* determine whether to pad with '=' or NUL terminate */
    564 	termchar = nulterm ? '\0' : '=';
    565 	c = clear;
    566 	e = encoded;
    567 			/* convert all but last 2 bytes */
    568 	for (i = len; i > 2; i -= 3, c += 3) {
    569 		*e++ = base64[(c[0] >> 2) & 0x3f];
    570 		*e++ = base64[((c[0] << 4) & 0x30) | ((c[1] >> 4) & 0x0f)];
    571 		*e++ = base64[((c[1] << 2) & 0x3c) | ((c[2] >> 6) & 0x03)];
    572 		*e++ = base64[(c[2]) & 0x3f];
    573 	}
    574 			/* handle slop at end */
    575 	if (i > 0) {
    576 		*e++ = base64[(c[0] >> 2) & 0x3f];
    577 		*e++ = base64[((c[0] << 4) & 0x30) |
    578 		     (i > 1 ? ((c[1] >> 4) & 0x0f) : 0)];
    579 		*e++ = (i > 1) ? base64[(c[1] << 2) & 0x3c] : termchar;
    580 		*e++ = termchar;
    581 	}
    582 	*e = '\0';
    583 }
    584 
    585 static void
    586 fact_modify(const char *fact, FILE *fd, factelem *fe)
    587 {
    588 	struct tm *t;
    589 
    590 	t = gmtime(&(fe->stat->st_mtime));
    591 	cprintf(fd, "%s=%04d%02d%02d%02d%02d%02d;", fact,
    592 	    TM_YEAR_BASE + t->tm_year,
    593 	    t->tm_mon+1, t->tm_mday,
    594 	    t->tm_hour, t->tm_min, t->tm_sec);
    595 }
    596 
    597 static void
    598 fact_perm(const char *fact, FILE *fd, factelem *fe)
    599 {
    600 	int		rok, wok, xok, pdirwok;
    601 	struct stat	*pdir;
    602 
    603 	if (fe->stat->st_uid == geteuid()) {
    604 		rok = ((fe->stat->st_mode & S_IRUSR) != 0);
    605 		wok = ((fe->stat->st_mode & S_IWUSR) != 0);
    606 		xok = ((fe->stat->st_mode & S_IXUSR) != 0);
    607 	} else if (matchgroup(fe->stat->st_gid)) {
    608 		rok = ((fe->stat->st_mode & S_IRGRP) != 0);
    609 		wok = ((fe->stat->st_mode & S_IWGRP) != 0);
    610 		xok = ((fe->stat->st_mode & S_IXGRP) != 0);
    611 	} else {
    612 		rok = ((fe->stat->st_mode & S_IROTH) != 0);
    613 		wok = ((fe->stat->st_mode & S_IWOTH) != 0);
    614 		xok = ((fe->stat->st_mode & S_IXOTH) != 0);
    615 	}
    616 
    617 	cprintf(fd, "%s=", fact);
    618 
    619 			/*
    620 			 * if parent info not provided, look it up, but
    621 			 * only if the current class has modify rights,
    622 			 * since we only need this info in such a case.
    623 			 */
    624 	pdir = fe->pdirstat;
    625 	if (pdir == NULL && CURCLASS_FLAGS_ISSET(modify)) {
    626 		size_t		len;
    627 		char		realdir[MAXPATHLEN], *p;
    628 		struct stat	dir;
    629 
    630 		len = strlcpy(realdir, fe->path, sizeof(realdir));
    631 		if (len < sizeof(realdir) - 4) {
    632 			if (S_ISDIR(fe->stat->st_mode))
    633 				strlcat(realdir, "/..", sizeof(realdir));
    634 			else {
    635 					/* if has a /, move back to it */
    636 					/* otherwise use '..' */
    637 				if ((p = strrchr(realdir, '/')) != NULL) {
    638 					if (p == realdir)
    639 						p++;
    640 					*p = '\0';
    641 				} else
    642 					strlcpy(realdir, "..", sizeof(realdir));
    643 			}
    644 			if (stat(realdir, &dir) == 0)
    645 				pdir = &dir;
    646 		}
    647 	}
    648 	pdirwok = 0;
    649 	if (pdir != NULL) {
    650 		if (pdir->st_uid == geteuid())
    651 			pdirwok = ((pdir->st_mode & S_IWUSR) != 0);
    652 		else if (matchgroup(pdir->st_gid))
    653 			pdirwok = ((pdir->st_mode & S_IWGRP) != 0);
    654 		else
    655 			pdirwok = ((pdir->st_mode & S_IWOTH) != 0);
    656 	}
    657 
    658 			/* 'a': can APPE to file */
    659 	if (wok && CURCLASS_FLAGS_ISSET(upload) && S_ISREG(fe->stat->st_mode))
    660 		CPUTC('a', fd);
    661 
    662 			/* 'c': can create or append to files in directory */
    663 	if (wok && CURCLASS_FLAGS_ISSET(modify) && S_ISDIR(fe->stat->st_mode))
    664 		CPUTC('c', fd);
    665 
    666 			/* 'd': can delete file or directory */
    667 	if (pdirwok && CURCLASS_FLAGS_ISSET(modify)) {
    668 		int candel;
    669 
    670 		candel = 1;
    671 		if (S_ISDIR(fe->stat->st_mode)) {
    672 			DIR *dirp;
    673 			struct dirent *dp;
    674 
    675 			if ((dirp = opendir(fe->display)) == NULL)
    676 				candel = 0;
    677 			else {
    678 				while ((dp = readdir(dirp)) != NULL) {
    679 					if (ISDOTDIR(dp->d_name) ||
    680 					    ISDOTDOTDIR(dp->d_name))
    681 						continue;
    682 					candel = 0;
    683 					break;
    684 				}
    685 				closedir(dirp);
    686 			}
    687 		}
    688 		if (candel)
    689 			CPUTC('d', fd);
    690 	}
    691 
    692 			/* 'e': can enter directory */
    693 	if (xok && S_ISDIR(fe->stat->st_mode))
    694 		CPUTC('e', fd);
    695 
    696 			/* 'f': can rename file or directory */
    697 	if (pdirwok && CURCLASS_FLAGS_ISSET(modify))
    698 		CPUTC('f', fd);
    699 
    700 			/* 'l': can list directory */
    701 	if (rok && xok && S_ISDIR(fe->stat->st_mode))
    702 		CPUTC('l', fd);
    703 
    704 			/* 'm': can create directory */
    705 	if (wok && CURCLASS_FLAGS_ISSET(modify) && S_ISDIR(fe->stat->st_mode))
    706 		CPUTC('m', fd);
    707 
    708 			/* 'p': can remove files in directory */
    709 	if (wok && CURCLASS_FLAGS_ISSET(modify) && S_ISDIR(fe->stat->st_mode))
    710 		CPUTC('p', fd);
    711 
    712 			/* 'r': can RETR file */
    713 	if (rok && S_ISREG(fe->stat->st_mode))
    714 		CPUTC('r', fd);
    715 
    716 			/* 'w': can STOR file */
    717 	if (wok && CURCLASS_FLAGS_ISSET(upload) && S_ISREG(fe->stat->st_mode))
    718 		CPUTC('w', fd);
    719 
    720 	CPUTC(';', fd);
    721 }
    722 
    723 static void
    724 fact_size(const char *fact, FILE *fd, factelem *fe)
    725 {
    726 
    727 	if (S_ISREG(fe->stat->st_mode))
    728 		cprintf(fd, "%s=" LLF ";", fact, (LLT)fe->stat->st_size);
    729 }
    730 
    731 static void
    732 fact_type(const char *fact, FILE *fd, factelem *fe)
    733 {
    734 
    735 	cprintf(fd, "%s=", fact);
    736 	switch (fe->stat->st_mode & S_IFMT) {
    737 	case S_IFDIR:
    738 		if (fe->flags & FE_MLSD) {
    739 			if ((fe->flags & FE_ISCURDIR) || ISDOTDIR(fe->display))
    740 				cprintf(fd, "cdir");
    741 			else if (ISDOTDOTDIR(fe->display))
    742 				cprintf(fd, "pdir");
    743 			else
    744 				cprintf(fd, "dir");
    745 		} else {
    746 			cprintf(fd, "dir");
    747 		}
    748 		break;
    749 	case S_IFREG:
    750 		cprintf(fd, "file");
    751 		break;
    752 	case S_IFIFO:
    753 		cprintf(fd, "OS.unix=fifo");
    754 		break;
    755 	case S_IFLNK:		/* XXX: probably a NO-OP with stat() */
    756 		cprintf(fd, "OS.unix=slink");
    757 		break;
    758 	case S_IFSOCK:
    759 		cprintf(fd, "OS.unix=socket");
    760 		break;
    761 	case S_IFBLK:
    762 	case S_IFCHR:
    763 		cprintf(fd, "OS.unix=%s-" ULLF "/" ULLF,
    764 		    S_ISBLK(fe->stat->st_mode) ? "blk" : "chr",
    765 		    (ULLT)major(fe->stat->st_rdev),
    766 		    (ULLT)minor(fe->stat->st_rdev));
    767 		break;
    768 	default:
    769 		cprintf(fd, "OS.unix=UNKNOWN(0%o)", fe->stat->st_mode & S_IFMT);
    770 		break;
    771 	}
    772 	CPUTC(';', fd);
    773 }
    774 
    775 static void
    776 fact_unique(const char *fact, FILE *fd, factelem *fe)
    777 {
    778 	char obuf[(sizeof(dev_t) + sizeof(ino_t) + 2) * 4 / 3 + 2];
    779 	char tbuf[sizeof(dev_t) + sizeof(ino_t)];
    780 
    781 	memcpy(tbuf,
    782 	    (char *)&(fe->stat->st_dev), sizeof(dev_t));
    783 	memcpy(tbuf + sizeof(dev_t),
    784 	    (char *)&(fe->stat->st_ino), sizeof(ino_t));
    785 	base64_encode(tbuf, sizeof(dev_t) + sizeof(ino_t), obuf, 1);
    786 	cprintf(fd, "%s=%s;", fact, obuf);
    787 }
    788 
    789 static int
    790 matchgroup(gid_t gid)
    791 {
    792 	int	i;
    793 
    794 	for (i = 0; i < gidcount; i++)
    795 		if (gid == gidlist[i])
    796 			return(1);
    797 	return (0);
    798 }
    799 
    800 static void
    801 mlsname(FILE *fp, factelem *fe)
    802 {
    803 	char realfile[MAXPATHLEN];
    804 	int userf = 0;
    805 	size_t i;
    806 
    807 	for (i = 0; i < FACTTABSIZE; i++) {
    808 		if (facttab[i].enabled)
    809 			(facttab[i].display)(facttab[i].name, fp, fe);
    810 	}
    811 	if ((fe->flags & FE_MLSD) &&
    812 	    !(fe->flags & FE_ISCURDIR) && !ISDOTDIR(fe->display)) {
    813 			/* if MLSD and not "." entry, display as-is */
    814 		userf = 0;
    815 	} else {
    816 			/* if MLST, or MLSD and "." entry, realpath(3) it */
    817 		if (realpath(fe->display, realfile) != NULL)
    818 			userf = 1;
    819 	}
    820 	cprintf(fp, " %s\r\n", userf ? realfile : fe->display);
    821 }
    822 
    823 static void
    824 replydirname(const char *name, const char *message)
    825 {
    826 	char *p, *ep;
    827 	char npath[MAXPATHLEN * 2];
    828 
    829 	p = npath;
    830 	ep = &npath[sizeof(npath) - 1];
    831 	while (*name) {
    832 		if (*name == '"') {
    833 			if (ep - p < 2)
    834 				break;
    835 			*p++ = *name++;
    836 			*p++ = '"';
    837 		} else {
    838 			if (ep - p < 1)
    839 				break;
    840 			*p++ = *name++;
    841 		}
    842 	}
    843 	*p = '\0';
    844 	reply(257, "\"%s\" %s", npath, message);
    845 }
    846 
    847 static void
    848 discover_path(char *last_path, const char *new_path)
    849 {
    850 	char tp[MAXPATHLEN + 1] = "";
    851 	char tq[MAXPATHLEN + 1] = "";
    852 	char *cp;
    853 	char *cq;
    854 	int sz1, sz2;
    855 	int nomorelink;
    856 	struct stat st1, st2;
    857 
    858 	if (new_path[0] != '/') {
    859 		(void)strlcpy(tp, last_path, MAXPATHLEN);
    860 		(void)strlcat(tp, "/", MAXPATHLEN);
    861 	}
    862 	(void)strlcat(tp, new_path, MAXPATHLEN);
    863 	(void)strlcat(tp, "/", MAXPATHLEN);
    864 
    865 	/*
    866 	 * resolve symlinks. A symlink may introduce another symlink, so we
    867 	 * loop trying to resolve symlinks until we don't find any of them.
    868 	 */
    869 	do {
    870 		/* Collapse any // into / */
    871 		while ((cp = strstr(tp, "//")) != NULL)
    872 			(void)memmove(cp, cp + 1, strlen(cp) - 1 + 1);
    873 
    874 		/* Collapse any /./ into / */
    875 		while ((cp = strstr(tp, "/./")) != NULL)
    876 			(void)memmove(cp, cp + 2, strlen(cp) - 2 + 1);
    877 
    878 		cp = tp;
    879 		nomorelink = 1;
    880 
    881 		while ((cp = strstr(cp + 1, "/")) != NULL) {
    882 			sz1 = (unsigned long)cp - (unsigned long)tp;
    883 			if (sz1 > MAXPATHLEN)
    884 				goto bad;
    885 			*cp = 0;
    886 			sz2 = readlink(tp, tq, MAXPATHLEN);
    887 			*cp = '/';
    888 
    889 			/* If this is not a symlink, move to next / */
    890 			if (sz2 <= 0)
    891 				continue;
    892 
    893 			/*
    894 			 * We found a symlink, so we will have to
    895 			 * do one more pass to check there is no
    896 			 * more symlink in the path
    897 			 */
    898 			nomorelink = 0;
    899 
    900 			/*
    901 			 * Null terminate the string and remove trailing /
    902 			 */
    903 			tq[sz2] = 0;
    904 			sz2 = strlen(tq);
    905 			if (tq[sz2 - 1] == '/')
    906 				tq[--sz2] = 0;
    907 
    908 			/*
    909 			 * Is this an absolute link or a relative link?
    910 			 */
    911 			if (tq[0] == '/') {
    912 				/* absolute link */
    913 				if (strlen(cp) + sz2 > MAXPATHLEN)
    914 					goto bad;
    915 				memmove(tp + sz2, cp, strlen(cp) + 1);
    916 				memcpy(tp, tq, sz2);
    917 			} else {
    918 				/* relative link */
    919 				for (cq = cp - 1; *cq != '/'; cq--);
    920 				if (strlen(tp) -
    921 				    ((unsigned long)cq - (unsigned long)cp)
    922 				    + 1 + sz2 > MAXPATHLEN)
    923 					goto bad;
    924 				(void)memmove(cq + 1 + sz2,
    925 				    cp, strlen(cp) + 1);
    926 				(void)memcpy(cq + 1, tq, sz2);
    927 			}
    928 
    929 			/*
    930 			 * start over, looking for new symlinks
    931 			 */
    932 			break;
    933 		}
    934 	} while (nomorelink == 0);
    935 
    936 	/* Collapse any /foo/../ into /foo/ */
    937 	while ((cp = strstr(tp, "/../")) != NULL) {
    938 		/* ^/../foo/ becomes ^/foo/ */
    939 		if (cp == tp) {
    940 			(void)memmove(cp, cp + 3,
    941 			    strlen(cp) - 3 + 1);
    942 		} else {
    943 			for (cq = cp - 1; *cq != '/'; cq--);
    944 			(void)memmove(cq, cp + 3,
    945 			    strlen(cp) - 3 + 1);
    946 		}
    947 	}
    948 
    949 	/* strip strailing / */
    950 	if (strlen(tp) != 1)
    951 		tp[strlen(tp) - 1] = '\0';
    952 
    953 	/* check that the path is correct */
    954 	if (stat(tp, &st1) == -1 || stat(".", &st2) == -1)
    955 		goto bad;
    956 	if ((st1.st_dev != st2.st_dev) || (st1.st_ino != st2.st_ino))
    957 		goto bad;
    958 
    959 	(void)strlcpy(last_path, tp, MAXPATHLEN);
    960 	return;
    961 
    962 bad:
    963 	(void)strlcat(last_path, "/", MAXPATHLEN);
    964 	(void)strlcat(last_path, new_path, MAXPATHLEN);
    965 	return;
    966 }
    967 
    968