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