]> andersk Git - openssh.git/commitdiff
- djm@cvs.openbsd.org 2008/02/08 23:24:07
authordjm <djm>
Sun, 10 Feb 2008 11:40:12 +0000 (11:40 +0000)
committerdjm <djm>
Sun, 10 Feb 2008 11:40:12 +0000 (11:40 +0000)
     [servconf.c servconf.h session.c sftp-server.c sftp.h sshd_config]
     [sshd_config.5]
     add sshd_config ChrootDirectory option to chroot(2) users to a directory
     and tweak internal sftp server to work with it (no special files in
     chroot required). ok markus@

Makefile.in
servconf.c
servconf.h
session.c
sftp-server-main.c [new file with mode: 0644]
sftp-server.c
sftp.h
sshd_config
sshd_config.5

index c9cb175ba02de366e137785a6c681ebf8e439758..060dd19ecbbc4dee37633f0eac5154201e045bfe 100644 (file)
@@ -156,8 +156,8 @@ ssh-keysign$(EXEEXT): $(LIBCOMPAT) libssh.a ssh-keysign.o
 ssh-keyscan$(EXEEXT): $(LIBCOMPAT) libssh.a ssh-keyscan.o
        $(LD) -o $@ ssh-keyscan.o $(LDFLAGS) -lssh -lopenbsd-compat -lssh $(LIBS)
 
-sftp-server$(EXEEXT): $(LIBCOMPAT) libssh.a sftp.o sftp-common.o sftp-server.o
-       $(LD) -o $@ sftp-server.o sftp-common.o $(LDFLAGS) -lssh -lopenbsd-compat $(LIBS)
+sftp-server$(EXEEXT): $(LIBCOMPAT) libssh.a sftp.o sftp-common.o sftp-server.o sftp-server-main.o
+       $(LD) -o $@ sftp-server.o sftp-common.o sftp-server-main.o $(LDFLAGS) -lssh -lopenbsd-compat $(LIBS)
 
 sftp$(EXEEXT): $(LIBCOMPAT) libssh.a sftp.o sftp-client.o sftp-common.o sftp-glob.o progressmeter.o
        $(LD) -o $@ progressmeter.o sftp.o sftp-client.o sftp-common.o sftp-glob.o $(LDFLAGS) -lssh -lopenbsd-compat $(LIBS) $(LIBEDIT)
