diff options
Diffstat (limited to 'libexec/ftpd')
-rw-r--r-- | libexec/ftpd/Makefile | 39 | ||||
-rw-r--r-- | libexec/ftpd/Makefile.depend | 21 | ||||
-rw-r--r-- | libexec/ftpd/Makefile.depend.options | 5 | ||||
-rw-r--r-- | libexec/ftpd/blacklist.c | 55 | ||||
-rw-r--r-- | libexec/ftpd/blacklist_client.h | 53 | ||||
-rw-r--r-- | libexec/ftpd/config.h | 280 | ||||
-rw-r--r-- | libexec/ftpd/extern.h | 110 | ||||
-rw-r--r-- | libexec/ftpd/ftpchroot.5 | 118 | ||||
-rw-r--r-- | libexec/ftpd/ftpcmd.y | 1806 | ||||
-rw-r--r-- | libexec/ftpd/ftpd.8 | 589 | ||||
-rw-r--r-- | libexec/ftpd/ftpd.c | 3446 | ||||
-rw-r--r-- | libexec/ftpd/ftpusers | 28 | ||||
-rw-r--r-- | libexec/ftpd/logwtmp.c | 70 | ||||
-rw-r--r-- | libexec/ftpd/pathnames.h | 39 | ||||
-rw-r--r-- | libexec/ftpd/popen.c | 193 |
15 files changed, 6852 insertions, 0 deletions
diff --git a/libexec/ftpd/Makefile b/libexec/ftpd/Makefile new file mode 100644 index 000000000000..a040fa57f7d7 --- /dev/null +++ b/libexec/ftpd/Makefile @@ -0,0 +1,39 @@ +.include <src.opts.mk> + +PACKAGE= ftpd + +CONFS= ftpusers +PROG= ftpd +MAN= ftpd.8 ftpchroot.5 +SRCS= ftpd.c ftpcmd.y logwtmp.c popen.c + +CFLAGS+=-DSETPROCTITLE -DLOGIN_CAP -DVIRTUAL_HOSTING +CFLAGS+=-I${.CURDIR} +YFLAGS= +WARNS?= 2 +WFORMAT=0 + +LIBADD= crypt md util + +.PATH: ${SRCTOP}/bin/ls +SRCS+= ls.c cmp.c print.c util.c +CFLAGS+=-Dmain=ls_main -I${SRCTOP}/bin/ls +LIBADD+= m + +.if ${MK_BLACKLIST_SUPPORT} != "no" +CFLAGS+= -DUSE_BLACKLIST -I${SRCTOP}/contrib/blocklist/include +SRCS+= blacklist.c +LIBADD+= blacklist +LDFLAGS+=-L${LIBBLACKLISTDIR} +.endif + +.if ${MK_INET6_SUPPORT} != "no" +CFLAGS+=-DINET6 +.endif + +.if ${MK_PAM_SUPPORT} != "no" +CFLAGS+=-DUSE_PAM +LIBADD+= pam +.endif + +.include <bsd.prog.mk> diff --git a/libexec/ftpd/Makefile.depend b/libexec/ftpd/Makefile.depend new file mode 100644 index 000000000000..b6e94a3cf93c --- /dev/null +++ b/libexec/ftpd/Makefile.depend @@ -0,0 +1,21 @@ +# Autogenerated - do NOT edit! + +DIRDEPS = \ + include \ + include/arpa \ + include/xlocale \ + lib/${CSU_DIR} \ + lib/libc \ + lib/libcompiler_rt \ + lib/libcrypt \ + lib/libmd \ + lib/libutil \ + lib/msun \ + usr.bin/yacc.host \ + + +.include <dirdeps.mk> + +.if ${DEP_RELDIR} == ${_DEP_RELDIR} +# local dependencies - needed for -jN in clean tree +.endif diff --git a/libexec/ftpd/Makefile.depend.options b/libexec/ftpd/Makefile.depend.options new file mode 100644 index 000000000000..5f186bb031f2 --- /dev/null +++ b/libexec/ftpd/Makefile.depend.options @@ -0,0 +1,5 @@ +# This file is not autogenerated - take care! + +DIRDEPS_OPTIONS= BLACKLIST_SUPPORT PAM_SUPPORT + +.include <dirdeps-options.mk> diff --git a/libexec/ftpd/blacklist.c b/libexec/ftpd/blacklist.c new file mode 100644 index 000000000000..0a45f9369074 --- /dev/null +++ b/libexec/ftpd/blacklist.c @@ -0,0 +1,55 @@ +/*- + * Copyright (c) 2016 The FreeBSD Foundation + * + * This software was developed by Kurt Lidl under sponsorship from the + * FreeBSD Foundation. + * + * 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. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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. */ + + +#include <ctype.h> +#include <stdarg.h> +#include <stdlib.h> +#include <unistd.h> + +#include <blacklist.h> +#include "blacklist_client.h" + +static struct blacklist *blstate; +extern int use_blacklist; + +void +blacklist_init(void) +{ + + if (use_blacklist) + blstate = blacklist_open(); +} + +void +blacklist_notify(int action, int fd, const char *msg) +{ + + if (blstate == NULL) + return; + (void)blacklist_r(blstate, action, fd, msg); +} diff --git a/libexec/ftpd/blacklist_client.h b/libexec/ftpd/blacklist_client.h new file mode 100644 index 000000000000..0b6805dc218e --- /dev/null +++ b/libexec/ftpd/blacklist_client.h @@ -0,0 +1,53 @@ +/*- + * Copyright (c) 2016 The FreeBSD Foundation + * + * This software was developed by Kurt Lidl under sponsorship from the + * FreeBSD Foundation. + * + * 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. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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 BLACKLIST_CLIENT_H +#define BLACKLIST_CLIENT_H + +#ifndef BLACKLIST_API_ENUM +enum { + BLACKLIST_AUTH_OK = 0, + BLACKLIST_AUTH_FAIL +}; +#endif + +#ifdef USE_BLACKLIST +void blacklist_init(void); +void blacklist_notify(int, int, const char *); + +#define BLACKLIST_INIT() blacklist_init() +#define BLACKLIST_NOTIFY(x, y, z) blacklist_notify(x, y, z) + +#else + +#define BLACKLIST_INIT() +#define BLACKLIST_NOTIFY(x, y, z) + +#endif + +#endif /* BLACKLIST_CLIENT_H */ diff --git a/libexec/ftpd/config.h b/libexec/ftpd/config.h new file mode 100644 index 000000000000..c5ca1f01e10e --- /dev/null +++ b/libexec/ftpd/config.h @@ -0,0 +1,280 @@ + + +/* 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 <sys/types.h> 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 <sys/time.h> and <time.h>. */ +#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 <arpa/nameser.h> header file. */ +#define HAVE_ARPA_NAMESER_H 1 + +/* Define if you have the <dirent.h> header file. */ +#define HAVE_DIRENT_H 1 + +/* Define if you have the <err.h> header file. */ +#define HAVE_ERR_H 1 + +/* Define if you have the <fts.h> header file. */ +#define HAVE_FTS_H 1 + +/* Define if you have the <libutil.h> header file. */ +#define HAVE_LIBUTIL_H 1 + +/* Define if you have the <ndir.h> header file. */ +/* #undef HAVE_NDIR_H */ + +/* Define if you have the <paths.h> header file. */ +#define HAVE_PATHS_H 1 + +/* Define if you have the <sys/dir.h> header file. */ +#define HAVE_SYS_DIR_H 1 + +/* Define if you have the <sys/ndir.h> header file. */ +/* #undef HAVE_SYS_NDIR_H */ + +/* Define if you have the <sys/sysmacros.h> header file. */ +/* #undef HAVE_SYS_SYSMACROS_H */ + +/* Define if you have the <util.h> 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 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 <sys/socket.h> */ +#define HAVE_AF_INET6 1 + +/* Define if `struct sockaddr_in6' exists in <netinet/in.h> */ +#define HAVE_SOCKADDR_IN6 1 + +/* Define if `struct addrinfo' exists in <netdb.h> */ +#define HAVE_ADDRINFO 1 + +/* + * Define if <netdb.h> 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 <glob.h> */ +#define HAVE_WORKING_GLOB 1 + +/* Define if crypt() is declared in <unistd.h> */ +#define HAVE_CRYPT_D 1 + +/* Define if fclose() is declared in <stdio.h> */ +#define HAVE_FCLOSE_D 1 + +/* Define if optarg is declared in <stdlib.h> or <unistd.h> */ +#define HAVE_OPTARG_D 1 + +/* Define if optind is declared in <stdlib.h> or <unistd.h> */ +#define HAVE_OPTIND_D 1 + +/* Define if optreset exists */ +#define HAVE_OPTRESET 1 + +/* Define if pclose() is declared in <stdio.h> */ +#define HAVE_PCLOSE_D 1 + +/* Define if getusershell() is declared in <unistd.h> */ +#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 <arpa/ftp.h> header file. */ +#define HAVE_FTP_NAMES 1 diff --git a/libexec/ftpd/extern.h b/libexec/ftpd/extern.h new file mode 100644 index 000000000000..047e8573dd09 --- /dev/null +++ b/libexec/ftpd/extern.h @@ -0,0 +1,110 @@ +/*- + * SPDX-License-Identifier: BSD-3-Clause + * + * 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. 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. + */ + +#include <sys/types.h> +#include <sys/socket.h> + +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 *); +int get_line(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 *); +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 **); + +extern int assumeutf8; +extern char cbuf[]; +extern union sockunion data_dest; +extern int epsvall; +extern int form; +extern int ftpdebug; +extern int guest; +extern union sockunion his_addr; +extern char *homedir; +extern int hostinfo; +extern char *hostname; +extern int maxtimeout; +extern int logged_in; +extern int logging; +extern int noepsv; +extern int noguestretr; +extern int noretr; +extern int paranoid; +extern struct passwd *pw; +extern int pdata; +extern char proctitle[]; +extern int readonly; +extern off_t restart_point; +extern int timeout; +extern char tmpline[]; +extern int type; +extern char *typenames[]; /* defined in <arpa/ftp.h> included from ftpd.c */ +extern int usedefault; + +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/libexec/ftpd/ftpchroot.5 b/libexec/ftpd/ftpchroot.5 new file mode 100644 index 000000000000..cb2f15f719ad --- /dev/null +++ b/libexec/ftpd/ftpchroot.5 @@ -0,0 +1,118 @@ +.\" Copyright (c) 2003 FreeBSD Project +.\" 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. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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. +.\" +.Dd January 26, 2003 +.Dt FTPCHROOT 5 +.Os +.Sh NAME +.Nm ftpchroot +.Nd "list users and groups subject to FTP access restrictions" +.Sh DESCRIPTION +The file +.Nm +is read by +.Xr ftpd 8 +at the beginning of an FTP session, after having authenticated the user. +Each line in +.Nm +corresponds to a user or group. +If a line in +.Nm +matches the current user or a group he is a member of, +access restrictions will be applied to this +session by changing its root directory with +.Xr chroot 2 +to that specified on the line or to the user's login directory. +.Pp +The order of records in +.Nm +is important because the first match will be used. +Fields on each line are separated by tabs or spaces. +.Pp +The first field specifies a user or group name. +If it is prefixed by an +.Dq at +sign, +.Ql @ , +it specifies a group name; +the line will match each user who is a member of this group. +As a special case, a single +.Ql @ +in this field will match any user. +A username is specified otherwise. +.Pp +The optional second field describes the directory for the user +or each member of the group to be locked up in using +.Xr chroot 2 . +Be it omitted, the user's login directory will be used. +If it is not an absolute pathname, then it will be relative +to the user's login directory. +If it contains the +.Pa /./ +separator, +.Xr ftpd 8 +will treat its left-hand side as the name of the directory to do +.Xr chroot 2 +to, and its right-hand side to change the current directory to afterwards. +.Sh FILES +.Bl -tag -width ".Pa /etc/ftpchroot" -compact +.It Pa /etc/ftpchroot +.El +.Sh EXAMPLES +These lines in +.Nm +will lock up the user +.Dq Li webuser +and each member of the group +.Dq Li hostee +in their respective login directories: +.Bd -literal -offset indent +webuser +@hostee +.Ed +.Pp +And this line will tell +.Xr ftpd 8 +to lock up the user +.Dq Li joe +in +.Pa /var/spool/ftp +and then to change the current directory to +.Pa /joe , +which is relative to the session's new root: +.Pp +.Dl "joe /var/spool/ftp/./joe" +.Pp +And finally the following line will lock up every user connecting +through FTP in his respective +.Pa ~/public_html , +thus lowering possible impact on the system +from intrinsic insecurity of FTP: +.Pp +.Dl "@ public_html" +.Sh SEE ALSO +.Xr chroot 2 , +.Xr group 5 , +.Xr passwd 5 , +.Xr ftpd 8 diff --git a/libexec/ftpd/ftpcmd.y b/libexec/ftpd/ftpcmd.y new file mode 100644 index 000000000000..c090130d8137 --- /dev/null +++ b/libexec/ftpd/ftpcmd.y @@ -0,0 +1,1806 @@ +/*- + * SPDX-License-Identifier: BSD-3-Clause + * + * Copyright (c) 1985, 1988, 1993, 1994 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +/* + * Grammar for FTP commands. + * See RFC 959. + */ + +%{ + +#include <sys/param.h> +#include <sys/socket.h> +#include <sys/stat.h> + +#include <netinet/in.h> +#include <arpa/ftp.h> + +#include <ctype.h> +#include <errno.h> +#include <glob.h> +#include <libutil.h> +#include <limits.h> +#include <md5.h> +#include <netdb.h> +#include <pwd.h> +#include <signal.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <syslog.h> +#include <time.h> +#include <unistd.h> + +#include "extern.h" +#include "pathnames.h" + +#define yylex ftpcmd_yylex + +off_t restart_point; + +static int cmd_type; +static int cmd_form; +static int cmd_bytesz; +static int state; +char cbuf[512]; +char *fromname = NULL; + +%} + +%union { + struct { + off_t o; + int i; + } u; + char *s; +} + +%token + A B C E F I + L N P R S T + ALL + + SP CRLF COMMA + + USER PASS ACCT REIN QUIT PORT + PASV TYPE STRU MODE RETR STOR + APPE MLFL MAIL MSND MSOM MSAM + MRSQ MRCP ALLO REST RNFR RNTO + ABOR DELE CWD LIST NLST SITE + STAT HELP NOOP MKD RMD PWD + CDUP STOU SMNT SYST SIZE MDTM + LPRT LPSV EPRT EPSV FEAT + + UMASK IDLE CHMOD MDFIVE + + LEXERR NOTIMPL + +%token <s> STRING +%token <u> NUMBER + +%type <u.i> check_login octal_number byte_size +%type <u.i> check_login_ro check_login_epsv +%type <u.i> struct_code mode_code type_code form_code +%type <s> pathstring pathname password username +%type <s> ALL NOTIMPL + +%start cmd_list + +%% + +cmd_list + : /* empty */ + | cmd_list cmd + { + if (fromname) + free(fromname); + fromname = NULL; + restart_point = 0; + } + | cmd_list rcmd + ; + +cmd + : USER SP username CRLF + { + user($3); + free($3); + } + | PASS SP password CRLF + { + pass($3); + free($3); + } + | PASS CRLF + { + pass(""); + } + | PORT check_login SP host_port CRLF + { + if (epsvall) { + reply(501, "No PORT allowed after EPSV ALL."); + goto port_done; + } + if (!$2) + goto port_done; + if (port_check("PORT") == 1) + goto port_done; +#ifdef INET6 + if ((his_addr.su_family != AF_INET6 || + !IN6_IS_ADDR_V4MAPPED(&his_addr.su_sin6.sin6_addr))) { + /* shoud never happen */ + usedefault = 1; + reply(500, "Invalid address rejected."); + goto port_done; + } + port_check_v6("pcmd"); +#endif + port_done: + ; + } + | LPRT check_login SP host_long_port CRLF + { + if (epsvall) { + reply(501, "No LPRT allowed after EPSV ALL."); + goto lprt_done; + } + if (!$2) + goto lprt_done; + if (port_check("LPRT") == 1) + goto lprt_done; +#ifdef INET6 + if (his_addr.su_family != AF_INET6) { + usedefault = 1; + reply(500, "Invalid address rejected."); + goto lprt_done; + } + if (port_check_v6("LPRT") == 1) + goto lprt_done; +#endif + lprt_done: + ; + } + | EPRT check_login SP STRING CRLF + { + char delim; + char *tmp = NULL; + char *p, *q; + char *result[3]; + struct addrinfo hints; + struct addrinfo *res; + int i; + + if (epsvall) { + reply(501, "No EPRT allowed after EPSV ALL."); + goto eprt_done; + } + if (!$2) + goto eprt_done; + + memset(&data_dest, 0, sizeof(data_dest)); + tmp = strdup($4); + if (ftpdebug) + syslog(LOG_DEBUG, "%s", tmp); + if (!tmp) { + fatalerror("not enough core"); + /*NOTREACHED*/ + } + p = tmp; + delim = p[0]; + p++; + memset(result, 0, sizeof(result)); + for (i = 0; i < 3; i++) { + q = strchr(p, delim); + if (!q || *q != delim) { + parsefail: + reply(500, + "Invalid argument, rejected."); + if (tmp) + free(tmp); + usedefault = 1; + goto eprt_done; + } + *q++ = '\0'; + result[i] = p; + if (ftpdebug) + syslog(LOG_DEBUG, "%d: %s", i, p); + p = q; + } + + /* some more sanity check */ + p = result[0]; + while (*p) { + if (!isdigit(*p)) + goto parsefail; + p++; + } + p = result[2]; + while (*p) { + if (!isdigit(*p)) + goto parsefail; + p++; + } + + /* grab address */ + memset(&hints, 0, sizeof(hints)); + if (atoi(result[0]) == 1) + hints.ai_family = PF_INET; +#ifdef INET6 + else if (atoi(result[0]) == 2) + hints.ai_family = PF_INET6; +#endif + else + hints.ai_family = PF_UNSPEC; /*XXX*/ + hints.ai_socktype = SOCK_STREAM; + i = getaddrinfo(result[1], result[2], &hints, &res); + if (i) + goto parsefail; + memcpy(&data_dest, res->ai_addr, res->ai_addrlen); +#ifdef INET6 + if (his_addr.su_family == AF_INET6 + && data_dest.su_family == AF_INET6) { + /* XXX more sanity checks! */ + data_dest.su_sin6.sin6_scope_id = + his_addr.su_sin6.sin6_scope_id; + } +#endif + free(tmp); + tmp = NULL; + + if (port_check("EPRT") == 1) + goto eprt_done; +#ifdef INET6 + if (his_addr.su_family != AF_INET6) { + usedefault = 1; + reply(500, "Invalid address rejected."); + goto eprt_done; + } + if (port_check_v6("EPRT") == 1) + goto eprt_done; +#endif + eprt_done: + free($4); + } + | PASV check_login CRLF + { + if (epsvall) + reply(501, "No PASV allowed after EPSV ALL."); + else if ($2) + passive(); + } + | LPSV check_login CRLF + { + if (epsvall) + reply(501, "No LPSV allowed after EPSV ALL."); + else if ($2) + long_passive("LPSV", PF_UNSPEC); + } + | EPSV check_login_epsv SP NUMBER CRLF + { + if ($2) { + int pf; + switch ($4.i) { + case 1: + pf = PF_INET; + break; +#ifdef INET6 + case 2: + pf = PF_INET6; + break; +#endif + default: + pf = -1; /*junk value*/ + break; + } + long_passive("EPSV", pf); + } + } + | EPSV check_login_epsv SP ALL CRLF + { + if ($2) { + reply(200, "EPSV ALL command successful."); + epsvall++; + } + } + | EPSV check_login_epsv CRLF + { + if ($2) + long_passive("EPSV", PF_UNSPEC); + } + | TYPE check_login SP type_code CRLF + { + if ($2) { + switch (cmd_type) { + + case TYPE_A: + if (cmd_form == FORM_N) { + reply(200, "Type set to A."); + type = cmd_type; + form = cmd_form; + } else + reply(504, "Form must be N."); + break; + + case TYPE_E: + reply(504, "Type E not implemented."); + break; + + case TYPE_I: + reply(200, "Type set to I."); + type = cmd_type; + break; + + case TYPE_L: +#if CHAR_BIT == 8 + if (cmd_bytesz == 8) { + reply(200, + "Type set to L (byte size 8)."); + type = cmd_type; + } else + reply(504, "Byte size must be 8."); +#else /* CHAR_BIT == 8 */ + UNIMPLEMENTED for CHAR_BIT != 8 +#endif /* CHAR_BIT == 8 */ + } + } + } + | STRU check_login SP struct_code CRLF + { + if ($2) { + switch ($4) { + + case STRU_F: + reply(200, "STRU F accepted."); + break; + + default: + reply(504, "Unimplemented STRU type."); + } + } + } + | MODE check_login SP mode_code CRLF + { + if ($2) { + switch ($4) { + + case MODE_S: + reply(200, "MODE S accepted."); + break; + + default: + reply(502, "Unimplemented MODE type."); + } + } + } + | ALLO check_login SP NUMBER CRLF + { + if ($2) { + reply(202, "ALLO command ignored."); + } + } + | ALLO check_login SP NUMBER SP R SP NUMBER CRLF + { + if ($2) { + reply(202, "ALLO command ignored."); + } + } + | RETR check_login SP pathname CRLF + { + if (noretr || (guest && noguestretr)) + reply(500, "RETR command disabled."); + else if ($2 && $4 != NULL) + retrieve(NULL, $4); + + if ($4 != NULL) + free($4); + } + | STOR check_login_ro SP pathname CRLF + { + if ($2 && $4 != NULL) + store($4, "w", 0); + if ($4 != NULL) + free($4); + } + | APPE check_login_ro SP pathname CRLF + { + if ($2 && $4 != NULL) + store($4, "a", 0); + if ($4 != NULL) + free($4); + } + | NLST check_login CRLF + { + if ($2) + send_file_list("."); + } + | NLST check_login SP pathstring CRLF + { + if ($2) + send_file_list($4); + free($4); + } + | LIST check_login CRLF + { + if ($2) + retrieve(_PATH_LS " -lA", ""); + } + | LIST check_login SP pathstring CRLF + { + if ($2) + retrieve(_PATH_LS " -lA %s", $4); + free($4); + } + | STAT check_login SP pathname CRLF + { + if ($2 && $4 != NULL) + statfilecmd($4); + if ($4 != NULL) + free($4); + } + | STAT check_login CRLF + { + if ($2) { + statcmd(); + } + } + | DELE check_login_ro SP pathname CRLF + { + if ($2 && $4 != NULL) + delete($4); + if ($4 != NULL) + free($4); + } + | RNTO check_login_ro SP pathname CRLF + { + if ($2 && $4 != NULL) { + if (fromname) { + renamecmd(fromname, $4); + free(fromname); + fromname = NULL; + } else { + reply(503, "Bad sequence of commands."); + } + } + if ($4 != NULL) + free($4); + } + | ABOR check_login CRLF + { + if ($2) + reply(225, "ABOR command successful."); + } + | CWD check_login CRLF + { + if ($2) { + cwd(homedir); + } + } + | CWD check_login SP pathname CRLF + { + if ($2 && $4 != NULL) + cwd($4); + if ($4 != NULL) + free($4); + } + | HELP CRLF + { + help(cmdtab, NULL); + } + | HELP SP STRING CRLF + { + char *cp = $3; + + if (strncasecmp(cp, "SITE", 4) == 0) { + cp = $3 + 4; + if (*cp == ' ') + cp++; + if (*cp) + help(sitetab, cp); + else + help(sitetab, NULL); + } else + help(cmdtab, $3); + free($3); + } + | NOOP CRLF + { + reply(200, "NOOP command successful."); + } + | MKD check_login_ro SP pathname CRLF + { + if ($2 && $4 != NULL) + makedir($4); + if ($4 != NULL) + free($4); + } + | RMD check_login_ro SP pathname CRLF + { + if ($2 && $4 != NULL) + removedir($4); + if ($4 != NULL) + free($4); + } + | PWD check_login CRLF + { + if ($2) + pwd(); + } + | CDUP check_login CRLF + { + if ($2) + cwd(".."); + } + | SITE SP HELP CRLF + { + help(sitetab, NULL); + } + | SITE SP HELP SP STRING CRLF + { + help(sitetab, $5); + free($5); + } + | SITE SP MDFIVE check_login SP pathname CRLF + { + char p[64], *q; + + if ($4 && $6) { + q = MD5File($6, p); + if (q != NULL) + reply(200, "MD5(%s) = %s", $6, p); + else + perror_reply(550, $6); + } + if ($6) + free($6); + } + | SITE SP UMASK check_login CRLF + { + int oldmask; + + if ($4) { + oldmask = umask(0); + (void) umask(oldmask); + reply(200, "Current UMASK is %03o.", oldmask); + } + } + | SITE SP UMASK check_login SP octal_number CRLF + { + int oldmask; + + if ($4) { + if (($6 == -1) || ($6 > 0777)) { + reply(501, "Bad UMASK value."); + } else { + oldmask = umask($6); + reply(200, + "UMASK set to %03o (was %03o).", + $6, oldmask); + } + } + } + | SITE SP CHMOD check_login_ro SP octal_number SP pathname CRLF + { + if ($4 && ($8 != NULL)) { + if (($6 == -1 ) || ($6 > 0777)) + reply(501, "Bad mode value."); + else if (chmod($8, $6) < 0) + perror_reply(550, $8); + else + reply(200, "CHMOD command successful."); + } + if ($8 != NULL) + free($8); + } + | SITE SP check_login IDLE CRLF + { + if ($3) + reply(200, + "Current IDLE time limit is %d seconds; max %d.", + timeout, maxtimeout); + } + | SITE SP check_login IDLE SP NUMBER CRLF + { + if ($3) { + if ($6.i < 30 || $6.i > maxtimeout) { + reply(501, + "Maximum IDLE time must be between 30 and %d seconds.", + maxtimeout); + } else { + timeout = $6.i; + (void) alarm(timeout); + reply(200, + "Maximum IDLE time set to %d seconds.", + timeout); + } + } + } + | STOU check_login_ro SP pathname CRLF + { + if ($2 && $4 != NULL) + store($4, "w", 1); + if ($4 != NULL) + free($4); + } + | FEAT CRLF + { + lreply(211, "Extensions supported:"); +#if 0 + /* XXX these two keywords are non-standard */ + printf(" EPRT\r\n"); + if (!noepsv) + printf(" EPSV\r\n"); +#endif + printf(" MDTM\r\n"); + printf(" REST STREAM\r\n"); + printf(" SIZE\r\n"); + if (assumeutf8) { + /* TVFS requires UTF8, see RFC 3659 */ + printf(" TVFS\r\n"); + printf(" UTF8\r\n"); + } + reply(211, "End."); + } + | SYST check_login CRLF + { + if ($2) { + if (hostinfo) +#ifdef BSD + reply(215, "UNIX Type: L%d Version: BSD-%d", + CHAR_BIT, BSD); +#else /* BSD */ + reply(215, "UNIX Type: L%d", CHAR_BIT); +#endif /* BSD */ + else + reply(215, "UNKNOWN Type: L%d", CHAR_BIT); + } + } + + /* + * SIZE is not in RFC959, but Postel has blessed it and + * it will be in the updated RFC. + * + * Return size of file in a format suitable for + * using with RESTART (we just count bytes). + */ + | SIZE check_login SP pathname CRLF + { + if ($2 && $4 != NULL) + sizecmd($4); + if ($4 != NULL) + free($4); + } + + /* + * MDTM is not in RFC959, but Postel has blessed it and + * it will be in the updated RFC. + * + * Return modification time of file as an ISO 3307 + * style time. E.g. YYYYMMDDHHMMSS or YYYYMMDDHHMMSS.xxx + * where xxx is the fractional second (of any precision, + * not necessarily 3 digits) + */ + | MDTM check_login SP pathname CRLF + { + if ($2 && $4 != NULL) { + struct stat stbuf; + if (stat($4, &stbuf) < 0) + perror_reply(550, $4); + else if (!S_ISREG(stbuf.st_mode)) { + reply(550, "%s: not a plain file.", $4); + } else { + struct tm *t; + t = gmtime(&stbuf.st_mtime); + reply(213, + "%04d%02d%02d%02d%02d%02d", + 1900 + t->tm_year, + t->tm_mon+1, t->tm_mday, + t->tm_hour, t->tm_min, t->tm_sec); + } + } + if ($4 != NULL) + free($4); + } + | QUIT CRLF + { + reply(221, "Goodbye."); + dologout(0); + } + | NOTIMPL + { + nack($1); + } + | error + { + yyclearin; /* discard lookahead data */ + yyerrok; /* clear error condition */ + state = CMD; /* reset lexer state */ + } + ; +rcmd + : RNFR check_login_ro SP pathname CRLF + { + restart_point = 0; + if ($2 && $4) { + if (fromname) + free(fromname); + fromname = NULL; + if (renamefrom($4)) + fromname = $4; + else + free($4); + } else if ($4) { + free($4); + } + } + | REST check_login SP NUMBER CRLF + { + if ($2) { + if (fromname) + free(fromname); + fromname = NULL; + restart_point = $4.o; + reply(350, "Restarting at %jd. %s", + (intmax_t)restart_point, + "Send STORE or RETRIEVE to initiate transfer."); + } + } + ; + +username + : STRING + ; + +password + : /* empty */ + { + $$ = (char *)calloc(1, sizeof(char)); + } + | STRING + ; + +byte_size + : NUMBER + { + $$ = $1.i; + } + ; + +host_port + : NUMBER COMMA NUMBER COMMA NUMBER COMMA NUMBER COMMA + NUMBER COMMA NUMBER + { + char *a, *p; + + data_dest.su_len = sizeof(struct sockaddr_in); + data_dest.su_family = AF_INET; + p = (char *)&data_dest.su_sin.sin_port; + p[0] = $9.i; p[1] = $11.i; + a = (char *)&data_dest.su_sin.sin_addr; + a[0] = $1.i; a[1] = $3.i; a[2] = $5.i; a[3] = $7.i; + } + ; + +host_long_port + : NUMBER COMMA NUMBER COMMA NUMBER COMMA NUMBER COMMA + NUMBER COMMA NUMBER COMMA NUMBER COMMA NUMBER COMMA + NUMBER COMMA NUMBER COMMA NUMBER COMMA NUMBER COMMA + NUMBER COMMA NUMBER COMMA NUMBER COMMA NUMBER COMMA + NUMBER COMMA NUMBER COMMA NUMBER COMMA NUMBER COMMA + NUMBER + { + char *a, *p; + + memset(&data_dest, 0, sizeof(data_dest)); + data_dest.su_len = sizeof(struct sockaddr_in6); + data_dest.su_family = AF_INET6; + p = (char *)&data_dest.su_port; + p[0] = $39.i; p[1] = $41.i; + a = (char *)&data_dest.su_sin6.sin6_addr; + a[0] = $5.i; a[1] = $7.i; a[2] = $9.i; a[3] = $11.i; + a[4] = $13.i; a[5] = $15.i; a[6] = $17.i; a[7] = $19.i; + a[8] = $21.i; a[9] = $23.i; a[10] = $25.i; a[11] = $27.i; + a[12] = $29.i; a[13] = $31.i; a[14] = $33.i; a[15] = $35.i; + if (his_addr.su_family == AF_INET6) { + /* XXX more sanity checks! */ + data_dest.su_sin6.sin6_scope_id = + his_addr.su_sin6.sin6_scope_id; + } + if ($1.i != 6 || $3.i != 16 || $37.i != 2) + memset(&data_dest, 0, sizeof(data_dest)); + } + | NUMBER COMMA NUMBER COMMA NUMBER COMMA NUMBER COMMA + NUMBER COMMA NUMBER COMMA NUMBER COMMA NUMBER COMMA + NUMBER + { + char *a, *p; + + memset(&data_dest, 0, sizeof(data_dest)); + data_dest.su_sin.sin_len = sizeof(struct sockaddr_in); + data_dest.su_family = AF_INET; + p = (char *)&data_dest.su_port; + p[0] = $15.i; p[1] = $17.i; + a = (char *)&data_dest.su_sin.sin_addr; + a[0] = $5.i; a[1] = $7.i; a[2] = $9.i; a[3] = $11.i; + if ($1.i != 4 || $3.i != 4 || $13.i != 2) + memset(&data_dest, 0, sizeof(data_dest)); + } + ; + +form_code + : N + { + $$ = FORM_N; + } + | T + { + $$ = FORM_T; + } + | C + { + $$ = FORM_C; + } + ; + +type_code + : A + { + cmd_type = TYPE_A; + cmd_form = FORM_N; + } + | A SP form_code + { + cmd_type = TYPE_A; + cmd_form = $3; + } + | E + { + cmd_type = TYPE_E; + cmd_form = FORM_N; + } + | E SP form_code + { + cmd_type = TYPE_E; + cmd_form = $3; + } + | I + { + cmd_type = TYPE_I; + } + | L + { + cmd_type = TYPE_L; + cmd_bytesz = CHAR_BIT; + } + | L SP byte_size + { + cmd_type = TYPE_L; + cmd_bytesz = $3; + } + /* this is for a bug in the BBN ftp */ + | L byte_size + { + cmd_type = TYPE_L; + cmd_bytesz = $2; + } + ; + +struct_code + : F + { + $$ = STRU_F; + } + | R + { + $$ = STRU_R; + } + | P + { + $$ = STRU_P; + } + ; + +mode_code + : S + { + $$ = MODE_S; + } + | B + { + $$ = MODE_B; + } + | C + { + $$ = MODE_C; + } + ; + +pathname + : pathstring + { + if (logged_in && $1) { + char *p; + + /* + * Expand ~user manually since glob(3) + * will return the unexpanded pathname + * if the corresponding file/directory + * doesn't exist yet. Using sole glob(3) + * would break natural commands like + * MKD ~user/newdir + * or + * RNTO ~/newfile + */ + if ((p = exptilde($1)) != NULL) { + $$ = expglob(p); + free(p); + } else + $$ = NULL; + free($1); + } else + $$ = $1; + } + ; + +pathstring + : STRING + ; + +octal_number + : NUMBER + { + int ret, dec, multby, digit; + + /* + * Convert a number that was read as decimal number + * to what it would be if it had been read as octal. + */ + dec = $1.i; + multby = 1; + ret = 0; + while (dec) { + digit = dec%10; + if (digit > 7) { + ret = -1; + break; + } + ret += digit * multby; + multby *= 8; + dec /= 10; + } + $$ = ret; + } + ; + + +check_login + : /* empty */ + { + $$ = check_login1(); + } + ; + +check_login_epsv + : /* empty */ + { + if (noepsv) { + reply(500, "EPSV command disabled."); + $$ = 0; + } + else + $$ = check_login1(); + } + ; + +check_login_ro + : /* empty */ + { + if (readonly) { + reply(550, "Permission denied."); + $$ = 0; + } + else + $$ = check_login1(); + } + ; + +%% + +#define CMD 0 /* beginning of command */ +#define ARGS 1 /* expect miscellaneous arguments */ +#define STR1 2 /* expect SP followed by STRING */ +#define STR2 3 /* expect STRING */ +#define OSTR 4 /* optional SP then STRING */ +#define ZSTR1 5 /* optional SP then optional STRING */ +#define ZSTR2 6 /* optional STRING after SP */ +#define SITECMD 7 /* SITE command */ +#define NSTR 8 /* Number followed by a string */ + +#define MAXGLOBARGS 1000 + +#define MAXASIZE 10240 /* Deny ASCII SIZE on files larger than that */ + +struct tab { + char *name; + short token; + short state; + short implemented; /* 1 if command is implemented */ + char *help; +}; + +struct tab cmdtab[] = { /* In order defined in RFC 765 */ + { "USER", USER, STR1, 1, "<sp> username" }, + { "PASS", PASS, ZSTR1, 1, "[<sp> [password]]" }, + { "ACCT", ACCT, STR1, 0, "(specify account)" }, + { "SMNT", SMNT, ARGS, 0, "(structure mount)" }, + { "REIN", REIN, ARGS, 0, "(reinitialize server state)" }, + { "QUIT", QUIT, ARGS, 1, "(terminate service)", }, + { "PORT", PORT, ARGS, 1, "<sp> b0, b1, b2, b3, b4, b5" }, + { "LPRT", LPRT, ARGS, 1, "<sp> af, hal, h1, h2, h3,..., pal, p1, p2..." }, + { "EPRT", EPRT, STR1, 1, "<sp> |af|addr|port|" }, + { "PASV", PASV, ARGS, 1, "(set server in passive mode)" }, + { "LPSV", LPSV, ARGS, 1, "(set server in passive mode)" }, + { "EPSV", EPSV, ARGS, 1, "[<sp> af|ALL]" }, + { "TYPE", TYPE, ARGS, 1, "<sp> { A | E | I | L }" }, + { "STRU", STRU, ARGS, 1, "(specify file structure)" }, + { "MODE", MODE, ARGS, 1, "(specify transfer mode)" }, + { "RETR", RETR, STR1, 1, "<sp> file-name" }, + { "STOR", STOR, STR1, 1, "<sp> file-name" }, + { "APPE", APPE, STR1, 1, "<sp> file-name" }, + { "MLFL", MLFL, OSTR, 0, "(mail file)" }, + { "MAIL", MAIL, OSTR, 0, "(mail to user)" }, + { "MSND", MSND, OSTR, 0, "(mail send to terminal)" }, + { "MSOM", MSOM, OSTR, 0, "(mail send to terminal or mailbox)" }, + { "MSAM", MSAM, OSTR, 0, "(mail send to terminal and mailbox)" }, + { "MRSQ", MRSQ, OSTR, 0, "(mail recipient scheme question)" }, + { "MRCP", MRCP, STR1, 0, "(mail recipient)" }, + { "ALLO", ALLO, ARGS, 1, "allocate storage (vacuously)" }, + { "REST", REST, ARGS, 1, "<sp> offset (restart command)" }, + { "RNFR", RNFR, STR1, 1, "<sp> file-name" }, + { "RNTO", RNTO, STR1, 1, "<sp> file-name" }, + { "ABOR", ABOR, ARGS, 1, "(abort operation)" }, + { "DELE", DELE, STR1, 1, "<sp> file-name" }, + { "CWD", CWD, OSTR, 1, "[ <sp> directory-name ]" }, + { "XCWD", CWD, OSTR, 1, "[ <sp> directory-name ]" }, + { "LIST", LIST, OSTR, 1, "[ <sp> path-name ]" }, + { "NLST", NLST, OSTR, 1, "[ <sp> path-name ]" }, + { "SITE", SITE, SITECMD, 1, "site-cmd [ <sp> arguments ]" }, + { "SYST", SYST, ARGS, 1, "(get type of operating system)" }, + { "FEAT", FEAT, ARGS, 1, "(get extended features)" }, + { "STAT", STAT, OSTR, 1, "[ <sp> path-name ]" }, + { "HELP", HELP, OSTR, 1, "[ <sp> <string> ]" }, + { "NOOP", NOOP, ARGS, 1, "" }, + { "MKD", MKD, STR1, 1, "<sp> path-name" }, + { "XMKD", MKD, STR1, 1, "<sp> path-name" }, + { "RMD", RMD, STR1, 1, "<sp> path-name" }, + { "XRMD", RMD, STR1, 1, "<sp> path-name" }, + { "PWD", PWD, ARGS, 1, "(return current directory)" }, + { "XPWD", PWD, ARGS, 1, "(return current directory)" }, + { "CDUP", CDUP, ARGS, 1, "(change to parent directory)" }, + { "XCUP", CDUP, ARGS, 1, "(change to parent directory)" }, + { "STOU", STOU, STR1, 1, "<sp> file-name" }, + { "SIZE", SIZE, OSTR, 1, "<sp> path-name" }, + { "MDTM", MDTM, OSTR, 1, "<sp> path-name" }, + { NULL, 0, 0, 0, 0 } +}; + +struct tab sitetab[] = { + { "MD5", MDFIVE, STR1, 1, "[ <sp> file-name ]" }, + { "UMASK", UMASK, ARGS, 1, "[ <sp> umask ]" }, + { "IDLE", IDLE, ARGS, 1, "[ <sp> maximum-idle-time ]" }, + { "CHMOD", CHMOD, NSTR, 1, "<sp> mode <sp> file-name" }, + { "HELP", HELP, OSTR, 1, "[ <sp> <string> ]" }, + { NULL, 0, 0, 0, 0 } +}; + +static char *copy(char *); +static char *expglob(char *); +static char *exptilde(char *); +static void help(struct tab *, char *); +static struct tab * + lookup(struct tab *, char *); +static int port_check(const char *); +#ifdef INET6 +static int port_check_v6(const char *); +#endif +static void sizecmd(char *); +static void toolong(int); +#ifdef INET6 +static void v4map_data_dest(void); +#endif +static int yylex(void); + +static struct tab * +lookup(struct tab *p, char *cmd) +{ + + for (; p->name != NULL; p++) + if (strcmp(cmd, p->name) == 0) + return (p); + return (0); +} + +#include <arpa/telnet.h> + +/* + * get_line - a hacked up version of fgets to ignore TELNET escape codes. + */ +int +get_line(char *s, int n, FILE *iop) +{ + int c; + register char *cs; + sigset_t sset, osset; + + cs = s; +/* tmpline may contain saved command from urgent mode interruption */ + for (c = 0; tmpline[c] != '\0' && --n > 0; ++c) { + *cs++ = tmpline[c]; + if (tmpline[c] == '\n') { + *cs++ = '\0'; + if (ftpdebug) + syslog(LOG_DEBUG, "command: %s", s); + tmpline[0] = '\0'; + return(0); + } + if (c == 0) + tmpline[0] = '\0'; + } + /* SIGURG would interrupt stdio if not blocked during the read loop */ + sigemptyset(&sset); + sigaddset(&sset, SIGURG); + sigprocmask(SIG_BLOCK, &sset, &osset); + while ((c = getc(iop)) != EOF) { + c &= 0377; + if (c == IAC) { + if ((c = getc(iop)) == EOF) + goto got_eof; + c &= 0377; + switch (c) { + case WILL: + case WONT: + if ((c = getc(iop)) == EOF) + goto got_eof; + printf("%c%c%c", IAC, DONT, 0377&c); + (void) fflush(stdout); + continue; + case DO: + case DONT: + if ((c = getc(iop)) == EOF) + goto got_eof; + printf("%c%c%c", IAC, WONT, 0377&c); + (void) fflush(stdout); + continue; + case IAC: + break; + default: + continue; /* ignore command */ + } + } + *cs++ = c; + if (--n <= 0) { + /* + * If command doesn't fit into buffer, discard the + * rest of the command and indicate truncation. + * This prevents the command to be split up into + * multiple commands. + */ + while (c != '\n' && (c = getc(iop)) != EOF) + ; + return (-2); + } + if (c == '\n') + break; + } +got_eof: + sigprocmask(SIG_SETMASK, &osset, NULL); + if (c == EOF && cs == s) + return (-1); + *cs++ = '\0'; + if (ftpdebug) { + if (!guest && strncasecmp("pass ", s, 5) == 0) { + /* Don't syslog passwords */ + syslog(LOG_DEBUG, "command: %.5s ???", s); + } else { + register char *cp; + register int len; + + /* Don't syslog trailing CR-LF */ + len = strlen(s); + cp = s + len - 1; + while (cp >= s && (*cp == '\n' || *cp == '\r')) { + --cp; + --len; + } + syslog(LOG_DEBUG, "command: %.*s", len, s); + } + } + return (0); +} + +static void +toolong(int signo) +{ + + reply(421, + "Timeout (%d seconds): closing control connection.", timeout); + if (logging) + syslog(LOG_INFO, "User %s timed out after %d seconds", + (pw ? pw -> pw_name : "unknown"), timeout); + dologout(1); +} + +static int +yylex(void) +{ + static int cpos; + char *cp, *cp2; + struct tab *p; + int n; + char c; + + for (;;) { + switch (state) { + + case CMD: + (void) signal(SIGALRM, toolong); + (void) alarm(timeout); + n = get_line(cbuf, sizeof(cbuf)-1, stdin); + if (n == -1) { + reply(221, "You could at least say goodbye."); + dologout(0); + } else if (n == -2) { + reply(500, "Command too long."); + (void) alarm(0); + continue; + } + (void) alarm(0); +#ifdef SETPROCTITLE + if (strncasecmp(cbuf, "PASS", 4) != 0) + setproctitle("%s: %s", proctitle, cbuf); +#endif /* SETPROCTITLE */ + if ((cp = strchr(cbuf, '\r'))) { + *cp++ = '\n'; + *cp = '\0'; + } + if ((cp = strpbrk(cbuf, " \n"))) + cpos = cp - cbuf; + if (cpos == 0) + cpos = 4; + c = cbuf[cpos]; + cbuf[cpos] = '\0'; + upper(cbuf); + p = lookup(cmdtab, cbuf); + cbuf[cpos] = c; + if (p != 0) { + yylval.s = p->name; + if (!p->implemented) + return (NOTIMPL); /* state remains CMD */ + state = p->state; + return (p->token); + } + break; + + case SITECMD: + if (cbuf[cpos] == ' ') { + cpos++; + return (SP); + } + cp = &cbuf[cpos]; + if ((cp2 = strpbrk(cp, " \n"))) + cpos = cp2 - cbuf; + c = cbuf[cpos]; + cbuf[cpos] = '\0'; + upper(cp); + p = lookup(sitetab, cp); + cbuf[cpos] = c; + if (guest == 0 && p != 0) { + yylval.s = p->name; + if (!p->implemented) { + state = CMD; + return (NOTIMPL); + } + state = p->state; + return (p->token); + } + state = CMD; + break; + + case ZSTR1: + case OSTR: + if (cbuf[cpos] == '\n') { + state = CMD; + return (CRLF); + } + /* FALLTHROUGH */ + + case STR1: + dostr1: + if (cbuf[cpos] == ' ') { + cpos++; + state = state == OSTR ? STR2 : state+1; + return (SP); + } + break; + + case ZSTR2: + if (cbuf[cpos] == '\n') { + state = CMD; + return (CRLF); + } + /* FALLTHROUGH */ + + case STR2: + cp = &cbuf[cpos]; + n = strlen(cp); + cpos += n - 1; + /* + * Make sure the string is nonempty and \n terminated. + */ + if (n > 1 && cbuf[cpos] == '\n') { + cbuf[cpos] = '\0'; + yylval.s = copy(cp); + cbuf[cpos] = '\n'; + state = ARGS; + return (STRING); + } + break; + + case NSTR: + if (cbuf[cpos] == ' ') { + cpos++; + return (SP); + } + if (isdigit(cbuf[cpos])) { + cp = &cbuf[cpos]; + while (isdigit(cbuf[++cpos])) + ; + c = cbuf[cpos]; + cbuf[cpos] = '\0'; + yylval.u.i = atoi(cp); + cbuf[cpos] = c; + state = STR1; + return (NUMBER); + } + state = STR1; + goto dostr1; + + case ARGS: + if (isdigit(cbuf[cpos])) { + cp = &cbuf[cpos]; + while (isdigit(cbuf[++cpos])) + ; + c = cbuf[cpos]; + cbuf[cpos] = '\0'; + yylval.u.i = atoi(cp); + yylval.u.o = strtoull(cp, NULL, 10); + cbuf[cpos] = c; + return (NUMBER); + } + if (strncasecmp(&cbuf[cpos], "ALL", 3) == 0 + && !isalnum(cbuf[cpos + 3])) { + cpos += 3; + return ALL; + } + switch (cbuf[cpos++]) { + + case '\n': + state = CMD; + return (CRLF); + + case ' ': + return (SP); + + case ',': + return (COMMA); + + case 'A': + case 'a': + return (A); + + case 'B': + case 'b': + return (B); + + case 'C': + case 'c': + return (C); + + case 'E': + case 'e': + return (E); + + case 'F': + case 'f': + return (F); + + case 'I': + case 'i': + return (I); + + case 'L': + case 'l': + return (L); + + case 'N': + case 'n': + return (N); + + case 'P': + case 'p': + return (P); + + case 'R': + case 'r': + return (R); + + case 'S': + case 's': + return (S); + + case 'T': + case 't': + return (T); + + } + break; + + default: + fatalerror("Unknown state in scanner."); + } + state = CMD; + return (LEXERR); + } +} + +void +upper(char *s) +{ + while (*s != '\0') { + if (islower(*s)) + *s = toupper(*s); + s++; + } +} + +static char * +copy(char *s) +{ + char *p; + + p = malloc(strlen(s) + 1); + if (p == NULL) + fatalerror("Ran out of memory."); + (void) strcpy(p, s); + return (p); +} + +static void +help(struct tab *ctab, char *s) +{ + struct tab *c; + int width, NCMDS; + char *type; + + if (ctab == sitetab) + type = "SITE "; + else + type = ""; + width = 0, NCMDS = 0; + for (c = ctab; c->name != NULL; c++) { + int len = strlen(c->name); + + if (len > width) + width = len; + NCMDS++; + } + width = (width + 8) &~ 7; + if (s == 0) { + int i, j, w; + int columns, lines; + + lreply(214, "The following %scommands are recognized %s.", + type, "(* =>'s unimplemented)"); + columns = 76 / width; + if (columns == 0) + columns = 1; + lines = (NCMDS + columns - 1) / columns; + for (i = 0; i < lines; i++) { + printf(" "); + for (j = 0; j < columns; j++) { + c = ctab + j * lines + i; + printf("%s%c", c->name, + c->implemented ? ' ' : '*'); + if (c + lines >= &ctab[NCMDS]) + break; + w = strlen(c->name) + 1; + while (w < width) { + putchar(' '); + w++; + } + } + printf("\r\n"); + } + (void) fflush(stdout); + if (hostinfo) + reply(214, "Direct comments to ftp-bugs@%s.", hostname); + else + reply(214, "End."); + return; + } + upper(s); + c = lookup(ctab, s); + if (c == NULL) { + reply(502, "Unknown command %s.", s); + return; + } + if (c->implemented) + reply(214, "Syntax: %s%s %s", type, c->name, c->help); + else + reply(214, "%s%-*s\t%s; unimplemented.", type, width, + c->name, c->help); +} + +static void +sizecmd(char *filename) +{ + switch (type) { + case TYPE_L: + case TYPE_I: { + struct stat stbuf; + if (stat(filename, &stbuf) < 0) + perror_reply(550, filename); + else if (!S_ISREG(stbuf.st_mode)) + reply(550, "%s: not a plain file.", filename); + else + reply(213, "%jd", (intmax_t)stbuf.st_size); + break; } + case TYPE_A: { + FILE *fin; + int c; + off_t count; + struct stat stbuf; + fin = fopen(filename, "r"); + if (fin == NULL) { + perror_reply(550, filename); + return; + } + if (fstat(fileno(fin), &stbuf) < 0) { + perror_reply(550, filename); + (void) fclose(fin); + return; + } else if (!S_ISREG(stbuf.st_mode)) { + reply(550, "%s: not a plain file.", filename); + (void) fclose(fin); + return; + } else if (stbuf.st_size > MAXASIZE) { + reply(550, "%s: too large for type A SIZE.", filename); + (void) fclose(fin); + return; + } + + count = 0; + while((c=getc(fin)) != EOF) { + if (c == '\n') /* will get expanded to \r\n */ + count++; + count++; + } + (void) fclose(fin); + + reply(213, "%jd", (intmax_t)count); + break; } + default: + reply(504, "SIZE not implemented for type %s.", + typenames[type]); + } +} + +/* Return 1, if port check is done. Return 0, if not yet. */ +static int +port_check(const char *pcmd) +{ + if (his_addr.su_family == AF_INET) { + if (data_dest.su_family != AF_INET) { + usedefault = 1; + reply(500, "Invalid address rejected."); + return 1; + } + if (paranoid && + ((ntohs(data_dest.su_port) < IPPORT_RESERVED) || + memcmp(&data_dest.su_sin.sin_addr, + &his_addr.su_sin.sin_addr, + sizeof(data_dest.su_sin.sin_addr)))) { + usedefault = 1; + reply(500, "Illegal PORT range rejected."); + } else { + usedefault = 0; + if (pdata >= 0) { + (void) close(pdata); + pdata = -1; + } + reply(200, "%s command successful.", pcmd); + } + return 1; + } + return 0; +} + +static int +check_login1(void) +{ + if (logged_in) + return 1; + else { + reply(530, "Please login with USER and PASS."); + return 0; + } +} + +/* + * Replace leading "~user" in a pathname by the user's login directory. + * Returned string will be in a freshly malloced buffer unless it's NULL. + */ +static char * +exptilde(char *s) +{ + char *p, *q; + char *path, *user; + struct passwd *ppw; + + if ((p = strdup(s)) == NULL) + return (NULL); + if (*p != '~') + return (p); + + user = p + 1; /* skip tilde */ + if ((path = strchr(p, '/')) != NULL) + *(path++) = '\0'; /* separate ~user from the rest of path */ + if (*user == '\0') /* no user specified, use the current user */ + user = pw->pw_name; + /* read passwd even for the current user since we may be chrooted */ + if ((ppw = getpwnam(user)) != NULL) { + /* user found, substitute login directory for ~user */ + if (path) + asprintf(&q, "%s/%s", ppw->pw_dir, path); + else + q = strdup(ppw->pw_dir); + free(p); + p = q; + } else { + /* user not found, undo the damage */ + if (path) + path[-1] = '/'; + } + return (p); +} + +/* + * Expand glob(3) patterns possibly present in a pathname. + * Avoid expanding to a pathname including '\r' or '\n' in order to + * not disrupt the FTP protocol. + * The expansion found must be unique. + * Return the result as a malloced string, or NULL if an error occurred. + * + * Problem: this production is used for all pathname + * processing, but only gives a 550 error reply. + * This is a valid reply in some cases but not in others. + */ +static char * +expglob(char *s) +{ + char *p, **pp, *rval; + int flags = GLOB_BRACE | GLOB_NOCHECK; + int n; + glob_t gl; + + memset(&gl, 0, sizeof(gl)); + flags |= GLOB_LIMIT; + gl.gl_matchc = MAXGLOBARGS; + if (glob(s, flags, NULL, &gl) == 0 && gl.gl_pathc != 0) { + for (pp = gl.gl_pathv, p = NULL, n = 0; *pp; pp++) + if (*(*pp + strcspn(*pp, "\r\n")) == '\0') { + p = *pp; + n++; + } + if (n == 0) + rval = strdup(s); + else if (n == 1) + rval = strdup(p); + else { + reply(550, "Wildcard is ambiguous."); + rval = NULL; + } + } else { + reply(550, "Wildcard expansion error."); + rval = NULL; + } + globfree(&gl); + return (rval); +} + +#ifdef INET6 +/* Return 1, if port check is done. Return 0, if not yet. */ +static int +port_check_v6(const char *pcmd) +{ + if (his_addr.su_family == AF_INET6) { + if (IN6_IS_ADDR_V4MAPPED(&his_addr.su_sin6.sin6_addr)) + /* Convert data_dest into v4 mapped sockaddr.*/ + v4map_data_dest(); + if (data_dest.su_family != AF_INET6) { + usedefault = 1; + reply(500, "Invalid address rejected."); + return 1; + } + if (paranoid && + ((ntohs(data_dest.su_port) < IPPORT_RESERVED) || + memcmp(&data_dest.su_sin6.sin6_addr, + &his_addr.su_sin6.sin6_addr, + sizeof(data_dest.su_sin6.sin6_addr)))) { + usedefault = 1; + reply(500, "Illegal PORT range rejected."); + } else { + usedefault = 0; + if (pdata >= 0) { + (void) close(pdata); + pdata = -1; + } + reply(200, "%s command successful.", pcmd); + } + return 1; + } + return 0; +} + +static void +v4map_data_dest(void) +{ + struct in_addr savedaddr; + int savedport; + + if (data_dest.su_family != AF_INET) { + usedefault = 1; + reply(500, "Invalid address rejected."); + return; + } + + savedaddr = data_dest.su_sin.sin_addr; + savedport = data_dest.su_port; + + memset(&data_dest, 0, sizeof(data_dest)); + data_dest.su_sin6.sin6_len = sizeof(struct sockaddr_in6); + data_dest.su_sin6.sin6_family = AF_INET6; + data_dest.su_sin6.sin6_port = savedport; + memset((caddr_t)&data_dest.su_sin6.sin6_addr.s6_addr[10], 0xff, 2); + memcpy((caddr_t)&data_dest.su_sin6.sin6_addr.s6_addr[12], + (caddr_t)&savedaddr, sizeof(savedaddr)); +} +#endif diff --git a/libexec/ftpd/ftpd.8 b/libexec/ftpd/ftpd.8 new file mode 100644 index 000000000000..96db4753209e --- /dev/null +++ b/libexec/ftpd/ftpd.8 @@ -0,0 +1,589 @@ +.\" Copyright (c) 1985, 1988, 1991, 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. 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. +.\" +.Dd June 26, 2025 +.Dt FTPD 8 +.Os +.Sh NAME +.Nm ftpd +.Nd Internet File Transfer Protocol server +.Sh SYNOPSIS +.Nm +.Op Fl 468BDdEhMmOoRrSUvW +.Bq Fl A | Fl n +.Op Fl l Op Fl l +.Op Fl a Ar address +.Op Fl P Ar port +.Op Fl p Ar file +.Op Fl T Ar maxtimeout +.Op Fl t Ar timeout +.Op Fl u Ar umask +.Sh DEPRECATION NOTICE +The +.Fx +base system +.Nm +is deprecated, and will be removed in +.Fx 15.0 . +Users are advised to install the +.Pa ftp/freebsd-ftpd +port or package instead. +.Sh DESCRIPTION +The +.Nm +utility is the +Internet File Transfer Protocol +server process. +The server uses the TCP protocol +and listens at the port specified with the +.Fl P +option or in the +.Dq ftp +service specification; see +.Xr services 5 . +.Pp +Available options: +.Bl -tag -width indent +.It Fl 4 +When +.Fl D +is specified, accept connections via +.Dv AF_INET +socket. +.It Fl 6 +When +.Fl D +is specified, accept connections via +.Dv AF_INET6 +socket. +.It Fl 8 +Enable transparent UTF-8 mode. +RFC\ 2640 compliant clients will be told that the character encoding +used by the server is UTF-8, which is the only effect of the option. +.Pp +This option does not enable any encoding conversion for server file names; +it implies instead that the names of files on the server are encoded +in UTF-8. +As for files uploaded via FTP, it is the duty of the RFC\ 2640 compliant +client to convert their names from the client's local encoding to UTF-8. +FTP command names and own +.Nm +messages are always encoded in ASCII, which is a subset of UTF-8. +Hence no need for server-side conversion at all. +.It Fl A +Allow only anonymous ftp access. +.It Fl a +When +.Fl D +is specified, accept connections only on the specified +.Ar address . +.It Fl B +With this option set, +.Nm +sends authentication success and failure messages to the +.Xr blacklistd 8 +daemon. +If this option is not specified, no communcation with the +.Xr blacklistd 8 +daemon is attempted. +.It Fl D +With this option set, +.Nm +will detach and become a daemon, accepting connections on the FTP port and +forking children processes to handle them. +This is lower overhead than starting +.Nm +from +.Xr inetd 8 +and is thus useful on busy servers to reduce load. +.It Fl d +Debugging information is written to the syslog using +.Dv LOG_FTP . +.It Fl E +Disable the EPSV command. +This is useful for servers behind older firewalls. +.It Fl h +Disable printing host-specific information, such as the +server software version or hostname, in server messages. +.It Fl l +Each successful and failed +.Xr ftp 1 +session is logged using syslog with a facility of +.Dv LOG_FTP . +If this option is specified twice, the retrieve (get), store (put), append, +delete, make directory, remove directory and rename operations and +their filename arguments are also logged. +By default, +.Xr syslogd 8 +logs these to +.Pa /var/log/xferlog . +.It Fl M +Prevent anonymous users from creating directories. +.It Fl m +Permit anonymous users to overwrite or modify +existing files if allowed by file system permissions. +By default, anonymous users cannot modify existing files; +in particular, files to upload will be created under a unique name. +.It Fl n +Disable anonymous FTP access. +The +.Fl n +option is mutually exclusive with the +.Fl A +option. +.It Fl O +Put server in write-only mode for anonymous users only. +RETR is disabled for anonymous users, preventing anonymous downloads. +This has no effect if +.Fl o +is also specified. +.It Fl o +Put server in write-only mode. +RETR is disabled, preventing downloads. +.It Fl P +When +.Fl D +is specified, accept connections at +.Ar port , +specified as a numeric value or service name, instead of at the default +.Dq ftp +port. +.It Fl p +When +.Fl D +is specified, write the daemon's process ID to +.Ar file +instead of the default pid file, +.Pa /var/run/ftpd.pid . +.It Fl R +With this option set, +.Nm +will revert to historical behavior with regard to security checks on +user operations and restrictions on PORT requests. +Currently, +.Nm +will only honor PORT commands directed to unprivileged ports on the +remote user's host (which violates the FTP protocol specification but +closes some security holes). +.It Fl r +Put server in read-only mode. +All commands which may modify the local file system are disabled. +.It Fl S +With this option set, +.Nm +logs all anonymous file downloads to the file +.Pa /var/log/ftpd +when this file exists. +.It Fl T +A client may also request a different timeout period; +the maximum period allowed may be set to +.Ar timeout +seconds with the +.Fl T +option. +The default limit is 2 hours. +.It Fl t +The inactivity timeout period is set to +.Ar timeout +seconds (the default is 15 minutes). +.It Fl U +This option instructs ftpd to use data ports in the range of +.Dv IP_PORTRANGE_DEFAULT +instead of in the range of +.Dv IP_PORTRANGE_HIGH . +Such a change may be useful for some specific firewall configurations; +see +.Xr ip 4 +for more information. +.Pp +Note that option is a virtual no-op in +.Fx 5.0 +and above; both port +ranges are identical by default. +.It Fl u +The default file creation mode mask is set to +.Ar umask , +which is expected to be an octal numeric value. +Refer to +.Xr umask 2 +for details. +This option may be overridden by +.Xr login.conf 5 . +.It Fl v +A synonym for +.Fl d . +.It Fl W +Do not log FTP sessions to the user accounting database. +.El +.Pp +The file +.Pa /var/run/nologin +can be used to disable ftp access. +If the file exists, +.Nm +displays it and exits. +If the file +.Pa /etc/ftpwelcome +exists, +.Nm +prints it before issuing the +.Dq ready +message. +If the file +.Pa /etc/ftpmotd +exists, +.Nm +prints it after a successful login. +Note the motd file used is the one +relative to the login environment. +This means the one in +.Pa ~ftp/etc +in the anonymous user's case. +.Pp +The ftp server currently supports the following ftp requests. +The case of the requests is ignored. +Requests marked [RW] are +disabled if +.Fl r +is specified. +.Bl -column "Request" -offset indent +.It Sy Request Ta Sy "Description" +.It ABOR Ta "abort previous command" +.It ACCT Ta "specify account (ignored)" +.It ALLO Ta "allocate storage (vacuously)" +.It APPE Ta "append to a file [RW]" +.It CDUP Ta "change to parent of current working directory" +.It CWD Ta "change working directory" +.It DELE Ta "delete a file [RW]" +.It EPRT Ta "specify data connection port, multiprotocol" +.It EPSV Ta "prepare for server-to-server transfer, multiprotocol" +.It FEAT Ta "give information on extended features of server" +.It HELP Ta "give help information" +.It LIST Ta "give list files in a directory" Pq Dq Li "ls -lA" +.It LPRT Ta "specify data connection port, multiprotocol" +.It LPSV Ta "prepare for server-to-server transfer, multiprotocol" +.It MDTM Ta "show last modification time of file" +.It MKD Ta "make a directory [RW]" +.It MODE Ta "specify data transfer" Em mode +.It NLST Ta "give name list of files in directory" +.It NOOP Ta "do nothing" +.It PASS Ta "specify password" +.It PASV Ta "prepare for server-to-server transfer" +.It PORT Ta "specify data connection port" +.It PWD Ta "print the current working directory" +.It QUIT Ta "terminate session" +.It REST Ta "restart incomplete transfer" +.It RETR Ta "retrieve a file" +.It RMD Ta "remove a directory [RW]" +.It RNFR Ta "specify rename-from file name [RW]" +.It RNTO Ta "specify rename-to file name [RW]" +.It SITE Ta "non-standard commands (see next section)" +.It SIZE Ta "return size of file" +.It STAT Ta "return status of server" +.It STOR Ta "store a file [RW]" +.It STOU Ta "store a file with a unique name [RW]" +.It STRU Ta "specify data transfer" Em structure +.It SYST Ta "show operating system type of server system" +.It TYPE Ta "specify data transfer" Em type +.It USER Ta "specify user name" +.It XCUP Ta "change to parent of current working directory (deprecated)" +.It XCWD Ta "change working directory (deprecated)" +.It XMKD Ta "make a directory (deprecated) [RW]" +.It XPWD Ta "print the current working directory (deprecated)" +.It XRMD Ta "remove a directory (deprecated) [RW]" +.El +.Pp +The following non-standard or +.Ux +specific commands are supported +by the +SITE request. +.Bl -column Request -offset indent +.It Sy Request Ta Sy Description +.It UMASK Ta change umask, e.g. ``SITE UMASK 002'' +.It IDLE Ta set idle-timer, e.g. ``SITE IDLE 60'' +.It CHMOD Ta "change mode of a file [RW], e.g. ``SITE CHMOD 755 filename''" +.It MD5 Ta "report the files MD5 checksum, e.g. ``SITE MD5 filename''" +.It HELP Ta give help information +.El +.Pp +Note: SITE requests are disabled in case of anonymous logins. +.Pp +The remaining ftp requests specified in Internet RFC 959 +are +recognized, but not implemented. +MDTM and SIZE are not specified in RFC 959, but will appear in the +next updated FTP RFC. +To avoid possible denial-of-service attacks, SIZE requests against +files larger than 10240 bytes will be denied if the current transfer +type is ASCII. +.Pp +The ftp server will abort an active file transfer only when the +ABOR +command is preceded by a Telnet "Interrupt Process" (IP) +signal and a Telnet "Synch" signal in the command Telnet stream, +as described in Internet RFC 959. +If a +STAT +command is received during a data transfer, preceded by a Telnet IP +and Synch, transfer status will be returned. +.Pp +The +.Nm +utility interprets file names according to the +.Dq globbing +conventions used by +.Xr csh 1 . +This allows users to utilize the metacharacters +.Dq Li \&*?[]{}~ . +.Pp +The +.Nm +utility authenticates users according to six rules. +.Bl -enum -offset indent +.It +The login name must be in the password data base +and not have a null password. +In this case a password must be provided by the client before any +file operations may be performed. +.It +The login name must not appear in the file +.Pa /etc/ftpusers . +.It +The login name must not be a member of a group specified in the file +.Pa /etc/ftpusers . +Entries in this file interpreted as group names are prefixed by an "at" +.Ql \&@ +sign. +.It +The user must have a standard shell returned by +.Xr getusershell 3 . +.It +If the user name appears in the file +.Pa /etc/ftpchroot , +or the user is a member of a group with a group entry in this file, +i.e., one prefixed with +.Ql \&@ , +the session's root will be changed to the directory specified +in this file or to the user's login directory by +.Xr chroot 2 +as for an +.Dq anonymous +or +.Dq ftp +account (see next item). +See +.Xr ftpchroot 5 +for a detailed description of the format of this file. +This facility may also be triggered by enabling the boolean "ftp-chroot" +capability in +.Xr login.conf 5 . +However, the user must still supply a password. +This feature is intended as a compromise between a fully anonymous +account and a fully privileged account. +The account should also be set up as for an anonymous account. +.It +If the user name is +.Dq anonymous +or +.Dq ftp , +an +anonymous ftp account must be present in the password +file (user +.Dq ftp ) . +In this case the user is allowed +to log in by specifying any password (by convention an email address for +the user should be used as the password). +When the +.Fl S +option is set, all transfers are logged as well. +.El +.Pp +In the last case, +.Nm +takes special measures to restrict the client's access privileges. +The server performs a +.Xr chroot 2 +to the home directory of the +.Dq ftp +user. +As a special case if the +.Dq ftp +user's home directory pathname contains the +.Pa /./ +separator, +.Nm +uses its left-hand side as the name of the directory to do +.Xr chroot 2 +to, and its right-hand side to change the current directory to afterwards. +A typical example for this case would be +.Pa /var/spool/ftp/./pub . +In order that system security is not breached, it is recommended +that the +.Dq ftp +subtree be constructed with care, following these rules: +.Bl -tag -width "~ftp/pub" -offset indent +.It Pa ~ftp +Make the home directory owned by +.Dq root +and unwritable by anyone. +.It Pa ~ftp/etc +Make this directory owned by +.Dq root +and unwritable by anyone (mode 555). +The files pwd.db (see +.Xr passwd 5 ) +and +.Xr group 5 +must be present for the +.Xr ls 1 +command to be able to produce owner names rather than numbers. +The password field in +.Xr passwd 5 +is not used, and should not contain real passwords. +The file +.Pa ftpmotd , +if present, will be printed after a successful login. +These files should be mode 444. +.It Pa ~ftp/pub +This directory and the subdirectories beneath it should be owned +by the users and groups responsible for placing files in them, +and be writable only by them (mode 755 or 775). +They should +.Em not +be owned or writable by +.Dq ftp +or its group, otherwise guest users +can fill the drive with unwanted files. +.El +.Pp +If the system has multiple IP addresses, +.Nm +supports the idea of virtual hosts, which provides the ability to +define multiple anonymous ftp areas, each one allocated to a different +internet address. +The file +.Pa /etc/ftphosts +contains information pertaining to each of the virtual hosts. +Each host is defined on its own line which contains a number of +fields separated by whitespace: +.Bl -tag -offset indent -width hostname +.It hostname +Contains the hostname or IP address of the virtual host. +.It user +Contains a user record in the system password file. +As with normal anonymous ftp, this user's access uid, gid and group +memberships determine file access to the anonymous ftp area. +The anonymous ftp area (to which any user is chrooted on login) +is determined by the home directory defined for the account. +User id and group for any ftp account may be the same as for the +standard ftp user. +.It statfile +File to which all file transfers are logged, which +defaults to +.Pa /var/log/ftpd . +.It welcome +This file is the welcome message displayed before the server ready +prompt. +It defaults to +.Pa /etc/ftpwelcome . +.It motd +This file is displayed after the user logs in. +It defaults to +.Pa /etc/ftpmotd . +.El +.Pp +Lines beginning with a '#' are ignored and can be used to include +comments. +.Pp +Defining a virtual host for the primary IP address or hostname +changes the default for ftp logins to that address. +The 'user', 'statfile', 'welcome' and 'motd' fields may be left +blank, or a single hyphen '-' used to indicate that the default +value is to be used. +.Pp +As with any anonymous login configuration, due care must be given +to setup and maintenance to guard against security related problems. +.Pp +The +.Nm +utility has internal support for handling remote requests to list +files, and will not execute +.Pa /bin/ls +in either a chrooted or non-chrooted environment. +The +.Pa ~/bin/ls +executable need not be placed into the chrooted tree, nor need the +.Pa ~/bin +directory exist. +.Sh FILES +.Bl -tag -width ".Pa /var/run/ftpd.pid" -compact +.It Pa /etc/ftpusers +List of unwelcome/restricted users. +.It Pa /etc/ftpchroot +List of normal users who should be chroot'd. +.It Pa /etc/ftphosts +Virtual hosting configuration file. +.It Pa /etc/ftpwelcome +Welcome notice. +.It Pa /etc/ftpmotd +Welcome notice after login. +.It Pa /var/run/ftpd.pid +Default pid file for daemon mode. +.It Pa /var/run/nologin +Displayed and access refused. +.It Pa /var/log/ftpd +Log file for anonymous transfers. +.It Pa /var/log/xferlog +Default place for session logs. +.It Pa /var/spool/ftp +Recommended directory for the FTP root directory +(the home directory of the ftp user). +.El +.Sh SEE ALSO +.Xr ftp 1 , +.Xr umask 2 , +.Xr getusershell 3 , +.Xr ftpchroot 5 , +.Xr login.conf 5 , +.Xr inetd 8 , +.Xr syslogd 8 +.Sh HISTORY +The +.Nm +utility appeared in +.Bx 4.2 . +IPv6 support was added in WIDE Hydrangea IPv6 stack kit. +.Sh BUGS +The server must run as the super-user +to create sockets with privileged port numbers. +It maintains +an effective user id of the logged in user, reverting to +the super-user only when binding addresses to sockets. +The +possible security holes have been extensively +scrutinized, but are possibly incomplete. diff --git a/libexec/ftpd/ftpd.c b/libexec/ftpd/ftpd.c new file mode 100644 index 000000000000..751d77b218b7 --- /dev/null +++ b/libexec/ftpd/ftpd.c @@ -0,0 +1,3446 @@ +/*- + * SPDX-License-Identifier: BSD-3-Clause + * + * 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. 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. + */ + +/* + * FTP server. + */ +#include <sys/param.h> +#include <sys/ioctl.h> +#include <sys/mman.h> +#include <sys/socket.h> +#include <sys/stat.h> +#include <sys/time.h> +#include <sys/wait.h> + +#include <netinet/in.h> +#include <netinet/in_systm.h> +#include <netinet/ip.h> +#include <netinet/tcp.h> + +#define FTP_NAMES +#include <arpa/ftp.h> +#include <arpa/inet.h> +#include <arpa/telnet.h> + +#include <ctype.h> +#include <dirent.h> +#include <err.h> +#include <errno.h> +#include <fcntl.h> +#include <glob.h> +#include <limits.h> +#include <netdb.h> +#include <pwd.h> +#include <grp.h> +#include <signal.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <syslog.h> +#include <time.h> +#include <unistd.h> +#include <libutil.h> +#ifdef LOGIN_CAP +#include <login_cap.h> +#endif + +#ifdef USE_PAM +#include <security/pam_appl.h> +#endif + +#include "blacklist_client.h" +#include "pathnames.h" +#include "extern.h" + +#include <stdarg.h> + +static char version[] = "Version 6.00LS"; +#undef main + +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 noanon = 0; /* disable anonymous ftp */ +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. */ +int use_blacklist = 0; + +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 wtmpid[20]; + +#ifdef USE_PAM +static int auth_pam(struct passwd**, const char*); +pam_handle_t *pamh = NULL; +#endif + +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 +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 **, int *); +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, s = STDIN_FILENO; + 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; + + /* + * 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:ABdDEhlmMnoOp: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 'B': +#ifdef USE_BLACKLIST + use_blacklist = 1; +#else + syslog(LOG_WARNING, "not compiled with USE_BLACKLIST support"); +#endif + 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 'n': + noanon = 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 (noanon && anon_only) { + syslog(LOG_ERR, "-n and -A are mutually exclusive"); + exit(1); + } + + /* handle filesize limit gracefully */ + sa.sa_handler = SIG_IGN; + (void)sigaction(SIGXFSZ, &sa, NULL); + + 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, s); + (void) dup2(fd, STDOUT_FILENO); + (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(s, (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(s, (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(s, 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(s, 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); + + (void)snprintf(wtmpid, sizeof(wtmpid), "%xftpd", getpid()); + + /* Try to handle urgent data inline */ +#ifdef SO_OOBINLINE + if (setsockopt(s, SOL_SOCKET, SO_OOBINLINE, &on, sizeof(on)) < 0) + syslog(LOG_WARNING, "control setsockopt (SO_OOBINLINE): %m"); +#endif + +#ifdef F_SETOWN + if (fcntl(s, 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."); + BLACKLIST_INIT(); + 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_class); + 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_class = sgetsave(p->pw_class); + 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) +{ + int ecode; + 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 (!noanon && + (strcmp(name, "ftp") == 0 || strcmp(name, "anonymous") == 0)) { + if (checkuser(_PATH_FTPUSERS, "ftp", 0, NULL, &ecode) || + (ecode != 0 && ecode != ENOENT)) + reply(530, "User %s access denied.", name); + else if (checkuser(_PATH_FTPUSERS, "anonymous", 0, NULL, &ecode) || + (ecode != 0 && ecode != ENOENT)) + 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, &ecode) || + (ecode != 0 && ecode != ENOENT))) { + 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) + strlcpy(curname, name, sizeof(curname)); + + 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, int *ecode) +{ + FILE *fd; + int found = 0; + size_t len; + char *line, *mp, *p; + + if (ecode != NULL) + *ecode = 0; + 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); + } else if (ecode != NULL) + *ecode = errno; + 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); +#ifdef LOGIN_CAP + setusercontext(NULL, getpwuid(0), 0, LOGIN_SETALL & ~(LOGIN_SETLOGIN | + LOGIN_SETUSER | LOGIN_SETGROUP | LOGIN_SETPATH | + LOGIN_SETENV)); +#endif + if (logged_in && dowtmp) + ftpd_logwtmp(wtmpid, NULL, NULL); + pw = NULL; +#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, ecode; + 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) { + goto skip; + } +#endif + xpasswd = crypt(passwd, pw->pw_passwd); + if (passwd[0] == '\0' && pw->pw_passwd[0] != '\0') + xpasswd = ":"; + rval = strcmp(pw->pw_passwd, xpasswd); + if (pw->pw_expire && time(NULL) >= pw->pw_expire) + rval = 1; /* failure */ +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."); + BLACKLIST_NOTIFY(BLACKLIST_AUTH_FAIL, STDIN_FILENO, "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; + } else { + BLACKLIST_NOTIFY(BLACKLIST_AUTH_OK, STDIN_FILENO, "Login successful"); + } + } + 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_SETALL & + ~(LOGIN_SETRESOURCES | LOGIN_SETUSER | LOGIN_SETPATH | LOGIN_SETENV)); +#else + 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 + + dochroot = + checkuser(_PATH_FTPCHROOT, pw->pw_name, 1, &residue, &ecode) +#ifdef LOGIN_CAP /* Allow login.conf configuration as well */ + || login_getcapbool(lc, "ftp-chroot", 0) +#endif + ; + /* + * It is possible that checkuser() failed to open the chroot file. + * If this is the case, report that logins are un-available, since we + * have no way of checking whether or not the user should be chrooted. + * We ignore ENOENT since it is not required that this file be present. + */ + if (ecode != 0 && ecode != ENOENT) { + reply(530, "Login not available right now."); + return; + } + chrootdir = NULL; + + /* Disable wtmp logging when chrooting. */ + if (dochroot || guest) + dowtmp = 0; + if (dowtmp) + ftpd_logwtmp(wtmpid, pw->pw_name, + (struct sockaddr *)&his_addr); + logged_in = 1; + +#ifdef LOGIN_CAP + setusercontext(lc, pw, 0, LOGIN_SETRESOURCES); +#endif + + 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; + } + + /* + * 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."); + } + if (guest || dochroot) { + /* + * 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; + } + __FreeBSD_libc_enter_restricted_mode(); + } 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) { + if (guest || dochroot) { + fatalerror("Can't set uid."); + } else { + reply(550, "Can't set uid."); + goto bad; + } + } + /* + * Do not allow the session to live if we're chroot()'ed and chdir() + * fails. Otherwise the chroot jail can be escaped. + */ + if (chdir(homedir) < 0) { + if (guest || dochroot) { + fatalerror("Can't change to base directory."); + } 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; + char line[BUFSIZ]; + + if (cmd == 0) { + fin = fopen(name, "r"), closefunc = fclose; + st.st_size = 0; + } else { + (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), + " (%jd 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) + +/* + * Transfer 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); + + 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, "%s", msg); + return (0); + } + +oldway: + 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 " -lA %s", filename); + fin = ftpd_popen(line, "r"); + if (fin == NULL) { + perror_reply(551, filename); + return; + } + 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); +#ifdef LOGIN_CAP + setusercontext(NULL, getpwuid(0), 0, LOGIN_SETALL & ~(LOGIN_SETLOGIN | + LOGIN_SETUSER | LOGIN_SETGROUP | LOGIN_SETPATH | + LOGIN_SETENV)); +#endif + ftpd_logwtmp(wtmpid, NULL, 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; + int ret; + + if (!transflag) { + syslog(LOG_ERR, "Internal: myoob() while no transfer"); + return (0); + } + cp = tmpline; + ret = get_line(cp, 7, stdin); + if (ret == -1) { + reply(221, "You could at least say goodbye."); + dologout(0); + } else if (ret == -2) { + /* Ignore truncated command. */ + return (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: %jd of %jd bytes transferred.", + (intmax_t)byte_count, (intmax_t)file_size); + else + reply(213, "Status: %jd 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; + 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]; + 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; + v4_reply: + 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; + 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; + } + } 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<null> 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); + + if (dir->d_name[0] == '.' && dir->d_namlen == 1) + continue; + if (dir->d_name[0] == '.' && dir->d_name[1] == '.' && + dir->d_namlen == 2) + continue; + + 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); +} + +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, " = %jd 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!%jd!%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"); + 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"); + } + 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/libexec/ftpd/ftpusers b/libexec/ftpd/ftpusers new file mode 100644 index 000000000000..2e6f80a5968c --- /dev/null +++ b/libexec/ftpd/ftpusers @@ -0,0 +1,28 @@ +# +# list of users disallowed any ftp access. +# read by ftpd(8). +root +toor +daemon +operator +bin +tty +kmem +games +news +ntpd +man +sshd +smmsp +mailnull +bind +unbound +proxy +_pflogd +_dhcp +uucp +pop +auditdistd +www +hast +nobody diff --git a/libexec/ftpd/logwtmp.c b/libexec/ftpd/logwtmp.c new file mode 100644 index 000000000000..85db1f13419d --- /dev/null +++ b/libexec/ftpd/logwtmp.c @@ -0,0 +1,70 @@ +/*- + * SPDX-License-Identifier: BSD-3-Clause + * + * 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. 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. + */ + +#include <sys/types.h> +#include <sys/stat.h> +#include <netinet/in.h> +#include <arpa/inet.h> +#include <sys/socket.h> + +#include <libutil.h> +#include <stdio.h> +#include <string.h> +#include <unistd.h> +#include <utmpx.h> +#include "extern.h" + +void +ftpd_logwtmp(char *id, char *user, struct sockaddr *addr) +{ + struct utmpx ut; + + memset(&ut, 0, sizeof(ut)); + + if (user != NULL) { + /* Log in. */ + ut.ut_type = USER_PROCESS; + (void)strncpy(ut.ut_user, user, sizeof(ut.ut_user)); + if (addr != NULL) + realhostname_sa(ut.ut_host, sizeof(ut.ut_host), + addr, addr->sa_len); + } else { + /* Log out. */ + ut.ut_type = DEAD_PROCESS; + } + + ut.ut_pid = getpid(); + gettimeofday(&ut.ut_tv, NULL); + (void)strncpy(ut.ut_id, id, sizeof(ut.ut_id)); + (void)strncpy(ut.ut_line, "ftpd", sizeof(ut.ut_line)); + + pututxline(&ut); +} diff --git a/libexec/ftpd/pathnames.h b/libexec/ftpd/pathnames.h new file mode 100644 index 000000000000..1ff753123b1c --- /dev/null +++ b/libexec/ftpd/pathnames.h @@ -0,0 +1,39 @@ +/*- + * SPDX-License-Identifier: BSD-3-Clause + * + * 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. 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. + */ + +#include <paths.h> + +#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/libexec/ftpd/popen.c b/libexec/ftpd/popen.c new file mode 100644 index 000000000000..5f48356a376f --- /dev/null +++ b/libexec/ftpd/popen.c @@ -0,0 +1,193 @@ +/*- + * SPDX-License-Identifier: BSD-3-Clause + * + * 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. 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. + */ + +#include <sys/types.h> +#include <sys/wait.h> +#include <netinet/in.h> + +#include <errno.h> +#include <glob.h> +#include <signal.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "extern.h" +#include "pathnames.h" +#include <syslog.h> +#include <time.h> + +#define MAXUSRARGS 100 +#define MAXGLOBARGS 1000 + +/* + * Special version of popen which avoids call to shell. This ensures no one + * 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 = calloc(fds, sizeof(int))) == NULL) + return (NULL); + } + 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 if (gl.gl_pathc > 0) { + for (pop = gl.gl_pathv; *pop && gargc < (MAXGLOBARGS-1); + pop++) + gargv[gargc++] = strdup(*pop); + } + globfree(&gl); + } + gargv[gargc] = NULL; + + iop = NULL; + fflush(NULL); + pid = (strcmp(gargv[0], _PATH_LS) == 0) ? fork() : vfork(); + 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]); + } + /* Drop privileges before proceeding */ + if (getuid() != geteuid() && setuid(geteuid()) < 0) + _exit(1); + 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)); + } + 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 == NULL || 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); +} |