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