1186872Ssimon/*	$NetBSD: ftpd.c,v 1.187 2008/09/13 03:30:35 lukem Exp $	*/
279968Sobrien
379968Sobrien/*
4133939Sobrien * Copyright (c) 1997-2004 The NetBSD Foundation, Inc.
579968Sobrien * All rights reserved.
679968Sobrien *
779968Sobrien * This code is derived from software contributed to The NetBSD Foundation
879968Sobrien * by Luke Mewburn.
979968Sobrien *
1079968Sobrien * Redistribution and use in source and binary forms, with or without
1179968Sobrien * modification, are permitted provided that the following conditions
1279968Sobrien * are met:
1379968Sobrien * 1. Redistributions of source code must retain the above copyright
1479968Sobrien *    notice, this list of conditions and the following disclaimer.
1579968Sobrien * 2. Redistributions in binary form must reproduce the above copyright
1679968Sobrien *    notice, this list of conditions and the following disclaimer in the
1779968Sobrien *    documentation and/or other materials provided with the distribution.
1879968Sobrien * 3. All advertising materials mentioning features or use of this software
1979968Sobrien *    must display the following acknowledgement:
2079968Sobrien *        This product includes software developed by the NetBSD
2179968Sobrien *        Foundation, Inc. and its contributors.
2279968Sobrien * 4. Neither the name of The NetBSD Foundation nor the names of its
2379968Sobrien *    contributors may be used to endorse or promote products derived
2479968Sobrien *    from this software without specific prior written permission.
2579968Sobrien *
2679968Sobrien * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
2779968Sobrien * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
2879968Sobrien * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
2979968Sobrien * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
3079968Sobrien * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
3179968Sobrien * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
3279968Sobrien * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
3379968Sobrien * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
3479968Sobrien * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
3579968Sobrien * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
3679968Sobrien * POSSIBILITY OF SUCH DAMAGE.
3779968Sobrien */
3879968Sobrien
3979968Sobrien/*
4079968Sobrien * Copyright (c) 1985, 1988, 1990, 1992, 1993, 1994
4179968Sobrien *	The Regents of the University of California.  All rights reserved.
4279968Sobrien *
4379968Sobrien * Redistribution and use in source and binary forms, with or without
4479968Sobrien * modification, are permitted provided that the following conditions
4579968Sobrien * are met:
4679968Sobrien * 1. Redistributions of source code must retain the above copyright
4779968Sobrien *    notice, this list of conditions and the following disclaimer.
4879968Sobrien * 2. Redistributions in binary form must reproduce the above copyright
4979968Sobrien *    notice, this list of conditions and the following disclaimer in the
5079968Sobrien *    documentation and/or other materials provided with the distribution.
51133939Sobrien * 3. Neither the name of the University nor the names of its contributors
5279968Sobrien *    may be used to endorse or promote products derived from this software
5379968Sobrien *    without specific prior written permission.
5479968Sobrien *
5579968Sobrien * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
5679968Sobrien * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
5779968Sobrien * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
5879968Sobrien * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
5979968Sobrien * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
6079968Sobrien * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
6179968Sobrien * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
6279968Sobrien * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
6379968Sobrien * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
6479968Sobrien * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
6579968Sobrien * SUCH DAMAGE.
6679968Sobrien */
6779968Sobrien
6879968Sobrien/*
6979968Sobrien * Copyright (C) 1997 and 1998 WIDE Project.
7079968Sobrien * All rights reserved.
7179968Sobrien *
7279968Sobrien * Redistribution and use in source and binary forms, with or without
7379968Sobrien * modification, are permitted provided that the following conditions
7479968Sobrien * are met:
7579968Sobrien * 1. Redistributions of source code must retain the above copyright
7679968Sobrien *    notice, this list of conditions and the following disclaimer.
7779968Sobrien * 2. Redistributions in binary form must reproduce the above copyright
7879968Sobrien *    notice, this list of conditions and the following disclaimer in the
7979968Sobrien *    documentation and/or other materials provided with the distribution.
8079968Sobrien * 3. Neither the name of the project nor the names of its contributors
8179968Sobrien *    may be used to endorse or promote products derived from this software
8279968Sobrien *    without specific prior written permission.
8379968Sobrien *
8479968Sobrien * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``AS IS'' AND
8579968Sobrien * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
8679968Sobrien * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
8779968Sobrien * ARE DISCLAIMED.  IN NO EVENT SHALL THE PROJECT OR CONTRIBUTORS BE LIABLE
8879968Sobrien * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
8979968Sobrien * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
9079968Sobrien * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
9179968Sobrien * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
9279968Sobrien * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
9379968Sobrien * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
9479968Sobrien * SUCH DAMAGE.
9579968Sobrien */
9679968Sobrien
97108746Sobrien#include <sys/cdefs.h>
98108746Sobrien#ifndef lint
99108746Sobrien__COPYRIGHT(
100108746Sobrien"@(#) Copyright (c) 1985, 1988, 1990, 1992, 1993, 1994\n\
101108746Sobrien	The Regents of the University of California.  All rights reserved.\n");
102108746Sobrien#endif /* not lint */
103108746Sobrien
104108746Sobrien#ifndef lint
105108746Sobrien#if 0
106108746Sobrienstatic char sccsid[] = "@(#)ftpd.c	8.5 (Berkeley) 4/28/95";
107108746Sobrien#else
108161770Sobrien__RCSID("$NetBSD: ftpd.c,v 1.176 2006/05/09 20:18:06 mrg Exp $");
109108746Sobrien#endif
110161770Sobrien__FBSDID("$FreeBSD$");
111108746Sobrien#endif /* not lint */
112108746Sobrien
11379968Sobrien/*
11479968Sobrien * FTP server.
11579968Sobrien */
116108746Sobrien#include <sys/param.h>
117108746Sobrien#include <sys/stat.h>
118108746Sobrien#include <sys/ioctl.h>
119108746Sobrien#include <sys/socket.h>
120108746Sobrien#include <sys/wait.h>
121108746Sobrien#include <sys/mman.h>
122108746Sobrien#include <sys/resource.h>
12379968Sobrien
124108746Sobrien#include <netinet/in.h>
125108746Sobrien#include <netinet/in_systm.h>
126108746Sobrien#include <netinet/ip.h>
127108746Sobrien
12879968Sobrien#define	FTP_NAMES
129108746Sobrien#include <arpa/ftp.h>
130108746Sobrien#include <arpa/inet.h>
131108746Sobrien#include <arpa/telnet.h>
13279968Sobrien
133108746Sobrien#include <ctype.h>
134108746Sobrien#include <dirent.h>
135108746Sobrien#include <err.h>
136108746Sobrien#include <errno.h>
137108746Sobrien#include <fcntl.h>
138108746Sobrien#include <fnmatch.h>
139108746Sobrien#include <glob.h>
140108746Sobrien#include <grp.h>
141108746Sobrien#include <limits.h>
142108746Sobrien#include <netdb.h>
143108746Sobrien#include <pwd.h>
144161770Sobrien#include <poll.h>
145108746Sobrien#include <signal.h>
146108746Sobrien#include <stdarg.h>
147108746Sobrien#include <stdio.h>
148108746Sobrien#include <stdlib.h>
149108746Sobrien#include <string.h>
150108746Sobrien#include <syslog.h>
151108746Sobrien#include <time.h>
152108746Sobrien#include <tzfile.h>
153108746Sobrien#include <unistd.h>
154108746Sobrien#include <util.h>
155108746Sobrien#ifdef SUPPORT_UTMP
156108746Sobrien#include <utmp.h>
15779968Sobrien#endif
158108746Sobrien#ifdef SUPPORT_UTMPX
159108746Sobrien#include <utmpx.h>
160108746Sobrien#endif
16179968Sobrien#ifdef SKEY
16279968Sobrien#include <skey.h>
16379968Sobrien#endif
16479968Sobrien#ifdef KERBEROS5
16579968Sobrien#include <com_err.h>
16679968Sobrien#include <krb5/krb5.h>
16779968Sobrien#endif
168161770Sobrien
169161770Sobrien#ifdef	LOGIN_CAP
170133939Sobrien#include <login_cap.h>
171133939Sobrien#endif
17279968Sobrien
173161770Sobrien#ifdef USE_PAM
174161770Sobrien#include <security/pam_appl.h>
175161770Sobrien#endif
176161770Sobrien
17779968Sobrien#define	GLOBAL
17879968Sobrien#include "extern.h"
17979968Sobrien#include "pathnames.h"
18079968Sobrien#include "version.h"
18179968Sobrien
182161770Sobrien#include "nbsd_pidfile.h"
183161770Sobrien
184133939Sobrienvolatile sig_atomic_t	transflag;
185133939Sobrienvolatile sig_atomic_t	urgflag;
186133939Sobrien
18779968Sobrienint	data;
188161770Sobrienint	Dflag;
18979968Sobrienint	sflag;
19079968Sobrienint	stru;			/* avoid C keyword */
19179968Sobrienint	mode;
19279968Sobrienint	dataport;		/* use specific data port */
19379968Sobrienint	dopidfile;		/* maintain pid file */
19479968Sobrienint	doutmp;			/* update utmp file */
19579968Sobrienint	dowtmp;			/* update wtmp file */
196133939Sobrienint	doxferlog;		/* syslog/write wu-ftpd style xferlog entries */
197133939Sobrienint	xferlogfd;		/* fd to write wu-ftpd xferlog entries to */
19879968Sobrienint	dropprivs;		/* if privileges should or have been dropped */
19979968Sobrienint	mapped;			/* IPv4 connection on AF_INET6 socket */
20079968Sobrienoff_t	file_size;
20179968Sobrienoff_t	byte_count;
20279968Sobrienstatic char ttyline[20];
203161770Sobrien
204161770Sobrien#ifdef USE_PAM
205161770Sobrienstatic int	auth_pam(struct passwd **, const char *);
206161770Sobrienpam_handle_t	*pamh = NULL;
207161770Sobrien#endif
208161770Sobrien
209108746Sobrien#ifdef SUPPORT_UTMP
21079968Sobrienstatic struct utmp utmp;	/* for utmp */
211108746Sobrien#endif
212108746Sobrien#ifdef SUPPORT_UTMPX
213108746Sobrienstatic struct utmpx utmpx;	/* for utmpx */
214108746Sobrien#endif
21579968Sobrien
21679968Sobrienstatic const char *anondir = NULL;
21779968Sobrienstatic const char *confdir = _DEFAULT_CONFDIR;
21879968Sobrien
219133939Sobrienstatic char	*curname;		/* current USER name */
220133939Sobrienstatic size_t	curname_len;		/* length of curname (include NUL) */
221133939Sobrien
22279968Sobrien#if defined(KERBEROS) || defined(KERBEROS5)
22379968Sobrienint	has_ccache = 0;
22479968Sobrienint	notickets = 1;
22579968Sobrienchar	*krbtkfile_env = NULL;
22679968Sobrienchar	*tty = ttyline;
22779968Sobrienint	login_krb5_forwardable_tgt = 0;
22879968Sobrien#endif
22979968Sobrien
23079968Sobrienint epsvall = 0;
23179968Sobrien
23279968Sobrien/*
23379968Sobrien * Timeout intervals for retrying connections
23479968Sobrien * to hosts that don't accept PORT cmds.  This
23579968Sobrien * is a kludge, but given the problems with TCP...
23679968Sobrien */
23779968Sobrien#define	SWAITMAX	90	/* wait at most 90 seconds */
23879968Sobrien#define	SWAITINT	5	/* interval between retries */
23979968Sobrien
24079968Sobrienint	swaitmax = SWAITMAX;
24179968Sobrienint	swaitint = SWAITINT;
24279968Sobrien
243108746Sobrienenum send_status {
244108746Sobrien	SS_SUCCESS,
245133939Sobrien	SS_ABORTED,			/* transfer aborted */
246108746Sobrien	SS_NO_TRANSFER,			/* no transfer made yet */
247108746Sobrien	SS_FILE_ERROR,			/* file read error */
248108746Sobrien	SS_DATA_ERROR			/* data send error */
249108746Sobrien};
250108746Sobrien
251108886Sobrien#ifdef USE_OPIE
252108886Sobrien#include <opie.h>
253108886Sobrienstatic struct opie opiedata;
254108886Sobrienstatic char opieprompt[OPIE_CHALLENGE_MAX+1];
255108886Sobrienstatic int pwok;
256108886Sobrien#endif
257108886Sobrien
25879968Sobrienstatic int	 bind_pasv_addr(void);
25979968Sobrienstatic int	 checkuser(const char *, const char *, int, int, char **);
26079968Sobrienstatic int	 checkaccess(const char *);
26179968Sobrienstatic int	 checkpassword(const struct passwd *, const char *);
26279968Sobrienstatic void	 end_login(void);
26379968Sobrienstatic FILE	*getdatasock(const char *);
26479968Sobrienstatic char	*gunique(const char *);
265161770Sobrienstatic void	 login_utmp(const char *, const char *, const char *,
266161770Sobrien		     struct sockinet *);
26779968Sobrienstatic void	 logremotehost(struct sockinet *);
26879968Sobrienstatic void	 lostconn(int);
269133939Sobrienstatic void	 toolong(int);
270133939Sobrienstatic void	 sigquit(int);
271133939Sobrienstatic void	 sigurg(int);
272133939Sobrienstatic int	 handleoobcmd(void);
27379968Sobrienstatic int	 receive_data(FILE *, FILE *);
274108746Sobrienstatic int	 send_data(FILE *, FILE *, const struct stat *, int);
27579968Sobrienstatic struct passwd *sgetpwnam(const char *);
276108746Sobrienstatic int	 write_data(int, char *, size_t, off_t *, struct timeval *,
277108746Sobrien		     int);
278108746Sobrienstatic enum send_status
279108746Sobrien		 send_data_with_read(int, int, const struct stat *, int);
280108746Sobrienstatic enum send_status
281108746Sobrien		 send_data_with_mmap(int, int, const struct stat *, int);
282108746Sobrienstatic void	 logrusage(const struct rusage *, const struct rusage *);
283108746Sobrienstatic void	 logout_utmp(void);
28479968Sobrien
28579968Sobrienint	main(int, char *[]);
28679968Sobrien
28779968Sobrien#if defined(KERBEROS)
28879968Sobrienint	klogin(struct passwd *, char *, char *, char *);
28979968Sobrienvoid	kdestroy(void);
29079968Sobrien#endif
29179968Sobrien#if defined(KERBEROS5)
29279968Sobrienint	k5login(struct passwd *, char *, char *, char *);
29379968Sobrienvoid	k5destroy(void);
29479968Sobrien#endif
29579968Sobrien
29679968Sobrienint
29779968Sobrienmain(int argc, char *argv[])
29879968Sobrien{
299161770Sobrien	int		ch, on = 1, tos, keepalive;
300161770Sobrien	socklen_t	addrlen;
30179968Sobrien#ifdef KERBEROS5
30279968Sobrien	krb5_error_code	kerror;
30379968Sobrien#endif
30479968Sobrien	char		*p;
305133939Sobrien	const char	*xferlogname = NULL;
306108746Sobrien	long		l;
307133939Sobrien	struct sigaction sa;
308161770Sobrien	sa_family_t	af = AF_UNSPEC;
30979968Sobrien
31079968Sobrien	connections = 1;
311161770Sobrien	ftpd_debug = 0;
31279968Sobrien	logging = 0;
31379968Sobrien	pdata = -1;
314161770Sobrien	Dflag = 0;
31579968Sobrien	sflag = 0;
31679968Sobrien	dataport = 0;
31779968Sobrien	dopidfile = 1;		/* default: DO use a pid file to count users */
31879968Sobrien	doutmp = 0;		/* default: Do NOT log to utmp */
31979968Sobrien	dowtmp = 1;		/* default: DO log to wtmp */
32079968Sobrien	doxferlog = 0;		/* default: Do NOT syslog xferlog */
321133939Sobrien	xferlogfd = -1;		/* default: Do NOT write xferlog file */
32279968Sobrien	dropprivs = 0;
32379968Sobrien	mapped = 0;
32479968Sobrien	usedefault = 1;
32579968Sobrien	emailaddr = NULL;
32679968Sobrien	hostname[0] = '\0';
32779968Sobrien	homedir[0] = '\0';
32879968Sobrien	gidcount = 0;
32979968Sobrien	is_oob = 0;
33079968Sobrien	version = FTPD_VERSION;
33179968Sobrien
33279968Sobrien	/*
33379968Sobrien	 * LOG_NDELAY sets up the logging connection immediately,
33479968Sobrien	 * necessary for anonymous ftp's that chroot and can't do it later.
33579968Sobrien	 */
336108746Sobrien	openlog("ftpd", LOG_PID | LOG_NDELAY, LOG_FTP);
33779968Sobrien
338161770Sobrien	while ((ch = getopt(argc, argv,
339161770Sobrien	    "46a:c:C:Dde:h:HlL:P:qQrst:T:uUvV:wWX")) != -1) {
34079968Sobrien		switch (ch) {
341161770Sobrien		case '4':
342161770Sobrien			af = AF_INET;
343161770Sobrien			break;
344161770Sobrien
345161770Sobrien		case '6':
346161770Sobrien			af = AF_INET6;
347161770Sobrien			break;
348161770Sobrien
34979968Sobrien		case 'a':
35079968Sobrien			anondir = optarg;
35179968Sobrien			break;
35279968Sobrien
35379968Sobrien		case 'c':
35479968Sobrien			confdir = optarg;
35579968Sobrien			break;
35679968Sobrien
35779968Sobrien		case 'C':
35879968Sobrien			pw = sgetpwnam(optarg);
35979968Sobrien			exit(checkaccess(optarg) ? 0 : 1);
36079968Sobrien			/* NOTREACHED */
36179968Sobrien
362161770Sobrien		case 'D':
363161770Sobrien			Dflag = 1;
364161770Sobrien			break;
365161770Sobrien
36679968Sobrien		case 'd':
36779968Sobrien		case 'v':		/* deprecated */
368161770Sobrien			ftpd_debug = 1;
36979968Sobrien			break;
37079968Sobrien
37179968Sobrien		case 'e':
37279968Sobrien			emailaddr = optarg;
37379968Sobrien			break;
37479968Sobrien
37579968Sobrien		case 'h':
37679968Sobrien			strlcpy(hostname, optarg, sizeof(hostname));
37779968Sobrien			break;
37879968Sobrien
37979968Sobrien		case 'H':
38079968Sobrien			if (gethostname(hostname, sizeof(hostname)) == -1)
38179968Sobrien				hostname[0] = '\0';
38279968Sobrien			hostname[sizeof(hostname) - 1] = '\0';
38379968Sobrien			break;
38479968Sobrien
38579968Sobrien		case 'l':
38679968Sobrien			logging++;	/* > 1 == extra logging */
38779968Sobrien			break;
38879968Sobrien
389133939Sobrien		case 'L':
390133939Sobrien			xferlogname = optarg;
391133939Sobrien			break;
392133939Sobrien
39379968Sobrien		case 'P':
394108746Sobrien			errno = 0;
395108746Sobrien			p = NULL;
396108746Sobrien			l = strtol(optarg, &p, 10);
397108746Sobrien			if (errno || *optarg == '\0' || *p != '\0' ||
398108746Sobrien			    l < IPPORT_RESERVED ||
399108746Sobrien			    l > IPPORT_ANONMAX) {
40079968Sobrien				syslog(LOG_WARNING, "Invalid dataport %s",
40179968Sobrien				    optarg);
40279968Sobrien				dataport = 0;
40379968Sobrien			}
404108746Sobrien			dataport = (int)l;
40579968Sobrien			break;
40679968Sobrien
40779968Sobrien		case 'q':
40879968Sobrien			dopidfile = 1;
40979968Sobrien			break;
41079968Sobrien
41179968Sobrien		case 'Q':
41279968Sobrien			dopidfile = 0;
41379968Sobrien			break;
41479968Sobrien
41579968Sobrien		case 'r':
41679968Sobrien			dropprivs = 1;
41779968Sobrien			break;
41879968Sobrien
41979968Sobrien		case 's':
42079968Sobrien			sflag = 1;
42179968Sobrien			break;
42279968Sobrien
42379968Sobrien		case 't':
42479968Sobrien		case 'T':
42579968Sobrien			syslog(LOG_WARNING,
42679968Sobrien			    "-%c has been deprecated in favour of ftpd.conf",
42779968Sobrien			    ch);
42879968Sobrien			break;
42979968Sobrien
43079968Sobrien		case 'u':
43179968Sobrien			doutmp = 1;
43279968Sobrien			break;
43379968Sobrien
43479968Sobrien		case 'U':
43579968Sobrien			doutmp = 0;
43679968Sobrien			break;
43779968Sobrien
43879968Sobrien		case 'V':
43979968Sobrien			if (EMPTYSTR(optarg) || strcmp(optarg, "-") == 0)
44079968Sobrien				version = NULL;
44179968Sobrien			else
442161770Sobrien				version = ftpd_strdup(optarg);
44379968Sobrien			break;
44479968Sobrien
44579968Sobrien		case 'w':
44679968Sobrien			dowtmp = 1;
44779968Sobrien			break;
44879968Sobrien
44979968Sobrien		case 'W':
45079968Sobrien			dowtmp = 0;
45179968Sobrien			break;
45279968Sobrien
45379968Sobrien		case 'X':
454133939Sobrien			doxferlog |= 1;
45579968Sobrien			break;
45679968Sobrien
45779968Sobrien		default:
45879968Sobrien			if (optopt == 'a' || optopt == 'C')
45979968Sobrien				exit(1);
46079968Sobrien			syslog(LOG_WARNING, "unknown flag -%c ignored", optopt);
46179968Sobrien			break;
46279968Sobrien		}
46379968Sobrien	}
46479968Sobrien	if (EMPTYSTR(confdir))
46579968Sobrien		confdir = _DEFAULT_CONFDIR;
46679968Sobrien
467161770Sobrien	if (dowtmp) {
468161770Sobrien#ifdef SUPPORT_UTMPX
469161770Sobrien		ftpd_initwtmpx();
470161770Sobrien#endif
471161770Sobrien#ifdef SUPPORT_UTMP
472161770Sobrien		ftpd_initwtmp();
473161770Sobrien#endif
474161770Sobrien	}
475133939Sobrien	errno = 0;
476133939Sobrien	l = sysconf(_SC_LOGIN_NAME_MAX);
477133939Sobrien	if (l == -1 && errno != 0) {
478133939Sobrien		syslog(LOG_ERR, "sysconf _SC_LOGIN_NAME_MAX: %m");
479133939Sobrien		exit(1);
480133939Sobrien	} else if (l <= 0) {
481133939Sobrien		syslog(LOG_WARNING, "using conservative LOGIN_NAME_MAX value");
482133939Sobrien		curname_len = _POSIX_LOGIN_NAME_MAX;
483133939Sobrien	} else
484133939Sobrien		curname_len = (size_t)l;
485133939Sobrien	curname = malloc(curname_len);
486133939Sobrien	if (curname == NULL) {
487133939Sobrien		syslog(LOG_ERR, "malloc: %m");
488133939Sobrien		exit(1);
489133939Sobrien	}
490133939Sobrien	curname[0] = '\0';
491133939Sobrien
492161770Sobrien	if (Dflag) {
493161770Sobrien		int error, fd, i, n, *socks;
494161770Sobrien		struct pollfd *fds;
495161770Sobrien		struct addrinfo hints, *res, *res0;
496161770Sobrien
497161770Sobrien		if (daemon(1, 0) == -1) {
498161770Sobrien			syslog(LOG_ERR, "failed to daemonize: %m");
499161770Sobrien			exit(1);
500161770Sobrien		}
501161770Sobrien		(void)memset(&sa, 0, sizeof(sa));
502161770Sobrien		sa.sa_handler = SIG_IGN;
503161770Sobrien		sa.sa_flags = SA_NOCLDWAIT;
504161770Sobrien		sigemptyset(&sa.sa_mask);
505161770Sobrien		(void)sigaction(SIGCHLD, &sa, NULL);
506161770Sobrien
507161770Sobrien		(void)memset(&hints, 0, sizeof(hints));
508161770Sobrien		hints.ai_flags = AI_PASSIVE;
509161770Sobrien		hints.ai_family = af;
510161770Sobrien		hints.ai_socktype = SOCK_STREAM;
511161770Sobrien		error = getaddrinfo(NULL, "ftp", &hints, &res0);
512161770Sobrien		if (error) {
513161770Sobrien			syslog(LOG_ERR, "getaddrinfo: %s", gai_strerror(error));
514161770Sobrien			exit(1);
515161770Sobrien		}
516161770Sobrien
517161770Sobrien		for (n = 0, res = res0; res != NULL; res = res->ai_next)
518161770Sobrien			n++;
519161770Sobrien		if (n == 0) {
520161770Sobrien			syslog(LOG_ERR, "no addresses available");
521161770Sobrien			exit(1);
522161770Sobrien		}
523161770Sobrien		socks = malloc(n * sizeof(int));
524161770Sobrien		fds = malloc(n * sizeof(struct pollfd));
525161770Sobrien		if (socks == NULL || fds == NULL) {
526161770Sobrien			syslog(LOG_ERR, "malloc: %m");
527161770Sobrien			exit(1);
528161770Sobrien		}
529161770Sobrien
530161770Sobrien		for (n = 0, res = res0; res != NULL; res = res->ai_next) {
531161770Sobrien			socks[n] = socket(res->ai_family, res->ai_socktype,
532161770Sobrien			    res->ai_protocol);
533161770Sobrien			if (socks[n] == -1)
534161770Sobrien				continue;
535161770Sobrien			(void)setsockopt(socks[n], SOL_SOCKET, SO_REUSEADDR,
536161770Sobrien			    &on, sizeof(on));
537161770Sobrien			if (bind(socks[n], res->ai_addr, res->ai_addrlen)
538161770Sobrien			    == -1) {
539161770Sobrien				(void)close(socks[n]);
540161770Sobrien				continue;
541161770Sobrien			}
542161770Sobrien			if (listen(socks[n], 12) == -1) {
543161770Sobrien				(void)close(socks[n]);
544161770Sobrien				continue;
545161770Sobrien			}
546161770Sobrien
547161770Sobrien			fds[n].fd = socks[n];
548161770Sobrien			fds[n].events = POLLIN;
549161770Sobrien			n++;
550161770Sobrien		}
551161770Sobrien		if (n == 0) {
552161770Sobrien			syslog(LOG_ERR, "%m");
553161770Sobrien			exit(1);
554161770Sobrien		}
555161770Sobrien		freeaddrinfo(res0);
556161770Sobrien
557161770Sobrien		if (pidfile(NULL) == -1)
558161770Sobrien			syslog(LOG_ERR, "failed to write a pid file: %m");
559161770Sobrien
560161770Sobrien		for (;;) {
561161770Sobrien			if (poll(fds, n, INFTIM) == -1) {
562161770Sobrien				if (errno == EINTR)
563161770Sobrien					continue;
564161770Sobrien				syslog(LOG_ERR, "poll: %m");
565161770Sobrien				exit(1);
566161770Sobrien			}
567161770Sobrien			for (i = 0; i < n; i++) {
568161770Sobrien				if (fds[i].revents & POLLIN) {
569161770Sobrien					fd = accept(fds[i].fd, NULL, NULL);
570161770Sobrien					if (fd == -1) {
571161770Sobrien						syslog(LOG_ERR, "accept: %m");
572161770Sobrien						continue;
573161770Sobrien					}
574161770Sobrien					switch (fork()) {
575161770Sobrien					case -1:
576161770Sobrien						syslog(LOG_ERR, "fork: %m");
577161770Sobrien						break;
578161770Sobrien					case 0:
579161770Sobrien						goto child;
580161770Sobrien						/* NOTREACHED */
581161770Sobrien					}
582161770Sobrien					(void)close(fd);
583161770Sobrien				}
584161770Sobrien			}
585161770Sobrien		}
586161770Sobrien child:
587161770Sobrien		(void)dup2(fd, STDIN_FILENO);
588161770Sobrien		(void)dup2(fd, STDOUT_FILENO);
589161770Sobrien		(void)dup2(fd, STDERR_FILENO);
590161770Sobrien		for (i = 0; i < n; i++)
591161770Sobrien			(void)close(socks[i]);
592161770Sobrien	}
593161770Sobrien
59479968Sobrien	memset((char *)&his_addr, 0, sizeof(his_addr));
59579968Sobrien	addrlen = sizeof(his_addr.si_su);
59679968Sobrien	if (getpeername(0, (struct sockaddr *)&his_addr.si_su, &addrlen) < 0) {
59779968Sobrien		syslog(LOG_ERR, "getpeername (%s): %m",argv[0]);
59879968Sobrien		exit(1);
59979968Sobrien	}
60079968Sobrien	his_addr.su_len = addrlen;
60179968Sobrien	memset((char *)&ctrl_addr, 0, sizeof(ctrl_addr));
60279968Sobrien	addrlen = sizeof(ctrl_addr.si_su);
60379968Sobrien	if (getsockname(0, (struct sockaddr *)&ctrl_addr, &addrlen) < 0) {
60479968Sobrien		syslog(LOG_ERR, "getsockname (%s): %m",argv[0]);
60579968Sobrien		exit(1);
60679968Sobrien	}
60779968Sobrien	ctrl_addr.su_len = addrlen;
60879968Sobrien#ifdef INET6
60979968Sobrien	if (his_addr.su_family == AF_INET6
61079968Sobrien	 && IN6_IS_ADDR_V4MAPPED(&his_addr.su_6addr)) {
61179968Sobrien#if 1
61279968Sobrien		/*
61379968Sobrien		 * IPv4 control connection arrived to AF_INET6 socket.
61479968Sobrien		 * I hate to do this, but this is the easiest solution.
61579968Sobrien		 *
61679968Sobrien		 * The assumption is untrue on SIIT environment.
61779968Sobrien		 */
61879968Sobrien		struct sockinet tmp_addr;
61979968Sobrien		const int off = sizeof(struct in6_addr) - sizeof(struct in_addr);
62079968Sobrien
62179968Sobrien		tmp_addr = his_addr;
62279968Sobrien		memset(&his_addr, 0, sizeof(his_addr));
62379968Sobrien		his_addr.su_family = AF_INET;
62479968Sobrien		his_addr.su_len = sizeof(his_addr.si_su.su_sin);
62579968Sobrien		memcpy(&his_addr.su_addr, &tmp_addr.su_6addr.s6_addr[off],
62679968Sobrien		    sizeof(his_addr.su_addr));
62779968Sobrien		his_addr.su_port = tmp_addr.su_port;
62879968Sobrien
62979968Sobrien		tmp_addr = ctrl_addr;
63079968Sobrien		memset(&ctrl_addr, 0, sizeof(ctrl_addr));
63179968Sobrien		ctrl_addr.su_family = AF_INET;
63279968Sobrien		ctrl_addr.su_len = sizeof(ctrl_addr.si_su.su_sin);
63379968Sobrien		memcpy(&ctrl_addr.su_addr, &tmp_addr.su_6addr.s6_addr[off],
63479968Sobrien		    sizeof(ctrl_addr.su_addr));
63579968Sobrien		ctrl_addr.su_port = tmp_addr.su_port;
63679968Sobrien#else
63779968Sobrien		while (fgets(line, sizeof(line), fd) != NULL) {
63879968Sobrien			if ((cp = strchr(line, '\n')) != NULL)
63979968Sobrien				*cp = '\0';
64079968Sobrien			reply(-530, "%s", line);
64179968Sobrien		}
64279968Sobrien		(void) fflush(stdout);
64379968Sobrien		(void) fclose(fd);
64479968Sobrien		reply(530,
64579968Sobrien		    "Connection from IPv4 mapped address is not supported.");
64679968Sobrien		exit(0);
64779968Sobrien#endif
64879968Sobrien
64979968Sobrien		mapped = 1;
65079968Sobrien	} else
65179968Sobrien#endif /* INET6 */
65279968Sobrien		mapped = 0;
65379968Sobrien#ifdef IP_TOS
65479968Sobrien	if (!mapped && his_addr.su_family == AF_INET) {
65579968Sobrien		tos = IPTOS_LOWDELAY;
65679968Sobrien		if (setsockopt(0, IPPROTO_IP, IP_TOS, (char *)&tos,
65779968Sobrien			       sizeof(int)) < 0)
65879968Sobrien			syslog(LOG_WARNING, "setsockopt (IP_TOS): %m");
65979968Sobrien	}
66079968Sobrien#endif
66179968Sobrien	/* if the hostname hasn't been given, attempt to determine it */
66279968Sobrien	if (hostname[0] == '\0') {
66379968Sobrien		if (getnameinfo((struct sockaddr *)&ctrl_addr.si_su,
66479968Sobrien		    ctrl_addr.su_len, hostname, sizeof(hostname), NULL, 0, 0)
66579968Sobrien		    != 0)
66679968Sobrien			(void)gethostname(hostname, sizeof(hostname));
66779968Sobrien		hostname[sizeof(hostname) - 1] = '\0';
66879968Sobrien	}
66979968Sobrien
67079968Sobrien	/* set this here so klogin can use it... */
67179968Sobrien	(void)snprintf(ttyline, sizeof(ttyline), "ftp%d", getpid());
67279968Sobrien
67379968Sobrien	(void) freopen(_PATH_DEVNULL, "w", stderr);
67479968Sobrien
675133939Sobrien	memset(&sa, 0, sizeof(sa));
676133939Sobrien	sa.sa_handler = SIG_DFL;
677133939Sobrien	sa.sa_flags = SA_RESTART;
678133939Sobrien	sigemptyset(&sa.sa_mask);
679133939Sobrien	(void) sigaction(SIGCHLD, &sa, NULL);
680133939Sobrien
681133939Sobrien	sa.sa_handler = sigquit;
682133939Sobrien	sa.sa_flags = SA_RESTART;
683133939Sobrien	sigfillset(&sa.sa_mask);	/* block all sigs in these handlers */
684133939Sobrien	(void) sigaction(SIGHUP, &sa, NULL);
685133939Sobrien	(void) sigaction(SIGINT, &sa, NULL);
686133939Sobrien	(void) sigaction(SIGQUIT, &sa, NULL);
687133939Sobrien	(void) sigaction(SIGTERM, &sa, NULL);
688133939Sobrien	sa.sa_handler = lostconn;
689133939Sobrien	(void) sigaction(SIGPIPE, &sa, NULL);
690133939Sobrien	sa.sa_handler = toolong;
691133939Sobrien	(void) sigaction(SIGALRM, &sa, NULL);
692133939Sobrien	sa.sa_handler = sigurg;
693133939Sobrien	(void) sigaction(SIGURG, &sa, NULL);
694133939Sobrien
69579968Sobrien	/* Try to handle urgent data inline */
69679968Sobrien#ifdef SO_OOBINLINE
69779968Sobrien	if (setsockopt(0, SOL_SOCKET, SO_OOBINLINE, (char *)&on, sizeof(on)) < 0)
69879968Sobrien		syslog(LOG_WARNING, "setsockopt: %m");
69979968Sobrien#endif
70079968Sobrien	/* Set keepalives on the socket to detect dropped connections.  */
70179968Sobrien#ifdef SO_KEEPALIVE
70279968Sobrien	keepalive = 1;
70379968Sobrien	if (setsockopt(0, SOL_SOCKET, SO_KEEPALIVE, (char *)&keepalive,
70479968Sobrien	    sizeof(int)) < 0)
70579968Sobrien		syslog(LOG_WARNING, "setsockopt (SO_KEEPALIVE): %m");
70679968Sobrien#endif
70779968Sobrien
70879968Sobrien#ifdef	F_SETOWN
70979968Sobrien	if (fcntl(fileno(stdin), F_SETOWN, getpid()) == -1)
71079968Sobrien		syslog(LOG_WARNING, "fcntl F_SETOWN: %m");
71179968Sobrien#endif
71279968Sobrien	logremotehost(&his_addr);
71379968Sobrien	/*
71479968Sobrien	 * Set up default state
71579968Sobrien	 */
71679968Sobrien	data = -1;
71779968Sobrien	type = TYPE_A;
71879968Sobrien	form = FORM_N;
71979968Sobrien	stru = STRU_F;
72079968Sobrien	mode = MODE_S;
72179968Sobrien	tmpline[0] = '\0';
72279968Sobrien	hasyyerrored = 0;
72379968Sobrien
72479968Sobrien#ifdef KERBEROS5
72579968Sobrien	kerror = krb5_init_context(&kcontext);
72679968Sobrien	if (kerror) {
72779968Sobrien		syslog(LOG_ERR, "%s when initializing Kerberos context",
72879968Sobrien		    error_message(kerror));
72979968Sobrien		exit(0);
73079968Sobrien	}
73179968Sobrien#endif /* KERBEROS5 */
73279968Sobrien
73379968Sobrien	init_curclass();
73479968Sobrien	curclass.timeout = 300;		/* 5 minutes, as per login(1) */
73579968Sobrien	curclass.type = CLASS_REAL;
73679968Sobrien
73779968Sobrien	/* If logins are disabled, print out the message. */
73879968Sobrien	if (display_file(_PATH_NOLOGIN, 530)) {
73979968Sobrien		reply(530, "System not available.");
74079968Sobrien		exit(0);
74179968Sobrien	}
742161770Sobrien	(void)display_file(conffilename(_NAME_FTPWELCOME), 220);
74379968Sobrien		/* reply(220,) must follow */
74479968Sobrien	if (EMPTYSTR(version))
74579968Sobrien		reply(220, "%s FTP server ready.", hostname);
74679968Sobrien	else
74779968Sobrien		reply(220, "%s FTP server (%s) ready.", hostname, version);
74879968Sobrien
749133939Sobrien	if (xferlogname != NULL) {
750133939Sobrien		xferlogfd = open(xferlogname, O_WRONLY | O_APPEND | O_CREAT,
751133939Sobrien		    0660);
752133939Sobrien		if (xferlogfd == -1)
753133939Sobrien			syslog(LOG_WARNING, "open xferlog `%s': %m",
754133939Sobrien			    xferlogname);
755133939Sobrien		else
756133939Sobrien			doxferlog |= 2;
757133939Sobrien	}
758133939Sobrien
75979968Sobrien	ftp_loop();
76079968Sobrien	/* NOTREACHED */
76179968Sobrien}
76279968Sobrien
76379968Sobrienstatic void
76479968Sobrienlostconn(int signo)
76579968Sobrien{
76679968Sobrien
767161770Sobrien	if (ftpd_debug)
76879968Sobrien		syslog(LOG_DEBUG, "lost connection");
76979968Sobrien	dologout(1);
77079968Sobrien}
77179968Sobrien
772133939Sobrienstatic void
773133939Sobrientoolong(int signo)
774133939Sobrien{
775133939Sobrien
776133939Sobrien		/* XXXSIGRACE */
777133939Sobrien	reply(421,
778133939Sobrien	    "Timeout (" LLF " seconds): closing control connection.",
779133939Sobrien	    (LLT)curclass.timeout);
780133939Sobrien	if (logging)
781133939Sobrien		syslog(LOG_INFO, "User %s timed out after " LLF " seconds",
782133939Sobrien		    (pw ? pw->pw_name : "unknown"), (LLT)curclass.timeout);
783133939Sobrien	dologout(1);
784133939Sobrien}
785133939Sobrien
786133939Sobrienstatic void
787133939Sobriensigquit(int signo)
788133939Sobrien{
789133939Sobrien
790161770Sobrien	if (ftpd_debug)
791133939Sobrien		syslog(LOG_DEBUG, "got signal %d", signo);
792133939Sobrien	dologout(1);
793133939Sobrien}
794133939Sobrien
795133939Sobrienstatic void
796133939Sobriensigurg(int signo)
797133939Sobrien{
798133939Sobrien
799133939Sobrien	urgflag = 1;
800133939Sobrien}
801133939Sobrien
802133939Sobrien
80379968Sobrien/*
80479968Sobrien * Save the result of a getpwnam.  Used for USER command, since
80579968Sobrien * the data returned must not be clobbered by any other command
80679968Sobrien * (e.g., globbing).
80779968Sobrien */
80879968Sobrienstatic struct passwd *
80979968Sobriensgetpwnam(const char *name)
81079968Sobrien{
81179968Sobrien	static struct passwd save;
81279968Sobrien	struct passwd *p;
81379968Sobrien
81479968Sobrien	if ((p = getpwnam(name)) == NULL)
81579968Sobrien		return (p);
81679968Sobrien	if (save.pw_name) {
81779968Sobrien		free((char *)save.pw_name);
81879968Sobrien		memset(save.pw_passwd, 0, strlen(save.pw_passwd));
81979968Sobrien		free((char *)save.pw_passwd);
82079968Sobrien		free((char *)save.pw_gecos);
82179968Sobrien		free((char *)save.pw_dir);
82279968Sobrien		free((char *)save.pw_shell);
82379968Sobrien	}
82479968Sobrien	save = *p;
825161770Sobrien	save.pw_name = ftpd_strdup(p->pw_name);
826161770Sobrien	save.pw_passwd = ftpd_strdup(p->pw_passwd);
827161770Sobrien	save.pw_gecos = ftpd_strdup(p->pw_gecos);
828161770Sobrien	save.pw_dir = ftpd_strdup(p->pw_dir);
829161770Sobrien	save.pw_shell = ftpd_strdup(p->pw_shell);
83079968Sobrien	return (&save);
83179968Sobrien}
83279968Sobrien
83379968Sobrienstatic int	login_attempts;	/* number of failed login attempts */
83492282Sobrienstatic int	askpasswd;	/* had USER command, ask for PASSwd */
83592282Sobrienstatic int	permitted;	/* USER permitted */
83679968Sobrien
83779968Sobrien/*
83879968Sobrien * USER command.
83979968Sobrien * Sets global passwd pointer pw if named account exists and is acceptable;
84079968Sobrien * sets askpasswd if a PASS command is expected.  If logged in previously,
84179968Sobrien * need to reset state.  If name is "ftp" or "anonymous", the name is not in
842161770Sobrien * _NAME_FTPUSERS, and ftp account exists, set guest and pw, then just return.
84379968Sobrien * If account doesn't exist, ask for passwd anyway.  Otherwise, check user
84479968Sobrien * requesting login privileges.  Disallow anyone who does not have a standard
84579968Sobrien * shell as returned by getusershell().  Disallow anyone mentioned in the file
846161770Sobrien * _NAME_FTPUSERS to allow people such as root and uucp to be avoided.
84779968Sobrien */
84879968Sobrienvoid
84979968Sobrienuser(const char *name)
85079968Sobrien{
851161770Sobrien	char	*class;
852161770Sobrien#ifdef	LOGIN_CAP
853161770Sobrien	login_cap_t *lc = NULL;
854133939Sobrien#endif
85592282Sobrien
85692282Sobrien	class = NULL;
85779968Sobrien	if (logged_in) {
85879968Sobrien		switch (curclass.type) {
85979968Sobrien		case CLASS_GUEST:
86079968Sobrien			reply(530, "Can't change user from guest login.");
86179968Sobrien			return;
86279968Sobrien		case CLASS_CHROOT:
86379968Sobrien			reply(530, "Can't change user from chroot user.");
86479968Sobrien			return;
86579968Sobrien		case CLASS_REAL:
86679968Sobrien			if (dropprivs) {
86779968Sobrien				reply(530, "Can't change user.");
86879968Sobrien				return;
86979968Sobrien			}
87079968Sobrien			end_login();
87179968Sobrien			break;
87279968Sobrien		default:
87379968Sobrien			abort();
87479968Sobrien		}
87579968Sobrien	}
87679968Sobrien
87779968Sobrien#if defined(KERBEROS)
87879968Sobrien	kdestroy();
87979968Sobrien#endif
88079968Sobrien#if defined(KERBEROS5)
88179968Sobrien	k5destroy();
88279968Sobrien#endif
88379968Sobrien
88479968Sobrien	curclass.type = CLASS_REAL;
88592282Sobrien	askpasswd = 0;
88692282Sobrien	permitted = 0;
88792282Sobrien
88879968Sobrien	if (strcmp(name, "ftp") == 0 || strcmp(name, "anonymous") == 0) {
88979968Sobrien			/* need `pw' setup for checkaccess() and checkuser () */
89079968Sobrien		if ((pw = sgetpwnam("ftp")) == NULL)
89179968Sobrien			reply(530, "User %s unknown.", name);
89279968Sobrien		else if (! checkaccess("ftp") || ! checkaccess("anonymous"))
89379968Sobrien			reply(530, "User %s access denied.", name);
89479968Sobrien		else {
89579968Sobrien			curclass.type = CLASS_GUEST;
89679968Sobrien			askpasswd = 1;
89779968Sobrien			reply(331,
89879968Sobrien			    "Guest login ok, type your name as password.");
89979968Sobrien		}
90092282Sobrien		if (!askpasswd) {
90192282Sobrien			if (logging)
90292282Sobrien				syslog(LOG_NOTICE,
90392282Sobrien				    "ANONYMOUS FTP LOGIN REFUSED FROM %s",
90492282Sobrien				    remotehost);
90592282Sobrien			end_login();
90692282Sobrien			goto cleanup_user;
90792282Sobrien		}
90892282Sobrien		name = "ftp";
90992282Sobrien	} else
91092282Sobrien		pw = sgetpwnam(name);
91179968Sobrien
912133939Sobrien	strlcpy(curname, name, curname_len);
91379968Sobrien
91492282Sobrien			/* check user in /etc/ftpusers, and setup class */
915161770Sobrien	permitted = checkuser(_NAME_FTPUSERS, curname, 1, 0, &class);
91692282Sobrien
91792282Sobrien			/* check user in /etc/ftpchroot */
918161770Sobrien	lc = login_getpwclass(pw);
919161770Sobrien	if (checkuser(_NAME_FTPCHROOT, curname, 0, 0, NULL)
920161770Sobrien#ifdef	LOGIN_CAP	/* Allow login.conf configuration as well */
921133939Sobrien	    || login_getcapbool(lc, "ftp-chroot", 0)
922133939Sobrien#endif
923161770Sobrien	) {
92492282Sobrien		if (curclass.type == CLASS_GUEST) {
92592282Sobrien			syslog(LOG_NOTICE,
92692282Sobrien	    "Can't change guest user to chroot class; remove entry in %s",
927161770Sobrien			    _NAME_FTPCHROOT);
92892282Sobrien			exit(1);
92992282Sobrien		}
93092282Sobrien		curclass.type = CLASS_CHROOT;
93192282Sobrien	}
93292282Sobrien			/* determine default class */
93392282Sobrien	if (class == NULL) {
93492282Sobrien		switch (curclass.type) {
93592282Sobrien		case CLASS_GUEST:
936161770Sobrien			class = ftpd_strdup("guest");
93792282Sobrien			break;
93892282Sobrien		case CLASS_CHROOT:
939161770Sobrien			class = ftpd_strdup("chroot");
94092282Sobrien			break;
94192282Sobrien		case CLASS_REAL:
942161770Sobrien			class = ftpd_strdup("real");
94392282Sobrien			break;
94492282Sobrien		default:
94592282Sobrien			syslog(LOG_ERR, "unknown curclass.type %d; aborting",
94692282Sobrien			    curclass.type);
94792282Sobrien			abort();
94892282Sobrien		}
94992282Sobrien	}
95092282Sobrien			/* parse ftpd.conf, setting up various parameters */
95192282Sobrien	parse_conf(class);
95292282Sobrien			/* if not guest user, check for valid shell */
95392282Sobrien	if (pw == NULL)
95492282Sobrien		permitted = 0;
95592282Sobrien	else {
95692282Sobrien		const char	*cp, *shell;
95792282Sobrien
95892282Sobrien		if ((shell = pw->pw_shell) == NULL || *shell == 0)
95992282Sobrien			shell = _PATH_BSHELL;
96092282Sobrien		while ((cp = getusershell()) != NULL)
96192282Sobrien			if (strcmp(cp, shell) == 0)
96292282Sobrien				break;
96392282Sobrien		endusershell();
96492282Sobrien		if (cp == NULL && curclass.type != CLASS_GUEST)
96592282Sobrien			permitted = 0;
96692282Sobrien	}
96792282Sobrien
96892282Sobrien			/* deny quickly (after USER not PASS) if requested */
96992282Sobrien	if (CURCLASS_FLAGS_ISSET(denyquick) && !permitted) {
97092282Sobrien		reply(530, "User %s may not use FTP.", curname);
97192282Sobrien		if (logging)
97292282Sobrien			syslog(LOG_NOTICE, "FTP LOGIN REFUSED FROM %s, %s",
97392282Sobrien			    remotehost, curname);
97492282Sobrien		end_login();
97592282Sobrien		goto cleanup_user;
97692282Sobrien	}
97792282Sobrien
97892282Sobrien			/* if haven't asked yet (i.e, not anon), ask now */
97992282Sobrien	if (!askpasswd) {
98092282Sobrien		askpasswd = 1;
98179968Sobrien#ifdef SKEY
98292282Sobrien		if (skey_haskey(curname) == 0) {
98392282Sobrien			const char *myskey;
98479968Sobrien
98592282Sobrien			myskey = skey_keyinfo(curname);
98692282Sobrien			reply(331, "Password [ %s ] required for %s.",
98792282Sobrien			    myskey ? myskey : "error getting challenge",
98892282Sobrien			    curname);
98992282Sobrien		} else
99079968Sobrien#endif
991108886Sobrien#ifdef USE_OPIE
992108886Sobrien		if (opiechallenge(&opiedata, (char *)curname, opieprompt) ==
993108886Sobrien		    0) {
994108886Sobrien			pwok = (pw != NULL) &&
995108886Sobrien			    opieaccessfile(remotehost) &&
996108886Sobrien			    opiealways(pw->pw_dir);
997108886Sobrien			reply(331, "Response to %s %s for %s.",
998108886Sobrien			    opieprompt, pwok ? "requested" : "required",
999108886Sobrien			    curname);
1000108886Sobrien		} else
1001108886Sobrien#endif
100292282Sobrien			reply(331, "Password required for %s.", curname);
100392282Sobrien	}
100479968Sobrien
100592282Sobrien cleanup_user:
1006133939Sobrien#ifdef LOGIN_CAP
1007133939Sobrien	login_close(lc);
1008133939Sobrien#endif
100979968Sobrien	/*
101079968Sobrien	 * Delay before reading passwd after first failed
101179968Sobrien	 * attempt to slow down passwd-guessing programs.
101279968Sobrien	 */
101379968Sobrien	if (login_attempts)
101479968Sobrien		sleep((unsigned) login_attempts);
101592282Sobrien
101692282Sobrien	if (class)
101792282Sobrien		free(class);
101879968Sobrien}
101979968Sobrien
102079968Sobrien/*
102179968Sobrien * Determine whether something is to happen (allow access, chroot)
102279968Sobrien * for a user. Each line is a shell-style glob followed by
102379968Sobrien * `yes' or `no'.
102479968Sobrien *
102592282Sobrien * For backward compatibility, `allow' and `deny' are synonymns
102679968Sobrien * for `yes' and `no', respectively.
102779968Sobrien *
102879968Sobrien * Each glob is matched against the username in turn, and the first
102979968Sobrien * match found is used. If no match is found, the result is the
103079968Sobrien * argument `def'. If a match is found but without and explicit
103179968Sobrien * `yes'/`no', the result is the opposite of def.
103279968Sobrien *
103379968Sobrien * If the file doesn't exist at all, the result is the argument
103479968Sobrien * `nofile'
103579968Sobrien *
103679968Sobrien * Any line starting with `#' is considered a comment and ignored.
103779968Sobrien *
103879968Sobrien * Returns 0 if the user is denied, or 1 if they are allowed.
103979968Sobrien *
104079968Sobrien * NOTE: needs struct passwd *pw setup before use.
104179968Sobrien */
104279968Sobrienstatic int
104379968Sobriencheckuser(const char *fname, const char *name, int def, int nofile,
104479968Sobrien	    char **retclass)
104579968Sobrien{
104679968Sobrien	FILE	*fd;
104779968Sobrien	int	 retval;
104892282Sobrien	char	*word, *perm, *class, *buf, *p;
104979968Sobrien	size_t	 len, line;
105079968Sobrien
105179968Sobrien	retval = def;
105279968Sobrien	if (retclass != NULL)
105379968Sobrien		*retclass = NULL;
105479968Sobrien	if ((fd = fopen(conffilename(fname), "r")) == NULL)
105579968Sobrien		return nofile;
105679968Sobrien
105779968Sobrien	line = 0;
105879968Sobrien	for (;
105979968Sobrien	    (buf = fparseln(fd, &len, &line, NULL, FPARSELN_UNESCCOMM |
1060108746Sobrien			    FPARSELN_UNESCCONT | FPARSELN_UNESCESC)) != NULL;
106179968Sobrien	    free(buf), buf = NULL) {
106292282Sobrien		word = perm = class = NULL;
106379968Sobrien		p = buf;
106479968Sobrien		if (len < 1)
106579968Sobrien			continue;
106679968Sobrien		if (p[len - 1] == '\n')
106779968Sobrien			p[--len] = '\0';
106879968Sobrien		if (EMPTYSTR(p))
106979968Sobrien			continue;
107079968Sobrien
107192282Sobrien		NEXTWORD(p, word);
107279968Sobrien		NEXTWORD(p, perm);
107379968Sobrien		NEXTWORD(p, class);
107492282Sobrien		if (EMPTYSTR(word))
107579968Sobrien			continue;
107679968Sobrien		if (!EMPTYSTR(class)) {
107779968Sobrien			if (strcasecmp(class, "all") == 0 ||
107879968Sobrien			    strcasecmp(class, "none") == 0) {
107979968Sobrien				syslog(LOG_WARNING,
108079968Sobrien		"%s line %d: illegal user-defined class `%s' - skipping entry",
108179968Sobrien					    fname, (int)line, class);
108279968Sobrien				continue;
108379968Sobrien			}
108479968Sobrien		}
108579968Sobrien
108679968Sobrien					/* have a host specifier */
108792282Sobrien		if ((p = strchr(word, '@')) != NULL) {
108879968Sobrien			unsigned long	net, mask, addr;
108979968Sobrien			int		bits;
109079968Sobrien
109179968Sobrien			*p++ = '\0';
109279968Sobrien					/* check against network or CIDR */
1093161770Sobrien			if (isdigit((unsigned char)*p) &&
109479968Sobrien			    (bits = inet_net_pton(AF_INET, p,
109579968Sobrien			    &net, sizeof(net))) != -1) {
109679968Sobrien				net = ntohl(net);
109779968Sobrien				mask = 0xffffffffU << (32 - bits);
109879968Sobrien				addr = ntohl(his_addr.su_addr.s_addr);
109979968Sobrien				if ((addr & mask) != net)
110079968Sobrien					continue;
110179968Sobrien
110279968Sobrien					/* check against hostname glob */
110392282Sobrien			} else if (fnmatch(p, remotehost, FNM_CASEFOLD) != 0)
110479968Sobrien				continue;
110579968Sobrien		}
110679968Sobrien
110779968Sobrien					/* have a group specifier */
110892282Sobrien		if ((p = strchr(word, ':')) != NULL) {
110979968Sobrien			gid_t	*groups, *ng;
111079968Sobrien			int	 gsize, i, found;
111179968Sobrien
111292282Sobrien			if (pw == NULL)
111392282Sobrien				continue;	/* no match for unknown user */
111479968Sobrien			*p++ = '\0';
111579968Sobrien			groups = NULL;
111679968Sobrien			gsize = 16;
111779968Sobrien			do {
111879968Sobrien				ng = realloc(groups, gsize * sizeof(gid_t));
111979968Sobrien				if (ng == NULL)
112079968Sobrien					fatal(
112179968Sobrien					    "Local resource failure: realloc");
112279968Sobrien				groups = ng;
112379968Sobrien			} while (getgrouplist(pw->pw_name, pw->pw_gid,
112479968Sobrien						groups, &gsize) == -1);
112579968Sobrien			found = 0;
112679968Sobrien			for (i = 0; i < gsize; i++) {
112779968Sobrien				struct group *g;
112879968Sobrien
112979968Sobrien				if ((g = getgrgid(groups[i])) == NULL)
113079968Sobrien					continue;
113179968Sobrien				if (fnmatch(p, g->gr_name, 0) == 0) {
113279968Sobrien					found = 1;
113379968Sobrien					break;
113479968Sobrien				}
113579968Sobrien			}
113679968Sobrien			free(groups);
113779968Sobrien			if (!found)
113879968Sobrien				continue;
113979968Sobrien		}
114079968Sobrien
114179968Sobrien					/* check against username glob */
114292282Sobrien		if (fnmatch(word, name, 0) != 0)
114379968Sobrien			continue;
114479968Sobrien
114579968Sobrien		if (perm != NULL &&
114679968Sobrien		    ((strcasecmp(perm, "allow") == 0) ||
114779968Sobrien		     (strcasecmp(perm, "yes") == 0)))
114879968Sobrien			retval = 1;
114979968Sobrien		else if (perm != NULL &&
115079968Sobrien		    ((strcasecmp(perm, "deny") == 0) ||
115179968Sobrien		     (strcasecmp(perm, "no") == 0)))
115279968Sobrien			retval = 0;
115379968Sobrien		else
115479968Sobrien			retval = !def;
115579968Sobrien		if (!EMPTYSTR(class) && retclass != NULL)
1156161770Sobrien			*retclass = ftpd_strdup(class);
115779968Sobrien		free(buf);
115879968Sobrien		break;
115979968Sobrien	}
116079968Sobrien	(void) fclose(fd);
116179968Sobrien	return (retval);
116279968Sobrien}
116379968Sobrien
116479968Sobrien/*
116579968Sobrien * Check if user is allowed by /etc/ftpusers
116679968Sobrien * returns 1 for yes, 0 for no
116779968Sobrien *
116879968Sobrien * NOTE: needs struct passwd *pw setup (for checkuser())
116979968Sobrien */
117079968Sobrienstatic int
117179968Sobriencheckaccess(const char *name)
117279968Sobrien{
117379968Sobrien
1174161770Sobrien	return (checkuser(_NAME_FTPUSERS, name, 1, 0, NULL));
117579968Sobrien}
117679968Sobrien
1177108746Sobrienstatic void
1178161770Sobrienlogin_utmp(const char *line, const char *name, const char *host,
1179161770Sobrien    struct sockinet *haddr)
1180108746Sobrien{
1181108746Sobrien#if defined(SUPPORT_UTMPX) || defined(SUPPORT_UTMP)
1182108746Sobrien	struct timeval tv;
1183108746Sobrien	(void)gettimeofday(&tv, NULL);
1184108746Sobrien#endif
1185108746Sobrien#ifdef SUPPORT_UTMPX
1186108746Sobrien	if (doutmp) {
1187108746Sobrien		(void)memset(&utmpx, 0, sizeof(utmpx));
1188108746Sobrien		utmpx.ut_tv = tv;
1189108746Sobrien		utmpx.ut_pid = getpid();
1190108746Sobrien		utmpx.ut_id[0] = 'f';
1191108746Sobrien		utmpx.ut_id[1] = 't';
1192108746Sobrien		utmpx.ut_id[2] = 'p';
1193108746Sobrien		utmpx.ut_id[3] = '*';
1194108746Sobrien		utmpx.ut_type = USER_PROCESS;
1195108746Sobrien		(void)strncpy(utmpx.ut_name, name, sizeof(utmpx.ut_name));
1196108746Sobrien		(void)strncpy(utmpx.ut_line, line, sizeof(utmpx.ut_line));
1197108746Sobrien		(void)strncpy(utmpx.ut_host, host, sizeof(utmpx.ut_host));
1198161770Sobrien		(void)memcpy(&utmpx.ut_ss, &haddr->si_su, haddr->su_len);
1199133939Sobrien		ftpd_loginx(&utmpx);
1200108746Sobrien	}
1201108746Sobrien	if (dowtmp)
1202161770Sobrien		ftpd_logwtmpx(line, name, host, haddr, 0, USER_PROCESS);
1203108746Sobrien#endif
1204108746Sobrien#ifdef SUPPORT_UTMP
1205108746Sobrien	if (doutmp) {
1206108746Sobrien		(void)memset(&utmp, 0, sizeof(utmp));
1207108746Sobrien		(void)time(&utmp.ut_time);
1208108746Sobrien		(void)strncpy(utmp.ut_name, name, sizeof(utmp.ut_name));
1209108746Sobrien		(void)strncpy(utmp.ut_line, line, sizeof(utmp.ut_line));
1210108746Sobrien		(void)strncpy(utmp.ut_host, host, sizeof(utmp.ut_host));
1211133939Sobrien		ftpd_login(&utmp);
1212108746Sobrien	}
1213108746Sobrien	if (dowtmp)
1214133939Sobrien		ftpd_logwtmp(line, name, host);
1215108746Sobrien#endif
1216108746Sobrien}
1217108746Sobrien
1218108746Sobrienstatic void
1219108746Sobrienlogout_utmp(void)
1220108746Sobrien{
1221108746Sobrien#ifdef SUPPORT_UTMPX
1222161770Sobrien	int okwtmpx = dowtmp;
1223108746Sobrien#endif
1224108746Sobrien#ifdef SUPPORT_UTMP
1225161770Sobrien	int okwtmp = dowtmp;
1226108746Sobrien#endif
1227161770Sobrien	if (logged_in) {
1228108746Sobrien#ifdef SUPPORT_UTMPX
1229161770Sobrien		if (doutmp)
1230161770Sobrien			okwtmpx &= ftpd_logoutx(ttyline, 0, DEAD_PROCESS);
1231161770Sobrien		if (okwtmpx)
1232161770Sobrien			ftpd_logwtmpx(ttyline, "", "", NULL, 0, DEAD_PROCESS);
1233108746Sobrien#endif
1234108746Sobrien#ifdef SUPPORT_UTMP
1235161770Sobrien		if (doutmp)
1236161770Sobrien			okwtmp &= ftpd_logout(ttyline);
1237161770Sobrien		if (okwtmp)
1238133939Sobrien			ftpd_logwtmp(ttyline, "", "");
1239108746Sobrien#endif
1240108746Sobrien	}
1241108746Sobrien}
1242108746Sobrien
124379968Sobrien/*
124479968Sobrien * Terminate login as previous user (if any), resetting state;
124579968Sobrien * used when USER command is given or login fails.
124679968Sobrien */
124779968Sobrienstatic void
124879968Sobrienend_login(void)
124979968Sobrien{
1250108886Sobrien#ifdef USE_PAM
1251108886Sobrien	int e;
1252108886Sobrien#endif
1253108746Sobrien	logout_utmp();
125479968Sobrien	show_chdir_messages(-1);		/* flush chdir cache */
125579968Sobrien	if (pw != NULL && pw->pw_passwd != NULL)
125679968Sobrien		memset(pw->pw_passwd, 0, strlen(pw->pw_passwd));
125779968Sobrien	pw = NULL;
125879968Sobrien	logged_in = 0;
125992282Sobrien	askpasswd = 0;
126092282Sobrien	permitted = 0;
126179968Sobrien	quietmessages = 0;
126279968Sobrien	gidcount = 0;
126379968Sobrien	curclass.type = CLASS_REAL;
126479968Sobrien	(void) seteuid((uid_t)0);
1265161770Sobrien#ifdef	LOGIN_CAP
1266223702Strasz	setusercontext(NULL, getpwuid(0), 0, LOGIN_SETALL & ~(LOGIN_SETLOGIN |
1267223702Strasz		       LOGIN_SETUSER | LOGIN_SETGROUP | LOGIN_SETPATH |
1268223702Strasz		       LOGIN_SETENV));
1269133939Sobrien#endif
1270108886Sobrien#ifdef USE_PAM
1271161770Sobrien	if (pamh) {
1272161770Sobrien		if ((e = pam_setcred(pamh, PAM_DELETE_CRED)) != PAM_SUCCESS)
1273161770Sobrien			syslog(LOG_ERR, "pam_setcred: %s",
1274161770Sobrien			    pam_strerror(pamh, e));
1275161770Sobrien		if ((e = pam_close_session(pamh,0)) != PAM_SUCCESS)
1276161770Sobrien			syslog(LOG_ERR, "pam_close_session: %s",
1277161770Sobrien			    pam_strerror(pamh, e));
1278161770Sobrien		if ((e = pam_end(pamh, e)) != PAM_SUCCESS)
1279161770Sobrien			syslog(LOG_ERR, "pam_end: %s", pam_strerror(pamh, e));
1280161770Sobrien		pamh = NULL;
1281161770Sobrien	}
1282108886Sobrien#endif
128379968Sobrien}
128479968Sobrien
128579968Sobrienvoid
128679968Sobrienpass(const char *passwd)
128779968Sobrien{
1288161770Sobrien	int		 rval;
1289161770Sobrien	char		 root[MAXPATHLEN];
1290161770Sobrien#ifdef	LOGIN_CAP
1291161770Sobrien	login_cap_t *lc = NULL;
1292133939Sobrien#endif
1293108886Sobrien#ifdef USE_PAM
1294161770Sobrien	int e;
1295108886Sobrien#endif
129679968Sobrien	if (logged_in || askpasswd == 0) {
129779968Sobrien		reply(503, "Login with USER first.");
129879968Sobrien		return;
129979968Sobrien	}
130079968Sobrien	askpasswd = 0;
130179968Sobrien	if (curclass.type != CLASS_GUEST) {
130279968Sobrien			/* "ftp" is the only account allowed with no password */
130379968Sobrien		if (pw == NULL) {
130479968Sobrien			rval = 1;	/* failure below */
130579968Sobrien			goto skip;
130679968Sobrien		}
1307161770Sobrien#ifdef USE_PAM
1308161770Sobrien		rval = auth_pam(&pw, passwd);
1309161770Sobrien#ifdef notdef
1310161770Sobrien		/* If PAM fails, we proceed with other authentications */
1311161770Sobrien		if (rval >= 0) {
1312161770Sobrien#ifdef USE_OPIE
1313161770Sobrien			opieunlock();
1314161770Sobrien#endif
1315161770Sobrien			goto skip;
1316161770Sobrien		}
1317161770Sobrien#else
1318161770Sobrien		/* If PAM fails, that's it */
1319161770Sobrien		goto skip;
1320161770Sobrien#endif
1321161770Sobrien#endif
132279968Sobrien#if defined(KERBEROS)
132379968Sobrien		if (klogin(pw, "", hostname, (char *)passwd) == 0) {
132479968Sobrien			rval = 0;
132579968Sobrien			goto skip;
132679968Sobrien		}
132779968Sobrien#endif
132879968Sobrien#if defined(KERBEROS5)
132979968Sobrien		if (k5login(pw, "", hostname, (char *)passwd) == 0) {
133079968Sobrien			rval = 0;
133179968Sobrien			goto skip;
133279968Sobrien		}
133379968Sobrien#endif
133479968Sobrien#ifdef SKEY
133579968Sobrien		if (skey_haskey(pw->pw_name) == 0) {
133679968Sobrien			char *p;
133779968Sobrien			int r;
133879968Sobrien
1339161770Sobrien			p = ftpd_strdup(passwd);
134079968Sobrien			r = skey_passcheck(pw->pw_name, p);
134179968Sobrien			free(p);
134279968Sobrien			if (r != -1) {
134379968Sobrien				rval = 0;
134479968Sobrien				goto skip;
134579968Sobrien			}
134679968Sobrien		}
134779968Sobrien#endif
1348108886Sobrien#ifdef USE_OPIE
1349108886Sobrien		if (opieverify(&opiedata, (char *)passwd) == 0) {
1350108886Sobrien			/* OPIE says ok, check expire time */
1351108886Sobrien			if (pw->pw_expire && time(NULL) >= pw->pw_expire)
1352108886Sobrien				rval = 2;
1353108886Sobrien			else
1354108886Sobrien				rval = 0;
1355108886Sobrien			goto skip;
1356108886Sobrien		}
1357108886Sobrien#endif
135879968Sobrien		if (!sflag)
135979968Sobrien			rval = checkpassword(pw, passwd);
136079968Sobrien		else
136179968Sobrien			rval = 1;
136279968Sobrien
136379968Sobrien skip:
136479968Sobrien
136579968Sobrien			/*
136679968Sobrien			 * If rval > 0, the user failed the authentication check
136779968Sobrien			 * above.  If rval == 0, either Kerberos or local
136879968Sobrien			 * authentication succeeded.
136979968Sobrien			 */
137079968Sobrien		if (rval) {
137179968Sobrien			reply(530, "%s", rval == 2 ? "Password expired." :
137279968Sobrien			    "Login incorrect.");
137379968Sobrien			if (logging) {
137479968Sobrien				syslog(LOG_NOTICE,
137579968Sobrien				    "FTP LOGIN FAILED FROM %s", remotehost);
137679968Sobrien				syslog(LOG_AUTHPRIV | LOG_NOTICE,
137779968Sobrien				    "FTP LOGIN FAILED FROM %s, %s",
137879968Sobrien				    remotehost, curname);
137979968Sobrien			}
138079968Sobrien			pw = NULL;
138179968Sobrien			if (login_attempts++ >= 5) {
138279968Sobrien				syslog(LOG_NOTICE,
138379968Sobrien				    "repeated login failures from %s",
138479968Sobrien				    remotehost);
138579968Sobrien				exit(0);
138679968Sobrien			}
138779968Sobrien			return;
138879968Sobrien		}
138979968Sobrien	}
139079968Sobrien
139192282Sobrien			/* password ok; check if anything else prevents login */
139292282Sobrien	if (! permitted) {
139379968Sobrien		reply(530, "User %s may not use FTP.", pw->pw_name);
139479968Sobrien		if (logging)
139579968Sobrien			syslog(LOG_NOTICE, "FTP LOGIN REFUSED FROM %s, %s",
139679968Sobrien			    remotehost, pw->pw_name);
139779968Sobrien		goto bad;
139879968Sobrien	}
139979968Sobrien
140079968Sobrien	login_attempts = 0;		/* this time successful */
140179968Sobrien	if (setegid((gid_t)pw->pw_gid) < 0) {
140279968Sobrien		reply(550, "Can't set gid.");
140379968Sobrien		goto bad;
140479968Sobrien	}
1405161770Sobrien#ifdef	LOGIN_CAP
1406133939Sobrien	if ((lc = login_getpwclass(pw)) != NULL) {
1407161770Sobrien#ifdef notyet
1408161770Sobrien		char	remote_ip[NI_MAXHOST];
1409133939Sobrien
1410161770Sobrien		if (getnameinfo((struct sockaddr *)&his_addr, his_addr.su_len,
1411161770Sobrien			remote_ip, sizeof(remote_ip) - 1, NULL, 0,
1412161770Sobrien			NI_NUMERICHOST))
1413161770Sobrien				*remote_ip = 0;
1414133939Sobrien		remote_ip[sizeof(remote_ip) - 1] = 0;
1415133939Sobrien		if (!auth_hostok(lc, remotehost, remote_ip)) {
1416133939Sobrien			syslog(LOG_INFO|LOG_AUTH,
1417133939Sobrien			    "FTP LOGIN FAILED (HOST) as %s: permission denied.",
1418133939Sobrien			    pw->pw_name);
1419161770Sobrien			reply(530, "Permission denied.");
1420133939Sobrien			pw = NULL;
1421133939Sobrien			return;
1422133939Sobrien		}
1423133939Sobrien		if (!auth_timeok(lc, time(NULL))) {
1424161770Sobrien			reply(530, "Login not available right now.");
1425133939Sobrien			pw = NULL;
1426133939Sobrien			return;
1427133939Sobrien		}
1428161770Sobrien#endif
1429133939Sobrien	}
1430161770Sobrien	setsid();
1431223702Strasz	setusercontext(lc, pw, 0, LOGIN_SETALL &
1432223702Strasz		       ~(LOGIN_SETUSER | LOGIN_SETPATH | LOGIN_SETENV));
1433161770Sobrien#else
143479968Sobrien	(void) initgroups(pw->pw_name, pw->pw_gid);
143579968Sobrien			/* cache groups for cmds.c::matchgroup() */
1436161770Sobrien#endif
1437108886Sobrien#ifdef USE_PAM
1438133939Sobrien	if (pamh) {
1439108886Sobrien		if ((e = pam_open_session(pamh, 0)) != PAM_SUCCESS) {
1440108886Sobrien			syslog(LOG_ERR, "pam_open_session: %s",
1441108886Sobrien			    pam_strerror(pamh, e));
1442161770Sobrien		} else if ((e = pam_setcred(pamh, PAM_ESTABLISH_CRED))
1443161770Sobrien		    != PAM_SUCCESS) {
1444108886Sobrien			syslog(LOG_ERR, "pam_setcred: %s",
1445108886Sobrien			    pam_strerror(pamh, e));
1446108886Sobrien		}
1447108886Sobrien	}
1448108886Sobrien#endif
1449161770Sobrien	gidcount = getgroups(0, NULL);
1450161770Sobrien	if (gidlist)
1451161770Sobrien		free(gidlist);
1452161770Sobrien	gidlist = malloc(gidcount * sizeof *gidlist);
1453161770Sobrien	gidcount = getgroups(gidcount, gidlist);
1454108886Sobrien
1455108746Sobrien	/* open utmp/wtmp before chroot */
1456161770Sobrien	login_utmp(ttyline, pw->pw_name, remotehost, &his_addr);
145779968Sobrien
145879968Sobrien	logged_in = 1;
145979968Sobrien
146079968Sobrien	connections = 1;
146179968Sobrien	if (dopidfile)
146279968Sobrien		count_users();
146379968Sobrien	if (curclass.limit != -1 && connections > curclass.limit) {
146479968Sobrien		if (! EMPTYSTR(curclass.limitfile))
146579968Sobrien			(void)display_file(conffilename(curclass.limitfile),
146679968Sobrien			    530);
146779968Sobrien		reply(530,
1468108746Sobrien		    "User %s access denied, connection limit of " LLF
1469108746Sobrien		    " reached.",
1470108746Sobrien		    pw->pw_name, (LLT)curclass.limit);
147179968Sobrien		syslog(LOG_NOTICE,
1472108746Sobrien		    "Maximum connection limit of " LLF
1473108746Sobrien		    " for class %s reached, login refused for %s",
1474108746Sobrien		    (LLT)curclass.limit, curclass.classname, pw->pw_name);
147579968Sobrien		goto bad;
147679968Sobrien	}
147779968Sobrien
147879968Sobrien	homedir[0] = '/';
147979968Sobrien	switch (curclass.type) {
148079968Sobrien	case CLASS_GUEST:
148179968Sobrien			/*
148279968Sobrien			 * We MUST do a chdir() after the chroot. Otherwise
148379968Sobrien			 * the old current directory will be accessible as "."
148479968Sobrien			 * outside the new root!
148579968Sobrien			 */
148679968Sobrien		format_path(root,
148779968Sobrien		    curclass.chroot ? curclass.chroot :
148879968Sobrien		    anondir ? anondir :
148979968Sobrien		    pw->pw_dir);
149079968Sobrien		format_path(homedir,
149179968Sobrien		    curclass.homedir ? curclass.homedir :
149279968Sobrien		    "/");
149379968Sobrien		if (EMPTYSTR(homedir))
149479968Sobrien			homedir[0] = '/';
149579968Sobrien		if (EMPTYSTR(root) || chroot(root) < 0) {
149679968Sobrien			syslog(LOG_NOTICE,
149779968Sobrien			    "GUEST user %s: can't chroot to %s: %m",
149879968Sobrien			    pw->pw_name, root);
149979968Sobrien			goto bad_guest;
150079968Sobrien		}
150179968Sobrien		if (chdir(homedir) < 0) {
150279968Sobrien			syslog(LOG_NOTICE,
150379968Sobrien			    "GUEST user %s: can't chdir to %s: %m",
150479968Sobrien			    pw->pw_name, homedir);
150579968Sobrien bad_guest:
150679968Sobrien			reply(550, "Can't set guest privileges.");
150779968Sobrien			goto bad;
150879968Sobrien		}
150979968Sobrien		break;
151079968Sobrien	case CLASS_CHROOT:
151179968Sobrien		format_path(root,
151279968Sobrien		    curclass.chroot ? curclass.chroot :
151379968Sobrien		    pw->pw_dir);
151479968Sobrien		format_path(homedir,
151579968Sobrien		    curclass.homedir ? curclass.homedir :
151679968Sobrien		    "/");
151779968Sobrien		if (EMPTYSTR(homedir))
151879968Sobrien			homedir[0] = '/';
151979968Sobrien		if (EMPTYSTR(root) || chroot(root) < 0) {
152079968Sobrien			syslog(LOG_NOTICE,
152179968Sobrien			    "CHROOT user %s: can't chroot to %s: %m",
152279968Sobrien			    pw->pw_name, root);
152379968Sobrien			goto bad_chroot;
152479968Sobrien		}
152579968Sobrien		if (chdir(homedir) < 0) {
152679968Sobrien			syslog(LOG_NOTICE,
152779968Sobrien			    "CHROOT user %s: can't chdir to %s: %m",
152879968Sobrien			    pw->pw_name, homedir);
152979968Sobrien bad_chroot:
153079968Sobrien			reply(550, "Can't change root.");
153179968Sobrien			goto bad;
153279968Sobrien		}
153379968Sobrien		break;
153479968Sobrien	case CLASS_REAL:
1535161770Sobrien			/* only chroot REAL if explicitly requested */
153679968Sobrien		if (! EMPTYSTR(curclass.chroot)) {
153779968Sobrien			format_path(root, curclass.chroot);
153879968Sobrien			if (EMPTYSTR(root) || chroot(root) < 0) {
153979968Sobrien				syslog(LOG_NOTICE,
154079968Sobrien				    "REAL user %s: can't chroot to %s: %m",
154179968Sobrien				    pw->pw_name, root);
154279968Sobrien				goto bad_chroot;
154379968Sobrien			}
154479968Sobrien		}
154579968Sobrien		format_path(homedir,
154679968Sobrien		    curclass.homedir ? curclass.homedir :
154779968Sobrien		    pw->pw_dir);
154879968Sobrien		if (EMPTYSTR(homedir) || chdir(homedir) < 0) {
154979968Sobrien			if (chdir("/") < 0) {
155079968Sobrien				syslog(LOG_NOTICE,
155179968Sobrien				    "REAL user %s: can't chdir to %s: %m",
155279968Sobrien				    pw->pw_name,
155379968Sobrien				    !EMPTYSTR(homedir) ?  homedir : "/");
155479968Sobrien				reply(530,
155579968Sobrien				    "User %s: can't change directory to %s.",
155679968Sobrien				    pw->pw_name,
155779968Sobrien				    !EMPTYSTR(homedir) ? homedir : "/");
155879968Sobrien				goto bad;
155979968Sobrien			} else {
156079968Sobrien				reply(-230,
156179968Sobrien				    "No directory! Logging in with home=/");
156279968Sobrien				homedir[0] = '/';
156379968Sobrien			}
156479968Sobrien		}
156579968Sobrien		break;
156679968Sobrien	}
1567161770Sobrien#ifndef LOGIN_CAP
1568133939Sobrien	setsid();
156979968Sobrien	setlogin(pw->pw_name);
1570161770Sobrien#endif
157179968Sobrien	if (dropprivs ||
157279968Sobrien	    (curclass.type != CLASS_REAL &&
157379968Sobrien	    ntohs(ctrl_addr.su_port) > IPPORT_RESERVED + 1)) {
157479968Sobrien		dropprivs++;
157579968Sobrien		if (setgid((gid_t)pw->pw_gid) < 0) {
157679968Sobrien			reply(550, "Can't set gid.");
157779968Sobrien			goto bad;
157879968Sobrien		}
157979968Sobrien		if (setuid((uid_t)pw->pw_uid) < 0) {
158079968Sobrien			reply(550, "Can't set uid.");
158179968Sobrien			goto bad;
158279968Sobrien		}
158379968Sobrien	} else {
158479968Sobrien		if (seteuid((uid_t)pw->pw_uid) < 0) {
158579968Sobrien			reply(550, "Can't set uid.");
158679968Sobrien			goto bad;
158779968Sobrien		}
158879968Sobrien	}
1589108746Sobrien	setenv("HOME", homedir, 1);
159079968Sobrien
159179968Sobrien	if (curclass.type == CLASS_GUEST && passwd[0] == '-')
159279968Sobrien		quietmessages = 1;
159379968Sobrien
159479968Sobrien			/*
159579968Sobrien			 * Display a login message, if it exists.
159679968Sobrien			 * N.B. reply(230,) must follow the message.
159779968Sobrien			 */
159892282Sobrien	if (! EMPTYSTR(curclass.motd))
159992282Sobrien		(void)display_file(conffilename(curclass.motd), 230);
160079968Sobrien	show_chdir_messages(230);
160179968Sobrien	if (curclass.type == CLASS_GUEST) {
160279968Sobrien		char *p;
160379968Sobrien
160479968Sobrien		reply(230, "Guest login ok, access restrictions apply.");
160579968Sobrien#if HAVE_SETPROCTITLE
160679968Sobrien		snprintf(proctitle, sizeof(proctitle),
160792282Sobrien		    "%s: anonymous/%s", remotehost, passwd);
160879968Sobrien		setproctitle("%s", proctitle);
160979968Sobrien#endif /* HAVE_SETPROCTITLE */
161079968Sobrien		if (logging)
161179968Sobrien			syslog(LOG_INFO,
161279968Sobrien			"ANONYMOUS FTP LOGIN FROM %s, %s (class: %s, type: %s)",
161379968Sobrien			    remotehost, passwd,
161479968Sobrien			    curclass.classname, CURCLASSTYPE);
161579968Sobrien			/* store guest password reply into pw_passwd */
1616161770Sobrien		REASSIGN(pw->pw_passwd, ftpd_strdup(passwd));
161779968Sobrien		for (p = pw->pw_passwd; *p; p++)
1618161770Sobrien			if (!isgraph((unsigned char)*p))
161979968Sobrien				*p = '_';
162079968Sobrien	} else {
162179968Sobrien		reply(230, "User %s logged in.", pw->pw_name);
162279968Sobrien#if HAVE_SETPROCTITLE
162379968Sobrien		snprintf(proctitle, sizeof(proctitle),
162479968Sobrien		    "%s: %s", remotehost, pw->pw_name);
162579968Sobrien		setproctitle("%s", proctitle);
162679968Sobrien#endif /* HAVE_SETPROCTITLE */
162779968Sobrien		if (logging)
162879968Sobrien			syslog(LOG_INFO,
162979968Sobrien			    "FTP LOGIN FROM %s as %s (class: %s, type: %s)",
163079968Sobrien			    remotehost, pw->pw_name,
163179968Sobrien			    curclass.classname, CURCLASSTYPE);
163279968Sobrien	}
1633161770Sobrien	(void) umask(curclass.umask);
1634161770Sobrien#ifdef	LOGIN_CAP
1635133939Sobrien	login_close(lc);
1636133939Sobrien#endif
163792282Sobrien	return;
163879968Sobrien
163979968Sobrien bad:
1640161770Sobrien#ifdef	LOGIN_CAP
1641133939Sobrien	login_close(lc);
1642133939Sobrien#endif
1643161770Sobrien			/* Forget all about it... */
164479968Sobrien	end_login();
164579968Sobrien}
164679968Sobrien
164779968Sobrienvoid
164879968Sobrienretrieve(char *argv[], const char *name)
164979968Sobrien{
165079968Sobrien	FILE *fin, *dout;
165179968Sobrien	struct stat st;
165279968Sobrien	int (*closefunc)(FILE *) = NULL;
1653108746Sobrien	int dolog, sendrv, closerv, stderrfd, isconversion, isdata, isls;
165479968Sobrien	struct timeval start, finish, td, *tdp;
1655108746Sobrien	struct rusage rusage_before, rusage_after;
165679968Sobrien	const char *dispname;
165792282Sobrien	char *error;
165879968Sobrien
165979968Sobrien	sendrv = closerv = stderrfd = -1;
1660108746Sobrien	isconversion = isdata = isls = dolog = 0;
166179968Sobrien	tdp = NULL;
166279968Sobrien	dispname = name;
166379968Sobrien	fin = dout = NULL;
166492282Sobrien	error = NULL;
166579968Sobrien	if (argv == NULL) {		/* if not running a command ... */
1666108746Sobrien		dolog = 1;
166779968Sobrien		isdata = 1;
166879968Sobrien		fin = fopen(name, "r");
166979968Sobrien		closefunc = fclose;
167079968Sobrien		if (fin == NULL)	/* doesn't exist?; try a conversion */
167179968Sobrien			argv = do_conversion(name);
167279968Sobrien		if (argv != NULL) {
167379968Sobrien			isconversion++;
167479968Sobrien			syslog(LOG_DEBUG, "get command: '%s' on '%s'",
167579968Sobrien			    argv[0], name);
167679968Sobrien		}
167779968Sobrien	}
167879968Sobrien	if (argv != NULL) {
167979968Sobrien		char temp[MAXPATHLEN];
168079968Sobrien
168179968Sobrien		if (strcmp(argv[0], INTERNAL_LS) == 0) {
168279968Sobrien			isls = 1;
168379968Sobrien			stderrfd = -1;
168479968Sobrien		} else {
168579968Sobrien			(void)snprintf(temp, sizeof(temp), "%s", TMPFILE);
168679968Sobrien			stderrfd = mkstemp(temp);
168779968Sobrien			if (stderrfd != -1)
168879968Sobrien				(void)unlink(temp);
168979968Sobrien		}
169079968Sobrien		dispname = argv[0];
169179968Sobrien		fin = ftpd_popen(argv, "r", stderrfd);
169279968Sobrien		closefunc = ftpd_pclose;
169379968Sobrien		st.st_size = -1;
169479968Sobrien		st.st_blksize = BUFSIZ;
169579968Sobrien	}
169679968Sobrien	if (fin == NULL) {
169779968Sobrien		if (errno != 0) {
169879968Sobrien			perror_reply(550, dispname);
1699108746Sobrien			if (dolog)
170079968Sobrien				logxfer("get", -1, name, NULL, NULL,
170179968Sobrien				    strerror(errno));
170279968Sobrien		}
170379968Sobrien		goto cleanupretrieve;
170479968Sobrien	}
170579968Sobrien	byte_count = -1;
170679968Sobrien	if (argv == NULL
170779968Sobrien	    && (fstat(fileno(fin), &st) < 0 || !S_ISREG(st.st_mode))) {
170892282Sobrien		error = "Not a plain file";
170992282Sobrien		reply(550, "%s: %s.", dispname, error);
171079968Sobrien		goto done;
171179968Sobrien	}
171279968Sobrien	if (restart_point) {
171379968Sobrien		if (type == TYPE_A) {
171479968Sobrien			off_t i;
171579968Sobrien			int c;
171679968Sobrien
171779968Sobrien			for (i = 0; i < restart_point; i++) {
171879968Sobrien				if ((c=getc(fin)) == EOF) {
171992282Sobrien					error = strerror(errno);
172079968Sobrien					perror_reply(550, dispname);
172179968Sobrien					goto done;
172279968Sobrien				}
172379968Sobrien				if (c == '\n')
172479968Sobrien					i++;
172579968Sobrien			}
172679968Sobrien		} else if (lseek(fileno(fin), restart_point, SEEK_SET) < 0) {
172792282Sobrien			error = strerror(errno);
172879968Sobrien			perror_reply(550, dispname);
172979968Sobrien			goto done;
173079968Sobrien		}
173179968Sobrien	}
173279968Sobrien	dout = dataconn(dispname, st.st_size, "w");
173379968Sobrien	if (dout == NULL)
173479968Sobrien		goto done;
173579968Sobrien
1736108746Sobrien	(void)getrusage(RUSAGE_SELF, &rusage_before);
173779968Sobrien	(void)gettimeofday(&start, NULL);
1738108746Sobrien	sendrv = send_data(fin, dout, &st, isdata);
173979968Sobrien	(void)gettimeofday(&finish, NULL);
1740108746Sobrien	(void)getrusage(RUSAGE_SELF, &rusage_after);
174192282Sobrien	closedataconn(dout);		/* close now to affect timing stats */
174279968Sobrien	timersub(&finish, &start, &td);
174379968Sobrien	tdp = &td;
174479968Sobrien done:
1745108746Sobrien	if (dolog) {
174692282Sobrien		logxfer("get", byte_count, name, NULL, tdp, error);
1747108746Sobrien		if (tdp != NULL)
1748108746Sobrien			logrusage(&rusage_before, &rusage_after);
1749108746Sobrien	}
175079968Sobrien	closerv = (*closefunc)(fin);
175179968Sobrien	if (sendrv == 0) {
175292282Sobrien		FILE *errf;
175379968Sobrien		struct stat sb;
175479968Sobrien
175579968Sobrien		if (!isls && argv != NULL && closerv != 0) {
175679968Sobrien			reply(-226,
175779968Sobrien			    "Command returned an exit status of %d",
175879968Sobrien			    closerv);
175979968Sobrien			if (isconversion)
176079968Sobrien				syslog(LOG_WARNING,
176179968Sobrien				    "retrieve command: '%s' returned %d",
176279968Sobrien				    argv[0], closerv);
176379968Sobrien		}
176479968Sobrien		if (!isls && argv != NULL && stderrfd != -1 &&
176579968Sobrien		    (fstat(stderrfd, &sb) == 0) && sb.st_size > 0 &&
176692282Sobrien		    ((errf = fdopen(stderrfd, "r")) != NULL)) {
176779968Sobrien			char *cp, line[LINE_MAX];
176879968Sobrien
176979968Sobrien			reply(-226, "Command error messages:");
177092282Sobrien			rewind(errf);
177192282Sobrien			while (fgets(line, sizeof(line), errf) != NULL) {
177279968Sobrien				if ((cp = strchr(line, '\n')) != NULL)
177379968Sobrien					*cp = '\0';
177479968Sobrien				reply(0, "  %s", line);
177579968Sobrien			}
177679968Sobrien			(void) fflush(stdout);
177792282Sobrien			(void) fclose(errf);
177879968Sobrien				/* a reply(226,) must follow */
177979968Sobrien		}
178079968Sobrien		reply(226, "Transfer complete.");
178179968Sobrien	}
178279968Sobrien cleanupretrieve:
178379968Sobrien	if (stderrfd != -1)
178479968Sobrien		(void)close(stderrfd);
178579968Sobrien	if (isconversion)
178679968Sobrien		free(argv);
178779968Sobrien}
178879968Sobrien
178979968Sobrienvoid
179092282Sobrienstore(const char *name, const char *fmode, int unique)
179179968Sobrien{
179279968Sobrien	FILE *fout, *din;
179379968Sobrien	struct stat st;
179479968Sobrien	int (*closefunc)(FILE *);
179579968Sobrien	struct timeval start, finish, td, *tdp;
179692282Sobrien	char *desc, *error;
179779968Sobrien
179879968Sobrien	din = NULL;
179992282Sobrien	desc = (*fmode == 'w') ? "put" : "append";
180092282Sobrien	error = NULL;
180179968Sobrien	if (unique && stat(name, &st) == 0 &&
180279968Sobrien	    (name = gunique(name)) == NULL) {
180379968Sobrien		logxfer(desc, -1, name, NULL, NULL,
180479968Sobrien		    "cannot create unique file");
180579968Sobrien		goto cleanupstore;
180679968Sobrien	}
180779968Sobrien
180879968Sobrien	if (restart_point)
180992282Sobrien		fmode = "r+";
181092282Sobrien	fout = fopen(name, fmode);
181179968Sobrien	closefunc = fclose;
181279968Sobrien	tdp = NULL;
181379968Sobrien	if (fout == NULL) {
181479968Sobrien		perror_reply(553, name);
181579968Sobrien		logxfer(desc, -1, name, NULL, NULL, strerror(errno));
181679968Sobrien		goto cleanupstore;
181779968Sobrien	}
181879968Sobrien	byte_count = -1;
181979968Sobrien	if (restart_point) {
182079968Sobrien		if (type == TYPE_A) {
182179968Sobrien			off_t i;
182279968Sobrien			int c;
182379968Sobrien
182479968Sobrien			for (i = 0; i < restart_point; i++) {
182579968Sobrien				if ((c=getc(fout)) == EOF) {
182692282Sobrien					error = strerror(errno);
182779968Sobrien					perror_reply(550, name);
182879968Sobrien					goto done;
182979968Sobrien				}
183079968Sobrien				if (c == '\n')
183179968Sobrien					i++;
183279968Sobrien			}
183379968Sobrien			/*
183479968Sobrien			 * We must do this seek to "current" position
183579968Sobrien			 * because we are changing from reading to
183679968Sobrien			 * writing.
183779968Sobrien			 */
183879968Sobrien			if (fseek(fout, 0L, SEEK_CUR) < 0) {
183992282Sobrien				error = strerror(errno);
184079968Sobrien				perror_reply(550, name);
184179968Sobrien				goto done;
184279968Sobrien			}
184379968Sobrien		} else if (lseek(fileno(fout), restart_point, SEEK_SET) < 0) {
184492282Sobrien			error = strerror(errno);
184579968Sobrien			perror_reply(550, name);
184679968Sobrien			goto done;
184779968Sobrien		}
184879968Sobrien	}
184979968Sobrien	din = dataconn(name, (off_t)-1, "r");
185079968Sobrien	if (din == NULL)
185179968Sobrien		goto done;
185279968Sobrien	(void)gettimeofday(&start, NULL);
185379968Sobrien	if (receive_data(din, fout) == 0) {
185479968Sobrien		if (unique)
185579968Sobrien			reply(226, "Transfer complete (unique file name:%s).",
185679968Sobrien			    name);
185779968Sobrien		else
185879968Sobrien			reply(226, "Transfer complete.");
185979968Sobrien	}
186079968Sobrien	(void)gettimeofday(&finish, NULL);
186192282Sobrien	closedataconn(din);		/* close now to affect timing stats */
186279968Sobrien	timersub(&finish, &start, &td);
186379968Sobrien	tdp = &td;
186479968Sobrien done:
186592282Sobrien	logxfer(desc, byte_count, name, NULL, tdp, error);
186679968Sobrien	(*closefunc)(fout);
186779968Sobrien cleanupstore:
186892282Sobrien	;
186979968Sobrien}
187079968Sobrien
187179968Sobrienstatic FILE *
187292282Sobriengetdatasock(const char *fmode)
187379968Sobrien{
187479968Sobrien	int		on, s, t, tries;
187579968Sobrien	in_port_t	port;
187679968Sobrien
187779968Sobrien	on = 1;
187879968Sobrien	if (data >= 0)
187992282Sobrien		return (fdopen(data, fmode));
188079968Sobrien	if (! dropprivs)
188179968Sobrien		(void) seteuid((uid_t)0);
188279968Sobrien	s = socket(ctrl_addr.su_family, SOCK_STREAM, 0);
188379968Sobrien	if (s < 0)
188479968Sobrien		goto bad;
188579968Sobrien	if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR,
188679968Sobrien	    (char *) &on, sizeof(on)) < 0)
188779968Sobrien		goto bad;
188879968Sobrien	if (setsockopt(s, SOL_SOCKET, SO_KEEPALIVE,
188979968Sobrien	    (char *) &on, sizeof(on)) < 0)
189079968Sobrien		goto bad;
189179968Sobrien			/* anchor socket to avoid multi-homing problems */
189279968Sobrien	data_source = ctrl_addr;
189379968Sobrien			/*
189479968Sobrien			 * By default source port for PORT connctions is
189579968Sobrien			 * ctrlport-1 (see RFC959 section 5.2).
189679968Sobrien			 * However, if privs have been dropped and that
189779968Sobrien			 * would be < IPPORT_RESERVED, use a random port
189879968Sobrien			 * instead.
189979968Sobrien			 */
190079968Sobrien	if (dataport)
190179968Sobrien		port = dataport;
190279968Sobrien	else
190379968Sobrien		port = ntohs(ctrl_addr.su_port) - 1;
190479968Sobrien	if (dropprivs && port < IPPORT_RESERVED)
190579968Sobrien		port = 0;		/* use random port */
190679968Sobrien	data_source.su_port = htons(port);
190779968Sobrien
190879968Sobrien	for (tries = 1; ; tries++) {
190979968Sobrien		if (bind(s, (struct sockaddr *)&data_source.si_su,
191079968Sobrien		    data_source.su_len) >= 0)
191179968Sobrien			break;
191279968Sobrien		if (errno != EADDRINUSE || tries > 10)
191379968Sobrien			goto bad;
191479968Sobrien		sleep(tries);
191579968Sobrien	}
191679968Sobrien	if (! dropprivs)
191779968Sobrien		(void) seteuid((uid_t)pw->pw_uid);
191879968Sobrien#ifdef IP_TOS
191979968Sobrien	if (!mapped && ctrl_addr.su_family == AF_INET) {
192079968Sobrien		on = IPTOS_THROUGHPUT;
192179968Sobrien		if (setsockopt(s, IPPROTO_IP, IP_TOS, (char *)&on,
192279968Sobrien			       sizeof(int)) < 0)
192379968Sobrien			syslog(LOG_WARNING, "setsockopt (IP_TOS): %m");
192479968Sobrien	}
192579968Sobrien#endif
192692282Sobrien	return (fdopen(s, fmode));
192779968Sobrien bad:
192879968Sobrien		/* Return the real value of errno (close may change it) */
192979968Sobrien	t = errno;
193079968Sobrien	if (! dropprivs)
193179968Sobrien		(void) seteuid((uid_t)pw->pw_uid);
193279968Sobrien	(void) close(s);
193379968Sobrien	errno = t;
193479968Sobrien	return (NULL);
193579968Sobrien}
193679968Sobrien
193779968SobrienFILE *
193892282Sobriendataconn(const char *name, off_t size, const char *fmode)
193979968Sobrien{
194079968Sobrien	char sizebuf[32];
194179968Sobrien	FILE *file;
1942110245Sobrien	int retry, tos, keepalive, conerrno;
194379968Sobrien
194479968Sobrien	file_size = size;
194579968Sobrien	byte_count = 0;
194679968Sobrien	if (size != (off_t) -1)
194779968Sobrien		(void)snprintf(sizebuf, sizeof(sizebuf), " (" LLF " byte%s)",
194879968Sobrien		    (LLT)size, PLURAL(size));
194979968Sobrien	else
195079968Sobrien		sizebuf[0] = '\0';
195179968Sobrien	if (pdata >= 0) {
195279968Sobrien		struct sockinet from;
1953161770Sobrien		int s;
1954161770Sobrien		socklen_t fromlen = sizeof(from.su_len);
195579968Sobrien
195679968Sobrien		(void) alarm(curclass.timeout);
195779968Sobrien		s = accept(pdata, (struct sockaddr *)&from.si_su, &fromlen);
195879968Sobrien		(void) alarm(0);
195979968Sobrien		if (s < 0) {
196079968Sobrien			reply(425, "Can't open data connection.");
196179968Sobrien			(void) close(pdata);
196279968Sobrien			pdata = -1;
196379968Sobrien			return (NULL);
196479968Sobrien		}
196579968Sobrien		(void) close(pdata);
196679968Sobrien		pdata = s;
196779968Sobrien		switch (from.su_family) {
196879968Sobrien		case AF_INET:
196979968Sobrien#ifdef IP_TOS
197079968Sobrien			if (!mapped) {
197179968Sobrien				tos = IPTOS_THROUGHPUT;
197279968Sobrien				(void) setsockopt(s, IPPROTO_IP, IP_TOS,
197379968Sobrien				    (char *)&tos, sizeof(int));
197479968Sobrien			}
197579968Sobrien			break;
197679968Sobrien#endif
197779968Sobrien		}
197879968Sobrien		/* Set keepalives on the socket to detect dropped conns. */
197979968Sobrien#ifdef SO_KEEPALIVE
198079968Sobrien		keepalive = 1;
198179968Sobrien		(void) setsockopt(s, SOL_SOCKET, SO_KEEPALIVE,
198279968Sobrien		    (char *)&keepalive, sizeof(int));
198379968Sobrien#endif
198479968Sobrien		reply(150, "Opening %s mode data connection for '%s'%s.",
198579968Sobrien		     type == TYPE_A ? "ASCII" : "BINARY", name, sizebuf);
198692282Sobrien		return (fdopen(pdata, fmode));
198779968Sobrien	}
198879968Sobrien	if (data >= 0) {
198979968Sobrien		reply(125, "Using existing data connection for '%s'%s.",
199079968Sobrien		    name, sizebuf);
199179968Sobrien		usedefault = 1;
199292282Sobrien		return (fdopen(data, fmode));
199379968Sobrien	}
199479968Sobrien	if (usedefault)
199579968Sobrien		data_dest = his_addr;
199679968Sobrien	usedefault = 1;
1997110245Sobrien	retry = conerrno = 0;
1998110245Sobrien	do {
1999110245Sobrien		file = getdatasock(fmode);
2000110245Sobrien		if (file == NULL) {
2001110245Sobrien			char hbuf[NI_MAXHOST];
2002110245Sobrien			char pbuf[NI_MAXSERV];
200379968Sobrien
2004110245Sobrien			if (getnameinfo((struct sockaddr *)&data_source.si_su,
2005110245Sobrien			    data_source.su_len, hbuf, sizeof(hbuf), pbuf,
2006110245Sobrien			    sizeof(pbuf), NI_NUMERICHOST | NI_NUMERICSERV))
2007110245Sobrien				strlcpy(hbuf, "?", sizeof(hbuf));
2008110245Sobrien			reply(425, "Can't create data socket (%s,%s): %s.",
2009110245Sobrien			      hbuf, pbuf, strerror(errno));
2010110245Sobrien			return (NULL);
2011110245Sobrien		}
2012110245Sobrien		data = fileno(file);
2013110245Sobrien		conerrno = 0;
2014110245Sobrien		if (connect(data, (struct sockaddr *)&data_dest.si_su,
2015110245Sobrien		    data_dest.su_len) == 0)
2016110245Sobrien			break;
2017110245Sobrien		conerrno = errno;
2018110245Sobrien		(void) fclose(file);
2019161770Sobrien		file = NULL;
2020110245Sobrien		data = -1;
2021110245Sobrien		if (conerrno == EADDRINUSE) {
202279968Sobrien			sleep((unsigned) swaitint);
202379968Sobrien			retry += swaitint;
2024110245Sobrien		} else {
2025110245Sobrien			break;
202679968Sobrien		}
2027110245Sobrien	} while (retry <= swaitmax);
2028110245Sobrien	if (conerrno != 0) {
202979968Sobrien		perror_reply(425, "Can't build data connection");
203079968Sobrien		return (NULL);
203179968Sobrien	}
203279968Sobrien	reply(150, "Opening %s mode data connection for '%s'%s.",
203379968Sobrien	     type == TYPE_A ? "ASCII" : "BINARY", name, sizebuf);
203479968Sobrien	return (file);
203579968Sobrien}
203679968Sobrien
203779968Sobrienvoid
203879968Sobrienclosedataconn(FILE *fd)
203979968Sobrien{
204079968Sobrien
204192282Sobrien	if (fd == NULL)
204292282Sobrien		return;
204392282Sobrien	(void)fclose(fd);
204479968Sobrien	data = -1;
204579968Sobrien	if (pdata >= 0)
204679968Sobrien		(void)close(pdata);
204779968Sobrien	pdata = -1;
204879968Sobrien}
204979968Sobrien
2050108746Sobrienint
2051108746Sobrienwrite_data(int fd, char *buf, size_t size, off_t *bufrem,
2052108746Sobrien    struct timeval *then, int isdata)
2053108746Sobrien{
2054108746Sobrien	struct timeval now, td;
2055108746Sobrien	ssize_t c;
2056108746Sobrien
2057108746Sobrien	while (size > 0) {
2058108746Sobrien		c = size;
2059108746Sobrien		if (curclass.writesize) {
2060108746Sobrien			if (curclass.writesize < c)
2061108746Sobrien				c = curclass.writesize;
2062108746Sobrien		}
2063108746Sobrien		if (curclass.rateget) {
2064108746Sobrien			if (*bufrem < c)
2065108746Sobrien				c = *bufrem;
2066108746Sobrien		}
2067108746Sobrien		(void) alarm(curclass.timeout);
2068108746Sobrien		c = write(fd, buf, c);
2069108746Sobrien		if (c <= 0)
2070108746Sobrien			return (1);
2071108746Sobrien		buf += c;
2072108746Sobrien		size -= c;
2073108746Sobrien		byte_count += c;
2074108746Sobrien		if (isdata) {
2075108746Sobrien			total_data_out += c;
2076108746Sobrien			total_data += c;
2077108746Sobrien		}
2078108746Sobrien		total_bytes_out += c;
2079108746Sobrien		total_bytes += c;
2080108746Sobrien		if (curclass.rateget) {
2081108746Sobrien			*bufrem -= c;
2082108746Sobrien			if (*bufrem == 0) {
2083108746Sobrien				(void)gettimeofday(&now, NULL);
2084108746Sobrien				timersub(&now, then, &td);
2085108746Sobrien				if (td.tv_sec == 0) {
2086108746Sobrien					usleep(1000000 - td.tv_usec);
2087108746Sobrien					(void)gettimeofday(then, NULL);
2088108746Sobrien				} else
2089108746Sobrien					*then = now;
2090108746Sobrien				*bufrem = curclass.rateget;
2091108746Sobrien			}
2092108746Sobrien		}
2093108746Sobrien	}
2094108746Sobrien	return (0);
2095108746Sobrien}
2096108746Sobrien
2097108746Sobrienstatic enum send_status
2098108746Sobriensend_data_with_read(int filefd, int netfd, const struct stat *st, int isdata)
2099108746Sobrien{
2100108746Sobrien	struct timeval then;
2101108746Sobrien	off_t bufrem;
2102108746Sobrien	size_t readsize;
2103108746Sobrien	char *buf;
2104108746Sobrien	int c, error;
2105108746Sobrien
2106108746Sobrien	if (curclass.readsize)
2107108746Sobrien		readsize = curclass.readsize;
2108108746Sobrien	else
2109108746Sobrien		readsize = (size_t)st->st_blksize;
2110108746Sobrien	if ((buf = malloc(readsize)) == NULL) {
2111108746Sobrien		perror_reply(451, "Local resource failure: malloc");
2112108746Sobrien		return (SS_NO_TRANSFER);
2113108746Sobrien	}
2114108746Sobrien
2115108746Sobrien	if (curclass.rateget) {
2116108746Sobrien		bufrem = curclass.rateget;
2117108746Sobrien		(void)gettimeofday(&then, NULL);
2118108746Sobrien	}
2119108746Sobrien	while (1) {
2120108746Sobrien		(void) alarm(curclass.timeout);
2121108746Sobrien		c = read(filefd, buf, readsize);
2122108746Sobrien		if (c == 0)
2123108746Sobrien			error = SS_SUCCESS;
2124108746Sobrien		else if (c < 0)
2125108746Sobrien			error = SS_FILE_ERROR;
2126108746Sobrien		else if (write_data(netfd, buf, c, &bufrem, &then, isdata))
2127108746Sobrien			error = SS_DATA_ERROR;
2128133939Sobrien		else if (urgflag && handleoobcmd())
2129133939Sobrien			error = SS_ABORTED;
2130108746Sobrien		else
2131108746Sobrien			continue;
2132108746Sobrien
2133108746Sobrien		free(buf);
2134108746Sobrien		return (error);
2135108746Sobrien	}
2136108746Sobrien}
2137108746Sobrien
2138108746Sobrienstatic enum send_status
2139108746Sobriensend_data_with_mmap(int filefd, int netfd, const struct stat *st, int isdata)
2140108746Sobrien{
2141108746Sobrien	struct timeval then;
2142108746Sobrien	off_t bufrem, filesize, off, origoff;
2143108746Sobrien	size_t mapsize, winsize;
2144108746Sobrien	int error, sendbufsize, sendlowat;
2145108746Sobrien	void *win;
2146108746Sobrien
2147108746Sobrien	if (curclass.sendbufsize) {
2148108746Sobrien		sendbufsize = curclass.sendbufsize;
2149108746Sobrien		if (setsockopt(netfd, SOL_SOCKET, SO_SNDBUF,
2150108746Sobrien		    &sendbufsize, sizeof(int)) == -1)
2151108746Sobrien			syslog(LOG_WARNING, "setsockopt(SO_SNDBUF, %d): %m",
2152108746Sobrien			    sendbufsize);
2153108746Sobrien	}
2154108746Sobrien
2155108746Sobrien	if (curclass.sendlowat) {
2156108746Sobrien		sendlowat = curclass.sendlowat;
2157108746Sobrien		if (setsockopt(netfd, SOL_SOCKET, SO_SNDLOWAT,
2158108746Sobrien		    &sendlowat, sizeof(int)) == -1)
2159108746Sobrien			syslog(LOG_WARNING, "setsockopt(SO_SNDLOWAT, %d): %m",
2160108746Sobrien			    sendlowat);
2161108746Sobrien	}
2162108746Sobrien
2163108746Sobrien	winsize = curclass.mmapsize;
2164108746Sobrien	filesize = st->st_size;
2165161770Sobrien	if (ftpd_debug)
2166108746Sobrien		syslog(LOG_INFO, "mmapsize = %ld, writesize = %ld",
2167108746Sobrien		    (long)winsize, (long)curclass.writesize);
2168108746Sobrien	if (winsize == 0)
2169108746Sobrien		goto try_read;
2170108746Sobrien
2171108746Sobrien	off = lseek(filefd, (off_t)0, SEEK_CUR);
2172108746Sobrien	if (off == -1)
2173108746Sobrien		goto try_read;
2174108746Sobrien
2175108746Sobrien	origoff = off;
2176108746Sobrien	if (curclass.rateget) {
2177108746Sobrien		bufrem = curclass.rateget;
2178108746Sobrien		(void)gettimeofday(&then, NULL);
2179108746Sobrien	}
2180108746Sobrien	while (1) {
2181108746Sobrien		mapsize = MIN(filesize - off, winsize);
2182108746Sobrien		if (mapsize == 0)
2183108746Sobrien			break;
2184108746Sobrien		win = mmap(NULL, mapsize, PROT_READ,
2185108746Sobrien		    MAP_FILE|MAP_SHARED, filefd, off);
2186108746Sobrien		if (win == MAP_FAILED) {
2187108746Sobrien			if (off == origoff)
2188108746Sobrien				goto try_read;
2189108746Sobrien			return (SS_FILE_ERROR);
2190108746Sobrien		}
2191108746Sobrien		(void) madvise(win, mapsize, MADV_SEQUENTIAL);
2192108746Sobrien		error = write_data(netfd, win, mapsize, &bufrem, &then,
2193108746Sobrien		    isdata);
2194108746Sobrien		(void) madvise(win, mapsize, MADV_DONTNEED);
2195108746Sobrien		munmap(win, mapsize);
2196133939Sobrien		if (urgflag && handleoobcmd())
2197133939Sobrien			return (SS_ABORTED);
2198108746Sobrien		if (error)
2199108746Sobrien			return (SS_DATA_ERROR);
2200108746Sobrien		off += mapsize;
2201108746Sobrien	}
2202108746Sobrien	return (SS_SUCCESS);
2203108746Sobrien
2204108746Sobrien try_read:
2205108746Sobrien	return (send_data_with_read(filefd, netfd, st, isdata));
2206108746Sobrien}
2207108746Sobrien
220879968Sobrien/*
220979968Sobrien * Tranfer the contents of "instr" to "outstr" peer using the appropriate
2210108746Sobrien * encapsulation of the data subject to Mode, Structure, and Type.
221179968Sobrien *
221279968Sobrien * NB: Form isn't handled.
221379968Sobrien */
221479968Sobrienstatic int
2215108746Sobriensend_data(FILE *instr, FILE *outstr, const struct stat *st, int isdata)
221679968Sobrien{
221779968Sobrien	int	 c, filefd, netfd, rval;
221879968Sobrien
2219133939Sobrien	urgflag = 0;
222079968Sobrien	transflag = 1;
222179968Sobrien	rval = -1;
222279968Sobrien
222379968Sobrien	switch (type) {
222479968Sobrien
222579968Sobrien	case TYPE_A:
222679968Sobrien /* XXXLUKEM: rate limit ascii send (get) */
222779968Sobrien		(void) alarm(curclass.timeout);
222879968Sobrien		while ((c = getc(instr)) != EOF) {
2229133939Sobrien			if (urgflag && handleoobcmd())
2230133939Sobrien				goto cleanup_send_data;
223179968Sobrien			byte_count++;
223279968Sobrien			if (c == '\n') {
223379968Sobrien				if (ferror(outstr))
223479968Sobrien					goto data_err;
223579968Sobrien				(void) putc('\r', outstr);
223679968Sobrien				if (isdata) {
223779968Sobrien					total_data_out++;
223879968Sobrien					total_data++;
223979968Sobrien				}
224079968Sobrien				total_bytes_out++;
224179968Sobrien				total_bytes++;
224279968Sobrien			}
224379968Sobrien			(void) putc(c, outstr);
224479968Sobrien			if (isdata) {
224579968Sobrien				total_data_out++;
224679968Sobrien				total_data++;
224779968Sobrien			}
224879968Sobrien			total_bytes_out++;
224979968Sobrien			total_bytes++;
225079968Sobrien			if ((byte_count % 4096) == 0)
225179968Sobrien				(void) alarm(curclass.timeout);
225279968Sobrien		}
225379968Sobrien		(void) alarm(0);
225479968Sobrien		fflush(outstr);
225579968Sobrien		if (ferror(instr))
225679968Sobrien			goto file_err;
225779968Sobrien		if (ferror(outstr))
225879968Sobrien			goto data_err;
225979968Sobrien		rval = 0;
226079968Sobrien		goto cleanup_send_data;
226179968Sobrien
226279968Sobrien	case TYPE_I:
226379968Sobrien	case TYPE_L:
226479968Sobrien		filefd = fileno(instr);
226579968Sobrien		netfd = fileno(outstr);
2266108746Sobrien		switch (send_data_with_mmap(filefd, netfd, st, isdata)) {
226779968Sobrien
2268108746Sobrien		case SS_SUCCESS:
2269108746Sobrien			break;
2270108746Sobrien
2271133939Sobrien		case SS_ABORTED:
2272108746Sobrien		case SS_NO_TRANSFER:
2273108746Sobrien			goto cleanup_send_data;
2274108746Sobrien
2275108746Sobrien		case SS_FILE_ERROR:
2276108746Sobrien			goto file_err;
2277108746Sobrien
2278108746Sobrien		case SS_DATA_ERROR:
2279108746Sobrien			goto data_err;
228079968Sobrien		}
228179968Sobrien		rval = 0;
228279968Sobrien		goto cleanup_send_data;
228379968Sobrien
228479968Sobrien	default:
228579968Sobrien		reply(550, "Unimplemented TYPE %d in send_data", type);
228679968Sobrien		goto cleanup_send_data;
228779968Sobrien	}
228879968Sobrien
228979968Sobrien data_err:
229079968Sobrien	(void) alarm(0);
229179968Sobrien	perror_reply(426, "Data connection");
229279968Sobrien	goto cleanup_send_data;
229379968Sobrien
229479968Sobrien file_err:
229579968Sobrien	(void) alarm(0);
229679968Sobrien	perror_reply(551, "Error on input file");
2297133939Sobrien	goto cleanup_send_data;
229879968Sobrien
229979968Sobrien cleanup_send_data:
230079968Sobrien	(void) alarm(0);
230179968Sobrien	transflag = 0;
2302133939Sobrien	urgflag = 0;
230379968Sobrien	if (isdata) {
230479968Sobrien		total_files_out++;
230579968Sobrien		total_files++;
230679968Sobrien	}
230779968Sobrien	total_xfers_out++;
230879968Sobrien	total_xfers++;
230979968Sobrien	return (rval);
231079968Sobrien}
231179968Sobrien
231279968Sobrien/*
231379968Sobrien * Transfer data from peer to "outstr" using the appropriate encapulation of
231479968Sobrien * the data subject to Mode, Structure, and Type.
231579968Sobrien *
231679968Sobrien * N.B.: Form isn't handled.
231779968Sobrien */
231879968Sobrienstatic int
231979968Sobrienreceive_data(FILE *instr, FILE *outstr)
232079968Sobrien{
232179968Sobrien	int	c, bare_lfs, netfd, filefd, rval;
232279968Sobrien	off_t	byteswritten;
2323161770Sobrien	char	*buf;
2324161770Sobrien	size_t	readsize;
2325133939Sobrien	struct sigaction sa, sa_saved;
2326161770Sobrien	struct stat st;
232779968Sobrien#ifdef __GNUC__
232879968Sobrien	(void) &bare_lfs;
232979968Sobrien#endif
233079968Sobrien
2331133939Sobrien	memset(&sa, 0, sizeof(sa));
2332133939Sobrien	sigfillset(&sa.sa_mask);
2333133939Sobrien	sa.sa_flags = SA_RESTART;
2334133939Sobrien	sa.sa_handler = lostconn;
2335133939Sobrien	(void) sigaction(SIGALRM, &sa, &sa_saved);
2336133939Sobrien
233779968Sobrien	bare_lfs = 0;
2338133939Sobrien	urgflag = 0;
233979968Sobrien	transflag = 1;
234079968Sobrien	rval = -1;
234179968Sobrien	byteswritten = 0;
2342161770Sobrien	buf = NULL;
234379968Sobrien
234479968Sobrien#define FILESIZECHECK(x) \
234579968Sobrien			do { \
234679968Sobrien				if (curclass.maxfilesize != -1 && \
234779968Sobrien				    (x) > curclass.maxfilesize) { \
234879968Sobrien					errno = EFBIG; \
234979968Sobrien					goto file_err; \
235079968Sobrien				} \
235179968Sobrien			} while (0)
235279968Sobrien
235379968Sobrien	switch (type) {
235479968Sobrien
235579968Sobrien	case TYPE_I:
235679968Sobrien	case TYPE_L:
235779968Sobrien		netfd = fileno(instr);
235879968Sobrien		filefd = fileno(outstr);
235979968Sobrien		(void) alarm(curclass.timeout);
2360161770Sobrien		if (curclass.readsize)
2361161770Sobrien			readsize = curclass.readsize;
2362161770Sobrien		else if (fstat(filefd, &st))
2363161770Sobrien			readsize = (size_t)st.st_blksize;
2364161770Sobrien		else
2365161770Sobrien			readsize = BUFSIZ;
2366161770Sobrien		if ((buf = malloc(readsize)) == NULL) {
2367161770Sobrien			perror_reply(451, "Local resource failure: malloc");
2368161770Sobrien			goto cleanup_recv_data;
2369161770Sobrien		}
237079968Sobrien		if (curclass.rateput) {
237179968Sobrien			while (1) {
237279968Sobrien				int d;
237379968Sobrien				struct timeval then, now, td;
237479968Sobrien				off_t bufrem;
237579968Sobrien
237679968Sobrien				(void)gettimeofday(&then, NULL);
237779968Sobrien				errno = c = d = 0;
237879968Sobrien				for (bufrem = curclass.rateput; bufrem > 0; ) {
237979968Sobrien					if ((c = read(netfd, buf,
2380161770Sobrien					    MIN(readsize, bufrem))) <= 0)
238179968Sobrien						goto recvdone;
2382133939Sobrien					if (urgflag && handleoobcmd())
2383133939Sobrien						goto cleanup_recv_data;
238479968Sobrien					FILESIZECHECK(byte_count + c);
238579968Sobrien					if ((d = write(filefd, buf, c)) != c)
238679968Sobrien						goto file_err;
238779968Sobrien					(void) alarm(curclass.timeout);
238879968Sobrien					bufrem -= c;
238979968Sobrien					byte_count += c;
239079968Sobrien					total_data_in += c;
239179968Sobrien					total_data += c;
239279968Sobrien					total_bytes_in += c;
239379968Sobrien					total_bytes += c;
239479968Sobrien				}
239579968Sobrien				(void)gettimeofday(&now, NULL);
239679968Sobrien				timersub(&now, &then, &td);
239779968Sobrien				if (td.tv_sec == 0)
239879968Sobrien					usleep(1000000 - td.tv_usec);
239979968Sobrien			}
240079968Sobrien		} else {
2401161770Sobrien			while ((c = read(netfd, buf, readsize)) > 0) {
2402133939Sobrien				if (urgflag && handleoobcmd())
2403133939Sobrien					goto cleanup_recv_data;
240479968Sobrien				FILESIZECHECK(byte_count + c);
240579968Sobrien				if (write(filefd, buf, c) != c)
240679968Sobrien					goto file_err;
240779968Sobrien				(void) alarm(curclass.timeout);
240879968Sobrien				byte_count += c;
240979968Sobrien				total_data_in += c;
241079968Sobrien				total_data += c;
241179968Sobrien				total_bytes_in += c;
241279968Sobrien				total_bytes += c;
241379968Sobrien			}
241479968Sobrien		}
241579968Sobrien recvdone:
241679968Sobrien		if (c < 0)
241779968Sobrien			goto data_err;
241879968Sobrien		rval = 0;
241979968Sobrien		goto cleanup_recv_data;
242079968Sobrien
242179968Sobrien	case TYPE_E:
242279968Sobrien		reply(553, "TYPE E not implemented.");
242379968Sobrien		goto cleanup_recv_data;
242479968Sobrien
242579968Sobrien	case TYPE_A:
242679968Sobrien		(void) alarm(curclass.timeout);
242779968Sobrien /* XXXLUKEM: rate limit ascii receive (put) */
242879968Sobrien		while ((c = getc(instr)) != EOF) {
2429133939Sobrien			if (urgflag && handleoobcmd())
2430133939Sobrien				goto cleanup_recv_data;
243179968Sobrien			byte_count++;
243279968Sobrien			total_data_in++;
243379968Sobrien			total_data++;
243479968Sobrien			total_bytes_in++;
243579968Sobrien			total_bytes++;
243679968Sobrien			if ((byte_count % 4096) == 0)
243779968Sobrien				(void) alarm(curclass.timeout);
243879968Sobrien			if (c == '\n')
243979968Sobrien				bare_lfs++;
244079968Sobrien			while (c == '\r') {
244179968Sobrien				if (ferror(outstr))
244279968Sobrien					goto data_err;
244379968Sobrien				if ((c = getc(instr)) != '\n') {
244479968Sobrien					byte_count++;
244579968Sobrien					total_data_in++;
244679968Sobrien					total_data++;
244779968Sobrien					total_bytes_in++;
244879968Sobrien					total_bytes++;
244979968Sobrien					if ((byte_count % 4096) == 0)
245079968Sobrien						(void) alarm(curclass.timeout);
245179968Sobrien					byteswritten++;
245279968Sobrien					FILESIZECHECK(byteswritten);
245379968Sobrien					(void) putc ('\r', outstr);
245479968Sobrien					if (c == '\0' || c == EOF)
245579968Sobrien						goto contin2;
245679968Sobrien				}
245779968Sobrien			}
245879968Sobrien			byteswritten++;
245979968Sobrien			FILESIZECHECK(byteswritten);
246079968Sobrien			(void) putc(c, outstr);
246179968Sobrien contin2:	;
246279968Sobrien		}
246379968Sobrien		(void) alarm(0);
246479968Sobrien		fflush(outstr);
246579968Sobrien		if (ferror(instr))
246679968Sobrien			goto data_err;
246779968Sobrien		if (ferror(outstr))
246879968Sobrien			goto file_err;
246979968Sobrien		if (bare_lfs) {
247079968Sobrien			reply(-226,
247179968Sobrien			    "WARNING! %d bare linefeeds received in ASCII mode",
247279968Sobrien			    bare_lfs);
247379968Sobrien			reply(0, "File may not have transferred correctly.");
247479968Sobrien		}
247579968Sobrien		rval = 0;
247679968Sobrien		goto cleanup_recv_data;
247779968Sobrien
247879968Sobrien	default:
247979968Sobrien		reply(550, "Unimplemented TYPE %d in receive_data", type);
248079968Sobrien		goto cleanup_recv_data;
248179968Sobrien	}
248279968Sobrien#undef FILESIZECHECK
248379968Sobrien
248479968Sobrien data_err:
248579968Sobrien	(void) alarm(0);
248679968Sobrien	perror_reply(426, "Data Connection");
248779968Sobrien	goto cleanup_recv_data;
248879968Sobrien
248979968Sobrien file_err:
249079968Sobrien	(void) alarm(0);
249179968Sobrien	perror_reply(452, "Error writing file");
249279968Sobrien	goto cleanup_recv_data;
249379968Sobrien
249479968Sobrien cleanup_recv_data:
249579968Sobrien	(void) alarm(0);
2496133939Sobrien	(void) sigaction(SIGALRM, &sa_saved, NULL);
2497161770Sobrien	if (buf)
2498161770Sobrien		free(buf);
249979968Sobrien	transflag = 0;
2500133939Sobrien	urgflag = 0;
250179968Sobrien	total_files_in++;
250279968Sobrien	total_files++;
250379968Sobrien	total_xfers_in++;
250479968Sobrien	total_xfers++;
250579968Sobrien	return (rval);
250679968Sobrien}
250779968Sobrien
250879968Sobrienvoid
250979968Sobrienstatcmd(void)
251079968Sobrien{
251179968Sobrien	struct sockinet *su = NULL;
251279968Sobrien	static char hbuf[NI_MAXHOST], sbuf[NI_MAXSERV];
2513108746Sobrien	u_char *a, *p;
251479968Sobrien	int ispassive, af;
251579968Sobrien	off_t otbi, otbo, otb;
251679968Sobrien
251779968Sobrien	a = p = (u_char *)NULL;
251879968Sobrien
251979968Sobrien	reply(-211, "%s FTP server status:", hostname);
252079968Sobrien	reply(0, "Version: %s", EMPTYSTR(version) ? "<suppressed>" : version);
252179968Sobrien	hbuf[0] = '\0';
252279968Sobrien	if (!getnameinfo((struct sockaddr *)&his_addr.si_su, his_addr.su_len,
252379968Sobrien			hbuf, sizeof(hbuf), NULL, 0, NI_NUMERICHOST)
252479968Sobrien	    && strcmp(remotehost, hbuf) != 0)
252579968Sobrien		reply(0, "Connected to %s (%s)", remotehost, hbuf);
252679968Sobrien	else
252779968Sobrien		reply(0, "Connected to %s", remotehost);
252879968Sobrien
252979968Sobrien	if (logged_in) {
253079968Sobrien		if (curclass.type == CLASS_GUEST)
253179968Sobrien			reply(0, "Logged in anonymously");
253279968Sobrien		else
253379968Sobrien			reply(0, "Logged in as %s%s", pw->pw_name,
253479968Sobrien			    curclass.type == CLASS_CHROOT ? " (chroot)" : "");
253579968Sobrien	} else if (askpasswd)
253679968Sobrien		reply(0, "Waiting for password");
253779968Sobrien	else
253879968Sobrien		reply(0, "Waiting for user name");
253979968Sobrien	cprintf(stdout, "    TYPE: %s", typenames[type]);
254079968Sobrien	if (type == TYPE_A || type == TYPE_E)
254179968Sobrien		cprintf(stdout, ", FORM: %s", formnames[form]);
254279968Sobrien	if (type == TYPE_L) {
254379968Sobrien#if NBBY == 8
254479968Sobrien		cprintf(stdout, " %d", NBBY);
254579968Sobrien#else
254679968Sobrien			/* XXX: `bytesize' needs to be defined in this case */
254779968Sobrien		cprintf(stdout, " %d", bytesize);
254879968Sobrien#endif
254979968Sobrien	}
255079968Sobrien	cprintf(stdout, "; STRUcture: %s; transfer MODE: %s\r\n",
255179968Sobrien	    strunames[stru], modenames[mode]);
255279968Sobrien	ispassive = 0;
255379968Sobrien	if (data != -1) {
2554108746Sobrien		reply(0, "Data connection open");
255579968Sobrien		su = NULL;
255679968Sobrien	} else if (pdata != -1) {
255779968Sobrien		reply(0, "in Passive mode");
255879968Sobrien		if (curclass.advertise.su_len != 0)
255979968Sobrien			su = &curclass.advertise;
256079968Sobrien		else
256179968Sobrien			su = &pasv_addr;
256279968Sobrien		ispassive = 1;
256379968Sobrien		goto printaddr;
256479968Sobrien	} else if (usedefault == 0) {
2565161770Sobrien		su = (struct sockinet *)&data_dest;
2566161770Sobrien
256779968Sobrien		if (epsvall) {
256879968Sobrien			reply(0, "EPSV only mode (EPSV ALL)");
256979968Sobrien			goto epsvonly;
257079968Sobrien		}
257179968Sobrien printaddr:
257279968Sobrien							/* PASV/PORT */
257379968Sobrien		if (su->su_family == AF_INET) {
257479968Sobrien			a = (u_char *) &su->su_addr;
257579968Sobrien			p = (u_char *) &su->su_port;
257679968Sobrien#define UC(b) (((int) b) & 0xff)
257779968Sobrien			reply(0, "%s (%d,%d,%d,%d,%d,%d)",
257879968Sobrien				ispassive ? "PASV" : "PORT" ,
257979968Sobrien				UC(a[0]), UC(a[1]), UC(a[2]), UC(a[3]),
258079968Sobrien				UC(p[0]), UC(p[1]));
258179968Sobrien		}
258279968Sobrien
258379968Sobrien							/* LPSV/LPRT */
258479968Sobrien	    {
258592282Sobrien		int alen, i;
258679968Sobrien
258779968Sobrien		alen = 0;
258879968Sobrien		switch (su->su_family) {
258979968Sobrien		case AF_INET:
259079968Sobrien			a = (u_char *) &su->su_addr;
259179968Sobrien			p = (u_char *) &su->su_port;
259279968Sobrien			alen = sizeof(su->su_addr);
259379968Sobrien			af = 4;
259479968Sobrien			break;
259579968Sobrien#ifdef INET6
259679968Sobrien		case AF_INET6:
259779968Sobrien			a = (u_char *) &su->su_6addr;
259879968Sobrien			p = (u_char *) &su->su_port;
259979968Sobrien			alen = sizeof(su->su_6addr);
260079968Sobrien			af = 6;
260179968Sobrien			break;
260279968Sobrien#endif
260379968Sobrien		default:
260479968Sobrien			af = 0;
260579968Sobrien			break;
260679968Sobrien		}
260779968Sobrien		if (af) {
260879968Sobrien			cprintf(stdout, "    %s (%d,%d",
260979968Sobrien			    ispassive ? "LPSV" : "LPRT", af, alen);
261079968Sobrien			for (i = 0; i < alen; i++)
261179968Sobrien				cprintf(stdout, ",%d", UC(a[i]));
261279968Sobrien			cprintf(stdout, ",%d,%d,%d)\r\n",
261379968Sobrien			    2, UC(p[0]), UC(p[1]));
261479968Sobrien#undef UC
261579968Sobrien		}
261679968Sobrien	    }
261779968Sobrien
261879968Sobrien		/* EPRT/EPSV */
261979968Sobrien epsvonly:
262079968Sobrien		af = af2epsvproto(su->su_family);
262179968Sobrien		hbuf[0] = '\0';
262279968Sobrien		if (af > 0) {
262379968Sobrien			struct sockinet tmp;
262479968Sobrien
262579968Sobrien			tmp = *su;
262679968Sobrien#ifdef INET6
262779968Sobrien			if (tmp.su_family == AF_INET6)
262879968Sobrien				tmp.su_scope_id = 0;
262979968Sobrien#endif
263079968Sobrien			if (getnameinfo((struct sockaddr *)&tmp.si_su,
263179968Sobrien			    tmp.su_len, hbuf, sizeof(hbuf), sbuf, sizeof(sbuf),
263279968Sobrien			    NI_NUMERICHOST | NI_NUMERICSERV) == 0)
263379968Sobrien				reply(0, "%s (|%d|%s|%s|)",
263479968Sobrien				    ispassive ? "EPSV" : "EPRT",
263579968Sobrien				    af, hbuf, sbuf);
263679968Sobrien		}
263779968Sobrien	} else
263879968Sobrien		reply(0, "No data connection");
263979968Sobrien
264079968Sobrien	if (logged_in) {
264179968Sobrien		reply(0,
264279968Sobrien		    "Data sent:        " LLF " byte%s in " LLF " file%s",
264379968Sobrien		    (LLT)total_data_out, PLURAL(total_data_out),
264479968Sobrien		    (LLT)total_files_out, PLURAL(total_files_out));
264579968Sobrien		reply(0,
264679968Sobrien		    "Data received:    " LLF " byte%s in " LLF " file%s",
264779968Sobrien		    (LLT)total_data_in, PLURAL(total_data_in),
264879968Sobrien		    (LLT)total_files_in, PLURAL(total_files_in));
264979968Sobrien		reply(0,
265079968Sobrien		    "Total data:       " LLF " byte%s in " LLF " file%s",
265179968Sobrien		    (LLT)total_data, PLURAL(total_data),
265279968Sobrien		    (LLT)total_files, PLURAL(total_files));
265379968Sobrien	}
265479968Sobrien	otbi = total_bytes_in;
265579968Sobrien	otbo = total_bytes_out;
265679968Sobrien	otb = total_bytes;
265779968Sobrien	reply(0, "Traffic sent:     " LLF " byte%s in " LLF " transfer%s",
265879968Sobrien	    (LLT)otbo, PLURAL(otbo),
265979968Sobrien	    (LLT)total_xfers_out, PLURAL(total_xfers_out));
266079968Sobrien	reply(0, "Traffic received: " LLF " byte%s in " LLF " transfer%s",
266179968Sobrien	    (LLT)otbi, PLURAL(otbi),
266279968Sobrien	    (LLT)total_xfers_in, PLURAL(total_xfers_in));
266379968Sobrien	reply(0, "Total traffic:    " LLF " byte%s in " LLF " transfer%s",
266479968Sobrien	    (LLT)otb, PLURAL(otb),
266579968Sobrien	    (LLT)total_xfers, PLURAL(total_xfers));
266679968Sobrien
266792282Sobrien	if (logged_in && !CURCLASS_FLAGS_ISSET(private)) {
266879968Sobrien		struct ftpconv *cp;
266979968Sobrien
267079968Sobrien		reply(0, "%s", "");
267179968Sobrien		reply(0, "Class: %s, type: %s",
267279968Sobrien		    curclass.classname, CURCLASSTYPE);
267379968Sobrien		reply(0, "Check PORT/LPRT commands: %sabled",
267479968Sobrien		    CURCLASS_FLAGS_ISSET(checkportcmd) ? "en" : "dis");
267579968Sobrien		if (! EMPTYSTR(curclass.display))
267679968Sobrien			reply(0, "Display file: %s", curclass.display);
267779968Sobrien		if (! EMPTYSTR(curclass.notify))
267879968Sobrien			reply(0, "Notify fileglob: %s", curclass.notify);
2679108746Sobrien		reply(0, "Idle timeout: " LLF ", maximum timeout: " LLF,
2680108746Sobrien		    (LLT)curclass.timeout, (LLT)curclass.maxtimeout);
268179968Sobrien		reply(0, "Current connections: %d", connections);
268279968Sobrien		if (curclass.limit == -1)
268379968Sobrien			reply(0, "Maximum connections: unlimited");
268479968Sobrien		else
2685108746Sobrien			reply(0, "Maximum connections: " LLF,
2686108746Sobrien			    (LLT)curclass.limit);
268779968Sobrien		if (curclass.limitfile)
268879968Sobrien			reply(0, "Connection limit exceeded message file: %s",
268992282Sobrien			    conffilename(curclass.limitfile));
269079968Sobrien		if (! EMPTYSTR(curclass.chroot))
269179968Sobrien			reply(0, "Chroot format: %s", curclass.chroot);
269292282Sobrien		reply(0, "Deny bad ftpusers(5) quickly: %sabled",
269392282Sobrien		    CURCLASS_FLAGS_ISSET(denyquick) ? "en" : "dis");
269479968Sobrien		if (! EMPTYSTR(curclass.homedir))
269579968Sobrien			reply(0, "Homedir format: %s", curclass.homedir);
269679968Sobrien		if (curclass.maxfilesize == -1)
269779968Sobrien			reply(0, "Maximum file size: unlimited");
269879968Sobrien		else
269979968Sobrien			reply(0, "Maximum file size: " LLF,
270079968Sobrien			    (LLT)curclass.maxfilesize);
270179968Sobrien		if (! EMPTYSTR(curclass.motd))
270292282Sobrien			reply(0, "MotD file: %s", conffilename(curclass.motd));
270379968Sobrien		reply(0,
270479968Sobrien	    "Modify commands (CHMOD, DELE, MKD, RMD, RNFR, UMASK): %sabled",
270579968Sobrien		    CURCLASS_FLAGS_ISSET(modify) ? "en" : "dis");
270679968Sobrien		reply(0, "Upload commands (APPE, STOR, STOU): %sabled",
270779968Sobrien		    CURCLASS_FLAGS_ISSET(upload) ? "en" : "dis");
270879968Sobrien		reply(0, "Sanitize file names: %sabled",
270979968Sobrien		    CURCLASS_FLAGS_ISSET(sanenames) ? "en" : "dis");
271079968Sobrien		reply(0, "PASV/LPSV/EPSV connections: %sabled",
271179968Sobrien		    CURCLASS_FLAGS_ISSET(passive) ? "en" : "dis");
271279968Sobrien		if (curclass.advertise.su_len != 0) {
271379968Sobrien			char buf[50];	/* big enough for IPv6 address */
271479968Sobrien			const char *bp;
271579968Sobrien
271679968Sobrien			bp = inet_ntop(curclass.advertise.su_family,
271779968Sobrien			    (void *)&curclass.advertise.su_addr,
271879968Sobrien			    buf, sizeof(buf));
271979968Sobrien			if (bp != NULL)
272079968Sobrien				reply(0, "PASV advertise address: %s", bp);
272179968Sobrien		}
272279968Sobrien		if (curclass.portmin && curclass.portmax)
2723108746Sobrien			reply(0, "PASV port range: " LLF " - " LLF,
2724108746Sobrien			    (LLT)curclass.portmin, (LLT)curclass.portmax);
272579968Sobrien		if (curclass.rateget)
272679968Sobrien			reply(0, "Rate get limit: " LLF " bytes/sec",
272779968Sobrien			    (LLT)curclass.rateget);
272879968Sobrien		else
272979968Sobrien			reply(0, "Rate get limit: disabled");
273079968Sobrien		if (curclass.rateput)
273179968Sobrien			reply(0, "Rate put limit: " LLF " bytes/sec",
273279968Sobrien			    (LLT)curclass.rateput);
273379968Sobrien		else
273479968Sobrien			reply(0, "Rate put limit: disabled");
2735108746Sobrien		if (curclass.mmapsize)
2736108746Sobrien			reply(0, "Mmap size: " LLF, (LLT)curclass.mmapsize);
2737108746Sobrien		else
2738108746Sobrien			reply(0, "Mmap size: disabled");
2739108746Sobrien		if (curclass.readsize)
2740108746Sobrien			reply(0, "Read size: " LLF, (LLT)curclass.readsize);
2741108746Sobrien		else
2742108746Sobrien			reply(0, "Read size: default");
2743108746Sobrien		if (curclass.writesize)
2744108746Sobrien			reply(0, "Write size: " LLF, (LLT)curclass.writesize);
2745108746Sobrien		else
2746108746Sobrien			reply(0, "Write size: default");
2747161770Sobrien		if (curclass.recvbufsize)
2748161770Sobrien			reply(0, "Receive buffer size: " LLF,
2749161770Sobrien			    (LLT)curclass.recvbufsize);
2750161770Sobrien		else
2751161770Sobrien			reply(0, "Receive buffer size: default");
2752108746Sobrien		if (curclass.sendbufsize)
2753108746Sobrien			reply(0, "Send buffer size: " LLF,
2754108746Sobrien			    (LLT)curclass.sendbufsize);
2755108746Sobrien		else
2756108746Sobrien			reply(0, "Send buffer size: default");
2757108746Sobrien		if (curclass.sendlowat)
2758108746Sobrien			reply(0, "Send low water mark: " LLF,
2759108746Sobrien			    (LLT)curclass.sendlowat);
2760108746Sobrien		else
2761108746Sobrien			reply(0, "Send low water mark: default");
276279968Sobrien		reply(0, "Umask: %.04o", curclass.umask);
276379968Sobrien		for (cp = curclass.conversions; cp != NULL; cp=cp->next) {
276479968Sobrien			if (cp->suffix == NULL || cp->types == NULL ||
276579968Sobrien			    cp->command == NULL)
276679968Sobrien				continue;
276779968Sobrien			reply(0, "Conversion: %s [%s] disable: %s, command: %s",
276879968Sobrien			    cp->suffix, cp->types, cp->disable, cp->command);
276979968Sobrien		}
277079968Sobrien	}
277179968Sobrien
277279968Sobrien	reply(211, "End of status");
277379968Sobrien}
277479968Sobrien
277579968Sobrienvoid
277679968Sobrienfatal(const char *s)
277779968Sobrien{
277879968Sobrien
277979968Sobrien	reply(451, "Error in server: %s\n", s);
278079968Sobrien	reply(221, "Closing connection due to server error.");
278179968Sobrien	dologout(0);
278279968Sobrien	/* NOTREACHED */
278379968Sobrien}
278479968Sobrien
278579968Sobrien/*
278679968Sobrien * reply() --
278779968Sobrien *	depending on the value of n, display fmt with a trailing CRLF and
278879968Sobrien *	prefix of:
278979968Sobrien *	n < -1		prefix the message with abs(n) + "-"	(initial line)
279079968Sobrien *	n == 0		prefix the message with 4 spaces	(middle lines)
279179968Sobrien *	n >  0		prefix the message with n + " "		(final line)
279279968Sobrien */
279379968Sobrienvoid
279479968Sobrienreply(int n, const char *fmt, ...)
279579968Sobrien{
2796133939Sobrien	char	msg[MAXPATHLEN * 2 + 100];
2797133939Sobrien	size_t	b;
2798133939Sobrien	va_list	ap;
279979968Sobrien
280079968Sobrien	b = 0;
280179968Sobrien	if (n == 0)
2802133939Sobrien		b = snprintf(msg, sizeof(msg), "    ");
280379968Sobrien	else if (n < 0)
2804133939Sobrien		b = snprintf(msg, sizeof(msg), "%d-", -n);
280579968Sobrien	else
2806133939Sobrien		b = snprintf(msg, sizeof(msg), "%d ", n);
2807133939Sobrien	va_start(ap, fmt);
2808133939Sobrien	vsnprintf(msg + b, sizeof(msg) - b, fmt, ap);
280992282Sobrien	va_end(ap);
2810133939Sobrien	cprintf(stdout, "%s\r\n", msg);
281179968Sobrien	(void)fflush(stdout);
2812161770Sobrien	if (ftpd_debug)
2813133939Sobrien		syslog(LOG_DEBUG, "<--- %s", msg);
281479968Sobrien}
281579968Sobrien
281679968Sobrienstatic void
281779968Sobrienlogremotehost(struct sockinet *who)
281879968Sobrien{
281979968Sobrien
282079968Sobrien	if (getnameinfo((struct sockaddr *)&who->si_su,
282179968Sobrien	    who->su_len, remotehost, sizeof(remotehost), NULL, 0, 0))
282279968Sobrien		strlcpy(remotehost, "?", sizeof(remotehost));
282379968Sobrien
282479968Sobrien#if HAVE_SETPROCTITLE
282579968Sobrien	snprintf(proctitle, sizeof(proctitle), "%s: connected", remotehost);
282679968Sobrien	setproctitle("%s", proctitle);
282779968Sobrien#endif /* HAVE_SETPROCTITLE */
282879968Sobrien	if (logging)
282979968Sobrien		syslog(LOG_INFO, "connection from %s to %s",
283079968Sobrien		    remotehost, hostname);
283179968Sobrien}
283279968Sobrien
283379968Sobrien/*
283479968Sobrien * Record logout in wtmp file and exit with supplied status.
2835133939Sobrien * NOTE: because this is called from signal handlers it cannot
2836133939Sobrien *       use stdio (or call other functions that use stdio).
283779968Sobrien */
283879968Sobrienvoid
283979968Sobriendologout(int status)
284079968Sobrien{
284179968Sobrien	/*
284279968Sobrien	* Prevent reception of SIGURG from resulting in a resumption
284379968Sobrien	* back to the main program loop.
284479968Sobrien	*/
284579968Sobrien	transflag = 0;
2846108746Sobrien	logout_utmp();
284779968Sobrien	if (logged_in) {
284879968Sobrien#ifdef KERBEROS
284979968Sobrien		if (!notickets && krbtkfile_env)
285079968Sobrien			unlink(krbtkfile_env);
285179968Sobrien#endif
285279968Sobrien	}
285379968Sobrien	/* beware of flushing buffers after a SIGPIPE */
2854133939Sobrien	if (xferlogfd != -1)
2855133939Sobrien		close(xferlogfd);
285679968Sobrien	_exit(status);
285779968Sobrien}
285879968Sobrien
285979968Sobrienvoid
286079968Sobrienabor(void)
286179968Sobrien{
286279968Sobrien
2863133939Sobrien	if (!transflag)
2864133939Sobrien		return;
286579968Sobrien	tmpline[0] = '\0';
286679968Sobrien	is_oob = 0;
286779968Sobrien	reply(426, "Transfer aborted. Data connection closed.");
286879968Sobrien	reply(226, "Abort successful");
2869133939Sobrien	transflag = 0;		/* flag that the transfer has aborted */
287079968Sobrien}
287179968Sobrien
287279968Sobrienvoid
287379968Sobrienstatxfer(void)
287479968Sobrien{
287579968Sobrien
2876133939Sobrien	if (!transflag)
2877133939Sobrien		return;
287879968Sobrien	tmpline[0] = '\0';
287979968Sobrien	is_oob = 0;
288079968Sobrien	if (file_size != (off_t) -1)
288179968Sobrien		reply(213,
288279968Sobrien		    "Status: " LLF " of " LLF " byte%s transferred",
288379968Sobrien		    (LLT)byte_count, (LLT)file_size,
288479968Sobrien		    PLURAL(byte_count));
288579968Sobrien	else
288679968Sobrien		reply(213, "Status: " LLF " byte%s transferred",
288779968Sobrien		    (LLT)byte_count, PLURAL(byte_count));
288879968Sobrien}
288979968Sobrien
2890133939Sobrien/*
2891133939Sobrien * Call when urgflag != 0 to handle Out Of Band commands.
2892133939Sobrien * Returns non zero if the OOB command aborted the transfer
2893133939Sobrien * by setting transflag to 0. (c.f., "ABOR").
2894133939Sobrien */
2895133939Sobrienstatic int
2896133939Sobrienhandleoobcmd()
289779968Sobrien{
289879968Sobrien	char *cp;
2899186872Ssimon	int ret;
290079968Sobrien
2901133939Sobrien	if (!urgflag)
2902133939Sobrien		return (0);
2903133939Sobrien	urgflag = 0;
290479968Sobrien	/* only process if transfer occurring */
290579968Sobrien	if (!transflag)
2906133939Sobrien		return (0);
290779968Sobrien	cp = tmpline;
2908186872Ssimon	ret = getline(cp, sizeof(tmpline)-1, stdin);
2909186872Ssimon	if (ret == -1) {
291079968Sobrien		reply(221, "You could at least say goodbye.");
291179968Sobrien		dologout(0);
2912186872Ssimon	} else if (ret == -2) {
2913186872Ssimon		/* Ignore truncated command */
2914186872Ssimon		/* XXX: abort xfer with "500 command too long", & return 1 ? */
2915186872Ssimon		return 0;
291679968Sobrien	}
2917133939Sobrien		/*
2918133939Sobrien		 * Manually parse OOB commands, because we can't
2919133939Sobrien		 * recursively call the yacc parser...
2920133939Sobrien		 */
2921133939Sobrien	if (strcasecmp(cp, "ABOR\r\n") == 0) {
2922133939Sobrien		abor();
2923133939Sobrien	} else if (strcasecmp(cp, "STAT\r\n") == 0) {
2924133939Sobrien		statxfer();
2925133939Sobrien	} else {
2926133939Sobrien		/* XXX: error with "500 unknown command" ? */
2927133939Sobrien	}
2928133939Sobrien	return (transflag == 0);
292979968Sobrien}
293079968Sobrien
293179968Sobrienstatic int
293279968Sobrienbind_pasv_addr(void)
293379968Sobrien{
293479968Sobrien	static int passiveport;
293579968Sobrien	int port, len;
293679968Sobrien
293779968Sobrien	len = pasv_addr.su_len;
293879968Sobrien	if (curclass.portmin == 0 && curclass.portmax == 0) {
293979968Sobrien		pasv_addr.su_port = 0;
294079968Sobrien		return (bind(pdata, (struct sockaddr *)&pasv_addr.si_su, len));
294179968Sobrien	}
294279968Sobrien
294379968Sobrien	if (passiveport == 0) {
294479968Sobrien		srand(getpid());
294579968Sobrien		passiveport = rand() % (curclass.portmax - curclass.portmin)
294679968Sobrien		    + curclass.portmin;
294779968Sobrien	}
294879968Sobrien
294979968Sobrien	port = passiveport;
295079968Sobrien	while (1) {
295179968Sobrien		port++;
295279968Sobrien		if (port > curclass.portmax)
295379968Sobrien			port = curclass.portmin;
295479968Sobrien		else if (port == passiveport) {
295579968Sobrien			errno = EAGAIN;
295679968Sobrien			return (-1);
295779968Sobrien		}
295879968Sobrien		pasv_addr.su_port = htons(port);
295979968Sobrien		if (bind(pdata, (struct sockaddr *)&pasv_addr.si_su, len) == 0)
296079968Sobrien			break;
296179968Sobrien		if (errno != EADDRINUSE)
296279968Sobrien			return (-1);
296379968Sobrien	}
296479968Sobrien	passiveport = port;
296579968Sobrien	return (0);
296679968Sobrien}
296779968Sobrien
296879968Sobrien/*
296979968Sobrien * Note: a response of 425 is not mentioned as a possible response to
297079968Sobrien *	the PASV command in RFC959. However, it has been blessed as
297179968Sobrien *	a legitimate response by Jon Postel in a telephone conversation
297279968Sobrien *	with Rick Adams on 25 Jan 89.
297379968Sobrien */
297479968Sobrienvoid
297579968Sobrienpassive(void)
297679968Sobrien{
2977161770Sobrien	socklen_t len, recvbufsize;
297879968Sobrien	char *p, *a;
297979968Sobrien
298079968Sobrien	if (pdata >= 0)
298179968Sobrien		close(pdata);
298279968Sobrien	pdata = socket(AF_INET, SOCK_STREAM, 0);
298379968Sobrien	if (pdata < 0 || !logged_in) {
298479968Sobrien		perror_reply(425, "Can't open passive connection");
298579968Sobrien		return;
298679968Sobrien	}
298779968Sobrien	pasv_addr = ctrl_addr;
298879968Sobrien
298979968Sobrien	if (bind_pasv_addr() < 0)
299079968Sobrien		goto pasv_error;
299179968Sobrien	len = pasv_addr.su_len;
299279968Sobrien	if (getsockname(pdata, (struct sockaddr *) &pasv_addr.si_su, &len) < 0)
299379968Sobrien		goto pasv_error;
299479968Sobrien	pasv_addr.su_len = len;
2995161770Sobrien	if (curclass.recvbufsize) {
2996161770Sobrien		recvbufsize = curclass.recvbufsize;
2997161770Sobrien		if (setsockopt(pdata, SOL_SOCKET, SO_RCVBUF, &recvbufsize,
2998161770Sobrien			       sizeof(int)) == -1)
2999161770Sobrien			syslog(LOG_WARNING, "setsockopt(SO_RCVBUF, %d): %m",
3000161770Sobrien			       recvbufsize);
3001161770Sobrien	}
300279968Sobrien	if (listen(pdata, 1) < 0)
300379968Sobrien		goto pasv_error;
300479968Sobrien	if (curclass.advertise.su_len != 0)
300579968Sobrien		a = (char *) &curclass.advertise.su_addr;
300679968Sobrien	else
300779968Sobrien		a = (char *) &pasv_addr.su_addr;
300879968Sobrien	p = (char *) &pasv_addr.su_port;
300979968Sobrien
301079968Sobrien#define UC(b) (((int) b) & 0xff)
301179968Sobrien
301279968Sobrien	reply(227, "Entering Passive Mode (%d,%d,%d,%d,%d,%d)", UC(a[0]),
301379968Sobrien		UC(a[1]), UC(a[2]), UC(a[3]), UC(p[0]), UC(p[1]));
301479968Sobrien	return;
301579968Sobrien
301679968Sobrien pasv_error:
301779968Sobrien	(void) close(pdata);
301879968Sobrien	pdata = -1;
301979968Sobrien	perror_reply(425, "Can't open passive connection");
302079968Sobrien	return;
302179968Sobrien}
302279968Sobrien
302379968Sobrien/*
302479968Sobrien * convert protocol identifier to/from AF
302579968Sobrien */
302679968Sobrienint
302779968Sobrienlpsvproto2af(int proto)
302879968Sobrien{
302979968Sobrien
303079968Sobrien	switch (proto) {
303179968Sobrien	case 4:
303279968Sobrien		return AF_INET;
303379968Sobrien#ifdef INET6
303479968Sobrien	case 6:
303579968Sobrien		return AF_INET6;
303679968Sobrien#endif
303779968Sobrien	default:
303879968Sobrien		return -1;
303979968Sobrien	}
304079968Sobrien}
304179968Sobrien
304279968Sobrienint
304379968Sobrienaf2lpsvproto(int af)
304479968Sobrien{
304579968Sobrien
304679968Sobrien	switch (af) {
304779968Sobrien	case AF_INET:
304879968Sobrien		return 4;
304979968Sobrien#ifdef INET6
305079968Sobrien	case AF_INET6:
305179968Sobrien		return 6;
305279968Sobrien#endif
305379968Sobrien	default:
305479968Sobrien		return -1;
305579968Sobrien	}
305679968Sobrien}
305779968Sobrien
305879968Sobrienint
305979968Sobrienepsvproto2af(int proto)
306079968Sobrien{
306179968Sobrien
306279968Sobrien	switch (proto) {
306379968Sobrien	case 1:
306479968Sobrien		return AF_INET;
306579968Sobrien#ifdef INET6
306679968Sobrien	case 2:
306779968Sobrien		return AF_INET6;
306879968Sobrien#endif
306979968Sobrien	default:
307079968Sobrien		return -1;
307179968Sobrien	}
307279968Sobrien}
307379968Sobrien
307479968Sobrienint
307579968Sobrienaf2epsvproto(int af)
307679968Sobrien{
307779968Sobrien
307879968Sobrien	switch (af) {
307979968Sobrien	case AF_INET:
308079968Sobrien		return 1;
308179968Sobrien#ifdef INET6
308279968Sobrien	case AF_INET6:
308379968Sobrien		return 2;
308479968Sobrien#endif
308579968Sobrien	default:
308679968Sobrien		return -1;
308779968Sobrien	}
308879968Sobrien}
308979968Sobrien
309079968Sobrien/*
309179968Sobrien * 228 Entering Long Passive Mode (af, hal, h1, h2, h3,..., pal, p1, p2...)
309279968Sobrien * 229 Entering Extended Passive Mode (|||port|)
309379968Sobrien */
309479968Sobrienvoid
309579968Sobrienlong_passive(char *cmd, int pf)
309679968Sobrien{
3097161770Sobrien	socklen_t len;
309879968Sobrien	char *p, *a;
309979968Sobrien
310079968Sobrien	if (!logged_in) {
310179968Sobrien		syslog(LOG_NOTICE, "long passive but not logged in");
310279968Sobrien		reply(503, "Login with USER first.");
310379968Sobrien		return;
310479968Sobrien	}
310579968Sobrien
310679968Sobrien	if (pf != PF_UNSPEC && ctrl_addr.su_family != pf) {
310779968Sobrien		/*
310879968Sobrien		 * XXX: only EPRT/EPSV ready clients will understand this
310979968Sobrien		 */
311079968Sobrien		if (strcmp(cmd, "EPSV") != 0)
311179968Sobrien			reply(501, "Network protocol mismatch"); /*XXX*/
311279968Sobrien		else
311379968Sobrien			epsv_protounsupp("Network protocol mismatch");
311479968Sobrien
311579968Sobrien		return;
311679968Sobrien	}
311779968Sobrien
311879968Sobrien	if (pdata >= 0)
311979968Sobrien		close(pdata);
312079968Sobrien	pdata = socket(ctrl_addr.su_family, SOCK_STREAM, 0);
312179968Sobrien	if (pdata < 0) {
312279968Sobrien		perror_reply(425, "Can't open passive connection");
312379968Sobrien		return;
312479968Sobrien	}
312579968Sobrien	pasv_addr = ctrl_addr;
312679968Sobrien	if (bind_pasv_addr() < 0)
312779968Sobrien		goto pasv_error;
312879968Sobrien	len = pasv_addr.su_len;
312979968Sobrien	if (getsockname(pdata, (struct sockaddr *) &pasv_addr.si_su, &len) < 0)
313079968Sobrien		goto pasv_error;
313179968Sobrien	pasv_addr.su_len = len;
313279968Sobrien	if (listen(pdata, 1) < 0)
313379968Sobrien		goto pasv_error;
313479968Sobrien	p = (char *) &pasv_addr.su_port;
313579968Sobrien
313679968Sobrien#define UC(b) (((int) b) & 0xff)
313779968Sobrien
313879968Sobrien	if (strcmp(cmd, "LPSV") == 0) {
313979968Sobrien		struct sockinet *advert;
314079968Sobrien
314179968Sobrien		if (curclass.advertise.su_len != 0)
314279968Sobrien			advert = &curclass.advertise;
314379968Sobrien		else
314479968Sobrien			advert = &pasv_addr;
314579968Sobrien		switch (advert->su_family) {
314679968Sobrien		case AF_INET:
314779968Sobrien			a = (char *) &advert->su_addr;
314879968Sobrien			reply(228,
314979968Sobrien    "Entering Long Passive Mode (%d,%d,%d,%d,%d,%d,%d,%d,%d)",
315079968Sobrien				4, 4, UC(a[0]), UC(a[1]), UC(a[2]), UC(a[3]),
315179968Sobrien				2, UC(p[0]), UC(p[1]));
315279968Sobrien			return;
315379968Sobrien#ifdef INET6
315479968Sobrien		case AF_INET6:
315579968Sobrien			a = (char *) &advert->su_6addr;
315679968Sobrien			reply(228,
315779968Sobrien    "Entering Long Passive Mode (%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d)",
315879968Sobrien				6, 16,
315979968Sobrien				UC(a[0]), UC(a[1]), UC(a[2]), UC(a[3]),
316079968Sobrien				UC(a[4]), UC(a[5]), UC(a[6]), UC(a[7]),
316179968Sobrien				UC(a[8]), UC(a[9]), UC(a[10]), UC(a[11]),
316279968Sobrien				UC(a[12]), UC(a[13]), UC(a[14]), UC(a[15]),
316379968Sobrien				2, UC(p[0]), UC(p[1]));
316479968Sobrien			return;
316579968Sobrien#endif
316679968Sobrien		}
316779968Sobrien#undef UC
316879968Sobrien	} else if (strcmp(cmd, "EPSV") == 0) {
316979968Sobrien		switch (pasv_addr.su_family) {
317079968Sobrien		case AF_INET:
317179968Sobrien#ifdef INET6
317279968Sobrien		case AF_INET6:
317379968Sobrien#endif
317479968Sobrien			reply(229, "Entering Extended Passive Mode (|||%d|)",
317579968Sobrien			    ntohs(pasv_addr.su_port));
317679968Sobrien			return;
317779968Sobrien		}
317879968Sobrien	} else {
317979968Sobrien		/* more proper error code? */
318079968Sobrien	}
318179968Sobrien
318279968Sobrien pasv_error:
318379968Sobrien	(void) close(pdata);
318479968Sobrien	pdata = -1;
318579968Sobrien	perror_reply(425, "Can't open passive connection");
318679968Sobrien	return;
318779968Sobrien}
318879968Sobrien
318979968Sobrienint
319079968Sobrienextended_port(const char *arg)
319179968Sobrien{
319279968Sobrien	char *tmp = NULL;
319379968Sobrien	char *result[3];
319479968Sobrien	char *p, *q;
319579968Sobrien	char delim;
319679968Sobrien	struct addrinfo hints;
319779968Sobrien	struct addrinfo *res = NULL;
319879968Sobrien	int i;
319979968Sobrien	unsigned long proto;
320079968Sobrien
3201161770Sobrien	tmp = ftpd_strdup(arg);
320279968Sobrien	p = tmp;
320379968Sobrien	delim = p[0];
320479968Sobrien	p++;
320579968Sobrien	memset(result, 0, sizeof(result));
320679968Sobrien	for (i = 0; i < 3; i++) {
320779968Sobrien		q = strchr(p, delim);
320879968Sobrien		if (!q || *q != delim)
320979968Sobrien			goto parsefail;
321079968Sobrien		*q++ = '\0';
321179968Sobrien		result[i] = p;
321279968Sobrien		p = q;
321379968Sobrien	}
321479968Sobrien
321579968Sobrien			/* some more sanity checks */
3216108746Sobrien	errno = 0;
321779968Sobrien	p = NULL;
321879968Sobrien	(void)strtoul(result[2], &p, 10);
3219108746Sobrien	if (errno || !*result[2] || *p)
322079968Sobrien		goto parsefail;
3221108746Sobrien	errno = 0;
322279968Sobrien	p = NULL;
322379968Sobrien	proto = strtoul(result[0], &p, 10);
3224108746Sobrien	if (errno || !*result[0] || *p)
322579968Sobrien		goto protounsupp;
322679968Sobrien
322779968Sobrien	memset(&hints, 0, sizeof(hints));
322879968Sobrien	hints.ai_family = epsvproto2af((int)proto);
322979968Sobrien	if (hints.ai_family < 0)
323079968Sobrien		goto protounsupp;
323179968Sobrien	hints.ai_socktype = SOCK_STREAM;
323279968Sobrien	hints.ai_flags = AI_NUMERICHOST;
323379968Sobrien	if (getaddrinfo(result[1], result[2], &hints, &res))
323479968Sobrien		goto parsefail;
323579968Sobrien	if (res->ai_next)
323679968Sobrien		goto parsefail;
323779968Sobrien	if (sizeof(data_dest) < res->ai_addrlen)
323879968Sobrien		goto parsefail;
323979968Sobrien	memcpy(&data_dest.si_su, res->ai_addr, res->ai_addrlen);
324079968Sobrien	data_dest.su_len = res->ai_addrlen;
324179968Sobrien#ifdef INET6
324279968Sobrien	if (his_addr.su_family == AF_INET6 &&
324379968Sobrien	    data_dest.su_family == AF_INET6) {
324479968Sobrien			/* XXX: more sanity checks! */
324579968Sobrien		data_dest.su_scope_id = his_addr.su_scope_id;
324679968Sobrien	}
324779968Sobrien#endif
324879968Sobrien
324979968Sobrien	if (tmp != NULL)
325079968Sobrien		free(tmp);
325179968Sobrien	if (res)
325279968Sobrien		freeaddrinfo(res);
325379968Sobrien	return 0;
325479968Sobrien
325579968Sobrien parsefail:
325679968Sobrien	reply(500, "Invalid argument, rejected.");
325779968Sobrien	usedefault = 1;
325879968Sobrien	if (tmp != NULL)
325979968Sobrien		free(tmp);
326079968Sobrien	if (res)
326179968Sobrien		freeaddrinfo(res);
326279968Sobrien	return -1;
326379968Sobrien
326479968Sobrien protounsupp:
326579968Sobrien	epsv_protounsupp("Protocol not supported");
326679968Sobrien	usedefault = 1;
326779968Sobrien	if (tmp != NULL)
326879968Sobrien		free(tmp);
326979968Sobrien	return -1;
327079968Sobrien}
327179968Sobrien
327279968Sobrien/*
327379968Sobrien * 522 Protocol not supported (proto,...)
327479968Sobrien * as we assume address family for control and data connections are the same,
327579968Sobrien * we do not return the list of address families we support - instead, we
327679968Sobrien * return the address family of the control connection.
327779968Sobrien */
327879968Sobrienvoid
327979968Sobrienepsv_protounsupp(const char *message)
328079968Sobrien{
328179968Sobrien	int proto;
328279968Sobrien
328379968Sobrien	proto = af2epsvproto(ctrl_addr.su_family);
328479968Sobrien	if (proto < 0)
328579968Sobrien		reply(501, "%s", message);	/* XXX */
328679968Sobrien	else
328779968Sobrien		reply(522, "%s, use (%d)", message, proto);
328879968Sobrien}
328979968Sobrien
329079968Sobrien/*
329179968Sobrien * Generate unique name for file with basename "local".
329279968Sobrien * The file named "local" is already known to exist.
329379968Sobrien * Generates failure reply on error.
329479968Sobrien *
329579968Sobrien * XXX:	this function should under go changes similar to
329679968Sobrien *	the mktemp(3)/mkstemp(3) changes.
329779968Sobrien */
329879968Sobrienstatic char *
329979968Sobriengunique(const char *local)
330079968Sobrien{
330179968Sobrien	static char new[MAXPATHLEN];
330279968Sobrien	struct stat st;
330379968Sobrien	char *cp;
330479968Sobrien	int count;
330579968Sobrien
330679968Sobrien	cp = strrchr(local, '/');
330779968Sobrien	if (cp)
330879968Sobrien		*cp = '\0';
330979968Sobrien	if (stat(cp ? local : ".", &st) < 0) {
331079968Sobrien		perror_reply(553, cp ? local : ".");
331179968Sobrien		return (NULL);
331279968Sobrien	}
331379968Sobrien	if (cp)
331479968Sobrien		*cp = '/';
331579968Sobrien	for (count = 1; count < 100; count++) {
331679968Sobrien		(void)snprintf(new, sizeof(new) - 1, "%s.%d", local, count);
331779968Sobrien		if (stat(new, &st) < 0)
331879968Sobrien			return (new);
331979968Sobrien	}
332079968Sobrien	reply(452, "Unique file name cannot be created.");
332179968Sobrien	return (NULL);
332279968Sobrien}
332379968Sobrien
332479968Sobrien/*
332579968Sobrien * Format and send reply containing system error number.
332679968Sobrien */
332779968Sobrienvoid
332879968Sobrienperror_reply(int code, const char *string)
332979968Sobrien{
333079968Sobrien	int save_errno;
333179968Sobrien
333279968Sobrien	save_errno = errno;
333379968Sobrien	reply(code, "%s: %s.", string, strerror(errno));
333479968Sobrien	errno = save_errno;
333579968Sobrien}
333679968Sobrien
333779968Sobrienstatic char *onefile[] = {
333879968Sobrien	"",
333979968Sobrien	0
334079968Sobrien};
334179968Sobrien
334279968Sobrienvoid
334379968Sobriensend_file_list(const char *whichf)
334479968Sobrien{
334579968Sobrien	struct stat st;
334679968Sobrien	DIR *dirp = NULL;
334779968Sobrien	struct dirent *dir;
334879968Sobrien	FILE *dout = NULL;
3349133939Sobrien	char **dirlist, *dirname, *p;
3350133939Sobrien	char *notglob = NULL;
335179968Sobrien	int simple = 0;
335279968Sobrien	int freeglob = 0;
335379968Sobrien	glob_t gl;
335479968Sobrien
335579968Sobrien#ifdef __GNUC__
335679968Sobrien	(void) &dout;
335779968Sobrien	(void) &dirlist;
335879968Sobrien	(void) &simple;
335979968Sobrien	(void) &freeglob;
336079968Sobrien#endif
3361133939Sobrien	urgflag = 0;
336279968Sobrien
336379968Sobrien	p = NULL;
336479968Sobrien	if (strpbrk(whichf, "~{[*?") != NULL) {
336579968Sobrien		int flags = GLOB_BRACE|GLOB_NOCHECK|GLOB_TILDE|GLOB_LIMIT;
336679968Sobrien
336779968Sobrien		memset(&gl, 0, sizeof(gl));
336879968Sobrien		freeglob = 1;
336979968Sobrien		if (glob(whichf, flags, 0, &gl)) {
3370161770Sobrien			reply(450, "Not found");
3371133939Sobrien			goto cleanup_send_file_list;
337279968Sobrien		} else if (gl.gl_pathc == 0) {
337379968Sobrien			errno = ENOENT;
3374161770Sobrien			perror_reply(450, whichf);
3375133939Sobrien			goto cleanup_send_file_list;
337679968Sobrien		}
337779968Sobrien		dirlist = gl.gl_pathv;
337879968Sobrien	} else {
3379161770Sobrien		notglob = ftpd_strdup(whichf);
338092282Sobrien		onefile[0] = notglob;
338179968Sobrien		dirlist = onefile;
338279968Sobrien		simple = 1;
338379968Sobrien	}
338479968Sobrien					/* XXX: } for vi sm */
338579968Sobrien
338679968Sobrien	while ((dirname = *dirlist++) != NULL) {
338779968Sobrien		int trailingslash = 0;
338879968Sobrien
338979968Sobrien		if (stat(dirname, &st) < 0) {
339079968Sobrien			/*
339179968Sobrien			 * If user typed "ls -l", etc, and the client
339279968Sobrien			 * used NLST, do what the user meant.
339379968Sobrien			 */
339479968Sobrien			/* XXX: nuke this support? */
339579968Sobrien			if (dirname[0] == '-' && *dirlist == NULL &&
339679968Sobrien			    transflag == 0) {
339779968Sobrien				char *argv[] = { INTERNAL_LS, "", NULL };
339879968Sobrien
339979968Sobrien				argv[1] = dirname;
340079968Sobrien				retrieve(argv, dirname);
3401133939Sobrien				goto cleanup_send_file_list;
340279968Sobrien			}
3403161770Sobrien			perror_reply(450, whichf);
340479968Sobrien			goto cleanup_send_file_list;
340579968Sobrien		}
340679968Sobrien
340779968Sobrien		if (S_ISREG(st.st_mode)) {
340879968Sobrien			/*
340979968Sobrien			 * XXXRFC:
341079968Sobrien			 *	should we follow RFC959 and not work
341179968Sobrien			 *	for non directories?
341279968Sobrien			 */
341379968Sobrien			if (dout == NULL) {
341479968Sobrien				dout = dataconn("file list", (off_t)-1, "w");
341579968Sobrien				if (dout == NULL)
3416133939Sobrien					goto cleanup_send_file_list;
3417133939Sobrien				transflag = 1;
341879968Sobrien			}
341979968Sobrien			cprintf(dout, "%s%s\n", dirname,
342079968Sobrien			    type == TYPE_A ? "\r" : "");
342179968Sobrien			continue;
342279968Sobrien		} else if (!S_ISDIR(st.st_mode))
342379968Sobrien			continue;
342479968Sobrien
342579968Sobrien		if (dirname[strlen(dirname) - 1] == '/')
342679968Sobrien			trailingslash++;
342779968Sobrien
342879968Sobrien		if ((dirp = opendir(dirname)) == NULL)
342979968Sobrien			continue;
343079968Sobrien
343179968Sobrien		while ((dir = readdir(dirp)) != NULL) {
343279968Sobrien			char nbuf[MAXPATHLEN];
343379968Sobrien
3434133939Sobrien			if (urgflag && handleoobcmd())
3435133939Sobrien				goto cleanup_send_file_list;
3436133939Sobrien
343779968Sobrien			if (ISDOTDIR(dir->d_name) || ISDOTDOTDIR(dir->d_name))
343879968Sobrien				continue;
343979968Sobrien
344079968Sobrien			(void)snprintf(nbuf, sizeof(nbuf), "%s%s%s", dirname,
344179968Sobrien			    trailingslash ? "" : "/", dir->d_name);
344279968Sobrien
344379968Sobrien			/*
344479968Sobrien			 * We have to do a stat to ensure it's
344579968Sobrien			 * not a directory or special file.
344679968Sobrien			 */
344779968Sobrien			/*
344879968Sobrien			 * XXXRFC:
344979968Sobrien			 *	should we follow RFC959 and filter out
345079968Sobrien			 *	non files ?   lukem - NO!, or not until
345179968Sobrien			 *	our ftp client uses MLS{T,D} for completion.
345279968Sobrien			 */
345379968Sobrien			if (simple || (stat(nbuf, &st) == 0 &&
345479968Sobrien			    S_ISREG(st.st_mode))) {
345579968Sobrien				if (dout == NULL) {
345679968Sobrien					dout = dataconn("file list", (off_t)-1,
345779968Sobrien						"w");
345879968Sobrien					if (dout == NULL)
3459133939Sobrien						goto cleanup_send_file_list;
3460133939Sobrien					transflag = 1;
346179968Sobrien				}
346279968Sobrien				p = nbuf;
346379968Sobrien				if (nbuf[0] == '.' && nbuf[1] == '/')
346479968Sobrien					p = &nbuf[2];
346579968Sobrien				cprintf(dout, "%s%s\n", p,
346679968Sobrien				    type == TYPE_A ? "\r" : "");
346779968Sobrien			}
346879968Sobrien		}
346979968Sobrien		(void) closedir(dirp);
347079968Sobrien	}
347179968Sobrien
347279968Sobrien	if (dout == NULL)
3473161770Sobrien		reply(450, "No files found.");
347479968Sobrien	else if (ferror(dout) != 0)
3475161770Sobrien		perror_reply(451, "Data connection");
347679968Sobrien	else
347779968Sobrien		reply(226, "Transfer complete.");
347879968Sobrien
347979968Sobrien cleanup_send_file_list:
3480133939Sobrien	closedataconn(dout);
348179968Sobrien	transflag = 0;
3482133939Sobrien	urgflag = 0;
348379968Sobrien	total_xfers++;
348479968Sobrien	total_xfers_out++;
348592282Sobrien	if (notglob)
348692282Sobrien		free(notglob);
348779968Sobrien	if (freeglob)
348879968Sobrien		globfree(&gl);
348979968Sobrien}
349079968Sobrien
349179968Sobrienchar *
349279968Sobrienconffilename(const char *s)
349379968Sobrien{
349479968Sobrien	static char filename[MAXPATHLEN];
349579968Sobrien
349679968Sobrien	if (*s == '/')
349779968Sobrien		strlcpy(filename, s, sizeof(filename));
349879968Sobrien	else
349979968Sobrien		(void)snprintf(filename, sizeof(filename), "%s/%s", confdir ,s);
350079968Sobrien	return (filename);
350179968Sobrien}
350279968Sobrien
350379968Sobrien/*
350479968Sobrien * logxfer --
350579968Sobrien *	if logging > 1, then based on the arguments, syslog a message:
350679968Sobrien *	 if bytes != -1		"<command> <file1> = <bytes> bytes"
350779968Sobrien *	 else if file2 != NULL	"<command> <file1> <file2>"
350879968Sobrien *	 else			"<command> <file1>"
350979968Sobrien *	if elapsed != NULL, append "in xxx.yyy seconds"
351079968Sobrien *	if error != NULL, append ": " + error
351179968Sobrien *
3512108746Sobrien *	if doxferlog != 0, bytes != -1, and command is "get", "put",
3513133939Sobrien *	or "append", syslog and/or write a wu-ftpd style xferlog entry
351479968Sobrien */
351579968Sobrienvoid
351679968Sobrienlogxfer(const char *command, off_t bytes, const char *file1, const char *file2,
3517108746Sobrien    const struct timeval *elapsed, const char *error)
351879968Sobrien{
3519161770Sobrien	char		 buf[MAXPATHLEN * 2 + 100];
3520161770Sobrien	char		 realfile1[MAXPATHLEN], realfile2[MAXPATHLEN];
352179968Sobrien	const char	*r1, *r2;
352279968Sobrien	char		 direction;
352379968Sobrien	size_t		 len;
352479968Sobrien	time_t		 now;
352579968Sobrien
352679968Sobrien	if (logging <=1 && !doxferlog)
352779968Sobrien		return;
352879968Sobrien
352979968Sobrien	r1 = r2 = NULL;
3530161770Sobrien	if ((r1 = realpath(file1, realfile1)) == NULL)
353179968Sobrien		r1 = file1;
353279968Sobrien	if (file2 != NULL)
3533161770Sobrien		if ((r2 = realpath(file2, realfile2)) == NULL)
353479968Sobrien			r2 = file2;
353579968Sobrien
353679968Sobrien		/*
353779968Sobrien		 * syslog command
353879968Sobrien		 */
353979968Sobrien	if (logging > 1) {
354079968Sobrien		len = snprintf(buf, sizeof(buf), "%s %s", command, r1);
354179968Sobrien		if (bytes != (off_t)-1)
354279968Sobrien			len += snprintf(buf + len, sizeof(buf) - len,
354379968Sobrien			    " = " LLF " byte%s", (LLT) bytes, PLURAL(bytes));
354479968Sobrien		else if (r2 != NULL)
354579968Sobrien			len += snprintf(buf + len, sizeof(buf) - len,
354679968Sobrien			    " %s", r2);
354779968Sobrien		if (elapsed != NULL)
354879968Sobrien			len += snprintf(buf + len, sizeof(buf) - len,
354979968Sobrien			    " in %ld.%.03d seconds", elapsed->tv_sec,
355079968Sobrien			    (int)(elapsed->tv_usec / 1000));
355179968Sobrien		if (error != NULL)
355279968Sobrien			len += snprintf(buf + len, sizeof(buf) - len,
355379968Sobrien			    ": %s", error);
355479968Sobrien		syslog(LOG_INFO, "%s", buf);
355579968Sobrien	}
355679968Sobrien
355779968Sobrien		/*
355879968Sobrien		 * syslog wu-ftpd style log entry, prefixed with "xferlog: "
355979968Sobrien		 */
356092282Sobrien	if (!doxferlog || bytes == -1)
356179968Sobrien		return;
356279968Sobrien
356379968Sobrien	if (strcmp(command, "get") == 0)
356479968Sobrien		direction = 'o';
356579968Sobrien	else if (strcmp(command, "put") == 0 || strcmp(command, "append") == 0)
356679968Sobrien		direction = 'i';
356779968Sobrien	else
356879968Sobrien		return;
356979968Sobrien
357079968Sobrien	time(&now);
3571133939Sobrien	len = snprintf(buf, sizeof(buf),
3572133939Sobrien	    "%.24s %ld %s " LLF " %s %c %s %c %c %s FTP 0 * %c\n",
357379968Sobrien
357479968Sobrien/*
3575133939Sobrien * XXX: wu-ftpd puts ' (send)' or ' (recv)' in the syslog message, and removes
357679968Sobrien *	the full date.  This may be problematic for accurate log parsing,
357779968Sobrien *	given that syslog messages don't contain the full date.
357879968Sobrien */
357979968Sobrien	    ctime(&now),
358079968Sobrien	    elapsed == NULL ? 0 : elapsed->tv_sec + (elapsed->tv_usec > 0),
358179968Sobrien	    remotehost,
358292282Sobrien	    (LLT) bytes,
358379968Sobrien	    r1,
358479968Sobrien	    type == TYPE_A ? 'a' : 'b',
358579968Sobrien	    "_",		/* XXX: take conversions into account? */
358679968Sobrien	    direction,
358779968Sobrien
358879968Sobrien	    curclass.type == CLASS_GUEST ?  'a' :
358979968Sobrien	    curclass.type == CLASS_CHROOT ? 'g' :
359079968Sobrien	    curclass.type == CLASS_REAL ?   'r' : '?',
359179968Sobrien
359279968Sobrien	    curclass.type == CLASS_GUEST ? pw->pw_passwd : pw->pw_name,
359379968Sobrien	    error != NULL ? 'i' : 'c'
359479968Sobrien	    );
3595133939Sobrien
3596133939Sobrien	if ((doxferlog & 2) && xferlogfd != -1)
3597133939Sobrien		write(xferlogfd, buf, len);
3598133939Sobrien	if ((doxferlog & 1)) {
3599133939Sobrien		buf[len-1] = '\n';	/* strip \n from syslog message */
3600133939Sobrien		syslog(LOG_INFO, "xferlog: %s", buf);
3601133939Sobrien	}
360279968Sobrien}
360379968Sobrien
360479968Sobrien/*
3605108746Sobrien * Log the resource usage.
3606108746Sobrien *
3607108746Sobrien * XXX: more resource usage to logging?
3608108746Sobrien */
3609108746Sobrienvoid
3610108746Sobrienlogrusage(const struct rusage *rusage_before,
3611108746Sobrien    const struct rusage *rusage_after)
3612108746Sobrien{
3613108746Sobrien	struct timeval usrtime, systime;
3614108746Sobrien
3615108746Sobrien	if (logging <= 1)
3616108746Sobrien		return;
3617108746Sobrien
3618108746Sobrien	timersub(&rusage_after->ru_utime, &rusage_before->ru_utime, &usrtime);
3619108746Sobrien	timersub(&rusage_after->ru_stime, &rusage_before->ru_stime, &systime);
3620108746Sobrien	syslog(LOG_INFO, "%ld.%.03du %ld.%.03ds %ld+%ldio %ldpf+%ldw",
3621108746Sobrien	    usrtime.tv_sec, (int)(usrtime.tv_usec / 1000),
3622108746Sobrien	    systime.tv_sec, (int)(systime.tv_usec / 1000),
3623108746Sobrien	    rusage_after->ru_inblock - rusage_before->ru_inblock,
3624108746Sobrien	    rusage_after->ru_oublock - rusage_before->ru_oublock,
3625108746Sobrien	    rusage_after->ru_majflt - rusage_before->ru_majflt,
3626108746Sobrien	    rusage_after->ru_nswap - rusage_before->ru_nswap);
3627108746Sobrien}
3628108746Sobrien
3629108746Sobrien/*
363079968Sobrien * Determine if `password' is valid for user given in `pw'.
363179968Sobrien * Returns 2 if password expired, 1 if otherwise failed, 0 if ok
363279968Sobrien */
363379968Sobrienint
363492282Sobriencheckpassword(const struct passwd *pwent, const char *password)
363579968Sobrien{
363679968Sobrien	char	*orig, *new;
3637161770Sobrien	time_t	 change, expire, now;
363879968Sobrien
3639161770Sobrien	change = expire = 0;
364092282Sobrien	if (pwent == NULL)
364179968Sobrien		return 1;
364279968Sobrien
3643161770Sobrien	time(&now);
364492282Sobrien	orig = pwent->pw_passwd;	/* save existing password */
364592282Sobrien	expire = pwent->pw_expire;
3646161770Sobrien	change = (pwent->pw_change == _PASSWORD_CHGNOW)? now : pwent->pw_change;
364779968Sobrien
364879968Sobrien	if (orig[0] == '\0')		/* don't allow empty passwords */
364979968Sobrien		return 1;
365079968Sobrien
365179968Sobrien	new = crypt(password, orig);	/* encrypt given password */
365279968Sobrien	if (strcmp(new, orig) != 0)	/* compare */
365379968Sobrien		return 1;
365479968Sobrien
3655161770Sobrien	if ((expire && now >= expire) || (change && now >= change))
365679968Sobrien		return 2;		/* check if expired */
365779968Sobrien
365879968Sobrien	return 0;			/* OK! */
365979968Sobrien}
366079968Sobrien
366179968Sobrienchar *
3662161770Sobrienftpd_strdup(const char *s)
366379968Sobrien{
366479968Sobrien	char *new = strdup(s);
366579968Sobrien
366679968Sobrien	if (new == NULL)
366779968Sobrien		fatal("Local resource failure: malloc");
366879968Sobrien		/* NOTREACHED */
366979968Sobrien	return (new);
367079968Sobrien}
367179968Sobrien
367279968Sobrien/*
367379968Sobrien * As per fprintf(), but increment total_bytes and total_bytes_out,
367479968Sobrien * by the appropriate amount.
367579968Sobrien */
367679968Sobrienvoid
367779968Sobriencprintf(FILE *fd, const char *fmt, ...)
367879968Sobrien{
367979968Sobrien	off_t b;
368079968Sobrien	va_list ap;
368179968Sobrien
368279968Sobrien	va_start(ap, fmt);
368379968Sobrien	b = vfprintf(fd, fmt, ap);
368492282Sobrien	va_end(ap);
368579968Sobrien	total_bytes += b;
368679968Sobrien	total_bytes_out += b;
368779968Sobrien}
3688161770Sobrien
3689161770Sobrien#ifdef USE_PAM
3690161770Sobrien/*
3691161770Sobrien * the following code is stolen from imap-uw PAM authentication module and
3692161770Sobrien * login.c
3693161770Sobrien */
3694161770Sobrien#define COPY_STRING(s) (s ? strdup(s) : NULL)
3695161770Sobrien
3696161770Sobrienstruct cred_t {
3697161770Sobrien	const char *uname;		/* user name */
3698161770Sobrien	const char *pass;		/* password */
3699161770Sobrien};
3700161770Sobrientypedef struct cred_t cred_t;
3701161770Sobrien
3702161770Sobrienstatic int
3703161770Sobrienauth_conv(int num_msg, const struct pam_message **msg,
3704161770Sobrien    struct pam_response **resp, void *appdata)
3705161770Sobrien{
3706161770Sobrien	int i;
3707161770Sobrien	cred_t *cred = (cred_t *) appdata;
3708161770Sobrien	struct pam_response *myreply;
3709161770Sobrien
3710161770Sobrien	myreply = calloc(num_msg, sizeof *myreply);
3711161770Sobrien	if (myreply == NULL)
3712161770Sobrien		return PAM_BUF_ERR;
3713161770Sobrien
3714161770Sobrien	for (i = 0; i < num_msg; i++) {
3715161770Sobrien		switch (msg[i]->msg_style) {
3716161770Sobrien		case PAM_PROMPT_ECHO_ON:	/* assume want user name */
3717161770Sobrien			myreply[i].resp_retcode = PAM_SUCCESS;
3718161770Sobrien			myreply[i].resp = COPY_STRING(cred->uname);
3719161770Sobrien			/* PAM frees resp. */
3720161770Sobrien			break;
3721161770Sobrien		case PAM_PROMPT_ECHO_OFF:	/* assume want password */
3722161770Sobrien			myreply[i].resp_retcode = PAM_SUCCESS;
3723161770Sobrien			myreply[i].resp = COPY_STRING(cred->pass);
3724161770Sobrien			/* PAM frees resp. */
3725161770Sobrien			break;
3726161770Sobrien		case PAM_TEXT_INFO:
3727161770Sobrien		case PAM_ERROR_MSG:
3728161770Sobrien			myreply[i].resp_retcode = PAM_SUCCESS;
3729161770Sobrien			myreply[i].resp = NULL;
3730161770Sobrien			break;
3731161770Sobrien		default:			/* unknown message style */
3732161770Sobrien			free(myreply);
3733161770Sobrien			return PAM_CONV_ERR;
3734161770Sobrien		}
3735161770Sobrien	}
3736161770Sobrien
3737161770Sobrien	*resp = myreply;
3738161770Sobrien	return PAM_SUCCESS;
3739161770Sobrien}
3740161770Sobrien
3741161770Sobrien/*
3742161770Sobrien * Attempt to authenticate the user using PAM.  Returns 0 if the user is
3743161770Sobrien * authenticated, or 1 if not authenticated.  If some sort of PAM system
3744161770Sobrien * error occurs (e.g., the "/etc/pam.conf" file is missing) then this
3745161770Sobrien * function returns -1.  This can be used as an indication that we should
3746161770Sobrien * fall back to a different authentication mechanism.
3747161770Sobrien */
3748161770Sobrienstatic int
3749161770Sobrienauth_pam(struct passwd **ppw, const char *pwstr)
3750161770Sobrien{
3751161770Sobrien	const char *tmpl_user;
3752161770Sobrien	const void *item;
3753161770Sobrien	int rval;
3754161770Sobrien	int e;
3755161770Sobrien	cred_t auth_cred = { (*ppw)->pw_name, pwstr };
3756161770Sobrien	struct pam_conv conv = { &auth_conv, &auth_cred };
3757161770Sobrien
3758161770Sobrien	e = pam_start("ftpd", (*ppw)->pw_name, &conv, &pamh);
3759161770Sobrien	if (e != PAM_SUCCESS) {
3760161770Sobrien		/*
3761161770Sobrien		 * In OpenPAM, it's OK to pass NULL to pam_strerror()
3762161770Sobrien		 * if context creation has failed in the first place.
3763161770Sobrien		 */
3764161770Sobrien		syslog(LOG_ERR, "pam_start: %s", pam_strerror(NULL, e));
3765161770Sobrien		return -1;
3766161770Sobrien	}
3767161770Sobrien
3768161770Sobrien	e = pam_set_item(pamh, PAM_RHOST, remotehost);
3769161770Sobrien	if (e != PAM_SUCCESS) {
3770161770Sobrien		syslog(LOG_ERR, "pam_set_item(PAM_RHOST): %s",
3771161770Sobrien			pam_strerror(pamh, e));
3772161770Sobrien		if ((e = pam_end(pamh, e)) != PAM_SUCCESS) {
3773161770Sobrien			syslog(LOG_ERR, "pam_end: %s", pam_strerror(pamh, e));
3774161770Sobrien		}
3775161770Sobrien		pamh = NULL;
3776161770Sobrien		return -1;
3777161770Sobrien	}
3778161770Sobrien
3779161770Sobrien	e = pam_authenticate(pamh, 0);
3780161770Sobrien	switch (e) {
3781161770Sobrien	case PAM_SUCCESS:
3782161770Sobrien		/*
3783161770Sobrien		 * With PAM we support the concept of a "template"
3784161770Sobrien		 * user.  The user enters a login name which is
3785161770Sobrien		 * authenticated by PAM, usually via a remote service
3786161770Sobrien		 * such as RADIUS or TACACS+.  If authentication
3787161770Sobrien		 * succeeds, a different but related "template" name
3788161770Sobrien		 * is used for setting the credentials, shell, and
3789161770Sobrien		 * home directory.  The name the user enters need only
3790161770Sobrien		 * exist on the remote authentication server, but the
3791161770Sobrien		 * template name must be present in the local password
3792161770Sobrien		 * database.
3793161770Sobrien		 *
3794161770Sobrien		 * This is supported by two various mechanisms in the
3795161770Sobrien		 * individual modules.  However, from the application's
3796161770Sobrien		 * point of view, the template user is always passed
3797161770Sobrien		 * back as a changed value of the PAM_USER item.
3798161770Sobrien		 */
3799161770Sobrien		if ((e = pam_get_item(pamh, PAM_USER, &item)) ==
3800161770Sobrien		    PAM_SUCCESS) {
3801161770Sobrien			tmpl_user = (const char *) item;
3802161770Sobrien			if (strcmp((*ppw)->pw_name, tmpl_user) != 0)
3803161770Sobrien				*ppw = sgetpwnam(tmpl_user);
3804161770Sobrien		} else
3805161770Sobrien			syslog(LOG_ERR, "Couldn't get PAM_USER: %s",
3806161770Sobrien			    pam_strerror(pamh, e));
3807161770Sobrien		rval = 0;
3808161770Sobrien		break;
3809161770Sobrien
3810161770Sobrien	case PAM_AUTH_ERR:
3811161770Sobrien	case PAM_USER_UNKNOWN:
3812161770Sobrien	case PAM_MAXTRIES:
3813161770Sobrien		rval = 1;
3814161770Sobrien		break;
3815161770Sobrien
3816161770Sobrien	default:
3817161770Sobrien		syslog(LOG_ERR, "pam_authenticate: %s", pam_strerror(pamh, e));
3818161770Sobrien		rval = -1;
3819161770Sobrien		break;
3820161770Sobrien	}
3821161770Sobrien
3822161770Sobrien	if (rval == 0) {
3823161770Sobrien		e = pam_acct_mgmt(pamh, 0);
3824161770Sobrien		if (e != PAM_SUCCESS) {
3825161770Sobrien			syslog(LOG_ERR, "pam_acct_mgmt: %s",
3826161770Sobrien						pam_strerror(pamh, e));
3827161770Sobrien			rval = 1;
3828161770Sobrien		}
3829161770Sobrien	}
3830161770Sobrien
3831161770Sobrien	if (rval != 0) {
3832161770Sobrien		if ((e = pam_end(pamh, e)) != PAM_SUCCESS) {
3833161770Sobrien			syslog(LOG_ERR, "pam_end: %s", pam_strerror(pamh, e));
3834161770Sobrien		}
3835161770Sobrien		pamh = NULL;
3836161770Sobrien	}
3837161770Sobrien	return rval;
3838161770Sobrien}
3839161770Sobrien
3840161770Sobrien#endif /* USE_PAM */
3841