Relax more the parent directories security check.
[captive.git] / src / client / sandbox-server / main.c
index 8f0b670..4f2124f 100644 (file)
 #include <locale.h>
 #include "captive/options.h"
 #include <glib-object.h>
+#include "captive/macros.h"
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <dirent.h>
+#include <errno.h>
+#include <grp.h>
+#include <pwd.h>
+#include <fcntl.h>
+#include <sys/file.h>
+#include <sys/resource.h>
+#include <orbit/orb-core/corba-defs.h>
+#include "captive/client.h"
+
+#ifdef HAVE_ORBIT_LINK
+char *link_get_tmpdir(void);
+void link_set_tmpdir(const char *dir);
+#else
+#include <linc/linc-protocol.h>        /* for linc_set_tmpdir() */
+#endif
+
+
+/* Do not: #include "../../libcaptive/sandbox/split.h" * for captive_sandbox_fd_closeup(); FIXME *
+ * as it has libcaptive-dependent includes conditioned by ORBIT2.
+ * FIXME: Unify this declaration:
+ */
+void captive_sandbox_fd_closeup(int fd_first_to_delete);
+void captive_corba_sandbox_child(const gchar *chrooted_orbit_dir);
+
+
+/* CONFIG: */
+
+/* FIXME: We hit linc-1.0.1/src/linc-protocols.c/linc_protocol_get_sockaddr_unix()
+ * limit of socket pathname 64 characters.
+ * With CHROOT_PATH_HASHKEY_LENGTH 12 "linc-%x-%x-%x%x" still does not fit completely.
+ */
+#define CHROOT_PATH_HASHKEY_LENGTH (12)
 
 
 GQuark sandbox_server_main_error_quark(void)
@@ -42,27 +79,541 @@ GQuark r=0;
 }
 
 
+static gchar *optarg_setuid=CAPTIVE_SANDBOX_SETUID;
+static gchar *optarg_setgid=CAPTIVE_SANDBOX_SETGID;
+static gchar *optarg_chroot=CAPTIVE_SANDBOX_CHROOT;
+static gint   optarg_no_rlimit=0;
+
 static const struct poptOption popt_table[]={
-               CAPTIVE_POPT_INCLUDE,
+#define SANDBOX_SERVER_POPT(longname,argInfoP,argP,descripP,argDescripP) \
+               { \
+                       longName: (longname), \
+                       shortName: 0, \
+                       argInfo: (argInfoP), \
+                       arg: (void *)argP, \
+                       val: 0, \
+                       descrip: (descripP), \
+                       argDescrip: (argDescripP), \
+               }
+
+               SANDBOX_SERVER_POPT("setuid"   ,POPT_ARG_STRING,&optarg_setuid,
+                               N_("Username or UID to become; \"-\" for disable"),N_("UID")),
+               SANDBOX_SERVER_POPT("setgid"   ,POPT_ARG_STRING,&optarg_setgid,
+                               N_("Groupname or GID to become; \"-\" for disable"),N_("GID")),
+               SANDBOX_SERVER_POPT("chroot"   ,POPT_ARG_STRING,&optarg_chroot,
+                               N_("Pathname to directory for chroot(2); \"-\" for disable"),N_("directory")),
+               SANDBOX_SERVER_POPT("no-rlimit",POPT_ARG_NONE  ,&optarg_no_rlimit,
+                               N_("Disable setrlimit(2) restrictions"),NULL),
+
+#undef SANDBOX_SERVER_POPT
                POPT_AUTOHELP
                POPT_TABLEEND
                };
 
 
