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