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