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