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