]>
Commit | Line | Data |
---|---|---|
1 | /* SPDX-License-Identifier: LGPL-2.1-or-later */ | |
2 | ||
3 | #include <poll.h> | |
4 | ||
5 | #include "sd-daemon.h" | |
6 | #include "sd-varlink.h" | |
7 | ||
8 | #include "alloc-util.h" | |
9 | #include "argv-util.h" | |
10 | #include "env-util.h" | |
11 | #include "errno-util.h" | |
12 | #include "fd-util.h" | |
13 | #include "group-record.h" | |
14 | #include "io-util.h" | |
15 | #include "json-util.h" | |
16 | #include "main-func.h" | |
17 | #include "pidref.h" | |
18 | #include "string-util.h" | |
19 | #include "time-util.h" | |
20 | #include "user-record.h" | |
21 | #include "user-util.h" | |
22 | #include "userdb.h" | |
23 | #include "varlink-io.systemd.UserDatabase.h" | |
24 | #include "varlink-util.h" | |
25 | ||
26 | #define ITERATIONS_MAX 64U | |
27 | #define RUNTIME_MAX_USEC (5 * USEC_PER_MINUTE) | |
28 | #define PRESSURE_SLEEP_TIME_USEC (50 * USEC_PER_MSEC) | |
29 | #define CONNECTION_IDLE_USEC (15 * USEC_PER_SEC) | |
30 | #define LISTEN_IDLE_USEC (90 * USEC_PER_SEC) | |
31 | ||
32 | typedef struct LookupParameters { | |
33 | const char *name; | |
34 | union { | |
35 | uid_t uid; | |
36 | gid_t gid; | |
37 | }; | |
38 | const char *service; | |
39 | UserDBMatch match; | |
40 | } LookupParameters; | |
41 | ||
42 | static void lookup_parameters_done(LookupParameters *p) { | |
43 | assert(p); | |
44 | ||
45 | userdb_match_done(&p->match); | |
46 | } | |
47 | ||
48 | static int add_nss_service(sd_json_variant **v) { | |
49 | _cleanup_(sd_json_variant_unrefp) sd_json_variant *status = NULL, *z = NULL; | |
50 | sd_id128_t mid; | |
51 | int r; | |
52 | ||
53 | assert(v); | |
54 | ||
55 | /* Patch in service field if it's missing. The assumption here is that this field is unset only for | |
56 | * NSS records */ | |
57 | ||
58 | if (sd_json_variant_by_key(*v, "service")) | |
59 | return 0; | |
60 | ||
61 | r = sd_id128_get_machine(&mid); | |
62 | if (r < 0) | |
63 | return r; | |
64 | ||
65 | status = sd_json_variant_ref(sd_json_variant_by_key(*v, "status")); | |
66 | z = sd_json_variant_ref(sd_json_variant_by_key(status, SD_ID128_TO_STRING(mid))); | |
67 | ||
68 | if (sd_json_variant_by_key(z, "service")) | |
69 | return 0; | |
70 | ||
71 | r = sd_json_variant_set_field_string(&z, "service", "io.systemd.NameServiceSwitch"); | |
72 | if (r < 0) | |
73 | return r; | |
74 | ||
75 | r = sd_json_variant_set_field(&status, SD_ID128_TO_STRING(mid), z); | |
76 | if (r < 0) | |
77 | return r; | |
78 | ||
79 | return sd_json_variant_set_field(v, "status", status); | |
80 | } | |
81 | ||
82 | static int build_user_json(sd_varlink *link, UserRecord *ur, sd_json_variant **ret) { | |
83 | _cleanup_(user_record_unrefp) UserRecord *stripped = NULL; | |
84 | _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL; | |
85 | UserRecordLoadFlags flags; | |
86 | uid_t peer_uid; | |
87 | bool trusted; | |
88 | int r; | |
89 | ||
90 | assert(ur); | |
91 | assert(ret); | |
92 | ||
93 | r = sd_varlink_get_peer_uid(link, &peer_uid); | |
94 | if (r < 0) { | |
95 | log_debug_errno(r, "Unable to query peer UID, ignoring: %m"); | |
96 | trusted = false; | |
97 | } else | |
98 | trusted = peer_uid == 0 || peer_uid == ur->uid; | |
99 | ||
100 | flags = USER_RECORD_REQUIRE_REGULAR|USER_RECORD_ALLOW_PER_MACHINE|USER_RECORD_ALLOW_BINDING|USER_RECORD_STRIP_SECRET|USER_RECORD_ALLOW_STATUS|USER_RECORD_ALLOW_SIGNATURE|USER_RECORD_PERMISSIVE; | |
101 | if (trusted) | |
102 | flags |= USER_RECORD_ALLOW_PRIVILEGED; | |
103 | else | |
104 | flags |= USER_RECORD_STRIP_PRIVILEGED; | |
105 | ||
106 | r = user_record_clone(ur, flags, &stripped); | |
107 | if (r < 0) | |
108 | return r; | |
109 | ||
110 | stripped->incomplete = | |
111 | ur->incomplete || | |
112 | (FLAGS_SET(ur->mask, USER_RECORD_PRIVILEGED) && | |
113 | !FLAGS_SET(stripped->mask, USER_RECORD_PRIVILEGED)); | |
114 | ||
115 | v = sd_json_variant_ref(stripped->json); | |
116 | r = add_nss_service(&v); | |
117 | if (r < 0) | |
118 | return r; | |
119 | ||
120 | return sd_json_buildo( | |
121 | ret, | |
122 | SD_JSON_BUILD_PAIR("record", SD_JSON_BUILD_VARIANT(v)), | |
123 | SD_JSON_BUILD_PAIR("incomplete", SD_JSON_BUILD_BOOLEAN(stripped->incomplete))); | |
124 | } | |
125 | ||
126 | static int userdb_flags_from_service(sd_varlink *link, const char *service, UserDBFlags *ret) { | |
127 | assert(link); | |
128 | assert(ret); | |
129 | ||
130 | if (streq_ptr(service, "io.systemd.NameServiceSwitch")) | |
131 | *ret = USERDB_NSS_ONLY|USERDB_AVOID_MULTIPLEXER; | |
132 | else if (streq_ptr(service, "io.systemd.DropIn")) | |
133 | *ret = USERDB_DROPIN_ONLY|USERDB_AVOID_MULTIPLEXER; | |
134 | else if (streq_ptr(service, "io.systemd.Multiplexer")) | |
135 | *ret = USERDB_AVOID_MULTIPLEXER; | |
136 | else | |
137 | return sd_varlink_error(link, "io.systemd.UserDatabase.BadService", NULL); | |
138 | ||
139 | return 0; | |
140 | } | |
141 | ||
142 | static int vl_method_get_user_record(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) { | |
143 | ||
144 | static const sd_json_dispatch_field dispatch_table[] = { | |
145 | { "uid", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uid_gid, offsetof(LookupParameters, uid), 0 }, | |
146 | { "userName", SD_JSON_VARIANT_STRING, json_dispatch_const_user_group_name, offsetof(LookupParameters, name), SD_JSON_RELAX }, | |
147 | { "service", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, offsetof(LookupParameters, service), 0 }, | |
148 | { "fuzzyNames", SD_JSON_VARIANT_ARRAY, sd_json_dispatch_strv, offsetof(LookupParameters, match.fuzzy_names), 0 }, | |
149 | { "dispositionMask", SD_JSON_VARIANT_ARRAY, json_dispatch_dispositions_mask, offsetof(LookupParameters, match.disposition_mask), 0 }, | |
150 | { "uidMin", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uid_gid, offsetof(LookupParameters, match.uid_min), 0 }, | |
151 | { "uidMax", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uid_gid, offsetof(LookupParameters, match.uid_max), 0 }, | |
152 | {} | |
153 | }; | |
154 | ||
155 | _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL; | |
156 | _cleanup_(user_record_unrefp) UserRecord *hr = NULL; | |
157 | _cleanup_(lookup_parameters_done) LookupParameters p = { | |
158 | .uid = UID_INVALID, | |
159 | .match = USERDB_MATCH_NULL, | |
160 | }; | |
161 | UserDBFlags userdb_flags; | |
162 | int r; | |
163 | ||
164 | assert(parameters); | |
165 | ||
166 | r = sd_varlink_dispatch(link, parameters, dispatch_table, &p); | |
167 | if (r != 0) | |
168 | return r; | |
169 | ||
170 | r = userdb_flags_from_service(link, p.service, &userdb_flags); | |
171 | if (r != 0) /* return value of < 0 means error (as usual); > 0 means 'already processed and replied, | |
172 | * we are done'; == 0 means 'not processed, caller should process now' */ | |
173 | return r; | |
174 | ||
175 | if (uid_is_valid(p.uid)) | |
176 | r = userdb_by_uid(p.uid, &p.match, userdb_flags, &hr); | |
177 | else if (p.name) | |
178 | r = userdb_by_name(p.name, &p.match, userdb_flags, &hr); | |
179 | else { | |
180 | _cleanup_(userdb_iterator_freep) UserDBIterator *iterator = NULL; | |
181 | _cleanup_(sd_json_variant_unrefp) sd_json_variant *last = NULL; | |
182 | ||
183 | r = userdb_all(&p.match, userdb_flags, &iterator); | |
184 | if (IN_SET(r, -ESRCH, -ENOLINK)) | |
185 | /* We turn off Varlink lookups in various cases (e.g. in case we only enable DropIn | |
186 | * backend) — this might make userdb_all return ENOLINK (which indicates that varlink | |
187 | * was off and no other suitable source or entries were found). Let's hide this | |
188 | * implementation detail and always return NoRecordFound in this case, since from a | |
189 | * client's perspective it's irrelevant if there was no entry at all or just not on | |
190 | * the service that the query was limited to. */ | |
191 | return sd_varlink_error(link, "io.systemd.UserDatabase.NoRecordFound", NULL); | |
192 | if (r < 0) | |
193 | return r; | |
194 | ||
195 | for (;;) { | |
196 | _cleanup_(user_record_unrefp) UserRecord *z = NULL; | |
197 | ||
198 | r = userdb_iterator_get(iterator, &p.match, &z); | |
199 | if (r == -ESRCH) | |
200 | break; | |
201 | if (r < 0) | |
202 | return r; | |
203 | ||
204 | if (last) { | |
205 | r = sd_varlink_notify(link, last); | |
206 | if (r < 0) | |
207 | return r; | |
208 | ||
209 | last = sd_json_variant_unref(last); | |
210 | } | |
211 | ||
212 | r = build_user_json(link, z, &last); | |
213 | if (r < 0) | |
214 | return r; | |
215 | } | |
216 | ||
217 | if (!last) | |
218 | return sd_varlink_error(link, "io.systemd.UserDatabase.NoRecordFound", NULL); | |
219 | ||
220 | return sd_varlink_reply(link, last); | |
221 | } | |
222 | if (r == -ESRCH) | |
223 | return sd_varlink_error(link, "io.systemd.UserDatabase.NoRecordFound", NULL); | |
224 | if (r == -ENOEXEC) | |
225 | return sd_varlink_error(link, "io.systemd.UserDatabase.NonMatchingRecordFound", NULL); | |
226 | if (r < 0) { | |
227 | log_debug_errno(r, "User lookup failed abnormally: %m"); | |
228 | return sd_varlink_error(link, "io.systemd.UserDatabase.ServiceNotAvailable", NULL); | |
229 | } | |
230 | ||
231 | if ((uid_is_valid(p.uid) && hr->uid != p.uid) || | |
232 | (p.name && !user_record_matches_user_name(hr, p.name))) | |
233 | return sd_varlink_error(link, "io.systemd.UserDatabase.ConflictingRecordFound", NULL); | |
234 | ||
235 | r = build_user_json(link, hr, &v); | |
236 | if (r < 0) | |
237 | return r; | |
238 | ||
239 | return sd_varlink_reply(link, v); | |
240 | } | |
241 | ||
242 | static int build_group_json(sd_varlink *link, GroupRecord *gr, sd_json_variant **ret) { | |
243 | _cleanup_(group_record_unrefp) GroupRecord *stripped = NULL; | |
244 | _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL; | |
245 | UserRecordLoadFlags flags; | |
246 | uid_t peer_uid; | |
247 | bool trusted; | |
248 | int r; | |
249 | ||
250 | assert(gr); | |
251 | assert(ret); | |
252 | ||
253 | r = sd_varlink_get_peer_uid(link, &peer_uid); | |
254 | if (r < 0) { | |
255 | log_debug_errno(r, "Unable to query peer UID, ignoring: %m"); | |
256 | trusted = false; | |
257 | } else | |
258 | trusted = peer_uid == 0; | |
259 | ||
260 | flags = USER_RECORD_REQUIRE_REGULAR|USER_RECORD_ALLOW_PER_MACHINE|USER_RECORD_ALLOW_BINDING|USER_RECORD_STRIP_SECRET|USER_RECORD_ALLOW_STATUS|USER_RECORD_ALLOW_SIGNATURE|USER_RECORD_PERMISSIVE; | |
261 | if (trusted) | |
262 | flags |= USER_RECORD_ALLOW_PRIVILEGED; | |
263 | else | |
264 | flags |= USER_RECORD_STRIP_PRIVILEGED; | |
265 | ||
266 | r = group_record_clone(gr, flags, &stripped); | |
267 | if (r < 0) | |
268 | return r; | |
269 | ||
270 | stripped->incomplete = | |
271 | gr->incomplete || | |
272 | (FLAGS_SET(gr->mask, USER_RECORD_PRIVILEGED) && | |
273 | !FLAGS_SET(stripped->mask, USER_RECORD_PRIVILEGED)); | |
274 | ||
275 | v = sd_json_variant_ref(gr->json); | |
276 | r = add_nss_service(&v); | |
277 | if (r < 0) | |
278 | return r; | |
279 | ||
280 | return sd_json_buildo( | |
281 | ret, | |
282 | SD_JSON_BUILD_PAIR("record", SD_JSON_BUILD_VARIANT(v)), | |
283 | SD_JSON_BUILD_PAIR("incomplete", SD_JSON_BUILD_BOOLEAN(stripped->incomplete))); | |
284 | } | |
285 | ||
286 | static int vl_method_get_group_record(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) { | |
287 | ||
288 | static const sd_json_dispatch_field dispatch_table[] = { | |
289 | { "gid", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uid_gid, offsetof(LookupParameters, gid), 0 }, | |
290 | { "groupName", SD_JSON_VARIANT_STRING, json_dispatch_const_user_group_name, offsetof(LookupParameters, name), SD_JSON_RELAX }, | |
291 | { "service", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, offsetof(LookupParameters, service), 0 }, | |
292 | { "fuzzyNames", SD_JSON_VARIANT_ARRAY, sd_json_dispatch_strv, offsetof(LookupParameters, match.fuzzy_names), 0 }, | |
293 | { "dispositionMask", SD_JSON_VARIANT_ARRAY, json_dispatch_dispositions_mask, offsetof(LookupParameters, match.disposition_mask), 0 }, | |
294 | { "gidMin", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uid_gid, offsetof(LookupParameters, match.gid_min), 0 }, | |
295 | { "gidMax", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uid_gid, offsetof(LookupParameters, match.gid_max), 0 }, | |
296 | {} | |
297 | }; | |
298 | ||
299 | _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL; | |
300 | _cleanup_(group_record_unrefp) GroupRecord *g = NULL; | |
301 | _cleanup_(lookup_parameters_done) LookupParameters p = { | |
302 | .gid = GID_INVALID, | |
303 | .match = USERDB_MATCH_NULL, | |
304 | }; | |
305 | UserDBFlags userdb_flags; | |
306 | int r; | |
307 | ||
308 | assert(parameters); | |
309 | ||
310 | r = sd_varlink_dispatch(link, parameters, dispatch_table, &p); | |
311 | if (r != 0) | |
312 | return r; | |
313 | ||
314 | r = userdb_flags_from_service(link, p.service, &userdb_flags); | |
315 | if (r != 0) | |
316 | return r; | |
317 | ||
318 | if (gid_is_valid(p.gid)) | |
319 | r = groupdb_by_gid(p.gid, &p.match, userdb_flags, &g); | |
320 | else if (p.name) | |
321 | r = groupdb_by_name(p.name, &p.match, userdb_flags, &g); | |
322 | else { | |
323 | _cleanup_(userdb_iterator_freep) UserDBIterator *iterator = NULL; | |
324 | _cleanup_(sd_json_variant_unrefp) sd_json_variant *last = NULL; | |
325 | ||
326 | r = groupdb_all(&p.match, userdb_flags, &iterator); | |
327 | if (IN_SET(r, -ESRCH, -ENOLINK)) | |
328 | return sd_varlink_error(link, "io.systemd.UserDatabase.NoRecordFound", NULL); | |
329 | if (r < 0) | |
330 | return r; | |
331 | ||
332 | for (;;) { | |
333 | _cleanup_(group_record_unrefp) GroupRecord *z = NULL; | |
334 | ||
335 | r = groupdb_iterator_get(iterator, &p.match, &z); | |
336 | if (r == -ESRCH) | |
337 | break; | |
338 | if (r < 0) | |
339 | return r; | |
340 | ||
341 | if (last) { | |
342 | r = sd_varlink_notify(link, last); | |
343 | if (r < 0) | |
344 | return r; | |
345 | ||
346 | last = sd_json_variant_unref(last); | |
347 | } | |
348 | ||
349 | r = build_group_json(link, z, &last); | |
350 | if (r < 0) | |
351 | return r; | |
352 | } | |
353 | ||
354 | if (!last) | |
355 | return sd_varlink_error(link, "io.systemd.UserDatabase.NoRecordFound", NULL); | |
356 | ||
357 | return sd_varlink_reply(link, last); | |
358 | } | |
359 | if (r == -ESRCH) | |
360 | return sd_varlink_error(link, "io.systemd.UserDatabase.NoRecordFound", NULL); | |
361 | if (r == -ENOEXEC) | |
362 | return sd_varlink_error(link, "io.systemd.UserDatabase.NonMatchingRecordFound", NULL); | |
363 | if (r < 0) { | |
364 | log_debug_errno(r, "Group lookup failed abnormally: %m"); | |
365 | return sd_varlink_error(link, "io.systemd.UserDatabase.ServiceNotAvailable", NULL); | |
366 | } | |
367 | ||
368 | if ((uid_is_valid(p.gid) && g->gid != p.gid) || | |
369 | (p.name && !group_record_matches_group_name(g, p.name))) | |
370 | return sd_varlink_error(link, "io.systemd.UserDatabase.ConflictingRecordFound", NULL); | |
371 | ||
372 | r = build_group_json(link, g, &v); | |
373 | if (r < 0) | |
374 | return r; | |
375 | ||
376 | return sd_varlink_reply(link, v); | |
377 | } | |
378 | ||
379 | typedef struct MembershipLookupParameters { | |
380 | const char *user_name; | |
381 | const char *group_name; | |
382 | const char *service; | |
383 | } MembershipLookupParameters; | |
384 | ||
385 | static int vl_method_get_memberships(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) { | |
386 | static const sd_json_dispatch_field dispatch_table[] = { | |
387 | { "userName", SD_JSON_VARIANT_STRING, json_dispatch_const_user_group_name, offsetof(MembershipLookupParameters, user_name), SD_JSON_RELAX }, | |
388 | { "groupName", SD_JSON_VARIANT_STRING, json_dispatch_const_user_group_name, offsetof(MembershipLookupParameters, group_name), SD_JSON_RELAX }, | |
389 | { "service", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, offsetof(MembershipLookupParameters, service), 0 }, | |
390 | {} | |
391 | }; | |
392 | ||
393 | _cleanup_free_ char *last_user_name = NULL, *last_group_name = NULL; | |
394 | _cleanup_(userdb_iterator_freep) UserDBIterator *iterator = NULL; | |
395 | MembershipLookupParameters p = {}; | |
396 | UserDBFlags userdb_flags; | |
397 | int r; | |
398 | ||
399 | assert(parameters); | |
400 | ||
401 | r = sd_varlink_dispatch(link, parameters, dispatch_table, &p); | |
402 | if (r != 0) | |
403 | return r; | |
404 | ||
405 | r = userdb_flags_from_service(link, p.service, &userdb_flags); | |
406 | if (r != 0) | |
407 | return r; | |
408 | ||
409 | if (p.group_name) | |
410 | r = membershipdb_by_group(p.group_name, userdb_flags, &iterator); | |
411 | else if (p.user_name) | |
412 | r = membershipdb_by_user(p.user_name, userdb_flags, &iterator); | |
413 | else | |
414 | r = membershipdb_all(userdb_flags, &iterator); | |
415 | if (IN_SET(r, -ESRCH, -ENOLINK)) | |
416 | return sd_varlink_error(link, "io.systemd.UserDatabase.NoRecordFound", NULL); | |
417 | if (r < 0) | |
418 | return r; | |
419 | ||
420 | for (;;) { | |
421 | _cleanup_free_ char *user_name = NULL, *group_name = NULL; | |
422 | ||
423 | r = membershipdb_iterator_get(iterator, &user_name, &group_name); | |
424 | if (r == -ESRCH) | |
425 | break; | |
426 | if (r < 0) | |
427 | return r; | |
428 | ||
429 | /* If both group + user are specified do a-posteriori filtering */ | |
430 | if (p.group_name && p.user_name && !streq(group_name, p.group_name)) | |
431 | continue; | |
432 | ||
433 | if (last_user_name) { | |
434 | assert(last_group_name); | |
435 | ||
436 | r = sd_varlink_notifybo( | |
437 | link, | |
438 | SD_JSON_BUILD_PAIR("userName", SD_JSON_BUILD_STRING(last_user_name)), | |
439 | SD_JSON_BUILD_PAIR("groupName", SD_JSON_BUILD_STRING(last_group_name))); | |
440 | if (r < 0) | |
441 | return r; | |
442 | } | |
443 | ||
444 | free_and_replace(last_user_name, user_name); | |
445 | free_and_replace(last_group_name, group_name); | |
446 | } | |
447 | ||
448 | if (!last_user_name) { | |
449 | assert(!last_group_name); | |
450 | return sd_varlink_error(link, "io.systemd.UserDatabase.NoRecordFound", NULL); | |
451 | } | |
452 | ||
453 | assert(last_group_name); | |
454 | ||
455 | return sd_varlink_replybo( | |
456 | link, | |
457 | SD_JSON_BUILD_PAIR("userName", SD_JSON_BUILD_STRING(last_user_name)), | |
458 | SD_JSON_BUILD_PAIR("groupName", SD_JSON_BUILD_STRING(last_group_name))); | |
459 | } | |
460 | ||
461 | static int process_connection(sd_varlink_server *server, int _fd) { | |
462 | _cleanup_close_ int fd = TAKE_FD(_fd); /* always take possession */ | |
463 | _cleanup_(sd_varlink_close_unrefp) sd_varlink *vl = NULL; | |
464 | int r; | |
465 | ||
466 | assert(server); | |
467 | assert(fd >= 0); | |
468 | ||
469 | r = sd_varlink_server_add_connection(server, fd, &vl); | |
470 | if (r < 0) | |
471 | return log_error_errno(r, "Failed to add connection: %m"); | |
472 | ||
473 | TAKE_FD(fd); | |
474 | vl = sd_varlink_ref(vl); | |
475 | ||
476 | for (;;) { | |
477 | r = sd_varlink_process(vl); | |
478 | if (r == -ENOTCONN) { | |
479 | log_debug("Connection terminated."); | |
480 | break; | |
481 | } | |
482 | if (r < 0) | |
483 | return log_error_errno(r, "Failed to process connection: %m"); | |
484 | if (r > 0) | |
485 | continue; | |
486 | ||
487 | r = sd_varlink_wait(vl, CONNECTION_IDLE_USEC); | |
488 | if (r < 0) | |
489 | return log_error_errno(r, "Failed to wait for connection events: %m"); | |
490 | if (r == 0) | |
491 | break; | |
492 | } | |
493 | ||
494 | return 0; | |
495 | } | |
496 | ||
497 | static int run(int argc, char *argv[]) { | |
498 | usec_t start_time, listen_idle_usec, last_busy_usec = USEC_INFINITY; | |
499 | _cleanup_(sd_varlink_server_unrefp) sd_varlink_server *server = NULL; | |
500 | _cleanup_(pidref_done) PidRef parent = PIDREF_NULL; | |
501 | unsigned n_iterations = 0; | |
502 | int m, listen_fd, r; | |
503 | ||
504 | log_setup(); | |
505 | ||
506 | m = sd_listen_fds(false); | |
507 | if (m < 0) | |
508 | return log_error_errno(m, "Failed to determine number of listening fds: %m"); | |
509 | if (m == 0) | |
510 | return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "No socket to listen on received."); | |
511 | if (m > 1) | |
512 | return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Worker can only listen on a single socket at a time."); | |
513 | ||
514 | listen_fd = SD_LISTEN_FDS_START; | |
515 | ||
516 | r = fd_nonblock(listen_fd, false); | |
517 | if (r < 0) | |
518 | return log_error_errno(r, "Failed to turn off non-blocking mode for listening socket: %m"); | |
519 | ||
520 | r = varlink_server_new(&server, 0, NULL); | |
521 | if (r < 0) | |
522 | return log_error_errno(r, "Failed to allocate varlink server: %m"); | |
523 | ||
524 | r = sd_varlink_server_add_interface(server, &vl_interface_io_systemd_UserDatabase); | |
525 | if (r < 0) | |
526 | return log_error_errno(r, "Failed to add UserDatabase interface to varlink server: %m"); | |
527 | ||
528 | r = sd_varlink_server_bind_method_many( | |
529 | server, | |
530 | "io.systemd.UserDatabase.GetUserRecord", vl_method_get_user_record, | |
531 | "io.systemd.UserDatabase.GetGroupRecord", vl_method_get_group_record, | |
532 | "io.systemd.UserDatabase.GetMemberships", vl_method_get_memberships); | |
533 | if (r < 0) | |
534 | return log_error_errno(r, "Failed to bind methods: %m"); | |
535 | ||
536 | r = getenv_bool("USERDB_FIXED_WORKER"); | |
537 | if (r < 0) | |
538 | return log_error_errno(r, "Failed to parse USERDB_FIXED_WORKER: %m"); | |
539 | listen_idle_usec = r ? USEC_INFINITY : LISTEN_IDLE_USEC; | |
540 | ||
541 | r = userdb_block_nss_systemd(true); | |
542 | if (r < 0) | |
543 | return log_error_errno(r, "Failed to disable userdb NSS compatibility: %m"); | |
544 | ||
545 | r = pidref_set_parent(&parent); | |
546 | if (r < 0) | |
547 | return log_error_errno(r, "Failed to acquire pidfd of parent process: %m"); | |
548 | if (parent.pid == 1) /* We got reparented away from userdbd? */ | |
549 | return log_error_errno(SYNTHETIC_ERRNO(ESRCH), "Parent already died, exiting."); | |
550 | ||
551 | start_time = now(CLOCK_MONOTONIC); | |
552 | ||
553 | for (;;) { | |
554 | _cleanup_close_ int fd = -EBADF; | |
555 | usec_t n; | |
556 | ||
557 | /* Exit the worker in regular intervals, to flush out all memory use */ | |
558 | if (n_iterations++ > ITERATIONS_MAX) { | |
559 | log_debug("Exiting worker, processed %u iterations, that's enough.", n_iterations); | |
560 | break; | |
561 | } | |
562 | ||
563 | n = now(CLOCK_MONOTONIC); | |
564 | if (n >= usec_add(start_time, RUNTIME_MAX_USEC)) { | |
565 | log_debug("Exiting worker, ran for %s, that's enough.", | |
566 | FORMAT_TIMESPAN(usec_sub_unsigned(n, start_time), 0)); | |
567 | break; | |
568 | } | |
569 | ||
570 | if (last_busy_usec == USEC_INFINITY) | |
571 | last_busy_usec = n; | |
572 | else if (listen_idle_usec != USEC_INFINITY && n >= usec_add(last_busy_usec, listen_idle_usec)) { | |
573 | log_debug("Exiting worker, been idle for %s.", | |
574 | FORMAT_TIMESPAN(usec_sub_unsigned(n, last_busy_usec), 0)); | |
575 | break; | |
576 | } | |
577 | ||
578 | (void) rename_process("systemd-userwork: waiting..."); | |
579 | fd = RET_NERRNO(accept4(listen_fd, NULL, NULL, SOCK_NONBLOCK|SOCK_CLOEXEC)); | |
580 | (void) rename_process("systemd-userwork: processing..."); | |
581 | ||
582 | if (fd == -EAGAIN) | |
583 | continue; /* The listening socket has SO_RECVTIMEO set, hence a timeout is expected | |
584 | * after a while, let's check if it's time to exit though. */ | |
585 | if (fd == -EINTR) | |
586 | continue; /* Might be that somebody attached via strace, let's just continue in that | |
587 | * case */ | |
588 | if (fd < 0) | |
589 | return log_error_errno(fd, "Failed to accept() from listening socket: %m"); | |
590 | ||
591 | if (now(CLOCK_MONOTONIC) <= usec_add(n, PRESSURE_SLEEP_TIME_USEC)) { | |
592 | /* We only slept a very short time? If so, let's see if there are more sockets | |
593 | * pending, and if so, let's ask our parent for more workers */ | |
594 | ||
595 | r = fd_wait_for_event(listen_fd, POLLIN, 0); | |
596 | if (r < 0) | |
597 | return log_error_errno(r, "Failed to test for POLLIN on listening socket: %m"); | |
598 | ||
599 | if (FLAGS_SET(r, POLLIN)) { | |
600 | r = pidref_kill(&parent, SIGUSR2); | |
601 | if (r == -ESRCH) | |
602 | return log_error_errno(r, "Parent already died?"); | |
603 | if (r < 0) | |
604 | return log_error_errno(r, "Failed to send SIGUSR2 signal to parent: %m"); | |
605 | } | |
606 | } | |
607 | ||
608 | (void) process_connection(server, TAKE_FD(fd)); | |
609 | last_busy_usec = USEC_INFINITY; | |
610 | } | |
611 | ||
612 | return 0; | |
613 | } | |
614 | ||
615 | DEFINE_MAIN_FUNCTION(run); |