]>
Commit | Line | Data |
---|---|---|
e735f4d4 MP |
1 | /*** |
2 | This file is part of systemd. | |
3 | ||
4 | Copyright 2010 Lennart Poettering | |
5 | Copyright 2013 Daniel Mack | |
6 | Copyright 2014 Kay Sievers | |
7 | Copyright 2014 David Herrmann | |
8 | ||
9 | systemd is free software; you can redistribute it and/or modify it | |
10 | under the terms of the GNU Lesser General Public License as published by | |
11 | the Free Software Foundation; either version 2.1 of the License, or | |
12 | (at your option) any later version. | |
13 | ||
14 | systemd is distributed in the hope that it will be useful, but | |
15 | WITHOUT ANY WARRANTY; without even the implied warranty of | |
16 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
17 | Lesser General Public License for more details. | |
18 | ||
19 | You should have received a copy of the GNU Lesser General Public License | |
20 | along with systemd; If not, see <http://www.gnu.org/licenses/>. | |
21 | ***/ | |
22 | ||
e735f4d4 MP |
23 | #include <errno.h> |
24 | #include <poll.h> | |
db2df898 MP |
25 | #include <string.h> |
26 | #include <sys/socket.h> | |
27 | #include <sys/types.h> | |
e735f4d4 | 28 | |
e735f4d4 | 29 | #include "sd-bus.h" |
db2df898 MP |
30 | #include "sd-daemon.h" |
31 | ||
32 | #include "alloc-util.h" | |
33 | #include "bus-control.h" | |
e735f4d4 MP |
34 | #include "bus-internal.h" |
35 | #include "bus-message.h" | |
36 | #include "bus-util.h" | |
e735f4d4 MP |
37 | #include "bus-xml-policy.h" |
38 | #include "driver.h" | |
db2df898 MP |
39 | #include "fd-util.h" |
40 | #include "formats-util.h" | |
41 | #include "log.h" | |
e735f4d4 | 42 | #include "proxy.h" |
db2df898 MP |
43 | #include "set.h" |
44 | #include "strv.h" | |
e735f4d4 | 45 | #include "synthesize.h" |
db2df898 MP |
46 | #include "user-util.h" |
47 | #include "util.h" | |
e735f4d4 MP |
48 | |
49 | static int proxy_create_destination(Proxy *p, const char *destination, const char *local_sec, bool negotiate_fds) { | |
4c89c718 | 50 | _cleanup_(sd_bus_flush_close_unrefp) sd_bus *b = NULL; |
e735f4d4 MP |
51 | int r; |
52 | ||
53 | r = sd_bus_new(&b); | |
54 | if (r < 0) | |
55 | return log_error_errno(r, "Failed to allocate bus: %m"); | |
56 | ||
57 | r = sd_bus_set_description(b, "sd-proxy"); | |
58 | if (r < 0) | |
59 | return log_error_errno(r, "Failed to set bus name: %m"); | |
60 | ||
61 | r = sd_bus_set_address(b, destination); | |
62 | if (r < 0) | |
63 | return log_error_errno(r, "Failed to set address to connect to: %m"); | |
64 | ||
65 | r = sd_bus_negotiate_fds(b, negotiate_fds); | |
66 | if (r < 0) | |
67 | return log_error_errno(r, "Failed to set FD negotiation: %m"); | |
68 | ||
69 | r = sd_bus_negotiate_creds(b, true, SD_BUS_CREDS_EUID|SD_BUS_CREDS_PID|SD_BUS_CREDS_EGID|SD_BUS_CREDS_SELINUX_CONTEXT); | |
70 | if (r < 0) | |
71 | return log_error_errno(r, "Failed to set credential negotiation: %m"); | |
72 | ||
73 | if (p->local_creds.pid > 0) { | |
74 | b->fake_pids.pid = p->local_creds.pid; | |
75 | b->fake_pids_valid = true; | |
76 | ||
77 | b->fake_creds.uid = UID_INVALID; | |
78 | b->fake_creds.euid = p->local_creds.uid; | |
79 | b->fake_creds.suid = UID_INVALID; | |
80 | b->fake_creds.fsuid = UID_INVALID; | |
81 | b->fake_creds.gid = GID_INVALID; | |
82 | b->fake_creds.egid = p->local_creds.gid; | |
83 | b->fake_creds.sgid = GID_INVALID; | |
84 | b->fake_creds.fsgid = GID_INVALID; | |
85 | b->fake_creds_valid = true; | |
86 | } | |
87 | ||
88 | if (local_sec) { | |
89 | b->fake_label = strdup(local_sec); | |
90 | if (!b->fake_label) | |
91 | return log_oom(); | |
92 | } | |
93 | ||
94 | b->manual_peer_interface = true; | |
95 | ||
96 | r = sd_bus_start(b); | |
97 | if (r < 0) | |
98 | return log_error_errno(r, "Failed to start bus client: %m"); | |
99 | ||
100 | p->destination_bus = b; | |
101 | b = NULL; | |
102 | return 0; | |
103 | } | |
104 | ||
db2df898 | 105 | static int proxy_create_local(Proxy *p, bool negotiate_fds) { |
e735f4d4 | 106 | sd_id128_t server_id; |
db2df898 | 107 | sd_bus *b; |
e735f4d4 MP |
108 | int r; |
109 | ||
110 | r = sd_bus_new(&b); | |
111 | if (r < 0) | |
112 | return log_error_errno(r, "Failed to allocate bus: %m"); | |
113 | ||
db2df898 MP |
114 | r = sd_bus_set_fd(b, p->local_in, p->local_out); |
115 | if (r < 0) { | |
116 | sd_bus_unref(b); | |
e735f4d4 | 117 | return log_error_errno(r, "Failed to set fds: %m"); |
db2df898 MP |
118 | } |
119 | ||
120 | /* The fds are now owned by the bus, and we indicate that by | |
121 | * storing the bus object in the proxy object. */ | |
122 | p->local_bus = b; | |
e735f4d4 MP |
123 | |
124 | r = sd_bus_get_bus_id(p->destination_bus, &server_id); | |
125 | if (r < 0) | |
126 | return log_error_errno(r, "Failed to get server ID: %m"); | |
127 | ||
128 | r = sd_bus_set_server(b, 1, server_id); | |
129 | if (r < 0) | |
130 | return log_error_errno(r, "Failed to set server mode: %m"); | |
131 | ||
132 | r = sd_bus_negotiate_fds(b, negotiate_fds); | |
133 | if (r < 0) | |
134 | return log_error_errno(r, "Failed to set FD negotiation: %m"); | |
135 | ||
136 | r = sd_bus_negotiate_creds(b, true, SD_BUS_CREDS_EUID|SD_BUS_CREDS_PID|SD_BUS_CREDS_EGID|SD_BUS_CREDS_SELINUX_CONTEXT); | |
137 | if (r < 0) | |
138 | return log_error_errno(r, "Failed to set credential negotiation: %m"); | |
139 | ||
140 | r = sd_bus_set_anonymous(b, true); | |
141 | if (r < 0) | |
142 | return log_error_errno(r, "Failed to set anonymous authentication: %m"); | |
143 | ||
144 | b->manual_peer_interface = true; | |
145 | ||
146 | r = sd_bus_start(b); | |
147 | if (r < 0) | |
148 | return log_error_errno(r, "Failed to start bus client: %m"); | |
149 | ||
e735f4d4 MP |
150 | return 0; |
151 | } | |
152 | ||
7035cd9e MP |
153 | static int proxy_match_synthetic(sd_bus_message *m, void *userdata, sd_bus_error *error) { |
154 | Proxy *p = userdata; | |
155 | ||
156 | p->synthetic_matched = true; | |
157 | return 0; /* make sure to continue processing it in further handlers */ | |
158 | } | |
159 | ||
fb183854 | 160 | /* |
7035cd9e MP |
161 | * We always need NameOwnerChanged so we can synthesize NameLost and |
162 | * NameAcquired. Furthermore, dbus-1 always passes unicast-signals through, so | |
163 | * subscribe unconditionally. | |
fb183854 | 164 | */ |
e735f4d4 MP |
165 | static int proxy_prepare_matches(Proxy *p) { |
166 | _cleanup_free_ char *match = NULL; | |
167 | const char *unique; | |
168 | int r; | |
169 | ||
170 | if (!p->destination_bus->is_kernel) | |
171 | return 0; | |
172 | ||
173 | r = sd_bus_get_unique_name(p->destination_bus, &unique); | |
174 | if (r < 0) | |
175 | return log_error_errno(r, "Failed to get unique name: %m"); | |
176 | ||
177 | match = strjoin("type='signal'," | |
178 | "sender='org.freedesktop.DBus'," | |
179 | "path='/org/freedesktop/DBus'," | |
180 | "interface='org.freedesktop.DBus'," | |
181 | "member='NameOwnerChanged'," | |
182 | "arg1='", | |
183 | unique, | |
184 | "'", | |
185 | NULL); | |
186 | if (!match) | |
187 | return log_oom(); | |
188 | ||
7035cd9e | 189 | r = sd_bus_add_match(p->destination_bus, NULL, match, proxy_match_synthetic, p); |
e735f4d4 MP |
190 | if (r < 0) |
191 | return log_error_errno(r, "Failed to add match for NameLost: %m"); | |
192 | ||
193 | free(match); | |
194 | match = strjoin("type='signal'," | |
195 | "sender='org.freedesktop.DBus'," | |
196 | "path='/org/freedesktop/DBus'," | |
197 | "interface='org.freedesktop.DBus'," | |
198 | "member='NameOwnerChanged'," | |
199 | "arg2='", | |
200 | unique, | |
201 | "'", | |
202 | NULL); | |
203 | if (!match) | |
204 | return log_oom(); | |
205 | ||
7035cd9e | 206 | r = sd_bus_add_match(p->destination_bus, NULL, match, proxy_match_synthetic, p); |
e735f4d4 MP |
207 | if (r < 0) |
208 | return log_error_errno(r, "Failed to add match for NameAcquired: %m"); | |
209 | ||
fb183854 MP |
210 | free(match); |
211 | match = strjoin("type='signal'," | |
212 | "destination='", | |
213 | unique, | |
214 | "'", | |
215 | NULL); | |
216 | if (!match) | |
217 | return log_oom(); | |
218 | ||
7035cd9e | 219 | r = sd_bus_add_match(p->destination_bus, NULL, match, proxy_match_synthetic, p); |
fb183854 MP |
220 | if (r < 0) |
221 | log_error_errno(r, "Failed to add match for directed signals: %m"); | |
222 | /* FIXME: temporarily ignore error to support older kdbus versions */ | |
223 | ||
e735f4d4 MP |
224 | return 0; |
225 | } | |
226 | ||
227 | int proxy_new(Proxy **out, int in_fd, int out_fd, const char *destination) { | |
228 | _cleanup_(proxy_freep) Proxy *p = NULL; | |
229 | _cleanup_free_ char *local_sec = NULL; | |
230 | bool is_unix; | |
231 | int r; | |
232 | ||
db2df898 MP |
233 | /* This takes possession/destroys the file descriptors passed |
234 | * in even on failure. The caller should hence forget about | |
235 | * the fds in all cases after calling this function and not | |
236 | * close them. */ | |
237 | ||
e735f4d4 | 238 | p = new0(Proxy, 1); |
db2df898 MP |
239 | if (!p) { |
240 | safe_close(in_fd); | |
241 | safe_close(out_fd); | |
e735f4d4 | 242 | return log_oom(); |
db2df898 | 243 | } |
e735f4d4 MP |
244 | |
245 | p->local_in = in_fd; | |
246 | p->local_out = out_fd; | |
247 | ||
248 | p->owned_names = set_new(&string_hash_ops); | |
249 | if (!p->owned_names) | |
250 | return log_oom(); | |
251 | ||
252 | is_unix = sd_is_socket(in_fd, AF_UNIX, 0, 0) > 0 && | |
253 | sd_is_socket(out_fd, AF_UNIX, 0, 0) > 0; | |
254 | ||
255 | if (is_unix) { | |
256 | (void) getpeercred(in_fd, &p->local_creds); | |
257 | (void) getpeersec(in_fd, &local_sec); | |
258 | } | |
259 | ||
260 | r = proxy_create_destination(p, destination, local_sec, is_unix); | |
261 | if (r < 0) | |
262 | return r; | |
263 | ||
db2df898 | 264 | r = proxy_create_local(p, is_unix); |
e735f4d4 MP |
265 | if (r < 0) |
266 | return r; | |
267 | ||
268 | r = proxy_prepare_matches(p); | |
269 | if (r < 0) | |
270 | return r; | |
271 | ||
272 | *out = p; | |
273 | p = NULL; | |
db2df898 | 274 | |
e735f4d4 MP |
275 | return 0; |
276 | } | |
277 | ||
278 | Proxy *proxy_free(Proxy *p) { | |
5fd56512 MP |
279 | ProxyActivation *activation; |
280 | ||
e735f4d4 MP |
281 | if (!p) |
282 | return NULL; | |
283 | ||
5fd56512 MP |
284 | while ((activation = p->activations)) { |
285 | LIST_REMOVE(activations_by_proxy, p->activations, activation); | |
286 | sd_bus_message_unref(activation->request); | |
287 | sd_bus_slot_unref(activation->slot); | |
288 | free(activation); | |
289 | } | |
290 | ||
db2df898 MP |
291 | if (p->local_bus) |
292 | sd_bus_flush_close_unref(p->local_bus); | |
293 | else { | |
294 | safe_close(p->local_in); | |
295 | if (p->local_out != p->local_in) | |
296 | safe_close(p->local_out); | |
297 | } | |
298 | ||
fb183854 | 299 | sd_bus_flush_close_unref(p->destination_bus); |
e735f4d4 MP |
300 | set_free_free(p->owned_names); |
301 | free(p); | |
302 | ||
303 | return NULL; | |
304 | } | |
305 | ||
306 | int proxy_set_policy(Proxy *p, SharedPolicy *sp, char **configuration) { | |
307 | _cleanup_strv_free_ char **strv = NULL; | |
308 | Policy *policy; | |
309 | int r; | |
310 | ||
311 | assert(p); | |
312 | assert(sp); | |
313 | ||
314 | /* no need to load legacy policy if destination is not kdbus */ | |
315 | if (!p->destination_bus->is_kernel) | |
316 | return 0; | |
317 | ||
318 | p->policy = sp; | |
319 | ||
320 | policy = shared_policy_acquire(sp); | |
321 | if (policy) { | |
322 | /* policy already pre-loaded */ | |
323 | shared_policy_release(sp, policy); | |
324 | return 0; | |
325 | } | |
326 | ||
327 | if (!configuration) { | |
328 | const char *scope; | |
329 | ||
330 | r = sd_bus_get_scope(p->destination_bus, &scope); | |
331 | if (r < 0) | |
332 | return log_error_errno(r, "Couldn't determine bus scope: %m"); | |
333 | ||
334 | if (streq(scope, "system")) | |
86f210e9 MP |
335 | strv = strv_new("/usr/share/dbus-1/system.conf", |
336 | "/etc/dbus-1/system.conf", | |
337 | "/usr/share/dbus-1/system.d/", | |
e735f4d4 MP |
338 | "/etc/dbus-1/system.d/", |
339 | "/etc/dbus-1/system-local.conf", | |
340 | NULL); | |
341 | else if (streq(scope, "user")) | |
86f210e9 MP |
342 | strv = strv_new("/usr/share/dbus-1/session.conf", |
343 | "/etc/dbus-1/session.conf", | |
344 | "/usr/share/dbus-1/session.d/", | |
e735f4d4 MP |
345 | "/etc/dbus-1/session.d/", |
346 | "/etc/dbus-1/session-local.conf", | |
347 | NULL); | |
348 | else | |
349 | return log_error("Unknown scope %s, don't know which policy to load. Refusing.", scope); | |
350 | ||
351 | if (!strv) | |
352 | return log_oom(); | |
353 | ||
354 | configuration = strv; | |
355 | } | |
356 | ||
357 | return shared_policy_preload(sp, configuration); | |
358 | } | |
359 | ||
360 | int proxy_hello_policy(Proxy *p, uid_t original_uid) { | |
361 | Policy *policy; | |
362 | int r = 0; | |
363 | ||
364 | assert(p); | |
365 | ||
366 | if (!p->policy) | |
367 | return 0; | |
368 | ||
369 | policy = shared_policy_acquire(p->policy); | |
370 | ||
371 | if (p->local_creds.uid == original_uid) | |
372 | log_debug("Permitting access, since bus owner matches bus client."); | |
373 | else if (policy_check_hello(policy, p->local_creds.uid, p->local_creds.gid)) | |
374 | log_debug("Permitting access due to XML policy."); | |
375 | else | |
376 | r = log_error_errno(EPERM, "Policy denied connection."); | |
377 | ||
378 | shared_policy_release(p->policy, policy); | |
379 | ||
380 | return r; | |
381 | } | |
382 | ||
383 | static int proxy_wait(Proxy *p) { | |
384 | uint64_t timeout_destination, timeout_local, t; | |
385 | int events_destination, events_local, fd; | |
386 | struct timespec _ts, *ts; | |
387 | struct pollfd *pollfd; | |
388 | int r; | |
389 | ||
390 | assert(p); | |
391 | ||
392 | fd = sd_bus_get_fd(p->destination_bus); | |
393 | if (fd < 0) | |
394 | return log_error_errno(fd, "Failed to get fd: %m"); | |
395 | ||
396 | events_destination = sd_bus_get_events(p->destination_bus); | |
397 | if (events_destination < 0) | |
398 | return log_error_errno(events_destination, "Failed to get events mask: %m"); | |
399 | ||
400 | r = sd_bus_get_timeout(p->destination_bus, &timeout_destination); | |
401 | if (r < 0) | |
402 | return log_error_errno(r, "Failed to get timeout: %m"); | |
403 | ||
404 | events_local = sd_bus_get_events(p->local_bus); | |
405 | if (events_local < 0) | |
406 | return log_error_errno(events_local, "Failed to get events mask: %m"); | |
407 | ||
408 | r = sd_bus_get_timeout(p->local_bus, &timeout_local); | |
409 | if (r < 0) | |
410 | return log_error_errno(r, "Failed to get timeout: %m"); | |
411 | ||
412 | t = timeout_destination; | |
413 | if (t == (uint64_t) -1 || (timeout_local != (uint64_t) -1 && timeout_local < timeout_destination)) | |
414 | t = timeout_local; | |
415 | ||
416 | if (t == (uint64_t) -1) | |
417 | ts = NULL; | |
418 | else { | |
419 | usec_t nw; | |
420 | ||
421 | nw = now(CLOCK_MONOTONIC); | |
422 | if (t > nw) | |
423 | t -= nw; | |
424 | else | |
425 | t = 0; | |
426 | ||
427 | ts = timespec_store(&_ts, t); | |
428 | } | |
429 | ||
430 | pollfd = (struct pollfd[3]) { | |
431 | { .fd = fd, .events = events_destination, }, | |
432 | { .fd = p->local_in, .events = events_local & POLLIN, }, | |
433 | { .fd = p->local_out, .events = events_local & POLLOUT, }, | |
434 | }; | |
435 | ||
436 | r = ppoll(pollfd, 3, ts, NULL); | |
437 | if (r < 0) | |
438 | return log_error_errno(errno, "ppoll() failed: %m"); | |
439 | ||
440 | return 0; | |
441 | } | |
442 | ||
443 | static int handle_policy_error(sd_bus_message *m, int r) { | |
444 | if (r == -ESRCH || r == -ENXIO) | |
445 | return synthetic_reply_method_errorf(m, SD_BUS_ERROR_NAME_HAS_NO_OWNER, "Name %s is currently not owned by anyone.", m->destination); | |
446 | ||
447 | return r; | |
448 | } | |
449 | ||
450 | static int process_policy_unlocked(sd_bus *from, sd_bus *to, sd_bus_message *m, Policy *policy, const struct ucred *our_ucred, Set *owned_names) { | |
451 | int r; | |
452 | ||
453 | assert(from); | |
454 | assert(to); | |
455 | assert(m); | |
456 | ||
457 | if (!policy) | |
458 | return 0; | |
459 | ||
460 | /* | |
461 | * dbus-1 distinguishes expected and non-expected replies by tracking | |
462 | * method-calls and timeouts. By default, DENY rules are *NEVER* applied | |
463 | * on expected replies, unless explicitly specified. But we dont track | |
464 | * method-calls, thus, we cannot know whether a reply is expected. | |
465 | * Fortunately, the kdbus forbids non-expected replies, so we can safely | |
466 | * ignore any policy on those and let the kernel deal with it. | |
467 | * | |
468 | * TODO: To be correct, we should only ignore policy-tags that are | |
469 | * applied on non-expected replies. However, so far we don't parse those | |
470 | * tags so we let everything pass. I haven't seen a DENY policy tag on | |
471 | * expected-replies, ever, so don't bother.. | |
472 | */ | |
473 | if (m->reply_cookie > 0) | |
474 | return 0; | |
475 | ||
476 | if (from->is_kernel) { | |
477 | uid_t sender_uid = UID_INVALID; | |
478 | gid_t sender_gid = GID_INVALID; | |
479 | char **sender_names = NULL; | |
480 | ||
481 | /* Driver messages are always OK */ | |
482 | if (streq_ptr(m->sender, "org.freedesktop.DBus")) | |
483 | return 0; | |
484 | ||
485 | /* The message came from the kernel, and is sent to our legacy client. */ | |
486 | (void) sd_bus_creds_get_well_known_names(&m->creds, &sender_names); | |
487 | ||
488 | (void) sd_bus_creds_get_euid(&m->creds, &sender_uid); | |
489 | (void) sd_bus_creds_get_egid(&m->creds, &sender_gid); | |
490 | ||
491 | if (sender_uid == UID_INVALID || sender_gid == GID_INVALID) { | |
4c89c718 | 492 | _cleanup_(sd_bus_creds_unrefp) sd_bus_creds *sender_creds = NULL; |
e735f4d4 MP |
493 | |
494 | /* If the message came from another legacy | |
495 | * client, then the message creds will be | |
496 | * missing, simply because on legacy clients | |
497 | * per-message creds were unknown. In this | |
498 | * case, query the creds of the peer | |
499 | * instead. */ | |
500 | ||
501 | r = bus_get_name_creds_kdbus(from, m->sender, SD_BUS_CREDS_EUID|SD_BUS_CREDS_EGID, true, &sender_creds); | |
502 | if (r < 0) | |
503 | return handle_policy_error(m, r); | |
504 | ||
505 | (void) sd_bus_creds_get_euid(sender_creds, &sender_uid); | |
506 | (void) sd_bus_creds_get_egid(sender_creds, &sender_gid); | |
507 | } | |
508 | ||
509 | /* First check whether the sender can send the message to our name */ | |
510 | if (policy_check_send(policy, sender_uid, sender_gid, m->header->type, owned_names, NULL, m->path, m->interface, m->member, false, NULL) && | |
511 | policy_check_recv(policy, our_ucred->uid, our_ucred->gid, m->header->type, NULL, sender_names, m->path, m->interface, m->member, false)) | |
512 | return 0; | |
513 | ||
514 | /* Return an error back to the caller */ | |
515 | if (m->header->type == SD_BUS_MESSAGE_METHOD_CALL) | |
516 | return synthetic_reply_method_errorf(m, SD_BUS_ERROR_ACCESS_DENIED, "Access prohibited by XML receiver policy."); | |
517 | ||
518 | /* Return 1, indicating that the message shall not be processed any further */ | |
519 | return 1; | |
520 | } | |
521 | ||
522 | if (to->is_kernel) { | |
4c89c718 | 523 | _cleanup_(sd_bus_creds_unrefp) sd_bus_creds *destination_creds = NULL; |
e735f4d4 MP |
524 | uid_t destination_uid = UID_INVALID; |
525 | gid_t destination_gid = GID_INVALID; | |
526 | const char *destination_unique = NULL; | |
527 | char **destination_names = NULL; | |
528 | char *n; | |
529 | ||
530 | /* Driver messages are always OK */ | |
531 | if (streq_ptr(m->destination, "org.freedesktop.DBus")) | |
532 | return 0; | |
533 | ||
534 | /* The message came from the legacy client, and is sent to kdbus. */ | |
535 | if (m->destination) { | |
536 | r = bus_get_name_creds_kdbus(to, m->destination, | |
537 | SD_BUS_CREDS_WELL_KNOWN_NAMES|SD_BUS_CREDS_UNIQUE_NAME| | |
538 | SD_BUS_CREDS_EUID|SD_BUS_CREDS_EGID|SD_BUS_CREDS_PID, | |
539 | true, &destination_creds); | |
540 | if (r < 0) | |
541 | return handle_policy_error(m, r); | |
542 | ||
543 | r = sd_bus_creds_get_unique_name(destination_creds, &destination_unique); | |
544 | if (r < 0) | |
545 | return handle_policy_error(m, r); | |
546 | ||
547 | (void) sd_bus_creds_get_well_known_names(destination_creds, &destination_names); | |
548 | ||
549 | (void) sd_bus_creds_get_euid(destination_creds, &destination_uid); | |
550 | (void) sd_bus_creds_get_egid(destination_creds, &destination_gid); | |
551 | } | |
552 | ||
553 | /* First check if we (the sender) can send to this name */ | |
fb183854 MP |
554 | if (sd_bus_message_is_signal(m, NULL, NULL)) { |
555 | /* If we forward a signal from dbus-1 to kdbus, we have | |
556 | * no idea who the recipient is. Therefore, we cannot | |
557 | * apply any dbus-1 policies that match on receiver | |
558 | * credentials. We know sd-bus always sets | |
559 | * KDBUS_MSG_SIGNAL, so the kernel applies policies to | |
560 | * the message. Therefore, skip policy checks in this | |
561 | * case. */ | |
562 | return 0; | |
563 | } else if (policy_check_send(policy, our_ucred->uid, our_ucred->gid, m->header->type, NULL, destination_names, m->path, m->interface, m->member, true, &n)) { | |
e735f4d4 MP |
564 | if (n) { |
565 | /* If we made a receiver decision, then remember which | |
566 | * name's policy we used, and to which unique ID it | |
567 | * mapped when we made the decision. Then, let's pass | |
568 | * this to the kernel when sending the message, so that | |
569 | * it refuses the operation should the name and unique | |
570 | * ID not map to each other anymore. */ | |
571 | ||
572 | r = free_and_strdup(&m->destination_ptr, n); | |
573 | if (r < 0) | |
574 | return r; | |
575 | ||
576 | r = bus_kernel_parse_unique_name(destination_unique, &m->verify_destination_id); | |
577 | if (r < 0) | |
578 | return r; | |
579 | } | |
580 | ||
fb183854 | 581 | if (policy_check_recv(policy, destination_uid, destination_gid, m->header->type, owned_names, NULL, m->path, m->interface, m->member, true)) |
e735f4d4 | 582 | return 0; |
e735f4d4 MP |
583 | } |
584 | ||
585 | /* Return an error back to the caller */ | |
586 | if (m->header->type == SD_BUS_MESSAGE_METHOD_CALL) | |
587 | return synthetic_reply_method_errorf(m, SD_BUS_ERROR_ACCESS_DENIED, "Access prohibited by XML sender policy."); | |
588 | ||
589 | /* Return 1, indicating that the message shall not be processed any further */ | |
590 | return 1; | |
591 | } | |
592 | ||
593 | return 0; | |
594 | } | |
595 | ||
596 | static int process_policy(sd_bus *from, sd_bus *to, sd_bus_message *m, SharedPolicy *sp, const struct ucred *our_ucred, Set *owned_names) { | |
597 | Policy *policy; | |
598 | int r; | |
599 | ||
600 | assert(sp); | |
601 | ||
602 | policy = shared_policy_acquire(sp); | |
603 | r = process_policy_unlocked(from, to, m, policy, our_ucred, owned_names); | |
604 | shared_policy_release(sp, policy); | |
605 | ||
606 | return r; | |
607 | } | |
608 | ||
609 | static int process_hello(Proxy *p, sd_bus_message *m) { | |
4c89c718 | 610 | _cleanup_(sd_bus_message_unrefp) sd_bus_message *n = NULL; |
e735f4d4 MP |
611 | bool is_hello; |
612 | int r; | |
613 | ||
614 | assert(p); | |
615 | assert(m); | |
616 | ||
617 | /* As reaction to hello we need to respond with two messages: | |
618 | * the callback reply and the NameAcquired for the unique | |
619 | * name, since hello is otherwise obsolete on kdbus. */ | |
620 | ||
621 | is_hello = | |
622 | sd_bus_message_is_method_call(m, "org.freedesktop.DBus", "Hello") && | |
623 | streq_ptr(m->destination, "org.freedesktop.DBus"); | |
624 | ||
625 | if (!is_hello) { | |
626 | if (p->got_hello) | |
627 | return 0; | |
628 | ||
629 | return log_error_errno(EIO, "First packet isn't hello (it's %s.%s), aborting.", m->interface, m->member); | |
630 | } | |
631 | ||
632 | if (p->got_hello) | |
633 | return log_error_errno(EIO, "Got duplicate hello, aborting."); | |
634 | ||
635 | p->got_hello = true; | |
636 | ||
637 | if (!p->destination_bus->is_kernel) | |
638 | return 0; | |
639 | ||
640 | r = sd_bus_message_new_method_return(m, &n); | |
641 | if (r < 0) | |
642 | return log_error_errno(r, "Failed to generate HELLO reply: %m"); | |
643 | ||
644 | r = sd_bus_message_append(n, "s", p->destination_bus->unique_name); | |
645 | if (r < 0) | |
646 | return log_error_errno(r, "Failed to append unique name to HELLO reply: %m"); | |
647 | ||
648 | r = bus_message_append_sender(n, "org.freedesktop.DBus"); | |
649 | if (r < 0) | |
650 | return log_error_errno(r, "Failed to append sender to HELLO reply: %m"); | |
651 | ||
652 | r = bus_seal_synthetic_message(p->local_bus, n); | |
653 | if (r < 0) | |
654 | return log_error_errno(r, "Failed to seal HELLO reply: %m"); | |
655 | ||
656 | r = sd_bus_send(p->local_bus, n, NULL); | |
657 | if (r < 0) | |
658 | return log_error_errno(r, "Failed to send HELLO reply: %m"); | |
659 | ||
660 | n = sd_bus_message_unref(n); | |
661 | r = sd_bus_message_new_signal( | |
662 | p->local_bus, | |
663 | &n, | |
664 | "/org/freedesktop/DBus", | |
665 | "org.freedesktop.DBus", | |
666 | "NameAcquired"); | |
667 | if (r < 0) | |
668 | return log_error_errno(r, "Failed to allocate initial NameAcquired message: %m"); | |
669 | ||
670 | r = sd_bus_message_append(n, "s", p->destination_bus->unique_name); | |
671 | if (r < 0) | |
672 | return log_error_errno(r, "Failed to append unique name to NameAcquired message: %m"); | |
673 | ||
674 | r = bus_message_append_sender(n, "org.freedesktop.DBus"); | |
675 | if (r < 0) | |
676 | return log_error_errno(r, "Failed to append sender to NameAcquired message: %m"); | |
677 | ||
5fd56512 MP |
678 | r = sd_bus_message_set_destination(n, p->destination_bus->unique_name); |
679 | if (r < 0) | |
680 | return log_error_errno(r, "Failed to set destination for NameAcquired message: %m"); | |
681 | ||
e735f4d4 MP |
682 | r = bus_seal_synthetic_message(p->local_bus, n); |
683 | if (r < 0) | |
684 | return log_error_errno(r, "Failed to seal NameAcquired message: %m"); | |
685 | ||
686 | r = sd_bus_send(p->local_bus, n, NULL); | |
687 | if (r < 0) | |
688 | return log_error_errno(r, "Failed to send NameAcquired message: %m"); | |
689 | ||
690 | return 1; | |
691 | } | |
692 | ||
693 | static int patch_sender(sd_bus *a, sd_bus_message *m) { | |
694 | char **well_known = NULL; | |
695 | sd_bus_creds *c; | |
696 | int r; | |
697 | ||
698 | assert(a); | |
699 | assert(m); | |
700 | ||
701 | if (!a->is_kernel) | |
702 | return 0; | |
703 | ||
704 | /* We will change the sender of messages from the bus driver | |
705 | * so that they originate from the bus driver. This is a | |
706 | * speciality originating from dbus1, where the bus driver did | |
707 | * not have a unique id, but only the well-known name. */ | |
708 | ||
709 | c = sd_bus_message_get_creds(m); | |
710 | if (!c) | |
711 | return 0; | |
712 | ||
713 | r = sd_bus_creds_get_well_known_names(c, &well_known); | |
714 | if (r < 0) | |
715 | return r; | |
716 | ||
717 | if (strv_contains(well_known, "org.freedesktop.DBus")) | |
718 | m->sender = "org.freedesktop.DBus"; | |
719 | ||
720 | return 0; | |
721 | } | |
722 | ||
723 | static int proxy_process_destination_to_local(Proxy *p) { | |
4c89c718 | 724 | _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; |
7035cd9e | 725 | bool matched, matched_synthetic; |
e735f4d4 MP |
726 | int r; |
727 | ||
728 | assert(p); | |
729 | ||
7035cd9e MP |
730 | /* |
731 | * Usually, we would just take any message that the bus passes to us | |
732 | * and forward it to the local connection. However, there are actually | |
733 | * applications that fail if they receive broadcasts that they didn't | |
734 | * subscribe to. Therefore, we actually emulate a real broadcast | |
735 | * matching here, and discard any broadcasts that weren't matched. Our | |
736 | * match-handlers remembers whether a message was matched by any rule, | |
737 | * by marking it in @p->message_matched. | |
738 | */ | |
739 | ||
e735f4d4 | 740 | r = sd_bus_process(p->destination_bus, &m); |
7035cd9e MP |
741 | |
742 | matched = p->message_matched; | |
743 | matched_synthetic = p->synthetic_matched; | |
744 | p->message_matched = false; | |
745 | p->synthetic_matched = false; | |
746 | ||
e735f4d4 MP |
747 | if (r == -ECONNRESET || r == -ENOTCONN) /* Treat 'connection reset by peer' as clean exit condition */ |
748 | return r; | |
749 | if (r < 0) { | |
750 | log_error_errno(r, "Failed to process destination bus: %m"); | |
751 | return r; | |
752 | } | |
753 | if (r == 0) | |
754 | return 0; | |
755 | if (!m) | |
756 | return 1; | |
757 | ||
758 | /* We officially got EOF, let's quit */ | |
759 | if (sd_bus_message_is_signal(m, "org.freedesktop.DBus.Local", "Disconnected")) | |
760 | return -ECONNRESET; | |
761 | ||
7035cd9e | 762 | r = synthesize_name_acquired(p, p->destination_bus, p->local_bus, m); |
e735f4d4 MP |
763 | if (r == -ECONNRESET || r == -ENOTCONN) |
764 | return r; | |
765 | if (r < 0) | |
766 | return log_error_errno(r, "Failed to synthesize message: %m"); | |
767 | ||
7035cd9e MP |
768 | /* discard broadcasts that were not matched by any MATCH rule */ |
769 | if (!matched && !sd_bus_message_get_destination(m)) { | |
770 | if (!matched_synthetic) | |
771 | log_debug("Dropped unmatched broadcast: uid=" UID_FMT " gid=" GID_FMT " pid=" PID_FMT " message=%s path=%s interface=%s member=%s sender=%s destination=%s", | |
772 | p->local_creds.uid, p->local_creds.gid, p->local_creds.pid, bus_message_type_to_string(m->header->type), | |
773 | strna(m->path), strna(m->interface), strna(m->member), strna(m->sender), strna(m->destination)); | |
774 | return 1; | |
775 | } | |
776 | ||
e735f4d4 MP |
777 | patch_sender(p->destination_bus, m); |
778 | ||
779 | if (p->policy) { | |
780 | r = process_policy(p->destination_bus, p->local_bus, m, p->policy, &p->local_creds, p->owned_names); | |
781 | if (r == -ECONNRESET || r == -ENOTCONN) | |
782 | return r; | |
783 | if (r < 0) | |
784 | return log_error_errno(r, "Failed to process policy: %m"); | |
785 | if (r > 0) | |
786 | return 1; | |
787 | } | |
788 | ||
789 | r = sd_bus_send(p->local_bus, m, NULL); | |
790 | if (r < 0) { | |
791 | if (r == -ECONNRESET || r == -ENOTCONN) | |
792 | return r; | |
793 | ||
794 | /* If the peer tries to send a reply and it is | |
13d276d0 | 795 | * rejected with EBADSLT by the kernel, we ignore the |
e735f4d4 MP |
796 | * error. This catches cases where the original |
797 | * method-call didn't had EXPECT_REPLY set, but the | |
798 | * proxy-peer still sends a reply. This is allowed in | |
799 | * dbus1, but not in kdbus. We don't want to track | |
800 | * reply-windows in the proxy, so we simply ignore | |
13d276d0 | 801 | * EBADSLT for all replies. The only downside is, that |
e735f4d4 MP |
802 | * callers are no longer notified if their replies are |
803 | * dropped. However, this is equivalent to the | |
804 | * caller's timeout to expire, so this should be | |
805 | * acceptable. Nobody sane sends replies without a | |
806 | * matching method-call, so nobody should care. */ | |
13d276d0 MP |
807 | |
808 | /* FIXME: remove -EPERM when kdbus is updated */ | |
809 | if ((r == -EPERM || r == -EBADSLT) && m->reply_cookie > 0) | |
e735f4d4 MP |
810 | return 1; |
811 | ||
812 | /* Return the error to the client, if we can */ | |
813 | synthetic_reply_method_errnof(m, r, "Failed to forward message we got from destination: %m"); | |
e3bff60a MP |
814 | if (r == -ENOBUFS) { |
815 | /* if local dbus1 peer does not dispatch its queue, warn only once */ | |
816 | if (!p->queue_overflow) | |
817 | log_error("Dropped messages due to queue overflow of local peer (pid: "PID_FMT" uid: "UID_FMT")", p->local_creds.pid, p->local_creds.uid); | |
818 | p->queue_overflow = true; | |
819 | } else | |
820 | log_error_errno(r, | |
821 | "Failed to forward message we got from destination: uid=" UID_FMT " gid=" GID_FMT" message=%s destination=%s path=%s interface=%s member=%s: %m", | |
822 | p->local_creds.uid, p->local_creds.gid, bus_message_type_to_string(m->header->type), | |
823 | strna(m->destination), strna(m->path), strna(m->interface), strna(m->member)); | |
824 | ||
e735f4d4 MP |
825 | return 1; |
826 | } | |
827 | ||
e3bff60a | 828 | p->queue_overflow = false; |
e735f4d4 MP |
829 | return 1; |
830 | } | |
831 | ||
832 | static int proxy_process_local_to_destination(Proxy *p) { | |
4c89c718 | 833 | _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; |
e735f4d4 MP |
834 | int r; |
835 | ||
836 | assert(p); | |
837 | ||
838 | r = sd_bus_process(p->local_bus, &m); | |
839 | if (r == -ECONNRESET || r == -ENOTCONN) /* Treat 'connection reset by peer' as clean exit condition */ | |
840 | return r; | |
841 | if (r < 0) { | |
842 | log_error_errno(r, "Failed to process local bus: %m"); | |
843 | return r; | |
844 | } | |
845 | if (r == 0) | |
846 | return 0; | |
847 | if (!m) | |
848 | return 1; | |
849 | ||
850 | /* We officially got EOF, let's quit */ | |
851 | if (sd_bus_message_is_signal(m, "org.freedesktop.DBus.Local", "Disconnected")) | |
852 | return -ECONNRESET; | |
853 | ||
854 | r = process_hello(p, m); | |
855 | if (r == -ECONNRESET || r == -ENOTCONN) | |
856 | return r; | |
857 | if (r < 0) | |
858 | return log_error_errno(r, "Failed to process HELLO: %m"); | |
859 | if (r > 0) | |
860 | return 1; | |
861 | ||
7035cd9e | 862 | r = bus_proxy_process_driver(p, p->destination_bus, p->local_bus, m, p->policy, &p->local_creds, p->owned_names); |
e735f4d4 MP |
863 | if (r == -ECONNRESET || r == -ENOTCONN) |
864 | return r; | |
865 | if (r < 0) | |
866 | return log_error_errno(r, "Failed to process driver calls: %m"); | |
867 | if (r > 0) | |
868 | return 1; | |
869 | ||
870 | for (;;) { | |
871 | if (p->policy) { | |
872 | r = process_policy(p->local_bus, p->destination_bus, m, p->policy, &p->local_creds, p->owned_names); | |
873 | if (r == -ECONNRESET || r == -ENOTCONN) | |
874 | return r; | |
875 | if (r < 0) | |
876 | return log_error_errno(r, "Failed to process policy: %m"); | |
877 | if (r > 0) | |
878 | return 1; | |
879 | } | |
880 | ||
881 | r = sd_bus_send(p->destination_bus, m, NULL); | |
882 | if (r < 0) { | |
883 | if (r == -ECONNRESET || r == -ENOTCONN) | |
884 | return r; | |
885 | ||
886 | /* The name database changed since the policy check, hence let's check again */ | |
887 | if (r == -EREMCHG) | |
888 | continue; | |
889 | ||
13d276d0 MP |
890 | /* see above why EBADSLT is ignored for replies */ |
891 | if ((r == -EPERM || r == -EBADSLT) && m->reply_cookie > 0) | |
e735f4d4 MP |
892 | return 1; |
893 | ||
894 | synthetic_reply_method_errnof(m, r, "Failed to forward message we got from local: %m"); | |
895 | log_error_errno(r, | |
896 | "Failed to forward message we got from local: uid=" UID_FMT " gid=" GID_FMT" message=%s destination=%s path=%s interface=%s member=%s: %m", | |
897 | p->local_creds.uid, p->local_creds.gid, bus_message_type_to_string(m->header->type), | |
898 | strna(m->destination), strna(m->path), strna(m->interface), strna(m->member)); | |
899 | return 1; | |
900 | } | |
901 | ||
902 | break; | |
903 | } | |
904 | ||
905 | return 1; | |
906 | } | |
907 | ||
7035cd9e MP |
908 | int proxy_match(sd_bus_message *m, void *userdata, sd_bus_error *error) { |
909 | Proxy *p = userdata; | |
910 | ||
911 | p->message_matched = true; | |
912 | return 0; /* make sure to continue processing it in further handlers */ | |
913 | } | |
914 | ||
e735f4d4 MP |
915 | int proxy_run(Proxy *p) { |
916 | int r; | |
917 | ||
918 | assert(p); | |
919 | ||
920 | for (;;) { | |
921 | bool busy = false; | |
922 | ||
923 | if (p->got_hello) { | |
924 | /* Read messages from bus, to pass them on to our client */ | |
925 | r = proxy_process_destination_to_local(p); | |
926 | if (r == -ECONNRESET || r == -ENOTCONN) | |
927 | return 0; | |
928 | if (r < 0) | |
929 | return r; | |
930 | if (r > 0) | |
931 | busy = true; | |
932 | } | |
933 | ||
934 | /* Read messages from our client, to pass them on to the bus */ | |
935 | r = proxy_process_local_to_destination(p); | |
936 | if (r == -ECONNRESET || r == -ENOTCONN) | |
937 | return 0; | |
938 | if (r < 0) | |
939 | return r; | |
940 | if (r > 0) | |
941 | busy = true; | |
942 | ||
943 | if (!busy) { | |
944 | r = proxy_wait(p); | |
945 | if (r == -ECONNRESET || r == -ENOTCONN) | |
946 | return 0; | |
947 | if (r < 0) | |
948 | return r; | |
949 | } | |
950 | } | |
951 | ||
952 | return 0; | |
953 | } |