From sakamoto@idc4u.tsnr.com  Wed Dec 14 10:57:31 2005
Return-Path: <sakamoto@idc4u.tsnr.com>
Received: from mx1.FreeBSD.org (mx1.freebsd.org [216.136.204.125])
	by hub.freebsd.org (Postfix) with ESMTP id CD34A16A41F
	for <FreeBSD-gnats-submit@freebsd.org>; Wed, 14 Dec 2005 10:57:31 +0000 (GMT)
	(envelope-from sakamoto@idc4u.tsnr.com)
Received: from idc4u.tsnr.com (tci04.tsnr.com [210.159.194.172])
	by mx1.FreeBSD.org (Postfix) with ESMTP id 6498643D5D
	for <FreeBSD-gnats-submit@freebsd.org>; Wed, 14 Dec 2005 10:57:31 +0000 (GMT)
	(envelope-from sakamoto@idc4u.tsnr.com)
Received: from idc4u.tsnr.com (localhost [127.0.0.1])
	by idc4u.tsnr.com (8.13.4/8.13.4) with ESMTP id jBEAvCkf086918;
	Wed, 14 Dec 2005 19:57:12 +0900 (JST)
	(envelope-from sakamoto@idc4u.tsnr.com)
Received: (from root@localhost)
	by idc4u.tsnr.com (8.13.4/8.13.4/Submit) id jBEAvB0U086917;
	Wed, 14 Dec 2005 19:57:11 +0900 (JST)
	(envelope-from sakamoto)
Message-Id: <200512141057.jBEAvB0U086917@idc4u.tsnr.com>
Date: Wed, 14 Dec 2005 19:57:11 +0900 (JST)
From: Hideki SAKAMOTO <sakamoto@tsnr.com>
Reply-To: Hideki SAKAMOTO <sakamoto@tsnr.com>
To: FreeBSD-gnats-submit@freebsd.org
Cc: sakamoto@tsnr.com
Subject: chroot patch for sftp-server
X-Send-Pr-Version: 3.113
X-GNATS-Notify:

>Number:         90384
>Category:       bin
>Synopsis:       [patch] chroot patch for sftp-server(8)
>Confidential:   no
>Severity:       non-critical
>Priority:       low
>Responsible:    freebsd-bugs
>State:          closed
>Quarter:        
>Keywords:       
>Date-Required:  
>Class:          change-request
>Submitter-Id:   current-users
>Arrival-Date:   Wed Dec 14 11:00:13 GMT 2005
>Closed-Date:    Fri Jan 15 21:58:39 CET 2010
>Last-Modified:  Fri Jan 15 21:58:39 CET 2010
>Originator:     Hideki SAKAMOTO
>Release:        FreeBSD 6.0-RELEASE i386
>Organization:
TSNR.COM
>Environment:
System: FreeBSD xxx.tsnr.com 6.0-RELEASE FreeBSD 6.0-RELEASE #1: Tue Nov 29 17:50:40 JST 2
005 sakamoto@xxx.tsnr.com:/usr/obj/usr/src/sys/XXX i386

>Description:
        This patch enable sftp-server to chroot any directory just like ftpd(8).
        Mostly part of this patch code is copy of source of ftpd.c.
        Chroot user description file is /etc/ssh/sftpchroot and syntex is ftpchroot(5) com
patible. (ftpchroot(5) manual looks old version in 6.0-RELEASE. I think you need to copy f
rom 4.x manual.)
        I also test this patch on 4.11-RELEASE-p10 system.

        merit: No need to prepare bin/lib in chrooted directory tree.
        demerit: Need sftp-server binary to set suid bit and owned by root.
>How-To-Repeat:
        1. cd /usr/src and apply this patch.
        2. Set SFTP_CHROOT=yes in /etc/make.conf
        3. cd /usr/src/secure/libexec/sftp-server
        4. make & make install
>Fix:


*** secure/libexec/sftp-server/Makefile,old	Wed Dec 14 10:21:53 2005
--- secure/libexec/sftp-server/Makefile	Wed Dec 14 10:16:01 2005
***************
*** 8,13 ****
--- 8,19 ----
  DPADD=	${LIBSSH} ${LIBCRYPT} ${LIBCRYPTO} ${LIBZ}
  LDADD=	-lssh -lcrypt -lcrypto -lz
  
