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