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