]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/sysusers/sysusers.c
d5a236703af7d744e35824d643602a2d25aefc0b
[thirdparty/systemd.git] / src / sysusers / sysusers.c
1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
2
3 #include <getopt.h>
4 #include <stdlib.h>
5 #include <sys/stat.h>
6
7 #include "alloc-util.h"
8 #include "build.h"
9 #include "chase.h"
10 #include "conf-files.h"
11 #include "constants.h"
12 #include "copy.h"
13 #include "creds-util.h"
14 #include "dissect-image.h"
15 #include "env-util.h"
16 #include "errno-util.h"
17 #include "extract-word.h"
18 #include "fd-util.h"
19 #include "fileio.h"
20 #include "format-util.h"
21 #include "fs-util.h"
22 #include "hashmap.h"
23 #include "image-policy.h"
24 #include "label-util.h"
25 #include "libaudit-util.h"
26 #include "libcrypt-util.h"
27 #include "log.h"
28 #include "loop-util.h"
29 #include "main-func.h"
30 #include "mount-util.h"
31 #include "pager.h"
32 #include "parse-argument.h"
33 #include "path-util.h"
34 #include "pretty-print.h"
35 #include "set.h"
36 #include "smack-util.h"
37 #include "specifier.h"
38 #include "string-util.h"
39 #include "strv.h"
40 #include "sync-util.h"
41 #include "time-util.h"
42 #include "tmpfile-util-label.h"
43 #include "uid-classification.h"
44 #include "uid-range.h"
45 #include "user-util.h"
46 #include "verbs.h"
47
48 typedef enum ItemType {
49 ADD_USER = 'u',
50 ADD_GROUP = 'g',
51 ADD_MEMBER = 'm',
52 ADD_RANGE = 'r',
53 } ItemType;
54
55 static const char* item_type_to_string(ItemType t) {
56 switch (t) {
57 case ADD_USER:
58 return "user";
59 case ADD_GROUP:
60 return "group";
61 case ADD_MEMBER:
62 return "member";
63 case ADD_RANGE:
64 return "range";
65 default:
66 assert_not_reached();
67 }
68 }
69
70 typedef struct Item {
71 ItemType type;
72
73 char *name;
74 char *group_name;
75 char *uid_path;
76 char *gid_path;
77 char *description;
78 char *home;
79 char *shell;
80
81 gid_t gid;
82 uid_t uid;
83
84 char *filename;
85 unsigned line;
86
87 bool gid_set;
88
89 /* When set the group with the specified GID must exist
90 * and the check if a UID clashes with the GID is skipped.
91 */
92 bool id_set_strict;
93
94 bool uid_set;
95
96 bool locked;
97
98 bool todo_user;
99 bool todo_group;
100 } Item;
101
102 static char *arg_root = NULL;
103 static char *arg_image = NULL;
104 static CatFlags arg_cat_flags = CAT_CONFIG_OFF;
105 static const char *arg_replace = NULL;
106 static bool arg_dry_run = false;
107 static bool arg_inline = false;
108 static PagerFlags arg_pager_flags = 0;
109 static ImagePolicy *arg_image_policy = NULL;
110
111 STATIC_DESTRUCTOR_REGISTER(arg_root, freep);
112 STATIC_DESTRUCTOR_REGISTER(arg_image, freep);
113 STATIC_DESTRUCTOR_REGISTER(arg_image_policy, image_policy_freep);
114
115 typedef struct Context {
116 int audit_fd;
117
118 OrderedHashmap *users, *groups;
119 OrderedHashmap *todo_uids, *todo_gids;
120 OrderedHashmap *members;
121
122 Hashmap *database_by_uid, *database_by_username;
123 Hashmap *database_by_gid, *database_by_groupname;
124
125 /* A helper set to hold names that are used by database_by_{uid,gid,username,groupname} above. */
126 Set *names;
127
128 uid_t search_uid;
129 UIDRange *uid_range;
130
131 UGIDAllocationRange login_defs;
132 bool login_defs_need_warning;
133 } Context;
134
135 static void context_done(Context *c) {
136 assert(c);
137
138 c->audit_fd = close_audit_fd(c->audit_fd);
139
140 ordered_hashmap_free(c->groups);
141 ordered_hashmap_free(c->users);
142 ordered_hashmap_free(c->members);
143 ordered_hashmap_free(c->todo_uids);
144 ordered_hashmap_free(c->todo_gids);
145
146 hashmap_free(c->database_by_uid);
147 hashmap_free(c->database_by_username);
148 hashmap_free(c->database_by_gid);
149 hashmap_free(c->database_by_groupname);
150
151 set_free(c->names);
152 uid_range_free(c->uid_range);
153 }
154
155 static void maybe_emit_login_defs_warning(Context *c) {
156 assert(c);
157
158 if (!c->login_defs_need_warning)
159 return;
160
161 if (c->login_defs.system_alloc_uid_min != SYSTEM_ALLOC_UID_MIN ||
162 c->login_defs.system_uid_max != SYSTEM_UID_MAX)
163 log_warning("login.defs specifies UID allocation range "UID_FMT"–"UID_FMT
164 " that is different than the built-in defaults ("UID_FMT"–"UID_FMT")",
165 c->login_defs.system_alloc_uid_min, c->login_defs.system_uid_max,
166 (uid_t) SYSTEM_ALLOC_UID_MIN, (uid_t) SYSTEM_UID_MAX);
167 if (c->login_defs.system_alloc_gid_min != SYSTEM_ALLOC_GID_MIN ||
168 c->login_defs.system_gid_max != SYSTEM_GID_MAX)
169 log_warning("login.defs specifies GID allocation range "GID_FMT"–"GID_FMT
170 " that is different than the built-in defaults ("GID_FMT"–"GID_FMT")",
171 c->login_defs.system_alloc_gid_min, c->login_defs.system_gid_max,
172 (gid_t) SYSTEM_ALLOC_GID_MIN, (gid_t) SYSTEM_GID_MAX);
173
174 c->login_defs_need_warning = false;
175 }
176
177 static void log_audit_accounts(Context *c, ItemType what) {
178 #if HAVE_AUDIT
179 assert(c);
180 assert(IN_SET(what, ADD_USER, ADD_GROUP));
181
182 if (arg_dry_run || c->audit_fd < 0)
183 return;
184
185 Item *i;
186 int type = what == ADD_USER ? AUDIT_ADD_USER : AUDIT_ADD_GROUP;
187 const char *op = what == ADD_USER ? "adding-user" : "adding-group";
188
189 /* Notes:
190 *
191 * The op must not contain whitespace. The format with a dash matches what Fedora shadow-utils uses.
192 *
193 * We send id == -1, even though we know the number, in particular on success. This is because if we
194 * send the id, the generated audit message will not contain the name. The name seems more useful
195 * than the number, hence send just the name:
196 *
197 * type=ADD_USER msg=audit(01/10/2025 16:02:00.639:3854) :
198 * pid=3846380 uid=root auid=zbyszek ses=2 msg='op=adding-user id=unknown(952) exe=systemd-sysusers ... res=success'
199 * vs.
200 * type=ADD_USER msg=audit(01/10/2025 16:03:15.457:3908) :
201 * pid=3846607 uid=root auid=zbyszek ses=2 msg='op=adding-user acct=foo5 exe=systemd-sysusers ... res=success'
202 */
203
204 ORDERED_HASHMAP_FOREACH(i, what == ADD_USER ? c->todo_uids : c->todo_gids)
205 audit_log_acct_message(
206 c->audit_fd,
207 type,
208 program_invocation_short_name,
209 op,
210 i->name,
211 /* id= */ (unsigned) -1,
212 /* host= */ NULL,
213 /* addr= */ NULL,
214 /* tty= */ NULL,
215 /* success= */ 1);
216 #endif
217 }
218
219 static int load_user_database(Context *c) {
220 _cleanup_free_ char *passwd_path = NULL;
221 _cleanup_fclose_ FILE *f = NULL;
222 struct passwd *pw;
223 int r;
224
225 assert(c);
226
227 r = chase_and_fopen_unlocked("/etc/passwd", arg_root, CHASE_PREFIX_ROOT, "re", &passwd_path, &f);
228 if (r == -ENOENT)
229 return 0;
230 if (r < 0)
231 return r;
232
233 while ((r = fgetpwent_sane(f, &pw)) > 0) {
234
235 char *n = strdup(pw->pw_name);
236 if (!n)
237 return -ENOMEM;
238
239 /* Note that we use trivial_hash_ops_free here, so identical strings can exist in the set. */
240 r = set_ensure_consume(&c->names, &trivial_hash_ops_free, n);
241 if (r < 0)
242 return r;
243 assert(r > 0); /* The set uses pointer comparisons, so n must not be in the set. */
244
245 r = hashmap_ensure_put(&c->database_by_username, &string_hash_ops, n, UID_TO_PTR(pw->pw_uid));
246 if (r == -EEXIST)
247 log_debug_errno(r, "%s: user '%s' is listed twice, ignoring duplicate uid.",
248 passwd_path, n);
249 else if (r < 0)
250 return r;
251
252 r = hashmap_ensure_put(&c->database_by_uid, /* hash_ops= */ NULL, UID_TO_PTR(pw->pw_uid), n);
253 if (r == -EEXIST)
254 log_debug_errno(r, "%s: uid "UID_FMT" is listed twice, ignoring duplicate name.",
255 passwd_path, pw->pw_uid);
256 else if (r < 0)
257 return r;
258 }
259 return r;
260 }
261
262 static int load_group_database(Context *c) {
263 _cleanup_free_ char *group_path = NULL;
264 _cleanup_fclose_ FILE *f = NULL;
265 struct group *gr;
266 int r;
267
268 assert(c);
269
270 r = chase_and_fopen_unlocked("/etc/group", arg_root, CHASE_PREFIX_ROOT, "re", &group_path, &f);
271 if (r == -ENOENT)
272 return 0;
273 if (r < 0)
274 return r;
275
276 while ((r = fgetgrent_sane(f, &gr)) > 0) {
277 char *n = strdup(gr->gr_name);
278 if (!n)
279 return -ENOMEM;
280
281 /* Note that we use trivial_hash_ops_free here, so identical strings can exist in the set. */
282 r = set_ensure_consume(&c->names, &trivial_hash_ops_free, n);
283 if (r < 0)
284 return r;
285 assert(r > 0); /* The set uses pointer comparisons, so n must not be in the set. */
286
287 r = hashmap_ensure_put(&c->database_by_groupname, &string_hash_ops, n, GID_TO_PTR(gr->gr_gid));
288 if (r == -EEXIST)
289 log_debug_errno(r, "%s: group '%s' is listed twice, ignoring duplicate gid.",
290 group_path, n);
291 else if (r < 0)
292 return r;
293
294 r = hashmap_ensure_put(&c->database_by_gid, /* hash_ops= */ NULL, GID_TO_PTR(gr->gr_gid), n);
295 if (r == -EEXIST)
296 log_debug_errno(r, "%s: gid "GID_FMT" is listed twice, ignoring duplicate name.",
297 group_path, gr->gr_gid);
298 else if (r < 0)
299 return r;
300 }
301 return r;
302 }
303
304 static int make_backup(const char *target, const char *x) {
305 _cleanup_(unlink_and_freep) char *dst_tmp = NULL;
306 _cleanup_fclose_ FILE *dst = NULL;
307 _cleanup_close_ int src = -EBADF;
308 const char *backup;
309 struct stat st;
310 int r;
311
312 assert(target);
313 assert(x);
314
315 src = open(x, O_RDONLY|O_CLOEXEC|O_NOCTTY);
316 if (src < 0) {
317 if (errno == ENOENT) /* No backup necessary... */
318 return 0;
319
320 return -errno;
321 }
322
323 if (fstat(src, &st) < 0)
324 return -errno;
325
326 r = fopen_temporary_label(
327 target, /* The path for which to the look up the label */
328 x, /* Where we want the file actually to end up */
329 &dst, /* The temporary file we write to */
330 &dst_tmp);
331 if (r < 0)
332 return r;
333
334 r = copy_bytes(src, fileno(dst), UINT64_MAX, COPY_REFLINK);
335 if (r < 0)
336 return r;
337
338 backup = strjoina(x, "-");
339
340 /* Copy over the access mask. Don't fail on chmod() or chown(). If it stays owned by us and/or
341 * unreadable by others, then it isn't too bad... */
342 r = fchmod_and_chown_with_fallback(fileno(dst), dst_tmp, st.st_mode & 07777, st.st_uid, st.st_gid);
343 if (r < 0)
344 log_warning_errno(r, "Failed to change access mode or ownership of %s: %m", backup);
345
346 if (futimens(fileno(dst), (const struct timespec[2]) { st.st_atim, st.st_mtim }) < 0)
347 log_warning_errno(errno, "Failed to fix access and modification time of %s: %m", backup);
348
349 r = fsync_full(fileno(dst));
350 if (r < 0)
351 return r;
352
353 if (rename(dst_tmp, backup) < 0)
354 return errno;
355
356 dst_tmp = mfree(dst_tmp); /* disable the unlink_and_freep() hook now that the file has been renamed */
357 return 0;
358 }
359
360 static int putgrent_with_members(
361 Context *c,
362 const struct group *gr,
363 FILE *group) {
364
365 char **a;
366 int r;
367
368 assert(c);
369 assert(gr);
370 assert(group);
371
372 a = ordered_hashmap_get(c->members, gr->gr_name);
373 if (a) {
374 _cleanup_strv_free_ char **l = NULL;
375 bool added = false;
376
377 l = strv_copy(gr->gr_mem);
378 if (!l)
379 return -ENOMEM;
380
381 STRV_FOREACH(i, a) {
382 if (strv_contains(l, *i))
383 continue;
384
385 r = strv_extend(&l, *i);
386 if (r < 0)
387 return r;
388
389 added = true;
390 }
391
392 if (added) {
393 struct group t;
394
395 strv_sort_uniq(l);
396
397 t = *gr;
398 t.gr_mem = l;
399
400 r = putgrent_sane(&t, group);
401 return r < 0 ? r : 1;
402 }
403 }
404
405 return putgrent_sane(gr, group);
406 }
407
408 #if ENABLE_GSHADOW
409 static int putsgent_with_members(
410 Context *c,
411 const struct sgrp *sg,
412 FILE *gshadow) {
413
414 char **a;
415 int r;
416
417 assert(sg);
418 assert(gshadow);
419
420 a = ordered_hashmap_get(c->members, sg->sg_namp);
421 if (a) {
422 _cleanup_strv_free_ char **l = NULL;
423 bool added = false;
424
425 l = strv_copy(sg->sg_mem);
426 if (!l)
427 return -ENOMEM;
428
429 STRV_FOREACH(i, a) {
430 if (strv_contains(l, *i))
431 continue;
432
433 r = strv_extend(&l, *i);
434 if (r < 0)
435 return r;
436
437 added = true;
438 }
439
440 if (added) {
441 struct sgrp t;
442
443 strv_sort_uniq(l);
444
445 t = *sg;
446 t.sg_mem = l;
447
448 r = putsgent_sane(&t, gshadow);
449 return r < 0 ? r : 1;
450 }
451 }
452
453 return putsgent_sane(sg, gshadow);
454 }
455 #endif
456
457 static const char* pick_shell(const Item *i) {
458 assert(i);
459
460 if (i->type != ADD_USER)
461 return NULL;
462 if (i->shell)
463 return i->shell;
464 if (i->uid_set && i->uid == 0)
465 return default_root_shell(arg_root);
466 return NOLOGIN;
467 }
468
469 static int write_temporary_passwd(
470 Context *c,
471 const char *passwd_path,
472 FILE **ret_tmpfile,
473 char **ret_tmpfile_path) {
474
475 _cleanup_fclose_ FILE *original = NULL, *passwd = NULL;
476 _cleanup_(unlink_and_freep) char *passwd_tmp = NULL;
477 struct passwd *pw = NULL;
478 Item *i;
479 int r;
480
481 assert(c);
482
483 if (ordered_hashmap_isempty(c->todo_uids))
484 goto done;
485
486 if (arg_dry_run) {
487 log_info("Would write /etc/passwd%s", glyph(GLYPH_ELLIPSIS));
488 goto done;
489 }
490
491 r = fopen_temporary_label("/etc/passwd", passwd_path, &passwd, &passwd_tmp);
492 if (r < 0)
493 return log_debug_errno(r, "Failed to open temporary copy of %s: %m", passwd_path);
494
495 original = fopen(passwd_path, "re");
496 if (original) {
497
498 /* Allow fallback path for when /proc is not mounted. On any normal system /proc will be
499 * mounted, but e.g. when 'dnf --installroot' is used, it might not be. There is no security
500 * relevance here, since the environment is ultimately trusted, and not requiring /proc makes
501 * it easier to depend on sysusers in packaging scripts and suchlike. */
502 r = copy_rights_with_fallback(fileno(original), fileno(passwd), passwd_tmp);
503 if (r < 0)
504 return log_debug_errno(r, "Failed to copy permissions from %s to %s: %m",
505 passwd_path, passwd_tmp);
506
507 while ((r = fgetpwent_sane(original, &pw)) > 0) {
508 i = ordered_hashmap_get(c->users, pw->pw_name);
509 if (i && i->todo_user)
510 return log_error_errno(SYNTHETIC_ERRNO(EEXIST),
511 "%s: User \"%s\" already exists.",
512 passwd_path, pw->pw_name);
513
514 if (ordered_hashmap_contains(c->todo_uids, UID_TO_PTR(pw->pw_uid)))
515 return log_error_errno(SYNTHETIC_ERRNO(EEXIST),
516 "%s: Detected collision for UID " UID_FMT ".",
517 passwd_path, pw->pw_uid);
518
519 /* Make sure we keep the NIS entries (if any) at the end. */
520 if (IN_SET(pw->pw_name[0], '+', '-'))
521 break;
522
523 r = putpwent_sane(pw, passwd);
524 if (r < 0)
525 return log_debug_errno(r, "Failed to add existing user \"%s\" to temporary passwd file: %m",
526 pw->pw_name);
527 }
528 if (r < 0)
529 return log_debug_errno(r, "Failed to read %s: %m", passwd_path);
530
531 } else {
532 if (errno != ENOENT)
533 return log_debug_errno(errno, "Failed to open %s: %m", passwd_path);
534 if (fchmod(fileno(passwd), 0644) < 0)
535 return log_debug_errno(errno, "Failed to fchmod %s: %m", passwd_tmp);
536 }
537
538 ORDERED_HASHMAP_FOREACH(i, c->todo_uids) {
539 _cleanup_free_ char *creds_shell = NULL, *cn = NULL;
540
541 struct passwd n = {
542 .pw_name = i->name,
543 .pw_uid = i->uid,
544 .pw_gid = i->gid,
545 .pw_gecos = (char*) strempty(i->description),
546
547 /* "x" means the password is stored in the shadow file */
548 .pw_passwd = (char*) PASSWORD_SEE_SHADOW,
549
550 /* We default to the root directory as home */
551 .pw_dir = i->home ?: (char*) "/",
552
553 /* Initialize the shell to nologin, with one exception:
554 * for root we patch in something special */
555 .pw_shell = (char*) pick_shell(i),
556 };
557
558 /* Try to pick up the shell for this account via the credentials logic */
559 cn = strjoin("passwd.shell.", i->name);
560 if (!cn)
561 return -ENOMEM;
562
563 r = read_credential(cn, (void**) &creds_shell, NULL);
564 if (r < 0)
565 log_debug_errno(r, "Couldn't read credential '%s', ignoring: %m", cn);
566 else
567 n.pw_shell = creds_shell;
568
569 r = putpwent_sane(&n, passwd);
570 if (r < 0)
571 return log_debug_errno(r, "Failed to add new user \"%s\" to temporary passwd file: %m",
572 i->name);
573 }
574
575 /* Append the remaining NIS entries if any */
576 while (pw) {
577 r = putpwent_sane(pw, passwd);
578 if (r < 0)
579 return log_debug_errno(r, "Failed to add existing user \"%s\" to temporary passwd file: %m",
580 pw->pw_name);
581
582 r = fgetpwent_sane(original, &pw);
583 if (r < 0)
584 return log_debug_errno(r, "Failed to read %s: %m", passwd_path);
585 if (r == 0)
586 break;
587 }
588
589 r = fflush_sync_and_check(passwd);
590 if (r < 0)
591 return log_debug_errno(r, "Failed to flush %s: %m", passwd_tmp);
592
593 done:
594 *ret_tmpfile = TAKE_PTR(passwd);
595 *ret_tmpfile_path = TAKE_PTR(passwd_tmp);
596 return 0;
597 }
598
599 static usec_t epoch_or_now(void) {
600 uint64_t epoch;
601
602 if (secure_getenv_uint64("SOURCE_DATE_EPOCH", &epoch) >= 0) {
603 if (epoch > UINT64_MAX/USEC_PER_SEC) /* Overflow check */
604 return USEC_INFINITY;
605 return (usec_t) epoch * USEC_PER_SEC;
606 }
607
608 return now(CLOCK_REALTIME);
609 }
610
611 static int write_temporary_shadow(
612 Context *c,
613 const char *shadow_path,
614 FILE **ret_tmpfile,
615 char **ret_tmpfile_path) {
616
617 _cleanup_fclose_ FILE *original = NULL, *shadow = NULL;
618 _cleanup_(unlink_and_freep) char *shadow_tmp = NULL;
619 struct spwd *sp = NULL;
620 long lstchg;
621 Item *i;
622 int r;
623
624 assert(c);
625
626 if (ordered_hashmap_isempty(c->todo_uids))
627 goto done;
628
629 if (arg_dry_run) {
630 log_info("Would write /etc/shadow%s", glyph(GLYPH_ELLIPSIS));
631 goto done;
632 }
633
634 r = fopen_temporary_label("/etc/shadow", shadow_path, &shadow, &shadow_tmp);
635 if (r < 0)
636 return log_debug_errno(r, "Failed to open temporary copy of %s: %m", shadow_path);
637
638 lstchg = (long) (epoch_or_now() / USEC_PER_DAY);
639
640 original = fopen(shadow_path, "re");
641 if (original) {
642
643 r = copy_rights_with_fallback(fileno(original), fileno(shadow), shadow_tmp);
644 if (r < 0)
645 return log_debug_errno(r, "Failed to copy permissions from %s to %s: %m",
646 shadow_path, shadow_tmp);
647
648 while ((r = fgetspent_sane(original, &sp)) > 0) {
649 i = ordered_hashmap_get(c->users, sp->sp_namp);
650 if (i && i->todo_user) {
651 /* we will update the existing entry */
652 sp->sp_lstchg = lstchg;
653
654 /* only the /etc/shadow stage is left, so we can
655 * safely remove the item from the todo set */
656 i->todo_user = false;
657 ordered_hashmap_remove(c->todo_uids, UID_TO_PTR(i->uid));
658 }
659
660 /* Make sure we keep the NIS entries (if any) at the end. */
661 if (IN_SET(sp->sp_namp[0], '+', '-'))
662 break;
663
664 r = putspent_sane(sp, shadow);
665 if (r < 0)
666 return log_debug_errno(r, "Failed to add existing user \"%s\" to temporary shadow file: %m",
667 sp->sp_namp);
668
669 }
670 if (r < 0)
671 return log_debug_errno(r, "Failed to read %s: %m", shadow_path);
672
673 } else {
674 if (errno != ENOENT)
675 return log_debug_errno(errno, "Failed to open %s: %m", shadow_path);
676 if (fchmod(fileno(shadow), 0000) < 0)
677 return log_debug_errno(errno, "Failed to fchmod %s: %m", shadow_tmp);
678 }
679
680 ORDERED_HASHMAP_FOREACH(i, c->todo_uids) {
681 _cleanup_(erase_and_freep) char *creds_password = NULL;
682 bool is_hashed;
683
684 struct spwd n = {
685 .sp_namp = i->name,
686 .sp_lstchg = lstchg,
687 .sp_min = -1,
688 .sp_max = -1,
689 .sp_warn = -1,
690 .sp_inact = -1,
691 .sp_expire = i->locked ? 1 : -1, /* Negative expiration means "unset". Expiration 0 or 1 means "locked" */
692 .sp_flag = ULONG_MAX, /* this appears to be what everybody does ... */
693 };
694
695 r = get_credential_user_password(i->name, &creds_password, &is_hashed);
696 if (r < 0)
697 log_debug_errno(r, "Couldn't read password credential for user '%s', ignoring: %m", i->name);
698
699 if (creds_password && !is_hashed) {
700 _cleanup_(erase_and_freep) char* plaintext_password = TAKE_PTR(creds_password);
701 r = hash_password(plaintext_password, &creds_password);
702 if (r < 0)
703 return log_debug_errno(r, "Failed to hash password: %m");
704 }
705
706 if (creds_password)
707 n.sp_pwdp = creds_password;
708 else if (streq(i->name, "root"))
709 /* Let firstboot set the password later */
710 n.sp_pwdp = (char*) PASSWORD_UNPROVISIONED;
711 else
712 n.sp_pwdp = (char*) PASSWORD_LOCKED_AND_INVALID;
713
714 r = putspent_sane(&n, shadow);
715 if (r < 0)
716 return log_debug_errno(r, "Failed to add new user \"%s\" to temporary shadow file: %m",
717 i->name);
718 }
719
720 /* Append the remaining NIS entries if any */
721 while (sp) {
722 r = putspent_sane(sp, shadow);
723 if (r < 0)
724 return log_debug_errno(r, "Failed to add existing user \"%s\" to temporary shadow file: %m",
725 sp->sp_namp);
726
727 r = fgetspent_sane(original, &sp);
728 if (r < 0)
729 return log_debug_errno(r, "Failed to read %s: %m", shadow_path);
730 if (r == 0)
731 break;
732 }
733 if (!IN_SET(errno, 0, ENOENT))
734 return -errno;
735
736 r = fflush_sync_and_check(shadow);
737 if (r < 0)
738 return log_debug_errno(r, "Failed to flush %s: %m", shadow_tmp);
739
740 done:
741 *ret_tmpfile = TAKE_PTR(shadow);
742 *ret_tmpfile_path = TAKE_PTR(shadow_tmp);
743 return 0;
744 }
745
746 static int write_temporary_group(
747 Context *c,
748 const char *group_path,
749 FILE **ret_tmpfile,
750 char **ret_tmpfile_path) {
751
752 _cleanup_fclose_ FILE *original = NULL, *group = NULL;
753 _cleanup_(unlink_and_freep) char *group_tmp = NULL;
754 bool group_changed = false;
755 struct group *gr = NULL;
756 Item *i;
757 int r;
758
759 assert(c);
760
761 if (ordered_hashmap_isempty(c->todo_gids) && ordered_hashmap_isempty(c->members))
762 goto done;
763
764 if (arg_dry_run) {
765 log_info("Would write /etc/group%s", glyph(GLYPH_ELLIPSIS));
766 goto done;
767 }
768
769 r = fopen_temporary_label("/etc/group", group_path, &group, &group_tmp);
770 if (r < 0)
771 return log_error_errno(r, "Failed to open temporary copy of %s: %m", group_path);
772
773 original = fopen(group_path, "re");
774 if (original) {
775
776 r = copy_rights_with_fallback(fileno(original), fileno(group), group_tmp);
777 if (r < 0)
778 return log_error_errno(r, "Failed to copy permissions from %s to %s: %m",
779 group_path, group_tmp);
780
781 while ((r = fgetgrent_sane(original, &gr)) > 0) {
782 /* Safety checks against name and GID collisions. Normally,
783 * this should be unnecessary, but given that we look at the
784 * entries anyway here, let's make an extra verification
785 * step that we don't generate duplicate entries. */
786
787 i = ordered_hashmap_get(c->groups, gr->gr_name);
788 if (i && i->todo_group)
789 return log_error_errno(SYNTHETIC_ERRNO(EEXIST),
790 "%s: Group \"%s\" already exists.",
791 group_path, gr->gr_name);
792
793 if (ordered_hashmap_contains(c->todo_gids, GID_TO_PTR(gr->gr_gid)))
794 return log_error_errno(SYNTHETIC_ERRNO(EEXIST),
795 "%s: Detected collision for GID " GID_FMT ".",
796 group_path, gr->gr_gid);
797
798 /* Make sure we keep the NIS entries (if any) at the end. */
799 if (IN_SET(gr->gr_name[0], '+', '-'))
800 break;
801
802 r = putgrent_with_members(c, gr, group);
803 if (r < 0)
804 return log_error_errno(r, "Failed to add existing group \"%s\" to temporary group file: %m",
805 gr->gr_name);
806 if (r > 0)
807 group_changed = true;
808 }
809 if (r < 0)
810 return log_error_errno(r, "Failed to read %s: %m", group_path);
811
812 } else {
813 if (errno != ENOENT)
814 return log_error_errno(errno, "Failed to open %s: %m", group_path);
815 if (fchmod(fileno(group), 0644) < 0)
816 return log_error_errno(errno, "Failed to fchmod %s: %m", group_tmp);
817 }
818
819 ORDERED_HASHMAP_FOREACH(i, c->todo_gids) {
820 struct group n = {
821 .gr_name = i->name,
822 .gr_gid = i->gid,
823 .gr_passwd = (char*) PASSWORD_SEE_SHADOW,
824 };
825
826 r = putgrent_with_members(c, &n, group);
827 if (r < 0)
828 return log_error_errno(r, "Failed to add new group \"%s\" to temporary group file: %m",
829 gr->gr_name);
830
831 group_changed = true;
832 }
833
834 /* Append the remaining NIS entries if any */
835 while (gr) {
836 r = putgrent_sane(gr, group);
837 if (r < 0)
838 return log_error_errno(r, "Failed to add existing group \"%s\" to temporary group file: %m",
839 gr->gr_name);
840
841 r = fgetgrent_sane(original, &gr);
842 if (r < 0)
843 return log_error_errno(r, "Failed to read %s: %m", group_path);
844 if (r == 0)
845 break;
846 }
847
848 r = fflush_sync_and_check(group);
849 if (r < 0)
850 return log_error_errno(r, "Failed to flush %s: %m", group_tmp);
851
852 done:
853 if (group_changed) {
854 *ret_tmpfile = TAKE_PTR(group);
855 *ret_tmpfile_path = TAKE_PTR(group_tmp);
856 } else {
857 *ret_tmpfile = NULL;
858 *ret_tmpfile_path = NULL;
859 }
860 return 0;
861 }
862
863 static int write_temporary_gshadow(
864 Context *c,
865 const char * gshadow_path,
866 FILE **ret_tmpfile,
867 char **ret_tmpfile_path) {
868
869 #if ENABLE_GSHADOW
870 _cleanup_fclose_ FILE *original = NULL, *gshadow = NULL;
871 _cleanup_(unlink_and_freep) char *gshadow_tmp = NULL;
872 bool group_changed = false;
873 Item *i;
874 int r;
875
876 assert(c);
877
878 if (ordered_hashmap_isempty(c->todo_gids) && ordered_hashmap_isempty(c->members))
879 goto done;
880
881 if (arg_dry_run) {
882 log_info("Would write /etc/gshadow%s", glyph(GLYPH_ELLIPSIS));
883 goto done;
884 }
885
886 r = fopen_temporary_label("/etc/gshadow", gshadow_path, &gshadow, &gshadow_tmp);
887 if (r < 0)
888 return log_error_errno(r, "Failed to open temporary copy of %s: %m", gshadow_path);
889
890 original = fopen(gshadow_path, "re");
891 if (original) {
892 struct sgrp *sg;
893
894 r = copy_rights_with_fallback(fileno(original), fileno(gshadow), gshadow_tmp);
895 if (r < 0)
896 return log_error_errno(r, "Failed to copy permissions from %s to %s: %m",
897 gshadow_path, gshadow_tmp);
898
899 while ((r = fgetsgent_sane(original, &sg)) > 0) {
900
901 i = ordered_hashmap_get(c->groups, sg->sg_namp);
902 if (i && i->todo_group)
903 return log_error_errno(SYNTHETIC_ERRNO(EEXIST),
904 "%s: Group \"%s\" already exists.",
905 gshadow_path, sg->sg_namp);
906
907 r = putsgent_with_members(c, sg, gshadow);
908 if (r < 0)
909 return log_error_errno(r, "Failed to add existing group \"%s\" to temporary gshadow file: %m",
910 sg->sg_namp);
911 if (r > 0)
912 group_changed = true;
913 }
914 if (r < 0)
915 return r;
916
917 } else {
918 if (errno != ENOENT)
919 return log_error_errno(errno, "Failed to open %s: %m", gshadow_path);
920 if (fchmod(fileno(gshadow), 0000) < 0)
921 return log_error_errno(errno, "Failed to fchmod %s: %m", gshadow_tmp);
922 }
923
924 ORDERED_HASHMAP_FOREACH(i, c->todo_gids) {
925 struct sgrp n = {
926 .sg_namp = i->name,
927 .sg_passwd = (char*) PASSWORD_LOCKED_AND_INVALID,
928 };
929
930 r = putsgent_with_members(c, &n, gshadow);
931 if (r < 0)
932 return log_error_errno(r, "Failed to add new group \"%s\" to temporary gshadow file: %m",
933 n.sg_namp);
934
935 group_changed = true;
936 }
937
938 r = fflush_sync_and_check(gshadow);
939 if (r < 0)
940 return log_error_errno(r, "Failed to flush %s: %m", gshadow_tmp);
941
942 done:
943 if (group_changed) {
944 *ret_tmpfile = TAKE_PTR(gshadow);
945 *ret_tmpfile_path = TAKE_PTR(gshadow_tmp);
946 } else
947 #endif
948 {
949 *ret_tmpfile = NULL;
950 *ret_tmpfile_path = NULL;
951 }
952 return 0;
953 }
954
955 static int write_files(Context *c) {
956 _cleanup_fclose_ FILE *passwd = NULL, *group = NULL, *shadow = NULL, *gshadow = NULL;
957 _cleanup_(unlink_and_freep) char *passwd_tmp = NULL, *group_tmp = NULL, *shadow_tmp = NULL, *gshadow_tmp = NULL;
958 int r;
959
960 _cleanup_free_ char *passwd_path = path_join(arg_root, "/etc/passwd");
961 if (!passwd_path)
962 return log_oom();
963
964 _cleanup_free_ char *shadow_path = path_join(arg_root, "/etc/shadow");
965 if (!shadow_path)
966 return log_oom();
967
968 _cleanup_free_ char *group_path = path_join(arg_root, "/etc/group");
969 if (!group_path)
970 return log_oom();
971
972 _cleanup_free_ char *gshadow_path = path_join(arg_root, "/etc/gshadow");
973 if (!gshadow_path)
974 return log_oom();
975
976 assert(c);
977
978 r = write_temporary_group(c, group_path, &group, &group_tmp);
979 if (r < 0)
980 return r;
981
982 r = write_temporary_gshadow(c, gshadow_path, &gshadow, &gshadow_tmp);
983 if (r < 0)
984 return r;
985
986 r = write_temporary_passwd(c, passwd_path, &passwd, &passwd_tmp);
987 if (r < 0)
988 return r;
989
990 r = write_temporary_shadow(c, shadow_path, &shadow, &shadow_tmp);
991 if (r < 0)
992 return r;
993
994 /* Make a backup of the old files */
995 if (group) {
996 r = make_backup("/etc/group", group_path);
997 if (r < 0)
998 return log_error_errno(r, "Failed to backup %s: %m", group_path);
999 }
1000 if (gshadow) {
1001 r = make_backup("/etc/gshadow", gshadow_path);
1002 if (r < 0)
1003 return log_error_errno(r, "Failed to backup %s: %m", gshadow_path);
1004 }
1005
1006 if (passwd) {
1007 r = make_backup("/etc/passwd", passwd_path);
1008 if (r < 0)
1009 return log_error_errno(r, "Failed to backup %s: %m", passwd_path);
1010 }
1011 if (shadow) {
1012 r = make_backup("/etc/shadow", shadow_path);
1013 if (r < 0)
1014 return log_error_errno(r, "Failed to backup %s: %m", shadow_path);
1015 }
1016
1017 /* And make the new files count */
1018 if (group) {
1019 r = rename_and_apply_smack_floor_label(group_tmp, group_path);
1020 if (r < 0)
1021 return log_error_errno(r, "Failed to rename %s to %s: %m",
1022 group_tmp, group_path);
1023 group_tmp = mfree(group_tmp);
1024 }
1025 /* OK, we have written the group entries successfully */
1026 log_audit_accounts(c, ADD_GROUP);
1027 if (gshadow) {
1028 r = rename_and_apply_smack_floor_label(gshadow_tmp, gshadow_path);
1029 if (r < 0)
1030 return log_error_errno(r, "Failed to rename %s to %s: %m",
1031 gshadow_tmp, gshadow_path);
1032
1033 gshadow_tmp = mfree(gshadow_tmp);
1034 }
1035
1036 if (passwd) {
1037 r = rename_and_apply_smack_floor_label(passwd_tmp, passwd_path);
1038 if (r < 0)
1039 return log_error_errno(r, "Failed to rename %s to %s: %m",
1040 passwd_tmp, passwd_path);
1041
1042 passwd_tmp = mfree(passwd_tmp);
1043 }
1044 /* OK, we have written the user entries successfully */
1045 log_audit_accounts(c, ADD_USER);
1046 if (shadow) {
1047 r = rename_and_apply_smack_floor_label(shadow_tmp, shadow_path);
1048 if (r < 0)
1049 return log_error_errno(r, "Failed to rename %s to %s: %m",
1050 shadow_tmp, shadow_path);
1051
1052 shadow_tmp = mfree(shadow_tmp);
1053 }
1054
1055 return 0;
1056 }
1057
1058 static int uid_is_ok(
1059 Context *c,
1060 uid_t uid,
1061 const char *name,
1062 bool check_with_gid) {
1063
1064 int r;
1065 assert(c);
1066
1067 /* Let's see if we already have assigned the UID a second time */
1068 if (ordered_hashmap_get(c->todo_uids, UID_TO_PTR(uid)))
1069 return 0;
1070
1071 /* Try to avoid using uids that are already used by a group
1072 * that doesn't have the same name as our new user. */
1073 if (check_with_gid) {
1074 Item *i;
1075
1076 i = ordered_hashmap_get(c->todo_gids, GID_TO_PTR(uid));
1077 if (i && !streq(i->name, name))
1078 return 0;
1079 }
1080
1081 /* Let's check the files directly */
1082 if (hashmap_contains(c->database_by_uid, UID_TO_PTR(uid)))
1083 return 0;
1084
1085 if (check_with_gid) {
1086 const char *n;
1087
1088 n = hashmap_get(c->database_by_gid, GID_TO_PTR(uid));
1089 if (n && !streq(n, name))
1090 return 0;
1091 }
1092
1093 /* Let's also check via NSS, to avoid UID clashes over LDAP and such, just in case */
1094 if (!arg_root) {
1095 _cleanup_free_ struct group *g = NULL;
1096
1097 r = getpwuid_malloc(uid, /* ret= */ NULL);
1098 if (r >= 0)
1099 return 0;
1100 if (r != -ESRCH)
1101 log_warning_errno(r, "Unexpected failure while looking up UID '" UID_FMT "' via NSS, assuming it doesn't exist: %m", uid);
1102
1103 if (check_with_gid) {
1104 r = getgrgid_malloc((gid_t) uid, &g);
1105 if (r >= 0) {
1106 if (!streq(g->gr_name, name))
1107 return 0;
1108 } else if (r != -ESRCH)
1109 log_warning_errno(r, "Unexpected failure while looking up GID '" GID_FMT "' via NSS, assuming it doesn't exist: %m", uid);
1110 }
1111 }
1112
1113 return 1;
1114 }
1115
1116 static int root_stat(const char *p, struct stat *ret_st) {
1117 return chase_and_stat(p, arg_root, CHASE_PREFIX_ROOT, /* ret_path= */ NULL, ret_st);
1118 }
1119
1120 static int read_id_from_file(Item *i, uid_t *ret_uid, gid_t *ret_gid) {
1121 struct stat st;
1122 bool found_uid = false, found_gid = false;
1123 uid_t uid = 0;
1124 gid_t gid = 0;
1125
1126 assert(i);
1127
1128 /* First, try to get the GID directly */
1129 if (ret_gid && i->gid_path && root_stat(i->gid_path, &st) >= 0) {
1130 gid = st.st_gid;
1131 found_gid = true;
1132 }
1133
1134 /* Then, try to get the UID directly */
1135 if ((ret_uid || (ret_gid && !found_gid))
1136 && i->uid_path
1137 && root_stat(i->uid_path, &st) >= 0) {
1138
1139 uid = st.st_uid;
1140 found_uid = true;
1141
1142 /* If we need the gid, but had no success yet, also derive it from the UID path */
1143 if (ret_gid && !found_gid) {
1144 gid = st.st_gid;
1145 found_gid = true;
1146 }
1147 }
1148
1149 /* If that didn't work yet, then let's reuse the GID as UID */
1150 if (ret_uid && !found_uid && i->gid_path) {
1151
1152 if (found_gid) {
1153 uid = (uid_t) gid;
1154 found_uid = true;
1155 } else if (root_stat(i->gid_path, &st) >= 0) {
1156 uid = (uid_t) st.st_gid;
1157 found_uid = true;
1158 }
1159 }
1160
1161 if (ret_uid) {
1162 if (!found_uid)
1163 return 0;
1164
1165 *ret_uid = uid;
1166 }
1167
1168 if (ret_gid) {
1169 if (!found_gid)
1170 return 0;
1171
1172 *ret_gid = gid;
1173 }
1174
1175 return 1;
1176 }
1177
1178 static int add_user(Context *c, Item *i) {
1179 void *z;
1180 int r;
1181
1182 assert(c);
1183 assert(i);
1184
1185 /* Check the database directly */
1186 z = hashmap_get(c->database_by_username, i->name);
1187 if (z) {
1188 log_debug("User %s already exists.", i->name);
1189 i->uid = PTR_TO_UID(z);
1190 i->uid_set = true;
1191 return 0;
1192 }
1193
1194 if (!arg_root) {
1195 _cleanup_free_ struct passwd *p = NULL;
1196
1197 /* Also check NSS */
1198 r = getpwnam_malloc(i->name, &p);
1199 if (r >= 0) {
1200 log_debug("User %s already exists.", i->name);
1201 i->uid = p->pw_uid;
1202 i->uid_set = true;
1203
1204 r = free_and_strdup(&i->description, p->pw_gecos);
1205 if (r < 0)
1206 return log_oom();
1207
1208 return 0;
1209 }
1210 if (r != -ESRCH)
1211 log_warning_errno(r, "Unexpected failure while looking up user '%s' via NSS, assuming it doesn't exist: %m", i->name);
1212 }
1213
1214 /* Try to use the suggested numeric UID */
1215 if (i->uid_set) {
1216 r = uid_is_ok(c, i->uid, i->name, !i->id_set_strict);
1217 if (r < 0)
1218 return log_error_errno(r, "Failed to verify UID " UID_FMT ": %m", i->uid);
1219 if (r == 0) {
1220 log_info("Suggested user ID " UID_FMT " for %s already used.", i->uid, i->name);
1221 i->uid_set = false;
1222 }
1223 }
1224
1225 /* If that didn't work, try to read it from the specified path */
1226 if (!i->uid_set) {
1227 uid_t candidate;
1228
1229 if (read_id_from_file(i, &candidate, NULL) > 0) {
1230
1231 if (candidate <= 0 || !uid_range_contains(c->uid_range, candidate))
1232 log_debug("User ID " UID_FMT " of file not suitable for %s.", candidate, i->name);
1233 else {
1234 r = uid_is_ok(c, candidate, i->name, true);
1235 if (r < 0)
1236 return log_error_errno(r, "Failed to verify UID " UID_FMT ": %m", i->uid);
1237 else if (r > 0) {
1238 i->uid = candidate;
1239 i->uid_set = true;
1240 } else
1241 log_debug("User ID " UID_FMT " of file for %s is already used.", candidate, i->name);
1242 }
1243 }
1244 }
1245
1246 /* Otherwise, try to reuse the group ID */
1247 if (!i->uid_set && i->gid_set) {
1248 r = uid_is_ok(c, (uid_t) i->gid, i->name, true);
1249 if (r < 0)
1250 return log_error_errno(r, "Failed to verify UID " UID_FMT ": %m", i->uid);
1251 if (r > 0) {
1252 i->uid = (uid_t) i->gid;
1253 i->uid_set = true;
1254 }
1255 }
1256
1257 /* And if that didn't work either, let's try to find a free one */
1258 if (!i->uid_set) {
1259 maybe_emit_login_defs_warning(c);
1260
1261 for (;;) {
1262 r = uid_range_next_lower(c->uid_range, &c->search_uid);
1263 if (r < 0)
1264 return log_error_errno(r, "No free user ID available for %s.", i->name);
1265
1266 r = uid_is_ok(c, c->search_uid, i->name, true);
1267 if (r < 0)
1268 return log_error_errno(r, "Failed to verify UID " UID_FMT ": %m", i->uid);
1269 else if (r > 0)
1270 break;
1271 }
1272
1273 i->uid_set = true;
1274 i->uid = c->search_uid;
1275 }
1276
1277 r = ordered_hashmap_ensure_put(&c->todo_uids, NULL, UID_TO_PTR(i->uid), i);
1278 if (r == -EEXIST)
1279 return log_error_errno(r, "Requested user %s with UID " UID_FMT " and gid" GID_FMT " to be created is duplicated "
1280 "or conflicts with another user.", i->name, i->uid, i->gid);
1281 if (r == -ENOMEM)
1282 return log_oom();
1283 if (r < 0)
1284 return log_error_errno(r, "Failed to store user %s with UID " UID_FMT " and GID " GID_FMT " to be created: %m",
1285 i->name, i->uid, i->gid);
1286
1287 i->todo_user = true;
1288 log_info("Creating user '%s' (%s) with UID " UID_FMT " and GID " GID_FMT ".",
1289 i->name, strna(i->description), i->uid, i->gid);
1290
1291 return 0;
1292 }
1293
1294 static int gid_is_ok(
1295 Context *c,
1296 gid_t gid,
1297 const char *groupname,
1298 bool check_with_uid) {
1299
1300 Item *user;
1301 char *username;
1302 int r;
1303
1304 assert(c);
1305 assert(groupname);
1306
1307 if (ordered_hashmap_get(c->todo_gids, GID_TO_PTR(gid)))
1308 return 0;
1309
1310 /* Avoid reusing gids that are already used by a different user */
1311 if (check_with_uid) {
1312 user = ordered_hashmap_get(c->todo_uids, UID_TO_PTR(gid));
1313 if (user && !streq(user->name, groupname))
1314 return 0;
1315 }
1316
1317 if (hashmap_contains(c->database_by_gid, GID_TO_PTR(gid)))
1318 return 0;
1319
1320 if (check_with_uid) {
1321 username = hashmap_get(c->database_by_uid, UID_TO_PTR(gid));
1322 if (username && !streq(username, groupname))
1323 return 0;
1324 }
1325
1326 if (!arg_root) {
1327 r = getgrgid_malloc(gid, /* ret= */ NULL);
1328 if (r >= 0)
1329 return 0;
1330 if (r != -ESRCH)
1331 log_warning_errno(r, "Unexpected failure while looking up GID '" GID_FMT "' via NSS, assuming it doesn't exist: %m", gid);
1332
1333 if (check_with_uid) {
1334 r = getpwuid_malloc(gid, /* ret= */ NULL);
1335 if (r >= 0)
1336 return 0;
1337 if (r != -ESRCH)
1338 log_warning_errno(r, "Unexpected failure while looking up GID '" GID_FMT "' via NSS, assuming it doesn't exist: %m", gid);
1339 }
1340 }
1341
1342 return 1;
1343 }
1344
1345 static int get_gid_by_name(
1346 Context *c,
1347 const char *name,
1348 gid_t *ret_gid) {
1349
1350 void *z;
1351 int r;
1352
1353 assert(c);
1354 assert(ret_gid);
1355
1356 /* Check the database directly */
1357 z = hashmap_get(c->database_by_groupname, name);
1358 if (z) {
1359 *ret_gid = PTR_TO_GID(z);
1360 return 0;
1361 }
1362
1363 /* Also check NSS */
1364 if (!arg_root) {
1365 _cleanup_free_ struct group *g = NULL;
1366
1367 r = getgrnam_malloc(name, &g);
1368 if (r >= 0) {
1369 *ret_gid = g->gr_gid;
1370 return 0;
1371 }
1372 if (r != -ESRCH)
1373 log_warning_errno(r, "Unexpected failure while looking up group '%s' via NSS, assuming it doesn't exist: %m", name);
1374 }
1375
1376 return -ENOENT;
1377 }
1378
1379 static int add_group(Context *c, Item *i) {
1380 int r;
1381
1382 assert(c);
1383 assert(i);
1384
1385 r = get_gid_by_name(c, i->name, &i->gid);
1386 if (r != -ENOENT) {
1387 if (r < 0)
1388 return r;
1389 log_debug("Group %s already exists.", i->name);
1390 i->gid_set = true;
1391 return 0;
1392 }
1393
1394 /* Try to use the suggested numeric GID */
1395 if (i->gid_set) {
1396 r = gid_is_ok(c, i->gid, i->name, false);
1397 if (r < 0)
1398 return log_error_errno(r, "Failed to verify GID " GID_FMT ": %m", i->gid);
1399 if (i->id_set_strict) {
1400 /* If we require the GID to already exist we can return here:
1401 * r > 0: means the GID does not exist -> fail
1402 * r == 0: means the GID exists -> nothing more to do.
1403 */
1404 if (r > 0)
1405 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
1406 "Failed to create %s: please create GID " GID_FMT,
1407 i->name, i->gid);
1408 if (r == 0)
1409 return 0;
1410 }
1411 if (r == 0) {
1412 log_info("Suggested group ID " GID_FMT " for %s already used.", i->gid, i->name);
1413 i->gid_set = false;
1414 }
1415 }
1416
1417 /* Try to reuse the numeric uid, if there's one */
1418 if (!i->gid_set && i->uid_set) {
1419 r = gid_is_ok(c, (gid_t) i->uid, i->name, true);
1420 if (r < 0)
1421 return log_error_errno(r, "Failed to verify GID " GID_FMT ": %m", i->gid);
1422 if (r > 0) {
1423 i->gid = (gid_t) i->uid;
1424 i->gid_set = true;
1425 }
1426 }
1427
1428 /* If that didn't work, try to read it from the specified path */
1429 if (!i->gid_set) {
1430 gid_t candidate;
1431
1432 if (read_id_from_file(i, NULL, &candidate) > 0) {
1433
1434 if (candidate <= 0 || !uid_range_contains(c->uid_range, candidate))
1435 log_debug("Group ID " GID_FMT " of file not suitable for %s.", candidate, i->name);
1436 else {
1437 r = gid_is_ok(c, candidate, i->name, true);
1438 if (r < 0)
1439 return log_error_errno(r, "Failed to verify GID " GID_FMT ": %m", i->gid);
1440 else if (r > 0) {
1441 i->gid = candidate;
1442 i->gid_set = true;
1443 } else
1444 log_debug("Group ID " GID_FMT " of file for %s already used.", candidate, i->name);
1445 }
1446 }
1447 }
1448
1449 /* And if that didn't work either, let's try to find a free one */
1450 if (!i->gid_set) {
1451 maybe_emit_login_defs_warning(c);
1452
1453 for (;;) {
1454 /* We look for new GIDs in the UID pool! */
1455 r = uid_range_next_lower(c->uid_range, &c->search_uid);
1456 if (r < 0)
1457 return log_error_errno(r, "No free group ID available for %s.", i->name);
1458
1459 r = gid_is_ok(c, c->search_uid, i->name, true);
1460 if (r < 0)
1461 return log_error_errno(r, "Failed to verify GID " GID_FMT ": %m", i->gid);
1462 else if (r > 0)
1463 break;
1464 }
1465
1466 i->gid_set = true;
1467 i->gid = c->search_uid;
1468 }
1469
1470 r = ordered_hashmap_ensure_put(&c->todo_gids, NULL, GID_TO_PTR(i->gid), i);
1471 if (r == -EEXIST)
1472 return log_error_errno(r, "Requested group %s with GID "GID_FMT " to be created is duplicated or conflicts with another user.", i->name, i->gid);
1473 if (r == -ENOMEM)
1474 return log_oom();
1475 if (r < 0)
1476 return log_error_errno(r, "Failed to store group %s with GID " GID_FMT " to be created: %m", i->name, i->gid);
1477
1478 i->todo_group = true;
1479 log_info("Creating group '%s' with GID " GID_FMT ".", i->name, i->gid);
1480
1481 return 0;
1482 }
1483
1484 static int process_item(Context *c, Item *i) {
1485 int r;
1486
1487 assert(c);
1488 assert(i);
1489
1490 switch (i->type) {
1491
1492 case ADD_USER: {
1493 Item *j = NULL;
1494
1495 if (!i->gid_set) {
1496 j = ordered_hashmap_get(c->groups, i->group_name ?: i->name);
1497
1498 /* If that's not a match, also check if the group name
1499 * matches a user name in the queue. */
1500 if (!j && i->group_name)
1501 j = ordered_hashmap_get(c->users, i->group_name);
1502 }
1503
1504 if (j && j->todo_group) {
1505 /* When a group with the target name is already in queue,
1506 * use the information about the group and do not create
1507 * duplicated group entry. */
1508 i->gid_set = j->gid_set;
1509 i->gid = j->gid;
1510 i->id_set_strict = true;
1511 } else if (i->group_name) {
1512 /* When a group name was given instead of a GID and it's
1513 * not in queue, then it must already exist. */
1514 r = get_gid_by_name(c, i->group_name, &i->gid);
1515 if (r < 0)
1516 return log_error_errno(r, "Group %s not found.", i->group_name);
1517 i->gid_set = true;
1518 i->id_set_strict = true;
1519 } else {
1520 r = add_group(c, i);
1521 if (r < 0)
1522 return r;
1523 }
1524
1525 return add_user(c, i);
1526 }
1527
1528 case ADD_GROUP:
1529 return add_group(c, i);
1530
1531 default:
1532 assert_not_reached();
1533 }
1534 }
1535
1536 static Item* item_free(Item *i) {
1537 if (!i)
1538 return NULL;
1539
1540 free(i->name);
1541 free(i->group_name);
1542 free(i->uid_path);
1543 free(i->gid_path);
1544 free(i->description);
1545 free(i->home);
1546 free(i->shell);
1547 free(i->filename);
1548 return mfree(i);
1549 }
1550
1551 DEFINE_TRIVIAL_CLEANUP_FUNC(Item*, item_free);
1552 DEFINE_PRIVATE_HASH_OPS_WITH_VALUE_DESTRUCTOR(item_hash_ops, char, string_hash_func, string_compare_func, Item, item_free);
1553
1554 static Item* item_new(ItemType type, const char *name, const char *filename, unsigned line) {
1555 assert(name);
1556 assert(!!filename == (line > 0));
1557
1558 _cleanup_(item_freep) Item *new = new(Item, 1);
1559 if (!new)
1560 return NULL;
1561
1562 *new = (Item) {
1563 .type = type,
1564 .line = line,
1565 };
1566
1567 if (free_and_strdup(&new->name, name) < 0 ||
1568 free_and_strdup(&new->filename, filename) < 0)
1569 return NULL;
1570
1571 return TAKE_PTR(new);
1572 }
1573
1574 static int add_implicit(Context *c) {
1575 char *g, **l;
1576 int r;
1577
1578 assert(c);
1579
1580 /* Implicitly create additional users and groups, if they were listed in "m" lines */
1581 ORDERED_HASHMAP_FOREACH_KEY(l, g, c->members) {
1582 STRV_FOREACH(m, l)
1583 if (!ordered_hashmap_get(c->users, *m)) {
1584 _cleanup_(item_freep) Item *j =
1585 item_new(ADD_USER, *m, /* filename= */ NULL, /* line= */ 0);
1586 if (!j)
1587 return log_oom();
1588
1589 r = ordered_hashmap_ensure_put(&c->users, &item_hash_ops, j->name, j);
1590 if (r == -ENOMEM)
1591 return log_oom();
1592 if (r < 0)
1593 return log_error_errno(r, "Failed to add implicit user '%s': %m", j->name);
1594
1595 log_debug("Adding implicit user '%s' due to m line", j->name);
1596 TAKE_PTR(j);
1597 }
1598
1599 if (!(ordered_hashmap_get(c->users, g) ||
1600 ordered_hashmap_get(c->groups, g))) {
1601 _cleanup_(item_freep) Item *j =
1602 item_new(ADD_GROUP, g, /* filename= */ NULL, /* line= */ 0);
1603 if (!j)
1604 return log_oom();
1605
1606 r = ordered_hashmap_ensure_put(&c->groups, &item_hash_ops, j->name, j);
1607 if (r == -ENOMEM)
1608 return log_oom();
1609 if (r < 0)
1610 return log_error_errno(r, "Failed to add implicit group '%s': %m", j->name);
1611
1612 log_debug("Adding implicit group '%s' due to m line", j->name);
1613 TAKE_PTR(j);
1614 }
1615 }
1616
1617 return 0;
1618 }
1619
1620 static int item_equivalent(Item *a, Item *b) {
1621 int r;
1622
1623 assert(a);
1624 assert(b);
1625
1626 if (a->type != b->type) {
1627 log_syntax(NULL, LOG_DEBUG, a->filename, a->line, 0,
1628 "Item not equivalent because types differ");
1629 return false;
1630 }
1631
1632 if (!streq_ptr(a->name, b->name)) {
1633 log_syntax(NULL, LOG_DEBUG, a->filename, a->line, 0,
1634 "Item not equivalent because names differ ('%s' vs. '%s')",
1635 a->name, b->name);
1636 return false;
1637 }
1638
1639 /* Paths were simplified previously, so we can use streq. */
1640 if (!streq_ptr(a->uid_path, b->uid_path)) {
1641 log_syntax(NULL, LOG_DEBUG, a->filename, a->line, 0,
1642 "Item not equivalent because UID paths differ (%s vs. %s)",
1643 a->uid_path ?: "(unset)", b->uid_path ?: "(unset)");
1644 return false;
1645 }
1646
1647 if (!streq_ptr(a->gid_path, b->gid_path)) {
1648 log_syntax(NULL, LOG_DEBUG, a->filename, a->line, 0,
1649 "Item not equivalent because GID paths differ (%s vs. %s)",
1650 a->gid_path ?: "(unset)", b->gid_path ?: "(unset)");
1651 return false;
1652 }
1653
1654 if (!streq_ptr(a->description, b->description)) {
1655 log_syntax(NULL, LOG_DEBUG, a->filename, a->line, 0,
1656 "Item not equivalent because descriptions differ ('%s' vs. '%s')",
1657 strempty(a->description), strempty(b->description));
1658 return false;
1659 }
1660
1661 if ((a->uid_set != b->uid_set) ||
1662 (a->uid_set && a->uid != b->uid)) {
1663 log_syntax(NULL, LOG_DEBUG, a->filename, a->line, 0,
1664 "Item not equivalent because UIDs differ (%s vs. %s)",
1665 a->uid_set ? FORMAT_UID(a->uid) : "(unset)",
1666 b->uid_set ? FORMAT_UID(b->uid) : "(unset)");
1667 return false;
1668 }
1669
1670 if ((a->gid_set != b->gid_set) ||
1671 (a->gid_set && a->gid != b->gid)) {
1672 log_syntax(NULL, LOG_DEBUG, a->filename, a->line, 0,
1673 "Item not equivalent because GIDs differ (%s vs. %s)",
1674 a->gid_set ? FORMAT_GID(a->gid) : "(unset)",
1675 b->gid_set ? FORMAT_GID(b->gid) : "(unset)");
1676 return false;
1677 }
1678
1679 if (!streq_ptr(a->home, b->home)) {
1680 log_syntax(NULL, LOG_DEBUG, a->filename, a->line, 0,
1681 "Item not equivalent because home directories differ ('%s' vs. '%s')",
1682 strempty(a->description), strempty(b->description));
1683 return false;
1684 }
1685
1686 /* Check if the two paths refer to the same file.
1687 * If the paths are equal (after normalization), it's obviously the same file.
1688 * If both paths specify a nologin shell, treat them as the same (e.g. /bin/true and /bin/false).
1689 * Otherwise, try to resolve the paths, and see if we get the same result, (e.g. /sbin/nologin and
1690 * /usr/sbin/nologin).
1691 * If we can't resolve something, treat different paths as different. */
1692
1693 const char *a_shell = pick_shell(a),
1694 *b_shell = pick_shell(b);
1695 if (!path_equal(a_shell, b_shell) &&
1696 !(is_nologin_shell(a_shell) && is_nologin_shell(b_shell))) {
1697 _cleanup_free_ char *pa = NULL, *pb = NULL;
1698
1699 r = chase(a_shell, arg_root, CHASE_PREFIX_ROOT | CHASE_NONEXISTENT, &pa, NULL);
1700 if (r < 0) {
1701 log_full_errno(ERRNO_IS_RESOURCE(r) ? LOG_ERR : LOG_DEBUG,
1702 r, "Failed to look up path '%s%s%s': %m",
1703 strempty(arg_root), arg_root ? "/" : "", a_shell);
1704 return ERRNO_IS_RESOURCE(r) ? r : false;
1705 }
1706
1707 r = chase(b_shell, arg_root, CHASE_PREFIX_ROOT | CHASE_NONEXISTENT, &pb, NULL);
1708 if (r < 0) {
1709 log_full_errno(ERRNO_IS_RESOURCE(r) ? LOG_ERR : LOG_DEBUG,
1710 r, "Failed to look up path '%s%s%s': %m",
1711 strempty(arg_root), arg_root ? "/" : "", b_shell);
1712 return ERRNO_IS_RESOURCE(r) ? r : false;
1713 }
1714
1715 if (!path_equal(pa, pb)) {
1716 log_syntax(NULL, LOG_DEBUG, a->filename, a->line, 0,
1717 "Item not equivalent because shells differ ('%s' vs. '%s')",
1718 pa, pb);
1719 return false;
1720 }
1721 }
1722
1723 return true;
1724 }
1725
1726 static int parse_line(
1727 const char *fname,
1728 unsigned line,
1729 const char *buffer,
1730 bool *invalid_config,
1731 void *context) {
1732
1733 Context *c = ASSERT_PTR(context);
1734 _cleanup_free_ char *action = NULL,
1735 *name = NULL, *resolved_name = NULL,
1736 *id = NULL, *resolved_id = NULL,
1737 *description = NULL, *resolved_description = NULL,
1738 *home = NULL, *resolved_home = NULL,
1739 *shell = NULL, *resolved_shell = NULL;
1740 _cleanup_(item_freep) Item *i = NULL;
1741 Item *existing;
1742 OrderedHashmap *h;
1743 int r;
1744 const char *p;
1745
1746 assert(fname);
1747 assert(line >= 1);
1748 assert(buffer);
1749 assert(!invalid_config); /* We don't support invalid_config yet. */
1750
1751 /* Parse columns */
1752 p = buffer;
1753 r = extract_many_words(&p, NULL, EXTRACT_UNQUOTE,
1754 &action, &name, &id, &description, &home, &shell);
1755 if (r < 0)
1756 return log_syntax(NULL, LOG_ERR, fname, line, r, "Syntax error.");
1757 if (r < 2)
1758 return log_syntax(NULL, LOG_ERR, fname, line, SYNTHETIC_ERRNO(EINVAL),
1759 "Missing action and name columns.");
1760 if (!isempty(p))
1761 return log_syntax(NULL, LOG_ERR, fname, line, SYNTHETIC_ERRNO(EINVAL),
1762 "Trailing garbage.");
1763
1764 if (isempty(action))
1765 return log_syntax(NULL, LOG_ERR, fname, line, SYNTHETIC_ERRNO(EBADMSG),
1766 "Empty command specification.");
1767
1768 bool locked = false;
1769 for (int pos = 1; action[pos]; pos++)
1770 if (action[pos] == '!' && !locked)
1771 locked = true;
1772 else
1773 return log_syntax(NULL, LOG_ERR, fname, line, SYNTHETIC_ERRNO(EBADMSG),
1774 "Unknown modifiers in command '%s'.", action);
1775
1776 if (!IN_SET(action[0], ADD_USER, ADD_GROUP, ADD_MEMBER, ADD_RANGE))
1777 return log_syntax(NULL, LOG_ERR, fname, line, SYNTHETIC_ERRNO(EBADMSG),
1778 "Unknown command type '%c'.", action[0]);
1779
1780 /* Verify name */
1781 if (empty_or_dash(name))
1782 name = mfree(name);
1783
1784 if (name) {
1785 r = specifier_printf(name, NAME_MAX, system_and_tmp_specifier_table, arg_root, NULL, &resolved_name);
1786 if (r < 0)
1787 return log_syntax(NULL, LOG_ERR, fname, line, r, "Failed to replace specifiers in '%s': %m", name);
1788
1789 if (!valid_user_group_name(resolved_name, 0))
1790 return log_syntax(NULL, LOG_ERR, fname, line, SYNTHETIC_ERRNO(EINVAL),
1791 "'%s' is not a valid user or group name.", resolved_name);
1792 }
1793
1794 /* Verify id */
1795 if (empty_or_dash(id))
1796 id = mfree(id);
1797
1798 if (id) {
1799 r = specifier_printf(id, PATH_MAX-1, system_and_tmp_specifier_table, arg_root, NULL, &resolved_id);
1800 if (r < 0)
1801 return log_syntax(NULL, LOG_ERR, fname, line, r,
1802 "Failed to replace specifiers in '%s': %m", name);
1803 }
1804
1805 /* Verify description */
1806 if (empty_or_dash(description))
1807 description = mfree(description);
1808
1809 if (description) {
1810 r = specifier_printf(description, LONG_LINE_MAX, system_and_tmp_specifier_table, arg_root, NULL, &resolved_description);
1811 if (r < 0)
1812 return log_syntax(NULL, LOG_ERR, fname, line, r,
1813 "Failed to replace specifiers in '%s': %m", description);
1814
1815 if (!valid_gecos(resolved_description))
1816 return log_syntax(NULL, LOG_ERR, fname, line, SYNTHETIC_ERRNO(EINVAL),
1817 "'%s' is not a valid GECOS field.", resolved_description);
1818 }
1819
1820 /* Verify home */
1821 if (empty_or_dash(home))
1822 home = mfree(home);
1823
1824 if (home) {
1825 r = specifier_printf(home, PATH_MAX-1, system_and_tmp_specifier_table, arg_root, NULL, &resolved_home);
1826 if (r < 0)
1827 return log_syntax(NULL, LOG_ERR, fname, line, r,
1828 "Failed to replace specifiers in '%s': %m", home);
1829
1830 path_simplify(resolved_home);
1831
1832 if (!valid_home(resolved_home))
1833 return log_syntax(NULL, LOG_ERR, fname, line, SYNTHETIC_ERRNO(EINVAL),
1834 "'%s' is not a valid home directory field.", resolved_home);
1835 }
1836
1837 /* Verify shell */
1838 if (empty_or_dash(shell))
1839 shell = mfree(shell);
1840
1841 if (shell) {
1842 r = specifier_printf(shell, PATH_MAX-1, system_and_tmp_specifier_table, arg_root, NULL, &resolved_shell);
1843 if (r < 0)
1844 return log_syntax(NULL, LOG_ERR, fname, line, r,
1845 "Failed to replace specifiers in '%s': %m", shell);
1846
1847 path_simplify(resolved_shell);
1848
1849 if (!valid_shell(resolved_shell))
1850 return log_syntax(NULL, LOG_ERR, fname, line, SYNTHETIC_ERRNO(EINVAL),
1851 "'%s' is not a valid login shell field.", resolved_shell);
1852 }
1853
1854 switch (action[0]) {
1855
1856 case ADD_RANGE:
1857 if (locked)
1858 return log_syntax(NULL, LOG_ERR, fname, line, SYNTHETIC_ERRNO(EINVAL),
1859 "Flag '!' not permitted on lines of type 'r'.");
1860
1861 if (resolved_name)
1862 return log_syntax(NULL, LOG_ERR, fname, line, SYNTHETIC_ERRNO(EINVAL),
1863 "Lines of type 'r' don't take a name field.");
1864
1865 if (!resolved_id)
1866 return log_syntax(NULL, LOG_ERR, fname, line, SYNTHETIC_ERRNO(EINVAL),
1867 "Lines of type 'r' require an ID range in the third field.");
1868
1869 if (description || home || shell)
1870 return log_syntax(NULL, LOG_ERR, fname, line, SYNTHETIC_ERRNO(EINVAL),
1871 "Lines of type '%c' don't take a %s field.",
1872 action[0],
1873 description ? "GECOS" : home ? "home directory" : "login shell");
1874
1875 r = uid_range_add_str(&c->uid_range, resolved_id);
1876 if (r < 0)
1877 return log_syntax(NULL, LOG_ERR, fname, line, SYNTHETIC_ERRNO(EINVAL),
1878 "Invalid UID range %s.", resolved_id);
1879
1880 return 0;
1881
1882 case ADD_MEMBER: {
1883 /* Try to extend an existing member or group item */
1884 if (!name)
1885 return log_syntax(NULL, LOG_ERR, fname, line, SYNTHETIC_ERRNO(EINVAL),
1886 "Lines of type 'm' require a user name in the second field.");
1887
1888 if (locked)
1889 return log_syntax(NULL, LOG_ERR, fname, line, SYNTHETIC_ERRNO(EINVAL),
1890 "Flag '!' not permitted on lines of type 'm'.");
1891
1892 if (!resolved_id)
1893 return log_syntax(NULL, LOG_ERR, fname, line, SYNTHETIC_ERRNO(EINVAL),
1894 "Lines of type 'm' require a group name in the third field.");
1895
1896 if (!valid_user_group_name(resolved_id, 0))
1897 return log_syntax(NULL, LOG_ERR, fname, line, SYNTHETIC_ERRNO(EINVAL),
1898 "'%s' is not a valid user or group name.", resolved_id);
1899
1900 if (description || home || shell)
1901 return log_syntax(NULL, LOG_ERR, fname, line, SYNTHETIC_ERRNO(EINVAL),
1902 "Lines of type '%c' don't take a %s field.",
1903 action[0],
1904 description ? "GECOS" : home ? "home directory" : "login shell");
1905
1906 r = string_strv_ordered_hashmap_put(&c->members, resolved_id, resolved_name);
1907 if (r < 0)
1908 return log_error_errno(r, "Failed to store mapping for %s: %m", resolved_id);
1909
1910 return 0;
1911 }
1912
1913 case ADD_USER:
1914 if (!name)
1915 return log_syntax(NULL, LOG_ERR, fname, line, SYNTHETIC_ERRNO(EINVAL),
1916 "Lines of type 'u' require a user name in the second field.");
1917
1918 r = ordered_hashmap_ensure_allocated(&c->users, &item_hash_ops);
1919 if (r < 0)
1920 return log_oom();
1921
1922 i = item_new(ADD_USER, resolved_name, fname, line);
1923 if (!i)
1924 return log_oom();
1925
1926 if (resolved_id) {
1927 if (path_is_absolute(resolved_id))
1928 i->uid_path = path_simplify(TAKE_PTR(resolved_id));
1929 else {
1930 _cleanup_free_ char *uid = NULL, *gid = NULL;
1931 if (split_pair(resolved_id, ":", &uid, &gid) == 0) {
1932 r = parse_gid(gid, &i->gid);
1933 if (r < 0) {
1934 if (valid_user_group_name(gid, 0))
1935 i->group_name = TAKE_PTR(gid);
1936 else
1937 return log_syntax(NULL, LOG_ERR, fname, line, r,
1938 "Failed to parse GID: '%s': %m", id);
1939 } else {
1940 i->gid_set = true;
1941 i->id_set_strict = true;
1942 }
1943 free_and_replace(resolved_id, uid);
1944 }
1945 if (!streq(resolved_id, "-")) {
1946 r = parse_uid(resolved_id, &i->uid);
1947 if (r < 0)
1948 return log_syntax(NULL, LOG_ERR, fname, line, r,
1949 "Failed to parse UID: '%s': %m", id);
1950 i->uid_set = true;
1951 }
1952 }
1953 }
1954
1955 i->description = TAKE_PTR(resolved_description);
1956 i->home = TAKE_PTR(resolved_home);
1957 i->shell = TAKE_PTR(resolved_shell);
1958 i->locked = locked;
1959
1960 h = c->users;
1961 break;
1962
1963 case ADD_GROUP:
1964 if (!name)
1965 return log_syntax(NULL, LOG_ERR, fname, line, SYNTHETIC_ERRNO(EINVAL),
1966 "Lines of type 'g' require a user name in the second field.");
1967
1968 if (locked)
1969 return log_syntax(NULL, LOG_ERR, fname, line, SYNTHETIC_ERRNO(EINVAL),
1970 "Flag '!' not permitted on lines of type 'g'.");
1971
1972 if (description || home || shell)
1973 return log_syntax(NULL, LOG_ERR, fname, line, SYNTHETIC_ERRNO(EINVAL),
1974 "Lines of type '%c' don't take a %s field.",
1975 action[0],
1976 description ? "GECOS" : home ? "home directory" : "login shell");
1977
1978 r = ordered_hashmap_ensure_allocated(&c->groups, &item_hash_ops);
1979 if (r < 0)
1980 return log_oom();
1981
1982 i = item_new(ADD_GROUP, resolved_name, fname, line);
1983 if (!i)
1984 return log_oom();
1985
1986 if (resolved_id) {
1987 if (path_is_absolute(resolved_id))
1988 i->gid_path = path_simplify(TAKE_PTR(resolved_id));
1989 else {
1990 r = parse_gid(resolved_id, &i->gid);
1991 if (r < 0)
1992 return log_syntax(NULL, LOG_ERR, fname, line, r,
1993 "Failed to parse GID: '%s': %m", id);
1994
1995 i->gid_set = true;
1996 }
1997 }
1998
1999 h = c->groups;
2000 break;
2001
2002 default:
2003 assert_not_reached();
2004 }
2005
2006 existing = ordered_hashmap_get(h, i->name);
2007 if (existing) {
2008 /* Two functionally-equivalent items are fine */
2009 r = item_equivalent(i, existing);
2010 if (r < 0)
2011 return r;
2012 if (r == 0) {
2013 if (existing->filename)
2014 log_syntax(NULL, LOG_WARNING, fname, line, 0,
2015 "Conflict with earlier configuration for %s '%s' in %s:%u, ignoring line.",
2016 item_type_to_string(i->type),
2017 i->name,
2018 existing->filename, existing->line);
2019 else
2020 log_syntax(NULL, LOG_WARNING, fname, line, 0,
2021 "Conflict with earlier configuration for %s '%s', ignoring line.",
2022 item_type_to_string(i->type),
2023 i->name);
2024 }
2025
2026 return 0;
2027 }
2028
2029 r = ordered_hashmap_put(h, i->name, i);
2030 if (r < 0)
2031 return log_oom();
2032
2033 i = NULL;
2034 return 0;
2035 }
2036
2037 static int read_config_file(Context *c, const char *fn, bool ignore_enoent) {
2038 return conf_file_read(
2039 arg_root,
2040 (const char**) CONF_PATHS_STRV("sysusers.d"),
2041 ASSERT_PTR(fn),
2042 parse_line,
2043 ASSERT_PTR(c),
2044 ignore_enoent,
2045 /* invalid_config= */ NULL);
2046 }
2047
2048 static int cat_config(void) {
2049 _cleanup_strv_free_ char **files = NULL;
2050 int r;
2051
2052 r = conf_files_list_with_replacement(arg_root, CONF_PATHS_STRV("sysusers.d"), arg_replace, &files, NULL);
2053 if (r < 0)
2054 return r;
2055
2056 pager_open(arg_pager_flags);
2057
2058 return cat_files(NULL, files, arg_cat_flags);
2059 }
2060
2061 static int help(void) {
2062 _cleanup_free_ char *link = NULL;
2063 int r;
2064
2065 r = terminal_urlify_man("systemd-sysusers.service", "8", &link);
2066 if (r < 0)
2067 return log_oom();
2068
2069 printf("%1$s [OPTIONS...] [CONFIGURATION FILE...]\n"
2070 "\n%2$sCreates system user and group accounts.%4$s\n"
2071 "\n%3$sCommands:%4$s\n"
2072 " --cat-config Show configuration files\n"
2073 " --tldr Show non-comment parts of configuration\n"
2074 " -h --help Show this help\n"
2075 " --version Show package version\n"
2076 "\n%3$sOptions:%4$s\n"
2077 " --root=PATH Operate on an alternate filesystem root\n"
2078 " --image=PATH Operate on disk image as filesystem root\n"
2079 " --image-policy=POLICY Specify disk image dissection policy\n"
2080 " --replace=PATH Treat arguments as replacement for PATH\n"
2081 " --dry-run Just print what would be done\n"
2082 " --inline Treat arguments as configuration lines\n"
2083 " --no-pager Do not pipe output into a pager\n"
2084 "\nSee the %5$s for details.\n",
2085 program_invocation_short_name,
2086 ansi_highlight(),
2087 ansi_underline(),
2088 ansi_normal(),
2089 link);
2090
2091 return 0;
2092 }
2093
2094 static int parse_argv(int argc, char *argv[]) {
2095
2096 enum {
2097 ARG_VERSION = 0x100,
2098 ARG_CAT_CONFIG,
2099 ARG_TLDR,
2100 ARG_ROOT,
2101 ARG_IMAGE,
2102 ARG_IMAGE_POLICY,
2103 ARG_REPLACE,
2104 ARG_DRY_RUN,
2105 ARG_INLINE,
2106 ARG_NO_PAGER,
2107 };
2108
2109 static const struct option options[] = {
2110 { "help", no_argument, NULL, 'h' },
2111 { "version", no_argument, NULL, ARG_VERSION },
2112 { "cat-config", no_argument, NULL, ARG_CAT_CONFIG },
2113 { "tldr", no_argument, NULL, ARG_TLDR },
2114 { "root", required_argument, NULL, ARG_ROOT },
2115 { "image", required_argument, NULL, ARG_IMAGE },
2116 { "image-policy", required_argument, NULL, ARG_IMAGE_POLICY },
2117 { "replace", required_argument, NULL, ARG_REPLACE },
2118 { "dry-run", no_argument, NULL, ARG_DRY_RUN },
2119 { "inline", no_argument, NULL, ARG_INLINE },
2120 { "no-pager", no_argument, NULL, ARG_NO_PAGER },
2121 {}
2122 };
2123
2124 int c, r;
2125
2126 assert(argc >= 0);
2127 assert(argv);
2128
2129 while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0)
2130
2131 switch (c) {
2132
2133 case 'h':
2134 return help();
2135
2136 case ARG_VERSION:
2137 return version();
2138
2139 case ARG_CAT_CONFIG:
2140 arg_cat_flags = CAT_CONFIG_ON;
2141 break;
2142
2143 case ARG_TLDR:
2144 arg_cat_flags = CAT_TLDR;
2145 break;
2146
2147 case ARG_ROOT:
2148 r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_root);
2149 if (r < 0)
2150 return r;
2151 break;
2152
2153 case ARG_IMAGE:
2154 #ifdef STANDALONE
2155 return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP),
2156 "This systemd-sysusers version is compiled without support for --image=.");
2157 #else
2158 r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_image);
2159 if (r < 0)
2160 return r;
2161 break;
2162 #endif
2163
2164 case ARG_IMAGE_POLICY:
2165 r = parse_image_policy_argument(optarg, &arg_image_policy);
2166 if (r < 0)
2167 return r;
2168 break;
2169
2170 case ARG_REPLACE:
2171 if (!path_is_absolute(optarg))
2172 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
2173 "The argument to --replace= must be an absolute path.");
2174 if (!endswith(optarg, ".conf"))
2175 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
2176 "The argument to --replace= must have the extension '.conf'.");
2177
2178 arg_replace = optarg;
2179 break;
2180
2181 case ARG_DRY_RUN:
2182 arg_dry_run = true;
2183 break;
2184
2185 case ARG_INLINE:
2186 arg_inline = true;
2187 break;
2188
2189 case ARG_NO_PAGER:
2190 arg_pager_flags |= PAGER_DISABLE;
2191 break;
2192
2193 case '?':
2194 return -EINVAL;
2195
2196 default:
2197 assert_not_reached();
2198 }
2199
2200 if (arg_replace && arg_cat_flags != CAT_CONFIG_OFF)
2201 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
2202 "Option --replace= is not supported with --cat-config/--tldr.");
2203
2204 if (arg_replace && optind >= argc)
2205 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
2206 "When --replace= is given, some configuration items must be specified.");
2207
2208 if (arg_image && arg_root)
2209 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
2210 "Use either --root= or --image=, the combination of both is not supported.");
2211
2212 return 1;
2213 }
2214
2215 static int parse_arguments(Context *c, char **args) {
2216 unsigned pos = 1;
2217 int r;
2218
2219 assert(c);
2220
2221 STRV_FOREACH(arg, args) {
2222 if (arg_inline)
2223 /* Use (argument):n, where n==1 for the first positional arg */
2224 r = parse_line("(argument)", pos, *arg, /* invalid_config= */ NULL, c);
2225 else
2226 r = read_config_file(c, *arg, /* ignore_enoent= */ false);
2227 if (r < 0)
2228 return r;
2229
2230 pos++;
2231 }
2232
2233 return 0;
2234 }
2235
2236 static int read_config_files(Context *c, char **args) {
2237 _cleanup_strv_free_ char **files = NULL;
2238 _cleanup_free_ char *p = NULL;
2239 int r;
2240
2241 assert(c);
2242
2243 r = conf_files_list_with_replacement(arg_root, CONF_PATHS_STRV("sysusers.d"), arg_replace, &files, &p);
2244 if (r < 0)
2245 return r;
2246
2247 STRV_FOREACH(f, files)
2248 if (p && path_equal(*f, p)) {
2249 log_debug("Parsing arguments at position \"%s\"%s", *f, glyph(GLYPH_ELLIPSIS));
2250
2251 r = parse_arguments(c, args);
2252 if (r < 0)
2253 return r;
2254 } else {
2255 log_debug("Reading config file \"%s\"%s", *f, glyph(GLYPH_ELLIPSIS));
2256
2257 /* Just warn, ignore result otherwise */
2258 (void) read_config_file(c, *f, /* ignore_enoent= */ true);
2259 }
2260
2261 return 0;
2262 }
2263
2264 static int read_credential_lines(Context *c) {
2265 _cleanup_free_ char *j = NULL;
2266 const char *d;
2267 int r;
2268
2269 assert(c);
2270
2271 r = get_credentials_dir(&d);
2272 if (r == -ENXIO)
2273 return 0;
2274 if (r < 0)
2275 return log_error_errno(r, "Failed to get credentials directory: %m");
2276
2277 j = path_join(d, "sysusers.extra");
2278 if (!j)
2279 return log_oom();
2280
2281 (void) read_config_file(c, j, /* ignore_enoent= */ true);
2282 return 0;
2283 }
2284
2285 static int run(int argc, char *argv[]) {
2286 #ifndef STANDALONE
2287 _cleanup_(loop_device_unrefp) LoopDevice *loop_device = NULL;
2288 _cleanup_(umount_and_freep) char *mounted_dir = NULL;
2289 #endif
2290 _cleanup_close_ int lock = -EBADF;
2291 _cleanup_(context_done) Context c = {
2292 .audit_fd = -EBADF,
2293 .search_uid = UID_INVALID,
2294 };
2295
2296 Item *i;
2297 int r;
2298
2299 r = parse_argv(argc, argv);
2300 if (r <= 0)
2301 return r;
2302
2303 log_setup();
2304
2305 if (arg_cat_flags != CAT_CONFIG_OFF)
2306 return cat_config();
2307
2308 if (should_bypass("SYSTEMD_SYSUSERS"))
2309 return 0;
2310
2311 umask(0022);
2312
2313 r = mac_init();
2314 if (r < 0)
2315 return r;
2316
2317 #ifndef STANDALONE
2318 if (arg_image) {
2319 assert(!arg_root);
2320
2321 r = mount_image_privately_interactively(
2322 arg_image,
2323 arg_image_policy,
2324 DISSECT_IMAGE_GENERIC_ROOT |
2325 DISSECT_IMAGE_REQUIRE_ROOT |
2326 DISSECT_IMAGE_VALIDATE_OS |
2327 DISSECT_IMAGE_RELAX_VAR_CHECK |
2328 DISSECT_IMAGE_FSCK |
2329 DISSECT_IMAGE_GROWFS |
2330 DISSECT_IMAGE_ALLOW_USERSPACE_VERITY,
2331 &mounted_dir,
2332 /* ret_dir_fd= */ NULL,
2333 &loop_device);
2334 if (r < 0)
2335 return r;
2336
2337 arg_root = strdup(mounted_dir);
2338 if (!arg_root)
2339 return log_oom();
2340 }
2341 #else
2342 assert(!arg_image);
2343 #endif
2344
2345 /* Prepare to emit audit events, but only if we're operating on the host system. */
2346 if (!arg_root)
2347 c.audit_fd = open_audit_fd_or_warn();
2348
2349 /* If command line arguments are specified along with --replace, read all configuration files and
2350 * insert the positional arguments at the specified place. Otherwise, if command line arguments are
2351 * specified, execute just them, and finally, without --replace= or any positional arguments, just
2352 * read configuration and execute it. */
2353 if (arg_replace || optind >= argc)
2354 r = read_config_files(&c, argv + optind);
2355 else
2356 r = parse_arguments(&c, argv + optind);
2357 if (r < 0)
2358 return r;
2359
2360 r = read_credential_lines(&c);
2361 if (r < 0)
2362 return r;
2363
2364 /* Let's tell nss-systemd not to synthesize the "root" and "nobody" entries for it, so that our
2365 * detection whether the names or UID/GID area already used otherwise doesn't get confused. After
2366 * all, even though nss-systemd synthesizes these users/groups, they should still appear in
2367 * /etc/passwd and /etc/group, as the synthesizing logic is merely supposed to be fallback for cases
2368 * where we run with a completely unpopulated /etc. */
2369 if (setenv("SYSTEMD_NSS_BYPASS_SYNTHETIC", "1", 1) < 0)
2370 return log_error_errno(errno, "Failed to set SYSTEMD_NSS_BYPASS_SYNTHETIC environment variable: %m");
2371
2372 if (!c.uid_range) {
2373 /* Default to default range of SYSTEMD_UID_MIN..SYSTEM_UID_MAX. */
2374 r = read_login_defs(&c.login_defs, NULL, arg_root);
2375 if (r < 0)
2376 return log_error_errno(r, "Failed to read %s%s: %m",
2377 strempty(arg_root), "/etc/login.defs");
2378
2379 c.login_defs_need_warning = true;
2380
2381 /* We pick a range that very conservative: we look at compiled-in maximum and the value in
2382 * /etc/login.defs. That way the UIDs/GIDs which we allocate will be interpreted correctly,
2383 * even if /etc/login.defs is removed later. (The bottom bound doesn't matter much, since
2384 * it's only used during allocation, so we use the configured value directly). */
2385 uid_t begin = c.login_defs.system_alloc_uid_min,
2386 end = MIN3((uid_t) SYSTEM_UID_MAX, c.login_defs.system_uid_max, c.login_defs.system_gid_max);
2387 if (begin < end) {
2388 r = uid_range_add(&c.uid_range, begin, end - begin + 1);
2389 if (r < 0)
2390 return log_oom();
2391 }
2392 }
2393
2394 r = add_implicit(&c);
2395 if (r < 0)
2396 return r;
2397
2398 if (!arg_dry_run) {
2399 lock = take_etc_passwd_lock(arg_root);
2400 if (lock < 0)
2401 return log_error_errno(lock, "Failed to take /etc/passwd lock: %m");
2402 }
2403
2404 r = load_user_database(&c);
2405 if (r < 0)
2406 return log_error_errno(r, "Failed to load user database: %m");
2407
2408 r = load_group_database(&c);
2409 if (r < 0)
2410 return log_error_errno(r, "Failed to read group database: %m");
2411
2412 ORDERED_HASHMAP_FOREACH(i, c.groups)
2413 (void) process_item(&c, i);
2414
2415 ORDERED_HASHMAP_FOREACH(i, c.users)
2416 (void) process_item(&c, i);
2417
2418 return write_files(&c);
2419 }
2420
2421 DEFINE_MAIN_FUNCTION(run);