155682Smarkm/*
255682Smarkm * Copyright (c) 1985, 1988, 1990, 1992, 1993, 1994
355682Smarkm *	The Regents of the University of California.  All rights reserved.
455682Smarkm *
555682Smarkm * Redistribution and use in source and binary forms, with or without
655682Smarkm * modification, are permitted provided that the following conditions
755682Smarkm * are met:
855682Smarkm * 1. Redistributions of source code must retain the above copyright
955682Smarkm *    notice, this list of conditions and the following disclaimer.
1055682Smarkm * 2. Redistributions in binary form must reproduce the above copyright
1155682Smarkm *    notice, this list of conditions and the following disclaimer in the
1255682Smarkm *    documentation and/or other materials provided with the distribution.
1355682Smarkm * 3. All advertising materials mentioning features or use of this software
1455682Smarkm *    must display the following acknowledgement:
1555682Smarkm *	This product includes software developed by the University of
1655682Smarkm *	California, Berkeley and its contributors.
1755682Smarkm * 4. Neither the name of the University nor the names of its contributors
1855682Smarkm *    may be used to endorse or promote products derived from this software
1955682Smarkm *    without specific prior written permission.
2055682Smarkm *
2155682Smarkm * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
2255682Smarkm * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
2355682Smarkm * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
2455682Smarkm * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
2555682Smarkm * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
2655682Smarkm * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
2755682Smarkm * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
2855682Smarkm * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
2955682Smarkm * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
3055682Smarkm * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
3155682Smarkm * SUCH DAMAGE.
3255682Smarkm */
3355682Smarkm
3455682Smarkm#define	FTP_NAMES
3555682Smarkm#include "ftpd_locl.h"
3655682Smarkm#ifdef KRB5
3755682Smarkm#include <krb5.h>
3855682Smarkm#endif
3955682Smarkm#include "getarg.h"
4055682Smarkm
41178825SdfrRCSID("$Id: ftpd.c 21222 2007-06-20 10:11:14Z lha $");
4255682Smarkm
4355682Smarkmstatic char version[] = "Version 6.00";
4455682Smarkm
4555682Smarkmextern	off_t restart_point;
4655682Smarkmextern	char cbuf[];
4755682Smarkm
4855682Smarkmstruct  sockaddr_storage ctrl_addr_ss;
4955682Smarkmstruct  sockaddr *ctrl_addr = (struct sockaddr *)&ctrl_addr_ss;
5055682Smarkm
5155682Smarkmstruct  sockaddr_storage data_source_ss;
5255682Smarkmstruct  sockaddr *data_source = (struct sockaddr *)&data_source_ss;
5355682Smarkm
5455682Smarkmstruct  sockaddr_storage data_dest_ss;
5555682Smarkmstruct  sockaddr *data_dest = (struct sockaddr *)&data_dest_ss;
5655682Smarkm
5755682Smarkmstruct  sockaddr_storage his_addr_ss;
5855682Smarkmstruct  sockaddr *his_addr = (struct sockaddr *)&his_addr_ss;
5955682Smarkm
6055682Smarkmstruct  sockaddr_storage pasv_addr_ss;
6155682Smarkmstruct  sockaddr *pasv_addr = (struct sockaddr *)&pasv_addr_ss;
6255682Smarkm
6355682Smarkmint	data;
6455682Smarkmint	logged_in;
6555682Smarkmstruct	passwd *pw;
6655682Smarkmint	debug = 0;
6755682Smarkmint	ftpd_timeout = 900;    /* timeout after 15 minutes of inactivity */
6855682Smarkmint	maxtimeout = 7200;/* don't allow idle time to be set beyond 2 hours */
6990926Snectarint	restricted_data_ports = 1;
7055682Smarkmint	logging;
7155682Smarkmint	guest;
7255682Smarkmint	dochroot;
7355682Smarkmint	type;
7455682Smarkmint	form;
7555682Smarkmint	stru;			/* avoid C keyword */
7655682Smarkmint	mode;
7755682Smarkmint	usedefault = 1;		/* for data transfers */
7855682Smarkmint	pdata = -1;		/* for passive mode */
79142403Snectarint	allow_insecure_oob = 1;
80142403Snectarstatic int transflag;
81142403Snectarstatic int urgflag;
8255682Smarkmoff_t	file_size;
8355682Smarkmoff_t	byte_count;
8455682Smarkm#if !defined(CMASK) || CMASK == 0
8555682Smarkm#undef CMASK
8655682Smarkm#define CMASK 027
8755682Smarkm#endif
8855682Smarkmint	defumask = CMASK;		/* default umask value */
8955682Smarkmint	guest_umask = 0777;	/* Paranoia for anonymous users */
9055682Smarkmchar	tmpline[10240];
9155682Smarkmchar	hostname[MaxHostNameLen];
9255682Smarkmchar	remotehost[MaxHostNameLen];
9355682Smarkmstatic char ttyline[20];
9455682Smarkm
9555682Smarkm#define AUTH_PLAIN	(1 << 0) /* allow sending passwords */
9655682Smarkm#define AUTH_OTP	(1 << 1) /* passwords are one-time */
9755682Smarkm#define AUTH_FTP	(1 << 2) /* allow anonymous login */
9855682Smarkm
9955682Smarkmstatic int auth_level = 0; /* Only allow kerberos login by default */
10055682Smarkm
10155682Smarkm/*
10255682Smarkm * Timeout intervals for retrying connections
10355682Smarkm * to hosts that don't accept PORT cmds.  This
10455682Smarkm * is a kludge, but given the problems with TCP...
10555682Smarkm */
10655682Smarkm#define	SWAITMAX	90	/* wait at most 90 seconds */
10755682Smarkm#define	SWAITINT	5	/* interval between retries */
10855682Smarkm
10955682Smarkmint	swaitmax = SWAITMAX;
11055682Smarkmint	swaitint = SWAITINT;
11155682Smarkm
11255682Smarkm#ifdef HAVE_SETPROCTITLE
11355682Smarkmchar	proctitle[BUFSIZ];	/* initial part of title */
11455682Smarkm#endif /* HAVE_SETPROCTITLE */
11555682Smarkm
11655682Smarkm#define LOGCMD(cmd, file) \
11755682Smarkm	if (logging > 1) \
11855682Smarkm	    syslog(LOG_INFO,"%s %s%s", cmd, \
11955682Smarkm		*(file) == '/' ? "" : curdir(), file);
12055682Smarkm#define LOGCMD2(cmd, file1, file2) \
12155682Smarkm	 if (logging > 1) \
12255682Smarkm	    syslog(LOG_INFO,"%s %s%s %s%s", cmd, \
12355682Smarkm		*(file1) == '/' ? "" : curdir(), file1, \
12455682Smarkm		*(file2) == '/' ? "" : curdir(), file2);
12555682Smarkm#define LOGBYTES(cmd, file, cnt) \
12655682Smarkm	if (logging > 1) { \
12755682Smarkm		if (cnt == (off_t)-1) \
12855682Smarkm		    syslog(LOG_INFO,"%s %s%s", cmd, \
12955682Smarkm			*(file) == '/' ? "" : curdir(), file); \
13055682Smarkm		else \
13155682Smarkm		    syslog(LOG_INFO, "%s %s%s = %ld bytes", \
13255682Smarkm			cmd, (*(file) == '/') ? "" : curdir(), file, (long)cnt); \
13355682Smarkm	}
13455682Smarkm
13555682Smarkmstatic void	 ack (char *);
13655682Smarkmstatic void	 myoob (int);
137142403Snectarstatic int	 handleoobcmd(void);
13855682Smarkmstatic int	 checkuser (char *, char *);
13955682Smarkmstatic int	 checkaccess (char *);
14055682Smarkmstatic FILE	*dataconn (const char *, off_t, const char *);
141178825Sdfrstatic void	 dolog (struct sockaddr *, int);
14255682Smarkmstatic void	 end_login (void);
143178825Sdfrstatic FILE	*getdatasock (const char *, int);
14455682Smarkmstatic char	*gunique (char *);
14555682Smarkmstatic RETSIGTYPE	 lostconn (int);
14655682Smarkmstatic int	 receive_data (FILE *, FILE *);
14755682Smarkmstatic void	 send_data (FILE *, FILE *);
14855682Smarkmstatic struct passwd * sgetpwnam (char *);
14955682Smarkm
15055682Smarkmstatic char *
15155682Smarkmcurdir(void)
15255682Smarkm{
15355682Smarkm	static char path[MaxPathLen+1];	/* path + '/' + '\0' */
15455682Smarkm
15555682Smarkm	if (getcwd(path, sizeof(path)-1) == NULL)
15655682Smarkm		return ("");
15755682Smarkm	if (path[1] != '\0')		/* special case for root dir. */
15855682Smarkm		strlcat(path, "/", sizeof(path));
15955682Smarkm	/* For guest account, skip / since it's chrooted */
16055682Smarkm	return (guest ? path+1 : path);
16155682Smarkm}
16255682Smarkm
16355682Smarkm#ifndef LINE_MAX
16455682Smarkm#define LINE_MAX 1024
16555682Smarkm#endif
16655682Smarkm
16755682Smarkmstatic int
16855682Smarkmparse_auth_level(char *str)
16955682Smarkm{
17055682Smarkm    char *p;
17155682Smarkm    int ret = 0;
17255682Smarkm    char *foo = NULL;
17355682Smarkm
17455682Smarkm    for(p = strtok_r(str, ",", &foo);
17555682Smarkm	p;
17655682Smarkm	p = strtok_r(NULL, ",", &foo)) {
17755682Smarkm	if(strcmp(p, "user") == 0)
17855682Smarkm	    ;
17955682Smarkm#ifdef OTP
18055682Smarkm	else if(strcmp(p, "otp") == 0)
18155682Smarkm	    ret |= AUTH_PLAIN|AUTH_OTP;
18255682Smarkm#endif
18355682Smarkm	else if(strcmp(p, "ftp") == 0 ||
18455682Smarkm		strcmp(p, "safe") == 0)
18555682Smarkm	    ret |= AUTH_FTP;
18655682Smarkm	else if(strcmp(p, "plain") == 0)
18755682Smarkm	    ret |= AUTH_PLAIN;
18855682Smarkm	else if(strcmp(p, "none") == 0)
18955682Smarkm	    ret |= AUTH_PLAIN|AUTH_FTP;
19055682Smarkm	else
19155682Smarkm	    warnx("bad value for -a: `%s'", p);
19255682Smarkm    }
19355682Smarkm    return ret;
19455682Smarkm}
19555682Smarkm
19655682Smarkm/*
19755682Smarkm * Print usage and die.
19855682Smarkm */
19955682Smarkm
20055682Smarkmstatic int interactive_flag;
20155682Smarkmstatic char *guest_umask_string;
20255682Smarkmstatic char *port_string;
20355682Smarkmstatic char *umask_string;
20455682Smarkmstatic char *auth_string;
20555682Smarkm
20655682Smarkmint use_builtin_ls = -1;
20755682Smarkm
20855682Smarkmstatic int help_flag;
20955682Smarkmstatic int version_flag;
21055682Smarkm
21172445Sassarstatic const char *good_chars = "+-=_,.";
21272445Sassar
21355682Smarkmstruct getargs args[] = {
21455682Smarkm    { NULL, 'a', arg_string, &auth_string, "required authentication" },
21555682Smarkm    { NULL, 'i', arg_flag, &interactive_flag, "don't assume stdin is a socket" },
21655682Smarkm    { NULL, 'p', arg_string, &port_string, "what port to listen to" },
21755682Smarkm    { NULL, 'g', arg_string, &guest_umask_string, "umask for guest logins" },
21855682Smarkm    { NULL, 'l', arg_counter, &logging, "log more stuff", "" },
21955682Smarkm    { NULL, 't', arg_integer, &ftpd_timeout, "initial timeout" },
22055682Smarkm    { NULL, 'T', arg_integer, &maxtimeout, "max timeout" },
22155682Smarkm    { NULL, 'u', arg_string, &umask_string, "umask for user logins" },
22290926Snectar    { NULL, 'U', arg_negative_flag, &restricted_data_ports, "don't use high data ports" },
22372445Sassar    { NULL, 'd', arg_flag, &debug, "enable debugging" },
22472445Sassar    { NULL, 'v', arg_flag, &debug, "enable debugging" },
22555682Smarkm    { "builtin-ls", 'B', arg_flag, &use_builtin_ls, "use built-in ls to list files" },
22672445Sassar    { "good-chars", 0, arg_string, &good_chars, "allowed anonymous upload filename chars" },
227142403Snectar    { "insecure-oob", 'I', arg_negative_flag, &allow_insecure_oob, "don't allow insecure OOB ABOR/STAT" },
228127808Snectar#ifdef KRB5
229127808Snectar    { "gss-bindings", 0,  arg_flag, &ftp_do_gss_bindings, "Require GSS-API bindings", NULL},
230127808Snectar#endif
23155682Smarkm    { "version", 0, arg_flag, &version_flag },
23255682Smarkm    { "help", 'h', arg_flag, &help_flag }
23355682Smarkm};
23455682Smarkm
23555682Smarkmstatic int num_args = sizeof(args) / sizeof(args[0]);
23655682Smarkm
23755682Smarkmstatic void
23855682Smarkmusage (int code)
23955682Smarkm{
24055682Smarkm    arg_printusage(args, num_args, NULL, "");
24155682Smarkm    exit (code);
24255682Smarkm}
24355682Smarkm
24455682Smarkm/* output contents of a file */
24555682Smarkmstatic int
24655682Smarkmshow_file(const char *file, int code)
24755682Smarkm{
24855682Smarkm    FILE *f;
24955682Smarkm    char buf[128];
25055682Smarkm
25155682Smarkm    f = fopen(file, "r");
25255682Smarkm    if(f == NULL)
25355682Smarkm	return -1;
25455682Smarkm    while(fgets(buf, sizeof(buf), f)){
25555682Smarkm	buf[strcspn(buf, "\r\n")] = '\0';
25655682Smarkm	lreply(code, "%s", buf);
25755682Smarkm    }
25855682Smarkm    fclose(f);
25955682Smarkm    return 0;
26055682Smarkm}
26155682Smarkm
26255682Smarkmint
26355682Smarkmmain(int argc, char **argv)
26455682Smarkm{
26572445Sassar    socklen_t his_addr_len, ctrl_addr_len;
26672445Sassar    int on = 1;
26755682Smarkm    int port;
26855682Smarkm    struct servent *sp;
26955682Smarkm
27055682Smarkm    int optind = 0;
27155682Smarkm
27278527Sassar    setprogname (argv[0]);
27355682Smarkm
27455682Smarkm    /* detach from any tickets and tokens */
27555682Smarkm    {
27672445Sassar#ifdef KRB4
27755682Smarkm	char tkfile[1024];
27855682Smarkm	snprintf(tkfile, sizeof(tkfile),
27955682Smarkm		 "/tmp/ftp_%u", (unsigned)getpid());
28055682Smarkm	krb_set_tkt_string(tkfile);
28172445Sassar#endif
282127808Snectar    }
28372445Sassar
28455682Smarkm    if(getarg(args, num_args, argc, argv, &optind))
28555682Smarkm	usage(1);
28655682Smarkm
28755682Smarkm    if(help_flag)
28855682Smarkm	usage(0);
28955682Smarkm
29055682Smarkm    if(version_flag) {
29155682Smarkm	print_version(NULL);
29255682Smarkm	exit(0);
29355682Smarkm    }
29455682Smarkm
29555682Smarkm    if(auth_string)
29655682Smarkm	auth_level = parse_auth_level(auth_string);
29755682Smarkm    {
29855682Smarkm	char *p;
29955682Smarkm	long val = 0;
30055682Smarkm
30155682Smarkm	if(guest_umask_string) {
30255682Smarkm	    val = strtol(guest_umask_string, &p, 8);
30355682Smarkm	    if (*p != '\0' || val < 0)
30455682Smarkm		warnx("bad value for -g");
30555682Smarkm	    else
30655682Smarkm		guest_umask = val;
30755682Smarkm	}
30855682Smarkm	if(umask_string) {
30955682Smarkm	    val = strtol(umask_string, &p, 8);
31055682Smarkm	    if (*p != '\0' || val < 0)
31155682Smarkm		warnx("bad value for -u");
31255682Smarkm	    else
31355682Smarkm		defumask = val;
31455682Smarkm	}
31555682Smarkm    }
316102644Snectar    sp = getservbyname("ftp", "tcp");
317102644Snectar    if(sp)
318102644Snectar	port = sp->s_port;
319102644Snectar    else
320102644Snectar	port = htons(21);
32155682Smarkm    if(port_string) {
32255682Smarkm	sp = getservbyname(port_string, "tcp");
32355682Smarkm	if(sp)
32455682Smarkm	    port = sp->s_port;
32555682Smarkm	else
326120945Snectar	    if(isdigit((unsigned char)port_string[0]))
32755682Smarkm		port = htons(atoi(port_string));
32855682Smarkm	    else
32955682Smarkm		warnx("bad value for -p");
33055682Smarkm    }
33155682Smarkm
33255682Smarkm    if (maxtimeout < ftpd_timeout)
33355682Smarkm	maxtimeout = ftpd_timeout;
33455682Smarkm
33555682Smarkm#if 0
33655682Smarkm    if (ftpd_timeout > maxtimeout)
33755682Smarkm	ftpd_timeout = maxtimeout;
33855682Smarkm#endif
33955682Smarkm
34055682Smarkm    if(interactive_flag)
34155682Smarkm	mini_inetd (port);
34255682Smarkm
34355682Smarkm    /*
34455682Smarkm     * LOG_NDELAY sets up the logging connection immediately,
34555682Smarkm     * necessary for anonymous ftp's that chroot and can't do it later.
34655682Smarkm     */
34755682Smarkm    openlog("ftpd", LOG_PID | LOG_NDELAY, LOG_FTP);
34855682Smarkm    his_addr_len = sizeof(his_addr_ss);
34955682Smarkm    if (getpeername(STDIN_FILENO, his_addr, &his_addr_len) < 0) {
35055682Smarkm	syslog(LOG_ERR, "getpeername (%s): %m",argv[0]);
35155682Smarkm	exit(1);
35255682Smarkm    }
35355682Smarkm    ctrl_addr_len = sizeof(ctrl_addr_ss);
35455682Smarkm    if (getsockname(STDIN_FILENO, ctrl_addr, &ctrl_addr_len) < 0) {
35555682Smarkm	syslog(LOG_ERR, "getsockname (%s): %m",argv[0]);
35655682Smarkm	exit(1);
35755682Smarkm    }
35855682Smarkm#if defined(IP_TOS) && defined(HAVE_SETSOCKOPT)
35972445Sassar    {
36072445Sassar	int tos = IPTOS_LOWDELAY;
36172445Sassar
36272445Sassar	if (setsockopt(STDIN_FILENO, IPPROTO_IP, IP_TOS,
36372445Sassar		       (void *)&tos, sizeof(int)) < 0)
36472445Sassar	    syslog(LOG_WARNING, "setsockopt (IP_TOS): %m");
36572445Sassar    }
36655682Smarkm#endif
36755682Smarkm    data_source->sa_family = ctrl_addr->sa_family;
36855682Smarkm    socket_set_port (data_source,
36955682Smarkm		     htons(ntohs(socket_get_port(ctrl_addr)) - 1));
37055682Smarkm
37155682Smarkm    /* set this here so it can be put in wtmp */
37255682Smarkm    snprintf(ttyline, sizeof(ttyline), "ftp%u", (unsigned)getpid());
37355682Smarkm
37455682Smarkm
37555682Smarkm    /*	freopen(_PATH_DEVNULL, "w", stderr); */
37655682Smarkm    signal(SIGPIPE, lostconn);
37755682Smarkm    signal(SIGCHLD, SIG_IGN);
37855682Smarkm#ifdef SIGURG
37955682Smarkm    if (signal(SIGURG, myoob) == SIG_ERR)
38055682Smarkm	syslog(LOG_ERR, "signal: %m");
38155682Smarkm#endif
38255682Smarkm
38355682Smarkm    /* Try to handle urgent data inline */
38455682Smarkm#if defined(SO_OOBINLINE) && defined(HAVE_SETSOCKOPT)
38555682Smarkm    if (setsockopt(0, SOL_SOCKET, SO_OOBINLINE, (void *)&on,
38655682Smarkm		   sizeof(on)) < 0)
38755682Smarkm	syslog(LOG_ERR, "setsockopt: %m");
38855682Smarkm#endif
38955682Smarkm
39055682Smarkm#ifdef	F_SETOWN
39155682Smarkm    if (fcntl(fileno(stdin), F_SETOWN, getpid()) == -1)
39255682Smarkm	syslog(LOG_ERR, "fcntl F_SETOWN: %m");
39355682Smarkm#endif
39455682Smarkm    dolog(his_addr, his_addr_len);
39555682Smarkm    /*
39655682Smarkm     * Set up default state
39755682Smarkm     */
39855682Smarkm    data = -1;
39955682Smarkm    type = TYPE_A;
40055682Smarkm    form = FORM_N;
40155682Smarkm    stru = STRU_F;
40255682Smarkm    mode = MODE_S;
40355682Smarkm    tmpline[0] = '\0';
40455682Smarkm
40555682Smarkm    /* If logins are disabled, print out the message. */
40655682Smarkm    if(show_file(_PATH_NOLOGIN, 530) == 0) {
40755682Smarkm	reply(530, "System not available.");
40855682Smarkm	exit(0);
40955682Smarkm    }
41055682Smarkm    show_file(_PATH_FTPWELCOME, 220);
41155682Smarkm    /* reply(220,) must follow */
41255682Smarkm    gethostname(hostname, sizeof(hostname));
41355682Smarkm
41455682Smarkm    reply(220, "%s FTP server (%s"
41555682Smarkm#ifdef KRB5
41655682Smarkm	  "+%s"
41755682Smarkm#endif
41855682Smarkm#ifdef KRB4
41955682Smarkm	  "+%s"
42055682Smarkm#endif
42155682Smarkm	  ") ready.", hostname, version
42255682Smarkm#ifdef KRB5
42355682Smarkm	  ,heimdal_version
42455682Smarkm#endif
42555682Smarkm#ifdef KRB4
42655682Smarkm	  ,krb4_version
42755682Smarkm#endif
42855682Smarkm	  );
42955682Smarkm
43055682Smarkm    for (;;)
43155682Smarkm	yyparse();
43255682Smarkm    /* NOTREACHED */
43355682Smarkm}
43455682Smarkm
43555682Smarkmstatic RETSIGTYPE
43655682Smarkmlostconn(int signo)
43755682Smarkm{
43855682Smarkm
43955682Smarkm	if (debug)
44055682Smarkm		syslog(LOG_DEBUG, "lost connection");
44155682Smarkm	dologout(-1);
44255682Smarkm}
44355682Smarkm
44455682Smarkm/*
44555682Smarkm * Helper function for sgetpwnam().
44655682Smarkm */
44755682Smarkmstatic char *
44855682Smarkmsgetsave(char *s)
44955682Smarkm{
45055682Smarkm	char *new = strdup(s);
45155682Smarkm
45255682Smarkm	if (new == NULL) {
45355682Smarkm		perror_reply(421, "Local resource failure: malloc");
45455682Smarkm		dologout(1);
45555682Smarkm		/* NOTREACHED */
45655682Smarkm	}
45755682Smarkm	return new;
45855682Smarkm}
45955682Smarkm
46055682Smarkm/*
46155682Smarkm * Save the result of a getpwnam.  Used for USER command, since
46255682Smarkm * the data returned must not be clobbered by any other command
46355682Smarkm * (e.g., globbing).
46455682Smarkm */
46555682Smarkmstatic struct passwd *
46655682Smarkmsgetpwnam(char *name)
46755682Smarkm{
46855682Smarkm	static struct passwd save;
46955682Smarkm	struct passwd *p;
47055682Smarkm
47155682Smarkm	if ((p = k_getpwnam(name)) == NULL)
47255682Smarkm		return (p);
47355682Smarkm	if (save.pw_name) {
47455682Smarkm		free(save.pw_name);
47555682Smarkm		free(save.pw_passwd);
47655682Smarkm		free(save.pw_gecos);
47755682Smarkm		free(save.pw_dir);
47855682Smarkm		free(save.pw_shell);
47955682Smarkm	}
48055682Smarkm	save = *p;
48155682Smarkm	save.pw_name = sgetsave(p->pw_name);
48255682Smarkm	save.pw_passwd = sgetsave(p->pw_passwd);
48355682Smarkm	save.pw_gecos = sgetsave(p->pw_gecos);
48455682Smarkm	save.pw_dir = sgetsave(p->pw_dir);
48555682Smarkm	save.pw_shell = sgetsave(p->pw_shell);
48655682Smarkm	return (&save);
48755682Smarkm}
48855682Smarkm
48955682Smarkmstatic int login_attempts;	/* number of failed login attempts */
49055682Smarkmstatic int askpasswd;		/* had user command, ask for passwd */
49155682Smarkmstatic char curname[10];	/* current USER name */
49255682Smarkm#ifdef OTP
49355682SmarkmOtpContext otp_ctx;
49455682Smarkm#endif
49555682Smarkm
49655682Smarkm/*
49755682Smarkm * USER command.
49855682Smarkm * Sets global passwd pointer pw if named account exists and is acceptable;
49955682Smarkm * sets askpasswd if a PASS command is expected.  If logged in previously,
50055682Smarkm * need to reset state.  If name is "ftp" or "anonymous", the name is not in
50155682Smarkm * _PATH_FTPUSERS, and ftp account exists, set guest and pw, then just return.
50255682Smarkm * If account doesn't exist, ask for passwd anyway.  Otherwise, check user
50355682Smarkm * requesting login privileges.  Disallow anyone who does not have a standard
50455682Smarkm * shell as returned by getusershell().  Disallow anyone mentioned in the file
50555682Smarkm * _PATH_FTPUSERS to allow people such as root and uucp to be avoided.
50655682Smarkm */
50755682Smarkmvoid
50855682Smarkmuser(char *name)
50955682Smarkm{
51055682Smarkm	char *cp, *shell;
51155682Smarkm
51255682Smarkm	if(auth_level == 0 && !sec_complete){
51355682Smarkm	    reply(530, "No login allowed without authorization.");
51455682Smarkm	    return;
51555682Smarkm	}
51655682Smarkm
51755682Smarkm	if (logged_in) {
51855682Smarkm		if (guest) {
51955682Smarkm			reply(530, "Can't change user from guest login.");
52055682Smarkm			return;
52155682Smarkm		} else if (dochroot) {
52255682Smarkm			reply(530, "Can't change user from chroot user.");
52355682Smarkm			return;
52455682Smarkm		}
52555682Smarkm		end_login();
52655682Smarkm	}
52755682Smarkm
52855682Smarkm	guest = 0;
52955682Smarkm	if (strcmp(name, "ftp") == 0 || strcmp(name, "anonymous") == 0) {
53055682Smarkm	    if ((auth_level & AUTH_FTP) == 0 ||
53155682Smarkm		checkaccess("ftp") ||
53255682Smarkm		checkaccess("anonymous"))
53355682Smarkm		reply(530, "User %s access denied.", name);
53455682Smarkm	    else if ((pw = sgetpwnam("ftp")) != NULL) {
53555682Smarkm		guest = 1;
53655682Smarkm		defumask = guest_umask;	/* paranoia for incoming */
53755682Smarkm		askpasswd = 1;
53855682Smarkm		reply(331, "Guest login ok, type your name as password.");
53955682Smarkm	    } else
54055682Smarkm		reply(530, "User %s unknown.", name);
54155682Smarkm	    if (!askpasswd && logging) {
54255682Smarkm		char data_addr[256];
54355682Smarkm
54455682Smarkm		if (inet_ntop (his_addr->sa_family,
54555682Smarkm			       socket_get_address(his_addr),
54655682Smarkm			       data_addr, sizeof(data_addr)) == NULL)
54755682Smarkm			strlcpy (data_addr, "unknown address",
54855682Smarkm					 sizeof(data_addr));
54955682Smarkm
55055682Smarkm		syslog(LOG_NOTICE,
55155682Smarkm		       "ANONYMOUS FTP LOGIN REFUSED FROM %s(%s)",
55255682Smarkm		       remotehost, data_addr);
55355682Smarkm	    }
55455682Smarkm	    return;
55555682Smarkm	}
55655682Smarkm	if((auth_level & AUTH_PLAIN) == 0 && !sec_complete){
55755682Smarkm	    reply(530, "Only authorized and anonymous login allowed.");
55855682Smarkm	    return;
55955682Smarkm	}
56055682Smarkm	if ((pw = sgetpwnam(name))) {
56155682Smarkm		if ((shell = pw->pw_shell) == NULL || *shell == 0)
56255682Smarkm			shell = _PATH_BSHELL;
56355682Smarkm		while ((cp = getusershell()) != NULL)
56455682Smarkm			if (strcmp(cp, shell) == 0)
56555682Smarkm				break;
56655682Smarkm		endusershell();
56755682Smarkm
56855682Smarkm		if (cp == NULL || checkaccess(name)) {
56955682Smarkm			reply(530, "User %s access denied.", name);
57055682Smarkm			if (logging) {
57155682Smarkm				char data_addr[256];
57255682Smarkm
57355682Smarkm				if (inet_ntop (his_addr->sa_family,
57455682Smarkm					       socket_get_address(his_addr),
57555682Smarkm					       data_addr,
57655682Smarkm					       sizeof(data_addr)) == NULL)
57755682Smarkm					strlcpy (data_addr,
57855682Smarkm							 "unknown address",
57955682Smarkm							 sizeof(data_addr));
58055682Smarkm
58155682Smarkm				syslog(LOG_NOTICE,
58255682Smarkm				       "FTP LOGIN REFUSED FROM %s(%s), %s",
58355682Smarkm				       remotehost,
58455682Smarkm				       data_addr,
58555682Smarkm				       name);
58655682Smarkm			}
58755682Smarkm			pw = (struct passwd *) NULL;
58855682Smarkm			return;
58955682Smarkm		}
59055682Smarkm	}
59155682Smarkm	if (logging)
59255682Smarkm	    strlcpy(curname, name, sizeof(curname));
59355682Smarkm	if(sec_complete) {
594178825Sdfr	    if(sec_userok(name) == 0) {
59555682Smarkm		do_login(232, name);
596178825Sdfr		sec_session(name);
597178825Sdfr	    } else
59855682Smarkm		reply(530, "User %s access denied.", name);
59955682Smarkm	} else {
600178825Sdfr#ifdef OTP
60155682Smarkm		char ss[256];
60255682Smarkm
60355682Smarkm		if (otp_challenge(&otp_ctx, name, ss, sizeof(ss)) == 0) {
60455682Smarkm			reply(331, "Password %s for %s required.",
60555682Smarkm			      ss, name);
60655682Smarkm			askpasswd = 1;
60755682Smarkm		} else
60855682Smarkm#endif
60955682Smarkm		if ((auth_level & AUTH_OTP) == 0) {
61055682Smarkm		    reply(331, "Password required for %s.", name);
61155682Smarkm		    askpasswd = 1;
61255682Smarkm		} else {
613178825Sdfr#ifdef OTP
61455682Smarkm		    char *s;
615178825Sdfr
61655682Smarkm		    if ((s = otp_error (&otp_ctx)) != NULL)
61755682Smarkm			lreply(530, "OTP: %s", s);
61855682Smarkm#endif
61955682Smarkm		    reply(530,
62055682Smarkm			  "Only authorized, anonymous"
62155682Smarkm#ifdef OTP
62255682Smarkm			  " and OTP "
62355682Smarkm#endif
62455682Smarkm			  "login allowed.");
62555682Smarkm		}
62655682Smarkm
62755682Smarkm	}
62855682Smarkm	/*
62955682Smarkm	 * Delay before reading passwd after first failed
63055682Smarkm	 * attempt to slow down passwd-guessing programs.
63155682Smarkm	 */
63255682Smarkm	if (login_attempts)
63355682Smarkm		sleep(login_attempts);
63455682Smarkm}
63555682Smarkm
63655682Smarkm/*
63755682Smarkm * Check if a user is in the file "fname"
63855682Smarkm */
63955682Smarkmstatic int
64055682Smarkmcheckuser(char *fname, char *name)
64155682Smarkm{
64255682Smarkm	FILE *fd;
64355682Smarkm	int found = 0;
64455682Smarkm	char *p, line[BUFSIZ];
64555682Smarkm
64655682Smarkm	if ((fd = fopen(fname, "r")) != NULL) {
64755682Smarkm		while (fgets(line, sizeof(line), fd) != NULL)
64855682Smarkm			if ((p = strchr(line, '\n')) != NULL) {
64955682Smarkm				*p = '\0';
65055682Smarkm				if (line[0] == '#')
65155682Smarkm					continue;
65255682Smarkm				if (strcmp(line, name) == 0) {
65355682Smarkm					found = 1;
65455682Smarkm					break;
65555682Smarkm				}
65655682Smarkm			}
65755682Smarkm		fclose(fd);
65855682Smarkm	}
65955682Smarkm	return (found);
66055682Smarkm}
66155682Smarkm
66255682Smarkm
66355682Smarkm/*
66455682Smarkm * Determine whether a user has access, based on information in
66555682Smarkm * _PATH_FTPUSERS. The users are listed one per line, with `allow'
66655682Smarkm * or `deny' after the username. If anything other than `allow', or
66755682Smarkm * just nothing, is given after the username, `deny' is assumed.
66855682Smarkm *
66955682Smarkm * If the user is not found in the file, but the pseudo-user `*' is,
67055682Smarkm * the permission is taken from that line.
67155682Smarkm *
67255682Smarkm * This preserves the old semantics where if a user was listed in the
67355682Smarkm * file he was denied, otherwise he was allowed.
67455682Smarkm *
67555682Smarkm * Return 1 if the user is denied, or 0 if he is allowed.  */
67655682Smarkm
67755682Smarkmstatic int
67855682Smarkmmatch(const char *pattern, const char *string)
67955682Smarkm{
68055682Smarkm    return fnmatch(pattern, string, FNM_NOESCAPE);
68155682Smarkm}
68255682Smarkm
68355682Smarkmstatic int
68455682Smarkmcheckaccess(char *name)
68555682Smarkm{
68655682Smarkm#define ALLOWED		0
68755682Smarkm#define	NOT_ALLOWED	1
68855682Smarkm    FILE *fd;
68955682Smarkm    int allowed = ALLOWED;
69055682Smarkm    char *user, *perm, line[BUFSIZ];
69155682Smarkm    char *foo;
69255682Smarkm
69355682Smarkm    fd = fopen(_PATH_FTPUSERS, "r");
69455682Smarkm
69555682Smarkm    if(fd == NULL)
69655682Smarkm	return allowed;
69755682Smarkm
69855682Smarkm    while (fgets(line, sizeof(line), fd) != NULL)  {
69955682Smarkm	foo = NULL;
70055682Smarkm	user = strtok_r(line, " \t\n", &foo);
70155682Smarkm	if (user == NULL || user[0] == '#')
70255682Smarkm	    continue;
70355682Smarkm	perm = strtok_r(NULL, " \t\n", &foo);
70455682Smarkm	if (match(user, name) == 0){
70555682Smarkm	    if(perm && strcmp(perm, "allow") == 0)
70655682Smarkm		allowed = ALLOWED;
70755682Smarkm	    else
70855682Smarkm		allowed = NOT_ALLOWED;
70955682Smarkm	    break;
71055682Smarkm	}
71155682Smarkm    }
71255682Smarkm    fclose(fd);
71355682Smarkm    return allowed;
71455682Smarkm}
71555682Smarkm#undef	ALLOWED
71655682Smarkm#undef	NOT_ALLOWED
71755682Smarkm
71855682Smarkm
71955682Smarkmint do_login(int code, char *passwd)
72055682Smarkm{
72155682Smarkm    login_attempts = 0;		/* this time successful */
72255682Smarkm    if (setegid((gid_t)pw->pw_gid) < 0) {
72355682Smarkm	reply(550, "Can't set gid.");
72455682Smarkm	return -1;
72555682Smarkm    }
72655682Smarkm    initgroups(pw->pw_name, pw->pw_gid);
727178825Sdfr#if defined(KRB4) || defined(KRB5)
728178825Sdfr    if(k_hasafs())
729178825Sdfr	k_setpag();
730178825Sdfr#endif
73155682Smarkm
73255682Smarkm    /* open wtmp before chroot */
73355682Smarkm    ftpd_logwtmp(ttyline, pw->pw_name, remotehost);
73455682Smarkm    logged_in = 1;
73555682Smarkm
73655682Smarkm    dochroot = checkuser(_PATH_FTPCHROOT, pw->pw_name);
73755682Smarkm    if (guest) {
73855682Smarkm	/*
73955682Smarkm	 * We MUST do a chdir() after the chroot. Otherwise
74055682Smarkm	 * the old current directory will be accessible as "."
74155682Smarkm	 * outside the new root!
74255682Smarkm	 */
74355682Smarkm	if (chroot(pw->pw_dir) < 0 || chdir("/") < 0) {
74455682Smarkm	    reply(550, "Can't set guest privileges.");
74555682Smarkm	    return -1;
74655682Smarkm	}
74755682Smarkm    } else if (dochroot) {
74855682Smarkm	if (chroot(pw->pw_dir) < 0 || chdir("/") < 0) {
74955682Smarkm	    reply(550, "Can't change root.");
75055682Smarkm	    return -1;
75155682Smarkm	}
75255682Smarkm    } else if (chdir(pw->pw_dir) < 0) {
75355682Smarkm	if (chdir("/") < 0) {
75455682Smarkm	    reply(530, "User %s: can't change directory to %s.",
75555682Smarkm		  pw->pw_name, pw->pw_dir);
75655682Smarkm	    return -1;
75755682Smarkm	} else
75855682Smarkm	    lreply(code, "No directory! Logging in with home=/");
75955682Smarkm    }
76055682Smarkm    if (seteuid((uid_t)pw->pw_uid) < 0) {
76155682Smarkm	reply(550, "Can't set uid.");
76255682Smarkm	return -1;
76355682Smarkm    }
76455682Smarkm
76555682Smarkm    if(use_builtin_ls == -1) {
76655682Smarkm	struct stat st;
76755682Smarkm	/* if /bin/ls exist and is a regular file, use it, otherwise
76855682Smarkm           use built-in ls */
76955682Smarkm	if(stat("/bin/ls", &st) == 0 &&
77055682Smarkm	   S_ISREG(st.st_mode))
77155682Smarkm	    use_builtin_ls = 0;
77255682Smarkm	else
77355682Smarkm	    use_builtin_ls = 1;
77455682Smarkm    }
77555682Smarkm
77655682Smarkm    /*
77755682Smarkm     * Display a login message, if it exists.
77855682Smarkm     * N.B. reply(code,) must follow the message.
77955682Smarkm     */
78055682Smarkm    show_file(_PATH_FTPLOGINMESG, code);
78155682Smarkm    if(show_file(_PATH_ISSUE_NET, code) != 0)
78255682Smarkm	show_file(_PATH_ISSUE, code);
78355682Smarkm    if (guest) {
78455682Smarkm	reply(code, "Guest login ok, access restrictions apply.");
78555682Smarkm#ifdef HAVE_SETPROCTITLE
78655682Smarkm	snprintf (proctitle, sizeof(proctitle),
78755682Smarkm		  "%s: anonymous/%s",
78855682Smarkm		  remotehost,
78955682Smarkm		  passwd);
79064593Skris	setproctitle("%s", proctitle);
79155682Smarkm#endif /* HAVE_SETPROCTITLE */
79255682Smarkm	if (logging) {
79355682Smarkm	    char data_addr[256];
79455682Smarkm
79555682Smarkm	    if (inet_ntop (his_addr->sa_family,
79655682Smarkm			   socket_get_address(his_addr),
79755682Smarkm			   data_addr, sizeof(data_addr)) == NULL)
79855682Smarkm		strlcpy (data_addr, "unknown address",
79955682Smarkm				 sizeof(data_addr));
80055682Smarkm
80155682Smarkm	    syslog(LOG_INFO, "ANONYMOUS FTP LOGIN FROM %s(%s), %s",
80255682Smarkm		   remotehost,
80355682Smarkm		   data_addr,
80455682Smarkm		   passwd);
80555682Smarkm	}
80655682Smarkm    } else {
80755682Smarkm	reply(code, "User %s logged in.", pw->pw_name);
80855682Smarkm#ifdef HAVE_SETPROCTITLE
80955682Smarkm	snprintf(proctitle, sizeof(proctitle), "%s: %s", remotehost, pw->pw_name);
81064593Skris	setproctitle("%s", proctitle);
81155682Smarkm#endif /* HAVE_SETPROCTITLE */
81255682Smarkm	if (logging) {
81355682Smarkm	    char data_addr[256];
81455682Smarkm
81555682Smarkm	    if (inet_ntop (his_addr->sa_family,
81655682Smarkm			   socket_get_address(his_addr),
81755682Smarkm			   data_addr, sizeof(data_addr)) == NULL)
81855682Smarkm		strlcpy (data_addr, "unknown address",
81955682Smarkm				 sizeof(data_addr));
82055682Smarkm
82155682Smarkm	    syslog(LOG_INFO, "FTP LOGIN FROM %s(%s) as %s",
82255682Smarkm		   remotehost,
82355682Smarkm		   data_addr,
82455682Smarkm		   pw->pw_name);
82555682Smarkm	}
82655682Smarkm    }
82755682Smarkm    umask(defumask);
82855682Smarkm    return 0;
82955682Smarkm}
83055682Smarkm
83155682Smarkm/*
83255682Smarkm * Terminate login as previous user, if any, resetting state;
83355682Smarkm * used when USER command is given or login fails.
83455682Smarkm */
83555682Smarkmstatic void
83655682Smarkmend_login(void)
83755682Smarkm{
83855682Smarkm
839178825Sdfr	if (seteuid((uid_t)0) < 0)
840178825Sdfr		fatal("Failed to seteuid");
84155682Smarkm	if (logged_in)
84255682Smarkm		ftpd_logwtmp(ttyline, "", "");
84355682Smarkm	pw = NULL;
84455682Smarkm	logged_in = 0;
84555682Smarkm	guest = 0;
84655682Smarkm	dochroot = 0;
84755682Smarkm}
84855682Smarkm
84972445Sassar#ifdef KRB5
85072445Sassarstatic int
85172445Sassarkrb5_verify(struct passwd *pwd, char *passwd)
85272445Sassar{
85372445Sassar   krb5_context context;
85472445Sassar   krb5_ccache  id;
85572445Sassar   krb5_principal princ;
85672445Sassar   krb5_error_code ret;
85772445Sassar
85872445Sassar   ret = krb5_init_context(&context);
85972445Sassar   if(ret)
86072445Sassar        return ret;
86172445Sassar
86272445Sassar  ret = krb5_parse_name(context, pwd->pw_name, &princ);
86372445Sassar  if(ret){
86472445Sassar        krb5_free_context(context);
86572445Sassar        return ret;
86672445Sassar  }
86772445Sassar  ret = krb5_cc_gen_new(context, &krb5_mcc_ops, &id);
86872445Sassar  if(ret){
86972445Sassar        krb5_free_principal(context, princ);
87072445Sassar        krb5_free_context(context);
87172445Sassar        return ret;
87272445Sassar  }
87372445Sassar  ret = krb5_verify_user(context,
87472445Sassar                         princ,
87572445Sassar                         id,
87672445Sassar                         passwd,
87772445Sassar                         1,
87872445Sassar                         NULL);
87972445Sassar  krb5_free_principal(context, princ);
88072445Sassar  if (k_hasafs()) {
88172445Sassar      krb5_afslog_uid_home(context, id,NULL, NULL,pwd->pw_uid, pwd->pw_dir);
88272445Sassar  }
88372445Sassar  krb5_cc_destroy(context, id);
88472445Sassar  krb5_free_context (context);
88572445Sassar  if(ret)
88672445Sassar      return ret;
88772445Sassar  return 0;
88872445Sassar}
88972445Sassar#endif /* KRB5 */
89072445Sassar
89155682Smarkmvoid
89255682Smarkmpass(char *passwd)
89355682Smarkm{
89455682Smarkm	int rval;
89555682Smarkm
89655682Smarkm	/* some clients insists on sending a password */
89755682Smarkm	if (logged_in && askpasswd == 0){
89872445Sassar	    reply(230, "Password not necessary");
89972445Sassar	    return;
90055682Smarkm	}
90155682Smarkm
90255682Smarkm	if (logged_in || askpasswd == 0) {
90355682Smarkm		reply(503, "Login with USER first.");
90455682Smarkm		return;
90555682Smarkm	}
90655682Smarkm	askpasswd = 0;
90755682Smarkm	rval = 1;
90855682Smarkm	if (!guest) {		/* "ftp" is only account allowed no password */
90955682Smarkm		if (pw == NULL)
91055682Smarkm			rval = 1;	/* failure below */
91155682Smarkm#ifdef OTP
91255682Smarkm		else if (otp_verify_user (&otp_ctx, passwd) == 0) {
91355682Smarkm		    rval = 0;
91455682Smarkm		}
91555682Smarkm#endif
91655682Smarkm		else if((auth_level & AUTH_OTP) == 0) {
91772445Sassar#ifdef KRB5
91872445Sassar		    rval = krb5_verify(pw, passwd);
91972445Sassar#endif
92055682Smarkm#ifdef KRB4
92172445Sassar		    if (rval) {
92272445Sassar			char realm[REALM_SZ];
92372445Sassar			if((rval = krb_get_lrealm(realm, 1)) == KSUCCESS)
92472445Sassar			    rval = krb_verify_user(pw->pw_name,
92572445Sassar						   "", realm,
92672445Sassar						   passwd,
92772445Sassar						   KRB_VERIFY_SECURE, NULL);
92872445Sassar			if (rval == KSUCCESS ) {
92972445Sassar			    chown (tkt_string(), pw->pw_uid, pw->pw_gid);
93072445Sassar			    if(k_hasafs())
93172445Sassar				krb_afslog(0, 0);
93272445Sassar			}
93372445Sassar		    }
93455682Smarkm#endif
93572445Sassar		    if (rval)
93655682Smarkm			rval = unix_verify_user(pw->pw_name, passwd);
93755682Smarkm		} else {
938178825Sdfr#ifdef OTP
93955682Smarkm		    char *s;
94055682Smarkm		    if ((s = otp_error(&otp_ctx)) != NULL)
94155682Smarkm			lreply(530, "OTP: %s", s);
94255682Smarkm#endif
94355682Smarkm		}
94455682Smarkm		memset (passwd, 0, strlen(passwd));
94555682Smarkm
94655682Smarkm		/*
94755682Smarkm		 * If rval == 1, the user failed the authentication
94855682Smarkm		 * check above.  If rval == 0, either Kerberos or
94955682Smarkm		 * local authentication succeeded.
95055682Smarkm		 */
95155682Smarkm		if (rval) {
95255682Smarkm			char data_addr[256];
95355682Smarkm
95455682Smarkm			if (inet_ntop (his_addr->sa_family,
95555682Smarkm				       socket_get_address(his_addr),
95655682Smarkm				       data_addr, sizeof(data_addr)) == NULL)
95755682Smarkm				strlcpy (data_addr, "unknown address",
95855682Smarkm						 sizeof(data_addr));
95955682Smarkm
96055682Smarkm			reply(530, "Login incorrect.");
96155682Smarkm			if (logging)
96255682Smarkm				syslog(LOG_NOTICE,
96355682Smarkm				    "FTP LOGIN FAILED FROM %s(%s), %s",
96455682Smarkm				       remotehost,
96555682Smarkm				       data_addr,
96655682Smarkm				       curname);
96755682Smarkm			pw = NULL;
96855682Smarkm			if (login_attempts++ >= 5) {
96955682Smarkm				syslog(LOG_NOTICE,
97055682Smarkm				       "repeated login failures from %s(%s)",
97155682Smarkm				       remotehost,
97255682Smarkm				       data_addr);
97355682Smarkm				exit(0);
97455682Smarkm			}
97555682Smarkm			return;
97655682Smarkm		}
97755682Smarkm	}
97855682Smarkm	if(!do_login(230, passwd))
97955682Smarkm	  return;
98055682Smarkm
98155682Smarkm	/* Forget all about it... */
98255682Smarkm	end_login();
98355682Smarkm}
98455682Smarkm
98555682Smarkmvoid
98655682Smarkmretrieve(const char *cmd, char *name)
98755682Smarkm{
98855682Smarkm	FILE *fin = NULL, *dout;
98955682Smarkm	struct stat st;
99055682Smarkm	int (*closefunc) (FILE *);
99155682Smarkm	char line[BUFSIZ];
99255682Smarkm
99355682Smarkm
99455682Smarkm	if (cmd == 0) {
99555682Smarkm		fin = fopen(name, "r");
99655682Smarkm		closefunc = fclose;
99755682Smarkm		st.st_size = 0;
99855682Smarkm		if(fin == NULL){
99955682Smarkm		    int save_errno = errno;
100055682Smarkm		    struct cmds {
100155682Smarkm			const char *ext;
100255682Smarkm			const char *cmd;
100355682Smarkm		        const char *rev_cmd;
100455682Smarkm		    } cmds[] = {
100555682Smarkm			{".tar", "/bin/gtar cPf - %s", NULL},
100655682Smarkm			{".tar.gz", "/bin/gtar zcPf - %s", NULL},
100755682Smarkm			{".tar.Z", "/bin/gtar ZcPf - %s", NULL},
100855682Smarkm			{".gz", "/bin/gzip -c -- %s", "/bin/gzip -c -d -- %s"},
100955682Smarkm			{".Z", "/bin/compress -c -- %s", "/bin/uncompress -c -- %s"},
101055682Smarkm			{NULL, NULL}
101155682Smarkm		    };
101255682Smarkm		    struct cmds *p;
101355682Smarkm		    for(p = cmds; p->ext; p++){
101455682Smarkm			char *tail = name + strlen(name) - strlen(p->ext);
101555682Smarkm			char c = *tail;
101655682Smarkm
101755682Smarkm			if(strcmp(tail, p->ext) == 0 &&
101855682Smarkm			   (*tail  = 0) == 0 &&
101955682Smarkm			   access(name, R_OK) == 0){
102055682Smarkm			    snprintf (line, sizeof(line), p->cmd, name);
102155682Smarkm			    *tail  = c;
102255682Smarkm			    break;
102355682Smarkm			}
102455682Smarkm			*tail = c;
102555682Smarkm			if (p->rev_cmd != NULL) {
102655682Smarkm			    char *ext;
1027178825Sdfr			    int ret;
102855682Smarkm
1029178825Sdfr			    ret = asprintf(&ext, "%s%s", name, p->ext);
1030178825Sdfr			    if (ret != -1) {
103155682Smarkm  			        if (access(ext, R_OK) == 0) {
103255682Smarkm				    snprintf (line, sizeof(line),
103355682Smarkm					      p->rev_cmd, ext);
103455682Smarkm				    free(ext);
103555682Smarkm				    break;
103655682Smarkm				}
103755682Smarkm			        free(ext);
103855682Smarkm			    }
103955682Smarkm			}
104055682Smarkm
104155682Smarkm		    }
104255682Smarkm		    if(p->ext){
104355682Smarkm			fin = ftpd_popen(line, "r", 0, 0);
104455682Smarkm			closefunc = ftpd_pclose;
104555682Smarkm			st.st_size = -1;
104655682Smarkm			cmd = line;
104755682Smarkm		    } else
104855682Smarkm			errno = save_errno;
104955682Smarkm		}
105055682Smarkm	} else {
105155682Smarkm		snprintf(line, sizeof(line), cmd, name);
105255682Smarkm		name = line;
105355682Smarkm		fin = ftpd_popen(line, "r", 1, 0);
105455682Smarkm		closefunc = ftpd_pclose;
105555682Smarkm		st.st_size = -1;
105655682Smarkm	}
105755682Smarkm	if (fin == NULL) {
105855682Smarkm		if (errno != 0) {
105955682Smarkm			perror_reply(550, name);
106055682Smarkm			if (cmd == 0) {
106155682Smarkm				LOGCMD("get", name);
106255682Smarkm			}
106355682Smarkm		}
106455682Smarkm		return;
106555682Smarkm	}
106655682Smarkm	byte_count = -1;
106755682Smarkm	if (cmd == 0){
106855682Smarkm	    if(fstat(fileno(fin), &st) < 0 || !S_ISREG(st.st_mode)) {
106955682Smarkm		reply(550, "%s: not a plain file.", name);
107055682Smarkm		goto done;
107155682Smarkm	    }
107255682Smarkm	}
107355682Smarkm	if (restart_point) {
107455682Smarkm		if (type == TYPE_A) {
107555682Smarkm			off_t i, n;
107655682Smarkm			int c;
107755682Smarkm
107855682Smarkm			n = restart_point;
107955682Smarkm			i = 0;
108055682Smarkm			while (i++ < n) {
108155682Smarkm				if ((c=getc(fin)) == EOF) {
108255682Smarkm					perror_reply(550, name);
108355682Smarkm					goto done;
108455682Smarkm				}
108555682Smarkm				if (c == '\n')
108655682Smarkm					i++;
108755682Smarkm			}
108855682Smarkm		} else if (lseek(fileno(fin), restart_point, SEEK_SET) < 0) {
108955682Smarkm			perror_reply(550, name);
109055682Smarkm			goto done;
109155682Smarkm		}
109255682Smarkm	}
109355682Smarkm	dout = dataconn(name, st.st_size, "w");
109455682Smarkm	if (dout == NULL)
109555682Smarkm		goto done;
109655682Smarkm	set_buffer_size(fileno(dout), 0);
109755682Smarkm	send_data(fin, dout);
109855682Smarkm	fclose(dout);
109955682Smarkm	data = -1;
110055682Smarkm	pdata = -1;
110155682Smarkmdone:
110255682Smarkm	if (cmd == 0)
110355682Smarkm		LOGBYTES("get", name, byte_count);
110455682Smarkm	(*closefunc)(fin);
110555682Smarkm}
110655682Smarkm
110755682Smarkm/* filename sanity check */
110855682Smarkm
110955682Smarkmint
111055682Smarkmfilename_check(char *filename)
111155682Smarkm{
1112178825Sdfr    char *p;
111355682Smarkm
1114178825Sdfr    p = strrchr(filename, '/');
111555682Smarkm    if(p)
111655682Smarkm	filename = p + 1;
111755682Smarkm
111855682Smarkm    p = filename;
111955682Smarkm
1120178825Sdfr    if(isalnum((unsigned char)*p)){
112155682Smarkm	p++;
1122178825Sdfr	while(*p && (isalnum((unsigned char)*p) || strchr(good_chars, (unsigned char)*p)))
112355682Smarkm	    p++;
112455682Smarkm	if(*p == '\0')
112555682Smarkm	    return 0;
112655682Smarkm    }
112772445Sassar    lreply(553, "\"%s\" is not an acceptable filename.", filename);
112855682Smarkm    lreply(553, "The filename must start with an alphanumeric "
112955682Smarkm	   "character and must only");
113055682Smarkm    reply(553, "consist of alphanumeric characters or any of the following: %s",
113155682Smarkm	  good_chars);
113255682Smarkm    return 1;
113355682Smarkm}
113455682Smarkm
113555682Smarkmvoid
113655682Smarkmdo_store(char *name, char *mode, int unique)
113755682Smarkm{
113855682Smarkm	FILE *fout, *din;
113955682Smarkm	struct stat st;
114055682Smarkm	int (*closefunc) (FILE *);
114155682Smarkm
114255682Smarkm	if(guest && filename_check(name))
114355682Smarkm	    return;
114455682Smarkm	if (unique && stat(name, &st) == 0 &&
114555682Smarkm	    (name = gunique(name)) == NULL) {
114655682Smarkm		LOGCMD(*mode == 'w' ? "put" : "append", name);
114755682Smarkm		return;
114855682Smarkm	}
114955682Smarkm
115055682Smarkm	if (restart_point)
115155682Smarkm		mode = "r+";
115255682Smarkm	fout = fopen(name, mode);
115355682Smarkm	closefunc = fclose;
115455682Smarkm	if (fout == NULL) {
115555682Smarkm		perror_reply(553, name);
115655682Smarkm		LOGCMD(*mode == 'w' ? "put" : "append", name);
115755682Smarkm		return;
115855682Smarkm	}
115955682Smarkm	byte_count = -1;
116055682Smarkm	if (restart_point) {
116155682Smarkm		if (type == TYPE_A) {
116255682Smarkm			off_t i, n;
116355682Smarkm			int c;
116455682Smarkm
116555682Smarkm			n = restart_point;
116655682Smarkm			i = 0;
116755682Smarkm			while (i++ < n) {
116855682Smarkm				if ((c=getc(fout)) == EOF) {
116955682Smarkm					perror_reply(550, name);
117055682Smarkm					goto done;
117155682Smarkm				}
117255682Smarkm				if (c == '\n')
117355682Smarkm					i++;
117455682Smarkm			}
117555682Smarkm			/*
117655682Smarkm			 * We must do this seek to "current" position
117755682Smarkm			 * because we are changing from reading to
117855682Smarkm			 * writing.
117955682Smarkm			 */
118055682Smarkm			if (fseek(fout, 0L, SEEK_CUR) < 0) {
118155682Smarkm				perror_reply(550, name);
118255682Smarkm				goto done;
118355682Smarkm			}
118455682Smarkm		} else if (lseek(fileno(fout), restart_point, SEEK_SET) < 0) {
118555682Smarkm			perror_reply(550, name);
118655682Smarkm			goto done;
118755682Smarkm		}
118855682Smarkm	}
118955682Smarkm	din = dataconn(name, (off_t)-1, "r");
119055682Smarkm	if (din == NULL)
119155682Smarkm		goto done;
119255682Smarkm	set_buffer_size(fileno(din), 1);
119355682Smarkm	if (receive_data(din, fout) == 0) {
119478527Sassar	    if((*closefunc)(fout) < 0)
119578527Sassar		perror_reply(552, name);
119678527Sassar	    else {
119755682Smarkm		if (unique)
119855682Smarkm			reply(226, "Transfer complete (unique file name:%s).",
119955682Smarkm			    name);
120055682Smarkm		else
120155682Smarkm			reply(226, "Transfer complete.");
120278527Sassar	    }
120378527Sassar	} else
120478527Sassar	    (*closefunc)(fout);
120555682Smarkm	fclose(din);
120655682Smarkm	data = -1;
120755682Smarkm	pdata = -1;
120855682Smarkmdone:
120955682Smarkm	LOGBYTES(*mode == 'w' ? "put" : "append", name, byte_count);
121055682Smarkm}
121155682Smarkm
121255682Smarkmstatic FILE *
1213178825Sdfrgetdatasock(const char *mode, int domain)
121455682Smarkm{
121555682Smarkm	int s, t, tries;
121655682Smarkm
121755682Smarkm	if (data >= 0)
121855682Smarkm		return (fdopen(data, mode));
1219178825Sdfr	if (seteuid(0) < 0)
1220178825Sdfr		fatal("Failed to seteuid");
1221178825Sdfr	s = socket(domain, SOCK_STREAM, 0);
122255682Smarkm	if (s < 0)
122355682Smarkm		goto bad;
122455682Smarkm	socket_set_reuseaddr (s, 1);
122555682Smarkm	/* anchor socket to avoid multi-homing problems */
122655682Smarkm	socket_set_address_and_port (data_source,
122755682Smarkm				     socket_get_address (ctrl_addr),
122855682Smarkm				     socket_get_port (data_source));
122955682Smarkm
123055682Smarkm	for (tries = 1; ; tries++) {
123155682Smarkm		if (bind(s, data_source,
123255682Smarkm			 socket_sockaddr_size (data_source)) >= 0)
123355682Smarkm			break;
123455682Smarkm		if (errno != EADDRINUSE || tries > 10)
123555682Smarkm			goto bad;
123655682Smarkm		sleep(tries);
123755682Smarkm	}
1238178825Sdfr	if (seteuid(pw->pw_uid) < 0)
1239178825Sdfr		fatal("Failed to seteuid");
124055682Smarkm#ifdef IPTOS_THROUGHPUT
124155682Smarkm	socket_set_tos (s, IPTOS_THROUGHPUT);
124255682Smarkm#endif
124355682Smarkm	return (fdopen(s, mode));
124455682Smarkmbad:
124555682Smarkm	/* Return the real value of errno (close may change it) */
124655682Smarkm	t = errno;
1247178825Sdfr	if (seteuid((uid_t)pw->pw_uid) < 0)
1248178825Sdfr		fatal("Failed to seteuid");
124955682Smarkm	close(s);
125055682Smarkm	errno = t;
125155682Smarkm	return (NULL);
125255682Smarkm}
125355682Smarkm
125490926Snectarstatic int
125590926Snectaraccept_with_timeout(int socket,
125690926Snectar		    struct sockaddr *address,
1257120945Snectar		    socklen_t *address_len,
125890926Snectar		    struct timeval *timeout)
125990926Snectar{
126090926Snectar    int ret;
126190926Snectar    fd_set rfd;
126290926Snectar    FD_ZERO(&rfd);
126390926Snectar    FD_SET(socket, &rfd);
126490926Snectar    ret = select(socket + 1, &rfd, NULL, NULL, timeout);
126590926Snectar    if(ret < 0)
126690926Snectar	return ret;
126790926Snectar    if(ret == 0) {
126890926Snectar	errno = ETIMEDOUT;
126990926Snectar	return -1;
127090926Snectar    }
127190926Snectar    return accept(socket, address, address_len);
127290926Snectar}
127390926Snectar
127455682Smarkmstatic FILE *
127555682Smarkmdataconn(const char *name, off_t size, const char *mode)
127655682Smarkm{
127755682Smarkm	char sizebuf[32];
127855682Smarkm	FILE *file;
1279178825Sdfr	int domain, retry = 0;
128055682Smarkm
128155682Smarkm	file_size = size;
128255682Smarkm	byte_count = 0;
128355682Smarkm	if (size >= 0)
128455682Smarkm	    snprintf(sizebuf, sizeof(sizebuf), " (%ld bytes)", (long)size);
128555682Smarkm	else
128655682Smarkm	    *sizebuf = '\0';
128755682Smarkm	if (pdata >= 0) {
128855682Smarkm		struct sockaddr_storage from_ss;
128955682Smarkm		struct sockaddr *from = (struct sockaddr *)&from_ss;
129090926Snectar		struct timeval timeout;
129155682Smarkm		int s;
129272445Sassar		socklen_t fromlen = sizeof(from_ss);
129355682Smarkm
129490926Snectar		timeout.tv_sec = 15;
129590926Snectar		timeout.tv_usec = 0;
129690926Snectar		s = accept_with_timeout(pdata, from, &fromlen, &timeout);
129755682Smarkm		if (s < 0) {
129855682Smarkm			reply(425, "Can't open data connection.");
129955682Smarkm			close(pdata);
130055682Smarkm			pdata = -1;
130155682Smarkm			return (NULL);
130255682Smarkm		}
130355682Smarkm		close(pdata);
130455682Smarkm		pdata = s;
130555682Smarkm#if defined(IP_TOS) && defined(HAVE_SETSOCKOPT)
130655682Smarkm		{
130755682Smarkm		    int tos = IPTOS_THROUGHPUT;
130855682Smarkm
130955682Smarkm		    setsockopt(s, IPPROTO_IP, IP_TOS, (void *)&tos,
131055682Smarkm			       sizeof(tos));
131155682Smarkm		}
131255682Smarkm#endif
131355682Smarkm		reply(150, "Opening %s mode data connection for '%s'%s.",
131455682Smarkm		     type == TYPE_A ? "ASCII" : "BINARY", name, sizebuf);
131555682Smarkm		return (fdopen(pdata, mode));
131655682Smarkm	}
131755682Smarkm	if (data >= 0) {
131855682Smarkm		reply(125, "Using existing data connection for '%s'%s.",
131955682Smarkm		    name, sizebuf);
132055682Smarkm		usedefault = 1;
132155682Smarkm		return (fdopen(data, mode));
132255682Smarkm	}
132355682Smarkm	if (usedefault)
132455682Smarkm		data_dest = his_addr;
132555682Smarkm	usedefault = 1;
1326178825Sdfr	/*
1327178825Sdfr	 * Default to using the same socket type as the ctrl address,
1328178825Sdfr	 * unless we know the type of the data address.
1329178825Sdfr	 */
1330178825Sdfr	domain = data_dest->sa_family;
1331178825Sdfr	if (domain == PF_UNSPEC)
1332178825Sdfr	    domain = ctrl_addr->sa_family;
1333178825Sdfr
1334178825Sdfr	file = getdatasock(mode, domain);
133555682Smarkm	if (file == NULL) {
133655682Smarkm		char data_addr[256];
133755682Smarkm
133855682Smarkm		if (inet_ntop (data_source->sa_family,
133955682Smarkm			       socket_get_address(data_source),
134055682Smarkm			       data_addr, sizeof(data_addr)) == NULL)
134155682Smarkm			strlcpy (data_addr, "unknown address",
134255682Smarkm					 sizeof(data_addr));
134355682Smarkm
134455682Smarkm		reply(425, "Can't create data socket (%s,%d): %s.",
134555682Smarkm		      data_addr,
134655682Smarkm		      socket_get_port (data_source),
134755682Smarkm		      strerror(errno));
134855682Smarkm		return (NULL);
134955682Smarkm	}
135055682Smarkm	data = fileno(file);
135155682Smarkm	while (connect(data, data_dest,
135255682Smarkm		       socket_sockaddr_size(data_dest)) < 0) {
135355682Smarkm		if (errno == EADDRINUSE && retry < swaitmax) {
135455682Smarkm			sleep(swaitint);
135555682Smarkm			retry += swaitint;
135655682Smarkm			continue;
135755682Smarkm		}
135855682Smarkm		perror_reply(425, "Can't build data connection");
135955682Smarkm		fclose(file);
136055682Smarkm		data = -1;
136155682Smarkm		return (NULL);
136255682Smarkm	}
136355682Smarkm	reply(150, "Opening %s mode data connection for '%s'%s.",
136455682Smarkm	     type == TYPE_A ? "ASCII" : "BINARY", name, sizebuf);
136555682Smarkm	return (file);
136655682Smarkm}
136755682Smarkm
136855682Smarkm/*
136955682Smarkm * Tranfer the contents of "instr" to "outstr" peer using the appropriate
137055682Smarkm * encapsulation of the data subject * to Mode, Structure, and Type.
137155682Smarkm *
137255682Smarkm * NB: Form isn't handled.
137355682Smarkm */
137455682Smarkmstatic void
137555682Smarkmsend_data(FILE *instr, FILE *outstr)
137655682Smarkm{
137755682Smarkm	int c, cnt, filefd, netfd;
137855682Smarkm	static char *buf;
137955682Smarkm	static size_t bufsize;
138055682Smarkm
1381142403Snectar	transflag = 1;
138255682Smarkm	switch (type) {
138355682Smarkm
138455682Smarkm	case TYPE_A:
138555682Smarkm	    while ((c = getc(instr)) != EOF) {
1386142403Snectar		if (urgflag && handleoobcmd())
1387142403Snectar		    return;
138855682Smarkm		byte_count++;
138955682Smarkm		if(c == '\n')
139055682Smarkm		    sec_putc('\r', outstr);
139155682Smarkm		sec_putc(c, outstr);
139255682Smarkm	    }
139355682Smarkm	    sec_fflush(outstr);
139455682Smarkm	    transflag = 0;
1395142403Snectar	    urgflag = 0;
139655682Smarkm	    if (ferror(instr))
139755682Smarkm		goto file_err;
139855682Smarkm	    if (ferror(outstr))
139955682Smarkm		goto data_err;
140055682Smarkm	    reply(226, "Transfer complete.");
140155682Smarkm	    return;
140255682Smarkm
140355682Smarkm	case TYPE_I:
140455682Smarkm	case TYPE_L:
1405142403Snectar#if 0 /* XXX handle urg flag */
140655682Smarkm#if defined(HAVE_MMAP) && !defined(NO_MMAP)
140755682Smarkm#ifndef MAP_FAILED
140855682Smarkm#define MAP_FAILED (-1)
140955682Smarkm#endif
141055682Smarkm	    {
141155682Smarkm		struct stat st;
141255682Smarkm		char *chunk;
141355682Smarkm		int in = fileno(instr);
141455682Smarkm		if(fstat(in, &st) == 0 && S_ISREG(st.st_mode)
141555682Smarkm		   && st.st_size > 0) {
141655682Smarkm		    /*
141755682Smarkm		     * mmap zero bytes has potential of loosing, don't do it.
141855682Smarkm		     */
141955682Smarkm		    chunk = mmap(0, st.st_size, PROT_READ,
142055682Smarkm				 MAP_SHARED, in, 0);
142155682Smarkm		    if((void *)chunk != (void *)MAP_FAILED) {
142255682Smarkm			cnt = st.st_size - restart_point;
142355682Smarkm			sec_write(fileno(outstr), chunk + restart_point, cnt);
142455682Smarkm			if (munmap(chunk, st.st_size) < 0)
142555682Smarkm			    warn ("munmap");
142655682Smarkm			sec_fflush(outstr);
142755682Smarkm			byte_count = cnt;
142855682Smarkm			transflag = 0;
1429142403Snectar			urgflag = 0;
143055682Smarkm		    }
143155682Smarkm		}
143255682Smarkm	    }
143355682Smarkm#endif
1434142403Snectar#endif
143555682Smarkm	if(transflag) {
143655682Smarkm	    struct stat st;
143755682Smarkm
143855682Smarkm	    netfd = fileno(outstr);
143955682Smarkm	    filefd = fileno(instr);
144055682Smarkm	    buf = alloc_buffer (buf, &bufsize,
144155682Smarkm				fstat(filefd, &st) >= 0 ? &st : NULL);
144255682Smarkm	    if (buf == NULL) {
144355682Smarkm		transflag = 0;
1444142403Snectar		urgflag = 0;
144555682Smarkm		perror_reply(451, "Local resource failure: malloc");
144655682Smarkm		return;
144755682Smarkm	    }
144855682Smarkm	    while ((cnt = read(filefd, buf, bufsize)) > 0 &&
1449142403Snectar		   sec_write(netfd, buf, cnt) == cnt) {
145055682Smarkm		byte_count += cnt;
1451142403Snectar		if (urgflag && handleoobcmd())
1452142403Snectar		    return;
1453142403Snectar	    }
145455682Smarkm	    sec_fflush(outstr); /* to end an encrypted stream */
145555682Smarkm	    transflag = 0;
1456142403Snectar	    urgflag = 0;
145755682Smarkm	    if (cnt != 0) {
145855682Smarkm		if (cnt < 0)
145955682Smarkm		    goto file_err;
146055682Smarkm		goto data_err;
146155682Smarkm	    }
146255682Smarkm	}
146355682Smarkm	reply(226, "Transfer complete.");
146455682Smarkm	return;
146555682Smarkm	default:
146655682Smarkm	    transflag = 0;
1467142403Snectar	    urgflag = 0;
146855682Smarkm	    reply(550, "Unimplemented TYPE %d in send_data", type);
146955682Smarkm	    return;
147055682Smarkm	}
147155682Smarkm
147255682Smarkmdata_err:
147355682Smarkm	transflag = 0;
1474142403Snectar	urgflag = 0;
147555682Smarkm	perror_reply(426, "Data connection");
147655682Smarkm	return;
147755682Smarkm
147855682Smarkmfile_err:
147955682Smarkm	transflag = 0;
1480142403Snectar	urgflag = 0;
148155682Smarkm	perror_reply(551, "Error on input file");
148255682Smarkm}
148355682Smarkm
148455682Smarkm/*
148555682Smarkm * Transfer data from peer to "outstr" using the appropriate encapulation of
148655682Smarkm * the data subject to Mode, Structure, and Type.
148755682Smarkm *
148855682Smarkm * N.B.: Form isn't handled.
148955682Smarkm */
149055682Smarkmstatic int
149155682Smarkmreceive_data(FILE *instr, FILE *outstr)
149255682Smarkm{
149355682Smarkm    int cnt, bare_lfs = 0;
149455682Smarkm    static char *buf;
149555682Smarkm    static size_t bufsize;
149655682Smarkm    struct stat st;
149755682Smarkm
1498142403Snectar    transflag = 1;
149955682Smarkm
150055682Smarkm    buf = alloc_buffer (buf, &bufsize,
150155682Smarkm			fstat(fileno(outstr), &st) >= 0 ? &st : NULL);
150255682Smarkm    if (buf == NULL) {
150355682Smarkm	transflag = 0;
1504142403Snectar	urgflag = 0;
150555682Smarkm	perror_reply(451, "Local resource failure: malloc");
150655682Smarkm	return -1;
150755682Smarkm    }
150855682Smarkm
150955682Smarkm    switch (type) {
151055682Smarkm
151155682Smarkm    case TYPE_I:
151255682Smarkm    case TYPE_L:
151355682Smarkm	while ((cnt = sec_read(fileno(instr), buf, bufsize)) > 0) {
151455682Smarkm	    if (write(fileno(outstr), buf, cnt) != cnt)
151555682Smarkm		goto file_err;
151655682Smarkm	    byte_count += cnt;
1517142403Snectar	    if (urgflag && handleoobcmd())
1518142403Snectar		return (-1);
151955682Smarkm	}
152055682Smarkm	if (cnt < 0)
152155682Smarkm	    goto data_err;
152255682Smarkm	transflag = 0;
1523142403Snectar	urgflag = 0;
152455682Smarkm	return (0);
152555682Smarkm
152655682Smarkm    case TYPE_E:
152755682Smarkm	reply(553, "TYPE E not implemented.");
152855682Smarkm	transflag = 0;
1529142403Snectar	urgflag = 0;
153055682Smarkm	return (-1);
153155682Smarkm
153255682Smarkm    case TYPE_A:
153355682Smarkm    {
153455682Smarkm	char *p, *q;
153555682Smarkm	int cr_flag = 0;
153655682Smarkm	while ((cnt = sec_read(fileno(instr),
153755682Smarkm				buf + cr_flag,
153855682Smarkm				bufsize - cr_flag)) > 0){
1539142403Snectar	    if (urgflag && handleoobcmd())
1540142403Snectar		return (-1);
154155682Smarkm	    byte_count += cnt;
154255682Smarkm	    cnt += cr_flag;
154355682Smarkm	    cr_flag = 0;
154455682Smarkm	    for(p = buf, q = buf; p < buf + cnt;) {
154555682Smarkm		if(*p == '\n')
154655682Smarkm		    bare_lfs++;
154755682Smarkm		if(*p == '\r') {
154855682Smarkm		    if(p == buf + cnt - 1){
154955682Smarkm			cr_flag = 1;
155055682Smarkm			p++;
155155682Smarkm			continue;
155255682Smarkm		    }else if(p[1] == '\n'){
155355682Smarkm			*q++ = '\n';
155455682Smarkm			p += 2;
155555682Smarkm			continue;
155655682Smarkm		    }
155755682Smarkm		}
155855682Smarkm		*q++ = *p++;
155955682Smarkm	    }
156055682Smarkm	    fwrite(buf, q - buf, 1, outstr);
156155682Smarkm	    if(cr_flag)
156255682Smarkm		buf[0] = '\r';
156355682Smarkm	}
156455682Smarkm	if(cr_flag)
156555682Smarkm	    putc('\r', outstr);
156655682Smarkm	fflush(outstr);
156755682Smarkm	if (ferror(instr))
156855682Smarkm	    goto data_err;
156955682Smarkm	if (ferror(outstr))
157055682Smarkm	    goto file_err;
157155682Smarkm	transflag = 0;
1572142403Snectar	urgflag = 0;
157355682Smarkm	if (bare_lfs) {
157455682Smarkm	    lreply(226, "WARNING! %d bare linefeeds received in ASCII mode\r\n"
157555682Smarkm		   "    File may not have transferred correctly.\r\n",
157655682Smarkm		   bare_lfs);
157755682Smarkm	}
157855682Smarkm	return (0);
157955682Smarkm    }
158055682Smarkm    default:
158155682Smarkm	reply(550, "Unimplemented TYPE %d in receive_data", type);
158255682Smarkm	transflag = 0;
1583142403Snectar	urgflag = 0;
158455682Smarkm	return (-1);
158555682Smarkm    }
158655682Smarkm
158755682Smarkmdata_err:
158855682Smarkm    transflag = 0;
1589142403Snectar    urgflag = 0;
159055682Smarkm    perror_reply(426, "Data Connection");
159155682Smarkm    return (-1);
159255682Smarkm
159355682Smarkmfile_err:
159455682Smarkm    transflag = 0;
1595142403Snectar    urgflag = 0;
159655682Smarkm    perror_reply(452, "Error writing file");
159755682Smarkm    return (-1);
159855682Smarkm}
159955682Smarkm
160055682Smarkmvoid
160155682Smarkmstatfilecmd(char *filename)
160255682Smarkm{
160355682Smarkm	FILE *fin;
160455682Smarkm	int c;
160555682Smarkm	char line[LINE_MAX];
160655682Smarkm
160755682Smarkm	snprintf(line, sizeof(line), "/bin/ls -la -- %s", filename);
160855682Smarkm	fin = ftpd_popen(line, "r", 1, 0);
160955682Smarkm	lreply(211, "status of %s:", filename);
161055682Smarkm	while ((c = getc(fin)) != EOF) {
161155682Smarkm		if (c == '\n') {
161255682Smarkm			if (ferror(stdout)){
161355682Smarkm				perror_reply(421, "control connection");
161455682Smarkm				ftpd_pclose(fin);
161555682Smarkm				dologout(1);
161655682Smarkm				/* NOTREACHED */
161755682Smarkm			}
161855682Smarkm			if (ferror(fin)) {
161955682Smarkm				perror_reply(551, filename);
162055682Smarkm				ftpd_pclose(fin);
162155682Smarkm				return;
162255682Smarkm			}
162355682Smarkm			putc('\r', stdout);
162455682Smarkm		}
162555682Smarkm		putc(c, stdout);
162655682Smarkm	}
162755682Smarkm	ftpd_pclose(fin);
162855682Smarkm	reply(211, "End of Status");
162955682Smarkm}
163055682Smarkm
163155682Smarkmvoid
163255682Smarkmstatcmd(void)
163355682Smarkm{
163455682Smarkm#if 0
163555682Smarkm	struct sockaddr_in *sin;
163655682Smarkm	u_char *a, *p;
163755682Smarkm
163855682Smarkm	lreply(211, "%s FTP server (%s) status:", hostname, version);
163955682Smarkm	printf("     %s\r\n", version);
164055682Smarkm	printf("     Connected to %s", remotehost);
1641178825Sdfr	if (!isdigit((unsigned char)remotehost[0]))
164255682Smarkm		printf(" (%s)", inet_ntoa(his_addr.sin_addr));
164355682Smarkm	printf("\r\n");
164455682Smarkm	if (logged_in) {
164555682Smarkm		if (guest)
164655682Smarkm			printf("     Logged in anonymously\r\n");
164755682Smarkm		else
164855682Smarkm			printf("     Logged in as %s\r\n", pw->pw_name);
164955682Smarkm	} else if (askpasswd)
165055682Smarkm		printf("     Waiting for password\r\n");
165155682Smarkm	else
165255682Smarkm		printf("     Waiting for user name\r\n");
165355682Smarkm	printf("     TYPE: %s", typenames[type]);
165455682Smarkm	if (type == TYPE_A || type == TYPE_E)
165555682Smarkm		printf(", FORM: %s", formnames[form]);
165655682Smarkm	if (type == TYPE_L)
165755682Smarkm#if NBBY == 8
165855682Smarkm		printf(" %d", NBBY);
165955682Smarkm#else
166055682Smarkm		printf(" %d", bytesize);	/* need definition! */
166155682Smarkm#endif
166255682Smarkm	printf("; STRUcture: %s; transfer MODE: %s\r\n",
166355682Smarkm	    strunames[stru], modenames[mode]);
166455682Smarkm	if (data != -1)
166555682Smarkm		printf("     Data connection open\r\n");
166655682Smarkm	else if (pdata != -1) {
166755682Smarkm		printf("     in Passive mode");
166855682Smarkm		sin = &pasv_addr;
166955682Smarkm		goto printaddr;
167055682Smarkm	} else if (usedefault == 0) {
167155682Smarkm		printf("     PORT");
167255682Smarkm		sin = &data_dest;
167355682Smarkmprintaddr:
167455682Smarkm		a = (u_char *) &sin->sin_addr;
167555682Smarkm		p = (u_char *) &sin->sin_port;
167655682Smarkm#define UC(b) (((int) b) & 0xff)
167755682Smarkm		printf(" (%d,%d,%d,%d,%d,%d)\r\n", UC(a[0]),
167855682Smarkm			UC(a[1]), UC(a[2]), UC(a[3]), UC(p[0]), UC(p[1]));
167955682Smarkm#undef UC
168055682Smarkm	} else
168155682Smarkm		printf("     No data connection\r\n");
168255682Smarkm#endif
168355682Smarkm	reply(211, "End of status");
168455682Smarkm}
168555682Smarkm
168655682Smarkmvoid
168755682Smarkmfatal(char *s)
168855682Smarkm{
168955682Smarkm
169055682Smarkm	reply(451, "Error in server: %s\n", s);
169155682Smarkm	reply(221, "Closing connection due to server error.");
169255682Smarkm	dologout(0);
169355682Smarkm	/* NOTREACHED */
169455682Smarkm}
169555682Smarkm
169655682Smarkmstatic void
169755682Smarkmint_reply(int, char *, const char *, va_list)
169855682Smarkm#ifdef __GNUC__
169955682Smarkm__attribute__ ((format (printf, 3, 0)))
170055682Smarkm#endif
170155682Smarkm;
170255682Smarkm
170355682Smarkmstatic void
170455682Smarkmint_reply(int n, char *c, const char *fmt, va_list ap)
170555682Smarkm{
170655682Smarkm    char buf[10240];
170755682Smarkm    char *p;
170855682Smarkm    p=buf;
170955682Smarkm    if(n){
171055682Smarkm	snprintf(p, sizeof(buf), "%d%s", n, c);
171155682Smarkm	p+=strlen(p);
171255682Smarkm    }
171355682Smarkm    vsnprintf(p, sizeof(buf) - strlen(p), fmt, ap);
171455682Smarkm    p+=strlen(p);
171555682Smarkm    snprintf(p, sizeof(buf) - strlen(p), "\r\n");
171655682Smarkm    p+=strlen(p);
171755682Smarkm    sec_fprintf(stdout, "%s", buf);
171855682Smarkm    fflush(stdout);
171955682Smarkm    if (debug)
172055682Smarkm	syslog(LOG_DEBUG, "<--- %s- ", buf);
172155682Smarkm}
172255682Smarkm
172355682Smarkmvoid
172455682Smarkmreply(int n, const char *fmt, ...)
172555682Smarkm{
172655682Smarkm  va_list ap;
172755682Smarkm  va_start(ap, fmt);
172855682Smarkm  int_reply(n, " ", fmt, ap);
172955682Smarkm  delete_ftp_command();
173055682Smarkm  va_end(ap);
173155682Smarkm}
173255682Smarkm
173355682Smarkmvoid
173455682Smarkmlreply(int n, const char *fmt, ...)
173555682Smarkm{
173655682Smarkm  va_list ap;
173755682Smarkm  va_start(ap, fmt);
173855682Smarkm  int_reply(n, "-", fmt, ap);
173955682Smarkm  va_end(ap);
174055682Smarkm}
174155682Smarkm
174255682Smarkmvoid
174355682Smarkmnreply(const char *fmt, ...)
174455682Smarkm{
174555682Smarkm  va_list ap;
174655682Smarkm  va_start(ap, fmt);
174755682Smarkm  int_reply(0, NULL, fmt, ap);
174855682Smarkm  va_end(ap);
174955682Smarkm}
175055682Smarkm
175155682Smarkmstatic void
175255682Smarkmack(char *s)
175355682Smarkm{
175455682Smarkm
175555682Smarkm	reply(250, "%s command successful.", s);
175655682Smarkm}
175755682Smarkm
175855682Smarkmvoid
175955682Smarkmnack(char *s)
176055682Smarkm{
176155682Smarkm
176255682Smarkm	reply(502, "%s command not implemented.", s);
176355682Smarkm}
176455682Smarkm
176555682Smarkmvoid
176655682Smarkmdo_delete(char *name)
176755682Smarkm{
176855682Smarkm	struct stat st;
176955682Smarkm
177055682Smarkm	LOGCMD("delete", name);
177155682Smarkm	if (stat(name, &st) < 0) {
177255682Smarkm		perror_reply(550, name);
177355682Smarkm		return;
177455682Smarkm	}
177555682Smarkm	if ((st.st_mode&S_IFMT) == S_IFDIR) {
177655682Smarkm		if (rmdir(name) < 0) {
177755682Smarkm			perror_reply(550, name);
177855682Smarkm			return;
177955682Smarkm		}
178055682Smarkm		goto done;
178155682Smarkm	}
178255682Smarkm	if (unlink(name) < 0) {
178355682Smarkm		perror_reply(550, name);
178455682Smarkm		return;
178555682Smarkm	}
178655682Smarkmdone:
178755682Smarkm	ack("DELE");
178855682Smarkm}
178955682Smarkm
179055682Smarkmvoid
179155682Smarkmcwd(char *path)
179255682Smarkm{
179355682Smarkm
179455682Smarkm	if (chdir(path) < 0)
179555682Smarkm		perror_reply(550, path);
179655682Smarkm	else
179755682Smarkm		ack("CWD");
179855682Smarkm}
179955682Smarkm
180055682Smarkmvoid
180155682Smarkmmakedir(char *name)
180255682Smarkm{
180355682Smarkm
180455682Smarkm	LOGCMD("mkdir", name);
180555682Smarkm	if(guest && filename_check(name))
180655682Smarkm	    return;
180755682Smarkm	if (mkdir(name, 0777) < 0)
180855682Smarkm		perror_reply(550, name);
180955682Smarkm	else{
181055682Smarkm	    if(guest)
181155682Smarkm		chmod(name, 0700); /* guest has umask 777 */
181255682Smarkm	    reply(257, "MKD command successful.");
181355682Smarkm	}
181455682Smarkm}
181555682Smarkm
181655682Smarkmvoid
181755682Smarkmremovedir(char *name)
181855682Smarkm{
181955682Smarkm
182055682Smarkm	LOGCMD("rmdir", name);
182155682Smarkm	if (rmdir(name) < 0)
182255682Smarkm		perror_reply(550, name);
182355682Smarkm	else
182455682Smarkm		ack("RMD");
182555682Smarkm}
182655682Smarkm
182755682Smarkmvoid
182855682Smarkmpwd(void)
182955682Smarkm{
183055682Smarkm    char path[MaxPathLen];
183155682Smarkm    char *ret;
183255682Smarkm
183355682Smarkm    /* SunOS has a broken getcwd that does popen(pwd) (!!!), this
183455682Smarkm     * failes miserably when running chroot
183555682Smarkm     */
183655682Smarkm    ret = getcwd(path, sizeof(path));
183755682Smarkm    if (ret == NULL)
183855682Smarkm	reply(550, "%s.", strerror(errno));
183955682Smarkm    else
184055682Smarkm	reply(257, "\"%s\" is current directory.", path);
184155682Smarkm}
184255682Smarkm
184355682Smarkmchar *
184455682Smarkmrenamefrom(char *name)
184555682Smarkm{
184655682Smarkm	struct stat st;
184755682Smarkm
184855682Smarkm	if (stat(name, &st) < 0) {
184955682Smarkm		perror_reply(550, name);
185055682Smarkm		return NULL;
185155682Smarkm	}
185255682Smarkm	reply(350, "File exists, ready for destination name");
185355682Smarkm	return (name);
185455682Smarkm}
185555682Smarkm
185655682Smarkmvoid
185755682Smarkmrenamecmd(char *from, char *to)
185855682Smarkm{
185955682Smarkm
186055682Smarkm	LOGCMD2("rename", from, to);
186155682Smarkm	if(guest && filename_check(to))
186255682Smarkm	    return;
186355682Smarkm	if (rename(from, to) < 0)
186455682Smarkm		perror_reply(550, "rename");
186555682Smarkm	else
186655682Smarkm		ack("RNTO");
186755682Smarkm}
186855682Smarkm
186955682Smarkmstatic void
187055682Smarkmdolog(struct sockaddr *sa, int len)
187155682Smarkm{
187255682Smarkm	getnameinfo_verified (sa, len, remotehost, sizeof(remotehost),
187355682Smarkm			      NULL, 0, 0);
187455682Smarkm#ifdef HAVE_SETPROCTITLE
187555682Smarkm	snprintf(proctitle, sizeof(proctitle), "%s: connected", remotehost);
187664593Skris	setproctitle("%s", proctitle);
187755682Smarkm#endif /* HAVE_SETPROCTITLE */
187855682Smarkm
187955682Smarkm	if (logging) {
188055682Smarkm		char data_addr[256];
188155682Smarkm
188255682Smarkm		if (inet_ntop (his_addr->sa_family,
188355682Smarkm			       socket_get_address(his_addr),
188455682Smarkm			       data_addr, sizeof(data_addr)) == NULL)
188555682Smarkm			strlcpy (data_addr, "unknown address",
188655682Smarkm					 sizeof(data_addr));
188755682Smarkm
188855682Smarkm
188955682Smarkm		syslog(LOG_INFO, "connection from %s(%s)",
189055682Smarkm		       remotehost,
189155682Smarkm		       data_addr);
189255682Smarkm	}
189355682Smarkm}
189455682Smarkm
189555682Smarkm/*
189655682Smarkm * Record logout in wtmp file
189755682Smarkm * and exit with supplied status.
189855682Smarkm */
189955682Smarkmvoid
190055682Smarkmdologout(int status)
190155682Smarkm{
190255682Smarkm    transflag = 0;
1903142403Snectar    urgflag = 0;
190455682Smarkm    if (logged_in) {
1905178825Sdfr#if KRB4 || KRB5
190655682Smarkm	cond_kdestroy();
190755682Smarkm#endif
1908178825Sdfr	seteuid((uid_t)0); /* No need to check, we call exit() below */
1909178825Sdfr	ftpd_logwtmp(ttyline, "", "");
191055682Smarkm    }
191155682Smarkm    /* beware of flushing buffers after a SIGPIPE */
191255682Smarkm#ifdef XXX
191355682Smarkm    exit(status);
191455682Smarkm#else
191555682Smarkm    _exit(status);
191655682Smarkm#endif
191755682Smarkm}
191855682Smarkm
191955682Smarkmvoid abor(void)
192055682Smarkm{
1921142403Snectar    if (!transflag)
1922142403Snectar	return;
1923142403Snectar    reply(426, "Transfer aborted. Data connection closed.");
1924142403Snectar    reply(226, "Abort successful");
1925142403Snectar    transflag = 0;
192655682Smarkm}
192755682Smarkm
192855682Smarkmstatic void
192955682Smarkmmyoob(int signo)
193055682Smarkm{
1931142403Snectar    urgflag = 1;
1932142403Snectar}
1933142403Snectar
1934142403Snectarstatic char *
1935142403Snectarmec_space(char *p)
1936142403Snectar{
1937142403Snectar    while(isspace(*(unsigned char *)p))
1938142403Snectar	  p++;
1939142403Snectar    return p;
1940142403Snectar}
1941142403Snectar
1942142403Snectarstatic int
1943142403Snectarhandleoobcmd(void)
1944142403Snectar{
194555682Smarkm	char *cp;
194655682Smarkm
194755682Smarkm	/* only process if transfer occurring */
194855682Smarkm	if (!transflag)
1949142403Snectar		return 0;
195055682Smarkm
1951142403Snectar	urgflag = 0;
195255682Smarkm
195355682Smarkm	cp = tmpline;
1954142403Snectar	if (ftpd_getline(cp, sizeof(tmpline)) == NULL) {
195555682Smarkm		reply(221, "You could at least say goodbye.");
195655682Smarkm		dologout(0);
195755682Smarkm	}
1958142403Snectar
1959142403Snectar	if (strncasecmp("MIC", cp, 3) == 0) {
1960142403Snectar	    mec(mec_space(cp + 3), prot_safe);
1961142403Snectar	} else if (strncasecmp("CONF", cp, 4) == 0) {
1962142403Snectar	    mec(mec_space(cp + 4), prot_confidential);
1963142403Snectar	} else if (strncasecmp("ENC", cp, 3) == 0) {
1964142403Snectar	    mec(mec_space(cp + 3), prot_private);
1965142403Snectar	} else if (!allow_insecure_oob) {
1966142403Snectar	    reply(533, "Command protection level denied "
1967142403Snectar		  "for paranoid reasons.");
1968142403Snectar	    goto out;
196955682Smarkm	}
1970142403Snectar
1971142403Snectar	if (secure_command())
1972142403Snectar	    cp = ftp_command;
1973142403Snectar
1974142403Snectar	if (strcasecmp(cp, "ABOR\r\n") == 0) {
1975142403Snectar		abor();
1976142403Snectar	} else if (strcasecmp(cp, "STAT\r\n") == 0) {
197755682Smarkm		if (file_size != (off_t) -1)
197855682Smarkm			reply(213, "Status: %ld of %ld bytes transferred",
197955682Smarkm			      (long)byte_count,
198055682Smarkm			      (long)file_size);
198155682Smarkm		else
1982142403Snectar			reply(213, "Status: %ld bytes transferred",
198355682Smarkm			      (long)byte_count);
198455682Smarkm	}
1985142403Snectarout:
1986142403Snectar	return (transflag == 0);
198755682Smarkm}
198855682Smarkm
198955682Smarkm/*
199055682Smarkm * Note: a response of 425 is not mentioned as a possible response to
199155682Smarkm *	the PASV command in RFC959. However, it has been blessed as
199255682Smarkm *	a legitimate response by Jon Postel in a telephone conversation
199355682Smarkm *	with Rick Adams on 25 Jan 89.
199455682Smarkm */
199555682Smarkmvoid
199655682Smarkmpasv(void)
199755682Smarkm{
199872445Sassar	socklen_t len;
199955682Smarkm	char *p, *a;
200055682Smarkm	struct sockaddr_in *sin;
200155682Smarkm
200255682Smarkm	if (ctrl_addr->sa_family != AF_INET) {
200355682Smarkm		reply(425,
200455682Smarkm		      "You cannot do PASV with something that's not IPv4");
200555682Smarkm		return;
200655682Smarkm	}
200755682Smarkm
200872445Sassar	if(pdata != -1)
200972445Sassar	    close(pdata);
201072445Sassar
201155682Smarkm	pdata = socket(ctrl_addr->sa_family, SOCK_STREAM, 0);
201255682Smarkm	if (pdata < 0) {
201355682Smarkm		perror_reply(425, "Can't open passive connection");
201455682Smarkm		return;
201555682Smarkm	}
201655682Smarkm	pasv_addr->sa_family = ctrl_addr->sa_family;
201755682Smarkm	socket_set_address_and_port (pasv_addr,
201855682Smarkm				     socket_get_address (ctrl_addr),
201955682Smarkm				     0);
202090926Snectar	socket_set_portrange(pdata, restricted_data_ports,
202190926Snectar	    pasv_addr->sa_family);
2022178825Sdfr	if (seteuid(0) < 0)
2023178825Sdfr		fatal("Failed to seteuid");
202455682Smarkm	if (bind(pdata, pasv_addr, socket_sockaddr_size (pasv_addr)) < 0) {
2025178825Sdfr		if (seteuid(pw->pw_uid) < 0)
2026178825Sdfr			fatal("Failed to seteuid");
202755682Smarkm		goto pasv_error;
202855682Smarkm	}
2029178825Sdfr	if (seteuid(pw->pw_uid) < 0)
2030178825Sdfr		fatal("Failed to seteuid");
203155682Smarkm	len = sizeof(pasv_addr_ss);
203255682Smarkm	if (getsockname(pdata, pasv_addr, &len) < 0)
203355682Smarkm		goto pasv_error;
203455682Smarkm	if (listen(pdata, 1) < 0)
203555682Smarkm		goto pasv_error;
203655682Smarkm	sin = (struct sockaddr_in *)pasv_addr;
203755682Smarkm	a = (char *) &sin->sin_addr;
203855682Smarkm	p = (char *) &sin->sin_port;
203955682Smarkm
204055682Smarkm#define UC(b) (((int) b) & 0xff)
204155682Smarkm
204255682Smarkm	reply(227, "Entering Passive Mode (%d,%d,%d,%d,%d,%d)", UC(a[0]),
204355682Smarkm		UC(a[1]), UC(a[2]), UC(a[3]), UC(p[0]), UC(p[1]));
204455682Smarkm	return;
204555682Smarkm
204655682Smarkmpasv_error:
204755682Smarkm	close(pdata);
204855682Smarkm	pdata = -1;
204955682Smarkm	perror_reply(425, "Can't open passive connection");
205055682Smarkm	return;
205155682Smarkm}
205255682Smarkm
205355682Smarkmvoid
205455682Smarkmepsv(char *proto)
205555682Smarkm{
205672445Sassar	socklen_t len;
205755682Smarkm
205855682Smarkm	pdata = socket(ctrl_addr->sa_family, SOCK_STREAM, 0);
205955682Smarkm	if (pdata < 0) {
206055682Smarkm		perror_reply(425, "Can't open passive connection");
206155682Smarkm		return;
206255682Smarkm	}
206355682Smarkm	pasv_addr->sa_family = ctrl_addr->sa_family;
206455682Smarkm	socket_set_address_and_port (pasv_addr,
206555682Smarkm				     socket_get_address (ctrl_addr),
206655682Smarkm				     0);
206790926Snectar	socket_set_portrange(pdata, restricted_data_ports,
206890926Snectar	    pasv_addr->sa_family);
2069178825Sdfr	if (seteuid(0) < 0)
2070178825Sdfr		fatal("Failed to seteuid");
207155682Smarkm	if (bind(pdata, pasv_addr, socket_sockaddr_size (pasv_addr)) < 0) {
2072178825Sdfr		if (seteuid(pw->pw_uid))
2073178825Sdfr			fatal("Failed to seteuid");
207455682Smarkm		goto pasv_error;
207555682Smarkm	}
2076178825Sdfr	if (seteuid(pw->pw_uid) < 0)
2077178825Sdfr		fatal("Failed to seteuid");
207855682Smarkm	len = sizeof(pasv_addr_ss);
207955682Smarkm	if (getsockname(pdata, pasv_addr, &len) < 0)
208055682Smarkm		goto pasv_error;
208155682Smarkm	if (listen(pdata, 1) < 0)
208255682Smarkm		goto pasv_error;
208355682Smarkm
208455682Smarkm	reply(229, "Entering Extended Passive Mode (|||%d|)",
208555682Smarkm	      ntohs(socket_get_port (pasv_addr)));
208655682Smarkm	return;
208755682Smarkm
208855682Smarkmpasv_error:
208955682Smarkm	close(pdata);
209055682Smarkm	pdata = -1;
209155682Smarkm	perror_reply(425, "Can't open passive connection");
209255682Smarkm	return;
209355682Smarkm}
209455682Smarkm
209555682Smarkmvoid
209655682Smarkmeprt(char *str)
209755682Smarkm{
209855682Smarkm	char *end;
209955682Smarkm	char sep;
210055682Smarkm	int af;
210155682Smarkm	int ret;
210255682Smarkm	int port;
210355682Smarkm
210455682Smarkm	usedefault = 0;
210555682Smarkm	if (pdata >= 0) {
210655682Smarkm	    close(pdata);
210755682Smarkm	    pdata = -1;
210855682Smarkm	}
210955682Smarkm
211055682Smarkm	sep = *str++;
211155682Smarkm	if (sep == '\0') {
211255682Smarkm		reply(500, "Bad syntax in EPRT");
211355682Smarkm		return;
211455682Smarkm	}
211555682Smarkm	af = strtol (str, &end, 0);
211655682Smarkm	if (af == 0 || *end != sep) {
211755682Smarkm		reply(500, "Bad syntax in EPRT");
211855682Smarkm		return;
211955682Smarkm	}
212055682Smarkm	str = end + 1;
212155682Smarkm	switch (af) {
212255682Smarkm#ifdef HAVE_IPV6
212355682Smarkm	case 2 :
212455682Smarkm	    data_dest->sa_family = AF_INET6;
212555682Smarkm	    break;
212655682Smarkm#endif
212755682Smarkm	case 1 :
212855682Smarkm	    data_dest->sa_family = AF_INET;
212955682Smarkm		break;
213055682Smarkm	default :
213155682Smarkm		reply(522, "Network protocol %d not supported, use (1"
213255682Smarkm#ifdef HAVE_IPV6
213355682Smarkm		      ",2"
213455682Smarkm#endif
213555682Smarkm		      ")", af);
213655682Smarkm		return;
213755682Smarkm	}
213855682Smarkm	end = strchr (str, sep);
213955682Smarkm	if (end == NULL) {
214055682Smarkm		reply(500, "Bad syntax in EPRT");
214155682Smarkm		return;
214255682Smarkm	}
214355682Smarkm	*end = '\0';
214455682Smarkm	ret = inet_pton (data_dest->sa_family, str,
214555682Smarkm			 socket_get_address (data_dest));
214655682Smarkm
214755682Smarkm	if (ret != 1) {
214855682Smarkm		reply(500, "Bad address syntax in EPRT");
214955682Smarkm		return;
215055682Smarkm	}
215155682Smarkm	str = end + 1;
215255682Smarkm	port = strtol (str, &end, 0);
215355682Smarkm	if (port == 0 || *end != sep) {
215455682Smarkm		reply(500, "Bad port syntax in EPRT");
215555682Smarkm		return;
215655682Smarkm	}
215755682Smarkm	socket_set_port (data_dest, htons(port));
215855682Smarkm	reply(200, "EPRT command successful.");
215955682Smarkm}
216055682Smarkm
216155682Smarkm/*
216255682Smarkm * Generate unique name for file with basename "local".
216355682Smarkm * The file named "local" is already known to exist.
216455682Smarkm * Generates failure reply on error.
216555682Smarkm */
216655682Smarkmstatic char *
216755682Smarkmgunique(char *local)
216855682Smarkm{
216955682Smarkm	static char new[MaxPathLen];
217055682Smarkm	struct stat st;
217155682Smarkm	int count;
217255682Smarkm	char *cp;
217355682Smarkm
217455682Smarkm	cp = strrchr(local, '/');
217555682Smarkm	if (cp)
217655682Smarkm		*cp = '\0';
217755682Smarkm	if (stat(cp ? local : ".", &st) < 0) {
217855682Smarkm		perror_reply(553, cp ? local : ".");
217955682Smarkm		return NULL;
218055682Smarkm	}
218155682Smarkm	if (cp)
218255682Smarkm		*cp = '/';
218355682Smarkm	for (count = 1; count < 100; count++) {
218455682Smarkm		snprintf (new, sizeof(new), "%s.%d", local, count);
218555682Smarkm		if (stat(new, &st) < 0)
218655682Smarkm			return (new);
218755682Smarkm	}
218855682Smarkm	reply(452, "Unique file name cannot be created.");
218955682Smarkm	return (NULL);
219055682Smarkm}
219155682Smarkm
219255682Smarkm/*
219355682Smarkm * Format and send reply containing system error number.
219455682Smarkm */
219555682Smarkmvoid
219655682Smarkmperror_reply(int code, const char *string)
219755682Smarkm{
219855682Smarkm	reply(code, "%s: %s.", string, strerror(errno));
219955682Smarkm}
220055682Smarkm
220155682Smarkmstatic char *onefile[] = {
220255682Smarkm	"",
220355682Smarkm	0
220455682Smarkm};
220555682Smarkm
220655682Smarkmvoid
220755682Smarkmlist_file(char *file)
220855682Smarkm{
220955682Smarkm    if(use_builtin_ls) {
221055682Smarkm	FILE *dout;
221155682Smarkm	dout = dataconn(file, -1, "w");
221255682Smarkm	if (dout == NULL)
221355682Smarkm	    return;
221455682Smarkm	set_buffer_size(fileno(dout), 0);
2215102644Snectar	if(builtin_ls(dout, file) == 0)
2216102644Snectar	    reply(226, "Transfer complete.");
2217102644Snectar	else
2218102644Snectar	    reply(451, "Requested action aborted. Local error in processing.");
221955682Smarkm	fclose(dout);
222055682Smarkm	data = -1;
222155682Smarkm	pdata = -1;
222255682Smarkm    } else {
222355682Smarkm#ifdef HAVE_LS_A
222472445Sassar	const char *cmd = "/bin/ls -lA %s";
222555682Smarkm#else
222672445Sassar	const char *cmd = "/bin/ls -la %s";
222755682Smarkm#endif
222855682Smarkm	retrieve(cmd, file);
222955682Smarkm    }
223055682Smarkm}
223155682Smarkm
223255682Smarkmvoid
223355682Smarkmsend_file_list(char *whichf)
223455682Smarkm{
2235142403Snectar    struct stat st;
2236142403Snectar    DIR *dirp = NULL;
2237142403Snectar    struct dirent *dir;
2238142403Snectar    FILE *dout = NULL;
2239142403Snectar    char **dirlist, *dirname;
2240142403Snectar    int simple = 0;
2241142403Snectar    int freeglob = 0;
2242142403Snectar    glob_t gl;
2243142403Snectar    char buf[MaxPathLen];
224455682Smarkm
2245142403Snectar    if (strpbrk(whichf, "~{[*?") != NULL) {
2246142403Snectar	int flags = GLOB_BRACE|GLOB_NOCHECK|GLOB_QUOTE|GLOB_TILDE|
224790926Snectar#ifdef GLOB_MAXPATH
2248142403Snectar	    GLOB_MAXPATH
224990926Snectar#else
2250142403Snectar	    GLOB_LIMIT
225190926Snectar#endif
2252142403Snectar	    ;
225355682Smarkm
2254142403Snectar	memset(&gl, 0, sizeof(gl));
2255142403Snectar	freeglob = 1;
2256142403Snectar	if (glob(whichf, flags, 0, &gl)) {
2257142403Snectar	    reply(550, "not found");
2258142403Snectar	    goto out;
2259142403Snectar	} else if (gl.gl_pathc == 0) {
2260142403Snectar	    errno = ENOENT;
2261142403Snectar	    perror_reply(550, whichf);
2262142403Snectar	    goto out;
2263142403Snectar	}
2264142403Snectar	dirlist = gl.gl_pathv;
2265142403Snectar    } else {
2266142403Snectar	onefile[0] = whichf;
2267142403Snectar	dirlist = onefile;
2268142403Snectar	simple = 1;
226955682Smarkm    }
227055682Smarkm
2271142403Snectar    while ((dirname = *dirlist++)) {
227255682Smarkm
2273142403Snectar	if (urgflag && handleoobcmd())
2274142403Snectar	    goto out;
227555682Smarkm
2276142403Snectar	if (stat(dirname, &st) < 0) {
2277142403Snectar	    /*
2278142403Snectar	     * If user typed "ls -l", etc, and the client
2279142403Snectar	     * used NLST, do what the user meant.
2280142403Snectar	     */
2281142403Snectar	    if (dirname[0] == '-' && *dirlist == NULL &&
2282142403Snectar		transflag == 0) {
2283142403Snectar		list_file(dirname);
2284142403Snectar		goto out;
2285142403Snectar	    }
2286142403Snectar	    perror_reply(550, whichf);
2287142403Snectar	    goto out;
2288142403Snectar	}
228955682Smarkm
2290142403Snectar	if (S_ISREG(st.st_mode)) {
2291142403Snectar	    if (dout == NULL) {
2292142403Snectar		dout = dataconn("file list", (off_t)-1, "w");
2293142403Snectar		if (dout == NULL)
2294142403Snectar		    goto out;
2295142403Snectar		transflag = 1;
2296142403Snectar	    }
2297142403Snectar	    snprintf(buf, sizeof(buf), "%s%s\n", dirname,
2298142403Snectar		     type == TYPE_A ? "\r" : "");
2299142403Snectar	    sec_write(fileno(dout), buf, strlen(buf));
2300142403Snectar	    byte_count += strlen(dirname) + 1;
2301142403Snectar	    continue;
2302142403Snectar	} else if (!S_ISDIR(st.st_mode))
2303142403Snectar	    continue;
230455682Smarkm
2305142403Snectar	if ((dirp = opendir(dirname)) == NULL)
2306142403Snectar	    continue;
230755682Smarkm
2308142403Snectar	while ((dir = readdir(dirp)) != NULL) {
2309142403Snectar	    char nbuf[MaxPathLen];
231055682Smarkm
2311142403Snectar	    if (urgflag && handleoobcmd())
2312142403Snectar		goto out;
2313142403Snectar
2314142403Snectar	    if (!strcmp(dir->d_name, "."))
2315142403Snectar		continue;
2316142403Snectar	    if (!strcmp(dir->d_name, ".."))
2317142403Snectar		continue;
2318142403Snectar
2319142403Snectar	    snprintf(nbuf, sizeof(nbuf), "%s/%s", dirname, dir->d_name);
2320142403Snectar
2321142403Snectar	    /*
2322142403Snectar	     * We have to do a stat to insure it's
2323142403Snectar	     * not a directory or special file.
2324142403Snectar	     */
2325142403Snectar	    if (simple || (stat(nbuf, &st) == 0 &&
2326142403Snectar			   S_ISREG(st.st_mode))) {
2327142403Snectar		if (dout == NULL) {
2328142403Snectar		    dout = dataconn("file list", (off_t)-1, "w");
2329142403Snectar		    if (dout == NULL)
2330142403Snectar			goto out;
2331142403Snectar		    transflag = 1;
2332142403Snectar		}
2333142403Snectar		if(strncmp(nbuf, "./", 2) == 0)
2334142403Snectar		    snprintf(buf, sizeof(buf), "%s%s\n", nbuf +2,
2335142403Snectar			     type == TYPE_A ? "\r" : "");
2336142403Snectar		else
2337142403Snectar		    snprintf(buf, sizeof(buf), "%s%s\n", nbuf,
2338142403Snectar			     type == TYPE_A ? "\r" : "");
2339142403Snectar		sec_write(fileno(dout), buf, strlen(buf));
2340142403Snectar		byte_count += strlen(nbuf) + 1;
2341142403Snectar	    }
234255682Smarkm	}
2343142403Snectar	closedir(dirp);
234455682Smarkm    }
2345142403Snectar    if (dout == NULL)
2346142403Snectar	reply(550, "No files found.");
2347142403Snectar    else if (ferror(dout) != 0)
2348142403Snectar	perror_reply(550, "Data connection");
2349142403Snectar    else
2350142403Snectar	reply(226, "Transfer complete.");
235155682Smarkm
2352142403Snectarout:
2353142403Snectar    transflag = 0;
2354142403Snectar    if (dout != NULL){
2355142403Snectar	sec_write(fileno(dout), buf, 0); /* XXX flush */
235655682Smarkm
2357142403Snectar	fclose(dout);
2358142403Snectar    }
2359142403Snectar    data = -1;
2360142403Snectar    pdata = -1;
2361142403Snectar    if (freeglob) {
2362142403Snectar	freeglob = 0;
2363142403Snectar	globfree(&gl);
2364142403Snectar    }
236555682Smarkm}
236655682Smarkm
236755682Smarkm
236855682Smarkmint
236955682Smarkmfind(char *pattern)
237055682Smarkm{
237155682Smarkm    char line[1024];
237255682Smarkm    FILE *f;
237355682Smarkm
237455682Smarkm    snprintf(line, sizeof(line),
237555682Smarkm	     "/bin/locate -d %s -- %s",
237655682Smarkm	     ftp_rooted("/etc/locatedb"),
237755682Smarkm	     pattern);
237855682Smarkm    f = ftpd_popen(line, "r", 1, 1);
237955682Smarkm    if(f == NULL){
238055682Smarkm	perror_reply(550, "/bin/locate");
238155682Smarkm	return 1;
238255682Smarkm    }
238355682Smarkm    lreply(200, "Output from find.");
238455682Smarkm    while(fgets(line, sizeof(line), f)){
238555682Smarkm	if(line[strlen(line)-1] == '\n')
238655682Smarkm	    line[strlen(line)-1] = 0;
238755682Smarkm	nreply("%s", line);
238855682Smarkm    }
238955682Smarkm    reply(200, "Done");
239055682Smarkm    ftpd_pclose(f);
239155682Smarkm    return 0;
239255682Smarkm}
239355682Smarkm
2394