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

systemd / systemd / 16791678039

06 Aug 2025 11:10PM UTC coverage: 72.181% (-0.04%) from 72.223%
16791678039

push

github

yuwata
logging: Improve logging messages related to NFTSet.

The 'NFTSet' directive in various units adds and removes entries in nftables
sets, it does not add or remove entire sets. The logging messages should
indicate that an entry was added or removed, not that a set was added or
removed.

2 of 6 new or added lines in 3 files covered. (33.33%)

496 existing lines in 52 files now uncovered.

302228 of 418708 relevant lines covered (72.18%)

647735.83 hits per line

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

85.91
/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
bool uid_is_valid(uid_t uid) {
11,074,406✔
32

33
        /* Also see POSIX IEEE Std 1003.1-2008, 2016 Edition, 3.436. */
34

35
        /* Some libc APIs use UID_INVALID as special placeholder */
36
        if (uid == (uid_t) UINT32_C(0xFFFFFFFF))
11,074,406✔
37
                return false;
38

39
        /* A long time ago UIDs where 16 bit, hence explicitly avoid the 16-bit -1 too */
40
        if (uid == (uid_t) UINT32_C(0xFFFF))
5,651,108✔
41
                return false;
18✔
42

43
        return true;
44
}
45

46
int parse_uid(const char *s, uid_t *ret) {
207,522✔
47
        uint32_t uid = 0;
207,522✔
48
        int r;
207,522✔
49

50
        assert(s);
207,522✔
51

52
        assert_cc(sizeof(uid_t) == sizeof(uint32_t));
207,522✔
53

54
        /* We are very strict when parsing UIDs, and prohibit +/- as prefix, leading zero as prefix, and
55
         * whitespace. We do this, since this call is often used in a context where we parse things as UID
56
         * first, and if that doesn't work we fall back to NSS. Thus we really want to make sure that UIDs
57
         * are parsed as UIDs only if they really really look like UIDs. */
58
        r = safe_atou32_full(s, 10
207,522✔
59
                             | SAFE_ATO_REFUSE_PLUS_MINUS
60
                             | SAFE_ATO_REFUSE_LEADING_ZERO
61
                             | SAFE_ATO_REFUSE_LEADING_WHITESPACE, &uid);
62
        if (r < 0)
207,522✔
63
                return r;
207,522✔
64

65
        if (!uid_is_valid(uid))
10,775✔
66
                return -ENXIO; /* we return ENXIO instead of EINVAL
67
                                * here, to make it easy to distinguish
68
                                * invalid numeric uids from invalid
69
                                * strings. */
70

71
        if (ret)
10,758✔
72
                *ret = uid;
10,232✔
73

74
        return 0;
75
}
76

77
int parse_uid_range(const char *s, uid_t *ret_lower, uid_t *ret_upper) {
35✔
78
        _cleanup_free_ char *word = NULL;
35✔
79
        uid_t l, u;
35✔
80
        int r;
35✔
81

82
        assert(s);
35✔
83
        assert(ret_lower);
35✔
84
        assert(ret_upper);
35✔
85

86
        r = extract_first_word(&s, &word, "-", EXTRACT_DONT_COALESCE_SEPARATORS);
35✔
87
        if (r < 0)
35✔
88
                return r;
89
        if (r == 0)
35✔
90
                return -EINVAL;
91

92
        r = parse_uid(word, &l);
35✔
93
        if (r < 0)
35✔
94
                return r;
95

96
        /* Check for the upper bound and extract it if needed */
97
        if (!s)
22✔
98
                /* Single number with no dash. */
99
                u = l;
7✔
100
        else if (!*s)
15✔
101
                /* Trailing dash is an error. */
102
                return -EINVAL;
103
        else {
104
                r = parse_uid(s, &u);
15✔
105
                if (r < 0)
15✔
106
                        return r;
107

108
                if (l > u)
10✔
109
                        return -EINVAL;
110
        }
111

112
        *ret_lower = l;
16✔
113
        *ret_upper = u;
16✔
114
        return 0;
16✔
115
}
116

117
char* getlogname_malloc(void) {
×
118
        uid_t uid;
×
119
        struct stat st;
×
120

121
        if (isatty_safe(STDIN_FILENO) && fstat(STDIN_FILENO, &st) >= 0)
×
122
                uid = st.st_uid;
×
123
        else
124
                uid = getuid();
×
125

126
        return uid_to_name(uid);
×
127
}
128

129
char* getusername_malloc(void) {
579✔
130
        const char *e;
579✔
131

132
        e = secure_getenv("USER");
579✔
133
        if (e)
579✔
134
                return strdup(e);
472✔
135

136
        return uid_to_name(getuid());
107✔
137
}
138

139
bool is_nologin_shell(const char *shell) {
2,511✔
140
        return PATH_IN_SET(shell,
2,511✔
141
                           /* 'nologin' is the friendliest way to disable logins for a user account. It prints a nice
142
                            * message and exits. Different distributions place the binary at different places though,
143
                            * hence let's list them all. */
144
                           "/bin/nologin",
145
                           "/sbin/nologin",
146
                           "/usr/bin/nologin",
147
                           "/usr/sbin/nologin",
148
                           /* 'true' and 'false' work too for the same purpose, but are less friendly as they don't do
149
                            * any message printing. Different distributions place the binary at various places but at
150
                            * least not in the 'sbin' directory. */
151
                           "/bin/false",
152
                           "/usr/bin/false",
153
                           "/bin/true",
154
                           "/usr/bin/true");
155
}
156

157
bool shell_is_placeholder(const char *shell) {
1,983✔
158
        return isempty(shell) || is_nologin_shell(shell);
3,966✔
159
}
160

