]>
Commit | Line | Data |
---|---|---|
db9ecf05 | 1 | /* SPDX-License-Identifier: LGPL-2.1-or-later */ |
269e4d2d LP |
2 | |
3 | #include "bus-internal.h" | |
4 | #include "bus-message.h" | |
5 | #include "bus-polkit.h" | |
73d3ac8e | 6 | #include "bus-util.h" |
269e4d2d LP |
7 | #include "strv.h" |
8 | #include "user-util.h" | |
9 | ||
10 | static int check_good_user(sd_bus_message *m, uid_t good_user) { | |
11 | _cleanup_(sd_bus_creds_unrefp) sd_bus_creds *creds = NULL; | |
12 | uid_t sender_uid; | |
13 | int r; | |
14 | ||
15 | assert(m); | |
16 | ||
17 | if (good_user == UID_INVALID) | |
18 | return 0; | |
19 | ||
20 | r = sd_bus_query_sender_creds(m, SD_BUS_CREDS_EUID, &creds); | |
21 | if (r < 0) | |
22 | return r; | |
23 | ||
24 | /* Don't trust augmented credentials for authorization */ | |
25 | assert_return((sd_bus_creds_get_augmented_mask(creds) & SD_BUS_CREDS_EUID) == 0, -EPERM); | |
26 | ||
27 | r = sd_bus_creds_get_euid(creds, &sender_uid); | |
28 | if (r < 0) | |
29 | return r; | |
30 | ||
31 | return sender_uid == good_user; | |
32 | } | |
33 | ||
95f82ae9 LP |
34 | #if ENABLE_POLKIT |
35 | static int bus_message_append_strv_key_value( | |
36 | sd_bus_message *m, | |
37 | const char **l) { | |
38 | ||
95f82ae9 LP |
39 | int r; |
40 | ||
41 | assert(m); | |
42 | ||
43 | r = sd_bus_message_open_container(m, 'a', "{ss}"); | |
44 | if (r < 0) | |
45 | return r; | |
46 | ||
47 | STRV_FOREACH_PAIR(k, v, l) { | |
48 | r = sd_bus_message_append(m, "{ss}", *k, *v); | |
49 | if (r < 0) | |
50 | return r; | |
51 | } | |
52 | ||
53 | r = sd_bus_message_close_container(m); | |
54 | if (r < 0) | |
55 | return r; | |
56 | ||
57 | return r; | |
58 | } | |
9f657af4 DT |
59 | |
60 | static int bus_message_new_polkit_auth_call( | |
61 | sd_bus_message *m, | |
62 | const char *action, | |
63 | const char **details, | |
64 | bool interactive, | |
65 | sd_bus_message **ret) { | |
66 | ||
67 | _cleanup_(sd_bus_message_unrefp) sd_bus_message *c = NULL; | |
68 | const char *sender; | |
69 | int r; | |
70 | ||
71 | assert(m); | |
72 | assert(action); | |
73 | assert(ret); | |
74 | ||
75 | sender = sd_bus_message_get_sender(m); | |
76 | if (!sender) | |
77 | return -EBADMSG; | |
78 | ||
79 | r = sd_bus_message_new_method_call( | |
80 | ASSERT_PTR(m->bus), | |
81 | &c, | |
82 | "org.freedesktop.PolicyKit1", | |
83 | "/org/freedesktop/PolicyKit1/Authority", | |
84 | "org.freedesktop.PolicyKit1.Authority", | |
85 | "CheckAuthorization"); | |
86 | if (r < 0) | |
87 | return r; | |
88 | ||
89 | r = sd_bus_message_append(c, "(sa{sv})s", "system-bus-name", 1, "name", "s", sender, action); | |
90 | if (r < 0) | |
91 | return r; | |
92 | ||
93 | r = bus_message_append_strv_key_value(c, details); | |
94 | if (r < 0) | |
95 | return r; | |
96 | ||
97 | r = sd_bus_message_append(c, "us", interactive, NULL); | |
98 | if (r < 0) | |
99 | return r; | |
100 | ||
101 | *ret = TAKE_PTR(c); | |
102 | return 0; | |
103 | } | |
95f82ae9 LP |
104 | #endif |
105 | ||
269e4d2d LP |
106 | int bus_test_polkit( |
107 | sd_bus_message *call, | |
108 | int capability, | |
109 | const char *action, | |
110 | const char **details, | |
111 | uid_t good_user, | |
112 | bool *_challenge, | |
773b1a79 | 113 | sd_bus_error *ret_error) { |
269e4d2d LP |
114 | |
115 | int r; | |
116 | ||
117 | assert(call); | |
118 | assert(action); | |
119 | ||
120 | /* Tests non-interactively! */ | |
121 | ||
122 | r = check_good_user(call, good_user); | |
123 | if (r != 0) | |
124 | return r; | |
125 | ||
126 | r = sd_bus_query_sender_privilege(call, capability); | |
127 | if (r < 0) | |
128 | return r; | |
129 | else if (r > 0) | |
130 | return 1; | |
269e4d2d | 131 | |
d32ac157 DT |
132 | #if ENABLE_POLKIT |
133 | _cleanup_(sd_bus_message_unrefp) sd_bus_message *request = NULL; | |
134 | _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; | |
135 | int authorized = false, challenge = false; | |
269e4d2d | 136 | |
d32ac157 DT |
137 | r = bus_message_new_polkit_auth_call(call, action, details, /* interactive = */ false, &request); |
138 | if (r < 0) | |
139 | return r; | |
269e4d2d | 140 | |
d32ac157 DT |
141 | r = sd_bus_call(call->bus, request, 0, ret_error, &reply); |
142 | if (r < 0) { | |
143 | /* Treat no PK available as access denied */ | |
144 | if (bus_error_is_unknown_service(ret_error)) { | |
145 | sd_bus_error_free(ret_error); | |
146 | return -EACCES; | |
269e4d2d LP |
147 | } |
148 | ||
d32ac157 DT |
149 | return r; |
150 | } | |
269e4d2d | 151 | |
d32ac157 DT |
152 | r = sd_bus_message_enter_container(reply, 'r', "bba{ss}"); |
153 | if (r < 0) | |
154 | return r; | |
155 | ||
156 | r = sd_bus_message_read(reply, "bb", &authorized, &challenge); | |
157 | if (r < 0) | |
158 | return r; | |
269e4d2d | 159 | |
d32ac157 DT |
160 | if (authorized) |
161 | return 1; | |
269e4d2d | 162 | |
d32ac157 DT |
163 | if (_challenge) { |
164 | *_challenge = challenge; | |
165 | return 0; | |
269e4d2d LP |
166 | } |
167 | #endif | |
168 | ||
169 | return -EACCES; | |
170 | } | |
171 | ||
172 | #if ENABLE_POLKIT | |
173 | ||
174 | typedef struct AsyncPolkitQuery { | |
7f569822 LP |
175 | char *action; |
176 | char **details; | |
177 | ||
269e4d2d | 178 | sd_bus_message *request, *reply; |
269e4d2d | 179 | sd_bus_slot *slot; |
63748626 | 180 | |
269e4d2d | 181 | Hashmap *registry; |
63748626 | 182 | sd_event_source *defer_event_source; |
269e4d2d LP |
183 | } AsyncPolkitQuery; |
184 | ||
bc8187f7 | 185 | static AsyncPolkitQuery *async_polkit_query_free(AsyncPolkitQuery *q) { |
269e4d2d | 186 | if (!q) |
bc8187f7 | 187 | return NULL; |
269e4d2d LP |
188 | |
189 | sd_bus_slot_unref(q->slot); | |
190 | ||
191 | if (q->registry && q->request) | |
192 | hashmap_remove(q->registry, q->request); | |
193 | ||
194 | sd_bus_message_unref(q->request); | |
195 | sd_bus_message_unref(q->reply); | |
196 | ||
7f569822 LP |
197 | free(q->action); |
198 | strv_free(q->details); | |
199 | ||
63748626 | 200 | sd_event_source_disable_unref(q->defer_event_source); |
bc8187f7 DT |
201 | |
202 | return mfree(q); | |
269e4d2d LP |
203 | } |
204 | ||
63748626 | 205 | static int async_polkit_defer(sd_event_source *s, void *userdata) { |
5a93e5df | 206 | AsyncPolkitQuery *q = ASSERT_PTR(userdata); |
63748626 LP |
207 | |
208 | assert(s); | |
209 | ||
210 | /* This is called as idle event source after we processed the async polkit reply, hopefully after the | |
211 | * method call we re-enqueued has been properly processed. */ | |
212 | ||
213 | async_polkit_query_free(q); | |
214 | return 0; | |
215 | } | |
216 | ||
269e4d2d | 217 | static int async_polkit_callback(sd_bus_message *reply, void *userdata, sd_bus_error *error) { |
99534007 | 218 | AsyncPolkitQuery *q = ASSERT_PTR(userdata); |
269e4d2d LP |
219 | int r; |
220 | ||
221 | assert(reply); | |
269e4d2d | 222 | |
63748626 | 223 | assert(q->slot); |
269e4d2d | 224 | q->slot = sd_bus_slot_unref(q->slot); |
63748626 LP |
225 | |
226 | assert(!q->reply); | |
269e4d2d LP |
227 | q->reply = sd_bus_message_ref(reply); |
228 | ||
63748626 LP |
229 | /* Now, let's dispatch the original message a second time be re-enqueing. This will then traverse the |
230 | * whole message processing again, and thus re-validating and re-retrieving the "userdata" field | |
231 | * again. | |
232 | * | |
233 | * We install an idle event loop event to clean-up the PolicyKit request data when we are idle again, | |
234 | * i.e. after the second time the message is processed is complete. */ | |
235 | ||
236 | assert(!q->defer_event_source); | |
237 | r = sd_event_add_defer(sd_bus_get_event(sd_bus_message_get_bus(reply)), &q->defer_event_source, async_polkit_defer, q); | |
238 | if (r < 0) | |
239 | goto fail; | |
240 | ||
241 | r = sd_event_source_set_priority(q->defer_event_source, SD_EVENT_PRIORITY_IDLE); | |
242 | if (r < 0) | |
243 | goto fail; | |
244 | ||
245 | r = sd_event_source_set_enabled(q->defer_event_source, SD_EVENT_ONESHOT); | |
246 | if (r < 0) | |
247 | goto fail; | |
248 | ||
269e4d2d | 249 | r = sd_bus_message_rewind(q->request, true); |
63748626 LP |
250 | if (r < 0) |
251 | goto fail; | |
252 | ||
bc130b68 | 253 | r = sd_bus_enqueue_for_read(sd_bus_message_get_bus(q->request), q->request); |
63748626 LP |
254 | if (r < 0) |
255 | goto fail; | |
269e4d2d | 256 | |
63748626 | 257 | return 1; |
269e4d2d | 258 | |
63748626 LP |
259 | fail: |
260 | log_debug_errno(r, "Processing asynchronous PolicyKit reply failed, ignoring: %m"); | |
261 | (void) sd_bus_reply_method_errno(q->request, r, NULL); | |
269e4d2d | 262 | async_polkit_query_free(q); |
269e4d2d LP |
263 | return r; |
264 | } | |
265 | ||
d2c50a17 DT |
266 | static int process_polkit_response( |
267 | AsyncPolkitQuery *q, | |
268 | sd_bus_message *call, | |
269 | const char *action, | |
270 | const char **details, | |
271 | Hashmap **registry, | |
272 | sd_bus_error *ret_error) { | |
273 | ||
274 | int authorized, challenge, r; | |
275 | ||
276 | assert(q); | |
277 | assert(call); | |
278 | assert(action); | |
279 | assert(registry); | |
280 | assert(ret_error); | |
281 | ||
282 | assert(q->action); | |
283 | assert(q->reply); | |
284 | ||
285 | /* If the operation we want to authenticate changed between the first and the second time, | |
286 | * let's not use this authentication, it might be out of date as the object and context we | |
287 | * operate on might have changed. */ | |
288 | if (!streq(q->action, action) || !strv_equal(q->details, (char**) details)) | |
289 | return -ESTALE; | |
290 | ||
291 | if (sd_bus_message_is_method_error(q->reply, NULL)) { | |
292 | const sd_bus_error *e; | |
293 | ||
294 | e = sd_bus_message_get_error(q->reply); | |
295 | ||
296 | /* Treat no PK available as access denied */ | |
297 | if (bus_error_is_unknown_service(e)) | |
298 | return -EACCES; | |
299 | ||
300 | /* Copy error from polkit reply */ | |
301 | sd_bus_error_copy(ret_error, e); | |
302 | return -sd_bus_error_get_errno(e); | |
303 | } | |
304 | ||
305 | r = sd_bus_message_enter_container(q->reply, 'r', "bba{ss}"); | |
306 | if (r >= 0) | |
307 | r = sd_bus_message_read(q->reply, "bb", &authorized, &challenge); | |
308 | if (r < 0) | |
309 | return r; | |
310 | ||
311 | if (authorized) | |
312 | return 1; | |
313 | ||
314 | if (challenge) | |
315 | return sd_bus_error_set(ret_error, SD_BUS_ERROR_INTERACTIVE_AUTHORIZATION_REQUIRED, "Interactive authentication required."); | |
316 | ||
317 | return -EACCES; | |
318 | } | |
319 | ||
269e4d2d LP |
320 | #endif |
321 | ||
322 | int bus_verify_polkit_async( | |
323 | sd_bus_message *call, | |
324 | int capability, | |
325 | const char *action, | |
326 | const char **details, | |
327 | bool interactive, | |
328 | uid_t good_user, | |
329 | Hashmap **registry, | |
773b1a79 | 330 | sd_bus_error *ret_error) { |
269e4d2d | 331 | |
269e4d2d LP |
332 | int r; |
333 | ||
334 | assert(call); | |
335 | assert(action); | |
336 | assert(registry); | |
337 | ||
338 | r = check_good_user(call, good_user); | |
339 | if (r != 0) | |
340 | return r; | |
341 | ||
342 | #if ENABLE_POLKIT | |
d5a99b7c | 343 | AsyncPolkitQuery *q = hashmap_get(*registry, call); |
d2c50a17 DT |
344 | /* This is the second invocation of this function, and there's already a response from |
345 | * polkit, let's process it */ | |
346 | if (q) | |
347 | return process_polkit_response(q, call, action, details, registry, ret_error); | |
269e4d2d LP |
348 | #endif |
349 | ||
350 | r = sd_bus_query_sender_privilege(call, capability); | |
351 | if (r < 0) | |
352 | return r; | |
353 | else if (r > 0) | |
354 | return 1; | |
355 | ||
63748626 | 356 | #if ENABLE_POLKIT |
d5a99b7c JJ |
357 | _cleanup_(sd_bus_message_unrefp) sd_bus_message *pk = NULL; |
358 | ||
359 | int c = sd_bus_message_get_allow_interactive_authorization(call); | |
269e4d2d LP |
360 | if (c < 0) |
361 | return c; | |
362 | if (c > 0) | |
363 | interactive = true; | |
364 | ||
365 | r = hashmap_ensure_allocated(registry, NULL); | |
366 | if (r < 0) | |
367 | return r; | |
368 | ||
9f657af4 | 369 | r = bus_message_new_polkit_auth_call(call, action, details, interactive, &pk); |
269e4d2d LP |
370 | if (r < 0) |
371 | return r; | |
372 | ||
f4425c72 | 373 | q = new(AsyncPolkitQuery, 1); |
269e4d2d LP |
374 | if (!q) |
375 | return -ENOMEM; | |
376 | ||
f4425c72 LP |
377 | *q = (AsyncPolkitQuery) { |
378 | .request = sd_bus_message_ref(call), | |
f4425c72 | 379 | }; |
269e4d2d | 380 | |
7f569822 LP |
381 | q->action = strdup(action); |
382 | if (!q->action) { | |
383 | async_polkit_query_free(q); | |
384 | return -ENOMEM; | |
385 | } | |
386 | ||
387 | q->details = strv_copy((char**) details); | |
388 | if (!q->details) { | |
389 | async_polkit_query_free(q); | |
390 | return -ENOMEM; | |
391 | } | |
392 | ||
269e4d2d LP |
393 | r = hashmap_put(*registry, call, q); |
394 | if (r < 0) { | |
395 | async_polkit_query_free(q); | |
396 | return r; | |
397 | } | |
398 | ||
399 | q->registry = *registry; | |
400 | ||
401 | r = sd_bus_call_async(call->bus, &q->slot, pk, async_polkit_callback, q, 0); | |
402 | if (r < 0) { | |
403 | async_polkit_query_free(q); | |
404 | return r; | |
405 | } | |
406 | ||
407 | return 0; | |
408 | #endif | |
409 | ||
410 | return -EACCES; | |
411 | } | |
412 | ||
3c2f8472 | 413 | Hashmap *bus_verify_polkit_async_registry_free(Hashmap *registry) { |
269e4d2d | 414 | #if ENABLE_POLKIT |
3c2f8472 YW |
415 | return hashmap_free_with_destructor(registry, async_polkit_query_free); |
416 | #else | |
417 | assert(hashmap_isempty(registry)); | |
418 | return hashmap_free(registry); | |
269e4d2d LP |
419 | #endif |
420 | } |