diff -r 000000000000 -r c6b0df440bee genericunixprotocols/ftpsrv/src/ftpcmd.y --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/genericunixprotocols/ftpsrv/src/ftpcmd.y Tue Mar 02 10:33:16 2010 +0530 @@ -0,0 +1,1374 @@ +// +// Copyright (c) 2005-2009 Nokia Corporation and/or its subsidiary(-ies). +// All rights reserved. +// This component and the accompanying materials are made available +// under the terms of the License "Eclipse Public License v1.0" +// which accompanies this distribution, and is available +// at the URL "http://www.eclipse.org/legal/epl-v10.html". +// +// Initial Contributors: +// Nokia Corporation - initial contribution. +// +// Contributors: +// +// Description: +// + +/* + * Copyright (c) 1985, 1988, 1993, 1994 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the University of + * California, Berkeley and its contributors. + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * @(#)ftpcmd.y 8.3 (Berkeley) 4/6/94 + * NetBSD: ftpcmd.y,v 1.7 1996/04/08 19:03:11 jtc Exp + * OpenBSD: ftpcmd.y,v 1.16 1998/05/22 06:46:09 deraadt Exp + */ + +/* + * Grammar for FTP commands. + * See RFC 959. + */ + +%{ + +char ftpcmd_rcsid[] = + "$Id: ftpcmd.y,v 1.11 1999/10/09 02:32:12 dholland Exp $"; + +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifndef __linux__ +#include +#else +#define TM_YEAR_BASE 1900 +#endif + +#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[]; +extern int portcheck; +extern struct sockaddr_in his_addr; + +off_t restart_point; + +static int cmd_type; +static int cmd_form; +static int cmd_bytesz; +char cbuf[512]; +char *fromname; + +struct tab; +static int yylex __P((void)); +//static void sizecmd __P((char *)); +static void help __P((struct tab *, char *)); + +extern struct tab cmdtab[]; +extern struct tab sitetab[]; + +%} + +%union { + int i; + char *s; +} + +%token + A B C E F I + L N P R S T + + 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 + + UMASK IDLE CHMOD + + LEXERR + +%token STRING +%token NUMBER + +%type check_login octal_number byte_size +%type struct_code mode_code type_code form_code +%type pathstring pathname password username +%type host_port + +%start cmd_list + +%% + +cmd_list + : /* empty */ + | cmd_list cmd + { + fromname = (char *) 0; + restart_point = (off_t) 0; + } + | cmd_list rcmd + ; + +cmd + : USER SP username CRLF + { + user($3); + free($3); + } + | PASS SP password CRLF + { + pass($3); + memset($3, 0, strlen($3)); + free($3); + } + | PORT check_login SP host_port CRLF + { + if ($2) { + if ($4) { + usedefault = 1; + reply(500, + "Illegal PORT rejected (range errors)."); + } else if (portcheck && + ntohs(data_dest.sin_port) < IPPORT_RESERVED) { + usedefault = 1; + reply(500, + "Illegal PORT rejected (reserved port)."); + } else if (portcheck && + memcmp(&data_dest.sin_addr, + &his_addr.sin_addr, + sizeof data_dest.sin_addr)) { + usedefault = 1; + reply(500, + "Illegal PORT rejected (address wrong)."); + } else { + usedefault = 0; + if (pdata >= 0) { + (void) close(pdata); + pdata = -1; + } + reply(200, "PORT command successful."); + } + } + } + | PASV check_login CRLF + { + if ($2) { + passive(); + } + } + | TYPE check_login SP type_code CRLF + { + if ($2) { + switch (cmd_type) { + + case TYPE_A: + if (cmd_form == FORM_N) { + reply(200, "Type set to A."); + type = cmd_type; + form = cmd_form; + } else + reply(504, "Form must be N."); + break; + + case TYPE_E: + reply(504, "Type E not implemented."); + break; + + case TYPE_I: + reply(200, "Type set to I."); + type = cmd_type; + break; + + case TYPE_L: + if (cmd_bytesz == 8) { + reply(200, + "Type set to L (byte size 8)."); + type = cmd_type; + } else + reply(504, "Byte size must be 8."); + + } + } + } + | STRU check_login SP struct_code CRLF + { + if ($2) { + switch ($4) { + + case STRU_F: + reply(200, "STRU F ok."); + break; + + default: + reply(504, "Unimplemented STRU type."); + } + } + } + | MODE check_login SP mode_code CRLF + { + if ($2) { + switch ($4) { + + case MODE_S: + reply(200, "MODE S ok."); + break; + + default: + reply(502, "Unimplemented MODE type."); + } + } + } + | ALLO check_login SP NUMBER CRLF + { + if ($2) { + reply(202, "ALLO command ignored."); + } + } + | ALLO check_login SP NUMBER SP R SP NUMBER CRLF + { + if ($2) { + reply(202, "ALLO command ignored."); + } + } + | RETR check_login SP pathname CRLF + { + if ($2 && $4 != NULL) + retrieve((char *) 0, $4); + if ($4 != NULL) + free($4); + } + | STOR check_login SP pathname CRLF + { + if ($2 && $4 != NULL) + store($4, "w", 0); + if ($4 != NULL) + free($4); + } + | APPE check_login SP pathname CRLF + { + if ($2 && $4 != NULL) + store($4, "a", 0); + if ($4 != NULL) + free($4); + } + | NLST check_login CRLF + { + if ($2) + send_file_list(".",1); + } + | NLST check_login SP STRING CRLF + { + if ($2 && $4 != NULL) + send_file_list($4,1); + if ($4 != NULL) + free($4); + } + | LIST check_login CRLF + { + if ($2) + send_file_list(".",0); + //retrieve("/bin/ls -lgA", ""); + } + | LIST check_login SP pathname CRLF + { + if ($2 && $4 != NULL) + send_file_list($4,0); + //retrieve("/bin/ls -lgA %s", $4); + if ($4 != NULL) + free($4); + } + | STAT check_login SP pathname CRLF + { + if ($2 && $4 != NULL) + statfilecmd($4); + if ($4 != NULL) + free($4); + } + | STAT check_login CRLF + { + if ($2) + statcmd(); + } + | DELE check_login SP pathname CRLF + { + if ($2 && $4 != NULL) + dele($4); + if ($4 != NULL) + free($4); + } + | RNTO check_login SP pathname CRLF + { + if ($2) { + if (fromname) { + renamecmd(fromname, $4); + free(fromname); + fromname = (char *) 0; + } else { + reply(503, + "Bad sequence of commands."); + } + } + free($4); + } + | ABOR check_login CRLF + { + if ($2) + reply(225, "ABOR command successful."); + } + | CWD check_login CRLF + { + if ($2) + cwd(pw->pw_dir); + } + | CWD check_login SP pathname CRLF + { + if ($2 && $4 != NULL) + cwd($4); + if ($4 != NULL) + free($4); + } + | HELP CRLF + { + help(cmdtab, (char *) 0); + } + | 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 + help(sitetab, (char *) 0); + } else + help(cmdtab, $3); + + if ($3 != NULL) + free ($3); + } + | NOOP CRLF + { + reply(200, "NOOP command successful."); + } + | MKD check_login SP pathname CRLF + { + if ($2 && $4 != NULL) + makedir($4); + if ($4 != NULL) + free($4); + } + | RMD check_login SP pathname CRLF + { + if ($2 && $4 != NULL) + removedir($4); + if ($4 != NULL) + free($4); + } + | PWD check_login CRLF + { + if ($2) + pwd(); + } + | CDUP check_login CRLF + { + if ($2) + cwd(".."); + } + | SITE SP HELP CRLF + { + help(sitetab, (char *) 0); + } + | SITE SP HELP SP STRING CRLF + { + help(sitetab, $5); + + if ($5 != NULL) + free ($5); + } + | SITE SP UMASK check_login CRLF + { + int oldmask; + + if ($4) { + oldmask = umask(0); + (void) umask(oldmask); + reply(200, "Current UMASK is %03o", oldmask); + } + } + | SITE SP UMASK check_login SP octal_number CRLF + { + int oldmask; + + if ($4) { + if (($6 == -1) || ($6 > 0777)) { + reply(501, "Bad UMASK value"); + } else { + oldmask = umask($6); + reply(200, + "UMASK set to %03o (was %03o)", + $6, oldmask); + } + } + } + | SITE SP CHMOD check_login SP octal_number SP pathname 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); + else + reply(200, "CHMOD command successful."); + } + if ($8 != NULL) + free($8); + } + | SITE SP check_login IDLE CRLF + { + if ($3) + reply(200, + "Current IDLE time limit is %d seconds; max %d", + timeout, maxtimeout); + } + | SITE SP check_login IDLE SP NUMBER CRLF + { + if ($3) { + if ($6 < 30 || $6 > maxtimeout) { + reply(501, + "Maximum IDLE time must be between 30 and %d seconds", + maxtimeout); + } else { + timeout = $6; + (void) alarm((unsigned) timeout); + reply(200, + "Maximum IDLE time set to %d seconds", + timeout); + } + } + } + | STOU check_login SP pathname CRLF + { + if ($2 && $4 != NULL) + store($4, "w", 1); + if ($4 != NULL) + free($4); + } + | SYST check_login CRLF + { + if ($2) +#ifdef __linux__ + reply(215, "UNIX Type: L%d (Linux)", CHAR_BIT); +#else +#ifdef unix +#ifdef BSD + reply(215, "UNIX Type: L%d Version: BSD-%d", + CHAR_BIT, BSD); +#else /* BSD */ + reply(215, "UNIX Type: L%d", CHAR_BIT); +#endif /* BSD */ +#else /* unix */ +#ifdef __SYMBIAN32__ + reply(215, "UNIX Type: L%d", CHAR_BIT); +#else + reply(215, "UNKNOWN Type: L%d", CHAR_BIT); +#endif /* __symbian32__ */ +#endif /* unix */ +#endif /* __linux__ */ + } + + /* + * 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). + */ + | SIZE check_login SP pathname CRLF + { + if ($2 && $4 != NULL) + sizecmd($4); + if ($4 != NULL) + free($4); + } + + /* + * 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, + * not necessarily 3 digits) + */ + | MDTM check_login SP pathname CRLF + { + if ($2 && $4 != NULL) { + struct stat stbuf; + if (stat($4, &stbuf) < 0) + reply(550, "%s: %s", + $4, strerror(errno)); + 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", + TM_YEAR_BASE + t->tm_year, + t->tm_mon+1, t->tm_mday, + t->tm_hour, t->tm_min, t->tm_sec); + } + } + if ($4 != NULL) + free($4); + } + | QUIT CRLF + { + reply(221, "Goodbye."); + dologout(0); + } + | error CRLF + { + yyerrok; + } + ; +rcmd + : RNFR check_login SP pathname CRLF + { + restart_point = (off_t) 0; + if ($2 && $4) { + fromname = renamefrom($4); + if (fromname == (char *) 0 && $4) { + free($4); + } + } else { + if ($4) + free ($4); + } + } + + | REST check_login SP byte_size CRLF + { + if ($2) { + fromname = (char *) 0; + restart_point = $4; /* XXX $4 is only "int" */ + reply(350, "Restarting at %qd. %s", + (quad_t) restart_point, + "Send STORE or RETRIEVE to initiate transfer."); + } + } + ; + +username + : STRING + ; + +password + : /* empty */ + { + $$ = (char *)calloc(1, sizeof(char)); + } + | STRING + ; + +byte_size + : NUMBER + ; + +host_port + : NUMBER COMMA NUMBER COMMA NUMBER COMMA NUMBER COMMA + NUMBER COMMA NUMBER + { + char *a, *p; + + if ($1 < 0 || $1 > 255 || $3 < 0 || $3 > 255 || + $5 < 0 || $5 > 255 || $7 < 0 || $7 > 255 || + $9 < 0 || $9 > 255 || $11 < 0 || $11 > 255) { + $$ = 1; + } else { +#ifndef __linux__ + data_dest.sin_len = sizeof(struct sockaddr_in); +#endif + data_dest.sin_family = AF_INET; + p = (char *)&data_dest.sin_port; + p[0] = $9; p[1] = $11; + a = (char *)&data_dest.sin_addr; + a[0] = $1; a[1] = $3; a[2] = $5; a[3] = $7; + $$ = 0; + } + } + ; + +form_code + : N + { + $$ = FORM_N; + } + | T + { + $$ = FORM_T; + } + | C + { + $$ = FORM_C; + } + ; + +type_code + : A + { + 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 = CHAR_BIT; + } + | L SP byte_size + { + cmd_type = TYPE_L; + cmd_bytesz = $3; + } + /* this is for a bug in the BBN ftp */ + | L byte_size + { + cmd_type = TYPE_L; + cmd_bytesz = $2; + } + ; + +struct_code + : F + { + $$ = STRU_F; + } + | R + { + $$ = STRU_R; + } + | P + { + $$ = STRU_P; + } + ; + +mode_code + : S + { + $$ = MODE_S; + } + | B + { + $$ = MODE_B; + } + | C + { + $$ = MODE_C; + } + ; + +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 && strchr($1, '~') != NULL) { + glob_t gl; +#ifdef __linux__ + /* see popen.c */ + int flags = GLOB_NOCHECK; +#else + int flags = + GLOB_BRACE|GLOB_NOCHECK|GLOB_QUOTE|GLOB_TILDE; +#endif + char *pptr = $1; + + /* + * glob() will only find a leading ~, but + * Netscape kindly puts a slash in front of + * it for publish URLs. There needs to be + * a flag for glob() that expands tildes + * anywhere in the string. + */ + if ((pptr[0] == '/') && (pptr[1] == '~')) + pptr++; + + memset(&gl, 0, sizeof(gl)); + if (glob(pptr, flags, NULL, &gl) || + gl.gl_pathc == 0) { + reply(550, "not found"); + $$ = NULL; + } else { + $$ = strdup(gl.gl_pathv[0]); + } + globfree(&gl); + free($1); + } else + $$ = $1; + } + ; + +pathstring + : STRING + ; + +octal_number + : NUMBER + { + int ret, dec, multby, digit; + + /* + * Convert a number that was read as decimal number + * to what it would be if it had been read as octal. + */ + dec = $1; + multby = 1; + ret = 0; + while (dec) { + digit = dec%10; + if (digit > 7) { + ret = -1; + break; + } + ret += digit * multby; + multby *= 8; + dec /= 10; + } + $$ = ret; + } + ; + + +check_login + : /* empty */ + { + if (logged_in) + $$ = 1; + else { + reply(530, "Please login with USER and PASS."); + $$ = 0; + } + } + ; + +%% + +extern jmp_buf errcatch; + +#define CMD 0 /* beginning of command */ +#define ARGS 1 /* expect miscellaneous arguments */ +#define STR1 2 /* expect SP followed by STRING */ +#define STR2 3 /* expect STRING */ +#define OSTR 4 /* optional SP then STRING */ +#define ZSTR1 5 /* SP then optional STRING */ +#define ZSTR2 6 /* optional STRING after SP */ +#define SITECMD 7 /* SITE command */ +#define NSTR 8 /* Number followed by a string */ + +struct tab { + const char *name; + short token; + short state; + short implemented; /* 1 if command is implemented */ + const 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 ]" }, + { "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 } +}; + +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 } +}; + +static void help __P((struct tab *, char *)); +static struct tab * + lookup __P((struct tab *, char *)); +static void sizecmd __P((char *)); +static int yylex __P((void)); + +static struct tab *lookup(struct tab *p, char *cmd) +{ + + for (; p->name != NULL; p++) + if (strcmp(cmd, p->name) == 0) + return (p); + return (0); +} + +#include + +/* + * getline - a hacked up version of fgets to ignore TELNET escape codes. + */ +char * ftpd_getline(char *s, int n, FILE *iop) +{ + int c; + register char *cs; + + cs = s; +/* tmpline may contain saved command from urgent mode interruption */ + for (c = 0; tmpline[c] != '\0' && --n > 0; ++c) { + *cs++ = tmpline[c]; + if (tmpline[c] == '\n') { + *cs++ = '\0'; + if (debug) + syslog(LOG_DEBUG, "command: %s", s); + tmpline[0] = '\0'; + return(s); + } + if (c == 0) + tmpline[0] = '\0'; + } + while ((c = getc(iop)) != EOF) { + c &= 0377; + if (c == IAC) { + if ((c = getc(iop)) != EOF) { + c &= 0377; + switch (c) { + case WILL: + case WONT: + c = getc(iop); + printf("%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); + (void) fflush(stdout); + continue; + case IAC: + break; + default: + continue; /* ignore command */ + } + } + } + *cs++ = c; + if (--n <= 0 || c == '\n') + break; + } + if (c == EOF && cs == s) + return (NULL); + *cs++ = '\0'; + if (debug) { + if (!guest && strncasecmp("pass ", s, 5) == 0) { + /* Don't syslog passwords */ + syslog(LOG_DEBUG, "command: %.5s ???", s); + } else { + register char *cp; + register int len; + + /* Don't syslog trailing CR-LF */ + len = strlen(s); + cp = s + len - 1; + while (cp >= s && (*cp == '\n' || *cp == '\r')) { + --cp; + --len; + } + syslog(LOG_DEBUG, "command: %.*s", len, s); + } + } + return (s); +} + +void toolong(int signo) +{ + (void)signo; + + reply(421, + "Timeout (%d seconds): closing control connection.", timeout); + if (logging) + syslog(LOG_INFO, "User %s timed out after %d seconds", + (pw ? pw -> pw_name : "unknown"), timeout); + dologout(1); +} + +static int yylex(void) +{ + static int cpos, state; + char *cp, *cp2; + struct tab *p; + int n, value; + char c; + + for (;;) { + switch (state) { + + case CMD: + (void) signal(SIGALRM, toolong); + (void) alarm((unsigned) timeout); + if (ftpd_getline(cbuf, sizeof(cbuf)-1, stdin)==NULL) { + reply(221, "You could at least say goodbye."); + dologout(0); + } + (void) alarm(0); + cp = strchr(cbuf, '\r'); + if (cp) { + *cp++ = '\n'; + *cp = '\0'; + } +#ifdef HASSETPROCTITLE + if (strncasecmp(cbuf, "PASS", 4) != 0) { + cp = strpbrk(cbuf, "\n"); + if (cp) { + c = *cp; + *cp = '\0'; + setproctitle("%s: %s", proctitle, cbuf); + *cp = c; + } + } +#endif /* HASSETPROCTITLE */ + cp = strpbrk(cbuf, " \n"); + if (cp) + cpos = cp - cbuf; + if (cpos == 0) + cpos = 4; + c = cbuf[cpos]; + cbuf[cpos] = '\0'; + upper(cbuf); + p = lookup(cmdtab, cbuf); + cbuf[cpos] = c; + if (p != 0) { + if (p->implemented == 0) { + nack(p->name); + longjmp(errcatch,0); + /* NOTREACHED */ + } + state = p->state; + yylval.s = (char *)p->name; /* XXX */ + return (p->token); + } + break; + + case SITECMD: + if (cbuf[cpos] == ' ') { + cpos++; + return (SP); + } + cp = &cbuf[cpos]; + cp2 = strpbrk(cp, " \n"); + if (cp2) + cpos = cp2 - cbuf; + c = cbuf[cpos]; + cbuf[cpos] = '\0'; + upper(cp); + p = lookup(sitetab, 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 = (char *) p->name; /* XXX */ + return (p->token); + } + state = CMD; + break; + + case OSTR: + if (cbuf[cpos] == '\n') { + state = CMD; + return (CRLF); + } + /* FALLTHROUGH */ + + case STR1: + case ZSTR1: + dostr1: + if (cbuf[cpos] == ' ') { + cpos++; + /* DOH!!! who wrote this? + * state = ++state; is undefined in C! + * state = state == OSTR ? STR2 : ++state; + * looks elegant but not correct, adding 'value' + */ + value = state == OSTR ? STR2 : ++state; + state = value; + 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 = strdup(cp); + if (yylval.s == NULL) + fatal("Ran out of memory."); + 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'; + yylval.i = atoi(cp); + cbuf[cpos] = c; + state = STR1; + return (NUMBER); + } + state = STR1; + goto dostr1; + + 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); + + } + break; + + default: + fatal("Unknown state in scanner."); + } + yyerror((char *) 0); + state = CMD; + longjmp(errcatch,0); + } +} + +void upper(char *s) +{ + while (*s != '\0') { + if (islower(*s)) + *s = toupper(*s); + s++; + } +} + +static void help(struct tab *ctab, char *s) +{ + struct tab *c; + int width, NCMDS; + const char *type; + + if (ctab == sitetab) + type = "SITE "; + else + type = ""; + width = 0, NCMDS = 0; + for (c = ctab; c->name != NULL; c++) { + int len = strlen(c->name); + + if (len > width) + width = len; + NCMDS++; + } + width = (width + 8) &~ 7; + if (s == 0) { + int i, j, w; + int columns, lines; + + lreply(214, "The following %scommands are recognized %s.", + type, "(* =>'s unimplemented)"); + columns = 76 / width; + if (columns == 0) + columns = 1; + lines = (NCMDS + columns - 1) / columns; + for (i = 0; i < lines; i++) { + printf(" "); + for (j = 0; j < columns; j++) { + c = ctab + j * lines + i; + printf("%s%c", c->name, + c->implemented ? ' ' : '*'); + if (c + lines >= &ctab[NCMDS]) + break; + w = strlen(c->name) + 1; + while (w < width) { + putchar(' '); + w++; + } + } + printf("\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) + reply(214, "Syntax: %s%s %s", type, c->name, c->help); + else + reply(214, "%s%-*s\t%s; unimplemented.", type, width, + c->name, c->help); +} + +static void sizecmd(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", (quad_t) 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", (quad_t) count); + break; } + default: + reply(504, "SIZE not implemented for Type %c.", "?AEIL"[type]); + } +}