chiark / gitweb /
logind: avoid creating stale session state files
[elogind.git] / src / login / pam-module.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 #include <fcntl.h>
24 #include <sys/file.h>
25 #include <pwd.h>
26 #include <endian.h>
27 #include <sys/capability.h>
28
29 #include <security/pam_modules.h>
30 #include <security/_pam_macros.h>
31 #include <security/pam_modutil.h>
32 #include <security/pam_ext.h>
33 #include <security/pam_misc.h>
34
35 #include "util.h"
36 #include "audit.h"
37 #include "macro.h"
38 #include "strv.h"
39 #include "dbus-common.h"
40 #include "def.h"
41 #include "socket-util.h"
42 #include "fileio.h"
43
44 static int parse_argv(pam_handle_t *handle,
45                       int argc, const char **argv,
46                       char ***controllers,
47                       char ***reset_controllers,
48                       bool *kill_processes,
49                       char ***kill_only_users,
50                       char ***kill_exclude_users,
51                       const char **class,
52                       bool *debug) {
53
54         unsigned i;
55
56         assert(argc >= 0);
57         assert(argc == 0 || argv);
58
59         for (i = 0; i < (unsigned) argc; i++) {
60                 int k;
61
62                 if (startswith(argv[i], "kill-session-processes=")) {
63                         if ((k = parse_boolean(argv[i] + 23)) < 0) {
64                                 pam_syslog(handle, LOG_ERR, "Failed to parse kill-session-processes= argument.");
65                                 return k;
66                         }
67
68                         if (kill_processes)
69                                 *kill_processes = k;
70
71                 } else if (startswith(argv[i], "kill-session=")) {
72                         /* As compatibility for old versions */
73
74                         if ((k = parse_boolean(argv[i] + 13)) < 0) {
75                                 pam_syslog(handle, LOG_ERR, "Failed to parse kill-session= argument.");
76                                 return k;
77                         }
78
79                         if (kill_processes)
80                                 *kill_processes = k;
81
82                 } else if (startswith(argv[i], "controllers=")) {
83
84                         if (controllers) {
85                                 char **l;
86
87                                 if (!(l = strv_split(argv[i] + 12, ","))) {
88                                         pam_syslog(handle, LOG_ERR, "Out of memory.");
89                                         return -ENOMEM;
90                                 }
91
92                                 strv_free(*controllers);
93                                 *controllers = l;
94                         }
95
96                 } else if (startswith(argv[i], "reset-controllers=")) {
97
98                         if (reset_controllers) {
99                                 char **l;
100
101                                 if (!(l = strv_split(argv[i] + 18, ","))) {
102                                         pam_syslog(handle, LOG_ERR, "Out of memory.");
103                                         return -ENOMEM;
104                                 }
105
106                                 strv_free(*reset_controllers);
107                                 *reset_controllers = l;
108                         }
109
110                 } else if (startswith(argv[i], "kill-only-users=")) {
111
112                         if (kill_only_users) {
113                                 char **l;
114
115                                 if (!(l = strv_split(argv[i] + 16, ","))) {
116                                         pam_syslog(handle, LOG_ERR, "Out of memory.");
117                                         return -ENOMEM;
118                                 }
119
120                                 strv_free(*kill_only_users);
121                                 *kill_only_users = l;
122                         }
123
124                 } else if (startswith(argv[i], "kill-exclude-users=")) {
125
126                         if (kill_exclude_users) {
127                                 char **l;
128
129                                 if (!(l = strv_split(argv[i] + 19, ","))) {
130                                         pam_syslog(handle, LOG_ERR, "Out of memory.");
131                                         return -ENOMEM;
132                                 }
133
134                                 strv_free(*kill_exclude_users);
135                                 *kill_exclude_users = l;
136                         }
137
138                 } else if (startswith(argv[i], "class=")) {
139
140                         if (class)
141                                 *class = argv[i] + 6;
142
143                 } else if (startswith(argv[i], "debug=")) {
144                         if ((k = parse_boolean(argv[i] + 6)) < 0) {
145                                 pam_syslog(handle, LOG_ERR, "Failed to parse debug= argument.");
146                                 return k;
147                         }
148
149                         if (debug)
150                                 *debug = k;
151
152                 } else if (startswith(argv[i], "create-session=") ||
153                            startswith(argv[i], "kill-user=")) {
154
155                         pam_syslog(handle, LOG_WARNING, "Option %s not supported anymore, ignoring.", argv[i]);
156
157                 } else {
158                         pam_syslog(handle, LOG_ERR, "Unknown parameter '%s'.", argv[i]);
159                         return -EINVAL;
160                 }
161         }
162
163         return 0;
164 }
165
166 static int get_user_data(
167                 pam_handle_t *handle,
168                 const char **ret_username,
169                 struct passwd **ret_pw) {
170
171         const char *username = NULL;
172         struct passwd *pw = NULL;
173         uid_t uid;
174         int r;
175
176         assert(handle);
177         assert(ret_username);
178         assert(ret_pw);
179
180         r = audit_loginuid_from_pid(0, &uid);
181         if (r >= 0)
182                 pw = pam_modutil_getpwuid(handle, uid);
183         else {
184                 r = pam_get_user(handle, &username, NULL);
185                 if (r != PAM_SUCCESS) {
186                         pam_syslog(handle, LOG_ERR, "Failed to get user name.");
187                         return r;
188                 }
189
190                 if (isempty(username)) {
191                         pam_syslog(handle, LOG_ERR, "User name not valid.");
192                         return PAM_AUTH_ERR;
193                 }
194
195                 pw = pam_modutil_getpwnam(handle, username);
196         }
197
198         if (!pw) {
199                 pam_syslog(handle, LOG_ERR, "Failed to get user data.");
200                 return PAM_USER_UNKNOWN;
201         }
202
203         *ret_pw = pw;
204         *ret_username = username ? username : pw->pw_name;
205
206         return PAM_SUCCESS;
207 }
208
209 static bool check_user_lists(
210                 pam_handle_t *handle,
211                 uid_t uid,
212                 char **kill_only_users,
213                 char **kill_exclude_users) {
214
215         const char *name = NULL;
216         char **l;
217
218         assert(handle);
219
220         if (uid == 0)
221                 name = "root"; /* Avoid obvious NSS requests, to suppress network traffic */
222         else {
223                 struct passwd *pw;
224
225                 pw = pam_modutil_getpwuid(handle, uid);
226                 if (pw)
227                         name = pw->pw_name;
228         }
229
230         STRV_FOREACH(l, kill_exclude_users) {
231                 uid_t u;
232
233                 if (parse_uid(*l, &u) >= 0)
234                         if (u == uid)
235                                 return false;
236
237                 if (name && streq(name, *l))
238                         return false;
239         }
240
241         if (strv_isempty(kill_only_users))
242                 return true;
243
244         STRV_FOREACH(l, kill_only_users) {
245                 uid_t u;
246
247                 if (parse_uid(*l, &u) >= 0)
248                         if (u == uid)
249                                 return true;
250
251                 if (name && streq(name, *l))
252                         return true;
253         }
254
255         return false;
256 }
257
258 static int get_seat_from_display(const char *display, const char **seat, uint32_t *vtnr) {
259         char _cleanup_free_ *p = NULL;
260         int r;
261         int _cleanup_close_ fd = -1;
262         union sockaddr_union sa = {
263                 .un.sun_family = AF_UNIX,
264         };
265         struct ucred ucred;
266         socklen_t l;
267         char _cleanup_free_ *tty = NULL;
268         int v;
269
270         assert(display);
271         assert(vtnr);
272
273         /* We deduce the X11 socket from the display name, then use
274          * SO_PEERCRED to determine the X11 server process, ask for
275          * the controlling tty of that and if it's a VC then we know
276          * the seat and the virtual terminal. Sounds ugly, is only
277          * semi-ugly. */
278
279         r = socket_from_display(display, &p);
280         if (r < 0)
281                 return r;
282         strncpy(sa.un.sun_path, p, sizeof(sa.un.sun_path)-1);
283
284         fd = socket(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC, 0);
285         if (fd < 0)
286                 return -errno;
287
288         if (connect(fd, &sa.sa, offsetof(struct sockaddr_un, sun_path) + strlen(sa.un.sun_path)) < 0)
289                 return -errno;
290
291         l = sizeof(ucred);
292         r = getsockopt(fd, SOL_SOCKET, SO_PEERCRED, &ucred, &l);
293         if (r < 0)
294                 return -errno;
295
296         r = get_ctty(ucred.pid, NULL, &tty);
297         if (r < 0)
298                 return r;
299
300         v = vtnr_from_tty(tty);
301         if (v < 0)
302                 return v;
303         else if (v == 0)
304                 return -ENOENT;
305
306         if (seat)
307                 *seat = "seat0";
308         *vtnr = (uint32_t) v;
309
310         return 0;
311 }
312
313 _public_ PAM_EXTERN int pam_sm_open_session(
314                 pam_handle_t *handle,
315                 int flags,
316                 int argc, const char **argv) {
317
318         struct passwd *pw;
319         bool kill_processes = false, debug = false;
320         const char *username, *id, *object_path, *runtime_path, *service = NULL, *tty = NULL, *display = NULL, *remote_user = NULL, *remote_host = NULL, *seat = NULL, *type = NULL, *class = NULL, *class_pam = NULL, *cvtnr = NULL;
321         char **controllers = NULL, **reset_controllers = NULL, **kill_only_users = NULL, **kill_exclude_users = NULL;
322         DBusError error;
323         uint32_t uid, pid;
324         DBusMessageIter iter;
325         dbus_bool_t kp;
326         int session_fd = -1;
327         DBusConnection *bus = NULL;
328         DBusMessage *m = NULL, *reply = NULL;
329         dbus_bool_t remote, existing;
330         int r;
331         uint32_t vtnr = 0;
332
333         assert(handle);
334
335         dbus_error_init(&error);
336
337         /* pam_syslog(handle, LOG_INFO, "pam-systemd initializing"); */
338
339         /* Make this a NOP on non-logind systems */
340         if (!logind_running())
341                 return PAM_SUCCESS;
342
343         if (parse_argv(handle,
344                        argc, argv,
345                        &controllers, &reset_controllers,
346                        &kill_processes, &kill_only_users, &kill_exclude_users,
347                        &class_pam, &debug) < 0) {
348                 r = PAM_SESSION_ERR;
349                 goto finish;
350         }
351
352         r = get_user_data(handle, &username, &pw);
353         if (r != PAM_SUCCESS)
354                 goto finish;
355
356         /* Make sure we don't enter a loop by talking to
357          * systemd-logind when it is actually waiting for the
358          * background to finish start-up. If the service is
359          * "systemd-shared" we simply set XDG_RUNTIME_DIR and
360          * leave. */
361
362         pam_get_item(handle, PAM_SERVICE, (const void**) &service);
363         if (streq_ptr(service, "systemd-shared")) {
364                 char *p, *rt = NULL;
365
366                 if (asprintf(&p, "/run/systemd/users/%lu", (unsigned long) pw->pw_uid) < 0) {
367                         r = PAM_BUF_ERR;
368                         goto finish;
369                 }
370
371                 r = parse_env_file(p, NEWLINE,
372                                    "RUNTIME", &rt,
373                                    NULL);
374                 free(p);
375
376                 if (r < 0 && r != -ENOENT) {
377                         r = PAM_SESSION_ERR;
378                         free(rt);
379                         goto finish;
380                 }
381
382                 if (rt)  {
383                         r = pam_misc_setenv(handle, "XDG_RUNTIME_DIR", rt, 0);
384                         free(rt);
385
386                         if (r != PAM_SUCCESS) {
387                                 pam_syslog(handle, LOG_ERR, "Failed to set runtime dir.");
388                                 goto finish;
389                         }
390                 }
391
392                 r = PAM_SUCCESS;
393                 goto finish;
394         }
395
396         if (kill_processes)
397                 kill_processes = check_user_lists(handle, pw->pw_uid, kill_only_users, kill_exclude_users);
398
399         dbus_connection_set_change_sigpipe(FALSE);
400
401         bus = dbus_bus_get_private(DBUS_BUS_SYSTEM, &error);
402         if (!bus) {
403                 pam_syslog(handle, LOG_ERR, "Failed to connect to system bus: %s", bus_error_message(&error));
404                 r = PAM_SESSION_ERR;
405                 goto finish;
406         }
407
408         m = dbus_message_new_method_call(
409                         "org.freedesktop.login1",
410                         "/org/freedesktop/login1",
411                         "org.freedesktop.login1.Manager",
412                         "CreateSession");
413         if (!m) {
414                 pam_syslog(handle, LOG_ERR, "Could not allocate create session message.");
415                 r = PAM_BUF_ERR;
416                 goto finish;
417         }
418
419         uid = pw->pw_uid;
420         pid = getpid();
421
422         pam_get_item(handle, PAM_XDISPLAY, (const void**) &display);
423         pam_get_item(handle, PAM_TTY, (const void**) &tty);
424         pam_get_item(handle, PAM_RUSER, (const void**) &remote_user);
425         pam_get_item(handle, PAM_RHOST, (const void**) &remote_host);
426
427         seat = pam_getenv(handle, "XDG_SEAT");
428         if (isempty(seat))
429                 seat = getenv("XDG_SEAT");
430
431         cvtnr = pam_getenv(handle, "XDG_VTNR");
432         if (isempty(cvtnr))
433                 cvtnr = getenv("XDG_VTNR");
434
435         service = strempty(service);
436         tty = strempty(tty);
437         display = strempty(display);
438         remote_user = strempty(remote_user);
439         remote_host = strempty(remote_host);
440         seat = strempty(seat);
441
442         if (strchr(tty, ':')) {
443                 /* A tty with a colon is usually an X11 display,
444                  * placed there to show up in utmp. We rearrange
445                  * things and don't pretend that an X display was a
446                  * tty. */
447
448                 if (isempty(display))
449                         display = tty;
450                 tty = "";
451         } else if (streq(tty, "cron")) {
452                 /* cron has been setting PAM_TTY to "cron" for a very
453                  * long time and it probably shouldn't stop doing that
454                  * for compatibility reasons. */
455                 tty = "";
456                 type = "unspecified";
457         } else if (streq(tty, "ssh")) {
458                 /* ssh has been setting PAM_TTY to "ssh" for a very
459                  * long time and probably shouldn't stop doing that
460                  * for compatibility reasons. */
461                 tty = "";
462                 type ="tty";
463         }
464
465         /* If this fails vtnr will be 0, that's intended */
466         if (!isempty(cvtnr))
467                 safe_atou32(cvtnr, &vtnr);
468
469         if (!isempty(display) && vtnr <= 0) {
470                 if (isempty(seat))
471                         get_seat_from_display(display, &seat, &vtnr);
472                 else if (streq(seat, "seat0"))
473                         get_seat_from_display(display, NULL, &vtnr);
474         }
475
476         if (!type)
477                 type = !isempty(display) ? "x11" :
478                         !isempty(tty) ? "tty" : "unspecified";
479
480         class = pam_getenv(handle, "XDG_SESSION_CLASS");
481         if (isempty(class))
482                 class = getenv("XDG_SESSION_CLASS");
483         if (isempty(class))
484                 class = class_pam;
485         if (isempty(class))
486                 class = streq(type, "unspecified") ? "background" : "user";
487
488         remote = !isempty(remote_host) &&
489                 !streq(remote_host, "localhost") &&
490                 !streq(remote_host, "localhost.localdomain");
491
492         if (!dbus_message_append_args(m,
493                                       DBUS_TYPE_UINT32, &uid,
494                                       DBUS_TYPE_UINT32, &pid,
495                                       DBUS_TYPE_STRING, &service,
496                                       DBUS_TYPE_STRING, &type,
497                                       DBUS_TYPE_STRING, &class,
498                                       DBUS_TYPE_STRING, &seat,
499                                       DBUS_TYPE_UINT32, &vtnr,
500                                       DBUS_TYPE_STRING, &tty,
501                                       DBUS_TYPE_STRING, &display,
502                                       DBUS_TYPE_BOOLEAN, &remote,
503                                       DBUS_TYPE_STRING, &remote_user,
504                                       DBUS_TYPE_STRING, &remote_host,
505                                       DBUS_TYPE_INVALID)) {
506                 pam_syslog(handle, LOG_ERR, "Could not attach parameters to message.");
507                 r = PAM_BUF_ERR;
508                 goto finish;
509         }
510
511         dbus_message_iter_init_append(m, &iter);
512
513         r = bus_append_strv_iter(&iter, controllers);
514         if (r < 0) {
515                 pam_syslog(handle, LOG_ERR, "Could not attach parameter to message.");
516                 r = PAM_BUF_ERR;
517                 goto finish;
518         }
519
520         r = bus_append_strv_iter(&iter, reset_controllers);
521         if (r < 0) {
522                 pam_syslog(handle, LOG_ERR, "Could not attach parameter to message.");
523                 r = PAM_BUF_ERR;
524                 goto finish;
525         }
526
527         kp = kill_processes;
528         if (!dbus_message_iter_append_basic(&iter, DBUS_TYPE_BOOLEAN, &kp)) {
529                 pam_syslog(handle, LOG_ERR, "Could not attach parameter to message.");
530                 r = PAM_BUF_ERR;
531                 goto finish;
532         }
533
534         if (debug)
535                 pam_syslog(handle, LOG_DEBUG, "Asking logind to create session: "
536                            "uid=%u pid=%u service=%s type=%s class=%s seat=%s vtnr=%u tty=%s display=%s remote=%s remote_user=%s remote_host=%s",
537                            uid, pid, service, type, class, seat, vtnr, tty, display, yes_no(remote), remote_user, remote_host);
538
539         reply = dbus_connection_send_with_reply_and_block(bus, m, -1, &error);
540         if (!reply) {
541                 pam_syslog(handle, LOG_ERR, "Failed to create session: %s", bus_error_message(&error));
542                 r = PAM_SESSION_ERR;
543                 goto finish;
544         }
545
546         if (!dbus_message_get_args(reply, &error,
547                                    DBUS_TYPE_STRING, &id,
548                                    DBUS_TYPE_OBJECT_PATH, &object_path,
549                                    DBUS_TYPE_STRING, &runtime_path,
550                                    DBUS_TYPE_UNIX_FD, &session_fd,
551                                    DBUS_TYPE_STRING, &seat,
552                                    DBUS_TYPE_UINT32, &vtnr,
553                                    DBUS_TYPE_BOOLEAN, &existing,
554                                    DBUS_TYPE_INVALID)) {
555                 pam_syslog(handle, LOG_ERR, "Failed to parse message: %s", bus_error_message(&error));
556                 r = PAM_SESSION_ERR;
557                 goto finish;
558         }
559
560         if (debug)
561                 pam_syslog(handle, LOG_DEBUG, "Reply from logind: "
562                            "id=%s object_path=%s runtime_path=%s session_fd=%d seat=%s vtnr=%u",
563                            id, object_path, runtime_path, session_fd, seat, vtnr);
564
565         r = pam_misc_setenv(handle, "XDG_SESSION_ID", id, 0);
566         if (r != PAM_SUCCESS) {
567                 pam_syslog(handle, LOG_ERR, "Failed to set session id.");
568                 goto finish;
569         }
570
571         r = pam_misc_setenv(handle, "XDG_RUNTIME_DIR", runtime_path, 0);
572         if (r != PAM_SUCCESS) {
573                 pam_syslog(handle, LOG_ERR, "Failed to set runtime dir.");
574                 goto finish;
575         }
576
577         if (!isempty(seat)) {
578                 r = pam_misc_setenv(handle, "XDG_SEAT", seat, 0);
579                 if (r != PAM_SUCCESS) {
580                         pam_syslog(handle, LOG_ERR, "Failed to set seat.");
581                         goto finish;
582                 }
583         }
584
585         if (vtnr > 0) {
586                 char buf[11];
587                 snprintf(buf, sizeof(buf), "%u", vtnr);
588                 char_array_0(buf);
589
590                 r = pam_misc_setenv(handle, "XDG_VTNR", buf, 0);
591                 if (r != PAM_SUCCESS) {
592                         pam_syslog(handle, LOG_ERR, "Failed to set virtual terminal number.");
593                         goto finish;
594                 }
595         }
596
597         r = pam_set_data(handle, "systemd.existing", INT_TO_PTR(!!existing), NULL);
598         if (r != PAM_SUCCESS) {
599                 pam_syslog(handle, LOG_ERR, "Failed to install existing flag.");
600                 return r;
601         }
602
603         if (session_fd >= 0) {
604                 r = pam_set_data(handle, "systemd.session-fd", INT_TO_PTR(session_fd+1), NULL);
605                 if (r != PAM_SUCCESS) {
606                         pam_syslog(handle, LOG_ERR, "Failed to install session fd.");
607                         return r;
608                 }
609         }
610
611         session_fd = -1;
612
613         r = PAM_SUCCESS;
614
615 finish:
616         strv_free(controllers);
617         strv_free(reset_controllers);
618         strv_free(kill_only_users);
619         strv_free(kill_exclude_users);
620
621         dbus_error_free(&error);
622
623         if (bus) {
624                 dbus_connection_close(bus);
625                 dbus_connection_unref(bus);
626         }
627
628         if (m)
629                 dbus_message_unref(m);
630
631         if (reply)
632                 dbus_message_unref(reply);
633
634         if (session_fd >= 0)
635                 close_nointr_nofail(session_fd);
636
637         return r;
638 }
639
640 _public_ PAM_EXTERN int pam_sm_close_session(
641                 pam_handle_t *handle,
642                 int flags,
643                 int argc, const char **argv) {
644
645         const void *p = NULL, *existing = NULL;
646         const char *id;
647         DBusConnection *bus = NULL;
648         DBusMessage *m = NULL, *reply = NULL;
649         DBusError error;
650         int r;
651
652         assert(handle);
653
654         dbus_error_init(&error);
655
656         /* Only release session if it wasn't pre-existing when we
657          * tried to create it */
658         pam_get_data(handle, "systemd.existing", &existing);
659
660         id = pam_getenv(handle, "XDG_SESSION_ID");
661         if (id && !existing) {
662
663                 /* Before we go and close the FIFO we need to tell
664                  * logind that this is a clean session shutdown, so
665                  * that it doesn't just go and slaughter us
666                  * immediately after closing the fd */
667
668                 bus = dbus_bus_get_private(DBUS_BUS_SYSTEM, &error);
669                 if (!bus) {
670                         pam_syslog(handle, LOG_ERR, "Failed to connect to system bus: %s", bus_error_message(&error));
671                         r = PAM_SESSION_ERR;
672                         goto finish;
673                 }
674
675                 m = dbus_message_new_method_call(
676                                 "org.freedesktop.login1",
677                                 "/org/freedesktop/login1",
678                                 "org.freedesktop.login1.Manager",
679                                 "ReleaseSession");
680                 if (!m) {
681                         pam_syslog(handle, LOG_ERR, "Could not allocate release session message.");
682                         r = PAM_BUF_ERR;
683                         goto finish;
684                 }
685
686                 if (!dbus_message_append_args(m,
687                                               DBUS_TYPE_STRING, &id,
688                                               DBUS_TYPE_INVALID)) {
689                         pam_syslog(handle, LOG_ERR, "Could not attach parameters to message.");
690                         r = PAM_BUF_ERR;
691                         goto finish;
692                 }
693
694                 reply = dbus_connection_send_with_reply_and_block(bus, m, -1, &error);
695                 if (!reply) {
696                         pam_syslog(handle, LOG_ERR, "Failed to release session: %s", bus_error_message(&error));
697                         r = PAM_SESSION_ERR;
698                         goto finish;
699                 }
700         }
701
702         r = PAM_SUCCESS;
703
704 finish:
705         pam_get_data(handle, "systemd.session-fd", &p);
706         if (p)
707                 close_nointr(PTR_TO_INT(p) - 1);
708
709         dbus_error_free(&error);
710
711         if (bus) {
712                 dbus_connection_close(bus);
713                 dbus_connection_unref(bus);
714         }
715
716         if (m)
717                 dbus_message_unref(m);
718
719         if (reply)
720                 dbus_message_unref(reply);
721
722         return r;
723 }