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