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