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