index 19c286c18d0b6333381b998df7fa3687ecd738a9..d38d0bfb17540af7dc3e1b16e55af77ca8590439 100644 (file)
@@ -1,4 +1,4 @@
-/* $OpenBSD: servconf.c,v 1.175 2008/01/01 09:27:33 dtucker Exp $ */
+/* $OpenBSD: servconf.c,v 1.176 2008/02/08 23:24:08 djm Exp $ */
 /*
  * Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland
  *                    All rights reserved
@@ -122,6 +122,7 @@ initialize_server_options(ServerOptions *options)
        options->permit_tun = -1;
        options->num_permitted_opens = -1;
        options->adm_forced_command = NULL;
+       options->chroot_directory = NULL;
 }
 
 void
@@ -291,7 +292,7 @@ typedef enum {
        sHostbasedUsesNameFromPacketOnly, sClientAliveInterval,
        sClientAliveCountMax, sAuthorizedKeysFile, sAuthorizedKeysFile2,
        sGssAuthentication, sGssCleanupCreds, sAcceptEnv, sPermitTunnel,
-       sMatch, sPermitOpen, sForceCommand,
+       sMatch, sPermitOpen, sForceCommand, sChrootDirectory,
        sUsePrivilegeSeparation,
        sDeprecated, sUnsupported
 } ServerOpCodes;
@@ -403,6 +404,7 @@ static struct {
        { "match", sMatch, SSHCFG_ALL },
        { "permitopen", sPermitOpen, SSHCFG_ALL },
        { "forcecommand", sForceCommand, SSHCFG_ALL },
+       { "chrootdirectory", sChrootDirectory, SSHCFG_ALL },
        { NULL, sBadOption, 0 }
 };
 
@@ -1147,6 +1149,7 @@ parse_flag:
        case sBanner:
                charptr = &options->banner;
                goto parse_filename;
+
        /*
         * These options can contain %X options expanded at
         * connect time, so that you can specify paths like:
@@ -1255,6 +1258,10 @@ parse_flag:
                        options->adm_forced_command = xstrdup(cp + len);
                return 0;
 
+       case sChrootDirectory:
+               charptr = &options->chroot_directory;
+               goto parse_filename;
+
        case sDeprecated:
                logit("%s line %d: Deprecated option %s",
                    filename, linenum, arg);
@@ -1363,6 +1370,7 @@ copy_set_server_options(ServerOptions *dst, ServerOptions *src, int preauth)
        if (preauth)
                return;
        M_CP_STROPT(adm_forced_command);
+       M_CP_STROPT(chroot_directory);
 }
 
 #undef M_CP_INTOPT
index 8a5b950ea071958f34f3d53ea326b8f5a415f720..81a68be89ac0b274c69243077352df056cc3439c 100644 (file)
@@ -1,4 +1,4 @@
-/* $OpenBSD: servconf.h,v 1.80 2007/02/19 10:45:58 dtucker Exp $ */
+/* $OpenBSD: servconf.h,v 1.81 2008/02/08 23:24:08 djm Exp $ */
 
 /*
  * Author: Tatu Ylonen <ylo@cs.hut.fi>
@@ -141,6 +141,8 @@ typedef struct {
        int     permit_tun;
 
        int     num_permitted_opens;
+
+       char   *chroot_directory;
 }       ServerOptions;
 
 void    initialize_server_options(ServerOptions *);
index a1319b38443488d35551248727217a2ade9abc69..1768c8c2f00c3581fef3d29db3d9fead11d9ec71 100644 (file)
--- a/session.c
+++ b/session.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: session.c,v 1.225 2008/02/04 21:53:00 markus Exp $ */
+/* $OpenBSD: session.c,v 1.226 2008/02/08 23:24:07 djm Exp $ */
 /*
  * Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland
  *                    All rights reserved
@@ -84,6 +84,7 @@
 #include "sshlogin.h"
 #include "serverloop.h"
 #include "canohost.h"
+#include "misc.h"
 #include "session.h"
 #include "kex.h"
 #include "monitor_wrap.h"
@@ -93,6 +94,9 @@
 #include <kafs.h>
 #endif
 
+/* Magic name for internal sftp-server */
+#define INTERNAL_SFTP_NAME     "internal-sftp"
+
 /* func */
 
 Session *session_new(void);
@@ -130,7 +134,7 @@ extern Buffer loginmsg;
 const char *original_command = NULL;
 
 /* data */
-#define MAX_SESSIONS 10
+#define MAX_SESSIONS 20
 Session        sessions[MAX_SESSIONS];
 
 #define SUBSYSTEM_NONE         0
@@ -688,13 +692,17 @@ do_exec(Session *s, const char *command)
        if (options.adm_forced_command) {
                original_command = command;
                command = options.adm_forced_command;
-               if (s->is_subsystem)
+               if (strcmp(INTERNAL_SFTP_NAME, command) == 0)
+                       s->is_subsystem = SUBSYSTEM_INT_SFTP;
+               else if (s->is_subsystem)
                        s->is_subsystem = SUBSYSTEM_EXT;
                debug("Forced command (config) '%.900s'", command);
        } else if (forced_command) {
                original_command = command;
                command = forced_command;
-               if (s->is_subsystem)
+               if (strcmp(INTERNAL_SFTP_NAME, command) == 0)
+                       s->is_subsystem = SUBSYSTEM_INT_SFTP;
+               else if (s->is_subsystem)
                        s->is_subsystem = SUBSYSTEM_EXT;
                debug("Forced command (key option) '%.900s'", command);
        }
@@ -710,7 +718,6 @@ do_exec(Session *s, const char *command)
                PRIVSEP(audit_run_command(shell));
        }
 #endif
-
        if (s->ttyfd != -1)
                do_exec_pty(s, command);
        else
@@ -1293,6 +1300,61 @@ do_nologin(struct passwd *pw)
        }
 }
 
