summaryrefslogtreecommitdiffstats
path: root/src/basic/user-util.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/basic/user-util.c')
-rw-r--r--src/basic/user-util.c481
1 files changed, 324 insertions, 157 deletions
diff --git a/src/basic/user-util.c b/src/basic/user-util.c
index 9e6926b..6bdf5bf 100644
--- a/src/basic/user-util.c
+++ b/src/basic/user-util.c
@@ -184,27 +184,28 @@ const char* default_root_shell(const char *root) {
static int synthesize_user_creds(
const char **username,
- uid_t *uid, gid_t *gid,
- const char **home,
- const char **shell,
+ uid_t *ret_uid, gid_t *ret_gid,
+ const char **ret_home,
+ const char **ret_shell,
UserCredsFlags flags) {
+ assert(username);
+ assert(*username);
+
/* We enforce some special rules for uid=0 and uid=65534: in order to avoid NSS lookups for root we hardcode
* their user record data. */
if (STR_IN_SET(*username, "root", "0")) {
*username = "root";
- if (uid)
- *uid = 0;
- if (gid)
- *gid = 0;
-
- if (home)
- *home = "/root";
-
- if (shell)
- *shell = default_root_shell(NULL);
+ if (ret_uid)
+ *ret_uid = 0;
+ if (ret_gid)
+ *ret_gid = 0;
+ if (ret_home)
+ *ret_home = "/root";
+ if (ret_shell)
+ *ret_shell = default_root_shell(NULL);
return 0;
}
@@ -213,16 +214,14 @@ static int synthesize_user_creds(
synthesize_nobody()) {
*username = NOBODY_USER_NAME;
- if (uid)
- *uid = UID_NOBODY;
- if (gid)
- *gid = GID_NOBODY;
-
- if (home)
- *home = FLAGS_SET(flags, USER_CREDS_CLEAN) ? NULL : "/";
-
- if (shell)
- *shell = FLAGS_SET(flags, USER_CREDS_CLEAN) ? NULL : NOLOGIN;
+ if (ret_uid)
+ *ret_uid = UID_NOBODY;
+ if (ret_gid)
+ *ret_gid = GID_NOBODY;
+ if (ret_home)
+ *ret_home = FLAGS_SET(flags, USER_CREDS_CLEAN) ? NULL : "/";
+ if (ret_shell)
+ *ret_shell = FLAGS_SET(flags, USER_CREDS_CLEAN) ? NULL : NOLOGIN;
return 0;
}
@@ -232,11 +231,12 @@ static int synthesize_user_creds(
int get_user_creds(
const char **username,
- uid_t *uid, gid_t *gid,
- const char **home,
- const char **shell,
+ uid_t *ret_uid, gid_t *ret_gid,
+ const char **ret_home,
+ const char **ret_shell,
UserCredsFlags flags) {
+ bool patch_username = false;
uid_t u = UID_INVALID;
struct passwd *p;
int r;
@@ -245,7 +245,7 @@ int get_user_creds(
assert(*username);
if (!FLAGS_SET(flags, USER_CREDS_PREFER_NSS) ||
- (!home && !shell)) {
+ (!ret_home && !ret_shell)) {
/* So here's the deal: normally, we'll try to synthesize all records we can synthesize, and override
* the user database with that. However, if the user specifies USER_CREDS_PREFER_NSS then the
@@ -256,7 +256,7 @@ int get_user_creds(
* of the relevant users, but changing the UID/GID mappings for them is something we explicitly don't
* support. */
- r = synthesize_user_creds(username, uid, gid, home, shell, flags);
+ r = synthesize_user_creds(username, ret_uid, ret_gid, ret_home, ret_shell, flags);
if (r >= 0)
return 0;
if (r != -ENOMEDIUM) /* not a username we can synthesize */
@@ -271,15 +271,15 @@ int get_user_creds(
* instead of the first occurrence in the database. However if the uid was configured by a numeric uid,
* then let's pick the real username from /etc/passwd. */
if (p)
- *username = p->pw_name;
- else if (FLAGS_SET(flags, USER_CREDS_ALLOW_MISSING) && !gid && !home && !shell) {
+ patch_username = true;
+ else if (FLAGS_SET(flags, USER_CREDS_ALLOW_MISSING) && !ret_gid && !ret_home && !ret_shell) {
/* If the specified user is a numeric UID and it isn't in the user database, and the caller
* passed USER_CREDS_ALLOW_MISSING and was only interested in the UID, then just return that
* and don't complain. */
- if (uid)
- *uid = u;
+ if (ret_uid)
+ *ret_uid = u;
return 0;
}
@@ -293,65 +293,57 @@ int get_user_creds(
r = IN_SET(errno, 0, ENOENT) ? -ESRCH : -errno;
/* If the user requested that we only synthesize as fallback, do so now */
- if (FLAGS_SET(flags, USER_CREDS_PREFER_NSS)) {
- if (synthesize_user_creds(username, uid, gid, home, shell, flags) >= 0)
+ if (FLAGS_SET(flags, USER_CREDS_PREFER_NSS))
+ if (synthesize_user_creds(username, ret_uid, ret_gid, ret_home, ret_shell, flags) >= 0)
return 0;
- }
return r;
}
- if (uid) {
- if (!uid_is_valid(p->pw_uid))
- return -EBADMSG;
+ if (ret_uid && !uid_is_valid(p->pw_uid))
+ return -EBADMSG;
- *uid = p->pw_uid;
- }
+ if (ret_gid && !gid_is_valid(p->pw_gid))
+ return -EBADMSG;
- if (gid) {
- if (!gid_is_valid(p->pw_gid))
- return -EBADMSG;
+ if (ret_uid)
+ *ret_uid = p->pw_uid;
- *gid = p->pw_gid;
- }
+ if (ret_gid)
+ *ret_gid = p->pw_gid;
- if (home) {
- if (FLAGS_SET(flags, USER_CREDS_CLEAN) &&
- (empty_or_root(p->pw_dir) ||
- !path_is_valid(p->pw_dir) ||
- !path_is_absolute(p->pw_dir)))
- *home = NULL; /* Note: we don't insist on normalized paths, since there are setups that have /./ in the path */
- else
- *home = p->pw_dir;
- }
+ if (ret_home)
+ /* Note: we don't insist on normalized paths, since there are setups that have /./ in the path */
+ *ret_home = (FLAGS_SET(flags, USER_CREDS_CLEAN) &&
+ (empty_or_root(p->pw_dir) ||
+ !path_is_valid(p->pw_dir) ||
+ !path_is_absolute(p->pw_dir))) ? NULL : p->pw_dir;
- if (shell) {
- if (FLAGS_SET(flags, USER_CREDS_CLEAN) &&
- (isempty(p->pw_shell) ||
- !path_is_valid(p->pw_shell) ||
- !path_is_absolute(p->pw_shell) ||
- is_nologin_shell(p->pw_shell)))
- *shell = NULL;
- else
- *shell = p->pw_shell;
- }
+ if (ret_shell)
+ *ret_shell = (FLAGS_SET(flags, USER_CREDS_CLEAN) &&
+ (isempty(p->pw_shell) ||
+ !path_is_valid(p->pw_shell) ||
+ !path_is_absolute(p->pw_shell) ||
+ is_nologin_shell(p->pw_shell))) ? NULL : p->pw_shell;
+
+ if (patch_username)
+ *username = p->pw_name;
return 0;
}
-int get_group_creds(const char **groupname, gid_t *gid, UserCredsFlags flags) {
- struct group *g;
- gid_t id;
+static int synthesize_group_creds(
+ const char **groupname,
+ gid_t *ret_gid) {
assert(groupname);
-
- /* We enforce some special rules for gid=0: in order to avoid NSS lookups for root we hardcode its data. */
+ assert(*groupname);
if (STR_IN_SET(*groupname, "root", "0")) {
*groupname = "root";
- if (gid)
- *gid = 0;
+ if (ret_gid)
+ *ret_gid = 0;
return 0;
}
@@ -360,21 +352,41 @@ int get_group_creds(const char **groupname, gid_t *gid, UserCredsFlags flags) {
synthesize_nobody()) {
*groupname = NOBODY_GROUP_NAME;
- if (gid)
- *gid = GID_NOBODY;
+ if (ret_gid)
+ *ret_gid = GID_NOBODY;
return 0;
}
+ return -ENOMEDIUM;
+}
+
+int get_group_creds(const char **groupname, gid_t *ret_gid, UserCredsFlags flags) {
+ bool patch_groupname = false;
+ struct group *g;
+ gid_t id;
+ int r;
+
+ assert(groupname);
+ assert(*groupname);
+
+ if (!FLAGS_SET(flags, USER_CREDS_PREFER_NSS)) {
+ r = synthesize_group_creds(groupname, ret_gid);
+ if (r >= 0)
+ return 0;
+ if (r != -ENOMEDIUM) /* not a groupname we can synthesize */
+ return r;
+ }
+
if (parse_gid(*groupname, &id) >= 0) {
errno = 0;
g = getgrgid(id);
if (g)
- *groupname = g->gr_name;
+ patch_groupname = true;
else if (FLAGS_SET(flags, USER_CREDS_ALLOW_MISSING)) {
- if (gid)
- *gid = id;
+ if (ret_gid)
+ *ret_gid = id;
return 0;
}
@@ -383,18 +395,28 @@ int get_group_creds(const char **groupname, gid_t *gid, UserCredsFlags flags) {
g = getgrnam(*groupname);
}
- if (!g)
+ if (!g) {
/* getgrnam() may fail with ENOENT if /etc/group is missing.
* For us that is equivalent to the name not being defined. */
- return IN_SET(errno, 0, ENOENT) ? -ESRCH : -errno;
+ r = IN_SET(errno, 0, ENOENT) ? -ESRCH : -errno;
+
+ if (FLAGS_SET(flags, USER_CREDS_PREFER_NSS))
+ if (synthesize_group_creds(groupname, ret_gid) >= 0)
+ return 0;
- if (gid) {
+ return r;
+ }
+
+ if (ret_gid) {
if (!gid_is_valid(g->gr_gid))
return -EBADMSG;
- *gid = g->gr_gid;
+ *ret_gid = g->gr_gid;
}
+ if (patch_groupname)
+ *groupname = g->gr_name;
+
return 0;
}
@@ -409,31 +431,11 @@ char* uid_to_name(uid_t uid) {
return strdup(NOBODY_USER_NAME);
if (uid_is_valid(uid)) {
- long bufsize;
-
- bufsize = sysconf(_SC_GETPW_R_SIZE_MAX);
- if (bufsize <= 0)
- bufsize = 4096;
+ _cleanup_free_ struct passwd *pw = NULL;
- for (;;) {
- struct passwd pwbuf, *pw = NULL;
- _cleanup_free_ char *buf = NULL;
-
- buf = malloc(bufsize);
- if (!buf)
- return NULL;
-
- r = getpwuid_r(uid, &pwbuf, buf, (size_t) bufsize, &pw);
- if (r == 0 && pw)
- return strdup(pw->pw_name);
- if (r != ERANGE)
- break;
-
- if (bufsize > LONG_MAX/2) /* overflow check */
- return NULL;
-
- bufsize *= 2;
- }
+ r = getpwuid_malloc(uid, &pw);
+ if (r >= 0)
+ return strdup(pw->pw_name);
}
if (asprintf(&ret, UID_FMT, uid) < 0)
@@ -452,31 +454,11 @@ char* gid_to_name(gid_t gid) {
return strdup(NOBODY_GROUP_NAME);
if (gid_is_valid(gid)) {
- long bufsize;
-
- bufsize = sysconf(_SC_GETGR_R_SIZE_MAX);
- if (bufsize <= 0)
- bufsize = 4096;
-
- for (;;) {
- struct group grbuf, *gr = NULL;
- _cleanup_free_ char *buf = NULL;
-
- buf = malloc(bufsize);
- if (!buf)
- return NULL;
-
- r = getgrgid_r(gid, &grbuf, buf, (size_t) bufsize, &gr);
- if (r == 0 && gr)
- return strdup(gr->gr_name);
- if (r != ERANGE)
- break;
-
- if (bufsize > LONG_MAX/2) /* overflow check */
- return NULL;
+ _cleanup_free_ struct group *gr = NULL;
- bufsize *= 2;
- }
+ r = getgrgid_malloc(gid, &gr);
+ if (r >= 0)
+ return strdup(gr->gr_name);
}
if (asprintf(&ret, GID_FMT, gid) < 0)
@@ -587,9 +569,10 @@ int getgroups_alloc(gid_t** gids) {
}
int get_home_dir(char **ret) {
- struct passwd *p;
+ _cleanup_free_ struct passwd *p = NULL;
const char *e;
uid_t u;
+ int r;
assert(ret);
@@ -604,19 +587,17 @@ int get_home_dir(char **ret) {
e = "/root";
goto found;
}
-
if (u == UID_NOBODY && synthesize_nobody()) {
e = "/";
goto found;
}
/* Check the database... */
- errno = 0;
- p = getpwuid(u);
- if (!p)
- return errno_or_else(ESRCH);
- e = p->pw_dir;
+ r = getpwuid_malloc(u, &p);
+ if (r < 0)
+ return r;
+ e = p->pw_dir;
if (!path_is_valid(e) || !path_is_absolute(e))
return -EINVAL;
@@ -625,9 +606,10 @@ int get_home_dir(char **ret) {
}
int get_shell(char **ret) {
- struct passwd *p;
+ _cleanup_free_ struct passwd *p = NULL;
const char *e;
uid_t u;
+ int r;
assert(ret);
@@ -648,12 +630,11 @@ int get_shell(char **ret) {
}
/* Check the database... */
- errno = 0;
- p = getpwuid(u);
- if (!p)
- return errno_or_else(ESRCH);
- e = p->pw_shell;
+ r = getpwuid_malloc(u, &p);
+ if (r < 0)
+ return r;
+ e = p->pw_shell;
if (!path_is_valid(e) || !path_is_absolute(e))
return -EINVAL;
@@ -661,17 +642,26 @@ int get_shell(char **ret) {
return path_simplify_alloc(e, ret);
}
-int reset_uid_gid(void) {
+int fully_set_uid_gid(uid_t uid, gid_t gid, const gid_t supplementary_gids[], size_t n_supplementary_gids) {
int r;
- r = maybe_setgroups(0, NULL);
+ assert(supplementary_gids || n_supplementary_gids == 0);
+
+ /* Sets all UIDs and all GIDs to the specified ones. Drops all auxiliary GIDs */
+
+ r = maybe_setgroups(n_supplementary_gids, supplementary_gids);
if (r < 0)
return r;
- if (setresgid(0, 0, 0) < 0)
- return -errno;
+ if (gid_is_valid(gid))
+ if (setresgid(gid, gid, gid) < 0)
+ return -errno;
+
+ if (uid_is_valid(uid))
+ if (setresuid(uid, uid, uid) < 0)
+ return -errno;
- return RET_NERRNO(setresuid(0, 0, 0));
+ return 0;
}
int take_etc_passwd_lock(const char *root) {
@@ -807,11 +797,11 @@ bool valid_user_group_name(const char *u, ValidUserFlags flags) {
sz = sysconf(_SC_LOGIN_NAME_MAX);
assert_se(sz > 0);
- if (l > (size_t) sz)
+ if (l > (size_t) sz) /* glibc: 256 */
return false;
- if (l > NAME_MAX) /* must fit in a filename */
+ if (l > NAME_MAX) /* must fit in a filename: 255 */
return false;
- if (l > UT_NAMESIZE - 1)
+ if (l > UT_NAMESIZE - 1) /* must fit in utmp: 31 */
return false;
}
@@ -987,8 +977,8 @@ int fgetpwent_sane(FILE *stream, struct passwd **pw) {
errno = 0;
struct passwd *p = fgetpwent(stream);
- if (!p && errno != ENOENT)
- return errno_or_else(EIO);
+ if (!p && !IN_SET(errno, 0, ENOENT))
+ return -errno;
*pw = p;
return !!p;
@@ -1000,8 +990,8 @@ int fgetspent_sane(FILE *stream, struct spwd **sp) {
errno = 0;
struct spwd *s = fgetspent(stream);
- if (!s && errno != ENOENT)
- return errno_or_else(EIO);
+ if (!s && !IN_SET(errno, 0, ENOENT))
+ return -errno;
*sp = s;
return !!s;
@@ -1013,8 +1003,8 @@ int fgetgrent_sane(FILE *stream, struct group **gr) {
errno = 0;
struct group *g = fgetgrent(stream);
- if (!g && errno != ENOENT)
- return errno_or_else(EIO);
+ if (!g && !IN_SET(errno, 0, ENOENT))
+ return -errno;
*gr = g;
return !!g;
@@ -1027,8 +1017,8 @@ int fgetsgent_sane(FILE *stream, struct sgrp **sg) {
errno = 0;
struct sgrp *s = fgetsgent(stream);
- if (!s && errno != ENOENT)
- return errno_or_else(EIO);
+ if (!s && !IN_SET(errno, 0, ENOENT))
+ return -errno;
*sg = s;
return !!s;
@@ -1058,3 +1048,180 @@ const char* get_home_root(void) {
return "/home";
}
+
+static size_t getpw_buffer_size(void) {
+ long bufsize = sysconf(_SC_GETPW_R_SIZE_MAX);
+ return bufsize <= 0 ? 4096U : (size_t) bufsize;
+}
+
+static bool errno_is_user_doesnt_exist(int error) {
+ /* See getpwnam(3) and getgrnam(3): those codes and others can be returned if the user or group are
+ * not found. */
+ return IN_SET(abs(error), ENOENT, ESRCH, EBADF, EPERM);
+}
+
+int getpwnam_malloc(const char *name, struct passwd **ret) {
+ size_t bufsize = getpw_buffer_size();
+ int r;
+
+ /* A wrapper around getpwnam_r() that allocates the necessary buffer on the heap. The caller must
+ * free() the returned structures! */
+
+ if (isempty(name))
+ return -EINVAL;
+
+ for (;;) {
+ _cleanup_free_ void *buf = NULL;
+
+ buf = malloc(ALIGN(sizeof(struct passwd)) + bufsize);
+ if (!buf)
+ return -ENOMEM;
+
+ struct passwd *pw = NULL;
+ r = getpwnam_r(name, buf, (char*) buf + ALIGN(sizeof(struct passwd)), (size_t) bufsize, &pw);
+ if (r == 0) {
+ if (pw) {
+ if (ret)
+ *ret = TAKE_PTR(buf);
+ return 0;
+ }
+
+ return -ESRCH;
+ }
+
+ assert(r > 0);
+
+ /* getpwnam() may fail with ENOENT if /etc/passwd is missing. For us that is equivalent to
+ * the name not being defined. */
+ if (errno_is_user_doesnt_exist(r))
+ return -ESRCH;
+ if (r != ERANGE)
+ return -r;
+
+ if (bufsize > SIZE_MAX/2 - ALIGN(sizeof(struct passwd)))
+ return -ENOMEM;
+ bufsize *= 2;
+ }
+}
+
+int getpwuid_malloc(uid_t uid, struct passwd **ret) {
+ size_t bufsize = getpw_buffer_size();
+ int r;
+
+ if (!uid_is_valid(uid))
+ return -EINVAL;
+
+ for (;;) {
+ _cleanup_free_ void *buf = NULL;
+
+ buf = malloc(ALIGN(sizeof(struct passwd)) + bufsize);
+ if (!buf)
+ return -ENOMEM;
+
+ struct passwd *pw = NULL;
+ r = getpwuid_r(uid, buf, (char*) buf + ALIGN(sizeof(struct passwd)), (size_t) bufsize, &pw);
+ if (r == 0) {
+ if (pw) {
+ if (ret)
+ *ret = TAKE_PTR(buf);
+ return 0;
+ }
+
+ return -ESRCH;
+ }
+
+ assert(r > 0);
+
+ if (errno_is_user_doesnt_exist(r))
+ return -ESRCH;
+ if (r != ERANGE)
+ return -r;
+
+ if (bufsize > SIZE_MAX/2 - ALIGN(sizeof(struct passwd)))
+ return -ENOMEM;
+ bufsize *= 2;
+ }
+}
+
+static size_t getgr_buffer_size(void) {
+ long bufsize = sysconf(_SC_GETGR_R_SIZE_MAX);
+ return bufsize <= 0 ? 4096U : (size_t) bufsize;
+}
+
+int getgrnam_malloc(const char *name, struct group **ret) {
+ size_t bufsize = getgr_buffer_size();
+ int r;
+
+ if (isempty(name))
+ return -EINVAL;
+
+ for (;;) {
+ _cleanup_free_ void *buf = NULL;
+
+ buf = malloc(ALIGN(sizeof(struct group)) + bufsize);
+ if (!buf)
+ return -ENOMEM;
+
+ struct group *gr = NULL;
+ r = getgrnam_r(name, buf, (char*) buf + ALIGN(sizeof(struct group)), (size_t) bufsize, &gr);
+ if (r == 0) {
+ if (gr) {
+ if (ret)
+ *ret = TAKE_PTR(buf);
+ return 0;
+ }
+
+ return -ESRCH;
+ }
+
+ assert(r > 0);
+
+ if (errno_is_user_doesnt_exist(r))
+ return -ESRCH;
+ if (r != ERANGE)
+ return -r;
+
+ if (bufsize > SIZE_MAX/2 - ALIGN(sizeof(struct group)))
+ return -ENOMEM;
+ bufsize *= 2;
+ }
+}
+
+int getgrgid_malloc(gid_t gid, struct group **ret) {
+ size_t bufsize = getgr_buffer_size();
+ int r;
+
+ if (!gid_is_valid(gid))
+ return -EINVAL;
+
+ for (;;) {
+ _cleanup_free_ void *buf = NULL;
+
+ buf = malloc(ALIGN(sizeof(struct group)) + bufsize);
+ if (!buf)
+ return -ENOMEM;
+
+ struct group *gr = NULL;
+ r = getgrgid_r(gid, buf, (char*) buf + ALIGN(sizeof(struct group)), (size_t) bufsize, &gr);
+ if (r == 0) {
+ if (gr) {
+ if (ret)
+ *ret = TAKE_PTR(buf);
+ return 0;
+ }
+
+ return -ESRCH;
+ }
+
+ assert(r > 0);
+
+ if (errno_is_user_doesnt_exist(r))
+ return -ESRCH;
+ if (r != ERANGE)
+ return -r;
+
+ if (bufsize > SIZE_MAX/2 - ALIGN(sizeof(struct group)))
+ return -ENOMEM;
+ bufsize *= 2;
+ }
+}