Subject: per-user initial directories for ftpd
To: None <tech-userlevel@netbsd.org>
From: Michael Graff <explorer@flame.org>
List: tech-userlevel
Date: 04/27/1999 14:19:08
I had a need to allow per-user initial directories, which ideally
would be fully anonymous sites, but that is harder.  I don't allow
normal users to ftp in at all, since I don't want cleartext passwords
flying around the net.

Logging in with
	ftp.userid
or
	anonymous.userid
depending on which is in /etc/ftpusers, will start the user out
somewhere other than /.

Note that the "userid" portion need not match a real user on your
system.  It could be a random string, which would keep people from
guessing them easily and just cd'ing into them.

I start ftpd with -A userftp, and when ftp.explorer comes in, the
initial directory is ~ftp/userftp/explorer rather than ~ftp/.

Here are the diffs.  If this isn't considered Really Bad, would others
find it, or something like it, useful?

/etc/ftpd.conf changes are that a new class, "guestish" is added, for
lack of a better name.  The defaults are identical to the defaults for
class "guest" but having them separate allows different umasks, etc.

--Michael

Index: conf.c
===================================================================
RCS file: /cvsroot/src/libexec/ftpd/conf.c,v
retrieving revision 1.18
diff -u -r1.18 conf.c
--- conf.c	1999/02/24 16:45:13	1.18
+++ conf.c	1999/04/27 19:53:44
@@ -110,7 +110,8 @@
 	curclass.timeout =	900;		/* 15 minutes */
 	curclass.umask =	027;
 
