xref: /netbsd-src/lib/libpam/modules/pam_ssh/pam_ssh.c (revision cefc293e00a80d48201bf14f466354a51d687319)
1*cefc293eShannken /*	$NetBSD: pam_ssh.c,v 1.30 2022/06/15 08:31:34 hannken Exp $	*/
2e7d22a2eSchristos 
36f11bdf1Schristos /*-
46f11bdf1Schristos  * Copyright (c) 2003 Networks Associates Technology, Inc.
56f11bdf1Schristos  * All rights reserved.
66f11bdf1Schristos  *
76f11bdf1Schristos  * This software was developed for the FreeBSD Project by ThinkSec AS and
86f11bdf1Schristos  * NAI Labs, the Security Research Division of Network Associates, Inc.
96f11bdf1Schristos  * under DARPA/SPAWAR contract N66001-01-C-8035 ("CBOSS"), as part of the
106f11bdf1Schristos  * DARPA CHATS research program.
116f11bdf1Schristos  *
126f11bdf1Schristos  * Redistribution and use in source and binary forms, with or without
136f11bdf1Schristos  * modification, are permitted provided that the following conditions
146f11bdf1Schristos  * are met:
156f11bdf1Schristos  * 1. Redistributions of source code must retain the above copyright
166f11bdf1Schristos  *    notice, this list of conditions and the following disclaimer.
176f11bdf1Schristos  * 2. Redistributions in binary form must reproduce the above copyright
186f11bdf1Schristos  *    notice, this list of conditions and the following disclaimer in the
196f11bdf1Schristos  *    documentation and/or other materials provided with the distribution.
206f11bdf1Schristos  * 3. The name of the author may not be used to endorse or promote
216f11bdf1Schristos  *    products derived from this software without specific prior written
226f11bdf1Schristos  *    permission.
236f11bdf1Schristos  *
246f11bdf1Schristos  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
256f11bdf1Schristos  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
266f11bdf1Schristos  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
276f11bdf1Schristos  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
286f11bdf1Schristos  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
296f11bdf1Schristos  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
306f11bdf1Schristos  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
316f11bdf1Schristos  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
326f11bdf1Schristos  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
336f11bdf1Schristos  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
346f11bdf1Schristos  * SUCH DAMAGE.
356f11bdf1Schristos  */
366f11bdf1Schristos 
376f11bdf1Schristos #include <sys/cdefs.h>
38474dd6daSlukem #ifdef __FreeBSD__
396f11bdf1Schristos __FBSDID("$FreeBSD: src/lib/libpam/modules/pam_ssh/pam_ssh.c,v 1.40 2004/02/10 10:13:21 des Exp $");
40e7d22a2eSchristos #else
41*cefc293eShannken __RCSID("$NetBSD: pam_ssh.c,v 1.30 2022/06/15 08:31:34 hannken Exp $");
42e7d22a2eSchristos #endif
436f11bdf1Schristos 
446f11bdf1Schristos #include <sys/param.h>
456f11bdf1Schristos #include <sys/wait.h>
466f11bdf1Schristos 
476f11bdf1Schristos #include <errno.h>
486f11bdf1Schristos #include <fcntl.h>
496f11bdf1Schristos #include <paths.h>
506f11bdf1Schristos #include <pwd.h>
516f11bdf1Schristos #include <signal.h>
526f11bdf1Schristos #include <stdio.h>
536f11bdf1Schristos #include <string.h>
546f11bdf1Schristos #include <unistd.h>
556f11bdf1Schristos 
566f11bdf1Schristos #define PAM_SM_AUTH
576f11bdf1Schristos #define PAM_SM_SESSION
586f11bdf1Schristos 
596f11bdf1Schristos #include <security/pam_appl.h>
606f11bdf1Schristos #include <security/pam_modules.h>
616f11bdf1Schristos #include <security/openpam.h>
626f11bdf1Schristos 
636f11bdf1Schristos #include <openssl/evp.h>
646f11bdf1Schristos 
658ea42f66Schristos #include "sshkey.h"
668ea42f66Schristos #include "sshbuf.h"
676f11bdf1Schristos #include "authfd.h"
686f11bdf1Schristos #include "authfile.h"
696f11bdf1Schristos 
70705315cdSdrochner #define ssh_add_identity(auth, key, comment) \
71*cefc293eShannken     ssh_add_identity_constrained(auth, key, comment, 0, 0, 0, NULL, NULL, 00)
72705315cdSdrochner 
736f11bdf1Schristos extern char **environ;
746f11bdf1Schristos 
756f11bdf1Schristos struct pam_ssh_key {
768ea42f66Schristos 	struct sshkey	*key;
776f11bdf1Schristos 	char	*comment;
786f11bdf1Schristos };
796f11bdf1Schristos 
806f11bdf1Schristos static const char *pam_ssh_prompt = "SSH passphrase: ";
816f11bdf1Schristos static const char *pam_ssh_have_keys = "pam_ssh_have_keys";
826f11bdf1Schristos 
836f11bdf1Schristos static const char *pam_ssh_keyfiles[] = {
846f11bdf1Schristos 	".ssh/identity",	/* SSH1 RSA key */
856f11bdf1Schristos 	".ssh/id_rsa",		/* SSH2 RSA key */
866f11bdf1Schristos 	".ssh/id_dsa",		/* SSH2 DSA key */
87cb4d5f3dSdrochner 	".ssh/id_ecdsa", 	/* SSH2 ECDSA key */
886f11bdf1Schristos 	NULL
896f11bdf1Schristos };
906f11bdf1Schristos 
916f11bdf1Schristos static const char *pam_ssh_agent = "/usr/bin/ssh-agent";
92705315cdSdrochner static const char *const pam_ssh_agent_argv[] = { "ssh_agent", "-s", NULL };
93705315cdSdrochner static const char *const pam_ssh_agent_envp[] = { NULL };
946f11bdf1Schristos 
956f11bdf1Schristos /*
966f11bdf1Schristos  * Attempts to load a private key from the specified file in the specified
976f11bdf1Schristos  * directory, using the specified passphrase.  If successful, returns a
986f11bdf1Schristos  * struct pam_ssh_key containing the key and its comment.
996f11bdf1Schristos  */
1006f11bdf1Schristos static struct pam_ssh_key *
pam_ssh_load_key(const char * dir,const char * kfn,const char * passphrase,int nullok)1012462eb04Sdrochner pam_ssh_load_key(const char *dir, const char *kfn, const char *passphrase,
1022462eb04Sdrochner     int nullok)
1036f11bdf1Schristos {
1046f11bdf1Schristos 	struct pam_ssh_key *psk;
1056f11bdf1Schristos 	char fn[PATH_MAX];
1068ea42f66Schristos 	int r;
1076f11bdf1Schristos 	char *comment;
1088ea42f66Schristos 	struct sshkey *key;
1096f11bdf1Schristos 
110705315cdSdrochner 	if (snprintf(fn, sizeof(fn), "%s/%s", dir, kfn) > (int)sizeof(fn))
1116f11bdf1Schristos 		return (NULL);
1126f11bdf1Schristos 	comment = NULL;
1132462eb04Sdrochner 	/*
1142462eb04Sdrochner 	 * If the key is unencrypted, OpenSSL ignores the passphrase, so
1152462eb04Sdrochner 	 * it will seem like the user typed in the right one.  This allows
1162462eb04Sdrochner 	 * a user to circumvent nullok by providing a dummy passphrase.
1172462eb04Sdrochner 	 * Verify that the key really *is* encrypted by trying to load it
1182462eb04Sdrochner 	 * with an empty passphrase, and if the key is not encrypted,
1192462eb04Sdrochner 	 * accept only an empty passphrase.
1202462eb04Sdrochner 	 */
1218ea42f66Schristos 	r = sshkey_load_private(fn, "", &key, &comment);
1228137552aSmlelstv 	if (r == 0 && !(*passphrase == '\0' && nullok)) {
1238137552aSmlelstv 		openpam_log(PAM_LOG_DEBUG, "rejected unencrypted key from %s", fn);
1248ea42f66Schristos 		sshkey_free(key);
1252462eb04Sdrochner 		free(comment);
1262462eb04Sdrochner 		return (NULL);
1272462eb04Sdrochner 	}
1288ea42f66Schristos 	if (r)
1298137552aSmlelstv 		r = sshkey_load_private(fn, passphrase, &key, &comment);
1308ea42f66Schristos 	if (r) {
13169de8de8Sdrochner 		openpam_log(PAM_LOG_DEBUG, "failed to load key from %s", fn);
1320c47a675Sjnemeth 		if (comment != NULL)
1330c47a675Sjnemeth 			free(comment);
1346f11bdf1Schristos 		return (NULL);
1356f11bdf1Schristos 	}
1366f11bdf1Schristos 
13769de8de8Sdrochner 	openpam_log(PAM_LOG_DEBUG, "loaded '%s' from %s", comment, fn);
1386f11bdf1Schristos 	if ((psk = malloc(sizeof(*psk))) == NULL) {
1398ea42f66Schristos 		sshkey_free(key);
1406f11bdf1Schristos 		free(comment);
1416f11bdf1Schristos 		return (NULL);
1426f11bdf1Schristos 	}
1436f11bdf1Schristos 	psk->key = key;
1446f11bdf1Schristos 	psk->comment = comment;
1456f11bdf1Schristos 	return (psk);
1466f11bdf1Schristos }
1476f11bdf1Schristos 
1486f11bdf1Schristos /*
1496f11bdf1Schristos  * Wipes a private key and frees the associated resources.
1506f11bdf1Schristos  */
1516f11bdf1Schristos static void
pam_ssh_free_key(pam_handle_t * pamh __unused,void * data,int pam_err __unused)1526f11bdf1Schristos pam_ssh_free_key(pam_handle_t *pamh __unused,
1536f11bdf1Schristos     void *data, int pam_err __unused)
1546f11bdf1Schristos {
1556f11bdf1Schristos 	struct pam_ssh_key *psk;
1566f11bdf1Schristos 
1576f11bdf1Schristos 	psk = data;
1588ea42f66Schristos 	sshkey_free(psk->key);
1596f11bdf1Schristos 	free(psk->comment);
1606f11bdf1Schristos 	free(psk);
1616f11bdf1Schristos }
1626f11bdf1Schristos 
1636f11bdf1Schristos PAM_EXTERN int
pam_sm_authenticate(pam_handle_t * pamh,int flags __unused,int argc __unused,const char * argv[]__unused)1646f11bdf1Schristos pam_sm_authenticate(pam_handle_t *pamh, int flags __unused,
1656f11bdf1Schristos     int argc __unused, const char *argv[] __unused)
1666f11bdf1Schristos {
1676f11bdf1Schristos 	const char **kfn, *passphrase, *user;
168705315cdSdrochner 	const void *item;
16959cbc9e2Sthorpej 	struct passwd *pwd, pwres;
1706f11bdf1Schristos 	struct pam_ssh_key *psk;
1712462eb04Sdrochner 	int nkeys, nullok, pam_err, pass;
17259cbc9e2Sthorpej 	char pwbuf[1024];
1736f11bdf1Schristos 
1742462eb04Sdrochner 	nullok = (openpam_get_option(pamh, "nullok") != NULL);
1752462eb04Sdrochner 
1766f11bdf1Schristos 	/* PEM is not loaded by default */
1776f11bdf1Schristos 	OpenSSL_add_all_algorithms();
1786f11bdf1Schristos 
1796f11bdf1Schristos 	/* get user name and home directory */
1806f11bdf1Schristos 	pam_err = pam_get_user(pamh, &user, NULL);
1816f11bdf1Schristos 	if (pam_err != PAM_SUCCESS)
1826f11bdf1Schristos 		return (pam_err);
1832a62e4e1Schristos 	if (getpwnam_r(user, &pwres, pwbuf, sizeof(pwbuf), &pwd) != 0 ||
1842a62e4e1Schristos 	    pwd == NULL)
1856f11bdf1Schristos 		return (PAM_USER_UNKNOWN);
1866f11bdf1Schristos 	if (pwd->pw_dir == NULL)
1876f11bdf1Schristos 		return (PAM_AUTH_ERR);
1886f11bdf1Schristos 
1892462eb04Sdrochner 	nkeys = 0;
190705315cdSdrochner 	pass = (pam_get_item(pamh, PAM_AUTHTOK, &item) == PAM_SUCCESS &&
191705315cdSdrochner 	    item != NULL);
1926f11bdf1Schristos  load_keys:
1936f11bdf1Schristos 	/* get passphrase */
1946f11bdf1Schristos 	pam_err = pam_get_authtok(pamh, PAM_AUTHTOK,
1956f11bdf1Schristos 	    &passphrase, pam_ssh_prompt);
1962a0c9a37Sdrochner 	if (pam_err != PAM_SUCCESS)
1976f11bdf1Schristos 		return (pam_err);
1982a0c9a37Sdrochner 
1992a0c9a37Sdrochner 	/* switch to user credentials */
2002a0c9a37Sdrochner 	pam_err = openpam_borrow_cred(pamh, pwd);
2012a0c9a37Sdrochner 	if (pam_err != PAM_SUCCESS)
2022a0c9a37Sdrochner 		return (pam_err);
2036f11bdf1Schristos 
2046f11bdf1Schristos 	/* try to load keys from all keyfiles we know of */
2056f11bdf1Schristos 	for (kfn = pam_ssh_keyfiles; *kfn != NULL; ++kfn) {
2062462eb04Sdrochner 		psk = pam_ssh_load_key(pwd->pw_dir, *kfn, passphrase, nullok);
2076f11bdf1Schristos 		if (psk != NULL) {
2086f11bdf1Schristos 			pam_set_data(pamh, *kfn, psk, pam_ssh_free_key);
2096f11bdf1Schristos 			++nkeys;
2106f11bdf1Schristos 		}
2116f11bdf1Schristos 	}
2126f11bdf1Schristos 
2132a0c9a37Sdrochner 	/* switch back to arbitrator credentials */
2142a0c9a37Sdrochner 	openpam_restore_cred(pamh);
2152a0c9a37Sdrochner 
2166f11bdf1Schristos 	/*
2176f11bdf1Schristos 	 * If we tried an old token and didn't get anything, and
2186f11bdf1Schristos 	 * try_first_pass was specified, try again after prompting the
2196f11bdf1Schristos 	 * user for a new passphrase.
2206f11bdf1Schristos 	 */
2216f11bdf1Schristos 	if (nkeys == 0 && pass == 1 &&
2226f11bdf1Schristos 	    openpam_get_option(pamh, "try_first_pass") != NULL) {
2236f11bdf1Schristos 		pam_set_item(pamh, PAM_AUTHTOK, NULL);
2246f11bdf1Schristos 		pass = 0;
2256f11bdf1Schristos 		goto load_keys;
2266f11bdf1Schristos 	}
2276f11bdf1Schristos 
2286f11bdf1Schristos 	/* no keys? */
2296f11bdf1Schristos 	if (nkeys == 0)
2306f11bdf1Schristos 		return (PAM_AUTH_ERR);
2316f11bdf1Schristos 
2326f11bdf1Schristos 	pam_set_data(pamh, pam_ssh_have_keys, NULL, NULL);
2336f11bdf1Schristos 	return (PAM_SUCCESS);
2346f11bdf1Schristos }
2356f11bdf1Schristos 
2366f11bdf1Schristos PAM_EXTERN int
pam_sm_setcred(pam_handle_t * pamh __unused,int flags __unused,int argc __unused,const char * argv[]__unused)2376f11bdf1Schristos pam_sm_setcred(pam_handle_t *pamh __unused, int flags __unused,
2386f11bdf1Schristos     int argc __unused, const char *argv[] __unused)
2396f11bdf1Schristos {
2406f11bdf1Schristos 
2416f11bdf1Schristos 	return (PAM_SUCCESS);
2426f11bdf1Schristos }
2436f11bdf1Schristos 
2446f11bdf1Schristos /*
2456f11bdf1Schristos  * Parses a line from ssh-agent's output.
2466f11bdf1Schristos  */
2476f11bdf1Schristos static void
pam_ssh_process_agent_output(pam_handle_t * pamh,FILE * f)2486f11bdf1Schristos pam_ssh_process_agent_output(pam_handle_t *pamh, FILE *f)
2496f11bdf1Schristos {
2506f11bdf1Schristos 	char *line, *p, *key, *val;
2516f11bdf1Schristos 	size_t len;
2526f11bdf1Schristos 
2536f11bdf1Schristos 	while ((line = fgetln(f, &len)) != NULL) {
2546f11bdf1Schristos 		if (len < 4 || strncmp(line, "SSH_", 4) != 0)
2556f11bdf1Schristos 			continue;
2566f11bdf1Schristos 
2576f11bdf1Schristos 		/* find equal sign at end of key */
2586f11bdf1Schristos 		for (p = key = line; p < line + len; ++p)
2596f11bdf1Schristos 			if (*p == '=')
2606f11bdf1Schristos 				break;
2616f11bdf1Schristos 		if (p == line + len || *p != '=')
2626f11bdf1Schristos 			continue;
2636f11bdf1Schristos 		*p = '\0';
2646f11bdf1Schristos 
2656f11bdf1Schristos 		/* find semicolon at end of value */
2666f11bdf1Schristos 		for (val = ++p; p < line + len; ++p)
2676f11bdf1Schristos 			if (*p == ';')
2686f11bdf1Schristos 				break;
2696f11bdf1Schristos 		if (p == line + len || *p != ';')
2706f11bdf1Schristos 			continue;
2716f11bdf1Schristos 		*p = '\0';
2726f11bdf1Schristos 
2736f11bdf1Schristos 		/* store key-value pair in environment */
2746f11bdf1Schristos 		openpam_log(PAM_LOG_DEBUG, "got %s: %s", key, val);
2756f11bdf1Schristos 		pam_setenv(pamh, key, val, 1);
2766f11bdf1Schristos 	}
2776f11bdf1Schristos }
2786f11bdf1Schristos 
2796f11bdf1Schristos /*
2806f11bdf1Schristos  * Starts an ssh agent and stores the environment variables derived from
2816f11bdf1Schristos  * its output.
2826f11bdf1Schristos  */
2836f11bdf1Schristos static int
pam_ssh_start_agent(pam_handle_t * pamh,struct passwd * pwd)284901ebd51Schristos pam_ssh_start_agent(pam_handle_t *pamh, struct passwd *pwd)
2856f11bdf1Schristos {
2866f11bdf1Schristos 	int agent_pipe[2];
2876f11bdf1Schristos 	pid_t pid;
2886f11bdf1Schristos 	FILE *f;
2896f11bdf1Schristos 
2906f11bdf1Schristos 	/* get a pipe which we will use to read the agent's output */
291901ebd51Schristos 	if (pipe(agent_pipe) == -1)
2926f11bdf1Schristos 		return (PAM_SYSTEM_ERR);
2936f11bdf1Schristos 
2946f11bdf1Schristos 	/* start the agent */
2956f11bdf1Schristos 	openpam_log(PAM_LOG_DEBUG, "starting an ssh agent");
2966f11bdf1Schristos 	pid = fork();
2976f11bdf1Schristos 	if (pid == (pid_t)-1) {
2986f11bdf1Schristos 		/* failed */
2996f11bdf1Schristos 		close(agent_pipe[0]);
3006f11bdf1Schristos 		close(agent_pipe[1]);
3016f11bdf1Schristos 		return (PAM_SYSTEM_ERR);
3026f11bdf1Schristos 	}
3036f11bdf1Schristos 	if (pid == 0) {
304e7d22a2eSchristos #ifndef F_CLOSEM
3056f11bdf1Schristos 		int fd;
306e7d22a2eSchristos #endif
3076f11bdf1Schristos 		/* child: drop privs, close fds and start agent */
308901ebd51Schristos 		if (setgid(pwd->pw_gid) == -1) {
3091b695acdSchristos 			openpam_log(PAM_LOG_DEBUG, "%s: Cannot setgid %d (%s)",
3101b695acdSchristos 			    __func__, (int)pwd->pw_gid, strerror(errno));
311901ebd51Schristos 			goto done;
312901ebd51Schristos 		}
313901ebd51Schristos 		if (initgroups(pwd->pw_name, pwd->pw_gid) == -1) {
314901ebd51Schristos 			openpam_log(PAM_LOG_DEBUG,
3151b695acdSchristos 			    "%s: Cannot initgroups for %s (%s)",
3161b695acdSchristos 			    __func__, pwd->pw_name, strerror(errno));
317901ebd51Schristos 			goto done;
318901ebd51Schristos 		}
319901ebd51Schristos 		if (setuid(pwd->pw_uid) == -1) {
3201b695acdSchristos 			openpam_log(PAM_LOG_DEBUG, "%s: Cannot setuid %d (%s)",
3211b695acdSchristos 			    __func__, (int)pwd->pw_uid, strerror(errno));
322901ebd51Schristos 			goto done;
323901ebd51Schristos 		}
324901ebd51Schristos 		(void)close(STDIN_FILENO);
325901ebd51Schristos 		(void)open(_PATH_DEVNULL, O_RDONLY);
326901ebd51Schristos 		(void)dup2(agent_pipe[1], STDOUT_FILENO);
327901ebd51Schristos 		(void)dup2(agent_pipe[1], STDERR_FILENO);
328e7d22a2eSchristos #ifdef F_CLOSEM
329e7d22a2eSchristos 		(void)fcntl(3, F_CLOSEM, 0);
330e7d22a2eSchristos #else
3316f11bdf1Schristos 		for (fd = 3; fd < getdtablesize(); ++fd)
332901ebd51Schristos 			(void)close(fd);
333e7d22a2eSchristos #endif
334901ebd51Schristos 		(void)execve(pam_ssh_agent,
335901ebd51Schristos 		    (char **)__UNCONST(pam_ssh_agent_argv),
336e7d22a2eSchristos 		    (char **)__UNCONST(pam_ssh_agent_envp));
337901ebd51Schristos done:
3386f11bdf1Schristos 		_exit(127);
3396f11bdf1Schristos 	}
3406f11bdf1Schristos 
3416f11bdf1Schristos 	/* parent */
3426f11bdf1Schristos 	close(agent_pipe[1]);
3436f11bdf1Schristos 	if ((f = fdopen(agent_pipe[0], "r")) == NULL)
3446f11bdf1Schristos 		return (PAM_SYSTEM_ERR);
3456f11bdf1Schristos 	pam_ssh_process_agent_output(pamh, f);
3466f11bdf1Schristos 	fclose(f);
3476f11bdf1Schristos 
3486f11bdf1Schristos 	return (PAM_SUCCESS);
3496f11bdf1Schristos }
3506f11bdf1Schristos 
3516f11bdf1Schristos /*
3526f11bdf1Schristos  * Adds previously stored keys to a running agent.
3536f11bdf1Schristos  */
3546f11bdf1Schristos static int
pam_ssh_add_keys_to_agent(pam_handle_t * pamh)3556f11bdf1Schristos pam_ssh_add_keys_to_agent(pam_handle_t *pamh)
3566f11bdf1Schristos {
3574466f6b5Schristos 	const struct pam_ssh_key *psk;
3586f11bdf1Schristos 	const char **kfn;
3596f11bdf1Schristos 	char **envlist, **env;
3606f11bdf1Schristos 	int pam_err;
361f804baf7Schristos 	int agent_fd;
3626f11bdf1Schristos 
3636f11bdf1Schristos 	/* switch to PAM environment */
3646f11bdf1Schristos 	envlist = environ;
3656f11bdf1Schristos 	if ((environ = pam_getenvlist(pamh)) == NULL) {
366901ebd51Schristos 		openpam_log(PAM_LOG_DEBUG, "%s: cannot get envlist",
367592fd7eaSragge 		    __func__);
3686f11bdf1Schristos 		environ = envlist;
3696f11bdf1Schristos 		return (PAM_SYSTEM_ERR);
3706f11bdf1Schristos 	}
3716f11bdf1Schristos 
3726f11bdf1Schristos 	/* get a connection to the agent */
373f804baf7Schristos 	if (ssh_get_authentication_socket(&agent_fd) != 0) {
374901ebd51Schristos 		openpam_log(PAM_LOG_DEBUG,
375901ebd51Schristos 		    "%s: cannot get authentication connection",
376592fd7eaSragge 		    __func__);
3776f11bdf1Schristos 		pam_err = PAM_SYSTEM_ERR;
378f804baf7Schristos 		agent_fd = -1;
3796f11bdf1Schristos 		goto end;
3806f11bdf1Schristos 	}
3816f11bdf1Schristos 
3826f11bdf1Schristos 	/* look for keys to add to it */
3836f11bdf1Schristos 	for (kfn = pam_ssh_keyfiles; *kfn != NULL; ++kfn) {
3844466f6b5Schristos 		const void *vp;
3854466f6b5Schristos 		pam_err = pam_get_data(pamh, *kfn, &vp);
3864466f6b5Schristos 		psk = vp;
3876f11bdf1Schristos 		if (pam_err == PAM_SUCCESS && psk != NULL) {
3889cbfe7dcSchristos 			if (ssh_add_identity(agent_fd, psk->key, psk->comment))
3896f11bdf1Schristos 				openpam_log(PAM_LOG_DEBUG,
3906f11bdf1Schristos 				    "added %s to ssh agent", psk->comment);
3916f11bdf1Schristos 			else
3926f11bdf1Schristos 				openpam_log(PAM_LOG_DEBUG, "failed "
3936f11bdf1Schristos 				    "to add %s to ssh agent", psk->comment);
3946f11bdf1Schristos 			/* we won't need the key again, so wipe it */
3956f11bdf1Schristos 			pam_set_data(pamh, *kfn, NULL, NULL);
3966f11bdf1Schristos 		}
3976f11bdf1Schristos 	}
3986f11bdf1Schristos 	pam_err = PAM_SUCCESS;
3996f11bdf1Schristos  end:
4006f11bdf1Schristos 	/* disconnect from agent */
401f804baf7Schristos 	if (agent_fd != -1)
402f804baf7Schristos 		ssh_close_authentication_socket(agent_fd);
4036f11bdf1Schristos 
4046f11bdf1Schristos 	/* switch back to original environment */
4056f11bdf1Schristos 	for (env = environ; *env != NULL; ++env)
4066f11bdf1Schristos 		free(*env);
4076f11bdf1Schristos 	free(environ);
4086f11bdf1Schristos 	environ = envlist;
4096f11bdf1Schristos 
4106f11bdf1Schristos 	return (pam_err);
4116f11bdf1Schristos }
4126f11bdf1Schristos 
4136f11bdf1Schristos PAM_EXTERN int
pam_sm_open_session(pam_handle_t * pamh,int flags __unused,int argc __unused,const char * argv[]__unused)4146f11bdf1Schristos pam_sm_open_session(pam_handle_t *pamh, int flags __unused,
4156f11bdf1Schristos     int argc __unused, const char *argv[] __unused)
4166f11bdf1Schristos {
41759cbc9e2Sthorpej 	struct passwd *pwd, pwres;
4186f11bdf1Schristos 	const char *user;
4194466f6b5Schristos 	const void *data;
420901ebd51Schristos 	int pam_err = PAM_SUCCESS;
42159cbc9e2Sthorpej 	char pwbuf[1024];
4226f11bdf1Schristos 
4236f11bdf1Schristos 	/* no keys, no work */
4246f11bdf1Schristos 	if (pam_get_data(pamh, pam_ssh_have_keys, &data) != PAM_SUCCESS &&
4256f11bdf1Schristos 	    openpam_get_option(pamh, "want_agent") == NULL)
4266f11bdf1Schristos 		return (PAM_SUCCESS);
4276f11bdf1Schristos 
4286f11bdf1Schristos 	/* switch to user credentials */
4296f11bdf1Schristos 	pam_err = pam_get_user(pamh, &user, NULL);
4306f11bdf1Schristos 	if (pam_err != PAM_SUCCESS)
4316f11bdf1Schristos 		return (pam_err);
4322a62e4e1Schristos 	if (getpwnam_r(user, &pwres, pwbuf, sizeof(pwbuf), &pwd) != 0 ||
4332a62e4e1Schristos 	    pwd == NULL)
4346f11bdf1Schristos 		return (PAM_USER_UNKNOWN);
4356f11bdf1Schristos 
4366f11bdf1Schristos 	/* start the agent */
437901ebd51Schristos 	pam_err = pam_ssh_start_agent(pamh, pwd);
438901ebd51Schristos 	if (pam_err != PAM_SUCCESS)
439901ebd51Schristos 		return pam_err;
440901ebd51Schristos 
441901ebd51Schristos 	pam_err = openpam_borrow_cred(pamh, pwd);
442901ebd51Schristos 	if (pam_err != PAM_SUCCESS)
443901ebd51Schristos 		return pam_err;
4446f11bdf1Schristos 
4456f11bdf1Schristos 	/* we have an agent, see if we can add any keys to it */
4466f11bdf1Schristos 	pam_err = pam_ssh_add_keys_to_agent(pamh);
4476f11bdf1Schristos 	if (pam_err != PAM_SUCCESS) {
4486f11bdf1Schristos 		/* XXX ignore failures */
449901ebd51Schristos 		openpam_log(PAM_LOG_DEBUG, "failed adding keys to ssh agent");
450901ebd51Schristos 		pam_err = PAM_SUCCESS;
4516f11bdf1Schristos 	}
4526f11bdf1Schristos 
4536f11bdf1Schristos 	openpam_restore_cred(pamh);
454901ebd51Schristos 	return pam_err;
4556f11bdf1Schristos }
4566f11bdf1Schristos 
4576f11bdf1Schristos PAM_EXTERN int
pam_sm_close_session(pam_handle_t * pamh,int flags __unused,int argc __unused,const char * argv[]__unused)4586f11bdf1Schristos pam_sm_close_session(pam_handle_t *pamh, int flags __unused,
4596f11bdf1Schristos     int argc __unused, const char *argv[] __unused)
4606f11bdf1Schristos {
4616f11bdf1Schristos 	const char *ssh_agent_pid;
4626f11bdf1Schristos 	char *end;
4636f11bdf1Schristos 	int status;
4646f11bdf1Schristos 	pid_t pid;
4656f11bdf1Schristos 
4666f11bdf1Schristos 	if ((ssh_agent_pid = pam_getenv(pamh, "SSH_AGENT_PID")) == NULL) {
4676f11bdf1Schristos 		openpam_log(PAM_LOG_DEBUG, "no ssh agent");
4686f11bdf1Schristos 		return (PAM_SUCCESS);
4696f11bdf1Schristos 	}
4706f11bdf1Schristos 	pid = (pid_t)strtol(ssh_agent_pid, &end, 10);
4716f11bdf1Schristos 	if (*ssh_agent_pid == '\0' || *end != '\0') {
4726f11bdf1Schristos 		openpam_log(PAM_LOG_DEBUG, "invalid ssh agent pid");
4736f11bdf1Schristos 		return (PAM_SESSION_ERR);
4746f11bdf1Schristos 	}
4756f11bdf1Schristos 	openpam_log(PAM_LOG_DEBUG, "killing ssh agent %d", (int)pid);
4766f11bdf1Schristos 	if (kill(pid, SIGTERM) == -1 ||
4776f11bdf1Schristos 	    (waitpid(pid, &status, 0) == -1 && errno != ECHILD))
4786f11bdf1Schristos 		return (PAM_SYSTEM_ERR);
4796f11bdf1Schristos 	return (PAM_SUCCESS);
4806f11bdf1Schristos }
4816f11bdf1Schristos 
4826f11bdf1Schristos PAM_MODULE_ENTRY("pam_ssh");
483