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.
27 /* sccsid[] = "@(#)ftpcmd.y 5.20.1.1 (Berkeley) 3/2/89"; */
29 #include <sys/param.h>
30 #include <sys/socket.h>
32 #include <netinet/in.h>
51 static void yyerror(const char *);
54 extern struct sockaddr_in data_dest;
56 extern struct passwd *pw;
63 extern int maxtimeout;
65 extern char hostname[], remotehost[];
66 extern char proctitle[];
68 extern int usedefault;
70 extern char tmpline[];
72 extern char **glob(char *);
73 extern char *renamefrom(char *);
74 extern void cwd(const char *);
76 extern void dologout(int);
77 extern void fatal(const char *);
78 extern void makedir(const char *);
79 extern void nack(const char *);
80 extern void pass(const char *);
81 extern void passive(void);
82 extern void pwd(void);
83 extern void removedir(char *);
84 extern void renamecmd(char *, char *);
85 extern void retrieve(const char *, const char *);
86 extern void send_file_list(const char *);
87 extern void statcmd(void);
88 extern void statfilecmd(const char *);
89 extern void store(char *, const char *, int);
90 extern void user(const char *);
92 extern void perror_reply(int, const char *, ...);
93 extern void reply(int, const char *, ...);
94 extern void lreply(int, const char *, ...);
98 static int cmd_bytesz;
106 short implemented; /* 1 if command is implemented */
110 static char * copy(const char *);
113 static void sizecmd(char *filename);
114 static void help(struct tab *ctab, char *s);
116 struct tab sitetab[];
120 yyerror(const char *msg)
152 SP CRLF COMMA STRING NUMBER
154 USER PASS ACCT REIN QUIT PORT
155 PASV TYPE STRU MODE RETR STOR
156 APPE MLFL MAIL MSND MSOM MSAM
157 MRSQ MRCP ALLO REST RNFR RNTO
158 ABOR DELE CWD LIST NLST SITE
159 STAT HELP NOOP MKD RMD PWD
160 CDUP STOU SMNT SYST SIZE MDTM
170 cmd_list: /* empty */
173 fromname = (char *) 0;
178 cmd: USER SP username CRLF
183 | PASS SP password CRLF
188 | PORT SP host_port CRLF
195 reply(200, "PORT command successful.");
201 | TYPE SP type_code CRLF
206 if (cmd_form == FORM_N) {
207 reply(200, "Type set to A.");
211 reply(504, "Form must be N.");
215 reply(504, "Type E not implemented.");
219 reply(200, "Type set to I.");
225 if (cmd_bytesz == 8) {
227 "Type set to L (byte size 8).");
230 reply(504, "Byte size must be 8.");
231 #else /* NBBY == 8 */
232 UNIMPLEMENTED for NBBY != 8
233 #endif /* NBBY == 8 */
236 | STRU SP struct_code CRLF
241 reply(200, "STRU F ok.");
245 reply(504, "Unimplemented STRU type.");
248 | MODE SP mode_code CRLF
253 reply(200, "MODE S ok.");
257 reply(502, "Unimplemented MODE type.");
260 | ALLO SP NUMBER CRLF
262 reply(202, "ALLO command ignored.");
264 | ALLO SP NUMBER SP R SP NUMBER CRLF
266 reply(202, "ALLO command ignored.");
268 | RETR check_login SP pathname CRLF
271 retrieve((char *) 0, $4);
275 | STOR check_login SP pathname CRLF
282 | APPE check_login SP pathname CRLF
289 | NLST check_login CRLF
294 | NLST check_login SP STRING CRLF
297 send_file_list((char *) $4);
301 | LIST check_login CRLF
304 retrieve("/bin/ls -lgA", "");
306 | LIST check_login SP pathname CRLF
309 retrieve("/bin/ls -lgA %s", $4);
313 | STAT check_login SP pathname CRLF
324 | DELE check_login SP pathname CRLF
331 | RNTO SP pathname CRLF
334 renamecmd(fromname, (char *) $3);
336 fromname = (char *) 0;
338 reply(503, "Bad sequence of commands.");
344 reply(225, "ABOR command successful.");
346 | CWD check_login CRLF
351 | CWD check_login SP pathname CRLF
360 help(cmdtab, (char *) 0);
362 | HELP SP STRING CRLF
364 register char *cp = (char *)$3;
366 if (strncasecmp(cp, "SITE", 4) == 0) {
373 help(sitetab, (char *) 0);
375 help(cmdtab, (char *) $3);
379 reply(200, "NOOP command successful.");
381 | MKD check_login SP pathname CRLF
384 makedir((char *) $4);
388 | RMD check_login SP pathname CRLF
391 removedir((char *) $4);
395 | PWD check_login CRLF
400 | CDUP check_login CRLF
407 help(sitetab, (char *) 0);
409 | SITE SP HELP SP STRING CRLF
411 help(sitetab, (char *) $5);
413 | SITE SP UMASK check_login CRLF
419 (void) umask(oldmask);
420 reply(200, "Current UMASK is %03o", oldmask);
423 | SITE SP UMASK check_login SP octal_number CRLF
428 if (($6 == -1) || ($6 > 0777)) {
429 reply(501, "Bad UMASK value");
433 "UMASK set to %03o (was %03o)",
438 | SITE SP CHMOD check_login SP octal_number SP pathname CRLF
440 if ($4 && ($8 != 0)) {
443 "CHMOD: Mode value must be between 0 and 0777");
444 else if (chmod((char *) $8, $6) < 0)
445 perror_reply(550, (char *) $8);
447 reply(200, "CHMOD command successful.");
455 "Current IDLE time limit is %d seconds; max %d",
456 timeout, maxtimeout);
458 | SITE SP IDLE SP NUMBER CRLF
460 if ($5 < 30 || $5 > maxtimeout) {
462 "Maximum IDLE time must be between 30 and %d seconds",
466 (void) alarm((unsigned) timeout);
468 "Maximum IDLE time set to %d seconds",
472 | STOU check_login SP pathname CRLF
475 store((char *) $4, "w", 1);
483 reply(215, "UNIX Type: L%d Version: BSD-%d",
486 reply(215, "UNIX Type: L%d", NBBY);
489 reply(215, "UNKNOWN Type: L%d", NBBY);
494 * SIZE is not in RFC959, but Postel has blessed it and
495 * it will be in the updated RFC.
497 * Return size of file in a format suitable for
498 * using with RESTART (we just count bytes).
500 | SIZE check_login SP pathname CRLF
503 sizecmd((char *) $4);
509 * MDTM is not in RFC959, but Postel has blessed it and
510 * it will be in the updated RFC.
512 * Return modification time of file as an ISO 3307
513 * style time. E.g. YYYYMMDDHHMMSS or YYYYMMDDHHMMSS.xxx
514 * where xxx is the fractional second (of any precision,
515 * not necessarily 3 digits)
517 | MDTM check_login SP pathname CRLF
521 if (stat((char *) $4, &stbuf) < 0)
522 perror_reply(550, "%s", (char *) $4);
523 else if ((stbuf.st_mode&S_IFMT) != S_IFREG) {
524 reply(550, "%s: not a plain file.",
527 register struct tm *t;
528 t = gmtime(&stbuf.st_mtime);
530 "%04d%02d%02d%02d%02d%02d",
532 t->tm_mon+1, t->tm_mday,
533 t->tm_hour, t->tm_min, t->tm_sec);
541 reply(221, "Goodbye.");
549 rcmd: RNFR check_login SP pathname CRLF
552 fromname = renamefrom((char *) $4);
553 if (fromname == (char *) 0 && $4) {
563 password: /* empty */
565 *(const char **)(&($$)) = "";
573 host_port: NUMBER COMMA NUMBER COMMA NUMBER COMMA NUMBER COMMA
576 register char *a, *p;
578 a = (char *)&data_dest.sin_addr;
583 p = (char *)&data_dest.sin_port;
586 data_dest.sin_family = AF_INET;
638 /* this is for a bug in the BBN ftp */
677 * Problem: this production is used for all pathname
678 * processing, but only gives a 550 error reply.
679 * This is a valid reply in some cases but not in others.
681 if (logged_in && $1 && strncmp((char *) $1, "~", 1) == 0) {
682 *(char **)&($$) = *glob((char *) $1);
698 register int ret, dec, multby, digit;
701 * Convert a number that was read as decimal number
702 * to what it would be if it had been read as octal.
713 ret += digit * multby;
721 check_login: /* empty */
726 reply(530, "Please login with USER and PASS.");
735 extern int YYLEX_DECL();
738 extern jmp_buf errcatch;
740 static void upper(char *);
742 #define CMD 0 /* beginning of command */
743 #define ARGS 1 /* expect miscellaneous arguments */
744 #define STR1 2 /* expect SP followed by STRING */
745 #define STR2 3 /* expect STRING */
746 #define OSTR 4 /* optional SP then STRING */
747 #define ZSTR1 5 /* SP then optional STRING */
748 #define ZSTR2 6 /* optional STRING after SP */
749 #define SITECMD 7 /* SITE command */
750 #define NSTR 8 /* Number followed by a string */
752 struct tab cmdtab[] = { /* In order defined in RFC 765 */
753 { "USER", USER, STR1, 1, "<sp> username" },
754 { "PASS", PASS, ZSTR1, 1, "<sp> password" },
755 { "ACCT", ACCT, STR1, 0, "(specify account)" },
756 { "SMNT", SMNT, ARGS, 0, "(structure mount)" },
757 { "REIN", REIN, ARGS, 0, "(reinitialize server state)" },
758 { "QUIT", QUIT, ARGS, 1, "(terminate service)", },
759 { "PORT", PORT, ARGS, 1, "<sp> b0, b1, b2, b3, b4" },
760 { "PASV", PASV, ARGS, 1, "(set server in passive mode)" },
761 { "TYPE", TYPE, ARGS, 1, "<sp> [ A | E | I | L ]" },
762 { "STRU", STRU, ARGS, 1, "(specify file structure)" },
763 { "MODE", MODE, ARGS, 1, "(specify transfer mode)" },
764 { "RETR", RETR, STR1, 1, "<sp> file-name" },
765 { "STOR", STOR, STR1, 1, "<sp> file-name" },
766 { "APPE", APPE, STR1, 1, "<sp> file-name" },
767 { "MLFL", MLFL, OSTR, 0, "(mail file)" },
768 { "MAIL", MAIL, OSTR, 0, "(mail to user)" },
769 { "MSND", MSND, OSTR, 0, "(mail send to terminal)" },
770 { "MSOM", MSOM, OSTR, 0, "(mail send to terminal or mailbox)" },
771 { "MSAM", MSAM, OSTR, 0, "(mail send to terminal and mailbox)" },
772 { "MRSQ", MRSQ, OSTR, 0, "(mail recipient scheme question)" },
773 { "MRCP", MRCP, STR1, 0, "(mail recipient)" },
774 { "ALLO", ALLO, ARGS, 1, "allocate storage (vacuously)" },
775 { "REST", REST, ARGS, 0, "(restart command)" },
776 { "RNFR", RNFR, STR1, 1, "<sp> file-name" },
777 { "RNTO", RNTO, STR1, 1, "<sp> file-name" },
778 { "ABOR", ABOR, ARGS, 1, "(abort operation)" },
779 { "DELE", DELE, STR1, 1, "<sp> file-name" },
780 { "CWD", CWD, OSTR, 1, "[ <sp> directory-name ]" },
781 { "XCWD", CWD, OSTR, 1, "[ <sp> directory-name ]" },
782 { "LIST", LIST, OSTR, 1, "[ <sp> path-name ]" },
783 { "NLST", NLST, OSTR, 1, "[ <sp> path-name ]" },
784 { "SITE", SITE, SITECMD, 1, "site-cmd [ <sp> arguments ]" },
785 { "SYST", SYST, ARGS, 1, "(get type of operating system)" },
786 { "STAT", STAT, OSTR, 1, "[ <sp> path-name ]" },
787 { "HELP", HELP, OSTR, 1, "[ <sp> <string> ]" },
788 { "NOOP", NOOP, ARGS, 1, "" },
789 { "MKD", MKD, STR1, 1, "<sp> path-name" },
790 { "XMKD", MKD, STR1, 1, "<sp> path-name" },
791 { "RMD", RMD, STR1, 1, "<sp> path-name" },
792 { "XRMD", RMD, STR1, 1, "<sp> path-name" },
793 { "PWD", PWD, ARGS, 1, "(return current directory)" },
794 { "XPWD", PWD, ARGS, 1, "(return current directory)" },
795 { "CDUP", CDUP, ARGS, 1, "(change to parent directory)" },
796 { "XCUP", CDUP, ARGS, 1, "(change to parent directory)" },
797 { "STOU", STOU, STR1, 1, "<sp> file-name" },
798 { "SIZE", SIZE, OSTR, 1, "<sp> path-name" },
799 { "MDTM", MDTM, OSTR, 1, "<sp> path-name" },
803 struct tab sitetab[] = {
804 { "UMASK", UMASK, ARGS, 1, "[ <sp> umask ]" },
805 { "IDLE", IDLE, ARGS, 1, "[ <sp> maximum-idle-time ]" },
806 { "CHMOD", CHMOD, NSTR, 1, "<sp> mode <sp> file-name" },
807 { "HELP", HELP, OSTR, 1, "[ <sp> <string> ]" },
812 lookup(struct tab *p, char *cmd)
815 for (; p->name != 0; p++)
816 if (strcmp(cmd, p->name) == 0)
821 #include <arpa/telnet.h>
824 * get_line - a hacked up version of fgets to ignore TELNET escape codes.
827 get_line(char *s, int n, FILE *iop)
833 /* tmpline may contain saved command from urgent mode interruption */
834 for (c = 0; tmpline[c] != '\0' && --n > 0; ++c) {
836 if (tmpline[c] == '\n') {
839 syslog(LOG_DEBUG, "command: %s", s);
846 while ((c = getc(iop)) != EOF) {
849 if ((c = getc(iop)) != EOF) {
855 printf("%c%c%c", IAC, DONT, 0377&c);
856 (void) fflush(stdout);
861 printf("%c%c%c", IAC, WONT, 0377&c);
862 (void) fflush(stdout);
867 continue; /* ignore command */
872 if (--n <= 0 || c == '\n')
875 if (c == EOF && cs == s)
879 syslog(LOG_DEBUG, "command: %s", s);
890 "Timeout (%d seconds): closing control connection.", timeout);
894 "User %s timed out after %d seconds at %s",
895 (pw ? pw -> pw_name : "unknown"), timeout, ctime(&now));
903 static int cpos, state;
904 register char *cp, *cp2;
905 register struct tab *p;
913 (void) signal(SIGALRM, toolong);
914 (void) alarm((unsigned) timeout);
915 if (get_line(cbuf, sizeof(cbuf)-1, stdin) == 0) {
916 reply(221, "You could at least say goodbye.");
921 if (strncasecmp(cbuf, "PASS", 4) != 0)
922 setproctitle("%s: %s", proctitle, cbuf);
923 #endif /* SETPROCTITLE */
924 if ((cp = strchr(cbuf, '\r'))) {
928 if ((cp = strpbrk(cbuf, " \n")))
929 cpos = (int) (cp - cbuf);
935 p = lookup(cmdtab, cbuf);
938 if (p->implemented == 0) {
944 *(const char **)(&yylval) = p->name;
950 if (cbuf[cpos] == ' ') {
955 if ((cp2 = strpbrk(cp, " \n")))
956 cpos = (int) (cp2 - cbuf);
960 p = lookup(sitetab, cp);
963 if (p->implemented == 0) {
970 *(const char **)(&yylval) = p->name;
977 if (cbuf[cpos] == '\n') {
986 if (cbuf[cpos] == ' ') {
997 if (cbuf[cpos] == '\n') {
1005 n = (int) strlen(cp);
1008 * Make sure the string is nonempty and \n terminated.
1010 if (n > 1 && cbuf[cpos] == '\n') {
1012 *(char **)&yylval = copy(cp);
1020 if (cbuf[cpos] == ' ') {
1024 if (isdigit(cbuf[cpos])) {
1026 while (isdigit(cbuf[++cpos]))
1030 yylval.ival = atoi(cp);
1039 if (isdigit(cbuf[cpos])) {
1041 while (isdigit(cbuf[++cpos]))
1045 yylval.ival = atoi(cp);
1049 switch (cbuf[cpos++]) {
1113 fatal("Unknown state in scanner.");
1115 yyerror((char *) 0);
1117 longjmp(errcatch,0);
1124 while (*s != '\0') {
1136 p = (char * )malloc(strlen(s) + 1);
1138 fatal("Ran out of memory.");
1140 (void) strcpy(p, s);
1145 help(struct tab *ctab, char *s)
1147 register struct tab *c;
1148 register int width, NCMDS;
1149 const char *help_type;
1151 if (ctab == sitetab)
1152 help_type = "SITE ";
1155 width = 0, NCMDS = 0;
1156 for (c = ctab; c->name != 0; c++) {
1157 int len = (int) strlen(c->name);
1163 width = (width + 8) &~ 7;
1165 register int i, j, w;
1168 lreply(214, "The following %scommands are recognized %s.",
1169 help_type, "(* =>'s unimplemented)");
1170 columns = 76 / width;
1173 lines = (NCMDS + columns - 1) / columns;
1174 for (i = 0; i < lines; i++) {
1176 for (j = 0; j < columns; j++) {
1177 c = ctab + j * lines + i;
1178 assert(c->name != 0);
1179 printf("%s%c", c->name,
1180 c->implemented ? ' ' : '*');
1181 if (c + lines >= &ctab[NCMDS])
1183 w = (int) strlen(c->name) + 1;
1191 (void) fflush(stdout);
1192 reply(214, "Direct comments to ftp-bugs@%s.", hostname);
1196 c = lookup(ctab, s);
1197 if (c == (struct tab *)0) {
1198 reply(502, "Unknown command %s.", s);
1202 reply(214, "Syntax: %s%s %s", help_type, c->name, c->help);
1204 reply(214, "%s%-*s\t%s; unimplemented.", help_type, width,
1209 sizecmd(char *filename)
1215 if (stat(filename, &stbuf) < 0 ||
1216 (stbuf.st_mode&S_IFMT) != S_IFREG)
1217 reply(550, "%s: not a plain file.", filename);
1219 #ifdef HAVE_LONG_LONG
1220 reply(213, "%llu", (long long) stbuf.st_size);
1222 reply(213, "%lu", stbuf.st_size);
1227 register int c, count;
1229 fin = fopen(filename, "r");
1231 perror_reply(550, filename);
1234 if (fstat(fileno(fin), &stbuf) < 0 ||
1235 (stbuf.st_mode&S_IFMT) != S_IFREG) {
1236 reply(550, "%s: not a plain file.", filename);
1242 while((c=getc(fin)) != EOF) {
1243 if (c == '\n') /* will get expanded to \r\n */
1249 reply(213, "%ld", count);
1252 reply(504, "SIZE not implemented for Type %c.", "?AEIL"[type]);