#####################################################################
# This patch provides a chroot+sftp hack.  The patch was created
# against OpenSSH source version 3.0.2p1.  It has only been tested
# on Debian (woody) Linux.
#
# See the sections below on CAVEATS and SECURITY before using.
#
# save this patch file to ~root/chroot+sftp-server.patch
# and follow the INSTALL instructions.
#
### INSTALL #########################################################
#
# On Debian systems, try this:
#
#      root:~# apt-get source ssh
#      root:~# cd ./openssh-3.0.2p1
#      root:~# patch -p0 < ../chroot+sftp-server.patch
#      root:~# debian/rules build
#      root:~# debian/rules binary
#      root:~# cd ..
#      root:~# dpkg -i ./ssh_3.0.2p1-x_i386.deb
#      root:~# chmod +s /usr/lib/sftp-server
# 
# On non-Debian systems, you can get the OpenSSH source from
# www.openssh.org and do something along these lines:
#
#      root:~# tar xvzf openssh-3.x.tar.gz
#      root:~# cd openssh-3x
#      root:~# patch -p0 < ../chroot+sftp-server.patch
#      root:~# ./configure
#      root:~# make
#      root:~# make install
#      root:~# chmod +s /usr/libexec/openssh/sftp-server
#
# You will need to add the following line to your sshd_config
# file (usually /etc/ssh/sshd_config) to have sshd invoke the
# sftp subprocess.
#
#      Subsystem   sftp    /<install-path-of>/sftp-server
#
# The patch file is small and could be applied by hand if the
# patch fails.  It only modifies sftp-server.c.
#
# Note that you need to make the sftp-server executable SUID.
# This is so that the sftp-server subprocess can internally call
# the chroot(2) function.  After making the call, the sftp-subprocess
# drops root privileges by calling setuid(2) and setgid(2) to
# become the non-privleged user that is invoking sftp.
# 
# The hacked sftp-server should continue to work in the normal
# way for non-chroot'd users.  A special entry in the /etc/passwd
# file tells the sftp-server subprocess wether or not to place
# the user in the chroot'd environment.  Users without the special
# entry will work just as they do under the non-hacked version
# of sftp-server.
# 
### SETUP ###########################################################
# 
# Setting up a user environment for chroot'd sftp access
# ------------------------------------------------------
# 1) create a new user (I'm using "foo") and set its password
#      root:~# adduser foo
#
# 2) set up the new user's directory.  the "newroot" directory
#    will be foo's new home directory and that is the only area
#    that we want owned/writable by user foo.  Everything else
#    should be owned by root and not writable/readable by anyone
#    including foo.  This includes all dot-file (.bashrc,.profile)
#
#      root:~# cd ~foo
#      root:~# mkdir newroot
#      root:~# chown -R root.root .
#      root:~# chmod -R u+rw,go-rwx .
#      root:~# chmod 555 newroot
#
#    The hacked chroot+sftp will write a warning to the system logs
#    and abort if the "newroot" directory is not owned by root.
#    You can make sub-directories below ~foo/newroot that are owned
#    and writable by the foo user.  These subdirectories are the
#    foo user's playground.
#
#    For example, you can create the following to provide a r/w and
#    ro area.
#
#      root:~# mkdir -p ~foo/newroot/incoming
#      root:~# mkdir -p ~foo/newroot/pub
#      root:~# chown foo.foo ~foo/newroot/incoming
#      root:~# chmod 755 ~foo/newroot/incoming
#      root:~# chmod 555 ~foo/newroot/pub
#
#    User foo can upload files to the incoming area, but can only
#    download files from pub.
# 
# 3) Edit the new user's /etc/passwd entry to make it look similar
#    to the example below.  Note the "/./" trailing foo's home
#    directory (/home/foo/newroot).  This is the special tag to tell
#    the sftp-server subprocess that this is a chroot'd user.  Note
#    also that we set the user's shell to the actual sftp-server
#    program which should be SUID root.
#
#    foo:x:501:501:Foo,,,:/home/foo/newroot/./:/usr/lib/sftp-server
#
#    Recall that any other users in /etc/passwd that DO NOT have the
#    special "/./" indicator are allowed to use the sftp-server
#    subprocess in the normal way!
#
#    NOTE: I did NOT need to add anything to /etc/shells to get this
#    to work.
# 
### ROBUSTNESS ######################################################
#
# I've tried to make the hack semi-robust regarding its new ability.
#  - If the sftp-server executable is not SUID, it should operate
#    in its original form.
#
#  - If the sftp-server is not SUID and a user entry contains the
#    "/./" indicator, they are dropped into the directory specified
#    below the indicator.  i.e. "/home/foo/newroot/./" becomes
#    "/home/foo/newroot" as their starting point.
#
#  - If a user does not contain "/./" in /etc/passwd, it should
#    operate in its original form regardless of the sftp-server
#    being SUID or not.
#
### CAVEATS #########################################################
#
# Any user containing the special sequence "/./" in their /etc/passwd
# entry will not be able to use the system for anything except
# the chroot'd sftp service.  This is because their shell is set to
# "sftp-server" rather than /bin/bash, /bin/sh, /usr/bin/csh, etc.
# Therefore, the users you want to limit via chroot and still have
# access to the system will require two separate accounts.
#
# In most "chroot" environments, it is necessary to re-create a mini
# root filesystem with entries like /etc, /dev, /bin, and /lib.  This
# is not the case with this hack.  Since the sftp-server subprocess
# is invoked as the user's shell, there is no need for the chroot'd
# environment to contain a mini system.
#
### SECURITY ########################################################
#
# WARNING! This patch is not supported by the OpenSSH team and is used
# by me for internal use only.  Use this patch at your own risk!  I am
# not responsible for any benefits or consequences to using this patch.
# YOU HAVE BEEN WARNED!  This software is provided as-is.
# 
# With that being said....
#
# I think I've gotten most of the security holes fixed from the
# original patch submitted to the openssh-unix-dev mailing list, but
# I'm no expert by a long stretch and further suggestions for
# improvement are welcome.
#
# Most notable improvements are:
#  - don't rely on HOME environment variables, pull from /etc/passwd
#  - make sure the new root is owned by root and is a directory
#
# However, since the sftp-server is now SUID, you want to make sure
# to do whatever you can to limit user access to it.  For example,
# if you can, I suggest you block access to port 22 accept from
# the remote IPs of your users.
# 
#####################################################################

