• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

systemd / systemd / 28630269468

02 Jul 2026 10:23PM UTC coverage: 72.618% (-0.06%) from 72.68%
28630269468

push

github

yuwata
test: ignore fails when the formatted timezone differs from the current one

When formatting a timestamp the C API takes into account historical data
from tzdata, so it returns a date strings with a historically-correct
timezone abbreviation. However, tzname[] doesn't do this and it returns
the most recent abbreviation for the given zone.

For example, according to tzdata America/Cancun switched from EST/EDT to
CST/CDT on 1998-08-02:

Zone America/Cancun     -5:47:04 -      LMT     1922 Jan  1  6:00u
                        -6:00   -       CST     1981 Dec 26  2:00
                        -5:00   -       EST     1983 Jan  4  0:00
                        -6:00   Mexico  C%sT    1997 Oct 26  2:00
                        -5:00   Mexico  E%sT    1998 Aug  2  2:00
                        -6:00   Mexico  C%sT    2015 Feb  1  2:00
                        -5:00   -       EST

So, formatting a timestamp from this time will yield a string with the
EDT timezone:

$ TZ=America/Cancun date -d "@902035565"
Sun Aug  2 01:26:05 EDT 1998

But using tzname[] (or strptime %z) shows the most recent data, where
America/Cancun uses EST (and doesn't use DST anymore, hence
tzname[1]=CDT that glibc remembers from the previous zone epoch):

$ TZ=America/Cancun ./tz
{EST, CDT}

This means that when we parse the formatted timestamp back we don't use
the historical timezone data, so we might end up with a different
offset:

TZ=America/Cancun, tzname[0]=EST, tzname[1]=CDT
@902035565603993 → Sun 1998-08-02 01:26:05 EDT → @902039165000000 → Sun 1998-08-02 01:26:05 CDT
src/test/test-time-util.c:452: Assertion failed: Expected "ignore" to be true
Aborted                    (core dumped) build-local/test-time-util

Instead of adding exceptions for every single timezone that switched
between different offsets in the past, let's address this a bit more
generally and skip the check if the parsed timezone doesn't match any of
the current timezones - this still keeps the check that the time... (continued)

1 of 5 new or added lines in 1 file covered. (20.0%)

9391 existing lines in 86 files now uncovered.

341680 of 470518 relevant lines covered (72.62%)

1343034.26 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

88.06
/src/basic/user-util.c
1
/* SPDX-License-Identifier: LGPL-2.1-or-later */
2

3
#include <fcntl.h>
4
#include <stdio.h>
5
#include <stdlib.h>
6
#include <sys/file.h>
7
#include <sys/stat.h>
8
#include <unistd.h>
9
#include <utmpx.h>
10

11
#include "sd-messages.h"
12

13
#include "alloc-util.h"
14
#include "chase.h"
15
#include "errno-util.h"
16
#include "extract-word.h"
17
#include "fd-util.h"
18
#include "fileio.h"
19
#include "format-util.h"
20
#include "lock-util.h"
21
#include "log.h"
22
#include "mkdir.h"
23
#include "parse-util.h"
24
#include "path-util.h"
25
#include "string-util.h"
26
#include "strv.h"
27
#include "terminal-util.h"
28
#include "user-util.h"
29
#include "utf8.h"
30

31
#define DEFINE_STRERROR_ACCOUNT(type)                                   \
32
        const char* strerror_##type(                                    \
33
                        int errnum,                                     \
34
                        char *buf,                                      \
35
                        size_t buflen) {                                \
36
                                                                        \
37
                errnum = ABS(errnum);                                   \
38
                switch (errnum) {                                       \
39
                case ESRCH:                                             \
40
                        return "Unknown " STRINGIFY(type);              \
41
                case ENOEXEC:                                           \
42
                        return "Not a system " STRINGIFY(type);         \
43
                default:                                                \
44
                        return strerror_r(errnum, buf, buflen);         \
45
                }                                                       \
46
        }
47

48
DEFINE_STRERROR_ACCOUNT(user);
11✔
49
DEFINE_STRERROR_ACCOUNT(group);
9✔
50

51
bool uid_is_valid(uid_t uid) {
14,590,138✔
52

53
        /* Also see POSIX IEEE Std 1003.1-2008, 2016 Edition, 3.436. */
54

55
        /* Some libc APIs use UID_INVALID as special placeholder */
56
        if (uid == (uid_t) UINT32_C(0xFFFFFFFF))
14,590,138✔
57
                return false;
58

59
        /* A long time ago UIDs where 16 bit, hence explicitly avoid the 16-bit -1 too */
60
        if (uid == (uid_t) UINT32_C(0xFFFF))
8,075,407✔
61
                return false;
19✔
62

63
        return true;
64
}
65

66
int parse_uid(const char *s, uid_t *ret) {
263,436✔
67
        uint32_t uid = 0;
263,436✔
68
        int r;
263,436✔
69

70
        assert(s);
263,436✔
71

72
        assert_cc(sizeof(uid_t) == sizeof(uint32_t));
263,436✔
73

74
        /* We are very strict when parsing UIDs, and prohibit +/- as prefix, leading zero as prefix, and
75
         * whitespace. We do this, since this call is often used in a context where we parse things as UID
76
         * first, and if that doesn't work we fall back to NSS. Thus we really want to make sure that UIDs
77
         * are parsed as UIDs only if they really really look like UIDs. */
78
        r = safe_atou32_full(s, 10
263,436✔
79
                             | SAFE_ATO_REFUSE_PLUS_MINUS
80
                             | SAFE_ATO_REFUSE_LEADING_ZERO
81
                             | SAFE_ATO_REFUSE_LEADING_WHITESPACE, &uid);
82
        if (r < 0)
263,436✔
83
                return r;
263,436✔
84

85
        if (!uid_is_valid(uid))
13,660✔
86
                return -ENXIO; /* we return ENXIO instead of EINVAL
87
                                * here, to make it easy to distinguish
88
                                * invalid numeric uids from invalid
89
                                * strings. */
90

91
        if (ret)
13,642✔
92
                *ret = uid;
12,782✔
93

94
        return 0;
95
}
96

97
int parse_uid_range(const char *s, uid_t *ret_lower, uid_t *ret_upper) {
75✔
98
        _cleanup_free_ char *word = NULL;
75✔
99
        uid_t l, u;
75✔
100
        int r;
75✔
101

102
        assert(s);
75✔
103
        assert(ret_lower);
75✔
104
        assert(ret_upper);
75✔
105

106
        r = extract_first_word(&s, &word, "-", EXTRACT_DONT_COALESCE_SEPARATORS);
75✔
107
        if (r < 0)
75✔
108
                return r;
109
        if (r == 0)
75✔
110
                return -EINVAL;
111

112
        r = parse_uid(word, &l);
75✔
113
        if (r < 0)
75✔
114
                return r;
115

116
        /* Check for the upper bound and extract it if needed */
117
        if (!s)
62✔
118
                /* Single number with no dash. */
119
                u = l;
9✔
120
        else if (!*s)
53✔
121
                /* Trailing dash is an error. */
122
                return -EINVAL;
123
        else {
124
                r = parse_uid(s, &u);
53✔
125
                if (r < 0)
53✔
126
                        return r;
127

128
                if (l > u)
48✔
129
                        return -EINVAL;
130
        }
131

132
        *ret_lower = l;
56✔
133
        *ret_upper = u;
56✔
134
        return 0;
56✔
135
}
136

137
char* getlogname_malloc(void) {
×
138
        uid_t uid;
×
139
        struct stat st;
×
140

141
        if (isatty_safe(STDIN_FILENO) && fstat(STDIN_FILENO, &st) >= 0)
×
142
                uid = st.st_uid;
×
143
        else
144
                uid = getuid();
×
145

146
        return uid_to_name(uid);
×
147
}
148

149
char* getusername_malloc(void) {
725✔
150
        const char *e;
725✔
151

152
        e = secure_getenv("USER");
725✔
153
        if (e)
725✔
154
                return strdup(e);
599✔
155

156
        return uid_to_name(getuid());
126✔
157
}
158

159
bool is_nologin_shell(const char *shell) {
2,843✔
160
        return PATH_IN_SET(shell,
2,843✔
161
                           /* 'nologin' is the friendliest way to disable logins for a user account. It prints a nice
162
                            * message and exits. Different distributions place the binary at different places though,
163
                            * hence let's list them all. */
164
                           "/bin/nologin",
165
                           "/sbin/nologin",
166
                           "/usr/bin/nologin",
167
                           "/usr/sbin/nologin",
168
                           /* 'true' and 'false' work too for the same purpose, but are less friendly as they don't do
169
                            * any message printing. Different distributions place the binary at various places but at
170
                            * least not in the 'sbin' directory. */
171
                           "/bin/false",
172
                           "/usr/bin/false",
173
                           "/bin/true",
174
                           "/usr/bin/true");
175
}
176

