chiark / gitweb /
logind: make sure login sessions are terminated with SIGHUP
[elogind.git] / src / login / loginctl.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 <dbus/dbus.h>
23 #include <unistd.h>
24 #include <errno.h>
25 #include <string.h>
26 #include <getopt.h>
27 #include <pwd.h>
28 #include <locale.h>
29
30 #include "log.h"
31 #include "util.h"
32 #include "macro.h"
33 #include "pager.h"
34 #include "dbus-common.h"
35 #include "build.h"
36 #include "strv.h"
37 #include "unit-name.h"
38 #include "sysfs-show.h"
39 #include "cgroup-show.h"
40 #include "cgroup-util.h"
41 #include "spawn-polkit-agent.h"
42
43 static char **arg_property = NULL;
44 static bool arg_all = false;
45 static bool arg_full = false;
46 static bool arg_no_pager = false;
47 static const char *arg_kill_who = NULL;
48 static int arg_signal = SIGTERM;
49 static enum transport {
50         TRANSPORT_NORMAL,
51         TRANSPORT_SSH,
52         TRANSPORT_POLKIT
53 } arg_transport = TRANSPORT_NORMAL;
54 static bool arg_ask_password = true;
55 static char *arg_host = NULL;
56 static char *arg_user = NULL;
57
58 static void pager_open_if_enabled(void) {
59
60         /* Cache result before we open the pager */
61         if (arg_no_pager)
62                 return;
63
64         pager_open(false);
65 }
66
67 static void polkit_agent_open_if_enabled(void) {
68
69         /* Open the polkit agent as a child process if necessary */
70
71         if (!arg_ask_password)
72                 return;
73
74         polkit_agent_open();
75 }
76
77 static int list_sessions(DBusConnection *bus, char **args, unsigned n) {
78         _cleanup_dbus_message_unref_ DBusMessage *reply = NULL;
79         int r;
80         DBusMessageIter iter, sub, sub2;
81         unsigned k = 0;
82
83         pager_open_if_enabled();
84
85         r = bus_method_call_with_reply (
86                         bus,
87                         "org.freedesktop.login1",
88                         "/org/freedesktop/login1",
89                         "org.freedesktop.login1.Manager",
90                         "ListSessions",
91                         &reply,
92                         NULL,
93                         DBUS_TYPE_INVALID);
94         if (r)
95                 return r;
96
97         if (!dbus_message_iter_init(reply, &iter) ||
98             dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY ||
99             dbus_message_iter_get_element_type(&iter) != DBUS_TYPE_STRUCT)  {
100                 log_error("Failed to parse reply.");
101                 return -EIO;
102         }
103
104         dbus_message_iter_recurse(&iter, &sub);
105
106         if (on_tty())
107                 printf("%10s %10s %-16s %-16s\n", "SESSION", "UID", "USER", "SEAT");
108
109         while (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_INVALID) {
110                 const char *id, *user, *seat, *object;
111                 uint32_t uid;
112
113                 if (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_STRUCT) {
114                         log_error("Failed to parse reply.");
115                         return -EIO;
116                 }
117
118                 dbus_message_iter_recurse(&sub, &sub2);
119
120                 if (bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_STRING, &id, true) < 0 ||
121                     bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_UINT32, &uid, true) < 0 ||
122                     bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_STRING, &user, true) < 0 ||
123                     bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_STRING, &seat, true) < 0 ||
124                     bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_OBJECT_PATH, &object, false) < 0) {
125                         log_error("Failed to parse reply.");
126                         return -EIO;
127                 }
128
129                 printf("%10s %10u %-16s %-16s\n", id, (unsigned) uid, user, seat);
130
131                 k++;
132
133                 dbus_message_iter_next(&sub);
134         }
135
136         if (on_tty())
137                 printf("\n%u sessions listed.\n", k);
138
139         return 0;
140 }
141
142 static int list_users(DBusConnection *bus, char **args, unsigned n) {
143         _cleanup_dbus_message_unref_ DBusMessage *reply = NULL;
144         int r;
145         DBusMessageIter iter, sub, sub2;
146         unsigned k = 0;
147
148         pager_open_if_enabled();
149
150         r = bus_method_call_with_reply (
151                         bus,
152                         "org.freedesktop.login1",
153                         "/org/freedesktop/login1",
154                         "org.freedesktop.login1.Manager",
155                         "ListUsers",
156                         &reply,
157                         NULL,
158                         DBUS_TYPE_INVALID);
159         if (r)
160                 return r;
161
162         if (!dbus_message_iter_init(reply, &iter) ||
163             dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY ||
164             dbus_message_iter_get_element_type(&iter) != DBUS_TYPE_STRUCT)  {
165                 log_error("Failed to parse reply.");
166                 return -EIO;
167         }
168
169         dbus_message_iter_recurse(&iter, &sub);
170
171         if (on_tty())
172                 printf("%10s %-16s\n", "UID", "USER");
173
174         while (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_INVALID) {
175                 const char *user, *object;
176                 uint32_t uid;
177
178                 if (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_STRUCT) {
179                         log_error("Failed to parse reply.");
180                         return -EIO;
181                 }
182
183                 dbus_message_iter_recurse(&sub, &sub2);
184
185                 if (bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_UINT32, &uid, true) < 0 ||
186                     bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_STRING, &user, true) < 0 ||
187                     bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_OBJECT_PATH, &object, false) < 0) {
188                         log_error("Failed to parse reply.");
189                         return -EIO;
190                 }
191
192                 printf("%10u %-16s\n", (unsigned) uid, user);
193
194                 k++;
195
196                 dbus_message_iter_next(&sub);
197         }
198
199         if (on_tty())
200                 printf("\n%u users listed.\n", k);
201
202         return 0;
203 }
204
205 static int list_seats(DBusConnection *bus, char **args, unsigned n) {
206         _cleanup_dbus_message_unref_ DBusMessage *reply = NULL;
207         int r;
208         DBusMessageIter iter, sub, sub2;
209         unsigned k = 0;
210
211         pager_open_if_enabled();
212
213         r = bus_method_call_with_reply (
214                         bus,
215                         "org.freedesktop.login1",
216                         "/org/freedesktop/login1",
217                         "org.freedesktop.login1.Manager",
218                         "ListSeats",
219                         &reply,
220                         NULL,
221                         DBUS_TYPE_INVALID);
222         if (r)
223                 return r;
224
225         if (!dbus_message_iter_init(reply, &iter) ||
226             dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY ||
227             dbus_message_iter_get_element_type(&iter) != DBUS_TYPE_STRUCT)  {
228                 log_error("Failed to parse reply.");
229                 return -EIO;
230         }
231
232         dbus_message_iter_recurse(&iter, &sub);
233
234         if (on_tty())
235                 printf("%-16s\n", "SEAT");
236
237         while (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_INVALID) {
238                 const char *seat, *object;
239
240                 if (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_STRUCT) {
241                         log_error("Failed to parse reply.");
242                         return -EIO;
243                 }
244
245                 dbus_message_iter_recurse(&sub, &sub2);
246
247                 if (bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_STRING, &seat, true) < 0 ||
248                     bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_OBJECT_PATH, &object, false) < 0) {
249                         log_error("Failed to parse reply.");
250                         return -EIO;
251                 }
252
253                 printf("%-16s\n", seat);
254
255                 k++;
256
257                 dbus_message_iter_next(&sub);
258         }
259
260         if (on_tty())
261                 printf("\n%u seats listed.\n", k);
262
263         return 0;
264 }
265
266 static int show_unit_cgroup(DBusConnection *bus, const char *interface, const char *unit, pid_t leader) {
267         const char *property = "ControlGroup";
268         _cleanup_dbus_message_unref_ DBusMessage *reply = NULL;
269         _cleanup_free_ char *path = NULL;
270         DBusMessageIter iter, sub;
271         const char *cgroup;
272         DBusError error;
273         int r, output_flags;
274         unsigned c;
275
276         assert(bus);
277         assert(unit);
278
279         if (arg_transport == TRANSPORT_SSH)
280                 return 0;
281
282         path = unit_dbus_path_from_name(unit);
283         if (!path)
284                 return log_oom();
285
286         r = bus_method_call_with_reply(
287                         bus,
288                         "org.freedesktop.systemd1",
289                         path,
290                         "org.freedesktop.DBus.Properties",
291                         "Get",
292                         &reply,
293                         &error,
294                         DBUS_TYPE_STRING, &interface,
295                         DBUS_TYPE_STRING, &property,
296                         DBUS_TYPE_INVALID);
297         if (r < 0) {
298                 log_error("Failed to query ControlGroup: %s", bus_error(&error, r));
299                 dbus_error_free(&error);
300                 return r;
301         }
302
303         if (!dbus_message_iter_init(reply, &iter) ||
304             dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_VARIANT) {
305                 log_error("Failed to parse reply.");
306                 return -EINVAL;
307         }
308
309         dbus_message_iter_recurse(&iter, &sub);
310         if (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_STRING) {
311                 log_error("Failed to parse reply.");
312                 return -EINVAL;
313         }
314
315         dbus_message_iter_get_basic(&sub, &cgroup);
316
317         if (isempty(cgroup))
318                 return 0;
319
320         if (cg_is_empty_recursive(SYSTEMD_CGROUP_CONTROLLER, cgroup, false) != 0 && leader <= 0)
321                 return 0;
322
323         output_flags =
324                 arg_all * OUTPUT_SHOW_ALL |
325                 arg_full * OUTPUT_FULL_WIDTH;
326
327         c = columns();
328         if (c > 18)
329                 c -= 18;
330         else
331                 c = 0;
332
333         show_cgroup_and_extra(SYSTEMD_CGROUP_CONTROLLER, cgroup, "\t\t  ", c, false, &leader, leader > 0, output_flags);
334         return 0;
335 }
336
337 typedef struct SessionStatusInfo {
338         const char *id;
339         uid_t uid;
340         const char *name;
341         usec_t timestamp;
342         int vtnr;
343         const char *seat;
344         const char *tty;
345         const char *display;
346         bool remote;
347         const char *remote_host;
348         const char *remote_user;
349         const char *service;
350         pid_t leader;
351         const char *type;
352         const char *class;
353         const char *state;
354         const char *scope;
355 } SessionStatusInfo;
356
357 typedef struct UserStatusInfo {
358         uid_t uid;
359         const char *name;
360         usec_t timestamp;
361         const char *state;
362         char **sessions;
363         const char *display;
364         const char *slice;
365 } UserStatusInfo;
366
367 typedef struct SeatStatusInfo {
368         const char *id;
369         const char *active_session;
370         char **sessions;
371 } SeatStatusInfo;
372
373 static void print_session_status_info(DBusConnection *bus, SessionStatusInfo *i) {
374         char since1[FORMAT_TIMESTAMP_RELATIVE_MAX], *s1;
375         char since2[FORMAT_TIMESTAMP_MAX], *s2;
376         assert(i);
377
378         printf("%s - ", strna(i->id));
379
380         if (i->name)
381                 printf("%s (%u)\n", i->name, (unsigned) i->uid);
382         else
383                 printf("%u\n", (unsigned) i->uid);
384
385         s1 = format_timestamp_relative(since1, sizeof(since1), i->timestamp);
386         s2 = format_timestamp(since2, sizeof(since2), i->timestamp);
387
388         if (s1)
389                 printf("\t   Since: %s; %s\n", s2, s1);
390         else if (s2)
391                 printf("\t   Since: %s\n", s2);
392
393         if (i->leader > 0) {
394                 _cleanup_free_ char *t = NULL;
395
396                 printf("\t  Leader: %u", (unsigned) i->leader);
397
398                 get_process_comm(i->leader, &t);
399                 if (t)
400                         printf(" (%s)", t);
401
402                 printf("\n");
403         }
404
405         if (i->seat) {
406                 printf("\t    Seat: %s", i->seat);
407
408                 if (i->vtnr > 0)
409                         printf("; vc%i", i->vtnr);
410
411                 printf("\n");
412         }
413
414         if (i->tty)
415                 printf("\t     TTY: %s\n", i->tty);
416         else if (i->display)
417                 printf("\t Display: %s\n", i->display);
418
419         if (i->remote_host && i->remote_user)
420                 printf("\t  Remote: %s@%s\n", i->remote_user, i->remote_host);
421         else if (i->remote_host)
422                 printf("\t  Remote: %s\n", i->remote_host);
423         else if (i->remote_user)
424                 printf("\t  Remote: user %s\n", i->remote_user);
425         else if (i->remote)
426                 printf("\t  Remote: Yes\n");
427
428         if (i->service) {
429                 printf("\t Service: %s", i->service);
430
431                 if (i->type)
432                         printf("; type %s", i->type);
433
434                 if (i->class)
435                         printf("; class %s", i->class);
436
437                 printf("\n");
438         } else if (i->type) {
439                 printf("\t    Type: %s\n", i->type);
440
441                 if (i->class)
442                         printf("; class %s", i->class);
443         } else if (i->class)
444                 printf("\t   Class: %s\n", i->class);
445
446         if (i->state)
447                 printf("\t   State: %s\n", i->state);
448
449         if (i->scope) {
450                 printf("\t    Unit: %s\n", i->scope);
451                 show_unit_cgroup(bus, "org.freedesktop.systemd1.Scope", i->scope, i->leader);
452         }
453 }
454
455 static void print_user_status_info(DBusConnection *bus, UserStatusInfo *i) {
456         char since1[FORMAT_TIMESTAMP_RELATIVE_MAX], *s1;
457         char since2[FORMAT_TIMESTAMP_MAX], *s2;
458         assert(i);
459
460         if (i->name)
461                 printf("%s (%u)\n", i->name, (unsigned) i->uid);
462         else
463                 printf("%u\n", (unsigned) i->uid);
464
465         s1 = format_timestamp_relative(since1, sizeof(since1), i->timestamp);
466         s2 = format_timestamp(since2, sizeof(since2), i->timestamp);
467
468         if (s1)
469                 printf("\t   Since: %s; %s\n", s2, s1);
470         else if (s2)
471                 printf("\t   Since: %s\n", s2);
472
473         if (!isempty(i->state))
474                 printf("\t   State: %s\n", i->state);
475
476
477         if (!strv_isempty(i->sessions)) {
478                 char **l;
479                 printf("\tSessions:");
480
481                 STRV_FOREACH(l, i->sessions) {
482                         if (streq_ptr(*l, i->display))
483                                 printf(" *%s", *l);
484                         else
485                                 printf(" %s", *l);
486                 }
487
488                 printf("\n");
489         }
490
491         if (i->slice) {
492                 printf("\t    Unit: %s\n", i->slice);
493                 show_unit_cgroup(bus, "org.freedesktop.systemd1.Slice", i->slice, 0);
494         }
495 }
496
497 static void print_seat_status_info(SeatStatusInfo *i) {
498         assert(i);
499
500         printf("%s\n", strna(i->id));
501
502         if (!strv_isempty(i->sessions)) {
503                 char **l;
504                 printf("\tSessions:");
505
506                 STRV_FOREACH(l, i->sessions) {
507                         if (streq_ptr(*l, i->active_session))
508                                 printf(" *%s", *l);
509                         else
510                                 printf(" %s", *l);
511                 }
512
513                 printf("\n");
514         }
515
516         if (arg_transport != TRANSPORT_SSH) {
517                 unsigned c;
518
519                 c = columns();
520                 if (c > 21)
521                         c -= 21;
522                 else
523                         c = 0;
524
525                 printf("\t Devices:\n");
526
527                 show_sysfs(i->id, "\t\t  ", c);
528         }
529 }
530
531 static int status_property_session(const char *name, DBusMessageIter *iter, SessionStatusInfo *i) {
532         assert(name);
533         assert(iter);
534         assert(i);
535
536         switch (dbus_message_iter_get_arg_type(iter)) {
537
538         case DBUS_TYPE_STRING: {
539                 const char *s;
540
541                 dbus_message_iter_get_basic(iter, &s);
542
543                 if (!isempty(s)) {
544                         if (streq(name, "Id"))
545                                 i->id = s;
546                         else if (streq(name, "Name"))
547                                 i->name = s;
548                         else if (streq(name, "TTY"))
549                                 i->tty = s;
550                         else if (streq(name, "Display"))
551                                 i->display = s;
552                         else if (streq(name, "RemoteHost"))
553                                 i->remote_host = s;
554                         else if (streq(name, "RemoteUser"))
555                                 i->remote_user = s;
556                         else if (streq(name, "Service"))
557                                 i->service = s;
558                         else if (streq(name, "Type"))
559                                 i->type = s;
560                         else if (streq(name, "Class"))
561                                 i->class = s;
562                         else if (streq(name, "Scope"))
563                                 i->scope = s;
564                         else if (streq(name, "State"))
565                                 i->state = s;
566                 }
567                 break;
568         }
569
570         case DBUS_TYPE_UINT32: {
571                 uint32_t u;
572
573                 dbus_message_iter_get_basic(iter, &u);
574
575                 if (streq(name, "VTNr"))
576                         i->vtnr = (int) u;
577                 else if (streq(name, "Leader"))
578                         i->leader = (pid_t) u;
579
580                 break;
581         }
582
583         case DBUS_TYPE_BOOLEAN: {
584                 dbus_bool_t b;
585
586                 dbus_message_iter_get_basic(iter, &b);
587
588                 if (streq(name, "Remote"))
589                         i->remote = b;
590
591                 break;
592         }
593
594         case DBUS_TYPE_UINT64: {
595                 uint64_t u;
596
597                 dbus_message_iter_get_basic(iter, &u);
598
599                 if (streq(name, "Timestamp"))
600                         i->timestamp = (usec_t) u;
601
602                 break;
603         }
604
605         case DBUS_TYPE_STRUCT: {
606                 DBusMessageIter sub;
607
608                 dbus_message_iter_recurse(iter, &sub);
609
610                 if (dbus_message_iter_get_arg_type(&sub) == DBUS_TYPE_UINT32 && streq(name, "User")) {
611                         uint32_t u;
612
613                         dbus_message_iter_get_basic(&sub, &u);
614                         i->uid = (uid_t) u;
615
616                 } else if (dbus_message_iter_get_arg_type(&sub) == DBUS_TYPE_STRING && streq(name, "Seat")) {
617                         const char *s;
618
619                         dbus_message_iter_get_basic(&sub, &s);
620
621                         if (!isempty(s))
622                                 i->seat = s;
623                 }
624
625                 break;
626         }
627         }
628
629         return 0;
630 }
631
632 static int status_property_user(const char *name, DBusMessageIter *iter, UserStatusInfo *i) {
633         assert(name);
634         assert(iter);
635         assert(i);
636
637         switch (dbus_message_iter_get_arg_type(iter)) {
638
639         case DBUS_TYPE_STRING: {
640                 const char *s;
641
642                 dbus_message_iter_get_basic(iter, &s);
643
644                 if (!isempty(s)) {
645                         if (streq(name, "Name"))
646                                 i->name = s;
647                         else if (streq(name, "Slice"))
648                                 i->slice = s;
649                         else if (streq(name, "State"))
650                                 i->state = s;
651                 }
652                 break;
653         }
654
655         case DBUS_TYPE_UINT32: {
656                 uint32_t u;
657
658                 dbus_message_iter_get_basic(iter, &u);
659
660                 if (streq(name, "UID"))
661                         i->uid = (uid_t) u;
662
663                 break;
664         }
665
666         case DBUS_TYPE_UINT64: {
667                 uint64_t u;
668
669                 dbus_message_iter_get_basic(iter, &u);
670
671                 if (streq(name, "Timestamp"))
672                         i->timestamp = (usec_t) u;
673
674                 break;
675         }
676
677         case DBUS_TYPE_STRUCT: {
678                 DBusMessageIter sub;
679
680                 dbus_message_iter_recurse(iter, &sub);
681
682                 if (dbus_message_iter_get_arg_type(&sub) == DBUS_TYPE_STRING && streq(name, "Display")) {
683                         const char *s;
684
685                         dbus_message_iter_get_basic(&sub, &s);
686
687                         if (!isempty(s))
688                                 i->display = s;
689                 }
690
691                 break;
692         }
693
694         case DBUS_TYPE_ARRAY: {
695
696                 if (dbus_message_iter_get_element_type(iter) == DBUS_TYPE_STRUCT && streq(name, "Sessions")) {
697                         DBusMessageIter sub, sub2;
698
699                         dbus_message_iter_recurse(iter, &sub);
700                         while (dbus_message_iter_get_arg_type(&sub) == DBUS_TYPE_STRUCT) {
701                                 const char *id;
702                                 const char *path;
703
704                                 dbus_message_iter_recurse(&sub, &sub2);
705
706                                 if (bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_STRING, &id, true) >= 0 &&
707                                     bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_OBJECT_PATH, &path, false) >= 0) {
708                                         char **l;
709
710                                         l = strv_append(i->sessions, id);
711                                         if (!l)
712                                                 return -ENOMEM;
713
714                                         strv_free(i->sessions);
715                                         i->sessions = l;
716                                 }
717
718                                 dbus_message_iter_next(&sub);
719                         }
720
721                         return 0;
722                 }
723         }
724         }
725
726         return 0;
727 }
728
729 static int status_property_seat(const char *name, DBusMessageIter *iter, SeatStatusInfo *i) {
730         assert(name);
731         assert(iter);
732         assert(i);
733
734         switch (dbus_message_iter_get_arg_type(iter)) {
735
736         case DBUS_TYPE_STRING: {
737                 const char *s;
738
739                 dbus_message_iter_get_basic(iter, &s);
740
741                 if (!isempty(s)) {
742                         if (streq(name, "Id"))
743                                 i->id = s;
744                 }
745                 break;
746         }
747
748         case DBUS_TYPE_STRUCT: {
749                 DBusMessageIter sub;
750
751                 dbus_message_iter_recurse(iter, &sub);
752
753                 if (dbus_message_iter_get_arg_type(&sub) == DBUS_TYPE_STRING && streq(name, "ActiveSession")) {
754                         const char *s;
755
756                         dbus_message_iter_get_basic(&sub, &s);
757
758                         if (!isempty(s))
759                                 i->active_session = s;
760                 }
761
762                 break;
763         }
764
765         case DBUS_TYPE_ARRAY: {
766
767                 if (dbus_message_iter_get_element_type(iter) == DBUS_TYPE_STRUCT && streq(name, "Sessions")) {
768                         DBusMessageIter sub, sub2;
769
770                         dbus_message_iter_recurse(iter, &sub);
771                         while (dbus_message_iter_get_arg_type(&sub) == DBUS_TYPE_STRUCT) {
772                                 const char *id;
773                                 const char *path;
774
775                                 dbus_message_iter_recurse(&sub, &sub2);
776
777                                 if (bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_STRING, &id, true) >= 0 &&
778                                     bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_OBJECT_PATH, &path, false) >= 0) {
779                                         char **l;
780
781                                         l = strv_append(i->sessions, id);
782                                         if (!l)
783                                                 return -ENOMEM;
784
785                                         strv_free(i->sessions);
786                                         i->sessions = l;
787                                 }
788
789                                 dbus_message_iter_next(&sub);
790                         }
791
792                         return 0;
793                 }
794         }
795         }
796
797         return 0;
798 }
799
800 static int print_property(const char *name, DBusMessageIter *iter) {
801         assert(name);
802         assert(iter);
803
804         if (arg_property && !strv_find(arg_property, name))
805                 return 0;
806
807         switch (dbus_message_iter_get_arg_type(iter)) {
808
809         case DBUS_TYPE_STRUCT: {
810                 DBusMessageIter sub;
811
812                 dbus_message_iter_recurse(iter, &sub);
813
814                 if (dbus_message_iter_get_arg_type(&sub) == DBUS_TYPE_STRING &&
815                     (streq(name, "Display") || streq(name, "ActiveSession"))) {
816                         const char *s;
817
818                         dbus_message_iter_get_basic(&sub, &s);
819
820                         if (arg_all || !isempty(s))
821                                 printf("%s=%s\n", name, s);
822                         return 0;
823                 }
824                 break;
825         }
826
827         case DBUS_TYPE_ARRAY:
828
829                 if (dbus_message_iter_get_element_type(iter) == DBUS_TYPE_STRUCT && streq(name, "Sessions")) {
830                         DBusMessageIter sub, sub2;
831                         bool found = false;
832
833                         dbus_message_iter_recurse(iter, &sub);
834                         while (dbus_message_iter_get_arg_type(&sub) == DBUS_TYPE_STRUCT) {
835                                 const char *id;
836                                 const char *path;
837
838                                 dbus_message_iter_recurse(&sub, &sub2);
839
840                                 if (bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_STRING, &id, true) >= 0 &&
841                                     bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_OBJECT_PATH, &path, false) >= 0) {
842                                         if (found)
843                                                 printf(" %s", id);
844                                         else {
845                                                 printf("%s=%s", name, id);
846                                                 found = true;
847                                         }
848                                 }
849
850                                 dbus_message_iter_next(&sub);
851                         }
852
853                         if (!found && arg_all)
854                                 printf("%s=\n", name);
855                         else if (found)
856                                 printf("\n");
857
858                         return 0;
859                 }
860
861                 break;
862         }
863
864         if (generic_print_property(name, iter, arg_all) > 0)
865                 return 0;
866
867         if (arg_all)
868                 printf("%s=[unprintable]\n", name);
869
870         return 0;
871 }
872
873 static int show_one(const char *verb, DBusConnection *bus, const char *path, bool show_properties, bool *new_line) {
874         _cleanup_dbus_message_unref_ DBusMessage *reply = NULL;
875         const char *interface = "";
876         int r;
877         DBusMessageIter iter, sub, sub2, sub3;
878         SessionStatusInfo session_info = {};
879         UserStatusInfo user_info = {};
880         SeatStatusInfo seat_info = {};
881
882         assert(path);
883         assert(new_line);
884
885         r = bus_method_call_with_reply(
886                         bus,
887                         "org.freedesktop.login1",
888                         path,
889                         "org.freedesktop.DBus.Properties",
890                         "GetAll",
891                         &reply,
892                         NULL,
893                         DBUS_TYPE_STRING, &interface,
894                         DBUS_TYPE_INVALID);
895         if (r < 0)
896                 goto finish;
897
898         if (!dbus_message_iter_init(reply, &iter) ||
899             dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY ||
900             dbus_message_iter_get_element_type(&iter) != DBUS_TYPE_DICT_ENTRY)  {
901                 log_error("Failed to parse reply.");
902                 r = -EIO;
903                 goto finish;
904         }
905
906         dbus_message_iter_recurse(&iter, &sub);
907
908         if (*new_line)
909                 printf("\n");
910
911         *new_line = true;
912
913         while (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_INVALID) {
914                 const char *name;
915
916                 if (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_DICT_ENTRY) {
917                         log_error("Failed to parse reply.");
918                         r = -EIO;
919                         goto finish;
920                 }
921
922                 dbus_message_iter_recurse(&sub, &sub2);
923
924                 if (bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_STRING, &name, true) < 0) {
925                         log_error("Failed to parse reply.");
926                         r = -EIO;
927                         goto finish;
928                 }
929
930                 if (dbus_message_iter_get_arg_type(&sub2) != DBUS_TYPE_VARIANT)  {
931                         log_error("Failed to parse reply.");
932                         r = -EIO;
933                         goto finish;
934                 }
935
936                 dbus_message_iter_recurse(&sub2, &sub3);
937
938                 if (show_properties)
939                         r = print_property(name, &sub3);
940                 else if (strstr(verb, "session"))
941                         r = status_property_session(name, &sub3, &session_info);
942                 else if (strstr(verb, "user"))
943                         r = status_property_user(name, &sub3, &user_info);
944                 else
945                         r = status_property_seat(name, &sub3, &seat_info);
946
947                 if (r < 0) {
948                         log_error("Failed to parse reply.");
949                         goto finish;
950                 }
951
952                 dbus_message_iter_next(&sub);
953         }
954
955         if (!show_properties) {
956                 if (strstr(verb, "session"))
957                         print_session_status_info(bus, &session_info);
958                 else if (strstr(verb, "user"))
959                         print_user_status_info(bus, &user_info);
960                 else
961                         print_seat_status_info(&seat_info);
962         }
963
964         r = 0;
965
966 finish:
967         strv_free(seat_info.sessions);
968         strv_free(user_info.sessions);
969
970         return r;
971 }
972
973 static int show(DBusConnection *bus, char **args, unsigned n) {
974         _cleanup_dbus_message_unref_ DBusMessage *reply = NULL;
975         int r, ret = 0;
976         DBusError error;
977         unsigned i;
978         bool show_properties, new_line = false;
979
980         assert(bus);
981         assert(args);
982
983         dbus_error_init(&error);
984
985         show_properties = !strstr(args[0], "status");
986
987         pager_open_if_enabled();
988
989         if (show_properties && n <= 1) {
990                 /* If not argument is specified inspect the manager
991                  * itself */
992
993                 ret = show_one(args[0], bus, "/org/freedesktop/login1", show_properties, &new_line);
994                 goto finish;
995         }
996
997         for (i = 1; i < n; i++) {
998                 const char *path = NULL;
999
1000                 if (strstr(args[0], "session")) {
1001
1002                         ret = bus_method_call_with_reply (
1003                                         bus,
1004                                         "org.freedesktop.login1",
1005                                         "/org/freedesktop/login1",
1006                                         "org.freedesktop.login1.Manager",
1007                                         "GetSession",
1008                                         &reply,
1009                                         NULL,
1010                                         DBUS_TYPE_STRING, &args[i],
1011                                         DBUS_TYPE_INVALID);
1012
1013                 } else if (strstr(args[0], "user")) {
1014                         uid_t uid;
1015                         uint32_t u;
1016
1017                         ret = get_user_creds((const char**) (args+i), &uid, NULL, NULL, NULL);
1018                         if (ret < 0) {
1019                                 log_error("User %s unknown.", args[i]);
1020                                 goto finish;
1021                         }
1022
1023                         u = (uint32_t) uid;
1024                         ret = bus_method_call_with_reply(
1025                                         bus,
1026                                         "org.freedesktop.login1",
1027                                         "/org/freedesktop/login1",
1028                                         "org.freedesktop.login1.Manager",
1029                                         "GetUser",
1030                                         &reply,
1031                                         NULL,
1032                                         DBUS_TYPE_UINT32, &u,
1033                                         DBUS_TYPE_INVALID);
1034
1035                 } else {
1036
1037                         ret = bus_method_call_with_reply(
1038                                         bus,
1039                                         "org.freedesktop.login1",
1040                                         "/org/freedesktop/login1",
1041                                         "org.freedesktop.login1.Manager",
1042                                         "GetSeat",
1043                                         &reply,
1044                                         NULL,
1045                                         DBUS_TYPE_STRING, &args[i],
1046                                         DBUS_TYPE_INVALID);
1047
1048                 }
1049
1050                 if (ret < 0)
1051                         goto finish;
1052
1053                 if (!dbus_message_get_args(reply, &error,
1054                                            DBUS_TYPE_OBJECT_PATH, &path,
1055                                            DBUS_TYPE_INVALID)) {
1056                         log_error("Failed to parse reply: %s", bus_error_message(&error));
1057                         ret = -EIO;
1058                         goto finish;
1059                 }
1060
1061                 r = show_one(args[0], bus, path, show_properties, &new_line);
1062                 if (r != 0)
1063                         ret = r;
1064         }
1065
1066 finish:
1067         dbus_error_free(&error);
1068
1069         return ret;
1070 }
1071
1072 static int activate(DBusConnection *bus, char **args, unsigned n) {
1073         int ret = 0;
1074         unsigned i;
1075
1076         assert(args);
1077
1078         for (i = 1; i < n; i++) {
1079
1080                 ret = bus_method_call_with_reply (
1081                                 bus,
1082                                 "org.freedesktop.login1",
1083                                 "/org/freedesktop/login1",
1084                                 "org.freedesktop.login1.Manager",
1085                                 streq(args[0], "lock-session")      ? "LockSession" :
1086                                 streq(args[0], "unlock-session")    ? "UnlockSession" :
1087                                 streq(args[0], "terminate-session") ? "TerminateSession" :
1088                                                                       "ActivateSession",
1089                                 NULL,
1090                                 NULL,
1091                                 DBUS_TYPE_STRING, &args[i],
1092                                 DBUS_TYPE_INVALID);
1093                 if (ret)
1094                         goto finish;
1095         }
1096
1097 finish:
1098         return ret;
1099 }
1100
1101 static int kill_session(DBusConnection *bus, char **args, unsigned n) {
1102         unsigned i;
1103
1104         assert(args);
1105
1106         if (!arg_kill_who)
1107                 arg_kill_who = "all";
1108
1109         for (i = 1; i < n; i++) {
1110                 int r;
1111
1112                 r = bus_method_call_with_reply (
1113                         bus,
1114                         "org.freedesktop.login1",
1115                         "/org/freedesktop/login1",
1116                         "org.freedesktop.login1.Manager",
1117                         "KillSession",
1118                         NULL,
1119                         NULL,
1120                         DBUS_TYPE_STRING, &args[i],
1121                         DBUS_TYPE_STRING, &arg_kill_who,
1122                         DBUS_TYPE_INT32, &arg_signal,
1123                         DBUS_TYPE_INVALID);
1124                 if (r)
1125                         return r;
1126         }
1127
1128         return 0;
1129 }
1130
1131 static int enable_linger(DBusConnection *bus, char **args, unsigned n) {
1132         unsigned i;
1133         dbus_bool_t b, interactive = true;
1134
1135         assert(args);
1136
1137         polkit_agent_open_if_enabled();
1138
1139         b = streq(args[0], "enable-linger");
1140
1141         for (i = 1; i < n; i++) {
1142                 uint32_t u;
1143                 uid_t uid;
1144                 int r;
1145
1146                 r = get_user_creds((const char**) (args+i), &uid, NULL, NULL, NULL);
1147                 if (r < 0) {
1148                         log_error("Failed to resolve user %s: %s", args[i], strerror(-r));
1149                         return r;
1150                 }
1151
1152                 u = (uint32_t) uid;
1153                 r = bus_method_call_with_reply (
1154                         bus,
1155                         "org.freedesktop.login1",
1156                         "/org/freedesktop/login1",
1157                         "org.freedesktop.login1.Manager",
1158                         "SetUserLinger",
1159                         NULL,
1160                         NULL,
1161                         DBUS_TYPE_UINT32, &u,
1162                         DBUS_TYPE_BOOLEAN, &b,
1163                         DBUS_TYPE_BOOLEAN, &interactive,
1164                         DBUS_TYPE_INVALID);
1165                 if (r)
1166                         return r;
1167         }
1168
1169         return 0;
1170 }
1171
1172 static int terminate_user(DBusConnection *bus, char **args, unsigned n) {
1173         unsigned i;
1174
1175         assert(args);
1176
1177         for (i = 1; i < n; i++) {
1178                 uint32_t u;
1179                 uid_t uid;
1180                 int r;
1181
1182                 r = get_user_creds((const char**) (args+i), &uid, NULL, NULL, NULL);
1183                 if (r < 0) {
1184                         log_error("Failed to look up user %s: %s", args[i], strerror(-r));
1185                         return r;
1186                 }
1187
1188                 u = (uint32_t) uid;
1189                 r = bus_method_call_with_reply (
1190                         bus,
1191                         "org.freedesktop.login1",
1192                         "/org/freedesktop/login1",
1193                         "org.freedesktop.login1.Manager",
1194                         "TerminateUser",
1195                         NULL,
1196                         NULL,
1197                         DBUS_TYPE_UINT32, &u,
1198                         DBUS_TYPE_INVALID);
1199                 if (r)
1200                         return r;
1201         }
1202
1203         return 0;
1204 }
1205
1206 static int kill_user(DBusConnection *bus, char **args, unsigned n) {
1207         unsigned i;
1208
1209         assert(args);
1210
1211         if (!arg_kill_who)
1212                 arg_kill_who = "all";
1213
1214         for (i = 1; i < n; i++) {
1215                 uid_t uid;
1216                 uint32_t u;
1217                 int r;
1218
1219                 r = get_user_creds((const char**) (args+i), &uid, NULL, NULL, NULL);
1220                 if (r < 0) {
1221                         log_error("Failed to look up user %s: %s", args[i], strerror(-r));
1222                         return r;
1223                 }
1224
1225                 u = (uint32_t) uid;
1226                 r = bus_method_call_with_reply (
1227                         bus,
1228                         "org.freedesktop.login1",
1229                         "/org/freedesktop/login1",
1230                         "org.freedesktop.login1.Manager",
1231                         "KillUser",
1232                         NULL,
1233                         NULL,
1234                         DBUS_TYPE_UINT32, &u,
1235                         DBUS_TYPE_INT32, &arg_signal,
1236                         DBUS_TYPE_INVALID);
1237                 if (r)
1238                         return r;
1239         }
1240
1241         return 0;
1242 }
1243
1244 static int attach(DBusConnection *bus, char **args, unsigned n) {
1245         unsigned i;
1246         dbus_bool_t interactive = true;
1247
1248         assert(args);
1249
1250         polkit_agent_open_if_enabled();
1251
1252         for (i = 2; i < n; i++) {
1253                 int r;
1254
1255                 r = bus_method_call_with_reply (
1256                         bus,
1257                         "org.freedesktop.login1",
1258                         "/org/freedesktop/login1",
1259                         "org.freedesktop.login1.Manager",
1260                         "AttachDevice",
1261                         NULL,
1262                         NULL,
1263                         DBUS_TYPE_STRING, &args[1],
1264                         DBUS_TYPE_STRING, &args[i],
1265                         DBUS_TYPE_BOOLEAN, &interactive,
1266                         DBUS_TYPE_INVALID);
1267                 if (r)
1268                         return r;
1269         }
1270
1271         return 0;
1272 }
1273
1274 static int flush_devices(DBusConnection *bus, char **args, unsigned n) {
1275         dbus_bool_t interactive = true;
1276
1277         assert(args);
1278
1279         polkit_agent_open_if_enabled();
1280
1281         return bus_method_call_with_reply (
1282                         bus,
1283                         "org.freedesktop.login1",
1284                         "/org/freedesktop/login1",
1285                         "org.freedesktop.login1.Manager",
1286                         "FlushDevices",
1287                         NULL,
1288                         NULL,
1289                         DBUS_TYPE_BOOLEAN, &interactive,
1290                         DBUS_TYPE_INVALID);
1291 }
1292
1293 static int lock_sessions(DBusConnection *bus, char **args, unsigned n) {
1294         assert(args);
1295
1296         polkit_agent_open_if_enabled();
1297
1298         return bus_method_call_with_reply (
1299                         bus,
1300                         "org.freedesktop.login1",
1301                         "/org/freedesktop/login1",
1302                         "org.freedesktop.login1.Manager",
1303                         streq(args[0], "lock-sessions") ? "LockSessions" : "UnlockSessions",
1304                         NULL,
1305                         NULL,
1306                         DBUS_TYPE_INVALID);
1307 }
1308
1309 static int terminate_seat(DBusConnection *bus, char **args, unsigned n) {
1310         unsigned i;
1311
1312         assert(args);
1313
1314         for (i = 1; i < n; i++) {
1315                 int r;
1316
1317                 r = bus_method_call_with_reply (
1318                         bus,
1319                         "org.freedesktop.login1",
1320                         "/org/freedesktop/login1",
1321                         "org.freedesktop.login1.Manager",
1322                         "TerminateSeat",
1323                         NULL,
1324                         NULL,
1325                         DBUS_TYPE_STRING, &args[i],
1326                         DBUS_TYPE_INVALID);
1327                 if (r)
1328                         return r;
1329         }
1330
1331         return 0;
1332 }
1333
1334 static int help(void) {
1335
1336         printf("%s [OPTIONS...] {COMMAND} ...\n\n"
1337                "Send control commands to or query the login manager.\n\n"
1338                "  -h --help              Show this help\n"
1339                "     --version           Show package version\n"
1340                "  -p --property=NAME     Show only properties by this name\n"
1341                "  -a --all               Show all properties, including empty ones\n"
1342                "     --kill-who=WHO      Who to send signal to\n"
1343                "  -l --full              Do not ellipsize output\n"
1344                "  -s --signal=SIGNAL     Which signal to send\n"
1345                "     --no-ask-password   Don't prompt for password\n"
1346                "  -H --host=[USER@]HOST  Show information for remote host\n"
1347                "  -P --privileged        Acquire privileges before execution\n"
1348                "     --no-pager          Do not pipe output into a pager\n\n"
1349                "Commands:\n"
1350                "  list-sessions                   List sessions\n"
1351                "  session-status [ID...]          Show session status\n"
1352                "  show-session [ID...]            Show properties of one or more sessions\n"
1353                "  activate [ID]                   Activate a session\n"
1354                "  lock-session [ID...]            Screen lock one or more sessions\n"
1355                "  unlock-session [ID...]          Screen unlock one or more sessions\n"
1356                "  lock-sessions                   Screen lock all current sessions\n"
1357                "  unlock-sessions                 Screen unlock all current sessions\n"
1358                "  terminate-session [ID...]       Terminate one or more sessions\n"
1359                "  kill-session [ID...]            Send signal to processes of a session\n"
1360                "  list-users                      List users\n"
1361                "  user-status [USER...]           Show user status\n"
1362                "  show-user [USER...]             Show properties of one or more users\n"
1363                "  enable-linger [USER...]         Enable linger state of one or more users\n"
1364                "  disable-linger [USER...]        Disable linger state of one or more users\n"
1365                "  terminate-user [USER...]        Terminate all sessions of one or more users\n"
1366                "  kill-user [USER...]             Send signal to processes of a user\n"
1367                "  list-seats                      List seats\n"
1368                "  seat-status [NAME...]           Show seat status\n"
1369                "  show-seat [NAME...]             Show properties of one or more seats\n"
1370                "  attach [NAME] [DEVICE...]       Attach one or more devices to a seat\n"
1371                "  flush-devices                   Flush all device associations\n"
1372                "  terminate-seat [NAME...]        Terminate all sessions on one or more seats\n",
1373                program_invocation_short_name);
1374
1375         return 0;
1376 }
1377
1378 static int parse_argv(int argc, char *argv[]) {
1379
1380         enum {
1381                 ARG_VERSION = 0x100,
1382                 ARG_NO_PAGER,
1383                 ARG_KILL_WHO,
1384                 ARG_NO_ASK_PASSWORD,
1385         };
1386
1387         static const struct option options[] = {
1388                 { "help",            no_argument,       NULL, 'h'                 },
1389                 { "version",         no_argument,       NULL, ARG_VERSION         },
1390                 { "property",        required_argument, NULL, 'p'                 },
1391                 { "all",             no_argument,       NULL, 'a'                 },
1392                 { "full",            no_argument,       NULL, 'l'                 },
1393                 { "no-pager",        no_argument,       NULL, ARG_NO_PAGER        },
1394                 { "kill-who",        required_argument, NULL, ARG_KILL_WHO        },
1395                 { "signal",          required_argument, NULL, 's'                 },
1396                 { "host",            required_argument, NULL, 'H'                 },
1397                 { "privileged",      no_argument,       NULL, 'P'                 },
1398                 { "no-ask-password", no_argument,       NULL, ARG_NO_ASK_PASSWORD },
1399                 { NULL,              0,                 NULL, 0                   }
1400         };
1401
1402         int c;
1403
1404         assert(argc >= 0);
1405         assert(argv);
1406
1407         while ((c = getopt_long(argc, argv, "hp:als:H:P", options, NULL)) >= 0) {
1408
1409                 switch (c) {
1410
1411                 case 'h':
1412                         help();
1413                         return 0;
1414
1415                 case ARG_VERSION:
1416                         puts(PACKAGE_STRING);
1417                         puts(SYSTEMD_FEATURES);
1418                         return 0;
1419
1420                 case 'p': {
1421                         char **l;
1422
1423                         l = strv_append(arg_property, optarg);
1424                         if (!l)
1425                                 return -ENOMEM;
1426
1427                         strv_free(arg_property);
1428                         arg_property = l;
1429
1430                         /* If the user asked for a particular
1431                          * property, show it to him, even if it is
1432                          * empty. */
1433                         arg_all = true;
1434                         break;
1435                 }
1436
1437                 case 'a':
1438                         arg_all = true;
1439                         break;
1440
1441                 case 'l':
1442                         arg_full = true;
1443                         break;
1444
1445                 case ARG_NO_PAGER:
1446                         arg_no_pager = true;
1447                         break;
1448
1449                 case ARG_NO_ASK_PASSWORD:
1450                         arg_ask_password = false;
1451                         break;
1452
1453                 case ARG_KILL_WHO:
1454                         arg_kill_who = optarg;
1455                         break;
1456
1457                 case 's':
1458                         arg_signal = signal_from_string_try_harder(optarg);
1459                         if (arg_signal < 0) {
1460                                 log_error("Failed to parse signal string %s.", optarg);
1461                                 return -EINVAL;
1462                         }
1463                         break;
1464
1465                 case 'P':
1466                         arg_transport = TRANSPORT_POLKIT;
1467                         break;
1468
1469                 case 'H':
1470                         arg_transport = TRANSPORT_SSH;
1471                         parse_user_at_host(optarg, &arg_user, &arg_host);
1472                         break;
1473
1474                 case '?':
1475                         return -EINVAL;
1476
1477                 default:
1478                         log_error("Unknown option code %c", c);
1479                         return -EINVAL;
1480                 }
1481         }
1482
1483         return 1;
1484 }
1485
1486 static int loginctl_main(DBusConnection *bus, int argc, char *argv[], DBusError *error) {
1487
1488         static const struct {
1489                 const char* verb;
1490                 const enum {
1491                         MORE,
1492                         LESS,
1493                         EQUAL
1494                 } argc_cmp;
1495                 const int argc;
1496                 int (* const dispatch)(DBusConnection *bus, char **args, unsigned n);
1497         } verbs[] = {
1498                 { "list-sessions",         LESS,   1, list_sessions     },
1499                 { "session-status",        MORE,   2, show              },
1500                 { "show-session",          MORE,   1, show              },
1501                 { "activate",              EQUAL,  2, activate          },
1502                 { "lock-session",          MORE,   2, activate          },
1503                 { "unlock-session",        MORE,   2, activate          },
1504                 { "lock-sessions",         EQUAL,  1, lock_sessions     },
1505                 { "unlock-sessions",       EQUAL,  1, lock_sessions     },
1506                 { "terminate-session",     MORE,   2, activate          },
1507                 { "kill-session",          MORE,   2, kill_session      },
1508                 { "list-users",            EQUAL,  1, list_users        },
1509                 { "user-status",           MORE,   2, show              },
1510                 { "show-user",             MORE,   1, show              },
1511                 { "enable-linger",         MORE,   2, enable_linger     },
1512                 { "disable-linger",        MORE,   2, enable_linger     },
1513                 { "terminate-user",        MORE,   2, terminate_user    },
1514                 { "kill-user",             MORE,   2, kill_user         },
1515                 { "list-seats",            EQUAL,  1, list_seats        },
1516                 { "seat-status",           MORE,   2, show              },
1517                 { "show-seat",             MORE,   1, show              },
1518                 { "attach",                MORE,   3, attach            },
1519                 { "flush-devices",         EQUAL,  1, flush_devices     },
1520                 { "terminate-seat",        MORE,   2, terminate_seat    },
1521         };
1522
1523         int left;
1524         unsigned i;
1525
1526         assert(argc >= 0);
1527         assert(argv);
1528         assert(error);
1529
1530         left = argc - optind;
1531
1532         if (left <= 0)
1533                 /* Special rule: no arguments means "list-sessions" */
1534                 i = 0;
1535         else {
1536                 if (streq(argv[optind], "help")) {
1537                         help();
1538                         return 0;
1539                 }
1540
1541                 for (i = 0; i < ELEMENTSOF(verbs); i++)
1542                         if (streq(argv[optind], verbs[i].verb))
1543                                 break;
1544
1545                 if (i >= ELEMENTSOF(verbs)) {
1546                         log_error("Unknown operation %s", argv[optind]);
1547                         return -EINVAL;
1548                 }
1549         }
1550
1551         switch (verbs[i].argc_cmp) {
1552
1553         case EQUAL:
1554                 if (left != verbs[i].argc) {
1555                         log_error("Invalid number of arguments.");
1556                         return -EINVAL;
1557                 }
1558
1559                 break;
1560
1561         case MORE:
1562                 if (left < verbs[i].argc) {
1563                         log_error("Too few arguments.");
1564                         return -EINVAL;
1565                 }
1566
1567                 break;
1568
1569         case LESS:
1570                 if (left > verbs[i].argc) {
1571                         log_error("Too many arguments.");
1572                         return -EINVAL;
1573                 }
1574
1575                 break;
1576
1577         default:
1578                 assert_not_reached("Unknown comparison operator.");
1579         }
1580
1581         if (!bus) {
1582                 log_error("Failed to get D-Bus connection: %s", error->message);
1583                 return -EIO;
1584         }
1585
1586         return verbs[i].dispatch(bus, argv + optind, left);
1587 }
1588
1589 int main(int argc, char*argv[]) {
1590         int r, retval = EXIT_FAILURE;
1591         DBusConnection *bus = NULL;
1592         DBusError error;
1593
1594         dbus_error_init(&error);
1595
1596         setlocale(LC_ALL, "");
1597         log_parse_environment();
1598         log_open();
1599
1600         r = parse_argv(argc, argv);
1601         if (r < 0)
1602                 goto finish;
1603         else if (r == 0) {
1604                 retval = EXIT_SUCCESS;
1605                 goto finish;
1606         }
1607
1608         if (arg_transport == TRANSPORT_NORMAL)
1609                 bus = dbus_bus_get_private(DBUS_BUS_SYSTEM, &error);
1610         else if (arg_transport == TRANSPORT_POLKIT)
1611                 bus_connect_system_polkit(&bus, &error);
1612         else if (arg_transport == TRANSPORT_SSH)
1613                 bus_connect_system_ssh(NULL, arg_host, &bus, &error);
1614         else
1615                 assert_not_reached("Uh, invalid transport...");
1616
1617         r = loginctl_main(bus, argc, argv, &error);
1618         retval = r < 0 ? EXIT_FAILURE : r;
1619
1620 finish:
1621         if (bus) {
1622                 dbus_connection_flush(bus);
1623                 dbus_connection_close(bus);
1624                 dbus_connection_unref(bus);
1625         }
1626
1627         dbus_error_free(&error);
1628         dbus_shutdown();
1629
1630         strv_free(arg_property);
1631
1632         pager_close();
1633
1634         return retval;
1635 }