cmds.c revision 92282
192282Sobrien/*	$NetBSD: cmds.c,v 1.16 2002/02/13 15:15:23 lukem Exp $	*/
279968Sobrien
379968Sobrien/*
479968Sobrien * Copyright (c) 1999-2001 The NetBSD Foundation, Inc.
579968Sobrien * All rights reserved.
679968Sobrien *
779968Sobrien * This code is derived from software contributed to The NetBSD Foundation
879968Sobrien * by Luke Mewburn.
979968Sobrien *
1079968Sobrien * Redistribution and use in source and binary forms, with or without
1179968Sobrien * modification, are permitted provided that the following conditions
1279968Sobrien * are met:
1379968Sobrien * 1. Redistributions of source code must retain the above copyright
1479968Sobrien *    notice, this list of conditions and the following disclaimer.
1579968Sobrien * 2. Redistributions in binary form must reproduce the above copyright
1679968Sobrien *    notice, this list of conditions and the following disclaimer in the
1779968Sobrien *    documentation and/or other materials provided with the distribution.
1879968Sobrien * 3. All advertising materials mentioning features or use of this software
1979968Sobrien *    must display the following acknowledgement:
2079968Sobrien *        This product includes software developed by the NetBSD
2179968Sobrien *        Foundation, Inc. and its contributors.
2279968Sobrien * 4. Neither the name of The NetBSD Foundation nor the names of its
2379968Sobrien *    contributors may be used to endorse or promote products derived
2479968Sobrien *    from this software without specific prior written permission.
2579968Sobrien *
2679968Sobrien * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
2779968Sobrien * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
2879968Sobrien * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
2979968Sobrien * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
3079968Sobrien * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
3179968Sobrien * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
3279968Sobrien * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
3379968Sobrien * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
3479968Sobrien * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
3579968Sobrien * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
3679968Sobrien * POSSIBILITY OF SUCH DAMAGE.
3779968Sobrien */
3879968Sobrien
3979968Sobrien/*
4079968Sobrien * Copyright (c) 1985, 1988, 1990, 1992, 1993, 1994
4179968Sobrien *	The Regents of the University of California.  All rights reserved.
4279968Sobrien *
4379968Sobrien * Redistribution and use in source and binary forms, with or without
4479968Sobrien * modification, are permitted provided that the following conditions
4579968Sobrien * are met:
4679968Sobrien * 1. Redistributions of source code must retain the above copyright
4779968Sobrien *    notice, this list of conditions and the following disclaimer.
4879968Sobrien * 2. Redistributions in binary form must reproduce the above copyright
4979968Sobrien *    notice, this list of conditions and the following disclaimer in the
5079968Sobrien *    documentation and/or other materials provided with the distribution.
5179968Sobrien * 3. All advertising materials mentioning features or use of this software
5279968Sobrien *    must display the following acknowledgement:
5379968Sobrien *	This product includes software developed by the University of
5479968Sobrien *	California, Berkeley and its contributors.
5579968Sobrien * 4. Neither the name of the University nor the names of its contributors
5679968Sobrien *    may be used to endorse or promote products derived from this software
5779968Sobrien *    without specific prior written permission.
5879968Sobrien *
5979968Sobrien * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
6079968Sobrien * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
6179968Sobrien * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
6279968Sobrien * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
6379968Sobrien * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
6479968Sobrien * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
6579968Sobrien * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
6679968Sobrien * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
6779968Sobrien * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
6879968Sobrien * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
6979968Sobrien * SUCH DAMAGE.
7079968Sobrien */
7179968Sobrien
7279968Sobrien/*
7379968Sobrien * Copyright (C) 1997 and 1998 WIDE Project.
7479968Sobrien * All rights reserved.
7579968Sobrien *
7679968Sobrien * Redistribution and use in source and binary forms, with or without
7779968Sobrien * modification, are permitted provided that the following conditions
7879968Sobrien * are met:
7979968Sobrien * 1. Redistributions of source code must retain the above copyright
8079968Sobrien *    notice, this list of conditions and the following disclaimer.
8179968Sobrien * 2. Redistributions in binary form must reproduce the above copyright
8279968Sobrien *    notice, this list of conditions and the following disclaimer in the
8379968Sobrien *    documentation and/or other materials provided with the distribution.
8479968Sobrien * 3. Neither the name of the project nor the names of its contributors
8579968Sobrien *    may be used to endorse or promote products derived from this software
8679968Sobrien *    without specific prior written permission.
8779968Sobrien *
8879968Sobrien * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``AS IS'' AND
8979968Sobrien * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
9079968Sobrien * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
9179968Sobrien * ARE DISCLAIMED.  IN NO EVENT SHALL THE PROJECT OR CONTRIBUTORS BE LIABLE
9279968Sobrien * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
9379968Sobrien * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
9479968Sobrien * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
9579968Sobrien * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
9679968Sobrien * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
9779968Sobrien * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
9879968Sobrien * SUCH DAMAGE.
9979968Sobrien */
10079968Sobrien
10179968Sobrien#include "lukemftpd.h"
10279968Sobrien
10379968Sobrien#include "extern.h"
10479968Sobrien
10592282Sobrientypedef enum {
10692282Sobrien	FE_MLSD		= 1<<0,		/* if op is MLSD (MLST otherwise ) */
10792282Sobrien	FE_ISCURDIR	= 1<<1,		/* if name is the current directory */
10892282Sobrien} factflag_t;
10992282Sobrien
11079968Sobrientypedef struct {
11179968Sobrien	const char	*path;		/* full pathname */
11279968Sobrien	const char	*display;	/* name to display */
11379968Sobrien	struct stat	*stat;		/* stat of path */
11479968Sobrien	struct stat	*pdirstat;	/* stat of path's parent dir */
11592282Sobrien	factflag_t	 flags;		/* flags */
11679968Sobrien} factelem;
11779968Sobrien
11879968Sobrienstatic void	ack(const char *);
11979968Sobrienstatic void	base64_encode(const char *, size_t, char *, int);
12079968Sobrienstatic void	fact_type(const char *, FILE *, factelem *);
12179968Sobrienstatic void	fact_size(const char *, FILE *, factelem *);
12279968Sobrienstatic void	fact_modify(const char *, FILE *, factelem *);
12379968Sobrienstatic void	fact_perm(const char *, FILE *, factelem *);
12479968Sobrienstatic void	fact_unique(const char *, FILE *, factelem *);
12579968Sobrienstatic int	matchgroup(gid_t);
12679968Sobrienstatic void	mlsname(FILE *, factelem *);
12779968Sobrienstatic void	replydirname(const char *, const char *);
12879968Sobrien
12979968Sobrienstruct ftpfact {
13079968Sobrien	const char	 *name;		/* name of fact */
13179968Sobrien	int		  enabled;	/* if fact is enabled */
13279968Sobrien	void		(*display)(const char *, FILE *, factelem *);
13379968Sobrien					/* function to display fact */
13479968Sobrien};
13579968Sobrien
13679968Sobrienstruct ftpfact facttab[] = {
13779968Sobrien	{ "Type",	1, fact_type },
13879968Sobrien#define	FACT_TYPE 0
13979968Sobrien	{ "Size",	1, fact_size },
14079968Sobrien	{ "Modify",	1, fact_modify },
14179968Sobrien	{ "Perm",	1, fact_perm },
14279968Sobrien	{ "Unique",	1, fact_unique },
14379968Sobrien	/* "Create" */
14479968Sobrien	/* "Lang" */
14579968Sobrien	/* "Media-Type" */
14679968Sobrien	/* "CharSet" */
14779968Sobrien};
14879968Sobrien
14979968Sobrien#define FACTTABSIZE	(sizeof(facttab) / sizeof(struct ftpfact))
15079968Sobrien
15179968Sobrien
15279968Sobrienvoid
15379968Sobriencwd(const char *path)
15479968Sobrien{
15579968Sobrien
15679968Sobrien	if (chdir(path) < 0)
15779968Sobrien		perror_reply(550, path);
15879968Sobrien	else {
15979968Sobrien		show_chdir_messages(250);
16079968Sobrien		ack("CWD");
16179968Sobrien	}
16279968Sobrien}
16379968Sobrien
16479968Sobrienvoid
16579968Sobriendelete(const char *name)
16679968Sobrien{
16779968Sobrien	char *p = NULL;
16879968Sobrien
16979968Sobrien	if (remove(name) < 0) {
17079968Sobrien		p = strerror(errno);
17179968Sobrien		perror_reply(550, name);
17279968Sobrien	} else
17379968Sobrien		ack("DELE");
17479968Sobrien	logxfer("delete", -1, name, NULL, NULL, p);
17579968Sobrien}
17679968Sobrien
17779968Sobrienvoid
17879968Sobrienfeat(void)
17979968Sobrien{
18079968Sobrien	int i;
18179968Sobrien
18279968Sobrien	reply(-211, "Features supported");
18379968Sobrien	cprintf(stdout, " MDTM\r\n");
18479968Sobrien	cprintf(stdout, " MLST ");
18579968Sobrien	for (i = 0; i < FACTTABSIZE; i++)
18679968Sobrien		cprintf(stdout, "%s%s;", facttab[i].name,
18779968Sobrien		    facttab[i].enabled ? "*" : "");
18879968Sobrien	cprintf(stdout, "\r\n");
18979968Sobrien	cprintf(stdout, " REST STREAM\r\n");
19079968Sobrien	cprintf(stdout, " SIZE\r\n");
19179968Sobrien	cprintf(stdout, " TVFS\r\n");
19279968Sobrien	reply(211,  "End");
19379968Sobrien}
19479968Sobrien
19579968Sobrienvoid
19679968Sobrienmakedir(const char *name)
19779968Sobrien{
19879968Sobrien	char *p = NULL;
19979968Sobrien
20079968Sobrien	if (mkdir(name, 0777) < 0) {
20179968Sobrien		p = strerror(errno);
20279968Sobrien		perror_reply(550, name);
20379968Sobrien	} else
20479968Sobrien		replydirname(name, "directory created.");
20579968Sobrien	logxfer("mkdir", -1, name, NULL, NULL, p);
20679968Sobrien}
20779968Sobrien
20879968Sobrienvoid
20979968Sobrienmlsd(const char *path)
21079968Sobrien{
21179968Sobrien	struct dirent	*dp;
21279968Sobrien	struct stat	 sb, pdirstat;
21379968Sobrien	factelem f;
21479968Sobrien	FILE	*dout;
21579968Sobrien	DIR	*dirp;
21679968Sobrien	char	name[MAXPATHLEN];
21779968Sobrien	int	hastypefact;
21879968Sobrien
21979968Sobrien	hastypefact = facttab[FACT_TYPE].enabled;
22079968Sobrien	if (path == NULL)
22179968Sobrien		path = ".";
22279968Sobrien	if (stat(path, &pdirstat) == -1) {
22379968Sobrien mlsdperror:
22479968Sobrien		perror_reply(550, path);
22579968Sobrien		return;
22679968Sobrien	}
22779968Sobrien	if (! S_ISDIR(pdirstat.st_mode)) {
22879968Sobrien		errno = ENOTDIR;
22979968Sobrien		perror_reply(501, path);
23079968Sobrien		return;
23179968Sobrien	}
23292282Sobrien	if ((dirp = opendir(path)) == NULL)
23392282Sobrien		goto mlsdperror;
23492282Sobrien
23579968Sobrien	dout = dataconn("MLSD", (off_t)-1, "w");
23679968Sobrien	if (dout == NULL)
23779968Sobrien		return;
23879968Sobrien
23992282Sobrien	memset(&f, 0, sizeof(f));
24079968Sobrien	f.stat = &sb;
24192282Sobrien	f.flags |= FE_MLSD;
24279968Sobrien	while ((dp = readdir(dirp)) != NULL) {
24379968Sobrien		snprintf(name, sizeof(name), "%s/%s", path, dp->d_name);
24479968Sobrien		if (ISDOTDIR(dp->d_name)) {	/* special case curdir: */
24579968Sobrien			if (! hastypefact)
24679968Sobrien				continue;
24779968Sobrien			f.pdirstat = NULL;	/*   require stat of parent */
24879968Sobrien			f.display = path;	/*   set name to real name */
24992282Sobrien			f.flags |= FE_ISCURDIR; /*   flag name is curdir */
25079968Sobrien		} else {
25179968Sobrien			if (ISDOTDOTDIR(dp->d_name)) {
25279968Sobrien				if (! hastypefact)
25379968Sobrien					continue;
25479968Sobrien				f.pdirstat = NULL;
25579968Sobrien			} else
25679968Sobrien				f.pdirstat = &pdirstat;	/* cache parent stat */
25779968Sobrien			f.display = dp->d_name;
25892282Sobrien			f.flags &= ~FE_ISCURDIR;
25979968Sobrien		}
26079968Sobrien		if (stat(name, &sb) == -1)
26179968Sobrien			continue;
26279968Sobrien		f.path = name;
26379968Sobrien		mlsname(dout, &f);
26479968Sobrien	}
26579968Sobrien	(void)closedir(dirp);
26679968Sobrien
26779968Sobrien	if (ferror(dout) != 0)
26879968Sobrien		perror_reply(550, "Data connection");
26979968Sobrien	else
27079968Sobrien		reply(226, "MLSD complete.");
27179968Sobrien	closedataconn(dout);
27279968Sobrien	total_xfers_out++;
27379968Sobrien	total_xfers++;
27479968Sobrien}
27579968Sobrien
27679968Sobrienvoid
27779968Sobrienmlst(const char *path)
27879968Sobrien{
27979968Sobrien	struct stat sb;
28079968Sobrien	factelem f;
28179968Sobrien
28279968Sobrien	if (path == NULL)
28379968Sobrien		path = ".";
28479968Sobrien	if (stat(path, &sb) == -1) {
28579968Sobrien		perror_reply(550, path);
28679968Sobrien		return;
28779968Sobrien	}
28879968Sobrien	reply(-250, "MLST %s", path);
28992282Sobrien	memset(&f, 0, sizeof(f));
29079968Sobrien	f.path = path;
29179968Sobrien	f.display = path;
29279968Sobrien	f.stat = &sb;
29379968Sobrien	f.pdirstat = NULL;
29479968Sobrien	CPUTC(' ', stdout);
29579968Sobrien	mlsname(stdout, &f);
29679968Sobrien	reply(250, "End");
29779968Sobrien}
29879968Sobrien
29979968Sobrien
30079968Sobrienvoid
30179968Sobrienopts(const char *command)
30279968Sobrien{
30379968Sobrien	struct tab *c;
30479968Sobrien	char *ep;
30579968Sobrien
30679968Sobrien	if ((ep = strchr(command, ' ')) != NULL)
30779968Sobrien		*ep++ = '\0';
30879968Sobrien	c = lookup(cmdtab, command);
30979968Sobrien	if (c == NULL) {
31079968Sobrien		reply(502, "Unknown command %s.", command);
31179968Sobrien		return;
31279968Sobrien	}
31379968Sobrien	if (! CMD_IMPLEMENTED(c)) {
31479968Sobrien		reply(501, "%s command not implemented.", c->name);
31579968Sobrien		return;
31679968Sobrien	}
31779968Sobrien	if (! CMD_HAS_OPTIONS(c)) {
31879968Sobrien		reply(501, "%s command does not support persistent options.",
31979968Sobrien		    c->name);
32079968Sobrien		return;
32179968Sobrien	}
32279968Sobrien
32379968Sobrien			/* special case: MLST */
32479968Sobrien	if (strcasecmp(command, "MLST") == 0) {
32579968Sobrien		int	 enabled[FACTTABSIZE];
32679968Sobrien		int	 i, onedone;
32779968Sobrien		size_t	 len;
32879968Sobrien		char	*p;
32979968Sobrien
33079968Sobrien		for (i = 0; i < sizeof(enabled) / sizeof(int); i++)
33179968Sobrien			enabled[i] = 0;
33279968Sobrien		if (ep == NULL || *ep == '\0')
33379968Sobrien			goto displaymlstopts;
33479968Sobrien
33579968Sobrien				/* don't like spaces, and need trailing ; */
33679968Sobrien		len = strlen(ep);
33779968Sobrien		if (strchr(ep, ' ') != NULL || ep[len - 1] != ';') {
33879968Sobrien badmlstopt:
33979968Sobrien			reply(501, "Invalid MLST options");
34079968Sobrien			return;
34179968Sobrien		}
34279968Sobrien		ep[len - 1] = '\0';
34379968Sobrien		while ((p = strsep(&ep, ";")) != NULL) {
34479968Sobrien			if (*p == '\0')
34579968Sobrien				goto badmlstopt;
34679968Sobrien			for (i = 0; i < FACTTABSIZE; i++)
34779968Sobrien				if (strcasecmp(p, facttab[i].name) == 0) {
34879968Sobrien					enabled[i] = 1;
34979968Sobrien					break;
35079968Sobrien				}
35179968Sobrien		}
35279968Sobrien
35379968Sobrien displaymlstopts:
35479968Sobrien		for (i = 0; i < FACTTABSIZE; i++)
35579968Sobrien			facttab[i].enabled = enabled[i];
35679968Sobrien		cprintf(stdout, "200 MLST OPTS");
35779968Sobrien		for (i = onedone = 0; i < FACTTABSIZE; i++) {
35879968Sobrien			if (facttab[i].enabled) {
35979968Sobrien				cprintf(stdout, "%s%s;", onedone ? "" : " ",
36079968Sobrien				    facttab[i].name);
36179968Sobrien				onedone++;
36279968Sobrien			}
36379968Sobrien		}
36479968Sobrien		cprintf(stdout, "\r\n");
36579968Sobrien		fflush(stdout);
36679968Sobrien		return;
36779968Sobrien	}
36879968Sobrien
36979968Sobrien			/* default cases */
37079968Sobrien	if (ep != NULL && *ep != '\0')
37179968Sobrien		REASSIGN(c->options, xstrdup(ep));
37279968Sobrien	if (c->options != NULL)
37379968Sobrien		reply(200, "Options for %s are '%s'.", c->name,
37479968Sobrien		    c->options);
37579968Sobrien	else
37679968Sobrien		reply(200, "No options defined for %s.", c->name);
37779968Sobrien}
37879968Sobrien
37979968Sobrienvoid
38079968Sobrienpwd(void)
38179968Sobrien{
38279968Sobrien	char path[MAXPATHLEN];
38379968Sobrien
38479968Sobrien	if (getcwd(path, sizeof(path) - 1) == NULL)
38579968Sobrien		reply(550, "Can't get the current directory: %s.",
38679968Sobrien		    strerror(errno));
38779968Sobrien	else
38879968Sobrien		replydirname(path, "is the current directory.");
38979968Sobrien}
39079968Sobrien
39179968Sobrienvoid
39279968Sobrienremovedir(const char *name)
39379968Sobrien{
39479968Sobrien	char *p = NULL;
39579968Sobrien
39679968Sobrien	if (rmdir(name) < 0) {
39779968Sobrien		p = strerror(errno);
39879968Sobrien		perror_reply(550, name);
39979968Sobrien	} else
40079968Sobrien		ack("RMD");
40179968Sobrien	logxfer("rmdir", -1, name, NULL, NULL, p);
40279968Sobrien}
40379968Sobrien
40479968Sobrienchar *
40579968Sobrienrenamefrom(const char *name)
40679968Sobrien{
40779968Sobrien	struct stat st;
40879968Sobrien
40979968Sobrien	if (stat(name, &st) < 0) {
41079968Sobrien		perror_reply(550, name);
41179968Sobrien		return (NULL);
41279968Sobrien	}
41379968Sobrien	reply(350, "File exists, ready for destination name");
41479968Sobrien	return (xstrdup(name));
41579968Sobrien}
41679968Sobrien
41779968Sobrienvoid
41879968Sobrienrenamecmd(const char *from, const char *to)
41979968Sobrien{
42079968Sobrien	char *p = NULL;
42179968Sobrien
42279968Sobrien	if (rename(from, to) < 0) {
42379968Sobrien		p = strerror(errno);
42479968Sobrien		perror_reply(550, "rename");
42579968Sobrien	} else
42679968Sobrien		ack("RNTO");
42779968Sobrien	logxfer("rename", -1, from, to, NULL, p);
42879968Sobrien}
42979968Sobrien
43079968Sobrienvoid
43179968Sobriensizecmd(const char *filename)
43279968Sobrien{
43379968Sobrien	switch (type) {
43479968Sobrien	case TYPE_L:
43579968Sobrien	case TYPE_I:
43679968Sobrien	    {
43779968Sobrien		struct stat stbuf;
43879968Sobrien		if (stat(filename, &stbuf) < 0 || !S_ISREG(stbuf.st_mode))
43979968Sobrien			reply(550, "%s: not a plain file.", filename);
44079968Sobrien		else
44179968Sobrien			reply(213, ULLF, (ULLT)stbuf.st_size);
44279968Sobrien		break;
44379968Sobrien	    }
44479968Sobrien	case TYPE_A:
44579968Sobrien	    {
44679968Sobrien		FILE *fin;
44779968Sobrien		int c;
44879968Sobrien		off_t count;
44979968Sobrien		struct stat stbuf;
45079968Sobrien		fin = fopen(filename, "r");
45179968Sobrien		if (fin == NULL) {
45279968Sobrien			perror_reply(550, filename);
45379968Sobrien			return;
45479968Sobrien		}
45579968Sobrien		if (fstat(fileno(fin), &stbuf) < 0 || !S_ISREG(stbuf.st_mode)) {
45679968Sobrien			reply(550, "%s: not a plain file.", filename);
45779968Sobrien			(void) fclose(fin);
45879968Sobrien			return;
45979968Sobrien		}
46092282Sobrien		if (stbuf.st_size > 10240) {
46192282Sobrien			reply(550, "%s: file too large for SIZE.", filename);
46292282Sobrien			(void) fclose(fin);
46392282Sobrien			return;
46492282Sobrien		}
46579968Sobrien
46679968Sobrien		count = 0;
46792282Sobrien		while((c = getc(fin)) != EOF) {
46879968Sobrien			if (c == '\n')	/* will get expanded to \r\n */
46979968Sobrien				count++;
47079968Sobrien			count++;
47179968Sobrien		}
47279968Sobrien		(void) fclose(fin);
47379968Sobrien
47479968Sobrien		reply(213, LLF, (LLT)count);
47579968Sobrien		break;
47679968Sobrien	    }
47779968Sobrien	default:
47879968Sobrien		reply(504, "SIZE not implemented for Type %c.", "?AEIL"[type]);
47979968Sobrien	}
48079968Sobrien}
48179968Sobrien
48279968Sobrienvoid
48379968Sobrienstatfilecmd(const char *filename)
48479968Sobrien{
48579968Sobrien	FILE *fin;
48679968Sobrien	int c;
48779968Sobrien	char *argv[] = { INTERNAL_LS, "-lgA", "", NULL };
48879968Sobrien
48979968Sobrien	argv[2] = (char *)filename;
49079968Sobrien	fin = ftpd_popen(argv, "r", STDOUT_FILENO);
49179968Sobrien	reply(-211, "status of %s:", filename);
49279968Sobrien/* XXX: use fgetln() or fparseln() here? */
49379968Sobrien	while ((c = getc(fin)) != EOF) {
49479968Sobrien		if (c == '\n') {
49579968Sobrien			if (ferror(stdout)){
49679968Sobrien				perror_reply(421, "control connection");
49779968Sobrien				(void) ftpd_pclose(fin);
49879968Sobrien				dologout(1);
49979968Sobrien				/* NOTREACHED */
50079968Sobrien			}
50179968Sobrien			if (ferror(fin)) {
50279968Sobrien				perror_reply(551, filename);
50379968Sobrien				(void) ftpd_pclose(fin);
50479968Sobrien				return;
50579968Sobrien			}
50679968Sobrien			CPUTC('\r', stdout);
50779968Sobrien		}
50879968Sobrien		CPUTC(c, stdout);
50979968Sobrien	}
51079968Sobrien	(void) ftpd_pclose(fin);
51179968Sobrien	reply(211, "End of Status");
51279968Sobrien}
51379968Sobrien
51479968Sobrien/* -- */
51579968Sobrien
51679968Sobrienstatic void
51779968Sobrienack(const char *s)
51879968Sobrien{
51979968Sobrien
52079968Sobrien	reply(250, "%s command successful.", s);
52179968Sobrien}
52279968Sobrien
52379968Sobrien/*
52479968Sobrien * Encode len bytes starting at clear using base64 encoding into encoded,
52579968Sobrien * which should be at least ((len + 2) * 4 / 3 + 1) in size.
52679968Sobrien * If nulterm is non-zero, terminate with \0 otherwise pad to 3 byte boundary
52779968Sobrien * with `='.
52879968Sobrien */
52979968Sobrienstatic void
53079968Sobrienbase64_encode(const char *clear, size_t len, char *encoded, int nulterm)
53179968Sobrien{
53279968Sobrien	static const char base64[] =
53379968Sobrien	    "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
53479968Sobrien	const char *c;
53579968Sobrien	char	*e, termchar;
53679968Sobrien	int	 i;
53779968Sobrien
53879968Sobrien			/* determine whether to pad with '=' or NUL terminate */
53979968Sobrien	termchar = nulterm ? '\0' : '=';
54079968Sobrien	c = clear;
54179968Sobrien	e = encoded;
54279968Sobrien			/* convert all but last 2 bytes */
54379968Sobrien	for (i = len; i > 2; i -= 3, c += 3) {
54479968Sobrien		*e++ = base64[(c[0] >> 2) & 0x3f];
54579968Sobrien		*e++ = base64[((c[0] << 4) & 0x30) | ((c[1] >> 4) & 0x0f)];
54679968Sobrien		*e++ = base64[((c[1] << 2) & 0x3c) | ((c[2] >> 6) & 0x03)];
54779968Sobrien		*e++ = base64[(c[2]) & 0x3f];
54879968Sobrien	}
54979968Sobrien			/* handle slop at end */
55079968Sobrien	if (i > 0) {
55179968Sobrien		*e++ = base64[(c[0] >> 2) & 0x3f];
55279968Sobrien		*e++ = base64[((c[0] << 4) & 0x30) |
55379968Sobrien		     (i > 1 ? ((c[1] >> 4) & 0x0f) : 0)];
55479968Sobrien		*e++ = (i > 1) ? base64[(c[1] << 2) & 0x3c] : termchar;
55579968Sobrien		*e++ = termchar;
55679968Sobrien	}
55779968Sobrien	*e = '\0';
55879968Sobrien}
55979968Sobrien
56079968Sobrienstatic void
56179968Sobrienfact_modify(const char *fact, FILE *fd, factelem *fe)
56279968Sobrien{
56379968Sobrien	struct tm *t;
56479968Sobrien
56579968Sobrien	t = gmtime(&(fe->stat->st_mtime));
56679968Sobrien	cprintf(fd, "%s=%04d%02d%02d%02d%02d%02d;", fact,
56779968Sobrien	    TM_YEAR_BASE + t->tm_year,
56879968Sobrien	    t->tm_mon+1, t->tm_mday,
56979968Sobrien	    t->tm_hour, t->tm_min, t->tm_sec);
57079968Sobrien}
57179968Sobrien
57279968Sobrienstatic void
57379968Sobrienfact_perm(const char *fact, FILE *fd, factelem *fe)
57479968Sobrien{
57579968Sobrien	int		rok, wok, xok, pdirwok;
57679968Sobrien	struct stat	*pdir;
57779968Sobrien
57879968Sobrien	if (fe->stat->st_uid == geteuid()) {
57979968Sobrien		rok = ((fe->stat->st_mode & S_IRUSR) != 0);
58079968Sobrien		wok = ((fe->stat->st_mode & S_IWUSR) != 0);
58179968Sobrien		xok = ((fe->stat->st_mode & S_IXUSR) != 0);
58279968Sobrien	} else if (matchgroup(fe->stat->st_gid)) {
58379968Sobrien		rok = ((fe->stat->st_mode & S_IRGRP) != 0);
58479968Sobrien		wok = ((fe->stat->st_mode & S_IWGRP) != 0);
58579968Sobrien		xok = ((fe->stat->st_mode & S_IXGRP) != 0);
58679968Sobrien	} else {
58779968Sobrien		rok = ((fe->stat->st_mode & S_IROTH) != 0);
58879968Sobrien		wok = ((fe->stat->st_mode & S_IWOTH) != 0);
58979968Sobrien		xok = ((fe->stat->st_mode & S_IXOTH) != 0);
59079968Sobrien	}
59179968Sobrien
59279968Sobrien	cprintf(fd, "%s=", fact);
59379968Sobrien
59479968Sobrien			/*
59579968Sobrien			 * if parent info not provided, look it up, but
59679968Sobrien			 * only if the current class has modify rights,
59779968Sobrien			 * since we only need this info in such a case.
59879968Sobrien			 */
59979968Sobrien	pdir = fe->pdirstat;
60079968Sobrien	if (pdir == NULL && CURCLASS_FLAGS_ISSET(modify)) {
60179968Sobrien		size_t		len;
60279968Sobrien		char		realdir[MAXPATHLEN], *p;
60379968Sobrien		struct stat	dir;
60479968Sobrien
60579968Sobrien		len = strlcpy(realdir, fe->path, sizeof(realdir));
60679968Sobrien		if (len < sizeof(realdir) - 4) {
60779968Sobrien			if (S_ISDIR(fe->stat->st_mode))
60879968Sobrien				strlcat(realdir, "/..", sizeof(realdir));
60979968Sobrien			else {
61079968Sobrien					/* if has a /, move back to it */
61179968Sobrien					/* otherwise use '..' */
61279968Sobrien				if ((p = strrchr(realdir, '/')) != NULL) {
61379968Sobrien					if (p == realdir)
61479968Sobrien						p++;
61579968Sobrien					*p = '\0';
61679968Sobrien				} else
61779968Sobrien					strlcpy(realdir, "..", sizeof(realdir));
61879968Sobrien			}
61979968Sobrien			if (stat(realdir, &dir) == 0)
62079968Sobrien				pdir = &dir;
62179968Sobrien		}
62279968Sobrien	}
62379968Sobrien	pdirwok = 0;
62479968Sobrien	if (pdir != NULL) {
62579968Sobrien		if (pdir->st_uid == geteuid())
62679968Sobrien			pdirwok = ((pdir->st_mode & S_IWUSR) != 0);
62779968Sobrien		else if (matchgroup(pdir->st_gid))
62879968Sobrien			pdirwok = ((pdir->st_mode & S_IWGRP) != 0);
62979968Sobrien		else
63079968Sobrien			pdirwok = ((pdir->st_mode & S_IWOTH) != 0);
63179968Sobrien	}
63279968Sobrien
63379968Sobrien			/* 'a': can APPE to file */
63479968Sobrien	if (wok && CURCLASS_FLAGS_ISSET(upload) && S_ISREG(fe->stat->st_mode))
63579968Sobrien		CPUTC('a', fd);
63679968Sobrien
63779968Sobrien			/* 'c': can create or append to files in directory */
63879968Sobrien	if (wok && CURCLASS_FLAGS_ISSET(modify) && S_ISDIR(fe->stat->st_mode))
63979968Sobrien		CPUTC('c', fd);
64079968Sobrien
64179968Sobrien			/* 'd': can delete file or directory */
64279968Sobrien	if (pdirwok && CURCLASS_FLAGS_ISSET(modify)) {
64379968Sobrien		int candel;
64479968Sobrien
64579968Sobrien		candel = 1;
64679968Sobrien		if (S_ISDIR(fe->stat->st_mode)) {
64779968Sobrien			DIR *dirp;
64879968Sobrien			struct dirent *dp;
64979968Sobrien
65079968Sobrien			if ((dirp = opendir(fe->display)) == NULL)
65179968Sobrien				candel = 0;
65279968Sobrien			else {
65379968Sobrien				while ((dp = readdir(dirp)) != NULL) {
65479968Sobrien					if (ISDOTDIR(dp->d_name) ||
65579968Sobrien					    ISDOTDOTDIR(dp->d_name))
65679968Sobrien						continue;
65779968Sobrien					candel = 0;
65879968Sobrien					break;
65979968Sobrien				}
66079968Sobrien				closedir(dirp);
66179968Sobrien			}
66279968Sobrien		}
66379968Sobrien		if (candel)
66479968Sobrien			CPUTC('d', fd);
66579968Sobrien	}
66679968Sobrien
66779968Sobrien			/* 'e': can enter directory */
66879968Sobrien	if (xok && S_ISDIR(fe->stat->st_mode))
66979968Sobrien		CPUTC('e', fd);
67079968Sobrien
67179968Sobrien			/* 'f': can rename file or directory */
67279968Sobrien	if (pdirwok && CURCLASS_FLAGS_ISSET(modify))
67379968Sobrien		CPUTC('f', fd);
67479968Sobrien
67579968Sobrien			/* 'l': can list directory */
67679968Sobrien	if (rok && xok && S_ISDIR(fe->stat->st_mode))
67779968Sobrien		CPUTC('l', fd);
67879968Sobrien
67979968Sobrien			/* 'm': can create directory */
68079968Sobrien	if (wok && CURCLASS_FLAGS_ISSET(modify) && S_ISDIR(fe->stat->st_mode))
68179968Sobrien		CPUTC('m', fd);
68279968Sobrien
68379968Sobrien			/* 'p': can remove files in directory */
68479968Sobrien	if (wok && CURCLASS_FLAGS_ISSET(modify) && S_ISDIR(fe->stat->st_mode))
68579968Sobrien		CPUTC('p', fd);
68679968Sobrien
68779968Sobrien			/* 'r': can RETR file */
68879968Sobrien	if (rok && S_ISREG(fe->stat->st_mode))
68979968Sobrien		CPUTC('r', fd);
69079968Sobrien
69179968Sobrien			/* 'w': can STOR file */
69279968Sobrien	if (wok && CURCLASS_FLAGS_ISSET(upload) && S_ISREG(fe->stat->st_mode))
69379968Sobrien		CPUTC('w', fd);
69479968Sobrien
69579968Sobrien	CPUTC(';', fd);
69679968Sobrien}
69779968Sobrien
69879968Sobrienstatic void
69979968Sobrienfact_size(const char *fact, FILE *fd, factelem *fe)
70079968Sobrien{
70179968Sobrien
70279968Sobrien	if (S_ISREG(fe->stat->st_mode))
70379968Sobrien		cprintf(fd, "%s=" LLF ";", fact, (LLT)fe->stat->st_size);
70479968Sobrien}
70579968Sobrien
70679968Sobrienstatic void
70779968Sobrienfact_type(const char *fact, FILE *fd, factelem *fe)
70879968Sobrien{
70979968Sobrien
71079968Sobrien	cprintf(fd, "%s=", fact);
71179968Sobrien	switch (fe->stat->st_mode & S_IFMT) {
71279968Sobrien	case S_IFDIR:
71392282Sobrien		if (fe->flags & FE_MLSD) {
71492282Sobrien			if ((fe->flags & FE_ISCURDIR) || ISDOTDIR(fe->display))
71592282Sobrien				cprintf(fd, "cdir");
71692282Sobrien			else if (ISDOTDOTDIR(fe->display))
71792282Sobrien				cprintf(fd, "pdir");
71892282Sobrien			else
71992282Sobrien				cprintf(fd, "dir");
72092282Sobrien		} else {
72179968Sobrien			cprintf(fd, "dir");
72292282Sobrien		}
72379968Sobrien		break;
72479968Sobrien	case S_IFREG:
72579968Sobrien		cprintf(fd, "file");
72679968Sobrien		break;
72779968Sobrien	case S_IFIFO:
72879968Sobrien		cprintf(fd, "OS.unix=fifo");
72979968Sobrien		break;
73079968Sobrien	case S_IFLNK:		/* XXX: probably a NO-OP with stat() */
73179968Sobrien		cprintf(fd, "OS.unix=slink");
73279968Sobrien		break;
73379968Sobrien	case S_IFSOCK:
73479968Sobrien		cprintf(fd, "OS.unix=socket");
73579968Sobrien		break;
73679968Sobrien	case S_IFBLK:
73779968Sobrien	case S_IFCHR:
73879968Sobrien		cprintf(fd, "OS.unix=%s-%d/%d",
73979968Sobrien		    S_ISBLK(fe->stat->st_mode) ? "blk" : "chr",
74079968Sobrien		    major(fe->stat->st_rdev), minor(fe->stat->st_rdev));
74179968Sobrien		break;
74279968Sobrien	default:
74379968Sobrien		cprintf(fd, "OS.unix=UNKNOWN(0%o)", fe->stat->st_mode & S_IFMT);
74479968Sobrien		break;
74579968Sobrien	}
74679968Sobrien	CPUTC(';', fd);
74779968Sobrien}
74879968Sobrien
74979968Sobrienstatic void
75079968Sobrienfact_unique(const char *fact, FILE *fd, factelem *fe)
75179968Sobrien{
75279968Sobrien	char obuf[(sizeof(dev_t) + sizeof(ino_t) + 2) * 4 / 3 + 2];
75379968Sobrien	char tbuf[sizeof(dev_t) + sizeof(ino_t)];
75479968Sobrien
75579968Sobrien	memcpy(tbuf,
75679968Sobrien	    (char *)&(fe->stat->st_dev), sizeof(dev_t));
75779968Sobrien	memcpy(tbuf + sizeof(dev_t),
75879968Sobrien	    (char *)&(fe->stat->st_ino), sizeof(ino_t));
75979968Sobrien	base64_encode(tbuf, sizeof(dev_t) + sizeof(ino_t), obuf, 1);
76079968Sobrien	cprintf(fd, "%s=%s;", fact, obuf);
76179968Sobrien}
76279968Sobrien
76379968Sobrienstatic int
76479968Sobrienmatchgroup(gid_t gid)
76579968Sobrien{
76679968Sobrien	int	i;
76779968Sobrien
76879968Sobrien	for (i = 0; i < gidcount; i++)
76979968Sobrien		if (gid == gidlist[i])
77079968Sobrien			return(1);
77179968Sobrien	return (0);
77279968Sobrien}
77379968Sobrien
77479968Sobrienstatic void
77579968Sobrienmlsname(FILE *fp, factelem *fe)
77679968Sobrien{
77792282Sobrien	char realfile[MAXPATHLEN];
77892282Sobrien	int i, userf;
77979968Sobrien
78079968Sobrien	for (i = 0; i < FACTTABSIZE; i++) {
78179968Sobrien		if (facttab[i].enabled)
78279968Sobrien			(facttab[i].display)(facttab[i].name, fp, fe);
78379968Sobrien	}
78492282Sobrien	if ((fe->flags & FE_MLSD) &&
78592282Sobrien	    !(fe->flags & FE_ISCURDIR) && !ISDOTDIR(fe->display)) {
78692282Sobrien			/* if MLSD and not "." entry, display as-is */
78792282Sobrien		userf = 0;
78892282Sobrien	} else {
78992282Sobrien			/* if MLST, or MLSD and "." entry, realpath(3) it */
79092282Sobrien		if (realpath(fe->display, realfile) != NULL)
79192282Sobrien			userf = 1;
79292282Sobrien	}
79392282Sobrien	cprintf(fp, " %s\r\n", userf ? realfile : fe->display);
79479968Sobrien}
79579968Sobrien
79679968Sobrienstatic void
79779968Sobrienreplydirname(const char *name, const char *message)
79879968Sobrien{
79979968Sobrien	char *p, *ep;
80079968Sobrien	char npath[MAXPATHLEN * 2];
80179968Sobrien
80279968Sobrien	p = npath;
80379968Sobrien	ep = &npath[sizeof(npath) - 1];
80479968Sobrien	while (*name) {
80579968Sobrien		if (*name == '"') {
80679968Sobrien			if (ep - p < 2)
80779968Sobrien				break;
80879968Sobrien			*p++ = *name++;
80979968Sobrien			*p++ = '"';
81079968Sobrien		} else {
81179968Sobrien			if (ep - p < 1)
81279968Sobrien				break;
81379968Sobrien			*p++ = *name++;
81479968Sobrien		}
81579968Sobrien	}
81679968Sobrien	*p = '\0';
81779968Sobrien	reply(257, "\"%s\" %s", npath, message);
81879968Sobrien}
819