| /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ |
| |
| /*** |
| This file is part of systemd. |
| |
| Copyright 2015 Lennart Poettering |
| |
| systemd is free software; you can redistribute it and/or modify it |
| under the terms of the GNU Lesser General Public License as published by |
| the Free Software Foundation; either version 2.1 of the License, or |
| (at your option) any later version. |
| |
| systemd 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 GNU |
| Lesser General Public License for more details. |
| |
| You should have received a copy of the GNU Lesser General Public License |
| along with systemd; If not, see <http://www.gnu.org/licenses/>. |
| ***/ |
| |
| #include <sys/types.h> |
| #include <unistd.h> |
| #include <grp.h> |
| |
| #include "util.h" |
| #include "signal-util.h" |
| #include "mkdir.h" |
| #include "process-util.h" |
| |
| #include "nspawn-setuid.h" |
| |
| static int spawn_getent(const char *database, const char *key, pid_t *rpid) { |
| int pipe_fds[2]; |
| 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"); |
| |
| pid = fork(); |
| if (pid < 0) |
| return log_error_errno(errno, "Failed to fork getent child: %m"); |
| else if (pid == 0) { |
| int nullfd; |
| char *empty_env = NULL; |
| |
| if (dup3(pipe_fds[1], STDOUT_FILENO, 0) < 0) |
| _exit(EXIT_FAILURE); |
| |
| if (pipe_fds[0] > 2) |
| safe_close(pipe_fds[0]); |
| if (pipe_fds[1] > 2) |
| safe_close(pipe_fds[1]); |
| |
| nullfd = open("/dev/null", O_RDWR); |
| if (nullfd < 0) |
| _exit(EXIT_FAILURE); |
| |
| if (dup3(nullfd, STDIN_FILENO, 0) < 0) |
| _exit(EXIT_FAILURE); |
| |
| if (dup3(nullfd, STDERR_FILENO, 0) < 0) |
| _exit(EXIT_FAILURE); |
| |
| if (nullfd > 2) |
| safe_close(nullfd); |
| |
| (void) reset_all_signal_handlers(); |
| (void) reset_signal_mask(); |
| close_all_fds(NULL, 0); |
| |
| 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(const char *user, char **_home) { |
| char line[LINE_MAX], *x, *u, *g, *h; |
| const char *word, *state; |
| _cleanup_free_ uid_t *uids = NULL; |
| _cleanup_free_ char *home = NULL; |
| _cleanup_fclose_ FILE *f = NULL; |
| _cleanup_close_ int fd = -1; |
| unsigned n_uids = 0; |
| size_t sz = 0, l; |
| uid_t uid; |
| gid_t gid; |
| pid_t pid; |
| int r; |
| |
| assert(_home); |
| |
| if (!user || streq(user, "root") || streq(user, "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"); |
| |
| *_home = NULL; |
| return 0; |
| } |
| |
| /* First, get user credentials */ |
| fd = spawn_getent("passwd", user, &pid); |
| if (fd < 0) |
| return fd; |
| |
| f = fdopen(fd, "r"); |
| if (!f) |
| return log_oom(); |
| fd = -1; |
| |
| if (!fgets(line, sizeof(line), f)) { |
| |
| if (!ferror(f)) { |
| log_error("Failed to resolve user %s.", user); |
| return -ESRCH; |
| } |
| |
| log_error_errno(errno, "Failed to read from getent: %m"); |
| return -errno; |
| } |
| |
| truncate_nl(line); |
| |
| wait_for_terminate_and_warn("getent passwd", pid, true); |
| |
| x = strchr(line, ':'); |
| if (!x) { |
| log_error("/etc/passwd entry has invalid user field."); |
| return -EIO; |
| } |
| |
| u = strchr(x+1, ':'); |
| if (!u) { |
| log_error("/etc/passwd entry has invalid password field."); |
| return -EIO; |
| } |
| |
| u++; |
| g = strchr(u, ':'); |
| if (!g) { |
| log_error("/etc/passwd entry has invalid UID field."); |
| return -EIO; |
| } |
| |
| *g = 0; |
| g++; |
| x = strchr(g, ':'); |
| if (!x) { |
| log_error("/etc/passwd entry has invalid GID field."); |
| return -EIO; |
| } |
| |
| *x = 0; |
| h = strchr(x+1, ':'); |
| if (!h) { |
| log_error("/etc/passwd entry has invalid GECOS field."); |
| return -EIO; |
| } |
| |
| h++; |
| x = strchr(h, ':'); |
| if (!x) { |
| log_error("/etc/passwd entry has invalid home directory field."); |
| return -EIO; |
| } |
| |
| *x = 0; |
| |
| r = parse_uid(u, &uid); |
| if (r < 0) { |
| log_error("Failed to parse UID of user."); |
| return -EIO; |
| } |
| |
| r = parse_gid(g, &gid); |
| if (r < 0) { |
| log_error("Failed to parse GID of user."); |
| return -EIO; |
| } |
| |
| home = strdup(h); |
| if (!home) |
| return log_oom(); |
| |
| /* Second, get group memberships */ |
| fd = spawn_getent("initgroups", user, &pid); |
| if (fd < 0) |
| return fd; |
| |
| fclose(f); |
| f = fdopen(fd, "r"); |
| if (!f) |
| return log_oom(); |
| fd = -1; |
| |
| if (!fgets(line, sizeof(line), f)) { |
| if (!ferror(f)) { |
| log_error("Failed to resolve user %s.", user); |
| return -ESRCH; |
| } |
| |
| log_error_errno(errno, "Failed to read from getent: %m"); |
| return -errno; |
| } |
| |
| truncate_nl(line); |
| |
| wait_for_terminate_and_warn("getent initgroups", pid, true); |
| |
| /* Skip over the username and subsequent separator whitespace */ |
| x = line; |
| x += strcspn(x, WHITESPACE); |
| x += strspn(x, WHITESPACE); |
| |
| FOREACH_WORD(word, l, x, state) { |
| char c[l+1]; |
| |
| memcpy(c, word, l); |
| c[l] = 0; |
| |
| if (!GREEDY_REALLOC(uids, sz, n_uids+1)) |
| return log_oom(); |
| |
| r = parse_uid(c, &uids[n_uids++]); |
| if (r < 0) { |
| log_error("Failed to parse group data from getent."); |
| return -EIO; |
| } |
| } |
| |
| 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); |
| if (r < 0 && r != -EEXIST) |
| return log_error_errno(r, "Failed to make home directory: %m"); |
| |
| (void) fchown(STDIN_FILENO, uid, gid); |
| (void) fchown(STDOUT_FILENO, uid, gid); |
| (void) fchown(STDERR_FILENO, uid, gid); |
| |
| if (setgroups(n_uids, uids) < 0) |
| return log_error_errno(errno, "Failed to set auxiliary groups: %m"); |
| |
| if (setresgid(gid, gid, gid) < 0) |
| return log_error_errno(errno, "setregid() failed: %m"); |
| |
| if (setresuid(uid, uid, uid) < 0) |
| return log_error_errno(errno, "setreuid() failed: %m"); |
| |
| if (_home) { |
| *_home = home; |
| home = NULL; |
| } |
| |
| return 0; |
| } |