Features:

* Add ftpd.conf(5) directive `advertise'; change the address that is
  advertised to the client for PASV transfers. this may be useful in
  certain firewall/NAT environments.

  Feature requested in [bin/9606] by Scott Presnell.

* Add -X option; syslog wu-ftpd style xferlog messages, prefixed with
  `xferlog: '.  An example line from syslog (wrapped):
	Dec 16 18:50:24 odysseus ftpd[571]: xferlog: Sat Dec 16 18:50:24 2000
	2 localhost 3747328 /pub/WLW2K601.EXE b _ o a lukem@ FTP 0 * c

  These messages can be converted to a wu-ftpd style xferlog file
  suitable for parsing with third-party tools with something like:
	grep 'xferlog: ' /var/log/xferlog | \
	    sed -e 's/^.*xferlog: //' >wuxferlog

  The format is the same as the wu-ftpd xferlog entries (with the leading
  syslog stuff), but different from the wu-ftpd syslogged xferlog entries
  because the latter is not as easy to convert into the standard xferlog
  file format.

  The choice to only syslog the xferlog messages rather than append to
  a /var/log/xferlog file was made because the latter doesn't work to
  well in the situation where the logfile is rotated and compressed and
  a long-running ftpd still has a file-descriptor to the now nonexistant
  xferlog file, and the log message will then get lost.

  Feature requested in [bin/11651] by Hubert Feyrer.


Fixes:

* In ftpd(8), clarify the -a and -c options.

* More clarifications in ftpd.conf(5).

* Ensure that all ftpd.conf commands set a parameter back to sane defaults
  if an argument of `none' or bad settings are given.

* Support the `chroot' directive for `REAL' users too (for consistency).

