/* SPDX-License-Identifier: LGPL-2.1-or-later */ #include #include #include #include "alloc-util.h" #include "def.h" #include "errno.h" #include "fd-util.h" #include "fileio.h" #include "mkdir.h" #include "nspawn-setuid.h" #include "process-util.h" #include "rlimit-util.h" #include "signal-util.h" #include "string-util.h" #include "strv.h" #include "user-util.h" #include "util.h" static int spawn_getent(const char *database, const char *key, pid_t *rpid) { int pipe_fds[2], r; pid_t pid; assert(database); assert(key); assert(rpid); if (pipe2(pipe_fds, O_CLOEXEC) < 0) return log_error_errno(errno, "Failed to allocate pipe: %m"); r = safe_fork("(getent)", FORK_RESET_SIGNALS|FORK_DEATHSIG|FORK_LOG, &pid); if (r < 0) { safe_close_pair(pipe_fds); return r; } if (r == 0) { char *empty_env = NULL; safe_close(pipe_fds[0]); if (rearrange_stdio(-1, pipe_fds[1], -1) < 0) _exit(EXIT_FAILURE); (void) close_all_fds(NULL, 0); (void) rlimit_nofile_safe(); execle("/usr/bin/getent", "getent", database, key, NULL, &empty_env); execle("/bin/getent", "getent", database, key, NULL, &empty_env); _exit(EXIT_FAILURE); } pipe_fds[1] = safe_close(pipe_fds[1]); *rpid = pid; return pipe_fds[0]; } int change_uid_gid_raw( uid_t uid, gid_t gid, const gid_t *supplementary_gids, size_t n_supplementary_gids, bool chown_stdio) { if (!uid_is_valid(uid)) uid = 0; if (!gid_is_valid(gid)) gid = 0; if (chown_stdio) { (void) fchown(STDIN_FILENO, uid, gid); (void) fchown(STDOUT_FILENO, uid, gid); (void) fchown(STDERR_FILENO, uid, gid); } if (setgroups(n_supplementary_gids, supplementary_gids) < 0) return log_error_errno(errno, "Failed to set auxiliary groups: %m"); if (setresgid(gid, gid, gid) < 0) return log_error_errno(errno, "setresgid() failed: %m"); if (setresuid(uid, uid, uid) < 0) return log_error_errno(errno, "setresuid() failed: %m"); return 0; } int change_uid_gid(const char *user, bool chown_stdio, char **ret_home) { char *x, *u, *g, *h; _cleanup_free_ gid_t *gids = NULL; _cleanup_free_ char *home = NULL, *line = NULL; _cleanup_fclose_ FILE *f = NULL; _cleanup_close_ int fd = -1; unsigned n_gids = 0; uid_t uid; gid_t gid; pid_t pid; int r; assert(ret_home); if (!user || STR_IN_SET(user, "root", "0")) { /* Reset everything fully to 0, just in case */ r = reset_uid_gid(); if (r < 0) return log_error_errno(r, "Failed to become root: %m"); *ret_home = NULL; return 0; } /* First, get user credentials */ fd = spawn_getent("passwd", user, &pid); if (fd < 0) return fd; f = take_fdopen(&fd, "r"); if (!f) return log_oom(); r = read_line(f, LONG_LINE_MAX, &line); if (r == 0) return log_error_errno(SYNTHETIC_ERRNO(ESRCH), "Failed to resolve user %s.", user); if (r < 0) return log_error_errno(r, "Failed to read from getent: %m"); (void) wait_for_terminate_and_check("getent passwd", pid, WAIT_LOG); x = strchr(line, ':'); if (!x) return log_error_errno(SYNTHETIC_ERRNO(EIO), "/etc/passwd entry has invalid user field."); u = strchr(x+1, ':'); if (!u) return log_error_errno(SYNTHETIC_ERRNO(EIO), "/etc/passwd entry has invalid password field."); u++; g = strchr(u, ':'); if (!g) return log_error_errno(SYNTHETIC_ERRNO(EIO), "/etc/passwd entry has invalid UID field."); *g = 0; g++; x = strchr(g, ':'); if (!x) return log_error_errno(SYNTHETIC_ERRNO(EIO), "/etc/passwd entry has invalid GID field."); *x = 0; h = strchr(x+1, ':'); if (!h) return log_error_errno(SYNTHETIC_ERRNO(EIO), "/etc/passwd entry has invalid GECOS field."); h++; x = strchr(h, ':'); if (!x) return log_error_errno(SYNTHETIC_ERRNO(EIO), "/etc/passwd entry has invalid home directory field."); *x = 0; r = parse_uid(u, &uid); if (r < 0) return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to parse UID of user."); r = parse_gid(g, &gid); if (r < 0) return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to parse GID of user."); home = strdup(h); if (!home) return log_oom(); f = safe_fclose(f); line = mfree(line); /* Second, get group memberships */ fd = spawn_getent("initgroups", user, &pid); if (fd < 0) return fd; f = take_fdopen(&fd, "r"); if (!f) return log_oom(); r = read_line(f, LONG_LINE_MAX, &line); if (r == 0) return log_error_errno(SYNTHETIC_ERRNO(ESRCH), "Failed to resolve user %s.", user); if (r < 0) return log_error_errno(r, "Failed to read from getent: %m"); (void) wait_for_terminate_and_check("getent initgroups", pid, WAIT_LOG); /* Skip over the username and subsequent separator whitespace */ x = line; x += strcspn(x, WHITESPACE); x += strspn(x, WHITESPACE); for (const char *p = x;;) { _cleanup_free_ char *word = NULL; r = extract_first_word(&p, &word, NULL, 0); if (r < 0) return log_error_errno(r, "Failed to parse group data from getent: %m"); if (r == 0) break; if (!GREEDY_REALLOC(gids, n_gids+1)) return log_oom(); r = parse_gid(word, &gids[n_gids++]); if (r < 0) return log_error_errno(r, "Failed to parse group data from getent: %m"); } r = mkdir_parents(home, 0775); if (r < 0) return log_error_errno(r, "Failed to make home root directory: %m"); r = mkdir_safe(home, 0755, uid, gid, 0); if (r < 0 && !IN_SET(r, -EEXIST, -ENOTDIR)) return log_error_errno(r, "Failed to make home directory: %m"); r = change_uid_gid_raw(uid, gid, gids, n_gids, chown_stdio); if (r < 0) return r; if (ret_home) *ret_home = TAKE_PTR(home); return 0; }