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