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