aboutsummaryrefslogtreecommitdiff
path: root/libexec/ftpd
diff options
context:
space:
mode:
Diffstat (limited to 'libexec/ftpd')
-rw-r--r--libexec/ftpd/Makefile39
-rw-r--r--libexec/ftpd/Makefile.depend21
-rw-r--r--libexec/ftpd/Makefile.depend.options5
-rw-r--r--libexec/ftpd/blacklist.c55
-rw-r--r--libexec/ftpd/blacklist_client.h53
-rw-r--r--libexec/ftpd/config.h280
-rw-r--r--libexec/ftpd/extern.h110
-rw-r--r--libexec/ftpd/ftpchroot.5118
-rw-r--r--libexec/ftpd/ftpcmd.y1806
-rw-r--r--libexec/ftpd/ftpd.8589
-rw-r--r--libexec/ftpd/ftpd.c3446
-rw-r--r--libexec/ftpd/ftpusers28
-rw-r--r--libexec/ftpd/logwtmp.c70
-rw-r--r--libexec/ftpd/pathnames.h39
-rw-r--r--libexec/ftpd/popen.c193
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);
+}