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

systemd / systemd / 18361374650

08 Oct 2025 08:17PM UTC coverage: 72.225% (-0.05%) from 72.276%
18361374650

push

github

YHNdnzj
man/userdbctl: fixup version info

Follow-up for 466562c69

303310 of 419951 relevant lines covered (72.23%)

1066065.8 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,126,378✔
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,126,378✔
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,660,684✔
41
                return false;
18✔
42

43
        return true;
44
}
45

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

50
        assert(s);
217,973✔
51

52
        assert_cc(sizeof(uid_t) == sizeof(uint32_t));
217,973✔
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
217,973✔
59
                             | SAFE_ATO_REFUSE_PLUS_MINUS
60
                             | SAFE_ATO_REFUSE_LEADING_ZERO
61
                             | SAFE_ATO_REFUSE_LEADING_WHITESPACE, &uid);
62
        if (r < 0)
217,973✔
63
                return r;
217,973✔
64

65
        if (!uid_is_valid(uid))
10,689✔
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,672✔
72
                *ret = uid;
10,184✔
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) {
795✔
130
        const char *e;
795✔
131

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

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

139
bool is_nologin_shell(const char *shell) {
2,538✔
140
        return PATH_IN_SET(shell,
2,538✔
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,987✔
158
        return isempty(shell) || is_nologin_shell(shell);
3,974✔
159
}
160

161
const char* default_root_shell_at(int rfd) {
7,180✔
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,180✔
167

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

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

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

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

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

187
static int synthesize_user_creds(
18,167✔
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,167✔
195
        assert(*username);
18,167✔
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,167✔
201
                *username = "root";
13,964✔
202

203
                if (ret_uid)
13,964✔
204
                        *ret_uid = 0;
6,813✔
205
                if (ret_gid)
13,964✔
206
                        *ret_gid = 0;
8✔
207
                if (ret_home)
13,964✔
208
                        *ret_home = "/root";
7,159✔
209
                if (ret_shell)
13,964✔
210
                        *ret_shell = default_root_shell(NULL);
7,159✔
211

212
                return 0;
13,964✔
213
        }
214

215
        if (STR_IN_SET(*username, NOBODY_USER_NAME, "65534") &&
4,214✔
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,192✔
232
}
233

234
int get_user_creds(
18,654✔
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,654✔
242
        uid_t u = UID_INVALID;
18,654✔
243
        struct passwd *p;
18,654✔
244
        int r;
18,654✔
245

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

250
        if (!FLAGS_SET(flags, USER_CREDS_PREFER_NSS) ||
18,654✔
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,167✔
263
                if (r >= 0)
18,167✔
264
                        return 0;
18,654✔
265
                if (r != -ENOMEDIUM) /* not a username we can synthesize */
4,192✔
266
                        return r;
267
        }
268

269
        if (parse_uid(*username, &u) >= 0) {
4,679✔
270
                errno = 0;
196✔
271
                p = getpwuid(u);
196✔
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)
196✔
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,483✔
291
                p = getpwnam(*username);
4,483✔
292
        }
293
        if (!p) {
4,486✔
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,670✔
307
                return -EBADMSG;
308

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

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

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

318
        if (ret_home)
4,670✔
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,234✔
322
                            ? NULL : p->pw_dir;
5,234✔
323

324
        if (ret_shell)
4,670✔
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,234✔
327
                             ? NULL : p->pw_shell;
5,234✔
328

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

332
        return 0;
333
}
334

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

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

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

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

348
                return 0;
5,103✔
349
        }
350

351
        if (STR_IN_SET(*groupname, NOBODY_GROUP_NAME, "65534") &&
4,921✔
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,913✔
362
}
363

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

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

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

381
        if (parse_gid(*groupname, &id) >= 0) {
4,913✔
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,913✔
395
                g = getgrnam(*groupname);
4,913✔
396
        }
397

398
        if (!g) {
4,913✔
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,912✔
411
                if (!gid_is_valid(g->gr_gid))
4,912✔
412
                        return -EBADMSG;
413

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

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

420
        return 0;
421
}
422

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

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

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

436
                r = getpwuid_malloc(uid, &pw);
199✔
437
                if (r >= 0)
199✔
438
                        return strdup(pw->pw_name);
199✔
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) {
385✔
471
        assert(list || size == 0);
385✔
472

473
        FOREACH_ARRAY(i, list, size)
550✔
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,825✔
512
        size_t nresult = 0;
9,825✔
513
        assert(ret);
9,825✔
514

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

518
        gid_t *buf = new(gid_t, size1 + size2);
9,825✔
519
        if (!buf)
9,825✔
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,859✔
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,139✔
527
                if (!gid_list_has(buf, nresult, list2[i]))
314✔
528
                        buf[nresult++] = list2[i];
301✔
529
        *ret = buf;
9,825✔
530
        return (int)nresult;
9,825✔
531
}
532

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

536
        assert(ret);
477✔
537

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

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

545
                ngroups = getgroups(ngroups, p);
477✔
546
                if (ngroups > 0) {
477✔
547
                        *ret = TAKE_PTR(p);
322✔
548
                        return ngroups;
322✔
549
                }
550
                if (ngroups == 0)
155✔
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;
155✔
569
        return 0;
155✔
570
}
571

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

578
        assert(ret);
11,879✔
579

580
        /* Take the user specified one */
581
        e = secure_getenv("HOME");
11,879✔
582
        if (e && path_is_valid(e) && path_is_absolute(e))
