chiark / gitweb /
Fix assertion failure when resuming from sleep/suspend
[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 = PAM_BUF_ERR;
183
184         if (is_kdbus_available()) {
185                 if (asprintf(&s, KERNEL_USER_BUS_ADDRESS_FMT ";" UNIX_USER_BUS_ADDRESS_FMT, uid, runtime) < 0)
186                         goto error;
187         } else {
188                 /* FIXME: We *really* should move the access() check into the
189                  * daemons that spawn dbus-daemon, instead of forcing
190                  * DBUS_SESSION_BUS_ADDRESS= here. */
191
192                 s = strjoin(runtime, "/bus", NULL);
193                 if (!s)
194                         goto error;
195
196                 if (access(s, F_OK) < 0)
197                         return PAM_SUCCESS;
198
199                 s = mfree(s);
200                 if (asprintf(&s, UNIX_USER_BUS_ADDRESS_FMT, runtime) < 0)
201                         goto error;
202         }
203
204         r = pam_misc_setenv(handle, "DBUS_SESSION_BUS_ADDRESS", s, 0);
205         if (r != PAM_SUCCESS)
206                 goto error;
207
208         return PAM_SUCCESS;
209
210 error:
211         pam_syslog(handle, LOG_ERR, "Failed to set bus variable.");
212         return r;
213 }
214
215 _public_ PAM_EXTERN int pam_sm_open_session(
216                 pam_handle_t *handle,
217                 int flags,
218                 int argc, const char **argv) {
219
220         _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
221         _cleanup_bus_message_unref_ sd_bus_message *reply = NULL;
222         const char
223                 *username, *id, *object_path, *runtime_path,
224                 *service = NULL,
225                 *tty = NULL, *display = NULL,
226                 *remote_user = NULL, *remote_host = NULL,
227                 *seat = NULL,
228                 *type = NULL, *class = NULL,
229                 *class_pam = NULL, *type_pam = NULL, *cvtnr = NULL, *desktop = NULL;
230         _cleanup_bus_flush_close_unref_ sd_bus *bus = NULL;
231         int session_fd = -1, existing, r;
232         bool debug = false, remote;
233         struct passwd *pw;
234         uint32_t vtnr = 0;
235         uid_t original_uid;
236
237         assert(handle);
238
239         /* Make this a NOP on non-logind systems */
240         if (!logind_running())
241                 return PAM_SUCCESS;
242
243         if (parse_argv(handle,
244                        argc, argv,
245                        &class_pam,
246                        &type_pam,
247                        &debug) < 0)
248                 return PAM_SESSION_ERR;
249
250         if (debug)
251                 pam_syslog(handle, LOG_DEBUG, "pam-systemd initializing");
252
253         r = get_user_data(handle, &username, &pw);
254         if (r != PAM_SUCCESS) {
255                 pam_syslog(handle, LOG_ERR, "Failed to get user data.");
256                 return r;
257         }
258
259         /* Make sure we don't enter a loop by talking to
260          * systemd-logind when it is actually waiting for the
261          * background to finish start-up. If the service is
262          * "systemd-user" we simply set XDG_RUNTIME_DIR and
263          * leave. */
264
265         pam_get_item(handle, PAM_SERVICE, (const void**) &service);
266         if (streq_ptr(service, "systemd-user")) {
267                 _cleanup_free_ char *p = NULL, *rt = NULL;
268
269                 if (asprintf(&p, "/run/systemd/users/"UID_FMT, pw->pw_uid) < 0)
270                         return PAM_BUF_ERR;
271
272                 r = parse_env_file(p, NEWLINE,
273                                    "RUNTIME", &rt,
274                                    NULL);
275                 if (r < 0 && r != -ENOENT)
276                         return PAM_SESSION_ERR;
277
278                 if (rt)  {
279                         r = pam_misc_setenv(handle, "XDG_RUNTIME_DIR", rt, 0);
280                         if (r != PAM_SUCCESS) {
281                                 pam_syslog(handle, LOG_ERR, "Failed to set runtime dir.");
282                                 return r;
283                         }
284
285                         r = export_legacy_dbus_address(handle, pw->pw_uid, rt);
286                         if (r != PAM_SUCCESS)
287                                 return r;
288                 }
289
290                 return PAM_SUCCESS;
291         }
292
293         /* Otherwise, we ask logind to create a session for us */
294
295         pam_get_item(handle, PAM_XDISPLAY, (const void**) &display);
296         pam_get_item(handle, PAM_TTY, (const void**) &tty);
297         pam_get_item(handle, PAM_RUSER, (const void**) &remote_user);
298         pam_get_item(handle, PAM_RHOST, (const void**) &remote_host);
299
300         seat = pam_getenv(handle, "XDG_SEAT");
301         if (isempty(seat))
302                 seat = getenv("XDG_SEAT");
303
304         cvtnr = pam_getenv(handle, "XDG_VTNR");
305         if (isempty(cvtnr))
306                 cvtnr = getenv("XDG_VTNR");
307
308         type = pam_getenv(handle, "XDG_SESSION_TYPE");
309         if (isempty(type))
310                 type = getenv("XDG_SESSION_TYPE");
311         if (isempty(type))
312                 type = type_pam;
313
314         class = pam_getenv(handle, "XDG_SESSION_CLASS");
315         if (isempty(class))
316                 class = getenv("XDG_SESSION_CLASS");
317         if (isempty(class))
318                 class = class_pam;
319
320         desktop = pam_getenv(handle, "XDG_SESSION_DESKTOP");
321         if (isempty(desktop))
322                 desktop = getenv("XDG_SESSION_DESKTOP");
323
324         tty = strempty(tty);
325
326         if (strchr(tty, ':')) {
327                 /* A tty with a colon is usually an X11 display,
328                  * placed there to show up in utmp. We rearrange
329                  * things and don't pretend that an X display was a
330                  * tty. */
331
332                 if (isempty(display))
333                         display = tty;
334                 tty = NULL;
335         } else if (streq(tty, "cron")) {
336                 /* cron has been setting PAM_TTY to "cron" for a very
337                  * long time and it probably shouldn't stop doing that
338                  * for compatibility reasons. */
339                 type = "unspecified";
340                 class = "background";
341                 tty = NULL;
342         } else if (streq(tty, "ssh")) {
343                 /* ssh has been setting PAM_TTY to "ssh" for a very
344                  * long time and probably shouldn't stop doing that
345                  * for compatibility reasons. */
346                 type ="tty";
347                 class = "user";
348                 tty = NULL;
349         }
350
351         /* If this fails vtnr will be 0, that's intended */
352         if (!isempty(cvtnr))
353                 (void) safe_atou32(cvtnr, &vtnr);
354
355         if (!isempty(display) && !vtnr) {
356                 if (isempty(seat))
357                         get_seat_from_display(display, &seat, &vtnr);
358                 else if (streq(seat, "seat0"))
359                         get_seat_from_display(display, NULL, &vtnr);
360         }
361
362         if (seat && !streq(seat, "seat0") && vtnr != 0) {
363                 pam_syslog(handle, LOG_DEBUG, "Ignoring vtnr %"PRIu32" for %s which is not seat0", vtnr, seat);
364                 vtnr = 0;
365         }
366
367         if (isempty(type))
368                 type = !isempty(display) ? "x11" :
369                            !isempty(tty) ? "tty" : "unspecified";
370
371         if (isempty(class))
372                 class = streq(type, "unspecified") ? "background" : "user";
373
374         remote = !isempty(remote_host) && !is_localhost(remote_host);
375
376         /* Talk to logind over the message bus */
377
378         r = sd_bus_open_system(&bus);
379         if (r < 0) {
380                 pam_syslog(handle, LOG_ERR, "Failed to connect to system bus: %s", strerror(-r));
381                 return PAM_SESSION_ERR;
382         }
383
384         if (debug)
385                 pam_syslog(handle, LOG_DEBUG, "Asking logind to create session: "
386                            "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",
387                            pw->pw_uid, getpid(),
388                            strempty(service),
389                            type, class, strempty(desktop),
390                            strempty(seat), vtnr, strempty(tty), strempty(display),
391                            yes_no(remote), strempty(remote_user), strempty(remote_host));
392
393         r = sd_bus_call_method(bus,
394                                "org.freedesktop.login1",
395                                "/org/freedesktop/login1",
396                                "org.freedesktop.login1.Manager",
397                                "CreateSession",
398                                &error,
399                                &reply,
400                                "uusssssussbssa(sv)",
401                                (uint32_t) pw->pw_uid,
402                                (uint32_t) getpid(),
403                                service,
404                                type,
405                                class,
406                                desktop,
407                                seat,
408                                vtnr,
409                                tty,
410                                display,
411                                remote,
412                                remote_user,
413                                remote_host,
414                                0);
415         if (r < 0) {
416                 if (sd_bus_error_has_name(&error, BUS_ERROR_SESSION_BUSY)) {
417                         pam_syslog(handle, LOG_DEBUG, "Cannot create session: %s", bus_error_message(&error, r));
418                         return PAM_SUCCESS;
419                 } else {
420                         pam_syslog(handle, LOG_ERR, "Failed to create session: %s", bus_error_message(&error, r));
421                         return PAM_SYSTEM_ERR;
422                 }
423         }
424
425         r = sd_bus_message_read(reply,
426                                 "soshusub",
427                                 &id,
428                                 &object_path,
429                                 &runtime_path,
430                                 &session_fd,
431                                 &original_uid,
432                                 &seat,
433                                 &vtnr,
434                                 &existing);
435         if (r < 0) {
436                 pam_syslog(handle, LOG_ERR, "Failed to parse message: %s", strerror(-r));
437                 return PAM_SESSION_ERR;
438         }
439
440         if (debug)
441                 pam_syslog(handle, LOG_DEBUG, "Reply from logind: "
442                            "id=%s object_path=%s runtime_path=%s session_fd=%d seat=%s vtnr=%u original_uid=%u",
443                            id, object_path, runtime_path, session_fd, seat, vtnr, original_uid);
444
445         r = pam_misc_setenv(handle, "XDG_SESSION_ID", id, 0);
446         if (r != PAM_SUCCESS) {
447                 pam_syslog(handle, LOG_ERR, "Failed to set session id.");
448                 return r;
449         }
450
451         if (original_uid == pw->pw_uid) {
452                 /* Don't set $XDG_RUNTIME_DIR if the user we now
453                  * authenticated for does not match the original user
454                  * of the session. We do this in order not to result
455                  * in privileged apps clobbering the runtime directory
456                  * unnecessarily. */
457
458                 r = pam_misc_setenv(handle, "XDG_RUNTIME_DIR", runtime_path, 0);
459                 if (r != PAM_SUCCESS) {
460                         pam_syslog(handle, LOG_ERR, "Failed to set runtime dir.");
461                         return r;
462                 }
463
464                 r = export_legacy_dbus_address(handle, pw->pw_uid, runtime_path);
465                 if (r != PAM_SUCCESS)
466                         return r;
467         }
468
469         if (!isempty(seat)) {
470                 r = pam_misc_setenv(handle, "XDG_SEAT", seat, 0);
471                 if (r != PAM_SUCCESS) {
472                         pam_syslog(handle, LOG_ERR, "Failed to set seat.");
473                         return r;
474                 }
475         }
476
477         if (vtnr > 0) {
478                 char buf[DECIMAL_STR_MAX(vtnr)];
479                 sprintf(buf, "%u", vtnr);
480
481                 r = pam_misc_setenv(handle, "XDG_VTNR", buf, 0);
482                 if (r != PAM_SUCCESS) {
483                         pam_syslog(handle, LOG_ERR, "Failed to set virtual terminal number.");
484                         return r;
485                 }
486         }
487
488         r = pam_set_data(handle, "systemd.existing", INT_TO_PTR(!!existing), NULL);
489         if (r != PAM_SUCCESS) {
490                 pam_syslog(handle, LOG_ERR, "Failed to install existing flag.");
491                 return r;
492         }
493
494         if (session_fd >= 0) {
495                 session_fd = fcntl(session_fd, F_DUPFD_CLOEXEC, 3);
496                 if (session_fd < 0) {
497                         pam_syslog(handle, LOG_ERR, "Failed to dup session fd: %m");
498                         return PAM_SESSION_ERR;
499                 }
500
501                 r = pam_set_data(handle, "systemd.session-fd", INT_TO_PTR(session_fd+1), NULL);
502                 if (r != PAM_SUCCESS) {
503                         pam_syslog(handle, LOG_ERR, "Failed to install session fd.");
504                         safe_close(session_fd);
505                         return r;
506                 }
507         }
508
509         return PAM_SUCCESS;
510 }
511
512 _public_ PAM_EXTERN int pam_sm_close_session(
513                 pam_handle_t *handle,
514                 int flags,
515                 int argc, const char **argv) {
516
517         _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
518         _cleanup_bus_flush_close_unref_ sd_bus *bus = NULL;
519         const void *existing = NULL;
520         const char *id;
521         int r;
522
523         assert(handle);
524
525         /* Only release session if it wasn't pre-existing when we
526          * tried to create it */
527         pam_get_data(handle, "systemd.existing", &existing);
528
529         id = pam_getenv(handle, "XDG_SESSION_ID");
530         if (id && !existing) {
531
532                 /* Before we go and close the FIFO we need to tell
533                  * logind that this is a clean session shutdown, so
534                  * that it doesn't just go and slaughter us
535                  * immediately after closing the fd */
536
537                 r = sd_bus_open_system(&bus);
538                 if (r < 0) {
539                         pam_syslog(handle, LOG_ERR, "Failed to connect to system bus: %s", strerror(-r));
540                         return PAM_SESSION_ERR;
541                 }
542
543                 r = sd_bus_call_method(bus,
544                                        "org.freedesktop.login1",
545                                        "/org/freedesktop/login1",
546                                        "org.freedesktop.login1.Manager",
547                                        "ReleaseSession",
548                                        &error,
549                                        NULL,
550                                        "s",
551                                        id);
552                 if (r < 0) {
553                         pam_syslog(handle, LOG_ERR, "Failed to release session: %s", bus_error_message(&error, r));
554                         return PAM_SESSION_ERR;
555                 }
556         }
557
558         /* Note that we are knowingly leaking the FIFO fd here. This
559          * way, logind can watch us die. If we closed it here it would
560          * not have any clue when that is completed. Given that one
561          * cannot really have multiple PAM sessions open from the same
562          * process this means we will leak one FD at max. */
563
564         return PAM_SUCCESS;
565 }