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)
130 SP CRLF COMMA STRING NUMBER
132 USER PASS ACCT REIN QUIT PORT
133 PASV TYPE STRU MODE RETR STOR
134 APPE MLFL MAIL MSND MSOM MSAM
135 MRSQ MRCP ALLO REST RNFR RNTO
136 ABOR DELE CWD LIST NLST SITE
137 STAT HELP NOOP MKD RMD PWD
138 CDUP STOU SMNT SYST SIZE MDTM
148 cmd_list: /* empty */
151 fromname = (char *) 0;
156 cmd: USER SP username CRLF
161 | PASS SP password CRLF
166 | PORT SP host_port CRLF
173 reply(200, "PORT command successful.");
179 | TYPE SP type_code CRLF
184 if (cmd_form == FORM_N) {
185 reply(200, "Type set to A.");
189 reply(504, "Form must be N.");
193 reply(504, "Type E not implemented.");
197 reply(200, "Type set to I.");
203 if (cmd_bytesz == 8) {
205 "Type set to L (byte size 8).");
208 reply(504, "Byte size must be 8.");
209 #else /* NBBY == 8 */
210 UNIMPLEMENTED for NBBY != 8
211 #endif /* NBBY == 8 */
214 | STRU SP struct_code CRLF
219 reply(200, "STRU F ok.");
223 reply(504, "Unimplemented STRU type.");
226 | MODE SP mode_code CRLF
231 reply(200, "MODE S ok.");
235 reply(502, "Unimplemented MODE type.");
238 | ALLO SP NUMBER CRLF
240 reply(202, "ALLO command ignored.");
242 | ALLO SP NUMBER SP R SP NUMBER CRLF
244 reply(202, "ALLO command ignored.");
246 | RETR check_login SP pathname CRLF
249 retrieve((char *) 0, (char *) $4);
253 | STOR check_login SP pathname CRLF
256 store((char *) $4, "w", 0);
260 | APPE check_login SP pathname CRLF
263 store((char *) $4, "a", 0);
267 | NLST check_login CRLF
272 | NLST check_login SP STRING CRLF
275 send_file_list((char *) $4);
279 | LIST check_login CRLF
282 retrieve("/bin/ls -lgA", "");
284 | LIST check_login SP pathname CRLF
287 retrieve("/bin/ls -lgA %s", (char *) $4);
291 | STAT check_login SP pathname CRLF
294 statfilecmd((char *) $4);
302 | DELE check_login SP pathname CRLF
309 | RNTO SP pathname CRLF
312 renamecmd(fromname, (char *) $3);
314 fromname = (char *) 0;
316 reply(503, "Bad sequence of commands.");
322 reply(225, "ABOR command successful.");
324 | CWD check_login CRLF
329 | CWD check_login SP pathname CRLF
338 help(cmdtab, (char *) 0);
340 | HELP SP STRING CRLF
342 register char *cp = (char *)$3;
344 if (strncasecmp(cp, "SITE", 4) == 0) {
351 help(sitetab, (char *) 0);
353 help(cmdtab, (char *) $3);
357 reply(200, "NOOP command successful.");
359 | MKD check_login SP pathname CRLF
362 makedir((char *) $4);
366 | RMD check_login SP pathname CRLF
369 removedir((char *) $4);
373 | PWD check_login CRLF
378 | CDUP check_login CRLF
385 help(sitetab, (char *) 0);
387 | SITE SP HELP SP STRING CRLF
389 help(sitetab, (char *) $5);
391 | SITE SP UMASK check_login CRLF
397 (void) umask(oldmask);
398 reply(200, "Current UMASK is %03o", oldmask);
401 | SITE SP UMASK check_login SP octal_number CRLF
406 if (($6 == -1) || ($6 > 0777)) {
407 reply(501, "Bad UMASK value");
411 "UMASK set to %03o (was %03o)",
416 | SITE SP CHMOD check_login SP octal_number SP pathname CRLF
418 if ($4 && ($8 != 0)) {
421 "CHMOD: Mode value must be between 0 and 0777");
422 else if (chmod((char *) $8, $6) < 0)
423 perror_reply(550, (char *) $8);
425 reply(200, "CHMOD command successful.");
433 "Current IDLE time limit is %d seconds; max %d",
434 timeout, maxtimeout);
436 | SITE SP IDLE SP NUMBER CRLF
438 if ($5 < 30 || $5 > maxtimeout) {
440 "Maximum IDLE time must be between 30 and %d seconds",
444 (void) alarm((unsigned) timeout);
446 "Maximum IDLE time set to %d seconds",
450 | STOU check_login SP pathname CRLF
453 store((char *) $4, "w", 1);
461 reply(215, "UNIX Type: L%d Version: BSD-%d",
464 reply(215, "UNIX Type: L%d", NBBY);
467 reply(215, "UNKNOWN Type: L%d", NBBY);
472 * SIZE is not in RFC959, but Postel has blessed it and
473 * it will be in the updated RFC.
475 * Return size of file in a format suitable for
476 * using with RESTART (we just count bytes).
478 | SIZE check_login SP pathname CRLF
481 sizecmd((char *) $4);
487 * MDTM is not in RFC959, but Postel has blessed it and
488 * it will be in the updated RFC.
490 * Return modification time of file as an ISO 3307
491 * style time. E.g. YYYYMMDDHHMMSS or YYYYMMDDHHMMSS.xxx
492 * where xxx is the fractional second (of any precision,
493 * not necessarily 3 digits)
495 | MDTM check_login SP pathname CRLF
499 if (stat((char *) $4, &stbuf) < 0)
500 perror_reply(550, "%s", (char *) $4);
501 else if ((stbuf.st_mode&S_IFMT) != S_IFREG) {
502 reply(550, "%s: not a plain file.",
505 register struct tm *t;
506 t = gmtime(&stbuf.st_mtime);
508 "%04d%02d%02d%02d%02d%02d",
510 t->tm_mon+1, t->tm_mday,
511 t->tm_hour, t->tm_min, t->tm_sec);
519 reply(221, "Goodbye.");
527 rcmd: RNFR check_login SP pathname CRLF
530 fromname = renamefrom((char *) $4);
531 if (fromname == (char *) 0 && $4) {
541 password: /* empty */
543 *(const char **)(&($$)) = "";
551 host_port: NUMBER COMMA NUMBER COMMA NUMBER COMMA NUMBER COMMA
554 register char *a, *p;
556 a = (char *)&data_dest.sin_addr;
557 a[0] = $1; a[1] = $3; a[2] = $5; a[3] = $7;
558 p = (char *)&data_dest.sin_port;
559 p[0] = $9; p[1] = $11;
560 data_dest.sin_family = AF_INET;
612 /* this is for a bug in the BBN ftp */
651 * Problem: this production is used for all pathname
652 * processing, but only gives a 550 error reply.
653 * This is a valid reply in some cases but not in others.
655 if (logged_in && $1 && strncmp((char *) $1, "~", 1) == 0) {
656 *(char **)&($$) = *glob((char *) $1);
672 register int ret, dec, multby, digit;
675 * Convert a number that was read as decimal number
676 * to what it would be if it had been read as octal.
687 ret += digit * multby;
695 check_login: /* empty */
700 reply(530, "Please login with USER and PASS.");
709 extern int YYLEX_DECL();
712 extern jmp_buf errcatch;
714 static void upper(char *);
716 #define CMD 0 /* beginning of command */
717 #define ARGS 1 /* expect miscellaneous arguments */
718 #define STR1 2 /* expect SP followed by STRING */
719 #define STR2 3 /* expect STRING */
720 #define OSTR 4 /* optional SP then STRING */
721 #define ZSTR1 5 /* SP then optional STRING */
722 #define ZSTR2 6 /* optional STRING after SP */
723 #define SITECMD 7 /* SITE command */
724 #define NSTR 8 /* Number followed by a string */
726 struct tab cmdtab[] = { /* In order defined in RFC 765 */
727 { "USER", USER, STR1, 1, "<sp> username" },
728 { "PASS", PASS, ZSTR1, 1, "<sp> password" },
729 { "ACCT", ACCT, STR1, 0, "(specify account)" },
730 { "SMNT", SMNT, ARGS, 0, "(structure mount)" },
731 { "REIN", REIN, ARGS, 0, "(reinitialize server state)" },
732 { "QUIT", QUIT, ARGS, 1, "(terminate service)", },
733 { "PORT", PORT, ARGS, 1, "<sp> b0, b1, b2, b3, b4" },
734 { "PASV", PASV, ARGS, 1, "(set server in passive mode)" },
735 { "TYPE", TYPE, ARGS, 1, "<sp> [ A | E | I | L ]" },
736 { "STRU", STRU, ARGS, 1, "(specify file structure)" },
737 { "MODE", MODE, ARGS, 1, "(specify transfer mode)" },
738 { "RETR", RETR, STR1, 1, "<sp> file-name" },
739 { "STOR", STOR, STR1, 1, "<sp> file-name" },
740 { "APPE", APPE, STR1, 1, "<sp> file-name" },
741 { "MLFL", MLFL, OSTR, 0, "(mail file)" },
742 { "MAIL", MAIL, OSTR, 0, "(mail to user)" },
743 { "MSND", MSND, OSTR, 0, "(mail send to terminal)" },
744 { "MSOM", MSOM, OSTR, 0, "(mail send to terminal or mailbox)" },
745 { "MSAM", MSAM, OSTR, 0, "(mail send to terminal and mailbox)" },
746 { "MRSQ", MRSQ, OSTR, 0, "(mail recipient scheme question)" },
747 { "MRCP", MRCP, STR1, 0, "(mail recipient)" },
748 { "ALLO", ALLO, ARGS, 1, "allocate storage (vacuously)" },
749 { "REST", REST, ARGS, 0, "(restart command)" },
750 { "RNFR", RNFR, STR1, 1, "<sp> file-name" },
751 { "RNTO", RNTO, STR1, 1, "<sp> file-name" },
752 { "ABOR", ABOR, ARGS, 1, "(abort operation)" },
753 { "DELE", DELE, STR1, 1, "<sp> file-name" },
754 { "CWD", CWD, OSTR, 1, "[ <sp> directory-name ]" },
755 { "XCWD", CWD, OSTR, 1, "[ <sp> directory-name ]" },
756 { "LIST", LIST, OSTR, 1, "[ <sp> path-name ]" },
757 { "NLST", NLST, OSTR, 1, "[ <sp> path-name ]" },
758 { "SITE", SITE, SITECMD, 1, "site-cmd [ <sp> arguments ]" },
759 { "SYST", SYST, ARGS, 1, "(get type of operating system)" },
760 { "STAT", STAT, OSTR, 1, "[ <sp> path-name ]" },
761 { "HELP", HELP, OSTR, 1, "[ <sp> <string> ]" },
762 { "NOOP", NOOP, ARGS, 1, "" },
763 { "MKD", MKD, STR1, 1, "<sp> path-name" },
764 { "XMKD", MKD, STR1, 1, "<sp> path-name" },
765 { "RMD", RMD, STR1, 1, "<sp> path-name" },
766 { "XRMD", RMD, STR1, 1, "<sp> path-name" },
767 { "PWD", PWD, ARGS, 1, "(return current directory)" },
768 { "XPWD", PWD, ARGS, 1, "(return current directory)" },
769 { "CDUP", CDUP, ARGS, 1, "(change to parent directory)" },
770 { "XCUP", CDUP, ARGS, 1, "(change to parent directory)" },
771 { "STOU", STOU, STR1, 1, "<sp> file-name" },
772 { "SIZE", SIZE, OSTR, 1, "<sp> path-name" },
773 { "MDTM", MDTM, OSTR, 1, "<sp> path-name" },
777 struct tab sitetab[] = {
778 { "UMASK", UMASK, ARGS, 1, "[ <sp> umask ]" },
779 { "IDLE", IDLE, ARGS, 1, "[ <sp> maximum-idle-time ]" },
780 { "CHMOD", CHMOD, NSTR, 1, "<sp> mode <sp> file-name" },
781 { "HELP", HELP, OSTR, 1, "[ <sp> <string> ]" },
786 lookup(struct tab *p, char *cmd)
789 for (; p->name != 0; p++)
790 if (strcmp(cmd, p->name) == 0)
795 #include <arpa/telnet.h>
798 * get_line - a hacked up version of fgets to ignore TELNET escape codes.
801 get_line(char *s, int n, FILE *iop)
807 /* tmpline may contain saved command from urgent mode interruption */
808 for (c = 0; tmpline[c] != '\0' && --n > 0; ++c) {
810 if (tmpline[c] == '\n') {
813 syslog(LOG_DEBUG, "command: %s", s);
820 while ((c = getc(iop)) != EOF) {
823 if ((c = getc(iop)) != EOF) {
829 printf("%c%c%c", IAC, DONT, 0377&c);
830 (void) fflush(stdout);
835 printf("%c%c%c", IAC, WONT, 0377&c);
836 (void) fflush(stdout);
841 continue; /* ignore command */
846 if (--n <= 0 || c == '\n')
849 if (c == EOF && cs == s)
853 syslog(LOG_DEBUG, "command: %s", s);
864 "Timeout (%d seconds): closing control connection.", timeout);
868 "User %s timed out after %d seconds at %s",
869 (pw ? pw -> pw_name : "unknown"), timeout, ctime(&now));
877 static int cpos, state;
878 register char *cp, *cp2;
879 register struct tab *p;
887 (void) signal(SIGALRM, toolong);
888 (void) alarm((unsigned) timeout);
889 if (get_line(cbuf, sizeof(cbuf)-1, stdin) == 0) {
890 reply(221, "You could at least say goodbye.");
895 if (strncasecmp(cbuf, "PASS", 4) != 0)
896 setproctitle("%s: %s", proctitle, cbuf);
897 #endif /* SETPROCTITLE */
898 if ((cp = strchr(cbuf, '\r'))) {
902 if ((cp = strpbrk(cbuf, " \n")))
909 p = lookup(cmdtab, cbuf);
912 if (p->implemented == 0) {
918 *(const char **)(&yylval) = p->name;
924 if (cbuf[cpos] == ' ') {
929 if ((cp2 = strpbrk(cp, " \n")))
934 p = lookup(sitetab, cp);
937 if (p->implemented == 0) {
944 *(const char **)(&yylval) = p->name;
951 if (cbuf[cpos] == '\n') {
960 if (cbuf[cpos] == ' ') {
971 if (cbuf[cpos] == '\n') {
982 * Make sure the string is nonempty and \n terminated.
984 if (n > 1 && cbuf[cpos] == '\n') {
986 *(char **)&yylval = copy(cp);
994 if (cbuf[cpos] == ' ') {
998 if (isdigit(cbuf[cpos])) {
1000 while (isdigit(cbuf[++cpos]))
1013 if (isdigit(cbuf[cpos])) {
1015 while (isdigit(cbuf[++cpos]))
1023 switch (cbuf[cpos++]) {
1087 fatal("Unknown state in scanner.");
1089 yyerror((char *) 0);
1091 longjmp(errcatch,0);
1098 while (*s != '\0') {
1110 p = (char * )malloc(strlen(s) + 1);
1112 fatal("Ran out of memory.");
1114 (void) strcpy(p, s);
1119 help(struct tab *ctab, char *s)
1121 register struct tab *c;
1122 register int width, NCMDS;
1123 const char *help_type;
1125 if (ctab == sitetab)
1126 help_type = "SITE ";
1129 width = 0, NCMDS = 0;
1130 for (c = ctab; c->name != 0; c++) {
1131 int len = strlen(c->name);
1137 width = (width + 8) &~ 7;
1139 register int i, j, w;
1142 lreply(214, "The following %scommands are recognized %s.",
1143 help_type, "(* =>'s unimplemented)");
1144 columns = 76 / width;
1147 lines = (NCMDS + columns - 1) / columns;
1148 for (i = 0; i < lines; i++) {
1150 for (j = 0; j < columns; j++) {
1151 c = ctab + j * lines + i;
1152 assert(c->name != 0);
1153 printf("%s%c", c->name,
1154 c->implemented ? ' ' : '*');
1155 if (c + lines >= &ctab[NCMDS])
1157 w = strlen(c->name) + 1;
1165 (void) fflush(stdout);
1166 reply(214, "Direct comments to ftp-bugs@%s.", hostname);
1170 c = lookup(ctab, s);
1171 if (c == (struct tab *)0) {
1172 reply(502, "Unknown command %s.", s);
1176 reply(214, "Syntax: %s%s %s", help_type, c->name, c->help);
1178 reply(214, "%s%-*s\t%s; unimplemented.", help_type, width,
1183 sizecmd(char *filename)
1189 if (stat(filename, &stbuf) < 0 ||
1190 (stbuf.st_mode&S_IFMT) != S_IFREG)
1191 reply(550, "%s: not a plain file.", filename);
1193 #ifdef HAVE_LONG_LONG
1194 reply(213, "%llu", (long long) stbuf.st_size);
1196 reply(213, "%lu", stbuf.st_size);
1201 register int c, count;
1203 fin = fopen(filename, "r");
1205 perror_reply(550, filename);
1208 if (fstat(fileno(fin), &stbuf) < 0 ||
1209 (stbuf.st_mode&S_IFMT) != S_IFREG) {
1210 reply(550, "%s: not a plain file.", filename);
1216 while((c=getc(fin)) != EOF) {
1217 if (c == '\n') /* will get expanded to \r\n */
1223 reply(213, "%ld", count);
1226 reply(504, "SIZE not implemented for Type %c.", "?AEIL"[type]);