]> andersk Git - openssh.git/blame - auth-pam.c
- (djm) Merge FreeBSD PAM code: replaces PAM password auth kludge with
[openssh.git] / auth-pam.c
CommitLineData
05114c74 1/*-
2 * Copyright (c) 2002 Networks Associates Technology, Inc.
3 * All rights reserved.
4 *
5 * This software was developed for the FreeBSD Project by ThinkSec AS and
6 * NAI Labs, the Security Research Division of Network Associates, Inc.
7 * under DARPA/SPAWAR contract N66001-01-C-8035 ("CBOSS"), as part of the
8 * DARPA CHATS research program.
09564242 9 *
10 * Redistribution and use in source and binary forms, with or without
11 * modification, are permitted provided that the following conditions
12 * are met:
13 * 1. Redistributions of source code must retain the above copyright
14 * notice, this list of conditions and the following disclaimer.
15 * 2. Redistributions in binary form must reproduce the above copyright
16 * notice, this list of conditions and the following disclaimer in the
17 * documentation and/or other materials provided with the distribution.
09564242 18 *
05114c74 19 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
20 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
22 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
23 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
24 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
25 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
26 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
27 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
28 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
29 * SUCH DAMAGE.
a5c9cd31 30 */
31
32#include "includes.h"
05114c74 33RCSID("$FreeBSD: src/crypto/openssh/auth2-pam-freebsd.c,v 1.11 2003/03/31 13:48:18 des Exp $");
a5c9cd31 34
35#ifdef USE_PAM
05114c74 36#include <security/pam_appl.h>
37
fde58bd4 38#include "auth.h"
5c377b3b 39#include "auth-pam.h"
05114c74 40#include "buffer.h"
41#include "bufaux.h"
42f11eb2 42#include "canohost.h"
05114c74 43#include "log.h"
44#include "monitor_wrap.h"
45#include "msg.h"
46#include "packet.h"
42f11eb2 47#include "readpass.h"
05114c74 48#include "servconf.h"
49#include "ssh2.h"
50#include "xmalloc.h"
a5c9cd31 51
05114c74 52#define __unused
277f55cf 53
05114c74 54#ifdef USE_POSIX_THREADS
55#include <pthread.h>
56/*
57 * Avoid namespace clash when *not* using pthreads for systems *with*
58 * pthreads, which unconditionally define pthread_t via sys/types.h
59 * (e.g. Linux)
60 */
61typedef pthread_t sp_pthread_t;
62#else
63/*
64 * Simulate threads with processes.
65 */
66typedef pid_t sp_pthread_t;
a5c9cd31 67
05114c74 68static void
69pthread_exit(void *value __unused)
70{
71 _exit(0);
72}
5daf7064 73
05114c74 74static int
75pthread_create(sp_pthread_t *thread, const void *attr __unused,
76 void *(*thread_start)(void *), void *arg)
77{
78 pid_t pid;
79
80 switch ((pid = fork())) {
81 case -1:
82 error("fork(): %s", strerror(errno));
83 return (-1);
84 case 0:
85 thread_start(arg);
86 _exit(1);
87 default:
88 *thread = pid;
89 return (0);
90 }
91}
a5c9cd31 92
05114c74 93static int
94pthread_cancel(sp_pthread_t thread)
8c9fe09e 95{
05114c74 96 return (kill(thread, SIGTERM));
8c9fe09e 97}
98
05114c74 99static int
100pthread_join(sp_pthread_t thread, void **value __unused)
8c9fe09e 101{
05114c74 102 int status;
103
104 waitpid(thread, &status, 0);
105 return (status);
8c9fe09e 106}
05114c74 107#endif
108
109
110static pam_handle_t *sshpam_handle;
111static int sshpam_err;
112static int sshpam_authenticated;
113static int sshpam_new_authtok_reqd;
114static int sshpam_session_open;
115static int sshpam_cred_established;
116
117struct pam_ctxt {
118 sp_pthread_t pam_thread;
119 int pam_psock;
120 int pam_csock;
121 int pam_done;
122};
123
124static void sshpam_free_ctx(void *);
ad55cd03 125
126/*
05114c74 127 * Conversation function for authentication thread.
ad55cd03 128 */
05114c74 129static int
130sshpam_thread_conv(int n,
131 const struct pam_message **msg,
132 struct pam_response **resp,
133 void *data)
a5c9cd31 134{
05114c74 135 Buffer buffer;
136 struct pam_ctxt *ctxt;
137 int i;
138
139 ctxt = data;
140 if (n <= 0 || n > PAM_MAX_NUM_MSG)
141 return (PAM_CONV_ERR);
142 *resp = xmalloc(n * sizeof **resp);
143 buffer_init(&buffer);
144 for (i = 0; i < n; ++i) {
145 resp[i]->resp_retcode = 0;
146 resp[i]->resp = NULL;
147 switch (PAM_MSG_MEMBER(msg, i, msg_style)) {
148 case PAM_PROMPT_ECHO_OFF:
149 buffer_put_cstring(&buffer, PAM_MSG_MEMBER(msg, i, msg));
150 ssh_msg_send(ctxt->pam_csock,
151 PAM_MSG_MEMBER(msg, i, msg_style), &buffer);
152 ssh_msg_recv(ctxt->pam_csock, &buffer);
153 if (buffer_get_char(&buffer) != PAM_AUTHTOK)
154 goto fail;
155 resp[i]->resp = buffer_get_string(&buffer, NULL);
156 break;
157 case PAM_PROMPT_ECHO_ON:
158 buffer_put_cstring(&buffer, PAM_MSG_MEMBER(msg, i, msg));
159 ssh_msg_send(ctxt->pam_csock,
160 PAM_MSG_MEMBER(msg, i, msg_style), &buffer);
161 ssh_msg_recv(ctxt->pam_csock, &buffer);
162 if (buffer_get_char(&buffer) != PAM_AUTHTOK)
163 goto fail;
164 resp[i]->resp = buffer_get_string(&buffer, NULL);
165 break;
166 case PAM_ERROR_MSG:
167 buffer_put_cstring(&buffer, PAM_MSG_MEMBER(msg, i, msg));
168 ssh_msg_send(ctxt->pam_csock,
169 PAM_MSG_MEMBER(msg, i, msg_style), &buffer);
170 break;
171 case PAM_TEXT_INFO:
172 buffer_put_cstring(&buffer, PAM_MSG_MEMBER(msg, i, msg));
173 ssh_msg_send(ctxt->pam_csock,
174 PAM_MSG_MEMBER(msg, i, msg_style), &buffer);
175 break;
176 default:
177 goto fail;
a5c9cd31 178 }
05114c74 179 buffer_clear(&buffer);
a5c9cd31 180 }
05114c74 181 buffer_free(&buffer);
182 return (PAM_SUCCESS);
183 fail:
184 while (i)
185 xfree(resp[--i]);
186 xfree(*resp);
187 *resp = NULL;
188 buffer_free(&buffer);
189 return (PAM_CONV_ERR);
190}
a5c9cd31 191
05114c74 192/*
193 * Authentication thread.
194 */
195static void *
196sshpam_thread(void *ctxtp)
197{
198 struct pam_ctxt *ctxt = ctxtp;
199 Buffer buffer;
200 struct pam_conv sshpam_conv = { sshpam_thread_conv, ctxt };
201#ifndef USE_POSIX_THREADS
202 const char *pam_user;
203
204 pam_get_item(sshpam_handle, PAM_USER, (const void **)&pam_user);
205 setproctitle("%s [pam]", pam_user);
206#endif
a5c9cd31 207
05114c74 208 buffer_init(&buffer);
209 sshpam_err = pam_set_item(sshpam_handle, PAM_CONV,
210 (const void *)&sshpam_conv);
211 if (sshpam_err != PAM_SUCCESS)
212 goto auth_fail;
213 sshpam_err = pam_authenticate(sshpam_handle, 0);
214 if (sshpam_err != PAM_SUCCESS)
215 goto auth_fail;
216 sshpam_err = pam_acct_mgmt(sshpam_handle, 0);
217 if (sshpam_err != PAM_SUCCESS && sshpam_err != PAM_NEW_AUTHTOK_REQD)
218 goto auth_fail;
219 buffer_put_cstring(&buffer, "OK");
220 ssh_msg_send(ctxt->pam_csock, sshpam_err, &buffer);
221 buffer_free(&buffer);
222 pthread_exit(NULL);
223
224 auth_fail:
225 buffer_put_cstring(&buffer,
226 pam_strerror(sshpam_handle, sshpam_err));
227 ssh_msg_send(ctxt->pam_csock, PAM_AUTH_ERR, &buffer);
228 buffer_free(&buffer);
229 pthread_exit(NULL);
230
231 return (NULL); /* Avoid warning for non-pthread case */
a5c9cd31 232}
233
05114c74 234static void
235sshpam_thread_cleanup(void *ctxtp)
a5c9cd31 236{
05114c74 237 struct pam_ctxt *ctxt = ctxtp;
a5c9cd31 238
05114c74 239 pthread_cancel(ctxt->pam_thread);
240 pthread_join(ctxt->pam_thread, NULL);
241 close(ctxt->pam_psock);
242 close(ctxt->pam_csock);
243}
a5c9cd31 244
05114c74 245static int
246sshpam_null_conv(int n,
247 const struct pam_message **msg,
248 struct pam_response **resp,
249 void *data)
250{
251
252 return (PAM_CONV_ERR);
253}
a5c9cd31 254
05114c74 255static struct pam_conv null_conv = { sshpam_null_conv, NULL };
256
257static void
258sshpam_cleanup(void *arg)
259{
260 (void)arg;
261 debug("PAM: cleanup");
262 pam_set_item(sshpam_handle, PAM_CONV, (const void *)&null_conv);
263 if (sshpam_cred_established) {
264 pam_setcred(sshpam_handle, PAM_DELETE_CRED);
265 sshpam_cred_established = 0;
266 }
267 if (sshpam_session_open) {
268 pam_close_session(sshpam_handle, PAM_SILENT);
269 sshpam_session_open = 0;
a5c9cd31 270 }
05114c74 271 sshpam_authenticated = sshpam_new_authtok_reqd = 0;
272 pam_end(sshpam_handle, sshpam_err);
273 sshpam_handle = NULL;
a5c9cd31 274}
275
05114c74 276static int
277sshpam_init(const char *user)
a5c9cd31 278{
279 extern ServerOptions options;
05114c74 280 extern u_int utmp_len;
281 const char *pam_rhost, *pam_user;
282
283 if (sshpam_handle != NULL) {
284 /* We already have a PAM context; check if the user matches */
285 sshpam_err = pam_get_item(sshpam_handle,
286 PAM_USER, (const void **)&pam_user);
287 if (sshpam_err == PAM_SUCCESS && strcmp(user, pam_user) == 0)
288 return (0);
289 fatal_remove_cleanup(sshpam_cleanup, NULL);
290 pam_end(sshpam_handle, sshpam_err);
291 sshpam_handle = NULL;
292 }
293 debug("PAM: initializing for \"%s\"", user);
294 sshpam_err = pam_start("sshd", user, &null_conv, &sshpam_handle);
295 if (sshpam_err != PAM_SUCCESS)
296 return (-1);
297 pam_rhost = get_remote_name_or_ip(utmp_len,
298 options.verify_reverse_mapping);
299 debug("PAM: setting PAM_RHOST to \"%s\"", pam_rhost);
300 sshpam_err = pam_set_item(sshpam_handle, PAM_RHOST, pam_rhost);
301 if (sshpam_err != PAM_SUCCESS) {
302 pam_end(sshpam_handle, sshpam_err);
303 sshpam_handle = NULL;
304 return (-1);
a5c9cd31 305 }
05114c74 306 fatal_add_cleanup(sshpam_cleanup, NULL);
307 return (0);
a5c9cd31 308}
309
05114c74 310static void *
311sshpam_init_ctx(Authctxt *authctxt)
a5c9cd31 312{
05114c74 313 struct pam_ctxt *ctxt;
314 int socks[2];
2b87da3b 315
05114c74 316 /* Initialize PAM */
317 if (sshpam_init(authctxt->user) == -1) {
318 error("PAM: initialization failed");
319 return (NULL);
320 }
5c377b3b 321
05114c74 322 ctxt = xmalloc(sizeof *ctxt);
323 ctxt->pam_done = 0;
324
325 /* Start the authentication thread */
326 if (socketpair(AF_UNIX, SOCK_STREAM, PF_UNSPEC, socks) == -1) {
327 error("PAM: failed create sockets: %s", strerror(errno));
328 xfree(ctxt);
329 return (NULL);
330 }
331 ctxt->pam_psock = socks[0];
332 ctxt->pam_csock = socks[1];
333 if (pthread_create(&ctxt->pam_thread, NULL, sshpam_thread, ctxt) == -1) {
334 error("PAM: failed to start authentication thread: %s",
335 strerror(errno));
336 close(socks[0]);
337 close(socks[1]);
338 xfree(ctxt);
339 return (NULL);
a5c9cd31 340 }
05114c74 341 fatal_add_cleanup(sshpam_thread_cleanup, ctxt);
342 return (ctxt);
343}
a5c9cd31 344
05114c74 345static int
346sshpam_query(void *ctx, char **name, char **info,
347 u_int *num, char ***prompts, u_int **echo_on)
348{
349 Buffer buffer;
350 struct pam_ctxt *ctxt = ctx;
351 size_t plen;
352 u_char type;
353 char *msg;
354
355 buffer_init(&buffer);
356 *name = xstrdup("");
357 *info = xstrdup("");
358 *prompts = xmalloc(sizeof(char *));
359 **prompts = NULL;
360 plen = 0;
361 *echo_on = xmalloc(sizeof(u_int));
362 while (ssh_msg_recv(ctxt->pam_psock, &buffer) == 0) {
363 type = buffer_get_char(&buffer);
364 msg = buffer_get_string(&buffer, NULL);
365 switch (type) {
366 case PAM_PROMPT_ECHO_ON:
367 case PAM_PROMPT_ECHO_OFF:
368 *num = 1;
369 **prompts = xrealloc(**prompts, plen + strlen(msg) + 1);
370 plen += sprintf(**prompts + plen, "%s", msg);
371 **echo_on = (type == PAM_PROMPT_ECHO_ON);
372 xfree(msg);
373 return (0);
374 case PAM_ERROR_MSG:
375 case PAM_TEXT_INFO:
376 /* accumulate messages */
377 **prompts = xrealloc(**prompts, plen + strlen(msg) + 1);
378 plen += sprintf(**prompts + plen, "%s", msg);
379 xfree(msg);
5daf7064 380 break;
381 case PAM_NEW_AUTHTOK_REQD:
05114c74 382 sshpam_new_authtok_reqd = 1;
383 /* FALLTHROUGH */
384 case PAM_SUCCESS:
385 case PAM_AUTH_ERR:
386 if (**prompts != NULL) {
387 /* drain any accumulated messages */
388#if 0 /* XXX - not compatible with privsep */
389 packet_start(SSH2_MSG_USERAUTH_BANNER);
390 packet_put_cstring(**prompts);
391 packet_put_cstring("");
392 packet_send();
393 packet_write_wait();
e108cd93 394#endif
05114c74 395 xfree(**prompts);
396 **prompts = NULL;
397 }
398 if (type == PAM_SUCCESS) {
399 *num = 0;
400 **echo_on = 0;
401 ctxt->pam_done = 1;
402 xfree(msg);
403 return (0);
404 }
405 error("PAM: %s", msg);
5daf7064 406 default:
05114c74 407 *num = 0;
408 **echo_on = 0;
409 xfree(msg);
410 ctxt->pam_done = -1;
411 return (-1);
412 }
a5c9cd31 413 }
05114c74 414 return (-1);
a5c9cd31 415}
416
05114c74 417/* XXX - see also comment in auth-chall.c:verify_response */
418static int
419sshpam_respond(void *ctx, u_int num, char **resp)
a5c9cd31 420{
05114c74 421 Buffer buffer;
422 struct pam_ctxt *ctxt = ctx;
423
424 debug2("PAM: %s", __func__);
425 switch (ctxt->pam_done) {
426 case 1:
427 sshpam_authenticated = 1;
428 return (0);
429 case 0:
430 break;
431 default:
432 return (-1);
a5c9cd31 433 }
05114c74 434 if (num != 1) {
435 error("PAM: expected one response, got %u", num);
436 return (-1);
437 }
438 buffer_init(&buffer);
439 buffer_put_cstring(&buffer, *resp);
440 ssh_msg_send(ctxt->pam_psock, PAM_AUTHTOK, &buffer);
441 buffer_free(&buffer);
442 return (1);
a5c9cd31 443}
444
05114c74 445static void
446sshpam_free_ctx(void *ctxtp)
a5c9cd31 447{
05114c74 448 struct pam_ctxt *ctxt = ctxtp;
39ce53de 449
05114c74 450 fatal_remove_cleanup(sshpam_thread_cleanup, ctxt);
451 sshpam_thread_cleanup(ctxtp);
452 xfree(ctxt);
453 /*
454 * We don't call sshpam_cleanup() here because we may need the PAM
455 * handle at a later stage, e.g. when setting up a session. It's
456 * still on the cleanup list, so pam_end() *will* be called before
457 * the server process terminates.
458 */
ad55cd03 459}
460
05114c74 461KbdintDevice sshpam_device = {
462 "pam",
463 sshpam_init_ctx,
464 sshpam_query,
465 sshpam_respond,
466 sshpam_free_ctx
467};
468
469KbdintDevice mm_sshpam_device = {
470 "pam",
471 mm_sshpam_init_ctx,
472 mm_sshpam_query,
473 mm_sshpam_respond,
474 mm_sshpam_free_ctx
475};
2919e060 476
2b87da3b 477/*
05114c74 478 * This replaces auth-pam.c
ad55cd03 479 */
05114c74 480void
481start_pam(const char *user)
ad55cd03 482{
05114c74 483 if (sshpam_init(user) == -1)
484 fatal("PAM: initialisation failed");
a5c9cd31 485}
486
05114c74 487void
488finish_pam(void)
a5c9cd31 489{
05114c74 490 fatal_remove_cleanup(sshpam_cleanup, NULL);
491 sshpam_cleanup(NULL);
a5c9cd31 492}
493
05114c74 494int
495do_pam_account(const char *user, const char *ruser)
a5c9cd31 496{
05114c74 497 /* XXX */
498 return (1);
499}
46c76c63 500
05114c74 501void
502do_pam_session(const char *user, const char *tty)
503{
504 sshpam_err = pam_set_item(sshpam_handle, PAM_CONV,
505 (const void *)&null_conv);
506 if (sshpam_err != PAM_SUCCESS)
507 fatal("PAM: failed to set PAM_CONV: %s",
508 pam_strerror(sshpam_handle, sshpam_err));
509 debug("PAM: setting PAM_TTY to \"%s\"", tty);
510 sshpam_err = pam_set_item(sshpam_handle, PAM_TTY, tty);
511 if (sshpam_err != PAM_SUCCESS)
512 fatal("PAM: failed to set PAM_TTY: %s",
513 pam_strerror(sshpam_handle, sshpam_err));
514 sshpam_err = pam_open_session(sshpam_handle, 0);
515 if (sshpam_err != PAM_SUCCESS)
516 fatal("PAM: pam_open_session(): %s",
517 pam_strerror(sshpam_handle, sshpam_err));
518 sshpam_session_open = 1;
519}
cbd7492e 520
05114c74 521void
522do_pam_setcred(int init)
523{
524 sshpam_err = pam_set_item(sshpam_handle, PAM_CONV,
525 (const void *)&null_conv);
526 if (sshpam_err != PAM_SUCCESS)
527 fatal("PAM: failed to set PAM_CONV: %s",
528 pam_strerror(sshpam_handle, sshpam_err));
529 if (init) {
530 debug("PAM: establishing credentials");
531 sshpam_err = pam_setcred(sshpam_handle, PAM_ESTABLISH_CRED);
532 } else {
533 debug("PAM: reinitializing credentials");
534 sshpam_err = pam_setcred(sshpam_handle, PAM_REINITIALIZE_CRED);
535 }
536 if (sshpam_err == PAM_SUCCESS) {
537 sshpam_cred_established = 1;
538 return;
539 }
540 if (sshpam_authenticated)
541 fatal("PAM: pam_setcred(): %s",
542 pam_strerror(sshpam_handle, sshpam_err));
543 else
544 debug("PAM: pam_setcred(): %s",
545 pam_strerror(sshpam_handle, sshpam_err));
a5c9cd31 546}
547
05114c74 548int
549is_pam_password_change_required(void)
a5c9cd31 550{
05114c74 551 return (sshpam_new_authtok_reqd);
a5c9cd31 552}
553
05114c74 554static int
555pam_chauthtok_conv(int n,
556 const struct pam_message **msg,
557 struct pam_response **resp,
558 void *data)
ee48c949 559{
05114c74 560 char input[PAM_MAX_MSG_SIZE];
ee48c949 561 int i;
562
05114c74 563 if (n <= 0 || n > PAM_MAX_NUM_MSG)
564 return (PAM_CONV_ERR);
565 *resp = xmalloc(n * sizeof **resp);
566 for (i = 0; i < n; ++i) {
567 switch (PAM_MSG_MEMBER(msg, i, msg_style)) {
568 case PAM_PROMPT_ECHO_OFF:
569 resp[i]->resp =
570 read_passphrase(PAM_MSG_MEMBER(msg, i, msg),
571 RP_ALLOW_STDIN);
572 resp[i]->resp_retcode = PAM_SUCCESS;
573 break;
574 case PAM_PROMPT_ECHO_ON:
575 fputs(PAM_MSG_MEMBER(msg, i, msg), stderr);
576 fgets(input, sizeof input, stdin);
577 resp[i]->resp = xstrdup(input);
578 resp[i]->resp_retcode = PAM_SUCCESS;
579 break;
580 case PAM_ERROR_MSG:
581 case PAM_TEXT_INFO:
582 fputs(PAM_MSG_MEMBER(msg, i, msg), stderr);
583 resp[i]->resp_retcode = PAM_SUCCESS;
584 break;
585 default:
586 goto fail;
587 }
ee48c949 588 }
05114c74 589 return (PAM_SUCCESS);
590 fail:
591 while (i)
592 xfree(resp[--i]);
593 xfree(*resp);
594 *resp = NULL;
595 return (PAM_CONV_ERR);
ee48c949 596}
597
05114c74 598/*
599 * XXX this should be done in the authentication phase, but ssh1 doesn't
600 * support that
601 */
602void
603do_pam_chauthtok(void)
a5c9cd31 604{
05114c74 605 struct pam_conv pam_conv = { pam_chauthtok_conv, NULL };
606
607 if (use_privsep)
608 fatal("PAM: chauthtok not supprted with privsep");
609 sshpam_err = pam_set_item(sshpam_handle, PAM_CONV,
610 (const void *)&pam_conv);
611 if (sshpam_err != PAM_SUCCESS)
612 fatal("PAM: failed to set PAM_CONV: %s",
613 pam_strerror(sshpam_handle, sshpam_err));
614 debug("PAM: changing password");
615 sshpam_err = pam_chauthtok(sshpam_handle, PAM_CHANGE_EXPIRED_AUTHTOK);
616 if (sshpam_err != PAM_SUCCESS)
617 fatal("PAM: pam_chauthtok(): %s",
618 pam_strerror(sshpam_handle, sshpam_err));
5daf7064 619}
620
05114c74 621void
622print_pam_messages(void)
5daf7064 623{
05114c74 624 /* XXX */
625}
2b87da3b 626
05114c74 627char **
628fetch_pam_environment(void)
629{
630#ifdef HAVE_PAM_GETENVLIST
631 debug("PAM: retrieving environment");
632 return (pam_getenvlist(sshpam_handle));
633#else
634 return (NULL);
635#endif
636}
5c377b3b 637
05114c74 638void
639free_pam_environment(char **env)
640{
641 char **envp;
5daf7064 642
05114c74 643 for (envp = env; *envp; envp++)
644 xfree(*envp);
645 xfree(env);
a5c9cd31 646}
647
648#endif /* USE_PAM */
This page took 0.214842 seconds and 5 git commands to generate.