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