diff options
Diffstat (limited to 'libexec/ftpd/ftpcmd.y')
-rw-r--r-- | libexec/ftpd/ftpcmd.y | 1806 |
1 files changed, 0 insertions, 1806 deletions
diff --git a/libexec/ftpd/ftpcmd.y b/libexec/ftpd/ftpcmd.y deleted file mode 100644 index c090130d8137..000000000000 --- a/libexec/ftpd/ftpcmd.y +++ /dev/null @@ -1,1806 +0,0 @@ -/*- - * SPDX-License-Identifier: BSD-3-Clause - * - * Copyright (c) 1985, 1988, 1993, 1994 - * The Regents of the University of California. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * 3. Neither the name of the University nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND - * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS - * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT - * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY - * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF - * SUCH DAMAGE. - */ - -/* - * Grammar for FTP commands. - * See RFC 959. - */ - -%{ - -#include <sys/param.h> -#include <sys/socket.h> -#include <sys/stat.h> - -#include <netinet/in.h> -#include <arpa/ftp.h> - -#include <ctype.h> -#include <errno.h> -#include <glob.h> -#include <libutil.h> -#include <limits.h> -#include <md5.h> -#include <netdb.h> -#include <pwd.h> -#include <signal.h> -#include <stdint.h> -#include <stdio.h> -#include <stdlib.h> -#include <string.h> -#include <syslog.h> -#include <time.h> -#include <unistd.h> - -#include "extern.h" -#include "pathnames.h" - -#define yylex ftpcmd_yylex - -off_t restart_point; - -static int cmd_type; -static int cmd_form; -static int cmd_bytesz; -static int state; -char cbuf[512]; -char *fromname = NULL; - -%} - -%union { - struct { - off_t o; - int i; - } u; - char *s; -} - -%token - A B C E F I - L N P R S T - ALL - - SP CRLF COMMA - - USER PASS ACCT REIN QUIT PORT - PASV TYPE STRU MODE RETR STOR - APPE MLFL MAIL MSND MSOM MSAM - MRSQ MRCP ALLO REST RNFR RNTO - ABOR DELE CWD LIST NLST SITE - STAT HELP NOOP MKD RMD PWD - CDUP STOU SMNT SYST SIZE MDTM - LPRT LPSV EPRT EPSV FEAT - - UMASK IDLE CHMOD MDFIVE - - LEXERR NOTIMPL - -%token <s> STRING -%token <u> NUMBER - -%type <u.i> check_login octal_number byte_size -%type <u.i> check_login_ro check_login_epsv -%type <u.i> struct_code mode_code type_code form_code -%type <s> pathstring pathname password username -%type <s> ALL NOTIMPL - -%start cmd_list - -%% - -cmd_list - : /* empty */ - | cmd_list cmd - { - if (fromname) - free(fromname); - fromname = NULL; - restart_point = 0; - } - | cmd_list rcmd - ; - -cmd - : USER SP username CRLF - { - user($3); - free($3); - } - | PASS SP password CRLF - { - pass($3); - free($3); - } - | PASS CRLF - { - pass(""); - } - | PORT check_login SP host_port CRLF - { - if (epsvall) { - reply(501, "No PORT allowed after EPSV ALL."); - goto port_done; - } - if (!$2) - goto port_done; - if (port_check("PORT") == 1) - goto port_done; -#ifdef INET6 - if ((his_addr.su_family != AF_INET6 || - !IN6_IS_ADDR_V4MAPPED(&his_addr.su_sin6.sin6_addr))) { - /* shoud never happen */ - usedefault = 1; - reply(500, "Invalid address rejected."); - goto port_done; - } - port_check_v6("pcmd"); -#endif - port_done: - ; - } - | LPRT check_login SP host_long_port CRLF - { - if (epsvall) { - reply(501, "No LPRT allowed after EPSV ALL."); - goto lprt_done; - } - if (!$2) - goto lprt_done; - if (port_check("LPRT") == 1) - goto lprt_done; -#ifdef INET6 - if (his_addr.su_family != AF_INET6) { - usedefault = 1; - reply(500, "Invalid address rejected."); - goto lprt_done; - } - if (port_check_v6("LPRT") == 1) - goto lprt_done; -#endif - lprt_done: - ; - } - | EPRT check_login SP STRING CRLF - { - char delim; - char *tmp = NULL; - char *p, *q; - char *result[3]; - struct addrinfo hints; - struct addrinfo *res; - int i; - - if (epsvall) { - reply(501, "No EPRT allowed after EPSV ALL."); - goto eprt_done; - } - if (!$2) - goto eprt_done; - - memset(&data_dest, 0, sizeof(data_dest)); - tmp = strdup($4); - if (ftpdebug) - syslog(LOG_DEBUG, "%s", tmp); - if (!tmp) { - fatalerror("not enough core"); - /*NOTREACHED*/ - } - p = tmp; - delim = p[0]; - p++; - memset(result, 0, sizeof(result)); - for (i = 0; i < 3; i++) { - q = strchr(p, delim); - if (!q || *q != delim) { - parsefail: - reply(500, - "Invalid argument, rejected."); - if (tmp) - free(tmp); - usedefault = 1; - goto eprt_done; - } - *q++ = '\0'; - result[i] = p; - if (ftpdebug) - syslog(LOG_DEBUG, "%d: %s", i, p); - p = q; - } - - /* some more sanity check */ - p = result[0]; - while (*p) { - if (!isdigit(*p)) - goto parsefail; - p++; - } - p = result[2]; - while (*p) { - if (!isdigit(*p)) - goto parsefail; - p++; - } - - /* grab address */ - memset(&hints, 0, sizeof(hints)); - if (atoi(result[0]) == 1) - hints.ai_family = PF_INET; -#ifdef INET6 - else if (atoi(result[0]) == 2) - hints.ai_family = PF_INET6; -#endif - else - hints.ai_family = PF_UNSPEC; /*XXX*/ - hints.ai_socktype = SOCK_STREAM; - i = getaddrinfo(result[1], result[2], &hints, &res); - if (i) - goto parsefail; - memcpy(&data_dest, res->ai_addr, res->ai_addrlen); -#ifdef INET6 - if (his_addr.su_family == AF_INET6 - && data_dest.su_family == AF_INET6) { - /* XXX more sanity checks! */ - data_dest.su_sin6.sin6_scope_id = - his_addr.su_sin6.sin6_scope_id; - } -#endif - free(tmp); - tmp = NULL; - - if (port_check("EPRT") == 1) - goto eprt_done; -#ifdef INET6 - if (his_addr.su_family != AF_INET6) { - usedefault = 1; - reply(500, "Invalid address rejected."); - goto eprt_done; - } - if (port_check_v6("EPRT") == 1) - goto eprt_done; -#endif - eprt_done: - free($4); - } - | PASV check_login CRLF - { - if (epsvall) - reply(501, "No PASV allowed after EPSV ALL."); - else if ($2) - passive(); - } - | LPSV check_login CRLF - { - if (epsvall) - reply(501, "No LPSV allowed after EPSV ALL."); - else if ($2) - long_passive("LPSV", PF_UNSPEC); - } - | EPSV check_login_epsv SP NUMBER CRLF - { - if ($2) { - int pf; - switch ($4.i) { - case 1: - pf = PF_INET; - break; -#ifdef INET6 - case 2: - pf = PF_INET6; - break; -#endif - default: - pf = -1; /*junk value*/ - break; - } - long_passive("EPSV", pf); - } - } - | EPSV check_login_epsv SP ALL CRLF - { - if ($2) { - reply(200, "EPSV ALL command successful."); - epsvall++; - } - } - | EPSV check_login_epsv CRLF - { - if ($2) - long_passive("EPSV", PF_UNSPEC); - } - | TYPE check_login SP type_code CRLF - { - if ($2) { - switch (cmd_type) { - - case TYPE_A: - if (cmd_form == FORM_N) { - reply(200, "Type set to A."); - type = cmd_type; - form = cmd_form; - } else - reply(504, "Form must be N."); - break; - - case TYPE_E: - reply(504, "Type E not implemented."); - break; - - case TYPE_I: - reply(200, "Type set to I."); - type = cmd_type; - break; - - case TYPE_L: -#if CHAR_BIT == 8 - if (cmd_bytesz == 8) { - reply(200, - "Type set to L (byte size 8)."); - type = cmd_type; - } else - reply(504, "Byte size must be 8."); -#else /* CHAR_BIT == 8 */ - UNIMPLEMENTED for CHAR_BIT != 8 -#endif /* CHAR_BIT == 8 */ - } - } - } - | STRU check_login SP struct_code CRLF - { - if ($2) { - switch ($4) { - - case STRU_F: - reply(200, "STRU F accepted."); - break; - - default: - reply(504, "Unimplemented STRU type."); - } - } - } - | MODE check_login SP mode_code CRLF - { - if ($2) { - switch ($4) { - - case MODE_S: - reply(200, "MODE S accepted."); - break; - - default: - reply(502, "Unimplemented MODE type."); - } - } - } - | ALLO check_login SP NUMBER CRLF - { - if ($2) { - reply(202, "ALLO command ignored."); - } - } - | ALLO check_login SP NUMBER SP R SP NUMBER CRLF - { - if ($2) { - reply(202, "ALLO command ignored."); - } - } - | RETR check_login SP pathname CRLF - { - if (noretr || (guest && noguestretr)) - reply(500, "RETR command disabled."); - else if ($2 && $4 != NULL) - retrieve(NULL, $4); - - if ($4 != NULL) - free($4); - } - | STOR check_login_ro SP pathname CRLF - { - if ($2 && $4 != NULL) - store($4, "w", 0); - if ($4 != NULL) - free($4); - } - | APPE check_login_ro SP pathname CRLF - { - if ($2 && $4 != NULL) - store($4, "a", 0); - if ($4 != NULL) - free($4); - } - | NLST check_login CRLF - { - if ($2) - send_file_list("."); - } - | NLST check_login SP pathstring CRLF - { - if ($2) - send_file_list($4); - free($4); - } - | LIST check_login CRLF - { - if ($2) - retrieve(_PATH_LS " -lA", ""); - } - | LIST check_login SP pathstring CRLF - { - if ($2) - retrieve(_PATH_LS " -lA %s", $4); - free($4); - } - | STAT check_login SP pathname CRLF - { - if ($2 && $4 != NULL) - statfilecmd($4); - if ($4 != NULL) - free($4); - } - | STAT check_login CRLF - { - if ($2) { - statcmd(); - } - } - | DELE check_login_ro SP pathname CRLF - { - if ($2 && $4 != NULL) - delete($4); - if ($4 != NULL) - free($4); - } - | RNTO check_login_ro SP pathname CRLF - { - if ($2 && $4 != NULL) { - if (fromname) { - renamecmd(fromname, $4); - free(fromname); - fromname = NULL; - } else { - reply(503, "Bad sequence of commands."); - } - } - if ($4 != NULL) - free($4); - } - | ABOR check_login CRLF - { - if ($2) - reply(225, "ABOR command successful."); - } - | CWD check_login CRLF - { - if ($2) { - cwd(homedir); - } - } - | CWD check_login SP pathname CRLF - { - if ($2 && $4 != NULL) - cwd($4); - if ($4 != NULL) - free($4); - } - | HELP CRLF - { - help(cmdtab, NULL); - } - | HELP SP STRING CRLF - { - char *cp = $3; - - if (strncasecmp(cp, "SITE", 4) == 0) { - cp = $3 + 4; - if (*cp == ' ') - cp++; - if (*cp) - help(sitetab, cp); - else - help(sitetab, NULL); - } else - help(cmdtab, $3); - free($3); - } - | NOOP CRLF - { - reply(200, "NOOP command successful."); - } - | MKD check_login_ro SP pathname CRLF - { - if ($2 && $4 != NULL) - makedir($4); - if ($4 != NULL) - free($4); - } - | RMD check_login_ro SP pathname CRLF - { - if ($2 && $4 != NULL) - removedir($4); - if ($4 != NULL) - free($4); - } - | PWD check_login CRLF - { - if ($2) - pwd(); - } - | CDUP check_login CRLF - { - if ($2) - cwd(".."); - } - | SITE SP HELP CRLF - { - help(sitetab, NULL); - } - | SITE SP HELP SP STRING CRLF - { - help(sitetab, $5); - free($5); - } - | SITE SP MDFIVE check_login SP pathname CRLF - { - char p[64], *q; - - if ($4 && $6) { - q = MD5File($6, p); - if (q != NULL) - reply(200, "MD5(%s) = %s", $6, p); - else - perror_reply(550, $6); - } - if ($6) - free($6); - } - | SITE SP UMASK check_login CRLF - { - int oldmask; - - if ($4) { - oldmask = umask(0); - (void) umask(oldmask); - reply(200, "Current UMASK is %03o.", oldmask); - } - } - | SITE SP UMASK check_login SP octal_number CRLF - { - int oldmask; - - if ($4) { - if (($6 == -1) || ($6 > 0777)) { - reply(501, "Bad UMASK value."); - } else { - oldmask = umask($6); - reply(200, - "UMASK set to %03o (was %03o).", - $6, oldmask); - } - } - } - | SITE SP CHMOD check_login_ro SP octal_number SP pathname CRLF - { - if ($4 && ($8 != NULL)) { - if (($6 == -1 ) || ($6 > 0777)) - reply(501, "Bad mode value."); - else if (chmod($8, $6) < 0) - perror_reply(550, $8); - else - reply(200, "CHMOD command successful."); - } - if ($8 != NULL) - free($8); - } - | SITE SP check_login IDLE CRLF - { - if ($3) - reply(200, - "Current IDLE time limit is %d seconds; max %d.", - timeout, maxtimeout); - } - | SITE SP check_login IDLE SP NUMBER CRLF - { - if ($3) { - if ($6.i < 30 || $6.i > maxtimeout) { - reply(501, - "Maximum IDLE time must be between 30 and %d seconds.", - maxtimeout); - } else { - timeout = $6.i; - (void) alarm(timeout); - reply(200, - "Maximum IDLE time set to %d seconds.", - timeout); - } - } - } - | STOU check_login_ro SP pathname CRLF - { - if ($2 && $4 != NULL) - store($4, "w", 1); - if ($4 != NULL) - free($4); - } - | FEAT CRLF - { - lreply(211, "Extensions supported:"); -#if 0 - /* XXX these two keywords are non-standard */ - printf(" EPRT\r\n"); - if (!noepsv) - printf(" EPSV\r\n"); -#endif - printf(" MDTM\r\n"); - printf(" REST STREAM\r\n"); - printf(" SIZE\r\n"); - if (assumeutf8) { - /* TVFS requires UTF8, see RFC 3659 */ - printf(" TVFS\r\n"); - printf(" UTF8\r\n"); - } - reply(211, "End."); - } - | SYST check_login CRLF - { - if ($2) { - if (hostinfo) -#ifdef BSD - reply(215, "UNIX Type: L%d Version: BSD-%d", - CHAR_BIT, BSD); -#else /* BSD */ - reply(215, "UNIX Type: L%d", CHAR_BIT); -#endif /* BSD */ - else - reply(215, "UNKNOWN Type: L%d", CHAR_BIT); - } - } - - /* - * SIZE is not in RFC959, but Postel has blessed it and - * it will be in the updated RFC. - * - * Return size of file in a format suitable for - * using with RESTART (we just count bytes). - */ - | SIZE check_login SP pathname CRLF - { - if ($2 && $4 != NULL) - sizecmd($4); - if ($4 != NULL) - free($4); - } - - /* - * MDTM is not in RFC959, but Postel has blessed it and - * it will be in the updated RFC. - * - * Return modification time of file as an ISO 3307 - * style time. E.g. YYYYMMDDHHMMSS or YYYYMMDDHHMMSS.xxx - * where xxx is the fractional second (of any precision, - * not necessarily 3 digits) - */ - | MDTM check_login SP pathname CRLF - { - if ($2 && $4 != NULL) { - struct stat stbuf; - if (stat($4, &stbuf) < 0) - perror_reply(550, $4); - else if (!S_ISREG(stbuf.st_mode)) { - reply(550, "%s: not a plain file.", $4); - } else { - struct tm *t; - t = gmtime(&stbuf.st_mtime); - reply(213, - "%04d%02d%02d%02d%02d%02d", - 1900 + t->tm_year, - t->tm_mon+1, t->tm_mday, - t->tm_hour, t->tm_min, t->tm_sec); - } - } - if ($4 != NULL) - free($4); - } - | QUIT CRLF - { - reply(221, "Goodbye."); - dologout(0); - } - | NOTIMPL - { - nack($1); - } - | error - { - yyclearin; /* discard lookahead data */ - yyerrok; /* clear error condition */ - state = CMD; /* reset lexer state */ - } - ; -rcmd - : RNFR check_login_ro SP pathname CRLF - { - restart_point = 0; - if ($2 && $4) { - if (fromname) - free(fromname); - fromname = NULL; - if (renamefrom($4)) - fromname = $4; - else - free($4); - } else if ($4) { - free($4); - } - } - | REST check_login SP NUMBER CRLF - { - if ($2) { - if (fromname) - free(fromname); - fromname = NULL; - restart_point = $4.o; - reply(350, "Restarting at %jd. %s", - (intmax_t)restart_point, - "Send STORE or RETRIEVE to initiate transfer."); - } - } - ; - -username - : STRING - ; - -password - : /* empty */ - { - $$ = (char *)calloc(1, sizeof(char)); - } - | STRING - ; - -byte_size - : NUMBER - { - $$ = $1.i; - } - ; - -host_port - : NUMBER COMMA NUMBER COMMA NUMBER COMMA NUMBER COMMA - NUMBER COMMA NUMBER - { - char *a, *p; - - data_dest.su_len = sizeof(struct sockaddr_in); - data_dest.su_family = AF_INET; - p = (char *)&data_dest.su_sin.sin_port; - p[0] = $9.i; p[1] = $11.i; - a = (char *)&data_dest.su_sin.sin_addr; - a[0] = $1.i; a[1] = $3.i; a[2] = $5.i; a[3] = $7.i; - } - ; - -host_long_port - : NUMBER COMMA NUMBER COMMA NUMBER COMMA NUMBER COMMA - NUMBER COMMA NUMBER COMMA NUMBER COMMA NUMBER COMMA - NUMBER COMMA NUMBER COMMA NUMBER COMMA NUMBER COMMA - NUMBER COMMA NUMBER COMMA NUMBER COMMA NUMBER COMMA - NUMBER COMMA NUMBER COMMA NUMBER COMMA NUMBER COMMA - NUMBER - { - char *a, *p; - - memset(&data_dest, 0, sizeof(data_dest)); - data_dest.su_len = sizeof(struct sockaddr_in6); - data_dest.su_family = AF_INET6; - p = (char *)&data_dest.su_port; - p[0] = $39.i; p[1] = $41.i; - a = (char *)&data_dest.su_sin6.sin6_addr; - a[0] = $5.i; a[1] = $7.i; a[2] = $9.i; a[3] = $11.i; - a[4] = $13.i; a[5] = $15.i; a[6] = $17.i; a[7] = $19.i; - a[8] = $21.i; a[9] = $23.i; a[10] = $25.i; a[11] = $27.i; - a[12] = $29.i; a[13] = $31.i; a[14] = $33.i; a[15] = $35.i; - if (his_addr.su_family == AF_INET6) { - /* XXX more sanity checks! */ - data_dest.su_sin6.sin6_scope_id = - his_addr.su_sin6.sin6_scope_id; - } - if ($1.i != 6 || $3.i != 16 || $37.i != 2) - memset(&data_dest, 0, sizeof(data_dest)); - } - | NUMBER COMMA NUMBER COMMA NUMBER COMMA NUMBER COMMA - NUMBER COMMA NUMBER COMMA NUMBER COMMA NUMBER COMMA - NUMBER - { - char *a, *p; - - memset(&data_dest, 0, sizeof(data_dest)); - data_dest.su_sin.sin_len = sizeof(struct sockaddr_in); - data_dest.su_family = AF_INET; - p = (char *)&data_dest.su_port; - p[0] = $15.i; p[1] = $17.i; - a = (char *)&data_dest.su_sin.sin_addr; - a[0] = $5.i; a[1] = $7.i; a[2] = $9.i; a[3] = $11.i; - if ($1.i != 4 || $3.i != 4 || $13.i != 2) - memset(&data_dest, 0, sizeof(data_dest)); - } - ; - -form_code - : N - { - $$ = FORM_N; - } - | T - { - $$ = FORM_T; - } - | C - { - $$ = FORM_C; - } - ; - -type_code - : A - { - cmd_type = TYPE_A; - cmd_form = FORM_N; - } - | A SP form_code - { - cmd_type = TYPE_A; - cmd_form = $3; - } - | E - { - cmd_type = TYPE_E; - cmd_form = FORM_N; - } - | E SP form_code - { - cmd_type = TYPE_E; - cmd_form = $3; - } - | I - { - cmd_type = TYPE_I; - } - | L - { - cmd_type = TYPE_L; - cmd_bytesz = CHAR_BIT; - } - | L SP byte_size - { - cmd_type = TYPE_L; - cmd_bytesz = $3; - } - /* this is for a bug in the BBN ftp */ - | L byte_size - { - cmd_type = TYPE_L; - cmd_bytesz = $2; - } - ; - -struct_code - : F - { - $$ = STRU_F; - } - | R - { - $$ = STRU_R; - } - | P - { - $$ = STRU_P; - } - ; - -mode_code - : S - { - $$ = MODE_S; - } - | B - { - $$ = MODE_B; - } - | C - { - $$ = MODE_C; - } - ; - -pathname - : pathstring - { - if (logged_in && $1) { - char *p; - - /* - * Expand ~user manually since glob(3) - * will return the unexpanded pathname - * if the corresponding file/directory - * doesn't exist yet. Using sole glob(3) - * would break natural commands like - * MKD ~user/newdir - * or - * RNTO ~/newfile - */ - if ((p = exptilde($1)) != NULL) { - $$ = expglob(p); - free(p); - } else - $$ = NULL; - free($1); - } else - $$ = $1; - } - ; - -pathstring - : STRING - ; - -octal_number - : NUMBER - { - int ret, dec, multby, digit; - - /* - * Convert a number that was read as decimal number - * to what it would be if it had been read as octal. - */ - dec = $1.i; - multby = 1; - ret = 0; - while (dec) { - digit = dec%10; - if (digit > 7) { - ret = -1; - break; - } - ret += digit * multby; - multby *= 8; - dec /= 10; - } - $$ = ret; - } - ; - - -check_login - : /* empty */ - { - $$ = check_login1(); - } - ; - -check_login_epsv - : /* empty */ - { - if (noepsv) { - reply(500, "EPSV command disabled."); - $$ = 0; - } - else - $$ = check_login1(); - } - ; - -check_login_ro - : /* empty */ - { - if (readonly) { - reply(550, "Permission denied."); - $$ = 0; - } - else - $$ = check_login1(); - } - ; - -%% - -#define CMD 0 /* beginning of command */ -#define ARGS 1 /* expect miscellaneous arguments */ -#define STR1 2 /* expect SP followed by STRING */ -#define STR2 3 /* expect STRING */ -#define OSTR 4 /* optional SP then STRING */ -#define ZSTR1 5 /* optional SP then optional STRING */ -#define ZSTR2 6 /* optional STRING after SP */ -#define SITECMD 7 /* SITE command */ -#define NSTR 8 /* Number followed by a string */ - -#define MAXGLOBARGS 1000 - -#define MAXASIZE 10240 /* Deny ASCII SIZE on files larger than that */ - -struct tab { - char *name; - short token; - short state; - short implemented; /* 1 if command is implemented */ - char *help; -}; - -struct tab cmdtab[] = { /* In order defined in RFC 765 */ - { "USER", USER, STR1, 1, "<sp> username" }, - { "PASS", PASS, ZSTR1, 1, "[<sp> [password]]" }, - { "ACCT", ACCT, STR1, 0, "(specify account)" }, - { "SMNT", SMNT, ARGS, 0, "(structure mount)" }, - { "REIN", REIN, ARGS, 0, "(reinitialize server state)" }, - { "QUIT", QUIT, ARGS, 1, "(terminate service)", }, - { "PORT", PORT, ARGS, 1, "<sp> b0, b1, b2, b3, b4, b5" }, - { "LPRT", LPRT, ARGS, 1, "<sp> af, hal, h1, h2, h3,..., pal, p1, p2..." }, - { "EPRT", EPRT, STR1, 1, "<sp> |af|addr|port|" }, - { "PASV", PASV, ARGS, 1, "(set server in passive mode)" }, - { "LPSV", LPSV, ARGS, 1, "(set server in passive mode)" }, - { "EPSV", EPSV, ARGS, 1, "[<sp> af|ALL]" }, - { "TYPE", TYPE, ARGS, 1, "<sp> { A | E | I | L }" }, - { "STRU", STRU, ARGS, 1, "(specify file structure)" }, - { "MODE", MODE, ARGS, 1, "(specify transfer mode)" }, - { "RETR", RETR, STR1, 1, "<sp> file-name" }, - { "STOR", STOR, STR1, 1, "<sp> file-name" }, - { "APPE", APPE, STR1, 1, "<sp> file-name" }, - { "MLFL", MLFL, OSTR, 0, "(mail file)" }, - { "MAIL", MAIL, OSTR, 0, "(mail to user)" }, - { "MSND", MSND, OSTR, 0, "(mail send to terminal)" }, - { "MSOM", MSOM, OSTR, 0, "(mail send to terminal or mailbox)" }, - { "MSAM", MSAM, OSTR, 0, "(mail send to terminal and mailbox)" }, - { "MRSQ", MRSQ, OSTR, 0, "(mail recipient scheme question)" }, - { "MRCP", MRCP, STR1, 0, "(mail recipient)" }, - { "ALLO", ALLO, ARGS, 1, "allocate storage (vacuously)" }, - { "REST", REST, ARGS, 1, "<sp> offset (restart command)" }, - { "RNFR", RNFR, STR1, 1, "<sp> file-name" }, - { "RNTO", RNTO, STR1, 1, "<sp> file-name" }, - { "ABOR", ABOR, ARGS, 1, "(abort operation)" }, - { "DELE", DELE, STR1, 1, "<sp> file-name" }, - { "CWD", CWD, OSTR, 1, "[ <sp> directory-name ]" }, - { "XCWD", CWD, OSTR, 1, "[ <sp> directory-name ]" }, - { "LIST", LIST, OSTR, 1, "[ <sp> path-name ]" }, - { "NLST", NLST, OSTR, 1, "[ <sp> path-name ]" }, - { "SITE", SITE, SITECMD, 1, "site-cmd [ <sp> arguments ]" }, - { "SYST", SYST, ARGS, 1, "(get type of operating system)" }, - { "FEAT", FEAT, ARGS, 1, "(get extended features)" }, - { "STAT", STAT, OSTR, 1, "[ <sp> path-name ]" }, - { "HELP", HELP, OSTR, 1, "[ <sp> <string> ]" }, - { "NOOP", NOOP, ARGS, 1, "" }, - { "MKD", MKD, STR1, 1, "<sp> path-name" }, - { "XMKD", MKD, STR1, 1, "<sp> path-name" }, - { "RMD", RMD, STR1, 1, "<sp> path-name" }, - { "XRMD", RMD, STR1, 1, "<sp> path-name" }, - { "PWD", PWD, ARGS, 1, "(return current directory)" }, - { "XPWD", PWD, ARGS, 1, "(return current directory)" }, - { "CDUP", CDUP, ARGS, 1, "(change to parent directory)" }, - { "XCUP", CDUP, ARGS, 1, "(change to parent directory)" }, - { "STOU", STOU, STR1, 1, "<sp> file-name" }, - { "SIZE", SIZE, OSTR, 1, "<sp> path-name" }, - { "MDTM", MDTM, OSTR, 1, "<sp> path-name" }, - { NULL, 0, 0, 0, 0 } -}; - -struct tab sitetab[] = { - { "MD5", MDFIVE, STR1, 1, "[ <sp> file-name ]" }, - { "UMASK", UMASK, ARGS, 1, "[ <sp> umask ]" }, - { "IDLE", IDLE, ARGS, 1, "[ <sp> maximum-idle-time ]" }, - { "CHMOD", CHMOD, NSTR, 1, "<sp> mode <sp> file-name" }, - { "HELP", HELP, OSTR, 1, "[ <sp> <string> ]" }, - { NULL, 0, 0, 0, 0 } -}; - -static char *copy(char *); -static char *expglob(char *); -static char *exptilde(char *); -static void help(struct tab *, char *); -static struct tab * - lookup(struct tab *, char *); -static int port_check(const char *); -#ifdef INET6 -static int port_check_v6(const char *); -#endif -static void sizecmd(char *); -static void toolong(int); -#ifdef INET6 -static void v4map_data_dest(void); -#endif -static int yylex(void); - -static struct tab * -lookup(struct tab *p, char *cmd) -{ - - for (; p->name != NULL; p++) - if (strcmp(cmd, p->name) == 0) - return (p); - return (0); -} - -#include <arpa/telnet.h> - -/* - * get_line - a hacked up version of fgets to ignore TELNET escape codes. - */ -int -get_line(char *s, int n, FILE *iop) -{ - int c; - register char *cs; - sigset_t sset, osset; - - cs = s; -/* tmpline may contain saved command from urgent mode interruption */ - for (c = 0; tmpline[c] != '\0' && --n > 0; ++c) { - *cs++ = tmpline[c]; - if (tmpline[c] == '\n') { - *cs++ = '\0'; - if (ftpdebug) - syslog(LOG_DEBUG, "command: %s", s); - tmpline[0] = '\0'; - return(0); - } - if (c == 0) - tmpline[0] = '\0'; - } - /* SIGURG would interrupt stdio if not blocked during the read loop */ - sigemptyset(&sset); - sigaddset(&sset, SIGURG); - sigprocmask(SIG_BLOCK, &sset, &osset); - while ((c = getc(iop)) != EOF) { - c &= 0377; - if (c == IAC) { - if ((c = getc(iop)) == EOF) - goto got_eof; - c &= 0377; - switch (c) { - case WILL: - case WONT: - if ((c = getc(iop)) == EOF) - goto got_eof; - printf("%c%c%c", IAC, DONT, 0377&c); - (void) fflush(stdout); - continue; - case DO: - case DONT: - if ((c = getc(iop)) == EOF) - goto got_eof; - printf("%c%c%c", IAC, WONT, 0377&c); - (void) fflush(stdout); - continue; - case IAC: - break; - default: - continue; /* ignore command */ - } - } - *cs++ = c; - if (--n <= 0) { - /* - * If command doesn't fit into buffer, discard the - * rest of the command and indicate truncation. - * This prevents the command to be split up into - * multiple commands. - */ - while (c != '\n' && (c = getc(iop)) != EOF) - ; - return (-2); - } - if (c == '\n') - break; - } -got_eof: - sigprocmask(SIG_SETMASK, &osset, NULL); - if (c == EOF && cs == s) - return (-1); - *cs++ = '\0'; - if (ftpdebug) { - if (!guest && strncasecmp("pass ", s, 5) == 0) { - /* Don't syslog passwords */ - syslog(LOG_DEBUG, "command: %.5s ???", s); - } else { - register char *cp; - register int len; - - /* Don't syslog trailing CR-LF */ - len = strlen(s); - cp = s + len - 1; - while (cp >= s && (*cp == '\n' || *cp == '\r')) { - --cp; - --len; - } - syslog(LOG_DEBUG, "command: %.*s", len, s); - } - } - return (0); -} - -static void -toolong(int signo) -{ - - reply(421, - "Timeout (%d seconds): closing control connection.", timeout); - if (logging) - syslog(LOG_INFO, "User %s timed out after %d seconds", - (pw ? pw -> pw_name : "unknown"), timeout); - dologout(1); -} - -static int -yylex(void) -{ - static int cpos; - char *cp, *cp2; - struct tab *p; - int n; - char c; - - for (;;) { - switch (state) { - - case CMD: - (void) signal(SIGALRM, toolong); - (void) alarm(timeout); - n = get_line(cbuf, sizeof(cbuf)-1, stdin); - if (n == -1) { - reply(221, "You could at least say goodbye."); - dologout(0); - } else if (n == -2) { - reply(500, "Command too long."); - (void) alarm(0); - continue; - } - (void) alarm(0); -#ifdef SETPROCTITLE - if (strncasecmp(cbuf, "PASS", 4) != 0) - setproctitle("%s: %s", proctitle, cbuf); -#endif /* SETPROCTITLE */ - if ((cp = strchr(cbuf, '\r'))) { - *cp++ = '\n'; - *cp = '\0'; - } - if ((cp = strpbrk(cbuf, " \n"))) - cpos = cp - cbuf; - if (cpos == 0) - cpos = 4; - c = cbuf[cpos]; - cbuf[cpos] = '\0'; - upper(cbuf); - p = lookup(cmdtab, cbuf); - cbuf[cpos] = c; - if (p != 0) { - yylval.s = p->name; - if (!p->implemented) - return (NOTIMPL); /* state remains CMD */ - state = p->state; - return (p->token); - } - break; - - case SITECMD: - if (cbuf[cpos] == ' ') { - cpos++; - return (SP); - } - cp = &cbuf[cpos]; - if ((cp2 = strpbrk(cp, " \n"))) - cpos = cp2 - cbuf; - c = cbuf[cpos]; - cbuf[cpos] = '\0'; - upper(cp); - p = lookup(sitetab, cp); - cbuf[cpos] = c; - if (guest == 0 && p != 0) { - yylval.s = p->name; - if (!p->implemented) { - state = CMD; - return (NOTIMPL); - } - state = p->state; - return (p->token); - } - state = CMD; - break; - - case ZSTR1: - case OSTR: - if (cbuf[cpos] == '\n') { - state = CMD; - return (CRLF); - } - /* FALLTHROUGH */ - - case STR1: - dostr1: - if (cbuf[cpos] == ' ') { - cpos++; - state = state == OSTR ? STR2 : state+1; - return (SP); - } - break; - - case ZSTR2: - if (cbuf[cpos] == '\n') { - state = CMD; - return (CRLF); - } - /* FALLTHROUGH */ - - case STR2: - cp = &cbuf[cpos]; - n = strlen(cp); - cpos += n - 1; - /* - * Make sure the string is nonempty and \n terminated. - */ - if (n > 1 && cbuf[cpos] == '\n') { - cbuf[cpos] = '\0'; - yylval.s = copy(cp); - cbuf[cpos] = '\n'; - state = ARGS; - return (STRING); - } - break; - - case NSTR: - if (cbuf[cpos] == ' ') { - cpos++; - return (SP); - } - if (isdigit(cbuf[cpos])) { - cp = &cbuf[cpos]; - while (isdigit(cbuf[++cpos])) - ; - c = cbuf[cpos]; - cbuf[cpos] = '\0'; - yylval.u.i = atoi(cp); - cbuf[cpos] = c; - state = STR1; - return (NUMBER); - } - state = STR1; - goto dostr1; - - case ARGS: - if (isdigit(cbuf[cpos])) { - cp = &cbuf[cpos]; - while (isdigit(cbuf[++cpos])) - ; - c = cbuf[cpos]; - cbuf[cpos] = '\0'; - yylval.u.i = atoi(cp); - yylval.u.o = strtoull(cp, NULL, 10); - cbuf[cpos] = c; - return (NUMBER); - } - if (strncasecmp(&cbuf[cpos], "ALL", 3) == 0 - && !isalnum(cbuf[cpos + 3])) { - cpos += 3; - return ALL; - } - switch (cbuf[cpos++]) { - - case '\n': - state = CMD; - return (CRLF); - - case ' ': - return (SP); - - case ',': - return (COMMA); - - case 'A': - case 'a': - return (A); - - case 'B': - case 'b': - return (B); - - case 'C': - case 'c': - return (C); - - case 'E': - case 'e': - return (E); - - case 'F': - case 'f': - return (F); - - case 'I': - case 'i': - return (I); - - case 'L': - case 'l': - return (L); - - case 'N': - case 'n': - return (N); - - case 'P': - case 'p': - return (P); - - case 'R': - case 'r': - return (R); - - case 'S': - case 's': - return (S); - - case 'T': - case 't': - return (T); - - } - break; - - default: - fatalerror("Unknown state in scanner."); - } - state = CMD; - return (LEXERR); - } -} - -void -upper(char *s) -{ - while (*s != '\0') { - if (islower(*s)) - *s = toupper(*s); - s++; - } -} - -static char * -copy(char *s) -{ - char *p; - - p = malloc(strlen(s) + 1); - if (p == NULL) - fatalerror("Ran out of memory."); - (void) strcpy(p, s); - return (p); -} - -static void -help(struct tab *ctab, char *s) -{ - struct tab *c; - int width, NCMDS; - char *type; - - if (ctab == sitetab) - type = "SITE "; - else - type = ""; - width = 0, NCMDS = 0; - for (c = ctab; c->name != NULL; c++) { - int len = strlen(c->name); - - if (len > width) - width = len; - NCMDS++; - } - width = (width + 8) &~ 7; - if (s == 0) { - int i, j, w; - int columns, lines; - - lreply(214, "The following %scommands are recognized %s.", - type, "(* =>'s unimplemented)"); - columns = 76 / width; - if (columns == 0) - columns = 1; - lines = (NCMDS + columns - 1) / columns; - for (i = 0; i < lines; i++) { - printf(" "); - for (j = 0; j < columns; j++) { - c = ctab + j * lines + i; - printf("%s%c", c->name, - c->implemented ? ' ' : '*'); - if (c + lines >= &ctab[NCMDS]) - break; - w = strlen(c->name) + 1; - while (w < width) { - putchar(' '); - w++; - } - } - printf("\r\n"); - } - (void) fflush(stdout); - if (hostinfo) - reply(214, "Direct comments to ftp-bugs@%s.", hostname); - else - reply(214, "End."); - return; - } - upper(s); - c = lookup(ctab, s); - if (c == NULL) { - reply(502, "Unknown command %s.", s); - return; - } - if (c->implemented) - reply(214, "Syntax: %s%s %s", type, c->name, c->help); - else - reply(214, "%s%-*s\t%s; unimplemented.", type, width, - c->name, c->help); -} - -static void -sizecmd(char *filename) -{ - switch (type) { - case TYPE_L: - case TYPE_I: { - struct stat stbuf; - if (stat(filename, &stbuf) < 0) - perror_reply(550, filename); - else if (!S_ISREG(stbuf.st_mode)) - reply(550, "%s: not a plain file.", filename); - else - reply(213, "%jd", (intmax_t)stbuf.st_size); - break; } - case TYPE_A: { - FILE *fin; - int c; - off_t count; - struct stat stbuf; - fin = fopen(filename, "r"); - if (fin == NULL) { - perror_reply(550, filename); - return; - } - if (fstat(fileno(fin), &stbuf) < 0) { - perror_reply(550, filename); - (void) fclose(fin); - return; - } else if (!S_ISREG(stbuf.st_mode)) { - reply(550, "%s: not a plain file.", filename); - (void) fclose(fin); - return; - } else if (stbuf.st_size > MAXASIZE) { - reply(550, "%s: too large for type A SIZE.", filename); - (void) fclose(fin); - return; - } - - count = 0; - while((c=getc(fin)) != EOF) { - if (c == '\n') /* will get expanded to \r\n */ - count++; - count++; - } - (void) fclose(fin); - - reply(213, "%jd", (intmax_t)count); - break; } - default: - reply(504, "SIZE not implemented for type %s.", - typenames[type]); - } -} - -/* Return 1, if port check is done. Return 0, if not yet. */ -static int -port_check(const char *pcmd) -{ - if (his_addr.su_family == AF_INET) { - if (data_dest.su_family != AF_INET) { - usedefault = 1; - reply(500, "Invalid address rejected."); - return 1; - } - if (paranoid && - ((ntohs(data_dest.su_port) < IPPORT_RESERVED) || - memcmp(&data_dest.su_sin.sin_addr, - &his_addr.su_sin.sin_addr, - sizeof(data_dest.su_sin.sin_addr)))) { - usedefault = 1; - reply(500, "Illegal PORT range rejected."); - } else { - usedefault = 0; - if (pdata >= 0) { - (void) close(pdata); - pdata = -1; - } - reply(200, "%s command successful.", pcmd); - } - return 1; - } - return 0; -} - -static int -check_login1(void) -{ - if (logged_in) - return 1; - else { - reply(530, "Please login with USER and PASS."); - return 0; - } -} - -/* - * Replace leading "~user" in a pathname by the user's login directory. - * Returned string will be in a freshly malloced buffer unless it's NULL. - */ -static char * -exptilde(char *s) -{ - char *p, *q; - char *path, *user; - struct passwd *ppw; - - if ((p = strdup(s)) == NULL) - return (NULL); - if (*p != '~') - return (p); - - user = p + 1; /* skip tilde */ - if ((path = strchr(p, '/')) != NULL) - *(path++) = '\0'; /* separate ~user from the rest of path */ - if (*user == '\0') /* no user specified, use the current user */ - user = pw->pw_name; - /* read passwd even for the current user since we may be chrooted */ - if ((ppw = getpwnam(user)) != NULL) { - /* user found, substitute login directory for ~user */ - if (path) - asprintf(&q, "%s/%s", ppw->pw_dir, path); - else - q = strdup(ppw->pw_dir); - free(p); - p = q; - } else { - /* user not found, undo the damage */ - if (path) - path[-1] = '/'; - } - return (p); -} - -/* - * Expand glob(3) patterns possibly present in a pathname. - * Avoid expanding to a pathname including '\r' or '\n' in order to - * not disrupt the FTP protocol. - * The expansion found must be unique. - * Return the result as a malloced string, or NULL if an error occurred. - * - * Problem: this production is used for all pathname - * processing, but only gives a 550 error reply. - * This is a valid reply in some cases but not in others. - */ -static char * -expglob(char *s) -{ - char *p, **pp, *rval; - int flags = GLOB_BRACE | GLOB_NOCHECK; - int n; - glob_t gl; - - memset(&gl, 0, sizeof(gl)); - flags |= GLOB_LIMIT; - gl.gl_matchc = MAXGLOBARGS; - if (glob(s, flags, NULL, &gl) == 0 && gl.gl_pathc != 0) { - for (pp = gl.gl_pathv, p = NULL, n = 0; *pp; pp++) - if (*(*pp + strcspn(*pp, "\r\n")) == '\0') { - p = *pp; - n++; - } - if (n == 0) - rval = strdup(s); - else if (n == 1) - rval = strdup(p); - else { - reply(550, "Wildcard is ambiguous."); - rval = NULL; - } - } else { - reply(550, "Wildcard expansion error."); - rval = NULL; - } - globfree(&gl); - return (rval); -} - -#ifdef INET6 -/* Return 1, if port check is done. Return 0, if not yet. */ -static int -port_check_v6(const char *pcmd) -{ - if (his_addr.su_family == AF_INET6) { - if (IN6_IS_ADDR_V4MAPPED(&his_addr.su_sin6.sin6_addr)) - /* Convert data_dest into v4 mapped sockaddr.*/ - v4map_data_dest(); - if (data_dest.su_family != AF_INET6) { - usedefault = 1; - reply(500, "Invalid address rejected."); - return 1; - } - if (paranoid && - ((ntohs(data_dest.su_port) < IPPORT_RESERVED) || - memcmp(&data_dest.su_sin6.sin6_addr, - &his_addr.su_sin6.sin6_addr, - sizeof(data_dest.su_sin6.sin6_addr)))) { - usedefault = 1; - reply(500, "Illegal PORT range rejected."); - } else { - usedefault = 0; - if (pdata >= 0) { - (void) close(pdata); - pdata = -1; - } - reply(200, "%s command successful.", pcmd); - } - return 1; - } - return 0; -} - -static void -v4map_data_dest(void) -{ - struct in_addr savedaddr; - int savedport; - - if (data_dest.su_family != AF_INET) { - usedefault = 1; - reply(500, "Invalid address rejected."); - return; - } - - savedaddr = data_dest.su_sin.sin_addr; - savedport = data_dest.su_port; - - memset(&data_dest, 0, sizeof(data_dest)); - data_dest.su_sin6.sin6_len = sizeof(struct sockaddr_in6); - data_dest.su_sin6.sin6_family = AF_INET6; - data_dest.su_sin6.sin6_port = savedport; - memset((caddr_t)&data_dest.su_sin6.sin6_addr.s6_addr[10], 0xff, 2); - memcpy((caddr_t)&data_dest.su_sin6.sin6_addr.s6_addr[12], - (caddr_t)&savedaddr, sizeof(savedaddr)); -} -#endif |