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