2 * SPDX-License-Identifier: BSD-3-Clause
4 * Copyright (c) 1985, 1988, 1993, 1994
5 * The Regents of the University of California. All rights reserved.
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
10 * 1. Redistributions of source code must retain the above copyright
11 * notice, this list of conditions and the following disclaimer.
12 * 2. Redistributions in binary form must reproduce the above copyright
13 * notice, this list of conditions and the following disclaimer in the
14 * documentation and/or other materials provided with the distribution.
15 * 3. Neither the name of the University nor the names of its contributors
16 * may be used to endorse or promote products derived from this software
17 * without specific prior written permission.
19 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
20 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
22 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
23 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
24 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
25 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
26 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
27 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
28 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
31 * @(#)ftpcmd.y 8.3 (Berkeley) 4/6/94
35 * Grammar for FTP commands.
43 static char sccsid[] = "@(#)ftpcmd.y 8.3 (Berkeley) 4/6/94";
47 #include <sys/cdefs.h>
48 __FBSDID("$FreeBSD$");
50 #include <sys/param.h>
51 #include <sys/socket.h>
54 #include <netinet/in.h>
75 #include "pathnames.h"
81 static int cmd_bytesz;
84 char *fromname = NULL;
103 USER PASS ACCT REIN QUIT PORT
104 PASV TYPE STRU MODE RETR STOR
105 APPE MLFL MAIL MSND MSOM MSAM
106 MRSQ MRCP ALLO REST RNFR RNTO
107 ABOR DELE CWD LIST NLST SITE
108 STAT HELP NOOP MKD RMD PWD
109 CDUP STOU SMNT SYST SIZE MDTM
110 LPRT LPSV EPRT EPSV FEAT
112 UMASK IDLE CHMOD MDFIVE
119 %type <u.i> check_login octal_number byte_size
120 %type <u.i> check_login_ro check_login_epsv
121 %type <u.i> struct_code mode_code type_code form_code
122 %type <s> pathstring pathname password username
123 %type <s> ALL NOTIMPL
142 : USER SP username CRLF
147 | PASS SP password CRLF
156 | PORT check_login SP host_port CRLF
159 reply(501, "No PORT allowed after EPSV ALL.");
164 if (port_check("PORT") == 1)
167 if ((his_addr.su_family != AF_INET6 ||
168 !IN6_IS_ADDR_V4MAPPED(&his_addr.su_sin6.sin6_addr))) {
169 /* shoud never happen */
171 reply(500, "Invalid address rejected.");
174 port_check_v6("pcmd");
179 | LPRT check_login SP host_long_port CRLF
182 reply(501, "No LPRT allowed after EPSV ALL.");
187 if (port_check("LPRT") == 1)
190 if (his_addr.su_family != AF_INET6) {
192 reply(500, "Invalid address rejected.");
195 if (port_check_v6("LPRT") == 1)
201 | EPRT check_login SP STRING CRLF
207 struct addrinfo hints;
208 struct addrinfo *res;
212 reply(501, "No EPRT allowed after EPSV ALL.");
218 memset(&data_dest, 0, sizeof(data_dest));
221 syslog(LOG_DEBUG, "%s", tmp);
223 fatalerror("not enough core");
229 memset(result, 0, sizeof(result));
230 for (i = 0; i < 3; i++) {
231 q = strchr(p, delim);
232 if (!q || *q != delim) {
235 "Invalid argument, rejected.");
244 syslog(LOG_DEBUG, "%d: %s", i, p);
248 /* some more sanity check */
263 memset(&hints, 0, sizeof(hints));
264 if (atoi(result[0]) == 1)
265 hints.ai_family = PF_INET;
267 else if (atoi(result[0]) == 2)
268 hints.ai_family = PF_INET6;
271 hints.ai_family = PF_UNSPEC; /*XXX*/
272 hints.ai_socktype = SOCK_STREAM;
273 i = getaddrinfo(result[1], result[2], &hints, &res);
276 memcpy(&data_dest, res->ai_addr, res->ai_addrlen);
278 if (his_addr.su_family == AF_INET6
279 && data_dest.su_family == AF_INET6) {
280 /* XXX more sanity checks! */
281 data_dest.su_sin6.sin6_scope_id =
282 his_addr.su_sin6.sin6_scope_id;
288 if (port_check("EPRT") == 1)
291 if (his_addr.su_family != AF_INET6) {
293 reply(500, "Invalid address rejected.");
296 if (port_check_v6("EPRT") == 1)
302 | PASV check_login CRLF
305 reply(501, "No PASV allowed after EPSV ALL.");
309 | LPSV check_login CRLF
312 reply(501, "No LPSV allowed after EPSV ALL.");
314 long_passive("LPSV", PF_UNSPEC);
316 | EPSV check_login_epsv SP NUMBER CRLF
330 pf = -1; /*junk value*/
333 long_passive("EPSV", pf);
336 | EPSV check_login_epsv SP ALL CRLF
339 reply(200, "EPSV ALL command successful.");
343 | EPSV check_login_epsv CRLF
346 long_passive("EPSV", PF_UNSPEC);
348 | TYPE check_login SP type_code CRLF
354 if (cmd_form == FORM_N) {
355 reply(200, "Type set to A.");
359 reply(504, "Form must be N.");
363 reply(504, "Type E not implemented.");
367 reply(200, "Type set to I.");
373 if (cmd_bytesz == 8) {
375 "Type set to L (byte size 8).");
378 reply(504, "Byte size must be 8.");
379 #else /* CHAR_BIT == 8 */
380 UNIMPLEMENTED for CHAR_BIT != 8
381 #endif /* CHAR_BIT == 8 */
385 | STRU check_login SP struct_code CRLF
391 reply(200, "STRU F accepted.");
395 reply(504, "Unimplemented STRU type.");
399 | MODE check_login SP mode_code CRLF
405 reply(200, "MODE S accepted.");
409 reply(502, "Unimplemented MODE type.");
413 | ALLO check_login SP NUMBER CRLF
416 reply(202, "ALLO command ignored.");
419 | ALLO check_login SP NUMBER SP R SP NUMBER CRLF
422 reply(202, "ALLO command ignored.");
425 | RETR check_login SP pathname CRLF
427 if (noretr || (guest && noguestretr))
428 reply(500, "RETR command disabled.");
429 else if ($2 && $4 != NULL)
435 | STOR check_login_ro SP pathname CRLF
437 if ($2 && $4 != NULL)
442 | APPE check_login_ro SP pathname CRLF
444 if ($2 && $4 != NULL)
449 | NLST check_login CRLF
454 | NLST check_login SP pathstring CRLF
460 | LIST check_login CRLF
463 retrieve(_PATH_LS " -lgA", "");
465 | LIST check_login SP pathstring CRLF
468 retrieve(_PATH_LS " -lgA %s", $4);
471 | STAT check_login SP pathname CRLF
473 if ($2 && $4 != NULL)
478 | STAT check_login CRLF
484 | DELE check_login_ro SP pathname CRLF
486 if ($2 && $4 != NULL)
491 | RNTO check_login_ro SP pathname CRLF
493 if ($2 && $4 != NULL) {
495 renamecmd(fromname, $4);
499 reply(503, "Bad sequence of commands.");
505 | ABOR check_login CRLF
508 reply(225, "ABOR command successful.");
510 | CWD check_login CRLF
516 | CWD check_login SP pathname CRLF
518 if ($2 && $4 != NULL)
527 | HELP SP STRING CRLF
531 if (strncasecmp(cp, "SITE", 4) == 0) {
545 reply(200, "NOOP command successful.");
547 | MKD check_login_ro SP pathname CRLF
549 if ($2 && $4 != NULL)
554 | RMD check_login_ro SP pathname CRLF
556 if ($2 && $4 != NULL)
561 | PWD check_login CRLF
566 | CDUP check_login CRLF
575 | SITE SP HELP SP STRING CRLF
580 | SITE SP MDFIVE check_login SP pathname CRLF
587 reply(200, "MD5(%s) = %s", $6, p);
589 perror_reply(550, $6);
594 | SITE SP UMASK check_login CRLF
600 (void) umask(oldmask);
601 reply(200, "Current UMASK is %03o.", oldmask);
604 | SITE SP UMASK check_login SP octal_number CRLF
609 if (($6 == -1) || ($6 > 0777)) {
610 reply(501, "Bad UMASK value.");
614 "UMASK set to %03o (was %03o).",
619 | SITE SP CHMOD check_login_ro SP octal_number SP pathname CRLF
621 if ($4 && ($8 != NULL)) {
622 if (($6 == -1 ) || ($6 > 0777))
623 reply(501, "Bad mode value.");
624 else if (chmod($8, $6) < 0)
625 perror_reply(550, $8);
627 reply(200, "CHMOD command successful.");
632 | SITE SP check_login IDLE CRLF
636 "Current IDLE time limit is %d seconds; max %d.",
637 timeout, maxtimeout);
639 | SITE SP check_login IDLE SP NUMBER CRLF
642 if ($6.i < 30 || $6.i > maxtimeout) {
644 "Maximum IDLE time must be between 30 and %d seconds.",
648 (void) alarm(timeout);
650 "Maximum IDLE time set to %d seconds.",
655 | STOU check_login_ro SP pathname CRLF
657 if ($2 && $4 != NULL)
664 lreply(211, "Extensions supported:");
666 /* XXX these two keywords are non-standard */
672 printf(" REST STREAM\r\n");
675 /* TVFS requires UTF8, see RFC 3659 */
681 | SYST check_login CRLF
686 reply(215, "UNIX Type: L%d Version: BSD-%d",
689 reply(215, "UNIX Type: L%d", CHAR_BIT);
692 reply(215, "UNKNOWN Type: L%d", CHAR_BIT);
697 * SIZE is not in RFC959, but Postel has blessed it and
698 * it will be in the updated RFC.
700 * Return size of file in a format suitable for
701 * using with RESTART (we just count bytes).
703 | SIZE check_login SP pathname CRLF
705 if ($2 && $4 != NULL)
712 * MDTM is not in RFC959, but Postel has blessed it and
713 * it will be in the updated RFC.
715 * Return modification time of file as an ISO 3307
716 * style time. E.g. YYYYMMDDHHMMSS or YYYYMMDDHHMMSS.xxx
717 * where xxx is the fractional second (of any precision,
718 * not necessarily 3 digits)
720 | MDTM check_login SP pathname CRLF
722 if ($2 && $4 != NULL) {
724 if (stat($4, &stbuf) < 0)
725 perror_reply(550, $4);
726 else if (!S_ISREG(stbuf.st_mode)) {
727 reply(550, "%s: not a plain file.", $4);
730 t = gmtime(&stbuf.st_mtime);
732 "%04d%02d%02d%02d%02d%02d",
734 t->tm_mon+1, t->tm_mday,
735 t->tm_hour, t->tm_min, t->tm_sec);
743 reply(221, "Goodbye.");
752 yyclearin; /* discard lookahead data */
753 yyerrok; /* clear error condition */
754 state = CMD; /* reset lexer state */
758 : RNFR check_login_ro SP pathname CRLF
773 | REST check_login SP NUMBER CRLF
779 restart_point = $4.o;
780 reply(350, "Restarting at %jd. %s",
781 (intmax_t)restart_point,
782 "Send STORE or RETRIEVE to initiate transfer.");
794 $$ = (char *)calloc(1, sizeof(char));
807 : NUMBER COMMA NUMBER COMMA NUMBER COMMA NUMBER COMMA
812 data_dest.su_len = sizeof(struct sockaddr_in);
813 data_dest.su_family = AF_INET;
814 p = (char *)&data_dest.su_sin.sin_port;
815 p[0] = $9.i; p[1] = $11.i;
816 a = (char *)&data_dest.su_sin.sin_addr;
817 a[0] = $1.i; a[1] = $3.i; a[2] = $5.i; a[3] = $7.i;
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
825 NUMBER COMMA NUMBER COMMA NUMBER COMMA NUMBER COMMA
826 NUMBER COMMA NUMBER COMMA NUMBER COMMA NUMBER COMMA
831 memset(&data_dest, 0, sizeof(data_dest));
832 data_dest.su_len = sizeof(struct sockaddr_in6);
833 data_dest.su_family = AF_INET6;
834 p = (char *)&data_dest.su_port;
835 p[0] = $39.i; p[1] = $41.i;
836 a = (char *)&data_dest.su_sin6.sin6_addr;
837 a[0] = $5.i; a[1] = $7.i; a[2] = $9.i; a[3] = $11.i;
838 a[4] = $13.i; a[5] = $15.i; a[6] = $17.i; a[7] = $19.i;
839 a[8] = $21.i; a[9] = $23.i; a[10] = $25.i; a[11] = $27.i;
840 a[12] = $29.i; a[13] = $31.i; a[14] = $33.i; a[15] = $35.i;
841 if (his_addr.su_family == AF_INET6) {
842 /* XXX more sanity checks! */
843 data_dest.su_sin6.sin6_scope_id =
844 his_addr.su_sin6.sin6_scope_id;
846 if ($1.i != 6 || $3.i != 16 || $37.i != 2)
847 memset(&data_dest, 0, sizeof(data_dest));
849 | NUMBER COMMA NUMBER COMMA NUMBER COMMA NUMBER COMMA
850 NUMBER COMMA NUMBER COMMA NUMBER COMMA NUMBER COMMA
855 memset(&data_dest, 0, sizeof(data_dest));
856 data_dest.su_sin.sin_len = sizeof(struct sockaddr_in);
857 data_dest.su_family = AF_INET;
858 p = (char *)&data_dest.su_port;
859 p[0] = $15.i; p[1] = $17.i;
860 a = (char *)&data_dest.su_sin.sin_addr;
861 a[0] = $5.i; a[1] = $7.i; a[2] = $9.i; a[3] = $11.i;
862 if ($1.i != 4 || $3.i != 4 || $13.i != 2)
863 memset(&data_dest, 0, sizeof(data_dest));
910 cmd_bytesz = CHAR_BIT;
917 /* this is for a bug in the BBN ftp */
958 if (logged_in && $1) {
962 * Expand ~user manually since glob(3)
963 * will return the unexpanded pathname
964 * if the corresponding file/directory
965 * doesn't exist yet. Using sole glob(3)
966 * would break natural commands like
971 if ((p = exptilde($1)) != NULL) {
989 int ret, dec, multby, digit;
992 * Convert a number that was read as decimal number
993 * to what it would be if it had been read as octal.
1004 ret += digit * multby;
1016 $$ = check_login1();
1024 reply(500, "EPSV command disabled.");
1028 $$ = check_login1();
1036 reply(550, "Permission denied.");
1040 $$ = check_login1();
1046 #define CMD 0 /* beginning of command */
1047 #define ARGS 1 /* expect miscellaneous arguments */
1048 #define STR1 2 /* expect SP followed by STRING */
1049 #define STR2 3 /* expect STRING */
1050 #define OSTR 4 /* optional SP then STRING */
1051 #define ZSTR1 5 /* optional SP then optional STRING */
1052 #define ZSTR2 6 /* optional STRING after SP */
1053 #define SITECMD 7 /* SITE command */
1054 #define NSTR 8 /* Number followed by a string */
1056 #define MAXGLOBARGS 1000
1058 #define MAXASIZE 10240 /* Deny ASCII SIZE on files larger than that */
1064 short implemented; /* 1 if command is implemented */
1068 struct tab cmdtab[] = { /* In order defined in RFC 765 */
1069 { "USER", USER, STR1, 1, "<sp> username" },
1070 { "PASS", PASS, ZSTR1, 1, "[<sp> [password]]" },
1071 { "ACCT", ACCT, STR1, 0, "(specify account)" },
1072 { "SMNT", SMNT, ARGS, 0, "(structure mount)" },
1073 { "REIN", REIN, ARGS, 0, "(reinitialize server state)" },
1074 { "QUIT", QUIT, ARGS, 1, "(terminate service)", },
1075 { "PORT", PORT, ARGS, 1, "<sp> b0, b1, b2, b3, b4, b5" },
1076 { "LPRT", LPRT, ARGS, 1, "<sp> af, hal, h1, h2, h3,..., pal, p1, p2..." },
1077 { "EPRT", EPRT, STR1, 1, "<sp> |af|addr|port|" },
1078 { "PASV", PASV, ARGS, 1, "(set server in passive mode)" },
1079 { "LPSV", LPSV, ARGS, 1, "(set server in passive mode)" },
1080 { "EPSV", EPSV, ARGS, 1, "[<sp> af|ALL]" },
1081 { "TYPE", TYPE, ARGS, 1, "<sp> { A | E | I | L }" },
1082 { "STRU", STRU, ARGS, 1, "(specify file structure)" },
1083 { "MODE", MODE, ARGS, 1, "(specify transfer mode)" },
1084 { "RETR", RETR, STR1, 1, "<sp> file-name" },
1085 { "STOR", STOR, STR1, 1, "<sp> file-name" },
1086 { "APPE", APPE, STR1, 1, "<sp> file-name" },
1087 { "MLFL", MLFL, OSTR, 0, "(mail file)" },
1088 { "MAIL", MAIL, OSTR, 0, "(mail to user)" },
1089 { "MSND", MSND, OSTR, 0, "(mail send to terminal)" },
1090 { "MSOM", MSOM, OSTR, 0, "(mail send to terminal or mailbox)" },
1091 { "MSAM", MSAM, OSTR, 0, "(mail send to terminal and mailbox)" },
1092 { "MRSQ", MRSQ, OSTR, 0, "(mail recipient scheme question)" },
1093 { "MRCP", MRCP, STR1, 0, "(mail recipient)" },
1094 { "ALLO", ALLO, ARGS, 1, "allocate storage (vacuously)" },
1095 { "REST", REST, ARGS, 1, "<sp> offset (restart command)" },
1096 { "RNFR", RNFR, STR1, 1, "<sp> file-name" },
1097 { "RNTO", RNTO, STR1, 1, "<sp> file-name" },
1098 { "ABOR", ABOR, ARGS, 1, "(abort operation)" },
1099 { "DELE", DELE, STR1, 1, "<sp> file-name" },
1100 { "CWD", CWD, OSTR, 1, "[ <sp> directory-name ]" },
1101 { "XCWD", CWD, OSTR, 1, "[ <sp> directory-name ]" },
1102 { "LIST", LIST, OSTR, 1, "[ <sp> path-name ]" },
1103 { "NLST", NLST, OSTR, 1, "[ <sp> path-name ]" },
1104 { "SITE", SITE, SITECMD, 1, "site-cmd [ <sp> arguments ]" },
1105 { "SYST", SYST, ARGS, 1, "(get type of operating system)" },
1106 { "FEAT", FEAT, ARGS, 1, "(get extended features)" },
1107 { "STAT", STAT, OSTR, 1, "[ <sp> path-name ]" },
1108 { "HELP", HELP, OSTR, 1, "[ <sp> <string> ]" },
1109 { "NOOP", NOOP, ARGS, 1, "" },
1110 { "MKD", MKD, STR1, 1, "<sp> path-name" },
1111 { "XMKD", MKD, STR1, 1, "<sp> path-name" },
1112 { "RMD", RMD, STR1, 1, "<sp> path-name" },
1113 { "XRMD", RMD, STR1, 1, "<sp> path-name" },
1114 { "PWD", PWD, ARGS, 1, "(return current directory)" },
1115 { "XPWD", PWD, ARGS, 1, "(return current directory)" },
1116 { "CDUP", CDUP, ARGS, 1, "(change to parent directory)" },
1117 { "XCUP", CDUP, ARGS, 1, "(change to parent directory)" },
1118 { "STOU", STOU, STR1, 1, "<sp> file-name" },
1119 { "SIZE", SIZE, OSTR, 1, "<sp> path-name" },
1120 { "MDTM", MDTM, OSTR, 1, "<sp> path-name" },
1121 { NULL, 0, 0, 0, 0 }
1124 struct tab sitetab[] = {
1125 { "MD5", MDFIVE, STR1, 1, "[ <sp> file-name ]" },
1126 { "UMASK", UMASK, ARGS, 1, "[ <sp> umask ]" },
1127 { "IDLE", IDLE, ARGS, 1, "[ <sp> maximum-idle-time ]" },
1128 { "CHMOD", CHMOD, NSTR, 1, "<sp> mode <sp> file-name" },
1129 { "HELP", HELP, OSTR, 1, "[ <sp> <string> ]" },
1130 { NULL, 0, 0, 0, 0 }
1133 static char *copy(char *);
1134 static char *expglob(char *);
1135 static char *exptilde(char *);
1136 static void help(struct tab *, char *);
1138 lookup(struct tab *, char *);
1139 static int port_check(const char *);
1141 static int port_check_v6(const char *);
1143 static void sizecmd(char *);
1144 static void toolong(int);
1146 static void v4map_data_dest(void);
1148 static int yylex(void);
1151 lookup(struct tab *p, char *cmd)
1154 for (; p->name != NULL; p++)
1155 if (strcmp(cmd, p->name) == 0)
1160 #include <arpa/telnet.h>
1163 * get_line - a hacked up version of fgets to ignore TELNET escape codes.
1166 get_line(char *s, int n, FILE *iop)
1170 sigset_t sset, osset;
1173 /* tmpline may contain saved command from urgent mode interruption */
1174 for (c = 0; tmpline[c] != '\0' && --n > 0; ++c) {
1176 if (tmpline[c] == '\n') {
1179 syslog(LOG_DEBUG, "command: %s", s);
1186 /* SIGURG would interrupt stdio if not blocked during the read loop */
1188 sigaddset(&sset, SIGURG);
1189 sigprocmask(SIG_BLOCK, &sset, &osset);
1190 while ((c = getc(iop)) != EOF) {
1193 if ((c = getc(iop)) == EOF)
1199 if ((c = getc(iop)) == EOF)
1201 printf("%c%c%c", IAC, DONT, 0377&c);
1202 (void) fflush(stdout);
1206 if ((c = getc(iop)) == EOF)
1208 printf("%c%c%c", IAC, WONT, 0377&c);
1209 (void) fflush(stdout);
1214 continue; /* ignore command */
1220 * If command doesn't fit into buffer, discard the
1221 * rest of the command and indicate truncation.
1222 * This prevents the command to be split up into
1223 * multiple commands.
1225 while (c != '\n' && (c = getc(iop)) != EOF)
1233 sigprocmask(SIG_SETMASK, &osset, NULL);
1234 if (c == EOF && cs == s)
1238 if (!guest && strncasecmp("pass ", s, 5) == 0) {
1239 /* Don't syslog passwords */
1240 syslog(LOG_DEBUG, "command: %.5s ???", s);
1245 /* Don't syslog trailing CR-LF */
1248 while (cp >= s && (*cp == '\n' || *cp == '\r')) {
1252 syslog(LOG_DEBUG, "command: %.*s", len, s);
1263 "Timeout (%d seconds): closing control connection.", timeout);
1265 syslog(LOG_INFO, "User %s timed out after %d seconds",
1266 (pw ? pw -> pw_name : "unknown"), timeout);
1283 (void) signal(SIGALRM, toolong);
1284 (void) alarm(timeout);
1285 n = get_line(cbuf, sizeof(cbuf)-1, stdin);
1287 reply(221, "You could at least say goodbye.");
1289 } else if (n == -2) {
1290 reply(500, "Command too long.");
1296 if (strncasecmp(cbuf, "PASS", 4) != 0)
1297 setproctitle("%s: %s", proctitle, cbuf);
1298 #endif /* SETPROCTITLE */
1299 if ((cp = strchr(cbuf, '\r'))) {
1303 if ((cp = strpbrk(cbuf, " \n")))
1310 p = lookup(cmdtab, cbuf);
1314 if (!p->implemented)
1315 return (NOTIMPL); /* state remains CMD */
1322 if (cbuf[cpos] == ' ') {
1327 if ((cp2 = strpbrk(cp, " \n")))
1332 p = lookup(sitetab, cp);
1334 if (guest == 0 && p != 0) {
1336 if (!p->implemented) {
1348 if (cbuf[cpos] == '\n') {
1356 if (cbuf[cpos] == ' ') {
1358 state = state == OSTR ? STR2 : state+1;
1364 if (cbuf[cpos] == '\n') {
1375 * Make sure the string is nonempty and \n terminated.
1377 if (n > 1 && cbuf[cpos] == '\n') {
1379 yylval.s = copy(cp);
1387 if (cbuf[cpos] == ' ') {
1391 if (isdigit(cbuf[cpos])) {
1393 while (isdigit(cbuf[++cpos]))
1397 yylval.u.i = atoi(cp);
1406 if (isdigit(cbuf[cpos])) {
1408 while (isdigit(cbuf[++cpos]))
1412 yylval.u.i = atoi(cp);
1413 yylval.u.o = strtoull(cp, NULL, 10);
1417 if (strncasecmp(&cbuf[cpos], "ALL", 3) == 0
1418 && !isalnum(cbuf[cpos + 3])) {
1422 switch (cbuf[cpos++]) {
1486 fatalerror("Unknown state in scanner.");
1496 while (*s != '\0') {
1508 p = malloc(strlen(s) + 1);
1510 fatalerror("Ran out of memory.");
1511 (void) strcpy(p, s);
1516 help(struct tab *ctab, char *s)
1522 if (ctab == sitetab)
1526 width = 0, NCMDS = 0;
1527 for (c = ctab; c->name != NULL; c++) {
1528 int len = strlen(c->name);
1534 width = (width + 8) &~ 7;
1539 lreply(214, "The following %scommands are recognized %s.",
1540 type, "(* =>'s unimplemented)");
1541 columns = 76 / width;
1544 lines = (NCMDS + columns - 1) / columns;
1545 for (i = 0; i < lines; i++) {
1547 for (j = 0; j < columns; j++) {
1548 c = ctab + j * lines + i;
1549 printf("%s%c", c->name,
1550 c->implemented ? ' ' : '*');
1551 if (c + lines >= &ctab[NCMDS])
1553 w = strlen(c->name) + 1;
1561 (void) fflush(stdout);
1563 reply(214, "Direct comments to ftp-bugs@%s.", hostname);
1569 c = lookup(ctab, s);
1571 reply(502, "Unknown command %s.", s);
1575 reply(214, "Syntax: %s%s %s", type, c->name, c->help);
1577 reply(214, "%s%-*s\t%s; unimplemented.", type, width,
1582 sizecmd(char *filename)
1588 if (stat(filename, &stbuf) < 0)
1589 perror_reply(550, filename);
1590 else if (!S_ISREG(stbuf.st_mode))
1591 reply(550, "%s: not a plain file.", filename);
1593 reply(213, "%jd", (intmax_t)stbuf.st_size);
1600 fin = fopen(filename, "r");
1602 perror_reply(550, filename);
1605 if (fstat(fileno(fin), &stbuf) < 0) {
1606 perror_reply(550, filename);
1609 } else if (!S_ISREG(stbuf.st_mode)) {
1610 reply(550, "%s: not a plain file.", filename);
1613 } else if (stbuf.st_size > MAXASIZE) {
1614 reply(550, "%s: too large for type A SIZE.", filename);
1620 while((c=getc(fin)) != EOF) {
1621 if (c == '\n') /* will get expanded to \r\n */
1627 reply(213, "%jd", (intmax_t)count);
1630 reply(504, "SIZE not implemented for type %s.",
1635 /* Return 1, if port check is done. Return 0, if not yet. */
1637 port_check(const char *pcmd)
1639 if (his_addr.su_family == AF_INET) {
1640 if (data_dest.su_family != AF_INET) {
1642 reply(500, "Invalid address rejected.");
1646 ((ntohs(data_dest.su_port) < IPPORT_RESERVED) ||
1647 memcmp(&data_dest.su_sin.sin_addr,
1648 &his_addr.su_sin.sin_addr,
1649 sizeof(data_dest.su_sin.sin_addr)))) {
1651 reply(500, "Illegal PORT range rejected.");
1655 (void) close(pdata);
1658 reply(200, "%s command successful.", pcmd);
1671 reply(530, "Please login with USER and PASS.");
1677 * Replace leading "~user" in a pathname by the user's login directory.
1678 * Returned string will be in a freshly malloced buffer unless it's NULL.
1687 if ((p = strdup(s)) == NULL)
1692 user = p + 1; /* skip tilde */
1693 if ((path = strchr(p, '/')) != NULL)
1694 *(path++) = '\0'; /* separate ~user from the rest of path */
1695 if (*user == '\0') /* no user specified, use the current user */
1697 /* read passwd even for the current user since we may be chrooted */
1698 if ((ppw = getpwnam(user)) != NULL) {
1699 /* user found, substitute login directory for ~user */
1701 asprintf(&q, "%s/%s", ppw->pw_dir, path);
1703 q = strdup(ppw->pw_dir);
1707 /* user not found, undo the damage */
1715 * Expand glob(3) patterns possibly present in a pathname.
1716 * Avoid expanding to a pathname including '\r' or '\n' in order to
1717 * not disrupt the FTP protocol.
1718 * The expansion found must be unique.
1719 * Return the result as a malloced string, or NULL if an error occurred.
1721 * Problem: this production is used for all pathname
1722 * processing, but only gives a 550 error reply.
1723 * This is a valid reply in some cases but not in others.
1728 char *p, **pp, *rval;
1729 int flags = GLOB_BRACE | GLOB_NOCHECK;
1733 memset(&gl, 0, sizeof(gl));
1734 flags |= GLOB_LIMIT;
1735 gl.gl_matchc = MAXGLOBARGS;
1736 if (glob(s, flags, NULL, &gl) == 0 && gl.gl_pathc != 0) {
1737 for (pp = gl.gl_pathv, p = NULL, n = 0; *pp; pp++)
1738 if (*(*pp + strcspn(*pp, "\r\n")) == '\0') {
1747 reply(550, "Wildcard is ambiguous.");
1751 reply(550, "Wildcard expansion error.");
1759 /* Return 1, if port check is done. Return 0, if not yet. */
1761 port_check_v6(const char *pcmd)
1763 if (his_addr.su_family == AF_INET6) {
1764 if (IN6_IS_ADDR_V4MAPPED(&his_addr.su_sin6.sin6_addr))
1765 /* Convert data_dest into v4 mapped sockaddr.*/
1767 if (data_dest.su_family != AF_INET6) {
1769 reply(500, "Invalid address rejected.");
1773 ((ntohs(data_dest.su_port) < IPPORT_RESERVED) ||
1774 memcmp(&data_dest.su_sin6.sin6_addr,
1775 &his_addr.su_sin6.sin6_addr,
1776 sizeof(data_dest.su_sin6.sin6_addr)))) {
1778 reply(500, "Illegal PORT range rejected.");
1782 (void) close(pdata);
1785 reply(200, "%s command successful.", pcmd);
1793 v4map_data_dest(void)
1795 struct in_addr savedaddr;
1798 if (data_dest.su_family != AF_INET) {
1800 reply(500, "Invalid address rejected.");
1804 savedaddr = data_dest.su_sin.sin_addr;
1805 savedport = data_dest.su_port;
1807 memset(&data_dest, 0, sizeof(data_dest));
1808 data_dest.su_sin6.sin6_len = sizeof(struct sockaddr_in6);
1809 data_dest.su_sin6.sin6_family = AF_INET6;
1810 data_dest.su_sin6.sin6_port = savedport;
1811 memset((caddr_t)&data_dest.su_sin6.sin6_addr.s6_addr[10], 0xff, 2);
1812 memcpy((caddr_t)&data_dest.su_sin6.sin6_addr.s6_addr[12],
1813 (caddr_t)&savedaddr, sizeof(savedaddr));