pam_ssh.c revision 91714
1/*-
2 * Copyright (c) 1999, 2000 Andrew J. Korty
3 * All rights reserved.
4 * Copyright (c) 2001 Networks Associates Technologies, Inc.
5 * All rights reserved.
6 *
7 * Portions of this software were developed for the FreeBSD Project by
8 * ThinkSec AS and NAI Labs, the Security Research Division of Network
9 * Associates, Inc.  under DARPA/SPAWAR contract N66001-01-C-8035
10 * ("CBOSS"), as part of the DARPA CHATS research program.
11 *
12 * Redistribution and use in source and binary forms, with or without
13 * modification, are permitted provided that the following conditions
14 * are met:
15 * 1. Redistributions of source code must retain the above copyright
16 *    notice, this list of conditions and the following disclaimer.
17 * 2. Redistributions in binary form must reproduce the above copyright
18 *    notice, this list of conditions and the following disclaimer in the
19 *    documentation and/or other materials provided with the distribution.
20 * 3. The name of the author may not be used to endorse or promote
21 *    products derived from this software without specific prior written
22 *    permission.
23 *
24 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
25 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
26 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
27 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
28 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
29 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
30 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
31 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
32 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
33 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
34 * SUCH DAMAGE.
35 */
36
37#include <sys/cdefs.h>
38__FBSDID("$FreeBSD: head/lib/libpam/modules/pam_ssh/pam_ssh.c 91714 2002-03-05 21:56:25Z des $");
39
40#include <sys/param.h>
41#include <sys/socket.h>
42#include <sys/stat.h>
43#include <sys/wait.h>
44
45#include <dirent.h>
46#include <pwd.h>
47#include <signal.h>
48#include <ssh.h>
49#include <stdio.h>
50#include <stdlib.h>
51#include <string.h>
52#include <syslog.h>
53#include <unistd.h>
54
55#define	PAM_SM_AUTH
56#define PAM_SM_ACCOUNT
57#define PAM_SM_SESSION
58#define PAM_SM_PASSWORD
59
60#include <security/pam_appl.h>
61#include <security/pam_modules.h>
62#include <security/pam_mod_misc.h>
63
64#include <openssl/dsa.h>
65#include <openssl/evp.h>
66
67#include "key.h"
68#include "authfd.h"
69#include "authfile.h"
70#include "log.h"
71#include "pam_ssh.h"
72
73static int auth_via_key(pam_handle_t *, int, const char *, const char *, const struct passwd *, const char *);
74static void key_cleanup(pam_handle_t *, void *, int);
75static void ssh_cleanup(pam_handle_t *, void *, int);
76
77/*
78 * Generic cleanup function for SSH "Key" type.
79 */
80
81static void
82key_cleanup(pam_handle_t *pamh __unused, void *data, int error_status __unused)
83{
84	if (data)
85		key_free(data);
86}
87
88
89/*
90 * Generic PAM cleanup function for this module.
91 */
92
93static void
94ssh_cleanup(pam_handle_t *pamh __unused, void *data, int error_status __unused)
95{
96	if (data)
97		free(data);
98}
99
100
101/*
102 * Authenticate a user's key by trying to decrypt it with the password
103 * provided.  The key and its comment are then stored for later
104 * retrieval by the session phase.  An increasing index is embedded in
105 * the PAM variable names so this function may be called multiple times
106 * for multiple keys.
107 */
108
109static int
110auth_via_key(pam_handle_t *pamh, int type, const char *file,
111    const char *dir, const struct passwd *user, const char *pass)
112{
113	char		*comment;		/* private key comment */
114	char		*data_name;		/* PAM state */
115	static int	 indx = 0;		/* for saved keys */
116	Key		*key;			/* user's key */
117	char		*path;			/* to key files */
118	int		 retval;		/* from calls */
119	uid_t		 saved_uid;		/* caller's uid */
120
121	/* locate the user's private key file */
122	if (!asprintf(&path, "%s/%s", dir, file)) {
123		syslog(LOG_CRIT, "%s: %m", MODULE_NAME);
124		return PAM_SERVICE_ERR;
125	}
126	saved_uid = geteuid();
127	/*
128	 * Try to decrypt the private key with the passphrase provided.
129	 * If success, the user is authenticated.
130	 */
131	seteuid(user->pw_uid);
132	key = key_load_private_type(type, path, pass, &comment);
133	free(path);
134	seteuid(saved_uid);
135	if (key == NULL)
136		return PAM_AUTH_ERR;
137	/*
138	 * Save the key and comment to pass to ssh-agent in the session
139	 * phase.
140	 */
141	if (!asprintf(&data_name, "ssh_private_key_%d", indx)) {
142		syslog(LOG_CRIT, "%s: %m", MODULE_NAME);
143		free(comment);
144		return PAM_SERVICE_ERR;
145	}
146	retval = pam_set_data(pamh, data_name, key, key_cleanup);
147	free(data_name);
148	if (retval != PAM_SUCCESS) {
149		key_free(key);
150		free(comment);
151		return retval;
152	}
153	if (!asprintf(&data_name, "ssh_key_comment_%d", indx)) {
154		syslog(LOG_CRIT, "%s: %m", MODULE_NAME);
155		free(comment);
156		return PAM_SERVICE_ERR;
157	}
158	retval = pam_set_data(pamh, data_name, comment, ssh_cleanup);
159	free(data_name);
160	if (retval != PAM_SUCCESS) {
161		free(comment);
162		return retval;
163	}
164	++indx;
165	return PAM_SUCCESS;
166}
167
168
169PAM_EXTERN int
170pam_sm_authenticate(pam_handle_t *pamh, int flags __unused, int argc, const char **argv)
171{
172	struct options	 options;		/* module options */
173	int		 authenticated;		/* user authenticated? */
174	char		*dotdir;		/* .ssh2 dir name */
175	struct dirent	*dotdir_ent;		/* .ssh2 dir entry */
176	DIR		*dotdir_p;		/* .ssh2 dir pointer */
177	const char	*pass;			/* passphrase */
178	struct passwd	*pwd;			/* user's passwd entry */
179	struct passwd	*pwd_keep;		/* our own copy */
180	int		 retval;		/* from calls */
181	int		 pam_auth_dsa;		/* Authorised via DSA */
182	int		 pam_auth_rsa;		/* Authorised via RSA */
183	const char	*user;			/* username */
184
185	pam_std_option(&options, NULL, argc, argv);
186
187	PAM_LOG("Options processed");
188
189	retval = pam_get_user(pamh, &user, NULL);
190	if (retval != PAM_SUCCESS)
191		PAM_RETURN(retval);
192	pwd = getpwnam(user);
193	if (pwd == NULL || pwd->pw_dir == NULL)
194		/* delay? */
195		PAM_RETURN(PAM_AUTH_ERR);
196
197	PAM_LOG("Got user: %s", user);
198
199	/*
200	 * Pass prompt message to application and receive
201	 * passphrase.
202	 */
203	retval = pam_get_authtok(pamh, &pass, NEED_PASSPHRASE);
204	if (retval != PAM_SUCCESS)
205		PAM_RETURN(retval);
206	OpenSSL_add_all_algorithms();	/* required for DSA */
207
208	PAM_LOG("Got passphrase");
209
210	/*
211	 * Either the DSA or the RSA key will authenticate us, but if
212	 * we can decrypt both, we'll do so here so we can cache them in
213	 * the session phase.
214	 */
215	if (!asprintf(&dotdir, "%s/%s", pwd->pw_dir, SSH_CLIENT_DIR)) {
216		syslog(LOG_CRIT, "%s: %m", MODULE_NAME);
217		PAM_RETURN(PAM_SERVICE_ERR);
218	}
219	pam_auth_dsa = auth_via_key(pamh, KEY_DSA, SSH_CLIENT_ID_DSA, dotdir,
220	    pwd, pass);
221	pam_auth_rsa = auth_via_key(pamh, KEY_RSA1, SSH_CLIENT_IDENTITY, dotdir,
222	    pwd, pass);
223	authenticated = 0;
224	if (pam_auth_dsa == PAM_SUCCESS)
225		authenticated++;
226	if (pam_auth_rsa == PAM_SUCCESS)
227		authenticated++;
228
229	PAM_LOG("Done pre-authenticating; got %d", authenticated);
230
231	/*
232	 * Compatibility with SSH2 from SSH Communications Security.
233	 */
234	if (!asprintf(&dotdir, "%s/%s", pwd->pw_dir, SSH2_CLIENT_DIR)) {
235		syslog(LOG_CRIT, "%s: %m", MODULE_NAME);
236		PAM_RETURN(PAM_SERVICE_ERR);
237	}
238	/*
239	 * Try to load anything that looks like a private key.  For
240	 * now, we only support DSA and RSA keys.
241	 */
242	dotdir_p = opendir(dotdir);
243	while (dotdir_p && (dotdir_ent = readdir(dotdir_p))) {
244		/* skip public keys */
245		if (strcmp(&dotdir_ent->d_name[dotdir_ent->d_namlen -
246		    strlen(SSH2_PUB_SUFFIX)], SSH2_PUB_SUFFIX) == 0)
247			continue;
248		/* DSA keys */
249		if (strncmp(dotdir_ent->d_name, SSH2_DSA_PREFIX,
250		    strlen(SSH2_DSA_PREFIX)) == 0)
251			retval = auth_via_key(pamh, KEY_DSA,
252			    dotdir_ent->d_name, dotdir, pwd, pass);
253		/* RSA keys */
254		else if (strncmp(dotdir_ent->d_name, SSH2_RSA_PREFIX,
255		    strlen(SSH2_RSA_PREFIX)) == 0)
256			retval = auth_via_key(pamh, KEY_RSA,
257			    dotdir_ent->d_name, dotdir, pwd, pass);
258		/* skip other files */
259		else
260			continue;
261		authenticated += (retval == PAM_SUCCESS);
262	}
263	if (!authenticated) {
264		PAM_VERBOSE_ERROR("SSH authentication refused");
265		PAM_RETURN(PAM_AUTH_ERR);
266	}
267
268	PAM_LOG("Done authenticating; got %d", authenticated);
269
270	/*
271	 * Copy the passwd entry (in case successive calls are made)
272	 * and save it for the session phase.
273	 */
274	pwd_keep = malloc(sizeof *pwd);
275	if (pwd_keep == NULL) {
276		syslog(LOG_CRIT, "%m");
277		PAM_RETURN(PAM_SERVICE_ERR);
278	}
279	memcpy(pwd_keep, pwd, sizeof *pwd_keep);
280	retval = pam_set_data(pamh, "ssh_passwd_entry", pwd_keep, ssh_cleanup);
281	if (retval != PAM_SUCCESS) {
282		free(pwd_keep);
283		PAM_RETURN(retval);
284	}
285
286	PAM_LOG("Saved ssh_passwd_entry");
287
288	PAM_RETURN(PAM_SUCCESS);
289}
290
291
292PAM_EXTERN int
293pam_sm_setcred(pam_handle_t *pamh __unused, int flags __unused, int argc, const char **argv)
294{
295	struct options	 options;		/* module options */
296
297	pam_std_option(&options, NULL, argc, argv);
298
299	PAM_LOG("Options processed");
300
301	PAM_RETURN(PAM_SUCCESS);
302}
303
304PAM_EXTERN int
305pam_sm_acct_mgmt(pam_handle_t *pamh __unused, int flags __unused, int argc ,const char **argv)
306{
307	struct options options;
308
309	pam_std_option(&options, NULL, argc, argv);
310
311	PAM_LOG("Options processed");
312
313	PAM_RETURN(PAM_IGNORE);
314}
315
316PAM_EXTERN int
317pam_sm_chauthtok(pam_handle_t *pamh __unused, int flags __unused, int argc, const char **argv)
318{
319	struct options options;
320
321	pam_std_option(&options, NULL, argc, argv);
322
323	PAM_LOG("Options processed");
324
325	PAM_RETURN(PAM_IGNORE);
326}
327
328typedef AuthenticationConnection AC;
329
330PAM_EXTERN int
331pam_sm_open_session(pam_handle_t *pamh, int flags __unused, int argc, const char **argv)
332{
333	struct options	 options;		/* module options */
334	AC		*ac;			/* to ssh-agent */
335	char		*agent_socket;		/* agent socket */
336	char		*comment;		/* on private key */
337	char		*env_end;		/* end of env */
338	char		*env_file;		/* to store env */
339	FILE		*env_fp;		/* env_file handle */
340	char		*env_value;		/* envariable value */
341	char		*data_name;		/* PAM state */
342	int		 final;			/* final return value */
343	int		 indx;			/* for saved keys */
344	Key		*key;			/* user's private key */
345	FILE		*lpipe;			/* ssh-agent handle */
346	struct passwd	*pwd;			/* user's passwd entry */
347	int		 retval;		/* from calls */
348	uid_t		 saved_uid;		/* caller's uid */
349	const char	*tty;			/* tty or display name */
350	char		 hname[MAXHOSTNAMELEN];	/* local hostname */
351	char		 env_string[BUFSIZ];   	/* environment string */
352
353	pam_std_option(&options, NULL, argc, argv);
354
355	PAM_LOG("Options processed");
356
357	/* dump output of ssh-agent in ~/.ssh */
358	retval = pam_get_data(pamh, "ssh_passwd_entry", (const void **)&pwd);
359	if (retval != PAM_SUCCESS)
360		PAM_RETURN(retval);
361
362	PAM_LOG("Got ssh_passwd_entry");
363
364	/* use the tty or X display name in the filename */
365	retval = pam_get_item(pamh, PAM_TTY, (const void **)&tty);
366	if (retval != PAM_SUCCESS)
367		PAM_RETURN(retval);
368
369	PAM_LOG("Got TTY");
370
371	if (gethostname(hname, sizeof hname) == 0) {
372		if (asprintf(&env_file, "%s/.ssh/agent-%s%s%s",
373		    pwd->pw_dir, hname, *tty == ':' ? "" : ":", tty)
374		    == -1) {
375			syslog(LOG_CRIT, "%s: %m", MODULE_NAME);
376			PAM_RETURN(PAM_SERVICE_ERR);
377		}
378	}
379	else if (asprintf(&env_file, "%s/.ssh/agent-%s", pwd->pw_dir,
380	    tty) == -1) {
381		syslog(LOG_CRIT, "%s: %m", MODULE_NAME);
382		PAM_RETURN(PAM_SERVICE_ERR);
383	}
384
385	PAM_LOG("Got env_file: %s", env_file);
386
387	/* save the filename so we can delete the file on session close */
388	retval = pam_set_data(pamh, "ssh_agent_env", env_file, ssh_cleanup);
389	if (retval != PAM_SUCCESS) {
390		free(env_file);
391		PAM_RETURN(retval);
392	}
393
394	PAM_LOG("Saved env_file");
395
396	/* start the agent as the user */
397	saved_uid = geteuid();
398	seteuid(pwd->pw_uid);
399	env_fp = fopen(env_file, "w");
400	if (env_fp != NULL)
401		chmod(env_file, S_IRUSR);
402	lpipe = popen(SSH_AGENT, "r");
403	seteuid(saved_uid);
404	if (!lpipe) {
405		syslog(LOG_ERR, "%s: %s: %m", MODULE_NAME, SSH_AGENT);
406		if (env_fp)
407			fclose(env_fp);
408		PAM_RETURN(PAM_SESSION_ERR);
409	}
410
411	PAM_LOG("Agent started as user");
412
413	/*
414	 * Save environment for application with pam_putenv().
415	 */
416	agent_socket = NULL;
417	while (fgets(env_string, sizeof env_string, lpipe)) {
418		if (env_fp)
419			fputs(env_string, env_fp);
420		env_value = strchr(env_string, '=');
421		if (env_value == NULL)
422			continue;
423		env_end = strchr(env_value, ';');
424		if (env_end == NULL)
425				continue;
426		*env_end = '\0';
427		/* pass to the application ... */
428		retval = pam_putenv(pamh, env_string);
429		if (retval != PAM_SUCCESS) {
430			pclose(lpipe);
431			if (env_fp)
432				fclose(env_fp);
433			PAM_RETURN(PAM_SERVICE_ERR);
434		}
435
436		PAM_LOG("Put to environment: %s", env_string);
437
438		*env_value++ = '\0';
439		if (strcmp(&env_string[strlen(env_string) -
440		    strlen(ENV_SOCKET_SUFFIX)], ENV_SOCKET_SUFFIX) == 0) {
441			agent_socket = strdup(env_value);
442			if (agent_socket == NULL) {
443				syslog(LOG_CRIT, "%s: %m", MODULE_NAME);
444				PAM_RETURN(PAM_SERVICE_ERR);
445			}
446		}
447		else if (strcmp(&env_string[strlen(env_string) -
448		    strlen(ENV_PID_SUFFIX)], ENV_PID_SUFFIX) == 0) {
449			env_value = strdup(env_value);
450			if (env_value == NULL) {
451				syslog(LOG_CRIT, "%s: %m", MODULE_NAME);
452				PAM_RETURN(PAM_SERVICE_ERR);
453			}
454			retval = pam_set_data(pamh, "ssh_agent_pid",
455			    env_value, ssh_cleanup);
456			if (retval != PAM_SUCCESS)
457				PAM_RETURN(retval);
458			PAM_LOG("Environment write successful");
459		}
460	}
461	if (env_fp)
462		fclose(env_fp);
463	retval = pclose(lpipe);
464	switch (retval) {
465	case -1:
466		syslog(LOG_ERR, "%s: %s: %m", MODULE_NAME, SSH_AGENT);
467		PAM_RETURN(PAM_SESSION_ERR);
468	case 0:
469		break;
470	case 127:
471		syslog(LOG_ERR, "%s: cannot execute %s", MODULE_NAME,
472		    SSH_AGENT);
473		PAM_RETURN(PAM_SESSION_ERR);
474	default:
475		syslog(LOG_ERR, "%s: %s exited %s %d", MODULE_NAME,
476		    SSH_AGENT, WIFSIGNALED(retval) ? "on signal" :
477		    "with status", WIFSIGNALED(retval) ? WTERMSIG(retval) :
478		    WEXITSTATUS(retval));
479		PAM_RETURN(PAM_SESSION_ERR);
480	}
481	if (agent_socket == NULL)
482		PAM_RETURN(PAM_SESSION_ERR);
483
484	PAM_LOG("Environment saved");
485
486	/*
487	 * Connect to the agent.
488	 *
489	 * XXX Because ssh_get_authentication_connection() gets the
490	 * XXX agent parameters from the environment, we have to
491	 * XXX temporarily replace the environment with the PAM
492	 * XXX environment list.  This is a hack.
493	 */
494	{
495		extern char **environ;
496		char **saved, **evp;
497
498		saved = environ;
499		if ((environ = pam_getenvlist(pamh)) == NULL) {
500			environ = saved;
501			syslog(LOG_ERR, "%s: %m", MODULE_NAME);
502			PAM_RETURN(PAM_BUF_ERR);
503		}
504		ac = ssh_get_authentication_connection();
505		for (evp = environ; *evp; evp++)
506			free(*evp);
507		free(environ);
508		environ = saved;
509	}
510	if (!ac) {
511		syslog(LOG_ERR, "%s: %s: %m", MODULE_NAME, agent_socket);
512		PAM_RETURN(PAM_SESSION_ERR);
513	}
514
515	PAM_LOG("Connected to agent");
516
517	/* hand off each private key to the agent */
518	final = 0;
519	for (indx = 0; ; indx++) {
520		if (!asprintf(&data_name, "ssh_private_key_%d", indx)) {
521			syslog(LOG_CRIT, "%s: %m", MODULE_NAME);
522			ssh_close_authentication_connection(ac);
523			PAM_RETURN(PAM_SERVICE_ERR);
524		}
525		retval = pam_get_data(pamh, data_name, (const void **)&key);
526		free(data_name);
527		if (retval != PAM_SUCCESS)
528			break;
529		if (!asprintf(&data_name, "ssh_key_comment_%d", indx)) {
530			syslog(LOG_CRIT, "%s: %m", MODULE_NAME);
531			ssh_close_authentication_connection(ac);
532			PAM_RETURN(PAM_SERVICE_ERR);
533		}
534		retval = pam_get_data(pamh, data_name, (const void **)&comment);
535		free(data_name);
536		if (retval != PAM_SUCCESS)
537			break;
538		retval = ssh_add_identity(ac, key, comment);
539		if (!final)
540			final = retval;
541	}
542	ssh_close_authentication_connection(ac);
543
544	PAM_LOG("Keys handed off");
545
546	PAM_RETURN(final ? PAM_SUCCESS : PAM_SESSION_ERR);
547}
548
549
550PAM_EXTERN int
551pam_sm_close_session(pam_handle_t *pamh, int flags __unused, int argc, const char **argv)
552{
553	struct options	 options;	/* module options */
554	const char	*env_file;	/* ssh-agent environment */
555	pid_t		 pid;		/* ssh-agent process id */
556	int	 	 retval;	/* from calls */
557	const char	*ssh_agent_pid;	/* ssh-agent pid string */
558
559	pam_std_option(&options, NULL, argc, argv);
560
561	PAM_LOG("Options processed");
562
563	/* retrieve environment filename, then remove the file */
564	retval = pam_get_data(pamh, "ssh_agent_env", (const void **)&env_file);
565	if (retval != PAM_SUCCESS)
566		PAM_RETURN(retval);
567	unlink(env_file);
568
569	PAM_LOG("Got ssh_agent_env");
570
571	/* retrieve the agent's process id */
572	retval = pam_get_data(pamh, "ssh_agent_pid", (const void **)&ssh_agent_pid);
573	if (retval != PAM_SUCCESS)
574		PAM_RETURN(retval);
575
576	PAM_LOG("Got ssh_agent_pid");
577
578	/*
579	 * Kill the agent.  SSH2 from SSH Communications Security does
580	 * not have a -k option, so we just call kill().
581	 */
582	pid = atoi(ssh_agent_pid);
583	if (pid <= 0)
584		PAM_RETURN(PAM_SESSION_ERR);
585	if (kill(pid, SIGTERM) != 0) {
586		syslog(LOG_ERR, "%s: %s: %m", MODULE_NAME, ssh_agent_pid);
587		PAM_RETURN(PAM_SESSION_ERR);
588	}
589
590	PAM_LOG("Agent killed");
591
592	PAM_RETURN(PAM_SUCCESS);
593}
594
595PAM_MODULE_ENTRY("pam_ssh");
596