From 62da011387586b019f85cdc44165baf17b9633da Mon Sep 17 00:00:00 2001 From: Ben Gras Date: Thu, 30 May 2013 14:47:40 +0000 Subject: [PATCH] import netbsd ftpd Change-Id: Id7a3dbd40a6f37c55bcbb0d1456301f60626298f --- distrib/sets/lists/minix/mi | 5 + etc/rc.daemons.dist | 2 +- libexec/Makefile | 2 +- libexec/ftpd/Makefile | 58 + libexec/ftpd/cmds.c | 967 +++++++++ libexec/ftpd/conf.c | 953 +++++++++ libexec/ftpd/extern.h | 380 ++++ libexec/ftpd/ftpcmd.c | 2578 ++++++++++++++++++++++ libexec/ftpd/ftpcmd.y | 1875 ++++++++++++++++ libexec/ftpd/ftpd.8 | 871 ++++++++ libexec/ftpd/ftpd.c | 4000 +++++++++++++++++++++++++++++++++++ libexec/ftpd/ftpd.conf.5 | 738 +++++++ libexec/ftpd/ftpusers.5 | 179 ++ libexec/ftpd/logutmp.c | 172 ++ libexec/ftpd/logwtmp.c | 145 ++ libexec/ftpd/pathnames.h | 48 + libexec/ftpd/popen.c | 245 +++ libexec/ftpd/version.h | 33 + releasetools/nbsd_ports | 1 + 19 files changed, 13250 insertions(+), 2 deletions(-) create mode 100644 libexec/ftpd/Makefile create mode 100644 libexec/ftpd/cmds.c create mode 100644 libexec/ftpd/conf.c create mode 100644 libexec/ftpd/extern.h create mode 100644 libexec/ftpd/ftpcmd.c create mode 100644 libexec/ftpd/ftpcmd.y create mode 100644 libexec/ftpd/ftpd.8 create mode 100644 libexec/ftpd/ftpd.c create mode 100644 libexec/ftpd/ftpd.conf.5 create mode 100644 libexec/ftpd/ftpusers.5 create mode 100644 libexec/ftpd/logutmp.c create mode 100644 libexec/ftpd/logwtmp.c create mode 100644 libexec/ftpd/pathnames.h create mode 100644 libexec/ftpd/popen.c create mode 100644 libexec/ftpd/version.h diff --git a/distrib/sets/lists/minix/mi b/distrib/sets/lists/minix/mi index 3ab7c83ce..affcc01d4 100644 --- a/distrib/sets/lists/minix/mi +++ b/distrib/sets/lists/minix/mi @@ -128,6 +128,7 @@ ./lib minix-sys ./lib/cpp minix-sys ./libexec minix-sys +./usr/libexec/ftpd minix-sys ./libexec/ld.elf_so minix-sys ./libexec/ld-elf.so.1 minix-sys ./mnt minix-sys @@ -3667,6 +3668,10 @@ ./usr/man/man5/editrc.5 minix-sys ./usr/man/man5/ethers.5 minix-sys ./usr/man/man5/fstab.5 minix-sys +./usr/man/man5/ftpchroot.5 minix-sys +./usr/man/man5/ftpd.conf.5 minix-sys +./usr/man/man5/ftpusers.5 minix-sys +./usr/man/man8/ftpd.8 minix-sys ./usr/man/man5/group.5 minix-sys ./usr/man/man5/hosts.5 minix-sys ./usr/man/man5/httpd.conf.5 minix-sys diff --git a/etc/rc.daemons.dist b/etc/rc.daemons.dist index ebd7db9b2..2bb8deaf3 100644 --- a/etc/rc.daemons.dist +++ b/etc/rc.daemons.dist @@ -1,4 +1,4 @@ daemonize talkd daemonize tcpd shell in.rshd daemonize tcpd telnet in.telnetd -daemonize tcpd ftp in.ftpd +daemonize tcpd ftp /usr/libexec/ftpd diff --git a/libexec/Makefile b/libexec/Makefile index f2a9cb621..fc47af434 100644 --- a/libexec/Makefile +++ b/libexec/Makefile @@ -4,7 +4,7 @@ .include SUBDIR= \ - \ + ftpd \ ld.elf_so .if defined(__MINIX) diff --git a/libexec/ftpd/Makefile b/libexec/ftpd/Makefile new file mode 100644 index 000000000..615c9bd7f --- /dev/null +++ b/libexec/ftpd/Makefile @@ -0,0 +1,58 @@ +# $NetBSD: Makefile,v 1.63 2011/08/14 11:46:28 christos Exp $ +# @(#)Makefile 8.2 (Berkeley) 4/4/94 + +.include + +PROG= ftpd +SRCS= cmds.c conf.c ftpd.c ftpcmd.y logutmp.c logwtmp.c popen.c +.ifdef __MINIX +CPPFLAGS+=-I${.CURDIR} +.else +CPPFLAGS+=-I${.CURDIR} -DSUPPORT_UTMP -DSUPPORT_UTMPX -DLOGIN_CAP +.endif +DPADD+= ${LIBCRYPT} ${LIBUTIL} +LDADD+= -lcrypt -lutil +MAN= ftpd.conf.5 ftpusers.5 ftpd.8 +MLINKS= ftpusers.5 ftpchroot.5 + +.if defined(NO_INTERNAL_LS) +CPPFLAGS+=-DNO_INTERNAL_LS +.else +# for `internal' ls +SRCS+= ls.c cmp.c print.c util.c +.PATH: ${NETBSDSRCDIR}/bin/ls +.endif + +.if (${USE_INET6} != "no") +CPPFLAGS+=-DINET6 +.endif + +.if (${USE_PAM} != "no") +CPPFLAGS+=-DUSE_PAM +DPADD+= ${LIBPAM} ${PAM_STATIC_DPADD} +LDADD+= -lpam ${PAM_STATIC_LDADD} +.else # USE_PAM == no +.if (${USE_SKEY} != "no") +CPPFLAGS+=-DSKEY +DPADD+= ${LIBSKEY} +LDADD+= -lskey +.endif +.endif # USE_PAM == no + +ftpd.o ftpcmd.o: version.h + +#.if (${USE_KERBEROS} != "no") +# +#.PATH: ${NETBSDSRCDIR}/usr.bin/login +# +#SRCS+= k5login.c +#CPPFLAGS+=-DKERBEROS5 +#DPADD+= ${LIBKRB5} ${LIBASN1} +#LDADD+= -lkrb5 -lasn1 +# +#DPADD+= ${LIBCRYPTO} ${{LIBCRYPT} ${LIBROKEN} ${LIBCOM_ERR} +#LDADD+= -lcrypto -lcrypt -lroken -lcom_err +# +#.endif + +.include diff --git a/libexec/ftpd/cmds.c b/libexec/ftpd/cmds.c new file mode 100644 index 000000000..e3d12ee65 --- /dev/null +++ b/libexec/ftpd/cmds.c @@ -0,0 +1,967 @@ +/* $NetBSD: cmds.c,v 1.31 2012/06/19 06:06:34 dholland Exp $ */ + +/* + * Copyright (c) 1999-2009 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. + * + * 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. 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 +#ifndef lint +__RCSID("$NetBSD: cmds.c,v 1.31 2012/06/19 06:06:34 dholland Exp $"); +#endif /* not lint */ + +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef KERBEROS5 +#include +#endif + +#include "extern.h" + +typedef enum { + FE_MLSD = 1<<0, /* if op is MLSD (MLST otherwise ) */ + FE_ISCURDIR = 1<<1, /* if name is the current directory */ +} factflag_t; + +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 */ + factflag_t flags; /* flags */ +} 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)) + +static char cached_path[MAXPATHLEN + 1] = "/"; +static void discover_path(char *, const char *); + +void +cwd(const char *path) +{ + + if (chdir(path) < 0) + perror_reply(550, path); + else { + show_chdir_messages(250); + ack("CWD"); + if (getcwd(cached_path, MAXPATHLEN) == NULL) { + discover_path(cached_path, path); + } + } +} + +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) +{ + size_t 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; + } + if ((dirp = opendir(path)) == NULL) + goto mlsdperror; + + dout = dataconn("MLSD", (off_t)-1, "w"); + if (dout == NULL) + return; + + memset(&f, 0, sizeof(f)); + f.stat = &sb; + f.flags |= FE_MLSD; + 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.flags |= FE_ISCURDIR; /* 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.flags &= ~FE_ISCURDIR; + } + 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); + memset(&f, 0, sizeof(f)); + f.path = path; + f.display = path; + f.stat = &sb; + f.pdirstat = NULL; + 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(502, "%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]; + size_t 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, ftpd_strdup(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) { + if (chdir(cached_path) < 0) { + reply(550, "Can't get the current directory: %s.", + strerror(errno)); + return; + } + (void)strlcpy(path, cached_path, MAXPATHLEN); + } + 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 (ftpd_strdup(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; + } + if (stbuf.st_size > 10240) { + reply(550, "%s: file too large for SIZE.", filename); + (void) fclose(fin); + return; + } + + count = 0; + while((c = getc(fin)) != EOF) { + if (c == '\n') /* will get expanded to \r\n */ + count++; + count++; + } + (void) fclose(fin); + + reply(213, LLF, (LLT)count); + break; + } + default: + reply(504, "SIZE not implemented for Type %c.", "?AEIL"[type]); + } +} + +void +statfilecmd(const char *filename) +{ + FILE *fin; + int c; + int atstart; + const char *argv[] = { INTERNAL_LS, "-lgA", "", NULL }; + + argv[2] = filename; + fin = ftpd_popen(argv, "r", STDOUT_FILENO); + reply(-211, "status of %s:", filename); +/* XXX: use fgetln() or fparseln() here? */ + atstart = 1; + while ((c = getc(fin)) != EOF) { + if (c == '\n') { + if (ferror(stdout)){ + perror_reply(421, "control connection"); + (void) ftpd_pclose(fin); + dologout(1); + /* NOTREACHED */ + } + if (ferror(fin)) { + perror_reply(551, filename); + (void) ftpd_pclose(fin); + return; + } + CPUTC('\r', stdout); + } + if (atstart && isdigit(c)) + CPUTC(' ', stdout); + CPUTC(c, stdout); + atstart = (c == '\n'); + } + (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->flags & FE_MLSD) { + if ((fe->flags & FE_ISCURDIR) || ISDOTDIR(fe->display)) + cprintf(fd, "cdir"); + else if (ISDOTDOTDIR(fe->display)) + cprintf(fd, "pdir"); + else + cprintf(fd, "dir"); + } 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-" ULLF "/" ULLF, + S_ISBLK(fe->stat->st_mode) ? "blk" : "chr", + (ULLT)major(fe->stat->st_rdev), + (ULLT)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) +{ + char realfile[MAXPATHLEN]; + int userf = 0; + size_t i; + + for (i = 0; i < FACTTABSIZE; i++) { + if (facttab[i].enabled) + (facttab[i].display)(facttab[i].name, fp, fe); + } + if ((fe->flags & FE_MLSD) && + !(fe->flags & FE_ISCURDIR) && !ISDOTDIR(fe->display)) { + /* if MLSD and not "." entry, display as-is */ + userf = 0; + } else { + /* if MLST, or MLSD and "." entry, realpath(3) it */ + if (realpath(fe->display, realfile) != NULL) + userf = 1; + } + cprintf(fp, " %s\r\n", userf ? realfile : 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); +} + +static void +discover_path(char *last_path, const char *new_path) +{ + char tp[MAXPATHLEN + 1] = ""; + char tq[MAXPATHLEN + 1] = ""; + char *cp; + char *cq; + int sz1, sz2; + int nomorelink; + struct stat st1, st2; + + if (new_path[0] != '/') { + (void)strlcpy(tp, last_path, MAXPATHLEN); + (void)strlcat(tp, "/", MAXPATHLEN); + } + (void)strlcat(tp, new_path, MAXPATHLEN); + (void)strlcat(tp, "/", MAXPATHLEN); + + /* + * resolve symlinks. A symlink may introduce another symlink, so we + * loop trying to resolve symlinks until we don't find any of them. + */ + do { + /* Collapse any // into / */ + while ((cp = strstr(tp, "//")) != NULL) + (void)memmove(cp, cp + 1, strlen(cp) - 1 + 1); + + /* Collapse any /./ into / */ + while ((cp = strstr(tp, "/./")) != NULL) + (void)memmove(cp, cp + 2, strlen(cp) - 2 + 1); + + cp = tp; + nomorelink = 1; + + while ((cp = strstr(++cp, "/")) != NULL) { + sz1 = (unsigned long)cp - (unsigned long)tp; + if (sz1 > MAXPATHLEN) + goto bad; + *cp = 0; + sz2 = readlink(tp, tq, MAXPATHLEN); + *cp = '/'; + + /* If this is not a symlink, move to next / */ + if (sz2 <= 0) + continue; + + /* + * We found a symlink, so we will have to + * do one more pass to check there is no + * more symlink in the path + */ + nomorelink = 0; + + /* + * Null terminate the string and remove trailing / + */ + tq[sz2] = 0; + sz2 = strlen(tq); + if (tq[sz2 - 1] == '/') + tq[--sz2] = 0; + + /* + * Is this an absolute link or a relative link? + */ + if (tq[0] == '/') { + /* absolute link */ + if (strlen(cp) + sz2 > MAXPATHLEN) + goto bad; + memmove(tp + sz2, cp, strlen(cp) + 1); + memcpy(tp, tq, sz2); + } else { + /* relative link */ + for (cq = cp - 1; *cq != '/'; cq--); + if (strlen(tp) - + ((unsigned long)cq - (unsigned long)cp) + + 1 + sz2 > MAXPATHLEN) + goto bad; + (void)memmove(cq + 1 + sz2, + cp, strlen(cp) + 1); + (void)memcpy(cq + 1, tq, sz2); + } + + /* + * start over, looking for new symlinks + */ + break; + } + } while (nomorelink == 0); + + /* Collapse any /foo/../ into /foo/ */ + while ((cp = strstr(tp, "/../")) != NULL) { + /* ^/../foo/ becomes ^/foo/ */ + if (cp == tp) { + (void)memmove(cp, cp + 3, + strlen(cp) - 3 + 1); + } else { + for (cq = cp - 1; *cq != '/'; cq--); + (void)memmove(cq, cp + 3, + strlen(cp) - 3 + 1); + } + } + + /* strip strailing / */ + if (strlen(tp) != 1) + tp[strlen(tp) - 1] = '\0'; + + /* check that the path is correct */ + stat(tp, &st1); + stat(".", &st2); + if ((st1.st_dev != st2.st_dev) || (st1.st_ino != st2.st_ino)) + goto bad; + + (void)strlcpy(last_path, tp, MAXPATHLEN); + return; + +bad: + (void)strlcat(last_path, "/", MAXPATHLEN); + (void)strlcat(last_path, new_path, MAXPATHLEN); + return; +} + diff --git a/libexec/ftpd/conf.c b/libexec/ftpd/conf.c new file mode 100644 index 000000000..b5d7c0fb3 --- /dev/null +++ b/libexec/ftpd/conf.c @@ -0,0 +1,953 @@ +/* $NetBSD: conf.c,v 1.63 2011/08/14 11:46:28 christos Exp $ */ + +/*- + * Copyright (c) 1997-2009 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. + * + * 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 +#ifndef lint +__RCSID("$NetBSD: conf.c,v 1.63 2011/08/14 11:46:28 christos Exp $"); +#endif /* not lint */ + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef KERBEROS5 +#include +#endif + +#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 /* rw-r----- */ + +/* + * 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, ftpd_strdup(_NAME_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.mmapsize = 0; + curclass.readsize = 0; + curclass.writesize = 0; + curclass.sendbufsize = 0; + curclass.sendlowat = 0; + + CURCLASS_FLAGS_SET(checkportcmd); + CURCLASS_FLAGS_CLR(denyquick); + CURCLASS_FLAGS_CLR(hidesymlinks); + CURCLASS_FLAGS_SET(modify); + CURCLASS_FLAGS_SET(passive); + CURCLASS_FLAGS_CLR(private); + 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, errbuf[100]; + char *class, *word, *arg, *template; + const char *infile; + size_t line; + struct ftpconv *conv, *cnext; + + init_curclass(); + REASSIGN(curclass.classname, ftpd_strdup(findclass)); + /* set more guest defaults */ + if (strcasecmp(findclass, "guest") == 0) { + CURCLASS_FLAGS_CLR(modify); + curclass.umask = 0707; + } + + infile = conffilename(_NAME_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(Field) \ + do { \ + if (none || \ + (!EMPTYSTR(arg) && strcasecmp(arg, "off") == 0)) \ + CURCLASS_FLAGS_CLR(Field); \ + else \ + CURCLASS_FLAGS_SET(Field); \ + } while (0) + +#define CONF_STRING(Field) \ + do { \ + if (none || EMPTYSTR(arg)) \ + arg = NULL; \ + else \ + arg = ftpd_strdup(arg); \ + REASSIGN(curclass.Field, arg); \ + } while (0) + +#define CONF_LL(Field,Arg,Min,Max) \ + do { \ + if (none || EMPTYSTR(Arg)) \ + goto nextline; \ + llval = strsuftollx(#Field, Arg, Min, Max, \ + errbuf, sizeof(errbuf)); \ + if (errbuf[0]) { \ + syslog(LOG_WARNING, "%s line %d: %s", \ + infile, (int)line, errbuf); \ + goto nextline; \ + } \ + curclass.Field = llval; \ + } while(0) + + if (0) { + /* no-op */ + + } else if ((strcasecmp(word, "advertise") == 0) + || (strcasecmp(word, "advertize") == 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 = ftpd_strdup(arg); + if (none || EMPTYSTR(types) || + EMPTYSTR(disable) || EMPTYSTR(convcmd)) { + types = NULL; + disable = NULL; + convcmd = NULL; + } else { + types = ftpd_strdup(types); + disable = ftpd_strdup(disable); + convcmd = ftpd_strdup(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, "denyquick") == 0) { + CONF_FLAG(denyquick); + + } else if (strcasecmp(word, "display") == 0) { + CONF_STRING(display); + + } else if (strcasecmp(word, "hidesymlinks") == 0) { + CONF_FLAG(hidesymlinks); + + } else if (strcasecmp(word, "homedir") == 0) { + CONF_STRING(homedir); + + } else if (strcasecmp(word, "limit") == 0) { + curclass.limit = DEFAULT_LIMIT; + REASSIGN(curclass.limitfile, NULL); + CONF_LL(limit, arg, -1, LLTMAX); + REASSIGN(curclass.limitfile, + EMPTYSTR(p) ? NULL : ftpd_strdup(p)); + + } else if (strcasecmp(word, "maxfilesize") == 0) { + curclass.maxfilesize = DEFAULT_MAXFILESIZE; + CONF_LL(maxfilesize, arg, -1, LLTMAX); + + } else if (strcasecmp(word, "maxtimeout") == 0) { + curclass.maxtimeout = DEFAULT_MAXTIMEOUT; + CONF_LL(maxtimeout, arg, + MIN(30, curclass.timeout), LLTMAX); + + } else if (strcasecmp(word, "mmapsize") == 0) { + curclass.mmapsize = 0; + CONF_LL(mmapsize, arg, 0, SSIZE_MAX); + + } else if (strcasecmp(word, "readsize") == 0) { + curclass.readsize = 0; + CONF_LL(readsize, arg, 0, SSIZE_MAX); + + } else if (strcasecmp(word, "writesize") == 0) { + curclass.writesize = 0; + CONF_LL(writesize, arg, 0, SSIZE_MAX); + + } else if (strcasecmp(word, "recvbufsize") == 0) { + curclass.recvbufsize = 0; + CONF_LL(recvbufsize, arg, 0, INT_MAX); + + } else if (strcasecmp(word, "sendbufsize") == 0) { + curclass.sendbufsize = 0; + CONF_LL(sendbufsize, arg, 0, INT_MAX); + + } else if (strcasecmp(word, "sendlowat") == 0) { + curclass.sendlowat = 0; + CONF_LL(sendlowat, arg, 0, INT_MAX); + + } 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) { + long minport, maxport; + + curclass.portmin = 0; + curclass.portmax = 0; + if (none || EMPTYSTR(arg)) + continue; + if (EMPTYSTR(p)) { + syslog(LOG_WARNING, + "%s line %d: missing maxport argument", + infile, (int)line); + continue; + } + minport = strsuftollx("minport", arg, IPPORT_RESERVED, + IPPORT_ANONMAX, errbuf, sizeof(errbuf)); + if (errbuf[0]) { + syslog(LOG_WARNING, "%s line %d: %s", + infile, (int)line, errbuf); + continue; + } + maxport = strsuftollx("maxport", p, IPPORT_RESERVED, + IPPORT_ANONMAX, errbuf, sizeof(errbuf)); + if (errbuf[0]) { + syslog(LOG_WARNING, "%s line %d: %s", + infile, (int)line, errbuf); + continue; + } + if (minport >= maxport) { + syslog(LOG_WARNING, + "%s line %d: minport %ld >= maxport %ld", + infile, (int)line, minport, maxport); + continue; + } + curclass.portmin = (int)minport; + curclass.portmax = (int)maxport; + + } else if (strcasecmp(word, "private") == 0) { + CONF_FLAG(private); + + } else if (strcasecmp(word, "rateget") == 0) { + curclass.maxrateget = curclass.rateget = 0; + CONF_LL(rateget, arg, 0, LLTMAX); + curclass.maxrateget = curclass.rateget; + + } else if (strcasecmp(word, "rateput") == 0) { + curclass.maxrateput = curclass.rateput = 0; + CONF_LL(rateput, arg, 0, LLTMAX); + curclass.maxrateput = curclass.rateput; + + } else if (strcasecmp(word, "sanenames") == 0) { + CONF_FLAG(sanenames); + + } else if (strcasecmp(word, "timeout") == 0) { + curclass.timeout = DEFAULT_TIMEOUT; + CONF_LL(timeout, arg, 30, curclass.maxtimeout); + + } else if (strcasecmp(word, "template") == 0) { + if (none) + continue; + REASSIGN(template, EMPTYSTR(arg) ? NULL : ftpd_strdup(arg)); + + } else if (strcasecmp(word, "umask") == 0) { + unsigned long fumask; + + curclass.umask = DEFAULT_UMASK; + if (none || EMPTYSTR(arg)) + continue; + errno = 0; + endp = NULL; + fumask = strtoul(arg, &endp, 8); + if (errno || *arg == '\0' || *endp != '\0' || + fumask > 0777) { + syslog(LOG_WARNING, + "%s line %d: invalid umask %s", + infile, (int)line, arg); + continue; + } + curclass.umask = (mode_t)fumask; + + } 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; + } + nextline: + ; + } + 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 curwd[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(curwd, sizeof(curwd) - 1) == NULL) { + syslog(LOG_WARNING, "can't getcwd: %s", strerror(errno)); + return; + } + if (sl_find(slist, curwd) != NULL) + return; + + cp = ftpd_strdup(curwd); + 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; + + memset(&gl, 0, sizeof(gl)); + if (glob(curclass.notify, GLOB_BRACE|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; + char curwd[MAXPATHLEN]; + 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 : ""); + break; + + case 'C': + if (getcwd(curwd, sizeof(curwd)-1) + == NULL){ + syslog(LOG_WARNING, + "can't getcwd: %s", + strerror(errno)); + continue; + } + cprintf(stdout, "%s", curwd); + 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, LLF, + (LLT)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 : ""); + 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 || l1 >= sizeof(buf)) + 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... + */ +const char ** +do_conversion(const char *fname) +{ + struct ftpconv *cp; + struct stat st; + int o_errno; + char *base = NULL; + char *cmd, *p, *lp; + char **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 = ftpd_strdup(cp->command); + p = cmd; + while (p) { + NEXTWORD(p, lp); + if (strcmp(lp, "%s") == 0) + lp = base; + if (sl_add(sl, ftpd_strdup(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 (void *)(intptr_t)argv; + + cleanup_do_conv: + if (sl) + sl_free(sl, 1); + free(cmd); + errno = o_errno; + return(NULL); +} + +/* + * Count the number of current connections, reading from + * /var/run/ftpd.pids- + * 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; + size_t i, last, count; + ssize_t scount; + pid_t *pids, mypid; + struct stat sb; + struct flock fl; + + (void)strlcpy(fn, _PATH_CLASSPIDS, sizeof(fn)); + (void)strlcat(fn, curclass.classname, sizeof(fn)); + pids = NULL; + connections = 1; + fl.l_start = 0; + fl.l_len = 0; + fl.l_pid = 0; + fl.l_type = F_WRLCK; + fl.l_whence = SEEK_SET; + + if ((fd = open(fn, O_RDWR | O_CREAT, 0600)) == -1) + return; + if (fcntl(fd, F_SETLK, &fl) == -1) + goto cleanup_count; + if (fstat(fd, &sb) == -1) + goto cleanup_count; + if ((pids = malloc(sb.st_size + sizeof(pid_t))) == NULL) + goto cleanup_count; +/* XXX: implement a better read loop */ + scount = read(fd, pids, sb.st_size); + if (scount == -1 || scount != sb.st_size || scount < 0) + goto cleanup_count; + count = (size_t)scount / 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; +/* XXX: implement a better write loop */ + scount = write(fd, pids, count); + if (scount == -1 || (size_t)scount != count) + goto cleanup_count; + (void)ftruncate(fd, count); + + cleanup_count: + fl.l_type = F_UNLCK; + (void)fcntl(fd, F_SETLK, &fl); + close(fd); + REASSIGN(pids, NULL); +} diff --git a/libexec/ftpd/extern.h b/libexec/ftpd/extern.h new file mode 100644 index 000000000..e5266d719 --- /dev/null +++ b/libexec/ftpd/extern.h @@ -0,0 +1,380 @@ +/* $NetBSD: extern.h,v 1.62 2011/08/29 20:41:06 joerg 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. 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-2009 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. + * + * 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) +# define LLTMIN LONG_MIN +# define LLTMAX LONG_MAX +#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) +# define LLTMIN LLONG_MIN +# define LLTMAX LLONG_MAX +#endif + +#define FTP_BUFLEN 512 + +void abor(void); +void closedataconn(FILE *); +char *conffilename(const char *); +void count_users(void); +void cprintf(FILE *, const char *, ...) + __attribute__((__format__(__printf__, 2, 3))); +void cwd(const char *); +FILE *dataconn(const char *, off_t, const char *); +void delete(const char *); +int display_file(const char *, int); +const char **do_conversion(const char *); +__dead void dologout(int); +__dead void fatal(const char *); +void feat(void); +void format_path(char *, const char *); +int ftpd_pclose(FILE *); +FILE *ftpd_popen(const char *[], const char *, int); +int get_line(char *, int, FILE *); +void init_curclass(void); +void logxfer(const char *, off_t, const char *, const char *, + const struct timeval *, const char *); +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(const 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 *, ...) + __attribute__((__format__(__printf__, 2, 3))); +void retrieve(const 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); +void user(const char *); +char *ftpd_strdup(const char *); +void yyerror(const char *); + +#ifdef SUPPORT_UTMP +struct utmp; + +void ftpd_initwtmp(void); +void ftpd_logwtmp(const char *, const char *, const char *); +void ftpd_login(const struct utmp *); +int ftpd_logout(const char *); +#endif + +#ifdef SUPPORT_UTMPX +struct utmpx; +struct sockinet; + +void ftpd_initwtmpx(void); +void ftpd_logwtmpx(const char *, const char *, const char *, + struct sockinet *, int, int); +void ftpd_loginx(const struct utmpx *); +int ftpd_logoutx(const char *, int, int); +#endif + +#include + +#if defined(__NetBSD__) +# define HAVE_SETPROCTITLE 1 +# define HAVE_STRUCT_SOCKADDR_SA_LEN 1 +# define HAVE_SOCKADDR_SNPRINTF 1 +#endif + +struct sockinet { + union sockunion { + struct sockaddr_in su_sin; +#ifdef INET6 + struct sockaddr_in6 su_sin6; +#endif + } si_su; +#if !defined(HAVE_STRUCT_SOCKADDR_SA_LEN) + int si_len; +#endif +}; + +#if !defined(HAVE_STRUCT_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 { + const char *name; + short token; + short state; + short flags; /* 1 if command implemented, 2 if has options, + 4 if can occur OOB */ + const 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_denyquick = 1<<1, /* Check ftpusers(5) before PASS */ + FLAG_hidesymlinks = 1<<2, /* For symbolic links, list the file + or directory the link references + rather than the link itself */ + FLAG_modify = 1<<3, /* Allow CHMOD, DELE, MKD, RMD, RNFR, + UMASK */ + FLAG_passive = 1<<4, /* Allow PASV mode */ + FLAG_private = 1<<5, /* Don't publish class info in STAT */ + FLAG_sanenames = 1<<6, /* Restrict names of uploaded files */ + FLAG_upload = 1<<7, /* 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 */ + LLT 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 */ + LLT maxtimeout; /* Maximum permitted timeout */ + char *motd; /* MotD file to display after login */ + char *notify; /* Files to notify about upon chdir */ + LLT portmin; /* Minumum port for passive mode */ + LLT portmax; /* Maximum port for passive mode */ + LLT rateget; /* Get (RETR) transfer rate throttle */ + LLT rateput; /* Put (STOR) transfer rate throttle */ + LLT timeout; /* Default timeout */ + class_ft type; /* Class type */ + mode_t umask; /* Umask to use */ + LLT mmapsize; /* mmap window size */ + LLT readsize; /* data read size */ + LLT writesize; /* data write size */ + LLT recvbufsize; /* SO_RCVBUF size */ + LLT sendbufsize; /* SO_SNDBUF size */ + LLT sendlowat; /* SO_SNDLOWAT size */ +}; + +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 ftpd_debug; +GLOBAL char *emailaddr; +GLOBAL int form; +GLOBAL int gidcount; /* number of entries in gidlist[] */ +GLOBAL gid_t *gidlist; +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 defined(HAVE_SETPROCTITLE) +GLOBAL char proctitle[BUFSIZ]; /* initial part of title */ +#endif +GLOBAL struct passwd *pw; +GLOBAL int quietmessages; +GLOBAL char remotehost[MAXHOSTNAMELEN+1]; +GLOBAL char remoteloghost[2 * MAXHOSTNAMELEN+1]; +GLOBAL off_t restart_point; +GLOBAL char tmpline[FTP_BUFLEN]; +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" : \ + "" + +#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/libexec/ftpd/ftpcmd.c b/libexec/ftpd/ftpcmd.c new file mode 100644 index 000000000..582a8e55a --- /dev/null +++ b/libexec/ftpd/ftpcmd.c @@ -0,0 +1,2578 @@ +#ifndef lint +static const char yysccsid[] = "@(#)yaccpar 1.9 (Berkeley) 02/21/93"; +#endif + +#ifdef _LIBC +#include "namespace.h" +#endif +#include +#include + +#define YYBYACC 1 +#define YYMAJOR 1 +#define YYMINOR 9 + +#define YYEMPTY (-1) +#define yyclearin (yychar = YYEMPTY) +#define yyerrok (yyerrflag = 0) +#define YYRECOVERING() (yyerrflag != 0) + +#define YYPREFIX "yy" + +#define YYPURE 0 + +#line 69 "ftpcmd.y" +#include + +#ifndef lint +#if 0 +static char sccsid[] = "@(#)ftpcmd.y 8.3 (Berkeley) 4/6/94"; +#else +__RCSID("$NetBSD: ftpcmd.y,v 1.93 2011/09/16 16:13:17 plunky Exp $"); +#endif +#endif /* not lint */ + +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef KERBEROS5 +#include +#endif + +#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; + +extern int epsvall; +struct tab sitetab[]; + +static int check_write(const char *, int); +static void help(struct tab *, const char *); +static void port_check(const char *, int); + int yylex(void); + +#line 124 "ftpcmd.y" +#ifdef YYSTYPE +#undef YYSTYPE_IS_DECLARED +#define YYSTYPE_IS_DECLARED 1 +#endif +#ifndef YYSTYPE_IS_DECLARED +#define YYSTYPE_IS_DECLARED 1 +typedef union { + struct { + LLT ll; + int i; + } u; + char *s; + const char *cs; +} YYSTYPE; +#endif /* !YYSTYPE_IS_DECLARED */ +#line 94 "ftpcmd.c" + +/* compatibility with bison */ +#ifdef YYPARSE_PARAM +/* compatibility with FreeBSD */ +# ifdef YYPARSE_PARAM_TYPE +# define YYPARSE_DECL() yyparse(YYPARSE_PARAM_TYPE YYPARSE_PARAM) +# else +# define YYPARSE_DECL() yyparse(void *YYPARSE_PARAM) +# endif +#else +# define YYPARSE_DECL() yyparse(void) +#endif + +/* Parameters sent to lex. */ +#ifdef YYLEX_PARAM +# define YYLEX_DECL() yylex(void *YYLEX_PARAM) +# define YYLEX yylex(YYLEX_PARAM) +#else +# define YYLEX_DECL() yylex(void) +# define YYLEX yylex() +#endif + +/* Parameters sent to yyerror. */ +#define YYERROR_DECL() yyerror(const char *s) +#define YYERROR_CALL(msg) yyerror(msg) + +extern int YYPARSE_DECL(); + + +#define A 257 +#define B 258 +#define C 259 +#define E 260 +#define F 261 +#define I 262 +#define L 263 +#define N 264 +#define P 265 +#define R 266 +#define S 267 +#define T 268 +#define SP 269 +#define CRLF 270 +#define COMMA 271 +#define ALL 272 +#define USER 273 +#define PASS 274 +#define ACCT 275 +#define CWD 276 +#define CDUP 277 +#define SMNT 278 +#define QUIT 279 +#define REIN 280 +#define PORT 281 +#define PASV 282 +#define TYPE 283 +#define STRU 284 +#define MODE 285 +#define RETR 286 +#define STOR 287 +#define STOU 288 +#define APPE 289 +#define ALLO 290 +#define REST 291 +#define RNFR 292 +#define RNTO 293 +#define ABOR 294 +#define DELE 295 +#define RMD 296 +#define MKD 297 +#define PWD 298 +#define LIST 299 +#define NLST 300 +#define SITE 301 +#define SYST 302 +#define STAT 303 +#define HELP 304 +#define NOOP 305 +#define AUTH 306 +#define ADAT 307 +#define PROT 308 +#define PBSZ 309 +#define CCC 310 +#define MIC 311 +#define CONF 312 +#define ENC 313 +#define FEAT 314 +#define OPTS 315 +#define SIZE 316 +#define MDTM 317 +#define MLST 318 +#define MLSD 319 +#define LPRT 320 +#define LPSV 321 +#define EPRT 322 +#define EPSV 323 +#define MAIL 324 +#define MLFL 325 +#define MRCP 326 +#define MRSQ 327 +#define MSAM 328 +#define MSND 329 +#define MSOM 330 +#define CHMOD 331 +#define IDLE 332 +#define RATEGET 333 +#define RATEPUT 334 +#define UMASK 335 +#define LEXERR 336 +#define STRING 337 +#define NUMBER 338 +#define YYERRCODE 256 +static const short yylhs[] = { -1, + 0, 0, 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, + 17, 17, 12, 11, 11, 3, 18, 19, 20, 7, + 7, 7, 6, 6, 6, 6, 6, 6, 6, 6, + 4, 4, 4, 5, 5, 5, 10, 9, 2, 13, + 14, 15, 8, 1, +}; +static const short yylen[] = { 2, + 1, 1, 4, 4, 3, 5, 3, 2, 5, 5, + 5, 5, 3, 3, 5, 5, 3, 5, 5, 5, + 5, 4, 4, 4, 5, 9, 4, 3, 4, 4, + 4, 3, 3, 5, 3, 5, 4, 8, 6, 5, + 7, 5, 7, 5, 7, 5, 7, 2, 5, 2, + 2, 4, 2, 4, 4, 4, 4, 2, 4, 4, + 4, 2, 4, 5, 5, 5, 3, 5, 3, 2, + 5, 4, 1, 0, 1, 1, 11, 17, 41, 1, + 1, 1, 1, 3, 1, 3, 1, 1, 3, 2, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 0, +}; +static const short yydefred[] = { 0, + 0, 0, 0, 104, 104, 0, 104, 104, 104, 104, + 104, 104, 0, 0, 0, 104, 104, 0, 0, 104, + 0, 0, 0, 104, 104, 104, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 104, 104, 104, 104, 104, 104, 104, 104, 0, + 1, 2, 70, 0, 0, 0, 0, 8, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 48, + 50, 0, 0, 51, 53, 0, 0, 0, 0, 58, + 0, 0, 0, 62, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 73, 0, 75, 0, 0, 5, 7, + 0, 13, 0, 0, 0, 0, 98, 97, 0, 0, + 0, 0, 0, 0, 0, 28, 0, 0, 0, 32, + 0, 33, 0, 35, 0, 0, 104, 104, 104, 104, + 0, 0, 100, 0, 101, 0, 102, 0, 103, 0, + 0, 0, 0, 0, 0, 0, 0, 67, 0, 69, + 0, 14, 0, 0, 17, 3, 4, 0, 0, 0, + 0, 0, 87, 0, 0, 91, 93, 92, 0, 95, + 96, 94, 0, 0, 22, 23, 24, 0, 0, 72, + 27, 29, 30, 31, 0, 0, 0, 37, 0, 0, + 0, 0, 0, 0, 52, 54, 55, 56, 57, 59, + 60, 61, 63, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 6, 0, 9, 0, 0, 0, 76, + 90, 18, 19, 20, 21, 0, 25, 71, 34, 36, + 0, 99, 0, 0, 40, 0, 42, 0, 44, 0, + 46, 49, 64, 65, 66, 68, 0, 10, 11, 12, + 16, 15, 0, 82, 80, 81, 84, 86, 89, 0, + 39, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 41, 43, 45, 47, 0, 0, 0, 38, 0, 0, + 26, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 77, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 79, +}; +static const short yydgoto[] = { 50, + 56, 243, 231, 179, 183, 175, 267, 150, 118, 119, + 107, 105, 144, 146, 148, 51, 52, 170, 219, 220, +}; +static const short yysindex[] = { -179, + -260, -245, -239, 0, 0, -231, 0, 0, 0, 0, + 0, 0, -228, -225, -205, 0, 0, -203, -199, 0, + -195, -181, -177, 0, 0, 0, -173, -194, -171, -257, + -169, -124, -109, -108, -107, -106, -104, -103, -102, -101, + -99, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, -174, -166, -242, -98, 0, -96, -95, + -93, -92, -91, -90, -163, -163, -163, -89, -88, -163, + -163, -87, -163, -163, -163, -86, -233, -191, -272, 0, + 0, -84, -155, 0, 0, -151, -150, -149, -170, 0, + -150, -150, -150, 0, -148, -79, -78, -189, -187, -77, + -76, -74, -185, 0, -73, 0, -72, -163, 0, 0, + -145, 0, -217, -193, -236, -163, 0, 0, -71, -70, + -69, -142, -136, -67, -65, 0, -63, -62, -61, 0, + -163, 0, -163, 0, -183, -59, 0, 0, 0, 0, + -163, -58, 0, -57, 0, -56, 0, -55, 0, -54, + -53, -52, -51, -50, -163, -163, -163, 0, -163, 0, + -134, 0, -126, -269, 0, 0, 0, -49, -48, -46, + -47, -43, 0, -267, -45, 0, 0, 0, -42, 0, + 0, 0, -41, -40, 0, 0, 0, -119, -39, 0, + 0, 0, 0, 0, -38, -37, -110, 0, -100, -117, + -115, -113, -111, -36, 0, 0, 0, 0, 0, 0, + 0, 0, 0, -35, -34, -33, -31, -30, -28, -27, + -26, -25, -24, 0, -85, 0, -253, -253, -83, 0, + 0, 0, 0, 0, 0, -19, 0, 0, 0, 0, + -22, 0, -29, -82, 0, -80, 0, -75, 0, -100, + 0, 0, 0, 0, 0, 0, -68, 0, 0, 0, + 0, 0, -21, 0, 0, 0, 0, 0, 0, -20, + 0, -163, -18, -16, -12, -11, -10, -64, -60, -7, + 0, 0, 0, 0, -32, -6, -4, 0, -3, -23, + 0, -17, -2, 1, -15, -14, 2, 4, -13, -9, + 0, 5, -8, 6, -5, 8, -1, 10, 3, 11, + 7, 12, 13, 14, 15, 16, 17, 18, 19, 20, + 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, + 31, 0, +}; +static const short yyrindex[] = { 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 33, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 34, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 35, -169, 0, 37, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 38, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, +}; +static const short yygindex[] = { 0, + 9, 36, 42, 0, 0, 0, 32, 0, 0, -66, + 0, 0, 0, -44, 0, 0, 0, 0, 0, 0, +}; +#define YYTABLESIZE 369 +static const short yytable[] = { 120, + 121, 229, 222, 124, 125, 264, 127, 128, 129, 53, + 265, 83, 84, 57, 266, 59, 60, 61, 62, 63, + 64, 180, 181, 54, 68, 69, 108, 109, 72, 55, + 182, 135, 76, 77, 78, 131, 132, 82, 58, 171, + 65, 168, 172, 66, 173, 174, 151, 152, 153, 184, + 96, 97, 98, 99, 100, 101, 102, 103, 136, 137, + 138, 139, 140, 67, 195, 70, 196, 176, 223, 71, + 230, 177, 178, 73, 204, 80, 1, 133, 134, 157, + 158, 159, 160, 164, 165, 197, 198, 74, 214, 215, + 216, 75, 217, 2, 3, 79, 4, 5, 81, 6, + 85, 7, 8, 9, 10, 11, 12, 13, 14, 15, + 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, + 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, + 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, + 46, 47, 48, 49, 86, 200, 201, 202, 203, 236, + 237, 244, 245, 246, 247, 248, 249, 250, 251, 87, + 88, 89, 104, 90, 91, 92, 93, 149, 94, 95, + 106, 110, 111, 117, 112, 113, 114, 115, 116, 122, + 123, 142, 126, 130, 141, 143, 145, 147, 154, 155, + 156, 161, 169, 162, 163, 188, 166, 167, 185, 186, + 187, 189, 190, 218, 191, 280, 192, 193, 194, 199, + 221, 205, 206, 207, 208, 209, 210, 211, 212, 213, + 224, 227, 225, 226, 232, 228, 241, 233, 234, 235, + 238, 239, 240, 252, 253, 254, 255, 242, 256, 272, + 257, 258, 259, 260, 261, 262, 270, 271, 279, 278, + 0, 281, 263, 282, 230, 273, 274, 283, 284, 268, + 285, 275, 288, 0, 290, 291, 0, 292, 295, 277, + 269, 296, 299, 286, 300, 303, 305, 287, 307, 0, + 309, 311, 313, 0, 315, 276, 317, 0, 319, 0, + 321, 0, 323, 0, 325, 0, 327, 0, 329, 0, + 331, 104, 0, 74, 83, 289, 88, 78, 0, 0, + 0, 0, 0, 0, 293, 0, 0, 0, 0, 0, + 294, 0, 297, 298, 301, 0, 0, 0, 302, 304, + 0, 0, 306, 0, 0, 0, 308, 0, 0, 0, + 310, 0, 0, 0, 312, 0, 0, 0, 0, 0, + 314, 0, 316, 0, 318, 0, 320, 0, 322, 0, + 324, 0, 326, 0, 328, 0, 330, 0, 332, +}; +static const short yycheck[] = { 66, + 67, 269, 272, 70, 71, 259, 73, 74, 75, 270, + 264, 269, 270, 5, 268, 7, 8, 9, 10, 11, + 12, 258, 259, 269, 16, 17, 269, 270, 20, 269, + 267, 304, 24, 25, 26, 269, 270, 29, 270, 257, + 269, 108, 260, 269, 262, 263, 91, 92, 93, 116, + 42, 43, 44, 45, 46, 47, 48, 49, 331, 332, + 333, 334, 335, 269, 131, 269, 133, 261, 338, 269, + 338, 265, 266, 269, 141, 270, 256, 269, 270, 269, + 270, 269, 270, 269, 270, 269, 270, 269, 155, 156, + 157, 269, 159, 273, 274, 269, 276, 277, 270, 279, + 270, 281, 282, 283, 284, 285, 286, 287, 288, 289, + 290, 291, 292, 293, 294, 295, 296, 297, 298, 299, + 300, 301, 302, 303, 304, 305, 306, 307, 308, 309, + 310, 311, 312, 313, 314, 315, 316, 317, 318, 319, + 320, 321, 322, 323, 269, 137, 138, 139, 140, 269, + 270, 269, 270, 269, 270, 269, 270, 269, 270, 269, + 269, 269, 337, 270, 269, 269, 269, 338, 270, 269, + 337, 270, 269, 337, 270, 269, 269, 269, 269, 269, + 269, 337, 270, 270, 269, 337, 337, 337, 337, 269, + 269, 269, 338, 270, 269, 338, 270, 270, 270, 270, + 270, 338, 270, 338, 270, 272, 270, 270, 270, 269, + 337, 270, 270, 270, 270, 270, 270, 270, 270, 270, + 270, 269, 271, 270, 270, 269, 337, 270, 270, 270, + 270, 270, 270, 270, 270, 270, 270, 338, 270, 269, + 271, 270, 270, 270, 270, 270, 266, 270, 269, 271, + -1, 270, 338, 270, 338, 338, 337, 270, 270, 228, + 271, 337, 270, -1, 271, 270, -1, 271, 271, 338, + 229, 271, 271, 338, 271, 271, 271, 338, 271, -1, + 271, 271, 271, -1, 271, 250, 271, -1, 271, -1, + 271, -1, 271, -1, 271, -1, 271, -1, 271, -1, + 271, 269, -1, 270, 270, 338, 270, 270, -1, -1, + -1, -1, -1, -1, 338, -1, -1, -1, -1, -1, + 338, -1, 338, 338, 338, -1, -1, -1, 338, 338, + -1, -1, 338, -1, -1, -1, 338, -1, -1, -1, + 338, -1, -1, -1, 338, -1, -1, -1, -1, -1, + 338, -1, 338, -1, 338, -1, 338, -1, 338, -1, + 338, -1, 338, -1, 338, -1, 338, -1, 338, +}; +#define YYFINAL 50 +#ifndef YYDEBUG +#define YYDEBUG 0 +#endif +#define YYMAXTOKEN 338 +#if YYDEBUG +static const char *yyname[] = { + +"end-of-file",0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,"A","B","C","E","F","I","L","N", +"P","R","S","T","SP","CRLF","COMMA","ALL","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","STRING","NUMBER", +}; +static const char *yyrule[] = { +"$accept : cmd_sel", +"cmd_sel : cmd", +"cmd_sel : rcmd", +"cmd : USER SP username CRLF", +"cmd : PASS SP password CRLF", +"cmd : CWD check_login CRLF", +"cmd : CWD check_login SP pathname CRLF", +"cmd : CDUP check_login CRLF", +"cmd : QUIT CRLF", +"cmd : PORT check_login SP host_port CRLF", +"cmd : LPRT check_login SP host_long_port4 CRLF", +"cmd : LPRT check_login SP host_long_port6 CRLF", +"cmd : EPRT check_login SP STRING CRLF", +"cmd : PASV check_login CRLF", +"cmd : LPSV check_login CRLF", +"cmd : EPSV check_login SP NUMBER CRLF", +"cmd : EPSV check_login SP ALL CRLF", +"cmd : EPSV check_login CRLF", +"cmd : TYPE check_login SP type_code CRLF", +"cmd : STRU check_login SP struct_code CRLF", +"cmd : MODE check_login SP mode_code CRLF", +"cmd : RETR check_login SP pathname CRLF", +"cmd : STOR SP pathname CRLF", +"cmd : STOU SP pathname CRLF", +"cmd : APPE SP pathname CRLF", +"cmd : ALLO check_login SP NUMBER CRLF", +"cmd : ALLO check_login SP NUMBER SP R SP NUMBER CRLF", +"cmd : RNTO SP pathname CRLF", +"cmd : ABOR check_login CRLF", +"cmd : DELE SP pathname CRLF", +"cmd : RMD SP pathname CRLF", +"cmd : MKD SP pathname CRLF", +"cmd : PWD check_login CRLF", +"cmd : LIST check_login CRLF", +"cmd : LIST check_login SP pathname CRLF", +"cmd : NLST check_login CRLF", +"cmd : NLST check_login SP pathname CRLF", +"cmd : SITE SP HELP CRLF", +"cmd : SITE SP CHMOD SP octal_number SP pathname CRLF", +"cmd : SITE SP HELP SP STRING CRLF", +"cmd : SITE SP IDLE check_login CRLF", +"cmd : SITE SP IDLE check_login SP NUMBER CRLF", +"cmd : SITE SP RATEGET check_login CRLF", +"cmd : SITE SP RATEGET check_login SP STRING CRLF", +"cmd : SITE SP RATEPUT check_login CRLF", +"cmd : SITE SP RATEPUT check_login SP STRING CRLF", +"cmd : SITE SP UMASK check_login CRLF", +"cmd : SITE SP UMASK check_login SP octal_number CRLF", +"cmd : SYST CRLF", +"cmd : STAT check_login SP pathname CRLF", +"cmd : STAT CRLF", +"cmd : HELP CRLF", +"cmd : HELP SP STRING CRLF", +"cmd : NOOP CRLF", +"cmd : AUTH SP mechanism_name CRLF", +"cmd : ADAT SP base64data CRLF", +"cmd : PROT SP prot_code CRLF", +"cmd : PBSZ SP decimal_integer CRLF", +"cmd : CCC CRLF", +"cmd : MIC SP base64data CRLF", +"cmd : CONF SP base64data CRLF", +"cmd : ENC SP base64data CRLF", +"cmd : FEAT CRLF", +"cmd : OPTS SP STRING CRLF", +"cmd : SIZE check_login SP pathname CRLF", +"cmd : MDTM check_login SP pathname CRLF", +"cmd : MLST check_login SP pathname CRLF", +"cmd : MLST check_login CRLF", +"cmd : MLSD check_login SP pathname CRLF", +"cmd : MLSD check_login CRLF", +"cmd : error CRLF", +"rcmd : REST check_login SP NUMBER CRLF", +"rcmd : RNFR SP pathname CRLF", +"username : STRING", +"password :", +"password : STRING", +"byte_size : NUMBER", +"host_port : NUMBER COMMA NUMBER COMMA NUMBER COMMA NUMBER COMMA NUMBER COMMA NUMBER", +"host_long_port4 : NUMBER COMMA NUMBER COMMA NUMBER COMMA NUMBER COMMA NUMBER COMMA NUMBER COMMA NUMBER COMMA NUMBER COMMA NUMBER", +"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", +"form_code : N", +"form_code : T", +"form_code : C", +"type_code : A", +"type_code : A SP form_code", +"type_code : E", +"type_code : E SP form_code", +"type_code : I", +"type_code : L", +"type_code : L SP byte_size", +"type_code : L byte_size", +"struct_code : F", +"struct_code : R", +"struct_code : P", +"mode_code : S", +"mode_code : B", +"mode_code : C", +"pathname : pathstring", +"pathstring : STRING", +"octal_number : NUMBER", +"mechanism_name : STRING", +"base64data : STRING", +"prot_code : STRING", +"decimal_integer : NUMBER", +"check_login :", + +}; +#endif + +int yydebug; +int yynerrs; + +int yyerrflag; +int yychar; +YYSTYPE yyval; +YYSTYPE yylval; + +/* define the initial stack-sizes */ +#ifdef YYSTACKSIZE +#undef YYMAXDEPTH +#define YYMAXDEPTH YYSTACKSIZE +#else +#ifdef YYMAXDEPTH +#define YYSTACKSIZE YYMAXDEPTH +#else +#define YYSTACKSIZE 500 +#define YYMAXDEPTH 500 +#endif +#endif + +#define YYINITSTACKSIZE 500 + +typedef struct { + unsigned stacksize; + short *s_base; + short *s_mark; + short *s_last; + YYSTYPE *l_base; + YYSTYPE *l_mark; +} YYSTACKDATA; +/* variables for the parser stack */ +static YYSTACKDATA yystack; +#line 1208 "ftpcmd.y" + +#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, " username", 0, }, + { "PASS", PASS, ZSTR1, 1, " password", 0, }, + { "ACCT", ACCT, STR1, 0, "(specify account)", 0, }, + { "CWD", CWD, OSTR, 1, "[ directory-name ]", 0, }, + { "CDUP", CDUP, NOARGS, 1, "(change to parent directory)", 0, }, + { "SMNT", SMNT, ARGS, 0, "(structure mount)", 0, }, + { "QUIT", QUIT, NOARGS, 1, "(terminate service)", 0, }, + { "REIN", REIN, NOARGS, 0, "(reinitialize server state)", 0, }, + { "PORT", PORT, ARGS, 1, " b0, b1, b2, b3, b4, b5", 0, }, + { "LPRT", LPRT, ARGS, 1, " af, hal, h1, h2, h3,..., pal, p1, p2...", 0, }, + { "EPRT", EPRT, STR1, 1, " |af|addr|port|", 0, }, + { "PASV", PASV, NOARGS, 1, "(set server in passive mode)", 0, }, + { "LPSV", LPSV, ARGS, 1, "(set server in passive mode)", 0, }, + { "EPSV", EPSV, ARGS, 1, "[ af|ALL]", 0, }, + { "TYPE", TYPE, ARGS, 1, " [ A | E | I | L ]", 0, }, + { "STRU", STRU, ARGS, 1, "(specify file structure)", 0, }, + { "MODE", MODE, ARGS, 1, "(specify transfer mode)", 0, }, + { "RETR", RETR, STR1, 1, " file-name", 0, }, + { "STOR", STOR, STR1, 1, " file-name", 0, }, + { "STOU", STOU, STR1, 1, " file-name", 0, }, + { "APPE", APPE, STR1, 1, " file-name", 0, }, + { "ALLO", ALLO, ARGS, 1, "allocate storage (vacuously)", 0, }, + { "REST", REST, ARGS, 1, " offset (restart command)", 0, }, + { "RNFR", RNFR, STR1, 1, " file-name", 0, }, + { "RNTO", RNTO, STR1, 1, " file-name", 0, }, + { "ABOR", ABOR, NOARGS, 4, "(abort operation)", 0, }, + { "DELE", DELE, STR1, 1, " file-name", 0, }, + { "RMD", RMD, STR1, 1, " path-name", 0, }, + { "MKD", MKD, STR1, 1, " path-name", 0, }, + { "PWD", PWD, NOARGS, 1, "(return current directory)", 0, }, + { "LIST", LIST, OSTR, 1, "[ path-name ]", 0, }, + { "NLST", NLST, OSTR, 1, "[ path-name ]", 0, }, + { "SITE", SITE, SITECMD, 1, "site-cmd [ arguments ]", 0, }, + { "SYST", SYST, NOARGS, 1, "(get type of operating system)", 0, }, + { "STAT", STAT, OSTR, 4, "[ path-name ]", 0, }, + { "HELP", HELP, OSTR, 1, "[ ]", 0, }, + { "NOOP", NOOP, NOARGS, 2, "", 0, }, + + /* From RFC 2228, in order defined */ + { "AUTH", AUTH, STR1, 1, " mechanism-name", 0, }, + { "ADAT", ADAT, STR1, 1, " base-64-data", 0, }, + { "PROT", PROT, STR1, 1, " prot-code", 0, }, + { "PBSZ", PBSZ, ARGS, 1, " decimal-integer", 0, }, + { "CCC", CCC, NOARGS, 1, "(Disable data protection)", 0, }, + { "MIC", MIC, STR1, 4, " base64data", 0, }, + { "CONF", CONF, STR1, 4, " base64data", 0, }, + { "ENC", ENC, STR1, 4, " base64data", 0, }, + + /* From RFC 2389, in order defined */ + { "FEAT", FEAT, NOARGS, 1, "(display extended features)", 0, }, + { "OPTS", OPTS, STR1, 1, " command [ options ]", 0, }, + + /* From RFC 3659, in order defined */ + { "MDTM", MDTM, OSTR, 1, " path-name", 0, }, + { "SIZE", SIZE, OSTR, 1, " path-name", 0, }, + { "MLST", MLST, OSTR, 2, "[ path-name ]", 0, }, + { "MLSD", MLSD, OSTR, 1, "[ directory-name ]", 0, }, + + /* obsolete commands */ + { "MAIL", MAIL, OSTR, 0, "(mail to user)", 0, }, + { "MLFL", MLFL, OSTR, 0, "(mail file)", 0, }, + { "MRCP", MRCP, STR1, 0, "(mail recipient)", 0, }, + { "MRSQ", MRSQ, OSTR, 0, "(mail recipient scheme question)", 0, }, + { "MSAM", MSAM, OSTR, 0, "(mail send to terminal and mailbox)", 0, }, + { "MSND", MSND, OSTR, 0, "(mail send to terminal)", 0, }, + { "MSOM", MSOM, OSTR, 0, "(mail send to terminal or mailbox)", 0, }, + { "XCUP", CDUP, NOARGS, 1, "(change to parent directory)", 0, }, + { "XCWD", CWD, OSTR, 1, "[ directory-name ]", 0, }, + { "XMKD", MKD, STR1, 1, " path-name", 0, }, + { "XPWD", PWD, NOARGS, 1, "(return current directory)", 0, }, + { "XRMD", RMD, STR1, 1, " path-name", 0, }, + + { NULL, 0, 0, 0, 0, 0, } +}; + +struct tab sitetab[] = { + { "CHMOD", CHMOD, NSTR, 1, " mode file-name", 0, }, + { "HELP", HELP, OSTR, 1, "[ ]", 0, }, + { "IDLE", IDLE, ARGS, 1, "[ maximum-idle-time ]", 0, }, + { "RATEGET", RATEGET,OSTR, 1, "[ get-throttle-rate ]", 0, }, + { "RATEPUT", RATEPUT,OSTR, 1, "[ put-throttle-rate ]", 0, }, + { "UMASK", UMASK, ARGS, 1, "[ umask ]", 0, }, + { NULL, 0, 0, 0, 0, 0, } +}; + +/* + * Check if a filename is allowed to be modified (isupload == 0) or + * uploaded (isupload == 1), and if necessary, check the filename is `sane'. + * If the filename is NULL, fail. + * If the filename is "", don't do the sane name check. + */ +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 (file[0] != '\0' && CURCLASS_FLAGS_ISSET(sanenames)) { + const char *p; + + if (file[0] == '.') + goto insane_name; + for (p = file; *p; p++) { + if (isalnum((unsigned char)*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 + +/* + * get_line - a hacked up version of fgets to ignore TELNET escape codes. + * `s' is the buffer to read into. + * `n' is the 1 less than the size of the buffer, to allow trailing NUL + * `iop' is the FILE to read from. + * Returns 0 on success, -1 on EOF, -2 if the command was too long. + */ +int +get_line(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 (ftpd_debug) + syslog(LOG_DEBUG, "command: %s", s); + tmpline[0] = '\0'; + return(0); + } + 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) { + /* + * If command doesn't fit into buffer, discard the + * rest of the command and indicate truncation. + * This prevents the command to be split up into + * multiple commands. + */ + if (ftpd_debug) + syslog(LOG_DEBUG, + "command too long, last char: %d", c); + while (c != '\n' && (c = getc(iop)) != EOF) + continue; + return (-2); + } + if (c == '\n') + break; + } + if (c == EOF && cs == s) + return (-1); + *cs++ = '\0'; + if (ftpd_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 (0); +} + +void +ftp_handle_line(char *cp) +{ + + cmdp = cp; + yyparse(); +} + +void +ftp_loop(void) +{ + int ret; + + while (1) { + (void) alarm(curclass.timeout); + ret = get_line(cbuf, sizeof(cbuf)-1, stdin); + (void) alarm(0); + if (ret == -1) { + reply(221, "You could at least say goodbye."); + dologout(0); + } else if (ret == -2) { + reply(500, "Command too long."); + } else { + ftp_handle_line(cbuf); + } + } + /*NOTREACHED*/ +} + +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 defined(HAVE_SETPROCTITLE) + if (strncasecmp(cmdp, "PASS", 4) != 0 && + strncasecmp(cmdp, "ACCT", 4) != 0) + setproctitle("%s: %s", proctitle, cmdp); +#endif /* defined(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.cs = 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.cs = 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 = ftpd_strdup(cp); + cmdp[cpos] = '\n'; + state = ARGS; + return (STRING); + } + break; + + case NSTR: + if (cmdp[cpos] == ' ') { + cpos++; + return (SP); + } + if (isdigit((unsigned char)cmdp[cpos])) { + cp = &cmdp[cpos]; + while (isdigit((unsigned char)cmdp[++cpos])) + ; + c = cmdp[cpos]; + cmdp[cpos] = '\0'; + yylval.u.i = atoi(cp); + cmdp[cpos] = c; + state = STR1; + return (NUMBER); + } + state = STR1; + goto dostr1; + + case ARGS: + if (isdigit((unsigned char)cmdp[cpos])) { + cp = &cmdp[cpos]; + while (isdigit((unsigned char)cmdp[++cpos])) + ; + c = cmdp[cpos]; + cmdp[cpos] = '\0'; + yylval.u.i = atoi(cp); + yylval.u.ll = STRTOLL(cp, NULL, 10); + cmdp[cpos] = c; + return (NUMBER); + } + if (strncasecmp(&cmdp[cpos], "ALL", 3) == 0 + && !isalnum((unsigned char)cmdp[cpos + 3])) { + 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; + return (0); +} + +/* ARGSUSED */ +void +yyerror(const 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; + const char *htype; + + if (ctab == sitetab) + htype = "SITE "; + else + htype = ""; + 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.", htype); + 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", htype, c->name, c->help); + else + reply(504, "%s%-*s\t%s; not implemented.", htype, 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); +} +#line 1263 "ftpcmd.c" + +#if YYDEBUG +#include /* needed for printf */ +#endif + +#include /* needed for malloc, etc */ +#include /* needed for memset */ + +/* allocate initial stack or double stack size, up to YYMAXDEPTH */ +static int yygrowstack(YYSTACKDATA *data) +{ + int i; + unsigned newsize; + short *newss; + YYSTYPE *newvs; + + if ((newsize = data->stacksize) == 0) + newsize = YYINITSTACKSIZE; + else if (newsize >= YYMAXDEPTH) + return -1; + else if ((newsize *= 2) > YYMAXDEPTH) + newsize = YYMAXDEPTH; + + i = data->s_mark - data->s_base; + newss = (short *)realloc(data->s_base, newsize * sizeof(*newss)); + if (newss == 0) + return -1; + + data->s_base = newss; + data->s_mark = newss + i; + + newvs = (YYSTYPE *)realloc(data->l_base, newsize * sizeof(*newvs)); + if (newvs == 0) + return -1; + + data->l_base = newvs; + data->l_mark = newvs + i; + + data->stacksize = newsize; + data->s_last = data->s_base + newsize - 1; + return 0; +} + +#if YYPURE || defined(YY_NO_LEAKS) +static void yyfreestack(YYSTACKDATA *data) +{ + free(data->s_base); + free(data->l_base); + memset(data, 0, sizeof(*data)); +} +#else +#define yyfreestack(data) /* nothing */ +#endif + +#define YYABORT goto yyabort +#define YYREJECT goto yyabort +#define YYACCEPT goto yyaccept +#define YYERROR goto yyerrlab + +int +YYPARSE_DECL() +{ + int yym, yyn, yystate; +#if YYDEBUG + const char *yys; + + if ((yys = getenv("YYDEBUG")) != 0) + { + yyn = *yys; + if (yyn >= '0' && yyn <= '9') + yydebug = yyn - '0'; + } +#endif + + yynerrs = 0; + yyerrflag = 0; + yychar = YYEMPTY; + yystate = 0; + +#if YYPURE + memset(&yystack, 0, sizeof(yystack)); +#endif + + if (yystack.s_base == NULL && yygrowstack(&yystack)) goto yyoverflow; + yystack.s_mark = yystack.s_base; + yystack.l_mark = yystack.l_base; + yystate = 0; + *yystack.s_mark = 0; + +yyloop: + if ((yyn = yydefred[yystate]) != 0) goto yyreduce; + if (yychar < 0) + { + if ((yychar = YYLEX) < 0) yychar = 0; +#if YYDEBUG + if (yydebug) + { + yys = 0; + if (yychar <= YYMAXTOKEN) yys = yyname[yychar]; + if (!yys) yys = "illegal-symbol"; + printf("%sdebug: state %d, reading %d (%s)\n", + YYPREFIX, yystate, yychar, yys); + } +#endif + } + if ((yyn = yysindex[yystate]) && (yyn += yychar) >= 0 && + yyn <= YYTABLESIZE && yycheck[yyn] == yychar) + { +#if YYDEBUG + if (yydebug) + printf("%sdebug: state %d, shifting to state %d\n", + YYPREFIX, yystate, yytable[yyn]); +#endif + if (yystack.s_mark >= yystack.s_last && yygrowstack(&yystack)) + { + goto yyoverflow; + } + yystate = yytable[yyn]; + *++yystack.s_mark = yytable[yyn]; + *++yystack.l_mark = yylval; + yychar = YYEMPTY; + if (yyerrflag > 0) --yyerrflag; + goto yyloop; + } + if ((yyn = yyrindex[yystate]) && (yyn += yychar) >= 0 && + yyn <= YYTABLESIZE && yycheck[yyn] == yychar) + { + yyn = yytable[yyn]; + goto yyreduce; + } + if (yyerrflag) goto yyinrecovery; + + yyerror("syntax error"); + + goto yyerrlab; + +yyerrlab: + ++yynerrs; + +yyinrecovery: + if (yyerrflag < 3) + { + yyerrflag = 3; + for (;;) + { + if ((yyn = yysindex[*yystack.s_mark]) && (yyn += YYERRCODE) >= 0 && + yyn <= YYTABLESIZE && yycheck[yyn] == YYERRCODE) + { +#if YYDEBUG + if (yydebug) + printf("%sdebug: state %d, error recovery shifting\ + to state %d\n", YYPREFIX, *yystack.s_mark, yytable[yyn]); +#endif + if (yystack.s_mark >= yystack.s_last && yygrowstack(&yystack)) + { + goto yyoverflow; + } + yystate = yytable[yyn]; + *++yystack.s_mark = yytable[yyn]; + *++yystack.l_mark = yylval; + goto yyloop; + } + else + { +#if YYDEBUG + if (yydebug) + printf("%sdebug: error recovery discarding state %d\n", + YYPREFIX, *yystack.s_mark); +#endif + if (yystack.s_mark <= yystack.s_base) goto yyabort; + --yystack.s_mark; + --yystack.l_mark; + } + } + } + else + { + if (yychar == 0) goto yyabort; +#if YYDEBUG + if (yydebug) + { + yys = 0; + if (yychar <= YYMAXTOKEN) yys = yyname[yychar]; + if (!yys) yys = "illegal-symbol"; + printf("%sdebug: state %d, error recovery discards token %d (%s)\n", + YYPREFIX, yystate, yychar, yys); + } +#endif + yychar = YYEMPTY; + goto yyloop; + } + +yyreduce: +#if YYDEBUG + if (yydebug) + printf("%sdebug: state %d, reducing by rule %d (%s)\n", + YYPREFIX, yystate, yyn, yyrule[yyn]); +#endif + yym = yylen[yyn]; + if (yym) + yyval = yystack.l_mark[1-yym]; + else + memset(&yyval, 0, sizeof yyval); + switch (yyn) + { +case 1: +#line 176 "ftpcmd.y" + { + REASSIGN(fromname, NULL); + restart_point = (off_t) 0; + } +break; +case 3: +#line 188 "ftpcmd.y" + { + user(yystack.l_mark[-1].s); + free(yystack.l_mark[-1].s); + } +break; +case 4: +#line 194 "ftpcmd.y" + { + pass(yystack.l_mark[-1].s); + memset(yystack.l_mark[-1].s, 0, strlen(yystack.l_mark[-1].s)); + free(yystack.l_mark[-1].s); + } +break; +case 5: +#line 201 "ftpcmd.y" + { + if (yystack.l_mark[-1].u.i) + cwd(homedir); + } +break; +case 6: +#line 207 "ftpcmd.y" + { + if (yystack.l_mark[-3].u.i && yystack.l_mark[-1].s != NULL) + cwd(yystack.l_mark[-1].s); + if (yystack.l_mark[-1].s != NULL) + free(yystack.l_mark[-1].s); + } +break; +case 7: +#line 215 "ftpcmd.y" + { + if (yystack.l_mark[-1].u.i) + cwd(".."); + } +break; +case 8: +#line 221 "ftpcmd.y" + { + 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); + } +break; +case 9: +#line 251 "ftpcmd.y" + { + if (yystack.l_mark[-3].u.i) + port_check("PORT", AF_INET); + } +break; +case 10: +#line 257 "ftpcmd.y" + { + if (yystack.l_mark[-3].u.i) + port_check("LPRT", AF_INET); + } +break; +case 11: +#line 263 "ftpcmd.y" + { +#ifdef INET6 + if (yystack.l_mark[-3].u.i) + port_check("LPRT", AF_INET6); +#else + reply(500, "IPv6 support not available."); +#endif + } +break; +case 12: +#line 273 "ftpcmd.y" + { + if (yystack.l_mark[-3].u.i) { + if (extended_port(yystack.l_mark[-1].s) == 0) + port_check("EPRT", -1); + } + free(yystack.l_mark[-1].s); + } +break; +case 13: +#line 282 "ftpcmd.y" + { + if (yystack.l_mark[-1].u.i) { + if (CURCLASS_FLAGS_ISSET(passive)) + passive(); + else + reply(500, "PASV mode not available."); + } + } +break; +case 14: +#line 292 "ftpcmd.y" + { + if (yystack.l_mark[-1].u.i) { + if (CURCLASS_FLAGS_ISSET(passive)) { + if (epsvall) + reply(501, + "LPSV disallowed after EPSV ALL"); + else + long_passive("LPSV", PF_UNSPEC); + } else + reply(500, "LPSV mode not available."); + } + } +break; +case 15: +#line 306 "ftpcmd.y" + { + if (yystack.l_mark[-3].u.i) { + if (CURCLASS_FLAGS_ISSET(passive)) + long_passive("EPSV", + epsvproto2af(yystack.l_mark[-1].u.i)); + else + reply(500, "EPSV mode not available."); + } + } +break; +case 16: +#line 317 "ftpcmd.y" + { + if (yystack.l_mark[-3].u.i) { + if (CURCLASS_FLAGS_ISSET(passive)) { + reply(200, + "EPSV ALL command successful."); + epsvall++; + } else + reply(500, "EPSV mode not available."); + } + } +break; +case 17: +#line 329 "ftpcmd.y" + { + if (yystack.l_mark[-1].u.i) { + if (CURCLASS_FLAGS_ISSET(passive)) + long_passive("EPSV", PF_UNSPEC); + else + reply(500, "EPSV mode not available."); + } + } +break; +case 18: +#line 339 "ftpcmd.y" + { + if (yystack.l_mark[-3].u.i) { + + 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 */ + } + + } + } +break; +case 19: +#line 379 "ftpcmd.y" + { + if (yystack.l_mark[-3].u.i) { + switch (yystack.l_mark[-1].u.i) { + + case STRU_F: + reply(200, "STRU F ok."); + break; + + default: + reply(504, "Unimplemented STRU type."); + } + } + } +break; +case 20: +#line 394 "ftpcmd.y" + { + if (yystack.l_mark[-3].u.i) { + switch (yystack.l_mark[-1].u.i) { + + case MODE_S: + reply(200, "MODE S ok."); + break; + + default: + reply(502, "Unimplemented MODE type."); + } + } + } +break; +case 21: +#line 409 "ftpcmd.y" + { + if (yystack.l_mark[-3].u.i && yystack.l_mark[-1].s != NULL) + retrieve(NULL, yystack.l_mark[-1].s); + if (yystack.l_mark[-1].s != NULL) + free(yystack.l_mark[-1].s); + } +break; +case 22: +#line 417 "ftpcmd.y" + { + if (check_write(yystack.l_mark[-1].s, 1)) + store(yystack.l_mark[-1].s, "w", 0); + if (yystack.l_mark[-1].s != NULL) + free(yystack.l_mark[-1].s); + } +break; +case 23: +#line 425 "ftpcmd.y" + { + if (check_write(yystack.l_mark[-1].s, 1)) + store(yystack.l_mark[-1].s, "w", 1); + if (yystack.l_mark[-1].s != NULL) + free(yystack.l_mark[-1].s); + } +break; +case 24: +#line 433 "ftpcmd.y" + { + if (check_write(yystack.l_mark[-1].s, 1)) + store(yystack.l_mark[-1].s, "a", 0); + if (yystack.l_mark[-1].s != NULL) + free(yystack.l_mark[-1].s); + } +break; +case 25: +#line 441 "ftpcmd.y" + { + if (yystack.l_mark[-3].u.i) + reply(202, "ALLO command ignored."); + } +break; +case 26: +#line 447 "ftpcmd.y" + { + if (yystack.l_mark[-7].u.i) + reply(202, "ALLO command ignored."); + } +break; +case 27: +#line 453 "ftpcmd.y" + { + if (check_write(yystack.l_mark[-1].s, 0)) { + if (fromname) { + renamecmd(fromname, yystack.l_mark[-1].s); + REASSIGN(fromname, NULL); + } else { + reply(503, "Bad sequence of commands."); + } + } + if (yystack.l_mark[-1].s != NULL) + free(yystack.l_mark[-1].s); + } +break; +case 28: +#line 467 "ftpcmd.y" + { + if (is_oob) + abor(); + else if (yystack.l_mark[-1].u.i) + reply(225, "ABOR command successful."); + } +break; +case 29: +#line 475 "ftpcmd.y" + { + if (check_write(yystack.l_mark[-1].s, 0)) + delete(yystack.l_mark[-1].s); + if (yystack.l_mark[-1].s != NULL) + free(yystack.l_mark[-1].s); + } +break; +case 30: +#line 483 "ftpcmd.y" + { + if (check_write(yystack.l_mark[-1].s, 0)) + removedir(yystack.l_mark[-1].s); + if (yystack.l_mark[-1].s != NULL) + free(yystack.l_mark[-1].s); + } +break; +case 31: +#line 491 "ftpcmd.y" + { + if (check_write(yystack.l_mark[-1].s, 0)) + makedir(yystack.l_mark[-1].s); + if (yystack.l_mark[-1].s != NULL) + free(yystack.l_mark[-1].s); + } +break; +case 32: +#line 499 "ftpcmd.y" + { + if (yystack.l_mark[-1].u.i) + pwd(); + } +break; +case 33: +#line 505 "ftpcmd.y" + { + const char *argv[] = { INTERNAL_LS, "-lgA", NULL }; + + if (CURCLASS_FLAGS_ISSET(hidesymlinks)) + argv[1] = "-LlgA"; + if (yystack.l_mark[-1].u.i) + retrieve(argv, ""); + } +break; +case 34: +#line 515 "ftpcmd.y" + { + const char *argv[] = { INTERNAL_LS, "-lgA", NULL, NULL }; + + if (CURCLASS_FLAGS_ISSET(hidesymlinks)) + argv[1] = "-LlgA"; + if (yystack.l_mark[-3].u.i && yystack.l_mark[-1].s != NULL) { + argv[2] = yystack.l_mark[-1].s; + retrieve(argv, yystack.l_mark[-1].s); + } + if (yystack.l_mark[-1].s != NULL) + free(yystack.l_mark[-1].s); + } +break; +case 35: +#line 529 "ftpcmd.y" + { + if (yystack.l_mark[-1].u.i) + send_file_list("."); + } +break; +case 36: +#line 535 "ftpcmd.y" + { + if (yystack.l_mark[-3].u.i) + send_file_list(yystack.l_mark[-1].s); + free(yystack.l_mark[-1].s); + } +break; +case 37: +#line 542 "ftpcmd.y" + { + help(sitetab, NULL); + } +break; +case 38: +#line 547 "ftpcmd.y" + { + if (check_write(yystack.l_mark[-1].s, 0)) { + if ((yystack.l_mark[-3].u.i == -1) || (yystack.l_mark[-3].u.i > 0777)) + reply(501, + "CHMOD: Mode value must be between 0 and 0777"); + else if (chmod(yystack.l_mark[-1].s, yystack.l_mark[-3].u.i) < 0) + perror_reply(550, yystack.l_mark[-1].s); + else + reply(200, "CHMOD command successful."); + } + if (yystack.l_mark[-1].s != NULL) + free(yystack.l_mark[-1].s); + } +break; +case 39: +#line 562 "ftpcmd.y" + { + help(sitetab, yystack.l_mark[-1].s); + free(yystack.l_mark[-1].s); + } +break; +case 40: +#line 568 "ftpcmd.y" + { + if (yystack.l_mark[-1].u.i) { + reply(200, + "Current IDLE time limit is " LLF + " seconds; max " LLF, + (LLT)curclass.timeout, + (LLT)curclass.maxtimeout); + } + } +break; +case 41: +#line 579 "ftpcmd.y" + { + if (yystack.l_mark[-3].u.i) { + if (yystack.l_mark[-1].u.i < 30 || yystack.l_mark[-1].u.i > curclass.maxtimeout) { + reply(501, + "IDLE time limit must be between 30 and " + LLF " seconds", + (LLT)curclass.maxtimeout); + } else { + curclass.timeout = yystack.l_mark[-1].u.i; + (void) alarm(curclass.timeout); + reply(200, + "IDLE time limit set to " + LLF " seconds", + (LLT)curclass.timeout); + } + } + } +break; +case 42: +#line 598 "ftpcmd.y" + { + if (yystack.l_mark[-1].u.i) { + reply(200, + "Current RATEGET is " LLF " bytes/sec", + (LLT)curclass.rateget); + } + } +break; +case 43: +#line 607 "ftpcmd.y" + { + char errbuf[100]; + char *p = yystack.l_mark[-1].s; + LLT rate; + + if (yystack.l_mark[-3].u.i) { + rate = strsuftollx("RATEGET", p, 0, + curclass.maxrateget + ? curclass.maxrateget + : LLTMAX, errbuf, sizeof(errbuf)); + if (errbuf[0]) + reply(501, "%s", errbuf); + else { + curclass.rateget = rate; + reply(200, + "RATEGET set to " LLF " bytes/sec", + (LLT)curclass.rateget); + } + } + free(yystack.l_mark[-1].s); + } +break; +case 44: +#line 630 "ftpcmd.y" + { + if (yystack.l_mark[-1].u.i) { + reply(200, + "Current RATEPUT is " LLF " bytes/sec", + (LLT)curclass.rateput); + } + } +break; +case 45: +#line 639 "ftpcmd.y" + { + char errbuf[100]; + char *p = yystack.l_mark[-1].s; + LLT rate; + + if (yystack.l_mark[-3].u.i) { + rate = strsuftollx("RATEPUT", p, 0, + curclass.maxrateput + ? curclass.maxrateput + : LLTMAX, errbuf, sizeof(errbuf)); + if (errbuf[0]) + reply(501, "%s", errbuf); + else { + curclass.rateput = rate; + reply(200, + "RATEPUT set to " LLF " bytes/sec", + (LLT)curclass.rateput); + } + } + free(yystack.l_mark[-1].s); + } +break; +case 46: +#line 662 "ftpcmd.y" + { + int oldmask; + + if (yystack.l_mark[-1].u.i) { + oldmask = umask(0); + (void) umask(oldmask); + reply(200, "Current UMASK is %03o", oldmask); + } + } +break; +case 47: +#line 673 "ftpcmd.y" + { + int oldmask; + + if (yystack.l_mark[-3].u.i && check_write("", 0)) { + if ((yystack.l_mark[-1].u.i == -1) || (yystack.l_mark[-1].u.i > 0777)) { + reply(501, "Bad UMASK value"); + } else { + oldmask = umask(yystack.l_mark[-1].u.i); + reply(200, + "UMASK set to %03o (was %03o)", + yystack.l_mark[-1].u.i, oldmask); + } + } + } +break; +case 48: +#line 689 "ftpcmd.y" + { + if (EMPTYSTR(version)) + reply(215, "UNIX Type: L%d", NBBY); + else + reply(215, "UNIX Type: L%d Version: %s", NBBY, + version); + } +break; +case 49: +#line 698 "ftpcmd.y" + { + if (yystack.l_mark[-3].u.i && yystack.l_mark[-1].s != NULL) + statfilecmd(yystack.l_mark[-1].s); + if (yystack.l_mark[-1].s != NULL) + free(yystack.l_mark[-1].s); + } +break; +case 50: +#line 706 "ftpcmd.y" + { + if (is_oob) + statxfer(); + else + statcmd(); + } +break; +case 51: +#line 714 "ftpcmd.y" + { + help(cmdtab, NULL); + } +break; +case 52: +#line 719 "ftpcmd.y" + { + char *cp = yystack.l_mark[-1].s; + + if (strncasecmp(cp, "SITE", 4) == 0) { + cp = yystack.l_mark[-1].s + 4; + if (*cp == ' ') + cp++; + if (*cp) + help(sitetab, cp); + else + help(sitetab, NULL); + } else + help(cmdtab, yystack.l_mark[-1].s); + free(yystack.l_mark[-1].s); + } +break; +case 53: +#line 736 "ftpcmd.y" + { + reply(200, "NOOP command successful."); + } +break; +case 54: +#line 742 "ftpcmd.y" + { + reply(502, "RFC 2228 authentication not implemented."); + free(yystack.l_mark[-1].s); + } +break; +case 55: +#line 748 "ftpcmd.y" + { + reply(503, + "Please set authentication state with AUTH."); + free(yystack.l_mark[-1].s); + } +break; +case 56: +#line 755 "ftpcmd.y" + { + reply(503, + "Please set protection buffer size with PBSZ."); + free(yystack.l_mark[-1].s); + } +break; +case 57: +#line 762 "ftpcmd.y" + { + reply(503, + "Please set authentication state with AUTH."); + } +break; +case 58: +#line 768 "ftpcmd.y" + { + reply(533, "No protection enabled."); + } +break; +case 59: +#line 773 "ftpcmd.y" + { + reply(502, "RFC 2228 authentication not implemented."); + free(yystack.l_mark[-1].s); + } +break; +case 60: +#line 779 "ftpcmd.y" + { + reply(502, "RFC 2228 authentication not implemented."); + free(yystack.l_mark[-1].s); + } +break; +case 61: +#line 785 "ftpcmd.y" + { + reply(502, "RFC 2228 authentication not implemented."); + free(yystack.l_mark[-1].s); + } +break; +case 62: +#line 792 "ftpcmd.y" + { + + feat(); + } +break; +case 63: +#line 798 "ftpcmd.y" + { + + opts(yystack.l_mark[-1].s); + free(yystack.l_mark[-1].s); + } +break; +case 64: +#line 812 "ftpcmd.y" + { + if (yystack.l_mark[-3].u.i && yystack.l_mark[-1].s != NULL) + sizecmd(yystack.l_mark[-1].s); + if (yystack.l_mark[-1].s != NULL) + free(yystack.l_mark[-1].s); + } +break; +case 65: +#line 826 "ftpcmd.y" + { + if (yystack.l_mark[-3].u.i && yystack.l_mark[-1].s != NULL) { + struct stat stbuf; + if (stat(yystack.l_mark[-1].s, &stbuf) < 0) + perror_reply(550, yystack.l_mark[-1].s); + else if (!S_ISREG(stbuf.st_mode)) { + reply(550, "%s: not a plain file.", yystack.l_mark[-1].s); + } 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 (yystack.l_mark[-1].s != NULL) + free(yystack.l_mark[-1].s); + } +break; +case 66: +#line 849 "ftpcmd.y" + { + if (yystack.l_mark[-3].u.i && yystack.l_mark[-1].s != NULL) + mlst(yystack.l_mark[-1].s); + if (yystack.l_mark[-1].s != NULL) + free(yystack.l_mark[-1].s); + } +break; +case 67: +#line 857 "ftpcmd.y" + { + mlst(NULL); + } +break; +case 68: +#line 862 "ftpcmd.y" + { + if (yystack.l_mark[-3].u.i && yystack.l_mark[-1].s != NULL) + mlsd(yystack.l_mark[-1].s); + if (yystack.l_mark[-1].s != NULL) + free(yystack.l_mark[-1].s); + } +break; +case 69: +#line 870 "ftpcmd.y" + { + mlsd(NULL); + } +break; +case 70: +#line 875 "ftpcmd.y" + { + yyerrok; + } +break; +case 71: +#line 882 "ftpcmd.y" + { + if (yystack.l_mark[-3].u.i) { + REASSIGN(fromname, NULL); + restart_point = (off_t)yystack.l_mark[-1].u.ll; + reply(350, + "Restarting at " LLF ". Send STORE or RETRIEVE to initiate transfer.", + (LLT)restart_point); + } + } +break; +case 72: +#line 893 "ftpcmd.y" + { + restart_point = (off_t) 0; + if (check_write(yystack.l_mark[-1].s, 0)) { + REASSIGN(fromname, NULL); + fromname = renamefrom(yystack.l_mark[-1].s); + } + if (yystack.l_mark[-1].s != NULL) + free(yystack.l_mark[-1].s); + } +break; +case 74: +#line 910 "ftpcmd.y" + { + yyval.s = (char *)calloc(1, sizeof(char)); + } +break; +case 76: +#line 919 "ftpcmd.y" + { + yyval.u.i = yystack.l_mark[0].u.i; + } +break; +case 77: +#line 927 "ftpcmd.y" + { + 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] = yystack.l_mark[-2].u.i; p[1] = yystack.l_mark[0].u.i; + a = (char *)&data_dest.su_addr; + a[0] = yystack.l_mark[-10].u.i; a[1] = yystack.l_mark[-8].u.i; a[2] = yystack.l_mark[-6].u.i; a[3] = yystack.l_mark[-4].u.i; + } +break; +case 78: +#line 944 "ftpcmd.y" + { + 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] = yystack.l_mark[-2].u.i; p[1] = yystack.l_mark[0].u.i; + a = (char *)&data_dest.su_addr; + a[0] = yystack.l_mark[-12].u.i; a[1] = yystack.l_mark[-10].u.i; a[2] = yystack.l_mark[-8].u.i; a[3] = yystack.l_mark[-6].u.i; + + /* reject invalid LPRT command */ + if (yystack.l_mark[-16].u.i != 4 || yystack.l_mark[-14].u.i != 4 || yystack.l_mark[-4].u.i != 2) + memset(&data_dest, 0, sizeof(data_dest)); + } +break; +case 79: +#line 968 "ftpcmd.y" + { +#ifdef INET6 + unsigned char buf[16]; + + (void)memset(&data_dest, 0, sizeof(data_dest)); + data_dest.su_len = sizeof(struct sockaddr_in6); + data_dest.su_family = AF_INET6; + buf[0] = yystack.l_mark[-2].u.i; buf[1] = yystack.l_mark[0].u.i; + (void)memcpy(&data_dest.su_port, buf, + sizeof(data_dest.su_port)); + buf[0] = yystack.l_mark[-36].u.i; buf[1] = yystack.l_mark[-34].u.i; + buf[2] = yystack.l_mark[-32].u.i; buf[3] = yystack.l_mark[-30].u.i; + buf[4] = yystack.l_mark[-28].u.i; buf[5] = yystack.l_mark[-26].u.i; + buf[6] = yystack.l_mark[-24].u.i; buf[7] = yystack.l_mark[-22].u.i; + buf[8] = yystack.l_mark[-20].u.i; buf[9] = yystack.l_mark[-18].u.i; + buf[10] = yystack.l_mark[-16].u.i; buf[11] = yystack.l_mark[-14].u.i; + buf[12] = yystack.l_mark[-12].u.i; buf[13] = yystack.l_mark[-10].u.i; + buf[14] = yystack.l_mark[-8].u.i; buf[15] = yystack.l_mark[-6].u.i; + (void)memcpy(&data_dest.si_su.su_sin6.sin6_addr, + buf, sizeof(data_dest.si_su.su_sin6.sin6_addr)); + 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 (yystack.l_mark[-40].u.i != 6 || yystack.l_mark[-38].u.i != 16 || yystack.l_mark[-4].u.i != 2) + memset(&data_dest, 0, sizeof(data_dest)); + } +break; +case 80: +#line 1003 "ftpcmd.y" + { + yyval.u.i = FORM_N; + } +break; +case 81: +#line 1008 "ftpcmd.y" + { + yyval.u.i = FORM_T; + } +break; +case 82: +#line 1013 "ftpcmd.y" + { + yyval.u.i = FORM_C; + } +break; +case 83: +#line 1020 "ftpcmd.y" + { + cmd_type = TYPE_A; + cmd_form = FORM_N; + } +break; +case 84: +#line 1026 "ftpcmd.y" + { + cmd_type = TYPE_A; + cmd_form = yystack.l_mark[0].u.i; + } +break; +case 85: +#line 1032 "ftpcmd.y" + { + cmd_type = TYPE_E; + cmd_form = FORM_N; + } +break; +case 86: +#line 1038 "ftpcmd.y" + { + cmd_type = TYPE_E; + cmd_form = yystack.l_mark[0].u.i; + } +break; +case 87: +#line 1044 "ftpcmd.y" + { + cmd_type = TYPE_I; + } +break; +case 88: +#line 1049 "ftpcmd.y" + { + cmd_type = TYPE_L; + cmd_bytesz = NBBY; + } +break; +case 89: +#line 1055 "ftpcmd.y" + { + cmd_type = TYPE_L; + cmd_bytesz = yystack.l_mark[0].u.i; + } +break; +case 90: +#line 1062 "ftpcmd.y" + { + cmd_type = TYPE_L; + cmd_bytesz = yystack.l_mark[0].u.i; + } +break; +case 91: +#line 1070 "ftpcmd.y" + { + yyval.u.i = STRU_F; + } +break; +case 92: +#line 1075 "ftpcmd.y" + { + yyval.u.i = STRU_R; + } +break; +case 93: +#line 1080 "ftpcmd.y" + { + yyval.u.i = STRU_P; + } +break; +case 94: +#line 1087 "ftpcmd.y" + { + yyval.u.i = MODE_S; + } +break; +case 95: +#line 1092 "ftpcmd.y" + { + yyval.u.i = MODE_B; + } +break; +case 96: +#line 1097 "ftpcmd.y" + { + yyval.u.i = MODE_C; + } +break; +case 97: +#line 1104 "ftpcmd.y" + { + /* + * 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 && yystack.l_mark[0].s && *yystack.l_mark[0].s == '~') { + char *path, *home, *result; + size_t len; + + path = strchr(yystack.l_mark[0].s + 1, '/'); + if (path != NULL) + *path++ = '\0'; + if (yystack.l_mark[0].s[1] == '\0') + home = homedir; + else { + struct passwd *hpw; + + if ((hpw = getpwnam(yystack.l_mark[0].s + 1)) != NULL) + home = hpw->pw_dir; + else + home = yystack.l_mark[0].s; + } + 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); + } + yyval.s = result; + free(yystack.l_mark[0].s); + } else + yyval.s = yystack.l_mark[0].s; + } +break; +case 99: +#line 1151 "ftpcmd.y" + { + 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 = yystack.l_mark[0].u.i; + multby = 1; + ret = 0; + while (dec) { + digit = dec%10; + if (digit > 7) { + ret = -1; + break; + } + ret += digit * multby; + multby *= 8; + dec /= 10; + } + yyval.u.i = ret; + } +break; +case 103: +#line 1189 "ftpcmd.y" + { + yyval.u.i = yystack.l_mark[0].u.i; + } +break; +case 104: +#line 1196 "ftpcmd.y" + { + if (logged_in) + yyval.u.i = 1; + else { + reply(530, "Please login with USER and PASS."); + yyval.u.i = 0; + hasyyerrored = 1; + } + } +break; +#line 2517 "ftpcmd.c" + } + yystack.s_mark -= yym; + yystate = *yystack.s_mark; + yystack.l_mark -= yym; + yym = yylhs[yyn]; + if (yystate == 0 && yym == 0) + { +#if YYDEBUG + if (yydebug) + printf("%sdebug: after reduction, shifting from state 0 to\ + state %d\n", YYPREFIX, YYFINAL); +#endif + yystate = YYFINAL; + *++yystack.s_mark = YYFINAL; + *++yystack.l_mark = yyval; + if (yychar < 0) + { + if ((yychar = YYLEX) < 0) yychar = 0; +#if YYDEBUG + if (yydebug) + { + yys = 0; + if (yychar <= YYMAXTOKEN) yys = yyname[yychar]; + if (!yys) yys = "illegal-symbol"; + printf("%sdebug: state %d, reading %d (%s)\n", + YYPREFIX, YYFINAL, yychar, yys); + } +#endif + } + if (yychar == 0) goto yyaccept; + goto yyloop; + } + if ((yyn = yygindex[yym]) && (yyn += yystate) >= 0 && + yyn <= YYTABLESIZE && yycheck[yyn] == yystate) + yystate = yytable[yyn]; + else + yystate = yydgoto[yym]; +#if YYDEBUG + if (yydebug) + printf("%sdebug: after reduction, shifting from state %d \ +to state %d\n", YYPREFIX, *yystack.s_mark, yystate); +#endif + if (yystack.s_mark >= yystack.s_last && yygrowstack(&yystack)) + { + goto yyoverflow; + } + *++yystack.s_mark = (short) yystate; + *++yystack.l_mark = yyval; + goto yyloop; + +yyoverflow: + yyerror("yacc stack overflow"); + +yyabort: + yyfreestack(&yystack); + return (1); + +yyaccept: + yyfreestack(&yystack); + return (0); +} diff --git a/libexec/ftpd/ftpcmd.y b/libexec/ftpd/ftpcmd.y new file mode 100644 index 000000000..be4256d41 --- /dev/null +++ b/libexec/ftpd/ftpcmd.y @@ -0,0 +1,1875 @@ +/* $NetBSD: ftpcmd.y,v 1.93 2011/09/16 16:13:17 plunky Exp $ */ + +/*- + * Copyright (c) 1997-2009 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. + * + * 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. 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 + +#ifndef lint +#if 0 +static char sccsid[] = "@(#)ftpcmd.y 8.3 (Berkeley) 4/6/94"; +#else +__RCSID("$NetBSD: ftpcmd.y,v 1.93 2011/09/16 16:13:17 plunky Exp $"); +#endif +#endif /* not lint */ + +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef KERBEROS5 +#include +#endif + +#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; + +extern int epsvall; +struct tab sitetab[]; + +static int check_write(const char *, int); +static void help(struct tab *, const char *); +static void port_check(const char *, int); + int yylex(void); + +%} + +%union { + struct { + LLT ll; + int i; + } u; + char *s; + const char *cs; +} + +%token + A B C E F I + L N P R S T + + SP CRLF COMMA ALL + + 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 STRING +%token NUMBER + +%type check_login octal_number byte_size +%type struct_code mode_code type_code form_code decimal_integer +%type pathstring pathname password username +%type mechanism_name base64data prot_code + +%start cmd_sel + +%% + +cmd_sel + : cmd + { + REASSIGN(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 (CURCLASS_FLAGS_ISSET(passive)) { + if (epsvall) + reply(501, + "LPSV disallowed after EPSV ALL"); + else + long_passive("LPSV", PF_UNSPEC); + } else + reply(500, "LPSV mode not available."); + } + } + + | EPSV check_login SP NUMBER CRLF + { + if ($2) { + if (CURCLASS_FLAGS_ISSET(passive)) + long_passive("EPSV", + epsvproto2af($4.i)); + else + reply(500, "EPSV mode not available."); + } + } + + | EPSV check_login SP ALL CRLF + { + if ($2) { + if (CURCLASS_FLAGS_ISSET(passive)) { + reply(200, + "EPSV ALL command successful."); + epsvall++; + } else + reply(500, "EPSV mode not available."); + } + } + + | EPSV check_login CRLF + { + if ($2) { + if (CURCLASS_FLAGS_ISSET(passive)) + long_passive("EPSV", PF_UNSPEC); + else + reply(500, "EPSV mode not available."); + } + } + + | 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); + REASSIGN(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 + { + const char *argv[] = { INTERNAL_LS, "-lgA", NULL }; + + if (CURCLASS_FLAGS_ISSET(hidesymlinks)) + argv[1] = "-LlgA"; + if ($2) + retrieve(argv, ""); + } + + | LIST check_login SP pathname CRLF + { + const char *argv[] = { INTERNAL_LS, "-lgA", NULL, NULL }; + + if (CURCLASS_FLAGS_ISSET(hidesymlinks)) + argv[1] = "-LlgA"; + 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 == -1) || ($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 " LLF + " seconds; max " LLF, + (LLT)curclass.timeout, + (LLT)curclass.maxtimeout); + } + } + + | SITE SP IDLE check_login SP NUMBER CRLF + { + if ($4) { + if ($6.i < 30 || $6.i > curclass.maxtimeout) { + reply(501, + "IDLE time limit must be between 30 and " + LLF " seconds", + (LLT)curclass.maxtimeout); + } else { + curclass.timeout = $6.i; + (void) alarm(curclass.timeout); + reply(200, + "IDLE time limit set to " + LLF " seconds", + (LLT)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 errbuf[100]; + char *p = $6; + LLT rate; + + if ($4) { + rate = strsuftollx("RATEGET", p, 0, + curclass.maxrateget + ? curclass.maxrateget + : LLTMAX, errbuf, sizeof(errbuf)); + if (errbuf[0]) + reply(501, "%s", errbuf); + 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 errbuf[100]; + char *p = $6; + LLT rate; + + if ($4) { + rate = strsuftollx("RATEPUT", p, 0, + curclass.maxrateput + ? curclass.maxrateput + : LLTMAX, errbuf, sizeof(errbuf)); + if (errbuf[0]) + reply(501, "%s", errbuf); + 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 && check_write("", 0)) { + 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); + } + + + /* RFC 3659 */ + + /* + * 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 NUMBER CRLF + { + if ($2) { + REASSIGN(fromname, NULL); + restart_point = (off_t)$4.ll; + 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)) { + REASSIGN(fromname, NULL); + fromname = renamefrom($3); + } + if ($3 != NULL) + free($3); + } + ; + +username + : STRING + ; + +password + : /* empty */ + { + $$ = (char *)calloc(1, sizeof(char)); + } + + | STRING + ; + +byte_size + : NUMBER + { + $$ = $1.i; + } + ; + +host_port + : NUMBER COMMA NUMBER COMMA NUMBER COMMA NUMBER COMMA + NUMBER COMMA NUMBER + { + char *a, *p; + + 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.i; p[1] = $11.i; + a = (char *)&data_dest.su_addr; + a[0] = $1.i; a[1] = $3.i; a[2] = $5.i; a[3] = $7.i; + } + ; + +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.i; p[1] = $17.i; + a = (char *)&data_dest.su_addr; + a[0] = $5.i; a[1] = $7.i; a[2] = $9.i; a[3] = $11.i; + + /* reject invalid LPRT command */ + if ($1.i != 4 || $3.i != 4 || $13.i != 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 + unsigned char buf[16]; + + (void)memset(&data_dest, 0, sizeof(data_dest)); + data_dest.su_len = sizeof(struct sockaddr_in6); + data_dest.su_family = AF_INET6; + buf[0] = $39.i; buf[1] = $41.i; + (void)memcpy(&data_dest.su_port, buf, + sizeof(data_dest.su_port)); + buf[0] = $5.i; buf[1] = $7.i; + buf[2] = $9.i; buf[3] = $11.i; + buf[4] = $13.i; buf[5] = $15.i; + buf[6] = $17.i; buf[7] = $19.i; + buf[8] = $21.i; buf[9] = $23.i; + buf[10] = $25.i; buf[11] = $27.i; + buf[12] = $29.i; buf[13] = $31.i; + buf[14] = $33.i; buf[15] = $35.i; + (void)memcpy(&data_dest.si_su.su_sin6.sin6_addr, + buf, sizeof(data_dest.si_su.su_sin6.sin6_addr)); + 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.i != 6 || $3.i != 16 || $37.i != 2) + memset(&data_dest, 0, sizeof(data_dest)); + } + ; + +form_code + : N + { + $$ = FORM_N; + } + + | T + { + $$ = FORM_T; + } + + | C + { + $$ = FORM_C; + } + ; + +type_code + : A + { + cmd_type = TYPE_A; + cmd_form = FORM_N; + } + + | A SP form_code + { + cmd_type = TYPE_A; + cmd_form = $3; + } + + | E + { + cmd_type = TYPE_E; + cmd_form = FORM_N; + } + + | E SP form_code + { + cmd_type = TYPE_E; + cmd_form = $3; + } + + | I + { + cmd_type = TYPE_I; + } + + | L + { + cmd_type = TYPE_L; + cmd_bytesz = 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 *hpw; + + if ((hpw = getpwnam($1 + 1)) != NULL) + home = hpw->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.i; + 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 + { + $$ = $1.i; + } + ; + +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, " username", 0, }, + { "PASS", PASS, ZSTR1, 1, " password", 0, }, + { "ACCT", ACCT, STR1, 0, "(specify account)", 0, }, + { "CWD", CWD, OSTR, 1, "[ directory-name ]", 0, }, + { "CDUP", CDUP, NOARGS, 1, "(change to parent directory)", 0, }, + { "SMNT", SMNT, ARGS, 0, "(structure mount)", 0, }, + { "QUIT", QUIT, NOARGS, 1, "(terminate service)", 0, }, + { "REIN", REIN, NOARGS, 0, "(reinitialize server state)", 0, }, + { "PORT", PORT, ARGS, 1, " b0, b1, b2, b3, b4, b5", 0, }, + { "LPRT", LPRT, ARGS, 1, " af, hal, h1, h2, h3,..., pal, p1, p2...", 0, }, + { "EPRT", EPRT, STR1, 1, " |af|addr|port|", 0, }, + { "PASV", PASV, NOARGS, 1, "(set server in passive mode)", 0, }, + { "LPSV", LPSV, ARGS, 1, "(set server in passive mode)", 0, }, + { "EPSV", EPSV, ARGS, 1, "[ af|ALL]", 0, }, + { "TYPE", TYPE, ARGS, 1, " [ A | E | I | L ]", 0, }, + { "STRU", STRU, ARGS, 1, "(specify file structure)", 0, }, + { "MODE", MODE, ARGS, 1, "(specify transfer mode)", 0, }, + { "RETR", RETR, STR1, 1, " file-name", 0, }, + { "STOR", STOR, STR1, 1, " file-name", 0, }, + { "STOU", STOU, STR1, 1, " file-name", 0, }, + { "APPE", APPE, STR1, 1, " file-name", 0, }, + { "ALLO", ALLO, ARGS, 1, "allocate storage (vacuously)", 0, }, + { "REST", REST, ARGS, 1, " offset (restart command)", 0, }, + { "RNFR", RNFR, STR1, 1, " file-name", 0, }, + { "RNTO", RNTO, STR1, 1, " file-name", 0, }, + { "ABOR", ABOR, NOARGS, 4, "(abort operation)", 0, }, + { "DELE", DELE, STR1, 1, " file-name", 0, }, + { "RMD", RMD, STR1, 1, " path-name", 0, }, + { "MKD", MKD, STR1, 1, " path-name", 0, }, + { "PWD", PWD, NOARGS, 1, "(return current directory)", 0, }, + { "LIST", LIST, OSTR, 1, "[ path-name ]", 0, }, + { "NLST", NLST, OSTR, 1, "[ path-name ]", 0, }, + { "SITE", SITE, SITECMD, 1, "site-cmd [ arguments ]", 0, }, + { "SYST", SYST, NOARGS, 1, "(get type of operating system)", 0, }, + { "STAT", STAT, OSTR, 4, "[ path-name ]", 0, }, + { "HELP", HELP, OSTR, 1, "[ ]", 0, }, + { "NOOP", NOOP, NOARGS, 2, "", 0, }, + + /* From RFC 2228, in order defined */ + { "AUTH", AUTH, STR1, 1, " mechanism-name", 0, }, + { "ADAT", ADAT, STR1, 1, " base-64-data", 0, }, + { "PROT", PROT, STR1, 1, " prot-code", 0, }, + { "PBSZ", PBSZ, ARGS, 1, " decimal-integer", 0, }, + { "CCC", CCC, NOARGS, 1, "(Disable data protection)", 0, }, + { "MIC", MIC, STR1, 4, " base64data", 0, }, + { "CONF", CONF, STR1, 4, " base64data", 0, }, + { "ENC", ENC, STR1, 4, " base64data", 0, }, + + /* From RFC 2389, in order defined */ + { "FEAT", FEAT, NOARGS, 1, "(display extended features)", 0, }, + { "OPTS", OPTS, STR1, 1, " command [ options ]", 0, }, + + /* From RFC 3659, in order defined */ + { "MDTM", MDTM, OSTR, 1, " path-name", 0, }, + { "SIZE", SIZE, OSTR, 1, " path-name", 0, }, + { "MLST", MLST, OSTR, 2, "[ path-name ]", 0, }, + { "MLSD", MLSD, OSTR, 1, "[ directory-name ]", 0, }, + + /* obsolete commands */ + { "MAIL", MAIL, OSTR, 0, "(mail to user)", 0, }, + { "MLFL", MLFL, OSTR, 0, "(mail file)", 0, }, + { "MRCP", MRCP, STR1, 0, "(mail recipient)", 0, }, + { "MRSQ", MRSQ, OSTR, 0, "(mail recipient scheme question)", 0, }, + { "MSAM", MSAM, OSTR, 0, "(mail send to terminal and mailbox)", 0, }, + { "MSND", MSND, OSTR, 0, "(mail send to terminal)", 0, }, + { "MSOM", MSOM, OSTR, 0, "(mail send to terminal or mailbox)", 0, }, + { "XCUP", CDUP, NOARGS, 1, "(change to parent directory)", 0, }, + { "XCWD", CWD, OSTR, 1, "[ directory-name ]", 0, }, + { "XMKD", MKD, STR1, 1, " path-name", 0, }, + { "XPWD", PWD, NOARGS, 1, "(return current directory)", 0, }, + { "XRMD", RMD, STR1, 1, " path-name", 0, }, + + { NULL, 0, 0, 0, 0, 0, } +}; + +struct tab sitetab[] = { + { "CHMOD", CHMOD, NSTR, 1, " mode file-name", 0, }, + { "HELP", HELP, OSTR, 1, "[ ]", 0, }, + { "IDLE", IDLE, ARGS, 1, "[ maximum-idle-time ]", 0, }, + { "RATEGET", RATEGET,OSTR, 1, "[ get-throttle-rate ]", 0, }, + { "RATEPUT", RATEPUT,OSTR, 1, "[ put-throttle-rate ]", 0, }, + { "UMASK", UMASK, ARGS, 1, "[ umask ]", 0, }, + { NULL, 0, 0, 0, 0, 0, } +}; + +/* + * Check if a filename is allowed to be modified (isupload == 0) or + * uploaded (isupload == 1), and if necessary, check the filename is `sane'. + * If the filename is NULL, fail. + * If the filename is "", don't do the sane name check. + */ +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 (file[0] != '\0' && CURCLASS_FLAGS_ISSET(sanenames)) { + const char *p; + + if (file[0] == '.') + goto insane_name; + for (p = file; *p; p++) { + if (isalnum((unsigned char)*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 + +/* + * get_line - a hacked up version of fgets to ignore TELNET escape codes. + * `s' is the buffer to read into. + * `n' is the 1 less than the size of the buffer, to allow trailing NUL + * `iop' is the FILE to read from. + * Returns 0 on success, -1 on EOF, -2 if the command was too long. + */ +int +get_line(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 (ftpd_debug) + syslog(LOG_DEBUG, "command: %s", s); + tmpline[0] = '\0'; + return(0); + } + 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) { + /* + * If command doesn't fit into buffer, discard the + * rest of the command and indicate truncation. + * This prevents the command to be split up into + * multiple commands. + */ + if (ftpd_debug) + syslog(LOG_DEBUG, + "command too long, last char: %d", c); + while (c != '\n' && (c = getc(iop)) != EOF) + continue; + return (-2); + } + if (c == '\n') + break; + } + if (c == EOF && cs == s) + return (-1); + *cs++ = '\0'; + if (ftpd_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 (0); +} + +void +ftp_handle_line(char *cp) +{ + + cmdp = cp; + yyparse(); +} + +void +ftp_loop(void) +{ + int ret; + + while (1) { + (void) alarm(curclass.timeout); + ret = get_line(cbuf, sizeof(cbuf)-1, stdin); + (void) alarm(0); + if (ret == -1) { + reply(221, "You could at least say goodbye."); + dologout(0); + } else if (ret == -2) { + reply(500, "Command too long."); + } else { + ftp_handle_line(cbuf); + } + } + /*NOTREACHED*/ +} + +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 defined(HAVE_SETPROCTITLE) + if (strncasecmp(cmdp, "PASS", 4) != 0 && + strncasecmp(cmdp, "ACCT", 4) != 0) + setproctitle("%s: %s", proctitle, cmdp); +#endif /* defined(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.cs = 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.cs = 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 = ftpd_strdup(cp); + cmdp[cpos] = '\n'; + state = ARGS; + return (STRING); + } + break; + + case NSTR: + if (cmdp[cpos] == ' ') { + cpos++; + return (SP); + } + if (isdigit((unsigned char)cmdp[cpos])) { + cp = &cmdp[cpos]; + while (isdigit((unsigned char)cmdp[++cpos])) + ; + c = cmdp[cpos]; + cmdp[cpos] = '\0'; + yylval.u.i = atoi(cp); + cmdp[cpos] = c; + state = STR1; + return (NUMBER); + } + state = STR1; + goto dostr1; + + case ARGS: + if (isdigit((unsigned char)cmdp[cpos])) { + cp = &cmdp[cpos]; + while (isdigit((unsigned char)cmdp[++cpos])) + ; + c = cmdp[cpos]; + cmdp[cpos] = '\0'; + yylval.u.i = atoi(cp); + yylval.u.ll = STRTOLL(cp, NULL, 10); + cmdp[cpos] = c; + return (NUMBER); + } + if (strncasecmp(&cmdp[cpos], "ALL", 3) == 0 + && !isalnum((unsigned char)cmdp[cpos + 3])) { + 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; + return (0); +} + +/* ARGSUSED */ +void +yyerror(const 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; + const char *htype; + + if (ctab == sitetab) + htype = "SITE "; + else + htype = ""; + 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.", htype); + 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", htype, c->name, c->help); + else + reply(504, "%s%-*s\t%s; not implemented.", htype, 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/libexec/ftpd/ftpd.8 b/libexec/ftpd/ftpd.8 new file mode 100644 index 000000000..c62d3d391 --- /dev/null +++ b/libexec/ftpd/ftpd.8 @@ -0,0 +1,871 @@ +.\" $NetBSD: ftpd.8,v 1.85 2009/05/01 10:53:27 wiz Exp $ +.\" +.\" Copyright (c) 1997-2008 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. +.\" +.\" 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. 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 May 1, 2009 +.Dt FTPD 8 +.Os +.Sh NAME +.Nm ftpd +.Nd +Internet File Transfer Protocol server +.Sh SYNOPSIS +.Nm +.Op Fl 46DdHlnQqrsUuWwX +.Op Fl a Ar anondir +.Op Fl C Ar user Ns Op @ Ns Ar host +.Op Fl c Ar confdir +.Op Fl e Ar emailaddr +.Op Fl h Ar hostname +.Op Fl L Ar xferlogfile +.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 4 +When +.Fl D +is specified, bind to IPv4 addresses only. +.It Fl 6 +When +.Fl D +is specified, bind to IPv6 addresses only. +.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 user Ns Op @ Ns Ar host +Check whether +.Ar user +.Po +as if connecting from +.Ar host , +if provided +.Pc +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 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 D +Run as daemon. +.Nm +will listen on the default FTP port for incoming connections +and fork a child for each connection. +This is lower overhead than starting +.Nm +from +.Xr inetd 8 +and thus might be useful on busy servers to reduce load. +.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 +Equivalent to +.Do +-h +`hostname` +.Dc . +.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 L Ar xferlogfile +Log +.Tn wu-ftpd +style +.Sq xferlog +entries to +.Ar xferlogfile . +.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 n +Don't attempt translation of IP addresses to hostnames. +.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 +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 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 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 +Don't log each concurrent +.Tn FTP +session to +.Pa /var/run/utmp . +This is the default. +.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 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 +Don't log each +.Tn FTP +session to +.Pa /var/log/wtmp . +.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 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 "sed -ne 's/^.*xferlog: //p' /var/log/xferlog \*[Gt] 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" "Description" -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 Description -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 +and +.Cm RFC 2228 ) +are recognized, but are not implemented: +.Sy ACCT , +.Sy ADAT , +.Sy AUTH , +.Sy CCC , +.Sy CONF , +.Sy ENC , +.Sy MIC , +.Sy PBSZ , +.Sy PROT , +.Sy REIN , +and +.Sy SMNT . +.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 use the metacharacters +.Dq Li \&*?[]{}~ . +.Ss User authentication +.Nm +authenticates users according to five rules. +.Pp +.Bl -enum -offset indent +.It +The login name must be in the password data base, +.Xr passwd 5 , +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 . +As per +.Xr shells 5 , +the user's shell must be listed with full path in +.Pa /etc/shells . +.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. +This facility may also be triggered by enabling the boolean +.Sy ftp-chroot +in +.Xr login.conf 5 . +However, the user must still supply a password. +This feature is intended as a compromise between a fully anonymous account +and a fully privileged account. +The account should also be set up as for an anonymous account. +.It +If the user name is +.Dq anonymous +or +.Dq ftp , +an +anonymous +.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 +.Ss Display file escape sequences +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 +.Ss Setting up a restricted ftp subtree +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" +.Dl "upload guest on" +.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 whose root directory should be changed via +.Xr chroot 2 . +.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 ftpchroot 5 , +.Xr ftpd.conf 5 , +.Xr ftpusers 5 , +.Xr login.conf 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 RFC 3659 . +.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 RFC 3659 +support was implemented in +.Nx 1.3 +and later releases by Luke Mewburn. +.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. +.Pp +To avoid possible denial-of-service attacks, +.Sy SIZE +requests against files larger than 10240 bytes will be denied if +the current transfer +.Sy TYPE +is +.Sq Li A +(ASCII). diff --git a/libexec/ftpd/ftpd.c b/libexec/ftpd/ftpd.c new file mode 100644 index 000000000..01792f004 --- /dev/null +++ b/libexec/ftpd/ftpd.c @@ -0,0 +1,4000 @@ +/* $NetBSD: ftpd.c,v 1.198 2012/06/19 06:06:34 dholland Exp $ */ + +/* + * Copyright (c) 1997-2009 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. + * + * 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. 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 +#ifndef lint +__COPYRIGHT("@(#) Copyright (c) 1985, 1988, 1990, 1992, 1993, 1994\ + The Regents of the University of California. All rights reserved."); +#endif /* not lint */ + +#ifndef lint +#if 0 +static char sccsid[] = "@(#)ftpd.c 8.5 (Berkeley) 4/28/95"; +#else +__RCSID("$NetBSD: ftpd.c,v 1.198 2012/06/19 06:06:34 dholland Exp $"); +#endif +#endif /* not lint */ + +/* + * FTP server. + */ +#include +#include +#include +#include +#include +#include +#include + +#include +#ifndef __minix +#include +#include +#endif + +#define FTP_NAMES +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef SUPPORT_UTMP +#include +#endif +#ifdef SUPPORT_UTMPX +#include +#endif +#ifdef SKEY +#include +#endif +#ifdef KERBEROS5 +#include +#include +#endif + +#ifdef LOGIN_CAP +#include +#endif + +#ifdef USE_PAM +#include +#endif + +#define GLOBAL +#include "extern.h" +#include "pathnames.h" +#include "version.h" + +static sig_atomic_t transflag; +static sig_atomic_t urgflag; + +int data; +int Dflag; +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/write wu-ftpd style xferlog entries */ +int xferlogfd; /* fd to write wu-ftpd xferlog entries to */ +int getnameopts; /* flags for use with getname() */ +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]; + +#ifdef USE_PAM +static int auth_pam(void); +pam_handle_t *pamh = NULL; +#endif + +#ifdef SUPPORT_UTMP +static struct utmp utmp; /* for utmp */ +#endif +#ifdef SUPPORT_UTMPX +static struct utmpx utmpx; /* for utmpx */ +#endif + +static const char *anondir = NULL; +static const char *confdir = _DEFAULT_CONFDIR; + +static char *curname; /* current USER name */ +static size_t curname_len; /* length of curname (include NUL) */ + +#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; + +enum send_status { + SS_SUCCESS, + SS_ABORTED, /* transfer aborted */ + SS_NO_TRANSFER, /* no transfer made yet */ + SS_FILE_ERROR, /* file read error */ + SS_DATA_ERROR /* data send error */ +}; + +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 do_pass(int, int, const char *); +static void end_login(void); +static FILE *getdatasock(const char *); +static char *gunique(const char *); +static void login_utmp(const char *, const char *, const char *, + struct sockinet *); +static void logremotehost(struct sockinet *); +__dead static void lostconn(int); +__dead static void toolong(int); +__dead static void sigquit(int); +static void sigurg(int); +static int handleoobcmd(void); +static int receive_data(FILE *, FILE *); +static int send_data(FILE *, FILE *, const struct stat *, int); +static struct passwd *sgetpwnam(const char *); +static int write_data(int, char *, size_t, off_t *, struct timeval *, + int); +static enum send_status + send_data_with_read(int, int, const struct stat *, int); +#ifndef __minix +static enum send_status + send_data_with_mmap(int, int, const struct stat *, int); +static void logrusage(const struct rusage *, const struct rusage *); +#endif +static void logout_utmp(void); + +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 + +int +main(int argc, char *argv[]) +{ + int ch, on = 1, tos, keepalive; + socklen_t addrlen; +#ifdef KERBEROS5 + krb5_error_code kerror; +#endif + char *p; + const char *xferlogname = NULL; + long l; + struct sigaction sa; + sa_family_t af = AF_UNSPEC; + + connections = 1; + ftpd_debug = 0; + logging = 0; + pdata = -1; + Dflag = 0; + 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 */ + xferlogfd = -1; /* default: Do NOT write xferlog file */ + getnameopts = 0; /* default: xlate addrs to name */ + 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, LOG_FTP); + + while ((ch = getopt(argc, argv, + "46a:c:C:Dde:h:HlL:nP:qQrst:T:uUvV:wWX")) != -1) { + switch (ch) { + case '4': + af = AF_INET; + break; + + case '6': + af = AF_INET6; + break; + + case 'a': + anondir = optarg; + break; + + case 'c': + confdir = optarg; + break; + + case 'C': + if ((p = strchr(optarg, '@')) != NULL) { + *p++ = '\0'; + strlcpy(remotehost, p, MAXHOSTNAMELEN + 1); + if (inet_pton(AF_INET, p, + &his_addr.su_addr) == 1) { + his_addr.su_family = AF_INET; + his_addr.su_len = + sizeof(his_addr.si_su.su_sin); +#ifdef INET6 + } else if (inet_pton(AF_INET6, p, + &his_addr.su_6addr) == 1) { + his_addr.su_family = AF_INET6; + his_addr.su_len = + sizeof(his_addr.si_su.su_sin6); +#endif + } else + his_addr.su_family = AF_UNSPEC; + } + pw = sgetpwnam(optarg); + exit(checkaccess(optarg) ? 0 : 1); + /* NOTREACHED */ + + case 'D': + Dflag = 1; + break; + + case 'd': + case 'v': /* deprecated */ + ftpd_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 'L': + xferlogname = optarg; + break; + + case 'n': + getnameopts = NI_NUMERICHOST; + break; + + case 'P': + errno = 0; + p = NULL; + l = strtol(optarg, &p, 10); + if (errno || *optarg == '\0' || *p != '\0' || + l < IPPORT_RESERVED || + l > IPPORT_ANONMAX) { + syslog(LOG_WARNING, "Invalid dataport %s", + optarg); + dataport = 0; + } + dataport = (int)l; + 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 = ftpd_strdup(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; + + if (dowtmp) { +#ifdef SUPPORT_UTMPX + ftpd_initwtmpx(); +#endif +#ifdef SUPPORT_UTMP + ftpd_initwtmp(); +#endif + } + errno = 0; +#ifndef __minix + l = sysconf(_SC_LOGIN_NAME_MAX); + if (l == -1 && errno != 0) { + syslog(LOG_ERR, "sysconf _SC_LOGIN_NAME_MAX: %m"); + exit(1); + } else if (l <= 0) { + syslog(LOG_WARNING, "using conservative LOGIN_NAME_MAX value"); + curname_len = _POSIX_LOGIN_NAME_MAX; + } else + curname_len = (size_t)l; +#else + curname_len = _POSIX_LOGIN_NAME_MAX; +#endif + curname = malloc(curname_len); + if (curname == NULL) { + syslog(LOG_ERR, "malloc: %m"); + exit(1); + } + curname[0] = '\0'; + + if (Dflag) { + int error, fd, i, n, *socks; + struct pollfd *fds; + struct addrinfo hints, *res, *res0; + + if (daemon(1, 0) == -1) { + syslog(LOG_ERR, "failed to daemonize: %m"); + exit(1); + } + (void)memset(&sa, 0, sizeof(sa)); + sa.sa_handler = SIG_IGN; + sa.sa_flags = SA_NOCLDWAIT; + sigemptyset(&sa.sa_mask); + (void)sigaction(SIGCHLD, &sa, NULL); + + (void)memset(&hints, 0, sizeof(hints)); + hints.ai_flags = AI_PASSIVE; + hints.ai_family = af; + hints.ai_socktype = SOCK_STREAM; + error = getaddrinfo(NULL, "ftp", &hints, &res0); + if (error) { + syslog(LOG_ERR, "getaddrinfo: %s", gai_strerror(error)); + exit(1); + } + + for (n = 0, res = res0; res != NULL; res = res->ai_next) + n++; + if (n == 0) { + syslog(LOG_ERR, "no addresses available"); + exit(1); + } + socks = malloc(n * sizeof(int)); + fds = malloc(n * sizeof(struct pollfd)); + if (socks == NULL || fds == NULL) { + syslog(LOG_ERR, "malloc: %m"); + exit(1); + } + + for (n = 0, res = res0; res != NULL; res = res->ai_next) { + socks[n] = socket(res->ai_family, res->ai_socktype, + res->ai_protocol); + if (socks[n] == -1) + continue; + (void)setsockopt(socks[n], SOL_SOCKET, SO_REUSEADDR, + &on, sizeof(on)); + if (bind(socks[n], res->ai_addr, res->ai_addrlen) + == -1) { + (void)close(socks[n]); + continue; + } + if (listen(socks[n], 12) == -1) { + (void)close(socks[n]); + continue; + } + + fds[n].fd = socks[n]; + fds[n].events = POLLIN; + n++; + } + if (n == 0) { + syslog(LOG_ERR, "%m"); + exit(1); + } + freeaddrinfo(res0); + + if (pidfile(NULL) == -1) + syslog(LOG_ERR, "failed to write a pid file: %m"); + + for (;;) { + if (poll(fds, n, INFTIM) == -1) { + if (errno == EINTR) + continue; + syslog(LOG_ERR, "poll: %m"); + exit(1); + } + for (i = 0; i < n; i++) { + if (fds[i].revents & POLLIN) { + fd = accept(fds[i].fd, NULL, NULL); + if (fd == -1) { + syslog(LOG_ERR, "accept: %m"); + continue; + } + switch (fork()) { + case -1: + syslog(LOG_ERR, "fork: %m"); + break; + case 0: + goto child; + /* NOTREACHED */ + } + (void)close(fd); + } + } + } + child: + (void)dup2(fd, STDIN_FILENO); + (void)dup2(fd, STDOUT_FILENO); + (void)dup2(fd, STDERR_FILENO); + for (i = 0; i < n; i++) + (void)close(socks[i]); + } + + 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((errno == ENOTCONN) ? LOG_NOTICE : 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, + getnameopts) != 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); + + memset(&sa, 0, sizeof(sa)); + sa.sa_handler = SIG_DFL; + sa.sa_flags = SA_RESTART; + sigemptyset(&sa.sa_mask); + (void) sigaction(SIGCHLD, &sa, NULL); + + sa.sa_handler = sigquit; + sa.sa_flags = SA_RESTART; + sigfillset(&sa.sa_mask); /* block all sigs in these handlers */ + (void) sigaction(SIGHUP, &sa, NULL); + (void) sigaction(SIGINT, &sa, NULL); + (void) sigaction(SIGQUIT, &sa, NULL); + (void) sigaction(SIGTERM, &sa, NULL); + sa.sa_handler = lostconn; + (void) sigaction(SIGPIPE, &sa, NULL); + sa.sa_handler = toolong; + (void) sigaction(SIGALRM, &sa, NULL); + sa.sa_handler = sigurg; +#ifndef __minix + (void) sigaction(SIGURG, &sa, NULL); + + /* 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 +#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(_NAME_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); + + if (xferlogname != NULL) { + xferlogfd = open(xferlogname, O_WRONLY | O_APPEND | O_CREAT, + 0660); + if (xferlogfd == -1) + syslog(LOG_WARNING, "open xferlog `%s': %m", + xferlogname); + else + doxferlog |= 2; + } + + ftp_loop(); + /* NOTREACHED */ +} + +static void +lostconn(int signo __unused) +{ + + if (ftpd_debug) + syslog(LOG_DEBUG, "lost connection"); + dologout(1); +} + +static void +toolong(int signo __unused) +{ + + /* XXXSIGRACE */ + reply(421, + "Timeout (" LLF " seconds): closing control connection.", + (LLT)curclass.timeout); + if (logging) + syslog(LOG_INFO, "User %s timed out after " LLF " seconds", + (pw ? pw->pw_name : "unknown"), (LLT)curclass.timeout); + dologout(1); +} + +static void +sigquit(int signo) +{ + + if (ftpd_debug) + syslog(LOG_DEBUG, "got signal %d", signo); + dologout(1); +} + +static void +sigurg(int signo __unused) +{ + + urgflag = 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 = ftpd_strdup(p->pw_name); + save.pw_passwd = ftpd_strdup(p->pw_passwd); + save.pw_gecos = ftpd_strdup(p->pw_gecos); + save.pw_dir = ftpd_strdup(p->pw_dir); + save.pw_shell = ftpd_strdup(p->pw_shell); + return (&save); +} + +static int login_attempts; /* number of failed login attempts */ +static int askpasswd; /* had USER command, ask for PASSwd */ +static int permitted; /* USER permitted */ + +/* + * 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 + * _NAME_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 + * _NAME_FTPUSERS to allow people such as root and uucp to be avoided. + */ +void +user(const char *name) +{ + char *class; +#ifdef LOGIN_CAP + login_cap_t *lc = NULL; +#endif +#ifdef USE_PAM + int e; +#endif + + class = NULL; + 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; + askpasswd = 0; + permitted = 0; + + 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) { + if (logging) + syslog(LOG_NOTICE, + "ANONYMOUS FTP LOGIN REFUSED FROM %s", + remoteloghost); + end_login(); + goto cleanup_user; + } + name = "ftp"; + } else + pw = sgetpwnam(name); + + strlcpy(curname, name, curname_len); + + /* check user in /etc/ftpusers, and setup class */ + permitted = checkuser(_NAME_FTPUSERS, curname, 1, 0, &class); + + /* check user in /etc/ftpchroot */ +#ifdef LOGIN_CAP + lc = login_getpwclass(pw); +#endif + if (checkuser(_NAME_FTPCHROOT, curname, 0, 0, NULL) +#ifdef LOGIN_CAP /* Allow login.conf configuration as well */ + || login_getcapbool(lc, "ftp-chroot", 0) +#endif + ) { + if (curclass.type == CLASS_GUEST) { + syslog(LOG_NOTICE, + "Can't change guest user to chroot class; remove entry in %s", + _NAME_FTPCHROOT); + exit(1); + } + curclass.type = CLASS_CHROOT; + } + + /* determine default class */ + if (class == NULL) { + switch (curclass.type) { + case CLASS_GUEST: + class = ftpd_strdup("guest"); + break; + case CLASS_CHROOT: + class = ftpd_strdup("chroot"); + break; + case CLASS_REAL: + class = ftpd_strdup("real"); + break; + default: + syslog(LOG_ERR, "unknown curclass.type %d; aborting", + curclass.type); + abort(); + } + } + /* parse ftpd.conf, setting up various parameters */ + parse_conf(class); + /* if not guest user, check for valid shell */ + if (pw == NULL) + permitted = 0; + else { + const char *cp, *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) + permitted = 0; + } + + /* deny quickly (after USER not PASS) if requested */ + if (CURCLASS_FLAGS_ISSET(denyquick) && !permitted) { + reply(530, "User %s may not use FTP.", curname); + if (logging) + syslog(LOG_NOTICE, "FTP LOGIN REFUSED FROM %s, %s", + remoteloghost, curname); + end_login(); + goto cleanup_user; + } + + /* if haven't asked yet (i.e, not anon), ask now */ + if (!askpasswd) { + askpasswd = 1; +#ifdef USE_PAM + e = auth_pam(); /* this does reply(331, ...) */ + do_pass(1, e, ""); + goto cleanup_user; +#else /* !USE_PAM */ +#ifdef SKEY + if (skey_haskey(curname) == 0) { + const char *myskey; + + myskey = skey_keyinfo(curname); + reply(331, "Password [ %s ] required for %s.", + myskey ? myskey : "error getting challenge", + curname); + } else +#endif + reply(331, "Password required for %s.", curname); +#endif /* !USE_PAM */ + } + + cleanup_user: +#ifdef LOGIN_CAP + login_close(lc); +#endif + /* + * Delay before reading passwd after first failed + * attempt to slow down passwd-guessing programs. + */ + if (login_attempts) + sleep((unsigned) login_attempts); + + if (class) + free(class); +} + +/* + * 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 compatibility, `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 *word, *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) { + word = perm = class = NULL; + p = buf; + if (len < 1) + continue; + if (p[len - 1] == '\n') + p[--len] = '\0'; + if (EMPTYSTR(p)) + continue; + + NEXTWORD(p, word); + NEXTWORD(p, perm); + NEXTWORD(p, class); + if (EMPTYSTR(word)) + 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(word, '@')) != NULL) { + unsigned char net[16], mask[16], *addr; + int addrlen, bits, bytes, a; + + *p++ = '\0'; + /* check against network or CIDR */ + memset(net, 0x00, sizeof(net)); + if ((bits = inet_net_pton(his_addr.su_family, p, net, + sizeof(net))) != -1) { +#ifdef INET6 + if (his_addr.su_family == AF_INET) { +#endif + addrlen = 4; + addr = (unsigned char *)&his_addr.su_addr; +#ifdef INET6 + } else { + addrlen = 16; + addr = (unsigned char *)&his_addr.su_6addr; + } +#endif + bytes = bits / 8; + bits = bits % 8; + if (bytes > 0) + memset(mask, 0xFF, bytes); + if (bytes < addrlen) + mask[bytes] = 0xFF << (8 - bits); + if (bytes + 1 < addrlen) + memset(mask + bytes + 1, 0x00, + addrlen - bytes - 1); + for (a = 0; a < addrlen; a++) + if ((addr[a] & mask[a]) != net[a]) + break; + if (a < addrlen) + continue; + + /* check against hostname glob */ + } else if (fnmatch(p, remotehost, FNM_CASEFOLD) != 0) + continue; + } + + /* have a group specifier */ + if ((p = strchr(word, ':')) != NULL) { + gid_t *groups, *ng; + int gsize, i, found; + + if (pw == NULL) + continue; /* no match for unknown user */ + *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(word, 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 = ftpd_strdup(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(_NAME_FTPUSERS, name, 1, 0, NULL)); +} + +static void +login_utmp(const char *line, const char *name, const char *host, + struct sockinet *haddr) +{ +#if defined(SUPPORT_UTMPX) || defined(SUPPORT_UTMP) + struct timeval tv; + (void)gettimeofday(&tv, NULL); +#endif +#ifdef SUPPORT_UTMPX + if (doutmp) { + (void)memset(&utmpx, 0, sizeof(utmpx)); + utmpx.ut_tv = tv; + utmpx.ut_pid = getpid(); + utmpx.ut_id[0] = 'f'; + utmpx.ut_id[1] = 't'; + utmpx.ut_id[2] = 'p'; + utmpx.ut_id[3] = '*'; + utmpx.ut_type = USER_PROCESS; + (void)strncpy(utmpx.ut_name, name, sizeof(utmpx.ut_name)); + (void)strncpy(utmpx.ut_line, line, sizeof(utmpx.ut_line)); + (void)strncpy(utmpx.ut_host, host, sizeof(utmpx.ut_host)); + (void)memcpy(&utmpx.ut_ss, &haddr->si_su, haddr->su_len); + ftpd_loginx(&utmpx); + } + if (dowtmp) + ftpd_logwtmpx(line, name, host, haddr, 0, USER_PROCESS); +#endif +#ifdef SUPPORT_UTMP + if (doutmp) { + (void)memset(&utmp, 0, sizeof(utmp)); + (void)time(&utmp.ut_time); + (void)strncpy(utmp.ut_name, name, sizeof(utmp.ut_name)); + (void)strncpy(utmp.ut_line, line, sizeof(utmp.ut_line)); + (void)strncpy(utmp.ut_host, host, sizeof(utmp.ut_host)); + ftpd_login(&utmp); + } + if (dowtmp) + ftpd_logwtmp(line, name, host); +#endif +} + +static void +logout_utmp(void) +{ +#ifdef SUPPORT_UTMPX + int okwtmpx = dowtmp; +#endif +#ifdef SUPPORT_UTMP + int okwtmp = dowtmp; +#endif + if (logged_in) { +#ifdef SUPPORT_UTMPX + if (doutmp) + okwtmpx &= ftpd_logoutx(ttyline, 0, DEAD_PROCESS); + if (okwtmpx) + ftpd_logwtmpx(ttyline, "", "", NULL, 0, DEAD_PROCESS); +#endif +#ifdef SUPPORT_UTMP + if (doutmp) + okwtmp &= ftpd_logout(ttyline); + if (okwtmp) + ftpd_logwtmp(ttyline, "", ""); +#endif + } +} + +/* + * Terminate login as previous user (if any), resetting state; + * used when USER command is given or login fails. + */ +static void +end_login(void) +{ +#ifdef USE_PAM + int e; +#endif + logout_utmp(); + 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; + askpasswd = 0; + permitted = 0; + quietmessages = 0; + gidcount = 0; + curclass.type = CLASS_REAL; + (void) seteuid((uid_t)0); +#ifdef LOGIN_CAP + setusercontext(NULL, getpwuid(0), 0, + LOGIN_SETPRIORITY|LOGIN_SETRESOURCES|LOGIN_SETUMASK); +#endif +#ifdef USE_PAM + if (pamh) { + if ((e = pam_setcred(pamh, PAM_DELETE_CRED)) != PAM_SUCCESS) + syslog(LOG_ERR, "pam_setcred: %s", + pam_strerror(pamh, e)); + if ((e = pam_close_session(pamh,0)) != PAM_SUCCESS) + syslog(LOG_ERR, "pam_close_session: %s", + pam_strerror(pamh, e)); + if ((e = pam_end(pamh, e)) != PAM_SUCCESS) + syslog(LOG_ERR, "pam_end: %s", pam_strerror(pamh, e)); + pamh = NULL; + } +#endif +} + +void +pass(const char *passwd) +{ + do_pass(0, 0, passwd); +} + +/* + * Perform the passwd confirmation and login. + * + * If pass_checked is zero, confirm passwd is correct, & ignore pass_rval. + * This is the traditional PASS implementation. + * + * If pass_checked is non-zero, use pass_rval and ignore passwd. + * This is used by auth_pam() which has already parsed PASS. + * This only applies to curclass.type != CLASS_GUEST. + */ +static void +do_pass(int pass_checked, int pass_rval, const char *passwd) +{ + int rval; + char root[MAXPATHLEN]; +#ifdef LOGIN_CAP + login_cap_t *lc = NULL; +#endif +#ifdef USE_PAM + int e; +#endif + + rval = 1; + + 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 (pass_checked) { /* password validated in user() */ + rval = pass_rval; + goto skip; + } +#ifdef USE_PAM + syslog(LOG_ERR, "do_pass: USE_PAM shouldn't get here"); + rval = 1; + goto skip; +#endif +#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 = ftpd_strdup(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", remoteloghost); + syslog(LOG_AUTHPRIV | LOG_NOTICE, + "FTP LOGIN FAILED FROM %s, %s", + remoteloghost, curname); + } + pw = NULL; + if (login_attempts++ >= 5) { + syslog(LOG_NOTICE, + "repeated login failures from %s", + remoteloghost); + exit(0); + } + return; + } + } + + /* password ok; check if anything else prevents login */ + if (! permitted) { + reply(530, "User %s may not use FTP.", pw->pw_name); + if (logging) + syslog(LOG_NOTICE, "FTP LOGIN REFUSED FROM %s, %s", + remoteloghost, 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; + } +#ifdef LOGIN_CAP + if ((lc = login_getpwclass(pw)) != NULL) { +#ifdef notyet + char remote_ip[NI_MAXHOST]; + + if (getnameinfo((struct sockaddr *)&his_addr, his_addr.su_len, + remote_ip, sizeof(remote_ip) - 1, NULL, 0, + NI_NUMERICHOST)) + *remote_ip = 0; + remote_ip[sizeof(remote_ip) - 1] = 0; + if (!auth_hostok(lc, remotehost, remote_ip)) { + syslog(LOG_INFO|LOG_AUTH, + "FTP LOGIN FAILED (HOST) as %s: permission denied.", + pw->pw_name); + reply(530, "Permission denied."); + pw = NULL; + return; + } + if (!auth_timeok(lc, time(NULL))) { + reply(530, "Login not available right now."); + pw = NULL; + return; + } +#endif + } + setsid(); + setusercontext(lc, pw, 0, + LOGIN_SETLOGIN|LOGIN_SETGROUP|LOGIN_SETPRIORITY| + LOGIN_SETRESOURCES|LOGIN_SETUMASK); +#else + (void) initgroups(pw->pw_name, pw->pw_gid); + /* cache groups for cmds.c::matchgroup() */ +#endif +#ifdef USE_PAM + if (pamh) { + if ((e = pam_open_session(pamh, 0)) != PAM_SUCCESS) { + syslog(LOG_ERR, "pam_open_session: %s", + pam_strerror(pamh, e)); + } else if ((e = pam_setcred(pamh, PAM_ESTABLISH_CRED)) + != PAM_SUCCESS) { + syslog(LOG_ERR, "pam_setcred: %s", + pam_strerror(pamh, e)); + } + } +#endif + gidcount = getgroups(0, NULL); + if (gidlist) + free(gidlist); + gidlist = malloc(gidcount * sizeof *gidlist); + gidcount = getgroups(gidcount, gidlist); + + /* open utmp/wtmp before chroot */ + login_utmp(ttyline, pw->pw_name, remotehost, &his_addr); + + logged_in = 1; + + 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 " LLF + " reached.", + pw->pw_name, (LLT)curclass.limit); + syslog(LOG_NOTICE, + "Maximum connection limit of " LLF + " for class %s reached, login refused for %s", + (LLT)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 explicitly 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; + } +#ifndef LOGIN_CAP + setsid(); +#ifndef __minix + setlogin(pw->pw_name); +#endif +#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; + } + } + setenv("HOME", homedir, 1); + + if (curclass.type == CLASS_GUEST && passwd[0] == '-') + quietmessages = 1; + + /* + * Display a login message, if it exists. + * N.B. reply(230,) must follow the message. + */ + if (! EMPTYSTR(curclass.motd)) + (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 defined(HAVE_SETPROCTITLE) + snprintf(proctitle, sizeof(proctitle), + "%s: anonymous/%s", remotehost, passwd); + setproctitle("%s", proctitle); +#endif /* defined(HAVE_SETPROCTITLE) */ + if (logging) + syslog(LOG_INFO, + "ANONYMOUS FTP LOGIN FROM %s, %s (class: %s, type: %s)", + remoteloghost, passwd, + curclass.classname, CURCLASSTYPE); + /* store guest password reply into pw_passwd */ + REASSIGN(pw->pw_passwd, ftpd_strdup(passwd)); + for (p = pw->pw_passwd; *p; p++) + if (!isgraph((unsigned char)*p)) + *p = '_'; + } else { + reply(230, "User %s logged in.", pw->pw_name); +#if defined(HAVE_SETPROCTITLE) + snprintf(proctitle, sizeof(proctitle), + "%s: %s", remotehost, pw->pw_name); + setproctitle("%s", proctitle); +#endif /* defined(HAVE_SETPROCTITLE) */ + if (logging) + syslog(LOG_INFO, + "FTP LOGIN FROM %s as %s (class: %s, type: %s)", + remoteloghost, pw->pw_name, + curclass.classname, CURCLASSTYPE); + } + (void) umask(curclass.umask); +#ifdef LOGIN_CAP + login_close(lc); +#endif + return; + + bad: +#ifdef LOGIN_CAP + login_close(lc); +#endif + /* Forget all about it... */ + end_login(); +} + +void +retrieve(const char *argv[], const char *name) +{ + FILE *fin, *dout; + struct stat st; + int (*closefunc)(FILE *) = NULL; + int dolog, sendrv, closerv, stderrfd, isconversion, isdata, isls; + struct timeval start, finish, td, *tdp; +#ifndef __minix + struct rusage rusage_before, rusage_after; +#endif + const char *dispname; + const char *error; + + sendrv = closerv = stderrfd = -1; + isconversion = isdata = isls = dolog = 0; + tdp = NULL; + dispname = name; + fin = dout = NULL; + error = NULL; + if (argv == NULL) { /* if not running a command ... */ + dolog = 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 (dolog) + 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))) { + error = "Not a plain file"; + reply(550, "%s: %s.", dispname, error); + 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) { + error = strerror(errno); + perror_reply(550, dispname); + goto done; + } + if (c == '\n') + i++; + } + } else if (lseek(fileno(fin), restart_point, SEEK_SET) < 0) { + error = strerror(errno); + perror_reply(550, dispname); + goto done; + } + } + dout = dataconn(dispname, st.st_size, "w"); + if (dout == NULL) + goto done; + +#ifndef __minix + (void)getrusage(RUSAGE_SELF, &rusage_before); +#endif + (void)gettimeofday(&start, NULL); + sendrv = send_data(fin, dout, &st, isdata); + (void)gettimeofday(&finish, NULL); +#ifndef __minix + (void)getrusage(RUSAGE_SELF, &rusage_after); +#endif + closedataconn(dout); /* close now to affect timing stats */ + timersub(&finish, &start, &td); + tdp = &td; + done: + if (dolog) { + logxfer("get", byte_count, name, NULL, tdp, error); +#ifndef __minix + if (tdp != NULL) + logrusage(&rusage_before, &rusage_after); +#endif + } + closerv = (*closefunc)(fin); + if (sendrv == 0) { + FILE *errf; + 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 && + ((errf = fdopen(stderrfd, "r")) != NULL)) { + char *cp, line[LINE_MAX]; + + reply(-226, "Command error messages:"); + rewind(errf); + while (fgets(line, sizeof(line), errf) != NULL) { + if ((cp = strchr(line, '\n')) != NULL) + *cp = '\0'; + reply(0, " %s", line); + } + (void) fflush(stdout); + (void) fclose(errf); + /* a reply(226,) must follow */ + } + reply(226, "Transfer complete."); + } + cleanupretrieve: + if (stderrfd != -1) + (void)close(stderrfd); + if (isconversion) + free(argv); +} + +void +store(const char *name, const char *fmode, int unique) +{ + FILE *fout, *din; + struct stat st; + int (*closefunc)(FILE *); + struct timeval start, finish, td, *tdp; + const char *desc, *error; + + din = NULL; + desc = (*fmode == 'w') ? "put" : "append"; + error = NULL; + 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) + fmode = "r+"; + fout = fopen(name, fmode); + 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) { + error = strerror(errno); + 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) { + error = strerror(errno); + perror_reply(550, name); + goto done; + } + } else if (lseek(fileno(fout), restart_point, SEEK_SET) < 0) { + error = strerror(errno); + 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); + closedataconn(din); /* close now to affect timing stats */ + timersub(&finish, &start, &td); + tdp = &td; + done: + logxfer(desc, byte_count, name, NULL, tdp, error); + (*closefunc)(fout); + cleanupstore: + ; +} + +static FILE * +getdatasock(const char *fmode) +{ + int on, s, t, tries; + in_port_t port; + + on = 1; + if (data >= 0) + return (fdopen(data, fmode)); + 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, fmode)); + 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 *fmode) +{ + char sizebuf[32]; + FILE *file; + int retry, tos, keepalive, conerrno; + + 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; + socklen_t 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; +#ifndef __minix + 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 + } +#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, fmode)); + } + if (data >= 0) { + reply(125, "Using existing data connection for '%s'%s.", + name, sizebuf); + usedefault = 1; + return (fdopen(data, fmode)); + } + if (usedefault) + data_dest = his_addr; + usedefault = 1; + retry = conerrno = 0; + do { + file = getdatasock(fmode); + 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); + conerrno = 0; + if (connect(data, (struct sockaddr *)&data_dest.si_su, + data_dest.su_len) == 0) + break; + conerrno = errno; + (void) fclose(file); + file = NULL; + data = -1; + if (conerrno == EADDRINUSE) { + sleep((unsigned) swaitint); + retry += swaitint; + } else { + break; + } + } while (retry <= swaitmax); + if (conerrno != 0) { + perror_reply(425, "Can't build data connection"); + 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) + return; + (void)fclose(fd); + data = -1; + if (pdata >= 0) + (void)close(pdata); + pdata = -1; +} + +int +write_data(int fd, char *buf, size_t size, off_t *bufrem, + struct timeval *then, int isdata) +{ + struct timeval now, td; + ssize_t c; + + while (size > 0) { + c = size; + if (curclass.writesize) { + if (curclass.writesize < c) + c = curclass.writesize; + } + if (curclass.rateget) { + if (*bufrem < c) + c = *bufrem; + } + (void) alarm(curclass.timeout); + c = write(fd, buf, c); + if (c <= 0) + return (1); + buf += c; + size -= c; + byte_count += c; + if (isdata) { + total_data_out += c; + total_data += c; + } + total_bytes_out += c; + total_bytes += c; + if (curclass.rateget) { + *bufrem -= c; + if (*bufrem == 0) { + (void)gettimeofday(&now, NULL); + timersub(&now, then, &td); + if (td.tv_sec == 0) { + usleep(1000000 - td.tv_usec); + (void)gettimeofday(then, NULL); + } else + *then = now; + *bufrem = curclass.rateget; + } + } + } + return (0); +} + +static enum send_status +send_data_with_read(int filefd, int netfd, const struct stat *st, int isdata) +{ + struct timeval then; + off_t bufrem; + ssize_t readsize; + char *buf; + int c, error; + + if (curclass.readsize > 0) + readsize = curclass.readsize; + else + readsize = st->st_blksize; + if ((buf = malloc(readsize)) == NULL) { + perror_reply(451, "Local resource failure: malloc"); + return (SS_NO_TRANSFER); + } + + if (curclass.rateget) { + bufrem = curclass.rateget; + (void)gettimeofday(&then, NULL); + } else + bufrem = readsize; + while (1) { + (void) alarm(curclass.timeout); + c = read(filefd, buf, readsize); + if (c == 0) + error = SS_SUCCESS; + else if (c < 0) + error = SS_FILE_ERROR; + else if (write_data(netfd, buf, c, &bufrem, &then, isdata)) + error = SS_DATA_ERROR; + else if (urgflag && handleoobcmd()) + error = SS_ABORTED; + else + continue; + + free(buf); + return (error); + } +} + +static enum send_status +send_data_with_mmap(int filefd, int netfd, const struct stat *st, int isdata) +{ + struct timeval then; + off_t bufrem, filesize, off, origoff; + ssize_t mapsize, winsize; + int error, sendbufsize, sendlowat; + void *win; + + bufrem = 0; + if (curclass.sendbufsize) { + sendbufsize = curclass.sendbufsize; + if (setsockopt(netfd, SOL_SOCKET, SO_SNDBUF, + &sendbufsize, sizeof(int)) == -1) + syslog(LOG_WARNING, "setsockopt(SO_SNDBUF, %d): %m", + sendbufsize); + } + + if (curclass.sendlowat) { + sendlowat = curclass.sendlowat; + if (setsockopt(netfd, SOL_SOCKET, SO_SNDLOWAT, + &sendlowat, sizeof(int)) == -1) + syslog(LOG_WARNING, "setsockopt(SO_SNDLOWAT, %d): %m", + sendlowat); + } + + winsize = curclass.mmapsize; + filesize = st->st_size; + if (ftpd_debug) + syslog(LOG_INFO, "mmapsize = " LLF ", writesize = " LLF, + (LLT)winsize, (LLT)curclass.writesize); + if (winsize <= 0) + goto try_read; + + off = lseek(filefd, (off_t)0, SEEK_CUR); + if (off == -1) + goto try_read; + + origoff = off; + if (curclass.rateget) { + bufrem = curclass.rateget; + (void)gettimeofday(&then, NULL); + } else + bufrem = winsize; +#ifndef __minix + while (1) { + mapsize = MIN(filesize - off, winsize); + if (mapsize == 0) + break; + win = mmap(NULL, mapsize, PROT_READ, + MAP_FILE|MAP_SHARED, filefd, off); + if (win == MAP_FAILED) { + if (off == origoff) + goto try_read; + return (SS_FILE_ERROR); + } + (void) madvise(win, mapsize, MADV_SEQUENTIAL); + error = write_data(netfd, win, mapsize, &bufrem, &then, + isdata); + (void) madvise(win, mapsize, MADV_DONTNEED); + munmap(win, mapsize); + if (urgflag && handleoobcmd()) + return (SS_ABORTED); + if (error) + return (SS_DATA_ERROR); + off += mapsize; + } + return (SS_SUCCESS); + +#endif + try_read: + return (send_data_with_read(filefd, netfd, st, isdata)); +} + +/* + * Transfer the contents of "instr" to "outstr" peer using the appropriate + * encapsulation of the data subject to Mode, Structure, and Type. + * + * NB: Form isn't handled. + */ +static int +send_data(FILE *instr, FILE *outstr, const struct stat *st, int isdata) +{ + int c, filefd, netfd, rval; + + urgflag = 0; + transflag = 1; + rval = -1; + + switch (type) { + + case TYPE_A: + /* XXXLUKEM: rate limit ascii send (get) */ + (void) alarm(curclass.timeout); + while ((c = getc(instr)) != EOF) { + if (urgflag && handleoobcmd()) + goto cleanup_send_data; + 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: + filefd = fileno(instr); + netfd = fileno(outstr); + switch (send_data_with_mmap(filefd, netfd, st, isdata)) { + + case SS_SUCCESS: + break; + + case SS_ABORTED: + case SS_NO_TRANSFER: + goto cleanup_send_data; + + case SS_FILE_ERROR: + goto file_err; + + case SS_DATA_ERROR: + goto data_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"); + goto cleanup_send_data; + + cleanup_send_data: + (void) alarm(0); + transflag = 0; + urgflag = 0; + 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, netfd, filefd, rval; + int volatile bare_lfs; + off_t byteswritten; + char *buf; + ssize_t readsize; + struct sigaction sa, sa_saved; + struct stat st; + + memset(&sa, 0, sizeof(sa)); + sigfillset(&sa.sa_mask); + sa.sa_flags = SA_RESTART; + sa.sa_handler = lostconn; + (void) sigaction(SIGALRM, &sa, &sa_saved); + + bare_lfs = 0; + urgflag = 0; + transflag = 1; + rval = -1; + byteswritten = 0; + buf = NULL; + +#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.readsize) + readsize = curclass.readsize; + else if (fstat(filefd, &st)) + readsize = (ssize_t)st.st_blksize; + else + readsize = BUFSIZ; + if ((buf = malloc(readsize)) == NULL) { + perror_reply(451, "Local resource failure: malloc"); + goto cleanup_recv_data; + } + 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(readsize, bufrem))) <= 0) + goto recvdone; + if (urgflag && handleoobcmd()) + goto cleanup_recv_data; + 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, readsize)) > 0) { + if (urgflag && handleoobcmd()) + goto cleanup_recv_data; + 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) { + if (urgflag && handleoobcmd()) + goto cleanup_recv_data; + 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); + (void) sigaction(SIGALRM, &sa_saved, NULL); + if (buf) + free(buf); + transflag = 0; + urgflag = 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]; + unsigned char *a, *p; + int ispassive, af; + off_t otbi, otbo, otb; + + a = p = NULL; + + reply(-211, "%s FTP server status:", hostname); + reply(0, "Version: %s", EMPTYSTR(version) ? "" : 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) { + su = (struct sockinet *)&data_dest; + + if (epsvall) { + reply(0, "EPSV only mode (EPSV ALL)"); + goto epsvonly; + } + printaddr: + /* PASV/PORT */ + if (su->su_family == AF_INET) { + a = (unsigned char *) &su->su_addr; + p = (unsigned 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, i; + + alen = 0; + switch (su->su_family) { + case AF_INET: + a = (unsigned char *) &su->su_addr; + p = (unsigned char *) &su->su_port; + alen = sizeof(su->su_addr); + af = 4; + break; +#ifdef INET6 + case AF_INET6: + a = (unsigned char *) &su->su_6addr; + p = (unsigned 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 && !CURCLASS_FLAGS_ISSET(private)) { + 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: " LLF ", maximum timeout: " LLF, + (LLT)curclass.timeout, (LLT)curclass.maxtimeout); + reply(0, "Current connections: %d", connections); + if (curclass.limit == -1) + reply(0, "Maximum connections: unlimited"); + else + reply(0, "Maximum connections: " LLF, + (LLT)curclass.limit); + if (curclass.limitfile) + reply(0, "Connection limit exceeded message file: %s", + conffilename(curclass.limitfile)); + if (! EMPTYSTR(curclass.chroot)) + reply(0, "Chroot format: %s", curclass.chroot); + reply(0, "Deny bad ftpusers(5) quickly: %sabled", + CURCLASS_FLAGS_ISSET(denyquick) ? "en" : "dis"); + 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", conffilename(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: " LLF " - " LLF, + (LLT)curclass.portmin, (LLT)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"); + if (curclass.mmapsize) + reply(0, "Mmap size: " LLF, (LLT)curclass.mmapsize); + else + reply(0, "Mmap size: disabled"); + if (curclass.readsize) + reply(0, "Read size: " LLF, (LLT)curclass.readsize); + else + reply(0, "Read size: default"); + if (curclass.writesize) + reply(0, "Write size: " LLF, (LLT)curclass.writesize); + else + reply(0, "Write size: default"); + if (curclass.recvbufsize) + reply(0, "Receive buffer size: " LLF, + (LLT)curclass.recvbufsize); + else + reply(0, "Receive buffer size: default"); + if (curclass.sendbufsize) + reply(0, "Send buffer size: " LLF, + (LLT)curclass.sendbufsize); + else + reply(0, "Send buffer size: default"); + if (curclass.sendlowat) + reply(0, "Send low water mark: " LLF, + (LLT)curclass.sendlowat); + else + reply(0, "Send low water mark: default"); + 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, ...) +{ + char msg[MAXPATHLEN * 2 + 100]; + size_t b; + va_list ap; + + b = 0; + if (n == 0) + b = snprintf(msg, sizeof(msg), " "); + else if (n < 0) + b = snprintf(msg, sizeof(msg), "%d-", -n); + else + b = snprintf(msg, sizeof(msg), "%d ", n); + va_start(ap, fmt); + vsnprintf(msg + b, sizeof(msg) - b, fmt, ap); + va_end(ap); + cprintf(stdout, "%s\r\n", msg); + (void)fflush(stdout); + if (ftpd_debug) + syslog(LOG_DEBUG, "<--- %s", msg); +} + +static void +logremotehost(struct sockinet *who) +{ + +#if defined(HAVE_SOCKADDR_SNPRINTF) + char abuf[BUFSIZ]; +#endif + + struct sockaddr *sa = (struct sockaddr *)&who->si_su; + if (getnameinfo(sa, who->su_len, remotehost, sizeof(remotehost), NULL, + 0, getnameopts)) + strlcpy(remotehost, "?", sizeof(remotehost)); +#if defined(HAVE_SOCKADDR_SNPRINTF) + sockaddr_snprintf(abuf, sizeof(abuf), "%a", sa); + snprintf(remoteloghost, sizeof(remoteloghost), "%s(%s)", remotehost, + abuf); +#else + strlcpy(remoteloghost, remotehost, sizeof(remoteloghost)); +#endif + +#if defined(HAVE_SETPROCTITLE) + snprintf(proctitle, sizeof(proctitle), "%s: connected", remotehost); + setproctitle("%s", proctitle); +#endif /* defined(HAVE_SETPROCTITLE) */ + if (logging) + syslog(LOG_INFO, "connection from %s to %s", + remoteloghost, hostname); +} + +/* + * Record logout in wtmp file and exit with supplied status. + * NOTE: because this is called from signal handlers it cannot + * use stdio (or call other functions that use stdio). + */ +void +dologout(int status) +{ + /* + * Prevent reception of SIGURG from resulting in a resumption + * back to the main program loop. + */ + transflag = 0; + logout_utmp(); + if (logged_in) { +#ifdef KERBEROS + if (!notickets && krbtkfile_env) + unlink(krbtkfile_env); +#endif + } + /* beware of flushing buffers after a SIGPIPE */ + if (xferlogfd != -1) + close(xferlogfd); + _exit(status); +} + +void +abor(void) +{ + + if (!transflag) + return; + tmpline[0] = '\0'; + is_oob = 0; + reply(426, "Transfer aborted. Data connection closed."); + reply(226, "Abort successful"); + transflag = 0; /* flag that the transfer has aborted */ +} + +void +statxfer(void) +{ + + if (!transflag) + return; + 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)); +} + +/* + * Call when urgflag != 0 to handle Out Of Band commands. + * Returns non zero if the OOB command aborted the transfer + * by setting transflag to 0. (c.f., "ABOR"). + */ +static int +handleoobcmd(void) +{ + char *cp; + int ret; + + if (!urgflag) + return (0); + urgflag = 0; + /* only process if transfer occurring */ + if (!transflag) + return (0); + cp = tmpline; + ret = get_line(cp, sizeof(tmpline)-1, stdin); + if (ret == -1) { + reply(221, "You could at least say goodbye."); + dologout(0); + } else if (ret == -2) { + /* Ignore truncated command */ + /* XXX: abort xfer with "500 command too long", & return 1 ? */ + return 0; + } + /* + * Manually parse OOB commands, because we can't + * recursively call the yacc parser... + */ + if (strcasecmp(cp, "ABOR\r\n") == 0) { + abor(); + } else if (strcasecmp(cp, "STAT\r\n") == 0) { + statxfer(); + } else { + /* XXX: error with "500 unknown command" ? */ + } + return (transflag == 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) +{ + socklen_t len; + int recvbufsize; + 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 (curclass.recvbufsize) { + recvbufsize = curclass.recvbufsize; + if (setsockopt(pdata, SOL_SOCKET, SO_RCVBUF, &recvbufsize, + sizeof(int)) == -1) + syslog(LOG_WARNING, "setsockopt(SO_RCVBUF, %d): %m", + recvbufsize); + } + 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(const char *cmd, int pf) +{ + socklen_t 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 = ftpd_strdup(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 */ + errno = 0; + p = NULL; + (void)strtoul(result[2], &p, 10); + if (errno || !*result[2] || *p) + goto parsefail; + errno = 0; + p = NULL; + proto = strtoul(result[0], &p, 10); + if (errno || !*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); + 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[] = { + NULL, + 0 +}; + +void +send_file_list(const char *whichf) +{ + struct stat st; + DIR *dirp; + struct dirent *dir; + FILE *volatile dout; + char **volatile dirlist; + char *dirname, *p; + char *notglob; + int volatile simple; + int volatile freeglob; + glob_t gl; + + dirp = NULL; + dout = NULL; + notglob = NULL; + simple = 0; + freeglob = 0; + urgflag = 0; + + 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(450, "Not found"); + goto cleanup_send_file_list; + } else if (gl.gl_pathc == 0) { + errno = ENOENT; + perror_reply(450, whichf); + goto cleanup_send_file_list; + } + dirlist = gl.gl_pathv; + } else { + notglob = ftpd_strdup(whichf); + onefile[0] = notglob; + dirlist = onefile; + simple = 1; + } + /* XXX: } for vi sm */ + + 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) { + const char *argv[] = { INTERNAL_LS, "", NULL }; + + argv[1] = dirname; + retrieve(argv, dirname); + goto cleanup_send_file_list; + } + perror_reply(450, 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 cleanup_send_file_list; + transflag = 1; + } + 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 (urgflag && handleoobcmd()) + goto cleanup_send_file_list; + + 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))) { + if (dout == NULL) { + dout = dataconn("file list", (off_t)-1, + "w"); + if (dout == NULL) + goto cleanup_send_file_list; + transflag = 1; + } + 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(450, "No files found."); + else if (ferror(dout) != 0) + perror_reply(451, "Data connection"); + else + reply(226, "Transfer complete."); + + cleanup_send_file_list: + closedataconn(dout); + transflag = 0; + urgflag = 0; + total_xfers++; + total_xfers_out++; + if (notglob) + free(notglob); + 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 " = bytes" + * else if file2 != NULL " " + * else " " + * if elapsed != NULL, append "in xxx.yyy seconds" + * if error != NULL, append ": " + error + * + * if doxferlog != 0, bytes != -1, and command is "get", "put", + * or "append", syslog and/or write 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]; + char realfile1[MAXPATHLEN], realfile2[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, realfile1)) == NULL) + r1 = file1; + if (file2 != NULL) + if ((r2 = realpath(file2, realfile2)) == 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 " LLF ".%.03ld seconds", + (LLT)elapsed->tv_sec, + (long)(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 || bytes == -1) + return; + + if (strcmp(command, "get") == 0) + direction = 'o'; + else if (strcmp(command, "put") == 0 || strcmp(command, "append") == 0) + direction = 'i'; + else + return; + + time(&now); + len = snprintf(buf, sizeof(buf), + "%.24s " LLF " %s " LLF " %s %c %s %c %c %s FTP 0 * %c\n", + +/* + * 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. + */ + ctime(&now), + (LLT) + (elapsed == NULL ? 0 : elapsed->tv_sec + (elapsed->tv_usec > 0)), + remotehost, + (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' + ); + + if ((doxferlog & 2) && xferlogfd != -1) + write(xferlogfd, buf, len); + if ((doxferlog & 1)) { + buf[len-1] = '\n'; /* strip \n from syslog message */ + syslog(LOG_INFO, "xferlog: %s", buf); + } +} + +#ifndef __minix +/* + * Log the resource usage. + * + * XXX: more resource usage to logging? + */ +void +logrusage(const struct rusage *rusage_before, + const struct rusage *rusage_after) +{ + struct timeval usrtime, systime; + + if (logging <= 1) + return; + + timersub(&rusage_after->ru_utime, &rusage_before->ru_utime, &usrtime); + timersub(&rusage_after->ru_stime, &rusage_before->ru_stime, &systime); + syslog(LOG_INFO, LLF ".%.03ldu " LLF ".%.03lds %ld+%ldio %ldpf+%ldw", + (LLT)usrtime.tv_sec, (long)(usrtime.tv_usec / 1000), + (LLT)systime.tv_sec, (long)(systime.tv_usec / 1000), + rusage_after->ru_inblock - rusage_before->ru_inblock, + rusage_after->ru_oublock - rusage_before->ru_oublock, + rusage_after->ru_majflt - rusage_before->ru_majflt, + rusage_after->ru_nswap - rusage_before->ru_nswap); +} +#endif + +/* + * 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 *pwent, const char *password) +{ + const char *orig; + char *new; + time_t change, expire, now; + + change = expire = 0; + if (pwent == NULL) + return 1; + + time(&now); + orig = pwent->pw_passwd; /* save existing password */ + expire = pwent->pw_expire; + change = pwent->pw_change; + if (change == _PASSWORD_CHGNOW) + change = now; + + 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 && now >= expire) || (change && now >= change)) + return 2; /* check if expired */ + + return 0; /* OK! */ +} + +char * +ftpd_strdup(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); + va_end(ap); + total_bytes += b; + total_bytes_out += b; +} + +#ifdef USE_PAM +/* + * the following code is stolen from imap-uw PAM authentication module and + * login.c + */ +typedef struct { + const char *uname; /* user name */ + int triedonce; /* if non-zero, tried before */ +} ftpd_cred_t; + +static int +auth_conv(int num_msg, const struct pam_message **msg, + struct pam_response **resp, void *appdata) +{ + int i, ret; + size_t n; + ftpd_cred_t *cred = (ftpd_cred_t *) appdata; + struct pam_response *myreply; + char pbuf[FTP_BUFLEN]; + + if (num_msg <= 0 || num_msg > PAM_MAX_NUM_MSG) + return (PAM_CONV_ERR); + myreply = calloc(num_msg, sizeof *myreply); + if (myreply == NULL) + return PAM_BUF_ERR; + + for (i = 0; i < num_msg; i++) { + myreply[i].resp_retcode = 0; + myreply[i].resp = NULL; + switch (msg[i]->msg_style) { + case PAM_PROMPT_ECHO_ON: /* user */ + myreply[i].resp = ftpd_strdup(cred->uname); + /* PAM frees resp. */ + break; + case PAM_PROMPT_ECHO_OFF: /* authtok (password) */ + /* + * Only send a single 331 reply and + * then expect a PASS. + */ + if (cred->triedonce) { + syslog(LOG_ERR, + "auth_conv: already performed PAM_PROMPT_ECHO_OFF"); + goto fail; + } + cred->triedonce++; + if (msg[i]->msg[0] == '\0') { + (void)strlcpy(pbuf, "password", sizeof(pbuf)); + } else { + /* Uncapitalize msg */ + (void)strlcpy(pbuf, msg[i]->msg, sizeof(pbuf)); + if (isupper((unsigned char)pbuf[0])) + pbuf[0] = tolower( + (unsigned char)pbuf[0]); + /* Remove trailing ':' and whitespace */ + n = strlen(pbuf); + while (n-- > 0) { + if (isspace((unsigned char)pbuf[n]) || + pbuf[n] == ':') + pbuf[n] = '\0'; + else + break; + } + } + /* Send reply, wait for a response. */ + reply(331, "User %s accepted, provide %s.", + cred->uname, pbuf); + (void) alarm(curclass.timeout); + ret = get_line(pbuf, sizeof(pbuf)-1, stdin); + (void) alarm(0); + if (ret == -1) { + reply(221, "You could at least say goodbye."); + dologout(0); + } else if (ret == -2) { + /* XXX: should we do this reply(-530, ..) ? */ + reply(-530, "Command too long."); + goto fail; + } + /* Ensure it is PASS */ + if (strncasecmp(pbuf, "PASS ", 5) != 0) { + syslog(LOG_ERR, + "auth_conv: unexpected reply '%.4s'", pbuf); + /* XXX: should we do this reply(-530, ..) ? */ + reply(-530, "Unexpected reply '%.4s'.", pbuf); + goto fail; + } + /* Strip CRLF from "PASS" reply */ + n = strlen(pbuf); + while (--n >= 5 && + (pbuf[n] == '\r' || pbuf[n] == '\n')) + pbuf[n] = '\0'; + /* Copy password into reply */ + myreply[i].resp = ftpd_strdup(pbuf+5); + /* PAM frees resp. */ + break; + case PAM_TEXT_INFO: + case PAM_ERROR_MSG: + break; + default: /* unknown message style */ + goto fail; + } + } + + *resp = myreply; + return PAM_SUCCESS; + + fail: + free(myreply); + *resp = NULL; + return PAM_CONV_ERR; +} + +/* + * Attempt to authenticate the user using PAM. Returns 0 if the user is + * authenticated, or 1 if not authenticated. If some sort of PAM system + * error occurs (e.g., the "/etc/pam.conf" file is missing) then this + * function returns -1. This can be used as an indication that we should + * fall back to a different authentication mechanism. + * pw maybe be updated to a new user if PAM_USER changes from curname. + */ +static int +auth_pam(void) +{ + const char *tmpl_user; + const void *item; + int rval; + int e; + ftpd_cred_t auth_cred = { curname, 0 }; + struct pam_conv conv = { &auth_conv, &auth_cred }; + + e = pam_start("ftpd", curname, &conv, &pamh); + if (e != PAM_SUCCESS) { + /* + * In OpenPAM, it's OK to pass NULL to pam_strerror() + * if context creation has failed in the first place. + */ + syslog(LOG_ERR, "pam_start: %s", pam_strerror(NULL, e)); + return -1; + } + + e = pam_set_item(pamh, PAM_RHOST, remotehost); + if (e != PAM_SUCCESS) { + syslog(LOG_ERR, "pam_set_item(PAM_RHOST): %s", + pam_strerror(pamh, e)); + if ((e = pam_end(pamh, e)) != PAM_SUCCESS) { + syslog(LOG_ERR, "pam_end: %s", pam_strerror(pamh, e)); + } + pamh = NULL; + return -1; + } + + e = pam_set_item(pamh, PAM_SOCKADDR, &his_addr); + if (e != PAM_SUCCESS) { + syslog(LOG_ERR, "pam_set_item(PAM_SOCKADDR): %s", + pam_strerror(pamh, e)); + if ((e = pam_end(pamh, e)) != PAM_SUCCESS) { + syslog(LOG_ERR, "pam_end: %s", pam_strerror(pamh, e)); + } + pamh = NULL; + return -1; + } + + e = pam_authenticate(pamh, 0); + if (ftpd_debug) + syslog(LOG_DEBUG, "pam_authenticate: user '%s' returned %d", + curname, e); + switch (e) { + case PAM_SUCCESS: + /* + * With PAM we support the concept of a "template" + * user. The user enters a login name which is + * authenticated by PAM, usually via a remote service + * such as RADIUS or TACACS+. If authentication + * succeeds, a different but related "template" name + * is used for setting the credentials, shell, and + * home directory. The name the user enters need only + * exist on the remote authentication server, but the + * template name must be present in the local password + * database. + * + * This is supported by two various mechanisms in the + * individual modules. However, from the application's + * point of view, the template user is always passed + * back as a changed value of the PAM_USER item. + */ + if ((e = pam_get_item(pamh, PAM_USER, &item)) == + PAM_SUCCESS) { + tmpl_user = (const char *) item; + if (pw == NULL + || strcmp(pw->pw_name, tmpl_user) != 0) { + pw = sgetpwnam(tmpl_user); + if (ftpd_debug) + syslog(LOG_DEBUG, + "auth_pam: PAM changed " + "user from '%s' to '%s'", + curname, pw->pw_name); + (void)strlcpy(curname, pw->pw_name, + curname_len); + } + } else + syslog(LOG_ERR, "Couldn't get PAM_USER: %s", + pam_strerror(pamh, e)); + rval = 0; + break; + + case PAM_AUTH_ERR: + case PAM_USER_UNKNOWN: + case PAM_MAXTRIES: + rval = 1; + break; + + default: + syslog(LOG_ERR, "pam_authenticate: %s", pam_strerror(pamh, e)); + rval = -1; + break; + } + + if (rval == 0) { + e = pam_acct_mgmt(pamh, 0); + if (e != PAM_SUCCESS) { + syslog(LOG_ERR, "pam_acct_mgmt: %s", + pam_strerror(pamh, e)); + rval = 1; + } + } + + if (rval != 0) { + if ((e = pam_end(pamh, e)) != PAM_SUCCESS) { + syslog(LOG_ERR, "pam_end: %s", pam_strerror(pamh, e)); + } + pamh = NULL; + } + return rval; +} + +#endif /* USE_PAM */ diff --git a/libexec/ftpd/ftpd.conf.5 b/libexec/ftpd/ftpd.conf.5 new file mode 100644 index 000000000..1a8da295f --- /dev/null +++ b/libexec/ftpd/ftpd.conf.5 @@ -0,0 +1,738 @@ +.\" $NetBSD: ftpd.conf.5,v 1.37 2009/04/09 02:25:45 joerg Exp $ +.\" +.\" Copyright (c) 1997-2008 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. +.\" +.\" 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 April 13, 2007 +.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 Ns 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 , +unless the +.Sy private +directive is set for the class. +.Pp +Each configuration line may be one of: +.Bl -tag -width 4n +.It Sy advertize Ar class Op 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 +.Ar host +not is specified, 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, especially for sites concerned +with potential security problems with +.Tn FTP +bounce attacks. +If +.Ar class +is +.Dq none +or +.Sy off +is specified, disable this feature, otherwise enable it. +.It Sy chroot Ar class Op Sy pathformat +If +.Ar pathformat +is not specified or +.Ar class +is +.Dq none , +use the default behavior (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 specified, 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 Sy conversion Ar class Ar suffix Op Ar "type disable command" +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 file types 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 denyquick Ar class Op Sy off +Enforce +.Xr ftpusers 5 +rules after the +.Sy USER +command is received, rather than after the +.Sy PASS +command is received. +Whilst enabling this feature may allow information leakage about +available accounts (for example, if you allow some users of a +.Sy REAL +or +.Sy CHROOT +class but not others), it is useful in preventing a denied user +(such as +.Sq root ) +from entering their password across an insecure connection. +This option is +.Em strongly +recommended for servers which run an anonymous-only service. +If +.Ar class +is +.Dq none +or +.Sy off +is specified, disable this feature, otherwise enable it. +.It Sy display Ar class Op Ar file +If +.Ar file +is not specified 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 hidesymlinks Ar class Op Sy off +If +.Ar class +is +.Dq none +or +.Sy off +is specified, disable this feature. +Otherwise, the +.Sy LIST +command lists symbolic links as the file or directory the link +references +.Pq Dq Li "ls -LlA" . +Servers which run an anonymous service may wish to enable this +feature for +.Sy GUEST +users, so that symbolic links do not leak names in +directories that are not searchable by +.Sy GUEST +users. +.It Sy homedir Ar class Op Sy pathformat +If +.Ar pathformat +is not specified or +.Ar class +is +.Dq none , +use the default behavior (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 Sy limit Ar class Op Ar count Op Ar file +Limit the maximum number of concurrent connections for +.Ar class +to +.Ar count , +with +.Sq \-1 +meaning unlimited connections. +If the limit is exceeded and +.Ar file +is specified, 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 Op Ar size +Set the maximum size of an uploaded file to +.Ar size , +with +.Sq \-1 +meaning unlimited connections. +If +.Ar class +is +.Dq none +or +.Ar size +is not specified, disable this. +.It Sy maxtimeout Ar class Op 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, use the default. +.It Sy mmapsize Ar class Op Ar size +Set the size of the sliding window to map a file using +.Xr mmap 2 . +If zero, +.Xr ftpd 8 +will use +.Xr read 2 +instead. +The default is zero. +This option affects only binary transfers. +If +.Ar class +is +.Dq none +or +.Ar size +is not specified, use the default. +.It Sy modify Ar class Op Sy off +If +.Ar class +is +.Dq none +or +.Sy off +is specified, 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 specified 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 specified 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 specified, prevent passive +.Sy ( PASV , +.Sy LPSV , +and +.Sy EPSV ) +connections. +Otherwise, enable them. +.It Sy portrange Ar class Op 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 specified, disable this. +.It Sy private Ar class Op Sy off +If +.Ar class +is +.Dq none +or +.Sy off +is specified, do not display class information in the output of the +.Sy STAT +command. +Otherwise, display the information. +.It Sy rateget Ar class Op 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 +.Ar rate +is not specified, disable this. +.It Sy rateput Ar class Op Ar rate +Set the maximum put +.Pq Sy STOR +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 +.Ar rate +is not specified, disable this. +.It Sy readsize Ar class Op Ar size +Set the size of the read buffer to +.Xr read 2 +a file. +The default is the file system block size. +This option affects only binary transfers. +If +.Ar class +is +.Dq none +or +.Ar size +is not specified, use the default. +.It Sy recvbufsize Ar class Op Ar size +Set the size of the socket receive buffer. +The default is zero and the system default value will be used. +This option affects only passive transfers. +If +.Ar class +is +.Dq none +or +.Ar size +is not specified, use the default. +.It Sy sanenames Ar class Op Sy off +If +.Ar class +is +.Dq none +or +.Sy off +is specified, 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 sendbufsize Ar class Op Ar size +Set the size of the socket send buffer. +The default is zero and the system default value will be used. +This option affects only binary transfers. +If +.Ar class +is +.Dq none +or +.Ar size +is not specified, use the default. +.It Sy sendlowat Ar class Op Ar size +Set the low water mark of socket send buffer. +The default is zero and system default value will be used. +This option affects only for binary transfer. +If +.Ar class +is +.Dq none +or +.Ar size +is not specified, use the default. +.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 specified, disable the template for +.Ar class . +.It Sy timeout Ar class Op 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, use the default. +.It Sy umask Ar class Op 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 specified, 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. +.It Sy writesize Ar class Op Ar size +Limit the number of bytes to +.Xr write 2 +at a time. +The default is zero, which means all the data available as a result of +.Xr mmap 2 +or +.Xr read 2 +will be written at a time. +This option affects only binary transfers. +If +.Ar class +is +.Dq none +or +.Ar size +is not specified, use the default. +.El +.Ss Numeric argument suffix parsing +Where command arguments are numeric, a decimal number is expected. +Two or more numbers may be separated by an +.Dq x +to indicate a product. +Each number may have one of the following optional suffixes: +.Bl -tag -width 3n -offset indent -compact +.It b +Block; multiply by 512 +.It k +Kibi; multiply by 1024 (1 KiB) +.It m +Mebi; multiply by 1048576 (1 MiB) +.It g +Gibi; multiply by 1073741824 (1 GiB) +.It t +Tebi; multiply by 1099511627776 (1 TiB) +.It w +Word; multiply by the number of bytes in an integer +.El +.Pp +See +.Xr strsuftoll 3 +for more information. +.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 strsuftoll 3 , +.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/libexec/ftpd/ftpusers.5 b/libexec/ftpd/ftpusers.5 new file mode 100644 index 000000000..285f385f3 --- /dev/null +++ b/libexec/ftpd/ftpusers.5 @@ -0,0 +1,179 @@ +.\" $NetBSD: ftpusers.5,v 1.17 2008/09/13 02:41:52 lukem Exp $ +.\" +.\" Copyright (c) 1997-2008 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. +.\" +.\" 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 an +.Xr fnmatch 3 +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-compatible 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-compatible with the old syntax. +.Sh FILES +.Bl -tag -width /usr/share/examples/ftpd/ftpusers -compact +.It Pa /etc/ftpchroot +List of normal users who should have their ftp session's root directory +changed by using +.Xr chroot 2 . +.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/libexec/ftpd/logutmp.c b/libexec/ftpd/logutmp.c new file mode 100644 index 000000000..eacd9eb8e --- /dev/null +++ b/libexec/ftpd/logutmp.c @@ -0,0 +1,172 @@ +/* $NetBSD: logutmp.c,v 1.12 2011/09/16 16:13:17 plunky Exp $ */ + +/* + * Portions Copyright (c) 1988, 1993 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +/* + * 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. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS + * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR(S) 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 +#ifndef lint +__RCSID("$NetBSD: logutmp.c,v 1.12 2011/09/16 16:13:17 plunky Exp $"); +#endif /* not lint */ + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#ifdef SUPPORT_UTMPX +#include +#endif +#include + +#include "extern.h" + +#ifdef SUPPORT_UTMP +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 +ftpd_login(const struct 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() != 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 +ftpd_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); +} +#endif /* SUPPORT_UTMP */ + +#ifdef SUPPORT_UTMPX +/* + * special version of loginx which updates utmpx only. + */ +void +ftpd_loginx(const struct utmpx *ut) +{ + (void)pututxline(ut); +} + +int +ftpd_logoutx(const char *line, int status, int mode) +{ + return logoutx(line, status, mode); +} +#endif diff --git a/libexec/ftpd/logwtmp.c b/libexec/ftpd/logwtmp.c new file mode 100644 index 000000000..50135514a --- /dev/null +++ b/libexec/ftpd/logwtmp.c @@ -0,0 +1,145 @@ +/* $NetBSD: logwtmp.c,v 1.25 2006/09/23 16:03:50 xtraeme 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. 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 +#ifndef lint +#if 0 +static char sccsid[] = "@(#)logwtmp.c 8.1 (Berkeley) 6/4/93"; +#else +__RCSID("$NetBSD: logwtmp.c,v 1.25 2006/09/23 16:03:50 xtraeme Exp $"); +#endif +#endif /* not lint */ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#ifdef SUPPORT_UTMP +#include +#endif +#ifdef SUPPORT_UTMPX +#include +#endif +#include + +#ifdef KERBEROS5 +#include +#endif + +#include "extern.h" + +#ifdef SUPPORT_UTMP +static int fd = -1; + +void +ftpd_initwtmp(void) +{ + const char *wf = _PATH_WTMP; + if ((fd = open(wf, O_WRONLY|O_APPEND, 0)) == -1) + syslog(LOG_ERR, "Cannot open `%s' (%m)", wf); +} + +/* + * 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 +ftpd_logwtmp(const char *line, const char *name, const char *host) +{ + struct utmp ut; + struct stat buf; + + if (fd < 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); + } +} +#endif + +#ifdef SUPPORT_UTMPX +static int fdx = -1; + +void +ftpd_initwtmpx(void) +{ + const char *wf = _PATH_WTMPX; + if ((fdx = open(wf, O_WRONLY|O_APPEND, 0)) == -1) + syslog(LOG_ERR, "Cannot open `%s' (%m)", wf); +} + +void +ftpd_logwtmpx(const char *line, const char *name, const char *host, + struct sockinet *haddr, int status, int utx_type) +{ + struct utmpx ut; + struct stat buf; + + if (fdx < 0) + return; + if (fstat(fdx, &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)); + if (haddr) + (void)memcpy(&ut.ut_ss, &haddr->si_su, haddr->su_len); + else + (void)memset(&ut.ut_ss, 0, sizeof(ut.ut_ss)); + ut.ut_type = utx_type; + if (WIFEXITED(status)) + ut.ut_exit.e_exit = (uint16_t)WEXITSTATUS(status); + if (WIFSIGNALED(status)) + ut.ut_exit.e_termination = (uint16_t)WTERMSIG(status); + (void)gettimeofday(&ut.ut_tv, NULL); + if(write(fdx, (char *)&ut, sizeof(struct utmpx)) != + sizeof(struct utmpx)) + (void)ftruncate(fdx, buf.st_size); + } +} +#endif diff --git a/libexec/ftpd/pathnames.h b/libexec/ftpd/pathnames.h new file mode 100644 index 000000000..9e127bb9f --- /dev/null +++ b/libexec/ftpd/pathnames.h @@ -0,0 +1,48 @@ +/* $NetBSD: pathnames.h,v 1.12 2004/12/11 18:37:26 christos 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. 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 + */ + +#include + +#ifndef _DEFAULT_CONFDIR +#define _DEFAULT_CONFDIR "/etc" +#endif + +#define _NAME_FTPCHROOT "ftpchroot" +#define _NAME_FTPDCONF "ftpd.conf" +#define _NAME_FTPLOGINMESG "motd" +#define _NAME_FTPUSERS "ftpusers" +#define _NAME_FTPWELCOME "ftpwelcome" + +#define _PATH_CLASSPIDS "/var/run/ftpd.pids-" + +#define TMPFILE "/tmp/ftpdXXXXXXX" diff --git a/libexec/ftpd/popen.c b/libexec/ftpd/popen.c new file mode 100644 index 000000000..ed52dc094 --- /dev/null +++ b/libexec/ftpd/popen.c @@ -0,0 +1,245 @@ +/* $NetBSD: popen.c,v 1.37 2010/03/20 18:23:30 christos Exp $ */ + +/*- + * Copyright (c) 1999-2009 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. + * + * 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. 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 +#ifndef lint +#if 0 +static char sccsid[] = "@(#)popen.c 8.3 (Berkeley) 4/6/94"; +#else +__RCSID("$NetBSD: popen.c,v 1.37 2010/03/20 18:23:30 christos Exp $"); +#endif +#endif /* not lint */ + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef KERBEROS5 +#include +#endif + +#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(const char *argv[], const char *ptype, int stderrfd) +{ + FILE *iop; + int argc, pdes[2], pid; + volatile int isls; + char **pop; + StringList *sl; + + iop = NULL; + isls = 0; + if ((*ptype != 'r' && *ptype != 'w') || ptype[1]) + return (NULL); + + if (!pids) { + if ((fds = getdtablesize()) <= 0) + return (NULL); + if ((pids = (int *)malloc((unsigned 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, ftpd_strdup(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) + || gl.gl_pathv == NULL) { + if (sl_add(sl, ftpd_strdup(argv[argc])) == -1) { + globfree(&gl); + goto pfree; + } + } else { + for (pop = gl.gl_pathv; *pop; pop++) { + if (sl_add(sl, ftpd_strdup(*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 + + pid = isls ? fork() : vfork(); + switch (pid) { + case -1: /* error */ + (void)close(pdes[0]); + (void)close(pdes[1]); + goto pfree; + /* NOTREACHED */ + case 0: /* child */ + if (*ptype == '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 */ + optreset = 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 (*ptype == 'r') { + iop = fdopen(pdes[0], ptype); + (void)close(pdes[1]); + } else { + iop = fdopen(pdes[1], ptype); + (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 nsigset, 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(&nsigset); + sigaddset(&nsigset, SIGINT); + sigaddset(&nsigset, SIGQUIT); + sigaddset(&nsigset, SIGHUP); + sigprocmask(SIG_BLOCK, &nsigset, &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/libexec/ftpd/version.h b/libexec/ftpd/version.h new file mode 100644 index 000000000..ff250ff82 --- /dev/null +++ b/libexec/ftpd/version.h @@ -0,0 +1,33 @@ +/* $NetBSD: version.h,v 1.74 2010/03/21 20:27:26 lukem Exp $ */ +/*- + * Copyright (c) 1999-2009 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. + * + * 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 20100320" +#endif diff --git a/releasetools/nbsd_ports b/releasetools/nbsd_ports index 5344b8717..fc01df8ac 100644 --- a/releasetools/nbsd_ports +++ b/releasetools/nbsd_ports @@ -59,6 +59,7 @@ 2011/05/26 00:00:00,external/public-domain/xz 2012/10/17 12:00:00,external/README 2012/10/17 12:00:00,include +2012/10/17 12:00:00,libexec/ftpd 2012/10/17 12:00:00,libexec/ld.elf_so 2012/10/17 12:00:00,libexec/Makefile 2012/10/17 12:00:00,libexec/Makefile.inc