177
bool shell_is_placeholder(const char *shell) {
2,278✔
178
        return isempty(shell) || is_nologin_shell(shell);
4,556✔
179
}
180

181
const char* default_root_shell_at(int rfd) {
9,717✔
182
        /* We want to use the preferred shell, i.e. DEFAULT_USER_SHELL, which usually
183
         * will be /bin/bash. Fall back to /bin/sh if DEFAULT_USER_SHELL is not found,
184
         * or any access errors. */
185

186
        assert(rfd >= 0 || rfd == AT_FDCWD);
9,717✔
187

188
        int r = chaseat(rfd, rfd, DEFAULT_USER_SHELL, /* flags= */ 0, NULL, NULL);
9,717✔
189
        if (r < 0 && r != -ENOENT)
9,717✔
190
                log_debug_errno(r, "Failed to look up shell '%s': %m", DEFAULT_USER_SHELL);
×
191
        if (r > 0)
9,717✔
192
                return DEFAULT_USER_SHELL;
9,701✔
193

194
        return "/bin/sh";
195
}
196

197
const char* default_root_shell(const char *root) {
9,711✔
198
        _cleanup_close_ int rfd = -EBADF;
9,711✔
199

200
        rfd = open(empty_to_root(root), O_CLOEXEC | O_DIRECTORY | O_PATH);
9,711✔
201
        if (rfd < 0)
9,711✔
202
                return "/bin/sh";
203

204
        return default_root_shell_at(rfd);
9,711✔
205
}
206

207
static int return_user_creds(
22,318✔
208
                const char *username,
209
                uid_t uid, gid_t gid,
210
                const char *home,
211
                const char *shell,
212
                char **ret_username,
213
                uid_t *ret_uid, gid_t *ret_gid,
214
                char **ret_home,
215
                char **ret_shell) {
216
        /* Helper function to help with the strdups and atomic setting of return params. */
217

218
        _cleanup_free_ char *s1 = NULL, *s2 = NULL, *s3 = NULL;
22,318✔
219
        int r;
22,318✔
220

221
        if (ret_username) {
22,318✔
222
                r = strdup_to(&s1, username);
11,952✔
223
                if (r < 0)
11,952✔
224
                        return r;
225
        }
226

227
        if (ret_home) {
22,318✔
228
                r = strdup_to(&s2, home);
11,952✔
229
                if (r < 0)
11,952✔
230
                        return r;
231
        }
232

233
        if (ret_shell) {
22,318✔
234
                r = strdup_to(&s3, shell);
11,952✔
235
                if (r < 0)
11,952✔
236
                        return r;
237
        }
238

239
        if (ret_username)
22,318✔
240
                *ret_username = TAKE_PTR(s1);
11,952✔
241
        if (ret_uid)
22,318✔
242
                *ret_uid = uid;
12,632✔
243
        if (ret_gid)
22,318✔
244
                *ret_gid = gid;
2,284✔
245
        if (ret_home)
22,318✔
246
                *ret_home = TAKE_PTR(s2);
11,952✔
247
        if (ret_shell)
22,318✔
248
                *ret_shell = TAKE_PTR(s3);
11,952✔
249
        return 0;
250
}
251

252
static int synthesize_user_creds(
21,723✔
253
                const char *username,
254
                UserCredsFlags flags,
255
                char **ret_username,
256
                uid_t *ret_uid, gid_t *ret_gid,
257
                char **ret_home,
258
                char **ret_shell) {
259
        assert(username);
21,723✔
260

261
        /* We enforce some special rules for uid=0 and uid=65534: in order to avoid nss lookups for root we
262
         * hardcode their user record data. */
263
        if (STR_IN_SET(username, "root", "0"))
21,723✔
264
                return return_user_creds("root", 0, 0,
27,620✔
265
                                         "/root",
266
                                         ret_shell ? default_root_shell(NULL) : NULL,
9,696✔
267
                                         ret_username,
268
                                         ret_uid, ret_gid,
269
                                         ret_home,
270
                                         ret_shell);
271

272
        if (STR_IN_SET(username, NOBODY_USER_NAME, "65534") &&
3,811✔
273
            synthesize_nobody())
12✔
274
                return return_user_creds(NOBODY_USER_NAME, UID_NOBODY, GID_NOBODY,
12✔
275
                                         FLAGS_SET(flags, USER_CREDS_SUPPRESS_PLACEHOLDER) ? NULL : "/",
276
                                         FLAGS_SET(flags, USER_CREDS_SUPPRESS_PLACEHOLDER) ? NULL : NOLOGIN,
12✔
277
                                         ret_username,
278
                                         ret_uid, ret_gid,
279
                                         ret_home,
280
                                         ret_shell);
281

282
        return -ENOMEDIUM;
3,787✔
283
}
284

285
int get_user_creds(
22,327✔
286
                const char *username,
287
                UserCredsFlags flags,
288
                char **ret_username,
289
                uid_t *ret_uid, gid_t *ret_gid,
290
                char **ret_home,
291
                char **ret_shell) {
292

293
        uid_t u = UID_INVALID;
22,327✔
294
        _cleanup_free_ struct passwd *pw = NULL;
22,327✔
295
        int r;
22,327✔
296

297
        assert(username);
22,327✔
298
        assert((ret_home || ret_shell) || !(flags & (USER_CREDS_SUPPRESS_PLACEHOLDER|USER_CREDS_CLEAN)));
22,327✔
299

300
        if (!FLAGS_SET(flags, USER_CREDS_PREFER_NSS) ||
22,327✔
301
            (!ret_home && !ret_shell)) {
302

303
                /* So here's the deal: normally, we'll try to synthesize all records we can synthesize, and override
304
                 * the user database with that. However, if the user specifies USER_CREDS_PREFER_NSS then the
305
                 * user database will override the synthetic records instead — except if the user is only interested in
306
                 * the UID and/or GID (but not the home directory, or the shell), in which case we'll always override
307
                 * the user database (i.e. the USER_CREDS_PREFER_NSS flag has no effect in this case). Why?
308
                 * Simply because there are valid usecase where the user might change the home directory or the shell
309
                 * of the relevant users, but changing the UID/GID mappings for them is something we explicitly don't
310
                 * support. */
311

312
                r = synthesize_user_creds(username, flags, ret_username, ret_uid, ret_gid, ret_home, ret_shell);
21,723✔
313
                if (r >= 0)
21,723✔
314
                        return 0;
315
                if (r != -ENOMEDIUM) /* not a username we can synthesize */
3,787✔
316
                        return r;
317
        }
318

319
        if (parse_uid(username, &u) >= 0) {
4,391✔
320
                r = getpwuid_malloc(u, &pw);
220✔
321

322
                /* If there are multiple users with the same id, make sure to leave $USER to the configured value
323
                 * instead of the first occurrence in the database. However if the uid was configured by a numeric uid,
324
                 * then let's pick the real username from /etc/passwd. */
325
                if (r >= 0)
220✔
326
                        username = pw->pw_name;
217✔
327

328
                else if (FLAGS_SET(flags, USER_CREDS_ALLOW_MISSING) && !ret_gid && !ret_home && !ret_shell) {
3✔
329
                        /* If the specified user is a numeric UID and it isn't in the user database, and the caller
330
                         * passed USER_CREDS_ALLOW_MISSING and was only interested in the UID, then just return that
331
                         * and don't complain. */
332
                        if (ret_username)
×
333
                                *ret_username = NULL;
×
334
                        if (ret_uid)
×
335
                                *ret_uid = u;
×
336
                        return 0;
337
                }
338
        } else
339
                r = getpwnam_malloc(username, &pw);
4,171✔
340

341
        if (r < 0) {
4,391✔
342
                /* If the user requested that we only synthesize as fallback, do so now */
343
                if (FLAGS_SET(flags, USER_CREDS_PREFER_NSS) &&
9✔
344
                    synthesize_user_creds(username, flags, ret_username, ret_uid, ret_gid, ret_home, ret_shell) >= 0)
×
345
                        return 0;
346

347
                return r;
9✔
348
        }
349

350
        if (ret_uid && !uid_is_valid(pw->pw_uid))
4,382✔
351
                return -EBADMSG;
352

353
        if (ret_gid && !gid_is_valid(pw->pw_gid))
4,382✔
354
                return -EBADMSG;
355

356
        /* Note: we don't insist on normalized paths, since there are setups that have /./ in the path */
357
        const char *h =
8,764✔
358
                (FLAGS_SET(flags, USER_CREDS_SUPPRESS_PLACEHOLDER) && empty_or_root(pw->pw_dir)) ||
1✔
359
                (FLAGS_SET(flags, USER_CREDS_CLEAN) && (!path_is_valid(pw->pw_dir) || !path_is_absolute(pw->pw_dir)))
4,382✔
360
                ? NULL : pw->pw_dir;
8,764✔
361

362
        const char *s =
8,764✔
363
                (FLAGS_SET(flags, USER_CREDS_SUPPRESS_PLACEHOLDER) && shell_is_placeholder(pw->pw_shell)) ||
1✔
364
                (FLAGS_SET(flags, USER_CREDS_CLEAN) && (!path_is_valid(pw->pw_shell) || !path_is_absolute(pw->pw_shell)))
4,382✔
365
                ? NULL : pw->pw_shell;
8,764✔
366

367
        return return_user_creds(username, pw->pw_uid, pw->pw_gid, h, s,
4,382✔
368
                                 ret_username, ret_uid, ret_gid, ret_home, ret_shell);
369
}
370

