int execvp(const char *file, char *const *argv) { sigset_t saved; int rc = -1; if (!pseudo_check_wrappers() || !real_execvp) { /* rc was initialized to the "failure" value */ pseudo_enosys("execvp"); return rc; } pseudo_debug(4, "called: execvp\n"); pseudo_sigblock(&saved); if (pseudo_getlock()) { errno = EBUSY; sigprocmask(SIG_SETMASK, &saved, NULL); return -1; } int save_errno; /* exec*() use this to restore the sig mask */ pseudo_saved_sigmask = saved; rc = wrap_execvp(file, argv); save_errno = errno; pseudo_droplock(); sigprocmask(SIG_SETMASK, &saved, NULL); pseudo_debug(4, "completed: execvp\n"); errno = save_errno; return rc; }
int fork(void) { sigset_t saved; int rc = -1; if (!pseudo_check_wrappers() || !real_fork) { /* rc was initialized to the "failure" value */ pseudo_enosys("fork"); return rc; } pseudo_debug(4, "called: fork\n"); pseudo_sigblock(&saved); if (pseudo_getlock()) { errno = EBUSY; sigprocmask(SIG_SETMASK, &saved, NULL); return -1; } int save_errno; rc = wrap_fork(); save_errno = errno; pseudo_droplock(); sigprocmask(SIG_SETMASK, &saved, NULL); pseudo_debug(4, "completed: fork\n"); errno = save_errno; return rc; }
static void pseudo_client_path(int fd, const char *path) { if (fd < 0) return; if (fd >= nfds) { int i; pseudo_debug(2, "expanding fds from %d to %d\n", nfds, fd + 1); fd_paths = realloc(fd_paths, (fd + 1) * sizeof(char *)); for (i = nfds; i < fd + 1; ++i) fd_paths[i] = 0; nfds = fd + 1; } else { if (fd_paths[fd]) { pseudo_debug(2, "reopening fd %d [%s] -- didn't see close\n", fd, fd_paths[fd]); } /* yes, it is safe to free null pointers. yay for C89 */ free(fd_paths[fd]); fd_paths[fd] = 0; } if (path) { fd_paths[fd] = strdup(path); } }
int pseudo_client_getcwd(void) { char *cwd; cwd = malloc(pseudo_path_max()); if (!cwd) { pseudo_diag("Can't allocate CWD buffer!\n"); return -1; } pseudo_debug(3, "getcwd: trying to find cwd.\n"); if (getcwd(cwd, pseudo_path_max())) { /* cwd now holds a canonical path to current directory */ free(pseudo_cwd); pseudo_cwd = cwd; pseudo_cwd_len = strlen(pseudo_cwd); pseudo_debug(3, "getcwd okay: [%s] %d bytes\n", pseudo_cwd, (int) pseudo_cwd_len); if (pseudo_chroot_len && pseudo_cwd_len >= pseudo_chroot_len && !memcmp(pseudo_cwd, pseudo_chroot, pseudo_chroot_len) && (pseudo_cwd[pseudo_chroot_len] == '\0' || pseudo_cwd[pseudo_chroot_len] == '/')) { pseudo_cwd_rel = pseudo_cwd + pseudo_chroot_len; } else { pseudo_cwd_rel = pseudo_cwd; } pseudo_debug(4, "abscwd: <%s>\n", pseudo_cwd); if (pseudo_cwd_rel != pseudo_cwd) { pseudo_debug(4, "relcwd: <%s>\n", pseudo_cwd_rel); } return 0; } else { pseudo_diag("Can't get CWD: %s\n", strerror(errno)); return -1; } }
static int client_connect(void) { /* we have a server pid, is it responsive? */ struct sockaddr_un sun = { .sun_family = AF_UNIX, .sun_path = PSEUDO_SOCKET }; int cwd_fd; #if PSEUDO_PORT_DARWIN sun.sun_len = strlen(PSEUDO_SOCKET) + 1; #endif connect_fd = socket(PF_UNIX, SOCK_STREAM, 0); connect_fd = pseudo_fd(connect_fd, MOVE_FD); if (connect_fd == -1) { pseudo_diag("can't create socket: %s (%s)\n", sun.sun_path, strerror(errno)); return 1; } pseudo_debug(3, "connecting socket...\n"); cwd_fd = open(".", O_RDONLY); if (cwd_fd == -1) { pseudo_diag("Couldn't stash directory before opening socket: %s", strerror(errno)); close(connect_fd); connect_fd = -1; return 1; } if (fchdir(pseudo_localstate_dir_fd) == -1) { pseudo_diag("Couldn't chdir to server directory [%d]: %s\n", pseudo_localstate_dir_fd, strerror(errno)); close(connect_fd); close(cwd_fd); connect_fd = -1; return 1; } if (connect(connect_fd, (struct sockaddr *) &sun, sizeof(sun)) == -1) { pseudo_debug(3, "can't connect socket to pseudo.socket: (%s)\n", strerror(errno)); close(connect_fd); if (fchdir(cwd_fd) == -1) { pseudo_diag("return to previous directory failed: %s\n", strerror(errno)); } close(cwd_fd); connect_fd = -1; return 1; } if (fchdir(cwd_fd) == -1) { pseudo_diag("return to previous directory failed: %s\n", strerror(errno)); } close(cwd_fd); pseudo_debug(4, "connected socket.\n"); return 0; }
int clone(int (*fn)(void *), void *child_stack, int flags, void *arg, ...) { sigset_t saved; va_list ap; pid_t *pid; struct user_desc *tls; pid_t *ctid; int rc = -1; if (!pseudo_check_wrappers() || !real_clone) { /* rc was initialized to the "failure" value */ pseudo_enosys("clone"); return rc; } va_start(ap, arg); pid = va_arg(ap, pid_t *); tls = va_arg(ap, struct user_desc *); ctid = va_arg(ap, pid_t *); va_end(ap); pseudo_debug(PDBGF_WRAPPER, "called: clone\n"); pseudo_sigblock(&saved); if (pseudo_getlock()) { errno = EBUSY; sigprocmask(SIG_SETMASK, &saved, NULL); return -1; } int save_errno; int save_disabled = pseudo_disabled; #include "guts/clone.c" if (save_disabled != pseudo_disabled) { if (pseudo_disabled) { pseudo_disabled = 0; pseudo_magic(); } else { pseudo_disabled = 1; pseudo_antimagic(); } } save_errno = errno; pseudo_droplock(); sigprocmask(SIG_SETMASK, &saved, NULL); pseudo_debug(PDBGF_WRAPPER, "completed: clone\n"); errno = save_errno; return rc; }
int pseudo_client_chroot(const char *path) { /* free old value */ free(pseudo_chroot); pseudo_debug(2, "client chroot: %s\n", path); if (!strcmp(path, "/")) { pseudo_chroot_len = 0; pseudo_chroot = 0; pseudo_set_value("PSEUDO_CHROOT", NULL); return 0; } /* allocate new value */ pseudo_chroot_len = strlen(path); pseudo_chroot = malloc(pseudo_chroot_len + 1); if (!pseudo_chroot) { pseudo_diag("Couldn't allocate chroot directory buffer.\n"); pseudo_chroot_len = 0; errno = ENOMEM; return -1; } memcpy(pseudo_chroot, path, pseudo_chroot_len + 1); pseudo_set_value("PSEUDO_CHROOT", pseudo_chroot); return 0; }
int execle(const char *file, const char *arg, ...) { sigset_t saved; va_list ap; char **argv; char **envp; int rc = -1; if (!pseudo_check_wrappers()) { /* rc was initialized to the "failure" value */ pseudo_enosys("execle"); return rc; } va_start(ap, arg); argv = execl_to_v(ap, arg, (char *const **)&envp); va_end(ap); if (!argv) { errno = ENOMEM; return -1; } pseudo_debug(4, "called: execle\n"); pseudo_sigblock(&saved); if (pseudo_getlock()) { errno = EBUSY; sigprocmask(SIG_SETMASK, &saved, NULL); return -1; } int save_errno; /* exec*() use this to restore the sig mask */ pseudo_saved_sigmask = saved; rc = wrap_execve(file, argv, envp); save_errno = errno; pseudo_droplock(); sigprocmask(SIG_SETMASK, &saved, NULL); pseudo_debug(4, "completed: execle\n"); errno = save_errno; free(argv); return rc; }
static pseudo_msg_t * pseudo_client_request(pseudo_msg_t *msg, size_t len, const char *path) { pseudo_msg_t *response = 0; int tries = 0; int rc; if (!msg) return 0; do { do { pseudo_debug(4, "sending a message: ino %llu\n", (unsigned long long) msg->ino); if (connect_fd < 0) { pseudo_debug(2, "trying to get server\n"); if (pseudo_client_setup()) { return 0; } } rc = pseudo_msg_send(connect_fd, msg, len, path); if (rc != 0) { pseudo_debug(2, "msg_send: %d%s\n", rc, rc == -1 ? " (sigpipe)" : " (short write)"); pseudo_client_setup(); ++tries; if (tries > 3) { pseudo_debug(1, "can't get server going again.\n"); return 0; } } } while (rc != 0); pseudo_debug(5, "sent!\n"); response = pseudo_msg_receive(connect_fd); if (!response) { ++tries; if (tries > 3) { pseudo_debug(1, "can't get responses.\n"); return 0; } } } while (response == 0); if (response->type != PSEUDO_MSG_ACK) { pseudo_debug(2, "got non-ack response %d\n", response->type); return 0; } else { pseudo_debug(4, "got response type %d\n", response->type); } return response; }
static int client_ping(void) { pseudo_msg_t ping; pseudo_msg_t *ack; char tagbuf[pseudo_path_max()]; char *tag = pseudo_get_value("PSEUDO_TAG"); memset(&ping, 0, sizeof(ping)); ping.type = PSEUDO_MSG_PING; ping.op = OP_NONE; ping.pathlen = snprintf(tagbuf, sizeof(tagbuf), "%s%c%s", program_invocation_name ? program_invocation_name : "<unknown>", 0, tag ? tag : ""); free(tag); ping.client = getpid(); ping.result = 0; errno = 0; pseudo_debug(4, "sending ping\n"); if (pseudo_msg_send(connect_fd, &ping, ping.pathlen, tagbuf)) { pseudo_debug(3, "error pinging server: %s\n", strerror(errno)); return 1; } ack = pseudo_msg_receive(connect_fd); if (!ack) { pseudo_debug(2, "no ping response from server: %s\n", strerror(errno)); /* and that's not good, so... */ server_pid = 0; return 1; } if (ack->type != PSEUDO_MSG_ACK) { pseudo_debug(1, "invalid ping response from server: expected ack, got %d\n", ack->type); /* and that's not good, so... */ server_pid = 0; return 1; } pseudo_debug(5, "ping ok\n"); return 0; }
static char * base_path(int dirfd, const char *path, int leave_last) { char *basepath = 0; size_t baselen = 0; size_t minlen = 0; char *newpath; if (!path) return NULL; if (!*path) return strdup(""); if (path[0] != '/') { if (dirfd != -1 && dirfd != AT_FDCWD) { if (dirfd >= 0) { basepath = fd_path(dirfd); baselen = strlen(basepath); } else { pseudo_diag("got *at() syscall for unknown directory, fd %d\n", dirfd); } } else { basepath = pseudo_cwd; baselen = pseudo_cwd_len; } if (!basepath) { pseudo_diag("unknown base path for fd %d, path %s\n", dirfd, path); return 0; } /* if there's a chroot path, and it's the start of basepath, * flag it for pseudo_fix_path */ if (pseudo_chroot_len && baselen >= pseudo_chroot_len && !memcmp(basepath, pseudo_chroot, pseudo_chroot_len) && (basepath[pseudo_chroot_len] == '\0' || basepath[pseudo_chroot_len] == '/')) { minlen = pseudo_chroot_len; } } else if (pseudo_chroot_len) { /* "absolute" is really relative to chroot path */ basepath = pseudo_chroot; baselen = pseudo_chroot_len; minlen = pseudo_chroot_len; } newpath = pseudo_fix_path(basepath, path, minlen, baselen, NULL, leave_last); pseudo_debug(4, "base_path: %s</>%s\n", basepath ? basepath : "<nil>", path ? path : "<nil>"); return newpath; }
char * pseudo_root_path(const char *func, int line, int dirfd, const char *path, int leave_last) { char *rc; pseudo_antimagic(); rc = base_path(dirfd, path, leave_last); pseudo_magic(); if (!rc) { pseudo_diag("couldn't allocate absolute path for '%s'.\n", path); } pseudo_debug(3, "root_path [%s, %d]: '%s' from '%s'\n", func, line, rc ? rc : "<nil>", path ? path : "<nil>"); return rc; }
static int pseudo_client_setup(void) { char * pseudo_pidfile; FILE *fp; server_pid = 0; /* avoid descriptor leak, I hope */ if (connect_fd >= 0) { close(connect_fd); connect_fd = -1; } pseudo_pidfile = pseudo_localstatedir_path(PSEUDO_PIDFILE); fp = fopen(pseudo_pidfile, "r"); if (fp) { if (fscanf(fp, "%d", &server_pid) != 1) { pseudo_debug(1, "Opened server PID file, but didn't get a pid.\n"); } fclose(fp); } if (server_pid) { if (kill(server_pid, 0) == -1) { pseudo_debug(1, "couldn't find server at pid %d: %s\n", server_pid, strerror(errno)); server_pid = 0; } } if (!server_pid) { if (client_spawn_server()) { return 1; } } if (!client_connect() && !client_ping()) { return 0; } pseudo_debug(2, "server seems to be gone, trying to restart\n"); if (client_spawn_server()) { pseudo_debug(1, "failed to spawn server, giving up.\n"); return 1; } else { pseudo_debug_verbose(); pseudo_debug(2, "restarted, new pid %d\n", server_pid); if (!client_connect() && !client_ping()) { pseudo_debug_terse(); return 0; } pseudo_debug_terse(); } pseudo_debug(1, "couldn't get a server, giving up.\n"); return 1; }
int pseudo_client_shutdown(void) { pseudo_msg_t msg; pseudo_msg_t *ack; char *pseudo_path; pseudo_path = pseudo_prefix_path(NULL); if (pseudo_prefix_dir_fd == -1) { if (pseudo_path) { pseudo_prefix_dir_fd = open(pseudo_path, O_RDONLY); /* directory is missing? */ if (pseudo_prefix_dir_fd == -1 && errno == ENOENT) { pseudo_debug(1, "prefix directory doesn't exist, trying to create\n"); mkdir_p(pseudo_path); pseudo_prefix_dir_fd = open(pseudo_path, O_RDONLY); } pseudo_prefix_dir_fd = pseudo_fd(pseudo_prefix_dir_fd, COPY_FD); free(pseudo_path); } else { pseudo_diag("No prefix available to to find server.\n"); exit(1); } if (pseudo_prefix_dir_fd == -1) { pseudo_diag("Can't open prefix path (%s) for server. (%s)\n", pseudo_prefix_path(NULL), strerror(errno)); exit(1); } } pseudo_path = pseudo_localstatedir_path(NULL); mkdir_p(pseudo_path); if (pseudo_localstate_dir_fd == -1) { if (pseudo_path) { pseudo_localstate_dir_fd = open(pseudo_path, O_RDONLY); /* directory is missing? */ if (pseudo_localstate_dir_fd == -1 && errno == ENOENT) { pseudo_debug(1, "local state dir doesn't exist, trying to create\n"); mkdir_p(pseudo_path); pseudo_localstate_dir_fd = open(pseudo_path, O_RDONLY); } pseudo_localstate_dir_fd = pseudo_fd(pseudo_localstate_dir_fd, COPY_FD); free(pseudo_path); } else { pseudo_diag("No prefix available to to find server.\n"); exit(1); } if (pseudo_localstate_dir_fd == -1) { pseudo_diag("Can't open local state path (%s) for server. (%s)\n", pseudo_localstatedir_path(NULL), strerror(errno)); exit(1); } } if (client_connect()) { pseudo_diag("Pseudo server seems to be already offline.\n"); return 0; } memset(&msg, 0, sizeof(pseudo_msg_t)); msg.type = PSEUDO_MSG_SHUTDOWN; msg.op = OP_NONE; msg.client = getpid(); pseudo_debug(2, "sending shutdown request\n"); if (pseudo_msg_send(connect_fd, &msg, 0, NULL)) { pseudo_debug(1, "error requesting shutdown: %s\n", strerror(errno)); return 1; } ack = pseudo_msg_receive(connect_fd); if (!ack) { pseudo_diag("server did not respond to shutdown query.\n"); return 1; } if (ack->type == PSEUDO_MSG_ACK) { return 0; } pseudo_diag("Server refused shutdown. Remaining client fds: %d\n", ack->fd); pseudo_diag("Client pids: %s\n", ack->path); pseudo_diag("Server will shut down after all clients exit.\n"); return 0; }
/* spawn server */ static int client_spawn_server(void) { int status; FILE *fp; char * pseudo_pidfile; if ((server_pid = fork()) != 0) { if (server_pid == -1) { pseudo_diag("couldn't fork server: %s\n", strerror(errno)); return 1; } pseudo_debug(4, "spawned server, pid %d\n", server_pid); /* wait for the child process to terminate, indicating server * is ready */ waitpid(server_pid, &status, 0); server_pid = -2; pseudo_pidfile = pseudo_localstatedir_path(PSEUDO_PIDFILE); fp = fopen(pseudo_pidfile, "r"); if (fp) { if (fscanf(fp, "%d", &server_pid) != 1) { pseudo_debug(1, "Opened server PID file, but didn't get a pid.\n"); } fclose(fp); } else { pseudo_debug(1, "no pid file (%s): %s\n", pseudo_pidfile, strerror(errno)); } pseudo_debug(2, "read new pid file: %d\n", server_pid); free(pseudo_pidfile); /* at this point, we should have a new server_pid */ return 0; } else { char *base_args[] = { NULL, NULL, NULL }; char **argv; char *option_string = pseudo_get_value("PSEUDO_OPTS"); int args; int fd; pseudo_new_pid(); base_args[0] = pseudo_bindir_path("pseudo"); base_args[1] = "-d"; if (option_string) { char *s; int arg; /* count arguments in PSEUDO_OPTS, starting at 2 * for pseudo/-d/NULL, plus one for the option string. * The number of additional arguments may be less * than the number of spaces, but can't be more. */ args = 4; for (s = option_string; *s; ++s) if (*s == ' ') ++args; argv = malloc(args * sizeof(char *)); argv[0] = base_args[0]; argv[1] = base_args[1]; arg = 2; while ((s = strsep(&option_string, " ")) != NULL) { if (*s) { argv[arg++] = strdup(s); } } argv[arg] = 0; } else { argv = base_args; } /* close any higher-numbered fds which might be open, * such as sockets. We don't have to worry about 0 and 1; * the server closes them already, and more importantly, * they can't have been opened or closed without us already * having spawned a server... The issue is just socket() * calls which could result in fds being left open, and those * can't overwrite fds 0-2 unless we closed them... * * No, really. It works. */ for (fd = 3; fd < 1024; ++fd) { if (fd != pseudo_util_debug_fd) close(fd); } /* and now, execute the server */ pseudo_set_value("PSEUDO_RELOADED", "YES"); pseudo_setupenv(); pseudo_dropenv(); /* drop PRELINK_LIBRARIES */ pseudo_debug(4, "calling execv on %s\n", argv[0]); execv(argv[0], argv); pseudo_diag("critical failure: exec of pseudo daemon failed: %s\n", strerror(errno)); exit(1); } }
void pseudo_init_client(void) { char *env; pseudo_antimagic(); pseudo_new_pid(); if (connect_fd != -1) { close(connect_fd); connect_fd = -1; } /* in child processes, PSEUDO_DISABLED may have become set to * some truthy value, in which case we'd disable pseudo, * or it may have gone away, in which case we'd enable * pseudo (and cause it to reinit the defaults). */ env = getenv("PSEUDO_DISABLED"); if (!env) { env = pseudo_get_value("PSEUDO_DISABLED"); } if (env) { int actually_disabled = 1; switch (*env) { case '0': case 'f': case 'F': case 'n': case 'N': actually_disabled = 0; break; case 's': case 'S': actually_disabled = 0; pseudo_local_only = 1; break; } if (actually_disabled) { if (!pseudo_disabled) { pseudo_antimagic(); pseudo_disabled = 1; } pseudo_set_value("PSEUDO_DISABLED", "1"); } else { if (pseudo_disabled) { pseudo_magic(); pseudo_disabled = 0; pseudo_inited = 0; /* Re-read the initial values! */ } pseudo_set_value("PSEUDO_DISABLED", "0"); } } else { pseudo_set_value("PSEUDO_DISABLED", "0"); } /* Setup global items needed for pseudo to function... */ if (!pseudo_inited) { /* Ensure that all of the values are reset */ server_pid = 0; pseudo_prefix_dir_fd = -1; pseudo_localstate_dir_fd = -1; pseudo_pwd_fd = -1; pseudo_pwd_lck_fd = -1; pseudo_pwd_lck_name = NULL; pseudo_pwd = NULL; pseudo_grp_fd = -1; pseudo_grp = NULL; pseudo_cwd = NULL; pseudo_cwd_len = 0; pseudo_chroot = NULL; pseudo_passwd = NULL; pseudo_chroot_len = 0; pseudo_cwd_rel = NULL; pseudo_nosymlinkexp = 0; } if (!pseudo_disabled && !pseudo_inited) { char *pseudo_path = 0; pseudo_path = pseudo_prefix_path(NULL); if (pseudo_prefix_dir_fd == -1) { if (pseudo_path) { pseudo_prefix_dir_fd = open(pseudo_path, O_RDONLY); /* directory is missing? */ if (pseudo_prefix_dir_fd == -1 && errno == ENOENT) { pseudo_debug(1, "prefix directory doesn't exist, trying to create\n"); mkdir_p(pseudo_path); pseudo_prefix_dir_fd = open(pseudo_path, O_RDONLY); } pseudo_prefix_dir_fd = pseudo_fd(pseudo_prefix_dir_fd, MOVE_FD); } else { pseudo_diag("No prefix available to to find server.\n"); exit(1); } if (pseudo_prefix_dir_fd == -1) { pseudo_diag("Can't open prefix path (%s) for server: %s\n", pseudo_path, strerror(errno)); exit(1); } } free(pseudo_path); pseudo_path = pseudo_localstatedir_path(NULL); if (pseudo_localstate_dir_fd == -1) { if (pseudo_path) { pseudo_localstate_dir_fd = open(pseudo_path, O_RDONLY); /* directory is missing? */ if (pseudo_localstate_dir_fd == -1 && errno == ENOENT) { pseudo_debug(1, "local state directory doesn't exist, trying to create\n"); mkdir_p(pseudo_path); pseudo_localstate_dir_fd = open(pseudo_path, O_RDONLY); } pseudo_localstate_dir_fd = pseudo_fd(pseudo_localstate_dir_fd, MOVE_FD); } else { pseudo_diag("No prefix available to to find server.\n"); exit(1); } if (pseudo_localstate_dir_fd == -1) { pseudo_diag("Can't open local state path (%s) for server: %s\n", pseudo_path, strerror(errno)); exit(1); } } free(pseudo_path); env = pseudo_get_value("PSEUDO_NOSYMLINKEXP"); if (env) { char *endptr; /* if the environment variable is not an empty string, * parse it; "0" means turn NOSYMLINKEXP off, "1" means * turn it on (disabling the feature). An empty string * or something we can't parse means to set the flag; this * is a safe default because if you didn't want the flag * set, you normally wouldn't set the environment variable * at all. */ if (*env) { pseudo_nosymlinkexp = strtol(env, &endptr, 10); if (*endptr) pseudo_nosymlinkexp = 1; } else { pseudo_nosymlinkexp = 1; } } else { pseudo_nosymlinkexp = 0; } free(env); env = pseudo_get_value("PSEUDO_UIDS"); if (env) sscanf(env, "%d,%d,%d,%d", &pseudo_ruid, &pseudo_euid, &pseudo_suid, &pseudo_fuid); free(env); env = pseudo_get_value("PSEUDO_GIDS"); if (env) sscanf(env, "%d,%d,%d,%d", &pseudo_rgid, &pseudo_egid, &pseudo_sgid, &pseudo_fuid); free(env); env = pseudo_get_value("PSEUDO_CHROOT"); if (env) { pseudo_chroot = strdup(env); if (pseudo_chroot) { pseudo_chroot_len = strlen(pseudo_chroot); } else { pseudo_diag("can't store chroot path (%s)\n", env); } } free(env); env = pseudo_get_value("PSEUDO_PASSWD"); if (env) { pseudo_passwd = strdup(env); } free(env); pseudo_inited = 1; } if (!pseudo_disabled) pseudo_client_getcwd(); pseudo_magic(); }