]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/sysusers/sysusers.c
sysusers: make ADD_GROUP always create a group
[thirdparty/systemd.git] / src / sysusers / sysusers.c
CommitLineData
53e1b683 1/* SPDX-License-Identifier: LGPL-2.1+ */
1b992147
LP
2/***
3 This file is part of systemd.
4
5 Copyright 2014 Lennart Poettering
6
7 systemd is free software; you can redistribute it and/or modify it
8 under the terms of the GNU Lesser General Public License as published by
9 the Free Software Foundation; either version 2.1 of the License, or
10 (at your option) any later version.
11
12 systemd is distributed in the hope that it will be useful, but
13 WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 Lesser General Public License for more details.
16
17 You should have received a copy of the GNU Lesser General Public License
18 along with systemd; If not, see <http://www.gnu.org/licenses/>.
19***/
20
3f6fd1ba 21#include <getopt.h>
1b992147 22#include <grp.h>
9ab315cc 23#include <gshadow.h>
3f6fd1ba
LP
24#include <pwd.h>
25#include <shadow.h>
932ad62b 26#include <utmp.h>
1b992147 27
b5efdb8a 28#include "alloc-util.h"
1b992147
LP
29#include "conf-files.h"
30#include "copy.h"
a0f29c76
LP
31#include "def.h"
32#include "fd-util.h"
1dd98a71 33#include "fs-util.h"
f7f628b5 34#include "fileio-label.h"
f97b34a6 35#include "format-util.h"
3f6fd1ba
LP
36#include "hashmap.h"
37#include "path-util.h"
38#include "selinux-util.h"
07630cea 39#include "smack-util.h"
3f6fd1ba 40#include "specifier.h"
07630cea 41#include "string-util.h"
3f6fd1ba
LP
42#include "strv.h"
43#include "uid-range.h"
a0f29c76 44#include "user-util.h"
3f6fd1ba
LP
45#include "utf8.h"
46#include "util.h"
1b992147
LP
47
48typedef enum ItemType {
49 ADD_USER = 'u',
50 ADD_GROUP = 'g',
a12b0cc3 51 ADD_MEMBER = 'm',
8530dc44 52 ADD_RANGE = 'r',
1b992147
LP
53} ItemType;
54typedef struct Item {
55 ItemType type;
56
57 char *name;
58 char *uid_path;
59 char *gid_path;
60 char *description;
7629889c 61 char *home;
1b992147
LP
62
63 gid_t gid;
64 uid_t uid;
65
66 bool gid_set:1;
4cb41413 67 bool gid_existing_ok:1;
1b992147
LP
68 bool uid_set:1;
69
c1b6b04f
KS
70 bool todo_user:1;
71 bool todo_group:1;
1b992147
LP
72} Item;
73
74static char *arg_root = NULL;
75
75eb6154 76static const char conf_file_dirs[] = CONF_PATHS_NULSTR("sysusers.d");
1b992147 77
5bc9c980
MV
78static OrderedHashmap *users = NULL, *groups = NULL;
79static OrderedHashmap *todo_uids = NULL, *todo_gids = NULL;
80static OrderedHashmap *members = NULL;
1b992147
LP
81
82static Hashmap *database_uid = NULL, *database_user = NULL;
83static Hashmap *database_gid = NULL, *database_group = NULL;
84
fed1e721 85static uid_t search_uid = UID_INVALID;
8530dc44
LP
86static UidRange *uid_range = NULL;
87static unsigned n_uid_range = 0;
1b992147 88
1b992147
LP
89static int load_user_database(void) {
90 _cleanup_fclose_ FILE *f = NULL;
91 const char *passwd_path;
92 struct passwd *pw;
93 int r;
94
1d13f648 95 passwd_path = prefix_roota(arg_root, "/etc/passwd");
1b992147
LP
96 f = fopen(passwd_path, "re");
97 if (!f)
98 return errno == ENOENT ? 0 : -errno;
99
d5099efc 100 r = hashmap_ensure_allocated(&database_user, &string_hash_ops);
1b992147
LP
101 if (r < 0)
102 return r;
103
d5099efc 104 r = hashmap_ensure_allocated(&database_uid, NULL);
1b992147
LP
105 if (r < 0)
106 return r;
107
108 errno = 0;
109 while ((pw = fgetpwent(f))) {
110 char *n;
111 int k, q;
112
113 n = strdup(pw->pw_name);
114 if (!n)
115 return -ENOMEM;
116
117 k = hashmap_put(database_user, n, UID_TO_PTR(pw->pw_uid));
118 if (k < 0 && k != -EEXIST) {
119 free(n);
120 return k;
121 }
122
123 q = hashmap_put(database_uid, UID_TO_PTR(pw->pw_uid), n);
124 if (q < 0 && q != -EEXIST) {
125 if (k < 0)
126 free(n);
127 return q;
128 }
129
130 if (q < 0 && k < 0)
131 free(n);
132
133 errno = 0;
134 }
135 if (!IN_SET(errno, 0, ENOENT))
136 return -errno;
137
138 return 0;
139}
140
141static int load_group_database(void) {
142 _cleanup_fclose_ FILE *f = NULL;
143 const char *group_path;
144 struct group *gr;
145 int r;
146
1d13f648 147 group_path = prefix_roota(arg_root, "/etc/group");
1b992147
LP
148 f = fopen(group_path, "re");
149 if (!f)
150 return errno == ENOENT ? 0 : -errno;
151
d5099efc 152 r = hashmap_ensure_allocated(&database_group, &string_hash_ops);
1b992147
LP
153 if (r < 0)
154 return r;
155
d5099efc 156 r = hashmap_ensure_allocated(&database_gid, NULL);
1b992147
LP
157 if (r < 0)
158 return r;
159
160 errno = 0;
161 while ((gr = fgetgrent(f))) {
162 char *n;
163 int k, q;
164
165 n = strdup(gr->gr_name);
166 if (!n)
167 return -ENOMEM;
168
169 k = hashmap_put(database_group, n, GID_TO_PTR(gr->gr_gid));
170 if (k < 0 && k != -EEXIST) {
171 free(n);
172 return k;
173 }
174
175 q = hashmap_put(database_gid, GID_TO_PTR(gr->gr_gid), n);
176 if (q < 0 && q != -EEXIST) {
177 if (k < 0)
178 free(n);
179 return q;
180 }
181
182 if (q < 0 && k < 0)
183 free(n);
184
185 errno = 0;
186 }
187 if (!IN_SET(errno, 0, ENOENT))
188 return -errno;
189
190 return 0;
191}
192
9f1c1940
ZJS
193static int make_backup(const char *target, const char *x) {
194 _cleanup_close_ int src = -1;
195 _cleanup_fclose_ FILE *dst = NULL;
0a12bb1e
EV
196 _cleanup_free_ char *temp = NULL;
197 char *backup;
1b992147
LP
198 struct timespec ts[2];
199 struct stat st;
200 int r;
201
202 src = open(x, O_RDONLY|O_CLOEXEC|O_NOCTTY);
203 if (src < 0) {
204 if (errno == ENOENT) /* No backup necessary... */
205 return 0;
206
207 return -errno;
208 }
209
210 if (fstat(src, &st) < 0)
211 return -errno;
212
9f1c1940
ZJS
213 r = fopen_temporary_label(target, x, &dst, &temp);
214 if (r < 0)
215 return r;
1b992147 216
1c876927 217 r = copy_bytes(src, fileno(dst), (uint64_t) -1, COPY_REFLINK);
1b992147
LP
218 if (r < 0)
219 goto fail;
220
9f1c1940
ZJS
221 /* Don't fail on chmod() or chown(). If it stays owned by us
222 * and/or unreadable by others, then it isn't too bad... */
223
63c372cb 224 backup = strjoina(x, "-");
9f1c1940 225
1b992147 226 /* Copy over the access mask */
9f1c1940 227 if (fchmod(fileno(dst), st.st_mode & 07777) < 0)
56f64d95 228 log_warning_errno(errno, "Failed to change mode on %s: %m", backup);
1b992147 229
9f1c1940 230 if (fchown(fileno(dst), st.st_uid, st.st_gid)< 0)
56f64d95 231 log_warning_errno(errno, "Failed to change ownership of %s: %m", backup);
1b992147
LP
232
233 ts[0] = st.st_atim;
234 ts[1] = st.st_mtim;
f06863bd 235 if (futimens(fileno(dst), ts) < 0)
56f64d95 236 log_warning_errno(errno, "Failed to fix access and modification time of %s: %m", backup);
1b992147 237
0675e94a
AJ
238 r = fflush_sync_and_check(dst);
239 if (r < 0)
240 goto fail;
241
242 if (rename(temp, backup) < 0) {
243 r = -errno;
1b992147 244 goto fail;
0675e94a 245 }
1b992147
LP
246
247 return 0;
248
249fail:
250 unlink(temp);
251 return r;
252}
253
a12b0cc3
LP
254static int putgrent_with_members(const struct group *gr, FILE *group) {
255 char **a;
256
257 assert(gr);
258 assert(group);
259
5bc9c980 260 a = ordered_hashmap_get(members, gr->gr_name);
a12b0cc3
LP
261 if (a) {
262 _cleanup_strv_free_ char **l = NULL;
263 bool added = false;
264 char **i;
265
266 l = strv_copy(gr->gr_mem);
267 if (!l)
268 return -ENOMEM;
269
270 STRV_FOREACH(i, a) {
271 if (strv_find(l, *i))
272 continue;
273
274 if (strv_extend(&l, *i) < 0)
275 return -ENOMEM;
276
277 added = true;
278 }
279
280 if (added) {
281 struct group t;
282
283 strv_uniq(l);
284 strv_sort(l);
285
286 t = *gr;
287 t.gr_mem = l;
288
289 errno = 0;
290 if (putgrent(&t, group) != 0)
f5e5c28f 291 return errno > 0 ? -errno : -EIO;
a12b0cc3
LP
292
293 return 1;
294 }
295 }
296
297 errno = 0;
298 if (putgrent(gr, group) != 0)
f5e5c28f 299 return errno > 0 ? -errno : -EIO;
a12b0cc3
LP
300
301 return 0;
302}
303
349cc4a5 304#if ENABLE_GSHADOW
9ab315cc
LP
305static int putsgent_with_members(const struct sgrp *sg, FILE *gshadow) {
306 char **a;
307
308 assert(sg);
309 assert(gshadow);
310
5bc9c980 311 a = ordered_hashmap_get(members, sg->sg_namp);
9ab315cc
LP
312 if (a) {
313 _cleanup_strv_free_ char **l = NULL;
314 bool added = false;
315 char **i;
316
317 l = strv_copy(sg->sg_mem);
318 if (!l)
319 return -ENOMEM;
320
321 STRV_FOREACH(i, a) {
322 if (strv_find(l, *i))
323 continue;
324
325 if (strv_extend(&l, *i) < 0)
326 return -ENOMEM;
327
328 added = true;
329 }
330
331 if (added) {
332 struct sgrp t;
333
334 strv_uniq(l);
335 strv_sort(l);
336
337 t = *sg;
338 t.sg_mem = l;
339
340 errno = 0;
341 if (putsgent(&t, gshadow) != 0)
f5e5c28f 342 return errno > 0 ? -errno : -EIO;
9ab315cc
LP
343
344 return 1;
345 }
346 }
347
348 errno = 0;
349 if (putsgent(sg, gshadow) != 0)
f5e5c28f 350 return errno > 0 ? -errno : -EIO;
9ab315cc
LP
351
352 return 0;
353}
b14e1b43 354#endif
9ab315cc 355
fff19499
LP
356static int sync_rights(FILE *from, FILE *to) {
357 struct stat st;
358
359 if (fstat(fileno(from), &st) < 0)
360 return -errno;
361
362 if (fchmod(fileno(to), st.st_mode & 07777) < 0)
363 return -errno;
364
365 if (fchown(fileno(to), st.st_uid, st.st_gid) < 0)
366 return -errno;
367
368 return 0;
369}
370
c02e7b1e
SW
371static int rename_and_apply_smack(const char *temp_path, const char *dest_path) {
372 int r = 0;
373 if (rename(temp_path, dest_path) < 0)
374 return -errno;
375
376#ifdef SMACK_RUN_LABEL
377 r = mac_smack_apply(dest_path, SMACK_ATTR_ACCESS, SMACK_FLOOR_LABEL);
378 if (r < 0)
379 return r;
380#endif
381 return r;
382}
383
b20b0b66
FB
384static int write_temporary_passwd(const char *passwd_path, FILE **tmpfile, char **tmpfile_path) {
385 _cleanup_fclose_ FILE *original = NULL, *passwd = NULL;
1dd98a71 386 _cleanup_(unlink_and_freep) char *passwd_tmp = NULL;
1b992147
LP
387 Iterator iterator;
388 Item *i;
389 int r;
390
5bc9c980 391 if (ordered_hashmap_size(todo_uids) == 0)
b20b0b66 392 return 0;
1b992147 393
b20b0b66
FB
394 r = fopen_temporary_label("/etc/passwd", passwd_path, &passwd, &passwd_tmp);
395 if (r < 0)
396 return r;
397
398 original = fopen(passwd_path, "re");
399 if (original) {
400 struct passwd *pw;
401
402 r = sync_rights(original, passwd);
1b992147 403 if (r < 0)
1dd98a71 404 return r;
1b992147 405
b20b0b66
FB
406 errno = 0;
407 while ((pw = fgetpwent(original))) {
1b992147 408
5bc9c980 409 i = ordered_hashmap_get(users, pw->pw_name);
b20b0b66
FB
410 if (i && i->todo_user) {
411 log_error("%s: User \"%s\" already exists.", passwd_path, pw->pw_name);
1dd98a71 412 return -EEXIST;
b20b0b66 413 }
e3c72c21 414
5bc9c980 415 if (ordered_hashmap_contains(todo_uids, UID_TO_PTR(pw->pw_uid))) {
b20b0b66 416 log_error("%s: Detected collision for UID " UID_FMT ".", passwd_path, pw->pw_uid);
1dd98a71 417 return -EEXIST;
1b992147 418 }
b20b0b66
FB
419
420 errno = 0;
1dd98a71
FB
421 if (putpwent(pw, passwd) < 0)
422 return errno ? -errno : -EIO;
1b992147 423
b20b0b66
FB
424 errno = 0;
425 }
1dd98a71
FB
426 if (!IN_SET(errno, 0, ENOENT))
427 return -errno;
428
429 } else {
430 if (errno != ENOENT)
431 return -errno;
432 if (fchmod(fileno(passwd), 0644) < 0)
433 return -errno;
b20b0b66
FB
434 }
435
5bc9c980 436 ORDERED_HASHMAP_FOREACH(i, todo_uids, iterator) {
b20b0b66
FB
437 struct passwd n = {
438 .pw_name = i->name,
439 .pw_uid = i->uid,
440 .pw_gid = i->gid,
441 .pw_gecos = i->description,
1b992147 442
b20b0b66
FB
443 /* "x" means the password is stored in the shadow file */
444 .pw_passwd = (char*) "x",
a12b0cc3 445
b20b0b66
FB
446 /* We default to the root directory as home */
447 .pw_dir = i->home ? i->home : (char*) "/",
1b992147 448
b20b0b66
FB
449 /* Initialize the shell to nologin, with one exception:
450 * for root we patch in something special */
451 .pw_shell = i->uid == 0 ? (char*) "/bin/sh" : (char*) "/sbin/nologin",
452 };
9ab315cc 453
b20b0b66 454 errno = 0;
1dd98a71
FB
455 if (putpwent(&n, passwd) != 0)
456 return errno ? -errno : -EIO;
b20b0b66
FB
457 }
458
459 r = fflush_and_check(passwd);
460 if (r < 0)
1dd98a71 461 return r;
b20b0b66
FB
462
463 *tmpfile = passwd;
464 *tmpfile_path = passwd_tmp;
465 passwd = NULL;
466 passwd_tmp = NULL;
467 return 0;
b20b0b66
FB
468}
469
470static int write_temporary_shadow(const char *shadow_path, FILE **tmpfile, char **tmpfile_path) {
471 _cleanup_fclose_ FILE *original = NULL, *shadow = NULL;
1dd98a71 472 _cleanup_(unlink_and_freep) char *shadow_tmp = NULL;
b20b0b66
FB
473 Iterator iterator;
474 long lstchg;
475 Item *i;
476 int r;
477
5bc9c980 478 if (ordered_hashmap_size(todo_uids) == 0)
b20b0b66
FB
479 return 0;
480
481 r = fopen_temporary_label("/etc/shadow", shadow_path, &shadow, &shadow_tmp);
482 if (r < 0)
483 return r;
9ab315cc 484
b20b0b66
FB
485 lstchg = (long) (now(CLOCK_REALTIME) / USEC_PER_DAY);
486
487 original = fopen(shadow_path, "re");
488 if (original) {
489 struct spwd *sp;
490
491 r = sync_rights(original, shadow);
9ab315cc 492 if (r < 0)
1dd98a71 493 return r;
9ab315cc 494
b20b0b66
FB
495 errno = 0;
496 while ((sp = fgetspent(original))) {
9ab315cc 497
5bc9c980 498 i = ordered_hashmap_get(users, sp->sp_namp);
b20b0b66
FB
499 if (i && i->todo_user) {
500 /* we will update the existing entry */
501 sp->sp_lstchg = lstchg;
e3c72c21 502
b20b0b66
FB
503 /* only the /etc/shadow stage is left, so we can
504 * safely remove the item from the todo set */
505 i->todo_user = false;
5bc9c980 506 ordered_hashmap_remove(todo_uids, UID_TO_PTR(i->uid));
9ab315cc 507 }
b20b0b66
FB
508
509 errno = 0;
1dd98a71
FB
510 if (putspent(sp, shadow) < 0)
511 return errno ? -errno : -EIO;
9ab315cc 512
b20b0b66
FB
513 errno = 0;
514 }
1dd98a71
FB
515 if (!IN_SET(errno, 0, ENOENT))
516 return -errno;
517
518 } else {
519 if (errno != ENOENT)
520 return -errno;
521 if (fchmod(fileno(shadow), 0000) < 0)
522 return -errno;
b20b0b66 523 }
9ab315cc 524
5bc9c980 525 ORDERED_HASHMAP_FOREACH(i, todo_uids, iterator) {
b20b0b66
FB
526 struct spwd n = {
527 .sp_namp = i->name,
528 .sp_pwdp = (char*) "!!",
529 .sp_lstchg = lstchg,
530 .sp_min = -1,
531 .sp_max = -1,
532 .sp_warn = -1,
533 .sp_inact = -1,
534 .sp_expire = -1,
535 .sp_flag = (unsigned long) -1, /* this appears to be what everybody does ... */
536 };
9ab315cc 537
b20b0b66 538 errno = 0;
1dd98a71
FB
539 if (putspent(&n, shadow) != 0)
540 return errno ? -errno : -EIO;
1b992147
LP
541 }
542
0675e94a 543 r = fflush_sync_and_check(shadow);
b20b0b66 544 if (r < 0)
1dd98a71 545 return r;
b20b0b66
FB
546
547 *tmpfile = shadow;
548 *tmpfile_path = shadow_tmp;
549 shadow = NULL;
550 shadow_tmp = NULL;
551 return 0;
b20b0b66 552}
1b992147 553
b20b0b66
FB
554static int write_temporary_group(const char *group_path, FILE **tmpfile, char **tmpfile_path) {
555 _cleanup_fclose_ FILE *original = NULL, *group = NULL;
1dd98a71 556 _cleanup_(unlink_and_freep) char *group_tmp = NULL;
b20b0b66
FB
557 bool group_changed = false;
558 Iterator iterator;
559 Item *i;
560 int r;
1b992147 561
5bc9c980 562 if (ordered_hashmap_size(todo_gids) == 0 && ordered_hashmap_size(members) == 0)
b20b0b66 563 return 0;
e3c72c21 564
b20b0b66
FB
565 r = fopen_temporary_label("/etc/group", group_path, &group, &group_tmp);
566 if (r < 0)
567 return r;
568
569 original = fopen(group_path, "re");
570 if (original) {
571 struct group *gr;
572
573 r = sync_rights(original, group);
574 if (r < 0)
1dd98a71 575 return r;
b20b0b66
FB
576
577 errno = 0;
578 while ((gr = fgetgrent(original))) {
579 /* Safety checks against name and GID collisions. Normally,
580 * this should be unnecessary, but given that we look at the
581 * entries anyway here, let's make an extra verification
582 * step that we don't generate duplicate entries. */
583
5bc9c980 584 i = ordered_hashmap_get(groups, gr->gr_name);
b20b0b66
FB
585 if (i && i->todo_group) {
586 log_error("%s: Group \"%s\" already exists.", group_path, gr->gr_name);
1dd98a71 587 return -EEXIST;
1b992147 588 }
b20b0b66 589
5bc9c980 590 if (ordered_hashmap_contains(todo_gids, GID_TO_PTR(gr->gr_gid))) {
b20b0b66 591 log_error("%s: Detected collision for GID " GID_FMT ".", group_path, gr->gr_gid);
1dd98a71 592 return -EEXIST;
1b992147
LP
593 }
594
b20b0b66
FB
595 r = putgrent_with_members(gr, group);
596 if (r < 0)
1dd98a71 597 return r;
b20b0b66
FB
598 if (r > 0)
599 group_changed = true;
600
601 errno = 0;
602 }
1dd98a71
FB
603 if (!IN_SET(errno, 0, ENOENT))
604 return -errno;
605
606 } else {
607 if (errno != ENOENT)
608 return -errno;
609 if (fchmod(fileno(group), 0644) < 0)
610 return -errno;
b20b0b66 611 }
7629889c 612
5bc9c980 613 ORDERED_HASHMAP_FOREACH(i, todo_gids, iterator) {
b20b0b66
FB
614 struct group n = {
615 .gr_name = i->name,
616 .gr_gid = i->gid,
617 .gr_passwd = (char*) "x",
618 };
1b992147 619
b20b0b66
FB
620 r = putgrent_with_members(&n, group);
621 if (r < 0)
1dd98a71 622 return r;
7629889c 623
b20b0b66
FB
624 group_changed = true;
625 }
1b992147 626
0675e94a 627 r = fflush_sync_and_check(group);
b20b0b66 628 if (r < 0)
1dd98a71 629 return r;
1b992147 630
b20b0b66
FB
631 if (group_changed) {
632 *tmpfile = group;
633 *tmpfile_path = group_tmp;
634 group = NULL;
635 group_tmp = NULL;
636 }
637 return 0;
b20b0b66
FB
638}
639
640static int write_temporary_gshadow(const char * gshadow_path, FILE **tmpfile, char **tmpfile_path) {
349cc4a5 641#if ENABLE_GSHADOW
b20b0b66 642 _cleanup_fclose_ FILE *original = NULL, *gshadow = NULL;
1dd98a71 643 _cleanup_(unlink_and_freep) char *gshadow_tmp = NULL;
b20b0b66
FB
644 bool group_changed = false;
645 Iterator iterator;
646 Item *i;
647 int r;
648
5bc9c980 649 if (ordered_hashmap_size(todo_gids) == 0 && ordered_hashmap_size(members) == 0)
b20b0b66
FB
650 return 0;
651
652 r = fopen_temporary_label("/etc/gshadow", gshadow_path, &gshadow, &gshadow_tmp);
653 if (r < 0)
654 return r;
9ab315cc 655
b20b0b66
FB
656 original = fopen(gshadow_path, "re");
657 if (original) {
658 struct sgrp *sg;
659
660 r = sync_rights(original, gshadow);
1b992147 661 if (r < 0)
1dd98a71 662 return r;
9ab315cc 663
b20b0b66
FB
664 errno = 0;
665 while ((sg = fgetsgent(original))) {
c5abf225 666
5bc9c980 667 i = ordered_hashmap_get(groups, sg->sg_namp);
b20b0b66
FB
668 if (i && i->todo_group) {
669 log_error("%s: Group \"%s\" already exists.", gshadow_path, sg->sg_namp);
1dd98a71 670 return -EEXIST;
b20b0b66 671 }
9ab315cc 672
b20b0b66 673 r = putsgent_with_members(sg, gshadow);
fff19499 674 if (r < 0)
1dd98a71 675 return r;
b20b0b66
FB
676 if (r > 0)
677 group_changed = true;
e3c72c21 678
9ab315cc 679 errno = 0;
b20b0b66 680 }
1dd98a71
FB
681 if (!IN_SET(errno, 0, ENOENT))
682 return -errno;
683
684 } else {
685 if (errno != ENOENT)
686 return -errno;
687 if (fchmod(fileno(gshadow), 0000) < 0)
688 return -errno;
b20b0b66 689 }
9ab315cc 690
5bc9c980 691 ORDERED_HASHMAP_FOREACH(i, todo_gids, iterator) {
b20b0b66
FB
692 struct sgrp n = {
693 .sg_namp = i->name,
694 .sg_passwd = (char*) "!!",
695 };
9ab315cc 696
b20b0b66 697 r = putsgent_with_members(&n, gshadow);
9ab315cc 698 if (r < 0)
1dd98a71 699 return r;
b20b0b66
FB
700
701 group_changed = true;
9ab315cc
LP
702 }
703
0675e94a 704 r = fflush_sync_and_check(gshadow);
b20b0b66 705 if (r < 0)
1dd98a71 706 return r;
b20b0b66 707
9ab315cc 708 if (group_changed) {
b20b0b66
FB
709 *tmpfile = gshadow;
710 *tmpfile_path = gshadow_tmp;
711 gshadow = NULL;
712 gshadow_tmp = NULL;
713 }
714 return 0;
b14e1b43
FB
715#else
716 return 0;
717#endif
b20b0b66
FB
718}
719
720static int write_files(void) {
b20b0b66 721 _cleanup_fclose_ FILE *passwd = NULL, *group = NULL, *shadow = NULL, *gshadow = NULL;
1dd98a71 722 _cleanup_(unlink_and_freep) char *passwd_tmp = NULL, *group_tmp = NULL, *shadow_tmp = NULL, *gshadow_tmp = NULL;
b20b0b66
FB
723 const char *passwd_path = NULL, *group_path = NULL, *shadow_path = NULL, *gshadow_path = NULL;
724 int r;
725
726 passwd_path = prefix_roota(arg_root, "/etc/passwd");
727 shadow_path = prefix_roota(arg_root, "/etc/shadow");
728 group_path = prefix_roota(arg_root, "/etc/group");
729 gshadow_path = prefix_roota(arg_root, "/etc/gshadow");
730
731 r = write_temporary_group(group_path, &group, &group_tmp);
732 if (r < 0)
1dd98a71 733 return r;
b20b0b66
FB
734
735 r = write_temporary_gshadow(gshadow_path, &gshadow, &gshadow_tmp);
736 if (r < 0)
1dd98a71 737 return r;
b20b0b66
FB
738
739 r = write_temporary_passwd(passwd_path, &passwd, &passwd_tmp);
740 if (r < 0)
1dd98a71 741 return r;
b20b0b66
FB
742
743 r = write_temporary_shadow(shadow_path, &shadow, &shadow_tmp);
744 if (r < 0)
1dd98a71 745 return r;
b20b0b66
FB
746
747 /* Make a backup of the old files */
748 if (group) {
749 r = make_backup("/etc/group", group_path);
750 if (r < 0)
1dd98a71 751 return r;
b20b0b66
FB
752 }
753 if (gshadow) {
754 r = make_backup("/etc/gshadow", gshadow_path);
755 if (r < 0)
1dd98a71 756 return r;
1b992147
LP
757 }
758
759 if (passwd) {
9f1c1940 760 r = make_backup("/etc/passwd", passwd_path);
1b992147 761 if (r < 0)
1dd98a71 762 return r;
1b992147 763 }
9ab315cc
LP
764 if (shadow) {
765 r = make_backup("/etc/shadow", shadow_path);
766 if (r < 0)
1dd98a71 767 return r;
9ab315cc 768 }
1b992147
LP
769
770 /* And make the new files count */
b20b0b66
FB
771 if (group) {
772 r = rename_and_apply_smack(group_tmp, group_path);
773 if (r < 0)
1dd98a71 774 return r;
9ab315cc 775
b20b0b66
FB
776 group_tmp = mfree(group_tmp);
777 }
778 if (gshadow) {
779 r = rename_and_apply_smack(gshadow_tmp, gshadow_path);
780 if (r < 0)
1dd98a71 781 return r;
1b992147 782
b20b0b66 783 gshadow_tmp = mfree(gshadow_tmp);
1b992147
LP
784 }
785
786 if (passwd) {
c02e7b1e
SW
787 r = rename_and_apply_smack(passwd_tmp, passwd_path);
788 if (r < 0)
1dd98a71 789 return r;
1b992147 790
97b11eed 791 passwd_tmp = mfree(passwd_tmp);
1b992147 792 }
9ab315cc 793 if (shadow) {
c02e7b1e
SW
794 r = rename_and_apply_smack(shadow_tmp, shadow_path);
795 if (r < 0)
1dd98a71 796 return r;
9ab315cc 797
97b11eed 798 shadow_tmp = mfree(shadow_tmp);
9ab315cc 799 }
1b992147 800
1dd98a71 801 return 0;
1b992147
LP
802}
803
804static int uid_is_ok(uid_t uid, const char *name) {
805 struct passwd *p;
806 struct group *g;
807 const char *n;
808 Item *i;
809
810 /* Let's see if we already have assigned the UID a second time */
5bc9c980 811 if (ordered_hashmap_get(todo_uids, UID_TO_PTR(uid)))
1b992147
LP
812 return 0;
813
814 /* Try to avoid using uids that are already used by a group
815 * that doesn't have the same name as our new user. */
5bc9c980 816 i = ordered_hashmap_get(todo_gids, GID_TO_PTR(uid));
1b992147
LP
817 if (i && !streq(i->name, name))
818 return 0;
819
820 /* Let's check the files directly */
821 if (hashmap_contains(database_uid, UID_TO_PTR(uid)))
822 return 0;
823
824 n = hashmap_get(database_gid, GID_TO_PTR(uid));
825 if (n && !streq(n, name))
826 return 0;
827
828 /* Let's also check via NSS, to avoid UID clashes over LDAP and such, just in case */
829 if (!arg_root) {
830 errno = 0;
831 p = getpwuid(uid);
832 if (p)
833 return 0;
b0284aba 834 if (!IN_SET(errno, 0, ENOENT))
1b992147
LP
835 return -errno;
836
837 errno = 0;
838 g = getgrgid((gid_t) uid);
839 if (g) {
840 if (!streq(g->gr_name, name))
841 return 0;
b0284aba 842 } else if (!IN_SET(errno, 0, ENOENT))
1b992147
LP
843 return -errno;
844 }
845
846 return 1;
847}
848
849static int root_stat(const char *p, struct stat *st) {
850 const char *fix;
851
1d13f648 852 fix = prefix_roota(arg_root, p);
1b992147
LP
853 if (stat(fix, st) < 0)
854 return -errno;
855
856 return 0;
857}
858
859static int read_id_from_file(Item *i, uid_t *_uid, gid_t *_gid) {
860 struct stat st;
861 bool found_uid = false, found_gid = false;
56d21cde
PDS
862 uid_t uid = 0;
863 gid_t gid = 0;
1b992147
LP
864
865 assert(i);
866
867 /* First, try to get the gid directly */
868 if (_gid && i->gid_path && root_stat(i->gid_path, &st) >= 0) {
869 gid = st.st_gid;
870 found_gid = true;
871 }
872
873 /* Then, try to get the uid directly */
874 if ((_uid || (_gid && !found_gid))
875 && i->uid_path
876 && root_stat(i->uid_path, &st) >= 0) {
877
878 uid = st.st_uid;
879 found_uid = true;
880
881 /* If we need the gid, but had no success yet, also derive it from the uid path */
882 if (_gid && !found_gid) {
883 gid = st.st_gid;
884 found_gid = true;
885 }
886 }
887
888 /* If that didn't work yet, then let's reuse the gid as uid */
889 if (_uid && !found_uid && i->gid_path) {
890
891 if (found_gid) {
892 uid = (uid_t) gid;
893 found_uid = true;
894 } else if (root_stat(i->gid_path, &st) >= 0) {
895 uid = (uid_t) st.st_gid;
896 found_uid = true;
897 }
898 }
899
900 if (_uid) {
901 if (!found_uid)
902 return 0;
903
904 *_uid = uid;
905 }
906
907 if (_gid) {
908 if (!found_gid)
909 return 0;
910
911 *_gid = gid;
912 }
913
914 return 1;
915}
916
917static int add_user(Item *i) {
918 void *z;
919 int r;
920
921 assert(i);
922
923 /* Check the database directly */
924 z = hashmap_get(database_user, i->name);
925 if (z) {
926 log_debug("User %s already exists.", i->name);
c1b6b04f 927 i->uid = PTR_TO_UID(z);
1b992147
LP
928 i->uid_set = true;
929 return 0;
930 }
931
932 if (!arg_root) {
933 struct passwd *p;
1b992147
LP
934
935 /* Also check NSS */
936 errno = 0;
937 p = getpwnam(i->name);
938 if (p) {
939 log_debug("User %s already exists.", i->name);
940 i->uid = p->pw_uid;
941 i->uid_set = true;
942
2fc09a9c
DM
943 r = free_and_strdup(&i->description, p->pw_gecos);
944 if (r < 0)
945 return log_oom();
946
1b992147
LP
947 return 0;
948 }
4a62c710
MS
949 if (!IN_SET(errno, 0, ENOENT))
950 return log_error_errno(errno, "Failed to check if user %s already exists: %m", i->name);
1b992147
LP
951 }
952
953 /* Try to use the suggested numeric uid */
954 if (i->uid_set) {
955 r = uid_is_ok(i->uid, i->name);
f647962d
MS
956 if (r < 0)
957 return log_error_errno(r, "Failed to verify uid " UID_FMT ": %m", i->uid);
1b992147
LP
958 if (r == 0) {
959 log_debug("Suggested user ID " UID_FMT " for %s already used.", i->uid, i->name);
960 i->uid_set = false;
961 }
962 }
963
964 /* If that didn't work, try to read it from the specified path */
965 if (!i->uid_set) {
966 uid_t c;
967
968 if (read_id_from_file(i, &c, NULL) > 0) {
969
8530dc44 970 if (c <= 0 || !uid_range_contains(uid_range, n_uid_range, c))
1b992147
LP
971 log_debug("User ID " UID_FMT " of file not suitable for %s.", c, i->name);
972 else {
973 r = uid_is_ok(c, i->name);
f647962d
MS
974 if (r < 0)
975 return log_error_errno(r, "Failed to verify uid " UID_FMT ": %m", i->uid);
976 else if (r > 0) {
1b992147
LP
977 i->uid = c;
978 i->uid_set = true;
979 } else
980 log_debug("User ID " UID_FMT " of file for %s is already used.", c, i->name);
981 }
982 }
983 }
984
b938cb90 985 /* Otherwise, try to reuse the group ID */
1b992147
LP
986 if (!i->uid_set && i->gid_set) {
987 r = uid_is_ok((uid_t) i->gid, i->name);
f647962d
MS
988 if (r < 0)
989 return log_error_errno(r, "Failed to verify uid " UID_FMT ": %m", i->uid);
1b992147
LP
990 if (r > 0) {
991 i->uid = (uid_t) i->gid;
992 i->uid_set = true;
993 }
994 }
995
996 /* And if that didn't work either, let's try to find a free one */
997 if (!i->uid_set) {
8530dc44
LP
998 for (;;) {
999 r = uid_range_next_lower(uid_range, n_uid_range, &search_uid);
1000 if (r < 0) {
1001 log_error("No free user ID available for %s.", i->name);
1002 return r;
1003 }
1b992147
LP
1004
1005 r = uid_is_ok(search_uid, i->name);
f647962d
MS
1006 if (r < 0)
1007 return log_error_errno(r, "Failed to verify uid " UID_FMT ": %m", i->uid);
1008 else if (r > 0)
1b992147
LP
1009 break;
1010 }
1011
1b992147
LP
1012 i->uid_set = true;
1013 i->uid = search_uid;
1b992147
LP
1014 }
1015
5bc9c980 1016 r = ordered_hashmap_ensure_allocated(&todo_uids, NULL);
1b992147
LP
1017 if (r < 0)
1018 return log_oom();
1019
5bc9c980 1020 r = ordered_hashmap_put(todo_uids, UID_TO_PTR(i->uid), i);
1b992147
LP
1021 if (r < 0)
1022 return log_oom();
1023
c1b6b04f 1024 i->todo_user = true;
1b992147
LP
1025 log_info("Creating user %s (%s) with uid " UID_FMT " and gid " GID_FMT ".", i->name, strna(i->description), i->uid, i->gid);
1026
1027 return 0;
1028}
1029
1030static int gid_is_ok(gid_t gid) {
1031 struct group *g;
1032 struct passwd *p;
1033
5bc9c980 1034 if (ordered_hashmap_get(todo_gids, GID_TO_PTR(gid)))
1b992147
LP
1035 return 0;
1036
1037 /* Avoid reusing gids that are already used by a different user */
5bc9c980 1038 if (ordered_hashmap_get(todo_uids, UID_TO_PTR(gid)))
1b992147
LP
1039 return 0;
1040
1041 if (hashmap_contains(database_gid, GID_TO_PTR(gid)))
1042 return 0;
1043
1044 if (hashmap_contains(database_uid, UID_TO_PTR(gid)))
1045 return 0;
1046
1047 if (!arg_root) {
1048 errno = 0;
1049 g = getgrgid(gid);
1050 if (g)
1051 return 0;
b0284aba 1052 if (!IN_SET(errno, 0, ENOENT))
1b992147
LP
1053 return -errno;
1054
1055 errno = 0;
1056 p = getpwuid((uid_t) gid);
1057 if (p)
1058 return 0;
b0284aba 1059 if (!IN_SET(errno, 0, ENOENT))
1b992147
LP
1060 return -errno;
1061 }
1062
1063 return 1;
1064}
1065
1066static int add_group(Item *i) {
1067 void *z;
1068 int r;
1069
1070 assert(i);
1071
1072 /* Check the database directly */
1073 z = hashmap_get(database_group, i->name);
1074 if (z) {
1075 log_debug("Group %s already exists.", i->name);
1076 i->gid = PTR_TO_GID(z);
1077 i->gid_set = true;
1078 return 0;
1079 }
1080
1081 /* Also check NSS */
1082 if (!arg_root) {
1083 struct group *g;
1084
1085 errno = 0;
1086 g = getgrnam(i->name);
1087 if (g) {
1088 log_debug("Group %s already exists.", i->name);
1089 i->gid = g->gr_gid;
1090 i->gid_set = true;
1091 return 0;
1092 }
4a62c710
MS
1093 if (!IN_SET(errno, 0, ENOENT))
1094 return log_error_errno(errno, "Failed to check if group %s already exists: %m", i->name);
1b992147
LP
1095 }
1096
1097 /* Try to use the suggested numeric gid */
1098 if (i->gid_set) {
1099 r = gid_is_ok(i->gid);
f647962d
MS
1100 if (r < 0)
1101 return log_error_errno(r, "Failed to verify gid " GID_FMT ": %m", i->gid);
1b992147
LP
1102 if (r == 0) {
1103 log_debug("Suggested group ID " GID_FMT " for %s already used.", i->gid, i->name);
4cb41413
MV
1104 if (i->gid_existing_ok)
1105 return 0;
1b992147
LP
1106 i->gid_set = false;
1107 }
1108 }
1109
1110 /* Try to reuse the numeric uid, if there's one */
1111 if (!i->gid_set && i->uid_set) {
1112 r = gid_is_ok((gid_t) i->uid);
f647962d
MS
1113 if (r < 0)
1114 return log_error_errno(r, "Failed to verify gid " GID_FMT ": %m", i->gid);
1b992147
LP
1115 if (r > 0) {
1116 i->gid = (gid_t) i->uid;
1117 i->gid_set = true;
1118 }
1119 }
1120
1121 /* If that didn't work, try to read it from the specified path */
1122 if (!i->gid_set) {
1123 gid_t c;
1124
1125 if (read_id_from_file(i, NULL, &c) > 0) {
1126
8530dc44 1127 if (c <= 0 || !uid_range_contains(uid_range, n_uid_range, c))
1b992147
LP
1128 log_debug("Group ID " GID_FMT " of file not suitable for %s.", c, i->name);
1129 else {
1130 r = gid_is_ok(c);
f647962d
MS
1131 if (r < 0)
1132 return log_error_errno(r, "Failed to verify gid " GID_FMT ": %m", i->gid);
1133 else if (r > 0) {
1b992147
LP
1134 i->gid = c;
1135 i->gid_set = true;
1136 } else
1137 log_debug("Group ID " GID_FMT " of file for %s already used.", c, i->name);
1138 }
1139 }
1140 }
1141
1142 /* And if that didn't work either, let's try to find a free one */
1143 if (!i->gid_set) {
8530dc44
LP
1144 for (;;) {
1145 /* We look for new GIDs in the UID pool! */
1146 r = uid_range_next_lower(uid_range, n_uid_range, &search_uid);
1147 if (r < 0) {
1148 log_error("No free group ID available for %s.", i->name);
1149 return r;
1150 }
1151
1152 r = gid_is_ok(search_uid);
f647962d
MS
1153 if (r < 0)
1154 return log_error_errno(r, "Failed to verify gid " GID_FMT ": %m", i->gid);
1155 else if (r > 0)
1b992147
LP
1156 break;
1157 }
1158
1b992147 1159 i->gid_set = true;
8530dc44 1160 i->gid = search_uid;
1b992147
LP
1161 }
1162
5bc9c980 1163 r = ordered_hashmap_ensure_allocated(&todo_gids, NULL);
1b992147
LP
1164 if (r < 0)
1165 return log_oom();
1166
5bc9c980 1167 r = ordered_hashmap_put(todo_gids, GID_TO_PTR(i->gid), i);
1b992147
LP
1168 if (r < 0)
1169 return log_oom();
1170
c1b6b04f 1171 i->todo_group = true;
1b992147
LP
1172 log_info("Creating group %s with gid " GID_FMT ".", i->name, i->gid);
1173
1174 return 0;
1175}
1176
1177static int process_item(Item *i) {
1178 int r;
1179
1180 assert(i);
1181
1182 switch (i->type) {
1183
1184 case ADD_USER:
1185 r = add_group(i);
1186 if (r < 0)
1187 return r;
1188
1189 return add_user(i);
1190
e2c2060f 1191 case ADD_GROUP:
1b992147 1192 return add_group(i);
1b992147 1193
a12b0cc3
LP
1194 default:
1195 assert_not_reached("Unknown item type");
1196 }
1b992147
LP
1197}
1198
1199static void item_free(Item *i) {
1200
1201 if (!i)
1202 return;
1203
1204 free(i->name);
1205 free(i->uid_path);
1206 free(i->gid_path);
1207 free(i->description);
d9b8ea54 1208 free(i->home);
1b992147
LP
1209 free(i);
1210}
1211
1212DEFINE_TRIVIAL_CLEANUP_FUNC(Item*, item_free);
1213
a12b0cc3
LP
1214static int add_implicit(void) {
1215 char *g, **l;
1216 Iterator iterator;
1217 int r;
1218
1219 /* Implicitly create additional users and groups, if they were listed in "m" lines */
1220
5bc9c980 1221 ORDERED_HASHMAP_FOREACH_KEY(l, g, members, iterator) {
a12b0cc3
LP
1222 Item *i;
1223 char **m;
1224
5bc9c980 1225 i = ordered_hashmap_get(groups, g);
a12b0cc3
LP
1226 if (!i) {
1227 _cleanup_(item_freep) Item *j = NULL;
1228
5bc9c980 1229 r = ordered_hashmap_ensure_allocated(&groups, &string_hash_ops);
a12b0cc3
LP
1230 if (r < 0)
1231 return log_oom();
1232
1233 j = new0(Item, 1);
1234 if (!j)
1235 return log_oom();
1236
1237 j->type = ADD_GROUP;
1238 j->name = strdup(g);
1239 if (!j->name)
1240 return log_oom();
1241
5bc9c980 1242 r = ordered_hashmap_put(groups, j->name, j);
a12b0cc3
LP
1243 if (r < 0)
1244 return log_oom();
1245
1246 log_debug("Adding implicit group '%s' due to m line", j->name);
1247 j = NULL;
1248 }
1249
1250 STRV_FOREACH(m, l) {
1251
5bc9c980 1252 i = ordered_hashmap_get(users, *m);
a12b0cc3
LP
1253 if (!i) {
1254 _cleanup_(item_freep) Item *j = NULL;
1255
5bc9c980 1256 r = ordered_hashmap_ensure_allocated(&users, &string_hash_ops);
a12b0cc3
LP
1257 if (r < 0)
1258 return log_oom();
1259
1260 j = new0(Item, 1);
1261 if (!j)
1262 return log_oom();
1263
1264 j->type = ADD_USER;
1265 j->name = strdup(*m);
1266 if (!j->name)
1267 return log_oom();
1268
5bc9c980 1269 r = ordered_hashmap_put(users, j->name, j);
a12b0cc3
LP
1270 if (r < 0)
1271 return log_oom();
1272
1273 log_debug("Adding implicit user '%s' due to m line", j->name);
1274 j = NULL;
1275 }
1276 }
1277 }
1278
1279 return 0;
1280}
1281
1b992147
LP
1282static bool item_equal(Item *a, Item *b) {
1283 assert(a);
1284 assert(b);
1285
1286 if (a->type != b->type)
1287 return false;
1288
1289 if (!streq_ptr(a->name, b->name))
1290 return false;
1291
1292 if (!streq_ptr(a->uid_path, b->uid_path))
1293 return false;
1294
1295 if (!streq_ptr(a->gid_path, b->gid_path))
1296 return false;
1297
1298 if (!streq_ptr(a->description, b->description))
1299 return false;
1300
1301 if (a->uid_set != b->uid_set)
1302 return false;
1303
1304 if (a->uid_set && a->uid != b->uid)
1305 return false;
1306
1307 if (a->gid_set != b->gid_set)
1308 return false;
1309
1310 if (a->gid_set && a->gid != b->gid)
1311 return false;
1312
7629889c
LP
1313 if (!streq_ptr(a->home, b->home))
1314 return false;
1315
1b992147
LP
1316 return true;
1317}
1318
1b992147
LP
1319static int parse_line(const char *fname, unsigned line, const char *buffer) {
1320
1321 static const Specifier specifier_table[] = {
1322 { 'm', specifier_machine_id, NULL },
1323 { 'b', specifier_boot_id, NULL },
1324 { 'H', specifier_host_name, NULL },
1325 { 'v', specifier_kernel_release, NULL },
1326 {}
1327 };
1328
8530dc44 1329 _cleanup_free_ char *action = NULL, *name = NULL, *id = NULL, *resolved_name = NULL, *resolved_id = NULL, *description = NULL, *home = NULL;
1b992147
LP
1330 _cleanup_(item_freep) Item *i = NULL;
1331 Item *existing;
5bc9c980 1332 OrderedHashmap *h;
7629889c
LP
1333 int r;
1334 const char *p;
1b992147
LP
1335
1336 assert(fname);
1337 assert(line >= 1);
1338 assert(buffer);
1339
7629889c
LP
1340 /* Parse columns */
1341 p = buffer;
12ba2c44 1342 r = extract_many_words(&p, NULL, EXTRACT_QUOTES, &action, &name, &id, &description, &home, NULL);
7629889c 1343 if (r < 0) {
1b992147 1344 log_error("[%s:%u] Syntax error.", fname, line);
7629889c
LP
1345 return r;
1346 }
1347 if (r < 2) {
1348 log_error("[%s:%u] Missing action and name columns.", fname, line);
1349 return -EINVAL;
1350 }
4b1c1753 1351 if (!isempty(p)) {
7629889c
LP
1352 log_error("[%s:%u] Trailing garbage.", fname, line);
1353 return -EINVAL;
1b992147
LP
1354 }
1355
7629889c 1356 /* Verify action */
1b992147
LP
1357 if (strlen(action) != 1) {
1358 log_error("[%s:%u] Unknown modifier '%s'", fname, line, action);
1359 return -EINVAL;
1360 }
1361
8530dc44 1362 if (!IN_SET(action[0], ADD_USER, ADD_GROUP, ADD_MEMBER, ADD_RANGE)) {
61233823 1363 log_error("[%s:%u] Unknown command type '%c'.", fname, line, action[0]);
1b992147
LP
1364 return -EBADMSG;
1365 }
1366
7629889c 1367 /* Verify name */
97b11eed
DH
1368 if (isempty(name) || streq(name, "-"))
1369 name = mfree(name);
1b992147 1370
8530dc44
LP
1371 if (name) {
1372 r = specifier_printf(name, specifier_table, NULL, &resolved_name);
1373 if (r < 0) {
1374 log_error("[%s:%u] Failed to replace specifiers: %s", fname, line, name);
1375 return r;
1376 }
1377
1378 if (!valid_user_group_name(resolved_name)) {
1379 log_error("[%s:%u] '%s' is not a valid user or group name.", fname, line, resolved_name);
1380 return -EINVAL;
1381 }
1b992147
LP
1382 }
1383
8530dc44 1384 /* Verify id */
97b11eed
DH
1385 if (isempty(id) || streq(id, "-"))
1386 id = mfree(id);
7629889c 1387
8530dc44
LP
1388 if (id) {
1389 r = specifier_printf(id, specifier_table, NULL, &resolved_id);
1390 if (r < 0) {
1391 log_error("[%s:%u] Failed to replace specifiers: %s", fname, line, name);
1392 return r;
1393 }
1394 }
1395
1396 /* Verify description */
97b11eed
DH
1397 if (isempty(description) || streq(description, "-"))
1398 description = mfree(description);
1b992147 1399
8530dc44
LP
1400 if (description) {
1401 if (!valid_gecos(description)) {
1402 log_error("[%s:%u] '%s' is not a valid GECOS field.", fname, line, description);
1403 return -EINVAL;
1404 }
1405 }
1406
1407 /* Verify home */
97b11eed
DH
1408 if (isempty(home) || streq(home, "-"))
1409 home = mfree(home);
1b992147 1410
8530dc44
LP
1411 if (home) {
1412 if (!valid_home(home)) {
1413 log_error("[%s:%u] '%s' is not a valid home directory field.", fname, line, home);
1414 return -EINVAL;
1415 }
1416 }
1417
a12b0cc3 1418 switch (action[0]) {
1b992147 1419
8530dc44
LP
1420 case ADD_RANGE:
1421 if (resolved_name) {
1422 log_error("[%s:%u] Lines of type 'r' don't take a name field.", fname, line);
1423 return -EINVAL;
1424 }
1b992147 1425
8530dc44
LP
1426 if (!resolved_id) {
1427 log_error("[%s:%u] Lines of type 'r' require a ID range in the third field.", fname, line);
1428 return -EINVAL;
1429 }
1b992147 1430
7629889c 1431 if (description) {
8530dc44 1432 log_error("[%s:%u] Lines of type 'r' don't take a GECOS field.", fname, line);
7629889c
LP
1433 return -EINVAL;
1434 }
1435
1436 if (home) {
8530dc44 1437 log_error("[%s:%u] Lines of type 'r' don't take a home directory field.", fname, line);
7629889c
LP
1438 return -EINVAL;
1439 }
1440
8530dc44
LP
1441 r = uid_range_add_str(&uid_range, &n_uid_range, resolved_id);
1442 if (r < 0) {
1443 log_error("[%s:%u] Invalid UID range %s.", fname, line, resolved_id);
a12b0cc3
LP
1444 return -EINVAL;
1445 }
1b992147 1446
8530dc44
LP
1447 return 0;
1448
1449 case ADD_MEMBER: {
1450 char **l;
1451
1452 /* Try to extend an existing member or group item */
1453 if (!name) {
1454 log_error("[%s:%u] Lines of type 'm' require a user name in the second field.", fname, line);
1455 return -EINVAL;
1456 }
1457
1458 if (!resolved_id) {
1459 log_error("[%s:%u] Lines of type 'm' require a group name in the third field.", fname, line);
1460 return -EINVAL;
a12b0cc3 1461 }
1b992147 1462
a12b0cc3
LP
1463 if (!valid_user_group_name(resolved_id)) {
1464 log_error("[%s:%u] '%s' is not a valid user or group name.", fname, line, resolved_id);
1465 return -EINVAL;
1466 }
1b992147 1467
8530dc44
LP
1468 if (description) {
1469 log_error("[%s:%u] Lines of type 'm' don't take a GECOS field.", fname, line);
1470 return -EINVAL;
1471 }
1472
1473 if (home) {
1474 log_error("[%s:%u] Lines of type 'm' don't take a home directory field.", fname, line);
1475 return -EINVAL;
1476 }
1477
5bc9c980 1478 r = ordered_hashmap_ensure_allocated(&members, &string_hash_ops);
7629889c
LP
1479 if (r < 0)
1480 return log_oom();
a12b0cc3 1481
5bc9c980 1482 l = ordered_hashmap_get(members, resolved_id);
a12b0cc3
LP
1483 if (l) {
1484 /* A list for this group name already exists, let's append to it */
1485 r = strv_push(&l, resolved_name);
1486 if (r < 0)
1487 return log_oom();
1488
1489 resolved_name = NULL;
1490
5bc9c980 1491 assert_se(ordered_hashmap_update(members, resolved_id, l) >= 0);
1b992147 1492 } else {
a12b0cc3
LP
1493 /* No list for this group name exists yet, create one */
1494
1495 l = new0(char *, 2);
1496 if (!l)
1497 return -ENOMEM;
1498
1499 l[0] = resolved_name;
1500 l[1] = NULL;
1b992147 1501
5bc9c980 1502 r = ordered_hashmap_put(members, resolved_id, l);
1b992147 1503 if (r < 0) {
a12b0cc3
LP
1504 free(l);
1505 return log_oom();
1b992147
LP
1506 }
1507
a12b0cc3 1508 resolved_id = resolved_name = NULL;
1b992147 1509 }
a12b0cc3
LP
1510
1511 return 0;
1b992147
LP
1512 }
1513
a12b0cc3 1514 case ADD_USER:
8530dc44
LP
1515 if (!name) {
1516 log_error("[%s:%u] Lines of type 'u' require a user name in the second field.", fname, line);
1517 return -EINVAL;
1518 }
1519
5bc9c980 1520 r = ordered_hashmap_ensure_allocated(&users, &string_hash_ops);
a12b0cc3
LP
1521 if (r < 0)
1522 return log_oom();
1523
1524 i = new0(Item, 1);
1525 if (!i)
1526 return log_oom();
1527
8530dc44
LP
1528 if (resolved_id) {
1529 if (path_is_absolute(resolved_id)) {
1530 i->uid_path = resolved_id;
1531 resolved_id = NULL;
a12b0cc3
LP
1532
1533 path_kill_slashes(i->uid_path);
a12b0cc3 1534 } else {
4cb41413
MV
1535 _cleanup_free_ char *uid = NULL, *gid = NULL;
1536 if (split_pair(resolved_id, ":", &uid, &gid) == 0) {
1537 r = parse_gid(gid, &i->gid);
1538 if (r < 0)
1539 return log_error_errno(r, "Failed to parse GID: '%s': %m", id);
1540 i->gid_set = true;
1541 i->gid_existing_ok = true;
1542 free_and_replace(resolved_id, uid);
a12b0cc3 1543 }
4cb41413
MV
1544 r = parse_uid(resolved_id, &i->uid);
1545 if (r < 0)
1546 return log_error_errno(r, "Failed to parse UID: '%s': %m", id);
a12b0cc3
LP
1547
1548 i->uid_set = true;
1549 }
1550 }
1551
8530dc44
LP
1552 i->description = description;
1553 description = NULL;
7629889c 1554
8530dc44
LP
1555 i->home = home;
1556 home = NULL;
a12b0cc3 1557
1b992147 1558 h = users;
a12b0cc3
LP
1559 break;
1560
1561 case ADD_GROUP:
8530dc44
LP
1562 if (!name) {
1563 log_error("[%s:%u] Lines of type 'g' require a user name in the second field.", fname, line);
1564 return -EINVAL;
1565 }
a12b0cc3 1566
7629889c 1567 if (description) {
a12b0cc3
LP
1568 log_error("[%s:%u] Lines of type 'g' don't take a GECOS field.", fname, line);
1569 return -EINVAL;
1570 }
1571
7629889c
LP
1572 if (home) {
1573 log_error("[%s:%u] Lines of type 'g' don't take a home directory field.", fname, line);
1574 return -EINVAL;
1575 }
1576
5bc9c980 1577 r = ordered_hashmap_ensure_allocated(&groups, &string_hash_ops);
7629889c
LP
1578 if (r < 0)
1579 return log_oom();
1580
a12b0cc3
LP
1581 i = new0(Item, 1);
1582 if (!i)
1583 return log_oom();
1584
8530dc44
LP
1585 if (resolved_id) {
1586 if (path_is_absolute(resolved_id)) {
1587 i->gid_path = resolved_id;
1588 resolved_id = NULL;
a12b0cc3
LP
1589
1590 path_kill_slashes(i->gid_path);
1591 } else {
8530dc44 1592 r = parse_gid(resolved_id, &i->gid);
4cb41413
MV
1593 if (r < 0)
1594 return log_error_errno(r, "Failed to parse GID: '%s': %m", id);
a12b0cc3
LP
1595
1596 i->gid_set = true;
1597 }
1598 }
1599
1b992147 1600 h = groups;
a12b0cc3 1601 break;
8530dc44 1602
bce415ed
RC
1603 default:
1604 return -EBADMSG;
1b992147 1605 }
a12b0cc3
LP
1606
1607 i->type = action[0];
1608 i->name = resolved_name;
1609 resolved_name = NULL;
1b992147 1610
5bc9c980 1611 existing = ordered_hashmap_get(h, i->name);
1b992147
LP
1612 if (existing) {
1613
1614 /* Two identical items are fine */
1615 if (!item_equal(existing, i))
1616 log_warning("Two or more conflicting lines for %s configured, ignoring.", i->name);
1617
1618 return 0;
1619 }
1620
5bc9c980 1621 r = ordered_hashmap_put(h, i->name, i);
a12b0cc3
LP
1622 if (r < 0)
1623 return log_oom();
1b992147
LP
1624
1625 i = NULL;
1626 return 0;
1627}
1628
1629static int read_config_file(const char *fn, bool ignore_enoent) {
dfc87cbf
LP
1630 _cleanup_fclose_ FILE *rf = NULL;
1631 FILE *f = NULL;
1b992147
LP
1632 char line[LINE_MAX];
1633 unsigned v = 0;
c4640902 1634 int r = 0;
1b992147
LP
1635
1636 assert(fn);
1637
dfc87cbf
LP
1638 if (streq(fn, "-"))
1639 f = stdin;
1640 else {
1641 r = search_and_fopen_nulstr(fn, "re", arg_root, conf_file_dirs, &rf);
1642 if (r < 0) {
1643 if (ignore_enoent && r == -ENOENT)
1644 return 0;
1b992147 1645
8d3d7072 1646 return log_error_errno(r, "Failed to open '%s', ignoring: %m", fn);
dfc87cbf
LP
1647 }
1648
1649 f = rf;
1b992147
LP
1650 }
1651
1652 FOREACH_LINE(line, f, break) {
1653 char *l;
1654 int k;
1655
1656 v++;
1657
1658 l = strstrip(line);
4c701096 1659 if (IN_SET(*l, 0, '#'))
1b992147
LP
1660 continue;
1661
1662 k = parse_line(fn, v, l);
1663 if (k < 0 && r == 0)
1664 r = k;
1665 }
1666
1667 if (ferror(f)) {
56f64d95 1668 log_error_errno(errno, "Failed to read from file %s: %m", fn);
1b992147
LP
1669 if (r == 0)
1670 r = -EIO;
1671 }
1672
1673 return r;
1674}
1675
1b992147
LP
1676static void free_database(Hashmap *by_name, Hashmap *by_id) {
1677 char *name;
1678
1679 for (;;) {
1680 name = hashmap_first(by_id);
1681 if (!name)
1682 break;
1683
1684 hashmap_remove(by_name, name);
1685
1686 hashmap_steal_first_key(by_id);
1687 free(name);
1688 }
1689
1690 while ((name = hashmap_steal_first_key(by_name)))
1691 free(name);
1692
1693 hashmap_free(by_name);
1694 hashmap_free(by_id);
1695}
1696
601185b4 1697static void help(void) {
1b992147
LP
1698 printf("%s [OPTIONS...] [CONFIGURATION FILE...]\n\n"
1699 "Creates system user accounts.\n\n"
1700 " -h --help Show this help\n"
1701 " --version Show package version\n"
601185b4
ZJS
1702 " --root=PATH Operate on an alternate filesystem root\n"
1703 , program_invocation_short_name);
1b992147
LP
1704}
1705
1706static int parse_argv(int argc, char *argv[]) {
1707
1708 enum {
1709 ARG_VERSION = 0x100,
1710 ARG_ROOT,
1711 };
1712
1713 static const struct option options[] = {
1714 { "help", no_argument, NULL, 'h' },
1715 { "version", no_argument, NULL, ARG_VERSION },
1716 { "root", required_argument, NULL, ARG_ROOT },
1717 {}
1718 };
1719
0f474365 1720 int c, r;
1b992147
LP
1721
1722 assert(argc >= 0);
1723 assert(argv);
1724
601185b4 1725 while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0)
1b992147
LP
1726
1727 switch (c) {
1728
1729 case 'h':
601185b4
ZJS
1730 help();
1731 return 0;
1b992147
LP
1732
1733 case ARG_VERSION:
3f6fd1ba 1734 return version();
1b992147
LP
1735
1736 case ARG_ROOT:
0f03c2a4 1737 r = parse_path_argument_and_warn(optarg, true, &arg_root);
0f474365 1738 if (r < 0)
0f03c2a4 1739 return r;
1b992147
LP
1740 break;
1741
1742 case '?':
1743 return -EINVAL;
1744
1745 default:
1746 assert_not_reached("Unhandled option");
1747 }
1b992147
LP
1748
1749 return 1;
1750}
1751
1752int main(int argc, char *argv[]) {
1753
1754 _cleanup_close_ int lock = -1;
1755 Iterator iterator;
1756 int r, k;
1757 Item *i;
a12b0cc3 1758 char *n;
1b992147
LP
1759
1760 r = parse_argv(argc, argv);
1761 if (r <= 0)
1762 goto finish;
1763
1764 log_set_target(LOG_TARGET_AUTO);
1765 log_parse_environment();
1766 log_open();
1767
1768 umask(0022);
1769
c3dacc8b 1770 r = mac_selinux_init();
9f1c1940 1771 if (r < 0) {
da927ba9 1772 log_error_errno(r, "SELinux setup failed: %m");
9f1c1940
ZJS
1773 goto finish;
1774 }
1b992147
LP
1775
1776 if (optind < argc) {
1777 int j;
1778
1779 for (j = optind; j < argc; j++) {
1780 k = read_config_file(argv[j], false);
1781 if (k < 0 && r == 0)
1782 r = k;
1783 }
1784 } else {
1785 _cleanup_strv_free_ char **files = NULL;
1786 char **f;
1787
b5084605 1788 r = conf_files_list_nulstr(&files, ".conf", arg_root, 0, conf_file_dirs);
1b992147 1789 if (r < 0) {
da927ba9 1790 log_error_errno(r, "Failed to enumerate sysusers.d files: %m");
1b992147
LP
1791 goto finish;
1792 }
1793
1794 STRV_FOREACH(f, files) {
1795 k = read_config_file(*f, true);
1796 if (k < 0 && r == 0)
1797 r = k;
1798 }
1799 }
1800
fe102d6a
LP
1801 /* Let's tell nss-systemd not to synthesize the "root" and "nobody" entries for it, so that our detection
1802 * whether the names or UID/GID area already used otherwise doesn't get confused. After all, even though
1803 * nss-systemd synthesizes these users/groups, they should still appear in /etc/passwd and /etc/group, as the
1804 * synthesizing logic is merely supposed to be fallback for cases where we run with a completely unpopulated
1805 * /etc. */
1806 if (setenv("SYSTEMD_NSS_BYPASS_SYNTHETIC", "1", 1) < 0) {
1807 r = log_error_errno(errno, "Failed to set SYSTEMD_NSS_BYPASS_SYNTHETIC environment variable: %m");
1808 goto finish;
1809 }
1810
8530dc44
LP
1811 if (!uid_range) {
1812 /* Default to default range of 1..SYSTEMD_UID_MAX */
1813 r = uid_range_add(&uid_range, &n_uid_range, 1, SYSTEM_UID_MAX);
1814 if (r < 0) {
1815 log_oom();
1816 goto finish;
1817 }
1818 }
1819
a12b0cc3
LP
1820 r = add_implicit();
1821 if (r < 0)
1822 goto finish;
1823
e929bee0 1824 lock = take_etc_passwd_lock(arg_root);
1b992147 1825 if (lock < 0) {
da927ba9 1826 log_error_errno(lock, "Failed to take lock: %m");
1b992147
LP
1827 goto finish;
1828 }
1829
1830 r = load_user_database();
1831 if (r < 0) {
da927ba9 1832 log_error_errno(r, "Failed to load user database: %m");
1b992147
LP
1833 goto finish;
1834 }
1835
1836 r = load_group_database();
1837 if (r < 0) {
da927ba9 1838 log_error_errno(r, "Failed to read group database: %m");
1b992147
LP
1839 goto finish;
1840 }
1841
5bc9c980 1842 ORDERED_HASHMAP_FOREACH(i, groups, iterator)
1b992147
LP
1843 process_item(i);
1844
5bc9c980 1845 ORDERED_HASHMAP_FOREACH(i, users, iterator)
1b992147
LP
1846 process_item(i);
1847
1848 r = write_files();
1849 if (r < 0)
da927ba9 1850 log_error_errno(r, "Failed to write files: %m");
1b992147
LP
1851
1852finish:
5bc9c980
MV
1853 ordered_hashmap_free_with_destructor(groups, item_free);
1854 ordered_hashmap_free_with_destructor(users, item_free);
1b992147 1855
5bc9c980
MV
1856 while ((n = ordered_hashmap_first_key(members))) {
1857 strv_free(ordered_hashmap_steal_first(members));
a12b0cc3
LP
1858 free(n);
1859 }
5bc9c980 1860 ordered_hashmap_free(members);
224b0e7a 1861
5bc9c980
MV
1862 ordered_hashmap_free(todo_uids);
1863 ordered_hashmap_free(todo_gids);
1b992147
LP
1864
1865 free_database(database_user, database_uid);
1866 free_database(database_group, database_gid);
1867
1868 free(arg_root);
1869
1870 return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
1871}