[tpop3d-discuss] Option to chroot() for virtual servers

Travis Miller tmiller at web-1hosting.net
Sun, 15 Dec 2002 18:48:54 -0600


This is a multi-part message in MIME format.
--------------090206050800020806050801
Content-Type: text/plain; charset=us-ascii; format=flowed
Content-Transfer-Encoding: 7bit

Chris (and everyone else):

A little history:  We currently use tpop3d for hosting email for 
"virtual" users (ie, they do not exist in /etc/passwd).  To do this, all 
of the mail boxes are owned by the same user "mailspool" in group 
"mailspool" and the pop3 user is authenticated via mysql.

And the idea:  So to improve a little on security I thought I would try 
patching tpop3d to chroot() to the mail spool and setgid()/setuid() to a 
  non-privledeged user right before net_loop() in main.c.  I have 
attached a patch that seems to work for this as long as you have a copy 
of /etc/passwd and /etc/group in /path/to/spools/etc. When you do not 
have  copies in the there, auth_mysql fails at parse_uid(),parse_gid and 
all the other passwd/group system calls (of course).  Of course, it 
breaks other things such as kill -HUP also....

My Question:
Does this seem like a worth while advantage to add?

It does mean leaving open file descriptors after the chroot(), 
setuid()... is that fact not worth worrying with with chroot'ing in the 
first place?

Would it be worth the effort to code in a work around for all the 
uid/gid calls when chroot() is enabled?

And lastly, I am no code wizard, so comments/ideas on methods of 
implementing this? (mine is very basic!)

(The attached patch is against 1.4.2, chroot is enabled via configure 
with --enable-chroot)

Travis
tmiller@web-1hosting.net

--------------090206050800020806050801
Content-Type: text/plain;
 name="tpop3d-1.4.2-chroot.diff"
Content-Transfer-Encoding: 7bit
Content-Disposition: inline;
 filename="tpop3d-1.4.2-chroot.diff"

diff -u tpop3d-1.4.2/configure.in tpop3d-1.4.2-chroot/configure.in
--- tpop3d-1.4.2/configure.in	2002-06-27 15:08:46.000000000 -0500
+++ tpop3d-1.4.2-chroot/configure.in	2002-12-15 17:53:25.000000000 -0600
@@ -198,6 +198,13 @@
     [enable_drac=$enableval],
     [enable_drac=no])
 
+dnl Enable chroot()ed server 
+AC_ARG_ENABLE(chroot,
+        [ --enable-choot                       Enable support for chroot()'d server.
+                                      [default=no]],
+    [enable_chroot=$enableval],
+    [enable_chroot=no])
+
 dnl Some options mainly useful for development/debugging.
 
 dnl Note the ugliness I've used to put a note in the output of
@@ -331,6 +338,11 @@
     AC_DEFINE(USE_DRAC,1,[Enables notification of a DRAC daemon.])
 fi
 
+if test x"$enable_chroot" = x"yes"
+then
+    AC_DEFINE(WITH_CHROOT,1,[Enables ablilty to chroot() server.])
+fi
+
 if test x"$enable_backtrace" = x"yes"
 then
     AC_DEFINE(APPALLING_BACKTRACE_HACK,1,[Produce a backtrace if the program crashes.])
Common subdirectories: tpop3d-1.4.2/darwin and tpop3d-1.4.2-chroot/darwin
Common subdirectories: tpop3d-1.4.2/init.d and tpop3d-1.4.2-chroot/init.d
diff -u tpop3d-1.4.2/main.c tpop3d-1.4.2-chroot/main.c
--- tpop3d-1.4.2/main.c	2002-06-25 15:28:00.000000000 -0500
+++ tpop3d-1.4.2-chroot/main.c	2002-12-15 17:41:31.000000000 -0600
@@ -32,6 +32,11 @@
 #include <netinet/in.h>
 #include <arpa/inet.h>
 
+#if defined(WITH_CHROOT)
+#include <pwd.h>
+#include <grp.h>
+#endif
+
 #include <sys/socket.h>
 #include <sys/time.h>
 #include <sys/types.h>
