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