]>
Commit | Line | Data |
---|---|---|
269e4d2d LP |
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 | ||
95f82ae9 LP |
33 | #if ENABLE_POLKIT |
34 | static int bus_message_append_strv_key_value( | |
35 | sd_bus_message *m, | |
36 | const char **l) { | |
37 | ||
38 | const char **k, **v; | |
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 */ | |
773b1a79 LP |
126 | if (sd_bus_error_has_name(ret_error, SD_BUS_ERROR_SERVICE_UNKNOWN)) { |
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 LP |
161 | sd_bus_message *request, *reply; |
162 | sd_bus_message_handler_t callback; | |
163 | void *userdata; | |
164 | sd_bus_slot *slot; | |
165 | Hashmap *registry; | |
166 | } AsyncPolkitQuery; | |
167 | ||
168 | static void async_polkit_query_free(AsyncPolkitQuery *q) { | |
169 | ||
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 | ||
269e4d2d LP |
184 | free(q); |
185 | } | |
186 | ||
187 | static int async_polkit_callback(sd_bus_message *reply, void *userdata, sd_bus_error *error) { | |
188 | _cleanup_(sd_bus_error_free) sd_bus_error error_buffer = SD_BUS_ERROR_NULL; | |
189 | AsyncPolkitQuery *q = userdata; | |
190 | int r; | |
191 | ||
192 | assert(reply); | |
193 | assert(q); | |
194 | ||
195 | q->slot = sd_bus_slot_unref(q->slot); | |
196 | q->reply = sd_bus_message_ref(reply); | |
197 | ||
198 | r = sd_bus_message_rewind(q->request, true); | |
199 | if (r < 0) { | |
200 | r = sd_bus_reply_method_errno(q->request, r, NULL); | |
201 | goto finish; | |
202 | } | |
203 | ||
204 | r = q->callback(q->request, q->userdata, &error_buffer); | |
205 | r = bus_maybe_reply_error(q->request, r, &error_buffer); | |
206 | ||
207 | finish: | |
208 | async_polkit_query_free(q); | |
209 | ||
210 | return r; | |
211 | } | |
212 | ||
213 | #endif | |
214 | ||
215 | int bus_verify_polkit_async( | |
216 | sd_bus_message *call, | |
217 | int capability, | |
218 | const char *action, | |
219 | const char **details, | |
220 | bool interactive, | |
221 | uid_t good_user, | |
222 | Hashmap **registry, | |
773b1a79 | 223 | sd_bus_error *ret_error) { |
269e4d2d LP |
224 | |
225 | #if ENABLE_POLKIT | |
226 | _cleanup_(sd_bus_message_unrefp) sd_bus_message *pk = NULL; | |
227 | AsyncPolkitQuery *q; | |
95f82ae9 | 228 | const char *sender; |
269e4d2d LP |
229 | sd_bus_message_handler_t callback; |
230 | void *userdata; | |
231 | int c; | |
232 | #endif | |
233 | int r; | |
234 | ||
235 | assert(call); | |
236 | assert(action); | |
237 | assert(registry); | |
238 | ||
239 | r = check_good_user(call, good_user); | |
240 | if (r != 0) | |
241 | return r; | |
242 | ||
243 | #if ENABLE_POLKIT | |
244 | q = hashmap_get(*registry, call); | |
245 | if (q) { | |
246 | int authorized, challenge; | |
247 | ||
7f569822 LP |
248 | /* This is the second invocation of this function, and there's already a response from |
249 | * polkit, let's process it */ | |
269e4d2d LP |
250 | assert(q->reply); |
251 | ||
7f569822 LP |
252 | /* If the operation we want to authenticate changed between the first and the second time, |
253 | * let's not use this authentication, it might be out of date as the object and context we | |
254 | * operate on might have changed. */ | |
255 | if (!streq(q->action, action) || | |
256 | !strv_equal(q->details, (char**) details)) | |
257 | return -ESTALE; | |
258 | ||
269e4d2d LP |
259 | if (sd_bus_message_is_method_error(q->reply, NULL)) { |
260 | const sd_bus_error *e; | |
261 | ||
262 | e = sd_bus_message_get_error(q->reply); | |
263 | ||
264 | /* Treat no PK available as access denied */ | |
265 | if (sd_bus_error_has_name(e, SD_BUS_ERROR_SERVICE_UNKNOWN) || | |
266 | sd_bus_error_has_name(e, SD_BUS_ERROR_NAME_HAS_NO_OWNER)) | |
267 | return -EACCES; | |
268 | ||
269 | /* Copy error from polkit reply */ | |
773b1a79 | 270 | sd_bus_error_copy(ret_error, e); |
269e4d2d LP |
271 | return -sd_bus_error_get_errno(e); |
272 | } | |
273 | ||
274 | r = sd_bus_message_enter_container(q->reply, 'r', "bba{ss}"); | |
275 | if (r >= 0) | |
276 | r = sd_bus_message_read(q->reply, "bb", &authorized, &challenge); | |
277 | if (r < 0) | |
278 | return r; | |
279 | ||
280 | if (authorized) | |
281 | return 1; | |
282 | ||
283 | if (challenge) | |
773b1a79 | 284 | return sd_bus_error_set(ret_error, SD_BUS_ERROR_INTERACTIVE_AUTHORIZATION_REQUIRED, "Interactive authentication required."); |
269e4d2d LP |
285 | |
286 | return -EACCES; | |
287 | } | |
288 | #endif | |
289 | ||
290 | r = sd_bus_query_sender_privilege(call, capability); | |
291 | if (r < 0) | |
292 | return r; | |
293 | else if (r > 0) | |
294 | return 1; | |
295 | ||
296 | #if ENABLE_POLKIT | |
297 | if (sd_bus_get_current_message(call->bus) != call) | |
298 | return -EINVAL; | |
299 | ||
300 | callback = sd_bus_get_current_handler(call->bus); | |
301 | if (!callback) | |
302 | return -EINVAL; | |
303 | ||
304 | userdata = sd_bus_get_current_userdata(call->bus); | |
305 | ||
306 | sender = sd_bus_message_get_sender(call); | |
307 | if (!sender) | |
308 | return -EBADMSG; | |
309 | ||
310 | c = sd_bus_message_get_allow_interactive_authorization(call); | |
311 | if (c < 0) | |
312 | return c; | |
313 | if (c > 0) | |
314 | interactive = true; | |
315 | ||
316 | r = hashmap_ensure_allocated(registry, NULL); | |
317 | if (r < 0) | |
318 | return r; | |
319 | ||
320 | r = sd_bus_message_new_method_call( | |
321 | call->bus, | |
322 | &pk, | |
323 | "org.freedesktop.PolicyKit1", | |
324 | "/org/freedesktop/PolicyKit1/Authority", | |
325 | "org.freedesktop.PolicyKit1.Authority", | |
326 | "CheckAuthorization"); | |
327 | if (r < 0) | |
328 | return r; | |
329 | ||
330 | r = sd_bus_message_append( | |
331 | pk, | |
332 | "(sa{sv})s", | |
333 | "system-bus-name", 1, "name", "s", sender, | |
334 | action); | |
335 | if (r < 0) | |
336 | return r; | |
337 | ||
95f82ae9 | 338 | r = bus_message_append_strv_key_value(pk, details); |
269e4d2d LP |
339 | if (r < 0) |
340 | return r; | |
341 | ||
342 | r = sd_bus_message_append(pk, "us", interactive, NULL); | |
343 | if (r < 0) | |
344 | return r; | |
345 | ||
f4425c72 | 346 | q = new(AsyncPolkitQuery, 1); |
269e4d2d LP |
347 | if (!q) |
348 | return -ENOMEM; | |
349 | ||
f4425c72 LP |
350 | *q = (AsyncPolkitQuery) { |
351 | .request = sd_bus_message_ref(call), | |
352 | .callback = callback, | |
353 | .userdata = userdata, | |
354 | }; | |
269e4d2d | 355 | |
7f569822 LP |
356 | q->action = strdup(action); |
357 | if (!q->action) { | |
358 | async_polkit_query_free(q); | |
359 | return -ENOMEM; | |
360 | } | |
361 | ||
362 | q->details = strv_copy((char**) details); | |
363 | if (!q->details) { | |
364 | async_polkit_query_free(q); | |
365 | return -ENOMEM; | |
366 | } | |
367 | ||
269e4d2d LP |
368 | r = hashmap_put(*registry, call, q); |
369 | if (r < 0) { | |
370 | async_polkit_query_free(q); | |
371 | return r; | |
372 | } | |
373 | ||
374 | q->registry = *registry; | |
375 | ||
376 | r = sd_bus_call_async(call->bus, &q->slot, pk, async_polkit_callback, q, 0); | |
377 | if (r < 0) { | |
378 | async_polkit_query_free(q); | |
379 | return r; | |
380 | } | |
381 | ||
382 | return 0; | |
383 | #endif | |
384 | ||
385 | return -EACCES; | |
386 | } | |
387 | ||
388 | void bus_verify_polkit_async_registry_free(Hashmap *registry) { | |
389 | #if ENABLE_POLKIT | |
390 | hashmap_free_with_destructor(registry, async_polkit_query_free); | |
391 | #endif | |
392 | } |