1 /* ftpcmd.y: yacc parser for the FTP daemon.
3 %%% portions-copyright-cmetz-96
4 Portions of this software are Copyright 1996-1997 by Craig Metz, All Rights
5 Reserved. The Inner Net License Version 2 applies to these portions of
7 You should have received a copy of the license with this software. If
8 you didn't get a copy, you may request one from <license@inner.net>.
12 Modified by cmetz for OPIE 2.3. Moved LS_COMMAND here.
13 Modified by cmetz for OPIE 2.2. Fixed a *lot* of warnings.
14 Use FUNCTION declaration et al. Removed useless strings.
15 Changed some char []s to char *s. Deleted comment address.
16 Changed tmpline references to be more pure-pointer
17 references. Changed tmpline declaration back to char [].
18 Modified at NRL for OPIE 2.1. Minor changes for autoconf.
19 Modified at NRL for OPIE 2.01. Added forward declaration for sitetab[]
20 -- fixes problems experienced by bison users. Merged in new
21 PORT attack fixes from Hobbit.
22 Modified at NRL for OPIE 2.0.
26 * Copyright (c) 1985, 1988 Regents of the University of California.
27 * All rights reserved.
29 * Redistribution and use in source and binary forms, with or without
30 * modification, are permitted provided that the following conditions
32 * 1. Redistributions of source code must retain the above copyright
33 * notice, this list of conditions and the following disclaimer.
34 * 2. Redistributions in binary form must reproduce the above copyright
35 * notice, this list of conditions and the following disclaimer in the
36 * documentation and/or other materials provided with the distribution.
37 * 3. All advertising materials mentioning features or use of this software
38 * must display the following acknowledgement:
39 * This product includes software developed by the University of
40 * California, Berkeley and its contributors.
41 * 4. Neither the name of the University nor the names of its contributors
42 * may be used to endorse or promote products derived from this software
43 * without specific prior written permission.
45 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
46 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
47 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
48 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
49 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
50 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
51 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
52 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
53 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
54 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
57 * @(#)ftpcmd.y 5.24 (Berkeley) 2/25/91
61 * Grammar for FTP commands.
68 #include <sys/param.h>
69 #include <sys/types.h>
70 #include <sys/socket.h>
72 #include <netinet/in.h>
79 #else /* TM_IN_SYS_TIME */
81 #endif /* TM_IN_SYS_TIME */
92 #define LS_COMMAND "/bin/ls -lgA"
93 #else /* HAVE_LS_G_FLAG */
94 #define LS_COMMAND "/bin/ls -lA"
95 #endif /* HAVE_LS_G_FLAG */
97 extern struct sockaddr_in data_dest;
98 extern struct sockaddr_in his_addr;
100 extern struct passwd *pw;
106 extern int maxtimeout;
108 extern char *remotehost;
109 extern char *proctitle;
110 extern char *globerr;
111 extern int usedefault;
112 extern int transflag;
113 extern char tmpline[];
116 VOIDRET dologout __P((int));
117 VOIDRET upper __P((char *));
118 VOIDRET nack __P((char *));
119 VOIDRET opiefatal __P((char *));
121 VOIDRET pass __P((char *));
122 int user __P((char *));
123 VOIDRET passive __P((void));
124 VOIDRET retrieve __P((char *, char *));
125 VOIDRET store __P((char *, char *, int));
126 VOIDRET send_file_list __P((char *));
127 VOIDRET statfilecmd __P((char *));
128 VOIDRET statcmd __P((void));
129 VOIDRET delete __P((char *));
130 VOIDRET renamecmd __P((char *, char *));
131 VOIDRET cwd __P((char *));
132 VOIDRET makedir __P((char *));
133 VOIDRET removedir __P((char *));
134 VOIDRET pwd __P((void));
136 VOIDRET sizecmd __P((char *));
142 static int cmd_bytesz;
143 static unsigned short cliport = 0;
151 short implemented; /* 1 if command is implemented */
155 VOIDRET help __P((struct tab *, char *));
157 struct tab cmdtab[], sitetab[];
165 SP CRLF COMMA STRING NUMBER
167 USER PASS ACCT REIN QUIT PORT
168 PASV TYPE STRU MODE RETR STOR
169 APPE MLFL MAIL MSND MSOM MSAM
170 MRSQ MRCP ALLO REST RNFR RNTO
171 ABOR DELE CWD LIST NLST SITE
172 STAT HELP NOOP MKD RMD PWD
173 CDUP STOU SMNT SYST SIZE MDTM
183 cmd_list: /* empty */
186 fromname = (char *) 0;
187 restart_point = (off_t) 0;
192 cmd: USER SP username CRLF
197 | PASS SP password CRLF
202 | PORT check_login SP host_port CRLF
209 /* H* port fix, part B: admonish the twit.
210 Also require login before PORT works */
212 if ((cliport > 1023) && (data_dest.sin_addr.s_addr > 0)) {
213 reply(200, "PORT command successful.");
215 syslog (LOG_WARNING, "refused %s from %s",
217 reply(500, "You've GOT to be joking.");
225 | PASV check_login CRLF
227 /* Require login for PASV, too. This actually fixes a bug -- telnet to an
228 unfixed wu-ftpd and type PASV first off, and it crashes! */
233 | TYPE SP type_code CRLF
238 if (cmd_form == FORM_N) {
239 reply(200, "Type set to A.");
243 reply(504, "Form must be N.");
247 reply(504, "Type E not implemented.");
251 reply(200, "Type set to I.");
257 if (cmd_bytesz == 8) {
259 "Type set to L (byte size 8).");
262 reply(504, "Byte size must be 8.");
263 #else /* NBBY == 8 */
264 UNIMPLEMENTED for NBBY != 8
265 #endif /* NBBY == 8 */
268 | STRU SP struct_code CRLF
273 reply(200, "STRU F ok.");
277 reply(504, "Unimplemented STRU type.");
280 | MODE SP mode_code CRLF
285 reply(200, "MODE S ok.");
289 reply(502, "Unimplemented MODE type.");
292 | ALLO SP NUMBER CRLF
294 reply(202, "ALLO command ignored.");
296 | ALLO SP NUMBER SP R SP NUMBER CRLF
298 reply(202, "ALLO command ignored.");
300 | RETR check_login SP pathname CRLF
303 retrieve((char *) 0, (char *) $4);
307 | STOR check_login SP pathname CRLF
310 store((char *) $4, "w", 0);
314 | APPE check_login SP pathname CRLF
317 store((char *) $4, "a", 0);
321 | NLST check_login CRLF
326 | NLST check_login SP STRING CRLF
329 send_file_list((char *) $4);
333 | LIST check_login CRLF
336 retrieve(LS_COMMAND, "");
338 | LIST check_login SP pathname CRLF
342 char buffer[sizeof(LS_COMMAND)+3];
343 strcpy(buffer, LS_COMMAND);
344 strcat(buffer, " %s");
345 retrieve(buffer, (char *) $4);
350 | STAT check_login SP pathname CRLF
353 statfilecmd((char *) $4);
361 | DELE check_login SP pathname CRLF
368 | RNTO SP pathname CRLF
371 renamecmd(fromname, (char *) $3);
373 fromname = (char *) 0;
375 reply(503, "Bad sequence of commands.");
381 reply(225, "ABOR command successful.");
383 | CWD check_login CRLF
388 | CWD check_login SP pathname CRLF
397 help(cmdtab, (char *) 0);
399 | HELP SP STRING CRLF
401 register char *cp = (char *)$3;
403 if (strncasecmp(cp, "SITE", 4) == 0) {
410 help(sitetab, (char *) 0);
412 help(cmdtab, (char *) $3);
416 reply(200, "NOOP command successful.");
418 | MKD check_login SP pathname CRLF
421 makedir((char *) $4);
425 | RMD check_login SP pathname CRLF
428 removedir((char *) $4);
432 | PWD check_login CRLF
437 | CDUP check_login CRLF
444 help(sitetab, (char *) 0);
446 | SITE SP HELP SP STRING CRLF
448 help(sitetab, (char *) $5);
450 | SITE SP UMASK check_login CRLF
456 (void) umask(oldmask);
457 reply(200, "Current UMASK is %03o", oldmask);
460 | SITE SP UMASK check_login SP octal_number CRLF
465 if (($6 == -1) || ($6 > 0777)) {
466 reply(501, "Bad UMASK value");
470 "UMASK set to %03o (was %03o)",
475 | SITE SP CHMOD check_login SP octal_number SP pathname CRLF
480 "CHMOD: Mode value must be between 0 and 0777");
481 else if (chmod((char *) $8, $6) < 0)
482 perror_reply(550, (char *) $8);
484 reply(200, "CHMOD command successful.");
492 "Current IDLE time limit is %d seconds; max %d",
493 timeout, maxtimeout);
495 | SITE SP IDLE SP NUMBER CRLF
497 if ($5 < 30 || $5 > maxtimeout) {
499 "Maximum IDLE time must be between 30 and %d seconds",
503 (void) alarm((unsigned) timeout);
505 "Maximum IDLE time set to %d seconds",
509 | STOU check_login SP pathname CRLF
512 store((char *) $4, "w", 1);
520 reply(215, "UNIX Type: L%d Version: BSD-%d",
523 reply(215, "UNIX Type: L%d", NBBY);
526 reply(215, "UNKNOWN Type: L%d", NBBY);
531 * SIZE is not in RFC959, but Postel has blessed it and
532 * it will be in the updated RFC.
534 * Return size of file in a format suitable for
535 * using with RESTART (we just count bytes).
537 | SIZE check_login SP pathname CRLF
540 sizecmd((char *) $4);
546 * MDTM is not in RFC959, but Postel has blessed it and
547 * it will be in the updated RFC.
549 * Return modification time of file as an ISO 3307
550 * style time. E.g. YYYYMMDDHHMMSS or YYYYMMDDHHMMSS.xxx
551 * where xxx is the fractional second (of any precision,
552 * not necessarily 3 digits)
554 | MDTM check_login SP pathname CRLF
558 if (stat((char *) $4, &stbuf) < 0)
559 perror_reply(550, (char *) $4);
560 else if ((stbuf.st_mode&S_IFMT) != S_IFREG) {
561 reply(550, "%s: not a plain file.",
564 register struct tm *t;
566 t = gmtime(&stbuf.st_mtime);
568 "%d%02d%02d%02d%02d%02d",
569 t->tm_year+1900, t->tm_mon+1, t->tm_mday,
570 t->tm_hour, t->tm_min, t->tm_sec);
578 reply(221, "Goodbye.");
586 rcmd: RNFR check_login SP pathname CRLF
590 restart_point = (off_t) 0;
592 fromname = renamefrom((char *) $4);
593 if (fromname == (char *) 0 && $4) {
598 | REST SP byte_size CRLF
602 fromname = (char *) 0;
604 reply(350, "Restarting at %ld. %s", restart_point,
605 "Send STORE or RETRIEVE to initiate transfer.");
612 password: /* empty */
614 *(char **)&($$) = (char *)calloc(1, sizeof(char));
622 host_port: NUMBER COMMA NUMBER COMMA NUMBER COMMA NUMBER COMMA
625 register char *a, *p;
627 a = (char *)&data_dest.sin_addr;
628 a[0] = $1; a[1] = $3; a[2] = $5; a[3] = $7;
630 /* H* port fix, part A-1: Check the args against the client addr */
631 p = (char *)&his_addr.sin_addr;
632 if (memcmp (a, p, sizeof (data_dest.sin_addr)))
633 memset (a, 0, sizeof (data_dest.sin_addr)); /* XXX */
635 p = (char *)&data_dest.sin_port;
637 /* H* port fix, part A-2: only allow client ports in "user space" */
639 cliport = ($9 << 8) + $11;
640 if (cliport > 1023) {
641 p[0] = $9; p[1] = $11;
644 p[0] = $9; p[1] = $11;
645 data_dest.sin_family = AF_INET;
697 /* this is for a bug in the BBN ftp */
736 * Problem: this production is used for all pathname
737 * processing, but only gives a 550 error reply.
738 * This is a valid reply in some cases but not in others.
740 if (logged_in && $1 && strncmp((char *) $1, "~", 1) == 0) {
741 *(char **)&($$) = *ftpglob((char *) $1);
742 if (globerr != NULL) {
758 register int ret, dec, multby, digit;
761 * Convert a number that was read as decimal number
762 * to what it would be if it had been read as octal.
773 ret += digit * multby;
781 check_login: /* empty */
786 reply(530, "Please login with USER and PASS.");
794 extern jmp_buf errcatch;
796 #define CMD 0 /* beginning of command */
797 #define ARGS 1 /* expect miscellaneous arguments */
798 #define STR1 2 /* expect SP followed by STRING */
799 #define STR2 3 /* expect STRING */
800 #define OSTR 4 /* optional SP then STRING */
801 #define ZSTR1 5 /* SP then optional STRING */
802 #define ZSTR2 6 /* optional STRING after SP */
803 #define SITECMD 7 /* SITE command */
804 #define NSTR 8 /* Number followed by a string */
806 struct tab cmdtab[] = { /* In order defined in RFC 765 */
807 { "USER", USER, STR1, 1, "<sp> username" },
808 { "PASS", PASS, ZSTR1, 1, "<sp> password" },
809 { "ACCT", ACCT, STR1, 0, "(specify account)" },
810 { "SMNT", SMNT, ARGS, 0, "(structure mount)" },
811 { "REIN", REIN, ARGS, 0, "(reinitialize server state)" },
812 { "QUIT", QUIT, ARGS, 1, "(terminate service)", },
813 { "PORT", PORT, ARGS, 1, "<sp> b0, b1, b2, b3, b4" },
814 { "PASV", PASV, ARGS, 1, "(set server in passive mode)" },
815 { "TYPE", TYPE, ARGS, 1, "<sp> [ A | E | I | L ]" },
816 { "STRU", STRU, ARGS, 1, "(specify file structure)" },
817 { "MODE", MODE, ARGS, 1, "(specify transfer mode)" },
818 { "RETR", RETR, STR1, 1, "<sp> file-name" },
819 { "STOR", STOR, STR1, 1, "<sp> file-name" },
820 { "APPE", APPE, STR1, 1, "<sp> file-name" },
821 { "MLFL", MLFL, OSTR, 0, "(mail file)" },
822 { "MAIL", MAIL, OSTR, 0, "(mail to user)" },
823 { "MSND", MSND, OSTR, 0, "(mail send to terminal)" },
824 { "MSOM", MSOM, OSTR, 0, "(mail send to terminal or mailbox)" },
825 { "MSAM", MSAM, OSTR, 0, "(mail send to terminal and mailbox)" },
826 { "MRSQ", MRSQ, OSTR, 0, "(mail recipient scheme question)" },
827 { "MRCP", MRCP, STR1, 0, "(mail recipient)" },
828 { "ALLO", ALLO, ARGS, 1, "allocate storage (vacuously)" },
829 { "REST", REST, ARGS, 1, "(restart command)" },
830 { "RNFR", RNFR, STR1, 1, "<sp> file-name" },
831 { "RNTO", RNTO, STR1, 1, "<sp> file-name" },
832 { "ABOR", ABOR, ARGS, 1, "(abort operation)" },
833 { "DELE", DELE, STR1, 1, "<sp> file-name" },
834 { "CWD", CWD, OSTR, 1, "[ <sp> directory-name ]" },
835 { "XCWD", CWD, OSTR, 1, "[ <sp> directory-name ]" },
836 { "LIST", LIST, OSTR, 1, "[ <sp> path-name ]" },
837 { "NLST", NLST, OSTR, 1, "[ <sp> path-name ]" },
838 { "SITE", SITE, SITECMD, 1, "site-cmd [ <sp> arguments ]" },
839 { "SYST", SYST, ARGS, 1, "(get type of operating system)" },
840 { "STAT", STAT, OSTR, 1, "[ <sp> path-name ]" },
841 { "HELP", HELP, OSTR, 1, "[ <sp> <string> ]" },
842 { "NOOP", NOOP, ARGS, 1, "" },
843 { "MKD", MKD, STR1, 1, "<sp> path-name" },
844 { "XMKD", MKD, STR1, 1, "<sp> path-name" },
845 { "RMD", RMD, STR1, 1, "<sp> path-name" },
846 { "XRMD", RMD, STR1, 1, "<sp> path-name" },
847 { "PWD", PWD, ARGS, 1, "(return current directory)" },
848 { "XPWD", PWD, ARGS, 1, "(return current directory)" },
849 { "CDUP", CDUP, ARGS, 1, "(change to parent directory)" },
850 { "XCUP", CDUP, ARGS, 1, "(change to parent directory)" },
851 { "STOU", STOU, STR1, 1, "<sp> file-name" },
852 { "SIZE", SIZE, OSTR, 1, "<sp> path-name" },
853 { "MDTM", MDTM, OSTR, 1, "<sp> path-name" },
857 struct tab sitetab[] = {
858 { "UMASK", UMASK, ARGS, 1, "[ <sp> umask ]" },
859 { "IDLE", IDLE, ARGS, 1, "[ <sp> maximum-idle-time ]" },
860 { "CHMOD", CHMOD, NSTR, 1, "<sp> mode <sp> file-name" },
861 { "HELP", HELP, OSTR, 1, "[ <sp> <string> ]" },
865 struct tab *lookup FUNCTION((p, cmd), register struct tab *p AND char *cmd)
868 for (; p->name != NULL; p++)
869 if (strcmp(cmd, p->name) == 0)
874 #include <arpa/telnet.h>
877 * getline - a hacked up version of fgets to ignore TELNET escape codes.
879 char *getline FUNCTION((s, n, iop), char *s AND int n AND FILE *iop)
885 /* tmpline may contain saved command from urgent mode interruption */
886 for (c = 0; *(tmpline + c) && --n > 0; ++c) {
887 *cs++ = *(tmpline + c);
888 if (*(tmpline + c) == '\n') {
891 syslog(LOG_DEBUG, "command: %s", s);
898 while ((c = getc(iop)) != EOF) {
901 if ((c = getc(iop)) != EOF) {
907 printf("%c%c%c", IAC, DONT, 0377&c);
908 (void) fflush(stdout);
913 printf("%c%c%c", IAC, WONT, 0377&c);
914 (void) fflush(stdout);
919 continue; /* ignore command */
924 if (--n <= 0 || c == '\n')
927 if (c == EOF && cs == s)
931 syslog(LOG_DEBUG, "command: %s", s);
935 static VOIDRET toolong FUNCTION((input), int input)
939 reply(421, "Timeout (%d seconds): closing control connection.", timeout);
941 syslog(LOG_INFO, "User %s timed out after %d seconds at %s",
942 (pw ? pw -> pw_name : "unknown"), timeout, ctime(&now));
946 int yylex FUNCTION_NOARGS
948 static int cpos, state;
949 register char *cp, *cp2;
950 register struct tab *p;
958 (void) signal(SIGALRM, toolong);
959 (void) alarm((unsigned) timeout);
960 if (getline(cbuf, sizeof(cbuf)-1, stdin) == NULL) {
961 reply(221, "You could at least say goodbye.");
966 if (strncasecmp(cbuf, "PASS", 4) != NULL)
967 setproctitle("%s: %s", proctitle, cbuf);
968 #endif /* SETPROCTITLE */
969 if ((cp = strchr(cbuf, '\r'))) {
973 if ((cp = strpbrk(cbuf, " \n")))
980 p = lookup(cmdtab, cbuf);
983 if (p->implemented == 0) {
989 *(char **)&yylval = p->name;
995 if (cbuf[cpos] == ' ') {
1000 if ((cp2 = strpbrk(cp, " \n")))
1005 p = lookup(sitetab, cp);
1008 if (p->implemented == 0) {
1011 longjmp(errcatch,0);
1015 *(char **)&yylval = p->name;
1022 if (cbuf[cpos] == '\n') {
1031 if (cbuf[cpos] == ' ') {
1033 state = state == OSTR ? STR2 : ++state;
1039 if (cbuf[cpos] == '\n') {
1050 * Make sure the string is nonempty and \n terminated.
1052 if (n > 1 && cbuf[cpos] == '\n') {
1054 *(char **)&yylval = copy(cp);
1062 if (cbuf[cpos] == ' ') {
1066 if (isdigit(cbuf[cpos])) {
1068 while (isdigit(cbuf[++cpos]))
1081 if (isdigit(cbuf[cpos])) {
1083 while (isdigit(cbuf[++cpos]))
1091 switch (cbuf[cpos++]) {
1155 opiefatal("Unknown state in scanner.");
1157 yyerror((char *) 0);
1159 longjmp(errcatch,0);
1163 VOIDRET upper FUNCTION((s), char *s)
1165 while (*s != '\0') {
1172 char *copy FUNCTION((s), char *s)
1176 p = malloc((unsigned) strlen(s) + 1);
1178 opiefatal("Ran out of memory.");
1179 (void) strcpy(p, s);
1183 VOIDRET help FUNCTION((ctab, s), struct tab *ctab AND char *s)
1185 register struct tab *c;
1186 register int width, NCMDS;
1189 if (ctab == sitetab)
1193 width = 0, NCMDS = 0;
1194 for (c = ctab; c->name != NULL; c++) {
1195 int len = strlen(c->name);
1201 width = (width + 8) &~ 7;
1203 register int i, j, w;
1206 lreply(214, "The following %scommands are recognized %s.",
1207 type, "(* =>'s unimplemented)");
1208 columns = 76 / width;
1211 lines = (NCMDS + columns - 1) / columns;
1212 for (i = 0; i < lines; i++) {
1214 for (j = 0; j < columns; j++) {
1215 c = ctab + j * lines + i;
1216 printf("%s%c", c->name,
1217 c->implemented ? ' ' : '*');
1218 if (c + lines >= &ctab[NCMDS])
1220 w = strlen(c->name) + 1;
1228 (void) fflush(stdout);
1233 c = lookup(ctab, s);
1234 if (c == (struct tab *)0) {
1235 reply(502, "Unknown command %s.", s);
1239 reply(214, "Syntax: %s%s %s", type, c->name, c->help);
1241 reply(214, "%s%-*s\t%s; unimplemented.", type, width,
1245 VOIDRET sizecmd FUNCTION((filename), char *filename)
1251 if (stat(filename, &stbuf) < 0 ||
1252 (stbuf.st_mode&S_IFMT) != S_IFREG)
1253 reply(550, "%s: not a plain file.", filename);
1255 reply(213, "%lu", stbuf.st_size);
1260 register long count;
1262 fin = fopen(filename, "r");
1264 perror_reply(550, filename);
1267 if (fstat(fileno(fin), &stbuf) < 0 ||
1268 (stbuf.st_mode&S_IFMT) != S_IFREG) {
1269 reply(550, "%s: not a plain file.", filename);
1275 while((c=getc(fin)) != EOF) {
1276 if (c == '\n') /* will get expanded to \r\n */
1282 reply(213, "%ld", count);
1285 reply(504, "SIZE not implemented for Type %c.", "?AEIL"[type]);