/* SPDX-License-Identifier: LGPL-2.1+ */
-/***
- This file is part of systemd.
-
- Copyright 2010 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 <alloca.h>
#include <errno.h>
#include <utmp.h>
#include "alloc-util.h"
+#include "errno-util.h"
#include "fd-util.h"
#include "fileio.h"
#include "format-util.h"
#include "missing.h"
#include "parse-util.h"
#include "path-util.h"
+#include "random-util.h"
#include "string-util.h"
#include "strv.h"
#include "user-util.h"
if (!uid_is_valid(uid))
return -ENXIO; /* we return ENXIO instead of EINVAL
- * here, to make it easy to distuingish
+ * here, to make it easy to distinguish
* invalid numeric uids from invalid
* strings. */
char *getusername_malloc(void) {
const char *e;
- e = getenv("USER");
+ e = secure_getenv("USER");
if (e)
return strdup(e);
return uid_to_name(getuid());
}
-int get_user_creds(
+static bool is_nologin_shell(const char *shell) {
+
+ return PATH_IN_SET(shell,
+ /* 'nologin' is the friendliest way to disable logins for a user account. It prints a nice
+ * message and exits. Different distributions place the binary at different places though,
+ * hence let's list them all. */
+ "/bin/nologin",
+ "/sbin/nologin",
+ "/usr/bin/nologin",
+ "/usr/sbin/nologin",
+ /* 'true' and 'false' work too for the same purpose, but are less friendly as they don't do
+ * any message printing. Different distributions place the binary at various places but at
+ * least not in the 'sbin' directory. */
+ "/bin/false",
+ "/usr/bin/false",
+ "/bin/true",
+ "/usr/bin/true");
+}
+
+static int synthesize_user_creds(
const char **username,
uid_t *uid, gid_t *gid,
const char **home,
- const char **shell) {
-
- struct passwd *p;
- uid_t u;
-
- assert(username);
- assert(*username);
+ const char **shell,
+ UserCredsFlags flags) {
/* 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. */
*gid = GID_NOBODY;
if (home)
- *home = "/";
+ *home = FLAGS_SET(flags, USER_CREDS_CLEAN) ? NULL : "/";
if (shell)
- *shell = "/sbin/nologin";
+ *shell = FLAGS_SET(flags, USER_CREDS_CLEAN) ? NULL : NOLOGIN;
return 0;
}
+ return -ENOMEDIUM;
+}
+
+int get_user_creds(
+ const char **username,
+ uid_t *uid, gid_t *gid,
+ const char **home,
+ const char **shell,
+ UserCredsFlags flags) {
+
+ uid_t u = UID_INVALID;
+ struct passwd *p;
+ int r;
+
+ assert(username);
+ assert(*username);
+
+ if (!FLAGS_SET(flags, USER_CREDS_PREFER_NSS) ||
+ (!home && !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
+ * user database will override the synthetic records instead — except if the user is only interested in
+ * the UID and/or GID (but not the home directory, or the shell), in which case we'll always override
+ * the user database (i.e. the USER_CREDS_PREFER_NSS flag has no effect in this case). Why?
+ * Simply because there are valid usecase where the user might change the home directory or the shell
+ * 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);
+ if (r >= 0)
+ return 0;
+ if (r != -ENOMEDIUM) /* not a username we can synthesize */
+ return r;
+ }
+
if (parse_uid(*username, &u) >= 0) {
errno = 0;
p = getpwuid(u);
- /* If there are multiple users with the same id, make
- * sure to leave $USER to the configured value 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 there are multiple users with the same id, make sure to leave $USER to the configured value
+ * 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) {
+
+ /* 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 juts return that
+ * and don't complain. */
+
+ if (uid)
+ *uid = u;
+
+ return 0;
+ }
} else {
errno = 0;
p = getpwnam(*username);
}
+ if (!p) {
+ r = errno_or_else(ESRCH);
- if (!p)
- return errno > 0 ? -errno : -ESRCH;
+ /* 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)
+ return 0;
+ }
+
+ return r;
+ }
if (uid) {
if (!uid_is_valid(p->pw_uid))
*gid = p->pw_gid;
}
- if (home)
- *home = p->pw_dir;
-
- if (shell)
- *shell = p->pw_shell;
-
- return 0;
-}
-
-static inline bool is_nologin_shell(const char *shell) {
-
- return PATH_IN_SET(shell,
- /* 'nologin' is the friendliest way to disable logins for a user account. It prints a nice
- * message and exits. Different distributions place the binary at different places though,
- * hence let's list them all. */
- "/bin/nologin",
- "/sbin/nologin",
- "/usr/bin/nologin",
- "/usr/sbin/nologin",
- /* 'true' and 'false' work too for the same purpose, but are less friendly as they don't do
- * any message printing. Different distributions place the binary at various places but at
- * least not in the 'sbin' directory. */
- "/bin/false",
- "/usr/bin/false",
- "/bin/true",
- "/usr/bin/true");
-}
-
-int get_user_creds_clean(
- const char **username,
- uid_t *uid, gid_t *gid,
- const char **home,
- const char **shell) {
-
- int r;
-
- /* Like get_user_creds(), but resets home/shell to NULL if they don't contain anything relevant. */
-
- r = get_user_creds(username, uid, gid, home, shell);
- if (r < 0)
- return r;
-
- if (shell &&
- (isempty(*shell) || is_nologin_shell(*shell)))
- *shell = NULL;
+ 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 (home &&
- (isempty(*home) || path_equal(*home, "/")))
- *home = NULL;
+ if (shell) {
+ if (FLAGS_SET(flags, USER_CREDS_CLEAN) &&
+ (isempty(p->pw_shell) ||
+ !path_is_valid(p->pw_dir) ||
+ !path_is_absolute(p->pw_shell) ||
+ is_nologin_shell(p->pw_shell)))
+ *shell = NULL;
+ else
+ *shell = p->pw_shell;
+ }
return 0;
}
-int get_group_creds(const char **groupname, gid_t *gid) {
+int get_group_creds(const char **groupname, gid_t *gid, UserCredsFlags flags) {
struct group *g;
gid_t id;
assert(groupname);
- /* We enforce some special rules for gid=0: in order to avoid
- * NSS lookups for root we hardcode its data. */
+ /* We enforce some special rules for gid=0: in order to avoid NSS lookups for root we hardcode its data. */
if (STR_IN_SET(*groupname, "root", "0")) {
*groupname = "root";
if (g)
*groupname = g->gr_name;
+ else if (FLAGS_SET(flags, USER_CREDS_ALLOW_MISSING)) {
+ if (gid)
+ *gid = id;
+
+ return 0;
+ }
} else {
errno = 0;
g = getgrnam(*groupname);
}
if (!g)
- return errno > 0 ? -errno : -ESRCH;
+ return errno_or_else(ESRCH);
if (gid) {
if (!gid_is_valid(g->gr_gid))
if (r != ERANGE)
break;
+ if (bufsize > LONG_MAX/2) /* overflow check */
+ return NULL;
+
bufsize *= 2;
}
}
if (r != ERANGE)
break;
+ if (bufsize > LONG_MAX/2) /* overflow check */
+ return NULL;
+
bufsize *= 2;
}
}
int r;
gid_t gid;
- r = get_group_creds(&name, &gid);
+ r = get_group_creds(&name, &gid, 0);
if (r < 0)
return r;
/* Take the user specified one */
e = secure_getenv("HOME");
- if (e && path_is_absolute(e)) {
+ if (e && path_is_valid(e) && path_is_absolute(e)) {
h = strdup(e);
if (!h)
return -ENOMEM;
- *_h = h;
+ *_h = path_simplify(h, true);
return 0;
}
errno = 0;
p = getpwuid(u);
if (!p)
- return errno > 0 ? -errno : -ESRCH;
+ return errno_or_else(ESRCH);
- if (!path_is_absolute(p->pw_dir))
+ if (!path_is_valid(p->pw_dir) ||
+ !path_is_absolute(p->pw_dir))
return -EINVAL;
h = strdup(p->pw_dir);
if (!h)
return -ENOMEM;
- *_h = h;
+ *_h = path_simplify(h, true);
return 0;
}
assert(_s);
/* Take the user specified one */
- e = getenv("SHELL");
- if (e) {
+ e = secure_getenv("SHELL");
+ if (e && path_is_valid(e) && path_is_absolute(e)) {
s = strdup(e);
if (!s)
return -ENOMEM;
- *_s = s;
+ *_s = path_simplify(s, true);
return 0;
}
}
if (synthesize_nobody() &&
u == UID_NOBODY) {
- s = strdup("/sbin/nologin");
+ s = strdup(NOLOGIN);
if (!s)
return -ENOMEM;
errno = 0;
p = getpwuid(u);
if (!p)
- return errno > 0 ? -errno : -ESRCH;
+ return errno_or_else(ESRCH);
- if (!path_is_absolute(p->pw_shell))
+ if (!path_is_valid(p->pw_shell) ||
+ !path_is_absolute(p->pw_shell))
return -EINVAL;
s = strdup(p->pw_shell);
if (!s)
return -ENOMEM;
- *_s = s;
+ *_s = path_simplify(s, true);
return 0;
}
return fd;
}
-bool valid_user_group_name(const char *u) {
+bool valid_user_group_name_full(const char *u, bool strict) {
const char *i;
long sz;
/* Checks if the specified name is a valid user/group name. Also see POSIX IEEE Std 1003.1-2008, 2016 Edition,
* 3.437. We are a bit stricter here however. Specifically we deviate from POSIX rules:
*
- * - We don't allow any dots (this would break chown syntax which permits dots as user/group name separator)
* - We require that names fit into the appropriate utmp field
* - We don't allow empty user names
+ * - No dots or digits in the first character
+ *
+ * If strict==true, additionally:
+ * - We don't allow any dots (this conflicts with chown syntax which permits dots as user/group name separator)
*
* Note that other systems are even more restrictive, and don't permit underscores or uppercase characters.
*/
u[0] != '_')
return false;
+ bool warned = false;
+
for (i = u+1; *i; i++) {
- if (!(*i >= 'a' && *i <= 'z') &&
- !(*i >= 'A' && *i <= 'Z') &&
- !(*i >= '0' && *i <= '9') &&
- !IN_SET(*i, '_', '-'))
- return false;
+ if (((*i >= 'a' && *i <= 'z') ||
+ (*i >= 'A' && *i <= 'Z') ||
+ (*i >= '0' && *i <= '9') ||
+ IN_SET(*i, '_', '-')))
+ continue;
+
+ if (*i == '.' && !strict) {
+ if (!warned) {
+ log_warning("Bad user or group name \"%s\", accepting for compatibility.", u);
+ warned = true;
+ }
+
+ continue;
+ }
+
+ return false;
}
sz = sysconf(_SC_LOGIN_NAME_MAX);
return true;
}
-bool valid_user_group_name_or_id(const char *u) {
+bool valid_user_group_name_or_id_full(const char *u, bool strict) {
- /* Similar as above, but is also fine with numeric UID/GID specifications, as long as they are in the right
- * range, and not the invalid user ids. */
+ /* Similar as above, but is also fine with numeric UID/GID specifications, as long as they are in the
+ * right range, and not the invalid user ids. */
if (isempty(u))
return false;
- if (valid_user_group_name(u))
+ if (valid_user_group_name_full(u, strict))
return true;
return parse_uid(u, NULL) >= 0;
}
bool synthesize_nobody(void) {
-
-#ifdef NOLEGACY
- return true;
-#else
/* Returns true when we shall synthesize the "nobody" user (which we do by default). This can be turned off by
* touching /etc/systemd/dont-synthesize-nobody in order to provide upgrade compatibility with legacy systems
* that used the "nobody" user name and group name for other UIDs/GIDs than 65534.
cache = access("/etc/systemd/dont-synthesize-nobody", F_OK) < 0;
return cache;
-#endif
}
int putpwent_sane(const struct passwd *pw, FILE *stream) {
errno = 0;
if (putpwent(pw, stream) != 0)
- return errno > 0 ? -errno : -EIO;
+ return errno_or_else(EIO);
return 0;
}
errno = 0;
if (putspent(sp, stream) != 0)
- return errno > 0 ? -errno : -EIO;
+ return errno_or_else(EIO);
return 0;
}
errno = 0;
if (putgrent(gr, stream) != 0)
- return errno > 0 ? -errno : -EIO;
+ return errno_or_else(EIO);
return 0;
}
errno = 0;
if (putsgent(sg, stream) != 0)
- return errno > 0 ? -errno : -EIO;
+ return errno_or_else(EIO);
return 0;
}
errno = 0;
p = fgetpwent(stream);
- if (p == NULL) {
- if (errno == ENOENT)
- return false;
- return errno > 0 ? -errno : -EIO;
- }
+ if (!p && errno != ENOENT)
+ return errno_or_else(EIO);
*pw = p;
- return true;
+ return !!p;
}
int fgetspent_sane(FILE *stream, struct spwd **sp) {
errno = 0;
s = fgetspent(stream);
- if (s == NULL) {
- if (errno == ENOENT)
- return false;
- return errno > 0 ? -errno : -EIO;
- }
+ if (!s && errno != ENOENT)
+ return errno_or_else(EIO);
*sp = s;
- return true;
+ return !!s;
}
int fgetgrent_sane(FILE *stream, struct group **gr) {
errno = 0;
g = fgetgrent(stream);
- if (g == NULL) {
- if (errno == ENOENT)
- return false;
- return errno > 0 ? -errno : -EIO;
- }
+ if (!g && errno != ENOENT)
+ return errno_or_else(EIO);
*gr = g;
- return true;
+ return !!g;
}
#if ENABLE_GSHADOW
errno = 0;
s = fgetsgent(stream);
- if (s == NULL) {
- if (errno == ENOENT)
- return false;
- return errno > 0 ? -errno : -EIO;
- }
+ if (!s && errno != ENOENT)
+ return errno_or_else(EIO);
*sg = s;
- return true;
+ return !!s;
}
#endif
+
+int make_salt(char **ret) {
+ static const char table[] =
+ "abcdefghijklmnopqrstuvwxyz"
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+ "0123456789"
+ "./";
+
+ uint8_t raw[16];
+ char *salt, *j;
+ size_t i;
+ int r;
+
+ /* This is a bit like crypt_gensalt_ra(), but doesn't require libcrypt, and doesn't do anything but
+ * SHA512, i.e. is legacy-free and minimizes our deps. */
+
+ assert_cc(sizeof(table) == 64U + 1U);
+
+ /* Insist on the best randomness by setting RANDOM_BLOCK, this is about keeping passwords secret after all. */
+ r = genuine_random_bytes(raw, sizeof(raw), RANDOM_BLOCK);
+ if (r < 0)
+ return r;
+
+ salt = new(char, 3+sizeof(raw)+1+1);
+ if (!salt)
+ return -ENOMEM;
+
+ /* We only bother with SHA512 hashed passwords, the rest is legacy, and we don't do legacy. */
+ j = stpcpy(salt, "$6$");
+ for (i = 0; i < sizeof(raw); i++)
+ j[i] = table[raw[i] & 63];
+ j[i++] = '$';
+ j[i] = 0;
+
+ *ret = salt;
+ return 0;
+}