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