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