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