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