sysusers.c revision 38c74dad1c3d605018e61074e0b80f6b9523b1c8
/*-*- 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 <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"
typedef enum ItemType {
ADD_USER = 'u',
ADD_GROUP = 'g',
ADD_MEMBER = 'm',
} ItemType;
typedef struct Item {
char *name;
char *uid_path;
char *gid_path;
char *description;
bool gid_set:1;
bool uid_set:1;
bool todo_user:1;
bool todo_group:1;
} Item;
static const char conf_file_dirs[] =
"/usr/local/lib/sysusers.d\0"
"/usr/lib/sysusers.d\0"
#ifdef HAVE_SPLIT_USR
"/lib/sysusers.d\0"
#endif
;
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 *x) {
int r;
if (src < 0) {
return 0;
return -errno;
}
return -errno;
if (dst < 0)
return dst;
if (r < 0)
goto fail;
/* Copy over the access mask */
r = -errno;
goto fail;
}
/* Don't fail on chmod(). If it stays owned by us, then it
* isn't too bad... */
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;
}
static int write_files(void) {
bool group_changed = false;
Item *i;
int r;
* only create user accounts without passwords anyway. */
if (r < 0)
goto finish;
r = -errno;
goto finish;
}
if (original) {
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;
}
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 (hashmap_size(todo_uids) > 0) {
if (r < 0)
goto finish;
r = -errno;
goto finish;
}
if (original) {
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;
}
struct passwd n = {
.pw_gecos = i->description,
.pw_passwd = (char*) "x",
};
/* Initialize the home directory and the shell
* to nologin, with one exception: for root we
* patch in something special */
if (i->uid == 0) {
n.pw_dir = (char*) "/root";
} else {
n.pw_dir = (char*) "/";
}
errno = 0;
goto finish;
}
}
r = fflush_and_check(passwd);
if (r < 0)
goto finish;
}
/* Make a backup of the old files */
if (group && group_changed) {
r = make_backup(group_path);
if (r < 0)
goto finish;
}
if (passwd) {
r = make_backup(passwd_path);
if (r < 0)
goto finish;
}
/* And make the new files count */
if (group && group_changed) {
r = -errno;
goto finish;
}
}
if (passwd) {
r = -errno;
goto finish;
}
passwd_tmp = NULL;
}
r = 0;
if (passwd_tmp)
if (group_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;
free(i->description);
return 0;
}
return -errno;
}
/* And shadow too, just to be sure */
errno = 0;
if (sp) {
return -EBADMSG;
}
return -errno;
}
}
/* Try to use the suggested numeric uid */
if (i->uid_set) {
if (r < 0) {
return r;
}
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) {
if (c <= 0 || c > SYSTEM_UID_MAX)
else {
if (r < 0) {
return r;
} else if (r > 0) {
i->uid = c;
i->uid_set = true;
} else
}
}
}
/* Otherwise try to reuse the group ID */
if (r < 0) {
return r;
}
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 (; search_uid > 0; search_uid--) {
if (r < 0) {
return r;
} else if (r > 0)
break;
}
if (search_uid <= 0) {
return -E2BIG;
}
i->uid_set = true;
i->uid = search_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;
}
return -errno;
}
}
/* Try to use the suggested numeric gid */
if (i->gid_set) {
if (r < 0) {
return r;
}
if (r == 0) {
i->gid_set = false;
}
}
/* Try to reuse the numeric uid, if there's one */
if (r < 0) {
return r;
}
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) {
if (c <= 0 || c > SYSTEM_GID_MAX)
else {
r = gid_is_ok(c);
if (r < 0) {
return r;
} 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 (; search_gid > 0; search_gid--) {
r = gid_is_ok(search_gid);
if (r < 0) {
return r;
} else if (r > 0)
break;
}
if (search_gid <= 0) {
return -E2BIG;
}
i->gid_set = true;
i->gid = search_gid;
search_gid--;
}
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 (!j->gid_path)
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 true;
}
static bool valid_user_group_name(const char *u) {
const char *i;
long sz;
if (isempty(u) < 0)
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 (!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 const Specifier specifier_table[] = {
{}
};
Hashmap *h;
int r, n = -1;
"%ms %ms %ms %n",
&action,
&name,
&id,
&n);
if (r < 2) {
return -EIO;
}
return -EINVAL;
}
return -EBADMSG;
}
if (r < 0) {
return r;
}
if (!valid_user_group_name(resolved_name)) {
return -EINVAL;
}
if (n >= 0) {
n = -1;
}
switch (action[0]) {
case ADD_MEMBER: {
char **l;
if (r < 0)
return log_oom();
/* Try to extend an existing member or group item */
return -EINVAL;
}
if (r < 0) {
return r;
}
if (!valid_user_group_name(resolved_id)) {
return -EINVAL;
}
if (n >= 0) {
return -EINVAL;
}
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 (r < 0)
return log_oom();
if (!i)
return log_oom();
if (path_is_absolute(id)) {
if (!i->uid_path)
return log_oom();
} else {
if (r < 0) {
return -EBADMSG;
}
i->uid_set = true;
}
}
if (n >= 0) {
if (!i->description)
return log_oom();
if (!valid_gecos(i->description)) {
return -EINVAL;
}
}
h = users;
break;
case ADD_GROUP:
if (r < 0)
return log_oom();
if (n >= 0) {
return -EINVAL;
}
if (!i)
return log_oom();
if (path_is_absolute(id)) {
if (!i->gid_path)
return log_oom();
} 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;
if (r < 0) {
if (ignore_enoent && r == -ENOENT)
return 0;
return r;
}
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 int 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",
return 0;
}
enum {
ARG_VERSION = 0x100,
};
{}
};
int c;
switch (c) {
case 'h':
return help();
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 = 0;
int j;
k = read_config_file(argv[j], false);
if (k < 0 && r == 0)
r = k;
}
} else {
char **f;
if (r < 0) {
goto finish;
}
STRV_FOREACH(f, files) {
k = read_config_file(*f, true);
if (k < 0 && r == 0)
r = k;
}
}
r = add_implicit();
if (r < 0)
goto finish;
if (lock < 0) {
goto finish;
}
r = load_user_database();
if (r < 0) {
goto finish;
}
r = load_group_database();
if (r < 0) {
goto finish;
}
process_item(i);
process_item(i);
r = write_files();
if (r < 0)
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;
}