371
static int synthesize_group_creds(const char *groupname, char **ret_name, gid_t *ret_gid) {
11,914✔
372
        assert(groupname);
11,914✔
373

374
        gid_t id;
11,914✔
375
        const char *n;
11,914✔
376
        int r;
11,914✔
377

378
        if (STR_IN_SET(groupname, "root", "0")) {
11,914✔
379
                id = 0;
380
                n = "root";
381
        } else if (STR_IN_SET(groupname, NOBODY_GROUP_NAME, "65534") &&
5,575✔
382
                   synthesize_nobody()) {
6✔
383
                id = GID_NOBODY;
6✔
384
                n = NOBODY_GROUP_NAME;
6✔
385
        } else
386
                return -ENOMEDIUM;
5,563✔
387

388
        r = strdup_to_full(ret_name, n);
6,351✔
389
        if (r < 0)
6,351✔
390
                return r;
391
        if (ret_gid)
6,351✔
392
                *ret_gid = id;
6,351✔
393
        return 0;
394
}
395

396
int get_group_creds(const char *groupname, UserCredsFlags flags, char **ret_name, gid_t *ret_gid) {
11,914✔
397
        _cleanup_free_ struct group *gr = NULL;
11,914✔
398
        gid_t id;
11,914✔
399
        int r;
11,914✔
400

401
        assert(groupname);
11,914✔
402

403
        if (!FLAGS_SET(flags, USER_CREDS_PREFER_NSS)) {
11,914✔
404
                r = synthesize_group_creds(groupname, ret_name, ret_gid);
11,914✔
405
                if (r >= 0)
11,914✔
406
                        return 0;
407
                if (r != -ENOMEDIUM) /* not a groupname we can synthesize */
5,563✔
408
                        return r;
409
        }
410

411
        if (parse_gid(groupname, &id) >= 0) {
5,563✔
412
                r = getgrgid_malloc(id, &gr);
×
413
                if (r >= 0)
×
414
                        groupname = gr->gr_name;
×
415
                else if (FLAGS_SET(flags, USER_CREDS_ALLOW_MISSING)) {
×
416
                        if (ret_gid)
×
417
                                *ret_gid = id;
×
418
                        if (ret_name)
×
419
                                *ret_name = NULL;
×
420
                        return 0;
421
                }
422
        } else
423
                r = getgrnam_malloc(groupname, &gr);
5,563✔
424

425
        if (r < 0) {
5,563✔
426
                if (FLAGS_SET(flags, USER_CREDS_PREFER_NSS) &&
1✔
427
                    synthesize_group_creds(groupname, ret_name, ret_gid) >= 0)
×
428
                        return 0;
429
                return r;
1✔
430
        }
431

432
        if (ret_gid && !gid_is_valid(gr->gr_gid))
5,562✔
433
                return -EBADMSG;
434

435
        r = strdup_to_full(ret_name, groupname);
5,562✔
436
        if (r < 0)
5,562✔
437
                return r;
438
        if (ret_gid)
5,562✔
439
                *ret_gid = gr->gr_gid;
5,562✔
440
        return 0;
441
}
442

443
char* uid_to_name(uid_t uid) {
553✔
444
        char *ret;
553✔
445
        int r;
553✔
446

447
        /* Shortcut things to avoid NSS lookups */
448
        if (uid == 0)
553✔
449
                return strdup("root");
553✔
450
        if (uid == UID_NOBODY && synthesize_nobody())
216✔
451
                return strdup(NOBODY_USER_NAME);
1✔
452

453
        if (uid_is_valid(uid)) {
215✔
454
                _cleanup_free_ struct passwd *pw = NULL;
213✔
455

456
                r = getpwuid_malloc(uid, &pw);
213✔
457
                if (r >= 0)
213✔
458
                        return strdup(pw->pw_name);
213✔
459
        }
460

461
        if (asprintf(&ret, UID_FMT, uid) < 0)
2✔
462
                return NULL;
463

464
        return ret;
2✔
465
}
466

467
char* gid_to_name(gid_t gid) {
114✔
468
        char *ret;
114✔
469
        int r;
114✔
470

471
        if (gid == 0)
114✔
472
                return strdup("root");
114✔
473
        if (gid == GID_NOBODY && synthesize_nobody())
51✔
474
                return strdup(NOBODY_GROUP_NAME);
1✔
475

476
        if (gid_is_valid(gid)) {
50✔
477
                _cleanup_free_ struct group *gr = NULL;
48✔
478

479
                r = getgrgid_malloc(gid, &gr);
48✔
480
                if (r >= 0)
48✔
481
                        return strdup(gr->gr_name);
48✔
482
        }
483

484
        if (asprintf(&ret, GID_FMT, gid) < 0)
2✔
485
                return NULL;
486

487
        return ret;
2✔
488
}
489

490
static bool gid_list_has(const gid_t *list, size_t size, gid_t val) {
563✔
491
        assert(list || size == 0);
563✔
492

493
        FOREACH_ARRAY(i, list, size)
750✔
494
                if (*i == val)
203✔
495
                        return true;
496

497
        return false;
498
}
499

500
int in_gid(gid_t gid) {
44✔
501
        _cleanup_free_ gid_t *gids = NULL;
44✔
502
        int ngroups;
44✔
503

504
        if (getgid() == gid)
44✔
505
                return 1;
506

507
        if (getegid() == gid)
41✔
508
                return 1;
509

510
        if (!gid_is_valid(gid))
41✔
511
                return -EINVAL;
512

513
        ngroups = getgroups_alloc(&gids);
40✔
514
        if (ngroups < 0)
40✔
515
                return ngroups;
516

517
        return gid_list_has(gids, ngroups, gid);
40✔
518
}
519

520
int in_group(const char *name) {
15✔
521
        int r;
15✔
522
        gid_t gid;
15✔
523

524
        r = get_group_creds(name, /* flags= */ 0, /* ret_name= */ NULL, &gid);
15✔
525
        if (r < 0)
15✔
526
                return r;
15✔
527

528
        return in_gid(gid);
14✔
529
}
530

531
int merge_gid_lists(const gid_t *list1, size_t size1, const gid_t *list2, size_t size2, gid_t **ret) {
12,758✔
532
        size_t nresult = 0;
12,758✔
533

534
        assert(size1 == 0 || list1);
12,758✔
535
        assert(size2 == 0 || list2);
12,758✔
536
        assert(ret);
12,758✔
537

538
        if (size2 > INT_MAX - size1)
12,758✔
539
                return -ENOBUFS;
540

541
        gid_t *buf = new(gid_t, size1 + size2);
12,758✔
542
        if (!buf)
12,758✔
543
                return -ENOMEM;
544

545
        /* Duplicates need to be skipped on merging, otherwise they'll be passed on and stored in the kernel. */
546
        for (size_t i = 0; i < size1; i++)
12,799✔
547
                if (!gid_list_has(buf, nresult, list1[i]))
41✔
548
                        buf[nresult++] = list1[i];
40✔
549
        for (size_t i = 0; i < size2; i++)
13,230✔
550
                if (!gid_list_has(buf, nresult, list2[i]))
472✔
551
                        buf[nresult++] = list2[i];
457✔
552
        *ret = buf;
12,758✔
553
        return (int)nresult;
12,758✔
554
}
555

