1 /* ftpcmd.y: yacc parser for the FTP daemon.
3 %%% portions-copyright-cmetz-96
4 Portions of this software are Copyright 1996-1998 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.
28 * Copyright (c) 1985, 1988 Regents of the University of California.
29 * All rights reserved.
31 * Redistribution and use in source and binary forms, with or without
32 * modification, are permitted provided that the following conditions
34 * 1. Redistributions of source code must retain the above copyright
35 * notice, this list of conditions and the following disclaimer.
36 * 2. Redistributions in binary form must reproduce the above copyright
37 * notice, this list of conditions and the following disclaimer in the
38 * documentation and/or other materials provided with the distribution.
39 * 3. All advertising materials mentioning features or use of this software
40 * must display the following acknowledgement:
41 * This product includes software developed by the University of
42 * California, Berkeley and its contributors.
43 * 4. Neither the name of the University nor the names of its contributors
44 * may be used to endorse or promote products derived from this software
45 * without specific prior written permission.
47 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
48 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
49 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
50 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
51 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
52 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
53 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
54 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
55 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
56 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
59 * @(#)ftpcmd.y 5.24 (Berkeley) 2/25/91
63 * Grammar for FTP commands.
70 #include <sys/param.h>
71 #include <sys/types.h>
72 #include <sys/socket.h>
74 #include <netinet/in.h>
81 #else /* TM_IN_SYS_TIME */
83 #endif /* TM_IN_SYS_TIME */
94 #define LS_COMMAND "/bin/ls -lgA"
95 #else /* HAVE_LS_G_FLAG */
96 #define LS_COMMAND "/bin/ls -lA"
97 #endif /* HAVE_LS_G_FLAG */
99 extern struct sockaddr_in data_dest;
100 extern struct sockaddr_in his_addr;
101 extern int logged_in;
102 extern struct passwd *pw;
108 extern int maxtimeout;
110 extern char *remotehost;
111 extern char *proctitle;
112 extern char *globerr;
113 extern int usedefault;
114 extern int transflag;
115 extern char tmpline[];
118 VOIDRET dologout __P((int));
119 VOIDRET upper __P((char *));
120 VOIDRET nack __P((char *));
121 VOIDRET opiefatal __P((char *));
123 VOIDRET pass __P((char *));
124 int user __P((char *));
125 VOIDRET passive __P((void));
126 VOIDRET retrieve __P((char *, char *));
127 VOIDRET store __P((char *, char *, int));
128 VOIDRET send_file_list __P((char *));
129 VOIDRET statfilecmd __P((char *));
130 VOIDRET statcmd __P((void));
131 VOIDRET delete __P((char *));
132 VOIDRET renamecmd __P((char *, char *));
133 VOIDRET cwd __P((char *));
134 VOIDRET makedir __P((char *));
135 VOIDRET removedir __P((char *));
136 VOIDRET pwd __P((void));
138 VOIDRET sizecmd __P((char *));
144 static int cmd_bytesz;
145 static unsigned short cliport = 0;
153 short implemented; /* 1 if command is implemented */
157 VOIDRET help __P((struct tab *, char *));
159 struct tab cmdtab[], sitetab[];
167 SP CRLF COMMA STRING NUMBER
169 USER PASS ACCT REIN QUIT PORT
170 PASV TYPE STRU MODE RETR STOR
171 APPE MLFL MAIL MSND MSOM MSAM
172 MRSQ MRCP ALLO REST RNFR RNTO
173 ABOR DELE CWD LIST NLST SITE
174 STAT HELP NOOP MKD RMD PWD
175 CDUP STOU SMNT SYST SIZE MDTM
185 cmd_list: /* empty */
188 fromname = (char *) 0;
189 restart_point = (off_t) 0;
194 cmd: USER SP username CRLF
199 | PASS SP password CRLF
204 | PORT check_login SP host_port CRLF
211 /* H* port fix, part B: admonish the twit.
212 Also require login before PORT works */
214 if ((cliport > 1023) && (data_dest.sin_addr.s_addr > 0)) {
215 reply(200, "PORT command successful.");
217 syslog (LOG_WARNING, "refused %s from %s",
219 reply(500, "You've GOT to be joking.");
227 | PASV check_login CRLF
229 /* Require login for PASV, too. This actually fixes a bug -- telnet to an
230 unfixed wu-ftpd and type PASV first off, and it crashes! */
235 | TYPE SP type_code CRLF
240 if (cmd_form == FORM_N) {
241 reply(200, "Type set to A.");
245 reply(504, "Form must be N.");
249 reply(504, "Type E not implemented.");
253 reply(200, "Type set to I.");
259 if (cmd_bytesz == 8) {
261 "Type set to L (byte size 8).");
264 reply(504, "Byte size must be 8.");
265 #else /* NBBY == 8 */
266 UNIMPLEMENTED for NBBY != 8
267 #endif /* NBBY == 8 */
270 | STRU SP struct_code CRLF
275 reply(200, "STRU F ok.");
279 reply(504, "Unimplemented STRU type.");
282 | MODE SP mode_code CRLF
287 reply(200, "MODE S ok.");
291 reply(502, "Unimplemented MODE type.");
294 | ALLO SP NUMBER CRLF
296 reply(202, "ALLO command ignored.");
298 | ALLO SP NUMBER SP R SP NUMBER CRLF
300 reply(202, "ALLO command ignored.");
302 | RETR check_login SP pathname CRLF
305 retrieve((char *) 0, (char *) $4);
309 | STOR check_login SP pathname CRLF
312 store((char *) $4, "w", 0);
316 | APPE check_login SP pathname CRLF
319 store((char *) $4, "a", 0);
323 | NLST check_login CRLF
328 | NLST check_login SP STRING CRLF
331 send_file_list((char *) $4);
335 | LIST check_login CRLF
338 retrieve(LS_COMMAND, "");
340 | LIST check_login SP pathname CRLF
344 char buffer[sizeof(LS_COMMAND)+3];
345 strcpy(buffer, LS_COMMAND);
346 strcat(buffer, " %s");
347 retrieve(buffer, (char *) $4);
352 | STAT check_login SP pathname CRLF
355 statfilecmd((char *) $4);
363 | DELE check_login SP pathname CRLF
370 | RNTO SP pathname CRLF
373 renamecmd(fromname, (char *) $3);
375 fromname = (char *) 0;
377 reply(503, "Bad sequence of commands.");
383 reply(225, "ABOR command successful.");
385 | CWD check_login CRLF
390 | CWD check_login SP pathname CRLF
399 help(cmdtab, (char *) 0);
401 | HELP SP STRING CRLF
403 register char *cp = (char *)$3;
405 if (strncasecmp(cp, "SITE", 4) == 0) {
412 help(sitetab, (char *) 0);
414 help(cmdtab, (char *) $3);
418 reply(200, "NOOP command successful.");
420 | MKD check_login SP pathname CRLF
423 makedir((char *) $4);
427 | RMD check_login SP pathname CRLF
430 removedir((char *) $4);
434 | PWD check_login CRLF
439 | CDUP check_login CRLF
446 help(sitetab, (char *) 0);
448 | SITE SP HELP SP STRING CRLF
450 help(sitetab, (char *) $5);
452 | SITE SP UMASK check_login CRLF
458 (void) umask(oldmask);
459 reply(200, "Current UMASK is %03o", oldmask);
462 | SITE SP UMASK check_login SP octal_number CRLF
467 if (($6 == -1) || ($6 > 0777)) {
468 reply(501, "Bad UMASK value");
472 "UMASK set to %03o (was %03o)",
477 | SITE SP CHMOD check_login SP octal_number SP pathname CRLF
482 "CHMOD: Mode value must be between 0 and 0777");
483 else if (chmod((char *) $8, $6) < 0)
484 perror_reply(550, (char *) $8);
486 reply(200, "CHMOD command successful.");
494 "Current IDLE time limit is %d seconds; max %d",
495 timeout, maxtimeout);
497 | SITE SP IDLE SP NUMBER CRLF
499 if ($5 < 30 || $5 > maxtimeout) {
501 "Maximum IDLE time must be between 30 and %d seconds",
505 (void) alarm((unsigned) timeout);
507 "Maximum IDLE time set to %d seconds",
511 | STOU check_login SP pathname CRLF
514 store((char *) $4, "w", 1);
522 reply(215, "UNIX Type: L%d Version: BSD-%d",
525 reply(215, "UNIX Type: L%d", NBBY);
528 reply(215, "UNKNOWN Type: L%d", NBBY);
533 * SIZE is not in RFC959, but Postel has blessed it and
534 * it will be in the updated RFC.
536 * Return size of file in a format suitable for
537 * using with RESTART (we just count bytes).
539 | SIZE check_login SP pathname CRLF
542 sizecmd((char *) $4);
548 * MDTM is not in RFC959, but Postel has blessed it and
549 * it will be in the updated RFC.
551 * Return modification time of file as an ISO 3307
552 * style time. E.g. YYYYMMDDHHMMSS or YYYYMMDDHHMMSS.xxx
553 * where xxx is the fractional second (of any precision,
554 * not necessarily 3 digits)
556 | MDTM check_login SP pathname CRLF
560 if (stat((char *) $4, &stbuf) < 0)
561 perror_reply(550, (char *) $4);
562 else if ((stbuf.st_mode&S_IFMT) != S_IFREG) {
563 reply(550, "%s: not a plain file.",
566 register struct tm *t;
568 t = gmtime(&stbuf.st_mtime);
570 "%d%02d%02d%02d%02d%02d",
571 t->tm_year+1900, t->tm_mon+1, t->tm_mday,
572 t->tm_hour, t->tm_min, t->tm_sec);
580 reply(221, "Goodbye.");
588 rcmd: RNFR check_login SP pathname CRLF
592 restart_point = (off_t) 0;
594 fromname = renamefrom((char *) $4);
595 if (fromname == (char *) 0 && $4) {
600 | REST SP byte_size CRLF
604 fromname = (char *) 0;
606 reply(350, "Restarting at %ld. %s", restart_point,
607 "Send STORE or RETRIEVE to initiate transfer.");
614 password: /* empty */
616 *(char **)&($$) = (char *)calloc(1, sizeof(char));
624 host_port: NUMBER COMMA NUMBER COMMA NUMBER COMMA NUMBER COMMA
627 register char *a, *p;
629 a = (char *)&data_dest.sin_addr;
630 a[0] = $1; a[1] = $3; a[2] = $5; a[3] = $7;
632 /* H* port fix, part A-1: Check the args against the client addr */
633 p = (char *)&his_addr.sin_addr;
634 if (memcmp (a, p, sizeof (data_dest.sin_addr)))
635 memset (a, 0, sizeof (data_dest.sin_addr)); /* XXX */
637 p = (char *)&data_dest.sin_port;
639 /* H* port fix, part A-2: only allow client ports in "user space" */
641 cliport = ($9 << 8) + $11;
642 if (cliport > 1023) {
643 p[0] = $9; p[1] = $11;
646 p[0] = $9; p[1] = $11;
647 data_dest.sin_family = AF_INET;
699 /* this is for a bug in the BBN ftp */
738 * Problem: this production is used for all pathname
739 * processing, but only gives a 550 error reply.
740 * This is a valid reply in some cases but not in others.
742 if (logged_in && $1 && strncmp((char *) $1, "~", 1) == 0) {
743 *(char **)&($$) = *ftpglob((char *) $1);
744 if (globerr != NULL) {
760 register int ret, dec, multby, digit;
763 * Convert a number that was read as decimal number
764 * to what it would be if it had been read as octal.
775 ret += digit * multby;
783 check_login: /* empty */
788 reply(530, "Please login with USER and PASS.");
796 extern jmp_buf errcatch;
798 #define CMD 0 /* beginning of command */
799 #define ARGS 1 /* expect miscellaneous arguments */
800 #define STR1 2 /* expect SP followed by STRING */
801 #define STR2 3 /* expect STRING */
802 #define OSTR 4 /* optional SP then STRING */
803 #define ZSTR1 5 /* SP then optional STRING */
804 #define ZSTR2 6 /* optional STRING after SP */
805 #define SITECMD 7 /* SITE command */
806 #define NSTR 8 /* Number followed by a string */
808 struct tab cmdtab[] = { /* In order defined in RFC 765 */
809 { "USER", USER, STR1, 1, "<sp> username" },
810 { "PASS", PASS, ZSTR1, 1, "<sp> password" },
811 { "ACCT", ACCT, STR1, 0, "(specify account)" },
812 { "SMNT", SMNT, ARGS, 0, "(structure mount)" },
813 { "REIN", REIN, ARGS, 0, "(reinitialize server state)" },
814 { "QUIT", QUIT, ARGS, 1, "(terminate service)", },
815 { "PORT", PORT, ARGS, 1, "<sp> b0, b1, b2, b3, b4" },
816 { "PASV", PASV, ARGS, 1, "(set server in passive mode)" },
817 { "TYPE", TYPE, ARGS, 1, "<sp> [ A | E | I | L ]" },
818 { "STRU", STRU, ARGS, 1, "(specify file structure)" },
819 { "MODE", MODE, ARGS, 1, "(specify transfer mode)" },
820 { "RETR", RETR, STR1, 1, "<sp> file-name" },
821 { "STOR", STOR, STR1, 1, "<sp> file-name" },
822 { "APPE", APPE, STR1, 1, "<sp> file-name" },
823 { "MLFL", MLFL, OSTR, 0, "(mail file)" },
824 { "MAIL", MAIL, OSTR, 0, "(mail to user)" },
825 { "MSND", MSND, OSTR, 0, "(mail send to terminal)" },
826 { "MSOM", MSOM, OSTR, 0, "(mail send to terminal or mailbox)" },
827 { "MSAM", MSAM, OSTR, 0, "(mail send to terminal and mailbox)" },
828 { "MRSQ", MRSQ, OSTR, 0, "(mail recipient scheme question)" },
829 { "MRCP", MRCP, STR1, 0, "(mail recipient)" },
830 { "ALLO", ALLO, ARGS, 1, "allocate storage (vacuously)" },
831 { "REST", REST, ARGS, 1, "(restart command)" },
832 { "RNFR", RNFR, STR1, 1, "<sp> file-name" },
833 { "RNTO", RNTO, STR1, 1, "<sp> file-name" },
834 { "ABOR", ABOR, ARGS, 1, "(abort operation)" },
835 { "DELE", DELE, STR1, 1, "<sp> file-name" },
836 { "CWD", CWD, OSTR, 1, "[ <sp> directory-name ]" },
837 { "XCWD", CWD, OSTR, 1, "[ <sp> directory-name ]" },
838 { "LIST", LIST, OSTR, 1, "[ <sp> path-name ]" },
839 { "NLST", NLST, OSTR, 1, "[ <sp> path-name ]" },
840 { "SITE", SITE, SITECMD, 1, "site-cmd [ <sp> arguments ]" },
841 { "SYST", SYST, ARGS, 1, "(get type of operating system)" },
842 { "STAT", STAT, OSTR, 1, "[ <sp> path-name ]" },
843 { "HELP", HELP, OSTR, 1, "[ <sp> <string> ]" },
844 { "NOOP", NOOP, ARGS, 1, "" },
845 { "MKD", MKD, STR1, 1, "<sp> path-name" },
846 { "XMKD", MKD, STR1, 1, "<sp> path-name" },
847 { "RMD", RMD, STR1, 1, "<sp> path-name" },
848 { "XRMD", RMD, STR1, 1, "<sp> path-name" },
849 { "PWD", PWD, ARGS, 1, "(return current directory)" },
850 { "XPWD", PWD, ARGS, 1, "(return current directory)" },
851 { "CDUP", CDUP, ARGS, 1, "(change to parent directory)" },
852 { "XCUP", CDUP, ARGS, 1, "(change to parent directory)" },
853 { "STOU", STOU, STR1, 1, "<sp> file-name" },
854 { "SIZE", SIZE, OSTR, 1, "<sp> path-name" },
855 { "MDTM", MDTM, OSTR, 1, "<sp> path-name" },
859 struct tab sitetab[] = {
860 { "UMASK", UMASK, ARGS, 1, "[ <sp> umask ]" },
861 { "IDLE", IDLE, ARGS, 1, "[ <sp> maximum-idle-time ]" },
862 { "CHMOD", CHMOD, NSTR, 1, "<sp> mode <sp> file-name" },
863 { "HELP", HELP, OSTR, 1, "[ <sp> <string> ]" },
867 struct tab *lookup FUNCTION((p, cmd), register struct tab *p AND char *cmd)
870 for (; p->name != NULL; p++)
871 if (strcmp(cmd, p->name) == 0)
876 #include <arpa/telnet.h>
879 * getline - a hacked up version of fgets to ignore TELNET escape codes.
881 char *getline FUNCTION((s, n, iop), char *s AND int n AND FILE *iop)
887 /* tmpline may contain saved command from urgent mode interruption */
888 for (c = 0; *(tmpline + c) && --n > 0; ++c) {
889 *cs++ = *(tmpline + c);
890 if (*(tmpline + c) == '\n') {
893 syslog(LOG_DEBUG, "command: %s", s);
900 while ((c = getc(iop)) != EOF) {
903 if ((c = getc(iop)) != EOF) {
909 printf("%c%c%c", IAC, DONT, 0377&c);
910 (void) fflush(stdout);
915 printf("%c%c%c", IAC, WONT, 0377&c);
916 (void) fflush(stdout);
921 continue; /* ignore command */
926 if (--n <= 0 || c == '\n')
929 if (c == EOF && cs == s)
933 syslog(LOG_DEBUG, "command: %s", s);
937 static VOIDRET toolong FUNCTION((input), int input)
941 reply(421, "Timeout (%d seconds): closing control connection.", timeout);
943 syslog(LOG_INFO, "User %s timed out after %d seconds at %s",
944 (pw ? pw -> pw_name : "unknown"), timeout, ctime(&now));
948 int yylex FUNCTION_NOARGS
950 static int cpos, state;
951 register char *cp, *cp2;
952 register struct tab *p;
960 (void) signal(SIGALRM, toolong);
961 (void) alarm((unsigned) timeout);
962 if (getline(cbuf, sizeof(cbuf)-1, stdin) == NULL) {
963 reply(221, "You could at least say goodbye.");
968 if (strncasecmp(cbuf, "PASS", 4) != NULL)
969 setproctitle("%s: %s", proctitle, cbuf);
970 #endif /* SETPROCTITLE */
971 if ((cp = strchr(cbuf, '\r'))) {
975 if ((cp = strpbrk(cbuf, " \n")))
982 p = lookup(cmdtab, cbuf);
985 if (p->implemented == 0) {
991 *(char **)&yylval = p->name;
997 if (cbuf[cpos] == ' ') {
1002 if ((cp2 = strpbrk(cp, " \n")))
1007 p = lookup(sitetab, cp);
1010 if (p->implemented == 0) {
1013 longjmp(errcatch,0);
1017 *(char **)&yylval = p->name;
1024 if (cbuf[cpos] == '\n') {
1033 if (cbuf[cpos] == ' ') {
1035 state = state == OSTR ? STR2 : ++state;
1041 if (cbuf[cpos] == '\n') {
1052 * Make sure the string is nonempty and \n terminated.
1054 if (n > 1 && cbuf[cpos] == '\n') {
1056 *(char **)&yylval = copy(cp);
1064 if (cbuf[cpos] == ' ') {
1068 if (isdigit(cbuf[cpos])) {
1070 while (isdigit(cbuf[++cpos]))
1083 if (isdigit(cbuf[cpos])) {
1085 while (isdigit(cbuf[++cpos]))
1093 switch (cbuf[cpos++]) {
1157 opiefatal("Unknown state in scanner.");
1159 yyerror((char *) 0);
1161 longjmp(errcatch,0);
1165 VOIDRET upper FUNCTION((s), char *s)
1167 while (*s != '\0') {
1174 char *copy FUNCTION((s), char *s)
1178 p = malloc((unsigned) strlen(s) + 1);
1180 opiefatal("Ran out of memory.");
1181 (void) strcpy(p, s);
1185 VOIDRET help FUNCTION((ctab, s), struct tab *ctab AND char *s)
1187 register struct tab *c;
1188 register int width, NCMDS;
1191 if (ctab == sitetab)
1195 width = 0, NCMDS = 0;
1196 for (c = ctab; c->name != NULL; c++) {
1197 int len = strlen(c->name);
1203 width = (width + 8) &~ 7;
1205 register int i, j, w;
1208 lreply(214, "The following %scommands are recognized %s.",
1209 type, "(* =>'s unimplemented)");
1210 columns = 76 / width;
1213 lines = (NCMDS + columns - 1) / columns;
1214 for (i = 0; i < lines; i++) {
1216 for (j = 0; j < columns; j++) {
1217 c = ctab + j * lines + i;
1218 printf("%s%c", c->name,
1219 c->implemented ? ' ' : '*');
1220 if (c + lines >= &ctab[NCMDS])
1222 w = strlen(c->name) + 1;
1230 (void) fflush(stdout);
1235 c = lookup(ctab, s);
1236 if (c == (struct tab *)0) {
1237 reply(502, "Unknown command %s.", s);
1241 reply(214, "Syntax: %s%s %s", type, c->name, c->help);
1243 reply(214, "%s%-*s\t%s; unimplemented.", type, width,
1247 VOIDRET sizecmd FUNCTION((filename), char *filename)
1253 if (stat(filename, &stbuf) < 0 ||
1254 (stbuf.st_mode&S_IFMT) != S_IFREG)
1255 reply(550, "%s: not a plain file.", filename);
1257 reply(213, "%lu", stbuf.st_size);
1262 register long count;
1264 fin = fopen(filename, "r");
1266 perror_reply(550, filename);
1269 if (fstat(fileno(fin), &stbuf) < 0 ||
1270 (stbuf.st_mode&S_IFMT) != S_IFREG) {
1271 reply(550, "%s: not a plain file.", filename);
1277 while((c=getc(fin)) != EOF) {
1278 if (c == '\n') /* will get expanded to \r\n */
1284 reply(213, "%ld", count);
1287 reply(504, "SIZE not implemented for Type %c.", "?AEIL"[type]);