161
const char* default_root_shell_at(int rfd) {
7,107✔
162
        /* We want to use the preferred shell, i.e. DEFAULT_USER_SHELL, which usually
163
         * will be /bin/bash. Fall back to /bin/sh if DEFAULT_USER_SHELL is not found,
164
         * or any access errors. */
165

166
        assert(rfd >= 0 || rfd == AT_FDCWD);
7,107✔
167

168
        int r = chaseat(rfd, DEFAULT_USER_SHELL, CHASE_AT_RESOLVE_IN_ROOT, NULL, NULL);
7,107✔
169
        if (r < 0 && r != -ENOENT)
7,107✔
170
                log_debug_errno(r, "Failed to look up shell '%s': %m", DEFAULT_USER_SHELL);
×
171
        if (r > 0)
7,107✔
172
                return DEFAULT_USER_SHELL;
7,091✔
173

174
        return "/bin/sh";
175
}
176

177
const char* default_root_shell(const char *root) {
7,101✔
178
        _cleanup_close_ int rfd = -EBADF;
7,101✔
179

180
        rfd = open(empty_to_root(root), O_CLOEXEC | O_DIRECTORY | O_PATH);
7,101✔
181
        if (rfd < 0)
7,101✔
182
                return "/bin/sh";
183

184
        return default_root_shell_at(rfd);
7,101✔
185
}
186

187
static int synthesize_user_creds(
18,165✔
188
                const char **username,
189
                uid_t *ret_uid, gid_t *ret_gid,
190
                const char **ret_home,
191
                const char **ret_shell,
192
                UserCredsFlags flags) {
193

194
        assert(username);
18,165✔
195
        assert(*username);
18,165✔
196

197
        /* We enforce some special rules for uid=0 and uid=65534: in order to avoid NSS lookups for root we hardcode
198
         * their user record data. */
199

200
        if (STR_IN_SET(*username, "root", "0")) {
18,165✔
201
                *username = "root";
13,947✔
202

203
                if (ret_uid)
13,947✔
204
                        *ret_uid = 0;
6,871✔
205
                if (ret_gid)
13,947✔
206
                        *ret_gid = 0;
10✔
207
                if (ret_home)
13,947✔
208
                        *ret_home = "/root";
7,086✔
209
                if (ret_shell)
13,947✔
210
                        *ret_shell = default_root_shell(NULL);
7,086✔
211

212
                return 0;
13,947✔
213
        }
214

215
        if (STR_IN_SET(*username, NOBODY_USER_NAME, "65534") &&
4,229✔
216
            synthesize_nobody()) {
11✔
217
                *username = NOBODY_USER_NAME;
11✔
218

219
                if (ret_uid)
11✔
220
                        *ret_uid = UID_NOBODY;
11✔
221
                if (ret_gid)
11✔
222
                        *ret_gid = GID_NOBODY;
7✔
223
                if (ret_home)
11✔
224
                        *ret_home = FLAGS_SET(flags, USER_CREDS_SUPPRESS_PLACEHOLDER) ? NULL : "/";
6✔
225
                if (ret_shell)
11✔
226
                        *ret_shell = FLAGS_SET(flags, USER_CREDS_SUPPRESS_PLACEHOLDER) ? NULL : NOLOGIN;
6✔
227

228
                return 0;
11✔
229
        }
230

231
        return -ENOMEDIUM;
4,207✔
232
}
233

234
int get_user_creds(
18,642✔
235
                const char **username,
236
                uid_t *ret_uid, gid_t *ret_gid,
237
                const char **ret_home,
238
                const char **ret_shell,
239
                UserCredsFlags flags) {
240

241
        bool patch_username = false;
18,642✔
242
        uid_t u = UID_INVALID;
18,642✔
243
        struct passwd *p;
18,642✔
244
        int r;
18,642✔
245

246
        assert(username);
18,642✔
247
        assert(*username);
18,642✔
248
        assert((ret_home || ret_shell) || !(flags & (USER_CREDS_SUPPRESS_PLACEHOLDER|USER_CREDS_CLEAN)));
18,642✔
249

250
        if (!FLAGS_SET(flags, USER_CREDS_PREFER_NSS) ||
18,642✔
251
            (!ret_home && !ret_shell)) {
252

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

262
                r = synthesize_user_creds(username, ret_uid, ret_gid, ret_home, ret_shell, flags);
18,165✔
263
                if (r >= 0)
18,165✔
264
                        return 0;
18,642✔
265
                if (r != -ENOMEDIUM) /* not a username we can synthesize */
4,207✔
266
                        return r;
267
        }
268

269
        if (parse_uid(*username, &u) >= 0) {
4,684✔
270
                errno = 0;
194✔
271
                p = getpwuid(u);
194✔
272

273
                /* If there are multiple users with the same id, make sure to leave $USER to the configured value
274
                 * instead of the first occurrence in the database. However if the uid was configured by a numeric uid,
275
                 * then let's pick the real username from /etc/passwd. */
276
                if (p)
194✔
277
                        patch_username = true;
278
                else if (FLAGS_SET(flags, USER_CREDS_ALLOW_MISSING) && !ret_gid && !ret_home && !ret_shell) {
3✔
279

280
                        /* If the specified user is a numeric UID and it isn't in the user database, and the caller
281
                         * passed USER_CREDS_ALLOW_MISSING and was only interested in the UID, then just return that
282
                         * and don't complain. */
283

284
                        if (ret_uid)
×
285
                                *ret_uid = u;
×
286

287
                        return 0;
×
288
                }
289
        } else {
290
                errno = 0;
4,490✔
291
                p = getpwnam(*username);
4,490✔
292
        }
293
        if (!p) {
4,493✔
294
                /* getpwnam() may fail with ENOENT if /etc/passwd is missing.
295
                 * For us that is equivalent to the name not being defined. */
296
                r = IN_SET(errno, 0, ENOENT) ? -ESRCH : -errno;
9✔
297

298
                /* If the user requested that we only synthesize as fallback, do so now */
299
                if (FLAGS_SET(flags, USER_CREDS_PREFER_NSS))
9✔
300
                        if (synthesize_user_creds(username, ret_uid, ret_gid, ret_home, ret_shell, flags) >= 0)
×
301
                                return 0;
302

303
                return r;
9✔
304
        }
305

306
        if (ret_uid && !uid_is_valid(p->pw_uid))
4,675✔
307
                return -EBADMSG;
308

309
        if (ret_gid && !gid_is_valid(p->pw_gid))
4,675✔
310
                return -EBADMSG;
311

312
        if (ret_uid)
4,675✔
313
                *ret_uid = p->pw_uid;
4,675✔
314

315
        if (ret_gid)
4,675✔
316
                *ret_gid = p->pw_gid;
2,632✔
317

318
        if (ret_home)
4,675✔
319
                /* Note: we don't insist on normalized paths, since there are setups that have /./ in the path */
320
                *ret_home = (FLAGS_SET(flags, USER_CREDS_SUPPRESS_PLACEHOLDER) && empty_or_root(p->pw_dir)) ||
1✔
321
                            (FLAGS_SET(flags, USER_CREDS_CLEAN) && (!path_is_valid(p->pw_dir) || !path_is_absolute(p->pw_dir)))
5,226✔
322
                            ? NULL : p->pw_dir;
5,226✔
323

324
        if (ret_shell)
4,675✔
325
                *ret_shell = (FLAGS_SET(flags, USER_CREDS_SUPPRESS_PLACEHOLDER) && shell_is_placeholder(p->pw_shell)) ||
1✔
326
                             (FLAGS_SET(flags, USER_CREDS_CLEAN) && (!path_is_valid(p->pw_shell) || !path_is_absolute(p->pw_shell)))
5,226✔
327
                             ? NULL : p->pw_shell;
5,226✔
328

329
        if (patch_username)
4,675✔
330
                *username = p->pw_name;
191✔
331

332
        return 0;
333
}
334

