968 lines
		
	
	
		
			24 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			968 lines
		
	
	
		
			24 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
/*	$NetBSD: cmds.c,v 1.31 2012/06/19 06:06:34 dholland Exp $	*/
 | 
						|
 | 
						|
/*
 | 
						|
 * Copyright (c) 1999-2009 The NetBSD Foundation, Inc.
 | 
						|
 * All rights reserved.
 | 
						|
 *
 | 
						|
 * This code is derived from software contributed to The NetBSD Foundation
 | 
						|
 * by Luke Mewburn.
 | 
						|
 *
 | 
						|
 * Redistribution and use in source and binary forms, with or without
 | 
						|
 * modification, are permitted provided that the following conditions
 | 
						|
 * are met:
 | 
						|
 * 1. Redistributions of source code must retain the above copyright
 | 
						|
 *    notice, this list of conditions and the following disclaimer.
 | 
						|
 * 2. Redistributions in binary form must reproduce the above copyright
 | 
						|
 *    notice, this list of conditions and the following disclaimer in the
 | 
						|
 *    documentation and/or other materials provided with the distribution.
 | 
						|
 *
 | 
						|
 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
 | 
						|
 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
 | 
						|
 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 | 
						|
 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
 | 
						|
 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 | 
						|
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 | 
						|
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 | 
						|
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 | 
						|
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 | 
						|
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 | 
						|
 * POSSIBILITY OF SUCH DAMAGE.
 | 
						|
 */
 | 
						|
 | 
						|
/*
 | 
						|
 * Copyright (c) 1985, 1988, 1990, 1992, 1993, 1994
 | 
						|
 *	The Regents of the University of California.  All rights reserved.
 | 
						|
 *
 | 
						|
 * Redistribution and use in source and binary forms, with or without
 | 
						|
 * modification, are permitted provided that the following conditions
 | 
						|
 * are met:
 | 
						|
 * 1. Redistributions of source code must retain the above copyright
 | 
						|
 *    notice, this list of conditions and the following disclaimer.
 | 
						|
 * 2. Redistributions in binary form must reproduce the above copyright
 | 
						|
 *    notice, this list of conditions and the following disclaimer in the
 | 
						|
 *    documentation and/or other materials provided with the distribution.
 | 
						|
 * 3. Neither the name of the University nor the names of its contributors
 | 
						|
 *    may be used to endorse or promote products derived from this software
 | 
						|
 *    without specific prior written permission.
 | 
						|
 *
 | 
						|
 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
 | 
						|
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 | 
						|
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 | 
						|
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
 | 
						|
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 | 
						|
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 | 
						|
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 | 
						|
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 | 
						|
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 | 
						|
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 | 
						|
 * SUCH DAMAGE.
 | 
						|
 */
 | 
						|
 | 
						|
/*
 | 
						|
 * Copyright (C) 1997 and 1998 WIDE Project.
 | 
						|
 * All rights reserved.
 | 
						|
 * 
 | 
						|
 * Redistribution and use in source and binary forms, with or without
 | 
						|
 * modification, are permitted provided that the following conditions
 | 
						|
 * are met:
 | 
						|
 * 1. Redistributions of source code must retain the above copyright
 | 
						|
 *    notice, this list of conditions and the following disclaimer.
 | 
						|
 * 2. Redistributions in binary form must reproduce the above copyright
 | 
						|
 *    notice, this list of conditions and the following disclaimer in the
 | 
						|
 *    documentation and/or other materials provided with the distribution.
 | 
						|
 * 3. Neither the name of the project nor the names of its contributors
 | 
						|
 *    may be used to endorse or promote products derived from this software
 | 
						|
 *    without specific prior written permission.
 | 
						|
 * 
 | 
						|
 * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``AS IS'' AND
 | 
						|
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 | 
						|
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 | 
						|
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE PROJECT OR CONTRIBUTORS BE LIABLE
 | 
						|
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 | 
						|
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 | 
						|
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 | 
						|
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 | 
						|
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 | 
						|
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 | 
						|
 * SUCH DAMAGE.
 | 
						|
 */
 | 
						|
 | 
						|
 | 
						|
#include <sys/cdefs.h>
 | 
						|
#ifndef lint
 | 
						|
__RCSID("$NetBSD: cmds.c,v 1.31 2012/06/19 06:06:34 dholland Exp $");
 | 
						|
#endif /* not lint */
 | 
						|
 | 
						|
#include <sys/param.h>
 | 
						|
#include <sys/stat.h>
 | 
						|
 | 
						|
#include <arpa/ftp.h>
 | 
						|
 | 
						|
#include <dirent.h>
 | 
						|
#include <errno.h>
 | 
						|
#include <stdio.h>
 | 
						|
#include <stdlib.h>
 | 
						|
#include <string.h>
 | 
						|
#include <tzfile.h>
 | 
						|
#include <unistd.h>
 | 
						|
#include <ctype.h>
 | 
						|
 | 
						|
#ifdef KERBEROS5
 | 
						|
#include <krb5/krb5.h>
 | 
						|
#endif
 | 
						|
 | 
						|
#include "extern.h"
 | 
						|
 | 
						|
typedef enum {
 | 
						|
	FE_MLSD		= 1<<0,		/* if op is MLSD (MLST otherwise ) */
 | 
						|
	FE_ISCURDIR	= 1<<1,		/* if name is the current directory */
 | 
						|
} factflag_t;
 | 
						|
 | 
						|
typedef struct {
 | 
						|
	const char	*path;		/* full pathname */
 | 
						|
	const char	*display;	/* name to display */
 | 
						|
	struct stat	*stat;		/* stat of path */
 | 
						|
	struct stat	*pdirstat;	/* stat of path's parent dir */
 | 
						|
	factflag_t	 flags;		/* flags */
 | 
						|
} factelem;
 | 
						|
 | 
						|
