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