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