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