From owner-freebsd-audit Thu Jan 4 9:14:53 2001 From owner-freebsd-audit@FreeBSD.ORG Thu Jan 4 09:14:42 2001 Return-Path: Delivered-To: freebsd-audit@freebsd.org Received: from athena.cs.vt.edu (athena.cs.vt.edu [128.173.40.29]) by hub.freebsd.org (Postfix) with ESMTP id 863E037B400; Thu, 4 Jan 2001 09:14:41 -0800 (PST) Received: (from dhagan@localhost) by athena.cs.vt.edu (8.11.1/8.11.1) id f04HEfB75136; Thu, 4 Jan 2001 12:14:41 -0500 (EST) (envelope-from dhagan) Date: Thu, 4 Jan 2001 12:14:41 -0500 (EST) Message-Id: <200101041714.f04HEfB75136@athena.cs.vt.edu> To: freebsd-audit@freebsd.org Subject: Re: ftpd and anonymous setup (modified ftpd) From: Daniel Hagan Reply-To: Daniel Hagan Sender: Daniel Hagan Cc: freebsd-security@freebsd.org Sender: owner-freebsd-audit@FreeBSD.ORG Precedence: bulk X-Loop: FreeBSD.ORG Here's a quick patch that includes the chroot/cwd patch mentioned earlier and a login.conf capability to set a session to read-only. [Apologies if you receive this twice, I think it got bounced at freebsd.org] Daniel Index: ftpcmd.y =================================================================== RCS file: /raid/ncvs/src/libexec/ftpd/ftpcmd.y,v retrieving revision 1.19 diff -u -r1.19 ftpcmd.y --- ftpcmd.y 2000/12/16 19:19:19 1.19 +++ ftpcmd.y 2001/01/04 15:55:42 @@ -92,6 +92,8 @@ extern char tmpline[]; extern int readonly; extern int noepsv; +extern int dochroot; +extern char *cd_dir, *chroot_dir; off_t restart_point; @@ -505,8 +507,11 @@ | CWD check_login CRLF { if ($2) { - if (guest) - cwd("/"); + if (guest || dochroot) + if (cd_dir != NULL) + cwd(cd_dir); + else + cwd("/"); else cwd(pw->pw_dir); } Index: ftpd.8 =================================================================== RCS file: /raid/ncvs/src/libexec/ftpd/ftpd.8,v retrieving revision 1.36 diff -u -r1.36 ftpd.8 --- ftpd.8 2000/12/18 08:33:25 1.36 +++ ftpd.8 2001/01/04 16:58:49 @@ -158,6 +158,10 @@ .It Fl r Put server in read-only mode. All commands which may modify the local filesystem are disabled. +Read-only mode may be set on a per account basis in +.Xr login.conf 5 +with the boolean capability "ftp-readonly". Once set in a session +it cannot be cleared (i.e. by USER). .It Fl E Disable the EPSV command. This is useful for servers behind older firewalls. @@ -311,13 +315,14 @@ or the user is a member of a group with a group entry in this file, i.e. one prefixed with .Ql \&@ , -the session's root will be changed to the user's login directory by +the session's root will be changed to the user's login directory (up to the first /./) by .Xr chroot 2 as for an .Dq anonymous or .Dq ftp account (see next item). +The user is placed into the directory that remainds after stripping the former from the user's login directory. This facility may also be triggered by enabling the boolean "ftp-chroot" capability in .Xr login.conf 5 . Index: ftpd.c =================================================================== RCS file: /raid/ncvs/src/libexec/ftpd/ftpd.c,v retrieving revision 1.72 diff -u -r1.72 ftpd.c --- ftpd.c 2000/12/20 03:34:54 1.72 +++ ftpd.c 2001/01/04 17:00:42 @@ -140,6 +140,7 @@ int anon_only = 0; /* Only anonymous ftp allowed */ int guest; int dochroot; +char *cd_dir = NULL, *chroot_dir = NULL; int stats; int statfd = -1; int type; @@ -188,6 +189,9 @@ char *pid_file = NULL; +/* WARNING: FTP_CHROOT_SEPARATOR *MUST* end in / */ +#define FTP_CHROOT_SEPARATOR "/./" + /* * Timeout intervals for retrying connections * to hosts that don't accept PORT cmds. This @@ -251,6 +255,7 @@ static char *sgetsave __P((char *)); static void reapchild __P((int)); static void logxfer __P((char *, long, long)); +static void get_chroot_and_cd_dirs __P((char *, char **, char **)); static char * curdir() @@ -1038,6 +1043,13 @@ logged_in = 0; guest = 0; dochroot = 0; + /* + * do not reset readonly to 0 b/c once session is ro, we leave it + * that way for security's sake. + */ + free(chroot_dir); + free(cd_dir); + chroot_dir = cd_dir = NULL; } #if !defined(NOPAM) @@ -1291,19 +1303,24 @@ login_getcapbool(lc, "ftp-chroot", 0) || #endif checkuser(_PATH_FTPCHROOT, pw->pw_name, 1); - if (guest) { +#ifdef LOGIN_CAP /* Check for ftp-readonly */ + if (readonly = 0) + readonly = login_getcapbool(lc, "ftp-readonly", 0); +#endif + if (guest || dochroot) { /* * We MUST do a chdir() after the chroot. Otherwise * the old current directory will be accessible as "." * outside the new root! */ - if (chroot(pw->pw_dir) < 0 || chdir("/") < 0) { - reply(550, "Can't set guest privileges."); - goto bad; - } - } else if (dochroot) { - if (chroot(pw->pw_dir) < 0 || chdir("/") < 0) { - reply(550, "Can't change root."); + get_chroot_and_cd_dirs(pw->pw_dir, &chroot_dir, &cd_dir); + /* + * Do not free chroot_dir & cd_dir b/c they are used in + * processing CWD commands from client. They should be + * free'd during a user logout. + */ + if (chroot(chroot_dir) < 0 || chdir(cd_dir) < 0) { + reply(550, guest ? "Can't set guest privileges." : "Can't change root."); goto bad; } } else if (chdir(pw->pw_dir) < 0) { @@ -2802,5 +2819,50 @@ ctime(&now)+4, ident, remotehost, path, name, size, now - start + (now == start)); write(statfd, buf, strlen(buf)); + } +} + +/* + * Make a pointer to the chroot dir and another to the cd dir. + * The first is all the path up to the first FTP_CHROOT_SEPARATOR. + * The later is the remaining chars, not including the FTP_CHROOT_SEPARATOR, + * but prepending a '/', if FTP_CHROOT_SEPARATOR is found. + * Otherwise, return user_home_dir as chroot_dir and "/" as cd_dir. + */ +static void +get_chroot_and_cd_dirs(user_home_dir, chroot_dir, cd_dir) + char *user_home_dir; + char **chroot_dir; + char **cd_dir; +{ + char *p; + + /* Make a pointer to first character of string FTP_CHROOT_SEPARATOR + inside user_home_dir. */ + p = (char *) strstr(user_home_dir, FTP_CHROOT_SEPARATOR); + if (p == NULL) { + /* + * There is not FTP_CHROOT_SEPARATOR string inside + * user_home_dir. Return user_home_dir as chroot_dir, + * and "/" as cd_dir. + */ + if ((*chroot_dir = (char *) strdup(user_home_dir)) == NULL) + fatal("Ran out of memory."); + if ((*cd_dir = (char *) strdup("/")) == NULL) + fatal("Ran out of memory."); + } else { + /* + * Use strlen(user_home_dir) as maximun length for + * both cd_dir and chroot_dir, as both are substrings of + * user_home_dir. + */ + if ((*chroot_dir = malloc(strlen(user_home_dir))) == NULL) + fatal("Ran out of memory."); + if ((*cd_dir = malloc(strlen(user_home_dir))) == NULL) + fatal("Ran out of memory."); + (void) strncpy(*chroot_dir, user_home_dir, p-user_home_dir); + /* Skip FTP_CHROOT_SEPARATOR (except the last /). */ + p += strlen(FTP_CHROOT_SEPARATOR)-1; + (void) strncpy(*cd_dir, p, strlen(p)); } } To Unsubscribe: send mail to majordomo@FreeBSD.org with "unsubscribe freebsd-audit" in the body of the message