]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/sysusers/sysusers.c
build-sys: Do not distribute generated emergency.service
[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>
26#include <getopt.h>
932ad62b 27#include <utmp.h>
1b992147
LP
28
29#include "util.h"
30#include "hashmap.h"
31#include "specifier.h"
32#include "path-util.h"
33#include "build.h"
34#include "strv.h"
35#include "conf-files.h"
36#include "copy.h"
37#include "utf8.h"
38
39typedef enum ItemType {
40 ADD_USER = 'u',
41 ADD_GROUP = 'g',
a12b0cc3 42 ADD_MEMBER = 'm',
1b992147
LP
43} ItemType;
44typedef struct Item {
45 ItemType type;
46
47 char *name;
48 char *uid_path;
49 char *gid_path;
50 char *description;
51
52 gid_t gid;
53 uid_t uid;
54
55 bool gid_set:1;
56 bool uid_set:1;
57
c1b6b04f
KS
58 bool todo_user:1;
59 bool todo_group:1;
1b992147
LP
60} Item;
61
62static char *arg_root = NULL;
63
64static const char conf_file_dirs[] =
938a560b
ZJS
65 "/etc/sysusers.d\0"
66 "/run/sysusers.d\0"
1b992147
LP
67 "/usr/local/lib/sysusers.d\0"
68 "/usr/lib/sysusers.d\0"
69#ifdef HAVE_SPLIT_USR
70 "/lib/sysusers.d\0"
71#endif
72 ;
73
74static Hashmap *users = NULL, *groups = NULL;
75static Hashmap *todo_uids = NULL, *todo_gids = NULL;
a12b0cc3 76static Hashmap *members = NULL;
1b992147
LP
77
78static Hashmap *database_uid = NULL, *database_user = NULL;
79static Hashmap *database_gid = NULL, *database_group = NULL;
80
81static uid_t search_uid = SYSTEM_UID_MAX;
82static gid_t search_gid = SYSTEM_GID_MAX;
83
84#define UID_TO_PTR(u) (ULONG_TO_PTR(u+1))
85#define PTR_TO_UID(u) ((uid_t) (PTR_TO_ULONG(u)-1))
86
87#define GID_TO_PTR(g) (ULONG_TO_PTR(g+1))
88#define PTR_TO_GID(g) ((gid_t) (PTR_TO_ULONG(g)-1))
89
90#define fix_root(x) (arg_root ? strappenda(arg_root, x) : x)
91
92static int load_user_database(void) {
93 _cleanup_fclose_ FILE *f = NULL;
94 const char *passwd_path;
95 struct passwd *pw;
96 int r;
97
98 passwd_path = fix_root("/etc/passwd");
99 f = fopen(passwd_path, "re");
100 if (!f)
101 return errno == ENOENT ? 0 : -errno;
102
103 r = hashmap_ensure_allocated(&database_user, string_hash_func, string_compare_func);
104 if (r < 0)
105 return r;
106
107 r = hashmap_ensure_allocated(&database_uid, trivial_hash_func, trivial_compare_func);
108 if (r < 0)
109 return r;
110
111 errno = 0;
112 while ((pw = fgetpwent(f))) {
113 char *n;
114 int k, q;
115
116 n = strdup(pw->pw_name);
117 if (!n)
118 return -ENOMEM;
119
120 k = hashmap_put(database_user, n, UID_TO_PTR(pw->pw_uid));
121 if (k < 0 && k != -EEXIST) {
122 free(n);
123 return k;
124 }
125
126 q = hashmap_put(database_uid, UID_TO_PTR(pw->pw_uid), n);
127 if (q < 0 && q != -EEXIST) {
128 if (k < 0)
129 free(n);
130 return q;
131 }
132
133 if (q < 0 && k < 0)
134 free(n);
135
136 errno = 0;
137 }
138 if (!IN_SET(errno, 0, ENOENT))
139 return -errno;
140
141 return 0;
142}
143
144static int load_group_database(void) {
145 _cleanup_fclose_ FILE *f = NULL;
146 const char *group_path;
147 struct group *gr;
148 int r;
149
150 group_path = fix_root("/etc/group");
151 f = fopen(group_path, "re");
152 if (!f)
153 return errno == ENOENT ? 0 : -errno;
154
155 r = hashmap_ensure_allocated(&database_group, string_hash_func, string_compare_func);
156 if (r < 0)
157 return r;
158
159 r = hashmap_ensure_allocated(&database_gid, trivial_hash_func, trivial_compare_func);
160 if (r < 0)
161 return r;
162
163 errno = 0;
164 while ((gr = fgetgrent(f))) {
165 char *n;
166 int k, q;
167
168 n = strdup(gr->gr_name);
169 if (!n)
170 return -ENOMEM;
171
172 k = hashmap_put(database_group, n, GID_TO_PTR(gr->gr_gid));
173 if (k < 0 && k != -EEXIST) {
174 free(n);
175 return k;
176 }
177
178 q = hashmap_put(database_gid, GID_TO_PTR(gr->gr_gid), n);
179 if (q < 0 && q != -EEXIST) {
180 if (k < 0)
181 free(n);
182 return q;
183 }
184
185 if (q < 0 && k < 0)
186 free(n);
187
188 errno = 0;
189 }
190 if (!IN_SET(errno, 0, ENOENT))
191 return -errno;
192
193 return 0;
194}
195
196static int make_backup(const char *x) {
197 _cleanup_close_ int src = -1, dst = -1;
198 char *backup, *temp;
199 struct timespec ts[2];
200 struct stat st;
201 int r;
202
203 src = open(x, O_RDONLY|O_CLOEXEC|O_NOCTTY);
204 if (src < 0) {
205 if (errno == ENOENT) /* No backup necessary... */
206 return 0;
207
208 return -errno;
209 }
210
211 if (fstat(src, &st) < 0)
212 return -errno;
213
214 temp = strappenda(x, ".XXXXXX");
215 dst = mkostemp_safe(temp, O_WRONLY|O_CLOEXEC|O_NOCTTY);
216 if (dst < 0)
217 return dst;
218
93240d3a 219 r = copy_bytes(src, dst, (off_t) -1);
1b992147
LP
220 if (r < 0)
221 goto fail;
222
223 /* Copy over the access mask */
224 if (fchmod(dst, st.st_mode & 07777) < 0) {
225 r = -errno;
226 goto fail;
227 }
228
229 /* Don't fail on chmod(). If it stays owned by us, then it
230 * isn't too bad... */
231 fchown(dst, st.st_uid, st.st_gid);
232
233 ts[0] = st.st_atim;
234 ts[1] = st.st_mtim;
235 futimens(dst, ts);
236
237 backup = strappenda(x, "-");
238 if (rename(temp, backup) < 0)
239 goto fail;
240
241 return 0;
242
243fail:
244 unlink(temp);
245 return r;
246}
247
a12b0cc3
LP
248static int putgrent_with_members(const struct group *gr, FILE *group) {
249 char **a;
250
251 assert(gr);
252 assert(group);
253
254 a = hashmap_get(members, gr->gr_name);
255 if (a) {
256 _cleanup_strv_free_ char **l = NULL;
257 bool added = false;
258 char **i;
259
260 l = strv_copy(gr->gr_mem);
261 if (!l)
262 return -ENOMEM;
263
264 STRV_FOREACH(i, a) {
265 if (strv_find(l, *i))
266 continue;
267
268 if (strv_extend(&l, *i) < 0)
269 return -ENOMEM;
270
271 added = true;
272 }
273
274 if (added) {
275 struct group t;
276
277 strv_uniq(l);
278 strv_sort(l);
279
280 t = *gr;
281 t.gr_mem = l;
282
283 errno = 0;
284 if (putgrent(&t, group) != 0)
285 return errno ? -errno : -EIO;
286
287 return 1;
288 }
289 }
290
291 errno = 0;
292 if (putgrent(gr, group) != 0)
293 return errno ? -errno : -EIO;
294
295 return 0;
296}
297
1b992147
LP
298static int write_files(void) {
299
300 _cleanup_fclose_ FILE *passwd = NULL, *group = NULL;
301 _cleanup_free_ char *passwd_tmp = NULL, *group_tmp = NULL;
302 const char *passwd_path = NULL, *group_path = NULL;
a12b0cc3 303 bool group_changed = false;
1b992147
LP
304 Iterator iterator;
305 Item *i;
306 int r;
307
308 /* We don't patch /etc/shadow or /etc/gshadow here, since we
309 * only create user accounts without passwords anyway. */
310
a12b0cc3 311 if (hashmap_size(todo_gids) > 0 || hashmap_size(members) > 0) {
1b992147
LP
312 _cleanup_fclose_ FILE *original = NULL;
313
314 group_path = fix_root("/etc/group");
315 r = fopen_temporary(group_path, &group, &group_tmp);
316 if (r < 0)
317 goto finish;
318
319 if (fchmod(fileno(group), 0644) < 0) {
320 r = -errno;
321 goto finish;
322 }
323
324 original = fopen(group_path, "re");
325 if (original) {
326 struct group *gr;
327
328 errno = 0;
329 while ((gr = fgetgrent(original))) {
330 /* Safety checks against name and GID
331 * collisions. Normally, this should
332 * be unnecessary, but given that we
333 * look at the entries anyway here,
334 * let's make an extra verification
335 * step that we don't generate
336 * duplicate entries. */
337
338 i = hashmap_get(groups, gr->gr_name);
c1b6b04f 339 if (i && i->todo_group) {
1b992147
LP
340 r = -EEXIST;
341 goto finish;
342 }
343
344 if (hashmap_contains(todo_gids, GID_TO_PTR(gr->gr_gid))) {
345 r = -EEXIST;
346 goto finish;
347 }
348
a12b0cc3
LP
349 r = putgrent_with_members(gr, group);
350 if (r < 0)
1b992147 351 goto finish;
a12b0cc3
LP
352
353 if (r > 0)
354 group_changed = true;
1b992147
LP
355
356 errno = 0;
357 }
358 if (!IN_SET(errno, 0, ENOENT)) {
359 r = -errno;
360 goto finish;
361 }
362
363 } else if (errno != ENOENT) {
364 r = -errno;
365 goto finish;
366 }
367
368 HASHMAP_FOREACH(i, todo_gids, iterator) {
369 struct group n = {
370 .gr_name = i->name,
371 .gr_gid = i->gid,
372 .gr_passwd = (char*) "x",
373 };
374
a12b0cc3
LP
375 r = putgrent_with_members(&n, group);
376 if (r < 0)
1b992147 377 goto finish;
a12b0cc3
LP
378
379 group_changed = true;
1b992147
LP
380 }
381
382 r = fflush_and_check(group);
383 if (r < 0)
384 goto finish;
385 }
386
387 if (hashmap_size(todo_uids) > 0) {
388 _cleanup_fclose_ FILE *original = NULL;
389
390 passwd_path = fix_root("/etc/passwd");
391 r = fopen_temporary(passwd_path, &passwd, &passwd_tmp);
392 if (r < 0)
393 goto finish;
394
395 if (fchmod(fileno(passwd), 0644) < 0) {
396 r = -errno;
397 goto finish;
398 }
399
400 original = fopen(passwd_path, "re");
401 if (original) {
402 struct passwd *pw;
403
404 errno = 0;
405 while ((pw = fgetpwent(original))) {
406
407 i = hashmap_get(users, pw->pw_name);
c1b6b04f 408 if (i && i->todo_user) {
1b992147
LP
409 r = -EEXIST;
410 goto finish;
411 }
412
413 if (hashmap_contains(todo_uids, UID_TO_PTR(pw->pw_uid))) {
414 r = -EEXIST;
415 goto finish;
416 }
417
a12b0cc3 418 errno = 0;
1b992147 419 if (putpwent(pw, passwd) < 0) {
a12b0cc3 420 r = errno ? -errno : -EIO;
1b992147
LP
421 goto finish;
422 }
423
424 errno = 0;
425 }
426 if (!IN_SET(errno, 0, ENOENT)) {
427 r = -errno;
428 goto finish;
429 }
430
431 } else if (errno != ENOENT) {
432 r = -errno;
433 goto finish;
434 }
435
436 HASHMAP_FOREACH(i, todo_uids, iterator) {
437 struct passwd n = {
438 .pw_name = i->name,
439 .pw_uid = i->uid,
440 .pw_gid = i->gid,
441 .pw_gecos = i->description,
442 .pw_passwd = (char*) "x",
443 };
444
445 /* Initialize the home directory and the shell
446 * to nologin, with one exception: for root we
447 * patch in something special */
448 if (i->uid == 0) {
449 n.pw_shell = (char*) "/bin/sh";
450 n.pw_dir = (char*) "/root";
451 } else {
452 n.pw_shell = (char*) "/sbin/nologin";
453 n.pw_dir = (char*) "/";
454 }
455
a12b0cc3
LP
456 errno = 0;
457 if (putpwent(&n, passwd) != 0) {
458 r = errno ? -errno : -EIO;
1b992147
LP
459 goto finish;
460 }
461 }
462
463 r = fflush_and_check(passwd);
464 if (r < 0)
465 goto finish;
466 }
467
468 /* Make a backup of the old files */
a12b0cc3 469 if (group && group_changed) {
1b992147
LP
470 r = make_backup(group_path);
471 if (r < 0)
472 goto finish;
473 }
474
475 if (passwd) {
476 r = make_backup(passwd_path);
477 if (r < 0)
478 goto finish;
479 }
480
481 /* And make the new files count */
a12b0cc3 482 if (group && group_changed) {
1b992147
LP
483 if (rename(group_tmp, group_path) < 0) {
484 r = -errno;
485 goto finish;
486 }
487
488 free(group_tmp);
489 group_tmp = NULL;
490 }
491
492 if (passwd) {
493 if (rename(passwd_tmp, passwd_path) < 0) {
494 r = -errno;
495 goto finish;
496 }
497
498 free(passwd_tmp);
499 passwd_tmp = NULL;
500 }
501
a12b0cc3 502 r = 0;
1b992147
LP
503
504finish:
a12b0cc3
LP
505 if (passwd_tmp)
506 unlink(passwd_tmp);
507 if (group_tmp)
508 unlink(group_tmp);
1b992147
LP
509
510 return r;
511}
512
513static int uid_is_ok(uid_t uid, const char *name) {
514 struct passwd *p;
515 struct group *g;
516 const char *n;
517 Item *i;
518
519 /* Let's see if we already have assigned the UID a second time */
520 if (hashmap_get(todo_uids, UID_TO_PTR(uid)))
521 return 0;
522
523 /* Try to avoid using uids that are already used by a group
524 * that doesn't have the same name as our new user. */
525 i = hashmap_get(todo_gids, GID_TO_PTR(uid));
526 if (i && !streq(i->name, name))
527 return 0;
528
529 /* Let's check the files directly */
530 if (hashmap_contains(database_uid, UID_TO_PTR(uid)))
531 return 0;
532
533 n = hashmap_get(database_gid, GID_TO_PTR(uid));
534 if (n && !streq(n, name))
535 return 0;
536
537 /* Let's also check via NSS, to avoid UID clashes over LDAP and such, just in case */
538 if (!arg_root) {
539 errno = 0;
540 p = getpwuid(uid);
541 if (p)
542 return 0;
b0284aba 543 if (!IN_SET(errno, 0, ENOENT))
1b992147
LP
544 return -errno;
545
546 errno = 0;
547 g = getgrgid((gid_t) uid);
548 if (g) {
549 if (!streq(g->gr_name, name))
550 return 0;
b0284aba 551 } else if (!IN_SET(errno, 0, ENOENT))
1b992147
LP
552 return -errno;
553 }
554
555 return 1;
556}
557
558static int root_stat(const char *p, struct stat *st) {
559 const char *fix;
560
561 fix = fix_root(p);
562 if (stat(fix, st) < 0)
563 return -errno;
564
565 return 0;
566}
567
568static int read_id_from_file(Item *i, uid_t *_uid, gid_t *_gid) {
569 struct stat st;
570 bool found_uid = false, found_gid = false;
571 uid_t uid;
572 gid_t gid;
573
574 assert(i);
575
576 /* First, try to get the gid directly */
577 if (_gid && i->gid_path && root_stat(i->gid_path, &st) >= 0) {
578 gid = st.st_gid;
579 found_gid = true;
580 }
581
582 /* Then, try to get the uid directly */
583 if ((_uid || (_gid && !found_gid))
584 && i->uid_path
585 && root_stat(i->uid_path, &st) >= 0) {
586
587 uid = st.st_uid;
588 found_uid = true;
589
590 /* If we need the gid, but had no success yet, also derive it from the uid path */
591 if (_gid && !found_gid) {
592 gid = st.st_gid;
593 found_gid = true;
594 }
595 }
596
597 /* If that didn't work yet, then let's reuse the gid as uid */
598 if (_uid && !found_uid && i->gid_path) {
599
600 if (found_gid) {
601 uid = (uid_t) gid;
602 found_uid = true;
603 } else if (root_stat(i->gid_path, &st) >= 0) {
604 uid = (uid_t) st.st_gid;
605 found_uid = true;
606 }
607 }
608
609 if (_uid) {
610 if (!found_uid)
611 return 0;
612
613 *_uid = uid;
614 }
615
616 if (_gid) {
617 if (!found_gid)
618 return 0;
619
620 *_gid = gid;
621 }
622
623 return 1;
624}
625
626static int add_user(Item *i) {
627 void *z;
628 int r;
629
630 assert(i);
631
632 /* Check the database directly */
633 z = hashmap_get(database_user, i->name);
634 if (z) {
635 log_debug("User %s already exists.", i->name);
c1b6b04f 636 i->uid = PTR_TO_UID(z);
1b992147
LP
637 i->uid_set = true;
638 return 0;
639 }
640
641 if (!arg_root) {
642 struct passwd *p;
643 struct spwd *sp;
644
645 /* Also check NSS */
646 errno = 0;
647 p = getpwnam(i->name);
648 if (p) {
649 log_debug("User %s already exists.", i->name);
650 i->uid = p->pw_uid;
651 i->uid_set = true;
652
653 free(i->description);
654 i->description = strdup(p->pw_gecos);
655 return 0;
656 }
b0284aba 657 if (!IN_SET(errno, 0, ENOENT)) {
1b992147
LP
658 log_error("Failed to check if user %s already exists: %m", i->name);
659 return -errno;
660 }
661
662 /* And shadow too, just to be sure */
663 errno = 0;
664 sp = getspnam(i->name);
665 if (sp) {
666 log_error("User %s already exists in shadow database, but not in user database.", i->name);
667 return -EBADMSG;
668 }
b0284aba 669 if (!IN_SET(errno, 0, ENOENT)) {
1b992147
LP
670 log_error("Failed to check if user %s already exists in shadow database: %m", i->name);
671 return -errno;
672 }
673 }
674
675 /* Try to use the suggested numeric uid */
676 if (i->uid_set) {
677 r = uid_is_ok(i->uid, i->name);
678 if (r < 0) {
679 log_error("Failed to verify uid " UID_FMT ": %s", i->uid, strerror(-r));
680 return r;
681 }
682 if (r == 0) {
683 log_debug("Suggested user ID " UID_FMT " for %s already used.", i->uid, i->name);
684 i->uid_set = false;
685 }
686 }
687
688 /* If that didn't work, try to read it from the specified path */
689 if (!i->uid_set) {
690 uid_t c;
691
692 if (read_id_from_file(i, &c, NULL) > 0) {
693
694 if (c <= 0 || c > SYSTEM_UID_MAX)
695 log_debug("User ID " UID_FMT " of file not suitable for %s.", c, i->name);
696 else {
697 r = uid_is_ok(c, i->name);
698 if (r < 0) {
699 log_error("Failed to verify uid " UID_FMT ": %s", i->uid, strerror(-r));
700 return r;
701 } else if (r > 0) {
702 i->uid = c;
703 i->uid_set = true;
704 } else
705 log_debug("User ID " UID_FMT " of file for %s is already used.", c, i->name);
706 }
707 }
708 }
709
710 /* Otherwise try to reuse the group ID */
711 if (!i->uid_set && i->gid_set) {
712 r = uid_is_ok((uid_t) i->gid, i->name);
713 if (r < 0) {
714 log_error("Failed to verify uid " UID_FMT ": %s", i->uid, strerror(-r));
715 return r;
716 }
717 if (r > 0) {
718 i->uid = (uid_t) i->gid;
719 i->uid_set = true;
720 }
721 }
722
723 /* And if that didn't work either, let's try to find a free one */
724 if (!i->uid_set) {
725 for (; search_uid > 0; search_uid--) {
726
727 r = uid_is_ok(search_uid, i->name);
728 if (r < 0) {
729 log_error("Failed to verify uid " UID_FMT ": %s", i->uid, strerror(-r));
730 return r;
731 } else if (r > 0)
732 break;
733 }
734
735 if (search_uid <= 0) {
736 log_error("No free user ID available for %s.", i->name);
737 return -E2BIG;
738 }
739
740 i->uid_set = true;
741 i->uid = search_uid;
742
743 search_uid--;
744 }
745
746 r = hashmap_ensure_allocated(&todo_uids, trivial_hash_func, trivial_compare_func);
747 if (r < 0)
748 return log_oom();
749
750 r = hashmap_put(todo_uids, UID_TO_PTR(i->uid), i);
751 if (r < 0)
752 return log_oom();
753
c1b6b04f 754 i->todo_user = true;
1b992147
LP
755 log_info("Creating user %s (%s) with uid " UID_FMT " and gid " GID_FMT ".", i->name, strna(i->description), i->uid, i->gid);
756
757 return 0;
758}
759
760static int gid_is_ok(gid_t gid) {
761 struct group *g;
762 struct passwd *p;
763
764 if (hashmap_get(todo_gids, GID_TO_PTR(gid)))
765 return 0;
766
767 /* Avoid reusing gids that are already used by a different user */
768 if (hashmap_get(todo_uids, UID_TO_PTR(gid)))
769 return 0;
770
771 if (hashmap_contains(database_gid, GID_TO_PTR(gid)))
772 return 0;
773
774 if (hashmap_contains(database_uid, UID_TO_PTR(gid)))
775 return 0;
776
777 if (!arg_root) {
778 errno = 0;
779 g = getgrgid(gid);
780 if (g)
781 return 0;
b0284aba 782 if (!IN_SET(errno, 0, ENOENT))
1b992147
LP
783 return -errno;
784
785 errno = 0;
786 p = getpwuid((uid_t) gid);
787 if (p)
788 return 0;
b0284aba 789 if (!IN_SET(errno, 0, ENOENT))
1b992147
LP
790 return -errno;
791 }
792
793 return 1;
794}
795
796static int add_group(Item *i) {
797 void *z;
798 int r;
799
800 assert(i);
801
802 /* Check the database directly */
803 z = hashmap_get(database_group, i->name);
804 if (z) {
805 log_debug("Group %s already exists.", i->name);
806 i->gid = PTR_TO_GID(z);
807 i->gid_set = true;
808 return 0;
809 }
810
811 /* Also check NSS */
812 if (!arg_root) {
813 struct group *g;
814
815 errno = 0;
816 g = getgrnam(i->name);
817 if (g) {
818 log_debug("Group %s already exists.", i->name);
819 i->gid = g->gr_gid;
820 i->gid_set = true;
821 return 0;
822 }
b0284aba 823 if (!IN_SET(errno, 0, ENOENT)) {
1b992147
LP
824 log_error("Failed to check if group %s already exists: %m", i->name);
825 return -errno;
826 }
827 }
828
829 /* Try to use the suggested numeric gid */
830 if (i->gid_set) {
831 r = gid_is_ok(i->gid);
832 if (r < 0) {
833 log_error("Failed to verify gid " GID_FMT ": %s", i->gid, strerror(-r));
834 return r;
835 }
836 if (r == 0) {
837 log_debug("Suggested group ID " GID_FMT " for %s already used.", i->gid, i->name);
838 i->gid_set = false;
839 }
840 }
841
842 /* Try to reuse the numeric uid, if there's one */
843 if (!i->gid_set && i->uid_set) {
844 r = gid_is_ok((gid_t) i->uid);
845 if (r < 0) {
846 log_error("Failed to verify gid " GID_FMT ": %s", i->gid, strerror(-r));
847 return r;
848 }
849 if (r > 0) {
850 i->gid = (gid_t) i->uid;
851 i->gid_set = true;
852 }
853 }
854
855 /* If that didn't work, try to read it from the specified path */
856 if (!i->gid_set) {
857 gid_t c;
858
859 if (read_id_from_file(i, NULL, &c) > 0) {
860
861 if (c <= 0 || c > SYSTEM_GID_MAX)
862 log_debug("Group ID " GID_FMT " of file not suitable for %s.", c, i->name);
863 else {
864 r = gid_is_ok(c);
865 if (r < 0) {
866 log_error("Failed to verify gid " GID_FMT ": %s", i->gid, strerror(-r));
867 return r;
868 } else if (r > 0) {
869 i->gid = c;
870 i->gid_set = true;
871 } else
872 log_debug("Group ID " GID_FMT " of file for %s already used.", c, i->name);
873 }
874 }
875 }
876
877 /* And if that didn't work either, let's try to find a free one */
878 if (!i->gid_set) {
879 for (; search_gid > 0; search_gid--) {
880 r = gid_is_ok(search_gid);
881 if (r < 0) {
882 log_error("Failed to verify gid " GID_FMT ": %s", i->gid, strerror(-r));
883 return r;
884 } else if (r > 0)
885 break;
886 }
887
888 if (search_gid <= 0) {
889 log_error("No free group ID available for %s.", i->name);
890 return -E2BIG;
891 }
892
893 i->gid_set = true;
894 i->gid = search_gid;
895
896 search_gid--;
897 }
898
899 r = hashmap_ensure_allocated(&todo_gids, trivial_hash_func, trivial_compare_func);
900 if (r < 0)
901 return log_oom();
902
903 r = hashmap_put(todo_gids, GID_TO_PTR(i->gid), i);
904 if (r < 0)
905 return log_oom();
906
c1b6b04f 907 i->todo_group = true;
1b992147
LP
908 log_info("Creating group %s with gid " GID_FMT ".", i->name, i->gid);
909
910 return 0;
911}
912
913static int process_item(Item *i) {
914 int r;
915
916 assert(i);
917
918 switch (i->type) {
919
920 case ADD_USER:
921 r = add_group(i);
922 if (r < 0)
923 return r;
924
925 return add_user(i);
926
927 case ADD_GROUP: {
928 Item *j;
929
930 j = hashmap_get(users, i->name);
931 if (j) {
932 /* There's already user to be created for this
933 * name, let's process that in one step */
934
935 if (i->gid_set) {
936 j->gid = i->gid;
937 j->gid_set = true;
938 }
939
940 if (i->gid_path) {
941 free(j->gid_path);
942 j->gid_path = strdup(i->gid_path);
943 if (!j->gid_path)
944 return log_oom();
945 }
946
947 return 0;
948 }
949
950 return add_group(i);
951 }
1b992147 952
a12b0cc3
LP
953 default:
954 assert_not_reached("Unknown item type");
955 }
1b992147
LP
956}
957
958static void item_free(Item *i) {
959
960 if (!i)
961 return;
962
963 free(i->name);
964 free(i->uid_path);
965 free(i->gid_path);
966 free(i->description);
967 free(i);
968}
969
970DEFINE_TRIVIAL_CLEANUP_FUNC(Item*, item_free);
971
a12b0cc3
LP
972static int add_implicit(void) {
973 char *g, **l;
974 Iterator iterator;
975 int r;
976
977 /* Implicitly create additional users and groups, if they were listed in "m" lines */
978
979 HASHMAP_FOREACH_KEY(l, g, members, iterator) {
980 Item *i;
981 char **m;
982
983 i = hashmap_get(groups, g);
984 if (!i) {
985 _cleanup_(item_freep) Item *j = NULL;
986
987 r = hashmap_ensure_allocated(&groups, string_hash_func, string_compare_func);
988 if (r < 0)
989 return log_oom();
990
991 j = new0(Item, 1);
992 if (!j)
993 return log_oom();
994
995 j->type = ADD_GROUP;
996 j->name = strdup(g);
997 if (!j->name)
998 return log_oom();
999
1000 r = hashmap_put(groups, j->name, j);
1001 if (r < 0)
1002 return log_oom();
1003
1004 log_debug("Adding implicit group '%s' due to m line", j->name);
1005 j = NULL;
1006 }
1007
1008 STRV_FOREACH(m, l) {
1009
1010 i = hashmap_get(users, *m);
1011 if (!i) {
1012 _cleanup_(item_freep) Item *j = NULL;
1013
1014 r = hashmap_ensure_allocated(&users, string_hash_func, string_compare_func);
1015 if (r < 0)
1016 return log_oom();
1017
1018 j = new0(Item, 1);
1019 if (!j)
1020 return log_oom();
1021
1022 j->type = ADD_USER;
1023 j->name = strdup(*m);
1024 if (!j->name)
1025 return log_oom();
1026
1027 r = hashmap_put(users, j->name, j);
1028 if (r < 0)
1029 return log_oom();
1030
1031 log_debug("Adding implicit user '%s' due to m line", j->name);
1032 j = NULL;
1033 }
1034 }
1035 }
1036
1037 return 0;
1038}
1039
1b992147
LP
1040static bool item_equal(Item *a, Item *b) {
1041 assert(a);
1042 assert(b);
1043
1044 if (a->type != b->type)
1045 return false;
1046
1047 if (!streq_ptr(a->name, b->name))
1048 return false;
1049
1050 if (!streq_ptr(a->uid_path, b->uid_path))
1051 return false;
1052
1053 if (!streq_ptr(a->gid_path, b->gid_path))
1054 return false;
1055
1056 if (!streq_ptr(a->description, b->description))
1057 return false;
1058
1059 if (a->uid_set != b->uid_set)
1060 return false;
1061
1062 if (a->uid_set && a->uid != b->uid)
1063 return false;
1064
1065 if (a->gid_set != b->gid_set)
1066 return false;
1067
1068 if (a->gid_set && a->gid != b->gid)
1069 return false;
1070
1071 return true;
1072}
1073
1074static bool valid_user_group_name(const char *u) {
1075 const char *i;
1076 long sz;
1077
1078 if (isempty(u) < 0)
1079 return false;
1080
1081 if (!(u[0] >= 'a' && u[0] <= 'z') &&
1082 !(u[0] >= 'A' && u[0] <= 'Z') &&
1083 u[0] != '_')
1084 return false;
1085
1086 for (i = u+1; *i; i++) {
1087 if (!(*i >= 'a' && *i <= 'z') &&
1088 !(*i >= 'A' && *i <= 'Z') &&
1089 !(*i >= '0' && *i <= '9') &&
1090 *i != '_' &&
1091 *i != '-')
1092 return false;
1093 }
1094
1095 sz = sysconf(_SC_LOGIN_NAME_MAX);
1096 assert_se(sz > 0);
1097
1098 if ((size_t) (i-u) > (size_t) sz)
1099 return false;
1100
932ad62b
LP
1101 if ((size_t) (i-u) > UT_NAMESIZE - 1)
1102 return false;
1103
1b992147
LP
1104 return true;
1105}
1106
1107static bool valid_gecos(const char *d) {
1108
1109 if (!utf8_is_valid(d))
1110 return false;
1111
38c74dad
LP
1112 if (string_has_cc(d, NULL))
1113 return false;
1114
1115 /* Colons are used as field separators, and hence not OK */
1116 if (strchr(d, ':'))
1b992147
LP
1117 return false;
1118
1119 return true;
1120}
1121
1122static int parse_line(const char *fname, unsigned line, const char *buffer) {
1123
1124 static const Specifier specifier_table[] = {
1125 { 'm', specifier_machine_id, NULL },
1126 { 'b', specifier_boot_id, NULL },
1127 { 'H', specifier_host_name, NULL },
1128 { 'v', specifier_kernel_release, NULL },
1129 {}
1130 };
1131
a12b0cc3 1132 _cleanup_free_ char *action = NULL, *name = NULL, *id = NULL, *resolved_name = NULL;
1b992147
LP
1133 _cleanup_(item_freep) Item *i = NULL;
1134 Item *existing;
1135 Hashmap *h;
1136 int r, n = -1;
1137
1138 assert(fname);
1139 assert(line >= 1);
1140 assert(buffer);
1141
1142 r = sscanf(buffer,
1143 "%ms %ms %ms %n",
1144 &action,
1145 &name,
1146 &id,
1147 &n);
1148 if (r < 2) {
1149 log_error("[%s:%u] Syntax error.", fname, line);
1150 return -EIO;
1151 }
1152
1153 if (strlen(action) != 1) {
1154 log_error("[%s:%u] Unknown modifier '%s'", fname, line, action);
1155 return -EINVAL;
1156 }
1157
a12b0cc3 1158 if (!IN_SET(action[0], ADD_USER, ADD_GROUP, ADD_MEMBER)) {
1b992147
LP
1159 log_error("[%s:%u] Unknown command command type '%c'.", fname, line, action[0]);
1160 return -EBADMSG;
1161 }
1162
a12b0cc3 1163 r = specifier_printf(name, specifier_table, NULL, &resolved_name);
1b992147
LP
1164 if (r < 0) {
1165 log_error("[%s:%u] Failed to replace specifiers: %s", fname, line, name);
1166 return r;
1167 }
1168
a12b0cc3
LP
1169 if (!valid_user_group_name(resolved_name)) {
1170 log_error("[%s:%u] '%s' is not a valid user or group name.", fname, line, resolved_name);
1b992147
LP
1171 return -EINVAL;
1172 }
1173
1174 if (n >= 0) {
1175 n += strspn(buffer+n, WHITESPACE);
1b992147 1176
a12b0cc3
LP
1177 if (STR_IN_SET(buffer + n, "", "-"))
1178 n = -1;
1b992147
LP
1179 }
1180
a12b0cc3 1181 switch (action[0]) {
1b992147 1182
a12b0cc3
LP
1183 case ADD_MEMBER: {
1184 _cleanup_free_ char *resolved_id = NULL;
1185 char **l;
1b992147 1186
a12b0cc3
LP
1187 r = hashmap_ensure_allocated(&members, string_hash_func, string_compare_func);
1188 if (r < 0)
1189 return log_oom();
1b992147 1190
a12b0cc3 1191 /* Try to extend an existing member or group item */
1b992147 1192
a12b0cc3
LP
1193 if (!id || streq(id, "-")) {
1194 log_error("[%s:%u] Lines of type 'm' require a group name in the third field.", fname, line);
1195 return -EINVAL;
1196 }
1b992147 1197
a12b0cc3
LP
1198 r = specifier_printf(id, specifier_table, NULL, &resolved_id);
1199 if (r < 0) {
1200 log_error("[%s:%u] Failed to replace specifiers: %s", fname, line, name);
1201 return r;
1202 }
1b992147 1203
a12b0cc3
LP
1204 if (!valid_user_group_name(resolved_id)) {
1205 log_error("[%s:%u] '%s' is not a valid user or group name.", fname, line, resolved_id);
1206 return -EINVAL;
1207 }
1b992147 1208
a12b0cc3
LP
1209 if (n >= 0) {
1210 log_error("[%s:%u] Lines of type 'm' don't take a GECOS field.", fname, line);
1211 return -EINVAL;
1212 }
1213
1214 l = hashmap_get(members, resolved_id);
1215 if (l) {
1216 /* A list for this group name already exists, let's append to it */
1217 r = strv_push(&l, resolved_name);
1218 if (r < 0)
1219 return log_oom();
1220
1221 resolved_name = NULL;
1222
1223 assert_se(hashmap_update(members, resolved_id, l) >= 0);
1b992147 1224 } else {
a12b0cc3
LP
1225 /* No list for this group name exists yet, create one */
1226
1227 l = new0(char *, 2);
1228 if (!l)
1229 return -ENOMEM;
1230
1231 l[0] = resolved_name;
1232 l[1] = NULL;
1b992147 1233
a12b0cc3 1234 r = hashmap_put(members, resolved_id, l);
1b992147 1235 if (r < 0) {
a12b0cc3
LP
1236 free(l);
1237 return log_oom();
1b992147
LP
1238 }
1239
a12b0cc3 1240 resolved_id = resolved_name = NULL;
1b992147 1241 }
a12b0cc3
LP
1242
1243 return 0;
1b992147
LP
1244 }
1245
a12b0cc3 1246 case ADD_USER:
1b992147 1247 r = hashmap_ensure_allocated(&users, string_hash_func, string_compare_func);
a12b0cc3
LP
1248 if (r < 0)
1249 return log_oom();
1250
1251 i = new0(Item, 1);
1252 if (!i)
1253 return log_oom();
1254
1255 if (id && !streq(id, "-")) {
1256
1257 if (path_is_absolute(id)) {
1258 i->uid_path = strdup(id);
1259 if (!i->uid_path)
1260 return log_oom();
1261
1262 path_kill_slashes(i->uid_path);
1263
1264 } else {
1265 r = parse_uid(id, &i->uid);
1266 if (r < 0) {
1267 log_error("Failed to parse UID: %s", id);
1268 return -EBADMSG;
1269 }
1270
1271 i->uid_set = true;
1272 }
1273 }
1274
1275 if (n >= 0) {
1276 i->description = unquote(buffer+n, "\"");
1277 if (!i->description)
1278 return log_oom();
1279
1280 if (!valid_gecos(i->description)) {
1281 log_error("[%s:%u] '%s' is not a valid GECOS field.", fname, line, i->description);
1282 return -EINVAL;
1283 }
1284 }
1285
1b992147 1286 h = users;
a12b0cc3
LP
1287 break;
1288
1289 case ADD_GROUP:
1b992147 1290 r = hashmap_ensure_allocated(&groups, string_hash_func, string_compare_func);
a12b0cc3
LP
1291 if (r < 0)
1292 return log_oom();
1293
1294 if (n >= 0) {
1295 log_error("[%s:%u] Lines of type 'g' don't take a GECOS field.", fname, line);
1296 return -EINVAL;
1297 }
1298
1299 i = new0(Item, 1);
1300 if (!i)
1301 return log_oom();
1302
1303 if (id && !streq(id, "-")) {
1304
1305 if (path_is_absolute(id)) {
1306 i->gid_path = strdup(id);
1307 if (!i->gid_path)
1308 return log_oom();
1309
1310 path_kill_slashes(i->gid_path);
1311 } else {
1312 r = parse_gid(id, &i->gid);
1313 if (r < 0) {
1314 log_error("Failed to parse GID: %s", id);
1315 return -EBADMSG;
1316 }
1317
1318 i->gid_set = true;
1319 }
1320 }
1321
1322
1b992147 1323 h = groups;
a12b0cc3 1324 break;
bce415ed
RC
1325 default:
1326 return -EBADMSG;
1b992147 1327 }
a12b0cc3
LP
1328
1329 i->type = action[0];
1330 i->name = resolved_name;
1331 resolved_name = NULL;
1b992147
LP
1332
1333 existing = hashmap_get(h, i->name);
1334 if (existing) {
1335
1336 /* Two identical items are fine */
1337 if (!item_equal(existing, i))
1338 log_warning("Two or more conflicting lines for %s configured, ignoring.", i->name);
1339
1340 return 0;
1341 }
1342
1343 r = hashmap_put(h, i->name, i);
a12b0cc3
LP
1344 if (r < 0)
1345 return log_oom();
1b992147
LP
1346
1347 i = NULL;
1348 return 0;
1349}
1350
1351static int read_config_file(const char *fn, bool ignore_enoent) {
1352 _cleanup_fclose_ FILE *f = NULL;
1353 char line[LINE_MAX];
1354 unsigned v = 0;
1355 int r;
1356
1357 assert(fn);
1358
1359 r = search_and_fopen_nulstr(fn, "re", arg_root, conf_file_dirs, &f);
1360 if (r < 0) {
1361 if (ignore_enoent && r == -ENOENT)
1362 return 0;
1363
1364 log_error("Failed to open '%s', ignoring: %s", fn, strerror(-r));
1365 return r;
1366 }
1367
1368 FOREACH_LINE(line, f, break) {
1369 char *l;
1370 int k;
1371
1372 v++;
1373
1374 l = strstrip(line);
1375 if (*l == '#' || *l == 0)
1376 continue;
1377
1378 k = parse_line(fn, v, l);
1379 if (k < 0 && r == 0)
1380 r = k;
1381 }
1382
1383 if (ferror(f)) {
1384 log_error("Failed to read from file %s: %m", fn);
1385 if (r == 0)
1386 r = -EIO;
1387 }
1388
1389 return r;
1390}
1391
1b992147
LP
1392static void free_database(Hashmap *by_name, Hashmap *by_id) {
1393 char *name;
1394
1395 for (;;) {
1396 name = hashmap_first(by_id);
1397 if (!name)
1398 break;
1399
1400 hashmap_remove(by_name, name);
1401
1402 hashmap_steal_first_key(by_id);
1403 free(name);
1404 }
1405
1406 while ((name = hashmap_steal_first_key(by_name)))
1407 free(name);
1408
1409 hashmap_free(by_name);
1410 hashmap_free(by_id);
1411}
1412
1413static int help(void) {
1414
1415 printf("%s [OPTIONS...] [CONFIGURATION FILE...]\n\n"
1416 "Creates system user accounts.\n\n"
1417 " -h --help Show this help\n"
1418 " --version Show package version\n"
1419 " --root=PATH Operate on an alternate filesystem root\n",
1420 program_invocation_short_name);
1421
1422 return 0;
1423}
1424
1425static int parse_argv(int argc, char *argv[]) {
1426
1427 enum {
1428 ARG_VERSION = 0x100,
1429 ARG_ROOT,
1430 };
1431
1432 static const struct option options[] = {
1433 { "help", no_argument, NULL, 'h' },
1434 { "version", no_argument, NULL, ARG_VERSION },
1435 { "root", required_argument, NULL, ARG_ROOT },
1436 {}
1437 };
1438
1439 int c;
1440
1441 assert(argc >= 0);
1442 assert(argv);
1443
1444 while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0) {
1445
1446 switch (c) {
1447
1448 case 'h':
1449 return help();
1450
1451 case ARG_VERSION:
1452 puts(PACKAGE_STRING);
1453 puts(SYSTEMD_FEATURES);
1454 return 0;
1455
1456 case ARG_ROOT:
1457 free(arg_root);
1458 arg_root = path_make_absolute_cwd(optarg);
1459 if (!arg_root)
1460 return log_oom();
1461
1462 path_kill_slashes(arg_root);
1463 break;
1464
1465 case '?':
1466 return -EINVAL;
1467
1468 default:
1469 assert_not_reached("Unhandled option");
1470 }
1471 }
1472
1473 return 1;
1474}
1475
1476int main(int argc, char *argv[]) {
1477
1478 _cleanup_close_ int lock = -1;
1479 Iterator iterator;
1480 int r, k;
1481 Item *i;
a12b0cc3 1482 char *n;
1b992147
LP
1483
1484 r = parse_argv(argc, argv);
1485 if (r <= 0)
1486 goto finish;
1487
1488 log_set_target(LOG_TARGET_AUTO);
1489 log_parse_environment();
1490 log_open();
1491
1492 umask(0022);
1493
1494 r = 0;
1495
1496 if (optind < argc) {
1497 int j;
1498
1499 for (j = optind; j < argc; j++) {
1500 k = read_config_file(argv[j], false);
1501 if (k < 0 && r == 0)
1502 r = k;
1503 }
1504 } else {
1505 _cleanup_strv_free_ char **files = NULL;
1506 char **f;
1507
1508 r = conf_files_list_nulstr(&files, ".conf", arg_root, conf_file_dirs);
1509 if (r < 0) {
1510 log_error("Failed to enumerate sysusers.d files: %s", strerror(-r));
1511 goto finish;
1512 }
1513
1514 STRV_FOREACH(f, files) {
1515 k = read_config_file(*f, true);
1516 if (k < 0 && r == 0)
1517 r = k;
1518 }
1519 }
1520
a12b0cc3
LP
1521 r = add_implicit();
1522 if (r < 0)
1523 goto finish;
1524
45035609 1525 lock = take_password_lock(arg_root);
1b992147
LP
1526 if (lock < 0) {
1527 log_error("Failed to take lock: %s", strerror(-lock));
1528 goto finish;
1529 }
1530
1531 r = load_user_database();
1532 if (r < 0) {
1533 log_error("Failed to load user database: %s", strerror(-r));
1534 goto finish;
1535 }
1536
1537 r = load_group_database();
1538 if (r < 0) {
1539 log_error("Failed to read group database: %s", strerror(-r));
1540 goto finish;
1541 }
1542
1543 HASHMAP_FOREACH(i, groups, iterator)
1544 process_item(i);
1545
1546 HASHMAP_FOREACH(i, users, iterator)
1547 process_item(i);
1548
1549 r = write_files();
1550 if (r < 0)
1551 log_error("Failed to write files: %s", strerror(-r));
1552
1553finish:
1554 while ((i = hashmap_steal_first(groups)))
1555 item_free(i);
1556
1557 while ((i = hashmap_steal_first(users)))
1558 item_free(i);
1559
a12b0cc3
LP
1560 while ((n = hashmap_first_key(members))) {
1561 strv_free(hashmap_steal_first(members));
1562 free(n);
1563 }
1564
1b992147
LP
1565 hashmap_free(groups);
1566 hashmap_free(users);
a12b0cc3 1567 hashmap_free(members);
1b992147
LP
1568 hashmap_free(todo_uids);
1569 hashmap_free(todo_gids);
1570
1571 free_database(database_user, database_uid);
1572 free_database(database_group, database_gid);
1573
1574 free(arg_root);
1575
1576 return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
1577}