556
int getgroups_alloc(gid_t **ret) {
645✔
557
        int ngroups = 8;
645✔
558

559
        assert(ret);
645✔
560

561
        for (unsigned attempt = 0;;) {
×
562
                _cleanup_free_ gid_t *p = NULL;
483✔
563

564
                p = new(gid_t, ngroups);
645✔
565
                if (!p)
645✔
566
                        return -ENOMEM;
567

568
                ngroups = getgroups(ngroups, p);
645✔
569
                if (ngroups > 0) {
645✔
570
                        *ret = TAKE_PTR(p);
483✔
571
                        return ngroups;
483✔
572
                }
573
                if (ngroups == 0)
162✔
574
                        break;
575
                if (errno != EINVAL)
×
576
                        return -errno;
×
577

578
                /* Give up eventually */
579
                if (attempt++ > 10)
×
580
                        return -EINVAL;
581

582
                /* Get actual size needed, and size the array explicitly. Note that this is potentially racy
583
                 * to use (in multi-threaded programs), hence let's call this in a loop. */
584
                ngroups = getgroups(0, NULL);
×
585
                if (ngroups < 0)
×
586
                        return -errno;
×
587
                if (ngroups == 0)
×
588
                        break;
589
        }
590

591
        *ret = NULL;
162✔
592
        return 0;
162✔
593
}
594

595
int get_home_dir(char **ret) {
17,634✔
596
        _cleanup_free_ struct passwd *p = NULL;
17,634✔
597
        const char *e;
17,634✔
598
        uid_t u;
17,634✔
599
        int r;
17,634✔
600

601
        assert(ret);
17,634✔
602

603
        /* Take the user specified one */
604
        e = secure_getenv("HOME");
17,634✔
605
        if (e && path_is_valid(e) && path_is_absolute(e))
34,574✔
606
                goto found;
16,938✔
607

608
        /* Hardcode home directory for root and nobody to avoid NSS */
609
        u = getuid();
696✔
610
        if (u == 0) {
696✔
611
                e = "/root";
696✔
612
                goto found;
696✔
613
        }
614
        if (u == UID_NOBODY && synthesize_nobody()) {
×
615
                e = "/";
×
616
                goto found;
×
617
        }
618

619
        /* Check the database... */
620
        r = getpwuid_malloc(u, &p);
×
621
        if (r < 0)
×
622
                return r;
623

624
        e = p->pw_dir;
×
625
        if (!path_is_valid(e) || !path_is_absolute(e))
17,634✔
626
                return -EINVAL;
627

628
 found:
×
629
        return path_simplify_alloc(e, ret);
17,634✔
630
}
631

632
int get_shell(char **ret) {
6✔
633
        _cleanup_free_ struct passwd *p = NULL;
6✔
634
        const char *e;
6✔
635
        uid_t u;
6✔
636
        int r;
6✔
637

638
        assert(ret);
6✔
639

640
        /* Take the user specified one */
641
        e = secure_getenv("SHELL");
6✔
642
        if (e && path_is_valid(e) && path_is_absolute(e))
9✔
643
                goto found;
3✔
644

645
        /* Hardcode shell for root and nobody to avoid NSS */
646
        u = getuid();
3✔
647
        if (u == 0) {
3✔
648
                e = default_root_shell(NULL);
3✔
649
                goto found;
3✔
650
        }
651
        if (u == UID_NOBODY && synthesize_nobody()) {
×
652
                e = NOLOGIN;
×
653
                goto found;
×
654
        }
655

656
        /* Check the database... */
657
        r = getpwuid_malloc(u, &p);
×
658
        if (r < 0)
×
659
                return r;
660

661
        e = p->pw_shell;
×
662
        if (!path_is_valid(e) || !path_is_absolute(e))
6✔
663
                return -EINVAL;
664

665
 found:
×
666
        return path_simplify_alloc(e, ret);
6✔
667
}
668

669
int fully_set_uid_gid(uid_t uid, gid_t gid, const gid_t supplementary_gids[], size_t n_supplementary_gids) {
684✔
670
        int r;
684✔
671

672
        assert(supplementary_gids || n_supplementary_gids == 0);
684✔
673

674
        /* Sets all UIDs and all GIDs to the specified ones. Drops all auxiliary GIDs */
675

676
        r = maybe_setgroups(n_supplementary_gids, supplementary_gids);
684✔
677
        if (r < 0)
684✔
678
                return r;
679

680
        if (gid_is_valid(gid))
684✔
681
                if (setresgid(gid, gid, gid) < 0)
684✔
682
                        return -errno;
2✔
683

684
        if (uid_is_valid(uid))
682✔
685
                if (setresuid(uid, uid, uid) < 0)
682✔
686
                        return -errno;
×
687

688
        return 0;
689
}
690

691
int take_etc_passwd_lock(const char *root) {
317✔
692
        int r;
317✔
693

694
        /* This is roughly the same as lckpwdf(), but not as awful. We don't want to use alarm() and signals,
695
         * hence we implement our own trivial version of this.
696
         *
697
         * Note that shadow-utils also takes per-database locks in addition to lckpwdf(). However, we don't,
698
         * given that they are redundant: they invoke lckpwdf() first and keep it during everything they do.
699
         * The per-database locks are awfully racy, and thus we just won't do them. */
700

701
        _cleanup_free_ char *path = path_join(root, ETC_PASSWD_LOCK_PATH);
634✔
702
        if (!path)
317✔
703
                return log_oom_debug();
×
704

705
        (void) mkdir_parents(path, 0755);
317✔
706

707
        _cleanup_close_ int fd = open(path, O_WRONLY|O_CREAT|O_CLOEXEC|O_NOCTTY|O_NOFOLLOW, 0600);
634✔
708
        if (fd < 0)
317✔
709
                return log_debug_errno(errno, "Cannot open %s: %m", path);
×
710

711
        r = unposix_lock(fd, LOCK_EX);
317✔
712
        if (r < 0)
317✔
713
                return log_debug_errno(r, "Locking %s failed: %m", path);
×
714

715
        return TAKE_FD(fd);
716
}
717

