]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/login/pam_systemd.c
Merge pull request #375 from msekletar/test-install-crashers
[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;
183
184 /* skip export if kdbus is not active */
185 if (!is_kdbus_available())
186 return PAM_SUCCESS;
187
188 if (asprintf(&s, KERNEL_USER_BUS_ADDRESS_FMT ";" UNIX_USER_BUS_ADDRESS_FMT, uid, runtime) < 0) {
189 pam_syslog(handle, LOG_ERR, "Failed to set bus variable.");
190 return PAM_BUF_ERR;
191 }
192
193 r = pam_misc_setenv(handle, "DBUS_SESSION_BUS_ADDRESS", s, 0);
194 if (r != PAM_SUCCESS) {
195 pam_syslog(handle, LOG_ERR, "Failed to set bus variable.");
196 return r;
197 }
198
199 return PAM_SUCCESS;
200 }
201
202 _public_ PAM_EXTERN int pam_sm_open_session(
203 pam_handle_t *handle,
204 int flags,
205 int argc, const char **argv) {
206
207 _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
208 _cleanup_bus_message_unref_ sd_bus_message *reply = NULL;
209 const char
210 *username, *id, *object_path, *runtime_path,
211 *service = NULL,
212 *tty = NULL, *display = NULL,
213 *remote_user = NULL, *remote_host = NULL,
214 *seat = NULL,
215 *type = NULL, *class = NULL,
216 *class_pam = NULL, *type_pam = NULL, *cvtnr = NULL, *desktop = NULL;
217 _cleanup_bus_flush_close_unref_ sd_bus *bus = NULL;
218 int session_fd = -1, existing, r;
219 bool debug = false, remote;
220 struct passwd *pw;
221 uint32_t vtnr = 0;
222 uid_t original_uid;
223
224 assert(handle);
225
226 /* Make this a NOP on non-logind systems */
227 if (!logind_running())
228 return PAM_SUCCESS;
229
230 if (parse_argv(handle,
231 argc, argv,
232 &class_pam,
233 &type_pam,
234 &debug) < 0)
235 return PAM_SESSION_ERR;
236
237 if (debug)
238 pam_syslog(handle, LOG_DEBUG, "pam-systemd initializing");
239
240 r = get_user_data(handle, &username, &pw);
241 if (r != PAM_SUCCESS) {
242 pam_syslog(handle, LOG_ERR, "Failed to get user data.");
243 return r;
244 }
245
246 /* Make sure we don't enter a loop by talking to
247 * systemd-logind when it is actually waiting for the
248 * background to finish start-up. If the service is
249 * "systemd-user" we simply set XDG_RUNTIME_DIR and
250 * leave. */
251
252 pam_get_item(handle, PAM_SERVICE, (const void**) &service);
253 if (streq_ptr(service, "systemd-user")) {
254 _cleanup_free_ char *p = NULL, *rt = NULL;
255
256 if (asprintf(&p, "/run/systemd/users/"UID_FMT, pw->pw_uid) < 0)
257 return PAM_BUF_ERR;
258
259 r = parse_env_file(p, NEWLINE,
260 "RUNTIME", &rt,
261 NULL);
262 if (r < 0 && r != -ENOENT)
263 return PAM_SESSION_ERR;
264
265 if (rt) {
266 r = pam_misc_setenv(handle, "XDG_RUNTIME_DIR", rt, 0);
267 if (r != PAM_SUCCESS) {
268 pam_syslog(handle, LOG_ERR, "Failed to set runtime dir.");
269 return r;
270 }
271
272 r = export_legacy_dbus_address(handle, pw->pw_uid, rt);
273 if (r != PAM_SUCCESS)
274 return r;
275 }
276
277 return PAM_SUCCESS;
278 }
279
280 /* Otherwise, we ask logind to create a session for us */
281
282 pam_get_item(handle, PAM_XDISPLAY, (const void**) &display);
283 pam_get_item(handle, PAM_TTY, (const void**) &tty);
284 pam_get_item(handle, PAM_RUSER, (const void**) &remote_user);
285 pam_get_item(handle, PAM_RHOST, (const void**) &remote_host);
286
287 seat = pam_getenv(handle, "XDG_SEAT");
288 if (isempty(seat))
289 seat = getenv("XDG_SEAT");
290
291 cvtnr = pam_getenv(handle, "XDG_VTNR");
292 if (isempty(cvtnr))
293 cvtnr = getenv("XDG_VTNR");
294
295 type = pam_getenv(handle, "XDG_SESSION_TYPE");
296 if (isempty(type))
297 type = getenv("XDG_SESSION_TYPE");
298 if (isempty(type))
299 type = type_pam;
300
301 class = pam_getenv(handle, "XDG_SESSION_CLASS");
302 if (isempty(class))
303 class = getenv("XDG_SESSION_CLASS");
304 if (isempty(class))
305 class = class_pam;
306
307 desktop = pam_getenv(handle, "XDG_SESSION_DESKTOP");
308 if (isempty(desktop))
309 desktop = getenv("XDG_SESSION_DESKTOP");
310
311 tty = strempty(tty);
312
313 if (strchr(tty, ':')) {
314 /* A tty with a colon is usually an X11 display,
315 * placed there to show up in utmp. We rearrange
316 * things and don't pretend that an X display was a
317 * tty. */
318
319 if (isempty(display))
320 display = tty;
321 tty = NULL;
322 } else if (streq(tty, "cron")) {
323 /* cron has been setting PAM_TTY to "cron" for a very
324 * long time and it probably shouldn't stop doing that
325 * for compatibility reasons. */
326 type = "unspecified";
327 class = "background";
328 tty = NULL;
329 } else if (streq(tty, "ssh")) {
330 /* ssh has been setting PAM_TTY to "ssh" for a very
331 * long time and probably shouldn't stop doing that
332 * for compatibility reasons. */
333 type ="tty";
334 class = "user";
335 tty = NULL;
336 }
337
338 /* If this fails vtnr will be 0, that's intended */
339 if (!isempty(cvtnr))
340 (void) safe_atou32(cvtnr, &vtnr);
341
342 if (!isempty(display) && !vtnr) {
343 if (isempty(seat))
344 get_seat_from_display(display, &seat, &vtnr);
345 else if (streq(seat, "seat0"))
346 get_seat_from_display(display, NULL, &vtnr);
347 }
348
349 if (seat && !streq(seat, "seat0") && vtnr != 0) {
350 pam_syslog(handle, LOG_DEBUG, "Ignoring vtnr %"PRIu32" for %s which is not seat0", vtnr, seat);
351 vtnr = 0;
352 }
353
354 if (isempty(type))
355 type = !isempty(display) ? "x11" :
356 !isempty(tty) ? "tty" : "unspecified";
357
358 if (isempty(class))
359 class = streq(type, "unspecified") ? "background" : "user";
360
361 remote = !isempty(remote_host) && !is_localhost(remote_host);
362
363 /* Talk to logind over the message bus */
364
365 r = sd_bus_open_system(&bus);
366 if (r < 0) {
367 pam_syslog(handle, LOG_ERR, "Failed to connect to system bus: %s", strerror(-r));
368 return PAM_SESSION_ERR;
369 }
370
371 if (debug)
372 pam_syslog(handle, LOG_DEBUG, "Asking logind to create session: "
373 "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",
374 pw->pw_uid, getpid(),
375 strempty(service),
376 type, class, strempty(desktop),
377 strempty(seat), vtnr, strempty(tty), strempty(display),
378 yes_no(remote), strempty(remote_user), strempty(remote_host));
379
380 r = sd_bus_call_method(bus,
381 "org.freedesktop.login1",
382 "/org/freedesktop/login1",
383 "org.freedesktop.login1.Manager",
384 "CreateSession",
385 &error,
386 &reply,
387 "uusssssussbssa(sv)",
388 (uint32_t) pw->pw_uid,
389 (uint32_t) getpid(),
390 service,
391 type,
392 class,
393 desktop,
394 seat,
395 vtnr,
396 tty,
397 display,
398 remote,
399 remote_user,
400 remote_host,
401 0);
402 if (r < 0) {
403 if (sd_bus_error_has_name(&error, BUS_ERROR_SESSION_BUSY)) {
404 pam_syslog(handle, LOG_DEBUG, "Cannot create session: %s", bus_error_message(&error, r));
405 return PAM_SUCCESS;
406 } else {
407 pam_syslog(handle, LOG_ERR, "Failed to create session: %s", bus_error_message(&error, r));
408 return PAM_SYSTEM_ERR;
409 }
410 }
411
412 r = sd_bus_message_read(reply,
413 "soshusub",
414 &id,
415 &object_path,
416 &runtime_path,
417 &session_fd,
418 &original_uid,
419 &seat,
420 &vtnr,
421 &existing);
422 if (r < 0) {
423 pam_syslog(handle, LOG_ERR, "Failed to parse message: %s", strerror(-r));
424 return PAM_SESSION_ERR;
425 }
426
427 if (debug)
428 pam_syslog(handle, LOG_DEBUG, "Reply from logind: "
429 "id=%s object_path=%s runtime_path=%s session_fd=%d seat=%s vtnr=%u original_uid=%u",
430 id, object_path, runtime_path, session_fd, seat, vtnr, original_uid);
431
432 r = pam_misc_setenv(handle, "XDG_SESSION_ID", id, 0);
433 if (r != PAM_SUCCESS) {
434 pam_syslog(handle, LOG_ERR, "Failed to set session id.");
435 return r;
436 }
437
438 if (original_uid == pw->pw_uid) {
439 /* Don't set $XDG_RUNTIME_DIR if the user we now
440 * authenticated for does not match the original user
441 * of the session. We do this in order not to result
442 * in privileged apps clobbering the runtime directory
443 * unnecessarily. */
444
445 r = pam_misc_setenv(handle, "XDG_RUNTIME_DIR", runtime_path, 0);
446 if (r != PAM_SUCCESS) {
447 pam_syslog(handle, LOG_ERR, "Failed to set runtime dir.");
448 return r;
449 }
450
451 r = export_legacy_dbus_address(handle, pw->pw_uid, runtime_path);
452 if (r != PAM_SUCCESS)
453 return r;
454 }
455
456 if (!isempty(seat)) {
457 r = pam_misc_setenv(handle, "XDG_SEAT", seat, 0);
458 if (r != PAM_SUCCESS) {
459 pam_syslog(handle, LOG_ERR, "Failed to set seat.");
460 return r;
461 }
462 }
463
464 if (vtnr > 0) {
465 char buf[DECIMAL_STR_MAX(vtnr)];
466 sprintf(buf, "%u", vtnr);
467
468 r = pam_misc_setenv(handle, "XDG_VTNR", buf, 0);
469 if (r != PAM_SUCCESS) {
470 pam_syslog(handle, LOG_ERR, "Failed to set virtual terminal number.");
471 return r;
472 }
473 }
474
475 r = pam_set_data(handle, "systemd.existing", INT_TO_PTR(!!existing), NULL);
476 if (r != PAM_SUCCESS) {
477 pam_syslog(handle, LOG_ERR, "Failed to install existing flag.");
478 return r;
479 }
480
481 if (session_fd >= 0) {
482 session_fd = fcntl(session_fd, F_DUPFD_CLOEXEC, 3);
483 if (session_fd < 0) {
484 pam_syslog(handle, LOG_ERR, "Failed to dup session fd: %m");
485 return PAM_SESSION_ERR;
486 }
487
488 r = pam_set_data(handle, "systemd.session-fd", INT_TO_PTR(session_fd+1), NULL);
489 if (r != PAM_SUCCESS) {
490 pam_syslog(handle, LOG_ERR, "Failed to install session fd.");
491 safe_close(session_fd);
492 return r;
493 }
494 }
495
496 return PAM_SUCCESS;
497 }
498
499 _public_ PAM_EXTERN int pam_sm_close_session(
500 pam_handle_t *handle,
501 int flags,
502 int argc, const char **argv) {
503
504 _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
505 _cleanup_bus_flush_close_unref_ sd_bus *bus = NULL;
506 const void *existing = NULL;
507 const char *id;
508 int r;
509
510 assert(handle);
511
512 /* Only release session if it wasn't pre-existing when we
513 * tried to create it */
514 pam_get_data(handle, "systemd.existing", &existing);
515
516 id = pam_getenv(handle, "XDG_SESSION_ID");
517 if (id && !existing) {
518
519 /* Before we go and close the FIFO we need to tell
520 * logind that this is a clean session shutdown, so
521 * that it doesn't just go and slaughter us
522 * immediately after closing the fd */
523
524 r = sd_bus_open_system(&bus);
525 if (r < 0) {
526 pam_syslog(handle, LOG_ERR, "Failed to connect to system bus: %s", strerror(-r));
527 return PAM_SESSION_ERR;
528 }
529
530 r = sd_bus_call_method(bus,
531 "org.freedesktop.login1",
532 "/org/freedesktop/login1",
533 "org.freedesktop.login1.Manager",
534 "ReleaseSession",
535 &error,
536 NULL,
537 "s",
538 id);
539 if (r < 0) {
540 pam_syslog(handle, LOG_ERR, "Failed to release session: %s", bus_error_message(&error, r));
541 return PAM_SESSION_ERR;
542 }
543 }
544
545 /* Note that we are knowingly leaking the FIFO fd here. This
546 * way, logind can watch us die. If we closed it here it would
547 * not have any clue when that is completed. Given that one
548 * cannot really have multiple PAM sessions open from the same
549 * process this means we will leak one FD at max. */
550
551 return PAM_SUCCESS;
552 }