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