BSD 4_4 release
[unix-history] / usr / src / libexec / ftpd / ftpd.c
index b5e8573..30c26ca 100644 (file)
@@ -1,6 +1,45 @@
+/*
+ * Copyright (c) 1985, 1988, 1990, 1992, 1993
+ *     The Regents of the University of California.  All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ *    must display the following acknowledgement:
+ *     This product includes software developed by the University of
+ *     California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
 #ifndef lint
 #ifndef lint
-static char sccsid[] = "@(#)ftpd.c     4.33 (Berkeley) %G%";
-#endif
+static char copyright[] =
+"@(#) Copyright (c) 1985, 1988, 1990, 1992, 1993\n\
+       The Regents of the University of California.  All rights reserved.\n";
+#endif /* not lint */
+
+#ifndef lint
+static char sccsid[] = "@(#)ftpd.c     8.1 (Berkeley) 6/4/93";
+#endif /* not lint */
 
 /*
  * FTP server.
 
 /*
  * FTP server.
@@ -9,59 +48,77 @@ static       char sccsid[] = "@(#)ftpd.c     4.33 (Berkeley) %G%";
 #include <sys/stat.h>
 #include <sys/ioctl.h>
 #include <sys/socket.h>
 #include <sys/stat.h>
 #include <sys/ioctl.h>
 #include <sys/socket.h>
-#include <sys/file.h>
 #include <sys/wait.h>
 
 #include <netinet/in.h>
 #include <sys/wait.h>
 
 #include <netinet/in.h>
+#include <netinet/in_systm.h>
+#include <netinet/ip.h>
 
 
+#define        FTP_NAMES
 #include <arpa/ftp.h>
 #include <arpa/inet.h>
 #include <arpa/ftp.h>
 #include <arpa/inet.h>
+#include <arpa/telnet.h>
 
 
-#include <stdio.h>
 #include <signal.h>
 #include <signal.h>
+#include <dirent.h>
+#include <fcntl.h>
+#include <time.h>
 #include <pwd.h>
 #include <setjmp.h>
 #include <netdb.h>
 #include <errno.h>
 #include <pwd.h>
 #include <setjmp.h>
 #include <netdb.h>
 #include <errno.h>
+#include <syslog.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <ctype.h>
+#include <stdlib.h>
+#include <string.h>
+#include "pathnames.h"
+#include "extern.h"
+
+#if __STDC__
+#include <stdarg.h>
+#else
+#include <varargs.h>
+#endif
 
 
-/*
- * File containing login names
- * NOT to be used on this machine.
- * Commonly used to disallow uucp.
- */
-#define        FTPUSERS        "/etc/ftpusers"
-
-extern int errno;
-extern char *sys_errlist[];
-extern char *crypt();
-extern char version[];
+extern off_t restart_point;
 extern char *home;             /* pointer to home directory for glob */
 extern char *home;             /* pointer to home directory for glob */
-extern FILE *popen(), *fopen();
-extern int pclose(), fclose();
+extern char cbuf[];
+extern char version[];
 
 struct sockaddr_in ctrl_addr;
 struct sockaddr_in data_source;
 struct sockaddr_in data_dest;
 struct sockaddr_in his_addr;
 
 struct sockaddr_in ctrl_addr;
 struct sockaddr_in data_source;
 struct sockaddr_in data_dest;
 struct sockaddr_in his_addr;
-
-struct hostent *hp;
+struct sockaddr_in pasv_addr;
 
 int    data;
 
 int    data;
-jmp_buf        errcatch;
+jmp_buf        errcatch, urgcatch;
 int    logged_in;
 struct passwd *pw;
 int    debug;
 int    logged_in;
 struct passwd *pw;
 int    debug;
-int    timeout;
+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    guest;
 int    logging;
 int    guest;
-int    wtmp;
 int    type;
 int    form;
 int    stru;                   /* avoid C keyword */
 int    mode;
 int    usedefault = 1;         /* for data transfers */
 int    type;
 int    form;
 int    stru;                   /* avoid C keyword */
 int    mode;
 int    usedefault = 1;         /* for data transfers */
-char   hostname[32];
-char   remotehost[32];
+int    pdata = -1;             /* for passive mode */
+int    transflag;
+off_t  file_size;
+off_t  byte_count;
+#if !defined(CMASK) || CMASK == 0
+#undef CMASK
+#define CMASK 027
+#endif
+int    defumask = CMASK;               /* default umask value */
+char   tmpline[7];
+char   hostname[MAXHOSTNAMELEN];
+char   remotehost[MAXHOSTNAMELEN];
 
 /*
  * Timeout intervals for retrying connections
 
 /*
  * Timeout intervals for retrying connections
@@ -74,31 +131,104 @@ char      remotehost[32];
 int    swaitmax = SWAITMAX;
 int    swaitint = SWAITINT;
 
 int    swaitmax = SWAITMAX;
 int    swaitint = SWAITINT;
 
-int    lostconn();
-int    reapchild();
-FILE   *getdatasock(), *dataconn();
+#ifdef SETPROCTITLE
+char   **Argv = NULL;          /* pointer to argument vector */
+char   *LastArgv = NULL;       /* end of argv */
+char   proctitle[BUFSIZ];      /* initial part of title */
+#endif /* SETPROCTITLE */
+
+#define MAXLINE         256
+
+#define LOGCMD(cmd, file) \
+       if (logging > 1) \
+           syslog(LOG_INFO,"%s %s%s", cmd, \
+               *(file) == '/' ? "" : curdir(), file);
+#define LOGCMD2(cmd, file1, file2) \
+        if (logging > 1) \
+           syslog(LOG_INFO,"%s %s%s %s%s", cmd, \
+               *(file1) == '/' ? "" : curdir(), file1, \
+               *(file2) == '/' ? "" : curdir(), file2);
+#define LOGBYTES(cmd, file, cnt) \
+       if (logging > 1) { \
+               if (cnt == (off_t)-1) \
+                   syslog(LOG_INFO,"%s %s%s", cmd, \
+                       *(file) == '/' ? "" : curdir(), file); \
+               else \
+                   syslog(LOG_INFO, "%s %s%s = %qd bytes", \
+                       cmd, (*(file) == '/') ? "" : curdir(), file, cnt); \
+       }
 
 
-main(argc, argv)
+static void     ack __P((char *));
+static void     myoob __P((int));
+static int      checkuser __P((char *));
+static FILE    *dataconn __P((char *, off_t, char *));
+static void     dolog __P((struct sockaddr_in *));
+static char    *curdir __P((void));
+static void     end_login __P((void));
+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 struct passwd *
+                sgetpwnam __P((char *));
+static char    *sgetsave __P((char *));
+
+static char *
+curdir()
+{
+       static char path[MAXPATHLEN+1+1];       /* path + '/' + '\0' */
+
+       if (getcwd(path, sizeof(path)-2) == NULL)
+               return ("");
+       if (path[1] != '\0')            /* special case for root dir. */
+               strcat(path, "/");
+       /* For guest account, skip / since it's chrooted */
+       return (guest ? path+1 : path);
+}
+
+int
+main(argc, argv, envp)
        int argc;
        char *argv[];
        int argc;
        char *argv[];
