bin/90384: chroot patch for sftp-server
Hideki SAKAMOTO
sakamoto at tsnr.com
Wed Dec 14 03:00:18 PST 2005
>Number: 90384
>Category: bin
>Synopsis: chroot patch for sftp-server
>Confidential: no
>Severity: non-critical
>Priority: low
>Responsible: freebsd-bugs
>State: open
>Quarter:
>Keywords:
>Date-Required:
>Class: change-request
>Submitter-Id: current-users
>Arrival-Date: Wed Dec 14 11:00:13 GMT 2005
>Closed-Date:
>Last-Modified:
>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 at 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:
>Unformatted:
More information about the freebsd-bugs
mailing list