summaryrefslogtreecommitdiffstats
path: root/contrib/lukemftpd/src/ftpd.c
diff options
context:
space:
mode:
Diffstat (limited to 'contrib/lukemftpd/src/ftpd.c')
-rw-r--r--contrib/lukemftpd/src/ftpd.c2947
1 files changed, 2947 insertions, 0 deletions
diff --git a/contrib/lukemftpd/src/ftpd.c b/contrib/lukemftpd/src/ftpd.c
new file mode 100644
index 0000000..c5c2f2e
--- /dev/null
+++ b/contrib/lukemftpd/src/ftpd.c
@@ -0,0 +1,2947 @@
+/* $NetBSD: ftpd.c,v 1.125 2001/04/25 01:46:26 lukem Exp $ */
+
+/*
+ * Copyright (c) 1997-2001 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to The NetBSD Foundation
+ * by Luke Mewburn.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ * must display the following acknowledgement:
+ * This product includes software developed by the NetBSD
+ * Foundation, Inc. and its contributors.
+ * 4. Neither the name of The NetBSD Foundation 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 NETBSD FOUNDATION, INC. 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 FOUNDATION 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.
+ */
+
+/*
+ * Copyright (c) 1985, 1988, 1990, 1992, 1993, 1994
+ * The Regents of the University of California. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ * must display the following acknowledgement:
+ * This product includes software developed by the University of
+ * California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/*
+ * Copyright (C) 1997 and 1998 WIDE 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.
+ * 3. Neither the name of the project 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 PROJECT 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 PROJECT 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.
+ */
+
+#define FTP_NAMES
+
+#include "lukemftpd.h"
+
+#if HAVE_GETSPNAM
+#include <shadow.h>
+#endif
+
+#include <arpa/telnet.h>
+
+#ifdef SKEY
+#include <skey.h>
+#endif
+#ifdef KERBEROS5
+#include <com_err.h>
+#include <krb5/krb5.h>
+#endif
+
+#define GLOBAL
+#include "extern.h"
+#include "pathnames.h"
+#include "version.h"
+
+int data;
+jmp_buf urgcatch;
+int sflag;
+int stru; /* avoid C keyword */
+int mode;
+int dataport; /* use specific data port */
+int dopidfile; /* maintain pid file */
+int doutmp; /* update utmp file */
+int dowtmp; /* update wtmp file */
+int doxferlog; /* syslog wu-ftpd style xferlog entries */
+int dropprivs; /* if privileges should or have been dropped */
+int mapped; /* IPv4 connection on AF_INET6 socket */
+off_t file_size;
+off_t byte_count;
+static char ttyline[20];
+static struct utmp utmp; /* for utmp */
+
+static const char *anondir = NULL;
+static const char *confdir = _DEFAULT_CONFDIR;
+
+#if defined(KERBEROS) || defined(KERBEROS5)
+int has_ccache = 0;
+int notickets = 1;
+char *krbtkfile_env = NULL;
+char *tty = ttyline;
+int login_krb5_forwardable_tgt = 0;
+#endif
+
+int epsvall = 0;
+
+/*
+ * 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;
+
+static int bind_pasv_addr(void);
+static int checkuser(const char *, const char *, int, int, char **);
+static int checkaccess(const char *);
+static int checkpassword(const struct passwd *, const char *);
+static void end_login(void);
+static FILE *getdatasock(const char *);
+static char *gunique(const char *);
+static void logremotehost(struct sockinet *);
+static void lostconn(int);
+static void myoob(int);
+static int receive_data(FILE *, FILE *);
+static int send_data(FILE *, FILE *, off_t, int);
+static struct passwd *sgetpwnam(const char *);
+
+int main(int, char *[]);
+
+#if defined(KERBEROS)
+int klogin(struct passwd *, char *, char *, char *);
+void kdestroy(void);
+#endif
+#if defined(KERBEROS5)
+int k5login(struct passwd *, char *, char *, char *);
+void k5destroy(void);
+#endif
+
+char * __progname;
+
+int
+main(int argc, char *argv[])
+{
+ int addrlen, ch, on = 1, tos, keepalive;
+#ifdef KERBEROS5
+ krb5_error_code kerror;
+#endif
+ char *p;
+
+ __progname = strrchr(argv[0], '/');
+ if (__progname == NULL)
+ __progname = argv[0];
+ else
+ __progname++;
+
+ connections = 1;
+ debug = 0;
+ logging = 0;
+ pdata = -1;
+ sflag = 0;
+ dataport = 0;
+ dopidfile = 1; /* default: DO use a pid file to count users */
+ doutmp = 0; /* default: Do NOT log to utmp */
+ dowtmp = 1; /* default: DO log to wtmp */
+ doxferlog = 0; /* default: Do NOT syslog xferlog */
+ dropprivs = 0;
+ mapped = 0;
+ usedefault = 1;
+ emailaddr = NULL;
+ hostname[0] = '\0';
+ homedir[0] = '\0';
+ gidcount = 0;
+ is_oob = 0;
+ version = FTPD_VERSION;
+
+ /*
+ * 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, FTPD_LOGTYPE);
+
+ while ((ch = getopt(argc, argv, "a:c:C:de:h:HlP:qQrst:T:uUvV:wWX"))
+ != -1) {
+ switch (ch) {
+ case 'a':
+ anondir = optarg;
+ break;
+
+ case 'c':
+ confdir = optarg;
+ break;
+
+ case 'C':
+ pw = sgetpwnam(optarg);
+ exit(checkaccess(optarg) ? 0 : 1);
+ /* NOTREACHED */
+
+ case 'd':
+ case 'v': /* deprecated */
+ debug = 1;
+ break;
+
+ case 'e':
+ emailaddr = optarg;
+ break;
+
+ case 'h':
+ strlcpy(hostname, optarg, sizeof(hostname));
+ break;
+
+ case 'H':
+ if (gethostname(hostname, sizeof(hostname)) == -1)
+ hostname[0] = '\0';
+ hostname[sizeof(hostname) - 1] = '\0';
+ break;
+
+ case 'l':
+ logging++; /* > 1 == extra logging */
+ break;
+
+ case 'P':
+ dataport = (int)strtol(optarg, &p, 10);
+ if (*p != '\0' || dataport < IPPORT_RESERVED ||
+ dataport > IPPORT_ANONMAX) {
+ syslog(LOG_WARNING, "Invalid dataport %s",
+ optarg);
+ dataport = 0;
+ }
+ break;
+
+ case 'q':
+ dopidfile = 1;
+ break;
+
+ case 'Q':
+ dopidfile = 0;
+ break;
+
+ case 'r':
+ dropprivs = 1;
+ break;
+
+ case 's':
+ sflag = 1;
+ break;
+
+ case 't':
+ case 'T':
+ syslog(LOG_WARNING,
+ "-%c has been deprecated in favour of ftpd.conf",
+ ch);
+ break;
+
+ case 'u':
+ doutmp = 1;
+ break;
+
+ case 'U':
+ doutmp = 0;
+ break;
+
+ case 'V':
+ if (EMPTYSTR(optarg) || strcmp(optarg, "-") == 0)
+ version = NULL;
+ else
+ version = xstrdup(optarg);
+ break;
+
+ case 'w':
+ dowtmp = 1;
+ break;
+
+ case 'W':
+ dowtmp = 0;
+ break;
+
+ case 'X':
+ doxferlog = 1;
+ break;
+
+ default:
+ if (optopt == 'a' || optopt == 'C')
+ exit(1);
+ syslog(LOG_WARNING, "unknown flag -%c ignored", optopt);
+ break;
+ }
+ }
+ if (EMPTYSTR(confdir))
+ confdir = _DEFAULT_CONFDIR;
+
+ memset((char *)&his_addr, 0, sizeof(his_addr));
+ addrlen = sizeof(his_addr.si_su);
+ if (getpeername(0, (struct sockaddr *)&his_addr.si_su, &addrlen) < 0) {
+ syslog(LOG_ERR, "getpeername (%s): %m",argv[0]);
+ exit(1);
+ }
+ his_addr.su_len = addrlen;
+ memset((char *)&ctrl_addr, 0, sizeof(ctrl_addr));
+ addrlen = sizeof(ctrl_addr.si_su);
+ if (getsockname(0, (struct sockaddr *)&ctrl_addr, &addrlen) < 0) {
+ syslog(LOG_ERR, "getsockname (%s): %m",argv[0]);
+ exit(1);
+ }
+ ctrl_addr.su_len = addrlen;
+#ifdef INET6
+ if (his_addr.su_family == AF_INET6
+ && IN6_IS_ADDR_V4MAPPED(&his_addr.su_6addr)) {
+#if 1
+ /*
+ * IPv4 control connection arrived to AF_INET6 socket.
+ * I hate to do this, but this is the easiest solution.
+ *
+ * The assumption is untrue on SIIT environment.
+ */
+ struct sockinet tmp_addr;
+ const int off = sizeof(struct in6_addr) - sizeof(struct in_addr);
+
+ tmp_addr = his_addr;
+ memset(&his_addr, 0, sizeof(his_addr));
+ his_addr.su_family = AF_INET;
+ his_addr.su_len = sizeof(his_addr.si_su.su_sin);
+ memcpy(&his_addr.su_addr, &tmp_addr.su_6addr.s6_addr[off],
+ sizeof(his_addr.su_addr));
+ his_addr.su_port = tmp_addr.su_port;
+
+ tmp_addr = ctrl_addr;
+ memset(&ctrl_addr, 0, sizeof(ctrl_addr));
+ ctrl_addr.su_family = AF_INET;
+ ctrl_addr.su_len = sizeof(ctrl_addr.si_su.su_sin);
+ memcpy(&ctrl_addr.su_addr, &tmp_addr.su_6addr.s6_addr[off],
+ sizeof(ctrl_addr.su_addr));
+ ctrl_addr.su_port = tmp_addr.su_port;
+#else
+ while (fgets(line, sizeof(line), fd) != NULL) {
+ if ((cp = strchr(line, '\n')) != NULL)
+ *cp = '\0';
+ reply(-530, "%s", line);
+ }
+ (void) fflush(stdout);
+ (void) fclose(fd);
+ reply(530,
+ "Connection from IPv4 mapped address is not supported.");
+ exit(0);
+#endif
+
+ mapped = 1;
+ } else
+#endif /* INET6 */
+ mapped = 0;
+#ifdef IP_TOS
+ if (!mapped && his_addr.su_family == AF_INET) {
+ tos = IPTOS_LOWDELAY;
+ if (setsockopt(0, IPPROTO_IP, IP_TOS, (char *)&tos,
+ sizeof(int)) < 0)
+ syslog(LOG_WARNING, "setsockopt (IP_TOS): %m");
+ }
+#endif
+ /* if the hostname hasn't been given, attempt to determine it */
+ if (hostname[0] == '\0') {
+ if (getnameinfo((struct sockaddr *)&ctrl_addr.si_su,
+ ctrl_addr.su_len, hostname, sizeof(hostname), NULL, 0, 0)
+ != 0)
+ (void)gethostname(hostname, sizeof(hostname));
+ hostname[sizeof(hostname) - 1] = '\0';
+ }
+
+ /* set this here so klogin can use it... */
+ (void)snprintf(ttyline, sizeof(ttyline), "ftp%d", getpid());
+
+ (void) freopen(_PATH_DEVNULL, "w", stderr);
+ (void) signal(SIGPIPE, lostconn);
+ (void) signal(SIGCHLD, SIG_IGN);
+ if (signal(SIGURG, myoob) == SIG_ERR)
+ syslog(LOG_WARNING, "signal: %m");
+
+ /* Try to handle urgent data inline */
+#ifdef SO_OOBINLINE
+ if (setsockopt(0, SOL_SOCKET, SO_OOBINLINE, (char *)&on, sizeof(on)) < 0)
+ syslog(LOG_WARNING, "setsockopt: %m");
+#endif
+ /* Set keepalives on the socket to detect dropped connections. */
+#ifdef SO_KEEPALIVE
+ keepalive = 1;
+ if (setsockopt(0, SOL_SOCKET, SO_KEEPALIVE, (char *)&keepalive,
+ sizeof(int)) < 0)
+ syslog(LOG_WARNING, "setsockopt (SO_KEEPALIVE): %m");
+#endif
+
+#ifdef F_SETOWN
+ if (fcntl(fileno(stdin), F_SETOWN, getpid()) == -1)
+ syslog(LOG_WARNING, "fcntl F_SETOWN: %m");
+#endif
+ logremotehost(&his_addr);
+ /*
+ * Set up default state
+ */
+ data = -1;
+ type = TYPE_A;
+ form = FORM_N;
+ stru = STRU_F;
+ mode = MODE_S;
+ tmpline[0] = '\0';
+ hasyyerrored = 0;
+
+#ifdef KERBEROS5
+ kerror = krb5_init_context(&kcontext);
+ if (kerror) {
+ syslog(LOG_ERR, "%s when initializing Kerberos context",
+ error_message(kerror));
+ exit(0);
+ }
+#endif /* KERBEROS5 */
+
+ init_curclass();
+ curclass.timeout = 300; /* 5 minutes, as per login(1) */
+ curclass.type = CLASS_REAL;
+
+ /* If logins are disabled, print out the message. */
+ if (display_file(_PATH_NOLOGIN, 530)) {
+ reply(530, "System not available.");
+ exit(0);
+ }
+ (void)display_file(conffilename(_PATH_FTPWELCOME), 220);
+ /* reply(220,) must follow */
+ if (EMPTYSTR(version))
+ reply(220, "%s FTP server ready.", hostname);
+ else
+ reply(220, "%s FTP server (%s) ready.", hostname, version);
+
+ (void) setjmp(errcatch);
+ ftp_loop();
+ /* NOTREACHED */
+}
+
+static void
+lostconn(int signo)
+{
+
+ if (debug)
+ syslog(LOG_DEBUG, "lost connection");
+ dologout(1);
+}
+
+/*
+ * 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).
+ */
+static struct passwd *
+sgetpwnam(const char *name)
+{
+ static struct passwd save;
+ struct passwd *p;
+
+ if ((p = getpwnam(name)) == NULL)
+ return (p);
+ if (save.pw_name) {
+ free((char *)save.pw_name);
+ memset(save.pw_passwd, 0, strlen(save.pw_passwd));
+ free((char *)save.pw_passwd);
+ free((char *)save.pw_gecos);
+ free((char *)save.pw_dir);
+ free((char *)save.pw_shell);
+ }
+ save = *p;
+ save.pw_name = xstrdup(p->pw_name);
+ save.pw_passwd = xstrdup(p->pw_passwd);
+ save.pw_gecos = xstrdup(p->pw_gecos);
+ save.pw_dir = xstrdup(p->pw_dir);
+ save.pw_shell = xstrdup(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[10]; /* 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(const char *name)
+{
+ if (logged_in) {
+ switch (curclass.type) {
+ case CLASS_GUEST:
+ reply(530, "Can't change user from guest login.");
+ return;
+ case CLASS_CHROOT:
+ reply(530, "Can't change user from chroot user.");
+ return;
+ case CLASS_REAL:
+ if (dropprivs) {
+ reply(530, "Can't change user.");
+ return;
+ }
+ end_login();
+ break;
+ default:
+ abort();
+ }
+ }
+
+#if defined(KERBEROS)
+ kdestroy();
+#endif
+#if defined(KERBEROS5)
+ k5destroy();
+#endif
+
+ curclass.type = CLASS_REAL;
+ if (strcmp(name, "ftp") == 0 || strcmp(name, "anonymous") == 0) {
+ /* need `pw' setup for checkaccess() and checkuser () */
+ if ((pw = sgetpwnam("ftp")) == NULL)
+ reply(530, "User %s unknown.", name);
+ else if (! checkaccess("ftp") || ! checkaccess("anonymous"))
+ reply(530, "User %s access denied.", name);
+ else {
+ curclass.type = CLASS_GUEST;
+ askpasswd = 1;
+ reply(331,
+ "Guest login ok, type your name as password.");
+ }
+ if (!askpasswd && logging)
+ syslog(LOG_NOTICE,
+ "ANONYMOUS FTP LOGIN REFUSED FROM %s", remotehost);
+ return;
+ }
+
+ pw = sgetpwnam(name);
+ if (logging)
+ strlcpy(curname, name, sizeof(curname));
+
+#ifdef SKEY
+ if (skey_haskey(name) == 0) {
+ const char *myskey;
+
+ myskey = skey_keyinfo(name);
+ reply(331, "Password [%s] required for %s.",
+ myskey ? myskey : "error getting challenge", name);
+ } else
+#endif
+ 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((unsigned) login_attempts);
+}
+
+/*
+ * Determine whether something is to happen (allow access, chroot)
+ * for a user. Each line is a shell-style glob followed by
+ * `yes' or `no'.
+ *
+ * For backward compatability, `allow' and `deny' are synonymns
+ * for `yes' and `no', respectively.
+ *
+ * Each glob is matched against the username in turn, and the first
+ * match found is used. If no match is found, the result is the
+ * argument `def'. If a match is found but without and explicit
+ * `yes'/`no', the result is the opposite of def.
+ *
+ * If the file doesn't exist at all, the result is the argument
+ * `nofile'
+ *
+ * Any line starting with `#' is considered a comment and ignored.
+ *
+ * Returns 0 if the user is denied, or 1 if they are allowed.
+ *
+ * NOTE: needs struct passwd *pw setup before use.
+ */
+static int
+checkuser(const char *fname, const char *name, int def, int nofile,
+ char **retclass)
+{
+ FILE *fd;
+ int retval;
+ char *glob, *perm, *class, *buf, *p;
+ size_t len, line;
+
+ retval = def;
+ if (retclass != NULL)
+ *retclass = NULL;
+ if ((fd = fopen(conffilename(fname), "r")) == NULL)
+ return nofile;
+
+ line = 0;
+ for (;
+ (buf = fparseln(fd, &len, &line, NULL, FPARSELN_UNESCCOMM |
+ FPARSELN_UNESCCONT | FPARSELN_UNESCESC)) != NULL;
+ free(buf), buf = NULL) {
+ glob = perm = class = NULL;
+ p = buf;
+ if (len < 1)
+ continue;
+ if (p[len - 1] == '\n')
+ p[--len] = '\0';
+ if (EMPTYSTR(p))
+ continue;
+
+ NEXTWORD(p, glob);
+ NEXTWORD(p, perm);
+ NEXTWORD(p, class);
+ if (EMPTYSTR(glob))
+ continue;
+ if (!EMPTYSTR(class)) {
+ if (strcasecmp(class, "all") == 0 ||
+ strcasecmp(class, "none") == 0) {
+ syslog(LOG_WARNING,
+ "%s line %d: illegal user-defined class `%s' - skipping entry",
+ fname, (int)line, class);
+ continue;
+ }
+ }
+
+ /* have a host specifier */
+ if ((p = strchr(glob, '@')) != NULL) {
+ unsigned long net, mask, addr;
+ int bits;
+
+ *p++ = '\0';
+ /* check against network or CIDR */
+ if (isdigit(*p) &&
+ (bits = inet_net_pton(AF_INET, p,
+ &net, sizeof(net))) != -1) {
+ net = ntohl(net);
+ mask = 0xffffffffU << (32 - bits);
+ addr = ntohl(his_addr.su_addr.s_addr);
+ if ((addr & mask) != net)
+ continue;
+
+ /* check against hostname glob */
+ } else if (fnmatch(p, remotehost, 0) != 0)
+ continue;
+ }
+
+ /* have a group specifier */
+ if ((p = strchr(glob, ':')) != NULL) {
+ gid_t *groups, *ng;
+ int gsize, i, found;
+
+ *p++ = '\0';
+ groups = NULL;
+ gsize = 16;
+ do {
+ ng = realloc(groups, gsize * sizeof(gid_t));
+ if (ng == NULL)
+ fatal(
+ "Local resource failure: realloc");
+ groups = ng;
+ } while (getgrouplist(pw->pw_name, pw->pw_gid,
+ groups, &gsize) == -1);
+ found = 0;
+ for (i = 0; i < gsize; i++) {
+ struct group *g;
+
+ if ((g = getgrgid(groups[i])) == NULL)
+ continue;
+ if (fnmatch(p, g->gr_name, 0) == 0) {
+ found = 1;
+ break;
+ }
+ }
+ free(groups);
+ if (!found)
+ continue;
+ }
+
+ /* check against username glob */
+ if (fnmatch(glob, name, 0) != 0)
+ continue;
+
+ if (perm != NULL &&
+ ((strcasecmp(perm, "allow") == 0) ||
+ (strcasecmp(perm, "yes") == 0)))
+ retval = 1;
+ else if (perm != NULL &&
+ ((strcasecmp(perm, "deny") == 0) ||
+ (strcasecmp(perm, "no") == 0)))
+ retval = 0;
+ else
+ retval = !def;
+ if (!EMPTYSTR(class) && retclass != NULL)
+ *retclass = xstrdup(class);
+ free(buf);
+ break;
+ }
+ (void) fclose(fd);
+ return (retval);
+}
+
+/*
+ * Check if user is allowed by /etc/ftpusers
+ * returns 1 for yes, 0 for no
+ *
+ * NOTE: needs struct passwd *pw setup (for checkuser())
+ */
+static int
+checkaccess(const char *name)
+{
+
+ return (checkuser(_PATH_FTPUSERS, name, 1, 0, NULL));
+}
+
+/*
+ * Terminate login as previous user (if any), resetting state;
+ * used when USER command is given or login fails.
+ */
+static void
+end_login(void)
+{
+
+ if (logged_in) {
+#ifdef NO_UTMP
+ if (dowtmp)
+ logwtmp(ttyline, "", "");
+ if (doutmp)
+ logout(utmp.ut_line);
+#endif /* NO_UTMP */
+ }
+ /* reset login state */
+ show_chdir_messages(-1); /* flush chdir cache */
+ if (pw != NULL && pw->pw_passwd != NULL)
+ memset(pw->pw_passwd, 0, strlen(pw->pw_passwd));
+ pw = NULL;
+ logged_in = 0;
+ quietmessages = 0;
+ gidcount = 0;
+ curclass.type = CLASS_REAL;
+ (void) seteuid((uid_t)0);
+}
+
+void
+pass(const char *passwd)
+{
+ int rval;
+ const char *cp, *shell;
+ char *class, root[MAXPATHLEN];
+ char *p;
+ int len;
+
+ class = NULL;
+ if (logged_in || askpasswd == 0) {
+ reply(503, "Login with USER first.");
+ return;
+ }
+ askpasswd = 0;
+ if (curclass.type != CLASS_GUEST) {
+ /* "ftp" is the only account allowed with no password */
+ if (pw == NULL) {
+ rval = 1; /* failure below */
+ goto skip;
+ }
+#if defined(KERBEROS)
+ if (klogin(pw, "", hostname, (char *)passwd) == 0) {
+ rval = 0;
+ goto skip;
+ }
+#endif
+#if defined(KERBEROS5)
+ if (k5login(pw, "", hostname, (char *)passwd) == 0) {
+ rval = 0;
+ goto skip;
+ }
+#endif
+#ifdef SKEY
+ if (skey_haskey(pw->pw_name) == 0) {
+ char *p;
+ int r;
+
+ p = xstrdup(passwd);
+ r = skey_passcheck(pw->pw_name, p);
+ free(p);
+ if (r != -1) {
+ rval = 0;
+ goto skip;
+ }
+ }
+#endif
+ if (!sflag)
+ rval = checkpassword(pw, passwd);
+ else
+ rval = 1;
+
+ skip:
+
+ /*
+ * If rval > 0, the user failed the authentication check
+ * above. If rval == 0, either Kerberos or local
+ * authentication succeeded.
+ */
+ if (rval) {
+ reply(530, "%s", rval == 2 ? "Password expired." :
+ "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;
+ }
+ }
+
+ /* password ok; see if anything else prevents login */
+ if (! checkuser(_PATH_FTPUSERS, pw->pw_name, 1, 0, &class)) {
+ reply(530, "User %s may not use FTP.", pw->pw_name);
+ if (logging)
+ syslog(LOG_NOTICE, "FTP LOGIN REFUSED FROM %s, %s",
+ remotehost, pw->pw_name);
+ goto bad;
+ }
+ /* if not guest user, check for valid shell */
+ if ((shell = pw->pw_shell) == NULL || *shell == 0)
+ shell = _PATH_BSHELL;
+ while ((cp = getusershell()) != NULL)
+ if (strcmp(cp, shell) == 0)
+ break;
+ endusershell();
+ if (cp == NULL && curclass.type != CLASS_GUEST) {
+ reply(530, "User %s may not use FTP.", pw->pw_name);
+ if (logging)
+ syslog(LOG_NOTICE, "FTP LOGIN REFUSED FROM %s, %s",
+ remotehost, pw->pw_name);
+ goto bad;
+ }
+
+ login_attempts = 0; /* this time successful */
+ if (setegid((gid_t)pw->pw_gid) < 0) {
+ reply(550, "Can't set gid.");
+ goto bad;
+ }
+ (void) initgroups(pw->pw_name, pw->pw_gid);
+ /* cache groups for cmds.c::matchgroup() */
+ gidcount = getgroups(sizeof(gidlist), gidlist);
+
+ /* open wtmp before chroot */
+#ifdef NO_UTMP
+ if (dowtmp)
+ logwtmp(ttyline, pw->pw_name, remotehost);
+
+ /* open utmp before chroot */
+ if (doutmp) {
+ memset((void *)&utmp, 0, sizeof(utmp));
+ (void)time(&utmp.ut_time);
+ (void)strncpy(utmp.ut_name, pw->pw_name, sizeof(utmp.ut_name));
+ (void)strncpy(utmp.ut_host, remotehost, sizeof(utmp.ut_host));
+ (void)strncpy(utmp.ut_line, ttyline, sizeof(utmp.ut_line));
+ login(&utmp);
+ }
+#endif /* NO_UTMP */
+
+ logged_in = 1;
+
+ /* check user in /etc/ftpchroot */
+ if (checkuser(_PATH_FTPCHROOT, pw->pw_name, 0, 0, NULL)) {
+ if (curclass.type == CLASS_GUEST) {
+ syslog(LOG_NOTICE,
+ "Can't change guest user to chroot class; remove entry in %s",
+ _PATH_FTPCHROOT);
+ exit(1);
+ }
+ curclass.type = CLASS_CHROOT;
+ }
+ if (class == NULL) {
+ switch (curclass.type) {
+ case CLASS_GUEST:
+ class = xstrdup("guest");
+ break;
+ case CLASS_CHROOT:
+ class = xstrdup("chroot");
+ break;
+ case CLASS_REAL:
+ class = xstrdup("real");
+ break;
+ default:
+ syslog(LOG_ERR, "unknown curclass.type %d; aborting",
+ curclass.type);
+ abort();
+ }
+ }
+
+ /* parse ftpd.conf, setting up various parameters */
+ parse_conf(class);
+ connections = 1;
+ if (dopidfile)
+ count_users();
+ if (curclass.limit != -1 && connections > curclass.limit) {
+ if (! EMPTYSTR(curclass.limitfile))
+ (void)display_file(conffilename(curclass.limitfile),
+ 530);
+ reply(530,
+ "User %s access denied, connection limit of %d reached.",
+ pw->pw_name, curclass.limit);
+ syslog(LOG_NOTICE,
+ "Maximum connection limit of %d for class %s reached, login refused for %s",
+ curclass.limit, curclass.classname, pw->pw_name);
+ goto bad;
+ }
+
+ homedir[0] = '/';
+ switch (curclass.type) {
+ case CLASS_GUEST:
+ /*
+ * We MUST do a chdir() after the chroot. Otherwise
+ * the old current directory will be accessible as "."
+ * outside the new root!
+ */
+ format_path(root,
+ curclass.chroot ? curclass.chroot :
+ anondir ? anondir :
+ pw->pw_dir);
+ format_path(homedir,
+ curclass.homedir ? curclass.homedir :
+ "/");
+ if (EMPTYSTR(homedir))
+ homedir[0] = '/';
+ if (EMPTYSTR(root) || chroot(root) < 0) {
+ syslog(LOG_NOTICE,
+ "GUEST user %s: can't chroot to %s: %m",
+ pw->pw_name, root);
+ goto bad_guest;
+ }
+ if (chdir(homedir) < 0) {
+ syslog(LOG_NOTICE,
+ "GUEST user %s: can't chdir to %s: %m",
+ pw->pw_name, homedir);
+ bad_guest:
+ reply(550, "Can't set guest privileges.");
+ goto bad;
+ }
+ break;
+ case CLASS_CHROOT:
+ format_path(root,
+ curclass.chroot ? curclass.chroot :
+ pw->pw_dir);
+ format_path(homedir,
+ curclass.homedir ? curclass.homedir :
+ "/");
+ if (EMPTYSTR(homedir))
+ homedir[0] = '/';
+ if (EMPTYSTR(root) || chroot(root) < 0) {
+ syslog(LOG_NOTICE,
+ "CHROOT user %s: can't chroot to %s: %m",
+ pw->pw_name, root);
+ goto bad_chroot;
+ }
+ if (chdir(homedir) < 0) {
+ syslog(LOG_NOTICE,
+ "CHROOT user %s: can't chdir to %s: %m",
+ pw->pw_name, homedir);
+ bad_chroot:
+ reply(550, "Can't change root.");
+ goto bad;
+ }
+ break;
+ case CLASS_REAL:
+ /* only chroot REAL if explictly requested */
+ if (! EMPTYSTR(curclass.chroot)) {
+ format_path(root, curclass.chroot);
+ if (EMPTYSTR(root) || chroot(root) < 0) {
+ syslog(LOG_NOTICE,
+ "REAL user %s: can't chroot to %s: %m",
+ pw->pw_name, root);
+ goto bad_chroot;
+ }
+ }
+ format_path(homedir,
+ curclass.homedir ? curclass.homedir :
+ pw->pw_dir);
+ if (EMPTYSTR(homedir) || chdir(homedir) < 0) {
+ if (chdir("/") < 0) {
+ syslog(LOG_NOTICE,
+ "REAL user %s: can't chdir to %s: %m",
+ pw->pw_name,
+ !EMPTYSTR(homedir) ? homedir : "/");
+ reply(530,
+ "User %s: can't change directory to %s.",
+ pw->pw_name,
+ !EMPTYSTR(homedir) ? homedir : "/");
+ goto bad;
+ } else {
+ reply(-230,
+ "No directory! Logging in with home=/");
+ homedir[0] = '/';
+ }
+ }
+ break;
+ }
+#if HAVE_SETLOGIN
+ setlogin(pw->pw_name);
+#endif
+ if (dropprivs ||
+ (curclass.type != CLASS_REAL &&
+ ntohs(ctrl_addr.su_port) > IPPORT_RESERVED + 1)) {
+ dropprivs++;
+ if (setgid((gid_t)pw->pw_gid) < 0) {
+ reply(550, "Can't set gid.");
+ goto bad;
+ }
+ if (setuid((uid_t)pw->pw_uid) < 0) {
+ reply(550, "Can't set uid.");
+ goto bad;
+ }
+ } else {
+ if (seteuid((uid_t)pw->pw_uid) < 0) {
+ reply(550, "Can't set uid.");
+ goto bad;
+ }
+ }
+ len = sizeof("HOME=") + strlen(homedir) + 1;;
+ p = malloc(len);
+ if (p == NULL) {
+ reply(550, "Local resource failure: malloc");
+ goto bad;
+ }
+ snprintf(p, len, "HOME=%s", homedir);
+ putenv(p);
+ free(p);
+
+ if (curclass.type == CLASS_GUEST && passwd[0] == '-')
+ quietmessages = 1;
+
+ /*
+ * Display a login message, if it exists.
+ * N.B. reply(230,) must follow the message.
+ */
+ (void)display_file(conffilename(curclass.motd), 230);
+ show_chdir_messages(230);
+ if (curclass.type == CLASS_GUEST) {
+ char *p;
+
+ reply(230, "Guest login ok, access restrictions apply.");
+#if HAVE_SETPROCTITLE
+ snprintf(proctitle, sizeof(proctitle),
+ "%s: anonymous/%.*s", remotehost,
+ (int) (sizeof(proctitle) - sizeof(remotehost) -
+ sizeof(": anonymous/")), passwd);
+ setproctitle("%s", proctitle);
+#endif /* HAVE_SETPROCTITLE */
+ if (logging)
+ syslog(LOG_INFO,
+ "ANONYMOUS FTP LOGIN FROM %s, %s (class: %s, type: %s)",
+ remotehost, passwd,
+ curclass.classname, CURCLASSTYPE);
+ /* store guest password reply into pw_passwd */
+ REASSIGN(pw->pw_passwd, xstrdup(passwd));
+ for (p = pw->pw_passwd; *p; p++)
+ if (!isgraph(*p))
+ *p = '_';
+ } else {
+ reply(230, "User %s logged in.", pw->pw_name);
+#if HAVE_SETPROCTITLE
+ snprintf(proctitle, sizeof(proctitle),
+ "%s: %s", remotehost, pw->pw_name);
+ setproctitle("%s", proctitle);
+#endif /* HAVE_SETPROCTITLE */
+ if (logging)
+ syslog(LOG_INFO,
+ "FTP LOGIN FROM %s as %s (class: %s, type: %s)",
+ remotehost, pw->pw_name,
+ curclass.classname, CURCLASSTYPE);
+ }
+ (void) umask(curclass.umask);
+ goto cleanuppass;
+
+ bad:
+ /* Forget all about it... */
+ end_login();
+
+ cleanuppass:
+ if (class)
+ free(class);
+}
+
+void
+retrieve(char *argv[], const char *name)
+{
+ FILE *fin, *dout;
+ struct stat st;
+ int (*closefunc)(FILE *) = NULL;
+ int log, sendrv, closerv, stderrfd, isconversion, isdata, isls;
+ struct timeval start, finish, td, *tdp;
+ const char *dispname;
+
+ sendrv = closerv = stderrfd = -1;
+ isconversion = isdata = isls = log = 0;
+ tdp = NULL;
+ dispname = name;
+ fin = dout = NULL;
+ if (argv == NULL) { /* if not running a command ... */
+ log = 1;
+ isdata = 1;
+ fin = fopen(name, "r");
+ closefunc = fclose;
+ if (fin == NULL) /* doesn't exist?; try a conversion */
+ argv = do_conversion(name);
+ if (argv != NULL) {
+ isconversion++;
+ syslog(LOG_DEBUG, "get command: '%s' on '%s'",
+ argv[0], name);
+ }
+ }
+ if (argv != NULL) {
+ char temp[MAXPATHLEN];
+
+ if (strcmp(argv[0], INTERNAL_LS) == 0) {
+ isls = 1;
+ stderrfd = -1;
+ } else {
+ (void)snprintf(temp, sizeof(temp), "%s", TMPFILE);
+ stderrfd = mkstemp(temp);
+ if (stderrfd != -1)
+ (void)unlink(temp);
+ }
+ dispname = argv[0];
+ fin = ftpd_popen(argv, "r", stderrfd);
+ closefunc = ftpd_pclose;
+ st.st_size = -1;
+ st.st_blksize = BUFSIZ;
+ }
+ if (fin == NULL) {
+ if (errno != 0) {
+ perror_reply(550, dispname);
+ if (log)
+ logxfer("get", -1, name, NULL, NULL,
+ strerror(errno));
+ }
+ goto cleanupretrieve;
+ }
+ byte_count = -1;
+ if (argv == NULL
+ && (fstat(fileno(fin), &st) < 0 || !S_ISREG(st.st_mode))) {
+ reply(550, "%s: not a plain file.", dispname);
+ goto done;
+ }
+ if (restart_point) {
+ if (type == TYPE_A) {
+ off_t i;
+ int c;
+
+ for (i = 0; i < restart_point; i++) {
+ if ((c=getc(fin)) == EOF) {
+ perror_reply(550, dispname);
+ goto done;
+ }
+ if (c == '\n')
+ i++;
+ }
+ } else if (lseek(fileno(fin), restart_point, SEEK_SET) < 0) {
+ perror_reply(550, dispname);
+ goto done;
+ }
+ }
+ dout = dataconn(dispname, st.st_size, "w");
+ if (dout == NULL)
+ goto done;
+
+ (void)gettimeofday(&start, NULL);
+ sendrv = send_data(fin, dout, st.st_blksize, isdata);
+ (void)gettimeofday(&finish, NULL);
+ (void) fclose(dout); /* close now to affect timing stats */
+ dout = NULL;
+ timersub(&finish, &start, &td);
+ tdp = &td;
+ done:
+ if (log)
+ logxfer("get", byte_count, name, NULL, tdp, NULL);
+ closerv = (*closefunc)(fin);
+ if (sendrv == 0) {
+ FILE *err;
+ struct stat sb;
+
+ if (!isls && argv != NULL && closerv != 0) {
+ reply(-226,
+ "Command returned an exit status of %d",
+ closerv);
+ if (isconversion)
+ syslog(LOG_WARNING,
+ "retrieve command: '%s' returned %d",
+ argv[0], closerv);
+ }
+ if (!isls && argv != NULL && stderrfd != -1 &&
+ (fstat(stderrfd, &sb) == 0) && sb.st_size > 0 &&
+ ((err = fdopen(stderrfd, "r")) != NULL)) {
+ char *cp, line[LINE_MAX];
+
+ reply(-226, "Command error messages:");
+ rewind(err);
+ while (fgets(line, sizeof(line), err) != NULL) {
+ if ((cp = strchr(line, '\n')) != NULL)
+ *cp = '\0';
+ reply(0, " %s", line);
+ }
+ (void) fflush(stdout);
+ (void) fclose(err);
+ /* a reply(226,) must follow */
+ }
+ reply(226, "Transfer complete.");
+ }
+ cleanupretrieve:
+ closedataconn(dout);
+ if (stderrfd != -1)
+ (void)close(stderrfd);
+ if (isconversion)
+ free(argv);
+}
+
+void
+store(const char *name, const char *mode, int unique)
+{
+ FILE *fout, *din;
+ struct stat st;
+ int (*closefunc)(FILE *);
+ struct timeval start, finish, td, *tdp;
+ char *desc;
+
+ din = NULL;
+ desc = (*mode == 'w') ? "put" : "append";
+ if (unique && stat(name, &st) == 0 &&
+ (name = gunique(name)) == NULL) {
+ logxfer(desc, -1, name, NULL, NULL,
+ "cannot create unique file");
+ goto cleanupstore;
+ }
+
+ if (restart_point)
+ mode = "r+";
+ fout = fopen(name, mode);
+ closefunc = fclose;
+ tdp = NULL;
+ if (fout == NULL) {
+ perror_reply(553, name);
+ logxfer(desc, -1, name, NULL, NULL, strerror(errno));
+ goto cleanupstore;
+ }
+ byte_count = -1;
+ if (restart_point) {
+ if (type == TYPE_A) {
+ off_t i;
+ int c;
+
+ for (i = 0; i < restart_point; i++) {
+ 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 (fseek(fout, 0L, SEEK_CUR) < 0) {
+ perror_reply(550, name);
+ goto done;
+ }
+ } else if (lseek(fileno(fout), restart_point, SEEK_SET) < 0) {
+ perror_reply(550, name);
+ goto done;
+ }
+ }
+ din = dataconn(name, (off_t)-1, "r");
+ if (din == NULL)
+ goto done;
+ (void)gettimeofday(&start, NULL);
+ if (receive_data(din, fout) == 0) {
+ if (unique)
+ reply(226, "Transfer complete (unique file name:%s).",
+ name);
+ else
+ reply(226, "Transfer complete.");
+ }
+ (void)gettimeofday(&finish, NULL);
+ (void) fclose(din); /* close now to affect timing stats */
+ din = NULL;
+ timersub(&finish, &start, &td);
+ tdp = &td;
+ done:
+ logxfer(desc, byte_count, name, NULL, tdp, NULL);
+ (*closefunc)(fout);
+ cleanupstore:
+ closedataconn(din);
+}
+
+static FILE *
+getdatasock(const char *mode)
+{
+ int on, s, t, tries;
+ in_port_t port;
+
+ on = 1;
+ if (data >= 0)
+ return (fdopen(data, mode));
+ if (! dropprivs)
+ (void) seteuid((uid_t)0);
+ s = socket(ctrl_addr.su_family, SOCK_STREAM, 0);
+ if (s < 0)
+ goto bad;
+ if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR,
+ (char *) &on, sizeof(on)) < 0)
+ goto bad;
+ if (setsockopt(s, SOL_SOCKET, SO_KEEPALIVE,
+ (char *) &on, sizeof(on)) < 0)
+ goto bad;
+ /* anchor socket to avoid multi-homing problems */
+ data_source = ctrl_addr;
+ /*
+ * By default source port for PORT connctions is
+ * ctrlport-1 (see RFC959 section 5.2).
+ * However, if privs have been dropped and that
+ * would be < IPPORT_RESERVED, use a random port
+ * instead.
+ */
+ if (dataport)
+ port = dataport;
+ else
+ port = ntohs(ctrl_addr.su_port) - 1;
+ if (dropprivs && port < IPPORT_RESERVED)
+ port = 0; /* use random port */
+ data_source.su_port = htons(port);
+
+ for (tries = 1; ; tries++) {
+ if (bind(s, (struct sockaddr *)&data_source.si_su,
+ data_source.su_len) >= 0)
+ break;
+ if (errno != EADDRINUSE || tries > 10)
+ goto bad;
+ sleep(tries);
+ }
+ if (! dropprivs)
+ (void) seteuid((uid_t)pw->pw_uid);
+#ifdef IP_TOS
+ if (!mapped && ctrl_addr.su_family == AF_INET) {
+ on = IPTOS_THROUGHPUT;
+ if (setsockopt(s, IPPROTO_IP, IP_TOS, (char *)&on,
+ sizeof(int)) < 0)
+ syslog(LOG_WARNING, "setsockopt (IP_TOS): %m");
+ }
+#endif
+ return (fdopen(s, mode));
+ bad:
+ /* Return the real value of errno (close may change it) */
+ t = errno;
+ if (! dropprivs)
+ (void) seteuid((uid_t)pw->pw_uid);
+ (void) close(s);
+ errno = t;
+ return (NULL);
+}
+
+FILE *
+dataconn(const char *name, off_t size, const char *mode)
+{
+ char sizebuf[32];
+ FILE *file;
+ int retry = 0, tos, keepalive;
+
+ file_size = size;
+ byte_count = 0;
+ if (size != (off_t) -1)
+ (void)snprintf(sizebuf, sizeof(sizebuf), " (" LLF " byte%s)",
+ (LLT)size, PLURAL(size));
+ else
+ sizebuf[0] = '\0';
+ if (pdata >= 0) {
+ struct sockinet from;
+ int s, fromlen = sizeof(from.su_len);
+
+ (void) alarm(curclass.timeout);
+ s = accept(pdata, (struct sockaddr *)&from.si_su, &fromlen);
+ (void) alarm(0);
+ if (s < 0) {
+ reply(425, "Can't open data connection.");
+ (void) close(pdata);
+ pdata = -1;
+ return (NULL);
+ }
+ (void) close(pdata);
+ pdata = s;
+ switch (from.su_family) {
+ case AF_INET:
+#ifdef IP_TOS
+ if (!mapped) {
+ tos = IPTOS_THROUGHPUT;
+ (void) setsockopt(s, IPPROTO_IP, IP_TOS,
+ (char *)&tos, sizeof(int));
+ }
+ break;
+#endif
+ }
+ /* Set keepalives on the socket to detect dropped conns. */
+#ifdef SO_KEEPALIVE
+ keepalive = 1;
+ (void) setsockopt(s, SOL_SOCKET, SO_KEEPALIVE,
+ (char *)&keepalive, sizeof(int));
+#endif
+ reply(150, "Opening %s mode data connection for '%s'%s.",
+ type == TYPE_A ? "ASCII" : "BINARY", name, sizebuf);
+ return (fdopen(pdata, mode));
+ }
+ 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;
+ file = getdatasock(mode);
+ if (file == NULL) {
+ char hbuf[NI_MAXHOST];
+ char pbuf[NI_MAXSERV];
+
+ if (getnameinfo((struct sockaddr *)&data_source.si_su,
+ data_source.su_len, hbuf, sizeof(hbuf), pbuf, sizeof(pbuf),
+ NI_NUMERICHOST | NI_NUMERICSERV))
+ strlcpy(hbuf, "?", sizeof(hbuf));
+ reply(425, "Can't create data socket (%s,%s): %s.",
+ hbuf, pbuf, strerror(errno));
+ return (NULL);
+ }
+ data = fileno(file);
+ while (connect(data, (struct sockaddr *)&data_dest.si_su,
+ data_dest.su_len) < 0) {
+ if (errno == EADDRINUSE && retry < swaitmax) {
+ sleep((unsigned) swaitint);
+ retry += swaitint;
+ continue;
+ }
+ perror_reply(425, "Can't build data connection");
+ (void) fclose(file);
+ data = -1;
+ return (NULL);
+ }
+ reply(150, "Opening %s mode data connection for '%s'%s.",
+ type == TYPE_A ? "ASCII" : "BINARY", name, sizebuf);
+ return (file);
+}
+
+void
+closedataconn(FILE *fd)
+{
+
+ if (fd != NULL)
+ (void)fclose(fd);
+ data = -1;
+ if (pdata >= 0)
+ (void)close(pdata);
+ pdata = -1;
+}
+
+/*
+ * Tranfer the contents of "instr" to "outstr" peer using the appropriate
+ * encapsulation of the data subject * to Mode, Structure, and Type.
+ *
+ * NB: Form isn't handled.
+ */
+static int
+send_data(FILE *instr, FILE *outstr, off_t blksize, int isdata)
+{
+ int c, filefd, netfd, rval;
+ char *buf;
+
+ transflag = 1;
+ rval = -1;
+ buf = NULL;
+ if (setjmp(urgcatch))
+ goto cleanup_send_data;
+
+ switch (type) {
+
+ case TYPE_A:
+ /* XXXLUKEM: rate limit ascii send (get) */
+ (void) alarm(curclass.timeout);
+ while ((c = getc(instr)) != EOF) {
+ byte_count++;
+ if (c == '\n') {
+ if (ferror(outstr))
+ goto data_err;
+ (void) putc('\r', outstr);
+ if (isdata) {
+ total_data_out++;
+ total_data++;
+ }
+ total_bytes_out++;
+ total_bytes++;
+ }
+ (void) putc(c, outstr);
+ if (isdata) {
+ total_data_out++;
+ total_data++;
+ }
+ total_bytes_out++;
+ total_bytes++;
+ if ((byte_count % 4096) == 0)
+ (void) alarm(curclass.timeout);
+ }
+ (void) alarm(0);
+ fflush(outstr);
+ if (ferror(instr))
+ goto file_err;
+ if (ferror(outstr))
+ goto data_err;
+ rval = 0;
+ goto cleanup_send_data;
+
+ case TYPE_I:
+ case TYPE_L:
+ if ((buf = malloc((size_t)blksize)) == NULL) {
+ perror_reply(451, "Local resource failure: malloc");
+ goto cleanup_send_data;
+ }
+ filefd = fileno(instr);
+ netfd = fileno(outstr);
+ (void) alarm(curclass.timeout);
+ if (curclass.rateget) {
+ while (1) {
+ int d;
+ struct timeval then, now, td;
+ off_t bufrem;
+ char *bufp;
+
+ (void)gettimeofday(&then, NULL);
+ errno = c = d = 0;
+ bufrem = curclass.rateget;
+ while (bufrem > 0) {
+ if ((c = read(filefd, buf,
+ MIN(blksize, bufrem))) <= 0)
+ goto senddone;
+ (void) alarm(curclass.timeout);
+ bufrem -= c;
+ byte_count += c;
+ if (isdata) {
+ total_data_out += c;
+ total_data += c;
+ }
+ total_bytes_out += c;
+ total_bytes += c;
+ for (bufp = buf; c > 0;
+ c -= d, bufp += d)
+ if ((d =
+ write(netfd, bufp, c)) <= 0)
+ break;
+ if (d < 0)
+ goto data_err;
+ }
+ (void)gettimeofday(&now, NULL);
+ timersub(&now, &then, &td);
+ if (td.tv_sec == 0)
+ usleep(1000000 - td.tv_usec);
+ }
+ } else {
+ while ((c = read(filefd, buf, (size_t)blksize)) > 0) {
+ if (write(netfd, buf, c) != c)
+ goto data_err;
+ (void) alarm(curclass.timeout);
+ byte_count += c;
+ if (isdata) {
+ total_data_out += c;
+ total_data += c;
+ }
+ total_bytes_out += c;
+ total_bytes += c;
+ }
+ }
+ senddone:
+ if (c < 0)
+ goto file_err;
+ rval = 0;
+ goto cleanup_send_data;
+
+ default:
+ reply(550, "Unimplemented TYPE %d in send_data", type);
+ goto cleanup_send_data;
+ }
+
+ data_err:
+ (void) alarm(0);
+ perror_reply(426, "Data connection");
+ goto cleanup_send_data;
+
+ file_err:
+ (void) alarm(0);
+ perror_reply(551, "Error on input file");
+ /* FALLTHROUGH */
+
+ cleanup_send_data:
+ (void) alarm(0);
+ transflag = 0;
+ if (buf)
+ free(buf);
+ if (isdata) {
+ total_files_out++;
+ total_files++;
+ }
+ total_xfers_out++;
+ total_xfers++;
+ return (rval);
+}
+
+/*
+ * 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, bare_lfs, netfd, filefd, rval;
+ off_t byteswritten;
+ char buf[BUFSIZ];
+#ifdef __GNUC__
+ (void) &bare_lfs;
+#endif
+
+ bare_lfs = 0;
+ transflag = 1;
+ rval = -1;
+ byteswritten = 0;
+ if (setjmp(urgcatch))
+ goto cleanup_recv_data;
+
+#define FILESIZECHECK(x) \
+ do { \
+ if (curclass.maxfilesize != -1 && \
+ (x) > curclass.maxfilesize) { \
+ errno = EFBIG; \
+ goto file_err; \
+ } \
+ } while (0)
+
+ switch (type) {
+
+ case TYPE_I:
+ case TYPE_L:
+ netfd = fileno(instr);
+ filefd = fileno(outstr);
+ (void) alarm(curclass.timeout);
+ if (curclass.rateput) {
+ while (1) {
+ int d;
+ struct timeval then, now, td;
+ off_t bufrem;
+
+ (void)gettimeofday(&then, NULL);
+ errno = c = d = 0;
+ for (bufrem = curclass.rateput; bufrem > 0; ) {
+ if ((c = read(netfd, buf,
+ MIN(sizeof(buf), bufrem))) <= 0)
+ goto recvdone;
+ FILESIZECHECK(byte_count + c);
+ if ((d = write(filefd, buf, c)) != c)
+ goto file_err;
+ (void) alarm(curclass.timeout);
+ bufrem -= c;
+ byte_count += c;
+ total_data_in += c;
+ total_data += c;
+ total_bytes_in += c;
+ total_bytes += c;
+ }
+ (void)gettimeofday(&now, NULL);
+ timersub(&now, &then, &td);
+ if (td.tv_sec == 0)
+ usleep(1000000 - td.tv_usec);
+ }
+ } else {
+ while ((c = read(netfd, buf, sizeof(buf))) > 0) {
+ FILESIZECHECK(byte_count + c);
+ if (write(filefd, buf, c) != c)
+ goto file_err;
+ (void) alarm(curclass.timeout);
+ byte_count += c;
+ total_data_in += c;
+ total_data += c;
+ total_bytes_in += c;
+ total_bytes += c;
+ }
+ }
+ recvdone:
+ if (c < 0)
+ goto data_err;
+ rval = 0;
+ goto cleanup_recv_data;
+
+ case TYPE_E:
+ reply(553, "TYPE E not implemented.");
+ goto cleanup_recv_data;
+
+ case TYPE_A:
+ (void) alarm(curclass.timeout);
+ /* XXXLUKEM: rate limit ascii receive (put) */
+ while ((c = getc(instr)) != EOF) {
+ byte_count++;
+ total_data_in++;
+ total_data++;
+ total_bytes_in++;
+ total_bytes++;
+ if ((byte_count % 4096) == 0)
+ (void) alarm(curclass.timeout);
+ if (c == '\n')
+ bare_lfs++;
+ while (c == '\r') {
+ if (ferror(outstr))
+ goto data_err;
+ if ((c = getc(instr)) != '\n') {
+ byte_count++;
+ total_data_in++;
+ total_data++;
+ total_bytes_in++;
+ total_bytes++;
+ if ((byte_count % 4096) == 0)
+ (void) alarm(curclass.timeout);
+ byteswritten++;
+ FILESIZECHECK(byteswritten);
+ (void) putc ('\r', outstr);
+ if (c == '\0' || c == EOF)
+ goto contin2;
+ }
+ }
+ byteswritten++;
+ FILESIZECHECK(byteswritten);
+ (void) putc(c, outstr);
+ contin2: ;
+ }
+ (void) alarm(0);
+ fflush(outstr);
+ if (ferror(instr))
+ goto data_err;
+ if (ferror(outstr))
+ goto file_err;
+ if (bare_lfs) {
+ reply(-226,
+ "WARNING! %d bare linefeeds received in ASCII mode",
+ bare_lfs);
+ reply(0, "File may not have transferred correctly.");
+ }
+ rval = 0;
+ goto cleanup_recv_data;
+
+ default:
+ reply(550, "Unimplemented TYPE %d in receive_data", type);
+ goto cleanup_recv_data;
+ }
+#undef FILESIZECHECK
+
+ data_err:
+ (void) alarm(0);
+ perror_reply(426, "Data Connection");
+ goto cleanup_recv_data;
+
+ file_err:
+ (void) alarm(0);
+ perror_reply(452, "Error writing file");
+ goto cleanup_recv_data;
+
+ cleanup_recv_data:
+ (void) alarm(0);
+ transflag = 0;
+ total_files_in++;
+ total_files++;
+ total_xfers_in++;
+ total_xfers++;
+ return (rval);
+}
+
+void
+statcmd(void)
+{
+ struct sockinet *su = NULL;
+ static char hbuf[NI_MAXHOST], sbuf[NI_MAXSERV];
+ u_char *a, *p;
+ int ispassive, af;
+ off_t otbi, otbo, otb;
+
+ a = p = (u_char *)NULL;
+
+ reply(-211, "%s FTP server status:", hostname);
+ reply(0, "Version: %s", EMPTYSTR(version) ? "<suppressed>" : version);
+ hbuf[0] = '\0';
+ if (!getnameinfo((struct sockaddr *)&his_addr.si_su, his_addr.su_len,
+ hbuf, sizeof(hbuf), NULL, 0, NI_NUMERICHOST)
+ && strcmp(remotehost, hbuf) != 0)
+ reply(0, "Connected to %s (%s)", remotehost, hbuf);
+ else
+ reply(0, "Connected to %s", remotehost);
+
+ if (logged_in) {
+ if (curclass.type == CLASS_GUEST)
+ reply(0, "Logged in anonymously");
+ else
+ reply(0, "Logged in as %s%s", pw->pw_name,
+ curclass.type == CLASS_CHROOT ? " (chroot)" : "");
+ } else if (askpasswd)
+ reply(0, "Waiting for password");
+ else
+ reply(0, "Waiting for user name");
+ cprintf(stdout, " TYPE: %s", typenames[type]);
+ if (type == TYPE_A || type == TYPE_E)
+ cprintf(stdout, ", FORM: %s", formnames[form]);
+ if (type == TYPE_L) {
+#if NBBY == 8
+ cprintf(stdout, " %d", NBBY);
+#else
+ /* XXX: `bytesize' needs to be defined in this case */
+ cprintf(stdout, " %d", bytesize);
+#endif
+ }
+ cprintf(stdout, "; STRUcture: %s; transfer MODE: %s\r\n",
+ strunames[stru], modenames[mode]);
+ ispassive = 0;
+ if (data != -1) {
+ reply(0, "Data connection open");
+ su = NULL;
+ } else if (pdata != -1) {
+ reply(0, "in Passive mode");
+ if (curclass.advertise.su_len != 0)
+ su = &curclass.advertise;
+ else
+ su = &pasv_addr;
+ ispassive = 1;
+ goto printaddr;
+ } else if (usedefault == 0) {
+ if (epsvall) {
+ reply(0, "EPSV only mode (EPSV ALL)");
+ goto epsvonly;
+ }
+ su = (struct sockinet *)&data_dest;
+ printaddr:
+ /* PASV/PORT */
+ if (su->su_family == AF_INET) {
+ a = (u_char *) &su->su_addr;
+ p = (u_char *) &su->su_port;
+#define UC(b) (((int) b) & 0xff)
+ reply(0, "%s (%d,%d,%d,%d,%d,%d)",
+ ispassive ? "PASV" : "PORT" ,
+ UC(a[0]), UC(a[1]), UC(a[2]), UC(a[3]),
+ UC(p[0]), UC(p[1]));
+ }
+
+ /* LPSV/LPRT */
+ {
+ int alen, af, i;
+
+ alen = 0;
+ switch (su->su_family) {
+ case AF_INET:
+ a = (u_char *) &su->su_addr;
+ p = (u_char *) &su->su_port;
+ alen = sizeof(su->su_addr);
+ af = 4;
+ break;
+#ifdef INET6
+ case AF_INET6:
+ a = (u_char *) &su->su_6addr;
+ p = (u_char *) &su->su_port;
+ alen = sizeof(su->su_6addr);
+ af = 6;
+ break;
+#endif
+ default:
+ af = 0;
+ break;
+ }
+ if (af) {
+ cprintf(stdout, " %s (%d,%d",
+ ispassive ? "LPSV" : "LPRT", af, alen);
+ for (i = 0; i < alen; i++)
+ cprintf(stdout, ",%d", UC(a[i]));
+ cprintf(stdout, ",%d,%d,%d)\r\n",
+ 2, UC(p[0]), UC(p[1]));
+#undef UC
+ }
+ }
+
+ /* EPRT/EPSV */
+ epsvonly:
+ af = af2epsvproto(su->su_family);
+ hbuf[0] = '\0';
+ if (af > 0) {
+ struct sockinet tmp;
+
+ tmp = *su;
+#ifdef INET6
+ if (tmp.su_family == AF_INET6)
+ tmp.su_scope_id = 0;
+#endif
+ if (getnameinfo((struct sockaddr *)&tmp.si_su,
+ tmp.su_len, hbuf, sizeof(hbuf), sbuf, sizeof(sbuf),
+ NI_NUMERICHOST | NI_NUMERICSERV) == 0)
+ reply(0, "%s (|%d|%s|%s|)",
+ ispassive ? "EPSV" : "EPRT",
+ af, hbuf, sbuf);
+ }
+ } else
+ reply(0, "No data connection");
+
+ if (logged_in) {
+ reply(0,
+ "Data sent: " LLF " byte%s in " LLF " file%s",
+ (LLT)total_data_out, PLURAL(total_data_out),
+ (LLT)total_files_out, PLURAL(total_files_out));
+ reply(0,
+ "Data received: " LLF " byte%s in " LLF " file%s",
+ (LLT)total_data_in, PLURAL(total_data_in),
+ (LLT)total_files_in, PLURAL(total_files_in));
+ reply(0,
+ "Total data: " LLF " byte%s in " LLF " file%s",
+ (LLT)total_data, PLURAL(total_data),
+ (LLT)total_files, PLURAL(total_files));
+ }
+ otbi = total_bytes_in;
+ otbo = total_bytes_out;
+ otb = total_bytes;
+ reply(0, "Traffic sent: " LLF " byte%s in " LLF " transfer%s",
+ (LLT)otbo, PLURAL(otbo),
+ (LLT)total_xfers_out, PLURAL(total_xfers_out));
+ reply(0, "Traffic received: " LLF " byte%s in " LLF " transfer%s",
+ (LLT)otbi, PLURAL(otbi),
+ (LLT)total_xfers_in, PLURAL(total_xfers_in));
+ reply(0, "Total traffic: " LLF " byte%s in " LLF " transfer%s",
+ (LLT)otb, PLURAL(otb),
+ (LLT)total_xfers, PLURAL(total_xfers));
+
+ if (logged_in) {
+ struct ftpconv *cp;
+
+ reply(0, "%s", "");
+ reply(0, "Class: %s, type: %s",
+ curclass.classname, CURCLASSTYPE);
+ reply(0, "Check PORT/LPRT commands: %sabled",
+ CURCLASS_FLAGS_ISSET(checkportcmd) ? "en" : "dis");
+ if (! EMPTYSTR(curclass.display))
+ reply(0, "Display file: %s", curclass.display);
+ if (! EMPTYSTR(curclass.notify))
+ reply(0, "Notify fileglob: %s", curclass.notify);
+ reply(0, "Idle timeout: %d, maximum timeout: %d",
+ curclass.timeout, curclass.maxtimeout);
+ reply(0, "Current connections: %d", connections);
+ if (curclass.limit == -1)
+ reply(0, "Maximum connections: unlimited");
+ else
+ reply(0, "Maximum connections: %d", curclass.limit);
+ if (curclass.limitfile)
+ reply(0, "Connection limit exceeded message file: %s",
+ curclass.limitfile);
+ if (! EMPTYSTR(curclass.chroot))
+ reply(0, "Chroot format: %s", curclass.chroot);
+ if (! EMPTYSTR(curclass.homedir))
+ reply(0, "Homedir format: %s", curclass.homedir);
+ if (curclass.maxfilesize == -1)
+ reply(0, "Maximum file size: unlimited");
+ else
+ reply(0, "Maximum file size: " LLF,
+ (LLT)curclass.maxfilesize);
+ if (! EMPTYSTR(curclass.motd))
+ reply(0, "MotD file: %s", curclass.motd);
+ reply(0,
+ "Modify commands (CHMOD, DELE, MKD, RMD, RNFR, UMASK): %sabled",
+ CURCLASS_FLAGS_ISSET(modify) ? "en" : "dis");
+ reply(0, "Upload commands (APPE, STOR, STOU): %sabled",
+ CURCLASS_FLAGS_ISSET(upload) ? "en" : "dis");
+ reply(0, "Sanitize file names: %sabled",
+ CURCLASS_FLAGS_ISSET(sanenames) ? "en" : "dis");
+ reply(0, "PASV/LPSV/EPSV connections: %sabled",
+ CURCLASS_FLAGS_ISSET(passive) ? "en" : "dis");
+ if (curclass.advertise.su_len != 0) {
+ char buf[50]; /* big enough for IPv6 address */
+ const char *bp;
+
+ bp = inet_ntop(curclass.advertise.su_family,
+ (void *)&curclass.advertise.su_addr,
+ buf, sizeof(buf));
+ if (bp != NULL)
+ reply(0, "PASV advertise address: %s", bp);
+ }
+ if (curclass.portmin && curclass.portmax)
+ reply(0, "PASV port range: %d - %d",
+ curclass.portmin, curclass.portmax);
+ if (curclass.rateget)
+ reply(0, "Rate get limit: " LLF " bytes/sec",
+ (LLT)curclass.rateget);
+ else
+ reply(0, "Rate get limit: disabled");
+ if (curclass.rateput)
+ reply(0, "Rate put limit: " LLF " bytes/sec",
+ (LLT)curclass.rateput);
+ else
+ reply(0, "Rate put limit: disabled");
+ reply(0, "Umask: %.04o", curclass.umask);
+ for (cp = curclass.conversions; cp != NULL; cp=cp->next) {
+ if (cp->suffix == NULL || cp->types == NULL ||
+ cp->command == NULL)
+ continue;
+ reply(0, "Conversion: %s [%s] disable: %s, command: %s",
+ cp->suffix, cp->types, cp->disable, cp->command);
+ }
+ }
+
+ reply(211, "End of status");
+}
+
+void
+fatal(const char *s)
+{
+
+ reply(451, "Error in server: %s\n", s);
+ reply(221, "Closing connection due to server error.");
+ dologout(0);
+ /* NOTREACHED */
+}
+
+/*
+ * reply() --
+ * depending on the value of n, display fmt with a trailing CRLF and
+ * prefix of:
+ * n < -1 prefix the message with abs(n) + "-" (initial line)
+ * n == 0 prefix the message with 4 spaces (middle lines)
+ * n > 0 prefix the message with n + " " (final line)
+ */
+void
+reply(int n, const char *fmt, ...)
+{
+ off_t b;
+ va_list ap;
+
+ va_start(ap, fmt);
+ b = 0;
+ if (n == 0)
+ cprintf(stdout, " ");
+ else if (n < 0)
+ cprintf(stdout, "%d-", -n);
+ else
+ cprintf(stdout, "%d ", n);
+ b = vprintf(fmt, ap);
+ total_bytes += b;
+ total_bytes_out += b;
+ cprintf(stdout, "\r\n");
+ (void)fflush(stdout);
+ if (debug) {
+ syslog(LOG_DEBUG, "<--- %d%c", abs(n), (n < 0) ? '-' : ' ');
+ vsyslog(LOG_DEBUG, fmt, ap);
+ }
+}
+
+static void
+logremotehost(struct sockinet *who)
+{
+
+ if (getnameinfo((struct sockaddr *)&who->si_su,
+ who->su_len, remotehost, sizeof(remotehost), NULL, 0, 0))
+ strlcpy(remotehost, "?", sizeof(remotehost));
+
+#if HAVE_SETPROCTITLE
+ snprintf(proctitle, sizeof(proctitle), "%s: connected", remotehost);
+ setproctitle("%s", proctitle);
+#endif /* HAVE_SETPROCTITLE */
+ if (logging)
+ syslog(LOG_INFO, "connection from %s to %s",
+ remotehost, hostname);
+}
+
+/*
+ * Record logout in wtmp file and exit with supplied status.
+ */
+void
+dologout(int status)
+{
+ /*
+ * Prevent reception of SIGURG from resulting in a resumption
+ * back to the main program loop.
+ */
+ transflag = 0;
+
+ if (logged_in) {
+#ifdef NO_UTMP
+ if (dowtmp)
+ logwtmp(ttyline, "", "");
+ if (doutmp)
+ logout(utmp.ut_line);
+#endif /* NO_UTMP */
+#ifdef KERBEROS
+ if (!notickets && krbtkfile_env)
+ unlink(krbtkfile_env);
+#endif
+ }
+ /* beware of flushing buffers after a SIGPIPE */
+ _exit(status);
+}
+
+void
+abor(void)
+{
+
+ tmpline[0] = '\0';
+ is_oob = 0;
+ reply(426, "Transfer aborted. Data connection closed.");
+ reply(226, "Abort successful");
+ longjmp(urgcatch, 1);
+}
+
+void
+statxfer(void)
+{
+
+ tmpline[0] = '\0';
+ is_oob = 0;
+ if (file_size != (off_t) -1)
+ reply(213,
+ "Status: " LLF " of " LLF " byte%s transferred",
+ (LLT)byte_count, (LLT)file_size,
+ PLURAL(byte_count));
+ else
+ reply(213, "Status: " LLF " byte%s transferred",
+ (LLT)byte_count, PLURAL(byte_count));
+}
+
+static void
+myoob(int signo)
+{
+ char *cp;
+
+ /* only process if transfer occurring */
+ if (!transflag)
+ return;
+ cp = tmpline;
+ if (getline(cp, sizeof(tmpline), stdin) == NULL) {
+ reply(221, "You could at least say goodbye.");
+ dologout(0);
+ }
+ is_oob = 1;
+ ftp_handle_line(cp);
+ is_oob = 0;
+}
+
+static int
+bind_pasv_addr(void)
+{
+ static int passiveport;
+ int port, len;
+
+ len = pasv_addr.su_len;
+ if (curclass.portmin == 0 && curclass.portmax == 0) {
+ pasv_addr.su_port = 0;
+ return (bind(pdata, (struct sockaddr *)&pasv_addr.si_su, len));
+ }
+
+ if (passiveport == 0) {
+ srand(getpid());
+ passiveport = rand() % (curclass.portmax - curclass.portmin)
+ + curclass.portmin;
+ }
+
+ port = passiveport;
+ while (1) {
+ port++;
+ if (port > curclass.portmax)
+ port = curclass.portmin;
+ else if (port == passiveport) {
+ errno = EAGAIN;
+ return (-1);
+ }
+ pasv_addr.su_port = htons(port);
+ if (bind(pdata, (struct sockaddr *)&pasv_addr.si_su, len) == 0)
+ break;
+ if (errno != EADDRINUSE)
+ return (-1);
+ }
+ passiveport = port;
+ 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)
+{
+ int len;
+ char *p, *a;
+
+ if (pdata >= 0)
+ close(pdata);
+ pdata = socket(AF_INET, SOCK_STREAM, 0);
+ if (pdata < 0 || !logged_in) {
+ perror_reply(425, "Can't open passive connection");
+ return;
+ }
+ pasv_addr = ctrl_addr;
+
+ if (bind_pasv_addr() < 0)
+ goto pasv_error;
+ len = pasv_addr.su_len;
+ if (getsockname(pdata, (struct sockaddr *) &pasv_addr.si_su, &len) < 0)
+ goto pasv_error;
+ pasv_addr.su_len = len;
+ if (listen(pdata, 1) < 0)
+ goto pasv_error;
+ if (curclass.advertise.su_len != 0)
+ a = (char *) &curclass.advertise.su_addr;
+ else
+ a = (char *) &pasv_addr.su_addr;
+ 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) close(pdata);
+ pdata = -1;
+ perror_reply(425, "Can't open passive connection");
+ return;
+}
+
+/*
+ * convert protocol identifier to/from AF
+ */
+int
+lpsvproto2af(int proto)
+{
+
+ switch (proto) {
+ case 4:
+ return AF_INET;
+#ifdef INET6
+ case 6:
+ return AF_INET6;
+#endif
+ default:
+ return -1;
+ }
+}
+
+int
+af2lpsvproto(int af)
+{
+
+ switch (af) {
+ case AF_INET:
+ return 4;
+#ifdef INET6
+ case AF_INET6:
+ return 6;
+#endif
+ default:
+ return -1;
+ }
+}
+
+int
+epsvproto2af(int proto)
+{
+
+ switch (proto) {
+ case 1:
+ return AF_INET;
+#ifdef INET6
+ case 2:
+ return AF_INET6;
+#endif
+ default:
+ return -1;
+ }
+}
+
+int
+af2epsvproto(int af)
+{
+
+ switch (af) {
+ case AF_INET:
+ return 1;
+#ifdef INET6
+ case AF_INET6:
+ return 2;
+#endif
+ default:
+ return -1;
+ }
+}
+
+/*
+ * 228 Entering Long Passive Mode (af, hal, h1, h2, h3,..., pal, p1, p2...)
+ * 229 Entering Extended Passive Mode (|||port|)
+ */
+void
+long_passive(char *cmd, int pf)
+{
+ int len;
+ char *p, *a;
+
+ if (!logged_in) {
+ syslog(LOG_NOTICE, "long passive but not logged in");
+ reply(503, "Login with USER first.");
+ return;
+ }
+
+ if (pf != PF_UNSPEC && ctrl_addr.su_family != pf) {
+ /*
+ * XXX: only EPRT/EPSV ready clients will understand this
+ */
+ if (strcmp(cmd, "EPSV") != 0)
+ reply(501, "Network protocol mismatch"); /*XXX*/
+ else
+ epsv_protounsupp("Network protocol mismatch");
+
+ return;
+ }
+
+ if (pdata >= 0)
+ close(pdata);
+ pdata = socket(ctrl_addr.su_family, SOCK_STREAM, 0);
+ if (pdata < 0) {
+ perror_reply(425, "Can't open passive connection");
+ return;
+ }
+ pasv_addr = ctrl_addr;
+ if (bind_pasv_addr() < 0)
+ goto pasv_error;
+ len = pasv_addr.su_len;
+ if (getsockname(pdata, (struct sockaddr *) &pasv_addr.si_su, &len) < 0)
+ goto pasv_error;
+ pasv_addr.su_len = len;
+ if (listen(pdata, 1) < 0)
+ goto pasv_error;
+ p = (char *) &pasv_addr.su_port;
+
+#define UC(b) (((int) b) & 0xff)
+
+ if (strcmp(cmd, "LPSV") == 0) {
+ struct sockinet *advert;
+
+ if (curclass.advertise.su_len != 0)
+ advert = &curclass.advertise;
+ else
+ advert = &pasv_addr;
+ switch (advert->su_family) {
+ case AF_INET:
+ a = (char *) &advert->su_addr;
+ reply(228,
+ "Entering Long Passive Mode (%d,%d,%d,%d,%d,%d,%d,%d,%d)",
+ 4, 4, UC(a[0]), UC(a[1]), UC(a[2]), UC(a[3]),
+ 2, UC(p[0]), UC(p[1]));
+ return;
+#ifdef INET6
+ case AF_INET6:
+ a = (char *) &advert->su_6addr;
+ reply(228,
+ "Entering Long Passive Mode (%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d)",
+ 6, 16,
+ UC(a[0]), UC(a[1]), UC(a[2]), UC(a[3]),
+ UC(a[4]), UC(a[5]), UC(a[6]), UC(a[7]),
+ UC(a[8]), UC(a[9]), UC(a[10]), UC(a[11]),
+ UC(a[12]), UC(a[13]), UC(a[14]), UC(a[15]),
+ 2, UC(p[0]), UC(p[1]));
+ return;
+#endif
+ }
+#undef UC
+ } else if (strcmp(cmd, "EPSV") == 0) {
+ switch (pasv_addr.su_family) {
+ case AF_INET:
+#ifdef INET6
+ case AF_INET6:
+#endif
+ reply(229, "Entering Extended Passive Mode (|||%d|)",
+ ntohs(pasv_addr.su_port));
+ return;
+ }
+ } else {
+ /* more proper error code? */
+ }
+
+ pasv_error:
+ (void) close(pdata);
+ pdata = -1;
+ perror_reply(425, "Can't open passive connection");
+ return;
+}
+
+int
+extended_port(const char *arg)
+{
+ char *tmp = NULL;
+ char *result[3];
+ char *p, *q;
+ char delim;
+ struct addrinfo hints;
+ struct addrinfo *res = NULL;
+ int i;
+ unsigned long proto;
+
+ tmp = xstrdup(arg);
+ 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)
+ goto parsefail;
+ *q++ = '\0';
+ result[i] = p;
+ p = q;
+ }
+
+ /* some more sanity checks */
+ p = NULL;
+ (void)strtoul(result[2], &p, 10);
+ if (!*result[2] || *p)
+ goto parsefail;
+ p = NULL;
+ proto = strtoul(result[0], &p, 10);
+ if (!*result[0] || *p)
+ goto protounsupp;
+
+ memset(&hints, 0, sizeof(hints));
+ hints.ai_family = epsvproto2af((int)proto);
+ if (hints.ai_family < 0)
+ goto protounsupp;
+ hints.ai_socktype = SOCK_STREAM;
+ hints.ai_flags = AI_NUMERICHOST;
+ if (getaddrinfo(result[1], result[2], &hints, &res))
+ goto parsefail;
+ if (res->ai_next)
+ goto parsefail;
+ if (sizeof(data_dest) < res->ai_addrlen)
+ goto parsefail;
+ memcpy(&data_dest.si_su, res->ai_addr, res->ai_addrlen);
+ data_dest.su_len = 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_scope_id = his_addr.su_scope_id;
+ }
+#endif
+
+ if (tmp != NULL)
+ free(tmp);
+ if (res)
+ freeaddrinfo(res);
+ return 0;
+
+ parsefail:
+ reply(500, "Invalid argument, rejected.");
+ usedefault = 1;
+ if (tmp != NULL)
+ free(tmp);
+ if (res)
+ freeaddrinfo(res);
+ return -1;
+
+ protounsupp:
+ epsv_protounsupp("Protocol not supported");
+ usedefault = 1;
+ if (tmp != NULL)
+ free(tmp);
+ if (res)
+ freeaddrinfo(res);
+ return -1;
+}
+
+/*
+ * 522 Protocol not supported (proto,...)
+ * as we assume address family for control and data connections are the same,
+ * we do not return the list of address families we support - instead, we
+ * return the address family of the control connection.
+ */
+void
+epsv_protounsupp(const char *message)
+{
+ int proto;
+
+ proto = af2epsvproto(ctrl_addr.su_family);
+ if (proto < 0)
+ reply(501, "%s", message); /* XXX */
+ else
+ reply(522, "%s, use (%d)", message, proto);
+}
+
+/*
+ * Generate unique name for file with basename "local".
+ * The file named "local" is already known to exist.
+ * Generates failure reply on error.
+ *
+ * XXX: this function should under go changes similar to
+ * the mktemp(3)/mkstemp(3) changes.
+ */
+static char *
+gunique(const char *local)
+{
+ static char new[MAXPATHLEN];
+ struct stat st;
+ char *cp;
+ int count;
+
+ cp = strrchr(local, '/');
+ if (cp)
+ *cp = '\0';
+ if (stat(cp ? local : ".", &st) < 0) {
+ perror_reply(553, cp ? local : ".");
+ return (NULL);
+ }
+ if (cp)
+ *cp = '/';
+ for (count = 1; count < 100; count++) {
+ (void)snprintf(new, sizeof(new) - 1, "%s.%d", local, count);
+ if (stat(new, &st) < 0)
+ return (new);
+ }
+ reply(452, "Unique file name cannot be created.");
+ return (NULL);
+}
+
+/*
+ * Format and send reply containing system error number.
+ */
+void
+perror_reply(int code, const char *string)
+{
+ int save_errno;
+
+ save_errno = errno;
+ reply(code, "%s: %s.", string, strerror(errno));
+ errno = save_errno;
+}
+
+static char *onefile[] = {
+ "",
+ 0
+};
+
+void
+send_file_list(const char *whichf)
+{
+ struct stat st;
+ DIR *dirp = NULL;
+ struct dirent *dir;
+ FILE *dout = NULL;
+ char **dirlist, *dirname, *p;
+ int simple = 0;
+ int freeglob = 0;
+ glob_t gl;
+
+#ifdef __GNUC__
+ (void) &dout;
+ (void) &dirlist;
+ (void) &simple;
+ (void) &freeglob;
+#endif
+
+ p = NULL;
+ if (strpbrk(whichf, "~{[*?") != NULL) {
+ int flags = GLOB_BRACE|GLOB_NOCHECK|GLOB_TILDE|GLOB_LIMIT;
+
+ memset(&gl, 0, sizeof(gl));
+ freeglob = 1;
+ if (glob(whichf, flags, 0, &gl)) {
+ reply(550, "not found");
+ goto out;
+ } else if (gl.gl_pathc == 0) {
+ errno = ENOENT;
+ perror_reply(550, whichf);
+ goto out;
+ }
+ dirlist = gl.gl_pathv;
+ } else {
+ p = xstrdup(whichf);
+ onefile[0] = p;
+ dirlist = onefile;
+ simple = 1;
+ }
+ /* XXX: } for vi sm */
+
+ if (setjmp(urgcatch)) {
+ transflag = 0;
+ goto out;
+ }
+ while ((dirname = *dirlist++) != NULL) {
+ int trailingslash = 0;
+
+ if (stat(dirname, &st) < 0) {
+ /*
+ * If user typed "ls -l", etc, and the client
+ * used NLST, do what the user meant.
+ */
+ /* XXX: nuke this support? */
+ if (dirname[0] == '-' && *dirlist == NULL &&
+ transflag == 0) {
+ char *argv[] = { INTERNAL_LS, "", NULL };
+
+ argv[1] = dirname;
+ retrieve(argv, dirname);
+ goto out;
+ }
+ perror_reply(550, whichf);
+ goto cleanup_send_file_list;
+ }
+
+ if (S_ISREG(st.st_mode)) {
+ /*
+ * XXXRFC:
+ * should we follow RFC959 and not work
+ * for non directories?
+ */
+ if (dout == NULL) {
+ dout = dataconn("file list", (off_t)-1, "w");
+ if (dout == NULL)
+ goto out;
+ transflag++;
+ }
+ cprintf(dout, "%s%s\n", dirname,
+ type == TYPE_A ? "\r" : "");
+ continue;
+ } else if (!S_ISDIR(st.st_mode))
+ continue;
+
+ if (dirname[strlen(dirname) - 1] == '/')
+ trailingslash++;
+
+ if ((dirp = opendir(dirname)) == NULL)
+ continue;
+
+ while ((dir = readdir(dirp)) != NULL) {
+ char nbuf[MAXPATHLEN];
+
+ if (ISDOTDIR(dir->d_name) || ISDOTDOTDIR(dir->d_name))
+ continue;
+
+ (void)snprintf(nbuf, sizeof(nbuf), "%s%s%s", dirname,
+ trailingslash ? "" : "/", dir->d_name);
+
+ /*
+ * We have to do a stat to ensure it's
+ * not a directory or special file.
+ */
+ /*
+ * XXXRFC:
+ * should we follow RFC959 and filter out
+ * non files ? lukem - NO!, or not until
+ * our ftp client uses MLS{T,D} for completion.
+ */
+ if (simple || (stat(nbuf, &st) == 0 &&
+ S_ISREG(st.st_mode))) {
+ char *p;
+
+ if (dout == NULL) {
+ dout = dataconn("file list", (off_t)-1,
+ "w");
+ if (dout == NULL)
+ goto out;
+ transflag++;
+ }
+ p = nbuf;
+ if (nbuf[0] == '.' && nbuf[1] == '/')
+ p = &nbuf[2];
+ cprintf(dout, "%s%s\n", p,
+ type == TYPE_A ? "\r" : "");
+ }
+ }
+ (void) closedir(dirp);
+ }
+
+ if (dout == NULL)
+ reply(550, "No files found.");
+ else if (ferror(dout) != 0)
+ perror_reply(550, "Data connection");
+ else
+ reply(226, "Transfer complete.");
+
+ cleanup_send_file_list:
+ transflag = 0;
+ closedataconn(dout);
+ out:
+ total_xfers++;
+ total_xfers_out++;
+ if (p)
+ free(p);
+ if (freeglob)
+ globfree(&gl);
+}
+
+char *
+conffilename(const char *s)
+{
+ static char filename[MAXPATHLEN];
+
+ if (*s == '/')
+ strlcpy(filename, s, sizeof(filename));
+ else
+ (void)snprintf(filename, sizeof(filename), "%s/%s", confdir ,s);
+ return (filename);
+}
+
+/*
+ * logxfer --
+ * if logging > 1, then based on the arguments, syslog a message:
+ * if bytes != -1 "<command> <file1> = <bytes> bytes"
+ * else if file2 != NULL "<command> <file1> <file2>"
+ * else "<command> <file1>"
+ * if elapsed != NULL, append "in xxx.yyy seconds"
+ * if error != NULL, append ": " + error
+ *
+ * if doxferlog != 0, syslog a wu-ftpd style xferlog entry
+ */
+void
+logxfer(const char *command, off_t bytes, const char *file1, const char *file2,
+ const struct timeval *elapsed, const char *error)
+{
+ char buf[MAXPATHLEN * 2 + 100], realfile[MAXPATHLEN];
+ const char *r1, *r2;
+ char direction;
+ size_t len;
+ time_t now;
+
+ if (logging <=1 && !doxferlog)
+ return;
+
+ r1 = r2 = NULL;
+ if ((r1 = realpath(file1, realfile)) == NULL)
+ r1 = file1;
+ if (file2 != NULL)
+ if ((r2 = realpath(file2, realfile)) == NULL)
+ r2 = file2;
+
+ /*
+ * syslog command
+ */
+ if (logging > 1) {
+ len = snprintf(buf, sizeof(buf), "%s %s", command, r1);
+ if (bytes != (off_t)-1)
+ len += snprintf(buf + len, sizeof(buf) - len,
+ " = " LLF " byte%s", (LLT) bytes, PLURAL(bytes));
+ else if (r2 != NULL)
+ len += snprintf(buf + len, sizeof(buf) - len,
+ " %s", r2);
+ if (elapsed != NULL)
+ len += snprintf(buf + len, sizeof(buf) - len,
+ " in %ld.%.03d seconds", elapsed->tv_sec,
+ (int)(elapsed->tv_usec / 1000));
+ if (error != NULL)
+ len += snprintf(buf + len, sizeof(buf) - len,
+ ": %s", error);
+ syslog(LOG_INFO, "%s", buf);
+ }
+
+
+ /*
+ * syslog wu-ftpd style log entry, prefixed with "xferlog: "
+ */
+ if (!doxferlog)
+ return;
+
+ if (strcmp(command, "get") == 0)
+ direction = 'o';
+ else if (strcmp(command, "put") == 0 || strcmp(command, "append") == 0)
+ direction = 'i';
+ else
+ return;
+
+ time(&now);
+ syslog(LOG_INFO,
+ "xferlog%s: %.24s %ld %s " LLF " %s %c %s %c %c %s FTP 0 * %c",
+
+/*
+ * XXX: wu-ftpd puts (send) or (recv) in the syslog message, and removes
+ * the full date. This may be problematic for accurate log parsing,
+ * given that syslog messages don't contain the full date.
+ */
+#if 1 /* lukem's method; easier to convert to actual xferlog file */
+ "",
+ ctime(&now),
+#else /* wu-ftpd's syslog method, with an extra unneeded space */
+ (direction == 'i') ? " (recv)" : " (send)",
+ "",
+#endif
+ elapsed == NULL ? 0 : elapsed->tv_sec + (elapsed->tv_usec > 0),
+ remotehost,
+ bytes == (off_t)-1 ? 0 : (LLT) bytes,
+ r1,
+ type == TYPE_A ? 'a' : 'b',
+ "_", /* XXX: take conversions into account? */
+ direction,
+
+ curclass.type == CLASS_GUEST ? 'a' :
+ curclass.type == CLASS_CHROOT ? 'g' :
+ curclass.type == CLASS_REAL ? 'r' : '?',
+
+ curclass.type == CLASS_GUEST ? pw->pw_passwd : pw->pw_name,
+ error != NULL ? 'i' : 'c'
+ );
+}
+
+/*
+ * Determine if `password' is valid for user given in `pw'.
+ * Returns 2 if password expired, 1 if otherwise failed, 0 if ok
+ */
+int
+checkpassword(const struct passwd *pw, const char *password)
+{
+ char *orig, *new;
+ time_t expire;
+#if HAVE_GETSPNAM
+ struct spwd *spw;
+#endif
+
+ expire = 0;
+ if (pw == NULL)
+ return 1;
+
+#if HAVE_GETSPNAM
+ if ((spw = getspnam(pw->pw_name)) == NULL)
+ return 1;
+ orig = spw->sp_pwdp;
+#else
+ orig = pw->pw_passwd; /* save existing password */
+#if HAVE_PW_EXPIRE
+ expire = pw->pw_expire;
+#endif
+#endif /* HAVE_GETSPNAM */
+
+ if (orig[0] == '\0') /* don't allow empty passwords */
+ return 1;
+
+ new = crypt(password, orig); /* encrypt given password */
+ if (strcmp(new, orig) != 0) /* compare */
+ return 1;
+
+ if (expire && time(NULL) >= expire)
+ return 2; /* check if expired */
+
+ return 0; /* OK! */
+}
+
+char *
+xstrdup(const char *s)
+{
+ char *new = strdup(s);
+
+ if (new == NULL)
+ fatal("Local resource failure: malloc");
+ /* NOTREACHED */
+ return (new);
+}
+
+/*
+ * As per fprintf(), but increment total_bytes and total_bytes_out,
+ * by the appropriate amount.
+ */
+void
+cprintf(FILE *fd, const char *fmt, ...)
+{
+ off_t b;
+ va_list ap;
+
+ va_start(ap, fmt);
+ b = vfprintf(fd, fmt, ap);
+ total_bytes += b;
+ total_bytes_out += b;
+}
OpenPOWER on IntegriCloud