]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/login/pam_systemd.c
Add SPDX license identifiers to source files under the LGPL
[thirdparty/systemd.git] / src / login / pam_systemd.c
CommitLineData
53e1b683 1/* SPDX-License-Identifier: LGPL-2.1+ */
8c6db833
LP
2/***
3 This file is part of systemd.
4
5 Copyright 2010 Lennart Poettering
6
7 systemd is free software; you can redistribute it and/or modify it
5430f7f2
LP
8 under the terms of the GNU Lesser General Public License as published by
9 the Free Software Foundation; either version 2.1 of the License, or
8c6db833
LP
10 (at your option) any later version.
11
12 systemd is distributed in the hope that it will be useful, but
13 WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
5430f7f2 15 Lesser General Public License for more details.
8c6db833 16
5430f7f2 17 You should have received a copy of the GNU Lesser General Public License
8c6db833
LP
18 along with systemd; If not, see <http://www.gnu.org/licenses/>.
19***/
20
00229fe4 21#include <endian.h>
8c6db833
LP
22#include <errno.h>
23#include <fcntl.h>
8c6db833 24#include <pwd.h>
8c6db833 25#include <security/_pam_macros.h>
8c6db833
LP
26#include <security/pam_ext.h>
27#include <security/pam_misc.h>
00229fe4
LP
28#include <security/pam_modules.h>
29#include <security/pam_modutil.h>
30#include <sys/file.h>
8c6db833 31
b5efdb8a 32#include "alloc-util.h"
430f0182 33#include "audit-util.h"
00229fe4
LP
34#include "bus-common-errors.h"
35#include "bus-error.h"
ffcfcb6b 36#include "bus-util.h"
98a28fef 37#include "def.h"
3ffd4af2 38#include "fd-util.h"
a5c32cff 39#include "fileio.h"
f97b34a6 40#include "format-util.h"
958b66ea 41#include "hostname-util.h"
00229fe4
LP
42#include "login-util.h"
43#include "macro.h"
6bedfcbb 44#include "parse-util.h"
df0ff127 45#include "process-util.h"
00229fe4
LP
46#include "socket-util.h"
47#include "strv.h"
48#include "terminal-util.h"
49#include "util.h"
c9ed61e7 50#include "path-util.h"
8c6db833 51
baae0358
LP
52static int parse_argv(
53 pam_handle_t *handle,
54 int argc, const char **argv,
55 const char **class,
49ebd11f 56 const char **type,
baae0358 57 bool *debug) {
8c6db833
LP
58
59 unsigned i;
60
61 assert(argc >= 0);
62 assert(argc == 0 || argv);
63
49ebd11f 64 for (i = 0; i < (unsigned) argc; i++) {
fb6becb4 65 if (startswith(argv[i], "class=")) {
485507b8
MM
66 if (class)
67 *class = argv[i] + 6;
68
49ebd11f
LP
69 } else if (startswith(argv[i], "type=")) {
70 if (type)
71 *type = argv[i] + 5;
72
05a049cc
ZJS
73 } else if (streq(argv[i], "debug")) {
74 if (debug)
75 *debug = true;
fb6becb4 76
05a049cc
ZJS
77 } else if (startswith(argv[i], "debug=")) {
78 int k;
0e318cad 79
05a049cc
ZJS
80 k = parse_boolean(argv[i] + 6);
81 if (k < 0)
82 pam_syslog(handle, LOG_WARNING, "Failed to parse debug= argument, ignoring.");
83 else if (debug)
0e318cad
MS
84 *debug = k;
85
05a049cc 86 } else
fb6becb4 87 pam_syslog(handle, LOG_WARNING, "Unknown parameter '%s', ignoring", argv[i]);
49ebd11f 88 }
8c6db833 89
8c6db833
LP
90 return 0;
91}
92
8c6db833
LP
93static int get_user_data(
94 pam_handle_t *handle,
95 const char **ret_username,
96 struct passwd **ret_pw) {
97
d90b9d27
LP
98 const char *username = NULL;
99 struct passwd *pw = NULL;
8c6db833
LP
100 int r;
101
102 assert(handle);
103 assert(ret_username);
104 assert(ret_pw);
105
baae0358
LP
106 r = pam_get_user(handle, &username, NULL);
107 if (r != PAM_SUCCESS) {
108 pam_syslog(handle, LOG_ERR, "Failed to get user name.");
109 return r;
110 }
d90b9d27 111
baae0358
LP
112 if (isempty(username)) {
113 pam_syslog(handle, LOG_ERR, "User name not valid.");
114 return PAM_AUTH_ERR;
8c6db833
LP
115 }
116
baae0358 117 pw = pam_modutil_getpwnam(handle, username);
d90b9d27 118 if (!pw) {
8c6db833
LP
119 pam_syslog(handle, LOG_ERR, "Failed to get user data.");
120 return PAM_USER_UNKNOWN;
121 }
122
123 *ret_pw = pw;
8e24a4f8 124 *ret_username = username;
8c6db833
LP
125
126 return PAM_SUCCESS;
127}
128
4d6d6518 129static int get_seat_from_display(const char *display, const char **seat, uint32_t *vtnr) {
b92bea5d
ZJS
130 union sockaddr_union sa = {
131 .un.sun_family = AF_UNIX,
132 };
baae0358
LP
133 _cleanup_free_ char *p = NULL, *tty = NULL;
134 _cleanup_close_ int fd = -1;
4d6d6518 135 struct ucred ucred;
baae0358 136 int v, r;
4d6d6518
LP
137
138 assert(display);
4d6d6518
LP
139 assert(vtnr);
140
141 /* We deduce the X11 socket from the display name, then use
142 * SO_PEERCRED to determine the X11 server process, ask for
143 * the controlling tty of that and if it's a VC then we know
144 * the seat and the virtual terminal. Sounds ugly, is only
145 * semi-ugly. */
146
147 r = socket_from_display(display, &p);
148 if (r < 0)
149 return r;
b92bea5d 150 strncpy(sa.un.sun_path, p, sizeof(sa.un.sun_path)-1);
4d6d6518
LP
151
152 fd = socket(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC, 0);
b92bea5d 153 if (fd < 0)
4d6d6518 154 return -errno;
4d6d6518 155
fc2fffe7 156 if (connect(fd, &sa.sa, SOCKADDR_UN_LEN(sa.un)) < 0)
4d6d6518 157 return -errno;
4d6d6518 158
eff05270 159 r = getpeercred(fd, &ucred);
4d6d6518 160 if (r < 0)
eff05270 161 return r;
4d6d6518
LP
162
163 r = get_ctty(ucred.pid, NULL, &tty);
164 if (r < 0)
165 return r;
166
167 v = vtnr_from_tty(tty);
4d6d6518
LP
168 if (v < 0)
169 return v;
170 else if (v == 0)
171 return -ENOENT;
172
fc7985ed
LP
173 if (seat)
174 *seat = "seat0";
4d6d6518
LP
175 *vtnr = (uint32_t) v;
176
177 return 0;
178}
179
8b255ecd
KS
180static int export_legacy_dbus_address(
181 pam_handle_t *handle,
182 uid_t uid,
183 const char *runtime) {
184
8b255ecd 185 _cleanup_free_ char *s = NULL;
3df49c28
DH
186 int r = PAM_BUF_ERR;
187
222953e8
DR
188 /* FIXME: We *really* should move the access() check into the
189 * daemons that spawn dbus-daemon, instead of forcing
190 * DBUS_SESSION_BUS_ADDRESS= here. */
191
605405c6 192 s = strjoin(runtime, "/bus");
222953e8
DR
193 if (!s)
194 goto error;
195
196 if (access(s, F_OK) < 0)
197 return PAM_SUCCESS;
198
199 s = mfree(s);
200 if (asprintf(&s, UNIX_USER_BUS_ADDRESS_FMT, runtime) < 0)
201 goto error;
8b255ecd
KS
202
203 r = pam_misc_setenv(handle, "DBUS_SESSION_BUS_ADDRESS", s, 0);
3df49c28
DH
204 if (r != PAM_SUCCESS)
205 goto error;
1b09f548 206
8b255ecd 207 return PAM_SUCCESS;
3df49c28
DH
208
209error:
210 pam_syslog(handle, LOG_ERR, "Failed to set bus variable.");
211 return r;
8b255ecd
KS
212}
213
98a28fef 214_public_ PAM_EXTERN int pam_sm_open_session(
8c6db833
LP
215 pam_handle_t *handle,
216 int flags,
217 int argc, const char **argv) {
218
4afd3348
LP
219 _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
220 _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
d1529c9e
LP
221 const char
222 *username, *id, *object_path, *runtime_path,
223 *service = NULL,
224 *tty = NULL, *display = NULL,
225 *remote_user = NULL, *remote_host = NULL,
226 *seat = NULL,
227 *type = NULL, *class = NULL,
a4cd87e9 228 *class_pam = NULL, *type_pam = NULL, *cvtnr = NULL, *desktop = NULL;
4afd3348 229 _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
d1529c9e 230 int session_fd = -1, existing, r;
d1529c9e
LP
231 bool debug = false, remote;
232 struct passwd *pw;
baae0358
LP
233 uint32_t vtnr = 0;
234 uid_t original_uid;
8c6db833 235
ffcfcb6b 236 assert(handle);
98a28fef 237
79d860fe
MP
238 /* Make this a NOP on non-logind systems */
239 if (!logind_running())
8c6db833
LP
240 return PAM_SUCCESS;
241
e9fbc77c
LP
242 if (parse_argv(handle,
243 argc, argv,
fb6becb4 244 &class_pam,
49ebd11f 245 &type_pam,
5a330cda
ZJS
246 &debug) < 0)
247 return PAM_SESSION_ERR;
74fe1fe3 248
baae0358 249 if (debug)
3831838a 250 pam_syslog(handle, LOG_DEBUG, "pam-systemd initializing");
baae0358 251
98a28fef 252 r = get_user_data(handle, &username, &pw);
5a330cda
ZJS
253 if (r != PAM_SUCCESS) {
254 pam_syslog(handle, LOG_ERR, "Failed to get user data.");
255 return r;
256 }
8c6db833 257
30b2c336
LP
258 /* Make sure we don't enter a loop by talking to
259 * systemd-logind when it is actually waiting for the
260 * background to finish start-up. If the service is
5c390a4a 261 * "systemd-user" we simply set XDG_RUNTIME_DIR and
30b2c336
LP
262 * leave. */
263
264 pam_get_item(handle, PAM_SERVICE, (const void**) &service);
5c390a4a 265 if (streq_ptr(service, "systemd-user")) {
f9e4283d 266 _cleanup_free_ char *rt = NULL;
30b2c336 267
f9e4283d 268 if (asprintf(&rt, "/run/user/"UID_FMT, pw->pw_uid) < 0)
ffcfcb6b 269 return PAM_BUF_ERR;
30b2c336 270
f9e4283d
DH
271 r = pam_misc_setenv(handle, "XDG_RUNTIME_DIR", rt, 0);
272 if (r != PAM_SUCCESS) {
273 pam_syslog(handle, LOG_ERR, "Failed to set runtime dir.");
274 return r;
30b2c336
LP
275 }
276
f9e4283d
DH
277 r = export_legacy_dbus_address(handle, pw->pw_uid, rt);
278 if (r != PAM_SUCCESS)
279 return r;
280
ffcfcb6b 281 return PAM_SUCCESS;
8c6db833
LP
282 }
283
ffcfcb6b 284 /* Otherwise, we ask logind to create a session for us */
8c6db833 285
98a28fef
LP
286 pam_get_item(handle, PAM_XDISPLAY, (const void**) &display);
287 pam_get_item(handle, PAM_TTY, (const void**) &tty);
288 pam_get_item(handle, PAM_RUSER, (const void**) &remote_user);
289 pam_get_item(handle, PAM_RHOST, (const void**) &remote_host);
a8573ccc 290
bbc73283 291 seat = pam_getenv(handle, "XDG_SEAT");
a8573ccc
LP
292 if (isempty(seat))
293 seat = getenv("XDG_SEAT");
294
bbc73283 295 cvtnr = pam_getenv(handle, "XDG_VTNR");
a8573ccc
LP
296 if (isempty(cvtnr))
297 cvtnr = getenv("XDG_VTNR");
98a28fef 298
49ebd11f
LP
299 type = pam_getenv(handle, "XDG_SESSION_TYPE");
300 if (isempty(type))
301 type = getenv("XDG_SESSION_TYPE");
302 if (isempty(type))
303 type = type_pam;
304
305 class = pam_getenv(handle, "XDG_SESSION_CLASS");
306 if (isempty(class))
307 class = getenv("XDG_SESSION_CLASS");
308 if (isempty(class))
309 class = class_pam;
310
a4cd87e9
LP
311 desktop = pam_getenv(handle, "XDG_SESSION_DESKTOP");
312 if (isempty(desktop))
313 desktop = getenv("XDG_SESSION_DESKTOP");
314
ed18b08b 315 tty = strempty(tty);
98a28fef 316
ee8545b0 317 if (strchr(tty, ':')) {
e2acb67b
LP
318 /* A tty with a colon is usually an X11 display,
319 * placed there to show up in utmp. We rearrange
320 * things and don't pretend that an X display was a
321 * tty. */
ee8545b0
LP
322
323 if (isempty(display))
324 display = tty;
a4cd87e9 325 tty = NULL;
1a4459d6 326 } else if (streq(tty, "cron")) {
0ad1271f
LP
327 /* cron has been setting PAM_TTY to "cron" for a very
328 * long time and it probably shouldn't stop doing that
329 * for compatibility reasons. */
0ad1271f 330 type = "unspecified";
49ebd11f 331 class = "background";
a4cd87e9 332 tty = NULL;
0ad1271f
LP
333 } else if (streq(tty, "ssh")) {
334 /* ssh has been setting PAM_TTY to "ssh" for a very
335 * long time and probably shouldn't stop doing that
336 * for compatibility reasons. */
0ad1271f 337 type ="tty";
49ebd11f 338 class = "user";
a4cd87e9 339 tty = NULL;
c9ed61e7
LP
340 } else
341 /* Chop off leading /dev prefix that some clients specify, but others do not. */
342 tty = skip_dev_prefix(tty);
ee8545b0 343
8e7705e5 344 /* If this fails vtnr will be 0, that's intended */
4d6d6518 345 if (!isempty(cvtnr))
2ae4842b 346 (void) safe_atou32(cvtnr, &vtnr);
4d6d6518 347
92bd5ff3 348 if (!isempty(display) && !vtnr) {
fc7985ed 349 if (isempty(seat))
6ef25fb6 350 get_seat_from_display(display, &seat, &vtnr);
fc7985ed 351 else if (streq(seat, "seat0"))
6ef25fb6 352 get_seat_from_display(display, NULL, &vtnr);
fc7985ed 353 }
4d6d6518 354
49ebd11f 355 if (seat && !streq(seat, "seat0") && vtnr != 0) {
1fa2f38f 356 pam_syslog(handle, LOG_DEBUG, "Ignoring vtnr %"PRIu32" for %s which is not seat0", vtnr, seat);
d7353ef6
MM
357 vtnr = 0;
358 }
359
49ebd11f 360 if (isempty(type))
0ad1271f 361 type = !isempty(display) ? "x11" :
49ebd11f 362 !isempty(tty) ? "tty" : "unspecified";
98a28fef 363
55efac6c 364 if (isempty(class))
e2acb67b 365 class = streq(type, "unspecified") ? "background" : "user";
55efac6c 366
fecc80c1 367 remote = !isempty(remote_host) && !is_localhost(remote_host);
98a28fef 368
4d49b48c 369 /* Talk to logind over the message bus */
5a330cda 370
ffcfcb6b
ZJS
371 r = sd_bus_open_system(&bus);
372 if (r < 0) {
373 pam_syslog(handle, LOG_ERR, "Failed to connect to system bus: %s", strerror(-r));
374 return PAM_SESSION_ERR;
cc377381
LP
375 }
376
ce959314
MS
377 if (debug)
378 pam_syslog(handle, LOG_DEBUG, "Asking logind to create session: "
1fa2f38f 379 "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",
df0ff127 380 pw->pw_uid, getpid_cached(),
baae0358 381 strempty(service),
cda7ecb0 382 type, class, strempty(desktop),
a4cd87e9 383 strempty(seat), vtnr, strempty(tty), strempty(display),
baae0358 384 yes_no(remote), strempty(remote_user), strempty(remote_host));
ce959314 385
ffcfcb6b
ZJS
386 r = sd_bus_call_method(bus,
387 "org.freedesktop.login1",
388 "/org/freedesktop/login1",
389 "org.freedesktop.login1.Manager",
390 "CreateSession",
391 &error,
392 &reply,
a4cd87e9 393 "uusssssussbssa(sv)",
baae0358 394 (uint32_t) pw->pw_uid,
df0ff127 395 (uint32_t) getpid_cached(),
a4cd87e9 396 service,
ffcfcb6b
ZJS
397 type,
398 class,
a4cd87e9
LP
399 desktop,
400 seat,
ffcfcb6b
ZJS
401 vtnr,
402 tty,
a4cd87e9 403 display,
ffcfcb6b 404 remote,
a4cd87e9
LP
405 remote_user,
406 remote_host,
ffcfcb6b
ZJS
407 0);
408 if (r < 0) {
b80120c4
DH
409 if (sd_bus_error_has_name(&error, BUS_ERROR_SESSION_BUSY)) {
410 pam_syslog(handle, LOG_DEBUG, "Cannot create session: %s", bus_error_message(&error, r));
411 return PAM_SUCCESS;
412 } else {
413 pam_syslog(handle, LOG_ERR, "Failed to create session: %s", bus_error_message(&error, r));
414 return PAM_SYSTEM_ERR;
415 }
98a28fef 416 }
74fe1fe3 417
ffcfcb6b 418 r = sd_bus_message_read(reply,
baae0358 419 "soshusub",
ffcfcb6b
ZJS
420 &id,
421 &object_path,
422 &runtime_path,
423 &session_fd,
baae0358 424 &original_uid,
ffcfcb6b
ZJS
425 &seat,
426 &vtnr,
427 &existing);
428 if (r < 0) {
429 pam_syslog(handle, LOG_ERR, "Failed to parse message: %s", strerror(-r));
5a330cda 430 return PAM_SESSION_ERR;
98a28fef 431 }
74fe1fe3 432
ce959314
MS
433 if (debug)
434 pam_syslog(handle, LOG_DEBUG, "Reply from logind: "
baae0358
LP
435 "id=%s object_path=%s runtime_path=%s session_fd=%d seat=%s vtnr=%u original_uid=%u",
436 id, object_path, runtime_path, session_fd, seat, vtnr, original_uid);
ce959314 437
98a28fef
LP
438 r = pam_misc_setenv(handle, "XDG_SESSION_ID", id, 0);
439 if (r != PAM_SUCCESS) {
440 pam_syslog(handle, LOG_ERR, "Failed to set session id.");
5a330cda 441 return r;
8c6db833
LP
442 }
443
baae0358
LP
444 if (original_uid == pw->pw_uid) {
445 /* Don't set $XDG_RUNTIME_DIR if the user we now
446 * authenticated for does not match the original user
447 * of the session. We do this in order not to result
448 * in privileged apps clobbering the runtime directory
449 * unnecessarily. */
450
451 r = pam_misc_setenv(handle, "XDG_RUNTIME_DIR", runtime_path, 0);
452 if (r != PAM_SUCCESS) {
453 pam_syslog(handle, LOG_ERR, "Failed to set runtime dir.");
454 return r;
455 }
8b255ecd
KS
456
457 r = export_legacy_dbus_address(handle, pw->pw_uid, runtime_path);
458 if (r != PAM_SUCCESS)
459 return r;
98a28fef 460 }
8c6db833 461
bbc73283
LP
462 if (!isempty(seat)) {
463 r = pam_misc_setenv(handle, "XDG_SEAT", seat, 0);
464 if (r != PAM_SUCCESS) {
465 pam_syslog(handle, LOG_ERR, "Failed to set seat.");
5a330cda 466 return r;
bbc73283
LP
467 }
468 }
469
470 if (vtnr > 0) {
29d230f6 471 char buf[DECIMAL_STR_MAX(vtnr)];
baae0358 472 sprintf(buf, "%u", vtnr);
bbc73283
LP
473
474 r = pam_misc_setenv(handle, "XDG_VTNR", buf, 0);
475 if (r != PAM_SUCCESS) {
476 pam_syslog(handle, LOG_ERR, "Failed to set virtual terminal number.");
5a330cda 477 return r;
bbc73283
LP
478 }
479 }
480
77085881
LP
481 r = pam_set_data(handle, "systemd.existing", INT_TO_PTR(!!existing), NULL);
482 if (r != PAM_SUCCESS) {
483 pam_syslog(handle, LOG_ERR, "Failed to install existing flag.");
5a330cda 484 return r;
77085881
LP
485 }
486
21c390cc 487 if (session_fd >= 0) {
85c08dc0 488 session_fd = fcntl(session_fd, F_DUPFD_CLOEXEC, 3);
5a330cda
ZJS
489 if (session_fd < 0) {
490 pam_syslog(handle, LOG_ERR, "Failed to dup session fd: %m");
491 return PAM_SESSION_ERR;
492 }
493
23e096cc 494 r = pam_set_data(handle, "systemd.session-fd", FD_TO_PTR(session_fd), NULL);
21c390cc
LP
495 if (r != PAM_SUCCESS) {
496 pam_syslog(handle, LOG_ERR, "Failed to install session fd.");
03e334a1 497 safe_close(session_fd);
5a330cda 498 return r;
21c390cc 499 }
98a28fef 500 }
8c6db833 501
ffcfcb6b 502 return PAM_SUCCESS;
98a28fef 503}
8c6db833 504
98a28fef
LP
505_public_ PAM_EXTERN int pam_sm_close_session(
506 pam_handle_t *handle,
507 int flags,
508 int argc, const char **argv) {
8c6db833 509
4afd3348
LP
510 _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
511 _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
5f41d1f1 512 const void *existing = NULL;
75c8e3cf 513 const char *id;
75c8e3cf 514 int r;
8c6db833 515
ffcfcb6b 516 assert(handle);
75c8e3cf 517
77085881
LP
518 /* Only release session if it wasn't pre-existing when we
519 * tried to create it */
520 pam_get_data(handle, "systemd.existing", &existing);
521
75c8e3cf 522 id = pam_getenv(handle, "XDG_SESSION_ID");
77085881 523 if (id && !existing) {
75c8e3cf
LP
524
525 /* Before we go and close the FIFO we need to tell
526 * logind that this is a clean session shutdown, so
527 * that it doesn't just go and slaughter us
528 * immediately after closing the fd */
529
ffcfcb6b
ZJS
530 r = sd_bus_open_system(&bus);
531 if (r < 0) {
5f41d1f1
LP
532 pam_syslog(handle, LOG_ERR, "Failed to connect to system bus: %s", strerror(-r));
533 return PAM_SESSION_ERR;
75c8e3cf
LP
534 }
535
ffcfcb6b
ZJS
536 r = sd_bus_call_method(bus,
537 "org.freedesktop.login1",
538 "/org/freedesktop/login1",
539 "org.freedesktop.login1.Manager",
540 "ReleaseSession",
541 &error,
542 NULL,
543 "s",
544 id);
545 if (r < 0) {
5f41d1f1
LP
546 pam_syslog(handle, LOG_ERR, "Failed to release session: %s", bus_error_message(&error, r));
547 return PAM_SESSION_ERR;
75c8e3cf
LP
548 }
549 }
550
5f41d1f1
LP
551 /* Note that we are knowingly leaking the FIFO fd here. This
552 * way, logind can watch us die. If we closed it here it would
553 * not have any clue when that is completed. Given that one
554 * cannot really have multiple PAM sessions open from the same
555 * process this means we will leak one FD at max. */
75c8e3cf 556
5f41d1f1 557 return PAM_SUCCESS;
8c6db833 558}