/* * pseudo_client.c, pseudo client library code * * Copyright (c) 2008-2013 Wind River Systems, Inc. * * This program is free software; you can redistribute it and/or modify * it under the terms of the Lesser GNU General Public License version 2.1 as * published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * See the Lesser GNU General Public License for more details. * * You should have received a copy of the Lesser GNU General Public License * version 2.1 along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * */ #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef PSEUDO_XATTRDB #include #endif #include "pseudo.h" #include "pseudo_ipc.h" #include "pseudo_client.h" /* GNU extension */ #if PSEUDO_PORT_LINUX extern char *program_invocation_name; #else static char *program_invocation_name = "unknown"; #endif static char *base_path(int dirfd, const char *path, int leave_last); static int connect_fd = -1; static int server_pid = 0; int pseudo_prefix_dir_fd = -1; int pseudo_localstate_dir_fd = -1; int pseudo_pwd_fd = -1; int pseudo_pwd_lck_fd = -1; char *pseudo_pwd_lck_name = NULL; FILE *pseudo_pwd = NULL; int pseudo_grp_fd = -1; FILE *pseudo_grp = NULL; char *pseudo_cwd = NULL; size_t pseudo_cwd_len; char *pseudo_chroot = NULL; char *pseudo_passwd = NULL; size_t pseudo_chroot_len = 0; char *pseudo_cwd_rel = NULL; /* used for PSEUDO_DISABLED */ int pseudo_disabled = 0; int pseudo_allow_fsync = 0; static int pseudo_local_only = 0; static int pseudo_client_logging = 1; int pseudo_umask = 022; static char **fd_paths = NULL; static int nfds = 0; static const char **passwd_paths = NULL; static int npasswd_paths = 0; #ifdef PSEUDO_PROFILING int pseudo_profile_fd = -1; static int profile_interval = 1; static pseudo_profile_t profile_data; struct timeval *pseudo_wrapper_time = &profile_data.wrapper_time; #endif static int pseudo_inited = 0; static int sent_messages = 0; int pseudo_nosymlinkexp = 0; /* note: these are int, not uid_t/gid_t, so I can use 'em with scanf */ uid_t pseudo_ruid; uid_t pseudo_euid; uid_t pseudo_suid; uid_t pseudo_fuid; gid_t pseudo_rgid; gid_t pseudo_egid; gid_t pseudo_sgid; gid_t pseudo_fgid; int (*pseudo_real_fork)(void) = fork; int (*pseudo_real_execv)(const char *, char * const *) = execv; #define PSEUDO_ETC_FILE(filename, realname, flags) pseudo_etc_file(filename, realname, flags, passwd_paths, npasswd_paths) /* helper function to make a directory, just like mkdir -p. * Can't use system() because the child shell would end up trying * to do the same thing... */ static void mkdir_p(char *path) { size_t len = strlen(path); size_t i; for (i = 1; i < len; ++i) { /* try to create all the directories in path, ignoring * failures */ if (path[i] == '/') { path[i] = '\0'; (void) mkdir(path, 0755); path[i] = '/'; } } (void) mkdir(path, 0755); } /* Populating an array of unknown size is one of my least favorite * things. The idea here is to ensure that the logic flow is the same * both when counting expected items, and when populating them. */ static void build_passwd_paths(void) { int np = 0; int pass = 0; /* should never happen... */ if (passwd_paths) { free(passwd_paths); passwd_paths = 0; npasswd_paths = 0; } #define SHOW_PATH pseudo_debug(PDBGF_CHROOT | PDBGF_VERBOSE, "passwd_paths[%d]: '%s'\n", np, (passwd_paths[np])) #define ADD_PATH(p) do { if (passwd_paths) { passwd_paths[np] = (p); SHOW_PATH; } ++np; } while(0) #define NUL_BYTE(p) do { if (passwd_paths) { *(p)++ = '\0'; } else { ++(p); } } while(0) do { if (pseudo_chroot) { ADD_PATH(pseudo_chroot); } if (pseudo_passwd) { char *s = pseudo_passwd; while (s) { char *t = strchr(s, ':'); if (t) { NUL_BYTE(t); } ADD_PATH(s); s = t; } } if (PSEUDO_PASSWD_FALLBACK) { ADD_PATH(PSEUDO_PASSWD_FALLBACK); } /* allocation and/or return */ if (passwd_paths) { if (np != npasswd_paths) { pseudo_diag("internal error: path allocation was inconsistent.\n"); } else { /* yes, we allocated one extra for a trailing * null pointer. */ passwd_paths[np] = NULL; } return; } else { passwd_paths = malloc((np + 1) * sizeof(*passwd_paths)); npasswd_paths = np; if (!passwd_paths) { pseudo_diag("couldn't allocate storage for password paths.\n"); exit(1); } np = 0; } } while (++pass < 2); /* in theory the second pass already returned, but. */ pseudo_diag("should totally not have gotten here.\n"); return; } #ifdef PSEUDO_XATTRDB /* We really want to avoid calling the wrappers for these inside the * implementation. pseudo_wrappers will reinitialize these after it's * gotten the real_* found. */ ssize_t (*pseudo_real_lgetxattr)(const char *, const char *, void *, size_t) = lgetxattr; ssize_t (*pseudo_real_fgetxattr)(int, const char *, void *, size_t) = fgetxattr; int (*pseudo_real_lsetxattr)(const char *, const char *, const void *, size_t, int) = lsetxattr; int (*pseudo_real_fsetxattr)(int, const char *, const void *, size_t, int) = fsetxattr; /* Executive summary: We use an extended attribute, * user.pseudo_data, to store exactly the data we would otherwise * have stored in the database. Which is to say, uid, gid, mode, rdev. * * If we don't find a value, save an empty one with a lower version * number to indicate that we don't have data to reduce round trips. */ typedef struct { int version; uid_t uid; gid_t gid; mode_t mode; dev_t rdev; } pseudo_db_data_t; static pseudo_msg_t xattrdb_data; pseudo_msg_t * pseudo_xattrdb_save(int fd, const char *path, const struct stat64 *buf) { int rc = -1; if (!path && fd < 0) return NULL; if (!buf) return NULL; pseudo_db_data_t pseudo_db_data = { .version = 1, .uid = buf->st_uid, .gid = buf->st_gid, .mode = buf->st_mode, .rdev = buf->st_rdev }; if (path) { rc = pseudo_real_lsetxattr(path, "user.pseudo_data", &pseudo_db_data, sizeof(pseudo_db_data), 0); } else if (fd >= 0) { rc = pseudo_real_fsetxattr(fd, "user.pseudo_data", &pseudo_db_data, sizeof(pseudo_db_data), 0); } pseudo_debug(PDBGF_XATTRDB, "tried to save data for %s/%d: uid %d, mode %o, rc %d.\n", path ? path : "", fd, (int) pseudo_db_data.uid, (int) pseudo_db_data.mode, rc); /* none of the other fields are checked on save, and the value * is currently only really used by mknod. */ if (rc == 0) { xattrdb_data.result = RESULT_SUCCEED; return &xattrdb_data; } return NULL; } pseudo_msg_t * pseudo_xattrdb_load(int fd, const char *path, const struct stat64 *buf) { int rc = -1, retryrc = -1; if (!path && fd < 0) return NULL; /* don't try to getxattr on a thing unless we think it is * likely to work. */ if (buf) { if (!S_ISDIR(buf->st_mode) && !S_ISREG(buf->st_mode)) { return NULL; } } pseudo_db_data_t pseudo_db_data; if (path) { rc = pseudo_real_lgetxattr(path, "user.pseudo_data", &pseudo_db_data, sizeof(pseudo_db_data)); if (rc == -1) { pseudo_db_data = (pseudo_db_data_t) { .version = 0 }; retryrc = pseudo_real_lsetxattr(path, "user.pseudo_data", &pseudo_db_data, sizeof(pseudo_db_data), 0); } } else if (fd >= 0) { rc = pseudo_real_fgetxattr(fd, "user.pseudo_data", &pseudo_db_data, sizeof(pseudo_db_data)); if (rc == -1) { pseudo_db_data = (pseudo_db_data_t) { .version = 0 }; retryrc = pseudo_real_fsetxattr(fd, "user.pseudo_data", &pseudo_db_data, sizeof(pseudo_db_data), 0); } } pseudo_debug(PDBGF_XATTRDB, "tried to load data for %s[%d]: rc %d, version %d.\n", path ? path : "", fd, rc, pseudo_db_data.version); if (rc == -1 && retryrc == 0) { /* there's no data, but there could have been; treat * this as an empty database result. */ pseudo_debug(PDBGF_XATTRDB, "wrote version 0 for %s[%d]\n", path ? path : "", fd); xattrdb_data.result = RESULT_FAIL; return &xattrdb_data; } else if (rc == -1) { /* we can't create an extended attribute, so we may have * used the database. */ return NULL; } /* Version 0 = just recording that we looked and found * nothing. * Version 1 = actually implemented. */ switch (pseudo_db_data.version) { case 0: default: xattrdb_data.result = RESULT_FAIL; break; case 1: xattrdb_data.uid = pseudo_db_data.uid; xattrdb_data.gid = pseudo_db_data.gid; xattrdb_data.mode = pseudo_db_data.mode; xattrdb_data.rdev = pseudo_db_data.rdev; xattrdb_data.result = RESULT_SUCCEED; break; } return &xattrdb_data; } #endif #ifdef PSEUDO_PROFILING static int pseudo_profile_pid = -2; static void pseudo_profile_start(void) { /* We use -1 as a starting value, and -2 as a value * indicating not to try to open it. */ int existing_data = 0; int pid = getpid(); if (pseudo_profile_pid > 0 && pseudo_profile_pid != pid) { /* looks like there's been a fork. We intentionally * abandon any existing stats, since the parent might * want to write them, and we want to combine with * any previous stats for this pid. */ close(pseudo_profile_fd); pseudo_profile_fd = -1; } if (pseudo_profile_fd == -1) { int fd = -2; char *profile_path = pseudo_get_value("PSEUDO_PROFILE_PATH"); pseudo_profile_pid = pid; if (profile_path) { fd = open(profile_path, O_RDWR | O_CREAT, 0600); if (fd >= 0) { fd = pseudo_fd(fd, MOVE_FD); } if (fd < 0) { fd = -2; } else { if (pid > 0) { pseudo_profile_t data; off_t rc; rc = lseek(fd, pid * sizeof(data), SEEK_SET); if (rc == (off_t) (pid * sizeof(data))) { rc = read(fd, &profile_data, sizeof(profile_data)); /* cumulative with other values in same file */ if (rc == sizeof(profile_data)) { pseudo_debug(PDBGF_PROFILE, "pid %d found existing profiling data.\n", pid); existing_data = 1; ++profile_data.processes; } else { pseudo_debug(PDBGF_PROFILE, "read failed for pid %d: %d, %d\n", pid, (int) rc, errno); } profile_interval = 1; } } } } pseudo_profile_fd = fd; } else { pseudo_debug(PDBGF_PROFILE, "_start called with existing fd? (pid %d)", (int) pseudo_profile_pid); existing_data = 1; ++profile_data.processes; profile_data.total_ops = 0; profile_data.messages = 0; profile_data.wrapper_time = (struct timeval) { .tv_sec = 0 }; profile_data.op_time = (struct timeval) { .tv_sec = 0 }; profile_data.ipc_time = (struct timeval) { .tv_sec = 0 }; } if (!existing_data) { pseudo_debug(PDBGF_PROFILE, "pid %d found no existing profiling data.\n", pid); profile_data = (pseudo_profile_t) { .processes = 1, .total_ops = 0, .messages = 0, .wrapper_time = (struct timeval) { .tv_sec = 0 }, .op_time = (struct timeval) { .tv_sec = 0 }, .ipc_time = (struct timeval) { .tv_sec = 0 }, }; } } static inline void fix_tv(struct timeval *tv) { if (tv->tv_usec > 1000000) { tv->tv_sec += tv->tv_usec / 1000000; tv->tv_usec %= 1000000; } else if (tv->tv_usec < 0) { /* C99 and later guarantee truncate-towards-zero. * We want -1 through -1000000 usec to produce * -1 seconds, etcetera. Note that sec is * negative, so yes, we want to add to tv_sec and * subtract from tv_usec. */ int sec = (tv->tv_usec - 999999) / 1000000; tv->tv_sec += sec; tv->tv_usec -= 1000000 * sec; } } static int profile_reported = 0; static void pseudo_profile_report(void) { if (pseudo_profile_fd < 0) { return; } fix_tv(&profile_data.wrapper_time); fix_tv(&profile_data.op_time); fix_tv(&profile_data.ipc_time); if (pseudo_profile_pid >= 0) { int rc1, rc2; rc1 = lseek(pseudo_profile_fd, pseudo_profile_pid * sizeof(profile_data), SEEK_SET); rc2 = write(pseudo_profile_fd, &profile_data, sizeof(profile_data)); if (!profile_reported) { pseudo_debug(PDBGF_PROFILE, "pid %d (%s) saving profiling info: %d, %d.\n", pseudo_profile_pid, program_invocation_name ? program_invocation_name : "unknown", rc1, rc2); profile_reported = 1; } } } #endif void pseudo_init_client(void) { char *env; pseudo_antimagic(); pseudo_new_pid(); if (connect_fd != -1) { close(connect_fd); connect_fd = -1; } #ifdef PSEUDO_PROFILING if (pseudo_profile_fd > -1) { close(pseudo_profile_fd); } pseudo_profile_fd = -1; #endif /* 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"); } /* ALLOW_FSYNC is here because some crazy hosts will otherwise * report incorrect values for st_size/st_blocks. I can sort of * understand st_blocks, but bogus values for st_size? Not cool, * dudes, not cool. */ env = getenv("PSEUDO_ALLOW_FSYNC"); if (!env) { env = pseudo_get_value("PSEUDO_ALLOW_FSYNC"); } else { pseudo_set_value("PSEUDO_ALLOW_FSYNC", env); } if (env) { pseudo_allow_fsync = 1; } else { pseudo_allow_fsync = 0; } /* in child processes, PSEUDO_UNLOAD may become set to * some truthy value, in which case we're being asked to * remove pseudo from the LD_PRELOAD. We need to make sure * this value gets loaded into the internal variables. * * If we've been told to unload, but are still available * we need to act as if unconditionally disabled. */ env = getenv("PSEUDO_UNLOAD"); if (env) { pseudo_set_value("PSEUDO_UNLOAD", env); pseudo_disabled = 1; } /* 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_umask = umask(022); umask(pseudo_umask); 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(PDBGF_CLIENT, "prefix directory '%s' doesn't exist, trying to create\n", pseudo_path); 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(PDBGF_CLIENT, "local state directory '%s' doesn't exist, trying to create\n", pseudo_path); 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 local state directory available for server/file interactions.\n"); exit(1); } if (pseudo_localstate_dir_fd == -1) { pseudo_diag("Can't open local state path '%s': %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) { /* note: this means that pseudo_passwd is a * string we're allowed to modify... */ pseudo_passwd = strdup(env); } free(env); build_passwd_paths(); pseudo_inited = 1; } if (!pseudo_disabled) { pseudo_client_getcwd(); #ifdef PSEUDO_PROFILING pseudo_profile_start(); #endif } pseudo_magic(); } static void pseudo_file_close(int *fd, FILE **fp) { if (!fp || !fd) { pseudo_diag("pseudo_file_close: needs valid pointers.\n"); return; } pseudo_antimagic(); if (*fp) { #if PSEUDO_PORT_DARWIN if (*fp == pseudo_host_etc_passwd_file) { endpwent(); } else if (*fp != pseudo_host_etc_group_file) { endgrent(); } else { fclose(*fp); } #else fclose(*fp); #endif *fd = -1; *fp = 0; } #if PSEUDO_PORT_DARWIN if (*fd == pseudo_host_etc_passwd_fd || *fd == pseudo_host_etc_group_fd) { *fd = -1; } #endif /* this should be impossible */ if (*fd >= 0) { close(*fd); *fd = -1; } pseudo_magic(); } static FILE * pseudo_file_open(char *name, int *fd, FILE **fp) { if (!fp || !fd || !name) { pseudo_diag("pseudo_file_open: needs valid pointers.\n"); return NULL; } pseudo_file_close(fd, fp); pseudo_antimagic(); *fd = PSEUDO_ETC_FILE(name, NULL, O_RDONLY); #if PSEUDO_PORT_DARWIN if (*fd == pseudo_host_etc_passwd_fd) { *fp = pseudo_host_etc_passwd_file; setpwent(); } else if (*fd == pseudo_host_etc_group_fd) { *fp = pseudo_host_etc_group_file; setgrent(); } #endif if (*fd >= 0) { *fd = pseudo_fd(*fd, MOVE_FD); *fp = fdopen(*fd, "r"); if (!*fp) { close(*fd); *fd = -1; } } pseudo_magic(); return *fp; } /* there is no spec I know of requiring us to defend this fd * against being closed by the user. */ int pseudo_pwd_lck_open(void) { pseudo_pwd_lck_close(); if (!pseudo_pwd_lck_name) { pseudo_pwd_lck_name = malloc(pseudo_path_max()); if (!pseudo_pwd_lck_name) { pseudo_diag("couldn't allocate space for passwd lockfile path.\n"); return -1; } } pseudo_antimagic(); pseudo_pwd_lck_fd = PSEUDO_ETC_FILE(".pwd.lock", pseudo_pwd_lck_name, O_RDWR | O_CREAT); pseudo_magic(); return pseudo_pwd_lck_fd; } int pseudo_pwd_lck_close(void) { if (pseudo_pwd_lck_fd != -1) { pseudo_antimagic(); close(pseudo_pwd_lck_fd); if (pseudo_pwd_lck_name) { unlink(pseudo_pwd_lck_name); free(pseudo_pwd_lck_name); pseudo_pwd_lck_name = 0; } pseudo_magic(); pseudo_pwd_lck_fd = -1; return 0; } else { return -1; } } FILE * pseudo_pwd_open(void) { return pseudo_file_open("passwd", &pseudo_pwd_fd, &pseudo_pwd); } void pseudo_pwd_close(void) { pseudo_file_close(&pseudo_pwd_fd, &pseudo_pwd); } FILE * pseudo_grp_open(void) { return pseudo_file_open("group", &pseudo_grp_fd, &pseudo_grp); } void pseudo_grp_close(void) { pseudo_file_close(&pseudo_grp_fd, &pseudo_grp); } void pseudo_client_touchuid(void) { static char uidbuf[256]; snprintf(uidbuf, 256, "%d,%d,%d,%d", pseudo_ruid, pseudo_euid, pseudo_suid, pseudo_fuid); pseudo_set_value("PSEUDO_UIDS", uidbuf); } void pseudo_client_touchgid(void) { static char gidbuf[256]; snprintf(gidbuf, 256, "%d,%d,%d,%d", pseudo_rgid, pseudo_egid, pseudo_sgid, pseudo_fgid); pseudo_set_value("PSEUDO_GIDS", gidbuf); } int pseudo_client_chroot(const char *path) { /* free old value */ free(pseudo_chroot); pseudo_debug(PDBGF_CLIENT | PDBGF_CHROOT, "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; } 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(PDBGF_CHROOT, "root_path [%s, %d]: '%s' from '%s'\n", func, line, rc ? rc : "", path ? path : ""); return rc; } 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(PDBGF_CLIENT | PDBGF_VERBOSE, "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(PDBGF_CLIENT | PDBGF_VERBOSE, "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(PDBGF_CLIENT | PDBGF_VERBOSE, "abscwd: <%s>\n", pseudo_cwd); if (pseudo_cwd_rel != pseudo_cwd) { pseudo_debug(PDBGF_CLIENT | PDBGF_VERBOSE, "relcwd: <%s>\n", pseudo_cwd_rel); } return 0; } else { pseudo_diag("Can't get CWD: %s\n", strerror(errno)); return -1; } } static char * fd_path(int fd) { if (fd >= 0 && fd < nfds) { return fd_paths[fd]; } if (fd == AT_FDCWD) { return pseudo_cwd; } return 0; } static void pseudo_client_path(int fd, const char *path) { if (fd < 0) return; if (fd >= nfds) { int i; pseudo_debug(PDBGF_CLIENT, "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(PDBGF_CLIENT, "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); } } static void pseudo_client_close(int fd) { if (fd < 0 || fd >= nfds) return; free(fd_paths[fd]); fd_paths[fd] = 0; } /* spawn server */ static int client_spawn_server(void) { int status; FILE *fp; char * pseudo_pidfile; if ((server_pid = pseudo_real_fork()) != 0) { if (server_pid == -1) { pseudo_diag("couldn't fork server: %s\n", strerror(errno)); return 1; } pseudo_evlog(PDBGF_CLIENT, "spawned new server, pid %d\n", server_pid); pseudo_debug(PDBGF_CLIENT | PDBGF_SERVER, "spawned server, pid %d\n", server_pid); /* wait for the child process to terminate, indicating server * is ready */ waitpid(server_pid, &status, 0); if (WIFEXITED(status)) { pseudo_evlog(PDBGF_CLIENT, "server exited status %d\n", WEXITSTATUS(status)); } if (WIFSIGNALED(status)) { pseudo_evlog(PDBGF_CLIENT, "server exited from signal %d\n", WTERMSIG(status)); } server_pid = -2; if (WIFEXITED(status) && WEXITSTATUS(status) == 0) { pseudo_evlog(PDBGF_CLIENT, "server reports successful startup, reading pid.\n"); pseudo_pidfile = pseudo_localstatedir_path(PSEUDO_PIDFILE); fp = fopen(pseudo_pidfile, "r"); if (fp) { if (fscanf(fp, "%d", &server_pid) != 1) { pseudo_debug(PDBGF_CLIENT, "Opened server PID file, but didn't get a pid.\n"); } fclose(fp); } else { pseudo_debug(PDBGF_CLIENT, "no pid file (%s): %s\n", pseudo_pidfile, strerror(errno)); } pseudo_debug(PDBGF_CLIENT, "read new pid file: %d\n", server_pid); free(pseudo_pidfile); /* at this point, we should have a new server_pid */ return 0; } else { pseudo_evlog(PDBGF_CLIENT, "server startup apparently unsuccessful. setting server pid to -1.\n"); server_pid = -1; return 1; } } 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_debug(PDBGF_CLIENT | PDBGF_SERVER | PDBGF_INVOKE, "calling execv on %s\n", argv[0]); /* don't try to log this exec, because it'll cause the process * that is supposed to be spawning the server to try to spawn * a server. Whoops. This is because the exec wrapper doesn't * respect antimagic, which I believe is intentional. */ pseudo_client_logging = 0; /* manual setup of environment, so we can call real-execv * instead of the wrapper. */ pseudo_set_value("PSEUDO_UNLOAD", "YES"); pseudo_setupenv(); pseudo_dropenv(); pseudo_real_execv(argv[0], argv); pseudo_diag("critical failure: exec of pseudo daemon failed: %s\n", strerror(errno)); exit(1); } } 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 : "", 0, tag ? tag : ""); free(tag); ping.client = getpid(); ping.result = 0; errno = 0; pseudo_debug(PDBGF_CLIENT | PDBGF_VERBOSE, "sending ping\n"); if (pseudo_msg_send(connect_fd, &ping, ping.pathlen, tagbuf)) { pseudo_debug(PDBGF_CLIENT | PDBGF_VERBOSE, "error pinging server: %s\n", strerror(errno)); return 1; } ack = pseudo_msg_receive(connect_fd); if (!ack) { pseudo_debug(PDBGF_CLIENT, "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(PDBGF_CLIENT, "invalid ping response from server: expected ack, got %d\n", ack->type); /* and that's not good, so... */ server_pid = 0; return 1; } else { /* The server tells us whether or not to log things. */ if (ack->result == RESULT_SUCCEED) { pseudo_client_logging = 1; } else { pseudo_client_logging = 0; } } pseudo_debug(PDBGF_CLIENT | PDBGF_VERBOSE, "ping ok\n"); return 0; } static void void_client_ping(void) { client_ping(); } int pseudo_fd(int fd, int how) { int newfd; if (fd < 0) return(-1); /* If already above PSEUDO_MIN_FD, no need to move */ if ((how == MOVE_FD) && (fd >= PSEUDO_MIN_FD)) { newfd = fd; } else { newfd = fcntl(fd, F_DUPFD, PSEUDO_MIN_FD); if (how == MOVE_FD) close(fd); } /* Set close on exec, even if we didn't move it. */ if ((newfd >= 0) && (fcntl(newfd, F_SETFD, FD_CLOEXEC) < 0)) pseudo_diag("Can't set close on exec flag: %s\n", strerror(errno)); return(newfd); } 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); pseudo_evlog(PDBGF_CLIENT, "creating socket %s.\n", sun.sun_path); if (connect_fd == -1) { char *e = strerror(errno); pseudo_diag("Can't create socket: %s (%s)\n", sun.sun_path, e); pseudo_evlog(PDBGF_CLIENT, "failed to create socket: %s\n", e); return 1; } pseudo_debug(PDBGF_CLIENT, "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) { char *e = strerror(errno); pseudo_debug(PDBGF_CLIENT, "Can't connect socket to pseudo.socket: (%s)\n", e); pseudo_evlog(PDBGF_CLIENT, "connect failed: %s\n", e); 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_evlog(PDBGF_CLIENT, "socket connect OK.\n"); pseudo_debug(PDBGF_CLIENT | PDBGF_VERBOSE, "connected socket.\n"); return 0; } 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; } if (client_connect() == 0) { pseudo_debug(PDBGF_CLIENT | PDBGF_VERBOSE, "connection started.\n"); if (client_ping() == 0) { pseudo_debug(PDBGF_CLIENT, "connection ping okay, server ready.\n"); return 0; } else { pseudo_debug(PDBGF_CLIENT, "connection up, but ping failed.\n"); /* and now we'll close the connection, and try to * kick the server. */ close(connect_fd); connect_fd = -1; } } pseudo_debug(PDBGF_CLIENT, "connection failed, checking server...\n"); /* This whole section is purely informational; we want to know * what might be up with the server, but we're going to try to * start one anyway. */ pseudo_pidfile = pseudo_localstatedir_path(PSEUDO_PIDFILE); fp = fopen(pseudo_pidfile, "r"); free(pseudo_pidfile); if (fp) { if (fscanf(fp, "%d", &server_pid) != 1) { pseudo_debug(PDBGF_CLIENT, "Opened server PID file, but didn't get a pid.\n"); } fclose(fp); } if (server_pid > 0) { if (kill(server_pid, 0) == -1) { pseudo_debug(PDBGF_CLIENT, "couldn't find server at pid %d: %s\n", server_pid, strerror(errno)); server_pid = 0; } else { pseudo_debug(PDBGF_CLIENT, "server pid should be %d, which exists, but connection failed.\n", server_pid); /* and we'll restart the server anyway. */ } } else { pseudo_debug(PDBGF_CLIENT, "no server pid available.\n"); } if (client_spawn_server()) { pseudo_evlog(PDBGF_CLIENT, "attempted respawn, failed.\n"); pseudo_debug(PDBGF_CLIENT, "failed to spawn server, waiting for retry.\n"); return 1; } else { pseudo_evlog(PDBGF_CLIENT, "restarted, new pid %d, will retry message\n", server_pid); pseudo_debug(PDBGF_CLIENT, "restarted, new pid %d, will retry message\n", server_pid); /* so we think a server has started. Now we'll retry the * connect/ping, because if they work we'll have set * connect_fd correctly, and will have succeeded. */ if (!client_connect()) { pseudo_debug(PDBGF_CLIENT, "connect okay to restarted server.\n"); pseudo_evlog(PDBGF_CLIENT, "connect okay to restarted server.\n"); if (!client_ping()) { pseudo_debug(PDBGF_CLIENT, "ping okay to restarted server.\n"); pseudo_evlog(PDBGF_CLIENT, "ping okay to restarted server.\n"); return 0; } else { pseudo_debug(PDBGF_CLIENT, "ping failed to restarted server.\n"); pseudo_evlog(PDBGF_CLIENT, "ping failed to restarted server.\n"); close(connect_fd); connect_fd = -1; return 1; } } else { pseudo_debug(PDBGF_CLIENT, "connect failed to restarted server.\n"); pseudo_evlog(PDBGF_CLIENT, "connect failed to restarted server.\n"); return 1; } } } #define PSEUDO_RETRIES 20 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; extern char *program_invocation_short_name; #if 0 if (!strcmp(program_invocation_short_name, "pseudo")) abort(); #endif if (!msg) return 0; /* Try to send a message. If sending fails, try to spawn a server, * and whether or not we succeed, wait a little bit and retry sending. * It's okay if we can't start a server sometimes, because another * client may have done it. */ pseudo_debug(PDBGF_CLIENT | PDBGF_VERBOSE, "sending a message: ino %llu\n", (unsigned long long) msg->ino); for (tries = 0; tries < PSEUDO_RETRIES; ++tries) { pseudo_evlog(PDBGF_CLIENT, "try %d, connect fd is %d\n", tries, connect_fd); rc = pseudo_msg_send(connect_fd, msg, len, path); if (rc != 0) { pseudo_evlog(PDBGF_CLIENT, "msg_send failed [%d].\n", rc); pseudo_debug(PDBGF_CLIENT | PDBGF_VERBOSE, "msg_send: %d%s\n", rc, rc == -1 ? " (sigpipe)" : " (short write)"); pseudo_debug(PDBGF_CLIENT, "trying to get server, try %d\n", tries); /* try to open server; if we fail, wait a bit before * retry. */ if (pseudo_client_setup()) { int ms = (getpid() % 5) + (3 * tries); struct timespec delay = { .tv_sec = 0, .tv_nsec = ms * 1000000 }; pseudo_evlog(PDBGF_CLIENT, "setup failed, delaying %d ms.\n", ms); nanosleep(&delay, NULL); } else { pseudo_evlog(PDBGF_CLIENT, "setup apparently successful, retrying message immediately.\n"); } continue; } /* note "continue" above; we only get here if rc was 0, * indicating a successful send. */ pseudo_debug(PDBGF_CLIENT | PDBGF_VERBOSE, "sent!\n"); if (msg->type != PSEUDO_MSG_FASTOP) { response = pseudo_msg_receive(connect_fd); if (!response) { pseudo_debug(PDBGF_CLIENT, "expected response did not occur; retrying\n"); } else { if (response->type != PSEUDO_MSG_ACK) { pseudo_debug(PDBGF_CLIENT, "got non-ack response %d\n", response->type); return 0; } else { pseudo_debug(PDBGF_CLIENT | PDBGF_VERBOSE, "got response type %d\n", response->type); return response; } } } else { return 0; } } pseudo_diag("pseudo: server connection persistently failed, aborting.\n"); pseudo_evlog_dump(); pseudo_diag("event log dumped, aborting.\n"); abort(); pseudo_diag("aborted.\n"); return 0; } int pseudo_client_shutdown(void) { pseudo_msg_t msg; pseudo_msg_t *ack; char *pseudo_path; pseudo_debug(PDBGF_INVOKE, "attempting to shut down server.\n"); 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(PDBGF_CLIENT, "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(PDBGF_CLIENT, "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_debug(PDBGF_INVOKE, "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(PDBGF_CLIENT | PDBGF_SERVER, "sending shutdown request\n"); if (pseudo_msg_send(connect_fd, &msg, 0, NULL)) { pseudo_debug(PDBGF_CLIENT | PDBGF_SERVER, "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; } 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 ""; if (path[0] != '/') { if (dirfd != -1 && dirfd != AT_FDCWD) { if (dirfd >= 0) { basepath = fd_path(dirfd); } if (basepath) { 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(PDBGF_PATH, "base_path: %s%s\n", basepath ? basepath : "", path ? path : ""); return newpath; } pseudo_msg_t * pseudo_client_op(pseudo_op_t op, int access, int fd, int dirfd, const char *path, const PSEUDO_STATBUF *buf, ...) { pseudo_msg_t *result = 0; pseudo_msg_t msg = { .type = PSEUDO_MSG_OP }; size_t pathlen = -1; int do_request = 0; char *path_extra_1 = 0; size_t path_extra_1len = 0; char *path_extra_2 = 0; size_t path_extra_2len = 0; static char *alloced_path = 0; static size_t alloced_len = 0; int strip_slash; #ifdef PSEUDO_PROFILING struct timeval tv1_op, tv2_op; gettimeofday(&tv1_op, NULL); ++profile_data.total_ops; #endif /* disable wrappers */ pseudo_antimagic(); /* note: I am not entirely sure this should happen even if no * messages have actually been sent. */ if (!sent_messages) { sent_messages = 1; atexit(void_client_ping); } /* if path isn't available, try to find one? */ if (!path && fd >= 0 && fd <= nfds) { path = fd_path(fd); if (!path) { pathlen = 0; } else { pathlen = strlen(path) + 1; } } #ifdef PSEUDO_XATTRDB if (buf) { struct stat64 bufcopy = *buf; int do_save = 0; /* maybe use xattr instead */ /* note: if we use xattr, logging won't work reliably * because the server won't get messages if these work. */ switch (op) { case OP_CHMOD: case OP_FCHMOD: case OP_CHOWN: case OP_FCHOWN: /* for these, we want to start with the existing db * values. */ bufcopy = *buf; result = pseudo_xattrdb_load(fd, path, buf); if (result && result->result == RESULT_SUCCEED) { pseudo_debug(PDBGF_XATTR, "merging existing values for xattr\n"); switch (op) { case OP_CHMOD: case OP_FCHMOD: bufcopy.st_uid = result->uid; bufcopy.st_gid = result->gid; break; case OP_CHOWN: case OP_FCHOWN: bufcopy.st_rdev = result->rdev; bufcopy.st_mode = result->mode; break; default: break; } } else { switch (op) { case OP_CHMOD: case OP_FCHMOD: bufcopy.st_uid = pseudo_fuid; bufcopy.st_gid = pseudo_fgid; break; default: break; } } result = NULL; do_save = 1; break; case OP_CREAT: case OP_MKDIR: case OP_MKNOD: bufcopy.st_uid = pseudo_fuid; bufcopy.st_gid = pseudo_fgid; do_save = 1; break; case OP_LINK: do_save = 1; break; case OP_FSTAT: case OP_STAT: result = pseudo_xattrdb_load(fd, path, buf); break; default: break; } if (do_save) { result = pseudo_xattrdb_save(fd, path, &bufcopy); } if (result) goto skip_path; } #endif if (op == OP_RENAME) { va_list ap; if (!path) { pseudo_diag("rename (%s) without new path.\n", path ? path : ""); pseudo_magic(); return 0; } va_start(ap, buf); path_extra_1 = va_arg(ap, char *); va_end(ap); /* last argument is the previous path of the file */ if (!path_extra_1) { pseudo_diag("rename (%s) without old path.\n", path ? path : ""); pseudo_magic(); return 0; } path_extra_1len = strlen(path_extra_1); pseudo_debug(PDBGF_PATH | PDBGF_FILE, "rename: %s -> %s\n", path_extra_1, path); } /* we treat the "create" and "replace" flags as logically * distinct operations, because they can fail when set can't. */ if (op == OP_SET_XATTR || op == OP_CREATE_XATTR || op == OP_REPLACE_XATTR) { va_list ap; va_start(ap, buf); path_extra_1 = va_arg(ap, char *); path_extra_1len = strlen(path_extra_1); path_extra_2 = va_arg(ap, char *); path_extra_2len = va_arg(ap, size_t); va_end(ap); pseudo_debug(PDBGF_XATTR, "setxattr, name '%s', value %d bytes\n", path_extra_1, (int) path_extra_2len); pseudo_debug_call(PDBGF_XATTR | PDBGF_VERBOSE, pseudo_dump_data, "xattr value", path_extra_2, path_extra_2len); } if (op == OP_GET_XATTR || op == OP_REMOVE_XATTR) { va_list ap; va_start(ap, buf); path_extra_1 = va_arg(ap, char *); path_extra_1len = strlen(path_extra_1); va_end(ap); pseudo_debug(PDBGF_XATTR, "%sxattr, name '%s'\n", op == OP_GET_XATTR ? "get" : "remove", path_extra_1); } if (path) { if (pathlen == (size_t) -1) { pathlen = strlen(path) + 1; } /* path fixup has to happen in the specific functions, * because they may have to make calls which have to be * fixed up for chroot stuff already. * ... but wait! / in chroot should not have that * trailing /. * (no attempt is made to handle a rename of "/" occurring * in a chroot...) */ strip_slash = (pathlen > 2 && (path[pathlen - 2]) == '/'); } else { path = ""; pathlen = 0; strip_slash = 0; } /* f*xattr operations can result in needing to send a path * value even though we don't have one available. We use an * empty path for that. */ if (path_extra_1) { size_t full_len = path_extra_1len + 1 + pathlen; size_t partial_len = pathlen - 1 - strip_slash; if (path_extra_2) { full_len += path_extra_2len + 1; } if (full_len > alloced_len) { free(alloced_path); alloced_path = malloc(full_len); alloced_len = full_len; if (!alloced_path) { pseudo_diag("Can't allocate space for paths for a rename operation. Sorry.\n"); alloced_len = 0; pseudo_magic(); return 0; } } memcpy(alloced_path, path, partial_len); alloced_path[partial_len] = '\0'; memcpy(alloced_path + partial_len + 1, path_extra_1, path_extra_1len); alloced_path[partial_len + path_extra_1len + 1] = '\0'; if (path_extra_2) { memcpy(alloced_path + partial_len + path_extra_1len + 2, path_extra_2, path_extra_2len); } alloced_path[full_len - 1] = '\0'; path = alloced_path; pathlen = full_len; pseudo_debug_call(PDBGF_IPC | PDBGF_VERBOSE, pseudo_dump_data, "combined path buffer", path, pathlen); } else { if (strip_slash) { if (pathlen > alloced_len) { free(alloced_path); alloced_path = malloc(pathlen); alloced_len = pathlen; if (!alloced_path) { pseudo_diag("Can't allocate space for paths for a rename operation. Sorry.\n"); alloced_len = 0; pseudo_magic(); return 0; } } memcpy(alloced_path, path, pathlen); alloced_path[pathlen - 2] = '\0'; path = alloced_path; } } #ifdef PSEUDO_XATTRDB /* If we were able to store things in xattr, we can easily skip * most of the fancy path computations and such. */ skip_path: #endif if (buf) pseudo_msg_stat(&msg, buf); if (pseudo_util_debug_flags & PDBGF_OP) { pseudo_debug(PDBGF_OP, "%s%s", pseudo_op_name(op), (dirfd != -1 && dirfd != AT_FDCWD && op != OP_DUP) ? "at" : ""); if (path_extra_1) { pseudo_debug(PDBGF_OP, " %s ->", path_extra_1); } if (path) { pseudo_debug(PDBGF_OP, " %s", path); } /* for OP_RENAME in renameat, "fd" is also used for the * second dirfd. */ if (fd != -1 && op != OP_RENAME) { pseudo_debug(PDBGF_OP, " [fd %d]", fd); } if (buf) { pseudo_debug(PDBGF_OP, " (+buf)"); if (fd != -1) { pseudo_debug(PDBGF_OP, " [dev/ino: %d/%llu]", (int) buf->st_dev, (unsigned long long) buf->st_ino); } pseudo_debug(PDBGF_OP, " (0%o)", (int) buf->st_mode); } pseudo_debug(PDBGF_OP, ": "); } msg.type = PSEUDO_MSG_OP; msg.op = op; msg.fd = fd; msg.access = access; msg.result = RESULT_NONE; msg.client = getpid(); /* do stuff */ pseudo_debug(PDBGF_CLIENT | PDBGF_VERBOSE, "processing request [ino %llu]\n", (unsigned long long) msg.ino); switch (msg.op) { case OP_CHDIR: pseudo_client_getcwd(); do_request = 0; break; case OP_CHROOT: if (pseudo_client_chroot(path) == 0) { /* return a non-zero value to show non-failure */ result = &msg; } do_request = 0; break; case OP_OPEN: pseudo_client_path(fd, path); case OP_EXEC: /* fallthrough */ do_request = pseudo_client_logging; break; case OP_CLOSE: /* no request needed */ if (fd >= 0) { if (fd == connect_fd) { connect_fd = pseudo_fd(connect_fd, COPY_FD); if (connect_fd == -1) { pseudo_diag("tried to close connection, couldn't dup: %s\n", strerror(errno)); } } else if (fd == pseudo_util_debug_fd) { pseudo_util_debug_fd = pseudo_fd(fd, COPY_FD); } else if (fd == pseudo_prefix_dir_fd) { pseudo_prefix_dir_fd = pseudo_fd(fd, COPY_FD); } else if (fd == pseudo_localstate_dir_fd) { pseudo_localstate_dir_fd = pseudo_fd(fd, COPY_FD); } else if (fd == pseudo_pwd_fd) { pseudo_pwd_fd = pseudo_fd(fd, COPY_FD); /* since we have a FILE * on it, we close that... */ fclose(pseudo_pwd); /* and open a new one on the copy */ pseudo_pwd = fdopen(pseudo_pwd_fd, "r"); } else if (fd == pseudo_grp_fd) { pseudo_grp_fd = pseudo_fd(fd, COPY_FD); /* since we have a FILE * on it, we close that... */ fclose(pseudo_grp); /* and open a new one on the copy */ pseudo_grp = fdopen(pseudo_grp_fd, "r"); } } pseudo_client_close(fd); do_request = 0; break; case OP_DUP: /* just copy the path over */ pseudo_debug(PDBGF_CLIENT | PDBGF_VERBOSE, "dup: fd_path(%d) = %p [%s], dup to %d\n", fd, fd_path(fd), fd_path(fd) ? fd_path(fd) : "", dirfd); pseudo_client_path(dirfd, fd_path(fd)); break; /* operations for which we should use the magic uid/gid */ case OP_CHMOD: case OP_CREAT: case OP_FCHMOD: case OP_MKDIR: case OP_MKNOD: case OP_SYMLINK: msg.uid = pseudo_fuid; msg.gid = pseudo_fgid; pseudo_debug(PDBGF_OP, "fuid: %d ", pseudo_fuid); /* FALLTHROUGH */ /* chown/fchown uid/gid already calculated, and * a link or rename should not change a file's ownership. * (operations which can create should be CREAT or MKNOD * or MKDIR) */ case OP_CHOWN: case OP_FCHOWN: case OP_FSTAT: case OP_LINK: case OP_RENAME: case OP_STAT: case OP_UNLINK: case OP_DID_UNLINK: case OP_CANCEL_UNLINK: case OP_MAY_UNLINK: case OP_GET_XATTR: case OP_LIST_XATTR: case OP_SET_XATTR: case OP_REMOVE_XATTR: do_request = 1; break; default: pseudo_diag("error: unknown or unimplemented operator %d (%s)", op, pseudo_op_name(op)); break; } /* result can only be set when PSEUDO_XATTRDB resulted in a * successful store to or read from the local database. */ if (do_request && !result) { #ifdef PSEUDO_PROFILING struct timeval tv1_ipc, tv2_ipc; #endif if (!pseudo_op_wait(msg.op)) msg.type = PSEUDO_MSG_FASTOP; pseudo_debug(PDBGF_CLIENT | PDBGF_VERBOSE, "sending request [ino %llu]\n", (unsigned long long) msg.ino); #ifdef PSEUDO_PROFILING gettimeofday(&tv1_ipc, NULL); #endif if (pseudo_local_only) { /* disable server */ result = NULL; } else { result = pseudo_client_request(&msg, pathlen, path); } #ifdef PSEUDO_PROFILING gettimeofday(&tv2_ipc, NULL); ++profile_data.messages; profile_data.ipc_time.tv_sec += (tv2_ipc.tv_sec - tv1_ipc.tv_sec); profile_data.ipc_time.tv_usec += (tv2_ipc.tv_usec - tv1_ipc.tv_usec); #endif if (result) { pseudo_debug(PDBGF_OP, "(%d) %s", getpid(), pseudo_res_name(result->result)); if (op == OP_STAT || op == OP_FSTAT) { pseudo_debug(PDBGF_OP, " mode 0%o uid %d:%d", (int) result->mode, (int) result->uid, (int) result->gid); } else if (op == OP_CHMOD || op == OP_FCHMOD) { pseudo_debug(PDBGF_OP, " mode 0%o", (int) result->mode); } else if (op == OP_CHOWN || op == OP_FCHOWN) { pseudo_debug(PDBGF_OP, " uid %d:%d", (int) result->uid, (int) result->gid); } } else if (msg.type != PSEUDO_MSG_FASTOP) { pseudo_debug(PDBGF_OP, "(%d) no answer", getpid()); } } else if (!result) { pseudo_debug(PDBGF_OP, "(%d) (no request)", getpid()); } #ifdef PSEUDO_XATTRDB else { pseudo_debug(PDBGF_OP, "(%d) (handled through xattrdb: %s)", getpid(), pseudo_res_name(result->result)); } #endif pseudo_debug(PDBGF_OP, "\n"); #ifdef PSEUDO_PROFILING gettimeofday(&tv2_op, NULL); profile_data.op_time.tv_sec += (tv2_op.tv_sec - tv1_op.tv_sec); profile_data.op_time.tv_usec += (tv2_op.tv_usec - tv1_op.tv_usec); if (profile_data.total_ops % profile_interval == 0) { pseudo_profile_report(); if (profile_interval < 100) { profile_interval = profile_interval * 10; } } #endif /* reenable wrappers */ pseudo_magic(); return result; } /* stuff for handling paths and execs */ static char *previous_path; static char *previous_path_segs; static char **path_segs; static size_t *path_lens; /* Makes an array of strings which are the path components * of previous_path. Does this by duping the path, then replacing * colons with null terminators, and storing the addresses of the * starts of the substrings. */ static void populate_path_segs(void) { size_t len = 0; char *s; int c = 0; free(path_segs); free(previous_path_segs); free(path_lens); path_segs = NULL; path_lens = NULL; previous_path_segs = NULL; if (!previous_path) return; for (s = previous_path; *s; ++s) { if (*s == ':') ++c; } path_segs = malloc((c+2) * sizeof(*path_segs)); if (!path_segs) { pseudo_diag("warning: failed to allocate space for %d path segments.\n", c); return; } path_lens = malloc((c + 2) * sizeof(*path_lens)); if (!path_lens) { pseudo_diag("warning: failed to allocate space for %d path lengths.\n", c); free(path_segs); path_segs = 0; return; } previous_path_segs = strdup(previous_path); if (!previous_path_segs) { pseudo_diag("warning: failed to allocate space for path copy.\n"); free(path_segs); path_segs = NULL; free(path_lens); path_lens = NULL; return; } c = 0; path_segs[c++] = previous_path; len = 0; for (s = previous_path; *s; ++s) { if (*s == ':') { *s = '\0'; path_lens[c - 1] = len; len = 0; path_segs[c++] = s + 1; } else { ++len; } } path_lens[c - 1] = len; path_segs[c] = NULL; path_lens[c] = 0; } const char * pseudo_exec_path(const char *filename, int search_path) { char *path = getenv("PATH"); char *candidate; int i; struct stat buf; if (!filename) return NULL; pseudo_antimagic(); if (!path) { free(path_segs); free(previous_path); path_segs = 0; previous_path = 0; } else if (!previous_path || strcmp(previous_path, path)) { free(previous_path); previous_path = strdup(path); populate_path_segs(); } /* absolute paths just get canonicalized. */ if (*filename == '/') { candidate = pseudo_fix_path(NULL, filename, 0, 0, NULL, 0); pseudo_magic(); return candidate; } if (!search_path || !path_segs) { candidate = pseudo_fix_path(pseudo_cwd, filename, 0, pseudo_cwd_len, NULL, 0); /* executable or not, it's the only thing we can try */ pseudo_magic(); return candidate; } for (i = 0; path_segs[i]; ++i) { path = path_segs[i]; pseudo_debug(PDBGF_CLIENT, "exec_path: checking %s for %s\n", path, filename); if (!*path || (*path == '.' && path_lens[i] == 1)) { /* empty path or . is cwd */ candidate = pseudo_fix_path(pseudo_cwd, filename, 0, pseudo_cwd_len, NULL, 0); pseudo_debug(PDBGF_CLIENT, "exec_path: in cwd, got %s\n", candidate); } else if (*path == '/') { candidate = pseudo_fix_path(path, filename, 0, path_lens[i], NULL, 0); pseudo_debug(PDBGF_CLIENT, "exec_path: got %s\n", candidate); } else { /* oh you jerk, making me do extra work */ size_t len; char *dir = pseudo_fix_path(pseudo_cwd, path, 0, pseudo_cwd_len, &len, 0); if (dir) { candidate = pseudo_fix_path(dir, filename, 0, len, NULL, 0); pseudo_debug(PDBGF_CLIENT, "exec_path: got %s for non-absolute path\n", candidate); } else { pseudo_diag("couldn't allocate intermediate path.\n"); candidate = NULL; } } if (candidate && !stat(candidate, &buf) && !S_ISDIR(buf.st_mode) && (buf.st_mode & 0111)) { pseudo_debug(PDBGF_CLIENT | PDBGF_VERBOSE, "exec_path: %s => %s\n", filename, candidate); pseudo_magic(); return candidate; } } /* blind guess being as good as anything */ pseudo_magic(); return filename; }