2 * Copyright (c) 1985, 1988 Regents of the University of California.
5 * Redistribution and use in source and binary forms are permitted
6 * provided that the above copyright notice and this paragraph are
7 * duplicated in all such forms and that any documentation,
8 * advertising materials, and other materials related to such
9 * distribution and use acknowledge that the software was developed
10 * by the University of California, Berkeley. The name of the
11 * University may not be used to endorse or promote products derived
12 * from this software without specific prior written permission.
13 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
14 * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
15 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
17 * @(#)ftpcmd.y 5.20.1.1 (Berkeley) 3/2/89
21 * Grammar for FTP commands.
28 static char sccsid[] = "@(#)ftpcmd.y 5.20.1.1 (Berkeley) 3/2/89";
31 #include <sys/param.h>
32 #include <sys/socket.h>
34 #include <netinet/in.h>
47 extern struct sockaddr_in data_dest;
49 extern struct passwd *pw;
56 extern int maxtimeout;
58 extern char hostname[], remotehost[];
59 extern char proctitle[];
61 extern int usedefault;
63 extern char tmpline[];
68 static int cmd_bytesz;
79 SP CRLF COMMA STRING NUMBER
81 USER PASS ACCT REIN QUIT PORT
82 PASV TYPE STRU MODE RETR STOR
83 APPE MLFL MAIL MSND MSOM MSAM
84 MRSQ MRCP ALLO REST RNFR RNTO
85 ABOR DELE CWD LIST NLST SITE
86 STAT HELP NOOP MKD RMD PWD
87 CDUP STOU SMNT SYST SIZE MDTM
100 fromname = (char *) 0;
105 cmd: USER SP username CRLF
110 | PASS SP password CRLF
115 | PORT SP host_port CRLF
122 reply(200, "PORT command successful.");
128 | TYPE SP type_code CRLF
133 if (cmd_form == FORM_N) {
134 reply(200, "Type set to A.");
138 reply(504, "Form must be N.");
142 reply(504, "Type E not implemented.");
146 reply(200, "Type set to I.");
152 if (cmd_bytesz == 8) {
154 "Type set to L (byte size 8).");
157 reply(504, "Byte size must be 8.");
158 #else /* NBBY == 8 */
159 UNIMPLEMENTED for NBBY != 8
160 #endif /* NBBY == 8 */
163 | STRU SP struct_code CRLF
168 reply(200, "STRU F ok.");
172 reply(504, "Unimplemented STRU type.");
175 | MODE SP mode_code CRLF
180 reply(200, "MODE S ok.");
184 reply(502, "Unimplemented MODE type.");
187 | ALLO SP NUMBER CRLF
189 reply(202, "ALLO command ignored.");
191 | ALLO SP NUMBER SP R SP NUMBER CRLF
193 reply(202, "ALLO command ignored.");
195 | RETR check_login SP pathname CRLF
197 if ($2 && $4 != NULL)
198 retrieve((char *) 0, (char *) $4);
202 | STOR check_login SP pathname CRLF
204 if ($2 && $4 != NULL)
205 store((char *) $4, "w", 0);
209 | APPE check_login SP pathname CRLF
211 if ($2 && $4 != NULL)
212 store((char *) $4, "a", 0);
216 | NLST check_login CRLF
221 | NLST check_login SP STRING CRLF
223 if ($2 && $4 != NULL)
224 send_file_list((char *) $4);
228 | LIST check_login CRLF
231 retrieve("/bin/ls -lgA", "");
233 | LIST check_login SP pathname CRLF
235 if ($2 && $4 != NULL)
236 retrieve("/bin/ls -lgA %s", (char *) $4);
240 | STAT check_login SP pathname CRLF
242 if ($2 && $4 != NULL)
243 statfilecmd((char *) $4);
251 | DELE check_login SP pathname CRLF
253 if ($2 && $4 != NULL)
258 | RNTO SP pathname CRLF
261 renamecmd(fromname, (char *) $3);
263 fromname = (char *) 0;
265 reply(503, "Bad sequence of commands.");
271 reply(225, "ABOR command successful.");
273 | CWD check_login CRLF
278 | CWD check_login SP pathname CRLF
280 if ($2 && $4 != NULL)
287 help(cmdtab, (char *) 0);
289 | HELP SP STRING CRLF
291 register char *cp = (char *)$3;
293 if (strncasecmp(cp, "SITE", 4) == 0) {
300 help(sitetab, (char *) 0);
302 help(cmdtab, (char *) $3);
306 reply(200, "NOOP command successful.");
308 | MKD check_login SP pathname CRLF
310 if ($2 && $4 != NULL)
311 makedir((char *) $4);
315 | RMD check_login SP pathname CRLF
317 if ($2 && $4 != NULL)
318 removedir((char *) $4);
322 | PWD check_login CRLF
327 | CDUP check_login CRLF
334 help(sitetab, (char *) 0);
336 | SITE SP HELP SP STRING CRLF
338 help(sitetab, (char *) $5);
340 | SITE SP UMASK check_login CRLF
346 (void) umask(oldmask);
347 reply(200, "Current UMASK is %03o", oldmask);
350 | SITE SP UMASK check_login SP octal_number CRLF
355 if (($6 == -1) || ($6 > 0777)) {
356 reply(501, "Bad UMASK value");
360 "UMASK set to %03o (was %03o)",
365 | SITE SP CHMOD check_login SP octal_number SP pathname CRLF
367 if ($4 && ($8 != NULL)) {
370 "CHMOD: Mode value must be between 0 and 0777");
371 else if (chmod((char *) $8, $6) < 0)
372 perror_reply(550, (char *) $8);
374 reply(200, "CHMOD command successful.");
382 "Current IDLE time limit is %d seconds; max %d",
383 timeout, maxtimeout);
385 | SITE SP IDLE SP NUMBER CRLF
387 if ($5 < 30 || $5 > maxtimeout) {
389 "Maximum IDLE time must be between 30 and %d seconds",
393 (void) alarm((unsigned) timeout);
395 "Maximum IDLE time set to %d seconds",
399 | STOU check_login SP pathname CRLF
401 if ($2 && $4 != NULL)
402 store((char *) $4, "w", 1);
410 reply(215, "UNIX Type: L%d Version: BSD-%d",
413 reply(215, "UNIX Type: L%d", NBBY);
416 reply(215, "UNKNOWN Type: L%d", NBBY);
421 * SIZE is not in RFC959, but Postel has blessed it and
422 * it will be in the updated RFC.
424 * Return size of file in a format suitable for
425 * using with RESTART (we just count bytes).
427 | SIZE check_login SP pathname CRLF
429 if ($2 && $4 != NULL)
430 sizecmd((char *) $4);
436 * MDTM is not in RFC959, but Postel has blessed it and
437 * it will be in the updated RFC.
439 * Return modification time of file as an ISO 3307
440 * style time. E.g. YYYYMMDDHHMMSS or YYYYMMDDHHMMSS.xxx
441 * where xxx is the fractional second (of any precision,
442 * not necessarily 3 digits)
444 | MDTM check_login SP pathname CRLF
446 if ($2 && $4 != NULL) {
448 if (stat((char *) $4, &stbuf) < 0)
449 perror_reply(550, "%s", (char *) $4);
450 else if ((stbuf.st_mode&S_IFMT) != S_IFREG) {
451 reply(550, "%s: not a plain file.",
454 register struct tm *t;
456 t = gmtime(&stbuf.st_mtime);
458 "%d%02d%02d%02d%02d%02d",
459 t->tm_year+1900, t->tm_mon+1, t->tm_mday,
460 t->tm_hour, t->tm_min, t->tm_sec);
468 reply(221, "Goodbye.");
476 rcmd: RNFR check_login SP pathname CRLF
481 fromname = renamefrom((char *) $4);
482 if (fromname == (char *) 0 && $4) {
492 password: /* empty */
494 *(char **)&($$) = "";
502 host_port: NUMBER COMMA NUMBER COMMA NUMBER COMMA NUMBER COMMA
505 register char *a, *p;
507 a = (char *)&data_dest.sin_addr;
508 a[0] = $1; a[1] = $3; a[2] = $5; a[3] = $7;
509 p = (char *)&data_dest.sin_port;
510 p[0] = $9; p[1] = $11;
511 data_dest.sin_family = AF_INET;
563 /* this is for a bug in the BBN ftp */
602 * Problem: this production is used for all pathname
603 * processing, but only gives a 550 error reply.
604 * This is a valid reply in some cases but not in others.
606 if (logged_in && $1 && strncmp((char *) $1, "~", 1) == 0) {
607 *(char **)&($$) = *glob((char *) $1);
608 if (globerr != NULL) {
623 register int ret, dec, multby, digit;
626 * Convert a number that was read as decimal number
627 * to what it would be if it had been read as octal.
638 ret += digit * multby;
646 check_login: /* empty */
651 reply(530, "Please login with USER and PASS.");
659 extern jmp_buf errcatch;
661 #define CMD 0 /* beginning of command */
662 #define ARGS 1 /* expect miscellaneous arguments */
663 #define STR1 2 /* expect SP followed by STRING */
664 #define STR2 3 /* expect STRING */
665 #define OSTR 4 /* optional SP then STRING */
666 #define ZSTR1 5 /* SP then optional STRING */
667 #define ZSTR2 6 /* optional STRING after SP */
668 #define SITECMD 7 /* SITE command */
669 #define NSTR 8 /* Number followed by a string */
675 short implemented; /* 1 if command is implemented */
679 struct tab cmdtab[] = { /* In order defined in RFC 765 */
680 { "USER", USER, STR1, 1, "<sp> username" },
681 { "PASS", PASS, ZSTR1, 1, "<sp> password" },
682 { "ACCT", ACCT, STR1, 0, "(specify account)" },
683 { "SMNT", SMNT, ARGS, 0, "(structure mount)" },
684 { "REIN", REIN, ARGS, 0, "(reinitialize server state)" },
685 { "QUIT", QUIT, ARGS, 1, "(terminate service)", },
686 { "PORT", PORT, ARGS, 1, "<sp> b0, b1, b2, b3, b4" },
687 { "PASV", PASV, ARGS, 1, "(set server in passive mode)" },
688 { "TYPE", TYPE, ARGS, 1, "<sp> [ A | E | I | L ]" },
689 { "STRU", STRU, ARGS, 1, "(specify file structure)" },
690 { "MODE", MODE, ARGS, 1, "(specify transfer mode)" },
691 { "RETR", RETR, STR1, 1, "<sp> file-name" },
692 { "STOR", STOR, STR1, 1, "<sp> file-name" },
693 { "APPE", APPE, STR1, 1, "<sp> file-name" },
694 { "MLFL", MLFL, OSTR, 0, "(mail file)" },
695 { "MAIL", MAIL, OSTR, 0, "(mail to user)" },
696 { "MSND", MSND, OSTR, 0, "(mail send to terminal)" },
697 { "MSOM", MSOM, OSTR, 0, "(mail send to terminal or mailbox)" },
698 { "MSAM", MSAM, OSTR, 0, "(mail send to terminal and mailbox)" },
699 { "MRSQ", MRSQ, OSTR, 0, "(mail recipient scheme question)" },
700 { "MRCP", MRCP, STR1, 0, "(mail recipient)" },
701 { "ALLO", ALLO, ARGS, 1, "allocate storage (vacuously)" },
702 { "REST", REST, ARGS, 0, "(restart command)" },
703 { "RNFR", RNFR, STR1, 1, "<sp> file-name" },
704 { "RNTO", RNTO, STR1, 1, "<sp> file-name" },
705 { "ABOR", ABOR, ARGS, 1, "(abort operation)" },
706 { "DELE", DELE, STR1, 1, "<sp> file-name" },
707 { "CWD", CWD, OSTR, 1, "[ <sp> directory-name ]" },
708 { "XCWD", CWD, OSTR, 1, "[ <sp> directory-name ]" },
709 { "LIST", LIST, OSTR, 1, "[ <sp> path-name ]" },
710 { "NLST", NLST, OSTR, 1, "[ <sp> path-name ]" },
711 { "SITE", SITE, SITECMD, 1, "site-cmd [ <sp> arguments ]" },
712 { "SYST", SYST, ARGS, 1, "(get type of operating system)" },
713 { "STAT", STAT, OSTR, 1, "[ <sp> path-name ]" },
714 { "HELP", HELP, OSTR, 1, "[ <sp> <string> ]" },
715 { "NOOP", NOOP, ARGS, 1, "" },
716 { "MKD", MKD, STR1, 1, "<sp> path-name" },
717 { "XMKD", MKD, STR1, 1, "<sp> path-name" },
718 { "RMD", RMD, STR1, 1, "<sp> path-name" },
719 { "XRMD", RMD, STR1, 1, "<sp> path-name" },
720 { "PWD", PWD, ARGS, 1, "(return current directory)" },
721 { "XPWD", PWD, ARGS, 1, "(return current directory)" },
722 { "CDUP", CDUP, ARGS, 1, "(change to parent directory)" },
723 { "XCUP", CDUP, ARGS, 1, "(change to parent directory)" },
724 { "STOU", STOU, STR1, 1, "<sp> file-name" },
725 { "SIZE", SIZE, OSTR, 1, "<sp> path-name" },
726 { "MDTM", MDTM, OSTR, 1, "<sp> path-name" },
730 struct tab sitetab[] = {
731 { "UMASK", UMASK, ARGS, 1, "[ <sp> umask ]" },
732 { "IDLE", IDLE, ARGS, 1, "[ <sp> maximum-idle-time ]" },
733 { "CHMOD", CHMOD, NSTR, 1, "<sp> mode <sp> file-name" },
734 { "HELP", HELP, OSTR, 1, "[ <sp> <string> ]" },
740 register struct tab *p;
744 for (; p->name != NULL; p++)
745 if (strcmp(cmd, p->name) == 0)
750 #include <arpa/telnet.h>
753 * getline - a hacked up version of fgets to ignore TELNET escape codes.
764 /* tmpline may contain saved command from urgent mode interruption */
765 for (c = 0; tmpline[c] != '\0' && --n > 0; ++c) {
767 if (tmpline[c] == '\n') {
770 syslog(LOG_DEBUG, "command: %s", s);
777 while ((c = getc(iop)) != EOF) {
780 if ((c = getc(iop)) != EOF) {
786 printf("%c%c%c", IAC, DONT, 0377&c);
787 (void) fflush(stdout);
792 printf("%c%c%c", IAC, WONT, 0377&c);
793 (void) fflush(stdout);
798 continue; /* ignore command */
803 if (--n <= 0 || c == '\n')
806 if (c == EOF && cs == s)
810 syslog(LOG_DEBUG, "command: %s", s);
818 extern char *ctime();
819 extern time_t time();
822 "Timeout (%d seconds): closing control connection.", timeout);
826 "User %s timed out after %d seconds at %s",
827 (pw ? pw -> pw_name : "unknown"), timeout, ctime(&now));
834 static int cpos, state;
835 register char *cp, *cp2;
836 register struct tab *p;
845 (void) signal(SIGALRM, toolong);
846 (void) alarm((unsigned) timeout);
847 if (getline(cbuf, sizeof(cbuf)-1, stdin) == NULL) {
848 reply(221, "You could at least say goodbye.");
853 if (strncasecmp(cbuf, "PASS", 4) != NULL)
854 setproctitle("%s: %s", proctitle, cbuf);
855 #endif /* SETPROCTITLE */
856 if ((cp = index(cbuf, '\r'))) {
860 if ((cp = strpbrk(cbuf, " \n")))
867 p = lookup(cmdtab, cbuf);
870 if (p->implemented == 0) {
876 *(char **)&yylval = p->name;
882 if (cbuf[cpos] == ' ') {
887 if ((cp2 = strpbrk(cp, " \n")))
892 p = lookup(sitetab, cp);
895 if (p->implemented == 0) {
902 *(char **)&yylval = p->name;
909 if (cbuf[cpos] == '\n') {
918 if (cbuf[cpos] == ' ') {
920 state = state == OSTR ? STR2 : ++state;
926 if (cbuf[cpos] == '\n') {
937 * Make sure the string is nonempty and \n terminated.
939 if (n > 1 && cbuf[cpos] == '\n') {
941 *(char **)&yylval = copy(cp);
949 if (cbuf[cpos] == ' ') {
953 if (isdigit(cbuf[cpos])) {
955 while (isdigit(cbuf[++cpos]))
968 if (isdigit(cbuf[cpos])) {
970 while (isdigit(cbuf[++cpos]))
978 switch (cbuf[cpos++]) {
1042 fatal("Unknown state in scanner.");
1044 yyerror((char *) 0);
1046 longjmp(errcatch,0);
1053 while (*s != '\0') {
1065 extern char *malloc(), *strcpy();
1067 p = malloc((unsigned) strlen(s) + 1);
1069 fatal("Ran out of memory.");
1070 (void) strcpy(p, s);
1078 register struct tab *c;
1079 register int width, NCMDS;
1082 if (ctab == sitetab)
1086 width = 0, NCMDS = 0;
1087 for (c = ctab; c->name != NULL; c++) {
1088 int len = strlen(c->name);
1094 width = (width + 8) &~ 7;
1096 register int i, j, w;
1099 lreply(214, "The following %scommands are recognized %s.",
1100 type, "(* =>'s unimplemented)");
1101 columns = 76 / width;
1104 lines = (NCMDS + columns - 1) / columns;
1105 for (i = 0; i < lines; i++) {
1107 for (j = 0; j < columns; j++) {
1108 c = ctab + j * lines + i;
1109 printf("%s%c", c->name,
1110 c->implemented ? ' ' : '*');
1111 if (c + lines >= &ctab[NCMDS])
1113 w = strlen(c->name) + 1;
1121 (void) fflush(stdout);
1122 reply(214, "Direct comments to ftp-bugs@%s.", hostname);
1126 c = lookup(ctab, s);
1127 if (c == (struct tab *)0) {
1128 reply(502, "Unknown command %s.", s);
1132 reply(214, "Syntax: %s%s %s", type, c->name, c->help);
1134 reply(214, "%s%-*s\t%s; unimplemented.", type, width,
1145 if (stat(filename, &stbuf) < 0 ||
1146 (stbuf.st_mode&S_IFMT) != S_IFREG)
1147 reply(550, "%s: not a plain file.", filename);
1149 reply(213, "%lu", stbuf.st_size);
1153 register int c, count;
1155 fin = fopen(filename, "r");
1157 perror_reply(550, filename);
1160 if (fstat(fileno(fin), &stbuf) < 0 ||
1161 (stbuf.st_mode&S_IFMT) != S_IFREG) {
1162 reply(550, "%s: not a plain file.", filename);
1168 while((c=getc(fin)) != EOF) {
1169 if (c == '\n') /* will get expanded to \r\n */
1175 reply(213, "%ld", count);
1178 reply(504, "SIZE not implemented for Type %c.", "?AEIL"[type]);