sysusers.c revision 12ba2c44dde4d7cfc0e531dbc3cbd0581c323637
/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
/***
This file is part of systemd.
Copyright 2014 Lennart Poettering
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 <pwd.h>
#include <grp.h>
#include <shadow.h>
#include <gshadow.h>
#include <getopt.h>
#include <utmp.h>
#include "util.h"
#include "hashmap.h"
#include "specifier.h"
#include "path-util.h"
#include "build.h"
#include "strv.h"
#include "conf-files.h"
#include "copy.h"
#include "utf8.h"
#include "fileio-label.h"
#include "uid-range.h"
#include "selinux-util.h"
#include "formats-util.h"
typedef enum ItemType {
ADD_USER = 'u',
ADD_GROUP = 'g',
ADD_MEMBER = 'm',
ADD_RANGE = 'r',
} ItemType;
typedef struct Item {
char *name;
char *uid_path;
char *gid_path;
char *description;
char *home;
bool gid_set:1;
bool uid_set:1;
bool todo_user:1;
bool todo_group:1;
} Item;
static unsigned n_uid_range = 0;
static int load_user_database(void) {
const char *passwd_path;
int r;
if (!f)
if (r < 0)
return r;
if (r < 0)
return r;
errno = 0;
char *n;
int k, q;
if (!n)
return -ENOMEM;
if (k < 0 && k != -EEXIST) {
free(n);
return k;
}
if (q < 0 && q != -EEXIST) {
if (k < 0)
free(n);
return q;
}
if (q < 0 && k < 0)
free(n);
errno = 0;
}
return -errno;
return 0;
}
static int load_group_database(void) {
const char *group_path;
int r;
if (!f)
if (r < 0)
return r;
if (r < 0)
return r;
errno = 0;
char *n;
int k, q;
if (!n)
return -ENOMEM;
if (k < 0 && k != -EEXIST) {
free(n);
return k;
}
if (q < 0 && q != -EEXIST) {
if (k < 0)
free(n);
return q;
}
if (q < 0 && k < 0)
free(n);
errno = 0;
}
return -errno;
return 0;
}
static int make_backup(const char *target, const char *x) {
int r;
if (src < 0) {
return 0;
return -errno;
}
return -errno;
if (r < 0)
return r;
if (r < 0)
goto fail;
/* Don't fail on chmod() or chown(). If it stays owned by us
/* Copy over the access mask */
goto fail;
return 0;
fail:
return r;
}
char **a;
if (a) {
_cleanup_strv_free_ char **l = NULL;
bool added = false;
char **i;
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;
return 1;
}
}
errno = 0;
return 0;
}
char **a;
if (a) {
_cleanup_strv_free_ char **l = NULL;
bool added = false;
char **i;
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 sgrp t;
strv_uniq(l);
strv_sort(l);
t = *sg;
t.sg_mem = l;
errno = 0;
return 1;
}
}
errno = 0;
return 0;
}
return -errno;
return -errno;
return -errno;
return 0;
}
static int write_files(void) {
bool group_changed = false;
Item *i;
int r;
/* First we update the actual group list file */
if (r < 0)
goto finish;
if (original) {
if (r < 0)
goto finish;
errno = 0;
/* Safety checks against name and GID
* collisions. Normally, this should
* be unnecessary, but given that we
* look at the entries anyway here,
* let's make an extra verification
* step that we don't generate
* duplicate entries. */
if (i && i->todo_group) {
r = -EEXIST;
goto finish;
}
r = -EEXIST;
goto finish;
}
if (r < 0)
goto finish;
if (r > 0)
group_changed = true;
errno = 0;
}
r = -errno;
goto finish;
}
r = -errno;
goto finish;
r = -errno;
goto finish;
}
struct group n = {
.gr_passwd = (char*) "x",
};
r = putgrent_with_members(&n, group);
if (r < 0)
goto finish;
group_changed = true;
}
r = fflush_and_check(group);
if (r < 0)
goto finish;
if (original) {
}
/* OK, now also update the shadow file for the group list */
if (r < 0)
goto finish;
if (original) {
if (r < 0)
goto finish;
errno = 0;
if (i && i->todo_group) {
r = -EEXIST;
goto finish;
}
if (r < 0)
goto finish;
if (r > 0)
group_changed = true;
errno = 0;
}
r = -errno;
goto finish;
}
r = -errno;
goto finish;
r = -errno;
goto finish;
}
struct sgrp n = {
.sg_passwd = (char*) "!!",
};
r = putsgent_with_members(&n, gshadow);
if (r < 0)
goto finish;
group_changed = true;
}
r = fflush_and_check(gshadow);
if (r < 0)
goto finish;
}
if (hashmap_size(todo_uids) > 0) {
long lstchg;
/* First we update the user database itself */
if (r < 0)
goto finish;
if (original) {
if (r < 0)
goto finish;
errno = 0;
if (i && i->todo_user) {
r = -EEXIST;
goto finish;
}
r = -EEXIST;
goto finish;
}
errno = 0;
goto finish;
}
errno = 0;
}
r = -errno;
goto finish;
}
r = -errno;
goto finish;
r = -errno;
goto finish;
}
struct passwd n = {
.pw_gecos = i->description,
/* "x" means the password is stored in
* the shadow file */
.pw_passwd = (char*) "x",
/* We default to the root directory as home */
/* Initialize the shell to nologin,
* with one exception: for root we
* patch in something special */
};
errno = 0;
goto finish;
}
}
r = fflush_and_check(passwd);
if (r < 0)
goto finish;
if (original) {
}
/* The we update the shadow database */
if (r < 0)
goto finish;
if (original) {
if (r < 0)
goto finish;
errno = 0;
if (i && i->todo_user) {
/* we will update the existing entry */
* safely remove the item from the todo set */
i->todo_user = false;
}
errno = 0;
goto finish;
}
errno = 0;
}
r = -errno;
goto finish;
}
r = -errno;
goto finish;
r = -errno;
goto finish;
}
struct spwd n = {
.sp_pwdp = (char*) "!!",
.sp_min = -1,
.sp_max = -1,
.sp_warn = -1,
.sp_inact = -1,
.sp_expire = -1,
};
errno = 0;
goto finish;
}
}
r = fflush_and_check(shadow);
if (r < 0)
goto finish;
}
/* Make a backup of the old files */
if (group_changed) {
if (group) {
if (r < 0)
goto finish;
}
if (gshadow) {
if (r < 0)
goto finish;
}
}
if (passwd) {
if (r < 0)
goto finish;
}
if (shadow) {
if (r < 0)
goto finish;
}
/* And make the new files count */
if (group_changed) {
if (group) {
r = -errno;
goto finish;
}
}
if (gshadow) {
r = -errno;
goto finish;
}
}
}
if (passwd) {
r = -errno;
goto finish;
}
}
if (shadow) {
r = -errno;
goto finish;
}
}
r = 0;
if (passwd_tmp)
if (shadow_tmp)
if (group_tmp)
if (gshadow_tmp)
return r;
}
struct passwd *p;
struct group *g;
const char *n;
Item *i;
/* Let's see if we already have assigned the UID a second time */
return 0;
/* Try to avoid using uids that are already used by a group
* that doesn't have the same name as our new user. */
return 0;
/* Let's check the files directly */
return 0;
return 0;
/* Let's also check via NSS, to avoid UID clashes over LDAP and such, just in case */
if (!arg_root) {
errno = 0;
if (p)
return 0;
return -errno;
errno = 0;
if (g) {
return 0;
return -errno;
}
return 1;
}
const char *fix;
return -errno;
return 0;
}
assert(i);
/* First, try to get the gid directly */
found_gid = true;
}
/* Then, try to get the uid directly */
&& i->uid_path
found_uid = true;
/* If we need the gid, but had no success yet, also derive it from the uid path */
found_gid = true;
}
}
/* If that didn't work yet, then let's reuse the gid as uid */
if (found_gid) {
found_uid = true;
found_uid = true;
}
}
if (_uid) {
if (!found_uid)
return 0;
}
if (_gid) {
if (!found_gid)
return 0;
}
return 1;
}
void *z;
int r;
assert(i);
/* Check the database directly */
if (z) {
i->uid = PTR_TO_UID(z);
i->uid_set = true;
return 0;
}
if (!arg_root) {
struct passwd *p;
/* Also check NSS */
errno = 0;
if (p) {
i->uid_set = true;
if (r < 0)
return log_oom();
return 0;
}
}
/* Try to use the suggested numeric uid */
if (i->uid_set) {
if (r < 0)
if (r == 0) {
i->uid_set = false;
}
}
/* If that didn't work, try to read it from the specified path */
if (!i->uid_set) {
uid_t c;
if (read_id_from_file(i, &c, NULL) > 0) {
else {
if (r < 0)
else if (r > 0) {
i->uid = c;
i->uid_set = true;
} else
}
}
}
/* Otherwise try to reuse the group ID */
if (r < 0)
if (r > 0) {
i->uid_set = true;
}
}
/* And if that didn't work either, let's try to find a free one */
if (!i->uid_set) {
for (;;) {
if (r < 0) {
return r;
}
if (r < 0)
else if (r > 0)
break;
}
i->uid_set = true;
i->uid = search_uid;
}
if (r < 0)
return log_oom();
if (r < 0)
return log_oom();
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;
}
struct group *g;
struct passwd *p;
return 0;
/* Avoid reusing gids that are already used by a different user */
return 0;
return 0;
return 0;
if (!arg_root) {
errno = 0;
if (g)
return 0;
return -errno;
errno = 0;
if (p)
return 0;
return -errno;
}
return 1;
}
void *z;
int r;
assert(i);
/* Check the database directly */
if (z) {
i->gid = PTR_TO_GID(z);
i->gid_set = true;
return 0;
}
/* Also check NSS */
if (!arg_root) {
struct group *g;
errno = 0;
if (g) {
i->gid_set = true;
return 0;
}
}
/* Try to use the suggested numeric gid */
if (i->gid_set) {
if (r < 0)
if (r == 0) {
i->gid_set = false;
}
}
/* Try to reuse the numeric uid, if there's one */
if (r < 0)
if (r > 0) {
i->gid_set = true;
}
}
/* If that didn't work, try to read it from the specified path */
if (!i->gid_set) {
gid_t c;
if (read_id_from_file(i, NULL, &c) > 0) {
else {
r = gid_is_ok(c);
if (r < 0)
else if (r > 0) {
i->gid = c;
i->gid_set = true;
} else
}
}
}
/* And if that didn't work either, let's try to find a free one */
if (!i->gid_set) {
for (;;) {
/* We look for new GIDs in the UID pool! */
if (r < 0) {
return r;
}
r = gid_is_ok(search_uid);
if (r < 0)
else if (r > 0)
break;
}
i->gid_set = true;
i->gid = search_uid;
}
if (r < 0)
return log_oom();
if (r < 0)
return log_oom();
i->todo_group = true;
return 0;
}
static int process_item(Item *i) {
int r;
assert(i);
switch (i->type) {
case ADD_USER:
r = add_group(i);
if (r < 0)
return r;
return add_user(i);
case ADD_GROUP: {
Item *j;
if (j) {
/* There's already user to be created for this
* name, let's process that in one step */
if (i->gid_set) {
j->gid_set = true;
}
if (i->gid_path) {
if (r < 0)
return log_oom();
}
return 0;
}
return add_group(i);
}
default:
assert_not_reached("Unknown item type");
}
}
if (!i)
return;
free(i->description);
free(i);
}
static int add_implicit(void) {
char *g, **l;
int r;
/* Implicitly create additional users and groups, if they were listed in "m" lines */
Item *i;
char **m;
i = hashmap_get(groups, g);
if (!i) {
if (r < 0)
return log_oom();
if (!j)
return log_oom();
if (!j->name)
return log_oom();
if (r < 0)
return log_oom();
j = NULL;
}
STRV_FOREACH(m, l) {
i = hashmap_get(users, *m);
if (!i) {
if (r < 0)
return log_oom();
if (!j)
return log_oom();
if (!j->name)
return log_oom();
if (r < 0)
return log_oom();
j = NULL;
}
}
}
return 0;
}
assert(a);
assert(b);
return false;
return false;
return false;
return false;
return false;
return false;
return false;
return false;
return false;
return false;
return true;
}
static bool valid_user_group_name(const char *u) {
const char *i;
long sz;
if (isempty(u))
return false;
if (!(u[0] >= 'a' && u[0] <= 'z') &&
!(u[0] >= 'A' && u[0] <= 'Z') &&
u[0] != '_')
return false;
for (i = u+1; *i; i++) {
if (!(*i >= 'a' && *i <= 'z') &&
!(*i >= 'A' && *i <= 'Z') &&
!(*i >= '0' && *i <= '9') &&
*i != '_' &&
*i != '-')
return false;
}
return false;
return false;
return true;
}
static bool valid_gecos(const char *d) {
if (!d)
return false;
if (!utf8_is_valid(d))
return false;
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;
}
static bool valid_home(const char *p) {
if (isempty(p))
return false;
if (!utf8_is_valid(p))
return false;
if (string_has_cc(p, NULL))
return false;
if (!path_is_absolute(p))
return false;
if (!path_is_safe(p))
return false;
/* Colons are used as field separators, and hence not OK */
if (strchr(p, ':'))
return false;
return true;
}
static const Specifier specifier_table[] = {
{}
};
_cleanup_free_ char *action = NULL, *name = NULL, *id = NULL, *resolved_name = NULL, *resolved_id = NULL, *description = NULL, *home = NULL;
Hashmap *h;
int r;
const char *p;
/* Parse columns */
p = buffer;
if (r < 0) {
return r;
}
if (r < 2) {
return -EINVAL;
}
if (!isempty(p)) {
return -EINVAL;
}
/* Verify action */
return -EINVAL;
}
return -EBADMSG;
}
/* Verify name */
if (name) {
if (r < 0) {
return r;
}
if (!valid_user_group_name(resolved_name)) {
return -EINVAL;
}
}
/* Verify id */
if (id) {
if (r < 0) {
return r;
}
}
/* Verify description */
if (description) {
if (!valid_gecos(description)) {
return -EINVAL;
}
}
/* Verify home */
if (home) {
if (!valid_home(home)) {
return -EINVAL;
}
}
switch (action[0]) {
case ADD_RANGE:
if (resolved_name) {
return -EINVAL;
}
if (!resolved_id) {
return -EINVAL;
}
if (description) {
return -EINVAL;
}
if (home) {
return -EINVAL;
}
if (r < 0) {
return -EINVAL;
}
return 0;
case ADD_MEMBER: {
char **l;
/* Try to extend an existing member or group item */
if (!name) {
return -EINVAL;
}
if (!resolved_id) {
return -EINVAL;
}
if (!valid_user_group_name(resolved_id)) {
return -EINVAL;
}
if (description) {
return -EINVAL;
}
if (home) {
return -EINVAL;
}
if (r < 0)
return log_oom();
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();
} 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;
if (r < 0) {
free(l);
return log_oom();
}
}
return 0;
}
case ADD_USER:
if (!name) {
return -EINVAL;
}
if (r < 0)
return log_oom();
if (!i)
return log_oom();
if (resolved_id) {
if (path_is_absolute(resolved_id)) {
i->uid_path = resolved_id;
resolved_id = NULL;
} else {
if (r < 0) {
return -EBADMSG;
}
i->uid_set = true;
}
}
i->description = description;
description = NULL;
h = users;
break;
case ADD_GROUP:
if (!name) {
return -EINVAL;
}
if (description) {
return -EINVAL;
}
if (home) {
return -EINVAL;
}
if (r < 0)
return log_oom();
if (!i)
return log_oom();
if (resolved_id) {
if (path_is_absolute(resolved_id)) {
i->gid_path = resolved_id;
resolved_id = NULL;
} else {
if (r < 0) {
return -EBADMSG;
}
i->gid_set = true;
}
}
h = groups;
break;
default:
return -EBADMSG;
}
i->name = resolved_name;
if (existing) {
/* Two identical items are fine */
if (!item_equal(existing, i))
return 0;
}
r = hashmap_put(h, i->name, i);
if (r < 0)
return log_oom();
i = NULL;
return 0;
}
unsigned v = 0;
int r = 0;
f = stdin;
else {
if (r < 0) {
if (ignore_enoent && r == -ENOENT)
return 0;
}
f = rf;
}
FOREACH_LINE(line, f, break) {
char *l;
int k;
v++;
if (*l == '#' || *l == 0)
continue;
k = parse_line(fn, v, l);
if (k < 0 && r == 0)
r = k;
}
if (ferror(f)) {
if (r == 0)
r = -EIO;
}
return r;
}
char *name;
for (;;) {
if (!name)
break;
}
}
static void help(void) {
printf("%s [OPTIONS...] [CONFIGURATION FILE...]\n\n"
"Creates system user accounts.\n\n"
" -h --help Show this help\n"
" --version Show package version\n"
" --root=PATH Operate on an alternate filesystem root\n"
}
enum {
ARG_VERSION = 0x100,
};
{}
};
int c;
switch (c) {
case 'h':
help();
return 0;
case ARG_VERSION:
return 0;
case ARG_ROOT:
if (!arg_root)
return log_oom();
break;
case '?':
return -EINVAL;
default:
assert_not_reached("Unhandled option");
}
return 1;
}
int r, k;
Item *i;
char *n;
if (r <= 0)
goto finish;
log_open();
umask(0022);
r = mac_selinux_init(NULL);
if (r < 0) {
log_error_errno(r, "SELinux setup failed: %m");
goto finish;
}
int j;
k = read_config_file(argv[j], false);
if (k < 0 && r == 0)
r = k;
}
} else {
char **f;
if (r < 0) {
log_error_errno(r, "Failed to enumerate sysusers.d files: %m");
goto finish;
}
STRV_FOREACH(f, files) {
k = read_config_file(*f, true);
if (k < 0 && r == 0)
r = k;
}
}
if (!uid_range) {
/* Default to default range of 1..SYSTEMD_UID_MAX */
if (r < 0) {
log_oom();
goto finish;
}
}
r = add_implicit();
if (r < 0)
goto finish;
if (lock < 0) {
goto finish;
}
r = load_user_database();
if (r < 0) {
log_error_errno(r, "Failed to load user database: %m");
goto finish;
}
r = load_group_database();
if (r < 0) {
log_error_errno(r, "Failed to read group database: %m");
goto finish;
}
process_item(i);
process_item(i);
r = write_files();
if (r < 0)
log_error_errno(r, "Failed to write files: %m");
while ((i = hashmap_steal_first(groups)))
item_free(i);
while ((i = hashmap_steal_first(users)))
item_free(i);
while ((n = hashmap_first_key(members))) {
free(n);
}
return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
}