335
static int synthesize_group_creds(
10,102✔
336
                const char **groupname,
337
                gid_t *ret_gid) {
338

339
        assert(groupname);
10,102✔
340
        assert(*groupname);
10,102✔
341

342
        if (STR_IN_SET(*groupname, "root", "0")) {
10,102✔
343
                *groupname = "root";
5,145✔
344

345
                if (ret_gid)
5,145✔
346
                        *ret_gid = 0;
5,145✔
347

348
                return 0;
5,145✔
349
        }
350

351
        if (STR_IN_SET(*groupname, NOBODY_GROUP_NAME, "65534") &&
4,961✔
352
            synthesize_nobody()) {
4✔
353
                *groupname = NOBODY_GROUP_NAME;
4✔
354

355
                if (ret_gid)
4✔
356
                        *ret_gid = GID_NOBODY;
4✔
357

358
                return 0;
4✔
359
        }
360

361
        return -ENOMEDIUM;
4,953✔
362
}
363

364
int get_group_creds(const char **groupname, gid_t *ret_gid, UserCredsFlags flags) {
10,102✔
365
        bool patch_groupname = false;
10,102✔
366
        struct group *g;
10,102✔
367
        gid_t id;
10,102✔
368
        int r;
10,102✔
369

370
        assert(groupname);
10,102✔
371
        assert(*groupname);
10,102✔
372

373
        if (!FLAGS_SET(flags, USER_CREDS_PREFER_NSS)) {
10,102✔
374
                r = synthesize_group_creds(groupname, ret_gid);
10,102✔
375
                if (r >= 0)
10,102✔
376
                        return 0;
10,102✔
377
                if (r != -ENOMEDIUM) /* not a groupname we can synthesize */
4,953✔
378
                        return r;
379
        }
380

381
        if (parse_gid(*groupname, &id) >= 0) {
4,953✔
382
                errno = 0;
×
383
                g = getgrgid(id);
×
384

385
                if (g)
×
386
                        patch_groupname = true;
387
                else if (FLAGS_SET(flags, USER_CREDS_ALLOW_MISSING)) {
×
388
                        if (ret_gid)
×
389
                                *ret_gid = id;
×
390

391
                        return 0;
×
392
                }
393
        } else {
394
                errno = 0;
4,953✔
395
                g = getgrnam(*groupname);
4,953✔
396
        }
397

398
        if (!g) {
4,953✔
399
                /* getgrnam() may fail with ENOENT if /etc/group is missing.
400
                 * For us that is equivalent to the name not being defined. */
401
                r = IN_SET(errno, 0, ENOENT) ? -ESRCH : -errno;
1✔
402

403
                if (FLAGS_SET(flags, USER_CREDS_PREFER_NSS))
1✔
404
                        if (synthesize_group_creds(groupname, ret_gid) >= 0)
×
405
                                return 0;
406

407
                return r;
1✔
408
        }
409

410
        if (ret_gid) {
4,952✔
411
                if (!gid_is_valid(g->gr_gid))
4,952✔
412
                        return -EBADMSG;
413

414
                *ret_gid = g->gr_gid;
4,952✔
415
        }
416

417
        if (patch_groupname)
4,952✔
418
                *groupname = g->gr_name;
×
419

420
        return 0;
421
}
422

423
char* uid_to_name(uid_t uid) {
493✔
424
        char *ret;
493✔
425
        int r;
493✔
426

427
        /* Shortcut things to avoid NSS lookups */
428
        if (uid == 0)
493✔
429
                return strdup("root");
493✔
430
        if (uid == UID_NOBODY && synthesize_nobody())
200✔
431
                return strdup(NOBODY_USER_NAME);
1✔
432

433
        if (uid_is_valid(uid)) {
199✔
434
                _cleanup_free_ struct passwd *pw = NULL;
197✔
435

436
                r = getpwuid_malloc(uid, &pw);
197✔
437
                if (r >= 0)
197✔
438
                        return strdup(pw->pw_name);
197✔
439
        }
440

441
        if (asprintf(&ret, UID_FMT, uid) < 0)
2✔
442
                return NULL;
443

444
        return ret;
2✔
445
}
446