718
bool valid_user_group_name(const char *u, ValidUserFlags flags) {
216,317✔
719
        const char *i;
216,317✔
720

721
        /* Checks if the specified name is a valid user/group name. There are two flavours of this call:
722
         * strict mode is the default which is POSIX plus some extra rules; and relaxed mode where we accept
723
         * pretty much everything except the really worst offending names.
724
         *
725
         * Whenever we synthesize users ourselves we should use the strict mode. But when we process users
726
         * created by other stuff, let's be more liberal. */
727

728
        if (isempty(u)) /* An empty user name is never valid */
216,317✔
729
                return false;
730

731
        if (parse_uid(u, NULL) >= 0) /* Something that parses as numeric UID string is valid exactly when the
216,301✔
732
                                      * flag for it is set */
733
                return FLAGS_SET(flags, VALID_USER_ALLOW_NUMERIC);
404✔
734

735
        if (FLAGS_SET(flags, VALID_USER_RELAX)) {
215,897✔
736

737
                /* In relaxed mode we just check very superficially. Apparently SSSD and other stuff is
738
                 * extremely liberal (way too liberal if you ask me, even inserting "@" in user names, which
739
                 * is bound to cause problems for example when used with an MTA), hence only filter the most
740
                 * obvious cases, or where things would result in an invalid entry if such a user name would
741
                 * show up in /etc/passwd (or equivalent getent output).
742
                 *
743
                 * Note that we stepped far out of POSIX territory here. It's not our fault though, but
744
                 * SSSD's, Samba's and everybody else who ignored POSIX on this. (I mean, I am happy to step
745
                 * outside of POSIX' bounds any day, but I must say in this case I probably wouldn't
746
                 * have...) */
747

748
                if (startswith(u, " ") || endswith(u, " ")) /* At least expect whitespace padding is removed
209,174✔
749
                                                             * at front and back (accept in the middle, since
750
                                                             * that's apparently a thing on Windows). Note
751
                                                             * that this also blocks usernames consisting of
752
                                                             * whitespace only. */
753
                        return false;
754

755
                if (!utf8_is_valid(u)) /* We want to synthesize JSON from this, hence insist on UTF-8 */
209,174✔
756
                        return false;
757

758
                if (string_has_cc(u, NULL)) /* CC characters are just dangerous (and \n in particular is the
209,174✔
759
                                             * record separator in /etc/passwd), so we can't allow that. */
760
                        return false;
761

762
                if (strpbrk(u, ":/")) /* Colons are the field separator in /etc/passwd, we can't allow
209,172✔
763
                                       * that. Slashes are special to file systems paths and user names
764
                                       * typically show up in the file system as home directories, hence
765
                                       * don't allow slashes. */
766
                        return false;
767

768
                if (in_charset(u, DIGITS)) /* Don't allow fully numeric strings, they might be confused with
209,160✔
769
                                            * UIDs (note that this test is more broad than the parse_uid()
770
                                            * test above, as it will cover more than the 32-bit range, and it
771
                                            * will detect 65535 (which is in invalid UID, even though in the
772
                                            * unsigned 32 bit range) */
773
                        return false;
774

775
                if (u[0] == '-' && in_charset(u + 1, DIGITS)) /* Don't allow negative fully numeric strings
209,152✔
776
                                                               * either. After all some people write 65535 as
777
                                                               * -1 (even though that's not even true on
778
                                                               * 32-bit uid_t anyway) */
779
                        return false;
780

781
                if (dot_or_dot_dot(u)) /* User names typically become home directory names, and these two are
209,150✔
782
                                        * special in that context, don't allow that. */
783
                        return false;
784

785
                /* Compare with strict result and warn if result doesn't match */
786
                if (FLAGS_SET(flags, VALID_USER_WARN) && !valid_user_group_name(u, 0))
209,144✔
787
                        log_struct(LOG_NOTICE,
×
788
                                   LOG_MESSAGE("Accepting user/group name '%s', which does not match strict user/group name rules.", u),
789
                                   LOG_ITEM("USER_GROUP_NAME=%s", u),
790
                                   LOG_MESSAGE_ID(SD_MESSAGE_UNSAFE_USER_NAME_STR));
791

792
                /* Note that we make no restrictions on the length in relaxed mode! */
793
        } else {
794
                long sz;
6,723✔
795
                size_t l;
6,723✔
796

797
                /* Also see POSIX IEEE Std 1003.1-2008, 2016 Edition, 3.437. We are a bit stricter here
798
                 * however. Specifically we deviate from POSIX rules:
799
                 *
800
                 * - We don't allow empty user names (see above)
801
                 * - We require that names fit into the appropriate utmp field
802
                 * - We don't allow any dots (this conflicts with chown syntax which permits dots as user/group name separator)
803
                 * - We don't allow dashes or digit as the first character
804
                 *
805
                 * Note that other systems are even more restrictive, and don't permit underscores or uppercase characters.
806
                 */
807

808
                if (!ascii_isalpha(u[0]) &&
6,723✔
809
                    u[0] != '_')
810
                        return false;
811

812
                for (i = u+1; *i; i++)
76,853✔
813
                        if (!ascii_isalpha(*i) &&
70,231✔
814
                            !ascii_isdigit(*i) &&
10,451✔
815
                            !IN_SET(*i, '_', '-'))
4,939✔
816
                                return false;
817

818
                l = i - u;
6,622✔
819

820
                sz = sysconf(_SC_LOGIN_NAME_MAX);
6,622✔
821
                assert_se(sz > 0);
6,622✔
822

823
                if (l > (size_t) sz) /* glibc: 256 */
6,622✔
824
                        return false;
825
                if (l > NAME_MAX) /* must fit in a filename: 255 */
6,622✔
826
                        return false;
827
                if (l > sizeof_field(struct utmpx, ut_user) - 1) /* must fit in utmp: 31 */
6,622✔
828
                        return false;
16✔
829
        }
830

831
        return true;
832
}
833

834
bool valid_gecos(const char *d) {
7,826✔
835

836
        if (!d)
7,826✔
837
                return false;
838

839
        if (!utf8_is_valid(d))
7,825✔
840
                return false;
841

842
        if (string_has_cc(d, NULL))
7,825✔
843
                return false;
844

845
        /* Colons are used as field separators, and hence not OK */
846
        if (strchr(d, ':'))
7,824✔
847
                return false;
1✔
848

849
        return true;
850
}
851

852
char* mangle_gecos(const char *d) {
8✔
853
        char *mangled;
8✔
854

855
        /* Makes sure the provided string becomes valid as a GEGOS field, by dropping bad chars. glibc's
856
         * putpwent() only changes \n and : to spaces. We do more: replace all CC too, and remove invalid
857
         * UTF-8 */
858

859
        mangled = strdup(d);
8✔
860
        if (!mangled)
8✔
861
                return NULL;
862

863
        for (char *i = mangled; *i; i++) {
65✔
864
                int len;
57✔
865

866
                if ((uint8_t) *i < (uint8_t) ' ' || *i == ':') {
57✔
867
                        *i = ' ';
8✔
868
                        continue;
8✔
869
                }
870

871
                len = utf8_encoded_valid_unichar(i, SIZE_MAX);
49✔
872
                if (len < 0) {
49✔
873
                        *i = ' ';
3✔
874
                        continue;
3✔
875
                }
876

877
                i += len - 1;
46✔
878
        }
879

880
        return mangled;
881
}
882

883
bool valid_home(const char *p) {
5,269✔
884
        /* Note that this function is also called by valid_shell(), any
885
         * changes must account for that. */
886

887
        if (isempty(p))
5,269✔
888
                return false;
889

890
        if (!utf8_is_valid(p))
5,265✔
891
                return false;
892

893
        if (string_has_cc(p, NULL))
5,265✔
894
                return false;
895

896
        if (!path_is_absolute(p))
5,263✔
897
                return false;
898

899
        if (!path_is_normalized(p))
5,257✔
900
                return false;
901

902
        /* Colons are used as field separators, and hence not OK */
903
        if (strchr(p, ':'))
5,253✔
904
                return false;
2✔
905

906
        return true;
907
}
908

909
bool valid_shell(const char *p) {
42✔
910
        /* We have the same requirements, so just piggy-back on the home check.
911
         *
912
         * Let's ignore /etc/shells because this is only applicable to real and not system users. It is also
913
         * incompatible with the idea of empty /etc/. */
914
        if (!valid_home(p))
42✔
915
                return false;
916

917
        return !endswith(p, "/"); /* one additional restriction: shells may not be dirs */
33✔
918
}
919

920
int maybe_setgroups(size_t size, const gid_t *list) {
1,169✔
921
        int r;
1,169✔
922

923
        /* Check if setgroups is allowed before we try to drop all the auxiliary groups */
924
        if (size == 0) { /* Dropping all aux groups? */
1,169✔
925

926
                /* The kernel refuses setgroups() if there are no GID mappings in the current
927
                 * user namespace, so check that beforehand and don't try to setgroups() if
928
                 * there are no GID mappings. */
929
                _cleanup_fclose_ FILE *f = fopen("/proc/self/gid_map", "re");
1,420✔
930
                if (!f && errno != ENOENT)
710✔
931
                        return -errno;
×
932
                if (f) {
933
                        r = safe_fgetc(f, /* ret= */ NULL);
652✔
934
                        if (r < 0)
652✔
935
                                return r;
936
                        if (r == 0) {
652✔
937
                                log_debug("Skipping setgroups(), /proc/self/gid_map is empty");
2✔
938
                                return 0;
939
                        }
940
                }
941

942
                _cleanup_free_ char *setgroups_content = NULL;
708✔
943
                r = read_one_line_file("/proc/self/setgroups", &setgroups_content);
708✔
944
                if (r < 0 && r != -ENOENT)
708✔
945
                        return r;
946
                if (r > 0 && streq(setgroups_content, "deny")) {
708✔
947
                        log_debug("Skipping setgroups(), /proc/self/setgroups is set to 'deny'");
×
948
                        return 0;
949
                }
950
        }
951

952
        return RET_NERRNO(setgroups(size, list));
1,171✔
953
}
954

955
bool synthesize_nobody(void) {
97✔
956
        /* Returns true when we shall synthesize the "nobody" user (which we do by default). This can be turned off by
957
         * touching /etc/systemd/dont-synthesize-nobody in order to provide upgrade compatibility with legacy systems
958
         * that used the "nobody" user name and group name for other UIDs/GIDs than 65534.
959
         *
960
         * Note that we do not employ any kind of synchronization on the following caching variable. If the variable is
961
         * accessed in multi-threaded programs in the worst case it might happen that we initialize twice, but that
962
         * shouldn't matter as each initialization should come to the same result. */
963
        static int cache = -1;
97✔
964

965
        if (cache < 0)
97✔
966
                cache = access("/etc/systemd/dont-synthesize-nobody", F_OK) < 0;
68✔
967

968
        return cache;
97✔
969
}
970

971
int putpwent_sane(const struct passwd *pw, FILE *stream) {
444✔
972
        assert(pw);
444✔
973
        assert(stream);
444✔
974

975
        errno = 0;
444✔
976
        if (putpwent(pw, stream) != 0)
444✔
977
                return errno_or_else(EIO);
×
978

979
        return 0;
980
}
981

