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