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