]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/login/pam_systemd.c
Merge pull request #1880 from fsateler/sysctl-doc
[thirdparty/systemd.git] / src / login / pam_systemd.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 <endian.h>
23 #include <errno.h>
24 #include <fcntl.h>
25 #include <pwd.h>
26 #include <security/_pam_macros.h>
27 #include <security/pam_ext.h>
28 #include <security/pam_misc.h>
29 #include <security/pam_modules.h>
30 #include <security/pam_modutil.h>
31 #include <sys/file.h>
32
33 #include "alloc-util.h"
34 #include "audit-util.h"
35 #include "bus-common-errors.h"
36 #include "bus-error.h"
37 #include "bus-util.h"
38 #include "def.h"
39 #include "fd-util.h"
40 #include "fileio.h"
41 #include "formats-util.h"
42 #include "hostname-util.h"
43 #include "login-util.h"
44 #include "macro.h"
45 #include "parse-util.h"
46 #include "socket-util.h"
47 #include "strv.h"
48 #include "terminal-util.h"
49 #include "util.h"
50
51 static int parse_argv(
52 pam_handle_t *handle,
53 int argc, const char **argv,
54 const char **class,
55 const char **type,
56 bool *debug) {
57
58 unsigned i;
59
60 assert(argc >= 0);
61 assert(argc == 0 || argv);
62
63 for (i = 0; i < (unsigned) argc; i++) {
64 if (startswith(argv[i], "class=")) {
65 if (class)
66 *class = argv[i] + 6;
67
68 } else if (startswith(argv[i], "type=")) {
69 if (type)
70 *type = argv[i] + 5;
71
72 } else if (streq(argv[i], "debug")) {
73 if (debug)
74 *debug = true;
75
76 } else if (startswith(argv[i], "debug=")) {
77 int k;
78
79 k = parse_boolean(argv[i] + 6);
80 if (k < 0)
81 pam_syslog(handle, LOG_WARNING, "Failed to parse debug= argument, ignoring.");
82 else if (debug)
83 *debug = k;
84
85 } else
86 pam_syslog(handle, LOG_WARNING, "Unknown parameter '%s', ignoring", argv[i]);
87 }
88
89 return 0;
90 }
91
92 static int get_user_data(
93 pam_handle_t *handle,
94 const char **ret_username,
95 struct passwd **ret_pw) {
96
97 const char *username = NULL;
98 struct passwd *pw = NULL;
99 int r;
100
101 assert(handle);
102 assert(ret_username);
103 assert(ret_pw);
104
105 r = pam_get_user(handle, &username, NULL);
106 if (r != PAM_SUCCESS) {
107 pam_syslog(handle, LOG_ERR, "Failed to get user name.");
108 return r;
109 }
110
111 if (isempty(username)) {
112 pam_syslog(handle, LOG_ERR, "User name not valid.");
113 return PAM_AUTH_ERR;
114 }
115
116 pw = pam_modutil_getpwnam(handle, username);
117 if (!pw) {
118 pam_syslog(handle, LOG_ERR, "Failed to get user data.");
119 return PAM_USER_UNKNOWN;
120 }
121
122 *ret_pw = pw;
123 *ret_username = username;
124
125 return PAM_SUCCESS;
126 }
127
128 static int get_seat_from_display(const char *display, const char **seat, uint32_t *vtnr) {
129 union sockaddr_union sa = {
130 .un.sun_family = AF_UNIX,
131 };
132 _cleanup_free_ char *p = NULL, *tty = NULL;
133 _cleanup_close_ int fd = -1;
134 struct ucred ucred;
135 int v, r;
136
137 assert(display);
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;
149 strncpy(sa.un.sun_path, p, sizeof(sa.un.sun_path)-1);
150
151 fd = socket(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC, 0);
152 if (fd < 0)
153 return -errno;
154
155 if (connect(fd, &sa.sa, offsetof(struct sockaddr_un, sun_path) + strlen(sa.un.sun_path)) < 0)
156 return -errno;
157
158 r = getpeercred(fd, &ucred);
159 if (r < 0)
160 return r;
161
162 r = get_ctty(ucred.pid, NULL, &tty);
163 if (r < 0)
164 return r;
165
166 v = vtnr_from_tty(tty);
167 if (v < 0)
168 return v;
169 else if (v == 0)
170 return -ENOENT;
171
172 if (seat)
173 *seat = "seat0";
174 *vtnr = (uint32_t) v;
175
176 return 0;
177 }
178
179 static int export_legacy_dbus_address(
180 pam_handle_t *handle,
181 uid_t uid,
182 const char *runtime) {
183
184 _cleanup_free_ char *s = NULL;
185 int r = PAM_BUF_ERR;
186
187 if (is_kdbus_available()) {
188 if (asprintf(&s, KERNEL_USER_BUS_ADDRESS_FMT ";" UNIX_USER_BUS_ADDRESS_FMT, uid, runtime) < 0)
189 goto error;
190 } else {
191 /* FIXME: We *really* should move the access() check into the
192 * daemons that spawn dbus-daemon, instead of forcing
193 * DBUS_SESSION_BUS_ADDRESS= here. */
194
195 s = strjoin(runtime, "/bus", NULL);
196 if (!s)
197 goto error;
198
199 if (access(s, F_OK) < 0)
200 return PAM_SUCCESS;
201
202 s = mfree(s);
203 if (asprintf(&s, UNIX_USER_BUS_ADDRESS_FMT, runtime) < 0)
204 goto error;
205 }
206
207 r = pam_misc_setenv(handle, "DBUS_SESSION_BUS_ADDRESS", s, 0);
208 if (r != PAM_SUCCESS)
209 goto error;
210
211 return PAM_SUCCESS;
212
213 error:
214 pam_syslog(handle, LOG_ERR, "Failed to set bus variable.");
215 return r;
216 }
217
218 _public_ PAM_EXTERN int pam_sm_open_session(
219 pam_handle_t *handle,
220 int flags,
221 int argc, const char **argv) {
222
223 _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
224 _cleanup_bus_message_unref_ sd_bus_message *reply = NULL;
225 const char
226 *username, *id, *object_path, *runtime_path,
227 *service = NULL,
228 *tty = NULL, *display = NULL,
229 *remote_user = NULL, *remote_host = NULL,
230 *seat = NULL,
231 *type = NULL, *class = NULL,
232 *class_pam = NULL, *type_pam = NULL, *cvtnr = NULL, *desktop = NULL;
233 _cleanup_bus_flush_close_unref_ sd_bus *bus = NULL;
234 int session_fd = -1, existing, r;
235 bool debug = false, remote;
236 struct passwd *pw;
237 uint32_t vtnr = 0;
238 uid_t original_uid;
239
240 assert(handle);
241
242 /* Make this a NOP on non-logind systems */
243 if (!logind_running())
244 return PAM_SUCCESS;
245
246 if (parse_argv(handle,
247 argc, argv,
248 &class_pam,
249 &type_pam,
250 &debug) < 0)
251 return PAM_SESSION_ERR;
252
253 if (debug)
254 pam_syslog(handle, LOG_DEBUG, "pam-systemd initializing");
255
256 r = get_user_data(handle, &username, &pw);
257 if (r != PAM_SUCCESS) {
258 pam_syslog(handle, LOG_ERR, "Failed to get user data.");
259 return r;
260 }
261
262 /* Make sure we don't enter a loop by talking to
263 * systemd-logind when it is actually waiting for the
264 * background to finish start-up. If the service is
265 * "systemd-user" we simply set XDG_RUNTIME_DIR and
266 * leave. */
267
268 pam_get_item(handle, PAM_SERVICE, (const void**) &service);
269 if (streq_ptr(service, "systemd-user")) {
270 _cleanup_free_ char *p = NULL, *rt = NULL;
271
272 if (asprintf(&p, "/run/systemd/users/"UID_FMT, pw->pw_uid) < 0)
273 return PAM_BUF_ERR;
274
275 r = parse_env_file(p, NEWLINE,
276 "RUNTIME", &rt,
277 NULL);
278 if (r < 0 && r != -ENOENT)
279 return PAM_SESSION_ERR;
280
281 if (rt) {
282 r = pam_misc_setenv(handle, "XDG_RUNTIME_DIR", rt, 0);
283 if (r != PAM_SUCCESS) {
284 pam_syslog(handle, LOG_ERR, "Failed to set runtime dir.");
285 return r;
286 }
287
288 r = export_legacy_dbus_address(handle, pw->pw_uid, rt);
289 if (r != PAM_SUCCESS)
290 return r;
291 }
292
293 return PAM_SUCCESS;
294 }
295
296 /* Otherwise, we ask logind to create a session for us */
297
298 pam_get_item(handle, PAM_XDISPLAY, (const void**) &display);
299 pam_get_item(handle, PAM_TTY, (const void**) &tty);
300 pam_get_item(handle, PAM_RUSER, (const void**) &remote_user);
301 pam_get_item(handle, PAM_RHOST, (const void**) &remote_host);
302
303 seat = pam_getenv(handle, "XDG_SEAT");
304 if (isempty(seat))
305 seat = getenv("XDG_SEAT");
306
307 cvtnr = pam_getenv(handle, "XDG_VTNR");
308 if (isempty(cvtnr))
309 cvtnr = getenv("XDG_VTNR");
310
311 type = pam_getenv(handle, "XDG_SESSION_TYPE");
312 if (isempty(type))
313 type = getenv("XDG_SESSION_TYPE");
314 if (isempty(type))
315 type = type_pam;
316
317 class = pam_getenv(handle, "XDG_SESSION_CLASS");
318 if (isempty(class))
319 class = getenv("XDG_SESSION_CLASS");
320 if (isempty(class))
321 class = class_pam;
322
323 desktop = pam_getenv(handle, "XDG_SESSION_DESKTOP");
324 if (isempty(desktop))
325 desktop = getenv("XDG_SESSION_DESKTOP");
326
327 tty = strempty(tty);
328
329 if (strchr(tty, ':')) {
330 /* A tty with a colon is usually an X11 display,
331 * placed there to show up in utmp. We rearrange
332 * things and don't pretend that an X display was a
333 * tty. */
334
335 if (isempty(display))
336 display = tty;
337 tty = NULL;
338 } else if (streq(tty, "cron")) {
339 /* cron has been setting PAM_TTY to "cron" for a very
340 * long time and it probably shouldn't stop doing that
341 * for compatibility reasons. */
342 type = "unspecified";
343 class = "background";
344 tty = NULL;
345 } else if (streq(tty, "ssh")) {
346 /* ssh has been setting PAM_TTY to "ssh" for a very
347 * long time and probably shouldn't stop doing that
348 * for compatibility reasons. */
349 type ="tty";
350 class = "user";
351 tty = NULL;
352 }
353
354 /* If this fails vtnr will be 0, that's intended */
355 if (!isempty(cvtnr))
356 (void) safe_atou32(cvtnr, &vtnr);
357
358 if (!isempty(display) && !vtnr) {
359 if (isempty(seat))
360 get_seat_from_display(display, &seat, &vtnr);
361 else if (streq(seat, "seat0"))
362 get_seat_from_display(display, NULL, &vtnr);
363 }
364
365 if (seat && !streq(seat, "seat0") && vtnr != 0) {
366 pam_syslog(handle, LOG_DEBUG, "Ignoring vtnr %"PRIu32" for %s which is not seat0", vtnr, seat);
367 vtnr = 0;
368 }
369
370 if (isempty(type))
371 type = !isempty(display) ? "x11" :
372 !isempty(tty) ? "tty" : "unspecified";
373
374 if (isempty(class))
375 class = streq(type, "unspecified") ? "background" : "user";
376
377 remote = !isempty(remote_host) && !is_localhost(remote_host);
378
379 /* Talk to logind over the message bus */
380
381 r = sd_bus_open_system(&bus);
382 if (r < 0) {
383 pam_syslog(handle, LOG_ERR, "Failed to connect to system bus: %s", strerror(-r));
384 return PAM_SESSION_ERR;
385 }
386
387 if (debug)
388 pam_syslog(handle, LOG_DEBUG, "Asking logind to create session: "
389 "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",
390 pw->pw_uid, getpid(),
391 strempty(service),
392 type, class, strempty(desktop),
393 strempty(seat), vtnr, strempty(tty), strempty(display),
394 yes_no(remote), strempty(remote_user), strempty(remote_host));
395
396 r = sd_bus_call_method(bus,
397 "org.freedesktop.login1",
398 "/org/freedesktop/login1",
399 "org.freedesktop.login1.Manager",
400 "CreateSession",
401 &error,
402 &reply,
403 "uusssssussbssa(sv)",
404 (uint32_t) pw->pw_uid,
405 (uint32_t) getpid(),
406 service,
407 type,
408 class,
409 desktop,
410 seat,
411 vtnr,
412 tty,
413 display,
414 remote,
415 remote_user,
416 remote_host,
417 0);
418 if (r < 0) {
419 if (sd_bus_error_has_name(&error, BUS_ERROR_SESSION_BUSY)) {
420 pam_syslog(handle, LOG_DEBUG, "Cannot create session: %s", bus_error_message(&error, r));
421 return PAM_SUCCESS;
422 } else {
423 pam_syslog(handle, LOG_ERR, "Failed to create session: %s", bus_error_message(&error, r));
424 return PAM_SYSTEM_ERR;
425 }
426 }
427
428 r = sd_bus_message_read(reply,
429 "soshusub",
430 &id,
431 &object_path,
432 &runtime_path,
433 &session_fd,
434 &original_uid,
435 &seat,
436 &vtnr,
437 &existing);
438 if (r < 0) {
439 pam_syslog(handle, LOG_ERR, "Failed to parse message: %s", strerror(-r));
440 return PAM_SESSION_ERR;
441 }
442
443 if (debug)
444 pam_syslog(handle, LOG_DEBUG, "Reply from logind: "
445 "id=%s object_path=%s runtime_path=%s session_fd=%d seat=%s vtnr=%u original_uid=%u",
446 id, object_path, runtime_path, session_fd, seat, vtnr, original_uid);
447
448 r = pam_misc_setenv(handle, "XDG_SESSION_ID", id, 0);
449 if (r != PAM_SUCCESS) {
450 pam_syslog(handle, LOG_ERR, "Failed to set session id.");
451 return r;
452 }
453
454 if (original_uid == pw->pw_uid) {
455 /* Don't set $XDG_RUNTIME_DIR if the user we now
456 * authenticated for does not match the original user
457 * of the session. We do this in order not to result
458 * in privileged apps clobbering the runtime directory
459 * unnecessarily. */
460
461 r = pam_misc_setenv(handle, "XDG_RUNTIME_DIR", runtime_path, 0);
462 if (r != PAM_SUCCESS) {
463 pam_syslog(handle, LOG_ERR, "Failed to set runtime dir.");
464 return r;
465 }
466
467 r = export_legacy_dbus_address(handle, pw->pw_uid, runtime_path);
468 if (r != PAM_SUCCESS)
469 return r;
470 }
471
472 if (!isempty(seat)) {
473 r = pam_misc_setenv(handle, "XDG_SEAT", seat, 0);
474 if (r != PAM_SUCCESS) {
475 pam_syslog(handle, LOG_ERR, "Failed to set seat.");
476 return r;
477 }
478 }
479
480 if (vtnr > 0) {
481 char buf[DECIMAL_STR_MAX(vtnr)];
482 sprintf(buf, "%u", vtnr);
483
484 r = pam_misc_setenv(handle, "XDG_VTNR", buf, 0);
485 if (r != PAM_SUCCESS) {
486 pam_syslog(handle, LOG_ERR, "Failed to set virtual terminal number.");
487 return r;
488 }
489 }
490
491 r = pam_set_data(handle, "systemd.existing", INT_TO_PTR(!!existing), NULL);
492 if (r != PAM_SUCCESS) {
493 pam_syslog(handle, LOG_ERR, "Failed to install existing flag.");
494 return r;
495 }
496
497 if (session_fd >= 0) {
498 session_fd = fcntl(session_fd, F_DUPFD_CLOEXEC, 3);
499 if (session_fd < 0) {
500 pam_syslog(handle, LOG_ERR, "Failed to dup session fd: %m");
501 return PAM_SESSION_ERR;
502 }
503
504 r = pam_set_data(handle, "systemd.session-fd", INT_TO_PTR(session_fd+1), NULL);
505 if (r != PAM_SUCCESS) {
506 pam_syslog(handle, LOG_ERR, "Failed to install session fd.");
507 safe_close(session_fd);
508 return r;
509 }
510 }
511
512 return PAM_SUCCESS;
513 }
514
515 _public_ PAM_EXTERN int pam_sm_close_session(
516 pam_handle_t *handle,
517 int flags,
518 int argc, const char **argv) {
519
520 _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
521 _cleanup_bus_flush_close_unref_ sd_bus *bus = NULL;
522 const void *existing = NULL;
523 const char *id;
524 int r;
525
526 assert(handle);
527
528 /* Only release session if it wasn't pre-existing when we
529 * tried to create it */
530 pam_get_data(handle, "systemd.existing", &existing);
531
532 id = pam_getenv(handle, "XDG_SESSION_ID");
533 if (id && !existing) {
534
535 /* Before we go and close the FIFO we need to tell
536 * logind that this is a clean session shutdown, so
537 * that it doesn't just go and slaughter us
538 * immediately after closing the fd */
539
540 r = sd_bus_open_system(&bus);
541 if (r < 0) {
542 pam_syslog(handle, LOG_ERR, "Failed to connect to system bus: %s", strerror(-r));
543 return PAM_SESSION_ERR;
544 }
545
546 r = sd_bus_call_method(bus,
547 "org.freedesktop.login1",
548 "/org/freedesktop/login1",
549 "org.freedesktop.login1.Manager",
550 "ReleaseSession",
551 &error,
552 NULL,
553 "s",
554 id);
555 if (r < 0) {
556 pam_syslog(handle, LOG_ERR, "Failed to release session: %s", bus_error_message(&error, r));
557 return PAM_SESSION_ERR;
558 }
559 }
560
561 /* Note that we are knowingly leaking the FIFO fd here. This
562 * way, logind can watch us die. If we closed it here it would
563 * not have any clue when that is completed. Given that one
564 * cannot really have multiple PAM sessions open from the same
565 * process this means we will leak one FD at max. */
566
567 return PAM_SUCCESS;
568 }