]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/core/dbus-job.c
selinux: rework selinux access check logic
[thirdparty/systemd.git] / src / core / dbus-job.c
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
8 systemd is free software; you can redistribute it and/or modify it
9 under the terms of the GNU Lesser General Public License as published by
10 the Free Software Foundation; either version 2.1 of the License, or
11 (at your option) any later version.
12
13 systemd is distributed in the hope that it will be useful, but
14 WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 Lesser General Public License for more details.
17
18 You should have received a copy of the GNU Lesser General Public License
19 along with systemd; If not, see <http://www.gnu.org/licenses/>.
20 ***/
21
22 #include <errno.h>
23
24 #include "dbus.h"
25 #include "log.h"
26 #include "dbus-job.h"
27 #include "dbus-common.h"
28 #include "selinux-access.h"
29
30 #define BUS_JOB_INTERFACE \
31 " <interface name=\"org.freedesktop.systemd1.Job\">\n" \
32 " <method name=\"Cancel\"/>\n" \
33 " <property name=\"Id\" type=\"u\" access=\"read\"/>\n" \
34 " <property name=\"Unit\" type=\"(so)\" access=\"read\"/>\n" \
35 " <property name=\"JobType\" type=\"s\" access=\"read\"/>\n" \
36 " <property name=\"State\" type=\"s\" access=\"read\"/>\n" \
37 " </interface>\n"
38
39 #define INTROSPECTION \
40 DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE \
41 "<node>\n" \
42 BUS_JOB_INTERFACE \
43 BUS_PROPERTIES_INTERFACE \
44 BUS_PEER_INTERFACE \
45 BUS_INTROSPECTABLE_INTERFACE \
46 "</node>\n"
47
48 const char bus_job_interface[] _introspect_("Job") = BUS_JOB_INTERFACE;
49
50 #define INTERFACES_LIST \
51 BUS_GENERIC_INTERFACES_LIST \
52 "org.freedesktop.systemd1.Job\0"
53
54 #define INVALIDATING_PROPERTIES \
55 "State\0"
56
57 static DEFINE_BUS_PROPERTY_APPEND_ENUM(bus_job_append_state, job_state, JobState);
58 static DEFINE_BUS_PROPERTY_APPEND_ENUM(bus_job_append_type, job_type, JobType);
59
60 static int bus_job_append_unit(DBusMessageIter *i, const char *property, void *data) {
61 Job *j = data;
62 DBusMessageIter sub;
63 char *p;
64
65 assert(i);
66 assert(property);
67 assert(j);
68
69 if (!dbus_message_iter_open_container(i, DBUS_TYPE_STRUCT, NULL, &sub))
70 return -ENOMEM;
71
72 p = unit_dbus_path(j->unit);
73 if (!p)
74 return -ENOMEM;
75
76 if (!dbus_message_iter_append_basic(&sub, DBUS_TYPE_STRING, &j->unit->id) ||
77 !dbus_message_iter_append_basic(&sub, DBUS_TYPE_OBJECT_PATH, &p)) {
78 free(p);
79 return -ENOMEM;
80 }
81
82 free(p);
83
84 if (!dbus_message_iter_close_container(i, &sub))
85 return -ENOMEM;
86
87 return 0;
88 }
89
90 static const BusProperty bus_job_properties[] = {
91 { "Id", bus_property_append_uint32, "u", offsetof(Job, id) },
92 { "State", bus_job_append_state, "s", offsetof(Job, state) },
93 { "JobType", bus_job_append_type, "s", offsetof(Job, type) },
94 { "Unit", bus_job_append_unit, "(so)", 0 },
95 { NULL, }
96 };
97
98 static DBusHandlerResult bus_job_message_dispatch(Job *j, DBusConnection *connection, DBusMessage *message) {
99 _cleanup_dbus_message_unref_ DBusMessage *reply = NULL;
100
101 if (dbus_message_is_method_call(message, "org.freedesktop.systemd1.Job", "Cancel")) {
102
103 SELINUX_UNIT_ACCESS_CHECK(j->unit, connection, message, "stop");
104
105 reply = dbus_message_new_method_return(message);
106 if (!reply)
107 return DBUS_HANDLER_RESULT_NEED_MEMORY;
108
109 job_finish_and_invalidate(j, JOB_CANCELED, true);
110 } else {
111 const BusBoundProperties bps[] = {
112 { "org.freedesktop.systemd1.Job", bus_job_properties, j },
113 { NULL, }
114 };
115
116 SELINUX_UNIT_ACCESS_CHECK(j->unit, connection, message, "status");
117
118 return bus_default_message_handler(connection, message, INTROSPECTION, INTERFACES_LIST, bps);
119 }
120
121 if (!dbus_connection_send(connection, reply, NULL))
122 return DBUS_HANDLER_RESULT_NEED_MEMORY;
123
124 return DBUS_HANDLER_RESULT_HANDLED;
125 }
126
127 static DBusHandlerResult bus_job_message_handler(DBusConnection *connection, DBusMessage *message, void *data) {
128 Manager *m = data;
129 Job *j;
130 int r;
131 _cleanup_dbus_message_unref_ DBusMessage *reply = NULL;
132
133 assert(connection);
134 assert(message);
135 assert(m);
136
137 if (streq(dbus_message_get_path(message), "/org/freedesktop/systemd1/job")) {
138 /* Be nice to gdbus and return introspection data for our mid-level paths */
139
140 if (dbus_message_is_method_call(message, "org.freedesktop.DBus.Introspectable", "Introspect")) {
141 char *introspection = NULL;
142 FILE *f;
143 Iterator i;
144 size_t size;
145
146 SELINUX_MANAGER_ACCESS_CHECK(m, connection, message, "status");
147
148 reply = dbus_message_new_method_return(message);
149 if (!reply)
150 goto oom;
151
152 /* We roll our own introspection code here, instead of
153 * relying on bus_default_message_handler() because we
154 * need to generate our introspection string
155 * dynamically. */
156
157 f = open_memstream(&introspection, &size);
158 if (!f)
159 goto oom;
160
161 fputs(DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE
162 "<node>\n", f);
163
164 fputs(BUS_INTROSPECTABLE_INTERFACE, f);
165 fputs(BUS_PEER_INTERFACE, f);
166
167 HASHMAP_FOREACH(j, m->jobs, i)
168 fprintf(f, "<node name=\"job/%lu\"/>", (unsigned long) j->id);
169
170 fputs("</node>\n", f);
171
172 if (ferror(f)) {
173 fclose(f);
174 free(introspection);
175 goto oom;
176 }
177
178 fclose(f);
179
180 if (!introspection)
181 goto oom;
182
183 if (!dbus_message_append_args(reply, DBUS_TYPE_STRING, &introspection, DBUS_TYPE_INVALID)) {
184 free(introspection);
185 goto oom;
186 }
187
188 free(introspection);
189
190 if (!dbus_connection_send(connection, reply, NULL))
191 goto oom;
192
193 return DBUS_HANDLER_RESULT_HANDLED;
194 }
195
196 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
197 }
198
199 r = manager_get_job_from_dbus_path(m, dbus_message_get_path(message), &j);
200 if (r == -ENOMEM)
201 goto oom;
202 if (r == -ENOENT) {
203 DBusError e;
204
205 dbus_error_init(&e);
206 dbus_set_error_const(&e, DBUS_ERROR_UNKNOWN_OBJECT, "Unknown job");
207 return bus_send_error_reply(connection, message, &e, r);
208 }
209 if (r < 0)
210 return bus_send_error_reply(connection, message, NULL, r);
211
212 return bus_job_message_dispatch(j, connection, message);
213
214 oom:
215 return DBUS_HANDLER_RESULT_NEED_MEMORY;
216 }
217
218 const DBusObjectPathVTable bus_job_vtable = {
219 .message_function = bus_job_message_handler
220 };
221
222 static int job_send_message(Job *j, DBusMessage* (*new_message)(Job *j)) {
223 DBusMessage *m = NULL;
224 int r;
225
226 assert(j);
227 assert(new_message);
228
229 if (bus_has_subscriber(j->manager) || j->forgot_bus_clients) {
230 m = new_message(j);
231 if (!m)
232 goto oom;
233 r = bus_broadcast(j->manager, m);
234 dbus_message_unref(m);
235 if (r < 0)
236 return r;
237
238 } else {
239 /* If nobody is subscribed, we just send the message
240 * to the client(s) which created the job */
241 JobBusClient *cl;
242 assert(j->bus_client_list);
243 LIST_FOREACH(client, cl, j->bus_client_list) {
244 assert(cl->bus);
245
246 m = new_message(j);
247 if (!m)
248 goto oom;
249
250 if (!dbus_message_set_destination(m, cl->name))
251 goto oom;
252
253 if (!dbus_connection_send(cl->bus, m, NULL))
254 goto oom;
255
256 dbus_message_unref(m);
257 m = NULL;
258 }
259 }
260
261 return 0;
262 oom:
263 if (m)
264 dbus_message_unref(m);
265 return -ENOMEM;
266 }
267
268 static DBusMessage* new_change_signal_message(Job *j) {
269 DBusMessage *m = NULL;
270 char *p = NULL;
271
272 p = job_dbus_path(j);
273 if (!p)
274 goto oom;
275
276 if (j->sent_dbus_new_signal) {
277 /* Send a properties changed signal */
278 m = bus_properties_changed_new(p, "org.freedesktop.systemd1.Job", INVALIDATING_PROPERTIES);
279 if (!m)
280 goto oom;
281
282 } else {
283 /* Send a new signal */
284
285 m = dbus_message_new_signal("/org/freedesktop/systemd1", "org.freedesktop.systemd1.Manager", "JobNew");
286 if (!m)
287 goto oom;
288
289 if (!dbus_message_append_args(m,
290 DBUS_TYPE_UINT32, &j->id,
291 DBUS_TYPE_OBJECT_PATH, &p,
292 DBUS_TYPE_STRING, &j->unit->id,
293 DBUS_TYPE_INVALID))
294 goto oom;
295 }
296
297 return m;
298
299 oom:
300 if (m)
301 dbus_message_unref(m);
302 free(p);
303 return NULL;
304 }
305
306 static DBusMessage* new_removed_signal_message(Job *j) {
307 DBusMessage *m = NULL;
308 char *p = NULL;
309 const char *r;
310
311 p = job_dbus_path(j);
312 if (!p)
313 goto oom;
314
315 m = dbus_message_new_signal("/org/freedesktop/systemd1", "org.freedesktop.systemd1.Manager", "JobRemoved");
316 if (!m)
317 goto oom;
318
319 r = job_result_to_string(j->result);
320
321 if (!dbus_message_append_args(m,
322 DBUS_TYPE_UINT32, &j->id,
323 DBUS_TYPE_OBJECT_PATH, &p,
324 DBUS_TYPE_STRING, &j->unit->id,
325 DBUS_TYPE_STRING, &r,
326 DBUS_TYPE_INVALID))
327 goto oom;
328
329 return m;
330
331 oom:
332 if (m)
333 dbus_message_unref(m);
334 free(p);
335 return NULL;
336 }
337
338 void bus_job_send_change_signal(Job *j) {
339 assert(j);
340
341 if (j->in_dbus_queue) {
342 LIST_REMOVE(Job, dbus_queue, j->manager->dbus_job_queue, j);
343 j->in_dbus_queue = false;
344 }
345
346 if (!bus_has_subscriber(j->manager) && !j->bus_client_list && !j->forgot_bus_clients) {
347 j->sent_dbus_new_signal = true;
348 return;
349 }
350
351 if (job_send_message(j, new_change_signal_message) < 0)
352 goto oom;
353
354 j->sent_dbus_new_signal = true;
355
356 return;
357
358 oom:
359 log_error("Failed to allocate job change signal.");
360 }
361
362 void bus_job_send_removed_signal(Job *j) {
363 assert(j);
364
365 if (!bus_has_subscriber(j->manager) && !j->bus_client_list && !j->forgot_bus_clients)
366 return;
367
368 if (!j->sent_dbus_new_signal)
369 bus_job_send_change_signal(j);
370
371 if (job_send_message(j, new_removed_signal_message) < 0)
372 goto oom;
373
374 return;
375
376 oom:
377 log_error("Failed to allocate job remove signal.");
378 }