447
char* gid_to_name(gid_t gid) {
104✔
448
        char *ret;
104✔
449
        int r;
104✔
450

451
        if (gid == 0)
104✔
452
                return strdup("root");
104✔
453
        if (gid == GID_NOBODY && synthesize_nobody())
49✔
454
                return strdup(NOBODY_GROUP_NAME);
1✔
455

456
        if (gid_is_valid(gid)) {
48✔
457
                _cleanup_free_ struct group *gr = NULL;
46✔
458

459
                r = getgrgid_malloc(gid, &gr);
46✔
460
                if (r >= 0)
46✔
461
                        return strdup(gr->gr_name);
46✔
462
        }
463

464
        if (asprintf(&ret, GID_FMT, gid) < 0)
2✔
465
                return NULL;
466

467
        return ret;
2✔
468
}
469

470
static bool gid_list_has(const gid_t *list, size_t size, gid_t val) {
378✔
471
        assert(list || size == 0);
378✔
472

473
        FOREACH_ARRAY(i, list, size)
543✔
474
                if (*i == val)
178✔
475
                        return true;
476

477
        return false;
478
}
479

480
int in_gid(gid_t gid) {
41✔
481
        _cleanup_free_ gid_t *gids = NULL;
41✔
482
        int ngroups;
41✔
483

484
        if (getgid() == gid)
41✔
485
                return 1;
486

487
        if (getegid() == gid)
38✔
488
                return 1;
489

490
        if (!gid_is_valid(gid))
38✔
491
                return -EINVAL;
492

493
        ngroups = getgroups_alloc(&gids);
37✔
494
        if (ngroups < 0)
37✔
495
                return ngroups;
496

497
        return gid_list_has(gids, ngroups, gid);
37✔
498
}
499

500
int in_group(const char *name) {
14✔
501
        int r;
14✔
502
        gid_t gid;
14✔
503

504
        r = get_group_creds(&name, &gid, 0);
14✔
505
        if (r < 0)
14✔
506
                return r;
14✔
507

508
        return in_gid(gid);
13✔
509
}
510

511
int merge_gid_lists(const gid_t *list1, size_t size1, const gid_t *list2, size_t size2, gid_t **ret) {
9,739✔
512
        size_t nresult = 0;
9,739✔
513
        assert(ret);
9,739✔
514

515
        if (size2 > INT_MAX - size1)
9,739✔
516
                return -ENOBUFS;
517

518
        gid_t *buf = new(gid_t, size1 + size2);
9,739✔
519
        if (!buf)
9,739✔
520
                return -ENOMEM;
521

522
        /* Duplicates need to be skipped on merging, otherwise they'll be passed on and stored in the kernel. */
523
        for (size_t i = 0; i < size1; i++)
9,773✔
524
                if (!gid_list_has(buf, nresult, list1[i]))
34✔
525
                        buf[nresult++] = list1[i];
34✔
526
        for (size_t i = 0; i < size2; i++)
10,046✔
527
                if (!gid_list_has(buf, nresult, list2[i]))
307✔
528
                        buf[nresult++] = list2[i];
294✔
529
        *ret = buf;
9,739✔
530
        return (int)nresult;
9,739✔
531
}
532

533
int getgroups_alloc(gid_t **ret) {
467✔
534
        int ngroups = 8;
467✔
535

536
        assert(ret);
467✔
537

538
        for (unsigned attempt = 0;;) {
×
539
                _cleanup_free_ gid_t *p = NULL;
315✔
540

541
                p = new(gid_t, ngroups);
467✔
542
                if (!p)
467✔
543
                        return -ENOMEM;
544

545
                ngroups = getgroups(ngroups, p);
467✔
546
                if (ngroups > 0) {
467✔
547
                        *ret = TAKE_PTR(p);
315✔
548
                        return ngroups;
315✔
549
                }
550
                if (ngroups == 0)
152✔
551
                        break;
552
                if (errno != EINVAL)
×
553
                        return -errno;
×
554

555
                /* Give up eventually */
556
                if (attempt++ > 10)
×
557
                        return -EINVAL;
558

559
                /* Get actual size needed, and size the array explicitly. Note that this is potentially racy
560
                 * to use (in multi-threaded programs), hence let's call this in a loop. */
561
                ngroups = getgroups(0, NULL);
×
562
                if (ngroups < 0)
×
563
                        return -errno;
×
564
                if (ngroups == 0)
×
565
                        break;
566
        }
567

568
        *ret = NULL;
152✔
569
        return 0;
152✔
570
}
571

572
int get_home_dir(char **ret) {
11,382✔
573
        _cleanup_free_ struct passwd *p = NULL;
11,382✔
574
        const char *e;
11,382✔
575
        uid_t u;
11,382✔
576
        int r;
11,382✔
577

578
        assert(ret);
11,382✔
579

580
        /* Take the user specified one */
581
        e = secure_getenv("HOME");
11,382✔
582
        if (e && path_is_valid(e) && path_is_absolute(e))
22,103✔
583
                goto found;
10,719✔
584

585
        /* Hardcode home directory for root and nobody to avoid NSS */
586
        u = getuid();
663✔
587
        if (u == 0) {
663✔
588
                e = "/root";
663✔
589
                goto found;
663✔
590
        }
591
        if (u == UID_NOBODY && synthesize_nobody()) {
×
592
                e = "/";
×
593
                goto found;
×
594
        }
595

596
        /* Check the database... */
597
        r = getpwuid_malloc(u, &p);
×
598
        if (r < 0)
×
599
                return r;
600

601
        e = p->pw_dir;
×
602
        if (!path_is_valid(e) || !path_is_absolute(e))
11,382✔
603
                return -EINVAL;
604

605
 found:
×
606
        return path_simplify_alloc(e, ret);
11,382✔
607
}
608

