chiark / gitweb /
pam: check environ[] for XDG_SEAT as fallback
[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
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_INVALID)) {
547                 pam_syslog(handle, LOG_ERR, "Failed to parse message: %s", bus_error_message(&error));
548                 r = PAM_SESSION_ERR;
549                 goto finish;
550         }
551
552         if (debug)
553                 pam_syslog(handle, LOG_DEBUG, "Reply from logind: "
554                            "id=%s object_path=%s runtime_path=%s session_fd=%d seat=%s vtnr=%u",
555                            id, object_path, runtime_path, session_fd, seat, vtnr);
556
557         r = pam_misc_setenv(handle, "XDG_SESSION_ID", id, 0);
558         if (r != PAM_SUCCESS) {
559                 pam_syslog(handle, LOG_ERR, "Failed to set session id.");
560                 goto finish;
561         }
562
563         r = pam_misc_setenv(handle, "XDG_RUNTIME_DIR", runtime_path, 0);
564         if (r != PAM_SUCCESS) {
565                 pam_syslog(handle, LOG_ERR, "Failed to set runtime dir.");
566                 goto finish;
567         }
568
569         if (!isempty(seat)) {
570                 r = pam_misc_setenv(handle, "XDG_SEAT", seat, 0);
571                 if (r != PAM_SUCCESS) {
572                         pam_syslog(handle, LOG_ERR, "Failed to set seat.");
573                         goto finish;
574                 }
575         }
576
577         if (vtnr > 0) {
578                 char buf[11];
579                 snprintf(buf, sizeof(buf), "%u", vtnr);
580                 char_array_0(buf);
581
582                 r = pam_misc_setenv(handle, "XDG_VTNR", buf, 0);
583                 if (r != PAM_SUCCESS) {
584                         pam_syslog(handle, LOG_ERR, "Failed to set virtual terminal number.");
585                         goto finish;
586                 }
587         }
588
589         if (session_fd >= 0) {
590                 r = pam_set_data(handle, "systemd.session-fd", INT_TO_PTR(session_fd+1), NULL);
591                 if (r != PAM_SUCCESS) {
592                         pam_syslog(handle, LOG_ERR, "Failed to install session fd.");
593                         return r;
594                 }
595         }
596
597         session_fd = -1;
598
599         r = PAM_SUCCESS;
600
601 finish:
602         strv_free(controllers);
603         strv_free(reset_controllers);
604         strv_free(kill_only_users);
605         strv_free(kill_exclude_users);
606
607         dbus_error_free(&error);
608
609         if (bus) {
610                 dbus_connection_close(bus);
611                 dbus_connection_unref(bus);
612         }
613
614         if (m)
615                 dbus_message_unref(m);
616
617         if (reply)
618                 dbus_message_unref(reply);
619
620         if (session_fd >= 0)
621                 close_nointr_nofail(session_fd);
622
623         return r;
624 }
625
626 _public_ PAM_EXTERN int pam_sm_close_session(
627                 pam_handle_t *handle,
628                 int flags,
629                 int argc, const char **argv) {
630
631         const void *p = NULL;
632         const char *id;
633         DBusConnection *bus = NULL;
634         DBusMessage *m = NULL, *reply = NULL;
635         DBusError error;
636         int r;
637
638         assert(handle);
639
640         dbus_error_init(&error);
641
642         id = pam_getenv(handle, "XDG_SESSION_ID");
643         if (id) {
644
645                 /* Before we go and close the FIFO we need to tell
646                  * logind that this is a clean session shutdown, so
647                  * that it doesn't just go and slaughter us
648                  * immediately after closing the fd */
649
650                 bus = dbus_bus_get_private(DBUS_BUS_SYSTEM, &error);
651                 if (!bus) {
652                         pam_syslog(handle, LOG_ERR, "Failed to connect to system bus: %s", bus_error_message(&error));
653                         r = PAM_SESSION_ERR;
654                         goto finish;
655                 }
656
657                 m = dbus_message_new_method_call(
658                                 "org.freedesktop.login1",
659                                 "/org/freedesktop/login1",
660                                 "org.freedesktop.login1.Manager",
661                                 "ReleaseSession");
662                 if (!m) {
663                         pam_syslog(handle, LOG_ERR, "Could not allocate release session message.");
664                         r = PAM_BUF_ERR;
665                         goto finish;
666                 }
667
668                 if (!dbus_message_append_args(m,
669                                               DBUS_TYPE_STRING, &id,
670                                               DBUS_TYPE_INVALID)) {
671                         pam_syslog(handle, LOG_ERR, "Could not attach parameters to message.");
672                         r = PAM_BUF_ERR;
673                         goto finish;
674                 }
675
676                 reply = dbus_connection_send_with_reply_and_block(bus, m, -1, &error);
677                 if (!reply) {
678                         pam_syslog(handle, LOG_ERR, "Failed to release session: %s", bus_error_message(&error));
679                         r = PAM_SESSION_ERR;
680                         goto finish;
681                 }
682         }
683
684         r = PAM_SUCCESS;
685
686 finish:
687         pam_get_data(handle, "systemd.session-fd", &p);
688         if (p)
689                 close_nointr(PTR_TO_INT(p) - 1);
690
691         dbus_error_free(&error);
692
693         if (bus) {
694                 dbus_connection_close(bus);
695                 dbus_connection_unref(bus);
696         }
697
698         if (m)
699                 dbus_message_unref(m);
700
701         if (reply)
702                 dbus_message_unref(reply);
703
704         return r;
705 }