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