diff --git a/build/jam/HaikuImage b/build/jam/HaikuImage index 5d1977edb7..cf2031ca5a 100644 --- a/build/jam/HaikuImage +++ b/build/jam/HaikuImage @@ -28,7 +28,8 @@ BEOS_BIN = "[" addattr alert arp base64 basename bc beep bison bzip2 cal cat cp copyattr $(X86_ONLY)CortexAddOnHost csplit ctags cut date dc dd desklink df diff diff3 dircolors dirname driveinfo dstcheck du echo eject env error expand expr - expr factor false fdinfo ffm find finddir fmt fold fortune frcode ftp funzip fwcontrol + expr factor false fdinfo ffm find finddir fmt fold fortune frcode ftp ftpd + funzip fwcontrol gawk $(X86_ONLY)gdb grep groups gzip gzexe hd head hey hostname id ident ideinfo idestatus ifconfig install installsound iroster isvolume join keymap kill less lessecho lesskey link listarea listattr listdev listimage diff --git a/data/settings/network/services b/data/settings/network/services index 7e1725035d..b1cad56855 100644 --- a/data/settings/network/services +++ b/data/settings/network/services @@ -1,3 +1,7 @@ service telnet { launch telnetd } + +service ftp { + launch ftpd +} diff --git a/src/bin/network/Jamfile b/src/bin/network/Jamfile index 3533dbfd31..dc5f51027c 100644 --- a/src/bin/network/Jamfile +++ b/src/bin/network/Jamfile @@ -3,6 +3,7 @@ SubDir HAIKU_TOP src bin network ; SubInclude HAIKU_TOP src bin network arp ; SubInclude HAIKU_TOP src bin network atftpd ; SubInclude HAIKU_TOP src bin network ftp ; +SubInclude HAIKU_TOP src bin network ftpd ; SubInclude HAIKU_TOP src bin network ifconfig ; SubInclude HAIKU_TOP src bin network login ; SubInclude HAIKU_TOP src bin network mount_nfs ; diff --git a/src/bin/network/ftpd/Jamfile b/src/bin/network/ftpd/Jamfile new file mode 100644 index 0000000000..f87b6576e8 --- /dev/null +++ b/src/bin/network/ftpd/Jamfile @@ -0,0 +1,24 @@ +SubDir HAIKU_TOP src bin network ftpd ; + +SetSubDirSupportedPlatforms $(HAIKU_BONE_COMPATIBLE_PLATFORMS) ; + +if ! $(TARGET_PLATFORM_HAIKU_COMPATIBLE) { + UseHeaders [ FDirName $(HAIKU_TOP) headers posix ] : true ; + # We need the public network headers also when not compiling for Haiku. + # Unfortunately we get more than we want, namely all POSIX headers. +} + +UseHeaders [ FDirName $(HAIKU_TOP) headers compatibility bsd ] : true ; +UseHeaders $(SUBDIR) : true ; + +BinCommand ftpd : + ftpd.c + ftpcmd.y + logwtmp.c + popen.c + + # from libmd + md5c.c + md5hl.c + : libutil.a libbsd.so $(TARGET_NETWORK_LIBS) +; diff --git a/src/bin/network/ftpd/config.h b/src/bin/network/ftpd/config.h new file mode 100644 index 0000000000..ac75ac232d --- /dev/null +++ b/src/bin/network/ftpd/config.h @@ -0,0 +1,284 @@ +/* $FreeBSD: src/libexec/ftpd/config.h,v 1.1 2001/07/19 17:45:14 obrien Exp $ */ + + +/* config.h. Generated automatically by configure. */ +/* config.h.in. Generated automatically from configure.in by autoheader. */ +/* $Id: config.h.in,v 1.15 2001/04/28 07:11:46 lukem Exp $ */ + + +/* Define if the closedir function returns void instead of int. */ +/* #undef CLOSEDIR_VOID */ + +/* Define to empty if the keyword does not work. */ +/* #undef const */ + +/* Define if your C compiler doesn't accept -c and -o together. */ +/* #undef NO_MINUS_C_MINUS_O */ + +/* Define if your Fortran 77 compiler doesn't accept -c and -o together. */ +/* #undef F77_NO_MINUS_C_MINUS_O */ + +/* Define to `long' if doesn't define. */ +/* #undef off_t */ + +/* Define to the type of arg1 for select(). */ +/* #undef SELECT_TYPE_ARG1 */ + +/* Define to the type of args 2, 3 and 4 for select(). */ +/* #undef SELECT_TYPE_ARG234 */ + +/* Define to the type of arg5 for select(). */ +/* #undef SELECT_TYPE_ARG5 */ + +/* Define if you have the ANSI C header files. */ +#define STDC_HEADERS 1 + +/* Define if you can safely include both and . */ +#define TIME_WITH_SYS_TIME 1 + +/* Define if the closedir function returns void instead of int. */ +/* #undef VOID_CLOSEDIR */ + +/* The number of bytes in a off_t. */ +#define SIZEOF_OFF_T 0 + +/* Define if you have the err function. */ +#define HAVE_ERR 1 + +/* Define if you have the fgetln function. */ +#define HAVE_FGETLN 1 + +/* Define if you have the flock function. */ +#define HAVE_FLOCK 1 + +/* Define if you have the fparseln function. */ +#define HAVE_FPARSELN 1 + +/* Define if you have the fts_open function. */ +#define HAVE_FTS_OPEN 1 + +/* Define if you have the getaddrinfo function. */ +#define HAVE_GETADDRINFO 1 + +/* Define if you have the getgrouplist function. */ +#define HAVE_GETGROUPLIST 1 + +/* Define if you have the getnameinfo function. */ +#define HAVE_GETNAMEINFO 1 + +/* Define if you have the getspnam function. */ +/* #undef HAVE_GETSPNAM */ + +/* Define if you have the getusershell function. */ +#define HAVE_GETUSERSHELL 1 + +/* Define if you have the inet_net_pton function. */ +#define HAVE_INET_NET_PTON 1 + +/* Define if you have the inet_ntop function. */ +#define HAVE_INET_NTOP 1 + +/* Define if you have the inet_pton function. */ +#define HAVE_INET_PTON 1 + +/* Define if you have the lockf function. */ +#define HAVE_LOCKF 1 + +/* Define if you have the mkstemp function. */ +#define HAVE_MKSTEMP 1 + +/* Define if you have the setlogin function. */ +#define HAVE_SETLOGIN 1 + +/* Define if you have the setproctitle function. */ +#define HAVE_SETPROCTITLE 1 + +/* Define if you have the sl_init function. */ +#define HAVE_SL_INIT 1 + +/* Define if you have the snprintf function. */ +#define HAVE_SNPRINTF 1 + +/* Define if you have the strdup function. */ +#define HAVE_STRDUP 1 + +/* Define if you have the strerror function. */ +#define HAVE_STRERROR 1 + +/* Define if you have the strlcat function. */ +#define HAVE_STRLCAT 1 + +/* Define if you have the strlcpy function. */ +#define HAVE_STRLCPY 1 + +/* Define if you have the strmode function. */ +#define HAVE_STRMODE 1 + +/* Define if you have the strsep function. */ +#define HAVE_STRSEP 1 + +/* Define if you have the strtoll function. */ +#define HAVE_STRTOLL 1 + +/* Define if you have the user_from_uid function. */ +#define HAVE_USER_FROM_UID 1 + +/* Define if you have the usleep function. */ +#define HAVE_USLEEP 1 + +/* Define if you have the vfork function. */ +#define HAVE_VFORK 1 + +/* Define if you have the vsyslog function. */ +#define HAVE_VSYSLOG 1 + +/* Define if you have the header file. */ +#define HAVE_ARPA_NAMESER_H 1 + +/* Define if you have the header file. */ +#define HAVE_DIRENT_H 1 + +/* Define if you have the header file. */ +#define HAVE_ERR_H 1 + +/* Define if you have the header file. */ +#define HAVE_FTS_H 1 + +/* Define if you have the header file. */ +#define HAVE_LIBUTIL_H 1 + +/* Define if you have the header file. */ +/* #undef HAVE_NDIR_H */ + +/* Define if you have the header file. */ +#define HAVE_PATHS_H 1 + +/* Define if you have the header file. */ +#define HAVE_SYS_DIR_H 1 + +/* Define if you have the header file. */ +/* #undef HAVE_SYS_NDIR_H */ + +/* Define if you have the header file. */ +/* #undef HAVE_SYS_SYSMACROS_H */ + +/* Define if you have the header file. */ +/* #undef HAVE_UTIL_H */ + +/* Define if you have the crypt library (-lcrypt). */ +#define HAVE_LIBCRYPT 1 + +/* Define if you have the nsl library (-lnsl). */ +/* #undef HAVE_LIBNSL */ + +/* Define if you have the skey library (-lskey). */ +/* #undef HAVE_LIBSKEY */ + +/* Define if you have the opie library (-lopie). */ +/* #undef HAVE_LIBOPIE */ + +/* Define if you have the socket library (-lsocket). */ +/* #undef HAVE_LIBSOCKET */ + +/* Define if you have the util library (-lutil). */ +#define HAVE_LIBUTIL 1 + +/* Define if your compiler supports `long long' */ +#define HAVE_LONG_LONG 1 + +/* Define if *printf() uses %qd to print `long long' (otherwise uses %lld) */ +#define HAVE_PRINTF_QD 1 + +/* Define if in_port_t exists */ +#define HAVE_IN_PORT_T 1 + +/* Define if struct sockaddr.sa_len exists (implies sockaddr_in.sin_len, etc) */ +#define HAVE_SOCKADDR_SA_LEN 1 + +/* Define if socklen_t exists */ +#define HAVE_SOCKLEN_T 1 + +/* Define if AF_INET6 exists in */ +/* #define HAVE_AF_INET6 1 */ + +/* Define if `struct sockaddr_in6' exists in */ +#define HAVE_SOCKADDR_IN6 1 + +/* Define if `struct addrinfo' exists in */ +#define HAVE_ADDRINFO 1 + +/* + * Define if contains AI_NUMERICHOST et al. + * Systems which only implement RFC2133 will need this. + */ +#define HAVE_RFC2553_NETDB 1 + +/* Define if `struct direct' has a d_namlen element */ +#define HAVE_D_NAMLEN 1 + +/* Define if struct passwd.pw_expire exists. */ +#define HAVE_PW_EXPIRE 1 + +/* Define if GLOB_BRACE, gl_path and gl_match exist in */ +#define HAVE_WORKING_GLOB 1 + +/* Define if crypt() is declared in */ +#define HAVE_CRYPT_D 1 + +/* Define if fclose() is declared in */ +#define HAVE_FCLOSE_D 1 + +/* Define if optarg is declared in or */ +#define HAVE_OPTARG_D 1 + +/* Define if optind is declared in or */ +#define HAVE_OPTIND_D 1 + +/* Define if optreset exists */ +#define HAVE_OPTRESET 1 + +/* Define if pclose() is declared in */ +#define HAVE_PCLOSE_D 1 + +/* Define if getusershell() is declared in */ +#define HAVE_GETUSERSHELL_D 1 + +/* Define if `long long' is supported and sizeof(off_t) >= 8 */ +#define HAVE_QUAD_SUPPORT 1 + +/* Define if not using in-built /bin/ls code */ +/* #undef NO_INTERNAL_LS */ + +/* Define if using S/Key */ +/* #undef SKEY */ + +/* + * Define this if compiling with SOCKS (the firewall traversal library). + * Also, you must define connect, getsockname, bind, accept, listen, and + * select to their R-versions. + */ +/* #undef SOCKS */ +/* #undef SOCKS4 */ +/* #undef SOCKS5 */ +/* #undef connect */ +/* #undef getsockname */ +/* #undef bind */ +/* #undef accept */ +/* #undef listen */ +/* #undef select */ +/* #undef dup */ +/* #undef dup2 */ +/* #undef fclose */ +/* #undef gethostbyname */ +/* #undef getpeername */ +/* #undef read */ +/* #undef recv */ +/* #undef recvfrom */ +/* #undef rresvport */ +/* #undef send */ +/* #undef sendto */ +/* #undef shutdown */ +/* #undef write */ + +/* Define if you have the header file. */ +#define HAVE_FTP_NAMES 1 diff --git a/src/bin/network/ftpd/extern.h b/src/bin/network/ftpd/extern.h new file mode 100644 index 0000000000..b0dab93704 --- /dev/null +++ b/src/bin/network/ftpd/extern.h @@ -0,0 +1,89 @@ +/*- + * Copyright (c) 1992, 1993 + * 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. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the University of + * California, Berkeley and its contributors. + * 4. 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. + * + * @(#)extern.h 8.2 (Berkeley) 4/4/94 + * $FreeBSD: src/libexec/ftpd/extern.h,v 1.19 2002/02/04 01:23:44 kris Exp $ + */ + +#include +#include + +void blkfree(char **); +char **copyblk(char **); +void cwd(char *); +void delete(char *); +void dologout(int); +void fatalerror(char *); +void ftpd_logwtmp(char *, char *, struct sockaddr *addr); +int ftpd_pclose(FILE *); +FILE *ftpd_popen(char *, char *); +char *getline(char *, int, FILE *); +void lreply(int, const char *, ...) __printflike(2, 3); +void makedir(char *); +void nack(char *); +void pass(char *); +void passive(void); +void long_passive(char *, int); +void perror_reply(int, char *); +void pwd(void); +void removedir(char *); +void renamecmd(char *, char *); +char *renamefrom(char *); +void reply(int, const char *, ...) __printflike(2, 3); +void retrieve(char *, char *); +void send_file_list(char *); +#ifdef OLD_SETPROCTITLE +void setproctitle(const char *, ...); +#endif +void statcmd(void); +void statfilecmd(char *); +void store(char *, char *, int); +void upper(char *); +void user(char *); +void yyerror(char *); +int yyparse(void); +int ls_main(int, char **); + +struct sockaddr_in; +struct sockaddr_in6; +union sockunion { + struct sockinet { + u_char si_len; + u_char si_family; + u_short si_port; + } su_si; + struct sockaddr_in su_sin; + struct sockaddr_in6 su_sin6; +}; +#define su_len su_si.si_len +#define su_family su_si.si_family +#define su_port su_si.si_port diff --git a/src/bin/network/ftpd/ftpcmd.y b/src/bin/network/ftpd/ftpcmd.y new file mode 100644 index 0000000000..244ba0be14 --- /dev/null +++ b/src/bin/network/ftpd/ftpcmd.y @@ -0,0 +1,1829 @@ +/* + * 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. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the University of + * California, Berkeley and its contributors. + * 4. 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. + * + * @(#)ftpcmd.y 8.3 (Berkeley) 4/6/94 + */ + +/* + * Grammar for FTP commands. + * See RFC 959. + */ + +%{ + +#ifndef lint +#if 0 +static char sccsid[] = "@(#)ftpcmd.y 8.3 (Berkeley) 4/6/94"; +#endif +#endif /* not lint */ + +#include +__FBSDID("$FreeBSD: src/libexec/ftpd/ftpcmd.y,v 1.66 2007/04/18 22:43:39 yar Exp $"); + +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "extern.h" +#include "pathnames.h" + +extern union sockunion data_dest, his_addr; +extern int hostinfo; +extern int logged_in; +extern struct passwd *pw; +extern int guest; +extern char *homedir; +extern int paranoid; +extern int logging; +extern int type; +extern int form; +extern int ftpdebug; +extern int timeout; +extern int maxtimeout; +extern int pdata; +extern char *hostname; +extern char proctitle[]; +extern int usedefault; +extern char tmpline[]; +extern int readonly; +extern int assumeutf8; +extern int noepsv; +extern int noretr; +extern int noguestretr; +extern char *typenames[]; /* defined in included from ftpd.c */ + +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; + +extern int epsvall; + +#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, " username" }, + { "PASS", PASS, ZSTR1, 1, "[ [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, " b0, b1, b2, b3, b4, b5" }, + { "LPRT", LPRT, ARGS, 1, " af, hal, h1, h2, h3,..., pal, p1, p2..." }, + { "EPRT", EPRT, STR1, 1, " |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, "[ af|ALL]" }, + { "TYPE", TYPE, ARGS, 1, " { A | E | I | L }" }, + { "STRU", STRU, ARGS, 1, "(specify file structure)" }, + { "MODE", MODE, ARGS, 1, "(specify transfer mode)" }, + { "RETR", RETR, STR1, 1, " file-name" }, + { "STOR", STOR, STR1, 1, " file-name" }, + { "APPE", APPE, STR1, 1, " 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, " offset (restart command)" }, + { "RNFR", RNFR, STR1, 1, " file-name" }, + { "RNTO", RNTO, STR1, 1, " file-name" }, + { "ABOR", ABOR, ARGS, 1, "(abort operation)" }, + { "DELE", DELE, STR1, 1, " file-name" }, + { "CWD", CWD, OSTR, 1, "[ directory-name ]" }, + { "XCWD", CWD, OSTR, 1, "[ directory-name ]" }, + { "LIST", LIST, OSTR, 1, "[ path-name ]" }, + { "NLST", NLST, OSTR, 1, "[ path-name ]" }, + { "SITE", SITE, SITECMD, 1, "site-cmd [ arguments ]" }, + { "SYST", SYST, ARGS, 1, "(get type of operating system)" }, + { "FEAT", FEAT, ARGS, 1, "(get extended features)" }, + { "STAT", STAT, OSTR, 1, "[ path-name ]" }, + { "HELP", HELP, OSTR, 1, "[ ]" }, + { "NOOP", NOOP, ARGS, 1, "" }, + { "MKD", MKD, STR1, 1, " path-name" }, + { "XMKD", MKD, STR1, 1, " path-name" }, + { "RMD", RMD, STR1, 1, " path-name" }, + { "XRMD", RMD, STR1, 1, " 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, " file-name" }, + { "SIZE", SIZE, OSTR, 1, " path-name" }, + { "MDTM", MDTM, OSTR, 1, " path-name" }, + { NULL, 0, 0, 0, 0 } +}; + +struct tab sitetab[] = { + { "MD5", MDFIVE, STR1, 1, "[ file-name ]" }, + { "UMASK", UMASK, ARGS, 1, "[ umask ]" }, + { "IDLE", IDLE, ARGS, 1, "[ maximum-idle-time ]" }, + { "CHMOD", CHMOD, NSTR, 1, " mode file-name" }, + { "HELP", HELP, OSTR, 1, "[ ]" }, + { 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 int check_login1(void); +static void sizecmd(char *); +static void toolong(int); +#ifdef INET6 +static void v4map_data_dest(void); +#endif +static int yylex(void); + +%} + +%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 STRING +%token NUMBER + +%type check_login octal_number byte_size +%type check_login_ro check_login_epsv +%type struct_code mode_code type_code form_code +%type pathstring pathname password username +%type 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 " -lgA", ""); + } + | LIST check_login SP pathstring CRLF + { + if ($2) + retrieve(_PATH_LS " -lgA %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 %lld. %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(); + } + ; + +%% + +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 + +/* + * getline - a hacked up version of fgets to ignore TELNET escape codes. + */ +char * +getline(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(s); + } + 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 || c == '\n') + break; + } +got_eof: + sigprocmask(SIG_SETMASK, &osset, NULL); + if (c == EOF && cs == s) + return (NULL); + *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 (s); +} + +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); + if (getline(cbuf, sizeof(cbuf)-1, stdin) == NULL) { + reply(221, "You could at least say goodbye."); + dologout(0); + } + (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, "%lld", (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, "%lld", (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 occured. + * + * 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 diff --git a/src/bin/network/ftpd/ftpd.c b/src/bin/network/ftpd/ftpd.c new file mode 100644 index 0000000000..d7d2a125d3 --- /dev/null +++ b/src/bin/network/ftpd/ftpd.c @@ -0,0 +1,3502 @@ +/* + * Copyright (c) 1985, 1988, 1990, 1992, 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. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the University of + * California, Berkeley and its contributors. + * 4. 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. + */ + +#if 0 +#ifndef lint +static char copyright[] = +"@(#) Copyright (c) 1985, 1988, 1990, 1992, 1993, 1994\n\ + The Regents of the University of California. All rights reserved.\n"; +#endif /* not lint */ +#endif + +#ifndef lint +#if 0 +static char sccsid[] = "@(#)ftpd.c 8.4 (Berkeley) 4/16/94"; +#endif +#endif /* not lint */ + +#include +__FBSDID("$FreeBSD: src/libexec/ftpd/ftpd.c,v 1.212 2007/04/18 22:43:39 yar Exp $"); + +/* + * FTP server. + */ +#include +#include +//#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#define FTP_NAMES +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef HAVE_LIBOPIE +#include +#endif +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef LOGIN_CAP +#include +#endif + +#ifdef USE_PAM +#include +#endif + +#include "pathnames.h" +#include "extern.h" + +#include + +static char version[] = "Version 6.00LS"; +#undef main + +extern off_t restart_point; +extern char cbuf[]; + +union sockunion ctrl_addr; +union sockunion data_source; +union sockunion data_dest; +union sockunion his_addr; +union sockunion pasv_addr; + +int daemon_mode; +int data; +int dataport; +int hostinfo = 1; /* print host-specific info in messages */ +int logged_in; +struct passwd *pw; +char *homedir; +int ftpdebug; +int timeout = 900; /* timeout after 15 minutes of inactivity */ +int maxtimeout = 7200;/* don't allow idle time to be set beyond 2 hours */ +int logging; +int restricted_data_ports = 1; +int paranoid = 1; /* be extra careful about security */ +int anon_only = 0; /* Only anonymous ftp allowed */ +int assumeutf8 = 0; /* Assume that server file names are in UTF-8 */ +int guest; +int dochroot; +char *chrootdir; +int dowtmp = 1; +int stats; +int statfd = -1; +int type; +int form; +int stru; /* avoid C keyword */ +int mode; +int usedefault = 1; /* for data transfers */ +int pdata = -1; /* for passive mode */ +int readonly = 0; /* Server is in readonly mode. */ +int noepsv = 0; /* EPSV command is disabled. */ +int noretr = 0; /* RETR command is disabled. */ +int noguestretr = 0; /* RETR command is disabled for anon users. */ +int noguestmkd = 0; /* MKD command is disabled for anon users. */ +int noguestmod = 1; /* anon users may not modify existing files. */ + +off_t file_size; +off_t byte_count; +#if !defined(CMASK) || CMASK == 0 +#undef CMASK +#define CMASK 027 +#endif +int defumask = CMASK; /* default umask value */ +char tmpline[7]; +char *hostname; +int epsvall = 0; + +#ifdef VIRTUAL_HOSTING +char *ftpuser; + +static struct ftphost { + struct ftphost *next; + struct addrinfo *hostinfo; + char *hostname; + char *anonuser; + char *statfile; + char *welcome; + char *loginmsg; +} *thishost, *firsthost; + +#endif +char remotehost[NI_MAXHOST]; +char *ident = NULL; + +static char ttyline[20]; +char *tty = ttyline; /* for klogin */ + +#ifdef USE_PAM +static int auth_pam(struct passwd**, const char*); +pam_handle_t *pamh = NULL; +#endif + +#ifdef HAVE_LIBOPIE +static struct opie opiedata; +static char opieprompt[OPIE_CHALLENGE_MAX+1]; +#endif +static int pwok; + +char *pid_file = NULL; /* means default location to pidfile(3) */ + +/* + * Limit number of pathnames that glob can return. + * A limit of 0 indicates the number of pathnames is unlimited. + */ +#define MAXGLOBARGS 16384 +# + +/* + * Timeout intervals for retrying connections + * to hosts that don't accept PORT cmds. This + * is a kludge, but given the problems with TCP... + */ +#define SWAITMAX 90 /* wait at most 90 seconds */ +#define SWAITINT 5 /* interval between retries */ + +int swaitmax = SWAITMAX; +int swaitint = SWAITINT; + +#ifdef SETPROCTITLE +#ifdef OLD_SETPROCTITLE +char **Argv = NULL; /* pointer to argument vector */ +char *LastArgv = NULL; /* end of argv */ +#endif /* OLD_SETPROCTITLE */ +char proctitle[LINE_MAX]; /* initial part of title */ +#endif /* SETPROCTITLE */ + +#define LOGCMD(cmd, file) logcmd((cmd), (file), NULL, -1) +#define LOGCMD2(cmd, file1, file2) logcmd((cmd), (file1), (file2), -1) +#define LOGBYTES(cmd, file, cnt) logcmd((cmd), (file), NULL, (cnt)) + +static volatile sig_atomic_t recvurg; +static int transflag; /* NB: for debugging only */ + +#define STARTXFER flagxfer(1) +#define ENDXFER flagxfer(0) + +#define START_UNSAFE maskurg(1) +#define END_UNSAFE maskurg(0) + +/* It's OK to put an `else' clause after this macro. */ +#define CHECKOOB(action) \ + if (recvurg) { \ + recvurg = 0; \ + if (myoob()) { \ + ENDXFER; \ + action; \ + } \ + } + +#ifdef VIRTUAL_HOSTING +static void inithosts(int); +static void selecthost(union sockunion *); +#endif +static void ack(char *); +static void sigurg(int); +static void maskurg(int); +static void flagxfer(int); +static int myoob(void); +static int checkuser(char *, char *, int, char **); +static FILE *dataconn(char *, off_t, char *); +static void dolog(struct sockaddr *); +static void end_login(void); +static FILE *getdatasock(char *); +static int guniquefd(char *, char **); +static void lostconn(int); +static void sigquit(int); +static int receive_data(FILE *, FILE *); +static int send_data(FILE *, FILE *, size_t, off_t, int); +static struct passwd * + sgetpwnam(char *); +static char *sgetsave(char *); +static void reapchild(int); +static void appendf(char **, char *, ...) __printflike(2, 3); +static void logcmd(char *, char *, char *, off_t); +static void logxfer(char *, off_t, time_t); +static char *doublequote(char *); +static int *socksetup(int, char *, const char *); + +int +main(int argc, char *argv[], char **envp) +{ + socklen_t addrlen; + int ch, on = 1, tos; + char *cp, line[LINE_MAX]; + FILE *fd; + char *bindname = NULL; + const char *bindport = "ftp"; + int family = AF_UNSPEC; + struct sigaction sa; + + tzset(); /* in case no timezone database in ~ftp */ + sigemptyset(&sa.sa_mask); + sa.sa_flags = SA_RESTART; + +#ifdef OLD_SETPROCTITLE + /* + * Save start and extent of argv for setproctitle. + */ + Argv = argv; + while (*envp) + envp++; + LastArgv = envp[-1] + strlen(envp[-1]); +#endif /* OLD_SETPROCTITLE */ + + /* + * Prevent diagnostic messages from appearing on stderr. + * We run as a daemon or from inetd; in both cases, there's + * more reason in logging to syslog. + */ + (void) freopen(_PATH_DEVNULL, "w", stderr); + opterr = 0; + + /* + * LOG_NDELAY sets up the logging connection immediately, + * necessary for anonymous ftp's that chroot and can't do it later. + */ + openlog("ftpd", LOG_PID | LOG_NDELAY, LOG_FTP); + + while ((ch = getopt(argc, argv, + "468a:AdDEhlmMoOp:P:rRSt:T:u:UvW")) != -1) { + switch (ch) { + case '4': + family = (family == AF_INET6) ? AF_UNSPEC : AF_INET; + break; + + case '6': + family = (family == AF_INET) ? AF_UNSPEC : AF_INET6; + break; + + case '8': + assumeutf8 = 1; + break; + + case 'a': + bindname = optarg; + break; + + case 'A': + anon_only = 1; + break; + + case 'd': + ftpdebug++; + break; + + case 'D': + daemon_mode++; + break; + + case 'E': + noepsv = 1; + break; + + case 'h': + hostinfo = 0; + break; + + case 'l': + logging++; /* > 1 == extra logging */ + break; + + case 'm': + noguestmod = 0; + break; + + case 'M': + noguestmkd = 1; + break; + + case 'o': + noretr = 1; + break; + + case 'O': + noguestretr = 1; + break; + + case 'p': + pid_file = optarg; + break; + + case 'P': + bindport = optarg; + break; + + case 'r': + readonly = 1; + break; + + case 'R': + paranoid = 0; + break; + + case 'S': + stats++; + break; + + case 't': + timeout = atoi(optarg); + if (maxtimeout < timeout) + maxtimeout = timeout; + break; + + case 'T': + maxtimeout = atoi(optarg); + if (timeout > maxtimeout) + timeout = maxtimeout; + break; + + case 'u': + { + long val = 0; + + val = strtol(optarg, &optarg, 8); + if (*optarg != '\0' || val < 0) + syslog(LOG_WARNING, "bad value for -u"); + else + defumask = val; + break; + } + case 'U': + restricted_data_ports = 0; + break; + + case 'v': + ftpdebug++; + break; + + case 'W': + dowtmp = 0; + break; + + default: + syslog(LOG_WARNING, "unknown flag -%c ignored", optopt); + break; + } + } + + if (daemon_mode) { + int *ctl_sock, fd, maxfd = -1, nfds, i; + fd_set defreadfds, readfds; + pid_t pid; + struct pidfh *pfh; + + if ((pfh = pidfile_open(pid_file, 0600, &pid)) == NULL) { + if (errno == EEXIST) { + syslog(LOG_ERR, "%s already running, pid %d", + getprogname(), (int)pid); + exit(1); + } + syslog(LOG_WARNING, "pidfile_open: %m"); + } + + /* + * Detach from parent. + */ + if (daemon(1, 1) < 0) { + syslog(LOG_ERR, "failed to become a daemon"); + exit(1); + } + + if (pfh != NULL && pidfile_write(pfh) == -1) + syslog(LOG_WARNING, "pidfile_write: %m"); + + sa.sa_handler = reapchild; + (void)sigaction(SIGCHLD, &sa, NULL); + +#ifdef VIRTUAL_HOSTING + inithosts(family); +#endif + + /* + * Open a socket, bind it to the FTP port, and start + * listening. + */ + ctl_sock = socksetup(family, bindname, bindport); + if (ctl_sock == NULL) + exit(1); + + FD_ZERO(&defreadfds); + for (i = 1; i <= *ctl_sock; i++) { + FD_SET(ctl_sock[i], &defreadfds); + if (listen(ctl_sock[i], 32) < 0) { + syslog(LOG_ERR, "control listen: %m"); + exit(1); + } + if (maxfd < ctl_sock[i]) + maxfd = ctl_sock[i]; + } + + /* + * Loop forever accepting connection requests and forking off + * children to handle them. + */ + while (1) { + FD_COPY(&defreadfds, &readfds); + nfds = select(maxfd + 1, &readfds, NULL, NULL, 0); + if (nfds <= 0) { + if (nfds < 0 && errno != EINTR) + syslog(LOG_WARNING, "select: %m"); + continue; + } + + pid = -1; + for (i = 1; i <= *ctl_sock; i++) + if (FD_ISSET(ctl_sock[i], &readfds)) { + addrlen = sizeof(his_addr); + fd = accept(ctl_sock[i], + (struct sockaddr *)&his_addr, + &addrlen); + if (fd == -1) { + syslog(LOG_WARNING, + "accept: %m"); + continue; + } + switch (pid = fork()) { + case 0: + /* child */ + (void) dup2(fd, 0); + (void) dup2(fd, 1); + (void) close(fd); + for (i = 1; i <= *ctl_sock; i++) + close(ctl_sock[i]); + if (pfh != NULL) + pidfile_close(pfh); + goto gotchild; + case -1: + syslog(LOG_WARNING, "fork: %m"); + /* FALLTHROUGH */ + default: + close(fd); + } + } + } + } else { + addrlen = sizeof(his_addr); + if (getpeername(0, (struct sockaddr *)&his_addr, &addrlen) < 0) { + syslog(LOG_ERR, "getpeername (%s): %m",argv[0]); + exit(1); + } + +#ifdef VIRTUAL_HOSTING + if (his_addr.su_family == AF_INET6 && + IN6_IS_ADDR_V4MAPPED(&his_addr.su_sin6.sin6_addr)) + family = AF_INET; + else + family = his_addr.su_family; + inithosts(family); +#endif + } + +gotchild: + sa.sa_handler = SIG_DFL; + (void)sigaction(SIGCHLD, &sa, NULL); + + sa.sa_handler = sigurg; + sa.sa_flags = 0; /* don't restart syscalls for SIGURG */ + (void)sigaction(SIGURG, &sa, NULL); + + sigfillset(&sa.sa_mask); /* block all signals in handler */ + sa.sa_flags = SA_RESTART; + sa.sa_handler = sigquit; + (void)sigaction(SIGHUP, &sa, NULL); + (void)sigaction(SIGINT, &sa, NULL); + (void)sigaction(SIGQUIT, &sa, NULL); + (void)sigaction(SIGTERM, &sa, NULL); + + sa.sa_handler = lostconn; + (void)sigaction(SIGPIPE, &sa, NULL); + + addrlen = sizeof(ctrl_addr); + if (getsockname(0, (struct sockaddr *)&ctrl_addr, &addrlen) < 0) { + syslog(LOG_ERR, "getsockname (%s): %m",argv[0]); + exit(1); + } + dataport = ntohs(ctrl_addr.su_port) - 1; /* as per RFC 959 */ +#ifdef VIRTUAL_HOSTING + /* select our identity from virtual host table */ + selecthost(&ctrl_addr); +#endif +#ifdef IP_TOS + if (ctrl_addr.su_family == AF_INET) + { + tos = IPTOS_LOWDELAY; + if (setsockopt(0, IPPROTO_IP, IP_TOS, &tos, sizeof(int)) < 0) + syslog(LOG_WARNING, "control setsockopt (IP_TOS): %m"); + } +#endif + /* + * Disable Nagle on the control channel so that we don't have to wait + * for peer's ACK before issuing our next reply. + */ + if (setsockopt(0, IPPROTO_TCP, TCP_NODELAY, &on, sizeof(on)) < 0) + syslog(LOG_WARNING, "control setsockopt (TCP_NODELAY): %m"); + + data_source.su_port = htons(ntohs(ctrl_addr.su_port) - 1); + + /* set this here so klogin can use it... */ + (void)snprintf(ttyline, sizeof(ttyline), "ftp%d", (int)getpid()); + + /* Try to handle urgent data inline */ +#ifdef SO_OOBINLINE + if (setsockopt(0, SOL_SOCKET, SO_OOBINLINE, &on, sizeof(on)) < 0) + syslog(LOG_WARNING, "control setsockopt (SO_OOBINLINE): %m"); +#endif + +#ifdef F_SETOWN + if (fcntl(fileno(stdin), F_SETOWN, getpid()) == -1) + syslog(LOG_ERR, "fcntl F_SETOWN: %m"); +#endif + dolog((struct sockaddr *)&his_addr); + /* + * Set up default state + */ + data = -1; + type = TYPE_A; + form = FORM_N; + stru = STRU_F; + mode = MODE_S; + tmpline[0] = '\0'; + + /* If logins are disabled, print out the message. */ + if ((fd = fopen(_PATH_NOLOGIN,"r")) != NULL) { + while (fgets(line, sizeof(line), fd) != NULL) { + if ((cp = strchr(line, '\n')) != NULL) + *cp = '\0'; + lreply(530, "%s", line); + } + (void) fflush(stdout); + (void) fclose(fd); + reply(530, "System not available."); + exit(0); + } +#ifdef VIRTUAL_HOSTING + fd = fopen(thishost->welcome, "r"); +#else + fd = fopen(_PATH_FTPWELCOME, "r"); +#endif + if (fd != NULL) { + while (fgets(line, sizeof(line), fd) != NULL) { + if ((cp = strchr(line, '\n')) != NULL) + *cp = '\0'; + lreply(220, "%s", line); + } + (void) fflush(stdout); + (void) fclose(fd); + /* reply(220,) must follow */ + } +#ifndef VIRTUAL_HOSTING + if ((hostname = malloc(MAXHOSTNAMELEN)) == NULL) + fatalerror("Ran out of memory."); + if (gethostname(hostname, MAXHOSTNAMELEN - 1) < 0) + hostname[0] = '\0'; + hostname[MAXHOSTNAMELEN - 1] = '\0'; +#endif + if (hostinfo) + reply(220, "%s FTP server (%s) ready.", hostname, version); + else + reply(220, "FTP server ready."); + for (;;) + (void) yyparse(); + /* NOTREACHED */ +} + +static void +lostconn(int signo) +{ + + if (ftpdebug) + syslog(LOG_DEBUG, "lost connection"); + dologout(1); +} + +static void +sigquit(int signo) +{ + + syslog(LOG_ERR, "got signal %d", signo); + dologout(1); +} + +#ifdef VIRTUAL_HOSTING +/* + * read in virtual host tables (if they exist) + */ + +static void +inithosts(int family) +{ + int insert; + size_t len; + FILE *fp; + char *cp, *mp, *line; + char *hostname; + char *vhost, *anonuser, *statfile, *welcome, *loginmsg; + struct ftphost *hrp, *lhrp; + struct addrinfo hints, *res, *ai; + + /* + * Fill in the default host information + */ + if ((hostname = malloc(MAXHOSTNAMELEN)) == NULL) + fatalerror("Ran out of memory."); + if (gethostname(hostname, MAXHOSTNAMELEN - 1) < 0) + hostname[0] = '\0'; + hostname[MAXHOSTNAMELEN - 1] = '\0'; + if ((hrp = malloc(sizeof(struct ftphost))) == NULL) + fatalerror("Ran out of memory."); + hrp->hostname = hostname; + hrp->hostinfo = NULL; + + memset(&hints, 0, sizeof(hints)); + hints.ai_flags = AI_PASSIVE; + hints.ai_family = family; + hints.ai_socktype = SOCK_STREAM; + if (getaddrinfo(hrp->hostname, NULL, &hints, &res) == 0) + hrp->hostinfo = res; + hrp->statfile = _PATH_FTPDSTATFILE; + hrp->welcome = _PATH_FTPWELCOME; + hrp->loginmsg = _PATH_FTPLOGINMESG; + hrp->anonuser = "ftp"; + hrp->next = NULL; + thishost = firsthost = lhrp = hrp; + if ((fp = fopen(_PATH_FTPHOSTS, "r")) != NULL) { + int addrsize, gothost; + void *addr; + struct hostent *hp; + + while ((line = fgetln(fp, &len)) != NULL) { + int i, hp_error; + + /* skip comments */ + if (line[0] == '#') + continue; + if (line[len - 1] == '\n') { + line[len - 1] = '\0'; + mp = NULL; + } else { + if ((mp = malloc(len + 1)) == NULL) + fatalerror("Ran out of memory."); + memcpy(mp, line, len); + mp[len] = '\0'; + line = mp; + } + cp = strtok(line, " \t"); + /* skip empty lines */ + if (cp == NULL) + goto nextline; + vhost = cp; + + /* set defaults */ + anonuser = "ftp"; + statfile = _PATH_FTPDSTATFILE; + welcome = _PATH_FTPWELCOME; + loginmsg = _PATH_FTPLOGINMESG; + + /* + * Preparse the line so we can use its info + * for all the addresses associated with + * the virtual host name. + * Field 0, the virtual host name, is special: + * it's already parsed off and will be strdup'ed + * later, after we know its canonical form. + */ + for (i = 1; i < 5 && (cp = strtok(NULL, " \t")); i++) + if (*cp != '-' && (cp = strdup(cp))) + switch (i) { + case 1: /* anon user permissions */ + anonuser = cp; + break; + case 2: /* statistics file */ + statfile = cp; + break; + case 3: /* welcome message */ + welcome = cp; + break; + case 4: /* login message */ + loginmsg = cp; + break; + default: /* programming error */ + abort(); + /* NOTREACHED */ + } + + hints.ai_flags = AI_PASSIVE; + hints.ai_family = family; + hints.ai_socktype = SOCK_STREAM; + if (getaddrinfo(vhost, NULL, &hints, &res) != 0) + goto nextline; + for (ai = res; ai != NULL && ai->ai_addr != NULL; + ai = ai->ai_next) { + + gothost = 0; + for (hrp = firsthost; hrp != NULL; hrp = hrp->next) { + struct addrinfo *hi; + + for (hi = hrp->hostinfo; hi != NULL; + hi = hi->ai_next) + if (hi->ai_addrlen == ai->ai_addrlen && + memcmp(hi->ai_addr, + ai->ai_addr, + ai->ai_addr->sa_len) == 0) { + gothost++; + break; + } + if (gothost) + break; + } + if (hrp == NULL) { + if ((hrp = malloc(sizeof(struct ftphost))) == NULL) + goto nextline; + hrp->hostname = NULL; + insert = 1; + } else { + if (hrp->hostinfo && hrp->hostinfo != res) + freeaddrinfo(hrp->hostinfo); + insert = 0; /* host already in the chain */ + } + hrp->hostinfo = res; + + /* + * determine hostname to use. + * force defined name if there is a valid alias + * otherwise fallback to primary hostname + */ + /* XXX: getaddrinfo() can't do alias check */ + switch(hrp->hostinfo->ai_family) { + case AF_INET: + addr = &((struct sockaddr_in *)hrp->hostinfo->ai_addr)->sin_addr; + addrsize = sizeof(struct in_addr); + break; + case AF_INET6: + addr = &((struct sockaddr_in6 *)hrp->hostinfo->ai_addr)->sin6_addr; + addrsize = sizeof(struct in6_addr); + break; + default: + /* should not reach here */ + freeaddrinfo(hrp->hostinfo); + if (insert) + free(hrp); /*not in chain, can free*/ + else + hrp->hostinfo = NULL; /*mark as blank*/ + goto nextline; + /* NOTREACHED */ + } + if ((hp = getipnodebyaddr(addr, addrsize, + hrp->hostinfo->ai_family, + &hp_error)) != NULL) { + if (strcmp(vhost, hp->h_name) != 0) { + if (hp->h_aliases == NULL) + vhost = hp->h_name; + else { + i = 0; + while (hp->h_aliases[i] && + strcmp(vhost, hp->h_aliases[i]) != 0) + ++i; + if (hp->h_aliases[i] == NULL) + vhost = hp->h_name; + } + } + } + if (hrp->hostname && + strcmp(hrp->hostname, vhost) != 0) { + free(hrp->hostname); + hrp->hostname = NULL; + } + if (hrp->hostname == NULL && + (hrp->hostname = strdup(vhost)) == NULL) { + freeaddrinfo(hrp->hostinfo); + hrp->hostinfo = NULL; /* mark as blank */ + if (hp) + freehostent(hp); + goto nextline; + } + hrp->anonuser = anonuser; + hrp->statfile = statfile; + hrp->welcome = welcome; + hrp->loginmsg = loginmsg; + if (insert) { + hrp->next = NULL; + lhrp->next = hrp; + lhrp = hrp; + } + if (hp) + freehostent(hp); + } +nextline: + if (mp) + free(mp); + } + (void) fclose(fp); + } +} + +static void +selecthost(union sockunion *su) +{ + struct ftphost *hrp; + u_int16_t port; +#ifdef INET6 + struct in6_addr *mapped_in6 = NULL; +#endif + struct addrinfo *hi; + +#ifdef INET6 + /* + * XXX IPv4 mapped IPv6 addr consideraton, + * specified in rfc2373. + */ + if (su->su_family == AF_INET6 && + IN6_IS_ADDR_V4MAPPED(&su->su_sin6.sin6_addr)) + mapped_in6 = &su->su_sin6.sin6_addr; +#endif + + hrp = thishost = firsthost; /* default */ + port = su->su_port; + su->su_port = 0; + while (hrp != NULL) { + for (hi = hrp->hostinfo; hi != NULL; hi = hi->ai_next) { + if (memcmp(su, hi->ai_addr, hi->ai_addrlen) == 0) { + thishost = hrp; + goto found; + } +#ifdef INET6 + /* XXX IPv4 mapped IPv6 addr consideraton */ + if (hi->ai_addr->sa_family == AF_INET && mapped_in6 != NULL && + (memcmp(&mapped_in6->s6_addr[12], + &((struct sockaddr_in *)hi->ai_addr)->sin_addr, + sizeof(struct in_addr)) == 0)) { + thishost = hrp; + goto found; + } +#endif + } + hrp = hrp->next; + } +found: + su->su_port = port; + /* setup static variables as appropriate */ + hostname = thishost->hostname; + ftpuser = thishost->anonuser; +} +#endif + +/* + * Helper function for sgetpwnam(). + */ +static char * +sgetsave(char *s) +{ + char *new = malloc(strlen(s) + 1); + + if (new == NULL) { + reply(421, "Ran out of memory."); + dologout(1); + /* NOTREACHED */ + } + (void) strcpy(new, s); + return (new); +} + +/* + * Save the result of a getpwnam. Used for USER command, since + * the data returned must not be clobbered by any other command + * (e.g., globbing). + * NB: The data returned by sgetpwnam() will remain valid until + * the next call to this function. Its difference from getpwnam() + * is that sgetpwnam() is known to be called from ftpd code only. + */ +static struct passwd * +sgetpwnam(char *name) +{ + static struct passwd save; + struct passwd *p; + + if ((p = getpwnam(name)) == NULL) + return (p); + if (save.pw_name) { + free(save.pw_name); + free(save.pw_passwd); + free(save.pw_gecos); + free(save.pw_dir); + free(save.pw_shell); + } + save = *p; + save.pw_name = sgetsave(p->pw_name); + save.pw_passwd = sgetsave(p->pw_passwd); + save.pw_gecos = sgetsave(p->pw_gecos); + save.pw_dir = sgetsave(p->pw_dir); + save.pw_shell = sgetsave(p->pw_shell); + return (&save); +} + +static int login_attempts; /* number of failed login attempts */ +static int askpasswd; /* had user command, ask for passwd */ +static char curname[MAXLOGNAME]; /* current USER name */ + +/* + * USER command. + * Sets global passwd pointer pw if named account exists and is acceptable; + * sets askpasswd if a PASS command is expected. If logged in previously, + * need to reset state. If name is "ftp" or "anonymous", the name is not in + * _PATH_FTPUSERS, and ftp account exists, set guest and pw, then just return. + * If account doesn't exist, ask for passwd anyway. Otherwise, check user + * requesting login privileges. Disallow anyone who does not have a standard + * shell as returned by getusershell(). Disallow anyone mentioned in the file + * _PATH_FTPUSERS to allow people such as root and uucp to be avoided. + */ +void +user(char *name) +{ + char *cp, *shell; + + if (logged_in) { + if (guest) { + reply(530, "Can't change user from guest login."); + return; + } else if (dochroot) { + reply(530, "Can't change user from chroot user."); + return; + } + end_login(); + } + + guest = 0; +#ifdef VIRTUAL_HOSTING + pw = sgetpwnam(thishost->anonuser); +#else + pw = sgetpwnam("ftp"); +#endif + if (strcmp(name, "ftp") == 0 || strcmp(name, "anonymous") == 0) { + if (checkuser(_PATH_FTPUSERS, "ftp", 0, NULL) || + checkuser(_PATH_FTPUSERS, "anonymous", 0, NULL)) + reply(530, "User %s access denied.", name); + else if (pw != NULL) { + guest = 1; + askpasswd = 1; + reply(331, + "Guest login ok, send your email address as password."); + } else + reply(530, "User %s unknown.", name); + if (!askpasswd && logging) + syslog(LOG_NOTICE, + "ANONYMOUS FTP LOGIN REFUSED FROM %s", remotehost); + return; + } + if (anon_only != 0) { + reply(530, "Sorry, only anonymous ftp allowed."); + return; + } + + if ((pw = sgetpwnam(name))) { + if ((shell = pw->pw_shell) == NULL || *shell == 0) + shell = _PATH_BSHELL; + setusershell(); + while ((cp = getusershell()) != NULL) + if (strcmp(cp, shell) == 0) + break; + endusershell(); + + if (cp == NULL || checkuser(_PATH_FTPUSERS, name, 1, NULL)) { + reply(530, "User %s access denied.", name); + if (logging) + syslog(LOG_NOTICE, + "FTP LOGIN REFUSED FROM %s, %s", + remotehost, name); + pw = NULL; + return; + } + } + if (logging) + strncpy(curname, name, sizeof(curname)-1); + + pwok = 0; +#ifdef USE_PAM + /* XXX Kluge! The conversation mechanism needs to be fixed. */ +#endif +#ifdef HAVE_LIBOPIE + if (opiechallenge(&opiedata, name, opieprompt) == 0) { + pwok = (pw != NULL) && + opieaccessfile(remotehost) && + opiealways(pw->pw_dir); + reply(331, "Response to %s %s for %s.", + opieprompt, pwok ? "requested" : "required", name); + } else +#endif + { + pwok = 1; + reply(331, "Password required for %s.", name); + } + askpasswd = 1; + /* + * Delay before reading passwd after first failed + * attempt to slow down passwd-guessing programs. + */ + if (login_attempts) + sleep(login_attempts); +} + +/* + * Check if a user is in the file "fname", + * return a pointer to a malloc'd string with the rest + * of the matching line in "residue" if not NULL. + */ +static int +checkuser(char *fname, char *name, int pwset, char **residue) +{ + FILE *fd; + int found = 0; + size_t len; + char *line, *mp, *p; + + if ((fd = fopen(fname, "r")) != NULL) { + while (!found && (line = fgetln(fd, &len)) != NULL) { + /* skip comments */ + if (line[0] == '#') + continue; + if (line[len - 1] == '\n') { + line[len - 1] = '\0'; + mp = NULL; + } else { + if ((mp = malloc(len + 1)) == NULL) + fatalerror("Ran out of memory."); + memcpy(mp, line, len); + mp[len] = '\0'; + line = mp; + } + /* avoid possible leading and trailing whitespace */ + p = strtok(line, " \t"); + /* skip empty lines */ + if (p == NULL) + goto nextline; + /* + * if first chr is '@', check group membership + */ + if (p[0] == '@') { + int i = 0; + struct group *grp; + + if (p[1] == '\0') /* single @ matches anyone */ + found = 1; + else { + if ((grp = getgrnam(p+1)) == NULL) + goto nextline; + /* + * Check user's default group + */ + if (pwset && grp->gr_gid == pw->pw_gid) + found = 1; + /* + * Check supplementary groups + */ + while (!found && grp->gr_mem[i]) + found = strcmp(name, + grp->gr_mem[i++]) + == 0; + } + } + /* + * Otherwise, just check for username match + */ + else + found = strcmp(p, name) == 0; + /* + * Save the rest of line to "residue" if matched + */ + if (found && residue) { + if ((p = strtok(NULL, "")) != NULL) + p += strspn(p, " \t"); + if (p && *p) { + if ((*residue = strdup(p)) == NULL) + fatalerror("Ran out of memory."); + } else + *residue = NULL; + } +nextline: + if (mp) + free(mp); + } + (void) fclose(fd); + } + return (found); +} + +/* + * Terminate login as previous user, if any, resetting state; + * used when USER command is given or login fails. + */ +static void +end_login(void) +{ +#ifdef USE_PAM + int e; +#endif + + (void) seteuid(0); + if (logged_in && dowtmp) + ftpd_logwtmp(ttyline, "", NULL); + pw = NULL; +#ifdef LOGIN_CAP + setusercontext(NULL, getpwuid(0), 0, + LOGIN_SETPRIORITY|LOGIN_SETRESOURCES|LOGIN_SETUMASK| + LOGIN_SETMAC); +#endif +#ifdef USE_PAM + if (pamh) { + if ((e = pam_setcred(pamh, PAM_DELETE_CRED)) != PAM_SUCCESS) + syslog(LOG_ERR, "pam_setcred: %s", pam_strerror(pamh, e)); + if ((e = pam_close_session(pamh,0)) != PAM_SUCCESS) + syslog(LOG_ERR, "pam_close_session: %s", pam_strerror(pamh, e)); + if ((e = pam_end(pamh, e)) != PAM_SUCCESS) + syslog(LOG_ERR, "pam_end: %s", pam_strerror(pamh, e)); + pamh = NULL; + } +#endif + logged_in = 0; + guest = 0; + dochroot = 0; +} + +#ifdef USE_PAM + +/* + * the following code is stolen from imap-uw PAM authentication module and + * login.c + */ +#define COPY_STRING(s) (s ? strdup(s) : NULL) + +struct cred_t { + const char *uname; /* user name */ + const char *pass; /* password */ +}; +typedef struct cred_t cred_t; + +static int +auth_conv(int num_msg, const struct pam_message **msg, + struct pam_response **resp, void *appdata) +{ + int i; + cred_t *cred = (cred_t *) appdata; + struct pam_response *reply; + + reply = calloc(num_msg, sizeof *reply); + if (reply == NULL) + return PAM_BUF_ERR; + + for (i = 0; i < num_msg; i++) { + switch (msg[i]->msg_style) { + case PAM_PROMPT_ECHO_ON: /* assume want user name */ + reply[i].resp_retcode = PAM_SUCCESS; + reply[i].resp = COPY_STRING(cred->uname); + /* PAM frees resp. */ + break; + case PAM_PROMPT_ECHO_OFF: /* assume want password */ + reply[i].resp_retcode = PAM_SUCCESS; + reply[i].resp = COPY_STRING(cred->pass); + /* PAM frees resp. */ + break; + case PAM_TEXT_INFO: + case PAM_ERROR_MSG: + reply[i].resp_retcode = PAM_SUCCESS; + reply[i].resp = NULL; + break; + default: /* unknown message style */ + free(reply); + return PAM_CONV_ERR; + } + } + + *resp = reply; + return PAM_SUCCESS; +} + +/* + * Attempt to authenticate the user using PAM. Returns 0 if the user is + * authenticated, or 1 if not authenticated. If some sort of PAM system + * error occurs (e.g., the "/etc/pam.conf" file is missing) then this + * function returns -1. This can be used as an indication that we should + * fall back to a different authentication mechanism. + */ +static int +auth_pam(struct passwd **ppw, const char *pass) +{ + const char *tmpl_user; + const void *item; + int rval; + int e; + cred_t auth_cred = { (*ppw)->pw_name, pass }; + struct pam_conv conv = { &auth_conv, &auth_cred }; + + e = pam_start("ftpd", (*ppw)->pw_name, &conv, &pamh); + if (e != PAM_SUCCESS) { + /* + * In OpenPAM, it's OK to pass NULL to pam_strerror() + * if context creation has failed in the first place. + */ + syslog(LOG_ERR, "pam_start: %s", pam_strerror(NULL, e)); + return -1; + } + + e = pam_set_item(pamh, PAM_RHOST, remotehost); + if (e != PAM_SUCCESS) { + syslog(LOG_ERR, "pam_set_item(PAM_RHOST): %s", + pam_strerror(pamh, e)); + if ((e = pam_end(pamh, e)) != PAM_SUCCESS) { + syslog(LOG_ERR, "pam_end: %s", pam_strerror(pamh, e)); + } + pamh = NULL; + return -1; + } + + e = pam_authenticate(pamh, 0); + switch (e) { + case PAM_SUCCESS: + /* + * With PAM we support the concept of a "template" + * user. The user enters a login name which is + * authenticated by PAM, usually via a remote service + * such as RADIUS or TACACS+. If authentication + * succeeds, a different but related "template" name + * is used for setting the credentials, shell, and + * home directory. The name the user enters need only + * exist on the remote authentication server, but the + * template name must be present in the local password + * database. + * + * This is supported by two various mechanisms in the + * individual modules. However, from the application's + * point of view, the template user is always passed + * back as a changed value of the PAM_USER item. + */ + if ((e = pam_get_item(pamh, PAM_USER, &item)) == + PAM_SUCCESS) { + tmpl_user = (const char *) item; + if (strcmp((*ppw)->pw_name, tmpl_user) != 0) + *ppw = getpwnam(tmpl_user); + } else + syslog(LOG_ERR, "Couldn't get PAM_USER: %s", + pam_strerror(pamh, e)); + rval = 0; + break; + + case PAM_AUTH_ERR: + case PAM_USER_UNKNOWN: + case PAM_MAXTRIES: + rval = 1; + break; + + default: + syslog(LOG_ERR, "pam_authenticate: %s", pam_strerror(pamh, e)); + rval = -1; + break; + } + + if (rval == 0) { + e = pam_acct_mgmt(pamh, 0); + if (e != PAM_SUCCESS) { + syslog(LOG_ERR, "pam_acct_mgmt: %s", + pam_strerror(pamh, e)); + rval = 1; + } + } + + if (rval != 0) { + if ((e = pam_end(pamh, e)) != PAM_SUCCESS) { + syslog(LOG_ERR, "pam_end: %s", pam_strerror(pamh, e)); + } + pamh = NULL; + } + return rval; +} + +#endif /* USE_PAM */ + +void +pass(char *passwd) +{ + int rval; + FILE *fd; +#ifdef LOGIN_CAP + login_cap_t *lc = NULL; +#endif +#ifdef USE_PAM + int e; +#endif + char *residue = NULL; + char *xpasswd; + + if (logged_in || askpasswd == 0) { + reply(503, "Login with USER first."); + return; + } + askpasswd = 0; + if (!guest) { /* "ftp" is only account allowed no password */ + if (pw == NULL) { + rval = 1; /* failure below */ + goto skip; + } +#ifdef USE_PAM + rval = auth_pam(&pw, passwd); + if (rval >= 0) { + opieunlock(); + goto skip; + } +#endif +#ifdef HAVE_LIBOPIE + if (opieverify(&opiedata, passwd) == 0) + xpasswd = pw->pw_passwd; + else +#endif + if (pwok) { + xpasswd = crypt(passwd, pw->pw_passwd); + if (passwd[0] == '\0' && pw->pw_passwd[0] != '\0') + xpasswd = ":"; + } else { + rval = 1; + goto skip; + } + rval = strcmp(pw->pw_passwd, xpasswd); +#ifndef __BEOS__ + if (pw->pw_expire && time(NULL) >= pw->pw_expire) + rval = 1; /* failure */ +#endif +skip: + /* + * If rval == 1, the user failed the authentication check + * above. If rval == 0, either PAM or local authentication + * succeeded. + */ + if (rval) { + reply(530, "Login incorrect."); + if (logging) { + syslog(LOG_NOTICE, + "FTP LOGIN FAILED FROM %s", + remotehost); + syslog(LOG_AUTHPRIV | LOG_NOTICE, + "FTP LOGIN FAILED FROM %s, %s", + remotehost, curname); + } + pw = NULL; + if (login_attempts++ >= 5) { + syslog(LOG_NOTICE, + "repeated login failures from %s", + remotehost); + exit(0); + } + return; + } + } + login_attempts = 0; /* this time successful */ + if (setegid(pw->pw_gid) < 0) { + reply(550, "Can't set gid."); + return; + } + /* May be overridden by login.conf */ + (void) umask(defumask); +#ifdef LOGIN_CAP + if ((lc = login_getpwclass(pw)) != NULL) { + char remote_ip[NI_MAXHOST]; + + if (getnameinfo((struct sockaddr *)&his_addr, his_addr.su_len, + remote_ip, sizeof(remote_ip) - 1, NULL, 0, + NI_NUMERICHOST)) + *remote_ip = 0; + remote_ip[sizeof(remote_ip) - 1] = 0; + if (!auth_hostok(lc, remotehost, remote_ip)) { + syslog(LOG_INFO|LOG_AUTH, + "FTP LOGIN FAILED (HOST) as %s: permission denied.", + pw->pw_name); + reply(530, "Permission denied."); + pw = NULL; + return; + } + if (!auth_timeok(lc, time(NULL))) { + reply(530, "Login not available right now."); + pw = NULL; + return; + } + } + setusercontext(lc, pw, 0, + LOGIN_SETLOGIN|LOGIN_SETGROUP|LOGIN_SETPRIORITY| + LOGIN_SETRESOURCES|LOGIN_SETUMASK|LOGIN_SETMAC); +#elif !defined(__BEOS__) + setlogin(pw->pw_name); + (void) initgroups(pw->pw_name, pw->pw_gid); +#endif + +#ifdef USE_PAM + if (pamh) { + if ((e = pam_open_session(pamh, 0)) != PAM_SUCCESS) { + syslog(LOG_ERR, "pam_open_session: %s", pam_strerror(pamh, e)); + } else if ((e = pam_setcred(pamh, PAM_ESTABLISH_CRED)) != PAM_SUCCESS) { + syslog(LOG_ERR, "pam_setcred: %s", pam_strerror(pamh, e)); + } + } +#endif + + /* open wtmp before chroot */ + if (dowtmp) + ftpd_logwtmp(ttyline, pw->pw_name, + (struct sockaddr *)&his_addr); + logged_in = 1; + + if (guest && stats && statfd < 0) +#ifdef VIRTUAL_HOSTING + statfd = open(thishost->statfile, O_WRONLY|O_APPEND); +#else + statfd = open(_PATH_FTPDSTATFILE, O_WRONLY|O_APPEND); +#endif + if (statfd < 0) + stats = 0; + + dochroot = + checkuser(_PATH_FTPCHROOT, pw->pw_name, 1, &residue) +#ifdef LOGIN_CAP /* Allow login.conf configuration as well */ + || login_getcapbool(lc, "ftp-chroot", 0) +#endif + ; + chrootdir = NULL; +#ifndef __BEOS__ + /* + * For a chrooted local user, + * a) see whether ftpchroot(5) specifies a chroot directory, + * b) extract the directory pathname from the line, + * c) expand it to the absolute pathname if necessary. + */ + if (dochroot && residue && + (chrootdir = strtok(residue, " \t")) != NULL) { + if (chrootdir[0] != '/') + asprintf(&chrootdir, "%s/%s", pw->pw_dir, chrootdir); + else + chrootdir = strdup(chrootdir); /* make it permanent */ + if (chrootdir == NULL) + fatalerror("Ran out of memory."); + } +#endif + if (guest || dochroot) { +#ifndef __BEOS__ + /* + * If no chroot directory set yet, use the login directory. + * Copy it so it can be modified while pw->pw_dir stays intact. + */ + if (chrootdir == NULL && + (chrootdir = strdup(pw->pw_dir)) == NULL) + fatalerror("Ran out of memory."); + /* + * Check for the "/chroot/./home" syntax, + * separate the chroot and home directory pathnames. + */ + if ((homedir = strstr(chrootdir, "/./")) != NULL) { + *(homedir++) = '\0'; /* wipe '/' */ + homedir++; /* skip '.' */ + } else { + /* + * We MUST do a chdir() after the chroot. Otherwise + * the old current directory will be accessible as "." + * outside the new root! + */ + homedir = "/"; + } + /* + * Finally, do chroot() + */ + if (chroot(chrootdir) < 0) { + reply(550, "Can't change root."); + goto bad; + } +#else + homedir = "/"; +#endif + } else /* real user w/o chroot */ + homedir = pw->pw_dir; + /* + * Set euid *before* doing chdir() so + * a) the user won't be carried to a directory that he couldn't reach + * on his own due to no permission to upper path components, + * b) NFS mounted homedirs w/restrictive permissions will be accessible + * (uid 0 has no root power over NFS if not mapped explicitly.) + */ + if (seteuid(pw->pw_uid) < 0) { + reply(550, "Can't set uid."); + goto bad; + } + if (chdir(homedir) < 0) { + if (guest || dochroot) { + reply(550, "Can't change to base directory."); + goto bad; + } else { + if (chdir("/") < 0) { + reply(550, "Root is inaccessible."); + goto bad; + } + lreply(230, "No directory! Logging in with home=/."); + } + } + + /* + * Display a login message, if it exists. + * N.B. reply(230,) must follow the message. + */ +#ifdef VIRTUAL_HOSTING + fd = fopen(thishost->loginmsg, "r"); +#else + fd = fopen(_PATH_FTPLOGINMESG, "r"); +#endif + if (fd != NULL) { + char *cp, line[LINE_MAX]; + + while (fgets(line, sizeof(line), fd) != NULL) { + if ((cp = strchr(line, '\n')) != NULL) + *cp = '\0'; + lreply(230, "%s", line); + } + (void) fflush(stdout); + (void) fclose(fd); + } + if (guest) { + if (ident != NULL) + free(ident); + ident = strdup(passwd); + if (ident == NULL) + fatalerror("Ran out of memory."); + + reply(230, "Guest login ok, access restrictions apply."); +#ifdef SETPROCTITLE +#ifdef VIRTUAL_HOSTING + if (thishost != firsthost) + snprintf(proctitle, sizeof(proctitle), + "%s: anonymous(%s)/%s", remotehost, hostname, + passwd); + else +#endif + snprintf(proctitle, sizeof(proctitle), + "%s: anonymous/%s", remotehost, passwd); + setproctitle("%s", proctitle); +#endif /* SETPROCTITLE */ + if (logging) + syslog(LOG_INFO, "ANONYMOUS FTP LOGIN FROM %s, %s", + remotehost, passwd); + } else { + if (dochroot) + reply(230, "User %s logged in, " + "access restrictions apply.", pw->pw_name); + else + reply(230, "User %s logged in.", pw->pw_name); + +#ifdef SETPROCTITLE + snprintf(proctitle, sizeof(proctitle), + "%s: user/%s", remotehost, pw->pw_name); + setproctitle("%s", proctitle); +#endif /* SETPROCTITLE */ + if (logging) + syslog(LOG_INFO, "FTP LOGIN FROM %s as %s", + remotehost, pw->pw_name); + } + if (logging && (guest || dochroot)) + syslog(LOG_INFO, "session root changed to %s", chrootdir); +#ifdef LOGIN_CAP + login_close(lc); +#endif + if (residue) + free(residue); + return; +bad: + /* Forget all about it... */ +#ifdef LOGIN_CAP + login_close(lc); +#endif + if (residue) + free(residue); + end_login(); +} + +void +retrieve(char *cmd, char *name) +{ + FILE *fin, *dout; + struct stat st; + int (*closefunc)(FILE *); + time_t start; + + if (cmd == 0) { + fin = fopen(name, "r"), closefunc = fclose; + st.st_size = 0; + } else { + char line[BUFSIZ]; + + (void) snprintf(line, sizeof(line), cmd, name), name = line; + fin = ftpd_popen(line, "r"), closefunc = ftpd_pclose; + st.st_size = -1; + st.st_blksize = BUFSIZ; + } + if (fin == NULL) { + if (errno != 0) { + perror_reply(550, name); + if (cmd == 0) { + LOGCMD("get", name); + } + } + return; + } + byte_count = -1; + if (cmd == 0) { + if (fstat(fileno(fin), &st) < 0) { + perror_reply(550, name); + goto done; + } + if (!S_ISREG(st.st_mode)) { + /* + * Never sending a raw directory is a workaround + * for buggy clients that will attempt to RETR + * a directory before listing it, e.g., Mozilla. + * Preventing a guest from getting irregular files + * is a simple security measure. + */ + if (S_ISDIR(st.st_mode) || guest) { + reply(550, "%s: not a plain file.", name); + goto done; + } + st.st_size = -1; + /* st.st_blksize is set for all descriptor types */ + } + } + if (restart_point) { + if (type == TYPE_A) { + off_t i, n; + int c; + + n = restart_point; + i = 0; + while (i++ < n) { + if ((c=getc(fin)) == EOF) { + perror_reply(550, name); + goto done; + } + if (c == '\n') + i++; + } + } else if (lseek(fileno(fin), restart_point, L_SET) < 0) { + perror_reply(550, name); + goto done; + } + } + dout = dataconn(name, st.st_size, "w"); + if (dout == NULL) + goto done; + time(&start); + send_data(fin, dout, st.st_blksize, st.st_size, + restart_point == 0 && cmd == 0 && S_ISREG(st.st_mode)); + if (cmd == 0 && guest && stats && byte_count > 0) + logxfer(name, byte_count, start); + (void) fclose(dout); + data = -1; + pdata = -1; +done: + if (cmd == 0) + LOGBYTES("get", name, byte_count); + (*closefunc)(fin); +} + +void +store(char *name, char *mode, int unique) +{ + int fd; + FILE *fout, *din; + int (*closefunc)(FILE *); + + if (*mode == 'a') { /* APPE */ + if (unique) { + /* Programming error */ + syslog(LOG_ERR, "Internal: unique flag to APPE"); + unique = 0; + } + if (guest && noguestmod) { + reply(550, "Appending to existing file denied."); + goto err; + } + restart_point = 0; /* not affected by preceding REST */ + } + if (unique) /* STOU overrides REST */ + restart_point = 0; + if (guest && noguestmod) { + if (restart_point) { /* guest STOR w/REST */ + reply(550, "Modifying existing file denied."); + goto err; + } else /* treat guest STOR as STOU */ + unique = 1; + } + + if (restart_point) + mode = "r+"; /* so ASCII manual seek can work */ + if (unique) { + if ((fd = guniquefd(name, &name)) < 0) + goto err; + fout = fdopen(fd, mode); + } else + fout = fopen(name, mode); + closefunc = fclose; + if (fout == NULL) { + perror_reply(553, name); + goto err; + } + byte_count = -1; + if (restart_point) { + if (type == TYPE_A) { + off_t i, n; + int c; + + n = restart_point; + i = 0; + while (i++ < n) { + if ((c=getc(fout)) == EOF) { + perror_reply(550, name); + goto done; + } + if (c == '\n') + i++; + } + /* + * We must do this seek to "current" position + * because we are changing from reading to + * writing. + */ + if (fseeko(fout, 0, SEEK_CUR) < 0) { + perror_reply(550, name); + goto done; + } + } else if (lseek(fileno(fout), restart_point, L_SET) < 0) { + perror_reply(550, name); + goto done; + } + } + din = dataconn(name, -1, "r"); + if (din == NULL) + goto done; + if (receive_data(din, fout) == 0) { + if (unique) + reply(226, "Transfer complete (unique file name:%s).", + name); + else + reply(226, "Transfer complete."); + } + (void) fclose(din); + data = -1; + pdata = -1; +done: + LOGBYTES(*mode == 'a' ? "append" : "put", name, byte_count); + (*closefunc)(fout); + return; +err: + LOGCMD(*mode == 'a' ? "append" : "put" , name); + return; +} + +static FILE * +getdatasock(char *mode) +{ + int on = 1, s, t, tries; + + if (data >= 0) + return (fdopen(data, mode)); + + s = socket(data_dest.su_family, SOCK_STREAM, 0); + if (s < 0) + goto bad; + if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) < 0) + syslog(LOG_WARNING, "data setsockopt (SO_REUSEADDR): %m"); + /* anchor socket to avoid multi-homing problems */ + data_source = ctrl_addr; + data_source.su_port = htons(dataport); + (void) seteuid(0); + for (tries = 1; ; tries++) { + /* + * We should loop here since it's possible that + * another ftpd instance has passed this point and is + * trying to open a data connection in active mode now. + * Until the other connection is opened, we'll be getting + * EADDRINUSE because no SOCK_STREAM sockets in the system + * can share both local and remote addresses, localIP:20 + * and *:* in this case. + */ + if (bind(s, (struct sockaddr *)&data_source, + data_source.su_len) >= 0) + break; + if (errno != EADDRINUSE || tries > 10) + goto bad; + sleep(tries); + } + (void) seteuid(pw->pw_uid); +#ifdef IP_TOS + if (data_source.su_family == AF_INET) + { + on = IPTOS_THROUGHPUT; + if (setsockopt(s, IPPROTO_IP, IP_TOS, &on, sizeof(int)) < 0) + syslog(LOG_WARNING, "data setsockopt (IP_TOS): %m"); + } +#endif +#ifdef TCP_NOPUSH + /* + * Turn off push flag to keep sender TCP from sending short packets + * at the boundaries of each write(). + */ + on = 1; + if (setsockopt(s, IPPROTO_TCP, TCP_NOPUSH, &on, sizeof on) < 0) + syslog(LOG_WARNING, "data setsockopt (TCP_NOPUSH): %m"); +#endif + return (fdopen(s, mode)); +bad: + /* Return the real value of errno (close may change it) */ + t = errno; + (void) seteuid(pw->pw_uid); + (void) close(s); + errno = t; + return (NULL); +} + +static FILE * +dataconn(char *name, off_t size, char *mode) +{ + char sizebuf[32]; + FILE *file; + int retry = 0, tos, conerrno; + + file_size = size; + byte_count = 0; + if (size != -1) + (void) snprintf(sizebuf, sizeof(sizebuf), + " (%lld bytes)", (intmax_t)size); + else + *sizebuf = '\0'; + if (pdata >= 0) { + union sockunion from; + socklen_t fromlen = ctrl_addr.su_len; + int flags, s; + struct timeval timeout; + fd_set set; + + FD_ZERO(&set); + FD_SET(pdata, &set); + + timeout.tv_usec = 0; + timeout.tv_sec = 120; + + /* + * Granted a socket is in the blocking I/O mode, + * accept() will block after a successful select() + * if the selected connection dies in between. + * Therefore set the non-blocking I/O flag here. + */ + if ((flags = fcntl(pdata, F_GETFL, 0)) == -1 || + fcntl(pdata, F_SETFL, flags | O_NONBLOCK) == -1) + goto pdata_err; + if (select(pdata+1, &set, NULL, NULL, &timeout) <= 0 || + (s = accept(pdata, (struct sockaddr *) &from, &fromlen)) < 0) + goto pdata_err; + (void) close(pdata); + pdata = s; + /* + * Unset the inherited non-blocking I/O flag + * on the child socket so stdio can work on it. + */ + if ((flags = fcntl(pdata, F_GETFL, 0)) == -1 || + fcntl(pdata, F_SETFL, flags & ~O_NONBLOCK) == -1) + goto pdata_err; +#ifdef IP_TOS + if (from.su_family == AF_INET) + { + tos = IPTOS_THROUGHPUT; + if (setsockopt(s, IPPROTO_IP, IP_TOS, &tos, sizeof(int)) < 0) + syslog(LOG_WARNING, "pdata setsockopt (IP_TOS): %m"); + } +#endif + reply(150, "Opening %s mode data connection for '%s'%s.", + type == TYPE_A ? "ASCII" : "BINARY", name, sizebuf); + return (fdopen(pdata, mode)); +pdata_err: + reply(425, "Can't open data connection."); + (void) close(pdata); + pdata = -1; + return (NULL); + } + if (data >= 0) { + reply(125, "Using existing data connection for '%s'%s.", + name, sizebuf); + usedefault = 1; + return (fdopen(data, mode)); + } + if (usedefault) + data_dest = his_addr; + usedefault = 1; + do { + file = getdatasock(mode); + if (file == NULL) { + char hostbuf[NI_MAXHOST], portbuf[NI_MAXSERV]; + + if (getnameinfo((struct sockaddr *)&data_source, + data_source.su_len, + hostbuf, sizeof(hostbuf) - 1, + portbuf, sizeof(portbuf) - 1, + NI_NUMERICHOST|NI_NUMERICSERV)) + *hostbuf = *portbuf = 0; + hostbuf[sizeof(hostbuf) - 1] = 0; + portbuf[sizeof(portbuf) - 1] = 0; + reply(425, "Can't create data socket (%s,%s): %s.", + hostbuf, portbuf, strerror(errno)); + return (NULL); + } + data = fileno(file); + conerrno = 0; + if (connect(data, (struct sockaddr *)&data_dest, + data_dest.su_len) == 0) + break; + conerrno = errno; + (void) fclose(file); + data = -1; + if (conerrno == EADDRINUSE) { + sleep(swaitint); + retry += swaitint; + } else { + break; + } + } while (retry <= swaitmax); + if (conerrno != 0) { + reply(425, "Can't build data connection: %s.", + strerror(conerrno)); + return (NULL); + } + reply(150, "Opening %s mode data connection for '%s'%s.", + type == TYPE_A ? "ASCII" : "BINARY", name, sizebuf); + return (file); +} + +/* + * A helper macro to avoid code duplication + * in send_data() and receive_data(). + * + * XXX We have to block SIGURG during putc() because BSD stdio + * is unable to restart interrupted write operations and hence + * the entire buffer contents will be lost as soon as a write() + * call indicates EINTR to stdio. + */ +#define FTPD_PUTC(ch, file, label) \ + do { \ + int ret; \ + \ + do { \ + START_UNSAFE; \ + ret = putc((ch), (file)); \ + END_UNSAFE; \ + CHECKOOB(return (-1)) \ + else if (ferror(file)) \ + goto label; \ + clearerr(file); \ + } while (ret == EOF); \ + } while (0) + +/* + * Tranfer the contents of "instr" to "outstr" peer using the appropriate + * encapsulation of the data subject to Mode, Structure, and Type. + * + * NB: Form isn't handled. + */ +static int +send_data(FILE *instr, FILE *outstr, size_t blksize, off_t filesize, int isreg) +{ + int c, cp, filefd, netfd; + char *buf; + + STARTXFER; + + switch (type) { + + case TYPE_A: + cp = EOF; + for (;;) { + c = getc(instr); + CHECKOOB(return (-1)) + else if (c == EOF && ferror(instr)) + goto file_err; + if (c == EOF) { + if (ferror(instr)) { /* resume after OOB */ + clearerr(instr); + continue; + } + if (feof(instr)) /* EOF */ + break; + syslog(LOG_ERR, "Internal: impossible condition" + " on file after getc()"); + goto file_err; + } + if (c == '\n' && cp != '\r') { + FTPD_PUTC('\r', outstr, data_err); + byte_count++; + } + FTPD_PUTC(c, outstr, data_err); + byte_count++; + cp = c; + } +#ifdef notyet /* BSD stdio isn't ready for that */ + while (fflush(outstr) == EOF) { + CHECKOOB(return (-1)) + else + goto data_err; + clearerr(outstr); + } + ENDXFER; +#else + ENDXFER; + if (fflush(outstr) == EOF) + goto data_err; +#endif + reply(226, "Transfer complete."); + return (0); + + case TYPE_I: + case TYPE_L: + /* + * isreg is only set if we are not doing restart and we + * are sending a regular file + */ + netfd = fileno(outstr); + filefd = fileno(instr); + +#ifndef __BEOS__ + if (isreg) { + char *msg = "Transfer complete."; + off_t cnt, offset; + int err; + + cnt = offset = 0; + + while (filesize > 0) { + err = sendfile(filefd, netfd, offset, 0, + NULL, &cnt, 0); + /* + * Calculate byte_count before OOB processing. + * It can be used in myoob() later. + */ + byte_count += cnt; + offset += cnt; + filesize -= cnt; + CHECKOOB(return (-1)) + else if (err == -1) { + if (errno != EINTR && + cnt == 0 && offset == 0) + goto oldway; + goto data_err; + } + if (err == -1) /* resume after OOB */ + continue; + /* + * We hit the EOF prematurely. + * Perhaps the file was externally truncated. + */ + if (cnt == 0) { + msg = "Transfer finished due to " + "premature end of file."; + break; + } + } + ENDXFER; + reply(226, msg); + return (0); + } + +oldway: +#endif /* !__BEOS__ */ + if ((buf = malloc(blksize)) == NULL) { + ENDXFER; + reply(451, "Ran out of memory."); + return (-1); + } + + for (;;) { + int cnt, len; + char *bp; + + cnt = read(filefd, buf, blksize); + CHECKOOB(free(buf); return (-1)) + else if (cnt < 0) { + free(buf); + goto file_err; + } + if (cnt < 0) /* resume after OOB */ + continue; + if (cnt == 0) /* EOF */ + break; + for (len = cnt, bp = buf; len > 0;) { + cnt = write(netfd, bp, len); + CHECKOOB(free(buf); return (-1)) + else if (cnt < 0) { + free(buf); + goto data_err; + } + if (cnt <= 0) + continue; + len -= cnt; + bp += cnt; + byte_count += cnt; + } + } + ENDXFER; + free(buf); + reply(226, "Transfer complete."); + return (0); + default: + ENDXFER; + reply(550, "Unimplemented TYPE %d in send_data.", type); + return (-1); + } + +data_err: + ENDXFER; + perror_reply(426, "Data connection"); + return (-1); + +file_err: + ENDXFER; + perror_reply(551, "Error on input file"); + return (-1); +} + +/* + * Transfer data from peer to "outstr" using the appropriate encapulation of + * the data subject to Mode, Structure, and Type. + * + * N.B.: Form isn't handled. + */ +static int +receive_data(FILE *instr, FILE *outstr) +{ + int c, cp; + int bare_lfs = 0; + + STARTXFER; + + switch (type) { + + case TYPE_I: + case TYPE_L: + for (;;) { + int cnt, len; + char *bp; + char buf[BUFSIZ]; + + cnt = read(fileno(instr), buf, sizeof(buf)); + CHECKOOB(return (-1)) + else if (cnt < 0) + goto data_err; + if (cnt < 0) /* resume after OOB */ + continue; + if (cnt == 0) /* EOF */ + break; + for (len = cnt, bp = buf; len > 0;) { + cnt = write(fileno(outstr), bp, len); + CHECKOOB(return (-1)) + else if (cnt < 0) + goto file_err; + if (cnt <= 0) + continue; + len -= cnt; + bp += cnt; + byte_count += cnt; + } + } + ENDXFER; + return (0); + + case TYPE_E: + ENDXFER; + reply(553, "TYPE E not implemented."); + return (-1); + + case TYPE_A: + cp = EOF; + for (;;) { + c = getc(instr); + CHECKOOB(return (-1)) + else if (c == EOF && ferror(instr)) + goto data_err; + if (c == EOF && ferror(instr)) { /* resume after OOB */ + clearerr(instr); + continue; + } + + if (cp == '\r') { + if (c != '\n') + FTPD_PUTC('\r', outstr, file_err); + } else + if (c == '\n') + bare_lfs++; + if (c == '\r') { + byte_count++; + cp = c; + continue; + } + + /* Check for EOF here in order not to lose last \r. */ + if (c == EOF) { + if (feof(instr)) /* EOF */ + break; + syslog(LOG_ERR, "Internal: impossible condition" + " on data stream after getc()"); + goto data_err; + } + + byte_count++; + FTPD_PUTC(c, outstr, file_err); + cp = c; + } +#ifdef notyet /* BSD stdio isn't ready for that */ + while (fflush(outstr) == EOF) { + CHECKOOB(return (-1)) + else + goto file_err; + clearerr(outstr); + } + ENDXFER; +#else + ENDXFER; + if (fflush(outstr) == EOF) + goto file_err; +#endif + if (bare_lfs) { + lreply(226, + "WARNING! %d bare linefeeds received in ASCII mode.", + bare_lfs); + (void)printf(" File may not have transferred correctly.\r\n"); + } + return (0); + default: + ENDXFER; + reply(550, "Unimplemented TYPE %d in receive_data.", type); + return (-1); + } + +data_err: + ENDXFER; + perror_reply(426, "Data connection"); + return (-1); + +file_err: + ENDXFER; + perror_reply(452, "Error writing to file"); + return (-1); +} + +void +statfilecmd(char *filename) +{ + FILE *fin; + int atstart; + int c, code; + char line[LINE_MAX]; + struct stat st; + + code = lstat(filename, &st) == 0 && S_ISDIR(st.st_mode) ? 212 : 213; + (void)snprintf(line, sizeof(line), _PATH_LS " -lgA %s", filename); + fin = ftpd_popen(line, "r"); + lreply(code, "Status of %s:", filename); + atstart = 1; + while ((c = getc(fin)) != EOF) { + if (c == '\n') { + if (ferror(stdout)){ + perror_reply(421, "Control connection"); + (void) ftpd_pclose(fin); + dologout(1); + /* NOTREACHED */ + } + if (ferror(fin)) { + perror_reply(551, filename); + (void) ftpd_pclose(fin); + return; + } + (void) putc('\r', stdout); + } + /* + * RFC 959 says neutral text should be prepended before + * a leading 3-digit number followed by whitespace, but + * many ftp clients can be confused by any leading digits, + * as a matter of fact. + */ + if (atstart && isdigit(c)) + (void) putc(' ', stdout); + (void) putc(c, stdout); + atstart = (c == '\n'); + } + (void) ftpd_pclose(fin); + reply(code, "End of status."); +} + +void +statcmd(void) +{ + union sockunion *su; + u_char *a, *p; + char hname[NI_MAXHOST]; + int ispassive; + + if (hostinfo) { + lreply(211, "%s FTP server status:", hostname); + printf(" %s\r\n", version); + } else + lreply(211, "FTP server status:"); + printf(" Connected to %s", remotehost); + if (!getnameinfo((struct sockaddr *)&his_addr, his_addr.su_len, + hname, sizeof(hname) - 1, NULL, 0, NI_NUMERICHOST)) { + hname[sizeof(hname) - 1] = 0; + if (strcmp(hname, remotehost) != 0) + printf(" (%s)", hname); + } + printf("\r\n"); + if (logged_in) { + if (guest) + printf(" Logged in anonymously\r\n"); + else + printf(" Logged in as %s\r\n", pw->pw_name); + } else if (askpasswd) + printf(" Waiting for password\r\n"); + else + printf(" Waiting for user name\r\n"); + printf(" TYPE: %s", typenames[type]); + if (type == TYPE_A || type == TYPE_E) + printf(", FORM: %s", formnames[form]); + if (type == TYPE_L) +#if CHAR_BIT == 8 + printf(" %d", CHAR_BIT); +#else + printf(" %d", bytesize); /* need definition! */ +#endif + printf("; STRUcture: %s; transfer MODE: %s\r\n", + strunames[stru], modenames[mode]); + if (data != -1) + printf(" Data connection open\r\n"); + else if (pdata != -1) { + ispassive = 1; + su = &pasv_addr; + goto printaddr; + } else if (usedefault == 0) { + ispassive = 0; + su = &data_dest; +printaddr: +#define UC(b) (((int) b) & 0xff) + if (epsvall) { + printf(" EPSV only mode (EPSV ALL)\r\n"); + goto epsvonly; + } + + /* PORT/PASV */ + if (su->su_family == AF_INET) { + a = (u_char *) &su->su_sin.sin_addr; + p = (u_char *) &su->su_sin.sin_port; + printf(" %s (%d,%d,%d,%d,%d,%d)\r\n", + ispassive ? "PASV" : "PORT", + UC(a[0]), UC(a[1]), UC(a[2]), UC(a[3]), + UC(p[0]), UC(p[1])); + } + + /* LPRT/LPSV */ + { + int alen, af, i; + + switch (su->su_family) { + case AF_INET: + a = (u_char *) &su->su_sin.sin_addr; + p = (u_char *) &su->su_sin.sin_port; + alen = sizeof(su->su_sin.sin_addr); + af = 4; + break; + case AF_INET6: + a = (u_char *) &su->su_sin6.sin6_addr; + p = (u_char *) &su->su_sin6.sin6_port; + alen = sizeof(su->su_sin6.sin6_addr); + af = 6; + break; + default: + af = 0; + break; + } + if (af) { + printf(" %s (%d,%d,", ispassive ? "LPSV" : "LPRT", + af, alen); + for (i = 0; i < alen; i++) + printf("%d,", UC(a[i])); + printf("%d,%d,%d)\r\n", 2, UC(p[0]), UC(p[1])); + } + } + +epsvonly:; + /* EPRT/EPSV */ + { + int af; + + switch (su->su_family) { + case AF_INET: + af = 1; + break; + case AF_INET6: + af = 2; + break; + default: + af = 0; + break; + } + if (af) { + union sockunion tmp; + + tmp = *su; + if (tmp.su_family == AF_INET6) + tmp.su_sin6.sin6_scope_id = 0; + if (!getnameinfo((struct sockaddr *)&tmp, tmp.su_len, + hname, sizeof(hname) - 1, NULL, 0, + NI_NUMERICHOST)) { + hname[sizeof(hname) - 1] = 0; + printf(" %s |%d|%s|%d|\r\n", + ispassive ? "EPSV" : "EPRT", + af, hname, htons(tmp.su_port)); + } + } + } +#undef UC + } else + printf(" No data connection\r\n"); + reply(211, "End of status."); +} + +void +fatalerror(char *s) +{ + + reply(451, "Error in server: %s", s); + reply(221, "Closing connection due to server error."); + dologout(0); + /* NOTREACHED */ +} + +void +reply(int n, const char *fmt, ...) +{ + va_list ap; + + (void)printf("%d ", n); + va_start(ap, fmt); + (void)vprintf(fmt, ap); + va_end(ap); + (void)printf("\r\n"); + (void)fflush(stdout); + if (ftpdebug) { + syslog(LOG_DEBUG, "<--- %d ", n); + va_start(ap, fmt); + vsyslog(LOG_DEBUG, fmt, ap); + va_end(ap); + } +} + +void +lreply(int n, const char *fmt, ...) +{ + va_list ap; + + (void)printf("%d- ", n); + va_start(ap, fmt); + (void)vprintf(fmt, ap); + va_end(ap); + (void)printf("\r\n"); + (void)fflush(stdout); + if (ftpdebug) { + syslog(LOG_DEBUG, "<--- %d- ", n); + va_start(ap, fmt); + vsyslog(LOG_DEBUG, fmt, ap); + va_end(ap); + } +} + +static void +ack(char *s) +{ + + reply(250, "%s command successful.", s); +} + +void +nack(char *s) +{ + + reply(502, "%s command not implemented.", s); +} + +/* ARGSUSED */ +void +yyerror(char *s) +{ + char *cp; + + if ((cp = strchr(cbuf,'\n'))) + *cp = '\0'; + reply(500, "%s: command not understood.", cbuf); +} + +void +delete(char *name) +{ + struct stat st; + + LOGCMD("delete", name); + if (lstat(name, &st) < 0) { + perror_reply(550, name); + return; + } + if (S_ISDIR(st.st_mode)) { + if (rmdir(name) < 0) { + perror_reply(550, name); + return; + } + goto done; + } + if (guest && noguestmod) { + reply(550, "Operation not permitted."); + return; + } + if (unlink(name) < 0) { + perror_reply(550, name); + return; + } +done: + ack("DELE"); +} + +void +cwd(char *path) +{ + + if (chdir(path) < 0) + perror_reply(550, path); + else + ack("CWD"); +} + +void +makedir(char *name) +{ + char *s; + + LOGCMD("mkdir", name); + if (guest && noguestmkd) + reply(550, "Operation not permitted."); + else if (mkdir(name, 0777) < 0) + perror_reply(550, name); + else { + if ((s = doublequote(name)) == NULL) + fatalerror("Ran out of memory."); + reply(257, "\"%s\" directory created.", s); + free(s); + } +} + +void +removedir(char *name) +{ + + LOGCMD("rmdir", name); + if (rmdir(name) < 0) + perror_reply(550, name); + else + ack("RMD"); +} + +void +pwd(void) +{ + char *s, path[MAXPATHLEN + 1]; + + if (getcwd(path, sizeof(path)) == NULL) + perror_reply(550, "Get current directory"); + else { + if ((s = doublequote(path)) == NULL) + fatalerror("Ran out of memory."); + reply(257, "\"%s\" is current directory.", s); + free(s); + } +} + +char * +renamefrom(char *name) +{ + struct stat st; + + if (guest && noguestmod) { + reply(550, "Operation not permitted."); + return (NULL); + } + if (lstat(name, &st) < 0) { + perror_reply(550, name); + return (NULL); + } + reply(350, "File exists, ready for destination name."); + return (name); +} + +void +renamecmd(char *from, char *to) +{ + struct stat st; + + LOGCMD2("rename", from, to); + + if (guest && (stat(to, &st) == 0)) { + reply(550, "%s: permission denied.", to); + return; + } + + if (rename(from, to) < 0) + perror_reply(550, "rename"); + else + ack("RNTO"); +} + +static void +dolog(struct sockaddr *who) +{ + char who_name[NI_MAXHOST]; + + realhostname_sa(remotehost, sizeof(remotehost) - 1, who, who->sa_len); + remotehost[sizeof(remotehost) - 1] = 0; + if (getnameinfo(who, who->sa_len, + who_name, sizeof(who_name) - 1, NULL, 0, NI_NUMERICHOST)) + *who_name = 0; + who_name[sizeof(who_name) - 1] = 0; + +#ifdef SETPROCTITLE +#ifdef VIRTUAL_HOSTING + if (thishost != firsthost) + snprintf(proctitle, sizeof(proctitle), "%s: connected (to %s)", + remotehost, hostname); + else +#endif + snprintf(proctitle, sizeof(proctitle), "%s: connected", + remotehost); + setproctitle("%s", proctitle); +#endif /* SETPROCTITLE */ + + if (logging) { +#ifdef VIRTUAL_HOSTING + if (thishost != firsthost) + syslog(LOG_INFO, "connection from %s (%s) to %s", + remotehost, who_name, hostname); + else +#endif + syslog(LOG_INFO, "connection from %s (%s)", + remotehost, who_name); + } +} + +/* + * Record logout in wtmp file + * and exit with supplied status. + */ +void +dologout(int status) +{ + + if (logged_in && dowtmp) { + (void) seteuid(0); + ftpd_logwtmp(ttyline, "", NULL); + } + /* beware of flushing buffers after a SIGPIPE */ + _exit(status); +} + +static void +sigurg(int signo) +{ + + recvurg = 1; +} + +static void +maskurg(int flag) +{ + int oerrno; + sigset_t sset; + + if (!transflag) { + syslog(LOG_ERR, "Internal: maskurg() while no transfer"); + return; + } + oerrno = errno; + sigemptyset(&sset); + sigaddset(&sset, SIGURG); + sigprocmask(flag ? SIG_BLOCK : SIG_UNBLOCK, &sset, NULL); + errno = oerrno; +} + +static void +flagxfer(int flag) +{ + + if (flag) { + if (transflag) + syslog(LOG_ERR, "Internal: flagxfer(1): " + "transfer already under way"); + transflag = 1; + maskurg(0); + recvurg = 0; + } else { + if (!transflag) + syslog(LOG_ERR, "Internal: flagxfer(0): " + "no active transfer"); + maskurg(1); + transflag = 0; + } +} + +/* + * Returns 0 if OK to resume or -1 if abort requested. + */ +static int +myoob(void) +{ + char *cp; + + if (!transflag) { + syslog(LOG_ERR, "Internal: myoob() while no transfer"); + return (0); + } + cp = tmpline; + if (getline(cp, 7, stdin) == NULL) { + reply(221, "You could at least say goodbye."); + dologout(0); + } + upper(cp); + if (strcmp(cp, "ABOR\r\n") == 0) { + tmpline[0] = '\0'; + reply(426, "Transfer aborted. Data connection closed."); + reply(226, "Abort successful."); + return (-1); + } + if (strcmp(cp, "STAT\r\n") == 0) { + tmpline[0] = '\0'; + if (file_size != -1) + reply(213, "Status: %lld of %lld bytes transferred.", + (intmax_t)byte_count, (intmax_t)file_size); + else + reply(213, "Status: %lld bytes transferred.", + (intmax_t)byte_count); + } + return (0); +} + +/* + * Note: a response of 425 is not mentioned as a possible response to + * the PASV command in RFC959. However, it has been blessed as + * a legitimate response by Jon Postel in a telephone conversation + * with Rick Adams on 25 Jan 89. + */ +void +passive(void) +{ + socklen_t len; + int on; + char *p, *a; + + if (pdata >= 0) /* close old port if one set */ + close(pdata); + + pdata = socket(ctrl_addr.su_family, SOCK_STREAM, 0); + if (pdata < 0) { + perror_reply(425, "Can't open passive connection"); + return; + } + on = 1; + if (setsockopt(pdata, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) < 0) + syslog(LOG_WARNING, "pdata setsockopt (SO_REUSEADDR): %m"); + + (void) seteuid(0); + +#ifdef IP_PORTRANGE + if (ctrl_addr.su_family == AF_INET) { + on = restricted_data_ports ? IP_PORTRANGE_HIGH + : IP_PORTRANGE_DEFAULT; + + if (setsockopt(pdata, IPPROTO_IP, IP_PORTRANGE, + &on, sizeof(on)) < 0) + goto pasv_error; + } +#endif +#ifdef IPV6_PORTRANGE + if (ctrl_addr.su_family == AF_INET6) { + on = restricted_data_ports ? IPV6_PORTRANGE_HIGH + : IPV6_PORTRANGE_DEFAULT; + + if (setsockopt(pdata, IPPROTO_IPV6, IPV6_PORTRANGE, + &on, sizeof(on)) < 0) + goto pasv_error; + } +#endif + + pasv_addr = ctrl_addr; + pasv_addr.su_port = 0; + if (bind(pdata, (struct sockaddr *)&pasv_addr, pasv_addr.su_len) < 0) + goto pasv_error; + + (void) seteuid(pw->pw_uid); + + len = sizeof(pasv_addr); + if (getsockname(pdata, (struct sockaddr *) &pasv_addr, &len) < 0) + goto pasv_error; + if (listen(pdata, 1) < 0) + goto pasv_error; + if (pasv_addr.su_family == AF_INET) + a = (char *) &pasv_addr.su_sin.sin_addr; +#ifdef HAVE_AF_INET6 + else if (pasv_addr.su_family == AF_INET6 && + IN6_IS_ADDR_V4MAPPED(&pasv_addr.su_sin6.sin6_addr)) + a = (char *) &pasv_addr.su_sin6.sin6_addr.s6_addr[12]; +#endif + else + goto pasv_error; + + p = (char *) &pasv_addr.su_port; + +#define UC(b) (((int) b) & 0xff) + + reply(227, "Entering Passive Mode (%d,%d,%d,%d,%d,%d)", UC(a[0]), + UC(a[1]), UC(a[2]), UC(a[3]), UC(p[0]), UC(p[1])); + return; + +pasv_error: + (void) seteuid(pw->pw_uid); + (void) close(pdata); + pdata = -1; + perror_reply(425, "Can't open passive connection"); + return; +} + +/* + * Long Passive defined in RFC 1639. + * 228 Entering Long Passive Mode + * (af, hal, h1, h2, h3,..., pal, p1, p2...) + */ + +void +long_passive(char *cmd, int pf) +{ + socklen_t len; + int on; + char *p, *a; + + if (pdata >= 0) /* close old port if one set */ + close(pdata); + + if (pf != PF_UNSPEC) { + if (ctrl_addr.su_family != pf) { + switch (ctrl_addr.su_family) { + case AF_INET: + pf = 1; + break; + case AF_INET6: + pf = 2; + break; + default: + pf = 0; + break; + } + /* + * XXX + * only EPRT/EPSV ready clients will understand this + */ + if (strcmp(cmd, "EPSV") == 0 && pf) { + reply(522, "Network protocol mismatch, " + "use (%d)", pf); + } else + reply(501, "Network protocol mismatch."); /*XXX*/ + + return; + } + } + + pdata = socket(ctrl_addr.su_family, SOCK_STREAM, 0); + if (pdata < 0) { + perror_reply(425, "Can't open passive connection"); + return; + } + on = 1; + if (setsockopt(pdata, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) < 0) + syslog(LOG_WARNING, "pdata setsockopt (SO_REUSEADDR): %m"); + + (void) seteuid(0); + + pasv_addr = ctrl_addr; + pasv_addr.su_port = 0; + len = pasv_addr.su_len; + +#ifdef IP_PORTRANGE + if (ctrl_addr.su_family == AF_INET) { + on = restricted_data_ports ? IP_PORTRANGE_HIGH + : IP_PORTRANGE_DEFAULT; + + if (setsockopt(pdata, IPPROTO_IP, IP_PORTRANGE, + &on, sizeof(on)) < 0) + goto pasv_error; + } +#endif +#ifdef IPV6_PORTRANGE + if (ctrl_addr.su_family == AF_INET6) { + on = restricted_data_ports ? IPV6_PORTRANGE_HIGH + : IPV6_PORTRANGE_DEFAULT; + + if (setsockopt(pdata, IPPROTO_IPV6, IPV6_PORTRANGE, + &on, sizeof(on)) < 0) + goto pasv_error; + } +#endif + + if (bind(pdata, (struct sockaddr *)&pasv_addr, len) < 0) + goto pasv_error; + + (void) seteuid(pw->pw_uid); + + if (getsockname(pdata, (struct sockaddr *) &pasv_addr, &len) < 0) + goto pasv_error; + if (listen(pdata, 1) < 0) + goto pasv_error; + +#define UC(b) (((int) b) & 0xff) + + if (strcmp(cmd, "LPSV") == 0) { + p = (char *)&pasv_addr.su_port; + switch (pasv_addr.su_family) { + case AF_INET: + a = (char *) &pasv_addr.su_sin.sin_addr; +#ifdef HAVE_AF_INET6 + v4_reply: +#endif + reply(228, +"Entering Long Passive Mode (%d,%d,%d,%d,%d,%d,%d,%d,%d)", + 4, 4, UC(a[0]), UC(a[1]), UC(a[2]), UC(a[3]), + 2, UC(p[0]), UC(p[1])); + return; +#ifdef HAVE_AF_INET6 + case AF_INET6: + if (IN6_IS_ADDR_V4MAPPED(&pasv_addr.su_sin6.sin6_addr)) { + a = (char *) &pasv_addr.su_sin6.sin6_addr.s6_addr[12]; + goto v4_reply; + } + a = (char *) &pasv_addr.su_sin6.sin6_addr; + reply(228, +"Entering Long Passive Mode " +"(%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d)", + 6, 16, UC(a[0]), UC(a[1]), UC(a[2]), UC(a[3]), + UC(a[4]), UC(a[5]), UC(a[6]), UC(a[7]), + UC(a[8]), UC(a[9]), UC(a[10]), UC(a[11]), + UC(a[12]), UC(a[13]), UC(a[14]), UC(a[15]), + 2, UC(p[0]), UC(p[1])); + return; +#endif + } + } else if (strcmp(cmd, "EPSV") == 0) { + switch (pasv_addr.su_family) { + case AF_INET: + case AF_INET6: + reply(229, "Entering Extended Passive Mode (|||%d|)", + ntohs(pasv_addr.su_port)); + return; + } + } else { + /* more proper error code? */ + } + +pasv_error: + (void) seteuid(pw->pw_uid); + (void) close(pdata); + pdata = -1; + perror_reply(425, "Can't open passive connection"); + return; +} + +/* + * Generate unique name for file with basename "local" + * and open the file in order to avoid possible races. + * Try "local" first, then "local.1", "local.2" etc, up to "local.99". + * Return descriptor to the file, set "name" to its name. + * + * Generates failure reply on error. + */ +static int +guniquefd(char *local, char **name) +{ + static char new[MAXPATHLEN]; + struct stat st; + char *cp; + int count; + int fd; + + cp = strrchr(local, '/'); + if (cp) + *cp = '\0'; + if (stat(cp ? local : ".", &st) < 0) { + perror_reply(553, cp ? local : "."); + return (-1); + } + if (cp) { + /* + * Let not overwrite dirname with counter suffix. + * -4 is for /nn\0 + * In this extreme case dot won't be put in front of suffix. + */ + if (strlen(local) > sizeof(new) - 4) { + reply(553, "Pathname too long."); + return (-1); + } + *cp = '/'; + } + /* -4 is for the .nn we put on the end below */ + (void) snprintf(new, sizeof(new) - 4, "%s", local); + cp = new + strlen(new); + /* + * Don't generate dotfile unless requested explicitly. + * This covers the case when basename gets truncated off + * by buffer size. + */ + if (cp > new && cp[-1] != '/') + *cp++ = '.'; + for (count = 0; count < 100; count++) { + /* At count 0 try unmodified name */ + if (count) + (void)sprintf(cp, "%d", count); + if ((fd = open(count ? new : local, + O_RDWR | O_CREAT | O_EXCL, 0666)) >= 0) { + *name = count ? new : local; + return (fd); + } + if (errno != EEXIST) { + perror_reply(553, count ? new : local); + return (-1); + } + } + reply(452, "Unique file name cannot be created."); + return (-1); +} + +/* + * Format and send reply containing system error number. + */ +void +perror_reply(int code, char *string) +{ + + reply(code, "%s: %s.", string, strerror(errno)); +} + +static char *onefile[] = { + "", + 0 +}; + +void +send_file_list(char *whichf) +{ + struct stat st; + DIR *dirp = NULL; + struct dirent *dir; + FILE *dout = NULL; + char **dirlist, *dirname; + int simple = 0; + int freeglob = 0; + glob_t gl; + + if (strpbrk(whichf, "~{[*?") != NULL) { + int flags = GLOB_BRACE|GLOB_NOCHECK|GLOB_TILDE; + + memset(&gl, 0, sizeof(gl)); + gl.gl_matchc = MAXGLOBARGS; + flags |= GLOB_LIMIT; + freeglob = 1; + if (glob(whichf, flags, 0, &gl)) { + reply(550, "No matching files found."); + goto out; + } else if (gl.gl_pathc == 0) { + errno = ENOENT; + perror_reply(550, whichf); + goto out; + } + dirlist = gl.gl_pathv; + } else { + onefile[0] = whichf; + dirlist = onefile; + simple = 1; + } + + while ((dirname = *dirlist++)) { + if (stat(dirname, &st) < 0) { + /* + * If user typed "ls -l", etc, and the client + * used NLST, do what the user meant. + */ + if (dirname[0] == '-' && *dirlist == NULL && + dout == NULL) + retrieve(_PATH_LS " %s", dirname); + else + perror_reply(550, whichf); + goto out; + } + + if (S_ISREG(st.st_mode)) { + if (dout == NULL) { + dout = dataconn("file list", -1, "w"); + if (dout == NULL) + goto out; + STARTXFER; + } + START_UNSAFE; + fprintf(dout, "%s%s\n", dirname, + type == TYPE_A ? "\r" : ""); + END_UNSAFE; + if (ferror(dout)) + goto data_err; + byte_count += strlen(dirname) + + (type == TYPE_A ? 2 : 1); + CHECKOOB(goto abrt); + continue; + } else if (!S_ISDIR(st.st_mode)) + continue; + + if ((dirp = opendir(dirname)) == NULL) + continue; + + while ((dir = readdir(dirp)) != NULL) { + char nbuf[MAXPATHLEN]; + + CHECKOOB(goto abrt); + +#ifdef __BEOS__ + if (dir->d_name[0] == '.' && dir->d_name[1] == '\0') + continue; + if (dir->d_name[0] == '.' && dir->d_name[1] == '.' && + dir->d_name[2] == '\0') + continue; +#else + if (dir->d_name[0] == '.' && dir->d_namlen == 1) + continue; + if (dir->d_name[0] == '.' && dir->d_name[1] == '.' && + dir->d_namlen == 2) + continue; +#endif + + snprintf(nbuf, sizeof(nbuf), + "%s/%s", dirname, dir->d_name); + + /* + * We have to do a stat to insure it's + * not a directory or special file. + */ + if (simple || (stat(nbuf, &st) == 0 && + S_ISREG(st.st_mode))) { + if (dout == NULL) { + dout = dataconn("file list", -1, "w"); + if (dout == NULL) + goto out; + STARTXFER; + } + START_UNSAFE; + if (nbuf[0] == '.' && nbuf[1] == '/') + fprintf(dout, "%s%s\n", &nbuf[2], + type == TYPE_A ? "\r" : ""); + else + fprintf(dout, "%s%s\n", nbuf, + type == TYPE_A ? "\r" : ""); + END_UNSAFE; + if (ferror(dout)) + goto data_err; + byte_count += strlen(nbuf) + + (type == TYPE_A ? 2 : 1); + CHECKOOB(goto abrt); + } + } + (void) closedir(dirp); + dirp = NULL; + } + + if (dout == NULL) + reply(550, "No files found."); + else if (ferror(dout)) +data_err: perror_reply(550, "Data connection"); + else + reply(226, "Transfer complete."); +out: + if (dout) { + ENDXFER; +abrt: + (void) fclose(dout); + data = -1; + pdata = -1; + } + if (dirp) + (void) closedir(dirp); + if (freeglob) { + freeglob = 0; + globfree(&gl); + } +} + +void +reapchild(int signo) +{ + while (waitpid(-1, NULL, WNOHANG) > 0); +} + +#ifdef OLD_SETPROCTITLE +/* + * Clobber argv so ps will show what we're doing. (Stolen from sendmail.) + * Warning, since this is usually started from inetd.conf, it often doesn't + * have much of an environment or arglist to overwrite. + */ +void +setproctitle(const char *fmt, ...) +{ + int i; + va_list ap; + char *p, *bp, ch; + char buf[LINE_MAX]; + + va_start(ap, fmt); + (void)vsnprintf(buf, sizeof(buf), fmt, ap); + + /* make ps print our process name */ + p = Argv[0]; + *p++ = '-'; + + i = strlen(buf); + if (i > LastArgv - p - 2) { + i = LastArgv - p - 2; + buf[i] = '\0'; + } + bp = buf; + while (ch = *bp++) + if (ch != '\n' && ch != '\r') + *p++ = ch; + while (p < LastArgv) + *p++ = ' '; +} +#endif /* OLD_SETPROCTITLE */ + +static void +appendf(char **strp, char *fmt, ...) +{ + va_list ap; + char *ostr, *p; + + va_start(ap, fmt); + vasprintf(&p, fmt, ap); + va_end(ap); + if (p == NULL) + fatalerror("Ran out of memory."); + if (*strp == NULL) + *strp = p; + else { + ostr = *strp; + asprintf(strp, "%s%s", ostr, p); + if (*strp == NULL) + fatalerror("Ran out of memory."); + free(ostr); + } +} + +static void +logcmd(char *cmd, char *file1, char *file2, off_t cnt) +{ + char *msg = NULL; + char wd[MAXPATHLEN + 1]; + + if (logging <= 1) + return; + + if (getcwd(wd, sizeof(wd) - 1) == NULL) + strcpy(wd, strerror(errno)); + + appendf(&msg, "%s", cmd); + if (file1) + appendf(&msg, " %s", file1); + if (file2) + appendf(&msg, " %s", file2); + if (cnt >= 0) + appendf(&msg, " = %lld bytes", (intmax_t)cnt); + appendf(&msg, " (wd: %s", wd); + if (guest || dochroot) + appendf(&msg, "; chrooted"); + appendf(&msg, ")"); + syslog(LOG_INFO, "%s", msg); + free(msg); +} + +static void +logxfer(char *name, off_t size, time_t start) +{ + char buf[MAXPATHLEN + 1024]; + char path[MAXPATHLEN + 1]; + time_t now; + + if (statfd >= 0) { + time(&now); + if (realpath(name, path) == NULL) { + syslog(LOG_NOTICE, "realpath failed on %s: %m", path); + return; + } + snprintf(buf, sizeof(buf), "%.20s!%s!%s!%s!%lld!%ld\n", + ctime(&now)+4, ident, remotehost, + path, (intmax_t)size, + (long)(now - start + (now == start))); + write(statfd, buf, strlen(buf)); + } +} + +static char * +doublequote(char *s) +{ + int n; + char *p, *s2; + + for (p = s, n = 0; *p; p++) + if (*p == '"') + n++; + + if ((s2 = malloc(p - s + n + 1)) == NULL) + return (NULL); + + for (p = s2; *s; s++, p++) { + if ((*p = *s) == '"') + *(++p) = '"'; + } + *p = '\0'; + + return (s2); +} + +/* setup server socket for specified address family */ +/* if af is PF_UNSPEC more than one socket may be returned */ +/* the returned list is dynamically allocated, so caller needs to free it */ +static int * +socksetup(int af, char *bindname, const char *bindport) +{ + struct addrinfo hints, *res, *r; + int error, maxs, *s, *socks; + const int on = 1; + + memset(&hints, 0, sizeof(hints)); + hints.ai_flags = AI_PASSIVE; + hints.ai_family = af; + hints.ai_socktype = SOCK_STREAM; + error = getaddrinfo(bindname, bindport, &hints, &res); + if (error) { + syslog(LOG_ERR, "%s", gai_strerror(error)); + if (error == EAI_SYSTEM) + syslog(LOG_ERR, "%s", strerror(errno)); + return NULL; + } + + /* Count max number of sockets we may open */ + for (maxs = 0, r = res; r; r = r->ai_next, maxs++) + ; + socks = malloc((maxs + 1) * sizeof(int)); + if (!socks) { + freeaddrinfo(res); + syslog(LOG_ERR, "couldn't allocate memory for sockets"); + return NULL; + } + + *socks = 0; /* num of sockets counter at start of array */ + s = socks + 1; + for (r = res; r; r = r->ai_next) { + *s = socket(r->ai_family, r->ai_socktype, r->ai_protocol); + if (*s < 0) { + syslog(LOG_DEBUG, "control socket: %m"); + continue; + } + if (setsockopt(*s, SOL_SOCKET, SO_REUSEADDR, + &on, sizeof(on)) < 0) + syslog(LOG_WARNING, + "control setsockopt (SO_REUSEADDR): %m"); +#ifdef HAVE_AF_INET6 + if (r->ai_family == AF_INET6) { + if (setsockopt(*s, IPPROTO_IPV6, IPV6_V6ONLY, + &on, sizeof(on)) < 0) + syslog(LOG_WARNING, + "control setsockopt (IPV6_V6ONLY): %m"); + } +#endif + if (bind(*s, r->ai_addr, r->ai_addrlen) < 0) { + syslog(LOG_DEBUG, "control bind: %m"); + close(*s); + continue; + } + (*socks)++; + s++; + } + + if (res) + freeaddrinfo(res); + + if (*socks == 0) { + syslog(LOG_ERR, "control socket: Couldn't bind to any socket"); + free(socks); + return NULL; + } + return(socks); +} diff --git a/src/bin/network/ftpd/logwtmp.c b/src/bin/network/ftpd/logwtmp.c new file mode 100644 index 0000000000..333fe7a1d0 --- /dev/null +++ b/src/bin/network/ftpd/logwtmp.c @@ -0,0 +1,96 @@ +/* + * Copyright (c) 1988, 1993 + * 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. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the University of + * California, Berkeley and its contributors. + * 4. 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. + */ + +#ifndef lint +#if 0 +static char sccsid[] = "@(#)logwtmp.c 8.1 (Berkeley) 6/4/93"; +#endif +#endif /* not lint */ + +#include +__FBSDID("$FreeBSD: src/libexec/ftpd/logwtmp.c,v 1.13 2004/11/18 13:46:29 yar Exp $"); + +#include +#include +#include +#include +#include + +#include +#include +#ifndef __BEOS__ +#include +#include +#endif +#include +#include +#include +#include +#include +#include "extern.h" + +static int fd = -1; + +/* + * Modified version of logwtmp that holds wtmp file open + * after first call, for use with ftp (which may chroot + * after login, but before logout). + */ +void +ftpd_logwtmp(line, name, addr) + char *line, *name; + struct sockaddr *addr; +{ +#ifndef __BEOS__ + struct utmp ut; + struct stat buf; + char host[UT_HOSTSIZE]; + + if (addr == NULL) + host[0] = '\0'; + else + realhostname_sa(host, sizeof(host), addr, addr->sa_len); + + if (fd < 0 && (fd = open(_PATH_WTMP, O_WRONLY|O_APPEND, 0)) < 0) + return; + if (fstat(fd, &buf) == 0) { + (void)strncpy(ut.ut_line, line, sizeof(ut.ut_line)); + (void)strncpy(ut.ut_name, name, sizeof(ut.ut_name)); + (void)strncpy(ut.ut_host, host, sizeof(ut.ut_host)); + ut.ut_time = _time_to_time32(time(NULL)); + if (write(fd, &ut, sizeof(struct utmp)) != + sizeof(struct utmp)) + (void)ftruncate(fd, buf.st_size); + } +#endif +} diff --git a/src/bin/network/ftpd/md5.h b/src/bin/network/ftpd/md5.h new file mode 100644 index 0000000000..14dc8fd6fe --- /dev/null +++ b/src/bin/network/ftpd/md5.h @@ -0,0 +1,56 @@ +/* MD5.H - header file for MD5C.C + * $FreeBSD: src/sys/sys/md5.h,v 1.20 2006/03/15 19:47:12 andre Exp $ + */ + +/*- + Copyright (C) 1991-2, RSA Data Security, Inc. Created 1991. All +rights reserved. + +License to copy and use this software is granted provided that it +is identified as the "RSA Data Security, Inc. MD5 Message-Digest +Algorithm" in all material mentioning or referencing this software +or this function. + +License is also granted to make and use derivative works provided +that such works are identified as "derived from the RSA Data +Security, Inc. MD5 Message-Digest Algorithm" in all material +mentioning or referencing the derived work. + +RSA Data Security, Inc. makes no representations concerning either +the merchantability of this software or the suitability of this +software for any particular purpose. It is provided "as is" +without express or implied warranty of any kind. + +These notices must be retained in any copies of any part of this +documentation and/or software. + */ + +#ifndef _SYS_MD5_H_ +#define _SYS_MD5_H_ + +#include + +#define MD5_BLOCK_LENGTH 64 +#define MD5_DIGEST_LENGTH 16 +#define MD5_DIGEST_STRING_LENGTH (MD5_DIGEST_LENGTH * 2 + 1) + +/* MD5 context. */ +typedef struct MD5Context { + u_int32_t state[4]; /* state (ABCD) */ + u_int32_t count[2]; /* number of bits, modulo 2^64 (lsb first) */ + unsigned char buffer[64]; /* input buffer */ +} MD5_CTX; + +#include + +__BEGIN_DECLS +void MD5Init(MD5_CTX *); +void MD5Update(MD5_CTX *, const void *, unsigned int); +void MD5Final(unsigned char [16], MD5_CTX *); +void MD5Pad(MD5_CTX *context); +char * MD5End(MD5_CTX *, char *); +char * MD5File(const char *, char *); +char * MD5FileChunk(const char *, char *, off_t, off_t); +char * MD5Data(const void *, unsigned int, char *); +__END_DECLS +#endif /* _SYS_MD5_H_ */ diff --git a/src/bin/network/ftpd/md5c.c b/src/bin/network/ftpd/md5c.c new file mode 100644 index 0000000000..ddfe96a51f --- /dev/null +++ b/src/bin/network/ftpd/md5c.c @@ -0,0 +1,340 @@ +/* + * MD5C.C - RSA Data Security, Inc., MD5 message-digest algorithm + * + * Copyright (C) 1991-2, RSA Data Security, Inc. Created 1991. All + * rights reserved. + * + * License to copy and use this software is granted provided that it + * is identified as the "RSA Data Security, Inc. MD5 Message-Digest + * Algorithm" in all material mentioning or referencing this software + * or this function. + * + * License is also granted to make and use derivative works provided + * that such works are identified as "derived from the RSA Data + * Security, Inc. MD5 Message-Digest Algorithm" in all material + * mentioning or referencing the derived work. + * + * RSA Data Security, Inc. makes no representations concerning either + * the merchantability of this software or the suitability of this + * software for any particular purpose. It is provided "as is" + * without express or implied warranty of any kind. + * + * These notices must be retained in any copies of any part of this + * documentation and/or software. + * + * This code is the same as the code published by RSA Inc. It has been + * edited for clarity and style only. + */ + +#include +__FBSDID("$FreeBSD: src/lib/libmd/md5c.c,v 1.17 2006/01/17 15:35:56 phk Exp $"); + +#include + +#ifdef _KERNEL +#include +#else +#include +#endif + +#ifndef __BEOS__ +#include +#include +#endif +#include +#include + +static void MD5Transform(u_int32_t [4], const unsigned char [64]); + +#ifdef _KERNEL +#define memset(x,y,z) bzero(x,z); +#define memcpy(x,y,z) bcopy(y, x, z) +#endif + +#if (BYTE_ORDER == LITTLE_ENDIAN) +#define Encode memcpy +#define Decode memcpy +#else + +/* + * Encodes input (u_int32_t) into output (unsigned char). Assumes len is + * a multiple of 4. + */ + +static void +Encode (unsigned char *output, u_int32_t *input, unsigned int len) +{ + unsigned int i; + u_int32_t *op = (u_int32_t *)output; + + for (i = 0; i < len / 4; i++) + op[i] = htole32(input[i]); +} + +/* + * Decodes input (unsigned char) into output (u_int32_t). Assumes len is + * a multiple of 4. + */ + +static void +Decode (u_int32_t *output, const unsigned char *input, unsigned int len) +{ + unsigned int i; + const u_int32_t *ip = (const u_int32_t *)input; + + for (i = 0; i < len / 4; i++) + output[i] = le32toh(ip[i]); +} +#endif + +static unsigned char PADDING[64] = { + 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 +}; + +/* F, G, H and I are basic MD5 functions. */ +#define F(x, y, z) (((x) & (y)) | ((~x) & (z))) +#define G(x, y, z) (((x) & (z)) | ((y) & (~z))) +#define H(x, y, z) ((x) ^ (y) ^ (z)) +#define I(x, y, z) ((y) ^ ((x) | (~z))) + +/* ROTATE_LEFT rotates x left n bits. */ +#define ROTATE_LEFT(x, n) (((x) << (n)) | ((x) >> (32-(n)))) + +/* + * FF, GG, HH, and II transformations for rounds 1, 2, 3, and 4. + * Rotation is separate from addition to prevent recomputation. + */ +#define FF(a, b, c, d, x, s, ac) { \ + (a) += F ((b), (c), (d)) + (x) + (u_int32_t)(ac); \ + (a) = ROTATE_LEFT ((a), (s)); \ + (a) += (b); \ + } +#define GG(a, b, c, d, x, s, ac) { \ + (a) += G ((b), (c), (d)) + (x) + (u_int32_t)(ac); \ + (a) = ROTATE_LEFT ((a), (s)); \ + (a) += (b); \ + } +#define HH(a, b, c, d, x, s, ac) { \ + (a) += H ((b), (c), (d)) + (x) + (u_int32_t)(ac); \ + (a) = ROTATE_LEFT ((a), (s)); \ + (a) += (b); \ + } +#define II(a, b, c, d, x, s, ac) { \ + (a) += I ((b), (c), (d)) + (x) + (u_int32_t)(ac); \ + (a) = ROTATE_LEFT ((a), (s)); \ + (a) += (b); \ + } + +/* MD5 initialization. Begins an MD5 operation, writing a new context. */ + +void +MD5Init (context) + MD5_CTX *context; +{ + + context->count[0] = context->count[1] = 0; + + /* Load magic initialization constants. */ + context->state[0] = 0x67452301; + context->state[1] = 0xefcdab89; + context->state[2] = 0x98badcfe; + context->state[3] = 0x10325476; +} + +/* + * MD5 block update operation. Continues an MD5 message-digest + * operation, processing another message block, and updating the + * context. + */ + +void +MD5Update (context, in, inputLen) + MD5_CTX *context; + const void *in; + unsigned int inputLen; +{ + unsigned int i, idx, partLen; + const unsigned char *input = in; + + /* Compute number of bytes mod 64 */ + idx = (unsigned int)((context->count[0] >> 3) & 0x3F); + + /* Update number of bits */ + if ((context->count[0] += ((u_int32_t)inputLen << 3)) + < ((u_int32_t)inputLen << 3)) + context->count[1]++; + context->count[1] += ((u_int32_t)inputLen >> 29); + + partLen = 64 - idx; + + /* Transform as many times as possible. */ + if (inputLen >= partLen) { + memcpy((void *)&context->buffer[idx], (const void *)input, + partLen); + MD5Transform (context->state, context->buffer); + + for (i = partLen; i + 63 < inputLen; i += 64) + MD5Transform (context->state, &input[i]); + + idx = 0; + } + else + i = 0; + + /* Buffer remaining input */ + memcpy ((void *)&context->buffer[idx], (const void *)&input[i], + inputLen-i); +} + +/* + * MD5 padding. Adds padding followed by original length. + */ + +void +MD5Pad (context) + MD5_CTX *context; +{ + unsigned char bits[8]; + unsigned int idx, padLen; + + /* Save number of bits */ + Encode (bits, context->count, 8); + + /* Pad out to 56 mod 64. */ + idx = (unsigned int)((context->count[0] >> 3) & 0x3f); + padLen = (idx < 56) ? (56 - idx) : (120 - idx); + MD5Update (context, PADDING, padLen); + + /* Append length (before padding) */ + MD5Update (context, bits, 8); +} + +/* + * MD5 finalization. Ends an MD5 message-digest operation, writing the + * the message digest and zeroizing the context. + */ + +void +MD5Final (digest, context) + unsigned char digest[16]; + MD5_CTX *context; +{ + /* Do padding. */ + MD5Pad (context); + + /* Store state in digest */ + Encode (digest, context->state, 16); + + /* Zeroize sensitive information. */ + memset ((void *)context, 0, sizeof (*context)); +} + +/* MD5 basic transformation. Transforms state based on block. */ + +static void +MD5Transform (state, block) + u_int32_t state[4]; + const unsigned char block[64]; +{ + u_int32_t a = state[0], b = state[1], c = state[2], d = state[3], x[16]; + + Decode (x, block, 64); + + /* Round 1 */ +#define S11 7 +#define S12 12 +#define S13 17 +#define S14 22 + FF (a, b, c, d, x[ 0], S11, 0xd76aa478); /* 1 */ + FF (d, a, b, c, x[ 1], S12, 0xe8c7b756); /* 2 */ + FF (c, d, a, b, x[ 2], S13, 0x242070db); /* 3 */ + FF (b, c, d, a, x[ 3], S14, 0xc1bdceee); /* 4 */ + FF (a, b, c, d, x[ 4], S11, 0xf57c0faf); /* 5 */ + FF (d, a, b, c, x[ 5], S12, 0x4787c62a); /* 6 */ + FF (c, d, a, b, x[ 6], S13, 0xa8304613); /* 7 */ + FF (b, c, d, a, x[ 7], S14, 0xfd469501); /* 8 */ + FF (a, b, c, d, x[ 8], S11, 0x698098d8); /* 9 */ + FF (d, a, b, c, x[ 9], S12, 0x8b44f7af); /* 10 */ + FF (c, d, a, b, x[10], S13, 0xffff5bb1); /* 11 */ + FF (b, c, d, a, x[11], S14, 0x895cd7be); /* 12 */ + FF (a, b, c, d, x[12], S11, 0x6b901122); /* 13 */ + FF (d, a, b, c, x[13], S12, 0xfd987193); /* 14 */ + FF (c, d, a, b, x[14], S13, 0xa679438e); /* 15 */ + FF (b, c, d, a, x[15], S14, 0x49b40821); /* 16 */ + + /* Round 2 */ +#define S21 5 +#define S22 9 +#define S23 14 +#define S24 20 + GG (a, b, c, d, x[ 1], S21, 0xf61e2562); /* 17 */ + GG (d, a, b, c, x[ 6], S22, 0xc040b340); /* 18 */ + GG (c, d, a, b, x[11], S23, 0x265e5a51); /* 19 */ + GG (b, c, d, a, x[ 0], S24, 0xe9b6c7aa); /* 20 */ + GG (a, b, c, d, x[ 5], S21, 0xd62f105d); /* 21 */ + GG (d, a, b, c, x[10], S22, 0x2441453); /* 22 */ + GG (c, d, a, b, x[15], S23, 0xd8a1e681); /* 23 */ + GG (b, c, d, a, x[ 4], S24, 0xe7d3fbc8); /* 24 */ + GG (a, b, c, d, x[ 9], S21, 0x21e1cde6); /* 25 */ + GG (d, a, b, c, x[14], S22, 0xc33707d6); /* 26 */ + GG (c, d, a, b, x[ 3], S23, 0xf4d50d87); /* 27 */ + GG (b, c, d, a, x[ 8], S24, 0x455a14ed); /* 28 */ + GG (a, b, c, d, x[13], S21, 0xa9e3e905); /* 29 */ + GG (d, a, b, c, x[ 2], S22, 0xfcefa3f8); /* 30 */ + GG (c, d, a, b, x[ 7], S23, 0x676f02d9); /* 31 */ + GG (b, c, d, a, x[12], S24, 0x8d2a4c8a); /* 32 */ + + /* Round 3 */ +#define S31 4 +#define S32 11 +#define S33 16 +#define S34 23 + HH (a, b, c, d, x[ 5], S31, 0xfffa3942); /* 33 */ + HH (d, a, b, c, x[ 8], S32, 0x8771f681); /* 34 */ + HH (c, d, a, b, x[11], S33, 0x6d9d6122); /* 35 */ + HH (b, c, d, a, x[14], S34, 0xfde5380c); /* 36 */ + HH (a, b, c, d, x[ 1], S31, 0xa4beea44); /* 37 */ + HH (d, a, b, c, x[ 4], S32, 0x4bdecfa9); /* 38 */ + HH (c, d, a, b, x[ 7], S33, 0xf6bb4b60); /* 39 */ + HH (b, c, d, a, x[10], S34, 0xbebfbc70); /* 40 */ + HH (a, b, c, d, x[13], S31, 0x289b7ec6); /* 41 */ + HH (d, a, b, c, x[ 0], S32, 0xeaa127fa); /* 42 */ + HH (c, d, a, b, x[ 3], S33, 0xd4ef3085); /* 43 */ + HH (b, c, d, a, x[ 6], S34, 0x4881d05); /* 44 */ + HH (a, b, c, d, x[ 9], S31, 0xd9d4d039); /* 45 */ + HH (d, a, b, c, x[12], S32, 0xe6db99e5); /* 46 */ + HH (c, d, a, b, x[15], S33, 0x1fa27cf8); /* 47 */ + HH (b, c, d, a, x[ 2], S34, 0xc4ac5665); /* 48 */ + + /* Round 4 */ +#define S41 6 +#define S42 10 +#define S43 15 +#define S44 21 + II (a, b, c, d, x[ 0], S41, 0xf4292244); /* 49 */ + II (d, a, b, c, x[ 7], S42, 0x432aff97); /* 50 */ + II (c, d, a, b, x[14], S43, 0xab9423a7); /* 51 */ + II (b, c, d, a, x[ 5], S44, 0xfc93a039); /* 52 */ + II (a, b, c, d, x[12], S41, 0x655b59c3); /* 53 */ + II (d, a, b, c, x[ 3], S42, 0x8f0ccc92); /* 54 */ + II (c, d, a, b, x[10], S43, 0xffeff47d); /* 55 */ + II (b, c, d, a, x[ 1], S44, 0x85845dd1); /* 56 */ + II (a, b, c, d, x[ 8], S41, 0x6fa87e4f); /* 57 */ + II (d, a, b, c, x[15], S42, 0xfe2ce6e0); /* 58 */ + II (c, d, a, b, x[ 6], S43, 0xa3014314); /* 59 */ + II (b, c, d, a, x[13], S44, 0x4e0811a1); /* 60 */ + II (a, b, c, d, x[ 4], S41, 0xf7537e82); /* 61 */ + II (d, a, b, c, x[11], S42, 0xbd3af235); /* 62 */ + II (c, d, a, b, x[ 2], S43, 0x2ad7d2bb); /* 63 */ + II (b, c, d, a, x[ 9], S44, 0xeb86d391); /* 64 */ + + state[0] += a; + state[1] += b; + state[2] += c; + state[3] += d; + + /* Zeroize sensitive information. */ + memset ((void *)x, 0, sizeof (x)); +} diff --git a/src/bin/network/ftpd/md5hl.c b/src/bin/network/ftpd/md5hl.c new file mode 100644 index 0000000000..c404565cd4 --- /dev/null +++ b/src/bin/network/ftpd/md5hl.c @@ -0,0 +1,98 @@ +/* mdXhl.c * ---------------------------------------------------------------------------- + * "THE BEER-WARE LICENSE" (Revision 42): + * wrote this file. As long as you retain this notice you + * can do whatever you want with this stuff. If we meet some day, and you think + * this stuff is worth it, you can buy me a beer in return. Poul-Henning Kamp + * ---------------------------------------------------------------------------- + */ + +#include +__FBSDID("$FreeBSD: src/lib/libmd/mdXhl.c,v 1.19 2006/01/17 15:35:56 phk Exp $"); + +#include +#include +#include +#include + +#include +#include +#include + +#include "md5.h" + +char * +MD5End(MD5_CTX *ctx, char *buf) +{ + int i; + unsigned char digest[MD5_DIGEST_LENGTH]; + static const char hex[]="0123456789abcdef"; + + if (!buf) + buf = malloc(2*MD5_DIGEST_LENGTH + 1); + if (!buf) + return 0; + MD5Final(digest, ctx); + for (i = 0; i < MD5_DIGEST_LENGTH; i++) { + buf[i+i] = hex[digest[i] >> 4]; + buf[i+i+1] = hex[digest[i] & 0x0f]; + } + buf[i+i] = '\0'; + return buf; +} + +char * +MD5File(const char *filename, char *buf) +{ + return (MD5FileChunk(filename, buf, 0, 0)); +} + +char * +MD5FileChunk(const char *filename, char *buf, off_t ofs, off_t len) +{ + unsigned char buffer[BUFSIZ]; + MD5_CTX ctx; + struct stat stbuf; + int f, i, e; + off_t n; + + MD5Init(&ctx); + f = open(filename, O_RDONLY); + if (f < 0) + return 0; + if (fstat(f, &stbuf) < 0) + return 0; + if (ofs > stbuf.st_size) + ofs = stbuf.st_size; + if ((len == 0) || (len > stbuf.st_size - ofs)) + len = stbuf.st_size - ofs; + if (lseek(f, ofs, SEEK_SET) < 0) + return 0; + n = len; + i = 0; + while (n > 0) { + if (n > sizeof(buffer)) + i = read(f, buffer, sizeof(buffer)); + else + i = read(f, buffer, n); + if (i < 0) + break; + MD5Update(&ctx, buffer, i); + n -= i; + } + e = errno; + close(f); + errno = e; + if (i < 0) + return 0; + return (MD5End(&ctx, buf)); +} + +char * +MD5Data (const void *data, unsigned int len, char *buf) +{ + MD5_CTX ctx; + + MD5Init(&ctx); + MD5Update(&ctx,data,len); + return (MD5End(&ctx, buf)); +} diff --git a/src/bin/network/ftpd/pathnames.h b/src/bin/network/ftpd/pathnames.h new file mode 100644 index 0000000000..24b74645ba --- /dev/null +++ b/src/bin/network/ftpd/pathnames.h @@ -0,0 +1,44 @@ +/* + * Copyright (c) 1989, 1993 + * 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. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the University of + * California, Berkeley and its contributors. + * 4. 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. + * + * @(#)pathnames.h 8.1 (Berkeley) 6/4/93 + * $FreeBSD: src/libexec/ftpd/pathnames.h,v 1.11 1999/08/28 00:09:31 peter Exp $ + */ + +#include + +#define _PATH_FTPCHROOT "/etc/ftpchroot" +#define _PATH_FTPWELCOME "/etc/ftpwelcome" +#define _PATH_FTPLOGINMESG "/etc/ftpmotd" +#define _PATH_FTPHOSTS "/etc/ftphosts" +#define _PATH_FTPDSTATFILE "/var/log/ftpd" +#define _PATH_LS "/bin/ls" diff --git a/src/bin/network/ftpd/popen.c b/src/bin/network/ftpd/popen.c new file mode 100644 index 0000000000..6f8942b15d --- /dev/null +++ b/src/bin/network/ftpd/popen.c @@ -0,0 +1,207 @@ +/* + * Copyright (c) 1988, 1993, 1994 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software written by Ken Arnold and + * published in UNIX Review, Vol. 6, No. 8. + * + * 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. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the University of + * California, Berkeley and its contributors. + * 4. 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. + */ + +#ifndef lint +#if 0 +static char sccsid[] = "@(#)popen.c 8.3 (Berkeley) 4/6/94"; +#endif +#endif /* not lint */ + +#include +__FBSDID("$FreeBSD: src/libexec/ftpd/popen.c,v 1.26 2004/11/18 13:46:29 yar Exp $"); + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "extern.h" +#include "pathnames.h" +#include +#include + +#define MAXUSRARGS 100 +#define MAXGLOBARGS 1000 + +/* + * Special version of popen which avoids call to shell. This ensures noone + * may create a pipe to a hidden program as a side effect of a list or dir + * command. + */ +static int *pids; +static int fds; + +FILE * +ftpd_popen(char *program, char *type) +{ + char *cp; + FILE *iop; + int argc, gargc, pdes[2], pid; + char **pop, *argv[MAXUSRARGS], *gargv[MAXGLOBARGS]; + + if (((*type != 'r') && (*type != 'w')) || type[1]) + return (NULL); + + if (!pids) { + if ((fds = getdtablesize()) <= 0) + return (NULL); + if ((pids = malloc(fds * sizeof(int))) == NULL) + return (NULL); + memset(pids, 0, fds * sizeof(int)); + } + if (pipe(pdes) < 0) + return (NULL); + + /* break up string into pieces */ + for (argc = 0, cp = program; argc < MAXUSRARGS; cp = NULL) { + if (!(argv[argc++] = strtok(cp, " \t\n"))) + break; + } + argv[argc - 1] = NULL; + + /* glob each piece */ + gargv[0] = argv[0]; + for (gargc = argc = 1; argv[argc] && gargc < (MAXGLOBARGS-1); argc++) { + glob_t gl; + int flags = GLOB_BRACE|GLOB_NOCHECK|GLOB_TILDE; + + memset(&gl, 0, sizeof(gl)); + gl.gl_matchc = MAXGLOBARGS; + flags |= GLOB_LIMIT; + if (glob(argv[argc], flags, NULL, &gl)) + gargv[gargc++] = strdup(argv[argc]); + else + for (pop = gl.gl_pathv; *pop && gargc < (MAXGLOBARGS-1); + pop++) + gargv[gargc++] = strdup(*pop); + globfree(&gl); + } + gargv[gargc] = NULL; + + iop = NULL; + fflush(NULL); +#ifdef BUILTIN_LS + pid = (strcmp(gargv[0], _PATH_LS) == 0) ? fork() : vfork(); +#else + pid = fork(); +#endif + switch(pid) { + case -1: /* error */ + (void)close(pdes[0]); + (void)close(pdes[1]); + goto pfree; + /* NOTREACHED */ + case 0: /* child */ + if (*type == 'r') { + if (pdes[1] != STDOUT_FILENO) { + dup2(pdes[1], STDOUT_FILENO); + (void)close(pdes[1]); + } + dup2(STDOUT_FILENO, STDERR_FILENO); /* stderr too! */ + (void)close(pdes[0]); + } else { + if (pdes[0] != STDIN_FILENO) { + dup2(pdes[0], STDIN_FILENO); + (void)close(pdes[0]); + } + (void)close(pdes[1]); + } +#ifdef BUILTIN_LS + if (strcmp(gargv[0], _PATH_LS) == 0) { + /* Reset getopt for ls_main() */ + optreset = optind = optopt = 1; + /* Close syslogging to remove pwd.db missing msgs */ + closelog(); + /* Trigger to sense new /etc/localtime after chroot */ + if (getenv("TZ") == NULL) { + setenv("TZ", "", 0); + tzset(); + unsetenv("TZ"); + tzset(); + } + exit(ls_main(gargc, gargv)); + } +#endif + execv(gargv[0], gargv); + _exit(1); + } + /* parent; assume fdopen can't fail... */ + if (*type == 'r') { + iop = fdopen(pdes[0], type); + (void)close(pdes[1]); + } else { + iop = fdopen(pdes[1], type); + (void)close(pdes[0]); + } + pids[fileno(iop)] = pid; + +pfree: for (argc = 1; gargv[argc] != NULL; argc++) + free(gargv[argc]); + + return (iop); +} + +int +ftpd_pclose(FILE *iop) +{ + int fdes, omask, status; + pid_t pid; + + /* + * pclose returns -1 if stream is not associated with a + * `popened' command, or, if already `pclosed'. + */ + if (pids == 0 || pids[fdes = fileno(iop)] == 0) + return (-1); + (void)fclose(iop); + omask = sigblock(sigmask(SIGINT)|sigmask(SIGQUIT)|sigmask(SIGHUP)); + while ((pid = waitpid(pids[fdes], &status, 0)) < 0 && errno == EINTR) + continue; + (void)sigsetmask(omask); + pids[fdes] = 0; + if (pid < 0) + return (pid); + if (WIFEXITED(status)) + return (WEXITSTATUS(status)); + return (1); +}