609
int get_shell(char **ret) {
6✔
610
        _cleanup_free_ struct passwd *p = NULL;
6✔
611
        const char *e;
6✔
612
        uid_t u;
6✔
613
        int r;
6✔
614

615
        assert(ret);
6✔
616

617
        /* Take the user specified one */
618
        e = secure_getenv("SHELL");
6✔
619
        if (e && path_is_valid(e) && path_is_absolute(e))
9✔
620
                goto found;
3✔
621

622
        /* Hardcode shell for root and nobody to avoid NSS */
623
        u = getuid();
3✔
624
        if (u == 0) {
3✔
625
                e = default_root_shell(NULL);
3✔
626
                goto found;
3✔
627
        }
628
        if (u == UID_NOBODY && synthesize_nobody()) {
×
629
                e = NOLOGIN;
×
630
                goto found;
×
631
        }
632

633
        /* Check the database... */
634
        r = getpwuid_malloc(u, &p);
×
635
        if (r < 0)
×
636
                return r;
637

638
        e = p->pw_shell;
×
639
        if (!path_is_valid(e) || !path_is_absolute(e))
6✔
640
                return -EINVAL;
641

642
 found:
×
643
        return path_simplify_alloc(e, ret);
6✔
644
}
645

646
int fully_set_uid_gid(uid_t uid, gid_t gid, const gid_t supplementary_gids[], size_t n_supplementary_gids) {
416✔
647
        int r;
416✔
648

649
        assert(supplementary_gids || n_supplementary_gids == 0);
416✔
650

651
        /* Sets all UIDs and all GIDs to the specified ones. Drops all auxiliary GIDs */
652

653
        r = maybe_setgroups(n_supplementary_gids, supplementary_gids);
416✔
654
        if (r < 0)
416✔
655
                return r;
656

657
        if (gid_is_valid(gid))
416✔
658
                if (setresgid(gid, gid, gid) < 0)
416✔
659
                        return -errno;
×
660

661
        if (uid_is_valid(uid))
416✔
662
                if (setresuid(uid, uid, uid) < 0)
416✔
663
                        return -errno;
×
664

665
        return 0;
666
}
667

668
int take_etc_passwd_lock(const char *root) {
314✔
669
        int r;
314✔
670

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

678
        _cleanup_free_ char *path = path_join(root, ETC_PASSWD_LOCK_PATH);
628✔
679
        if (!path)
314✔
680
                return log_oom_debug();
×
681

682
        (void) mkdir_parents(path, 0755);
314✔
683

684
        _cleanup_close_ int fd = open(path, O_WRONLY|O_CREAT|O_CLOEXEC|O_NOCTTY|O_NOFOLLOW, 0600);
628✔
685
        if (fd < 0)
314✔
686
                return log_debug_errno(errno, "Cannot open %s: %m", path);
×
687

688
        r = unposix_lock(fd, LOCK_EX);
314✔
689
        if (r < 0)
314✔
690
                return log_debug_errno(r, "Locking %s failed: %m", path);
×
691

692
        return TAKE_FD(fd);
693
}
694

695
bool valid_user_group_name(const char *u, ValidUserFlags flags) {
166,529✔
696
        const char *i;
166,529✔
697

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

705
        if (isempty(u)) /* An empty user name is never valid */
166,529✔
706
                return false;
707

708
        if (parse_uid(u, NULL) >= 0) /* Something that parses as numeric UID string is valid exactly when the
166,512✔
709
                                      * flag for it is set */
710
                return FLAGS_SET(flags, VALID_USER_ALLOW_NUMERIC);
127✔
711

712
        if (FLAGS_SET(flags, VALID_USER_RELAX)) {
166,385✔
713

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

725
                if (startswith(u, " ") || endswith(u, " ")) /* At least expect whitespace padding is removed
163,769✔
726
                                                             * at front and back (accept in the middle, since
727
                                                             * that's apparently a thing on Windows). Note
728
                                                             * that this also blocks usernames consisting of
729
                                                             * whitespace only. */
730
                        return false;
731

732
                if (!utf8_is_valid(u)) /* We want to synthesize JSON from this, hence insist on UTF-8 */
163,769✔
733
                        return false;
734

735
                if (string_has_cc(u, NULL)) /* CC characters are just dangerous (and \n in particular is the
163,769✔
736
                                             * record separator in /etc/passwd), so we can't allow that. */
737
                        return false;
738

739
                if (strpbrk(u, ":/")) /* Colons are the field separator in /etc/passwd, we can't allow
163,767✔
740
                                       * that. Slashes are special to file systems paths and user names
741
                                       * typically show up in the file system as home directories, hence
742
                                       * don't allow slashes. */
743
                        return false;
744

745
                if (in_charset(u, "0123456789")) /* Don't allow fully numeric strings, they might be confused
163,758✔
746
                                                  * with UIDs (note that this test is more broad than
747
                                                  * the parse_uid() test above, as it will cover more than
748
                                                  * the 32-bit range, and it will detect 65535 (which is in
749
                                                  * invalid UID, even though in the unsigned 32 bit range) */
750
                        return false;
751

752
                if (u[0] == '-' && in_charset(u + 1, "0123456789")) /* Don't allow negative fully numeric
163,750✔
753
                                                                     * strings either. After all some people
754
                                                                     * write 65535 as -1 (even though that's
755
                                                                     * not even true on 32-bit uid_t
756
                                                                     * anyway) */
757
                        return false;
758

759
                if (dot_or_dot_dot(u)) /* User names typically become home directory names, and these two are
163,748✔
760
                                        * special in that context, don't allow that. */
761
                        return false;
762

763
                /* Compare with strict result and warn if result doesn't match */
764
                if (FLAGS_SET(flags, VALID_USER_WARN) && !valid_user_group_name(u, 0))
163,742✔
765
                        log_struct(LOG_NOTICE,
×
766
                                   LOG_MESSAGE("Accepting user/group name '%s', which does not match strict user/group name rules.", u),
767
                                   LOG_ITEM("USER_GROUP_NAME=%s", u),
768
                                   LOG_MESSAGE_ID(SD_MESSAGE_UNSAFE_USER_NAME_STR));
769

770
                /* Note that we make no restrictions on the length in relaxed mode! */
771
        } else {
772
                long sz;
2,616✔
773
                size_t l;
2,616✔
774

775
                /* Also see POSIX IEEE Std 1003.1-2008, 2016 Edition, 3.437. We are a bit stricter here
776
                 * however. Specifically we deviate from POSIX rules:
777
                 *
778
                 * - We don't allow empty user names (see above)
779
                 * - We require that names fit into the appropriate utmp field
780
                 * - We don't allow any dots (this conflicts with chown syntax which permits dots as user/group name separator)
781
                 * - We don't allow dashes or digit as the first character
782
                 *
783
                 * Note that other systems are even more restrictive, and don't permit underscores or uppercase characters.
784
                 */
785

786
                if (!ascii_isalpha(u[0]) &&
2,616✔
787
                    u[0] != '_')
788
                        return false;
789

790
                for (i = u+1; *i; i++)
21,949✔
791
                        if (!ascii_isalpha(*i) &&
19,376✔
792
                            !ascii_isdigit(*i) &&
1,478✔
793
                            !IN_SET(*i, '_', '-'))
954✔
794
                                return false;
795

796
                l = i - u;
2,573✔
797

798
                sz = sysconf(_SC_LOGIN_NAME_MAX);
2,573✔
799
                assert_se(sz > 0);
2,573✔
800

801
                if (l > (size_t) sz) /* glibc: 256 */
2,573✔
802
                        return false;
803
                if (l > NAME_MAX) /* must fit in a filename: 255 */
2,573✔
804
                        return false;
805
                if (l > sizeof_field(struct utmpx, ut_user) - 1) /* must fit in utmp: 31 */
2,573✔
UNCOV
806
                        return false;
×
807
        }
808

809
        return true;
810
}
811

