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