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