chiark / gitweb /
35bf4480cbf6490d46bd44757f5ba6bb2d3bed8e
[elogind.git] / src / login / logind-session-dbus.c
1 /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
2
3 /***
4   This file is part of systemd.
5
6   Copyright 2011 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 #include <string.h>
24
25 #include "logind.h"
26 #include "logind-session.h"
27 #include "dbus-common.h"
28 #include "util.h"
29
30 #define BUS_SESSION_INTERFACE \
31         " <interface name=\"org.freedesktop.login1.Session\">\n"        \
32         "  <method name=\"Terminate\"/>\n"                              \
33         "  <method name=\"Activate\"/>\n"                               \
34         "  <method name=\"Lock\"/>\n"                                   \
35         "  <method name=\"Unlock\"/>\n"                                 \
36         "  <method name=\"SetIdleHint\">\n"                             \
37         "   <arg name=\"b\" type=\"b\"/>\n"                             \
38         "  </method>\n"                                                 \
39         "  <method name=\"Kill\">\n"                                    \
40         "   <arg name=\"who\" type=\"s\"/>\n"                           \
41         "   <arg name=\"signal\" type=\"s\"/>\n"                        \
42         "  </method>\n"                                                 \
43         "  <method name=\"TakeControl\"/>\n"                            \
44         "   <arg name=\"force\" type=\"b\"/>\n"                         \
45         "  </method>\n"                                                 \
46         "  <method name=\"ReleaseControl\"/>\n"                         \
47         "  <signal name=\"Lock\"/>\n"                                   \
48         "  <signal name=\"Unlock\"/>\n"                                 \
49         "  <property name=\"Id\" type=\"s\" access=\"read\"/>\n"        \
50         "  <property name=\"User\" type=\"(uo)\" access=\"read\"/>\n"   \
51         "  <property name=\"Name\" type=\"s\" access=\"read\"/>\n"      \
52         "  <property name=\"Timestamp\" type=\"t\" access=\"read\"/>\n" \
53         "  <property name=\"TimestampMonotonic\" type=\"t\" access=\"read\"/>\n" \
54         "  <property name=\"VTNr\" type=\"u\" access=\"read\"/>\n"      \
55         "  <property name=\"Seat\" type=\"(so)\" access=\"read\"/>\n"   \
56         "  <property name=\"TTY\" type=\"s\" access=\"read\"/>\n"       \
57         "  <property name=\"Display\" type=\"s\" access=\"read\"/>\n"   \
58         "  <property name=\"Remote\" type=\"b\" access=\"read\"/>\n"    \
59         "  <property name=\"RemoteHost\" type=\"s\" access=\"read\"/>\n" \
60         "  <property name=\"RemoteUser\" type=\"s\" access=\"read\"/>\n" \
61         "  <property name=\"Service\" type=\"s\" access=\"read\"/>\n"   \
62         "  <property name=\"Scope\" type=\"s\" access=\"read\"/>\n"     \
63         "  <property name=\"Leader\" type=\"u\" access=\"read\"/>\n"    \
64         "  <property name=\"Audit\" type=\"u\" access=\"read\"/>\n"     \
65         "  <property name=\"Type\" type=\"s\" access=\"read\"/>\n"      \
66         "  <property name=\"Class\" type=\"s\" access=\"read\"/>\n"     \
67         "  <property name=\"Active\" type=\"b\" access=\"read\"/>\n"    \
68         "  <property name=\"State\" type=\"s\" access=\"read\"/>\n"     \
69         "  <property name=\"IdleHint\" type=\"b\" access=\"read\"/>\n"  \
70         "  <property name=\"IdleSinceHint\" type=\"t\" access=\"read\"/>\n" \
71         "  <property name=\"IdleSinceHintMonotonic\" type=\"t\" access=\"read\"/>\n" \
72         " </interface>\n"
73
74 #define INTROSPECTION                                                   \
75         DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE                       \
76         "<node>\n"                                                      \
77         BUS_SESSION_INTERFACE                                           \
78         BUS_PROPERTIES_INTERFACE                                        \
79         BUS_PEER_INTERFACE                                              \
80         BUS_INTROSPECTABLE_INTERFACE                                    \
81         "</node>\n"
82
83 #define INTERFACES_LIST                              \
84         BUS_GENERIC_INTERFACES_LIST                  \
85         "org.freedesktop.login1.Session\0"
86
87 static int bus_session_append_seat(DBusMessageIter *i, const char *property, void *data) {
88         DBusMessageIter sub;
89         Session *s = data;
90         const char *id, *path;
91         char *p = NULL;
92
93         assert(i);
94         assert(property);
95         assert(s);
96
97         if (!dbus_message_iter_open_container(i, DBUS_TYPE_STRUCT, NULL, &sub))
98                 return -ENOMEM;
99
100         if (s->seat) {
101                 id = s->seat->id;
102                 path = p = seat_bus_path(s->seat);
103
104                 if (!p)
105                         return -ENOMEM;
106         } else {
107                 id = "";
108                 path = "/";
109         }
110
111         if (!dbus_message_iter_append_basic(&sub, DBUS_TYPE_STRING, &id) ||
112             !dbus_message_iter_append_basic(&sub, DBUS_TYPE_OBJECT_PATH, &path))
113                 return -ENOMEM;
114
115         if (!dbus_message_iter_close_container(i, &sub))
116                 return -ENOMEM;
117
118         return 0;
119 }
120
121 static int bus_session_append_user(DBusMessageIter *i, const char *property, void *data) {
122         DBusMessageIter sub;
123         User *u = data;
124         _cleanup_free_ char *p = NULL;
125
126         assert(i);
127         assert(property);
128         assert(u);
129
130         if (!dbus_message_iter_open_container(i, DBUS_TYPE_STRUCT, NULL, &sub))
131                 return -ENOMEM;
132
133         p = user_bus_path(u);
134         if (!p)
135                 return -ENOMEM;
136
137         if (!dbus_message_iter_append_basic(&sub, DBUS_TYPE_UINT32, &u->uid) ||
138             !dbus_message_iter_append_basic(&sub, DBUS_TYPE_OBJECT_PATH, &p))
139                 return -ENOMEM;
140
141         if (!dbus_message_iter_close_container(i, &sub))
142                 return -ENOMEM;
143
144         return 0;
145 }
146
147 static int bus_session_append_active(DBusMessageIter *i, const char *property, void *data) {
148         Session *s = data;
149         dbus_bool_t b;
150
151         assert(i);
152         assert(property);
153         assert(s);
154
155         b = session_is_active(s);
156         if (!dbus_message_iter_append_basic(i, DBUS_TYPE_BOOLEAN, &b))
157                 return -ENOMEM;
158
159         return 0;
160 }
161
162 static int bus_session_append_idle_hint(DBusMessageIter *i, const char *property, void *data) {
163         Session *s = data;
164         int b;
165
166         assert(i);
167         assert(property);
168         assert(s);
169
170         b = session_get_idle_hint(s, NULL) > 0;
171         if (!dbus_message_iter_append_basic(i, DBUS_TYPE_BOOLEAN, &b))
172                 return -ENOMEM;
173
174         return 0;
175 }
176
177 static int bus_session_append_idle_hint_since(DBusMessageIter *i, const char *property, void *data) {
178         Session *s = data;
179         dual_timestamp t;
180         uint64_t u;
181         int r;
182
183         assert(i);
184         assert(property);
185         assert(s);
186
187         r = session_get_idle_hint(s, &t);
188         if (r < 0)
189                 return r;
190
191         u = streq(property, "IdleSinceHint") ? t.realtime : t.monotonic;
192
193         if (!dbus_message_iter_append_basic(i, DBUS_TYPE_UINT64, &u))
194                 return -ENOMEM;
195
196         return 0;
197 }
198
199 static DEFINE_BUS_PROPERTY_APPEND_ENUM(bus_session_append_type, session_type, SessionType);
200 static DEFINE_BUS_PROPERTY_APPEND_ENUM(bus_session_append_class, session_class, SessionClass);
201
202 static int bus_session_append_state(DBusMessageIter *i, const char *property, void *data) {
203         Session *s = data;
204         const char *state;
205
206         assert(i);
207         assert(property);
208         assert(s);
209
210         state = session_state_to_string(session_get_state(s));
211
212         if (!dbus_message_iter_append_basic(i, DBUS_TYPE_STRING, &state))
213                 return -ENOMEM;
214
215         return 0;
216 }
217
218 static int get_session_for_path(Manager *m, const char *path, Session **_s) {
219         _cleanup_free_ char *id = NULL;
220         Session *s;
221
222         assert(m);
223         assert(path);
224         assert(_s);
225
226         if (!startswith(path, "/org/freedesktop/login1/session/"))
227                 return -EINVAL;
228
229         id = bus_path_unescape(path + 32);
230         if (!id)
231                 return -ENOMEM;
232
233         s = hashmap_get(m->sessions, id);
234         if (!s)
235                 return -ENOENT;
236
237         *_s = s;
238         return 0;
239 }
240
241 static const BusProperty bus_login_session_properties[] = {
242         { "Id",                     bus_property_append_string,         "s", offsetof(Session, id),                 true },
243         { "Timestamp",              bus_property_append_usec,           "t", offsetof(Session, timestamp.realtime)  },
244         { "TimestampMonotonic",     bus_property_append_usec,           "t", offsetof(Session, timestamp.monotonic) },
245         { "VTNr",                   bus_property_append_uint32,         "u", offsetof(Session, vtnr)                },
246         { "Seat",                   bus_session_append_seat,         "(so)", 0 },
247         { "TTY",                    bus_property_append_string,         "s", offsetof(Session, tty),                true },
248         { "Display",                bus_property_append_string,         "s", offsetof(Session, display),            true },
249         { "Remote",                 bus_property_append_bool,           "b", offsetof(Session, remote)              },
250         { "RemoteUser",             bus_property_append_string,         "s", offsetof(Session, remote_user),        true },
251         { "RemoteHost",             bus_property_append_string,         "s", offsetof(Session, remote_host),        true },
252         { "Service",                bus_property_append_string,         "s", offsetof(Session, service),            true },
253         { "Scope",                  bus_property_append_string,         "s", offsetof(Session, scope),              true },
254         { "Leader",                 bus_property_append_pid,            "u", offsetof(Session, leader)              },
255         { "Audit",                  bus_property_append_uint32,         "u", offsetof(Session, audit_id)            },
256         { "Type",                   bus_session_append_type,            "s", offsetof(Session, type)                },
257         { "Class",                  bus_session_append_class,           "s", offsetof(Session, class)               },
258         { "Active",                 bus_session_append_active,          "b", 0 },
259         { "State",                  bus_session_append_state,           "s", 0 },
260         { "IdleHint",               bus_session_append_idle_hint,       "b", 0 },
261         { "IdleSinceHint",          bus_session_append_idle_hint_since, "t", 0 },
262         { "IdleSinceHintMonotonic", bus_session_append_idle_hint_since, "t", 0 },
263         { NULL, }
264 };
265
266 static const BusProperty bus_login_session_user_properties[] = {
267         { "User",                   bus_session_append_user,         "(uo)", 0 },
268         { "Name",                   bus_property_append_string,         "s", offsetof(User, name),                  true },
269         { NULL, }
270 };
271
272 static DBusHandlerResult session_message_dispatch(
273                 Session *s,
274                 DBusConnection *connection,
275                 DBusMessage *message) {
276
277         DBusError error;
278         _cleanup_dbus_message_unref_ DBusMessage *reply = NULL;
279         int r;
280
281         assert(s);
282         assert(connection);
283         assert(message);
284
285         dbus_error_init(&error);
286
287         if (dbus_message_is_method_call(message, "org.freedesktop.login1.Session", "Terminate")) {
288
289                 r = session_stop(s);
290                 if (r < 0)
291                         return bus_send_error_reply(connection, message, NULL, r);
292
293                 reply = dbus_message_new_method_return(message);
294                 if (!reply)
295                         goto oom;
296
297         } else if (dbus_message_is_method_call(message, "org.freedesktop.login1.Session", "Activate")) {
298
299                 r = session_activate(s);
300                 if (r < 0)
301                         return bus_send_error_reply(connection, message, NULL, r);
302
303                 reply = dbus_message_new_method_return(message);
304                 if (!reply)
305                         goto oom;
306
307         } else if (dbus_message_is_method_call(message, "org.freedesktop.login1.Session", "Lock") ||
308                    dbus_message_is_method_call(message, "org.freedesktop.login1.Session", "Unlock")) {
309
310                 if (session_send_lock(s, streq(dbus_message_get_member(message), "Lock")) < 0)
311                         goto oom;
312
313                 reply = dbus_message_new_method_return(message);
314                 if (!reply)
315                         goto oom;
316
317         } else if (dbus_message_is_method_call(message, "org.freedesktop.login1.Session", "SetIdleHint")) {
318                 dbus_bool_t b;
319                 unsigned long ul;
320
321                 if (!dbus_message_get_args(
322                                     message,
323                                     &error,
324                                     DBUS_TYPE_BOOLEAN, &b,
325                                     DBUS_TYPE_INVALID))
326                         return bus_send_error_reply(connection, message, &error, -EINVAL);
327
328                 ul = dbus_bus_get_unix_user(connection, dbus_message_get_sender(message), &error);
329                 if (ul == (unsigned long) -1)
330                         return bus_send_error_reply(connection, message, &error, -EIO);
331
332                 if (ul != 0 && ul != s->user->uid)
333                         return bus_send_error_reply(connection, message, NULL, -EPERM);
334
335                 session_set_idle_hint(s, b);
336
337                 reply = dbus_message_new_method_return(message);
338                 if (!reply)
339                         goto oom;
340
341         } else if (dbus_message_is_method_call(message, "org.freedesktop.login1.Session", "Kill")) {
342                 const char *swho;
343                 int32_t signo;
344                 KillWho who;
345
346                 if (!dbus_message_get_args(
347                                     message,
348                                     &error,
349                                     DBUS_TYPE_STRING, &swho,
350                                     DBUS_TYPE_INT32, &signo,
351                                     DBUS_TYPE_INVALID))
352                         return bus_send_error_reply(connection, message, &error, -EINVAL);
353
354                 if (isempty(swho))
355                         who = KILL_ALL;
356                 else {
357                         who = kill_who_from_string(swho);
358                         if (who < 0)
359                                 return bus_send_error_reply(connection, message, &error, -EINVAL);
360                 }
361
362                 if (signo <= 0 || signo >= _NSIG)
363                         return bus_send_error_reply(connection, message, &error, -EINVAL);
364
365                 r = session_kill(s, who, signo);
366                 if (r < 0)
367                         return bus_send_error_reply(connection, message, NULL, r);
368
369                 reply = dbus_message_new_method_return(message);
370                 if (!reply)
371                         goto oom;
372
373         } else if (dbus_message_is_method_call(message, "org.freedesktop.login1.Session", "TakeControl")) {
374                 dbus_bool_t force;
375                 unsigned long ul;
376
377                 if (!dbus_message_get_args(
378                                     message,
379                                     &error,
380                                     DBUS_TYPE_BOOLEAN, &force,
381                                     DBUS_TYPE_INVALID))
382                         return bus_send_error_reply(connection, message, &error, -EINVAL);
383
384                 ul = dbus_bus_get_unix_user(connection, dbus_message_get_sender(message), &error);
385                 if (ul == (unsigned long) -1)
386                         return bus_send_error_reply(connection, message, &error, -EIO);
387
388                 if (ul != 0 && (force || ul != s->user->uid))
389                         return bus_send_error_reply(connection, message, NULL, -EPERM);
390
391                 r = session_set_controller(s, bus_message_get_sender_with_fallback(message), force);
392                 if (r < 0)
393                         return bus_send_error_reply(connection, message, NULL, r);
394
395                 reply = dbus_message_new_method_return(message);
396                 if (!reply)
397                         goto oom;
398
399         } else if (dbus_message_is_method_call(message, "org.freedesktop.login1.Session", "ReleaseControl")) {
400                 const char *sender = bus_message_get_sender_with_fallback(message);
401
402                 if (!session_is_controller(s, sender))
403                         return bus_send_error_reply(connection, message, NULL, -EPERM);
404
405                 session_drop_controller(s);
406
407                 reply = dbus_message_new_method_return(message);
408                 if (!reply)
409                         goto oom;
410
411         } else {
412                 const BusBoundProperties bps[] = {
413                         { "org.freedesktop.login1.Session", bus_login_session_properties,      s       },
414                         { "org.freedesktop.login1.Session", bus_login_session_user_properties, s->user },
415                         { NULL, }
416                 };
417                 return bus_default_message_handler(connection, message, INTROSPECTION, INTERFACES_LIST, bps);
418         }
419
420         if (reply) {
421                 if (!bus_maybe_send_reply(connection, message, reply))
422                         goto oom;
423         }
424
425         return DBUS_HANDLER_RESULT_HANDLED;
426
427 oom:
428         dbus_error_free(&error);
429
430         return DBUS_HANDLER_RESULT_NEED_MEMORY;
431 }
432
433 static DBusHandlerResult session_message_handler(
434                 DBusConnection *connection,
435                 DBusMessage *message,
436                 void *userdata) {
437
438         Manager *m = userdata;
439         Session *s;
440         int r;
441
442         r = get_session_for_path(m, dbus_message_get_path(message), &s);
443         if (r < 0) {
444
445                 if (r == -ENOMEM)
446                         return DBUS_HANDLER_RESULT_NEED_MEMORY;
447
448                 if (r == -ENOENT) {
449                         DBusError e;
450
451                         dbus_error_init(&e);
452                         dbus_set_error_const(&e, DBUS_ERROR_UNKNOWN_OBJECT, "Unknown session");
453                         return bus_send_error_reply(connection, message, &e, r);
454                 }
455
456                 return bus_send_error_reply(connection, message, NULL, r);
457         }
458
459         return session_message_dispatch(s, connection, message);
460 }
461
462 const DBusObjectPathVTable bus_session_vtable = {
463         .message_function = session_message_handler
464 };
465
466 char *session_bus_path(Session *s) {
467         _cleanup_free_ char *t = NULL;
468
469         assert(s);
470
471         t = bus_path_escape(s->id);
472         if (!t)
473                 return NULL;
474
475         return strappend("/org/freedesktop/login1/session/", t);
476 }
477
478 int session_send_signal(Session *s, bool new_session) {
479         _cleanup_dbus_message_unref_ DBusMessage *m = NULL;
480         _cleanup_free_ char *p = NULL;
481
482         assert(s);
483
484         m = dbus_message_new_signal("/org/freedesktop/login1",
485                                     "org.freedesktop.login1.Manager",
486                                     new_session ? "SessionNew" : "SessionRemoved");
487
488         if (!m)
489                 return -ENOMEM;
490
491         p = session_bus_path(s);
492         if (!p)
493                 return -ENOMEM;
494
495         if (!dbus_message_append_args(
496                             m,
497                             DBUS_TYPE_STRING, &s->id,
498                             DBUS_TYPE_OBJECT_PATH, &p,
499                             DBUS_TYPE_INVALID))
500                 return -ENOMEM;
501
502         if (!dbus_connection_send(s->manager->bus, m, NULL))
503                 return -ENOMEM;
504
505         return 0;
506 }
507
508 int session_send_changed(Session *s, const char *properties) {
509         _cleanup_dbus_message_unref_ DBusMessage *m = NULL;
510         _cleanup_free_ char *p = NULL;
511
512         assert(s);
513
514         if (!s->started)
515                 return 0;
516
517         p = session_bus_path(s);
518         if (!p)
519                 return -ENOMEM;
520
521         m = bus_properties_changed_new(p, "org.freedesktop.login1.Session", properties);
522         if (!m)
523                 return -ENOMEM;
524
525         if (!dbus_connection_send(s->manager->bus, m, NULL))
526                 return -ENOMEM;
527
528         return 0;
529 }
530
531 int session_send_lock(Session *s, bool lock) {
532         _cleanup_dbus_message_unref_ DBusMessage *m = NULL;
533         bool b;
534         _cleanup_free_ char *p = NULL;
535
536         assert(s);
537
538         p = session_bus_path(s);
539         if (!p)
540                 return -ENOMEM;
541
542         m = dbus_message_new_signal(p, "org.freedesktop.login1.Session", lock ? "Lock" : "Unlock");
543
544         if (!m)
545                 return -ENOMEM;
546
547         b = dbus_connection_send(s->manager->bus, m, NULL);
548         if (!b)
549                 return -ENOMEM;
550
551         return 0;
552 }
553
554 int session_send_lock_all(Manager *m, bool lock) {
555         Session *session;
556         Iterator i;
557         int r = 0;
558
559         assert(m);
560
561         HASHMAP_FOREACH(session, m->sessions, i) {
562                 int k;
563
564                 k = session_send_lock(session, lock);
565                 if (k < 0)
566                         r = k;
567         }
568
569         return r;
570 }
571
572 int session_send_create_reply(Session *s, DBusError *error) {
573         _cleanup_dbus_message_unref_ DBusMessage *reply = NULL;
574
575         assert(s);
576
577         if (!s->create_message)
578                 return 0;
579
580         /* This is called after the session scope was successfully
581          * created, and finishes where bus_manager_create_session()
582          * left off. */
583
584         if (error) {
585                 DBusError buffer;
586
587                 dbus_error_init(&buffer);
588
589                 if (!dbus_error_is_set(error)) {
590                         dbus_set_error_const(&buffer, DBUS_ERROR_INVALID_ARGS, "Invalid Arguments");
591                         error = &buffer;
592                 }
593
594                 reply = dbus_message_new_error(s->create_message, error->name, error->message);
595                 dbus_error_free(&buffer);
596
597                 if (!reply)
598                         return log_oom();
599         } else {
600                 _cleanup_close_ int fifo_fd = -1;
601                 _cleanup_free_ char *path = NULL;
602                 const char *cseat;
603                 uint32_t vtnr;
604                 dbus_bool_t exists;
605
606                 fifo_fd = session_create_fifo(s);
607                 if (fifo_fd < 0) {
608                         log_error("Failed to create fifo: %s", strerror(-fifo_fd));
609                         return fifo_fd;
610                 }
611
612                 path = session_bus_path(s);
613                 if (!path)
614                         return log_oom();
615
616                 reply = dbus_message_new_method_return(s->create_message);
617                 if (!reply)
618                         return log_oom();
619
620                 cseat = s->seat ? s->seat->id : "";
621                 vtnr = s->vtnr;
622                 exists = false;
623
624                 if (!dbus_message_append_args(
625                                     reply,
626                                     DBUS_TYPE_STRING, &s->id,
627                                     DBUS_TYPE_OBJECT_PATH, &path,
628                                     DBUS_TYPE_STRING, &s->user->runtime_path,
629                                     DBUS_TYPE_UNIX_FD, &fifo_fd,
630                                     DBUS_TYPE_STRING, &cseat,
631                                     DBUS_TYPE_UINT32, &vtnr,
632                                     DBUS_TYPE_BOOLEAN, &exists,
633                                     DBUS_TYPE_INVALID))
634                         return log_oom();
635         }
636
637         /* Update the state file before we notify the client about the
638          * result */
639         session_save(s);
640
641         if (!dbus_connection_send(s->manager->bus, reply, NULL))
642                 return log_oom();
643
644         dbus_message_unref(s->create_message);
645         s->create_message = NULL;
646
647         return 0;
648 }