+       char **envp;
 {
 {
-       int options = 0, addrlen;
-       char *cp;
+       int addrlen, on = 1, tos;
+       char *cp, line[MAXLINE];
+       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);
        addrlen = sizeof (his_addr);
-       if (getpeername(0, &his_addr, &addrlen) < 0) {
-               fprintf(stderr, "%s: ", argv[0]);
-               perror("getpeername");
+       if (getpeername(0, (struct sockaddr *)&his_addr, &addrlen) < 0) {
+               syslog(LOG_ERR, "getpeername (%s): %m",argv[0]);
                exit(1);
        }
        addrlen = sizeof (ctrl_addr);
                exit(1);
        }
        addrlen = sizeof (ctrl_addr);
-       if (getsockname(0, &ctrl_addr, &addrlen) < 0) {
-               fprintf(stderr, "%s: ", argv[0]);
-               perror("getsockname");
+       if (getsockname(0, (struct sockaddr *)&ctrl_addr, &addrlen) < 0) {
+               syslog(LOG_ERR, "getsockname (%s): %m",argv[0]);
                exit(1);
        }
                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;
        data_source.sin_port = htons(ntohs(ctrl_addr.sin_port) - 1);
        debug = 0;
+#ifdef SETPROCTITLE
+       /*
+        *  Save start and extent of argv for setproctitle.
+        */
+       Argv = argv;
+       while (*envp)
+               envp++;
+       LastArgv = envp[-1] + strlen(envp[-1]);
+#endif /* SETPROCTITLE */
+
        argc--, argv++;
        while (argc > 0 && *argv[0] == '-') {
                for (cp = &argv[0][1]; *cp; cp++) switch (*cp) {
        argc--, argv++;
        while (argc > 0 && *argv[0] == '-') {
                for (cp = &argv[0][1]; *cp; cp++) switch (*cp) {
@@ -109,17 +239,36 @@ main(argc, argv)
 
                case 'd':
                        debug = 1;
 
                case 'd':
                        debug = 1;
-                       options |= SO_DEBUG;
                        break;
 
                case 'l':
                        break;
 
                case 'l':
-                       logging = 1;
+                       logging++;      /* > 1 == extra logging */
                        break;
 
                case 't':
                        timeout = atoi(++cp);
                        break;
 
                case 't':
                        timeout = atoi(++cp);
+                       if (maxtimeout < timeout)
+                               maxtimeout = timeout;
                        goto nextopt;
                        goto nextopt;
-                       break;
+
+               case 'T':
+                       maxtimeout = atoi(++cp);
+                       if (timeout > maxtimeout)
+                               timeout = maxtimeout;
+                       goto nextopt;
+
+               case 'u':
+                   {
+                       int val = 0;
+
+                       while (*++cp && *cp >= '0' && *cp <= '9')
+                               val = val*8 + *cp - '0';
+                       if (*cp)
+                               fprintf(stderr, "ftpd: Bad value for -u\n");
+                       else
+                               defumask = val;
+                       goto nextopt;
+                   }
 
                default:
                        fprintf(stderr, "ftpd: Unknown flag -%c ignored.\n",
 
                default:
                        fprintf(stderr, "ftpd: Unknown flag -%c ignored.\n",
@@ -129,119 +278,359 @@ main(argc, argv)
 nextopt:
                argc--, argv++;
        }
 nextopt:
                argc--, argv++;
        }
-       signal(SIGPIPE, lostconn);
-       signal(SIGCHLD, SIG_IGN);
+       (void) freopen(_PATH_DEVNULL, "w", stderr);
+       (void) signal(SIGPIPE, lostconn);
+       (void) signal(SIGCHLD, SIG_IGN);
+       if ((int)signal(SIGURG, myoob) < 0)
+               syslog(LOG_ERR, "signal: %m");
+
+       /* Try to handle urgent data inline */
+#ifdef SO_OOBINLINE
+       if (setsockopt(0, SOL_SOCKET, SO_OOBINLINE, (char *)&on, sizeof(on)) < 0)
+               syslog(LOG_ERR, "setsockopt: %m");
+#endif
+
+#ifdef F_SETOWN
+       if (fcntl(fileno(stdin), F_SETOWN, getpid()) == -1)
+               syslog(LOG_ERR, "fcntl F_SETOWN: %m");
+#endif
        dolog(&his_addr);
        dolog(&his_addr);
-       /* do telnet option negotiation here */
        /*
         * Set up default state
         */
        /*
         * Set up default state
         */
-       logged_in = 0;
        data = -1;
        type = TYPE_A;
        form = FORM_N;
        stru = STRU_F;
        mode = MODE_S;
        data = -1;
        type = TYPE_A;
        form = FORM_N;
        stru = STRU_F;
        mode = MODE_S;
-       gethostname(hostname, sizeof (hostname));
-       reply(220, "%s FTP server (%s) ready.",
-               hostname, version);
-       for (;;) {
-               setjmp(errcatch);
-               yyparse();
+       tmpline[0] = '\0';
+
+       /* If logins are disabled, print out the message. */
+       if ((fd = fopen(_PATH_NOLOGIN,"r")) != NULL) {
+               while (fgets(line, sizeof (line), fd) != NULL) {
+                       if ((cp = strchr(line, '\n')) != NULL)
+                               *cp = '\0';
+                       lreply(530, "%s", line);
+               }
+               (void) fflush(stdout);
+               (void) fclose(fd);
+               reply(530, "System not available.");
+               exit(0);
+       }
+       if ((fd = fopen(_PATH_FTPWELCOME, "r")) != NULL) {
+               while (fgets(line, sizeof (line), fd) != NULL) {
+                       if ((cp = strchr(line, '\n')) != NULL)
+                               *cp = '\0';
+                       lreply(220, "%s", line);
+               }
+               (void) fflush(stdout);
+               (void) fclose(fd);
+               /* reply(220,) must follow */
        }
        }
+       (void) gethostname(hostname, sizeof (hostname));
+       reply(220, "%s FTP server (%s) ready.", hostname, version);
+       (void) setjmp(errcatch);
+       for (;;)
+               (void) yyparse();
+       /* NOTREACHED */
 }
 
 }
 
-reapchild()
+static void
+lostconn(signo)
+       int signo;
 {
 {
-       union wait status;
+       if (debug)
+               syslog(LOG_DEBUG, "lost connection");
+       dologout(-1);
+}
+
+static char ttyline[20];
 
 
-       while (wait3(&status, WNOHANG, 0) > 0)
-               ;
+/*
+ * Helper function for sgetpwnam().
+ */
+static char *
+sgetsave(s)
+       char *s;
+{
+       char *new = malloc((unsigned) strlen(s) + 1);
+
+       if (new == NULL) {
+               perror_reply(421, "Local resource failure: malloc");
+               dologout(1);
+               /* NOTREACHED */
+       }
+       (void) strcpy(new, s);
+       return (new);
 }
 
 }
 
-lostconn()
+/*
+ * Save the result of a getpwnam.  Used for USER command, since
+ * the data returned must not be clobbered by any other command
+ * (e.g., globbing).
+ */
+static struct passwd *
+sgetpwnam(name)
+       char *name;
 {
 {
+       static struct passwd save;
+       register struct passwd *p;
+       char *sgetsave();
+
+       if ((p = getpwnam(name)) == NULL)
+               return (p);
+       if (save.pw_name) {
+               free(save.pw_name);
+               free(save.pw_passwd);
+               free(save.pw_gecos);
+               free(save.pw_dir);
+               free(save.pw_shell);
+       }
+       save = *p;
+       save.pw_name = sgetsave(p->pw_name);
+       save.pw_passwd = sgetsave(p->pw_passwd);
+       save.pw_gecos = sgetsave(p->pw_gecos);
+       save.pw_dir = sgetsave(p->pw_dir);
+       save.pw_shell = sgetsave(p->pw_shell);
+       return (&save);
+}
 
 
-       if (debug)
-               fprintf(stderr, "Lost connection.\n");
-       dologout(-1);
+static int login_attempts;     /* number of failed login attempts */
+static int askpasswd;          /* had user command, ask for passwd */
+static char curname[10];       /* current USER name */
+
+/*
+ * USER command.
+ * Sets global passwd pointer pw if named account exists and is acceptable;
+ * sets askpasswd if a PASS command is expected.  If logged in previously,
+ * need to reset state.  If name is "ftp" or "anonymous", the name is not in
+ * _PATH_FTPUSERS, and ftp account exists, set guest and pw, then just return.
+ * If account doesn't exist, ask for passwd anyway.  Otherwise, check user
+ * requesting login privileges.  Disallow anyone who does not have a standard
+ * shell as returned by getusershell().  Disallow anyone mentioned in the file
+ * _PATH_FTPUSERS to allow people such as root and uucp to be avoided.
+ */
+void
+user(name)
+       char *name;
+{
+       register char *cp;
+       char *shell;
+
+       if (logged_in) {
+               if (guest) {
+                       reply(530, "Can't change user from guest login.");
+                       return;
+               }
+               end_login();
+       }
+
+       guest = 0;
+       if (strcmp(name, "ftp") == 0 || strcmp(name, "anonymous") == 0) {
+               if (checkuser("ftp") || checkuser("anonymous"))
+                       reply(530, "User %s access denied.", name);
+               else if ((pw = sgetpwnam("ftp")) != NULL) {
+                       guest = 1;
+                       askpasswd = 1;
+                       reply(331,
+                           "Guest login ok, type your name as password.");
+               } else
+                       reply(530, "User %s unknown.", name);
+               if (!askpasswd && logging)
+                       syslog(LOG_NOTICE,
+                           "ANONYMOUS FTP LOGIN REFUSED FROM %s", remotehost);
+               return;
+       }
+       if (pw = sgetpwnam(name)) {
+               if ((shell = pw->pw_shell) == NULL || *shell == 0)
+                       shell = _PATH_BSHELL;
+               while ((cp = getusershell()) != NULL)
+                       if (strcmp(cp, shell) == 0)
+                               break;
+               endusershell();
+
+               if (cp == NULL || checkuser(name)) {
+                       reply(530, "User %s access denied.", name);
+                       if (logging)
+                               syslog(LOG_NOTICE,
+                                   "FTP LOGIN REFUSED FROM %s, %s",
+                                   remotehost, name);
+                       pw = (struct passwd *) NULL;
+                       return;
+               }
+       }
+       if (logging)
+               strncpy(curname, name, sizeof(curname)-1);
+       reply(331, "Password required for %s.", name);
+       askpasswd = 1;
+       /*
+        * Delay before reading passwd after first failed
+        * attempt to slow down passwd-guessing programs.
+        */
+       if (login_attempts)
+               sleep((unsigned) login_attempts);
 }
 
 }
 
+/*
+ * Check if a user is in the file _PATH_FTPUSERS
+ */
+static int
+checkuser(name)
+       char *name;
+{
+       register FILE *fd;
+       register char *p;
+       char line[BUFSIZ];
+       int found = 0;
+
+       if ((fd = fopen(_PATH_FTPUSERS, "r")) != NULL) {
+               while (fgets(line, sizeof(line), fd) != NULL)
+                       if ((p = strchr(line, '\n')) != NULL) {
+                               *p = '\0';
+                               if (line[0] == '#')
+                                       continue;
+                               if (strcmp(p, name) == 0) {
+                                       found = 1;
+                                       break;
+                               }
+                       }
+               (void) fclose(fd);
+       }
+       return (found);
+}
+
+/*
+ * Terminate login as previous user, if any, resetting state;
+ * used when USER command is given or login fails.
+ */
+static void
+end_login()
+{
+
+       (void) seteuid((uid_t)0);
+       if (logged_in)
+               logwtmp(ttyline, "", "");
+       pw = NULL;
+       logged_in = 0;
+       guest = 0;
+}
+
+void
 pass(passwd)
        char *passwd;
 {
 pass(passwd)
        char *passwd;
 {
-       char *xpasswd, *savestr();
-       static struct passwd save;
+       char *xpasswd, *salt;
+       FILE *fd;
 
 
-       if (logged_in || pw == NULL) {
+       if (logged_in || askpasswd == 0) {
                reply(503, "Login with USER first.");
                return;
        }
                reply(503, "Login with USER first.");
                return;
        }
+       askpasswd = 0;
        if (!guest) {           /* "ftp" is only account allowed no password */
        if (!guest) {           /* "ftp" is only account allowed no password */
-               xpasswd = crypt(passwd, pw->pw_passwd);
+               if (pw == NULL)
+                       salt = "xx";
+               else
+                       salt = pw->pw_passwd;
+               xpasswd = crypt(passwd, salt);
                /* The strcmp does not catch null passwords! */
                /* The strcmp does not catch null passwords! */
-               if (*pw->pw_passwd == '\0' || strcmp(xpasswd, pw->pw_passwd)) {
+               if (pw == NULL || *pw->pw_passwd == '\0' ||
+                   strcmp(xpasswd, pw->pw_passwd)) {
                        reply(530, "Login incorrect.");
                        reply(530, "Login incorrect.");
+                       if (logging)
+                               syslog(LOG_NOTICE,
+                                   "FTP LOGIN FAILED FROM %s, %s",
+                                   remotehost, curname);
                        pw = NULL;
                        pw = NULL;
+                       if (login_attempts++ >= 5) {
+                               syslog(LOG_NOTICE,
+                                   "repeated login failures from %s",
+                                   remotehost);
+                               exit(0);
+                       }
                        return;
                }
        }
                        return;
                }
        }
-       setegid(pw->pw_gid);
-       initgroups(pw->pw_name, pw->pw_gid);
-       if (chdir(pw->pw_dir)) {
-               reply(550, "User %s: can't change directory to $s.",
-                       pw->pw_name, pw->pw_dir);
-               goto bad;
+       login_attempts = 0;             /* this time successful */
+       if (setegid((gid_t)pw->pw_gid) < 0) {
+               reply(550, "Can't set gid.");
+               return;
        }
        }
+       (void) initgroups(pw->pw_name, pw->pw_gid);
 
 
-       /* grab wtmp before chroot */
-       wtmp = open("/usr/adm/wtmp", O_WRONLY|O_APPEND);
-       if (guest && chroot(pw->pw_dir) < 0) {
-               reply(550, "Can't set guest privileges.");
-               if (wtmp >= 0) {
-                       (void) close(wtmp);
-                       wtmp = -1;
+       /* open wtmp before chroot */
+       (void)sprintf(ttyline, "ftp%d", getpid());
+       logwtmp(ttyline, pw->pw_name, remotehost);
+       logged_in = 1;
+
+       if (guest) {
+               /*
+                * We MUST do a chdir() after the chroot. Otherwise
+                * the old current directory will be accessible as "."
+                * outside the new root!
+                */
+               if (chroot(pw->pw_dir) < 0 || chdir("/") < 0) {
+                       reply(550, "Can't set guest privileges.");
+                       goto bad;
                }
                }
+       } else if (chdir(pw->pw_dir) < 0) {
+               if (chdir("/") < 0) {
+                       reply(530, "User %s: can't change directory to %s.",
+                           pw->pw_name, pw->pw_dir);
+                       goto bad;
+               } else
+                       lreply(230, "No directory! Logging in with home=/");
+       }
+       if (seteuid((uid_t)pw->pw_uid) < 0) {
+               reply(550, "Can't set uid.");
                goto bad;
        }
                goto bad;
        }
-       if (!guest)
-               reply(230, "User %s logged in.", pw->pw_name);
-       else
-               reply(230, "Guest login ok, access restrictions apply.");
-       logged_in = 1;
-       dologin(pw);
-       seteuid(pw->pw_uid);
        /*
        /*
-        * Save everything so globbing doesn't
-        * clobber the fields.
+        * Display a login message, if it exists.
+        * N.B. reply(230,) must follow the message.
         */
         */
-       save = *pw;
-       save.pw_name = savestr(pw->pw_name);
-       save.pw_passwd = savestr(pw->pw_passwd);
-       save.pw_comment = savestr(pw->pw_comment);
-       save.pw_gecos = savestr(pw->pw_gecos, &save.pw_gecos);
-       save.pw_dir = savestr(pw->pw_dir);
-       save.pw_shell = savestr(pw->pw_shell);
-       pw = &save;
-       home = pw->pw_dir;              /* home dir for globbing */
+       if ((fd = fopen(_PATH_FTPLOGINMESG, "r")) != NULL) {
+               char *cp, line[MAXLINE];
+
+               while (fgets(line, sizeof (line), fd) != NULL) {
+                       if ((cp = strchr(line, '\n')) != NULL)
+                               *cp = '\0';
+                       lreply(230, "%s", line);
+               }
+               (void) fflush(stdout);
+               (void) fclose(fd);
+       }
+       if (guest) {
+               reply(230, "Guest login ok, access restrictions apply.");
+#ifdef SETPROCTITLE
+               sprintf(proctitle, "%s: anonymous/%.*s", remotehost,
+                   sizeof(proctitle) - sizeof(remotehost) -
+                   sizeof(": anonymous/"), passwd);
+               setproctitle(proctitle);
+#endif /* SETPROCTITLE */
+               if (logging)
+                       syslog(LOG_INFO, "ANONYMOUS FTP LOGIN FROM %s, %s",
+                           remotehost, passwd);
+               home = "/";             /* guest home dir for globbing */
+       } else {
+               reply(230, "User %s logged in.", pw->pw_name);
+#ifdef SETPROCTITLE
+               sprintf(proctitle, "%s: %s", remotehost, pw->pw_name);
+               setproctitle(proctitle);
+#endif /* SETPROCTITLE */
+               if (logging)
+                       syslog(LOG_INFO, "FTP LOGIN FROM %s as %s",
+                           remotehost, pw->pw_name);
+               home = pw->pw_dir;      /* home dir for globbing */
+       }
+       (void) umask(defumask);
        return;
 bad:
        return;
 bad:
-       seteuid(0);
-       pw = NULL;
-}
-
-char *
-savestr(s)
-       char *s;
-{
-       char *malloc();
-       char *new = malloc(strlen(s) + 1);
-       
-       if (new != NULL)
-               strcpy(new, s);
-       return (new);
+       /* Forget all about it... */
+       end_login();
 }
 
 }
 
+void
 retrieve(cmd, name)
        char *cmd, *name;
 {
 retrieve(cmd, name)
        char *cmd, *name;
 {
@@ -250,107 +639,178 @@ retrieve(cmd, name)
        int (*closefunc)();
 
        if (cmd == 0) {
        int (*closefunc)();
 
        if (cmd == 0) {
-#ifdef notdef
-               /* no remote command execution -- it's a security hole */
-               if (*name == '|')
-                       fin = popen(name + 1, "r"), closefunc = pclose;
-               else
-#endif
-                       fin = fopen(name, "r"), closefunc = fclose;
+               fin = fopen(name, "r"), closefunc = fclose;
+               st.st_size = 0;
        } else {
                char line[BUFSIZ];
 
        } else {
                char line[BUFSIZ];
 
-               sprintf(line, cmd, name), name = line;
-               fin = popen(line, "r"), closefunc = pclose;
+               (void) sprintf(line, cmd, name), name = line;
+               fin = ftpd_popen(line, "r"), closefunc = ftpd_pclose;
+               st.st_size = -1;
+               st.st_blksize = BUFSIZ;
        }
        if (fin == NULL) {
        }
        if (fin == NULL) {
-               if (errno != 0)
-                       reply(550, "%s: %s.", name, sys_errlist[errno]);
+               if (errno != 0) {
+                       perror_reply(550, name);
+                       if (cmd == 0) {
+                               LOGCMD("get", name);
+                       }
+               }
                return;
        }
                return;
        }
-       st.st_size = 0;
+       byte_count = -1;
        if (cmd == 0 &&
        if (cmd == 0 &&
-           (stat(name, &st) < 0 || (st.st_mode&S_IFMT) != S_IFREG)) {
+           (fstat(fileno(fin), &st) < 0 || (st.st_mode&S_IFMT) != S_IFREG)) {
                reply(550, "%s: not a plain file.", name);
                goto done;
        }
                reply(550, "%s: not a plain file.", name);
                goto done;
        }
+       if (restart_point) {
+               if (type == TYPE_A) {
+                       register int i, n, c;
+
+                       n = restart_point;
+                       i = 0;
+                       while (i++ < n) {
+                               if ((c=getc(fin)) == EOF) {
+                                       perror_reply(550, name);
+                                       goto done;
+                               }
+                               if (c == '\n')
+                                       i++;
+                       }
+               } else if (lseek(fileno(fin), restart_point, L_SET) < 0) {
+                       perror_reply(550, name);
+                       goto done;
+               }
+       }
        dout = dataconn(name, st.st_size, "w");
        if (dout == NULL)
                goto done;
        dout = dataconn(name, st.st_size, "w");
        if (dout == NULL)
                goto done;
-       if (send_data(fin, dout) || ferror(dout))
-               reply(550, "%s: %s.", name, sys_errlist[errno]);
-       else
-               reply(226, "Transfer complete.");
-       fclose(dout), data = -1;
+       send_data(fin, dout, st.st_blksize);
+       (void) fclose(dout);
+       data = -1;
+       pdata = -1;
 done:
 done:
+       if (cmd == 0)
+               LOGBYTES("get", name, byte_count);
        (*closefunc)(fin);
 }
 
        (*closefunc)(fin);
 }
 
-store(name, mode)
+void
+store(name, mode, unique)
        char *name, *mode;
        char *name, *mode;
+       int unique;
 {
        FILE *fout, *din;
 {
        FILE *fout, *din;
-       int (*closefunc)(), dochown = 0;
-
-#ifdef notdef
-       /* no remote command execution -- it's a security hole */
-       if (name[0] == '|')
-               fout = popen(&name[1], "w"), closefunc = pclose;
-       else
-#endif
-       {
-               struct stat st;
+       struct stat st;
+       int (*closefunc)();
 
 
-               if (stat(name, &st) < 0)
-                       dochown++;
-               fout = fopen(name, mode), closefunc = fclose;
+       if (unique && stat(name, &st) == 0 &&
+           (name = gunique(name)) == NULL) {
+               LOGCMD(*mode == 'w' ? "put" : "append", name);
+               return;
        }
        }
+
+       if (restart_point)
+               mode = "r+w";
+       fout = fopen(name, mode);
+       closefunc = fclose;
        if (fout == NULL) {
        if (fout == NULL) {
-               reply(550, "%s: %s.", name, sys_errlist[errno]);
+               perror_reply(553, name);
+               LOGCMD(*mode == 'w' ? "put" : "append", name);
                return;
        }
                return;
        }
+       byte_count = -1;
+       if (restart_point) {
+               if (type == TYPE_A) {
+                       register int i, n, c;
+
+                       n = restart_point;
+                       i = 0;
+                       while (i++ < n) {
+                               if ((c=getc(fout)) == EOF) {
+                                       perror_reply(550, name);
+                                       goto done;
+                               }
+                               if (c == '\n')
+                                       i++;
+                       }
+                       /*
+                        * We must do this seek to "current" position
+                        * because we are changing from reading to
+                        * writing.
+                        */
+                       if (fseek(fout, 0L, L_INCR) < 0) {
+                               perror_reply(550, name);
+                               goto done;
+                       }
+               } else if (lseek(fileno(fout), restart_point, L_SET) < 0) {
+                       perror_reply(550, name);
+                       goto done;
+               }
+       }
        din = dataconn(name, (off_t)-1, "r");
        if (din == NULL)
                goto done;
        din = dataconn(name, (off_t)-1, "r");
        if (din == NULL)
                goto done;
-       if (receive_data(din, fout) || ferror(fout))
-               reply(550, "%s: %s.", name, sys_errlist[errno]);
-       else
-               reply(226, "Transfer complete.");
-       fclose(din), data = -1;
+       if (receive_data(din, fout) == 0) {
+               if (unique)
+                       reply(226, "Transfer complete (unique file name:%s).",
+                           name);
+               else
+                       reply(226, "Transfer complete.");
+       }
+       (void) fclose(din);
+       data = -1;
+       pdata = -1;
 done:
 done:
-       if (dochown)
-               (void) chown(name, pw->pw_uid, -1);
+       LOGBYTES(*mode == 'w' ? "put" : "append", name, byte_count);
        (*closefunc)(fout);
 }
 
        (*closefunc)(fout);
 }
 
-FILE *
+static FILE *
 getdatasock(mode)
        char *mode;
 {
 getdatasock(mode)
        char *mode;
 {
-       int s, on = 1;
+       int s, on = 1, tries;
+       int t;
 
        if (data >= 0)
                return (fdopen(data, mode));
 
        if (data >= 0)
                return (fdopen(data, mode));
+       (void) seteuid((uid_t)0);
        s = socket(AF_INET, SOCK_STREAM, 0);
        if (s < 0)
        s = socket(AF_INET, SOCK_STREAM, 0);
        if (s < 0)
-               return (NULL);
-       seteuid(0);
-       if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &on, sizeof (on)) < 0)
+               goto bad;
+       if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR,
+           (char *) &on, sizeof (on)) < 0)
                goto bad;
        /* anchor socket to avoid multi-homing problems */
        data_source.sin_family = AF_INET;
        data_source.sin_addr = ctrl_addr.sin_addr;
                goto bad;
        /* anchor socket to avoid multi-homing problems */
        data_source.sin_family = AF_INET;
        data_source.sin_addr = ctrl_addr.sin_addr;
-       if (bind(s, &data_source, sizeof (data_source), 0) < 0)
-               goto bad;
-       seteuid(pw->pw_uid);
+       for (tries = 1; ; tries++) {
+               if (bind(s, (struct sockaddr *)&data_source,
+                   sizeof (data_source)) >= 0)
+                       break;
+               if (errno != EADDRINUSE || tries > 10)
+                       goto bad;
+               sleep(tries);
+       }
+       (void) seteuid((uid_t)pw->pw_uid);
+#ifdef IP_TOS
+       on = IPTOS_THROUGHPUT;
+       if (setsockopt(s, IPPROTO_IP, IP_TOS, (char *)&on, sizeof(int)) < 0)
+               syslog(LOG_WARNING, "setsockopt (IP_TOS): %m");
+#endif
        return (fdopen(s, mode));
 bad:
        return (fdopen(s, mode));
 bad:
-       seteuid(pw->pw_uid);
-       close(s);
+       /* Return the real value of errno (close may change it) */
+       t = errno;
+       (void) seteuid((uid_t)pw->pw_uid);
+       (void) close(s);
+       errno = t;
        return (NULL);
 }
 
        return (NULL);
 }
 
