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