mirror of
https://github.com/systemd/systemd
synced 2024-10-06 16:21:34 +00:00
sysusers: add new line type "m" to add users as members to groups
This commit is contained in:
parent
aaf86f95a0
commit
a12b0cc34d
|
@ -86,7 +86,8 @@
|
|||
<programlisting># Type Name ID GECOS
|
||||
u httpd 440 "HTTP User"
|
||||
u authd /usr/bin/authd "Authorization user"
|
||||
g input - -</programlisting>
|
||||
g input - -
|
||||
m authd input</programlisting>
|
||||
|
||||
<refsect2>
|
||||
<title>Type</title>
|
||||
|
@ -125,6 +126,15 @@ g input - -</programlisting>
|
|||
created with no password
|
||||
set.</para></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><varname>m</varname></term>
|
||||
<listitem><para>Add a user to
|
||||
a group. If the user or group
|
||||
are not existing yet, they
|
||||
will be implicitly
|
||||
created.</para></listitem>
|
||||
</varlistentry>
|
||||
</variablelist>
|
||||
</refsect2>
|
||||
|
||||
|
@ -141,13 +151,18 @@ g input - -</programlisting>
|
|||
scheme to guarantee this is by prefixing all
|
||||
system and group names with the underscore,
|
||||
and avoiding too generic names.</para>
|
||||
|
||||
<para>For <varname>m</varname> lines this
|
||||
field should contain the user name to add to a
|
||||
group.</para>
|
||||
</refsect2>
|
||||
|
||||
<refsect2>
|
||||
<title>ID</title>
|
||||
|
||||
<para>The numeric 32bit UID or GID of the
|
||||
user/group. Do not use IDs 65535 or
|
||||
<para>For <varname>u</varname> and
|
||||
<varname>g</varname> the numeric 32bit UID or
|
||||
GID of the user/group. Do not use IDs 65535 or
|
||||
4294967295, as they have special placeholder
|
||||
meanings. Specify "-" for automatic UID/GID
|
||||
allocation for the user or
|
||||
|
@ -157,6 +172,10 @@ g input - -</programlisting>
|
|||
useful to create users whose UID/GID match the
|
||||
owners of pre-existing files (such as SUID or
|
||||
SGID binaries).</para>
|
||||
|
||||
<para>For <varname>m</varname> lines this
|
||||
field should contain the group name to add to
|
||||
a user to.</para>
|
||||
</refsect2>
|
||||
|
||||
<refsect2>
|
||||
|
@ -165,6 +184,10 @@ g input - -</programlisting>
|
|||
<para>A short, descriptive string for users to
|
||||
be created, enclosed in quotation marks. Note
|
||||
that this field may not contain colons.</para>
|
||||
|
||||
<para>Only applies to lines of type
|
||||
<varname>u</varname> and should otherwise be
|
||||
left unset.</para>
|
||||
</refsect2>
|
||||
|
||||
</refsect1>
|
||||
|
|
|
@ -38,6 +38,7 @@
|
|||
typedef enum ItemType {
|
||||
ADD_USER = 'u',
|
||||
ADD_GROUP = 'g',
|
||||
ADD_MEMBER = 'm',
|
||||
} ItemType;
|
||||
typedef struct Item {
|
||||
ItemType type;
|
||||
|
@ -69,6 +70,7 @@ static const char conf_file_dirs[] =
|
|||
|
||||
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;
|
||||
|
@ -240,11 +242,62 @@ 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;
|
||||
|
@ -252,7 +305,7 @@ static int write_files(void) {
|
|||
/* 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");
|
||||
|
@ -290,10 +343,12 @@ static int write_files(void) {
|
|||
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;
|
||||
}
|
||||
|
@ -314,10 +369,11 @@ static int write_files(void) {
|
|||
.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);
|
||||
|
@ -356,8 +412,9 @@ static int write_files(void) {
|
|||
goto finish;
|
||||
}
|
||||
|
||||
errno = 0;
|
||||
if (putpwent(pw, passwd) < 0) {
|
||||
r = -errno;
|
||||
r = errno ? -errno : -EIO;
|
||||
goto finish;
|
||||
}
|
||||
|
||||
|
@ -393,8 +450,9 @@ static int write_files(void) {
|
|||
n.pw_dir = (char*) "/";
|
||||
}
|
||||
|
||||
if (putpwent(&n, passwd) < 0) {
|
||||
r = -r;
|
||||
errno = 0;
|
||||
if (putpwent(&n, passwd) != 0) {
|
||||
r = errno ? -errno : -EIO;
|
||||
goto finish;
|
||||
}
|
||||
}
|
||||
|
@ -405,7 +463,7 @@ static int write_files(void) {
|
|||
}
|
||||
|
||||
/* Make a backup of the old files */
|
||||
if (group) {
|
||||
if (group && group_changed) {
|
||||
r = make_backup(group_path);
|
||||
if (r < 0)
|
||||
goto finish;
|
||||
|
@ -418,7 +476,7 @@ static int write_files(void) {
|
|||
}
|
||||
|
||||
/* And make the new files count */
|
||||
if (group) {
|
||||
if (group && group_changed) {
|
||||
if (rename(group_tmp, group_path) < 0) {
|
||||
r = -errno;
|
||||
goto finish;
|
||||
|
@ -438,15 +496,13 @@ static int write_files(void) {
|
|||
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;
|
||||
}
|
||||
|
@ -890,9 +946,10 @@ static int process_item(Item *i) {
|
|||
|
||||
return add_group(i);
|
||||
}
|
||||
}
|
||||
|
||||
assert_not_reached("Unknown item type");
|
||||
default:
|
||||
assert_not_reached("Unknown item type");
|
||||
}
|
||||
}
|
||||
|
||||
static void item_free(Item *i) {
|
||||
|
@ -909,6 +966,74 @@ 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);
|
||||
|
@ -994,7 +1119,7 @@ static int parse_line(const char *fname, unsigned line, const char *buffer) {
|
|||
{}
|
||||
};
|
||||
|
||||
_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;
|
||||
|
@ -1020,31 +1145,124 @@ static int parse_line(const char *fname, unsigned line, const char *buffer) {
|
|||
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)) {
|
||||
|
||||
if (STR_IN_SET(buffer + n, "", "-"))
|
||||
n = -1;
|
||||
}
|
||||
|
||||
switch (action[0]) {
|
||||
|
||||
case ADD_MEMBER: {
|
||||
_cleanup_free_ char *resolved_id = NULL;
|
||||
char **l;
|
||||
|
||||
r = hashmap_ensure_allocated(&members, string_hash_func, string_compare_func);
|
||||
if (r < 0)
|
||||
return log_oom();
|
||||
|
||||
/* Try to extend an existing member or group item */
|
||||
|
||||
if (!id || streq(id, "-")) {
|
||||
log_error("[%s:%u] Lines of type 'm' require a group name in the third field.", fname, line);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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 {
|
||||
/* 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 = hashmap_put(members, resolved_id, l);
|
||||
if (r < 0) {
|
||||
free(l);
|
||||
return log_oom();
|
||||
}
|
||||
|
||||
resolved_id = resolved_name = NULL;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
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();
|
||||
|
@ -1054,56 +1272,51 @@ static int parse_line(const char *fname, unsigned line, const char *buffer) {
|
|||
return -EINVAL;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (id && !streq(id, "-")) {
|
||||
|
||||
if (path_is_absolute(id)) {
|
||||
char *p;
|
||||
|
||||
p = strdup(id);
|
||||
if (!p)
|
||||
return log_oom();
|
||||
|
||||
path_kill_slashes(p);
|
||||
|
||||
if (i->type == ADD_USER)
|
||||
i->uid_path = p;
|
||||
else
|
||||
i->gid_path = p;
|
||||
|
||||
} 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;
|
||||
}
|
||||
|
||||
i->uid_set = true;
|
||||
|
||||
} else {
|
||||
assert(i->type == ADD_GROUP);
|
||||
|
||||
r = parse_gid(id, &i->gid);
|
||||
if (r < 0) {
|
||||
log_error("Failed to parse GID: %s", id);
|
||||
return -EBADMSG;
|
||||
}
|
||||
|
||||
i->gid_set = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (i->type == ADD_USER) {
|
||||
r = hashmap_ensure_allocated(&users, string_hash_func, string_compare_func);
|
||||
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;
|
||||
}
|
||||
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) {
|
||||
|
@ -1116,10 +1329,8 @@ static int parse_line(const char *fname, unsigned line, const char *buffer) {
|
|||
}
|
||||
|
||||
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;
|
||||
|
@ -1292,6 +1503,7 @@ int main(int argc, char *argv[]) {
|
|||
Iterator iterator;
|
||||
int r, k;
|
||||
Item *i;
|
||||
char *n;
|
||||
|
||||
r = parse_argv(argc, argv);
|
||||
if (r <= 0)
|
||||
|
@ -1330,6 +1542,10 @@ int main(int argc, char *argv[]) {
|
|||
}
|
||||
}
|
||||
|
||||
r = add_implicit();
|
||||
if (r < 0)
|
||||
goto finish;
|
||||
|
||||
lock = take_lock();
|
||||
if (lock < 0) {
|
||||
log_error("Failed to take lock: %s", strerror(-lock));
|
||||
|
@ -1365,8 +1581,14 @@ 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);
|
||||
|
||||
|
|
Loading…
Reference in a new issue