1124211Sdes/*-
2124211Sdes * Copyright (c) 2002 Networks Associates Technology, Inc.
3124211Sdes * All rights reserved.
469591Sgreen *
5124211Sdes * This software was developed for the FreeBSD Project by ThinkSec AS and
6124211Sdes * NAI Labs, the Security Research Division of Network Associates, Inc.
7124211Sdes * under DARPA/SPAWAR contract N66001-01-C-8035 ("CBOSS"), as part of the
8124211Sdes * DARPA CHATS research program.
9124211Sdes *
1069591Sgreen * Redistribution and use in source and binary forms, with or without
1169591Sgreen * modification, are permitted provided that the following conditions
1269591Sgreen * are met:
1369591Sgreen * 1. Redistributions of source code must retain the above copyright
1469591Sgreen *    notice, this list of conditions and the following disclaimer.
1569591Sgreen * 2. Redistributions in binary form must reproduce the above copyright
1669591Sgreen *    notice, this list of conditions and the following disclaimer in the
1769591Sgreen *    documentation and/or other materials provided with the distribution.
1869591Sgreen *
19124211Sdes * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
20124211Sdes * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21124211Sdes * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
22124211Sdes * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
23124211Sdes * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
24124211Sdes * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
25124211Sdes * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
26124211Sdes * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
27124211Sdes * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
28124211Sdes * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
29124211Sdes * SUCH DAMAGE.
3069591Sgreen */
31137019Sdes/*
32137019Sdes * Copyright (c) 2003,2004 Damien Miller <djm@mindrot.org>
33137019Sdes * Copyright (c) 2003,2004 Darren Tucker <dtucker@zip.com.au>
34137019Sdes *
35137019Sdes * Permission to use, copy, modify, and distribute this software for any
36137019Sdes * purpose with or without fee is hereby granted, provided that the above
37137019Sdes * copyright notice and this permission notice appear in all copies.
38137019Sdes *
39137019Sdes * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
40137019Sdes * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
41137019Sdes * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
42137019Sdes * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
43137019Sdes * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
44137019Sdes * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
45137019Sdes * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
46137019Sdes */
4769591Sgreen
48225614Sdes/* Based on $FreeBSD: src/crypto/openssh/auth2-pam-freebsd.c,v 1.11 2003/03/31 13:48:18 des Exp $ */
4969591Sgreen#include "includes.h"
5069591Sgreen
51162856Sdes#include <sys/types.h>
52162856Sdes#include <sys/stat.h>
53162856Sdes#include <sys/wait.h>
54162856Sdes
55162856Sdes#include <errno.h>
56162856Sdes#include <signal.h>
57162856Sdes#include <stdarg.h>
58162856Sdes#include <string.h>
59162856Sdes#include <unistd.h>
60162856Sdes
6169591Sgreen#ifdef USE_PAM
62126277Sdes#if defined(HAVE_SECURITY_PAM_APPL_H)
63124211Sdes#include <security/pam_appl.h>
64126277Sdes#elif defined (HAVE_PAM_PAM_APPL_H)
65126277Sdes#include <pam/pam_appl.h>
66126277Sdes#endif
67124211Sdes
68149753Sdes/* OpenGroup RFC86.0 and XSSO specify no "const" on arguments */
69149753Sdes#ifdef PAM_SUN_CODEBASE
70149753Sdes# define sshpam_const		/* Solaris, HP-UX, AIX */
71149753Sdes#else
72149753Sdes# define sshpam_const	const	/* LinuxPAM, OpenPAM */
73149753Sdes#endif
74149753Sdes
75162856Sdes/* Ambiguity in spec: is it an array of pointers or a pointer to an array? */
76162856Sdes#ifdef PAM_SUN_CODEBASE
77162856Sdes# define PAM_MSG_MEMBER(msg, n, member) ((*(msg))[(n)].member)
78162856Sdes#else
79162856Sdes# define PAM_MSG_MEMBER(msg, n, member) ((msg)[(n)]->member)
80162856Sdes#endif
81162856Sdes
82162856Sdes#include "xmalloc.h"
83162856Sdes#include "buffer.h"
84162856Sdes#include "key.h"
85162856Sdes#include "hostfile.h"
8698941Sdes#include "auth.h"
8798941Sdes#include "auth-pam.h"
8898941Sdes#include "canohost.h"
89124211Sdes#include "log.h"
90124211Sdes#include "msg.h"
91124211Sdes#include "packet.h"
92137019Sdes#include "misc.h"
93124211Sdes#include "servconf.h"
94124211Sdes#include "ssh2.h"
95124211Sdes#include "auth-options.h"
96162856Sdes#ifdef GSSAPI
97162856Sdes#include "ssh-gss.h"
98162856Sdes#endif
99162856Sdes#include "monitor_wrap.h"
10069591Sgreen
101124211Sdesextern ServerOptions options;
102126277Sdesextern Buffer loginmsg;
103126277Sdesextern int compat20;
104128460Sdesextern u_int utmp_len;
10569591Sgreen
106147005Sdes/* so we don't silently change behaviour */
107124211Sdes#ifdef USE_POSIX_THREADS
108147005Sdes# error "USE_POSIX_THREADS replaced by UNSUPPORTED_POSIX_THREADS_HACK"
109147005Sdes#endif
110147005Sdes
111147005Sdes/*
112147005Sdes * Formerly known as USE_POSIX_THREADS, using this is completely unsupported
113147005Sdes * and generally a bad idea.  Use at own risk and do not expect support if
114147005Sdes * this breaks.
115147005Sdes */
116147005Sdes#ifdef UNSUPPORTED_POSIX_THREADS_HACK
117124211Sdes#include <pthread.h>
118124211Sdes/*
119126277Sdes * Avoid namespace clash when *not* using pthreads for systems *with*
120126277Sdes * pthreads, which unconditionally define pthread_t via sys/types.h
121124211Sdes * (e.g. Linux)
122124211Sdes */
123126277Sdestypedef pthread_t sp_pthread_t;
124124211Sdes#else
125126277Sdestypedef pid_t sp_pthread_t;
126126277Sdes#endif
127126277Sdes
128126277Sdesstruct pam_ctxt {
129126277Sdes	sp_pthread_t	 pam_thread;
130126277Sdes	int		 pam_psock;
131126277Sdes	int		 pam_csock;
132126277Sdes	int		 pam_done;
133126277Sdes};
134126277Sdes
135126277Sdesstatic void sshpam_free_ctx(void *);
136126277Sdesstatic struct pam_ctxt *cleanup_ctxt;
137126277Sdes
138147005Sdes#ifndef UNSUPPORTED_POSIX_THREADS_HACK
139124211Sdes/*
140124211Sdes * Simulate threads with processes.
141124211Sdes */
142106130Sdes
143126277Sdesstatic int sshpam_thread_status = -1;
144126277Sdesstatic mysig_t sshpam_oldsig;
145126277Sdes
146149753Sdesstatic void
147126277Sdessshpam_sigchld_handler(int sig)
148126277Sdes{
149137019Sdes	signal(SIGCHLD, SIG_DFL);
150126277Sdes	if (cleanup_ctxt == NULL)
151126277Sdes		return;	/* handler called after PAM cleanup, shouldn't happen */
152137019Sdes	if (waitpid(cleanup_ctxt->pam_thread, &sshpam_thread_status, WNOHANG)
153149753Sdes	    <= 0) {
154137019Sdes		/* PAM thread has not exitted, privsep slave must have */
155137019Sdes		kill(cleanup_ctxt->pam_thread, SIGTERM);
156137019Sdes		if (waitpid(cleanup_ctxt->pam_thread, &sshpam_thread_status, 0)
157137019Sdes		    <= 0)
158137019Sdes			return; /* could not wait */
159137019Sdes	}
160126277Sdes	if (WIFSIGNALED(sshpam_thread_status) &&
161126277Sdes	    WTERMSIG(sshpam_thread_status) == SIGTERM)
162126277Sdes		return;	/* terminated by pthread_cancel */
163126277Sdes	if (!WIFEXITED(sshpam_thread_status))
164181111Sdes		sigdie("PAM: authentication thread exited unexpectedly");
165126277Sdes	if (WEXITSTATUS(sshpam_thread_status) != 0)
166181111Sdes		sigdie("PAM: authentication thread exited uncleanly");
167126277Sdes}
168126277Sdes
169162856Sdes/* ARGSUSED */
170124211Sdesstatic void
171162856Sdespthread_exit(void *value)
172124211Sdes{
173124211Sdes	_exit(0);
174124211Sdes}
17569591Sgreen
176162856Sdes/* ARGSUSED */
177124211Sdesstatic int
178162856Sdespthread_create(sp_pthread_t *thread, const void *attr,
179124211Sdes    void *(*thread_start)(void *), void *arg)
180124211Sdes{
181124211Sdes	pid_t pid;
182149753Sdes	struct pam_ctxt *ctx = arg;
18369591Sgreen
184128460Sdes	sshpam_thread_status = -1;
185124211Sdes	switch ((pid = fork())) {
186124211Sdes	case -1:
187124211Sdes		error("fork(): %s", strerror(errno));
188124211Sdes		return (-1);
189124211Sdes	case 0:
190149753Sdes		close(ctx->pam_psock);
191149753Sdes		ctx->pam_psock = -1;
192124211Sdes		thread_start(arg);
193124211Sdes		_exit(1);
194124211Sdes	default:
195124211Sdes		*thread = pid;
196149753Sdes		close(ctx->pam_csock);
197149753Sdes		ctx->pam_csock = -1;
198126277Sdes		sshpam_oldsig = signal(SIGCHLD, sshpam_sigchld_handler);
199124211Sdes		return (0);
200124211Sdes	}
201124211Sdes}
20269591Sgreen
203124211Sdesstatic int
204124211Sdespthread_cancel(sp_pthread_t thread)
20576394Salfred{
206126277Sdes	signal(SIGCHLD, sshpam_oldsig);
207124211Sdes	return (kill(thread, SIGTERM));
20876394Salfred}
20976394Salfred
210162856Sdes/* ARGSUSED */
211124211Sdesstatic int
212162856Sdespthread_join(sp_pthread_t thread, void **value)
21398941Sdes{
214124211Sdes	int status;
215124211Sdes
216126277Sdes	if (sshpam_thread_status != -1)
217126277Sdes		return (sshpam_thread_status);
218126277Sdes	signal(SIGCHLD, sshpam_oldsig);
219124211Sdes	waitpid(thread, &status, 0);
220124211Sdes	return (status);
22198941Sdes}
222124211Sdes#endif
22398941Sdes
224124211Sdes
225124211Sdesstatic pam_handle_t *sshpam_handle = NULL;
226124211Sdesstatic int sshpam_err = 0;
227124211Sdesstatic int sshpam_authenticated = 0;
228124211Sdesstatic int sshpam_session_open = 0;
229124211Sdesstatic int sshpam_cred_established = 0;
230126277Sdesstatic int sshpam_account_status = -1;
231126277Sdesstatic char **sshpam_env = NULL;
232128460Sdesstatic Authctxt *sshpam_authctxt = NULL;
233137019Sdesstatic const char *sshpam_password = NULL;
234147005Sdesstatic char badpw[] = "\b\n\r\177INCORRECT";
235124211Sdes
236126277Sdes/* Some PAM implementations don't implement this */
237126277Sdes#ifndef HAVE_PAM_GETENVLIST
238126277Sdesstatic char **
239126277Sdespam_getenvlist(pam_handle_t *pamh)
240126277Sdes{
241126277Sdes	/*
242126277Sdes	 * XXX - If necessary, we can still support envrionment passing
243126277Sdes	 * for platforms without pam_getenvlist by searching for known
244126277Sdes	 * env vars (e.g. KRB5CCNAME) from the PAM environment.
245126277Sdes	 */
246126277Sdes	 return NULL;
247126277Sdes}
248126277Sdes#endif
249124211Sdes
250137019Sdes/*
251137019Sdes * Some platforms, notably Solaris, do not enforce password complexity
252137019Sdes * rules during pam_chauthtok() if the real uid of the calling process
253137019Sdes * is 0, on the assumption that it's being called by "passwd" run by root.
254137019Sdes * This wraps pam_chauthtok and sets/restore the real uid so PAM will do
255137019Sdes * the right thing.
256137019Sdes */
257137019Sdes#ifdef SSHPAM_CHAUTHTOK_NEEDS_RUID
258137019Sdesstatic int
259137019Sdessshpam_chauthtok_ruid(pam_handle_t *pamh, int flags)
260137019Sdes{
261137019Sdes	int result;
262137019Sdes
263137019Sdes	if (sshpam_authctxt == NULL)
264137019Sdes		fatal("PAM: sshpam_authctxt not initialized");
265137019Sdes	if (setreuid(sshpam_authctxt->pw->pw_uid, -1) == -1)
266137019Sdes		fatal("%s: setreuid failed: %s", __func__, strerror(errno));
267137019Sdes	result = pam_chauthtok(pamh, flags);
268137019Sdes	if (setreuid(0, -1) == -1)
269137019Sdes		fatal("%s: setreuid failed: %s", __func__, strerror(errno));
270137019Sdes	return result;
271137019Sdes}
272137019Sdes# define pam_chauthtok(a,b)	(sshpam_chauthtok_ruid((a), (b)))
273137019Sdes#endif
274137019Sdes
275126277Sdesvoid
276137019Sdessshpam_password_change_required(int reqd)
277126277Sdes{
278126277Sdes	debug3("%s %d", __func__, reqd);
279128460Sdes	if (sshpam_authctxt == NULL)
280128460Sdes		fatal("%s: PAM authctxt not initialized", __func__);
281128460Sdes	sshpam_authctxt->force_pwchange = reqd;
282126277Sdes	if (reqd) {
283126277Sdes		no_port_forwarding_flag |= 2;
284126277Sdes		no_agent_forwarding_flag |= 2;
285126277Sdes		no_x11_forwarding_flag |= 2;
286126277Sdes	} else {
287126277Sdes		no_port_forwarding_flag &= ~2;
288126277Sdes		no_agent_forwarding_flag &= ~2;
289126277Sdes		no_x11_forwarding_flag &= ~2;
290126277Sdes	}
291126277Sdes}
292124211Sdes
293126277Sdes/* Import regular and PAM environment from subprocess */
294126277Sdesstatic void
295126277Sdesimport_environments(Buffer *b)
296126277Sdes{
297126277Sdes	char *env;
298126277Sdes	u_int i, num_env;
299126277Sdes	int err;
300126277Sdes
301126277Sdes	debug3("PAM: %s entering", __func__);
302126277Sdes
303147005Sdes#ifndef UNSUPPORTED_POSIX_THREADS_HACK
304126277Sdes	/* Import variables set by do_pam_account */
305126277Sdes	sshpam_account_status = buffer_get_int(b);
306137019Sdes	sshpam_password_change_required(buffer_get_int(b));
307126277Sdes
308126277Sdes	/* Import environment from subprocess */
309126277Sdes	num_env = buffer_get_int(b);
310162856Sdes	if (num_env > 1024)
311162856Sdes		fatal("%s: received %u environment variables, expected <= 1024",
312162856Sdes		    __func__, num_env);
313162856Sdes	sshpam_env = xcalloc(num_env + 1, sizeof(*sshpam_env));
314126277Sdes	debug3("PAM: num env strings %d", num_env);
315126277Sdes	for(i = 0; i < num_env; i++)
316126277Sdes		sshpam_env[i] = buffer_get_string(b, NULL);
317126277Sdes
318126277Sdes	sshpam_env[num_env] = NULL;
319126277Sdes
320126277Sdes	/* Import PAM environment from subprocess */
321126277Sdes	num_env = buffer_get_int(b);
322126277Sdes	debug("PAM: num PAM env strings %d", num_env);
323126277Sdes	for(i = 0; i < num_env; i++) {
324126277Sdes		env = buffer_get_string(b, NULL);
325126277Sdes
326126277Sdes#ifdef HAVE_PAM_PUTENV
327126277Sdes		/* Errors are not fatal here */
328126277Sdes		if ((err = pam_putenv(sshpam_handle, env)) != PAM_SUCCESS) {
329126277Sdes			error("PAM: pam_putenv: %s",
330126277Sdes			    pam_strerror(sshpam_handle, sshpam_err));
331126277Sdes		}
332126277Sdes#endif
333126277Sdes	}
334128460Sdes#endif
335126277Sdes}
336126277Sdes
33776394Salfred/*
338124211Sdes * Conversation function for authentication thread.
33969591Sgreen */
340124211Sdesstatic int
341149753Sdessshpam_thread_conv(int n, sshpam_const struct pam_message **msg,
342124211Sdes    struct pam_response **resp, void *data)
34369591Sgreen{
344124211Sdes	Buffer buffer;
345124211Sdes	struct pam_ctxt *ctxt;
34669591Sgreen	struct pam_response *reply;
347124211Sdes	int i;
34869591Sgreen
349126277Sdes	debug3("PAM: %s entering, %d messages", __func__, n);
350124211Sdes	*resp = NULL;
35169591Sgreen
352137019Sdes	if (data == NULL) {
353137019Sdes		error("PAM: conversation function passed a null context");
354137019Sdes		return (PAM_CONV_ERR);
355137019Sdes	}
356124211Sdes	ctxt = data;
357124211Sdes	if (n <= 0 || n > PAM_MAX_NUM_MSG)
358124211Sdes		return (PAM_CONV_ERR);
359124211Sdes
360162856Sdes	if ((reply = calloc(n, sizeof(*reply))) == NULL)
361124211Sdes		return (PAM_CONV_ERR);
362124211Sdes
363124211Sdes	buffer_init(&buffer);
364124211Sdes	for (i = 0; i < n; ++i) {
365124211Sdes		switch (PAM_MSG_MEMBER(msg, i, msg_style)) {
366124211Sdes		case PAM_PROMPT_ECHO_OFF:
367126277Sdes			buffer_put_cstring(&buffer,
368124211Sdes			    PAM_MSG_MEMBER(msg, i, msg));
369126277Sdes			if (ssh_msg_send(ctxt->pam_csock,
370126277Sdes			    PAM_MSG_MEMBER(msg, i, msg_style), &buffer) == -1)
371126277Sdes				goto fail;
372126277Sdes			if (ssh_msg_recv(ctxt->pam_csock, &buffer) == -1)
373126277Sdes				goto fail;
374124211Sdes			if (buffer_get_char(&buffer) != PAM_AUTHTOK)
375124211Sdes				goto fail;
376124211Sdes			reply[i].resp = buffer_get_string(&buffer, NULL);
377124211Sdes			break;
378124211Sdes		case PAM_PROMPT_ECHO_ON:
379126277Sdes			buffer_put_cstring(&buffer,
380124211Sdes			    PAM_MSG_MEMBER(msg, i, msg));
381126277Sdes			if (ssh_msg_send(ctxt->pam_csock,
382126277Sdes			    PAM_MSG_MEMBER(msg, i, msg_style), &buffer) == -1)
383126277Sdes				goto fail;
384126277Sdes			if (ssh_msg_recv(ctxt->pam_csock, &buffer) == -1)
385126277Sdes				goto fail;
386124211Sdes			if (buffer_get_char(&buffer) != PAM_AUTHTOK)
387124211Sdes				goto fail;
388124211Sdes			reply[i].resp = buffer_get_string(&buffer, NULL);
389124211Sdes			break;
390124211Sdes		case PAM_ERROR_MSG:
391126277Sdes			buffer_put_cstring(&buffer,
392124211Sdes			    PAM_MSG_MEMBER(msg, i, msg));
393126277Sdes			if (ssh_msg_send(ctxt->pam_csock,
394126277Sdes			    PAM_MSG_MEMBER(msg, i, msg_style), &buffer) == -1)
395126277Sdes				goto fail;
396124211Sdes			break;
397124211Sdes		case PAM_TEXT_INFO:
398126277Sdes			buffer_put_cstring(&buffer,
399124211Sdes			    PAM_MSG_MEMBER(msg, i, msg));
400126277Sdes			if (ssh_msg_send(ctxt->pam_csock,
401126277Sdes			    PAM_MSG_MEMBER(msg, i, msg_style), &buffer) == -1)
402126277Sdes				goto fail;
403124211Sdes			break;
404124211Sdes		default:
405124211Sdes			goto fail;
40669591Sgreen		}
407124211Sdes		buffer_clear(&buffer);
40869591Sgreen	}
409124211Sdes	buffer_free(&buffer);
41069591Sgreen	*resp = reply;
411124211Sdes	return (PAM_SUCCESS);
41269591Sgreen
413124211Sdes fail:
414124211Sdes	for(i = 0; i < n; i++) {
415255767Sdes		free(reply[i].resp);
416124211Sdes	}
417255767Sdes	free(reply);
418124211Sdes	buffer_free(&buffer);
419124211Sdes	return (PAM_CONV_ERR);
42069591Sgreen}
42169591Sgreen
422124211Sdes/*
423124211Sdes * Authentication thread.
424124211Sdes */
425124211Sdesstatic void *
426124211Sdessshpam_thread(void *ctxtp)
42769591Sgreen{
428124211Sdes	struct pam_ctxt *ctxt = ctxtp;
429124211Sdes	Buffer buffer;
430124211Sdes	struct pam_conv sshpam_conv;
431137019Sdes	int flags = (options.permit_empty_passwd == 0 ?
432137019Sdes	    PAM_DISALLOW_NULL_AUTHTOK : 0);
433147005Sdes#ifndef UNSUPPORTED_POSIX_THREADS_HACK
434126277Sdes	extern char **environ;
435126277Sdes	char **env_from_pam;
436126277Sdes	u_int i;
437124211Sdes	const char *pam_user;
438149753Sdes	const char **ptr_pam_user = &pam_user;
439162856Sdes	char *tz = getenv("TZ");
44069591Sgreen
441149753Sdes	pam_get_item(sshpam_handle, PAM_USER,
442149753Sdes	    (sshpam_const void **)ptr_pam_user);
443162856Sdes
444126277Sdes	environ[0] = NULL;
445162856Sdes	if (tz != NULL)
446162856Sdes		if (setenv("TZ", tz, 1) == -1)
447162856Sdes			error("PAM: could not set TZ environment: %s",
448162856Sdes			    strerror(errno));
449137019Sdes
450137019Sdes	if (sshpam_authctxt != NULL) {
451137019Sdes		setproctitle("%s [pam]",
452137019Sdes		    sshpam_authctxt->valid ? pam_user : "unknown");
453137019Sdes	}
454124211Sdes#endif
45569591Sgreen
456124211Sdes	sshpam_conv.conv = sshpam_thread_conv;
457124211Sdes	sshpam_conv.appdata_ptr = ctxt;
45869591Sgreen
459128460Sdes	if (sshpam_authctxt == NULL)
460128460Sdes		fatal("%s: PAM authctxt not initialized", __func__);
461128460Sdes
462124211Sdes	buffer_init(&buffer);
463124211Sdes	sshpam_err = pam_set_item(sshpam_handle, PAM_CONV,
464124211Sdes	    (const void *)&sshpam_conv);
465124211Sdes	if (sshpam_err != PAM_SUCCESS)
466124211Sdes		goto auth_fail;
467137019Sdes	sshpam_err = pam_authenticate(sshpam_handle, flags);
468124211Sdes	if (sshpam_err != PAM_SUCCESS)
469124211Sdes		goto auth_fail;
470126277Sdes
471126277Sdes	if (compat20) {
472162856Sdes		if (!do_pam_account()) {
473162856Sdes			sshpam_err = PAM_ACCT_EXPIRED;
474126277Sdes			goto auth_fail;
475162856Sdes		}
476128460Sdes		if (sshpam_authctxt->force_pwchange) {
477126277Sdes			sshpam_err = pam_chauthtok(sshpam_handle,
478126277Sdes			    PAM_CHANGE_EXPIRED_AUTHTOK);
479126277Sdes			if (sshpam_err != PAM_SUCCESS)
480126277Sdes				goto auth_fail;
481137019Sdes			sshpam_password_change_required(0);
482126277Sdes		}
483126277Sdes	}
484126277Sdes
485124211Sdes	buffer_put_cstring(&buffer, "OK");
486126277Sdes
487147005Sdes#ifndef UNSUPPORTED_POSIX_THREADS_HACK
488126277Sdes	/* Export variables set by do_pam_account */
489126277Sdes	buffer_put_int(&buffer, sshpam_account_status);
490128460Sdes	buffer_put_int(&buffer, sshpam_authctxt->force_pwchange);
491126277Sdes
492126277Sdes	/* Export any environment strings set in child */
493126277Sdes	for(i = 0; environ[i] != NULL; i++)
494126277Sdes		; /* Count */
495126277Sdes	buffer_put_int(&buffer, i);
496126277Sdes	for(i = 0; environ[i] != NULL; i++)
497126277Sdes		buffer_put_cstring(&buffer, environ[i]);
498126277Sdes
499126277Sdes	/* Export any environment strings set by PAM in child */
500126277Sdes	env_from_pam = pam_getenvlist(sshpam_handle);
501126277Sdes	for(i = 0; env_from_pam != NULL && env_from_pam[i] != NULL; i++)
502126277Sdes		; /* Count */
503126277Sdes	buffer_put_int(&buffer, i);
504126277Sdes	for(i = 0; env_from_pam != NULL && env_from_pam[i] != NULL; i++)
505126277Sdes		buffer_put_cstring(&buffer, env_from_pam[i]);
506147005Sdes#endif /* UNSUPPORTED_POSIX_THREADS_HACK */
507126277Sdes
508126277Sdes	/* XXX - can't do much about an error here */
509124211Sdes	ssh_msg_send(ctxt->pam_csock, sshpam_err, &buffer);
510124211Sdes	buffer_free(&buffer);
511124211Sdes	pthread_exit(NULL);
512124211Sdes
513124211Sdes auth_fail:
514124211Sdes	buffer_put_cstring(&buffer,
515124211Sdes	    pam_strerror(sshpam_handle, sshpam_err));
516126277Sdes	/* XXX - can't do much about an error here */
517162856Sdes	if (sshpam_err == PAM_ACCT_EXPIRED)
518162856Sdes		ssh_msg_send(ctxt->pam_csock, PAM_ACCT_EXPIRED, &buffer);
519162856Sdes	else
520162856Sdes		ssh_msg_send(ctxt->pam_csock, PAM_AUTH_ERR, &buffer);
521124211Sdes	buffer_free(&buffer);
522124211Sdes	pthread_exit(NULL);
523126277Sdes
524124211Sdes	return (NULL); /* Avoid warning for non-pthread case */
52569591Sgreen}
52669591Sgreen
527126277Sdesvoid
528126277Sdessshpam_thread_cleanup(void)
52969591Sgreen{
530126277Sdes	struct pam_ctxt *ctxt = cleanup_ctxt;
53169591Sgreen
532126277Sdes	debug3("PAM: %s entering", __func__);
533126277Sdes	if (ctxt != NULL && ctxt->pam_thread != 0) {
534126277Sdes		pthread_cancel(ctxt->pam_thread);
535126277Sdes		pthread_join(ctxt->pam_thread, NULL);
536126277Sdes		close(ctxt->pam_psock);
537126277Sdes		close(ctxt->pam_csock);
538126277Sdes		memset(ctxt, 0, sizeof(*ctxt));
539126277Sdes		cleanup_ctxt = NULL;
540126277Sdes	}
541124211Sdes}
54276394Salfred
543124211Sdesstatic int
544149753Sdessshpam_null_conv(int n, sshpam_const struct pam_message **msg,
545124211Sdes    struct pam_response **resp, void *data)
546124211Sdes{
547126277Sdes	debug3("PAM: %s entering, %d messages", __func__, n);
548124211Sdes	return (PAM_CONV_ERR);
549124211Sdes}
55098941Sdes
551124211Sdesstatic struct pam_conv null_conv = { sshpam_null_conv, NULL };
552124211Sdes
553147005Sdesstatic int
554149753Sdessshpam_store_conv(int n, sshpam_const struct pam_message **msg,
555147005Sdes    struct pam_response **resp, void *data)
556147005Sdes{
557147005Sdes	struct pam_response *reply;
558147005Sdes	int i;
559147005Sdes	size_t len;
560147005Sdes
561147005Sdes	debug3("PAM: %s called with %d messages", __func__, n);
562147005Sdes	*resp = NULL;
563147005Sdes
564147005Sdes	if (n <= 0 || n > PAM_MAX_NUM_MSG)
565147005Sdes		return (PAM_CONV_ERR);
566147005Sdes
567162856Sdes	if ((reply = calloc(n, sizeof(*reply))) == NULL)
568147005Sdes		return (PAM_CONV_ERR);
569147005Sdes
570147005Sdes	for (i = 0; i < n; ++i) {
571147005Sdes		switch (PAM_MSG_MEMBER(msg, i, msg_style)) {
572147005Sdes		case PAM_ERROR_MSG:
573147005Sdes		case PAM_TEXT_INFO:
574147005Sdes			len = strlen(PAM_MSG_MEMBER(msg, i, msg));
575147005Sdes			buffer_append(&loginmsg, PAM_MSG_MEMBER(msg, i, msg), len);
576147005Sdes			buffer_append(&loginmsg, "\n", 1 );
577147005Sdes			reply[i].resp_retcode = PAM_SUCCESS;
578147005Sdes			break;
579147005Sdes		default:
580147005Sdes			goto fail;
581147005Sdes		}
582147005Sdes	}
583147005Sdes	*resp = reply;
584147005Sdes	return (PAM_SUCCESS);
585147005Sdes
586147005Sdes fail:
587147005Sdes	for(i = 0; i < n; i++) {
588255767Sdes		free(reply[i].resp);
589147005Sdes	}
590255767Sdes	free(reply);
591147005Sdes	return (PAM_CONV_ERR);
592147005Sdes}
593147005Sdes
594147005Sdesstatic struct pam_conv store_conv = { sshpam_store_conv, NULL };
595147005Sdes
596126277Sdesvoid
597126277Sdessshpam_cleanup(void)
598124211Sdes{
599181111Sdes	if (sshpam_handle == NULL || (use_privsep && !mm_is_monitor()))
600181111Sdes		return;
601124211Sdes	debug("PAM: cleanup");
602124211Sdes	pam_set_item(sshpam_handle, PAM_CONV, (const void *)&null_conv);
603197679Sdes	if (sshpam_session_open) {
604197679Sdes		debug("PAM: closing session");
605197679Sdes		pam_close_session(sshpam_handle, PAM_SILENT);
606197679Sdes		sshpam_session_open = 0;
607197679Sdes	}
608124211Sdes	if (sshpam_cred_established) {
609181111Sdes		debug("PAM: deleting credentials");
610124211Sdes		pam_setcred(sshpam_handle, PAM_DELETE_CRED);
611124211Sdes		sshpam_cred_established = 0;
61269591Sgreen	}
613126277Sdes	sshpam_authenticated = 0;
614124211Sdes	pam_end(sshpam_handle, sshpam_err);
615124211Sdes	sshpam_handle = NULL;
61669591Sgreen}
61769591Sgreen
618124211Sdesstatic int
619128460Sdessshpam_init(Authctxt *authctxt)
62069591Sgreen{
621124211Sdes	extern char *__progname;
622128460Sdes	const char *pam_rhost, *pam_user, *user = authctxt->user;
623149753Sdes	const char **ptr_pam_user = &pam_user;
62476394Salfred
625124211Sdes	if (sshpam_handle != NULL) {
626124211Sdes		/* We already have a PAM context; check if the user matches */
627124211Sdes		sshpam_err = pam_get_item(sshpam_handle,
628149753Sdes		    PAM_USER, (sshpam_const void **)ptr_pam_user);
629124211Sdes		if (sshpam_err == PAM_SUCCESS && strcmp(user, pam_user) == 0)
630124211Sdes			return (0);
631124211Sdes		pam_end(sshpam_handle, sshpam_err);
632124211Sdes		sshpam_handle = NULL;
63369591Sgreen	}
634124211Sdes	debug("PAM: initializing for \"%s\"", user);
635124211Sdes	sshpam_err =
636147005Sdes	    pam_start(SSHD_PAM_SERVICE, user, &store_conv, &sshpam_handle);
637128460Sdes	sshpam_authctxt = authctxt;
638128460Sdes
639124211Sdes	if (sshpam_err != PAM_SUCCESS) {
640124211Sdes		pam_end(sshpam_handle, sshpam_err);
641124211Sdes		sshpam_handle = NULL;
642124211Sdes		return (-1);
643124211Sdes	}
644124211Sdes	pam_rhost = get_remote_name_or_ip(utmp_len, options.use_dns);
645124211Sdes	debug("PAM: setting PAM_RHOST to \"%s\"", pam_rhost);
646124211Sdes	sshpam_err = pam_set_item(sshpam_handle, PAM_RHOST, pam_rhost);
647124211Sdes	if (sshpam_err != PAM_SUCCESS) {
648124211Sdes		pam_end(sshpam_handle, sshpam_err);
649124211Sdes		sshpam_handle = NULL;
650124211Sdes		return (-1);
651124211Sdes	}
652124211Sdes#ifdef PAM_TTY_KLUDGE
653126277Sdes	/*
654126277Sdes	 * Some silly PAM modules (e.g. pam_time) require a TTY to operate.
655126277Sdes	 * sshd doesn't set the tty until too late in the auth process and
656124211Sdes	 * may not even set one (for tty-less connections)
657126277Sdes	 */
658124211Sdes	debug("PAM: setting PAM_TTY to \"ssh\"");
659124211Sdes	sshpam_err = pam_set_item(sshpam_handle, PAM_TTY, "ssh");
660124211Sdes	if (sshpam_err != PAM_SUCCESS) {
661124211Sdes		pam_end(sshpam_handle, sshpam_err);
662124211Sdes		sshpam_handle = NULL;
663124211Sdes		return (-1);
664124211Sdes	}
66598941Sdes#endif
666124211Sdes	return (0);
66769591Sgreen}
66869591Sgreen
669124211Sdesstatic void *
670124211Sdessshpam_init_ctx(Authctxt *authctxt)
67169591Sgreen{
672124211Sdes	struct pam_ctxt *ctxt;
673124211Sdes	int socks[2];
67469591Sgreen
675126277Sdes	debug3("PAM: %s entering", __func__);
676162856Sdes	/*
677162856Sdes	 * Refuse to start if we don't have PAM enabled or do_pam_account
678162856Sdes	 * has previously failed.
679162856Sdes	 */
680162856Sdes	if (!options.use_pam || sshpam_account_status == 0)
681124211Sdes		return NULL;
68276394Salfred
683124211Sdes	/* Initialize PAM */
684128460Sdes	if (sshpam_init(authctxt) == -1) {
685124211Sdes		error("PAM: initialization failed");
686124211Sdes		return (NULL);
68769591Sgreen	}
68869591Sgreen
689181111Sdes	ctxt = xcalloc(1, sizeof *ctxt);
69076394Salfred
691124211Sdes	/* Start the authentication thread */
692124211Sdes	if (socketpair(AF_UNIX, SOCK_STREAM, PF_UNSPEC, socks) == -1) {
693124211Sdes		error("PAM: failed create sockets: %s", strerror(errno));
694255767Sdes		free(ctxt);
695124211Sdes		return (NULL);
696124211Sdes	}
697124211Sdes	ctxt->pam_psock = socks[0];
698124211Sdes	ctxt->pam_csock = socks[1];
699124211Sdes	if (pthread_create(&ctxt->pam_thread, NULL, sshpam_thread, ctxt) == -1) {
700124211Sdes		error("PAM: failed to start authentication thread: %s",
701124211Sdes		    strerror(errno));
702124211Sdes		close(socks[0]);
703124211Sdes		close(socks[1]);
704255767Sdes		free(ctxt);
705124211Sdes		return (NULL);
706124211Sdes	}
707126277Sdes	cleanup_ctxt = ctxt;
708124211Sdes	return (ctxt);
70969591Sgreen}
71069591Sgreen
711124211Sdesstatic int
712124211Sdessshpam_query(void *ctx, char **name, char **info,
713124211Sdes    u_int *num, char ***prompts, u_int **echo_on)
71469591Sgreen{
715124211Sdes	Buffer buffer;
716124211Sdes	struct pam_ctxt *ctxt = ctx;
717124211Sdes	size_t plen;
718124211Sdes	u_char type;
719124211Sdes	char *msg;
720147005Sdes	size_t len, mlen;
72176394Salfred
722126277Sdes	debug3("PAM: %s entering", __func__);
723124211Sdes	buffer_init(&buffer);
724124211Sdes	*name = xstrdup("");
725124211Sdes	*info = xstrdup("");
726124211Sdes	*prompts = xmalloc(sizeof(char *));
727124211Sdes	**prompts = NULL;
728124211Sdes	plen = 0;
729124211Sdes	*echo_on = xmalloc(sizeof(u_int));
730124211Sdes	while (ssh_msg_recv(ctxt->pam_psock, &buffer) == 0) {
731124211Sdes		type = buffer_get_char(&buffer);
732124211Sdes		msg = buffer_get_string(&buffer, NULL);
733147005Sdes		mlen = strlen(msg);
734124211Sdes		switch (type) {
735124211Sdes		case PAM_PROMPT_ECHO_ON:
736124211Sdes		case PAM_PROMPT_ECHO_OFF:
737124211Sdes			*num = 1;
738147005Sdes			len = plen + mlen + 1;
739162856Sdes			**prompts = xrealloc(**prompts, 1, len);
740147005Sdes			strlcpy(**prompts + plen, msg, len - plen);
741147005Sdes			plen += mlen;
742124211Sdes			**echo_on = (type == PAM_PROMPT_ECHO_ON);
743255767Sdes			free(msg);
744124211Sdes			return (0);
745124211Sdes		case PAM_ERROR_MSG:
746124211Sdes		case PAM_TEXT_INFO:
747124211Sdes			/* accumulate messages */
748147005Sdes			len = plen + mlen + 2;
749162856Sdes			**prompts = xrealloc(**prompts, 1, len);
750147005Sdes			strlcpy(**prompts + plen, msg, len - plen);
751147005Sdes			plen += mlen;
752147005Sdes			strlcat(**prompts + plen, "\n", len - plen);
753147005Sdes			plen++;
754255767Sdes			free(msg);
755124211Sdes			break;
756162856Sdes		case PAM_ACCT_EXPIRED:
757162856Sdes			sshpam_account_status = 0;
758162856Sdes			/* FALLTHROUGH */
759157019Sdes		case PAM_AUTH_ERR:
760162856Sdes			debug3("PAM: %s", pam_strerror(sshpam_handle, type));
761157019Sdes			if (**prompts != NULL && strlen(**prompts) != 0) {
762157019Sdes				*info = **prompts;
763157019Sdes				**prompts = NULL;
764157019Sdes				*num = 0;
765157019Sdes				**echo_on = 0;
766157019Sdes				ctxt->pam_done = -1;
767255767Sdes				free(msg);
768157019Sdes				return 0;
769157019Sdes			}
770157019Sdes			/* FALLTHROUGH */
771124211Sdes		case PAM_SUCCESS:
772124211Sdes			if (**prompts != NULL) {
773124211Sdes				/* drain any accumulated messages */
774126277Sdes				debug("PAM: %s", **prompts);
775126277Sdes				buffer_append(&loginmsg, **prompts,
776126277Sdes				    strlen(**prompts));
777255767Sdes				free(**prompts);
778124211Sdes				**prompts = NULL;
779124211Sdes			}
780124211Sdes			if (type == PAM_SUCCESS) {
781147005Sdes				if (!sshpam_authctxt->valid ||
782147005Sdes				    (sshpam_authctxt->pw->pw_uid == 0 &&
783147005Sdes				    options.permit_root_login != PERMIT_YES))
784147005Sdes					fatal("Internal error: PAM auth "
785147005Sdes					    "succeeded when it should have "
786147005Sdes					    "failed");
787126277Sdes				import_environments(&buffer);
788124211Sdes				*num = 0;
789124211Sdes				**echo_on = 0;
790124211Sdes				ctxt->pam_done = 1;
791255767Sdes				free(msg);
792124211Sdes				return (0);
793124211Sdes			}
794128460Sdes			error("PAM: %s for %s%.100s from %.100s", msg,
795128460Sdes			    sshpam_authctxt->valid ? "" : "illegal user ",
796128460Sdes			    sshpam_authctxt->user,
797128460Sdes			    get_remote_name_or_ip(utmp_len, options.use_dns));
798126277Sdes			/* FALLTHROUGH */
799124211Sdes		default:
800124211Sdes			*num = 0;
801124211Sdes			**echo_on = 0;
802255767Sdes			free(msg);
803124211Sdes			ctxt->pam_done = -1;
804124211Sdes			return (-1);
805124211Sdes		}
806124211Sdes	}
807124211Sdes	return (-1);
808124211Sdes}
80998941Sdes
810124211Sdes/* XXX - see also comment in auth-chall.c:verify_response */
811124211Sdesstatic int
812124211Sdessshpam_respond(void *ctx, u_int num, char **resp)
813124211Sdes{
814124211Sdes	Buffer buffer;
815124211Sdes	struct pam_ctxt *ctxt = ctx;
81698941Sdes
817157019Sdes	debug2("PAM: %s entering, %u responses", __func__, num);
818124211Sdes	switch (ctxt->pam_done) {
819124211Sdes	case 1:
820124211Sdes		sshpam_authenticated = 1;
821124211Sdes		return (0);
822124211Sdes	case 0:
823124211Sdes		break;
824124211Sdes	default:
825124211Sdes		return (-1);
826124211Sdes	}
827124211Sdes	if (num != 1) {
828124211Sdes		error("PAM: expected one response, got %u", num);
829124211Sdes		return (-1);
830124211Sdes	}
831124211Sdes	buffer_init(&buffer);
832147005Sdes	if (sshpam_authctxt->valid &&
833147005Sdes	    (sshpam_authctxt->pw->pw_uid != 0 ||
834149753Sdes	    options.permit_root_login == PERMIT_YES))
835147005Sdes		buffer_put_cstring(&buffer, *resp);
836147005Sdes	else
837147005Sdes		buffer_put_cstring(&buffer, badpw);
838126277Sdes	if (ssh_msg_send(ctxt->pam_psock, PAM_AUTHTOK, &buffer) == -1) {
839126277Sdes		buffer_free(&buffer);
840126277Sdes		return (-1);
841126277Sdes	}
842124211Sdes	buffer_free(&buffer);
843124211Sdes	return (1);
84469591Sgreen}
84569591Sgreen
846124211Sdesstatic void
847124211Sdessshpam_free_ctx(void *ctxtp)
84869591Sgreen{
849124211Sdes	struct pam_ctxt *ctxt = ctxtp;
850124211Sdes
851126277Sdes	debug3("PAM: %s entering", __func__);
852126277Sdes	sshpam_thread_cleanup();
853255767Sdes	free(ctxt);
854124211Sdes	/*
855124211Sdes	 * We don't call sshpam_cleanup() here because we may need the PAM
856124211Sdes	 * handle at a later stage, e.g. when setting up a session.  It's
857124211Sdes	 * still on the cleanup list, so pam_end() *will* be called before
858124211Sdes	 * the server process terminates.
859124211Sdes	 */
86069591Sgreen}
86169591Sgreen
862124211SdesKbdintDevice sshpam_device = {
863124211Sdes	"pam",
864124211Sdes	sshpam_init_ctx,
865124211Sdes	sshpam_query,
866124211Sdes	sshpam_respond,
867124211Sdes	sshpam_free_ctx
868124211Sdes};
869124211Sdes
870124211SdesKbdintDevice mm_sshpam_device = {
871124211Sdes	"pam",
872124211Sdes	mm_sshpam_init_ctx,
873124211Sdes	mm_sshpam_query,
874124211Sdes	mm_sshpam_respond,
875124211Sdes	mm_sshpam_free_ctx
876124211Sdes};
877124211Sdes
87898941Sdes/*
879124211Sdes * This replaces auth-pam.c
88069591Sgreen */
881124211Sdesvoid
882128460Sdesstart_pam(Authctxt *authctxt)
88369591Sgreen{
884124211Sdes	if (!options.use_pam)
885124211Sdes		fatal("PAM: initialisation requested when UsePAM=no");
88669591Sgreen
887128460Sdes	if (sshpam_init(authctxt) == -1)
888124211Sdes		fatal("PAM: initialisation failed");
88969591Sgreen}
89069591Sgreen
891124211Sdesvoid
892124211Sdesfinish_pam(void)
89369591Sgreen{
894126277Sdes	sshpam_cleanup();
89569591Sgreen}
89669591Sgreen
897124211Sdesu_int
898124211Sdesdo_pam_account(void)
89969591Sgreen{
900147005Sdes	debug("%s: called", __func__);
901126277Sdes	if (sshpam_account_status != -1)
902126277Sdes		return (sshpam_account_status);
903126277Sdes
904124211Sdes	sshpam_err = pam_acct_mgmt(sshpam_handle, 0);
905147005Sdes	debug3("PAM: %s pam_acct_mgmt = %d (%s)", __func__, sshpam_err,
906147005Sdes	    pam_strerror(sshpam_handle, sshpam_err));
907149753Sdes
908126277Sdes	if (sshpam_err != PAM_SUCCESS && sshpam_err != PAM_NEW_AUTHTOK_REQD) {
909126277Sdes		sshpam_account_status = 0;
910126277Sdes		return (sshpam_account_status);
911124211Sdes	}
91269591Sgreen
913126277Sdes	if (sshpam_err == PAM_NEW_AUTHTOK_REQD)
914137019Sdes		sshpam_password_change_required(1);
91569591Sgreen
916126277Sdes	sshpam_account_status = 1;
917126277Sdes	return (sshpam_account_status);
918124211Sdes}
91998941Sdes
920124211Sdesvoid
921124211Sdesdo_pam_set_tty(const char *tty)
922124211Sdes{
923124211Sdes	if (tty != NULL) {
924124211Sdes		debug("PAM: setting PAM_TTY to \"%s\"", tty);
925124211Sdes		sshpam_err = pam_set_item(sshpam_handle, PAM_TTY, tty);
926124211Sdes		if (sshpam_err != PAM_SUCCESS)
927124211Sdes			fatal("PAM: failed to set PAM_TTY: %s",
928124211Sdes			    pam_strerror(sshpam_handle, sshpam_err));
929124211Sdes	}
930124211Sdes}
93169591Sgreen
932124211Sdesvoid
933124211Sdesdo_pam_setcred(int init)
934124211Sdes{
935124211Sdes	sshpam_err = pam_set_item(sshpam_handle, PAM_CONV,
936147005Sdes	    (const void *)&store_conv);
937124211Sdes	if (sshpam_err != PAM_SUCCESS)
938124211Sdes		fatal("PAM: failed to set PAM_CONV: %s",
939124211Sdes		    pam_strerror(sshpam_handle, sshpam_err));
940124211Sdes	if (init) {
941124211Sdes		debug("PAM: establishing credentials");
942124211Sdes		sshpam_err = pam_setcred(sshpam_handle, PAM_ESTABLISH_CRED);
943124211Sdes	} else {
944124211Sdes		debug("PAM: reinitializing credentials");
945124211Sdes		sshpam_err = pam_setcred(sshpam_handle, PAM_REINITIALIZE_CRED);
946124211Sdes	}
947124211Sdes	if (sshpam_err == PAM_SUCCESS) {
948124211Sdes		sshpam_cred_established = 1;
949124211Sdes		return;
950124211Sdes	}
951124211Sdes	if (sshpam_authenticated)
952124211Sdes		fatal("PAM: pam_setcred(): %s",
953124211Sdes		    pam_strerror(sshpam_handle, sshpam_err));
954124211Sdes	else
955124211Sdes		debug("PAM: pam_setcred(): %s",
956124211Sdes		    pam_strerror(sshpam_handle, sshpam_err));
95769591Sgreen}
95869591Sgreen
959124211Sdesstatic int
960149753Sdessshpam_tty_conv(int n, sshpam_const struct pam_message **msg,
961124211Sdes    struct pam_response **resp, void *data)
962106130Sdes{
963124211Sdes	char input[PAM_MAX_MSG_SIZE];
964124211Sdes	struct pam_response *reply;
965106130Sdes	int i;
966106130Sdes
967126277Sdes	debug3("PAM: %s called with %d messages", __func__, n);
968126277Sdes
969124211Sdes	*resp = NULL;
970124211Sdes
971126277Sdes	if (n <= 0 || n > PAM_MAX_NUM_MSG || !isatty(STDIN_FILENO))
972124211Sdes		return (PAM_CONV_ERR);
973124211Sdes
974162856Sdes	if ((reply = calloc(n, sizeof(*reply))) == NULL)
975124211Sdes		return (PAM_CONV_ERR);
976124211Sdes
977124211Sdes	for (i = 0; i < n; ++i) {
978124211Sdes		switch (PAM_MSG_MEMBER(msg, i, msg_style)) {
979124211Sdes		case PAM_PROMPT_ECHO_OFF:
980124211Sdes			reply[i].resp =
981126277Sdes			    read_passphrase(PAM_MSG_MEMBER(msg, i, msg),
982124211Sdes			    RP_ALLOW_STDIN);
983124211Sdes			reply[i].resp_retcode = PAM_SUCCESS;
984124211Sdes			break;
985124211Sdes		case PAM_PROMPT_ECHO_ON:
986126277Sdes			fprintf(stderr, "%s\n", PAM_MSG_MEMBER(msg, i, msg));
987181111Sdes			if (fgets(input, sizeof input, stdin) == NULL)
988181111Sdes				input[0] = '\0';
989137019Sdes			if ((reply[i].resp = strdup(input)) == NULL)
990137019Sdes				goto fail;
991124211Sdes			reply[i].resp_retcode = PAM_SUCCESS;
992124211Sdes			break;
993124211Sdes		case PAM_ERROR_MSG:
994124211Sdes		case PAM_TEXT_INFO:
995126277Sdes			fprintf(stderr, "%s\n", PAM_MSG_MEMBER(msg, i, msg));
996124211Sdes			reply[i].resp_retcode = PAM_SUCCESS;
997124211Sdes			break;
998124211Sdes		default:
999124211Sdes			goto fail;
1000124211Sdes		}
1001106130Sdes	}
1002124211Sdes	*resp = reply;
1003124211Sdes	return (PAM_SUCCESS);
1004124211Sdes
1005124211Sdes fail:
1006124211Sdes	for(i = 0; i < n; i++) {
1007255767Sdes		free(reply[i].resp);
1008124211Sdes	}
1009255767Sdes	free(reply);
1010124211Sdes	return (PAM_CONV_ERR);
1011106130Sdes}
1012106130Sdes
1013137019Sdesstatic struct pam_conv tty_conv = { sshpam_tty_conv, NULL };
1014126277Sdes
1015124211Sdes/*
1016124211Sdes * XXX this should be done in the authentication phase, but ssh1 doesn't
1017124211Sdes * support that
1018124211Sdes */
1019124211Sdesvoid
1020124211Sdesdo_pam_chauthtok(void)
102169591Sgreen{
1022124211Sdes	if (use_privsep)
1023124211Sdes		fatal("Password expired (unable to change with privsep)");
1024124211Sdes	sshpam_err = pam_set_item(sshpam_handle, PAM_CONV,
1025126277Sdes	    (const void *)&tty_conv);
1026124211Sdes	if (sshpam_err != PAM_SUCCESS)
1027124211Sdes		fatal("PAM: failed to set PAM_CONV: %s",
1028124211Sdes		    pam_strerror(sshpam_handle, sshpam_err));
1029124211Sdes	debug("PAM: changing password");
1030124211Sdes	sshpam_err = pam_chauthtok(sshpam_handle, PAM_CHANGE_EXPIRED_AUTHTOK);
1031124211Sdes	if (sshpam_err != PAM_SUCCESS)
1032124211Sdes		fatal("PAM: pam_chauthtok(): %s",
1033124211Sdes		    pam_strerror(sshpam_handle, sshpam_err));
103469591Sgreen}
103569591Sgreen
1036126277Sdesvoid
1037126277Sdesdo_pam_session(void)
1038126277Sdes{
1039126277Sdes	debug3("PAM: opening session");
1040126277Sdes	sshpam_err = pam_set_item(sshpam_handle, PAM_CONV,
1041126277Sdes	    (const void *)&store_conv);
1042126277Sdes	if (sshpam_err != PAM_SUCCESS)
1043126277Sdes		fatal("PAM: failed to set PAM_CONV: %s",
1044126277Sdes		    pam_strerror(sshpam_handle, sshpam_err));
1045126277Sdes	sshpam_err = pam_open_session(sshpam_handle, 0);
1046147005Sdes	if (sshpam_err == PAM_SUCCESS)
1047147005Sdes		sshpam_session_open = 1;
1048147005Sdes	else {
1049147005Sdes		sshpam_session_open = 0;
1050147005Sdes		disable_forwarding();
1051147005Sdes		error("PAM: pam_open_session(): %s",
1052126277Sdes		    pam_strerror(sshpam_handle, sshpam_err));
1053147005Sdes	}
1054147005Sdes
1055126277Sdes}
1056126277Sdes
1057147005Sdesint
1058147005Sdesis_pam_session_open(void)
1059147005Sdes{
1060147005Sdes	return sshpam_session_open;
1061147005Sdes}
1062147005Sdes
1063126277Sdes/*
1064124211Sdes * Set a PAM environment string. We need to do this so that the session
1065124211Sdes * modules can handle things like Kerberos/GSI credentials that appear
1066124211Sdes * during the ssh authentication process.
1067124211Sdes */
1068124211Sdesint
1069126277Sdesdo_pam_putenv(char *name, char *value)
107069591Sgreen{
1071124211Sdes	int ret = 1;
1072126277Sdes#ifdef HAVE_PAM_PUTENV
1073124211Sdes	char *compound;
1074124211Sdes	size_t len;
107569591Sgreen
1076124211Sdes	len = strlen(name) + strlen(value) + 2;
1077124211Sdes	compound = xmalloc(len);
107869591Sgreen
1079124211Sdes	snprintf(compound, len, "%s=%s", name, value);
1080124211Sdes	ret = pam_putenv(sshpam_handle, compound);
1081255767Sdes	free(compound);
1082124211Sdes#endif
108369591Sgreen
1084124211Sdes	return (ret);
1085124211Sdes}
108669591Sgreen
1087126277Sdeschar **
1088126277Sdesfetch_pam_child_environment(void)
1089124211Sdes{
1090126277Sdes	return sshpam_env;
109169591Sgreen}
109269591Sgreen
1093124211Sdeschar **
1094124211Sdesfetch_pam_environment(void)
1095124211Sdes{
1096124211Sdes	return (pam_getenvlist(sshpam_handle));
1097124211Sdes}
1098124211Sdes
1099124211Sdesvoid
1100124211Sdesfree_pam_environment(char **env)
1101124211Sdes{
1102124211Sdes	char **envp;
1103124211Sdes
1104124211Sdes	if (env == NULL)
1105124211Sdes		return;
1106124211Sdes
1107124211Sdes	for (envp = env; *envp; envp++)
1108255767Sdes		free(*envp);
1109255767Sdes	free(env);
1110124211Sdes}
1111124211Sdes
1112137019Sdes/*
1113137019Sdes * "Blind" conversation function for password authentication.  Assumes that
1114137019Sdes * echo-off prompts are for the password and stores messages for later
1115137019Sdes * display.
1116137019Sdes */
1117137019Sdesstatic int
1118149753Sdessshpam_passwd_conv(int n, sshpam_const struct pam_message **msg,
1119137019Sdes    struct pam_response **resp, void *data)
1120137019Sdes{
1121137019Sdes	struct pam_response *reply;
1122137019Sdes	int i;
1123137019Sdes	size_t len;
1124137019Sdes
1125137019Sdes	debug3("PAM: %s called with %d messages", __func__, n);
1126137019Sdes
1127137019Sdes	*resp = NULL;
1128137019Sdes
1129137019Sdes	if (n <= 0 || n > PAM_MAX_NUM_MSG)
1130137019Sdes		return (PAM_CONV_ERR);
1131137019Sdes
1132181111Sdes	if ((reply = calloc(n, sizeof(*reply))) == NULL)
1133137019Sdes		return (PAM_CONV_ERR);
1134137019Sdes
1135137019Sdes	for (i = 0; i < n; ++i) {
1136137019Sdes		switch (PAM_MSG_MEMBER(msg, i, msg_style)) {
1137137019Sdes		case PAM_PROMPT_ECHO_OFF:
1138137019Sdes			if (sshpam_password == NULL)
1139137019Sdes				goto fail;
1140137019Sdes			if ((reply[i].resp = strdup(sshpam_password)) == NULL)
1141137019Sdes				goto fail;
1142137019Sdes			reply[i].resp_retcode = PAM_SUCCESS;
1143137019Sdes			break;
1144137019Sdes		case PAM_ERROR_MSG:
1145137019Sdes		case PAM_TEXT_INFO:
1146137019Sdes			len = strlen(PAM_MSG_MEMBER(msg, i, msg));
1147137019Sdes			if (len > 0) {
1148137019Sdes				buffer_append(&loginmsg,
1149137019Sdes				    PAM_MSG_MEMBER(msg, i, msg), len);
1150137019Sdes				buffer_append(&loginmsg, "\n", 1);
1151137019Sdes			}
1152137019Sdes			if ((reply[i].resp = strdup("")) == NULL)
1153137019Sdes				goto fail;
1154137019Sdes			reply[i].resp_retcode = PAM_SUCCESS;
1155137019Sdes			break;
1156137019Sdes		default:
1157137019Sdes			goto fail;
1158137019Sdes		}
1159137019Sdes	}
1160137019Sdes	*resp = reply;
1161137019Sdes	return (PAM_SUCCESS);
1162137019Sdes
1163149753Sdes fail:
1164137019Sdes	for(i = 0; i < n; i++) {
1165255767Sdes		free(reply[i].resp);
1166137019Sdes	}
1167255767Sdes	free(reply);
1168137019Sdes	return (PAM_CONV_ERR);
1169137019Sdes}
1170137019Sdes
1171137019Sdesstatic struct pam_conv passwd_conv = { sshpam_passwd_conv, NULL };
1172137019Sdes
1173137019Sdes/*
1174137019Sdes * Attempt password authentication via PAM
1175137019Sdes */
1176137019Sdesint
1177137019Sdessshpam_auth_passwd(Authctxt *authctxt, const char *password)
1178137019Sdes{
1179137019Sdes	int flags = (options.permit_empty_passwd == 0 ?
1180137019Sdes	    PAM_DISALLOW_NULL_AUTHTOK : 0);
1181137019Sdes
1182137019Sdes	if (!options.use_pam || sshpam_handle == NULL)
1183137019Sdes		fatal("PAM: %s called when PAM disabled or failed to "
1184137019Sdes		    "initialise.", __func__);
1185137019Sdes
1186137019Sdes	sshpam_password = password;
1187137019Sdes	sshpam_authctxt = authctxt;
1188137019Sdes
1189137019Sdes	/*
1190137019Sdes	 * If the user logging in is invalid, or is root but is not permitted
1191137019Sdes	 * by PermitRootLogin, use an invalid password to prevent leaking
1192137019Sdes	 * information via timing (eg if the PAM config has a delay on fail).
1193137019Sdes	 */
1194137019Sdes	if (!authctxt->valid || (authctxt->pw->pw_uid == 0 &&
1195149753Sdes	    options.permit_root_login != PERMIT_YES))
1196137019Sdes		sshpam_password = badpw;
1197137019Sdes
1198137019Sdes	sshpam_err = pam_set_item(sshpam_handle, PAM_CONV,
1199137019Sdes	    (const void *)&passwd_conv);
1200137019Sdes	if (sshpam_err != PAM_SUCCESS)
1201137019Sdes		fatal("PAM: %s: failed to set PAM_CONV: %s", __func__,
1202137019Sdes		    pam_strerror(sshpam_handle, sshpam_err));
1203137019Sdes
1204137019Sdes	sshpam_err = pam_authenticate(sshpam_handle, flags);
1205137019Sdes	sshpam_password = NULL;
1206137019Sdes	if (sshpam_err == PAM_SUCCESS && authctxt->valid) {
1207137019Sdes		debug("PAM: password authentication accepted for %.100s",
1208137019Sdes		    authctxt->user);
1209149753Sdes		return 1;
1210137019Sdes	} else {
1211137019Sdes		debug("PAM: password authentication failed for %.100s: %s",
1212137019Sdes		    authctxt->valid ? authctxt->user : "an illegal user",
1213137019Sdes		    pam_strerror(sshpam_handle, sshpam_err));
1214137019Sdes		return 0;
1215137019Sdes	}
1216137019Sdes}
121769591Sgreen#endif /* USE_PAM */
1218