]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/sysusers/sysusers.c
rpm macros: add %sysusers_create_package
[thirdparty/systemd.git] / src / sysusers / sysusers.c
CommitLineData
53e1b683 1/* SPDX-License-Identifier: LGPL-2.1+ */
1b992147
LP
2/***
3 This file is part of systemd.
4
5 Copyright 2014 Lennart Poettering
6
7 systemd is free software; you can redistribute it and/or modify it
8 under the terms of the GNU Lesser General Public License as published by
9 the Free Software Foundation; either version 2.1 of the License, or
10 (at your option) any later version.
11
12 systemd is distributed in the hope that it will be useful, but
13 WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 Lesser General Public License for more details.
16
17 You should have received a copy of the GNU Lesser General Public License
18 along with systemd; If not, see <http://www.gnu.org/licenses/>.
19***/
20
3f6fd1ba 21#include <getopt.h>
1b992147 22#include <grp.h>
9ab315cc 23#include <gshadow.h>
3f6fd1ba
LP
24#include <pwd.h>
25#include <shadow.h>
932ad62b 26#include <utmp.h>
1b992147 27
b5efdb8a 28#include "alloc-util.h"
1b992147
LP
29#include "conf-files.h"
30#include "copy.h"
a0f29c76
LP
31#include "def.h"
32#include "fd-util.h"
1dd98a71 33#include "fs-util.h"
f7f628b5 34#include "fileio-label.h"
f97b34a6 35#include "format-util.h"
3f6fd1ba
LP
36#include "hashmap.h"
37#include "path-util.h"
38#include "selinux-util.h"
07630cea 39#include "smack-util.h"
3f6fd1ba 40#include "specifier.h"
07630cea 41#include "string-util.h"
3f6fd1ba
LP
42#include "strv.h"
43#include "uid-range.h"
a0f29c76 44#include "user-util.h"
3f6fd1ba
LP
45#include "utf8.h"
46#include "util.h"
1b992147
LP
47
48typedef enum ItemType {
49 ADD_USER = 'u',
50 ADD_GROUP = 'g',
a12b0cc3 51 ADD_MEMBER = 'm',
8530dc44 52 ADD_RANGE = 'r',
1b992147
LP
53} ItemType;
54typedef struct Item {
55 ItemType type;
56
57 char *name;
58 char *uid_path;
59 char *gid_path;
60 char *description;
7629889c 61 char *home;
7b1aaf66 62 char *shell;
1b992147
LP
63
64 gid_t gid;
65 uid_t uid;
66
67 bool gid_set:1;
b9ee05c2
MV
68 // id_set_strict means that the group with the specified gid must
69 // exist and that the check if a uid clashes with a gid is skipped
70 bool id_set_strict:1;
1b992147
LP
71 bool uid_set:1;
72
c1b6b04f
KS
73 bool todo_user:1;
74 bool todo_group:1;
1b992147
LP
75} Item;
76
77static char *arg_root = NULL;
d16a1c1b 78static const char *arg_replace = NULL;
1b600bd5 79static bool arg_inline = false;
1b992147 80
75eb6154 81static const char conf_file_dirs[] = CONF_PATHS_NULSTR("sysusers.d");
1b992147 82
5bc9c980
MV
83static OrderedHashmap *users = NULL, *groups = NULL;
84static OrderedHashmap *todo_uids = NULL, *todo_gids = NULL;
85static OrderedHashmap *members = NULL;
1b992147
LP
86
87static Hashmap *database_uid = NULL, *database_user = NULL;
88static Hashmap *database_gid = NULL, *database_group = NULL;
89
fed1e721 90static uid_t search_uid = UID_INVALID;
8530dc44
LP
91static UidRange *uid_range = NULL;
92static unsigned n_uid_range = 0;
1b992147 93
1b992147
LP
94static int load_user_database(void) {
95 _cleanup_fclose_ FILE *f = NULL;
96 const char *passwd_path;
97 struct passwd *pw;
98 int r;
99
1d13f648 100 passwd_path = prefix_roota(arg_root, "/etc/passwd");
1b992147
LP
101 f = fopen(passwd_path, "re");
102 if (!f)
103 return errno == ENOENT ? 0 : -errno;
104
d5099efc 105 r = hashmap_ensure_allocated(&database_user, &string_hash_ops);
1b992147
LP
106 if (r < 0)
107 return r;
108
d5099efc 109 r = hashmap_ensure_allocated(&database_uid, NULL);
1b992147
LP
110 if (r < 0)
111 return r;
112
113 errno = 0;
114 while ((pw = fgetpwent(f))) {
115 char *n;
116 int k, q;
117
118 n = strdup(pw->pw_name);
119 if (!n)
120 return -ENOMEM;
121
122 k = hashmap_put(database_user, n, UID_TO_PTR(pw->pw_uid));
123 if (k < 0 && k != -EEXIST) {
124 free(n);
125 return k;
126 }
127
128 q = hashmap_put(database_uid, UID_TO_PTR(pw->pw_uid), n);
129 if (q < 0 && q != -EEXIST) {
130 if (k < 0)
131 free(n);
132 return q;
133 }
134
135 if (q < 0 && k < 0)
136 free(n);
137
138 errno = 0;
139 }
140 if (!IN_SET(errno, 0, ENOENT))
141 return -errno;
142
143 return 0;
144}
145
146static int load_group_database(void) {
147 _cleanup_fclose_ FILE *f = NULL;
148 const char *group_path;
149 struct group *gr;
150 int r;
151
1d13f648 152 group_path = prefix_roota(arg_root, "/etc/group");
1b992147
LP
153 f = fopen(group_path, "re");
154 if (!f)
155 return errno == ENOENT ? 0 : -errno;
156
d5099efc 157 r = hashmap_ensure_allocated(&database_group, &string_hash_ops);
1b992147
LP
158 if (r < 0)
159 return r;
160
d5099efc 161 r = hashmap_ensure_allocated(&database_gid, NULL);
1b992147
LP
162 if (r < 0)
163 return r;
164
165 errno = 0;
166 while ((gr = fgetgrent(f))) {
167 char *n;
168 int k, q;
169
170 n = strdup(gr->gr_name);
171 if (!n)
172 return -ENOMEM;
173
174 k = hashmap_put(database_group, n, GID_TO_PTR(gr->gr_gid));
175 if (k < 0 && k != -EEXIST) {
176 free(n);
177 return k;
178 }
179
180 q = hashmap_put(database_gid, GID_TO_PTR(gr->gr_gid), n);
181 if (q < 0 && q != -EEXIST) {
182 if (k < 0)
183 free(n);
184 return q;
185 }
186
187 if (q < 0 && k < 0)
188 free(n);
189
190 errno = 0;
191 }
192 if (!IN_SET(errno, 0, ENOENT))
193 return -errno;
194
195 return 0;
196}
197
9f1c1940
ZJS
198static int make_backup(const char *target, const char *x) {
199 _cleanup_close_ int src = -1;
200 _cleanup_fclose_ FILE *dst = NULL;
0a12bb1e
EV
201 _cleanup_free_ char *temp = NULL;
202 char *backup;
1b992147
LP
203 struct timespec ts[2];
204 struct stat st;
205 int r;
206
207 src = open(x, O_RDONLY|O_CLOEXEC|O_NOCTTY);
208 if (src < 0) {
209 if (errno == ENOENT) /* No backup necessary... */
210 return 0;
211
212 return -errno;
213 }
214
215 if (fstat(src, &st) < 0)
216 return -errno;
217
9f1c1940
ZJS
218 r = fopen_temporary_label(target, x, &dst, &temp);
219 if (r < 0)
220 return r;
1b992147 221
1c876927 222 r = copy_bytes(src, fileno(dst), (uint64_t) -1, COPY_REFLINK);
1b992147
LP
223 if (r < 0)
224 goto fail;
225
9f1c1940
ZJS
226 /* Don't fail on chmod() or chown(). If it stays owned by us
227 * and/or unreadable by others, then it isn't too bad... */
228
63c372cb 229 backup = strjoina(x, "-");
9f1c1940 230
1b992147 231 /* Copy over the access mask */
9f1c1940 232 if (fchmod(fileno(dst), st.st_mode & 07777) < 0)
56f64d95 233 log_warning_errno(errno, "Failed to change mode on %s: %m", backup);
1b992147 234
9f1c1940 235 if (fchown(fileno(dst), st.st_uid, st.st_gid)< 0)
56f64d95 236 log_warning_errno(errno, "Failed to change ownership of %s: %m", backup);
1b992147
LP
237
238 ts[0] = st.st_atim;
239 ts[1] = st.st_mtim;
f06863bd 240 if (futimens(fileno(dst), ts) < 0)
56f64d95 241 log_warning_errno(errno, "Failed to fix access and modification time of %s: %m", backup);
1b992147 242
0675e94a
AJ
243 r = fflush_sync_and_check(dst);
244 if (r < 0)
245 goto fail;
246
247 if (rename(temp, backup) < 0) {
248 r = -errno;
1b992147 249 goto fail;
0675e94a 250 }
1b992147
LP
251
252 return 0;
253
254fail:
255 unlink(temp);
256 return r;
257}
258
a12b0cc3
LP
259static int putgrent_with_members(const struct group *gr, FILE *group) {
260 char **a;
261
262 assert(gr);
263 assert(group);
264
5bc9c980 265 a = ordered_hashmap_get(members, gr->gr_name);
a12b0cc3
LP
266 if (a) {
267 _cleanup_strv_free_ char **l = NULL;
268 bool added = false;
269 char **i;
270
271 l = strv_copy(gr->gr_mem);
272 if (!l)
273 return -ENOMEM;
274
275 STRV_FOREACH(i, a) {
276 if (strv_find(l, *i))
277 continue;
278
279 if (strv_extend(&l, *i) < 0)
280 return -ENOMEM;
281
282 added = true;
283 }
284
285 if (added) {
286 struct group t;
287
288 strv_uniq(l);
289 strv_sort(l);
290
291 t = *gr;
292 t.gr_mem = l;
293
294 errno = 0;
295 if (putgrent(&t, group) != 0)
f5e5c28f 296 return errno > 0 ? -errno : -EIO;
a12b0cc3
LP
297
298 return 1;
299 }
300 }
301
302 errno = 0;
303 if (putgrent(gr, group) != 0)
f5e5c28f 304 return errno > 0 ? -errno : -EIO;
a12b0cc3
LP
305
306 return 0;
307}
308
349cc4a5 309#if ENABLE_GSHADOW
9ab315cc
LP
310static int putsgent_with_members(const struct sgrp *sg, FILE *gshadow) {
311 char **a;
312
313 assert(sg);
314 assert(gshadow);
315
5bc9c980 316 a = ordered_hashmap_get(members, sg->sg_namp);
9ab315cc
LP
317 if (a) {
318 _cleanup_strv_free_ char **l = NULL;
319 bool added = false;
320 char **i;
321
322 l = strv_copy(sg->sg_mem);
323 if (!l)
324 return -ENOMEM;
325
326 STRV_FOREACH(i, a) {
327 if (strv_find(l, *i))
328 continue;
329
330 if (strv_extend(&l, *i) < 0)
331 return -ENOMEM;
332
333 added = true;
334 }
335
336 if (added) {
337 struct sgrp t;
338
339 strv_uniq(l);
340 strv_sort(l);
341
342 t = *sg;
343 t.sg_mem = l;
344
345 errno = 0;
346 if (putsgent(&t, gshadow) != 0)
f5e5c28f 347 return errno > 0 ? -errno : -EIO;
9ab315cc
LP
348
349 return 1;
350 }
351 }
352
353 errno = 0;
354 if (putsgent(sg, gshadow) != 0)
f5e5c28f 355 return errno > 0 ? -errno : -EIO;
9ab315cc
LP
356
357 return 0;
358}
b14e1b43 359#endif
9ab315cc 360
fff19499
LP
361static int sync_rights(FILE *from, FILE *to) {
362 struct stat st;
363
364 if (fstat(fileno(from), &st) < 0)
365 return -errno;
366
367 if (fchmod(fileno(to), st.st_mode & 07777) < 0)
368 return -errno;
369
370 if (fchown(fileno(to), st.st_uid, st.st_gid) < 0)
371 return -errno;
372
373 return 0;
374}
375
c02e7b1e
SW
376static int rename_and_apply_smack(const char *temp_path, const char *dest_path) {
377 int r = 0;
378 if (rename(temp_path, dest_path) < 0)
379 return -errno;
380
381#ifdef SMACK_RUN_LABEL
382 r = mac_smack_apply(dest_path, SMACK_ATTR_ACCESS, SMACK_FLOOR_LABEL);
383 if (r < 0)
384 return r;
385#endif
386 return r;
387}
388
7b1aaf66
ZJS
389static const char* default_shell(uid_t uid) {
390 return uid == 0 ? "/bin/sh" : "/sbin/nologin";
391}
392
b20b0b66
FB
393static int write_temporary_passwd(const char *passwd_path, FILE **tmpfile, char **tmpfile_path) {
394 _cleanup_fclose_ FILE *original = NULL, *passwd = NULL;
1dd98a71 395 _cleanup_(unlink_and_freep) char *passwd_tmp = NULL;
1b992147
LP
396 Iterator iterator;
397 Item *i;
398 int r;
399
5bc9c980 400 if (ordered_hashmap_size(todo_uids) == 0)
b20b0b66 401 return 0;
1b992147 402
b20b0b66
FB
403 r = fopen_temporary_label("/etc/passwd", passwd_path, &passwd, &passwd_tmp);
404 if (r < 0)
405 return r;
406
407 original = fopen(passwd_path, "re");
408 if (original) {
409 struct passwd *pw;
410
411 r = sync_rights(original, passwd);
1b992147 412 if (r < 0)
1dd98a71 413 return r;
1b992147 414
b20b0b66
FB
415 errno = 0;
416 while ((pw = fgetpwent(original))) {
1b992147 417
5bc9c980 418 i = ordered_hashmap_get(users, pw->pw_name);
b20b0b66
FB
419 if (i && i->todo_user) {
420 log_error("%s: User \"%s\" already exists.", passwd_path, pw->pw_name);
1dd98a71 421 return -EEXIST;
b20b0b66 422 }
e3c72c21 423
5bc9c980 424 if (ordered_hashmap_contains(todo_uids, UID_TO_PTR(pw->pw_uid))) {
b20b0b66 425 log_error("%s: Detected collision for UID " UID_FMT ".", passwd_path, pw->pw_uid);
1dd98a71 426 return -EEXIST;
1b992147 427 }
b20b0b66
FB
428
429 errno = 0;
1dd98a71
FB
430 if (putpwent(pw, passwd) < 0)
431 return errno ? -errno : -EIO;
1b992147 432
b20b0b66
FB
433 errno = 0;
434 }
1dd98a71
FB
435 if (!IN_SET(errno, 0, ENOENT))
436 return -errno;
437
438 } else {
439 if (errno != ENOENT)
440 return -errno;
441 if (fchmod(fileno(passwd), 0644) < 0)
442 return -errno;
b20b0b66
FB
443 }
444
5bc9c980 445 ORDERED_HASHMAP_FOREACH(i, todo_uids, iterator) {
b20b0b66
FB
446 struct passwd n = {
447 .pw_name = i->name,
448 .pw_uid = i->uid,
449 .pw_gid = i->gid,
450 .pw_gecos = i->description,
1b992147 451
b20b0b66
FB
452 /* "x" means the password is stored in the shadow file */
453 .pw_passwd = (char*) "x",
a12b0cc3 454
b20b0b66
FB
455 /* We default to the root directory as home */
456 .pw_dir = i->home ? i->home : (char*) "/",
1b992147 457
b20b0b66
FB
458 /* Initialize the shell to nologin, with one exception:
459 * for root we patch in something special */
7b1aaf66 460 .pw_shell = i->shell ?: (char*) default_shell(i->uid),
b20b0b66 461 };
9ab315cc 462
b20b0b66 463 errno = 0;
1dd98a71
FB
464 if (putpwent(&n, passwd) != 0)
465 return errno ? -errno : -EIO;
b20b0b66
FB
466 }
467
468 r = fflush_and_check(passwd);
469 if (r < 0)
1dd98a71 470 return r;
b20b0b66
FB
471
472 *tmpfile = passwd;
473 *tmpfile_path = passwd_tmp;
474 passwd = NULL;
475 passwd_tmp = NULL;
476 return 0;
b20b0b66
FB
477}
478
479static int write_temporary_shadow(const char *shadow_path, FILE **tmpfile, char **tmpfile_path) {
480 _cleanup_fclose_ FILE *original = NULL, *shadow = NULL;
1dd98a71 481 _cleanup_(unlink_and_freep) char *shadow_tmp = NULL;
b20b0b66
FB
482 Iterator iterator;
483 long lstchg;
484 Item *i;
485 int r;
486
5bc9c980 487 if (ordered_hashmap_size(todo_uids) == 0)
b20b0b66
FB
488 return 0;
489
490 r = fopen_temporary_label("/etc/shadow", shadow_path, &shadow, &shadow_tmp);
491 if (r < 0)
492 return r;
9ab315cc 493
b20b0b66
FB
494 lstchg = (long) (now(CLOCK_REALTIME) / USEC_PER_DAY);
495
496 original = fopen(shadow_path, "re");
497 if (original) {
498 struct spwd *sp;
499
500 r = sync_rights(original, shadow);
9ab315cc 501 if (r < 0)
1dd98a71 502 return r;
9ab315cc 503
b20b0b66
FB
504 errno = 0;
505 while ((sp = fgetspent(original))) {
9ab315cc 506
5bc9c980 507 i = ordered_hashmap_get(users, sp->sp_namp);
b20b0b66
FB
508 if (i && i->todo_user) {
509 /* we will update the existing entry */
510 sp->sp_lstchg = lstchg;
e3c72c21 511
b20b0b66
FB
512 /* only the /etc/shadow stage is left, so we can
513 * safely remove the item from the todo set */
514 i->todo_user = false;
5bc9c980 515 ordered_hashmap_remove(todo_uids, UID_TO_PTR(i->uid));
9ab315cc 516 }
b20b0b66
FB
517
518 errno = 0;
1dd98a71
FB
519 if (putspent(sp, shadow) < 0)
520 return errno ? -errno : -EIO;
9ab315cc 521
b20b0b66
FB
522 errno = 0;
523 }
1dd98a71
FB
524 if (!IN_SET(errno, 0, ENOENT))
525 return -errno;
526
527 } else {
528 if (errno != ENOENT)
529 return -errno;
530 if (fchmod(fileno(shadow), 0000) < 0)
531 return -errno;
b20b0b66 532 }
9ab315cc 533
5bc9c980 534 ORDERED_HASHMAP_FOREACH(i, todo_uids, iterator) {
b20b0b66
FB
535 struct spwd n = {
536 .sp_namp = i->name,
537 .sp_pwdp = (char*) "!!",
538 .sp_lstchg = lstchg,
539 .sp_min = -1,
540 .sp_max = -1,
541 .sp_warn = -1,
542 .sp_inact = -1,
543 .sp_expire = -1,
544 .sp_flag = (unsigned long) -1, /* this appears to be what everybody does ... */
545 };
9ab315cc 546
b20b0b66 547 errno = 0;
1dd98a71
FB
548 if (putspent(&n, shadow) != 0)
549 return errno ? -errno : -EIO;
1b992147
LP
550 }
551
0675e94a 552 r = fflush_sync_and_check(shadow);
b20b0b66 553 if (r < 0)
1dd98a71 554 return r;
b20b0b66
FB
555
556 *tmpfile = shadow;
557 *tmpfile_path = shadow_tmp;
558 shadow = NULL;
559 shadow_tmp = NULL;
560 return 0;
b20b0b66 561}
1b992147 562
b20b0b66
FB
563static int write_temporary_group(const char *group_path, FILE **tmpfile, char **tmpfile_path) {
564 _cleanup_fclose_ FILE *original = NULL, *group = NULL;
1dd98a71 565 _cleanup_(unlink_and_freep) char *group_tmp = NULL;
b20b0b66
FB
566 bool group_changed = false;
567 Iterator iterator;
568 Item *i;
569 int r;
1b992147 570
5bc9c980 571 if (ordered_hashmap_size(todo_gids) == 0 && ordered_hashmap_size(members) == 0)
b20b0b66 572 return 0;
e3c72c21 573
b20b0b66
FB
574 r = fopen_temporary_label("/etc/group", group_path, &group, &group_tmp);
575 if (r < 0)
576 return r;
577
578 original = fopen(group_path, "re");
579 if (original) {
580 struct group *gr;
581
582 r = sync_rights(original, group);
583 if (r < 0)
1dd98a71 584 return r;
b20b0b66
FB
585
586 errno = 0;
587 while ((gr = fgetgrent(original))) {
588 /* Safety checks against name and GID collisions. Normally,
589 * this should be unnecessary, but given that we look at the
590 * entries anyway here, let's make an extra verification
591 * step that we don't generate duplicate entries. */
592
5bc9c980 593 i = ordered_hashmap_get(groups, gr->gr_name);
b20b0b66
FB
594 if (i && i->todo_group) {
595 log_error("%s: Group \"%s\" already exists.", group_path, gr->gr_name);
1dd98a71 596 return -EEXIST;
1b992147 597 }
b20b0b66 598
5bc9c980 599 if (ordered_hashmap_contains(todo_gids, GID_TO_PTR(gr->gr_gid))) {
b20b0b66 600 log_error("%s: Detected collision for GID " GID_FMT ".", group_path, gr->gr_gid);
1dd98a71 601 return -EEXIST;
1b992147
LP
602 }
603
b20b0b66
FB
604 r = putgrent_with_members(gr, group);
605 if (r < 0)
1dd98a71 606 return r;
b20b0b66
FB
607 if (r > 0)
608 group_changed = true;
609
610 errno = 0;
611 }
1dd98a71
FB
612 if (!IN_SET(errno, 0, ENOENT))
613 return -errno;
614
615 } else {
616 if (errno != ENOENT)
617 return -errno;
618 if (fchmod(fileno(group), 0644) < 0)
619 return -errno;
b20b0b66 620 }
7629889c 621
5bc9c980 622 ORDERED_HASHMAP_FOREACH(i, todo_gids, iterator) {
b20b0b66
FB
623 struct group n = {
624 .gr_name = i->name,
625 .gr_gid = i->gid,
626 .gr_passwd = (char*) "x",
627 };
1b992147 628
b20b0b66
FB
629 r = putgrent_with_members(&n, group);
630 if (r < 0)
1dd98a71 631 return r;
7629889c 632
b20b0b66
FB
633 group_changed = true;
634 }
1b992147 635
0675e94a 636 r = fflush_sync_and_check(group);
b20b0b66 637 if (r < 0)
1dd98a71 638 return r;
1b992147 639
b20b0b66
FB
640 if (group_changed) {
641 *tmpfile = group;
642 *tmpfile_path = group_tmp;
643 group = NULL;
644 group_tmp = NULL;
645 }
646 return 0;
b20b0b66
FB
647}
648
649static int write_temporary_gshadow(const char * gshadow_path, FILE **tmpfile, char **tmpfile_path) {
349cc4a5 650#if ENABLE_GSHADOW
b20b0b66 651 _cleanup_fclose_ FILE *original = NULL, *gshadow = NULL;
1dd98a71 652 _cleanup_(unlink_and_freep) char *gshadow_tmp = NULL;
b20b0b66
FB
653 bool group_changed = false;
654 Iterator iterator;
655 Item *i;
656 int r;
657
5bc9c980 658 if (ordered_hashmap_size(todo_gids) == 0 && ordered_hashmap_size(members) == 0)
b20b0b66
FB
659 return 0;
660
661 r = fopen_temporary_label("/etc/gshadow", gshadow_path, &gshadow, &gshadow_tmp);
662 if (r < 0)
663 return r;
9ab315cc 664
b20b0b66
FB
665 original = fopen(gshadow_path, "re");
666 if (original) {
667 struct sgrp *sg;
668
669 r = sync_rights(original, gshadow);
1b992147 670 if (r < 0)
1dd98a71 671 return r;
9ab315cc 672
b20b0b66
FB
673 errno = 0;
674 while ((sg = fgetsgent(original))) {
c5abf225 675
5bc9c980 676 i = ordered_hashmap_get(groups, sg->sg_namp);
b20b0b66
FB
677 if (i && i->todo_group) {
678 log_error("%s: Group \"%s\" already exists.", gshadow_path, sg->sg_namp);
1dd98a71 679 return -EEXIST;
b20b0b66 680 }
9ab315cc 681
b20b0b66 682 r = putsgent_with_members(sg, gshadow);
fff19499 683 if (r < 0)
1dd98a71 684 return r;
b20b0b66
FB
685 if (r > 0)
686 group_changed = true;
e3c72c21 687
9ab315cc 688 errno = 0;
b20b0b66 689 }
1dd98a71
FB
690 if (!IN_SET(errno, 0, ENOENT))
691 return -errno;
692
693 } else {
694 if (errno != ENOENT)
695 return -errno;
696 if (fchmod(fileno(gshadow), 0000) < 0)
697 return -errno;
b20b0b66 698 }
9ab315cc 699
5bc9c980 700 ORDERED_HASHMAP_FOREACH(i, todo_gids, iterator) {
b20b0b66
FB
701 struct sgrp n = {
702 .sg_namp = i->name,
703 .sg_passwd = (char*) "!!",
704 };
9ab315cc 705
b20b0b66 706 r = putsgent_with_members(&n, gshadow);
9ab315cc 707 if (r < 0)
1dd98a71 708 return r;
b20b0b66
FB
709
710 group_changed = true;
9ab315cc
LP
711 }
712
0675e94a 713 r = fflush_sync_and_check(gshadow);
b20b0b66 714 if (r < 0)
1dd98a71 715 return r;
b20b0b66 716
9ab315cc 717 if (group_changed) {
b20b0b66
FB
718 *tmpfile = gshadow;
719 *tmpfile_path = gshadow_tmp;
720 gshadow = NULL;
721 gshadow_tmp = NULL;
722 }
723 return 0;
b14e1b43
FB
724#else
725 return 0;
726#endif
b20b0b66
FB
727}
728
729static int write_files(void) {
b20b0b66 730 _cleanup_fclose_ FILE *passwd = NULL, *group = NULL, *shadow = NULL, *gshadow = NULL;
1dd98a71 731 _cleanup_(unlink_and_freep) char *passwd_tmp = NULL, *group_tmp = NULL, *shadow_tmp = NULL, *gshadow_tmp = NULL;
b20b0b66
FB
732 const char *passwd_path = NULL, *group_path = NULL, *shadow_path = NULL, *gshadow_path = NULL;
733 int r;
734
735 passwd_path = prefix_roota(arg_root, "/etc/passwd");
736 shadow_path = prefix_roota(arg_root, "/etc/shadow");
737 group_path = prefix_roota(arg_root, "/etc/group");
738 gshadow_path = prefix_roota(arg_root, "/etc/gshadow");
739
740 r = write_temporary_group(group_path, &group, &group_tmp);
741 if (r < 0)
1dd98a71 742 return r;
b20b0b66
FB
743
744 r = write_temporary_gshadow(gshadow_path, &gshadow, &gshadow_tmp);
745 if (r < 0)
1dd98a71 746 return r;
b20b0b66
FB
747
748 r = write_temporary_passwd(passwd_path, &passwd, &passwd_tmp);
749 if (r < 0)
1dd98a71 750 return r;
b20b0b66
FB
751
752 r = write_temporary_shadow(shadow_path, &shadow, &shadow_tmp);
753 if (r < 0)
1dd98a71 754 return r;
b20b0b66
FB
755
756 /* Make a backup of the old files */
757 if (group) {
758 r = make_backup("/etc/group", group_path);
759 if (r < 0)
1dd98a71 760 return r;
b20b0b66
FB
761 }
762 if (gshadow) {
763 r = make_backup("/etc/gshadow", gshadow_path);
764 if (r < 0)
1dd98a71 765 return r;
1b992147
LP
766 }
767
768 if (passwd) {
9f1c1940 769 r = make_backup("/etc/passwd", passwd_path);
1b992147 770 if (r < 0)
1dd98a71 771 return r;
1b992147 772 }
9ab315cc
LP
773 if (shadow) {
774 r = make_backup("/etc/shadow", shadow_path);
775 if (r < 0)
1dd98a71 776 return r;
9ab315cc 777 }
1b992147
LP
778
779 /* And make the new files count */
b20b0b66
FB
780 if (group) {
781 r = rename_and_apply_smack(group_tmp, group_path);
782 if (r < 0)
1dd98a71 783 return r;
9ab315cc 784
b20b0b66
FB
785 group_tmp = mfree(group_tmp);
786 }
787 if (gshadow) {
788 r = rename_and_apply_smack(gshadow_tmp, gshadow_path);
789 if (r < 0)
1dd98a71 790 return r;
1b992147 791
b20b0b66 792 gshadow_tmp = mfree(gshadow_tmp);
1b992147
LP
793 }
794
795 if (passwd) {
c02e7b1e
SW
796 r = rename_and_apply_smack(passwd_tmp, passwd_path);
797 if (r < 0)
1dd98a71 798 return r;
1b992147 799
97b11eed 800 passwd_tmp = mfree(passwd_tmp);
1b992147 801 }
9ab315cc 802 if (shadow) {
c02e7b1e
SW
803 r = rename_and_apply_smack(shadow_tmp, shadow_path);
804 if (r < 0)
1dd98a71 805 return r;
9ab315cc 806
97b11eed 807 shadow_tmp = mfree(shadow_tmp);
9ab315cc 808 }
1b992147 809
1dd98a71 810 return 0;
1b992147
LP
811}
812
b9ee05c2 813static int uid_is_ok(uid_t uid, const char *name, bool check_with_gid) {
1b992147
LP
814 struct passwd *p;
815 struct group *g;
816 const char *n;
817 Item *i;
818
819 /* Let's see if we already have assigned the UID a second time */
5bc9c980 820 if (ordered_hashmap_get(todo_uids, UID_TO_PTR(uid)))
1b992147
LP
821 return 0;
822
823 /* Try to avoid using uids that are already used by a group
824 * that doesn't have the same name as our new user. */
b9ee05c2
MV
825 if (check_with_gid) {
826 i = ordered_hashmap_get(todo_gids, GID_TO_PTR(uid));
827 if (i && !streq(i->name, name))
828 return 0;
829 }
1b992147
LP
830
831 /* Let's check the files directly */
832 if (hashmap_contains(database_uid, UID_TO_PTR(uid)))
833 return 0;
834
b9ee05c2
MV
835 if (check_with_gid) {
836 n = hashmap_get(database_gid, GID_TO_PTR(uid));
837 if (n && !streq(n, name))
838 return 0;
839 }
1b992147
LP
840
841 /* Let's also check via NSS, to avoid UID clashes over LDAP and such, just in case */
842 if (!arg_root) {
843 errno = 0;
844 p = getpwuid(uid);
845 if (p)
846 return 0;
b0284aba 847 if (!IN_SET(errno, 0, ENOENT))
1b992147
LP
848 return -errno;
849
b9ee05c2
MV
850 if (check_with_gid) {
851 errno = 0;
852 g = getgrgid((gid_t) uid);
853 if (g) {
854 if (!streq(g->gr_name, name))
855 return 0;
856 } else if (!IN_SET(errno, 0, ENOENT))
857 return -errno;
858 }
1b992147
LP
859 }
860
861 return 1;
862}
863
864static int root_stat(const char *p, struct stat *st) {
865 const char *fix;
866
1d13f648 867 fix = prefix_roota(arg_root, p);
1b992147
LP
868 if (stat(fix, st) < 0)
869 return -errno;
870
871 return 0;
872}
873
874static int read_id_from_file(Item *i, uid_t *_uid, gid_t *_gid) {
875 struct stat st;
876 bool found_uid = false, found_gid = false;
56d21cde
PDS
877 uid_t uid = 0;
878 gid_t gid = 0;
1b992147
LP
879
880 assert(i);
881
882 /* First, try to get the gid directly */
883 if (_gid && i->gid_path && root_stat(i->gid_path, &st) >= 0) {
884 gid = st.st_gid;
885 found_gid = true;
886 }
887
888 /* Then, try to get the uid directly */
889 if ((_uid || (_gid && !found_gid))
890 && i->uid_path
891 && root_stat(i->uid_path, &st) >= 0) {
892
893 uid = st.st_uid;
894 found_uid = true;
895
896 /* If we need the gid, but had no success yet, also derive it from the uid path */
897 if (_gid && !found_gid) {
898 gid = st.st_gid;
899 found_gid = true;
900 }
901 }
902
903 /* If that didn't work yet, then let's reuse the gid as uid */
904 if (_uid && !found_uid && i->gid_path) {
905
906 if (found_gid) {
907 uid = (uid_t) gid;
908 found_uid = true;
909 } else if (root_stat(i->gid_path, &st) >= 0) {
910 uid = (uid_t) st.st_gid;
911 found_uid = true;
912 }
913 }
914
915 if (_uid) {
916 if (!found_uid)
917 return 0;
918
919 *_uid = uid;
920 }
921
922 if (_gid) {
923 if (!found_gid)
924 return 0;
925
926 *_gid = gid;
927 }
928
929 return 1;
930}
931
932static int add_user(Item *i) {
933 void *z;
934 int r;
935
936 assert(i);
937
938 /* Check the database directly */
939 z = hashmap_get(database_user, i->name);
940 if (z) {
941 log_debug("User %s already exists.", i->name);
c1b6b04f 942 i->uid = PTR_TO_UID(z);
1b992147
LP
943 i->uid_set = true;
944 return 0;
945 }
946
947 if (!arg_root) {
948 struct passwd *p;
1b992147
LP
949
950 /* Also check NSS */
951 errno = 0;
952 p = getpwnam(i->name);
953 if (p) {
954 log_debug("User %s already exists.", i->name);
955 i->uid = p->pw_uid;
956 i->uid_set = true;
957
2fc09a9c
DM
958 r = free_and_strdup(&i->description, p->pw_gecos);
959 if (r < 0)
960 return log_oom();
961
1b992147
LP
962 return 0;
963 }
4a62c710
MS
964 if (!IN_SET(errno, 0, ENOENT))
965 return log_error_errno(errno, "Failed to check if user %s already exists: %m", i->name);
1b992147
LP
966 }
967
968 /* Try to use the suggested numeric uid */
969 if (i->uid_set) {
b9ee05c2 970 r = uid_is_ok(i->uid, i->name, !i->id_set_strict);
f647962d
MS
971 if (r < 0)
972 return log_error_errno(r, "Failed to verify uid " UID_FMT ": %m", i->uid);
1b992147
LP
973 if (r == 0) {
974 log_debug("Suggested user ID " UID_FMT " for %s already used.", i->uid, i->name);
975 i->uid_set = false;
976 }
977 }
978
979 /* If that didn't work, try to read it from the specified path */
980 if (!i->uid_set) {
981 uid_t c;
982
983 if (read_id_from_file(i, &c, NULL) > 0) {
984
8530dc44 985 if (c <= 0 || !uid_range_contains(uid_range, n_uid_range, c))
1b992147
LP
986 log_debug("User ID " UID_FMT " of file not suitable for %s.", c, i->name);
987 else {
b9ee05c2 988 r = uid_is_ok(c, i->name, true);
f647962d
MS
989 if (r < 0)
990 return log_error_errno(r, "Failed to verify uid " UID_FMT ": %m", i->uid);
991 else if (r > 0) {
1b992147
LP
992 i->uid = c;
993 i->uid_set = true;
994 } else
995 log_debug("User ID " UID_FMT " of file for %s is already used.", c, i->name);
996 }
997 }
998 }
999
b938cb90 1000 /* Otherwise, try to reuse the group ID */
1b992147 1001 if (!i->uid_set && i->gid_set) {
b9ee05c2 1002 r = uid_is_ok((uid_t) i->gid, i->name, true);
f647962d
MS
1003 if (r < 0)
1004 return log_error_errno(r, "Failed to verify uid " UID_FMT ": %m", i->uid);
1b992147
LP
1005 if (r > 0) {
1006 i->uid = (uid_t) i->gid;
1007 i->uid_set = true;
1008 }
1009 }
1010
1011 /* And if that didn't work either, let's try to find a free one */
1012 if (!i->uid_set) {
8530dc44
LP
1013 for (;;) {
1014 r = uid_range_next_lower(uid_range, n_uid_range, &search_uid);
1015 if (r < 0) {
1016 log_error("No free user ID available for %s.", i->name);
1017 return r;
1018 }
1b992147 1019
b9ee05c2 1020 r = uid_is_ok(search_uid, i->name, true);
f647962d
MS
1021 if (r < 0)
1022 return log_error_errno(r, "Failed to verify uid " UID_FMT ": %m", i->uid);
1023 else if (r > 0)
1b992147
LP
1024 break;
1025 }
1026
1b992147
LP
1027 i->uid_set = true;
1028 i->uid = search_uid;
1b992147
LP
1029 }
1030
5bc9c980 1031 r = ordered_hashmap_ensure_allocated(&todo_uids, NULL);
1b992147
LP
1032 if (r < 0)
1033 return log_oom();
1034
5bc9c980 1035 r = ordered_hashmap_put(todo_uids, UID_TO_PTR(i->uid), i);
1b992147
LP
1036 if (r < 0)
1037 return log_oom();
1038
c1b6b04f 1039 i->todo_user = true;
1b992147
LP
1040 log_info("Creating user %s (%s) with uid " UID_FMT " and gid " GID_FMT ".", i->name, strna(i->description), i->uid, i->gid);
1041
1042 return 0;
1043}
1044
1045static int gid_is_ok(gid_t gid) {
1046 struct group *g;
1047 struct passwd *p;
1048
5bc9c980 1049 if (ordered_hashmap_get(todo_gids, GID_TO_PTR(gid)))
1b992147
LP
1050 return 0;
1051
1052 /* Avoid reusing gids that are already used by a different user */
5bc9c980 1053 if (ordered_hashmap_get(todo_uids, UID_TO_PTR(gid)))
1b992147
LP
1054 return 0;
1055
1056 if (hashmap_contains(database_gid, GID_TO_PTR(gid)))
1057 return 0;
1058
1059 if (hashmap_contains(database_uid, UID_TO_PTR(gid)))
1060 return 0;
1061
1062 if (!arg_root) {
1063 errno = 0;
1064 g = getgrgid(gid);
1065 if (g)
1066 return 0;
b0284aba 1067 if (!IN_SET(errno, 0, ENOENT))
1b992147
LP
1068 return -errno;
1069
1070 errno = 0;
1071 p = getpwuid((uid_t) gid);
1072 if (p)
1073 return 0;
b0284aba 1074 if (!IN_SET(errno, 0, ENOENT))
1b992147
LP
1075 return -errno;
1076 }
1077
1078 return 1;
1079}
1080
1081static int add_group(Item *i) {
1082 void *z;
1083 int r;
1084
1085 assert(i);
1086
1087 /* Check the database directly */
1088 z = hashmap_get(database_group, i->name);
1089 if (z) {
1090 log_debug("Group %s already exists.", i->name);
1091 i->gid = PTR_TO_GID(z);
1092 i->gid_set = true;
1093 return 0;
1094 }
1095
1096 /* Also check NSS */
1097 if (!arg_root) {
1098 struct group *g;
1099
1100 errno = 0;
1101 g = getgrnam(i->name);
1102 if (g) {
1103 log_debug("Group %s already exists.", i->name);
1104 i->gid = g->gr_gid;
1105 i->gid_set = true;
1106 return 0;
1107 }
4a62c710
MS
1108 if (!IN_SET(errno, 0, ENOENT))
1109 return log_error_errno(errno, "Failed to check if group %s already exists: %m", i->name);
1b992147
LP
1110 }
1111
1112 /* Try to use the suggested numeric gid */
1113 if (i->gid_set) {
1114 r = gid_is_ok(i->gid);
f647962d
MS
1115 if (r < 0)
1116 return log_error_errno(r, "Failed to verify gid " GID_FMT ": %m", i->gid);
b9ee05c2 1117 if (i->id_set_strict) {
28e7fad7
MV
1118 /* If we require the gid to already exist we can return here:
1119 * r > 0: means the gid does not exist -> fail
1120 * r == 0: means the gid exists -> nothing more to do.
1121 */
1122 if (r > 0) {
1123 log_error("Failed to create %s: please create GID %d", i->name, i->gid);
1124 return -EINVAL;
1125 }
1126 if (r == 0)
1127 return 0;
1128 }
1b992147
LP
1129 if (r == 0) {
1130 log_debug("Suggested group ID " GID_FMT " for %s already used.", i->gid, i->name);
1131 i->gid_set = false;
1132 }
1133 }
1134
1135 /* Try to reuse the numeric uid, if there's one */
1136 if (!i->gid_set && i->uid_set) {
1137 r = gid_is_ok((gid_t) i->uid);
f647962d
MS
1138 if (r < 0)
1139 return log_error_errno(r, "Failed to verify gid " GID_FMT ": %m", i->gid);
1b992147
LP
1140 if (r > 0) {
1141 i->gid = (gid_t) i->uid;
1142 i->gid_set = true;
1143 }
1144 }
1145
1146 /* If that didn't work, try to read it from the specified path */
1147 if (!i->gid_set) {
1148 gid_t c;
1149
1150 if (read_id_from_file(i, NULL, &c) > 0) {
1151
8530dc44 1152 if (c <= 0 || !uid_range_contains(uid_range, n_uid_range, c))
1b992147
LP
1153 log_debug("Group ID " GID_FMT " of file not suitable for %s.", c, i->name);
1154 else {
1155 r = gid_is_ok(c);
f647962d
MS
1156 if (r < 0)
1157 return log_error_errno(r, "Failed to verify gid " GID_FMT ": %m", i->gid);
1158 else if (r > 0) {
1b992147
LP
1159 i->gid = c;
1160 i->gid_set = true;
1161 } else
1162 log_debug("Group ID " GID_FMT " of file for %s already used.", c, i->name);
1163 }
1164 }
1165 }
1166
1167 /* And if that didn't work either, let's try to find a free one */
1168 if (!i->gid_set) {
8530dc44
LP
1169 for (;;) {
1170 /* We look for new GIDs in the UID pool! */
1171 r = uid_range_next_lower(uid_range, n_uid_range, &search_uid);
1172 if (r < 0) {
1173 log_error("No free group ID available for %s.", i->name);
1174 return r;
1175 }
1176
1177 r = gid_is_ok(search_uid);
f647962d
MS
1178 if (r < 0)
1179 return log_error_errno(r, "Failed to verify gid " GID_FMT ": %m", i->gid);
1180 else if (r > 0)
1b992147
LP
1181 break;
1182 }
1183
1b992147 1184 i->gid_set = true;
8530dc44 1185 i->gid = search_uid;
1b992147
LP
1186 }
1187
5bc9c980 1188 r = ordered_hashmap_ensure_allocated(&todo_gids, NULL);
1b992147
LP
1189 if (r < 0)
1190 return log_oom();
1191
5bc9c980 1192 r = ordered_hashmap_put(todo_gids, GID_TO_PTR(i->gid), i);
1b992147
LP
1193 if (r < 0)
1194 return log_oom();
1195
c1b6b04f 1196 i->todo_group = true;
1b992147
LP
1197 log_info("Creating group %s with gid " GID_FMT ".", i->name, i->gid);
1198
1199 return 0;
1200}
1201
1202static int process_item(Item *i) {
1203 int r;
1204
1205 assert(i);
1206
1207 switch (i->type) {
1208
1209 case ADD_USER:
1210 r = add_group(i);
1211 if (r < 0)
1212 return r;
1213
1214 return add_user(i);
1215
e2c2060f 1216 case ADD_GROUP:
1b992147 1217 return add_group(i);
1b992147 1218
a12b0cc3
LP
1219 default:
1220 assert_not_reached("Unknown item type");
1221 }
1b992147
LP
1222}
1223
1224static void item_free(Item *i) {
1225
1226 if (!i)
1227 return;
1228
1229 free(i->name);
1230 free(i->uid_path);
1231 free(i->gid_path);
1232 free(i->description);
d9b8ea54 1233 free(i->home);
7b1aaf66 1234 free(i->shell);
1b992147
LP
1235 free(i);
1236}
1237
1238DEFINE_TRIVIAL_CLEANUP_FUNC(Item*, item_free);
1239
a12b0cc3
LP
1240static int add_implicit(void) {
1241 char *g, **l;
1242 Iterator iterator;
1243 int r;
1244
1245 /* Implicitly create additional users and groups, if they were listed in "m" lines */
1246
5bc9c980 1247 ORDERED_HASHMAP_FOREACH_KEY(l, g, members, iterator) {
a12b0cc3
LP
1248 Item *i;
1249 char **m;
1250
5bc9c980 1251 i = ordered_hashmap_get(groups, g);
a12b0cc3
LP
1252 if (!i) {
1253 _cleanup_(item_freep) Item *j = NULL;
1254
5bc9c980 1255 r = ordered_hashmap_ensure_allocated(&groups, &string_hash_ops);
a12b0cc3
LP
1256 if (r < 0)
1257 return log_oom();
1258
1259 j = new0(Item, 1);
1260 if (!j)
1261 return log_oom();
1262
1263 j->type = ADD_GROUP;
1264 j->name = strdup(g);
1265 if (!j->name)
1266 return log_oom();
1267
5bc9c980 1268 r = ordered_hashmap_put(groups, j->name, j);
a12b0cc3
LP
1269 if (r < 0)
1270 return log_oom();
1271
1272 log_debug("Adding implicit group '%s' due to m line", j->name);
1273 j = NULL;
1274 }
1275
1276 STRV_FOREACH(m, l) {
1277
5bc9c980 1278 i = ordered_hashmap_get(users, *m);
a12b0cc3
LP
1279 if (!i) {
1280 _cleanup_(item_freep) Item *j = NULL;
1281
5bc9c980 1282 r = ordered_hashmap_ensure_allocated(&users, &string_hash_ops);
a12b0cc3
LP
1283 if (r < 0)
1284 return log_oom();
1285
1286 j = new0(Item, 1);
1287 if (!j)
1288 return log_oom();
1289
1290 j->type = ADD_USER;
1291 j->name = strdup(*m);
1292 if (!j->name)
1293 return log_oom();
1294
5bc9c980 1295 r = ordered_hashmap_put(users, j->name, j);
a12b0cc3
LP
1296 if (r < 0)
1297 return log_oom();
1298
1299 log_debug("Adding implicit user '%s' due to m line", j->name);
1300 j = NULL;
1301 }
1302 }
1303 }
1304
1305 return 0;
1306}
1307
1b992147
LP
1308static bool item_equal(Item *a, Item *b) {
1309 assert(a);
1310 assert(b);
1311
1312 if (a->type != b->type)
1313 return false;
1314
1315 if (!streq_ptr(a->name, b->name))
1316 return false;
1317
1318 if (!streq_ptr(a->uid_path, b->uid_path))
1319 return false;
1320
1321 if (!streq_ptr(a->gid_path, b->gid_path))
1322 return false;
1323
1324 if (!streq_ptr(a->description, b->description))
1325 return false;
1326
1327 if (a->uid_set != b->uid_set)
1328 return false;
1329
1330 if (a->uid_set && a->uid != b->uid)
1331 return false;
1332
1333 if (a->gid_set != b->gid_set)
1334 return false;
1335
1336 if (a->gid_set && a->gid != b->gid)
1337 return false;
1338
7629889c
LP
1339 if (!streq_ptr(a->home, b->home))
1340 return false;
1341
7b1aaf66
ZJS
1342 if (!streq_ptr(a->shell, b->shell))
1343 return false;
1344
1b992147
LP
1345 return true;
1346}
1347
1b992147
LP
1348static int parse_line(const char *fname, unsigned line, const char *buffer) {
1349
1350 static const Specifier specifier_table[] = {
1351 { 'm', specifier_machine_id, NULL },
1352 { 'b', specifier_boot_id, NULL },
1353 { 'H', specifier_host_name, NULL },
1354 { 'v', specifier_kernel_release, NULL },
1355 {}
1356 };
1357
7b1aaf66
ZJS
1358 _cleanup_free_ char *action = NULL,
1359 *name = NULL, *resolved_name = NULL,
1360 *id = NULL, *resolved_id = NULL,
1361 *description = NULL,
1362 *home = NULL,
1363 *shell, *resolved_shell = NULL;
1b992147
LP
1364 _cleanup_(item_freep) Item *i = NULL;
1365 Item *existing;
5bc9c980 1366 OrderedHashmap *h;
7629889c
LP
1367 int r;
1368 const char *p;
1b992147
LP
1369
1370 assert(fname);
1371 assert(line >= 1);
1372 assert(buffer);
1373
7629889c
LP
1374 /* Parse columns */
1375 p = buffer;
7b1aaf66
ZJS
1376 r = extract_many_words(&p, NULL, EXTRACT_QUOTES,
1377 &action, &name, &id, &description, &home, &shell, NULL);
7629889c 1378 if (r < 0) {
1b992147 1379 log_error("[%s:%u] Syntax error.", fname, line);
7629889c
LP
1380 return r;
1381 }
1382 if (r < 2) {
1383 log_error("[%s:%u] Missing action and name columns.", fname, line);
1384 return -EINVAL;
1385 }
4b1c1753 1386 if (!isempty(p)) {
7629889c
LP
1387 log_error("[%s:%u] Trailing garbage.", fname, line);
1388 return -EINVAL;
1b992147
LP
1389 }
1390
7629889c 1391 /* Verify action */
1b992147
LP
1392 if (strlen(action) != 1) {
1393 log_error("[%s:%u] Unknown modifier '%s'", fname, line, action);
1394 return -EINVAL;
1395 }
1396
8530dc44 1397 if (!IN_SET(action[0], ADD_USER, ADD_GROUP, ADD_MEMBER, ADD_RANGE)) {
61233823 1398 log_error("[%s:%u] Unknown command type '%c'.", fname, line, action[0]);
1b992147
LP
1399 return -EBADMSG;
1400 }
1401
7629889c 1402 /* Verify name */
97b11eed
DH
1403 if (isempty(name) || streq(name, "-"))
1404 name = mfree(name);
1b992147 1405
8530dc44
LP
1406 if (name) {
1407 r = specifier_printf(name, specifier_table, NULL, &resolved_name);
1408 if (r < 0) {
1409 log_error("[%s:%u] Failed to replace specifiers: %s", fname, line, name);
1410 return r;
1411 }
1412
1413 if (!valid_user_group_name(resolved_name)) {
1414 log_error("[%s:%u] '%s' is not a valid user or group name.", fname, line, resolved_name);
1415 return -EINVAL;
1416 }
1b992147
LP
1417 }
1418
8530dc44 1419 /* Verify id */
97b11eed
DH
1420 if (isempty(id) || streq(id, "-"))
1421 id = mfree(id);
7629889c 1422
8530dc44
LP
1423 if (id) {
1424 r = specifier_printf(id, specifier_table, NULL, &resolved_id);
1425 if (r < 0) {
1426 log_error("[%s:%u] Failed to replace specifiers: %s", fname, line, name);
1427 return r;
1428 }
1429 }
1430
1431 /* Verify description */
97b11eed
DH
1432 if (isempty(description) || streq(description, "-"))
1433 description = mfree(description);
1b992147 1434
8530dc44
LP
1435 if (description) {
1436 if (!valid_gecos(description)) {
1437 log_error("[%s:%u] '%s' is not a valid GECOS field.", fname, line, description);
1438 return -EINVAL;
1439 }
1440 }
1441
1442 /* Verify home */
97b11eed
DH
1443 if (isempty(home) || streq(home, "-"))
1444 home = mfree(home);
1b992147 1445
8530dc44
LP
1446 if (home) {
1447 if (!valid_home(home)) {
1448 log_error("[%s:%u] '%s' is not a valid home directory field.", fname, line, home);
1449 return -EINVAL;
1450 }
1451 }
1452
7b1aaf66
ZJS
1453 /* Verify shell */
1454 if (isempty(shell) || streq(shell, "-"))
1455 shell = mfree(shell);
1456
1457 if (shell) {
1458 r = specifier_printf(shell, specifier_table, NULL, &resolved_shell);
1459 if (r < 0) {
1460 log_error("[%s:%u] Failed to replace specifiers: %s", fname, line, shell);
1461 return r;
1462 }
1463
1464 if (!valid_shell(resolved_shell)) {
1465 log_error("[%s:%u] '%s' is not a valid login shell field.", fname, line, resolved_shell);
1466 return -EINVAL;
1467 }
1468 }
1469
1470
a12b0cc3 1471 switch (action[0]) {
1b992147 1472
8530dc44
LP
1473 case ADD_RANGE:
1474 if (resolved_name) {
1475 log_error("[%s:%u] Lines of type 'r' don't take a name field.", fname, line);
1476 return -EINVAL;
1477 }
1b992147 1478
8530dc44
LP
1479 if (!resolved_id) {
1480 log_error("[%s:%u] Lines of type 'r' require a ID range in the third field.", fname, line);
1481 return -EINVAL;
1482 }
1b992147 1483
7b1aaf66
ZJS
1484 if (description || home || shell) {
1485 log_error("[%s:%u] Lines of type '%c' don't take a %s field.",
1486 fname, line, action[0],
1487 description ? "GECOS" : home ? "home directory" : "login shell");
7629889c
LP
1488 return -EINVAL;
1489 }
1490
8530dc44
LP
1491 r = uid_range_add_str(&uid_range, &n_uid_range, resolved_id);
1492 if (r < 0) {
1493 log_error("[%s:%u] Invalid UID range %s.", fname, line, resolved_id);
a12b0cc3
LP
1494 return -EINVAL;
1495 }
1b992147 1496
8530dc44
LP
1497 return 0;
1498
1499 case ADD_MEMBER: {
1500 char **l;
1501
1502 /* Try to extend an existing member or group item */
1503 if (!name) {
1504 log_error("[%s:%u] Lines of type 'm' require a user name in the second field.", fname, line);
1505 return -EINVAL;
1506 }
1507
1508 if (!resolved_id) {
1509 log_error("[%s:%u] Lines of type 'm' require a group name in the third field.", fname, line);
1510 return -EINVAL;
a12b0cc3 1511 }
1b992147 1512
a12b0cc3
LP
1513 if (!valid_user_group_name(resolved_id)) {
1514 log_error("[%s:%u] '%s' is not a valid user or group name.", fname, line, resolved_id);
1515 return -EINVAL;
1516 }
1b992147 1517
7b1aaf66
ZJS
1518 if (description || home || shell) {
1519 log_error("[%s:%u] Lines of type '%c' don't take a %s field.",
1520 fname, line, action[0],
1521 description ? "GECOS" : home ? "home directory" : "login shell");
8530dc44
LP
1522 return -EINVAL;
1523 }
1524
5bc9c980 1525 r = ordered_hashmap_ensure_allocated(&members, &string_hash_ops);
7629889c
LP
1526 if (r < 0)
1527 return log_oom();
a12b0cc3 1528
5bc9c980 1529 l = ordered_hashmap_get(members, resolved_id);
a12b0cc3
LP
1530 if (l) {
1531 /* A list for this group name already exists, let's append to it */
1532 r = strv_push(&l, resolved_name);
1533 if (r < 0)
1534 return log_oom();
1535
1536 resolved_name = NULL;
1537
5bc9c980 1538 assert_se(ordered_hashmap_update(members, resolved_id, l) >= 0);
1b992147 1539 } else {
a12b0cc3
LP
1540 /* No list for this group name exists yet, create one */
1541
1542 l = new0(char *, 2);
1543 if (!l)
1544 return -ENOMEM;
1545
1546 l[0] = resolved_name;
1547 l[1] = NULL;
1b992147 1548
5bc9c980 1549 r = ordered_hashmap_put(members, resolved_id, l);
1b992147 1550 if (r < 0) {
a12b0cc3
LP
1551 free(l);
1552 return log_oom();
1b992147
LP
1553 }
1554
a12b0cc3 1555 resolved_id = resolved_name = NULL;
1b992147 1556 }
a12b0cc3
LP
1557
1558 return 0;
1b992147
LP
1559 }
1560
a12b0cc3 1561 case ADD_USER:
8530dc44
LP
1562 if (!name) {
1563 log_error("[%s:%u] Lines of type 'u' require a user name in the second field.", fname, line);
1564 return -EINVAL;
1565 }
1566
5bc9c980 1567 r = ordered_hashmap_ensure_allocated(&users, &string_hash_ops);
a12b0cc3
LP
1568 if (r < 0)
1569 return log_oom();
1570
1571 i = new0(Item, 1);
1572 if (!i)
1573 return log_oom();
1574
8530dc44
LP
1575 if (resolved_id) {
1576 if (path_is_absolute(resolved_id)) {
1577 i->uid_path = resolved_id;
1578 resolved_id = NULL;
a12b0cc3
LP
1579
1580 path_kill_slashes(i->uid_path);
a12b0cc3 1581 } else {
4cb41413
MV
1582 _cleanup_free_ char *uid = NULL, *gid = NULL;
1583 if (split_pair(resolved_id, ":", &uid, &gid) == 0) {
1584 r = parse_gid(gid, &i->gid);
1585 if (r < 0)
1586 return log_error_errno(r, "Failed to parse GID: '%s': %m", id);
1587 i->gid_set = true;
b9ee05c2 1588 i->id_set_strict = true;
4cb41413 1589 free_and_replace(resolved_id, uid);
a12b0cc3 1590 }
4cb41413
MV
1591 r = parse_uid(resolved_id, &i->uid);
1592 if (r < 0)
1593 return log_error_errno(r, "Failed to parse UID: '%s': %m", id);
a12b0cc3
LP
1594
1595 i->uid_set = true;
1596 }
1597 }
1598
8530dc44
LP
1599 i->description = description;
1600 description = NULL;
7629889c 1601
8530dc44
LP
1602 i->home = home;
1603 home = NULL;
a12b0cc3 1604
7b1aaf66
ZJS
1605 i->shell = resolved_shell;
1606 resolved_shell = NULL;
1607
1b992147 1608 h = users;
a12b0cc3
LP
1609 break;
1610
1611 case ADD_GROUP:
8530dc44
LP
1612 if (!name) {
1613 log_error("[%s:%u] Lines of type 'g' require a user name in the second field.", fname, line);
1614 return -EINVAL;
1615 }
a12b0cc3 1616
7b1aaf66
ZJS
1617 if (description || home || shell) {
1618 log_error("[%s:%u] Lines of type '%c' don't take a %s field.",
1619 fname, line, action[0],
1620 description ? "GECOS" : home ? "home directory" : "login shell");
7629889c
LP
1621 return -EINVAL;
1622 }
1623
5bc9c980 1624 r = ordered_hashmap_ensure_allocated(&groups, &string_hash_ops);
7629889c
LP
1625 if (r < 0)
1626 return log_oom();
1627
a12b0cc3
LP
1628 i = new0(Item, 1);
1629 if (!i)
1630 return log_oom();
1631
8530dc44
LP
1632 if (resolved_id) {
1633 if (path_is_absolute(resolved_id)) {
1634 i->gid_path = resolved_id;
1635 resolved_id = NULL;
a12b0cc3
LP
1636
1637 path_kill_slashes(i->gid_path);
1638 } else {
8530dc44 1639 r = parse_gid(resolved_id, &i->gid);
4cb41413
MV
1640 if (r < 0)
1641 return log_error_errno(r, "Failed to parse GID: '%s': %m", id);
a12b0cc3
LP
1642
1643 i->gid_set = true;
1644 }
1645 }
1646
1b992147 1647 h = groups;
a12b0cc3 1648 break;
8530dc44 1649
bce415ed
RC
1650 default:
1651 return -EBADMSG;
1b992147 1652 }
a12b0cc3
LP
1653
1654 i->type = action[0];
1655 i->name = resolved_name;
1656 resolved_name = NULL;
1b992147 1657
5bc9c980 1658 existing = ordered_hashmap_get(h, i->name);
1b992147
LP
1659 if (existing) {
1660
1661 /* Two identical items are fine */
1662 if (!item_equal(existing, i))
1663 log_warning("Two or more conflicting lines for %s configured, ignoring.", i->name);
1664
1665 return 0;
1666 }
1667
5bc9c980 1668 r = ordered_hashmap_put(h, i->name, i);
a12b0cc3
LP
1669 if (r < 0)
1670 return log_oom();
1b992147
LP
1671
1672 i = NULL;
1673 return 0;
1674}
1675
1676static int read_config_file(const char *fn, bool ignore_enoent) {
dfc87cbf
LP
1677 _cleanup_fclose_ FILE *rf = NULL;
1678 FILE *f = NULL;
1b992147
LP
1679 char line[LINE_MAX];
1680 unsigned v = 0;
c4640902 1681 int r = 0;
1b992147
LP
1682
1683 assert(fn);
1684
dfc87cbf
LP
1685 if (streq(fn, "-"))
1686 f = stdin;
1687 else {
1688 r = search_and_fopen_nulstr(fn, "re", arg_root, conf_file_dirs, &rf);
1689 if (r < 0) {
1690 if (ignore_enoent && r == -ENOENT)
1691 return 0;
1b992147 1692
8d3d7072 1693 return log_error_errno(r, "Failed to open '%s', ignoring: %m", fn);
dfc87cbf
LP
1694 }
1695
1696 f = rf;
1b992147
LP
1697 }
1698
1699 FOREACH_LINE(line, f, break) {
1700 char *l;
1701 int k;
1702
1703 v++;
1704
1705 l = strstrip(line);
4c701096 1706 if (IN_SET(*l, 0, '#'))
1b992147
LP
1707 continue;
1708
1709 k = parse_line(fn, v, l);
1710 if (k < 0 && r == 0)
1711 r = k;
1712 }
1713
1714 if (ferror(f)) {
56f64d95 1715 log_error_errno(errno, "Failed to read from file %s: %m", fn);
1b992147
LP
1716 if (r == 0)
1717 r = -EIO;
1718 }
1719
1720 return r;
1721}
1722
1b992147
LP
1723static void free_database(Hashmap *by_name, Hashmap *by_id) {
1724 char *name;
1725
1726 for (;;) {
1727 name = hashmap_first(by_id);
1728 if (!name)
1729 break;
1730
1731 hashmap_remove(by_name, name);
1732
1733 hashmap_steal_first_key(by_id);
1734 free(name);
1735 }
1736
1737 while ((name = hashmap_steal_first_key(by_name)))
1738 free(name);
1739
1740 hashmap_free(by_name);
1741 hashmap_free(by_id);
1742}
1743
601185b4 1744static void help(void) {
1b992147
LP
1745 printf("%s [OPTIONS...] [CONFIGURATION FILE...]\n\n"
1746 "Creates system user accounts.\n\n"
1747 " -h --help Show this help\n"
1748 " --version Show package version\n"
601185b4 1749 " --root=PATH Operate on an alternate filesystem root\n"
d16a1c1b 1750 " --replace=PATH Treat arguments as replacement for PATH\n"
1b600bd5 1751 " --inline Treat arguments as configuration lines\n"
601185b4 1752 , program_invocation_short_name);
1b992147
LP
1753}
1754
1755static int parse_argv(int argc, char *argv[]) {
1756
1757 enum {
1758 ARG_VERSION = 0x100,
1759 ARG_ROOT,
d16a1c1b 1760 ARG_REPLACE,
1b600bd5 1761 ARG_INLINE,
1b992147
LP
1762 };
1763
1764 static const struct option options[] = {
1765 { "help", no_argument, NULL, 'h' },
1766 { "version", no_argument, NULL, ARG_VERSION },
1767 { "root", required_argument, NULL, ARG_ROOT },
d16a1c1b 1768 { "replace", required_argument, NULL, ARG_REPLACE },
1b600bd5 1769 { "inline", no_argument, NULL, ARG_INLINE },
1b992147
LP
1770 {}
1771 };
1772
0f474365 1773 int c, r;
1b992147
LP
1774
1775 assert(argc >= 0);
1776 assert(argv);
1777
601185b4 1778 while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0)
1b992147
LP
1779
1780 switch (c) {
1781
1782 case 'h':
601185b4
ZJS
1783 help();
1784 return 0;
1b992147
LP
1785
1786 case ARG_VERSION:
3f6fd1ba 1787 return version();
1b992147
LP
1788
1789 case ARG_ROOT:
0f03c2a4 1790 r = parse_path_argument_and_warn(optarg, true, &arg_root);
0f474365 1791 if (r < 0)
0f03c2a4 1792 return r;
1b992147
LP
1793 break;
1794
d16a1c1b
ZJS
1795 case ARG_REPLACE:
1796 if (!path_is_absolute(optarg) ||
1797 !endswith(optarg, ".conf")) {
1798 log_error("The argument to --replace= must an absolute path to a config file");
1799 return -EINVAL;
1800 }
1801
1802 arg_replace = optarg;
1803 break;
1804
1b600bd5
ZJS
1805 case ARG_INLINE:
1806 arg_inline = true;
1807 break;
1808
1b992147
LP
1809 case '?':
1810 return -EINVAL;
1811
1812 default:
1813 assert_not_reached("Unhandled option");
1814 }
1b992147 1815
d16a1c1b
ZJS
1816 if (arg_replace && optind >= argc) {
1817 log_error("When --replace= is given, some configuration items must be specified");
1818 return -EINVAL;
1819 }
1820
1b992147
LP
1821 return 1;
1822}
1823
d16a1c1b
ZJS
1824static int parse_arguments(char **args) {
1825 char **arg;
1826 unsigned pos = 1;
1827 int r;
1828
1829 STRV_FOREACH(arg, args) {
1830 if (arg_inline)
1831 /* Use (argument):n, where n==1 for the first positional arg */
1832 r = parse_line("(argument)", pos, *arg);
1833 else
1834 r = read_config_file(*arg, false);
1835 if (r < 0)
1836 return r;
1837
1838 pos++;
1839 }
1840
1841 return 0;
1842}
1843
1844static int read_config_files(const char* dirs, char **args) {
1845 _cleanup_strv_free_ char **files = NULL;
1846 _cleanup_free_ char *p = NULL;
1847 char **f;
1848 int r;
1849
1850 r = conf_files_list_nulstr(&files, ".conf", arg_root, 0, dirs);
1851 if (r < 0)
1852 return log_error_errno(r, "Failed to enumerate sysusers.d files: %m");
1853
1854 if (arg_replace) {
1855 r = conf_files_insert(&files, arg_root, dirs, arg_replace);
1856 if (r < 0)
1857 return log_error_errno(r, "Failed to extend sysusers.d file list: %m");
1858
1859 p = path_join(arg_root, arg_replace, NULL);
1860 if (!p)
1861 return log_oom();
1862 }
1863
1864 STRV_FOREACH(f, files)
1865 if (p && path_equal(*f, p)) {
1866 log_debug("Parsing arguments at position \"%s\"…", *f);
1867
1868 r = parse_arguments(args);
1869 if (r < 0)
1870 return r;
1871 } else {
1872 log_debug("Reading config file \"%s\"…", *f);
1873
1874 /* Just warn, ignore result otherwise */
1875 (void) read_config_file(*f, true);
1876 }
1877
1878 return 0;
1879}
1880
1b992147
LP
1881int main(int argc, char *argv[]) {
1882
1883 _cleanup_close_ int lock = -1;
1884 Iterator iterator;
d16a1c1b 1885 int r;
1b992147 1886 Item *i;
a12b0cc3 1887 char *n;
1b992147
LP
1888
1889 r = parse_argv(argc, argv);
1890 if (r <= 0)
1891 goto finish;
1892
1893 log_set_target(LOG_TARGET_AUTO);
1894 log_parse_environment();
1895 log_open();
1896
1897 umask(0022);
1898
c3dacc8b 1899 r = mac_selinux_init();
9f1c1940 1900 if (r < 0) {
da927ba9 1901 log_error_errno(r, "SELinux setup failed: %m");
9f1c1940
ZJS
1902 goto finish;
1903 }
1b992147 1904
d16a1c1b
ZJS
1905 /* If command line arguments are specified along with --replace, read all
1906 * configuration files and insert the positional arguments at the specified
1907 * place. Otherwise, if command line arguments are specified, execute just
1908 * them, and finally, without --replace= or any positional arguments, just
1909 * read configuration and execute it.
1910 */
1911 if (arg_replace || optind >= argc)
1912 r = read_config_files(conf_file_dirs, argv + optind);
1913 else
1914 r = parse_arguments(argv + optind);
1915 if (r < 0)
1916 goto finish;
1b992147 1917
fe102d6a
LP
1918 /* Let's tell nss-systemd not to synthesize the "root" and "nobody" entries for it, so that our detection
1919 * whether the names or UID/GID area already used otherwise doesn't get confused. After all, even though
1920 * nss-systemd synthesizes these users/groups, they should still appear in /etc/passwd and /etc/group, as the
1921 * synthesizing logic is merely supposed to be fallback for cases where we run with a completely unpopulated
1922 * /etc. */
1923 if (setenv("SYSTEMD_NSS_BYPASS_SYNTHETIC", "1", 1) < 0) {
1924 r = log_error_errno(errno, "Failed to set SYSTEMD_NSS_BYPASS_SYNTHETIC environment variable: %m");
1925 goto finish;
1926 }
1927
8530dc44 1928 if (!uid_range) {
b9ee05c2 1929 /* Default to default range of 1..SYSTEM_UID_MAX */
8530dc44
LP
1930 r = uid_range_add(&uid_range, &n_uid_range, 1, SYSTEM_UID_MAX);
1931 if (r < 0) {
1932 log_oom();
1933 goto finish;
1934 }
1935 }
1936
a12b0cc3
LP
1937 r = add_implicit();
1938 if (r < 0)
1939 goto finish;
1940
e929bee0 1941 lock = take_etc_passwd_lock(arg_root);
1b992147 1942 if (lock < 0) {
d1e4b8fd 1943 log_error_errno(lock, "Failed to take /etc/passwd lock: %m");
1b992147
LP
1944 goto finish;
1945 }
1946
1947 r = load_user_database();
1948 if (r < 0) {
da927ba9 1949 log_error_errno(r, "Failed to load user database: %m");
1b992147
LP
1950 goto finish;
1951 }
1952
1953 r = load_group_database();
1954 if (r < 0) {
da927ba9 1955 log_error_errno(r, "Failed to read group database: %m");
1b992147
LP
1956 goto finish;
1957 }
1958
5bc9c980 1959 ORDERED_HASHMAP_FOREACH(i, groups, iterator)
1b992147
LP
1960 process_item(i);
1961
5bc9c980 1962 ORDERED_HASHMAP_FOREACH(i, users, iterator)
1b992147
LP
1963 process_item(i);
1964
1965 r = write_files();
1966 if (r < 0)
da927ba9 1967 log_error_errno(r, "Failed to write files: %m");
1b992147
LP
1968
1969finish:
5bc9c980
MV
1970 ordered_hashmap_free_with_destructor(groups, item_free);
1971 ordered_hashmap_free_with_destructor(users, item_free);
1b992147 1972
5bc9c980
MV
1973 while ((n = ordered_hashmap_first_key(members))) {
1974 strv_free(ordered_hashmap_steal_first(members));
a12b0cc3
LP
1975 free(n);
1976 }
5bc9c980 1977 ordered_hashmap_free(members);
224b0e7a 1978
5bc9c980
MV
1979 ordered_hashmap_free(todo_uids);
1980 ordered_hashmap_free(todo_gids);
1b992147
LP
1981
1982 free_database(database_user, database_uid);
1983 free_database(database_group, database_gid);
1984
1985 free(arg_root);
1986
1987 return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
1988}