812
bool valid_gecos(const char *d) {
7,773✔
813

814
        if (!d)
7,773✔
815
                return false;
816

817
        if (!utf8_is_valid(d))
7,772✔
818
                return false;
819

820
        if (string_has_cc(d, NULL))
7,772✔
821
                return false;
822

823
        /* Colons are used as field separators, and hence not OK */
824
        if (strchr(d, ':'))
7,771✔
825
                return false;
1✔
826

827
        return true;
828
}
829

830
char* mangle_gecos(const char *d) {
8✔
831
        char *mangled;
8✔
832

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

837
        mangled = strdup(d);
8✔
838
        if (!mangled)
8✔
839
                return NULL;
840

841
        for (char *i = mangled; *i; i++) {
65✔
842
                int len;
57✔
843

844
                if ((uint8_t) *i < (uint8_t) ' ' || *i == ':') {
57✔
845
                        *i = ' ';
8✔
846
                        continue;
8✔
847
                }
848

849
                len = utf8_encoded_valid_unichar(i, SIZE_MAX);
49✔
850
                if (len < 0) {
49✔
851
                        *i = ' ';
3✔
852
                        continue;
3✔
853
                }
854

855
                i += len - 1;
46✔
856
        }
857

858
        return mangled;
859
}
860

861
bool valid_home(const char *p) {
5,076✔
862
        /* Note that this function is also called by valid_shell(), any
863
         * changes must account for that. */
864

865
        if (isempty(p))
5,076✔
866
                return false;
867

868
        if (!utf8_is_valid(p))
5,072✔
869
                return false;
870

871
        if (string_has_cc(p, NULL))
5,072✔
872
                return false;
873

874
        if (!path_is_absolute(p))
5,070✔
875
                return false;
876

877
        if (!path_is_normalized(p))
5,064✔
878
                return false;
879

880
        /* Colons are used as field separators, and hence not OK */
881
        if (strchr(p, ':'))
5,060✔
882
                return false;
2✔
883

884
        return true;
885
}
886

887
bool valid_shell(const char *p) {
42✔
888
        /* We have the same requirements, so just piggy-back on the home check.
889
         *
890
         * Let's ignore /etc/shells because this is only applicable to real and not system users. It is also
891
         * incompatible with the idea of empty /etc/. */
892
        if (!valid_home(p))
42✔
893
                return false;
894

895
        return !endswith(p, "/"); /* one additional restriction: shells may not be dirs */
33✔
896
}
897

898
int maybe_setgroups(size_t size, const gid_t *list) {
736✔
899
        int r;
736✔
900

901
        /* Check if setgroups is allowed before we try to drop all the auxiliary groups */
902
        if (size == 0) { /* Dropping all aux groups? */
736✔
903
                _cleanup_free_ char *setgroups_content = NULL;
444✔
904
                bool can_setgroups;
444✔
905

906
                r = read_one_line_file("/proc/self/setgroups", &setgroups_content);
444✔
907
                if (r == -ENOENT)
444✔
908
                        /* Old kernels don't have /proc/self/setgroups, so assume we can use setgroups */
909
                        can_setgroups = true;
910
                else if (r < 0)
392✔
911
                        return r;
912
                else
913
                        can_setgroups = streq(setgroups_content, "allow");
392✔
914

915
                if (!can_setgroups) {
392✔
916
                        log_debug("Skipping setgroups(), /proc/self/setgroups is set to 'deny'");
×
917
                        return 0;
×
918
                }
919
        }
920

921
        return RET_NERRNO(setgroups(size, list));
737✔
922
}
923

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

934
        if (cache < 0)
89✔
935
                cache = access("/etc/systemd/dont-synthesize-nobody", F_OK) < 0;
63✔
936

937
        return cache;
89✔
938
}
939

