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
48296633Sdes/* Based on FreeBSD: src/crypto/openssh/auth2-pam-freebsd.c,v 1.11 2003/03/31 13:48:18 des */
49296633Sdes
5069591Sgreen#include "includes.h"
5169591Sgreen
52162856Sdes#include <sys/types.h>
53162856Sdes#include <sys/stat.h>
54162856Sdes#include <sys/wait.h>
55162856Sdes
56162856Sdes#include <errno.h>
57162856Sdes#include <signal.h>
58162856Sdes#include <stdarg.h>
59162856Sdes#include <string.h>
60162856Sdes#include <unistd.h>
61162856Sdes
6269591Sgreen#ifdef USE_PAM
63126277Sdes#if defined(HAVE_SECURITY_PAM_APPL_H)
64124211Sdes#include <security/pam_appl.h>
65126277Sdes#elif defined (HAVE_PAM_PAM_APPL_H)
66126277Sdes#include <pam/pam_appl.h>
67126277Sdes#endif
68124211Sdes
69323134Sdes#if !defined(SSHD_PAM_SERVICE)
70323134Sdesextern char *__progname;
71323134Sdes# define SSHD_PAM_SERVICE		__progname
72323134Sdes#endif
73323134Sdes
74149753Sdes/* OpenGroup RFC86.0 and XSSO specify no "const" on arguments */
75149753Sdes#ifdef PAM_SUN_CODEBASE
76323129Sdes# define sshpam_const		/* Solaris, HP-UX, SunOS */
77149753Sdes#else
78323129Sdes# define sshpam_const	const	/* LinuxPAM, OpenPAM, AIX */
79149753Sdes#endif
80149753Sdes
81162856Sdes/* Ambiguity in spec: is it an array of pointers or a pointer to an array? */
82162856Sdes#ifdef PAM_SUN_CODEBASE
83162856Sdes# define PAM_MSG_MEMBER(msg, n, member) ((*(msg))[(n)].member)
84162856Sdes#else
85162856Sdes# define PAM_MSG_MEMBER(msg, n, member) ((msg)[(n)]->member)
86162856Sdes#endif
87162856Sdes
88162856Sdes#include "xmalloc.h"
89162856Sdes#include "buffer.h"
90162856Sdes#include "key.h"
91162856Sdes#include "hostfile.h"
9298941Sdes#include "auth.h"
9398941Sdes#include "auth-pam.h"
9498941Sdes#include "canohost.h"
95124211Sdes#include "log.h"
96124211Sdes#include "msg.h"
97124211Sdes#include "packet.h"
98137019Sdes#include "misc.h"
99124211Sdes#include "servconf.h"
100124211Sdes#include "ssh2.h"
101124211Sdes#include "auth-options.h"
102162856Sdes#ifdef GSSAPI
103162856Sdes#include "ssh-gss.h"
104162856Sdes#endif
105162856Sdes#include "monitor_wrap.h"
106305476Slidl#include "blacklist_client.h"
10769591Sgreen
108124211Sdesextern ServerOptions options;
109126277Sdesextern Buffer loginmsg;
110126277Sdesextern int compat20;
111128460Sdesextern u_int utmp_len;
11269591Sgreen
113147005Sdes/* so we don't silently change behaviour */
114124211Sdes#ifdef USE_POSIX_THREADS
115147005Sdes# error "USE_POSIX_THREADS replaced by UNSUPPORTED_POSIX_THREADS_HACK"
116147005Sdes#endif
117147005Sdes
118147005Sdes/*
119147005Sdes * Formerly known as USE_POSIX_THREADS, using this is completely unsupported
120147005Sdes * and generally a bad idea.  Use at own risk and do not expect support if
121147005Sdes * this breaks.
122147005Sdes */
123147005Sdes#ifdef UNSUPPORTED_POSIX_THREADS_HACK
124124211Sdes#include <pthread.h>
125124211Sdes/*
126126277Sdes * Avoid namespace clash when *not* using pthreads for systems *with*
127126277Sdes * pthreads, which unconditionally define pthread_t via sys/types.h
128124211Sdes * (e.g. Linux)
129124211Sdes */
130126277Sdestypedef pthread_t sp_pthread_t;
131124211Sdes#else
132126277Sdestypedef pid_t sp_pthread_t;
133126277Sdes#endif
134126277Sdes
135126277Sdesstruct pam_ctxt {
136126277Sdes	sp_pthread_t	 pam_thread;
137126277Sdes	int		 pam_psock;
138126277Sdes	int		 pam_csock;
139126277Sdes	int		 pam_done;
140126277Sdes};
141126277Sdes
142126277Sdesstatic void sshpam_free_ctx(void *);
143126277Sdesstatic struct pam_ctxt *cleanup_ctxt;
144126277Sdes
145147005Sdes#ifndef UNSUPPORTED_POSIX_THREADS_HACK
146124211Sdes/*
147124211Sdes * Simulate threads with processes.
148124211Sdes */
149106130Sdes
150126277Sdesstatic int sshpam_thread_status = -1;
151126277Sdesstatic mysig_t sshpam_oldsig;
152126277Sdes
153149753Sdesstatic void
154126277Sdessshpam_sigchld_handler(int sig)
155126277Sdes{
156137019Sdes	signal(SIGCHLD, SIG_DFL);
157126277Sdes	if (cleanup_ctxt == NULL)
158126277Sdes		return;	/* handler called after PAM cleanup, shouldn't happen */
159137019Sdes	if (waitpid(cleanup_ctxt->pam_thread, &sshpam_thread_status, WNOHANG)
160149753Sdes	    <= 0) {
161137019Sdes		/* PAM thread has not exitted, privsep slave must have */
162137019Sdes		kill(cleanup_ctxt->pam_thread, SIGTERM);
163323129Sdes		while (waitpid(cleanup_ctxt->pam_thread,
164323129Sdes		    &sshpam_thread_status, 0) == -1) {
165323129Sdes			if (errno == EINTR)
166323129Sdes				continue;
167323129Sdes			return;
168323129Sdes		}
169137019Sdes	}
170126277Sdes	if (WIFSIGNALED(sshpam_thread_status) &&
171126277Sdes	    WTERMSIG(sshpam_thread_status) == SIGTERM)
172126277Sdes		return;	/* terminated by pthread_cancel */
173126277Sdes	if (!WIFEXITED(sshpam_thread_status))
174181111Sdes		sigdie("PAM: authentication thread exited unexpectedly");
175126277Sdes	if (WEXITSTATUS(sshpam_thread_status) != 0)
176181111Sdes		sigdie("PAM: authentication thread exited uncleanly");
177126277Sdes}
178126277Sdes
179162856Sdes/* ARGSUSED */
180124211Sdesstatic void
181162856Sdespthread_exit(void *value)
182124211Sdes{
183124211Sdes	_exit(0);
184124211Sdes}
18569591Sgreen
186162856Sdes/* ARGSUSED */
187124211Sdesstatic int
188162856Sdespthread_create(sp_pthread_t *thread, const void *attr,
189124211Sdes    void *(*thread_start)(void *), void *arg)
190124211Sdes{
191124211Sdes	pid_t pid;
192149753Sdes	struct pam_ctxt *ctx = arg;
19369591Sgreen
194128460Sdes	sshpam_thread_status = -1;
195124211Sdes	switch ((pid = fork())) {
196124211Sdes	case -1:
197124211Sdes		error("fork(): %s", strerror(errno));
198124211Sdes		return (-1);
199124211Sdes	case 0:
200149753Sdes		close(ctx->pam_psock);
201149753Sdes		ctx->pam_psock = -1;
202124211Sdes		thread_start(arg);
203124211Sdes		_exit(1);
204124211Sdes	default:
205124211Sdes		*thread = pid;
206149753Sdes		close(ctx->pam_csock);
207149753Sdes		ctx->pam_csock = -1;
208126277Sdes		sshpam_oldsig = signal(SIGCHLD, sshpam_sigchld_handler);
209124211Sdes		return (0);
210124211Sdes	}
211124211Sdes}
21269591Sgreen
213124211Sdesstatic int
214124211Sdespthread_cancel(sp_pthread_t thread)
21576394Salfred{
216126277Sdes	signal(SIGCHLD, sshpam_oldsig);
217124211Sdes	return (kill(thread, SIGTERM));
21876394Salfred}
21976394Salfred
220162856Sdes/* ARGSUSED */
221124211Sdesstatic int
222162856Sdespthread_join(sp_pthread_t thread, void **value)
22398941Sdes{
224124211Sdes	int status;
225124211Sdes
226126277Sdes	if (sshpam_thread_status != -1)
227126277Sdes		return (sshpam_thread_status);
228126277Sdes	signal(SIGCHLD, sshpam_oldsig);
229323129Sdes	while (waitpid(thread, &status, 0) == -1) {
230323129Sdes		if (errno == EINTR)
231323129Sdes			continue;
232323129Sdes		fatal("%s: waitpid: %s", __func__, strerror(errno));
233323129Sdes	}
234124211Sdes	return (status);
23598941Sdes}
236124211Sdes#endif
23798941Sdes
238124211Sdes
239124211Sdesstatic pam_handle_t *sshpam_handle = NULL;
240124211Sdesstatic int sshpam_err = 0;
241124211Sdesstatic int sshpam_authenticated = 0;
242124211Sdesstatic int sshpam_session_open = 0;
243124211Sdesstatic int sshpam_cred_established = 0;
244126277Sdesstatic int sshpam_account_status = -1;
245323129Sdesstatic int sshpam_maxtries_reached = 0;
246126277Sdesstatic char **sshpam_env = NULL;
247128460Sdesstatic Authctxt *sshpam_authctxt = NULL;
248137019Sdesstatic const char *sshpam_password = NULL;
249124211Sdes
250126277Sdes/* Some PAM implementations don't implement this */
251126277Sdes#ifndef HAVE_PAM_GETENVLIST
252126277Sdesstatic char **
253126277Sdespam_getenvlist(pam_handle_t *pamh)
254126277Sdes{
255126277Sdes	/*
256126277Sdes	 * XXX - If necessary, we can still support envrionment passing
257126277Sdes	 * for platforms without pam_getenvlist by searching for known
258126277Sdes	 * env vars (e.g. KRB5CCNAME) from the PAM environment.
259126277Sdes	 */
260126277Sdes	 return NULL;
261126277Sdes}
262126277Sdes#endif
263124211Sdes
264137019Sdes/*
265137019Sdes * Some platforms, notably Solaris, do not enforce password complexity
266137019Sdes * rules during pam_chauthtok() if the real uid of the calling process
267137019Sdes * is 0, on the assumption that it's being called by "passwd" run by root.
268137019Sdes * This wraps pam_chauthtok and sets/restore the real uid so PAM will do
269137019Sdes * the right thing.
270137019Sdes */
271137019Sdes#ifdef SSHPAM_CHAUTHTOK_NEEDS_RUID
272137019Sdesstatic int
273137019Sdessshpam_chauthtok_ruid(pam_handle_t *pamh, int flags)
274137019Sdes{
275137019Sdes	int result;
276137019Sdes
277137019Sdes	if (sshpam_authctxt == NULL)
278137019Sdes		fatal("PAM: sshpam_authctxt not initialized");
279137019Sdes	if (setreuid(sshpam_authctxt->pw->pw_uid, -1) == -1)
280137019Sdes		fatal("%s: setreuid failed: %s", __func__, strerror(errno));
281137019Sdes	result = pam_chauthtok(pamh, flags);
282137019Sdes	if (setreuid(0, -1) == -1)
283137019Sdes		fatal("%s: setreuid failed: %s", __func__, strerror(errno));
284137019Sdes	return result;
285137019Sdes}
286137019Sdes# define pam_chauthtok(a,b)	(sshpam_chauthtok_ruid((a), (b)))
287137019Sdes#endif
288137019Sdes
289126277Sdesvoid
290137019Sdessshpam_password_change_required(int reqd)
291126277Sdes{
292126277Sdes	debug3("%s %d", __func__, reqd);
293128460Sdes	if (sshpam_authctxt == NULL)
294128460Sdes		fatal("%s: PAM authctxt not initialized", __func__);
295128460Sdes	sshpam_authctxt->force_pwchange = reqd;
296126277Sdes	if (reqd) {
297126277Sdes		no_port_forwarding_flag |= 2;
298126277Sdes		no_agent_forwarding_flag |= 2;
299126277Sdes		no_x11_forwarding_flag |= 2;
300126277Sdes	} else {
301126277Sdes		no_port_forwarding_flag &= ~2;
302126277Sdes		no_agent_forwarding_flag &= ~2;
303126277Sdes		no_x11_forwarding_flag &= ~2;
304126277Sdes	}
305126277Sdes}
306124211Sdes
307126277Sdes/* Import regular and PAM environment from subprocess */
308126277Sdesstatic void
309126277Sdesimport_environments(Buffer *b)
310126277Sdes{
311126277Sdes	char *env;
312126277Sdes	u_int i, num_env;
313126277Sdes	int err;
314126277Sdes
315126277Sdes	debug3("PAM: %s entering", __func__);
316126277Sdes
317147005Sdes#ifndef UNSUPPORTED_POSIX_THREADS_HACK
318126277Sdes	/* Import variables set by do_pam_account */
319126277Sdes	sshpam_account_status = buffer_get_int(b);
320137019Sdes	sshpam_password_change_required(buffer_get_int(b));
321126277Sdes
322126277Sdes	/* Import environment from subprocess */
323126277Sdes	num_env = buffer_get_int(b);
324162856Sdes	if (num_env > 1024)
325162856Sdes		fatal("%s: received %u environment variables, expected <= 1024",
326162856Sdes		    __func__, num_env);
327162856Sdes	sshpam_env = xcalloc(num_env + 1, sizeof(*sshpam_env));
328126277Sdes	debug3("PAM: num env strings %d", num_env);
329126277Sdes	for(i = 0; i < num_env; i++)
330126277Sdes		sshpam_env[i] = buffer_get_string(b, NULL);
331126277Sdes
332126277Sdes	sshpam_env[num_env] = NULL;
333126277Sdes
334126277Sdes	/* Import PAM environment from subprocess */
335126277Sdes	num_env = buffer_get_int(b);
336126277Sdes	debug("PAM: num PAM env strings %d", num_env);
337126277Sdes	for(i = 0; i < num_env; i++) {
338126277Sdes		env = buffer_get_string(b, NULL);
339126277Sdes
340126277Sdes#ifdef HAVE_PAM_PUTENV
341126277Sdes		/* Errors are not fatal here */
342126277Sdes		if ((err = pam_putenv(sshpam_handle, env)) != PAM_SUCCESS) {
343126277Sdes			error("PAM: pam_putenv: %s",
344126277Sdes			    pam_strerror(sshpam_handle, sshpam_err));
345126277Sdes		}
346126277Sdes#endif
347126277Sdes	}
348128460Sdes#endif
349126277Sdes}
350126277Sdes
35176394Salfred/*
352124211Sdes * Conversation function for authentication thread.
35369591Sgreen */
354124211Sdesstatic int
355149753Sdessshpam_thread_conv(int n, sshpam_const struct pam_message **msg,
356124211Sdes    struct pam_response **resp, void *data)
35769591Sgreen{
358124211Sdes	Buffer buffer;
359124211Sdes	struct pam_ctxt *ctxt;
36069591Sgreen	struct pam_response *reply;
361124211Sdes	int i;
36269591Sgreen
363126277Sdes	debug3("PAM: %s entering, %d messages", __func__, n);
364124211Sdes	*resp = NULL;
36569591Sgreen
366137019Sdes	if (data == NULL) {
367137019Sdes		error("PAM: conversation function passed a null context");
368137019Sdes		return (PAM_CONV_ERR);
369137019Sdes	}
370124211Sdes	ctxt = data;
371124211Sdes	if (n <= 0 || n > PAM_MAX_NUM_MSG)
372124211Sdes		return (PAM_CONV_ERR);
373124211Sdes
374162856Sdes	if ((reply = calloc(n, sizeof(*reply))) == NULL)
375124211Sdes		return (PAM_CONV_ERR);
376124211Sdes
377124211Sdes	buffer_init(&buffer);
378124211Sdes	for (i = 0; i < n; ++i) {
379124211Sdes		switch (PAM_MSG_MEMBER(msg, i, msg_style)) {
380124211Sdes		case PAM_PROMPT_ECHO_OFF:
381124211Sdes		case PAM_PROMPT_ECHO_ON:
382126277Sdes			buffer_put_cstring(&buffer,
383124211Sdes			    PAM_MSG_MEMBER(msg, i, msg));
384126277Sdes			if (ssh_msg_send(ctxt->pam_csock,
385126277Sdes			    PAM_MSG_MEMBER(msg, i, msg_style), &buffer) == -1)
386126277Sdes				goto fail;
387126277Sdes			if (ssh_msg_recv(ctxt->pam_csock, &buffer) == -1)
388126277Sdes				goto fail;
389124211Sdes			if (buffer_get_char(&buffer) != PAM_AUTHTOK)
390124211Sdes				goto fail;
391124211Sdes			reply[i].resp = buffer_get_string(&buffer, NULL);
392124211Sdes			break;
393124211Sdes		case PAM_ERROR_MSG:
394124211Sdes		case PAM_TEXT_INFO:
395126277Sdes			buffer_put_cstring(&buffer,
396124211Sdes			    PAM_MSG_MEMBER(msg, i, msg));
397126277Sdes			if (ssh_msg_send(ctxt->pam_csock,
398126277Sdes			    PAM_MSG_MEMBER(msg, i, msg_style), &buffer) == -1)
399126277Sdes				goto fail;
400124211Sdes			break;
401124211Sdes		default:
402124211Sdes			goto fail;
40369591Sgreen		}
404124211Sdes		buffer_clear(&buffer);
40569591Sgreen	}
406124211Sdes	buffer_free(&buffer);
40769591Sgreen	*resp = reply;
408124211Sdes	return (PAM_SUCCESS);
40969591Sgreen
410124211Sdes fail:
411124211Sdes	for(i = 0; i < n; i++) {
412255767Sdes		free(reply[i].resp);
413124211Sdes	}
414255767Sdes	free(reply);
415124211Sdes	buffer_free(&buffer);
416124211Sdes	return (PAM_CONV_ERR);
41769591Sgreen}
41869591Sgreen
419124211Sdes/*
420124211Sdes * Authentication thread.
421124211Sdes */
422124211Sdesstatic void *
423124211Sdessshpam_thread(void *ctxtp)
42469591Sgreen{
425124211Sdes	struct pam_ctxt *ctxt = ctxtp;
426124211Sdes	Buffer buffer;
427124211Sdes	struct pam_conv sshpam_conv;
428137019Sdes	int flags = (options.permit_empty_passwd == 0 ?
429137019Sdes	    PAM_DISALLOW_NULL_AUTHTOK : 0);
430147005Sdes#ifndef UNSUPPORTED_POSIX_THREADS_HACK
431126277Sdes	extern char **environ;
432126277Sdes	char **env_from_pam;
433126277Sdes	u_int i;
434124211Sdes	const char *pam_user;
435149753Sdes	const char **ptr_pam_user = &pam_user;
436162856Sdes	char *tz = getenv("TZ");
43769591Sgreen
438261320Sdes	sshpam_err = pam_get_item(sshpam_handle, PAM_USER,
439149753Sdes	    (sshpam_const void **)ptr_pam_user);
440261320Sdes	if (sshpam_err != PAM_SUCCESS)
441261320Sdes		goto auth_fail;
442162856Sdes
443126277Sdes	environ[0] = NULL;
444162856Sdes	if (tz != NULL)
445162856Sdes		if (setenv("TZ", tz, 1) == -1)
446162856Sdes			error("PAM: could not set TZ environment: %s",
447162856Sdes			    strerror(errno));
448137019Sdes
449137019Sdes	if (sshpam_authctxt != NULL) {
450137019Sdes		setproctitle("%s [pam]",
451137019Sdes		    sshpam_authctxt->valid ? pam_user : "unknown");
452137019Sdes	}
453124211Sdes#endif
45469591Sgreen
455124211Sdes	sshpam_conv.conv = sshpam_thread_conv;
456124211Sdes	sshpam_conv.appdata_ptr = ctxt;
45769591Sgreen
458128460Sdes	if (sshpam_authctxt == NULL)
459128460Sdes		fatal("%s: PAM authctxt not initialized", __func__);
460128460Sdes
461124211Sdes	buffer_init(&buffer);
462124211Sdes	sshpam_err = pam_set_item(sshpam_handle, PAM_CONV,
463124211Sdes	    (const void *)&sshpam_conv);
464124211Sdes	if (sshpam_err != PAM_SUCCESS)
465124211Sdes		goto auth_fail;
466137019Sdes	sshpam_err = pam_authenticate(sshpam_handle, flags);
467323129Sdes	if (sshpam_err == PAM_MAXTRIES)
468323129Sdes		sshpam_set_maxtries_reached(1);
469124211Sdes	if (sshpam_err != PAM_SUCCESS)
470124211Sdes		goto auth_fail;
471126277Sdes
472126277Sdes	if (compat20) {
473162856Sdes		if (!do_pam_account()) {
474162856Sdes			sshpam_err = PAM_ACCT_EXPIRED;
475126277Sdes			goto auth_fail;
476162856Sdes		}
477128460Sdes		if (sshpam_authctxt->force_pwchange) {
478126277Sdes			sshpam_err = pam_chauthtok(sshpam_handle,
479126277Sdes			    PAM_CHANGE_EXPIRED_AUTHTOK);
480126277Sdes			if (sshpam_err != PAM_SUCCESS)
481126277Sdes				goto auth_fail;
482137019Sdes			sshpam_password_change_required(0);
483126277Sdes		}
484126277Sdes	}
485126277Sdes
486124211Sdes	buffer_put_cstring(&buffer, "OK");
487126277Sdes
488147005Sdes#ifndef UNSUPPORTED_POSIX_THREADS_HACK
489126277Sdes	/* Export variables set by do_pam_account */
490126277Sdes	buffer_put_int(&buffer, sshpam_account_status);
491128460Sdes	buffer_put_int(&buffer, sshpam_authctxt->force_pwchange);
492126277Sdes
493126277Sdes	/* Export any environment strings set in child */
494126277Sdes	for(i = 0; environ[i] != NULL; i++)
495126277Sdes		; /* Count */
496126277Sdes	buffer_put_int(&buffer, i);
497126277Sdes	for(i = 0; environ[i] != NULL; i++)
498126277Sdes		buffer_put_cstring(&buffer, environ[i]);
499126277Sdes
500126277Sdes	/* Export any environment strings set by PAM in child */
501126277Sdes	env_from_pam = pam_getenvlist(sshpam_handle);
502126277Sdes	for(i = 0; env_from_pam != NULL && env_from_pam[i] != NULL; i++)
503126277Sdes		; /* Count */
504126277Sdes	buffer_put_int(&buffer, i);
505126277Sdes	for(i = 0; env_from_pam != NULL && env_from_pam[i] != NULL; i++)
506126277Sdes		buffer_put_cstring(&buffer, env_from_pam[i]);
507147005Sdes#endif /* UNSUPPORTED_POSIX_THREADS_HACK */
508126277Sdes
509126277Sdes	/* XXX - can't do much about an error here */
510124211Sdes	ssh_msg_send(ctxt->pam_csock, sshpam_err, &buffer);
511124211Sdes	buffer_free(&buffer);
512124211Sdes	pthread_exit(NULL);
513124211Sdes
514124211Sdes auth_fail:
515124211Sdes	buffer_put_cstring(&buffer,
516124211Sdes	    pam_strerror(sshpam_handle, sshpam_err));
517126277Sdes	/* XXX - can't do much about an error here */
518162856Sdes	if (sshpam_err == PAM_ACCT_EXPIRED)
519162856Sdes		ssh_msg_send(ctxt->pam_csock, PAM_ACCT_EXPIRED, &buffer);
520323129Sdes	else if (sshpam_maxtries_reached)
521323129Sdes		ssh_msg_send(ctxt->pam_csock, PAM_MAXTRIES, &buffer);
522162856Sdes	else
523162856Sdes		ssh_msg_send(ctxt->pam_csock, PAM_AUTH_ERR, &buffer);
524124211Sdes	buffer_free(&buffer);
525124211Sdes	pthread_exit(NULL);
526126277Sdes
527124211Sdes	return (NULL); /* Avoid warning for non-pthread case */
52869591Sgreen}
52969591Sgreen
530126277Sdesvoid
531126277Sdessshpam_thread_cleanup(void)
53269591Sgreen{
533126277Sdes	struct pam_ctxt *ctxt = cleanup_ctxt;
53469591Sgreen
535126277Sdes	debug3("PAM: %s entering", __func__);
536126277Sdes	if (ctxt != NULL && ctxt->pam_thread != 0) {
537126277Sdes		pthread_cancel(ctxt->pam_thread);
538126277Sdes		pthread_join(ctxt->pam_thread, NULL);
539126277Sdes		close(ctxt->pam_psock);
540126277Sdes		close(ctxt->pam_csock);
541126277Sdes		memset(ctxt, 0, sizeof(*ctxt));
542126277Sdes		cleanup_ctxt = NULL;
543126277Sdes	}
544124211Sdes}
54576394Salfred
546124211Sdesstatic int
547149753Sdessshpam_null_conv(int n, sshpam_const struct pam_message **msg,
548124211Sdes    struct pam_response **resp, void *data)
549124211Sdes{
550126277Sdes	debug3("PAM: %s entering, %d messages", __func__, n);
551124211Sdes	return (PAM_CONV_ERR);
552124211Sdes}
55398941Sdes
554124211Sdesstatic struct pam_conv null_conv = { sshpam_null_conv, NULL };
555124211Sdes
556147005Sdesstatic int
557149753Sdessshpam_store_conv(int n, sshpam_const struct pam_message **msg,
558147005Sdes    struct pam_response **resp, void *data)
559147005Sdes{
560147005Sdes	struct pam_response *reply;
561147005Sdes	int i;
562147005Sdes	size_t len;
563147005Sdes
564147005Sdes	debug3("PAM: %s called with %d messages", __func__, n);
565147005Sdes	*resp = NULL;
566147005Sdes
567147005Sdes	if (n <= 0 || n > PAM_MAX_NUM_MSG)
568147005Sdes		return (PAM_CONV_ERR);
569147005Sdes
570162856Sdes	if ((reply = calloc(n, sizeof(*reply))) == NULL)
571147005Sdes		return (PAM_CONV_ERR);
572147005Sdes
573147005Sdes	for (i = 0; i < n; ++i) {
574147005Sdes		switch (PAM_MSG_MEMBER(msg, i, msg_style)) {
575147005Sdes		case PAM_ERROR_MSG:
576147005Sdes		case PAM_TEXT_INFO:
577147005Sdes			len = strlen(PAM_MSG_MEMBER(msg, i, msg));
578147005Sdes			buffer_append(&loginmsg, PAM_MSG_MEMBER(msg, i, msg), len);
579147005Sdes			buffer_append(&loginmsg, "\n", 1 );
580147005Sdes			reply[i].resp_retcode = PAM_SUCCESS;
581147005Sdes			break;
582147005Sdes		default:
583147005Sdes			goto fail;
584147005Sdes		}
585147005Sdes	}
586147005Sdes	*resp = reply;
587147005Sdes	return (PAM_SUCCESS);
588147005Sdes
589147005Sdes fail:
590147005Sdes	for(i = 0; i < n; i++) {
591255767Sdes		free(reply[i].resp);
592147005Sdes	}
593255767Sdes	free(reply);
594147005Sdes	return (PAM_CONV_ERR);
595147005Sdes}
596147005Sdes
597147005Sdesstatic struct pam_conv store_conv = { sshpam_store_conv, NULL };
598147005Sdes
599126277Sdesvoid
600126277Sdessshpam_cleanup(void)
601124211Sdes{
602181111Sdes	if (sshpam_handle == NULL || (use_privsep && !mm_is_monitor()))
603181111Sdes		return;
604124211Sdes	debug("PAM: cleanup");
605124211Sdes	pam_set_item(sshpam_handle, PAM_CONV, (const void *)&null_conv);
606197679Sdes	if (sshpam_session_open) {
607197679Sdes		debug("PAM: closing session");
608197679Sdes		pam_close_session(sshpam_handle, PAM_SILENT);
609197679Sdes		sshpam_session_open = 0;
610197679Sdes	}
611124211Sdes	if (sshpam_cred_established) {
612181111Sdes		debug("PAM: deleting credentials");
613124211Sdes		pam_setcred(sshpam_handle, PAM_DELETE_CRED);
614124211Sdes		sshpam_cred_established = 0;
61569591Sgreen	}
616126277Sdes	sshpam_authenticated = 0;
617124211Sdes	pam_end(sshpam_handle, sshpam_err);
618124211Sdes	sshpam_handle = NULL;
61969591Sgreen}
62069591Sgreen
621124211Sdesstatic int
622128460Sdessshpam_init(Authctxt *authctxt)
62369591Sgreen{
624128460Sdes	const char *pam_rhost, *pam_user, *user = authctxt->user;
625149753Sdes	const char **ptr_pam_user = &pam_user;
626323129Sdes	struct ssh *ssh = active_state; /* XXX */
62776394Salfred
628124211Sdes	if (sshpam_handle != NULL) {
629124211Sdes		/* We already have a PAM context; check if the user matches */
630124211Sdes		sshpam_err = pam_get_item(sshpam_handle,
631149753Sdes		    PAM_USER, (sshpam_const void **)ptr_pam_user);
632124211Sdes		if (sshpam_err == PAM_SUCCESS && strcmp(user, pam_user) == 0)
633124211Sdes			return (0);
634124211Sdes		pam_end(sshpam_handle, sshpam_err);
635124211Sdes		sshpam_handle = NULL;
63669591Sgreen	}
637124211Sdes	debug("PAM: initializing for \"%s\"", user);
638124211Sdes	sshpam_err =
639147005Sdes	    pam_start(SSHD_PAM_SERVICE, user, &store_conv, &sshpam_handle);
640128460Sdes	sshpam_authctxt = authctxt;
641128460Sdes
642124211Sdes	if (sshpam_err != PAM_SUCCESS) {
643124211Sdes		pam_end(sshpam_handle, sshpam_err);
644124211Sdes		sshpam_handle = NULL;
645124211Sdes		return (-1);
646124211Sdes	}
647323129Sdes	pam_rhost = auth_get_canonical_hostname(ssh, options.use_dns);
648124211Sdes	debug("PAM: setting PAM_RHOST to \"%s\"", pam_rhost);
649124211Sdes	sshpam_err = pam_set_item(sshpam_handle, PAM_RHOST, pam_rhost);
650124211Sdes	if (sshpam_err != PAM_SUCCESS) {
651124211Sdes		pam_end(sshpam_handle, sshpam_err);
652124211Sdes		sshpam_handle = NULL;
653124211Sdes		return (-1);
654124211Sdes	}
655124211Sdes#ifdef PAM_TTY_KLUDGE
656126277Sdes	/*
657126277Sdes	 * Some silly PAM modules (e.g. pam_time) require a TTY to operate.
658126277Sdes	 * sshd doesn't set the tty until too late in the auth process and
659124211Sdes	 * may not even set one (for tty-less connections)
660126277Sdes	 */
661124211Sdes	debug("PAM: setting PAM_TTY to \"ssh\"");
662124211Sdes	sshpam_err = pam_set_item(sshpam_handle, PAM_TTY, "ssh");
663124211Sdes	if (sshpam_err != PAM_SUCCESS) {
664124211Sdes		pam_end(sshpam_handle, sshpam_err);
665124211Sdes		sshpam_handle = NULL;
666124211Sdes		return (-1);
667124211Sdes	}
66898941Sdes#endif
669124211Sdes	return (0);
67069591Sgreen}
67169591Sgreen
672124211Sdesstatic void *
673124211Sdessshpam_init_ctx(Authctxt *authctxt)
67469591Sgreen{
675124211Sdes	struct pam_ctxt *ctxt;
676124211Sdes	int socks[2];
67769591Sgreen
678126277Sdes	debug3("PAM: %s entering", __func__);
679162856Sdes	/*
680162856Sdes	 * Refuse to start if we don't have PAM enabled or do_pam_account
681162856Sdes	 * has previously failed.
682162856Sdes	 */
683162856Sdes	if (!options.use_pam || sshpam_account_status == 0)
684124211Sdes		return NULL;
68576394Salfred
686124211Sdes	/* Initialize PAM */
687128460Sdes	if (sshpam_init(authctxt) == -1) {
688124211Sdes		error("PAM: initialization failed");
689124211Sdes		return (NULL);
69069591Sgreen	}
69169591Sgreen
692181111Sdes	ctxt = xcalloc(1, sizeof *ctxt);
69376394Salfred
694124211Sdes	/* Start the authentication thread */
695124211Sdes	if (socketpair(AF_UNIX, SOCK_STREAM, PF_UNSPEC, socks) == -1) {
696124211Sdes		error("PAM: failed create sockets: %s", strerror(errno));
697255767Sdes		free(ctxt);
698124211Sdes		return (NULL);
699124211Sdes	}
700124211Sdes	ctxt->pam_psock = socks[0];
701124211Sdes	ctxt->pam_csock = socks[1];
702124211Sdes	if (pthread_create(&ctxt->pam_thread, NULL, sshpam_thread, ctxt) == -1) {
703124211Sdes		error("PAM: failed to start authentication thread: %s",
704124211Sdes		    strerror(errno));
705124211Sdes		close(socks[0]);
706124211Sdes		close(socks[1]);
707255767Sdes		free(ctxt);
708124211Sdes		return (NULL);
709124211Sdes	}
710126277Sdes	cleanup_ctxt = ctxt;
711124211Sdes	return (ctxt);
71269591Sgreen}
71369591Sgreen
714124211Sdesstatic int
715124211Sdessshpam_query(void *ctx, char **name, char **info,
716124211Sdes    u_int *num, char ***prompts, u_int **echo_on)
71769591Sgreen{
718323129Sdes	struct ssh *ssh = active_state; /* XXX */
719124211Sdes	Buffer buffer;
720124211Sdes	struct pam_ctxt *ctxt = ctx;
721124211Sdes	size_t plen;
722124211Sdes	u_char type;
723124211Sdes	char *msg;
724147005Sdes	size_t len, mlen;
72576394Salfred
726126277Sdes	debug3("PAM: %s entering", __func__);
727124211Sdes	buffer_init(&buffer);
728124211Sdes	*name = xstrdup("");
729124211Sdes	*info = xstrdup("");
730124211Sdes	*prompts = xmalloc(sizeof(char *));
731124211Sdes	**prompts = NULL;
732124211Sdes	plen = 0;
733124211Sdes	*echo_on = xmalloc(sizeof(u_int));
734124211Sdes	while (ssh_msg_recv(ctxt->pam_psock, &buffer) == 0) {
735124211Sdes		type = buffer_get_char(&buffer);
736124211Sdes		msg = buffer_get_string(&buffer, NULL);
737147005Sdes		mlen = strlen(msg);
738124211Sdes		switch (type) {
739124211Sdes		case PAM_PROMPT_ECHO_ON:
740124211Sdes		case PAM_PROMPT_ECHO_OFF:
741124211Sdes			*num = 1;
742147005Sdes			len = plen + mlen + 1;
743294336Sdes			**prompts = xreallocarray(**prompts, 1, len);
744147005Sdes			strlcpy(**prompts + plen, msg, len - plen);
745147005Sdes			plen += mlen;
746124211Sdes			**echo_on = (type == PAM_PROMPT_ECHO_ON);
747255767Sdes			free(msg);
748124211Sdes			return (0);
749124211Sdes		case PAM_ERROR_MSG:
750124211Sdes		case PAM_TEXT_INFO:
751124211Sdes			/* accumulate messages */
752147005Sdes			len = plen + mlen + 2;
753294336Sdes			**prompts = xreallocarray(**prompts, 1, len);
754147005Sdes			strlcpy(**prompts + plen, msg, len - plen);
755147005Sdes			plen += mlen;
756147005Sdes			strlcat(**prompts + plen, "\n", len - plen);
757147005Sdes			plen++;
758255767Sdes			free(msg);
759124211Sdes			break;
760162856Sdes		case PAM_ACCT_EXPIRED:
761323129Sdes		case PAM_MAXTRIES:
762323129Sdes			if (type == PAM_ACCT_EXPIRED)
763323129Sdes				sshpam_account_status = 0;
764323129Sdes			if (type == PAM_MAXTRIES)
765323129Sdes				sshpam_set_maxtries_reached(1);
766162856Sdes			/* FALLTHROUGH */
767157019Sdes		case PAM_AUTH_ERR:
768162856Sdes			debug3("PAM: %s", pam_strerror(sshpam_handle, type));
769157019Sdes			if (**prompts != NULL && strlen(**prompts) != 0) {
770157019Sdes				*info = **prompts;
771157019Sdes				**prompts = NULL;
772157019Sdes				*num = 0;
773157019Sdes				**echo_on = 0;
774157019Sdes				ctxt->pam_done = -1;
775255767Sdes				free(msg);
776157019Sdes				return 0;
777157019Sdes			}
778157019Sdes			/* FALLTHROUGH */
779124211Sdes		case PAM_SUCCESS:
780124211Sdes			if (**prompts != NULL) {
781124211Sdes				/* drain any accumulated messages */
782126277Sdes				debug("PAM: %s", **prompts);
783126277Sdes				buffer_append(&loginmsg, **prompts,
784126277Sdes				    strlen(**prompts));
785255767Sdes				free(**prompts);
786124211Sdes				**prompts = NULL;
787124211Sdes			}
788124211Sdes			if (type == PAM_SUCCESS) {
789147005Sdes				if (!sshpam_authctxt->valid ||
790147005Sdes				    (sshpam_authctxt->pw->pw_uid == 0 &&
791147005Sdes				    options.permit_root_login != PERMIT_YES))
792147005Sdes					fatal("Internal error: PAM auth "
793147005Sdes					    "succeeded when it should have "
794147005Sdes					    "failed");
795126277Sdes				import_environments(&buffer);
796124211Sdes				*num = 0;
797124211Sdes				**echo_on = 0;
798124211Sdes				ctxt->pam_done = 1;
799255767Sdes				free(msg);
800124211Sdes				return (0);
801124211Sdes			}
802318402Slidl			BLACKLIST_NOTIFY(BLACKLIST_BAD_USER,
803318402Slidl			    sshpam_authctxt->user);
804128460Sdes			error("PAM: %s for %s%.100s from %.100s", msg,
805128460Sdes			    sshpam_authctxt->valid ? "" : "illegal user ",
806128460Sdes			    sshpam_authctxt->user,
807323129Sdes			    auth_get_canonical_hostname(ssh, options.use_dns));
808126277Sdes			/* FALLTHROUGH */
809124211Sdes		default:
810124211Sdes			*num = 0;
811124211Sdes			**echo_on = 0;
812255767Sdes			free(msg);
813124211Sdes			ctxt->pam_done = -1;
814124211Sdes			return (-1);
815124211Sdes		}
816124211Sdes	}
817124211Sdes	return (-1);
818124211Sdes}
81998941Sdes
820323129Sdes/*
821323129Sdes * Returns a junk password of identical length to that the user supplied.
822323129Sdes * Used to mitigate timing attacks against crypt(3)/PAM stacks that
823323129Sdes * vary processing time in proportion to password length.
824323129Sdes */
825323129Sdesstatic char *
826323129Sdesfake_password(const char *wire_password)
827323129Sdes{
828323129Sdes	const char junk[] = "\b\n\r\177INCORRECT";
829323129Sdes	char *ret = NULL;
830323129Sdes	size_t i, l = wire_password != NULL ? strlen(wire_password) : 0;
831323129Sdes
832323129Sdes	if (l >= INT_MAX)
833323129Sdes		fatal("%s: password length too long: %zu", __func__, l);
834323129Sdes
835323129Sdes	ret = malloc(l + 1);
836323136Sdes	if (ret == NULL)
837323136Sdes		return NULL;
838323129Sdes	for (i = 0; i < l; i++)
839323129Sdes		ret[i] = junk[i % (sizeof(junk) - 1)];
840323129Sdes	ret[i] = '\0';
841323129Sdes	return ret;
842323129Sdes}
843323129Sdes
844124211Sdes/* XXX - see also comment in auth-chall.c:verify_response */
845124211Sdesstatic int
846124211Sdessshpam_respond(void *ctx, u_int num, char **resp)
847124211Sdes{
848124211Sdes	Buffer buffer;
849124211Sdes	struct pam_ctxt *ctxt = ctx;
850323129Sdes	char *fake;
85198941Sdes
852157019Sdes	debug2("PAM: %s entering, %u responses", __func__, num);
853124211Sdes	switch (ctxt->pam_done) {
854124211Sdes	case 1:
855124211Sdes		sshpam_authenticated = 1;
856124211Sdes		return (0);
857124211Sdes	case 0:
858124211Sdes		break;
859124211Sdes	default:
860124211Sdes		return (-1);
861124211Sdes	}
862124211Sdes	if (num != 1) {
863124211Sdes		error("PAM: expected one response, got %u", num);
864124211Sdes		return (-1);
865124211Sdes	}
866124211Sdes	buffer_init(&buffer);
867147005Sdes	if (sshpam_authctxt->valid &&
868147005Sdes	    (sshpam_authctxt->pw->pw_uid != 0 ||
869149753Sdes	    options.permit_root_login == PERMIT_YES))
870147005Sdes		buffer_put_cstring(&buffer, *resp);
871323129Sdes	else {
872323129Sdes		fake = fake_password(*resp);
873323129Sdes		buffer_put_cstring(&buffer, fake);
874323129Sdes		free(fake);
875323129Sdes	}
876126277Sdes	if (ssh_msg_send(ctxt->pam_psock, PAM_AUTHTOK, &buffer) == -1) {
877126277Sdes		buffer_free(&buffer);
878126277Sdes		return (-1);
879126277Sdes	}
880124211Sdes	buffer_free(&buffer);
881124211Sdes	return (1);
88269591Sgreen}
88369591Sgreen
884124211Sdesstatic void
885124211Sdessshpam_free_ctx(void *ctxtp)
88669591Sgreen{
887124211Sdes	struct pam_ctxt *ctxt = ctxtp;
888124211Sdes
889126277Sdes	debug3("PAM: %s entering", __func__);
890126277Sdes	sshpam_thread_cleanup();
891255767Sdes	free(ctxt);
892124211Sdes	/*
893124211Sdes	 * We don't call sshpam_cleanup() here because we may need the PAM
894124211Sdes	 * handle at a later stage, e.g. when setting up a session.  It's
895124211Sdes	 * still on the cleanup list, so pam_end() *will* be called before
896124211Sdes	 * the server process terminates.
897124211Sdes	 */
89869591Sgreen}
89969591Sgreen
900124211SdesKbdintDevice sshpam_device = {
901124211Sdes	"pam",
902124211Sdes	sshpam_init_ctx,
903124211Sdes	sshpam_query,
904124211Sdes	sshpam_respond,
905124211Sdes	sshpam_free_ctx
906124211Sdes};
907124211Sdes
908124211SdesKbdintDevice mm_sshpam_device = {
909124211Sdes	"pam",
910124211Sdes	mm_sshpam_init_ctx,
911124211Sdes	mm_sshpam_query,
912124211Sdes	mm_sshpam_respond,
913124211Sdes	mm_sshpam_free_ctx
914124211Sdes};
915124211Sdes
91698941Sdes/*
917124211Sdes * This replaces auth-pam.c
91869591Sgreen */
919124211Sdesvoid
920128460Sdesstart_pam(Authctxt *authctxt)
92169591Sgreen{
922124211Sdes	if (!options.use_pam)
923124211Sdes		fatal("PAM: initialisation requested when UsePAM=no");
92469591Sgreen
925128460Sdes	if (sshpam_init(authctxt) == -1)
926124211Sdes		fatal("PAM: initialisation failed");
92769591Sgreen}
92869591Sgreen
929124211Sdesvoid
930124211Sdesfinish_pam(void)
93169591Sgreen{
932126277Sdes	sshpam_cleanup();
93369591Sgreen}
93469591Sgreen
935124211Sdesu_int
936124211Sdesdo_pam_account(void)
93769591Sgreen{
938147005Sdes	debug("%s: called", __func__);
939126277Sdes	if (sshpam_account_status != -1)
940126277Sdes		return (sshpam_account_status);
941126277Sdes
942124211Sdes	sshpam_err = pam_acct_mgmt(sshpam_handle, 0);
943147005Sdes	debug3("PAM: %s pam_acct_mgmt = %d (%s)", __func__, sshpam_err,
944147005Sdes	    pam_strerror(sshpam_handle, sshpam_err));
945149753Sdes
946126277Sdes	if (sshpam_err != PAM_SUCCESS && sshpam_err != PAM_NEW_AUTHTOK_REQD) {
947126277Sdes		sshpam_account_status = 0;
948126277Sdes		return (sshpam_account_status);
949124211Sdes	}
95069591Sgreen
951126277Sdes	if (sshpam_err == PAM_NEW_AUTHTOK_REQD)
952137019Sdes		sshpam_password_change_required(1);
95369591Sgreen
954126277Sdes	sshpam_account_status = 1;
955126277Sdes	return (sshpam_account_status);
956124211Sdes}
95798941Sdes
958124211Sdesvoid
959124211Sdesdo_pam_setcred(int init)
960124211Sdes{
961124211Sdes	sshpam_err = pam_set_item(sshpam_handle, PAM_CONV,
962147005Sdes	    (const void *)&store_conv);
963124211Sdes	if (sshpam_err != PAM_SUCCESS)
964124211Sdes		fatal("PAM: failed to set PAM_CONV: %s",
965124211Sdes		    pam_strerror(sshpam_handle, sshpam_err));
966124211Sdes	if (init) {
967124211Sdes		debug("PAM: establishing credentials");
968124211Sdes		sshpam_err = pam_setcred(sshpam_handle, PAM_ESTABLISH_CRED);
969124211Sdes	} else {
970124211Sdes		debug("PAM: reinitializing credentials");
971124211Sdes		sshpam_err = pam_setcred(sshpam_handle, PAM_REINITIALIZE_CRED);
972124211Sdes	}
973124211Sdes	if (sshpam_err == PAM_SUCCESS) {
974124211Sdes		sshpam_cred_established = 1;
975124211Sdes		return;
976124211Sdes	}
977124211Sdes	if (sshpam_authenticated)
978124211Sdes		fatal("PAM: pam_setcred(): %s",
979124211Sdes		    pam_strerror(sshpam_handle, sshpam_err));
980124211Sdes	else
981124211Sdes		debug("PAM: pam_setcred(): %s",
982124211Sdes		    pam_strerror(sshpam_handle, sshpam_err));
98369591Sgreen}
98469591Sgreen
985124211Sdesstatic int
986149753Sdessshpam_tty_conv(int n, sshpam_const struct pam_message **msg,
987124211Sdes    struct pam_response **resp, void *data)
988106130Sdes{
989124211Sdes	char input[PAM_MAX_MSG_SIZE];
990124211Sdes	struct pam_response *reply;
991106130Sdes	int i;
992106130Sdes
993126277Sdes	debug3("PAM: %s called with %d messages", __func__, n);
994126277Sdes
995124211Sdes	*resp = NULL;
996124211Sdes
997126277Sdes	if (n <= 0 || n > PAM_MAX_NUM_MSG || !isatty(STDIN_FILENO))
998124211Sdes		return (PAM_CONV_ERR);
999124211Sdes
1000162856Sdes	if ((reply = calloc(n, sizeof(*reply))) == NULL)
1001124211Sdes		return (PAM_CONV_ERR);
1002124211Sdes
1003124211Sdes	for (i = 0; i < n; ++i) {
1004124211Sdes		switch (PAM_MSG_MEMBER(msg, i, msg_style)) {
1005124211Sdes		case PAM_PROMPT_ECHO_OFF:
1006124211Sdes			reply[i].resp =
1007126277Sdes			    read_passphrase(PAM_MSG_MEMBER(msg, i, msg),
1008124211Sdes			    RP_ALLOW_STDIN);
1009124211Sdes			reply[i].resp_retcode = PAM_SUCCESS;
1010124211Sdes			break;
1011124211Sdes		case PAM_PROMPT_ECHO_ON:
1012126277Sdes			fprintf(stderr, "%s\n", PAM_MSG_MEMBER(msg, i, msg));
1013181111Sdes			if (fgets(input, sizeof input, stdin) == NULL)
1014181111Sdes				input[0] = '\0';
1015137019Sdes			if ((reply[i].resp = strdup(input)) == NULL)
1016137019Sdes				goto fail;
1017124211Sdes			reply[i].resp_retcode = PAM_SUCCESS;
1018124211Sdes			break;
1019124211Sdes		case PAM_ERROR_MSG:
1020124211Sdes		case PAM_TEXT_INFO:
1021126277Sdes			fprintf(stderr, "%s\n", PAM_MSG_MEMBER(msg, i, msg));
1022124211Sdes			reply[i].resp_retcode = PAM_SUCCESS;
1023124211Sdes			break;
1024124211Sdes		default:
1025124211Sdes			goto fail;
1026124211Sdes		}
1027106130Sdes	}
1028124211Sdes	*resp = reply;
1029124211Sdes	return (PAM_SUCCESS);
1030124211Sdes
1031124211Sdes fail:
1032124211Sdes	for(i = 0; i < n; i++) {
1033255767Sdes		free(reply[i].resp);
1034124211Sdes	}
1035255767Sdes	free(reply);
1036124211Sdes	return (PAM_CONV_ERR);
1037106130Sdes}
1038106130Sdes
1039137019Sdesstatic struct pam_conv tty_conv = { sshpam_tty_conv, NULL };
1040126277Sdes
1041124211Sdes/*
1042124211Sdes * XXX this should be done in the authentication phase, but ssh1 doesn't
1043124211Sdes * support that
1044124211Sdes */
1045124211Sdesvoid
1046124211Sdesdo_pam_chauthtok(void)
104769591Sgreen{
1048124211Sdes	if (use_privsep)
1049124211Sdes		fatal("Password expired (unable to change with privsep)");
1050124211Sdes	sshpam_err = pam_set_item(sshpam_handle, PAM_CONV,
1051126277Sdes	    (const void *)&tty_conv);
1052124211Sdes	if (sshpam_err != PAM_SUCCESS)
1053124211Sdes		fatal("PAM: failed to set PAM_CONV: %s",
1054124211Sdes		    pam_strerror(sshpam_handle, sshpam_err));
1055124211Sdes	debug("PAM: changing password");
1056124211Sdes	sshpam_err = pam_chauthtok(sshpam_handle, PAM_CHANGE_EXPIRED_AUTHTOK);
1057124211Sdes	if (sshpam_err != PAM_SUCCESS)
1058124211Sdes		fatal("PAM: pam_chauthtok(): %s",
1059124211Sdes		    pam_strerror(sshpam_handle, sshpam_err));
106069591Sgreen}
106169591Sgreen
1062126277Sdesvoid
1063126277Sdesdo_pam_session(void)
1064126277Sdes{
1065126277Sdes	debug3("PAM: opening session");
1066126277Sdes	sshpam_err = pam_set_item(sshpam_handle, PAM_CONV,
1067126277Sdes	    (const void *)&store_conv);
1068126277Sdes	if (sshpam_err != PAM_SUCCESS)
1069126277Sdes		fatal("PAM: failed to set PAM_CONV: %s",
1070126277Sdes		    pam_strerror(sshpam_handle, sshpam_err));
1071126277Sdes	sshpam_err = pam_open_session(sshpam_handle, 0);
1072147005Sdes	if (sshpam_err == PAM_SUCCESS)
1073147005Sdes		sshpam_session_open = 1;
1074147005Sdes	else {
1075147005Sdes		sshpam_session_open = 0;
1076147005Sdes		disable_forwarding();
1077147005Sdes		error("PAM: pam_open_session(): %s",
1078126277Sdes		    pam_strerror(sshpam_handle, sshpam_err));
1079147005Sdes	}
1080147005Sdes
1081126277Sdes}
1082126277Sdes
1083147005Sdesint
1084147005Sdesis_pam_session_open(void)
1085147005Sdes{
1086147005Sdes	return sshpam_session_open;
1087147005Sdes}
1088147005Sdes
1089126277Sdes/*
1090124211Sdes * Set a PAM environment string. We need to do this so that the session
1091124211Sdes * modules can handle things like Kerberos/GSI credentials that appear
1092124211Sdes * during the ssh authentication process.
1093124211Sdes */
1094124211Sdesint
1095126277Sdesdo_pam_putenv(char *name, char *value)
109669591Sgreen{
1097124211Sdes	int ret = 1;
1098126277Sdes#ifdef HAVE_PAM_PUTENV
1099124211Sdes	char *compound;
1100124211Sdes	size_t len;
110169591Sgreen
1102124211Sdes	len = strlen(name) + strlen(value) + 2;
1103124211Sdes	compound = xmalloc(len);
110469591Sgreen
1105124211Sdes	snprintf(compound, len, "%s=%s", name, value);
1106124211Sdes	ret = pam_putenv(sshpam_handle, compound);
1107255767Sdes	free(compound);
1108124211Sdes#endif
110969591Sgreen
1110124211Sdes	return (ret);
1111124211Sdes}
111269591Sgreen
1113126277Sdeschar **
1114126277Sdesfetch_pam_child_environment(void)
1115124211Sdes{
1116126277Sdes	return sshpam_env;
111769591Sgreen}
111869591Sgreen
1119124211Sdeschar **
1120124211Sdesfetch_pam_environment(void)
1121124211Sdes{
1122124211Sdes	return (pam_getenvlist(sshpam_handle));
1123124211Sdes}
1124124211Sdes
1125124211Sdesvoid
1126124211Sdesfree_pam_environment(char **env)
1127124211Sdes{
1128124211Sdes	char **envp;
1129124211Sdes
1130124211Sdes	if (env == NULL)
1131124211Sdes		return;
1132124211Sdes
1133124211Sdes	for (envp = env; *envp; envp++)
1134255767Sdes		free(*envp);
1135255767Sdes	free(env);
1136124211Sdes}
1137124211Sdes
1138137019Sdes/*
1139137019Sdes * "Blind" conversation function for password authentication.  Assumes that
1140137019Sdes * echo-off prompts are for the password and stores messages for later
1141137019Sdes * display.
1142137019Sdes */
1143137019Sdesstatic int
1144149753Sdessshpam_passwd_conv(int n, sshpam_const struct pam_message **msg,
1145137019Sdes    struct pam_response **resp, void *data)
1146137019Sdes{
1147137019Sdes	struct pam_response *reply;
1148137019Sdes	int i;
1149137019Sdes	size_t len;
1150137019Sdes
1151137019Sdes	debug3("PAM: %s called with %d messages", __func__, n);
1152137019Sdes
1153137019Sdes	*resp = NULL;
1154137019Sdes
1155137019Sdes	if (n <= 0 || n > PAM_MAX_NUM_MSG)
1156137019Sdes		return (PAM_CONV_ERR);
1157137019Sdes
1158181111Sdes	if ((reply = calloc(n, sizeof(*reply))) == NULL)
1159137019Sdes		return (PAM_CONV_ERR);
1160137019Sdes
1161137019Sdes	for (i = 0; i < n; ++i) {
1162137019Sdes		switch (PAM_MSG_MEMBER(msg, i, msg_style)) {
1163137019Sdes		case PAM_PROMPT_ECHO_OFF:
1164137019Sdes			if (sshpam_password == NULL)
1165137019Sdes				goto fail;
1166137019Sdes			if ((reply[i].resp = strdup(sshpam_password)) == NULL)
1167137019Sdes				goto fail;
1168137019Sdes			reply[i].resp_retcode = PAM_SUCCESS;
1169137019Sdes			break;
1170137019Sdes		case PAM_ERROR_MSG:
1171137019Sdes		case PAM_TEXT_INFO:
1172137019Sdes			len = strlen(PAM_MSG_MEMBER(msg, i, msg));
1173137019Sdes			if (len > 0) {
1174137019Sdes				buffer_append(&loginmsg,
1175137019Sdes				    PAM_MSG_MEMBER(msg, i, msg), len);
1176137019Sdes				buffer_append(&loginmsg, "\n", 1);
1177137019Sdes			}
1178137019Sdes			if ((reply[i].resp = strdup("")) == NULL)
1179137019Sdes				goto fail;
1180137019Sdes			reply[i].resp_retcode = PAM_SUCCESS;
1181137019Sdes			break;
1182137019Sdes		default:
1183137019Sdes			goto fail;
1184137019Sdes		}
1185137019Sdes	}
1186137019Sdes	*resp = reply;
1187137019Sdes	return (PAM_SUCCESS);
1188137019Sdes
1189149753Sdes fail:
1190137019Sdes	for(i = 0; i < n; i++) {
1191255767Sdes		free(reply[i].resp);
1192137019Sdes	}
1193255767Sdes	free(reply);
1194137019Sdes	return (PAM_CONV_ERR);
1195137019Sdes}
1196137019Sdes
1197137019Sdesstatic struct pam_conv passwd_conv = { sshpam_passwd_conv, NULL };
1198137019Sdes
1199137019Sdes/*
1200137019Sdes * Attempt password authentication via PAM
1201137019Sdes */
1202137019Sdesint
1203137019Sdessshpam_auth_passwd(Authctxt *authctxt, const char *password)
1204137019Sdes{
1205137019Sdes	int flags = (options.permit_empty_passwd == 0 ?
1206137019Sdes	    PAM_DISALLOW_NULL_AUTHTOK : 0);
1207323129Sdes	char *fake = NULL;
1208137019Sdes
1209137019Sdes	if (!options.use_pam || sshpam_handle == NULL)
1210137019Sdes		fatal("PAM: %s called when PAM disabled or failed to "
1211137019Sdes		    "initialise.", __func__);
1212137019Sdes
1213137019Sdes	sshpam_password = password;
1214137019Sdes	sshpam_authctxt = authctxt;
1215137019Sdes
1216137019Sdes	/*
1217137019Sdes	 * If the user logging in is invalid, or is root but is not permitted
1218137019Sdes	 * by PermitRootLogin, use an invalid password to prevent leaking
1219137019Sdes	 * information via timing (eg if the PAM config has a delay on fail).
1220137019Sdes	 */
1221137019Sdes	if (!authctxt->valid || (authctxt->pw->pw_uid == 0 &&
1222149753Sdes	    options.permit_root_login != PERMIT_YES))
1223323129Sdes		sshpam_password = fake = fake_password(password);
1224137019Sdes
1225137019Sdes	sshpam_err = pam_set_item(sshpam_handle, PAM_CONV,
1226137019Sdes	    (const void *)&passwd_conv);
1227137019Sdes	if (sshpam_err != PAM_SUCCESS)
1228137019Sdes		fatal("PAM: %s: failed to set PAM_CONV: %s", __func__,
1229137019Sdes		    pam_strerror(sshpam_handle, sshpam_err));
1230137019Sdes
1231137019Sdes	sshpam_err = pam_authenticate(sshpam_handle, flags);
1232137019Sdes	sshpam_password = NULL;
1233323129Sdes	free(fake);
1234323129Sdes	if (sshpam_err == PAM_MAXTRIES)
1235323129Sdes		sshpam_set_maxtries_reached(1);
1236137019Sdes	if (sshpam_err == PAM_SUCCESS && authctxt->valid) {
1237137019Sdes		debug("PAM: password authentication accepted for %.100s",
1238137019Sdes		    authctxt->user);
1239149753Sdes		return 1;
1240137019Sdes	} else {
1241137019Sdes		debug("PAM: password authentication failed for %.100s: %s",
1242137019Sdes		    authctxt->valid ? authctxt->user : "an illegal user",
1243137019Sdes		    pam_strerror(sshpam_handle, sshpam_err));
1244137019Sdes		return 0;
1245137019Sdes	}
1246137019Sdes}
1247323129Sdes
1248323129Sdesint
1249323129Sdessshpam_get_maxtries_reached(void)
1250323129Sdes{
1251323129Sdes	return sshpam_maxtries_reached;
1252323129Sdes}
1253323129Sdes
1254323129Sdesvoid
1255323129Sdessshpam_set_maxtries_reached(int reached)
1256323129Sdes{
1257323129Sdes	if (reached == 0 || sshpam_maxtries_reached)
1258323129Sdes		return;
1259323129Sdes	sshpam_maxtries_reached = 1;
1260323129Sdes	options.password_authentication = 0;
1261323129Sdes	options.kbd_interactive_authentication = 0;
1262323129Sdes	options.challenge_response_authentication = 0;
1263323129Sdes}
126469591Sgreen#endif /* USE_PAM */
1265