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