summaryrefslogtreecommitdiffstats
path: root/contrib/lukemftpd/src
diff options
context:
space:
mode:
authorobrien <obrien@FreeBSD.org>2001-07-19 16:25:08 +0000
committerobrien <obrien@FreeBSD.org>2001-07-19 16:25:08 +0000
commite4751f9e00971d0c774736bb50344b7ea67d40b0 (patch)
tree5e3fc097f0652fa73b8d2880dc1208f8bf557809 /contrib/lukemftpd/src
downloadFreeBSD-src-e4751f9e00971d0c774736bb50344b7ea67d40b0.zip
FreeBSD-src-e4751f9e00971d0c774736bb50344b7ea67d40b0.tar.gz
Import of LukeM's ftpd version 1.1.
Diffstat (limited to 'contrib/lukemftpd/src')
-rw-r--r--contrib/lukemftpd/src/Makefile.in61
-rw-r--r--contrib/lukemftpd/src/arpaftp.h111
-rw-r--r--contrib/lukemftpd/src/cmds.c791
-rw-r--r--contrib/lukemftpd/src/conf.c1007
-rw-r--r--contrib/lukemftpd/src/extern.h372
-rw-r--r--contrib/lukemftpd/src/ftpcmd.y1808
-rw-r--r--contrib/lukemftpd/src/ftpd.8833
-rw-r--r--contrib/lukemftpd/src/ftpd.c2947
-rw-r--r--contrib/lukemftpd/src/ftpd.conf.5587
-rw-r--r--contrib/lukemftpd/src/ftpusers.5183
-rw-r--r--contrib/lukemftpd/src/logutmp.c111
-rw-r--r--contrib/lukemftpd/src/logwtmp.c65
-rw-r--r--contrib/lukemftpd/src/pathnames.h51
-rw-r--r--contrib/lukemftpd/src/popen.c236
-rw-r--r--contrib/lukemftpd/src/version.h40
15 files changed, 9203 insertions, 0 deletions
diff --git a/contrib/lukemftpd/src/Makefile.in b/contrib/lukemftpd/src/Makefile.in
new file mode 100644
index 0000000..e4fe6c6
--- /dev/null
+++ b/contrib/lukemftpd/src/Makefile.in
@@ -0,0 +1,61 @@
+# $Id: Makefile.in,v 1.5 2001/03/29 05:29:07 lukem Exp $
+#
+
+srcdir = @srcdir@
+VPATH = @srcdir@
+SHELL = /bin/sh
+
+prefix = @prefix@
+exec_prefix = @exec_prefix@
+bindir = @bindir@
+mandir = @mandir@
+sbindir = @sbindir@
+
+mandircat5 = ${mandir}/cat5
+mandircat8 = ${mandir}/cat8
+
+CC = @CC@
+CFLAGS = -I${srcdir} -I${srcdir}/.. -I. -I.. @INCLUDES@ @CFLAGS@
+LIBS = @LIBS@
+LDFLAGS = @LDFLAGS@
+
+INSTALL = @INSTALL@
+
+PROG = ftpd
+OBJS = cmds.o conf.o ftpd.o ftpcmd.o popen.o @LSOBJS@
+# removed: logutmp.o logwtmp.o
+
+all: ${PROG}
+
+cmp.o: ${srcdir}/../ls/cmp.c
+ ${CC} ${CFLAGS} -c -o cmp.o ${srcdir}/../ls/cmp.c
+
+ls.o: ${srcdir}/../ls/ls.c
+ ${CC} ${CFLAGS} -c -o ls.o ${srcdir}/../ls/ls.c
+
+print.o: ${srcdir}/../ls/print.c
+ ${CC} ${CFLAGS} -c -o print.o ${srcdir}/../ls/print.c
+
+stat_flags.o: ${srcdir}/../ls/stat_flags.c
+ ${CC} ${CFLAGS} -c -o stat_flags.o ${srcdir}/../ls/stat_flags.c
+
+util.o: ${srcdir}/../ls/util.c
+ ${CC} ${CFLAGS} -c -o util.o ${srcdir}/../ls/util.c
+
+install: all
+ -mkdir -p ${sbindir}
+ ${INSTALL} -m 555 ${PROG} ${sbindir}
+ -mkdir -p ${mandircat5}
+ ${INSTALL} -m 444 ${srcdir}/ftpd.conf.cat5 ${mandircat5}/ftpd.conf.5
+ ${INSTALL} -m 444 ${srcdir}/ftpusers.cat5 ${mandircat5}/ftpusers.5
+ -mkdir -p ${mandircat8}
+ ${INSTALL} -m 444 ${srcdir}/${PROG}.cat8 ${mandircat8}/${PROG}.8
+
+${PROG}: ${OBJS} @LIBDEPENDS@
+ ${CC} ${CFLAGS} ${LDFLAGS} -o ${PROG} ${OBJS} ${LIBS}
+
+clean:
+ rm -f core ${PROG} ${OBJS}
+
+distclean: clean
+ rm -f Makefile
diff --git a/contrib/lukemftpd/src/arpaftp.h b/contrib/lukemftpd/src/arpaftp.h
new file mode 100644
index 0000000..52be7c1
--- /dev/null
+++ b/contrib/lukemftpd/src/arpaftp.h
@@ -0,0 +1,111 @@
+/* $NetBSD: ftp.h,v 1.5 1998/02/10 00:32:50 perry Exp $ */
+
+/*
+ * Copyright (c) 1983, 1989, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ * must display the following acknowledgement:
+ * This product includes software developed by the University of
+ * California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * @(#)ftp.h 8.1 (Berkeley) 6/2/93
+ */
+
+#ifndef _ARPA_FTP_H_
+#define _ARPA_FTP_H_
+
+/* Definitions for FTP; see RFC-765. */
+
+/*
+ * Reply codes.
+ */
+#define PRELIM 1 /* positive preliminary */
+#define COMPLETE 2 /* positive completion */
+#define CONTINUE 3 /* positive intermediate */
+#define TRANSIENT 4 /* transient negative completion */
+#define ERROR 5 /* permanent negative completion */
+
+/*
+ * Type codes
+ */
+#define TYPE_A 1 /* ASCII */
+#define TYPE_E 2 /* EBCDIC */
+#define TYPE_I 3 /* image */
+#define TYPE_L 4 /* local byte size */
+
+#ifdef FTP_NAMES
+char *typenames[] = {"0", "ASCII", "EBCDIC", "Image", "Local" };
+#endif
+
+/*
+ * Form codes
+ */
+#define FORM_N 1 /* non-print */
+#define FORM_T 2 /* telnet format effectors */
+#define FORM_C 3 /* carriage control (ASA) */
+#ifdef FTP_NAMES
+char *formnames[] = {"0", "Nonprint", "Telnet", "Carriage-control" };
+#endif
+
+/*
+ * Structure codes
+ */
+#define STRU_F 1 /* file (no record structure) */
+#define STRU_R 2 /* record structure */
+#define STRU_P 3 /* page structure */
+#ifdef FTP_NAMES
+char *strunames[] = {"0", "File", "Record", "Page" };
+#endif
+
+/*
+ * Mode types
+ */
+#define MODE_S 1 /* stream */
+#define MODE_B 2 /* block */
+#define MODE_C 3 /* compressed */
+#ifdef FTP_NAMES
+char *modenames[] = {"0", "Stream", "Block", "Compressed" };
+#endif
+
+/*
+ * Record Tokens
+ */
+#define REC_ESC '\377' /* Record-mode Escape */
+#define REC_EOR '\001' /* Record-mode End-of-Record */
+#define REC_EOF '\002' /* Record-mode End-of-File */
+
+/*
+ * Block Header
+ */
+#define BLK_EOR 0x80 /* Block is End-of-Record */
+#define BLK_EOF 0x40 /* Block is End-of-File */
+#define BLK_ERRORS 0x20 /* Block is suspected of containing errors */
+#define BLK_RESTART 0x10 /* Block is Restart Marker */
+
+#define BLK_BYTECOUNT 2 /* Bytes in this block */
+
+#endif /* _ARPA_FTP_H_ */
diff --git a/contrib/lukemftpd/src/cmds.c b/contrib/lukemftpd/src/cmds.c
new file mode 100644
index 0000000..51efbb8
--- /dev/null
+++ b/contrib/lukemftpd/src/cmds.c
@@ -0,0 +1,791 @@
+/* $NetBSD: cmds.c,v 1.13 2001/04/25 01:46:25 lukem Exp $ */
+
+/*
+ * Copyright (c) 1999-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.
+ */
+
+#include "lukemftpd.h"
+
+#include "extern.h"
+
+typedef struct {
+ const char *path; /* full pathname */
+ const char *display; /* name to display */
+ struct stat *stat; /* stat of path */
+ struct stat *pdirstat; /* stat of path's parent dir */
+ int iscurdir; /* nonzero if name is the current dir */
+} factelem;
+
+static void ack(const char *);
+static void base64_encode(const char *, size_t, char *, int);
+static void fact_type(const char *, FILE *, factelem *);
+static void fact_size(const char *, FILE *, factelem *);
+static void fact_modify(const char *, FILE *, factelem *);
+static void fact_perm(const char *, FILE *, factelem *);
+static void fact_unique(const char *, FILE *, factelem *);
+static int matchgroup(gid_t);
+static void mlsname(FILE *, factelem *);
+static void replydirname(const char *, const char *);
+
+struct ftpfact {
+ const char *name; /* name of fact */
+ int enabled; /* if fact is enabled */
+ void (*display)(const char *, FILE *, factelem *);
+ /* function to display fact */
+};
+
+struct ftpfact facttab[] = {
+ { "Type", 1, fact_type },
+#define FACT_TYPE 0
+ { "Size", 1, fact_size },
+ { "Modify", 1, fact_modify },
+ { "Perm", 1, fact_perm },
+ { "Unique", 1, fact_unique },
+ /* "Create" */
+ /* "Lang" */
+ /* "Media-Type" */
+ /* "CharSet" */
+};
+
+#define FACTTABSIZE (sizeof(facttab) / sizeof(struct ftpfact))
+
+
+void
+cwd(const char *path)
+{
+
+ if (chdir(path) < 0)
+ perror_reply(550, path);
+ else {
+ show_chdir_messages(250);
+ ack("CWD");
+ }
+}
+
+void
+delete(const char *name)
+{
+ char *p = NULL;
+
+ if (remove(name) < 0) {
+ p = strerror(errno);
+ perror_reply(550, name);
+ } else
+ ack("DELE");
+ logxfer("delete", -1, name, NULL, NULL, p);
+}
+
+void
+feat(void)
+{
+ int i;
+
+ reply(-211, "Features supported");
+ cprintf(stdout, " MDTM\r\n");
+ cprintf(stdout, " MLST ");
+ for (i = 0; i < FACTTABSIZE; i++)
+ cprintf(stdout, "%s%s;", facttab[i].name,
+ facttab[i].enabled ? "*" : "");
+ cprintf(stdout, "\r\n");
+ cprintf(stdout, " REST STREAM\r\n");
+ cprintf(stdout, " SIZE\r\n");
+ cprintf(stdout, " TVFS\r\n");
+ reply(211, "End");
+}
+
+void
+makedir(const char *name)
+{
+ char *p = NULL;
+
+ if (mkdir(name, 0777) < 0) {
+ p = strerror(errno);
+ perror_reply(550, name);
+ } else
+ replydirname(name, "directory created.");
+ logxfer("mkdir", -1, name, NULL, NULL, p);
+}
+
+void
+mlsd(const char *path)
+{
+ struct dirent *dp;
+ struct stat sb, pdirstat;
+ factelem f;
+ FILE *dout;
+ DIR *dirp;
+ char name[MAXPATHLEN];
+ int hastypefact;
+
+ hastypefact = facttab[FACT_TYPE].enabled;
+ if (path == NULL)
+ path = ".";
+ if (stat(path, &pdirstat) == -1) {
+ mlsdperror:
+ perror_reply(550, path);
+ return;
+ }
+ if (! S_ISDIR(pdirstat.st_mode)) {
+ errno = ENOTDIR;
+ perror_reply(501, path);
+ return;
+ }
+ dout = dataconn("MLSD", (off_t)-1, "w");
+ if (dout == NULL)
+ return;
+
+ if ((dirp = opendir(path)) == NULL)
+ goto mlsdperror;
+ f.stat = &sb;
+ while ((dp = readdir(dirp)) != NULL) {
+ snprintf(name, sizeof(name), "%s/%s", path, dp->d_name);
+ if (ISDOTDIR(dp->d_name)) { /* special case curdir: */
+ if (! hastypefact)
+ continue;
+ f.pdirstat = NULL; /* require stat of parent */
+ f.display = path; /* set name to real name */
+ f.iscurdir = 1; /* flag name is curdir */
+ } else {
+ if (ISDOTDOTDIR(dp->d_name)) {
+ if (! hastypefact)
+ continue;
+ f.pdirstat = NULL;
+ } else
+ f.pdirstat = &pdirstat; /* cache parent stat */
+ f.display = dp->d_name;
+ f.iscurdir = 0;
+ }
+ if (stat(name, &sb) == -1)
+ continue;
+ f.path = name;
+ mlsname(dout, &f);
+ }
+ (void)closedir(dirp);
+
+ if (ferror(dout) != 0)
+ perror_reply(550, "Data connection");
+ else
+ reply(226, "MLSD complete.");
+ closedataconn(dout);
+ total_xfers_out++;
+ total_xfers++;
+}
+
+void
+mlst(const char *path)
+{
+ struct stat sb;
+ factelem f;
+
+ if (path == NULL)
+ path = ".";
+ if (stat(path, &sb) == -1) {
+ perror_reply(550, path);
+ return;
+ }
+ reply(-250, "MLST %s", path);
+ f.path = path;
+ f.display = path;
+ f.stat = &sb;
+ f.pdirstat = NULL;
+ f.iscurdir = 0;
+ CPUTC(' ', stdout);
+ mlsname(stdout, &f);
+ reply(250, "End");
+}
+
+
+void
+opts(const char *command)
+{
+ struct tab *c;
+ char *ep;
+
+ if ((ep = strchr(command, ' ')) != NULL)
+ *ep++ = '\0';
+ c = lookup(cmdtab, command);
+ if (c == NULL) {
+ reply(502, "Unknown command %s.", command);
+ return;
+ }
+ if (! CMD_IMPLEMENTED(c)) {
+ reply(501, "%s command not implemented.", c->name);
+ return;
+ }
+ if (! CMD_HAS_OPTIONS(c)) {
+ reply(501, "%s command does not support persistent options.",
+ c->name);
+ return;
+ }
+
+ /* special case: MLST */
+ if (strcasecmp(command, "MLST") == 0) {
+ int enabled[FACTTABSIZE];
+ int i, onedone;
+ size_t len;
+ char *p;
+
+ for (i = 0; i < sizeof(enabled) / sizeof(int); i++)
+ enabled[i] = 0;
+ if (ep == NULL || *ep == '\0')
+ goto displaymlstopts;
+
+ /* don't like spaces, and need trailing ; */
+ len = strlen(ep);
+ if (strchr(ep, ' ') != NULL || ep[len - 1] != ';') {
+ badmlstopt:
+ reply(501, "Invalid MLST options");
+ return;
+ }
+ ep[len - 1] = '\0';
+ while ((p = strsep(&ep, ";")) != NULL) {
+ if (*p == '\0')
+ goto badmlstopt;
+ for (i = 0; i < FACTTABSIZE; i++)
+ if (strcasecmp(p, facttab[i].name) == 0) {
+ enabled[i] = 1;
+ break;
+ }
+ }
+
+ displaymlstopts:
+ for (i = 0; i < FACTTABSIZE; i++)
+ facttab[i].enabled = enabled[i];
+ cprintf(stdout, "200 MLST OPTS");
+ for (i = onedone = 0; i < FACTTABSIZE; i++) {
+ if (facttab[i].enabled) {
+ cprintf(stdout, "%s%s;", onedone ? "" : " ",
+ facttab[i].name);
+ onedone++;
+ }
+ }
+ cprintf(stdout, "\r\n");
+ fflush(stdout);
+ return;
+ }
+
+ /* default cases */
+ if (ep != NULL && *ep != '\0')
+ REASSIGN(c->options, xstrdup(ep));
+ if (c->options != NULL)
+ reply(200, "Options for %s are '%s'.", c->name,
+ c->options);
+ else
+ reply(200, "No options defined for %s.", c->name);
+}
+
+void
+pwd(void)
+{
+ char path[MAXPATHLEN];
+
+ if (getcwd(path, sizeof(path) - 1) == NULL)
+ reply(550, "Can't get the current directory: %s.",
+ strerror(errno));
+ else
+ replydirname(path, "is the current directory.");
+}
+
+void
+removedir(const char *name)
+{
+ char *p = NULL;
+
+ if (rmdir(name) < 0) {
+ p = strerror(errno);
+ perror_reply(550, name);
+ } else
+ ack("RMD");
+ logxfer("rmdir", -1, name, NULL, NULL, p);
+}
+
+char *
+renamefrom(const char *name)
+{
+ struct stat st;
+
+ if (stat(name, &st) < 0) {
+ perror_reply(550, name);
+ return (NULL);
+ }
+ reply(350, "File exists, ready for destination name");
+ return (xstrdup(name));
+}
+
+void
+renamecmd(const char *from, const char *to)
+{
+ char *p = NULL;
+
+ if (rename(from, to) < 0) {
+ p = strerror(errno);
+ perror_reply(550, "rename");
+ } else
+ ack("RNTO");
+ logxfer("rename", -1, from, to, NULL, p);
+}
+
+void
+sizecmd(const char *filename)
+{
+ switch (type) {
+ case TYPE_L:
+ case TYPE_I:
+ {
+ struct stat stbuf;
+ if (stat(filename, &stbuf) < 0 || !S_ISREG(stbuf.st_mode))
+ reply(550, "%s: not a plain file.", filename);
+ else
+ reply(213, ULLF, (ULLT)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 || !S_ISREG(stbuf.st_mode)) {
+ reply(550, "%s: not a plain file.", 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, LLF, (LLT)count);
+ break;
+ }
+ default:
+ reply(504, "SIZE not implemented for Type %c.", "?AEIL"[type]);
+ }
+}
+
+void
+statfilecmd(const char *filename)
+{
+ FILE *fin;
+ int c;
+ char *argv[] = { INTERNAL_LS, "-lgA", "", NULL };
+
+ argv[2] = (char *)filename;
+ fin = ftpd_popen(argv, "r", STDOUT_FILENO);
+ reply(-211, "status of %s:", filename);
+/* XXX: use fgetln() or fparseln() here? */
+ 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;
+ }
+ CPUTC('\r', stdout);
+ }
+ CPUTC(c, stdout);
+ }
+ (void) ftpd_pclose(fin);
+ reply(211, "End of Status");
+}
+
+/* -- */
+
+static void
+ack(const char *s)
+{
+
+ reply(250, "%s command successful.", s);
+}
+
+/*
+ * Encode len bytes starting at clear using base64 encoding into encoded,
+ * which should be at least ((len + 2) * 4 / 3 + 1) in size.
+ * If nulterm is non-zero, terminate with \0 otherwise pad to 3 byte boundary
+ * with `='.
+ */
+static void
+base64_encode(const char *clear, size_t len, char *encoded, int nulterm)
+{
+ static const char base64[] =
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
+ const char *c;
+ char *e, termchar;
+ int i;
+
+ /* determine whether to pad with '=' or NUL terminate */
+ termchar = nulterm ? '\0' : '=';
+ c = clear;
+ e = encoded;
+ /* convert all but last 2 bytes */
+ for (i = len; i > 2; i -= 3, c += 3) {
+ *e++ = base64[(c[0] >> 2) & 0x3f];
+ *e++ = base64[((c[0] << 4) & 0x30) | ((c[1] >> 4) & 0x0f)];
+ *e++ = base64[((c[1] << 2) & 0x3c) | ((c[2] >> 6) & 0x03)];
+ *e++ = base64[(c[2]) & 0x3f];
+ }
+ /* handle slop at end */
+ if (i > 0) {
+ *e++ = base64[(c[0] >> 2) & 0x3f];
+ *e++ = base64[((c[0] << 4) & 0x30) |
+ (i > 1 ? ((c[1] >> 4) & 0x0f) : 0)];
+ *e++ = (i > 1) ? base64[(c[1] << 2) & 0x3c] : termchar;
+ *e++ = termchar;
+ }
+ *e = '\0';
+}
+
+static void
+fact_modify(const char *fact, FILE *fd, factelem *fe)
+{
+ struct tm *t;
+
+ t = gmtime(&(fe->stat->st_mtime));
+ cprintf(fd, "%s=%04d%02d%02d%02d%02d%02d;", fact,
+ TM_YEAR_BASE + t->tm_year,
+ t->tm_mon+1, t->tm_mday,
+ t->tm_hour, t->tm_min, t->tm_sec);
+}
+
+static void
+fact_perm(const char *fact, FILE *fd, factelem *fe)
+{
+ int rok, wok, xok, pdirwok;
+ struct stat *pdir;
+
+ if (fe->stat->st_uid == geteuid()) {
+ rok = ((fe->stat->st_mode & S_IRUSR) != 0);
+ wok = ((fe->stat->st_mode & S_IWUSR) != 0);
+ xok = ((fe->stat->st_mode & S_IXUSR) != 0);
+ } else if (matchgroup(fe->stat->st_gid)) {
+ rok = ((fe->stat->st_mode & S_IRGRP) != 0);
+ wok = ((fe->stat->st_mode & S_IWGRP) != 0);
+ xok = ((fe->stat->st_mode & S_IXGRP) != 0);
+ } else {
+ rok = ((fe->stat->st_mode & S_IROTH) != 0);
+ wok = ((fe->stat->st_mode & S_IWOTH) != 0);
+ xok = ((fe->stat->st_mode & S_IXOTH) != 0);
+ }
+
+ cprintf(fd, "%s=", fact);
+
+ /*
+ * if parent info not provided, look it up, but
+ * only if the current class has modify rights,
+ * since we only need this info in such a case.
+ */
+ pdir = fe->pdirstat;
+ if (pdir == NULL && CURCLASS_FLAGS_ISSET(modify)) {
+ size_t len;
+ char realdir[MAXPATHLEN], *p;
+ struct stat dir;
+
+ len = strlcpy(realdir, fe->path, sizeof(realdir));
+ if (len < sizeof(realdir) - 4) {
+ if (S_ISDIR(fe->stat->st_mode))
+ strlcat(realdir, "/..", sizeof(realdir));
+ else {
+ /* if has a /, move back to it */
+ /* otherwise use '..' */
+ if ((p = strrchr(realdir, '/')) != NULL) {
+ if (p == realdir)
+ p++;
+ *p = '\0';
+ } else
+ strlcpy(realdir, "..", sizeof(realdir));
+ }
+ if (stat(realdir, &dir) == 0)
+ pdir = &dir;
+ }
+ }
+ pdirwok = 0;
+ if (pdir != NULL) {
+ if (pdir->st_uid == geteuid())
+ pdirwok = ((pdir->st_mode & S_IWUSR) != 0);
+ else if (matchgroup(pdir->st_gid))
+ pdirwok = ((pdir->st_mode & S_IWGRP) != 0);
+ else
+ pdirwok = ((pdir->st_mode & S_IWOTH) != 0);
+ }
+
+ /* 'a': can APPE to file */
+ if (wok && CURCLASS_FLAGS_ISSET(upload) && S_ISREG(fe->stat->st_mode))
+ CPUTC('a', fd);
+
+ /* 'c': can create or append to files in directory */
+ if (wok && CURCLASS_FLAGS_ISSET(modify) && S_ISDIR(fe->stat->st_mode))
+ CPUTC('c', fd);
+
+ /* 'd': can delete file or directory */
+ if (pdirwok && CURCLASS_FLAGS_ISSET(modify)) {
+ int candel;
+
+ candel = 1;
+ if (S_ISDIR(fe->stat->st_mode)) {
+ DIR *dirp;
+ struct dirent *dp;
+
+ if ((dirp = opendir(fe->display)) == NULL)
+ candel = 0;
+ else {
+ while ((dp = readdir(dirp)) != NULL) {
+ if (ISDOTDIR(dp->d_name) ||
+ ISDOTDOTDIR(dp->d_name))
+ continue;
+ candel = 0;
+ break;
+ }
+ closedir(dirp);
+ }
+ }
+ if (candel)
+ CPUTC('d', fd);
+ }
+
+ /* 'e': can enter directory */
+ if (xok && S_ISDIR(fe->stat->st_mode))
+ CPUTC('e', fd);
+
+ /* 'f': can rename file or directory */
+ if (pdirwok && CURCLASS_FLAGS_ISSET(modify))
+ CPUTC('f', fd);
+
+ /* 'l': can list directory */
+ if (rok && xok && S_ISDIR(fe->stat->st_mode))
+ CPUTC('l', fd);
+
+ /* 'm': can create directory */
+ if (wok && CURCLASS_FLAGS_ISSET(modify) && S_ISDIR(fe->stat->st_mode))
+ CPUTC('m', fd);
+
+ /* 'p': can remove files in directory */
+ if (wok && CURCLASS_FLAGS_ISSET(modify) && S_ISDIR(fe->stat->st_mode))
+ CPUTC('p', fd);
+
+ /* 'r': can RETR file */
+ if (rok && S_ISREG(fe->stat->st_mode))
+ CPUTC('r', fd);
+
+ /* 'w': can STOR file */
+ if (wok && CURCLASS_FLAGS_ISSET(upload) && S_ISREG(fe->stat->st_mode))
+ CPUTC('w', fd);
+
+ CPUTC(';', fd);
+}
+
+static void
+fact_size(const char *fact, FILE *fd, factelem *fe)
+{
+
+ if (S_ISREG(fe->stat->st_mode))
+ cprintf(fd, "%s=" LLF ";", fact, (LLT)fe->stat->st_size);
+}
+
+static void
+fact_type(const char *fact, FILE *fd, factelem *fe)
+{
+
+ cprintf(fd, "%s=", fact);
+ switch (fe->stat->st_mode & S_IFMT) {
+ case S_IFDIR:
+ if (fe->iscurdir || ISDOTDIR(fe->display))
+ cprintf(fd, "cdir");
+ else if (ISDOTDOTDIR(fe->display))
+ cprintf(fd, "pdir");
+ else
+ cprintf(fd, "dir");
+ break;
+ case S_IFREG:
+ cprintf(fd, "file");
+ break;
+ case S_IFIFO:
+ cprintf(fd, "OS.unix=fifo");
+ break;
+ case S_IFLNK: /* XXX: probably a NO-OP with stat() */
+ cprintf(fd, "OS.unix=slink");
+ break;
+ case S_IFSOCK:
+ cprintf(fd, "OS.unix=socket");
+ break;
+ case S_IFBLK:
+ case S_IFCHR:
+ cprintf(fd, "OS.unix=%s-%d/%d",
+ S_ISBLK(fe->stat->st_mode) ? "blk" : "chr",
+ major(fe->stat->st_rdev), minor(fe->stat->st_rdev));
+ break;
+ default:
+ cprintf(fd, "OS.unix=UNKNOWN(0%o)", fe->stat->st_mode & S_IFMT);
+ break;
+ }
+ CPUTC(';', fd);
+}
+
+static void
+fact_unique(const char *fact, FILE *fd, factelem *fe)
+{
+ char obuf[(sizeof(dev_t) + sizeof(ino_t) + 2) * 4 / 3 + 2];
+ char tbuf[sizeof(dev_t) + sizeof(ino_t)];
+
+ memcpy(tbuf,
+ (char *)&(fe->stat->st_dev), sizeof(dev_t));
+ memcpy(tbuf + sizeof(dev_t),
+ (char *)&(fe->stat->st_ino), sizeof(ino_t));
+ base64_encode(tbuf, sizeof(dev_t) + sizeof(ino_t), obuf, 1);
+ cprintf(fd, "%s=%s;", fact, obuf);
+}
+
+static int
+matchgroup(gid_t gid)
+{
+ int i;
+
+ for (i = 0; i < gidcount; i++)
+ if (gid == gidlist[i])
+ return(1);
+ return (0);
+}
+
+static void
+mlsname(FILE *fp, factelem *fe)
+{
+ int i;
+
+ for (i = 0; i < FACTTABSIZE; i++) {
+ if (facttab[i].enabled)
+ (facttab[i].display)(facttab[i].name, fp, fe);
+ }
+ cprintf(fp, " %s\r\n", fe->display);
+}
+
+static void
+replydirname(const char *name, const char *message)
+{
+ char *p, *ep;
+ char npath[MAXPATHLEN * 2];
+
+ p = npath;
+ ep = &npath[sizeof(npath) - 1];
+ while (*name) {
+ if (*name == '"') {
+ if (ep - p < 2)
+ break;
+ *p++ = *name++;
+ *p++ = '"';
+ } else {
+ if (ep - p < 1)
+ break;
+ *p++ = *name++;
+ }
+ }
+ *p = '\0';
+ reply(257, "\"%s\" %s", npath, message);
+}
diff --git a/contrib/lukemftpd/src/conf.c b/contrib/lukemftpd/src/conf.c
new file mode 100644
index 0000000..19b9283
--- /dev/null
+++ b/contrib/lukemftpd/src/conf.c
@@ -0,0 +1,1007 @@
+/* $NetBSD: conf.c,v 1.41 2001/04/25 01:46:25 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 Simon Burge and 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.
+ */
+
+#include "lukemftpd.h"
+
+#include "extern.h"
+#include "pathnames.h"
+
+static char *strend(const char *, char *);
+static int filetypematch(char *, int);
+
+
+ /* class defaults */
+#define DEFAULT_LIMIT -1 /* unlimited connections */
+#define DEFAULT_MAXFILESIZE -1 /* unlimited file size */
+#define DEFAULT_MAXTIMEOUT 7200 /* 2 hours */
+#define DEFAULT_TIMEOUT 900 /* 15 minutes */
+#define DEFAULT_UMASK 027 /* 15 minutes */
+
+/*
+ * Initialise curclass to an `empty' state
+ */
+void
+init_curclass(void)
+{
+ struct ftpconv *conv, *cnext;
+
+ for (conv = curclass.conversions; conv != NULL; conv = cnext) {
+ REASSIGN(conv->suffix, NULL);
+ REASSIGN(conv->types, NULL);
+ REASSIGN(conv->disable, NULL);
+ REASSIGN(conv->command, NULL);
+ cnext = conv->next;
+ free(conv);
+ }
+
+ memset((char *)&curclass.advertise, 0, sizeof(curclass.advertise));
+ curclass.advertise.su_len = 0; /* `not used' */
+ REASSIGN(curclass.chroot, NULL);
+ REASSIGN(curclass.classname, NULL);
+ curclass.conversions = NULL;
+ REASSIGN(curclass.display, NULL);
+ REASSIGN(curclass.homedir, NULL);
+ curclass.limit = DEFAULT_LIMIT;
+ REASSIGN(curclass.limitfile, NULL);
+ curclass.maxfilesize = DEFAULT_MAXFILESIZE;
+ curclass.maxrateget = 0;
+ curclass.maxrateput = 0;
+ curclass.maxtimeout = DEFAULT_MAXTIMEOUT;
+ REASSIGN(curclass.motd, xstrdup(_PATH_FTPLOGINMESG));
+ REASSIGN(curclass.notify, NULL);
+ curclass.portmin = 0;
+ curclass.portmax = 0;
+ curclass.rateget = 0;
+ curclass.rateput = 0;
+ curclass.timeout = DEFAULT_TIMEOUT;
+ /* curclass.type is set elsewhere */
+ curclass.umask = DEFAULT_UMASK;
+
+ CURCLASS_FLAGS_SET(checkportcmd);
+ CURCLASS_FLAGS_SET(modify);
+ CURCLASS_FLAGS_SET(passive);
+ CURCLASS_FLAGS_CLR(sanenames);
+ CURCLASS_FLAGS_SET(upload);
+}
+
+/*
+ * Parse the configuration file, looking for the named class, and
+ * define curclass to contain the appropriate settings.
+ */
+void
+parse_conf(const char *findclass)
+{
+ FILE *f;
+ char *buf, *p;
+ size_t len;
+ LLT llval;
+ int none, match;
+ char *endp;
+ char *class, *word, *arg, *template;
+ const char *infile;
+ size_t line;
+ unsigned int timeout;
+ struct ftpconv *conv, *cnext;
+
+ init_curclass();
+ REASSIGN(curclass.classname, xstrdup(findclass));
+ /* set more guest defaults */
+ if (strcasecmp(findclass, "guest") == 0) {
+ CURCLASS_FLAGS_CLR(modify);
+ curclass.umask = 0707;
+ }
+
+ infile = conffilename(_PATH_FTPDCONF);
+ if ((f = fopen(infile, "r")) == NULL)
+ return;
+
+ line = 0;
+ template = NULL;
+ for (;
+ (buf = fparseln(f, &len, &line, NULL, FPARSELN_UNESCCOMM |
+ FPARSELN_UNESCCONT | FPARSELN_UNESCESC)) != NULL;
+ free(buf)) {
+ none = match = 0;
+ p = buf;
+ if (len < 1)
+ continue;
+ if (p[len - 1] == '\n')
+ p[--len] = '\0';
+ if (EMPTYSTR(p))
+ continue;
+
+ NEXTWORD(p, word);
+ NEXTWORD(p, class);
+ NEXTWORD(p, arg);
+ if (EMPTYSTR(word) || EMPTYSTR(class))
+ continue;
+ if (strcasecmp(class, "none") == 0)
+ none = 1;
+ if (! (strcasecmp(class, findclass) == 0 ||
+ (template != NULL && strcasecmp(class, template) == 0) ||
+ none ||
+ strcasecmp(class, "all") == 0) )
+ continue;
+
+#define CONF_FLAG(x) \
+ do { \
+ if (none || \
+ (!EMPTYSTR(arg) && strcasecmp(arg, "off") == 0)) \
+ CURCLASS_FLAGS_CLR(x); \
+ else \
+ CURCLASS_FLAGS_SET(x); \
+ } while (0)
+
+#define CONF_STRING(x) \
+ do { \
+ if (none || EMPTYSTR(arg)) \
+ arg = NULL; \
+ else \
+ arg = xstrdup(arg); \
+ REASSIGN(curclass.x, arg); \
+ } while (0)
+
+
+ if (0) {
+ /* no-op */
+
+ } else if (strcasecmp(word, "advertise") == 0) {
+ struct addrinfo hints, *res;
+ int error;
+
+ memset((char *)&curclass.advertise, 0,
+ sizeof(curclass.advertise));
+ curclass.advertise.su_len = 0;
+ if (none || EMPTYSTR(arg))
+ continue;
+ res = NULL;
+ memset(&hints, 0, sizeof(hints));
+ /*
+ * only get addresses of the family
+ * that we're listening on
+ */
+ hints.ai_family = ctrl_addr.su_family;
+ hints.ai_socktype = SOCK_STREAM;
+ error = getaddrinfo(arg, "0", &hints, &res);
+ if (error) {
+ syslog(LOG_WARNING, "%s line %d: %s",
+ infile, (int)line, gai_strerror(error));
+ advertiseparsefail:
+ if (res)
+ freeaddrinfo(res);
+ continue;
+ }
+ if (res->ai_next) {
+ syslog(LOG_WARNING,
+ "%s line %d: multiple addresses returned for `%s'; please be more specific",
+ infile, (int)line, arg);
+ goto advertiseparsefail;
+ }
+ if (sizeof(curclass.advertise) < res->ai_addrlen || (
+#ifdef INET6
+ res->ai_family != AF_INET6 &&
+#endif
+ res->ai_family != AF_INET)) {
+ syslog(LOG_WARNING,
+ "%s line %d: unsupported protocol %d for `%s'",
+ infile, (int)line, res->ai_family, arg);
+ goto advertiseparsefail;
+ }
+ memcpy(&curclass.advertise, res->ai_addr,
+ res->ai_addrlen);
+ curclass.advertise.su_len = res->ai_addrlen;
+ freeaddrinfo(res);
+
+ } else if (strcasecmp(word, "checkportcmd") == 0) {
+ CONF_FLAG(checkportcmd);
+
+ } else if (strcasecmp(word, "chroot") == 0) {
+ CONF_STRING(chroot);
+
+ } else if (strcasecmp(word, "classtype") == 0) {
+ if (!none && !EMPTYSTR(arg)) {
+ if (strcasecmp(arg, "GUEST") == 0)
+ curclass.type = CLASS_GUEST;
+ else if (strcasecmp(arg, "CHROOT") == 0)
+ curclass.type = CLASS_CHROOT;
+ else if (strcasecmp(arg, "REAL") == 0)
+ curclass.type = CLASS_REAL;
+ else {
+ syslog(LOG_WARNING,
+ "%s line %d: unknown class type `%s'",
+ infile, (int)line, arg);
+ continue;
+ }
+ }
+
+ } else if (strcasecmp(word, "conversion") == 0) {
+ char *suffix, *types, *disable, *convcmd;
+
+ if (EMPTYSTR(arg)) {
+ syslog(LOG_WARNING,
+ "%s line %d: %s requires a suffix",
+ infile, (int)line, word);
+ continue; /* need a suffix */
+ }
+ NEXTWORD(p, types);
+ NEXTWORD(p, disable);
+ convcmd = p;
+ if (convcmd)
+ convcmd += strspn(convcmd, " \t");
+ suffix = xstrdup(arg);
+ if (none || EMPTYSTR(types) ||
+ EMPTYSTR(disable) || EMPTYSTR(convcmd)) {
+ types = NULL;
+ disable = NULL;
+ convcmd = NULL;
+ } else {
+ types = xstrdup(types);
+ disable = xstrdup(disable);
+ convcmd = xstrdup(convcmd);
+ }
+ for (conv = curclass.conversions; conv != NULL;
+ conv = conv->next) {
+ if (strcmp(conv->suffix, suffix) == 0)
+ break;
+ }
+ if (conv == NULL) {
+ conv = (struct ftpconv *)
+ calloc(1, sizeof(struct ftpconv));
+ if (conv == NULL) {
+ syslog(LOG_WARNING, "can't malloc");
+ continue;
+ }
+ conv->next = NULL;
+ for (cnext = curclass.conversions;
+ cnext != NULL; cnext = cnext->next)
+ if (cnext->next == NULL)
+ break;
+ if (cnext != NULL)
+ cnext->next = conv;
+ else
+ curclass.conversions = conv;
+ }
+ REASSIGN(conv->suffix, suffix);
+ REASSIGN(conv->types, types);
+ REASSIGN(conv->disable, disable);
+ REASSIGN(conv->command, convcmd);
+
+ } else if (strcasecmp(word, "display") == 0) {
+ CONF_STRING(display);
+
+ } else if (strcasecmp(word, "homedir") == 0) {
+ CONF_STRING(homedir);
+
+ } else if (strcasecmp(word, "limit") == 0) {
+ int limit;
+
+ curclass.limit = DEFAULT_LIMIT;
+ REASSIGN(curclass.limitfile, NULL);
+ if (none || EMPTYSTR(arg))
+ continue;
+ limit = (int)strtol(arg, &endp, 10);
+ if (*endp != 0) {
+ syslog(LOG_WARNING,
+ "%s line %d: invalid limit %s",
+ infile, (int)line, arg);
+ continue;
+ }
+ curclass.limit = limit;
+ REASSIGN(curclass.limitfile,
+ EMPTYSTR(p) ? NULL : xstrdup(p));
+
+ } else if (strcasecmp(word, "maxfilesize") == 0) {
+ curclass.maxfilesize = DEFAULT_MAXFILESIZE;
+ if (none || EMPTYSTR(arg))
+ continue;
+ llval = strsuftoll(arg);
+ if (llval == -1) {
+ syslog(LOG_WARNING,
+ "%s line %d: invalid maxfilesize %s",
+ infile, (int)line, arg);
+ continue;
+ }
+ curclass.maxfilesize = llval;
+
+ } else if (strcasecmp(word, "maxtimeout") == 0) {
+ curclass.maxtimeout = DEFAULT_MAXTIMEOUT;
+ if (none || EMPTYSTR(arg))
+ continue;
+ timeout = (unsigned int)strtoul(arg, &endp, 10);
+ if (*endp != 0) {
+ syslog(LOG_WARNING,
+ "%s line %d: invalid maxtimeout %s",
+ infile, (int)line, arg);
+ continue;
+ }
+ if (timeout < 30) {
+ syslog(LOG_WARNING,
+ "%s line %d: maxtimeout %d < 30 seconds",
+ infile, (int)line, timeout);
+ continue;
+ }
+ if (timeout < curclass.timeout) {
+ syslog(LOG_WARNING,
+ "%s line %d: maxtimeout %d < timeout (%d)",
+ infile, (int)line, timeout,
+ curclass.timeout);
+ continue;
+ }
+ curclass.maxtimeout = timeout;
+
+ } else if (strcasecmp(word, "modify") == 0) {
+ CONF_FLAG(modify);
+
+ } else if (strcasecmp(word, "motd") == 0) {
+ CONF_STRING(motd);
+
+ } else if (strcasecmp(word, "notify") == 0) {
+ CONF_STRING(notify);
+
+ } else if (strcasecmp(word, "passive") == 0) {
+ CONF_FLAG(passive);
+
+ } else if (strcasecmp(word, "portrange") == 0) {
+ int minport, maxport;
+ char *min, *max;
+
+ curclass.portmin = 0;
+ curclass.portmax = 0;
+ if (none || EMPTYSTR(arg))
+ continue;
+ min = arg;
+ NEXTWORD(p, max);
+ if (EMPTYSTR(max)) {
+ syslog(LOG_WARNING,
+ "%s line %d: missing maxport argument",
+ infile, (int)line);
+ continue;
+ }
+ minport = (int)strtol(min, &endp, 10);
+ if (*endp != 0 || minport < IPPORT_RESERVED ||
+ minport > IPPORT_ANONMAX) {
+ syslog(LOG_WARNING,
+ "%s line %d: invalid minport %s",
+ infile, (int)line, min);
+ continue;
+ }
+ maxport = (int)strtol(max, &endp, 10);
+ if (*endp != 0 || maxport < IPPORT_RESERVED ||
+ maxport > IPPORT_ANONMAX) {
+ syslog(LOG_WARNING,
+ "%s line %d: invalid maxport %s",
+ infile, (int)line, max);
+ continue;
+ }
+ if (minport >= maxport) {
+ syslog(LOG_WARNING,
+ "%s line %d: minport %d >= maxport %d",
+ infile, (int)line, minport, maxport);
+ continue;
+ }
+ curclass.portmin = minport;
+ curclass.portmax = maxport;
+
+ } else if (strcasecmp(word, "rateget") == 0) {
+ curclass.maxrateget = 0;
+ curclass.rateget = 0;
+ if (none || EMPTYSTR(arg))
+ continue;
+ llval = strsuftoll(arg);
+ if (llval == -1) {
+ syslog(LOG_WARNING,
+ "%s line %d: invalid rateget %s",
+ infile, (int)line, arg);
+ continue;
+ }
+ curclass.maxrateget = llval;
+ curclass.rateget = llval;
+
+ } else if (strcasecmp(word, "rateput") == 0) {
+ curclass.maxrateput = 0;
+ curclass.rateput = 0;
+ if (none || EMPTYSTR(arg))
+ continue;
+ llval = strsuftoll(arg);
+ if (llval == -1) {
+ syslog(LOG_WARNING,
+ "%s line %d: invalid rateput %s",
+ infile, (int)line, arg);
+ continue;
+ }
+ curclass.maxrateput = llval;
+ curclass.rateput = llval;
+
+ } else if (strcasecmp(word, "sanenames") == 0) {
+ CONF_FLAG(sanenames);
+
+ } else if (strcasecmp(word, "timeout") == 0) {
+ curclass.timeout = DEFAULT_TIMEOUT;
+ if (none || EMPTYSTR(arg))
+ continue;
+ timeout = (unsigned int)strtoul(arg, &endp, 10);
+ if (*endp != 0) {
+ syslog(LOG_WARNING,
+ "%s line %d: invalid timeout %s",
+ infile, (int)line, arg);
+ continue;
+ }
+ if (timeout < 30) {
+ syslog(LOG_WARNING,
+ "%s line %d: timeout %d < 30 seconds",
+ infile, (int)line, timeout);
+ continue;
+ }
+ if (timeout > curclass.maxtimeout) {
+ syslog(LOG_WARNING,
+ "%s line %d: timeout %d > maxtimeout (%d)",
+ infile, (int)line, timeout,
+ curclass.maxtimeout);
+ continue;
+ }
+ curclass.timeout = timeout;
+
+ } else if (strcasecmp(word, "template") == 0) {
+ if (none)
+ continue;
+ REASSIGN(template, EMPTYSTR(arg) ? NULL : xstrdup(arg));
+
+ } else if (strcasecmp(word, "umask") == 0) {
+ mode_t umask;
+
+ curclass.umask = DEFAULT_UMASK;
+ if (none || EMPTYSTR(arg))
+ continue;
+ umask = (mode_t)strtoul(arg, &endp, 8);
+ if (*endp != 0 || umask > 0777) {
+ syslog(LOG_WARNING,
+ "%s line %d: invalid umask %s",
+ infile, (int)line, arg);
+ continue;
+ }
+ curclass.umask = umask;
+
+ } else if (strcasecmp(word, "upload") == 0) {
+ CONF_FLAG(upload);
+ if (! CURCLASS_FLAGS_ISSET(upload))
+ CURCLASS_FLAGS_CLR(modify);
+
+ } else {
+ syslog(LOG_WARNING,
+ "%s line %d: unknown directive '%s'",
+ infile, (int)line, word);
+ continue;
+ }
+ }
+ REASSIGN(template, NULL);
+ fclose(f);
+}
+
+/*
+ * Show file listed in curclass.display first time in, and list all the
+ * files named in curclass.notify in the current directory.
+ * Send back responses with the prefix `code' + "-".
+ * If code == -1, flush the internal cache of directory names and return.
+ */
+void
+show_chdir_messages(int code)
+{
+ static StringList *slist = NULL;
+
+ struct stat st;
+ struct tm *t;
+ glob_t gl;
+ time_t now, then;
+ int age;
+ char cwd[MAXPATHLEN];
+ char *cp, **rlist;
+
+ if (code == -1) {
+ if (slist != NULL)
+ sl_free(slist, 1);
+ slist = NULL;
+ return;
+ }
+
+ if (quietmessages)
+ return;
+
+ /* Setup list for directory cache */
+ if (slist == NULL)
+ slist = sl_init();
+ if (slist == NULL) {
+ syslog(LOG_WARNING, "can't allocate memory for stringlist");
+ return;
+ }
+
+ /* Check if this directory has already been visited */
+ if (getcwd(cwd, sizeof(cwd) - 1) == NULL) {
+ syslog(LOG_WARNING, "can't getcwd: %s", strerror(errno));
+ return;
+ }
+ if (sl_find(slist, cwd) != NULL)
+ return;
+
+ cp = xstrdup(cwd);
+ if (sl_add(slist, cp) == -1)
+ syslog(LOG_WARNING, "can't add `%s' to stringlist", cp);
+
+ /* First check for a display file */
+ (void)display_file(curclass.display, code);
+
+ /* Now see if there are any notify files */
+ if (EMPTYSTR(curclass.notify))
+ return;
+
+ gl.gl_offs = 0;
+ if (glob(curclass.notify, GLOB_LIMIT, NULL, &gl) != 0
+ || gl.gl_matchc == 0) {
+ globfree(&gl);
+ return;
+ }
+ time(&now);
+ for (rlist = gl.gl_pathv; *rlist != NULL; rlist++) {
+ if (stat(*rlist, &st) != 0)
+ continue;
+ if (!S_ISREG(st.st_mode))
+ continue;
+ then = st.st_mtime;
+ if (code != 0) {
+ reply(-code, "%s", "");
+ code = 0;
+ }
+ reply(-code, "Please read the file %s", *rlist);
+ t = localtime(&now);
+ age = 365 * t->tm_year + t->tm_yday;
+ t = localtime(&then);
+ age -= 365 * t->tm_year + t->tm_yday;
+ reply(-code, " it was last modified on %.24s - %d day%s ago",
+ ctime(&then), age, PLURAL(age));
+ }
+ globfree(&gl);
+}
+
+int
+display_file(const char *file, int code)
+{
+ FILE *f;
+ char *buf, *p, *cwd;
+ size_t len;
+ off_t lastnum;
+ time_t now;
+
+ lastnum = 0;
+ if (quietmessages)
+ return (0);
+
+ if (EMPTYSTR(file))
+ return(0);
+ if ((f = fopen(file, "r")) == NULL)
+ return (0);
+ reply(-code, "%s", "");
+
+ for (;
+ (buf = fparseln(f, &len, NULL, "\0\0\0", 0)) != NULL; free(buf)) {
+ if (len > 0)
+ if (buf[len - 1] == '\n')
+ buf[--len] = '\0';
+ cprintf(stdout, " ");
+
+ for (p = buf; *p; p++) {
+ if (*p == '%') {
+ p++;
+ switch (*p) {
+
+ case 'c':
+ cprintf(stdout, "%s",
+ curclass.classname ?
+ curclass.classname : "<unknown>");
+ break;
+
+ case 'C':
+ if (getcwd(cwd, sizeof(cwd)-1) == NULL){
+ syslog(LOG_WARNING,
+ "can't getcwd: %s",
+ strerror(errno));
+ continue;
+ }
+ cprintf(stdout, "%s", cwd);
+ break;
+
+ case 'E':
+ if (! EMPTYSTR(emailaddr))
+ cprintf(stdout, "%s",
+ emailaddr);
+ break;
+
+ case 'L':
+ cprintf(stdout, "%s", hostname);
+ break;
+
+ case 'M':
+ if (curclass.limit == -1) {
+ cprintf(stdout, "unlimited");
+ lastnum = 0;
+ } else {
+ cprintf(stdout, "%d",
+ curclass.limit);
+ lastnum = curclass.limit;
+ }
+ break;
+
+ case 'N':
+ cprintf(stdout, "%d", connections);
+ lastnum = connections;
+ break;
+
+ case 'R':
+ cprintf(stdout, "%s", remotehost);
+ break;
+
+ case 's':
+ if (lastnum != 1)
+ cprintf(stdout, "s");
+ break;
+
+ case 'S':
+ if (lastnum != 1)
+ cprintf(stdout, "S");
+ break;
+
+ case 'T':
+ now = time(NULL);
+ cprintf(stdout, "%.24s", ctime(&now));
+ break;
+
+ case 'U':
+ cprintf(stdout, "%s",
+ pw ? pw->pw_name : "<unknown>");
+ break;
+
+ case '%':
+ CPUTC('%', stdout);
+ break;
+
+ }
+ } else
+ CPUTC(*p, stdout);
+ }
+ cprintf(stdout, "\r\n");
+ }
+
+ (void)fflush(stdout);
+ (void)fclose(f);
+ return (1);
+}
+
+/*
+ * Parse src, expanding '%' escapes, into dst (which must be at least
+ * MAXPATHLEN long).
+ */
+void
+format_path(char *dst, const char *src)
+{
+ size_t len;
+ const char *p;
+
+ dst[0] = '\0';
+ len = 0;
+ if (src == NULL)
+ return;
+ for (p = src; *p && len < MAXPATHLEN; p++) {
+ if (*p == '%') {
+ p++;
+ switch (*p) {
+
+ case 'c':
+ len += strlcpy(dst + len, curclass.classname,
+ MAXPATHLEN - len);
+ break;
+
+ case 'd':
+ len += strlcpy(dst + len, pw->pw_dir,
+ MAXPATHLEN - len);
+ break;
+
+ case 'u':
+ len += strlcpy(dst + len, pw->pw_name,
+ MAXPATHLEN - len);
+ break;
+
+ case '%':
+ dst[len++] = '%';
+ break;
+
+ }
+ } else
+ dst[len++] = *p;
+ }
+ if (len < MAXPATHLEN)
+ dst[len] = '\0';
+ dst[MAXPATHLEN - 1] = '\0';
+}
+
+/*
+ * Find s2 at the end of s1. If found, return a string up to (but
+ * not including) s2, otherwise returns NULL.
+ */
+static char *
+strend(const char *s1, char *s2)
+{
+ static char buf[MAXPATHLEN];
+
+ char *start;
+ size_t l1, l2;
+
+ l1 = strlen(s1);
+ l2 = strlen(s2);
+
+ if (l2 >= l1)
+ return(NULL);
+
+ strlcpy(buf, s1, sizeof(buf));
+ start = buf + (l1 - l2);
+
+ if (strcmp(start, s2) == 0) {
+ *start = '\0';
+ return(buf);
+ } else
+ return(NULL);
+}
+
+static int
+filetypematch(char *types, int mode)
+{
+ for ( ; types[0] != '\0'; types++)
+ switch (*types) {
+ case 'd':
+ if (S_ISDIR(mode))
+ return(1);
+ break;
+ case 'f':
+ if (S_ISREG(mode))
+ return(1);
+ break;
+ }
+ return(0);
+}
+
+/*
+ * Look for a conversion. If we succeed, return a pointer to the
+ * command to execute for the conversion.
+ *
+ * The command is stored in a static array so there's no memory
+ * leak problems, and not too much to change in ftpd.c. This
+ * routine doesn't need to be re-entrant unless we start using a
+ * multi-threaded ftpd, and that's not likely for a while...
+ */
+char **
+do_conversion(const char *fname)
+{
+ struct ftpconv *cp;
+ struct stat st;
+ int o_errno;
+ char *base = NULL;
+ char *cmd, *p, *lp, **argv;
+ StringList *sl;
+
+ o_errno = errno;
+ sl = NULL;
+ cmd = NULL;
+ for (cp = curclass.conversions; cp != NULL; cp = cp->next) {
+ if (cp->suffix == NULL) {
+ syslog(LOG_WARNING,
+ "cp->suffix==NULL in conv list; SHOULDN'T HAPPEN!");
+ continue;
+ }
+ if ((base = strend(fname, cp->suffix)) == NULL)
+ continue;
+ if (cp->types == NULL || cp->disable == NULL ||
+ cp->command == NULL)
+ continue;
+ /* Is it enabled? */
+ if (strcmp(cp->disable, ".") != 0 &&
+ stat(cp->disable, &st) == 0)
+ continue;
+ /* Does the base exist? */
+ if (stat(base, &st) < 0)
+ continue;
+ /* Is the file type ok */
+ if (!filetypematch(cp->types, st.st_mode))
+ continue;
+ break; /* "We have a winner!" */
+ }
+
+ /* If we got through the list, no conversion */
+ if (cp == NULL)
+ goto cleanup_do_conv;
+
+ /* Split up command into an argv */
+ if ((sl = sl_init()) == NULL)
+ goto cleanup_do_conv;
+ cmd = xstrdup(cp->command);
+ p = cmd;
+ while (p) {
+ NEXTWORD(p, lp);
+ if (strcmp(lp, "%s") == 0)
+ lp = base;
+ if (sl_add(sl, xstrdup(lp)) == -1)
+ goto cleanup_do_conv;
+ }
+
+ if (sl_add(sl, NULL) == -1)
+ goto cleanup_do_conv;
+ argv = sl->sl_str;
+ free(cmd);
+ free(sl);
+ return(argv);
+
+ cleanup_do_conv:
+ if (sl)
+ sl_free(sl, 1);
+ free(cmd);
+ errno = o_errno;
+ return(NULL);
+}
+
+/*
+ * Convert the string `arg' to a long long, which may have an optional SI suffix
+ * (`b', `k', `m', `g', `t'). Returns the number for success, -1 otherwise.
+ */
+LLT
+strsuftoll(const char *arg)
+{
+ char *cp;
+ LLT val;
+
+ if (!isdigit((unsigned char)arg[0]))
+ return (-1);
+
+ val = STRTOLL(arg, &cp, 10);
+ if (cp != NULL) {
+ if (cp[0] != '\0' && cp[1] != '\0')
+ return (-1);
+ switch (tolower((unsigned char)cp[0])) {
+ case '\0':
+ case 'b':
+ break;
+ case 'k':
+ val <<= 10;
+ break;
+ case 'm':
+ val <<= 20;
+ break;
+ case 'g':
+ val <<= 30;
+ break;
+#ifndef NO_LONG_LONG
+ case 't':
+ val <<= 40;
+ break;
+#endif
+ default:
+ return (-1);
+ }
+ }
+ if (val < 0)
+ return (-1);
+
+ return (val);
+}
+
+/*
+ * Count the number of current connections, reading from
+ * /var/run/ftpd.pids-<class>
+ * Does a kill -0 on each pid in that file, and only counts
+ * processes that exist (or frees the slot if it doesn't).
+ * Adds getpid() to the first free slot. Truncates the file
+ * if possible.
+ */
+void
+count_users(void)
+{
+ char fn[MAXPATHLEN];
+ int fd, i, last;
+ size_t count;
+ pid_t *pids, mypid;
+ struct stat sb;
+
+ (void)strlcpy(fn, _PATH_CLASSPIDS, sizeof(fn));
+ (void)strlcat(fn, curclass.classname, sizeof(fn));
+ pids = NULL;
+ connections = 1;
+
+ if ((fd = open(fn, O_RDWR | O_CREAT, 0600)) == -1)
+ return;
+#if HAVE_LOCKF
+ if (lockf(fd, F_TLOCK, 0) == -1)
+ goto cleanup_count;
+#elif HAVE_FLOCK
+ if (flock(fd, LOCK_EX | LOCK_NB) != 0)
+ goto cleanup_count;
+#else
+ /* XXX: use fcntl ? */
+#endif
+ if (fstat(fd, &sb) == -1)
+ goto cleanup_count;
+ if ((pids = malloc(sb.st_size + sizeof(pid_t))) == NULL)
+ goto cleanup_count;
+ count = read(fd, pids, sb.st_size);
+ if (count < 0 || count != sb.st_size)
+ goto cleanup_count;
+ count /= sizeof(pid_t);
+ mypid = getpid();
+ last = 0;
+ for (i = 0; i < count; i++) {
+ if (pids[i] == 0)
+ continue;
+ if (kill(pids[i], 0) == -1 && errno != EPERM) {
+ if (mypid != 0) {
+ pids[i] = mypid;
+ mypid = 0;
+ last = i;
+ }
+ } else {
+ connections++;
+ last = i;
+ }
+ }
+ if (mypid != 0) {
+ if (pids[last] != 0)
+ last++;
+ pids[last] = mypid;
+ }
+ count = (last + 1) * sizeof(pid_t);
+ if (lseek(fd, 0, SEEK_SET) == -1)
+ goto cleanup_count;
+ if (write(fd, pids, count) == -1)
+ goto cleanup_count;
+ (void)ftruncate(fd, count);
+
+ cleanup_count:
+#if HAVE_LOCKF
+ if (lseek(fd, 0, SEEK_SET) != -1)
+ (void)lockf(fd, F_ULOCK, 0);
+#elif HAVE_FLOCK
+ (void)flock(fd, LOCK_UN);
+#else
+ /* XXX: use fcntl ? */
+#endif
+ close(fd);
+ REASSIGN(pids, NULL);
+}
diff --git a/contrib/lukemftpd/src/extern.h b/contrib/lukemftpd/src/extern.h
new file mode 100644
index 0000000..15cf939
--- /dev/null
+++ b/contrib/lukemftpd/src/extern.h
@@ -0,0 +1,372 @@
+/* $NetBSD: extern.h,v 1.41 2001/04/25 01:46:25 lukem Exp $ */
+
+/*-
+ * Copyright (c) 1992, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ * must display the following acknowledgement:
+ * This product includes software developed by the University of
+ * California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * @(#)extern.h 8.2 (Berkeley) 4/4/94
+ */
+
+/*-
+ * 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) 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.
+ */
+
+#ifdef NO_LONG_LONG
+# define LLF "%ld"
+# define LLFP(x) "%" x "ld"
+# define LLT long
+# define ULLF "%lu"
+# define ULLFP(x) "%" x "lu"
+# define ULLT unsigned long
+# define STRTOLL(x,y,z) strtol(x,y,z)
+#else
+#if HAVE_PRINTF_QD
+# define LLF "%qd"
+# define LLFP(x) "%" x "qd"
+# define LLT long long
+# define ULLF "%qu"
+# define ULLFP(x) "%" x "qu"
+# define ULLT unsigned long long
+# define STRTOLL(x,y,z) strtoll(x,y,z)
+#else
+# define LLF "%lld"
+# define LLFP(x) "%" x "lld"
+# define LLT long long
+# define ULLF "%llu"
+# define ULLFP(x) "%" x "llu"
+# define ULLT unsigned long long
+# define STRTOLL(x,y,z) strtoll(x,y,z)
+#endif
+#endif
+
+#define FTP_BUFLEN 512
+
+void abor(void);
+void blkfree(char **);
+void closedataconn(FILE *);
+char *conffilename(const char *);
+char **copyblk(char **);
+void count_users(void);
+void cprintf(FILE *, const char *, ...)
+ ;
+void cwd(const char *);
+FILE *dataconn(const char *, off_t, const char *);
+void delete(const char *);
+int display_file(const char *, int);
+char **do_conversion(const char *);
+void dologout(int);
+void fatal(const char *);
+void feat(void);
+void format_path(char *, const char *);
+int ftpd_pclose(FILE *);
+FILE *ftpd_popen(char *[], const char *, int);
+char *getline(char *, int, FILE *);
+void init_curclass(void);
+void logxfer(const char *, off_t, const char *, const char *,
+ const struct timeval *, const char *);
+#if 0
+void logwtmp(const char *, const char *, const char *);
+#endif
+struct tab *lookup(struct tab *, const char *);
+void makedir(const char *);
+void mlsd(const char *);
+void mlst(const char *);
+void opts(const char *);
+void parse_conf(const char *);
+void pass(const char *);
+void passive(void);
+int lpsvproto2af(int);
+int af2lpsvproto(int);
+int epsvproto2af(int);
+int af2epsvproto(int);
+void long_passive(char *, int);
+int extended_port(const char *);
+void epsv_protounsupp(const char *);
+void perror_reply(int, const char *);
+void pwd(void);
+void removedir(const char *);
+void renamecmd(const char *, const char *);
+char *renamefrom(const char *);
+void reply(int, const char *, ...)
+ ;
+void retrieve(char *[], const char *);
+void send_file_list(const char *);
+void show_chdir_messages(int);
+void sizecmd(const char *);
+void statcmd(void);
+void statfilecmd(const char *);
+void statxfer(void);
+void store(const char *, const char *, int);
+LLT strsuftoll(const char *);
+void user(const char *);
+char *xstrdup(const char *);
+void yyerror(char *);
+
+#include <netinet/in.h>
+
+#ifdef BSD4_4
+# define HAVE_SETPROCTITLE 1
+# define HAVE_SOCKADDR_SA_LEN 1
+#endif
+
+struct sockinet {
+ union sockunion {
+ struct sockaddr_in su_sin;
+#ifdef INET6
+ struct sockaddr_in6 su_sin6;
+#endif
+ } si_su;
+#if !HAVE_SOCKADDR_SA_LEN
+ int si_len;
+#endif
+};
+
+#if !HAVE_SOCKADDR_SA_LEN
+# define su_len si_len
+#else
+# define su_len si_su.su_sin.sin_len
+#endif
+#define su_addr si_su.su_sin.sin_addr
+#define su_family si_su.su_sin.sin_family
+#define su_port si_su.su_sin.sin_port
+#ifdef INET6
+# define su_6addr si_su.su_sin6.sin6_addr
+# define su_scope_id si_su.su_sin6.sin6_scope_id
+#endif
+
+struct tab {
+ char *name;
+ short token;
+ short state;
+ short flags; /* 1 if command implemented, 2 if has options,
+ 4 if can occur OOB */
+ char *help;
+ char *options;
+};
+
+struct ftpconv {
+ struct ftpconv *next;
+ char *suffix; /* Suffix of requested name */
+ char *types; /* Valid file types */
+ char *disable; /* File to disable conversions */
+ char *command; /* Command to do the conversion */
+};
+
+typedef enum {
+ CLASS_GUEST,
+ CLASS_CHROOT,
+ CLASS_REAL
+} class_ft;
+
+typedef enum {
+ FLAG_checkportcmd = 1<<0, /* Check port commands */
+ FLAG_modify = 1<<1, /* Allow CHMOD, DELE, MKD, RMD, RNFR,
+ UMASK */
+ FLAG_passive = 1<<2, /* Allow PASV mode */
+ FLAG_sanenames = 1<<3, /* Restrict names of uploaded files */
+ FLAG_upload = 1<<4 /* As per modify, but also allow
+ APPE, STOR, STOU */
+} classflag_t;
+
+#define CURCLASS_FLAGS_SET(x) (curclass.flags |= (FLAG_ ## x))
+#define CURCLASS_FLAGS_CLR(x) (curclass.flags &= ~(FLAG_ ## x))
+#define CURCLASS_FLAGS_ISSET(x) (curclass.flags & (FLAG_ ## x))
+
+struct ftpclass {
+ struct sockinet advertise; /* PASV address to advertise as */
+ char *chroot; /* Directory to chroot(2) to at login */
+ char *classname; /* Current class */
+ struct ftpconv *conversions; /* List of conversions */
+ char *display; /* File to display upon chdir */
+ char *homedir; /* Directory to chdir(2) to at login */
+ classflag_t flags; /* Flags; see classflag_t above */
+ int limit; /* Max connections (-1 = unlimited) */
+ char *limitfile; /* File to display if limit reached */
+ LLT maxfilesize; /* Maximum file size of uploads */
+ LLT maxrateget; /* Maximum get transfer rate throttle */
+ LLT maxrateput; /* Maximum put transfer rate throttle */
+ unsigned int maxtimeout; /* Maximum permitted timeout */
+ char *motd; /* MotD file to display after login */
+ char *notify; /* Files to notify about upon chdir */
+ int portmin; /* Minumum port for passive mode */
+ int portmax; /* Maximum port for passive mode */
+ LLT rateget; /* Get (RETR) transfer rate throttle */
+ LLT rateput; /* Put (STOR) transfer rate throttle */
+ unsigned int timeout; /* Default timeout */
+ class_ft type; /* Class type */
+ mode_t umask; /* Umask to use */
+};
+
+extern void ftp_loop(void) __attribute__ ((noreturn));
+extern void ftp_handle_line(char *);
+
+#ifndef GLOBAL
+#define GLOBAL extern
+#endif
+
+
+GLOBAL struct sockinet ctrl_addr;
+GLOBAL struct sockinet data_dest;
+GLOBAL struct sockinet data_source;
+GLOBAL struct sockinet his_addr;
+GLOBAL struct sockinet pasv_addr;
+GLOBAL int connections;
+GLOBAL struct ftpclass curclass;
+GLOBAL int debug;
+GLOBAL jmp_buf errcatch;
+GLOBAL char *emailaddr;
+GLOBAL int form;
+GLOBAL int gidcount; /* number of entries in gidlist[] */
+GLOBAL gid_t gidlist[NGROUPS_MAX];
+GLOBAL int hasyyerrored;
+GLOBAL char hostname[MAXHOSTNAMELEN+1];
+GLOBAL char homedir[MAXPATHLEN];
+#ifdef KERBEROS5
+GLOBAL krb5_context kcontext;
+#endif
+GLOBAL int logged_in;
+GLOBAL int logging;
+GLOBAL int pdata; /* for passive mode */
+#if HAVE_SETPROCTITLE
+GLOBAL char proctitle[BUFSIZ]; /* initial part of title */
+#endif
+GLOBAL struct passwd *pw;
+GLOBAL int quietmessages;
+GLOBAL char remotehost[MAXHOSTNAMELEN+1];
+GLOBAL off_t restart_point;
+GLOBAL char tmpline[FTP_BUFLEN];
+GLOBAL sig_atomic_t transflag;
+GLOBAL int type;
+GLOBAL int usedefault; /* for data transfers */
+GLOBAL const char *version;
+GLOBAL int is_oob;
+
+ /* total file data bytes */
+GLOBAL off_t total_data_in, total_data_out, total_data;
+ /* total number of data files */
+GLOBAL off_t total_files_in, total_files_out, total_files;
+ /* total bytes */
+GLOBAL off_t total_bytes_in, total_bytes_out, total_bytes;
+ /* total number of xfers */
+GLOBAL off_t total_xfers_in, total_xfers_out, total_xfers;
+
+extern struct tab cmdtab[];
+
+#define INTERNAL_LS "/bin/ls"
+
+
+#define CMD_IMPLEMENTED(x) ((x)->flags != 0)
+#define CMD_HAS_OPTIONS(x) ((x)->flags & 0x2)
+#define CMD_OOB(x) ((x)->flags & 0x4)
+
+#define CPUTC(c, f) do { \
+ putc(c, f); total_bytes++; total_bytes_out++; \
+ } while (0);
+
+#define CURCLASSTYPE curclass.type == CLASS_GUEST ? "GUEST" : \
+ curclass.type == CLASS_CHROOT ? "CHROOT" : \
+ curclass.type == CLASS_REAL ? "REAL" : \
+ "<unknown>"
+
+#define ISDOTDIR(x) (x[0] == '.' && x[1] == '\0')
+#define ISDOTDOTDIR(x) (x[0] == '.' && x[1] == '.' && x[2] == '\0')
+
+#define EMPTYSTR(p) ((p) == NULL || *(p) == '\0')
+#define NEXTWORD(P, W) do { \
+ (W) = strsep(&(P), " \t"); \
+ } while ((W) != NULL && *(W) == '\0')
+#define PLURAL(s) ((s) == 1 ? "" : "s")
+#define REASSIGN(X,Y) do { if (X) free(X); (X)=(Y); } while (/*CONSTCOND*/0)
+
+#ifndef IPPORT_ANONMAX
+# define IPPORT_ANONMAX 65535
+#endif
diff --git a/contrib/lukemftpd/src/ftpcmd.y b/contrib/lukemftpd/src/ftpcmd.y
new file mode 100644
index 0000000..aeea190
--- /dev/null
+++ b/contrib/lukemftpd/src/ftpcmd.y
@@ -0,0 +1,1808 @@
+/* $NetBSD: ftpcmd.y,v 1.65 2001/04/25 01:46:25 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, 1993, 1994
+ * The Regents of the University of California. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ * must display the following acknowledgement:
+ * This product includes software developed by the University of
+ * California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * @(#)ftpcmd.y 8.3 (Berkeley) 4/6/94
+ */
+
+/*
+ * Grammar for FTP commands.
+ * See RFC 959.
+ */
+
+%{
+#include "lukemftpd.h"
+
+#include "extern.h"
+#include "version.h"
+
+static int cmd_type;
+static int cmd_form;
+static int cmd_bytesz;
+
+char cbuf[FTP_BUFLEN];
+char *cmdp;
+char *fromname;
+
+%}
+
+%union {
+ int i;
+ char *s;
+}
+
+%token
+ A B C E F I
+ L N P R S T
+
+ SP CRLF COMMA
+
+ USER PASS ACCT CWD CDUP SMNT
+ QUIT REIN PORT PASV TYPE STRU
+ MODE RETR STOR STOU APPE ALLO
+ REST RNFR RNTO ABOR DELE RMD
+ MKD PWD LIST NLST SITE SYST
+ STAT HELP NOOP
+
+ AUTH ADAT PROT PBSZ CCC MIC
+ CONF ENC
+
+ FEAT OPTS
+
+ SIZE MDTM MLST MLSD
+
+ LPRT LPSV EPRT EPSV
+
+ MAIL MLFL MRCP MRSQ MSAM MSND
+ MSOM
+
+ CHMOD IDLE RATEGET RATEPUT UMASK
+
+ LEXERR
+
+%token <s> STRING
+%token <s> ALL
+%token <i> NUMBER
+
+%type <i> check_login octal_number byte_size
+%type <i> struct_code mode_code type_code form_code decimal_integer
+%type <s> pathstring pathname password username
+%type <s> mechanism_name base64data prot_code
+
+%start cmd_sel
+
+%%
+
+cmd_sel
+ : cmd
+ {
+ fromname = NULL;
+ restart_point = (off_t) 0;
+ }
+
+ | rcmd
+
+ ;
+
+cmd
+ /* RFC 959 */
+ : USER SP username CRLF
+ {
+ user($3);
+ free($3);
+ }
+
+ | PASS SP password CRLF
+ {
+ pass($3);
+ memset($3, 0, strlen($3));
+ free($3);
+ }
+
+ | 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);
+ }
+
+ | CDUP check_login CRLF
+ {
+ if ($2)
+ cwd("..");
+ }
+
+ | QUIT CRLF
+ {
+ if (logged_in) {
+ reply(-221, "%s", "");
+ reply(0,
+ "Data traffic for this session was " LLF " byte%s in " LLF " file%s.",
+ (LLT)total_data, PLURAL(total_data),
+ (LLT)total_files, PLURAL(total_files));
+ reply(0,
+ "Total traffic for this session was " LLF " byte%s in " LLF " transfer%s.",
+ (LLT)total_bytes, PLURAL(total_bytes),
+ (LLT)total_xfers, PLURAL(total_xfers));
+ }
+ reply(221,
+ "Thank you for using the FTP service on %s.",
+ hostname);
+ if (logged_in && logging) {
+ syslog(LOG_INFO,
+ "Data traffic: " LLF " byte%s in " LLF " file%s",
+ (LLT)total_data, PLURAL(total_data),
+ (LLT)total_files, PLURAL(total_files));
+ syslog(LOG_INFO,
+ "Total traffic: " LLF " byte%s in " LLF " transfer%s",
+ (LLT)total_bytes, PLURAL(total_bytes),
+ (LLT)total_xfers, PLURAL(total_xfers));
+ }
+
+ dologout(0);
+ }
+
+ | PORT check_login SP host_port CRLF
+ {
+ if ($2)
+ port_check("PORT", AF_INET);
+ }
+
+ | LPRT check_login SP host_long_port4 CRLF
+ {
+ if ($2)
+ port_check("LPRT", AF_INET);
+ }
+
+ | LPRT check_login SP host_long_port6 CRLF
+ {
+#ifdef INET6
+ if ($2)
+ port_check("LPRT", AF_INET6);
+#else
+ reply(500, "IPv6 support not available.");
+#endif
+ }
+
+ | EPRT check_login SP STRING CRLF
+ {
+ if ($2) {
+ if (extended_port($4) == 0)
+ port_check("EPRT", -1);
+ }
+ free($4);
+ }
+
+ | PASV check_login CRLF
+ {
+ if ($2) {
+ if (CURCLASS_FLAGS_ISSET(passive))
+ passive();
+ else
+ reply(500, "PASV mode not available.");
+ }
+ }
+
+ | LPSV check_login CRLF
+ {
+ if ($2) {
+ if (epsvall)
+ reply(501,
+ "LPSV disallowed after EPSV ALL");
+ else
+ long_passive("LPSV", PF_UNSPEC);
+ }
+ }
+
+ | EPSV check_login SP NUMBER CRLF
+ {
+ if ($2)
+ long_passive("EPSV", epsvproto2af($4));
+ }
+
+ | EPSV check_login SP ALL CRLF
+ {
+ if ($2) {
+ reply(200, "EPSV ALL command successful.");
+ epsvall++;
+ }
+ }
+
+ | EPSV check_login 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 NBBY == 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 /* NBBY == 8 */
+ UNIMPLEMENTED for NBBY != 8
+#endif /* NBBY == 8 */
+ }
+
+ }
+ }
+
+ | STRU check_login SP struct_code CRLF
+ {
+ if ($2) {
+ switch ($4) {
+
+ case STRU_F:
+ reply(200, "STRU F ok.");
+ 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 ok.");
+ break;
+
+ default:
+ reply(502, "Unimplemented MODE type.");
+ }
+ }
+ }
+
+ | RETR check_login SP pathname CRLF
+ {
+ if ($2 && $4 != NULL)
+ retrieve(NULL, $4);
+ if ($4 != NULL)
+ free($4);
+ }
+
+ | STOR SP pathname CRLF
+ {
+ if (check_write($3, 1))
+ store($3, "w", 0);
+ if ($3 != NULL)
+ free($3);
+ }
+
+ | STOU SP pathname CRLF
+ {
+ if (check_write($3, 1))
+ store($3, "w", 1);
+ if ($3 != NULL)
+ free($3);
+ }
+
+ | APPE SP pathname CRLF
+ {
+ if (check_write($3, 1))
+ store($3, "a", 0);
+ if ($3 != NULL)
+ free($3);
+ }
+
+ | 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.");
+ }
+
+ | RNTO SP pathname CRLF
+ {
+ if (check_write($3, 0)) {
+ if (fromname) {
+ renamecmd(fromname, $3);
+ free(fromname);
+ fromname = NULL;
+ } else {
+ reply(503, "Bad sequence of commands.");
+ }
+ }
+ if ($3 != NULL)
+ free($3);
+ }
+
+ | ABOR check_login CRLF
+ {
+ if (is_oob)
+ abor();
+ else if ($2)
+ reply(225, "ABOR command successful.");
+ }
+
+ | DELE SP pathname CRLF
+ {
+ if (check_write($3, 0))
+ delete($3);
+ if ($3 != NULL)
+ free($3);
+ }
+
+ | RMD SP pathname CRLF
+ {
+ if (check_write($3, 0))
+ removedir($3);
+ if ($3 != NULL)
+ free($3);
+ }
+
+ | MKD SP pathname CRLF
+ {
+ if (check_write($3, 0))
+ makedir($3);
+ if ($3 != NULL)
+ free($3);
+ }
+
+ | PWD check_login CRLF
+ {
+ if ($2)
+ pwd();
+ }
+
+ | LIST check_login CRLF
+ {
+ char *argv[] = { INTERNAL_LS, "-lgA", NULL };
+
+ if ($2)
+ retrieve(argv, "");
+ }
+
+ | LIST check_login SP pathname CRLF
+ {
+ char *argv[] = { INTERNAL_LS, "-lgA", NULL, NULL };
+
+ if ($2 && $4 != NULL) {
+ argv[2] = $4;
+ retrieve(argv, $4);
+ }
+ if ($4 != NULL)
+ free($4);
+ }
+
+ | NLST check_login CRLF
+ {
+ if ($2)
+ send_file_list(".");
+ }
+
+ | NLST check_login SP pathname CRLF
+ {
+ if ($2)
+ send_file_list($4);
+ free($4);
+ }
+
+ | SITE SP HELP CRLF
+ {
+ help(sitetab, NULL);
+ }
+
+ | SITE SP CHMOD SP octal_number SP pathname CRLF
+ {
+ if (check_write($7, 0)) {
+ if ($5 > 0777)
+ reply(501,
+ "CHMOD: Mode value must be between 0 and 0777");
+ else if (chmod($7, $5) < 0)
+ perror_reply(550, $7);
+ else
+ reply(200, "CHMOD command successful.");
+ }
+ if ($7 != NULL)
+ free($7);
+ }
+
+ | SITE SP HELP SP STRING CRLF
+ {
+ help(sitetab, $5);
+ free($5);
+ }
+
+ | SITE SP IDLE check_login CRLF
+ {
+ if ($4) {
+ reply(200,
+ "Current IDLE time limit is %d seconds; max %d",
+ curclass.timeout, curclass.maxtimeout);
+ }
+ }
+
+ | SITE SP IDLE check_login SP NUMBER CRLF
+ {
+ if ($4) {
+ if ($6 < 30 || $6 > curclass.maxtimeout) {
+ reply(501,
+ "IDLE time limit must be between 30 and %d seconds",
+ curclass.maxtimeout);
+ } else {
+ curclass.timeout = $6;
+ (void) alarm(curclass.timeout);
+ reply(200,
+ "IDLE time limit set to %d seconds",
+ curclass.timeout);
+ }
+ }
+ }
+
+ | SITE SP RATEGET check_login CRLF
+ {
+ if ($4) {
+ reply(200,
+ "Current RATEGET is " LLF " bytes/sec",
+ (LLT)curclass.rateget);
+ }
+ }
+
+ | SITE SP RATEGET check_login SP STRING CRLF
+ {
+ char *p = $6;
+ LLT rate;
+
+ if ($4) {
+ rate = strsuftoll(p);
+ if (rate == -1)
+ reply(501, "Invalid RATEGET %s", p);
+ else if (curclass.maxrateget &&
+ rate > curclass.maxrateget)
+ reply(501,
+ "RATEGET " LLF " is larger than maximum RATEGET " LLF,
+ (LLT)rate,
+ (LLT)curclass.maxrateget);
+ else {
+ curclass.rateget = rate;
+ reply(200,
+ "RATEGET set to " LLF " bytes/sec",
+ (LLT)curclass.rateget);
+ }
+ }
+ free($6);
+ }
+
+ | SITE SP RATEPUT check_login CRLF
+ {
+ if ($4) {
+ reply(200,
+ "Current RATEPUT is " LLF " bytes/sec",
+ (LLT)curclass.rateput);
+ }
+ }
+
+ | SITE SP RATEPUT check_login SP STRING CRLF
+ {
+ char *p = $6;
+ LLT rate;
+
+ if ($4) {
+ rate = strsuftoll(p);
+ if (rate == -1)
+ reply(501, "Invalid RATEPUT %s", p);
+ else if (curclass.maxrateput &&
+ rate > curclass.maxrateput)
+ reply(501,
+ "RATEPUT " LLF " is larger than maximum RATEPUT " LLF,
+ (LLT)rate,
+ (LLT)curclass.maxrateput);
+ else {
+ curclass.rateput = rate;
+ reply(200,
+ "RATEPUT set to " LLF " bytes/sec",
+ (LLT)curclass.rateput);
+ }
+ }
+ 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 && CURCLASS_FLAGS_ISSET(modify)) {
+ if (($6 == -1) || ($6 > 0777)) {
+ reply(501, "Bad UMASK value");
+ } else {
+ oldmask = umask($6);
+ reply(200,
+ "UMASK set to %03o (was %03o)",
+ $6, oldmask);
+ }
+ }
+ }
+
+ | SYST CRLF
+ {
+ if (EMPTYSTR(version))
+ reply(215, "UNIX Type: L%d", NBBY);
+ else
+ reply(215, "UNIX Type: L%d Version: %s", NBBY,
+ version);
+ }
+
+ | STAT check_login SP pathname CRLF
+ {
+ if ($2 && $4 != NULL)
+ statfilecmd($4);
+ if ($4 != NULL)
+ free($4);
+ }
+
+ | STAT CRLF
+ {
+ if (is_oob)
+ statxfer();
+ else
+ statcmd();
+ }
+
+ | 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.");
+ }
+
+ /* RFC 2228 */
+ | AUTH SP mechanism_name CRLF
+ {
+ reply(502, "RFC 2228 authentication not implemented.");
+ free($3);
+ }
+
+ | ADAT SP base64data CRLF
+ {
+ reply(503,
+ "Please set authentication state with AUTH.");
+ free($3);
+ }
+
+ | PROT SP prot_code CRLF
+ {
+ reply(503,
+ "Please set protection buffer size with PBSZ.");
+ free($3);
+ }
+
+ | PBSZ SP decimal_integer CRLF
+ {
+ reply(503,
+ "Please set authentication state with AUTH.");
+ }
+
+ | CCC CRLF
+ {
+ reply(533, "No protection enabled.");
+ }
+
+ | MIC SP base64data CRLF
+ {
+ reply(502, "RFC 2228 authentication not implemented.");
+ free($3);
+ }
+
+ | CONF SP base64data CRLF
+ {
+ reply(502, "RFC 2228 authentication not implemented.");
+ free($3);
+ }
+
+ | ENC SP base64data CRLF
+ {
+ reply(502, "RFC 2228 authentication not implemented.");
+ free($3);
+ }
+
+ /* RFC 2389 */
+ | FEAT CRLF
+ {
+
+ feat();
+ }
+
+ | OPTS SP STRING CRLF
+ {
+
+ opts($3);
+ free($3);
+ }
+
+
+ /* extensions from draft-ietf-ftpext-mlst-11 */
+
+ /*
+ * 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);
+ }
+
+ /*
+ * 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",
+ TM_YEAR_BASE + t->tm_year,
+ t->tm_mon+1, t->tm_mday,
+ t->tm_hour, t->tm_min, t->tm_sec);
+ }
+ }
+ if ($4 != NULL)
+ free($4);
+ }
+
+ | MLST check_login SP pathname CRLF
+ {
+ if ($2 && $4 != NULL)
+ mlst($4);
+ if ($4 != NULL)
+ free($4);
+ }
+
+ | MLST check_login CRLF
+ {
+ mlst(NULL);
+ }
+
+ | MLSD check_login SP pathname CRLF
+ {
+ if ($2 && $4 != NULL)
+ mlsd($4);
+ if ($4 != NULL)
+ free($4);
+ }
+
+ | MLSD check_login CRLF
+ {
+ mlsd(NULL);
+ }
+
+ | error CRLF
+ {
+ yyerrok;
+ }
+ ;
+
+rcmd
+ : REST check_login SP byte_size CRLF
+ {
+ if ($2) {
+ fromname = NULL;
+ restart_point = $4; /* XXX: $4 is only "int" */
+ reply(350,
+ "Restarting at " LLF ". Send STORE or RETRIEVE to initiate transfer.",
+ (LLT)restart_point);
+ }
+ }
+
+ | RNFR SP pathname CRLF
+ {
+ restart_point = (off_t) 0;
+ if (check_write($3, 0))
+ fromname = renamefrom($3);
+ if ($3 != NULL)
+ free($3);
+ }
+ ;
+
+username
+ : STRING
+ ;
+
+password
+ : /* empty */
+ {
+ $$ = (char *)calloc(1, sizeof(char));
+ }
+
+ | STRING
+ ;
+
+byte_size
+ : NUMBER
+ ;
+
+host_port
+ : 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_in);
+ data_dest.su_family = AF_INET;
+ p = (char *)&data_dest.su_port;
+ p[0] = $9; p[1] = $11;
+ a = (char *)&data_dest.su_addr;
+ a[0] = $1; a[1] = $3; a[2] = $5; a[3] = $7;
+ }
+ ;
+
+host_long_port4
+ : 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_in);
+ data_dest.su_family = AF_INET;
+ p = (char *)&data_dest.su_port;
+ p[0] = $15; p[1] = $17;
+ a = (char *)&data_dest.su_addr;
+ a[0] = $5; a[1] = $7; a[2] = $9; a[3] = $11;
+
+ /* reject invalid LPRT command */
+ if ($1 != 4 || $3 != 4 || $13 != 2)
+ memset(&data_dest, 0, sizeof(data_dest));
+ }
+ ;
+
+host_long_port6
+ : 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
+ {
+#ifdef INET6
+ 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; p[1] = $41;
+ a = (char *)&data_dest.si_su.su_sin6.sin6_addr;
+ a[0] = $5; a[1] = $7; a[2] = $9; a[3] = $11;
+ a[4] = $13; a[5] = $15; a[6] = $17; a[7] = $19;
+ a[8] = $21; a[9] = $23; a[10] = $25; a[11] = $27;
+ a[12] = $29; a[13] = $31; a[14] = $33; a[15] = $35;
+ if (his_addr.su_family == AF_INET6) {
+ /* XXX: more sanity checks! */
+ data_dest.su_scope_id = his_addr.su_scope_id;
+ }
+#else
+ memset(&data_dest, 0, sizeof(data_dest));
+#endif /* INET6 */
+ /* reject invalid LPRT command */
+ if ($1 != 6 || $3 != 16 || $37 != 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 = NBBY;
+ }
+
+ | 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
+ {
+ /*
+ * 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.
+ */
+ if (logged_in && $1 && *$1 == '~') {
+ char *path, *home, *result;
+ size_t len;
+
+ path = strchr($1 + 1, '/');
+ if (path != NULL)
+ *path++ = '\0';
+ if ($1[1] == '\0')
+ home = homedir;
+ else {
+ struct passwd *pw;
+
+ if ((pw = getpwnam($1 + 1)) != NULL)
+ home = pw->pw_dir;
+ else
+ home = $1;
+ }
+ len = strlen(home) + 1;
+ if (path != NULL)
+ len += strlen(path) + 1;
+ if ((result = malloc(len)) == NULL)
+ fatal("Local resource failure: malloc");
+ strlcpy(result, home, len);
+ if (path != NULL) {
+ strlcat(result, "/", len);
+ strlcat(result, path, len);
+ }
+ $$ = result;
+ 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;
+ multby = 1;
+ ret = 0;
+ while (dec) {
+ digit = dec%10;
+ if (digit > 7) {
+ ret = -1;
+ break;
+ }
+ ret += digit * multby;
+ multby *= 8;
+ dec /= 10;
+ }
+ $$ = ret;
+ }
+ ;
+
+mechanism_name
+ : STRING
+ ;
+
+base64data
+ : STRING
+ ;
+
+prot_code
+ : STRING
+ ;
+
+decimal_integer
+ : NUMBER
+ ;
+
+check_login
+ : /* empty */
+ {
+ if (logged_in)
+ $$ = 1;
+ else {
+ reply(530, "Please login with USER and PASS.");
+ $$ = 0;
+ hasyyerrored = 1;
+ }
+ }
+ ;
+
+%%
+
+#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 /* 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 NOARGS 9 /* No arguments allowed */
+#define EOLN 10 /* End of line */
+
+struct tab cmdtab[] = {
+ /* From RFC 959, in order defined (5.3.1) */
+ { "USER", USER, STR1, 1, "<sp> username" },
+ { "PASS", PASS, ZSTR1, 1, "<sp> password" },
+ { "ACCT", ACCT, STR1, 0, "(specify account)" },
+ { "CWD", CWD, OSTR, 1, "[ <sp> directory-name ]" },
+ { "CDUP", CDUP, NOARGS, 1, "(change to parent directory)" },
+ { "SMNT", SMNT, ARGS, 0, "(structure mount)" },
+ { "QUIT", QUIT, NOARGS, 1, "(terminate service)" },
+ { "REIN", REIN, NOARGS, 0, "(reinitialize server state)" },
+ { "PORT", PORT, ARGS, 1, "<sp> b0, b1, b2, b3, b4" },
+ { "LPRT", LPRT, ARGS, 1, "<sp> af, hal, h1, h2, h3,..., pal, p1, p2..." },
+ { "EPRT", EPRT, STR1, 1, "<sp> |af|addr|port|" },
+ { "PASV", PASV, NOARGS, 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" },
+ { "STOU", STOU, STR1, 1, "<sp> file-name" },
+ { "APPE", APPE, STR1, 1, "<sp> file-name" },
+ { "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, NOARGS, 4, "(abort operation)" },
+ { "DELE", DELE, STR1, 1, "<sp> file-name" },
+ { "RMD", RMD, STR1, 1, "<sp> path-name" },
+ { "MKD", MKD, STR1, 1, "<sp> path-name" },
+ { "PWD", PWD, NOARGS, 1, "(return current directory)" },
+ { "LIST", LIST, OSTR, 1, "[ <sp> path-name ]" },
+ { "NLST", NLST, OSTR, 1, "[ <sp> path-name ]" },
+ { "SITE", SITE, SITECMD, 1, "site-cmd [ <sp> arguments ]" },
+ { "SYST", SYST, NOARGS, 1, "(get type of operating system)" },
+ { "STAT", STAT, OSTR, 4, "[ <sp> path-name ]" },
+ { "HELP", HELP, OSTR, 1, "[ <sp> <string> ]" },
+ { "NOOP", NOOP, NOARGS, 2, "" },
+
+ /* From RFC 2228, in order defined */
+ { "AUTH", AUTH, STR1, 1, "<sp> mechanism-name" },
+ { "ADAT", ADAT, STR1, 1, "<sp> base-64-data" },
+ { "PROT", PROT, STR1, 1, "<sp> prot-code" },
+ { "PBSZ", PBSZ, ARGS, 1, "<sp> decimal-integer" },
+ { "CCC", CCC, NOARGS, 1, "(Disable data protection)" },
+ { "MIC", MIC, STR1, 4, "<sp> base64data" },
+ { "CONF", CONF, STR1, 4, "<sp> base64data" },
+ { "ENC", ENC, STR1, 4, "<sp> base64data" },
+
+ /* From RFC 2389, in order defined */
+ { "FEAT", FEAT, NOARGS, 1, "(display extended features)" },
+ { "OPTS", OPTS, STR1, 1, "<sp> command [ <sp> options ]" },
+
+ /* from draft-ietf-ftpext-mlst-11 */
+ { "MDTM", MDTM, OSTR, 1, "<sp> path-name" },
+ { "SIZE", SIZE, OSTR, 1, "<sp> path-name" },
+ { "MLST", MLST, OSTR, 2, "[ <sp> path-name ]" },
+ { "MLSD", MLSD, OSTR, 1, "[ <sp> directory-name ]" },
+
+ /* obsolete commands */
+ { "MAIL", MAIL, OSTR, 0, "(mail to user)" },
+ { "MLFL", MLFL, OSTR, 0, "(mail file)" },
+ { "MRCP", MRCP, STR1, 0, "(mail recipient)" },
+ { "MRSQ", MRSQ, OSTR, 0, "(mail recipient scheme question)" },
+ { "MSAM", MSAM, OSTR, 0, "(mail send to terminal and mailbox)" },
+ { "MSND", MSND, OSTR, 0, "(mail send to terminal)" },
+ { "MSOM", MSOM, OSTR, 0, "(mail send to terminal or mailbox)" },
+ { "XCUP", CDUP, NOARGS, 1, "(change to parent directory)" },
+ { "XCWD", CWD, OSTR, 1, "[ <sp> directory-name ]" },
+ { "XMKD", MKD, STR1, 1, "<sp> path-name" },
+ { "XPWD", PWD, NOARGS, 1, "(return current directory)" },
+ { "XRMD", RMD, STR1, 1, "<sp> path-name" },
+
+ { NULL, 0, 0, 0, 0 }
+};
+
+struct tab sitetab[] = {
+ { "CHMOD", CHMOD, NSTR, 1, "<sp> mode <sp> file-name" },
+ { "HELP", HELP, OSTR, 1, "[ <sp> <string> ]" },
+ { "IDLE", IDLE, ARGS, 1, "[ <sp> maximum-idle-time ]" },
+ { "RATEGET", RATEGET,OSTR, 1, "[ <sp> get-throttle-rate ]" },
+ { "RATEPUT", RATEPUT,OSTR, 1, "[ <sp> put-throttle-rate ]" },
+ { "UMASK", UMASK, ARGS, 1, "[ <sp> umask ]" },
+ { NULL, 0, 0, 0, NULL }
+};
+
+static int check_write(const char *, int);
+static void help(struct tab *, const char *);
+static void port_check(const char *, int);
+static void toolong(int);
+static int yylex(void);
+
+extern int epsvall;
+
+/*
+ * Check if a filename is allowed to be modified (isupload == 0) or
+ * uploaded (isupload == 1), and if necessary, check the filename is `sane'.
+ */
+static int
+check_write(const char *file, int isupload)
+{
+ if (file == NULL)
+ return (0);
+ if (! logged_in) {
+ reply(530, "Please login with USER and PASS.");
+ return (0);
+ }
+ /* checking modify */
+ if (! isupload && ! CURCLASS_FLAGS_ISSET(modify)) {
+ reply(502, "No permission to use this command.");
+ return (0);
+ }
+ /* checking upload */
+ if (isupload && ! CURCLASS_FLAGS_ISSET(upload)) {
+ reply(502, "No permission to use this command.");
+ return (0);
+ }
+ /* checking sanenames */
+ if (CURCLASS_FLAGS_ISSET(sanenames)) {
+ const char *p;
+
+ if (file[0] == '.')
+ goto insane_name;
+ for (p = file; *p; p++) {
+ if (isalnum(*p) || *p == '-' || *p == '+' ||
+ *p == ',' || *p == '.' || *p == '_')
+ continue;
+ insane_name:
+ reply(553, "File name `%s' not allowed.", file);
+ return (0);
+ }
+ }
+ return (1);
+}
+
+struct tab *
+lookup(struct tab *p, const char *cmd)
+{
+
+ for (; p->name != NULL; p++)
+ if (strcasecmp(cmd, p->name) == 0)
+ return (p);
+ return (0);
+}
+
+#include <arpa/telnet.h>
+
+/*
+ * getline - a hacked up version of fgets to ignore TELNET escape codes.
+ */
+char *
+getline(char *s, int n, FILE *iop)
+{
+ int c;
+ char *cs;
+
+ 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 (debug)
+ syslog(LOG_DEBUG, "command: %s", s);
+ tmpline[0] = '\0';
+ return(s);
+ }
+ if (c == 0)
+ tmpline[0] = '\0';
+ }
+ while ((c = getc(iop)) != EOF) {
+ total_bytes++;
+ total_bytes_in++;
+ c &= 0377;
+ if (c == IAC) {
+ if ((c = getc(iop)) != EOF) {
+ total_bytes++;
+ total_bytes_in++;
+ c &= 0377;
+ switch (c) {
+ case WILL:
+ case WONT:
+ c = getc(iop);
+ total_bytes++;
+ total_bytes_in++;
+ cprintf(stdout, "%c%c%c", IAC, DONT, 0377&c);
+ (void) fflush(stdout);
+ continue;
+ case DO:
+ case DONT:
+ c = getc(iop);
+ total_bytes++;
+ total_bytes_in++;
+ cprintf(stdout, "%c%c%c", IAC, WONT, 0377&c);
+ (void) fflush(stdout);
+ continue;
+ case IAC:
+ break;
+ default:
+ continue; /* ignore command */
+ }
+ }
+ }
+ *cs++ = c;
+ if (--n <= 0 || c == '\n')
+ break;
+ }
+ if (c == EOF && cs == s)
+ return (NULL);
+ *cs++ = '\0';
+ if (debug) {
+ if ((curclass.type != CLASS_GUEST &&
+ strncasecmp(s, "PASS ", 5) == 0) ||
+ strncasecmp(s, "ACCT ", 5) == 0) {
+ /* Don't syslog passwords */
+ syslog(LOG_DEBUG, "command: %.4s ???", s);
+ } else {
+ char *cp;
+ int len;
+
+ /* Don't syslog trailing CR-LF */
+ len = strlen(s);
+ cp = s + len - 1;
+ while (cp >= s && (*cp == '\n' || *cp == '\r')) {
+ --cp;
+ --len;
+ }
+ syslog(LOG_DEBUG, "command: %.*s", len, s);
+ }
+ }
+ return (s);
+}
+
+static void
+toolong(int signo)
+{
+
+ reply(421,
+ "Timeout (%d seconds): closing control connection.",
+ curclass.timeout);
+ if (logging)
+ syslog(LOG_INFO, "User %s timed out after %d seconds",
+ (pw ? pw->pw_name : "unknown"), curclass.timeout);
+ dologout(1);
+}
+
+void
+ftp_handle_line(char *cp)
+{
+
+ cmdp = cp;
+ yyparse();
+}
+
+void
+ftp_loop(void)
+{
+
+ while (1) {
+ (void) signal(SIGALRM, toolong);
+ (void) alarm(curclass.timeout);
+ if (getline(cbuf, sizeof(cbuf)-1, stdin) == NULL) {
+ reply(221, "You could at least say goodbye.");
+ dologout(0);
+ }
+ (void) alarm(0);
+ ftp_handle_line(cbuf);
+ }
+ /*NOTREACHED*/
+}
+
+static int
+yylex(void)
+{
+ static int cpos, state;
+ char *cp, *cp2;
+ struct tab *p;
+ int n;
+ char c;
+
+ switch (state) {
+
+ case CMD:
+ hasyyerrored = 0;
+ if ((cp = strchr(cmdp, '\r'))) {
+ *cp = '\0';
+#if HAVE_SETPROCTITLE
+ if (strncasecmp(cmdp, "PASS", 4) != 0 &&
+ strncasecmp(cmdp, "ACCT", 4) != 0)
+ setproctitle("%s: %s", proctitle, cmdp);
+#endif /* HAVE_SETPROCTITLE */
+ *cp++ = '\n';
+ *cp = '\0';
+ }
+ if ((cp = strpbrk(cmdp, " \n")))
+ cpos = cp - cmdp;
+ if (cpos == 0)
+ cpos = 4;
+ c = cmdp[cpos];
+ cmdp[cpos] = '\0';
+ p = lookup(cmdtab, cmdp);
+ cmdp[cpos] = c;
+ if (p != NULL) {
+ if (is_oob && ! CMD_OOB(p)) {
+ /* command will be handled in-band */
+ return (0);
+ } else if (! CMD_IMPLEMENTED(p)) {
+ reply(502, "%s command not implemented.",
+ p->name);
+ hasyyerrored = 1;
+ break;
+ }
+ state = p->state;
+ yylval.s = p->name;
+ return (p->token);
+ }
+ break;
+
+ case SITECMD:
+ if (cmdp[cpos] == ' ') {
+ cpos++;
+ return (SP);
+ }
+ cp = &cmdp[cpos];
+ if ((cp2 = strpbrk(cp, " \n")))
+ cpos = cp2 - cmdp;
+ c = cmdp[cpos];
+ cmdp[cpos] = '\0';
+ p = lookup(sitetab, cp);
+ cmdp[cpos] = c;
+ if (p != NULL) {
+ if (!CMD_IMPLEMENTED(p)) {
+ reply(502, "SITE %s command not implemented.",
+ p->name);
+ hasyyerrored = 1;
+ break;
+ }
+ state = p->state;
+ yylval.s = p->name;
+ return (p->token);
+ }
+ break;
+
+ case OSTR:
+ if (cmdp[cpos] == '\n') {
+ state = EOLN;
+ return (CRLF);
+ }
+ /* FALLTHROUGH */
+
+ case STR1:
+ case ZSTR1:
+ dostr1:
+ if (cmdp[cpos] == ' ') {
+ cpos++;
+ state = state == OSTR ? STR2 : state+1;
+ return (SP);
+ }
+ break;
+
+ case ZSTR2:
+ if (cmdp[cpos] == '\n') {
+ state = EOLN;
+ return (CRLF);
+ }
+ /* FALLTHROUGH */
+
+ case STR2:
+ cp = &cmdp[cpos];
+ n = strlen(cp);
+ cpos += n - 1;
+ /*
+ * Make sure the string is nonempty and \n terminated.
+ */
+ if (n > 1 && cmdp[cpos] == '\n') {
+ cmdp[cpos] = '\0';
+ yylval.s = xstrdup(cp);
+ cmdp[cpos] = '\n';
+ state = ARGS;
+ return (STRING);
+ }
+ break;
+
+ case NSTR:
+ if (cmdp[cpos] == ' ') {
+ cpos++;
+ return (SP);
+ }
+ if (isdigit(cmdp[cpos])) {
+ cp = &cmdp[cpos];
+ while (isdigit(cmdp[++cpos]))
+ ;
+ c = cmdp[cpos];
+ cmdp[cpos] = '\0';
+ yylval.i = atoi(cp);
+ cmdp[cpos] = c;
+ state = STR1;
+ return (NUMBER);
+ }
+ state = STR1;
+ goto dostr1;
+
+ case ARGS:
+ if (isdigit(cmdp[cpos])) {
+ cp = &cmdp[cpos];
+ while (isdigit(cmdp[++cpos]))
+ ;
+ c = cmdp[cpos];
+ cmdp[cpos] = '\0';
+ yylval.i = atoi(cp);
+ cmdp[cpos] = c;
+ return (NUMBER);
+ }
+ if (strncasecmp(&cmdp[cpos], "ALL", 3) == 0
+ && !isalnum(cmdp[cpos + 3])) {
+ yylval.s = xstrdup("ALL");
+ cpos += 3;
+ return ALL;
+ }
+ switch (cmdp[cpos++]) {
+
+ case '\n':
+ state = EOLN;
+ 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;
+
+ case NOARGS:
+ if (cmdp[cpos] == '\n') {
+ state = EOLN;
+ return (CRLF);
+ }
+ c = cmdp[cpos];
+ cmdp[cpos] = '\0';
+ reply(501, "'%s' command does not take any arguments.", cmdp);
+ hasyyerrored = 1;
+ cmdp[cpos] = c;
+ break;
+
+ case EOLN:
+ state = CMD;
+ return (0);
+
+ default:
+ fatal("Unknown state in scanner.");
+ }
+ yyerror(NULL);
+ state = CMD;
+ is_oob = 0;
+ longjmp(errcatch, 0);
+ /* NOTREACHED */
+}
+
+/* ARGSUSED */
+void
+yyerror(char *s)
+{
+ char *cp;
+
+ if (hasyyerrored || is_oob)
+ return;
+ if ((cp = strchr(cmdp,'\n')) != NULL)
+ *cp = '\0';
+ reply(500, "'%s': command not understood.", cmdp);
+ hasyyerrored = 1;
+}
+
+static void
+help(struct tab *ctab, const 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;
+
+ reply(-214, "%s", "");
+ reply(0, "The following %scommands are recognized.", type);
+ reply(0, "(`-' = not implemented, `+' = supports options)");
+ columns = 76 / width;
+ if (columns == 0)
+ columns = 1;
+ lines = (NCMDS + columns - 1) / columns;
+ for (i = 0; i < lines; i++) {
+ cprintf(stdout, " ");
+ for (j = 0; j < columns; j++) {
+ c = ctab + j * lines + i;
+ cprintf(stdout, "%s", c->name);
+ w = strlen(c->name);
+ if (! CMD_IMPLEMENTED(c)) {
+ CPUTC('-', stdout);
+ w++;
+ }
+ if (CMD_HAS_OPTIONS(c)) {
+ CPUTC('+', stdout);
+ w++;
+ }
+ if (c + lines >= &ctab[NCMDS])
+ break;
+ while (w < width) {
+ CPUTC(' ', stdout);
+ w++;
+ }
+ }
+ cprintf(stdout, "\r\n");
+ }
+ (void) fflush(stdout);
+ reply(214, "Direct comments to ftp-bugs@%s.", hostname);
+ return;
+ }
+ c = lookup(ctab, s);
+ if (c == (struct tab *)0) {
+ reply(502, "Unknown command %s.", s);
+ return;
+ }
+ if (CMD_IMPLEMENTED(c))
+ reply(214, "Syntax: %s%s %s", type, c->name, c->help);
+ else
+ reply(214, "%s%-*s\t%s; not implemented.", type, width,
+ c->name, c->help);
+}
+
+/*
+ * Check that the structures used for a PORT, LPRT or EPRT command are
+ * valid (data_dest, his_addr), and if necessary, detect ftp bounce attacks.
+ * If family != -1 check that his_addr.su_family == family.
+ */
+static void
+port_check(const char *cmd, int family)
+{
+ char h1[NI_MAXHOST], h2[NI_MAXHOST];
+ char s1[NI_MAXHOST], s2[NI_MAXHOST];
+#ifdef NI_WITHSCOPEID
+ const int niflags = NI_NUMERICHOST | NI_NUMERICSERV | NI_WITHSCOPEID;
+#else
+ const int niflags = NI_NUMERICHOST | NI_NUMERICSERV;
+#endif
+
+ if (epsvall) {
+ reply(501, "%s disallowed after EPSV ALL", cmd);
+ return;
+ }
+
+ if (family != -1 && his_addr.su_family != family) {
+ port_check_fail:
+ reply(500, "Illegal %s command rejected", cmd);
+ return;
+ }
+
+ if (data_dest.su_family != his_addr.su_family)
+ goto port_check_fail;
+
+ /* be paranoid, if told so */
+ if (CURCLASS_FLAGS_ISSET(checkportcmd)) {
+#ifdef INET6
+ /*
+ * be paranoid, there are getnameinfo implementation that does
+ * not present scopeid portion
+ */
+ if (data_dest.su_family == AF_INET6 &&
+ data_dest.su_scope_id != his_addr.su_scope_id)
+ goto port_check_fail;
+#endif
+
+ if (getnameinfo((struct sockaddr *)&data_dest, data_dest.su_len,
+ h1, sizeof(h1), s1, sizeof(s1), niflags))
+ goto port_check_fail;
+ if (getnameinfo((struct sockaddr *)&his_addr, his_addr.su_len,
+ h2, sizeof(h2), s2, sizeof(s2), niflags))
+ goto port_check_fail;
+
+ if (atoi(s1) < IPPORT_RESERVED || strcmp(h1, h2) != 0)
+ goto port_check_fail;
+ }
+
+ usedefault = 0;
+ if (pdata >= 0) {
+ (void) close(pdata);
+ pdata = -1;
+ }
+ reply(200, "%s command successful.", cmd);
+}
diff --git a/contrib/lukemftpd/src/ftpd.8 b/contrib/lukemftpd/src/ftpd.8
new file mode 100644
index 0000000..65f993a
--- /dev/null
+++ b/contrib/lukemftpd/src/ftpd.8
@@ -0,0 +1,833 @@
+.\" $NetBSD: ftpd.8,v 1.63 2000/12/18 02:32:51 lukem Exp $
+.\"
+.\" Copyright (c) 1997-2000 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, 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. 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.
+.\"
+.\" @(#)ftpd.8 8.2 (Berkeley) 4/19/94
+.\"
+.Dd December 18, 2000
+.Dt FTPD 8
+.Os
+.Sh NAME
+.Nm ftpd
+.Nd
+Internet File Transfer Protocol server
+.Sh SYNOPSIS
+.Nm
+.Op Fl dHlqQrsuUwWX
+.Op Fl a Ar anondir
+.Op Fl c Ar confdir
+.Op Fl C Ar user
+.Op Fl e Ar emailaddr
+.Op Fl h Ar hostname
+.Op Fl P Ar dataport
+.Op Fl V Ar version
+.Sh DESCRIPTION
+.Nm
+is the Internet File Transfer Protocol server process.
+The server uses the
+.Tn TCP
+protocol and listens at the port specified in the
+.Dq ftp
+service specification; see
+.Xr services 5 .
+.Pp
+Available options:
+.Bl -tag -width Ds
+.It Fl a Ar anondir
+Define
+.Ar anondir
+as the directory to
+.Xr chroot 2
+into for anonymous logins.
+Default is the home directory for the ftp user.
+This can also be specified with the
+.Xr ftpd.conf 5
+.Sy chroot
+directive.
+.It Fl c Ar confdir
+Change the root directory of the configuration files from
+.Dq Pa /etc
+to
+.Ar confdir .
+This changes the directory for the following files:
+.Pa /etc/ftpchroot ,
+.Pa /etc/ftpusers ,
+.Pa /etc/ftpwelcome ,
+.Pa /etc/motd ,
+and the file specified by the
+.Xr ftpd.conf 5
+.Sy limit
+directive.
+.It Fl C Ar user
+Check whether
+.Ar user
+would be granted access under
+the restrictions given in
+.Xr ftpusers 5
+and exit without attempting a connection.
+.Nm
+exits with an exit code of 0 if access would be granted, or 1 otherwise.
+This can be useful for testing configurations.
+.It Fl d
+Debugging information is written to the syslog using a facility of
+.Dv LOG_FTP .
+.It Fl e Ar emailaddr
+Use
+.Ar emailaddr
+for the
+.Dq "\&%E"
+escape sequence (see
+.Sx Display file escape sequences )
+.It Fl h Ar hostname
+Explicitly set the hostname to advertise as to
+.Ar hostname .
+The default is the hostname associated with the IP address that
+.Nm
+is listening on.
+This ability (with or without
+.Fl h ) ,
+in conjunction with
+.Fl c Ar confdir ,
+is useful when configuring
+.Sq virtual
+.Tn FTP
+servers, each listening on separate addresses as separate names.
+Refer to
+.Xr inetd.conf 5
+for more information on starting services to listen on specific IP addresses.
+.It Fl H
+Equivalent to
+.Do
+-h
+`hostname`
+.Dc .
+.It Fl l
+Each successful and failed
+.Tn FTP
+session is logged using syslog with a facility of
+.Dv LOG_FTP .
+If this option is specified more than once, the retrieve (get), store (put),
+append, delete, make directory, remove directory and rename operations and
+their file name arguments are also logged.
+.It Fl P Ar dataport
+Use
+.Ar dataport
+as the data port, overriding the default of using the port one less
+that the port
+.Nm
+is listening on.
+.It Fl q
+Enable the use of pid files for keeping track of the number of logged-in
+users per class.
+This is the default.
+.It Fl Q
+Disable the use of pid files for keeping track of the number of logged-in
+users per class.
+This may reduce the load on heavily loaded
+.Tn FTP
+servers.
+.It Fl r
+Permanently drop root privileges once the user is logged in.
+The use of this option may result in the server using a port other
+than the (listening-port - 1) for
+.Sy PORT
+style commands, which is contrary to the
+.Cm RFC 959
+specification, but in practice very few clients rely upon this behaviour.
+See
+.Sx SECURITY CONSIDERATIONS
+below for more details.
+.It Fl s
+Require a secure authentication mechanism like Kerberos or S/Key to be used.
+.It Fl u
+Log each concurrent
+.Tn FTP
+session to
+.Pa /var/run/utmp ,
+making them visible to commands such as
+.Xr who 1 .
+.It Fl U
+Don't log each concurrent
+.Tn FTP
+session to
+.Pa /var/run/utmp .
+This is the default.
+.It Fl V Ar version
+Use
+.Ar version
+as the version to advertise in the login banner and in the output of
+.Sy STAT
+and
+.Sy SYST
+instead of the default version information.
+If
+.Ar version
+is empty or
+.Sq -
+then don't display any version information.
+.It Fl w
+Log each
+.Tn FTP
+session to
+.Pa /var/log/wtmp ,
+making them visible to commands such as
+.Xr last 1 .
+This is the default.
+.It Fl W
+Don't log each
+.Tn FTP
+session to
+.Pa /var/log/wtmp .
+.It Fl X
+Log
+.Tn wu-ftpd
+style
+.Sq xferlog
+entries to the syslog, prefixed with
+.Dq "xferlog:\ " ,
+using a facility of
+.Dv LOG_FTP .
+These syslog entries can be converted to a
+.Tn wu-ftpd
+style
+.Pa xferlog
+file suitable for input into a third-party log analysis tool with a command
+similar to:
+.Dl "grep 'xferlog: ' /var/log/xferlog | \e"
+.Dl "\ \ \ sed -e 's/^.*xferlog: //' > wuxferlog"
+.El
+.Pp
+The file
+.Pa /etc/nologin
+can be used to disable
+.Tn 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/motd
+exists (under the chroot directory if applicable),
+.Nm
+prints it after a successful login.
+This may be changed with the
+.Xr ftpd.conf 5
+directive
+.Sy motd .
+.Pp
+The
+.Nm
+server currently supports the following
+.Tn FTP
+requests.
+The case of the requests is ignored.
+.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"
+.It CDUP Ta "change to parent of current working directory"
+.It CWD Ta "change working directory"
+.It DELE Ta "delete a file"
+.It EPSV Ta "prepare for server-to-server transfer"
+.It EPRT Ta "specify data connection port"
+.It FEAT Ta "list extra features that are not defined in" Cm "RFC 959"
+.It HELP Ta "give help information"
+.It LIST Ta "give list files in a directory" Pq Dq Li "ls -lA"
+.It LPSV Ta "prepare for server-to-server transfer"
+.It LPRT Ta "specify data connection port"
+.It MLSD Ta "list contents of directory in a machine-processable form"
+.It MLST Ta "show a pathname in a machine-processable form"
+.It MKD Ta "make a directory"
+.It MDTM Ta "show last modification time of file"
+.It MODE Ta "specify data transfer" Em mode
+.It NLST Ta "give name list of files in directory"
+.It NOOP Ta "do nothing"
+.It OPTS Ta "define persistent options for a given command"
+.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"
+.It RNFR Ta "specify rename-from file name"
+.It RNTO Ta "specify rename-to file name"
+.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"
+.It STOU Ta "store a file with a unique name"
+.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)"
+.It XPWD Ta "print the current working directory (deprecated)"
+.It XRMD Ta "remove a directory (deprecated)"
+.El
+.Pp
+The following non-standard or
+.Ux
+specific commands are supported by the SITE request.
+.Pp
+.Bl -column Request -offset indent
+.It Sy Request Ta Sy Description
+.It CHMOD Ta "change mode of a file, e.g. ``SITE CHMOD 755 filename''"
+.It HELP Ta "give help information."
+.It IDLE Ta "set idle-timer, e.g. ``SITE IDLE 60''"
+.It RATEGET Ta "set maximum get rate throttle in bytes/second, e.g. ``SITE RATEGET 5k''"
+.It RATEPUT Ta "set maximum put rate throttle in bytes/second, e.g. ``SITE RATEPUT 5k''"
+.It UMASK Ta "change umask, e.g. ``SITE UMASK 002''"
+.El
+.Pp
+The following
+.Tn FTP
+requests (as specified in
+.Cm RFC 959 )
+are recognized, but are not implemented:
+.Sy ACCT ,
+.Sy SMNT ,
+and
+.Sy REIN .
+.Sy MDTM
+and
+.Sy SIZE
+are not specified in
+.Cm RFC 959 ,
+but will appear in the
+next updated
+.Tn FTP
+RFC.
+.Pp
+The
+.Nm
+server will abort an active file transfer only when the
+.Sy 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
+.Cm RFC 959 .
+If a
+.Sy STAT
+command is received during a data transfer, preceded by a Telnet IP
+and Synch, transfer status will be returned.
+.Pp
+.Nm
+interprets file names according to the
+.Dq globbing
+conventions used by
+.Xr csh 1 .
+This allows users to utilize the metacharacters
+.Dq Li \&*?[]{}~ .
+.Sh User authentication
+.Pp
+.Nm
+authenticates users according to five rules.
+.Pp
+.Bl -enum -offset indent
+.It
+The login name must be in the password data base,
+.Pa /etc/pwd.db ,
+and not have a null password.
+In this case a password must be provided by the client before any
+file operations may be performed.
+If the user has an S/Key key, the response from a successful
+.Sy USER
+command will include an S/Key challenge.
+The client may choose to respond with a
+.Sy PASS
+command giving either
+a standard password or an S/Key one-time password.
+The server will automatically determine which type of password it
+has been given and attempt to authenticate accordingly.
+See
+.Xr skey 1
+for more information on S/Key authentication.
+S/Key is a Trademark of Bellcore.
+.It
+The login name must be allowed based on the information in
+.Xr ftpusers 5 .
+.It
+The user must have a standard shell returned by
+.Xr getusershell 3 .
+If the user's shell field in the password database is empty, the
+shell is assumed to be
+.Pa /bin/sh .
+.It
+If directed by the file
+.Xr ftpchroot 5
+the session's root directory will be changed by
+.Xr chroot 2
+to the directory specified in the
+.Xr ftpd.conf 5
+.Sy chroot
+directive (if set),
+or to the home directory of the user.
+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
+.Tn 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).
+.Pp
+The server performs a
+.Xr chroot 2
+to the directory specified in the
+.Xr ftpd.conf 5
+.Sy chroot
+directive (if set),
+the
+.Fl a Ar anondir
+directory (if set),
+or to the home directory of the
+.Dq ftp
+user.
+.Pp
+The server then performs a
+.Xr chdir 2
+to the directory specified in the
+.Xr ftpd.conf 5
+.Sy homedir
+directive (if set), otherwise to
+.Pa / .
+.Pp
+If other restrictions are required (such as disabling of certain
+commands and the setting of a specific umask), then appropriate
+entries in
+.Xr ftpd.conf 5
+are required.
+.Pp
+If the first character of the password supplied by an anonymous user
+is
+.Dq - ,
+then the verbose messages displayed at login and upon a
+.Sy CWD
+command are suppressed.
+.El
+.Sh Display file escape sequences
+.Pp
+When
+.Nm
+displays various files back to the client (such as
+.Pa /etc/ftpwelcome
+and
+.Pa /etc/motd ) ,
+various escape strings are replaced with information pertinent
+to the current connection.
+.Pp
+The supported escape strings are:
+.Bl -tag -width "Escape" -offset indent -compact
+.It Sy "Escape"
+.Sy Description
+.It "\&%c"
+Class name.
+.It "\&%C"
+Current working directory.
+.It "\&%E"
+Email address given with
+.Fl e .
+.It "\&%L"
+Local hostname.
+.It "\&%M"
+Maximum number of users for this class.
+Displays
+.Dq unlimited
+if there's no limit.
+.It "\&%N"
+Current number of users for this class.
+.It "\&%R"
+Remote hostname.
+.It "\&%s"
+If the result of the most recent
+.Dq "\&%M"
+or
+.Dq "\&%N"
+was not
+.Dq Li 1 ,
+print an
+.Dq s .
+.It "\&%S"
+If the result of the most recent
+.Dq "\&%M"
+or
+.Dq "\&%N"
+was not
+.Dq Li 1 ,
+print an
+.Dq S .
+.It "\&%T"
+Current time.
+.It "\&%U"
+User name.
+.It "\&%\&%"
+A
+.Dq \&%
+character.
+.El
+.Sh Setting up a restricted ftp subtree
+.Pp
+In order that system security is not breached, it is recommended
+that the
+subtrees for the
+.Dq ftp
+and
+.Dq chroot
+accounts be constructed with care, following these rules
+(replace
+.Dq ftp
+in the following directory names
+with the appropriate account name for
+.Sq chroot
+users):
+.Bl -tag -width "~ftp/incoming" -offset indent
+.It Pa ~ftp
+Make the home directory owned by
+.Dq root
+and unwritable by anyone.
+.It Pa ~ftp/bin
+Make this directory owned by
+.Dq root
+and unwritable by anyone (mode 555).
+Generally any conversion commands should be installed
+here (mode 111).
+.It Pa ~ftp/etc
+Make this directory owned by
+.Dq root
+and unwritable by anyone (mode 555).
+The files
+.Pa pwd.db
+(see
+.Xr passwd 5 )
+and
+.Pa group
+(see
+.Xr group 5 )
+must be present for the
+.Sy LIST
+command to be able to display owner and group names instead of numbers.
+The password field in
+.Xr passwd 5
+is not used, and should not contain real passwords.
+The file
+.Pa motd ,
+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 ftp or its group.
+.It Pa ~ftp/incoming
+This directory is where anonymous users place files they upload.
+The owners should be the user
+.Dq ftp
+and an appropriate group.
+Members of this group will be the only users with access to these
+files after they have been uploaded; these should be people who
+know how to deal with them appropriately.
+If you wish anonymous
+.Tn FTP
+users to be able to see the names of the
+files in this directory the permissions should be 770, otherwise
+they should be 370.
+.Pp
+The following
+.Xr ftpd.conf 5
+directives should be used:
+.Dl "modify guest off"
+.Dl "umask guest 0707"
+.Pp
+This will result in anonymous users being able to upload files to this
+directory, but they will not be able to download them, delete them, or
+overwrite them, due to the umask and disabling of the commands mentioned
+above.
+.It Pa ~ftp/tmp
+This directory is used to create temporary files which contain
+the error messages generated by a conversion or
+.Sy LIST
+command.
+The owner should be the user
+.Dq ftp .
+The permissions should be 300.
+.Pp
+If you don't enable conversion commands, or don't want anonymous users
+uploading files here (see
+.Pa ~ftp/incoming
+above), then don't create this directory.
+However, error messages from conversion or
+.Sy LIST
+commands won't be returned to the user.
+(This is the traditional behaviour.)
+Note that the
+.Xr ftpd.conf 5
+directive
+.Sy upload
+can be used to prevent users uploading here.
+.El
+.Pp
+To set up "ftp-only" accounts that provide only
+.Tn FTP ,
+but no valid shell
+login, you can copy/link
+.Pa /sbin/nologin
+to
+.Pa /sbin/ftplogin ,
+and enter
+.Pa /sbin/ftplogin
+to
+.Pa /etc/shells
+to allow logging-in via
+.Tn FTP
+into the accounts, which must have
+.Pa /sbin/ftplogin
+as login shell.
+.Sh FILES
+.Bl -tag -width /etc/ftpwelcome -compact
+.It Pa /etc/ftpchroot
+List of normal users who should be
+.Xr chroot 2 ed.
+.It Pa /etc/ftpd.conf
+Configure file conversions and other settings.
+.It Pa /etc/ftpusers
+List of unwelcome/restricted users.
+.It Pa /etc/ftpwelcome
+Welcome notice before login.
+.It Pa /etc/motd
+Welcome notice after login.
+.It Pa /etc/nologin
+If it exists, displayed and access is refused.
+.It Pa /var/run/ftpd.pids-CLASS
+State file of logged-in processes for the
+.Nm
+class
+.Sq CLASS .
+.It Pa /var/run/utmp
+List of logged-in users on the system.
+.It Pa /var/log/wtmp
+Login history database.
+.El
+.Sh SEE ALSO
+.Xr ftp 1 ,
+.Xr skey 1 ,
+.Xr who 1 ,
+.Xr getusershell 3 ,
+.Xr ftpd.conf 5 ,
+.Xr ftpchroot 5 ,
+.Xr ftpusers 5 ,
+.Xr syslogd 8
+.Sh STANDARDS
+.Nm
+recognizes all commands in
+.Cm RFC 959 ,
+follows the guidelines in
+.Cm RFC 1123 ,
+recognizes all commands in
+.Cm RFC 2228
+(although they are not supported yet),
+and supports the extensions from
+.Cm RFC 2389 ,
+.Cm RFC 2428
+and
+.Cm draft-ietf-ftpext-mlst-11 .
+.Sh HISTORY
+The
+.Nm
+command appeared in
+.Bx 4.2 .
+.Pp
+Various features such as the
+.Xr ftpd.conf 5
+functionality,
+.Cm RFC 2389 ,
+and
+.Cm draft-ietf-ftpext-mlst-11
+support was implemented in
+.Nx 1.3
+and later releases by Luke Mewburn <lukem@netbsd.org>.
+.Sh BUGS
+The server must run as the super-user to create sockets with
+privileged port numbers (i.e, those less than
+.Dv IPPORT_RESERVED ,
+which is 1024).
+If
+.Nm
+is listening on a privileged port
+it maintains an effective user id of the logged in user, reverting
+to the super-user only when binding addresses to privileged sockets.
+The
+.Fl r
+option can be used to override this behaviour and force privileges to
+be permanently revoked; see
+.Sx SECURITY CONSIDERATIONS
+below for more details.
+.Pp
+.Nm
+may have trouble handling connections from scoped IPv6 addresses, or
+IPv4 mapped addresses
+.Po
+IPv4 connection on
+.Dv AF_INET6
+socket
+.Pc .
+For the latter case, running two daemons,
+one for IPv4 and one for IPv6, will avoid the problem.
+.Sh SECURITY CONSIDERATIONS
+.Cm RFC 959
+provides no restrictions on the
+.Sy PORT
+command, and this can lead to security problems, as
+.Nm
+can be fooled into connecting to any service on any host.
+With the
+.Dq checkportcmd
+feature of the
+.Xr ftpd.conf 5 ,
+.Sy PORT
+commands with different host addresses, or TCP ports lower than
+.Dv IPPORT_RESERVED
+will be rejected.
+This also prevents
+.Sq third-party proxy ftp
+from working.
+Use of this option is
+.Em strongly
+recommended, and enabled by default.
+.Pp
+By default
+.Nm
+uses a port that is one less than the port it is listening on to
+communicate back to the client for the
+.Sy EPRT ,
+.Sy LPRT ,
+and
+.Sy PORT
+commands, unless overridden with
+.Fl P Ar dataport .
+As the default port for
+.Nm
+(21) is a privileged port below
+.Dv IPPORT_RESERVED ,
+.Nm
+retains the ability to switch back to root privileges to bind these
+ports.
+In order to increase security by reducing the potential for a bug in
+.Nm
+providing a remote root compromise,
+.Nm
+will permanently drop root privileges if one of the following is true:
+.Bl -enum -offset indent
+.It
+.Nm
+is running on a port greater than
+.Dv IPPORT_RESERVED
+and the user has logged in as a
+.Sq guest
+or
+.Sq chroot
+user.
+.It
+.Nm
+was invoked with
+.Fl r .
+.El
+.Pp
+Don't create
+.Pa ~ftp/tmp
+if you don't want anonymous users to upload files there.
+That directory is only necessary if you want to display the error
+messages of conversion commands to the user.
+Note that if uploads are disabled with the
+.Xr ftpd.conf 5
+directive
+.Sy upload ,
+then this directory cannot be abused by the user in this way, so it
+should be safe to create.
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;
+}
diff --git a/contrib/lukemftpd/src/ftpd.conf.5 b/contrib/lukemftpd/src/ftpd.conf.5
new file mode 100644
index 0000000..0c7dc68
--- /dev/null
+++ b/contrib/lukemftpd/src/ftpd.conf.5
@@ -0,0 +1,587 @@
+.\" $NetBSD: ftpd.conf.5,v 1.15 2000/12/18 02:32:51 lukem Exp $
+.\"
+.\" Copyright (c) 1997-2000 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.
+.\"
+.Dd December 18, 2000
+.Dt FTPD.CONF 5
+.Os
+.Sh NAME
+.Nm ftpd.conf
+.Nd
+.Xr ftpd 8
+configuration file
+.Sh DESCRIPTION
+The
+.Nm
+file specifies various configuration options for
+.Xr ftpd 8
+that apply once a user has authenticated their connection.
+.Pp
+.Nm
+consists of a series of lines, each of which may contain a
+configuration directive, a comment, or a blank line.
+Directives that appear later in the file override settings by previous
+directives.
+This allows
+.Sq wildcard
+entries to define defaults, and then have class-specific overrides.
+.Pp
+A directive line has the format:
+.Dl command class [arguments]
+.Pp
+A
+.Dq \e
+is the escape character; it can be used to escape the meaning of the
+comment character, or if it is the last character on a line, extends
+a configuration directive across multiple lines.
+A
+.Dq #
+is the comment character, and all characters from it to the end of
+line are ignored (unless it is escaped with the escape character).
+.Pp
+Each authenticated user is a member of a
+.Em class ,
+which is determined by
+.Xr ftpusers 5 .
+.Em class
+is used to determine which
+.Nm
+entries apply to the user.
+The following special classes exist when parsing entries in
+.Nm "" :
+.Bl -tag -width "chroot" -compact -offset indent
+.It Sy all
+Matches any class.
+.It Sy none
+Matches no class.
+.El
+.Pp
+Each class has a type, which may be one of:
+.Bl -tag -width "CHROOT" -offset indent
+.It Sy GUEST
+Guests (as per the
+.Dq anonymous
+and
+.Dq ftp
+logins).
+A
+.Xr chroot 2
+is performed after login.
+.It Sy CHROOT
+.Xr chroot 2 ed
+users (as per
+.Xr ftpchroot 5 ) .
+A
+.Xr chroot 2
+is performed after login.
+.It Sy REAL
+Normal users.
+.El
+.Pp
+The
+.Xr ftpd 8
+.Sy STAT
+command will return the class settings for the current user as defined by
+.Nm "" .
+.Pp
+Each configuration line may be one of:
+.Bl -tag -width 4n
+.It Sy advertise Ar class Ar host
+Set the address to advertise in the response to the
+.Sy PASV
+and
+.Sy LPSV
+commands to the address for
+.Ar host
+(which may be either a host name or IP address).
+This may be useful in some firewall configurations, although many
+ftp clients may not work if the address being advertised is different
+to the address that they've connected to.
+If
+.Ar class
+is
+.Dq none
+or no argument is given, disable this.
+.It Sy checkportcmd Ar class Op Sy off
+Check the
+.Sy PORT
+command for validity.
+The
+.Sy PORT
+command will fail if the IP address specified does not match the
+.Tn FTP
+command connection, or if the remote TCP port number is less than
+.Dv IPPORT_RESERVED .
+It is
+.Em strongly
+encouraged that this option be used, espcially for sites concerned
+with potential security problems with
+.Tn FTP
+bounce attacks.
+If
+.Ar class
+is
+.Dq none
+or
+.Sy off
+is given, disable this feature, otherwise enable it.
+.It Sy chroot Ar class Op Sy pathformat
+If
+.Ar pathformat
+is not given or
+.Ar class
+is
+.Dq none ,
+use the default behaviour (see below).
+Otherwise,
+.Ar pathformat
+is parsed to create a directory to create as the root directory with
+.Xr chroot 2
+into upon login.
+.Pp
+.Ar pathformat
+can contain the following escape strings:
+.Bl -tag -width "Escape" -offset indent -compact
+.It Sy "Escape"
+.Sy Description
+.It "\&%c"
+Class name.
+.It "\&%d"
+Home directory of user.
+.It "\&%u"
+User name.
+.It "\&%\&%"
+A
+.Dq \&%
+character.
+.El
+.Pp
+The default root directory is:
+.Bl -tag -width "CHROOT" -offset indent -compact
+.It Sy CHROOT
+The user's home directory.
+.It Sy GUEST
+If
+.Fl a Ar anondir
+is given, use
+.Ar anondir ,
+otherwise the home directory of the
+.Sq ftp
+user.
+.It Sy REAL
+By default no
+.Xr chroot 2
+is performed.
+.El
+.It Sy classtype Ar class Ar type
+Set the class type of
+.Ar class
+to
+.Ar type
+(see above).
+.It Xo Sy conversion Ar class
+.Ar suffix Op Ar "type disable command"
+.Xc
+Define an automatic in-line file conversion.
+If a file to retrieve ends in
+.Ar suffix ,
+and a real file (sans
+.Ar suffix )
+exists, then the output of
+.Ar command
+is returned instead of the contents of the file.
+.Pp
+.Bl -tag -width "disable" -offset indent
+.It Ar suffix
+The suffix to initiate the conversion.
+.It Ar type
+A list of valid filetypes for the conversion.
+Valid types are:
+.Sq f
+(file), and
+.Sq d
+(directory).
+.It Ar disable
+The name of file that will prevent conversion if it exists.
+A file name of
+.Dq Pa \&.
+will prevent this disabling action
+(i.e., the conversion is always permitted.)
+.It Ar command
+The command to run for the conversion.
+The first word should be the full path name
+of the command, as
+.Xr execv 3
+is used to execute the command.
+All instances of the word
+.Dq %s
+in
+.Ar command
+are replaced with the requested file (sans
+.Ar suffix ) .
+.El
+.Pp
+Conversion directives specified later in the file override earlier
+conversions with the same suffix.
+.It Sy display Ar class Op Ar file
+If
+.Ar file
+is not given or
+.Ar class
+is
+.Dq none ,
+disable this.
+Otherwise, each time the user enters a new directory, check if
+.Ar file
+exists, and if so, display its contents to the user.
+Escape sequences are supported; refer to
+.Sx Display file escape sequences
+in
+.Xr ftpd 8
+for more information.
+.It Sy homedir Ar class Op Sy pathformat
+If
+.Ar pathformat
+is not given or
+.Ar class
+is
+.Dq none ,
+use the default behaviour (see below).
+Otherwise,
+.Ar pathformat
+is parsed to create a directory to change into upon login, and to use
+as the
+.Sq home
+directory of the user for tilde expansion in pathnames, etc.
+.Ar pathformat
+is parsed as per the
+.Sy chroot
+directive.
+.Pp
+The default home directory is the home directory of the user for
+.Sy REAL
+users, and
+.Pa /
+for
+.Sy GUEST
+and
+.Sy CHROOT
+users.
+.It Xo Sy limit Ar class
+.Ar count Op Ar file
+.Xc
+Limit the maximum number of concurrent connections for
+.Ar class
+to
+.Ar count ,
+with
+.Sq 0
+meaning unlimited connections.
+If the limit is exceeded and
+.Ar file
+is given, display its contents to the user.
+If
+.Ar class
+is
+.Dq none
+or
+.Ar count
+is not specified, disable this.
+If
+.Ar file
+is a relative path, it will be searched for in
+.Pa /etc
+(which can be overridden with
+.Fl c Ar confdir ) .
+.It Sy maxfilesize Ar class Ar size
+Set the maximum size of an uploaded file to
+.Ar size .
+If
+.Ar class
+is
+.Dq none
+or no argument is given, disable this.
+.It Sy maxtimeout Ar class Ar time
+Set the maximum timeout period that a client may request,
+defaulting to two hours.
+This cannot be less than 30 seconds, or the value for
+.Sy timeout .
+If
+.Ar class
+is
+.Dq none
+or
+.Ar time
+is not specified, set to default of 2 hours.
+.It Sy modify Ar class Op Sy off
+If
+.Ar class
+is
+.Dq none
+or
+.Sy off
+is given, disable the following commands:
+.Sy CHMOD ,
+.Sy DELE ,
+.Sy MKD ,
+.Sy RMD ,
+.Sy RNFR ,
+and
+.Sy UMASK .
+Otherwise, enable them.
+.It Sy motd Ar class Op Ar file
+If
+.Ar file
+is not given or
+.Ar class
+is
+.Dq none ,
+disable this.
+Otherwise, use
+.Ar file
+as the message of the day file to display after login.
+Escape sequences are supported; refer to
+.Sx Display file escape sequences
+in
+.Xr ftpd 8
+for more information.
+If
+.Ar file
+is a relative path, it will be searched for in
+.Pa /etc
+(which can be overridden with
+.Fl c Ar confdir ) .
+.It Sy notify Ar class Op Ar fileglob
+If
+.Ar fileglob
+is not given or
+.Ar class
+is
+.Dq none ,
+disable this.
+Otherwise, each time the user enters a new directory,
+notify the user of any files matching
+.Ar fileglob .
+.It Sy passive Ar class Op Sy off
+If
+.Ar class
+is
+.Dq none
+or
+.Sy off
+is given, disallow passive
+.Sy ( PASV ,
+.Sy LPSV ,
+and
+.Sy EPSV )
+connections.
+Otherwise, enable them.
+.It Sy portrange Ar class Ar min Ar max
+Set the range of port number which will be used for the passive data port.
+.Ar max
+must be greater than
+.Ar min ,
+and both numbers must be be between
+.Dv IPPORT_RESERVED
+(1024) and 65535.
+If
+.Ar class
+is
+.Dq none
+or no arguments are given, disable this.
+.It Sy rateget Ar class Ar rate
+Set the maximum get
+.Pq Sy RETR
+transfer rate throttle for
+.Ar class
+to
+.Ar rate
+bytes per second.
+If
+.Ar rate
+is 0, the throttle is disabled.
+If
+.Ar class
+is
+.Dq none
+or no arguments are given, disable this.
+.Pp
+An optional suffix may be provided, which changes the intrepretation of
+.Ar rate
+as follows:
+.Bl -tag -width 3n -offset indent -compact
+.It b
+Causes no modification. (Default; optional)
+.It k
+Kilo; multiply the argument by 1024
+.It m
+Mega; multiply the argument by 1048576
+.It g
+Giga; multiply the argument by 1073741824
+.It t
+Tera; multiply the argument by 1099511627776
+.El
+.It Sy rateput Ar class Ar rate
+Set the maximum put
+.Pq Sy STOR
+transfer rate throttle for
+.Ar class
+to
+.Ar rate
+bytes per second,
+which is parsed as per
+.Sy rateget Ar rate .
+If
+.Ar class
+is
+.Dq none
+or no arguments are given, disable this.
+.It Sy sanenames Ar class Op Sy off
+If
+.Ar class
+is
+.Dq none
+or
+.Sy off
+is given, allow uploaded file names to contain any characters valid for a
+file name.
+Otherwise, only permit file names which don't start with a
+.Sq \&.
+and only comprise of characters from the set
+.Dq [-+,._A-Za-z0-9] .
+.It Sy template Ar class Op Ar refclass
+Define
+.Ar refclass
+as the
+.Sq template
+for
+.Ar class ;
+any reference to
+.Ar refclass
+in following directives will also apply to members of
+.Ar class .
+This is useful to define a template class so that other classes which are
+to share common attributes can be easily defined without unnecessary
+duplication.
+There can be only one template defined at a time.
+If
+.Ar refclass
+is not given, disable the template for
+.Ar class .
+.It Sy timeout Ar class Ar time
+Set the inactivity timeout period.
+(the default is fifteen minutes).
+This cannot be less than 30 seconds, or greater than the value for
+.Sy maxtimeout .
+If
+.Ar class
+is
+.Dq none
+or
+.Ar time
+is not specified, set to the default of 15 minutes.
+.It Sy umask Ar class Ar umaskval
+Set the umask to
+.Ar umaskval .
+If
+.Ar class
+is
+.Dq none
+or
+.Ar umaskval
+is not specified, set to the default of
+.Li 027 .
+.It Sy upload Ar class Op Sy off
+If
+.Ar class
+is
+.Dq none
+or
+.Sy off
+is given, disable the following commands:
+.Sy APPE ,
+.Sy STOR ,
+and
+.Sy STOU ,
+as well as the modify commands:
+.Sy CHMOD ,
+.Sy DELE ,
+.Sy MKD ,
+.Sy RMD ,
+.Sy RNFR ,
+and
+.Sy UMASK .
+Otherwise, enable them.
+.El
+.Sh DEFAULTS
+The following defaults are used:
+.Pp
+.Bd -literal -offset indent -compact
+checkportcmd all
+classtype chroot CHROOT
+classtype guest GUEST
+classtype real REAL
+display none
+limit all -1 # unlimited connections
+maxtimeout all 7200 # 2 hours
+modify all
+motd all motd
+notify none
+passive all
+timeout all 900 # 15 minutes
+umask all 027
+upload all
+modify guest off
+umask guest 0707
+.Ed
+.Sh FILES
+.Bl -tag -width /usr/share/examples/ftpd/ftpd.conf -compact
+.It Pa /etc/ftpd.conf
+This file.
+.It Pa /usr/share/examples/ftpd/ftpd.conf
+A sample
+.Nm
+file.
+.El
+.Sh SEE ALSO
+.Xr ftpchroot 5 ,
+.Xr ftpusers 5 ,
+.Xr ftpd 8
+.Sh HISTORY
+The
+.Nm
+functionality was implemented in
+.Nx 1.3
+and later releases by Luke Mewburn, based on work by Simon Burge.
diff --git a/contrib/lukemftpd/src/ftpusers.5 b/contrib/lukemftpd/src/ftpusers.5
new file mode 100644
index 0000000..85f500f
--- /dev/null
+++ b/contrib/lukemftpd/src/ftpusers.5
@@ -0,0 +1,183 @@
+.\" $NetBSD: ftpusers.5,v 1.10 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.
+.\"
+.Dd July 17, 2000
+.Dt FTPUSERS 5
+.Os
+.Sh NAME
+.Nm ftpusers ,
+.Nm ftpchroot
+.Nd
+.Xr ftpd 8
+access control file
+.Sh DESCRIPTION
+The
+.Nm
+file provides user access control for
+.Xr ftpd 8
+by defining which users may login.
+.Pp
+If the
+.Nm
+file does not exist, all users are denied access.
+.Pp
+A
+.Dq \e
+is the escape character; it can be used to escape the meaning of the
+comment character, or if it is the last character on a line, extends
+a configuration directive across multiple lines.
+A
+.Dq #
+is the comment character, and all characters from it to the end of
+line are ignored (unless it is escaped with the escape character).
+.Pp
+The syntax of each line is:
+.Dl userglob[:groupglob][@host] [directive [class]]
+.Pp
+These elements are:
+.Bl -tag -width "groupglob" -offset indent
+.It Sy userglob
+matched against the user name, using
+.Xr fnmatch 3
+glob matching
+(e.g,
+.Sq f* ) .
+.It Sy groupglob
+matched against all the groups that the user is a member of, using
+.Xr fnmatch 3
+glob matching
+(e.g,
+.Sq *src ) .
+.It Sy host
+either a CIDR address (refer to
+.Xr inet_net_pton 3 )
+to match against the remote address
+(e.g,
+.Sq 1.2.3.4/24 ) ,
+or a glob to match against the remote hostname
+(e.g,
+.Sq *.netbsd.org ) .
+.It Sy directive
+If
+.Dq allow
+or
+.Dq yes
+the user is allowed access.
+If
+.Dq deny
+or
+.Dq no ,
+or
+.Sy directive
+is not given, the user is denied access.
+.It Sy class
+defines the class to use in
+.Xr ftpd.conf 5 .
+.El
+.Pp
+If
+.Sy class
+is not given, it defaults to one of the following:
+.Bl -tag -width "chroot" -offset indent
+.It Sy chroot
+If there is a match in
+.Sx /etc/ftpchroot
+for the user.
+.It Sy guest
+If the user name is
+.Dq anonymous
+or
+.Sq ftp .
+.It Sy real
+If neither of the above is true.
+.El
+.Pp
+No further comparisons are attempted after the first successful match.
+If no match is found, the user is granted access.
+This syntax is backward-compatable with the old syntax.
+.Pp
+If a user requests a guest login, the
+.Xr ftpd 8
+server checks to see that
+both
+.Dq anonymous
+and
+.Dq ftp
+have access, so if you deny all users by default, you will need to add both
+.Dq "anonymous allow"
+and
+.Dq "ftp allow"
+to
+.Pa /etc/ftpusers
+in order to allow guest logins.
+.Ss /etc/ftpchroot
+The file
+.Pa /etc/ftpchroot
+is used to determine which users will have their session's root directory
+changed (using
+.Xr chroot 2 ) ,
+either to the directory specified in the
+.Xr ftpd.conf 5
+.Sy chroot
+directive (if set),
+or to the home directory of the user.
+If the file does not exist, the root directory change is not performed.
+.Pp
+The syntax is similar to
+.Nm "" ,
+except that the
+.Sy class
+argument is ignored.
+If there's a positive match, the session's root directory is changed.
+No further comparisons are attempted after the first successful match.
+This syntax is backward-compatable with the old syntax.
+.Sh FILES
+.Bl -tag -width /etc/ftpchroot -compact
+.It Pa /etc/ftpchroot
+List of normal users who should be
+.Xr chroot 2 ed.
+.It Pa /etc/ftpusers
+This file.
+.It Pa /usr/share/examples/ftpd/ftpusers
+A sample
+.Nm
+file.
+.El
+.Sh SEE ALSO
+.Xr fnmatch 3 ,
+.Xr inet_net_pton 3 ,
+.Xr ftpd.conf 5 ,
+.Xr ftpd 8
diff --git a/contrib/lukemftpd/src/logutmp.c b/contrib/lukemftpd/src/logutmp.c
new file mode 100644
index 0000000..7ef6437
--- /dev/null
+++ b/contrib/lukemftpd/src/logutmp.c
@@ -0,0 +1,111 @@
+/*
+ * Portions Copyright (c) 1988, 1993
+ * The Regents of the University of California. All rights reserved.
+ * Portions Copyright (c) 1996, Jason Downs. 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.
+ */
+
+#include "lukemftpd.h"
+
+typedef struct utmp UTMP;
+
+static int fd = -1;
+static int topslot = -1;
+
+/*
+ * Special versions of login()/logout() which hold the utmp file open,
+ * for use with ftpd.
+ */
+
+void
+login(const UTMP *ut)
+{
+ UTMP ubuf;
+
+ /*
+ * First, loop through /etc/ttys, if needed, to initialize the
+ * top of the tty slots, since ftpd has no tty.
+ */
+ if (topslot < 0) {
+ topslot = 0;
+ while (getttyent() != (struct ttyent *)NULL)
+ topslot++;
+ }
+ if ((topslot < 0) || ((fd < 0)
+ && (fd = open(_PATH_UTMP, O_RDWR|O_CREAT, 0644)) < 0))
+ return;
+
+ /*
+ * Now find a slot that's not in use...
+ */
+ (void)lseek(fd, (off_t)(topslot * sizeof(UTMP)), SEEK_SET);
+
+ while (1) {
+ if (read(fd, &ubuf, sizeof(UTMP)) == sizeof(UTMP)) {
+ if (!ubuf.ut_name[0]) {
+ (void)lseek(fd, -(off_t)sizeof(UTMP), SEEK_CUR);
+ break;
+ }
+ topslot++;
+ } else {
+ (void)lseek(fd, (off_t)(topslot * sizeof(UTMP)),
+ SEEK_SET);
+ break;
+ }
+ }
+
+ (void)write(fd, ut, sizeof(UTMP));
+}
+
+int
+logout(const char *line)
+{
+ UTMP ut;
+ int rval;
+
+ rval = 0;
+ if (fd < 0)
+ return(rval);
+
+ (void)lseek(fd, 0, SEEK_SET);
+
+ while (read(fd, &ut, sizeof(UTMP)) == sizeof(UTMP)) {
+ if (!ut.ut_name[0]
+ || strncmp(ut.ut_line, line, UT_LINESIZE))
+ continue;
+ memset(ut.ut_name, 0, UT_NAMESIZE);
+ memset(ut.ut_host, 0, UT_HOSTSIZE);
+ (void)time(&ut.ut_time);
+ (void)lseek(fd, -(off_t)sizeof(UTMP), SEEK_CUR);
+ (void)write(fd, &ut, sizeof(UTMP));
+ rval = 1;
+ }
+ return(rval);
+}
diff --git a/contrib/lukemftpd/src/logwtmp.c b/contrib/lukemftpd/src/logwtmp.c
new file mode 100644
index 0000000..b7f2925
--- /dev/null
+++ b/contrib/lukemftpd/src/logwtmp.c
@@ -0,0 +1,65 @@
+/* $NetBSD: logwtmp.c,v 1.16 2001/02/04 22:04:12 christos Exp $ */
+
+/*
+ * Copyright (c) 1988, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ * must display the following acknowledgement:
+ * This product includes software developed by the University of
+ * California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ */
+
+#include "lukemftpd.h"
+
+#include "extern.h"
+
+static int fd = -1;
+
+/*
+ * Modified version of logwtmp that holds wtmp file open
+ * after first call, for use with ftp (which may chroot
+ * after login, but before logout).
+ */
+void
+logwtmp(const char *line, const char *name, const char *host)
+{
+ struct utmp ut;
+ struct stat buf;
+
+ if (fd < 0 && (fd = open(_PATH_WTMP, O_WRONLY|O_APPEND, 0)) < 0)
+ return;
+ if (fstat(fd, &buf) == 0) {
+ (void)strncpy(ut.ut_line, line, sizeof(ut.ut_line));
+ (void)strncpy(ut.ut_name, name, sizeof(ut.ut_name));
+ (void)strncpy(ut.ut_host, host, sizeof(ut.ut_host));
+ (void)time(&ut.ut_time);
+ if (write(fd, (char *)&ut, sizeof(struct utmp)) !=
+ sizeof(struct utmp))
+ (void)ftruncate(fd, buf.st_size);
+ }
+}
diff --git a/contrib/lukemftpd/src/pathnames.h b/contrib/lukemftpd/src/pathnames.h
new file mode 100644
index 0000000..a5766b5
--- /dev/null
+++ b/contrib/lukemftpd/src/pathnames.h
@@ -0,0 +1,51 @@
+/* $NetBSD: pathnames.h,v 1.10 2000/03/06 21:42:26 lukem Exp $ */
+
+/*
+ * Copyright (c) 1989, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ * must display the following acknowledgement:
+ * This product includes software developed by the University of
+ * California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * @(#)pathnames.h 8.1 (Berkeley) 6/4/93
+ */
+
+#ifndef _DEFAULT_CONFDIR
+#define _DEFAULT_CONFDIR "/etc"
+#endif
+
+#define _PATH_FTPCHROOT "ftpchroot"
+#define _PATH_FTPDCONF "ftpd.conf"
+#define _PATH_FTPLOGINMESG "motd"
+#undef _PATH_FTPUSERS
+#define _PATH_FTPUSERS "ftpusers"
+#define _PATH_FTPWELCOME "ftpwelcome"
+
+#define _PATH_CLASSPIDS "/var/run/ftpd.pids-"
+
+#define TMPFILE "/tmp/ftpdXXXXXXX"
diff --git a/contrib/lukemftpd/src/popen.c b/contrib/lukemftpd/src/popen.c
new file mode 100644
index 0000000..7e8d036
--- /dev/null
+++ b/contrib/lukemftpd/src/popen.c
@@ -0,0 +1,236 @@
+/* $NetBSD: popen.c,v 1.26 2001/04/25 01:46:26 lukem Exp $ */
+
+/*-
+ * Copyright (c) 1999-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) 1988, 1993, 1994
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software written by Ken Arnold and
+ * published in UNIX Review, Vol. 6, No. 8.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ * must display the following acknowledgement:
+ * This product includes software developed by the University of
+ * California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ */
+
+#include "lukemftpd.h"
+
+#include "extern.h"
+
+#define INCR 100
+/*
+ * 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.
+ * If stderrfd != -1, then send stderr of a read command there,
+ * otherwise close stderr.
+ */
+static int *pids;
+static int fds;
+
+extern int ls_main(int, char *[]);
+
+FILE *
+ftpd_popen(char *argv[], const char *type, int stderrfd)
+{
+ FILE *iop;
+ int argc, pdes[2], pid, isls;
+ char **pop;
+ StringList *sl;
+
+ iop = NULL;
+ isls = 0;
+ if ((*type != 'r' && *type != 'w') || type[1])
+ return (NULL);
+
+ if (!pids) {
+ if ((fds = getdtablesize()) <= 0)
+ return (NULL);
+ if ((pids = (int *)malloc((u_int)(fds * sizeof(int)))) == NULL)
+ return (NULL);
+ memset(pids, 0, fds * sizeof(int));
+ }
+ if (pipe(pdes) < 0)
+ return (NULL);
+
+ if ((sl = sl_init()) == NULL)
+ goto pfree;
+
+ /* glob each piece */
+ if (sl_add(sl, xstrdup(argv[0])) == -1)
+ goto pfree;
+ for (argc = 1; argv[argc]; argc++) {
+ glob_t gl;
+ int flags = GLOB_BRACE|GLOB_NOCHECK|GLOB_TILDE|GLOB_LIMIT;
+
+ memset(&gl, 0, sizeof(gl));
+ if (glob(argv[argc], flags, NULL, &gl)) {
+ if (sl_add(sl, xstrdup(argv[argc])) == -1) {
+ globfree(&gl);
+ goto pfree;
+ }
+ } else {
+ for (pop = gl.gl_pathv; *pop; pop++) {
+ if (sl_add(sl, xstrdup(*pop)) == -1) {
+ globfree(&gl);
+ goto pfree;
+ }
+ }
+ }
+ globfree(&gl);
+ }
+ if (sl_add(sl, NULL) == -1)
+ goto pfree;
+
+#ifndef NO_INTERNAL_LS
+ isls = (strcmp(sl->sl_str[0], INTERNAL_LS) == 0);
+#endif
+
+#if HAVE_VFORK
+ pid = isls ? fork() : vfork();
+#else
+ pid = fork();
+#endif
+ switch (pid) {
+ case -1: /* error */
+ (void)close(pdes[0]);
+ (void)close(pdes[1]);
+ goto pfree;
+ /* NOTREACHED */
+ case 0: /* child */
+ if (*type == 'r') {
+ if (pdes[1] != STDOUT_FILENO) {
+ dup2(pdes[1], STDOUT_FILENO);
+ (void)close(pdes[1]);
+ }
+ if (stderrfd == -1)
+ (void)close(STDERR_FILENO);
+ else
+ dup2(stderrfd, STDERR_FILENO);
+ (void)close(pdes[0]);
+ } else {
+ if (pdes[0] != STDIN_FILENO) {
+ dup2(pdes[0], STDIN_FILENO);
+ (void)close(pdes[0]);
+ }
+ (void)close(pdes[1]);
+ }
+#ifndef NO_INTERNAL_LS
+ if (isls) { /* use internal ls */
+#if HAVE_OPTRESET
+ optreset = 1;
+#endif
+ optind = optopt = 1;
+ closelog();
+ exit(ls_main(sl->sl_cur - 1, sl->sl_str));
+ }
+#endif
+
+ execv(sl->sl_str[0], sl->sl_str);
+ _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:
+ if (sl)
+ sl_free(sl, 1);
+ return (iop);
+}
+
+int
+ftpd_pclose(FILE *iop)
+{
+ int fdes, status;
+ pid_t pid;
+ sigset_t sigset, osigset;
+
+ /*
+ * pclose returns -1 if stream is not associated with a
+ * `popened' command, or, if already `pclosed'.
+ */
+ if (pids == 0 || pids[fdes = fileno(iop)] == 0)
+ return (-1);
+ (void)fclose(iop);
+ sigemptyset(&sigset);
+ sigaddset(&sigset, SIGINT);
+ sigaddset(&sigset, SIGQUIT);
+ sigaddset(&sigset, SIGHUP);
+ sigprocmask(SIG_BLOCK, &sigset, &osigset);
+ while ((pid = waitpid(pids[fdes], &status, 0)) < 0 && errno == EINTR)
+ continue;
+ sigprocmask(SIG_SETMASK, &osigset, NULL);
+ pids[fdes] = 0;
+ if (pid < 0)
+ return (pid);
+ if (WIFEXITED(status))
+ return (WEXITSTATUS(status));
+ return (1);
+}
diff --git a/contrib/lukemftpd/src/version.h b/contrib/lukemftpd/src/version.h
new file mode 100644
index 0000000..1a075c7
--- /dev/null
+++ b/contrib/lukemftpd/src/version.h
@@ -0,0 +1,40 @@
+/* $NetBSD: version.h,v 1.32 2001/04/25 01:46:26 lukem Exp $ */
+/*-
+ * Copyright (c) 1999-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.
+ */
+
+#ifndef FTPD_VERSION
+#define FTPD_VERSION "NetBSD-ftpd 20010425"
+#endif
OpenPOWER on IntegriCloud