-	if (strcasecmp(findclass, "guest") == 0) {
+	if (strcasecmp(findclass, CLASS_GUEST) == 0
+	    || strcasecmp(findclass, CLASS_GUESTISH) == 0) {
 		curclass.modify = 0;
 		curclass.umask = 0707;
 	}
Index: extern.h
===================================================================
RCS file: /cvsroot/src/libexec/ftpd/extern.h,v
retrieving revision 1.15
diff -u -r1.15 extern.h
--- extern.h	1998/12/28 04:54:01	1.15
+++ extern.h	1999/04/27 19:53:44
@@ -71,6 +71,7 @@
 
 #define CLASS_CHROOT	"chroot"
 #define CLASS_GUEST	"guest"
+#define CLASS_GUESTISH	"guestish"
 #define CLASS_REAL	"real"
 
 struct ftpconv {
@@ -102,6 +103,7 @@
 extern	int		debug;
 extern	int		form;
 extern	int		guest;
+extern	int		guestish;
 extern	int		hasyyerrored;
 extern	struct sockaddr_in his_addr;
 extern	char		hostname[];
Index: ftpd.8
===================================================================
RCS file: /cvsroot/src/libexec/ftpd/ftpd.8,v
retrieving revision 1.35
diff -u -r1.35 ftpd.8
--- ftpd.8	1999/03/22 18:25:44	1.35
+++ ftpd.8	1999/04/27 19:53:44
@@ -64,6 +64,16 @@
 .Xr chroot 2
 into for anonymous logins.
 Default is the home directory for the ftp user.
+.It Fl A
+Define the initial directory for "guestish" logins.  These are logins of the
+form "ftp.user" or "anonymous.user".  They behave mostly as a normal guest
+login, other than the
+.Pa ftpusers
+file must list the exact name used, and
+.Pa ftpd.conf
+should have lines for class "guestish" to define the default permissions, etc.
+This directory should be owned by root:wheel, and be mode 111.  The individial
+user directories should be owned by user:ftp, and be mode 770.
 .It Fl c
 Change the root directory of the configuration files from
 .Dq Pa /etc
@@ -304,7 +314,11 @@
 .Dq "ftp allow"
 to
 .Pa /etc/ftpusers
-in order to allow guest logins.
+in order to allow guest logins.  If you allow per-user anonymous initial
+directories, you must add those as well, in the form of
+.Dq "ftp.username allow"
+and/or
+.Dq "anonymous.username allow" .
 .Ss /etc/ftpchroot
 The file
 .Pa /etc/ftpchroot
@@ -482,6 +496,8 @@
 and
 .Dq ftp
 users.
+.It Sy guestish
+Per-user initial guest login.
 .It Sy all
 Matches any class.
 .It Sy none
@@ -492,14 +508,16 @@
 .Bd -literal -offset indent -compact
 checkportcmd  none
 display       none
-maxtimeout    all    7200   # 2 hours
+maxtimeout    all       7200   # 2 hours
 modify        all
-modify        guest  off
+modify        guest      off
+modify        guestish   off
 notify        none
 passive       all
-timeout       all    900    # 15 minutes
-umask         all    027
-umask         guest  0707
+timeout       all        900    # 15 minutes
+umask         all        027
+umask         guest     0707
+umask         guestish  0707
 .Ed
 .Pp
 Directives that appear later in the file override settings by previous
Index: ftpd.c
===================================================================
RCS file: /cvsroot/src/libexec/ftpd/ftpd.c,v
retrieving revision 1.61
diff -u -r1.61 ftpd.c
--- ftpd.c	1998/12/28 04:54:01	1.61
+++ ftpd.c	1999/04/27 19:53:44
@@ -121,6 +121,7 @@
 int	sflag;
 int	logging;
 int	guest;
+int	guestish;
 int	dochroot;
 int	type;
 int	form;
@@ -138,6 +139,8 @@
 char	*tty = ttyline;		/* for klogin */
 
 static char *anondir = NULL;
+static char *anonishdir = NULL;
+static char *initialdir = NULL;
 static char confdir[MAXPATHLEN];
 
 #if defined(KERBEROS) || defined(KERBEROS5)
@@ -199,12 +202,17 @@
 	sflag = 0;
 	(void)strcpy(confdir, _DEFAULT_CONFDIR);
 
-	while ((ch = getopt(argc, argv, "a:c:C:dlst:T:u:v")) != -1) {
+	while ((ch = getopt(argc, argv, "a:A:c:C:dlst:T:u:v")) != -1) {
 		switch (ch) {
 		case 'a':
 			anondir = optarg;
 			break;
 
+		case 'A':
+			anonishdir = optarg;
+			syslog(LOG_NOTICE, "anonishdir == %s", anonishdir);
+			break;
+
 		case 'c':
 			(void)strncpy(confdir, optarg, sizeof(confdir));
 			confdir[sizeof(confdir)-1] = '\0';
@@ -395,6 +403,8 @@
 user(name)
 	char *name;
 {
+	char *tname;
+
 	if (logged_in) {
 		if (guest) {
 			reply(530, "Can't change user from guest login.");
@@ -410,15 +420,47 @@
 	kdestroy();
 #endif
 
+	/*
+	 * If the name is of the format "ftp.user" or "anonymous.user"
+	 * put the user in ~ftp/anonishdir/user initially.  "anonishdir"
+	 * is set via the -A option on the ftpd command line, and is
+	 * relative to ~ftp.
+	 *
+	 * ~ftp/userftp should be owned by root, and be mode 111.
+	 */
 	guest = 0;
-	if (strcmp(name, "ftp") == 0 || strcmp(name, "anonymous") == 0) {
-		if (checkaccess("ftp") || checkaccess("anonymous"))
+	guestish = 0;
+	if (strcmp(name, "ftp") == 0
+	    || strcmp(name, "anonymous") == 0
+	    || strncmp(name, "ftp.", 4) == 0
+	    || strncmp(name, "anonymous.", 10) == 0) {
+		if (checkaccess("ftp")
+		    || checkaccess("anonymous")
+		    || checkaccess(name))
 			reply(530, "User %s access denied.", name);
 		else if ((pw = sgetpwnam("ftp")) != NULL) {
 			guest = 1;
 			askpasswd = 1;
 			reply(331,
-			    "Guest login ok, type your name as password.");
+			      "Guest login ok, type your name as password.");
+			tname = strchr(name, '.');
+			if (tname != NULL && anonishdir != NULL) {
+				tname++;
+				if (*tname != '\0') {
+					char *t;
+
+					t = malloc(strlen(tname)
+						   + strlen(anonishdir)
+						   + 3);
+					sprintf(t, "/%s/%s",
+						anonishdir, tname);
+					initialdir = t;
+					guestish = 1;
+					syslog(LOG_NOTICE,
+					       "Initial directory %s", t);
+				}
+			}
+			
 		} else
 			reply(530, "User %s unknown.", name);
 		if (!askpasswd && logging)
@@ -533,6 +575,11 @@
 	pw = NULL;
 	logged_in = 0;
 	guest = 0;
+	guestish = 0;
+	if (initialdir != NULL) {
+		free(initialdir);
+		initialdir = NULL;
+	}
 	dochroot = 0;
 }
 
@@ -649,8 +696,10 @@
 	dochroot = checkuser(_PATH_FTPCHROOT, pw->pw_name, FALSE, FALSE);
 
 	/* parse ftpd.conf, setting up various parameters */
-	if (guest)
+	if (guest && !guestish)
 		parse_conf(CLASS_GUEST);
+	else if (guest && guestish)
+		parse_conf(CLASS_GUESTISH);
 	else if (dochroot)
 		parse_conf(CLASS_CHROOT);
 	else
@@ -663,8 +712,12 @@
 		 * the old current directory will be accessible as "."
 		 * outside the new root!
 		 */
-		if (chroot(anondir ? anondir : pw->pw_dir) < 0 ||
-		    chdir("/") < 0) {
+		if (chroot(anondir ? anondir : pw->pw_dir) < 0
+			|| chdir("/") < 0) {
+			reply(550, "Can't set guest privileges.");
+			goto bad;
+		}
+		if ((initialdir != NULL) && (chdir(initialdir) < 0)) {
 			reply(550, "Can't set guest privileges.");
 			goto bad;
 		}