940
int putpwent_sane(const struct passwd *pw, FILE *stream) {
412✔
941
        assert(pw);
412✔
942
        assert(stream);
412✔
943

944
        errno = 0;
412✔
945
        if (putpwent(pw, stream) != 0)
412✔
946
                return errno_or_else(EIO);
×
947

948
        return 0;
949
}
950

951
int putspent_sane(const struct spwd *sp, FILE *stream) {
382✔
952
        assert(sp);
382✔
953
        assert(stream);
382✔
954

955
        errno = 0;
382✔
956
        if (putspent(sp, stream) != 0)
382✔
957
                return errno_or_else(EIO);
×
958

959
        return 0;
960
}
961

962
int putgrent_sane(const struct group *gr, FILE *stream) {
768✔
963
        assert(gr);
768✔
964
        assert(stream);
768✔
965

966
        errno = 0;
768✔
967
        if (putgrent(gr, stream) != 0)
768✔
968
                return errno_or_else(EIO);
×
969

970
        return 0;
971
}
972

973
#if ENABLE_GSHADOW
974
int putsgent_sane(const struct sgrp *sg, FILE *stream) {
743✔
975
        assert(sg);
743✔
976
        assert(stream);
743✔
977

978
        errno = 0;
743✔
979
        if (putsgent(sg, stream) != 0)
743✔
980
                return errno_or_else(EIO);
×
981

982
        return 0;
983
}
984
#endif
985

986
int fgetpwent_sane(FILE *stream, struct passwd **pw) {
1,147✔
987
        assert(stream);
1,147✔
988
        assert(pw);
1,147✔
989

990
        errno = 0;
1,147✔
991
        struct passwd *p = fgetpwent(stream);
1,147✔
992
        if (!p && !IN_SET(errno, 0, ENOENT))
1,147✔
993
                return -errno;
×
994

995
        *pw = p;
1,147✔
996
        return !!p;
1,147✔
997
}
998

999
int fgetspent_sane(FILE *stream, struct spwd **sp) {
220✔
1000
        assert(stream);
220✔
1001
        assert(sp);
220✔
1002

1003
        errno = 0;
220✔
1004
        struct spwd *s = fgetspent(stream);
220✔
1005
        if (!s && !IN_SET(errno, 0, ENOENT))
220✔
1006
                return -errno;
×
1007

1008
        *sp = s;
220✔
1009
        return !!s;
220✔
1010
}
1011

1012
int fgetgrent_sane(FILE *stream, struct group **gr) {
2,299✔
1013
        assert(stream);
2,299✔
1014
        assert(gr);
2,299✔
1015

1016
        errno = 0;
2,299✔
1017
        struct group *g = fgetgrent(stream);
2,299✔
1018
        if (!g && !IN_SET(errno, 0, ENOENT))
2,299✔
1019
                return -errno;
×
1020

1021
        *gr = g;
2,299✔
1022
        return !!g;
2,299✔
1023
}
1024

1025
#if ENABLE_GSHADOW
1026
int fgetsgent_sane(FILE *stream, struct sgrp **sg) {
231✔
1027
        assert(stream);
231✔
1028
        assert(sg);
231✔
1029

1030
        errno = 0;
231✔
1031
        struct sgrp *s = fgetsgent(stream);
231✔
1032
        if (!s && !IN_SET(errno, 0, ENOENT))
231✔
1033
                return -errno;
×
1034

1035
        *sg = s;
231✔
1036
        return !!s;
231✔
1037
}
1038
#endif
1039

1040
int is_this_me(const char *username) {
83✔
1041
        uid_t uid;
83✔
1042
        int r;
83✔
1043

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

1046
        r = get_user_creds(&username, &uid, NULL, NULL, NULL, USER_CREDS_ALLOW_MISSING);
83✔
1047
        if (r < 0)
83✔
1048
                return r;
83✔
1049

1050
        return uid == getuid();
82✔
1051
}
1052

1053
const char* get_home_root(void) {
6,193✔
1054
        const char *e;
6,193✔
1055

1056
        /* For debug purposes allow overriding where we look for home dirs */
1057
        e = secure_getenv("SYSTEMD_HOME_ROOT");
6,193✔
1058
        if (e && path_is_absolute(e) && path_is_normalized(e))
6,193✔
1059
                return e;
×
1060

1061
        return "/home";
1062
}
1063

1064
static size_t getpw_buffer_size(void) {
4,081✔
1065
        long bufsize = sysconf(_SC_GETPW_R_SIZE_MAX);
4,081✔
1066
        return bufsize <= 0 ? 4096U : (size_t) bufsize;
4,081✔
1067
}
1068

1069
static bool errno_is_user_doesnt_exist(int error) {
1✔
1070
        /* See getpwnam(3) and getgrnam(3): those codes and others can be returned if the user or group are
1071
         * not found. */
1072
        return IN_SET(abs(error), ENOENT, ESRCH, EBADF, EPERM);
1✔
1073
}
1074

1075
int getpwnam_malloc(const char *name, struct passwd **ret) {
438✔
1076
        size_t bufsize = getpw_buffer_size();
438✔
1077
        int r;
438✔
1078

1079
        /* A wrapper around getpwnam_r() that allocates the necessary buffer on the heap. The caller must
1080
         * free() the returned structures! */
1081

1082
        if (isempty(name))
876✔
1083
                return -EINVAL;
1084

1085
        for (;;) {
438✔
1086
                _cleanup_free_ void *buf = NULL;
438✔
1087

1088
                buf = malloc0(ALIGN(sizeof(struct passwd)) + bufsize);
438✔
1089
                if (!buf)
438✔
1090
                        return -ENOMEM;
1091

1092
                struct passwd *pw = NULL;
438✔
1093
                r = getpwnam_r(name, buf, (char*) buf + ALIGN(sizeof(struct passwd)), (size_t) bufsize, &pw);
438✔
1094
                if (r == 0) {
438✔
1095
                        if (pw) {
438✔
1096
                                if (ret)
245✔
1097
                                        *ret = TAKE_PTR(buf);
245✔
1098
                                return 0;
245✔
1099
                        }
1100

1101
                        return -ESRCH;
1102
                }
1103

1104
                assert(r > 0);
×
1105

1106
                /* getpwnam() may fail with ENOENT if /etc/passwd is missing.  For us that is equivalent to
1107
                 * the name not being defined. */
1108
                if (errno_is_user_doesnt_exist(r))
×
1109
                        return -ESRCH;
1110
                if (r != ERANGE)
×
1111
                        return -r;
×
1112

1113
                if (bufsize > SIZE_MAX/2 - ALIGN(sizeof(struct passwd)))
×
1114
                        return -ENOMEM;
1115
                bufsize *= 2;
×
1116
        }
1117
}
1118

