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