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