chiark / gitweb /
logind: make session type and class settable via the same ways
[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 "util.h"
36 #include "audit.h"
37 #include "macro.h"
38 #include "strv.h"
39 #include "bus-util.h"
40 #include "def.h"
41 #include "socket-util.h"
42 #include "fileio.h"
43 #include "bus-error.h"
44
45 static int parse_argv(
46                 pam_handle_t *handle,
47                 int argc, const char **argv,
48                 const char **class,
49                 const char **type,
50                 bool *debug) {
51
52         unsigned i;
53
54         assert(argc >= 0);
55         assert(argc == 0 || argv);
56
57         for (i = 0; i < (unsigned) argc; i++) {
58                 if (startswith(argv[i], "class=")) {
59                         if (class)
60                                 *class = argv[i] + 6;
61
62                 } else if (startswith(argv[i], "type=")) {
63                         if (type)
64                                 *type = argv[i] + 5;
65
66                 } else if (streq(argv[i], "debug")) {
67                         if (debug)
68                                 *debug = true;
69
70                 } else if (startswith(argv[i], "debug=")) {
71                         int k;
72
73                         k = parse_boolean(argv[i] + 6);
74                         if (k < 0)
75                                 pam_syslog(handle, LOG_WARNING, "Failed to parse debug= argument, ignoring.");
76                         else if (debug)
77                                 *debug = k;
78
79                 } else
80                         pam_syslog(handle, LOG_WARNING, "Unknown parameter '%s', ignoring", argv[i]);
81         }
82
83         return 0;
84 }
85
86 static int get_user_data(
87                 pam_handle_t *handle,
88                 const char **ret_username,
89                 struct passwd **ret_pw) {
90
91         const char *username = NULL;
92         struct passwd *pw = NULL;
93         int r;
94
95         assert(handle);
96         assert(ret_username);
97         assert(ret_pw);
98
99         r = pam_get_user(handle, &username, NULL);
100         if (r != PAM_SUCCESS) {
101                 pam_syslog(handle, LOG_ERR, "Failed to get user name.");
102                 return r;
103         }
104
105         if (isempty(username)) {
106                 pam_syslog(handle, LOG_ERR, "User name not valid.");
107                 return PAM_AUTH_ERR;
108         }
109
110         pw = pam_modutil_getpwnam(handle, username);
111         if (!pw) {
112                 pam_syslog(handle, LOG_ERR, "Failed to get user data.");
113                 return PAM_USER_UNKNOWN;
114         }
115
116         *ret_pw = pw;
117         *ret_username = username ? username : pw->pw_name;
118
119         return PAM_SUCCESS;
120 }
121
122 static int get_seat_from_display(const char *display, const char **seat, uint32_t *vtnr) {
123         union sockaddr_union sa = {
124                 .un.sun_family = AF_UNIX,
125         };
126         _cleanup_free_ char *p = NULL, *tty = NULL;
127         _cleanup_close_ int fd = -1;
128         struct ucred ucred;
129         int v, r;
130
131         assert(display);
132         assert(vtnr);
133
134         /* We deduce the X11 socket from the display name, then use
135          * SO_PEERCRED to determine the X11 server process, ask for
136          * the controlling tty of that and if it's a VC then we know
137          * the seat and the virtual terminal. Sounds ugly, is only
138          * semi-ugly. */
139
140         r = socket_from_display(display, &p);
141         if (r < 0)
142                 return r;
143         strncpy(sa.un.sun_path, p, sizeof(sa.un.sun_path)-1);
144
145         fd = socket(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC, 0);
146         if (fd < 0)
147                 return -errno;
148
149         if (connect(fd, &sa.sa, offsetof(struct sockaddr_un, sun_path) + strlen(sa.un.sun_path)) < 0)
150                 return -errno;
151
152         r = getpeercred(fd, &ucred);
153         if (r < 0)
154                 return r;
155
156         r = get_ctty(ucred.pid, NULL, &tty);
157         if (r < 0)
158                 return r;
159
160         v = vtnr_from_tty(tty);
161         if (v < 0)
162                 return v;
163         else if (v == 0)
164                 return -ENOENT;
165
166         if (seat)
167                 *seat = "seat0";
168         *vtnr = (uint32_t) v;
169
170         return 0;
171 }
172
173 static int export_legacy_dbus_address(
174                 pam_handle_t *handle,
175                 uid_t uid,
176                 const char *runtime) {
177
178 #ifdef ENABLE_KDBUS
179         _cleanup_free_ char *s = NULL;
180         int r;
181
182         /* skip export if kdbus is not active */
183         if (access("/dev/kdbus", F_OK) < 0)
184                 return PAM_SUCCESS;
185
186         if (asprintf(&s, KERNEL_USER_BUS_FMT ";" UNIX_USER_BUS_FMT,
187                      (unsigned long) uid, runtime) < 0) {
188                 pam_syslog(handle, LOG_ERR, "Failed to set bus variable.");
189                 return PAM_BUF_ERR;
190         }
191
192         r = pam_misc_setenv(handle, "DBUS_SESSION_BUS_ADDRESS", s, 0);
193         if (r != PAM_SUCCESS) {
194                 pam_syslog(handle, LOG_ERR, "Failed to set bus variable.");
195                 return r;
196         }
197 #endif
198         return PAM_SUCCESS;
199 }
200
201 _public_ PAM_EXTERN int pam_sm_open_session(
202                 pam_handle_t *handle,
203                 int flags,
204                 int argc, const char **argv) {
205
206         _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
207         _cleanup_bus_message_unref_ sd_bus_message *reply = NULL;
208         const char
209                 *username, *id, *object_path, *runtime_path,
210                 *service = NULL,
211                 *tty = NULL, *display = NULL,
212                 *remote_user = NULL, *remote_host = NULL,
213                 *seat = NULL,
214                 *type = NULL, *class = NULL,
215                 *class_pam = NULL, *type_pam = NULL, *cvtnr = NULL;
216         _cleanup_bus_unref_ sd_bus *bus = NULL;
217         int session_fd = -1, existing, r;
218         bool debug = false, remote;
219         struct passwd *pw;
220         uint32_t vtnr = 0;
221         uid_t original_uid;
222
223         assert(handle);
224
225         /* Make this a NOP on non-logind systems */
226         if (!logind_running())
227                 return PAM_SUCCESS;
228
229         if (parse_argv(handle,
230                        argc, argv,
231                        &class_pam,
232                        &type_pam,
233                        &debug) < 0)
234                 return PAM_SESSION_ERR;
235
236         if (debug)
237                 pam_syslog(handle, LOG_INFO, "pam-systemd initializing");
238
239         r = get_user_data(handle, &username, &pw);
240         if (r != PAM_SUCCESS) {
241                 pam_syslog(handle, LOG_ERR, "Failed to get user data.");
242                 return r;
243         }
244
245         /* Make sure we don't enter a loop by talking to
246          * systemd-logind when it is actually waiting for the
247          * background to finish start-up. If the service is
248          * "systemd-user" we simply set XDG_RUNTIME_DIR and
249          * leave. */
250
251         pam_get_item(handle, PAM_SERVICE, (const void**) &service);
252         if (streq_ptr(service, "systemd-user")) {
253                 _cleanup_free_ char *p = NULL, *rt = NULL;
254
255                 if (asprintf(&p, "/run/systemd/users/%lu", (unsigned long) pw->pw_uid) < 0)
256                         return PAM_BUF_ERR;
257
258                 r = parse_env_file(p, NEWLINE,
259                                    "RUNTIME", &rt,
260                                    NULL);
261                 if (r < 0 && r != -ENOENT)
262                         return PAM_SESSION_ERR;
263
264                 if (rt)  {
265                         r = pam_misc_setenv(handle, "XDG_RUNTIME_DIR", rt, 0);
266                         if (r != PAM_SUCCESS) {
267                                 pam_syslog(handle, LOG_ERR, "Failed to set runtime dir.");
268                                 return r;
269                         }
270
271                         r = export_legacy_dbus_address(handle, pw->pw_uid, rt);
272                         if (r != PAM_SUCCESS)
273                                 return r;
274                 }
275
276                 return PAM_SUCCESS;
277         }
278
279         /* Otherwise, we ask logind to create a session for us */
280
281         pam_get_item(handle, PAM_XDISPLAY, (const void**) &display);
282         pam_get_item(handle, PAM_TTY, (const void**) &tty);
283         pam_get_item(handle, PAM_RUSER, (const void**) &remote_user);
284         pam_get_item(handle, PAM_RHOST, (const void**) &remote_host);
285
286         seat = pam_getenv(handle, "XDG_SEAT");
287         if (isempty(seat))
288                 seat = getenv("XDG_SEAT");
289
290         cvtnr = pam_getenv(handle, "XDG_VTNR");
291         if (isempty(cvtnr))
292                 cvtnr = getenv("XDG_VTNR");
293
294         type = pam_getenv(handle, "XDG_SESSION_TYPE");
295         if (isempty(type))
296                 type = getenv("XDG_SESSION_TYPE");
297         if (isempty(type))
298                 type = type_pam;
299
300         class = pam_getenv(handle, "XDG_SESSION_CLASS");
301         if (isempty(class))
302                 class = getenv("XDG_SESSION_CLASS");
303         if (isempty(class))
304                 class = class_pam;
305
306         tty = strempty(tty);
307         display = strempty(display);
308
309         if (strchr(tty, ':')) {
310                 /* A tty with a colon is usually an X11 display,
311                  * placed there to show up in utmp. We rearrange
312                  * things and don't pretend that an X display was a
313                  * tty. */
314
315                 if (isempty(display))
316                         display = tty;
317                 tty = "";
318         } else if (streq(tty, "cron")) {
319                 /* cron has been setting PAM_TTY to "cron" for a very
320                  * long time and it probably shouldn't stop doing that
321                  * for compatibility reasons. */
322                 type = "unspecified";
323                 class = "background";
324                 tty = "";
325         } else if (streq(tty, "ssh")) {
326                 /* ssh has been setting PAM_TTY to "ssh" for a very
327                  * long time and probably shouldn't stop doing that
328                  * for compatibility reasons. */
329                 type ="tty";
330                 class = "user";
331                 tty = "";
332         }
333
334         /* If this fails vtnr will be 0, that's intended */
335         if (!isempty(cvtnr))
336                 safe_atou32(cvtnr, &vtnr);
337
338         if (!isempty(display) && !vtnr) {
339                 if (isempty(seat))
340                         get_seat_from_display(display, &seat, &vtnr);
341                 else if (streq(seat, "seat0"))
342                         get_seat_from_display(display, NULL, &vtnr);
343         }
344
345         if (seat && !streq(seat, "seat0") && vtnr != 0) {
346                 pam_syslog(handle, LOG_DEBUG, "Ignoring vtnr %d for %s which is not seat0", vtnr, seat);
347                 vtnr = 0;
348         }
349
350         if (isempty(type))
351                 type = !isempty(display) ? "x11" :
352                            !isempty(tty) ? "tty" : "unspecified";
353
354         if (isempty(class))
355                 class = streq(type, "unspecified") ? "background" : "user";
356
357         remote = !isempty(remote_host) &&
358                 !streq_ptr(remote_host, "localhost") &&
359                 !streq_ptr(remote_host, "localhost.localdomain");
360
361         /* Talk to logind over the message bus */
362
363         r = sd_bus_open_system(&bus);
364         if (r < 0) {
365                 pam_syslog(handle, LOG_ERR, "Failed to connect to system bus: %s", strerror(-r));
366                 return PAM_SESSION_ERR;
367         }
368
369         if (debug)
370                 pam_syslog(handle, LOG_DEBUG, "Asking logind to create session: "
371                            "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",
372                            pw->pw_uid, getpid(),
373                            strempty(service),
374                            type, class,
375                            strempty(seat), vtnr, tty, strempty(display),
376                            yes_no(remote), strempty(remote_user), strempty(remote_host));
377
378         r = sd_bus_call_method(bus,
379                                "org.freedesktop.login1",
380                                "/org/freedesktop/login1",
381                                "org.freedesktop.login1.Manager",
382                                "CreateSession",
383                                &error,
384                                &reply,
385                                "uussssussbssa(sv)",
386                                (uint32_t) pw->pw_uid,
387                                (uint32_t) getpid(),
388                                strempty(service),
389                                type,
390                                class,
391                                strempty(seat),
392                                vtnr,
393                                tty,
394                                strempty(display),
395                                remote,
396                                strempty(remote_user),
397                                strempty(remote_host),
398                                0);
399         if (r < 0) {
400                 pam_syslog(handle, LOG_ERR, "Failed to create session: %s", bus_error_message(&error, r));
401                 return PAM_SYSTEM_ERR;
402         }
403
404         r = sd_bus_message_read(reply,
405                                 "soshusub",
406                                 &id,
407                                 &object_path,
408                                 &runtime_path,
409                                 &session_fd,
410                                 &original_uid,
411                                 &seat,
412                                 &vtnr,
413                                 &existing);
414         if (r < 0) {
415                 pam_syslog(handle, LOG_ERR, "Failed to parse message: %s", strerror(-r));
416                 return PAM_SESSION_ERR;
417         }
418
419         if (debug)
420                 pam_syslog(handle, LOG_DEBUG, "Reply from logind: "
421                            "id=%s object_path=%s runtime_path=%s session_fd=%d seat=%s vtnr=%u original_uid=%u",
422                            id, object_path, runtime_path, session_fd, seat, vtnr, original_uid);
423
424         r = pam_misc_setenv(handle, "XDG_SESSION_ID", id, 0);
425         if (r != PAM_SUCCESS) {
426                 pam_syslog(handle, LOG_ERR, "Failed to set session id.");
427                 return r;
428         }
429
430         if (original_uid == pw->pw_uid) {
431                 /* Don't set $XDG_RUNTIME_DIR if the user we now
432                  * authenticated for does not match the original user
433                  * of the session. We do this in order not to result
434                  * in privileged apps clobbering the runtime directory
435                  * unnecessarily. */
436
437                 r = pam_misc_setenv(handle, "XDG_RUNTIME_DIR", runtime_path, 0);
438                 if (r != PAM_SUCCESS) {
439                         pam_syslog(handle, LOG_ERR, "Failed to set runtime dir.");
440                         return r;
441                 }
442
443                 r = export_legacy_dbus_address(handle, pw->pw_uid, runtime_path);
444                 if (r != PAM_SUCCESS)
445                         return r;
446         }
447
448         if (!isempty(seat)) {
449                 r = pam_misc_setenv(handle, "XDG_SEAT", seat, 0);
450                 if (r != PAM_SUCCESS) {
451                         pam_syslog(handle, LOG_ERR, "Failed to set seat.");
452                         return r;
453                 }
454         }
455
456         if (vtnr > 0) {
457                 char buf[DECIMAL_STR_MAX(vtnr)];
458                 sprintf(buf, "%u", vtnr);
459
460                 r = pam_misc_setenv(handle, "XDG_VTNR", buf, 0);
461                 if (r != PAM_SUCCESS) {
462                         pam_syslog(handle, LOG_ERR, "Failed to set virtual terminal number.");
463                         return r;
464                 }
465         }
466
467         r = pam_set_data(handle, "systemd.existing", INT_TO_PTR(!!existing), NULL);
468         if (r != PAM_SUCCESS) {
469                 pam_syslog(handle, LOG_ERR, "Failed to install existing flag.");
470                 return r;
471         }
472
473         if (session_fd >= 0) {
474                 session_fd = dup(session_fd);
475                 if (session_fd < 0) {
476                         pam_syslog(handle, LOG_ERR, "Failed to dup session fd: %m");
477                         return PAM_SESSION_ERR;
478                 }
479
480                 r = pam_set_data(handle, "systemd.session-fd", INT_TO_PTR(session_fd+1), NULL);
481                 if (r != PAM_SUCCESS) {
482                         pam_syslog(handle, LOG_ERR, "Failed to install session fd.");
483                         close_nointr_nofail(session_fd);
484                         return r;
485                 }
486         }
487
488         return PAM_SUCCESS;
489 }
490
491 _public_ PAM_EXTERN int pam_sm_close_session(
492                 pam_handle_t *handle,
493                 int flags,
494                 int argc, const char **argv) {
495
496         _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
497         _cleanup_bus_unref_ sd_bus *bus = NULL;
498         const void *p = NULL, *existing = NULL;
499         const char *id;
500         int r;
501
502         assert(handle);
503
504         /* Only release session if it wasn't pre-existing when we
505          * tried to create it */
506         pam_get_data(handle, "systemd.existing", &existing);
507
508         id = pam_getenv(handle, "XDG_SESSION_ID");
509         if (id && !existing) {
510
511                 /* Before we go and close the FIFO we need to tell
512                  * logind that this is a clean session shutdown, so
513                  * that it doesn't just go and slaughter us
514                  * immediately after closing the fd */
515
516                 r = sd_bus_open_system(&bus);
517                 if (r < 0) {
518                         pam_syslog(handle, LOG_ERR,
519                                   "Failed to connect to system bus: %s", strerror(-r));
520                         r = PAM_SESSION_ERR;
521                         goto finish;
522                 }
523
524                 r = sd_bus_call_method(bus,
525                                        "org.freedesktop.login1",
526                                        "/org/freedesktop/login1",
527                                        "org.freedesktop.login1.Manager",
528                                        "ReleaseSession",
529                                        &error,
530                                        NULL,
531                                        "s",
532                                        id);
533                 if (r < 0) {
534                         pam_syslog(handle, LOG_ERR,
535                                    "Failed to release session: %s", bus_error_message(&error, r));
536
537                         r = PAM_SESSION_ERR;
538                         goto finish;
539                 }
540         }
541
542         r = PAM_SUCCESS;
543
544 finish:
545         pam_get_data(handle, "systemd.session-fd", &p);
546         if (p)
547                 close_nointr(PTR_TO_INT(p) - 1);
548
549         return r;
550 }