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