]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/login/pam-module.c
core: failed scope units may not be restarted
[thirdparty/systemd.git] / src / login / pam-module.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>
ac123445 27#include <sys/capability.h>
8c6db833
LP
28
29#include <security/pam_modules.h>
30#include <security/_pam_macros.h>
31#include <security/pam_modutil.h>
32#include <security/pam_ext.h>
33#include <security/pam_misc.h>
34
8c6db833 35#include "util.h"
d7832d2c 36#include "audit.h"
8c6db833 37#include "macro.h"
74fe1fe3 38#include "strv.h"
98a28fef
LP
39#include "dbus-common.h"
40#include "def.h"
4d6d6518 41#include "socket-util.h"
a5c32cff 42#include "fileio.h"
8c6db833
LP
43
44static int parse_argv(pam_handle_t *handle,
45 int argc, const char **argv,
485507b8 46 const char **class,
0e318cad 47 bool *debug) {
8c6db833
LP
48
49 unsigned i;
50
51 assert(argc >= 0);
52 assert(argc == 0 || argv);
53
54 for (i = 0; i < (unsigned) argc; i++) {
55 int k;
56
fb6becb4 57 if (startswith(argv[i], "class=")) {
485507b8
MM
58
59 if (class)
60 *class = argv[i] + 6;
61
0e318cad 62 } else if (startswith(argv[i], "debug=")) {
fb6becb4
LP
63 k = parse_boolean(argv[i] + 6);
64
65 if (k < 0) {
0e318cad
MS
66 pam_syslog(handle, LOG_ERR, "Failed to parse debug= argument.");
67 return k;
68 }
69
70 if (debug)
71 *debug = k;
72
8c6db833 73 } else {
fb6becb4
LP
74 pam_syslog(handle, LOG_WARNING, "Unknown parameter '%s', ignoring", argv[i]);
75 return 0;
8c6db833
LP
76 }
77 }
78
8c6db833
LP
79 return 0;
80}
81
8c6db833
LP
82static int get_user_data(
83 pam_handle_t *handle,
84 const char **ret_username,
85 struct passwd **ret_pw) {
86
d90b9d27
LP
87 const char *username = NULL;
88 struct passwd *pw = NULL;
87d2c1ff 89 uid_t uid;
8c6db833
LP
90 int r;
91
92 assert(handle);
93 assert(ret_username);
94 assert(ret_pw);
95
87d2c1ff
LP
96 r = audit_loginuid_from_pid(0, &uid);
97 if (r >= 0)
98 pw = pam_modutil_getpwuid(handle, uid);
99 else {
100 r = pam_get_user(handle, &username, NULL);
101 if (r != PAM_SUCCESS) {
d90b9d27
LP
102 pam_syslog(handle, LOG_ERR, "Failed to get user name.");
103 return r;
104 }
105
87d2c1ff 106 if (isempty(username)) {
d90b9d27
LP
107 pam_syslog(handle, LOG_ERR, "User name not valid.");
108 return PAM_AUTH_ERR;
109 }
110
111 pw = pam_modutil_getpwnam(handle, username);
8c6db833
LP
112 }
113
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;
d90b9d27 120 *ret_username = username ? username : pw->pw_name;
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) {
7fd1b19b 126 _cleanup_free_ char *p = NULL;
4d6d6518 127 int r;
7fd1b19b 128 _cleanup_close_ int fd = -1;
b92bea5d
ZJS
129 union sockaddr_union sa = {
130 .un.sun_family = AF_UNIX,
131 };
4d6d6518
LP
132 struct ucred ucred;
133 socklen_t l;
7fd1b19b 134 _cleanup_free_ char *tty = NULL;
4d6d6518
LP
135 int v;
136
137 assert(display);
4d6d6518
LP
138 assert(vtnr);
139
140 /* We deduce the X11 socket from the display name, then use
141 * SO_PEERCRED to determine the X11 server process, ask for
142 * the controlling tty of that and if it's a VC then we know
143 * the seat and the virtual terminal. Sounds ugly, is only
144 * semi-ugly. */
145
146 r = socket_from_display(display, &p);
147 if (r < 0)
148 return r;
b92bea5d 149 strncpy(sa.un.sun_path, p, sizeof(sa.un.sun_path)-1);
4d6d6518
LP
150
151 fd = socket(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC, 0);
b92bea5d 152 if (fd < 0)
4d6d6518 153 return -errno;
4d6d6518 154
b92bea5d 155 if (connect(fd, &sa.sa, offsetof(struct sockaddr_un, sun_path) + strlen(sa.un.sun_path)) < 0)
4d6d6518 156 return -errno;
4d6d6518
LP
157
158 l = sizeof(ucred);
159 r = getsockopt(fd, SOL_SOCKET, SO_PEERCRED, &ucred, &l);
4d6d6518
LP
160 if (r < 0)
161 return -errno;
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
98a28fef 180_public_ PAM_EXTERN int pam_sm_open_session(
8c6db833
LP
181 pam_handle_t *handle,
182 int flags,
183 int argc, const char **argv) {
184
8c6db833 185 struct passwd *pw;
fb6becb4 186 bool debug = false;
485507b8 187 const char *username, *id, *object_path, *runtime_path, *service = NULL, *tty = NULL, *display = NULL, *remote_user = NULL, *remote_host = NULL, *seat = NULL, *type = NULL, *class = NULL, *class_pam = NULL, *cvtnr = NULL;
98a28fef
LP
188 DBusError error;
189 uint32_t uid, pid;
190 DBusMessageIter iter;
98a28fef
LP
191 int session_fd = -1;
192 DBusConnection *bus = NULL;
193 DBusMessage *m = NULL, *reply = NULL;
77085881 194 dbus_bool_t remote, existing;
ed18b08b 195 int r;
4d6d6518 196 uint32_t vtnr = 0;
8c6db833
LP
197
198 assert(handle);
199
98a28fef
LP
200 dbus_error_init(&error);
201
ed18b08b 202 /* pam_syslog(handle, LOG_INFO, "pam-systemd initializing"); */
98a28fef 203
79d860fe
MP
204 /* Make this a NOP on non-logind systems */
205 if (!logind_running())
8c6db833
LP
206 return PAM_SUCCESS;
207
e9fbc77c
LP
208 if (parse_argv(handle,
209 argc, argv,
fb6becb4
LP
210 &class_pam,
211 &debug) < 0) {
ed18b08b
LP
212 r = PAM_SESSION_ERR;
213 goto finish;
214 }
74fe1fe3 215
98a28fef
LP
216 r = get_user_data(handle, &username, &pw);
217 if (r != PAM_SUCCESS)
8c6db833
LP
218 goto finish;
219
30b2c336
LP
220 /* Make sure we don't enter a loop by talking to
221 * systemd-logind when it is actually waiting for the
222 * background to finish start-up. If the service is
223 * "systemd-shared" we simply set XDG_RUNTIME_DIR and
224 * leave. */
225
226 pam_get_item(handle, PAM_SERVICE, (const void**) &service);
227 if (streq_ptr(service, "systemd-shared")) {
228 char *p, *rt = NULL;
229
230 if (asprintf(&p, "/run/systemd/users/%lu", (unsigned long) pw->pw_uid) < 0) {
231 r = PAM_BUF_ERR;
232 goto finish;
233 }
234
235 r = parse_env_file(p, NEWLINE,
236 "RUNTIME", &rt,
237 NULL);
238 free(p);
239
240 if (r < 0 && r != -ENOENT) {
241 r = PAM_SESSION_ERR;
242 free(rt);
243 goto finish;
244 }
245
246 if (rt) {
247 r = pam_misc_setenv(handle, "XDG_RUNTIME_DIR", rt, 0);
248 free(rt);
249
250 if (r != PAM_SUCCESS) {
251 pam_syslog(handle, LOG_ERR, "Failed to set runtime dir.");
252 goto finish;
253 }
254 }
255
256 r = PAM_SUCCESS;
257 goto finish;
258 }
259
ed18b08b
LP
260 dbus_connection_set_change_sigpipe(FALSE);
261
98a28fef
LP
262 bus = dbus_bus_get_private(DBUS_BUS_SYSTEM, &error);
263 if (!bus) {
264 pam_syslog(handle, LOG_ERR, "Failed to connect to system bus: %s", bus_error_message(&error));
265 r = PAM_SESSION_ERR;
8c6db833
LP
266 goto finish;
267 }
268
98a28fef
LP
269 m = dbus_message_new_method_call(
270 "org.freedesktop.login1",
271 "/org/freedesktop/login1",
272 "org.freedesktop.login1.Manager",
273 "CreateSession");
98a28fef
LP
274 if (!m) {
275 pam_syslog(handle, LOG_ERR, "Could not allocate create session message.");
8c6db833
LP
276 r = PAM_BUF_ERR;
277 goto finish;
278 }
279
98a28fef
LP
280 uid = pw->pw_uid;
281 pid = getpid();
282
98a28fef
LP
283 pam_get_item(handle, PAM_XDISPLAY, (const void**) &display);
284 pam_get_item(handle, PAM_TTY, (const void**) &tty);
285 pam_get_item(handle, PAM_RUSER, (const void**) &remote_user);
286 pam_get_item(handle, PAM_RHOST, (const void**) &remote_host);
a8573ccc 287
bbc73283 288 seat = pam_getenv(handle, "XDG_SEAT");
a8573ccc
LP
289 if (isempty(seat))
290 seat = getenv("XDG_SEAT");
291
bbc73283 292 cvtnr = pam_getenv(handle, "XDG_VTNR");
a8573ccc
LP
293 if (isempty(cvtnr))
294 cvtnr = getenv("XDG_VTNR");
98a28fef 295
ed18b08b
LP
296 service = strempty(service);
297 tty = strempty(tty);
298 display = strempty(display);
299 remote_user = strempty(remote_user);
300 remote_host = strempty(remote_host);
301 seat = strempty(seat);
98a28fef 302
ee8545b0 303 if (strchr(tty, ':')) {
e2acb67b
LP
304 /* A tty with a colon is usually an X11 display,
305 * placed there to show up in utmp. We rearrange
306 * things and don't pretend that an X display was a
307 * tty. */
ee8545b0
LP
308
309 if (isempty(display))
310 display = tty;
cd58752a 311 tty = "";
1a4459d6 312 } else if (streq(tty, "cron")) {
0ad1271f
LP
313 /* cron has been setting PAM_TTY to "cron" for a very
314 * long time and it probably shouldn't stop doing that
315 * for compatibility reasons. */
1a4459d6 316 tty = "";
0ad1271f
LP
317 type = "unspecified";
318 } else if (streq(tty, "ssh")) {
319 /* ssh has been setting PAM_TTY to "ssh" for a very
320 * long time and probably shouldn't stop doing that
321 * for compatibility reasons. */
322 tty = "";
323 type ="tty";
ee8545b0
LP
324 }
325
8e7705e5 326 /* If this fails vtnr will be 0, that's intended */
4d6d6518
LP
327 if (!isempty(cvtnr))
328 safe_atou32(cvtnr, &vtnr);
329
fc7985ed
LP
330 if (!isempty(display) && vtnr <= 0) {
331 if (isempty(seat))
6ef25fb6 332 get_seat_from_display(display, &seat, &vtnr);
fc7985ed 333 else if (streq(seat, "seat0"))
6ef25fb6 334 get_seat_from_display(display, NULL, &vtnr);
fc7985ed 335 }
4d6d6518 336
0ad1271f
LP
337 if (!type)
338 type = !isempty(display) ? "x11" :
339 !isempty(tty) ? "tty" : "unspecified";
98a28fef 340
55efac6c 341 class = pam_getenv(handle, "XDG_SESSION_CLASS");
a8573ccc
LP
342 if (isempty(class))
343 class = getenv("XDG_SESSION_CLASS");
485507b8
MM
344 if (isempty(class))
345 class = class_pam;
55efac6c 346 if (isempty(class))
e2acb67b 347 class = streq(type, "unspecified") ? "background" : "user";
55efac6c
LP
348
349 remote = !isempty(remote_host) &&
350 !streq(remote_host, "localhost") &&
351 !streq(remote_host, "localhost.localdomain");
98a28fef
LP
352
353 if (!dbus_message_append_args(m,
354 DBUS_TYPE_UINT32, &uid,
355 DBUS_TYPE_UINT32, &pid,
356 DBUS_TYPE_STRING, &service,
357 DBUS_TYPE_STRING, &type,
55efac6c 358 DBUS_TYPE_STRING, &class,
98a28fef 359 DBUS_TYPE_STRING, &seat,
4d6d6518 360 DBUS_TYPE_UINT32, &vtnr,
98a28fef
LP
361 DBUS_TYPE_STRING, &tty,
362 DBUS_TYPE_STRING, &display,
363 DBUS_TYPE_BOOLEAN, &remote,
364 DBUS_TYPE_STRING, &remote_user,
365 DBUS_TYPE_STRING, &remote_host,
366 DBUS_TYPE_INVALID)) {
367 pam_syslog(handle, LOG_ERR, "Could not attach parameters to message.");
368 r = PAM_BUF_ERR;
369 goto finish;
370 }
8c6db833 371
98a28fef 372 dbus_message_iter_init_append(m, &iter);
8c6db833 373
ce959314
MS
374 if (debug)
375 pam_syslog(handle, LOG_DEBUG, "Asking logind to create session: "
f904bdf2
CG
376 "uid=%u pid=%u service=%s type=%s class=%s seat=%s vtnr=%u tty=%s display=%s remote=%s remote_user=%s remote_host=%s",
377 uid, pid, service, type, class, seat, vtnr, tty, display, yes_no(remote), remote_user, remote_host);
ce959314 378
98a28fef
LP
379 reply = dbus_connection_send_with_reply_and_block(bus, m, -1, &error);
380 if (!reply) {
381 pam_syslog(handle, LOG_ERR, "Failed to create session: %s", bus_error_message(&error));
382 r = PAM_SESSION_ERR;
383 goto finish;
384 }
74fe1fe3 385
98a28fef
LP
386 if (!dbus_message_get_args(reply, &error,
387 DBUS_TYPE_STRING, &id,
388 DBUS_TYPE_OBJECT_PATH, &object_path,
389 DBUS_TYPE_STRING, &runtime_path,
390 DBUS_TYPE_UNIX_FD, &session_fd,
bbc73283
LP
391 DBUS_TYPE_STRING, &seat,
392 DBUS_TYPE_UINT32, &vtnr,
77085881 393 DBUS_TYPE_BOOLEAN, &existing,
98a28fef
LP
394 DBUS_TYPE_INVALID)) {
395 pam_syslog(handle, LOG_ERR, "Failed to parse message: %s", bus_error_message(&error));
396 r = PAM_SESSION_ERR;
397 goto finish;
398 }
74fe1fe3 399
ce959314
MS
400 if (debug)
401 pam_syslog(handle, LOG_DEBUG, "Reply from logind: "
402 "id=%s object_path=%s runtime_path=%s session_fd=%d seat=%s vtnr=%u",
403 id, object_path, runtime_path, session_fd, seat, vtnr);
404
98a28fef
LP
405 r = pam_misc_setenv(handle, "XDG_SESSION_ID", id, 0);
406 if (r != PAM_SUCCESS) {
407 pam_syslog(handle, LOG_ERR, "Failed to set session id.");
408 goto finish;
8c6db833
LP
409 }
410
98a28fef
LP
411 r = pam_misc_setenv(handle, "XDG_RUNTIME_DIR", runtime_path, 0);
412 if (r != PAM_SUCCESS) {
413 pam_syslog(handle, LOG_ERR, "Failed to set runtime dir.");
414 goto finish;
415 }
8c6db833 416
bbc73283
LP
417 if (!isempty(seat)) {
418 r = pam_misc_setenv(handle, "XDG_SEAT", seat, 0);
419 if (r != PAM_SUCCESS) {
420 pam_syslog(handle, LOG_ERR, "Failed to set seat.");
421 goto finish;
422 }
423 }
424
425 if (vtnr > 0) {
426 char buf[11];
427 snprintf(buf, sizeof(buf), "%u", vtnr);
428 char_array_0(buf);
429
430 r = pam_misc_setenv(handle, "XDG_VTNR", buf, 0);
431 if (r != PAM_SUCCESS) {
432 pam_syslog(handle, LOG_ERR, "Failed to set virtual terminal number.");
433 goto finish;
434 }
435 }
436
77085881
LP
437 r = pam_set_data(handle, "systemd.existing", INT_TO_PTR(!!existing), NULL);
438 if (r != PAM_SUCCESS) {
439 pam_syslog(handle, LOG_ERR, "Failed to install existing flag.");
440 return r;
441 }
442
21c390cc
LP
443 if (session_fd >= 0) {
444 r = pam_set_data(handle, "systemd.session-fd", INT_TO_PTR(session_fd+1), NULL);
445 if (r != PAM_SUCCESS) {
446 pam_syslog(handle, LOG_ERR, "Failed to install session fd.");
447 return r;
448 }
98a28fef 449 }
8c6db833 450
98a28fef 451 session_fd = -1;
8c6db833 452
98a28fef 453 r = PAM_SUCCESS;
8c6db833 454
98a28fef 455finish:
98a28fef 456 dbus_error_free(&error);
35d2e7ec 457
98a28fef
LP
458 if (bus) {
459 dbus_connection_close(bus);
460 dbus_connection_unref(bus);
8c6db833
LP
461 }
462
98a28fef
LP
463 if (m)
464 dbus_message_unref(m);
8c6db833 465
ed18b08b
LP
466 if (reply)
467 dbus_message_unref(reply);
468
98a28fef
LP
469 if (session_fd >= 0)
470 close_nointr_nofail(session_fd);
672c48cc 471
98a28fef
LP
472 return r;
473}
8c6db833 474
98a28fef
LP
475_public_ PAM_EXTERN int pam_sm_close_session(
476 pam_handle_t *handle,
477 int flags,
478 int argc, const char **argv) {
8c6db833 479
77085881 480 const void *p = NULL, *existing = NULL;
75c8e3cf
LP
481 const char *id;
482 DBusConnection *bus = NULL;
483 DBusMessage *m = NULL, *reply = NULL;
484 DBusError error;
485 int r;
8c6db833 486
75c8e3cf
LP
487 assert(handle);
488
489 dbus_error_init(&error);
490
77085881
LP
491 /* Only release session if it wasn't pre-existing when we
492 * tried to create it */
493 pam_get_data(handle, "systemd.existing", &existing);
494
75c8e3cf 495 id = pam_getenv(handle, "XDG_SESSION_ID");
77085881 496 if (id && !existing) {
75c8e3cf
LP
497
498 /* Before we go and close the FIFO we need to tell
499 * logind that this is a clean session shutdown, so
500 * that it doesn't just go and slaughter us
501 * immediately after closing the fd */
502
503 bus = dbus_bus_get_private(DBUS_BUS_SYSTEM, &error);
504 if (!bus) {
505 pam_syslog(handle, LOG_ERR, "Failed to connect to system bus: %s", bus_error_message(&error));
506 r = PAM_SESSION_ERR;
507 goto finish;
508 }
509
510 m = dbus_message_new_method_call(
511 "org.freedesktop.login1",
512 "/org/freedesktop/login1",
513 "org.freedesktop.login1.Manager",
514 "ReleaseSession");
515 if (!m) {
516 pam_syslog(handle, LOG_ERR, "Could not allocate release session message.");
517 r = PAM_BUF_ERR;
518 goto finish;
519 }
520
521 if (!dbus_message_append_args(m,
522 DBUS_TYPE_STRING, &id,
523 DBUS_TYPE_INVALID)) {
524 pam_syslog(handle, LOG_ERR, "Could not attach parameters to message.");
525 r = PAM_BUF_ERR;
526 goto finish;
527 }
74fe1fe3 528
75c8e3cf
LP
529 reply = dbus_connection_send_with_reply_and_block(bus, m, -1, &error);
530 if (!reply) {
531 pam_syslog(handle, LOG_ERR, "Failed to release session: %s", bus_error_message(&error));
532 r = PAM_SESSION_ERR;
533 goto finish;
534 }
535 }
536
537 r = PAM_SUCCESS;
538
539finish:
540 pam_get_data(handle, "systemd.session-fd", &p);
98a28fef
LP
541 if (p)
542 close_nointr(PTR_TO_INT(p) - 1);
1f73f0f1 543
75c8e3cf
LP
544 dbus_error_free(&error);
545
546 if (bus) {
547 dbus_connection_close(bus);
548 dbus_connection_unref(bus);
549 }
550
551 if (m)
552 dbus_message_unref(m);
553
554 if (reply)
555 dbus_message_unref(reply);
556
557 return r;
8c6db833 558}