]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/login/pam_systemd.c
man: add link to glib in sd_event_run(3)
[thirdparty/systemd.git] / src / login / pam_systemd.c
CommitLineData
d6c9574f 1/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
8c6db833
LP
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
5430f7f2
LP
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
8c6db833
LP
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
5430f7f2 16 Lesser General Public License for more details.
8c6db833 17
5430f7f2 18 You should have received a copy of the GNU Lesser General Public License
8c6db833
LP
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>
a838e6a1 26#include <endian.h>
8c6db833
LP
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
8c6db833 34#include "util.h"
d7832d2c 35#include "audit.h"
8c6db833 36#include "macro.h"
74fe1fe3 37#include "strv.h"
ffcfcb6b 38#include "bus-util.h"
98a28fef 39#include "def.h"
4d6d6518 40#include "socket-util.h"
a5c32cff 41#include "fileio.h"
8159d91a 42#include "bus-error.h"
6482f626 43#include "formats-util.h"
288a74cc 44#include "terminal-util.h"
8c6db833 45
baae0358
LP
46static int parse_argv(
47 pam_handle_t *handle,
48 int argc, const char **argv,
49 const char **class,
49ebd11f 50 const char **type,
baae0358 51 bool *debug) {
8c6db833
LP
52
53 unsigned i;
54
55 assert(argc >= 0);
56 assert(argc == 0 || argv);
57
49ebd11f 58 for (i = 0; i < (unsigned) argc; i++) {
fb6becb4 59 if (startswith(argv[i], "class=")) {
485507b8
MM
60 if (class)
61 *class = argv[i] + 6;
62
49ebd11f
LP
63 } else if (startswith(argv[i], "type=")) {
64 if (type)
65 *type = argv[i] + 5;
66
05a049cc
ZJS
67 } else if (streq(argv[i], "debug")) {
68 if (debug)
69 *debug = true;
fb6becb4 70
05a049cc
ZJS
71 } else if (startswith(argv[i], "debug=")) {
72 int k;
0e318cad 73
05a049cc
ZJS
74 k = parse_boolean(argv[i] + 6);
75 if (k < 0)
76 pam_syslog(handle, LOG_WARNING, "Failed to parse debug= argument, ignoring.");
77 else if (debug)
0e318cad
MS
78 *debug = k;
79
05a049cc 80 } else
fb6becb4 81 pam_syslog(handle, LOG_WARNING, "Unknown parameter '%s', ignoring", argv[i]);
49ebd11f 82 }
8c6db833 83
8c6db833
LP
84 return 0;
85}
86
8c6db833
LP
87static int get_user_data(
88 pam_handle_t *handle,
89 const char **ret_username,
90 struct passwd **ret_pw) {
91
d90b9d27
LP
92 const char *username = NULL;
93 struct passwd *pw = NULL;
8c6db833
LP
94 int r;
95
96 assert(handle);
97 assert(ret_username);
98 assert(ret_pw);
99
baae0358
LP
100 r = pam_get_user(handle, &username, NULL);
101 if (r != PAM_SUCCESS) {
102 pam_syslog(handle, LOG_ERR, "Failed to get user name.");
103 return r;
104 }
d90b9d27 105
baae0358
LP
106 if (isempty(username)) {
107 pam_syslog(handle, LOG_ERR, "User name not valid.");
108 return PAM_AUTH_ERR;
8c6db833
LP
109 }
110
baae0358 111 pw = pam_modutil_getpwnam(handle, username);
d90b9d27 112 if (!pw) {
8c6db833
LP
113 pam_syslog(handle, LOG_ERR, "Failed to get user data.");
114 return PAM_USER_UNKNOWN;
115 }
116
117 *ret_pw = pw;
8e24a4f8 118 *ret_username = username;
8c6db833
LP
119
120 return PAM_SUCCESS;
121}
122
4d6d6518 123static int get_seat_from_display(const char *display, const char **seat, uint32_t *vtnr) {
b92bea5d
ZJS
124 union sockaddr_union sa = {
125 .un.sun_family = AF_UNIX,
126 };
baae0358
LP
127 _cleanup_free_ char *p = NULL, *tty = NULL;
128 _cleanup_close_ int fd = -1;
4d6d6518 129 struct ucred ucred;
baae0358 130 int v, r;
4d6d6518
LP
131
132 assert(display);
4d6d6518
LP
133 assert(vtnr);
134
135 /* We deduce the X11 socket from the display name, then use
136 * SO_PEERCRED to determine the X11 server process, ask for
137 * the controlling tty of that and if it's a VC then we know
138 * the seat and the virtual terminal. Sounds ugly, is only
139 * semi-ugly. */
140
141 r = socket_from_display(display, &p);
142 if (r < 0)
143 return r;
b92bea5d 144 strncpy(sa.un.sun_path, p, sizeof(sa.un.sun_path)-1);
4d6d6518
LP
145
146 fd = socket(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC, 0);
b92bea5d 147 if (fd < 0)
4d6d6518 148 return -errno;
4d6d6518 149
b92bea5d 150 if (connect(fd, &sa.sa, offsetof(struct sockaddr_un, sun_path) + strlen(sa.un.sun_path)) < 0)
4d6d6518 151 return -errno;
4d6d6518 152
eff05270 153 r = getpeercred(fd, &ucred);
4d6d6518 154 if (r < 0)
eff05270 155 return r;
4d6d6518
LP
156
157 r = get_ctty(ucred.pid, NULL, &tty);
158 if (r < 0)
159 return r;
160
161 v = vtnr_from_tty(tty);
4d6d6518
LP
162 if (v < 0)
163 return v;
164 else if (v == 0)
165 return -ENOENT;
166
fc7985ed
LP
167 if (seat)
168 *seat = "seat0";
4d6d6518
LP
169 *vtnr = (uint32_t) v;
170
171 return 0;
172}
173
8b255ecd
KS
174static int export_legacy_dbus_address(
175 pam_handle_t *handle,
176 uid_t uid,
177 const char *runtime) {
178
5eecd85f 179#ifdef ENABLE_KDBUS
8b255ecd
KS
180 _cleanup_free_ char *s = NULL;
181 int r;
182
8042e377 183 /* skip export if kdbus is not active */
63cc4c31 184 if (access("/sys/fs/kdbus", F_OK) < 0)
8042e377
KS
185 return PAM_SUCCESS;
186
e3afaf6b 187 if (asprintf(&s, KERNEL_USER_BUS_ADDRESS_FMT ";" UNIX_USER_BUS_ADDRESS_FMT, uid, runtime) < 0) {
8b255ecd
KS
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 }
5eecd85f 197#endif
8b255ecd
KS
198 return PAM_SUCCESS;
199}
200
98a28fef 201_public_ PAM_EXTERN int pam_sm_open_session(
8c6db833
LP
202 pam_handle_t *handle,
203 int flags,
204 int argc, const char **argv) {
205
ffcfcb6b
ZJS
206 _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
207 _cleanup_bus_message_unref_ sd_bus_message *reply = NULL;
d1529c9e
LP
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,
a4cd87e9 215 *class_pam = NULL, *type_pam = NULL, *cvtnr = NULL, *desktop = NULL;
24996861 216 _cleanup_bus_close_unref_ sd_bus *bus = NULL;
d1529c9e 217 int session_fd = -1, existing, r;
d1529c9e
LP
218 bool debug = false, remote;
219 struct passwd *pw;
baae0358
LP
220 uint32_t vtnr = 0;
221 uid_t original_uid;
8c6db833 222
ffcfcb6b 223 assert(handle);
98a28fef 224
79d860fe
MP
225 /* Make this a NOP on non-logind systems */
226 if (!logind_running())
8c6db833
LP
227 return PAM_SUCCESS;
228
e9fbc77c
LP
229 if (parse_argv(handle,
230 argc, argv,
fb6becb4 231 &class_pam,
49ebd11f 232 &type_pam,
5a330cda
ZJS
233 &debug) < 0)
234 return PAM_SESSION_ERR;
74fe1fe3 235
baae0358 236 if (debug)
3831838a 237 pam_syslog(handle, LOG_DEBUG, "pam-systemd initializing");
baae0358 238
98a28fef 239 r = get_user_data(handle, &username, &pw);
5a330cda
ZJS
240 if (r != PAM_SUCCESS) {
241 pam_syslog(handle, LOG_ERR, "Failed to get user data.");
242 return r;
243 }
8c6db833 244
30b2c336
LP
245 /* Make sure we don't enter a loop by talking to
246 * systemd-logind when it is actually waiting for the
247 * background to finish start-up. If the service is
5c390a4a 248 * "systemd-user" we simply set XDG_RUNTIME_DIR and
30b2c336
LP
249 * leave. */
250
251 pam_get_item(handle, PAM_SERVICE, (const void**) &service);
5c390a4a 252 if (streq_ptr(service, "systemd-user")) {
05a049cc 253 _cleanup_free_ char *p = NULL, *rt = NULL;
30b2c336 254
de0671ee 255 if (asprintf(&p, "/run/systemd/users/"UID_FMT, pw->pw_uid) < 0)
ffcfcb6b 256 return PAM_BUF_ERR;
30b2c336
LP
257
258 r = parse_env_file(p, NEWLINE,
259 "RUNTIME", &rt,
260 NULL);
ffcfcb6b
ZJS
261 if (r < 0 && r != -ENOENT)
262 return PAM_SESSION_ERR;
30b2c336
LP
263
264 if (rt) {
265 r = pam_misc_setenv(handle, "XDG_RUNTIME_DIR", rt, 0);
30b2c336
LP
266 if (r != PAM_SUCCESS) {
267 pam_syslog(handle, LOG_ERR, "Failed to set runtime dir.");
ffcfcb6b 268 return r;
30b2c336 269 }
8b255ecd
KS
270
271 r = export_legacy_dbus_address(handle, pw->pw_uid, rt);
272 if (r != PAM_SUCCESS)
273 return r;
30b2c336
LP
274 }
275
ffcfcb6b 276 return PAM_SUCCESS;
8c6db833
LP
277 }
278
ffcfcb6b 279 /* Otherwise, we ask logind to create a session for us */
8c6db833 280
98a28fef
LP
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);
a8573ccc 285
bbc73283 286 seat = pam_getenv(handle, "XDG_SEAT");
a8573ccc
LP
287 if (isempty(seat))
288 seat = getenv("XDG_SEAT");
289
bbc73283 290 cvtnr = pam_getenv(handle, "XDG_VTNR");
a8573ccc
LP
291 if (isempty(cvtnr))
292 cvtnr = getenv("XDG_VTNR");
98a28fef 293
49ebd11f
LP
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
a4cd87e9
LP
306 desktop = pam_getenv(handle, "XDG_SESSION_DESKTOP");
307 if (isempty(desktop))
308 desktop = getenv("XDG_SESSION_DESKTOP");
309
ed18b08b 310 tty = strempty(tty);
98a28fef 311
ee8545b0 312 if (strchr(tty, ':')) {
e2acb67b
LP
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. */
ee8545b0
LP
317
318 if (isempty(display))
319 display = tty;
a4cd87e9 320 tty = NULL;
1a4459d6 321 } else if (streq(tty, "cron")) {
0ad1271f
LP
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. */
0ad1271f 325 type = "unspecified";
49ebd11f 326 class = "background";
a4cd87e9 327 tty = NULL;
0ad1271f
LP
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. */
0ad1271f 332 type ="tty";
49ebd11f 333 class = "user";
a4cd87e9 334 tty = NULL;
ee8545b0
LP
335 }
336
8e7705e5 337 /* If this fails vtnr will be 0, that's intended */
4d6d6518
LP
338 if (!isempty(cvtnr))
339 safe_atou32(cvtnr, &vtnr);
340
92bd5ff3 341 if (!isempty(display) && !vtnr) {
fc7985ed 342 if (isempty(seat))
6ef25fb6 343 get_seat_from_display(display, &seat, &vtnr);
fc7985ed 344 else if (streq(seat, "seat0"))
6ef25fb6 345 get_seat_from_display(display, NULL, &vtnr);
fc7985ed 346 }
4d6d6518 347
49ebd11f 348 if (seat && !streq(seat, "seat0") && vtnr != 0) {
1fa2f38f 349 pam_syslog(handle, LOG_DEBUG, "Ignoring vtnr %"PRIu32" for %s which is not seat0", vtnr, seat);
d7353ef6
MM
350 vtnr = 0;
351 }
352
49ebd11f 353 if (isempty(type))
0ad1271f 354 type = !isempty(display) ? "x11" :
49ebd11f 355 !isempty(tty) ? "tty" : "unspecified";
98a28fef 356
55efac6c 357 if (isempty(class))
e2acb67b 358 class = streq(type, "unspecified") ? "background" : "user";
55efac6c 359
fecc80c1 360 remote = !isempty(remote_host) && !is_localhost(remote_host);
98a28fef 361
4d49b48c 362 /* Talk to logind over the message bus */
5a330cda 363
ffcfcb6b
ZJS
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;
cc377381
LP
368 }
369
ce959314
MS
370 if (debug)
371 pam_syslog(handle, LOG_DEBUG, "Asking logind to create session: "
1fa2f38f 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",
baae0358
LP
373 pw->pw_uid, getpid(),
374 strempty(service),
cda7ecb0 375 type, class, strempty(desktop),
a4cd87e9 376 strempty(seat), vtnr, strempty(tty), strempty(display),
baae0358 377 yes_no(remote), strempty(remote_user), strempty(remote_host));
ce959314 378
ffcfcb6b
ZJS
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,
a4cd87e9 386 "uusssssussbssa(sv)",
baae0358
LP
387 (uint32_t) pw->pw_uid,
388 (uint32_t) getpid(),
a4cd87e9 389 service,
ffcfcb6b
ZJS
390 type,
391 class,
a4cd87e9
LP
392 desktop,
393 seat,
ffcfcb6b
ZJS
394 vtnr,
395 tty,
a4cd87e9 396 display,
ffcfcb6b 397 remote,
a4cd87e9
LP
398 remote_user,
399 remote_host,
ffcfcb6b
ZJS
400 0);
401 if (r < 0) {
8159d91a 402 pam_syslog(handle, LOG_ERR, "Failed to create session: %s", bus_error_message(&error, r));
ffcfcb6b 403 return PAM_SYSTEM_ERR;
98a28fef 404 }
74fe1fe3 405
ffcfcb6b 406 r = sd_bus_message_read(reply,
baae0358 407 "soshusub",
ffcfcb6b
ZJS
408 &id,
409 &object_path,
410 &runtime_path,
411 &session_fd,
baae0358 412 &original_uid,
ffcfcb6b
ZJS
413 &seat,
414 &vtnr,
415 &existing);
416 if (r < 0) {
417 pam_syslog(handle, LOG_ERR, "Failed to parse message: %s", strerror(-r));
5a330cda 418 return PAM_SESSION_ERR;
98a28fef 419 }
74fe1fe3 420
ce959314
MS
421 if (debug)
422 pam_syslog(handle, LOG_DEBUG, "Reply from logind: "
baae0358
LP
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);
ce959314 425
98a28fef
LP
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.");
5a330cda 429 return r;
8c6db833
LP
430 }
431
baae0358
LP
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 }
8b255ecd
KS
444
445 r = export_legacy_dbus_address(handle, pw->pw_uid, runtime_path);
446 if (r != PAM_SUCCESS)
447 return r;
98a28fef 448 }
8c6db833 449
bbc73283
LP
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.");
5a330cda 454 return r;
bbc73283
LP
455 }
456 }
457
458 if (vtnr > 0) {
29d230f6 459 char buf[DECIMAL_STR_MAX(vtnr)];
baae0358 460 sprintf(buf, "%u", vtnr);
bbc73283
LP
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.");
5a330cda 465 return r;
bbc73283
LP
466 }
467 }
468
77085881
LP
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.");
5a330cda 472 return r;
77085881
LP
473 }
474
21c390cc 475 if (session_fd >= 0) {
85c08dc0 476 session_fd = fcntl(session_fd, F_DUPFD_CLOEXEC, 3);
5a330cda
ZJS
477 if (session_fd < 0) {
478 pam_syslog(handle, LOG_ERR, "Failed to dup session fd: %m");
479 return PAM_SESSION_ERR;
480 }
481
21c390cc
LP
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.");
03e334a1 485 safe_close(session_fd);
5a330cda 486 return r;
21c390cc 487 }
98a28fef 488 }
8c6db833 489
ffcfcb6b 490 return PAM_SUCCESS;
98a28fef 491}
8c6db833 492
98a28fef
LP
493_public_ PAM_EXTERN int pam_sm_close_session(
494 pam_handle_t *handle,
495 int flags,
496 int argc, const char **argv) {
8c6db833 497
29d230f6 498 _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
24996861 499 _cleanup_bus_close_unref_ sd_bus *bus = NULL;
5f41d1f1 500 const void *existing = NULL;
75c8e3cf 501 const char *id;
75c8e3cf 502 int r;
8c6db833 503
ffcfcb6b 504 assert(handle);
75c8e3cf 505
77085881
LP
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
75c8e3cf 510 id = pam_getenv(handle, "XDG_SESSION_ID");
77085881 511 if (id && !existing) {
75c8e3cf
LP
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
ffcfcb6b
ZJS
518 r = sd_bus_open_system(&bus);
519 if (r < 0) {
5f41d1f1
LP
520 pam_syslog(handle, LOG_ERR, "Failed to connect to system bus: %s", strerror(-r));
521 return PAM_SESSION_ERR;
75c8e3cf
LP
522 }
523
ffcfcb6b
ZJS
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) {
5f41d1f1
LP
534 pam_syslog(handle, LOG_ERR, "Failed to release session: %s", bus_error_message(&error, r));
535 return PAM_SESSION_ERR;
75c8e3cf
LP
536 }
537 }
538
5f41d1f1
LP
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. */
75c8e3cf 544
5f41d1f1 545 return PAM_SUCCESS;
8c6db833 546}