1119
int getpwuid_malloc(uid_t uid, struct passwd **ret) {
3,643✔
1120
        size_t bufsize = getpw_buffer_size();
3,643✔
1121
        int r;
3,643✔
1122

1123
        if (!uid_is_valid(uid))
3,643✔
1124
                return -EINVAL;
1125

1126
        for (;;) {
3,643✔
1127
                _cleanup_free_ void *buf = NULL;
3,643✔
1128

1129
                buf = malloc0(ALIGN(sizeof(struct passwd)) + bufsize);
3,643✔
1130
                if (!buf)
3,643✔
1131
                        return -ENOMEM;
1132

1133
                struct passwd *pw = NULL;
3,643✔
1134
                r = getpwuid_r(uid, buf, (char*) buf + ALIGN(sizeof(struct passwd)), (size_t) bufsize, &pw);
3,643✔
1135
                if (r == 0) {
3,643✔
1136
                        if (pw) {
3,643✔
1137
                                if (ret)
421✔
1138
                                        *ret = TAKE_PTR(buf);
421✔
1139
                                return 0;
421✔
1140
                        }
1141

1142
                        return -ESRCH;
1143
                }
1144

1145
                assert(r > 0);
×
1146

1147
                if (errno_is_user_doesnt_exist(r))
×
1148
                        return -ESRCH;
1149
                if (r != ERANGE)
×
1150
                        return -r;
×
1151

1152
                if (bufsize > SIZE_MAX/2 - ALIGN(sizeof(struct passwd)))
×
1153
                        return -ENOMEM;
1154
                bufsize *= 2;
×
1155
        }
1156
}
1157

1158
static size_t getgr_buffer_size(void) {
7,515✔
1159
        long bufsize = sysconf(_SC_GETGR_R_SIZE_MAX);
7,515✔
1160
        return bufsize <= 0 ? 4096U : (size_t) bufsize;
7,515✔
1161
}
1162

1163
int getgrnam_malloc(const char *name, struct group **ret) {
1,965✔
1164
        size_t bufsize = getgr_buffer_size();
1,965✔
1165
        int r;
1,965✔
1166

1167
        if (isempty(name))
3,930✔
1168
                return -EINVAL;
1169

1170
        for (;;) {
1,965✔
1171
                _cleanup_free_ void *buf = NULL;
1,965✔
1172

1173
                buf = malloc0(ALIGN(sizeof(struct group)) + bufsize);
1,965✔
1174
                if (!buf)
1,965✔
1175
                        return -ENOMEM;
1176

1177
                struct group *gr = NULL;
1,965✔
1178
                r = getgrnam_r(name, buf, (char*) buf + ALIGN(sizeof(struct group)), (size_t) bufsize, &gr);
1,965✔
1179
                if (r == 0) {
1,965✔
1180
                        if (gr) {
1,964✔
1181
                                if (ret)
1,764✔
1182
                                        *ret = TAKE_PTR(buf);
1,764✔
1183
                                return 0;
1,764✔
1184
                        }
1185

1186
                        return -ESRCH;
1187
                }
1188

1189
                assert(r > 0);
1✔
1190

1191
                if (errno_is_user_doesnt_exist(r))
1✔
1192
                        return -ESRCH;
1193
                if (r != ERANGE)
1✔
1194
                        return -r;
1✔
1195

1196
                if (bufsize > SIZE_MAX/2 - ALIGN(sizeof(struct group)))
×
1197
                        return -ENOMEM;
1198
                bufsize *= 2;
×
1199
        }
1200
}
1201

1202
int getgrgid_malloc(gid_t gid, struct group **ret) {
5,550✔
1203
        size_t bufsize = getgr_buffer_size();
5,550✔
1204
        int r;
5,550✔
1205

1206
        if (!gid_is_valid(gid))
5,550✔
1207
                return -EINVAL;
1208

1209
        for (;;) {
5,550✔
1210
                _cleanup_free_ void *buf = NULL;
5,550✔
1211

1212
                buf = malloc0(ALIGN(sizeof(struct group)) + bufsize);
5,550✔
1213
                if (!buf)
5,550✔
1214
                        return -ENOMEM;
1215

1216
                struct group *gr = NULL;
5,550✔
1217
                r = getgrgid_r(gid, buf, (char*) buf + ALIGN(sizeof(struct group)), (size_t) bufsize, &gr);
5,550✔
1218
                if (r == 0) {
5,550✔
1219
                        if (gr) {
5,550✔
1220
                                if (ret)
2,275✔
1221
                                        *ret = TAKE_PTR(buf);
2,275✔
1222
                                return 0;
2,275✔
1223
                        }
1224

1225
                        return -ESRCH;
1226
                }
1227

1228
                assert(r > 0);
×
1229

1230
                if (errno_is_user_doesnt_exist(r))
×
1231
                        return -ESRCH;
1232
                if (r != ERANGE)
×
1233
                        return -r;
×
1234

1235
                if (bufsize > SIZE_MAX/2 - ALIGN(sizeof(struct group)))
×
1236
                        return -ENOMEM;
1237
                bufsize *= 2;
×
1238
        }
1239
}
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