+/*
+ * Chroot into a directory after checking it for safety: all path components
+ * must be root-owned directories with strict permissions.
+ */
+static void
+safely_chroot(const char *path, uid_t uid)
+{
+       const char *cp;
+       char component[MAXPATHLEN];
+       struct stat st;
+
+       if (*path != '/')
+               fatal("chroot path does not begin at root");
+       if (strlen(path) >= sizeof(component))
+               fatal("chroot path too long");
+
+       /*
+        * Descend the path, checking that each component is a
+        * root-owned directory with strict permissions.
+        */
+       for (cp = path; cp != NULL;) {
+               if ((cp = strchr(cp, '/')) == NULL)
+                       strlcpy(component, path, sizeof(component));
+               else {
+                       cp++;
+                       memcpy(component, path, cp - path);
+                       component[cp - path] = '\0';
+               }
+       
+               debug3("%s: checking '%s'", __func__, component);
+
+               if (stat(component, &st) != 0)
+                       fatal("%s: stat(\"%s\"): %s", __func__,
+                           component, strerror(errno));
+               if (st.st_uid != 0 || (st.st_mode & 022) != 0)
+                       fatal("bad ownership or modes for chroot "
+                           "directory %s\"%s\"", 
+                           cp == NULL ? "" : "component ", component);
+               if (!S_ISDIR(st.st_mode))
+                       fatal("chroot path %s\"%s\" is not a directory",
+                           cp == NULL ? "" : "component ", component);
+
+       }
+
+       if (chdir(path) == -1)
+               fatal("Unable to chdir to chroot path \"%s\": "
+                   "%s", path, strerror(errno));
+       if (chroot(path) == -1)
+               fatal("chroot(\"%s\"): %s", path, strerror(errno));
+       if (chdir("/") == -1)
+               fatal("%s: chdir(/) after chroot: %s",
+                   __func__, strerror(errno));
+       verbose("Changed root directory to \"%s\"", path);
+}
+
 /* Set login name, uid, gid, and groups. */
 void
 do_setusercontext(struct passwd *pw)
@@ -1324,7 +1386,7 @@ do_setusercontext(struct passwd *pw)
                }
 # endif /* USE_PAM */
                if (setusercontext(lc, pw, pw->pw_uid,
-                   (LOGIN_SETALL & ~LOGIN_SETPATH)) < 0) {
+                   (LOGIN_SETALL & ~(LOGIN_SETPATH|LOGIN_SETUSER))) < 0) {
                        perror("unable to set user context");
                        exit(1);
                }
@@ -1347,13 +1409,13 @@ do_setusercontext(struct passwd *pw)
                        exit(1);
                }
                endgrent();
-#ifdef GSSAPI
+# ifdef GSSAPI
                if (options.gss_authentication) {
                        temporarily_use_uid(pw);
                        ssh_gssapi_storecreds();
                        restore_uid();
                }
-#endif
+# endif
 # ifdef USE_PAM
                /*
                 * PAM credentials may take the form of supplementary groups.
@@ -1367,15 +1429,33 @@ do_setusercontext(struct passwd *pw)
 # endif /* USE_PAM */
 # if defined(WITH_IRIX_PROJECT) || defined(WITH_IRIX_JOBS) || defined(WITH_IRIX_ARRAY)
                irix_setusercontext(pw);
-#  endif /* defined(WITH_IRIX_PROJECT) || defined(WITH_IRIX_JOBS) || defined(WITH_IRIX_ARRAY) */
+# endif /* defined(WITH_IRIX_PROJECT) || defined(WITH_IRIX_JOBS) || defined(WITH_IRIX_ARRAY) */
 # ifdef _AIX
                aix_usrinfo(pw);
 # endif /* _AIX */
-#ifdef USE_LIBIAF
+# ifdef USE_LIBIAF
                if (set_id(pw->pw_name) != 0) {
                        exit(1);
                }
