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. Neither the name of the University nor the names of its contributors
14 * may be used to endorse or promote products derived from this software
15 * without specific prior written permission.
17 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
18 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
21 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
22 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
23 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
24 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
26 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
29 * @(#)ftpcmd.y 8.3 (Berkeley) 4/6/94
33 * Grammar for FTP commands.
41 static char sccsid[] = "@(#)ftpcmd.y 8.3 (Berkeley) 4/6/94";
45 #include <sys/cdefs.h>
46 __FBSDID("$FreeBSD$");
48 #include <sys/param.h>
49 #include <sys/socket.h>
52 #include <netinet/in.h>
73 #include "pathnames.h"
79 static int cmd_bytesz;
82 char *fromname = NULL;
101 USER PASS ACCT REIN QUIT PORT
102 PASV TYPE STRU MODE RETR STOR
103 APPE MLFL MAIL MSND MSOM MSAM
104 MRSQ MRCP ALLO REST RNFR RNTO
105 ABOR DELE CWD LIST NLST SITE
106 STAT HELP NOOP MKD RMD PWD
107 CDUP STOU SMNT SYST SIZE MDTM
108 LPRT LPSV EPRT EPSV FEAT
110 UMASK IDLE CHMOD MDFIVE
117 %type <u.i> check_login octal_number byte_size
118 %type <u.i> check_login_ro check_login_epsv
119 %type <u.i> struct_code mode_code type_code form_code
120 %type <s> pathstring pathname password username
121 %type <s> ALL NOTIMPL
140 : USER SP username CRLF
145 | PASS SP password CRLF
154 | PORT check_login SP host_port CRLF
157 reply(501, "No PORT allowed after EPSV ALL.");
162 if (port_check("PORT") == 1)
165 if ((his_addr.su_family != AF_INET6 ||
166 !IN6_IS_ADDR_V4MAPPED(&his_addr.su_sin6.sin6_addr))) {
167 /* shoud never happen */
169 reply(500, "Invalid address rejected.");
172 port_check_v6("pcmd");
177 | LPRT check_login SP host_long_port CRLF
180 reply(501, "No LPRT allowed after EPSV ALL.");
185 if (port_check("LPRT") == 1)
188 if (his_addr.su_family != AF_INET6) {
190 reply(500, "Invalid address rejected.");
193 if (port_check_v6("LPRT") == 1)
199 | EPRT check_login SP STRING CRLF
205 struct addrinfo hints;
206 struct addrinfo *res;
210 reply(501, "No EPRT allowed after EPSV ALL.");
216 memset(&data_dest, 0, sizeof(data_dest));
219 syslog(LOG_DEBUG, "%s", tmp);
221 fatalerror("not enough core");
227 memset(result, 0, sizeof(result));
228 for (i = 0; i < 3; i++) {
229 q = strchr(p, delim);
230 if (!q || *q != delim) {
233 "Invalid argument, rejected.");
242 syslog(LOG_DEBUG, "%d: %s", i, p);
246 /* some more sanity check */
261 memset(&hints, 0, sizeof(hints));
262 if (atoi(result[0]) == 1)
263 hints.ai_family = PF_INET;
265 else if (atoi(result[0]) == 2)
266 hints.ai_family = PF_INET6;
269 hints.ai_family = PF_UNSPEC; /*XXX*/
270 hints.ai_socktype = SOCK_STREAM;
271 i = getaddrinfo(result[1], result[2], &hints, &res);
274 memcpy(&data_dest, res->ai_addr, res->ai_addrlen);
276 if (his_addr.su_family == AF_INET6
277 && data_dest.su_family == AF_INET6) {
278 /* XXX more sanity checks! */
279 data_dest.su_sin6.sin6_scope_id =
280 his_addr.su_sin6.sin6_scope_id;
286 if (port_check("EPRT") == 1)
289 if (his_addr.su_family != AF_INET6) {
291 reply(500, "Invalid address rejected.");
294 if (port_check_v6("EPRT") == 1)
300 | PASV check_login CRLF
303 reply(501, "No PASV allowed after EPSV ALL.");
307 | LPSV check_login CRLF
310 reply(501, "No LPSV allowed after EPSV ALL.");
312 long_passive("LPSV", PF_UNSPEC);
314 | EPSV check_login_epsv SP NUMBER CRLF
328 pf = -1; /*junk value*/
331 long_passive("EPSV", pf);
334 | EPSV check_login_epsv SP ALL CRLF
337 reply(200, "EPSV ALL command successful.");
341 | EPSV check_login_epsv CRLF
344 long_passive("EPSV", PF_UNSPEC);
346 | TYPE check_login SP type_code CRLF
352 if (cmd_form == FORM_N) {
353 reply(200, "Type set to A.");
357 reply(504, "Form must be N.");
361 reply(504, "Type E not implemented.");
365 reply(200, "Type set to I.");
371 if (cmd_bytesz == 8) {
373 "Type set to L (byte size 8).");
376 reply(504, "Byte size must be 8.");
377 #else /* CHAR_BIT == 8 */
378 UNIMPLEMENTED for CHAR_BIT != 8
379 #endif /* CHAR_BIT == 8 */
383 | STRU check_login SP struct_code CRLF
389 reply(200, "STRU F accepted.");
393 reply(504, "Unimplemented STRU type.");
397 | MODE check_login SP mode_code CRLF
403 reply(200, "MODE S accepted.");
407 reply(502, "Unimplemented MODE type.");
411 | ALLO check_login SP NUMBER CRLF
414 reply(202, "ALLO command ignored.");
417 | ALLO check_login SP NUMBER SP R SP NUMBER CRLF
420 reply(202, "ALLO command ignored.");
423 | RETR check_login SP pathname CRLF
425 if (noretr || (guest && noguestretr))
426 reply(500, "RETR command disabled.");
427 else if ($2 && $4 != NULL)
433 | STOR check_login_ro SP pathname CRLF
435 if ($2 && $4 != NULL)
440 | APPE check_login_ro SP pathname CRLF
442 if ($2 && $4 != NULL)
447 | NLST check_login CRLF
452 | NLST check_login SP pathstring CRLF
458 | LIST check_login CRLF
461 retrieve(_PATH_LS " -lgA", "");
463 | LIST check_login SP pathstring CRLF
466 retrieve(_PATH_LS " -lgA %s", $4);
469 | STAT check_login SP pathname CRLF
471 if ($2 && $4 != NULL)
476 | STAT check_login CRLF
482 | DELE check_login_ro SP pathname CRLF
484 if ($2 && $4 != NULL)
489 | RNTO check_login_ro SP pathname CRLF
491 if ($2 && $4 != NULL) {
493 renamecmd(fromname, $4);
497 reply(503, "Bad sequence of commands.");
503 | ABOR check_login CRLF
506 reply(225, "ABOR command successful.");
508 | CWD check_login CRLF
514 | CWD check_login SP pathname CRLF
516 if ($2 && $4 != NULL)
525 | HELP SP STRING CRLF
529 if (strncasecmp(cp, "SITE", 4) == 0) {
543 reply(200, "NOOP command successful.");
545 | MKD check_login_ro SP pathname CRLF
547 if ($2 && $4 != NULL)
552 | RMD check_login_ro SP pathname CRLF
554 if ($2 && $4 != NULL)
559 | PWD check_login CRLF
564 | CDUP check_login CRLF
573 | SITE SP HELP SP STRING CRLF
578 | SITE SP MDFIVE check_login SP pathname CRLF
585 reply(200, "MD5(%s) = %s", $6, p);
587 perror_reply(550, $6);
592 | SITE SP UMASK check_login CRLF
598 (void) umask(oldmask);
599 reply(200, "Current UMASK is %03o.", oldmask);
602 | SITE SP UMASK check_login SP octal_number CRLF
607 if (($6 == -1) || ($6 > 0777)) {
608 reply(501, "Bad UMASK value.");
612 "UMASK set to %03o (was %03o).",
617 | SITE SP CHMOD check_login_ro SP octal_number SP pathname CRLF
619 if ($4 && ($8 != NULL)) {
620 if (($6 == -1 ) || ($6 > 0777))
621 reply(501, "Bad mode value.");
622 else if (chmod($8, $6) < 0)
623 perror_reply(550, $8);
625 reply(200, "CHMOD command successful.");
630 | SITE SP check_login IDLE CRLF
634 "Current IDLE time limit is %d seconds; max %d.",
635 timeout, maxtimeout);
637 | SITE SP check_login IDLE SP NUMBER CRLF
640 if ($6.i < 30 || $6.i > maxtimeout) {
642 "Maximum IDLE time must be between 30 and %d seconds.",
646 (void) alarm(timeout);
648 "Maximum IDLE time set to %d seconds.",
653 | STOU check_login_ro SP pathname CRLF
655 if ($2 && $4 != NULL)
662 lreply(211, "Extensions supported:");
664 /* XXX these two keywords are non-standard */
670 printf(" REST STREAM\r\n");
673 /* TVFS requires UTF8, see RFC 3659 */
679 | SYST check_login CRLF
684 reply(215, "UNIX Type: L%d Version: BSD-%d",
687 reply(215, "UNIX Type: L%d", CHAR_BIT);
690 reply(215, "UNKNOWN Type: L%d", CHAR_BIT);
695 * SIZE is not in RFC959, but Postel has blessed it and
696 * it will be in the updated RFC.
698 * Return size of file in a format suitable for
699 * using with RESTART (we just count bytes).
701 | SIZE check_login SP pathname CRLF
703 if ($2 && $4 != NULL)
710 * MDTM is not in RFC959, but Postel has blessed it and
711 * it will be in the updated RFC.
713 * Return modification time of file as an ISO 3307
714 * style time. E.g. YYYYMMDDHHMMSS or YYYYMMDDHHMMSS.xxx
715 * where xxx is the fractional second (of any precision,
716 * not necessarily 3 digits)
718 | MDTM check_login SP pathname CRLF
720 if ($2 && $4 != NULL) {
722 if (stat($4, &stbuf) < 0)
723 perror_reply(550, $4);
724 else if (!S_ISREG(stbuf.st_mode)) {
725 reply(550, "%s: not a plain file.", $4);
728 t = gmtime(&stbuf.st_mtime);
730 "%04d%02d%02d%02d%02d%02d",
732 t->tm_mon+1, t->tm_mday,
733 t->tm_hour, t->tm_min, t->tm_sec);
741 reply(221, "Goodbye.");
750 yyclearin; /* discard lookahead data */
751 yyerrok; /* clear error condition */
752 state = CMD; /* reset lexer state */
756 : RNFR check_login_ro SP pathname CRLF
771 | REST check_login SP NUMBER CRLF
777 restart_point = $4.o;
778 reply(350, "Restarting at %jd. %s",
779 (intmax_t)restart_point,
780 "Send STORE or RETRIEVE to initiate transfer.");
792 $$ = (char *)calloc(1, sizeof(char));
805 : NUMBER COMMA NUMBER COMMA NUMBER COMMA NUMBER COMMA
810 data_dest.su_len = sizeof(struct sockaddr_in);
811 data_dest.su_family = AF_INET;
812 p = (char *)&data_dest.su_sin.sin_port;
813 p[0] = $9.i; p[1] = $11.i;
814 a = (char *)&data_dest.su_sin.sin_addr;
815 a[0] = $1.i; a[1] = $3.i; a[2] = $5.i; a[3] = $7.i;
820 : NUMBER COMMA NUMBER COMMA NUMBER COMMA NUMBER COMMA
821 NUMBER COMMA NUMBER COMMA NUMBER COMMA NUMBER COMMA
822 NUMBER COMMA NUMBER COMMA NUMBER COMMA NUMBER COMMA
823 NUMBER COMMA NUMBER COMMA NUMBER COMMA NUMBER COMMA
824 NUMBER COMMA NUMBER COMMA NUMBER COMMA NUMBER COMMA
829 memset(&data_dest, 0, sizeof(data_dest));
830 data_dest.su_len = sizeof(struct sockaddr_in6);
831 data_dest.su_family = AF_INET6;
832 p = (char *)&data_dest.su_port;
833 p[0] = $39.i; p[1] = $41.i;
834 a = (char *)&data_dest.su_sin6.sin6_addr;
835 a[0] = $5.i; a[1] = $7.i; a[2] = $9.i; a[3] = $11.i;
836 a[4] = $13.i; a[5] = $15.i; a[6] = $17.i; a[7] = $19.i;
837 a[8] = $21.i; a[9] = $23.i; a[10] = $25.i; a[11] = $27.i;
838 a[12] = $29.i; a[13] = $31.i; a[14] = $33.i; a[15] = $35.i;
839 if (his_addr.su_family == AF_INET6) {
840 /* XXX more sanity checks! */
841 data_dest.su_sin6.sin6_scope_id =
842 his_addr.su_sin6.sin6_scope_id;
844 if ($1.i != 6 || $3.i != 16 || $37.i != 2)
845 memset(&data_dest, 0, sizeof(data_dest));
847 | NUMBER COMMA NUMBER COMMA NUMBER COMMA NUMBER COMMA
848 NUMBER COMMA NUMBER COMMA NUMBER COMMA NUMBER COMMA
853 memset(&data_dest, 0, sizeof(data_dest));
854 data_dest.su_sin.sin_len = sizeof(struct sockaddr_in);
855 data_dest.su_family = AF_INET;
856 p = (char *)&data_dest.su_port;
857 p[0] = $15.i; p[1] = $17.i;
858 a = (char *)&data_dest.su_sin.sin_addr;
859 a[0] = $5.i; a[1] = $7.i; a[2] = $9.i; a[3] = $11.i;
860 if ($1.i != 4 || $3.i != 4 || $13.i != 2)
861 memset(&data_dest, 0, sizeof(data_dest));
908 cmd_bytesz = CHAR_BIT;
915 /* this is for a bug in the BBN ftp */
956 if (logged_in && $1) {
960 * Expand ~user manually since glob(3)
961 * will return the unexpanded pathname
962 * if the corresponding file/directory
963 * doesn't exist yet. Using sole glob(3)
964 * would break natural commands like
969 if ((p = exptilde($1)) != NULL) {
987 int ret, dec, multby, digit;
990 * Convert a number that was read as decimal number
991 * to what it would be if it had been read as octal.
1002 ret += digit * multby;
1014 $$ = check_login1();
1022 reply(500, "EPSV command disabled.");
1026 $$ = check_login1();
1034 reply(550, "Permission denied.");
1038 $$ = check_login1();
1044 #define CMD 0 /* beginning of command */
1045 #define ARGS 1 /* expect miscellaneous arguments */
1046 #define STR1 2 /* expect SP followed by STRING */
1047 #define STR2 3 /* expect STRING */
1048 #define OSTR 4 /* optional SP then STRING */
1049 #define ZSTR1 5 /* optional SP then optional STRING */
1050 #define ZSTR2 6 /* optional STRING after SP */
1051 #define SITECMD 7 /* SITE command */
1052 #define NSTR 8 /* Number followed by a string */
1054 #define MAXGLOBARGS 1000
1056 #define MAXASIZE 10240 /* Deny ASCII SIZE on files larger than that */
1062 short implemented; /* 1 if command is implemented */
1066 struct tab cmdtab[] = { /* In order defined in RFC 765 */
1067 { "USER", USER, STR1, 1, "<sp> username" },
1068 { "PASS", PASS, ZSTR1, 1, "[<sp> [password]]" },
1069 { "ACCT", ACCT, STR1, 0, "(specify account)" },
1070 { "SMNT", SMNT, ARGS, 0, "(structure mount)" },
1071 { "REIN", REIN, ARGS, 0, "(reinitialize server state)" },
1072 { "QUIT", QUIT, ARGS, 1, "(terminate service)", },
1073 { "PORT", PORT, ARGS, 1, "<sp> b0, b1, b2, b3, b4, b5" },
1074 { "LPRT", LPRT, ARGS, 1, "<sp> af, hal, h1, h2, h3,..., pal, p1, p2..." },
1075 { "EPRT", EPRT, STR1, 1, "<sp> |af|addr|port|" },
1076 { "PASV", PASV, ARGS, 1, "(set server in passive mode)" },
1077 { "LPSV", LPSV, ARGS, 1, "(set server in passive mode)" },
1078 { "EPSV", EPSV, ARGS, 1, "[<sp> af|ALL]" },
1079 { "TYPE", TYPE, ARGS, 1, "<sp> { A | E | I | L }" },
1080 { "STRU", STRU, ARGS, 1, "(specify file structure)" },
1081 { "MODE", MODE, ARGS, 1, "(specify transfer mode)" },
1082 { "RETR", RETR, STR1, 1, "<sp> file-name" },
1083 { "STOR", STOR, STR1, 1, "<sp> file-name" },
1084 { "APPE", APPE, STR1, 1, "<sp> file-name" },
1085 { "MLFL", MLFL, OSTR, 0, "(mail file)" },
1086 { "MAIL", MAIL, OSTR, 0, "(mail to user)" },
1087 { "MSND", MSND, OSTR, 0, "(mail send to terminal)" },
1088 { "MSOM", MSOM, OSTR, 0, "(mail send to terminal or mailbox)" },
1089 { "MSAM", MSAM, OSTR, 0, "(mail send to terminal and mailbox)" },
1090 { "MRSQ", MRSQ, OSTR, 0, "(mail recipient scheme question)" },
1091 { "MRCP", MRCP, STR1, 0, "(mail recipient)" },
1092 { "ALLO", ALLO, ARGS, 1, "allocate storage (vacuously)" },
1093 { "REST", REST, ARGS, 1, "<sp> offset (restart command)" },
1094 { "RNFR", RNFR, STR1, 1, "<sp> file-name" },
1095 { "RNTO", RNTO, STR1, 1, "<sp> file-name" },
1096 { "ABOR", ABOR, ARGS, 1, "(abort operation)" },
1097 { "DELE", DELE, STR1, 1, "<sp> file-name" },
1098 { "CWD", CWD, OSTR, 1, "[ <sp> directory-name ]" },
1099 { "XCWD", CWD, OSTR, 1, "[ <sp> directory-name ]" },
1100 { "LIST", LIST, OSTR, 1, "[ <sp> path-name ]" },
1101 { "NLST", NLST, OSTR, 1, "[ <sp> path-name ]" },
1102 { "SITE", SITE, SITECMD, 1, "site-cmd [ <sp> arguments ]" },
1103 { "SYST", SYST, ARGS, 1, "(get type of operating system)" },
1104 { "FEAT", FEAT, ARGS, 1, "(get extended features)" },
1105 { "STAT", STAT, OSTR, 1, "[ <sp> path-name ]" },
1106 { "HELP", HELP, OSTR, 1, "[ <sp> <string> ]" },
1107 { "NOOP", NOOP, ARGS, 1, "" },
1108 { "MKD", MKD, STR1, 1, "<sp> path-name" },
1109 { "XMKD", MKD, STR1, 1, "<sp> path-name" },
1110 { "RMD", RMD, STR1, 1, "<sp> path-name" },
1111 { "XRMD", RMD, STR1, 1, "<sp> path-name" },
1112 { "PWD", PWD, ARGS, 1, "(return current directory)" },
1113 { "XPWD", PWD, ARGS, 1, "(return current directory)" },
1114 { "CDUP", CDUP, ARGS, 1, "(change to parent directory)" },
1115 { "XCUP", CDUP, ARGS, 1, "(change to parent directory)" },
1116 { "STOU", STOU, STR1, 1, "<sp> file-name" },
1117 { "SIZE", SIZE, OSTR, 1, "<sp> path-name" },
1118 { "MDTM", MDTM, OSTR, 1, "<sp> path-name" },
1119 { NULL, 0, 0, 0, 0 }
1122 struct tab sitetab[] = {
1123 { "MD5", MDFIVE, STR1, 1, "[ <sp> file-name ]" },
1124 { "UMASK", UMASK, ARGS, 1, "[ <sp> umask ]" },
1125 { "IDLE", IDLE, ARGS, 1, "[ <sp> maximum-idle-time ]" },
1126 { "CHMOD", CHMOD, NSTR, 1, "<sp> mode <sp> file-name" },
1127 { "HELP", HELP, OSTR, 1, "[ <sp> <string> ]" },
1128 { NULL, 0, 0, 0, 0 }
1131 static char *copy(char *);
1132 static char *expglob(char *);
1133 static char *exptilde(char *);
1134 static void help(struct tab *, char *);
1136 lookup(struct tab *, char *);
1137 static int port_check(const char *);
1139 static int port_check_v6(const char *);
1141 static void sizecmd(char *);
1142 static void toolong(int);
1144 static void v4map_data_dest(void);
1146 static int yylex(void);
1149 lookup(struct tab *p, char *cmd)
1152 for (; p->name != NULL; p++)
1153 if (strcmp(cmd, p->name) == 0)
1158 #include <arpa/telnet.h>
1161 * get_line - a hacked up version of fgets to ignore TELNET escape codes.
1164 get_line(char *s, int n, FILE *iop)
1168 sigset_t sset, osset;
1171 /* tmpline may contain saved command from urgent mode interruption */
1172 for (c = 0; tmpline[c] != '\0' && --n > 0; ++c) {
1174 if (tmpline[c] == '\n') {
1177 syslog(LOG_DEBUG, "command: %s", s);
1184 /* SIGURG would interrupt stdio if not blocked during the read loop */
1186 sigaddset(&sset, SIGURG);
1187 sigprocmask(SIG_BLOCK, &sset, &osset);
1188 while ((c = getc(iop)) != EOF) {
1191 if ((c = getc(iop)) == EOF)
1197 if ((c = getc(iop)) == EOF)
1199 printf("%c%c%c", IAC, DONT, 0377&c);
1200 (void) fflush(stdout);
1204 if ((c = getc(iop)) == EOF)
1206 printf("%c%c%c", IAC, WONT, 0377&c);
1207 (void) fflush(stdout);
1212 continue; /* ignore command */
1218 * If command doesn't fit into buffer, discard the
1219 * rest of the command and indicate truncation.
1220 * This prevents the command to be split up into
1221 * multiple commands.
1223 while (c != '\n' && (c = getc(iop)) != EOF)
1231 sigprocmask(SIG_SETMASK, &osset, NULL);
1232 if (c == EOF && cs == s)
1236 if (!guest && strncasecmp("pass ", s, 5) == 0) {
1237 /* Don't syslog passwords */
1238 syslog(LOG_DEBUG, "command: %.5s ???", s);
1243 /* Don't syslog trailing CR-LF */
1246 while (cp >= s && (*cp == '\n' || *cp == '\r')) {
1250 syslog(LOG_DEBUG, "command: %.*s", len, s);
1261 "Timeout (%d seconds): closing control connection.", timeout);
1263 syslog(LOG_INFO, "User %s timed out after %d seconds",
1264 (pw ? pw -> pw_name : "unknown"), timeout);
1281 (void) signal(SIGALRM, toolong);
1282 (void) alarm(timeout);
1283 n = get_line(cbuf, sizeof(cbuf)-1, stdin);
1285 reply(221, "You could at least say goodbye.");
1287 } else if (n == -2) {
1288 reply(500, "Command too long.");
1294 if (strncasecmp(cbuf, "PASS", 4) != 0)
1295 setproctitle("%s: %s", proctitle, cbuf);
1296 #endif /* SETPROCTITLE */
1297 if ((cp = strchr(cbuf, '\r'))) {
1301 if ((cp = strpbrk(cbuf, " \n")))
1308 p = lookup(cmdtab, cbuf);
1312 if (!p->implemented)
1313 return (NOTIMPL); /* state remains CMD */
1320 if (cbuf[cpos] == ' ') {
1325 if ((cp2 = strpbrk(cp, " \n")))
1330 p = lookup(sitetab, cp);
1332 if (guest == 0 && p != 0) {
1334 if (!p->implemented) {
1346 if (cbuf[cpos] == '\n') {
1354 if (cbuf[cpos] == ' ') {
1356 state = state == OSTR ? STR2 : state+1;
1362 if (cbuf[cpos] == '\n') {
1373 * Make sure the string is nonempty and \n terminated.
1375 if (n > 1 && cbuf[cpos] == '\n') {
1377 yylval.s = copy(cp);
1385 if (cbuf[cpos] == ' ') {
1389 if (isdigit(cbuf[cpos])) {
1391 while (isdigit(cbuf[++cpos]))
1395 yylval.u.i = atoi(cp);
1404 if (isdigit(cbuf[cpos])) {
1406 while (isdigit(cbuf[++cpos]))
1410 yylval.u.i = atoi(cp);
1411 yylval.u.o = strtoull(cp, NULL, 10);
1415 if (strncasecmp(&cbuf[cpos], "ALL", 3) == 0
1416 && !isalnum(cbuf[cpos + 3])) {
1420 switch (cbuf[cpos++]) {
1484 fatalerror("Unknown state in scanner.");
1494 while (*s != '\0') {
1506 p = malloc(strlen(s) + 1);
1508 fatalerror("Ran out of memory.");
1509 (void) strcpy(p, s);
1514 help(struct tab *ctab, char *s)
1520 if (ctab == sitetab)
1524 width = 0, NCMDS = 0;
1525 for (c = ctab; c->name != NULL; c++) {
1526 int len = strlen(c->name);
1532 width = (width + 8) &~ 7;
1537 lreply(214, "The following %scommands are recognized %s.",
1538 type, "(* =>'s unimplemented)");
1539 columns = 76 / width;
1542 lines = (NCMDS + columns - 1) / columns;
1543 for (i = 0; i < lines; i++) {
1545 for (j = 0; j < columns; j++) {
1546 c = ctab + j * lines + i;
1547 printf("%s%c", c->name,
1548 c->implemented ? ' ' : '*');
1549 if (c + lines >= &ctab[NCMDS])
1551 w = strlen(c->name) + 1;
1559 (void) fflush(stdout);
1561 reply(214, "Direct comments to ftp-bugs@%s.", hostname);
1567 c = lookup(ctab, s);
1569 reply(502, "Unknown command %s.", s);
1573 reply(214, "Syntax: %s%s %s", type, c->name, c->help);
1575 reply(214, "%s%-*s\t%s; unimplemented.", type, width,
1580 sizecmd(char *filename)
1586 if (stat(filename, &stbuf) < 0)
1587 perror_reply(550, filename);
1588 else if (!S_ISREG(stbuf.st_mode))
1589 reply(550, "%s: not a plain file.", filename);
1591 reply(213, "%jd", (intmax_t)stbuf.st_size);
1598 fin = fopen(filename, "r");
1600 perror_reply(550, filename);
1603 if (fstat(fileno(fin), &stbuf) < 0) {
1604 perror_reply(550, filename);
1607 } else if (!S_ISREG(stbuf.st_mode)) {
1608 reply(550, "%s: not a plain file.", filename);
1611 } else if (stbuf.st_size > MAXASIZE) {
1612 reply(550, "%s: too large for type A SIZE.", filename);
1618 while((c=getc(fin)) != EOF) {
1619 if (c == '\n') /* will get expanded to \r\n */
1625 reply(213, "%jd", (intmax_t)count);
1628 reply(504, "SIZE not implemented for type %s.",
1633 /* Return 1, if port check is done. Return 0, if not yet. */
1635 port_check(const char *pcmd)
1637 if (his_addr.su_family == AF_INET) {
1638 if (data_dest.su_family != AF_INET) {
1640 reply(500, "Invalid address rejected.");
1644 ((ntohs(data_dest.su_port) < IPPORT_RESERVED) ||
1645 memcmp(&data_dest.su_sin.sin_addr,
1646 &his_addr.su_sin.sin_addr,
1647 sizeof(data_dest.su_sin.sin_addr)))) {
1649 reply(500, "Illegal PORT range rejected.");
1653 (void) close(pdata);
1656 reply(200, "%s command successful.", pcmd);
1669 reply(530, "Please login with USER and PASS.");
1675 * Replace leading "~user" in a pathname by the user's login directory.
1676 * Returned string will be in a freshly malloced buffer unless it's NULL.
1685 if ((p = strdup(s)) == NULL)
1690 user = p + 1; /* skip tilde */
1691 if ((path = strchr(p, '/')) != NULL)
1692 *(path++) = '\0'; /* separate ~user from the rest of path */
1693 if (*user == '\0') /* no user specified, use the current user */
1695 /* read passwd even for the current user since we may be chrooted */
1696 if ((ppw = getpwnam(user)) != NULL) {
1697 /* user found, substitute login directory for ~user */
1699 asprintf(&q, "%s/%s", ppw->pw_dir, path);
1701 q = strdup(ppw->pw_dir);
1705 /* user not found, undo the damage */
1713 * Expand glob(3) patterns possibly present in a pathname.
1714 * Avoid expanding to a pathname including '\r' or '\n' in order to
1715 * not disrupt the FTP protocol.
1716 * The expansion found must be unique.
1717 * Return the result as a malloced string, or NULL if an error occurred.
1719 * Problem: this production is used for all pathname
1720 * processing, but only gives a 550 error reply.
1721 * This is a valid reply in some cases but not in others.
1726 char *p, **pp, *rval;
1727 int flags = GLOB_BRACE | GLOB_NOCHECK;
1731 memset(&gl, 0, sizeof(gl));
1732 flags |= GLOB_LIMIT;
1733 gl.gl_matchc = MAXGLOBARGS;
1734 if (glob(s, flags, NULL, &gl) == 0 && gl.gl_pathc != 0) {
1735 for (pp = gl.gl_pathv, p = NULL, n = 0; *pp; pp++)
1736 if (*(*pp + strcspn(*pp, "\r\n")) == '\0') {
1745 reply(550, "Wildcard is ambiguous.");
1749 reply(550, "Wildcard expansion error.");
1757 /* Return 1, if port check is done. Return 0, if not yet. */
1759 port_check_v6(const char *pcmd)
1761 if (his_addr.su_family == AF_INET6) {
1762 if (IN6_IS_ADDR_V4MAPPED(&his_addr.su_sin6.sin6_addr))
1763 /* Convert data_dest into v4 mapped sockaddr.*/
1765 if (data_dest.su_family != AF_INET6) {
1767 reply(500, "Invalid address rejected.");
1771 ((ntohs(data_dest.su_port) < IPPORT_RESERVED) ||
1772 memcmp(&data_dest.su_sin6.sin6_addr,
1773 &his_addr.su_sin6.sin6_addr,
1774 sizeof(data_dest.su_sin6.sin6_addr)))) {
1776 reply(500, "Illegal PORT range rejected.");
1780 (void) close(pdata);
1783 reply(200, "%s command successful.", pcmd);
1791 v4map_data_dest(void)
1793 struct in_addr savedaddr;
1796 if (data_dest.su_family != AF_INET) {
1798 reply(500, "Invalid address rejected.");
1802 savedaddr = data_dest.su_sin.sin_addr;
1803 savedport = data_dest.su_port;
1805 memset(&data_dest, 0, sizeof(data_dest));
1806 data_dest.su_sin6.sin6_len = sizeof(struct sockaddr_in6);
1807 data_dest.su_sin6.sin6_family = AF_INET6;
1808 data_dest.su_sin6.sin6_port = savedport;
1809 memset((caddr_t)&data_dest.su_sin6.sin6_addr.s6_addr[10], 0xff, 2);
1810 memcpy((caddr_t)&data_dest.su_sin6.sin6_addr.s6_addr[12],
1811 (caddr_t)&savedaddr, sizeof(savedaddr));