]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/login/pam-module.c
Add pam configuration to allow user sessions to work out of the box
[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;
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 /* pam_syslog(handle, LOG_INFO, "pam-systemd initializing"); */
203
204 /* Make this a NOP on non-logind systems */
205 if (!logind_running())
206 return PAM_SUCCESS;
207
208 if (parse_argv(handle,
209 argc, argv,
210 &class_pam,
211 &debug) < 0) {
212 r = PAM_SESSION_ERR;
213 goto finish;
214 }
215
216 r = get_user_data(handle, &username, &pw);
217 if (r != PAM_SUCCESS)
218 goto finish;
219
220 /* Make sure we don't enter a loop by talking to
221 * systemd-logind when it is actually waiting for the
222 * background to finish start-up. If the service is
223 * "systemd-user" we simply set XDG_RUNTIME_DIR and
224 * leave. */
225
226 pam_get_item(handle, PAM_SERVICE, (const void**) &service);
227 if (streq_ptr(service, "systemd-user")) {
228 char *p, *rt = NULL;
229
230 if (asprintf(&p, "/run/systemd/users/%lu", (unsigned long) pw->pw_uid) < 0) {
231 r = PAM_BUF_ERR;
232 goto finish;
233 }
234
235 r = parse_env_file(p, NEWLINE,
236 "RUNTIME", &rt,
237 NULL);
238 free(p);
239
240 if (r < 0 && r != -ENOENT) {
241 r = PAM_SESSION_ERR;
242 free(rt);
243 goto finish;
244 }
245
246 if (rt) {
247 r = pam_misc_setenv(handle, "XDG_RUNTIME_DIR", rt, 0);
248 free(rt);
249
250 if (r != PAM_SUCCESS) {
251 pam_syslog(handle, LOG_ERR, "Failed to set runtime dir.");
252 goto finish;
253 }
254 }
255
256 r = PAM_SUCCESS;
257 goto finish;
258 }
259
260 dbus_connection_set_change_sigpipe(FALSE);
261
262 bus = dbus_bus_get_private(DBUS_BUS_SYSTEM, &error);
263 if (!bus) {
264 pam_syslog(handle, LOG_ERR, "Failed to connect to system bus: %s", bus_error_message(&error));
265 r = PAM_SESSION_ERR;
266 goto finish;
267 }
268
269 m = dbus_message_new_method_call(
270 "org.freedesktop.login1",
271 "/org/freedesktop/login1",
272 "org.freedesktop.login1.Manager",
273 "CreateSession");
274 if (!m) {
275 pam_syslog(handle, LOG_ERR, "Could not allocate create session message.");
276 r = PAM_BUF_ERR;
277 goto finish;
278 }
279
280 uid = pw->pw_uid;
281 pid = getpid();
282
283 pam_get_item(handle, PAM_XDISPLAY, (const void**) &display);
284 pam_get_item(handle, PAM_TTY, (const void**) &tty);
285 pam_get_item(handle, PAM_RUSER, (const void**) &remote_user);
286 pam_get_item(handle, PAM_RHOST, (const void**) &remote_host);
287
288 seat = pam_getenv(handle, "XDG_SEAT");
289 if (isempty(seat))
290 seat = getenv("XDG_SEAT");
291
292 cvtnr = pam_getenv(handle, "XDG_VTNR");
293 if (isempty(cvtnr))
294 cvtnr = getenv("XDG_VTNR");
295
296 service = strempty(service);
297 tty = strempty(tty);
298 display = strempty(display);
299 remote_user = strempty(remote_user);
300 remote_host = strempty(remote_host);
301 seat = strempty(seat);
302
303 if (strchr(tty, ':')) {
304 /* A tty with a colon is usually an X11 display,
305 * placed there to show up in utmp. We rearrange
306 * things and don't pretend that an X display was a
307 * tty. */
308
309 if (isempty(display))
310 display = tty;
311 tty = "";
312 } else if (streq(tty, "cron")) {
313 /* cron has been setting PAM_TTY to "cron" for a very
314 * long time and it probably shouldn't stop doing that
315 * for compatibility reasons. */
316 tty = "";
317 type = "unspecified";
318 } else if (streq(tty, "ssh")) {
319 /* ssh has been setting PAM_TTY to "ssh" for a very
320 * long time and probably shouldn't stop doing that
321 * for compatibility reasons. */
322 tty = "";
323 type ="tty";
324 }
325
326 /* If this fails vtnr will be 0, that's intended */
327 if (!isempty(cvtnr))
328 safe_atou32(cvtnr, &vtnr);
329
330 if (!isempty(display) && vtnr <= 0) {
331 if (isempty(seat))
332 get_seat_from_display(display, &seat, &vtnr);
333 else if (streq(seat, "seat0"))
334 get_seat_from_display(display, NULL, &vtnr);
335 }
336
337 if (!type)
338 type = !isempty(display) ? "x11" :
339 !isempty(tty) ? "tty" : "unspecified";
340
341 class = pam_getenv(handle, "XDG_SESSION_CLASS");
342 if (isempty(class))
343 class = getenv("XDG_SESSION_CLASS");
344 if (isempty(class))
345 class = class_pam;
346 if (isempty(class))
347 class = streq(type, "unspecified") ? "background" : "user";
348
349 remote = !isempty(remote_host) &&
350 !streq(remote_host, "localhost") &&
351 !streq(remote_host, "localhost.localdomain");
352
353 if (!dbus_message_append_args(m,
354 DBUS_TYPE_UINT32, &uid,
355 DBUS_TYPE_UINT32, &pid,
356 DBUS_TYPE_STRING, &service,
357 DBUS_TYPE_STRING, &type,
358 DBUS_TYPE_STRING, &class,
359 DBUS_TYPE_STRING, &seat,
360 DBUS_TYPE_UINT32, &vtnr,
361 DBUS_TYPE_STRING, &tty,
362 DBUS_TYPE_STRING, &display,
363 DBUS_TYPE_BOOLEAN, &remote,
364 DBUS_TYPE_STRING, &remote_user,
365 DBUS_TYPE_STRING, &remote_host,
366 DBUS_TYPE_INVALID)) {
367 pam_syslog(handle, LOG_ERR, "Could not attach parameters to message.");
368 r = PAM_BUF_ERR;
369 goto finish;
370 }
371
372 dbus_message_iter_init_append(m, &iter);
373
374 if (debug)
375 pam_syslog(handle, LOG_DEBUG, "Asking logind to create session: "
376 "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",
377 uid, pid, service, type, class, seat, vtnr, tty, display, yes_no(remote), remote_user, remote_host);
378
379 reply = dbus_connection_send_with_reply_and_block(bus, m, -1, &error);
380 if (!reply) {
381 pam_syslog(handle, LOG_ERR, "Failed to create session: %s", bus_error_message(&error));
382 r = PAM_SESSION_ERR;
383 goto finish;
384 }
385
386 if (!dbus_message_get_args(reply, &error,
387 DBUS_TYPE_STRING, &id,
388 DBUS_TYPE_OBJECT_PATH, &object_path,
389 DBUS_TYPE_STRING, &runtime_path,
390 DBUS_TYPE_UNIX_FD, &session_fd,
391 DBUS_TYPE_STRING, &seat,
392 DBUS_TYPE_UINT32, &vtnr,
393 DBUS_TYPE_BOOLEAN, &existing,
394 DBUS_TYPE_INVALID)) {
395 pam_syslog(handle, LOG_ERR, "Failed to parse message: %s", bus_error_message(&error));
396 r = PAM_SESSION_ERR;
397 goto finish;
398 }
399
400 if (debug)
401 pam_syslog(handle, LOG_DEBUG, "Reply from logind: "
402 "id=%s object_path=%s runtime_path=%s session_fd=%d seat=%s vtnr=%u",
403 id, object_path, runtime_path, session_fd, seat, vtnr);
404
405 r = pam_misc_setenv(handle, "XDG_SESSION_ID", id, 0);
406 if (r != PAM_SUCCESS) {
407 pam_syslog(handle, LOG_ERR, "Failed to set session id.");
408 goto finish;
409 }
410
411 r = pam_misc_setenv(handle, "XDG_RUNTIME_DIR", runtime_path, 0);
412 if (r != PAM_SUCCESS) {
413 pam_syslog(handle, LOG_ERR, "Failed to set runtime dir.");
414 goto finish;
415 }
416
417 if (!isempty(seat)) {
418 r = pam_misc_setenv(handle, "XDG_SEAT", seat, 0);
419 if (r != PAM_SUCCESS) {
420 pam_syslog(handle, LOG_ERR, "Failed to set seat.");
421 goto finish;
422 }
423 }
424
425 if (vtnr > 0) {
426 char buf[11];
427 snprintf(buf, sizeof(buf), "%u", vtnr);
428 char_array_0(buf);
429
430 r = pam_misc_setenv(handle, "XDG_VTNR", buf, 0);
431 if (r != PAM_SUCCESS) {
432 pam_syslog(handle, LOG_ERR, "Failed to set virtual terminal number.");
433 goto finish;
434 }
435 }
436
437 r = pam_set_data(handle, "systemd.existing", INT_TO_PTR(!!existing), NULL);
438 if (r != PAM_SUCCESS) {
439 pam_syslog(handle, LOG_ERR, "Failed to install existing flag.");
440 return r;
441 }
442
443 if (session_fd >= 0) {
444 r = pam_set_data(handle, "systemd.session-fd", INT_TO_PTR(session_fd+1), NULL);
445 if (r != PAM_SUCCESS) {
446 pam_syslog(handle, LOG_ERR, "Failed to install session fd.");
447 return r;
448 }
449 }
450
451 session_fd = -1;
452
453 r = PAM_SUCCESS;
454
455 finish:
456 dbus_error_free(&error);
457
458 if (bus) {
459 dbus_connection_close(bus);
460 dbus_connection_unref(bus);
461 }
462
463 if (m)
464 dbus_message_unref(m);
465
466 if (reply)
467 dbus_message_unref(reply);
468
469 if (session_fd >= 0)
470 close_nointr_nofail(session_fd);
471
472 return r;
473 }
474
475 _public_ PAM_EXTERN int pam_sm_close_session(
476 pam_handle_t *handle,
477 int flags,
478 int argc, const char **argv) {
479
480 const void *p = NULL, *existing = NULL;
481 const char *id;
482 DBusConnection *bus = NULL;
483 DBusMessage *m = NULL, *reply = NULL;
484 DBusError error;
485 int r;
486
487 assert(handle);
488
489 dbus_error_init(&error);
490
491 /* Only release session if it wasn't pre-existing when we
492 * tried to create it */
493 pam_get_data(handle, "systemd.existing", &existing);
494
495 id = pam_getenv(handle, "XDG_SESSION_ID");
496 if (id && !existing) {
497
498 /* Before we go and close the FIFO we need to tell
499 * logind that this is a clean session shutdown, so
500 * that it doesn't just go and slaughter us
501 * immediately after closing the fd */
502
503 bus = dbus_bus_get_private(DBUS_BUS_SYSTEM, &error);
504 if (!bus) {
505 pam_syslog(handle, LOG_ERR, "Failed to connect to system bus: %s", bus_error_message(&error));
506 r = PAM_SESSION_ERR;
507 goto finish;
508 }
509
510 m = dbus_message_new_method_call(
511 "org.freedesktop.login1",
512 "/org/freedesktop/login1",
513 "org.freedesktop.login1.Manager",
514 "ReleaseSession");
515 if (!m) {
516 pam_syslog(handle, LOG_ERR, "Could not allocate release session message.");
517 r = PAM_BUF_ERR;
518 goto finish;
519 }
520
521 if (!dbus_message_append_args(m,
522 DBUS_TYPE_STRING, &id,
523 DBUS_TYPE_INVALID)) {
524 pam_syslog(handle, LOG_ERR, "Could not attach parameters to message.");
525 r = PAM_BUF_ERR;
526 goto finish;
527 }
528
529 reply = dbus_connection_send_with_reply_and_block(bus, m, -1, &error);
530 if (!reply) {
531 pam_syslog(handle, LOG_ERR, "Failed to release session: %s", bus_error_message(&error));
532 r = PAM_SESSION_ERR;
533 goto finish;
534 }
535 }
536
537 r = PAM_SUCCESS;
538
539 finish:
540 pam_get_data(handle, "systemd.session-fd", &p);
541 if (p)
542 close_nointr(PTR_TO_INT(p) - 1);
543
544 dbus_error_free(&error);
545
546 if (bus) {
547 dbus_connection_close(bus);
548 dbus_connection_unref(bus);
549 }
550
551 if (m)
552 dbus_message_unref(m);
553
554 if (reply)
555 dbus_message_unref(reply);
556
557 return r;
558 }