* For `GUEST' users, store the supplied password in pw->pw_passwd for use
  later in the xferlog.

* If show_chdir_messages() is given a code of -1, flush the cache of
  visited directories.  Invoke show_chdir_messages(-1) in end_login().

* Only syslog session stats if logging is requested.

* Rename logcmd() -> logxfer(), and dolog() -> logremotehost().

* Use cprintf() instead of fprintf() where appropriate.

* Minor KNF, and make a couple of functions static that were declared static.
This commit is contained in:
lukem 2000-12-18 02:32:50 +00:00
parent c25ab2f1ec
commit 5015048190
8 changed files with 503 additions and 197 deletions

View File

@ -1,4 +1,4 @@
/* $NetBSD: cmds.c,v 1.9 2000/12/04 10:50:39 itojun Exp $ */
/* $NetBSD: cmds.c,v 1.10 2000/12/18 02:32:50 lukem Exp $ */
/*
* Copyright (c) 1999-2000 The NetBSD Foundation, Inc.
@ -101,7 +101,7 @@
#include <sys/cdefs.h>
#ifndef lint
__RCSID("$NetBSD: cmds.c,v 1.9 2000/12/04 10:50:39 itojun Exp $");
__RCSID("$NetBSD: cmds.c,v 1.10 2000/12/18 02:32:50 lukem Exp $");
#endif /* not lint */
#include <sys/param.h>
@ -188,7 +188,7 @@ delete(const char *name)
perror_reply(550, name);
} else
ack("DELE");
logcmd("delete", -1, name, NULL, NULL, p);
logxfer("delete", -1, name, NULL, NULL, p);
}
void
@ -219,7 +219,7 @@ makedir(const char *name)
perror_reply(550, name);
} else
replydirname(name, "directory created.");
logcmd("mkdir", -1, name, NULL, NULL, p);
logxfer("mkdir", -1, name, NULL, NULL, p);
}
void
@ -412,7 +412,7 @@ removedir(const char *name)
perror_reply(550, name);
} else
ack("RMD");
logcmd("rmdir", -1, name, NULL, NULL, p);
logxfer("rmdir", -1, name, NULL, NULL, p);
}
char *
@ -438,7 +438,7 @@ renamecmd(const char *from, const char *to)
perror_reply(550, "rename");
} else
ack("RNTO");
logcmd("rename", -1, from, to, NULL, p);
logxfer("rename", -1, from, to, NULL, p);
}
void
@ -446,14 +446,17 @@ sizecmd(const char *filename)
{
switch (type) {
case TYPE_L:
case TYPE_I: {
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: {
break;
}
case TYPE_A:
{
FILE *fin;
int c;
off_t count;
@ -478,7 +481,8 @@ sizecmd(const char *filename)
(void) fclose(fin);
reply(213, LLF, (LLT)count);
break; }
break;
}
default:
reply(504, "SIZE not implemented for Type %c.", "?AEIL"[type]);
}

View File

@ -1,4 +1,4 @@
/* $NetBSD: conf.c,v 1.36 2000/11/16 13:15:13 lukem Exp $ */
/* $NetBSD: conf.c,v 1.37 2000/12/18 02:32:50 lukem Exp $ */
/*-
* Copyright (c) 1997-2000 The NetBSD Foundation, Inc.
@ -38,17 +38,19 @@
#include <sys/cdefs.h>
#ifndef lint
__RCSID("$NetBSD: conf.c,v 1.36 2000/11/16 13:15:13 lukem Exp $");
__RCSID("$NetBSD: conf.c,v 1.37 2000/12/18 02:32:50 lukem Exp $");
#endif /* not lint */
#include <sys/types.h>
#include <sys/param.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <ctype.h>
#include <errno.h>
#include <fcntl.h>
#include <glob.h>
#include <netdb.h>
#include <setjmp.h>
#include <signal.h>
#include <stdio.h>
@ -71,6 +73,13 @@ static char *strend(const char *, char *);
static int filetypematch(char *, int);
/* class defaults */
#define DEFAULT_LIMIT -1 /* unlimited connections */
#define DEFAULT_MAXFILESIZE -1 /* unlimited file size */
#define DEFAULT_MAXTIMEOUT 7200 /* 2 hours */
#define DEFAULT_TIMEOUT 900 /* 15 minutes */
#define DEFAULT_UMASK 027 /* 15 minutes */
/*
* Initialise curclass to an `empty' state
*/
@ -88,26 +97,28 @@ init_curclass(void)
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 = -1; /* unlimited connections */
curclass.limit = DEFAULT_LIMIT;
REASSIGN(curclass.limitfile, NULL);
curclass.maxfilesize = -1; /* unlimited file size */
curclass.maxfilesize = DEFAULT_MAXFILESIZE;
curclass.maxrateget = 0;
curclass.maxrateput = 0;
curclass.maxtimeout = 7200; /* 2 hours */
curclass.maxtimeout = DEFAULT_MAXTIMEOUT;
REASSIGN(curclass.motd, xstrdup(_PATH_FTPLOGINMESG));
REASSIGN(curclass.notify, NULL);
curclass.portmin = 0;
curclass.portmax = 0;
curclass.rateget = 0;
curclass.rateput = 0;
curclass.timeout = 900; /* 15 minutes */
curclass.timeout = DEFAULT_TIMEOUT;
/* curclass.type is set elsewhere */
curclass.umask = 027;
curclass.umask = DEFAULT_UMASK;
CURCLASS_FLAGS_SET(checkportcmd);
CURCLASS_FLAGS_SET(modify);
@ -137,6 +148,7 @@ parse_conf(const char *findclass)
init_curclass();
REASSIGN(curclass.classname, xstrdup(findclass));
/* set more guest defaults */
if (strcasecmp(findclass, "guest") == 0) {
CURCLASS_FLAGS_CLR(modify);
curclass.umask = 0707;
@ -192,7 +204,58 @@ parse_conf(const char *findclass)
REASSIGN(curclass.x, arg); \
} while (0)
if (strcasecmp(word, "checkportcmd") == 0) {
if (0) {
/* no-op */
} else if (strcasecmp(word, "advertise") == 0) {
struct addrinfo hints, *res;
int error;
memset((char *)&curclass.advertise, 0,
sizeof(curclass.advertise));
curclass.advertise.su_len = 0;
if (none || EMPTYSTR(arg))
continue;
res = NULL;
memset(&hints, 0, sizeof(hints));
/*
* only get addresses of the family
* that we're listening on
*/
hints.ai_family = ctrl_addr.su_family;
hints.ai_socktype = SOCK_STREAM;
error = getaddrinfo(arg, "0", &hints, &res);
if (error) {
syslog(LOG_WARNING, "%s line %d: %s",
infile, (int)line, gai_strerror(error));
advertiseparsefail:
if (res)
freeaddrinfo(res);
continue;
}
if (res->ai_next) {
syslog(LOG_WARNING,
"%s line %d: multiple addresses returned for `%s'; please be more specific",
infile, (int)line, arg);
goto advertiseparsefail;
}
if (sizeof(curclass.advertise) < res->ai_addrlen || (
#ifdef INET6
res->ai_family != AF_INET6 &&
#endif
res->ai_family != AF_INET)) {
syslog(LOG_WARNING,
"%s line %d: unsupported protocol %d for `%s'",
infile, (int)line, res->ai_family, arg);
goto advertiseparsefail;
}
memcpy(&curclass.advertise, res->ai_addr,
res->ai_addrlen);
curclass.advertise.su_len = res->ai_addrlen;
freeaddrinfo(res);
} else if (strcasecmp(word, "checkportcmd") == 0) {
CONF_FLAG(checkportcmd);
} else if (strcasecmp(word, "chroot") == 0) {
@ -272,21 +335,11 @@ parse_conf(const char *findclass)
} else if (strcasecmp(word, "homedir") == 0) {
CONF_STRING(homedir);
} else if (strcasecmp(word, "maxfilesize") == 0) {
if (none || EMPTYSTR(arg))
continue;
llval = strsuftoll(arg);
if (llval == -1) {
syslog(LOG_WARNING,
"%s line %d: invalid maxfilesize %s",
infile, (int)line, arg);
continue;
}
curclass.maxfilesize = llval;
} else if (strcasecmp(word, "limit") == 0) {
int limit;
curclass.limit = DEFAULT_LIMIT;
REASSIGN(curclass.limitfile, NULL);
if (none || EMPTYSTR(arg))
continue;
limit = (int)strtol(arg, &endp, 10);
@ -300,7 +353,21 @@ parse_conf(const char *findclass)
REASSIGN(curclass.limitfile,
EMPTYSTR(p) ? NULL : xstrdup(p));
} else if (strcasecmp(word, "maxfilesize") == 0) {
curclass.maxfilesize = DEFAULT_MAXFILESIZE;
if (none || EMPTYSTR(arg))
continue;
llval = strsuftoll(arg);
if (llval == -1) {
syslog(LOG_WARNING,
"%s line %d: invalid maxfilesize %s",
infile, (int)line, arg);
continue;
}
curclass.maxfilesize = llval;
} else if (strcasecmp(word, "maxtimeout") == 0) {
curclass.maxtimeout = DEFAULT_MAXTIMEOUT;
if (none || EMPTYSTR(arg))
continue;
timeout = (unsigned int)strtoul(arg, &endp, 10);
@ -341,12 +408,9 @@ parse_conf(const char *findclass)
int minport, maxport;
char *min, *max;
if (none) {
curclass.portmin = 0;
curclass.portmax = 0;
continue;
}
if (EMPTYSTR(arg))
curclass.portmin = 0;
curclass.portmax = 0;
if (none || EMPTYSTR(arg))
continue;
min = arg;
NEXTWORD(p, max);
@ -382,6 +446,8 @@ parse_conf(const char *findclass)
curclass.portmax = maxport;
} else if (strcasecmp(word, "rateget") == 0) {
curclass.maxrateget = 0;
curclass.rateget = 0;
if (none || EMPTYSTR(arg))
continue;
llval = strsuftoll(arg);
@ -395,6 +461,8 @@ parse_conf(const char *findclass)
curclass.rateget = llval;
} else if (strcasecmp(word, "rateput") == 0) {
curclass.maxrateput = 0;
curclass.rateput = 0;
if (none || EMPTYSTR(arg))
continue;
llval = strsuftoll(arg);
@ -411,6 +479,7 @@ parse_conf(const char *findclass)
CONF_FLAG(sanenames);
} else if (strcasecmp(word, "timeout") == 0) {
curclass.timeout = DEFAULT_TIMEOUT;
if (none || EMPTYSTR(arg))
continue;
timeout = (unsigned int)strtoul(arg, &endp, 10);
@ -443,6 +512,7 @@ parse_conf(const char *findclass)
} else if (strcasecmp(word, "umask") == 0) {
mode_t umask;
curclass.umask = DEFAULT_UMASK;
if (none || EMPTYSTR(arg))
continue;
umask = (mode_t)strtoul(arg, &endp, 8);
@ -472,8 +542,9 @@ parse_conf(const char *findclass)
/*
* 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' + "-".
* 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)
@ -488,6 +559,13 @@ show_chdir_messages(int code)
char cwd[MAXPATHLEN];
char *cp, **rlist;
if (code == -1) {
if (slist != NULL)
sl_free(slist, 1);
slist = NULL;
return;
}
if (quietmessages)
return;
@ -669,7 +747,6 @@ format_path(char *dst, const char *src)
len = 0;
if (src == NULL)
return;
for (p = src; *p && len < MAXPATHLEN; p++) {
if (*p == '%') {
p++;

View File

@ -1,4 +1,4 @@
/* $NetBSD: extern.h,v 1.36 2000/11/30 02:59:11 lukem Exp $ */
/* $NetBSD: extern.h,v 1.37 2000/12/18 02:32:51 lukem Exp $ */
/*-
* Copyright (c) 1992, 1993
@ -138,7 +138,7 @@ int ftpd_pclose(FILE *);
FILE *ftpd_popen(char *[], const char *, int);
char *getline(char *, int, FILE *);
void init_curclass(void);
void logcmd(const char *, off_t, const char *, const char *,
void logxfer(const char *, off_t, const char *, const char *,
const struct timeval *, const char *);
void logwtmp(const char *, const char *, const char *);
struct tab *lookup(struct tab *, const char *);
@ -175,6 +175,38 @@ void user(const char *);
char *xstrdup(const char *);
void yyerror(char *);
#include <netinet/in.h>
#ifdef BSD4_4
# define HAVE_SETPROCTITLE 1
# define HAVE_SOCKADDR_SA_LEN 1
#endif
struct sockinet {
union sockunion {
struct sockaddr_in su_sin;
#ifdef INET6
struct sockaddr_in6 su_sin6;
#endif
} si_su;
#if !HAVE_SOCKADDR_SA_LEN
int si_len;
#endif
};
#if !HAVE_SOCKADDR_SA_LEN
# define su_len si_len
#else
# define su_len si_su.su_sin.sin_len
#endif
#define su_addr si_su.su_sin.sin_addr
#define su_family si_su.su_sin.sin_family
#define su_port si_su.su_sin.sin_port
#ifdef INET6
# define su_6addr si_su.su_sin6.sin6_addr
# define su_scope_id si_su.su_sin6.sin6_scope_id
#endif
struct tab {
char *name;
short token;
@ -213,6 +245,7 @@ typedef enum {
#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 */
@ -236,38 +269,6 @@ struct ftpclass {
mode_t umask; /* Umask to use */
};
#include <netinet/in.h>
#ifdef BSD4_4
# define HAVE_SETPROCTITLE 1
# define HAVE_SOCKADDR_SA_LEN 1
#endif
struct sockinet {
union sockunion {
struct sockaddr_in su_sin;
#ifdef INET6
struct sockaddr_in6 su_sin6;
#endif
} si_su;
#if !HAVE_SOCKADDR_SA_LEN
int si_len;
#endif
};
#if !HAVE_SOCKADDR_SA_LEN
# define su_len si_len
#else
# define su_len si_su.su_sin.sin_len
#endif
#define su_addr si_su.su_sin.sin_addr
#define su_family si_su.su_sin.sin_family
#define su_port si_su.su_sin.sin_port
#ifdef INET6
# define su_6addr si_su.su_sin6.sin6_addr
# define su_scope_id si_su.su_sin6.sin6_scope_id
#endif
extern int yyparse(void);
#ifndef GLOBAL

View File

@ -1,4 +1,4 @@
/* $NetBSD: ftpcmd.y,v 1.58 2000/11/30 02:59:11 lukem Exp $ */
/* $NetBSD: ftpcmd.y,v 1.59 2000/12/18 02:32:51 lukem Exp $ */
/*-
* Copyright (c) 1997-2000 The NetBSD Foundation, Inc.
@ -83,7 +83,7 @@
#if 0
static char sccsid[] = "@(#)ftpcmd.y 8.3 (Berkeley) 4/6/94";
#else
__RCSID("$NetBSD: ftpcmd.y,v 1.58 2000/11/30 02:59:11 lukem Exp $");
__RCSID("$NetBSD: ftpcmd.y,v 1.59 2000/12/18 02:32:51 lukem Exp $");
#endif
#endif /* not lint */
@ -237,7 +237,7 @@ cmd
reply(221,
"Thank you for using the FTP service on %s.",
hostname);
if (logged_in) {
if (logged_in && logging) {
syslog(LOG_INFO,
"Data traffic: " LLF " byte%s in " LLF " file%s",
(LLT)total_data, PLURAL(total_data),
@ -862,7 +862,7 @@ rcmd
{
if ($2) {
fromname = NULL;
restart_point = $4; /* XXX $4 is only "int" */
restart_point = $4; /* XXX: $4 is only "int" */
reply(350,
"Restarting at " LLF ". Send STORE or RETRIEVE to initiate transfer.",
(LLT)restart_point);
@ -955,7 +955,7 @@ host_long_port6
a[8] = $21; a[9] = $23; a[10] = $25; a[11] = $27;
a[12] = $29; a[13] = $31; a[14] = $33; a[15] = $35;
if (his_addr.su_family == AF_INET6) {
/* XXX more sanity checks! */
/* XXX: more sanity checks! */
data_dest.su_scope_id = his_addr.su_scope_id;
}
#else

View File

@ -1,4 +1,4 @@
.\" $NetBSD: ftpd.8,v 1.62 2000/12/01 07:59:47 lukem Exp $
.\" $NetBSD: ftpd.8,v 1.63 2000/12/18 02:32:51 lukem Exp $
.\"
.\" Copyright (c) 1997-2000 The NetBSD Foundation, Inc.
.\" All rights reserved.
@ -67,7 +67,7 @@
.\"
.\" @(#)ftpd.8 8.2 (Berkeley) 4/19/94
.\"
.Dd December 1, 2000
.Dd December 18, 2000
.Dt FTPD 8
.Os
.Sh NAME
@ -76,7 +76,7 @@
Internet File Transfer Protocol server
.Sh SYNOPSIS
.Nm
.Op Fl dHlqQrsuUwW
.Op Fl dHlqQrsuUwWX
.Op Fl a Ar anondir
.Op Fl c Ar confdir
.Op Fl C Ar user
@ -103,11 +103,24 @@ as the directory to
.Xr chroot 2
into for anonymous logins.
Default is the home directory for the ftp user.
This can also be specified with the
.Xr ftpd.conf 5
.Sy chroot
directive.
.It Fl c Ar confdir
Change the root directory of the configuration files from
.Dq Pa /etc
to
.Ar confdir .
This changes the directory for the following files:
.Pa /etc/ftpchroot ,
.Pa /etc/ftpusers ,
.Pa /etc/ftpwelcome ,
.Pa /etc/motd ,
and the file specified by the
.Xr ftpd.conf 5
.Sy limit
directive.
.It Fl C Ar user
Check whether
.Ar user
@ -119,7 +132,7 @@ and exit without attempting a connection.
exits with an exit code of 0 if access would be granted, or 1 otherwise.
This can be useful for testing configurations.
.It Fl d
Debugging information is written to the syslog using
Debugging information is written to the syslog using a facility of
.Dv LOG_FTP .
.It Fl e Ar emailaddr
Use
@ -156,8 +169,8 @@ Each successful and failed
.Tn FTP
session is logged using syslog with a facility of
.Dv LOG_FTP .
If this option is specified twice, the retrieve (get), store (put), append,
delete, make directory, remove directory and rename operations and
If this option is specified more than once, the retrieve (get), store (put),
append, delete, make directory, remove directory and rename operations and
their file name arguments are also logged.
.It Fl P Ar dataport
Use
@ -228,6 +241,23 @@ Don't log each
.Tn FTP
session to
.Pa /var/log/wtmp .
.It Fl X
Log
.Tn wu-ftpd
style
.Sq xferlog
entries to the syslog, prefixed with
.Dq "xferlog:\ " ,
using a facility of
.Dv LOG_FTP .
These syslog entries can be converted to a
.Tn wu-ftpd
style
.Pa xferlog
file suitable for input into a third-party log analysis tool with a command
similar to:
.Dl "grep 'xferlog: ' /var/log/xferlog | \e"
.Dl "\ \ \ sed -e 's/^.*xferlog: //' > wuxferlog"
.El
.Pp
The file
@ -247,13 +277,13 @@ prints it before issuing the
message.
If the file
.Pa /etc/motd
exists,
exists (under the chroot directory if applicable),
.Nm
prints it after a successful login.
(This may be changed with the
This may be changed with the
.Xr ftpd.conf 5
directive
.Sy upload . )
.Sy motd .
.Pp
The
.Nm

View File

@ -1,4 +1,4 @@
/* $NetBSD: ftpd.c,v 1.117 2000/11/30 08:33:33 lukem Exp $ */
/* $NetBSD: ftpd.c,v 1.118 2000/12/18 02:32:51 lukem Exp $ */
/*
* Copyright (c) 1997-2000 The NetBSD Foundation, Inc.
@ -109,7 +109,7 @@ __COPYRIGHT(
#if 0
static char sccsid[] = "@(#)ftpd.c 8.5 (Berkeley) 4/28/95";
#else
__RCSID("$NetBSD: ftpd.c,v 1.117 2000/11/30 08:33:33 lukem Exp $");
__RCSID("$NetBSD: ftpd.c,v 1.118 2000/12/18 02:32:51 lukem Exp $");
#endif
#endif /* not lint */
@ -150,6 +150,7 @@ __RCSID("$NetBSD: ftpd.c,v 1.117 2000/11/30 08:33:33 lukem Exp $");
#include <string.h>
#include <syslog.h>
#include <time.h>
#include <tzfile.h>
#include <unistd.h>
#include <util.h>
#include <utmp.h>
@ -176,6 +177,7 @@ int dataport; /* use specific data port */
int dopidfile; /* maintain pid file */
int doutmp; /* update utmp file */
int dowtmp; /* update wtmp file */
int doxferlog; /* syslog wu-ftpd style xferlog entries */
int dropprivs; /* if privileges should or have been dropped */
int mapped; /* IPv4 connection on AF_INET6 socket */
off_t file_size;
@ -211,10 +213,10 @@ 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 dolog(struct sockinet *);
static void end_login(void);
static FILE *getdatasock(const char *);
static char *gunique(const char *);
static void logremotehost(struct sockinet *);
static void lostconn(int);
static void myoob(int);
static int receive_data(FILE *, FILE *);
@ -248,8 +250,9 @@ main(int argc, char *argv[])
sflag = 0;
dataport = 0;
dopidfile = 1; /* default: DO use a pid file to count users */
doutmp = 0; /* default: don't log to utmp */
doutmp = 0; /* default: Do NOT log to utmp */
dowtmp = 1; /* default: DO log to wtmp */
doxferlog = 0; /* default: Do NOT syslog xferlog */
dropprivs = 0;
mapped = 0;
usedefault = 1;
@ -265,7 +268,7 @@ main(int argc, char *argv[])
*/
openlog("ftpd", LOG_PID | LOG_NDELAY, LOG_FTP);
while ((ch = getopt(argc, argv, "a:c:C:de:h:HlP:qQrst:T:uUvV:wW"))
while ((ch = getopt(argc, argv, "a:c:C:de:h:HlP:qQrst:T:uUvV:wWX"))
!= -1) {
switch (ch) {
case 'a':
@ -360,6 +363,10 @@ main(int argc, char *argv[])
dowtmp = 0;
break;
case 'X':
doxferlog = 1;
break;
default:
if (optopt == 'a' || optopt == 'C')
exit(1);
@ -370,14 +377,14 @@ main(int argc, char *argv[])
if (EMPTYSTR(confdir))
confdir = _DEFAULT_CONFDIR;
memset((char *)&his_addr, 0, sizeof (his_addr));
memset((char *)&his_addr, 0, sizeof(his_addr));
addrlen = sizeof(his_addr.si_su);
if (getpeername(0, (struct sockaddr *)&his_addr.si_su, &addrlen) < 0) {
syslog(LOG_ERR, "getpeername (%s): %m",argv[0]);
exit(1);
}
his_addr.su_len = addrlen;
memset((char *)&ctrl_addr, 0, sizeof (ctrl_addr));
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]);
@ -472,7 +479,7 @@ main(int argc, char *argv[])
if (fcntl(fileno(stdin), F_SETOWN, getpid()) == -1)
syslog(LOG_ERR, "fcntl F_SETOWN: %m");
#endif
dolog(&his_addr);
logremotehost(&his_addr);
/*
* Set up default state
*/
@ -664,7 +671,7 @@ user(const char *name)
*
* NOTE: needs struct passwd *pw setup before use.
*/
int
static int
checkuser(const char *fname, const char *name, int def, int nofile,
char **retclass)
{
@ -790,7 +797,7 @@ checkuser(const char *fname, const char *name, int def, int nofile,
*
* NOTE: needs struct passwd *pw setup (for checkuser())
*/
int
static int
checkaccess(const char *name)
{
@ -798,7 +805,7 @@ checkaccess(const char *name)
}
/*
* Terminate login as previous user, if any, resetting state;
* Terminate login as previous user (if any), resetting state;
* used when USER command is given or login fails.
*/
static void
@ -812,12 +819,15 @@ end_login(void)
logout(utmp.ut_line);
}
/* reset login state */
(void) seteuid((uid_t)0);
show_chdir_messages(-1); /* flush chdir cache */
if (pw != NULL && pw->pw_passwd != NULL)
memset(pw->pw_passwd, 0, strlen(pw->pw_passwd));
pw = NULL;
logged_in = 0;
quietmessages = 0;
gidcount = 0;
curclass.type = CLASS_REAL;
(void) seteuid((uid_t)0);
}
void
@ -968,6 +978,8 @@ pass(const char *passwd)
class = xstrdup("real");
break;
default:
syslog(LOG_ERR, "unknown curclass.type %d; aborting",
curclass.type);
abort();
}
}
@ -1047,6 +1059,16 @@ pass(const char *passwd)
}
break;
case CLASS_REAL:
/* only chroot REAL if explictly requested */
if (! EMPTYSTR(curclass.chroot)) {
format_path(root, curclass.chroot);
if (EMPTYSTR(root) || chroot(root) < 0) {
syslog(LOG_NOTICE,
"REAL user %s: can't chroot to %s: %m",
pw->pw_name, root);
goto bad_chroot;
}
}
format_path(homedir,
curclass.homedir ? curclass.homedir :
pw->pw_dir);
@ -1100,6 +1122,8 @@ pass(const char *passwd)
(void)display_file(conffilename(curclass.motd), 230);
show_chdir_messages(230);
if (curclass.type == CLASS_GUEST) {
char *p;
reply(230, "Guest login ok, access restrictions apply.");
#if HAVE_SETPROCTITLE
snprintf(proctitle, sizeof(proctitle),
@ -1113,6 +1137,11 @@ pass(const char *passwd)
"ANONYMOUS FTP LOGIN FROM %s, %s (class: %s, type: %s)",
remotehost, passwd,
curclass.classname, CURCLASSTYPE);
/* store guest password reply into pw_passwd */
REASSIGN(pw->pw_passwd, xstrdup(passwd));
for (p = pw->pw_passwd; *p; p++)
if (!isgraph(*p))
*p = '_';
} else {
reply(230, "User %s logged in.", pw->pw_name);
#if HAVE_SETPROCTITLE
@ -1153,16 +1182,16 @@ retrieve(char *argv[], const char *name)
tdp = NULL;
dispname = name;
fin = dout = NULL;
if (argv == NULL) {
if (argv == NULL) { /* if not running a command ... */
log = 1;
isdata = 1;
fin = fopen(name, "r");
closefunc = fclose;
if (fin == NULL)
if (fin == NULL) /* doesn't exist?; try a conversion */
argv = do_conversion(name);
if (argv != NULL) {
isconversion++;
syslog(LOG_INFO, "get command: '%s' on '%s'",
syslog(LOG_DEBUG, "get command: '%s' on '%s'",
argv[0], name);
}
}
@ -1188,7 +1217,7 @@ retrieve(char *argv[], const char *name)
if (errno != 0) {
perror_reply(550, dispname);
if (log)
logcmd("get", -1, name, NULL, NULL,
logxfer("get", -1, name, NULL, NULL,
strerror(errno));
}
goto cleanupretrieve;
@ -1230,7 +1259,7 @@ retrieve(char *argv[], const char *name)
tdp = &td;
done:
if (log)
logcmd("get", byte_count, name, NULL, tdp, NULL);
logxfer("get", byte_count, name, NULL, tdp, NULL);
closerv = (*closefunc)(fin);
if (sendrv == 0) {
FILE *err;
@ -1284,7 +1313,8 @@ store(const char *name, const char *mode, int unique)
desc = (*mode == 'w') ? "put" : "append";
if (unique && stat(name, &st) == 0 &&
(name = gunique(name)) == NULL) {
logcmd(desc, -1, name, NULL, NULL, "cannot create unique file");
logxfer(desc, -1, name, NULL, NULL,
"cannot create unique file");
goto cleanupstore;
}
@ -1295,7 +1325,7 @@ store(const char *name, const char *mode, int unique)
tdp = NULL;
if (fout == NULL) {
perror_reply(553, name);
logcmd(desc, -1, name, NULL, NULL, strerror(errno));
logxfer(desc, -1, name, NULL, NULL, strerror(errno));
goto cleanupstore;
}
byte_count = -1;
@ -1343,7 +1373,7 @@ store(const char *name, const char *mode, int unique)
timersub(&finish, &start, &td);
tdp = &td;
done:
logcmd(desc, byte_count, name, NULL, tdp, NULL);
logxfer(desc, byte_count, name, NULL, tdp, NULL);
(*closefunc)(fout);
cleanupstore:
closedataconn(din);
@ -1883,7 +1913,10 @@ statcmd(void)
su = NULL;
} else if (pdata != -1) {
reply(0, "in Passive mode");
su = (struct sockinet *)&pasv_addr;
if (curclass.advertise.su_len != 0)
su = &curclass.advertise;
else
su = &pasv_addr;
ispassive = 1;
goto printaddr;
} else if (usedefault == 0) {
@ -2030,6 +2063,16 @@ statcmd(void)
CURCLASS_FLAGS_ISSET(sanenames) ? "en" : "dis");
reply(0, "PASV/LPSV/EPSV connections: %sabled",
CURCLASS_FLAGS_ISSET(passive) ? "en" : "dis");
if (curclass.advertise.su_len != 0) {
char buf[50]; /* big enough for IPv6 address */
const char *bp;
bp = inet_ntop(curclass.advertise.su_family,
(void *)&curclass.advertise.su_addr,
buf, sizeof(buf));
if (bp != NULL)
reply(0, "PASV advertise address: %s", bp);
}
if (curclass.portmin && curclass.portmax)
reply(0, "PASV port range: %d - %d",
curclass.portmin, curclass.portmax);
@ -2100,7 +2143,7 @@ reply(int n, const char *fmt, ...)
}
static void
dolog(struct sockinet *who)
logremotehost(struct sockinet *who)
{
if (getnameinfo((struct sockaddr *)&who->si_su,
@ -2117,8 +2160,7 @@ dolog(struct sockinet *who)
}
/*
* Record logout in wtmp file
* and exit with supplied status.
* Record logout in wtmp file and exit with supplied status.
*/
void
dologout(int status)
@ -2241,7 +2283,10 @@ passive(void)
pasv_addr.su_len = len;
if (listen(pdata, 1) < 0)
goto pasv_error;
a = (char *) &pasv_addr.su_addr;
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)
@ -2373,9 +2418,15 @@ long_passive(char *cmd, int pf)
#define UC(b) (((int) b) & 0xff)
if (strcmp(cmd, "LPSV") == 0) {
switch (pasv_addr.su_family) {
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 *) &pasv_addr.su_addr;
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]),
@ -2383,7 +2434,7 @@ long_passive(char *cmd, int pf)
return;
#ifdef INET6
case AF_INET6:
a = (char *) &pasv_addr.su_6addr;
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,
@ -2465,7 +2516,8 @@ extended_port(const char *arg)
goto parsefail;
if (sizeof(data_dest) < res->ai_addrlen)
goto parsefail;
memcpy(&data_dest, res->ai_addr, res->ai_addrlen);
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) {
@ -2580,7 +2632,6 @@ send_file_list(const char *whichf)
int simple = 0;
int freeglob = 0;
glob_t gl;
off_t b;
#ifdef __GNUC__
(void) &dout;
@ -2638,16 +2689,19 @@ send_file_list(const char *whichf)
}
if (S_ISREG(st.st_mode)) {
/*
* XXXRFC:
* should we follow RFC959 and not work
* for non directories?
*/
if (dout == NULL) {
dout = dataconn("file list", (off_t)-1, "w");
if (dout == NULL)
goto out;
transflag++;
}
b = fprintf(dout, "%s%s\n", dirname,
cprintf(dout, "%s%s\n", dirname,
type == TYPE_A ? "\r" : "");
total_bytes += b;
total_bytes_out += b;
byte_count += strlen(dirname) + 1;
continue;
} else if (!S_ISDIR(st.st_mode))
@ -2672,7 +2726,12 @@ send_file_list(const char *whichf)
* We have to do a stat to ensure it's
* not a directory or special file.
*/
/* XXX: follow RFC959 and filter out non files ? */
/*
* XXXRFC:
* should we follow RFC959 and filter out
* non files ? lukem - NO!, or not until
* our ftp client uses MLS{T,D} for completion.
*/
if (simple || (stat(nbuf, &st) == 0 &&
S_ISREG(st.st_mode))) {
char *p;
@ -2687,10 +2746,8 @@ send_file_list(const char *whichf)
p = nbuf;
if (nbuf[0] == '.' && nbuf[1] == '/')
p = &nbuf[2];
b = fprintf(dout, "%s%s\n", p,
cprintf(dout, "%s%s\n", p,
type == TYPE_A ? "\r" : "");
total_bytes += b;
total_bytes_out += b;
byte_count += strlen(nbuf) + 1;
}
}
@ -2729,48 +2786,102 @@ conffilename(const char *s)
}
/*
* logcmd --
* based on the arguments, syslog a message:
* logxfer --
* if logging > 1, then based on the arguments, syslog a message:
* if bytes != -1 "<command> <file1> = <bytes> bytes"
* else if file2 != NULL "<command> <file1> <file2>"
* else "<command> <file1>"
* if elapsed != NULL, append "in xxx.yyy seconds"
* if error != NULL, append ": " + error
*
* if doxferlog != 0, syslog a wu-ftpd style xferlog entry
*/
void
logcmd(const char *command, off_t bytes, const char *file1, const char *file2,
logxfer(const char *command, off_t bytes, const char *file1, const char *file2,
const struct timeval *elapsed, const char *error)
{
char buf[MAXPATHLEN * 2 + 100], realfile[MAXPATHLEN];
const char *p;
size_t len;
char buf[MAXPATHLEN * 2 + 100], realfile[MAXPATHLEN];
const char *r1, *r2;
char direction;
size_t len;
time_t now;
if (logging <=1)
if (logging <=1 && !doxferlog)
return;
if ((p = realpath(file1, realfile)) == NULL)
p = file1;
len = snprintf(buf, sizeof(buf), "%s %s", command, p);
r1 = r2 = NULL;
if ((r1 = realpath(file1, realfile)) == NULL)
r1 = file1;
if (file2 != NULL)
if ((r2 = realpath(file2, realfile)) == NULL)
r2 = file2;
if (bytes != (off_t)-1) {
len += snprintf(buf + len, sizeof(buf) - len,
" = " LLF " byte%s", (LLT) bytes, PLURAL(bytes));
} else if (file2 != NULL) {
if ((p = realpath(file2, realfile)) == NULL)
p = file2;
len += snprintf(buf + len, sizeof(buf) - len, " %s", p);
/*
* syslog command
*/
if (logging > 1) {
len = snprintf(buf, sizeof(buf), "%s %s", command, r1);
if (bytes != (off_t)-1)
len += snprintf(buf + len, sizeof(buf) - len,
" = " LLF " byte%s", (LLT) bytes, PLURAL(bytes));
else if (r2 != NULL)
len += snprintf(buf + len, sizeof(buf) - len,
" %s", r2);
if (elapsed != NULL)
len += snprintf(buf + len, sizeof(buf) - len,
" in %ld.%.03d seconds", elapsed->tv_sec,
(int)(elapsed->tv_usec / 1000));
if (error != NULL)
len += snprintf(buf + len, sizeof(buf) - len,
": %s", error);
syslog(LOG_INFO, "%s", buf);
}
if (elapsed != NULL) {
len += snprintf(buf + len, sizeof(buf) - len,
" in %ld.%.03d seconds", elapsed->tv_sec,
(int)(elapsed->tv_usec / 1000));
}
if (error != NULL)
len += snprintf(buf + len, sizeof(buf) - len, ": %s", error);
/*
* syslog wu-ftpd style log entry, prefixed with "xferlog: "
*/
if (!doxferlog)
return;
syslog(LOG_INFO, "%s", buf);
if (strcmp(command, "get") == 0)
direction = 'o';
else if (strcmp(command, "put") == 0 || strcmp(command, "append") == 0)
direction = 'i';
else
return;
time(&now);
syslog(LOG_INFO,
"xferlog%s: %.24s %ld %s " LLF " %s %c %s %c %c %s FTP 0 * %c",
/*
* XXX: wu-ftpd puts (send) or (recv) in the syslog message, and removes
* the full date. This may be problematic for accurate log parsing,
* given that syslog messages don't contain the full date.
*/
#if 1 /* lukem's method; easier to convert to actual xferlog file */
"",
ctime(&now),
#else /* wu-ftpd's syslog method, with an extra unneeded space */
(direction == 'i') ? " (recv)" : " (send)",
"",
#endif
elapsed == NULL ? 0 : elapsed->tv_sec + (elapsed->tv_usec > 0),
remotehost,
bytes == (off_t)-1 ? 0 : (LLT) bytes,
r1,
type == TYPE_A ? 'a' : 'b',
"_", /* XXX: take conversions into account? */
direction,
curclass.type == CLASS_GUEST ? 'a' :
curclass.type == CLASS_CHROOT ? 'g' :
curclass.type == CLASS_REAL ? 'r' : '?',
curclass.type == CLASS_GUEST ? pw->pw_passwd : pw->pw_name,
error != NULL ? 'i' : 'c'
);
}
/*

View File

@ -1,4 +1,4 @@
.\" $NetBSD: ftpd.conf.5,v 1.14 2000/11/16 13:15:14 lukem Exp $
.\" $NetBSD: ftpd.conf.5,v 1.15 2000/12/18 02:32:51 lukem Exp $
.\"
.\" Copyright (c) 1997-2000 The NetBSD Foundation, Inc.
.\" All rights reserved.
@ -34,7 +34,7 @@
.\" ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
.\" POSSIBILITY OF SUCH DAMAGE.
.\"
.Dd November 16, 2000
.Dd December 18, 2000
.Dt FTPD.CONF 5
.Os
.Sh NAME
@ -118,12 +118,28 @@ command will return the class settings for the current user as defined by
.Pp
Each configuration line may be one of:
.Bl -tag -width 4n
.It Sy advertise Ar class Ar host
Set the address to advertise in the response to the
.Sy PASV
and
.Sy LPSV
commands to the address for
.Ar host
(which may be either a host name or IP address).
This may be useful in some firewall configurations, although many
ftp clients may not work if the address being advertised is different
to the address that they've connected to.
If
.Ar class
is
.Dq none
or no argument is given, disable this.
.It Sy checkportcmd Ar class Op Sy off
Check the
PORT
.Sy PORT
command for validity.
The
PORT
.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
@ -172,15 +188,23 @@ A
character.
.El
.Pp
The default root directory is
.Pa /
for
.Sy REAL
users, and the user's home directory for
.Sy GUEST
and
.Sy CHROOT
users.
The default root directory is:
.Bl -tag -width "CHROOT" -offset indent -compact
.It Sy CHROOT
The user's home directory.
.It Sy GUEST
If
.Fl a Ar anondir
is given, use
.Ar anondir ,
otherwise the home directory of the
.Sq ftp
user.
.It Sy REAL
By default no
.Xr chroot 2
is performed.
.El
.It Sy classtype Ar class Ar type
Set the class type of
.Ar class
@ -247,26 +271,6 @@ Escape sequences are supported; refer to
in
.Xr ftpd 8
for more information.
.It Xo Sy limit Ar class
.Ar count Op Ar file
.Xc
Limit the maximum number of concurrent connections for
.Ar class
to
.Ar count ,
with
.Sq 0
meaning unlimited connections.
If the limit is exceeded and
.Ar file
is given, display its contents to the user.
Ignored if
.Ar class
is
.Dq none
or
.Ar count
is not specified.
.It Sy homedir Ar class Op Sy pathformat
If
.Ar pathformat
@ -295,21 +299,52 @@ for
and
.Sy CHROOT
users.
.It Xo Sy limit Ar class
.Ar count Op Ar file
.Xc
Limit the maximum number of concurrent connections for
.Ar class
to
.Ar count ,
with
.Sq 0
meaning unlimited connections.
If the limit is exceeded and
.Ar file
is given, display its contents to the user.
If
.Ar class
is
.Dq none
or
.Ar count
is not specified, disable this.
If
.Ar file
is a relative path, it will be searched for in
.Pa /etc
(which can be overridden with
.Fl c Ar confdir ) .
.It Sy maxfilesize Ar class Ar size
Set the maximum size of an uploaded file to
.Ar size .
If
.Ar class
is
.Dq none
or no argument is given, disable this.
.It Sy maxtimeout Ar class Ar time
Set the maximum timeout period that a client may request,
defaulting to two hours.
This cannot be less than 30 seconds, or the value for
.Sy timeout .
Ignored if
If
.Ar class
is
.Dq none
or
.Ar time
is not specified.
is not specified, set to default of 2 hours.
.It Sy modify Ar class Op Sy off
If
.Ar class
@ -318,7 +353,13 @@ is
or
.Sy off
is given, disable the following commands:
CHMOD, DELE, MKD, RMD, RNFR, and UMASK.
.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
@ -336,6 +377,12 @@ Escape sequences are supported; refer to
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
@ -354,7 +401,12 @@ is
.Dq none
or
.Sy off
is given, disallow passive (PASV/LPSV/EPSV) connections.
is given, disallow passive
.Sy ( PASV ,
.Sy LPSV ,
and
.Sy EPSV )
connections.
Otherwise, enable them.
.It Sy portrange Ar class Ar min Ar max
Set the range of port number which will be used for the passive data port.
@ -364,8 +416,15 @@ must be greater than
and both numbers must be be between
.Dv IPPORT_RESERVED
(1024) and 65535.
If
.Ar class
is
.Dq none
or no arguments are given, disable this.
.It Sy rateget Ar class Ar rate
Set the maximum get (RETR) transfer rate throttle for
Set the maximum get
.Pq Sy RETR
transfer rate throttle for
.Ar class
to
.Ar rate
@ -373,28 +432,42 @@ bytes per second.
If
.Ar rate
is 0, the throttle is disabled.
If
.Ar class
is
.Dq none
or no arguments are given, disable this.
.Pp
An optional suffix may be provided, which changes the intrepretation of
.Ar rate
as follows:
.Bl -tag -width 3n -offset indent -compact
.It b
Causes no modification. (Optional)
Causes no modification. (Default; optional)
.It k
Kilo; multiply the argument by 1024
.It m
Mega; multiply the argument by 1048576
.It g
Giga; multiply the argument by 1073741824
.It t
Tera; multiply the argument by 1099511627776
.El
.It Sy rateput Ar class Ar rate
Set the maximum put (STOR) transfer rate throttle for
Set the maximum put
.Pq Sy STOR
transfer rate throttle for
.Ar class
to
.Ar rate
bytes per second,
which is parsed as per
.Sy rateget Ar rate .
If
.Ar class
is
.Dq none
or no arguments are given, disable this.
.It Sy sanenames Ar class Op Sy off
If
.Ar class
@ -432,23 +505,24 @@ 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 .
Ignored if
If
.Ar class
is
.Dq none
or
.Ar time
is not specified.
is not specified, set to the default of 15 minutes.
.It Sy umask Ar class Ar umaskval
Set the umask to
.Ar umaskval .
Ignored if
If
.Ar class
is
.Dq none
or
.Ar umaskval
is not specified.
is not specified, set to the default of
.Li 027 .
.It Sy upload Ar class Op Sy off
If
.Ar class
@ -457,9 +531,18 @@ is
or
.Sy off
is given, disable the following commands:
APPE, STOR, and STOU,
.Sy APPE ,
.Sy STOR ,
and
.Sy STOU ,
as well as the modify commands:
CHMOD, DELE, MKD, RMD, RNFR, and UMASK.
.Sy CHMOD ,
.Sy DELE ,
.Sy MKD ,
.Sy RMD ,
.Sy RNFR ,
and
.Sy UMASK .
Otherwise, enable them.
.El
.Sh DEFAULTS

View File

@ -1,4 +1,4 @@
/* $NetBSD: version.h,v 1.26 2000/12/04 10:50:39 itojun Exp $ */
/* $NetBSD: version.h,v 1.27 2000/12/18 02:32:51 lukem Exp $ */
/*-
* Copyright (c) 1999, 2000 The NetBSD Foundation, Inc.
* All rights reserved.
@ -36,5 +36,5 @@
*/
#ifndef FTPD_VERSION
#define FTPD_VERSION "NetBSD-ftpd 20001204"
#define FTPD_VERSION "NetBSD-ftpd 20001218"
#endif