static void	ack(const char *);
 | 
						|
static void	base64_encode(const char *, size_t, char *, int);
 | 
						|
static void	fact_type(const char *, FILE *, factelem *);
 | 
						|
static void	fact_size(const char *, FILE *, factelem *);
 | 
						|
static void	fact_modify(const char *, FILE *, factelem *);
 | 
						|
static void	fact_perm(const char *, FILE *, factelem *);
 | 
						|
static void	fact_unique(const char *, FILE *, factelem *);
 | 
						|
static int	matchgroup(gid_t);
 | 
						|
static void	mlsname(FILE *, factelem *);
 | 
						|
static void	replydirname(const char *, const char *);
 | 
						|
 | 
						|
struct ftpfact {
 | 
						|
	const char	 *name;		/* name of fact */
 | 
						|
	int		  enabled;	/* if fact is enabled */
 | 
						|
	void		(*display)(const char *, FILE *, factelem *);
 | 
						|
					/* function to display fact */
 | 
						|
};
 | 
						|
 | 
						|
struct ftpfact facttab[] = {
 | 
						|
	{ "Type",	1, fact_type },
 | 
						|
#define	FACT_TYPE 0
 | 
						|
	{ "Size",	1, fact_size },
 | 
						|
	{ "Modify",	1, fact_modify },
 | 
						|
	{ "Perm",	1, fact_perm },
 | 
						|
	{ "Unique",	1, fact_unique },
 | 
						|
	/* "Create" */
 | 
						|
	/* "Lang" */
 | 
						|
	/* "Media-Type" */
 | 
						|
	/* "CharSet" */
 | 
						|
};
 | 
						|
 | 
						|
#define FACTTABSIZE	(sizeof(facttab) / sizeof(struct ftpfact))
 | 
						|
 | 
						|
static char cached_path[MAXPATHLEN + 1] = "/";
 | 
						|
static void discover_path(char *, const char *);
 | 
						|
 | 
						|
void
 | 
						|
