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