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