2017-11-18 17:09:20 +01:00
|
|
|
/* SPDX-License-Identifier: LGPL-2.1+ */
|
2015-09-07 18:42:14 +02:00
|
|
|
|
2015-10-24 22:58:24 +02:00
|
|
|
#include <grp.h>
|
2015-09-07 18:42:14 +02:00
|
|
|
#include <sys/types.h>
|
|
|
|
#include <unistd.h>
|
|
|
|
|
2015-10-27 03:01:06 +01:00
|
|
|
#include "alloc-util.h"
|
2018-02-26 15:29:30 +01:00
|
|
|
#include "def.h"
|
2018-01-11 00:39:12 +01:00
|
|
|
#include "errno.h"
|
2015-10-25 13:14:12 +01:00
|
|
|
#include "fd-util.h"
|
2018-02-26 15:29:30 +01:00
|
|
|
#include "fileio.h"
|
2015-09-07 18:42:14 +02:00
|
|
|
#include "mkdir.h"
|
2015-10-25 13:14:12 +01:00
|
|
|
#include "nspawn-setuid.h"
|
2015-09-07 18:42:14 +02:00
|
|
|
#include "process-util.h"
|
2015-10-24 22:58:24 +02:00
|
|
|
#include "signal-util.h"
|
|
|
|
#include "string-util.h"
|
2018-02-26 15:30:05 +01:00
|
|
|
#include "strv.h"
|
2015-10-25 22:32:30 +01:00
|
|
|
#include "user-util.h"
|
2015-10-24 22:58:24 +02:00
|
|
|
#include "util.h"
|
2015-09-07 18:42:14 +02:00
|
|
|
|
|
|
|
static int spawn_getent(const char *database, const char *key, pid_t *rpid) {
|
2017-12-22 13:08:14 +01:00
|
|
|
int pipe_fds[2], r;
|
2015-09-07 18:42:14 +02:00
|
|
|
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");
|
|
|
|
|
2017-12-27 21:49:19 +01:00
|
|
|
r = safe_fork("(getent)", FORK_RESET_SIGNALS|FORK_DEATHSIG|FORK_LOG, &pid);
|
2018-02-26 20:51:04 +01:00
|
|
|
if (r < 0) {
|
|
|
|
safe_close_pair(pipe_fds);
|
2017-12-27 21:49:19 +01:00
|
|
|
return r;
|
2018-02-26 20:51:04 +01:00
|
|
|
}
|
2017-12-22 13:08:14 +01:00
|
|
|
if (r == 0) {
|
2015-09-07 18:42:14 +02:00
|
|
|
char *empty_env = NULL;
|
|
|
|
|
2018-02-28 23:32:49 +01:00
|
|
|
safe_close(pipe_fds[0]);
|
2015-09-07 18:42:14 +02:00
|
|
|
|
2018-02-28 23:32:49 +01:00
|
|
|
if (rearrange_stdio(-1, pipe_fds[1], -1) < 0)
|
2015-09-07 18:42:14 +02:00
|
|
|
_exit(EXIT_FAILURE);
|
|
|
|
|
|
|
|
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) {
|
2018-02-26 15:29:30 +01:00
|
|
|
char *x, *u, *g, *h;
|
2015-09-07 18:42:14 +02:00
|
|
|
const char *word, *state;
|
|
|
|
_cleanup_free_ uid_t *uids = NULL;
|
2018-02-26 15:29:30 +01:00
|
|
|
_cleanup_free_ char *home = NULL, *line = NULL;
|
2015-09-07 18:42:14 +02:00
|
|
|
_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);
|
|
|
|
|
2018-02-26 15:30:05 +01:00
|
|
|
if (!user || STR_IN_SET(user, "root", "0")) {
|
2015-09-07 18:42:14 +02:00
|
|
|
/* 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;
|
|
|
|
|
2018-02-26 15:29:30 +01:00
|
|
|
f = fdopen(fd, "re");
|
2015-09-07 18:42:14 +02:00
|
|
|
if (!f)
|
|
|
|
return log_oom();
|
|
|
|
fd = -1;
|
|
|
|
|
2018-02-26 15:29:30 +01:00
|
|
|
r = read_line(f, LONG_LINE_MAX, &line);
|
2018-11-20 23:40:44 +01:00
|
|
|
if (r == 0)
|
|
|
|
return log_error_errno(SYNTHETIC_ERRNO(ESRCH),
|
|
|
|
"Failed to resolve user %s.", user);
|
2018-02-26 15:29:30 +01:00
|
|
|
if (r < 0)
|
|
|
|
return log_error_errno(r, "Failed to read from getent: %m");
|
2015-09-07 18:42:14 +02:00
|
|
|
|
2017-12-28 00:51:19 +01:00
|
|
|
(void) wait_for_terminate_and_check("getent passwd", pid, WAIT_LOG);
|
2015-09-07 18:42:14 +02:00
|
|
|
|
|
|
|
x = strchr(line, ':');
|
2018-11-20 23:40:44 +01:00
|
|
|
if (!x)
|
|
|
|
return log_error_errno(SYNTHETIC_ERRNO(EIO),
|
|
|
|
"/etc/passwd entry has invalid user field.");
|
2015-09-07 18:42:14 +02:00
|
|
|
|
|
|
|
u = strchr(x+1, ':');
|
2018-11-20 23:40:44 +01:00
|
|
|
if (!u)
|
|
|
|
return log_error_errno(SYNTHETIC_ERRNO(EIO),
|
|
|
|
"/etc/passwd entry has invalid password field.");
|
2015-09-07 18:42:14 +02:00
|
|
|
|
|
|
|
u++;
|
|
|
|
g = strchr(u, ':');
|
2018-11-20 23:40:44 +01:00
|
|
|
if (!g)
|
|
|
|
return log_error_errno(SYNTHETIC_ERRNO(EIO),
|
|
|
|
"/etc/passwd entry has invalid UID field.");
|
2015-09-07 18:42:14 +02:00
|
|
|
|
|
|
|
*g = 0;
|
|
|
|
g++;
|
|
|
|
x = strchr(g, ':');
|
2018-11-20 23:40:44 +01:00
|
|
|
if (!x)
|
|
|
|
return log_error_errno(SYNTHETIC_ERRNO(EIO),
|
|
|
|
"/etc/passwd entry has invalid GID field.");
|
2015-09-07 18:42:14 +02:00
|
|
|
|
|
|
|
*x = 0;
|
|
|
|
h = strchr(x+1, ':');
|
2018-11-20 23:40:44 +01:00
|
|
|
if (!h)
|
|
|
|
return log_error_errno(SYNTHETIC_ERRNO(EIO),
|
|
|
|
"/etc/passwd entry has invalid GECOS field.");
|
2015-09-07 18:42:14 +02:00
|
|
|
|
|
|
|
h++;
|
|
|
|
x = strchr(h, ':');
|
2018-11-20 23:40:44 +01:00
|
|
|
if (!x)
|
|
|
|
return log_error_errno(SYNTHETIC_ERRNO(EIO),
|
|
|
|
"/etc/passwd entry has invalid home directory field.");
|
2015-09-07 18:42:14 +02:00
|
|
|
|
|
|
|
*x = 0;
|
|
|
|
|
|
|
|
r = parse_uid(u, &uid);
|
2018-11-20 23:40:44 +01:00
|
|
|
if (r < 0)
|
|
|
|
return log_error_errno(SYNTHETIC_ERRNO(EIO),
|
|
|
|
"Failed to parse UID of user.");
|
2015-09-07 18:42:14 +02:00
|
|
|
|
|
|
|
r = parse_gid(g, &gid);
|
2018-11-20 23:40:44 +01:00
|
|
|
if (r < 0)
|
|
|
|
return log_error_errno(SYNTHETIC_ERRNO(EIO),
|
|
|
|
"Failed to parse GID of user.");
|
2015-09-07 18:42:14 +02:00
|
|
|
|
|
|
|
home = strdup(h);
|
|
|
|
if (!home)
|
|
|
|
return log_oom();
|
|
|
|
|
2018-02-26 15:29:30 +01:00
|
|
|
f = safe_fclose(f);
|
|
|
|
line = mfree(line);
|
|
|
|
|
2015-09-07 18:42:14 +02:00
|
|
|
/* Second, get group memberships */
|
|
|
|
fd = spawn_getent("initgroups", user, &pid);
|
|
|
|
if (fd < 0)
|
|
|
|
return fd;
|
|
|
|
|
2018-02-26 15:29:30 +01:00
|
|
|
f = fdopen(fd, "re");
|
2015-09-07 18:42:14 +02:00
|
|
|
if (!f)
|
|
|
|
return log_oom();
|
|
|
|
fd = -1;
|
|
|
|
|
2018-02-26 15:29:30 +01:00
|
|
|
r = read_line(f, LONG_LINE_MAX, &line);
|
2018-11-20 23:40:44 +01:00
|
|
|
if (r == 0)
|
|
|
|
return log_error_errno(SYNTHETIC_ERRNO(ESRCH),
|
|
|
|
"Failed to resolve user %s.", user);
|
2018-02-26 15:29:30 +01:00
|
|
|
if (r < 0)
|
|
|
|
return log_error_errno(r, "Failed to read from getent: %m");
|
2015-09-07 18:42:14 +02:00
|
|
|
|
2017-12-28 00:51:19 +01:00
|
|
|
(void) wait_for_terminate_and_check("getent initgroups", pid, WAIT_LOG);
|
2015-09-07 18:42:14 +02:00
|
|
|
|
|
|
|
/* 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++]);
|
2018-02-26 15:30:19 +01:00
|
|
|
if (r < 0)
|
|
|
|
return log_error_errno(r, "Failed to parse group data from getent: %m");
|
2015-09-07 18:42:14 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
r = mkdir_parents(home, 0775);
|
|
|
|
if (r < 0)
|
|
|
|
return log_error_errno(r, "Failed to make home root directory: %m");
|
|
|
|
|
2018-03-22 12:38:01 +01:00
|
|
|
r = mkdir_safe(home, 0755, uid, gid, 0);
|
tree-wide: warn when a directory path already exists but has bad mode/owner/type
When we are attempting to create directory somewhere in the bowels of /var/lib
and get an error that it already exists, it can be quite hard to diagnose what
is wrong (especially for a user who is not aware that the directory must have
the specified owner, and permissions not looser than what was requested). Let's
print a warning in most cases. A warning is appropriate, because such state is
usually a sign of borked installation and needs to be resolved by the adminstrator.
$ build/test-fs-util
Path "/tmp/test-readlink_and_make_absolute" already exists and is not a directory, refusing.
(or)
Directory "/tmp/test-readlink_and_make_absolute" already exists, but has mode 0775 that is too permissive (0755 was requested), refusing.
(or)
Directory "/tmp/test-readlink_and_make_absolute" already exists, but is owned by 1001:1000 (1000:1000 was requested), refusing.
Assertion 'mkdir_safe(tempdir, 0755, getuid(), getgid(), MKDIR_WARN_MODE) >= 0' failed at ../src/test/test-fs-util.c:320, function test_readlink_and_make_absolute(). Aborting.
No functional change except for the new log lines.
2018-03-22 13:03:41 +01:00
|
|
|
if (r < 0 && !IN_SET(r, -EEXIST, -ENOTDIR))
|
2015-09-07 18:42:14 +02:00
|
|
|
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)
|
2016-01-06 14:56:14 +01:00
|
|
|
return log_error_errno(errno, "setresgid() failed: %m");
|
2015-09-07 18:42:14 +02:00
|
|
|
|
|
|
|
if (setresuid(uid, uid, uid) < 0)
|
2016-01-06 14:56:14 +01:00
|
|
|
return log_error_errno(errno, "setresuid() failed: %m");
|
2015-09-07 18:42:14 +02:00
|
|
|
|
2018-04-05 07:26:26 +02:00
|
|
|
if (_home)
|
|
|
|
*_home = TAKE_PTR(home);
|
2015-09-07 18:42:14 +02:00
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|