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