Bug 23944

Summary: Patch for ftpd to add a cd after the chroot.
Product: Base System Reporter: fschapachnik <fschapachnik>
Component: binAssignee: Yar Tikhiy <yar>
Status: Closed FIXED    
Severity: Affects Only Me    
Priority: Normal    
Version: 4.2-RELEASE   
Hardware: Any   
OS: Any   
Attachments:
Description Flags
file.diff
none
ftpd.patch-4.5
none
ftpd.patch-4.6.1 none

Description fschapachnik 2000-12-29 16:20:01 UTC
	A patch is supplied to add similar functionality to
	ftpd.

How-To-Repeat: 
	See patch.
Comment 1 dhagan 2001-01-04 16:05:58 UTC
I've rewritten some of your patch to not use dangling pointers.  I also 
added the cd_dir functionality to the ftp guest account, not because I
think it's a great idea, but because it will be consistent behavior.
(If someone had specified /info/ftp/./pub as HOME for ftp, your patch would
have chroot'd ftp into /info/ftp/pub, instead of /info/ftp.  And this 'bug'
would only appear for the ftp account.  ;-))

In the event that this mail doesn't get munged the way I'd like, please 
respond to dhagan@colltech.com

Thanks,

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 15:46:24
@@ -311,13 +311,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 15:56:59
@@ -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,8 @@
 	logged_in = 0;
 	guest = 0;
 	dochroot = 0;
+       free(chroot_dir);
+       free(cd_dir);
 }
 
 #if !defined(NOPAM)
@@ -1291,19 +1298,20 @@
 		login_getcapbool(lc, "ftp-chroot", 0) ||
 #endif
 		checkuser(_PATH_FTPCHROOT, pw->pw_name, 1);
-	if (guest) {
+	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 +2810,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));
 	}
 }
Comment 2 dhagan 2001-01-04 16:13:02 UTC
Oh, forgot to mention -- this is against -CURRENT not -STABLE.

Daniel

Daniel Hagan wrote:
> 
> I've rewritten some of your patch to not use dangling pointers.  I also
Comment 3 dhagan 2001-01-04 21:58:24 UTC
Here's a patch that addresses several different PRs and discussions
recently concerning ftpd.  Available at
http://vtopus.cs.vt.edu/~dhagan/freebsd/ftpd.patch

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 20:16:35
@@ -91,7 +91,10 @@
 extern  int transflag;
 extern  char tmpline[];
 extern int readonly;
+extern int readonly_user;
 extern int noepsv;
+extern int dochroot;
+extern char *cd_dir, *chroot_dir;
 
 off_t  restart_point;
 
@@ -505,8 +508,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);
                        }
@@ -979,6 +985,10 @@
                {
                if (readonly) {
                        reply(202, "Command ignored. Server is in
readonly mode.");
+                       $$ = 0;
+               }
+               else if (readonly_user) {
+                       reply(202, "Command ignored. User is in readonly
session.");
                        $$ = 0;
                }
                else
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 20:23:27
@@ -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
+.Dq ftp-readonly .
 .It Fl E
 Disable the EPSV command.
 This is useful for servers behind older firewalls.
@@ -258,8 +262,12 @@
 .Pp
 The ftp server will abort an active file transfer only when the
 ABOR
-command is preceded by a Telnet "Interrupt Process" (IP)
-signal and a Telnet "Synch" signal in the command Telnet stream,
+command is preceded by a Telnet
+.Dq "Interrupt Process"
+.Pq IP
+signal and a Telnet
+.Dq Synch
+signal in the command Telnet stream,
 as described in Internet RFC 959.
 If a
 STAT
@@ -299,7 +307,8 @@
 .It
 The login name must not be a member of a group specified in the file
 .Pa /etc/ftpusers .
-Entries in this file interpreted as group names are prefixed by an "at"
+Entries in this file interpreted as group names are prefixed by an
+.Dq at
 .Ql \&@
 sign.
 .It
@@ -311,14 +320,19 @@
 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
+.Pq "up to the first /./"
+by
 .Xr chroot 2
 as for an
 .Dq anonymous
 or
 .Dq ftp
 account (see next item).
-This facility may also be triggered by enabling the boolean
"ftp-chroot"
+The user is placed into the directory that remains after stripping the
+former from the user's login directory.
+This facility may also be triggered by enabling the boolean
+.Dq ftp-chroot
 capability in
 .Xr login.conf 5 .
 However, the user must still supply a password.
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 20:11:48
@@ -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;
@@ -149,6 +150,7 @@
 int    usedefault = 1;         /* for data transfers */
 int    pdata = -1;             /* for passive mode */
 int    readonly=0;             /* Server is in readonly mode.  */
+int    readonly_user = 0;      /* User's session is readonly. */
 int    noepsv=0;               /* EPSV command is disabled.    */
 sig_atomic_t transflag;
 off_t  file_size;
@@ -188,6 +190,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 +256,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 +1044,10 @@
        logged_in = 0;
        guest = 0;
        dochroot = 0;
+       readonly_user = 0;
+       free(chroot_dir);
+       free(cd_dir);
+       chroot_dir = cd_dir = NULL;
 }
 
 #if !defined(NOPAM)
@@ -1291,19 +1301,23 @@
                login_getcapbool(lc, "ftp-chroot", 0) ||
 #endif
                checkuser(_PATH_FTPCHROOT, pw->pw_name, 1);
-       if (guest) {
+#ifdef LOGIN_CAP       /* Check for ftp-readonly */
+               readonly_user = 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 +2816,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));
        }
 }