--- sftp-server.c	Wed Jan 30 13:57:50 2002
+++ chroot-sftp-server.c	Wed Jan 30 13:58:04 2002
@@ -33,6 +33,16 @@
 #include "sftp.h"
 #include "sftp-common.h"
 
+#define CHROOT
+#ifdef CHROOT /* needed for getpwuid */
+#include <syslog.h>
+#include <pwd.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <string.h>
+#endif /* CHROOT */
+
 /* helper */
 #define get_int64()			buffer_get_int64(&iqueue);
 #define get_int()			buffer_get_int(&iqueue);
@@ -1028,9 +1038,101 @@
 	}
 }
 
+#ifdef CHROOT
+int
+chroot_init(void)
+{
+	int can_chroot=1;
+	char *user_dir, *new_root;
+	struct passwd *passent;
+	struct stat st_root;
+
+	if (geteuid() != 0) {
+		/* if we're not SUID root, then we can't call chroot but we can
+     * go ahead and allow "regular" sftp access.  If the requesting
+     * user has the "/./" in /etc/passwd, we'll strip that and use
+     * the base as the new home dir. */
+		openlog("chroot+sftp-server",LOG_NDELAY|LOG_PID,LOG_DAEMON);
+		syslog(LOG_DAEMON|LOG_ERR,"must be SUID root for the CHROOT hack");
+		closelog();
+		can_chroot=0;
+	}
+
+	/* grab HOME from /etc/passwd */
+	if ((passent = getpwuid(getuid())) == NULL) {
+		openlog("chroot+sftp-server",LOG_NDELAY|LOG_PID,LOG_DAEMON);
+		syslog(LOG_DAEMON|LOG_ERR,"could not obtain home directory");
+		closelog();
+		fatal("could not obtain home directory from getpwuid()");
+	}
+
+	user_dir = passent->pw_dir;
+	new_root = user_dir + 1;
+
+	while ((new_root = strchr(new_root, '.')) != NULL) {
+		new_root--;
+		if (strncmp(new_root, "/./", 3) == 0) {
+			*new_root = '\0';
+			new_root += 2;
+
+			/* do some sanity checking on new_root */
+			if ((stat(user_dir,&st_root)) != 0) {
+				openlog("chroot+sftp-server",LOG_NDELAY|LOG_PID,LOG_DAEMON);
+				syslog(LOG_DAEMON|LOG_ERR,"can't stat %s: %s",user_dir,strerror(errno));
+				closelog();
+				fatal("can't stat %s: %s",user_dir,strerror(errno));
+			}
+			if (!S_ISDIR(st_root.st_mode)) {
+				openlog("chroot+sftp-server",LOG_NDELAY|LOG_PID,LOG_DAEMON);
+				syslog(LOG_DAEMON|LOG_ERR,"%s not a directory",user_dir);
+				closelog();
+				fatal("%s not a directory",user_dir);
+			}
+			if (st_root.st_uid != 0 && can_chroot) {
+				openlog("chroot+sftp-server",LOG_NDELAY|LOG_PID,LOG_DAEMON);
+				syslog(LOG_DAEMON|LOG_ERR,"owner %s uid not root: %d",user_dir,st_root.st_uid);
+				closelog();
+				fatal("owner of %s is uid %d, not root's uid",user_dir,st_root.st_uid);
+			}
+			if (st_root.st_gid != 0 && can_chroot) {
+				openlog("chroot+sftp-server",LOG_NDELAY|LOG_PID,LOG_DAEMON);
+				syslog(LOG_DAEMON|LOG_ERR,"owner %s gid not root: %d",user_dir,st_root.st_gid);
+				closelog();
+				fatal("group of %s is gid %d, not root's gid",user_dir,st_root.st_gid);
+			}
+
+			/* ok, it is a dir and root owns it, let's try and use it */
+			if (chdir(user_dir) != 0) {
+				openlog("chroot+sftp-server",LOG_NDELAY|LOG_PID,LOG_DAEMON);
+				syslog(LOG_DAEMON|LOG_ERR,"can't chdir %s: %s",user_dir,strerror(errno));
+				closelog();
+				fatal("can't chdir to %s: %s",user_dir,strerror(errno));
+			}
+			if (can_chroot) {
+				if (chroot(user_dir) != 0) {
+					openlog("chroot+sftp-server",LOG_NDELAY|LOG_PID,LOG_DAEMON);
+					syslog(LOG_DAEMON|LOG_ERR,"can't chroot %s: %s",user_dir,strerror(errno));
+					closelog();
+					fatal("can't chroot to %s: %s",user_dir,strerror(errno));
+				}
+			}
+
+			setenv("HOME", new_root, 1);
+			break;
+		}
+		new_root += 2;
+	}
+	return can_chroot;
+}
+#endif /* CHROOT */
+
+
 int
 main(int ac, char **av)
 {
+#ifdef CHROOT
+	int can_chroot=1;
+#endif
 	fd_set *rset, *wset;
 	int in, out, max;
 	ssize_t len, olen, set_size;
@@ -1043,6 +1145,16 @@
 #ifdef DEBUG_SFTP_SERVER
 	log_init("sftp-server", SYSLOG_LEVEL_DEBUG1, SYSLOG_FACILITY_AUTH, 0);
 #endif
+
+#ifdef CHROOT
+	can_chroot = chroot_init();
+#endif
+	/* drop the need to be root */
+	if (can_chroot) {
+		if (setuid(getuid()) != 0 || setgid(getgid()) != 0) {
+			fatal("couldn't drop privileges: %s", strerror(errno));
+		}
+	}
 
 	in = dup(STDIN_FILENO);
 	out = dup(STDOUT_FILENO);
