1313c6c94Schristos /*-
2313c6c94Schristos  * Copyright (c) 2002 Networks Associates Technology, Inc.
3313c6c94Schristos  * All rights reserved.
4313c6c94Schristos  *
5313c6c94Schristos  * This software was developed for the FreeBSD Project by ThinkSec AS and
6313c6c94Schristos  * NAI Labs, the Security Research Division of Network Associates, Inc.
7313c6c94Schristos  * under DARPA/SPAWAR contract N66001-01-C-8035 ("CBOSS"), as part of the
8313c6c94Schristos  * DARPA CHATS research program.
9313c6c94Schristos  *
10313c6c94Schristos  * Redistribution and use in source and binary forms, with or without
11313c6c94Schristos  * modification, are permitted provided that the following conditions
12313c6c94Schristos  * are met:
13313c6c94Schristos  * 1. Redistributions of source code must retain the above copyright
14313c6c94Schristos  *    notice, this list of conditions and the following disclaimer.
15313c6c94Schristos  * 2. Redistributions in binary form must reproduce the above copyright
16313c6c94Schristos  *    notice, this list of conditions and the following disclaimer in the
17313c6c94Schristos  *    documentation and/or other materials provided with the distribution.
18313c6c94Schristos  *
19313c6c94Schristos  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
20313c6c94Schristos  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21313c6c94Schristos  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
22313c6c94Schristos  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
23313c6c94Schristos  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
24313c6c94Schristos  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
25313c6c94Schristos  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
26313c6c94Schristos  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
27313c6c94Schristos  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
28313c6c94Schristos  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
29313c6c94Schristos  * SUCH DAMAGE.
30313c6c94Schristos  */
31313c6c94Schristos /*
32313c6c94Schristos  * Copyright (c) 2003,2004 Damien Miller <djm@mindrot.org>
33313c6c94Schristos  * Copyright (c) 2003,2004 Darren Tucker <dtucker@zip.com.au>
34313c6c94Schristos  *
35313c6c94Schristos  * Permission to use, copy, modify, and distribute this software for any
36313c6c94Schristos  * purpose with or without fee is hereby granted, provided that the above
37313c6c94Schristos  * copyright notice and this permission notice appear in all copies.
38313c6c94Schristos  *
39313c6c94Schristos  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
40313c6c94Schristos  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
41313c6c94Schristos  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
42313c6c94Schristos  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
43313c6c94Schristos  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
44313c6c94Schristos  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
45313c6c94Schristos  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
46313c6c94Schristos  */
47313c6c94Schristos 
480357011aSchristos /* Based on FreeBSD: src/crypto/openssh/auth2-pam-freebsd.c,v 1.11 2003/03/31 13:48:18 des */
490357011aSchristos 
50313c6c94Schristos #include "includes.h"
51313c6c94Schristos /*
52313c6c94Schristos  * NetBSD local changes
53313c6c94Schristos  */
54*19a1dfedSchristos __RCSID("$NetBSD: auth-pam.c,v 1.20 2021/09/02 11:26:17 christos Exp $");
550357011aSchristos #define _LIB_PTHREAD_H
56313c6c94Schristos #undef USE_POSIX_THREADS /* Not yet */
57313c6c94Schristos #define HAVE_SECURITY_PAM_APPL_H
58313c6c94Schristos #define HAVE_PAM_GETENVLIST
59313c6c94Schristos #define HAVE_PAM_PUTENV
60313c6c94Schristos #define sshpam_const	const	/* LinuxPAM, OpenPAM */
61313c6c94Schristos #define PAM_MSG_MEMBER(msg, n, member) ((*(msg))[(n)].member)
62313c6c94Schristos #define mysig_t sig_t
63313c6c94Schristos void sshpam_password_change_required(int);
64e1a26310Schristos #define SSHD_PAM_SERVICE               getprogname()
65313c6c94Schristos /* end NetBSD local changes */
66313c6c94Schristos 
67313c6c94Schristos #include <sys/types.h>
68c152af9fSchristos #include <sys/socket.h>
69313c6c94Schristos #include <sys/stat.h>
70313c6c94Schristos #include <sys/wait.h>
71313c6c94Schristos 
72313c6c94Schristos #include <errno.h>
73313c6c94Schristos #include <signal.h>
74313c6c94Schristos #include <stdarg.h>
75313c6c94Schristos #include <string.h>
76313c6c94Schristos #include <unistd.h>
77313c6c94Schristos #include <pwd.h>
78313c6c94Schristos 
79313c6c94Schristos #ifdef USE_PAM
80313c6c94Schristos #if defined(HAVE_SECURITY_PAM_APPL_H)
81313c6c94Schristos #include <security/pam_appl.h>
82313c6c94Schristos #elif defined (HAVE_PAM_PAM_APPL_H)
83313c6c94Schristos #include <pam/pam_appl.h>
84313c6c94Schristos #endif
85313c6c94Schristos 
86e1a26310Schristos #if !defined(SSHD_PAM_SERVICE)
87e1a26310Schristos extern char *__progname;
88e1a26310Schristos # define SSHD_PAM_SERVICE		__progname
89e1a26310Schristos #endif
90e1a26310Schristos 
910357011aSchristos #ifndef __NetBSD__
920357011aSchristos /* OpenGroup RFC86.0 and XSSO specify no "const" on arguments */
930357011aSchristos #ifdef PAM_SUN_CODEBASE
940357011aSchristos # define sshpam_const		/* Solaris, HP-UX, SunOS */
950357011aSchristos #else
960357011aSchristos # define sshpam_const	const	/* LinuxPAM, OpenPAM, AIX */
970357011aSchristos #endif
98e1a26310Schristos 
99313c6c94Schristos /* Ambiguity in spec: is it an array of pointers or a pointer to an array? */
100313c6c94Schristos #ifdef PAM_SUN_CODEBASE
101313c6c94Schristos # define PAM_MSG_MEMBER(msg, n, member) ((*(msg))[(n)].member)
102313c6c94Schristos #else
103313c6c94Schristos # define PAM_MSG_MEMBER(msg, n, member) ((msg)[(n)]->member)
104313c6c94Schristos #endif
105313c6c94Schristos #endif
106313c6c94Schristos 
107313c6c94Schristos #include "xmalloc.h"
1080357011aSchristos #include "sshbuf.h"
1090357011aSchristos #include "ssherr.h"
110313c6c94Schristos #include "hostfile.h"
111313c6c94Schristos #include "auth.h"
112313c6c94Schristos #include "auth-pam.h"
113313c6c94Schristos #include "canohost.h"
114313c6c94Schristos #include "log.h"
115313c6c94Schristos #include "msg.h"
116313c6c94Schristos #include "packet.h"
117313c6c94Schristos #include "misc.h"
118313c6c94Schristos #include "servconf.h"
119313c6c94Schristos #include "ssh2.h"
120313c6c94Schristos #include "auth-options.h"
121313c6c94Schristos #ifdef GSSAPI
122313c6c94Schristos #include "ssh-gss.h"
123313c6c94Schristos #endif
124313c6c94Schristos #include "monitor_wrap.h"
125313c6c94Schristos 
126313c6c94Schristos extern ServerOptions options;
1270357011aSchristos extern struct sshbuf *loginmsg;
128313c6c94Schristos extern u_int utmp_len;
129313c6c94Schristos 
130313c6c94Schristos /* so we don't silently change behaviour */
131313c6c94Schristos #ifdef USE_POSIX_THREADS
132313c6c94Schristos # error "USE_POSIX_THREADS replaced by UNSUPPORTED_POSIX_THREADS_HACK"
133313c6c94Schristos #endif
134313c6c94Schristos 
135313c6c94Schristos /*
136313c6c94Schristos  * Formerly known as USE_POSIX_THREADS, using this is completely unsupported
137313c6c94Schristos  * and generally a bad idea.  Use at own risk and do not expect support if
138313c6c94Schristos  * this breaks.
139313c6c94Schristos  */
140313c6c94Schristos #ifdef UNSUPPORTED_POSIX_THREADS_HACK
1410357011aSchristos #error "foo"
142313c6c94Schristos #include <pthread.h>
143313c6c94Schristos /*
144313c6c94Schristos  * Avoid namespace clash when *not* using pthreads for systems *with*
145313c6c94Schristos  * pthreads, which unconditionally define pthread_t via sys/types.h
146313c6c94Schristos  * (e.g. Linux)
147313c6c94Schristos  */
148313c6c94Schristos typedef pthread_t sp_pthread_t;
149313c6c94Schristos #else
150313c6c94Schristos typedef pid_t sp_pthread_t;
151313c6c94Schristos #endif
152313c6c94Schristos 
153313c6c94Schristos struct pam_ctxt {
154313c6c94Schristos 	sp_pthread_t	 pam_thread;
155313c6c94Schristos 	int		 pam_psock;
156313c6c94Schristos 	int		 pam_csock;
157313c6c94Schristos 	int		 pam_done;
158313c6c94Schristos };
159313c6c94Schristos 
160313c6c94Schristos static void sshpam_free_ctx(void *);
161313c6c94Schristos static struct pam_ctxt *cleanup_ctxt;
162313c6c94Schristos 
163313c6c94Schristos #ifndef UNSUPPORTED_POSIX_THREADS_HACK
164313c6c94Schristos /*
165313c6c94Schristos  * Simulate threads with processes.
166313c6c94Schristos  */
167313c6c94Schristos 
168313c6c94Schristos static int sshpam_thread_status = -1;
169313c6c94Schristos static mysig_t sshpam_oldsig;
170313c6c94Schristos 
171313c6c94Schristos static void
sshpam_sigchld_handler(int sig)172313c6c94Schristos sshpam_sigchld_handler(int sig)
173313c6c94Schristos {
174313c6c94Schristos 	signal(SIGCHLD, SIG_DFL);
175313c6c94Schristos 	if (cleanup_ctxt == NULL)
176313c6c94Schristos 		return;	/* handler called after PAM cleanup, shouldn't happen */
177313c6c94Schristos 	if (waitpid(cleanup_ctxt->pam_thread, &sshpam_thread_status, WNOHANG)
178313c6c94Schristos 	    <= 0) {
179313c6c94Schristos 		/* PAM thread has not exitted, privsep slave must have */
180313c6c94Schristos 		kill(cleanup_ctxt->pam_thread, SIGTERM);
181e1a26310Schristos 		while (waitpid(cleanup_ctxt->pam_thread,
182e1a26310Schristos 		    &sshpam_thread_status, 0) == -1) {
183e1a26310Schristos 			if (errno == EINTR)
184e1a26310Schristos 				continue;
185e1a26310Schristos 			return;
186e1a26310Schristos 		}
187313c6c94Schristos 	}
188313c6c94Schristos 	if (WIFSIGNALED(sshpam_thread_status) &&
189313c6c94Schristos 	    WTERMSIG(sshpam_thread_status) == SIGTERM)
190313c6c94Schristos 		return;	/* terminated by pthread_cancel */
191313c6c94Schristos 	if (!WIFEXITED(sshpam_thread_status))
192313c6c94Schristos 		sigdie("PAM: authentication thread exited unexpectedly");
193313c6c94Schristos 	if (WEXITSTATUS(sshpam_thread_status) != 0)
194313c6c94Schristos 		sigdie("PAM: authentication thread exited uncleanly");
195313c6c94Schristos }
196313c6c94Schristos 
197313c6c94Schristos /* ARGSUSED */
198df9fe6e5Stnn __dead static void
pthread_exit(void * value)199313c6c94Schristos pthread_exit(void *value)
200313c6c94Schristos {
201313c6c94Schristos 	_exit(0);
202313c6c94Schristos }
203313c6c94Schristos 
204313c6c94Schristos /* ARGSUSED */
205313c6c94Schristos static int
pthread_create(sp_pthread_t * thread,const void * attr,void * (* thread_start)(void *),void * arg)206313c6c94Schristos pthread_create(sp_pthread_t *thread, const void *attr,
207313c6c94Schristos     void *(*thread_start)(void *), void *arg)
208313c6c94Schristos {
209313c6c94Schristos 	pid_t pid;
210313c6c94Schristos 	struct pam_ctxt *ctx = arg;
211313c6c94Schristos 
212313c6c94Schristos 	sshpam_thread_status = -1;
213313c6c94Schristos 	switch ((pid = fork())) {
214313c6c94Schristos 	case -1:
215313c6c94Schristos 		error("fork(): %s", strerror(errno));
216313c6c94Schristos 		return (-1);
217313c6c94Schristos 	case 0:
218313c6c94Schristos 		close(ctx->pam_psock);
219313c6c94Schristos 		ctx->pam_psock = -1;
220313c6c94Schristos 		thread_start(arg);
221313c6c94Schristos 		_exit(1);
222313c6c94Schristos 	default:
223313c6c94Schristos 		*thread = pid;
224313c6c94Schristos 		close(ctx->pam_csock);
225313c6c94Schristos 		ctx->pam_csock = -1;
226313c6c94Schristos 		sshpam_oldsig = signal(SIGCHLD, sshpam_sigchld_handler);
227313c6c94Schristos 		return (0);
228313c6c94Schristos 	}
229313c6c94Schristos }
230313c6c94Schristos 
231313c6c94Schristos static int
pthread_cancel(sp_pthread_t thread)232313c6c94Schristos pthread_cancel(sp_pthread_t thread)
233313c6c94Schristos {
234313c6c94Schristos 	signal(SIGCHLD, sshpam_oldsig);
235313c6c94Schristos 	return (kill(thread, SIGTERM));
236313c6c94Schristos }
237313c6c94Schristos 
238313c6c94Schristos /* ARGSUSED */
239313c6c94Schristos static int
pthread_join(sp_pthread_t thread,void ** value)240313c6c94Schristos pthread_join(sp_pthread_t thread, void **value)
241313c6c94Schristos {
242313c6c94Schristos 	int status;
243313c6c94Schristos 
244313c6c94Schristos 	if (sshpam_thread_status != -1)
245313c6c94Schristos 		return (sshpam_thread_status);
246313c6c94Schristos 	signal(SIGCHLD, sshpam_oldsig);
247e1a26310Schristos 	while (waitpid(thread, &status, 0) == -1) {
248e1a26310Schristos 		if (errno == EINTR)
249e1a26310Schristos 			continue;
250e1a26310Schristos 		fatal("%s: waitpid: %s", __func__, strerror(errno));
251e1a26310Schristos 	}
252313c6c94Schristos 	return (status);
253313c6c94Schristos }
254313c6c94Schristos #endif
255313c6c94Schristos 
256313c6c94Schristos 
257313c6c94Schristos static pam_handle_t *sshpam_handle = NULL;
258313c6c94Schristos static int sshpam_err = 0;
259313c6c94Schristos static int sshpam_authenticated = 0;
260313c6c94Schristos static int sshpam_session_open = 0;
261313c6c94Schristos static int sshpam_cred_established = 0;
262313c6c94Schristos static int sshpam_account_status = -1;
263e1a26310Schristos static int sshpam_maxtries_reached = 0;
264313c6c94Schristos static char **sshpam_env = NULL;
265313c6c94Schristos static Authctxt *sshpam_authctxt = NULL;
266313c6c94Schristos static const char *sshpam_password = NULL;
2678f892fcaSchristos static char *sshpam_rhost = NULL;
2688f892fcaSchristos static char *sshpam_laddr = NULL;
2698f892fcaSchristos static char *sshpam_conninfo = NULL;
270313c6c94Schristos 
271313c6c94Schristos /* Some PAM implementations don't implement this */
272313c6c94Schristos #ifndef HAVE_PAM_GETENVLIST
273313c6c94Schristos static char **
pam_getenvlist(pam_handle_t * pamh)274313c6c94Schristos pam_getenvlist(pam_handle_t *pamh)
275313c6c94Schristos {
276313c6c94Schristos 	/*
277313c6c94Schristos 	 * XXX - If necessary, we can still support envrionment passing
278313c6c94Schristos 	 * for platforms without pam_getenvlist by searching for known
279313c6c94Schristos 	 * env vars (e.g. KRB5CCNAME) from the PAM environment.
280313c6c94Schristos 	 */
281313c6c94Schristos 	 return NULL;
282313c6c94Schristos }
283313c6c94Schristos #endif
284313c6c94Schristos 
285313c6c94Schristos /*
286313c6c94Schristos  * Some platforms, notably Solaris, do not enforce password complexity
287313c6c94Schristos  * rules during pam_chauthtok() if the real uid of the calling process
288313c6c94Schristos  * is 0, on the assumption that it's being called by "passwd" run by root.
289313c6c94Schristos  * This wraps pam_chauthtok and sets/restore the real uid so PAM will do
290313c6c94Schristos  * the right thing.
291313c6c94Schristos  */
292313c6c94Schristos #ifdef SSHPAM_CHAUTHTOK_NEEDS_RUID
293313c6c94Schristos static int
sshpam_chauthtok_ruid(pam_handle_t * pamh,int flags)294313c6c94Schristos sshpam_chauthtok_ruid(pam_handle_t *pamh, int flags)
295313c6c94Schristos {
296313c6c94Schristos 	int result;
297313c6c94Schristos 
298313c6c94Schristos 	if (sshpam_authctxt == NULL)
299313c6c94Schristos 		fatal("PAM: sshpam_authctxt not initialized");
300313c6c94Schristos 	if (setreuid(sshpam_authctxt->pw->pw_uid, -1) == -1)
301313c6c94Schristos 		fatal("%s: setreuid failed: %s", __func__, strerror(errno));
302313c6c94Schristos 	result = pam_chauthtok(pamh, flags);
303313c6c94Schristos 	if (setreuid(0, -1) == -1)
304313c6c94Schristos 		fatal("%s: setreuid failed: %s", __func__, strerror(errno));
305313c6c94Schristos 	return result;
306313c6c94Schristos }
307313c6c94Schristos # define pam_chauthtok(a,b)	(sshpam_chauthtok_ruid((a), (b)))
308313c6c94Schristos #endif
309313c6c94Schristos 
310313c6c94Schristos void
sshpam_password_change_required(int reqd)311313c6c94Schristos sshpam_password_change_required(int reqd)
312313c6c94Schristos {
313e1a26310Schristos 	extern struct sshauthopt *auth_opts;
314e1a26310Schristos 	static int saved_port, saved_agent, saved_x11;
315e1a26310Schristos 
316313c6c94Schristos 	debug3("%s %d", __func__, reqd);
317313c6c94Schristos 	if (sshpam_authctxt == NULL)
318313c6c94Schristos 		fatal("%s: PAM authctxt not initialized", __func__);
319313c6c94Schristos 	sshpam_authctxt->force_pwchange = reqd;
320313c6c94Schristos 	if (reqd) {
321e1a26310Schristos 		saved_port = auth_opts->permit_port_forwarding_flag;
322e1a26310Schristos 		saved_agent = auth_opts->permit_agent_forwarding_flag;
323e1a26310Schristos 		saved_x11 = auth_opts->permit_x11_forwarding_flag;
324e1a26310Schristos 		auth_opts->permit_port_forwarding_flag = 0;
325e1a26310Schristos 		auth_opts->permit_agent_forwarding_flag = 0;
326e1a26310Schristos 		auth_opts->permit_x11_forwarding_flag = 0;
327313c6c94Schristos 	} else {
328e1a26310Schristos 		if (saved_port)
329e1a26310Schristos 			auth_opts->permit_port_forwarding_flag = saved_port;
330e1a26310Schristos 		if (saved_agent)
331e1a26310Schristos 			auth_opts->permit_agent_forwarding_flag = saved_agent;
332e1a26310Schristos 		if (saved_x11)
333e1a26310Schristos 			auth_opts->permit_x11_forwarding_flag = saved_x11;
334313c6c94Schristos 	}
335313c6c94Schristos }
336313c6c94Schristos 
337313c6c94Schristos /* Import regular and PAM environment from subprocess */
338313c6c94Schristos static void
import_environments(struct sshbuf * b)3390357011aSchristos import_environments(struct sshbuf *b)
340313c6c94Schristos {
341313c6c94Schristos 	char *env;
3420357011aSchristos 	u_int n, i, num_env;
3430357011aSchristos 	int r;
344313c6c94Schristos 
345313c6c94Schristos 	debug3("PAM: %s entering", __func__);
346313c6c94Schristos 
347313c6c94Schristos #ifndef UNSUPPORTED_POSIX_THREADS_HACK
348313c6c94Schristos 	/* Import variables set by do_pam_account */
3490357011aSchristos 	if ((r = sshbuf_get_u32(b, &n)) != 0)
3500357011aSchristos 		fatal("%s: buffer error: %s", __func__, ssh_err(r));
3510357011aSchristos 	if (n > INT_MAX)
3520357011aSchristos 		fatal("%s: invalid PAM account status %u", __func__, n);
3530357011aSchristos 	sshpam_account_status = (int)n;
3540357011aSchristos 	if ((r = sshbuf_get_u32(b, &n)) != 0)
3550357011aSchristos 		fatal("%s: buffer error: %s", __func__, ssh_err(r));
3560357011aSchristos 	sshpam_password_change_required(n != 0);
357313c6c94Schristos 
358313c6c94Schristos 	/* Import environment from subprocess */
3590357011aSchristos 	if ((r = sshbuf_get_u32(b, &num_env)) != 0)
3600357011aSchristos 		fatal("%s: buffer error: %s", __func__, ssh_err(r));
361313c6c94Schristos 	if (num_env > 1024)
362313c6c94Schristos 		fatal("%s: received %u environment variables, expected <= 1024",
363313c6c94Schristos 		    __func__, num_env);
364313c6c94Schristos 	sshpam_env = xcalloc(num_env + 1, sizeof(*sshpam_env));
365313c6c94Schristos 	debug3("PAM: num env strings %d", num_env);
3660357011aSchristos 	for(i = 0; i < num_env; i++) {
3670357011aSchristos 		if ((r = sshbuf_get_cstring(b, &(sshpam_env[i]), NULL)) != 0)
3680357011aSchristos 			fatal("%s: buffer error: %s", __func__, ssh_err(r));
3690357011aSchristos 	}
370313c6c94Schristos 	sshpam_env[num_env] = NULL;
371313c6c94Schristos 
372313c6c94Schristos 	/* Import PAM environment from subprocess */
3730357011aSchristos 	if ((r = sshbuf_get_u32(b, &num_env)) != 0)
3740357011aSchristos 		fatal("%s: buffer error: %s", __func__, ssh_err(r));
375313c6c94Schristos 	debug("PAM: num PAM env strings %d", num_env);
376313c6c94Schristos 	for (i = 0; i < num_env; i++) {
3770357011aSchristos 		if ((r = sshbuf_get_cstring(b, &env, NULL)) != 0)
3780357011aSchristos 			fatal("%s: buffer error: %s", __func__, ssh_err(r));
379313c6c94Schristos #ifdef HAVE_PAM_PUTENV
380313c6c94Schristos 		/* Errors are not fatal here */
3810357011aSchristos 		if ((r = pam_putenv(sshpam_handle, env)) != PAM_SUCCESS) {
382313c6c94Schristos 			error("PAM: pam_putenv: %s",
3830357011aSchristos 			    pam_strerror(sshpam_handle, r));
384313c6c94Schristos 		}
385313c6c94Schristos #endif
3860357011aSchristos 		/* XXX leak env? */
387313c6c94Schristos 	}
388313c6c94Schristos #endif
389313c6c94Schristos }
390313c6c94Schristos 
391313c6c94Schristos /*
392313c6c94Schristos  * Conversation function for authentication thread.
393313c6c94Schristos  */
394313c6c94Schristos static int
sshpam_thread_conv(int n,sshpam_const struct pam_message ** msg,struct pam_response ** resp,void * data)395313c6c94Schristos sshpam_thread_conv(int n, sshpam_const struct pam_message **msg,
396313c6c94Schristos     struct pam_response **resp, void *data)
397313c6c94Schristos {
3980357011aSchristos 	struct sshbuf *buffer;
399313c6c94Schristos 	struct pam_ctxt *ctxt;
400313c6c94Schristos 	struct pam_response *reply;
4010357011aSchristos 	int r, i;
4020357011aSchristos 	u_char status;
403313c6c94Schristos 
404313c6c94Schristos 	debug3("PAM: %s entering, %d messages", __func__, n);
405313c6c94Schristos 	*resp = NULL;
406313c6c94Schristos 
407313c6c94Schristos 	if (data == NULL) {
408313c6c94Schristos 		error("PAM: conversation function passed a null context");
409313c6c94Schristos 		return (PAM_CONV_ERR);
410313c6c94Schristos 	}
411313c6c94Schristos 	ctxt = data;
412313c6c94Schristos 	if (n <= 0 || n > PAM_MAX_NUM_MSG)
413313c6c94Schristos 		return (PAM_CONV_ERR);
414313c6c94Schristos 
415313c6c94Schristos 	if ((reply = calloc(n, sizeof(*reply))) == NULL)
4160357011aSchristos 		return PAM_CONV_ERR;
4170357011aSchristos 	if ((buffer = sshbuf_new()) == NULL) {
4180357011aSchristos 		free(reply);
4190357011aSchristos 		return PAM_CONV_ERR;
4200357011aSchristos 	}
421313c6c94Schristos 
422313c6c94Schristos 	for (i = 0; i < n; ++i) {
423313c6c94Schristos 		switch (PAM_MSG_MEMBER(msg, i, msg_style)) {
424313c6c94Schristos 		case PAM_PROMPT_ECHO_OFF:
425313c6c94Schristos 		case PAM_PROMPT_ECHO_ON:
4260357011aSchristos 			if ((r = sshbuf_put_cstring(buffer,
4270357011aSchristos 			    PAM_MSG_MEMBER(msg, i, msg))) != 0)
4280357011aSchristos 				fatal("%s: buffer error: %s",
4290357011aSchristos 				    __func__, ssh_err(r));
430313c6c94Schristos 			if (ssh_msg_send(ctxt->pam_csock,
4310357011aSchristos 			    PAM_MSG_MEMBER(msg, i, msg_style), buffer) == -1)
432313c6c94Schristos 				goto fail;
4330357011aSchristos 
4340357011aSchristos 			if (ssh_msg_recv(ctxt->pam_csock, buffer) == -1)
435313c6c94Schristos 				goto fail;
4360357011aSchristos 			if ((r = sshbuf_get_u8(buffer, &status)) != 0)
4370357011aSchristos 				fatal("%s: buffer error: %s",
4380357011aSchristos 				    __func__, ssh_err(r));
4390357011aSchristos 			if (status != PAM_AUTHTOK)
440313c6c94Schristos 				goto fail;
4410357011aSchristos 			if ((r = sshbuf_get_cstring(buffer,
4420357011aSchristos 			    &reply[i].resp, NULL)) != 0)
4430357011aSchristos 				fatal("%s: buffer error: %s",
4440357011aSchristos 				    __func__, ssh_err(r));
445313c6c94Schristos 			break;
446313c6c94Schristos 		case PAM_ERROR_MSG:
447313c6c94Schristos 		case PAM_TEXT_INFO:
4480357011aSchristos 			if ((r = sshbuf_put_cstring(buffer,
4490357011aSchristos 			    PAM_MSG_MEMBER(msg, i, msg))) != 0)
4500357011aSchristos 				fatal("%s: buffer error: %s",
4510357011aSchristos 				    __func__, ssh_err(r));
452313c6c94Schristos 			if (ssh_msg_send(ctxt->pam_csock,
4530357011aSchristos 			    PAM_MSG_MEMBER(msg, i, msg_style), buffer) == -1)
454313c6c94Schristos 				goto fail;
455313c6c94Schristos 			break;
456313c6c94Schristos 		default:
457313c6c94Schristos 			goto fail;
458313c6c94Schristos 		}
4590357011aSchristos 		sshbuf_reset(buffer);
460313c6c94Schristos 	}
4610357011aSchristos 	sshbuf_free(buffer);
462313c6c94Schristos 	*resp = reply;
463313c6c94Schristos 	return (PAM_SUCCESS);
464313c6c94Schristos 
465313c6c94Schristos  fail:
466313c6c94Schristos 	for(i = 0; i < n; i++) {
4674ec0b1fcSchristos 		free(reply[i].resp);
468313c6c94Schristos 	}
4694ec0b1fcSchristos 	free(reply);
4700357011aSchristos 	sshbuf_free(buffer);
471313c6c94Schristos 	return (PAM_CONV_ERR);
472313c6c94Schristos }
473313c6c94Schristos 
474313c6c94Schristos /*
475313c6c94Schristos  * Authentication thread.
476313c6c94Schristos  */
477313c6c94Schristos static void *
sshpam_thread(void * ctxtp)478313c6c94Schristos sshpam_thread(void *ctxtp)
479313c6c94Schristos {
480313c6c94Schristos 	struct pam_ctxt *ctxt = ctxtp;
4810357011aSchristos 	struct sshbuf *buffer = NULL;
482313c6c94Schristos 	struct pam_conv sshpam_conv;
4830357011aSchristos 	int r, flags = (options.permit_empty_passwd == 0 ?
484313c6c94Schristos 	    PAM_DISALLOW_NULL_AUTHTOK : 0);
485313c6c94Schristos #ifndef UNSUPPORTED_POSIX_THREADS_HACK
486313c6c94Schristos 	extern char **environ;
487313c6c94Schristos 	char **env_from_pam;
488313c6c94Schristos 	u_int i;
489313c6c94Schristos 	const char *pam_user;
490313c6c94Schristos 	const char **ptr_pam_user = &pam_user;
491313c6c94Schristos 	char *tz = getenv("TZ");
492313c6c94Schristos 
493e1a26310Schristos 	sshpam_err = pam_get_item(sshpam_handle, PAM_USER,
494313c6c94Schristos 	    (sshpam_const void **)ptr_pam_user);
495e1a26310Schristos 	if (sshpam_err != PAM_SUCCESS)
496e1a26310Schristos 		goto auth_fail;
497313c6c94Schristos 
498313c6c94Schristos 	environ[0] = NULL;
499313c6c94Schristos 	if (tz != NULL)
500313c6c94Schristos 		if (setenv("TZ", tz, 1) == -1)
501313c6c94Schristos 			error("PAM: could not set TZ environment: %s",
502313c6c94Schristos 			    strerror(errno));
503313c6c94Schristos 
504313c6c94Schristos 	if (sshpam_authctxt != NULL) {
505313c6c94Schristos 		setproctitle("%s [pam]",
506313c6c94Schristos 		    sshpam_authctxt->valid ? pam_user : "unknown");
507313c6c94Schristos 	}
508313c6c94Schristos #endif
509313c6c94Schristos 
510313c6c94Schristos 	sshpam_conv.conv = sshpam_thread_conv;
511313c6c94Schristos 	sshpam_conv.appdata_ptr = ctxt;
512313c6c94Schristos 
513313c6c94Schristos 	if (sshpam_authctxt == NULL)
514313c6c94Schristos 		fatal("%s: PAM authctxt not initialized", __func__);
515313c6c94Schristos 
5160357011aSchristos 	if ((buffer = sshbuf_new()) == NULL)
5170357011aSchristos 		fatal("%s: sshbuf_new failed", __func__);
5180357011aSchristos 
519313c6c94Schristos 	sshpam_err = pam_set_item(sshpam_handle, PAM_CONV,
520313c6c94Schristos 	    (const void *)&sshpam_conv);
521313c6c94Schristos 	if (sshpam_err != PAM_SUCCESS)
522313c6c94Schristos 		goto auth_fail;
523313c6c94Schristos 	sshpam_err = pam_authenticate(sshpam_handle, flags);
524e1a26310Schristos 	if (sshpam_err == PAM_MAXTRIES)
525e1a26310Schristos 		sshpam_set_maxtries_reached(1);
526313c6c94Schristos 	if (sshpam_err != PAM_SUCCESS)
527313c6c94Schristos 		goto auth_fail;
528313c6c94Schristos 
529313c6c94Schristos 	if (!do_pam_account()) {
530313c6c94Schristos 		sshpam_err = PAM_ACCT_EXPIRED;
531313c6c94Schristos 		goto auth_fail;
532313c6c94Schristos 	}
533313c6c94Schristos 	if (sshpam_authctxt->force_pwchange) {
534313c6c94Schristos 		sshpam_err = pam_chauthtok(sshpam_handle,
535313c6c94Schristos 		    PAM_CHANGE_EXPIRED_AUTHTOK);
536313c6c94Schristos 		if (sshpam_err != PAM_SUCCESS)
537313c6c94Schristos 			goto auth_fail;
538313c6c94Schristos 		sshpam_password_change_required(0);
539313c6c94Schristos 	}
540313c6c94Schristos 
5410357011aSchristos 	if ((r = sshbuf_put_cstring(buffer, "OK")) != 0)
5420357011aSchristos 		fatal("%s: buffer error: %s", __func__, ssh_err(r));
543313c6c94Schristos 
544313c6c94Schristos #ifndef UNSUPPORTED_POSIX_THREADS_HACK
545313c6c94Schristos 	/* Export variables set by do_pam_account */
5460357011aSchristos 	if ((r = sshbuf_put_u32(buffer, sshpam_account_status)) != 0 ||
5470357011aSchristos 	    (r = sshbuf_put_u32(buffer, sshpam_authctxt->force_pwchange)) != 0)
5480357011aSchristos 		fatal("%s: buffer error: %s", __func__, ssh_err(r));
549313c6c94Schristos 
550313c6c94Schristos 	/* Export any environment strings set in child */
5510357011aSchristos 	for (i = 0; environ[i] != NULL; i++) {
5520357011aSchristos 		/* Count */
5530357011aSchristos 		if (i > INT_MAX)
5540357011aSchristos 			fatal("%s: too many enviornment strings", __func__);
5550357011aSchristos 	}
5560357011aSchristos 	if ((r = sshbuf_put_u32(buffer, i)) != 0)
5570357011aSchristos 		fatal("%s: buffer error: %s", __func__, ssh_err(r));
5580357011aSchristos 	for (i = 0; environ[i] != NULL; i++) {
5590357011aSchristos 		if ((r = sshbuf_put_cstring(buffer, environ[i])) != 0)
5600357011aSchristos 			fatal("%s: buffer error: %s", __func__, ssh_err(r));
5610357011aSchristos 	}
562313c6c94Schristos 	/* Export any environment strings set by PAM in child */
563313c6c94Schristos 	env_from_pam = pam_getenvlist(sshpam_handle);
5640357011aSchristos 	for (i = 0; env_from_pam != NULL && env_from_pam[i] != NULL; i++) {
5650357011aSchristos 		/* Count */
5660357011aSchristos 		if (i > INT_MAX)
5670357011aSchristos 			fatal("%s: too many PAM enviornment strings", __func__);
5680357011aSchristos 	}
5690357011aSchristos 	if ((r = sshbuf_put_u32(buffer, i)) != 0)
5700357011aSchristos 		fatal("%s: buffer error: %s", __func__, ssh_err(r));
5710357011aSchristos 	for (i = 0; env_from_pam != NULL && env_from_pam[i] != NULL; i++) {
5720357011aSchristos 		if ((r = sshbuf_put_cstring(buffer, env_from_pam[i])) != 0)
5730357011aSchristos 			fatal("%s: buffer error: %s", __func__, ssh_err(r));
5740357011aSchristos 	}
575313c6c94Schristos #endif /* UNSUPPORTED_POSIX_THREADS_HACK */
576313c6c94Schristos 
577313c6c94Schristos 	/* XXX - can't do much about an error here */
5780357011aSchristos 	ssh_msg_send(ctxt->pam_csock, sshpam_err, buffer);
5790357011aSchristos 	sshbuf_free(buffer);
580313c6c94Schristos 	pthread_exit(NULL);
581313c6c94Schristos 
582313c6c94Schristos  auth_fail:
5830357011aSchristos 	if ((r = sshbuf_put_cstring(buffer,
5840357011aSchristos 	    pam_strerror(sshpam_handle, sshpam_err))) != 0)
5850357011aSchristos 		fatal("%s: buffer error: %s", __func__, ssh_err(r));
586313c6c94Schristos 	/* XXX - can't do much about an error here */
587313c6c94Schristos 	if (sshpam_err == PAM_ACCT_EXPIRED)
5880357011aSchristos 		ssh_msg_send(ctxt->pam_csock, PAM_ACCT_EXPIRED, buffer);
589e1a26310Schristos 	else if (sshpam_maxtries_reached)
5900357011aSchristos 		ssh_msg_send(ctxt->pam_csock, PAM_MAXTRIES, buffer);
591313c6c94Schristos 	else
5920357011aSchristos 		ssh_msg_send(ctxt->pam_csock, PAM_AUTH_ERR, buffer);
5930357011aSchristos 	sshbuf_free(buffer);
594313c6c94Schristos 	pthread_exit(NULL);
595313c6c94Schristos 
596313c6c94Schristos 	return (NULL); /* Avoid warning for non-pthread case */
597313c6c94Schristos }
598313c6c94Schristos 
599313c6c94Schristos void
sshpam_thread_cleanup(void)600313c6c94Schristos sshpam_thread_cleanup(void)
601313c6c94Schristos {
602313c6c94Schristos 	struct pam_ctxt *ctxt = cleanup_ctxt;
603313c6c94Schristos 
604313c6c94Schristos 	debug3("PAM: %s entering", __func__);
605313c6c94Schristos 	if (ctxt != NULL && ctxt->pam_thread != 0) {
606313c6c94Schristos 		pthread_cancel(ctxt->pam_thread);
607313c6c94Schristos 		pthread_join(ctxt->pam_thread, NULL);
608313c6c94Schristos 		close(ctxt->pam_psock);
609313c6c94Schristos 		close(ctxt->pam_csock);
610313c6c94Schristos 		memset(ctxt, 0, sizeof(*ctxt));
611313c6c94Schristos 		cleanup_ctxt = NULL;
612313c6c94Schristos 	}
613313c6c94Schristos }
614313c6c94Schristos 
615313c6c94Schristos static int
sshpam_null_conv(int n,sshpam_const struct pam_message ** msg,struct pam_response ** resp,void * data)616313c6c94Schristos sshpam_null_conv(int n, sshpam_const struct pam_message **msg,
617313c6c94Schristos     struct pam_response **resp, void *data)
618313c6c94Schristos {
619313c6c94Schristos 	debug3("PAM: %s entering, %d messages", __func__, n);
620313c6c94Schristos 	return (PAM_CONV_ERR);
621313c6c94Schristos }
622313c6c94Schristos 
623313c6c94Schristos static struct pam_conv null_conv = { sshpam_null_conv, NULL };
624313c6c94Schristos 
625313c6c94Schristos static int
sshpam_store_conv(int n,sshpam_const struct pam_message ** msg,struct pam_response ** resp,void * data)626313c6c94Schristos sshpam_store_conv(int n, sshpam_const struct pam_message **msg,
627313c6c94Schristos     struct pam_response **resp, void *data)
628313c6c94Schristos {
629313c6c94Schristos 	struct pam_response *reply;
6300357011aSchristos 	int r, i;
631313c6c94Schristos 
632313c6c94Schristos 	debug3("PAM: %s called with %d messages", __func__, n);
633313c6c94Schristos 	*resp = NULL;
634313c6c94Schristos 
635313c6c94Schristos 	if (n <= 0 || n > PAM_MAX_NUM_MSG)
636313c6c94Schristos 		return (PAM_CONV_ERR);
637313c6c94Schristos 
638313c6c94Schristos 	if ((reply = calloc(n, sizeof(*reply))) == NULL)
639313c6c94Schristos 		return (PAM_CONV_ERR);
640313c6c94Schristos 
641313c6c94Schristos 	for (i = 0; i < n; ++i) {
642313c6c94Schristos 		switch (PAM_MSG_MEMBER(msg, i, msg_style)) {
643313c6c94Schristos 		case PAM_ERROR_MSG:
644313c6c94Schristos 		case PAM_TEXT_INFO:
6450357011aSchristos 			if ((r = sshbuf_putf(loginmsg, "%s\n",
6460357011aSchristos 			    PAM_MSG_MEMBER(msg, i, msg))) != 0)
6470357011aSchristos 				fatal("%s: buffer error: %s",
6480357011aSchristos 				    __func__, ssh_err(r));
649313c6c94Schristos 			reply[i].resp_retcode = PAM_SUCCESS;
650313c6c94Schristos 			break;
651313c6c94Schristos 		default:
652313c6c94Schristos 			goto fail;
653313c6c94Schristos 		}
654313c6c94Schristos 	}
655313c6c94Schristos 	*resp = reply;
656313c6c94Schristos 	return (PAM_SUCCESS);
657313c6c94Schristos 
658313c6c94Schristos  fail:
659313c6c94Schristos 	for(i = 0; i < n; i++) {
6604ec0b1fcSchristos 		free(reply[i].resp);
661313c6c94Schristos 	}
6624ec0b1fcSchristos 	free(reply);
663313c6c94Schristos 	return (PAM_CONV_ERR);
664313c6c94Schristos }
665313c6c94Schristos 
666313c6c94Schristos static struct pam_conv store_conv = { sshpam_store_conv, NULL };
667313c6c94Schristos 
668313c6c94Schristos void
sshpam_cleanup(void)669313c6c94Schristos sshpam_cleanup(void)
670313c6c94Schristos {
671313c6c94Schristos 	if (sshpam_handle == NULL || (use_privsep && !mm_is_monitor()))
672313c6c94Schristos 		return;
673313c6c94Schristos 	debug("PAM: cleanup");
674313c6c94Schristos 	pam_set_item(sshpam_handle, PAM_CONV, (const void *)&null_conv);
675313c6c94Schristos 	if (sshpam_session_open) {
676313c6c94Schristos 		debug("PAM: closing session");
677313c6c94Schristos 		pam_close_session(sshpam_handle, PAM_SILENT);
678313c6c94Schristos 		sshpam_session_open = 0;
679313c6c94Schristos 	}
68047dc7704Schristos 	if (sshpam_cred_established) {
68147dc7704Schristos 		debug("PAM: deleting credentials");
68247dc7704Schristos 		pam_setcred(sshpam_handle, PAM_DELETE_CRED);
68347dc7704Schristos 		sshpam_cred_established = 0;
68447dc7704Schristos 	}
685313c6c94Schristos 	sshpam_authenticated = 0;
686313c6c94Schristos 	pam_end(sshpam_handle, sshpam_err);
687313c6c94Schristos 	sshpam_handle = NULL;
688313c6c94Schristos }
689313c6c94Schristos 
690313c6c94Schristos static int
sshpam_init(struct ssh * ssh,Authctxt * authctxt)6918f892fcaSchristos sshpam_init(struct ssh *ssh, Authctxt *authctxt)
692313c6c94Schristos {
6938f892fcaSchristos 	const char *pam_user, *user = authctxt->user;
694313c6c94Schristos 	const char **ptr_pam_user = &pam_user;
695313c6c94Schristos 
6968f892fcaSchristos 	if (sshpam_handle == NULL) {
6978f892fcaSchristos 		if (ssh == NULL) {
6988f892fcaSchristos 			fatal("%s: called initially with no "
6998f892fcaSchristos 			    "packet context", __func__);
7008f892fcaSchristos 		}
7018f892fcaSchristos 	} if (sshpam_handle != NULL) {
702313c6c94Schristos 		/* We already have a PAM context; check if the user matches */
703313c6c94Schristos 		sshpam_err = pam_get_item(sshpam_handle,
704313c6c94Schristos 		    PAM_USER, (sshpam_const void **)ptr_pam_user);
705313c6c94Schristos 		if (sshpam_err == PAM_SUCCESS && strcmp(user, pam_user) == 0)
706313c6c94Schristos 			return (0);
707313c6c94Schristos 		pam_end(sshpam_handle, sshpam_err);
708313c6c94Schristos 		sshpam_handle = NULL;
709313c6c94Schristos 	}
710313c6c94Schristos 	debug("PAM: initializing for \"%s\"", user);
711313c6c94Schristos 	sshpam_err =
712313c6c94Schristos 	    pam_start(SSHD_PAM_SERVICE, user, &store_conv, &sshpam_handle);
713313c6c94Schristos 	sshpam_authctxt = authctxt;
714313c6c94Schristos 
715313c6c94Schristos 	if (sshpam_err != PAM_SUCCESS) {
716313c6c94Schristos 		pam_end(sshpam_handle, sshpam_err);
717313c6c94Schristos 		sshpam_handle = NULL;
718313c6c94Schristos 		return (-1);
719313c6c94Schristos 	}
7208f892fcaSchristos 
7218f892fcaSchristos 	if (ssh != NULL && sshpam_rhost == NULL) {
7228f892fcaSchristos 		/*
7238f892fcaSchristos 		 * We need to cache these as we don't have packet context
7248f892fcaSchristos 		 * during the kbdint flow.
7258f892fcaSchristos 		 */
7268f892fcaSchristos 		sshpam_rhost = xstrdup(auth_get_canonical_hostname(ssh,
7278f892fcaSchristos 		    options.use_dns));
7288f892fcaSchristos 	        sshpam_laddr = get_local_ipaddr(
7298f892fcaSchristos 		    ssh_packet_get_connection_in(ssh));
7308f892fcaSchristos 	        xasprintf(&sshpam_conninfo, "SSH_CONNECTION=%.50s %d %.50s %d",
7318f892fcaSchristos 		    ssh_remote_ipaddr(ssh), ssh_remote_port(ssh),
7328f892fcaSchristos 		    sshpam_laddr, ssh_local_port(ssh));
7338f892fcaSchristos 	}
7348f892fcaSchristos 	if (sshpam_rhost != NULL) {
7358f892fcaSchristos 		debug("PAM: setting PAM_RHOST to \"%s\"", sshpam_rhost);
7368f892fcaSchristos 		sshpam_err = pam_set_item(sshpam_handle, PAM_RHOST,
7378f892fcaSchristos 		    sshpam_rhost);
738313c6c94Schristos 		if (sshpam_err != PAM_SUCCESS) {
739313c6c94Schristos 			pam_end(sshpam_handle, sshpam_err);
740313c6c94Schristos 			sshpam_handle = NULL;
741313c6c94Schristos 			return (-1);
742313c6c94Schristos 		}
7438f892fcaSchristos 		/* Put SSH_CONNECTION in the PAM environment too */
7448f892fcaSchristos 		pam_putenv(sshpam_handle, sshpam_conninfo);
7458f892fcaSchristos 	}
7468f892fcaSchristos 
747313c6c94Schristos #ifdef PAM_TTY_KLUDGE
748313c6c94Schristos 	/*
749313c6c94Schristos 	 * Some silly PAM modules (e.g. pam_time) require a TTY to operate.
750313c6c94Schristos 	 * sshd doesn't set the tty until too late in the auth process and
751313c6c94Schristos 	 * may not even set one (for tty-less connections)
752313c6c94Schristos 	 */
753313c6c94Schristos 	debug("PAM: setting PAM_TTY to \"ssh\"");
754313c6c94Schristos 	sshpam_err = pam_set_item(sshpam_handle, PAM_TTY, "ssh");
755313c6c94Schristos 	if (sshpam_err != PAM_SUCCESS) {
756313c6c94Schristos 		pam_end(sshpam_handle, sshpam_err);
757313c6c94Schristos 		sshpam_handle = NULL;
758313c6c94Schristos 		return (-1);
759313c6c94Schristos 	}
760313c6c94Schristos #endif
761313c6c94Schristos 	return (0);
762313c6c94Schristos }
763313c6c94Schristos 
7640357011aSchristos static void
expose_authinfo(const char * caller)7650357011aSchristos expose_authinfo(const char *caller)
7660357011aSchristos {
7670357011aSchristos 	char *auth_info;
7680357011aSchristos 
7690357011aSchristos 	/*
7700357011aSchristos 	 * Expose authentication information to PAM.
7710357011aSchristos 	 * The environment variable is versioned. Please increment the
7720357011aSchristos 	 * version suffix if the format of session_info changes.
7730357011aSchristos 	 */
7740357011aSchristos 	if (sshpam_authctxt->session_info == NULL)
7750357011aSchristos 		auth_info = xstrdup("");
7760357011aSchristos 	else if ((auth_info = sshbuf_dup_string(
7770357011aSchristos 	    sshpam_authctxt->session_info)) == NULL)
7780357011aSchristos 		fatal("%s: sshbuf_dup_string failed", __func__);
7790357011aSchristos 
7800357011aSchristos 	debug2("%s: auth information in SSH_AUTH_INFO_0", caller);
7810357011aSchristos 	do_pam_putenv("SSH_AUTH_INFO_0", auth_info);
7820357011aSchristos 	free(auth_info);
7830357011aSchristos }
7840357011aSchristos 
785313c6c94Schristos static void *
sshpam_init_ctx(Authctxt * authctxt)786313c6c94Schristos sshpam_init_ctx(Authctxt *authctxt)
787313c6c94Schristos {
788313c6c94Schristos 	struct pam_ctxt *ctxt;
789313c6c94Schristos 	int socks[2];
790313c6c94Schristos 
791313c6c94Schristos 	debug3("PAM: %s entering", __func__);
792313c6c94Schristos 	/*
793313c6c94Schristos 	 * Refuse to start if we don't have PAM enabled or do_pam_account
794313c6c94Schristos 	 * has previously failed.
795313c6c94Schristos 	 */
796313c6c94Schristos 	if (!options.use_pam || sshpam_account_status == 0)
797313c6c94Schristos 		return NULL;
798313c6c94Schristos 
799313c6c94Schristos 	/* Initialize PAM */
8008f892fcaSchristos 	if (sshpam_init(NULL, authctxt) == -1) {
801313c6c94Schristos 		error("PAM: initialization failed");
802313c6c94Schristos 		return (NULL);
803313c6c94Schristos 	}
804313c6c94Schristos 
8050357011aSchristos 	expose_authinfo(__func__);
806313c6c94Schristos 	ctxt = xcalloc(1, sizeof *ctxt);
807313c6c94Schristos 
808313c6c94Schristos 	/* Start the authentication thread */
809313c6c94Schristos 	if (socketpair(AF_UNIX, SOCK_STREAM, PF_UNSPEC, socks) == -1) {
810313c6c94Schristos 		error("PAM: failed create sockets: %s", strerror(errno));
8114ec0b1fcSchristos 		free(ctxt);
812313c6c94Schristos 		return (NULL);
813313c6c94Schristos 	}
814313c6c94Schristos 	ctxt->pam_psock = socks[0];
815313c6c94Schristos 	ctxt->pam_csock = socks[1];
816313c6c94Schristos 	if (pthread_create(&ctxt->pam_thread, NULL, sshpam_thread, ctxt) == -1) {
817313c6c94Schristos 		error("PAM: failed to start authentication thread: %s",
818313c6c94Schristos 		    strerror(errno));
819313c6c94Schristos 		close(socks[0]);
820313c6c94Schristos 		close(socks[1]);
8214ec0b1fcSchristos 		free(ctxt);
822313c6c94Schristos 		return (NULL);
823313c6c94Schristos 	}
824313c6c94Schristos 	cleanup_ctxt = ctxt;
825313c6c94Schristos 	return (ctxt);
826313c6c94Schristos }
827313c6c94Schristos 
828313c6c94Schristos static int
sshpam_query(void * ctx,char ** name,char ** info,u_int * num,char *** prompts,u_int ** echo_on)829313c6c94Schristos sshpam_query(void *ctx, char **name, char **info,
830313c6c94Schristos     u_int *num, char ***prompts, u_int **echo_on)
831313c6c94Schristos {
8320357011aSchristos 	struct sshbuf *buffer;
833313c6c94Schristos 	struct pam_ctxt *ctxt = ctx;
834313c6c94Schristos 	size_t plen;
835313c6c94Schristos 	u_char type;
836313c6c94Schristos 	char *msg;
837313c6c94Schristos 	size_t len, mlen;
8380357011aSchristos 	int r;
839313c6c94Schristos 
840313c6c94Schristos 	debug3("PAM: %s entering", __func__);
8410357011aSchristos 	if ((buffer = sshbuf_new()) == NULL)
8420357011aSchristos 		fatal("%s: sshbuf_new failed", __func__);
843313c6c94Schristos 	*name = xstrdup("");
844313c6c94Schristos 	*info = xstrdup("");
845313c6c94Schristos 	*prompts = xmalloc(sizeof(char *));
846313c6c94Schristos 	**prompts = NULL;
847313c6c94Schristos 	plen = 0;
848313c6c94Schristos 	*echo_on = xmalloc(sizeof(u_int));
8490357011aSchristos 	while (ssh_msg_recv(ctxt->pam_psock, buffer) == 0) {
8500357011aSchristos 		if ((r = sshbuf_get_u8(buffer, &type)) != 0 ||
8510357011aSchristos 		    (r = sshbuf_get_cstring(buffer, &msg, &mlen)) != 0)
8520357011aSchristos 			fatal("%s: buffer error: %s", __func__, ssh_err(r));
853313c6c94Schristos 		switch (type) {
854313c6c94Schristos 		case PAM_PROMPT_ECHO_ON:
855313c6c94Schristos 		case PAM_PROMPT_ECHO_OFF:
856313c6c94Schristos 			*num = 1;
857313c6c94Schristos 			len = plen + mlen + 1;
858c6a11926Schristos 			**prompts = xreallocarray(**prompts, 1, len);
859313c6c94Schristos 			strlcpy(**prompts + plen, msg, len - plen);
860313c6c94Schristos 			plen += mlen;
861313c6c94Schristos 			**echo_on = (type == PAM_PROMPT_ECHO_ON);
8624ec0b1fcSchristos 			free(msg);
863313c6c94Schristos 			return (0);
864313c6c94Schristos 		case PAM_ERROR_MSG:
865313c6c94Schristos 		case PAM_TEXT_INFO:
866313c6c94Schristos 			/* accumulate messages */
867313c6c94Schristos 			len = plen + mlen + 2;
868c6a11926Schristos 			**prompts = xreallocarray(**prompts, 1, len);
869313c6c94Schristos 			strlcpy(**prompts + plen, msg, len - plen);
870313c6c94Schristos 			plen += mlen;
871313c6c94Schristos 			strlcat(**prompts + plen, "\n", len - plen);
872313c6c94Schristos 			plen++;
8734ec0b1fcSchristos 			free(msg);
874313c6c94Schristos 			break;
875313c6c94Schristos 		case PAM_ACCT_EXPIRED:
876e1a26310Schristos 		case PAM_MAXTRIES:
877e1a26310Schristos 			if (type == PAM_ACCT_EXPIRED)
878313c6c94Schristos 				sshpam_account_status = 0;
879e1a26310Schristos 			if (type == PAM_MAXTRIES)
880e1a26310Schristos 				sshpam_set_maxtries_reached(1);
881313c6c94Schristos 			/* FALLTHROUGH */
882313c6c94Schristos 		case PAM_AUTH_ERR:
883313c6c94Schristos 			debug3("PAM: %s", pam_strerror(sshpam_handle, type));
884313c6c94Schristos 			if (**prompts != NULL && strlen(**prompts) != 0) {
885313c6c94Schristos 				*info = **prompts;
886313c6c94Schristos 				**prompts = NULL;
887313c6c94Schristos 				*num = 0;
888313c6c94Schristos 				**echo_on = 0;
889313c6c94Schristos 				ctxt->pam_done = -1;
8904ec0b1fcSchristos 				free(msg);
891313c6c94Schristos 				return 0;
892313c6c94Schristos 			}
893313c6c94Schristos 			/* FALLTHROUGH */
894313c6c94Schristos 		case PAM_SUCCESS:
895313c6c94Schristos 			if (**prompts != NULL) {
896313c6c94Schristos 				/* drain any accumulated messages */
897313c6c94Schristos 				debug("PAM: %s", **prompts);
8980357011aSchristos 				if ((r = sshbuf_put(loginmsg, **prompts,
8990357011aSchristos 				    strlen(**prompts))) != 0)
9000357011aSchristos 					fatal("%s: buffer error: %s",
9010357011aSchristos 					    __func__, ssh_err(r));
9024ec0b1fcSchristos 				free(**prompts);
903313c6c94Schristos 				**prompts = NULL;
904313c6c94Schristos 			}
905313c6c94Schristos 			if (type == PAM_SUCCESS) {
906313c6c94Schristos 				if (!sshpam_authctxt->valid ||
907313c6c94Schristos 				    (sshpam_authctxt->pw->pw_uid == 0 &&
908313c6c94Schristos 				    options.permit_root_login != PERMIT_YES))
909313c6c94Schristos 					fatal("Internal error: PAM auth "
910313c6c94Schristos 					    "succeeded when it should have "
911313c6c94Schristos 					    "failed");
9120357011aSchristos 				import_environments(buffer);
913313c6c94Schristos 				*num = 0;
914313c6c94Schristos 				**echo_on = 0;
915313c6c94Schristos 				ctxt->pam_done = 1;
9164ec0b1fcSchristos 				free(msg);
917313c6c94Schristos 				return (0);
918313c6c94Schristos 			}
919313c6c94Schristos 			error("PAM: %s for %s%.100s from %.100s", msg,
920313c6c94Schristos 			    sshpam_authctxt->valid ? "" : "illegal user ",
9218f892fcaSchristos 			    sshpam_authctxt->user, sshpam_rhost);
922313c6c94Schristos 			/* FALLTHROUGH */
923313c6c94Schristos 		default:
924313c6c94Schristos 			*num = 0;
925313c6c94Schristos 			**echo_on = 0;
9264ec0b1fcSchristos 			free(msg);
927313c6c94Schristos 			ctxt->pam_done = -1;
928313c6c94Schristos 			return (-1);
929313c6c94Schristos 		}
930313c6c94Schristos 	}
931313c6c94Schristos 	return (-1);
932313c6c94Schristos }
933313c6c94Schristos 
934e1a26310Schristos /*
935e1a26310Schristos  * Returns a junk password of identical length to that the user supplied.
936e1a26310Schristos  * Used to mitigate timing attacks against crypt(3)/PAM stacks that
937e1a26310Schristos  * vary processing time in proportion to password length.
938e1a26310Schristos  */
939e1a26310Schristos static char *
fake_password(const char * wire_password)940e1a26310Schristos fake_password(const char *wire_password)
941e1a26310Schristos {
942e1a26310Schristos 	const char junk[] = "\b\n\r\177INCORRECT";
943e1a26310Schristos 	char *ret = NULL;
944e1a26310Schristos 	size_t i, l = wire_password != NULL ? strlen(wire_password) : 0;
945e1a26310Schristos 
946e1a26310Schristos 	if (l >= INT_MAX)
947e1a26310Schristos 		fatal("%s: password length too long: %zu", __func__, l);
948e1a26310Schristos 
949e1a26310Schristos 	ret = malloc(l + 1);
950e1a26310Schristos 	if (ret == NULL)
951e1a26310Schristos 		return NULL;
952e1a26310Schristos 	for (i = 0; i < l; i++)
953e1a26310Schristos 		ret[i] = junk[i % (sizeof(junk) - 1)];
954e1a26310Schristos 	ret[i] = '\0';
955e1a26310Schristos 	return ret;
956e1a26310Schristos }
957e1a26310Schristos 
958313c6c94Schristos /* XXX - see also comment in auth-chall.c:verify_response */
959313c6c94Schristos static int
sshpam_respond(void * ctx,u_int num,char ** resp)960313c6c94Schristos sshpam_respond(void *ctx, u_int num, char **resp)
961313c6c94Schristos {
9620357011aSchristos 	struct sshbuf *buffer;
963313c6c94Schristos 	struct pam_ctxt *ctxt = ctx;
964e1a26310Schristos 	char *fake;
9650357011aSchristos 	int r;
966313c6c94Schristos 
967313c6c94Schristos 	debug2("PAM: %s entering, %u responses", __func__, num);
968313c6c94Schristos 	switch (ctxt->pam_done) {
969313c6c94Schristos 	case 1:
970313c6c94Schristos 		sshpam_authenticated = 1;
971313c6c94Schristos 		return (0);
972313c6c94Schristos 	case 0:
973313c6c94Schristos 		break;
974313c6c94Schristos 	default:
975313c6c94Schristos 		return (-1);
976313c6c94Schristos 	}
977313c6c94Schristos 	if (num != 1) {
978313c6c94Schristos 		error("PAM: expected one response, got %u", num);
979313c6c94Schristos 		return (-1);
980313c6c94Schristos 	}
9810357011aSchristos 	if ((buffer = sshbuf_new()) == NULL)
9820357011aSchristos 		fatal("%s: sshbuf_new failed", __func__);
983313c6c94Schristos 	if (sshpam_authctxt->valid &&
984313c6c94Schristos 	    (sshpam_authctxt->pw->pw_uid != 0 ||
9850357011aSchristos 	    options.permit_root_login == PERMIT_YES)) {
9860357011aSchristos 		if ((r = sshbuf_put_cstring(buffer, *resp)) != 0)
9870357011aSchristos 			fatal("%s: buffer error: %s", __func__, ssh_err(r));
9880357011aSchristos 	} else {
989e1a26310Schristos 		fake = fake_password(*resp);
9900357011aSchristos 		if ((r = sshbuf_put_cstring(buffer, fake)) != 0)
9910357011aSchristos 			fatal("%s: buffer error: %s", __func__, ssh_err(r));
992e1a26310Schristos 		free(fake);
993e1a26310Schristos 	}
9940357011aSchristos 	if (ssh_msg_send(ctxt->pam_psock, PAM_AUTHTOK, buffer) == -1) {
9950357011aSchristos 		sshbuf_free(buffer);
996313c6c94Schristos 		return (-1);
997313c6c94Schristos 	}
9980357011aSchristos 	sshbuf_free(buffer);
999313c6c94Schristos 	return (1);
1000313c6c94Schristos }
1001313c6c94Schristos 
1002313c6c94Schristos static void
sshpam_free_ctx(void * ctxtp)1003313c6c94Schristos sshpam_free_ctx(void *ctxtp)
1004313c6c94Schristos {
1005313c6c94Schristos 	struct pam_ctxt *ctxt = ctxtp;
1006313c6c94Schristos 
1007313c6c94Schristos 	debug3("PAM: %s entering", __func__);
1008313c6c94Schristos 	sshpam_thread_cleanup();
10094ec0b1fcSchristos 	free(ctxt);
1010313c6c94Schristos 	/*
1011313c6c94Schristos 	 * We don't call sshpam_cleanup() here because we may need the PAM
1012313c6c94Schristos 	 * handle at a later stage, e.g. when setting up a session.  It's
1013313c6c94Schristos 	 * still on the cleanup list, so pam_end() *will* be called before
1014313c6c94Schristos 	 * the server process terminates.
1015313c6c94Schristos 	 */
1016313c6c94Schristos }
1017313c6c94Schristos 
1018313c6c94Schristos KbdintDevice sshpam_device = {
1019313c6c94Schristos 	"pam",
1020313c6c94Schristos 	sshpam_init_ctx,
1021313c6c94Schristos 	sshpam_query,
1022313c6c94Schristos 	sshpam_respond,
1023313c6c94Schristos 	sshpam_free_ctx
1024313c6c94Schristos };
1025313c6c94Schristos 
1026313c6c94Schristos KbdintDevice mm_sshpam_device = {
1027313c6c94Schristos 	"pam",
1028313c6c94Schristos 	mm_sshpam_init_ctx,
1029313c6c94Schristos 	mm_sshpam_query,
1030313c6c94Schristos 	mm_sshpam_respond,
1031313c6c94Schristos 	mm_sshpam_free_ctx
1032313c6c94Schristos };
1033313c6c94Schristos 
1034313c6c94Schristos /*
1035313c6c94Schristos  * This replaces auth-pam.c
1036313c6c94Schristos  */
1037313c6c94Schristos void
start_pam(struct ssh * ssh)10388f892fcaSchristos start_pam(struct ssh *ssh)
1039313c6c94Schristos {
10408f892fcaSchristos 	Authctxt *authctxt = ssh->authctxt;
10418f892fcaSchristos 
1042313c6c94Schristos 	if (!options.use_pam)
1043313c6c94Schristos 		fatal("PAM: initialisation requested when UsePAM=no");
1044313c6c94Schristos 
10458f892fcaSchristos 	if (sshpam_init(ssh, authctxt) == -1)
1046313c6c94Schristos 		fatal("PAM: initialisation failed");
1047313c6c94Schristos }
1048313c6c94Schristos 
1049313c6c94Schristos void
finish_pam(void)1050313c6c94Schristos finish_pam(void)
1051313c6c94Schristos {
1052313c6c94Schristos 	sshpam_cleanup();
1053313c6c94Schristos }
1054313c6c94Schristos 
1055e1a26310Schristos 
1056313c6c94Schristos u_int
do_pam_account(void)1057313c6c94Schristos do_pam_account(void)
1058313c6c94Schristos {
1059313c6c94Schristos 	debug("%s: called", __func__);
1060313c6c94Schristos 	if (sshpam_account_status != -1)
1061313c6c94Schristos 		return (sshpam_account_status);
1062313c6c94Schristos 
1063e1a26310Schristos 	expose_authinfo(__func__);
1064e1a26310Schristos 
1065313c6c94Schristos 	sshpam_err = pam_acct_mgmt(sshpam_handle, 0);
1066313c6c94Schristos 	debug3("PAM: %s pam_acct_mgmt = %d (%s)", __func__, sshpam_err,
1067313c6c94Schristos 	    pam_strerror(sshpam_handle, sshpam_err));
1068313c6c94Schristos 
1069313c6c94Schristos 	if (sshpam_err != PAM_SUCCESS && sshpam_err != PAM_NEW_AUTHTOK_REQD) {
1070313c6c94Schristos 		sshpam_account_status = 0;
1071313c6c94Schristos 		return (sshpam_account_status);
1072313c6c94Schristos 	}
1073313c6c94Schristos 
1074313c6c94Schristos 	if (sshpam_err == PAM_NEW_AUTHTOK_REQD)
1075313c6c94Schristos 		sshpam_password_change_required(1);
1076313c6c94Schristos 
1077313c6c94Schristos 	sshpam_account_status = 1;
1078313c6c94Schristos 	return (sshpam_account_status);
1079313c6c94Schristos }
1080313c6c94Schristos 
1081313c6c94Schristos void
do_pam_setcred(int init)1082313c6c94Schristos do_pam_setcred(int init)
1083313c6c94Schristos {
1084313c6c94Schristos 	sshpam_err = pam_set_item(sshpam_handle, PAM_CONV,
1085313c6c94Schristos 	    (const void *)&store_conv);
1086313c6c94Schristos 	if (sshpam_err != PAM_SUCCESS)
1087313c6c94Schristos 		fatal("PAM: failed to set PAM_CONV: %s",
1088313c6c94Schristos 		    pam_strerror(sshpam_handle, sshpam_err));
1089313c6c94Schristos 	if (init) {
1090313c6c94Schristos 		debug("PAM: establishing credentials");
1091313c6c94Schristos 		sshpam_err = pam_setcred(sshpam_handle, PAM_ESTABLISH_CRED);
1092313c6c94Schristos 	} else {
1093313c6c94Schristos 		debug("PAM: reinitializing credentials");
1094313c6c94Schristos 		sshpam_err = pam_setcred(sshpam_handle, PAM_REINITIALIZE_CRED);
1095313c6c94Schristos 	}
1096313c6c94Schristos 	if (sshpam_err == PAM_SUCCESS) {
1097313c6c94Schristos 		sshpam_cred_established = 1;
1098313c6c94Schristos 		return;
1099313c6c94Schristos 	}
1100313c6c94Schristos 	if (sshpam_authenticated)
1101313c6c94Schristos 		fatal("PAM: pam_setcred(): %s",
1102313c6c94Schristos 		    pam_strerror(sshpam_handle, sshpam_err));
1103313c6c94Schristos 	else
1104313c6c94Schristos 		debug("PAM: pam_setcred(): %s",
1105313c6c94Schristos 		    pam_strerror(sshpam_handle, sshpam_err));
1106313c6c94Schristos }
1107313c6c94Schristos 
1108313c6c94Schristos static int
sshpam_tty_conv(int n,sshpam_const struct pam_message ** msg,struct pam_response ** resp,void * data)1109313c6c94Schristos sshpam_tty_conv(int n, sshpam_const struct pam_message **msg,
1110313c6c94Schristos     struct pam_response **resp, void *data)
1111313c6c94Schristos {
1112313c6c94Schristos 	char input[PAM_MAX_MSG_SIZE];
1113313c6c94Schristos 	struct pam_response *reply;
1114313c6c94Schristos 	int i;
1115313c6c94Schristos 
1116313c6c94Schristos 	debug3("PAM: %s called with %d messages", __func__, n);
1117313c6c94Schristos 
1118313c6c94Schristos 	*resp = NULL;
1119313c6c94Schristos 
1120313c6c94Schristos 	if (n <= 0 || n > PAM_MAX_NUM_MSG || !isatty(STDIN_FILENO))
1121313c6c94Schristos 		return (PAM_CONV_ERR);
1122313c6c94Schristos 
1123313c6c94Schristos 	if ((reply = calloc(n, sizeof(*reply))) == NULL)
1124313c6c94Schristos 		return (PAM_CONV_ERR);
1125313c6c94Schristos 
1126313c6c94Schristos 	for (i = 0; i < n; ++i) {
1127313c6c94Schristos 		switch (PAM_MSG_MEMBER(msg, i, msg_style)) {
1128313c6c94Schristos 		case PAM_PROMPT_ECHO_OFF:
1129313c6c94Schristos 			reply[i].resp =
1130313c6c94Schristos 			    read_passphrase(PAM_MSG_MEMBER(msg, i, msg),
1131313c6c94Schristos 			    RP_ALLOW_STDIN);
1132313c6c94Schristos 			reply[i].resp_retcode = PAM_SUCCESS;
1133313c6c94Schristos 			break;
1134313c6c94Schristos 		case PAM_PROMPT_ECHO_ON:
1135313c6c94Schristos 			fprintf(stderr, "%s\n", PAM_MSG_MEMBER(msg, i, msg));
1136313c6c94Schristos 			if (fgets(input, sizeof input, stdin) == NULL)
1137313c6c94Schristos 				input[0] = '\0';
1138313c6c94Schristos 			if ((reply[i].resp = strdup(input)) == NULL)
1139313c6c94Schristos 				goto fail;
1140313c6c94Schristos 			reply[i].resp_retcode = PAM_SUCCESS;
1141313c6c94Schristos 			break;
1142313c6c94Schristos 		case PAM_ERROR_MSG:
1143313c6c94Schristos 		case PAM_TEXT_INFO:
1144313c6c94Schristos 			fprintf(stderr, "%s\n", PAM_MSG_MEMBER(msg, i, msg));
1145313c6c94Schristos 			reply[i].resp_retcode = PAM_SUCCESS;
1146313c6c94Schristos 			break;
1147313c6c94Schristos 		default:
1148313c6c94Schristos 			goto fail;
1149313c6c94Schristos 		}
1150313c6c94Schristos 	}
1151313c6c94Schristos 	*resp = reply;
1152313c6c94Schristos 	return (PAM_SUCCESS);
1153313c6c94Schristos 
1154313c6c94Schristos  fail:
1155313c6c94Schristos 	for(i = 0; i < n; i++) {
11564ec0b1fcSchristos 		free(reply[i].resp);
1157313c6c94Schristos 	}
11584ec0b1fcSchristos 	free(reply);
1159313c6c94Schristos 	return (PAM_CONV_ERR);
1160313c6c94Schristos }
1161313c6c94Schristos 
1162313c6c94Schristos static struct pam_conv tty_conv = { sshpam_tty_conv, NULL };
1163313c6c94Schristos 
1164313c6c94Schristos /*
1165313c6c94Schristos  * XXX this should be done in the authentication phase, but ssh1 doesn't
1166313c6c94Schristos  * support that
1167313c6c94Schristos  */
1168313c6c94Schristos void
do_pam_chauthtok(void)1169313c6c94Schristos do_pam_chauthtok(void)
1170313c6c94Schristos {
1171313c6c94Schristos 	if (use_privsep)
1172313c6c94Schristos 		fatal("Password expired (unable to change with privsep)");
1173313c6c94Schristos 	sshpam_err = pam_set_item(sshpam_handle, PAM_CONV,
1174313c6c94Schristos 	    (const void *)&tty_conv);
1175313c6c94Schristos 	if (sshpam_err != PAM_SUCCESS)
1176313c6c94Schristos 		fatal("PAM: failed to set PAM_CONV: %s",
1177313c6c94Schristos 		    pam_strerror(sshpam_handle, sshpam_err));
1178313c6c94Schristos 	debug("PAM: changing password");
1179313c6c94Schristos 	sshpam_err = pam_chauthtok(sshpam_handle, PAM_CHANGE_EXPIRED_AUTHTOK);
1180313c6c94Schristos 	if (sshpam_err != PAM_SUCCESS)
1181313c6c94Schristos 		fatal("PAM: pam_chauthtok(): %s",
1182313c6c94Schristos 		    pam_strerror(sshpam_handle, sshpam_err));
1183313c6c94Schristos }
1184313c6c94Schristos 
1185313c6c94Schristos void
do_pam_session(struct ssh * ssh)1186e1a26310Schristos do_pam_session(struct ssh *ssh)
1187313c6c94Schristos {
1188313c6c94Schristos 	debug3("PAM: opening session");
1189e1a26310Schristos 
1190e1a26310Schristos 	expose_authinfo(__func__);
1191e1a26310Schristos 
1192313c6c94Schristos 	sshpam_err = pam_set_item(sshpam_handle, PAM_CONV,
1193313c6c94Schristos 	    (const void *)&store_conv);
1194313c6c94Schristos 	if (sshpam_err != PAM_SUCCESS)
1195313c6c94Schristos 		fatal("PAM: failed to set PAM_CONV: %s",
1196313c6c94Schristos 		    pam_strerror(sshpam_handle, sshpam_err));
1197313c6c94Schristos 	sshpam_err = pam_open_session(sshpam_handle, 0);
1198313c6c94Schristos 	if (sshpam_err == PAM_SUCCESS)
1199313c6c94Schristos 		sshpam_session_open = 1;
1200313c6c94Schristos 	else {
1201313c6c94Schristos 		sshpam_session_open = 0;
1202e1a26310Schristos 		auth_restrict_session(ssh);
1203313c6c94Schristos 		error("PAM: pam_open_session(): %s",
1204313c6c94Schristos 		    pam_strerror(sshpam_handle, sshpam_err));
1205313c6c94Schristos 	}
1206313c6c94Schristos 
1207313c6c94Schristos }
1208313c6c94Schristos 
1209313c6c94Schristos int
is_pam_session_open(void)1210313c6c94Schristos is_pam_session_open(void)
1211313c6c94Schristos {
1212313c6c94Schristos 	return sshpam_session_open;
1213313c6c94Schristos }
1214313c6c94Schristos 
1215313c6c94Schristos /*
1216313c6c94Schristos  * Set a PAM environment string. We need to do this so that the session
1217313c6c94Schristos  * modules can handle things like Kerberos/GSI credentials that appear
1218313c6c94Schristos  * during the ssh authentication process.
1219313c6c94Schristos  */
1220313c6c94Schristos int
do_pam_putenv(const char * name,char * value)1221e1a26310Schristos do_pam_putenv(const char *name, char *value)
1222313c6c94Schristos {
1223313c6c94Schristos 	int ret = 1;
1224313c6c94Schristos #ifdef HAVE_PAM_PUTENV
1225313c6c94Schristos 	char *compound;
1226313c6c94Schristos 	size_t len;
1227313c6c94Schristos 
1228313c6c94Schristos 	len = strlen(name) + strlen(value) + 2;
1229313c6c94Schristos 	compound = xmalloc(len);
1230313c6c94Schristos 
1231313c6c94Schristos 	snprintf(compound, len, "%s=%s", name, value);
1232313c6c94Schristos 	ret = pam_putenv(sshpam_handle, compound);
12334ec0b1fcSchristos 	free(compound);
1234313c6c94Schristos #endif
1235313c6c94Schristos 
1236313c6c94Schristos 	return (ret);
1237313c6c94Schristos }
1238313c6c94Schristos 
1239313c6c94Schristos char **
fetch_pam_child_environment(void)1240313c6c94Schristos fetch_pam_child_environment(void)
1241313c6c94Schristos {
1242313c6c94Schristos 	return sshpam_env;
1243313c6c94Schristos }
1244313c6c94Schristos 
1245313c6c94Schristos char **
fetch_pam_environment(void)1246313c6c94Schristos fetch_pam_environment(void)
1247313c6c94Schristos {
1248313c6c94Schristos 	return (pam_getenvlist(sshpam_handle));
1249313c6c94Schristos }
1250313c6c94Schristos 
1251313c6c94Schristos void
free_pam_environment(char ** env)1252313c6c94Schristos free_pam_environment(char **env)
1253313c6c94Schristos {
1254313c6c94Schristos 	char **envp;
1255313c6c94Schristos 
1256313c6c94Schristos 	if (env == NULL)
1257313c6c94Schristos 		return;
1258313c6c94Schristos 
1259313c6c94Schristos 	for (envp = env; *envp; envp++)
12604ec0b1fcSchristos 		free(*envp);
12614ec0b1fcSchristos 	free(env);
1262313c6c94Schristos }
1263313c6c94Schristos 
1264313c6c94Schristos /*
1265313c6c94Schristos  * "Blind" conversation function for password authentication.  Assumes that
1266313c6c94Schristos  * echo-off prompts are for the password and stores messages for later
1267313c6c94Schristos  * display.
1268313c6c94Schristos  */
1269313c6c94Schristos static int
sshpam_passwd_conv(int n,sshpam_const struct pam_message ** msg,struct pam_response ** resp,void * data)1270313c6c94Schristos sshpam_passwd_conv(int n, sshpam_const struct pam_message **msg,
1271313c6c94Schristos     struct pam_response **resp, void *data)
1272313c6c94Schristos {
1273313c6c94Schristos 	struct pam_response *reply;
12740357011aSchristos 	int r, i;
1275313c6c94Schristos 	size_t len;
1276313c6c94Schristos 
1277313c6c94Schristos 	debug3("PAM: %s called with %d messages", __func__, n);
1278313c6c94Schristos 
1279313c6c94Schristos 	*resp = NULL;
1280313c6c94Schristos 
1281313c6c94Schristos 	if (n <= 0 || n > PAM_MAX_NUM_MSG)
1282313c6c94Schristos 		return (PAM_CONV_ERR);
1283313c6c94Schristos 
1284313c6c94Schristos 	if ((reply = calloc(n, sizeof(*reply))) == NULL)
1285313c6c94Schristos 		return (PAM_CONV_ERR);
1286313c6c94Schristos 
1287313c6c94Schristos 	for (i = 0; i < n; ++i) {
1288313c6c94Schristos 		switch (PAM_MSG_MEMBER(msg, i, msg_style)) {
1289313c6c94Schristos 		case PAM_PROMPT_ECHO_OFF:
1290313c6c94Schristos 			if (sshpam_password == NULL)
1291313c6c94Schristos 				goto fail;
1292313c6c94Schristos 			if ((reply[i].resp = strdup(sshpam_password)) == NULL)
1293313c6c94Schristos 				goto fail;
1294313c6c94Schristos 			reply[i].resp_retcode = PAM_SUCCESS;
1295313c6c94Schristos 			break;
1296313c6c94Schristos 		case PAM_ERROR_MSG:
1297313c6c94Schristos 		case PAM_TEXT_INFO:
1298313c6c94Schristos 			len = strlen(PAM_MSG_MEMBER(msg, i, msg));
1299313c6c94Schristos 			if (len > 0) {
13000357011aSchristos 				if ((r = sshbuf_putf(loginmsg, "%s\n",
13010357011aSchristos 				    PAM_MSG_MEMBER(msg, i, msg))) != 0)
13020357011aSchristos 					fatal("%s: buffer error: %s",
13030357011aSchristos 					    __func__, ssh_err(r));
1304313c6c94Schristos 			}
1305313c6c94Schristos 			if ((reply[i].resp = strdup("")) == NULL)
1306313c6c94Schristos 				goto fail;
1307313c6c94Schristos 			reply[i].resp_retcode = PAM_SUCCESS;
1308313c6c94Schristos 			break;
1309313c6c94Schristos 		default:
1310313c6c94Schristos 			goto fail;
1311313c6c94Schristos 		}
1312313c6c94Schristos 	}
1313313c6c94Schristos 	*resp = reply;
1314313c6c94Schristos 	return (PAM_SUCCESS);
1315313c6c94Schristos 
1316313c6c94Schristos  fail:
1317313c6c94Schristos 	for(i = 0; i < n; i++) {
13184ec0b1fcSchristos 		free(reply[i].resp);
1319313c6c94Schristos 	}
13204ec0b1fcSchristos 	free(reply);
1321313c6c94Schristos 	return (PAM_CONV_ERR);
1322313c6c94Schristos }
1323313c6c94Schristos 
1324313c6c94Schristos static struct pam_conv passwd_conv = { sshpam_passwd_conv, NULL };
1325313c6c94Schristos 
1326313c6c94Schristos /*
1327313c6c94Schristos  * Attempt password authentication via PAM
1328313c6c94Schristos  */
1329313c6c94Schristos int
sshpam_auth_passwd(Authctxt * authctxt,const char * password)1330313c6c94Schristos sshpam_auth_passwd(Authctxt *authctxt, const char *password)
1331313c6c94Schristos {
1332313c6c94Schristos 	int flags = (options.permit_empty_passwd == 0 ?
1333313c6c94Schristos 	    PAM_DISALLOW_NULL_AUTHTOK : 0);
1334e1a26310Schristos 	char *fake = NULL;
1335313c6c94Schristos 
1336313c6c94Schristos 	if (!options.use_pam || sshpam_handle == NULL)
1337313c6c94Schristos 		fatal("PAM: %s called when PAM disabled or failed to "
1338313c6c94Schristos 		    "initialise.", __func__);
1339313c6c94Schristos 
1340313c6c94Schristos 	sshpam_password = password;
1341313c6c94Schristos 	sshpam_authctxt = authctxt;
1342313c6c94Schristos 
1343313c6c94Schristos 	/*
1344313c6c94Schristos 	 * If the user logging in is invalid, or is root but is not permitted
1345313c6c94Schristos 	 * by PermitRootLogin, use an invalid password to prevent leaking
1346313c6c94Schristos 	 * information via timing (eg if the PAM config has a delay on fail).
1347313c6c94Schristos 	 */
1348313c6c94Schristos 	if (!authctxt->valid || (authctxt->pw->pw_uid == 0 &&
1349313c6c94Schristos 	    options.permit_root_login != PERMIT_YES))
1350e1a26310Schristos 		sshpam_password = fake = fake_password(password);
1351313c6c94Schristos 
1352313c6c94Schristos 	sshpam_err = pam_set_item(sshpam_handle, PAM_CONV,
1353313c6c94Schristos 	    (const void *)&passwd_conv);
1354313c6c94Schristos 	if (sshpam_err != PAM_SUCCESS)
1355313c6c94Schristos 		fatal("PAM: %s: failed to set PAM_CONV: %s", __func__,
1356313c6c94Schristos 		    pam_strerror(sshpam_handle, sshpam_err));
1357313c6c94Schristos 
1358313c6c94Schristos 	sshpam_err = pam_authenticate(sshpam_handle, flags);
1359313c6c94Schristos 	sshpam_password = NULL;
1360e1a26310Schristos 	free(fake);
1361e1a26310Schristos 	if (sshpam_err == PAM_MAXTRIES)
1362e1a26310Schristos 		sshpam_set_maxtries_reached(1);
1363313c6c94Schristos 	if (sshpam_err == PAM_SUCCESS && authctxt->valid) {
1364313c6c94Schristos 		debug("PAM: password authentication accepted for %.100s",
1365313c6c94Schristos 		    authctxt->user);
1366313c6c94Schristos 		return 1;
1367313c6c94Schristos 	} else {
1368313c6c94Schristos 		debug("PAM: password authentication failed for %.100s: %s",
1369313c6c94Schristos 		    authctxt->valid ? authctxt->user : "an illegal user",
1370313c6c94Schristos 		    pam_strerror(sshpam_handle, sshpam_err));
1371313c6c94Schristos 		return 0;
1372313c6c94Schristos 	}
1373313c6c94Schristos }
1374e1a26310Schristos 
1375e1a26310Schristos int
sshpam_get_maxtries_reached(void)1376e1a26310Schristos sshpam_get_maxtries_reached(void)
1377e1a26310Schristos {
1378e1a26310Schristos 	return sshpam_maxtries_reached;
1379e1a26310Schristos }
1380e1a26310Schristos 
1381e1a26310Schristos void
sshpam_set_maxtries_reached(int reached)1382e1a26310Schristos sshpam_set_maxtries_reached(int reached)
1383e1a26310Schristos {
1384e1a26310Schristos 	if (reached == 0 || sshpam_maxtries_reached)
1385e1a26310Schristos 		return;
1386e1a26310Schristos 	sshpam_maxtries_reached = 1;
1387e1a26310Schristos 	options.password_authentication = 0;
1388e1a26310Schristos 	options.kbd_interactive_authentication = 0;
1389e1a26310Schristos }
1390313c6c94Schristos #endif /* USE_PAM */
1391