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