Please note that diffs are not public domain; they are subject to the copyright notices on the relevant files. =================================================================== RCS file: /ftp/cvs/cvsroot/src/libexec/ftpd/ftpcmd.y,v rcsdiff: /ftp/cvs/cvsroot/src/libexec/ftpd/ftpcmd.y,v: warning: Unknown phrases like `commitid ...;' are present. retrieving revision 1.25 retrieving revision 1.48.2.2 diff -u -p -r1.25 -r1.48.2.2 --- src/libexec/ftpd/ftpcmd.y 1999/02/05 21:40:49 1.25 +++ src/libexec/ftpd/ftpcmd.y 2001/03/29 14:14:17 1.48.2.2 @@ -1,4 +1,40 @@ -/* $NetBSD: ftpcmd.y,v 1.25 1999/02/05 21:40:49 lukem Exp $ */ +/* $NetBSD: ftpcmd.y,v 1.48.2.2 2001/03/29 14:14:17 lukem Exp $ */ + +/*- + * Copyright (c) 1997-2000 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code is derived from software contributed to The NetBSD Foundation + * by Luke Mewburn. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the NetBSD + * Foundation, Inc. and its contributors. + * 4. Neither the name of The NetBSD Foundation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ /* * Copyright (c) 1985, 1988, 1993, 1994 @@ -47,7 +83,7 @@ #if 0 static char sccsid[] = "@(#)ftpcmd.y 8.3 (Berkeley) 4/6/94"; #else -__RCSID("$NetBSD: ftpcmd.y,v 1.25 1999/02/05 21:40:49 lukem Exp $"); +__RCSID("$NetBSD: ftpcmd.y,v 1.48.2.2 2001/03/29 14:14:17 lukem Exp $"); #endif #endif /* not lint */ @@ -72,19 +108,21 @@ __RCSID("$NetBSD: ftpcmd.y,v 1.25 1999/0 #include #include #include +#include -#include "extern.h" +#ifdef KERBEROS5 +#include +#endif -off_t restart_point; +#include "extern.h" +#include "version.h" static int cmd_type; static int cmd_form; static int cmd_bytesz; + char cbuf[512]; char *fromname; -int hasyyerrored; - -extern jmp_buf errcatch; %} @@ -111,19 +149,22 @@ extern jmp_buf errcatch; FEAT OPTS - SIZE MDTM + SIZE MDTM MLST MLSD + + LPRT LPSV EPRT EPSV MAIL MLFL MRCP MRSQ MSAM MSND MSOM - UMASK IDLE CHMOD + CHMOD IDLE RATEGET RATEPUT UMASK LEXERR %token STRING +%token ALL %token NUMBER -%type check_login check_modify octal_number byte_size +%type check_login octal_number byte_size %type struct_code mode_code type_code form_code decimal_integer %type pathstring pathname password username %type mechanism_name base64data prot_code @@ -156,13 +197,14 @@ cmd | PASS SP password CRLF { pass($3); + memset($3, 0, strlen($3)); free($3); } | CWD check_login CRLF { if ($2) - cwd(pw->pw_dir); + cwd(homedir); } | CWD check_login SP pathname CRLF @@ -181,42 +223,110 @@ cmd | QUIT CRLF { - reply(221, "Goodbye."); + if (logged_in) { + reply(-221, "%s", ""); + reply(0, + "Data traffic for this session was " LLF " byte%s in " LLF " file%s.", + (LLT)total_data, PLURAL(total_data), + (LLT)total_files, PLURAL(total_files)); + reply(0, + "Total traffic for this session was " LLF " byte%s in " LLF " transfer%s.", + (LLT)total_bytes, PLURAL(total_bytes), + (LLT)total_xfers, PLURAL(total_xfers)); + } + reply(221, + "Thank you for using the FTP service on %s.", + hostname); + if (logged_in && logging) { + syslog(LOG_INFO, + "Data traffic: " LLF " byte%s in " LLF " file%s", + (LLT)total_data, PLURAL(total_data), + (LLT)total_files, PLURAL(total_files)); + syslog(LOG_INFO, + "Total traffic: " LLF " byte%s in " LLF " transfer%s", + (LLT)total_bytes, PLURAL(total_bytes), + (LLT)total_xfers, PLURAL(total_xfers)); + } + dologout(0); } | PORT check_login SP host_port CRLF { + if ($2) + port_check("PORT", AF_INET); + } + + | LPRT check_login SP host_long_port4 CRLF + { + if ($2) + port_check("LPRT", AF_INET); + } + + | LPRT check_login SP host_long_port6 CRLF + { +#ifdef INET6 + if ($2) + port_check("LPRT", AF_INET6); +#else + reply(500, "IPv6 support not available."); +#endif + } + + | EPRT check_login SP STRING CRLF + { if ($2) { - /* be paranoid, if told so */ - if (curclass.checkportcmd && - ((ntohs(data_dest.sin_port) < IPPORT_RESERVED) || - memcmp(&data_dest.sin_addr, &his_addr.sin_addr, - sizeof(data_dest.sin_addr)) != 0)) { - reply(500, - "Illegal PORT command rejected"); - } else { - usedefault = 0; - if (pdata >= 0) { - (void) close(pdata); - pdata = -1; - } - reply(200, "PORT command successful."); - } + if (extended_port($4) == 0) + port_check("EPRT", -1); } + free($4); } | PASV check_login CRLF { - if (curclass.passive) { - passive(); - } else { - reply(500, "PASV mode not available."); + if ($2) { + if (CURCLASS_FLAGS_ISSET(passive)) + passive(); + else + reply(500, "PASV mode not available."); + } + } + + | LPSV check_login CRLF + { + if ($2) { + if (epsvall) + reply(501, + "LPSV disallowed after EPSV ALL"); + else + long_passive("LPSV", PF_UNSPEC); } } - | TYPE SP type_code CRLF + | EPSV check_login SP NUMBER CRLF { + if ($2) + long_passive("EPSV", epsvproto2af($4)); + } + + | EPSV check_login SP ALL CRLF + { + if ($2) { + reply(200, "EPSV ALL command successful."); + epsvall++; + } + } + + | EPSV check_login CRLF + { + if ($2) + long_passive("EPSV", PF_UNSPEC); + } + + | TYPE check_login SP type_code CRLF + { + if ($2) { + switch (cmd_type) { case TYPE_A: @@ -249,31 +359,37 @@ cmd UNIMPLEMENTED for NBBY != 8 #endif /* NBBY == 8 */ } + + } } - | STRU SP struct_code CRLF + | STRU check_login SP struct_code CRLF { - switch ($3) { + if ($2) { + switch ($4) { - case STRU_F: - reply(200, "STRU F ok."); - break; + case STRU_F: + reply(200, "STRU F ok."); + break; - default: - reply(504, "Unimplemented STRU type."); + default: + reply(504, "Unimplemented STRU type."); + } } } - | MODE SP mode_code CRLF + | MODE check_login SP mode_code CRLF { - switch ($3) { + if ($2) { + switch ($4) { - case MODE_S: - reply(200, "MODE S ok."); - break; + case MODE_S: + reply(200, "MODE S ok."); + break; - default: - reply(502, "Unimplemented MODE type."); + default: + reply(502, "Unimplemented MODE type."); + } } } @@ -285,79 +401,85 @@ cmd free($4); } - | STOR check_login SP pathname CRLF + | STOR SP pathname CRLF { - if ($2 && $4 != NULL) - store($4, "w", 0); - if ($4 != NULL) - free($4); + if (check_write($3, 1)) + store($3, "w", 0); + if ($3 != NULL) + free($3); } - | STOU check_login SP pathname CRLF + | STOU SP pathname CRLF { - if ($2 && $4 != NULL) - store($4, "w", 1); - if ($4 != NULL) - free($4); + if (check_write($3, 1)) + store($3, "w", 1); + if ($3 != NULL) + free($3); } - | APPE check_login SP pathname CRLF + | APPE SP pathname CRLF { - if ($2 && $4 != NULL) - store($4, "a", 0); - if ($4 != NULL) - free($4); + if (check_write($3, 1)) + store($3, "a", 0); + if ($3 != NULL) + free($3); } - | ALLO SP NUMBER CRLF + | ALLO check_login SP NUMBER CRLF { - reply(202, "ALLO command ignored."); + if ($2) + reply(202, "ALLO command ignored."); } - | ALLO SP NUMBER SP R SP NUMBER CRLF + | ALLO check_login SP NUMBER SP R SP NUMBER CRLF { - reply(202, "ALLO command ignored."); + if ($2) + reply(202, "ALLO command ignored."); } | RNTO SP pathname CRLF { - if (fromname) { - renamecmd(fromname, $3); - free(fromname); - fromname = NULL; - } else { - reply(503, "Bad sequence of commands."); + if (check_write($3, 0)) { + if (fromname) { + renamecmd(fromname, $3); + free(fromname); + fromname = NULL; + } else { + reply(503, "Bad sequence of commands."); + } } - free($3); + if ($3 != NULL) + free($3); } - | ABOR CRLF + | ABOR check_login CRLF { - reply(225, "ABOR command successful."); + if ($2) + reply(225, "ABOR command successful."); } - | DELE check_modify SP pathname CRLF + | DELE SP pathname CRLF { - if ($2 && $4 != NULL) - delete($4); - if ($4 != NULL) - free($4); + if (check_write($3, 0)) + delete($3); + if ($3 != NULL) + free($3); } - | RMD check_modify SP pathname CRLF + | RMD SP pathname CRLF { - if ($2 && $4 != NULL) - removedir($4); - if ($4 != NULL) - free($4); + if (check_write($3, 0)) + removedir($3); + if ($3 != NULL) + free($3); } - | MKD check_modify SP pathname CRLF + | MKD SP pathname CRLF { - if ($2 && $4 != NULL) - makedir($4); - if ($4 != NULL) - free($4); + if (check_write($3, 0)) + makedir($3); + if ($3 != NULL) + free($3); } | PWD check_login CRLF @@ -368,14 +490,20 @@ cmd | LIST check_login CRLF { + char *argv[] = { INTERNAL_LS, "-lgA", NULL }; + if ($2) - retrieve("/bin/ls -lgA", ""); + retrieve(argv, ""); } | LIST check_login SP pathname CRLF { - if ($2 && $4 != NULL) - retrieve("/bin/ls -lgA %s", $4); + char *argv[] = { INTERNAL_LS, "-lgA", NULL, NULL }; + + if ($2 && $4 != NULL) { + argv[2] = $4; + retrieve(argv, $4); + } if ($4 != NULL) free($4); } @@ -386,12 +514,11 @@ cmd send_file_list("."); } - | NLST check_login SP STRING CRLF + | NLST check_login SP pathname CRLF { - if ($2 && $4 != NULL) + if ($2) send_file_list($4); - if ($4 != NULL) - free($4); + free($4); } | SITE SP HELP CRLF @@ -399,78 +526,155 @@ cmd help(sitetab, NULL); } + | SITE SP CHMOD SP octal_number SP pathname CRLF + { + if (check_write($7, 0)) { + if ($5 > 0777) + reply(501, + "CHMOD: Mode value must be between 0 and 0777"); + else if (chmod($7, $5) < 0) + perror_reply(550, $7); + else + reply(200, "CHMOD command successful."); + } + if ($7 != NULL) + free($7); + } + | SITE SP HELP SP STRING CRLF { help(sitetab, $5); + free($5); } - | SITE SP UMASK check_login CRLF + | SITE SP IDLE check_login CRLF { - int oldmask; + if ($4) { + reply(200, + "Current IDLE time limit is %d seconds; max %d", + curclass.timeout, curclass.maxtimeout); + } + } + | SITE SP IDLE check_login SP NUMBER CRLF + { if ($4) { - oldmask = umask(0); - (void) umask(oldmask); - reply(200, "Current UMASK is %03o", oldmask); + if ($6 < 30 || $6 > curclass.maxtimeout) { + reply(501, + "IDLE time limit must be between 30 and %d seconds", + curclass.maxtimeout); + } else { + curclass.timeout = $6; + (void) alarm(curclass.timeout); + reply(200, + "IDLE time limit set to %d seconds", + curclass.timeout); + } } } - | SITE SP UMASK check_modify SP octal_number CRLF + | SITE SP RATEGET check_login CRLF { - int oldmask; + if ($4) { + reply(200, + "Current RATEGET is " LLF " bytes/sec", + (LLT)curclass.rateget); + } + } + + | SITE SP RATEGET check_login SP STRING CRLF + { + char *p = $6; + LLT rate; if ($4) { - if (($6 == -1) || ($6 > 0777)) { - reply(501, "Bad UMASK value"); - } else { - oldmask = umask($6); + rate = strsuftoll(p); + if (rate == -1) + reply(501, "Invalid RATEGET %s", p); + else if (curclass.maxrateget && + rate > curclass.maxrateget) + reply(501, + "RATEGET " LLF " is larger than maximum RATEGET " LLF, + (LLT)rate, + (LLT)curclass.maxrateget); + else { + curclass.rateget = rate; reply(200, - "UMASK set to %03o (was %03o)", - $6, oldmask); + "RATEGET set to " LLF " bytes/sec", + (LLT)curclass.rateget); } } + free($6); + } + + | SITE SP RATEPUT check_login CRLF + { + if ($4) { + reply(200, + "Current RATEPUT is " LLF " bytes/sec", + (LLT)curclass.rateput); + } } - | SITE SP CHMOD check_modify SP octal_number SP pathname CRLF + | SITE SP RATEPUT check_login SP STRING CRLF { - if ($4 && ($8 != NULL)) { - if ($6 > 0777) + char *p = $6; + LLT rate; + + if ($4) { + rate = strsuftoll(p); + if (rate == -1) + reply(501, "Invalid RATEPUT %s", p); + else if (curclass.maxrateput && + rate > curclass.maxrateput) reply(501, - "CHMOD: Mode value must be between 0 and 0777"); - else if (chmod($8, $6) < 0) - perror_reply(550, $8); - else - reply(200, "CHMOD command successful."); + "RATEPUT " LLF " is larger than maximum RATEPUT " LLF, + (LLT)rate, + (LLT)curclass.maxrateput); + else { + curclass.rateput = rate; + reply(200, + "RATEPUT set to " LLF " bytes/sec", + (LLT)curclass.rateput); + } } - if ($8 != NULL) - free($8); + free($6); } - | SITE SP IDLE CRLF + | SITE SP UMASK check_login CRLF { - reply(200, - "Current IDLE time limit is %d seconds; max %d", - curclass.timeout, curclass.maxtimeout); + int oldmask; + + if ($4) { + oldmask = umask(0); + (void) umask(oldmask); + reply(200, "Current UMASK is %03o", oldmask); + } } - | SITE SP IDLE SP NUMBER CRLF + | SITE SP UMASK check_login SP octal_number CRLF { - if ($5 < 30 || $5 > curclass.maxtimeout) { - reply(501, - "IDLE time limit must be between 30 and %d seconds", - curclass.maxtimeout); - } else { - curclass.timeout = $5; - (void) alarm(curclass.timeout); - reply(200, - "IDLE time limit set to %d seconds", - curclass.timeout); + int oldmask; + + if ($4 && CURCLASS_FLAGS_ISSET(modify)) { + if (($6 == -1) || ($6 > 0777)) { + reply(501, "Bad UMASK value"); + } else { + oldmask = umask($6); + reply(200, + "UMASK set to %03o (was %03o)", + $6, oldmask); + } } } | SYST CRLF { - reply(215, "UNIX Type: L%d %s", NBBY, version); + if (EMPTYSTR(version)) + reply(215, "UNIX Type: L%d", NBBY); + else + reply(215, "UNIX Type: L%d Version: %s", NBBY, + version); } | STAT check_login SP pathname CRLF @@ -505,6 +709,7 @@ cmd help(sitetab, NULL); } else help(cmdtab, $3); + free($3); } | NOOP CRLF @@ -516,18 +721,21 @@ cmd | AUTH SP mechanism_name CRLF { reply(502, "RFC 2228 authentication not implemented."); + free($3); } | ADAT SP base64data CRLF { reply(503, "Please set authentication state with AUTH."); + free($3); } | PROT SP prot_code CRLF { reply(503, "Please set protection buffer size with PBSZ."); + free($3); } | PBSZ SP decimal_integer CRLF @@ -544,41 +752,39 @@ cmd | MIC SP base64data CRLF { reply(502, "RFC 2228 authentication not implemented."); + free($3); } | CONF SP base64data CRLF { reply(502, "RFC 2228 authentication not implemented."); + free($3); } | ENC SP base64data CRLF { reply(502, "RFC 2228 authentication not implemented."); + free($3); } /* RFC 2389 */ | FEAT CRLF { - lreply(211, "Features supported"); - printf(" MDTM\r\n"); - printf(" REST STREAM\r\n"); - printf(" SIZE\r\n"); - reply(211, "End"); + + feat(); } | OPTS SP STRING CRLF { opts($3); + free($3); } - /* BSD extensions */ + /* extensions from draft-ietf-ftpext-mlst-11 */ /* - * SIZE is not in RFC 959, but Postel has blessed it and - * it will be in the updated RFC. - * * Return size of file in a format suitable for * using with RESTART (we just count bytes). */ @@ -591,9 +797,6 @@ cmd } /* - * MDTM is not in RFC 959, but Postel has blessed it and - * it will be in the updated RFC. - * * Return modification time of file as an ISO 3307 * style time. E.g. YYYYMMDDHHMMSS or YYYYMMDDHHMMSS.xxx * where xxx is the fractional second (of any precision, @@ -609,6 +812,7 @@ cmd reply(550, "%s: not a plain file.", $4); } else { struct tm *t; + t = gmtime(&stbuf.st_mtime); reply(213, "%04d%02d%02d%02d%02d%02d", @@ -621,6 +825,32 @@ cmd free($4); } + | MLST check_login SP pathname CRLF + { + if ($2 && $4 != NULL) + mlst($4); + if ($4 != NULL) + free($4); + } + + | MLST check_login CRLF + { + mlst(NULL); + } + + | MLSD check_login SP pathname CRLF + { + if ($2 && $4 != NULL) + mlsd($4); + if ($4 != NULL) + free($4); + } + + | MLSD check_login CRLF + { + mlsd(NULL); + } + | error CRLF { yyerrok; @@ -628,22 +858,24 @@ cmd ; rcmd - : REST SP byte_size CRLF + : REST check_login SP byte_size CRLF { - fromname = NULL; - restart_point = $3; /* XXX $3 is only "int" */ - reply(350, "Restarting at %qd. %s", restart_point, - "Send STORE or RETRIEVE to initiate transfer."); + if ($2) { + fromname = NULL; + restart_point = $4; /* XXX: $4 is only "int" */ + reply(350, + "Restarting at " LLF ". Send STORE or RETRIEVE to initiate transfer.", + (LLT)restart_point); + } } - | RNFR check_modify SP pathname CRLF + + | RNFR SP pathname CRLF { restart_point = (off_t) 0; - if ($2 && $4) { - fromname = renamefrom($4); - if (fromname == NULL && $4) { - free($4); - } - } + if (check_write($3, 0)) + fromname = renamefrom($3); + if ($3 != NULL) + free($3); } ; @@ -670,15 +902,71 @@ host_port { char *a, *p; - data_dest.sin_len = sizeof(struct sockaddr_in); - data_dest.sin_family = AF_INET; - p = (char *)&data_dest.sin_port; + memset(&data_dest, 0, sizeof(data_dest)); + data_dest.su_len = sizeof(struct sockaddr_in); + data_dest.su_family = AF_INET; + p = (char *)&data_dest.su_port; p[0] = $9; p[1] = $11; - a = (char *)&data_dest.sin_addr; + a = (char *)&data_dest.su_addr; a[0] = $1; a[1] = $3; a[2] = $5; a[3] = $7; } ; +host_long_port4 + : NUMBER COMMA NUMBER COMMA NUMBER COMMA NUMBER COMMA + NUMBER COMMA NUMBER COMMA NUMBER COMMA NUMBER COMMA + NUMBER + { + char *a, *p; + + memset(&data_dest, 0, sizeof(data_dest)); + data_dest.su_len = sizeof(struct sockaddr_in); + data_dest.su_family = AF_INET; + p = (char *)&data_dest.su_port; + p[0] = $15; p[1] = $17; + a = (char *)&data_dest.su_addr; + a[0] = $5; a[1] = $7; a[2] = $9; a[3] = $11; + + /* reject invalid LPRT command */ + if ($1 != 4 || $3 != 4 || $13 != 2) + memset(&data_dest, 0, sizeof(data_dest)); + } + ; + +host_long_port6 + : NUMBER COMMA NUMBER COMMA NUMBER COMMA NUMBER COMMA + NUMBER COMMA NUMBER COMMA NUMBER COMMA NUMBER COMMA + NUMBER COMMA NUMBER COMMA NUMBER COMMA NUMBER COMMA + NUMBER COMMA NUMBER COMMA NUMBER COMMA NUMBER COMMA + NUMBER COMMA NUMBER COMMA NUMBER COMMA NUMBER COMMA + NUMBER + { +#ifdef INET6 + char *a, *p; + + memset(&data_dest, 0, sizeof(data_dest)); + data_dest.su_len = sizeof(struct sockaddr_in6); + data_dest.su_family = AF_INET6; + p = (char *)&data_dest.su_port; + p[0] = $39; p[1] = $41; + a = (char *)&data_dest.si_su.su_sin6.sin6_addr; + a[0] = $5; a[1] = $7; a[2] = $9; a[3] = $11; + a[4] = $13; a[5] = $15; a[6] = $17; a[7] = $19; + a[8] = $21; a[9] = $23; a[10] = $25; a[11] = $27; + a[12] = $29; a[13] = $31; a[14] = $33; a[15] = $35; + if (his_addr.su_family == AF_INET6) { + /* XXX: more sanity checks! */ + data_dest.su_scope_id = his_addr.su_scope_id; + } +#else + memset(&data_dest, 0, sizeof(data_dest)); +#endif /* INET6 */ + /* reject invalid LPRT command */ + if ($1 != 6 || $3 != 16 || $37 != 2) + memset(&data_dest, 0, sizeof(data_dest)); + } + ; + form_code : N { @@ -791,11 +1079,10 @@ pathname */ if (logged_in && $1 && *$1 == '~') { glob_t gl; - int flags = - GLOB_BRACE|GLOB_NOCHECK|GLOB_TILDE; + int flags = GLOB_BRACE|GLOB_NOCHECK|GLOB_TILDE; if ($1[1] == '\0') - $$ = xstrdup(pw->pw_dir); + $$ = xstrdup(homedir); else { memset(&gl, 0, sizeof(gl)); if (glob($1, flags, NULL, &gl) || @@ -871,25 +1158,6 @@ check_login } ; -check_modify - : /* empty */ - { - if (logged_in) { - if (curclass.modify) - $$ = 1; - else { - reply(502, - "No permission to use this command."); - $$ = 0; - hasyyerrored = 1; - } - } else { - reply(530, "Please login with USER and PASS."); - $$ = 0; - hasyyerrored = 1; - } - } - %% #define CMD 0 /* beginning of command */ @@ -903,106 +1171,144 @@ check_modify #define NSTR 8 /* Number followed by a string */ #define NOARGS 9 /* No arguments allowed */ -struct tab { - char *name; - short token; - short state; - short implemented; /* 1 if command is implemented */ - short hasopts; /* 1 if command takes options */ - char *help; - char *options; -}; - struct tab cmdtab[] = { /* From RFC 959, in order defined (5.3.1) */ - { "USER", USER, STR1, 1, 0, " username" }, - { "PASS", PASS, ZSTR1, 1, 0, " password" }, - { "ACCT", ACCT, STR1, 0, 0, "(specify account)" }, - { "CWD", CWD, OSTR, 1, 0, "[ directory-name ]" }, - { "CDUP", CDUP, NOARGS, 1, 0, "(change to parent directory)" }, - { "SMNT", SMNT, ARGS, 0, 0, "(structure mount)" }, - { "QUIT", QUIT, NOARGS, 1, 0, "(terminate service)", }, - { "REIN", REIN, NOARGS, 0, 0, "(reinitialize server state)" }, - { "PORT", PORT, ARGS, 1, 0, " b0, b1, b2, b3, b4" }, - { "PASV", PASV, NOARGS, 1, 0, "(set server in passive mode)" }, - { "TYPE", TYPE, ARGS, 1, 0, " [ A | E | I | L ]" }, - { "STRU", STRU, ARGS, 1, 0, "(specify file structure)" }, - { "MODE", MODE, ARGS, 1, 0, "(specify transfer mode)" }, - { "RETR", RETR, STR1, 1, 0, " file-name" }, - { "STOR", STOR, STR1, 1, 0, " file-name" }, - { "STOU", STOU, STR1, 1, 0, " file-name" }, - { "APPE", APPE, STR1, 1, 0, " file-name" }, - { "ALLO", ALLO, ARGS, 1, 0, "allocate storage (vacuously)" }, - { "REST", REST, ARGS, 1, 0, " offset (restart command)" }, - { "RNFR", RNFR, STR1, 1, 0, " file-name" }, - { "RNTO", RNTO, STR1, 1, 0, " file-name" }, - { "ABOR", ABOR, NOARGS, 1, 0, "(abort operation)" }, - { "DELE", DELE, STR1, 1, 0, " file-name" }, - { "RMD", RMD, STR1, 1, 0, " path-name" }, - { "MKD", MKD, STR1, 1, 0, " path-name" }, - { "PWD", PWD, NOARGS, 1, 0, "(return current directory)" }, - { "LIST", LIST, OSTR, 1, 0, "[ path-name ]" }, - { "NLST", NLST, OSTR, 1, 0, "[ path-name ]" }, - { "SITE", SITE, SITECMD, 1, 0, "site-cmd [ arguments ]" }, - { "SYST", SYST, NOARGS, 1, 0, "(get type of operating system)" }, - { "STAT", STAT, OSTR, 1, 0, "[ path-name ]" }, - { "HELP", HELP, OSTR, 1, 0, "[ ]" }, - { "NOOP", NOOP, NOARGS, 1, 1, "" }, + { "USER", USER, STR1, 1, " username" }, + { "PASS", PASS, ZSTR1, 1, " password" }, + { "ACCT", ACCT, STR1, 0, "(specify account)" }, + { "CWD", CWD, OSTR, 1, "[ directory-name ]" }, + { "CDUP", CDUP, NOARGS, 1, "(change to parent directory)" }, + { "SMNT", SMNT, ARGS, 0, "(structure mount)" }, + { "QUIT", QUIT, NOARGS, 1, "(terminate service)" }, + { "REIN", REIN, NOARGS, 0, "(reinitialize server state)" }, + { "PORT", PORT, ARGS, 1, " b0, b1, b2, b3, b4" }, + { "LPRT", LPRT, ARGS, 1, " af, hal, h1, h2, h3,..., pal, p1, p2..." }, + { "EPRT", EPRT, STR1, 1, " |af|addr|port|" }, + { "PASV", PASV, NOARGS, 1, "(set server in passive mode)" }, + { "LPSV", LPSV, ARGS, 1, "(set server in passive mode)" }, + { "EPSV", EPSV, ARGS, 1, "[ af|ALL]" }, + { "TYPE", TYPE, ARGS, 1, " [ A | E | I | L ]" }, + { "STRU", STRU, ARGS, 1, "(specify file structure)" }, + { "MODE", MODE, ARGS, 1, "(specify transfer mode)" }, + { "RETR", RETR, STR1, 1, " file-name" }, + { "STOR", STOR, STR1, 1, " file-name" }, + { "STOU", STOU, STR1, 1, " file-name" }, + { "APPE", APPE, STR1, 1, " file-name" }, + { "ALLO", ALLO, ARGS, 1, "allocate storage (vacuously)" }, + { "REST", REST, ARGS, 1, " offset (restart command)" }, + { "RNFR", RNFR, STR1, 1, " file-name" }, + { "RNTO", RNTO, STR1, 1, " file-name" }, + { "ABOR", ABOR, NOARGS, 1, "(abort operation)" }, + { "DELE", DELE, STR1, 1, " file-name" }, + { "RMD", RMD, STR1, 1, " path-name" }, + { "MKD", MKD, STR1, 1, " path-name" }, + { "PWD", PWD, NOARGS, 1, "(return current directory)" }, + { "LIST", LIST, OSTR, 1, "[ path-name ]" }, + { "NLST", NLST, OSTR, 1, "[ path-name ]" }, + { "SITE", SITE, SITECMD, 1, "site-cmd [ arguments ]" }, + { "SYST", SYST, NOARGS, 1, "(get type of operating system)" }, + { "STAT", STAT, OSTR, 1, "[ path-name ]" }, + { "HELP", HELP, OSTR, 1, "[ ]" }, + { "NOOP", NOOP, NOARGS, 2, "" }, /* From RFC 2228, in order defined */ - { "AUTH", AUTH, STR1, 1, 0, " mechanism-name" }, - { "ADAT", ADAT, STR1, 1, 0, " base-64-data" }, - { "PROT", PROT, STR1, 1, 0, " prot-code" }, - { "PBSZ", PBSZ, ARGS, 1, 0, " decimal-integer" }, - { "CCC", CCC, NOARGS, 1, 0, "(Disable data protection)" }, - { "MIC", MIC, STR1, 1, 0, " base64data" }, - { "CONF", CONF, STR1, 1, 0, " base64data" }, - { "ENC", ENC, STR1, 1, 0, " base64data" }, + { "AUTH", AUTH, STR1, 1, " mechanism-name" }, + { "ADAT", ADAT, STR1, 1, " base-64-data" }, + { "PROT", PROT, STR1, 1, " prot-code" }, + { "PBSZ", PBSZ, ARGS, 1, " decimal-integer" }, + { "CCC", CCC, NOARGS, 1, "(Disable data protection)" }, + { "MIC", MIC, STR1, 1, " base64data" }, + { "CONF", CONF, STR1, 1, " base64data" }, + { "ENC", ENC, STR1, 1, " base64data" }, /* From RFC 2389, in order defined */ - { "FEAT", FEAT, NOARGS, 1, 0, "(display extended features)" }, - { "OPTS", OPTS, STR1, 1, 0, " command [ options ]" }, + { "FEAT", FEAT, NOARGS, 1, "(display extended features)" }, + { "OPTS", OPTS, STR1, 1, " command [ options ]" }, - /* Non standardized extensions */ - { "SIZE", SIZE, OSTR, 1, 0, " path-name" }, - { "MDTM", MDTM, OSTR, 1, 0, " path-name" }, + /* from draft-ietf-ftpext-mlst-11 */ + { "MDTM", MDTM, OSTR, 1, " path-name" }, + { "SIZE", SIZE, OSTR, 1, " path-name" }, + { "MLST", MLST, OSTR, 2, "[ path-name ]" }, + { "MLSD", MLSD, OSTR, 1, "[ directory-name ]" }, /* obsolete commands */ - { "MAIL", MAIL, OSTR, 0, 0, "(mail to user)" }, - { "MLFL", MLFL, OSTR, 0, 0, "(mail file)" }, - { "MRCP", MRCP, STR1, 0, 0, "(mail recipient)" }, - { "MRSQ", MRSQ, OSTR, 0, 0, "(mail recipient scheme question)" }, - { "MSAM", MSAM, OSTR, 0, 0, "(mail send to terminal and mailbox)" }, - { "MSND", MSND, OSTR, 0, 0, "(mail send to terminal)" }, - { "MSOM", MSOM, OSTR, 0, 0, "(mail send to terminal or mailbox)" }, - { "XCUP", CDUP, NOARGS, 1, 0, "(change to parent directory)" }, - { "XCWD", CWD, OSTR, 1, 0, "[ directory-name ]" }, - { "XMKD", MKD, STR1, 1, 0, " path-name" }, - { "XPWD", PWD, NOARGS, 1, 0, "(return current directory)" }, - { "XRMD", RMD, STR1, 1, 0, " path-name" }, + { "MAIL", MAIL, OSTR, 0, "(mail to user)" }, + { "MLFL", MLFL, OSTR, 0, "(mail file)" }, + { "MRCP", MRCP, STR1, 0, "(mail recipient)" }, + { "MRSQ", MRSQ, OSTR, 0, "(mail recipient scheme question)" }, + { "MSAM", MSAM, OSTR, 0, "(mail send to terminal and mailbox)" }, + { "MSND", MSND, OSTR, 0, "(mail send to terminal)" }, + { "MSOM", MSOM, OSTR, 0, "(mail send to terminal or mailbox)" }, + { "XCUP", CDUP, NOARGS, 1, "(change to parent directory)" }, + { "XCWD", CWD, OSTR, 1, "[ directory-name ]" }, + { "XMKD", MKD, STR1, 1, " path-name" }, + { "XPWD", PWD, NOARGS, 1, "(return current directory)" }, + { "XRMD", RMD, STR1, 1, " path-name" }, - { NULL, 0, 0, 0, 0, 0 } + { NULL, 0, 0, 0, 0 } }; struct tab sitetab[] = { - { "UMASK", UMASK, ARGS, 1, 0, "[ umask ]" }, - { "IDLE", IDLE, ARGS, 1, 0, "[ maximum-idle-time ]" }, - { "CHMOD", CHMOD, NSTR, 1, 0, " mode file-name" }, - { "HELP", HELP, OSTR, 1, 0, "[ ]" }, - { NULL, 0, 0, 0, 0, 0 } + { "CHMOD", CHMOD, NSTR, 1, " mode file-name" }, + { "HELP", HELP, OSTR, 1, "[ ]" }, + { "IDLE", IDLE, ARGS, 1, "[ maximum-idle-time ]" }, + { "RATEGET", RATEGET,OSTR, 1, "[ get-throttle-rate ]" }, + { "RATEPUT", RATEPUT,OSTR, 1, "[ put-throttle-rate ]" }, + { "UMASK", UMASK, ARGS, 1, "[ umask ]" }, + { NULL, 0, 0, 0, NULL } }; -static void help __P((struct tab *, char *)); -static struct tab *lookup __P((struct tab *, const char *)); -static void opts __P((const char *)); -static void sizecmd __P((char *)); -static void toolong __P((int)); -static int yylex __P((void)); +static int check_write(const char *, int); +static void help(struct tab *, const char *); +static void port_check(const char *, int); +static void toolong(int); +static int yylex(void); -static struct tab * -lookup(p, cmd) - struct tab *p; - const char *cmd; +extern int epsvall; + +/* + * Check if a filename is allowed to be modified (isupload == 0) or + * uploaded (isupload == 1), and if necessary, check the filename is `sane'. + */ +static int +check_write(const char *file, int isupload) +{ + if (file == NULL) + return (0); + if (! logged_in) { + reply(530, "Please login with USER and PASS."); + return (0); + } + /* checking modify */ + if (! isupload && ! CURCLASS_FLAGS_ISSET(modify)) { + reply(502, "No permission to use this command."); + return (0); + } + /* checking upload */ + if (isupload && ! CURCLASS_FLAGS_ISSET(upload)) { + reply(502, "No permission to use this command."); + return (0); + } + /* checking sanenames */ + if (CURCLASS_FLAGS_ISSET(sanenames)) { + const char *p; + + if (file[0] == '.') + goto insane_name; + for (p = file; *p; p++) { + if (isalnum(*p) || *p == '-' || *p == '+' || + *p == ',' || *p == '.' || *p == '_') + continue; + insane_name: + reply(553, "File name `%s' not allowed.", file); + return (0); + } + } + return (1); +} + +struct tab * +lookup(struct tab *p, const char *cmd) { for (; p->name != NULL; p++) @@ -1017,10 +1323,7 @@ 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; +getline(char *s, int n, FILE *iop) { int c; char *cs; @@ -1040,21 +1343,29 @@ getline(s, n, iop) tmpline[0] = '\0'; } while ((c = getc(iop)) != EOF) { + total_bytes++; + total_bytes_in++; c &= 0377; if (c == IAC) { if ((c = getc(iop)) != EOF) { + total_bytes++; + total_bytes_in++; c &= 0377; switch (c) { case WILL: case WONT: c = getc(iop); - printf("%c%c%c", IAC, DONT, 0377&c); + total_bytes++; + total_bytes_in++; + cprintf(stdout, "%c%c%c", IAC, DONT, 0377&c); (void) fflush(stdout); continue; case DO: case DONT: c = getc(iop); - printf("%c%c%c", IAC, WONT, 0377&c); + total_bytes++; + total_bytes_in++; + cprintf(stdout, "%c%c%c", IAC, WONT, 0377&c); (void) fflush(stdout); continue; case IAC: @@ -1072,9 +1383,11 @@ getline(s, n, iop) return (NULL); *cs++ = '\0'; if (debug) { - if (!guest && strncasecmp("pass ", s, 5) == 0) { + if ((curclass.type != CLASS_GUEST && + strncasecmp(s, "PASS ", 5) == 0) || + strncasecmp(s, "ACCT ", 5) == 0) { /* Don't syslog passwords */ - syslog(LOG_DEBUG, "command: %.5s ???", s); + syslog(LOG_DEBUG, "command: %.4s ???", s); } else { char *cp; int len; @@ -1093,8 +1406,7 @@ getline(s, n, iop) } static void -toolong(signo) - int signo; +toolong(int signo) { reply(421, @@ -1102,12 +1414,12 @@ toolong(signo) curclass.timeout); if (logging) syslog(LOG_INFO, "User %s timed out after %d seconds", - (pw ? pw -> pw_name : "unknown"), curclass.timeout); + (pw ? pw->pw_name : "unknown"), curclass.timeout); dologout(1); } static int -yylex() +yylex(void) { static int cpos, state; char *cp, *cp2; @@ -1126,11 +1438,13 @@ yylex() dologout(0); } (void) alarm(0); -#ifdef HASSETPROCTITLE - if (strncasecmp(cbuf, "PASS", 4) != 0) - setproctitle("%s: %s", proctitle, cbuf); -#endif /* HASSETPROCTITLE */ if ((cp = strchr(cbuf, '\r'))) { + *cp = '\0'; +#if HAVE_SETPROCTITLE + if (strncasecmp(cbuf, "PASS", 4) != 0 && + strncasecmp(cbuf, "ACCT", 4) != 0) + setproctitle("%s: %s", proctitle, cbuf); +#endif /* HAVE_SETPROCTITLE */ *cp++ = '\n'; *cp = '\0'; } @@ -1143,7 +1457,7 @@ yylex() p = lookup(cmdtab, cbuf); cbuf[cpos] = c; if (p != NULL) { - if (p->implemented == 0) { + if (! CMD_IMPLEMENTED(p)) { reply(502, "%s command not implemented.", p->name); hasyyerrored = 1; @@ -1168,7 +1482,7 @@ yylex() p = lookup(sitetab, cp); cbuf[cpos] = c; if (p != NULL) { - if (p->implemented == 0) { + if (!CMD_IMPLEMENTED(p)) { reply(502, "SITE %s command not implemented.", p->name); hasyyerrored = 1; @@ -1192,7 +1506,7 @@ yylex() dostr1: if (cbuf[cpos] == ' ') { cpos++; - state = state == OSTR ? STR2 : ++state; + state = state == OSTR ? STR2 : state+1; return (SP); } break; @@ -1250,6 +1564,12 @@ yylex() cbuf[cpos] = c; return (NUMBER); } + if (strncasecmp(&cbuf[cpos], "ALL", 3) == 0 + && !isalnum(cbuf[cpos + 3])) { + yylval.s = xstrdup("ALL"); + cpos += 3; + return ALL; + } switch (cbuf[cpos++]) { case '\n': @@ -1336,8 +1656,7 @@ yylex() /* ARGSUSED */ void -yyerror(s) - char *s; +yyerror(char *s) { char *cp; @@ -1350,9 +1669,7 @@ yyerror(s) } static void -help(ctab, s) - struct tab *ctab; - char *s; +help(struct tab *ctab, const char *s) { struct tab *c; int width, NCMDS; @@ -1375,35 +1692,35 @@ help(ctab, s) int i, j, w; int columns, lines; - lreply(214, "The following %scommands are recognized.", type); - printf( - " (`-' = not implemented, `+' = supports options)\r\n"); + reply(-214, "%s", ""); + reply(0, "The following %scommands are recognized.", type); + reply(0, "(`-' = not implemented, `+' = supports options)"); columns = 76 / width; if (columns == 0) columns = 1; lines = (NCMDS + columns - 1) / columns; for (i = 0; i < lines; i++) { - printf(" "); + cprintf(stdout, " "); for (j = 0; j < columns; j++) { c = ctab + j * lines + i; - fputs(c->name, stdout); + cprintf(stdout, "%s", c->name); w = strlen(c->name); - if (! c->implemented) { - putchar('-'); + if (! CMD_IMPLEMENTED(c)) { + CPUTC('-', stdout); w++; } - if (c->hasopts) { - putchar('+'); + if (CMD_HAS_OPTIONS(c)) { + CPUTC('+', stdout); w++; } if (c + lines >= &ctab[NCMDS]) break; while (w < width) { - putchar(' '); + CPUTC(' ', stdout); w++; } } - printf("\r\n"); + cprintf(stdout, "\r\n"); } (void) fflush(stdout); reply(214, "Direct comments to ftp-bugs@%s.", hostname); @@ -1414,88 +1731,65 @@ help(ctab, s) reply(502, "Unknown command %s.", s); return; } - if (c->implemented) + if (CMD_IMPLEMENTED(c)) reply(214, "Syntax: %s%s %s", type, c->name, c->help); else reply(214, "%s%-*s\t%s; not implemented.", type, width, c->name, c->help); } +/* + * Check that the structures used for a PORT, LPRT or EPRT command are + * valid (data_dest, his_addr), and if necessary, detect ftp bounce attacks. + * If family != -1 check that his_addr.su_family == family. + */ static void -sizecmd(filename) - char *filename; -{ - switch (type) { - case TYPE_L: - case TYPE_I: { - struct stat stbuf; - if (stat(filename, &stbuf) < 0 || !S_ISREG(stbuf.st_mode)) - reply(550, "%s: not a plain file.", filename); - else - reply(213, "%qu", stbuf.st_size); - break; } - case TYPE_A: { - FILE *fin; - int c; - off_t count; - struct stat stbuf; - fin = fopen(filename, "r"); - if (fin == NULL) { - perror_reply(550, filename); - return; - } - if (fstat(fileno(fin), &stbuf) < 0 || !S_ISREG(stbuf.st_mode)) { - reply(550, "%s: not a plain file.", filename); - (void) fclose(fin); - return; - } - - count = 0; - while((c=getc(fin)) != EOF) { - if (c == '\n') /* will get expanded to \r\n */ - count++; - count++; - } - (void) fclose(fin); - - reply(213, "%qd", count); - break; } - default: - reply(504, "SIZE not implemented for Type %c.", "?AEIL"[type]); - } -} - -static void -opts(command) - const char *command; +port_check(const char *cmd, int family) { - struct tab *c; - char *ep; - if ((ep = strchr(command, ' ')) != NULL) - *ep++ = '\0'; - c = lookup(cmdtab, command); - if (c == NULL) { - reply(502, "Unknown command %s.", command); + if (epsvall) { + reply(501, "%s disallowed after EPSV ALL", cmd); return; } - if (c->implemented == 0) { - reply(502, "%s command not implemented.", c->name); + + if (family != -1 && his_addr.su_family != family) { + port_check_fail: + reply(500, "Illegal %s command rejected", cmd); return; } - if (c->hasopts == 0) { - reply(501, "%s command does not support persistent options.", - c->name); - return; + + if (data_dest.su_family != his_addr.su_family) + goto port_check_fail; + + /* be paranoid, if told so */ + if (CURCLASS_FLAGS_ISSET(checkportcmd)) { + if ((ntohs(data_dest.su_port) < IPPORT_RESERVED) || + (data_dest.su_len != his_addr.su_len)) + goto port_check_fail; + switch (data_dest.su_family) { + case AF_INET: + if (memcmp(&data_dest.su_addr, &his_addr.su_addr, + data_dest.su_len) != 0) + goto port_check_fail; + break; +#ifdef INET6 + case AF_INET6: + if (memcmp(&data_dest.su_6addr, &his_addr.su_6addr, + sizeof(data_dest.su_6addr)) != 0) + goto port_check_fail; + if (data_dest.su_scope_id != his_addr.su_scope_id) + goto port_check_fail; + break; +#endif + default: + goto port_check_fail; + } } - if (ep != NULL && *ep != '\0') { - if (c->options != NULL) - free(c->options); - c->options = xstrdup(ep); + usedefault = 0; + if (pdata >= 0) { + (void) close(pdata); + pdata = -1; } - if (c->options != NULL) - reply(200, "Options for %s are '%s'.", c->name, c->options); - else - reply(200, "No options defined for %s.", c->name); + reply(200, "%s command successful.", cmd); }