From bf9ce431742c80472b7ecab7eb0f3948b9eee24c Mon Sep 17 00:00:00 2001 From: Peter Avalos Date: Fri, 2 Jan 2009 18:16:23 -0500 Subject: [PATCH] Sync ftpd(8) with FreeBSD. Here are the highlights: -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 | 32 +- libexec/ftpd/Makefile | 33 +- libexec/ftpd/extern.h | 71 +- libexec/ftpd/ftpchroot.5 | 35 +- libexec/ftpd/ftpcmd.y | 405 +++++++----- libexec/ftpd/ftpd.8 | 182 ++--- libexec/ftpd/ftpd.c | 1620 ++++++++++++++++++++++++++------------------- libexec/ftpd/popen.c | 33 +- libexec/ftpd/skey-stuff.c | 31 - 9 files changed, 1408 insertions(+), 1034 deletions(-) rewrite etc/pam.d/ftpd (63%) delete mode 100644 libexec/ftpd/skey-stuff.c diff --git a/etc/pam.d/ftpd b/etc/pam.d/ftpd dissimilarity index 63% index ece3d86b95..62cdd0c7e3 100644 --- a/etc/pam.d/ftpd +++ b/etc/pam.d/ftpd @@ -1,11 +1,21 @@ -# -# $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 +# +# $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 +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 diff --git a/libexec/ftpd/Makefile b/libexec/ftpd/Makefile index a66e9f35e7..92954df920 100644 --- a/libexec/ftpd/Makefile +++ b/libexec/ftpd/Makefile @@ -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 diff --git a/libexec/ftpd/extern.h b/libexec/ftpd/extern.h index 739068d365..48ecf0a612 100644 --- a/libexec/ftpd/extern.h +++ b/libexec/ftpd/extern.h @@ -31,51 +31,48 @@ * 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 #include -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; diff --git a/libexec/ftpd/ftpchroot.5 b/libexec/ftpd/ftpchroot.5 index 2b6e66be07..43f24a10c3 100644 --- a/libexec/ftpd/ftpchroot.5 +++ b/libexec/ftpd/ftpchroot.5 @@ -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 , diff --git a/libexec/ftpd/ftpcmd.y b/libexec/ftpd/ftpcmd.y index feec74f9dc..14e895d2fa 100644 --- a/libexec/ftpd/ftpcmd.y +++ b/libexec/ftpd/ftpcmd.y @@ -30,10 +30,8 @@ * 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 #include #include +#include #include #include #include @@ -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, "[ path-name ]" }, { "SITE", SITE, SITECMD, 1, "site-cmd [ arguments ]" }, { "SYST", SYST, ARGS, 1, "(get type of operating system)" }, + { "FEAT", FEAT, ARGS, 1, "(get extended features)" }, { "STAT", STAT, OSTR, 1, "[ path-name ]" }, { "HELP", HELP, OSTR, 1, "[ ]" }, { "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; diff --git a/libexec/ftpd/ftpd.8 b/libexec/ftpd/ftpd.8 index f900d1d8bd..63545c7c1a 100644 --- a/libexec/ftpd/ftpd.8 +++ b/libexec/ftpd/ftpd.8 @@ -30,10 +30,10 @@ .\" 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 @@ -41,20 +41,22 @@ .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. diff --git a/libexec/ftpd/ftpd.c b/libexec/ftpd/ftpd.c index 960de72381..cefdd99140 100644 --- a/libexec/ftpd/ftpd.c +++ b/libexec/ftpd/ftpd.c @@ -30,16 +30,11 @@ * 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 #include #include +#include #include +#include #include #include #include @@ -83,22 +80,15 @@ static const char rcsid[] = #include #endif -#ifdef SKEY -#include -#endif - -#if !defined(NOPAM) +#ifdef USE_PAM #include #endif #include "pathnames.h" #include "extern.h" +#include "pidfile.h" -#if __STDC__ #include -#else -#include -#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.", @@ -1960,45 +2005,84 @@ pdata_err: } /* + * 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. * * 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 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); +} diff --git a/libexec/ftpd/popen.c b/libexec/ftpd/popen.c index 548ed626ea..7f0b1af635 100644 --- a/libexec/ftpd/popen.c +++ b/libexec/ftpd/popen.c @@ -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 #include -#include #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 index 699238c4dc..0000000000 --- a/libexec/ftpd/skey-stuff.c +++ /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 -#include -#include - -#include - -/* 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); -} -- 2.11.4.GIT