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