982
int putspent_sane(const struct spwd *sp, FILE *stream) {
414✔
983
        assert(sp);
414✔
984
        assert(stream);
414✔
985

986
        errno = 0;
414✔
987
        if (putspent(sp, stream) != 0)
414✔
988
                return errno_or_else(EIO);
×
989

990
        return 0;
991
}
992

993
int putgrent_sane(const struct group *gr, FILE *stream) {
897✔
994
        assert(gr);
897✔
995
        assert(stream);
897✔
996

997
        errno = 0;
897✔
998
        if (putgrent(gr, stream) != 0)
897✔
999
                return errno_or_else(EIO);
×
1000

1001
        return 0;
1002
}
1003

1004
#if ENABLE_GSHADOW
1005
int putsgent_sane(const struct sgrp *sg, FILE *stream) {
872✔
1006
        assert(sg);
872✔
1007
        assert(stream);
872✔
1008

1009
        errno = 0;
872✔
1010
        if (putsgent(sg, stream) != 0)
872✔
1011
                return errno_or_else(EIO);
×
1012

1013
        return 0;
1014
}
1015
#endif
1016

1017
int fgetpwent_sane(FILE *stream, struct passwd **pw) {
1,268✔
1018
        assert(stream);
1,268✔
1019
        assert(pw);
1,268✔
1020

1021
        errno = 0;
1,268✔
1022
        struct passwd *p = fgetpwent(stream);
1,268✔
1023
        if (!p && !IN_SET(errno, 0, ENOENT))
1,268✔
1024
                return -errno;
×
1025

1026
        *pw = p;
1,268✔
1027
        return !!p;
1,268✔
1028
}
1029

1030
int fgetspent_sane(FILE *stream, struct spwd **sp) {
260✔
1031
        assert(stream);
260✔
1032
        assert(sp);
260✔
1033

1034
        errno = 0;
260✔
1035
        struct spwd *s = fgetspent(stream);
260✔
1036
        if (!s && !IN_SET(errno, 0, ENOENT))
260✔
1037
                return -errno;
×
1038

1039
        *sp = s;
260✔
1040
        return !!s;
260✔
1041
}
1042

1043
int fgetgrent_sane(FILE *stream, struct group **gr) {
2,525✔
1044
        assert(stream);
2,525✔
1045
        assert(gr);
2,525✔
1046

1047
        errno = 0;
2,525✔
1048
        struct group *g = fgetgrent(stream);
2,525✔
1049
        if (!g && !IN_SET(errno, 0, ENOENT))
2,525✔
1050
                return -errno;
×
1051

1052
        *gr = g;
2,525✔
1053
        return !!g;
2,525✔
1054
}
1055

1056
#if ENABLE_GSHADOW
1057
int fgetsgent_sane(FILE *stream, struct sgrp **sg) {
359✔
1058
        assert(stream);
359✔
1059
        assert(sg);
359✔
1060

1061
        errno = 0;
359✔
1062
        struct sgrp *s = fgetsgent(stream);
359✔
1063
        if (!s && !IN_SET(errno, 0, ENOENT))
359✔
1064
                return -errno;
×
1065

1066
        *sg = s;
359✔
1067
        return !!s;
359✔
1068
}
1069
#endif
1070

1071
int is_this_me(const char *username) {
99✔
1072
        uid_t uid;
99✔
1073
        int r;
99✔
1074

1075
        /* Checks if the specified username is our current one. Passed string might be a UID or a user name. */
1076

1077
        r = get_user_creds(username, /* flags= */ USER_CREDS_ALLOW_MISSING, NULL, &uid, NULL, NULL, NULL);
99✔
1078
        if (r < 0)
99✔
1079
                return r;
99✔
1080

1081
        return uid == getuid();
98✔
1082
}
1083

1084
const char* get_home_root(void) {
14,046✔
1085
        /* For debug purposes allow overriding where we look for home dirs */
1086
        const char *e = secure_getenv("SYSTEMD_HOME_ROOT");
14,046✔
1087
        if (e && path_is_absolute(e) && path_is_normalized(e))
14,046✔
1088
                return e;
×
1089

1090
        return "/home";
1091
}
1092

1093
static int copy_struct_passwd(const struct passwd *pw, struct passwd **ret) {
6✔
1094
        assert(pw);
6✔
1095
        assert(ret);
6✔
1096

1097
        size_t need_bytes = sizeof(struct passwd)
12✔
1098
                + strlen_ptr(pw->pw_name) + 1
6✔
1099
                + strlen_ptr(pw->pw_passwd) + 1
6✔
1100
                + strlen_ptr(pw->pw_gecos) + 1
6✔
1101
                + strlen_ptr(pw->pw_dir) + 1
6✔
1102
                + strlen_ptr(pw->pw_shell) + 1;
6✔
1103

1104
        char *buf = malloc(need_bytes);
6✔
1105
        if (!buf)
6✔
1106
                return -ENOMEM;
1107

1108
        struct passwd *newpw = (struct passwd *) buf;
6✔
1109

1110
        /* The layout in our buffer:
1111
         * struct passwd, and then individual strings. */
1112
        char *p = buf + sizeof(struct passwd);
6✔
1113

1114
        newpw->pw_name = p;
6✔
1115
        p = stpcpy(p, strempty(pw->pw_name)) + 1;
6✔
1116

1117
        newpw->pw_passwd = p;
6✔
1118
        p = stpcpy(p, strempty(pw->pw_passwd)) + 1;
6✔
1119

1120
        newpw->pw_uid = pw->pw_uid;
6✔
1121
        newpw->pw_gid = pw->pw_gid;
6✔
1122

1123
        newpw->pw_gecos = p;
6✔
1124
        p = stpcpy(p, strempty(pw->pw_gecos)) + 1;
6✔
1125

1126
        newpw->pw_dir = p;
6✔
1127
        p = stpcpy(p, strempty(pw->pw_dir)) + 1;
6✔
1128

1129
        newpw->pw_shell = p;
6✔
1130
        p = stpcpy(p, strempty(pw->pw_shell)) + 1;
6✔
1131

1132
        *ret = newpw;
6✔
1133
        return 0;
6✔
1134
}
1135

1136
/* Iterate the given list of passwd-format files looking for an entry matching the predicate (by
1137
 * name if 'name' is non-NULL and by 'uid' if valid). Returns -ESRCH if no entry is found. */
1138
int lookup_pwent_in_files(
12✔
1139
                char * const *files,
1140
                const char *name,
1141
                uid_t uid,
1142
                struct passwd **ret) {
1143

1144
        int r;
12✔
1145

1146
        assert(files);
12✔
1147
        assert(name || uid_is_valid(uid));
12✔
1148

1149
        STRV_FOREACH(fname, files) {
18✔
1150
                _cleanup_fclose_ FILE *f = NULL;
13✔
1151
                struct passwd *pw;
13✔
1152

1153
                r = fopen_unlocked(*fname, "re", &f);
13✔
1154
                if (r == -ENOENT)
13✔
1155
                        continue;
2✔
1156
                if (r < 0)
11✔
1157
                        return r;
1158

1159
                while ((r = fgetpwent_sane(f, &pw)) > 0) {
21✔
1160
                        if (name && !streq_ptr(pw->pw_name, name))
17✔
1161
                                continue;
5✔
1162
                        if (uid_is_valid(uid) && pw->pw_uid != uid)
12✔
1163
                                continue;
5✔
1164
                        if (ret)
7✔
1165
                                return copy_struct_passwd(pw, ret);
6✔
1166
                        return 0;
1167
                }
1168
                if (r < 0)
4✔
1169
                        return r;
1170
        }
1171

1172
        return -ESRCH;
1173
}
1174

1175
#if !BUILD_STATIC
1176

1177
static size_t getpw_buffer_size(void) {
8,784✔
1178
        long bufsize = sysconf(_SC_GETPW_R_SIZE_MAX);
8,784✔
1179
        return bufsize <= 0 ? 4096U : (size_t) bufsize;
8,784✔
1180
}
1181

1182
static bool errno_is_user_doesnt_exist(int error) {
1✔
1183
        /* See getpwnam(3) and getgrnam(3): those codes and others can be returned if the user or group are
1184
         * not found. */
1185
        return IN_SET(abs(error), ENOENT, ESRCH, EBADF, EPERM);
1✔
1186
}
1187

1188
#endif
1189

