Sync ftpd(8) with FreeBSD. Here are the highlights:
authorPeter Avalos <pavalos@theshell.com>
Fri, 2 Jan 2009 23:16:23 +0000 (18:16 -0500)
committerPeter Avalos <pavalos@theshell.com>
Sat, 3 Jan 2009 16:47:19 +0000 (11:47 -0500)
-Prevent cross-site forgery attacks on ftpd(8) due to splitting
long commands into multiple requests.

-Switch from S/Key to OPIE.

-Add PAM support for account management and sessions.

-Avoid calling uninitialized function pointers in protocol switch
code.

-Add support for RFC 2389 (FEAT) and RFC 2640 (UTF8) to ftpd(8).

-Use uniform punctuation, capitalization, and language style
in server messages wherever this doesn't contradict to a particular
message format.

-Use the standardized CHAR_BIT constant instead of NBBY.

-Let tilde expansion be done even if a file/directory doesn't exist yet.
This makes such natural commands as "MKD ~user/newdir" or "STOR
~/newfile" do what they are supposed to instead of failing miserably
with the "File not found" error.

-ANSI function declarations.

-Remove (void) casts and register keyword.

-Block SIGURG while reading from the control channel.
SIGURG is configured by ftpd to interrupt system calls, which is useful
during data transfers.  However, SIGURG could interrupt I/O on the
control channel as well, which was mistaken for the end of the session.
A practical example could be aborting the download of a tiny file,
when the abort sequence reached ftpd after ftpd had passed the file
data to the system and returned to its command loop.

-Improve error handling in getline().

-Log pathname arguments to ftp commands as the user specified them;
add the working directory pathname to the log message if any of
such arguments isn't absolute.  This has advantage over the old
way of logging that an admin can see what users are actually trying
to do, and where.  The old code was also not too robust when it
came to a chrooted session and an absolute pathname.

-Improve handling SIGURG and OOB commands on the control channel.
The major change is to process STAT sent as an OOB command w/o
breaking the current data transfer.  As a side effect, this gives
better error checking in the code performing data transfers.

-Never emit a message to stderr: use syslog instead.
When in inetd mode, this prevents bogus messages from
appearing on the control channel.  When running as a
daemon, we shouldn't write to the terminal we used to
have at all.

-Don't depend on IPv4-mapped IPv6 address to bind to both IPv4
and IPv6.

-Work around a bug in some clients by never returning raw directory
contents in reply to a RETR command.  Such clients consider RETR
as a way to tell a file from a directory.

-Log the actual number of bytes sent on the wire to /var/log/ftpd
instead of the disk size of the file sent.   Since the log file
is intended to provide data for anonymous ftp traffic accounting,
the disk size of the file isn't really informative in this case.

etc/pam.d/ftpd
libexec/ftpd/Makefile
libexec/ftpd/extern.h
libexec/ftpd/ftpchroot.5
libexec/ftpd/ftpcmd.y
libexec/ftpd/ftpd.8
libexec/ftpd/ftpd.c
libexec/ftpd/popen.c
libexec/ftpd/skey-stuff.c [deleted file]

index ece3d86..62cdd0c 100644 (file)
@@ -1,11 +1,21 @@
 #
+# $FreeBSD: src/etc/pam.d/ftpd,v 1.19 2007/06/10 18:57:20 yar Exp $
 # $DragonFly: src/etc/pam.d/ftpd,v 1.1 2005/07/22 18:20:43 joerg Exp $
 #
 # PAM configuration for the "ftpd" service
 #
 
-auth           sufficient      pam_opie.so                     no_fake_prompts
-#auth          requisite       pam_opieaccess.so
-auth           requisite       pam_cleartext_pass_ok.so
-#auth          sufficient      pam_krb5.so                     try_first_pass
-auth           required        pam_unix.so                     try_first_pass
+# auth
+auth           sufficient      pam_opie.so             no_warn no_fake_prompts
+auth           requisite       pam_opieaccess.so       no_warn allow_local
+#auth          sufficient      pam_krb5.so             no_warn
+#auth           sufficient      pam_ssh.so             no_warn try_first_pass
+auth           required        pam_unix.so             no_warn try_first_pass
+
+# account
+account                required        pam_nologin.so
+#account       required        pam_krb5.so
+account                required        pam_unix.so
+
+# session
+session                required        pam_permit.so
index a66e9f3..92954df 100644 (file)
@@ -1,29 +1,40 @@
 #      @(#)Makefile    8.2 (Berkeley) 4/4/94
-# $FreeBSD: src/libexec/ftpd/Makefile,v 1.33.2.6 2003/02/11 14:28:28 yar Exp $
+# $FreeBSD: src/libexec/ftpd/Makefile,v 1.57 2006/06/05 15:50:34 yar Exp $
 # $DragonFly: src/libexec/ftpd/Makefile,v 1.3 2004/01/23 14:55:52 joerg Exp $
 
 PROG=  ftpd
 MAN=   ftpd.8 ftpchroot.5
-SRCS=  ftpd.c ftpcmd.y logwtmp.c popen.c skey-stuff.c
+SRCS=  ftpd.c ftpcmd.y logwtmp.c popen.c
 
-CFLAGS+=-DSETPROCTITLE -DSKEY -DLOGIN_CAP -DVIRTUAL_HOSTING -Wall
-CFLAGS+=-DINET6
+CFLAGS+=-DSETPROCTITLE -DLOGIN_CAP -DVIRTUAL_HOSTING
+CFLAGS+=-I${.CURDIR}
 YFLAGS=
+WARNS?=        2
+WFORMAT=0
+
+DPADD= ${LIBUTIL} ${LIBCRYPT}
+LDADD= -lutil -lcrypt
 
-LDADD= -lskey -lmd -lcrypt -lutil
-DPADD= ${LIBSKEY} ${LIBMD} ${LIBCRYPT} ${LIBUTIL}
+DPADD+=        ${LIBOPIE} ${LIBMD}
+LDADD+=        -lopie -lmd
 
 LSDIR= ../../bin/ls
 .PATH: ${.CURDIR}/${LSDIR}
 SRCS+= ls.c cmp.c print.c util.c
-CFLAGS+=-Dmain=ls_main -I${.CURDIR} -I${.CURDIR}/${LSDIR}
-
+CFLAGS+=-Dmain=ls_main -I${.CURDIR}/${LSDIR}
 DPADD+=        ${LIBM}
 LDADD+=        -lm
 
-.if defined(NOPAM)
-CFLAGS+=-DNOPAM
-.else
+.PATH: ${.CURDIR}/../../usr.sbin/nscd
+SRCS+= pidfile.c
+CFLAGS+=-I${.CURDIR}/../../usr.sbin/nscd
+
+.if !defined(NO_INET6)
+CFLAGS+=-DINET6
+.endif
+
+.if !defined(NO_PAM)
+CFLAGS+=-DUSE_PAM
 DPADD+= ${LIBPAM}
 LDADD+= ${MINUSLPAM}
 .endif
