]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/login/pam_systemd.c
Merge pull request #417 from ssahani/ipv6-private
[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 "util.h"
35 #include "audit.h"
36 #include "macro.h"
37 #include "strv.h"
38 #include "bus-util.h"
39 #include "def.h"
40 #include "socket-util.h"
41 #include "fileio.h"
42 #include "bus-error.h"
43 #include "formats-util.h"
44 #include "terminal-util.h"
45 #include "hostname-util.h"
46
47 static int parse_argv(
48 pam_handle_t *handle,
49 int argc, const char **argv,
50 const char **class,
51 const char **type,
52 bool *debug) {
53
54 unsigned i;
55
56 assert(argc >= 0);
57 assert(argc == 0 || argv);
58
59 for (i = 0; i < (unsigned) argc; i++) {
60 if (startswith(argv[i], "class=")) {
61 if (class)
62 *class = argv[i] + 6;
63
64 } else if (startswith(argv[i], "type=")) {
65 if (type)
66 *type = argv[i] + 5;
67
68 } else if (streq(argv[i], "debug")) {
69 if (debug)
70 *debug = true;
71
72 } else if (startswith(argv[i], "debug=")) {
73 int k;
74
75 k = parse_boolean(argv[i] + 6);
76 if (k < 0)
77 pam_syslog(handle, LOG_WARNING, "Failed to parse debug= argument, ignoring.");
78 else if (debug)
79 *debug = k;
80
81 } else
82 pam_syslog(handle, LOG_WARNING, "Unknown parameter '%s', ignoring", argv[i]);
83 }
84
85 return 0;
86 }
87
88 static int get_user_data(
89 pam_handle_t *handle,
90 const char **ret_username,
91 struct passwd **ret_pw) {
92
93 const char *username = NULL;
94 struct passwd *pw = NULL;
95 int r;
96
97 assert(handle);
98 assert(ret_username);
99 assert(ret_pw);
100
101 r = pam_get_user(handle, &username, NULL);
102 if (r != PAM_SUCCESS) {
103 pam_syslog(handle, LOG_ERR, "Failed to get user name.");
104 return r;
105 }
106
107 if (isempty(username)) {
108 pam_syslog(handle, LOG_ERR, "User name not valid.");
109 return PAM_AUTH_ERR;
110 }
111
112 pw = pam_modutil_getpwnam(handle, username);
113 if (!pw) {
114 pam_syslog(handle, LOG_ERR, "Failed to get user data.");
115 return PAM_USER_UNKNOWN;
116 }
117
118 *ret_pw = pw;
119 *ret_username = username;
120
121 return PAM_SUCCESS;
122 }
123
124 static int get_seat_from_display(const char *display, const char **seat, uint32_t *vtnr) {
125 union sockaddr_union sa = {
126 .un.sun_family = AF_UNIX,
127 };
128 _cleanup_free_ char *p = NULL, *tty = NULL;
129 _cleanup_close_ int fd = -1;
130 struct ucred ucred;
131 int v, r;
132
133 assert(display);
134 assert(vtnr);
135
136 /* We deduce the X11 socket from the display name, then use
137 * SO_PEERCRED to determine the X11 server process, ask for
138 * the controlling tty of that and if it's a VC then we know
139 * the seat and the virtual terminal. Sounds ugly, is only
140 * semi-ugly. */
141
142 r = socket_from_display(display, &p);
143 if (r < 0)
144 return r;
145 strncpy(sa.un.sun_path, p, sizeof(sa.un.sun_path)-1);
146
147 fd = socket(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC, 0);
148 if (fd < 0)
149 return -errno;
150
151 if (connect(fd, &sa.sa, offsetof(struct sockaddr_un, sun_path) + strlen(sa.un.sun_path)) < 0)
152 return -errno;
153
154 r = getpeercred(fd, &ucred);
155 if (r < 0)
156 return r;
157
158 r = get_ctty(ucred.pid, NULL, &tty);
159 if (r < 0)
160 return r;
161
162 v = vtnr_from_tty(tty);
163 if (v < 0)
164 return v;
165 else if (v == 0)
166 return -ENOENT;
167
168 if (seat)
169 *seat = "seat0";
170 *vtnr = (uint32_t) v;
171
172 return 0;
173 }
174
175 static int export_legacy_dbus_address(
176 pam_handle_t *handle,
177 uid_t uid,
178 const char *runtime) {
179
180 _cleanup_free_ char *s = NULL;
181 int r;
182
183 /* skip export if kdbus is not active */
184 if (!is_kdbus_available())
185 return PAM_SUCCESS;
186
187 if (asprintf(&s, KERNEL_USER_BUS_ADDRESS_FMT ";" UNIX_USER_BUS_ADDRESS_FMT, 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
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_flush_close_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/"UID_FMT, 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 (void) 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 %"PRIu32" 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) && !is_localhost(remote_host);
361
362 /* Talk to logind over the message bus */
363
364 r = sd_bus_open_system(&bus);
365 if (r < 0) {
366 pam_syslog(handle, LOG_ERR, "Failed to connect to system bus: %s", strerror(-r));
367 return PAM_SESSION_ERR;
368 }
369
370 if (debug)
371 pam_syslog(handle, LOG_DEBUG, "Asking logind to create session: "
372 "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",
373 pw->pw_uid, getpid(),
374 strempty(service),
375 type, class, strempty(desktop),
376 strempty(seat), vtnr, strempty(tty), strempty(display),
377 yes_no(remote), strempty(remote_user), strempty(remote_host));
378
379 r = sd_bus_call_method(bus,
380 "org.freedesktop.login1",
381 "/org/freedesktop/login1",
382 "org.freedesktop.login1.Manager",
383 "CreateSession",
384 &error,
385 &reply,
386 "uusssssussbssa(sv)",
387 (uint32_t) pw->pw_uid,
388 (uint32_t) getpid(),
389 service,
390 type,
391 class,
392 desktop,
393 seat,
394 vtnr,
395 tty,
396 display,
397 remote,
398 remote_user,
399 remote_host,
400 0);
401 if (r < 0) {
402 pam_syslog(handle, LOG_ERR, "Failed to create session: %s", bus_error_message(&error, r));
403 return PAM_SYSTEM_ERR;
404 }
405
406 r = sd_bus_message_read(reply,
407 "soshusub",
408 &id,
409 &object_path,
410 &runtime_path,
411 &session_fd,
412 &original_uid,
413 &seat,
414 &vtnr,
415 &existing);
416 if (r < 0) {
417 pam_syslog(handle, LOG_ERR, "Failed to parse message: %s", strerror(-r));
418 return PAM_SESSION_ERR;
419 }
420
421 if (debug)
422 pam_syslog(handle, LOG_DEBUG, "Reply from logind: "
423 "id=%s object_path=%s runtime_path=%s session_fd=%d seat=%s vtnr=%u original_uid=%u",
424 id, object_path, runtime_path, session_fd, seat, vtnr, original_uid);
425
426 r = pam_misc_setenv(handle, "XDG_SESSION_ID", id, 0);
427 if (r != PAM_SUCCESS) {
428 pam_syslog(handle, LOG_ERR, "Failed to set session id.");
429 return r;
430 }
431
432 if (original_uid == pw->pw_uid) {
433 /* Don't set $XDG_RUNTIME_DIR if the user we now
434 * authenticated for does not match the original user
435 * of the session. We do this in order not to result
436 * in privileged apps clobbering the runtime directory
437 * unnecessarily. */
438
439 r = pam_misc_setenv(handle, "XDG_RUNTIME_DIR", runtime_path, 0);
440 if (r != PAM_SUCCESS) {
441 pam_syslog(handle, LOG_ERR, "Failed to set runtime dir.");
442 return r;
443 }
444
445 r = export_legacy_dbus_address(handle, pw->pw_uid, runtime_path);
446 if (r != PAM_SUCCESS)
447 return r;
448 }
449
450 if (!isempty(seat)) {
451 r = pam_misc_setenv(handle, "XDG_SEAT", seat, 0);
452 if (r != PAM_SUCCESS) {
453 pam_syslog(handle, LOG_ERR, "Failed to set seat.");
454 return r;
455 }
456 }
457
458 if (vtnr > 0) {
459 char buf[DECIMAL_STR_MAX(vtnr)];
460 sprintf(buf, "%u", vtnr);
461
462 r = pam_misc_setenv(handle, "XDG_VTNR", buf, 0);
463 if (r != PAM_SUCCESS) {
464 pam_syslog(handle, LOG_ERR, "Failed to set virtual terminal number.");
465 return r;
466 }
467 }
468
469 r = pam_set_data(handle, "systemd.existing", INT_TO_PTR(!!existing), NULL);
470 if (r != PAM_SUCCESS) {
471 pam_syslog(handle, LOG_ERR, "Failed to install existing flag.");
472 return r;
473 }
474
475 if (session_fd >= 0) {
476 session_fd = fcntl(session_fd, F_DUPFD_CLOEXEC, 3);
477 if (session_fd < 0) {
478 pam_syslog(handle, LOG_ERR, "Failed to dup session fd: %m");
479 return PAM_SESSION_ERR;
480 }
481
482 r = pam_set_data(handle, "systemd.session-fd", INT_TO_PTR(session_fd+1), NULL);
483 if (r != PAM_SUCCESS) {
484 pam_syslog(handle, LOG_ERR, "Failed to install session fd.");
485 safe_close(session_fd);
486 return r;
487 }
488 }
489
490 return PAM_SUCCESS;
491 }
492
493 _public_ PAM_EXTERN int pam_sm_close_session(
494 pam_handle_t *handle,
495 int flags,
496 int argc, const char **argv) {
497
498 _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
499 _cleanup_bus_flush_close_unref_ sd_bus *bus = NULL;
500 const void *existing = NULL;
501 const char *id;
502 int r;
503
504 assert(handle);
505
506 /* Only release session if it wasn't pre-existing when we
507 * tried to create it */
508 pam_get_data(handle, "systemd.existing", &existing);
509
510 id = pam_getenv(handle, "XDG_SESSION_ID");
511 if (id && !existing) {
512
513 /* Before we go and close the FIFO we need to tell
514 * logind that this is a clean session shutdown, so
515 * that it doesn't just go and slaughter us
516 * immediately after closing the fd */
517
518 r = sd_bus_open_system(&bus);
519 if (r < 0) {
520 pam_syslog(handle, LOG_ERR, "Failed to connect to system bus: %s", strerror(-r));
521 return PAM_SESSION_ERR;
522 }
523
524 r = sd_bus_call_method(bus,
525 "org.freedesktop.login1",
526 "/org/freedesktop/login1",
527 "org.freedesktop.login1.Manager",
528 "ReleaseSession",
529 &error,
530 NULL,
531 "s",
532 id);
533 if (r < 0) {
534 pam_syslog(handle, LOG_ERR, "Failed to release session: %s", bus_error_message(&error, r));
535 return PAM_SESSION_ERR;
536 }
537 }
538
539 /* Note that we are knowingly leaking the FIFO fd here. This
540 * way, logind can watch us die. If we closed it here it would
541 * not have any clue when that is completed. Given that one
542 * cannot really have multiple PAM sessions open from the same
543 * process this means we will leak one FD at max. */
544
545 return PAM_SUCCESS;
546 }