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