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