@@ -453,6 +458,77 @@
 
 #define EXIT_REMOVING_PIDFILE(n) do { if (pidfile) remove_pid_file(pidfile); exit((n)); } while (0)
 
+#if defined(WITH_CHROOT)
+/* do_chroot:
+ * Jail the server to a particular section of the file system.  This, of course, is not
+ * useful if we are still root; so we drop root priviledges also... This has a *large*
+ * effect on how the rest of the server works, it may currently break things.  Returns 0
+ * on failure and 1 on success.
+ */
+int do_chroot(void) {
+    struct passwd *pw;
+    struct group *gr;
+    int new_uid;
+    int new_gid;
+    char *path;
+    char *tmp;
+
+    /* Get the path to chroot() to */
+    if ((path = config_get_string("chroot-path")) == NULL) {
+        log_print(LOG_INFO, _("do_chroot: could not determine chroot-path"),path);
+        return(0);
+    }
+
+    /* We need to know what user to setuid() to after chroot(), first attemt to get
+    * the username from the config, if that fails try to get the uid. */
+    if ((tmp = config_get_string("chroot-user")) != NULL) {
+        if ((pw = getpwnam(tmp)) != NULL) {
+            new_uid = pw->pw_uid;
+        } else {
+            log_print(LOG_INFO, _("do_chroot: could not determine chroot-user (chroot-user: %s)"),tmp);
+            return(0);
+        }
+    } else if (config_get_int("chroot-uid",&new_uid) != 1) {
+        log_print(LOG_INFO, _("do_chroot: could not determine chroot-user"),new_uid);
+        return(0);
+    }
+
+    /* Now we do the exact same thing for the setgid() call */
+    if ((tmp = config_get_string("chroot-group")) != NULL) {
+        if ((gr = getgrnam(tmp)) != NULL) {
+            new_gid = gr->gr_gid;
+        } else {
+            log_print(LOG_INFO, _("do_chroot: could not determine chroot-group (chroot-group: %s)"),tmp);
+            return(0);
+        }
+    } else if (config_get_int("chroot-gid",&new_gid) != 1) {
+        log_print(LOG_INFO, _("do_chroot: could not determine chroot-group"));
+        return(0);
+    }
+
+    /* chroot and chdir */
+    if (chroot(path) == -1) {
+        log_print(LOG_INFO, _("do_chroot: chroot(%s) failed!"),path);
+        return(0);
+    } else if (chdir("/") == -1) {
+        log_print(LOG_INFO, _("do_chroot: chdir(/) failed!"));
+        return(0);
+    }
+
+    /* become a non-root user, too easy to break chroot() as root */
+    if (setgid((gid_t)new_gid) == -1) {
+        log_print(LOG_ERR, "do_chroot: setgid(%d)",new_gid);
+        return(0);
+    } else if (setuid((uid_t)new_uid) == -1) {
+        log_print(LOG_ERR, "do_chroot: setuid(%d)",new_uid);
+        return(0);
+    }
+
+    /* Success! */
+    return(1);
+}
+#endif
+
 /* usage:
  * Print usage information.
  */
@@ -747,6 +823,19 @@
         EXIT_REMOVING_PIDFILE(1);
     } else log_print(LOG_INFO, _("%d authentication drivers successfully loaded"), na);
    
+#if defined(WITH_CHROOT)
+    /* Now that we have all the open file descriptors and everything else that we should
+     * need, we can chroot(). */
+    if (config_get_bool("chroot")) {
+        int cr = do_chroot();
+        if (!cr) {
+            log_print(LOG_ERR, _("Unable to successfully chroot(); aborting."));
+            log_print(LOG_ERR, _("you may wish to check your config file %s"), configfile);
+            EXIT_REMOVING_PIDFILE(1);
+        } else log_print(LOG_INFO, _("Successfully chroot()'d"));
+    }
+#endif
+
     net_loop();
 
     if (!post_fork)

--------------090206050800020806050801--