]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/login/pam_systemd.c
Merge pull request #509 from dvdhrm/logind
[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
b80120c4 34#include "bus-common-errors.h"
8c6db833 35#include "util.h"
d7832d2c 36#include "audit.h"
8c6db833 37#include "macro.h"
74fe1fe3 38#include "strv.h"
ffcfcb6b 39#include "bus-util.h"
98a28fef 40#include "def.h"
4d6d6518 41#include "socket-util.h"
a5c32cff 42#include "fileio.h"
8159d91a 43#include "bus-error.h"
6482f626 44#include "formats-util.h"
288a74cc 45#include "terminal-util.h"
958b66ea 46#include "hostname-util.h"
8c6db833 47
baae0358
LP
48static int parse_argv(
49 pam_handle_t *handle,
50 int argc, const char **argv,
51 const char **class,
49ebd11f 52 const char **type,
baae0358 53 bool *debug) {
8c6db833
LP
54
55 unsigned i;
56
57 assert(argc >= 0);
58 assert(argc == 0 || argv);
59
49ebd11f 60 for (i = 0; i < (unsigned) argc; i++) {
fb6becb4 61 if (startswith(argv[i], "class=")) {
485507b8
MM
62 if (class)
63 *class = argv[i] + 6;
64
49ebd11f
LP
65 } else if (startswith(argv[i], "type=")) {
66 if (type)
67 *type = argv[i] + 5;
68
05a049cc
ZJS
69 } else if (streq(argv[i], "debug")) {
70 if (debug)
71 *debug = true;
fb6becb4 72
05a049cc
ZJS
73 } else if (startswith(argv[i], "debug=")) {
74 int k;
0e318cad 75
05a049cc
ZJS
76 k = parse_boolean(argv[i] + 6);
77 if (k < 0)
78 pam_syslog(handle, LOG_WARNING, "Failed to parse debug= argument, ignoring.");
79 else if (debug)
0e318cad
MS
80 *debug = k;
81
05a049cc 82 } else
fb6becb4 83 pam_syslog(handle, LOG_WARNING, "Unknown parameter '%s', ignoring", argv[i]);
49ebd11f 84 }
8c6db833 85
8c6db833
LP
86 return 0;
87}
88
8c6db833
LP
89static int get_user_data(
90 pam_handle_t *handle,
91 const char **ret_username,
92 struct passwd **ret_pw) {
93
d90b9d27
LP
94 const char *username = NULL;
95 struct passwd *pw = NULL;
8c6db833
LP
96 int r;
97
98 assert(handle);
99 assert(ret_username);
100 assert(ret_pw);
101
baae0358
LP
102 r = pam_get_user(handle, &username, NULL);
103 if (r != PAM_SUCCESS) {
104 pam_syslog(handle, LOG_ERR, "Failed to get user name.");
105 return r;
106 }
d90b9d27 107
baae0358
LP
108 if (isempty(username)) {
109 pam_syslog(handle, LOG_ERR, "User name not valid.");
110 return PAM_AUTH_ERR;
8c6db833
LP
111 }
112
baae0358 113 pw = pam_modutil_getpwnam(handle, username);
d90b9d27 114 if (!pw) {
8c6db833
LP
115 pam_syslog(handle, LOG_ERR, "Failed to get user data.");
116 return PAM_USER_UNKNOWN;
117 }
118
119 *ret_pw = pw;
8e24a4f8 120 *ret_username = username;
8c6db833
LP
121
122 return PAM_SUCCESS;
123}
124
4d6d6518 125static int get_seat_from_display(const char *display, const char **seat, uint32_t *vtnr) {
b92bea5d
ZJS
126 union sockaddr_union sa = {
127 .un.sun_family = AF_UNIX,
128 };
baae0358
LP
129 _cleanup_free_ char *p = NULL, *tty = NULL;
130 _cleanup_close_ int fd = -1;
4d6d6518 131 struct ucred ucred;
baae0358 132 int v, r;
4d6d6518
LP
133
134 assert(display);
4d6d6518
LP
135 assert(vtnr);
136
137 /* We deduce the X11 socket from the display name, then use
138 * SO_PEERCRED to determine the X11 server process, ask for
139 * the controlling tty of that and if it's a VC then we know
140 * the seat and the virtual terminal. Sounds ugly, is only
141 * semi-ugly. */
142
143 r = socket_from_display(display, &p);
144 if (r < 0)
145 return r;
b92bea5d 146 strncpy(sa.un.sun_path, p, sizeof(sa.un.sun_path)-1);
4d6d6518
LP
147
148 fd = socket(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC, 0);
b92bea5d 149 if (fd < 0)
4d6d6518 150 return -errno;
4d6d6518 151
b92bea5d 152 if (connect(fd, &sa.sa, offsetof(struct sockaddr_un, sun_path) + strlen(sa.un.sun_path)) < 0)
4d6d6518 153 return -errno;
4d6d6518 154
eff05270 155 r = getpeercred(fd, &ucred);
4d6d6518 156 if (r < 0)
eff05270 157 return r;
4d6d6518
LP
158
159 r = get_ctty(ucred.pid, NULL, &tty);
160 if (r < 0)
161 return r;
162
163 v = vtnr_from_tty(tty);
4d6d6518
LP
164 if (v < 0)
165 return v;
166 else if (v == 0)
167 return -ENOENT;
168
fc7985ed
LP
169 if (seat)
170 *seat = "seat0";
4d6d6518
LP
171 *vtnr = (uint32_t) v;
172
173 return 0;
174}
175
8b255ecd
KS
176static int export_legacy_dbus_address(
177 pam_handle_t *handle,
178 uid_t uid,
179 const char *runtime) {
180
8b255ecd
KS
181 _cleanup_free_ char *s = NULL;
182 int r;
183
8042e377 184 /* skip export if kdbus is not active */
c5d452bb 185 if (!is_kdbus_available())
8042e377
KS
186 return PAM_SUCCESS;
187
e3afaf6b 188 if (asprintf(&s, KERNEL_USER_BUS_ADDRESS_FMT ";" UNIX_USER_BUS_ADDRESS_FMT, uid, runtime) < 0) {
8b255ecd
KS
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 }
1b09f548 198
8b255ecd
KS
199 return PAM_SUCCESS;
200}
201
98a28fef 202_public_ PAM_EXTERN int pam_sm_open_session(
8c6db833
LP
203 pam_handle_t *handle,
204 int flags,
205 int argc, const char **argv) {
206
ffcfcb6b
ZJS
207 _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
208 _cleanup_bus_message_unref_ sd_bus_message *reply = NULL;
d1529c9e
LP
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,
a4cd87e9 216 *class_pam = NULL, *type_pam = NULL, *cvtnr = NULL, *desktop = NULL;
03976f7b 217 _cleanup_bus_flush_close_unref_ sd_bus *bus = NULL;
d1529c9e 218 int session_fd = -1, existing, r;
d1529c9e
LP
219 bool debug = false, remote;
220 struct passwd *pw;
baae0358
LP
221 uint32_t vtnr = 0;
222 uid_t original_uid;
8c6db833 223
ffcfcb6b 224 assert(handle);
98a28fef 225
79d860fe
MP
226 /* Make this a NOP on non-logind systems */
227 if (!logind_running())
8c6db833
LP
228 return PAM_SUCCESS;
229
e9fbc77c
LP
230 if (parse_argv(handle,
231 argc, argv,
fb6becb4 232 &class_pam,
49ebd11f 233 &type_pam,
5a330cda
ZJS
234 &debug) < 0)
235 return PAM_SESSION_ERR;
74fe1fe3 236
baae0358 237 if (debug)
3831838a 238 pam_syslog(handle, LOG_DEBUG, "pam-systemd initializing");
baae0358 239
98a28fef 240 r = get_user_data(handle, &username, &pw);
5a330cda
ZJS
241 if (r != PAM_SUCCESS) {
242 pam_syslog(handle, LOG_ERR, "Failed to get user data.");
243 return r;
244 }
8c6db833 245
30b2c336
LP
246 /* Make sure we don't enter a loop by talking to
247 * systemd-logind when it is actually waiting for the
248 * background to finish start-up. If the service is
5c390a4a 249 * "systemd-user" we simply set XDG_RUNTIME_DIR and
30b2c336
LP
250 * leave. */
251
252 pam_get_item(handle, PAM_SERVICE, (const void**) &service);
5c390a4a 253 if (streq_ptr(service, "systemd-user")) {
05a049cc 254 _cleanup_free_ char *p = NULL, *rt = NULL;
30b2c336 255
de0671ee 256 if (asprintf(&p, "/run/systemd/users/"UID_FMT, pw->pw_uid) < 0)
ffcfcb6b 257 return PAM_BUF_ERR;
30b2c336
LP
258
259 r = parse_env_file(p, NEWLINE,
260 "RUNTIME", &rt,
261 NULL);
ffcfcb6b
ZJS
262 if (r < 0 && r != -ENOENT)
263 return PAM_SESSION_ERR;
30b2c336
LP
264
265 if (rt) {
266 r = pam_misc_setenv(handle, "XDG_RUNTIME_DIR", rt, 0);
30b2c336
LP
267 if (r != PAM_SUCCESS) {
268 pam_syslog(handle, LOG_ERR, "Failed to set runtime dir.");
ffcfcb6b 269 return r;
30b2c336 270 }
8b255ecd
KS
271
272 r = export_legacy_dbus_address(handle, pw->pw_uid, rt);
273 if (r != PAM_SUCCESS)
274 return r;
30b2c336
LP
275 }
276
ffcfcb6b 277 return PAM_SUCCESS;
8c6db833
LP
278 }
279
ffcfcb6b 280 /* Otherwise, we ask logind to create a session for us */
8c6db833 281
98a28fef
LP
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);
a8573ccc 286
bbc73283 287 seat = pam_getenv(handle, "XDG_SEAT");
a8573ccc
LP
288 if (isempty(seat))
289 seat = getenv("XDG_SEAT");
290
bbc73283 291 cvtnr = pam_getenv(handle, "XDG_VTNR");
a8573ccc
LP
292 if (isempty(cvtnr))
293 cvtnr = getenv("XDG_VTNR");
98a28fef 294
49ebd11f
LP
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
a4cd87e9
LP
307 desktop = pam_getenv(handle, "XDG_SESSION_DESKTOP");
308 if (isempty(desktop))
309 desktop = getenv("XDG_SESSION_DESKTOP");
310
ed18b08b 311 tty = strempty(tty);
98a28fef 312
ee8545b0 313 if (strchr(tty, ':')) {
e2acb67b
LP
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. */
ee8545b0
LP
318
319 if (isempty(display))
320 display = tty;
a4cd87e9 321 tty = NULL;
1a4459d6 322 } else if (streq(tty, "cron")) {
0ad1271f
LP
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. */
0ad1271f 326 type = "unspecified";
49ebd11f 327 class = "background";
a4cd87e9 328 tty = NULL;
0ad1271f
LP
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. */
0ad1271f 333 type ="tty";
49ebd11f 334 class = "user";
a4cd87e9 335 tty = NULL;
ee8545b0
LP
336 }
337
8e7705e5 338 /* If this fails vtnr will be 0, that's intended */
4d6d6518 339 if (!isempty(cvtnr))
2ae4842b 340 (void) safe_atou32(cvtnr, &vtnr);
4d6d6518 341
92bd5ff3 342 if (!isempty(display) && !vtnr) {
fc7985ed 343 if (isempty(seat))
6ef25fb6 344 get_seat_from_display(display, &seat, &vtnr);
fc7985ed 345 else if (streq(seat, "seat0"))
6ef25fb6 346 get_seat_from_display(display, NULL, &vtnr);
fc7985ed 347 }
4d6d6518 348
49ebd11f 349 if (seat && !streq(seat, "seat0") && vtnr != 0) {
1fa2f38f 350 pam_syslog(handle, LOG_DEBUG, "Ignoring vtnr %"PRIu32" for %s which is not seat0", vtnr, seat);
d7353ef6
MM
351 vtnr = 0;
352 }
353
49ebd11f 354 if (isempty(type))
0ad1271f 355 type = !isempty(display) ? "x11" :
49ebd11f 356 !isempty(tty) ? "tty" : "unspecified";
98a28fef 357
55efac6c 358 if (isempty(class))
e2acb67b 359 class = streq(type, "unspecified") ? "background" : "user";
55efac6c 360
fecc80c1 361 remote = !isempty(remote_host) && !is_localhost(remote_host);
98a28fef 362
4d49b48c 363 /* Talk to logind over the message bus */
5a330cda 364
ffcfcb6b
ZJS
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;
cc377381
LP
369 }
370
ce959314
MS
371 if (debug)
372 pam_syslog(handle, LOG_DEBUG, "Asking logind to create session: "
1fa2f38f 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",
baae0358
LP
374 pw->pw_uid, getpid(),
375 strempty(service),
cda7ecb0 376 type, class, strempty(desktop),
a4cd87e9 377 strempty(seat), vtnr, strempty(tty), strempty(display),
baae0358 378 yes_no(remote), strempty(remote_user), strempty(remote_host));
ce959314 379
ffcfcb6b
ZJS
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,
a4cd87e9 387 "uusssssussbssa(sv)",
baae0358
LP
388 (uint32_t) pw->pw_uid,
389 (uint32_t) getpid(),
a4cd87e9 390 service,
ffcfcb6b
ZJS
391 type,
392 class,
a4cd87e9
LP
393 desktop,
394 seat,
ffcfcb6b
ZJS
395 vtnr,
396 tty,
a4cd87e9 397 display,
ffcfcb6b 398 remote,
a4cd87e9
LP
399 remote_user,
400 remote_host,
ffcfcb6b
ZJS
401 0);
402 if (r < 0) {
b80120c4
DH
403 if (sd_bus_error_has_name(&error, BUS_ERROR_SESSION_BUSY)) {
404 pam_syslog(handle, LOG_DEBUG, "Cannot create session: %s", bus_error_message(&error, r));
405 return PAM_SUCCESS;
406 } else {
407 pam_syslog(handle, LOG_ERR, "Failed to create session: %s", bus_error_message(&error, r));
408 return PAM_SYSTEM_ERR;
409 }
98a28fef 410 }
74fe1fe3 411
ffcfcb6b 412 r = sd_bus_message_read(reply,
baae0358 413 "soshusub",
ffcfcb6b
ZJS
414 &id,
415 &object_path,
416 &runtime_path,
417 &session_fd,
baae0358 418 &original_uid,
ffcfcb6b
ZJS
419 &seat,
420 &vtnr,
421 &existing);
422 if (r < 0) {
423 pam_syslog(handle, LOG_ERR, "Failed to parse message: %s", strerror(-r));
5a330cda 424 return PAM_SESSION_ERR;
98a28fef 425 }
74fe1fe3 426
ce959314
MS
427 if (debug)
428 pam_syslog(handle, LOG_DEBUG, "Reply from logind: "
baae0358
LP
429 "id=%s object_path=%s runtime_path=%s session_fd=%d seat=%s vtnr=%u original_uid=%u",
430 id, object_path, runtime_path, session_fd, seat, vtnr, original_uid);
ce959314 431
98a28fef
LP
432 r = pam_misc_setenv(handle, "XDG_SESSION_ID", id, 0);
433 if (r != PAM_SUCCESS) {
434 pam_syslog(handle, LOG_ERR, "Failed to set session id.");
5a330cda 435 return r;
8c6db833
LP
436 }
437
baae0358
LP
438 if (original_uid == pw->pw_uid) {
439 /* Don't set $XDG_RUNTIME_DIR if the user we now
440 * authenticated for does not match the original user
441 * of the session. We do this in order not to result
442 * in privileged apps clobbering the runtime directory
443 * unnecessarily. */
444
445 r = pam_misc_setenv(handle, "XDG_RUNTIME_DIR", runtime_path, 0);
446 if (r != PAM_SUCCESS) {
447 pam_syslog(handle, LOG_ERR, "Failed to set runtime dir.");
448 return r;
449 }
8b255ecd
KS
450
451 r = export_legacy_dbus_address(handle, pw->pw_uid, runtime_path);
452 if (r != PAM_SUCCESS)
453 return r;
98a28fef 454 }
8c6db833 455
bbc73283
LP
456 if (!isempty(seat)) {
457 r = pam_misc_setenv(handle, "XDG_SEAT", seat, 0);
458 if (r != PAM_SUCCESS) {
459 pam_syslog(handle, LOG_ERR, "Failed to set seat.");
5a330cda 460 return r;
bbc73283
LP
461 }
462 }
463
464 if (vtnr > 0) {
29d230f6 465 char buf[DECIMAL_STR_MAX(vtnr)];
baae0358 466 sprintf(buf, "%u", vtnr);
bbc73283
LP
467
468 r = pam_misc_setenv(handle, "XDG_VTNR", buf, 0);
469 if (r != PAM_SUCCESS) {
470 pam_syslog(handle, LOG_ERR, "Failed to set virtual terminal number.");
5a330cda 471 return r;
bbc73283
LP
472 }
473 }
474
77085881
LP
475 r = pam_set_data(handle, "systemd.existing", INT_TO_PTR(!!existing), NULL);
476 if (r != PAM_SUCCESS) {
477 pam_syslog(handle, LOG_ERR, "Failed to install existing flag.");
5a330cda 478 return r;
77085881
LP
479 }
480
21c390cc 481 if (session_fd >= 0) {
85c08dc0 482 session_fd = fcntl(session_fd, F_DUPFD_CLOEXEC, 3);
5a330cda
ZJS
483 if (session_fd < 0) {
484 pam_syslog(handle, LOG_ERR, "Failed to dup session fd: %m");
485 return PAM_SESSION_ERR;
486 }
487
21c390cc
LP
488 r = pam_set_data(handle, "systemd.session-fd", INT_TO_PTR(session_fd+1), NULL);
489 if (r != PAM_SUCCESS) {
490 pam_syslog(handle, LOG_ERR, "Failed to install session fd.");
03e334a1 491 safe_close(session_fd);
5a330cda 492 return r;
21c390cc 493 }
98a28fef 494 }
8c6db833 495
ffcfcb6b 496 return PAM_SUCCESS;
98a28fef 497}
8c6db833 498
98a28fef
LP
499_public_ PAM_EXTERN int pam_sm_close_session(
500 pam_handle_t *handle,
501 int flags,
502 int argc, const char **argv) {
8c6db833 503
29d230f6 504 _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
03976f7b 505 _cleanup_bus_flush_close_unref_ sd_bus *bus = NULL;
5f41d1f1 506 const void *existing = NULL;
75c8e3cf 507 const char *id;
75c8e3cf 508 int r;
8c6db833 509
ffcfcb6b 510 assert(handle);
75c8e3cf 511
77085881
LP
512 /* Only release session if it wasn't pre-existing when we
513 * tried to create it */
514 pam_get_data(handle, "systemd.existing", &existing);
515
75c8e3cf 516 id = pam_getenv(handle, "XDG_SESSION_ID");
77085881 517 if (id && !existing) {
75c8e3cf
LP
518
519 /* Before we go and close the FIFO we need to tell
520 * logind that this is a clean session shutdown, so
521 * that it doesn't just go and slaughter us
522 * immediately after closing the fd */
523
ffcfcb6b
ZJS
524 r = sd_bus_open_system(&bus);
525 if (r < 0) {
5f41d1f1
LP
526 pam_syslog(handle, LOG_ERR, "Failed to connect to system bus: %s", strerror(-r));
527 return PAM_SESSION_ERR;
75c8e3cf
LP
528 }
529
ffcfcb6b
ZJS
530 r = sd_bus_call_method(bus,
531 "org.freedesktop.login1",
532 "/org/freedesktop/login1",
533 "org.freedesktop.login1.Manager",
534 "ReleaseSession",
535 &error,
536 NULL,
537 "s",
538 id);
539 if (r < 0) {
5f41d1f1
LP
540 pam_syslog(handle, LOG_ERR, "Failed to release session: %s", bus_error_message(&error, r));
541 return PAM_SESSION_ERR;
75c8e3cf
LP
542 }
543 }
544
5f41d1f1
LP
545 /* Note that we are knowingly leaking the FIFO fd here. This
546 * way, logind can watch us die. If we closed it here it would
547 * not have any clue when that is completed. Given that one
548 * cannot really have multiple PAM sessions open from the same
549 * process this means we will leak one FD at max. */
75c8e3cf 550
5f41d1f1 551 return PAM_SUCCESS;
8c6db833 552}