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