2 * Copyright (c) 1985, 1988, 1993, 1994
3 * The Regents of the University of California. All rights reserved.
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
8 * 1. Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 * notice, this list of conditions and the following disclaimer in the
12 * documentation and/or other materials provided with the distribution.
13 * 3. All advertising materials mentioning features or use of this software
14 * must display the following acknowledgement:
15 * This product includes software developed by the University of
16 * California, Berkeley and its contributors.
17 * 4. Neither the name of the University nor the names of its contributors
18 * may be used to endorse or promote products derived from this software
19 * without specific prior written permission.
21 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
22 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
24 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
25 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
26 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
27 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
28 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
29 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
30 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
33 * @(#)ftpcmd.y 8.3 (Berkeley) 4/6/94
37 * Grammar for FTP commands.
45 static char sccsid[] = "@(#)ftpcmd.y 8.3 (Berkeley) 4/6/94";
47 static const char rcsid[] =
51 #include <sys/param.h>
52 #include <sys/socket.h>
55 #include <netinet/in.h>
74 extern struct sockaddr_in data_dest, his_addr;
76 extern struct passwd *pw;
84 extern int maxtimeout;
86 extern char *hostname;
87 extern char remotehost[];
88 extern char proctitle[];
89 extern int usedefault;
91 extern char tmpline[];
97 static int cmd_bytesz;
114 USER PASS ACCT REIN QUIT PORT
115 PASV TYPE STRU MODE RETR STOR
116 APPE MLFL MAIL MSND MSOM MSAM
117 MRSQ MRCP ALLO REST RNFR RNTO
118 ABOR DELE CWD LIST NLST SITE
119 STAT HELP NOOP MKD RMD PWD
120 CDUP STOU SMNT SYST SIZE MDTM
129 %type <i> check_login octal_number byte_size
130 %type <i> struct_code mode_code type_code form_code
131 %type <s> pathstring pathname password username
141 fromname = (char *) 0;
142 restart_point = (off_t) 0;
148 : USER SP username CRLF
153 | PASS SP password CRLF
158 | PORT check_login SP host_port CRLF
162 ((ntohs(data_dest.sin_port) <
164 memcmp(&data_dest.sin_addr,
166 sizeof(data_dest.sin_addr)))) {
169 "Illegal PORT range rejected.");
176 reply(200, "PORT command successful.");
180 | PASV check_login CRLF
185 | TYPE SP type_code CRLF
190 if (cmd_form == FORM_N) {
191 reply(200, "Type set to A.");
195 reply(504, "Form must be N.");
199 reply(504, "Type E not implemented.");
203 reply(200, "Type set to I.");
209 if (cmd_bytesz == 8) {
211 "Type set to L (byte size 8).");
214 reply(504, "Byte size must be 8.");
215 #else /* NBBY == 8 */
216 UNIMPLEMENTED for NBBY != 8
217 #endif /* NBBY == 8 */
220 | STRU SP struct_code CRLF
225 reply(200, "STRU F ok.");
229 reply(504, "Unimplemented STRU type.");
232 | MODE SP mode_code CRLF
237 reply(200, "MODE S ok.");
241 reply(502, "Unimplemented MODE type.");
244 | ALLO SP NUMBER CRLF
246 reply(202, "ALLO command ignored.");
248 | ALLO SP NUMBER SP R SP NUMBER CRLF
250 reply(202, "ALLO command ignored.");
252 | RETR check_login SP pathname CRLF
254 if ($2 && $4 != NULL)
255 retrieve((char *) 0, $4);
259 | STOR check_login SP pathname CRLF
261 if ($2 && $4 != NULL)
266 | APPE check_login SP pathname CRLF
268 if ($2 && $4 != NULL)
273 | NLST check_login CRLF
278 | NLST check_login SP STRING CRLF
280 if ($2 && $4 != NULL)
285 | LIST check_login CRLF
288 retrieve("/bin/ls -lgA", "");
290 | LIST check_login SP pathname CRLF
292 if ($2 && $4 != NULL)
293 retrieve("/bin/ls -lgA %s", $4);
297 | STAT check_login SP pathname CRLF
299 if ($2 && $4 != NULL)
308 | DELE check_login SP pathname CRLF
310 if ($2 && $4 != NULL)
315 | RNTO check_login SP pathname CRLF
319 renamecmd(fromname, $4);
321 fromname = (char *) 0;
323 reply(503, "Bad sequence of commands.");
330 reply(225, "ABOR command successful.");
332 | CWD check_login CRLF
337 | CWD check_login SP pathname CRLF
339 if ($2 && $4 != NULL)
346 help(cmdtab, (char *) 0);
348 | HELP SP STRING CRLF
352 if (strncasecmp(cp, "SITE", 4) == 0) {
359 help(sitetab, (char *) 0);
365 reply(200, "NOOP command successful.");
367 | MKD check_login SP pathname CRLF
369 if ($2 && $4 != NULL)
374 | RMD check_login SP pathname CRLF
376 if ($2 && $4 != NULL)
381 | PWD check_login CRLF
386 | CDUP check_login CRLF
393 help(sitetab, (char *) 0);
395 | SITE SP HELP SP STRING CRLF
399 | SITE SP UMASK check_login CRLF
405 (void) umask(oldmask);
406 reply(200, "Current UMASK is %03o", oldmask);
409 | SITE SP UMASK check_login SP octal_number CRLF
414 if (($6 == -1) || ($6 > 0777)) {
415 reply(501, "Bad UMASK value");
419 "UMASK set to %03o (was %03o)",
424 | SITE SP CHMOD check_login SP octal_number SP pathname CRLF
426 if ($4 && ($8 != NULL)) {
429 "CHMOD: Mode value must be between 0 and 0777");
430 else if (chmod($8, $6) < 0)
431 perror_reply(550, $8);
433 reply(200, "CHMOD command successful.");
441 "Current IDLE time limit is %d seconds; max %d",
442 timeout, maxtimeout);
444 | SITE SP IDLE SP NUMBER CRLF
446 if ($5 < 30 || $5 > maxtimeout) {
448 "Maximum IDLE time must be between 30 and %d seconds",
452 (void) alarm((unsigned) timeout);
454 "Maximum IDLE time set to %d seconds",
458 | STOU check_login SP pathname CRLF
460 if ($2 && $4 != NULL)
469 reply(215, "UNIX Type: L%d Version: BSD-%d",
472 reply(215, "UNIX Type: L%d", NBBY);
475 reply(215, "UNKNOWN Type: L%d", NBBY);
480 * SIZE is not in RFC959, but Postel has blessed it and
481 * it will be in the updated RFC.
483 * Return size of file in a format suitable for
484 * using with RESTART (we just count bytes).
486 | SIZE check_login SP pathname CRLF
488 if ($2 && $4 != NULL)
495 * MDTM is not in RFC959, but Postel has blessed it and
496 * it will be in the updated RFC.
498 * Return modification time of file as an ISO 3307
499 * style time. E.g. YYYYMMDDHHMMSS or YYYYMMDDHHMMSS.xxx
500 * where xxx is the fractional second (of any precision,
501 * not necessarily 3 digits)
503 | MDTM check_login SP pathname CRLF
505 if ($2 && $4 != NULL) {
507 if (stat($4, &stbuf) < 0)
509 $4, strerror(errno));
510 else if (!S_ISREG(stbuf.st_mode)) {
511 reply(550, "%s: not a plain file.", $4);
514 t = gmtime(&stbuf.st_mtime);
516 "%04d%02d%02d%02d%02d%02d",
518 t->tm_mon+1, t->tm_mday,
519 t->tm_hour, t->tm_min, t->tm_sec);
527 reply(221, "Goodbye.");
536 : RNFR check_login SP pathname CRLF
540 restart_point = (off_t) 0;
542 fromname = renamefrom($4);
543 if (fromname == (char *) 0 && $4) {
548 | REST SP byte_size CRLF
550 fromname = (char *) 0;
551 restart_point = $3; /* XXX $3 is only "int" */
552 reply(350, "Restarting at %qd. %s", restart_point,
553 "Send STORE or RETRIEVE to initiate transfer.");
564 $$ = (char *)calloc(1, sizeof(char));
574 : NUMBER COMMA NUMBER COMMA NUMBER COMMA NUMBER COMMA
579 data_dest.sin_len = sizeof(struct sockaddr_in);
580 data_dest.sin_family = AF_INET;
581 p = (char *)&data_dest.sin_port;
582 p[0] = $9; p[1] = $11;
583 a = (char *)&data_dest.sin_addr;
584 a[0] = $1; a[1] = $3; a[2] = $5; a[3] = $7;
638 /* this is for a bug in the BBN ftp */
680 * Problem: this production is used for all pathname
681 * processing, but only gives a 550 error reply.
682 * This is a valid reply in some cases but not in others.
684 if (logged_in && $1 && *$1 == '~') {
687 GLOB_BRACE|GLOB_NOCHECK|GLOB_QUOTE|GLOB_TILDE;
689 memset(&gl, 0, sizeof(gl));
690 if (glob($1, flags, NULL, &gl) ||
692 reply(550, "not found");
695 $$ = strdup(gl.gl_pathv[0]);
711 int ret, dec, multby, digit;
714 * Convert a number that was read as decimal number
715 * to what it would be if it had been read as octal.
726 ret += digit * multby;
741 reply(530, "Please login with USER and PASS.");
749 extern jmp_buf errcatch;
751 #define CMD 0 /* beginning of command */
752 #define ARGS 1 /* expect miscellaneous arguments */
753 #define STR1 2 /* expect SP followed by STRING */
754 #define STR2 3 /* expect STRING */
755 #define OSTR 4 /* optional SP then STRING */
756 #define ZSTR1 5 /* SP then optional STRING */
757 #define ZSTR2 6 /* optional STRING after SP */
758 #define SITECMD 7 /* SITE command */
759 #define NSTR 8 /* Number followed by a string */
765 short implemented; /* 1 if command is implemented */
769 struct tab cmdtab[] = { /* In order defined in RFC 765 */
770 { "USER", USER, STR1, 1, "<sp> username" },
771 { "PASS", PASS, ZSTR1, 1, "<sp> password" },
772 { "ACCT", ACCT, STR1, 0, "(specify account)" },
773 { "SMNT", SMNT, ARGS, 0, "(structure mount)" },
774 { "REIN", REIN, ARGS, 0, "(reinitialize server state)" },
775 { "QUIT", QUIT, ARGS, 1, "(terminate service)", },
776 { "PORT", PORT, ARGS, 1, "<sp> b0, b1, b2, b3, b4" },
777 { "PASV", PASV, ARGS, 1, "(set server in passive mode)" },
778 { "TYPE", TYPE, ARGS, 1, "<sp> [ A | E | I | L ]" },
779 { "STRU", STRU, ARGS, 1, "(specify file structure)" },
780 { "MODE", MODE, ARGS, 1, "(specify transfer mode)" },
781 { "RETR", RETR, STR1, 1, "<sp> file-name" },
782 { "STOR", STOR, STR1, 1, "<sp> file-name" },
783 { "APPE", APPE, STR1, 1, "<sp> file-name" },
784 { "MLFL", MLFL, OSTR, 0, "(mail file)" },
785 { "MAIL", MAIL, OSTR, 0, "(mail to user)" },
786 { "MSND", MSND, OSTR, 0, "(mail send to terminal)" },
787 { "MSOM", MSOM, OSTR, 0, "(mail send to terminal or mailbox)" },
788 { "MSAM", MSAM, OSTR, 0, "(mail send to terminal and mailbox)" },
789 { "MRSQ", MRSQ, OSTR, 0, "(mail recipient scheme question)" },
790 { "MRCP", MRCP, STR1, 0, "(mail recipient)" },
791 { "ALLO", ALLO, ARGS, 1, "allocate storage (vacuously)" },
792 { "REST", REST, ARGS, 1, "<sp> offset (restart command)" },
793 { "RNFR", RNFR, STR1, 1, "<sp> file-name" },
794 { "RNTO", RNTO, STR1, 1, "<sp> file-name" },
795 { "ABOR", ABOR, ARGS, 1, "(abort operation)" },
796 { "DELE", DELE, STR1, 1, "<sp> file-name" },
797 { "CWD", CWD, OSTR, 1, "[ <sp> directory-name ]" },
798 { "XCWD", CWD, OSTR, 1, "[ <sp> directory-name ]" },
799 { "LIST", LIST, OSTR, 1, "[ <sp> path-name ]" },
800 { "NLST", NLST, OSTR, 1, "[ <sp> path-name ]" },
801 { "SITE", SITE, SITECMD, 1, "site-cmd [ <sp> arguments ]" },
802 { "SYST", SYST, ARGS, 1, "(get type of operating system)" },
803 { "STAT", STAT, OSTR, 1, "[ <sp> path-name ]" },
804 { "HELP", HELP, OSTR, 1, "[ <sp> <string> ]" },
805 { "NOOP", NOOP, ARGS, 1, "" },
806 { "MKD", MKD, STR1, 1, "<sp> path-name" },
807 { "XMKD", MKD, STR1, 1, "<sp> path-name" },
808 { "RMD", RMD, STR1, 1, "<sp> path-name" },
809 { "XRMD", RMD, STR1, 1, "<sp> path-name" },
810 { "PWD", PWD, ARGS, 1, "(return current directory)" },
811 { "XPWD", PWD, ARGS, 1, "(return current directory)" },
812 { "CDUP", CDUP, ARGS, 1, "(change to parent directory)" },
813 { "XCUP", CDUP, ARGS, 1, "(change to parent directory)" },
814 { "STOU", STOU, STR1, 1, "<sp> file-name" },
815 { "SIZE", SIZE, OSTR, 1, "<sp> path-name" },
816 { "MDTM", MDTM, OSTR, 1, "<sp> path-name" },
820 struct tab sitetab[] = {
821 { "UMASK", UMASK, ARGS, 1, "[ <sp> umask ]" },
822 { "IDLE", IDLE, ARGS, 1, "[ <sp> maximum-idle-time ]" },
823 { "CHMOD", CHMOD, NSTR, 1, "<sp> mode <sp> file-name" },
824 { "HELP", HELP, OSTR, 1, "[ <sp> <string> ]" },
828 static char *copy __P((char *));
829 static void help __P((struct tab *, char *));
831 lookup __P((struct tab *, char *));
832 static void sizecmd __P((char *));
833 static void toolong __P((int));
834 static int yylex __P((void));
842 for (; p->name != NULL; p++)
843 if (strcmp(cmd, p->name) == 0)
848 #include <arpa/telnet.h>
851 * getline - a hacked up version of fgets to ignore TELNET escape codes.
863 /* tmpline may contain saved command from urgent mode interruption */
864 for (c = 0; tmpline[c] != '\0' && --n > 0; ++c) {
866 if (tmpline[c] == '\n') {
869 syslog(LOG_DEBUG, "command: %s", s);
876 while ((c = getc(iop)) != EOF) {
879 if ((c = getc(iop)) != EOF) {
885 printf("%c%c%c", IAC, DONT, 0377&c);
886 (void) fflush(stdout);
891 printf("%c%c%c", IAC, WONT, 0377&c);
892 (void) fflush(stdout);
897 continue; /* ignore command */
902 if (--n <= 0 || c == '\n')
905 if (c == EOF && cs == s)
909 if (!guest && strncasecmp("pass ", s, 5) == 0) {
910 /* Don't syslog passwords */
911 syslog(LOG_DEBUG, "command: %.5s ???", s);
916 /* Don't syslog trailing CR-LF */
919 while (cp >= s && (*cp == '\n' || *cp == '\r')) {
923 syslog(LOG_DEBUG, "command: %.*s", len, s);
935 "Timeout (%d seconds): closing control connection.", timeout);
937 syslog(LOG_INFO, "User %s timed out after %d seconds",
938 (pw ? pw -> pw_name : "unknown"), timeout);
945 static int cpos, state;
955 (void) signal(SIGALRM, toolong);
956 (void) alarm((unsigned) timeout);
957 if (getline(cbuf, sizeof(cbuf)-1, stdin) == NULL) {
958 reply(221, "You could at least say goodbye.");
963 if (strncasecmp(cbuf, "PASS", 4) != 0)
964 setproctitle("%s: %s", proctitle, cbuf);
965 #endif /* SETPROCTITLE */
966 if ((cp = strchr(cbuf, '\r'))) {
970 if ((cp = strpbrk(cbuf, " \n")))
977 p = lookup(cmdtab, cbuf);
980 if (p->implemented == 0) {
992 if (cbuf[cpos] == ' ') {
997 if ((cp2 = strpbrk(cp, " \n")))
1002 p = lookup(sitetab, cp);
1004 if (guest == 0 && p != 0) {
1005 if (p->implemented == 0) {
1008 longjmp(errcatch,0);
1019 if (cbuf[cpos] == '\n') {
1028 if (cbuf[cpos] == ' ') {
1030 state = state == OSTR ? STR2 : ++state;
1036 if (cbuf[cpos] == '\n') {
1047 * Make sure the string is nonempty and \n terminated.
1049 if (n > 1 && cbuf[cpos] == '\n') {
1051 yylval.s = copy(cp);
1059 if (cbuf[cpos] == ' ') {
1063 if (isdigit(cbuf[cpos])) {
1065 while (isdigit(cbuf[++cpos]))
1069 yylval.i = atoi(cp);
1078 if (isdigit(cbuf[cpos])) {
1080 while (isdigit(cbuf[++cpos]))
1084 yylval.i = atoi(cp);
1088 switch (cbuf[cpos++]) {
1152 fatal("Unknown state in scanner.");
1154 yyerror((char *) 0);
1156 longjmp(errcatch,0);
1164 while (*s != '\0') {
1177 p = malloc((unsigned) strlen(s) + 1);
1179 fatal("Ran out of memory.");
1180 (void) strcpy(p, s);
1193 if (ctab == sitetab)
1197 width = 0, NCMDS = 0;
1198 for (c = ctab; c->name != NULL; c++) {
1199 int len = strlen(c->name);
1205 width = (width + 8) &~ 7;
1210 lreply(214, "The following %scommands are recognized %s.",
1211 type, "(* =>'s unimplemented)");
1212 columns = 76 / width;
1215 lines = (NCMDS + columns - 1) / columns;
1216 for (i = 0; i < lines; i++) {
1218 for (j = 0; j < columns; j++) {
1219 c = ctab + j * lines + i;
1220 printf("%s%c", c->name,
1221 c->implemented ? ' ' : '*');
1222 if (c + lines >= &ctab[NCMDS])
1224 w = strlen(c->name) + 1;
1232 (void) fflush(stdout);
1233 reply(214, "Direct comments to ftp-bugs@%s.", hostname);
1237 c = lookup(ctab, s);
1238 if (c == (struct tab *)0) {
1239 reply(502, "Unknown command %s.", s);
1243 reply(214, "Syntax: %s%s %s", type, c->name, c->help);
1245 reply(214, "%s%-*s\t%s; unimplemented.", type, width,
1257 if (stat(filename, &stbuf) < 0 || !S_ISREG(stbuf.st_mode))
1258 reply(550, "%s: not a plain file.", filename);
1260 reply(213, "%qu", stbuf.st_size);
1267 fin = fopen(filename, "r");
1269 perror_reply(550, filename);
1272 if (fstat(fileno(fin), &stbuf) < 0 || !S_ISREG(stbuf.st_mode)) {
1273 reply(550, "%s: not a plain file.", filename);
1279 while((c=getc(fin)) != EOF) {
1280 if (c == '\n') /* will get expanded to \r\n */
1286 reply(213, "%qd", count);
1289 reply(504, "SIZE not implemented for Type %c.", "?AEIL"[type]);