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.
44 static char sccsid[] = "@(#)ftpcmd.y 8.3 (Berkeley) 4/6/94";
47 #include <sys/param.h>
48 #include <sys/socket.h>
51 #include <netinet/in.h>
69 extern struct sockaddr_in data_dest;
71 extern struct passwd *pw;
78 extern int maxtimeout;
80 extern char hostname[], remotehost[];
81 extern char proctitle[];
82 extern int usedefault;
84 extern char tmpline[];
90 static int cmd_bytesz;
107 USER PASS ACCT REIN QUIT PORT
108 PASV TYPE STRU MODE RETR STOR
109 APPE MLFL MAIL MSND MSOM MSAM
110 MRSQ MRCP ALLO REST RNFR RNTO
111 ABOR DELE CWD LIST NLST SITE
112 STAT HELP NOOP MKD RMD PWD
113 CDUP STOU SMNT SYST SIZE MDTM
122 %type <i> check_login octal_number byte_size
123 %type <i> struct_code mode_code type_code form_code
124 %type <s> pathstring pathname password username
134 fromname = (char *) 0;
135 restart_point = (off_t) 0;
141 : USER SP username CRLF
146 | PASS SP password CRLF
151 | PORT SP host_port CRLF
158 reply(200, "PORT command successful.");
164 | TYPE SP type_code CRLF
169 if (cmd_form == FORM_N) {
170 reply(200, "Type set to A.");
174 reply(504, "Form must be N.");
178 reply(504, "Type E not implemented.");
182 reply(200, "Type set to I.");
188 if (cmd_bytesz == 8) {
190 "Type set to L (byte size 8).");
193 reply(504, "Byte size must be 8.");
194 #else /* NBBY == 8 */
195 UNIMPLEMENTED for NBBY != 8
196 #endif /* NBBY == 8 */
199 | STRU SP struct_code CRLF
204 reply(200, "STRU F ok.");
208 reply(504, "Unimplemented STRU type.");
211 | MODE SP mode_code CRLF
216 reply(200, "MODE S ok.");
220 reply(502, "Unimplemented MODE type.");
223 | ALLO SP NUMBER CRLF
225 reply(202, "ALLO command ignored.");
227 | ALLO SP NUMBER SP R SP NUMBER CRLF
229 reply(202, "ALLO command ignored.");
231 | RETR check_login SP pathname CRLF
233 if ($2 && $4 != NULL)
234 retrieve((char *) 0, $4);
238 | STOR check_login SP pathname CRLF
240 if ($2 && $4 != NULL)
245 | APPE check_login SP pathname CRLF
247 if ($2 && $4 != NULL)
252 | NLST check_login CRLF
257 | NLST check_login SP STRING CRLF
259 if ($2 && $4 != NULL)
264 | LIST check_login CRLF
267 retrieve("/bin/ls -lgA", "");
269 | LIST check_login SP pathname CRLF
271 if ($2 && $4 != NULL)
272 retrieve("/bin/ls -lgA %s", $4);
276 | STAT check_login SP pathname CRLF
278 if ($2 && $4 != NULL)
287 | DELE check_login SP pathname CRLF
289 if ($2 && $4 != NULL)
294 | RNTO SP pathname CRLF
297 renamecmd(fromname, $3);
299 fromname = (char *) 0;
301 reply(503, "Bad sequence of commands.");
307 reply(225, "ABOR command successful.");
309 | CWD check_login CRLF
314 | CWD check_login SP pathname CRLF
316 if ($2 && $4 != NULL)
323 help(cmdtab, (char *) 0);
325 | HELP SP STRING CRLF
329 if (strncasecmp(cp, "SITE", 4) == 0) {
336 help(sitetab, (char *) 0);
342 reply(200, "NOOP command successful.");
344 | MKD check_login SP pathname CRLF
346 if ($2 && $4 != NULL)
351 | RMD check_login SP pathname CRLF
353 if ($2 && $4 != NULL)
358 | PWD check_login CRLF
363 | CDUP check_login CRLF
370 help(sitetab, (char *) 0);
372 | SITE SP HELP SP STRING CRLF
376 | SITE SP UMASK check_login CRLF
382 (void) umask(oldmask);
383 reply(200, "Current UMASK is %03o", oldmask);
386 | SITE SP UMASK check_login SP octal_number CRLF
391 if (($6 == -1) || ($6 > 0777)) {
392 reply(501, "Bad UMASK value");
396 "UMASK set to %03o (was %03o)",
401 | SITE SP CHMOD check_login SP octal_number SP pathname CRLF
403 if ($4 && ($8 != NULL)) {
406 "CHMOD: Mode value must be between 0 and 0777");
407 else if (chmod($8, $6) < 0)
408 perror_reply(550, $8);
410 reply(200, "CHMOD command successful.");
418 "Current IDLE time limit is %d seconds; max %d",
419 timeout, maxtimeout);
421 | SITE SP IDLE SP NUMBER CRLF
423 if ($5 < 30 || $5 > maxtimeout) {
425 "Maximum IDLE time must be between 30 and %d seconds",
429 (void) alarm((unsigned) timeout);
431 "Maximum IDLE time set to %d seconds",
435 | STOU check_login SP pathname CRLF
437 if ($2 && $4 != NULL)
445 reply(215, "BSD Type: L%d", NBBY);
449 reply(215, "UNIX Type: L%d Version: BSD-%d", NBBY, BSD);
451 reply(215, "UNIX Type: L%d", NBBY);
454 reply(215, "UNKNOWN Type: L%d", NBBY);
456 #endif /* __APPLE__ */
460 * SIZE is not in RFC959, but Postel has blessed it and
461 * it will be in the updated RFC.
463 * Return size of file in a format suitable for
464 * using with RESTART (we just count bytes).
466 | SIZE check_login SP pathname CRLF
468 if ($2 && $4 != NULL)
475 * MDTM is not in RFC959, but Postel has blessed it and
476 * it will be in the updated RFC.
478 * Return modification time of file as an ISO 3307
479 * style time. E.g. YYYYMMDDHHMMSS or YYYYMMDDHHMMSS.xxx
480 * where xxx is the fractional second (of any precision,
481 * not necessarily 3 digits)
483 | MDTM check_login SP pathname CRLF
485 if ($2 && $4 != NULL) {
487 if (stat($4, &stbuf) < 0)
489 $4, strerror(errno));
490 else if (!S_ISREG(stbuf.st_mode)) {
491 reply(550, "%s: not a plain file.", $4);
494 t = gmtime(&stbuf.st_mtime);
496 "%04d%02d%02d%02d%02d%02d",
497 1900+t->tm_year, t->tm_mon+1, t->tm_mday,
498 t->tm_hour, t->tm_min, t->tm_sec);
506 reply(221, "Goodbye.");
515 : RNFR check_login SP pathname CRLF
519 restart_point = (off_t) 0;
521 fromname = renamefrom($4);
522 if (fromname == (char *) 0 && $4) {
527 | REST SP byte_size CRLF
529 fromname = (char *) 0;
530 restart_point = $3; /* XXX $3 is only "int" */
531 reply(350, "Restarting at %qd. %s", restart_point,
532 "Send STORE or RETRIEVE to initiate transfer.");
543 $$ = (char *)calloc(1, sizeof(char));
553 : NUMBER COMMA NUMBER COMMA NUMBER COMMA NUMBER COMMA
558 a = (char *)&data_dest.sin_addr;
559 a[0] = $1; a[1] = $3; a[2] = $5; a[3] = $7;
560 p = (char *)&data_dest.sin_port;
561 p[0] = $9; p[1] = $11;
562 data_dest.sin_family = AF_INET;
616 /* this is for a bug in the BBN ftp */
658 * Problem: this production is used for all pathname
659 * processing, but only gives a 550 error reply.
660 * This is a valid reply in some cases but not in others.
662 if (logged_in && $1 && *$1 == '~') {
665 GLOB_BRACE|GLOB_NOCHECK|GLOB_QUOTE|GLOB_TILDE;
667 memset(&gl, 0, sizeof(gl));
668 if (glob($1, flags, NULL, &gl) ||
670 reply(550, "not found");
673 $$ = strdup(gl.gl_pathv[0]);
689 int ret, dec, multby, digit;
692 * Convert a number that was read as decimal number
693 * to what it would be if it had been read as octal.
704 ret += digit * multby;
719 reply(530, "Please login with USER and PASS.");
727 extern jmp_buf errcatch;
729 #define CMD 0 /* beginning of command */
730 #define ARGS 1 /* expect miscellaneous arguments */
731 #define STR1 2 /* expect SP followed by STRING */
732 #define STR2 3 /* expect STRING */
733 #define OSTR 4 /* optional SP then STRING */
734 #define ZSTR1 5 /* SP then optional STRING */
735 #define ZSTR2 6 /* optional STRING after SP */
736 #define SITECMD 7 /* SITE command */
737 #define NSTR 8 /* Number followed by a string */
743 short implemented; /* 1 if command is implemented */
747 struct tab cmdtab[] = { /* In order defined in RFC 765 */
748 { "USER", USER, STR1, 1, "<sp> username" },
749 { "PASS", PASS, ZSTR1, 1, "<sp> password" },
750 { "ACCT", ACCT, STR1, 0, "(specify account)" },
751 { "SMNT", SMNT, ARGS, 0, "(structure mount)" },
752 { "REIN", REIN, ARGS, 0, "(reinitialize server state)" },
753 { "QUIT", QUIT, ARGS, 1, "(terminate service)", },
754 { "PORT", PORT, ARGS, 1, "<sp> b0, b1, b2, b3, b4" },
755 { "PASV", PASV, ARGS, 1, "(set server in passive mode)" },
756 { "TYPE", TYPE, ARGS, 1, "<sp> [ A | E | I | L ]" },
757 { "STRU", STRU, ARGS, 1, "(specify file structure)" },
758 { "MODE", MODE, ARGS, 1, "(specify transfer mode)" },
759 { "RETR", RETR, STR1, 1, "<sp> file-name" },
760 { "STOR", STOR, STR1, 1, "<sp> file-name" },
761 { "APPE", APPE, STR1, 1, "<sp> file-name" },
762 { "MLFL", MLFL, OSTR, 0, "(mail file)" },
763 { "MAIL", MAIL, OSTR, 0, "(mail to user)" },
764 { "MSND", MSND, OSTR, 0, "(mail send to terminal)" },
765 { "MSOM", MSOM, OSTR, 0, "(mail send to terminal or mailbox)" },
766 { "MSAM", MSAM, OSTR, 0, "(mail send to terminal and mailbox)" },
767 { "MRSQ", MRSQ, OSTR, 0, "(mail recipient scheme question)" },
768 { "MRCP", MRCP, STR1, 0, "(mail recipient)" },
769 { "ALLO", ALLO, ARGS, 1, "allocate storage (vacuously)" },
770 { "REST", REST, ARGS, 1, "<sp> offset (restart command)" },
771 { "RNFR", RNFR, STR1, 1, "<sp> file-name" },
772 { "RNTO", RNTO, STR1, 1, "<sp> file-name" },
773 { "ABOR", ABOR, ARGS, 1, "(abort operation)" },
774 { "DELE", DELE, STR1, 1, "<sp> file-name" },
775 { "CWD", CWD, OSTR, 1, "[ <sp> directory-name ]" },
776 { "XCWD", CWD, OSTR, 1, "[ <sp> directory-name ]" },
777 { "LIST", LIST, OSTR, 1, "[ <sp> path-name ]" },
778 { "NLST", NLST, OSTR, 1, "[ <sp> path-name ]" },
779 { "SITE", SITE, SITECMD, 1, "site-cmd [ <sp> arguments ]" },
780 { "SYST", SYST, ARGS, 1, "(get type of operating system)" },
781 { "STAT", STAT, OSTR, 1, "[ <sp> path-name ]" },
782 { "HELP", HELP, OSTR, 1, "[ <sp> <string> ]" },
783 { "NOOP", NOOP, ARGS, 1, "" },
784 { "MKD", MKD, STR1, 1, "<sp> path-name" },
785 { "XMKD", MKD, STR1, 1, "<sp> path-name" },
786 { "RMD", RMD, STR1, 1, "<sp> path-name" },
787 { "XRMD", RMD, STR1, 1, "<sp> path-name" },
788 { "PWD", PWD, ARGS, 1, "(return current directory)" },
789 { "XPWD", PWD, ARGS, 1, "(return current directory)" },
790 { "CDUP", CDUP, ARGS, 1, "(change to parent directory)" },
791 { "XCUP", CDUP, ARGS, 1, "(change to parent directory)" },
792 { "STOU", STOU, STR1, 1, "<sp> file-name" },
793 { "SIZE", SIZE, OSTR, 1, "<sp> path-name" },
794 { "MDTM", MDTM, OSTR, 1, "<sp> path-name" },
798 struct tab sitetab[] = {
799 { "UMASK", UMASK, ARGS, 1, "[ <sp> umask ]" },
800 { "IDLE", IDLE, ARGS, 1, "[ <sp> maximum-idle-time ]" },
801 { "CHMOD", CHMOD, NSTR, 1, "<sp> mode <sp> file-name" },
802 { "HELP", HELP, OSTR, 1, "[ <sp> <string> ]" },
806 static char *copy __P((char *));
807 static void help __P((struct tab *, char *));
809 lookup __P((struct tab *, char *));
810 static void sizecmd __P((char *));
811 static void toolong __P((int));
812 static int yylex __P((void));
820 for (; p->name != NULL; p++)
821 if (strcmp(cmd, p->name) == 0)
826 #include <arpa/telnet.h>
829 * getline - a hacked up version of fgets to ignore TELNET escape codes.
841 /* tmpline may contain saved command from urgent mode interruption */
842 for (c = 0; tmpline[c] != '\0' && --n > 0; ++c) {
844 if (tmpline[c] == '\n') {
847 syslog(LOG_DEBUG, "command: %s", s);
854 while ((c = getc(iop)) != EOF) {
857 if ((c = getc(iop)) != EOF) {
863 printf("%c%c%c", IAC, DONT, 0377&c);
864 (void) fflush(stdout);
869 printf("%c%c%c", IAC, WONT, 0377&c);
870 (void) fflush(stdout);
875 continue; /* ignore command */
880 if (--n <= 0 || c == '\n')
883 if (c == EOF && cs == s)
887 if (!guest && strncasecmp("pass ", s, 5) == 0) {
888 /* Don't syslog passwords */
889 syslog(LOG_DEBUG, "command: %.5s ???", s);
894 /* Don't syslog trailing CR-LF */
897 while (cp >= s && (*cp == '\n' || *cp == '\r')) {
901 syslog(LOG_DEBUG, "command: %.*s", len, s);
913 "Timeout (%d seconds): closing control connection.", timeout);
915 syslog(LOG_INFO, "User %s timed out after %d seconds",
916 (pw ? pw -> pw_name : "unknown"), timeout);
923 static int cpos, state;
933 (void) signal(SIGALRM, toolong);
934 (void) alarm((unsigned) timeout);
935 if (getline(cbuf, sizeof(cbuf)-1, stdin) == NULL) {
936 reply(221, "You could at least say goodbye.");
941 if (strncasecmp(cbuf, "PASS", 4) != NULL)
942 setproctitle("%s: %s", proctitle, cbuf);
943 #endif /* SETPROCTITLE */
944 if ((cp = strchr(cbuf, '\r'))) {
948 if ((cp = strpbrk(cbuf, " \n")))
955 p = lookup(cmdtab, cbuf);
958 if (p->implemented == 0) {
970 if (cbuf[cpos] == ' ') {
975 if ((cp2 = strpbrk(cp, " \n")))
980 p = lookup(sitetab, cp);
983 if (p->implemented == 0) {
997 if (cbuf[cpos] == '\n') {
1006 if (cbuf[cpos] == ' ') {
1008 state = state == OSTR ? STR2 : ++state;
1014 if (cbuf[cpos] == '\n') {
1025 * Make sure the string is nonempty and \n terminated.
1027 if (n > 1 && cbuf[cpos] == '\n') {
1029 yylval.s = copy(cp);
1037 if (cbuf[cpos] == ' ') {
1041 if (isdigit(cbuf[cpos])) {
1043 while (isdigit(cbuf[++cpos]))
1047 yylval.i = atoi(cp);
1056 if (isdigit(cbuf[cpos])) {
1058 while (isdigit(cbuf[++cpos]))
1062 yylval.i = atoi(cp);
1066 switch (cbuf[cpos++]) {
1130 fatal("Unknown state in scanner.");
1132 yyerror((char *) 0);
1134 longjmp(errcatch,0);
1142 while (*s != '\0') {
1155 p = malloc((unsigned) strlen(s) + 1);
1157 fatal("Ran out of memory.");
1158 (void) strcpy(p, s);
1171 if (ctab == sitetab)
1175 width = 0, NCMDS = 0;
1176 for (c = ctab; c->name != NULL; c++) {
1177 int len = strlen(c->name);
1183 width = (width + 8) &~ 7;
1188 lreply(214, "The following %scommands are recognized %s.",
1189 type, "(* =>'s unimplemented)");
1190 columns = 76 / width;
1193 lines = (NCMDS + columns - 1) / columns;
1194 for (i = 0; i < lines; i++) {
1196 for (j = 0; j < columns; j++) {
1197 c = ctab + j * lines + i;
1198 printf("%s%c", c->name,
1199 c->implemented ? ' ' : '*');
1200 if (c + lines >= &ctab[NCMDS])
1202 w = strlen(c->name) + 1;
1210 (void) fflush(stdout);
1211 reply(214, "Direct comments to ftp-bugs@%s.", hostname);
1215 c = lookup(ctab, s);
1216 if (c == (struct tab *)0) {
1217 reply(502, "Unknown command %s.", s);
1221 reply(214, "Syntax: %s%s %s", type, c->name, c->help);
1223 reply(214, "%s%-*s\t%s; unimplemented.", type, width,
1235 if (stat(filename, &stbuf) < 0 || !S_ISREG(stbuf.st_mode))
1236 reply(550, "%s: not a plain file.", filename);
1238 reply(213, "%qu", stbuf.st_size);
1245 fin = fopen(filename, "r");
1247 perror_reply(550, filename);
1250 if (fstat(fileno(fin), &stbuf) < 0 || !S_ISREG(stbuf.st_mode)) {
1251 reply(550, "%s: not a plain file.", filename);
1257 while((c=getc(fin)) != EOF) {
1258 if (c == '\n') /* will get expanded to \r\n */
1264 reply(213, "%qd", count);
1267 reply(504, "SIZE not implemented for Type %c.", "?AEIL"[type]);