diff options
Diffstat (limited to 'libexec/ftpd')
-rw-r--r-- | libexec/ftpd/Makefile | 28 | ||||
-rw-r--r-- | libexec/ftpd/extern.h | 10 | ||||
-rw-r--r-- | libexec/ftpd/ftpcmd.y | 72 | ||||
-rw-r--r-- | libexec/ftpd/ftpd.8 | 189 | ||||
-rw-r--r-- | libexec/ftpd/ftpd.c | 818 | ||||
-rw-r--r-- | libexec/ftpd/logwtmp.c | 18 | ||||
-rw-r--r-- | libexec/ftpd/pathnames.h | 8 | ||||
-rw-r--r-- | libexec/ftpd/popen.c | 36 | ||||
-rw-r--r-- | libexec/ftpd/skey-stuff.c | 29 |
9 files changed, 1068 insertions, 140 deletions
diff --git a/libexec/ftpd/Makefile b/libexec/ftpd/Makefile index 946aab7..49e98d5 100644 --- a/libexec/ftpd/Makefile +++ b/libexec/ftpd/Makefile @@ -1,9 +1,31 @@ # @(#)Makefile 8.2 (Berkeley) 4/4/94 +# $Id: Makefile,v 1.21 1997/04/26 12:12:10 davidn Exp $ PROG= ftpd -CFLAGS+=-DSETPROCTITLE -SRCS= ftpd.c ftpcmd.c logwtmp.c popen.c -MAN8= ftpd.0 +MAN8= ftpd.8 +SRCS= ftpd.c ftpcmd.c logwtmp.c popen.c skey-stuff.c + +CFLAGS+=-DSETPROCTITLE -DSKEY -DLOGIN_CAP -DVIRTUAL_HOSTING -Wall + +LDADD= -lskey -lmd -lcrypt -lutil +DPADD= ${LIBSKEY} ${LIBMD} ${LIBCRYPT} ${LIBUTIL} + CLEANFILES+=ftpcmd.c y.tab.h +.ifdef FTPD_INTERNAL_LS +LSDIR= ../../bin/ls +.PATH: ${.CURDIR}/${LSDIR} +SRCS+= ls.c cmp.c print.c stat_flags.c util.c +CFLAGS+=-DINTERNAL_LS -Dmain=ls_main -I${.CURDIR}/${LSDIR} +.endif + +.if exists(${DESTDIR}/usr/lib/libkrb.a) && defined(MAKE_EBONES) +.PATH: ${.CURDIR}/../../usr.bin/login +SRCS+= klogin.c +LDADD+= -lkrb -ldes +DPADD+= ${LIBKRB} ${LIBDES} +CFLAGS+=-DKERBEROS +DISTRIBUTION= krb +.endif + .include <bsd.prog.mk> diff --git a/libexec/ftpd/extern.h b/libexec/ftpd/extern.h index e3336b5..616da79 100644 --- a/libexec/ftpd/extern.h +++ b/libexec/ftpd/extern.h @@ -31,6 +31,7 @@ * SUCH DAMAGE. * * @(#)extern.h 8.2 (Berkeley) 4/4/94 + * $Id: extern.h,v 1.8 1997/02/22 14:21:26 peter Exp $ */ void blkfree __P((char **)); @@ -56,10 +57,19 @@ char *renamefrom __P((char *)); void reply __P((int, const char *, ...)); void retrieve __P((char *, char *)); void send_file_list __P((char *)); +#ifdef OLD_SETPROCTITLE void setproctitle __P((const char *, ...)); +#endif void statcmd __P((void)); void statfilecmd __P((char *)); void store __P((char *, char *, int)); void upper __P((char *)); void user __P((char *)); void yyerror __P((char *)); +int yyparse __P((void)); +#if defined(SKEY) && defined(_PWD_H_) /* XXX evil */ +char *skey_challenge __P((char *, struct passwd *, int)); +#endif +#if defined(INTERNAL_LS) +int ls_main __P((int, char **)); +#endif diff --git a/libexec/ftpd/ftpcmd.y b/libexec/ftpd/ftpcmd.y index 6ec3d25..659081b 100644 --- a/libexec/ftpd/ftpcmd.y +++ b/libexec/ftpd/ftpcmd.y @@ -31,6 +31,7 @@ * SUCH DAMAGE. * * @(#)ftpcmd.y 8.3 (Berkeley) 4/6/94 + * $Id: ftpcmd.y,v 1.10 1997/02/22 14:21:27 peter Exp $ */ /* @@ -63,13 +64,15 @@ static char sccsid[] = "@(#)ftpcmd.y 8.3 (Berkeley) 4/6/94"; #include <syslog.h> #include <time.h> #include <unistd.h> +#include <libutil.h> #include "extern.h" -extern struct sockaddr_in data_dest; +extern struct sockaddr_in data_dest, his_addr; extern int logged_in; extern struct passwd *pw; extern int guest; +extern int paranoid; extern int logging; extern int type; extern int form; @@ -77,7 +80,8 @@ extern int debug; extern int timeout; extern int maxtimeout; extern int pdata; -extern char hostname[], remotehost[]; +extern char *hostname; +extern char remotehost[]; extern char proctitle[]; extern int usedefault; extern int transflag; @@ -148,18 +152,32 @@ cmd pass($3); free($3); } - | PORT SP host_port CRLF - { - usedefault = 0; - if (pdata >= 0) { - (void) close(pdata); - pdata = -1; + | PORT check_login SP host_port CRLF + { + if ($2) { + if (paranoid && + ((ntohs(data_dest.sin_port) < + IPPORT_RESERVED) || + memcmp(&data_dest.sin_addr, + &his_addr.sin_addr, + sizeof(data_dest.sin_addr)))) { + usedefault = 1; + reply(500, + "Illegal PORT range rejected."); + } else { + usedefault = 0; + if (pdata >= 0) { + (void) close(pdata); + pdata = -1; + } + reply(200, "PORT command successful."); + } } - reply(200, "PORT command successful."); } - | PASV CRLF + | PASV check_login CRLF { - passive(); + if ($2) + passive(); } | TYPE SP type_code CRLF { @@ -291,16 +309,18 @@ cmd if ($4 != NULL) free($4); } - | RNTO SP pathname CRLF + | RNTO check_login SP pathname CRLF { - if (fromname) { - renamecmd(fromname, $3); - free(fromname); - fromname = (char *) 0; - } else { - reply(503, "Bad sequence of commands."); + if ($2) { + if (fromname) { + renamecmd(fromname, $4); + free(fromname); + fromname = (char *) 0; + } else { + reply(503, "Bad sequence of commands."); + } } - free($3); + free($4); } | ABOR CRLF { @@ -490,8 +510,9 @@ cmd struct tm *t; t = gmtime(&stbuf.st_mtime); reply(213, - "19%02d%02d%02d%02d%02d%02d", - t->tm_year, t->tm_mon+1, t->tm_mday, + "%04d%02d%02d%02d%02d%02d", + 1900 + t->tm_year, + t->tm_mon+1, t->tm_mday, t->tm_hour, t->tm_min, t->tm_sec); } } @@ -552,11 +573,12 @@ host_port { char *a, *p; - a = (char *)&data_dest.sin_addr; - a[0] = $1; a[1] = $3; a[2] = $5; a[3] = $7; + data_dest.sin_len = sizeof(struct sockaddr_in); + data_dest.sin_family = AF_INET; p = (char *)&data_dest.sin_port; p[0] = $9; p[1] = $11; - data_dest.sin_family = AF_INET; + a = (char *)&data_dest.sin_addr; + a[0] = $1; a[1] = $3; a[2] = $5; a[3] = $7; } ; @@ -976,7 +998,7 @@ yylex() upper(cp); p = lookup(sitetab, cp); cbuf[cpos] = c; - if (p != 0) { + if (guest == 0 && p != 0) { if (p->implemented == 0) { state = CMD; nack(p->name); diff --git a/libexec/ftpd/ftpd.8 b/libexec/ftpd/ftpd.8 index eb93c38..b3fa6bd 100644 --- a/libexec/ftpd/ftpd.8 +++ b/libexec/ftpd/ftpd.8 @@ -29,9 +29,10 @@ .\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF .\" SUCH DAMAGE. .\" -.\" @(#)ftpd.8 8.3 (Berkeley) 6/1/94 +.\" @(#)ftpd.8 8.2 (Berkeley) 4/19/94 +.\" $Id: ftpd.8,v 1.17 1997/04/27 08:29:21 davidn Exp $ .\" -.Dd June 1, 1994 +.Dd April 19, 1994 .Dt FTPD 8 .Os BSD 4.2 .Sh NAME @@ -41,8 +42,14 @@ Internet File Transfer Protocol server .Sh SYNOPSIS .Nm ftpd .Op Fl dl +.Op Fl D +.Op Fl R +.Op Fl S +.Op Fl U .Op Fl T Ar maxtimeout .Op Fl t Ar timeout +.Op Fl a Ar address +.Op Fl p Ar file .Sh DESCRIPTION .Nm Ftpd is the @@ -65,7 +72,47 @@ Each successful and failed session is logged using syslog with a facility of LOG_FTP. If this option is specified twice, the retrieve (get), store (put), append, delete, make directory, remove directory and rename operations and -their filename arguments are also logged. +their filename arguments are also logged. Note: LOG_FTP messages +are not displayed by +.Xr syslogd 8 +by default, and may have to be enabled in +.Xr syslogd 8 Ns 's +configuration file. +.It Fl D +With this option set, +.Nm ftpd +will detach and become a daemon, accepting connections on the FTP port and +forking children processes to handle them. This is lower overhead than +starting +.Nm ftpd +from +.Xr inetd 8 +and is thus useful on busy servers to reduce load. +.It Fl R +With this option set, +.Nm ftpd +will revert to historical behavior with regard to security checks on +user operations and restrictions on PORT requests. +Currently, +.Nm ftpd +will only honor PORT commands directed to unprivileged ports on the +remote user's host (which violates the FTP protocol specification but +closes some security holes). +. +.It Fl S +With this option set, +.Nm ftpd +logs all anonymous transfers to the file +.Pa /var/log/ftpd +when this file exists. +. +.It Fl U +In previous versions of +.Nm ftpd , +when a passive mode client requested a data connection to the server, +the server would use data ports in the range 1024..4999. Now, by default, +the server will use data ports in the range 40000..44999. Specifying this +option will revert to the old behavior. .It Fl T A client may also request a different timeout period; the maximum period allowed may be set to @@ -78,6 +125,18 @@ The default limit is 2 hours. The inactivity timeout period is set to .Ar timeout seconds (the default is 15 minutes). +.It Fl a +When +.Fl D +is specified, accept connections only on the specified +.Ar address . +.It Fl p +When +.Fl D +is specified, write the daemon's process ID to +.Ar file . +.It Fl A +Allow only anonymous ftp access .El .Pp The file @@ -94,7 +153,7 @@ prints it before issuing the .Dq ready message. If the file -.Pa /etc/motd +.Pa /etc/ftpmotd exists, .Nm prints it after a successful login. @@ -182,22 +241,55 @@ This allows users to utilize the metacharacters .Dq Li \&*?[]{}~ . .Pp .Nm Ftpd -authenticates users according to three rules. +authenticates users according to five rules. .Pp .Bl -enum -offset indent .It -The login name must be in the password data base, -.Pa /etc/passwd , +The login name must be in the password data base and not have a null password. In this case a password must be provided by the client before any file operations may be performed. +If the user has an S/Key key, the response from a successful USER +command will include an S/Key challenge. The client may choose to respond +with a PASS command giving either a standard password or an S/Key +one-time password. The server will automatically determine which type of +password it has been given and attempt to authenticate accordingly. See +.Xr key 1 +for more information on S/Key authentication. S/Key is a Trademark of +Bellcore. .It The login name must not appear in the file .Pa /etc/ftpusers . .It +The login name must not be a member of a group specified in the file +.Pa /etc/ftpusers . +Entries in this file interpreted as group names are prefixed by an "at" +.Ql \&@ +sign. +.It The user must have a standard shell returned by .Xr getusershell 3 . .It +If the user name appears in the file +.Pa /etc/ftpchroot , +or the user is a member of a group with a group entry in this file, +i.e. one prefixed with +.Ql \&@ , +the session's root will be changed to the user's login directory by +.Xr chroot 2 +as for an +.Dq anonymous +or +.Dq ftp +account (see next item). +This facility may also be triggered by enabling the boolean "ftp-chroot" +capability in +.Xr login.conf 5 . +However, the user must still supply a password. +This feature is intended as a compromise between a fully anonymous +account and a fully privileged account. +The account should also be set up as for an anonymous account. +.It If the user name is .Dq anonymous or @@ -209,6 +301,9 @@ file (user In this case the user is allowed to log in by specifying any password (by convention an email address for the user should be used as the password). +When the +.Fl S +option is set, all transfers are logged as well. .El .Pp In the last case, @@ -228,7 +323,6 @@ subtree be constructed with care, following these rules: Make the home directory owned by .Dq root and unwritable by anyone. -.ne 1i .It Pa ~ftp/bin Make this directory owned by .Dq root @@ -241,8 +335,8 @@ This program should be mode 111. Make this directory owned by .Dq root and unwritable by anyone (mode 555). -The files -.Xr passwd 5 +The files pwd.db (see +.Xr passwd 5 ) and .Xr group 5 must be present for the @@ -252,7 +346,7 @@ The password field in .Xr passwd is not used, and should not contain real passwords. The file -.Pa motd , +.Pa ftpmotd , if present, will be printed after a successful login. These files should be mode 444. .It Pa ~ftp/pub @@ -262,20 +356,91 @@ Guests can then place files which are to be accessible via the anonymous account in this directory. .El +.Pp +If the system has multiple IP addresses, +.Nm ftpd +supports the idea of virtual hosts, which provides the ability to +define multiple anonymous ftp areas, each one allocated to a different +internet address. +The file +.Pa /etc/ftphosts +contains information pertaining to each of the virtual hosts. +Each host is defined on its own line which contains a number of +fields separated by whitespace: +.Bl -tag -offset indent -width hostname +.It hostname +Contains the hostname or IP address of the virtual host. +.It user +Contains a user record in the system password file. +As with normal anonymous ftp, this user's access uid, gid and group +memberships determine file access to the anonymous ftp area. +The anonymous ftp area (to which any user is chrooted on login) +is determined by the home directory defined for the account. +User id and group for any ftp account may be the same as for the +standard ftp user. +.It statfile +File to which all file transfers are logged, which +defaults to +.Pa /var/log/ftpd . +.It welcome +This file is the welcome message displayed before the server ready +prompt. +It defaults to +.Pa /etc/ftpwelcome . +.It motd +This file is displayed after the user logs in. +It defaults to +.Pa /etc/ftpmotd . +.El +.Pp +Defining a virtual host for the primary IP address or hostname +changes the default for ftp logins to that address. +The 'user', 'statfile', 'welcome' and 'motd' fields may be left +blank, or a single hypen '-' used to indicate that the default +value is to be used. +.Pp +As with any anonymous login configuration, due care must be given +to setup and maintenance to guard against security related problems. +.Pp +If compiled with the +.Em INTERNAL_LS +option, +.Nm ftpd +will have internal support for handling remote requests to list +files, and will not execute +.Pa /bin/ls +in either a chrooted or non-chrooted environment. +In this case, the +.Pa ~/bin/ls +executable need not be placed into the chrooted tree, nor need the +.Pa ~/bin +directory exist. +This support may be added by making ftpd with the +.Em FTP_INTERNAL_LS +variable set either in +.Pa /etc/make.conf +or in the shell's environment. .Sh FILES .Bl -tag -width /etc/ftpwelcome -compact .It Pa /etc/ftpusers List of unwelcome/restricted users. +.It Pa /etc/ftpchroot +List of normal users who should be chroot'd. .It Pa /etc/ftpwelcome Welcome notice. -.It Pa /etc/motd +.It Pa /etc/ftpmotd Welcome notice after login. .It Pa /etc/nologin Displayed and access refused. +.It Pa /var/log/ftpd +Log file for anonymous transfers. .El .Sh SEE ALSO .Xr ftp 1 , +.Xr key 1 , .Xr getusershell 3 , +.Xr login.conf 5 , +.Xr inetd 8 , .Xr syslogd 8 .Sh BUGS The server must run as the super-user diff --git a/libexec/ftpd/ftpd.c b/libexec/ftpd/ftpd.c index 875ec62..37407a3 100644 --- a/libexec/ftpd/ftpd.c +++ b/libexec/ftpd/ftpd.c @@ -29,17 +29,23 @@ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. + * + * $Id: ftpd.c,v 1.40 1997/05/21 23:24:41 danny Exp $ */ +#if 0 #ifndef lint static char copyright[] = "@(#) Copyright (c) 1985, 1988, 1990, 1992, 1993, 1994\n\ The Regents of the University of California. All rights reserved.\n"; #endif /* not lint */ +#endif +#if 0 #ifndef lint -static char sccsid[] = "@(#)ftpd.c 8.5 (Berkeley) 4/28/95"; +static char sccsid[] = "@(#)ftpd.c 8.4 (Berkeley) 4/16/94"; #endif /* not lint */ +#endif /* * FTP server. @@ -49,10 +55,12 @@ static char sccsid[] = "@(#)ftpd.c 8.5 (Berkeley) 4/28/95"; #include <sys/ioctl.h> #include <sys/socket.h> #include <sys/wait.h> +#include <sys/mman.h> #include <netinet/in.h> #include <netinet/in_systm.h> #include <netinet/ip.h> +#include <netinet/tcp.h> #define FTP_NAMES #include <arpa/ftp.h> @@ -68,6 +76,7 @@ static char sccsid[] = "@(#)ftpd.c 8.5 (Berkeley) 4/28/95"; #include <limits.h> #include <netdb.h> #include <pwd.h> +#include <grp.h> #include <setjmp.h> #include <signal.h> #include <stdio.h> @@ -76,6 +85,14 @@ static char sccsid[] = "@(#)ftpd.c 8.5 (Berkeley) 4/28/95"; #include <syslog.h> #include <time.h> #include <unistd.h> +#include <libutil.h> +#ifdef LOGIN_CAP +#include <login_cap.h> +#endif + +#ifdef SKEY +#include <skey.h> +#endif #include "pathnames.h" #include "extern.h" @@ -86,17 +103,24 @@ static char sccsid[] = "@(#)ftpd.c 8.5 (Berkeley) 4/28/95"; #include <varargs.h> #endif +#ifdef INTERNAL_LS +static char version[] = "Version 6.00LS"; +#undef main +#else static char version[] = "Version 6.00"; +#endif extern off_t restart_point; extern char cbuf[]; +struct sockaddr_in server_addr; struct sockaddr_in ctrl_addr; struct sockaddr_in data_source; struct sockaddr_in data_dest; struct sockaddr_in his_addr; struct sockaddr_in pasv_addr; +int daemon_mode; int data; jmp_buf errcatch, urgcatch; int logged_in; @@ -105,7 +129,13 @@ int debug; int timeout = 900; /* timeout after 15 minutes of inactivity */ int maxtimeout = 7200;/* don't allow idle time to be set beyond 2 hours */ int logging; +int restricted_data_ports = 1; +int paranoid = 1; /* be extra careful about security */ +int anon_only = 0; /* Only anonymous ftp allowed */ int guest; +int dochroot; +int stats; +int statfd = -1; int type; int form; int stru; /* avoid C keyword */ @@ -121,8 +151,39 @@ off_t byte_count; #endif int defumask = CMASK; /* default umask value */ char tmpline[7]; -char hostname[MAXHOSTNAMELEN]; +char *hostname; +#ifdef VIRTUAL_HOSTING +char *ftpuser; + +static struct ftphost { + struct ftphost *next; + struct in_addr hostaddr; + char *hostname; + char *anonuser; + char *statfile; + char *welcome; + char *loginmsg; +} *thishost, *firsthost; + +#endif char remotehost[MAXHOSTNAMELEN]; +char *ident = NULL; + +static char ttyline[20]; +char *tty = ttyline; /* for klogin */ + +#ifdef KERBEROS +int klogin __P((struct passwd *, char *, char *, char *)); +#endif + +struct in_addr bind_address; +char *pid_file = NULL; + +#if defined(KERBEROS) +int notickets = 1; +int noticketsdontcomplain = 1; +char *krbtkfile_env = NULL; +#endif /* * Timeout intervals for retrying connections @@ -136,11 +197,18 @@ int swaitmax = SWAITMAX; int swaitint = SWAITINT; #ifdef SETPROCTITLE +#ifdef OLD_SETPROCTITLE char **Argv = NULL; /* pointer to argument vector */ char *LastArgv = NULL; /* end of argv */ +#endif /* OLD_SETPROCTITLE */ char proctitle[LINE_MAX]; /* initial part of title */ #endif /* SETPROCTITLE */ +#ifdef SKEY +int pwok = 0; +char addr_string[20]; /* XXX */ +#endif + #define LOGCMD(cmd, file) \ if (logging > 1) \ syslog(LOG_INFO,"%s %s%s", cmd, \ @@ -160,9 +228,13 @@ char proctitle[LINE_MAX]; /* initial part of title */ cmd, (*(file) == '/') ? "" : curdir(), file, cnt); \ } +#ifdef VIRTUAL_HOSTING +static void inithosts __P((void)); +static void selecthost __P((struct in_addr *)); +#endif static void ack __P((char *)); static void myoob __P((int)); -static int checkuser __P((char *)); +static int checkuser __P((char *, char *)); static FILE *dataconn __P((char *, off_t, char *)); static void dolog __P((struct sockaddr_in *)); static char *curdir __P((void)); @@ -171,10 +243,12 @@ static FILE *getdatasock __P((char *)); static char *gunique __P((char *)); static void lostconn __P((int)); static int receive_data __P((FILE *, FILE *)); -static void send_data __P((FILE *, FILE *, off_t)); +static void send_data __P((FILE *, FILE *, off_t, off_t, int)); static struct passwd * sgetpwnam __P((char *)); static char *sgetsave __P((char *)); +static void reapchild __P((int)); +static void logxfer __P((char *, long, long)); static char * curdir() @@ -199,29 +273,9 @@ main(argc, argv, envp) char *cp, line[LINE_MAX]; FILE *fd; - /* - * LOG_NDELAY sets up the logging connection immediately, - * necessary for anonymous ftp's that chroot and can't do it later. - */ - openlog("ftpd", LOG_PID | LOG_NDELAY, LOG_FTP); - addrlen = sizeof(his_addr); - if (getpeername(0, (struct sockaddr *)&his_addr, &addrlen) < 0) { - syslog(LOG_ERR, "getpeername (%s): %m",argv[0]); - exit(1); - } - addrlen = sizeof(ctrl_addr); - if (getsockname(0, (struct sockaddr *)&ctrl_addr, &addrlen) < 0) { - syslog(LOG_ERR, "getsockname (%s): %m",argv[0]); - exit(1); - } -#ifdef IP_TOS - tos = IPTOS_LOWDELAY; - if (setsockopt(0, IPPROTO_IP, IP_TOS, (char *)&tos, sizeof(int)) < 0) - syslog(LOG_WARNING, "setsockopt (IP_TOS): %m"); -#endif - data_source.sin_port = htons(ntohs(ctrl_addr.sin_port) - 1); - debug = 0; -#ifdef SETPROCTITLE + tzset(); /* in case no timezone database in ~ftp */ + +#ifdef OLD_SETPROCTITLE /* * Save start and extent of argv for setproctitle. */ @@ -229,22 +283,30 @@ main(argc, argv, envp) while (*envp) envp++; LastArgv = envp[-1] + strlen(envp[-1]); -#endif /* SETPROCTITLE */ +#endif /* OLD_SETPROCTITLE */ + - while ((ch = getopt(argc, argv, "dlt:T:u:v")) != EOF) { + bind_address.s_addr = htonl(INADDR_ANY); + while ((ch = getopt(argc, argv, "AdlDSURt:T:u:va:p:")) != -1) { switch (ch) { + case 'D': + daemon_mode++; + break; + case 'd': - debug = 1; + debug++; break; case 'l': logging++; /* > 1 == extra logging */ break; - case 't': - timeout = atoi(optarg); - if (maxtimeout < timeout) - maxtimeout = timeout; + case 'R': + paranoid = 0; + break; + + case 'S': + stats++; break; case 'T': @@ -253,6 +315,25 @@ main(argc, argv, envp) timeout = maxtimeout; break; + case 't': + timeout = atoi(optarg); + if (maxtimeout < timeout) + maxtimeout = timeout; + break; + + case 'U': + restricted_data_ports = 0; + break; + + case 'a': + if (!inet_aton(optarg, &bind_address)) + errx(1, "invalid address for -a"); + break; + + case 'p': + pid_file = optarg; + break; + case 'u': { long val = 0; @@ -264,6 +345,9 @@ main(argc, argv, envp) defumask = val; break; } + case 'A': + anon_only = 1; + break; case 'v': debug = 1; @@ -274,12 +358,133 @@ main(argc, argv, envp) break; } } + +#ifdef VIRTUAL_HOSTING + inithosts(); +#endif (void) freopen(_PATH_DEVNULL, "w", stderr); - (void) signal(SIGPIPE, lostconn); + + /* + * LOG_NDELAY sets up the logging connection immediately, + * necessary for anonymous ftp's that chroot and can't do it later. + */ + openlog("ftpd", LOG_PID | LOG_NDELAY, LOG_FTP); + + if (daemon_mode) { + int ctl_sock, fd; + struct servent *sv; + + /* + * Detach from parent. + */ + if (daemon(1, 1) < 0) { + syslog(LOG_ERR, "failed to become a daemon"); + exit(1); + } + (void) signal(SIGCHLD, reapchild); + /* + * Get port number for ftp/tcp. + */ + sv = getservbyname("ftp", "tcp"); + if (sv == NULL) { + syslog(LOG_ERR, "getservbyname for ftp failed"); + exit(1); + } + /* + * Open a socket, bind it to the FTP port, and start + * listening. + */ + ctl_sock = socket(AF_INET, SOCK_STREAM, 0); + if (ctl_sock < 0) { + syslog(LOG_ERR, "control socket: %m"); + exit(1); + } + if (setsockopt(ctl_sock, SOL_SOCKET, SO_REUSEADDR, + (char *)&on, sizeof(on)) < 0) + syslog(LOG_ERR, "control setsockopt: %m");; + server_addr.sin_family = AF_INET; + server_addr.sin_addr = bind_address; + server_addr.sin_port = sv->s_port; + if (bind(ctl_sock, (struct sockaddr *)&server_addr, sizeof(server_addr))) { + syslog(LOG_ERR, "control bind: %m"); + exit(1); + } + if (listen(ctl_sock, 32) < 0) { + syslog(LOG_ERR, "control listen: %m"); + exit(1); + } + /* + * Atomically write process ID + */ + if (pid_file) + { + int fd; + char buf[20]; + + fd = open(pid_file, O_CREAT | O_WRONLY | O_TRUNC + | O_NONBLOCK | O_EXLOCK, 0644); + if (fd < 0) + if (errno == EAGAIN) + errx(1, "%s: file locked", pid_file); + else + err(1, "%s", pid_file); + snprintf(buf, sizeof(buf), + "%lu\n", (unsigned long) getpid()); + if (write(fd, buf, strlen(buf)) < 0) + err(1, "%s: write", pid_file); + /* Leave the pid file open and locked */ + } + /* + * Loop forever accepting connection requests and forking off + * children to handle them. + */ + while (1) { + addrlen = sizeof(his_addr); + fd = accept(ctl_sock, (struct sockaddr *)&his_addr, &addrlen); + if (fork() == 0) { + /* child */ + (void) dup2(fd, 0); + (void) dup2(fd, 1); + close(ctl_sock); + break; + } + close(fd); + } + } else { + addrlen = sizeof(his_addr); + if (getpeername(0, (struct sockaddr *)&his_addr, &addrlen) < 0) { + syslog(LOG_ERR, "getpeername (%s): %m",argv[0]); + exit(1); + } + } + (void) signal(SIGCHLD, SIG_IGN); + (void) signal(SIGPIPE, lostconn); if ((int)signal(SIGURG, myoob) < 0) syslog(LOG_ERR, "signal: %m"); +#ifdef SKEY + strncpy(addr_string, inet_ntoa(his_addr.sin_addr), sizeof(addr_string)); +#endif + addrlen = sizeof(ctrl_addr); + if (getsockname(0, (struct sockaddr *)&ctrl_addr, &addrlen) < 0) { + syslog(LOG_ERR, "getsockname (%s): %m",argv[0]); + exit(1); + } +#ifdef VIRTUAL_HOSTING + /* select our identity from virtual host table */ + selecthost(&ctrl_addr.sin_addr); +#endif +#ifdef IP_TOS + tos = IPTOS_LOWDELAY; + if (setsockopt(0, IPPROTO_IP, IP_TOS, (char *)&tos, sizeof(int)) < 0) + syslog(LOG_WARNING, "setsockopt (IP_TOS): %m"); +#endif + data_source.sin_port = htons(ntohs(ctrl_addr.sin_port) - 1); + + /* set this here so klogin can use it... */ + (void)sprintf(ttyline, "ftp%d", getpid()); + /* Try to handle urgent data inline */ #ifdef SO_OOBINLINE if (setsockopt(0, SOL_SOCKET, SO_OOBINLINE, (char *)&on, sizeof(on)) < 0) @@ -313,7 +518,11 @@ main(argc, argv, envp) reply(530, "System not available."); exit(0); } +#ifdef VIRTUAL_HOSTING + if ((fd = fopen(thishost->welcome, "r")) != NULL) { +#else if ((fd = fopen(_PATH_FTPWELCOME, "r")) != NULL) { +#endif while (fgets(line, sizeof(line), fd) != NULL) { if ((cp = strchr(line, '\n')) != NULL) *cp = '\0'; @@ -323,7 +532,11 @@ main(argc, argv, envp) (void) fclose(fd); /* reply(220,) must follow */ } - (void) gethostname(hostname, sizeof(hostname)); +#ifndef VIRTUAL_HOSTING + if ((hostname = malloc(MAXHOSTNAMELEN)) == NULL) + fatal("Ran out of memory."); + (void) gethostname(hostname, MAXHOSTNAMELEN); +#endif reply(220, "%s FTP server (%s) ready.", hostname, version); (void) setjmp(errcatch); for (;;) @@ -341,7 +554,146 @@ lostconn(signo) dologout(-1); } -static char ttyline[20]; +#ifdef VIRTUAL_HOSTING +/* + * read in virtual host tables (if they exist) + */ + +static void +inithosts() +{ + FILE *fp; + char *cp; + struct hostent *hp; + struct ftphost *hrp, *lhrp; + char line[1024]; + + /* + * Fill in the default host information + */ + if (gethostname(line, sizeof(line)) < 0) + line[0] = '\0'; + if ((hrp = malloc(sizeof(struct ftphost))) == NULL || + (hrp->hostname = strdup(line)) == NULL) + fatal("Ran out of memory."); + memset(&hrp->hostaddr, 0, sizeof hrp->hostaddr); + if ((hp = gethostbyname(hrp->hostname)) != NULL) + (void) memcpy(&hrp->hostaddr, + hp->h_addr_list[0], + sizeof(hrp->hostaddr)); + hrp->statfile = _PATH_FTPDSTATFILE; + hrp->welcome = _PATH_FTPWELCOME; + hrp->loginmsg = _PATH_FTPLOGINMESG; + hrp->anonuser = "ftp"; + hrp->next = NULL; + thishost = firsthost = lhrp = hrp; + if ((fp = fopen(_PATH_FTPHOSTS, "r")) != NULL) { + while (fgets(line, sizeof(line), fp) != NULL) { + int i; + + if ((cp = strchr(line, '\n')) == NULL) { + /* ignore long lines */ + while (fgets(line, sizeof(line), fp) != NULL && + strchr(line, '\n') == NULL) + ; + continue; + } + *cp = '\0'; + cp = strtok(line, " \t"); + /* skip comments and empty lines */ + if (cp == NULL || line[0] == '#') + continue; + /* first, try a standard gethostbyname() */ + if ((hp = gethostbyname(cp)) == NULL) + continue; + for (hrp = firsthost; hrp != NULL; hrp = hrp->next) { + if (memcmp(&hrp->hostaddr, + hp->h_addr_list[0], + sizeof(hrp->hostaddr)) == 0) + break; + } + if (hrp == NULL) { + if ((hrp = malloc(sizeof(struct ftphost))) == NULL) + continue; + /* defaults */ + hrp->statfile = _PATH_FTPDSTATFILE; + hrp->welcome = _PATH_FTPWELCOME; + hrp->loginmsg = _PATH_FTPLOGINMESG; + hrp->anonuser = "ftp"; + hrp->next = NULL; + lhrp->next = hrp; + lhrp = hrp; + } + (void) memcpy(&hrp->hostaddr, + hp->h_addr_list[0], + sizeof(hrp->hostaddr)); + /* + * determine hostname to use. + * force defined name if it is a valid alias + * otherwise fallback to primary hostname + */ + if ((hp = gethostbyaddr((char*)&hrp->hostaddr, + sizeof(hrp->hostaddr), + AF_INET)) != NULL) { + if (strcmp(cp, hp->h_name) != 0) { + if (hp->h_aliases == NULL) + cp = hp->h_name; + else { + i = 0; + while (hp->h_aliases[i] && + strcmp(cp, hp->h_aliases[i]) != 0) + ++i; + if (hp->h_aliases[i] == NULL) + cp = hp->h_name; + } + } + } + hrp->hostname = strdup(cp); + /* ok, now we now peel off the rest */ + i = 0; + while (i < 4 && (cp = strtok(NULL, " \t")) != NULL) { + if (*cp != '-' && (cp = strdup(cp)) != NULL) { + switch (i) { + case 0: /* anon user permissions */ + hrp->anonuser = cp; + break; + case 1: /* statistics file */ + hrp->statfile = cp; + break; + case 2: /* welcome message */ + hrp->welcome = cp; + break; + case 3: /* login message */ + hrp->loginmsg = cp; + break; + } + } + ++i; + } + } + (void) fclose(fp); + } +} + +static void +selecthost(a) + struct in_addr *a; +{ + struct ftphost *hrp; + + hrp = thishost = firsthost; /* default */ + while (hrp != NULL) { + if (memcmp(a, &hrp->hostaddr, sizeof(hrp->hostaddr)) == 0) { + thishost = hrp; + break; + } + hrp = hrp->next; + } + /* setup static variables as appropriate */ + hostname = thishost->hostname; + ftpuser = thishost->anonuser; +} +#endif /* * Helper function for sgetpwnam(). @@ -416,19 +768,27 @@ user(name) if (guest) { reply(530, "Can't change user from guest login."); return; + } else if (dochroot) { + reply(530, "Can't change user from chroot user."); + return; } end_login(); } guest = 0; if (strcmp(name, "ftp") == 0 || strcmp(name, "anonymous") == 0) { - if (checkuser("ftp") || checkuser("anonymous")) + if (checkuser(_PATH_FTPUSERS, "ftp") || + checkuser(_PATH_FTPUSERS, "anonymous")) reply(530, "User %s access denied.", name); +#ifdef VIRTUAL_HOSTING + else if ((pw = sgetpwnam(thishost->anonuser)) != NULL) { +#else else if ((pw = sgetpwnam("ftp")) != NULL) { +#endif guest = 1; askpasswd = 1; reply(331, - "Guest login ok, type your name as password."); + "Guest login ok, send your email address as password."); } else reply(530, "User %s unknown.", name); if (!askpasswd && logging) @@ -436,7 +796,12 @@ user(name) "ANONYMOUS FTP LOGIN REFUSED FROM %s", remotehost); return; } - if (pw = sgetpwnam(name)) { + if (anon_only != 0) { + reply(530, "Sorry, only anonymous ftp allowed."); + return; + } + + if ((pw = sgetpwnam(name))) { if ((shell = pw->pw_shell) == NULL || *shell == 0) shell = _PATH_BSHELL; while ((cp = getusershell()) != NULL) @@ -444,7 +809,7 @@ user(name) break; endusershell(); - if (cp == NULL || checkuser(name)) { + if (cp == NULL || checkuser(_PATH_FTPUSERS, name)) { reply(530, "User %s access denied.", name); if (logging) syslog(LOG_NOTICE, @@ -456,7 +821,12 @@ user(name) } if (logging) strncpy(curname, name, sizeof(curname)-1); +#ifdef SKEY + pwok = skeyaccess(name, NULL, remotehost, addr_string); + reply(331, "%s", skey_challenge(name, pw, pwok)); +#else reply(331, "Password required for %s.", name); +#endif askpasswd = 1; /* * Delay before reading passwd after first failed @@ -467,26 +837,42 @@ user(name) } /* - * Check if a user is in the file _PATH_FTPUSERS + * Check if a user is in the file "fname" */ static int -checkuser(name) +checkuser(fname, name) + char *fname; char *name; { FILE *fd; int found = 0; char *p, line[BUFSIZ]; - if ((fd = fopen(_PATH_FTPUSERS, "r")) != NULL) { - while (fgets(line, sizeof(line), fd) != NULL) + if ((fd = fopen(fname, "r")) != NULL) { + while (!found && fgets(line, sizeof(line), fd) != NULL) if ((p = strchr(line, '\n')) != NULL) { *p = '\0'; if (line[0] == '#') continue; - if (strcmp(line, name) == 0) { - found = 1; - break; + /* + * if first chr is '@', check group membership + */ + if (line[0] == '@') { + int i = 0; + struct group *grp; + + if ((grp = getgrnam(line+1)) == NULL) + continue; + while (!found && grp->gr_mem[i]) + found = strcmp(name, + grp->gr_mem[i++]) + == 0; } + /* + * Otherwise, just check for username match + */ + else + found = strcmp(line, name) == 0; } (void) fclose(fd); } @@ -505,16 +891,25 @@ end_login() if (logged_in) logwtmp(ttyline, "", ""); pw = NULL; +#ifdef LOGIN_CAP + setusercontext(NULL, getpwuid(0), (uid_t)0, + LOGIN_SETPRIORITY|LOGIN_SETRESOURCES|LOGIN_SETUMASK); +#endif logged_in = 0; guest = 0; + dochroot = 0; } void pass(passwd) char *passwd; { - char *salt, *xpasswd; + int rval; FILE *fd; +#ifdef LOGIN_CAP + login_cap_t *lc = NULL; +#endif + static char homedir[MAXPATHLEN]; if (logged_in || askpasswd == 0) { reply(503, "Login with USER first."); @@ -522,14 +917,33 @@ pass(passwd) } askpasswd = 0; if (!guest) { /* "ftp" is only account allowed no password */ - if (pw == NULL) - salt = "xx"; - else - salt = pw->pw_passwd; - xpasswd = crypt(passwd, salt); + if (pw == NULL) { + rval = 1; /* failure below */ + goto skip; + } +#if defined(KERBEROS) + rval = klogin(pw, "", hostname, passwd); + if (rval == 0) + goto skip; +#endif +#ifdef SKEY + rval = strcmp(skey_crypt(passwd, pw->pw_passwd, pw, pwok), + pw->pw_passwd); + pwok = 0; +#else + rval = strcmp(crypt(passwd, pw->pw_passwd), pw->pw_passwd); +#endif /* The strcmp does not catch null passwords! */ - if (pw == NULL || *pw->pw_passwd == '\0' || - strcmp(xpasswd, pw->pw_passwd)) { + if (*pw->pw_passwd == '\0' || + (pw->pw_expire && time(NULL) >= pw->pw_expire)) + rval = 1; /* failure */ +skip: + /* + * If rval == 1, the user failed the authentication check + * above. If rval == 0, either Kerberos or local authentication + * succeeded. + */ + if (rval) { reply(530, "Login incorrect."); if (logging) syslog(LOG_NOTICE, @@ -550,13 +964,52 @@ pass(passwd) reply(550, "Can't set gid."); return; } + /* May be overridden by login.conf */ + (void) umask(defumask); +#ifdef LOGIN_CAP + if ((lc = login_getpwclass(pw)) != NULL) { + char remote_ip[MAXHOSTNAMELEN]; + + strncpy(remote_ip, inet_ntoa(his_addr.sin_addr), + sizeof(remote_ip) - 1); + remote_ip[sizeof(remote_ip) - 1] = 0; + if (!auth_hostok(lc, remotehost, remote_ip)) { + syslog(LOG_INFO|LOG_AUTH, + "FTP LOGIN FAILED (HOST) as %s: permission denied.", + pw->pw_name); + reply(530, "Permission denied.\n"); + pw = NULL; + return; + } + if (!auth_timeok(lc, time(NULL))) { + reply(530, "Login not available right now.\n"); + pw = NULL; + return; + } + } + setusercontext(lc, pw, (uid_t)0, + LOGIN_SETGROUP|LOGIN_SETPRIORITY|LOGIN_SETRESOURCES|LOGIN_SETUMASK); +#else (void) initgroups(pw->pw_name, pw->pw_gid); +#endif /* open wtmp before chroot */ - (void)sprintf(ttyline, "ftp%d", getpid()); logwtmp(ttyline, pw->pw_name, remotehost); logged_in = 1; + if (guest && stats && statfd < 0) +#ifdef VIRTUAL_HOSTING + if ((statfd = open(thishost->statfile, O_WRONLY|O_APPEND)) < 0) +#else + if ((statfd = open(_PATH_FTPDSTATFILE, O_WRONLY|O_APPEND)) < 0) +#endif + stats = 0; + + dochroot = +#ifdef LOGIN_CAP /* Allow login.conf configuration as well */ + login_getcapbool(lc, "ftp-chroot", 0) || +#endif + checkuser(_PATH_FTPCHROOT, pw->pw_name); if (guest) { /* * We MUST do a chdir() after the chroot. Otherwise @@ -567,6 +1020,11 @@ pass(passwd) reply(550, "Can't set guest privileges."); goto bad; } + } else if (dochroot) { + if (chroot(pw->pw_dir) < 0 || chdir("/") < 0) { + reply(550, "Can't change root."); + goto bad; + } } else if (chdir(pw->pw_dir) < 0) { if (chdir("/") < 0) { reply(530, "User %s: can't change directory to %s.", @@ -579,11 +1037,22 @@ pass(passwd) reply(550, "Can't set uid."); goto bad; } + + /* + * Set home directory so that use of ~ (tilde) works correctly. + */ + if (getcwd(homedir, MAXPATHLEN) != NULL) + setenv("HOME", homedir, 1); + /* * Display a login message, if it exists. * N.B. reply(230,) must follow the message. */ +#ifdef VIRTUAL_HOSTING + if ((fd = fopen(thishost->loginmsg, "r")) != NULL) { +#else if ((fd = fopen(_PATH_FTPLOGINMESG, "r")) != NULL) { +#endif char *cp, line[LINE_MAX]; while (fgets(line, sizeof(line), fd) != NULL) { @@ -595,32 +1064,56 @@ pass(passwd) (void) fclose(fd); } if (guest) { + if (ident != NULL) + free(ident); + ident = strdup(passwd); + if (ident == NULL) + fatal("Ran out of memory."); + reply(230, "Guest login ok, access restrictions apply."); #ifdef SETPROCTITLE - snprintf(proctitle, sizeof(proctitle), - "%s: anonymous/%.*s", remotehost, - sizeof(proctitle) - sizeof(remotehost) - - sizeof(": anonymous/"), passwd); - setproctitle(proctitle); +#ifdef VIRTUAL_HOSTING + if (thishost != firsthost) + snprintf(proctitle, sizeof(proctitle), + "%s: anonymous(%s)/%.*s", remotehost, hostname, + sizeof(proctitle) - sizeof(remotehost) - + sizeof(": anonymous/"), passwd); + else +#endif + snprintf(proctitle, sizeof(proctitle), + "%s: anonymous/%.*s", remotehost, + sizeof(proctitle) - sizeof(remotehost) - + sizeof(": anonymous/"), passwd); + setproctitle("%s", proctitle); #endif /* SETPROCTITLE */ if (logging) syslog(LOG_INFO, "ANONYMOUS FTP LOGIN FROM %s, %s", remotehost, passwd); } else { + if (dochroot) + reply(230, "User %s logged in, access restrictions apply.", + pw->pw_name); + else reply(230, "User %s logged in.", pw->pw_name); + #ifdef SETPROCTITLE snprintf(proctitle, sizeof(proctitle), - "%s: %s", remotehost, pw->pw_name); - setproctitle(proctitle); + "%s: %s", remotehost, pw->pw_name); + setproctitle("%s", proctitle); #endif /* SETPROCTITLE */ if (logging) syslog(LOG_INFO, "FTP LOGIN FROM %s as %s", remotehost, pw->pw_name); } - (void) umask(defumask); +#ifdef LOGIN_CAP + login_close(lc); +#endif return; bad: /* Forget all about it... */ +#ifdef LOGIN_CAP + login_close(lc); +#endif end_login(); } @@ -631,6 +1124,7 @@ retrieve(cmd, name) FILE *fin, *dout; struct stat st; int (*closefunc) __P((FILE *)); + long start; if (cmd == 0) { fin = fopen(name, "r"), closefunc = fclose; @@ -680,7 +1174,11 @@ retrieve(cmd, name) dout = dataconn(name, st.st_size, "w"); if (dout == NULL) goto done; - send_data(fin, dout, st.st_blksize); + time(&start); + send_data(fin, dout, st.st_blksize, st.st_size, + restart_point == 0 && cmd == 0 && S_ISREG(st.st_mode)); + if (cmd == 0 && guest && stats) + logxfer(name, st.st_size, start); (void) fclose(dout); data = -1; pdata = -1; @@ -699,7 +1197,7 @@ store(name, mode, unique) struct stat st; int (*closefunc) __P((FILE *)); - if (unique && stat(name, &st) == 0 && + if ((unique || guest) && stat(name, &st) == 0 && (name = gunique(name)) == NULL) { LOGCMD(*mode == 'w' ? "put" : "append", name); return; @@ -778,6 +1276,7 @@ getdatasock(mode) (char *) &on, sizeof(on)) < 0) goto bad; /* anchor socket to avoid multi-homing problems */ + data_source.sin_len = sizeof(struct sockaddr_in); data_source.sin_family = AF_INET; data_source.sin_addr = ctrl_addr.sin_addr; for (tries = 1; ; tries++) { @@ -794,6 +1293,23 @@ getdatasock(mode) if (setsockopt(s, IPPROTO_IP, IP_TOS, (char *)&on, sizeof(int)) < 0) syslog(LOG_WARNING, "setsockopt (IP_TOS): %m"); #endif +#ifdef TCP_NOPUSH + /* + * Turn off push flag to keep sender TCP from sending short packets + * at the boundaries of each write(). Should probably do a SO_SNDBUF + * to set the send buffer size as well, but that may not be desirable + * in heavy-load situations. + */ + on = 1; + if (setsockopt(s, IPPROTO_TCP, TCP_NOPUSH, (char *)&on, sizeof on) < 0) + syslog(LOG_WARNING, "setsockopt (TCP_NOPUSH): %m"); +#endif +#ifdef SO_SNDBUF + on = 65536; + if (setsockopt(s, SOL_SOCKET, SO_SNDBUF, (char *)&on, sizeof on) < 0) + syslog(LOG_WARNING, "setsockopt (SO_SNDBUF): %m"); +#endif + return (fdopen(s, mode)); bad: /* Return the real value of errno (close may change it) */ @@ -823,9 +1339,17 @@ dataconn(name, size, mode) if (pdata >= 0) { struct sockaddr_in from; int s, fromlen = sizeof(from); + struct timeval timeout; + fd_set set; + + FD_ZERO(&set); + FD_SET(pdata, &set); - s = accept(pdata, (struct sockaddr *)&from, &fromlen); - if (s < 0) { + timeout.tv_usec = 0; + timeout.tv_sec = 120; + + if (select(pdata+1, &set, (fd_set *) 0, (fd_set *) 0, &timeout) == 0 || + (s = accept(pdata, (struct sockaddr *) &from, &fromlen)) < 0) { reply(425, "Can't open data connection."); (void) close(pdata); pdata = -1; @@ -834,7 +1358,7 @@ dataconn(name, size, mode) (void) close(pdata); pdata = s; #ifdef IP_TOS - tos = IPTOS_LOWDELAY; + tos = IPTOS_THROUGHPUT; (void) setsockopt(s, IPPROTO_IP, IP_TOS, (char *)&tos, sizeof(int)); #endif @@ -878,17 +1402,20 @@ dataconn(name, size, mode) /* * Tranfer the contents of "instr" to "outstr" peer using the appropriate - * encapsulation of the data subject * to Mode, Structure, and Type. + * encapsulation of the data subject to Mode, Structure, and Type. * * NB: Form isn't handled. */ static void -send_data(instr, outstr, blksize) +send_data(instr, outstr, blksize, filesize, isreg) FILE *instr, *outstr; off_t blksize; + off_t filesize; + int isreg; { int c, cnt, filefd, netfd; - char *buf; + char *buf, *bp; + size_t len; transflag++; if (setjmp(urgcatch)) { @@ -918,13 +1445,45 @@ send_data(instr, outstr, blksize) case TYPE_I: case TYPE_L: + /* + * isreg is only set if we are not doing restart and we + * are sending a regular file + */ + netfd = fileno(outstr); + filefd = fileno(instr); + + if (isreg && filesize < (off_t)16 * 1024 * 1024) { + buf = mmap(0, filesize, PROT_READ, MAP_SHARED, filefd, + (off_t)0); + if (buf == MAP_FAILED) { + syslog(LOG_WARNING, "mmap(%lu): %m", + (unsigned long)filesize); + goto oldway; + } + bp = buf; + len = filesize; + do { + cnt = write(netfd, bp, len); + len -= cnt; + bp += cnt; + if (cnt > 0) byte_count += cnt; + } while(cnt > 0 && len > 0); + + transflag = 0; + munmap(buf, (size_t)filesize); + if (cnt < 0) + goto data_err; + reply(226, "Transfer complete."); + return; + } + +oldway: if ((buf = malloc((u_int)blksize)) == NULL) { transflag = 0; perror_reply(451, "Local resource failure: malloc"); return; } - netfd = fileno(outstr); - filefd = fileno(instr); + while ((cnt = read(filefd, buf, (u_int)blksize)) > 0 && write(netfd, buf, cnt) == cnt) byte_count += cnt; @@ -964,7 +1523,7 @@ receive_data(instr, outstr) FILE *instr, *outstr; { int c; - int cnt, bare_lfs = 0; + int cnt, bare_lfs; char buf[BUFSIZ]; transflag++; @@ -972,6 +1531,9 @@ receive_data(instr, outstr) transflag = 0; return (-1); } + + bare_lfs = 0; + switch (type) { case TYPE_I: @@ -1046,7 +1608,7 @@ statfilecmd(filename) int c; char line[LINE_MAX]; - (void)snprintf(line, sizeof(line), "/bin/ls -lgA %s", filename); + (void)snprintf(line, sizeof(line), _PATH_LS " -lgA %s", filename); fin = ftpd_popen(line, "r"); lreply(211, "status of %s:", filename); while ((c = getc(fin)) != EOF) { @@ -1209,7 +1771,7 @@ yyerror(s) { char *cp; - if (cp = strchr(cbuf,'\n')) + if ((cp = strchr(cbuf,'\n'))) *cp = '\0'; reply(500, "'%s': command not understood.", cbuf); } @@ -1304,8 +1866,15 @@ void renamecmd(from, to) char *from, *to; { + struct stat st; LOGCMD2("rename", from, to); + + if (guest && (stat(to, &st) == 0)) { + reply(550, "%s: permission denied", to); + return; + } + if (rename(from, to) < 0) perror_reply(550, "rename"); else @@ -1325,12 +1894,26 @@ dolog(sin) (void) strncpy(remotehost, inet_ntoa(sin->sin_addr), sizeof(remotehost)); #ifdef SETPROCTITLE - snprintf(proctitle, sizeof(proctitle), "%s: connected", remotehost); - setproctitle(proctitle); +#ifdef VIRTUAL_HOSTING + if (thishost != firsthost) + snprintf(proctitle, sizeof(proctitle), "%s: connected (to %s)", + remotehost, hostname); + else +#endif + snprintf(proctitle, sizeof(proctitle), "%s: connected", + remotehost); + setproctitle("%s", proctitle); #endif /* SETPROCTITLE */ - if (logging) - syslog(LOG_INFO, "connection from %s", remotehost); + if (logging) { +#ifdef VIRTUAL_HOSTING + if (thishost != firsthost) + syslog(LOG_INFO, "connection from %s (to %s)", + remotehost, hostname); + else +#endif + syslog(LOG_INFO, "connection from %s", remotehost); + } } /* @@ -1341,10 +1924,19 @@ void dologout(status) int status; { + /* + * Prevent reception of SIGURG from resulting in a resumption + * back to the main program loop. + */ + transflag = 0; if (logged_in) { (void) seteuid((uid_t)0); logwtmp(ttyline, "", ""); +#if defined(KERBEROS) + if (!notickets && krbtkfile_env) + unlink(krbtkfile_env); +#endif } /* beware of flushing buffers after a SIGPIPE */ _exit(status); @@ -1392,19 +1984,36 @@ passive() int len; char *p, *a; + if (pdata >= 0) /* close old port if one set */ + close(pdata); + pdata = socket(AF_INET, SOCK_STREAM, 0); if (pdata < 0) { perror_reply(425, "Can't open passive connection"); return; } + + (void) seteuid((uid_t)0); + +#ifdef IP_PORTRANGE + { + int on = restricted_data_ports ? IP_PORTRANGE_HIGH + : IP_PORTRANGE_DEFAULT; + + if (setsockopt(pdata, IPPROTO_IP, IP_PORTRANGE, + (char *)&on, sizeof(on)) < 0) + goto pasv_error; + } +#endif + pasv_addr = ctrl_addr; pasv_addr.sin_port = 0; - (void) seteuid((uid_t)0); - if (bind(pdata, (struct sockaddr *)&pasv_addr, sizeof(pasv_addr)) < 0) { - (void) seteuid((uid_t)pw->pw_uid); + if (bind(pdata, (struct sockaddr *)&pasv_addr, + sizeof(pasv_addr)) < 0) goto pasv_error; - } + (void) seteuid((uid_t)pw->pw_uid); + len = sizeof(pasv_addr); if (getsockname(pdata, (struct sockaddr *) &pasv_addr, &len) < 0) goto pasv_error; @@ -1420,6 +2029,7 @@ passive() return; pasv_error: + (void) seteuid((uid_t)pw->pw_uid); (void) close(pdata); pdata = -1; perror_reply(425, "Can't open passive connection"); @@ -1449,7 +2059,7 @@ gunique(local) } if (cp) *cp = '/'; - (void) strcpy(new, local); + (void) snprintf(new, sizeof(new), "%s", local); cp = new + strlen(new); *cp++ = '.'; for (count = 1; count < 100; count++) { @@ -1515,7 +2125,7 @@ send_file_list(whichf) transflag = 0; goto out; } - while (dirname = *dirlist++) { + while ((dirname = *dirlist++)) { if (stat(dirname, &st) < 0) { /* * If user typed "ls -l", etc, and the client @@ -1523,7 +2133,7 @@ send_file_list(whichf) */ if (dirname[0] == '-' && *dirlist == NULL && transflag == 0) { - retrieve("/bin/ls %s", dirname); + retrieve(_PATH_LS " %s", dirname); goto out; } perror_reply(550, whichf); @@ -1608,7 +2218,14 @@ out: } } -#ifdef SETPROCTITLE +void +reapchild(signo) + int signo; +{ + while (wait3(NULL, WNOHANG, NULL) > 0); +} + +#ifdef OLD_SETPROCTITLE /* * Clobber argv so ps will show what we're doing. (Stolen from sendmail.) * Warning, since this is usually started from inetd.conf, it often doesn't @@ -1651,4 +2268,23 @@ setproctitle(fmt, va_alist) while (p < LastArgv) *p++ = ' '; } -#endif /* SETPROCTITLE */ +#endif /* OLD_SETPROCTITLE */ + +static void +logxfer(name, size, start) + char *name; + long size; + long start; +{ + char buf[1024]; + char path[MAXPATHLEN + 1]; + long now; + + if (statfd >= 0 && getwd(path) != NULL) { + time(&now); + snprintf(buf, sizeof(buf), "%.20s!%s!%s!%s/%s!%ld!%ld\n", + ctime(&now)+4, ident, remotehost, + path, name, size, now - start + (now == start)); + write(statfd, buf, strlen(buf)); + } +} diff --git a/libexec/ftpd/logwtmp.c b/libexec/ftpd/logwtmp.c index d40840c..9562795 100644 --- a/libexec/ftpd/logwtmp.c +++ b/libexec/ftpd/logwtmp.c @@ -30,6 +30,7 @@ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * + * $Id$ */ #ifndef lint @@ -37,10 +38,13 @@ static char sccsid[] = "@(#)logwtmp.c 8.1 (Berkeley) 6/4/93"; #endif /* not lint */ #include <sys/types.h> -#include <sys/time.h> #include <sys/stat.h> +#include <netinet/in.h> +#include <arpa/inet.h> #include <fcntl.h> +#include <time.h> +#include <netdb.h> #include <utmp.h> #include <unistd.h> #include <stdio.h> @@ -61,6 +65,18 @@ logwtmp(line, name, host) struct utmp ut; struct stat buf; + if (strlen(host) > UT_HOSTSIZE) { + struct hostent *hp = gethostbyname(host); + + if (hp != NULL) { + struct in_addr in; + + memmove(&in, hp->h_addr, sizeof(in)); + host = inet_ntoa(in); + } else + host = "invalid hostname"; + } + if (fd < 0 && (fd = open(_PATH_WTMP, O_WRONLY|O_APPEND, 0)) < 0) return; if (fstat(fd, &buf) == 0) { diff --git a/libexec/ftpd/pathnames.h b/libexec/ftpd/pathnames.h index 2a50063..d2f8b73 100644 --- a/libexec/ftpd/pathnames.h +++ b/libexec/ftpd/pathnames.h @@ -31,10 +31,14 @@ * SUCH DAMAGE. * * @(#)pathnames.h 8.1 (Berkeley) 6/4/93 + * $Id: pathnames.h,v 1.9 1997/04/26 12:12:10 davidn Exp $ */ #include <paths.h> -#define _PATH_FTPUSERS "/etc/ftpusers" +#define _PATH_FTPCHROOT "/etc/ftpchroot" #define _PATH_FTPWELCOME "/etc/ftpwelcome" -#define _PATH_FTPLOGINMESG "/etc/motd" +#define _PATH_FTPLOGINMESG "/etc/ftpmotd" +#define _PATH_FTPHOSTS "/etc/ftphosts" +#define _PATH_FTPDSTATFILE "/var/log/ftpd" +#define _PATH_LS "/bin/ls" diff --git a/libexec/ftpd/popen.c b/libexec/ftpd/popen.c index b26732e..ac0b76a 100644 --- a/libexec/ftpd/popen.c +++ b/libexec/ftpd/popen.c @@ -33,11 +33,14 @@ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * + * $Id: popen.c,v 1.7 1997/02/22 14:21:31 peter Exp $ */ +#if 0 #ifndef lint static char sccsid[] = "@(#)popen.c 8.3 (Berkeley) 4/6/94"; #endif /* not lint */ +#endif #include <sys/types.h> #include <sys/wait.h> @@ -51,6 +54,12 @@ static char sccsid[] = "@(#)popen.c 8.3 (Berkeley) 4/6/94"; #include <unistd.h> #include "extern.h" +#ifdef INTERNAL_LS +#include "pathnames.h" +#endif + +#define MAXUSRARGS 100 +#define MAXGLOBARGS 1000 /* * Special version of popen which avoids call to shell. This ensures noone @@ -67,9 +76,9 @@ ftpd_popen(program, type) char *cp; FILE *iop; int argc, gargc, pdes[2], pid; - char **pop, *argv[100], *gargv[1000]; + char **pop, *argv[MAXUSRARGS], *gargv[MAXGLOBARGS]; - if (*type != 'r' && *type != 'w' || type[1]) + if (((*type != 'r') && (*type != 'w')) || type[1]) return (NULL); if (!pids) { @@ -83,13 +92,13 @@ ftpd_popen(program, type) return (NULL); /* break up string into pieces */ - for (argc = 0, cp = program;; cp = NULL) + for (argc = 0, cp = program; argc < MAXUSRARGS; cp = NULL) if (!(argv[argc++] = strtok(cp, " \t\n"))) break; /* glob each piece */ gargv[0] = argv[0]; - for (gargc = argc = 1; argv[argc]; argc++) { + for (gargc = argc = 1; argv[argc] && gargc < (MAXGLOBARGS-1); argc++) { glob_t gl; int flags = GLOB_BRACE|GLOB_NOCHECK|GLOB_QUOTE|GLOB_TILDE; @@ -97,14 +106,21 @@ ftpd_popen(program, type) if (glob(argv[argc], flags, NULL, &gl)) gargv[gargc++] = strdup(argv[argc]); else - for (pop = gl.gl_pathv; *pop; pop++) + for (pop = gl.gl_pathv; *pop && gargc < (MAXGLOBARGS-1); + pop++) gargv[gargc++] = strdup(*pop); globfree(&gl); } gargv[gargc] = NULL; iop = NULL; - switch(pid = vfork()) { +#ifdef INTERNAL_LS + fflush(NULL); + pid = (strcmp(gargv[0], _PATH_LS) == 0) ? fork() : vfork(); +#else + pid = vfork(); +#endif + switch(pid) { case -1: /* error */ (void)close(pdes[0]); (void)close(pdes[1]); @@ -125,6 +141,14 @@ ftpd_popen(program, type) } (void)close(pdes[1]); } +#ifdef INTERNAL_LS + if (strcmp(gargv[0], _PATH_LS) == 0) { + extern int optreset; + /* Reset getopt for ls_main() */ + optreset = optind = optopt = 1; + exit(ls_main(gargc, gargv)); + } +#endif execv(gargv[0], gargv); _exit(1); } diff --git a/libexec/ftpd/skey-stuff.c b/libexec/ftpd/skey-stuff.c new file mode 100644 index 0000000..b6aba40 --- /dev/null +++ b/libexec/ftpd/skey-stuff.c @@ -0,0 +1,29 @@ +/* Author: Wietse Venema, Eindhoven University of Technology. + * + * $Id$ + */ + +#include <stdio.h> +#include <string.h> +#include <pwd.h> + +#include <skey.h> + +/* skey_challenge - additional password prompt stuff */ + +char *skey_challenge(name, pwd, pwok) +char *name; +struct passwd *pwd; +int pwok; +{ + static char buf[128]; + struct skey skey; + + /* Display s/key challenge where appropriate. */ + + if (pwd == NULL || skeychallenge(&skey, pwd->pw_name, buf)) + sprintf(buf, "Password required for %s.", name); + else if (!pwok) + strcat(buf, " (s/key required)"); + return (buf); +} |