From c31e16f75ea78ef2c02ee381b2bbca920883dd9a Mon Sep 17 00:00:00 2001 From: lukem Date: Tue, 4 Dec 2001 13:54:12 +0000 Subject: [PATCH] Add two new ftpd.conf(5) directives: - 'denyquick'; deny a connection so tagged by ftpusers(5) after the USER command instead of the PASS command. whilst this might provide some info leakage of accounts names if you have some `real' or `chroot' users enabled and not others, it does prevent accidental entering of such passwords if you have all such users denied. This option is strongly recommended on anonymous-only servers. Functionality requested by Rob Windsor in [bin/12602] - 'private'; don't display class related information in the output of STAT. For paranoid admins. --- libexec/ftpd/conf.c | 12 ++- libexec/ftpd/extern.h | 12 +-- libexec/ftpd/ftpd.c | 179 +++++++++++++++++++++++---------------- libexec/ftpd/ftpd.conf.5 | 49 ++++++++++- libexec/ftpd/version.h | 4 +- 5 files changed, 171 insertions(+), 85 deletions(-) diff --git a/libexec/ftpd/conf.c b/libexec/ftpd/conf.c index dd34d4c8a60a..7a771e10d7ae 100644 --- a/libexec/ftpd/conf.c +++ b/libexec/ftpd/conf.c @@ -1,4 +1,4 @@ -/* $NetBSD: conf.c,v 1.45 2001/12/01 10:25:29 lukem Exp $ */ +/* $NetBSD: conf.c,v 1.46 2001/12/04 13:54:12 lukem Exp $ */ /*- * Copyright (c) 1997-2001 The NetBSD Foundation, Inc. @@ -38,7 +38,7 @@ #include #ifndef lint -__RCSID("$NetBSD: conf.c,v 1.45 2001/12/01 10:25:29 lukem Exp $"); +__RCSID("$NetBSD: conf.c,v 1.46 2001/12/04 13:54:12 lukem Exp $"); #endif /* not lint */ #include @@ -121,8 +121,10 @@ init_curclass(void) curclass.umask = DEFAULT_UMASK; CURCLASS_FLAGS_SET(checkportcmd); + CURCLASS_FLAGS_CLR(denyquick); CURCLASS_FLAGS_SET(modify); CURCLASS_FLAGS_SET(passive); + CURCLASS_FLAGS_CLR(private); CURCLASS_FLAGS_CLR(sanenames); CURCLASS_FLAGS_SET(upload); } @@ -330,6 +332,9 @@ parse_conf(const char *findclass) 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); @@ -446,6 +451,9 @@ parse_conf(const char *findclass) curclass.portmin = minport; curclass.portmax = maxport; + } else if (strcasecmp(word, "private") == 0) { + CONF_FLAG(private); + } else if (strcasecmp(word, "rateget") == 0) { curclass.maxrateget = 0; curclass.rateget = 0; diff --git a/libexec/ftpd/extern.h b/libexec/ftpd/extern.h index b7464ad1e37f..dc60fc0d22c7 100644 --- a/libexec/ftpd/extern.h +++ b/libexec/ftpd/extern.h @@ -1,4 +1,4 @@ -/* $NetBSD: extern.h,v 1.42 2001/07/13 05:37:49 lukem Exp $ */ +/* $NetBSD: extern.h,v 1.43 2001/12/04 13:54:12 lukem Exp $ */ /*- * Copyright (c) 1992, 1993 @@ -239,11 +239,13 @@ typedef enum { typedef enum { FLAG_checkportcmd = 1<<0, /* Check port commands */ - FLAG_modify = 1<<1, /* Allow CHMOD, DELE, MKD, RMD, RNFR, + FLAG_denyquick = 1<<1, /* Check ftpusers(5) before PASS */ + FLAG_modify = 1<<2, /* Allow CHMOD, DELE, MKD, RMD, RNFR, UMASK */ - FLAG_passive = 1<<2, /* Allow PASV mode */ - FLAG_sanenames = 1<<3, /* Restrict names of uploaded files */ - FLAG_upload = 1<<4 /* As per modify, but also allow + FLAG_passive = 1<<3, /* Allow PASV mode */ + FLAG_private = 1<<4, /* Don't publish class info in STAT */ + FLAG_sanenames = 1<<5, /* Restrict names of uploaded files */ + FLAG_upload = 1<<6, /* As per modify, but also allow APPE, STOR, STOU */ } classflag_t; diff --git a/libexec/ftpd/ftpd.c b/libexec/ftpd/ftpd.c index adfdd294ae51..46308ca6f7d0 100644 --- a/libexec/ftpd/ftpd.c +++ b/libexec/ftpd/ftpd.c @@ -1,4 +1,4 @@ -/* $NetBSD: ftpd.c,v 1.132 2001/12/01 10:25:30 lukem Exp $ */ +/* $NetBSD: ftpd.c,v 1.133 2001/12/04 13:54:12 lukem Exp $ */ /* * Copyright (c) 1997-2001 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.132 2001/12/01 10:25:30 lukem Exp $"); +__RCSID("$NetBSD: ftpd.c,v 1.133 2001/12/04 13:54:12 lukem Exp $"); #endif #endif /* not lint */ @@ -561,7 +561,8 @@ sgetpwnam(const char *name) } static int login_attempts; /* number of failed login attempts */ -static int askpasswd; /* had user command, ask for passwd */ +static int askpasswd; /* had USER command, ask for PASSwd */ +static int permitted; /* USER permitted */ static char curname[10]; /* current USER name */ /* @@ -578,6 +579,9 @@ static char curname[10]; /* current USER name */ void user(const char *name) { + char *class; + + class = NULL; if (logged_in) { switch (curclass.type) { case CLASS_GUEST: @@ -606,6 +610,9 @@ user(const char *name) #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) @@ -618,34 +625,106 @@ user(const char *name) reply(331, "Guest login ok, type your name as password."); } - if (!askpasswd && logging) - syslog(LOG_NOTICE, - "ANONYMOUS FTP LOGIN REFUSED FROM %s", remotehost); - return; - } + if (!askpasswd) { + if (logging) + syslog(LOG_NOTICE, + "ANONYMOUS FTP LOGIN REFUSED FROM %s", + remotehost); + end_login(); + goto cleanup_user; + } + name = "ftp"; + } else + pw = sgetpwnam(name); - pw = sgetpwnam(name); if (logging) strlcpy(curname, name, sizeof(curname)); + /* check user in /etc/ftpusers, and setup class */ + permitted = checkuser(_PATH_FTPUSERS, curname, 1, 0, &class); + + /* check user in /etc/ftpchroot */ + if (checkuser(_PATH_FTPCHROOT, curname, 0, 0, NULL)) { + if (curclass.type == CLASS_GUEST) { + syslog(LOG_NOTICE, + "Can't change guest user to chroot class; remove entry in %s", + _PATH_FTPCHROOT); + exit(1); + } + curclass.type = CLASS_CHROOT; + } + /* determine default class */ + if (class == NULL) { + switch (curclass.type) { + case CLASS_GUEST: + class = xstrdup("guest"); + break; + case CLASS_CHROOT: + class = xstrdup("chroot"); + break; + case CLASS_REAL: + class = xstrdup("real"); + break; + default: + syslog(LOG_ERR, "unknown curclass.type %d; aborting", + curclass.type); + abort(); + } + } + /* parse ftpd.conf, setting up various parameters */ + parse_conf(class); + /* 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", + remotehost, curname); + end_login(); + goto cleanup_user; + } + + /* if haven't asked yet (i.e, not anon), ask now */ + if (!askpasswd) { + askpasswd = 1; #ifdef SKEY - if (skey_haskey(name) == 0) { - const char *myskey; + if (skey_haskey(curname) == 0) { + const char *myskey; - myskey = skey_keyinfo(name); - reply(331, "Password [%s] required for %s.", - myskey ? myskey : "error getting challenge", name); - } else + 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.", name); + reply(331, "Password required for %s.", curname); + } - askpasswd = 1; + cleanup_user: /* * 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); } /* @@ -740,6 +819,8 @@ checkuser(const char *fname, const char *name, int def, int nofile, gid_t *groups, *ng; int gsize, i, found; + if (pw == NULL) + continue; /* no match for unknown user */ *p++ = '\0'; groups = NULL; gsize = 16; @@ -823,6 +904,8 @@ end_login(void) 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; @@ -833,10 +916,8 @@ void pass(const char *passwd) { int rval; - const char *cp, *shell; - char *class, root[MAXPATHLEN]; + char root[MAXPATHLEN]; - class = NULL; if (logged_in || askpasswd == 0) { reply(503, "Login with USER first."); return; @@ -907,22 +988,8 @@ pass(const char *passwd) } } - /* password ok; see if anything else prevents login */ - if (! checkuser(_PATH_FTPUSERS, pw->pw_name, 1, 0, &class)) { - reply(530, "User %s may not use FTP.", pw->pw_name); - if (logging) - syslog(LOG_NOTICE, "FTP LOGIN REFUSED FROM %s, %s", - remotehost, pw->pw_name); - goto bad; - } - /* if not guest user, check for valid shell */ - if ((shell = pw->pw_shell) == NULL || *shell == 0) - shell = _PATH_BSHELL; - while ((cp = getusershell()) != NULL) - if (strcmp(cp, shell) == 0) - break; - endusershell(); - if (cp == NULL && curclass.type != CLASS_GUEST) { + /* 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", @@ -955,36 +1022,6 @@ pass(const char *passwd) logged_in = 1; - /* check user in /etc/ftpchroot */ - if (checkuser(_PATH_FTPCHROOT, pw->pw_name, 0, 0, NULL)) { - if (curclass.type == CLASS_GUEST) { - syslog(LOG_NOTICE, - "Can't change guest user to chroot class; remove entry in %s", - _PATH_FTPCHROOT); - exit(1); - } - curclass.type = CLASS_CHROOT; - } - if (class == NULL) { - switch (curclass.type) { - case CLASS_GUEST: - class = xstrdup("guest"); - break; - case CLASS_CHROOT: - class = xstrdup("chroot"); - break; - case CLASS_REAL: - class = xstrdup("real"); - break; - default: - syslog(LOG_ERR, "unknown curclass.type %d; aborting", - curclass.type); - abort(); - } - } - - /* parse ftpd.conf, setting up various parameters */ - parse_conf(class); connections = 1; if (dopidfile) count_users(); @@ -1154,15 +1191,11 @@ pass(const char *passwd) curclass.classname, CURCLASSTYPE); } (void) umask(curclass.umask); - goto cleanuppass; + return; bad: /* Forget all about it... */ end_login(); - - cleanuppass: - if (class) - free(class); } void @@ -2019,7 +2052,7 @@ statcmd(void) (LLT)otb, PLURAL(otb), (LLT)total_xfers, PLURAL(total_xfers)); - if (logged_in) { + if (logged_in && !CURCLASS_FLAGS_ISSET(private)) { struct ftpconv *cp; reply(0, "%s", ""); @@ -2043,6 +2076,8 @@ statcmd(void) 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) diff --git a/libexec/ftpd/ftpd.conf.5 b/libexec/ftpd/ftpd.conf.5 index ff42342f3b04..4ee2a31530a5 100644 --- a/libexec/ftpd/ftpd.conf.5 +++ b/libexec/ftpd/ftpd.conf.5 @@ -1,4 +1,4 @@ -.\" $NetBSD: ftpd.conf.5,v 1.17 2001/07/08 07:27:14 lukem Exp $ +.\" $NetBSD: ftpd.conf.5,v 1.18 2001/12/04 13:54:13 lukem Exp $ .\" .\" Copyright (c) 1997-2001 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 July 8, 2001 +.Dd December 5, 2001 .Dt FTPD.CONF 5 .Os .Sh NAME @@ -114,7 +114,10 @@ The .Xr ftpd 8 .Sy STAT command will return the class settings for the current user as defined by -.Nm "" . +.Nm "" , +unless the +.Sy private +directive is set for the class. .Pp Each configuration line may be one of: .Bl -tag -width 4n @@ -256,6 +259,33 @@ are replaced with the requested file (sans .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 given, disable this feature, otherwise enable it. .It Sy display Ar class Op Ar file If .Ar file @@ -402,7 +432,7 @@ is .Dq none or .Sy off -is given, disallow passive +is given, prevent passive .Sy ( PASV , .Sy LPSV , and @@ -422,6 +452,17 @@ If is .Dq none or no arguments are given, disable this. +.It Sy private Ar class Op Sy off +If +.Ar class +is +.Dq none +or +.Sy off +is given, do not display class information in the output of the +.Sy STAT +command. +Otherwise, display the information. .It Sy rateget Ar class Ar rate Set the maximum get .Pq Sy RETR diff --git a/libexec/ftpd/version.h b/libexec/ftpd/version.h index 9f9c60df7dd6..8ff3a975399b 100644 --- a/libexec/ftpd/version.h +++ b/libexec/ftpd/version.h @@ -1,4 +1,4 @@ -/* $NetBSD: version.h,v 1.36 2001/12/01 10:25:30 lukem Exp $ */ +/* $NetBSD: version.h,v 1.37 2001/12/04 13:54:13 lukem Exp $ */ /*- * Copyright (c) 1999-2001 The NetBSD Foundation, Inc. * All rights reserved. @@ -36,5 +36,5 @@ */ #ifndef FTPD_VERSION -#define FTPD_VERSION "NetBSD-ftpd 20011201" +#define FTPD_VERSION "NetBSD-ftpd 20011205" #endif