chiark / gitweb /
sysusers: fix selinux context of backup files
[elogind.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 #include "label.h"
39 #include "fileio-label.h"
40
41 typedef enum ItemType {
42         ADD_USER = 'u',
43         ADD_GROUP = 'g',
44         ADD_MEMBER = 'm',
45 } ItemType;
46 typedef 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
60         bool todo_user:1;
61         bool todo_group:1;
62 } Item;
63
64 static char *arg_root = NULL;
65
66 static const char conf_file_dirs[] =
67         "/etc/sysusers.d\0"
68         "/run/sysusers.d\0"
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
76 static Hashmap *users = NULL, *groups = NULL;
77 static Hashmap *todo_uids = NULL, *todo_gids = NULL;
78 static Hashmap *members = NULL;
79
80 static Hashmap *database_uid = NULL, *database_user = NULL;
81 static Hashmap *database_gid = NULL, *database_group = NULL;
82
83 static uid_t search_uid = SYSTEM_UID_MAX;
84 static 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
94 static 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
146 static 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
198 static int make_backup(const char *target, const char *x) {
199         _cleanup_close_ int src = -1;
200         _cleanup_fclose_ FILE *dst = NULL;
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
217         r = fopen_temporary_label(target, x, &dst, &temp);
218         if (r < 0)
219                 return r;
220
221         r = copy_bytes(src, fileno(dst), (off_t) -1);
222         if (r < 0)
223                 goto fail;
224
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
230         /* Copy over the access mask */
231         if (fchmod(fileno(dst), st.st_mode & 07777) < 0)
232                 log_warning("Failed to change mode on %s: %m", backup);
233
234         if (fchown(fileno(dst), st.st_uid, st.st_gid)< 0)
235                 log_warning("Failed to change ownership of %s: %m", backup);
236
237         ts[0] = st.st_atim;
238         ts[1] = st.st_mtim;
239         futimens(fileno(dst), ts);
240
241         if (rename(temp, backup) < 0)
242                 goto fail;
243
244         return 0;
245
246 fail:
247         unlink(temp);
248         return r;
249 }
250
251 static 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
301 static 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;
306         bool group_changed = false;
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
314         if (hashmap_size(todo_gids) > 0 || hashmap_size(members) > 0) {
315                 _cleanup_fclose_ FILE *original = NULL;
316
317                 group_path = fix_root("/etc/group");
318                 r = fopen_temporary_label("/etc/group", group_path, &group, &group_tmp);
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);
342                                 if (i && i->todo_group) {
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
352                                 r = putgrent_with_members(gr, group);
353                                 if (r < 0)
354                                         goto finish;
355
356                                 if (r > 0)
357                                         group_changed = true;
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
378                         r = putgrent_with_members(&n, group);
379                         if (r < 0)
380                                 goto finish;
381
382                         group_changed = true;
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");
394                 r = fopen_temporary_label("/etc/passwd", passwd_path, &passwd, &passwd_tmp);
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);
411                                 if (i && i->todo_user) {
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
421                                 errno = 0;
422                                 if (putpwent(pw, passwd) < 0) {
423                                         r = errno ? -errno : -EIO;
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
459                         errno = 0;
460                         if (putpwent(&n, passwd) != 0) {
461                                 r = errno ? -errno : -EIO;
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 */
472         if (group && group_changed) {
473                 r = make_backup("/etc/group", group_path);
474                 if (r < 0)
475                         goto finish;
476         }
477
478         if (passwd) {
479                 r = make_backup("/etc/passwd", passwd_path);
480                 if (r < 0)
481                         goto finish;
482         }
483
484         /* And make the new files count */
485         if (group && group_changed) {
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
505         r = 0;
506
507 finish:
508         if (passwd_tmp)
509                 unlink(passwd_tmp);
510         if (group_tmp)
511                 unlink(group_tmp);
512
513         return r;
514 }
515
516 static 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;
546                 if (!IN_SET(errno, 0, ENOENT))
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;
554                 } else if (!IN_SET(errno, 0, ENOENT))
555                         return -errno;
556         }
557
558         return 1;
559 }
560
561 static 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
571 static 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
629 static 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);
639                 i->uid = PTR_TO_UID(z);
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                 }
660                 if (!IN_SET(errno, 0, ENOENT)) {
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                 }
672                 if (!IN_SET(errno, 0, ENOENT)) {
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
757         i->todo_user = true;
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
763 static 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;
785                 if (!IN_SET(errno, 0, ENOENT))
786                         return -errno;
787
788                 errno = 0;
789                 p = getpwuid((uid_t) gid);
790                 if (p)
791                         return 0;
792                 if (!IN_SET(errno, 0, ENOENT))
793                         return -errno;
794         }
795
796         return 1;
797 }
798
799 static 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                 }
826                 if (!IN_SET(errno, 0, ENOENT)) {
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
910         i->todo_group = true;
911         log_info("Creating group %s with gid " GID_FMT ".", i->name, i->gid);
912
913         return 0;
914 }
915
916 static 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         }
955
956         default:
957                 assert_not_reached("Unknown item type");
958         }
959 }
960
961 static 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
973 DEFINE_TRIVIAL_CLEANUP_FUNC(Item*, item_free);
974
975 static 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
1043 static 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
1077 static 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
1104         if ((size_t) (i-u) > UT_NAMESIZE - 1)
1105                 return false;
1106
1107         return true;
1108 }
1109
1110 static bool valid_gecos(const char *d) {
1111
1112         if (!utf8_is_valid(d))
1113                 return false;
1114
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, ':'))
1120                 return false;
1121
1122         return true;
1123 }
1124
1125 static 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
1135         _cleanup_free_ char *action = NULL, *name = NULL, *id = NULL, *resolved_name = NULL;
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
1161         if (!IN_SET(action[0], ADD_USER, ADD_GROUP, ADD_MEMBER)) {
1162                 log_error("[%s:%u] Unknown command command type '%c'.", fname, line, action[0]);
1163                 return -EBADMSG;
1164         }
1165
1166         r = specifier_printf(name, specifier_table, NULL, &resolved_name);
1167         if (r < 0) {
1168                 log_error("[%s:%u] Failed to replace specifiers: %s", fname, line, name);
1169                 return r;
1170         }
1171
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);
1174                 return -EINVAL;
1175         }
1176
1177         if (n >= 0) {
1178                 n += strspn(buffer+n, WHITESPACE);
1179
1180                 if (STR_IN_SET(buffer + n, "", "-"))
1181                         n = -1;
1182         }
1183
1184         switch (action[0]) {
1185
1186         case ADD_MEMBER: {
1187                 _cleanup_free_ char *resolved_id = NULL;
1188                 char **l;
1189
1190                 r = hashmap_ensure_allocated(&members, string_hash_func, string_compare_func);
1191                 if (r < 0)
1192                         return log_oom();
1193
1194                 /* Try to extend an existing member or group item */
1195
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                 }
1200
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                 }
1206
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                 }
1211
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);
1227                 } else {
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;
1236
1237                         r = hashmap_put(members, resolved_id, l);
1238                         if (r < 0) {
1239                                 free(l);
1240                                 return log_oom();
1241                         }
1242
1243                         resolved_id = resolved_name = NULL;
1244                 }
1245
1246                 return 0;
1247         }
1248
1249         case ADD_USER:
1250                 r = hashmap_ensure_allocated(&users, string_hash_func, string_compare_func);
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
1289                 h = users;
1290                 break;
1291
1292         case ADD_GROUP:
1293                 r = hashmap_ensure_allocated(&groups, string_hash_func, string_compare_func);
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
1326                 h = groups;
1327                 break;
1328         default:
1329                 return -EBADMSG;
1330         }
1331
1332         i->type = action[0];
1333         i->name = resolved_name;
1334         resolved_name = NULL;
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);
1347         if (r < 0)
1348                 return log_oom();
1349
1350         i = NULL;
1351         return 0;
1352 }
1353
1354 static 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
1395 static 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
1416 static int help(void) {
1417
1418         printf("%s [OPTIONS...] [CONFIGURATION FILE...]\n\n"
1419                "Creates system user accounts.\n\n"
1420                "  -h --help                 Show this help\n"
1421                "     --version              Show package version\n"
1422                "     --root=PATH            Operate on an alternate filesystem root\n",
1423                program_invocation_short_name);
1424
1425         return 0;
1426 }
1427
1428 static int parse_argv(int argc, char *argv[]) {
1429
1430         enum {
1431                 ARG_VERSION = 0x100,
1432                 ARG_ROOT,
1433         };
1434
1435         static const struct option options[] = {
1436                 { "help",    no_argument,       NULL, 'h'         },
1437                 { "version", no_argument,       NULL, ARG_VERSION },
1438                 { "root",    required_argument, NULL, ARG_ROOT    },
1439                 {}
1440         };
1441
1442         int c;
1443
1444         assert(argc >= 0);
1445         assert(argv);
1446
1447         while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0) {
1448
1449                 switch (c) {
1450
1451                 case 'h':
1452                         return help();
1453
1454                 case ARG_VERSION:
1455                         puts(PACKAGE_STRING);
1456                         puts(SYSTEMD_FEATURES);
1457                         return 0;
1458
1459                 case ARG_ROOT:
1460                         free(arg_root);
1461                         arg_root = path_make_absolute_cwd(optarg);
1462                         if (!arg_root)
1463                                 return log_oom();
1464
1465                         path_kill_slashes(arg_root);
1466                         break;
1467
1468                 case '?':
1469                         return -EINVAL;
1470
1471                 default:
1472                         assert_not_reached("Unhandled option");
1473                 }
1474         }
1475
1476         return 1;
1477 }
1478
1479 int main(int argc, char *argv[]) {
1480
1481         _cleanup_close_ int lock = -1;
1482         Iterator iterator;
1483         int r, k;
1484         Item *i;
1485         char *n;
1486
1487         r = parse_argv(argc, argv);
1488         if (r <= 0)
1489                 goto finish;
1490
1491         log_set_target(LOG_TARGET_AUTO);
1492         log_parse_environment();
1493         log_open();
1494
1495         umask(0022);
1496
1497         r = label_init(NULL);
1498         if (r < 0) {
1499                 log_error("SELinux setup failed: %s", strerror(-r));
1500                 goto finish;
1501         }
1502
1503         if (optind < argc) {
1504                 int j;
1505
1506                 for (j = optind; j < argc; j++) {
1507                         k = read_config_file(argv[j], false);
1508                         if (k < 0 && r == 0)
1509                                 r = k;
1510                 }
1511         } else {
1512                 _cleanup_strv_free_ char **files = NULL;
1513                 char **f;
1514
1515                 r = conf_files_list_nulstr(&files, ".conf", arg_root, conf_file_dirs);
1516                 if (r < 0) {
1517                         log_error("Failed to enumerate sysusers.d files: %s", strerror(-r));
1518                         goto finish;
1519                 }
1520
1521                 STRV_FOREACH(f, files) {
1522                         k = read_config_file(*f, true);
1523                         if (k < 0 && r == 0)
1524                                 r = k;
1525                 }
1526         }
1527
1528         r = add_implicit();
1529         if (r < 0)
1530                 goto finish;
1531
1532         lock = take_password_lock(arg_root);
1533         if (lock < 0) {
1534                 log_error("Failed to take lock: %s", strerror(-lock));
1535                 goto finish;
1536         }
1537
1538         r = load_user_database();
1539         if (r < 0) {
1540                 log_error("Failed to load user database: %s", strerror(-r));
1541                 goto finish;
1542         }
1543
1544         r = load_group_database();
1545         if (r < 0) {
1546                 log_error("Failed to read group database: %s", strerror(-r));
1547                 goto finish;
1548         }
1549
1550         HASHMAP_FOREACH(i, groups, iterator)
1551                 process_item(i);
1552
1553         HASHMAP_FOREACH(i, users, iterator)
1554                 process_item(i);
1555
1556         r = write_files();
1557         if (r < 0)
1558                 log_error("Failed to write files: %s", strerror(-r));
1559
1560 finish:
1561         while ((i = hashmap_steal_first(groups)))
1562                 item_free(i);
1563
1564         while ((i = hashmap_steal_first(users)))
1565                 item_free(i);
1566
1567         while ((n = hashmap_first_key(members))) {
1568                 strv_free(hashmap_steal_first(members));
1569                 free(n);
1570         }
1571
1572         hashmap_free(groups);
1573         hashmap_free(users);
1574         hashmap_free(members);
1575         hashmap_free(todo_uids);
1576         hashmap_free(todo_gids);
1577
1578         free_database(database_user, database_uid);
1579         free_database(database_group, database_gid);
1580
1581         free(arg_root);
1582
1583         return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
1584 }