]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/shared/bus-polkit.c
bus-polkit: drop unnecessary else
[thirdparty/systemd.git] / src / shared / bus-polkit.c
1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
2
3 #include "bus-internal.h"
4 #include "bus-message.h"
5 #include "bus-polkit.h"
6 #include "bus-util.h"
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
34 #if ENABLE_POLKIT
35 static int bus_message_append_strv_key_value(
36 sd_bus_message *m,
37 const char **l) {
38
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
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 }
104 #endif
105
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,
113 sd_bus_error *ret_error) {
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 if (r > 0)
130 return 1;
131
132 #if ENABLE_POLKIT
133 _cleanup_(sd_bus_message_unrefp) sd_bus_message *request = NULL, *reply = NULL;
134 int authorized = false, challenge = false;
135
136 r = bus_message_new_polkit_auth_call(call, action, details, /* interactive = */ false, &request);
137 if (r < 0)
138 return r;
139
140 r = sd_bus_call(call->bus, request, 0, ret_error, &reply);
141 if (r < 0) {
142 /* Treat no PK available as access denied */
143 if (bus_error_is_unknown_service(ret_error)) {
144 sd_bus_error_free(ret_error);
145 return -EACCES;
146 }
147
148 return r;
149 }
150
151 r = sd_bus_message_enter_container(reply, 'r', "bba{ss}");
152 if (r < 0)
153 return r;
154
155 r = sd_bus_message_read(reply, "bb", &authorized, &challenge);
156 if (r < 0)
157 return r;
158
159 if (authorized)
160 return 1;
161
162 if (_challenge) {
163 *_challenge = challenge;
164 return 0;
165 }
166 #endif
167
168 return -EACCES;
169 }
170
171 #if ENABLE_POLKIT
172
173 typedef struct AsyncPolkitQuery {
174 char *action;
175 char **details;
176
177 sd_bus_message *request, *reply;
178 sd_bus_slot *slot;
179
180 Hashmap *registry;
181 sd_event_source *defer_event_source;
182 } AsyncPolkitQuery;
183
184 static AsyncPolkitQuery *async_polkit_query_free(AsyncPolkitQuery *q) {
185 if (!q)
186 return NULL;
187
188 sd_bus_slot_unref(q->slot);
189
190 if (q->registry && q->request)
191 hashmap_remove(q->registry, q->request);
192
193 sd_bus_message_unref(q->request);
194 sd_bus_message_unref(q->reply);
195
196 free(q->action);
197 strv_free(q->details);
198
199 sd_event_source_disable_unref(q->defer_event_source);
200
201 return mfree(q);
202 }
203
204 static int async_polkit_defer(sd_event_source *s, void *userdata) {
205 AsyncPolkitQuery *q = ASSERT_PTR(userdata);
206
207 assert(s);
208
209 /* This is called as idle event source after we processed the async polkit reply, hopefully after the
210 * method call we re-enqueued has been properly processed. */
211
212 async_polkit_query_free(q);
213 return 0;
214 }
215
216 static int async_polkit_callback(sd_bus_message *reply, void *userdata, sd_bus_error *error) {
217 AsyncPolkitQuery *q = ASSERT_PTR(userdata);
218 int r;
219
220 assert(reply);
221
222 assert(q->slot);
223 q->slot = sd_bus_slot_unref(q->slot);
224
225 assert(!q->reply);
226 q->reply = sd_bus_message_ref(reply);
227
228 /* Now, let's dispatch the original message a second time be re-enqueing. This will then traverse the
229 * whole message processing again, and thus re-validating and re-retrieving the "userdata" field
230 * again.
231 *
232 * We install an idle event loop event to clean-up the PolicyKit request data when we are idle again,
233 * i.e. after the second time the message is processed is complete. */
234
235 assert(!q->defer_event_source);
236 r = sd_event_add_defer(sd_bus_get_event(sd_bus_message_get_bus(reply)), &q->defer_event_source, async_polkit_defer, q);
237 if (r < 0)
238 goto fail;
239
240 r = sd_event_source_set_priority(q->defer_event_source, SD_EVENT_PRIORITY_IDLE);
241 if (r < 0)
242 goto fail;
243
244 r = sd_event_source_set_enabled(q->defer_event_source, SD_EVENT_ONESHOT);
245 if (r < 0)
246 goto fail;
247
248 r = sd_bus_message_rewind(q->request, true);
249 if (r < 0)
250 goto fail;
251
252 r = sd_bus_enqueue_for_read(sd_bus_message_get_bus(q->request), q->request);
253 if (r < 0)
254 goto fail;
255
256 return 1;
257
258 fail:
259 log_debug_errno(r, "Processing asynchronous PolicyKit reply failed, ignoring: %m");
260 (void) sd_bus_reply_method_errno(q->request, r, NULL);
261 async_polkit_query_free(q);
262 return r;
263 }
264
265 static int process_polkit_response(
266 AsyncPolkitQuery *q,
267 sd_bus_message *call,
268 const char *action,
269 const char **details,
270 Hashmap **registry,
271 sd_bus_error *ret_error) {
272
273 int authorized, challenge, r;
274
275 assert(q);
276 assert(call);
277 assert(action);
278 assert(registry);
279 assert(ret_error);
280
281 assert(q->action);
282 assert(q->reply);
283
284 /* If the operation we want to authenticate changed between the first and the second time,
285 * let's not use this authentication, it might be out of date as the object and context we
286 * operate on might have changed. */
287 if (!streq(q->action, action) || !strv_equal(q->details, (char**) details))
288 return -ESTALE;
289
290 if (sd_bus_message_is_method_error(q->reply, NULL)) {
291 const sd_bus_error *e;
292
293 e = sd_bus_message_get_error(q->reply);
294
295 /* Treat no PK available as access denied */
296 if (bus_error_is_unknown_service(e))
297 return -EACCES;
298
299 /* Copy error from polkit reply */
300 sd_bus_error_copy(ret_error, e);
301 return -sd_bus_error_get_errno(e);
302 }
303
304 r = sd_bus_message_enter_container(q->reply, 'r', "bba{ss}");
305 if (r >= 0)
306 r = sd_bus_message_read(q->reply, "bb", &authorized, &challenge);
307 if (r < 0)
308 return r;
309
310 if (authorized)
311 return 1;
312
313 if (challenge)
314 return sd_bus_error_set(ret_error, SD_BUS_ERROR_INTERACTIVE_AUTHORIZATION_REQUIRED, "Interactive authentication required.");
315
316 return -EACCES;
317 }
318
319 #endif
320
321 int bus_verify_polkit_async(
322 sd_bus_message *call,
323 int capability,
324 const char *action,
325 const char **details,
326 bool interactive,
327 uid_t good_user,
328 Hashmap **registry,
329 sd_bus_error *ret_error) {
330
331 int r;
332
333 assert(call);
334 assert(action);
335 assert(registry);
336
337 r = check_good_user(call, good_user);
338 if (r != 0)
339 return r;
340
341 #if ENABLE_POLKIT
342 AsyncPolkitQuery *q = hashmap_get(*registry, call);
343 /* This is the second invocation of this function, and there's already a response from
344 * polkit, let's process it */
345 if (q)
346 return process_polkit_response(q, call, action, details, registry, ret_error);
347 #endif
348
349 r = sd_bus_query_sender_privilege(call, capability);
350 if (r < 0)
351 return r;
352 if (r > 0)
353 return 1;
354
355 #if ENABLE_POLKIT
356 _cleanup_(sd_bus_message_unrefp) sd_bus_message *pk = NULL;
357
358 int c = sd_bus_message_get_allow_interactive_authorization(call);
359 if (c < 0)
360 return c;
361 if (c > 0)
362 interactive = true;
363
364 r = hashmap_ensure_allocated(registry, NULL);
365 if (r < 0)
366 return r;
367
368 r = bus_message_new_polkit_auth_call(call, action, details, interactive, &pk);
369 if (r < 0)
370 return r;
371
372 q = new(AsyncPolkitQuery, 1);
373 if (!q)
374 return -ENOMEM;
375
376 *q = (AsyncPolkitQuery) {
377 .request = sd_bus_message_ref(call),
378 };
379
380 q->action = strdup(action);
381 if (!q->action) {
382 async_polkit_query_free(q);
383 return -ENOMEM;
384 }
385
386 q->details = strv_copy((char**) details);
387 if (!q->details) {
388 async_polkit_query_free(q);
389 return -ENOMEM;
390 }
391
392 r = hashmap_put(*registry, call, q);
393 if (r < 0) {
394 async_polkit_query_free(q);
395 return r;
396 }
397
398 q->registry = *registry;
399
400 r = sd_bus_call_async(call->bus, &q->slot, pk, async_polkit_callback, q, 0);
401 if (r < 0) {
402 async_polkit_query_free(q);
403 return r;
404 }
405
406 return 0;
407 #endif
408
409 return -EACCES;
410 }
411
412 Hashmap *bus_verify_polkit_async_registry_free(Hashmap *registry) {
413 #if ENABLE_POLKIT
414 return hashmap_free_with_destructor(registry, async_polkit_query_free);
415 #else
416 assert(hashmap_isempty(registry));
417 return hashmap_free(registry);
418 #endif
419 }