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