diff options
author | obrien <obrien@FreeBSD.org> | 2001-07-19 16:25:08 +0000 |
---|---|---|
committer | obrien <obrien@FreeBSD.org> | 2001-07-19 16:25:08 +0000 |
commit | e4751f9e00971d0c774736bb50344b7ea67d40b0 (patch) | |
tree | 5e3fc097f0652fa73b8d2880dc1208f8bf557809 /contrib/lukemftpd/src | |
download | FreeBSD-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.in | 61 | ||||
-rw-r--r-- | contrib/lukemftpd/src/arpaftp.h | 111 | ||||
-rw-r--r-- | contrib/lukemftpd/src/cmds.c | 791 | ||||
-rw-r--r-- | contrib/lukemftpd/src/conf.c | 1007 | ||||
-rw-r--r-- | contrib/lukemftpd/src/extern.h | 372 | ||||
-rw-r--r-- | contrib/lukemftpd/src/ftpcmd.y | 1808 | ||||
-rw-r--r-- | contrib/lukemftpd/src/ftpd.8 | 833 | ||||
-rw-r--r-- | contrib/lukemftpd/src/ftpd.c | 2947 | ||||
-rw-r--r-- | contrib/lukemftpd/src/ftpd.conf.5 | 587 | ||||
-rw-r--r-- | contrib/lukemftpd/src/ftpusers.5 | 183 | ||||
-rw-r--r-- | contrib/lukemftpd/src/logutmp.c | 111 | ||||
-rw-r--r-- | contrib/lukemftpd/src/logwtmp.c | 65 | ||||
-rw-r--r-- | contrib/lukemftpd/src/pathnames.h | 51 | ||||
-rw-r--r-- | contrib/lukemftpd/src/popen.c | 236 | ||||
-rw-r--r-- | contrib/lukemftpd/src/version.h | 40 |
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 |