Comment 4 fschapachnik 2002-02-12 20:08:05 UTC
Hi,
	Here it is an updated version of the patch against
4.5-RELEASE. I'd like to see it come into the main tree.

	Regards.


Fernando P. Schapachnik
Gerente de tecnología de red
y sistemas de información
VIA NET.WORKS ARGENTINA S.A.
fschapachnik@vianetworks.com.ar
Tel.: (54-11) 4323-3381
Comment 5 Johan Karlsson freebsd_committer freebsd_triage 2002-08-21 20:54:32 UTC
Responsible Changed
From-To: freebsd-bugs->yar

Another ftpd PR with alot of patches.
Comment 6 Yar Tikhiy freebsd_committer freebsd_triage 2003-01-29 09:02:07 UTC
Hi Fernando,

I'm installing a patch to FreeBSD-current that should add the feature
you proposed 2 years ago to ftpd(8) (it's a shame it took us that
long to deal with your proposal.)  Could you test my solution in
-current, or shall I provide a patch against 4.7-stable for your
convenience?

-- 
Yar
Comment 7 Yar Tikhiy freebsd_committer freebsd_triage 2003-01-29 10:10:36 UTC
State Changed
From-To: open->patched

Implemented in -current.
Comment 8 Fernando Schapachnik 2003-01-29 13:28:01 UTC
Hi Yar,
	I'm relocated now. Unfortunatedly all I have are RELENG_4_7 machines.

	I'd gladly test it. I can also provide my current patches, in case you
need them.

	Kind regards!



Lic. Fernando Schapachnik
Proyecto de Informática
Ministerio de Economía
----- Forwarded message from Hernan Nunez <hnunez@vianetworks.com.ar> -----

X-Sieve: cmu-sieve 1.3
From: Hernan Nunez <hnunez@vianetworks.com.ar>
Reply-To: <hnunez@vianetworks.com.ar>
To: "Yar Tikhiy" <yar@FreeBSD.org>
Cc: "Fernando Schapachnik" <fernando@mecon.gov.ar>
Subject: Re: bin/23944: Patch for ftpd to add a cd after the chroot.
Date: Wed, 29 Jan 2003 10:20:35 -0300
X-Priority: 3
X-MSMail-Priority: Normal
X-Mailer: Microsoft Outlook Express 6.00.2800.1106
X-MimeOLE: Produced By Microsoft MimeOLE V6.00.2800.1106

Yar,

 You could contact Fernando at fernando@mecon.gov.ar. 



----- Original Message ----- 
From: "Yar Tikhiy" <yar@FreeBSD.org>
To: <freebsd-gnats-submit@FreeBSD.org>; <fpscha@ns1.via-net-works.net.ar>
Sent: Wednesday, January 29, 2003 6:02 AM
Subject: Re: bin/23944: Patch for ftpd to add a cd after the chroot.


> Hi Fernando,
> 
> I'm installing a patch to FreeBSD-current that should add the feature
> you proposed 2 years ago to ftpd(8) (it's a shame it took us that
> long to deal with your proposal.)  Could you test my solution in
> -current, or shall I provide a patch against 4.7-stable for your
> convenience?
> 
> -- 
> Yar
> 

----- End forwarded message -----
Comment 9 Yar Tikhiy freebsd_committer freebsd_triage 2003-01-29 16:41:45 UTC
Fernando,

On Wed, Jan 29, 2003 at 10:28:01AM -0300, Fernando Schapachnik wrote:
>
> I'm relocated now. Unfortunatedly all I have are RELENG_4_7 machines.
> 
> I'd gladly test it. I can also provide my current patches, in case you
> need them.

Thank you.  I'll make a patch against RELENG_4_7 and send it to you ASAP.
As for your current patches, may I have a look at them?

-- 
Yar
Comment 10 Fernando Schapachnik 2003-01-29 17:09:13 UTC
En un mensaje anterior, Yar Tikhiy escribió:
> Thank you.  I'll make a patch against RELENG_4_7 and send it to you ASAP.
> As for your current patches, may I have a look at them?

Here you have it.

They are against 4.6.1, but I think there aplied cleanly against 4.7.

Kind regards.

Fernando.
Comment 11 Yar Tikhiy freebsd_committer freebsd_triage 2003-02-11 14:52:26 UTC
State Changed
From-To: patched->closed

The proposed feature has been merged to STABLE as well.  Thanks!