]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/nss-systemd/userdb-glue.c
strv: make iterator in STRV_FOREACH() declaread in the loop
[thirdparty/systemd.git] / src / nss-systemd / userdb-glue.c
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
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
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
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
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
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
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
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
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
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
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
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 }