+static gchar *fatal_argv0;
+
+static void fatal(const char *fmt,...)
+{
+va_list ap;
+
+       fprintf(stderr,"%s: ",fatal_argv0);
+       va_start(ap,fmt);
+       vfprintf(stderr,fmt,ap);
+       va_end(ap);
+       fprintf(stderr,"!\nAborting!\n");
+       exit(EXIT_FAILURE);
+       /* NOTREACHED */
+       for (;;);
+}
+
+static void check_dir_safety(const gchar *dir)
+{
+gchar *local_dir;
+const gchar *cs;
+static gint depth=0;
+
+       if (++depth>=1000)
+               fatal("Loop count >=%d during check_dir_safety(\"%s\")",depth,dir);
+
+       if (*dir!='/')
+               fatal("chroot path \"%s\" not absolute",dir);
+       dir=captive_printf_alloca("%s/",dir);
+       local_dir=(gchar *)captive_strdup_alloca(dir);
+       for (cs=dir;cs;cs=strchr(cs+1,'/')) {
+struct stat statbuf;
+
+               g_assert(*cs=='/');
+               /* Include the trailing '/' to resolve the root directory as "/". */
+               memcpy(local_dir,dir,cs+1-dir);
+               local_dir[cs+1-dir]=0;
+               if (lstat(local_dir,&statbuf))
+                       fatal("lstat(\"%s\") of chroot path component: %m",local_dir);
+               if (S_ISLNK(statbuf.st_mode)) {
+char linkbuf[PATH_MAX];
+int linkbuflen;
+
+                       if (0>(linkbuflen=readlink(local_dir,linkbuf,sizeof(linkbuf)-1)))
+                               fatal("readlink(\"%s\") of chroot path component: %m",local_dir);
+                       linkbuf[linkbuflen]=0;
+                       check_dir_safety(linkbuf);
+                       if (stat(local_dir,&statbuf))   /* NOT lstat(2) */
+                               fatal("stat(\"%s\") of chroot path component: %m",local_dir);
+                       }
+               if (!S_ISDIR(statbuf.st_mode))
+                       fatal("lstat/stat(\"%s\") of chroot path component is !S_ISDIR",local_dir);
+               if (statbuf.st_uid!=0)
+                       fatal("lstat/stat(\"%s\") of chroot path component has UID %d !=0",local_dir,(int)statbuf.st_uid);
+               if (statbuf.st_gid!=0 && (statbuf.st_mode&0020))
+                       fatal("lstat/stat(\"%s\") of chroot path component has GID %d !=0 and mode has 0020",local_dir,(int)statbuf.st_gid);
+               if (                     (statbuf.st_mode&0002))
+                       fatal("lstat/stat(\"%s\") of chroot path component mode has 0002",local_dir,(int)statbuf.st_gid);
+               }
+
+       depth--;
+}
+
+static void chrooted_unlink_recursive(const gchar *pathname)
+{
+DIR *dir;
+struct dirent *dirent;
+static gint depth=0;
+struct stat statbuf;
+
+       if (++depth>=1000)
+               fatal("Loop count >=%d during chrooted_unlink_recursive(\"%s\")",depth,pathname);
+
+       /* Security: Do not allow anyone to escape the sandbox directory by symlinks. */
+       if (lstat(pathname,&statbuf))
+               fatal("Cannot lstat(\"%s\") to delete leftover sandbox files: %m",pathname);
+       if (!S_ISDIR(statbuf.st_mode)) {
+               if (unlink(pathname))
+                       fatal("Cannot unlink(\"%s\") to delete leftover sandbox files: %m",pathname);
+               goto done;
+               }
+       if (!(dir=opendir(pathname)))
+               fatal("Cannot opendir(\"%s\") to delete leftover sandbox files: %m",pathname);
+       while (errno=0,(dirent=readdir(dir))) {
+gchar *dirent_path;
+
+               if (!strcmp(dirent->d_name,".") || !strcmp(dirent->d_name,".."))
+                       continue;
+               dirent_path=g_strdup_printf("%s/%s",pathname,dirent->d_name);
+               chrooted_unlink_recursive(dirent_path);
+               g_free(dirent_path);
+               }
+       if (errno)
+               fatal("Cannot readdir(\"%s\") during delete of leftover sandbox files: %m",pathname);
+       if (closedir(dir))
+               fatal("Cannot closedir(\"%s\") during delete of leftover sandbox files: %m",pathname);
+       if (rmdir(pathname))
+               fatal("Cannot rmdir(\"%s\") during delete of leftover sandbox files: %m",pathname);
+
+done:
+       depth--;
+}
+
+static void chrooted_cleanuplockeddirs(const gchar *pathname,const gchar *prefix)
+{
+DIR *dir;
+struct dirent *dirent;
+
+       if (!(dir=opendir(pathname))) {
+               if (errno!=ENOTDIR)
+                       fatal("Cannot opendir(\"%s\") to delete leftover sandbox files: %m",pathname);
+               /* errno==ENOTDIR, a regular file */
+               if (unlink(pathname))
+                       fatal("Cannot unlink(\"%s\") to delete leftover sandbox files: %m",pathname);
+               return;
+               }
+       while (errno=0,(dirent=readdir(dir))) {
+gchar *dirent_path;
+int direntfd;
+
+               if (!strcmp(dirent->d_name,".") || !strcmp(dirent->d_name,".."))
+                       continue;
+               if (strncmp(dirent->d_name,prefix,strlen(prefix)))
+                       continue;
+               dirent_path=g_strdup_printf("%s/%s",pathname,dirent->d_name);
+               if (-1==(direntfd=open(dirent_path,O_RDONLY))) {
+                       if (errno==ENOENT)      /* It could disappear in the meantime. */
+                               goto next_dirent_free_dirent_path;
+                       fatal("Cannot open(\"%s\") as the child directory during delete of leftover sandbox files: %m",dirent_path);
+                       }
+               if (flock(direntfd,LOCK_EX|LOCK_NB)) {
+                       if (errno==EWOULDBLOCK) /* Valid directory in use. */
+                               goto next_dirent_close_direntfd;
+                       fatal("Cannot flock(\"%s\",LOCK_EX|LOCK_NB) child directory during delete of leftover sandbox files: %m",dirent_path);
+                       }
+               chrooted_unlink_recursive(dirent_path);
+next_dirent_close_direntfd:
+               if (close(direntfd))
+                       fatal("Cannot close(\"%s\") child directory during delete of leftover sandbox files: %m",dirent_path);
+next_dirent_free_dirent_path:
+               g_free(dirent_path);
+               }
+       if (errno)
+               fatal("Cannot readdir(\"%s\") during delete of leftover sandbox files: %m",pathname);
+       if (closedir(dir))
+               fatal("Cannot closedir(\"%s\") during delete of leftover sandbox files: %m",pathname);
+}
+
+static void chrooted_createdir(const gchar *dir,uid_t uid,gid_t gid,gboolean lock)
+{
+gint retries;
+
+       for (retries=0;retries<10;retries++) {
+struct stat statbuf;
+int dirfd;
+
+               if (mkdir(dir,0711)) {
+                       if (errno!=EEXIST)
+                               fatal("Failed to create chroot directory \"%s\": %m",dir);
+                       chrooted_unlink_recursive(dir);
+                       if (mkdir(dir,0711))
+                               fatal("Failed to create chroot directory \"%s\" after attempted unlink: %m",dir);
+                       }
+               if (!lock)
+                       break;
+               dirfd=open(dir,O_RDONLY);
+               if (dirfd==-1) {
+                       if (errno!=ENOENT)
+                               fatal("Failed to open created chroot directory \"%s\" to lock it: %m",dir);
+                       continue;
+                       }
+               /* Do not use 'LOCK_NB' here as the garbage collector should release it soon. */
+               if (flock(dirfd,LOCK_EX))
+                       fatal("Failed to lock created chroot directory \"%s\": %m",dir);
+               if (lstat(dir,&statbuf)) {
+                       if (errno!=ENOENT)
+                               fatal("Failed to lstat(2) created chroot directory \"%s\": %m",dir);
+                       if (close(dirfd))
+                               fatal("Failed to close created and locked chroot directory \"%s\": %m",dir);
+                       continue;
+                       }
+               /* Leave 'dirfd' open to leave it LOCK_EX-ed. */
+               break;
+               }
+       if (chown(dir,uid,gid))
+               fatal("Failed to chown(\"%s\",%d,%d): %m",dir,uid,gid);
+       if (chmod(dir,0711))    /* Just to be safe after chown(2); should be already done by mkdir(2). */
+               fatal("Failed to chmod(\"%s\",0%o): %m",dir,0711);
+}
+
+static void sandbox_server_rlimit(int resource,const gchar *resource_string,rlim_t rlim_max)
+{
+struct rlimit rlim;
+
+       rlim.rlim_cur=rlim.rlim_max=rlim_max;
+       if (setrlimit(resource,&rlim))
+               fatal("setrlimit(%s,%d): %m",resource_string,(int)rlim_max);
+       if (getrlimit(resource,&rlim))
+               fatal("getrlimit(%s,%d): %m",resource_string,(int)rlim_max);
+       if (rlim.rlim_cur!=rlim_max || rlim.rlim_max!=rlim_max)
+               fatal("Unsuccessful setrlimit(%s)",resource_string);
+}
+
+static void sandbox_server_mkdir_p(const gchar *dirpathname)
+{
+gchar *pathname=(/* de-const */ gchar *)captive_strdup_alloca(dirpathname);
+gchar *gs,*gs2;
+
+       /* Missing mkdir(2) of the last component path is intentional: */
+       for (gs=pathname;(gs2=strchr(gs,'/'));gs=gs2) {
+               *gs2='\0';
+               if (*pathname && mkdir(pathname,S_ISVTX|0777)) {
+                       if (errno!=EEXIST)
+                               fatal("Failed to mkdir(\"%s\"): %m",pathname);
+                       }
+               *gs2++='/';
+               }
+}
+
+static const gchar *chrooted_orbit_dir;
+
+static void chroot_setup(gboolean fragile)
+{
+uid_t want_uid=0;
+const gchar *want_uid_name=NULL;
+gid_t want_gid=0;
+char *endptr;
+const gchar *chroot_pid_hashkey_dir=NULL;
+
+       if (fragile) {
+               captive_sandbox_fd_closeup(2 /* STDERR */ +1);
+               clearenv();
+               }
+
+#define CLEANEMPTY(var) G_STMT_START { \
+               if ((var) && (!*(var) || *(var)=='-')) \
+                       (var)=NULL; \
+               } G_STMT_END
+       CLEANEMPTY(optarg_setgid);
+       CLEANEMPTY(optarg_setuid);
+       CLEANEMPTY(optarg_chroot);
+#undef CLEANEMPTY
+
+       if (optarg_setgid) {
+long want_gidl;
+
+               want_gidl=strtol(optarg_setgid,&endptr,10);
+               if (!endptr || !*endptr) {
+                       want_gid=want_gidl;
+                       if (want_gidl<=0 || want_gid!=(gid_t)want_gidl)
+                               fatal("Numeric setgid not parsable: %s",optarg_setgid);
+                       }
+               else {
+struct group *want_gid_group=NULL;
+                       if (!(want_gid_group=getgrnam(optarg_setgid)))
+                               fatal("Unable to query setgid group name \"%s\"",optarg_setgid);
+                       want_gid=want_gid_group->gr_gid;
+                       }
+               }
+
+       if (optarg_setuid) {
+long want_uidl;
+struct passwd *want_uid_passwd;
+               want_uidl=strtol(optarg_setuid,&endptr,10);
+               if (!endptr || !*endptr) {
+                       want_uid=want_uidl;
+                       if (want_uidl<=0 || want_uid!=(gid_t)want_uidl)
+                               fatal("Numeric setuid not parsable: %s",optarg_setuid);
+                       }
+               else {
+                       if (!(want_uid_passwd=getpwnam(optarg_setuid)))
+                               fatal("Unable to query setuid user name \"%s\"",optarg_setuid);
+                       want_uid=want_uid_passwd->pw_uid;
+                       }
+               if (!want_uid)
+                       fatal("Unable to detect setuid UID");
+               if (!(want_uid_passwd=getpwuid(want_uid)))
+                       fatal("Unable to query name of UID %d",(int)want_uid);
+               want_uid_name=captive_strdup_alloca(want_uid_passwd->pw_name);
+               }
+
+       /* Prevent: GLib-WARNING **: getpwuid_r(): failed due to unknown user id (42)
+        * Try to invoke GLib g_get_any_init() before possible chroot(2) below.
+        */
+       g_get_user_name();
+       g_get_real_name();
+       g_get_home_dir();
+       g_get_tmp_dir();
+
+       /* Pre-resolve "link_get_tmpdir" symbol to prevent its later failed
+        * resolving in chroot(2) mode in Debian dynamic build.
+        */
+#ifdef HAVE_ORBIT_LINK
+       g_free(link_get_tmpdir());      /* returns g_strdup()ed string */
+#else
+       g_free(linc_get_tmpdir());      /* returns g_strdup()ed string */
+#endif
+
+       if (fragile && !optarg_chroot)
+               fatal("Fragile setuid/root environment but no --chroot set");
+       if (optarg_chroot) {
+const gchar *chroot_pid_dir;
+GRand *grand;
+gchar chroot_hashkey[CHROOT_PATH_HASHKEY_LENGTH+1],*s;
+gint gi;
+
+               check_dir_safety(optarg_chroot);
+               if (!(grand=g_rand_new()))      /* I hope g_rand_new() is security-safe. It looks so. */
+                       fatal("Cannot initialize random number generator g_rand_new()");
+               for (s=chroot_hashkey;s<chroot_hashkey+CHROOT_PATH_HASHKEY_LENGTH;s++) {
+                       gi=g_rand_int_range(grand,0,10+26+26);
+                       /**/ if (gi>=0 && gi<10)
+                               *s='0'+gi-(0);
+                       else if (gi>=10+0 && gi<10+26)
+                               *s='a'+gi-(10);
+                       else if (gi>=10+26+0 && gi<10+26+26)
+                               *s='A'+gi-(10+26);
+                       else g_assert_not_reached();
+                       }
+               g_rand_free(grand);
+               *s=0;
+               if (geteuid()==0) {     /* Not 'fragile' as we can be native 'root'. */
+                       chrooted_cleanuplockeddirs(optarg_chroot,"s-");
+                       chrooted_cleanuplockeddirs(captive_printf_alloca("%s/tmp",optarg_chroot),"captive-orbit-");
+                       }
+               chroot_pid_dir=captive_printf_alloca("%s/s-%d",optarg_chroot,(int)getpid());
+               chrooted_createdir(chroot_pid_dir,(!optarg_setuid ? (uid_t)-1 : want_uid),(!optarg_setgid ? (gid_t)-1 : want_gid),
+                               TRUE);  /* lock */
+               chroot_pid_hashkey_dir=captive_printf_alloca("%s/%s",chroot_pid_dir,chroot_hashkey);
+               chrooted_createdir(chroot_pid_hashkey_dir,(!optarg_setuid ? (uid_t)-1 : want_uid),(!optarg_setgid ? (gid_t)-1 : want_gid),
+                               FALSE); /* lock */
+               if (chroot(chroot_pid_hashkey_dir))
+                       fatal("Failed to chroot(\"%s\"): %m",chroot_pid_hashkey_dir);
+               if (chdir("/"))
+                       fatal("Failed to chdir(\"%s\"): %m","/");
+               /* Now it is safe to set umask(0000) as we are protected by 'chroot_hashkey'.
+                * We need it to permit our spawning parent to hardlink its sockets to us.
+                */
+               umask(0000);
+               if (umask(0000)!=0000)
+                       fatal("Failed to set umask(0%o): %m",0000);
+               printf("chroot_pid_hashkey_dir=%s\n",chroot_pid_hashkey_dir);
+               }
+
+       if (fragile && !optarg_setgid)
+               fatal("Fragile setuid/root environment but no --setgid set");
+       if (optarg_setgid) {
+               if (!want_gid || setgid(want_gid))
+                       fatal("Failed to setgid(%d)",(!want_gid ? -1 : (int)want_gid));
+               if (setgroups(1 /* size */,&want_gid))
+                       fatal("Failed to setgroups(1,[%d])",(!want_gid ? -1 : (int)want_gid));
+               }
+       if (fragile && !optarg_setuid)
+               fatal("Fragile setuid/root environment but no --setuid set");
+       if (optarg_setuid) {
+               if (!want_uid || setuid(want_uid))
+                       fatal("Failed to setuid(%d)",(!want_uid ? -1 : (int)want_uid));
+               }
+
+       /* Prepare /t for /t/o-$PID directories for ORBit2
+        * and also for parent's hardlink to its /t/o-$pid directory. */
+       if (optarg_chroot) {
+gchar *chrooted_orbit_dir_old;
+
+               if (mkdir("/t",S_ISVTX|0777)) {
+                       if (errno!=EEXIST)
+                               fatal("Failed to mkdir(\"%s\"): %m","/t");
+                       }
+               if (mkdir("/etc",0700))
+                       fatal("Failed to mkdir(\"%s\"): %m","/etc");
+               if (want_uid_name && want_uid && want_gid) {
+FILE *f;
+                       if (!(f=fopen("/etc/passwd","w")))
+                               fatal("Failed to fopen(\"%s\",\"w\"): %m","/etc/passwd");
+                       if (0>fprintf(f,"%s:*:%d:%d:%s:%s:/bin/false\n",want_uid_name,(int)want_uid,(int)want_gid,want_uid_name,optarg_chroot))
+                               fatal("Failed to fprintf(\"%s\"): %m","/etc/passwd");
+                       if (fclose(f))
+                               fatal("Failed to fclose(\"%s\"): %m","/etc/passwd");
+                       }
+               g_assert(chroot_pid_hashkey_dir!=NULL);
+               chrooted_orbit_dir=g_strdup_printf("%s/t/o-%d",chroot_pid_hashkey_dir,getpid());
+               /* Last pathname component is not created: */
+               sandbox_server_mkdir_p(chrooted_orbit_dir);
+               /* Prepare '/tmp' for the initial CORBA_ORB_init() default path.
+                * Workaround sandbox_server_mkdir_p() does not create last component.
+                * Do not use '/tmp' directly as some distributions may set custom
+                * tmpdir pathname by $ENV{"TMPDIR"} etc.
+                */
+               sandbox_server_mkdir_p(captive_printf_alloca("%s/",g_get_tmp_dir()));
+               /* Set '0700' to prevent: Wrong permissions for ...
+                * by linc-1.0.1-1/src/linc-protocols.c/make_local_tmpdir()
+                */
+               if (mkdir(chrooted_orbit_dir,0700)) {
+                       /* Do not: g_assert(errno==EEXIST);
+                        * as if 'optarg_chroot' the whole chroot(2)ed directory should be ours.
+                        */
+                       fatal("Cannot created chrooted_orbit_dir \"%s\": %m",chrooted_orbit_dir);
+                       }
+               /* Init 'orb' to pass through its linc_set_tmpdir() to not to be overriden below. */
+               {
+CORBA_ORB orb;
+CORBA_Environment ev;
+int orb_argc=1;
+gchar *orb_argv[]={
+               (gchar *)captive_strdup_alloca("captive-sandbox-server"),
+               NULL};
+
+                       CORBA_exception_init(&ev);
+                       /* libcaptive is single-threaded only, caller must lock it.
+                        * If thread A spawned the sandbox while currently doing its own work
+                        * and thread B calls the sandbox thread B waits on ORB_run()
+                        * while the sandbox waits for the response of thread A ORB. Deadlock.
+                        * "orbit-local-non-threaded-orb" requests thread unaware ORB.
+                        */
+                       orb=CORBA_ORB_init(&orb_argc,orb_argv,"orbit-local-non-threaded-orb",&ev);
+                       if (orb==CORBA_OBJECT_NIL)
+                               fatal("Cannot initialize CORBA ORB (CORBA_OBJECT_NIL): %m");
+                       if (ev._major!=CORBA_NO_EXCEPTION)
+                               fatal("Cannot initialize CORBA ORB (exception): %m");
+                       }
+#ifdef HAVE_ORBIT_LINK
+               chrooted_orbit_dir_old=link_get_tmpdir();       /* returns g_strdup()ed string */
+#else
+               chrooted_orbit_dir_old=linc_get_tmpdir();       /* returns g_strdup()ed string */
+#endif
+               g_assert(chrooted_orbit_dir_old!=NULL);
+#ifdef HAVE_ORBIT_LINK
+               link_set_tmpdir(chrooted_orbit_dir);
+#else
+               linc_set_tmpdir(chrooted_orbit_dir);
+#endif
+               if (!*chrooted_orbit_dir_old)
+                       fatal("Cannot detect chrooted_orbit_dir: --with-orbit-line incompatible with ORBit2 version");
+               if (rmdir(chrooted_orbit_dir_old))
+                       fatal("Cannot remove old chrooted_orbit_dir \"%s\": %m",chrooted_orbit_dir_old);
+               g_free(chrooted_orbit_dir_old);
+               /* chmod(2) it to prevent mode limitation by
+                * active ulimit(2) of being executed by mount(8).
+                */
+               /* Set '0777' as our parent does not have 'captive' user permissions. */
+               if (chmod(chrooted_orbit_dir,S_ISVTX|0777))
+                       fatal("Cannot chmod 0%o chrooted_orbit_dir \"%s\": %m",S_ISVTX|0777,chrooted_orbit_dir);
+               printf("chrooted_orbit_dir=%s\n",chrooted_orbit_dir);
+               }
+
+       if (fragile || !optarg_no_rlimit) {
+#define SANDBOX_SERVER_RLIMIT(what,how) sandbox_server_rlimit((what),G_STRINGIFY(what),(how))
+               SANDBOX_SERVER_RLIMIT(RLIMIT_NPROC,0);
+               SANDBOX_SERVER_RLIMIT(RLIMIT_MEMLOCK,0);
+               SANDBOX_SERVER_RLIMIT(RLIMIT_CORE,0);
+               SANDBOX_SERVER_RLIMIT(RLIMIT_FSIZE,0);
+               SANDBOX_SERVER_RLIMIT(RLIMIT_NOFILE,16);        /* >=6; newer ORBit2/link require >6 */
+               /* FIXME: Why flock(dirfd,...) in chrooted_createdir() succeeds?: */
+               SANDBOX_SERVER_RLIMIT(RLIMIT_LOCKS,0);
+#undef SANDBOX_SERVER_RLIMIT
+               }
+
+       if (fragile) {
+gid_t gid_list[2];
+int gid_list_size,i;
+
+               if (getuid()!=want_uid)
+                       fatal("getuid()=%d != want_uid=%d",(int)getuid(),(int)want_uid);
+               if (geteuid()!=want_uid)
+                       fatal("geteuid()=%d != want_uid=%d",(int)geteuid(),(int)want_uid);
+               if (getgid()!=want_gid)
+                       fatal("getgid()=%d != want_gid=%d",(int)getgid(),(int)want_gid);
+               if (getegid()!=want_gid)
+                       fatal("getegid()=%d != want_gid=%d",(int)getegid(),(int)want_gid);
+               gid_list_size=getgroups(G_N_ELEMENTS(gid_list),gid_list);
+               for (i=0;i<gid_list_size;i++) {
+                       if (gid_list[i]!=want_gid)
+                               fatal("getgroups() list member @%d %d != want_gid=%d",i,(int)gid_list[i],(int)want_gid);
+                       }
+               }
+}
+
+
 int main(int argc,char **argv)
 {
 poptContext context;
 int errint;
 const char *cmd_arg;
 struct captive_options options;
+gboolean fragile;
+
+       g_log_set_always_fatal(~(0
+                       |G_LOG_LEVEL_MESSAGE
+                       |G_LOG_LEVEL_INFO
+                       |G_LOG_LEVEL_DEBUG
+                       ));
+
+       fatal_argv0=argv[0];
+       fragile=(getuid()!=geteuid() || getuid()==0 || geteuid()==0);
 
-       /* Initialize the i18n stuff */
-       setlocale(LC_ALL,"");
-       bindtextdomain(PACKAGE,LOCALEDIR);
-       textdomain(PACKAGE);
+#ifndef MAINTAINER_MODE
+       if (fragile && (argc!=1 || argv[1]))
+               fatal("Arguments invalid as running in fragile setuid/root environment");
 
-       /* Initialize GObject subsystem of GLib. */
-       g_type_init();
+       if (fragile)
+               chroot_setup(TRUE);
+#endif /* MAINTAINER_MODE */
+
+       captive_standalone_init();
 
        captive_options_init(&options);
        captive_options=&options;       /* for parsing by 'CAPTIVE_POPT_INCLUDE' */
@@ -95,9 +646,13 @@ struct captive_options options;
        /* 'cmd_arg'-style args gets cleared by 'poptFreeContext(context);' below */
        poptFreeContext(context);
 
+#ifdef MAINTAINER_MODE
+       chroot_setup(FALSE);
+#endif /* MAINTAINER_MODE */
+
        captive_options=NULL;   /* already parsed by 'CAPTIVE_POPT_INCLUDE' */
 
-       captive_corba_sandbox_child();
+       captive_corba_sandbox_child(chrooted_orbit_dir);
 
        g_assert_not_reached();
        return EXIT_SUCCESS;