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