pam_ssh.c revision 81036
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 81036 2001-08-02 10:35:41Z markm $
27 *
28 */
29
30
31#include <sys/param.h>
32#include <sys/stat.h>
33#include <sys/wait.h>
34
35#include <dirent.h>
36#include <pwd.h>
37#include <signal.h>
38#include <ssh.h>
39#include <stdio.h>
40#include <stdlib.h>
41#include <string.h>
42#include <unistd.h>
43
44#define	PAM_SM_AUTH
45#define	PAM_SM_SESSION
46#include <security/pam_modules.h>
47#include <security/pam_mod_misc.h>
48
49#include <openssl/dsa.h>
50#include <openssl/evp.h>
51
52#include "key.h"
53#include "authfd.h"
54#include "authfile.h"
55#include "log.h"
56#include "pam_ssh.h"
57
58/*
59 * Generic cleanup function for SSH "Key" type.
60 */
61
62void
63key_cleanup(pam_handle_t *pamh, void *data, int error_status)
64{
65	if (data)
66		key_free(data);
67}
68
69
70/*
71 * Generic PAM cleanup function for this module.
72 */
73
74void
75ssh_cleanup(pam_handle_t *pamh, void *data, int error_status)
76{
77	if (data)
78		free(data);
79}
80
81
82/*
83 * Authenticate a user's key by trying to decrypt it with the password
84 * provided.  The key and its comment are then stored for later
85 * retrieval by the session phase.  An increasing index is embedded in
86 * the PAM variable names so this function may be called multiple times
87 * for multiple keys.
88 */
89
90int
91auth_via_key(pam_handle_t *pamh, int type, const char *file,
92    const char *dir, const struct passwd *user, const char *pass)
93{
94	char		*comment;		/* private key comment */
95	char		*data_name;		/* PAM state */
96	static int	 index = 0;		/* for saved keys */
97	Key		*key;			/* user's key */
98	char		*path;			/* to key files */
99	int		 retval;		/* from calls */
100	uid_t		 saved_uid;		/* caller's uid */
101
102	/* locate the user's private key file */
103	if (!asprintf(&path, "%s/%s", dir, file)) {
104		syslog(LOG_CRIT, "%s: %m", MODULE_NAME);
105		return PAM_SERVICE_ERR;
106	}
107	saved_uid = geteuid();
108	/*
109	 * Try to decrypt the private key with the passphrase provided.
110	 * If success, the user is authenticated.
111	 */
112	seteuid(user->pw_uid);
113	key = key_load_private_type(type, path, pass, &comment);
114	free(path);
115	seteuid(saved_uid);
116	if (key == NULL)
117		return PAM_AUTH_ERR;
118	/*
119	 * Save the key and comment to pass to ssh-agent in the session
120	 * phase.
121	 */
122	if (!asprintf(&data_name, "ssh_private_key_%d", index)) {
123		syslog(LOG_CRIT, "%s: %m", MODULE_NAME);
124		free(comment);
125		return PAM_SERVICE_ERR;
126	}
127	retval = pam_set_data(pamh, data_name, key, key_cleanup);
128	free(data_name);
129	if (retval != PAM_SUCCESS) {
130		key_free(key);
131		free(comment);
132		return retval;
133	}
134	if (!asprintf(&data_name, "ssh_key_comment_%d", index)) {
135		syslog(LOG_CRIT, "%s: %m", MODULE_NAME);
136		free(comment);
137		return PAM_SERVICE_ERR;
138	}
139	retval = pam_set_data(pamh, data_name, comment, ssh_cleanup);
140	free(data_name);
141	if (retval != PAM_SUCCESS) {
142		free(comment);
143		return retval;
144	}
145	++index;
146	return PAM_SUCCESS;
147}
148
149
150PAM_EXTERN int
151pam_sm_authenticate(pam_handle_t *pamh, int flags, int argc, const char **argv)
152{
153	struct options	 options;		/* module options */
154	int		 authenticated;		/* user authenticated? */
155	char		*dotdir;		/* .ssh2 dir name */
156	struct dirent	*dotdir_ent;		/* .ssh2 dir entry */
157	DIR		*dotdir_p;		/* .ssh2 dir pointer */
158	const char	*pass;			/* passphrase */
159	struct passwd	*pwd;			/* user's passwd entry */
160	struct passwd	*pwd_keep;		/* our own copy */
161	int		 retval;		/* from calls */
162	int		 pam_auth_dsa;		/* Authorised via DSA */
163	int		 pam_auth_rsa;		/* Authorised via RSA */
164	const char	*user;			/* username */
165
166	pam_std_option(&options, NULL, argc, argv);
167
168	PAM_LOG("Options processed");
169
170	retval = pam_get_user(pamh, &user, NULL);
171	if (retval != PAM_SUCCESS)
172		PAM_RETURN(retval);
173	pwd = getpwnam(user);
174	if (pwd == NULL || pwd->pw_dir == NULL)
175		/* delay? */
176		PAM_RETURN(PAM_AUTH_ERR);
177
178	PAM_LOG("Got user: %s", user);
179
180	/*
181	 * Pass prompt message to application and receive
182	 * passphrase.
183	 */
184	retval = pam_get_pass(pamh, &pass, NEED_PASSPHRASE, &options);
185	if (retval != PAM_SUCCESS)
186		PAM_RETURN(retval);
187	OpenSSL_add_all_algorithms();	/* required for DSA */
188
189	PAM_LOG("Got passphrase");
190
191	/*
192	 * Either the DSA or the RSA key will authenticate us, but if
193	 * we can decrypt both, we'll do so here so we can cache them in
194	 * the session phase.
195	 */
196	if (!asprintf(&dotdir, "%s/%s", pwd->pw_dir, SSH_CLIENT_DIR)) {
197		syslog(LOG_CRIT, "%s: %m", MODULE_NAME);
198		PAM_RETURN(PAM_SERVICE_ERR);
199	}
200	pam_auth_dsa = auth_via_key(pamh, KEY_DSA, SSH_CLIENT_ID_DSA, dotdir,
201	    pwd, pass);
202	pam_auth_rsa = auth_via_key(pamh, KEY_RSA, SSH_CLIENT_IDENTITY, dotdir,
203	    pwd, pass);
204	authenticated = 0;
205	if (pam_auth_dsa == PAM_SUCCESS)
206		authenticated++;
207	if (pam_auth_rsa == PAM_SUCCESS)
208		authenticated++;
209
210	PAM_LOG("Done pre-authenticating; got %d", authenticated);
211
212	/*
213	 * Compatibility with SSH2 from SSH Communications Security.
214	 */
215	if (!asprintf(&dotdir, "%s/%s", pwd->pw_dir, SSH2_CLIENT_DIR)) {
216		syslog(LOG_CRIT, "%s: %m", MODULE_NAME);
217		PAM_RETURN(PAM_SERVICE_ERR);
218	}
219	/*
220	 * Try to load anything that looks like a private key.  For
221	 * now, we only support DSA and RSA keys.
222	 */
223	dotdir_p = opendir(dotdir);
224	while (dotdir_p && (dotdir_ent = readdir(dotdir_p))) {
225		/* skip public keys */
226		if (strcmp(&dotdir_ent->d_name[dotdir_ent->d_namlen -
227		    strlen(SSH2_PUB_SUFFIX)], SSH2_PUB_SUFFIX) == 0)
228			continue;
229		/* DSA keys */
230		if (strncmp(dotdir_ent->d_name, SSH2_DSA_PREFIX,
231		    strlen(SSH2_DSA_PREFIX)) == 0)
232			retval = auth_via_key(pamh, KEY_DSA,
233			    dotdir_ent->d_name, dotdir, pwd, pass);
234		/* RSA keys */
235		else if (strncmp(dotdir_ent->d_name, SSH2_RSA_PREFIX,
236		    strlen(SSH2_RSA_PREFIX)) == 0)
237			retval = auth_via_key(pamh, KEY_DSA,
238			    dotdir_ent->d_name, dotdir, pwd, pass);
239		/* skip other files */
240		else
241			continue;
242		authenticated += (retval == PAM_SUCCESS);
243	}
244	if (!authenticated)
245		PAM_RETURN(PAM_AUTH_ERR);
246
247	PAM_LOG("Done authenticating; got %d", authenticated);
248
249	/*
250	 * Copy the passwd entry (in case successive calls are made)
251	 * and save it for the session phase.
252	 */
253	pwd_keep = malloc(sizeof *pwd);
254	if (pwd_keep == NULL) {
255		syslog(LOG_CRIT, "%m");
256		PAM_RETURN(PAM_SERVICE_ERR);
257	}
258	memcpy(pwd_keep, pwd, sizeof *pwd_keep);
259	retval = pam_set_data(pamh, "ssh_passwd_entry", pwd_keep, ssh_cleanup);
260	if (retval != PAM_SUCCESS) {
261		free(pwd_keep);
262		PAM_RETURN(retval);
263	}
264
265	PAM_LOG("Saved ssh_passwd_entry");
266
267	PAM_RETURN(PAM_SUCCESS);
268}
269
270
271PAM_EXTERN int
272pam_sm_setcred(pam_handle_t *pamh, int flags, int argc, const char **argv)
273{
274	struct options	 options;		/* module options */
275
276	pam_std_option(&options, NULL, argc, argv);
277
278	PAM_LOG("Options processed");
279
280	PAM_RETURN(PAM_SUCCESS);
281}
282
283
284typedef AuthenticationConnection AC;
285
286PAM_EXTERN int
287pam_sm_open_session(pam_handle_t *pamh, int flags, int argc, const char **argv)
288{
289	struct options	 options;		/* module options */
290	AC		*ac;			/* to ssh-agent */
291	char		*agent_socket;		/* agent socket */
292	char		*comment;		/* on private key */
293	char		*env_end;		/* end of env */
294	char		*env_file;		/* to store env */
295	FILE		*env_fp;		/* env_file handle */
296	char		*env_value;		/* envariable value */
297	char		*data_name;		/* PAM state */
298	int		 final;			/* final return value */
299	int		 index;			/* for saved keys */
300	Key		*key;			/* user's private key */
301	FILE		*pipe;			/* ssh-agent handle */
302	struct passwd	*pwd;			/* user's passwd entry */
303	int		 retval;		/* from calls */
304	uid_t		 saved_uid;		/* caller's uid */
305	const char	*tty;			/* tty or display name */
306	char		 hname[MAXHOSTNAMELEN];	/* local hostname */
307	char		 env_string[BUFSIZ];   	/* environment string */
308
309	pam_std_option(&options, NULL, argc, argv);
310
311	PAM_LOG("Options processed");
312
313	/* dump output of ssh-agent in ~/.ssh */
314	retval = pam_get_data(pamh, "ssh_passwd_entry", (const void **)&pwd);
315	if (retval != PAM_SUCCESS)
316		PAM_RETURN(retval);
317
318	PAM_LOG("Got ssh_passwd_entry");
319
320	/* use the tty or X display name in the filename */
321	retval = pam_get_item(pamh, PAM_TTY, (const void **)&tty);
322	if (retval != PAM_SUCCESS)
323		PAM_RETURN(retval);
324
325	PAM_LOG("Got TTY");
326
327	if (gethostname(hname, sizeof hname) == 0) {
328		if (asprintf(&env_file, "%s/.ssh/agent-%s%s%s",
329		    pwd->pw_dir, hname, *tty == ':' ? "" : ":", tty)
330		    == -1) {
331			syslog(LOG_CRIT, "%s: %m", MODULE_NAME);
332			PAM_RETURN(PAM_SERVICE_ERR);
333		}
334	}
335	else if (asprintf(&env_file, "%s/.ssh/agent-%s", pwd->pw_dir,
336	    tty) == -1) {
337		syslog(LOG_CRIT, "%s: %m", MODULE_NAME);
338		PAM_RETURN(PAM_SERVICE_ERR);
339	}
340
341	PAM_LOG("Got env_file: %s", env_file);
342
343	/* save the filename so we can delete the file on session close */
344	retval = pam_set_data(pamh, "ssh_agent_env", env_file, ssh_cleanup);
345	if (retval != PAM_SUCCESS) {
346		free(env_file);
347		PAM_RETURN(retval);
348	}
349
350	PAM_LOG("Saved env_file");
351
352	/* start the agent as the user */
353	saved_uid = geteuid();
354	seteuid(pwd->pw_uid);
355	env_fp = fopen(env_file, "w");
356	if (env_fp != NULL)
357		chmod(env_file, S_IRUSR);
358	pipe = popen(SSH_AGENT, "r");
359	seteuid(saved_uid);
360	if (!pipe) {
361		syslog(LOG_ERR, "%s: %s: %m", MODULE_NAME, SSH_AGENT);
362		if (env_fp)
363			fclose(env_fp);
364		PAM_RETURN(PAM_SESSION_ERR);
365	}
366
367	PAM_LOG("Agent started as user");
368
369	/*
370	 * Save environment for application with pam_putenv().
371	 */
372	agent_socket = NULL;
373	while (fgets(env_string, sizeof env_string, pipe)) {
374		if (env_fp)
375			fputs(env_string, env_fp);
376		env_value = strchr(env_string, '=');
377		if (env_value == NULL) {
378			env_end = strchr(env_value, ';');
379			if (env_end != NULL)
380				continue;
381		}
382		*env_end = '\0';
383		/* pass to the application ... */
384		retval = pam_putenv(pamh, env_string);
385		if (retval != PAM_SUCCESS) {
386			pclose(pipe);
387			if (env_fp)
388				fclose(env_fp);
389			PAM_RETURN(PAM_SERVICE_ERR);
390		}
391		*env_value++ = '\0';
392		if (strcmp(&env_string[strlen(env_string) -
393		    strlen(ENV_SOCKET_SUFFIX)], ENV_SOCKET_SUFFIX) == 0) {
394			agent_socket = strdup(env_value);
395			if (agent_socket == NULL) {
396				syslog(LOG_CRIT, "%s: %m", MODULE_NAME);
397				PAM_RETURN(PAM_SERVICE_ERR);
398			}
399		}
400		else if (strcmp(&env_string[strlen(env_string) -
401		    strlen(ENV_PID_SUFFIX)], ENV_PID_SUFFIX) == 0) {
402			retval = pam_set_data(pamh, "ssh_agent_pid",
403			    env_value, ssh_cleanup);
404			if (retval != PAM_SUCCESS)
405				PAM_RETURN(retval);
406		}
407	}
408	if (env_fp)
409		fclose(env_fp);
410	retval = pclose(pipe);
411	switch (retval) {
412	case -1:
413		syslog(LOG_ERR, "%s: %s: %m", MODULE_NAME, SSH_AGENT);
414		PAM_RETURN(PAM_SESSION_ERR);
415	case 0:
416		break;
417	case 127:
418		syslog(LOG_ERR, "%s: cannot execute %s", MODULE_NAME,
419		    SSH_AGENT);
420		PAM_RETURN(PAM_SESSION_ERR);
421	default:
422		syslog(LOG_ERR, "%s: %s exited %s %d", MODULE_NAME,
423		    SSH_AGENT, WIFSIGNALED(retval) ? "on signal" :
424		    "with status", WIFSIGNALED(retval) ? WTERMSIG(retval) :
425		    WEXITSTATUS(retval));
426		PAM_RETURN(PAM_SESSION_ERR);
427	}
428	if (agent_socket == NULL)
429		PAM_RETURN(PAM_SESSION_ERR);
430
431	PAM_LOG("Environment saved");
432
433	/* connect to the agent */
434	ac = ssh_get_authentication_connection();
435	if (!ac) {
436		syslog(LOG_ERR, "%s: %s: %m", MODULE_NAME, agent_socket);
437		PAM_RETURN(PAM_SESSION_ERR);
438	}
439
440	PAM_LOG("Connected to agent");
441
442	/* hand off each private key to the agent */
443	final = 0;
444	for (index = 0; ; index++) {
445		if (!asprintf(&data_name, "ssh_private_key_%d", index)) {
446			syslog(LOG_CRIT, "%s: %m", MODULE_NAME);
447			ssh_close_authentication_connection(ac);
448			PAM_RETURN(PAM_SERVICE_ERR);
449		}
450		retval = pam_get_data(pamh, data_name, (const void **)&key);
451		free(data_name);
452		if (retval != PAM_SUCCESS)
453			break;
454		if (!asprintf(&data_name, "ssh_key_comment_%d", index)) {
455			syslog(LOG_CRIT, "%s: %m", MODULE_NAME);
456			ssh_close_authentication_connection(ac);
457			PAM_RETURN(PAM_SERVICE_ERR);
458		}
459		retval = pam_get_data(pamh, data_name, (const void **)&comment);
460		free(data_name);
461		if (retval != PAM_SUCCESS)
462			break;
463		retval = ssh_add_identity(ac, key, comment);
464		if (!final)
465			final = retval;
466	}
467	ssh_close_authentication_connection(ac);
468
469	PAM_LOG("Keys handed off");
470
471	PAM_RETURN(final ? PAM_SUCCESS : PAM_SESSION_ERR);
472}
473
474
475PAM_EXTERN int
476pam_sm_close_session(pam_handle_t *pamh, int flags, int argc, const char **argv)
477{
478	struct options	 options;	/* module options */
479	const char	*env_file;	/* ssh-agent environment */
480	pid_t		 pid;		/* ssh-agent process id */
481	int	 	 retval;	/* from calls */
482	const char	*ssh_agent_pid;	/* ssh-agent pid string */
483
484	pam_std_option(&options, NULL, argc, argv);
485
486	PAM_LOG("Options processed");
487
488	/* retrieve environment filename, then remove the file */
489	retval = pam_get_data(pamh, "ssh_agent_env", (const void **)&env_file);
490	if (retval != PAM_SUCCESS)
491		PAM_RETURN(retval);
492	unlink(env_file);
493
494	PAM_LOG("Got ssh_agent_env");
495
496	/* retrieve the agent's process id */
497	retval = pam_get_data(pamh, "ssh_agent_pid", (const void **)&ssh_agent_pid);
498	if (retval != PAM_SUCCESS)
499		PAM_RETURN(retval);
500
501	PAM_LOG("Got ssh_agent_pid");
502
503	/*
504	 * Kill the agent.  SSH2 from SSH Communications Security does
505	 * not have a -k option, so we just call kill().
506	 */
507	pid = atoi(ssh_agent_pid);
508	if (pid <= 0)
509		PAM_RETURN(PAM_SESSION_ERR);
510	if (kill(pid, SIGTERM) != 0) {
511		syslog(LOG_ERR, "%s: %s: %m", MODULE_NAME, ssh_agent_pid);
512		PAM_RETURN(PAM_SESSION_ERR);
513	}
514
515	PAM_LOG("Agent killed");
516
517	PAM_RETURN(PAM_SUCCESS);
518}
519
520
521PAM_MODULE_ENTRY(MODULE_NAME);
522