* Copyright (c) 1985 Regents of the University of California.
* Redistribution and use in source and binary forms are permitted
* provided that the above copyright notice and this paragraph are
* duplicated in all such forms and that any documentation,
* advertising materials, and other materials related to such
* distribution and use acknowledge that the software was developed
* by the University of California, Berkeley. The name of the
* University may not be used to endorse or promote products derived
* from this software without specific prior written permission.
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
* WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
* @(#)ftpcmd.y 5.11 (Berkeley) 6/18/88
* Grammar for FTP commands.
static char sccsid[] = "@(#)ftpcmd.y 5.11 (Berkeley) 6/18/88";
extern struct sockaddr_in data_dest;
extern struct passwd *pw;
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 XMKD XRMD XPWD
cmd: USER SP username CRLF
extern struct passwd *getpwnam();
if (strcmp((char *) $3, "ftp") == 0 ||
strcmp((char *) $3, "anonymous") == 0) {
if ((pw = getpwnam("ftp")) != NULL) {
"Guest login ok, send ident as password.");
reply(530, "User %s unknown.", $3);
} else if (checkuser((char *) $3)) {
pw = getpwnam((char *) $3);
reply(530, "User %s unknown.", $3);
reply(331, "Password required for %s.", $3);
reply(530, "User %s access denied.", $3);
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.");
| 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.");
| RETR check_login SP pathname CRLF
retrieve((char *) 0, (char *) $4);
| STOR check_login SP pathname CRLF
| APPE check_login SP pathname CRLF
| NLST check_login SP pathname CRLF
retrieve("/bin/ls %s", (char *) $4);
retrieve("/bin/ls -lg", "");
| LIST check_login SP pathname CRLF
retrieve("/bin/ls -lg %s", (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
reply(200, "NOOP command successful.");
| XMKD check_login SP pathname CRLF
| XRMD check_login SP pathname CRLF
| STOU check_login SP pathname CRLF
rcmd: RNFR check_login SP pathname CRLF
fromname = renamefrom((char *) $4);
if (fromname == (char *) 0 && $4) {
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 ($1 && strncmp((char *) $1, "~", 1) == 0) {
$$ = (int)*glob((char *) $1);
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 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, STR1, 1, "<sp> password" },
{ "ACCT", ACCT, STR1, 0, "(specify account)" },
{ "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, STR1, 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, STR1, 0, "(get site parameters)" },
{ "STAT", STAT, OSTR, 0, "(get server status)" },
{ "HELP", HELP, OSTR, 1, "[ <sp> <string> ]" },
{ "NOOP", NOOP, ARGS, 1, "" },
{ "MKD", XMKD, STR1, 1, "<sp> path-name" },
{ "XMKD", XMKD, STR1, 1, "<sp> path-name" },
{ "RMD", XRMD, STR1, 1, "<sp> path-name" },
{ "XRMD", XRMD, STR1, 1, "<sp> path-name" },
{ "PWD", XPWD, ARGS, 1, "(return current directory)" },
{ "XPWD", XPWD, ARGS, 1, "(return current directory)" },
{ "CDUP", XCUP, ARGS, 1, "(change to parent directory)" },
{ "XCUP", XCUP, ARGS, 1, "(change to parent directory)" },
{ "STOU", STOU, STR1, 1, "<sp> file-name" },
for (p = cmdtab; 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, "FTPD: command: %s", s);
while (--n > 0 && (c = getc(iop)) != EOF) {
switch (c = 0377 & getc(iop)) {
printf("%c%c%c", IAC, WONT, c);
printf("%c%c%c", IAC, DONT, c);
c = 0377 & getc(iop); /* try next character */
syslog(LOG_DEBUG, "FTPD: command: %s", s);
"Timeout (%d seconds): closing control connection.", timeout);
"FTPD: User %s timed out after %d seconds at %s",
(pw ? pw -> pw_name : "unknown"), timeout, ctime(&now));
(void) signal(SIGALRM, toolong);
(void) alarm((unsigned) timeout);
if (getline(cbuf, sizeof(cbuf)-1, stdin) == NULL) {
reply(221, "You could at least say goodbye.");
cpos = index(cbuf, ' ') - cbuf;
cpos = index(cbuf, '\n') - cbuf;
if (p->implemented == 0) {
if (cbuf[cpos] == '\n') {
* Make sure the string is nonempty and \n terminated.
if (n > 1 && cbuf[cpos] == '\n') {
if (isdigit(cbuf[cpos])) {
while (isdigit(cbuf[++cpos]))
fatal("Unknown state in scanner.");
extern char *malloc(), *strcpy();
p = malloc((unsigned) strlen(s) + 1);
fatal("Ran out of memory.");
register int width, NCMDS;
for (c = cmdtab; c->name != NULL; c++) {
int len = strlen(c->name) + 1;
width = (width + 8) &~ 7;
"The following commands are recognized (* =>'s unimplemented).");
lines = (NCMDS + columns - 1) / columns;
for (i = 0; i < lines; i++) {
for (j = 0; j < columns; j++) {
c = cmdtab + j * lines + i;
c->implemented ? ' ' : '*');
if (c + lines >= &cmdtab[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", c->name, c->help);
reply(214, "%-*s\t%s; unimplemented.", width, c->name, c->help);