int main(int argc, char *argv[]) { int rc, sync[2]; pid_t pid = -1; siginfo_t status; struct mount *mounts = NULL; struct netif *netifs = NULL; struct cgroup *cgroups = NULL; struct user *users = NULL; #if HAVE_LIBCAP_NG struct capability *caps = NULL; #endif char *master; _close_ int master_fd = -1; char ephemeral_dir[] = "/tmp/pflask-ephemeral-XXXXXX"; int clone_flags = CLONE_NEWNS | CLONE_NEWIPC | CLONE_NEWPID | #ifdef CLONE_NEWCGROUP CLONE_NEWCGROUP | #endif CLONE_NEWUTS; struct gengetopt_args_info args; if (cmdline_parser(argc, argv, &args) != 0) return 1; for (unsigned int i = 0; i < args.mount_given; i++) { validate_optlist("--mount", args.mount_arg[i]); mount_add_from_spec(&mounts, args.mount_arg[i]); } for (unsigned int i = 0; i < args.netif_given; i++) { clone_flags |= CLONE_NEWNET; if (args.netif_arg != NULL) { validate_optlist("--netif", args.netif_arg[i]); netif_add_from_spec(&netifs, args.netif_arg[i]); } } if (args.user_given && !args.user_map_given) { uid_t uid; gid_t gid; clone_flags |= CLONE_NEWUSER; if (user_get_uid_gid(args.user_arg, &uid, &gid)) { user_add_map(&users, 'u', uid, uid, 1); user_add_map(&users, 'g', gid, gid, 1); } } for (unsigned int i = 0; i < args.user_map_given; i++) { size_t count; uid_t id, host_id; char *start = args.user_map_arg[i], *end = NULL; validate_optlist("--user-map", args.user_map_arg[i]); clone_flags |= CLONE_NEWUSER; id = strtoul(start, &end, 10); if (*end != ':') fail_printf("Invalid value '%s' for --user-map", args.user_map_arg[i]); start = end + 1; host_id = strtoul(start, &end, 10); if (*end != ':') fail_printf("Invalid value '%s' for --user-map", args.user_map_arg[i]); start = end + 1; count = strtoul(start, &end, 10); if (*end != '\0') fail_printf("Invalid value '%s' for --user-map", args.user_map_arg[i]); user_add_map(&users, 'u', id, host_id, count); user_add_map(&users, 'g', id, host_id, count); } for (unsigned int i = 0; i < args.cgroup_given; i++) cgroup_add(&cgroups, args.cgroup_arg[i]); #if HAVE_LIBCAP_NG for (unsigned int i = 0; i < args.caps_given; i++) capability_add(&caps, args.caps_arg[i]); #endif if (args.no_userns_flag) clone_flags &= ~(CLONE_NEWUSER); if (args.no_mountns_flag) clone_flags &= ~(CLONE_NEWNS); if (args.no_netns_flag) clone_flags &= ~(CLONE_NEWNET); if (args.no_ipcns_flag) clone_flags &= ~(CLONE_NEWIPC); if (args.no_utsns_flag) clone_flags &= ~(CLONE_NEWUTS); if (args.no_pidns_flag) clone_flags &= ~(CLONE_NEWPID); if (args.attach_given) { master_fd = recv_pty(args.attach_arg); fail_if(master_fd < 0, "Invalid PID '%u'", args.attach_arg); process_pty(master_fd); return 0; } open_master_pty(&master_fd, &master); if (args.detach_flag) do_daemonize(); sync_init(sync); if (args.ephemeral_flag) { if (!mkdtemp(ephemeral_dir)) sysf_printf("mkdtemp()"); } pid = do_clone(&clone_flags); if (!pid) { closep(&master_fd); rc = prctl(PR_SET_PDEATHSIG, SIGKILL); sys_fail_if(rc < 0, "prctl(PR_SET_PDEATHSIG)"); rc = setsid(); sys_fail_if(rc < 0, "setsid()"); sync_barrier_parent(sync, SYNC_START); sync_close(sync); open_slave_pty(master); setup_user(args.user_arg); if (args.hostname_given) { rc = sethostname(args.hostname_arg, strlen(args.hostname_arg)); sys_fail_if(rc < 0, "Error setting hostname"); } setup_mount(mounts, args.chroot_arg, args.ephemeral_flag ? ephemeral_dir : NULL); if (args.chroot_given) { setup_nodes(args.chroot_arg); setup_ptmx(args.chroot_arg); setup_symlinks(args.chroot_arg); setup_console(args.chroot_arg, master); do_chroot(args.chroot_arg); } if (clone_flags & CLONE_NEWNET) config_netif(); umask(0022); #if HAVE_LIBCAP_NG setup_capabilities(caps); #endif if (args.chdir_given) { rc = chdir(args.chdir_arg); sys_fail_if(rc < 0, "Error changing cwd"); } if (args.chroot_given) { char *term = getenv("TERM"); if (!args.keepenv_flag) clearenv(); setenv("PATH", "/usr/sbin:/usr/bin:/sbin:/bin", 1); setenv("USER", args.user_arg, 1); setenv("LOGNAME", args.user_arg, 1); if (term) setenv("TERM", term, 1); } for (unsigned int i = 0; i < args.setenv_given; i++) { rc = putenv(strdup(args.setenv_arg[i])); sys_fail_if(rc != 0, "Error setting environment"); } setenv("container", "pflask", 1); if (argc > optind) rc = execvpe(argv[optind], argv + optind, environ); else rc = execle("/bin/bash", "-bash", NULL, environ); sys_fail_if(rc < 0, "Error executing command"); } sync_wait_child(sync, SYNC_START); if (args.chroot_given && (clone_flags & CLONE_NEWUSER)) setup_console_owner(master, users); setup_cgroup(cgroups, pid); setup_netif(netifs, pid); #ifdef HAVE_DBUS register_machine(pid, args.chroot_given ? args.chroot_arg : ""); #endif if (clone_flags & CLONE_NEWUSER) setup_user_map(users, pid); sync_wake_child(sync, SYNC_DONE); sync_close(sync); if (args.detach_flag) serve_pty(master_fd); else process_pty(master_fd); kill(pid, SIGKILL); rc = waitid(P_PID, pid, &status, WEXITED); sys_fail_if(rc < 0, "Error waiting for child"); switch (status.si_code) { case CLD_EXITED: if (status.si_status != 0) err_printf("Child failed with code '%d'", status.si_status); else ok_printf("Child exited"); break; case CLD_KILLED: err_printf("Child was terminated by signal '%d'", status.si_status); break; default: err_printf("Child failed"); break; } sync_close(sync); clean_cgroup(cgroups); if (args.ephemeral_flag) { rc = rmdir(ephemeral_dir); sys_fail_if(rc != 0, "Error deleting ephemeral directory: %s", ephemeral_dir); } cmdline_parser_free(&args); return status.si_status; }
int main(int argc, char *argv[]) { int rc, i; pid_t pid = -1; pid_t ppid = getpid(); uid_t uid = -1; gid_t gid = -1; _free_ char *user = NULL; _free_ char *dest = NULL; _free_ char *change = NULL; _free_ char *env = NULL; _free_ char *cgroup = NULL; _close_ int master_fd = -1; char *master_name; int detach = 0; int keepenv = 0; siginfo_t status; while ((rc = getopt_long(argc, argv, short_opts, long_opts, &i)) !=-1) { switch (rc) { case 'm': validate_optlist("--mount", optarg); add_mount_from_spec(optarg); break; case 'n': clone_flags |= CLONE_NEWNET; if (optarg != NULL) { validate_optlist("--netif", optarg); add_netif_from_spec(optarg); } break; case 'u': clone_flags |= CLONE_NEWUSER; freep(&user); user = strdup(optarg); break; case 'r': freep(&dest); dest = realpath(optarg, NULL); if (dest == NULL) sysf_printf("realpath()"); break; case 'c': freep(&change); change = strdup(optarg); break; case 'g': validate_optlist("--cgroup", optarg); validate_cgroup_spec(optarg); freep(&change); cgroup = strdup(optarg); break; case 'd': detach = 1; break; case 'a': { char *end = NULL; pid = strtol(optarg, &end, 10); if (*end != '\0') fail_printf("Invalid value '%s' for --attach", optarg); break; } case 's': { validate_optlist("--setenv", optarg); if (env != NULL) { char *tmp = env; rc = asprintf(&env, "%s,%s", env, optarg); if (rc < 0) fail_printf("OOM"); freep(&tmp); } else { env = strdup(optarg); } break; } case 'k': keepenv = 1; break; case 'U': clone_flags &= ~(CLONE_NEWUSER); break; case 'M': clone_flags &= ~(CLONE_NEWNS); break; case 'N': clone_flags &= ~(CLONE_NEWNET); break; case 'I': clone_flags &= ~(CLONE_NEWIPC); break; case 'H': clone_flags &= ~(CLONE_NEWUTS); break; case 'P': clone_flags &= ~(CLONE_NEWPID); break; case '?': case 'h': help(); return 0; } } if (pid != -1) { master_fd = recv_pty(pid); if (master_fd < 0) fail_printf("Invalid PID '%u'", pid); pid = -1; goto process_fd; } if (user == NULL) { user = strdup("root"); if (user == NULL) fail_printf("OOM"); } open_master_pty(&master_fd, &master_name); uid = getuid(); gid = getgid(); if (detach == 1) do_daemonize(); pid = do_clone(); if (pid == 0) { closep(&master_fd); open_slave_pty(master_name); rc = setsid(); if (rc < 0) sysf_printf("setsid()"); rc = prctl(PR_SET_PDEATHSIG, SIGKILL); if (rc < 0) sysf_printf("prctl(PR_SET_PDEATHSIG)"); if (clone_flags & CLONE_NEWUSER) map_user_to_user(uid, gid, user); do_cgroup(cgroup, ppid); do_mount(dest); if (dest != NULL) { copy_nodes(dest); make_ptmx(dest); make_symlinks(dest); make_console(dest, master_name); do_chroot(dest); } if (clone_flags & CLONE_NEWNET) setup_loopback(); umask(0022); /* TODO: drop capabilities */ do_user(user); if (change != NULL) { rc = chdir(change); if (rc < 0) sysf_printf("chdir()"); } if (dest != NULL) { char *term = getenv("TERM"); if (keepenv == 0) clearenv(); setenv("PATH", "/usr/sbin:/usr/bin:/sbin:/bin", 1); setenv("USER", user, 1); setenv("LOGNAME", user, 1); setenv("TERM", term, 1); } if (env != NULL) { size_t i, c; _free_ char **vars = NULL; _free_ char *tmp = strdup(env); if (tmp == NULL) fail_printf("OOM"); c = split_str(tmp, &vars, ","); for (i = 0; i < c; i++) { rc = putenv(strdup(vars[i])); if (rc != 0) sysf_printf("putenv()"); } } setenv("container", "pflask", 1); if (argc > optind) rc = execvpe(argv[optind], argv + optind, environ); else rc = execle("/bin/bash", "-bash", NULL, environ); if (rc < 0) sysf_printf("exec()"); } do_netif(pid); process_fd: if (detach == 1) serve_pty(master_fd); else process_pty(master_fd); if (pid == -1) return 0; kill(pid, SIGKILL); rc = waitid(P_PID, pid, &status, WEXITED); if (rc < 0) sysf_printf("waitid()"); switch (status.si_code) { case CLD_EXITED: if (status.si_status != 0) err_printf("Child failed with code '%d'", status.si_status); else ok_printf("Child exited"); break; case CLD_KILLED: err_printf("Child was terminated"); break; default: err_printf("Child failed"); break; } undo_cgroup(cgroup, ppid); return status.si_status; }