pam_ssh.c revision 81476
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 81476 2001-08-10 19:21:45Z 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_VERBOSE_ERROR("SSH authentication refused");
246		PAM_RETURN(PAM_AUTH_ERR);
247	}
248
249	PAM_LOG("Done authenticating; got %d", authenticated);
250
251	/*
252	 * Copy the passwd entry (in case successive calls are made)
253	 * and save it for the session phase.
254	 */
255	pwd_keep = malloc(sizeof *pwd);
256	if (pwd_keep == NULL) {
257		syslog(LOG_CRIT, "%m");
258		PAM_RETURN(PAM_SERVICE_ERR);
259	}
260	memcpy(pwd_keep, pwd, sizeof *pwd_keep);
261	retval = pam_set_data(pamh, "ssh_passwd_entry", pwd_keep, ssh_cleanup);
262	if (retval != PAM_SUCCESS) {
263		free(pwd_keep);
264		PAM_RETURN(retval);
265	}
266
267	PAM_LOG("Saved ssh_passwd_entry");
268
269	PAM_RETURN(PAM_SUCCESS);
270}
271
272
273PAM_EXTERN int
274pam_sm_setcred(pam_handle_t *pamh, int flags, int argc, const char **argv)
275{
276	struct options	 options;		/* module options */
277
278	pam_std_option(&options, NULL, argc, argv);
279
280	PAM_LOG("Options processed");
281
282	PAM_RETURN(PAM_SUCCESS);
283}
284
285
286typedef AuthenticationConnection AC;
287
288PAM_EXTERN int
289pam_sm_open_session(pam_handle_t *pamh, int flags, int argc, const char **argv)
290{
291	struct options	 options;		/* module options */
292	AC		*ac;			/* to ssh-agent */
293	char		*agent_socket;		/* agent socket */
294	char		*comment;		/* on private key */
295	char		*env_end;		/* end of env */
296	char		*env_file;		/* to store env */
297	FILE		*env_fp;		/* env_file handle */
298	char		*env_value;		/* envariable value */
299	char		*data_name;		/* PAM state */
300	int		 final;			/* final return value */
301	int		 index;			/* for saved keys */
302	Key		*key;			/* user's private key */
303	FILE		*pipe;			/* ssh-agent handle */
304	struct passwd	*pwd;			/* user's passwd entry */
305	int		 retval;		/* from calls */
306	uid_t		 saved_uid;		/* caller's uid */
307	const char	*tty;			/* tty or display name */
308	char		 hname[MAXHOSTNAMELEN];	/* local hostname */
309	char		 env_string[BUFSIZ];   	/* environment string */
310
311	pam_std_option(&options, NULL, argc, argv);
312
313	PAM_LOG("Options processed");
314
315	/* dump output of ssh-agent in ~/.ssh */
316	retval = pam_get_data(pamh, "ssh_passwd_entry", (const void **)&pwd);
317	if (retval != PAM_SUCCESS)
318		PAM_RETURN(retval);
319
320	PAM_LOG("Got ssh_passwd_entry");
321
322	/* use the tty or X display name in the filename */
323	retval = pam_get_item(pamh, PAM_TTY, (const void **)&tty);
324	if (retval != PAM_SUCCESS)
325		PAM_RETURN(retval);
326
327	PAM_LOG("Got TTY");
328
329	if (gethostname(hname, sizeof hname) == 0) {
330		if (asprintf(&env_file, "%s/.ssh/agent-%s%s%s",
331		    pwd->pw_dir, hname, *tty == ':' ? "" : ":", tty)
332		    == -1) {
333			syslog(LOG_CRIT, "%s: %m", MODULE_NAME);
334			PAM_RETURN(PAM_SERVICE_ERR);
335		}
336	}
337	else if (asprintf(&env_file, "%s/.ssh/agent-%s", pwd->pw_dir,
338	    tty) == -1) {
339		syslog(LOG_CRIT, "%s: %m", MODULE_NAME);
340		PAM_RETURN(PAM_SERVICE_ERR);
341	}
342
343	PAM_LOG("Got env_file: %s", env_file);
344
345	/* save the filename so we can delete the file on session close */
346	retval = pam_set_data(pamh, "ssh_agent_env", env_file, ssh_cleanup);
347	if (retval != PAM_SUCCESS) {
348		free(env_file);
349		PAM_RETURN(retval);
350	}
351
352	PAM_LOG("Saved env_file");
353
354	/* start the agent as the user */
355	saved_uid = geteuid();
356	seteuid(pwd->pw_uid);
357	env_fp = fopen(env_file, "w");
358	if (env_fp != NULL)
359		chmod(env_file, S_IRUSR);
360	pipe = popen(SSH_AGENT, "r");
361	seteuid(saved_uid);
362	if (!pipe) {
363		syslog(LOG_ERR, "%s: %s: %m", MODULE_NAME, SSH_AGENT);
364		if (env_fp)
365			fclose(env_fp);
366		PAM_RETURN(PAM_SESSION_ERR);
367	}
368
369	PAM_LOG("Agent started as user");
370
371	/*
372	 * Save environment for application with pam_putenv().
373	 */
374	agent_socket = NULL;
375	while (fgets(env_string, sizeof env_string, pipe)) {
376		if (env_fp)
377			fputs(env_string, env_fp);
378		env_value = strchr(env_string, '=');
379		if (env_value == NULL) {
380			env_end = strchr(env_value, ';');
381			if (env_end == NULL)
382				continue;
383			*env_end = '\0';
384		}
385		/* pass to the application ... */
386		retval = pam_putenv(pamh, env_string);
387		if (retval != PAM_SUCCESS) {
388			pclose(pipe);
389			if (env_fp)
390				fclose(env_fp);
391			PAM_RETURN(PAM_SERVICE_ERR);
392		}
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