1124211Sdes/*-
2124211Sdes * Copyright (c) 2002 Networks Associates Technology, Inc.
3124211Sdes * All rights reserved.
469591Sgreen *
5124211Sdes * This software was developed for the FreeBSD Project by ThinkSec AS and
6124211Sdes * NAI Labs, the Security Research Division of Network Associates, Inc.
7124211Sdes * under DARPA/SPAWAR contract N66001-01-C-8035 ("CBOSS"), as part of the
8124211Sdes * DARPA CHATS research program.
9124211Sdes *
1069591Sgreen * Redistribution and use in source and binary forms, with or without
1169591Sgreen * modification, are permitted provided that the following conditions
1269591Sgreen * are met:
1369591Sgreen * 1. Redistributions of source code must retain the above copyright
1469591Sgreen *    notice, this list of conditions and the following disclaimer.
1569591Sgreen * 2. Redistributions in binary form must reproduce the above copyright
1669591Sgreen *    notice, this list of conditions and the following disclaimer in the
1769591Sgreen *    documentation and/or other materials provided with the distribution.
1869591Sgreen *
19124211Sdes * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
20124211Sdes * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21124211Sdes * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
22124211Sdes * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
23124211Sdes * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
24124211Sdes * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
25124211Sdes * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
26124211Sdes * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
27124211Sdes * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
28124211Sdes * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
29124211Sdes * SUCH DAMAGE.
3069591Sgreen */
31137019Sdes/*
32137019Sdes * Copyright (c) 2003,2004 Damien Miller <djm@mindrot.org>
33137019Sdes * Copyright (c) 2003,2004 Darren Tucker <dtucker@zip.com.au>
34137019Sdes *
35137019Sdes * Permission to use, copy, modify, and distribute this software for any
36137019Sdes * purpose with or without fee is hereby granted, provided that the above
37137019Sdes * copyright notice and this permission notice appear in all copies.
38137019Sdes *
39137019Sdes * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
40137019Sdes * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
41137019Sdes * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
42137019Sdes * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
43137019Sdes * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
44137019Sdes * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
45137019Sdes * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
46137019Sdes */
4769591Sgreen
48225614Sdes/* Based on $FreeBSD: src/crypto/openssh/auth2-pam-freebsd.c,v 1.11 2003/03/31 13:48:18 des Exp $ */
4969591Sgreen#include "includes.h"
5069591Sgreen
51162856Sdes#include <sys/types.h>
52162856Sdes#include <sys/stat.h>
53162856Sdes#include <sys/wait.h>
54162856Sdes
55162856Sdes#include <errno.h>
56162856Sdes#include <signal.h>
57162856Sdes#include <stdarg.h>
58162856Sdes#include <string.h>
59162856Sdes#include <unistd.h>
60162856Sdes
6169591Sgreen#ifdef USE_PAM
62126277Sdes#if defined(HAVE_SECURITY_PAM_APPL_H)
63124211Sdes#include <security/pam_appl.h>
64126277Sdes#elif defined (HAVE_PAM_PAM_APPL_H)
65126277Sdes#include <pam/pam_appl.h>
66126277Sdes#endif
67124211Sdes
68149753Sdes/* OpenGroup RFC86.0 and XSSO specify no "const" on arguments */
69149753Sdes#ifdef PAM_SUN_CODEBASE
70149753Sdes# define sshpam_const		/* Solaris, HP-UX, AIX */
71149753Sdes#else
72149753Sdes# define sshpam_const	const	/* LinuxPAM, OpenPAM */
73149753Sdes#endif
74149753Sdes
75162856Sdes/* Ambiguity in spec: is it an array of pointers or a pointer to an array? */
76162856Sdes#ifdef PAM_SUN_CODEBASE
77162856Sdes# define PAM_MSG_MEMBER(msg, n, member) ((*(msg))[(n)].member)
78162856Sdes#else
79162856Sdes# define PAM_MSG_MEMBER(msg, n, member) ((msg)[(n)]->member)
80162856Sdes#endif
81162856Sdes
82162856Sdes#include "xmalloc.h"
83162856Sdes#include "buffer.h"
84162856Sdes#include "key.h"
85162856Sdes#include "hostfile.h"
8698941Sdes#include "auth.h"
8798941Sdes#include "auth-pam.h"
8898941Sdes#include "canohost.h"
89124211Sdes#include "log.h"
90124211Sdes#include "msg.h"
91124211Sdes#include "packet.h"
92137019Sdes#include "misc.h"
93124211Sdes#include "servconf.h"
94124211Sdes#include "ssh2.h"
95124211Sdes#include "auth-options.h"
96162856Sdes#ifdef GSSAPI
97162856Sdes#include "ssh-gss.h"
98162856Sdes#endif
99162856Sdes#include "monitor_wrap.h"
10069591Sgreen
101124211Sdesextern ServerOptions options;
102126277Sdesextern Buffer loginmsg;
103126277Sdesextern int compat20;
104128460Sdesextern u_int utmp_len;
10569591Sgreen
106147005Sdes/* so we don't silently change behaviour */
107124211Sdes#ifdef USE_POSIX_THREADS
108147005Sdes# error "USE_POSIX_THREADS replaced by UNSUPPORTED_POSIX_THREADS_HACK"
109147005Sdes#endif
110147005Sdes
111147005Sdes/*
112147005Sdes * Formerly known as USE_POSIX_THREADS, using this is completely unsupported
113147005Sdes * and generally a bad idea.  Use at own risk and do not expect support if
114147005Sdes * this breaks.
115147005Sdes */
116147005Sdes#ifdef UNSUPPORTED_POSIX_THREADS_HACK
117124211Sdes#include <pthread.h>
118124211Sdes/*
119126277Sdes * Avoid namespace clash when *not* using pthreads for systems *with*
120126277Sdes * pthreads, which unconditionally define pthread_t via sys/types.h
121124211Sdes * (e.g. Linux)
122124211Sdes */
123126277Sdestypedef pthread_t sp_pthread_t;
124124211Sdes#else
125126277Sdestypedef pid_t sp_pthread_t;
126126277Sdes#endif
127126277Sdes
128126277Sdesstruct pam_ctxt {
129126277Sdes	sp_pthread_t	 pam_thread;
130126277Sdes	int		 pam_psock;
131126277Sdes	int		 pam_csock;
132126277Sdes	int		 pam_done;
133126277Sdes};
134126277Sdes
135126277Sdesstatic void sshpam_free_ctx(void *);
136126277Sdesstatic struct pam_ctxt *cleanup_ctxt;
137126277Sdes
138147005Sdes#ifndef UNSUPPORTED_POSIX_THREADS_HACK
139124211Sdes/*
140124211Sdes * Simulate threads with processes.
141124211Sdes */
142106130Sdes
143126277Sdesstatic int sshpam_thread_status = -1;
144126277Sdesstatic mysig_t sshpam_oldsig;
145126277Sdes
146149753Sdesstatic void
147126277Sdessshpam_sigchld_handler(int sig)
148126277Sdes{
149137019Sdes	signal(SIGCHLD, SIG_DFL);
150126277Sdes	if (cleanup_ctxt == NULL)
151126277Sdes		return;	/* handler called after PAM cleanup, shouldn't happen */
152137019Sdes	if (waitpid(cleanup_ctxt->pam_thread, &sshpam_thread_status, WNOHANG)
153149753Sdes	    <= 0) {
154137019Sdes		/* PAM thread has not exitted, privsep slave must have */
155137019Sdes		kill(cleanup_ctxt->pam_thread, SIGTERM);
156137019Sdes		if (waitpid(cleanup_ctxt->pam_thread, &sshpam_thread_status, 0)
157137019Sdes		    <= 0)
158137019Sdes			return; /* could not wait */
159137019Sdes	}
160126277Sdes	if (WIFSIGNALED(sshpam_thread_status) &&
161126277Sdes	    WTERMSIG(sshpam_thread_status) == SIGTERM)
162126277Sdes		return;	/* terminated by pthread_cancel */
163126277Sdes	if (!WIFEXITED(sshpam_thread_status))
164181111Sdes		sigdie("PAM: authentication thread exited unexpectedly");
165126277Sdes	if (WEXITSTATUS(sshpam_thread_status) != 0)
166181111Sdes		sigdie("PAM: authentication thread exited uncleanly");
167126277Sdes}
168126277Sdes
169162856Sdes/* ARGSUSED */
170124211Sdesstatic void
171162856Sdespthread_exit(void *value)
172124211Sdes{
173124211Sdes	_exit(0);
174124211Sdes}
17569591Sgreen
176162856Sdes/* ARGSUSED */
177124211Sdesstatic int
178162856Sdespthread_create(sp_pthread_t *thread, const void *attr,
179124211Sdes    void *(*thread_start)(void *), void *arg)
180124211Sdes{
181124211Sdes	pid_t pid;
182149753Sdes	struct pam_ctxt *ctx = arg;
18369591Sgreen
184128460Sdes	sshpam_thread_status = -1;
185124211Sdes	switch ((pid = fork())) {
186124211Sdes	case -1:
187124211Sdes		error("fork(): %s", strerror(errno));
188124211Sdes		return (-1);
189124211Sdes	case 0:
190149753Sdes		close(ctx->pam_psock);
191149753Sdes		ctx->pam_psock = -1;
192124211Sdes		thread_start(arg);
193124211Sdes		_exit(1);
194124211Sdes	default:
195124211Sdes		*thread = pid;
196149753Sdes		close(ctx->pam_csock);
197149753Sdes		ctx->pam_csock = -1;
198126277Sdes		sshpam_oldsig = signal(SIGCHLD, sshpam_sigchld_handler);
199124211Sdes		return (0);
200124211Sdes	}
201124211Sdes}
20269591Sgreen
203124211Sdesstatic int
204124211Sdespthread_cancel(sp_pthread_t thread)
20576394Salfred{
206126277Sdes	signal(SIGCHLD, sshpam_oldsig);
207124211Sdes	return (kill(thread, SIGTERM));
20876394Salfred}
20976394Salfred
210162856Sdes/* ARGSUSED */
211124211Sdesstatic int
212162856Sdespthread_join(sp_pthread_t thread, void **value)
21398941Sdes{
214124211Sdes	int status;
215124211Sdes
216126277Sdes	if (sshpam_thread_status != -1)
217126277Sdes		return (sshpam_thread_status);
218126277Sdes	signal(SIGCHLD, sshpam_oldsig);
219124211Sdes	waitpid(thread, &status, 0);
220124211Sdes	return (status);
22198941Sdes}
222124211Sdes#endif
22398941Sdes
224124211Sdes
225124211Sdesstatic pam_handle_t *sshpam_handle = NULL;
226124211Sdesstatic int sshpam_err = 0;
227124211Sdesstatic int sshpam_authenticated = 0;
228124211Sdesstatic int sshpam_session_open = 0;
229124211Sdesstatic int sshpam_cred_established = 0;
230126277Sdesstatic int sshpam_account_status = -1;
231126277Sdesstatic char **sshpam_env = NULL;
232128460Sdesstatic Authctxt *sshpam_authctxt = NULL;
233137019Sdesstatic const char *sshpam_password = NULL;
234147005Sdesstatic char badpw[] = "\b\n\r\177INCORRECT";
235124211Sdes
236126277Sdes/* Some PAM implementations don't implement this */
237126277Sdes#ifndef HAVE_PAM_GETENVLIST
238126277Sdesstatic char **
239126277Sdespam_getenvlist(pam_handle_t *pamh)
240126277Sdes{
241126277Sdes	/*
242126277Sdes	 * XXX - If necessary, we can still support envrionment passing
243126277Sdes	 * for platforms without pam_getenvlist by searching for known
244126277Sdes	 * env vars (e.g. KRB5CCNAME) from the PAM environment.
245126277Sdes	 */
246126277Sdes	 return NULL;
247126277Sdes}
248126277Sdes#endif
249124211Sdes
250137019Sdes/*
251137019Sdes * Some platforms, notably Solaris, do not enforce password complexity
252137019Sdes * rules during pam_chauthtok() if the real uid of the calling process
253137019Sdes * is 0, on the assumption that it's being called by "passwd" run by root.
254137019Sdes * This wraps pam_chauthtok and sets/restore the real uid so PAM will do
255137019Sdes * the right thing.
256137019Sdes */
257137019Sdes#ifdef SSHPAM_CHAUTHTOK_NEEDS_RUID
258137019Sdesstatic int
259137019Sdessshpam_chauthtok_ruid(pam_handle_t *pamh, int flags)
260137019Sdes{
261137019Sdes	int result;
262137019Sdes
263137019Sdes	if (sshpam_authctxt == NULL)
264137019Sdes		fatal("PAM: sshpam_authctxt not initialized");
265137019Sdes	if (setreuid(sshpam_authctxt->pw->pw_uid, -1) == -1)
266137019Sdes		fatal("%s: setreuid failed: %s", __func__, strerror(errno));
267137019Sdes	result = pam_chauthtok(pamh, flags);
268137019Sdes	if (setreuid(0, -1) == -1)
269137019Sdes		fatal("%s: setreuid failed: %s", __func__, strerror(errno));
270137019Sdes	return result;
271137019Sdes}
272137019Sdes# define pam_chauthtok(a,b)	(sshpam_chauthtok_ruid((a), (b)))
273137019Sdes#endif
274137019Sdes
275126277Sdesvoid
276137019Sdessshpam_password_change_required(int reqd)
277126277Sdes{
278126277Sdes	debug3("%s %d", __func__, reqd);
279128460Sdes	if (sshpam_authctxt == NULL)
280128460Sdes		fatal("%s: PAM authctxt not initialized", __func__);
281128460Sdes	sshpam_authctxt->force_pwchange = reqd;
282126277Sdes	if (reqd) {
283126277Sdes		no_port_forwarding_flag |= 2;
284126277Sdes		no_agent_forwarding_flag |= 2;
285126277Sdes		no_x11_forwarding_flag |= 2;
286126277Sdes	} else {
287126277Sdes		no_port_forwarding_flag &= ~2;
288126277Sdes		no_agent_forwarding_flag &= ~2;
289126277Sdes		no_x11_forwarding_flag &= ~2;
290126277Sdes	}
291126277Sdes}
292124211Sdes
293126277Sdes/* Import regular and PAM environment from subprocess */
294126277Sdesstatic void
295126277Sdesimport_environments(Buffer *b)
296126277Sdes{
297126277Sdes	char *env;
298126277Sdes	u_int i, num_env;
299126277Sdes	int err;
300126277Sdes
301126277Sdes	debug3("PAM: %s entering", __func__);
302126277Sdes
303147005Sdes#ifndef UNSUPPORTED_POSIX_THREADS_HACK
304126277Sdes	/* Import variables set by do_pam_account */
305126277Sdes	sshpam_account_status = buffer_get_int(b);
306137019Sdes	sshpam_password_change_required(buffer_get_int(b));
307126277Sdes
308126277Sdes	/* Import environment from subprocess */
309126277Sdes	num_env = buffer_get_int(b);
310162856Sdes	if (num_env > 1024)
311162856Sdes		fatal("%s: received %u environment variables, expected <= 1024",
312162856Sdes		    __func__, num_env);
313162856Sdes	sshpam_env = xcalloc(num_env + 1, sizeof(*sshpam_env));
314126277Sdes	debug3("PAM: num env strings %d", num_env);
315126277Sdes	for(i = 0; i < num_env; i++)
316126277Sdes		sshpam_env[i] = buffer_get_string(b, NULL);
317126277Sdes
318126277Sdes	sshpam_env[num_env] = NULL;
319126277Sdes
320126277Sdes	/* Import PAM environment from subprocess */
321126277Sdes	num_env = buffer_get_int(b);
322126277Sdes	debug("PAM: num PAM env strings %d", num_env);
323126277Sdes	for(i = 0; i < num_env; i++) {
324126277Sdes		env = buffer_get_string(b, NULL);
325126277Sdes
326126277Sdes#ifdef HAVE_PAM_PUTENV
327126277Sdes		/* Errors are not fatal here */
328126277Sdes		if ((err = pam_putenv(sshpam_handle, env)) != PAM_SUCCESS) {
329126277Sdes			error("PAM: pam_putenv: %s",
330126277Sdes			    pam_strerror(sshpam_handle, sshpam_err));
331126277Sdes		}
332126277Sdes#endif
333126277Sdes	}
334128460Sdes#endif
335126277Sdes}
336126277Sdes
33776394Salfred/*
338124211Sdes * Conversation function for authentication thread.
33969591Sgreen */
340124211Sdesstatic int
341149753Sdessshpam_thread_conv(int n, sshpam_const struct pam_message **msg,
342124211Sdes    struct pam_response **resp, void *data)
34369591Sgreen{
344124211Sdes	Buffer buffer;
345124211Sdes	struct pam_ctxt *ctxt;
34669591Sgreen	struct pam_response *reply;
347124211Sdes	int i;
34869591Sgreen
349126277Sdes	debug3("PAM: %s entering, %d messages", __func__, n);
350124211Sdes	*resp = NULL;
35169591Sgreen
352137019Sdes	if (data == NULL) {
353137019Sdes		error("PAM: conversation function passed a null context");
354137019Sdes		return (PAM_CONV_ERR);
355137019Sdes	}
356124211Sdes	ctxt = data;
357124211Sdes	if (n <= 0 || n > PAM_MAX_NUM_MSG)
358124211Sdes		return (PAM_CONV_ERR);
359124211Sdes
360162856Sdes	if ((reply = calloc(n, sizeof(*reply))) == NULL)
361124211Sdes		return (PAM_CONV_ERR);
362124211Sdes
363124211Sdes	buffer_init(&buffer);
364124211Sdes	for (i = 0; i < n; ++i) {
365124211Sdes		switch (PAM_MSG_MEMBER(msg, i, msg_style)) {
366124211Sdes		case PAM_PROMPT_ECHO_OFF:
367126277Sdes			buffer_put_cstring(&buffer,
368124211Sdes			    PAM_MSG_MEMBER(msg, i, msg));
369126277Sdes			if (ssh_msg_send(ctxt->pam_csock,
370126277Sdes			    PAM_MSG_MEMBER(msg, i, msg_style), &buffer) == -1)
371126277Sdes				goto fail;
372126277Sdes			if (ssh_msg_recv(ctxt->pam_csock, &buffer) == -1)
373126277Sdes				goto fail;
374124211Sdes			if (buffer_get_char(&buffer) != PAM_AUTHTOK)
375124211Sdes				goto fail;
376124211Sdes			reply[i].resp = buffer_get_string(&buffer, NULL);
377124211Sdes			break;
378124211Sdes		case PAM_PROMPT_ECHO_ON:
379126277Sdes			buffer_put_cstring(&buffer,
380124211Sdes			    PAM_MSG_MEMBER(msg, i, msg));
381126277Sdes			if (ssh_msg_send(ctxt->pam_csock,
382126277Sdes			    PAM_MSG_MEMBER(msg, i, msg_style), &buffer) == -1)
383126277Sdes				goto fail;
384126277Sdes			if (ssh_msg_recv(ctxt->pam_csock, &buffer) == -1)
385126277Sdes				goto fail;
386124211Sdes			if (buffer_get_char(&buffer) != PAM_AUTHTOK)
387124211Sdes				goto fail;
388124211Sdes			reply[i].resp = buffer_get_string(&buffer, NULL);
389124211Sdes			break;
390124211Sdes		case PAM_ERROR_MSG:
391126277Sdes			buffer_put_cstring(&buffer,
392124211Sdes			    PAM_MSG_MEMBER(msg, i, msg));
393126277Sdes			if (ssh_msg_send(ctxt->pam_csock,
394126277Sdes			    PAM_MSG_MEMBER(msg, i, msg_style), &buffer) == -1)
395126277Sdes				goto fail;
396124211Sdes			break;
397124211Sdes		case PAM_TEXT_INFO:
398126277Sdes			buffer_put_cstring(&buffer,
399124211Sdes			    PAM_MSG_MEMBER(msg, i, msg));
400126277Sdes			if (ssh_msg_send(ctxt->pam_csock,
401126277Sdes			    PAM_MSG_MEMBER(msg, i, msg_style), &buffer) == -1)
402126277Sdes				goto fail;
403124211Sdes			break;
404124211Sdes		default:
405124211Sdes			goto fail;
40669591Sgreen		}
407124211Sdes		buffer_clear(&buffer);
40869591Sgreen	}
409124211Sdes	buffer_free(&buffer);
41069591Sgreen	*resp = reply;
411124211Sdes	return (PAM_SUCCESS);
41269591Sgreen
413124211Sdes fail:
414124211Sdes	for(i = 0; i < n; i++) {
415263970Sdes		free(reply[i].resp);
416124211Sdes	}
417263970Sdes	free(reply);
418124211Sdes	buffer_free(&buffer);
419124211Sdes	return (PAM_CONV_ERR);
42069591Sgreen}
42169591Sgreen
422124211Sdes/*
423124211Sdes * Authentication thread.
424124211Sdes */
425124211Sdesstatic void *
426124211Sdessshpam_thread(void *ctxtp)
42769591Sgreen{
428124211Sdes	struct pam_ctxt *ctxt = ctxtp;
429124211Sdes	Buffer buffer;
430124211Sdes	struct pam_conv sshpam_conv;
431137019Sdes	int flags = (options.permit_empty_passwd == 0 ?
432137019Sdes	    PAM_DISALLOW_NULL_AUTHTOK : 0);
433147005Sdes#ifndef UNSUPPORTED_POSIX_THREADS_HACK
434126277Sdes	extern char **environ;
435126277Sdes	char **env_from_pam;
436126277Sdes	u_int i;
437124211Sdes	const char *pam_user;
438149753Sdes	const char **ptr_pam_user = &pam_user;
439162856Sdes	char *tz = getenv("TZ");
44069591Sgreen
441263970Sdes	sshpam_err = pam_get_item(sshpam_handle, PAM_USER,
442149753Sdes	    (sshpam_const void **)ptr_pam_user);
443263970Sdes	if (sshpam_err != PAM_SUCCESS)
444263970Sdes		goto auth_fail;
445162856Sdes
446126277Sdes	environ[0] = NULL;
447162856Sdes	if (tz != NULL)
448162856Sdes		if (setenv("TZ", tz, 1) == -1)
449162856Sdes			error("PAM: could not set TZ environment: %s",
450162856Sdes			    strerror(errno));
451137019Sdes
452137019Sdes	if (sshpam_authctxt != NULL) {
453137019Sdes		setproctitle("%s [pam]",
454137019Sdes		    sshpam_authctxt->valid ? pam_user : "unknown");
455137019Sdes	}
456124211Sdes#endif
45769591Sgreen
458124211Sdes	sshpam_conv.conv = sshpam_thread_conv;
459124211Sdes	sshpam_conv.appdata_ptr = ctxt;
46069591Sgreen
461128460Sdes	if (sshpam_authctxt == NULL)
462128460Sdes		fatal("%s: PAM authctxt not initialized", __func__);
463128460Sdes
464124211Sdes	buffer_init(&buffer);
465124211Sdes	sshpam_err = pam_set_item(sshpam_handle, PAM_CONV,
466124211Sdes	    (const void *)&sshpam_conv);
467124211Sdes	if (sshpam_err != PAM_SUCCESS)
468124211Sdes		goto auth_fail;
469137019Sdes	sshpam_err = pam_authenticate(sshpam_handle, flags);
470124211Sdes	if (sshpam_err != PAM_SUCCESS)
471124211Sdes		goto auth_fail;
472126277Sdes
473126277Sdes	if (compat20) {
474162856Sdes		if (!do_pam_account()) {
475162856Sdes			sshpam_err = PAM_ACCT_EXPIRED;
476126277Sdes			goto auth_fail;
477162856Sdes		}
478128460Sdes		if (sshpam_authctxt->force_pwchange) {
479126277Sdes			sshpam_err = pam_chauthtok(sshpam_handle,
480126277Sdes			    PAM_CHANGE_EXPIRED_AUTHTOK);
481126277Sdes			if (sshpam_err != PAM_SUCCESS)
482126277Sdes				goto auth_fail;
483137019Sdes			sshpam_password_change_required(0);
484126277Sdes		}
485126277Sdes	}
486126277Sdes
487124211Sdes	buffer_put_cstring(&buffer, "OK");
488126277Sdes
489147005Sdes#ifndef UNSUPPORTED_POSIX_THREADS_HACK
490126277Sdes	/* Export variables set by do_pam_account */
491126277Sdes	buffer_put_int(&buffer, sshpam_account_status);
492128460Sdes	buffer_put_int(&buffer, sshpam_authctxt->force_pwchange);
493126277Sdes
494126277Sdes	/* Export any environment strings set in child */
495126277Sdes	for(i = 0; environ[i] != NULL; i++)
496126277Sdes		; /* Count */
497126277Sdes	buffer_put_int(&buffer, i);
498126277Sdes	for(i = 0; environ[i] != NULL; i++)
499126277Sdes		buffer_put_cstring(&buffer, environ[i]);
500126277Sdes
501126277Sdes	/* Export any environment strings set by PAM in child */
502126277Sdes	env_from_pam = pam_getenvlist(sshpam_handle);
503126277Sdes	for(i = 0; env_from_pam != NULL && env_from_pam[i] != NULL; i++)
504126277Sdes		; /* Count */
505126277Sdes	buffer_put_int(&buffer, i);
506126277Sdes	for(i = 0; env_from_pam != NULL && env_from_pam[i] != NULL; i++)
507126277Sdes		buffer_put_cstring(&buffer, env_from_pam[i]);
508147005Sdes#endif /* UNSUPPORTED_POSIX_THREADS_HACK */
509126277Sdes
510126277Sdes	/* XXX - can't do much about an error here */
511124211Sdes	ssh_msg_send(ctxt->pam_csock, sshpam_err, &buffer);
512124211Sdes	buffer_free(&buffer);
513124211Sdes	pthread_exit(NULL);
514124211Sdes
515124211Sdes auth_fail:
516124211Sdes	buffer_put_cstring(&buffer,
517124211Sdes	    pam_strerror(sshpam_handle, sshpam_err));
518126277Sdes	/* XXX - can't do much about an error here */
519162856Sdes	if (sshpam_err == PAM_ACCT_EXPIRED)
520162856Sdes		ssh_msg_send(ctxt->pam_csock, PAM_ACCT_EXPIRED, &buffer);
521162856Sdes	else
522162856Sdes		ssh_msg_send(ctxt->pam_csock, PAM_AUTH_ERR, &buffer);
523124211Sdes	buffer_free(&buffer);
524124211Sdes	pthread_exit(NULL);
525126277Sdes
526124211Sdes	return (NULL); /* Avoid warning for non-pthread case */
52769591Sgreen}
52869591Sgreen
529126277Sdesvoid
530126277Sdessshpam_thread_cleanup(void)
53169591Sgreen{
532126277Sdes	struct pam_ctxt *ctxt = cleanup_ctxt;
53369591Sgreen
534126277Sdes	debug3("PAM: %s entering", __func__);
535126277Sdes	if (ctxt != NULL && ctxt->pam_thread != 0) {
536126277Sdes		pthread_cancel(ctxt->pam_thread);
537126277Sdes		pthread_join(ctxt->pam_thread, NULL);
538126277Sdes		close(ctxt->pam_psock);
539126277Sdes		close(ctxt->pam_csock);
540126277Sdes		memset(ctxt, 0, sizeof(*ctxt));
541126277Sdes		cleanup_ctxt = NULL;
542126277Sdes	}
543124211Sdes}
54476394Salfred
545124211Sdesstatic int
546149753Sdessshpam_null_conv(int n, sshpam_const struct pam_message **msg,
547124211Sdes    struct pam_response **resp, void *data)
548124211Sdes{
549126277Sdes	debug3("PAM: %s entering, %d messages", __func__, n);
550124211Sdes	return (PAM_CONV_ERR);
551124211Sdes}
55298941Sdes
553124211Sdesstatic struct pam_conv null_conv = { sshpam_null_conv, NULL };
554124211Sdes
555147005Sdesstatic int
556149753Sdessshpam_store_conv(int n, sshpam_const struct pam_message **msg,
557147005Sdes    struct pam_response **resp, void *data)
558147005Sdes{
559147005Sdes	struct pam_response *reply;
560147005Sdes	int i;
561147005Sdes	size_t len;
562147005Sdes
563147005Sdes	debug3("PAM: %s called with %d messages", __func__, n);
564147005Sdes	*resp = NULL;
565147005Sdes
566147005Sdes	if (n <= 0 || n > PAM_MAX_NUM_MSG)
567147005Sdes		return (PAM_CONV_ERR);
568147005Sdes
569162856Sdes	if ((reply = calloc(n, sizeof(*reply))) == NULL)
570147005Sdes		return (PAM_CONV_ERR);
571147005Sdes
572147005Sdes	for (i = 0; i < n; ++i) {
573147005Sdes		switch (PAM_MSG_MEMBER(msg, i, msg_style)) {
574147005Sdes		case PAM_ERROR_MSG:
575147005Sdes		case PAM_TEXT_INFO:
576147005Sdes			len = strlen(PAM_MSG_MEMBER(msg, i, msg));
577147005Sdes			buffer_append(&loginmsg, PAM_MSG_MEMBER(msg, i, msg), len);
578147005Sdes			buffer_append(&loginmsg, "\n", 1 );
579147005Sdes			reply[i].resp_retcode = PAM_SUCCESS;
580147005Sdes			break;
581147005Sdes		default:
582147005Sdes			goto fail;
583147005Sdes		}
584147005Sdes	}
585147005Sdes	*resp = reply;
586147005Sdes	return (PAM_SUCCESS);
587147005Sdes
588147005Sdes fail:
589147005Sdes	for(i = 0; i < n; i++) {
590263970Sdes		free(reply[i].resp);
591147005Sdes	}
592263970Sdes	free(reply);
593147005Sdes	return (PAM_CONV_ERR);
594147005Sdes}
595147005Sdes
596147005Sdesstatic struct pam_conv store_conv = { sshpam_store_conv, NULL };
597147005Sdes
598126277Sdesvoid
599126277Sdessshpam_cleanup(void)
600124211Sdes{
601181111Sdes	if (sshpam_handle == NULL || (use_privsep && !mm_is_monitor()))
602181111Sdes		return;
603124211Sdes	debug("PAM: cleanup");
604124211Sdes	pam_set_item(sshpam_handle, PAM_CONV, (const void *)&null_conv);
605197679Sdes	if (sshpam_session_open) {
606197679Sdes		debug("PAM: closing session");
607197679Sdes		pam_close_session(sshpam_handle, PAM_SILENT);
608197679Sdes		sshpam_session_open = 0;
609197679Sdes	}
610124211Sdes	if (sshpam_cred_established) {
611181111Sdes		debug("PAM: deleting credentials");
612124211Sdes		pam_setcred(sshpam_handle, PAM_DELETE_CRED);
613124211Sdes		sshpam_cred_established = 0;
61469591Sgreen	}
615126277Sdes	sshpam_authenticated = 0;
616124211Sdes	pam_end(sshpam_handle, sshpam_err);
617124211Sdes	sshpam_handle = NULL;
61869591Sgreen}
61969591Sgreen
620124211Sdesstatic int
621128460Sdessshpam_init(Authctxt *authctxt)
62269591Sgreen{
623124211Sdes	extern char *__progname;
624128460Sdes	const char *pam_rhost, *pam_user, *user = authctxt->user;
625149753Sdes	const char **ptr_pam_user = &pam_user;
62676394Salfred
627124211Sdes	if (sshpam_handle != NULL) {
628124211Sdes		/* We already have a PAM context; check if the user matches */
629124211Sdes		sshpam_err = pam_get_item(sshpam_handle,
630149753Sdes		    PAM_USER, (sshpam_const void **)ptr_pam_user);
631124211Sdes		if (sshpam_err == PAM_SUCCESS && strcmp(user, pam_user) == 0)
632124211Sdes			return (0);
633124211Sdes		pam_end(sshpam_handle, sshpam_err);
634124211Sdes		sshpam_handle = NULL;
63569591Sgreen	}
636124211Sdes	debug("PAM: initializing for \"%s\"", user);
637124211Sdes	sshpam_err =
638147005Sdes	    pam_start(SSHD_PAM_SERVICE, user, &store_conv, &sshpam_handle);
639128460Sdes	sshpam_authctxt = authctxt;
640128460Sdes
641124211Sdes	if (sshpam_err != PAM_SUCCESS) {
642124211Sdes		pam_end(sshpam_handle, sshpam_err);
643124211Sdes		sshpam_handle = NULL;
644124211Sdes		return (-1);
645124211Sdes	}
646124211Sdes	pam_rhost = get_remote_name_or_ip(utmp_len, options.use_dns);
647124211Sdes	debug("PAM: setting PAM_RHOST to \"%s\"", pam_rhost);
648124211Sdes	sshpam_err = pam_set_item(sshpam_handle, PAM_RHOST, pam_rhost);
649124211Sdes	if (sshpam_err != PAM_SUCCESS) {
650124211Sdes		pam_end(sshpam_handle, sshpam_err);
651124211Sdes		sshpam_handle = NULL;
652124211Sdes		return (-1);
653124211Sdes	}
654124211Sdes#ifdef PAM_TTY_KLUDGE
655126277Sdes	/*
656126277Sdes	 * Some silly PAM modules (e.g. pam_time) require a TTY to operate.
657126277Sdes	 * sshd doesn't set the tty until too late in the auth process and
658124211Sdes	 * may not even set one (for tty-less connections)
659126277Sdes	 */
660124211Sdes	debug("PAM: setting PAM_TTY to \"ssh\"");
661124211Sdes	sshpam_err = pam_set_item(sshpam_handle, PAM_TTY, "ssh");
662124211Sdes	if (sshpam_err != PAM_SUCCESS) {
663124211Sdes		pam_end(sshpam_handle, sshpam_err);
664124211Sdes		sshpam_handle = NULL;
665124211Sdes		return (-1);
666124211Sdes	}
66798941Sdes#endif
668124211Sdes	return (0);
66969591Sgreen}
67069591Sgreen
671124211Sdesstatic void *
672124211Sdessshpam_init_ctx(Authctxt *authctxt)
67369591Sgreen{
674124211Sdes	struct pam_ctxt *ctxt;
675124211Sdes	int socks[2];
67669591Sgreen
677126277Sdes	debug3("PAM: %s entering", __func__);
678162856Sdes	/*
679162856Sdes	 * Refuse to start if we don't have PAM enabled or do_pam_account
680162856Sdes	 * has previously failed.
681162856Sdes	 */
682162856Sdes	if (!options.use_pam || sshpam_account_status == 0)
683124211Sdes		return NULL;
68476394Salfred
685124211Sdes	/* Initialize PAM */
686128460Sdes	if (sshpam_init(authctxt) == -1) {
687124211Sdes		error("PAM: initialization failed");
688124211Sdes		return (NULL);
68969591Sgreen	}
69069591Sgreen
691181111Sdes	ctxt = xcalloc(1, sizeof *ctxt);
69276394Salfred
693124211Sdes	/* Start the authentication thread */
694124211Sdes	if (socketpair(AF_UNIX, SOCK_STREAM, PF_UNSPEC, socks) == -1) {
695124211Sdes		error("PAM: failed create sockets: %s", strerror(errno));
696263970Sdes		free(ctxt);
697124211Sdes		return (NULL);
698124211Sdes	}
699124211Sdes	ctxt->pam_psock = socks[0];
700124211Sdes	ctxt->pam_csock = socks[1];
701124211Sdes	if (pthread_create(&ctxt->pam_thread, NULL, sshpam_thread, ctxt) == -1) {
702124211Sdes		error("PAM: failed to start authentication thread: %s",
703124211Sdes		    strerror(errno));
704124211Sdes		close(socks[0]);
705124211Sdes		close(socks[1]);
706263970Sdes		free(ctxt);
707124211Sdes		return (NULL);
708124211Sdes	}
709126277Sdes	cleanup_ctxt = ctxt;
710124211Sdes	return (ctxt);
71169591Sgreen}
71269591Sgreen
713124211Sdesstatic int
714124211Sdessshpam_query(void *ctx, char **name, char **info,
715124211Sdes    u_int *num, char ***prompts, u_int **echo_on)
71669591Sgreen{
717124211Sdes	Buffer buffer;
718124211Sdes	struct pam_ctxt *ctxt = ctx;
719124211Sdes	size_t plen;
720124211Sdes	u_char type;
721124211Sdes	char *msg;
722147005Sdes	size_t len, mlen;
72376394Salfred
724126277Sdes	debug3("PAM: %s entering", __func__);
725124211Sdes	buffer_init(&buffer);
726124211Sdes	*name = xstrdup("");
727124211Sdes	*info = xstrdup("");
728124211Sdes	*prompts = xmalloc(sizeof(char *));
729124211Sdes	**prompts = NULL;
730124211Sdes	plen = 0;
731124211Sdes	*echo_on = xmalloc(sizeof(u_int));
732124211Sdes	while (ssh_msg_recv(ctxt->pam_psock, &buffer) == 0) {
733124211Sdes		type = buffer_get_char(&buffer);
734124211Sdes		msg = buffer_get_string(&buffer, NULL);
735147005Sdes		mlen = strlen(msg);
736124211Sdes		switch (type) {
737124211Sdes		case PAM_PROMPT_ECHO_ON:
738124211Sdes		case PAM_PROMPT_ECHO_OFF:
739124211Sdes			*num = 1;
740147005Sdes			len = plen + mlen + 1;
741162856Sdes			**prompts = xrealloc(**prompts, 1, len);
742147005Sdes			strlcpy(**prompts + plen, msg, len - plen);
743147005Sdes			plen += mlen;
744124211Sdes			**echo_on = (type == PAM_PROMPT_ECHO_ON);
745263970Sdes			free(msg);
746124211Sdes			return (0);
747124211Sdes		case PAM_ERROR_MSG:
748124211Sdes		case PAM_TEXT_INFO:
749124211Sdes			/* accumulate messages */
750147005Sdes			len = plen + mlen + 2;
751162856Sdes			**prompts = xrealloc(**prompts, 1, len);
752147005Sdes			strlcpy(**prompts + plen, msg, len - plen);
753147005Sdes			plen += mlen;
754147005Sdes			strlcat(**prompts + plen, "\n", len - plen);
755147005Sdes			plen++;
756263970Sdes			free(msg);
757124211Sdes			break;
758162856Sdes		case PAM_ACCT_EXPIRED:
759162856Sdes			sshpam_account_status = 0;
760162856Sdes			/* FALLTHROUGH */
761157019Sdes		case PAM_AUTH_ERR:
762162856Sdes			debug3("PAM: %s", pam_strerror(sshpam_handle, type));
763157019Sdes			if (**prompts != NULL && strlen(**prompts) != 0) {
764157019Sdes				*info = **prompts;
765157019Sdes				**prompts = NULL;
766157019Sdes				*num = 0;
767157019Sdes				**echo_on = 0;
768157019Sdes				ctxt->pam_done = -1;
769263970Sdes				free(msg);
770157019Sdes				return 0;
771157019Sdes			}
772157019Sdes			/* FALLTHROUGH */
773124211Sdes		case PAM_SUCCESS:
774124211Sdes			if (**prompts != NULL) {
775124211Sdes				/* drain any accumulated messages */
776126277Sdes				debug("PAM: %s", **prompts);
777126277Sdes				buffer_append(&loginmsg, **prompts,
778126277Sdes				    strlen(**prompts));
779263970Sdes				free(**prompts);
780124211Sdes				**prompts = NULL;
781124211Sdes			}
782124211Sdes			if (type == PAM_SUCCESS) {
783147005Sdes				if (!sshpam_authctxt->valid ||
784147005Sdes				    (sshpam_authctxt->pw->pw_uid == 0 &&
785147005Sdes				    options.permit_root_login != PERMIT_YES))
786147005Sdes					fatal("Internal error: PAM auth "
787147005Sdes					    "succeeded when it should have "
788147005Sdes					    "failed");
789126277Sdes				import_environments(&buffer);
790124211Sdes				*num = 0;
791124211Sdes				**echo_on = 0;
792124211Sdes				ctxt->pam_done = 1;
793263970Sdes				free(msg);
794124211Sdes				return (0);
795124211Sdes			}
796128460Sdes			error("PAM: %s for %s%.100s from %.100s", msg,
797128460Sdes			    sshpam_authctxt->valid ? "" : "illegal user ",
798128460Sdes			    sshpam_authctxt->user,
799128460Sdes			    get_remote_name_or_ip(utmp_len, options.use_dns));
800126277Sdes			/* FALLTHROUGH */
801124211Sdes		default:
802124211Sdes			*num = 0;
803124211Sdes			**echo_on = 0;
804263970Sdes			free(msg);
805124211Sdes			ctxt->pam_done = -1;
806124211Sdes			return (-1);
807124211Sdes		}
808124211Sdes	}
809124211Sdes	return (-1);
810124211Sdes}
81198941Sdes
812124211Sdes/* XXX - see also comment in auth-chall.c:verify_response */
813124211Sdesstatic int
814124211Sdessshpam_respond(void *ctx, u_int num, char **resp)
815124211Sdes{
816124211Sdes	Buffer buffer;
817124211Sdes	struct pam_ctxt *ctxt = ctx;
81898941Sdes
819157019Sdes	debug2("PAM: %s entering, %u responses", __func__, num);
820124211Sdes	switch (ctxt->pam_done) {
821124211Sdes	case 1:
822124211Sdes		sshpam_authenticated = 1;
823124211Sdes		return (0);
824124211Sdes	case 0:
825124211Sdes		break;
826124211Sdes	default:
827124211Sdes		return (-1);
828124211Sdes	}
829124211Sdes	if (num != 1) {
830124211Sdes		error("PAM: expected one response, got %u", num);
831124211Sdes		return (-1);
832124211Sdes	}
833124211Sdes	buffer_init(&buffer);
834147005Sdes	if (sshpam_authctxt->valid &&
835147005Sdes	    (sshpam_authctxt->pw->pw_uid != 0 ||
836149753Sdes	    options.permit_root_login == PERMIT_YES))
837147005Sdes		buffer_put_cstring(&buffer, *resp);
838147005Sdes	else
839147005Sdes		buffer_put_cstring(&buffer, badpw);
840126277Sdes	if (ssh_msg_send(ctxt->pam_psock, PAM_AUTHTOK, &buffer) == -1) {
841126277Sdes		buffer_free(&buffer);
842126277Sdes		return (-1);
843126277Sdes	}
844124211Sdes	buffer_free(&buffer);
845124211Sdes	return (1);
84669591Sgreen}
84769591Sgreen
848124211Sdesstatic void
849124211Sdessshpam_free_ctx(void *ctxtp)
85069591Sgreen{
851124211Sdes	struct pam_ctxt *ctxt = ctxtp;
852124211Sdes
853126277Sdes	debug3("PAM: %s entering", __func__);
854126277Sdes	sshpam_thread_cleanup();
855263970Sdes	free(ctxt);
856124211Sdes	/*
857124211Sdes	 * We don't call sshpam_cleanup() here because we may need the PAM
858124211Sdes	 * handle at a later stage, e.g. when setting up a session.  It's
859124211Sdes	 * still on the cleanup list, so pam_end() *will* be called before
860124211Sdes	 * the server process terminates.
861124211Sdes	 */
86269591Sgreen}
86369591Sgreen
864124211SdesKbdintDevice sshpam_device = {
865124211Sdes	"pam",
866124211Sdes	sshpam_init_ctx,
867124211Sdes	sshpam_query,
868124211Sdes	sshpam_respond,
869124211Sdes	sshpam_free_ctx
870124211Sdes};
871124211Sdes
872124211SdesKbdintDevice mm_sshpam_device = {
873124211Sdes	"pam",
874124211Sdes	mm_sshpam_init_ctx,
875124211Sdes	mm_sshpam_query,
876124211Sdes	mm_sshpam_respond,
877124211Sdes	mm_sshpam_free_ctx
878124211Sdes};
879124211Sdes
88098941Sdes/*
881124211Sdes * This replaces auth-pam.c
88269591Sgreen */
883124211Sdesvoid
884128460Sdesstart_pam(Authctxt *authctxt)
88569591Sgreen{
886124211Sdes	if (!options.use_pam)
887124211Sdes		fatal("PAM: initialisation requested when UsePAM=no");
88869591Sgreen
889128460Sdes	if (sshpam_init(authctxt) == -1)
890124211Sdes		fatal("PAM: initialisation failed");
89169591Sgreen}
89269591Sgreen
893124211Sdesvoid
894124211Sdesfinish_pam(void)
89569591Sgreen{
896126277Sdes	sshpam_cleanup();
89769591Sgreen}
89869591Sgreen
899124211Sdesu_int
900124211Sdesdo_pam_account(void)
90169591Sgreen{
902147005Sdes	debug("%s: called", __func__);
903126277Sdes	if (sshpam_account_status != -1)
904126277Sdes		return (sshpam_account_status);
905126277Sdes
906124211Sdes	sshpam_err = pam_acct_mgmt(sshpam_handle, 0);
907147005Sdes	debug3("PAM: %s pam_acct_mgmt = %d (%s)", __func__, sshpam_err,
908147005Sdes	    pam_strerror(sshpam_handle, sshpam_err));
909149753Sdes
910126277Sdes	if (sshpam_err != PAM_SUCCESS && sshpam_err != PAM_NEW_AUTHTOK_REQD) {
911126277Sdes		sshpam_account_status = 0;
912126277Sdes		return (sshpam_account_status);
913124211Sdes	}
91469591Sgreen
915126277Sdes	if (sshpam_err == PAM_NEW_AUTHTOK_REQD)
916137019Sdes		sshpam_password_change_required(1);
91769591Sgreen
918126277Sdes	sshpam_account_status = 1;
919126277Sdes	return (sshpam_account_status);
920124211Sdes}
92198941Sdes
922124211Sdesvoid
923124211Sdesdo_pam_set_tty(const char *tty)
924124211Sdes{
925124211Sdes	if (tty != NULL) {
926124211Sdes		debug("PAM: setting PAM_TTY to \"%s\"", tty);
927124211Sdes		sshpam_err = pam_set_item(sshpam_handle, PAM_TTY, tty);
928124211Sdes		if (sshpam_err != PAM_SUCCESS)
929124211Sdes			fatal("PAM: failed to set PAM_TTY: %s",
930124211Sdes			    pam_strerror(sshpam_handle, sshpam_err));
931124211Sdes	}
932124211Sdes}
93369591Sgreen
934124211Sdesvoid
935124211Sdesdo_pam_setcred(int init)
936124211Sdes{
937124211Sdes	sshpam_err = pam_set_item(sshpam_handle, PAM_CONV,
938147005Sdes	    (const void *)&store_conv);
939124211Sdes	if (sshpam_err != PAM_SUCCESS)
940124211Sdes		fatal("PAM: failed to set PAM_CONV: %s",
941124211Sdes		    pam_strerror(sshpam_handle, sshpam_err));
942124211Sdes	if (init) {
943124211Sdes		debug("PAM: establishing credentials");
944124211Sdes		sshpam_err = pam_setcred(sshpam_handle, PAM_ESTABLISH_CRED);
945124211Sdes	} else {
946124211Sdes		debug("PAM: reinitializing credentials");
947124211Sdes		sshpam_err = pam_setcred(sshpam_handle, PAM_REINITIALIZE_CRED);
948124211Sdes	}
949124211Sdes	if (sshpam_err == PAM_SUCCESS) {
950124211Sdes		sshpam_cred_established = 1;
951124211Sdes		return;
952124211Sdes	}
953124211Sdes	if (sshpam_authenticated)
954124211Sdes		fatal("PAM: pam_setcred(): %s",
955124211Sdes		    pam_strerror(sshpam_handle, sshpam_err));
956124211Sdes	else
957124211Sdes		debug("PAM: pam_setcred(): %s",
958124211Sdes		    pam_strerror(sshpam_handle, sshpam_err));
95969591Sgreen}
96069591Sgreen
961124211Sdesstatic int
962149753Sdessshpam_tty_conv(int n, sshpam_const struct pam_message **msg,
963124211Sdes    struct pam_response **resp, void *data)
964106130Sdes{
965124211Sdes	char input[PAM_MAX_MSG_SIZE];
966124211Sdes	struct pam_response *reply;
967106130Sdes	int i;
968106130Sdes
969126277Sdes	debug3("PAM: %s called with %d messages", __func__, n);
970126277Sdes
971124211Sdes	*resp = NULL;
972124211Sdes
973126277Sdes	if (n <= 0 || n > PAM_MAX_NUM_MSG || !isatty(STDIN_FILENO))
974124211Sdes		return (PAM_CONV_ERR);
975124211Sdes
976162856Sdes	if ((reply = calloc(n, sizeof(*reply))) == NULL)
977124211Sdes		return (PAM_CONV_ERR);
978124211Sdes
979124211Sdes	for (i = 0; i < n; ++i) {
980124211Sdes		switch (PAM_MSG_MEMBER(msg, i, msg_style)) {
981124211Sdes		case PAM_PROMPT_ECHO_OFF:
982124211Sdes			reply[i].resp =
983126277Sdes			    read_passphrase(PAM_MSG_MEMBER(msg, i, msg),
984124211Sdes			    RP_ALLOW_STDIN);
985124211Sdes			reply[i].resp_retcode = PAM_SUCCESS;
986124211Sdes			break;
987124211Sdes		case PAM_PROMPT_ECHO_ON:
988126277Sdes			fprintf(stderr, "%s\n", PAM_MSG_MEMBER(msg, i, msg));
989181111Sdes			if (fgets(input, sizeof input, stdin) == NULL)
990181111Sdes				input[0] = '\0';
991137019Sdes			if ((reply[i].resp = strdup(input)) == NULL)
992137019Sdes				goto fail;
993124211Sdes			reply[i].resp_retcode = PAM_SUCCESS;
994124211Sdes			break;
995124211Sdes		case PAM_ERROR_MSG:
996124211Sdes		case PAM_TEXT_INFO:
997126277Sdes			fprintf(stderr, "%s\n", PAM_MSG_MEMBER(msg, i, msg));
998124211Sdes			reply[i].resp_retcode = PAM_SUCCESS;
999124211Sdes			break;
1000124211Sdes		default:
1001124211Sdes			goto fail;
1002124211Sdes		}
1003106130Sdes	}
1004124211Sdes	*resp = reply;
1005124211Sdes	return (PAM_SUCCESS);
1006124211Sdes
1007124211Sdes fail:
1008124211Sdes	for(i = 0; i < n; i++) {
1009263970Sdes		free(reply[i].resp);
1010124211Sdes	}
1011263970Sdes	free(reply);
1012124211Sdes	return (PAM_CONV_ERR);
1013106130Sdes}
1014106130Sdes
1015137019Sdesstatic struct pam_conv tty_conv = { sshpam_tty_conv, NULL };
1016126277Sdes
1017124211Sdes/*
1018124211Sdes * XXX this should be done in the authentication phase, but ssh1 doesn't
1019124211Sdes * support that
1020124211Sdes */
1021124211Sdesvoid
1022124211Sdesdo_pam_chauthtok(void)
102369591Sgreen{
1024124211Sdes	if (use_privsep)
1025124211Sdes		fatal("Password expired (unable to change with privsep)");
1026124211Sdes	sshpam_err = pam_set_item(sshpam_handle, PAM_CONV,
1027126277Sdes	    (const void *)&tty_conv);
1028124211Sdes	if (sshpam_err != PAM_SUCCESS)
1029124211Sdes		fatal("PAM: failed to set PAM_CONV: %s",
1030124211Sdes		    pam_strerror(sshpam_handle, sshpam_err));
1031124211Sdes	debug("PAM: changing password");
1032124211Sdes	sshpam_err = pam_chauthtok(sshpam_handle, PAM_CHANGE_EXPIRED_AUTHTOK);
1033124211Sdes	if (sshpam_err != PAM_SUCCESS)
1034124211Sdes		fatal("PAM: pam_chauthtok(): %s",
1035124211Sdes		    pam_strerror(sshpam_handle, sshpam_err));
103669591Sgreen}
103769591Sgreen
1038126277Sdesvoid
1039126277Sdesdo_pam_session(void)
1040126277Sdes{
1041126277Sdes	debug3("PAM: opening session");
1042126277Sdes	sshpam_err = pam_set_item(sshpam_handle, PAM_CONV,
1043126277Sdes	    (const void *)&store_conv);
1044126277Sdes	if (sshpam_err != PAM_SUCCESS)
1045126277Sdes		fatal("PAM: failed to set PAM_CONV: %s",
1046126277Sdes		    pam_strerror(sshpam_handle, sshpam_err));
1047126277Sdes	sshpam_err = pam_open_session(sshpam_handle, 0);
1048147005Sdes	if (sshpam_err == PAM_SUCCESS)
1049147005Sdes		sshpam_session_open = 1;
1050147005Sdes	else {
1051147005Sdes		sshpam_session_open = 0;
1052147005Sdes		disable_forwarding();
1053147005Sdes		error("PAM: pam_open_session(): %s",
1054126277Sdes		    pam_strerror(sshpam_handle, sshpam_err));
1055147005Sdes	}
1056147005Sdes
1057126277Sdes}
1058126277Sdes
1059147005Sdesint
1060147005Sdesis_pam_session_open(void)
1061147005Sdes{
1062147005Sdes	return sshpam_session_open;
1063147005Sdes}
1064147005Sdes
1065126277Sdes/*
1066124211Sdes * Set a PAM environment string. We need to do this so that the session
1067124211Sdes * modules can handle things like Kerberos/GSI credentials that appear
1068124211Sdes * during the ssh authentication process.
1069124211Sdes */
1070124211Sdesint
1071126277Sdesdo_pam_putenv(char *name, char *value)
107269591Sgreen{
1073124211Sdes	int ret = 1;
1074126277Sdes#ifdef HAVE_PAM_PUTENV
1075124211Sdes	char *compound;
1076124211Sdes	size_t len;
107769591Sgreen
1078124211Sdes	len = strlen(name) + strlen(value) + 2;
1079124211Sdes	compound = xmalloc(len);
108069591Sgreen
1081124211Sdes	snprintf(compound, len, "%s=%s", name, value);
1082124211Sdes	ret = pam_putenv(sshpam_handle, compound);
1083263970Sdes	free(compound);
1084124211Sdes#endif
108569591Sgreen
1086124211Sdes	return (ret);
1087124211Sdes}
108869591Sgreen
1089126277Sdeschar **
1090126277Sdesfetch_pam_child_environment(void)
1091124211Sdes{
1092126277Sdes	return sshpam_env;
109369591Sgreen}
109469591Sgreen
1095124211Sdeschar **
1096124211Sdesfetch_pam_environment(void)
1097124211Sdes{
1098124211Sdes	return (pam_getenvlist(sshpam_handle));
1099124211Sdes}
1100124211Sdes
1101124211Sdesvoid
1102124211Sdesfree_pam_environment(char **env)
1103124211Sdes{
1104124211Sdes	char **envp;
1105124211Sdes
1106124211Sdes	if (env == NULL)
1107124211Sdes		return;
1108124211Sdes
1109124211Sdes	for (envp = env; *envp; envp++)
1110263970Sdes		free(*envp);
1111263970Sdes	free(env);
1112124211Sdes}
1113124211Sdes
1114137019Sdes/*
1115137019Sdes * "Blind" conversation function for password authentication.  Assumes that
1116137019Sdes * echo-off prompts are for the password and stores messages for later
1117137019Sdes * display.
1118137019Sdes */
1119137019Sdesstatic int
1120149753Sdessshpam_passwd_conv(int n, sshpam_const struct pam_message **msg,
1121137019Sdes    struct pam_response **resp, void *data)
1122137019Sdes{
1123137019Sdes	struct pam_response *reply;
1124137019Sdes	int i;
1125137019Sdes	size_t len;
1126137019Sdes
1127137019Sdes	debug3("PAM: %s called with %d messages", __func__, n);
1128137019Sdes
1129137019Sdes	*resp = NULL;
1130137019Sdes
1131137019Sdes	if (n <= 0 || n > PAM_MAX_NUM_MSG)
1132137019Sdes		return (PAM_CONV_ERR);
1133137019Sdes
1134181111Sdes	if ((reply = calloc(n, sizeof(*reply))) == NULL)
1135137019Sdes		return (PAM_CONV_ERR);
1136137019Sdes
1137137019Sdes	for (i = 0; i < n; ++i) {
1138137019Sdes		switch (PAM_MSG_MEMBER(msg, i, msg_style)) {
1139137019Sdes		case PAM_PROMPT_ECHO_OFF:
1140137019Sdes			if (sshpam_password == NULL)
1141137019Sdes				goto fail;
1142137019Sdes			if ((reply[i].resp = strdup(sshpam_password)) == NULL)
1143137019Sdes				goto fail;
1144137019Sdes			reply[i].resp_retcode = PAM_SUCCESS;
1145137019Sdes			break;
1146137019Sdes		case PAM_ERROR_MSG:
1147137019Sdes		case PAM_TEXT_INFO:
1148137019Sdes			len = strlen(PAM_MSG_MEMBER(msg, i, msg));
1149137019Sdes			if (len > 0) {
1150137019Sdes				buffer_append(&loginmsg,
1151137019Sdes				    PAM_MSG_MEMBER(msg, i, msg), len);
1152137019Sdes				buffer_append(&loginmsg, "\n", 1);
1153137019Sdes			}
1154137019Sdes			if ((reply[i].resp = strdup("")) == NULL)
1155137019Sdes				goto fail;
1156137019Sdes			reply[i].resp_retcode = PAM_SUCCESS;
1157137019Sdes			break;
1158137019Sdes		default:
1159137019Sdes			goto fail;
1160137019Sdes		}
1161137019Sdes	}
1162137019Sdes	*resp = reply;
1163137019Sdes	return (PAM_SUCCESS);
1164137019Sdes
1165149753Sdes fail:
1166137019Sdes	for(i = 0; i < n; i++) {
1167263970Sdes		free(reply[i].resp);
1168137019Sdes	}
1169263970Sdes	free(reply);
1170137019Sdes	return (PAM_CONV_ERR);
1171137019Sdes}
1172137019Sdes
1173137019Sdesstatic struct pam_conv passwd_conv = { sshpam_passwd_conv, NULL };
1174137019Sdes
1175137019Sdes/*
1176137019Sdes * Attempt password authentication via PAM
1177137019Sdes */
1178137019Sdesint
1179137019Sdessshpam_auth_passwd(Authctxt *authctxt, const char *password)
1180137019Sdes{
1181137019Sdes	int flags = (options.permit_empty_passwd == 0 ?
1182137019Sdes	    PAM_DISALLOW_NULL_AUTHTOK : 0);
1183137019Sdes
1184137019Sdes	if (!options.use_pam || sshpam_handle == NULL)
1185137019Sdes		fatal("PAM: %s called when PAM disabled or failed to "
1186137019Sdes		    "initialise.", __func__);
1187137019Sdes
1188137019Sdes	sshpam_password = password;
1189137019Sdes	sshpam_authctxt = authctxt;
1190137019Sdes
1191137019Sdes	/*
1192137019Sdes	 * If the user logging in is invalid, or is root but is not permitted
1193137019Sdes	 * by PermitRootLogin, use an invalid password to prevent leaking
1194137019Sdes	 * information via timing (eg if the PAM config has a delay on fail).
1195137019Sdes	 */
1196137019Sdes	if (!authctxt->valid || (authctxt->pw->pw_uid == 0 &&
1197149753Sdes	    options.permit_root_login != PERMIT_YES))
1198137019Sdes		sshpam_password = badpw;
1199137019Sdes
1200137019Sdes	sshpam_err = pam_set_item(sshpam_handle, PAM_CONV,
1201137019Sdes	    (const void *)&passwd_conv);
1202137019Sdes	if (sshpam_err != PAM_SUCCESS)
1203137019Sdes		fatal("PAM: %s: failed to set PAM_CONV: %s", __func__,
1204137019Sdes		    pam_strerror(sshpam_handle, sshpam_err));
1205137019Sdes
1206137019Sdes	sshpam_err = pam_authenticate(sshpam_handle, flags);
1207137019Sdes	sshpam_password = NULL;
1208137019Sdes	if (sshpam_err == PAM_SUCCESS && authctxt->valid) {
1209137019Sdes		debug("PAM: password authentication accepted for %.100s",
1210137019Sdes		    authctxt->user);
1211149753Sdes		return 1;
1212137019Sdes	} else {
1213137019Sdes		debug("PAM: password authentication failed for %.100s: %s",
1214137019Sdes		    authctxt->valid ? authctxt->user : "an illegal user",
1215137019Sdes		    pam_strerror(sshpam_handle, sshpam_err));
1216137019Sdes		return 0;
1217137019Sdes	}
1218137019Sdes}
121969591Sgreen#endif /* USE_PAM */
1220