#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 "../../libcaptive/sandbox/split.h" /* for captive_sandbox_fd_closeup(); FIXME */
+#include <grp.h>
+#include <pwd.h>
+
+
+/* CONFIG: */
+
+#define CHROOT_PATH_HASHKEY_LENGTH (64)
GQuark sandbox_server_main_error_quark(void)
}
+static gchar *optarg_setuid=CAPTIVE_SANDBOX_SETUID;
+static gchar *optarg_setgid=CAPTIVE_SANDBOX_SETGID;
+static gchar *optarg_chroot=CAPTIVE_SANDBOX_CHROOT;
+
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")),
+
+#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)
+ fatal("lstat/stat(\"%s\") of chroot path component has GID %d !=0",local_dir,(int)statbuf.st_gid);
+ if ((statbuf.st_mode&(S_IFDIR|S_ISVTX|0111)) != (S_IFDIR|0111))
+ fatal("lstat/stat(\"%s\") of chroot path component has mode 0%o !=04[01]111",local_dir,(int)statbuf.st_mode);
+ }
+
+ depth--;
+}
+
+static void unlink_recursive(const gchar *pathname)
+{
+DIR *dir;
+struct dirent *dirent;
+static gint depth=0;
+
+ if (++depth>=1000)
+ fatal("Loop count >=%d during unlink_recursive(\"%s\")",depth,pathname);
+
+ 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 pathname(\"%s\") to delete leftover sandbox files: %m",pathname);
+ goto done;
+ }
+ 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);
+ 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_createdir(const gchar *dir,uid_t uid,gid_t gid)
+{
+ if (mkdir(dir,0711)) {
+ if (errno!=EEXIST)
+ fatal("Failed to create chroot directory \"%s\": %m",dir);
+ unlink_recursive(dir);
+ if (mkdir(dir,0711))
+ fatal("Failed to create chroot directory \"%s\" after attempted unlink: %m",dir);
+ }
+ 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 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;
+
+ 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);
+ }
+
+ if (fragile && !optarg_chroot)
+ fatal("Fragile setuid/root environment but no --chroot set");
+ if (optarg_chroot) {
+const gchar *chroot_pid_dir,*chroot_pid_hashkey_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;
+ chroot_pid_dir=captive_printf_alloca("%s/sandbox-server-%d",optarg_chroot,(int)getpid());
+ chrooted_createdir(chroot_pid_dir,(!optarg_setuid ? (uid_t)-1 : want_uid),(!optarg_setgid ? (gid_t)-1 : want_gid));
+ 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));
+ 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);
+ if (want_uid_name) {
+ printf("chroot_pid_hashkey_dir=%s\n",chroot_pid_hashkey_dir);
+ chrooted_orbit_dir=g_strdup_printf("/tmp/orbit-%s",want_uid_name);
+ printf("chrooted_orbit_dir=%s\n",chrooted_orbit_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 /tmp for /tmp/orbit-$username directories for ORBit2
+ * and also for parent's hardlink to its /tmp/captive-orbit-$pid directory. */
+ if (optarg_chroot) {
+ if (mkdir("/tmp",S_ISVTX|0777))
+ fatal("Failed to mkdir(\"%s\"): %m","/tmp");
+ 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",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");
+ }
+ }
+
+ 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);
+
+#ifndef MAINTAINER_MODE
+ if (fragile && (argc!=1 || argv[1]))
+ fatal("Arguments invalid as running in fragile setuid/root environment");
+
+ if (fragile)
+ chroot_setup(TRUE);
+#endif /* MAINTAINER_MODE */
/* Initialize the i18n stuff */
setlocale(LC_ALL,"");
/* '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;