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