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