23,097✔
583
                goto found;
11,216✔
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,879✔
603
                return -EINVAL;
604

605
 found:
×
606
        return path_simplify_alloc(e, ret);
11,879✔
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) {
435✔
647
        int r;
435✔
648

649
        assert(supplementary_gids || n_supplementary_gids == 0);
435✔
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);
435✔
654
        if (r < 0)
435✔
655
                return r;
656

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

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

665
        return 0;
666
}
667

668
int take_etc_passwd_lock(const char *root) {
320✔
669
        int r;
320✔
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);
640✔
679
        if (!path)
320✔
680
                return log_oom_debug();
×
681

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

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

688
        r = unposix_lock(fd, LOCK_EX);
320✔
689
        if (r < 0)
320✔
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) {
177,153✔
696
        const char *i;
177,153✔
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 */
177,153✔
706
                return false;
707

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

712
        if (FLAGS_SET(flags, VALID_USER_RELAX)) {
177,012✔
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
174,298✔
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 */
174,298✔
733
                        return false;
734

735
                if (string_has_cc(u, NULL)) /* CC characters are just dangerous (and \n in particular is the
174,298✔
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
174,296✔
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
174,287✔
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
174,279✔
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
174,277✔
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))
174,271✔
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,714✔
773
                size_t l;
2,714✔
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,714✔
787
                    u[0] != '_')
788
                        return false;
789

790
                for (i = u+1; *i; i++)
22,617✔
791
                        if (!ascii_isalpha(*i) &&
19,946✔
792
                            !ascii_isdigit(*i) &&
1,278✔
793
                            !IN_SET(*i, '_', '-'))
920✔
794
                                return false;
795

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

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

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

809
        return true;
810
}
811

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

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

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

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

823
        /* Colons are used as field separators, and hence not OK */
824
        if (strchr(d, ':'))
7,930✔
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,280✔
862
        /* Note that this function is also called by valid_shell(), any
863
         * changes must account for that. */
864

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

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

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

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

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

880
        /* Colons are used as field separators, and hence not OK */
881
        if (strchr(p, ':'))
5,264✔
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) {
758✔
899
        int r;
758✔
900

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

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

915
                if (!can_setgroups) {
403✔
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));
759✔
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;
64✔
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,146✔
987
        assert(stream);
1,146✔
988
        assert(pw);
1,146✔
989

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

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

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

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

1008
        *sp = s;
219✔
1009
        return !!s;
219✔
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) {
7,006✔
1054
        const char *e;
7,006✔
1055

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

1061
        return "/home";
1062
}
1063

1064
static size_t getpw_buffer_size(void) {
4,125✔
1065
        long bufsize = sysconf(_SC_GETPW_R_SIZE_MAX);
4,125✔
1066
        return bufsize <= 0 ? 4096U : (size_t) bufsize;
4,125✔
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) {
473✔
1076
        size_t bufsize = getpw_buffer_size();
473✔
1077
        int r;
473✔
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))
946✔
1083
                return -EINVAL;
1084

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

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

1092
                struct passwd *pw = NULL;
473✔
1093
                r = getpwnam_r(name, buf, (char*) buf + ALIGN(sizeof(struct passwd)), (size_t) bufsize, &pw);
473✔
1094
                if (r == 0) {
473✔
1095
                        if (pw) {
473✔
1096
                                if (ret)
281✔
1097
                                        *ret = TAKE_PTR(buf);
281✔
1098
                                return 0;
281✔
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,652✔
1120
        size_t bufsize = getpw_buffer_size();
3,652✔
1121
        int r;
3,652✔
1122

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

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

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

1133
                struct passwd *pw = NULL;
3,652✔
1134
                r = getpwuid_r(uid, buf, (char*) buf + ALIGN(sizeof(struct passwd)), (size_t) bufsize, &pw);
3,652✔
1135
                if (r == 0) {
3,652✔
1136
                        if (pw) {
3,652✔
1137
                                if (ret)
428✔
1138
                                        *ret = TAKE_PTR(buf);
428✔
1139
                                return 0;
428✔
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,845✔
1159
        long bufsize = sysconf(_SC_GETGR_R_SIZE_MAX);
7,845✔
1160
        return bufsize <= 0 ? 4096U : (size_t) bufsize;
7,845✔
1161
}
1162

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

1167
        if (isempty(name))
4,452✔
1168
                return -EINVAL;
1169

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

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

1177
                struct group *gr = NULL;
2,226✔
1178
                r = getgrnam_r(name, buf, (char*) buf + ALIGN(sizeof(struct group)), (size_t) bufsize, &gr);
2,226✔
1179
                if (r == 0) {
2,226✔
1180
                        if (gr) {
2,225✔
1181
                                if (ret)
2,026✔
1182
                                        *ret = TAKE_PTR(buf);
2,026✔
1183
                                return 0;
2,026✔
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,619✔
1203
        size_t bufsize = getgr_buffer_size();
5,619✔
1204
        int r;
5,619✔
1205

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

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

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

1216
                struct group *gr = NULL;
5,619✔
1217
                r = getgrgid_r(gid, buf, (char*) buf + ALIGN(sizeof(struct group)), (size_t) bufsize, &gr);
5,619✔
1218
                if (r == 0) {
5,619✔
1219
                        if (gr) {
5,619✔
1220
                                if (ret)
2,353✔
1221
                                        *ret = TAKE_PTR(buf);
2,353✔
1222
                                return 0;
2,353✔
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