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
19 * @(#)ftpcmd.y 5.20.1.1 (Berkeley) 3/2/89
23 * Grammar for FTP commands.
29 #include <sys/param.h>
30 #include <sys/socket.h>
32 #include <netinet/in.h>
45 extern struct sockaddr_in data_dest;
47 extern struct passwd *pw;
54 extern int maxtimeout;
56 extern char hostname[], remotehost[];
57 extern char proctitle[];
59 extern int usedefault;
61 extern char tmpline[];
66 static int cmd_bytesz;
77 SP CRLF COMMA STRING NUMBER
79 USER PASS ACCT REIN QUIT PORT
80 PASV TYPE STRU MODE RETR STOR
81 APPE MLFL MAIL MSND MSOM MSAM
82 MRSQ MRCP ALLO REST RNFR RNTO
83 ABOR DELE CWD LIST NLST SITE
84 STAT HELP NOOP MKD RMD PWD
85 CDUP STOU SMNT SYST SIZE MDTM
98 fromname = (char *) 0;
103 cmd: USER SP username CRLF
108 | PASS SP password CRLF
113 | PORT SP host_port CRLF
120 reply(200, "PORT command successful.");
126 | TYPE SP type_code CRLF
131 if (cmd_form == FORM_N) {
132 reply(200, "Type set to A.");
136 reply(504, "Form must be N.");
140 reply(504, "Type E not implemented.");
144 reply(200, "Type set to I.");
150 if (cmd_bytesz == 8) {
152 "Type set to L (byte size 8).");
155 reply(504, "Byte size must be 8.");
156 #else /* NBBY == 8 */
157 UNIMPLEMENTED for NBBY != 8
158 #endif /* NBBY == 8 */
161 | STRU SP struct_code CRLF
166 reply(200, "STRU F ok.");
170 reply(504, "Unimplemented STRU type.");
173 | MODE SP mode_code CRLF
178 reply(200, "MODE S ok.");
182 reply(502, "Unimplemented MODE type.");
185 | ALLO SP NUMBER CRLF
187 reply(202, "ALLO command ignored.");
189 | ALLO SP NUMBER SP R SP NUMBER CRLF
191 reply(202, "ALLO command ignored.");
193 | RETR check_login SP pathname CRLF
195 if ($2 && $4 != NULL)
196 retrieve((char *) 0, (char *) $4);
200 | STOR check_login SP pathname CRLF
202 if ($2 && $4 != NULL)
203 store((char *) $4, "w", 0);
207 | APPE check_login SP pathname CRLF
209 if ($2 && $4 != NULL)
210 store((char *) $4, "a", 0);
214 | NLST check_login CRLF
219 | NLST check_login SP STRING CRLF
221 if ($2 && $4 != NULL)
222 send_file_list((char *) $4);
226 | LIST check_login CRLF
229 retrieve("/bin/ls -lgA", "");
231 | LIST check_login SP pathname CRLF
233 if ($2 && $4 != NULL)
234 retrieve("/bin/ls -lgA %s", (char *) $4);
238 | STAT check_login SP pathname CRLF
240 if ($2 && $4 != NULL)
241 statfilecmd((char *) $4);
249 | DELE check_login SP pathname CRLF
251 if ($2 && $4 != NULL)
256 | RNTO SP pathname CRLF
259 renamecmd(fromname, (char *) $3);
261 fromname = (char *) 0;
263 reply(503, "Bad sequence of commands.");
269 reply(225, "ABOR command successful.");
271 | CWD check_login CRLF
276 | CWD check_login SP pathname CRLF
278 if ($2 && $4 != NULL)
285 help(cmdtab, (char *) 0);
287 | HELP SP STRING CRLF
289 register char *cp = (char *)$3;
291 if (strncasecmp(cp, "SITE", 4) == 0) {
298 help(sitetab, (char *) 0);
300 help(cmdtab, (char *) $3);
304 reply(200, "NOOP command successful.");
306 | MKD check_login SP pathname CRLF
308 if ($2 && $4 != NULL)
309 makedir((char *) $4);
313 | RMD check_login SP pathname CRLF
315 if ($2 && $4 != NULL)
316 removedir((char *) $4);
320 | PWD check_login CRLF
325 | CDUP check_login CRLF
332 help(sitetab, (char *) 0);
334 | SITE SP HELP SP STRING CRLF
336 help(sitetab, (char *) $5);
338 | SITE SP UMASK check_login CRLF
344 (void) umask(oldmask);
345 reply(200, "Current UMASK is %03o", oldmask);
348 | SITE SP UMASK check_login SP octal_number CRLF
353 if (($6 == -1) || ($6 > 0777)) {
354 reply(501, "Bad UMASK value");
358 "UMASK set to %03o (was %03o)",
363 | SITE SP CHMOD check_login SP octal_number SP pathname CRLF
365 if ($4 && ($8 != NULL)) {
368 "CHMOD: Mode value must be between 0 and 0777");
369 else if (chmod((char *) $8, $6) < 0)
370 perror_reply(550, (char *) $8);
372 reply(200, "CHMOD command successful.");
380 "Current IDLE time limit is %d seconds; max %d",
381 timeout, maxtimeout);
383 | SITE SP IDLE SP NUMBER CRLF
385 if ($5 < 30 || $5 > maxtimeout) {
387 "Maximum IDLE time must be between 30 and %d seconds",
391 (void) alarm((unsigned) timeout);
393 "Maximum IDLE time set to %d seconds",
397 | STOU check_login SP pathname CRLF
399 if ($2 && $4 != NULL)
400 store((char *) $4, "w", 1);
408 reply(215, "UNIX Type: L%d Version: BSD-%d",
411 reply(215, "UNIX Type: L%d", NBBY);
414 reply(215, "UNKNOWN Type: L%d", NBBY);
419 * SIZE is not in RFC959, but Postel has blessed it and
420 * it will be in the updated RFC.
422 * Return size of file in a format suitable for
423 * using with RESTART (we just count bytes).
425 | SIZE check_login SP pathname CRLF
427 if ($2 && $4 != NULL)
428 sizecmd((char *) $4);
434 * MDTM is not in RFC959, but Postel has blessed it and
435 * it will be in the updated RFC.
437 * Return modification time of file as an ISO 3307
438 * style time. E.g. YYYYMMDDHHMMSS or YYYYMMDDHHMMSS.xxx
439 * where xxx is the fractional second (of any precision,
440 * not necessarily 3 digits)
442 | MDTM check_login SP pathname CRLF
444 if ($2 && $4 != NULL) {
446 if (stat((char *) $4, &stbuf) < 0)
447 perror_reply(550, "%s", (char *) $4);
448 else if ((stbuf.st_mode&S_IFMT) != S_IFREG) {
449 reply(550, "%s: not a plain file.",
452 register struct tm *t;
454 t = gmtime(&stbuf.st_mtime);
456 "%d%02d%02d%02d%02d%02d",
457 t->tm_year+1900, t->tm_mon+1, t->tm_mday,
458 t->tm_hour, t->tm_min, t->tm_sec);
466 reply(221, "Goodbye.");
474 rcmd: RNFR check_login SP pathname CRLF
479 fromname = renamefrom((char *) $4);
480 if (fromname == (char *) 0 && $4) {
490 password: /* empty */
492 *(char **)&($$) = "";
500 host_port: NUMBER COMMA NUMBER COMMA NUMBER COMMA NUMBER COMMA
503 register char *a, *p;
505 a = (char *)&data_dest.sin_addr;
506 a[0] = $1; a[1] = $3; a[2] = $5; a[3] = $7;
507 p = (char *)&data_dest.sin_port;
508 p[0] = $9; p[1] = $11;
509 data_dest.sin_family = AF_INET;
561 /* this is for a bug in the BBN ftp */
600 * Problem: this production is used for all pathname
601 * processing, but only gives a 550 error reply.
602 * This is a valid reply in some cases but not in others.
604 if (logged_in && $1 && strncmp((char *) $1, "~", 1) == 0) {
605 *(char **)&($$) = *glob((char *) $1);
606 if (globerr != NULL) {
621 register int ret, dec, multby, digit;
624 * Convert a number that was read as decimal number
625 * to what it would be if it had been read as octal.
636 ret += digit * multby;
644 check_login: /* empty */
649 reply(530, "Please login with USER and PASS.");
657 extern jmp_buf errcatch;
659 #define CMD 0 /* beginning of command */
660 #define ARGS 1 /* expect miscellaneous arguments */
661 #define STR1 2 /* expect SP followed by STRING */
662 #define STR2 3 /* expect STRING */
663 #define OSTR 4 /* optional SP then STRING */
664 #define ZSTR1 5 /* SP then optional STRING */
665 #define ZSTR2 6 /* optional STRING after SP */
666 #define SITECMD 7 /* SITE command */
667 #define NSTR 8 /* Number followed by a string */
673 short implemented; /* 1 if command is implemented */
677 struct tab cmdtab[] = { /* In order defined in RFC 765 */
678 { "USER", USER, STR1, 1, "<sp> username" },
679 { "PASS", PASS, ZSTR1, 1, "<sp> password" },
680 { "ACCT", ACCT, STR1, 0, "(specify account)" },
681 { "SMNT", SMNT, ARGS, 0, "(structure mount)" },
682 { "REIN", REIN, ARGS, 0, "(reinitialize server state)" },
683 { "QUIT", QUIT, ARGS, 1, "(terminate service)", },
684 { "PORT", PORT, ARGS, 1, "<sp> b0, b1, b2, b3, b4" },
685 { "PASV", PASV, ARGS, 1, "(set server in passive mode)" },
686 { "TYPE", TYPE, ARGS, 1, "<sp> [ A | E | I | L ]" },
687 { "STRU", STRU, ARGS, 1, "(specify file structure)" },
688 { "MODE", MODE, ARGS, 1, "(specify transfer mode)" },
689 { "RETR", RETR, STR1, 1, "<sp> file-name" },
690 { "STOR", STOR, STR1, 1, "<sp> file-name" },
691 { "APPE", APPE, STR1, 1, "<sp> file-name" },
692 { "MLFL", MLFL, OSTR, 0, "(mail file)" },
693 { "MAIL", MAIL, OSTR, 0, "(mail to user)" },
694 { "MSND", MSND, OSTR, 0, "(mail send to terminal)" },
695 { "MSOM", MSOM, OSTR, 0, "(mail send to terminal or mailbox)" },
696 { "MSAM", MSAM, OSTR, 0, "(mail send to terminal and mailbox)" },
697 { "MRSQ", MRSQ, OSTR, 0, "(mail recipient scheme question)" },
698 { "MRCP", MRCP, STR1, 0, "(mail recipient)" },
699 { "ALLO", ALLO, ARGS, 1, "allocate storage (vacuously)" },
700 { "REST", REST, ARGS, 0, "(restart command)" },
701 { "RNFR", RNFR, STR1, 1, "<sp> file-name" },
702 { "RNTO", RNTO, STR1, 1, "<sp> file-name" },
703 { "ABOR", ABOR, ARGS, 1, "(abort operation)" },
704 { "DELE", DELE, STR1, 1, "<sp> file-name" },
705 { "CWD", CWD, OSTR, 1, "[ <sp> directory-name ]" },
706 { "XCWD", CWD, OSTR, 1, "[ <sp> directory-name ]" },
707 { "LIST", LIST, OSTR, 1, "[ <sp> path-name ]" },
708 { "NLST", NLST, OSTR, 1, "[ <sp> path-name ]" },
709 { "SITE", SITE, SITECMD, 1, "site-cmd [ <sp> arguments ]" },
710 { "SYST", SYST, ARGS, 1, "(get type of operating system)" },
711 { "STAT", STAT, OSTR, 1, "[ <sp> path-name ]" },
712 { "HELP", HELP, OSTR, 1, "[ <sp> <string> ]" },
713 { "NOOP", NOOP, ARGS, 1, "" },
714 { "MKD", MKD, STR1, 1, "<sp> path-name" },
715 { "XMKD", MKD, STR1, 1, "<sp> path-name" },
716 { "RMD", RMD, STR1, 1, "<sp> path-name" },
717 { "XRMD", RMD, STR1, 1, "<sp> path-name" },
718 { "PWD", PWD, ARGS, 1, "(return current directory)" },
719 { "XPWD", PWD, ARGS, 1, "(return current directory)" },
720 { "CDUP", CDUP, ARGS, 1, "(change to parent directory)" },
721 { "XCUP", CDUP, ARGS, 1, "(change to parent directory)" },
722 { "STOU", STOU, STR1, 1, "<sp> file-name" },
723 { "SIZE", SIZE, OSTR, 1, "<sp> path-name" },
724 { "MDTM", MDTM, OSTR, 1, "<sp> path-name" },
728 struct tab sitetab[] = {
729 { "UMASK", UMASK, ARGS, 1, "[ <sp> umask ]" },
730 { "IDLE", IDLE, ARGS, 1, "[ <sp> maximum-idle-time ]" },
731 { "CHMOD", CHMOD, NSTR, 1, "<sp> mode <sp> file-name" },
732 { "HELP", HELP, OSTR, 1, "[ <sp> <string> ]" },
738 register struct tab *p;
742 for (; p->name != NULL; p++)
743 if (strcmp(cmd, p->name) == 0)
748 #include <arpa/telnet.h>
751 * getline - a hacked up version of fgets to ignore TELNET escape codes.
762 /* tmpline may contain saved command from urgent mode interruption */
763 for (c = 0; tmpline[c] != '\0' && --n > 0; ++c) {
765 if (tmpline[c] == '\n') {
768 syslog(LOG_DEBUG, "command: %s", s);
775 while ((c = getc(iop)) != EOF) {
778 if ((c = getc(iop)) != EOF) {
784 printf("%c%c%c", IAC, DONT, 0377&c);
785 (void) fflush(stdout);
790 printf("%c%c%c", IAC, WONT, 0377&c);
791 (void) fflush(stdout);
796 continue; /* ignore command */
801 if (--n <= 0 || c == '\n')
804 if (c == EOF && cs == s)
808 syslog(LOG_DEBUG, "command: %s", s);
816 extern char *ctime();
817 extern time_t time();
820 "Timeout (%d seconds): closing control connection.", timeout);
824 "User %s timed out after %d seconds at %s",
825 (pw ? pw -> pw_name : "unknown"), timeout, ctime(&now));
832 static int cpos, state;
833 register char *cp, *cp2;
834 register struct tab *p;
843 (void) signal(SIGALRM, toolong);
844 (void) alarm((unsigned) timeout);
845 if (getline(cbuf, sizeof(cbuf)-1, stdin) == NULL) {
846 reply(221, "You could at least say goodbye.");
851 if (strncasecmp(cbuf, "PASS", 4) != NULL)
852 setproctitle("%s: %s", proctitle, cbuf);
853 #endif /* SETPROCTITLE */
854 if ((cp = index(cbuf, '\r'))) {
858 if ((cp = strpbrk(cbuf, " \n")))
865 p = lookup(cmdtab, cbuf);
868 if (p->implemented == 0) {
874 *(char **)&yylval = p->name;
880 if (cbuf[cpos] == ' ') {
885 if ((cp2 = strpbrk(cp, " \n")))
890 p = lookup(sitetab, cp);
893 if (p->implemented == 0) {
900 *(char **)&yylval = p->name;
907 if (cbuf[cpos] == '\n') {
916 if (cbuf[cpos] == ' ') {
918 state = state == OSTR ? STR2 : ++state;
924 if (cbuf[cpos] == '\n') {
935 * Make sure the string is nonempty and \n terminated.
937 if (n > 1 && cbuf[cpos] == '\n') {
939 *(char **)&yylval = copy(cp);
947 if (cbuf[cpos] == ' ') {
951 if (isdigit(cbuf[cpos])) {
953 while (isdigit(cbuf[++cpos]))
966 if (isdigit(cbuf[cpos])) {
968 while (isdigit(cbuf[++cpos]))
976 switch (cbuf[cpos++]) {
1040 fatal("Unknown state in scanner.");
1042 yyerror((char *) 0);
1044 longjmp(errcatch,0);
1051 while (*s != '\0') {
1063 extern char *malloc(), *strcpy();
1065 p = malloc((unsigned) strlen(s) + 1);
1067 fatal("Ran out of memory.");
1068 (void) strcpy(p, s);
1076 register struct tab *c;
1077 register int width, NCMDS;
1080 if (ctab == sitetab)
1084 width = 0, NCMDS = 0;
1085 for (c = ctab; c->name != NULL; c++) {
1086 int len = strlen(c->name);
1092 width = (width + 8) &~ 7;
1094 register int i, j, w;
1097 lreply(214, "The following %scommands are recognized %s.",
1098 type, "(* =>'s unimplemented)");
1099 columns = 76 / width;
1102 lines = (NCMDS + columns - 1) / columns;
1103 for (i = 0; i < lines; i++) {
1105 for (j = 0; j < columns; j++) {
1106 c = ctab + j * lines + i;
1107 printf("%s%c", c->name,
1108 c->implemented ? ' ' : '*');
1109 if (c + lines >= &ctab[NCMDS])
1111 w = strlen(c->name) + 1;
1119 (void) fflush(stdout);
1120 reply(214, "Direct comments to ftp-bugs@%s.", hostname);
1124 c = lookup(ctab, s);
1125 if (c == (struct tab *)0) {
1126 reply(502, "Unknown command %s.", s);
1130 reply(214, "Syntax: %s%s %s", type, c->name, c->help);
1132 reply(214, "%s%-*s\t%s; unimplemented.", type, width,
1143 if (stat(filename, &stbuf) < 0 ||
1144 (stbuf.st_mode&S_IFMT) != S_IFREG)
1145 reply(550, "%s: not a plain file.", filename);
1147 reply(213, "%lu", stbuf.st_size);
1151 register int c, count;
1153 fin = fopen(filename, "r");
1155 perror_reply(550, filename);
1158 if (fstat(fileno(fin), &stbuf) < 0 ||
1159 (stbuf.st_mode&S_IFMT) != S_IFREG) {
1160 reply(550, "%s: not a plain file.", filename);
1166 while((c=getc(fin)) != EOF) {
1167 if (c == '\n') /* will get expanded to \r\n */
1173 reply(213, "%ld", count);
1176 reply(504, "SIZE not implemented for Type %c.", "?AEIL"[type]);