1190
int getpwnam_malloc(const char *name, struct passwd **ret) {
4,768✔
1191
        /* For static builds use a very simplistic implementation that looks at 'passwd' in /etc/
1192
         * and /usr/lib/, and for non-static builds use NSS. The first matching entry wins. The
1193
         * same logic is implemented in getpwnam_malloc, getpwuid_malloc, getgrnam_malloc, and
1194
         * getgrgid_malloc. */
1195

1196
        if (isempty(name))
9,536✔
1197
                return -EINVAL;
1198

1199
#if BUILD_STATIC
1200
        return lookup_pwent_in_files(PASSWD_FILES, name, UID_INVALID, ret);
1201
#else
1202
        size_t bufsize = getpw_buffer_size();
4,768✔
1203
        int r;
4,768✔
1204

1205
        /* A wrapper around getpwnam_r() that allocates the necessary buffer on the heap. The caller must
1206
         * free() the returned structure! */
1207

1208
        for (;;) {
4,768✔
1209
                _cleanup_free_ void *buf = NULL;
4,768✔
1210

1211
                /* Silence static analyzers */
1212
                assert(bufsize <= SIZE_MAX - ALIGN(sizeof(struct passwd)));
4,768✔
1213
                buf = malloc0(ALIGN(sizeof(struct passwd)) + bufsize);
4,768✔
1214
                if (!buf)
4,768✔
1215
                        return -ENOMEM;
1216

1217
                struct passwd *pw = NULL;
4,768✔
1218
                r = getpwnam_r(name, buf, (char*) buf + ALIGN(sizeof(struct passwd)), bufsize, &pw);
4,768✔
1219
                if (r == 0) {
4,768✔
1220
                        if (pw) {
4,768✔
1221
                                if (ret)
4,479✔
1222
                                        *ret = TAKE_PTR(buf);
4,479✔
1223
                                return 0;
1224
                        }
1225

1226
                        return -ESRCH;
1227
                }
1228

UNCOV
1229
                assert(r > 0);
×
1230

1231
                /* getpwnam() may fail with ENOENT if /etc/passwd is missing.  For us that is equivalent to
1232
                 * the name not being defined. */
1233
                if (errno_is_user_doesnt_exist(r))
×
1234
                        return -ESRCH;
UNCOV
1235
                if (r != ERANGE)
×
UNCOV
1236
                        return -r;
×
1237

UNCOV
1238
                if (bufsize > SIZE_MAX/2 - ALIGN(sizeof(struct passwd)))
×
1239
                        return -ENOMEM;
UNCOV
1240
                bufsize *= 2;
×
1241
        }
1242
#endif
1243
}
1244

1245
int getpwuid_malloc(uid_t uid, struct passwd **ret) {
4,016✔
1246
        if (!uid_is_valid(uid))
4,016✔
1247
                return -EINVAL;
1248

1249
#if BUILD_STATIC
1250
        return lookup_pwent_in_files(PASSWD_FILES, /* name= */ NULL, uid, ret);
1251
#else
1252
        size_t bufsize = getpw_buffer_size();
4,016✔
1253
        int r;
4,016✔
1254

1255
        for (;;) {
4,016✔
1256
                _cleanup_free_ void *buf = NULL;
4,016✔
1257

1258
                /* Silence static analyzers */
1259
                assert(bufsize <= SIZE_MAX - ALIGN(sizeof(struct passwd)));
4,016✔
1260
                buf = malloc0(ALIGN(sizeof(struct passwd)) + bufsize);
4,016✔
1261
                if (!buf)
4,016✔
1262
                        return -ENOMEM;
1263

1264
                struct passwd *pw = NULL;
4,016✔
1265
                r = getpwuid_r(uid, buf, (char*) buf + ALIGN(sizeof(struct passwd)), bufsize, &pw);
4,016✔
1266
                if (r == 0) {
4,016✔
1267
                        if (pw) {
4,016✔
1268
                                if (ret)
695✔
1269
                                        *ret = TAKE_PTR(buf);
695✔
1270
                                return 0;
1271
                        }
1272

1273
                        return -ESRCH;
1274
                }
1275

UNCOV
1276
                assert(r > 0);
×
1277

UNCOV
1278
                if (errno_is_user_doesnt_exist(r))
×
1279
                        return -ESRCH;
UNCOV
1280
                if (r != ERANGE)
×
UNCOV
1281
                        return -r;
×
1282

UNCOV
1283
                if (bufsize > SIZE_MAX/2 - ALIGN(sizeof(struct passwd)))
×
1284
                        return -ENOMEM;
UNCOV
1285
                bufsize *= 2;
×
1286
        }
1287
#endif
1288
}
1289

1290
static int copy_struct_group(const struct group *gr, struct group **ret) {
5✔
1291
        assert(gr);
5✔
1292
        assert(ret);
5✔
1293

1294
        size_t need_bytes = sizeof(struct group)
10✔
1295
                + strlen_ptr(gr->gr_name) + 1
5✔
1296
                + strlen_ptr(gr->gr_passwd) + 1,
5✔
1297
                n_mem = 0;
5✔
1298
        STRV_FOREACH(s, gr->gr_mem) {
11✔
1299
                need_bytes += sizeof(char*) + strlen(*s) + 1;
6✔
1300
                n_mem++;
6✔
1301
        }
1302
        need_bytes += sizeof(char*);  /* NULL terminator for gr_mem */
5✔
1303

1304
        char *buf = malloc(need_bytes);
5✔
1305
        if (!buf)
5✔
1306
                return -ENOMEM;
1307

1308
        struct group *newgr = (struct group *) buf;
5✔
1309

1310
        /* The layout in our buffer:
1311
         * struct group, ->gr_mem pointers terminated by NULL, ->gr_name, ->gr_passwd, ->gr_mem items */
1312
        /* The ->gr_mem array is first, because it needs alignment. */
1313
        assert_cc(sizeof(struct group) % alignof(char*) == 0);
5✔
1314

1315
        char *p = buf + sizeof(struct group) + (n_mem + 1) * sizeof(char*);
5✔
1316

1317
        newgr->gr_name = p;
5✔
1318
        p = stpcpy(p, strempty(gr->gr_name)) + 1;
5✔
1319

1320
        newgr->gr_passwd = p;
5✔
1321
        p = stpcpy(p, strempty(gr->gr_passwd)) + 1;
5✔
1322

1323
        newgr->gr_gid = gr->gr_gid;
5✔
1324

1325
        newgr->gr_mem = (char**) (buf + sizeof(struct group));
5✔
1326
        for (size_t i = 0; i < n_mem; i++) {
11✔
1327
                newgr->gr_mem[i] = p;
6✔
1328
                p = stpcpy(p, gr->gr_mem[i]) + 1;
6✔
1329
        }
1330
        newgr->gr_mem[n_mem] = NULL;
5✔
1331

1332
        *ret = newgr;
5✔
1333
        return 0;
5✔
1334
}
1335

1336
/* See lookup_pwent_in_files() for the analogous passwd-file version. */
1337
int lookup_grent_in_files(
8✔
1338
                char * const *files,
1339
                const char *name,
1340
                gid_t gid,
1341
                struct group **ret) {
1342

1343
        int r;
8✔
1344

1345
        assert(files);
8✔
1346
        assert(name || gid_is_valid(gid));
11✔
1347

1348
        STRV_FOREACH(fname, files) {
11✔
1349
                _cleanup_fclose_ FILE *f = NULL;
9✔
1350
                struct group *gr;
9✔
1351

1352
                r = fopen_unlocked(*fname, "re", &f);
9✔
1353
                if (r == -ENOENT)
9✔
1354
                        continue;
1✔
1355
                if (r < 0)
8✔
1356
                        return r;
1357

1358
                while ((r = fgetgrent_sane(f, &gr)) > 0) {
14✔
1359
                        if (name && !streq_ptr(gr->gr_name, name))
12✔
1360
                                continue;
3✔
1361
                        if (gid_is_valid(gid) && gr->gr_gid != gid)
9✔
1362
                                continue;
3✔
1363
                        if (ret)
6✔
1364
                                return copy_struct_group(gr, ret);
5✔
1365
                        return 0;
1366
                }
1367
                if (r < 0)
2✔
1368
                        return r;
1369
        }
1370

1371
        return -ESRCH;
1372
}
1373

1374
int sysconf_ngroups_max(void) {
29,633✔
1375
        /* Query sysconf _SC_NGROUPS_MAX. Returns an int because the expected value is 64k
1376
         * and later on this is used as an int with various glibc consumers. */
1377

1378
        errno = 0;
29,633✔
1379
        long ngroups_max = sysconf(_SC_NGROUPS_MAX);
29,633✔
1380
        if (ngroups_max <= 0)
29,633✔
UNCOV
1381
                return errno_or_else(EOPNOTSUPP);
×
1382
        if (ngroups_max > INT_MAX)
29,633✔
1383
                return -ERANGE;
1384
        return ngroups_max;
29,633✔
1385
}
1386

