]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/login/pam_systemd.c
util: split all hostname related calls into hostname-util.c
[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 #ifdef ENABLE_KDBUS
181 _cleanup_free_ char *s = NULL;
182 int r;
183
184 /* skip export if kdbus is not active */
185 if (access("/sys/fs/kdbus", F_OK) < 0)
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 #endif
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_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 pam_syslog(handle, LOG_ERR, "Failed to create session: %s", bus_error_message(&error, r));
404 return PAM_SYSTEM_ERR;
405 }
406
407 r = sd_bus_message_read(reply,
408 "soshusub",
409 &id,
410 &object_path,
411 &runtime_path,
412 &session_fd,
413 &original_uid,
414 &seat,
415 &vtnr,
416 &existing);
417 if (r < 0) {
418 pam_syslog(handle, LOG_ERR, "Failed to parse message: %s", strerror(-r));
419 return PAM_SESSION_ERR;
420 }
421
422 if (debug)
423 pam_syslog(handle, LOG_DEBUG, "Reply from logind: "
424 "id=%s object_path=%s runtime_path=%s session_fd=%d seat=%s vtnr=%u original_uid=%u",
425 id, object_path, runtime_path, session_fd, seat, vtnr, original_uid);
426
427 r = pam_misc_setenv(handle, "XDG_SESSION_ID", id, 0);
428 if (r != PAM_SUCCESS) {
429 pam_syslog(handle, LOG_ERR, "Failed to set session id.");
430 return r;
431 }
432
433 if (original_uid == pw->pw_uid) {
434 /* Don't set $XDG_RUNTIME_DIR if the user we now
435 * authenticated for does not match the original user
436 * of the session. We do this in order not to result
437 * in privileged apps clobbering the runtime directory
438 * unnecessarily. */
439
440 r = pam_misc_setenv(handle, "XDG_RUNTIME_DIR", runtime_path, 0);
441 if (r != PAM_SUCCESS) {
442 pam_syslog(handle, LOG_ERR, "Failed to set runtime dir.");
443 return r;
444 }
445
446 r = export_legacy_dbus_address(handle, pw->pw_uid, runtime_path);
447 if (r != PAM_SUCCESS)
448 return r;
449 }
450
451 if (!isempty(seat)) {
452 r = pam_misc_setenv(handle, "XDG_SEAT", seat, 0);
453 if (r != PAM_SUCCESS) {
454 pam_syslog(handle, LOG_ERR, "Failed to set seat.");
455 return r;
456 }
457 }
458
459 if (vtnr > 0) {
460 char buf[DECIMAL_STR_MAX(vtnr)];
461 sprintf(buf, "%u", vtnr);
462
463 r = pam_misc_setenv(handle, "XDG_VTNR", buf, 0);
464 if (r != PAM_SUCCESS) {
465 pam_syslog(handle, LOG_ERR, "Failed to set virtual terminal number.");
466 return r;
467 }
468 }
469
470 r = pam_set_data(handle, "systemd.existing", INT_TO_PTR(!!existing), NULL);
471 if (r != PAM_SUCCESS) {
472 pam_syslog(handle, LOG_ERR, "Failed to install existing flag.");
473 return r;
474 }
475
476 if (session_fd >= 0) {
477 session_fd = fcntl(session_fd, F_DUPFD_CLOEXEC, 3);
478 if (session_fd < 0) {
479 pam_syslog(handle, LOG_ERR, "Failed to dup session fd: %m");
480 return PAM_SESSION_ERR;
481 }
482
483 r = pam_set_data(handle, "systemd.session-fd", INT_TO_PTR(session_fd+1), NULL);
484 if (r != PAM_SUCCESS) {
485 pam_syslog(handle, LOG_ERR, "Failed to install session fd.");
486 safe_close(session_fd);
487 return r;
488 }
489 }
490
491 return PAM_SUCCESS;
492 }
493
494 _public_ PAM_EXTERN int pam_sm_close_session(
495 pam_handle_t *handle,
496 int flags,
497 int argc, const char **argv) {
498
499 _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
500 _cleanup_bus_close_unref_ sd_bus *bus = NULL;
501 const void *existing = NULL;
502 const char *id;
503 int r;
504
505 assert(handle);
506
507 /* Only release session if it wasn't pre-existing when we
508 * tried to create it */
509 pam_get_data(handle, "systemd.existing", &existing);
510
511 id = pam_getenv(handle, "XDG_SESSION_ID");
512 if (id && !existing) {
513
514 /* Before we go and close the FIFO we need to tell
515 * logind that this is a clean session shutdown, so
516 * that it doesn't just go and slaughter us
517 * immediately after closing the fd */
518
519 r = sd_bus_open_system(&bus);
520 if (r < 0) {
521 pam_syslog(handle, LOG_ERR, "Failed to connect to system bus: %s", strerror(-r));
522 return PAM_SESSION_ERR;
523 }
524
525 r = sd_bus_call_method(bus,
526 "org.freedesktop.login1",
527 "/org/freedesktop/login1",
528 "org.freedesktop.login1.Manager",
529 "ReleaseSession",
530 &error,
531 NULL,
532 "s",
533 id);
534 if (r < 0) {
535 pam_syslog(handle, LOG_ERR, "Failed to release session: %s", bus_error_message(&error, r));
536 return PAM_SESSION_ERR;
537 }
538 }
539
540 /* Note that we are knowingly leaking the FIFO fd here. This
541 * way, logind can watch us die. If we closed it here it would
542 * not have any clue when that is completed. Given that one
543 * cannot really have multiple PAM sessions open from the same
544 * process this means we will leak one FD at max. */
545
546 return PAM_SUCCESS;
547 }