#include <grp.h>
#include <shadow.h>
#include <getopt.h>
+#include <utmp.h>
#include "util.h"
#include "hashmap.h"
#include "conf-files.h"
#include "copy.h"
#include "utf8.h"
+#include "label.h"
+#include "fileio-label.h"
typedef enum ItemType {
ADD_USER = 'u',
ADD_GROUP = 'g',
+ ADD_MEMBER = 'm',
} ItemType;
typedef struct Item {
ItemType type;
bool gid_set:1;
bool uid_set:1;
- bool todo:1;
+ bool todo_user:1;
+ bool todo_group:1;
} Item;
static char *arg_root = NULL;
static const char conf_file_dirs[] =
+ "/etc/sysusers.d\0"
+ "/run/sysusers.d\0"
"/usr/local/lib/sysusers.d\0"
"/usr/lib/sysusers.d\0"
#ifdef HAVE_SPLIT_USR
static Hashmap *users = NULL, *groups = NULL;
static Hashmap *todo_uids = NULL, *todo_gids = NULL;
+static Hashmap *members = NULL;
static Hashmap *database_uid = NULL, *database_user = NULL;
static Hashmap *database_gid = NULL, *database_group = NULL;
if (dst < 0)
return dst;
- r = copy_bytes(src, dst);
+ r = copy_bytes(src, dst, (off_t) -1);
if (r < 0)
goto fail;
return r;
}
+static int putgrent_with_members(const struct group *gr, FILE *group) {
+ char **a;
+
+ assert(gr);
+ assert(group);
+
+ a = hashmap_get(members, gr->gr_name);
+ if (a) {
+ _cleanup_strv_free_ char **l = NULL;
+ bool added = false;
+ char **i;
+
+ l = strv_copy(gr->gr_mem);
+ if (!l)
+ return -ENOMEM;
+
+ STRV_FOREACH(i, a) {
+ if (strv_find(l, *i))
+ continue;
+
+ if (strv_extend(&l, *i) < 0)
+ return -ENOMEM;
+
+ added = true;
+ }
+
+ if (added) {
+ struct group t;
+
+ strv_uniq(l);
+ strv_sort(l);
+
+ t = *gr;
+ t.gr_mem = l;
+
+ errno = 0;
+ if (putgrent(&t, group) != 0)
+ return errno ? -errno : -EIO;
+
+ return 1;
+ }
+ }
+
+ errno = 0;
+ if (putgrent(gr, group) != 0)
+ return errno ? -errno : -EIO;
+
+ return 0;
+}
+
static int write_files(void) {
_cleanup_fclose_ FILE *passwd = NULL, *group = NULL;
_cleanup_free_ char *passwd_tmp = NULL, *group_tmp = NULL;
const char *passwd_path = NULL, *group_path = NULL;
+ bool group_changed = false;
Iterator iterator;
Item *i;
int r;
/* We don't patch /etc/shadow or /etc/gshadow here, since we
* only create user accounts without passwords anyway. */
- if (hashmap_size(todo_gids) > 0) {
+ if (hashmap_size(todo_gids) > 0 || hashmap_size(members) > 0) {
_cleanup_fclose_ FILE *original = NULL;
group_path = fix_root("/etc/group");
- r = fopen_temporary(group_path, &group, &group_tmp);
+ r = fopen_temporary_label("/etc/group", group_path, &group, &group_tmp);
if (r < 0)
goto finish;
* duplicate entries. */
i = hashmap_get(groups, gr->gr_name);
- if (i && i->todo) {
+ if (i && i->todo_group) {
r = -EEXIST;
goto finish;
}
goto finish;
}
- if (putgrent(gr, group) < 0) {
- r = -errno;
+ r = putgrent_with_members(gr, group);
+ if (r < 0)
goto finish;
- }
+
+ if (r > 0)
+ group_changed = true;
errno = 0;
}
.gr_passwd = (char*) "x",
};
- if (putgrent(&n, group) < 0) {
- r = -errno;
+ r = putgrent_with_members(&n, group);
+ if (r < 0)
goto finish;
- }
+
+ group_changed = true;
}
r = fflush_and_check(group);
_cleanup_fclose_ FILE *original = NULL;
passwd_path = fix_root("/etc/passwd");
- r = fopen_temporary(passwd_path, &passwd, &passwd_tmp);
+ r = fopen_temporary_label("/etc/passwd", passwd_path, &passwd, &passwd_tmp);
if (r < 0)
goto finish;
while ((pw = fgetpwent(original))) {
i = hashmap_get(users, pw->pw_name);
- if (i && i->todo) {
+ if (i && i->todo_user) {
r = -EEXIST;
goto finish;
}
goto finish;
}
+ errno = 0;
if (putpwent(pw, passwd) < 0) {
- r = -errno;
+ r = errno ? -errno : -EIO;
goto finish;
}
n.pw_dir = (char*) "/";
}
- if (putpwent(&n, passwd) < 0) {
- r = -r;
+ errno = 0;
+ if (putpwent(&n, passwd) != 0) {
+ r = errno ? -errno : -EIO;
goto finish;
}
}
}
/* Make a backup of the old files */
- if (group) {
+ if (group && group_changed) {
r = make_backup(group_path);
if (r < 0)
goto finish;
}
/* And make the new files count */
- if (group) {
+ if (group && group_changed) {
if (rename(group_tmp, group_path) < 0) {
r = -errno;
goto finish;
passwd_tmp = NULL;
}
- return 0;
+ r = 0;
finish:
- if (r < 0) {
- if (passwd_tmp)
- unlink(passwd_tmp);
- if (group_tmp)
- unlink(group_tmp);
- }
+ if (passwd_tmp)
+ unlink(passwd_tmp);
+ if (group_tmp)
+ unlink(group_tmp);
return r;
}
p = getpwuid(uid);
if (p)
return 0;
- if (errno != 0)
+ if (!IN_SET(errno, 0, ENOENT))
return -errno;
errno = 0;
if (g) {
if (!streq(g->gr_name, name))
return 0;
- } else if (errno != 0)
+ } else if (!IN_SET(errno, 0, ENOENT))
return -errno;
}
z = hashmap_get(database_user, i->name);
if (z) {
log_debug("User %s already exists.", i->name);
- i->uid = PTR_TO_GID(z);
+ i->uid = PTR_TO_UID(z);
i->uid_set = true;
return 0;
}
i->description = strdup(p->pw_gecos);
return 0;
}
- if (errno != 0) {
+ if (!IN_SET(errno, 0, ENOENT)) {
log_error("Failed to check if user %s already exists: %m", i->name);
return -errno;
}
log_error("User %s already exists in shadow database, but not in user database.", i->name);
return -EBADMSG;
}
- if (errno != 0) {
+ if (!IN_SET(errno, 0, ENOENT)) {
log_error("Failed to check if user %s already exists in shadow database: %m", i->name);
return -errno;
}
if (r < 0)
return log_oom();
- i->todo = true;
+ i->todo_user = true;
log_info("Creating user %s (%s) with uid " UID_FMT " and gid " GID_FMT ".", i->name, strna(i->description), i->uid, i->gid);
return 0;
g = getgrgid(gid);
if (g)
return 0;
- if (errno != 0)
+ if (!IN_SET(errno, 0, ENOENT))
return -errno;
errno = 0;
p = getpwuid((uid_t) gid);
if (p)
return 0;
- if (errno != 0)
+ if (!IN_SET(errno, 0, ENOENT))
return -errno;
}
i->gid_set = true;
return 0;
}
- if (errno != 0) {
+ if (!IN_SET(errno, 0, ENOENT)) {
log_error("Failed to check if group %s already exists: %m", i->name);
return -errno;
}
if (r < 0)
return log_oom();
- i->todo = true;
+ i->todo_group = true;
log_info("Creating group %s with gid " GID_FMT ".", i->name, i->gid);
return 0;
return add_group(i);
}
- }
- assert_not_reached("Unknown item type");
+ default:
+ assert_not_reached("Unknown item type");
+ }
}
static void item_free(Item *i) {
DEFINE_TRIVIAL_CLEANUP_FUNC(Item*, item_free);
+static int add_implicit(void) {
+ char *g, **l;
+ Iterator iterator;
+ int r;
+
+ /* Implicitly create additional users and groups, if they were listed in "m" lines */
+
+ HASHMAP_FOREACH_KEY(l, g, members, iterator) {
+ Item *i;
+ char **m;
+
+ i = hashmap_get(groups, g);
+ if (!i) {
+ _cleanup_(item_freep) Item *j = NULL;
+
+ r = hashmap_ensure_allocated(&groups, string_hash_func, string_compare_func);
+ if (r < 0)
+ return log_oom();
+
+ j = new0(Item, 1);
+ if (!j)
+ return log_oom();
+
+ j->type = ADD_GROUP;
+ j->name = strdup(g);
+ if (!j->name)
+ return log_oom();
+
+ r = hashmap_put(groups, j->name, j);
+ if (r < 0)
+ return log_oom();
+
+ log_debug("Adding implicit group '%s' due to m line", j->name);
+ j = NULL;
+ }
+
+ STRV_FOREACH(m, l) {
+
+ i = hashmap_get(users, *m);
+ if (!i) {
+ _cleanup_(item_freep) Item *j = NULL;
+
+ r = hashmap_ensure_allocated(&users, string_hash_func, string_compare_func);
+ if (r < 0)
+ return log_oom();
+
+ j = new0(Item, 1);
+ if (!j)
+ return log_oom();
+
+ j->type = ADD_USER;
+ j->name = strdup(*m);
+ if (!j->name)
+ return log_oom();
+
+ r = hashmap_put(users, j->name, j);
+ if (r < 0)
+ return log_oom();
+
+ log_debug("Adding implicit user '%s' due to m line", j->name);
+ j = NULL;
+ }
+ }
+ }
+
+ return 0;
+}
+
static bool item_equal(Item *a, Item *b) {
assert(a);
assert(b);
if ((size_t) (i-u) > (size_t) sz)
return false;
+ if ((size_t) (i-u) > UT_NAMESIZE - 1)
+ return false;
+
return true;
}
if (!utf8_is_valid(d))
return false;
- if (strpbrk(d, ":\n"))
+ if (string_has_cc(d, NULL))
+ return false;
+
+ /* Colons are used as field separators, and hence not OK */
+ if (strchr(d, ':'))
return false;
return true;
{}
};
- _cleanup_free_ char *action = NULL, *name = NULL, *id = NULL;
+ _cleanup_free_ char *action = NULL, *name = NULL, *id = NULL, *resolved_name = NULL;
_cleanup_(item_freep) Item *i = NULL;
Item *existing;
Hashmap *h;
return -EINVAL;
}
- if (!IN_SET(action[0], ADD_USER, ADD_GROUP)) {
+ if (!IN_SET(action[0], ADD_USER, ADD_GROUP, ADD_MEMBER)) {
log_error("[%s:%u] Unknown command command type '%c'.", fname, line, action[0]);
return -EBADMSG;
}
- i = new0(Item, 1);
- if (!i)
- return log_oom();
-
- i->type = action[0];
-
- r = specifier_printf(name, specifier_table, NULL, &i->name);
+ r = specifier_printf(name, specifier_table, NULL, &resolved_name);
if (r < 0) {
log_error("[%s:%u] Failed to replace specifiers: %s", fname, line, name);
return r;
}
- if (!valid_user_group_name(i->name)) {
- log_error("[%s:%u] '%s' is not a valid user or group name.", fname, line, i->name);
+ if (!valid_user_group_name(resolved_name)) {
+ log_error("[%s:%u] '%s' is not a valid user or group name.", fname, line, resolved_name);
return -EINVAL;
}
if (n >= 0) {
n += strspn(buffer+n, WHITESPACE);
- if (buffer[n] != 0 && (buffer[n] != '-' || buffer[n+1] != 0)) {
- i->description = unquote(buffer+n, "\"");
- if (!i->description)
- return log_oom();
- if (!valid_gecos(i->description)) {
- log_error("[%s:%u] '%s' is not a valid GECOS field.", fname, line, i->description);
- return -EINVAL;
- }
- }
+ if (STR_IN_SET(buffer + n, "", "-"))
+ n = -1;
}
- if (id && !streq(id, "-")) {
+ switch (action[0]) {
- if (path_is_absolute(id)) {
- char *p;
+ case ADD_MEMBER: {
+ _cleanup_free_ char *resolved_id = NULL;
+ char **l;
- p = strdup(id);
- if (!p)
- return log_oom();
+ r = hashmap_ensure_allocated(&members, string_hash_func, string_compare_func);
+ if (r < 0)
+ return log_oom();
- path_kill_slashes(p);
+ /* Try to extend an existing member or group item */
- if (i->type == ADD_USER)
- i->uid_path = p;
- else
- i->gid_path = p;
+ if (!id || streq(id, "-")) {
+ log_error("[%s:%u] Lines of type 'm' require a group name in the third field.", fname, line);
+ return -EINVAL;
+ }
- } else if (i->type == ADD_USER) {
- r = parse_uid(id, &i->uid);
- if (r < 0) {
- log_error("Failed to parse UID: %s", id);
- return -EBADMSG;
- }
+ r = specifier_printf(id, specifier_table, NULL, &resolved_id);
+ if (r < 0) {
+ log_error("[%s:%u] Failed to replace specifiers: %s", fname, line, name);
+ return r;
+ }
- i->uid_set = true;
+ if (!valid_user_group_name(resolved_id)) {
+ log_error("[%s:%u] '%s' is not a valid user or group name.", fname, line, resolved_id);
+ return -EINVAL;
+ }
+
+ if (n >= 0) {
+ log_error("[%s:%u] Lines of type 'm' don't take a GECOS field.", fname, line);
+ return -EINVAL;
+ }
+
+ l = hashmap_get(members, resolved_id);
+ if (l) {
+ /* A list for this group name already exists, let's append to it */
+ r = strv_push(&l, resolved_name);
+ if (r < 0)
+ return log_oom();
+ resolved_name = NULL;
+
+ assert_se(hashmap_update(members, resolved_id, l) >= 0);
} else {
- assert(i->type == ADD_GROUP);
+ /* No list for this group name exists yet, create one */
+
+ l = new0(char *, 2);
+ if (!l)
+ return -ENOMEM;
+
+ l[0] = resolved_name;
+ l[1] = NULL;
- r = parse_gid(id, &i->gid);
+ r = hashmap_put(members, resolved_id, l);
if (r < 0) {
- log_error("Failed to parse GID: %s", id);
- return -EBADMSG;
+ free(l);
+ return log_oom();
}
- i->gid_set = true;
+ resolved_id = resolved_name = NULL;
}
+
+ return 0;
}
- if (i->type == ADD_USER) {
+ case ADD_USER:
r = hashmap_ensure_allocated(&users, string_hash_func, string_compare_func);
+ if (r < 0)
+ return log_oom();
+
+ i = new0(Item, 1);
+ if (!i)
+ return log_oom();
+
+ if (id && !streq(id, "-")) {
+
+ if (path_is_absolute(id)) {
+ i->uid_path = strdup(id);
+ if (!i->uid_path)
+ return log_oom();
+
+ path_kill_slashes(i->uid_path);
+
+ } else {
+ r = parse_uid(id, &i->uid);
+ if (r < 0) {
+ log_error("Failed to parse UID: %s", id);
+ return -EBADMSG;
+ }
+
+ i->uid_set = true;
+ }
+ }
+
+ if (n >= 0) {
+ i->description = unquote(buffer+n, "\"");
+ if (!i->description)
+ return log_oom();
+
+ if (!valid_gecos(i->description)) {
+ log_error("[%s:%u] '%s' is not a valid GECOS field.", fname, line, i->description);
+ return -EINVAL;
+ }
+ }
+
h = users;
- } else {
- assert(i->type == ADD_GROUP);
+ break;
+
+ case ADD_GROUP:
r = hashmap_ensure_allocated(&groups, string_hash_func, string_compare_func);
+ if (r < 0)
+ return log_oom();
+
+ if (n >= 0) {
+ log_error("[%s:%u] Lines of type 'g' don't take a GECOS field.", fname, line);
+ return -EINVAL;
+ }
+
+ i = new0(Item, 1);
+ if (!i)
+ return log_oom();
+
+ if (id && !streq(id, "-")) {
+
+ if (path_is_absolute(id)) {
+ i->gid_path = strdup(id);
+ if (!i->gid_path)
+ return log_oom();
+
+ path_kill_slashes(i->gid_path);
+ } else {
+ r = parse_gid(id, &i->gid);
+ if (r < 0) {
+ log_error("Failed to parse GID: %s", id);
+ return -EBADMSG;
+ }
+
+ i->gid_set = true;
+ }
+ }
+
+
h = groups;
+ break;
+ default:
+ return -EBADMSG;
}
- if (r < 0)
- return log_oom();
+
+ i->type = action[0];
+ i->name = resolved_name;
+ resolved_name = NULL;
existing = hashmap_get(h, i->name);
if (existing) {
}
r = hashmap_put(h, i->name, i);
- if (r < 0) {
- log_error("Failed to insert item %s: %s", i->name, strerror(-r));
- return r;
- }
+ if (r < 0)
+ return log_oom();
i = NULL;
return 0;
return r;
}
-static int take_lock(void) {
-
- struct flock flock = {
- .l_type = F_WRLCK,
- .l_whence = SEEK_SET,
- .l_start = 0,
- .l_len = 0,
- };
-
- const char *path;
- int fd, r;
-
- /* This is roughly the same as lckpwdf(), but not as awful. We
- * don't want to use alarm() and signals, hence we implement
- * our own trivial version of this.
- *
- * Note that shadow-utils also takes per-database locks in
- * addition to lckpwdf(). However, we don't given that they
- * are redundant as they they invoke lckpwdf() first and keep
- * it during everything they do. The per-database locks are
- * awfully racy, and thus we just won't do them. */
-
- path = fix_root("/etc/.pwd.lock");
- fd = open(path, O_WRONLY|O_CREAT|O_CLOEXEC|O_NOCTTY|O_NOFOLLOW, 0600);
- if (fd < 0)
- return -errno;
-
- r = fcntl(fd, F_SETLKW, &flock);
- if (r < 0) {
- safe_close(fd);
- return -errno;
- }
-
- return fd;
-}
-
static void free_database(Hashmap *by_name, Hashmap *by_id) {
char *name;
Iterator iterator;
int r, k;
Item *i;
+ char *n;
r = parse_argv(argc, argv);
if (r <= 0)
umask(0022);
+ label_init(NULL);
+
r = 0;
if (optind < argc) {
}
}
- lock = take_lock();
+ r = add_implicit();
+ if (r < 0)
+ goto finish;
+
+ lock = take_password_lock(arg_root);
if (lock < 0) {
log_error("Failed to take lock: %s", strerror(-lock));
goto finish;
while ((i = hashmap_steal_first(users)))
item_free(i);
+ while ((n = hashmap_first_key(members))) {
+ strv_free(hashmap_steal_first(members));
+ free(n);
+ }
+
hashmap_free(groups);
hashmap_free(users);
+ hashmap_free(members);
hashmap_free(todo_uids);
hashmap_free(todo_gids);