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