chiark / gitweb /
Use initalization instead of explicit zeroing
[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, place
444                  * there to show up in utmp. We rearrange things and
445                  * don't pretend that an X display was a tty */
446
447                 if (isempty(display))
448                         display = tty;
449                 tty = "";
450         } else if (streq(tty, "cron")) {
451                 /* cron has been setting PAM_TTY to "cron" for a very
452                  * long time and it probably shouldn't stop doing that
453                  * for compatibility reasons. */
454                 tty = "";
455                 type = "unspecified";
456         } else if (streq(tty, "ssh")) {
457                 /* ssh has been setting PAM_TTY to "ssh" for a very
458                  * long time and probably shouldn't stop doing that
459                  * for compatibility reasons. */
460                 tty = "";
461                 type ="tty";
462         }
463
464         /* If this fails vtnr will be 0, that's intended */
465         if (!isempty(cvtnr))
466                 safe_atou32(cvtnr, &vtnr);
467
468         if (!isempty(display) && vtnr <= 0) {
469                 if (isempty(seat))
470                         get_seat_from_display(display, &seat, &vtnr);
471                 else if (streq(seat, "seat0"))
472                         get_seat_from_display(display, NULL, &vtnr);
473         }
474
475         if (!type)
476                 type = !isempty(display) ? "x11" :
477                         !isempty(tty) ? "tty" : "unspecified";
478
479         class = pam_getenv(handle, "XDG_SESSION_CLASS");
480         if (isempty(class))
481                 class = getenv("XDG_SESSION_CLASS");
482         if (isempty(class))
483                 class = class_pam;
484         if (isempty(class))
485                 class = "user";
486
487         remote = !isempty(remote_host) &&
488                 !streq(remote_host, "localhost") &&
489                 !streq(remote_host, "localhost.localdomain");
490
491         if (!dbus_message_append_args(m,
492                                       DBUS_TYPE_UINT32, &uid,
493                                       DBUS_TYPE_UINT32, &pid,
494                                       DBUS_TYPE_STRING, &service,
495                                       DBUS_TYPE_STRING, &type,
496                                       DBUS_TYPE_STRING, &class,
497                                       DBUS_TYPE_STRING, &seat,
498                                       DBUS_TYPE_UINT32, &vtnr,
499                                       DBUS_TYPE_STRING, &tty,
500                                       DBUS_TYPE_STRING, &display,
501                                       DBUS_TYPE_BOOLEAN, &remote,
502                                       DBUS_TYPE_STRING, &remote_user,
503                                       DBUS_TYPE_STRING, &remote_host,
504                                       DBUS_TYPE_INVALID)) {
505                 pam_syslog(handle, LOG_ERR, "Could not attach parameters to message.");
506                 r = PAM_BUF_ERR;
507                 goto finish;
508         }
509
510         dbus_message_iter_init_append(m, &iter);
511
512         r = bus_append_strv_iter(&iter, controllers);
513         if (r < 0) {
514                 pam_syslog(handle, LOG_ERR, "Could not attach parameter to message.");
515                 r = PAM_BUF_ERR;
516                 goto finish;
517         }
518
519         r = bus_append_strv_iter(&iter, reset_controllers);
520         if (r < 0) {
521                 pam_syslog(handle, LOG_ERR, "Could not attach parameter to message.");
522                 r = PAM_BUF_ERR;
523                 goto finish;
524         }
525
526         kp = kill_processes;
527         if (!dbus_message_iter_append_basic(&iter, DBUS_TYPE_BOOLEAN, &kp)) {
528                 pam_syslog(handle, LOG_ERR, "Could not attach parameter to message.");
529                 r = PAM_BUF_ERR;
530                 goto finish;
531         }
532
533         if (debug)
534                 pam_syslog(handle, LOG_DEBUG, "Asking logind to create session: "
535                            "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",
536                            uid, pid, service, type, class, seat, vtnr, tty, display, yes_no(remote), remote_user, remote_host);
537
538         reply = dbus_connection_send_with_reply_and_block(bus, m, -1, &error);
539         if (!reply) {
540                 pam_syslog(handle, LOG_ERR, "Failed to create session: %s", bus_error_message(&error));
541                 r = PAM_SESSION_ERR;
542                 goto finish;
543         }
544
545         if (!dbus_message_get_args(reply, &error,
546                                    DBUS_TYPE_STRING, &id,
547                                    DBUS_TYPE_OBJECT_PATH, &object_path,
548                                    DBUS_TYPE_STRING, &runtime_path,
549                                    DBUS_TYPE_UNIX_FD, &session_fd,
550                                    DBUS_TYPE_STRING, &seat,
551                                    DBUS_TYPE_UINT32, &vtnr,
552                                    DBUS_TYPE_BOOLEAN, &existing,
553                                    DBUS_TYPE_INVALID)) {
554                 pam_syslog(handle, LOG_ERR, "Failed to parse message: %s", bus_error_message(&error));
555                 r = PAM_SESSION_ERR;
556                 goto finish;
557         }
558
559         if (debug)
560                 pam_syslog(handle, LOG_DEBUG, "Reply from logind: "
561                            "id=%s object_path=%s runtime_path=%s session_fd=%d seat=%s vtnr=%u",
562                            id, object_path, runtime_path, session_fd, seat, vtnr);
563
564         r = pam_misc_setenv(handle, "XDG_SESSION_ID", id, 0);
565         if (r != PAM_SUCCESS) {
566                 pam_syslog(handle, LOG_ERR, "Failed to set session id.");
567                 goto finish;
568         }
569
570         r = pam_misc_setenv(handle, "XDG_RUNTIME_DIR", runtime_path, 0);
571         if (r != PAM_SUCCESS) {
572                 pam_syslog(handle, LOG_ERR, "Failed to set runtime dir.");
573                 goto finish;
574         }
575
576         if (!isempty(seat)) {
577                 r = pam_misc_setenv(handle, "XDG_SEAT", seat, 0);
578                 if (r != PAM_SUCCESS) {
579                         pam_syslog(handle, LOG_ERR, "Failed to set seat.");
580                         goto finish;
581                 }
582         }
583
584         if (vtnr > 0) {
585                 char buf[11];
586                 snprintf(buf, sizeof(buf), "%u", vtnr);
587                 char_array_0(buf);
588
589                 r = pam_misc_setenv(handle, "XDG_VTNR", buf, 0);
590                 if (r != PAM_SUCCESS) {
591                         pam_syslog(handle, LOG_ERR, "Failed to set virtual terminal number.");
592                         goto finish;
593                 }
594         }
595
596         r = pam_set_data(handle, "systemd.existing", INT_TO_PTR(!!existing), NULL);
597         if (r != PAM_SUCCESS) {
598                 pam_syslog(handle, LOG_ERR, "Failed to install existing flag.");
599                 return r;
600         }
601
602         if (session_fd >= 0) {
603                 r = pam_set_data(handle, "systemd.session-fd", INT_TO_PTR(session_fd+1), NULL);
604                 if (r != PAM_SUCCESS) {
605                         pam_syslog(handle, LOG_ERR, "Failed to install session fd.");
606                         return r;
607                 }
608         }
609
610         session_fd = -1;
611
612         r = PAM_SUCCESS;
613
614 finish:
615         strv_free(controllers);
616         strv_free(reset_controllers);
617         strv_free(kill_only_users);
618         strv_free(kill_exclude_users);
619
620         dbus_error_free(&error);
621
622         if (bus) {
623                 dbus_connection_close(bus);
624                 dbus_connection_unref(bus);
625         }
626
627         if (m)
628                 dbus_message_unref(m);
629
630         if (reply)
631                 dbus_message_unref(reply);
632
633         if (session_fd >= 0)
634                 close_nointr_nofail(session_fd);
635
636         return r;
637 }
638
639 _public_ PAM_EXTERN int pam_sm_close_session(
640                 pam_handle_t *handle,
641                 int flags,
642                 int argc, const char **argv) {
643
644         const void *p = NULL, *existing = NULL;
645         const char *id;
646         DBusConnection *bus = NULL;
647         DBusMessage *m = NULL, *reply = NULL;
648         DBusError error;
649         int r;
650
651         assert(handle);
652
653         dbus_error_init(&error);
654
655         /* Only release session if it wasn't pre-existing when we
656          * tried to create it */
657         pam_get_data(handle, "systemd.existing", &existing);
658
659         id = pam_getenv(handle, "XDG_SESSION_ID");
660         if (id && !existing) {
661
662                 /* Before we go and close the FIFO we need to tell
663                  * logind that this is a clean session shutdown, so
664                  * that it doesn't just go and slaughter us
665                  * immediately after closing the fd */
666
667                 bus = dbus_bus_get_private(DBUS_BUS_SYSTEM, &error);
668                 if (!bus) {
669                         pam_syslog(handle, LOG_ERR, "Failed to connect to system bus: %s", bus_error_message(&error));
670                         r = PAM_SESSION_ERR;
671                         goto finish;
672                 }
673
674                 m = dbus_message_new_method_call(
675                                 "org.freedesktop.login1",
676                                 "/org/freedesktop/login1",
677                                 "org.freedesktop.login1.Manager",
678                                 "ReleaseSession");
679                 if (!m) {
680                         pam_syslog(handle, LOG_ERR, "Could not allocate release session message.");
681                         r = PAM_BUF_ERR;
682                         goto finish;
683                 }
684
685                 if (!dbus_message_append_args(m,
686                                               DBUS_TYPE_STRING, &id,
687                                               DBUS_TYPE_INVALID)) {
688                         pam_syslog(handle, LOG_ERR, "Could not attach parameters to message.");
689                         r = PAM_BUF_ERR;
690                         goto finish;
691                 }
692
693                 reply = dbus_connection_send_with_reply_and_block(bus, m, -1, &error);
694                 if (!reply) {
695                         pam_syslog(handle, LOG_ERR, "Failed to release session: %s", bus_error_message(&error));
696                         r = PAM_SESSION_ERR;
697                         goto finish;
698                 }
699         }
700
701         r = PAM_SUCCESS;
702
703 finish:
704         pam_get_data(handle, "systemd.session-fd", &p);
705         if (p)
706                 close_nointr(PTR_TO_INT(p) - 1);
707
708         dbus_error_free(&error);
709
710         if (bus) {
711                 dbus_connection_close(bus);
712                 dbus_connection_unref(bus);
713         }
714
715         if (m)
716                 dbus_message_unref(m);
717
718         if (reply)
719                 dbus_message_unref(reply);
720
721         return r;
722 }