-FILE *
+static FILE *
 dataconn(name, size, mode)
        char *name;
        off_t size;
 dataconn(name, size, mode)
        char *name;
        off_t size;
@@ -358,14 +818,38 @@ dataconn(name, size, mode)
 {
        char sizebuf[32];
        FILE *file;
 {
        char sizebuf[32];
        FILE *file;
-       int retry = 0;
+       int retry = 0, tos;
 
 
-       if (size >= 0)
-               sprintf (sizebuf, " (%ld bytes)", size);
+       file_size = size;
+       byte_count = 0;
+       if (size != (off_t) -1)
+               (void) sprintf (sizebuf, " (%qd bytes)", size);
        else
                (void) strcpy(sizebuf, "");
        else
                (void) strcpy(sizebuf, "");
+       if (pdata >= 0) {
+               struct sockaddr_in from;
+               int s, fromlen = sizeof(from);
+
+               s = accept(pdata, (struct sockaddr *)&from, &fromlen);
+               if (s < 0) {
+                       reply(425, "Can't open data connection.");
+                       (void) close(pdata);
+                       pdata = -1;
+                       return(NULL);
+               }
+               (void) close(pdata);
+               pdata = s;
+#ifdef IP_TOS
+               tos = IPTOS_LOWDELAY;
+               (void) setsockopt(s, IPPROTO_IP, IP_TOS, (char *)&tos,
+                   sizeof(int));
+#endif
+               reply(150, "Opening %s mode data connection for '%s'%s.",
+                    type == TYPE_A ? "ASCII" : "BINARY", name, sizebuf);
+               return(fdopen(pdata, mode));
+       }
        if (data >= 0) {
        if (data >= 0) {
-               reply(125, "Using existing data connection for %s%s.",
+               reply(125, "Using existing data connection for '%s'%s.",
                    name, sizebuf);
                usedefault = 1;
                return (fdopen(data, mode));
                    name, sizebuf);
                usedefault = 1;
                return (fdopen(data, mode));
@@ -377,260 +861,428 @@ dataconn(name, size, mode)
        if (file == NULL) {
                reply(425, "Can't create data socket (%s,%d): %s.",
                    inet_ntoa(data_source.sin_addr),
        if (file == NULL) {
                reply(425, "Can't create data socket (%s,%d): %s.",
                    inet_ntoa(data_source.sin_addr),
-                   ntohs(data_source.sin_port),
-                   sys_errlist[errno]);
+                   ntohs(data_source.sin_port), strerror(errno));
                return (NULL);
        }
                return (NULL);
        }
-       reply(150, "Opening data connection for %s (%s,%d)%s.",
-           name, inet_ntoa(data_dest.sin_addr.s_addr),
-           ntohs(data_dest.sin_port), sizebuf);
        data = fileno(file);
        data = fileno(file);
-       while (connect(data, &data_dest, sizeof (data_dest), 0) < 0) {
+       while (connect(data, (struct sockaddr *)&data_dest,
+           sizeof (data_dest)) < 0) {
                if (errno == EADDRINUSE && retry < swaitmax) {
                if (errno == EADDRINUSE && retry < swaitmax) {
-                       sleep(swaitint);
+                       sleep((unsigned) swaitint);
                        retry += swaitint;
                        continue;
                }
                        retry += swaitint;
                        continue;
                }
-               reply(425, "Can't build data connection: %s.",
-                   sys_errlist[errno]);
+               perror_reply(425, "Can't build data connection");
                (void) fclose(file);
                data = -1;
                return (NULL);
        }
                (void) fclose(file);
                data = -1;
                return (NULL);
        }
+       reply(150, "Opening %s mode data connection for '%s'%s.",
+            type == TYPE_A ? "ASCII" : "BINARY", name, sizebuf);
        return (file);
 }
 
 /*
        return (file);
 }
 
 /*
- * Tranfer the contents of "instr" to
- * "outstr" peer using the appropriate
- * encapulation of the date subject
- * to Mode, Structure, and Type.
+ * Tranfer the contents of "instr" to "outstr" peer using the appropriate
+ * encapsulation of the data subject * to Mode, Structure, and Type.
  *
  * NB: Form isn't handled.
  */
  *
  * NB: Form isn't handled.
  */
-send_data(instr, outstr)
+static void
+send_data(instr, outstr, blksize)
        FILE *instr, *outstr;
        FILE *instr, *outstr;
+       off_t blksize;
 {
 {
-       register int c;
-       int netfd, filefd, cnt;
-       char buf[BUFSIZ];
+       register int c, cnt;
+       register char *buf;
+       int netfd, filefd;
 
 
+       transflag++;
+       if (setjmp(urgcatch)) {
+               transflag = 0;
+               return;
+       }
        switch (type) {
 
        case TYPE_A:
                while ((c = getc(instr)) != EOF) {
        switch (type) {
 
        case TYPE_A:
                while ((c = getc(instr)) != EOF) {
+                       byte_count++;
                        if (c == '\n') {
                        if (c == '\n') {
-                               if (ferror (outstr))
-                                       return (1);
-                               putc('\r', outstr);
+                               if (ferror(outstr))
+                                       goto data_err;
+                               (void) putc('\r', outstr);
                        }
                        }
-                       putc(c, outstr);
-                       if (c == '\r')
-                               putc ('\0', outstr);
+                       (void) putc(c, outstr);
                }
                }
-               if (ferror (instr) || ferror (outstr))
-                       return (1);
-               return (0);
-               
+               fflush(outstr);
+               transflag = 0;
+               if (ferror(instr))
+                       goto file_err;
+               if (ferror(outstr))
+                       goto data_err;
+               reply(226, "Transfer complete.");
+               return;
+
        case TYPE_I:
        case TYPE_L:
        case TYPE_I:
        case TYPE_L:
+               if ((buf = malloc((u_int)blksize)) == NULL) {
+                       transflag = 0;
+                       perror_reply(451, "Local resource failure: malloc");
+                       return;
+               }
                netfd = fileno(outstr);
                filefd = fileno(instr);
                netfd = fileno(outstr);
                filefd = fileno(instr);
-
-               while ((cnt = read(filefd, buf, sizeof (buf))) > 0)
-                       if (write(netfd, buf, cnt) < 0)
-                               return (1);
-               return (cnt < 0);
+               while ((cnt = read(filefd, buf, (u_int)blksize)) > 0 &&
+                   write(netfd, buf, cnt) == cnt)
+                       byte_count += cnt;
+               transflag = 0;
+               (void)free(buf);
+               if (cnt != 0) {
+                       if (cnt < 0)
+                               goto file_err;
+                       goto data_err;
+               }
+               reply(226, "Transfer complete.");
+               return;
+       default:
+               transflag = 0;
+               reply(550, "Unimplemented TYPE %d in send_data", type);
+               return;
        }
        }
-       reply(504,"Unimplemented TYPE %d in send_data", type);
-       return (1);
+
+data_err:
+       transflag = 0;
+       perror_reply(426, "Data connection");
+       return;
+
+file_err:
+       transflag = 0;
+       perror_reply(551, "Error on input file");
 }
 
 /*
 }
 
 /*
- * Transfer data from peer to
- * "outstr" using the appropriate
- * encapulation of the data subject
- * to Mode, Structure, and Type.
+ * Transfer data from peer to "outstr" using the appropriate encapulation of
+ * the data subject to Mode, Structure, and Type.
  *
  * N.B.: Form isn't handled.
  */
  *
  * N.B.: Form isn't handled.
  */
+static int
 receive_data(instr, outstr)
        FILE *instr, *outstr;
 {
        register int c;
 receive_data(instr, outstr)
        FILE *instr, *outstr;
 {
        register int c;
-       int cnt;
+       int cnt, bare_lfs = 0;
        char buf[BUFSIZ];
 
        char buf[BUFSIZ];
 
-
+       transflag++;
+       if (setjmp(urgcatch)) {
+               transflag = 0;
+               return (-1);
+       }
        switch (type) {
 
        case TYPE_I:
        case TYPE_L:
        switch (type) {
 
        case TYPE_I:
        case TYPE_L:
-               while ((cnt = read(fileno(instr), buf, sizeof buf)) > 0)
-                       if (write(fileno(outstr), buf, cnt) < 0)
-                               return (1);
-               return (cnt < 0);
+               while ((cnt = read(fileno(instr), buf, sizeof buf)) > 0) {
+                       if (write(fileno(outstr), buf, cnt) != cnt)
+                               goto file_err;
+                       byte_count += cnt;
+               }
+               if (cnt < 0)
+                       goto data_err;
+               transflag = 0;
+               return (0);
 
        case TYPE_E:
 
        case TYPE_E:
-               reply(504, "TYPE E not implemented.");
-               return (1);
+               reply(553, "TYPE E not implemented.");
+               transflag = 0;
+               return (-1);
 
        case TYPE_A:
                while ((c = getc(instr)) != EOF) {
 
        case TYPE_A:
                while ((c = getc(instr)) != EOF) {
-                       if (c == '\r') {
-                               if (ferror (outstr))
-                                       return (1);
-                               if ((c = getc(instr)) != '\n')
-                                       putc ('\r', outstr);
-                               if (c == '\0')
-                                       continue;
+                       byte_count++;
+                       if (c == '\n')
+                               bare_lfs++;
+                       while (c == '\r') {
+                               if (ferror(outstr))
+                                       goto data_err;
+                               if ((c = getc(instr)) != '\n') {
+                                       (void) putc ('\r', outstr);
+                                       if (c == '\0' || c == EOF)
+                                               goto contin2;
+                               }
                        }
                        }
-                       putc (c, outstr);
+                       (void) putc(c, outstr);
+       contin2:        ;
+               }
+               fflush(outstr);
+               if (ferror(instr))
+                       goto data_err;
+               if (ferror(outstr))
+                       goto file_err;
+               transflag = 0;
+               if (bare_lfs) {
+                       lreply(230, "WARNING! %d bare linefeeds received in ASCII mode", bare_lfs);
+                       printf("   File may not have transferred correctly.\r\n");
                }
                }
-               if (ferror (instr) || ferror (outstr))
-                       return (1);
                return (0);
                return (0);
+       default:
+               reply(550, "Unimplemented TYPE %d in receive_data", type);
+               transflag = 0;
+               return (-1);
+       }
+
+data_err:
+       transflag = 0;
+       perror_reply(426, "Data Connection");
+       return (-1);
+
+file_err:
+       transflag = 0;
+       perror_reply(452, "Error writing file");
+       return (-1);
+}
+
+void
+statfilecmd(filename)
+       char *filename;
+{
+       char line[BUFSIZ];
+       FILE *fin;
+       int c;
+
+       (void)snprintf(line, sizeof(line), "/bin/ls -lgA %s", filename);
+       fin = ftpd_popen(line, "r");
+       lreply(211, "status of %s:", filename);
+       while ((c = getc(fin)) != EOF) {
+               if (c == '\n') {
+                       if (ferror(stdout)){
+                               perror_reply(421, "control connection");
+                               (void) ftpd_pclose(fin);
+                               dologout(1);
+                               /* NOTREACHED */
+                       }
+                       if (ferror(fin)) {
+                               perror_reply(551, filename);
+                               (void) ftpd_pclose(fin);
+                               return;
+                       }
+                       (void) putc('\r', stdout);
+               }
+               (void) putc(c, stdout);
        }
        }
-       fatal("Unknown type in receive_data.");
-       /*NOTREACHED*/
+       (void) ftpd_pclose(fin);
+       reply(211, "End of Status");
+}
+
+void
+statcmd()
+{
+       struct sockaddr_in *sin;
+       u_char *a, *p;
+
+       lreply(211, "%s FTP server status:", hostname, version);
+       printf("     %s\r\n", version);
+       printf("     Connected to %s", remotehost);
+       if (!isdigit(remotehost[0]))
+               printf(" (%s)", inet_ntoa(his_addr.sin_addr));
+       printf("\r\n");
+       if (logged_in) {
+               if (guest)
+                       printf("     Logged in anonymously\r\n");
+               else
+                       printf("     Logged in as %s\r\n", pw->pw_name);
+       } else if (askpasswd)
+               printf("     Waiting for password\r\n");
+       else
+               printf("     Waiting for user name\r\n");
+       printf("     TYPE: %s", typenames[type]);
+       if (type == TYPE_A || type == TYPE_E)
+               printf(", FORM: %s", formnames[form]);
+       if (type == TYPE_L)
+#if NBBY == 8
+               printf(" %d", NBBY);
+#else
+               printf(" %d", bytesize);        /* need definition! */
+#endif
+       printf("; STRUcture: %s; transfer MODE: %s\r\n",
+           strunames[stru], modenames[mode]);
+       if (data != -1)
+               printf("     Data connection open\r\n");
+       else if (pdata != -1) {
+               printf("     in Passive mode");
+               sin = &pasv_addr;
+               goto printaddr;
+       } else if (usedefault == 0) {
+               printf("     PORT");
+               sin = &data_dest;
+printaddr:
+               a = (u_char *) &sin->sin_addr;
+               p = (u_char *) &sin->sin_port;
+#define UC(b) (((int) b) & 0xff)
+               printf(" (%d,%d,%d,%d,%d,%d)\r\n", UC(a[0]),
+                       UC(a[1]), UC(a[2]), UC(a[3]), UC(p[0]), UC(p[1]));
+#undef UC
+       } else
+               printf("     No data connection\r\n");
+       reply(211, "End of status");
 }
 
 }
 
+void
 fatal(s)
        char *s;
 {
        reply(451, "Error in server: %s\n", s);
        reply(221, "Closing connection due to server error.");
        dologout(0);
 fatal(s)
        char *s;
 {
        reply(451, "Error in server: %s\n", s);
        reply(221, "Closing connection due to server error.");
        dologout(0);
+       /* NOTREACHED */
 }
 
 }
 
-reply(n, s, args)
+void
+#if __STDC__
+reply(int n, const char *fmt, ...)
+#else
+reply(n, fmt, va_alist)
        int n;
        int n;
-       char *s;
+       char *fmt;
+        va_dcl
+#endif
 {
 {
-
-       printf("%d ", n);
-       _doprnt(s, &args, stdout);
-       printf("\r\n");
-       fflush(stdout);
+       va_list ap;
+#if __STDC__
+       va_start(ap, fmt);
+#else
+       va_start(ap);
+#endif
+       (void)printf("%d ", n);
+       (void)vprintf(fmt, ap);
+       (void)printf("\r\n");
+       (void)fflush(stdout);
        if (debug) {
        if (debug) {
-               fprintf(stderr, "<--- %d ", n);
-               _doprnt(s, &args, stderr);
-               fprintf(stderr, "\n");
-               fflush(stderr);
+               syslog(LOG_DEBUG, "<--- %d ", n);
+               vsyslog(LOG_DEBUG, fmt, ap);
        }
 }
 
        }
 }
 
-lreply(n, s, args)
+void
+#if __STDC__
+lreply(int n, const char *fmt, ...)
+#else
+lreply(n, fmt, va_alist)
        int n;
        int n;
-       char *s;
+       char *fmt;
+        va_dcl
+#endif
 {
 {
-       printf("%d-", n);
-       _doprnt(s, &args, stdout);
-       printf("\r\n");
-       fflush(stdout);
+       va_list ap;
+#if __STDC__
+       va_start(ap, fmt);
+#else
+       va_start(ap);
+#endif
+       (void)printf("%d- ", n);
+       (void)vprintf(fmt, ap);
+       (void)printf("\r\n");
+       (void)fflush(stdout);
        if (debug) {
        if (debug) {
-               fprintf(stderr, "<--- %d-", n);
-               _doprnt(s, &args, stderr);
-               fprintf(stderr, "\n");
+               syslog(LOG_DEBUG, "<--- %d- ", n);
+               vsyslog(LOG_DEBUG, fmt, ap);
        }
 }
 
        }
 }
 
-replystr(s)
-       char *s;
-{
-       printf("%s\r\n", s);
-       fflush(stdout);
-       if (debug)
-               fprintf(stderr, "<--- %s\n", s);
-}
-
+static void
 ack(s)
        char *s;
 {
 ack(s)
        char *s;
 {
-       reply(200, "%s command okay.", s);
+       reply(250, "%s command successful.", s);
 }
 
 }
 
+void
 nack(s)
        char *s;
 {
        reply(502, "%s command not implemented.", s);
 }
 
 nack(s)
        char *s;
 {
        reply(502, "%s command not implemented.", s);
 }
 
-yyerror()
+/* ARGSUSED */
+char *
+yyerror(s)
+       char *s;
 {
 {
-       reply(500, "Command not understood.");
+       char *cp;
+
+       if (cp = strchr(cbuf,'\n'))
+               *cp = '\0';
+       reply(500, "'%s': command not understood.", cbuf);
 }
 
 }
 
+void
 delete(name)
        char *name;
 {
        struct stat st;
 
 delete(name)
        char *name;
 {
        struct stat st;
 
+       LOGCMD("delete", name);
        if (stat(name, &st) < 0) {
        if (stat(name, &st) < 0) {
-               reply(550, "%s: %s.", name, sys_errlist[errno]);
+               perror_reply(550, name);
                return;
        }
        if ((st.st_mode&S_IFMT) == S_IFDIR) {
                if (rmdir(name) < 0) {
                return;
        }
        if ((st.st_mode&S_IFMT) == S_IFDIR) {
                if (rmdir(name) < 0) {
-                       reply(550, "%s: %s.", name, sys_errlist[errno]);
+                       perror_reply(550, name);
                        return;
                }
                goto done;
        }
        if (unlink(name) < 0) {
                        return;
                }
                goto done;
        }
        if (unlink(name) < 0) {
-               reply(550, "%s: %s.", name, sys_errlist[errno]);
+               perror_reply(550, name);
                return;
        }
 done:
        ack("DELE");
 }
 
                return;
        }
 done:
        ack("DELE");
 }
 
+void
 cwd(path)
        char *path;
 {
 cwd(path)
        char *path;
 {
-
-       if (chdir(path) < 0) {
-               reply(550, "%s: %s.", path, sys_errlist[errno]);
-               return;
-       }
-       ack("CWD");
+       if (chdir(path) < 0)
+               perror_reply(550, path);
+       else
+               ack("CWD");
 }
 
 }
 
+void
 makedir(name)
        char *name;
 {
 makedir(name)
        char *name;
 {
-       struct stat st;
-       int dochown = stat(name, &st) < 0;
-       
-       if (mkdir(name, 0777) < 0) {
-               reply(550, "%s: %s.", name, sys_errlist[errno]);
-               return;
-       }
-       if (dochown)
-               (void) chown(name, pw->pw_uid, -1);
-       ack("MKDIR");
+       LOGCMD("mkdir", name);
+       if (mkdir(name, 0777) < 0)
+               perror_reply(550, name);
+       else
+               reply(257, "MKD command successful.");
 }
 
 }
 
+void
 removedir(name)
        char *name;
 {
 removedir(name)
        char *name;
 {
-
-       if (rmdir(name) < 0) {
-               reply(550, "%s: %s.", name, sys_errlist[errno]);
-               return;
-       }
-       ack("RMDIR");
+       LOGCMD("rmdir", name);
+       if (rmdir(name) < 0)
+               perror_reply(550, name);
+       else
+               ack("RMD");
 }
 
 }
 
+void
 pwd()
 {
        char path[MAXPATHLEN + 1];
 
 pwd()
 {
        char path[MAXPATHLEN + 1];
 
-       if (getwd(path) == NULL) {
-               reply(451, "%s.", path);
-               return;
-       }
-       reply(251, "\"%s\" is current directory.", path);
+       if (getwd(path) == (char *)NULL)
+               reply(550, "%s.", path);
+       else
+               reply(257, "\"%s\" is current directory.", path);
 }
 
 char *
 }
 
 char *
@@ -640,224 +1292,350 @@ renamefrom(name)
        struct stat st;
 
        if (stat(name, &st) < 0) {
        struct stat st;
 
        if (stat(name, &st) < 0) {
-               reply(550, "%s: %s.", name, sys_errlist[errno]);
+               perror_reply(550, name);
                return ((char *)0);
        }
        reply(350, "File exists, ready for destination name");
        return (name);
 }
 
                return ((char *)0);
        }
        reply(350, "File exists, ready for destination name");
        return (name);
 }
 
+void
 renamecmd(from, to)
        char *from, *to;
 {
 renamecmd(from, to)
        char *from, *to;
 {
-
-       if (rename(from, to) < 0) {
-               reply(550, "rename: %s.", sys_errlist[errno]);
-               return;
-       }
-       ack("RNTO");
+       LOGCMD2("rename", from, to);
+       if (rename(from, to) < 0)
+               perror_reply(550, "rename");
+       else
+               ack("RNTO");
 }
 
 }
 
+static void
 dolog(sin)
        struct sockaddr_in *sin;
 {
 dolog(sin)
        struct sockaddr_in *sin;
 {
-       struct hostent *hp = gethostbyaddr(&sin->sin_addr,
+       struct hostent *hp = gethostbyaddr((char *)&sin->sin_addr,
                sizeof (struct in_addr), AF_INET);
                sizeof (struct in_addr), AF_INET);
-       time_t t;
 
 
-       if (hp) {
-               strncpy(remotehost, hp->h_name, sizeof (remotehost));
-               endhostent();
-       } else
-               strncpy(remotehost, inet_ntoa(sin->sin_addr),
+       if (hp)
+               (void) strncpy(remotehost, hp->h_name, sizeof (remotehost));
+       else
+               (void) strncpy(remotehost, inet_ntoa(sin->sin_addr),
                    sizeof (remotehost));
                    sizeof (remotehost));
-       if (!logging)
-               return;
-       t = time(0);
-       fprintf(stderr,"FTPD: connection from %s at %s", remotehost, ctime(&t));
-       fflush(stderr);
-}
-
-#include <utmp.h>
+#ifdef SETPROCTITLE
+       sprintf(proctitle, "%s: connected", remotehost);
+       setproctitle(proctitle);
+#endif /* SETPROCTITLE */
 
 
-#define        SCPYN(a, b)     strncpy(a, b, sizeof (a))
-struct utmp utmp;
-
-/*
- * Record login in wtmp file.
- */
-dologin(pw)
-       struct passwd *pw;
-{
-       char line[32];
-
-       if (wtmp >= 0) {
-               /* hack, but must be unique and no tty line */
-               sprintf(line, "ftp%d", getpid());
-               SCPYN(utmp.ut_line, line);
-               SCPYN(utmp.ut_name, pw->pw_name);
-               SCPYN(utmp.ut_host, remotehost);
-               utmp.ut_time = time(0);
-               (void) write(wtmp, (char *)&utmp, sizeof (utmp));
-               if (!guest) {           /* anon must hang on */
-                       (void) close(wtmp);
-                       wtmp = -1;
-               }
-       }
+       if (logging)
+               syslog(LOG_INFO, "connection from %s", remotehost);
 }
 
 /*
  * Record logout in wtmp file
  * and exit with supplied status.
  */
 }
 
 /*
  * Record logout in wtmp file
  * and exit with supplied status.
  */
+void
 dologout(status)
        int status;
 {
 dologout(status)
        int status;
 {
-
        if (logged_in) {
        if (logged_in) {
-               (void) seteuid(0);
-               if (wtmp < 0)
-                       wtmp = open("/usr/adm/wtmp", O_WRONLY|O_APPEND);
-               if (wtmp >= 0) {
-                       SCPYN(utmp.ut_name, "");
-                       SCPYN(utmp.ut_host, "");
-                       utmp.ut_time = time(0);
-                       (void) write(wtmp, (char *)&utmp, sizeof (utmp));
-                       (void) close(wtmp);
-               }
+               (void) seteuid((uid_t)0);
+               logwtmp(ttyline, "", "");
        }
        /* beware of flushing buffers after a SIGPIPE */
        _exit(status);
 }
 
        }
        /* beware of flushing buffers after a SIGPIPE */
        _exit(status);
 }
 
+static void
+myoob(signo)
+       int signo;
+{
+       char *cp;
+
+       /* only process if transfer occurring */
+       if (!transflag)
+               return;
+       cp = tmpline;
+       if (getline(cp, 7, stdin) == NULL) {
+               reply(221, "You could at least say goodbye.");
+               dologout(0);
+       }
+       upper(cp);
+       if (strcmp(cp, "ABOR\r\n") == 0) {
+               tmpline[0] = '\0';
+               reply(426, "Transfer aborted. Data connection closed.");
+               reply(226, "Abort successful");
+               longjmp(urgcatch, 1);
+       }
+       if (strcmp(cp, "STAT\r\n") == 0) {
+               if (file_size != (off_t) -1)
+                       reply(213, "Status: %qd of %qd bytes transferred",
+                           byte_count, file_size);
+               else
+                       reply(213, "Status: %qd bytes transferred", byte_count);
+       }
+}
+
 /*
 /*
- * Special version of popen which avoids
- * call to shell.  This insures noone may 
- * create a pipe to a hidden program as a side
- * effect of a list or dir command.
+ * Note: a response of 425 is not mentioned as a possible response to
+ *     the PASV command in RFC959. However, it has been blessed as
+ *     a legitimate response by Jon Postel in a telephone conversation
+ *     with Rick Adams on 25 Jan 89.
  */
  */
-#define        tst(a,b)        (*mode == 'r'? (b) : (a))
-#define        RDR     0
-#define        WTR     1
-static int popen_pid[5];
+void
+passive()
+{
+       int len;
+       register char *p, *a;
+
+       pdata = socket(AF_INET, SOCK_STREAM, 0);
+       if (pdata < 0) {
+               perror_reply(425, "Can't open passive connection");
+               return;
+       }
+       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);
+               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;
+       if (listen(pdata, 1) < 0)
+               goto pasv_error;
+       a = (char *) &pasv_addr.sin_addr;
+       p = (char *) &pasv_addr.sin_port;
+
+#define UC(b) (((int) b) & 0xff)
+
+       reply(227, "Entering Passive Mode (%d,%d,%d,%d,%d,%d)", UC(a[0]),
+               UC(a[1]), UC(a[2]), UC(a[3]), UC(p[0]), UC(p[1]));
+       return;
+
+pasv_error:
+       (void) close(pdata);
+       pdata = -1;
+       perror_reply(425, "Can't open passive connection");
+       return;
+}
 
 
+/*
+ * Generate unique name for file with basename "local".
+ * The file named "local" is already known to exist.
+ * Generates failure reply on error.
+ */
 static char *
 static char *
-nextarg(cpp)
-       char *cpp;
+gunique(local)
+       char *local;
 {
 {
-       register char *cp = cpp;
+       static char new[MAXPATHLEN];
+       struct stat st;
+       int count;
+       char *cp;
 
 
-       if (cp == 0)
-               return (cp);
-       while (*cp && *cp != ' ' && *cp != '\t')
-               cp++;
-       if (*cp == ' ' || *cp == '\t') {
-               *cp++ = '\0';
-               while (*cp == ' ' || *cp == '\t')
-                       cp++;
+       cp = strrchr(local, '/');
+       if (cp)
+               *cp = '\0';
+       if (stat(cp ? local : ".", &st) < 0) {
+               perror_reply(553, cp ? local : ".");
+               return((char *) 0);
        }
        }
-       if (cp == cpp)
-               return ((char *)0);
-       return (cp);
+       if (cp)
+               *cp = '/';
+       (void) strcpy(new, local);
+       cp = new + strlen(new);
+       *cp++ = '.';
+       for (count = 1; count < 100; count++) {
+               (void)sprintf(cp, "%d", count);
+               if (stat(new, &st) < 0)
+                       return(new);
+       }
+       reply(452, "Unique file name cannot be created.");
+       return(NULL);
 }
 
 }
 
-FILE *
-popen(cmd, mode)
-       char *cmd, *mode;
+/*
+ * Format and send reply containing system error number.
+ */
+void
+perror_reply(code, string)
+       int code;
+       char *string;
 {
 {
-       int p[2], ac, gac;
-       register myside, hisside, pid;
-       char *av[20], *gav[512];
-       register char *cp;
+       reply(code, "%s: %s.", string, strerror(errno));
+}
 
 
-       if (pipe(p) < 0)
-               return (NULL);
-       cp = cmd, ac = 0;
-       /* break up string into pieces */
-       do {
-               av[ac++] = cp;
-               cp = nextarg(cp);
-       } while (cp && *cp && ac < 20);
-       av[ac] = (char *)0;
-       gav[0] = av[0];
-       /* glob each piece */
-       for (gac = ac = 1; av[ac] != NULL; ac++) {
-               char **pop;
-               extern char **glob();
-
-               pop = glob(av[ac]);
-               if (pop) {
-                       av[ac] = (char *)pop;           /* save to free later */
-                       while (*pop && gac < 512)
-                               gav[gac++] = *pop++;
+static char *onefile[] = {
+       "",
+       0
+};
+
+void
+send_file_list(whichfiles)
+       char *whichfiles;
+{
+       struct stat st;
+       DIR *dirp = NULL;
+       struct dirent *dir;
+       FILE *dout = NULL;
+       register char **dirlist, *dirname;
+       int simple = 0;
+
+       if (strpbrk(whichfiles, "~{[*?") != NULL) {
+               extern char *globerr;
+
+               globerr = NULL;
+               dirlist = ftpglob(whichfiles);
+               if (globerr != NULL) {
+                       reply(550, globerr);
+                       return;
+               } else if (dirlist == NULL) {
+                       errno = ENOENT;
+                       perror_reply(550, whichfiles);
+                       return;
                }
                }
+       } else {
+               onefile[0] = whichfiles;
+               dirlist = onefile;
+               simple = 1;
        }
        }
-       gav[gac] = (char *)0;
-       myside = tst(p[WTR], p[RDR]);
-       hisside = tst(p[RDR], p[WTR]);
-       if ((pid = fork()) == 0) {
-               /* myside and hisside reverse roles in child */
-               close(myside);
-               dup2(hisside, tst(0, 1));
-               close(hisside);
-               execv(gav[0], gav);
-               _exit(1);
-       }
-       for (ac = 1; av[ac] != NULL; ac++)
-               blkfree((char **)av[ac]);
-       if (pid == -1)
-               return (NULL);
-       popen_pid[myside] = pid;
-       close(hisside);
-       return (fdopen(myside, mode));
-}
 
 
-pclose(ptr)
-       FILE *ptr;
-{
-       register f, r, (*hstat)(), (*istat)(), (*qstat)();
-       int status;
+       if (setjmp(urgcatch)) {
+               transflag = 0;
+               return;
+       }
+       while (dirname = *dirlist++) {
+               if (stat(dirname, &st) < 0) {
+                       /*
+                        * If user typed "ls -l", etc, and the client
+                        * used NLST, do what the user meant.
+                        */
+                       if (dirname[0] == '-' && *dirlist == NULL &&
+                           transflag == 0) {
+                               retrieve("/bin/ls %s", dirname);
+                               return;
+                       }
+                       perror_reply(550, whichfiles);
+                       if (dout != NULL) {
+                               (void) fclose(dout);
+                               transflag = 0;
+                               data = -1;
+                               pdata = -1;
+                       }
+                       return;
+               }
 
 
-       f = fileno(ptr);
-       fclose(ptr);
-       istat = signal(SIGINT, SIG_IGN);
-       qstat = signal(SIGQUIT, SIG_IGN);
-       hstat = signal(SIGHUP, SIG_IGN);
-       while ((r = wait(&status)) != popen_pid[f] && r != -1)
-               ;
-       if (r == -1)
-               status = -1;
-       signal(SIGINT, istat);
-       signal(SIGQUIT, qstat);
-       signal(SIGHUP, hstat);
-       return (status);
+               if ((st.st_mode&S_IFMT) == S_IFREG) {
+                       if (dout == NULL) {
+                               dout = dataconn("file list", (off_t)-1, "w");
+                               if (dout == NULL)
+                                       return;
+                               transflag++;
+                       }
+                       fprintf(dout, "%s%s\n", dirname,
+                               type == TYPE_A ? "\r" : "");
+                       byte_count += strlen(dirname) + 1;
+                       continue;
+               } else if ((st.st_mode&S_IFMT) != S_IFDIR)
+                       continue;
+
+               if ((dirp = opendir(dirname)) == NULL)
+                       continue;
+
+               while ((dir = readdir(dirp)) != NULL) {
+                       char nbuf[MAXPATHLEN];
+
+                       if (dir->d_name[0] == '.' && dir->d_namlen == 1)
+                               continue;
+                       if (dir->d_name[0] == '.' && dir->d_name[1] == '.' &&
+                           dir->d_namlen == 2)
+                               continue;
+
+                       sprintf(nbuf, "%s/%s", dirname, dir->d_name);
+
+                       /*
+                        * We have to do a stat to insure it's
+                        * not a directory or special file.
+                        */
+                       if (simple || (stat(nbuf, &st) == 0 &&
+                           (st.st_mode&S_IFMT) == S_IFREG)) {
+                               if (dout == NULL) {
+                                       dout = dataconn("file list", (off_t)-1,
+                                               "w");
+                                       if (dout == NULL)
+                                               return;
+                                       transflag++;
+                               }
+                               if (nbuf[0] == '.' && nbuf[1] == '/')
+                                       fprintf(dout, "%s%s\n", &nbuf[2],
+                                               type == TYPE_A ? "\r" : "");
+                               else
+                                       fprintf(dout, "%s%s\n", nbuf,
+                                               type == TYPE_A ? "\r" : "");
+                               byte_count += strlen(nbuf) + 1;
+                       }
+               }
+               (void) closedir(dirp);
+       }
+
+       if (dout == NULL)
+               reply(550, "No files found.");
+       else if (ferror(dout) != 0)
+               perror_reply(550, "Data connection");
+       else
+               reply(226, "Transfer complete.");
+
+       transflag = 0;
+       if (dout != NULL)
+               (void) fclose(dout);
+       data = -1;
+       pdata = -1;
 }
 
 }
 
+#ifdef SETPROCTITLE
 /*
 /*
- * Check user requesting login priviledges.
- * Disallow anyone mentioned in the file FTPUSERS
- * to allow people such as uucp to be avoided.
+ * 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
+ * have much of an environment or arglist to overwrite.
  */
  */
-checkuser(name)
-       register char *name;
+void
+#if __STDC__
+setproctitle(const char *fmt, ...)
+#else
+setproctitle(fmt, va_alist)
+       char *fmt;
+        va_dcl
+#endif
 {
 {
-       char line[BUFSIZ], *index();
-       FILE *fd;
-       int found = 0;
+       register char *p, *bp, ch;
+       register int i;
+       va_list ap;
+       char buf[BUFSIZ];
+#if __STDC__
+       va_start(ap, fmt);
+#else
+       va_start(ap);
+#endif
+       (void)vsnprintf(buf, sizeof(buf), fmt, ap);
 
 
-       fd = fopen(FTPUSERS, "r");
-       if (fd == NULL)
-               return (1);
-       while (fgets(line, sizeof (line), fd) != NULL) {
-               register char *cp = index(line, '\n');
+       /* make ps print our process name */
+       p = Argv[0];
+       *p++ = '-';
 
 
-               if (cp)
-                       *cp = '\0';
-               if (strcmp(line, name) == 0) {
-                       found++;
-                       break;
-               }
+       i = strlen(buf);
+       if (i > LastArgv - p - 2) {
+               i = LastArgv - p - 2;
+               buf[i] = '\0';
        }
        }
-       fclose(fd);
-       return (!found);
+       bp = buf;
+       while (ch = *bp++)
+               if (ch != '\n' && ch != '\r')
+                       *p++ = ch;
+       while (p < LastArgv)
+               *p++ = ' ';
 }
 }
+#endif /* SETPROCTITLE */