cmds.c revision 108746
1/*	$NetBSD: cmds.c,v 1.18 2002/10/12 08:35:16 darrenr 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. All advertising materials mentioning features or use of this software
52 *    must display the following acknowledgement:
53 *	This product includes software developed by the University of
54 *	California, Berkeley and its contributors.
55 * 4. Neither the name of the University nor the names of its contributors
56 *    may be used to endorse or promote products derived from this software
57 *    without specific prior written permission.
58 *
59 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
60 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
61 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
62 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
63 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
64 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
65 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
66 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
67 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
68 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
69 * SUCH DAMAGE.
70 */
71
72/*
73 * Copyright (C) 1997 and 1998 WIDE Project.
74 * All rights reserved.
75 *
76 * Redistribution and use in source and binary forms, with or without
77 * modification, are permitted provided that the following conditions
78 * are met:
79 * 1. Redistributions of source code must retain the above copyright
80 *    notice, this list of conditions and the following disclaimer.
81 * 2. Redistributions in binary form must reproduce the above copyright
82 *    notice, this list of conditions and the following disclaimer in the
83 *    documentation and/or other materials provided with the distribution.
84 * 3. Neither the name of the project nor the names of its contributors
85 *    may be used to endorse or promote products derived from this software
86 *    without specific prior written permission.
87 *
88 * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``AS IS'' AND
89 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
90 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
91 * ARE DISCLAIMED.  IN NO EVENT SHALL THE PROJECT OR CONTRIBUTORS BE LIABLE
92 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
93 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
94 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
95 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
96 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
97 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
98 * SUCH DAMAGE.
99 */
100
101
102#include <sys/cdefs.h>
103#ifndef lint
104__RCSID("$NetBSD: cmds.c,v 1.18 2002/10/12 08:35:16 darrenr Exp $");
105#endif /* not lint */
106
107#include <sys/param.h>
108#include <sys/stat.h>
109
110#include <arpa/ftp.h>
111
112#include <dirent.h>
113#include <errno.h>
114#include <setjmp.h>
115#include <stdio.h>
116#include <stdlib.h>
117#include <string.h>
118#include <tzfile.h>
119#include <unistd.h>
120#include <ctype.h>
121
122#ifdef KERBEROS5
123#include <krb5/krb5.h>
124#endif
125
126#include "extern.h"
127
128typedef enum {
129	FE_MLSD		= 1<<0,		/* if op is MLSD (MLST otherwise ) */
130	FE_ISCURDIR	= 1<<1,		/* if name is the current directory */
131} factflag_t;
132
133typedef struct {
134	const char	*path;		/* full pathname */
135	const char	*display;	/* name to display */
136	struct stat	*stat;		/* stat of path */
137	struct stat	*pdirstat;	/* stat of path's parent dir */
138	factflag_t	 flags;		/* flags */
139} factelem;
140
141static void	ack(const char *);
142static void	base64_encode(const char *, size_t, char *, int);
143static void	fact_type(const char *, FILE *, factelem *);
144static void	fact_size(const char *, FILE *, factelem *);
145static void	fact_modify(const char *, FILE *, factelem *);
146static void	fact_perm(const char *, FILE *, factelem *);
147static void	fact_unique(const char *, FILE *, factelem *);
148static int	matchgroup(gid_t);
149static void	mlsname(FILE *, factelem *);
150static void	replydirname(const char *, const char *);
151
152struct ftpfact {
153	const char	 *name;		/* name of fact */
154	int		  enabled;	/* if fact is enabled */
155	void		(*display)(const char *, FILE *, factelem *);
156					/* function to display fact */
157};
158
159struct ftpfact facttab[] = {
160	{ "Type",	1, fact_type },
161#define	FACT_TYPE 0
162	{ "Size",	1, fact_size },
163	{ "Modify",	1, fact_modify },
164	{ "Perm",	1, fact_perm },
165	{ "Unique",	1, fact_unique },
166	/* "Create" */
167	/* "Lang" */
168	/* "Media-Type" */
169	/* "CharSet" */
170};
171
172#define FACTTABSIZE	(sizeof(facttab) / sizeof(struct ftpfact))
173
174
175void
176cwd(const char *path)
177{
178
179	if (chdir(path) < 0)
180		perror_reply(550, path);
181	else {
182		show_chdir_messages(250);
183		ack("CWD");
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, xstrdup(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		reply(550, "Can't get the current directory: %s.",
409		    strerror(errno));
410	else
411		replydirname(path, "is the current directory.");
412}
413
414void
415removedir(const char *name)
416{
417	char *p = NULL;
418
419	if (rmdir(name) < 0) {
420		p = strerror(errno);
421		perror_reply(550, name);
422	} else
423		ack("RMD");
424	logxfer("rmdir", -1, name, NULL, NULL, p);
425}
426
427char *
428renamefrom(const char *name)
429{
430	struct stat st;
431
432	if (stat(name, &st) < 0) {
433		perror_reply(550, name);
434		return (NULL);
435	}
436	reply(350, "File exists, ready for destination name");
437	return (xstrdup(name));
438}
439
440void
441renamecmd(const char *from, const char *to)
442{
443	char *p = NULL;
444
445	if (rename(from, to) < 0) {
446		p = strerror(errno);
447		perror_reply(550, "rename");
448	} else
449		ack("RNTO");
450	logxfer("rename", -1, from, to, NULL, p);
451}
452
453void
454sizecmd(const char *filename)
455{
456	switch (type) {
457	case TYPE_L:
458	case TYPE_I:
459	    {
460		struct stat stbuf;
461		if (stat(filename, &stbuf) < 0 || !S_ISREG(stbuf.st_mode))
462			reply(550, "%s: not a plain file.", filename);
463		else
464			reply(213, ULLF, (ULLT)stbuf.st_size);
465		break;
466	    }
467	case TYPE_A:
468	    {
469		FILE *fin;
470		int c;
471		off_t count;
472		struct stat stbuf;
473		fin = fopen(filename, "r");
474		if (fin == NULL) {
475			perror_reply(550, filename);
476			return;
477		}
478		if (fstat(fileno(fin), &stbuf) < 0 || !S_ISREG(stbuf.st_mode)) {
479			reply(550, "%s: not a plain file.", filename);
480			(void) fclose(fin);
481			return;
482		}
483		if (stbuf.st_size > 10240) {
484			reply(550, "%s: file too large for SIZE.", filename);
485			(void) fclose(fin);
486			return;
487		}
488
489		count = 0;
490		while((c = getc(fin)) != EOF) {
491			if (c == '\n')	/* will get expanded to \r\n */
492				count++;
493			count++;
494		}
495		(void) fclose(fin);
496
497		reply(213, LLF, (LLT)count);
498		break;
499	    }
500	default:
501		reply(504, "SIZE not implemented for Type %c.", "?AEIL"[type]);
502	}
503}
504
505void
506statfilecmd(const char *filename)
507{
508	FILE *fin;
509	int c;
510	int atstart;
511	char *argv[] = { INTERNAL_LS, "-lgA", "", NULL };
512
513	argv[2] = (char *)filename;
514	fin = ftpd_popen(argv, "r", STDOUT_FILENO);
515	reply(-211, "status of %s:", filename);
516/* XXX: use fgetln() or fparseln() here? */
517	atstart = 1;
518	while ((c = getc(fin)) != EOF) {
519		if (c == '\n') {
520			if (ferror(stdout)){
521				perror_reply(421, "control connection");
522				(void) ftpd_pclose(fin);
523				dologout(1);
524				/* NOTREACHED */
525			}
526			if (ferror(fin)) {
527				perror_reply(551, filename);
528				(void) ftpd_pclose(fin);
529				return;
530			}
531			CPUTC('\r', stdout);
532		}
533		if (atstart && isdigit(c))
534			CPUTC(' ', stdout);
535		CPUTC(c, stdout);
536		atstart = (c == '\n');
537	}
538	(void) ftpd_pclose(fin);
539	reply(211, "End of Status");
540}
541
542/* -- */
543
544static void
545ack(const char *s)
546{
547
548	reply(250, "%s command successful.", s);
549}
550
551/*
552 * Encode len bytes starting at clear using base64 encoding into encoded,
553 * which should be at least ((len + 2) * 4 / 3 + 1) in size.
554 * If nulterm is non-zero, terminate with \0 otherwise pad to 3 byte boundary
555 * with `='.
556 */
557static void
558base64_encode(const char *clear, size_t len, char *encoded, int nulterm)
559{
560	static const char base64[] =
561	    "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
562	const char *c;
563	char	*e, termchar;
564	int	 i;
565
566			/* determine whether to pad with '=' or NUL terminate */
567	termchar = nulterm ? '\0' : '=';
568	c = clear;
569	e = encoded;
570			/* convert all but last 2 bytes */
571	for (i = len; i > 2; i -= 3, c += 3) {
572		*e++ = base64[(c[0] >> 2) & 0x3f];
573		*e++ = base64[((c[0] << 4) & 0x30) | ((c[1] >> 4) & 0x0f)];
574		*e++ = base64[((c[1] << 2) & 0x3c) | ((c[2] >> 6) & 0x03)];
575		*e++ = base64[(c[2]) & 0x3f];
576	}
577			/* handle slop at end */
578	if (i > 0) {
579		*e++ = base64[(c[0] >> 2) & 0x3f];
580		*e++ = base64[((c[0] << 4) & 0x30) |
581		     (i > 1 ? ((c[1] >> 4) & 0x0f) : 0)];
582		*e++ = (i > 1) ? base64[(c[1] << 2) & 0x3c] : termchar;
583		*e++ = termchar;
584	}
585	*e = '\0';
586}
587
588static void
589fact_modify(const char *fact, FILE *fd, factelem *fe)
590{
591	struct tm *t;
592
593	t = gmtime(&(fe->stat->st_mtime));
594	cprintf(fd, "%s=%04d%02d%02d%02d%02d%02d;", fact,
595	    TM_YEAR_BASE + t->tm_year,
596	    t->tm_mon+1, t->tm_mday,
597	    t->tm_hour, t->tm_min, t->tm_sec);
598}
599
600static void
601fact_perm(const char *fact, FILE *fd, factelem *fe)
602{
603	int		rok, wok, xok, pdirwok;
604	struct stat	*pdir;
605
606	if (fe->stat->st_uid == geteuid()) {
607		rok = ((fe->stat->st_mode & S_IRUSR) != 0);
608		wok = ((fe->stat->st_mode & S_IWUSR) != 0);
609		xok = ((fe->stat->st_mode & S_IXUSR) != 0);
610	} else if (matchgroup(fe->stat->st_gid)) {
611		rok = ((fe->stat->st_mode & S_IRGRP) != 0);
612		wok = ((fe->stat->st_mode & S_IWGRP) != 0);
613		xok = ((fe->stat->st_mode & S_IXGRP) != 0);
614	} else {
615		rok = ((fe->stat->st_mode & S_IROTH) != 0);
616		wok = ((fe->stat->st_mode & S_IWOTH) != 0);
617		xok = ((fe->stat->st_mode & S_IXOTH) != 0);
618	}
619
620	cprintf(fd, "%s=", fact);
621
622			/*
623			 * if parent info not provided, look it up, but
624			 * only if the current class has modify rights,
625			 * since we only need this info in such a case.
626			 */
627	pdir = fe->pdirstat;
628	if (pdir == NULL && CURCLASS_FLAGS_ISSET(modify)) {
629		size_t		len;
630		char		realdir[MAXPATHLEN], *p;
631		struct stat	dir;
632
633		len = strlcpy(realdir, fe->path, sizeof(realdir));
634		if (len < sizeof(realdir) - 4) {
635			if (S_ISDIR(fe->stat->st_mode))
636				strlcat(realdir, "/..", sizeof(realdir));
637			else {
638					/* if has a /, move back to it */
639					/* otherwise use '..' */
640				if ((p = strrchr(realdir, '/')) != NULL) {
641					if (p == realdir)
642						p++;
643					*p = '\0';
644				} else
645					strlcpy(realdir, "..", sizeof(realdir));
646			}
647			if (stat(realdir, &dir) == 0)
648				pdir = &dir;
649		}
650	}
651	pdirwok = 0;
652	if (pdir != NULL) {
653		if (pdir->st_uid == geteuid())
654			pdirwok = ((pdir->st_mode & S_IWUSR) != 0);
655		else if (matchgroup(pdir->st_gid))
656			pdirwok = ((pdir->st_mode & S_IWGRP) != 0);
657		else
658			pdirwok = ((pdir->st_mode & S_IWOTH) != 0);
659	}
660
661			/* 'a': can APPE to file */
662	if (wok && CURCLASS_FLAGS_ISSET(upload) && S_ISREG(fe->stat->st_mode))
663		CPUTC('a', fd);
664
665			/* 'c': can create or append to files in directory */
666	if (wok && CURCLASS_FLAGS_ISSET(modify) && S_ISDIR(fe->stat->st_mode))
667		CPUTC('c', fd);
668
669			/* 'd': can delete file or directory */
670	if (pdirwok && CURCLASS_FLAGS_ISSET(modify)) {
671		int candel;
672
673		candel = 1;
674		if (S_ISDIR(fe->stat->st_mode)) {
675			DIR *dirp;
676			struct dirent *dp;
677
678			if ((dirp = opendir(fe->display)) == NULL)
679				candel = 0;
680			else {
681				while ((dp = readdir(dirp)) != NULL) {
682					if (ISDOTDIR(dp->d_name) ||
683					    ISDOTDOTDIR(dp->d_name))
684						continue;
685					candel = 0;
686					break;
687				}
688				closedir(dirp);
689			}
690		}
691		if (candel)
692			CPUTC('d', fd);
693	}
694
695			/* 'e': can enter directory */
696	if (xok && S_ISDIR(fe->stat->st_mode))
697		CPUTC('e', fd);
698
699			/* 'f': can rename file or directory */
700	if (pdirwok && CURCLASS_FLAGS_ISSET(modify))
701		CPUTC('f', fd);
702
703			/* 'l': can list directory */
704	if (rok && xok && S_ISDIR(fe->stat->st_mode))
705		CPUTC('l', fd);
706
707			/* 'm': can create directory */
708	if (wok && CURCLASS_FLAGS_ISSET(modify) && S_ISDIR(fe->stat->st_mode))
709		CPUTC('m', fd);
710
711			/* 'p': can remove files in directory */
712	if (wok && CURCLASS_FLAGS_ISSET(modify) && S_ISDIR(fe->stat->st_mode))
713		CPUTC('p', fd);
714
715			/* 'r': can RETR file */
716	if (rok && S_ISREG(fe->stat->st_mode))
717		CPUTC('r', fd);
718
719			/* 'w': can STOR file */
720	if (wok && CURCLASS_FLAGS_ISSET(upload) && S_ISREG(fe->stat->st_mode))
721		CPUTC('w', fd);
722
723	CPUTC(';', fd);
724}
725
726static void
727fact_size(const char *fact, FILE *fd, factelem *fe)
728{
729
730	if (S_ISREG(fe->stat->st_mode))
731		cprintf(fd, "%s=" LLF ";", fact, (LLT)fe->stat->st_size);
732}
733
734static void
735fact_type(const char *fact, FILE *fd, factelem *fe)
736{
737
738	cprintf(fd, "%s=", fact);
739	switch (fe->stat->st_mode & S_IFMT) {
740	case S_IFDIR:
741		if (fe->flags & FE_MLSD) {
742			if ((fe->flags & FE_ISCURDIR) || ISDOTDIR(fe->display))
743				cprintf(fd, "cdir");
744			else if (ISDOTDOTDIR(fe->display))
745				cprintf(fd, "pdir");
746			else
747				cprintf(fd, "dir");
748		} else {
749			cprintf(fd, "dir");
750		}
751		break;
752	case S_IFREG:
753		cprintf(fd, "file");
754		break;
755	case S_IFIFO:
756		cprintf(fd, "OS.unix=fifo");
757		break;
758	case S_IFLNK:		/* XXX: probably a NO-OP with stat() */
759		cprintf(fd, "OS.unix=slink");
760		break;
761	case S_IFSOCK:
762		cprintf(fd, "OS.unix=socket");
763		break;
764	case S_IFBLK:
765	case S_IFCHR:
766		cprintf(fd, "OS.unix=%s-%d/%d",
767		    S_ISBLK(fe->stat->st_mode) ? "blk" : "chr",
768		    major(fe->stat->st_rdev), 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
777static void
778fact_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
791static int
792matchgroup(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
802static void
803mlsname(FILE *fp, factelem *fe)
804{
805	char realfile[MAXPATHLEN];
806	int i, userf;
807
808	for (i = 0; i < FACTTABSIZE; i++) {
809		if (facttab[i].enabled)
810			(facttab[i].display)(facttab[i].name, fp, fe);
811	}
812	if ((fe->flags & FE_MLSD) &&
813	    !(fe->flags & FE_ISCURDIR) && !ISDOTDIR(fe->display)) {
814			/* if MLSD and not "." entry, display as-is */
815		userf = 0;
816	} else {
817			/* if MLST, or MLSD and "." entry, realpath(3) it */
818		if (realpath(fe->display, realfile) != NULL)
819			userf = 1;
820	}
821	cprintf(fp, " %s\r\n", userf ? realfile : fe->display);
822}
823
824static void
825replydirname(const char *name, const char *message)
826{
827	char *p, *ep;
828	char npath[MAXPATHLEN * 2];
829
830	p = npath;
831	ep = &npath[sizeof(npath) - 1];
832	while (*name) {
833		if (*name == '"') {
834			if (ep - p < 2)
835				break;
836			*p++ = *name++;
837			*p++ = '"';
838		} else {
839			if (ep - p < 1)
840				break;
841			*p++ = *name++;
842		}
843	}
844	*p = '\0';
845	reply(257, "\"%s\" %s", npath, message);
846}
847