pam_ssh.c revision 69590
1/*-
2 * Copyright (c) 1999, 2000 Andrew J. Korty
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 * 1. Redistributions of source code must retain the above copyright
9 *    notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 *    notice, this list of conditions and the following disclaimer in the
12 *    documentation and/or other materials provided with the distribution.
13 *
14 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
15 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
18 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
20 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
21 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
22 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
23 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
24 * SUCH DAMAGE.
25 *
26 * $FreeBSD: head/lib/libpam/modules/pam_ssh/pam_ssh.c 69590 2000-12-05 02:41:01Z green $
27 *
28 */
29
30
31#include <sys/param.h>
32#include <sys/queue.h>
33
34#include <fcntl.h>
35#include <paths.h>
36#include <pwd.h>
37#include <stdio.h>
38#include <stdlib.h>
39#include <string.h>
40#include <unistd.h>
41
42#define	PAM_SM_AUTH
43#define	PAM_SM_SESSION
44#include <security/pam_modules.h>
45#include <security/pam_mod_misc.h>
46
47#include <openssl/dsa.h>
48
49#include "includes.h"
50#include "rsa.h"
51#include "key.h"
52#include "ssh.h"
53#include "authfd.h"
54#include "authfile.h"
55
56#define	MODULE_NAME	"pam_ssh"
57#define	NEED_PASSPHRASE	"Need passphrase for %s (%s).\nEnter passphrase: "
58#define	PATH_SSH_AGENT	"/usr/bin/ssh-agent"
59
60
61void
62rsa_cleanup(pam_handle_t *pamh, void *data, int error_status)
63{
64	if (data)
65		RSA_free(data);
66}
67
68
69void
70ssh_cleanup(pam_handle_t *pamh, void *data, int error_status)
71{
72	if (data)
73		free(data);
74}
75
76
77/*
78 * The following set of functions allow the module to manipulate the
79 * environment without calling the putenv() or setenv() stdlib functions.
80 * At least one version of these functions, on the first call, copies
81 * the environment into dynamically-allocated memory and then augments
82 * it.  On subsequent calls, the realloc() call is used to grow the
83 * previously allocated buffer.  Problems arise when the "environ"
84 * variable is changed to point to static memory after putenv()/setenv()
85 * have been called.
86 *
87 * We don't use putenv() or setenv() in case the application subsequently
88 * manipulates environ, (e.g., to clear the environment by pointing
89 * environ at an array of one element equal to NULL).
90 */
91
92SLIST_HEAD(env_head, env_entry);
93
94struct env_entry {
95	char			*ee_env;
96	SLIST_ENTRY(env_entry)	 ee_entries;
97};
98
99typedef struct env {
100	char		**e_environ_orig;
101	char		**e_environ_new;
102	int		  e_count;
103	struct env_head	  e_head;
104	int		  e_committed;
105} ENV;
106
107extern char **environ;
108
109
110static ENV *
111env_new(void)
112{
113	ENV	*self;
114
115	if (!(self = malloc(sizeof (ENV)))) {
116		syslog(LOG_CRIT, "%m");
117		return NULL;
118	}
119	SLIST_INIT(&self->e_head);
120	self->e_count = 0;
121	self->e_committed = 0;
122	return self;
123}
124
125
126static int
127env_put(ENV *self, char *s)
128{
129	struct env_entry	*env;
130
131	if (!(env = malloc(sizeof (struct env_entry))) ||
132	    !(env->ee_env = strdup(s))) {
133		syslog(LOG_CRIT, "%m");
134		return PAM_SERVICE_ERR;
135	}
136	SLIST_INSERT_HEAD(&self->e_head, env, ee_entries);
137	++self->e_count;
138	return PAM_SUCCESS;
139}
140
141
142static void
143env_swap(ENV *self, int which)
144{
145	environ = which ? self->e_environ_new : self->e_environ_orig;
146}
147
148
149static int
150env_commit(ENV *self)
151{
152	int			  n;
153	struct env_entry	 *p;
154	char 			**v;
155
156	for (v = environ, n = 0; v && *v; v++, n++)
157		;
158	if (!(v = malloc((n + self->e_count + 1) * sizeof (char *)))) {
159		syslog(LOG_CRIT, "%m");
160		return PAM_SERVICE_ERR;
161	}
162	self->e_committed = 1;
163	(void)memcpy(v, environ, n * sizeof (char *));
164	SLIST_FOREACH(p, &self->e_head, ee_entries)
165		v[n++] = p->ee_env;
166	v[n] = NULL;
167	self->e_environ_orig = environ;
168	self->e_environ_new = v;
169	env_swap(self, 1);
170	return PAM_SUCCESS;
171}
172
173
174static void
175env_destroy(ENV *self)
176{
177	struct env_entry	 *p;
178
179	if (self->e_committed)
180		env_swap(self, 0);
181	SLIST_FOREACH(p, &self->e_head, ee_entries) {
182		free(p->ee_env);
183		free(p);
184	}
185	if (self->e_committed)
186		free(self->e_environ_new);
187	free(self);
188}
189
190
191void
192env_cleanup(pam_handle_t *pamh, void *data, int error_status)
193{
194	if (data)
195		env_destroy(data);
196}
197
198
199typedef struct passwd PASSWD;
200
201PAM_EXTERN int
202pam_sm_authenticate(
203	pam_handle_t	 *pamh,
204	int		  flags,
205	int		  argc,
206	const char	**argv)
207{
208	char		*comment_priv;		/* on private key */
209	char		*comment_pub;		/* on public key */
210	char		*identity;		/* user's identity file */
211	Key		 key;			/* user's private key */
212	int		 options;		/* module options */
213	const char	*pass;			/* passphrase */
214	char		*prompt;		/* passphrase prompt */
215	Key		 public_key;		/* user's public key */
216	const PASSWD	*pwent;			/* user's passwd entry */
217	PASSWD		*pwent_keep;		/* our own copy */
218	int		 retval;		/* from calls */
219	uid_t		 saved_uid;		/* caller's uid */
220	const char	*user;			/* username */
221
222	options = 0;
223	while (argc--)
224		pam_std_option(&options, *argv++);
225	if ((retval = pam_get_user(pamh, &user, NULL)) != PAM_SUCCESS)
226		return retval;
227	if (!((pwent = getpwnam(user)) && pwent->pw_dir)) {
228		/* delay? */
229		return PAM_AUTH_ERR;
230	}
231	/* locate the user's private key file */
232	if (!asprintf(&identity, "%s/%s", pwent->pw_dir,
233	    SSH_CLIENT_IDENTITY)) {
234		syslog(LOG_CRIT, "%s: %m", MODULE_NAME);
235		return PAM_SERVICE_ERR;
236	}
237	/*
238	 * Fail unless we can load the public key.  Change to the
239	 * owner's UID to appease load_public_key().
240	 */
241	key.type = KEY_RSA;
242	key.rsa = RSA_new();
243	public_key.type = KEY_RSA;
244	public_key.rsa = RSA_new();
245	saved_uid = getuid();
246	(void)setreuid(pwent->pw_uid, saved_uid);
247	retval = load_public_key(identity, &public_key, &comment_pub);
248	(void)setuid(saved_uid);
249	if (!retval) {
250		free(identity);
251		return PAM_AUTH_ERR;
252	}
253	RSA_free(public_key.rsa);
254	/* build the passphrase prompt */
255	retval = asprintf(&prompt, NEED_PASSPHRASE, identity, comment_pub);
256	free(comment_pub);
257	if (!retval) {
258		syslog(LOG_CRIT, "%s: %m", MODULE_NAME);
259		free(identity);
260		return PAM_SERVICE_ERR;
261	}
262	/* pass prompt message to application and receive passphrase */
263	retval = pam_get_pass(pamh, &pass, prompt, options);
264	free(prompt);
265	if (retval != PAM_SUCCESS) {
266		free(identity);
267		return retval;
268	}
269	/*
270	 * Try to decrypt the private key with the passphrase provided.
271	 * If success, the user is authenticated.
272	 */
273	(void)setreuid(pwent->pw_uid, saved_uid);
274	retval = load_private_key(identity, pass, &key, &comment_priv);
275	free(identity);
276	(void)setuid(saved_uid);
277	if (!retval)
278		return PAM_AUTH_ERR;
279	/*
280	 * Save the key and comment to pass to ssh-agent in the session
281	 * phase.
282	 */
283	if ((retval = pam_set_data(pamh, "ssh_private_key", key.rsa,
284	    rsa_cleanup)) != PAM_SUCCESS) {
285		RSA_free(key.rsa);
286		free(comment_priv);
287		return retval;
288	}
289	if ((retval = pam_set_data(pamh, "ssh_key_comment", comment_priv,
290	    ssh_cleanup)) != PAM_SUCCESS) {
291		free(comment_priv);
292		return retval;
293	}
294	/*
295	 * Copy the passwd entry (in case successive calls are made)
296	 * and save it for the session phase.
297	 */
298	if (!(pwent_keep = malloc(sizeof *pwent))) {
299		syslog(LOG_CRIT, "%m");
300		return PAM_SERVICE_ERR;
301	}
302	(void)memcpy(pwent_keep, pwent, sizeof *pwent_keep);
303	if ((retval = pam_set_data(pamh, "ssh_passwd_entry", pwent_keep,
304	    ssh_cleanup)) != PAM_SUCCESS) {
305		free(pwent_keep);
306		return retval;
307	}
308	return PAM_SUCCESS;
309}
310
311
312PAM_EXTERN int
313pam_sm_setcred(
314	pam_handle_t	 *pamh,
315	int		  flags,
316	int		  argc,
317	const char	**argv)
318{
319	return PAM_SUCCESS;
320}
321
322
323typedef AuthenticationConnection AC;
324
325PAM_EXTERN int
326pam_sm_open_session(
327	pam_handle_t	 *pamh,
328	int		  flags,
329	int		  argc,
330	const char	**argv)
331{
332	AC		*ac;			/* to ssh-agent */
333	char		*comment;		/* on private key */
334	char		*env_end;		/* end of env */
335	char		*env_file;		/* to store env */
336	FILE		*env_fp;		/* env_file handle */
337	Key		 key;			/* user's private key */
338	FILE		*pipe;			/* ssh-agent handle */
339	const PASSWD	*pwent;			/* user's passwd entry */
340	int		 retval;		/* from calls */
341	uid_t		 saved_uid;		/* caller's uid */
342	ENV		*ssh_env;		/* env handle */
343	const char	*tty;			/* tty or display name */
344	char		 hname[MAXHOSTNAMELEN];	/* local hostname */
345	char		 parse[BUFSIZ];		/* commands output */
346
347	/* dump output of ssh-agent in ~/.ssh */
348	if ((retval = pam_get_data(pamh, "ssh_passwd_entry",
349	    (const void **)&pwent)) != PAM_SUCCESS)
350		return retval;
351	/* use the tty or X display name in the filename */
352	if ((retval = pam_get_item(pamh, PAM_TTY, (const void **)&tty))
353	    != PAM_SUCCESS)
354		return retval;
355	if (*tty == ':' && gethostname(hname, sizeof hname) == 0) {
356		if (asprintf(&env_file, "%s/.ssh/agent-%s%s",
357		    pwent->pw_dir, hname, tty) == -1) {
358			syslog(LOG_CRIT, "%s: %m", MODULE_NAME);
359			return PAM_SERVICE_ERR;
360		}
361	} else if (asprintf(&env_file, "%s/.ssh/agent-%s", pwent->pw_dir,
362	    tty) == -1) {
363		syslog(LOG_CRIT, "%s: %m", MODULE_NAME);
364		return PAM_SERVICE_ERR;
365	}
366	/* save the filename so we can delete the file on session close */
367	if ((retval = pam_set_data(pamh, "ssh_agent_env", env_file,
368	    ssh_cleanup)) != PAM_SUCCESS) {
369		free(env_file);
370		return retval;
371	}
372	/* start the agent as the user */
373	saved_uid = geteuid();
374	(void)seteuid(pwent->pw_uid);
375	env_fp = fopen(env_file, "w");
376	pipe = popen(PATH_SSH_AGENT, "r");
377	(void)seteuid(saved_uid);
378	if (!pipe) {
379		syslog(LOG_ERR, "%s: %s: %m", MODULE_NAME, PATH_SSH_AGENT);
380		if (env_fp)
381			(void)fclose(env_fp);
382		return PAM_SESSION_ERR;
383	}
384	if (!(ssh_env = env_new()))
385		return PAM_SESSION_ERR;
386	if ((retval = pam_set_data(pamh, "ssh_env_handle", ssh_env,
387	    env_cleanup)) != PAM_SUCCESS)
388		return retval;
389	while (fgets(parse, sizeof parse, pipe)) {
390		if (env_fp)
391			(void)fputs(parse, env_fp);
392		/*
393		 * Save environment for application with pam_putenv()
394		 * but also with env_* functions for our own call to
395		 * ssh_get_authentication_connection().
396		 */
397		if (strchr(parse, '=') && (env_end = strchr(parse, ';'))) {
398			*env_end = '\0';
399			/* pass to the application ... */
400			if (!((retval = pam_putenv(pamh, parse)) ==
401			    PAM_SUCCESS)) {
402				(void)pclose(pipe);
403				if (env_fp)
404					(void)fclose(env_fp);
405				env_destroy(ssh_env);
406				return PAM_SERVICE_ERR;
407			}
408			env_put(ssh_env, parse);
409		}
410	}
411	if (env_fp)
412		(void)fclose(env_fp);
413	switch (retval = pclose(pipe)) {
414	case -1:
415		syslog(LOG_ERR, "%s: %s: %m", MODULE_NAME, PATH_SSH_AGENT);
416		env_destroy(ssh_env);
417		return PAM_SESSION_ERR;
418	case 0:
419		break;
420	case 127:
421		syslog(LOG_ERR, "%s: cannot execute %s", MODULE_NAME,
422		    PATH_SSH_AGENT);
423		env_destroy(ssh_env);
424		return PAM_SESSION_ERR;
425	default:
426		syslog(LOG_ERR, "%s: %s exited with status %d",
427		    MODULE_NAME, PATH_SSH_AGENT, WEXITSTATUS(retval));
428		env_destroy(ssh_env);
429		return PAM_SESSION_ERR;
430	}
431	key.type = KEY_RSA;
432	/* connect to the agent and hand off the private key */
433	if ((retval = pam_get_data(pamh, "ssh_private_key",
434	    (const void **)&key.rsa)) != PAM_SUCCESS ||
435	    (retval = pam_get_data(pamh, "ssh_key_comment",
436	    (const void **)&comment)) != PAM_SUCCESS ||
437	    (retval = env_commit(ssh_env)) != PAM_SUCCESS) {
438		env_destroy(ssh_env);
439		return retval;
440	}
441	if (!(ac = ssh_get_authentication_connection())) {
442		syslog(LOG_ERR, "%s: could not connect to agent",
443		    MODULE_NAME);
444		env_destroy(ssh_env);
445		return PAM_SESSION_ERR;
446	}
447	retval = ssh_add_identity(ac, &key, comment);
448	ssh_close_authentication_connection(ac);
449	env_swap(ssh_env, 0);
450	return retval ? PAM_SUCCESS : PAM_SESSION_ERR;
451}
452
453
454PAM_EXTERN int
455pam_sm_close_session(
456	pam_handle_t	 *pamh,
457	int		  flags,
458	int		  argc,
459	const char	**argv)
460{
461	const char	*env_file;	/* ssh-agent environment */
462	int	 	 retval;	/* from calls */
463	ENV		*ssh_env;	/* env handle */
464
465	if ((retval = pam_get_data(pamh, "ssh_env_handle",
466	    (const void **)&ssh_env)) != PAM_SUCCESS)
467		return retval;
468	env_swap(ssh_env, 1);
469	/* kill the agent */
470	retval = system(PATH_SSH_AGENT " -k");
471	env_destroy(ssh_env);
472	switch (retval) {
473	case -1:
474		syslog(LOG_ERR, "%s: %s -k: %m", MODULE_NAME,
475		    PATH_SSH_AGENT);
476		return PAM_SESSION_ERR;
477	case 0:
478		break;
479	case 127:
480		syslog(LOG_ERR, "%s: cannot execute %s -k", MODULE_NAME,
481		    PATH_SSH_AGENT);
482		return PAM_SESSION_ERR;
483	default:
484		syslog(LOG_ERR, "%s: %s -k exited with status %d",
485		    MODULE_NAME, PATH_SSH_AGENT, WEXITSTATUS(retval));
486		return PAM_SESSION_ERR;
487	}
488	/* retrieve environment filename, then remove the file */
489	if ((retval = pam_get_data(pamh, "ssh_agent_env",
490	    (const void **)&env_file)) != PAM_SUCCESS)
491		return retval;
492	(void)unlink(env_file);
493	return PAM_SUCCESS;
494}
495
496
497PAM_MODULE_ENTRY(MODULE_NAME);
498