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