]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/shared/bus-polkit.c
shared: split out polkit stuff from bus-util.c → bus-polkit.c
[thirdparty/systemd.git] / src / shared / bus-polkit.c
1 /* SPDX-License-Identifier: LGPL-2.1+ */
2
3 #include "bus-internal.h"
4 #include "bus-message.h"
5 #include "bus-polkit.h"
6 #include "strv.h"
7 #include "user-util.h"
8
9 static int check_good_user(sd_bus_message *m, uid_t good_user) {
10 _cleanup_(sd_bus_creds_unrefp) sd_bus_creds *creds = NULL;
11 uid_t sender_uid;
12 int r;
13
14 assert(m);
15
16 if (good_user == UID_INVALID)
17 return 0;
18
19 r = sd_bus_query_sender_creds(m, SD_BUS_CREDS_EUID, &creds);
20 if (r < 0)
21 return r;
22
23 /* Don't trust augmented credentials for authorization */
24 assert_return((sd_bus_creds_get_augmented_mask(creds) & SD_BUS_CREDS_EUID) == 0, -EPERM);
25
26 r = sd_bus_creds_get_euid(creds, &sender_uid);
27 if (r < 0)
28 return r;
29
30 return sender_uid == good_user;
31 }
32
33 int bus_test_polkit(
34 sd_bus_message *call,
35 int capability,
36 const char *action,
37 const char **details,
38 uid_t good_user,
39 bool *_challenge,
40 sd_bus_error *e) {
41
42 int r;
43
44 assert(call);
45 assert(action);
46
47 /* Tests non-interactively! */
48
49 r = check_good_user(call, good_user);
50 if (r != 0)
51 return r;
52
53 r = sd_bus_query_sender_privilege(call, capability);
54 if (r < 0)
55 return r;
56 else if (r > 0)
57 return 1;
58 #if ENABLE_POLKIT
59 else {
60 _cleanup_(sd_bus_message_unrefp) sd_bus_message *request = NULL;
61 _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
62 int authorized = false, challenge = false;
63 const char *sender, **k, **v;
64
65 sender = sd_bus_message_get_sender(call);
66 if (!sender)
67 return -EBADMSG;
68
69 r = sd_bus_message_new_method_call(
70 call->bus,
71 &request,
72 "org.freedesktop.PolicyKit1",
73 "/org/freedesktop/PolicyKit1/Authority",
74 "org.freedesktop.PolicyKit1.Authority",
75 "CheckAuthorization");
76 if (r < 0)
77 return r;
78
79 r = sd_bus_message_append(
80 request,
81 "(sa{sv})s",
82 "system-bus-name", 1, "name", "s", sender,
83 action);
84 if (r < 0)
85 return r;
86
87 r = sd_bus_message_open_container(request, 'a', "{ss}");
88 if (r < 0)
89 return r;
90
91 STRV_FOREACH_PAIR(k, v, details) {
92 r = sd_bus_message_append(request, "{ss}", *k, *v);
93 if (r < 0)
94 return r;
95 }
96
97 r = sd_bus_message_close_container(request);
98 if (r < 0)
99 return r;
100
101 r = sd_bus_message_append(request, "us", 0, NULL);
102 if (r < 0)
103 return r;
104
105 r = sd_bus_call(call->bus, request, 0, e, &reply);
106 if (r < 0) {
107 /* Treat no PK available as access denied */
108 if (sd_bus_error_has_name(e, SD_BUS_ERROR_SERVICE_UNKNOWN)) {
109 sd_bus_error_free(e);
110 return -EACCES;
111 }
112
113 return r;
114 }
115
116 r = sd_bus_message_enter_container(reply, 'r', "bba{ss}");
117 if (r < 0)
118 return r;
119
120 r = sd_bus_message_read(reply, "bb", &authorized, &challenge);
121 if (r < 0)
122 return r;
123
124 if (authorized)
125 return 1;
126
127 if (_challenge) {
128 *_challenge = challenge;
129 return 0;
130 }
131 }
132 #endif
133
134 return -EACCES;
135 }
136
137 #if ENABLE_POLKIT
138
139 typedef struct AsyncPolkitQuery {
140 sd_bus_message *request, *reply;
141 sd_bus_message_handler_t callback;
142 void *userdata;
143 sd_bus_slot *slot;
144 Hashmap *registry;
145 } AsyncPolkitQuery;
146
147 static void async_polkit_query_free(AsyncPolkitQuery *q) {
148
149 if (!q)
150 return;
151
152 sd_bus_slot_unref(q->slot);
153
154 if (q->registry && q->request)
155 hashmap_remove(q->registry, q->request);
156
157 sd_bus_message_unref(q->request);
158 sd_bus_message_unref(q->reply);
159
160 free(q);
161 }
162
163 static int async_polkit_callback(sd_bus_message *reply, void *userdata, sd_bus_error *error) {
164 _cleanup_(sd_bus_error_free) sd_bus_error error_buffer = SD_BUS_ERROR_NULL;
165 AsyncPolkitQuery *q = userdata;
166 int r;
167
168 assert(reply);
169 assert(q);
170
171 q->slot = sd_bus_slot_unref(q->slot);
172 q->reply = sd_bus_message_ref(reply);
173
174 r = sd_bus_message_rewind(q->request, true);
175 if (r < 0) {
176 r = sd_bus_reply_method_errno(q->request, r, NULL);
177 goto finish;
178 }
179
180 r = q->callback(q->request, q->userdata, &error_buffer);
181 r = bus_maybe_reply_error(q->request, r, &error_buffer);
182
183 finish:
184 async_polkit_query_free(q);
185
186 return r;
187 }
188
189 #endif
190
191 int bus_verify_polkit_async(
192 sd_bus_message *call,
193 int capability,
194 const char *action,
195 const char **details,
196 bool interactive,
197 uid_t good_user,
198 Hashmap **registry,
199 sd_bus_error *error) {
200
201 #if ENABLE_POLKIT
202 _cleanup_(sd_bus_message_unrefp) sd_bus_message *pk = NULL;
203 AsyncPolkitQuery *q;
204 const char *sender, **k, **v;
205 sd_bus_message_handler_t callback;
206 void *userdata;
207 int c;
208 #endif
209 int r;
210
211 assert(call);
212 assert(action);
213 assert(registry);
214
215 r = check_good_user(call, good_user);
216 if (r != 0)
217 return r;
218
219 #if ENABLE_POLKIT
220 q = hashmap_get(*registry, call);
221 if (q) {
222 int authorized, challenge;
223
224 /* This is the second invocation of this function, and
225 * there's already a response from polkit, let's
226 * process it */
227 assert(q->reply);
228
229 if (sd_bus_message_is_method_error(q->reply, NULL)) {
230 const sd_bus_error *e;
231
232 e = sd_bus_message_get_error(q->reply);
233
234 /* Treat no PK available as access denied */
235 if (sd_bus_error_has_name(e, SD_BUS_ERROR_SERVICE_UNKNOWN) ||
236 sd_bus_error_has_name(e, SD_BUS_ERROR_NAME_HAS_NO_OWNER))
237 return -EACCES;
238
239 /* Copy error from polkit reply */
240 sd_bus_error_copy(error, e);
241 return -sd_bus_error_get_errno(e);
242 }
243
244 r = sd_bus_message_enter_container(q->reply, 'r', "bba{ss}");
245 if (r >= 0)
246 r = sd_bus_message_read(q->reply, "bb", &authorized, &challenge);
247 if (r < 0)
248 return r;
249
250 if (authorized)
251 return 1;
252
253 if (challenge)
254 return sd_bus_error_set(error, SD_BUS_ERROR_INTERACTIVE_AUTHORIZATION_REQUIRED, "Interactive authentication required.");
255
256 return -EACCES;
257 }
258 #endif
259
260 r = sd_bus_query_sender_privilege(call, capability);
261 if (r < 0)
262 return r;
263 else if (r > 0)
264 return 1;
265
266 #if ENABLE_POLKIT
267 if (sd_bus_get_current_message(call->bus) != call)
268 return -EINVAL;
269
270 callback = sd_bus_get_current_handler(call->bus);
271 if (!callback)
272 return -EINVAL;
273
274 userdata = sd_bus_get_current_userdata(call->bus);
275
276 sender = sd_bus_message_get_sender(call);
277 if (!sender)
278 return -EBADMSG;
279
280 c = sd_bus_message_get_allow_interactive_authorization(call);
281 if (c < 0)
282 return c;
283 if (c > 0)
284 interactive = true;
285
286 r = hashmap_ensure_allocated(registry, NULL);
287 if (r < 0)
288 return r;
289
290 r = sd_bus_message_new_method_call(
291 call->bus,
292 &pk,
293 "org.freedesktop.PolicyKit1",
294 "/org/freedesktop/PolicyKit1/Authority",
295 "org.freedesktop.PolicyKit1.Authority",
296 "CheckAuthorization");
297 if (r < 0)
298 return r;
299
300 r = sd_bus_message_append(
301 pk,
302 "(sa{sv})s",
303 "system-bus-name", 1, "name", "s", sender,
304 action);
305 if (r < 0)
306 return r;
307
308 r = sd_bus_message_open_container(pk, 'a', "{ss}");
309 if (r < 0)
310 return r;
311
312 STRV_FOREACH_PAIR(k, v, details) {
313 r = sd_bus_message_append(pk, "{ss}", *k, *v);
314 if (r < 0)
315 return r;
316 }
317
318 r = sd_bus_message_close_container(pk);
319 if (r < 0)
320 return r;
321
322 r = sd_bus_message_append(pk, "us", interactive, NULL);
323 if (r < 0)
324 return r;
325
326 q = new0(AsyncPolkitQuery, 1);
327 if (!q)
328 return -ENOMEM;
329
330 q->request = sd_bus_message_ref(call);
331 q->callback = callback;
332 q->userdata = userdata;
333
334 r = hashmap_put(*registry, call, q);
335 if (r < 0) {
336 async_polkit_query_free(q);
337 return r;
338 }
339
340 q->registry = *registry;
341
342 r = sd_bus_call_async(call->bus, &q->slot, pk, async_polkit_callback, q, 0);
343 if (r < 0) {
344 async_polkit_query_free(q);
345 return r;
346 }
347
348 return 0;
349 #endif
350
351 return -EACCES;
352 }
353
354 void bus_verify_polkit_async_registry_free(Hashmap *registry) {
355 #if ENABLE_POLKIT
356 hashmap_free_with_destructor(registry, async_polkit_query_free);
357 #endif
358 }