Coverage Report

Created: 2022-04-19 08:24

/src/systemd/src/shared/user-record-nss.c
Line
Count
Source (jump to first uncovered line)
1
/* SPDX-License-Identifier: LGPL-2.1-or-later */
2
3
#include "errno-util.h"
4
#include "format-util.h"
5
#include "libcrypt-util.h"
6
#include "strv.h"
7
#include "user-record-nss.h"
8
#include "user-util.h"
9
#include "utf8.h"
10
11
#define SET_IF(field, condition, value, fallback)  \
12
0
        field = (condition) ? (value) : (fallback)
13
14
0
static inline const char* utf8_only(const char *s) {
15
0
        return s && utf8_is_valid(s) ? s : NULL;
16
0
}
17
18
0
static inline int strv_extend_strv_utf8_only(char ***dst, char **src, bool filter_duplicates) {
19
0
        _cleanup_free_ char **t = NULL;
20
0
        size_t l, j = 0;
21
22
        /* First, do a shallow copy of s, filtering for only valid utf-8 strings */
23
0
        l = strv_length(src);
24
0
        t = new(char*, l + 1);
25
0
        if (!t)
26
0
                return -ENOMEM;
27
28
0
        for (size_t i = 0; i < l; i++)
29
0
                if (utf8_is_valid(src[i]))
30
0
                        t[j++] = src[i];
31
0
        if (j == 0)
32
0
                return 0;
33
34
0
        t[j] = NULL;
35
0
        return strv_extend_strv(dst, t, filter_duplicates);
36
0
}
37
38
int nss_passwd_to_user_record(
39
                const struct passwd *pwd,
40
                const struct spwd *spwd,
41
0
                UserRecord **ret) {
42
43
0
        _cleanup_(user_record_unrefp) UserRecord *hr = NULL;
44
0
        int r;
45
46
0
        assert(pwd);
47
0
        assert(ret);
48
49
0
        if (isempty(pwd->pw_name))
50
0
                return -EINVAL;
51
52
0
        if (spwd && !streq_ptr(spwd->sp_namp, pwd->pw_name))
53
0
                return -EINVAL;
54
55
0
        hr = user_record_new();
56
0
        if (!hr)
57
0
                return -ENOMEM;
58
59
0
        r = free_and_strdup(&hr->user_name, pwd->pw_name);
60
0
        if (r < 0)
61
0
                return r;
62
63
        /* Some bad NSS modules synthesize GECOS fields with embedded ":" or "\n" characters, which are not
64
         * something we can output in /etc/passwd compatible format, since these are record separators
65
         * there. We normally refuse that, but we need to maintain compatibility with arbitrary NSS modules,
66
         * hence let's do what glibc does: mangle the data to fit the format. */
67
0
        if (isempty(pwd->pw_gecos) || streq_ptr(pwd->pw_gecos, hr->user_name))
68
0
                hr->real_name = mfree(hr->real_name);
69
0
        else if (valid_gecos(pwd->pw_gecos)) {
70
0
                r = free_and_strdup(&hr->real_name, pwd->pw_gecos);
71
0
                if (r < 0)
72
0
                        return r;
73
0
        } else {
74
0
                _cleanup_free_ char *mangled = NULL;
75
76
0
                mangled = mangle_gecos(pwd->pw_gecos);
77
0
                if (!mangled)
78
0
                        return -ENOMEM;
79
80
0
                free_and_replace(hr->real_name, mangled);
81
0
        }
82
83
0
        r = free_and_strdup(&hr->home_directory, utf8_only(empty_to_null(pwd->pw_dir)));
84
0
        if (r < 0)
85
0
                return r;
86
87
0
        r = free_and_strdup(&hr->shell, utf8_only(empty_to_null(pwd->pw_shell)));
88
0
        if (r < 0)
89
0
                return r;
90
91
0
        hr->uid = pwd->pw_uid;
92
0
        hr->gid = pwd->pw_gid;
93
94
0
        if (spwd &&
95
0
            looks_like_hashed_password(utf8_only(spwd->sp_pwdp))) { /* Ignore locked, disabled, and mojibake passwords */
96
0
                strv_free_erase(hr->hashed_password);
97
0
                hr->hashed_password = strv_new(spwd->sp_pwdp);
98
0
                if (!hr->hashed_password)
99
0
                        return -ENOMEM;
100
0
        } else
101
0
                hr->hashed_password = strv_free_erase(hr->hashed_password);
102
103
        /* shadow-utils suggests using "chage -E 0" (or -E 1, depending on which man page you check)
104
         * for locking a whole account, hence check for that. Note that it also defines a way to lock
105
         * just a password instead of the whole account, but that's mostly pointless in times of
106
         * password-less authorization, hence let's not bother. */
107
108
0
         SET_IF(hr->locked,
109
0
                spwd && spwd->sp_expire >= 0,
110
0
                spwd->sp_expire <= 1, -1);
111
112
0
         SET_IF(hr->not_after_usec,
113
0
                spwd && spwd->sp_expire > 1 && (uint64_t) spwd->sp_expire < (UINT64_MAX-1)/USEC_PER_DAY,
114
0
                spwd->sp_expire * USEC_PER_DAY, UINT64_MAX);
115
116
0
         SET_IF(hr->password_change_now,
117
0
                spwd && spwd->sp_lstchg >= 0,
118
0
                spwd->sp_lstchg == 0, -1);
119
120
0
         SET_IF(hr->last_password_change_usec,
121
0
                spwd && spwd->sp_lstchg > 0 && (uint64_t) spwd->sp_lstchg <= (UINT64_MAX-1)/USEC_PER_DAY,
122
0
                spwd->sp_lstchg * USEC_PER_DAY, UINT64_MAX);
123
124
0
         SET_IF(hr->password_change_min_usec,
125
0
                spwd && spwd->sp_min > 0 && (uint64_t) spwd->sp_min <= (UINT64_MAX-1)/USEC_PER_DAY,
126
0
                spwd->sp_min * USEC_PER_DAY, UINT64_MAX);
127
128
0
         SET_IF(hr->password_change_max_usec,
129
0
                spwd && spwd->sp_max > 0 && (uint64_t) spwd->sp_max <= (UINT64_MAX-1)/USEC_PER_DAY,
130
0
                spwd->sp_max * USEC_PER_DAY, UINT64_MAX);
131
132
0
         SET_IF(hr->password_change_warn_usec,
133
0
                spwd && spwd->sp_warn > 0 && (uint64_t) spwd->sp_warn <= (UINT64_MAX-1)/USEC_PER_DAY,
134
0
                spwd->sp_warn * USEC_PER_DAY, UINT64_MAX);
135
136
0
         SET_IF(hr->password_change_inactive_usec,
137
0
                spwd && spwd->sp_inact > 0 && (uint64_t) spwd->sp_inact <= (UINT64_MAX-1)/USEC_PER_DAY,
138
0
                spwd->sp_inact * USEC_PER_DAY, UINT64_MAX);
139
140
0
        hr->json = json_variant_unref(hr->json);
141
0
        r = json_build(&hr->json, JSON_BUILD_OBJECT(
142
0
                                       JSON_BUILD_PAIR("userName", JSON_BUILD_STRING(hr->user_name)),
143
0
                                       JSON_BUILD_PAIR("uid", JSON_BUILD_UNSIGNED(hr->uid)),
144
0
                                       JSON_BUILD_PAIR("gid", JSON_BUILD_UNSIGNED(hr->gid)),
145
0
                                       JSON_BUILD_PAIR_CONDITION(hr->real_name, "realName", JSON_BUILD_STRING(hr->real_name)),
146
0
                                       JSON_BUILD_PAIR_CONDITION(hr->home_directory, "homeDirectory", JSON_BUILD_STRING(hr->home_directory)),
147
0
                                       JSON_BUILD_PAIR_CONDITION(hr->shell, "shell", JSON_BUILD_STRING(hr->shell)),
148
0
                                       JSON_BUILD_PAIR_CONDITION(!strv_isempty(hr->hashed_password), "privileged", JSON_BUILD_OBJECT(JSON_BUILD_PAIR("hashedPassword", JSON_BUILD_STRV(hr->hashed_password)))),
149
0
                                       JSON_BUILD_PAIR_CONDITION(hr->locked >= 0, "locked", JSON_BUILD_BOOLEAN(hr->locked)),
150
0
                                       JSON_BUILD_PAIR_CONDITION(hr->not_after_usec != UINT64_MAX, "notAfterUSec", JSON_BUILD_UNSIGNED(hr->not_after_usec)),
151
0
                                       JSON_BUILD_PAIR_CONDITION(hr->password_change_now >= 0, "passwordChangeNow", JSON_BUILD_BOOLEAN(hr->password_change_now)),
152
0
                                       JSON_BUILD_PAIR_CONDITION(hr->last_password_change_usec != UINT64_MAX, "lastPasswordChangeUSec", JSON_BUILD_UNSIGNED(hr->last_password_change_usec)),
153
0
                                       JSON_BUILD_PAIR_CONDITION(hr->password_change_min_usec != UINT64_MAX, "passwordChangeMinUSec", JSON_BUILD_UNSIGNED(hr->password_change_min_usec)),
154
0
                                       JSON_BUILD_PAIR_CONDITION(hr->password_change_max_usec != UINT64_MAX, "passwordChangeMaxUSec", JSON_BUILD_UNSIGNED(hr->password_change_max_usec)),
155
0
                                       JSON_BUILD_PAIR_CONDITION(hr->password_change_warn_usec != UINT64_MAX, "passwordChangeWarnUSec", JSON_BUILD_UNSIGNED(hr->password_change_warn_usec)),
156
0
                                       JSON_BUILD_PAIR_CONDITION(hr->password_change_inactive_usec != UINT64_MAX, "passwordChangeInactiveUSec", JSON_BUILD_UNSIGNED(hr->password_change_inactive_usec))));
157
158
0
        if (r < 0)
159
0
                return r;
160
161
0
        hr->mask = USER_RECORD_REGULAR |
162
0
                (!strv_isempty(hr->hashed_password) ? USER_RECORD_PRIVILEGED : 0);
163
164
0
        *ret = TAKE_PTR(hr);
165
0
        return 0;
166
0
}
167
168
0
int nss_spwd_for_passwd(const struct passwd *pwd, struct spwd *ret_spwd, char **ret_buffer) {
169
0
        size_t buflen = 4096;
170
0
        int r;
171
172
0
        assert(pwd);
173
0
        assert(ret_spwd);
174
0
        assert(ret_buffer);
175
176
0
        for (;;) {
177
0
                _cleanup_free_ char *buf = NULL;
178
0
                struct spwd spwd, *result;
179
180
0
                buf = malloc(buflen);
181
0
                if (!buf)
182
0
                        return -ENOMEM;
183
184
0
                r = getspnam_r(pwd->pw_name, &spwd, buf, buflen, &result);
185
0
                if (r == 0) {
186
0
                        if (!result)
187
0
                                return -ESRCH;
188
189
0
                        *ret_spwd = *result;
190
0
                        *ret_buffer = TAKE_PTR(buf);
191
0
                        return 0;
192
0
                }
193
0
                if (r < 0)
194
0
                        return -EIO; /* Weird, this should not return negative! */
195
0
                if (r != ERANGE)
196
0
                        return -r;
197
198
0
                if (buflen > SIZE_MAX / 2)
199
0
                        return -ERANGE;
200
201
0
                buflen *= 2;
202
0
                buf = mfree(buf);
203
0
        }
204
0
}
205
206
int nss_user_record_by_name(
207
                const char *name,
208
                bool with_shadow,
209
0
                UserRecord **ret) {
210
211
0
        _cleanup_free_ char *buf = NULL, *sbuf = NULL;
212
0
        struct passwd pwd, *result;
213
0
        bool incomplete = false;
214
0
        size_t buflen = 4096;
215
0
        struct spwd spwd, *sresult = NULL;
216
0
        int r;
217
218
0
        assert(name);
219
0
        assert(ret);
220
221
0
        for (;;) {
222
0
                buf = malloc(buflen);
223
0
                if (!buf)
224
0
                        return -ENOMEM;
225
226
0
                r = getpwnam_r(name, &pwd, buf, buflen, &result);
227
0
                if (r == 0)  {
228
0
                        if (!result)
229
0
                                return -ESRCH;
230
231
0
                        break;
232
0
                }
233
234
0
                if (r < 0)
235
0
                        return log_debug_errno(SYNTHETIC_ERRNO(EIO), "getpwnam_r() returned a negative value");
236
0
                if (r != ERANGE)
237
0
                        return -r;
238
239
0
                if (buflen > SIZE_MAX / 2)
240
0
                        return -ERANGE;
241
242
0
                buflen *= 2;
243
0
                buf = mfree(buf);
244
0
        }
245
246
0
        if (with_shadow) {
247
0
                r = nss_spwd_for_passwd(result, &spwd, &sbuf);
248
0
                if (r < 0) {
249
0
                        log_debug_errno(r, "Failed to do shadow lookup for user %s, ignoring: %m", name);
250
0
                        incomplete = ERRNO_IS_PRIVILEGE(r);
251
0
                } else
252
0
                        sresult = &spwd;
253
0
        } else
254
0
                incomplete = true;
255
256
0
        r = nss_passwd_to_user_record(result, sresult, ret);
257
0
        if (r < 0)
258
0
                return r;
259
260
0
        (*ret)->incomplete = incomplete;
261
0
        return 0;
262
0
}
263
264
int nss_user_record_by_uid(
265
                uid_t uid,
266
                bool with_shadow,
267
0
                UserRecord **ret) {
268
269
0
        _cleanup_free_ char *buf = NULL, *sbuf = NULL;
270
0
        struct passwd pwd, *result;
271
0
        bool incomplete = false;
272
0
        size_t buflen = 4096;
273
0
        struct spwd spwd, *sresult = NULL;
274
0
        int r;
275
276
0
        assert(ret);
277
278
0
        for (;;) {
279
0
                buf = malloc(buflen);
280
0
                if (!buf)
281
0
                        return -ENOMEM;
282
283
0
                r = getpwuid_r(uid, &pwd, buf, buflen, &result);
284
0
                if (r == 0)  {
285
0
                        if (!result)
286
0
                                return -ESRCH;
287
288
0
                        break;
289
0
                }
290
0
                if (r < 0)
291
0
                        return log_debug_errno(SYNTHETIC_ERRNO(EIO), "getpwuid_r() returned a negative value");
292
0
                if (r != ERANGE)
293
0
                        return -r;
294
295
0
                if (buflen > SIZE_MAX / 2)
296
0
                        return -ERANGE;
297
298
0
                buflen *= 2;
299
0
                buf = mfree(buf);
300
0
        }
301
302
0
        if (with_shadow)  {
303
0
                r = nss_spwd_for_passwd(result, &spwd, &sbuf);
304
0
                if (r < 0) {
305
0
                        log_debug_errno(r, "Failed to do shadow lookup for UID " UID_FMT ", ignoring: %m", uid);
306
0
                        incomplete = ERRNO_IS_PRIVILEGE(r);
307
0
                } else
308
0
                        sresult = &spwd;
309
0
        } else
310
0
                incomplete = true;
311
312
0
        r = nss_passwd_to_user_record(result, sresult, ret);
313
0
        if (r < 0)
314
0
                return r;
315
316
0
        (*ret)->incomplete = incomplete;
317
0
        return 0;
318
0
}
319
320
int nss_group_to_group_record(
321
                const struct group *grp,
322
                const struct sgrp *sgrp,
323
0
                GroupRecord **ret) {
324
325
0
        _cleanup_(group_record_unrefp) GroupRecord *g = NULL;
326
0
        int r;
327
328
0
        assert(grp);
329
0
        assert(ret);
330
331
0
        if (isempty(grp->gr_name))
332
0
                return -EINVAL;
333
334
0
        if (sgrp && !streq_ptr(sgrp->sg_namp, grp->gr_name))
335
0
                return -EINVAL;
336
337
0
        g = group_record_new();
338
0
        if (!g)
339
0
                return -ENOMEM;
340
341
0
        g->group_name = strdup(grp->gr_name);
342
0
        if (!g->group_name)
343
0
                return -ENOMEM;
344
345
0
        r = strv_extend_strv_utf8_only(&g->members, grp->gr_mem, false);
346
0
        if (r < 0)
347
0
                return r;
348
349
0
        g->gid = grp->gr_gid;
350
351
0
        if (sgrp) {
352
0
                if (looks_like_hashed_password(utf8_only(sgrp->sg_passwd))) {
353
0
                        g->hashed_password = strv_new(sgrp->sg_passwd);
354
0
                        if (!g->hashed_password)
355
0
                                return -ENOMEM;
356
0
                }
357
358
0
                r = strv_extend_strv_utf8_only(&g->members, sgrp->sg_mem, true);
359
0
                if (r < 0)
360
0
                        return r;
361
362
0
                r = strv_extend_strv_utf8_only(&g->administrators, sgrp->sg_adm, false);
363
0
                if (r < 0)
364
0
                        return r;
365
0
        }
366
367
0
        r = json_build(&g->json, JSON_BUILD_OBJECT(
368
0
                                       JSON_BUILD_PAIR("groupName", JSON_BUILD_STRING(g->group_name)),
369
0
                                       JSON_BUILD_PAIR("gid", JSON_BUILD_UNSIGNED(g->gid)),
370
0
                                       JSON_BUILD_PAIR_CONDITION(!strv_isempty(g->members), "members", JSON_BUILD_STRV(g->members)),
371
0
                                       JSON_BUILD_PAIR_CONDITION(!strv_isempty(g->hashed_password), "privileged", JSON_BUILD_OBJECT(JSON_BUILD_PAIR("hashedPassword", JSON_BUILD_STRV(g->hashed_password)))),
372
0
                                       JSON_BUILD_PAIR_CONDITION(!strv_isempty(g->administrators), "administrators", JSON_BUILD_STRV(g->administrators))));
373
0
        if (r < 0)
374
0
                return r;
375
376
0
        g->mask = USER_RECORD_REGULAR |
377
0
                (!strv_isempty(g->hashed_password) ? USER_RECORD_PRIVILEGED : 0);
378
379
0
        *ret = TAKE_PTR(g);
380
0
        return 0;
381
0
}
382
383
0
int nss_sgrp_for_group(const struct group *grp, struct sgrp *ret_sgrp, char **ret_buffer) {
384
0
        size_t buflen = 4096;
385
0
        int r;
386
387
0
        assert(grp);
388
0
        assert(ret_sgrp);
389
0
        assert(ret_buffer);
390
391
0
        for (;;) {
392
0
                _cleanup_free_ char *buf = NULL;
393
0
                struct sgrp sgrp, *result;
394
395
0
                buf = malloc(buflen);
396
0
                if (!buf)
397
0
                        return -ENOMEM;
398
399
0
                r = getsgnam_r(grp->gr_name, &sgrp, buf, buflen, &result);
400
0
                if (r == 0) {
401
0
                        if (!result)
402
0
                                return -ESRCH;
403
404
0
                        *ret_sgrp = *result;
405
0
                        *ret_buffer = TAKE_PTR(buf);
406
0
                        return 0;
407
0
                }
408
0
                if (r < 0)
409
0
                        return -EIO; /* Weird, this should not return negative! */
410
0
                if (r != ERANGE)
411
0
                        return -r;
412
413
0
                if (buflen > SIZE_MAX / 2)
414
0
                        return -ERANGE;
415
416
0
                buflen *= 2;
417
0
                buf = mfree(buf);
418
0
        }
419
0
}
420
421
int nss_group_record_by_name(
422
                const char *name,
423
                bool with_shadow,
424
0
                GroupRecord **ret) {
425
426
0
        _cleanup_free_ char *buf = NULL, *sbuf = NULL;
427
0
        struct group grp, *result;
428
0
        bool incomplete = false;
429
0
        size_t buflen = 4096;
430
0
        struct sgrp sgrp, *sresult = NULL;
431
0
        int r;
432
433
0
        assert(name);
434
0
        assert(ret);
435
436
0
        for (;;) {
437
0
                buf = malloc(buflen);
438
0
                if (!buf)
439
0
                        return -ENOMEM;
440
441
0
                r = getgrnam_r(name, &grp, buf, buflen, &result);
442
0
                if (r == 0)  {
443
0
                        if (!result)
444
0
                                return -ESRCH;
445
446
0
                        break;
447
0
                }
448
449
0
                if (r < 0)
450
0
                        return log_debug_errno(SYNTHETIC_ERRNO(EIO), "getgrnam_r() returned a negative value");
451
0
                if (r != ERANGE)
452
0
                        return -r;
453
0
                if (buflen > SIZE_MAX / 2)
454
0
                        return -ERANGE;
455
456
0
                buflen *= 2;
457
0
                buf = mfree(buf);
458
0
        }
459
460
0
        if (with_shadow) {
461
0
                r = nss_sgrp_for_group(result, &sgrp, &sbuf);
462
0
                if (r < 0) {
463
0
                        log_debug_errno(r, "Failed to do shadow lookup for group %s, ignoring: %m", result->gr_name);
464
0
                        incomplete = ERRNO_IS_PRIVILEGE(r);
465
0
                } else
466
0
                        sresult = &sgrp;
467
0
        } else
468
0
                incomplete = true;
469
470
0
        r = nss_group_to_group_record(result, sresult, ret);
471
0
        if (r < 0)
472
0
                return r;
473
474
0
        (*ret)->incomplete = incomplete;
475
0
        return 0;
476
0
}
477
478
int nss_group_record_by_gid(
479
                gid_t gid,
480
                bool with_shadow,
481
0
                GroupRecord **ret) {
482
483
0
        _cleanup_free_ char *buf = NULL, *sbuf = NULL;
484
0
        struct group grp, *result;
485
0
        bool incomplete = false;
486
0
        size_t buflen = 4096;
487
0
        struct sgrp sgrp, *sresult = NULL;
488
0
        int r;
489
490
0
        assert(ret);
491
492
0
        for (;;) {
493
0
                buf = malloc(buflen);
494
0
                if (!buf)
495
0
                        return -ENOMEM;
496
497
0
                r = getgrgid_r(gid, &grp, buf, buflen, &result);
498
0
                if (r == 0)  {
499
0
                        if (!result)
500
0
                                return -ESRCH;
501
0
                        break;
502
0
                }
503
504
0
                if (r < 0)
505
0
                        return log_debug_errno(SYNTHETIC_ERRNO(EIO), "getgrgid_r() returned a negative value");
506
0
                if (r != ERANGE)
507
0
                        return -r;
508
0
                if (buflen > SIZE_MAX / 2)
509
0
                        return -ERANGE;
510
511
0
                buflen *= 2;
512
0
                buf = mfree(buf);
513
0
        }
514
515
0
        if (with_shadow) {
516
0
                r = nss_sgrp_for_group(result, &sgrp, &sbuf);
517
0
                if (r < 0) {
518
0
                        log_debug_errno(r, "Failed to do shadow lookup for group %s, ignoring: %m", result->gr_name);
519
0
                        incomplete = ERRNO_IS_PRIVILEGE(r);
520
0
                } else
521
0
                        sresult = &sgrp;
522
0
        } else
523
0
                incomplete = true;
524
525
0
        r = nss_group_to_group_record(result, sresult, ret);
526
0
        if (r < 0)
527
0
                return r;
528
529
0
        (*ret)->incomplete = incomplete;
530
0
        return 0;
531
0
}