1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
2 
3 #include "env-util.h"
4 #include "fd-util.h"
5 #include "nss-systemd.h"
6 #include "strv.h"
7 #include "user-record-nss.h"
8 #include "user-record.h"
9 #include "user-util.h"
10 #include "userdb-glue.h"
11 #include "userdb.h"
12 
nss_glue_userdb_flags(void)13 UserDBFlags nss_glue_userdb_flags(void) {
14         UserDBFlags flags = USERDB_EXCLUDE_NSS;
15 
16         /* Make sure that we don't go in circles when allocating a dynamic UID by checking our own database */
17         if (getenv_bool_secure("SYSTEMD_NSS_DYNAMIC_BYPASS") > 0)
18                 flags |= USERDB_EXCLUDE_DYNAMIC_USER;
19 
20         return flags;
21 }
22 
nss_pack_user_record(UserRecord * hr,struct passwd * pwd,char * buffer,size_t buflen)23 int nss_pack_user_record(
24                 UserRecord *hr,
25                 struct passwd *pwd,
26                 char *buffer,
27                 size_t buflen) {
28 
29         const char *rn, *hd, *shell;
30         size_t required;
31 
32         assert(hr);
33         assert(pwd);
34 
35         assert(hr->user_name);
36         required = strlen(hr->user_name) + 1;
37 
38         required += 2; /* strlen(PASSWORD_SEE_SHADOW) + 1 */
39 
40         assert_se(rn = user_record_real_name(hr));
41         required += strlen(rn) + 1;
42 
43         assert_se(hd = user_record_home_directory(hr));
44         required += strlen(hd) + 1;
45 
46         assert_se(shell = user_record_shell(hr));
47         required += strlen(shell) + 1;
48 
49         if (buflen < required)
50                 return -ERANGE;
51 
52         *pwd = (struct passwd) {
53                 .pw_name = buffer,
54                 .pw_uid = hr->uid,
55                 .pw_gid = user_record_gid(hr),
56         };
57 
58         assert(buffer);
59 
60         pwd->pw_passwd = stpcpy(pwd->pw_name, hr->user_name) + 1;
61         pwd->pw_gecos = stpcpy(pwd->pw_passwd, PASSWORD_SEE_SHADOW) + 1;
62         pwd->pw_dir = stpcpy(pwd->pw_gecos, rn) + 1;
63         pwd->pw_shell = stpcpy(pwd->pw_dir, hd) + 1;
64         strcpy(pwd->pw_shell, shell);
65 
66         return 0;
67 }
68 
userdb_getpwnam(const char * name,struct passwd * pwd,char * buffer,size_t buflen,int * errnop)69 enum nss_status userdb_getpwnam(
70                 const char *name,
71                 struct passwd *pwd,
72                 char *buffer, size_t buflen,
73                 int *errnop) {
74 
75         _cleanup_(user_record_unrefp) UserRecord *hr = NULL;
76         int r;
77 
78         assert(pwd);
79         assert(errnop);
80 
81         if (_nss_systemd_is_blocked())
82                 return NSS_STATUS_NOTFOUND;
83 
84         r = userdb_by_name(name, nss_glue_userdb_flags()|USERDB_SUPPRESS_SHADOW, &hr);
85         if (r == -ESRCH)
86                 return NSS_STATUS_NOTFOUND;
87         if (r < 0) {
88                 *errnop = -r;
89                 return NSS_STATUS_UNAVAIL;
90         }
91 
92         r = nss_pack_user_record(hr, pwd, buffer, buflen);
93         if (r < 0) {
94                 *errnop = -r;
95                 return NSS_STATUS_TRYAGAIN;
96         }
97 
98         return NSS_STATUS_SUCCESS;
99 }
100 
userdb_getpwuid(uid_t uid,struct passwd * pwd,char * buffer,size_t buflen,int * errnop)101 enum nss_status userdb_getpwuid(
102                 uid_t uid,
103                 struct passwd *pwd,
104                 char *buffer,
105                 size_t buflen,
106                 int *errnop) {
107 
108         _cleanup_(user_record_unrefp) UserRecord *hr = NULL;
109         int r;
110 
111         assert(pwd);
112         assert(errnop);
113 
114         if (_nss_systemd_is_blocked())
115                 return NSS_STATUS_NOTFOUND;
116 
117         r = userdb_by_uid(uid, nss_glue_userdb_flags()|USERDB_SUPPRESS_SHADOW, &hr);
118         if (r == -ESRCH)
119                 return NSS_STATUS_NOTFOUND;
120         if (r < 0) {
121                 *errnop = -r;
122                 return NSS_STATUS_UNAVAIL;
123         }
124 
125         r = nss_pack_user_record(hr, pwd, buffer, buflen);
126         if (r < 0) {
127                 *errnop = -r;
128                 return NSS_STATUS_TRYAGAIN;
129         }
130 
131         return NSS_STATUS_SUCCESS;
132 }
133 
nss_pack_user_record_shadow(UserRecord * hr,struct spwd * spwd,char * buffer,size_t buflen)134 int nss_pack_user_record_shadow(
135                 UserRecord *hr,
136                 struct spwd *spwd,
137                 char *buffer,
138                 size_t buflen) {
139 
140         const char *hashed;
141         size_t required;
142 
143         assert(hr);
144         assert(spwd);
145 
146         assert(hr->user_name);
147         required = strlen(hr->user_name) + 1;
148 
149         assert_se(hashed = strv_isempty(hr->hashed_password) ? PASSWORD_LOCKED_AND_INVALID : hr->hashed_password[0]);
150         required += strlen(hashed) + 1;
151 
152         if (buflen < required)
153                 return -ERANGE;
154 
155         *spwd = (struct spwd) {
156                 .sp_namp = buffer,
157                 .sp_lstchg = hr->last_password_change_usec == 0 ? 1 :               /* map 0 to 1, since 0 means please change password on next login */
158                              hr->last_password_change_usec == UINT64_MAX ? -1 :
159                              (long int) (hr->last_password_change_usec / USEC_PER_DAY),
160                 .sp_min = hr->password_change_min_usec != UINT64_MAX ? (long int) (hr->password_change_min_usec / USEC_PER_DAY) : -1,
161                 .sp_max = hr->password_change_max_usec != UINT64_MAX ? (long int) (hr->password_change_max_usec / USEC_PER_DAY) : -1,
162                 .sp_warn = hr->password_change_warn_usec != UINT64_MAX ? (long int) (hr->password_change_warn_usec / USEC_PER_DAY) : -1,
163                 .sp_inact = hr->password_change_inactive_usec != UINT64_MAX ? (long int) (hr->password_change_inactive_usec / USEC_PER_DAY) : -1,
164                 .sp_expire = hr->locked > 0 || hr->not_after_usec == 0 ? 1 : /* already expired/locked */
165                              hr->not_after_usec == UINT64_MAX ? -1 :
166                              (long int) (hr->not_after_usec / USEC_PER_DAY),
167                 .sp_flag = ULONG_MAX,
168         };
169 
170         assert(buffer);
171 
172         spwd->sp_pwdp = stpcpy(spwd->sp_namp, hr->user_name) + 1;
173         strcpy(spwd->sp_pwdp, hashed);
174 
175         return 0;
176 }
177 
userdb_getspnam(const char * name,struct spwd * spwd,char * buffer,size_t buflen,int * errnop)178 enum nss_status userdb_getspnam(
179                 const char *name,
180                 struct spwd *spwd,
181                 char *buffer, size_t buflen,
182                 int *errnop) {
183 
184         _cleanup_(user_record_unrefp) UserRecord *hr = NULL;
185         int r;
186 
187         assert(spwd);
188         assert(errnop);
189 
190         if (_nss_systemd_is_blocked())
191                 return NSS_STATUS_NOTFOUND;
192 
193         r = userdb_by_name(name, nss_glue_userdb_flags(), &hr);
194         if (r == -ESRCH)
195                 return NSS_STATUS_NOTFOUND;
196         if (r < 0) {
197                 *errnop = -r;
198                 return NSS_STATUS_UNAVAIL;
199         }
200 
201         if (hr->incomplete) /* protected records missing? */
202                 return NSS_STATUS_NOTFOUND;
203 
204         r = nss_pack_user_record_shadow(hr, spwd, buffer, buflen);
205         if (r < 0) {
206                 *errnop = -r;
207                 return NSS_STATUS_TRYAGAIN;
208         }
209 
210         return NSS_STATUS_SUCCESS;
211 }
212 
nss_pack_group_record(GroupRecord * g,char ** extra_members,struct group * gr,char * buffer,size_t buflen)213 int nss_pack_group_record(
214                 GroupRecord *g,
215                 char **extra_members,
216                 struct group *gr,
217                 char *buffer,
218                 size_t buflen) {
219 
220         char **array = NULL, *p;
221         size_t required, n = 0, i = 0;
222 
223         assert(g);
224         assert(gr);
225 
226         assert(g->group_name);
227         required = strlen(g->group_name) + 1;
228 
229         STRV_FOREACH(m, g->members) {
230                 required += sizeof(char*);  /* space for ptr array entry */
231                 required += strlen(*m) + 1;
232                 n++;
233         }
234         STRV_FOREACH(m, extra_members) {
235                 if (strv_contains(g->members, *m))
236                         continue;
237 
238                 required += sizeof(char*);
239                 required += strlen(*m) + 1;
240                 n++;
241         }
242 
243         required += sizeof(char*); /* trailing NULL in ptr array entry */
244 
245         if (buflen < required)
246                 return -ERANGE;
247 
248         array = (char**) buffer; /* place ptr array at beginning of buffer, under assumption buffer is aligned */
249         p = buffer + sizeof(void*) * (n + 1); /* place member strings right after the ptr array */
250 
251         STRV_FOREACH(m, g->members) {
252                 array[i++] = p;
253                 p = stpcpy(p, *m) + 1;
254         }
255         STRV_FOREACH(m, extra_members) {
256                 if (strv_contains(g->members, *m))
257                         continue;
258 
259                 array[i++] = p;
260                 p = stpcpy(p, *m) + 1;
261         }
262 
263         assert_se(i == n);
264         array[n] = NULL;
265 
266         *gr = (struct group) {
267                 .gr_name = strcpy(p, g->group_name),
268                 .gr_gid = g->gid,
269                 .gr_passwd = (char*) PASSWORD_SEE_SHADOW,
270                 .gr_mem = array,
271         };
272 
273         return 0;
274 }
275 
userdb_getgrnam(const char * name,struct group * gr,char * buffer,size_t buflen,int * errnop)276 enum nss_status userdb_getgrnam(
277                 const char *name,
278                 struct group *gr,
279                 char *buffer,
280                 size_t buflen,
281                 int *errnop) {
282 
283         _cleanup_(group_record_unrefp) GroupRecord *g = NULL;
284         _cleanup_strv_free_ char **members = NULL;
285         int r;
286 
287         assert(gr);
288         assert(errnop);
289 
290         if (_nss_systemd_is_blocked())
291                 return NSS_STATUS_NOTFOUND;
292 
293         r = groupdb_by_name(name, nss_glue_userdb_flags()|USERDB_SUPPRESS_SHADOW, &g);
294         if (r < 0 && r != -ESRCH) {
295                 *errnop = -r;
296                 return NSS_STATUS_UNAVAIL;
297         }
298 
299         r = membershipdb_by_group_strv(name, nss_glue_userdb_flags()|USERDB_SUPPRESS_SHADOW, &members);
300         if (r < 0 && r != -ESRCH) {
301                 *errnop = -r;
302                 return NSS_STATUS_UNAVAIL;
303         }
304 
305         if (!g) {
306                 _unused_ _cleanup_(_nss_systemd_unblockp) bool blocked = false;
307 
308                 if (strv_isempty(members))
309                         return NSS_STATUS_NOTFOUND;
310 
311                 /* Grmbl, so we are supposed to extend a group entry, but the group entry itself is not
312                  * accessible via non-NSS. Hence let's do what we have to do, and query NSS after all to
313                  * acquire it, so that we can extend it (that's because glibc's group merging feature will
314                  * merge groups only if both GID and name match and thus we need to have both first). It
315                  * sucks behaving recursively likely this, but it's apparently what everybody does. We break
316                  * the recursion for ourselves via the _nss_systemd_block_nss() lock. */
317 
318                 r = _nss_systemd_block(true);
319                 if (r < 0)
320                         return r;
321 
322                 blocked = true;
323 
324                 r = nss_group_record_by_name(name, false, &g);
325                 if (r == -ESRCH)
326                         return NSS_STATUS_NOTFOUND;
327                 if (r < 0) {
328                         *errnop = -r;
329                         return NSS_STATUS_UNAVAIL;
330                 }
331         }
332 
333         r = nss_pack_group_record(g, members, gr, buffer, buflen);
334         if (r < 0) {
335                 *errnop = -r;
336                 return NSS_STATUS_TRYAGAIN;
337         }
338 
339         return NSS_STATUS_SUCCESS;
340 }
341 
userdb_getgrgid(gid_t gid,struct group * gr,char * buffer,size_t buflen,int * errnop)342 enum nss_status userdb_getgrgid(
343                 gid_t gid,
344                 struct group *gr,
345                 char *buffer,
346                 size_t buflen,
347                 int *errnop) {
348 
349 
350         _cleanup_(group_record_unrefp) GroupRecord *g = NULL;
351         _cleanup_strv_free_ char **members = NULL;
352         bool from_nss;
353         int r;
354 
355         assert(gr);
356         assert(errnop);
357 
358         if (_nss_systemd_is_blocked())
359                 return NSS_STATUS_NOTFOUND;
360 
361         r = groupdb_by_gid(gid, nss_glue_userdb_flags()|USERDB_SUPPRESS_SHADOW, &g);
362         if (r < 0 && r != -ESRCH) {
363                 *errnop = -r;
364                 return NSS_STATUS_UNAVAIL;
365         }
366 
367         if (!g) {
368                 _unused_ _cleanup_(_nss_systemd_unblockp) bool blocked = false;
369 
370                 /* So, quite possibly we have to extend an existing group record with additional members. But
371                  * to do this we need to know the group name first. The group didn't exist via non-NSS
372                  * queries though, hence let's try to acquire it here recursively via NSS. */
373 
374                 r = _nss_systemd_block(true);
375                 if (r < 0)
376                         return r;
377 
378                 blocked = true;
379 
380                 r = nss_group_record_by_gid(gid, false, &g);
381                 if (r == -ESRCH)
382                         return NSS_STATUS_NOTFOUND;
383                 if (r < 0) {
384                         *errnop = -r;
385                         return NSS_STATUS_UNAVAIL;
386                 }
387 
388                 from_nss = true;
389         } else
390                 from_nss = false;
391 
392         r = membershipdb_by_group_strv(g->group_name, nss_glue_userdb_flags()|USERDB_SUPPRESS_SHADOW, &members);
393         if (r < 0 && r != -ESRCH) {
394                 *errnop = -r;
395                 return NSS_STATUS_UNAVAIL;
396         }
397 
398         /* If we acquired the record via NSS then there's no reason to respond unless we have to augment the
399          * list of members of the group */
400         if (from_nss && strv_isempty(members))
401                 return NSS_STATUS_NOTFOUND;
402 
403         r = nss_pack_group_record(g, members, gr, buffer, buflen);
404         if (r < 0) {
405                 *errnop = -r;
406                 return NSS_STATUS_TRYAGAIN;
407         }
408 
409         return NSS_STATUS_SUCCESS;
410 }
411 
nss_pack_group_record_shadow(GroupRecord * hr,struct sgrp * sgrp,char * buffer,size_t buflen)412 int nss_pack_group_record_shadow(
413                 GroupRecord *hr,
414                 struct sgrp *sgrp,
415                 char *buffer,
416                 size_t buflen) {
417 
418         const char *hashed;
419         size_t required;
420 
421         assert(hr);
422         assert(sgrp);
423 
424         assert(hr->group_name);
425         required = strlen(hr->group_name) + 1;
426 
427         assert_se(hashed = strv_isempty(hr->hashed_password) ? PASSWORD_LOCKED_AND_INVALID : hr->hashed_password[0]);
428         required += strlen(hashed) + 1;
429 
430         if (buflen < required)
431                 return -ERANGE;
432 
433         *sgrp = (struct sgrp) {
434                 .sg_namp = buffer,
435         };
436 
437         assert(buffer);
438 
439         sgrp->sg_passwd = stpcpy(sgrp->sg_namp, hr->group_name) + 1;
440         strcpy(sgrp->sg_passwd, hashed);
441 
442         return 0;
443 }
444 
userdb_getsgnam(const char * name,struct sgrp * sgrp,char * buffer,size_t buflen,int * errnop)445 enum nss_status userdb_getsgnam(
446                 const char *name,
447                 struct sgrp *sgrp,
448                 char *buffer, size_t buflen,
449                 int *errnop) {
450 
451         _cleanup_(group_record_unrefp) GroupRecord *hr = NULL;
452         int r;
453 
454         assert(sgrp);
455         assert(errnop);
456 
457         if (_nss_systemd_is_blocked())
458                 return NSS_STATUS_NOTFOUND;
459 
460         r = groupdb_by_name(name, nss_glue_userdb_flags(), &hr);
461         if (r == -ESRCH)
462                 return NSS_STATUS_NOTFOUND;
463         if (r < 0) {
464                 *errnop = -r;
465                 return NSS_STATUS_UNAVAIL;
466         }
467 
468         if (hr->incomplete) /* protected records missing? */
469                 return NSS_STATUS_NOTFOUND;
470 
471         r = nss_pack_group_record_shadow(hr, sgrp, buffer, buflen);
472         if (r < 0) {
473                 *errnop = -r;
474                 return NSS_STATUS_TRYAGAIN;
475         }
476 
477         return NSS_STATUS_SUCCESS;
478 }
479