+ .if defined(SFTP_CHROOT)
+ CFLAGS+=-DSFTPCHROOT
+ BINOWN= root
+ BINMODE=4555
+ .endif
+ 
  .include <bsd.prog.mk>
  
  .PATH:	${SSHDIR}
*** crypto/openssh/pathnames.h,old	Wed Dec 14 09:25:10 2005
--- crypto/openssh/pathnames.h	Wed Dec 14 09:37:23 2005
***************
*** 43,48 ****
--- 43,50 ----
  /* Backwards compatibility */
  #define _PATH_DH_PRIMES			SSHDIR "/primes"
  
+ #define _PATH_SFTPCHROOT_FILE		SSHDIR "/sftpchroot"
+ 
  #ifndef _PATH_SSH_PROGRAM
  #define _PATH_SSH_PROGRAM		"/usr/bin/ssh"
  #endif
*** crypto/openssh/sftp-server.c,old	Wed Dec 14 09:24:59 2005
--- crypto/openssh/sftp-server.c	Wed Dec 14 17:47:36 2005
***************
*** 21,26 ****
--- 21,29 ----
  #include "getput.h"
  #include "log.h"
  #include "xmalloc.h"
+ #ifdef SFTPCHROOT
+ #include "pathnames.h"
+ #endif
  
  #include "sftp.h"
  #include "sftp-common.h"
***************
*** 1029,1040 ****
--- 1032,1136 ----
  		buffer_consume(&iqueue, msg_len - consumed);
  }
  