-#endif /* USE_LIBIAF */
+# endif /* USE_LIBIAF */
+#endif
+
+               if (options.chroot_directory != NULL &&
+                   strcasecmp(options.chroot_directory, "none") != 0) {
+                       char *chroot_path;
+
+                       chroot_path = percent_expand(options.chroot_directory,
+                           "h", pw->pw_dir, "u", pw->pw_name, (char *)NULL);
+                       safely_chroot(chroot_path, pw->pw_uid);
+                       free(chroot_path);
+               }
+
+#ifdef HAVE_LOGIN_CAP
+               if (setusercontext(lc, pw, pw->pw_uid, LOGIN_SETUSER) < 0) {
+                       perror("unable to set user context (setuser)");
+                       exit(1);
+               }
+#else
                /* Permanently switch to the desired uid. */
                permanently_set_uid(pw);
 #endif
@@ -1625,7 +1705,7 @@ do_child(Session *s, const char *command)
                argv[i] = NULL;
                optind = optreset = 1;
                __progname = argv[0];
-               exit(sftp_server_main(i, argv));
+               exit(sftp_server_main(i, argv, s->pw));
        }
 
        if (options.use_login) {
@@ -1900,7 +1980,7 @@ session_subsystem_req(Session *s)
                if (strcmp(subsys, options.subsystem_name[i]) == 0) {
                        prog = options.subsystem_command[i];
                        cmd = options.subsystem_args[i];
-                       if (!strcmp("internal-sftp", prog)) {
+                       if (!strcmp(INTERNAL_SFTP_NAME, prog)) {
                                s->is_subsystem = SUBSYSTEM_INT_SFTP;
                        } else if (stat(prog, &st) < 0) {
                                error("subsystem: cannot stat %s: %s", prog,
diff --git a/sftp-server-main.c b/sftp-server-main.c
new file mode 100644 (file)
index 0000000..1d2ea62
--- /dev/null
@@ -0,0 +1,50 @@
+/* $OpenBSD: */
+/*
+ * Copyright (c) 2008 Markus Friedl.  All rights reserved.
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include "includes.h"
+
+#include <sys/types.h>
+#include <pwd.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <unistd.h>
+
+#include "log.h"
+#include "sftp.h"
+#include "misc.h"
+
+void
+cleanup_exit(int i)
+{
+       sftp_server_cleanup_exit(i);
+}
+
+int
+main(int argc, char **argv)
+{
+       struct passwd *user_pw;
+
+       /* Ensure that fds 0, 1 and 2 are open or directed to /dev/null */
+       sanitise_stdfd();
+
+       if ((user_pw = getpwuid(getuid())) == NULL) {
+               fprintf(stderr, "No user found for uid %lu", (u_long)getuid());
+               return 1;
+       }
+
+       return (sftp_server_main(argc, argv, user_pw));
+}
index 373bd5eda5ea8889d7f7e70fd3a50b14aa9c513a..44ccefff8881ca8bb4d07ee0ce35e5772e29c22d 100644 (file)
@@ -1,4 +1,4 @@
-/* $OpenBSD: sftp-server.c,v 1.76 2008/02/04 21:53:00 markus Exp $ */
+/* $OpenBSD: sftp-server.c,v 1.77 2008/02/08 23:24:07 djm Exp $ */
 /*
  * Copyright (c) 2000-2004 Markus Friedl.  All rights reserved.
  *
@@ -1219,7 +1219,7 @@ sftp_server_usage(void)
 }
 
 int
-sftp_server_main(int argc, char **argv)
+sftp_server_main(int argc, char **argv, struct passwd *user_pw)
 {
        fd_set *rset, *wset;
        int in, out, max, ch, skipargs = 0, log_stderr = 0;
@@ -1277,11 +1277,7 @@ sftp_server_main(int argc, char **argv)
        } else
                client_addr = xstrdup("UNKNOWN");
 
-       if ((pw = getpwuid(getuid())) == NULL) {
-               error("No user found for uid %lu", (u_long)getuid());
-               sftp_server_cleanup_exit(255);
-       }
-       pw = pwcopy(pw);
+       pw = pwcopy(user_pw);
 
        logit("session opened for local user %s from [%s]",
            pw->pw_name, client_addr);
diff --git a/sftp.h b/sftp.h
index 12b9cc05680290152c4e0f73acee295c72674bda..0835da6ed414dab32fedebc9059334a85e293717 100644 (file)
--- a/sftp.h
+++ b/sftp.h
@@ -1,4 +1,4 @@
-/* $OpenBSD: sftp.h,v 1.6 2008/02/04 21:53:00 markus Exp $ */
+/* $OpenBSD: sftp.h,v 1.7 2008/02/08 23:24:07 djm Exp $ */
 
 /*
  * Copyright (c) 2001 Markus Friedl.  All rights reserved.
@@ -91,5 +91,7 @@
 #define SSH2_FX_OP_UNSUPPORTED         8
 #define SSH2_FX_MAX                    8
 
-int    sftp_server_main(int, char **);
+struct passwd;
+
+int    sftp_server_main(int, char **, struct passwd *);
 void   sftp_server_cleanup_exit(int) __dead;
index c7094e7759cf98ad72708473b34c48549586d1d4..ddfbbe91eb1f4318633129301aca264c14e40597 100644 (file)
@@ -1,4 +1,4 @@
-#      $OpenBSD: sshd_config,v 1.76 2007/08/23 03:22:16 djm Exp $
+#      $OpenBSD: sshd_config,v 1.77 2008/02/08 23:24:07 djm Exp $
 
 # This is the sshd server system-wide configuration file.  See
 # sshd_config(5) for more information.
@@ -102,6 +102,7 @@ Protocol 2
 #PidFile /var/run/sshd.pid
 #MaxStartups 10
 #PermitTunnel no
+#ChrootDirectory none
 
 # no default banner path
 #Banner none
index 5d6fbf68c7a063bfb30fffd0421a65595eed6c97..6746f66c8a3ee57bb36ad41dc57c00a1d2c0856d 100644 (file)
@@ -34,7 +34,7 @@
 .\" (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 .\" THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 .\"
-.\" $OpenBSD: sshd_config.5,v 1.79 2008/01/01 09:27:33 dtucker Exp $
+.\" $OpenBSD: sshd_config.5,v 1.80 2008/02/08 23:24:07 djm Exp $
 .Dd $Mdocdate$
 .Dt SSHD_CONFIG 5
 .Os
@@ -173,6 +173,45 @@ All authentication styles from
 are supported.
 The default is
 .Dq yes .
+.It Cm ChrootDirectory
+Specifies a path to
+.Xr chroot 2
+to after authentication.
+This path, and all its components, must be root-owned directories that are
+not writable by any other user or group.
+.Pp
+The path may contain the following tokens that are expanded at runtime once
+the connecting user has been authenticated: %% is replaced by a literal '%',
+%h is replaced by the home directory of the user being authenticated, and
+%u is replaced by the username of that user.
+.Pp
+The
+.Cm ChrootDirectory
+must contain the necessary files and directories to support the
+users' session.
+For an interactive session this requires at least a shell, typically
+.Xr sh 1 ,
+and basic
+.Pa /dev
+nodes such as
+.Xr null 4 ,
+.Xr zero 4 ,
+.Xr stdin 4 ,
+.Xr stdout 4 ,
+.Xr stderr 4 ,
+.Xr arandom 4
+and
+.Xr tty 4
+devices.
+For file transfer sessions using
+.Dq sftp ,
+no additional configuration of the environment is necessary if the
+in-process sftp server is used (see
+.Cm Subsystem
+for details.
+.Pp
+The default is not to
+.Xr chroot 2 .
 .It Cm Ciphers
 Specifies the ciphers allowed for protocol version 2.
 Multiple ciphers must be comma-separated.
@@ -740,11 +779,22 @@ The default is
 Configures an external subsystem (e.g. file transfer daemon).
 Arguments should be a subsystem name and a command (with optional arguments)
 to execute upon subsystem request.
+.Pp
 The command
 .Xr sftp-server 8
 implements the
 .Dq sftp
 file transfer subsystem.
+.Pp
+Alternately the name
+.Dq internal-sftp
+implements an in-process
+.Dq sftp
+server.
+This may simplify configurations using
+.Cm ChrootDirectory
+to force a different filesystem root on clients.
+.Pp
 By default no subsystems are defined.
 Note that this option applies to protocol version 2 only.
 .It Cm SyslogFacility
This page took 0.083821 seconds and 5 git commands to generate.