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