Date: Wed, 14 Dec 2005 19:57:11 +0900 (JST) From: Hideki SAKAMOTO <sakamoto@tsnr.com> To: FreeBSD-gnats-submit@FreeBSD.org Cc: sakamoto@tsnr.com Subject: bin/90384: chroot patch for sftp-server Message-ID: <200512141057.jBEAvB0U086917@idc4u.tsnr.com> Resent-Message-ID: <200512141100.jBEB0EYb055812@freefall.freebsd.org>
next in thread | raw e-mail | index | archive | help
>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@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:
Want to link to this message? Use this URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?200512141057.jBEAvB0U086917>