* Copyright (c) 1985, 1988, 1993
* The Regents of the University of California. All rights reserved.
* %sccs.include.redist.c%
* @(#)ftpcmd.y 8.1 (Berkeley) %G%
* Grammar for FTP commands.
static char sccsid[] = "@(#)ftpcmd.y 8.1 (Berkeley) %G%";
extern struct sockaddr_in data_dest;
extern struct passwd *pw;
extern char hostname[], remotehost[];
SP CRLF COMMA STRING NUMBER
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
cmd: USER SP username CRLF
reply(200, "PORT command successful.");
if (cmd_form == FORM_N) {
reply(200, "Type set to A.");
reply(504, "Form must be N.");
reply(504, "Type E not implemented.");
reply(200, "Type set to I.");
"Type set to L (byte size 8).");
reply(504, "Byte size must be 8.");
UNIMPLEMENTED for NBBY != 8
| STRU SP struct_code CRLF
reply(200, "STRU F ok.");
reply(504, "Unimplemented STRU type.");
reply(200, "MODE S ok.");
reply(502, "Unimplemented MODE type.");
reply(202, "ALLO command ignored.");
| ALLO SP NUMBER SP R SP NUMBER CRLF
reply(202, "ALLO command ignored.");
| RETR check_login SP pathname CRLF
retrieve((char *) 0, (char *) $4);
| STOR check_login SP pathname CRLF
store((char *) $4, "w", 0);
| APPE check_login SP pathname CRLF
store((char *) $4, "a", 0);
| NLST check_login SP STRING CRLF
send_file_list((char *) $4);
retrieve("/bin/ls -lgA", "");
| LIST check_login SP pathname CRLF
retrieve("/bin/ls -lgA %s", (char *) $4);
| STAT check_login SP pathname CRLF
statfilecmd((char *) $4);
| DELE check_login SP pathname CRLF
renamecmd(fromname, (char *) $3);
reply(503, "Bad sequence of commands.");
reply(225, "ABOR command successful.");
| CWD check_login SP pathname CRLF
help(cmdtab, (char *) 0);
register char *cp = (char *)$3;
if (strncasecmp(cp, "SITE", 4) == 0) {
help(sitetab, (char *) 0);
help(cmdtab, (char *) $3);
reply(200, "NOOP command successful.");
| MKD check_login SP pathname CRLF
| RMD check_login SP pathname CRLF
help(sitetab, (char *) 0);
| SITE SP HELP SP STRING CRLF
help(sitetab, (char *) $5);
| SITE SP UMASK check_login CRLF
reply(200, "Current UMASK is %03o", oldmask);
| SITE SP UMASK check_login SP octal_number CRLF
if (($6 == -1) || ($6 > 0777)) {
reply(501, "Bad UMASK value");
"UMASK set to %03o (was %03o)",
| SITE SP CHMOD check_login SP octal_number SP pathname CRLF
if ($4 && ($8 != NULL)) {
"CHMOD: Mode value must be between 0 and 0777");
else if (chmod((char *) $8, $6) < 0)
perror_reply(550, (char *) $8);
reply(200, "CHMOD command successful.");
"Current IDLE time limit is %d seconds; max %d",
| SITE SP IDLE SP NUMBER CRLF
if ($5 < 30 || $5 > maxtimeout) {
"Maximum IDLE time must be between 30 and %d seconds",
(void) alarm((unsigned) timeout);
"Maximum IDLE time set to %d seconds",
| STOU check_login SP pathname CRLF
store((char *) $4, "w", 1);
reply(215, "UNIX Type: L%d Version: BSD-%d",
reply(215, "UNIX Type: L%d", NBBY);
reply(215, "UNKNOWN Type: L%d", NBBY);
* 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
* 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 (stat((char *) $4, &stbuf) < 0)
(char *)$4, strerror(errno));
else if ((stbuf.st_mode&S_IFMT) != S_IFREG) {
reply(550, "%s: not a plain file.",
t = gmtime(&stbuf.st_mtime);
"19%02d%02d%02d%02d%02d%02d",
t->tm_year, t->tm_mon+1, t->tm_mday,
t->tm_hour, t->tm_min, t->tm_sec);
rcmd: RNFR check_login SP pathname CRLF
fromname = renamefrom((char *) $4);
if (fromname == (char *) 0 && $4) {
*(char **)&($$) = (char *)calloc(1, sizeof(char));
host_port: NUMBER COMMA NUMBER COMMA NUMBER COMMA NUMBER COMMA
a = (char *)&data_dest.sin_addr;
a[0] = $1; a[1] = $3; a[2] = $5; a[3] = $7;
p = (char *)&data_dest.sin_port;
data_dest.sin_family = AF_INET;
/* this is for a bug in the BBN ftp */
* 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 && strncmp((char *) $1, "~", 1) == 0) {
*(char **)&($$) = *ftpglob((char *) $1);
register 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.
reply(530, "Please login with USER and PASS.");
#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 */
short implemented; /* 1 if command is implemented */
struct tab cmdtab[] = { /* In order defined in RFC 765 */
{ "USER", USER, STR1, 1, "<sp> username" },
{ "PASS", PASS, ZSTR1, 1, "<sp> 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, "<sp> b0, b1, b2, b3, b4" },
{ "PASV", PASV, ARGS, 1, "(set server in passive mode)" },
{ "TYPE", TYPE, ARGS, 1, "<sp> [ A | E | I | L ]" },
{ "STRU", STRU, ARGS, 1, "(specify file structure)" },
{ "MODE", MODE, ARGS, 1, "(specify transfer mode)" },
{ "RETR", RETR, STR1, 1, "<sp> file-name" },
{ "STOR", STOR, STR1, 1, "<sp> file-name" },
{ "APPE", APPE, STR1, 1, "<sp> 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, 0, "(restart command)" },
{ "RNFR", RNFR, STR1, 1, "<sp> file-name" },
{ "RNTO", RNTO, STR1, 1, "<sp> file-name" },
{ "ABOR", ABOR, ARGS, 1, "(abort operation)" },
{ "DELE", DELE, STR1, 1, "<sp> file-name" },
{ "CWD", CWD, OSTR, 1, "[ <sp> directory-name ]" },
{ "XCWD", CWD, OSTR, 1, "[ <sp> directory-name ]" },
{ "LIST", LIST, OSTR, 1, "[ <sp> path-name ]" },
{ "NLST", NLST, OSTR, 1, "[ <sp> path-name ]" },
{ "SITE", SITE, SITECMD, 1, "site-cmd [ <sp> arguments ]" },
{ "SYST", SYST, ARGS, 1, "(get type of operating system)" },
{ "STAT", STAT, OSTR, 1, "[ <sp> path-name ]" },
{ "HELP", HELP, OSTR, 1, "[ <sp> <string> ]" },
{ "NOOP", NOOP, ARGS, 1, "" },
{ "MKD", MKD, STR1, 1, "<sp> path-name" },
{ "XMKD", MKD, STR1, 1, "<sp> path-name" },
{ "RMD", RMD, STR1, 1, "<sp> path-name" },
{ "XRMD", RMD, STR1, 1, "<sp> 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, "<sp> file-name" },
{ "SIZE", SIZE, OSTR, 1, "<sp> path-name" },
{ "MDTM", MDTM, OSTR, 1, "<sp> path-name" },
{ "UMASK", UMASK, ARGS, 1, "[ <sp> umask ]" },
{ "IDLE", IDLE, ARGS, 1, "[ <sp> maximum-idle-time ]" },
{ "CHMOD", CHMOD, NSTR, 1, "<sp> mode <sp> file-name" },
{ "HELP", HELP, OSTR, 1, "[ <sp> <string> ]" },
static char *copy __P((char *));
static void help __P((struct tab *, char *));
lookup __P((struct tab *, char *));
static void sizecmd __P((char *));
static void toolong __P((int));
static int yylex __P((void));
for (; p->name != NULL; p++)
if (strcmp(cmd, p->name) == 0)
* getline - a hacked up version of fgets to ignore TELNET escape codes.
/* tmpline may contain saved command from urgent mode interruption */
for (c = 0; tmpline[c] != '\0' && --n > 0; ++c) {
if (tmpline[c] == '\n') {
syslog(LOG_DEBUG, "command: %s", s);
while ((c = getc(iop)) != EOF) {
if ((c = getc(iop)) != EOF) {
printf("%c%c%c", IAC, DONT, 0377&c);
printf("%c%c%c", IAC, WONT, 0377&c);
continue; /* ignore command */
if (--n <= 0 || c == '\n')
if (!guest && strncasecmp("pass ", s, 5) == 0) {
/* Don't syslog passwords */
syslog(LOG_DEBUG, "command: %.5s ???", s);
/* Don't syslog trailing CR-LF */
while (cp >= s && (*cp == '\n' || *cp == '\r')) {
syslog(LOG_DEBUG, "command: %.*s", len, s);
"Timeout (%d seconds): closing control connection.", timeout);
syslog(LOG_INFO, "User %s timed out after %d seconds",
(pw ? pw -> pw_name : "unknown"), timeout);
(void) signal(SIGALRM, toolong);
(void) alarm((unsigned) timeout);
if (getline(cbuf, sizeof(cbuf)-1, stdin) == NULL) {
reply(221, "You could at least say goodbye.");
if (strncasecmp(cbuf, "PASS", 4) != NULL)
setproctitle("%s: %s", proctitle, cbuf);
#endif /* SETPROCTITLE */
if ((cp = strchr(cbuf, '\r'))) {
if ((cp = strpbrk(cbuf, " \n")))
p = lookup(cmdtab, cbuf);
if (p->implemented == 0) {
*(char **)&yylval = p->name;
if ((cp2 = strpbrk(cp, " \n")))
if (p->implemented == 0) {
*(char **)&yylval = p->name;
if (cbuf[cpos] == '\n') {
state = state == OSTR ? STR2 : ++state;
if (cbuf[cpos] == '\n') {
* Make sure the string is nonempty and \n terminated.
if (n > 1 && cbuf[cpos] == '\n') {
*(char **)&yylval = copy(cp);
if (isdigit(cbuf[cpos])) {
while (isdigit(cbuf[++cpos]))
if (isdigit(cbuf[cpos])) {
while (isdigit(cbuf[++cpos]))
fatal("Unknown state in scanner.");
p = malloc((unsigned) strlen(s) + 1);
fatal("Ran out of memory.");
register int width, NCMDS;
for (c = ctab; c->name != NULL; c++) {
int len = strlen(c->name);
width = (width + 8) &~ 7;
lreply(214, "The following %scommands are recognized %s.",
type, "(* =>'s unimplemented)");
lines = (NCMDS + columns - 1) / columns;
for (i = 0; i < lines; i++) {
for (j = 0; j < columns; j++) {
c = ctab + j * lines + i;
c->implemented ? ' ' : '*');
if (c + lines >= &ctab[NCMDS])
reply(214, "Direct comments to ftp-bugs@%s.", hostname);
if (c == (struct tab *)0) {
reply(502, "Unknown command %s.", s);
reply(214, "Syntax: %s%s %s", type, c->name, c->help);
reply(214, "%s%-*s\t%s; unimplemented.", type, width,
if (stat(filename, &stbuf) < 0 ||
(stbuf.st_mode&S_IFMT) != S_IFREG)
reply(550, "%s: not a plain file.", filename);
reply(213, "%lu", stbuf.st_size);
fin = fopen(filename, "r");
perror_reply(550, filename);
if (fstat(fileno(fin), &stbuf) < 0 ||
(stbuf.st_mode&S_IFMT) != S_IFREG) {
reply(550, "%s: not a plain file.", filename);
while((c=getc(fin)) != EOF) {
if (c == '\n') /* will get expanded to \r\n */
reply(213, "%ld", count);
reply(504, "SIZE not implemented for Type %c.", "?AEIL"[type]);