cwd(const char *path)
 | 
						|
{
 | 
						|
 | 
						|
	if (chdir(path) < 0)
 | 
						|
		perror_reply(550, path);
 | 
						|
	else {
 | 
						|
		show_chdir_messages(250);
 | 
						|
		ack("CWD");
 | 
						|
		if (getcwd(cached_path, MAXPATHLEN) == NULL) {
 | 
						|
			discover_path(cached_path, path);
 | 
						|
		}
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
void
 | 
						|
delete(const char *name)
 | 
						|
{
 | 
						|
	char *p = NULL;
 | 
						|
 | 
						|
	if (remove(name) < 0) {
 | 
						|
		p = strerror(errno);
 | 
						|
		perror_reply(550, name);
 | 
						|
	} else
 | 
						|
		ack("DELE");
 | 
						|
	logxfer("delete", -1, name, NULL, NULL, p);
 | 
						|
}
 | 
						|
 | 
						|
void
 | 
						|
feat(void)
 | 
						|
{
 | 
						|
	size_t i;
 | 
						|
 | 
						|
	reply(-211, "Features supported");
 | 
						|
	cprintf(stdout, " MDTM\r\n");
 | 
						|
	cprintf(stdout, " MLST ");
 | 
						|
	for (i = 0; i < FACTTABSIZE; i++)
 | 
						|
		cprintf(stdout, "%s%s;", facttab[i].name,
 | 
						|
		    facttab[i].enabled ? "*" : "");
 | 
						|
	cprintf(stdout, "\r\n");
 | 
						|
	cprintf(stdout, " REST STREAM\r\n");
 | 
						|
	cprintf(stdout, " SIZE\r\n");
 | 
						|
	cprintf(stdout, " TVFS\r\n");
 | 
						|
	reply(211,  "End");
 | 
						|
}
 | 
						|
 | 
						|
void
 | 
						|
makedir(const char *name)
 | 
						|
{
 | 
						|
	char *p = NULL;
 | 
						|
 | 
						|
	if (mkdir(name, 0777) < 0) {
 | 
						|
		p = strerror(errno);
 | 
						|
		perror_reply(550, name);
 | 
						|
	} else
 | 
						|
		replydirname(name, "directory created.");
 | 
						|
	logxfer("mkdir", -1, name, NULL, NULL, p);
 | 
						|
}
 | 
						|
 | 
						|
void
 | 
						|
mlsd(const char *path)
 | 
						|
{
 | 
						|
	struct dirent	*dp;
 | 
						|
	struct stat	 sb, pdirstat;
 | 
						|
	factelem f;
 | 
						|
	FILE	*dout;
 | 
						|
	DIR	*dirp;
 | 
						|
	char	name[MAXPATHLEN];
 | 
						|
	int	hastypefact;
 | 
						|
 | 
						|
	hastypefact = facttab[FACT_TYPE].enabled;
 | 
						|
	if (path == NULL)
 | 
						|
		path = ".";
 | 
						|
	if (stat(path, &pdirstat) == -1) {
 | 
						|
 mlsdperror:
 | 
						|
		perror_reply(550, path);
 | 
						|
		return;
 | 
						|
	}
 | 
						|
	if (! S_ISDIR(pdirstat.st_mode)) {
 | 
						|
		errno = ENOTDIR;
 | 
						|
		perror_reply(501, path);
 | 
						|
		return;
 | 
						|
	}
 | 
						|
	if ((dirp = opendir(path)) == NULL)
 | 
						|
		goto mlsdperror;
 | 
						|
 | 
						|
	dout = dataconn("MLSD", (off_t)-1, "w");
 | 
						|
	if (dout == NULL)
 | 
						|
		return;
 | 
						|
 | 
						|
	memset(&f, 0, sizeof(f));
 | 
						|
	f.stat = &sb;
 | 
						|
	f.flags |= FE_MLSD;
 | 
						|
	while ((dp = readdir(dirp)) != NULL) {
 | 
						|
		snprintf(name, sizeof(name), "%s/%s", path, dp->d_name);
 | 
						|
		if (ISDOTDIR(dp->d_name)) {	/* special case curdir: */
 | 
						|
			if (! hastypefact)
 | 
						|
				continue;
 | 
						|
			f.pdirstat = NULL;	/*   require stat of parent */
 | 
						|
			f.display = path;	/*   set name to real name */
 | 
						|
			f.flags |= FE_ISCURDIR; /*   flag name is curdir */
 | 
						|
		} else {
 | 
						|
			if (ISDOTDOTDIR(dp->d_name)) {
 | 
						|
				if (! hastypefact)
 | 
						|
					continue;
 | 
						|
				f.pdirstat = NULL;
 | 
						|
			} else
 | 
						|
				f.pdirstat = &pdirstat;	/* cache parent stat */
 | 
						|
			f.display = dp->d_name;
 | 
						|
			f.flags &= ~FE_ISCURDIR;
 | 
						|
		}
 | 
						|
		if (stat(name, &sb) == -1)
 | 
						|
			continue;
 | 
						|
		f.path = name;
 | 
						|
		mlsname(dout, &f);
 | 
						|
	}
 | 
						|
	(void)closedir(dirp);
 | 
						|
 | 
						|
	if (ferror(dout) != 0)
 | 
						|
		perror_reply(550, "Data connection");
 | 
						|
	else
 | 
						|
		reply(226, "MLSD complete.");
 | 
						|
	closedataconn(dout);
 | 
						|
	total_xfers_out++;
 | 
						|
	total_xfers++;
 | 
						|
}
 | 
						|
 | 
						|
void
 | 
						|
mlst(const char *path)
 | 
						|
{
 | 
						|
	struct stat sb;
 | 
						|
	factelem f;
 | 
						|
 | 
						|
	if (path == NULL)
 | 
						|
		path = ".";
 | 
						|
	if (stat(path, &sb) == -1) {
 | 
						|
		perror_reply(550, path);
 | 
						|
		return;
 | 
						|
	}
 | 
						|
	reply(-250, "MLST %s", path);
 | 
						|
	memset(&f, 0, sizeof(f));
 | 
						|
	f.path = path;
 | 
						|
	f.display = path;
 | 
						|
	f.stat = &sb;
 | 
						|
	f.pdirstat = NULL;
 | 
						|
	CPUTC(' ', stdout);
 | 
						|
	mlsname(stdout, &f);
 | 
						|
	reply(250, "End");
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
void
 | 
						|
opts(const char *command)
 | 
						|
{
 | 
						|
	struct tab *c;
 | 
						|
	char *ep;
 | 
						|
 | 
						|
	if ((ep = strchr(command, ' ')) != NULL)
 | 
						|
		*ep++ = '\0';
 | 
						|
	c = lookup(cmdtab, command);
 | 
						|
	if (c == NULL) {
 | 
						|
		reply(502, "Unknown command '%s'.", command);
 | 
						|
		return;
 | 
						|
	}
 | 
						|
	if (! CMD_IMPLEMENTED(c)) {
 | 
						|
		reply(502, "%s command not implemented.", c->name);
 | 
						|
		return;
 | 
						|
	}
 | 
						|
	if (! CMD_HAS_OPTIONS(c)) {
 | 
						|
		reply(501, "%s command does not support persistent options.",
 | 
						|
		    c->name);
 | 
						|
		return;
 | 
						|
	}
 | 
						|
 | 
						|
			/* special case: MLST */
 | 
						|
	if (strcasecmp(command, "MLST") == 0) {
 | 
						|
		int	 enabled[FACTTABSIZE];
 | 
						|
		size_t	 i, onedone;
 | 
						|
		size_t	 len;
 | 
						|
		char	*p;
 | 
						|
 | 
						|
		for (i = 0; i < sizeof(enabled) / sizeof(int); i++)
 | 
						|
			enabled[i] = 0;
 | 
						|
		if (ep == NULL || *ep == '\0')
 | 
						|
			goto displaymlstopts;
 | 
						|
 | 
						|
				/* don't like spaces, and need trailing ; */
 | 
						|
		len = strlen(ep);
 | 
						|
		if (strchr(ep, ' ') != NULL || ep[len - 1] != ';') {
 | 
						|
 badmlstopt:
 | 
						|
			reply(501, "Invalid MLST options");
 | 
						|
			return;
 | 
						|
		}
 | 
						|
		ep[len - 1] = '\0';
 | 
						|
		while ((p = strsep(&ep, ";")) != NULL) {
 | 
						|
			if (*p == '\0')
 | 
						|
				goto badmlstopt;
 | 
						|
			for (i = 0; i < FACTTABSIZE; i++)
 | 
						|
				if (strcasecmp(p, facttab[i].name) == 0) {
 | 
						|
					enabled[i] = 1;
 | 
						|
					break;
 | 
						|
				}
 | 
						|
		}
 | 
						|
 | 
						|
 displaymlstopts:
 | 
						|
		for (i = 0; i < FACTTABSIZE; i++)
 | 
						|
			facttab[i].enabled = enabled[i];
 | 
						|
		cprintf(stdout, "200 MLST OPTS");
 | 
						|
		for (i = onedone = 0; i < FACTTABSIZE; i++) {
 | 
						|
			if (facttab[i].enabled) {
 | 
						|
				cprintf(stdout, "%s%s;", onedone ? "" : " ",
 | 
						|
				    facttab[i].name);
 | 
						|
				onedone++;
 | 
						|
			}
 | 
						|
		}
 | 
						|
		cprintf(stdout, "\r\n");
 | 
						|
		fflush(stdout);
 | 
						|
		return;
 | 
						|
	}
 | 
						|
 | 
						|
			/* default cases */
 | 
						|
	if (ep != NULL && *ep != '\0')
 | 
						|
		REASSIGN(c->options, ftpd_strdup(ep));
 | 
						|
	if (c->options != NULL)
 | 
						|
		reply(200, "Options for %s are '%s'.", c->name,
 | 
						|
		    c->options);
 | 
						|
	else
 | 
						|
		reply(200, "No options defined for %s.", c->name);
 | 
						|
}
 | 
						|
 | 
						|
void
 | 
						|
pwd(void)
 | 
						|
{
 | 
						|
	char path[MAXPATHLEN];
 | 
						|
 | 
						|
	if (getcwd(path, sizeof(path) - 1) == NULL) {
 | 
						|
		if (chdir(cached_path) < 0) {
 | 
						|
			reply(550, "Can't get the current directory: %s.",
 | 
						|
			    strerror(errno));
 | 
						|
			return;
 | 
						|
		}
 | 
						|
		(void)strlcpy(path, cached_path, MAXPATHLEN);
 | 
						|
	}
 | 
						|
	replydirname(path, "is the current directory.");
 | 
						|
}
 | 
						|
 | 
						|
void
 | 
						|
removedir(const char *name)
 | 
						|
{
 | 
						|
	char *p = NULL;
 | 
						|
 | 
						|
	if (rmdir(name) < 0) {
 | 
						|
		p = strerror(errno);
 | 
						|
		perror_reply(550, name);
 | 
						|
	} else
 | 
						|
		ack("RMD");
 | 
						|
	logxfer("rmdir", -1, name, NULL, NULL, p);
 | 
						|
}
 | 
						|
 | 
						|
char *
 | 
						|
renamefrom(const char *name)
 | 
						|
{
 | 
						|
	struct stat st;
 | 
						|
 | 
						|
	if (stat(name, &st) < 0) {
 | 
						|
		perror_reply(550, name);
 | 
						|
		return (NULL);
 | 
						|
	}
 | 
						|
	reply(350, "File exists, ready for destination name");
 | 
						|
	return (ftpd_strdup(name));
 | 
						|
}
 | 
						|
 | 
						|
void
 | 
						|
renamecmd(const char *from, const char *to)
 | 
						|
{
 | 
						|
	char *p = NULL;
 | 
						|
 | 
						|
	if (rename(from, to) < 0) {
 | 
						|
		p = strerror(errno);
 | 
						|
		perror_reply(550, "rename");
 | 
						|
	} else
 | 
						|
		ack("RNTO");
 | 
						|
	logxfer("rename", -1, from, to, NULL, p);
 | 
						|
}
 | 
						|
 | 
						|
void
 | 
						|
sizecmd(const char *filename)
 | 
						|
{
 | 
						|
	switch (type) {
 | 
						|
	case TYPE_L:
 | 
						|
	case TYPE_I:
 | 
						|
	    {
 | 
						|
		struct stat stbuf;
 | 
						|
		if (stat(filename, &stbuf) < 0 || !S_ISREG(stbuf.st_mode))
 | 
						|
			reply(550, "%s: not a plain file.", filename);
 | 
						|
		else
 | 
						|
			reply(213, ULLF, (ULLT)stbuf.st_size);
 | 
						|
		break;
 | 
						|
	    }
 | 
						|
	case TYPE_A:
 | 
						|
	    {
 | 
						|
		FILE *fin;
 | 
						|
		int c;
 | 
						|
		off_t count;
 | 
						|
		struct stat stbuf;
 | 
						|
		fin = fopen(filename, "r");
 | 
						|
		if (fin == NULL) {
 | 
						|
			perror_reply(550, filename);
 | 
						|
			return;
 | 
						|
		}
 | 
						|
		if (fstat(fileno(fin), &stbuf) < 0 || !S_ISREG(stbuf.st_mode)) {
 | 
						|
			reply(550, "%s: not a plain file.", filename);
 | 
						|
			(void) fclose(fin);
 | 
						|
			return;
 | 
						|
		}
 | 
						|
		if (stbuf.st_size > 10240) {
 | 
						|
			reply(550, "%s: file too large for SIZE.", filename);
 | 
						|
			(void) fclose(fin);
 | 
						|
			return;
 | 
						|
		}
 | 
						|
 | 
						|
		count = 0;
 | 
						|
		while((c = getc(fin)) != EOF) {
 | 
						|
			if (c == '\n')	/* will get expanded to \r\n */
 | 
						|
				count++;
 | 
						|
			count++;
 | 
						|
		}
 | 
						|
		(void) fclose(fin);
 | 
						|
 | 
						|
		reply(213, LLF, (LLT)count);
 | 
						|
		break;
 | 
						|
	    }
 | 
						|
	default:
 | 
						|
		reply(504, "SIZE not implemented for Type %c.", "?AEIL"[type]);
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
void
 | 
						|
statfilecmd(const char *filename)
 | 
						|
{
 | 
						|
	FILE *fin;
 | 
						|
	int c;
 | 
						|
	int atstart;
 | 
						|
	const char *argv[] = { INTERNAL_LS, "-lgA", "", NULL };
 | 
						|
 | 
						|
	argv[2] = filename;
 | 
						|
	fin = ftpd_popen(argv, "r", STDOUT_FILENO);
 | 
						|
	reply(-211, "status of %s:", filename);
 | 
						|
/* XXX: use fgetln() or fparseln() here? */
 | 
						|
	atstart = 1;
 | 
						|
	while ((c = getc(fin)) != EOF) {
 | 
						|
		if (c == '\n') {
 | 
						|
			if (ferror(stdout)){
 | 
						|
				perror_reply(421, "control connection");
 | 
						|
				(void) ftpd_pclose(fin);
 | 
						|
				dologout(1);
 | 
						|
				/* NOTREACHED */
 | 
						|
			}
 | 
						|
			if (ferror(fin)) {
 | 
						|
				perror_reply(551, filename);
 | 
						|
				(void) ftpd_pclose(fin);
 | 
						|
				return;
 | 
						|
			}
 | 
						|
			CPUTC('\r', stdout);
 | 
						|
		}
 | 
						|
		if (atstart && isdigit(c))
 | 
						|
			CPUTC(' ', stdout);
 | 
						|
		CPUTC(c, stdout);
 | 
						|
		atstart = (c == '\n');
 | 
						|
	}
 | 
						|
	(void) ftpd_pclose(fin);
 | 
						|
	reply(211, "End of Status");
 | 
						|
}
 | 
						|
 | 
						|
/* -- */
 | 
						|
 | 
						|
static void
 | 
						|
ack(const char *s)
 | 
						|
{
 | 
						|
 | 
						|
	reply(250, "%s command successful.", s);
 | 
						|
}
 | 
						|
 | 
						|
/*
 | 
						|
 * Encode len bytes starting at clear using base64 encoding into encoded,
 | 
						|
 * which should be at least ((len + 2) * 4 / 3 + 1) in size.
 | 
						|
 * If nulterm is non-zero, terminate with \0 otherwise pad to 3 byte boundary
 | 
						|
 * with `='.
 | 
						|
 */
 | 
						|
static void
 | 
						|
base64_encode(const char *clear, size_t len, char *encoded, int nulterm)
 | 
						|
{
 | 
						|
	static const char base64[] =
 | 
						|
	    "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
 | 
						|
	const char *c;
 | 
						|
	char	*e, termchar;
 | 
						|
	int	 i;
 | 
						|
 | 
						|
			/* determine whether to pad with '=' or NUL terminate */
 | 
						|
	termchar = nulterm ? '\0' : '=';
 | 
						|
	c = clear;
 | 
						|
	e = encoded;
 | 
						|
			/* convert all but last 2 bytes */
 | 
						|
	for (i = len; i > 2; i -= 3, c += 3) {
 | 
						|
		*e++ = base64[(c[0] >> 2) & 0x3f];
 | 
						|
		*e++ = base64[((c[0] << 4) & 0x30) | ((c[1] >> 4) & 0x0f)];
 | 
						|
		*e++ = base64[((c[1] << 2) & 0x3c) | ((c[2] >> 6) & 0x03)];
 | 
						|
		*e++ = base64[(c[2]) & 0x3f];
 | 
						|
	}
 | 
						|
			/* handle slop at end */
 | 
						|
	if (i > 0) {
 | 
						|
		*e++ = base64[(c[0] >> 2) & 0x3f];
 | 
						|
		*e++ = base64[((c[0] << 4) & 0x30) |
 | 
						|
		     (i > 1 ? ((c[1] >> 4) & 0x0f) : 0)];
 | 
						|
		*e++ = (i > 1) ? base64[(c[1] << 2) & 0x3c] : termchar;
 | 
						|
		*e++ = termchar;
 | 
						|
	}
 | 
						|
	*e = '\0';
 | 
						|
}
 | 
						|
 | 
						|
static void
 | 
						|
fact_modify(const char *fact, FILE *fd, factelem *fe)
 | 
						|
{
 | 
						|
	struct tm *t;
 | 
						|
 | 
						|
	t = gmtime(&(fe->stat->st_mtime));
 | 
						|
	cprintf(fd, "%s=%04d%02d%02d%02d%02d%02d;", fact,
 | 
						|
	    TM_YEAR_BASE + t->tm_year,
 | 
						|
	    t->tm_mon+1, t->tm_mday,
 | 
						|
	    t->tm_hour, t->tm_min, t->tm_sec);
 | 
						|
}
 | 
						|
 | 
						|
static void
 | 
						|
fact_perm(const char *fact, FILE *fd, factelem *fe)
 | 
						|
{
 | 
						|
	int		rok, wok, xok, pdirwok;
 | 
						|
	struct stat	*pdir;
 | 
						|
 | 
						|
	if (fe->stat->st_uid == geteuid()) {
 | 
						|
		rok = ((fe->stat->st_mode & S_IRUSR) != 0);
 | 
						|
		wok = ((fe->stat->st_mode & S_IWUSR) != 0);
 | 
						|
		xok = ((fe->stat->st_mode & S_IXUSR) != 0);
 | 
						|
	} else if (matchgroup(fe->stat->st_gid)) {
 | 
						|
		rok = ((fe->stat->st_mode & S_IRGRP) != 0);
 | 
						|
		wok = ((fe->stat->st_mode & S_IWGRP) != 0);
 | 
						|
		xok = ((fe->stat->st_mode & S_IXGRP) != 0);
 | 
						|
	} else {
 | 
						|
		rok = ((fe->stat->st_mode & S_IROTH) != 0);
 | 
						|
		wok = ((fe->stat->st_mode & S_IWOTH) != 0);
 | 
						|
		xok = ((fe->stat->st_mode & S_IXOTH) != 0);
 | 
						|
	}
 | 
						|
 | 
						|
	cprintf(fd, "%s=", fact);
 | 
						|
 | 
						|
			/*
 | 
						|
			 * if parent info not provided, look it up, but
 | 
						|
			 * only if the current class has modify rights,
 | 
						|
			 * since we only need this info in such a case.
 | 
						|
			 */
 | 
						|
	pdir = fe->pdirstat;
 | 
						|
	if (pdir == NULL && CURCLASS_FLAGS_ISSET(modify)) {
 | 
						|
		size_t		len;
 | 
						|
		char		realdir[MAXPATHLEN], *p;
 | 
						|
		struct stat	dir;
 | 
						|
 | 
						|
		len = strlcpy(realdir, fe->path, sizeof(realdir));
 | 
						|
		if (len < sizeof(realdir) - 4) {
 | 
						|
			if (S_ISDIR(fe->stat->st_mode))
 | 
						|
				strlcat(realdir, "/..", sizeof(realdir));
 | 
						|
			else {
 | 
						|
					/* if has a /, move back to it */
 | 
						|
					/* otherwise use '..' */
 | 
						|
				if ((p = strrchr(realdir, '/')) != NULL) {
 | 
						|
					if (p == realdir)
 | 
						|
						p++;
 | 
						|
					*p = '\0';
 | 
						|
				} else
 | 
						|
					strlcpy(realdir, "..", sizeof(realdir));
 | 
						|
			}
 | 
						|
			if (stat(realdir, &dir) == 0)
 | 
						|
				pdir = &dir;
 | 
						|
		}
 | 
						|
	}
 | 
						|
	pdirwok = 0;
 | 
						|
	if (pdir != NULL) {
 | 
						|
		if (pdir->st_uid == geteuid())
 | 
						|
			pdirwok = ((pdir->st_mode & S_IWUSR) != 0);
 | 
						|
		else if (matchgroup(pdir->st_gid))
 | 
						|
			pdirwok = ((pdir->st_mode & S_IWGRP) != 0);
 | 
						|
		else
 | 
						|
			pdirwok = ((pdir->st_mode & S_IWOTH) != 0);
 | 
						|
	}
 | 
						|
 | 
						|
			/* 'a': can APPE to file */
 | 
						|
	if (wok && CURCLASS_FLAGS_ISSET(upload) && S_ISREG(fe->stat->st_mode))
 | 
						|
		CPUTC('a', fd);
 | 
						|
 | 
						|
			/* 'c': can create or append to files in directory */
 | 
						|
	if (wok && CURCLASS_FLAGS_ISSET(modify) && S_ISDIR(fe->stat->st_mode))
 | 
						|
		CPUTC('c', fd);
 | 
						|
 | 
						|
			/* 'd': can delete file or directory */
 | 
						|
	if (pdirwok && CURCLASS_FLAGS_ISSET(modify)) {
 | 
						|
		int candel;
 | 
						|
 | 
						|
		candel = 1;
 | 
						|
		if (S_ISDIR(fe->stat->st_mode)) {
 | 
						|
			DIR *dirp;
 | 
						|
			struct dirent *dp;
 | 
						|
 | 
						|
			if ((dirp = opendir(fe->display)) == NULL)
 | 
						|
				candel = 0;
 | 
						|
			else {
 | 
						|
				while ((dp = readdir(dirp)) != NULL) {
 | 
						|
					if (ISDOTDIR(dp->d_name) ||
 | 
						|
					    ISDOTDOTDIR(dp->d_name))
 | 
						|
						continue;
 | 
						|
					candel = 0;
 | 
						|
					break;
 | 
						|
				}
 | 
						|
				closedir(dirp);
 | 
						|
			}
 | 
						|
		}
 | 
						|
		if (candel)
 | 
						|
			CPUTC('d', fd);
 | 
						|
	}
 | 
						|
 | 
						|
			/* 'e': can enter directory */
 | 
						|
	if (xok && S_ISDIR(fe->stat->st_mode))
 | 
						|
		CPUTC('e', fd);
 | 
						|
 | 
						|
			/* 'f': can rename file or directory */
 | 
						|
	if (pdirwok && CURCLASS_FLAGS_ISSET(modify))
 | 
						|
		CPUTC('f', fd);
 | 
						|
 | 
						|
			/* 'l': can list directory */
 | 
						|
	if (rok && xok && S_ISDIR(fe->stat->st_mode))
 | 
						|
		CPUTC('l', fd);
 | 
						|
 | 
						|
			/* 'm': can create directory */
 | 
						|
	if (wok && CURCLASS_FLAGS_ISSET(modify) && S_ISDIR(fe->stat->st_mode))
 | 
						|
		CPUTC('m', fd);
 | 
						|
 | 
						|
			/* 'p': can remove files in directory */
 | 
						|
	if (wok && CURCLASS_FLAGS_ISSET(modify) && S_ISDIR(fe->stat->st_mode))
 | 
						|
		CPUTC('p', fd);
 | 
						|
 | 
						|
			/* 'r': can RETR file */
 | 
						|
	if (rok && S_ISREG(fe->stat->st_mode))
 | 
						|
		CPUTC('r', fd);
 | 
						|
 | 
						|
			/* 'w': can STOR file */
 | 
						|
	if (wok && CURCLASS_FLAGS_ISSET(upload) && S_ISREG(fe->stat->st_mode))
 | 
						|
		CPUTC('w', fd);
 | 
						|
 | 
						|
	CPUTC(';', fd);
 | 
						|
}
 | 
						|
 | 
						|
static void
 | 
						|
fact_size(const char *fact, FILE *fd, factelem *fe)
 | 
						|
{
 | 
						|
 | 
						|
	if (S_ISREG(fe->stat->st_mode))
 | 
						|
		cprintf(fd, "%s=" LLF ";", fact, (LLT)fe->stat->st_size);
 | 
						|
}
 | 
						|
 | 
						|
static void
 | 
						|
fact_type(const char *fact, FILE *fd, factelem *fe)
 | 
						|
{
 | 
						|
 | 
						|
	cprintf(fd, "%s=", fact);
 | 
						|
	switch (fe->stat->st_mode & S_IFMT) {
 | 
						|
	case S_IFDIR:
 | 
						|
		if (fe->flags & FE_MLSD) {
 | 
						|
			if ((fe->flags & FE_ISCURDIR) || ISDOTDIR(fe->display))
 | 
						|
				cprintf(fd, "cdir");
 | 
						|
			else if (ISDOTDOTDIR(fe->display))
 | 
						|
				cprintf(fd, "pdir");
 | 
						|
			else
 | 
						|
				cprintf(fd, "dir");
 | 
						|
		} else {
 | 
						|
			cprintf(fd, "dir");
 | 
						|
		}
 | 
						|
		break;
 | 
						|
	case S_IFREG:
 | 
						|
		cprintf(fd, "file");
 | 
						|
		break;
 | 
						|
	case S_IFIFO:
 | 
						|
		cprintf(fd, "OS.unix=fifo");
 | 
						|
		break;
 | 
						|
	case S_IFLNK:		/* XXX: probably a NO-OP with stat() */
 | 
						|
		cprintf(fd, "OS.unix=slink");
 | 
						|
		break;
 | 
						|
	case S_IFSOCK:
 | 
						|
		cprintf(fd, "OS.unix=socket");
 | 
						|
		break;
 | 
						|
	case S_IFBLK:
 | 
						|
	case S_IFCHR:
 | 
						|
		cprintf(fd, "OS.unix=%s-" ULLF "/" ULLF,
 | 
						|
		    S_ISBLK(fe->stat->st_mode) ? "blk" : "chr",
 | 
						|
		    (ULLT)major(fe->stat->st_rdev),
 | 
						|
		    (ULLT)minor(fe->stat->st_rdev));
 | 
						|
		break;
 | 
						|
	default:
 | 
						|
		cprintf(fd, "OS.unix=UNKNOWN(0%o)", fe->stat->st_mode & S_IFMT);
 | 
						|
		break;
 | 
						|
	}
 | 
						|
	CPUTC(';', fd);
 | 
						|
}
 | 
						|
 | 
						|
static void
 | 
						|
fact_unique(const char *fact, FILE *fd, factelem *fe)
 | 
						|
{
 | 
						|
	char obuf[(sizeof(dev_t) + sizeof(ino_t) + 2) * 4 / 3 + 2];
 | 
						|
	char tbuf[sizeof(dev_t) + sizeof(ino_t)];
 | 
						|
 | 
						|
	memcpy(tbuf,
 | 
						|
	    (char *)&(fe->stat->st_dev), sizeof(dev_t));
 | 
						|
	memcpy(tbuf + sizeof(dev_t),
 | 
						|
	    (char *)&(fe->stat->st_ino), sizeof(ino_t));
 | 
						|
	base64_encode(tbuf, sizeof(dev_t) + sizeof(ino_t), obuf, 1);
 | 
						|
	cprintf(fd, "%s=%s;", fact, obuf);
 | 
						|
}
 | 
						|
 | 
						|
static int
 | 
						|
matchgroup(gid_t gid)
 | 
						|
{
 | 
						|
	int	i;
 | 
						|
 | 
						|
	for (i = 0; i < gidcount; i++)
 | 
						|
		if (gid == gidlist[i])
 | 
						|
			return(1);
 | 
						|
	return (0);
 | 
						|
}
 | 
						|
 | 
						|
static void
 | 
						|
mlsname(FILE *fp, factelem *fe)
 | 
						|
{
 | 
						|
	char realfile[MAXPATHLEN];
 | 
						|
	int userf = 0;
 | 
						|
	size_t i;
 | 
						|
 | 
						|
	for (i = 0; i < FACTTABSIZE; i++) {
 | 
						|
		if (facttab[i].enabled)
 | 
						|
			(facttab[i].display)(facttab[i].name, fp, fe);
 | 
						|
	}
 | 
						|
	if ((fe->flags & FE_MLSD) &&
 | 
						|
	    !(fe->flags & FE_ISCURDIR) && !ISDOTDIR(fe->display)) {
 | 
						|
			/* if MLSD and not "." entry, display as-is */
 | 
						|
		userf = 0;
 | 
						|
	} else {
 | 
						|
			/* if MLST, or MLSD and "." entry, realpath(3) it */
 | 
						|
		if (realpath(fe->display, realfile) != NULL)
 | 
						|
			userf = 1;
 | 
						|
	}
 | 
						|
	cprintf(fp, " %s\r\n", userf ? realfile : fe->display);
 | 
						|
}
 | 
						|
 | 
						|
static void
 | 
						|
replydirname(const char *name, const char *message)
 | 
						|
{
 | 
						|
	char *p, *ep;
 | 
						|
	char npath[MAXPATHLEN * 2];
 | 
						|
 | 
						|
	p = npath;
 | 
						|
	ep = &npath[sizeof(npath) - 1];
 | 
						|
	while (*name) {
 | 
						|
		if (*name == '"') {
 | 
						|
			if (ep - p < 2)
 | 
						|
				break;
 | 
						|
			*p++ = *name++;
 | 
						|
			*p++ = '"';
 | 
						|
		} else {
 | 
						|
			if (ep - p < 1)
 | 
						|
				break;
 | 
						|
			*p++ = *name++;
 | 
						|
		}
 | 
						|
	}
 | 
						|
	*p = '\0';
 | 
						|
	reply(257, "\"%s\" %s", npath, message);
 | 
						|
}
 | 
						|
 | 
						|
static void
 | 
						|
discover_path(char *last_path, const char *new_path) 
 | 
						|
{
 | 
						|
	char tp[MAXPATHLEN + 1] = "";
 | 
						|
	char tq[MAXPATHLEN + 1] = "";
 | 
						|
	char *cp;
 | 
						|
	char *cq; 
 | 
						|
	int sz1, sz2;
 | 
						|
	int nomorelink;
 | 
						|
	struct stat st1, st2;
 | 
						|
	
 | 
						|
	if (new_path[0] != '/') {
 | 
						|
		(void)strlcpy(tp, last_path, MAXPATHLEN);
 | 
						|
		(void)strlcat(tp, "/", MAXPATHLEN);
 | 
						|
	}
 | 
						|
	(void)strlcat(tp, new_path, MAXPATHLEN);
 | 
						|
	(void)strlcat(tp, "/", MAXPATHLEN);
 | 
						|
 | 
						|
	/* 
 | 
						|
	 * resolve symlinks. A symlink may introduce another symlink, so we
 | 
						|
	 * loop trying to resolve symlinks until we don't find any of them.
 | 
						|
	 */
 | 
						|
	do {
 | 
						|
		/* Collapse any // into / */
 | 
						|
		while ((cp = strstr(tp, "//")) != NULL)
 | 
						|
			(void)memmove(cp, cp + 1, strlen(cp) - 1 + 1);
 | 
						|
 | 
						|
		/* Collapse any /./ into / */
 | 
						|
		while ((cp = strstr(tp, "/./")) != NULL)
 | 
						|
			(void)memmove(cp, cp + 2, strlen(cp) - 2 + 1);
 | 
						|
 | 
						|
		cp = tp;
 | 
						|
		nomorelink = 1;
 | 
						|
		
 | 
						|
		while ((cp = strstr(++cp, "/")) != NULL) {
 | 
						|
			sz1 = (unsigned long)cp - (unsigned long)tp;
 | 
						|
			if (sz1 > MAXPATHLEN)
 | 
						|
				goto bad;
 | 
						|
			*cp = 0;
 | 
						|
			sz2 = readlink(tp, tq, MAXPATHLEN); 
 | 
						|
			*cp = '/';
 | 
						|
 | 
						|
			/* If this is not a symlink, move to next / */
 | 
						|
			if (sz2 <= 0)
 | 
						|
				continue;
 | 
						|
 | 
						|
			/*
 | 
						|
			 * We found a symlink, so we will have to 
 | 
						|
			 * do one more pass to check there is no 
 | 
						|
			 * more symlink in the path
 | 
						|
			 */
 | 
						|
			nomorelink = 0;
 | 
						|
 | 
						|
			/* 
 | 
						|
			 * Null terminate the string and remove trailing /
 | 
						|
			 */
 | 
						|
			tq[sz2] = 0;
 | 
						|
			sz2 = strlen(tq);
 | 
						|
			if (tq[sz2 - 1] == '/') 
 | 
						|
				tq[--sz2] = 0;
 | 
						|
 | 
						|
			/* 
 | 
						|
			 * Is this an absolute link or a relative link? 
 | 
						|
			 */
 | 
						|
			if (tq[0] == '/') {
 | 
						|
				/* absolute link */
 | 
						|
				if (strlen(cp) + sz2 > MAXPATHLEN)
 | 
						|
					goto bad;
 | 
						|
				memmove(tp + sz2, cp, strlen(cp) + 1);
 | 
						|
				memcpy(tp, tq, sz2);
 | 
						|
			} else {			
 | 
						|
				/* relative link */
 | 
						|
				for (cq = cp - 1; *cq != '/'; cq--);
 | 
						|
				if (strlen(tp) -
 | 
						|
				    ((unsigned long)cq - (unsigned long)cp)
 | 
						|
				    + 1 + sz2 > MAXPATHLEN)
 | 
						|
					goto bad;
 | 
						|
				(void)memmove(cq + 1 + sz2, 
 | 
						|
				    cp, strlen(cp) + 1);
 | 
						|
				(void)memcpy(cq + 1, tq, sz2);
 | 
						|
			}
 | 
						|
 | 
						|
			/* 
 | 
						|
			 * start over, looking for new symlinks 
 | 
						|
			 */
 | 
						|
			break;
 | 
						|
		}
 | 
						|
	} while (nomorelink == 0);
 | 
						|
 | 
						|
	/* Collapse any /foo/../ into /foo/ */
 | 
						|
	while ((cp = strstr(tp, "/../")) != NULL) {
 | 
						|
		/* ^/../foo/ becomes ^/foo/ */
 | 
						|
		if (cp == tp) {
 | 
						|
			(void)memmove(cp, cp + 3,
 | 
						|
			    strlen(cp) - 3 + 1);
 | 
						|
		} else {
 | 
						|
			for (cq = cp - 1; *cq != '/'; cq--);
 | 
						|
			(void)memmove(cq, cp + 3,
 | 
						|
			    strlen(cp) - 3 + 1);
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	/* strip strailing / */
 | 
						|
	if (strlen(tp) != 1)
 | 
						|
		tp[strlen(tp) - 1] = '\0';
 | 
						|
 | 
						|
	/* check that the path is correct */
 | 
						|
	stat(tp, &st1);
 | 
						|
	stat(".", &st2);
 | 
						|
	if ((st1.st_dev != st2.st_dev) || (st1.st_ino != st2.st_ino))
 | 
						|
		goto bad;
 | 
						|
 | 
						|
	(void)strlcpy(last_path, tp, MAXPATHLEN);
 | 
						|
	return;
 | 
						|
 | 
						|
bad:
 | 
						|
	(void)strlcat(last_path, "/", MAXPATHLEN);
 | 
						|
	(void)strlcat(last_path, new_path, MAXPATHLEN);
 | 
						|
	return;
 | 
						|
}
 | 
						|
 |