chiark / gitweb /
bus: update bloom filter description a bit
[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 (seat && !streq(seat, "seat0")) {
325                 pam_syslog(handle, LOG_DEBUG,
326                       "Ignoring vtnr %d for %s which is not seat0", vtnr, seat);
327                 vtnr = 0;
328         }
329
330         if (!type)
331                 type = !isempty(display) ? "x11" :
332                         !isempty(tty) ? "tty" : "unspecified";
333
334         class = pam_getenv(handle, "XDG_SESSION_CLASS");
335         if (isempty(class))
336                 class = getenv("XDG_SESSION_CLASS");
337         if (isempty(class))
338                 class = class_pam;
339         if (isempty(class))
340                 class = streq(type, "unspecified") ? "background" : "user";
341
342         remote = !isempty(remote_host) &&
343                 !streq_ptr(remote_host, "localhost") &&
344                 !streq_ptr(remote_host, "localhost.localdomain");
345
346         /* Talk to logind over the message bus */
347
348         r = sd_bus_open_system(&bus);
349         if (r < 0) {
350                 pam_syslog(handle, LOG_ERR, "Failed to connect to system bus: %s", strerror(-r));
351                 return PAM_SESSION_ERR;
352         }
353
354         if (debug)
355                 pam_syslog(handle, LOG_DEBUG, "Asking logind to create session: "
356                            "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",
357                            pw->pw_uid, getpid(),
358                            strempty(service),
359                            type, class,
360                            strempty(seat), vtnr, tty, strempty(display),
361                            yes_no(remote), strempty(remote_user), strempty(remote_host));
362
363         r = sd_bus_call_method(bus,
364                                "org.freedesktop.login1",
365                                "/org/freedesktop/login1",
366                                "org.freedesktop.login1.Manager",
367                                "CreateSession",
368                                &error,
369                                &reply,
370                                "uussssussbssa(sv)",
371                                (uint32_t) pw->pw_uid,
372                                (uint32_t) getpid(),
373                                strempty(service),
374                                type,
375                                class,
376                                strempty(seat),
377                                vtnr,
378                                tty,
379                                strempty(display),
380                                remote,
381                                strempty(remote_user),
382                                strempty(remote_host),
383                                0);
384         if (r < 0) {
385                 pam_syslog(handle, LOG_ERR, "Failed to create session: %s", bus_error_message(&error, r));
386                 return PAM_SYSTEM_ERR;
387         }
388
389         r = sd_bus_message_read(reply,
390                                 "soshusub",
391                                 &id,
392                                 &object_path,
393                                 &runtime_path,
394                                 &session_fd,
395                                 &original_uid,
396                                 &seat,
397                                 &vtnr,
398                                 &existing);
399         if (r < 0) {
400                 pam_syslog(handle, LOG_ERR, "Failed to parse message: %s", strerror(-r));
401                 return PAM_SESSION_ERR;
402         }
403
404         if (debug)
405                 pam_syslog(handle, LOG_DEBUG, "Reply from logind: "
406                            "id=%s object_path=%s runtime_path=%s session_fd=%d seat=%s vtnr=%u original_uid=%u",
407                            id, object_path, runtime_path, session_fd, seat, vtnr, original_uid);
408
409         r = pam_misc_setenv(handle, "XDG_SESSION_ID", id, 0);
410         if (r != PAM_SUCCESS) {
411                 pam_syslog(handle, LOG_ERR, "Failed to set session id.");
412                 return r;
413         }
414
415         if (original_uid == pw->pw_uid) {
416                 /* Don't set $XDG_RUNTIME_DIR if the user we now
417                  * authenticated for does not match the original user
418                  * of the session. We do this in order not to result
419                  * in privileged apps clobbering the runtime directory
420                  * unnecessarily. */
421
422                 r = pam_misc_setenv(handle, "XDG_RUNTIME_DIR", runtime_path, 0);
423                 if (r != PAM_SUCCESS) {
424                         pam_syslog(handle, LOG_ERR, "Failed to set runtime dir.");
425                         return r;
426                 }
427
428                 r = export_legacy_dbus_address(handle, pw->pw_uid, runtime_path);
429                 if (r != PAM_SUCCESS)
430                         return r;
431         }
432
433         if (!isempty(seat)) {
434                 r = pam_misc_setenv(handle, "XDG_SEAT", seat, 0);
435                 if (r != PAM_SUCCESS) {
436                         pam_syslog(handle, LOG_ERR, "Failed to set seat.");
437                         return r;
438                 }
439         }
440
441         if (vtnr > 0) {
442                 char buf[DECIMAL_STR_MAX(vtnr)];
443                 sprintf(buf, "%u", vtnr);
444
445                 r = pam_misc_setenv(handle, "XDG_VTNR", buf, 0);
446                 if (r != PAM_SUCCESS) {
447                         pam_syslog(handle, LOG_ERR, "Failed to set virtual terminal number.");
448                         return r;
449                 }
450         }
451
452         r = pam_set_data(handle, "systemd.existing", INT_TO_PTR(!!existing), NULL);
453         if (r != PAM_SUCCESS) {
454                 pam_syslog(handle, LOG_ERR, "Failed to install existing flag.");
455                 return r;
456         }
457
458         if (session_fd >= 0) {
459                 session_fd = dup(session_fd);
460                 if (session_fd < 0) {
461                         pam_syslog(handle, LOG_ERR, "Failed to dup session fd: %m");
462                         return PAM_SESSION_ERR;
463                 }
464
465                 r = pam_set_data(handle, "systemd.session-fd", INT_TO_PTR(session_fd+1), NULL);
466                 if (r != PAM_SUCCESS) {
467                         pam_syslog(handle, LOG_ERR, "Failed to install session fd.");
468                         close_nointr_nofail(session_fd);
469                         return r;
470                 }
471         }
472
473         return PAM_SUCCESS;
474 }
475
476 _public_ PAM_EXTERN int pam_sm_close_session(
477                 pam_handle_t *handle,
478                 int flags,
479                 int argc, const char **argv) {
480
481         _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
482         _cleanup_bus_unref_ sd_bus *bus = NULL;
483         const void *p = NULL, *existing = NULL;
484         const char *id;
485         int r;
486
487         assert(handle);
488
489         /* Only release session if it wasn't pre-existing when we
490          * tried to create it */
491         pam_get_data(handle, "systemd.existing", &existing);
492
493         id = pam_getenv(handle, "XDG_SESSION_ID");
494         if (id && !existing) {
495
496                 /* Before we go and close the FIFO we need to tell
497                  * logind that this is a clean session shutdown, so
498                  * that it doesn't just go and slaughter us
499                  * immediately after closing the fd */
500
501                 r = sd_bus_open_system(&bus);
502                 if (r < 0) {
503                         pam_syslog(handle, LOG_ERR,
504                                   "Failed to connect to system bus: %s", strerror(-r));
505                         r = PAM_SESSION_ERR;
506                         goto finish;
507                 }
508
509                 r = sd_bus_call_method(bus,
510                                        "org.freedesktop.login1",
511                                        "/org/freedesktop/login1",
512                                        "org.freedesktop.login1.Manager",
513                                        "ReleaseSession",
514                                        &error,
515                                        NULL,
516                                        "s",
517                                        id);
518                 if (r < 0) {
519                         pam_syslog(handle, LOG_ERR,
520                                    "Failed to release session: %s", bus_error_message(&error, r));
521
522                         r = PAM_SESSION_ERR;
523                         goto finish;
524                 }
525         }
526
527         r = PAM_SUCCESS;
528
529 finish:
530         pam_get_data(handle, "systemd.session-fd", &p);
531         if (p)
532                 close_nointr(PTR_TO_INT(p) - 1);
533
534         return r;
535 }