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