static void env_clean_except_real(const char *const preserve_envs[]) { ARRAY_TYPE(const_string) copy; const char *value, *const *envp; unsigned int i; t_array_init(©, 16); for (i = 0; preserve_envs[i] != NULL; i++) { const char *key = preserve_envs[i]; value = getenv(key); if (value != NULL) { value = t_strconcat(key, "=", value, NULL); array_append(©, &value, 1); } } /* Note that if the original environment was set with env_put(), the environment strings will be invalid after env_clean(). That's why we t_strconcat() them above. */ env_clean(); array_foreach(©, envp) env_put(*envp); }
static void exec_child(struct master_service_connection *conn, const char *const *args) { unsigned int i, socket_count; if (dup2(conn->fd, STDIN_FILENO) < 0) i_fatal("dup2() failed: %m"); if (dup2(conn->fd, STDOUT_FILENO) < 0) i_fatal("dup2() failed: %m"); /* close all fds */ socket_count = master_service_get_socket_count(master_service); for (i = 0; i < socket_count; i++) { if (close(MASTER_LISTEN_FD_FIRST + i) < 0) i_error("close(listener) failed: %m"); } if (close(MASTER_STATUS_FD) < 0) i_error("close(status) failed: %m"); if (close(conn->fd) < 0) i_error("close(conn->fd) failed: %m"); for (; *args != NULL; args++) array_append(&exec_args, args, 1); array_append_zero(&exec_args); env_clean(); args = array_idx(&exec_args, 0); execvp_const(args[0], args); }
static void client_connected(struct master_service_connection *conn) { enum mail_storage_service_flags flags = MAIL_STORAGE_SERVICE_FLAG_NO_PLUGINS; string_t *instr, *keys; const char **args, *key, *value, *error, *version_line, *data_line; struct mail_storage_service_ctx *service_ctx; struct mail_storage_service_input input; struct mail_storage_service_user *user; char buf[1024]; unsigned int i, socket_count; int fd = -1; ssize_t ret; alarm(SCRIPT_LOGIN_READ_TIMEOUT_SECS); net_set_nonblock(conn->fd, FALSE); instr = t_str_new(1024); ret = fd_read(conn->fd, buf, sizeof(buf), &fd); while (ret > 0) { str_append_n(instr, buf, ret); if (buf[ret-1] == '\n' && strchr(str_c(instr), '\n')[1] != '\0') { str_truncate(instr, str_len(instr)-1); break; } ret = read(conn->fd, buf, sizeof(buf)); } version_line = str_c(instr); data_line = strchr(version_line, '\n'); if (data_line != NULL) version_line = t_strdup_until(version_line, data_line++); else version_line = NULL; if (ret > 0 || version_line != NULL) { if (version_line == NULL || !version_string_verify(version_line, "script-login", SCRIPT_LOGIN_PROTOCOL_VERSION_MAJOR)) { i_fatal("Client not compatible with this binary " "(connecting to wrong socket?)"); } } if (ret <= 0) { if (ret < 0) i_fatal("read() failed: %m"); else i_fatal("read() failed: disconnected"); } if (fd == -1) i_fatal("client fd not received"); alarm(0); /* put everything to environment */ env_clean(); keys = t_str_new(256); args = t_strsplit_tab(data_line); if (str_array_length(args) < 3) i_fatal("Missing input fields"); i = 0; memset(&input, 0, sizeof(input)); input.module = "mail"; /* need to get mail_uid, mail_gid */ input.service = "script-login"; (void)net_addr2ip(args[i++], &input.local_ip); (void)net_addr2ip(args[i++], &input.remote_ip); input.username = args[i++]; input.userdb_fields = args + i; env_put(t_strconcat("LOCAL_IP=", net_ip2addr(&input.local_ip), NULL)); env_put(t_strconcat("IP=", net_ip2addr(&input.remote_ip), NULL)); env_put(t_strconcat("USER="******"%s ", key); } } env_put(t_strconcat(ENV_USERDB_KEYS"=", str_c(keys), NULL)); master_service_init_log(master_service, t_strdup_printf("script-login(%s): ", input.username)); if (drop_to_userdb_privileges) { service_ctx = mail_storage_service_init(master_service, NULL, flags); if (mail_storage_service_lookup(service_ctx, &input, &user, &error) <= 0) i_fatal("%s", error); mail_storage_service_restrict_setenv(service_ctx, user); /* we can't exec anything in a chroot */ env_remove("RESTRICT_CHROOT"); restrict_access_by_env(getenv("HOME"), TRUE); } if (dup2(fd, STDIN_FILENO) < 0) i_fatal("dup2() failed: %m"); if (dup2(fd, STDOUT_FILENO) < 0) i_fatal("dup2() failed: %m"); if (close(fd) < 0) i_fatal("close() failed: %m"); if (conn->fd != SCRIPT_COMM_FD) { if (dup2(conn->fd, SCRIPT_COMM_FD) < 0) i_fatal("dup2() failed: %m"); if (close(conn->fd) < 0) i_fatal("close() failed: %m"); } /* close all listener sockets */ socket_count = master_service_get_socket_count(master_service); for (i = 0; i < socket_count; i++) { if (close(MASTER_LISTEN_FD_FIRST + i) < 0) i_error("close(listener) failed: %m"); } if (close(MASTER_STATUS_FD) < 0) i_error("close(status) failed: %m"); execvp_const(exec_args[0], exec_args); }
static void exec_child (const char *bin_path, const char *const *args, const char *const *envs, int in_fd, int out_fd, int *extra_fds, bool drop_stderr) { ARRAY_TYPE(const_string) exec_args; /* Setup stdin/stdout */ if ( in_fd < 0 ) { in_fd = open("/dev/null", O_RDONLY); if ( in_fd == -1 ) i_fatal("open(/dev/null) failed: %m"); } if ( out_fd < 0 ) { out_fd = open("/dev/null", O_WRONLY); if ( out_fd == -1 ) i_fatal("open(/dev/null) failed: %m"); } if ( dup2(in_fd, STDIN_FILENO) < 0 ) i_fatal("dup2(stdin) failed: %m"); if ( dup2(out_fd, STDOUT_FILENO) < 0 ) i_fatal("dup2(stdout) failed: %m"); if ( close(in_fd) < 0 ) i_error("close(in_fd) failed: %m"); if ( (out_fd != in_fd) && close(out_fd) < 0 ) i_error("close(out_fd) failed: %m"); /* Drop stderr if requested */ if ( drop_stderr ) { int err_fd = open("/dev/null", O_WRONLY); if ( err_fd == -1 ) i_fatal("open(/dev/null) failed: %m"); if ( dup2(err_fd, STDERR_FILENO) < 0 ) i_fatal("dup2(stderr) failed: %m"); if ( close(err_fd) < 0 ) i_error("close(err_fd) failed: %m"); } /* Setup extra fds */ if ( extra_fds != NULL ) { for (; *extra_fds != -1; extra_fds += 2 ) { if ( dup2(extra_fds[0], extra_fds[1]) < 0 ) i_fatal("dup2(extra_fd=%d) failed: %m", extra_fds[1]); if ( close(extra_fds[0]) < 0 ) i_error("close(extra_fd=%d) failed: %m", extra_fds[1]); } } /* Compose argv */ t_array_init(&exec_args, 16); array_append(&exec_args, &bin_path, 1); if ( args != NULL ) { for (; *args != NULL; args++) array_append(&exec_args, args, 1); } (void)array_append_space(&exec_args); /* Setup environment */ env_clean(); if ( envs != NULL ) { for (; *envs != NULL; envs++) env_put(*envs); } /* Execute */ args = array_idx(&exec_args, 0); execvp_const(args[0], args); }
int main(const int argc, char * const argv[]) /*@globals environ, errno@*/ /*@modifies environ, errno@*/ { struct passwd *pw; char **target_env = environ; static char *empty_env[] = { NULL }; environ = empty_env; (void) umask(~(mode_t)0); /* (no file perms, if signalled to dump core)*/ /* check that calling UID exists, is not root, and shell matches */ if (NULL != (pw = getpwuid(getuid())) && 0 != pw->pw_uid && 0 == memcmp(pw->pw_shell,CHROOTING_SHELL,sizeof(CHROOTING_SHELL)) ) { /* require commands of the form: "sh" "-c" "command args args args" */ if (3 == argc && 0 == memcmp(argv[1], "-c", 3)) { const char *chroot_dir = (const char *)pw->pw_dir; const char *home = "/"; struct stat st; if (0 == memcmp(pw->pw_dir, GROUP_CHROOT_DIR, sizeof(GROUP_CHROOT_DIR)-1) && getgroups(0, (gid_t *)NULL) > 1) { chroot_dir = GROUP_CHROOT_DIR; home = (const char *)pw->pw_dir+(sizeof(GROUP_CHROOT_DIR)-2); if (*home != '/' && *++home != '/') { home = "/"; } } if (!( 0 == stat(chroot_dir, &st) && pw->pw_uid != st.st_uid /* (not caller; typically root) */ && 0 == (st.st_mode & (S_IWGRP|S_IWOTH)))) { fatal(argc, pw, chroot_dir); } openlog("chressh", LOG_NDELAY|LOG_PID, LOG_AUTHPRIV); if (/*@-superuser@*/ 0 == chroot(chroot_dir) /*@=superuser@*/ && 0 == setuid(getuid()) && 0 != setuid(0) && 0 == chdir(home)) { char **target_argv; size_t len; errno = 0; /* (reset errno after expected setuid(0) failure) */ environ = target_env = env_clean(target_env, pw, home); target_argv = shell_parse(argv[2]); if (NULL == target_argv || NULL == target_argv[0]) { fatal(argc, pw, NULL); } (void) umask((mode_t)UMASK); if (0 == strcmp(target_argv[0], "scp")) { if (0 == set_limits(NPROC_SCP)) { (void) execve(CHROOTED_CMD_DIR "scp", target_argv, target_env); } } else if (0 == strcmp(target_argv[0], "rsync")) { if (0 == filter_args_rsync(target_argv) && 0 == set_limits(NPROC_RSYNC)) { (void) execve(CHROOTED_CMD_DIR "rsync", target_argv, target_env); } } else if (0 == strcmp(target_argv[0], "unison")) { if (0 == filter_args_unison(target_argv) && 0 == set_limits(NPROC_UNISON)) { (void) execve(CHROOTED_CMD_DIR "unison", target_argv, target_env); } } else { if ( 11 <= (len = strlen(target_argv[0])) && 0 == memcmp(target_argv[0]+len-11, "sftp-server", 11) && 0 == set_limits(NPROC_SFTP_SERVER)) { /*('chressh -c /usr/local/libexec/sftp-server')*/ /*(only tests for "sftp-server" suffix, which is fine)*/ /*(discard additional args to sftp-server, if present)*/ char sftp_server[] = "sftp-server"; char *target_argv_static[] = { sftp_server, NULL }; (void) execve(CHROOTED_CMD_DIR "sftp-server", target_argv_static, target_env); } } #if 0 /* which code do you think is clearer? above or below? */ switch ((len = strlen(target_argv[0]))) { case 3: if (0 == memcmp(target_argv[0], "scp", 3) && 0 == set_limits(NPROC_SCP)) { (void) execve(CHROOTED_CMD_DIR "scp", target_argv, target_env); } break; case 5: if (0 == memcmp(target_argv[0], "rsync", 5) && 0 == filter_args_rsync(target_argv) && 0 == set_limits(NPROC_RSYNC)) { (void) execve(CHROOTED_CMD_DIR "rsync", target_argv, target_env); } break; case 6: if (0 == memcmp(target_argv[0], "unison", 6) && 0 == filter_args_unison(target_argv) && 0 == set_limits(NPROC_UNISON)) { (void) execve(CHROOTED_CMD_DIR "unison", target_argv, target_env); } break; default: if ( 11 <= len && 0 == memcmp(target_argv[0]+len-11, "sftp-server", 11) && 0 == set_limits(NPROC_SFTP_SERVER)) { /*('chressh -c /usr/local/libexec/sftp-server')*/ /*(only tests for "sftp-server" suffix, which is fine)*/ /*(discard additional args to sftp-server, if present)*/ char sftp_server[] = "sftp-server"; char *target_argv_static[] = { sftp_server, NULL }; (void) execve(CHROOTED_CMD_DIR "sftp-server", target_argv_static, target_env); } break; } #endif } } #ifdef PASSWD_PROGRAM /* If login attempt (argc == 1), use PASSWD_PROGRAM as 'shell' * (*argv[0] == '-', too, for login shells on most (all?) systems) * (privileges intentionally dropped even if passwd_program setuid) */ else if (1 == argc && 0 == setuid(getuid()) && 0 != setuid(0)) { char passwd_program[] = PASSWD_PROGRAM; char *target_argv[] = { passwd_program, NULL }; errno = 0; /* (reset errno after expected setuid(0) failure) */ target_env = env_clean(target_env, pw, NULL); (void) execve(PASSWD_PROGRAM, target_argv, target_env); } #endif } fatal(argc, pw, NULL); return 0; /*(UNREACHED)*/ }