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