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.7 retrieving revision 1.48.2.2 diff -u -p -r1.7 -r1.48.2.2 --- src/libexec/ftpd/ftpcmd.y 1996/04/08 19:03:11 1.7 +++ src/libexec/ftpd/ftpcmd.y 2001/03/29 14:14:17 1.48.2.2 @@ -1,4 +1,40 @@ -/* $NetBSD: ftpcmd.y,v 1.7 1996/04/08 19:03:11 jtc 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 @@ -41,12 +77,13 @@ */ %{ +#include #ifndef lint #if 0 static char sccsid[] = "@(#)ftpcmd.y 8.3 (Berkeley) 4/6/94"; #else -static char rcsid[] = "$NetBSD: ftpcmd.y,v 1.7 1996/04/08 19:03:11 jtc Exp $"; +__RCSID("$NetBSD: ftpcmd.y,v 1.48.2.2 2001/03/29 14:14:17 lukem Exp $"); #endif #endif /* not lint */ @@ -56,6 +93,7 @@ static char rcsid[] = "$NetBSD: ftpcmd.y #include #include +#include #include #include @@ -68,32 +106,21 @@ static char rcsid[] = "$NetBSD: ftpcmd.y #include #include #include +#include #include +#include -#include "extern.h" - -extern struct sockaddr_in data_dest; -extern int logged_in; -extern struct passwd *pw; -extern int guest; -extern int logging; -extern int type; -extern int form; -extern int debug; -extern int timeout; -extern int maxtimeout; -extern int pdata; -extern char hostname[], remotehost[]; -extern char proctitle[]; -extern int usedefault; -extern int transflag; -extern char tmpline[]; +#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; @@ -110,24 +137,37 @@ char *fromname; SP CRLF COMMA - USER PASS ACCT REIN QUIT PORT - PASV TYPE STRU MODE RETR STOR - APPE MLFL MAIL MSND MSOM MSAM - MRSQ MRCP ALLO REST RNFR RNTO - ABOR DELE CWD LIST NLST SITE - STAT HELP NOOP MKD RMD PWD - CDUP STOU SMNT SYST SIZE MDTM + USER PASS ACCT CWD CDUP SMNT + QUIT REIN PORT PASV TYPE STRU + MODE RETR STOR STOU APPE ALLO + REST RNFR RNTO ABOR DELE RMD + MKD PWD LIST NLST SITE SYST + STAT HELP NOOP + + AUTH ADAT PROT PBSZ CCC MIC + CONF ENC + + FEAT OPTS + + SIZE MDTM MLST MLSD + + LPRT LPSV EPRT EPSV - UMASK IDLE CHMOD + MAIL MLFL MRCP MRSQ MSAM MSND + MSOM + + CHMOD IDLE RATEGET RATEPUT UMASK LEXERR %token STRING +%token ALL %token NUMBER %type check_login octal_number byte_size -%type struct_code mode_code type_code form_code +%type struct_code mode_code type_code form_code decimal_integer %type pathstring pathname password username +%type mechanism_name base64data prot_code %start cmd_list @@ -135,40 +175,158 @@ char *fromname; cmd_list : /* empty */ + | cmd_list cmd { - fromname = (char *) 0; + fromname = NULL; restart_point = (off_t) 0; } + | cmd_list rcmd + ; cmd + /* RFC 959 */ : USER SP username CRLF { user($3); free($3); } + | PASS SP password CRLF { pass($3); + memset($3, 0, strlen($3)); free($3); } - | PORT SP host_port CRLF + + | CWD check_login CRLF + { + if ($2) + cwd(homedir); + } + + | CWD check_login SP pathname CRLF + { + if ($2 && $4 != NULL) + cwd($4); + if ($4 != NULL) + free($4); + } + + | CDUP check_login CRLF + { + if ($2) + cwd(".."); + } + + | QUIT CRLF { - usedefault = 0; - if (pdata >= 0) { - (void) close(pdata); - pdata = -1; + 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)); } - reply(200, "PORT command successful."); + + dologout(0); + } + + | PORT check_login SP host_port CRLF + { + if ($2) + port_check("PORT", AF_INET); } - | PASV CRLF + + | LPRT check_login SP host_long_port4 CRLF { - passive(); + if ($2) + port_check("LPRT", AF_INET); } - | TYPE SP type_code CRLF + + | 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) { + if (extended_port($4) == 0) + port_check("EPRT", -1); + } + free($4); + } + + | PASV check_login CRLF + { + 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); + } + } + + | 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: @@ -201,184 +359,288 @@ 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."); + } } } - | ALLO SP NUMBER CRLF - { - reply(202, "ALLO command ignored."); - } - | ALLO SP NUMBER SP R SP NUMBER CRLF - { - reply(202, "ALLO command ignored."); - } + | RETR check_login SP pathname CRLF { if ($2 && $4 != NULL) - retrieve((char *) 0, $4); + retrieve(NULL, $4); if ($4 != NULL) 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); } - | APPE check_login SP pathname CRLF + + | STOU SP pathname CRLF { - if ($2 && $4 != NULL) - store($4, "a", 0); - if ($4 != NULL) - free($4); + if (check_write($3, 1)) + store($3, "w", 1); + if ($3 != NULL) + free($3); } - | NLST check_login CRLF + + | APPE SP pathname CRLF { - if ($2) - send_file_list("."); + if (check_write($3, 1)) + store($3, "a", 0); + if ($3 != NULL) + free($3); } - | NLST check_login SP STRING CRLF + + | ALLO check_login SP NUMBER CRLF { - if ($2 && $4 != NULL) - send_file_list($4); - if ($4 != NULL) - free($4); + if ($2) + reply(202, "ALLO command ignored."); } - | LIST check_login CRLF + + | ALLO check_login SP NUMBER SP R SP NUMBER CRLF { if ($2) - retrieve("/bin/ls -lgA", ""); + reply(202, "ALLO command ignored."); } - | LIST check_login SP pathname CRLF + + | RNTO SP pathname CRLF { - if ($2 && $4 != NULL) - retrieve("/bin/ls -lgA %s", $4); - if ($4 != NULL) - free($4); + if (check_write($3, 0)) { + if (fromname) { + renamecmd(fromname, $3); + free(fromname); + fromname = NULL; + } else { + reply(503, "Bad sequence of commands."); + } + } + if ($3 != NULL) + free($3); } - | STAT check_login SP pathname CRLF + + | ABOR check_login CRLF { - if ($2 && $4 != NULL) - statfilecmd($4); - if ($4 != NULL) - free($4); + if ($2) + reply(225, "ABOR command successful."); } - | STAT CRLF + + | DELE SP pathname CRLF { - statcmd(); + if (check_write($3, 0)) + delete($3); + if ($3 != NULL) + free($3); } - | DELE check_login SP pathname CRLF + + | RMD SP pathname CRLF { - if ($2 && $4 != NULL) - delete($4); - if ($4 != NULL) - free($4); + if (check_write($3, 0)) + removedir($3); + if ($3 != NULL) + free($3); } - | RNTO SP pathname CRLF + + | MKD SP pathname CRLF { - if (fromname) { - renamecmd(fromname, $3); - free(fromname); - fromname = (char *) 0; - } else { - reply(503, "Bad sequence of commands."); - } - free($3); + if (check_write($3, 0)) + makedir($3); + if ($3 != NULL) + free($3); } - | ABOR CRLF + + | PWD check_login CRLF { - reply(225, "ABOR command successful."); + if ($2) + pwd(); } - | CWD check_login CRLF + + | LIST check_login CRLF { + char *argv[] = { INTERNAL_LS, "-lgA", NULL }; + if ($2) - cwd(pw->pw_dir); + retrieve(argv, ""); } - | CWD check_login SP pathname CRLF + + | LIST check_login SP pathname CRLF { - if ($2 && $4 != NULL) - cwd($4); + char *argv[] = { INTERNAL_LS, "-lgA", NULL, NULL }; + + if ($2 && $4 != NULL) { + argv[2] = $4; + retrieve(argv, $4); + } if ($4 != NULL) free($4); } - | HELP CRLF + + | NLST check_login CRLF { - help(cmdtab, (char *) 0); + if ($2) + send_file_list("."); } - | HELP SP STRING CRLF + + | NLST check_login SP pathname CRLF { - char *cp = $3; + if ($2) + send_file_list($4); + free($4); + } - if (strncasecmp(cp, "SITE", 4) == 0) { - cp = $3 + 4; - if (*cp == ' ') - cp++; - if (*cp) - help(sitetab, cp); + | SITE SP HELP CRLF + { + 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 - help(sitetab, (char *) 0); - } else - help(cmdtab, $3); + reply(200, "CHMOD command successful."); + } + if ($7 != NULL) + free($7); } - | NOOP CRLF + + | SITE SP HELP SP STRING CRLF { - reply(200, "NOOP command successful."); + help(sitetab, $5); + free($5); } - | MKD check_login SP pathname CRLF + + | SITE SP IDLE check_login CRLF { - if ($2 && $4 != NULL) - makedir($4); - if ($4 != NULL) - free($4); + if ($4) { + reply(200, + "Current IDLE time limit is %d seconds; max %d", + curclass.timeout, curclass.maxtimeout); + } } - | RMD check_login SP pathname CRLF + + | SITE SP IDLE check_login SP NUMBER CRLF { - if ($2 && $4 != NULL) - removedir($4); - if ($4 != NULL) - free($4); + if ($4) { + 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); + } + } } - | PWD check_login CRLF + + | SITE SP RATEGET check_login CRLF { - if ($2) - pwd(); + if ($4) { + reply(200, + "Current RATEGET is " LLF " bytes/sec", + (LLT)curclass.rateget); + } } - | CDUP check_login CRLF + + | SITE SP RATEGET check_login SP STRING CRLF { - if ($2) - cwd(".."); + char *p = $6; + LLT rate; + + if ($4) { + 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, + "RATEGET set to " LLF " bytes/sec", + (LLT)curclass.rateget); + } + } + free($6); } - | SITE SP HELP CRLF + + | SITE SP RATEPUT check_login CRLF { - help(sitetab, (char *) 0); + if ($4) { + reply(200, + "Current RATEPUT is " LLF " bytes/sec", + (LLT)curclass.rateput); + } } - | SITE SP HELP SP STRING CRLF + + | SITE SP RATEPUT check_login SP STRING CRLF { - help(sitetab, $5); + 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, + "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); + } + } + free($6); } + | SITE SP UMASK check_login CRLF { int oldmask; @@ -389,11 +651,12 @@ cmd reply(200, "Current UMASK is %03o", oldmask); } } + | SITE SP UMASK check_login SP octal_number CRLF { int oldmask; - if ($4) { + if ($4 && CURCLASS_FLAGS_ISSET(modify)) { if (($6 == -1) || ($6 > 0777)) { reply(501, "Bad UMASK value"); } else { @@ -404,65 +667,124 @@ cmd } } } - | SITE SP CHMOD check_login SP octal_number SP pathname CRLF + + | SYST CRLF { - if ($4 && ($8 != NULL)) { - if ($6 > 0777) - reply(501, - "CHMOD: Mode value must be between 0 and 0777"); - else if (chmod($8, $6) < 0) - perror_reply(550, $8); + 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 + { + if ($2 && $4 != NULL) + statfilecmd($4); + if ($4 != NULL) + free($4); + } + + | STAT CRLF + { + statcmd(); + } + + | HELP CRLF + { + help(cmdtab, NULL); + } + + | HELP SP STRING CRLF + { + char *cp = $3; + + if (strncasecmp(cp, "SITE", 4) == 0) { + cp = $3 + 4; + if (*cp == ' ') + cp++; + if (*cp) + help(sitetab, cp); else - reply(200, "CHMOD command successful."); - } - if ($8 != NULL) - free($8); + help(sitetab, NULL); + } else + help(cmdtab, $3); + free($3); } - | SITE SP IDLE CRLF + + | NOOP CRLF { - reply(200, - "Current IDLE time limit is %d seconds; max %d", - timeout, maxtimeout); + reply(200, "NOOP command successful."); } - | SITE SP IDLE SP NUMBER CRLF + + /* RFC 2228 */ + | AUTH SP mechanism_name CRLF { - if ($5 < 30 || $5 > maxtimeout) { - reply(501, - "Maximum IDLE time must be between 30 and %d seconds", - maxtimeout); - } else { - timeout = $5; - (void) alarm((unsigned) timeout); - reply(200, - "Maximum IDLE time set to %d seconds", - timeout); - } + reply(502, "RFC 2228 authentication not implemented."); + free($3); } - | STOU check_login SP pathname CRLF + + | ADAT SP base64data CRLF { - if ($2 && $4 != NULL) - store($4, "w", 1); - if ($4 != NULL) - free($4); + reply(503, + "Please set authentication state with AUTH."); + free($3); } - | SYST CRLF + + | PROT SP prot_code CRLF + { + reply(503, + "Please set protection buffer size with PBSZ."); + free($3); + } + + | PBSZ SP decimal_integer CRLF + { + reply(503, + "Please set authentication state with AUTH."); + } + + | CCC CRLF + { + reply(533, "No protection enabled."); + } + + | 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 + { + + feat(); + } + + | OPTS SP STRING CRLF { -#ifdef unix -#ifdef BSD - reply(215, "UNIX Type: L%d Version: BSD-%d", - NBBY, BSD); -#else /* BSD */ - reply(215, "UNIX Type: L%d", NBBY); -#endif /* BSD */ -#else /* unix */ - reply(215, "UNKNOWN Type: L%d", NBBY); -#endif /* unix */ + + opts($3); + free($3); } + + /* extensions from draft-ietf-ftpext-mlst-11 */ + /* - * SIZE is not in RFC959, 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). */ @@ -475,9 +797,6 @@ cmd } /* - * MDTM is not in RFC959, 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, @@ -488,16 +807,16 @@ 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 { struct tm *t; + t = gmtime(&stbuf.st_mtime); reply(213, "%04d%02d%02d%02d%02d%02d", - 1900 + t->tm_year, + TM_YEAR_BASE + t->tm_year, t->tm_mon+1, t->tm_mday, t->tm_hour, t->tm_min, t->tm_sec); } @@ -505,35 +824,58 @@ cmd if ($4 != NULL) free($4); } - | QUIT CRLF + + | MLST check_login SP pathname CRLF { - reply(221, "Goodbye."); - dologout(0); + 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; } ; + rcmd - : RNFR check_login SP pathname CRLF + : REST check_login SP byte_size CRLF { - char *renamefrom(); - - restart_point = (off_t) 0; - if ($2 && $4) { - fromname = renamefrom($4); - if (fromname == (char *) 0 && $4) { - free($4); - } + 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); } } - | REST SP byte_size CRLF + + | RNFR SP pathname CRLF { - fromname = (char *) 0; - restart_point = $3; /* XXX $3 is only "int" */ - reply(350, "Restarting at %qd. %s", restart_point, - "Send STORE or RETRIEVE to initiate transfer."); + restart_point = (off_t) 0; + if (check_write($3, 0)) + fromname = renamefrom($3); + if ($3 != NULL) + free($3); } ; @@ -546,6 +888,7 @@ password { $$ = (char *)calloc(1, sizeof(char)); } + | STRING ; @@ -559,24 +902,82 @@ 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 { $$ = FORM_N; } + | T { $$ = FORM_T; } + | C { $$ = FORM_C; @@ -589,35 +990,42 @@ type_code cmd_type = TYPE_A; cmd_form = FORM_N; } + | A SP form_code { cmd_type = TYPE_A; cmd_form = $3; } + | E { cmd_type = TYPE_E; cmd_form = FORM_N; } + | E SP form_code { cmd_type = TYPE_E; cmd_form = $3; } + | I { cmd_type = TYPE_I; } + | L { cmd_type = TYPE_L; cmd_bytesz = NBBY; } + | L SP byte_size { cmd_type = TYPE_L; cmd_bytesz = $3; } + /* this is for a bug in the BBN ftp */ | L byte_size { @@ -631,10 +1039,12 @@ struct_code { $$ = STRU_F; } + | R { $$ = STRU_R; } + | P { $$ = STRU_P; @@ -646,10 +1056,12 @@ mode_code { $$ = MODE_S; } + | B { $$ = MODE_B; } + | C { $$ = MODE_C; @@ -662,22 +1074,25 @@ pathname /* * 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. + * This is a valid reply in some cases but not in + * others. */ if (logged_in && $1 && *$1 == '~') { 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)); - if (glob($1, flags, NULL, &gl) || - gl.gl_pathc == 0) { - reply(550, "not found"); - $$ = NULL; - } else { - $$ = strdup(gl.gl_pathv[0]); + if ($1[1] == '\0') + $$ = xstrdup(homedir); + else { + memset(&gl, 0, sizeof(gl)); + if (glob($1, flags, NULL, &gl) || + gl.gl_pathc == 0) { + reply(550, "not found"); + $$ = NULL; + } else + $$ = xstrdup(gl.gl_pathv[0]); + globfree(&gl); } - globfree(&gl); free($1); } else $$ = $1; @@ -714,6 +1129,21 @@ octal_number } ; +mechanism_name + : STRING + ; + +base64data + : STRING + ; + +prot_code + : STRING + ; + +decimal_integer + : NUMBER + ; check_login : /* empty */ @@ -723,14 +1153,13 @@ check_login else { reply(530, "Please login with USER and PASS."); $$ = 0; + hasyyerrored = 1; } } ; %% -extern jmp_buf errcatch; - #define CMD 0 /* beginning of command */ #define ARGS 1 /* expect miscellaneous arguments */ #define STR1 2 /* expect SP followed by STRING */ @@ -740,90 +1169,150 @@ extern jmp_buf errcatch; #define ZSTR2 6 /* optional STRING after SP */ #define SITECMD 7 /* SITE command */ #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 */ - char *help; -}; - -struct tab cmdtab[] = { /* In order defined in RFC 765 */ - { "USER", USER, STR1, 1, " username" }, - { "PASS", PASS, ZSTR1, 1, " password" }, - { "ACCT", ACCT, STR1, 0, "(specify account)" }, - { "SMNT", SMNT, ARGS, 0, "(structure mount)" }, - { "REIN", REIN, ARGS, 0, "(reinitialize server state)" }, - { "QUIT", QUIT, ARGS, 1, "(terminate service)", }, - { "PORT", PORT, ARGS, 1, " b0, b1, b2, b3, b4" }, - { "PASV", PASV, ARGS, 1, "(set server in passive mode)" }, - { "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" }, - { "APPE", APPE, STR1, 1, " file-name" }, - { "MLFL", MLFL, OSTR, 0, "(mail file)" }, - { "MAIL", MAIL, OSTR, 0, "(mail to user)" }, - { "MSND", MSND, OSTR, 0, "(mail send to terminal)" }, - { "MSOM", MSOM, OSTR, 0, "(mail send to terminal or mailbox)" }, - { "MSAM", MSAM, OSTR, 0, "(mail send to terminal and mailbox)" }, - { "MRSQ", MRSQ, OSTR, 0, "(mail recipient scheme question)" }, - { "MRCP", MRCP, STR1, 0, "(mail recipient)" }, - { "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, ARGS, 1, "(abort operation)" }, - { "DELE", DELE, STR1, 1, " file-name" }, - { "CWD", CWD, OSTR, 1, "[ directory-name ]" }, - { "XCWD", CWD, OSTR, 1, "[ directory-name ]" }, - { "LIST", LIST, OSTR, 1, "[ path-name ]" }, - { "NLST", NLST, OSTR, 1, "[ path-name ]" }, +struct tab cmdtab[] = { + /* From RFC 959, in order defined (5.3.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, ARGS, 1, "(get type of operating system)" }, - { "STAT", STAT, OSTR, 1, "[ path-name ]" }, - { "HELP", HELP, OSTR, 1, "[ ]" }, - { "NOOP", NOOP, ARGS, 1, "" }, - { "MKD", MKD, STR1, 1, " path-name" }, - { "XMKD", MKD, STR1, 1, " path-name" }, - { "RMD", RMD, STR1, 1, " path-name" }, - { "XRMD", RMD, STR1, 1, " path-name" }, - { "PWD", PWD, ARGS, 1, "(return current directory)" }, - { "XPWD", PWD, ARGS, 1, "(return current directory)" }, - { "CDUP", CDUP, ARGS, 1, "(change to parent directory)" }, - { "XCUP", CDUP, ARGS, 1, "(change to parent directory)" }, - { "STOU", STOU, STR1, 1, " file-name" }, - { "SIZE", SIZE, OSTR, 1, " path-name" }, - { "MDTM", MDTM, OSTR, 1, " path-name" }, - { NULL, 0, 0, 0, 0 } + { "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, " 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, "(display extended features)" }, + { "OPTS", OPTS, STR1, 1, " command [ options ]" }, + + /* 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, "(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 } }; struct tab sitetab[] = { - { "UMASK", UMASK, ARGS, 1, "[ umask ]" }, - { "IDLE", IDLE, ARGS, 1, "[ maximum-idle-time ]" }, - { "CHMOD", CHMOD, NSTR, 1, " mode file-name" }, - { "HELP", HELP, OSTR, 1, "[ ]" }, - { NULL, 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 char *copy __P((char *)); -static void help __P((struct tab *, char *)); -static struct tab * - lookup __P((struct tab *, 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; - 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++) - if (strcmp(cmd, p->name) == 0) + if (strcasecmp(cmd, p->name) == 0) return (p); return (0); } @@ -834,13 +1323,10 @@ 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; - register char *cs; + char *cs; cs = s; /* tmpline may contain saved command from urgent mode interruption */ @@ -857,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: @@ -889,12 +1383,14 @@ 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 { - register char *cp; - register int len; + char *cp; + int len; /* Don't syslog trailing CR-LF */ len = strlen(s); @@ -910,20 +1406,20 @@ getline(s, n, iop) } static void -toolong(signo) - int signo; +toolong(int signo) { reply(421, - "Timeout (%d seconds): closing control connection.", timeout); + "Timeout (%d seconds): closing control connection.", + curclass.timeout); if (logging) syslog(LOG_INFO, "User %s timed out after %d seconds", - (pw ? pw -> pw_name : "unknown"), timeout); + (pw ? pw->pw_name : "unknown"), curclass.timeout); dologout(1); } static int -yylex() +yylex(void) { static int cpos, state; char *cp, *cp2; @@ -931,243 +1427,249 @@ yylex() int n; char c; - for (;;) { - switch (state) { + switch (state) { - case CMD: - (void) signal(SIGALRM, toolong); - (void) alarm((unsigned) timeout); - if (getline(cbuf, sizeof(cbuf)-1, stdin) == NULL) { - reply(221, "You could at least say goodbye."); - dologout(0); - } - (void) alarm(0); -#ifdef HASSETPROCTITLE - if (strncasecmp(cbuf, "PASS", 4) != NULL) + case CMD: + hasyyerrored = 0; + (void) signal(SIGALRM, toolong); + (void) alarm(curclass.timeout); + if (getline(cbuf, sizeof(cbuf)-1, stdin) == NULL) { + reply(221, "You could at least say goodbye."); + dologout(0); + } + (void) alarm(0); + 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 /* HASSETPROCTITLE */ - if ((cp = strchr(cbuf, '\r'))) { - *cp++ = '\n'; - *cp = '\0'; - } - if ((cp = strpbrk(cbuf, " \n"))) - cpos = cp - cbuf; - if (cpos == 0) - cpos = 4; +#endif /* HAVE_SETPROCTITLE */ + *cp++ = '\n'; + *cp = '\0'; + } + if ((cp = strpbrk(cbuf, " \n"))) + cpos = cp - cbuf; + if (cpos == 0) + cpos = 4; + c = cbuf[cpos]; + cbuf[cpos] = '\0'; + p = lookup(cmdtab, cbuf); + cbuf[cpos] = c; + if (p != NULL) { + if (! CMD_IMPLEMENTED(p)) { + reply(502, "%s command not implemented.", + p->name); + hasyyerrored = 1; + break; + } + state = p->state; + yylval.s = p->name; + return (p->token); + } + break; + + case SITECMD: + if (cbuf[cpos] == ' ') { + cpos++; + return (SP); + } + cp = &cbuf[cpos]; + if ((cp2 = strpbrk(cp, " \n"))) + cpos = cp2 - cbuf; + c = cbuf[cpos]; + cbuf[cpos] = '\0'; + p = lookup(sitetab, cp); + cbuf[cpos] = c; + if (p != NULL) { + if (!CMD_IMPLEMENTED(p)) { + reply(502, "SITE %s command not implemented.", + p->name); + hasyyerrored = 1; + break; + } + state = p->state; + yylval.s = p->name; + return (p->token); + } + break; + + case OSTR: + if (cbuf[cpos] == '\n') { + state = CMD; + return (CRLF); + } + /* FALLTHROUGH */ + + case STR1: + case ZSTR1: + dostr1: + if (cbuf[cpos] == ' ') { + cpos++; + state = state == OSTR ? STR2 : state+1; + return (SP); + } + break; + + case ZSTR2: + if (cbuf[cpos] == '\n') { + state = CMD; + return (CRLF); + } + /* FALLTHROUGH */ + + case STR2: + cp = &cbuf[cpos]; + n = strlen(cp); + cpos += n - 1; + /* + * Make sure the string is nonempty and \n terminated. + */ + if (n > 1 && cbuf[cpos] == '\n') { + cbuf[cpos] = '\0'; + yylval.s = xstrdup(cp); + cbuf[cpos] = '\n'; + state = ARGS; + return (STRING); + } + break; + + case NSTR: + if (cbuf[cpos] == ' ') { + cpos++; + return (SP); + } + if (isdigit(cbuf[cpos])) { + cp = &cbuf[cpos]; + while (isdigit(cbuf[++cpos])) + ; c = cbuf[cpos]; cbuf[cpos] = '\0'; - upper(cbuf); - p = lookup(cmdtab, cbuf); + yylval.i = atoi(cp); cbuf[cpos] = c; - if (p != 0) { - if (p->implemented == 0) { - nack(p->name); - longjmp(errcatch,0); - /* NOTREACHED */ - } - state = p->state; - yylval.s = p->name; - return (p->token); - } - break; + state = STR1; + return (NUMBER); + } + state = STR1; + goto dostr1; - case SITECMD: - if (cbuf[cpos] == ' ') { - cpos++; - return (SP); - } + case ARGS: + if (isdigit(cbuf[cpos])) { cp = &cbuf[cpos]; - if ((cp2 = strpbrk(cp, " \n"))) - cpos = cp2 - cbuf; + while (isdigit(cbuf[++cpos])) + ; c = cbuf[cpos]; cbuf[cpos] = '\0'; - upper(cp); - p = lookup(sitetab, cp); + yylval.i = atoi(cp); cbuf[cpos] = c; - if (p != 0) { - if (p->implemented == 0) { - state = CMD; - nack(p->name); - longjmp(errcatch,0); - /* NOTREACHED */ - } - state = p->state; - yylval.s = p->name; - return (p->token); - } + 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': state = CMD; - break; + return (CRLF); - case OSTR: - if (cbuf[cpos] == '\n') { - state = CMD; - return (CRLF); - } - /* FALLTHROUGH */ - - case STR1: - case ZSTR1: - dostr1: - if (cbuf[cpos] == ' ') { - cpos++; - state = state == OSTR ? STR2 : ++state; - return (SP); - } - break; + case ' ': + return (SP); - case ZSTR2: - if (cbuf[cpos] == '\n') { - state = CMD; - return (CRLF); - } - /* FALLTHROUGH */ + case ',': + return (COMMA); - case STR2: - cp = &cbuf[cpos]; - n = strlen(cp); - cpos += n - 1; - /* - * Make sure the string is nonempty and \n terminated. - */ - if (n > 1 && cbuf[cpos] == '\n') { - cbuf[cpos] = '\0'; - yylval.s = copy(cp); - cbuf[cpos] = '\n'; - state = ARGS; - return (STRING); - } - break; + case 'A': + case 'a': + return (A); - case NSTR: - if (cbuf[cpos] == ' ') { - cpos++; - return (SP); - } - if (isdigit(cbuf[cpos])) { - cp = &cbuf[cpos]; - while (isdigit(cbuf[++cpos])) - ; - c = cbuf[cpos]; - cbuf[cpos] = '\0'; - yylval.i = atoi(cp); - cbuf[cpos] = c; - state = STR1; - return (NUMBER); - } - state = STR1; - goto dostr1; + case 'B': + case 'b': + return (B); - case ARGS: - if (isdigit(cbuf[cpos])) { - cp = &cbuf[cpos]; - while (isdigit(cbuf[++cpos])) - ; - c = cbuf[cpos]; - cbuf[cpos] = '\0'; - yylval.i = atoi(cp); - cbuf[cpos] = c; - return (NUMBER); - } - switch (cbuf[cpos++]) { - - case '\n': - state = CMD; - return (CRLF); - - case ' ': - return (SP); - - case ',': - return (COMMA); - - case 'A': - case 'a': - return (A); - - case 'B': - case 'b': - return (B); - - case 'C': - case 'c': - return (C); - - case 'E': - case 'e': - return (E); - - case 'F': - case 'f': - return (F); - - case 'I': - case 'i': - return (I); - - case 'L': - case 'l': - return (L); - - case 'N': - case 'n': - return (N); - - case 'P': - case 'p': - return (P); - - case 'R': - case 'r': - return (R); - - case 'S': - case 's': - return (S); - - case 'T': - case 't': - return (T); + case 'C': + case 'c': + return (C); - } - break; + case 'E': + case 'e': + return (E); + + case 'F': + case 'f': + return (F); + + case 'I': + case 'i': + return (I); + + case 'L': + case 'l': + return (L); + + case 'N': + case 'n': + return (N); + + case 'P': + case 'p': + return (P); + + case 'R': + case 'r': + return (R); + + case 'S': + case 's': + return (S); + + case 'T': + case 't': + return (T); - default: - fatal("Unknown state in scanner."); } - yyerror((char *) 0); - state = CMD; - longjmp(errcatch,0); - } -} + break; -void -upper(s) - char *s; -{ - while (*s != '\0') { - if (islower(*s)) - *s = toupper(*s); - s++; + case NOARGS: + if (cbuf[cpos] == '\n') { + state = CMD; + return (CRLF); + } + c = cbuf[cpos]; + cbuf[cpos] = '\0'; + reply(501, "'%s' command does not take any arguments.", cbuf); + hasyyerrored = 1; + cbuf[cpos] = c; + break; + + default: + fatal("Unknown state in scanner."); } + yyerror(NULL); + state = CMD; + longjmp(errcatch, 0); + /* NOTREACHED */ } -static char * -copy(s) - char *s; +/* ARGSUSED */ +void +yyerror(char *s) { - char *p; + char *cp; - p = malloc((unsigned) strlen(s) + 1); - if (p == NULL) - fatal("Ran out of memory."); - (void) strcpy(p, s); - return (p); + if (hasyyerrored) + return; + if ((cp = strchr(cbuf,'\n')) != NULL) + *cp = '\0'; + reply(500, "'%s': command not understood.", cbuf); + hasyyerrored = 1; } static void -help(ctab, s) - struct tab *ctab; - char *s; +help(struct tab *ctab, const char *s) { struct tab *c; int width, NCMDS; @@ -1190,85 +1692,104 @@ help(ctab, s) int i, j, w; int columns, lines; - lreply(214, "The following %scommands are recognized %s.", - type, "(* =>'s unimplemented)"); + 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; - printf("%s%c", c->name, - c->implemented ? ' ' : '*'); + cprintf(stdout, "%s", c->name); + w = strlen(c->name); + if (! CMD_IMPLEMENTED(c)) { + CPUTC('-', stdout); + w++; + } + if (CMD_HAS_OPTIONS(c)) { + CPUTC('+', stdout); + w++; + } if (c + lines >= &ctab[NCMDS]) break; - w = strlen(c->name) + 1; 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); return; } - upper(s); c = lookup(ctab, s); if (c == (struct tab *)0) { 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; unimplemented.", type, width, + 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; +port_check(const char *cmd, int family) { - 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++; + + if (epsvall) { + reply(501, "%s disallowed after EPSV ALL", cmd); + return; + } + + if (family != -1 && his_addr.su_family != family) { + port_check_fail: + reply(500, "Illegal %s command rejected", cmd); + 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; } - (void) fclose(fin); + } - reply(213, "%qd", count); - break; } - default: - reply(504, "SIZE not implemented for Type %c.", "?AEIL"[type]); + usedefault = 0; + if (pdata >= 0) { + (void) close(pdata); + pdata = -1; } + reply(200, "%s command successful.", cmd); }