]>
Commit | Line | Data |
---|---|---|
db9ecf05 | 1 | /* SPDX-License-Identifier: LGPL-2.1-or-later */ |
d093b62c LP |
2 | |
3 | #include <poll.h> | |
d093b62c LP |
4 | |
5 | #include "sd-daemon.h" | |
25ff515b | 6 | #include "sd-varlink.h" |
d093b62c | 7 | |
2a5559c2 | 8 | #include "alloc-util.h" |
76d62b63 | 9 | #include "argv-util.h" |
d093b62c | 10 | #include "env-util.h" |
76d62b63 | 11 | #include "errno-util.h" |
d093b62c | 12 | #include "fd-util.h" |
d093b62c | 13 | #include "group-record.h" |
0f2d351f | 14 | #include "io-util.h" |
0376ef36 | 15 | #include "json-util.h" |
d093b62c | 16 | #include "main-func.h" |
2a5559c2 DDM |
17 | #include "pidref.h" |
18 | #include "string-util.h" | |
d093b62c | 19 | #include "time-util.h" |
d093b62c LP |
20 | #include "user-record.h" |
21 | #include "user-util.h" | |
22 | #include "userdb.h" | |
abef4a7b | 23 | #include "varlink-io.systemd.UserDatabase.h" |
fd409ff0 | 24 | #include "varlink-util.h" |
d093b62c LP |
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 { | |
45e587d8 | 33 | const char *name; |
d093b62c LP |
34 | union { |
35 | uid_t uid; | |
36 | gid_t gid; | |
37 | }; | |
38 | const char *service; | |
5ec4933d | 39 | UserDBMatch match; |
d093b62c LP |
40 | } LookupParameters; |
41 | ||
5ec4933d LP |
42 | static void lookup_parameters_done(LookupParameters *p) { |
43 | assert(p); | |
44 | ||
45 | userdb_match_done(&p->match); | |
46 | } | |
47 | ||
309a747f LP |
48 | static int add_nss_service(sd_json_variant **v) { |
49 | _cleanup_(sd_json_variant_unrefp) sd_json_variant *status = NULL, *z = NULL; | |
d093b62c LP |
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 | ||
309a747f | 58 | if (sd_json_variant_by_key(*v, "service")) |
d093b62c LP |
59 | return 0; |
60 | ||
61 | r = sd_id128_get_machine(&mid); | |
62 | if (r < 0) | |
63 | return r; | |
64 | ||
309a747f LP |
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))); | |
d093b62c | 67 | |
309a747f | 68 | if (sd_json_variant_by_key(z, "service")) |
d093b62c LP |
69 | return 0; |
70 | ||
309a747f | 71 | r = sd_json_variant_set_field_string(&z, "service", "io.systemd.NameServiceSwitch"); |
d093b62c LP |
72 | if (r < 0) |
73 | return r; | |
74 | ||
309a747f | 75 | r = sd_json_variant_set_field(&status, SD_ID128_TO_STRING(mid), z); |
d093b62c LP |
76 | if (r < 0) |
77 | return r; | |
78 | ||
309a747f | 79 | return sd_json_variant_set_field(v, "status", status); |
d093b62c LP |
80 | } |
81 | ||
25ff515b | 82 | static int build_user_json(sd_varlink *link, UserRecord *ur, sd_json_variant **ret) { |
d093b62c | 83 | _cleanup_(user_record_unrefp) UserRecord *stripped = NULL; |
309a747f | 84 | _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL; |
d093b62c LP |
85 | UserRecordLoadFlags flags; |
86 | uid_t peer_uid; | |
87 | bool trusted; | |
88 | int r; | |
89 | ||
90 | assert(ur); | |
91 | assert(ret); | |
92 | ||
25ff515b | 93 | r = sd_varlink_get_peer_uid(link, &peer_uid); |
d093b62c LP |
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 | ||
bfc0cc1a | 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; |
d093b62c LP |
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 | ||
309a747f | 115 | v = sd_json_variant_ref(stripped->json); |
d093b62c LP |
116 | r = add_nss_service(&v); |
117 | if (r < 0) | |
118 | return r; | |
119 | ||
be5bee2a LP |
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))); | |
d093b62c LP |
124 | } |
125 | ||
25ff515b | 126 | static int userdb_flags_from_service(sd_varlink *link, const char *service, UserDBFlags *ret) { |
134ff8f4 | 127 | assert(link); |
134ff8f4 LP |
128 | assert(ret); |
129 | ||
130 | if (streq_ptr(service, "io.systemd.NameServiceSwitch")) | |
131 | *ret = USERDB_NSS_ONLY|USERDB_AVOID_MULTIPLEXER; | |
cf7c7512 | 132 | else if (streq_ptr(service, "io.systemd.DropIn")) |
8fbb1941 | 133 | *ret = USERDB_DROPIN_ONLY|USERDB_AVOID_MULTIPLEXER; |
134ff8f4 LP |
134 | else if (streq_ptr(service, "io.systemd.Multiplexer")) |
135 | *ret = USERDB_AVOID_MULTIPLEXER; | |
136 | else | |
25ff515b | 137 | return sd_varlink_error(link, "io.systemd.UserDatabase.BadService", NULL); |
134ff8f4 LP |
138 | |
139 | return 0; | |
140 | } | |
141 | ||
25ff515b | 142 | static int vl_method_get_user_record(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) { |
d093b62c | 143 | |
309a747f | 144 | static const sd_json_dispatch_field dispatch_table[] = { |
5ec4933d LP |
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 }, | |
d093b62c LP |
152 | {} |
153 | }; | |
154 | ||
309a747f | 155 | _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL; |
d093b62c | 156 | _cleanup_(user_record_unrefp) UserRecord *hr = NULL; |
5ec4933d | 157 | _cleanup_(lookup_parameters_done) LookupParameters p = { |
d093b62c | 158 | .uid = UID_INVALID, |
5ec4933d | 159 | .match = USERDB_MATCH_NULL, |
d093b62c | 160 | }; |
134ff8f4 | 161 | UserDBFlags userdb_flags; |
d093b62c LP |
162 | int r; |
163 | ||
164 | assert(parameters); | |
165 | ||
25ff515b | 166 | r = sd_varlink_dispatch(link, parameters, dispatch_table, &p); |
f1b622a0 | 167 | if (r != 0) |
d093b62c LP |
168 | return r; |
169 | ||
134ff8f4 | 170 | r = userdb_flags_from_service(link, p.service, &userdb_flags); |
18e94a40 LP |
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' */ | |
134ff8f4 | 173 | return r; |
d093b62c | 174 | |
134ff8f4 | 175 | if (uid_is_valid(p.uid)) |
5ec4933d | 176 | r = userdb_by_uid(p.uid, &p.match, userdb_flags, &hr); |
45e587d8 | 177 | else if (p.name) |
5ec4933d | 178 | r = userdb_by_name(p.name, &p.match, userdb_flags, &hr); |
134ff8f4 LP |
179 | else { |
180 | _cleanup_(userdb_iterator_freep) UserDBIterator *iterator = NULL; | |
309a747f | 181 | _cleanup_(sd_json_variant_unrefp) sd_json_variant *last = NULL; |
d093b62c | 182 | |
5ec4933d | 183 | r = userdb_all(&p.match, userdb_flags, &iterator); |
e908961d LP |
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. */ | |
25ff515b | 191 | return sd_varlink_error(link, "io.systemd.UserDatabase.NoRecordFound", NULL); |
134ff8f4 LP |
192 | if (r < 0) |
193 | return r; | |
d093b62c | 194 | |
134ff8f4 LP |
195 | for (;;) { |
196 | _cleanup_(user_record_unrefp) UserRecord *z = NULL; | |
d093b62c | 197 | |
5ec4933d | 198 | r = userdb_iterator_get(iterator, &p.match, &z); |
134ff8f4 LP |
199 | if (r == -ESRCH) |
200 | break; | |
201 | if (r < 0) | |
202 | return r; | |
d093b62c | 203 | |
134ff8f4 | 204 | if (last) { |
25ff515b | 205 | r = sd_varlink_notify(link, last); |
d093b62c | 206 | if (r < 0) |
d093b62c | 207 | return r; |
d093b62c | 208 | |
309a747f | 209 | last = sd_json_variant_unref(last); |
d093b62c LP |
210 | } |
211 | ||
134ff8f4 | 212 | r = build_user_json(link, z, &last); |
d093b62c LP |
213 | if (r < 0) |
214 | return r; | |
134ff8f4 | 215 | } |
d093b62c | 216 | |
134ff8f4 | 217 | if (!last) |
25ff515b | 218 | return sd_varlink_error(link, "io.systemd.UserDatabase.NoRecordFound", NULL); |
d093b62c | 219 | |
25ff515b | 220 | return sd_varlink_reply(link, last); |
134ff8f4 | 221 | } |
d093b62c | 222 | if (r == -ESRCH) |
25ff515b | 223 | return sd_varlink_error(link, "io.systemd.UserDatabase.NoRecordFound", NULL); |
5ec4933d LP |
224 | if (r == -ENOEXEC) |
225 | return sd_varlink_error(link, "io.systemd.UserDatabase.NonMatchingRecordFound", NULL); | |
d093b62c LP |
226 | if (r < 0) { |
227 | log_debug_errno(r, "User lookup failed abnormally: %m"); | |
25ff515b | 228 | return sd_varlink_error(link, "io.systemd.UserDatabase.ServiceNotAvailable", NULL); |
d093b62c LP |
229 | } |
230 | ||
231 | if ((uid_is_valid(p.uid) && hr->uid != p.uid) || | |
45e587d8 | 232 | (p.name && !user_record_matches_user_name(hr, p.name))) |
25ff515b | 233 | return sd_varlink_error(link, "io.systemd.UserDatabase.ConflictingRecordFound", NULL); |
d093b62c LP |
234 | |
235 | r = build_user_json(link, hr, &v); | |
236 | if (r < 0) | |
237 | return r; | |
238 | ||
25ff515b | 239 | return sd_varlink_reply(link, v); |
d093b62c LP |
240 | } |
241 | ||
25ff515b | 242 | static int build_group_json(sd_varlink *link, GroupRecord *gr, sd_json_variant **ret) { |
d093b62c | 243 | _cleanup_(group_record_unrefp) GroupRecord *stripped = NULL; |
309a747f | 244 | _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL; |
d093b62c LP |
245 | UserRecordLoadFlags flags; |
246 | uid_t peer_uid; | |
247 | bool trusted; | |
248 | int r; | |
249 | ||
250 | assert(gr); | |
251 | assert(ret); | |
252 | ||
25ff515b | 253 | r = sd_varlink_get_peer_uid(link, &peer_uid); |
d093b62c LP |
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 | ||
bfc0cc1a | 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; |
d093b62c LP |
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 | ||
309a747f | 275 | v = sd_json_variant_ref(gr->json); |
d093b62c LP |
276 | r = add_nss_service(&v); |
277 | if (r < 0) | |
278 | return r; | |
279 | ||
be5bee2a LP |
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))); | |
d093b62c LP |
284 | } |
285 | ||
25ff515b | 286 | static int vl_method_get_group_record(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) { |
d093b62c | 287 | |
309a747f | 288 | static const sd_json_dispatch_field dispatch_table[] = { |
5ec4933d LP |
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 }, | |
d093b62c LP |
296 | {} |
297 | }; | |
298 | ||
309a747f | 299 | _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL; |
d093b62c | 300 | _cleanup_(group_record_unrefp) GroupRecord *g = NULL; |
5ec4933d | 301 | _cleanup_(lookup_parameters_done) LookupParameters p = { |
d093b62c | 302 | .gid = GID_INVALID, |
5ec4933d | 303 | .match = USERDB_MATCH_NULL, |
d093b62c | 304 | }; |
134ff8f4 | 305 | UserDBFlags userdb_flags; |
d093b62c LP |
306 | int r; |
307 | ||
308 | assert(parameters); | |
309 | ||
25ff515b | 310 | r = sd_varlink_dispatch(link, parameters, dispatch_table, &p); |
f1b622a0 | 311 | if (r != 0) |
d093b62c LP |
312 | return r; |
313 | ||
134ff8f4 | 314 | r = userdb_flags_from_service(link, p.service, &userdb_flags); |
18e94a40 | 315 | if (r != 0) |
134ff8f4 | 316 | return r; |
d093b62c | 317 | |
134ff8f4 | 318 | if (gid_is_valid(p.gid)) |
5ec4933d | 319 | r = groupdb_by_gid(p.gid, &p.match, userdb_flags, &g); |
45e587d8 | 320 | else if (p.name) |
5ec4933d | 321 | r = groupdb_by_name(p.name, &p.match, userdb_flags, &g); |
134ff8f4 LP |
322 | else { |
323 | _cleanup_(userdb_iterator_freep) UserDBIterator *iterator = NULL; | |
309a747f | 324 | _cleanup_(sd_json_variant_unrefp) sd_json_variant *last = NULL; |
d093b62c | 325 | |
5ec4933d | 326 | r = groupdb_all(&p.match, userdb_flags, &iterator); |
e908961d | 327 | if (IN_SET(r, -ESRCH, -ENOLINK)) |
25ff515b | 328 | return sd_varlink_error(link, "io.systemd.UserDatabase.NoRecordFound", NULL); |
134ff8f4 LP |
329 | if (r < 0) |
330 | return r; | |
d093b62c | 331 | |
134ff8f4 LP |
332 | for (;;) { |
333 | _cleanup_(group_record_unrefp) GroupRecord *z = NULL; | |
d093b62c | 334 | |
5ec4933d | 335 | r = groupdb_iterator_get(iterator, &p.match, &z); |
134ff8f4 LP |
336 | if (r == -ESRCH) |
337 | break; | |
338 | if (r < 0) | |
339 | return r; | |
d093b62c | 340 | |
134ff8f4 | 341 | if (last) { |
25ff515b | 342 | r = sd_varlink_notify(link, last); |
d093b62c | 343 | if (r < 0) |
d093b62c | 344 | return r; |
d093b62c | 345 | |
309a747f | 346 | last = sd_json_variant_unref(last); |
d093b62c LP |
347 | } |
348 | ||
134ff8f4 | 349 | r = build_group_json(link, z, &last); |
d093b62c LP |
350 | if (r < 0) |
351 | return r; | |
134ff8f4 | 352 | } |
d093b62c | 353 | |
134ff8f4 | 354 | if (!last) |
25ff515b | 355 | return sd_varlink_error(link, "io.systemd.UserDatabase.NoRecordFound", NULL); |
d093b62c | 356 | |
25ff515b | 357 | return sd_varlink_reply(link, last); |
134ff8f4 | 358 | } |
d093b62c | 359 | if (r == -ESRCH) |
25ff515b | 360 | return sd_varlink_error(link, "io.systemd.UserDatabase.NoRecordFound", NULL); |
5ec4933d LP |
361 | if (r == -ENOEXEC) |
362 | return sd_varlink_error(link, "io.systemd.UserDatabase.NonMatchingRecordFound", NULL); | |
d093b62c LP |
363 | if (r < 0) { |
364 | log_debug_errno(r, "Group lookup failed abnormally: %m"); | |
25ff515b | 365 | return sd_varlink_error(link, "io.systemd.UserDatabase.ServiceNotAvailable", NULL); |
d093b62c LP |
366 | } |
367 | ||
368 | if ((uid_is_valid(p.gid) && g->gid != p.gid) || | |
45e587d8 | 369 | (p.name && !group_record_matches_group_name(g, p.name))) |
25ff515b | 370 | return sd_varlink_error(link, "io.systemd.UserDatabase.ConflictingRecordFound", NULL); |
d093b62c LP |
371 | |
372 | r = build_group_json(link, g, &v); | |
373 | if (r < 0) | |
374 | return r; | |
375 | ||
25ff515b | 376 | return sd_varlink_reply(link, v); |
d093b62c LP |
377 | } |
378 | ||
45e587d8 LP |
379 | typedef struct MembershipLookupParameters { |
380 | const char *user_name; | |
381 | const char *group_name; | |
382 | const char *service; | |
383 | } MembershipLookupParameters; | |
384 | ||
25ff515b | 385 | static int vl_method_get_memberships(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) { |
309a747f | 386 | static const sd_json_dispatch_field dispatch_table[] = { |
45e587d8 LP |
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 }, | |
d093b62c LP |
390 | {} |
391 | }; | |
392 | ||
134ff8f4 LP |
393 | _cleanup_free_ char *last_user_name = NULL, *last_group_name = NULL; |
394 | _cleanup_(userdb_iterator_freep) UserDBIterator *iterator = NULL; | |
45e587d8 | 395 | MembershipLookupParameters p = {}; |
134ff8f4 | 396 | UserDBFlags userdb_flags; |
d093b62c LP |
397 | int r; |
398 | ||
399 | assert(parameters); | |
400 | ||
25ff515b | 401 | r = sd_varlink_dispatch(link, parameters, dispatch_table, &p); |
f1b622a0 | 402 | if (r != 0) |
d093b62c LP |
403 | return r; |
404 | ||
134ff8f4 | 405 | r = userdb_flags_from_service(link, p.service, &userdb_flags); |
18e94a40 | 406 | if (r != 0) |
134ff8f4 | 407 | return r; |
d093b62c | 408 | |
134ff8f4 LP |
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); | |
e908961d | 415 | if (IN_SET(r, -ESRCH, -ENOLINK)) |
25ff515b | 416 | return sd_varlink_error(link, "io.systemd.UserDatabase.NoRecordFound", NULL); |
134ff8f4 LP |
417 | if (r < 0) |
418 | return r; | |
d093b62c | 419 | |
134ff8f4 LP |
420 | for (;;) { |
421 | _cleanup_free_ char *user_name = NULL, *group_name = NULL; | |
d093b62c | 422 | |
134ff8f4 LP |
423 | r = membershipdb_iterator_get(iterator, &user_name, &group_name); |
424 | if (r == -ESRCH) | |
425 | break; | |
d093b62c LP |
426 | if (r < 0) |
427 | return r; | |
428 | ||
134ff8f4 LP |
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; | |
d093b62c | 432 | |
134ff8f4 LP |
433 | if (last_user_name) { |
434 | assert(last_group_name); | |
435 | ||
25ff515b | 436 | r = sd_varlink_notifybo( |
be5bee2a LP |
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))); | |
d093b62c LP |
440 | if (r < 0) |
441 | return r; | |
d093b62c LP |
442 | } |
443 | ||
6ac65492 YW |
444 | free_and_replace(last_user_name, user_name); |
445 | free_and_replace(last_group_name, group_name); | |
134ff8f4 | 446 | } |
d093b62c | 447 | |
134ff8f4 LP |
448 | if (!last_user_name) { |
449 | assert(!last_group_name); | |
25ff515b | 450 | return sd_varlink_error(link, "io.systemd.UserDatabase.NoRecordFound", NULL); |
d093b62c LP |
451 | } |
452 | ||
134ff8f4 LP |
453 | assert(last_group_name); |
454 | ||
25ff515b | 455 | return sd_varlink_replybo( |
be5bee2a LP |
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))); | |
d093b62c LP |
459 | } |
460 | ||
25ff515b | 461 | static int process_connection(sd_varlink_server *server, int _fd) { |
d562667f | 462 | _cleanup_close_ int fd = TAKE_FD(_fd); /* always take possession */ |
25ff515b | 463 | _cleanup_(sd_varlink_close_unrefp) sd_varlink *vl = NULL; |
d093b62c LP |
464 | int r; |
465 | ||
25ff515b LP |
466 | assert(server); |
467 | assert(fd >= 0); | |
468 | ||
469 | r = sd_varlink_server_add_connection(server, fd, &vl); | |
523121d5 | 470 | if (r < 0) |
d093b62c | 471 | return log_error_errno(r, "Failed to add connection: %m"); |
d093b62c | 472 | |
523121d5 | 473 | TAKE_FD(fd); |
25ff515b | 474 | vl = sd_varlink_ref(vl); |
d093b62c LP |
475 | |
476 | for (;;) { | |
25ff515b | 477 | r = sd_varlink_process(vl); |
d093b62c LP |
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 | ||
25ff515b | 487 | r = sd_varlink_wait(vl, CONNECTION_IDLE_USEC); |
d093b62c LP |
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; | |
25ff515b | 499 | _cleanup_(sd_varlink_server_unrefp) sd_varlink_server *server = NULL; |
7c695bea | 500 | _cleanup_(pidref_done) PidRef parent = PIDREF_NULL; |
d093b62c LP |
501 | unsigned n_iterations = 0; |
502 | int m, listen_fd, r; | |
503 | ||
d2acb93d | 504 | log_setup(); |
d093b62c LP |
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 | ||
fd409ff0 | 520 | r = varlink_server_new(&server, 0, NULL); |
d093b62c | 521 | if (r < 0) |
fd409ff0 | 522 | return log_error_errno(r, "Failed to allocate varlink server: %m"); |
d093b62c | 523 | |
25ff515b | 524 | r = sd_varlink_server_add_interface(server, &vl_interface_io_systemd_UserDatabase); |
abef4a7b LP |
525 | if (r < 0) |
526 | return log_error_errno(r, "Failed to add UserDatabase interface to varlink server: %m"); | |
527 | ||
25ff515b | 528 | r = sd_varlink_server_bind_method_many( |
d093b62c LP |
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 | ||
037b0a47 LP |
541 | r = userdb_block_nss_systemd(true); |
542 | if (r < 0) | |
d093b62c LP |
543 | return log_error_errno(r, "Failed to disable userdb NSS compatibility: %m"); |
544 | ||
7c695bea LP |
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 | ||
d093b62c LP |
551 | start_time = now(CLOCK_MONOTONIC); |
552 | ||
553 | for (;;) { | |
254d1313 | 554 | _cleanup_close_ int fd = -EBADF; |
d093b62c LP |
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)) { | |
e14db350 | 565 | log_debug("Exiting worker, ran for %s, that's enough.", |
5291f26d | 566 | FORMAT_TIMESPAN(usec_sub_unsigned(n, start_time), 0)); |
d093b62c LP |
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)) { | |
40fd0a77 | 573 | log_debug("Exiting worker, been idle for %s.", |
5291f26d | 574 | FORMAT_TIMESPAN(usec_sub_unsigned(n, last_busy_usec), 0)); |
d093b62c LP |
575 | break; |
576 | } | |
577 | ||
578 | (void) rename_process("systemd-userwork: waiting..."); | |
7c248223 | 579 | fd = RET_NERRNO(accept4(listen_fd, NULL, NULL, SOCK_NONBLOCK|SOCK_CLOEXEC)); |
d093b62c LP |
580 | (void) rename_process("systemd-userwork: processing..."); |
581 | ||
582 | if (fd == -EAGAIN) | |
e14db350 | 583 | continue; /* The listening socket has SO_RECVTIMEO set, hence a timeout is expected |
d093b62c LP |
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)) { | |
d093b62c LP |
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 | ||
0f2d351f LP |
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"); | |
d093b62c | 598 | |
0f2d351f | 599 | if (FLAGS_SET(r, POLLIN)) { |
7c695bea LP |
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"); | |
d093b62c LP |
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); |