+ /*
+  * Check if a user is in the file "fname",
+  * return a pointer to a malloc'd string with the rest
+  * of the matching line in "residue" if not NULL.
+  */
+ #ifdef SFTPCHROOT
+ static int
+ checkuser(char *fname, char *name, gid_t pw_gid, char **residue)
+ {
+ 	FILE *fd;
+ 	int found = 0;
+ 	size_t len;
+ 	char *line, *mp, *p;
+ 
+ 	if ((fd = fopen(fname, "r")) != NULL) {
+ 		while (!found && (line = fgetln(fd, &len)) != NULL) {
+ 			/* skip comments */
+ 			if (line[0] == '#')
+ 				continue;
+ 			if (line[len - 1] == '\n') {
+ 				line[len - 1] = '\0';
+ 				mp = NULL;
+ 			} else {
+ 				if ((mp = malloc(len + 1)) == NULL)
+ 					fatal("Ran out of memory.");
+ 				memcpy(mp, line, len);
+ 				mp[len] = '\0';
+ 				line = mp;
+ 			}
+ 			/* avoid possible leading and trailing whitespace */
+ 			p = strtok(line, " \t");
+ 			/* skip empty lines */
+ 			if (p == NULL)
+ 				goto nextline;
+ 			/*
+ 			 * if first chr is '@', check group membership
+ 			 */
+ 			if (p[0] == '@') {
+ 				int i = 0;
+ 				struct group *grp;
+ 
+ 				if (p[1] == '\0') /* single @ matches anyone */
+ 					found = 1;
+ 				else {
+ 					if ((grp = getgrnam(p+1)) == NULL)
+ 						goto nextline;
+ 					/*
+ 					 * Check user's default group
+ 					 */
+ 					if (grp->gr_gid == pw_gid)
+ 						found = 1;
+ 					/*
+ 					 * Check supplementary groups
+ 					 */
+ 					while (!found && grp->gr_mem[i])
+ 						found = strcmp(name,
+ 							grp->gr_mem[i++])
+ 							== 0;
+ 				}
+ 			}
+ 			/*
+ 			 * Otherwise, just check for username match
+ 			 */
+ 			else
+ 				found = strcmp(p, name) == 0;
+ 			/*
+ 			 * Save the rest of line to "residue" if matched
+ 			 */
+ 			if (found && residue) {
+ 				if ((p = strtok(NULL, "")) != NULL)
+ 					p += strspn(p, " \t");
+ 				if (p && *p) {
+ 					if ((*residue = strdup(p)) == NULL)
+ 						fatal("Ran out of memory.");
+ 				} else
+ 					*residue = NULL;
+ 			}
+ nextline:
+ 			if (mp)
+ 				free(mp);
+ 		}
+ 		(void) fclose(fd);
+ 	}
+ 	return (found);
+ }
+ #endif	/* SFTPCHROOT */
+ 
  int
  main(int ac, char **av)
  {
  	fd_set *rset, *wset;
  	int in, out, max;
  	ssize_t len, olen, set_size;
+ #ifdef SFTPCHROOT
+ 	int dochroot;
+ 	char *chrootdir, *homedir, *residue;
+ 	uid_t uid;
+ 	struct passwd *pw;
+ #endif
  
  	/* XXX should use getopt */
  
***************
*** 1065,1070 ****
--- 1161,1243 ----
  	set_size = howmany(max + 1, NFDBITS) * sizeof(fd_mask);
  	rset = (fd_set *)xmalloc(set_size);
  	wset = (fd_set *)xmalloc(set_size);
+ 
+ #ifdef	SFTPCHROOT
+ 	uid = getuid();
+ 	if ((pw = getpwuid(uid)) == NULL)
+ 		fatal("Cannot get user info");
+ 	residue = NULL;
+ 	dochroot =
+ 		checkuser(_PATH_SFTPCHROOT_FILE, pw->pw_name, pw->pw_gid, &residue);
+ 	chrootdir = NULL;
+ 	homedir = NULL;
+ 	/*
+ 	 * For a chrooted local user,
+ 	 * a) see whether ftpchroot(5) specifies a chroot directory,
+ 	 * b) extract the directory pathname from the line,
+ 	 * c) expand it to the absolute pathname if necessary.
+ 	 */
+ 	if (dochroot && residue &&
+ 	    (chrootdir = strtok(residue, " \t")) != NULL) {
+ 		if (chrootdir[0] != '/')
+ 			asprintf(&chrootdir, "%s/%s", pw->pw_dir, chrootdir);
+ 		else
+ 			chrootdir = strdup(chrootdir); /* make it permanent */
+ 		if (chrootdir == NULL)
+ 			fatal("Ran out of memory.");
+ 	}
+ 	if (dochroot) {
+ 		/*
+ 		 * If no chroot directory set yet, use the login directory.
+ 		 * Copy it so it can be modified while pw->pw_dir stays intact.
+ 		 */
+ 		if (chrootdir == NULL &&
+ 		    (chrootdir = strdup(pw->pw_dir)) == NULL)
+ 			fatal("Ran out of memory.");
+ 		/*
+ 		 * Check for the "/chroot/./home" syntax,
+ 		 * separate the chroot and home directory pathnames.
+ 		 */
+ 		if ((homedir = strstr(chrootdir, "/./")) != NULL) {
+ 			*(homedir++) = '\0';    /* wipe '/' */
+ 			homedir++;	      /* skip '.' */
+ 		} else {
+ 			/*
+ 			 * We MUST do a chdir() after the chroot. Otherwise
+ 			 * the old current directory will be accessible as "."
+ 			 * outside the new root!
+ 			 */
+ 			homedir = "/";
+ 		}
+ 		/*
+ 		 * Finally, do chroot()
+ 		 */
+ 		if (chroot(chrootdir) < 0) {
+ 			fatal("Can't change root.");
+ 		}
+ 	} else  /* real user w/o chroot */
+ 		homedir = pw->pw_dir;
+ 	/*
+ 	 * Set euid *before* doing chdir() so
+ 	 * a) the user won't be carried to a directory that he couldn't reach
+ 	 *    on his own due to no permission to upper path components,
+ 	 * b) NFS mounted homedirs w/restrictive permissions will be accessible
+ 	 *    (uid 0 has no root power over NFS if not mapped explicitly.)
+ 	 */
+ 	if (seteuid(pw->pw_uid) < 0) {
+ 		fatal("Can't set uid.");
+ 	}
+ 	if (chdir(homedir) < 0) {
+ 		if (dochroot) {
+ 			fatal("Can't change to base directory.");
+ 		} else {
+ 			if (chdir("/") < 0) {
+ 				fatal("Root is inaccessible.");
+ 			}
+ 			fatal("No directory! Logging in with home=/.");
+ 		}
+ 	}
+ #endif	/* SFTPCHROOT */
  
  	for (;;) {
  		memset(rset, 0, set_size);

>Release-Note:
>Audit-Trail:
State-Changed-From-To: open->closed 
State-Changed-By: brueffer 
State-Changed-When: Fri Jan 15 21:56:43 CET 2010 
State-Changed-Why:  
First of all, sorry this was never looked after.  The good news is 
that OpenSSH supports sftp chroot by default now, so this is no longer 
necessary. Thanks for working on making FreeBSD better! 

http://www.freebsd.org/cgi/query-pr.cgi?pr=90384 
>Unformatted:
