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