]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/login/pam_systemd.c
Merge pull request #1934 from martinpitt/master
[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_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
224 _cleanup_(sd_bus_message_unrefp) 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_(sd_bus_flush_close_unrefp) 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 *rt = NULL;
271
272 if (asprintf(&rt, "/run/user/"UID_FMT, pw->pw_uid) < 0)
273 return PAM_BUF_ERR;
274
275 r = pam_misc_setenv(handle, "XDG_RUNTIME_DIR", rt, 0);
276 if (r != PAM_SUCCESS) {
277 pam_syslog(handle, LOG_ERR, "Failed to set runtime dir.");
278 return r;
279 }
280
281 r = export_legacy_dbus_address(handle, pw->pw_uid, rt);
282 if (r != PAM_SUCCESS)
283 return r;
284
285 return PAM_SUCCESS;
286 }
287
288 /* Otherwise, we ask logind to create a session for us */
289
290 pam_get_item(handle, PAM_XDISPLAY, (const void**) &display);
291 pam_get_item(handle, PAM_TTY, (const void**) &tty);
292 pam_get_item(handle, PAM_RUSER, (const void**) &remote_user);
293 pam_get_item(handle, PAM_RHOST, (const void**) &remote_host);
294
295 seat = pam_getenv(handle, "XDG_SEAT");
296 if (isempty(seat))
297 seat = getenv("XDG_SEAT");
298
299 cvtnr = pam_getenv(handle, "XDG_VTNR");
300 if (isempty(cvtnr))
301 cvtnr = getenv("XDG_VTNR");
302
303 type = pam_getenv(handle, "XDG_SESSION_TYPE");
304 if (isempty(type))
305 type = getenv("XDG_SESSION_TYPE");
306 if (isempty(type))
307 type = type_pam;
308
309 class = pam_getenv(handle, "XDG_SESSION_CLASS");
310 if (isempty(class))
311 class = getenv("XDG_SESSION_CLASS");
312 if (isempty(class))
313 class = class_pam;
314
315 desktop = pam_getenv(handle, "XDG_SESSION_DESKTOP");
316 if (isempty(desktop))
317 desktop = getenv("XDG_SESSION_DESKTOP");
318
319 tty = strempty(tty);
320
321 if (strchr(tty, ':')) {
322 /* A tty with a colon is usually an X11 display,
323 * placed there to show up in utmp. We rearrange
324 * things and don't pretend that an X display was a
325 * tty. */
326
327 if (isempty(display))
328 display = tty;
329 tty = NULL;
330 } else if (streq(tty, "cron")) {
331 /* cron has been setting PAM_TTY to "cron" for a very
332 * long time and it probably shouldn't stop doing that
333 * for compatibility reasons. */
334 type = "unspecified";
335 class = "background";
336 tty = NULL;
337 } else if (streq(tty, "ssh")) {
338 /* ssh has been setting PAM_TTY to "ssh" for a very
339 * long time and probably shouldn't stop doing that
340 * for compatibility reasons. */
341 type ="tty";
342 class = "user";
343 tty = NULL;
344 }
345
346 /* If this fails vtnr will be 0, that's intended */
347 if (!isempty(cvtnr))
348 (void) safe_atou32(cvtnr, &vtnr);
349
350 if (!isempty(display) && !vtnr) {
351 if (isempty(seat))
352 get_seat_from_display(display, &seat, &vtnr);
353 else if (streq(seat, "seat0"))
354 get_seat_from_display(display, NULL, &vtnr);
355 }
356
357 if (seat && !streq(seat, "seat0") && vtnr != 0) {
358 pam_syslog(handle, LOG_DEBUG, "Ignoring vtnr %"PRIu32" for %s which is not seat0", vtnr, seat);
359 vtnr = 0;
360 }
361
362 if (isempty(type))
363 type = !isempty(display) ? "x11" :
364 !isempty(tty) ? "tty" : "unspecified";
365
366 if (isempty(class))
367 class = streq(type, "unspecified") ? "background" : "user";
368
369 remote = !isempty(remote_host) && !is_localhost(remote_host);
370
371 /* Talk to logind over the message bus */
372
373 r = sd_bus_open_system(&bus);
374 if (r < 0) {
375 pam_syslog(handle, LOG_ERR, "Failed to connect to system bus: %s", strerror(-r));
376 return PAM_SESSION_ERR;
377 }
378
379 if (debug)
380 pam_syslog(handle, LOG_DEBUG, "Asking logind to create session: "
381 "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",
382 pw->pw_uid, getpid(),
383 strempty(service),
384 type, class, strempty(desktop),
385 strempty(seat), vtnr, strempty(tty), strempty(display),
386 yes_no(remote), strempty(remote_user), strempty(remote_host));
387
388 r = sd_bus_call_method(bus,
389 "org.freedesktop.login1",
390 "/org/freedesktop/login1",
391 "org.freedesktop.login1.Manager",
392 "CreateSession",
393 &error,
394 &reply,
395 "uusssssussbssa(sv)",
396 (uint32_t) pw->pw_uid,
397 (uint32_t) getpid(),
398 service,
399 type,
400 class,
401 desktop,
402 seat,
403 vtnr,
404 tty,
405 display,
406 remote,
407 remote_user,
408 remote_host,
409 0);
410 if (r < 0) {
411 if (sd_bus_error_has_name(&error, BUS_ERROR_SESSION_BUSY)) {
412 pam_syslog(handle, LOG_DEBUG, "Cannot create session: %s", bus_error_message(&error, r));
413 return PAM_SUCCESS;
414 } else {
415 pam_syslog(handle, LOG_ERR, "Failed to create session: %s", bus_error_message(&error, r));
416 return PAM_SYSTEM_ERR;
417 }
418 }
419
420 r = sd_bus_message_read(reply,
421 "soshusub",
422 &id,
423 &object_path,
424 &runtime_path,
425 &session_fd,
426 &original_uid,
427 &seat,
428 &vtnr,
429 &existing);
430 if (r < 0) {
431 pam_syslog(handle, LOG_ERR, "Failed to parse message: %s", strerror(-r));
432 return PAM_SESSION_ERR;
433 }
434
435 if (debug)
436 pam_syslog(handle, LOG_DEBUG, "Reply from logind: "
437 "id=%s object_path=%s runtime_path=%s session_fd=%d seat=%s vtnr=%u original_uid=%u",
438 id, object_path, runtime_path, session_fd, seat, vtnr, original_uid);
439
440 r = pam_misc_setenv(handle, "XDG_SESSION_ID", id, 0);
441 if (r != PAM_SUCCESS) {
442 pam_syslog(handle, LOG_ERR, "Failed to set session id.");
443 return r;
444 }
445
446 if (original_uid == pw->pw_uid) {
447 /* Don't set $XDG_RUNTIME_DIR if the user we now
448 * authenticated for does not match the original user
449 * of the session. We do this in order not to result
450 * in privileged apps clobbering the runtime directory
451 * unnecessarily. */
452
453 r = pam_misc_setenv(handle, "XDG_RUNTIME_DIR", runtime_path, 0);
454 if (r != PAM_SUCCESS) {
455 pam_syslog(handle, LOG_ERR, "Failed to set runtime dir.");
456 return r;
457 }
458
459 r = export_legacy_dbus_address(handle, pw->pw_uid, runtime_path);
460 if (r != PAM_SUCCESS)
461 return r;
462 }
463
464 if (!isempty(seat)) {
465 r = pam_misc_setenv(handle, "XDG_SEAT", seat, 0);
466 if (r != PAM_SUCCESS) {
467 pam_syslog(handle, LOG_ERR, "Failed to set seat.");
468 return r;
469 }
470 }
471
472 if (vtnr > 0) {
473 char buf[DECIMAL_STR_MAX(vtnr)];
474 sprintf(buf, "%u", vtnr);
475
476 r = pam_misc_setenv(handle, "XDG_VTNR", buf, 0);
477 if (r != PAM_SUCCESS) {
478 pam_syslog(handle, LOG_ERR, "Failed to set virtual terminal number.");
479 return r;
480 }
481 }
482
483 r = pam_set_data(handle, "systemd.existing", INT_TO_PTR(!!existing), NULL);
484 if (r != PAM_SUCCESS) {
485 pam_syslog(handle, LOG_ERR, "Failed to install existing flag.");
486 return r;
487 }
488
489 if (session_fd >= 0) {
490 session_fd = fcntl(session_fd, F_DUPFD_CLOEXEC, 3);
491 if (session_fd < 0) {
492 pam_syslog(handle, LOG_ERR, "Failed to dup session fd: %m");
493 return PAM_SESSION_ERR;
494 }
495
496 r = pam_set_data(handle, "systemd.session-fd", FD_TO_PTR(session_fd), NULL);
497 if (r != PAM_SUCCESS) {
498 pam_syslog(handle, LOG_ERR, "Failed to install session fd.");
499 safe_close(session_fd);
500 return r;
501 }
502 }
503
504 return PAM_SUCCESS;
505 }
506
507 _public_ PAM_EXTERN int pam_sm_close_session(
508 pam_handle_t *handle,
509 int flags,
510 int argc, const char **argv) {
511
512 _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
513 _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
514 const void *existing = NULL;
515 const char *id;
516 int r;
517
518 assert(handle);
519
520 /* Only release session if it wasn't pre-existing when we
521 * tried to create it */
522 pam_get_data(handle, "systemd.existing", &existing);
523
524 id = pam_getenv(handle, "XDG_SESSION_ID");
525 if (id && !existing) {
526
527 /* Before we go and close the FIFO we need to tell
528 * logind that this is a clean session shutdown, so
529 * that it doesn't just go and slaughter us
530 * immediately after closing the fd */
531
532 r = sd_bus_open_system(&bus);
533 if (r < 0) {
534 pam_syslog(handle, LOG_ERR, "Failed to connect to system bus: %s", strerror(-r));
535 return PAM_SESSION_ERR;
536 }
537
538 r = sd_bus_call_method(bus,
539 "org.freedesktop.login1",
540 "/org/freedesktop/login1",
541 "org.freedesktop.login1.Manager",
542 "ReleaseSession",
543 &error,
544 NULL,
545 "s",
546 id);
547 if (r < 0) {
548 pam_syslog(handle, LOG_ERR, "Failed to release session: %s", bus_error_message(&error, r));
549 return PAM_SESSION_ERR;
550 }
551 }
552
553 /* Note that we are knowingly leaking the FIFO fd here. This
554 * way, logind can watch us die. If we closed it here it would
555 * not have any clue when that is completed. Given that one
556 * cannot really have multiple PAM sessions open from the same
557 * process this means we will leak one FD at max. */
558
559 return PAM_SUCCESS;
560 }