index 739068d..48ecf0a 100644 (file)
  * SUCH DAMAGE.
  *
  *     @(#)extern.h    8.2 (Berkeley) 4/4/94
- * $FreeBSD: src/libexec/ftpd/extern.h,v 1.14.2.2 2002/02/16 14:02:00 dwmalone Exp $
+ * $FreeBSD: src/libexec/ftpd/extern.h,v 1.20 2008/12/23 01:23:09 cperciva Exp $
  * $DragonFly: src/libexec/ftpd/extern.h,v 1.3 2003/11/14 03:54:30 dillon Exp $
  */
 
 #include <sys/types.h>
 #include <sys/socket.h>
 
-void   blkfree (char **);
-char  **copyblk (char **);
-void   cwd (char *);
-void   delete (char *);
-void   dologout (int);
-void   fatalerror (char *);
-void    ftpd_logwtmp (char *, char *, struct sockaddr *addr);
-int    ftpd_pclose (FILE *);
-FILE   *ftpd_popen (char *, char *);
-char   *getline (char *, int, FILE *);
-void   lreply (int, const char *, ...);
-void   makedir (char *);
-void   nack (char *);
-void   pass (char *);
-void   passive (void);
-void   long_passive (char *, int);
-void   perror_reply (int, char *);
-void   pwd (void);
-void   removedir (char *);
-void   renamecmd (char *, char *);
-char   *renamefrom (char *);
-void   reply (int, const char *, ...);
-void   retrieve (char *, char *);
-void   send_file_list (char *);
+void   blkfree(char **);
+char  **copyblk(char **);
+void   cwd(char *);
+void   delete(char *);
+void   dologout(int);
+void   fatalerror(char *);
+void    ftpd_logwtmp(char *, char *, struct sockaddr *addr);
+int    ftpd_pclose(FILE *);
+FILE   *ftpd_popen(char *, char *);
+int    getline(char *, int, FILE *);
+void   lreply(int, const char *, ...) __printflike(2, 3);
+void   makedir(char *);
+void   nack(char *);
+void   pass(char *);
+void   passive(void);
+void   long_passive(char *, int);
+void   perror_reply(int, char *);
+void   pwd(void);
+void   removedir(char *);
+void   renamecmd(char *, char *);
+char   *renamefrom(char *);
+void   reply(int, const char *, ...) __printflike(2, 3);
+void   retrieve(char *, char *);
+void   send_file_list(char *);
 #ifdef OLD_SETPROCTITLE
-void   setproctitle (const char *, ...);
+void   setproctitle(const char *, ...);
 #endif
-void   statcmd (void);
-void   statfilecmd (char *);
-void   store (char *, char *, int);
-void   upper (char *);
-void   user (char *);
-void   yyerror (char *);
-int    yyparse (void);
-#if defined(SKEY) && defined(_PWD_H_) /* XXX evil */
-char   *skey_challenge (char *, struct passwd *, int);
-#endif
-int    ls_main (int, char **);
+void   statcmd(void);
+void   statfilecmd(char *);
+void   store(char *, char *, int);
+void   upper(char *);
+void   user(char *);
+void   yyerror(char *);
+int    yyparse(void);
+int    ls_main(int, char **);
 
 struct sockaddr_in;
 struct sockaddr_in6;
index 2b6e66b..43f24a1 100644 (file)
@@ -22,7 +22,7 @@
 .\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 .\" SUCH DAMAGE.
 .\"
-.\" $FreeBSD: src/libexec/ftpd/ftpchroot.5,v 1.2.2.1 2003/02/11 14:28:28 yar Exp $
+.\" $FreeBSD: src/libexec/ftpd/ftpchroot.5,v 1.3 2003/06/01 19:52:36 ru Exp $
 .\" $DragonFly: src/libexec/ftpd/ftpchroot.5,v 1.4 2007/05/17 08:19:01 swildner Exp $
 .\"
 .Dd January 26, 2003
@@ -30,7 +30,7 @@
 .Os
 .Sh NAME
 .Nm ftpchroot
-.Nd list users and groups subject to FTP access restrictions
+.Nd "list users and groups subject to FTP access restrictions"
 .Sh DESCRIPTION
 The file
 .Nm
@@ -39,7 +39,8 @@ is read by
 at the beginning of an FTP session, after having authenticated the user.
 Each line in
 .Nm
-corresponds to a user or group.  If a line in
+corresponds to a user or group.
+If a line in
 .Nm
 matches the current user or a group he is a member of,
 access restrictions will be applied to this
@@ -54,13 +55,13 @@ Fields on each line are separated by tabs or spaces.
 .Pp
 The first field specifies a user or group name.
 If it is prefixed by an
-.Qq at
+.Dq at
 sign,
-.Ql \&@ ,
+.Ql @ ,
 it specifies a group name;
 the line will match each user who is a member of this group.
 As a special case, a single
-.Ql \&@
+.Ql @
 in this field will match any user.
 A username is specified otherwise.
 .Pp
@@ -71,23 +72,23 @@ Be it omitted, the user's login directory will be used.
 If it is not an absolute pathname, then it will be relative
 to the user's login directory.
 If it contains the
-.Qq \&/./
+.Pa /./
 separator,
 .Xr ftpd 8
 will treat its left-hand side as the name of the directory to do
 .Xr chroot 2
 to, and its right-hand side to change the current directory to afterwards.
 .Sh FILES
-.Bl -tag -width /etc/ftpchroot -compact
+.Bl -tag -width ".Pa /etc/ftpchroot" -compact
 .It Pa /etc/ftpchroot
 .El
 .Sh EXAMPLES
 These lines in
 .Nm
 will lock up the user
-.Qq webuser
+.Dq Li webuser
 and each member of the group
-.Qq hostee
+.Dq Li hostee
 in their respective login directories:
 .Bd -literal -offset indent
 webuser
@@ -97,24 +98,22 @@ webuser
 And this line will tell
 .Xr ftpd 8
 to lock up the user
-.Qq joe
+.Dq Li joe
 in
 .Pa /var/spool/ftp
 and then to change the current directory to
 .Pa /joe ,
 which is relative to the session's new root:
-.Bd -literal -offset indent
-joe    /var/spool/ftp/./joe
-.Ed
+.Pp
+.Dl "joe       /var/spool/ftp/./joe"
 .Pp
 And finally the following line will lock up every user connecting
 through FTP in his respective
-.Pa \&~/public_html ,
+.Pa ~/public_html ,
 thus lowering possible impact on the system
 from intrinsic insecurity of FTP:
-.Bd -literal -offset indent
-@      public_html
-.Ed
+.Pp
+.Dl "@ public_html"
 .Sh SEE ALSO
 .Xr chroot 2 ,
 .Xr group 5 ,
index feec74f..14e895d 100644 (file)
  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
  * SUCH DAMAGE.
  *
- *     @(#)ftpcmd.y    8.3 (Berkeley) 4/6/94
- *
  * @(#)ftpcmd.y        8.3 (Berkeley) 4/6/94
- * $FreeBSD: src/libexec/ftpd/ftpcmd.y,v 1.16.2.19 2003/02/11 14:28:28 yar Exp $
+ * $FreeBSD: src/libexec/ftpd/ftpcmd.y,v 1.67 2008/12/23 01:23:09 cperciva Exp $
  * $DragonFly: src/libexec/ftpd/ftpcmd.y,v 1.4 2004/06/19 20:36:04 joerg Exp $
  */
 
@@ -60,6 +58,7 @@
 #include <netdb.h>
 #include <pwd.h>
 #include <signal.h>
+#include <stdint.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
@@ -85,12 +84,11 @@ extern      int timeout;
 extern int maxtimeout;
 extern  int pdata;
 extern char *hostname;
-extern char remotehost[];
 extern char proctitle[];
 extern int usedefault;
-extern  int transflag;
 extern  char tmpline[];
 extern int readonly;
+extern int assumeutf8;
 extern int noepsv;
 extern int noretr;
 extern int noguestretr;
@@ -103,7 +101,7 @@ static      int cmd_form;
 static int cmd_bytesz;
 static int state;
 char   cbuf[512];
-char   *fromname = (char *) 0;
+char   *fromname = NULL;
 
 extern int epsvall;
 
@@ -131,7 +129,7 @@ extern int epsvall;
        ABOR    DELE    CWD     LIST    NLST    SITE
        STAT    HELP    NOOP    MKD     RMD     PWD
        CDUP    STOU    SMNT    SYST    SIZE    MDTM
-       LPRT    LPSV    EPRT    EPSV
+       LPRT    LPSV    EPRT    EPSV    FEAT
 
        UMASK   IDLE    CHMOD   MDFIVE
 
@@ -156,8 +154,8 @@ cmd_list
                {
                        if (fromname)
                                free(fromname);
-                       fromname = (char *) 0;
-                       restart_point = (off_t) 0;
+                       fromname = NULL;
+                       restart_point = 0;
                }
        | cmd_list rcmd
        ;
@@ -180,7 +178,7 @@ cmd
        | PORT check_login SP host_port CRLF
                {
                        if (epsvall) {
-                               reply(501, "no PORT allowed after EPSV ALL");
+                               reply(501, "No PORT allowed after EPSV ALL.");
                                goto port_done;
                        }
                        if (!$2)
@@ -203,7 +201,7 @@ cmd
        | LPRT check_login SP host_long_port CRLF
                {
                        if (epsvall) {
-                               reply(501, "no LPRT allowed after EPSV ALL");
+                               reply(501, "No LPRT allowed after EPSV ALL.");
                                goto lprt_done;
                        }
                        if (!$2)
@@ -233,7 +231,7 @@ cmd
                        int i;
 
                        if (epsvall) {
-                               reply(501, "no EPRT allowed after EPSV ALL");
+                               reply(501, "No EPRT allowed after EPSV ALL.");
                                goto eprt_done;
                        }
                        if (!$2)
@@ -326,14 +324,14 @@ cmd
        | PASV check_login CRLF
                {
                        if (epsvall)
-                               reply(501, "no PASV allowed after EPSV ALL");
+                               reply(501, "No PASV allowed after EPSV ALL.");
                        else if ($2)
                                passive();
                }
        | LPSV check_login CRLF
                {
                        if (epsvall)
-                               reply(501, "no LPSV allowed after EPSV ALL");
+                               reply(501, "No LPSV allowed after EPSV ALL.");
                        else if ($2)
                                long_passive("LPSV", PF_UNSPEC);
                }
@@ -360,8 +358,7 @@ cmd
        | EPSV check_login_epsv SP ALL CRLF
                {
                        if ($2) {
-                               reply(200,
-                                     "EPSV ALL command successful.");
+                               reply(200, "EPSV ALL command successful.");
                                epsvall++;
                        }
                }
@@ -394,16 +391,16 @@ cmd
                                        break;
 
                                case TYPE_L:
-#if NBBY == 8
+#if CHAR_BIT == 8
                                        if (cmd_bytesz == 8) {
                                                reply(200,
                                                    "Type set to L (byte size 8).");
                                                type = cmd_type;
                                        } else
                                                reply(504, "Byte size must be 8.");
-#else /* NBBY == 8 */
-                                       UNIMPLEMENTED for NBBY != 8
-#endif /* NBBY == 8 */
+#else /* CHAR_BIT == 8 */
+                                       UNIMPLEMENTED for CHAR_BIT != 8
+#endif /* CHAR_BIT == 8 */
                                }
                        }
                }
@@ -413,7 +410,7 @@ cmd
                                switch ($4) {
 
                                case STRU_F:
-                                       reply(200, "STRU F ok.");
+                                       reply(200, "STRU F accepted.");
                                        break;
 
                                default:
@@ -427,7 +424,7 @@ cmd
                                switch ($4) {
 
                                case MODE_S:
-                                       reply(200, "MODE S ok.");
+                                       reply(200, "MODE S accepted.");
                                        break;
        
                                default:
@@ -450,9 +447,9 @@ cmd
        | RETR check_login SP pathname CRLF
                {
                        if (noretr || (guest && noguestretr))
-                               reply(500, "RETR command is disabled");
+                               reply(500, "RETR command disabled.");
                        else if ($2 && $4 != NULL)
-                               retrieve((char *) 0, $4);
+                               retrieve(NULL, $4);
 
                        if ($4 != NULL)
                                free($4);
@@ -519,7 +516,7 @@ cmd
                                if (fromname) {
                                        renamecmd(fromname, $4);
                                        free(fromname);
-                                       fromname = (char *) 0;
+                                       fromname = NULL;
                                } else {
                                        reply(503, "Bad sequence of commands.");
                                }
@@ -547,7 +544,7 @@ cmd
                }
        | HELP CRLF
                {
-                       help(cmdtab, (char *) 0);
+                       help(cmdtab, NULL);
                }
        | HELP SP STRING CRLF
                {
@@ -560,7 +557,7 @@ cmd
                                if (*cp)
                                        help(sitetab, cp);
                                else
-                                       help(sitetab, (char *) 0);
+                                       help(sitetab, NULL);
                        } else
                                help(cmdtab, $3);
                        free($3);
@@ -595,7 +592,7 @@ cmd
                }
        | SITE SP HELP CRLF
                {
-                       help(sitetab, (char *) 0);
+                       help(sitetab, NULL);
                }
        | SITE SP HELP SP STRING CRLF
                {
@@ -622,8 +619,8 @@ cmd
 
                        if ($4) {
                                oldmask = umask(0);
-                               (void) umask(oldmask);
-                               reply(200, "Current UMASK is %03o", oldmask);
+                               umask(oldmask);
+                               reply(200, "Current UMASK is %03o.", oldmask);
                        }
                }
        | SITE SP UMASK check_login SP octal_number CRLF
@@ -632,11 +629,11 @@ cmd
 
                        if ($4) {
                                if (($6 == -1) || ($6 > 0777)) {
-                                       reply(501, "Bad UMASK value");
+                                       reply(501, "Bad UMASK value.");
                                } else {
                                        oldmask = umask($6);
                                        reply(200,
-                                           "UMASK set to %03o (was %03o)",
+                                           "UMASK set to %03o (was %03o).",
                                            $6, oldmask);
                                }
                        }
@@ -645,7 +642,7 @@ cmd
                {
                        if ($4 && ($8 != NULL)) {
                                if (($6 == -1 ) || ($6 > 0777))
-                                       reply(501, "Bad mode value");
+                                       reply(501, "Bad mode value.");
                                else if (chmod($8, $6) < 0)
                                        perror_reply(550, $8);
                                else
@@ -658,7 +655,7 @@ cmd
                {
                        if ($3)
                                reply(200,
-                                   "Current IDLE time limit is %d seconds; max %d",
+                                   "Current IDLE time limit is %d seconds; max %d.",
                                    timeout, maxtimeout);
                }
        | SITE SP check_login IDLE SP NUMBER CRLF
@@ -666,13 +663,13 @@ cmd
                        if ($3) {
                                if ($6.i < 30 || $6.i > maxtimeout) {
                                        reply(501,
-                                           "Maximum IDLE time must be between 30 and %d seconds",
+                                           "Maximum IDLE time must be between 30 and %d seconds.",
                                            maxtimeout);
                                } else {
                                        timeout = $6.i;
-                                       (void) alarm((unsigned) timeout);
+                                       alarm(timeout);
                                        reply(200,
-                                           "Maximum IDLE time set to %d seconds",
+                                           "Maximum IDLE time set to %d seconds.",
                                            timeout);
                                }
                        }
@@ -684,19 +681,38 @@ cmd
                        if ($4 != NULL)
                                free($4);
                }
+       | FEAT CRLF
+               {
+                       lreply(211, "Extensions supported:");
+#if 0
+                       /* XXX these two keywords are non-standard */
+                       printf(" EPRT\r\n");
+                       if (!noepsv)
+                               printf(" EPSV\r\n");
+#endif
+                       printf(" MDTM\r\n");
+                       printf(" REST STREAM\r\n");
+                       printf(" SIZE\r\n");
+                       if (assumeutf8) {
+                               /* TVFS requires UTF8, see RFC 3659 */
+                               printf(" TVFS\r\n");
+                               printf(" UTF8\r\n");
+                       }
+                       reply(211, "End.");
+               }
        | SYST check_login CRLF
                {
-                       if ($2)
-#ifdef unix
+                       if ($2) {
+                               if (hostinfo)
 #ifdef BSD
-                       reply(215, "UNIX Type: L%d Version: BSD-%d",
-                               NBBY, BSD);
+                                       reply(215, "UNIX Type: L%d Version: BSD-%d",
+                                             CHAR_BIT, BSD);
 #else /* BSD */
-                       reply(215, "UNIX Type: L%d", NBBY);
+                                       reply(215, "UNIX Type: L%d", CHAR_BIT);
 #endif /* BSD */
-#else /* unix */
-                       reply(215, "UNKNOWN Type: L%d", NBBY);
-#endif /* unix */
+                               else
+                                       reply(215, "UNKNOWN Type: L%d", CHAR_BIT);
+                       }
                }
 
                /*
@@ -728,8 +744,7 @@ cmd
                        if ($2 && $4 != NULL) {
                                struct stat stbuf;
                                if (stat($4, &stbuf) < 0)
-                                       reply(550, "%s: %s",
-                                           $4, strerror(errno));
+                                       perror_reply(550, $4);
                                else if (!S_ISREG(stbuf.st_mode)) {
                                        reply(550, "%s: not a plain file.", $4);
                                } else {
@@ -764,11 +779,11 @@ cmd
 rcmd
        : RNFR check_login_ro SP pathname CRLF
                {
-                       restart_point = (off_t) 0;
+                       restart_point = 0;
                        if ($2 && $4) {
                                if (fromname)
                                        free(fromname);
-                               fromname = (char *) 0;
+                               fromname = NULL;
                                if (renamefrom($4))
                                        fromname = $4;
                                else
@@ -782,10 +797,10 @@ rcmd
                        if ($2) {
                                if (fromname)
                                        free(fromname);
-                               fromname = (char *) 0;
+                               fromname = NULL;
                                restart_point = $4.o;
-                               reply(350, "Restarting at %llu. %s",
-                                   restart_point,
+                               reply(350, "Restarting at %jd. %s",
+                                   (intmax_t)restart_point,
                                    "Send STORE or RETRIEVE to initiate transfer.");
                        }
                }
@@ -914,7 +929,7 @@ type_code
        | L
                {
                        cmd_type = TYPE_L;
-                       cmd_bytesz = NBBY;
+                       cmd_bytesz = CHAR_BIT;
                }
        | L SP byte_size
                {
@@ -962,43 +977,24 @@ mode_code
 pathname
        : pathstring
                {
-                       /*
-                        * Problem: this production is used for all pathname
-                        * processing, but only gives a 550 error reply.
-                        * This is a valid reply in some cases but not in others.
-                        */
                        if (logged_in && $1) {
-                               glob_t gl;
-                               char *p, **pp;
-                               int flags =
-                                GLOB_BRACE|GLOB_NOCHECK|GLOB_QUOTE|GLOB_TILDE;
-                               int n;
-
-                               memset(&gl, 0, sizeof(gl));
-                               flags |= GLOB_LIMIT;
-                               gl.gl_matchc = MAXGLOBARGS;
-                               if (glob($1, flags, NULL, &gl) ||
-                                   gl.gl_pathc == 0) {
-                                       reply(550, "wildcard expansion error");
+                               char *p;
+
+                               /*
+                                * Expand ~user manually since glob(3)
+                                * will return the unexpanded pathname
+                                * if the corresponding file/directory
+                                * doesn't exist yet.  Using sole glob(3)
+                                * would break natural commands like
+                                * MKD ~user/newdir
+                                * or
+                                * RNTO ~/newfile
+                                */
+                               if ((p = exptilde($1)) != NULL) {
+                                       $$ = expglob(p);
+                                       free(p);
+                               } else
                                        $$ = NULL;
-                               } else {
-                                       n = 0;
-                                       for (pp = gl.gl_pathv; *pp; pp++)
-                                               if (strcspn(*pp, "\r\n") ==
-                                                   strlen(*pp)) {
-                                                       p = *pp;
-                                                       n++;
-                                               }
-                                       if (n == 0)
-                                               $$ = strdup($1);
-                                       else if (n == 1)
-                                               $$ = strdup(p);
-                                       else {
-                                               reply(550, "ambiguous");
-                                               $$ = NULL;
-                                       }
-                               }
-                               globfree(&gl);
                                free($1);
                        } else
                                $$ = $1;
@@ -1047,7 +1043,7 @@ check_login_epsv
        : /* empty */
                {
                if (noepsv) {
-                       reply(500, "EPSV command disabled");
+                       reply(500, "EPSV command disabled.");
                        $$ = 0;
                }
                else
@@ -1129,6 +1125,7 @@ struct tab cmdtab[] = {           /* In order defined in RFC 765 */
        { "NLST", NLST, OSTR, 1,        "[ <sp> path-name ]" },
        { "SITE", SITE, SITECMD, 1,     "site-cmd [ <sp> arguments ]" },
        { "SYST", SYST, ARGS, 1,        "(get type of operating system)" },
+       { "FEAT", FEAT, ARGS, 1,        "(get extended features)" },
        { "STAT", STAT, OSTR, 1,        "[ <sp> path-name ]" },
        { "HELP", HELP, OSTR, 1,        "[ <sp> <string> ]" },
        { "NOOP", NOOP, ARGS, 1,        "" },
@@ -1155,21 +1152,25 @@ struct tab sitetab[] = {
        { NULL,   0,    0,    0,        0 }
 };
 
-static char    *copy (char *);
-static void     help (struct tab *, char *);
+static char    *copy(char *);
+static char    *expglob(char *);
+static char    *exptilde(char *);
+static void     help(struct tab *, char *);
 static struct tab *
-                lookup (struct tab *, char *);
-static int      port_check (const char *);
-static int      port_check_v6 (const char *);
-static void     sizecmd (char *);
-static void     toolong (int);
-static void     v4map_data_dest (void);
-static int      yylex (void);
+                lookup(struct tab *, char *);
+static int      port_check(const char *);
+#ifdef INET6
+static int      port_check_v6(const char *);
+#endif
+static void     sizecmd(char *);
+static void     toolong(int);
+#ifdef INET6
+static void     v4map_data_dest(void);
+#endif
+static int      yylex(void);
 
 static struct tab *
-lookup(p, cmd)
-       struct tab *p;
-       char *cmd;
+lookup(struct tab *p, char *cmd)
 {
 
        for (; p->name != NULL; p++)
@@ -1183,14 +1184,12 @@ lookup(p, cmd)
 /*
  * getline - a hacked up version of fgets to ignore TELNET escape codes.
  */
-char *
-getline(s, n, iop)
-       char *s;
-       int n;
-       FILE *iop;
+int
+getline(char *s, int n, FILE *iop)
 {
        int c;
-       register char *cs;
+       char *cs;
+       sigset_t sset, osset;
 
        cs = s;
 /* tmpline may contain saved command from urgent mode interruption */
@@ -1201,50 +1200,69 @@ getline(s, n, iop)
                        if (ftpdebug)
                                syslog(LOG_DEBUG, "command: %s", s);
                        tmpline[0] = '\0';
-                       return(s);
+                       return(0);
                }
                if (c == 0)
                        tmpline[0] = '\0';
        }
+       /* SIGURG would interrupt stdio if not blocked during the read loop */
+       sigemptyset(&sset);
+       sigaddset(&sset, SIGURG);
+       sigprocmask(SIG_BLOCK, &sset, &osset);
        while ((c = getc(iop)) != EOF) {
                c &= 0377;
                if (c == IAC) {
-                   if ((c = getc(iop)) != EOF) {
+                       if ((c = getc(iop)) == EOF)
+                               goto got_eof;
                        c &= 0377;
                        switch (c) {
                        case WILL:
                        case WONT:
-                               c = getc(iop);
+                               if ((c = getc(iop)) == EOF)
+                                       goto got_eof;
                                printf("%c%c%c", IAC, DONT, 0377&c);
-                               (void) fflush(stdout);
+                               fflush(stdout);
                                continue;
                        case DO:
                        case DONT:
-                               c = getc(iop);
+                               if ((c = getc(iop)) == EOF)
+                                       goto got_eof;
                                printf("%c%c%c", IAC, WONT, 0377&c);
-                               (void) fflush(stdout);
+                               fflush(stdout);
                                continue;
                        case IAC:
                                break;
                        default:
                                continue;       /* ignore command */
                        }
-                   }
                }
                *cs++ = c;
-               if (--n <= 0 || c == '\n')
+               if (--n <= 0) {
+                       /*
+                        * If command doesn't fit into buffer, discard the
+                        * rest of the command and indicate truncation.
+                        * This prevents the command to be split up into
+                        * multiple commands.
+                        */
+                       while (c != '\n' && (c = getc(iop)) != EOF)
+                               ;
+                       return (-2);
+               }
+               if (c == '\n')
                        break;
        }
+got_eof:
+       sigprocmask(SIG_SETMASK, &osset, NULL);
        if (c == EOF && cs == s)
-               return (NULL);
+               return (-1);
        *cs++ = '\0';
        if (ftpdebug) {
                if (!guest && strncasecmp("pass ", s, 5) == 0) {
                        /* Don't syslog passwords */
                        syslog(LOG_DEBUG, "command: %.5s ???", s);
                } else {
-                       register char *cp;
-                       register int len;
+                       char *cp;
+                       int len;
 
                        /* Don't syslog trailing CR-LF */
                        len = strlen(s);
@@ -1256,12 +1274,11 @@ getline(s, n, iop)
                        syslog(LOG_DEBUG, "command: %.*s", len, s);
                }
        }
-       return (s);
+       return (0);
 }
 
 static void
-toolong(signo)
-       int signo;
+toolong(int signo)
 {
 
        reply(421,
@@ -1273,7 +1290,7 @@ toolong(signo)
 }
 
 static int
-yylex()
+yylex(void)
 {
        static int cpos;
        char *cp, *cp2;
@@ -1285,13 +1302,18 @@ yylex()
                switch (state) {
 
                case CMD:
-                       (void) signal(SIGALRM, toolong);
-                       (void) alarm((unsigned) timeout);
-                       if (getline(cbuf, sizeof(cbuf)-1, stdin) == NULL) {
+                       signal(SIGALRM, toolong);
+                       alarm(timeout);
+                       n = getline(cbuf, sizeof(cbuf)-1, stdin);
+                       if (n == -1) {
                                reply(221, "You could at least say goodbye.");
                                dologout(0);
+                       } else if (n == -2) {
+                               reply(500, "Command too long.");
+                               alarm(0);
+                               continue;
                        }
-                       (void) alarm(0);
+                       alarm(0);
 #ifdef SETPROCTITLE
                        if (strncasecmp(cbuf, "PASS", 4) != 0)
                                setproctitle("%s: %s", proctitle, cbuf);
@@ -1410,7 +1432,7 @@ yylex()
                                c = cbuf[cpos];
                                cbuf[cpos] = '\0';
                                yylval.u.i = atoi(cp);
-                               yylval.u.o = strtoull(cp, (char **)NULL, 10);
+                               yylval.u.o = strtoull(cp, NULL, 10);
                                cbuf[cpos] = c;
                                return (NUMBER);
                        }
@@ -1491,8 +1513,7 @@ yylex()
 }
 
 void
-upper(s)
-       char *s;
+upper(char *s)
 {
        while (*s != '\0') {
                if (islower(*s))
@@ -1502,22 +1523,19 @@ upper(s)
 }
 
 static char *
-copy(s)
-       char *s;
+copy(char *s)
 {
        char *p;
 
-       p = malloc((unsigned) strlen(s) + 1);
+       p = malloc(strlen(s) + 1);
        if (p == NULL)
                fatalerror("Ran out of memory.");
-       (void) strcpy(p, s);
+       strcpy(p, s);
        return (p);
 }
 
 static void
-help(ctab, s)
-       struct tab *ctab;
-       char *s;
+help(struct tab *ctab, char *s)
 {
        struct tab *c;
        int width, NCMDS;
@@ -1562,7 +1580,7 @@ help(ctab, s)
                        }
                        printf("\r\n");
                }
-               (void) fflush(stdout);
+               fflush(stdout);
                if (hostinfo)
                        reply(214, "Direct comments to ftp-bugs@%s.", hostname);
                else
@@ -1571,7 +1589,7 @@ help(ctab, s)
        }
        upper(s);
        c = lookup(ctab, s);
-       if (c == (struct tab *)0) {
+       if (c == NULL) {
                reply(502, "Unknown command %s.", s);
                return;
        }
@@ -1583,8 +1601,7 @@ help(ctab, s)
 }
 
 static void
-sizecmd(filename)
-       char *filename;
+sizecmd(char *filename)
 {
        switch (type) {
        case TYPE_L:
@@ -1595,7 +1612,7 @@ sizecmd(filename)
                else if (!S_ISREG(stbuf.st_mode))
                        reply(550, "%s: not a plain file.", filename);
                else
-                       reply(213, "%qu", stbuf.st_size);
+                       reply(213, "%jd", (intmax_t)stbuf.st_size);
                break; }
        case TYPE_A: {
                FILE *fin;
@@ -1609,15 +1626,15 @@ sizecmd(filename)
                }
                if (fstat(fileno(fin), &stbuf) < 0) {
                        perror_reply(550, filename);
-                       (void) fclose(fin);
+                       fclose(fin);
                        return;
                } else if (!S_ISREG(stbuf.st_mode)) {
                        reply(550, "%s: not a plain file.", filename);
-                       (void) fclose(fin);
+                       fclose(fin);
                        return;
                } else if (stbuf.st_size > MAXASIZE) {
                        reply(550, "%s: too large for type A SIZE.", filename);
-                       (void) fclose(fin);
+                       fclose(fin);
                        return;
                }
 
@@ -1627,9 +1644,9 @@ sizecmd(filename)
                                count++;
                        count++;
                }
-               (void) fclose(fin);
+               fclose(fin);
 
-               reply(213, "%qd", count);
+               reply(213, "%jd", (intmax_t)count);
                break; }
        default:
                reply(504, "SIZE not implemented for type %s.",
@@ -1639,8 +1656,7 @@ sizecmd(filename)
 
 /* Return 1, if port check is done. Return 0, if not yet. */
 static int
-port_check(pcmd)
-       const char *pcmd;
+port_check(const char *pcmd)
 {
        if (his_addr.su_family == AF_INET) {
                if (data_dest.su_family != AF_INET) {
@@ -1658,7 +1674,7 @@ port_check(pcmd)
                } else {
                        usedefault = 0;
                        if (pdata >= 0) {
-                               (void) close(pdata);
+                               close(pdata);
                                pdata = -1;
                        }
                        reply(200, "%s command successful.", pcmd);
@@ -1669,7 +1685,7 @@ port_check(pcmd)
 }
 
 static int
-check_login1()
+check_login1(void)
 {
        if (logged_in)
                return 1;
@@ -1679,11 +1695,92 @@ check_login1()
        }
 }
 
+/*
+ * Replace leading "~user" in a pathname by the user's login directory.
+ * Returned string will be in a freshly malloced buffer unless it's NULL.
+ */
+static char *
+exptilde(char *s)
+{
+       char *p, *q;
+       char *path, *user;
+       struct passwd *ppw;
+
+       if ((p = strdup(s)) == NULL)
+               return (NULL);
+       if (*p != '~')
+               return (p);
+
+       user = p + 1;   /* skip tilde */
+       if ((path = strchr(p, '/')) != NULL)
+               *(path++) = '\0'; /* separate ~user from the rest of path */
+       if (*user == '\0') /* no user specified, use the current user */
+               user = pw->pw_name;
+       /* read passwd even for the current user since we may be chrooted */
+       if ((ppw = getpwnam(user)) != NULL) {
+               /* user found, substitute login directory for ~user */
+               if (path)
+                       asprintf(&q, "%s/%s", ppw->pw_dir, path);
+               else
+                       q = strdup(ppw->pw_dir);
+               free(p);
+               p = q;
+       } else {
+               /* user not found, undo the damage */
+               if (path)
+                       path[-1] = '/';
+       }
+       return (p);
+}
+
+/*
+ * Expand glob(3) patterns possibly present in a pathname.
+ * Avoid expanding to a pathname including '\r' or '\n' in order to
+ * not disrupt the FTP protocol.
+ * The expansion found must be unique.
+ * Return the result as a malloced string, or NULL if an error occured.
+ *
+ * Problem: this production is used for all pathname
+ * processing, but only gives a 550 error reply.
+ * This is a valid reply in some cases but not in others.
+ */
+static char *
+expglob(char *s)
+{
+       char *p, **pp, *rval;
+       int flags = GLOB_BRACE | GLOB_NOCHECK;
+       int n;
+       glob_t gl;
+
+       memset(&gl, 0, sizeof(gl));
+       flags |= GLOB_LIMIT;
+       gl.gl_matchc = MAXGLOBARGS;
+       if (glob(s, flags, NULL, &gl) == 0 && gl.gl_pathc != 0) {
+               for (pp = gl.gl_pathv, p = NULL, n = 0; *pp; pp++)
+                       if (*(*pp + strcspn(*pp, "\r\n")) == '\0') {
+                               p = *pp;
+                               n++;
+                       }
+               if (n == 0)
+                       rval = strdup(s);
+               else if (n == 1)
+                       rval = strdup(p);
+               else {
+                       reply(550, "Wildcard is ambiguous.");
+                       rval = NULL;
+               }
+       } else {
+               reply(550, "Wildcard expansion error.");
+               rval = NULL;
+       }
+       globfree(&gl);
+       return (rval);
+}
+
 #ifdef INET6
 /* Return 1, if port check is done. Return 0, if not yet. */
 static int
-port_check_v6(pcmd)
-       const char *pcmd;
+port_check_v6(const char *pcmd)
 {
        if (his_addr.su_family == AF_INET6) {
                if (IN6_IS_ADDR_V4MAPPED(&his_addr.su_sin6.sin6_addr))
@@ -1704,7 +1801,7 @@ port_check_v6(pcmd)
                } else {
                        usedefault = 0;
                        if (pdata >= 0) {
-                               (void) close(pdata);
+                               close(pdata);
                                pdata = -1;
                        }
                        reply(200, "%s command successful.", pcmd);
@@ -1715,7 +1812,7 @@ port_check_v6(pcmd)
 }
 
 static void
-v4map_data_dest()
+v4map_data_dest(void)
 {
        struct in_addr savedaddr;
        int savedport;
index f900d1d..63545c7 100644 (file)
 .\" SUCH DAMAGE.
 .\"
 .\"     @(#)ftpd.8     8.2 (Berkeley) 4/19/94
-.\" $FreeBSD: src/libexec/ftpd/ftpd.8,v 1.31.2.18 2003/02/11 14:28:28 yar Exp $
+.\" $FreeBSD: src/libexec/ftpd/ftpd.8,v 1.74 2007/04/20 09:08:20 trhodes Exp $
 .\" $DragonFly: src/libexec/ftpd/ftpd.8,v 1.7 2008/05/02 02:05:04 swildner Exp $
 .\"
-.Dd January 27, 2000
+.Dd April 20, 2007
 .Dt FTPD 8
 .Os
 .Sh NAME
 .Nd Internet File Transfer Protocol server
 .Sh SYNOPSIS
 .Nm
-.Op Fl 46AdDEhmMoOrRSUvW
+.Op Fl 468ADdEhMmOoRrSUvW
 .Op Fl l Op Fl l
 .Op Fl a Ar address
 .Op Fl H Ar host
-.Op Fl p Ar file
 .Op Fl P Ar port
-.Op Fl t Ar timeout
+.Op Fl p Ar file
 .Op Fl T Ar maxtimeout
+.Op Fl t Ar timeout
 .Op Fl u Ar umask
 .Sh DESCRIPTION
-.Nm Ftpd
-is the
+The
+.Nm
+utility is the
 Internet File Transfer Protocol
-server process.  The server uses the
+server process.
+The server uses the
 .Tn TCP
 protocol
 and listens at the port specified with the
@@ -69,15 +71,7 @@ Available options:
 .It Fl 4
 When
 .Fl D
-is specified, accept IPv4 connections.
-When
-.Fl 6
-is also specified, accept IPv4 connection via
-.Dv AF_INET6
-socket.
-When
-.Fl 6
-is not specified, accept IPv4 connection via
+is specified, accept connections via
 .Dv AF_INET
 socket.
 .It Fl 6
@@ -86,16 +80,27 @@ When
 is specified, accept connections via
 .Dv AF_INET6
 socket.
+.It Fl 8
+Enable transparent UTF-8 mode.
+RFC\ 2640 compliant clients will be told that the character encoding
+used by the server is UTF-8, which is the only effect of the option.
+.Pp
+This option does not enable any encoding conversion for server file names;
+it implies instead that the names of files on the server are encoded
+in UTF-8.
+As for files uploaded via FTP, it is the duty of the RFC\ 2640 compliant
+client to convert their names from the client's local encoding to UTF-8.
+FTP command names and own
+.Nm
+messages are always encoded in ASCII, which is a subset of UTF-8.
+Hence no need for server-side conversion at all.
+.It Fl A
+Allow only anonymous ftp access.
 .It Fl a
 When
 .Fl D
 is specified, accept connections only on the specified
 .Ar address .
-.It Fl A
-Allow only anonymous ftp access.
-.It Fl d
-Debugging information is written to the syslog using
-.Dv LOG_FTP .
 .It Fl D
 With this option set,
 .Nm
@@ -106,6 +111,9 @@ This is lower overhead than starting
 from
 .Xr inetd 8
 and is thus useful on busy servers to reduce load.
+.It Fl d
+Debugging information is written to the syslog using
+.Dv LOG_FTP .
 .It Fl E
 Disable the EPSV command.
 This is useful for servers behind older firewalls.
@@ -125,35 +133,26 @@ session is logged using syslog with a facility of
 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.
-Note:
-.Dv LOG_FTP
-messages
-are not displayed by
+By default,
 .Xr syslogd 8
-by default, and may have to be enabled in
-.Xr syslogd 8 Ns 's
-configuration file.
+logs these to
+.Pa /var/log/xferlog .
+.It Fl M
+Prevent anonymous users from creating directories.
 .It Fl m
 Permit anonymous users to overwrite or modify
-existing files if allowed by filesystem permissions.
+existing files if allowed by file system permissions.
 By default, anonymous users cannot modify existing files;
 in particular, files to upload will be created under a unique name.
-.It Fl M
-Prevent anonymous users from creating directories.
-.It Fl o
-Put server in write-only mode.
-RETR is disabled, preventing downloads.
 .It Fl O
 Put server in write-only mode for anonymous users only.
 RETR is disabled for anonymous users, preventing anonymous downloads.
 This has no effect if
 .Fl o
 is also specified.
-.It Fl p
-When
-.Fl D
-is specified, write the daemon's process ID to
-.Ar file .
+.It Fl o
+Put server in write-only mode.
+RETR is disabled, preventing downloads.
 .It Fl P
 When
 .Fl D
@@ -162,9 +161,13 @@ is specified, accept connections at
 specified as a numeric value or service name, instead of at the default
 .Dq ftp
 port.
-.It Fl r
-Put server in read-only mode.
-All commands which may modify the local filesystem are disabled.
+.It Fl p
+When
+.Fl D
+is specified, write the daemon's process ID to
+.Ar file
+instead of the default pid file,
+.Pa /var/run/ftpd.pid .
 .It Fl R
 With this option set,
 .Nm
@@ -175,16 +178,15 @@ Currently,
 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 r
+Put server in read-only mode.
+All commands which may modify the local file system are disabled.
 .It Fl S
 With this option set,
 .Nm
 logs all anonymous file downloads to the file
 .Pa /var/log/ftpd
 when this file exists.
-.It Fl t
-The inactivity timeout period is set to
-.Ar timeout
-seconds (the default is 15 minutes).
 .It Fl T
 A client may also request a different timeout period;
 the maximum period allowed may be set to
@@ -193,6 +195,24 @@ seconds with the
 .Fl T
 option.
 The default limit is 2 hours.
+.It Fl t
+The inactivity timeout period is set to
+.Ar timeout
+seconds (the default is 15 minutes).
+.It Fl U
+This option instructs ftpd to use data ports in the range of
+.Dv IP_PORTRANGE_DEFAULT
+instead of in the range of
+.Dv IP_PORTRANGE_HIGH .
+Such a change may be useful for some specific firewall configurations;
+see
+.Xr ip 4
+for more information.
+.Pp
+Note that option is a virtual no-op in
+.Fx 5.0
+and above; both port
+ranges are indentical by default.
 .It Fl u
 The default file creation mode mask is set to
 .Ar umask ,
@@ -200,18 +220,13 @@ which is expected to be an octal numeric value.
 Refer to
 .Xr umask 2
 for details.
-.It Fl U
-In previous versions of
-.Nm ,
-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 49152..65535.  Specifying this
-option will revert to the old behavior.
+This option may be overridden by
+.Xr login.conf 5 .
 .It Fl v
 A synonym for
 .Fl d .
 .It Fl W
-Don't log FTP sessions to
+Do not log FTP sessions to
 .Pa /var/log/wtmp .
 .El
 .Pp
@@ -232,13 +247,16 @@ If the file
 .Pa /etc/ftpmotd
 exists,
 .Nm
-prints it after a successful login.  Note the motd file used is the one
-relative to the login environment.  This means the one in
+prints it after a successful login.
+Note the motd file used is the one
+relative to the login environment.
+This means the one in
 .Pa ~ftp/etc
 in the anonymous user's case.
 .Pp
 The ftp server currently supports the following ftp requests.
-The case of the requests is ignored.  Requests marked [RW] are
+The case of the requests is ignored.
+Requests marked [RW] are
 disabled if
 .Fl r
 is specified.
@@ -253,6 +271,7 @@ is specified.
 .It DELE Ta "delete a file [RW]"
 .It EPRT Ta "specify data connection port, multiprotocol"
 .It EPSV Ta "prepare for server-to-server transfer, multiprotocol"
+.It FEAT Ta "give information on extended features of server"
 .It HELP Ta "give help information"
 .It LIST Ta "give list files in a directory" Pq Dq Li "ls -lgA"
 .It LPRT Ta "specify data connection port, multiprotocol"
@@ -323,32 +342,33 @@ STAT
 command is received during a data transfer, preceded by a Telnet IP
 and Synch, transfer status will be returned.
 .Pp
-.Nm Ftpd
-interprets file names according to the
+The
+.Nm
+utility interprets file names according to the
 .Dq globbing
 conventions used by
 .Xr csh 1 .
 This allows users to utilize the metacharacters
 .Dq Li \&*?[]{}~ .
 .Pp
-.Nm Ftpd
-authenticates users according to six rules.
+The
+.Nm
+utility authenticates users according to six rules.
 .Bl -enum -offset indent
 .It
 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.
+If the user has an OPIE key, the response from a successful USER
+command will include an OPIE challenge.
 The client may choose to respond with a PASS command giving either
-a standard password or an S/Key one-time password.
+a standard password or an OPIE 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.
+.Xr opie 4
+for more information on OPIE authentication.
 .It
 The login name must not appear in the file
 .Pa /etc/ftpusers .
@@ -365,7 +385,7 @@ The user must have a standard shell returned by
 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
+i.e., one prefixed with
 .Ql \&@ ,
 the session's root will be changed to the directory specified
 in this file or to the user's login directory by
@@ -413,7 +433,7 @@ user.
 As a special case if the
 .Dq ftp
 user's home directory pathname contains the
-.Dq \&/./
+.Pa /./
 separator,
 .Nm
 uses its left-hand side as the name of the directory to do
@@ -508,17 +528,19 @@ value is to be used.
 As with any anonymous login configuration, due care must be given
 to setup and maintenance to guard against security related problems.
 .Pp
+The
 .Nm
-has internal support for handling remote requests to list
+utility has internal support for handling remote requests to list
 files, and will not execute
 .Pa /bin/ls
-in either a chrooted or non-chrooted environment.  The
+in either a chrooted or non-chrooted environment.
+The
 .Pa ~/bin/ls
 executable need not be placed into the chrooted tree, nor need the
 .Pa ~/bin
 directory exist.
 .Sh FILES
-.Bl -tag -width /etc/ftpwelcome -compact
+.Bl -tag -width ".Pa /var/run/ftpd.pid" -compact
 .It Pa /etc/ftpusers
 List of unwelcome/restricted users.
 .It Pa /etc/ftpchroot
@@ -529,16 +551,20 @@ Virtual hosting configuration file.
 Welcome notice.
 .It Pa /etc/ftpmotd
 Welcome notice after login.
+.It Pa /var/run/ftpd.pid
+Default pid file for daemon mode.
 .It Pa /var/run/nologin
 Displayed and access refused.
 .It Pa /var/log/ftpd
 Log file for anonymous transfers.
+.It Pa /var/log/xferlog
+Default place for session logs.
 .El
 .Sh SEE ALSO
 .Xr ftp 1 ,
-.Xr key 1 ,
 .Xr umask 2 ,
 .Xr getusershell 3 ,
+.Xr opie 4 ,
 .Xr ftpchroot 5 ,
 .Xr login.conf 5 ,
 .Xr inetd 8 ,
@@ -546,13 +572,15 @@ Log file for anonymous transfers.
 .Sh HISTORY
 The
 .Nm
-command appeared in
+utility appeared in
 .Bx 4.2 .
 IPv6 support was added in WIDE Hydrangea IPv6 stack kit.
 .Sh BUGS
 The server must run as the super-user
-to create sockets with privileged port numbers.  It maintains
+to create sockets with privileged port numbers.
+It maintains
 an effective user id of the logged in user, reverting to
-the super-user only when binding addresses to sockets.  The
+the super-user only when binding addresses to sockets.
+The
 possible security holes have been extensively
 scrutinized, but are possibly incomplete.
index 960de72..cefdd99 100644 (file)
  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
  * SUCH DAMAGE.
  *
- * @(#) Copyright (c) 1985, 1988, 1990, 1992, 1993, 1994 The Regents of the University of California.  All rights reserved.
  * @(#)ftpd.c  8.4 (Berkeley) 4/16/94
+ * $FreeBSD: src/libexec/ftpd/ftpd.c,v 1.213 2008/12/23 01:23:09 cperciva Exp $
+ * $DragonFly: src/libexec/ftpd/ftpd.c,v 1.7 2005/10/28 18:06:57 joerg Exp $
  */
 
-#if 0
-static const char rcsid[] =
-  "$FreeBSD: src/libexec/ftpd/ftpd.c,v 1.62.2.48 2003/02/14 12:42:42 yar Exp $";
-  "$DragonFly: src/libexec/ftpd/ftpd.c,v 1.7 2005/10/28 18:06:57 joerg Exp $";
-#endif /* not lint */
-
 /*
  * FTP server.
  */
@@ -71,7 +66,9 @@ static const char rcsid[] =
 #include <netdb.h>
 #include <pwd.h>
 #include <grp.h>
+#include <opie.h>
 #include <signal.h>
+#include <stdint.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
@@ -83,22 +80,15 @@ static const char rcsid[] =
 #include <login_cap.h>
 #endif
 
-#ifdef SKEY
-#include <skey.h>
-#endif
-
-#if !defined(NOPAM)
+#ifdef USE_PAM
 #include <security/pam_appl.h>
 #endif
 
 #include "pathnames.h"
 #include "extern.h"
+#include "pidfile.h"
 
-#if __STDC__
 #include <stdarg.h>
-#else
-#include <varargs.h>
-#endif
 
 static char version[] = "Version 6.00LS";
 #undef main
@@ -106,7 +96,6 @@ static char version[] = "Version 6.00LS";
 extern off_t restart_point;
 extern char cbuf[];
 
-union sockunion server_addr;
 union sockunion ctrl_addr;
 union sockunion data_source;
 union sockunion data_dest;
@@ -127,8 +116,10 @@ int        logging;
 int    restricted_data_ports = 1;
 int    paranoid = 1;     /* be extra careful about security */
 int    anon_only = 0;    /* Only anonymous ftp allowed */
+int    assumeutf8 = 0;   /* Assume that server file names are in UTF-8 */
 int    guest;
 int    dochroot;
+char   *chrootdir;
 int    dowtmp = 1;
 int    stats;
 int    statfd = -1;
@@ -138,15 +129,13 @@ int       stru;                   /* avoid C keyword */
 int    mode;
 int    usedefault = 1;         /* for data transfers */
 int    pdata = -1;             /* for passive mode */
-int    readonly=0;             /* Server is in readonly mode.  */
-int    noepsv=0;               /* EPSV command is disabled.    */
-int    noretr=0;               /* RETR command is disabled.    */
-int    noguestretr=0;          /* RETR command is disabled for anon users. */
-int    noguestmkd=0;           /* MKD command is disabled for anon users. */
-int    noguestmod=1;           /* anon users may not modify existing files. */
-
-static volatile sig_atomic_t recvurg;
-sig_atomic_t transflag;
+int    readonly = 0;           /* Server is in readonly mode.  */
+int    noepsv = 0;             /* EPSV command is disabled.    */
+int    noretr = 0;             /* RETR command is disabled.    */
+int    noguestretr = 0;        /* RETR command is disabled for anon users. */
+int    noguestmkd = 0;         /* MKD command is disabled for anon users. */
+int    noguestmod = 1;         /* anon users may not modify existing files. */
+
 off_t  file_size;
 off_t  byte_count;
 #if !defined(CMASK) || CMASK == 0
@@ -172,17 +161,22 @@ static struct ftphost {
 } *thishost, *firsthost;
 
 #endif
-char   remotehost[MAXHOSTNAMELEN];
+char   remotehost[NI_MAXHOST];
 char   *ident = NULL;
 
-static char ttyline[20];
-char   *tty = ttyline;         /* for klogin */
+static char    ttyline[20];
+char           *tty = ttyline;         /* for klogin */
 
-#if !defined(NOPAM)
-static int     auth_pam (struct passwd**, const char*);
+#ifdef USE_PAM
+static int     auth_pam(struct passwd**, const char*);
+pam_handle_t   *pamh = NULL;
 #endif
 
-char   *pid_file = NULL;
+static struct opie     opiedata;
+static char            opieprompt[OPIE_CHALLENGE_MAX+1];
+static int             pwok;
+
+char   *pid_file = NULL; /* means default location to pidfile(3) */
 
 /*
  * Limit number of pathnames that glob can return.
@@ -210,81 +204,68 @@ char      *LastArgv = NULL;       /* end of argv */
 char   proctitle[LINE_MAX];    /* initial part of title */
 #endif /* SETPROCTITLE */
 
-#ifdef SKEY
-int    pwok = 0;
-#endif
+#define LOGCMD(cmd, file)              logcmd((cmd), (file), NULL, -1)
+#define LOGCMD2(cmd, file1, file2)     logcmd((cmd), (file1), (file2), -1)
+#define LOGBYTES(cmd, file, cnt)       logcmd((cmd), (file), NULL, (cnt))
+
+static volatile sig_atomic_t recvurg;
+static int transflag;          /* NB: for debugging only */
+
+#define STARTXFER      flagxfer(1)
+#define ENDXFER                flagxfer(0)
 
-#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); \
+#define START_UNSAFE   maskurg(1)
+#define END_UNSAFE     maskurg(0)
+
+/* It's OK to put an `else' clause after this macro. */
+#define CHECKOOB(action)                                               \
+       if (recvurg) {                                                  \
+               recvurg = 0;                                            \
+               if (myoob()) {                                          \
+                       ENDXFER;                                        \
+                       action;                                         \
+               }                                                       \
        }
 
 #ifdef VIRTUAL_HOSTING
-static void     inithosts (void);
-static void    selecthost (union sockunion *);
+static void     inithosts(int);
+static void     selecthost(union sockunion *);
 #endif
-static void     ack (char *);
-static void     sigurg (int);
-static void     myoob (void);
-static int      checkuser (char *, char *, int, char **);
-static FILE    *dataconn (char *, off_t, char *);
-static void     dolog (struct sockaddr *);
-static char    *curdir (void);
-static void     end_login (void);
-static FILE    *getdatasock (char *);
-static int      guniquefd (char *, char **);
-static void     lostconn (int);
-static void     sigquit (int);
-static int      receive_data (FILE *, FILE *);
-static int      send_data (FILE *, FILE *, off_t, off_t, int);
+static void     ack(char *);
+static void     sigurg(int);
+static void     maskurg(int);
+static void     flagxfer(int);
+static int      myoob(void);
+static int      checkuser(char *, char *, int, char **);
+static FILE    *dataconn(char *, off_t, char *);
+static void     dolog(struct sockaddr *);
+static void     end_login(void);
+static FILE    *getdatasock(char *);
+static int      guniquefd(char *, char **);
+static void     lostconn(int);
+static void     sigquit(int);
+static int      receive_data(FILE *, FILE *);
+static int      send_data(FILE *, FILE *, size_t, off_t, int);
 static struct passwd *
-                sgetpwnam (char *);
-static char    *sgetsave (char *);
-static void     reapchild (int);
-static void      logxfer (char *, off_t, time_t);
-static char    *doublequote (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);
-}
+                sgetpwnam(char *);
+static char    *sgetsave(char *);
+static void     reapchild(int);
+static void     appendf(char **, char *, ...) __printflike(2, 3);
+static void     logcmd(char *, char *, char *, off_t);
+static void      logxfer(char *, off_t, time_t);
+static char    *doublequote(char *);
+static int     *socksetup(int, char *, const char *);
 
 int
-main(argc, argv, envp)
-       int argc;
-       char *argv[];
-       char **envp;
+main(int argc, char *argv[], char **envp)
 {
-       int addrlen, ch, on = 1, tos;
+       socklen_t addrlen;
+       int ch, on = 1, tos;
        char *cp, line[LINE_MAX];
        FILE *fd;
-       int error;
        char    *bindname = NULL;
        const char *bindport = "ftp";
        int     family = AF_UNSPEC;
-       int     enable_v4 = 0;
        struct sigaction sa;
 
        tzset();                /* in case no timezone database in ~ftp */
@@ -301,18 +282,33 @@ main(argc, argv, envp)
        LastArgv = envp[-1] + strlen(envp[-1]);
 #endif /* OLD_SETPROCTITLE */
 
+       /*
+        * Prevent diagnostic messages from appearing on stderr.
+        * We run as a daemon or from inetd; in both cases, there's
+        * more reason in logging to syslog.
+        */
+       freopen(_PATH_DEVNULL, "w", stderr);
+       opterr = 0;
+
+       /*
+        * 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);
 
        while ((ch = getopt(argc, argv,
-                           "46a:AdDEH:hlmMoOp:P:rRSt:T:u:UvW")) != -1) {
+                           "468a:AdDEH:hlmMoOp:P:rRSt:T:u:UvW")) != -1) {
                switch (ch) {
                case '4':
-                       enable_v4 = 1;
-                       if (family == AF_UNSPEC)
-                               family = AF_INET;
+                       family = (family == AF_INET6) ? AF_UNSPEC : AF_INET;
                        break;
 
                case '6':
-                       family = AF_INET6;
+                       family = (family == AF_INET) ? AF_UNSPEC : AF_INET6;
+                       break;
+
+               case '8':
+                       assumeutf8 = 1;
                        break;
 
                case 'a':
@@ -401,7 +397,7 @@ main(argc, argv, envp)
 
                        val = strtol(optarg, &optarg, 8);
                        if (*optarg != '\0' || val < 0)
-                               warnx("bad value for -u");
+                               syslog(LOG_WARNING, "bad value for -u");
                        else
                                defumask = val;
                        break;
@@ -419,25 +415,25 @@ main(argc, argv, envp)
                        break;
 
                default:
-                       warnx("unknown flag -%c ignored", optopt);
+                       syslog(LOG_WARNING, "unknown flag -%c ignored", optopt);
                        break;
                }
        }
 
-#ifdef VIRTUAL_HOSTING
-       inithosts();
-#endif
-       (void) freopen(_PATH_DEVNULL, "w", stderr);
-
-       /*
-        * 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 addrinfo hints, *res;
+               int *ctl_sock, fd, maxfd = -1, nfds, i;
+               fd_set defreadfds, readfds;
+               pid_t pid;
+               struct pidfh *pfh;
+
+               if ((pfh = pidfile_open(pid_file, 0600, &pid)) == NULL) {
+                       if (errno == EEXIST) {
+                               syslog(LOG_ERR, "%s already running, pid %d",
+                                      getprogname(), (int)pid);
+                               exit(1);
+                       }
+                       syslog(LOG_WARNING, "pidfile_open: %m");
+               }
 
                /*
                 * Detach from parent.
@@ -446,103 +442,79 @@ main(argc, argv, envp)
                        syslog(LOG_ERR, "failed to become a daemon");
                        exit(1);
                }
+
+               if (pfh != NULL && pidfile_write(pfh) == -1)
+                       syslog(LOG_WARNING, "pidfile_write: %m");
+
                sa.sa_handler = reapchild;
-               (void)sigaction(SIGCHLD, &sa, NULL);
-               /* init bind_sa */
-               memset(&hints, 0, sizeof(hints));
-
-               hints.ai_family = family == AF_UNSPEC ? AF_INET : family;
-               hints.ai_socktype = SOCK_STREAM;
-               hints.ai_protocol = 0;
-               hints.ai_flags = AI_PASSIVE;
-               error = getaddrinfo(bindname, bindport, &hints, &res);
-               if (error) {
-                       if (family == AF_UNSPEC) {
-                               hints.ai_family = AF_UNSPEC;
-                               error = getaddrinfo(bindname, bindport, &hints,
-                                                   &res);
-                       }
-               }
-               if (error) {
-                       syslog(LOG_ERR, "%s", gai_strerror(error));
-                       if (error == EAI_SYSTEM)
-                               syslog(LOG_ERR, "%s", strerror(errno));
-                       exit(1);
-               }
-               if (res->ai_addr == NULL) {
-                       syslog(LOG_ERR, "-a %s: getaddrinfo failed", hostname);
-                       exit(1);
-               } else
-                       family = res->ai_addr->sa_family;
+               sigaction(SIGCHLD, &sa, NULL);
+
+#ifdef VIRTUAL_HOSTING
+               inithosts(family);
+#endif
+
                /*
                 * Open a socket, bind it to the FTP port, and start
                 * listening.
                 */
-               ctl_sock = socket(family, SOCK_STREAM, 0);
-               if (ctl_sock < 0) {
-                       syslog(LOG_ERR, "control socket: %m");
+               ctl_sock = socksetup(family, bindname, bindport);
+               if (ctl_sock == NULL)
                        exit(1);
-               }
-               if (setsockopt(ctl_sock, SOL_SOCKET, SO_REUSEADDR,
-                   &on, sizeof(on)) < 0)
-                       syslog(LOG_WARNING,
-                              "control setsockopt (SO_REUSEADDR): %m");
-               if (family == AF_INET6 && enable_v4 == 0) {
-                       if (setsockopt(ctl_sock, IPPROTO_IPV6, IPV6_V6ONLY,
-                                      &on, sizeof (on)) < 0)
-                               syslog(LOG_WARNING,
-                                      "control setsockopt (IPV6_V6ONLY): %m");
-               }
-               memcpy(&server_addr, res->ai_addr, res->ai_addr->sa_len);
-               if (bind(ctl_sock, (struct sockaddr *)&server_addr,
-                        server_addr.su_len) < 0) {
-                       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);
+
+               FD_ZERO(&defreadfds);
+               for (i = 1; i <= *ctl_sock; i++) {
+                       FD_SET(ctl_sock[i], &defreadfds);
+                       if (listen(ctl_sock[i], 32) < 0) {
+                               syslog(LOG_ERR, "control listen: %m");
+                               exit(1);
                        }
-                       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 */
+                       if (maxfd < ctl_sock[i])
+                               maxfd = ctl_sock[i];
                }
+
                /*
                 * Loop forever accepting connection requests and forking off
                 * children to handle them.
                 */
                while (1) {
-                       addrlen = server_addr.su_len;
-                       fd = accept(ctl_sock, (struct sockaddr *)&his_addr, &addrlen);
-
-                       if (fd >= 0) {
-                               if (fork() == 0) {
-                                       /* child */
-                                       (void) dup2(fd, 0);
-                                       (void) dup2(fd, 1);
-                                       close(ctl_sock);
-                                       break;
-                               }
-                               close(fd);
+                       FD_COPY(&defreadfds, &readfds);
+                       nfds = select(maxfd + 1, &readfds, NULL, NULL, 0);
+                       if (nfds <= 0) {
+                               if (nfds < 0 && errno != EINTR)
+                                       syslog(LOG_WARNING, "select: %m");
+                               continue;
                        }
+
+                       pid = -1;
+                        for (i = 1; i <= *ctl_sock; i++)
+                               if (FD_ISSET(ctl_sock[i], &readfds)) {
+                                       addrlen = sizeof(his_addr);
+                                       fd = accept(ctl_sock[i],
+                                           (struct sockaddr *)&his_addr,
+                                           &addrlen);
+                                       if (fd == -1) {
+                                               syslog(LOG_WARNING,
+                                                      "accept: %m");
+                                               continue;
+                                       }
+                                       switch (pid = fork()) {
+                                       case 0:
+                                               /* child */
+                                               dup2(fd, 0);
+                                               dup2(fd, 1);
+                                               close(fd);
+                                               for (i = 1; i <= *ctl_sock; i++)
+                                                       close(ctl_sock[i]);
+                                               if (pfh != NULL)
+                                                       pidfile_close(pfh);
+                                               goto gotchild;
+                                       case -1:
+                                               syslog(LOG_WARNING, "fork: %m");
+                                               /* FALLTHROUGH */
+                                       default:
+                                               close(fd);
+                                       }
+                               }
                }
        } else {
                addrlen = sizeof(his_addr);
@@ -550,25 +522,35 @@ main(argc, argv, envp)
                        syslog(LOG_ERR, "getpeername (%s): %m",argv[0]);
                        exit(1);
                }
+
+#ifdef VIRTUAL_HOSTING
+               if (his_addr.su_family == AF_INET6 &&
+                   IN6_IS_ADDR_V4MAPPED(&his_addr.su_sin6.sin6_addr))
+                       family = AF_INET;
+               else
+                       family = his_addr.su_family;
+               inithosts(family);
+#endif
        }
 
+gotchild:
        sa.sa_handler = SIG_DFL;
-       (void)sigaction(SIGCHLD, &sa, NULL);
+       sigaction(SIGCHLD, &sa, NULL);
 
        sa.sa_handler = sigurg;
        sa.sa_flags = 0;                /* don't restart syscalls for SIGURG */
-       (void)sigaction(SIGURG, &sa, NULL);
+       sigaction(SIGURG, &sa, NULL);
 
        sigfillset(&sa.sa_mask);        /* block all signals in handler */
        sa.sa_flags = SA_RESTART;
        sa.sa_handler = sigquit;
-       (void)sigaction(SIGHUP, &sa, NULL);
-       (void)sigaction(SIGINT, &sa, NULL);
-       (void)sigaction(SIGQUIT, &sa, NULL);
-       (void)sigaction(SIGTERM, &sa, NULL);
+       sigaction(SIGHUP, &sa, NULL);
+       sigaction(SIGINT, &sa, NULL);
+       sigaction(SIGQUIT, &sa, NULL);
+       sigaction(SIGTERM, &sa, NULL);
 
        sa.sa_handler = lostconn;
-       (void)sigaction(SIGPIPE, &sa, NULL);
+       sigaction(SIGPIPE, &sa, NULL);
 
        addrlen = sizeof(ctrl_addr);
        if (getsockname(0, (struct sockaddr *)&ctrl_addr, &addrlen) < 0) {
@@ -598,7 +580,7 @@ main(argc, argv, envp)
        data_source.su_port = htons(ntohs(ctrl_addr.su_port) - 1);
 
        /* set this here so klogin can use it... */
-       (void)snprintf(ttyline, sizeof(ttyline), "ftp%d", getpid());
+       snprintf(ttyline, sizeof(ttyline), "ftp%d", getpid());
 
        /* Try to handle urgent data inline */
 #ifdef SO_OOBINLINE
@@ -628,30 +610,32 @@ main(argc, argv, envp)
                                *cp = '\0';
                        lreply(530, "%s", line);
                }
-               (void) fflush(stdout);
-               (void) fclose(fd);
+               fflush(stdout);
+               fclose(fd);
                reply(530, "System not available.");
                exit(0);
        }
 #ifdef VIRTUAL_HOSTING
-       if ((fd = fopen(thishost->welcome, "r")) != NULL) {
+       fd = fopen(thishost->welcome, "r");
 #else
-       if ((fd = fopen(_PATH_FTPWELCOME, "r")) != NULL) {
+       fd = fopen(_PATH_FTPWELCOME, "r");
 #endif
+       if (fd != 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);
+               fflush(stdout);
+               fclose(fd);
                /* reply(220,) must follow */
        }
 #ifndef VIRTUAL_HOSTING
        if (hostname == NULL) {
                if ((hostname = malloc(MAXHOSTNAMELEN)) == NULL)
                        fatalerror("Ran out of memory.");
-               gethostname(hostname, MAXHOSTNAMELEN - 1);
+               if (gethostname(hostname, MAXHOSTNAMELEN - 1) < 0)
+                       hostname[0] = '\0';
                hostname[MAXHOSTNAMELEN - 1] = '\0';
        }
 #endif
@@ -660,13 +644,12 @@ main(argc, argv, envp)
        else
                reply(220, "FTP server ready.");
        for (;;)
-               (void) yyparse();
+               yyparse();
        /* NOTREACHED */
 }
 
 static void
-lostconn(signo)
-       int signo;
+lostconn(int signo)
 {
 
        if (ftpdebug)
@@ -675,8 +658,7 @@ lostconn(signo)
 }
 
 static void
-sigquit(signo)
-       int signo;
+sigquit(int signo)
 {
 
        syslog(LOG_ERR, "got signal %d", signo);
@@ -689,7 +671,7 @@ sigquit(signo)
  */
 
 static void
-inithosts()
+inithosts(int family)
 {
        int insert;
        size_t len;
@@ -705,7 +687,7 @@ inithosts()
        if (hostname == NULL) {
                if ((hostname = malloc(MAXHOSTNAMELEN)) == NULL)
                        fatalerror("Ran out of memory.");
-               if (gethostname(hostname, MAXHOSTNAMELEN) < 0)
+               if (gethostname(hostname, MAXHOSTNAMELEN - 1) < 0)
                        hostname[0] = '\0';
                hostname[MAXHOSTNAMELEN - 1] = '\0';
        }
@@ -715,8 +697,9 @@ inithosts()
        hrp->hostinfo = NULL;
 
        memset(&hints, 0, sizeof(hints));
-       hints.ai_flags = AI_CANONNAME;
-       hints.ai_family = AF_UNSPEC;
+       hints.ai_flags = AI_PASSIVE;
+       hints.ai_family = family;
+       hints.ai_socktype = SOCK_STREAM;
        if (getaddrinfo(hrp->hostname, NULL, &hints, &res) == 0)
                hrp->hostinfo = res;
        hrp->statfile = _PATH_FTPDSTATFILE;
@@ -786,9 +769,9 @@ inithosts()
                                                /* NOTREACHED */
                                        }
 
-                       hints.ai_flags = 0;
-                       hints.ai_family = AF_UNSPEC;
                        hints.ai_flags = AI_PASSIVE;
+                       hints.ai_family = family;
+                       hints.ai_socktype = SOCK_STREAM;
                        if (getaddrinfo(vhost, NULL, &hints, &res) != 0)
                                goto nextline;
                        for (ai = res; ai != NULL && ai->ai_addr != NULL;
@@ -892,13 +875,12 @@ nextline:
                        if (mp)
                                free(mp);
                }
-               (void) fclose(fp);
+               fclose(fp);
        }
 }
 
 static void
-selecthost(su)
-       union sockunion *su;
+selecthost(union sockunion *su)
 {
        struct ftphost  *hrp;
        u_int16_t port;
@@ -924,7 +906,7 @@ selecthost(su)
            for (hi = hrp->hostinfo; hi != NULL; hi = hi->ai_next) {
                if (memcmp(su, hi->ai_addr, hi->ai_addrlen) == 0) {
                        thishost = hrp;
-                       break;
+                       goto found;
                }
 #ifdef INET6
                /* XXX IPv4 mapped IPv6 addr consideraton */
@@ -933,12 +915,13 @@ selecthost(su)
                            &((struct sockaddr_in *)hi->ai_addr)->sin_addr,
                            sizeof(struct in_addr)) == 0)) {
                        thishost = hrp;
-                       break;
+                       goto found;
                }
 #endif
            }
            hrp = hrp->next;
        }
+found:
        su->su_port = port;
        /* setup static variables as appropriate */
        hostname = thishost->hostname;
@@ -950,17 +933,16 @@ selecthost(su)
  * Helper function for sgetpwnam().
  */
 static char *
-sgetsave(s)
-       char *s;
+sgetsave(char *s)
 {
-       char *new = malloc((unsigned) strlen(s) + 1);
+       char *new = malloc(strlen(s) + 1);
 
        if (new == NULL) {
-               perror_reply(421, "Local resource failure: malloc");
+               reply(421, "Ran out of memory.");
                dologout(1);
                /* NOTREACHED */
        }
-       (void) strcpy(new, s);
+       strcpy(new, s);
        return (new);
 }
 
@@ -968,10 +950,12 @@ sgetsave(s)
  * 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).
+ * NB: The data returned by sgetpwnam() will remain valid until
+ * the next call to this function.  Its difference from getpwnam()
+ * is that sgetpwnam() is known to be called from ftpd code only.
  */
 static struct passwd *
-sgetpwnam(name)
-       char *name;
+sgetpwnam(char *name)
 {
        static struct passwd save;
        struct passwd *p;
@@ -1010,8 +994,7 @@ static char curname[MAXLOGNAME];   /* current USER name */
  * _PATH_FTPUSERS to allow people such as root and uucp to be avoided.
  */
 void
-user(name)
-       char *name;
+user(char *name)
 {
        char *cp, *shell;
 
@@ -1027,15 +1010,16 @@ user(name)
        }
 
        guest = 0;
+#ifdef VIRTUAL_HOSTING
+       pw = sgetpwnam(thishost->anonuser);
+#else
+       pw = sgetpwnam("ftp");
+#endif
        if (strcmp(name, "ftp") == 0 || strcmp(name, "anonymous") == 0) {
                if (checkuser(_PATH_FTPUSERS, "ftp", 0, NULL) ||
                    checkuser(_PATH_FTPUSERS, "anonymous", 0, NULL))
                        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
+               else if (pw != NULL) {
                        guest = 1;
                        askpasswd = 1;
                        reply(331,
@@ -1055,6 +1039,7 @@ user(name)
        if ((pw = sgetpwnam(name))) {
                if ((shell = pw->pw_shell) == NULL || *shell == 0)
                        shell = _PATH_BSHELL;
+               setusershell();
                while ((cp = getusershell()) != NULL)
                        if (strcmp(cp, shell) == 0)
                                break;
@@ -1066,25 +1051,34 @@ user(name)
                                syslog(LOG_NOTICE,
                                    "FTP LOGIN REFUSED FROM %s, %s",
                                    remotehost, name);
-                       pw = (struct passwd *) NULL;
+                       pw = NULL;
                        return;
                }
        }
        if (logging)
                strncpy(curname, name, sizeof(curname)-1);
-#ifdef SKEY
-       pwok = skeyaccess(name, NULL, remotehost, remotehost);
-       reply(331, "%s", skey_challenge(name, pw, pwok));
-#else
-       reply(331, "Password required for %s.", name);
+
+       pwok = 0;
+#ifdef USE_PAM
+       /* XXX Kluge! The conversation mechanism needs to be fixed. */
 #endif
+       if (opiechallenge(&opiedata, name, opieprompt) == 0) {
+               pwok = (pw != NULL) &&
+                      opieaccessfile(remotehost) &&
+                      opiealways(pw->pw_dir);
+               reply(331, "Response to %s %s for %s.",
+                     opieprompt, pwok ? "requested" : "required", name);
+       } else {
+               pwok = 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);
+               sleep(login_attempts);
 }
 
 /*
@@ -1093,11 +1087,7 @@ user(name)
  * of the matching line in "residue" if not NULL.
  */
 static int
-checkuser(fname, name, pwset, residue)
-       char *fname;
-       char *name;
-       int pwset;
-       char **residue;
+checkuser(char *fname, char *name, int pwset, char **residue)
 {
        FILE *fd;
        int found = 0;
@@ -1171,7 +1161,7 @@ nextline:
                        if (mp)
                                free(mp);
                }
-               (void) fclose(fd);
+               fclose(fd);
        }
        return (found);
 }
@@ -1181,23 +1171,38 @@ nextline:
  * used when USER command is given or login fails.
  */
 static void
-end_login()
+end_login(void)
 {
+#ifdef USE_PAM
+       int e;
+#endif
 
-       (void) seteuid((uid_t)0);
+       seteuid(0);
        if (logged_in && dowtmp)
                ftpd_logwtmp(ttyline, "", NULL);
        pw = NULL;
 #ifdef LOGIN_CAP
-       setusercontext(NULL, getpwuid(0), (uid_t)0,
+       /* XXX Missing LOGIN_SETMAC */
+       setusercontext(NULL, getpwuid(0), 0,
                       LOGIN_SETPRIORITY|LOGIN_SETRESOURCES|LOGIN_SETUMASK);
+#endif
+#ifdef USE_PAM
+       if (pamh) {
+               if ((e = pam_setcred(pamh, PAM_DELETE_CRED)) != PAM_SUCCESS)
+                       syslog(LOG_ERR, "pam_setcred: %s", pam_strerror(pamh, e));
+               if ((e = pam_close_session(pamh,0)) != PAM_SUCCESS)
+                       syslog(LOG_ERR, "pam_close_session: %s", pam_strerror(pamh, e));
+               if ((e = pam_end(pamh, e)) != PAM_SUCCESS)
+                       syslog(LOG_ERR, "pam_end: %s", pam_strerror(pamh, e));
+               pamh = NULL;
+       }
 #endif
        logged_in = 0;
        guest = 0;
        dochroot = 0;
 }
 
-#if !defined(NOPAM)
+#ifdef USE_PAM
 
 /*
  * the following code is stolen from imap-uw PAM authentication module and
@@ -1217,8 +1222,11 @@ auth_conv(int num_msg, const struct pam_message **msg,
 {
        int i;
        cred_t *cred = (cred_t *) appdata;
-       struct pam_response *reply =
-                       malloc(sizeof(struct pam_response) * num_msg);
+       struct pam_response *reply;
+
+       reply = calloc(num_msg, sizeof *reply);
+       if (reply == NULL)
+               return PAM_BUF_ERR;
 
        for (i = 0; i < num_msg; i++) {
                switch (msg[i]->msg_style) {
@@ -1257,7 +1265,6 @@ auth_conv(int num_msg, const struct pam_message **msg,
 static int
 auth_pam(struct passwd **ppw, const char *pass)
 {
-       pam_handle_t *pamh = NULL;
        const char *tmpl_user;
        const void *item;
        int rval;
@@ -1267,7 +1274,11 @@ auth_pam(struct passwd **ppw, const char *pass)
 
        e = pam_start("ftpd", (*ppw)->pw_name, &conv, &pamh);
        if (e != PAM_SUCCESS) {
-               syslog(LOG_ERR, "pam_start: %s", pam_strerror(pamh, e));
+               /*
+                * In OpenPAM, it's OK to pass NULL to pam_strerror()
+                * if context creation has failed in the first place.
+                */
+               syslog(LOG_ERR, "pam_start: %s", pam_strerror(NULL, e));
                return -1;
        }
 
@@ -1275,6 +1286,10 @@ auth_pam(struct passwd **ppw, const char *pass)
        if (e != PAM_SUCCESS) {
                syslog(LOG_ERR, "pam_set_item(PAM_RHOST): %s",
                        pam_strerror(pamh, e));
+               if ((e = pam_end(pamh, e)) != PAM_SUCCESS) {
+                       syslog(LOG_ERR, "pam_end: %s", pam_strerror(pamh, e));
+               }
+               pamh = NULL;
                return -1;
        }
 
@@ -1316,31 +1331,44 @@ auth_pam(struct passwd **ppw, const char *pass)
                break;
 
        default:
-               syslog(LOG_ERR, "auth_pam: %s", pam_strerror(pamh, e));
+               syslog(LOG_ERR, "pam_authenticate: %s", pam_strerror(pamh, e));
                rval = -1;
                break;
        }
 
-       if ((e = pam_end(pamh, e)) != PAM_SUCCESS) {
-               syslog(LOG_ERR, "pam_end: %s", pam_strerror(pamh, e));
-               rval = -1;
+       if (rval == 0) {
+               e = pam_acct_mgmt(pamh, 0);
+               if (e != PAM_SUCCESS) {
+                       syslog(LOG_ERR, "pam_acct_mgmt: %s",
+                                               pam_strerror(pamh, e));
+                       rval = 1;
+               }
+       }
+
+       if (rval != 0) {
+               if ((e = pam_end(pamh, e)) != PAM_SUCCESS) {
+                       syslog(LOG_ERR, "pam_end: %s", pam_strerror(pamh, e));
+               }
+               pamh = NULL;
        }
        return rval;
 }
 
-#endif /* !defined(NOPAM) */
+#endif /* USE_PAM */
 
 void
-pass(passwd)
-       char *passwd;
+pass(char *passwd)
 {
        int rval;
        FILE *fd;
 #ifdef LOGIN_CAP
        login_cap_t *lc = NULL;
 #endif
-       char *chrootdir;
+#ifdef USE_PAM
+       int e;
+#endif
        char *residue = NULL;
+       char *xpasswd;
 
        if (logged_in || askpasswd == 0) {
                reply(503, "Login with USER first.");
@@ -1352,24 +1380,25 @@ pass(passwd)
                        rval = 1;       /* failure below */
                        goto skip;
                }
-#if !defined(NOPAM)
+#ifdef USE_PAM
                rval = auth_pam(&pw, passwd);
-               if (rval >= 0)
+               if (rval >= 0) {
+                       opieunlock();
                        goto skip;
+               }
 #endif
-#ifdef SKEY
-               if (pwok)
-                       rval = strcmp(pw->pw_passwd,
-                           crypt(passwd, pw->pw_passwd));
-               if (rval)
-                       rval = strcmp(pw->pw_passwd,
-                           skey_crypt(passwd, pw->pw_passwd, pw, pwok));
-#else
-               rval = strcmp(pw->pw_passwd, crypt(passwd, pw->pw_passwd));
-#endif
-               /* The strcmp does not catch null passwords! */
-               if (*pw->pw_passwd == '\0' ||
-                   (pw->pw_expire && time(NULL) >= pw->pw_expire))
+               if (opieverify(&opiedata, passwd) == 0)
+                       xpasswd = pw->pw_passwd;
+               else if (pwok) {
+                       xpasswd = crypt(passwd, pw->pw_passwd);
+                       if (passwd[0] == '\0' && pw->pw_passwd[0] != '\0')
+                               xpasswd = ":";
+               } else {
+                       rval = 1;
+                       goto skip;
+               }
+               rval = strcmp(pw->pw_passwd, xpasswd);
+               if (pw->pw_expire && time(NULL) >= pw->pw_expire)
                        rval = 1;       /* failure */
 skip:
                /*
@@ -1397,44 +1426,53 @@ skip:
                        return;
                }
        }
-#ifdef SKEY
-       pwok = 0;
-#endif
        login_attempts = 0;             /* this time successful */
-       if (setegid((gid_t)pw->pw_gid) < 0) {
+       if (setegid(pw->pw_gid) < 0) {
                reply(550, "Can't set gid.");
                return;
        }
        /* May be overridden by login.conf */
-       (void) umask(defumask);
+       umask(defumask);
 #ifdef LOGIN_CAP
        if ((lc = login_getpwclass(pw)) != NULL) {
-               char    remote_ip[MAXHOSTNAMELEN];
+               char    remote_ip[NI_MAXHOST];
 
-               getnameinfo((struct sockaddr *)&his_addr, his_addr.su_len,
+               if (getnameinfo((struct sockaddr *)&his_addr, his_addr.su_len,
                        remote_ip, sizeof(remote_ip) - 1, NULL, 0,
-                       NI_NUMERICHOST);
+                       NI_NUMERICHOST))
+                               *remote_ip = 0;
                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");
+                       reply(530, "Permission denied.");
                        pw = NULL;
                        return;
                }
                if (!auth_timeok(lc, time(NULL))) {
-                       reply(530, "Login not available right now.\n");
+                       reply(530, "Login not available right now.");
                        pw = NULL;
                        return;
                }
        }
-       setusercontext(lc, pw, (uid_t)0,
+       /* XXX Missing LOGIN_SETMAC */
+       setusercontext(lc, pw, 0,
                LOGIN_SETLOGIN|LOGIN_SETGROUP|LOGIN_SETPRIORITY|
                LOGIN_SETRESOURCES|LOGIN_SETUMASK);
 #else
        setlogin(pw->pw_name);
-       (void) initgroups(pw->pw_name, pw->pw_gid);
+       initgroups(pw->pw_name, pw->pw_gid);
+#endif
+
+#ifdef USE_PAM
+       if (pamh) {
+               if ((e = pam_open_session(pamh, 0)) != PAM_SUCCESS) {
+                       syslog(LOG_ERR, "pam_open_session: %s", pam_strerror(pamh, e));
+               } else if ((e = pam_setcred(pamh, PAM_ESTABLISH_CRED)) != PAM_SUCCESS) {
+                       syslog(LOG_ERR, "pam_setcred: %s", pam_strerror(pamh, e));
+               }
+       }
 #endif
 
        /* open wtmp before chroot */
@@ -1445,10 +1483,11 @@ skip:
 
        if (guest && stats && statfd < 0)
 #ifdef VIRTUAL_HOSTING
-               if ((statfd = open(thishost->statfile, O_WRONLY|O_APPEND)) < 0)
+               statfd = open(thishost->statfile, O_WRONLY|O_APPEND);
 #else
-               if ((statfd = open(_PATH_FTPDSTATFILE, O_WRONLY|O_APPEND)) < 0)
+               statfd = open(_PATH_FTPDSTATFILE, O_WRONLY|O_APPEND);
 #endif
+               if (statfd < 0)
                        stats = 0;
 
        dochroot =
@@ -1465,9 +1504,11 @@ skip:
         * c) expand it to the absolute pathname if necessary.
         */
        if (dochroot && residue &&
-           (chrootdir = strtok(residue, " \t")) != NULL &&
-           chrootdir[0] != '/') {
-               asprintf(&chrootdir, "%s/%s", pw->pw_dir, chrootdir);
+           (chrootdir = strtok(residue, " \t")) != NULL) {
+               if (chrootdir[0] != '/')
+                       asprintf(&chrootdir, "%s/%s", pw->pw_dir, chrootdir);
+               else
+                       chrootdir = strdup(chrootdir); /* make it permanent */
                if (chrootdir == NULL)
                        fatalerror("Ran out of memory.");
        }
@@ -1486,9 +1527,6 @@ skip:
                if ((homedir = strstr(chrootdir, "/./")) != NULL) {
                        *(homedir++) = '\0';    /* wipe '/' */
                        homedir++;              /* skip '.' */
-                       /* so chrootdir can be freed later */
-                       if ((homedir = strdup(homedir)) == NULL)
-                               fatalerror("Ran out of memory.");
                } else {
                        /*
                         * We MUST do a chdir() after the chroot. Otherwise
@@ -1513,7 +1551,7 @@ skip:
         * b) NFS mounted homedirs w/restrictive permissions will be accessible
         *    (uid 0 has no root power over NFS if not mapped explicitly.)
         */
-       if (seteuid((uid_t)pw->pw_uid) < 0) {
+       if (seteuid(pw->pw_uid) < 0) {
                reply(550, "Can't set uid.");
                goto bad;
        }
@@ -1526,7 +1564,7 @@ skip:
                                reply(550, "Root is inaccessible.");
                                goto bad;
                        }
-                       lreply(230, "No directory! Logging in with home=/");
+                       lreply(230, "No directory! Logging in with home=/.");
                }
        }
 
@@ -1535,10 +1573,11 @@ skip:
         * N.B. reply(230,) must follow the message.
         */
 #ifdef VIRTUAL_HOSTING
-       if ((fd = fopen(thishost->loginmsg, "r")) != NULL) {
+       fd = fopen(thishost->loginmsg, "r");
 #else
-       if ((fd = fopen(_PATH_FTPLOGINMESG, "r")) != NULL) {
+       fd = fopen(_PATH_FTPLOGINMESG, "r");
 #endif
+       if (fd != NULL) {
                char *cp, line[LINE_MAX];
 
                while (fgets(line, sizeof(line), fd) != NULL) {
@@ -1546,8 +1585,8 @@ skip:
                                *cp = '\0';
                        lreply(230, "%s", line);
                }
-               (void) fflush(stdout);
-               (void) fclose(fd);
+               fflush(stdout);
+               fclose(fd);
        }
        if (guest) {
                if (ident != NULL)
@@ -1588,11 +1627,11 @@ skip:
                        syslog(LOG_INFO, "FTP LOGIN FROM %s as %s",
                            remotehost, pw->pw_name);
        }
+       if (logging && (guest || dochroot))
+               syslog(LOG_INFO, "session root changed to %s", chrootdir);
 #ifdef LOGIN_CAP
        login_close(lc);
 #endif
-       if (chrootdir)
-               free(chrootdir);
        if (residue)
                free(residue);
        return;
@@ -1601,20 +1640,17 @@ bad:
 #ifdef LOGIN_CAP
        login_close(lc);
 #endif
-       if (chrootdir)
-               free(chrootdir);
        if (residue)
                free(residue);
        end_login();
 }
 
 void
-retrieve(cmd, name)
-       char *cmd, *name;
+retrieve(char *cmd, char *name)
 {
        FILE *fin, *dout;
        struct stat st;
-       int (*closefunc) (FILE *);
+       int (*closefunc)(FILE *);
        time_t start;
 
        if (cmd == 0) {
@@ -1623,7 +1659,7 @@ retrieve(cmd, name)
        } else {
                char line[BUFSIZ];
 
-               (void) snprintf(line, sizeof(line), cmd, name), name = line;
+               snprintf(line, sizeof(line), cmd, name), name = line;
                fin = ftpd_popen(line, "r"), closefunc = ftpd_pclose;
                st.st_size = -1;
                st.st_blksize = BUFSIZ;
@@ -1644,7 +1680,14 @@ retrieve(cmd, name)
                        goto done;
                }
                if (!S_ISREG(st.st_mode)) {
-                       if (guest) {
+                       /*
+                        * Never sending a raw directory is a workaround
+                        * for buggy clients that will attempt to RETR
+                        * a directory before listing it, e.g., Mozilla.
+                        * Preventing a guest from getting irregular files
+                        * is a simple security measure.
+                        */
+                       if (S_ISDIR(st.st_mode) || guest) {
                                reply(550, "%s: not a plain file.", name);
                                goto done;
                        }
@@ -1678,9 +1721,9 @@ retrieve(cmd, name)
        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);
+       if (cmd == 0 && guest && stats && byte_count > 0)
+               logxfer(name, byte_count, start);
+       fclose(dout);
        data = -1;
        pdata = -1;
 done:
@@ -1690,13 +1733,11 @@ done:
 }
 
 void
-store(name, mode, unique)
-       char *name, *mode;
-       int unique;
+store(char *name, char *mode, int unique)
 {
        int fd;
        FILE *fout, *din;
-       int (*closefunc) (FILE *);
+       int (*closefunc)(FILE *);
 
        if (*mode == 'a') {             /* APPE */
                if (unique) {
@@ -1705,7 +1746,7 @@ store(name, mode, unique)
                        unique = 0;
                }
                if (guest && noguestmod) {
-                       reply(550, "Appending to existing file denied");
+                       reply(550, "Appending to existing file denied.");
                        goto err;
                }
                restart_point = 0;      /* not affected by preceding REST */
@@ -1714,7 +1755,7 @@ store(name, mode, unique)
                restart_point = 0;
        if (guest && noguestmod) {
                if (restart_point) {    /* guest STOR w/REST */
-                       reply(550, "Modifying existing file denied");
+                       reply(550, "Modifying existing file denied.");
                        goto err;
                } else                  /* treat guest STOR as STOU */
                        unique = 1;
@@ -1754,7 +1795,7 @@ store(name, mode, unique)
                         * because we are changing from reading to
                         * writing.
                         */
-                       if (fseeko(fout, (off_t)0, SEEK_CUR) < 0) {
+                       if (fseeko(fout, 0, SEEK_CUR) < 0) {
                                perror_reply(550, name);
                                goto done;
                        }
@@ -1763,7 +1804,7 @@ store(name, mode, unique)
                        goto done;
                }
        }
-       din = dataconn(name, (off_t)-1, "r");
+       din = dataconn(name, -1, "r");
        if (din == NULL)
                goto done;
        if (receive_data(din, fout) == 0) {
@@ -1773,7 +1814,7 @@ store(name, mode, unique)
                else
                        reply(226, "Transfer complete.");
        }
-       (void) fclose(din);
+       fclose(din);
        data = -1;
        pdata = -1;
 done:
@@ -1786,8 +1827,7 @@ err:
 }
 
 static FILE *
-getdatasock(mode)
-       char *mode;
+getdatasock(char *mode)
 {
        int on = 1, s, t, tries;
 
@@ -1802,8 +1842,17 @@ getdatasock(mode)
        /* anchor socket to avoid multi-homing problems */
        data_source = ctrl_addr;
        data_source.su_port = htons(dataport);
-       (void) seteuid((uid_t)0);
+       seteuid(0);
        for (tries = 1; ; tries++) {
+               /*
+                * We should loop here since it's possible that
+                * another ftpd instance has passed this point and is
+                * trying to open a data connection in active mode now.
+                * Until the other connection is opened, we'll be getting
+                * EADDRINUSE because no SOCK_STREAM sockets in the system
+                * can share both local and remote addresses, localIP:20
+                * and *:* in this case.
+                */
                if (bind(s, (struct sockaddr *)&data_source,
                    data_source.su_len) >= 0)
                        break;
@@ -1811,7 +1860,7 @@ getdatasock(mode)
                        goto bad;
                sleep(tries);
        }
-       (void) seteuid((uid_t)pw->pw_uid);
+       seteuid(pw->pw_uid);
 #ifdef IP_TOS
        if (data_source.su_family == AF_INET)
       {
@@ -1823,35 +1872,24 @@ getdatasock(mode)
 #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.
+        * at the boundaries of each write().
         */
        on = 1;
        if (setsockopt(s, IPPROTO_TCP, TCP_NOPUSH, &on, sizeof on) < 0)
                syslog(LOG_WARNING, "data setsockopt (TCP_NOPUSH): %m");
 #endif
-#ifdef SO_SNDBUF
-       on = 65536;
-       if (setsockopt(s, SOL_SOCKET, SO_SNDBUF, &on, sizeof on) < 0)
-               syslog(LOG_WARNING, "data setsockopt (SO_SNDBUF): %m");
-#endif
-
        return (fdopen(s, mode));
 bad:
        /* Return the real value of errno (close may change it) */
        t = errno;
-       (void) seteuid((uid_t)pw->pw_uid);
-       (void) close(s);
+       seteuid(pw->pw_uid);
+       close(s);
        errno = t;
        return (NULL);
 }
 
 static FILE *
-dataconn(name, size, mode)
-       char *name;
-       off_t size;
-       char *mode;
+dataconn(char *name, off_t size, char *mode)
 {
        char sizebuf[32];
        FILE *file;
@@ -1859,14 +1897,15 @@ dataconn(name, size, mode)
 
        file_size = size;
        byte_count = 0;
-       if (size != (off_t) -1)
-               (void) snprintf(sizebuf, sizeof(sizebuf), " (%qd bytes)", size);
+       if (size != -1)
+               snprintf(sizebuf, sizeof(sizebuf),
+                               " (%jd bytes)", (intmax_t)size);
        else
                *sizebuf = '\0';
        if (pdata >= 0) {
                union sockunion from;
-               int flags;
-               int s, fromlen = ctrl_addr.su_len;
+               socklen_t fromlen = ctrl_addr.su_len;
+               int flags, s;
                struct timeval timeout;
                fd_set set;
 
@@ -1885,10 +1924,10 @@ dataconn(name, size, mode)
                if ((flags = fcntl(pdata, F_GETFL, 0)) == -1 ||
                    fcntl(pdata, F_SETFL, flags | O_NONBLOCK) == -1)
                        goto pdata_err;
-               if (select(pdata+1, &set, (fd_set *) 0, (fd_set *) 0, &timeout) <= 0 ||
+               if (select(pdata+1, &set, NULL, NULL, &timeout) <= 0 ||
                    (s = accept(pdata, (struct sockaddr *) &from, &fromlen)) < 0)
                        goto pdata_err;
-               (void) close(pdata);
+               close(pdata);
                pdata = s;
                /*
                 * Unset the inherited non-blocking I/O flag
@@ -1910,7 +1949,7 @@ dataconn(name, size, mode)
                return (fdopen(pdata, mode));
 pdata_err:
                reply(425, "Can't open data connection.");
-               (void) close(pdata);
+               close(pdata);
                pdata = -1;
                return (NULL);
        }
@@ -1926,11 +1965,16 @@ pdata_err:
        do {
                file = getdatasock(mode);
                if (file == NULL) {
-                       char hostbuf[BUFSIZ], portbuf[BUFSIZ];
-                       getnameinfo((struct sockaddr *)&data_source,
-                               data_source.su_len, hostbuf, sizeof(hostbuf) - 1,
-                               portbuf, sizeof(portbuf),
-                               NI_NUMERICHOST|NI_NUMERICSERV);
+                       char hostbuf[NI_MAXHOST], portbuf[NI_MAXSERV];
+
+                       if (getnameinfo((struct sockaddr *)&data_source,
+                               data_source.su_len,
+                               hostbuf, sizeof(hostbuf) - 1,
+                               portbuf, sizeof(portbuf) - 1,
+                               NI_NUMERICHOST|NI_NUMERICSERV))
+                                       *hostbuf = *portbuf = 0;
+                       hostbuf[sizeof(hostbuf) - 1] = 0;
+                       portbuf[sizeof(portbuf) - 1] = 0;
                        reply(425, "Can't create data socket (%s,%s): %s.",
                                hostbuf, portbuf, strerror(errno));
                        return (NULL);
@@ -1941,17 +1985,18 @@ pdata_err:
                    data_dest.su_len) == 0)
                        break;
                conerrno = errno;
-               (void) fclose(file);
+               fclose(file);
                data = -1;
                if (conerrno == EADDRINUSE) {
-                       sleep((unsigned) swaitint);
+                       sleep(swaitint);
                        retry += swaitint;
                } else {
                        break;
                }
        } while (retry <= swaitmax);
        if (conerrno != 0) {
-               perror_reply(425, "Can't build data connection");
+               reply(425, "Can't build data connection: %s.",
+                          strerror(conerrno));
                return (NULL);
        }
        reply(150, "Opening %s mode data connection for '%s'%s.",
@@ -1959,6 +2004,30 @@ pdata_err:
        return (file);
 }
 
+/*
+ * A helper macro to avoid code duplication
+ * in send_data() and receive_data().
+ *
+ * XXX We have to block SIGURG during putc() because BSD stdio
+ * is unable to restart interrupted write operations and hence
+ * the entire buffer contents will be lost as soon as a write()
+ * call indicates EINTR to stdio.
+ */
+#define FTPD_PUTC(ch, file, label)                                     \
+       do {                                                            \
+               int ret;                                                \
+                                                                       \
+               do {                                                    \
+                       START_UNSAFE;                                   \
+                       ret = putc((ch), (file));                       \
+                       END_UNSAFE;                                     \
+                       CHECKOOB(return (-1))                           \
+                       else if (ferror(file))                          \
+                               goto label;                             \
+                       clearerr(file);                                 \
+               } while (ret == EOF);                                   \
+       } while (0)
+
 /*
  * Tranfer the contents of "instr" to "outstr" peer using the appropriate
  * encapsulation of the data subject to Mode, Structure, and Type.
@@ -1966,39 +2035,54 @@ pdata_err:
  * NB: Form isn't handled.
  */
 static int
-send_data(instr, outstr, blksize, filesize, isreg)
-       FILE *instr, *outstr;
-       off_t blksize;
-       off_t filesize;
-       int isreg;
+send_data(FILE *instr, FILE *outstr, size_t blksize, off_t filesize, int isreg)
 {
-       int c, filefd, netfd;
+       int c, cp, filefd, netfd;
        char *buf;
-       off_t cnt;
 
-       transflag++;
+       STARTXFER;
+
        switch (type) {
 
        case TYPE_A:
-               while ((c = getc(instr)) != EOF) {
-                       if (recvurg)
-                               goto got_oob;
-                       byte_count++;
-                       if (c == '\n') {
-                               if (ferror(outstr))
-                                       goto data_err;
-                               (void) putc('\r', outstr);
+               cp = EOF;
+               for (;;) {
+                       c = getc(instr);
+                       CHECKOOB(return (-1))
+                       else if (c == EOF && ferror(instr))
+                               goto file_err;
+                       if (c == EOF) {
+                               if (ferror(instr)) {    /* resume after OOB */
+                                       clearerr(instr);
+                                       continue;
+                               }
+                               if (feof(instr))        /* EOF */
+                                       break;
+                               syslog(LOG_ERR, "Internal: impossible condition"
+                                               " on file after getc()");
+                               goto file_err;
+                       }
+                       if (c == '\n' && cp != '\r') {
+                               FTPD_PUTC('\r', outstr, data_err);
+                               byte_count++;
                        }
-                       (void) putc(c, outstr);
+                       FTPD_PUTC(c, outstr, data_err);
+                       byte_count++;
+                       cp = c;
                }
-               if (recvurg)
-                       goto got_oob;
-               fflush(outstr);
-               transflag = 0;
-               if (ferror(instr))
-                       goto file_err;
-               if (ferror(outstr))
+#ifdef notyet  /* BSD stdio isn't ready for that */
+               while (fflush(outstr) == EOF) {
+                       CHECKOOB(return (-1))
+                       else
+                               goto data_err;
+                       clearerr(outstr);
+               }
+               ENDXFER;
+#else
+               ENDXFER;
+               if (fflush(outstr) == EOF)
                        goto data_err;
+#endif
                reply(226, "Transfer complete.");
                return (0);
 
@@ -2012,78 +2096,100 @@ send_data(instr, outstr, blksize, filesize, isreg)
                filefd = fileno(instr);
 
                if (isreg) {
-
-                       off_t offset;
+                       char *msg = "Transfer complete.";
+                       off_t cnt, offset;
                        int err;
 
-                       err = cnt = offset = 0;
+                       cnt = offset = 0;
 
-                       while (err != -1 && filesize > 0) {
+                       while (filesize > 0) {
                                err = sendfile(filefd, netfd, offset, 0,
-                                       (struct sf_hdtr *) NULL, &cnt, 0);
+                                              NULL, &cnt, 0);
                                /*
                                 * Calculate byte_count before OOB processing.
                                 * It can be used in myoob() later.
                                 */
                                byte_count += cnt;
-                               if (recvurg)
-                                       goto got_oob;
                                offset += cnt;
                                filesize -= cnt;
-
-                               if (err == -1) {
-                                       if (!cnt)
+                               CHECKOOB(return (-1))
+                               else if (err == -1) {
+                                       if (errno != EINTR &&
+                                           cnt == 0 && offset == 0)
                                                goto oldway;
-
                                        goto data_err;
                                }
+                               if (err == -1)  /* resume after OOB */
+                                       continue;
+                               /*
+                                * We hit the EOF prematurely.
+                                * Perhaps the file was externally truncated.
+                                */
+                               if (cnt == 0) {
+                                       msg = "Transfer finished due to "
+                                             "premature end of file.";
+                                       break;
+                               }
                        }
-
-                       transflag = 0;
-                       reply(226, "Transfer complete.");
+                       ENDXFER;
+                       reply(226, msg);
                        return (0);
                }
 
 oldway:
-               if ((buf = malloc((u_int)blksize)) == NULL) {
-                       transflag = 0;
-                       perror_reply(451, "Local resource failure: malloc");
+               if ((buf = malloc(blksize)) == NULL) {
+                       ENDXFER;
+                       reply(451, "Ran out of memory.");
                        return (-1);
                }
 
-               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)
+               for (;;) {
+                       int cnt, len;
+                       char *bp;
+
+                       cnt = read(filefd, buf, blksize);
+                       CHECKOOB(free(buf); return (-1))
+                       else if (cnt < 0) {
+                               free(buf);
                                goto file_err;
-                       goto data_err;
+                       }
+                       if (cnt < 0)    /* resume after OOB */
+                               continue;
+                       if (cnt == 0)   /* EOF */
+                               break;
+                       for (len = cnt, bp = buf; len > 0;) {
+                               cnt = write(netfd, bp, len);
+                               CHECKOOB(free(buf); return (-1))
+                               else if (cnt < 0) {
+                                       free(buf);
+                                       goto data_err;
+                               }
+                               if (cnt <= 0)
+                                       continue;
+                               len -= cnt;
+                               bp += cnt;
+                               byte_count += cnt;
+                       }
                }
+               ENDXFER;
+               free(buf);
                reply(226, "Transfer complete.");
                return (0);
        default:
-               transflag = 0;
-               reply(550, "Unimplemented TYPE %d in send_data", type);
+               ENDXFER;
+               reply(550, "Unimplemented TYPE %d in send_data.", type);
                return (-1);
        }
 
 data_err:
-       transflag = 0;
+       ENDXFER;
        perror_reply(426, "Data connection");
        return (-1);
 
 file_err:
-       transflag = 0;
+       ENDXFER;
        perror_reply(551, "Error on input file");
        return (-1);
-
-got_oob:
-       myoob();
-       recvurg = 0;
-       transflag = 0;
-       return (-1);
 }
 
 /*
@@ -2093,123 +2199,152 @@ got_oob:
  * N.B.: Form isn't handled.
  */
 static int
-receive_data(instr, outstr)
-       FILE *instr, *outstr;
+receive_data(FILE *instr, FILE *outstr)
 {
-       int c;
-       int cnt, bare_lfs;
-       char buf[BUFSIZ];
+       int c, cp;
+       int bare_lfs = 0;
 
-       transflag++;
-       bare_lfs = 0;
+       STARTXFER;
 
        switch (type) {
 
        case TYPE_I:
        case TYPE_L:
-               while ((cnt = read(fileno(instr), buf, sizeof(buf))) > 0) {
-                       if (recvurg)
-                               goto got_oob;
-                       if (write(fileno(outstr), buf, cnt) != cnt)
-                               goto file_err;
-                       byte_count += cnt;
+               for (;;) {
+                       int cnt, len;
+                       char *bp;
+                       char buf[BUFSIZ];
+
+                       cnt = read(fileno(instr), buf, sizeof(buf));
+                       CHECKOOB(return (-1))
+                       else if (cnt < 0)
+                               goto data_err;
+                       if (cnt < 0)    /* resume after OOB */
+                               continue;
+                       if (cnt == 0)   /* EOF */
+                               break;
+                       for (len = cnt, bp = buf; len > 0;) {
+                               cnt = write(fileno(outstr), bp, len);
+                               CHECKOOB(return (-1))
+                               else if (cnt < 0)
+                                       goto file_err;
+                               if (cnt <= 0)
+                                       continue;
+                               len -= cnt;
+                               bp += cnt;
+                               byte_count += cnt;
+                       }
                }
-               if (recvurg)
-                       goto got_oob;
-               if (cnt < 0)
-                       goto data_err;
-               transflag = 0;
+               ENDXFER;
                return (0);
 
        case TYPE_E:
+               ENDXFER;
                reply(553, "TYPE E not implemented.");
-               transflag = 0;
                return (-1);
 
        case TYPE_A:
-               while ((c = getc(instr)) != EOF) {
-                       if (recvurg)
-                               goto got_oob;
-                       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;
-                               }
+               cp = EOF;
+               for (;;) {
+                       c = getc(instr);
+                       CHECKOOB(return (-1))
+                       else if (c == EOF && ferror(instr))
+                               goto data_err;
+                       if (c == EOF && ferror(instr)) { /* resume after OOB */
+                               clearerr(instr);
+                               continue;
+                       }
+
+                       if (cp == '\r') {
+                               if (c != '\n')
+                                       FTPD_PUTC('\r', outstr, file_err);
+                       } else
+                               if (c == '\n')
+                                       bare_lfs++;
+                       if (c == '\r') {
+                               byte_count++;
+                               cp = c;
+                               continue;
                        }
-                       (void) putc(c, outstr);
-       contin2:        ;
+
+                       /* Check for EOF here in order not to lose last \r. */
+                       if (c == EOF) {
+                               if (feof(instr))        /* EOF */
+                                       break;
+                               syslog(LOG_ERR, "Internal: impossible condition"
+                                               " on data stream after getc()");
+                               goto data_err;
+                       }
+
+                       byte_count++;
+                       FTPD_PUTC(c, outstr, file_err);
+                       cp = c;
                }
-               if (recvurg)
-                       goto got_oob;
-               fflush(outstr);
-               if (ferror(instr))
-                       goto data_err;
-               if (ferror(outstr))
+#ifdef notyet  /* BSD stdio isn't ready for that */
+               while (fflush(outstr) == EOF) {
+                       CHECKOOB(return (-1))
+                       else
+                               goto file_err;
+                       clearerr(outstr);
+               }
+               ENDXFER;
+#else
+               ENDXFER;
+               if (fflush(outstr) == EOF)
                        goto file_err;
-               transflag = 0;
+#endif
                if (bare_lfs) {
                        lreply(226,
-               "WARNING! %d bare linefeeds received in ASCII mode",
+               "WARNING! %d bare linefeeds received in ASCII mode.",
                            bare_lfs);
-               (void)printf("   File may not have transferred correctly.\r\n");
+               printf("   File may not have transferred correctly.\r\n");
                }
                return (0);
        default:
-               reply(550, "Unimplemented TYPE %d in receive_data", type);
-               transflag = 0;
+               ENDXFER;
+               reply(550, "Unimplemented TYPE %d in receive_data.", type);
                return (-1);
        }
 
 data_err:
-       transflag = 0;
-       perror_reply(426, "Data Connection");
+       ENDXFER;
+       perror_reply(426, "Data connection");
        return (-1);
 
 file_err:
-       transflag = 0;
-       perror_reply(452, "Error writing file");
-       return (-1);
-
-got_oob:
-       myoob();
-       recvurg = 0;
-       transflag = 0;
+       ENDXFER;
+       perror_reply(452, "Error writing to file");
        return (-1);
 }
 
 void
-statfilecmd(filename)
-       char *filename;
+statfilecmd(char *filename)
 {
        FILE *fin;
        int atstart;
-       int c;
+       int c, code;
        char line[LINE_MAX];
+       struct stat st;
 
-       (void)snprintf(line, sizeof(line), _PATH_LS " -lgA %s", filename);
+       code = lstat(filename, &st) == 0 && S_ISDIR(st.st_mode) ? 212 : 213;
+       snprintf(line, sizeof(line), _PATH_LS " -lgA %s", filename);
        fin = ftpd_popen(line, "r");
-       lreply(211, "status of %s:", filename);
+       lreply(code, "Status of %s:", filename);
        atstart = 1;
        while ((c = getc(fin)) != EOF) {
                if (c == '\n') {
                        if (ferror(stdout)){
-                               perror_reply(421, "control connection");
-                               (void) ftpd_pclose(fin);
+                               perror_reply(421, "Control connection");
+                               ftpd_pclose(fin);
                                dologout(1);
                                /* NOTREACHED */
                        }
                        if (ferror(fin)) {
                                perror_reply(551, filename);
-                               (void) ftpd_pclose(fin);
+                               ftpd_pclose(fin);
                                return;
                        }
-                       (void) putc('\r', stdout);
+                       putc('\r', stdout);
                }
                /*
                 * RFC 959 says neutral text should be prepended before
@@ -2218,16 +2353,16 @@ statfilecmd(filename)
                 * as a matter of fact.
                 */
                if (atstart && isdigit(c))
-                       (void) putc(' ', stdout);
-               (void) putc(c, stdout);
+                       putc(' ', stdout);
+               putc(c, stdout);
                atstart = (c == '\n');
        }
-       (void) ftpd_pclose(fin);
-       reply(211, "End of Status");
+       ftpd_pclose(fin);
+       reply(code, "End of status.");
 }
 
 void
-statcmd()
+statcmd(void)
 {
        union sockunion *su;
        u_char *a, *p;
@@ -2242,6 +2377,7 @@ statcmd()
        printf("     Connected to %s", remotehost);
        if (!getnameinfo((struct sockaddr *)&his_addr, his_addr.su_len,
                         hname, sizeof(hname) - 1, NULL, 0, NI_NUMERICHOST)) {
+               hname[sizeof(hname) - 1] = 0;
                if (strcmp(hname, remotehost) != 0)
                        printf(" (%s)", hname);
        }
@@ -2259,8 +2395,8 @@ statcmd()
        if (type == TYPE_A || type == TYPE_E)
                printf(", FORM: %s", formnames[form]);
        if (type == TYPE_L)
-#if NBBY == 8
-               printf(" %d", NBBY);
+#if CHAR_BIT == 8
+               printf(" %d", CHAR_BIT);
 #else
                printf(" %d", bytesize);        /* need definition! */
 #endif
@@ -2347,6 +2483,7 @@ epsvonly:;
                        if (!getnameinfo((struct sockaddr *)&tmp, tmp.su_len,
                                        hname, sizeof(hname) - 1, NULL, 0,
                                        NI_NUMERICHOST)) {
+                               hname[sizeof(hname) - 1] = 0;
                                printf("     %s |%d|%s|%d|\r\n",
                                        ispassive ? "EPSV" : "EPRT",
                                        af, hname, htons(tmp.su_port));
@@ -2356,83 +2493,66 @@ epsvonly:;
 #undef UC
        } else
                printf("     No data connection\r\n");
-       reply(211, "End of status");
+       reply(211, "End of status.");
 }
 
 void
-fatalerror(s)
-       char *s;
+fatalerror(char *s)
 {
 
-       reply(451, "Error in server: %s\n", s);
+       reply(451, "Error in server: %s", s);
        reply(221, "Closing connection due to server error.");
        dologout(0);
        /* NOTREACHED */
 }
 
 void
-#if __STDC__
 reply(int n, const char *fmt, ...)
-#else
-reply(n, fmt, va_alist)
-       int n;
-       char *fmt;
-        va_dcl
-#endif
 {
        va_list ap;
-#if __STDC__
+
+       printf("%d ", n);
        va_start(ap, fmt);
-#else
-       va_start(ap);
-#endif
-       (void)printf("%d ", n);
-       (void)vprintf(fmt, ap);
-       (void)printf("\r\n");
-       (void)fflush(stdout);
+       vprintf(fmt, ap);
+       va_end(ap);
+       printf("\r\n");
+       fflush(stdout);
        if (ftpdebug) {
                syslog(LOG_DEBUG, "<--- %d ", n);
+               va_start(ap, fmt);
                vsyslog(LOG_DEBUG, fmt, ap);
+               va_end(ap);
        }
 }
 
 void
-#if __STDC__
 lreply(int n, const char *fmt, ...)
-#else
-lreply(n, fmt, va_alist)
-       int n;
-       char *fmt;
-        va_dcl
-#endif
 {
        va_list ap;
-#if __STDC__
+
+       printf("%d- ", n);
        va_start(ap, fmt);
-#else
-       va_start(ap);
-#endif
-       (void)printf("%d- ", n);
-       (void)vprintf(fmt, ap);
-       (void)printf("\r\n");
-       (void)fflush(stdout);
+       vprintf(fmt, ap);
+       va_end(ap);
+       printf("\r\n");
+       fflush(stdout);
        if (ftpdebug) {
                syslog(LOG_DEBUG, "<--- %d- ", n);
+               va_start(ap, fmt);
                vsyslog(LOG_DEBUG, fmt, ap);
+               va_end(ap);
        }
 }
 
 static void
-ack(s)
-       char *s;
+ack(char *s)
 {
 
        reply(250, "%s command successful.", s);
 }
 
 void
-nack(s)
-       char *s;
+nack(char *s)
 {
 
        reply(502, "%s command not implemented.", s);
@@ -2440,19 +2560,17 @@ nack(s)
 
 /* ARGSUSED */
 void
-yyerror(s)
-       char *s;
+yyerror(char *s)
 {
        char *cp;
 
        if ((cp = strchr(cbuf,'\n')))
                *cp = '\0';
-       reply(500, "'%s': command not understood.", cbuf);
+       reply(500, "%s: command not understood.", cbuf);
 }
 
 void
-delete(name)
-       char *name;
+delete(char *name)
 {
        struct stat st;
 
@@ -2461,13 +2579,17 @@ delete(name)
                perror_reply(550, name);
                return;
        }
-       if ((st.st_mode&S_IFMT) == S_IFDIR) {
+       if (S_ISDIR(st.st_mode)) {
                if (rmdir(name) < 0) {
                        perror_reply(550, name);
                        return;
                }
                goto done;
        }
+       if (guest && noguestmod) {
+               reply(550, "Operation not permitted.");
+               return;
+       }
        if (unlink(name) < 0) {
                perror_reply(550, name);
                return;
@@ -2477,8 +2599,7 @@ done:
 }
 
 void
-cwd(path)
-       char *path;
+cwd(char *path)
 {
 
        if (chdir(path) < 0)
@@ -2488,14 +2609,13 @@ cwd(path)
 }
 
 void
-makedir(name)
-       char *name;
+makedir(char *name)
 {
        char *s;
 
        LOGCMD("mkdir", name);
        if (guest && noguestmkd)
-               reply(550, "%s: permission denied", name);
+               reply(550, "Operation not permitted.");
        else if (mkdir(name, 0777) < 0)
                perror_reply(550, name);
        else {
@@ -2507,8 +2627,7 @@ makedir(name)
 }
 
 void
-removedir(name)
-       char *name;
+removedir(char *name)
 {
 
        LOGCMD("rmdir", name);
@@ -2519,12 +2638,12 @@ removedir(name)
 }
 
 void
-pwd()
+pwd(void)
 {
        char *s, path[MAXPATHLEN + 1];
 
-       if (getwd(path) == (char *)NULL)
-               reply(550, "%s.", path);
+       if (getcwd(path, sizeof(path)) == NULL)
+               perror_reply(550, "Get current directory");
        else {
                if ((s = doublequote(path)) == NULL)
                        fatalerror("Ran out of memory.");
@@ -2534,29 +2653,31 @@ pwd()
 }
 
 char *
-renamefrom(name)
-       char *name;
+renamefrom(char *name)
 {
        struct stat st;
 
+       if (guest && noguestmod) {
+               reply(550, "Operation not permitted.");
+               return (NULL);
+       }
        if (lstat(name, &st) < 0) {
                perror_reply(550, name);
-               return ((char *)0);
+               return (NULL);
        }
-       reply(350, "File exists, ready for destination name");
+       reply(350, "File exists, ready for destination name.");
        return (name);
 }
 
 void
-renamecmd(from, to)
-       char *from, *to;
+renamecmd(char *from, char *to)
 {
        struct stat st;
 
        LOGCMD2("rename", from, to);
 
        if (guest && (stat(to, &st) == 0)) {
-               reply(550, "%s: permission denied", to);
+               reply(550, "%s: permission denied.", to);
                return;
        }
 
@@ -2567,12 +2688,16 @@ renamecmd(from, to)
 }
 
 static void
-dolog(who)
-       struct sockaddr *who;
+dolog(struct sockaddr *who)
 {
-       int error;
+       char who_name[NI_MAXHOST];
 
        realhostname_sa(remotehost, sizeof(remotehost) - 1, who, who->sa_len);
+       remotehost[sizeof(remotehost) - 1] = 0;
+       if (getnameinfo(who, who->sa_len,
+               who_name, sizeof(who_name) - 1, NULL, 0, NI_NUMERICHOST))
+                       *who_name = 0;
+       who_name[sizeof(who_name) - 1] = 0;
 
 #ifdef SETPROCTITLE
 #ifdef VIRTUAL_HOSTING
@@ -2589,19 +2714,12 @@ dolog(who)
        if (logging) {
 #ifdef VIRTUAL_HOSTING
                if (thishost != firsthost)
-                       syslog(LOG_INFO, "connection from %s (to %s)",
-                              remotehost, hostname);
+                       syslog(LOG_INFO, "connection from %s (%s) to %s",
+                              remotehost, who_name, hostname);
                else
 #endif
-               {
-                       char    who_name[MAXHOSTNAMELEN];
-
-                       error = getnameinfo(who, who->sa_len,
-                                           who_name, sizeof(who_name) - 1,
-                                           NULL, 0, NI_NUMERICHOST);
-                       syslog(LOG_INFO, "connection from %s (%s)", remotehost,
-                              error == 0 ? who_name : "");
-               }
+                       syslog(LOG_INFO, "connection from %s (%s)",
+                              remotehost, who_name);
        }
 }
 
@@ -2610,17 +2728,11 @@ dolog(who)
  * and exit with supplied status.
  */
 void
-dologout(status)
-       int status;
+dologout(int status)
 {
-       /*
-        * Prevent reception of SIGURG from resulting in a resumption
-        * back to the main program loop.
-        */
-       transflag = 0;
 
        if (logged_in && dowtmp) {
-               (void) seteuid((uid_t)0);
+               seteuid(0);
                ftpd_logwtmp(ttyline, "", NULL);
        }
        /* beware of flushing buffers after a SIGPIPE */
@@ -2628,40 +2740,88 @@ dologout(status)
 }
 
 static void
-sigurg(signo)
-       int signo;
+sigurg(int signo)
 {
 
        recvurg = 1;
 }
 
 static void
-myoob()
+maskurg(int flag)
 {
-       char *cp;
+       int oerrno;
+       sigset_t sset;
 
-       /* only process if transfer occurring */
-       if (!transflag)
+       if (!transflag) {
+               syslog(LOG_ERR, "Internal: maskurg() while no transfer");
                return;
+       }
+       oerrno = errno;
+       sigemptyset(&sset);
+       sigaddset(&sset, SIGURG);
+       sigprocmask(flag ? SIG_BLOCK : SIG_UNBLOCK, &sset, NULL);
+       errno = oerrno;
+}
+
+static void
+flagxfer(int flag)
+{
+
+       if (flag) {
+               if (transflag)
+                       syslog(LOG_ERR, "Internal: flagxfer(1): "
+                                       "transfer already under way");
+               transflag = 1;
+               maskurg(0);
+               recvurg = 0;
+       } else {
+               if (!transflag)
+                       syslog(LOG_ERR, "Internal: flagxfer(0): "
+                                       "no active transfer");
+               maskurg(1);
+               transflag = 0;
+       }
+}
+
+/*
+ * Returns 0 if OK to resume or -1 if abort requested.
+ */
+static int
+myoob(void)
+{
+       char *cp;
+       int ret;
+
+       if (!transflag) {
+               syslog(LOG_ERR, "Internal: myoob() while no transfer");
+               return (0);
+       }
        cp = tmpline;
-       if (getline(cp, 7, stdin) == NULL) {
+       ret = getline(cp, 7, stdin);
+       if (ret == -1) {
                reply(221, "You could at least say goodbye.");
                dologout(0);
+       } else if (ret == -2) {
+               /* Ignore truncated command. */
+               return (0);
        }
        upper(cp);
        if (strcmp(cp, "ABOR\r\n") == 0) {
                tmpline[0] = '\0';
                reply(426, "Transfer aborted. Data connection closed.");
-               reply(226, "Abort successful");
+               reply(226, "Abort successful.");
+               return (-1);
        }
        if (strcmp(cp, "STAT\r\n") == 0) {
                tmpline[0] = '\0';
-               if (file_size != (off_t) -1)
-                       reply(213, "Status: %qd of %qd bytes transferred",
-                           byte_count, file_size);
+               if (file_size != -1)
+                       reply(213, "Status: %jd of %jd bytes transferred.",
+                                  (intmax_t)byte_count, (intmax_t)file_size);
                else
-                       reply(213, "Status: %qd bytes transferred", byte_count);
+                       reply(213, "Status: %jd bytes transferred.",
+                                  (intmax_t)byte_count);
        }
+       return (0);
 }
 
 /*
@@ -2671,9 +2831,10 @@ myoob()
  *     with Rick Adams on 25 Jan 89.
  */
 void
-passive()
+passive(void)
 {
-       int len, on;
+       socklen_t len;
+       int on;
        char *p, *a;
 
        if (pdata >= 0)         /* close old port if one set */
@@ -2688,7 +2849,7 @@ passive()
        if (setsockopt(pdata, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) < 0)
                syslog(LOG_WARNING, "pdata setsockopt (SO_REUSEADDR): %m");
 
-       (void) seteuid((uid_t)0);
+       seteuid(0);
 
 #ifdef IP_PORTRANGE
        if (ctrl_addr.su_family == AF_INET) {
@@ -2716,7 +2877,7 @@ passive()
        if (bind(pdata, (struct sockaddr *)&pasv_addr, pasv_addr.su_len) < 0)
                goto pasv_error;
 
-       (void) seteuid((uid_t)pw->pw_uid);
+       seteuid(pw->pw_uid);
 
        len = sizeof(pasv_addr);
        if (getsockname(pdata, (struct sockaddr *) &pasv_addr, &len) < 0)
@@ -2740,8 +2901,8 @@ passive()
        return;
 
 pasv_error:
-       (void) seteuid((uid_t)pw->pw_uid);
-       (void) close(pdata);
+       seteuid(pw->pw_uid);
+       close(pdata);
        pdata = -1;
        perror_reply(425, "Can't open passive connection");
        return;
@@ -2754,11 +2915,10 @@ pasv_error:
  */
 
 void
-long_passive(cmd, pf)
-       char *cmd;
-       int pf;
+long_passive(char *cmd, int pf)
 {
-       int len, on;
+       socklen_t len;
+       int on;
        char *p, *a;
 
        if (pdata >= 0)         /* close old port if one set */
@@ -2785,7 +2945,7 @@ long_passive(cmd, pf)
                                reply(522, "Network protocol mismatch, "
                                        "use (%d)", pf);
                        } else
-                               reply(501, "Network protocol mismatch"); /*XXX*/
+                               reply(501, "Network protocol mismatch."); /*XXX*/
 
                        return;
                }
@@ -2800,7 +2960,7 @@ long_passive(cmd, pf)
        if (setsockopt(pdata, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) < 0)
                syslog(LOG_WARNING, "pdata setsockopt (SO_REUSEADDR): %m");
 
-       (void) seteuid((uid_t)0);
+       seteuid(0);
 
        pasv_addr = ctrl_addr;
        pasv_addr.su_port = 0;
@@ -2830,7 +2990,7 @@ long_passive(cmd, pf)
        if (bind(pdata, (struct sockaddr *)&pasv_addr, len) < 0)
                goto pasv_error;
 
-       (void) seteuid((uid_t)pw->pw_uid);
+       seteuid(pw->pw_uid);
 
        if (getsockname(pdata, (struct sockaddr *) &pasv_addr, &len) < 0)
                goto pasv_error;
@@ -2879,8 +3039,8 @@ long_passive(cmd, pf)
        }
 
 pasv_error:
-       (void) seteuid((uid_t)pw->pw_uid);
-       (void) close(pdata);
+       seteuid(pw->pw_uid);
+       close(pdata);
        pdata = -1;
        perror_reply(425, "Can't open passive connection");
        return;
@@ -2895,9 +3055,7 @@ pasv_error:
  * Generates failure reply on error.
  */
 static int
-guniquefd(local, name)
-       char *local;
-       char **name;
+guniquefd(char *local, char **name)
 {
        static char new[MAXPATHLEN];
        struct stat st;
@@ -2919,13 +3077,13 @@ guniquefd(local, name)
                 * In this extreme case dot won't be put in front of suffix.
                 */
                if (strlen(local) > sizeof(new) - 4) {
-                       reply(553, "Pathname too long");
+                       reply(553, "Pathname too long.");
                        return (-1);
                }
                *cp = '/';
        }
        /* -4 is for the .nn<null> we put on the end below */
-       (void) snprintf(new, sizeof(new) - 4, "%s", local);
+       snprintf(new, sizeof(new) - 4, "%s", local);
        cp = new + strlen(new);
        /* 
         * Don't generate dotfile unless requested explicitly.
@@ -2937,7 +3095,7 @@ guniquefd(local, name)
        for (count = 0; count < 100; count++) {
                /* At count 0 try unmodified name */
                if (count)
-                       (void)sprintf(cp, "%d", count);
+                       sprintf(cp, "%d", count);
                if ((fd = open(count ? new : local,
                    O_RDWR | O_CREAT | O_EXCL, 0666)) >= 0) {
                        *name = count ? new : local;
@@ -2956,9 +3114,7 @@ guniquefd(local, name)
  * Format and send reply containing system error number.
  */
 void
-perror_reply(code, string)
-       int code;
-       char *string;
+perror_reply(int code, char *string)
 {
 
        reply(code, "%s: %s.", string, strerror(errno));
@@ -2970,8 +3126,7 @@ static char *onefile[] = {
 };
 
 void
-send_file_list(whichf)
-       char *whichf;
+send_file_list(char *whichf)
 {
        struct stat st;
        DIR *dirp = NULL;
@@ -2983,14 +3138,14 @@ send_file_list(whichf)
        glob_t gl;
 
        if (strpbrk(whichf, "~{[*?") != NULL) {
-               int flags = GLOB_BRACE|GLOB_NOCHECK|GLOB_QUOTE|GLOB_TILDE;
+               int flags = GLOB_BRACE|GLOB_NOCHECK|GLOB_TILDE;
 
                memset(&gl, 0, sizeof(gl));
                gl.gl_matchc = MAXGLOBARGS;
                flags |= GLOB_LIMIT;
                freeglob = 1;
                if (glob(whichf, flags, 0, &gl)) {
-                       reply(550, "not found");
+                       reply(550, "No matching files found.");
                        goto out;
                } else if (gl.gl_pathc == 0) {
                        errno = ENOENT;
@@ -3011,30 +3166,29 @@ send_file_list(whichf)
                         * used NLST, do what the user meant.
                         */
                        if (dirname[0] == '-' && *dirlist == NULL &&
-                           transflag == 0) {
+                           dout == NULL)
                                retrieve(_PATH_LS " %s", dirname);
-                               goto out;
-                       }
-                       perror_reply(550, whichf);
-                       if (dout != NULL) {
-                               (void) fclose(dout);
-                               transflag = 0;
-                               data = -1;
-                               pdata = -1;
-                       }
+                       else
+                               perror_reply(550, whichf);
                        goto out;
                }
 
                if (S_ISREG(st.st_mode)) {
                        if (dout == NULL) {
-                               dout = dataconn("file list", (off_t)-1, "w");
+                               dout = dataconn("file list", -1, "w");
                                if (dout == NULL)
                                        goto out;
-                               transflag++;
+                               STARTXFER;
                        }
+                       START_UNSAFE;
                        fprintf(dout, "%s%s\n", dirname,
                                type == TYPE_A ? "\r" : "");
-                       byte_count += strlen(dirname) + 1;
+                       END_UNSAFE;
+                       if (ferror(dout))
+                               goto data_err;
+                       byte_count += strlen(dirname) +
+                                     (type == TYPE_A ? 2 : 1);
+                       CHECKOOB(goto abrt);
                        continue;
                } else if (!S_ISDIR(st.st_mode))
                        continue;
@@ -3045,19 +3199,14 @@ send_file_list(whichf)
                while ((dir = readdir(dirp)) != NULL) {
                        char nbuf[MAXPATHLEN];
 
-                       if (recvurg) {
-                               myoob();
-                               recvurg = 0;
-                               transflag = 0;
-                               goto out;
-                       }
+                       CHECKOOB(goto abrt);
 
                        if (strcmp(dir->d_name, ".") == 0)
                                continue;
                        if (strcmp(dir->d_name, "..") == 0)
                                continue;
 
-                       snprintf(nbuf, sizeof(nbuf), 
+                       snprintf(nbuf, sizeof(nbuf),
                                "%s/%s", dirname, dir->d_name);
 
                        /*
@@ -3067,37 +3216,46 @@ send_file_list(whichf)
                        if (simple || (stat(nbuf, &st) == 0 &&
                            S_ISREG(st.st_mode))) {
                                if (dout == NULL) {
-                                       dout = dataconn("file list", (off_t)-1,
-                                               "w");
+                                       dout = dataconn("file list", -1, "w");
                                        if (dout == NULL)
                                                goto out;
-                                       transflag++;
+                                       STARTXFER;
                                }
+                               START_UNSAFE;
                                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;
+                               END_UNSAFE;
+                               if (ferror(dout))
+                                       goto data_err;
+                               byte_count += strlen(nbuf) +
+                                             (type == TYPE_A ? 2 : 1);
+                               CHECKOOB(goto abrt);
                        }
                }
-               (void) closedir(dirp);
+               closedir(dirp);
+               dirp = NULL;
        }
 
        if (dout == NULL)
                reply(550, "No files found.");
-       else if (ferror(dout) != 0)
-               perror_reply(550, "Data connection");
+       else if (ferror(dout))
+data_err:      perror_reply(550, "Data connection");
        else
                reply(226, "Transfer complete.");
-
-       transflag = 0;
-       if (dout != NULL)
-               (void) fclose(dout);
-       data = -1;
-       pdata = -1;
 out:
+       if (dout) {
+               ENDXFER;
+abrt:
+               fclose(dout);
+               data = -1;
+               pdata = -1;
+       }
+       if (dirp)
+               closedir(dirp);
        if (freeglob) {
                freeglob = 0;
                globfree(&gl);
@@ -3105,10 +3263,9 @@ out:
 }
 
 void
-reapchild(signo)
-       int signo;
+reapchild(int signo)
 {
-       while (wait3(NULL, WNOHANG, NULL) > 0);
+       while (waitpid(-1, NULL, WNOHANG) > 0);
 }
 
 #ifdef OLD_SETPROCTITLE
@@ -3118,25 +3275,15 @@ reapchild(signo)
  * have much of an environment or arglist to overwrite.
  */
 void
-#if __STDC__
 setproctitle(const char *fmt, ...)
-#else
-setproctitle(fmt, va_alist)
-       char *fmt;
-        va_dcl
-#endif
 {
        int i;
        va_list ap;
        char *p, *bp, ch;
        char buf[LINE_MAX];
 
-#if __STDC__
        va_start(ap, fmt);
-#else
-       va_start(ap);
-#endif
-       (void)vsnprintf(buf, sizeof(buf), fmt, ap);
+       vsnprintf(buf, sizeof(buf), fmt, ap);
 
        /* make ps print our process name */
        p = Argv[0];
@@ -3157,28 +3304,77 @@ setproctitle(fmt, va_alist)
 #endif /* OLD_SETPROCTITLE */
 
 static void
-logxfer(name, size, start)
-       char *name;
-       off_t size;
-       time_t start;
+appendf(char **strp, char *fmt, ...)
 {
-       char buf[1024];
+       va_list ap;
+       char *ostr, *p;
+
+       va_start(ap, fmt);
+       vasprintf(&p, fmt, ap);
+       va_end(ap);
+       if (p == NULL)
+               fatalerror("Ran out of memory.");
+       if (*strp == NULL)
+               *strp = p;
+       else {
+               ostr = *strp;
+               asprintf(strp, "%s%s", ostr, p);
+               if (*strp == NULL)
+                       fatalerror("Ran out of memory.");
+               free(ostr);
+       }
+}
+
+static void
+logcmd(char *cmd, char *file1, char *file2, off_t cnt)
+{
+       char *msg = NULL;
+       char wd[MAXPATHLEN + 1];
+
+       if (logging <= 1)
+               return;
+
+       if (getcwd(wd, sizeof(wd) - 1) == NULL)
+               strcpy(wd, strerror(errno));
+
+       appendf(&msg, "%s", cmd);
+       if (file1)
+               appendf(&msg, " %s", file1);
+       if (file2)
+               appendf(&msg, " %s", file2);
+       if (cnt >= 0)
+               appendf(&msg, " = %jd bytes", (intmax_t)cnt);
+       appendf(&msg, " (wd: %s", wd);
+       if (guest || dochroot)
+               appendf(&msg, "; chrooted");
+       appendf(&msg, ")");
+       syslog(LOG_INFO, "%s", msg);
+       free(msg);
+}
+
+static void
+logxfer(char *name, off_t size, time_t start)
+{
+       char buf[MAXPATHLEN + 1024];
        char path[MAXPATHLEN + 1];
        time_t now;
 
-       if (statfd >= 0 && getwd(path) != NULL) {
+       if (statfd >= 0) {
                time(&now);
-               snprintf(buf, sizeof(buf), "%.20s!%s!%s!%s/%s!%qd!%ld\n",
+               if (realpath(name, path) == NULL) {
+                       syslog(LOG_NOTICE, "realpath failed on %s: %m", path);
+                       return;
+               }
+               snprintf(buf, sizeof(buf), "%.20s!%s!%s!%s!%jd!%ld\n",
                        ctime(&now)+4, ident, remotehost,
-                       path, name, (long long)size,
+                       path, (intmax_t)size,
                        (long)(now - start + (now == start)));
                write(statfd, buf, strlen(buf));
        }
 }
 
 static char *
-doublequote(s)
-       char *s;
+doublequote(char *s)
 {
        int n;
        char *p, *s2;
@@ -3198,3 +3394,73 @@ doublequote(s)
 
        return (s2);
 }
+
+/* setup server socket for specified address family */
+/* if af is PF_UNSPEC more than one socket may be returned */
+/* the returned list is dynamically allocated, so caller needs to free it */
+static int *
+socksetup(int af, char *bindname, const char *bindport)
+{
+       struct addrinfo hints, *res, *r;
+       int error, maxs, *s, *socks;
+       const int on = 1;
+
+       memset(&hints, 0, sizeof(hints));
+       hints.ai_flags = AI_PASSIVE;
+       hints.ai_family = af;
+       hints.ai_socktype = SOCK_STREAM;
+       error = getaddrinfo(bindname, bindport, &hints, &res);
+       if (error) {
+               syslog(LOG_ERR, "%s", gai_strerror(error));
+               if (error == EAI_SYSTEM)
+                       syslog(LOG_ERR, "%s", strerror(errno));
+               return NULL;
+       }
+
+       /* Count max number of sockets we may open */
+       for (maxs = 0, r = res; r; r = r->ai_next, maxs++)
+               ;
+       socks = malloc((maxs + 1) * sizeof(int));
+       if (!socks) {
+               freeaddrinfo(res);
+               syslog(LOG_ERR, "couldn't allocate memory for sockets");
+               return NULL;
+       }
+
+       *socks = 0;   /* num of sockets counter at start of array */
+       s = socks + 1;
+       for (r = res; r; r = r->ai_next) {
+               *s = socket(r->ai_family, r->ai_socktype, r->ai_protocol);
+               if (*s < 0) {
+                       syslog(LOG_DEBUG, "control socket: %m");
+                       continue;
+               }
+               if (setsockopt(*s, SOL_SOCKET, SO_REUSEADDR,
+                   &on, sizeof(on)) < 0)
+                       syslog(LOG_WARNING,
+                           "control setsockopt (SO_REUSEADDR): %m");
+               if (r->ai_family == AF_INET6) {
+                       if (setsockopt(*s, IPPROTO_IPV6, IPV6_V6ONLY,
+                           &on, sizeof(on)) < 0)
+                               syslog(LOG_WARNING,
+                                   "control setsockopt (IPV6_V6ONLY): %m");
+               }
+               if (bind(*s, r->ai_addr, r->ai_addrlen) < 0) {
+                       syslog(LOG_DEBUG, "control bind: %m");
+                       close(*s);
+                       continue;
+               }
+               (*socks)++;
+               s++;
+       }
+
+       if (res)
+               freeaddrinfo(res);
+
+       if (*socks == 0) {
+               syslog(LOG_ERR, "control socket: Couldn't bind to any socket");
+               free(socks);
+               return NULL;
+       }
+       return(socks);
+}
index 548ed62..7f0b1af 100644 (file)
@@ -34,7 +34,7 @@
  * SUCH DAMAGE.
  *
  * @(#)popen.c 8.3 (Berkeley) 4/6/94
- * $FreeBSD: src/libexec/ftpd/popen.c,v 1.18.2.3 2001/08/09 00:53:18 mikeh Exp $
+ * $FreeBSD: src/libexec/ftpd/popen.c,v 1.26 2004/11/18 13:46:29 yar Exp $
  * $DragonFly: src/libexec/ftpd/popen.c,v 1.3 2006/01/12 13:43:10 corecode Exp $
  */
 
@@ -54,7 +54,6 @@
 #include "pathnames.h"
 #include <syslog.h>
 #include <time.h>
-#include <varargs.h>
 
 #define        MAXUSRARGS      100
 #define        MAXGLOBARGS     1000
@@ -68,8 +67,7 @@ static int *pids;
 static int fds;
 
 FILE *
-ftpd_popen(program, type)
-       char *program, *type;
+ftpd_popen(char *program, char *type)
 {
        char *cp;
        FILE *iop;
@@ -82,7 +80,7 @@ ftpd_popen(program, type)
        if (!pids) {
                if ((fds = getdtablesize()) <= 0)
                        return (NULL);
-               if ((pids = (int *)malloc((u_int)(fds * sizeof(int)))) == NULL)
+               if ((pids = malloc(fds * sizeof(int))) == NULL)
                        return (NULL);
                memset(pids, 0, fds * sizeof(int));
        }
@@ -100,7 +98,7 @@ ftpd_popen(program, type)
        gargv[0] = argv[0];
        for (gargc = argc = 1; argv[argc] && gargc < (MAXGLOBARGS-1); argc++) {
                glob_t gl;
-               int flags = GLOB_BRACE|GLOB_NOCHECK|GLOB_QUOTE|GLOB_TILDE;
+               int flags = GLOB_BRACE|GLOB_NOCHECK|GLOB_TILDE;
 
                memset(&gl, 0, sizeof(gl));
                gl.gl_matchc = MAXGLOBARGS;
@@ -120,24 +118,24 @@ ftpd_popen(program, type)
        pid = (strcmp(gargv[0], _PATH_LS) == 0) ? fork() : vfork();
        switch(pid) {
        case -1:                        /* error */
-               (void)close(pdes[0]);
-               (void)close(pdes[1]);
+               close(pdes[0]);
+               close(pdes[1]);
                goto pfree;
                /* NOTREACHED */
        case 0:                         /* child */
                if (*type == 'r') {
                        if (pdes[1] != STDOUT_FILENO) {
                                dup2(pdes[1], STDOUT_FILENO);
-                               (void)close(pdes[1]);
+                               close(pdes[1]);
                        }
                        dup2(STDOUT_FILENO, STDERR_FILENO); /* stderr too! */
-                       (void)close(pdes[0]);
+                       close(pdes[0]);
                } else {
                        if (pdes[0] != STDIN_FILENO) {
                                dup2(pdes[0], STDIN_FILENO);
-                               (void)close(pdes[0]);
+                               close(pdes[0]);
                        }
-                       (void)close(pdes[1]);
+                       close(pdes[1]);
                }
                if (strcmp(gargv[0], _PATH_LS) == 0) {
                        /* Reset getopt for ls_main() */
@@ -160,10 +158,10 @@ ftpd_popen(program, type)
        /* parent; assume fdopen can't fail...  */
        if (*type == 'r') {
                iop = fdopen(pdes[0], type);
-               (void)close(pdes[1]);
+               close(pdes[1]);
        } else {
                iop = fdopen(pdes[1], type);
-               (void)close(pdes[0]);
+               close(pdes[0]);
        }
        pids[fileno(iop)] = pid;
 
@@ -174,8 +172,7 @@ pfree:      for (argc = 1; gargv[argc] != NULL; argc++)
 }
 
 int
-ftpd_pclose(iop)
-       FILE *iop;
+ftpd_pclose(FILE *iop)
 {
        int fdes, omask, status;
        pid_t pid;
@@ -186,11 +183,11 @@ ftpd_pclose(iop)
         */
        if (pids == 0 || pids[fdes = fileno(iop)] == 0)
                return (-1);
-       (void)fclose(iop);
+       fclose(iop);
        omask = sigblock(sigmask(SIGINT)|sigmask(SIGQUIT)|sigmask(SIGHUP));
        while ((pid = waitpid(pids[fdes], &status, 0)) < 0 && errno == EINTR)
                continue;
-       (void)sigsetmask(omask);
+       sigsetmask(omask);
        pids[fdes] = 0;
        if (pid < 0)
                return (pid);
diff --git a/libexec/ftpd/skey-stuff.c b/libexec/ftpd/skey-stuff.c
deleted file mode 100644 (file)
index 699238c..0000000
+++ /dev/null
@@ -1,31 +0,0 @@
-/* Author: Wietse Venema, Eindhoven University of Technology. 
- *
- * $FreeBSD: src/libexec/ftpd/skey-stuff.c,v 1.12 1999/08/28 00:09:32 peter Exp $
- * $DragonFly: src/libexec/ftpd/skey-stuff.c,v 1.2 2003/06/17 04:27:07 dillon Exp $
- */
-
-#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. */
-
-    *buf = '\0';
-    if (pwd == NULL || skeychallenge(&skey, pwd->pw_name, buf))
-       snprintf(buf, sizeof(buf), "Password required for %s.", name);
-    else if (!pwok)
-       strcat(buf, " (s/key required)");
-    return (buf);
-}