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";
49 #include <sys/cdefs.h>
50 __FBSDID("$FreeBSD$");
52 #include <sys/param.h>
53 #include <sys/socket.h>
56 #include <netinet/in.h>
77 #include "pathnames.h"
79 extern union sockunion data_dest, his_addr;
82 extern struct passwd *pw;
91 extern int maxtimeout;
93 extern char *hostname;
94 extern char proctitle[];
95 extern int usedefault;
96 extern char tmpline[];
98 extern int assumeutf8;
101 extern int noguestretr;
102 extern char *typenames[]; /* defined in <arpa/ftp.h> included from ftpd.c */
108 static int cmd_bytesz;
111 char *fromname = NULL;
132 USER PASS ACCT REIN QUIT PORT
133 PASV TYPE STRU MODE RETR STOR
134 APPE MLFL MAIL MSND MSOM MSAM
135 MRSQ MRCP ALLO REST RNFR RNTO
136 ABOR DELE CWD LIST NLST SITE
137 STAT HELP NOOP MKD RMD PWD
138 CDUP STOU SMNT SYST SIZE MDTM
139 LPRT LPSV EPRT EPSV FEAT
141 UMASK IDLE CHMOD MDFIVE
148 %type <u.i> check_login octal_number byte_size
149 %type <u.i> check_login_ro check_login_epsv
150 %type <u.i> struct_code mode_code type_code form_code
151 %type <s> pathstring pathname password username
152 %type <s> ALL NOTIMPL
171 : USER SP username CRLF
176 | PASS SP password CRLF
185 | PORT check_login SP host_port CRLF
188 reply(501, "No PORT allowed after EPSV ALL.");
193 if (port_check("PORT") == 1)
196 if ((his_addr.su_family != AF_INET6 ||
197 !IN6_IS_ADDR_V4MAPPED(&his_addr.su_sin6.sin6_addr))) {
198 /* shoud never happen */
200 reply(500, "Invalid address rejected.");
203 port_check_v6("pcmd");
208 | LPRT check_login SP host_long_port CRLF
211 reply(501, "No LPRT allowed after EPSV ALL.");
216 if (port_check("LPRT") == 1)
219 if (his_addr.su_family != AF_INET6) {
221 reply(500, "Invalid address rejected.");
224 if (port_check_v6("LPRT") == 1)
230 | EPRT check_login SP STRING CRLF
236 struct addrinfo hints;
237 struct addrinfo *res;
241 reply(501, "No EPRT allowed after EPSV ALL.");
247 memset(&data_dest, 0, sizeof(data_dest));
250 syslog(LOG_DEBUG, "%s", tmp);
252 fatalerror("not enough core");
258 memset(result, 0, sizeof(result));
259 for (i = 0; i < 3; i++) {
260 q = strchr(p, delim);
261 if (!q || *q != delim) {
264 "Invalid argument, rejected.");
273 syslog(LOG_DEBUG, "%d: %s", i, p);
277 /* some more sanity check */
292 memset(&hints, 0, sizeof(hints));
293 if (atoi(result[0]) == 1)
294 hints.ai_family = PF_INET;
296 else if (atoi(result[0]) == 2)
297 hints.ai_family = PF_INET6;
300 hints.ai_family = PF_UNSPEC; /*XXX*/
301 hints.ai_socktype = SOCK_STREAM;
302 i = getaddrinfo(result[1], result[2], &hints, &res);
305 memcpy(&data_dest, res->ai_addr, res->ai_addrlen);
307 if (his_addr.su_family == AF_INET6
308 && data_dest.su_family == AF_INET6) {
309 /* XXX more sanity checks! */
310 data_dest.su_sin6.sin6_scope_id =
311 his_addr.su_sin6.sin6_scope_id;
317 if (port_check("EPRT") == 1)
320 if (his_addr.su_family != AF_INET6) {
322 reply(500, "Invalid address rejected.");
325 if (port_check_v6("EPRT") == 1)
331 | PASV check_login CRLF
334 reply(501, "No PASV allowed after EPSV ALL.");
338 | LPSV check_login CRLF
341 reply(501, "No LPSV allowed after EPSV ALL.");
343 long_passive("LPSV", PF_UNSPEC);
345 | EPSV check_login_epsv SP NUMBER CRLF
359 pf = -1; /*junk value*/
362 long_passive("EPSV", pf);
365 | EPSV check_login_epsv SP ALL CRLF
368 reply(200, "EPSV ALL command successful.");
372 | EPSV check_login_epsv CRLF
375 long_passive("EPSV", PF_UNSPEC);
377 | TYPE check_login SP type_code CRLF
383 if (cmd_form == FORM_N) {
384 reply(200, "Type set to A.");
388 reply(504, "Form must be N.");
392 reply(504, "Type E not implemented.");
396 reply(200, "Type set to I.");
402 if (cmd_bytesz == 8) {
404 "Type set to L (byte size 8).");
407 reply(504, "Byte size must be 8.");
408 #else /* CHAR_BIT == 8 */
409 UNIMPLEMENTED for CHAR_BIT != 8
410 #endif /* CHAR_BIT == 8 */
414 | STRU check_login SP struct_code CRLF
420 reply(200, "STRU F accepted.");
424 reply(504, "Unimplemented STRU type.");
428 | MODE check_login SP mode_code CRLF
434 reply(200, "MODE S accepted.");
438 reply(502, "Unimplemented MODE type.");
442 | ALLO check_login SP NUMBER CRLF
445 reply(202, "ALLO command ignored.");
448 | ALLO check_login SP NUMBER SP R SP NUMBER CRLF
451 reply(202, "ALLO command ignored.");
454 | RETR check_login SP pathname CRLF
456 if (noretr || (guest && noguestretr))
457 reply(500, "RETR command disabled.");
458 else if ($2 && $4 != NULL)
464 | STOR check_login_ro SP pathname CRLF
466 if ($2 && $4 != NULL)
471 | APPE check_login_ro SP pathname CRLF
473 if ($2 && $4 != NULL)
478 | NLST check_login CRLF
483 | NLST check_login SP pathstring CRLF
489 | LIST check_login CRLF
492 retrieve(_PATH_LS " -lgA", "");
494 | LIST check_login SP pathstring CRLF
497 retrieve(_PATH_LS " -lgA %s", $4);
500 | STAT check_login SP pathname CRLF
502 if ($2 && $4 != NULL)
507 | STAT check_login CRLF
513 | DELE check_login_ro SP pathname CRLF
515 if ($2 && $4 != NULL)
520 | RNTO check_login_ro SP pathname CRLF
522 if ($2 && $4 != NULL) {
524 renamecmd(fromname, $4);
528 reply(503, "Bad sequence of commands.");
534 | ABOR check_login CRLF
537 reply(225, "ABOR command successful.");
539 | CWD check_login CRLF
545 | CWD check_login SP pathname CRLF
547 if ($2 && $4 != NULL)
556 | HELP SP STRING CRLF
560 if (strncasecmp(cp, "SITE", 4) == 0) {
574 reply(200, "NOOP command successful.");
576 | MKD check_login_ro SP pathname CRLF
578 if ($2 && $4 != NULL)
583 | RMD check_login_ro SP pathname CRLF
585 if ($2 && $4 != NULL)
590 | PWD check_login CRLF
595 | CDUP check_login CRLF
604 | SITE SP HELP SP STRING CRLF
609 | SITE SP MDFIVE check_login SP pathname CRLF
616 reply(200, "MD5(%s) = %s", $6, p);
618 perror_reply(550, $6);
623 | SITE SP UMASK check_login CRLF
629 (void) umask(oldmask);
630 reply(200, "Current UMASK is %03o.", oldmask);
633 | SITE SP UMASK check_login SP octal_number CRLF
638 if (($6 == -1) || ($6 > 0777)) {
639 reply(501, "Bad UMASK value.");
643 "UMASK set to %03o (was %03o).",
648 | SITE SP CHMOD check_login_ro SP octal_number SP pathname CRLF
650 if ($4 && ($8 != NULL)) {
651 if (($6 == -1 ) || ($6 > 0777))
652 reply(501, "Bad mode value.");
653 else if (chmod($8, $6) < 0)
654 perror_reply(550, $8);
656 reply(200, "CHMOD command successful.");
661 | SITE SP check_login IDLE CRLF
665 "Current IDLE time limit is %d seconds; max %d.",
666 timeout, maxtimeout);
668 | SITE SP check_login IDLE SP NUMBER CRLF
671 if ($6.i < 30 || $6.i > maxtimeout) {
673 "Maximum IDLE time must be between 30 and %d seconds.",
677 (void) alarm(timeout);
679 "Maximum IDLE time set to %d seconds.",
684 | STOU check_login_ro SP pathname CRLF
686 if ($2 && $4 != NULL)
693 lreply(211, "Extensions supported:");
695 /* XXX these two keywords are non-standard */
701 printf(" REST STREAM\r\n");
704 /* TVFS requires UTF8, see RFC 3659 */
710 | SYST check_login CRLF
715 reply(215, "UNIX Type: L%d Version: BSD-%d",
718 reply(215, "UNIX Type: L%d", CHAR_BIT);
721 reply(215, "UNKNOWN Type: L%d", CHAR_BIT);
726 * SIZE is not in RFC959, but Postel has blessed it and
727 * it will be in the updated RFC.
729 * Return size of file in a format suitable for
730 * using with RESTART (we just count bytes).
732 | SIZE check_login SP pathname CRLF
734 if ($2 && $4 != NULL)
741 * MDTM is not in RFC959, but Postel has blessed it and
742 * it will be in the updated RFC.
744 * Return modification time of file as an ISO 3307
745 * style time. E.g. YYYYMMDDHHMMSS or YYYYMMDDHHMMSS.xxx
746 * where xxx is the fractional second (of any precision,
747 * not necessarily 3 digits)
749 | MDTM check_login SP pathname CRLF
751 if ($2 && $4 != NULL) {
753 if (stat($4, &stbuf) < 0)
754 perror_reply(550, $4);
755 else if (!S_ISREG(stbuf.st_mode)) {
756 reply(550, "%s: not a plain file.", $4);
759 t = gmtime(&stbuf.st_mtime);
761 "%04d%02d%02d%02d%02d%02d",
763 t->tm_mon+1, t->tm_mday,
764 t->tm_hour, t->tm_min, t->tm_sec);
772 reply(221, "Goodbye.");
781 yyclearin; /* discard lookahead data */
782 yyerrok; /* clear error condition */
783 state = CMD; /* reset lexer state */
787 : RNFR check_login_ro SP pathname CRLF
802 | REST check_login SP NUMBER CRLF
808 restart_point = $4.o;
809 reply(350, "Restarting at %jd. %s",
810 (intmax_t)restart_point,
811 "Send STORE or RETRIEVE to initiate transfer.");
823 $$ = (char *)calloc(1, sizeof(char));
836 : NUMBER COMMA NUMBER COMMA NUMBER COMMA NUMBER COMMA
841 data_dest.su_len = sizeof(struct sockaddr_in);
842 data_dest.su_family = AF_INET;
843 p = (char *)&data_dest.su_sin.sin_port;
844 p[0] = $9.i; p[1] = $11.i;
845 a = (char *)&data_dest.su_sin.sin_addr;
846 a[0] = $1.i; a[1] = $3.i; a[2] = $5.i; a[3] = $7.i;
851 : NUMBER COMMA NUMBER COMMA NUMBER COMMA NUMBER COMMA
852 NUMBER COMMA NUMBER COMMA NUMBER COMMA NUMBER COMMA
853 NUMBER COMMA NUMBER COMMA NUMBER COMMA NUMBER COMMA
854 NUMBER COMMA NUMBER COMMA NUMBER COMMA NUMBER COMMA
855 NUMBER COMMA NUMBER COMMA NUMBER COMMA NUMBER COMMA
860 memset(&data_dest, 0, sizeof(data_dest));
861 data_dest.su_len = sizeof(struct sockaddr_in6);
862 data_dest.su_family = AF_INET6;
863 p = (char *)&data_dest.su_port;
864 p[0] = $39.i; p[1] = $41.i;
865 a = (char *)&data_dest.su_sin6.sin6_addr;
866 a[0] = $5.i; a[1] = $7.i; a[2] = $9.i; a[3] = $11.i;
867 a[4] = $13.i; a[5] = $15.i; a[6] = $17.i; a[7] = $19.i;
868 a[8] = $21.i; a[9] = $23.i; a[10] = $25.i; a[11] = $27.i;
869 a[12] = $29.i; a[13] = $31.i; a[14] = $33.i; a[15] = $35.i;
870 if (his_addr.su_family == AF_INET6) {
871 /* XXX more sanity checks! */
872 data_dest.su_sin6.sin6_scope_id =
873 his_addr.su_sin6.sin6_scope_id;
875 if ($1.i != 6 || $3.i != 16 || $37.i != 2)
876 memset(&data_dest, 0, sizeof(data_dest));
878 | NUMBER COMMA NUMBER COMMA NUMBER COMMA NUMBER COMMA
879 NUMBER COMMA NUMBER COMMA NUMBER COMMA NUMBER COMMA
884 memset(&data_dest, 0, sizeof(data_dest));
885 data_dest.su_sin.sin_len = sizeof(struct sockaddr_in);
886 data_dest.su_family = AF_INET;
887 p = (char *)&data_dest.su_port;
888 p[0] = $15.i; p[1] = $17.i;
889 a = (char *)&data_dest.su_sin.sin_addr;
890 a[0] = $5.i; a[1] = $7.i; a[2] = $9.i; a[3] = $11.i;
891 if ($1.i != 4 || $3.i != 4 || $13.i != 2)
892 memset(&data_dest, 0, sizeof(data_dest));
939 cmd_bytesz = CHAR_BIT;
946 /* this is for a bug in the BBN ftp */
987 if (logged_in && $1) {
991 * Expand ~user manually since glob(3)
992 * will return the unexpanded pathname
993 * if the corresponding file/directory
994 * doesn't exist yet. Using sole glob(3)
995 * would break natural commands like
1000 if ((p = exptilde($1)) != NULL) {
1018 int ret, dec, multby, digit;
1021 * Convert a number that was read as decimal number
1022 * to what it would be if it had been read as octal.
1033 ret += digit * multby;
1045 $$ = check_login1();
1053 reply(500, "EPSV command disabled.");
1057 $$ = check_login1();
1065 reply(550, "Permission denied.");
1069 $$ = check_login1();
1075 #define CMD 0 /* beginning of command */
1076 #define ARGS 1 /* expect miscellaneous arguments */
1077 #define STR1 2 /* expect SP followed by STRING */
1078 #define STR2 3 /* expect STRING */
1079 #define OSTR 4 /* optional SP then STRING */
1080 #define ZSTR1 5 /* optional SP then optional STRING */
1081 #define ZSTR2 6 /* optional STRING after SP */
1082 #define SITECMD 7 /* SITE command */
1083 #define NSTR 8 /* Number followed by a string */
1085 #define MAXGLOBARGS 1000
1087 #define MAXASIZE 10240 /* Deny ASCII SIZE on files larger than that */
1093 short implemented; /* 1 if command is implemented */
1097 struct tab cmdtab[] = { /* In order defined in RFC 765 */
1098 { "USER", USER, STR1, 1, "<sp> username" },
1099 { "PASS", PASS, ZSTR1, 1, "[<sp> [password]]" },
1100 { "ACCT", ACCT, STR1, 0, "(specify account)" },
1101 { "SMNT", SMNT, ARGS, 0, "(structure mount)" },
1102 { "REIN", REIN, ARGS, 0, "(reinitialize server state)" },
1103 { "QUIT", QUIT, ARGS, 1, "(terminate service)", },
1104 { "PORT", PORT, ARGS, 1, "<sp> b0, b1, b2, b3, b4, b5" },
1105 { "LPRT", LPRT, ARGS, 1, "<sp> af, hal, h1, h2, h3,..., pal, p1, p2..." },
1106 { "EPRT", EPRT, STR1, 1, "<sp> |af|addr|port|" },
1107 { "PASV", PASV, ARGS, 1, "(set server in passive mode)" },
1108 { "LPSV", LPSV, ARGS, 1, "(set server in passive mode)" },
1109 { "EPSV", EPSV, ARGS, 1, "[<sp> af|ALL]" },
1110 { "TYPE", TYPE, ARGS, 1, "<sp> { A | E | I | L }" },
1111 { "STRU", STRU, ARGS, 1, "(specify file structure)" },
1112 { "MODE", MODE, ARGS, 1, "(specify transfer mode)" },
1113 { "RETR", RETR, STR1, 1, "<sp> file-name" },
1114 { "STOR", STOR, STR1, 1, "<sp> file-name" },
1115 { "APPE", APPE, STR1, 1, "<sp> file-name" },
1116 { "MLFL", MLFL, OSTR, 0, "(mail file)" },
1117 { "MAIL", MAIL, OSTR, 0, "(mail to user)" },
1118 { "MSND", MSND, OSTR, 0, "(mail send to terminal)" },
1119 { "MSOM", MSOM, OSTR, 0, "(mail send to terminal or mailbox)" },
1120 { "MSAM", MSAM, OSTR, 0, "(mail send to terminal and mailbox)" },
1121 { "MRSQ", MRSQ, OSTR, 0, "(mail recipient scheme question)" },
1122 { "MRCP", MRCP, STR1, 0, "(mail recipient)" },
1123 { "ALLO", ALLO, ARGS, 1, "allocate storage (vacuously)" },
1124 { "REST", REST, ARGS, 1, "<sp> offset (restart command)" },
1125 { "RNFR", RNFR, STR1, 1, "<sp> file-name" },
1126 { "RNTO", RNTO, STR1, 1, "<sp> file-name" },
1127 { "ABOR", ABOR, ARGS, 1, "(abort operation)" },
1128 { "DELE", DELE, STR1, 1, "<sp> file-name" },
1129 { "CWD", CWD, OSTR, 1, "[ <sp> directory-name ]" },
1130 { "XCWD", CWD, OSTR, 1, "[ <sp> directory-name ]" },
1131 { "LIST", LIST, OSTR, 1, "[ <sp> path-name ]" },
1132 { "NLST", NLST, OSTR, 1, "[ <sp> path-name ]" },
1133 { "SITE", SITE, SITECMD, 1, "site-cmd [ <sp> arguments ]" },
1134 { "SYST", SYST, ARGS, 1, "(get type of operating system)" },
1135 { "FEAT", FEAT, ARGS, 1, "(get extended features)" },
1136 { "STAT", STAT, OSTR, 1, "[ <sp> path-name ]" },
1137 { "HELP", HELP, OSTR, 1, "[ <sp> <string> ]" },
1138 { "NOOP", NOOP, ARGS, 1, "" },
1139 { "MKD", MKD, STR1, 1, "<sp> path-name" },
1140 { "XMKD", MKD, STR1, 1, "<sp> path-name" },
1141 { "RMD", RMD, STR1, 1, "<sp> path-name" },
1142 { "XRMD", RMD, STR1, 1, "<sp> path-name" },
1143 { "PWD", PWD, ARGS, 1, "(return current directory)" },
1144 { "XPWD", PWD, ARGS, 1, "(return current directory)" },
1145 { "CDUP", CDUP, ARGS, 1, "(change to parent directory)" },
1146 { "XCUP", CDUP, ARGS, 1, "(change to parent directory)" },
1147 { "STOU", STOU, STR1, 1, "<sp> file-name" },
1148 { "SIZE", SIZE, OSTR, 1, "<sp> path-name" },
1149 { "MDTM", MDTM, OSTR, 1, "<sp> path-name" },
1150 { NULL, 0, 0, 0, 0 }
1153 struct tab sitetab[] = {
1154 { "MD5", MDFIVE, STR1, 1, "[ <sp> file-name ]" },
1155 { "UMASK", UMASK, ARGS, 1, "[ <sp> umask ]" },
1156 { "IDLE", IDLE, ARGS, 1, "[ <sp> maximum-idle-time ]" },
1157 { "CHMOD", CHMOD, NSTR, 1, "<sp> mode <sp> file-name" },
1158 { "HELP", HELP, OSTR, 1, "[ <sp> <string> ]" },
1159 { NULL, 0, 0, 0, 0 }
1162 static char *copy(char *);
1163 static char *expglob(char *);
1164 static char *exptilde(char *);
1165 static void help(struct tab *, char *);
1167 lookup(struct tab *, char *);
1168 static int port_check(const char *);
1170 static int port_check_v6(const char *);
1172 static void sizecmd(char *);
1173 static void toolong(int);
1175 static void v4map_data_dest(void);
1177 static int yylex(void);
1180 lookup(struct tab *p, char *cmd)
1183 for (; p->name != NULL; p++)
1184 if (strcmp(cmd, p->name) == 0)
1189 #include <arpa/telnet.h>
1192 * getline - a hacked up version of fgets to ignore TELNET escape codes.
1195 getline(char *s, int n, FILE *iop)
1199 sigset_t sset, osset;
1202 /* tmpline may contain saved command from urgent mode interruption */
1203 for (c = 0; tmpline[c] != '\0' && --n > 0; ++c) {
1205 if (tmpline[c] == '\n') {
1208 syslog(LOG_DEBUG, "command: %s", s);
1215 /* SIGURG would interrupt stdio if not blocked during the read loop */
1217 sigaddset(&sset, SIGURG);
1218 sigprocmask(SIG_BLOCK, &sset, &osset);
1219 while ((c = getc(iop)) != EOF) {
1222 if ((c = getc(iop)) == EOF)
1228 if ((c = getc(iop)) == EOF)
1230 printf("%c%c%c", IAC, DONT, 0377&c);
1231 (void) fflush(stdout);
1235 if ((c = getc(iop)) == EOF)
1237 printf("%c%c%c", IAC, WONT, 0377&c);
1238 (void) fflush(stdout);
1243 continue; /* ignore command */
1249 * If command doesn't fit into buffer, discard the
1250 * rest of the command and indicate truncation.
1251 * This prevents the command to be split up into
1252 * multiple commands.
1254 while (c != '\n' && (c = getc(iop)) != EOF)
1262 sigprocmask(SIG_SETMASK, &osset, NULL);
1263 if (c == EOF && cs == s)
1267 if (!guest && strncasecmp("pass ", s, 5) == 0) {
1268 /* Don't syslog passwords */
1269 syslog(LOG_DEBUG, "command: %.5s ???", s);
1274 /* Don't syslog trailing CR-LF */
1277 while (cp >= s && (*cp == '\n' || *cp == '\r')) {
1281 syslog(LOG_DEBUG, "command: %.*s", len, s);
1292 "Timeout (%d seconds): closing control connection.", timeout);
1294 syslog(LOG_INFO, "User %s timed out after %d seconds",
1295 (pw ? pw -> pw_name : "unknown"), timeout);
1312 (void) signal(SIGALRM, toolong);
1313 (void) alarm(timeout);
1314 n = getline(cbuf, sizeof(cbuf)-1, stdin);
1316 reply(221, "You could at least say goodbye.");
1318 } else if (n == -2) {
1319 reply(500, "Command too long.");
1325 if (strncasecmp(cbuf, "PASS", 4) != 0)
1326 setproctitle("%s: %s", proctitle, cbuf);
1327 #endif /* SETPROCTITLE */
1328 if ((cp = strchr(cbuf, '\r'))) {
1332 if ((cp = strpbrk(cbuf, " \n")))
1339 p = lookup(cmdtab, cbuf);
1343 if (!p->implemented)
1344 return (NOTIMPL); /* state remains CMD */
1351 if (cbuf[cpos] == ' ') {
1356 if ((cp2 = strpbrk(cp, " \n")))
1361 p = lookup(sitetab, cp);
1363 if (guest == 0 && p != 0) {
1365 if (!p->implemented) {
1377 if (cbuf[cpos] == '\n') {
1385 if (cbuf[cpos] == ' ') {
1387 state = state == OSTR ? STR2 : state+1;
1393 if (cbuf[cpos] == '\n') {
1404 * Make sure the string is nonempty and \n terminated.
1406 if (n > 1 && cbuf[cpos] == '\n') {
1408 yylval.s = copy(cp);
1416 if (cbuf[cpos] == ' ') {
1420 if (isdigit(cbuf[cpos])) {
1422 while (isdigit(cbuf[++cpos]))
1426 yylval.u.i = atoi(cp);
1435 if (isdigit(cbuf[cpos])) {
1437 while (isdigit(cbuf[++cpos]))
1441 yylval.u.i = atoi(cp);
1442 yylval.u.o = strtoull(cp, NULL, 10);
1446 if (strncasecmp(&cbuf[cpos], "ALL", 3) == 0
1447 && !isalnum(cbuf[cpos + 3])) {
1451 switch (cbuf[cpos++]) {
1515 fatalerror("Unknown state in scanner.");
1525 while (*s != '\0') {
1537 p = malloc(strlen(s) + 1);
1539 fatalerror("Ran out of memory.");
1540 (void) strcpy(p, s);
1545 help(struct tab *ctab, char *s)
1551 if (ctab == sitetab)
1555 width = 0, NCMDS = 0;
1556 for (c = ctab; c->name != NULL; c++) {
1557 int len = strlen(c->name);
1563 width = (width + 8) &~ 7;
1568 lreply(214, "The following %scommands are recognized %s.",
1569 type, "(* =>'s unimplemented)");
1570 columns = 76 / width;
1573 lines = (NCMDS + columns - 1) / columns;
1574 for (i = 0; i < lines; i++) {
1576 for (j = 0; j < columns; j++) {
1577 c = ctab + j * lines + i;
1578 printf("%s%c", c->name,
1579 c->implemented ? ' ' : '*');
1580 if (c + lines >= &ctab[NCMDS])
1582 w = strlen(c->name) + 1;
1590 (void) fflush(stdout);
1592 reply(214, "Direct comments to ftp-bugs@%s.", hostname);
1598 c = lookup(ctab, s);
1600 reply(502, "Unknown command %s.", s);
1604 reply(214, "Syntax: %s%s %s", type, c->name, c->help);
1606 reply(214, "%s%-*s\t%s; unimplemented.", type, width,
1611 sizecmd(char *filename)
1617 if (stat(filename, &stbuf) < 0)
1618 perror_reply(550, filename);
1619 else if (!S_ISREG(stbuf.st_mode))
1620 reply(550, "%s: not a plain file.", filename);
1622 reply(213, "%jd", (intmax_t)stbuf.st_size);
1629 fin = fopen(filename, "r");
1631 perror_reply(550, filename);
1634 if (fstat(fileno(fin), &stbuf) < 0) {
1635 perror_reply(550, filename);
1638 } else if (!S_ISREG(stbuf.st_mode)) {
1639 reply(550, "%s: not a plain file.", filename);
1642 } else if (stbuf.st_size > MAXASIZE) {
1643 reply(550, "%s: too large for type A SIZE.", filename);
1649 while((c=getc(fin)) != EOF) {
1650 if (c == '\n') /* will get expanded to \r\n */
1656 reply(213, "%jd", (intmax_t)count);
1659 reply(504, "SIZE not implemented for type %s.",
1664 /* Return 1, if port check is done. Return 0, if not yet. */
1666 port_check(const char *pcmd)
1668 if (his_addr.su_family == AF_INET) {
1669 if (data_dest.su_family != AF_INET) {
1671 reply(500, "Invalid address rejected.");
1675 ((ntohs(data_dest.su_port) < IPPORT_RESERVED) ||
1676 memcmp(&data_dest.su_sin.sin_addr,
1677 &his_addr.su_sin.sin_addr,
1678 sizeof(data_dest.su_sin.sin_addr)))) {
1680 reply(500, "Illegal PORT range rejected.");
1684 (void) close(pdata);
1687 reply(200, "%s command successful.", pcmd);
1700 reply(530, "Please login with USER and PASS.");
1706 * Replace leading "~user" in a pathname by the user's login directory.
1707 * Returned string will be in a freshly malloced buffer unless it's NULL.
1716 if ((p = strdup(s)) == NULL)
1721 user = p + 1; /* skip tilde */
1722 if ((path = strchr(p, '/')) != NULL)
1723 *(path++) = '\0'; /* separate ~user from the rest of path */
1724 if (*user == '\0') /* no user specified, use the current user */
1726 /* read passwd even for the current user since we may be chrooted */
1727 if ((ppw = getpwnam(user)) != NULL) {
1728 /* user found, substitute login directory for ~user */
1730 asprintf(&q, "%s/%s", ppw->pw_dir, path);
1732 q = strdup(ppw->pw_dir);
1736 /* user not found, undo the damage */
1744 * Expand glob(3) patterns possibly present in a pathname.
1745 * Avoid expanding to a pathname including '\r' or '\n' in order to
1746 * not disrupt the FTP protocol.
1747 * The expansion found must be unique.
1748 * Return the result as a malloced string, or NULL if an error occured.
1750 * Problem: this production is used for all pathname
1751 * processing, but only gives a 550 error reply.
1752 * This is a valid reply in some cases but not in others.
1757 char *p, **pp, *rval;
1758 int flags = GLOB_BRACE | GLOB_NOCHECK;
1762 memset(&gl, 0, sizeof(gl));
1763 flags |= GLOB_LIMIT;
1764 gl.gl_matchc = MAXGLOBARGS;
1765 if (glob(s, flags, NULL, &gl) == 0 && gl.gl_pathc != 0) {
1766 for (pp = gl.gl_pathv, p = NULL, n = 0; *pp; pp++)
1767 if (*(*pp + strcspn(*pp, "\r\n")) == '\0') {
1776 reply(550, "Wildcard is ambiguous.");
1780 reply(550, "Wildcard expansion error.");
1788 /* Return 1, if port check is done. Return 0, if not yet. */
1790 port_check_v6(const char *pcmd)
1792 if (his_addr.su_family == AF_INET6) {
1793 if (IN6_IS_ADDR_V4MAPPED(&his_addr.su_sin6.sin6_addr))
1794 /* Convert data_dest into v4 mapped sockaddr.*/
1796 if (data_dest.su_family != AF_INET6) {
1798 reply(500, "Invalid address rejected.");
1802 ((ntohs(data_dest.su_port) < IPPORT_RESERVED) ||
1803 memcmp(&data_dest.su_sin6.sin6_addr,
1804 &his_addr.su_sin6.sin6_addr,
1805 sizeof(data_dest.su_sin6.sin6_addr)))) {
1807 reply(500, "Illegal PORT range rejected.");
1811 (void) close(pdata);
1814 reply(200, "%s command successful.", pcmd);
1822 v4map_data_dest(void)
1824 struct in_addr savedaddr;
1827 if (data_dest.su_family != AF_INET) {
1829 reply(500, "Invalid address rejected.");
1833 savedaddr = data_dest.su_sin.sin_addr;
1834 savedport = data_dest.su_port;
1836 memset(&data_dest, 0, sizeof(data_dest));
1837 data_dest.su_sin6.sin6_len = sizeof(struct sockaddr_in6);
1838 data_dest.su_sin6.sin6_family = AF_INET6;
1839 data_dest.su_sin6.sin6_port = savedport;
1840 memset((caddr_t)&data_dest.su_sin6.sin6_addr.s6_addr[10], 0xff, 2);
1841 memcpy((caddr_t)&data_dest.su_sin6.sin6_addr.s6_addr[12],
1842 (caddr_t)&savedaddr, sizeof(savedaddr));