pam_ssh.c revision 110653
1/*-
2 * Copyright (c) 2003 Networks Associates Technology, Inc.
3 * All rights reserved.
4 *
5 * This software was developed for the FreeBSD Project by ThinkSec AS and
6 * NAI Labs, the Security Research Division of Network Associates, Inc.
7 * under DARPA/SPAWAR contract N66001-01-C-8035 ("CBOSS"), as part of the
8 * DARPA CHATS research program.
9 *
10 * Redistribution and use in source and binary forms, with or without
11 * modification, are permitted provided that the following conditions
12 * are met:
13 * 1. Redistributions of source code must retain the above copyright
14 *    notice, this list of conditions and the following disclaimer.
15 * 2. Redistributions in binary form must reproduce the above copyright
16 *    notice, this list of conditions and the following disclaimer in the
17 *    documentation and/or other materials provided with the distribution.
18 * 3. The name of the author may not be used to endorse or promote
19 *    products derived from this software without specific prior written
20 *    permission.
21 *
22 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
23 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
24 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
25 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
26 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
27 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
28 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
29 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
30 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
31 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
32 * SUCH DAMAGE.
33 */
34
35#include <sys/cdefs.h>
36__FBSDID("$FreeBSD: head/lib/libpam/modules/pam_ssh/pam_ssh.c 110653 2003-02-10 18:59:20Z des $");
37
38#include <sys/param.h>
39#include <sys/wait.h>
40
41#include <fcntl.h>
42#include <paths.h>
43#include <pwd.h>
44#include <signal.h>
45#include <stdio.h>
46#include <string.h>
47#include <unistd.h>
48
49#define PAM_SM_AUTH
50#define PAM_SM_SESSION
51
52#include <security/pam_appl.h>
53#include <security/pam_modules.h>
54#include <security/openpam.h>
55
56#include <openssl/evp.h>
57
58#include "key.h"
59#include "authfd.h"
60#include "authfile.h"
61
62extern char **environ;
63
64struct pam_ssh_key {
65	Key	*key;
66	char	*comment;
67};
68
69static const char *pam_ssh_prompt = "SSH passphrase: ";
70static const char *pam_ssh_have_keys = "pam_ssh_have_keys";
71
72static const char *pam_ssh_keyfiles[] = {
73	".ssh/identity",	/* SSH1 RSA key */
74	".ssh/id_rsa",		/* SSH2 RSA key */
75	".ssh/id_dsa",		/* SSH2 DSA key */
76	NULL
77};
78
79static const char *pam_ssh_agent = "/usr/bin/ssh-agent";
80static const char *pam_ssh_agent_argv[] = { "ssh_agent", "-s", NULL };
81static const char *pam_ssh_agent_envp[] = { NULL };
82
83/*
84 * Attempts to load a private key from the specified file in the specified
85 * directory, using the specified passphrase.  If successful, returns a
86 * struct pam_ssh_key containing the key and its comment.
87 */
88static struct pam_ssh_key *
89pam_ssh_load_key(const char *dir, const char *kfn, const char *passphrase)
90{
91	struct pam_ssh_key *psk;
92	char fn[PATH_MAX];
93	char *comment;
94	Key *key;
95
96	if (snprintf(fn, sizeof(fn), "%s/%s", dir, kfn) > (int)sizeof(fn))
97		return (NULL);
98	comment = NULL;
99	key = key_load_private(fn, passphrase, &comment);
100	if (key == NULL) {
101		openpam_log(PAM_LOG_DEBUG, "failed to load key from %s\n", fn);
102		return (NULL);
103	}
104
105	openpam_log(PAM_LOG_DEBUG, "loaded '%s' from %s\n", comment, fn);
106	if ((psk = malloc(sizeof(*psk))) == NULL) {
107		key_free(key);
108		free(comment);
109		return (NULL);
110	}
111	psk->key = key;
112	psk->comment = comment;
113	return (psk);
114}
115
116/*
117 * Wipes a private key and frees the associated resources.
118 */
119static void
120pam_ssh_free_key(pam_handle_t *pamh __unused,
121    void *data, int pam_err __unused)
122{
123	struct pam_ssh_key *psk;
124
125	psk = data;
126	key_free(psk->key);
127	free(psk->comment);
128	free(psk);
129}
130
131PAM_EXTERN int
132pam_sm_authenticate(pam_handle_t *pamh, int flags __unused,
133    int argc __unused, const char *argv[] __unused)
134{
135	const char **kfn, *passphrase, *user;
136	struct passwd *pwd;
137	struct pam_ssh_key *psk;
138	int nkeys, pam_err, pass;
139
140	/* PEM is not loaded by default */
141	OpenSSL_add_all_algorithms();
142
143	/* get user name and home directory */
144	pam_err = pam_get_user(pamh, &user, NULL);
145	if (pam_err != PAM_SUCCESS)
146		return (pam_err);
147	pwd = getpwnam(user);
148	if (pwd == NULL)
149		return (PAM_USER_UNKNOWN);
150	if (pwd->pw_dir == NULL)
151		return (PAM_AUTH_ERR);
152
153	/* switch to user credentials */
154	pam_err = openpam_borrow_cred(pamh, pwd);
155	if (pam_err != PAM_SUCCESS)
156		return (pam_err);
157
158	pass = (pam_get_item(pamh, PAM_AUTHTOK,
159	    (const void **)&passphrase) == PAM_SUCCESS);
160 load_keys:
161	/* get passphrase */
162	pam_err = pam_get_authtok(pamh, PAM_AUTHTOK,
163	    &passphrase, pam_ssh_prompt);
164	if (pam_err != PAM_SUCCESS) {
165		openpam_restore_cred(pamh);
166		return (pam_err);
167	}
168
169	/* try to load keys from all keyfiles we know of */
170	nkeys = 0;
171	for (kfn = pam_ssh_keyfiles; *kfn != NULL; ++kfn) {
172		psk = pam_ssh_load_key(pwd->pw_dir, *kfn, passphrase);
173		if (psk != NULL) {
174			pam_set_data(pamh, *kfn, psk, pam_ssh_free_key);
175			++nkeys;
176		}
177	}
178
179	/*
180	 * If we tried an old token and didn't get anything, and
181	 * try_first_pass was specified, try again after prompting the
182	 * user for a new passphrase.
183	 */
184	if (nkeys == 0 && pass == 1 &&
185	    openpam_get_option(pamh, "try_first_pass") != NULL) {
186		pam_set_item(pamh, PAM_AUTHTOK, NULL);
187		pass = 0;
188		goto load_keys;
189	}
190
191	/* switch back to arbitrator credentials before returning */
192	openpam_restore_cred(pamh);
193
194	/* no keys? */
195	if (nkeys == 0)
196		return (PAM_AUTH_ERR);
197
198	pam_set_data(pamh, pam_ssh_have_keys, NULL, NULL);
199	return (PAM_SUCCESS);
200}
201
202PAM_EXTERN int
203pam_sm_setcred(pam_handle_t *pamh __unused, int flags __unused,
204    int argc __unused, const char *argv[] __unused)
205{
206
207	return (PAM_SUCCESS);
208}
209
210/*
211 * Parses a line from ssh-agent's output.
212 */
213static void
214pam_ssh_process_agent_output(pam_handle_t *pamh, FILE *f)
215{
216	char *line, *p, *key, *val;
217	size_t len;
218
219	while ((line = fgetln(f, &len)) != NULL) {
220		if (len < 4 || strncmp(line, "SSH_", 4) != 0)
221			continue;
222
223		/* find equal sign at end of key */
224		for (p = key = line; p < line + len; ++p)
225			if (*p == '=')
226				break;
227		if (p == line + len || *p != '=')
228			continue;
229		*p = '\0';
230
231		/* find semicolon at end of value */
232		for (val = ++p; p < line + len; ++p)
233			if (*p == ';')
234				break;
235		if (p == line + len || *p != ';')
236			continue;
237		*p = '\0';
238
239		/* store key-value pair in environment */
240		openpam_log(PAM_LOG_DEBUG, "got %s: %s", key, val);
241		pam_setenv(pamh, key, val, 1);
242	}
243}
244
245/*
246 * Starts an ssh agent and stores the environment variables derived from
247 * its output.
248 */
249static int
250pam_ssh_start_agent(pam_handle_t *pamh)
251{
252	int agent_pipe[2];
253	pid_t pid;
254	FILE *f;
255
256	/* get a pipe which we will use to read the agent's output */
257	if (pipe(agent_pipe) == -1) {
258		openpam_restore_cred(pamh);
259		return (PAM_SYSTEM_ERR);
260	}
261
262	/* start the agent */
263	openpam_log(PAM_LOG_DEBUG, "starting an ssh agent");
264	pid = fork();
265	if (pid == (pid_t)-1) {
266		/* failed */
267		close(agent_pipe[0]);
268		close(agent_pipe[1]);
269		return (PAM_SYSTEM_ERR);
270	}
271	if (pid == 0) {
272		int fd;
273
274		/* child: drop privs, close fds and start agent */
275		setgid(getegid());
276		setuid(geteuid());
277		close(STDIN_FILENO);
278		open(_PATH_DEVNULL, O_RDONLY);
279		dup2(agent_pipe[1], STDOUT_FILENO);
280		dup2(agent_pipe[1], STDERR_FILENO);
281		for (fd = 3; fd < getdtablesize(); ++fd)
282			close(fd);
283		execve(pam_ssh_agent, pam_ssh_agent_argv, pam_ssh_agent_envp);
284		_exit(127);
285	}
286
287	/* parent */
288	close(agent_pipe[1]);
289	if ((f = fdopen(agent_pipe[0], "r")) == NULL)
290		return (PAM_SYSTEM_ERR);
291	pam_ssh_process_agent_output(pamh, f);
292	fclose(f);
293
294	return (PAM_SUCCESS);
295}
296
297/*
298 * Adds previously stored keys to a running agent.
299 */
300static int
301pam_ssh_add_keys_to_agent(pam_handle_t *pamh)
302{
303	AuthenticationConnection *ac;
304	struct pam_ssh_key *psk;
305	const char **kfn;
306	char **envlist, **env;
307	int pam_err;
308
309	/* switch to PAM environment */
310	envlist = environ;
311	if ((environ = pam_getenvlist(pamh)) == NULL) {
312		environ = envlist;
313		return (PAM_SYSTEM_ERR);
314	}
315
316	/* get a connection to the agent */
317	if ((ac = ssh_get_authentication_connection()) == NULL) {
318		pam_err = PAM_SYSTEM_ERR;
319		goto end;
320	}
321
322	/* look for keys to add to it */
323	for (kfn = pam_ssh_keyfiles; *kfn != NULL; ++kfn) {
324		pam_err = pam_get_data(pamh, *kfn, (const void **)&psk);
325		if (pam_err == PAM_SUCCESS && psk != NULL) {
326			if (ssh_add_identity(ac, psk->key, psk->comment))
327				openpam_log(PAM_LOG_DEBUG,
328				    "added %s to ssh agent", psk->comment);
329			else
330				openpam_log(PAM_LOG_DEBUG, "failed "
331				    "to add %s to ssh agent", psk->comment);
332			/* we won't need the key again, so wipe it */
333			pam_set_data(pamh, *kfn, NULL, NULL);
334		}
335	}
336	pam_err = PAM_SUCCESS;
337 end:
338	/* disconnect from agent */
339	if (ac != NULL)
340		ssh_close_authentication_connection(ac);
341
342	/* switch back to original environment */
343	for (env = environ; *env != NULL; ++env)
344		free(*env);
345	free(environ);
346	environ = envlist;
347
348	return (pam_err);
349}
350
351PAM_EXTERN int
352pam_sm_open_session(pam_handle_t *pamh, int flags __unused,
353    int argc __unused, const char *argv[] __unused)
354{
355	struct passwd *pwd;
356	const char *user;
357	const void *data;
358	int pam_err;
359
360	/* no keys, no work */
361	if (pam_get_data(pamh, pam_ssh_have_keys, &data) != PAM_SUCCESS &&
362	    openpam_get_option(pamh, "want_agent") == NULL)
363		return (PAM_SUCCESS);
364
365	/* switch to user credentials */
366	pam_err = pam_get_user(pamh, &user, NULL);
367	if (pam_err != PAM_SUCCESS)
368		return (pam_err);
369	pwd = getpwnam(user);
370	if (pwd == NULL)
371		return (PAM_USER_UNKNOWN);
372	pam_err = openpam_borrow_cred(pamh, pwd);
373	if (pam_err != PAM_SUCCESS)
374		return (pam_err);
375
376	/* start the agent */
377	pam_err = pam_ssh_start_agent(pamh);
378	if (pam_err != PAM_SUCCESS) {
379		openpam_restore_cred(pamh);
380		return (pam_err);
381	}
382
383	/* we have an agent, see if we can add any keys to it */
384	pam_err = pam_ssh_add_keys_to_agent(pamh);
385	if (pam_err != PAM_SUCCESS) {
386		/* XXX ignore failures */
387	}
388
389	openpam_restore_cred(pamh);
390	return (PAM_SUCCESS);
391}
392
393PAM_EXTERN int
394pam_sm_close_session(pam_handle_t *pamh, int flags __unused,
395    int argc __unused, const char *argv[] __unused)
396{
397	const char *ssh_agent_pid;
398	char *end;
399	int status;
400	pid_t pid;
401
402	if ((ssh_agent_pid = pam_getenv(pamh, "SSH_AGENT_PID")) == NULL) {
403		openpam_log(PAM_LOG_DEBUG, "no ssh agent");
404		return (PAM_SUCCESS);
405	}
406	pid = (pid_t)strtol(ssh_agent_pid, &end, 10);
407	if (*ssh_agent_pid == '\0' || *end != '\0') {
408		openpam_log(PAM_LOG_DEBUG, "invalid ssh agent pid");
409		return (PAM_SESSION_ERR);
410	}
411	openpam_log(PAM_LOG_DEBUG, "killing ssh agent %d", (int)pid);
412	if (kill(pid, SIGTERM) == -1 ||
413	    waitpid(pid, &status, 0) == -1)
414		return (PAM_SYSTEM_ERR);
415	return (PAM_SUCCESS);
416}
417
418PAM_MODULE_ENTRY("pam_ssh");
419