1387
ssize_t lookup_groups_in_files(
3✔
1388
                char * const *files,
1389
                const char *name,
1390
                gid_t gid,
1391
                gid_t **ret) {
1392

1393
        _cleanup_free_ gid_t *arr = NULL;
3✔
1394
        ssize_t n_arr = 0;
3✔
1395
        int r;
3✔
1396

1397
        assert(files);
3✔
1398
        assert(name);
3✔
1399
        assert(gid_is_valid(gid));
3✔
1400

1401
        int ngroups_max = sysconf_ngroups_max();
3✔
1402
        if (ngroups_max < 0)
3✔
UNCOV
1403
                return ngroups_max;
×
1404

1405
        if (!GREEDY_REALLOC(arr, n_arr + 1))
3✔
1406
                return -ENOMEM;
1407
        arr[n_arr++] = gid;
3✔
1408

1409
        STRV_FOREACH(fname, files) {
6✔
1410
                _cleanup_fclose_ FILE *f = NULL;
3✔
1411
                struct group *gr;
3✔
1412

1413
                r = fopen_unlocked(*fname, "re", &f);
3✔
1414
                if (r == -ENOENT)
3✔
UNCOV
1415
                        continue;
×
1416
                if (r < 0)
3✔
UNCOV
1417
                        return r;
×
1418

1419
                while ((r = fgetgrent_sane(f, &gr)) > 0) {
15✔
1420
                        if (gid == gr->gr_gid)
12✔
1421
                                continue;
2✔
1422

1423
                        /* Deduplicate groups, in case the group is doubly defined in the same or
1424
                         * different files. */
1425
                        if (gid_list_has(arr, n_arr, gr->gr_gid))
10✔
UNCOV
1426
                                continue;
×
1427

1428
                        if (!strv_contains(gr->gr_mem, name))
10✔
1429
                                continue;
5✔
1430

1431
                        /* Do not allow more than _SC_NGROUPS_MAX. */
1432
                        if (n_arr >= ngroups_max)
5✔
1433
                                return -ENOBUFS;
1434

1435
                        if (!GREEDY_REALLOC(arr, n_arr + 1))
5✔
1436
                                return -ENOMEM;
1437
                        arr[n_arr++] = gr->gr_gid;
5✔
1438
                }
1439
                if (r < 0)
3✔
UNCOV
1440
                        return r;
×
1441
        }
1442

1443
        if (ret)
3✔
1444
                *ret = TAKE_PTR(arr);
3✔
1445
        return n_arr;
1446
}
1447

1448
int getgrouplist_malloc(const char *user, gid_t gid, gid_t **ret) {
12✔
1449
#if BUILD_STATIC
1450
        return lookup_groups_in_files(GROUP_FILES, user, gid, ret);
1451
#else
1452
        int ngroups_max = sysconf_ngroups_max();
12✔
1453
        if (ngroups_max < 0)
12✔
1454
                return ngroups_max;
12✔
1455

1456
        _cleanup_free_ gid_t *gids = new(gid_t, ngroups_max);
24✔
1457
        if (!gids)
12✔
1458
                return -ENOMEM;
1459

1460
        int k = ngroups_max;
12✔
1461
        if (getgrouplist(user, gid, gids, &k) < 0)
12✔
1462
                return -EIO;  /* getgrouplist does not set errno */
1463

1464
        if (ret)
12✔
1465
                *ret = TAKE_PTR(gids);
12✔
1466
        return k;
12✔
1467
#endif
1468
}
1469

1470
int initgroups_wrapper(const char *user, gid_t gid) {
2,145✔
1471
#if BUILD_STATIC
1472
        _cleanup_free_ gid_t *groups = NULL;
1473
        int r, n_groups;
1474

1475
        n_groups = getgrouplist_malloc(user, gid, &groups);
1476
        if (n_groups <= 0)
1477
                return n_groups;
1478

1479
        /* Try to set the maximum number of groups the kernel can handle. This is what glibc does. */
1480
        for (; n_groups > 0; n_groups--) {
1481
                r = RET_NERRNO(setgroups(n_groups, groups));
1482
                if (r != -EINVAL)
1483
                        break;
1484
        }
1485

1486
        return r;
1487
#else
1488
        return RET_NERRNO(initgroups(user, gid));
2,145✔
1489
#endif
1490
}
1491

1492
#if !BUILD_STATIC
1493

1494
static size_t getgr_buffer_size(void) {
14,118✔
1495
        long bufsize = sysconf(_SC_GETGR_R_SIZE_MAX);
14,118✔
1496
        return bufsize <= 0 ? 4096U : (size_t) bufsize;
14,118✔
1497
}
1498

1499
#endif
1500

1501
int getgrnam_malloc(const char *name, struct group **ret) {
8,167✔
1502
        if (isempty(name))
16,334✔
1503
                return -EINVAL;
1504

1505
#if BUILD_STATIC
1506
        return lookup_grent_in_files(GROUP_FILES, name, GID_INVALID, ret);
1507
#else
1508
        size_t bufsize = getgr_buffer_size();
8,167✔
1509
        int r;
8,167✔
1510

1511
        for (;;) {
8,167✔
1512
                _cleanup_free_ void *buf = NULL;
8,167✔
1513

1514
                /* Silence static analyzers */
1515
                assert(bufsize <= SIZE_MAX - ALIGN(sizeof(struct group)));
8,167✔
1516
                buf = malloc0(ALIGN(sizeof(struct group)) + bufsize);
8,167✔
1517
                if (!buf)
8,167✔
1518
                        return -ENOMEM;
1519

1520
                struct group *gr = NULL;
8,167✔
1521
                r = getgrnam_r(name, buf, (char*) buf + ALIGN(sizeof(struct group)), bufsize, &gr);
8,167✔
1522
                if (r == 0) {
8,167✔
1523
                        if (gr) {
8,166✔
1524
                                if (ret)
7,889✔
1525
                                        *ret = TAKE_PTR(buf);
7,887✔
1526
                                return 0;
1527
                        }
1528

1529
                        return -ESRCH;
1530
                }
1531

1532
                assert(r > 0);
1✔
1533

1534
                if (errno_is_user_doesnt_exist(r))
1✔
1535
                        return -ESRCH;
1536
                if (r != ERANGE)
1✔
1537
                        return -r;
1✔
1538

UNCOV
1539
                if (bufsize > SIZE_MAX/2 - ALIGN(sizeof(struct group)))
×
1540
                        return -ENOMEM;
UNCOV
1541
                bufsize *= 2;
×
1542
        }
1543
#endif
1544
}
1545

1546
int getgrgid_malloc(gid_t gid, struct group **ret) {
5,951✔
1547
        if (!gid_is_valid(gid))
5,951✔
1548
                return -EINVAL;
1549

1550
#if BUILD_STATIC
1551
        return lookup_grent_in_files(GROUP_FILES, /* name= */ NULL, gid, ret);
1552
#else
1553
        size_t bufsize = getgr_buffer_size();
5,951✔
1554
        int r;
5,951✔
1555

1556
        for (;;) {
5,951✔
1557
                _cleanup_free_ void *buf = NULL;
5,951✔
1558

1559
                /* Silence static analyzers */
1560
                assert(bufsize <= SIZE_MAX - ALIGN(sizeof(struct group)));
5,951✔
1561
                buf = malloc0(ALIGN(sizeof(struct group)) + bufsize);
5,951✔
1562
                if (!buf)
5,951✔
1563
                        return -ENOMEM;
1564

1565
                struct group *gr = NULL;
5,951✔
1566
                r = getgrgid_r(gid, buf, (char*) buf + ALIGN(sizeof(struct group)), bufsize, &gr);
5,951✔
1567
                if (r == 0) {
5,951✔
1568
                        if (gr) {
5,951✔
1569
                                if (ret)
2,580✔
1570
                                        *ret = TAKE_PTR(buf);
2,580✔
1571
                                return 0;
1572
                        }
1573

1574
                        return -ESRCH;
1575
                }
1576

UNCOV
1577
                assert(r > 0);
×
1578

UNCOV
1579
                if (errno_is_user_doesnt_exist(r))
×
1580
                        return -ESRCH;
UNCOV
1581
                if (r != ERANGE)
×
UNCOV
1582
                        return -r;
×
1583

UNCOV
1584
                if (bufsize > SIZE_MAX/2 - ALIGN(sizeof(struct group)))
×
1585
                        return -ENOMEM;
UNCOV
1586
                bufsize *= 2;
×
1587
        }
1588
#endif
1589
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc