xref: /freebsd/lib/libpam/modules/pam_ssh/pam_ssh.c (revision 6adf353a56a161443406b44a45d00c688ca7b857)
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$
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 
62 void
63 key_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 
74 void
75 ssh_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 
90 int
91 auth_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 
150 PAM_EXTERN int
151 pam_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_RETURN(PAM_AUTH_ERR);
246 
247 	PAM_LOG("Done authenticating; got %d", authenticated);
248 
249 	/*
250 	 * Copy the passwd entry (in case successive calls are made)
251 	 * and save it for the session phase.
252 	 */
253 	pwd_keep = malloc(sizeof *pwd);
254 	if (pwd_keep == NULL) {
255 		syslog(LOG_CRIT, "%m");
256 		PAM_RETURN(PAM_SERVICE_ERR);
257 	}
258 	memcpy(pwd_keep, pwd, sizeof *pwd_keep);
259 	retval = pam_set_data(pamh, "ssh_passwd_entry", pwd_keep, ssh_cleanup);
260 	if (retval != PAM_SUCCESS) {
261 		free(pwd_keep);
262 		PAM_RETURN(retval);
263 	}
264 
265 	PAM_LOG("Saved ssh_passwd_entry");
266 
267 	PAM_RETURN(PAM_SUCCESS);
268 }
269 
270 
271 PAM_EXTERN int
272 pam_sm_setcred(pam_handle_t *pamh, int flags, int argc, const char **argv)
273 {
274 	struct options	 options;		/* module options */
275 
276 	pam_std_option(&options, NULL, argc, argv);
277 
278 	PAM_LOG("Options processed");
279 
280 	PAM_RETURN(PAM_SUCCESS);
281 }
282 
283 
284 typedef AuthenticationConnection AC;
285 
286 PAM_EXTERN int
287 pam_sm_open_session(pam_handle_t *pamh, int flags, int argc, const char **argv)
288 {
289 	struct options	 options;		/* module options */
290 	AC		*ac;			/* to ssh-agent */
291 	char		*agent_socket;		/* agent socket */
292 	char		*comment;		/* on private key */
293 	char		*env_end;		/* end of env */
294 	char		*env_file;		/* to store env */
295 	FILE		*env_fp;		/* env_file handle */
296 	char		*env_value;		/* envariable value */
297 	char		*data_name;		/* PAM state */
298 	int		 final;			/* final return value */
299 	int		 index;			/* for saved keys */
300 	Key		*key;			/* user's private key */
301 	FILE		*pipe;			/* ssh-agent handle */
302 	struct passwd	*pwd;			/* user's passwd entry */
303 	int		 retval;		/* from calls */
304 	uid_t		 saved_uid;		/* caller's uid */
305 	const char	*tty;			/* tty or display name */
306 	char		 hname[MAXHOSTNAMELEN];	/* local hostname */
307 	char		 env_string[BUFSIZ];   	/* environment string */
308 
309 	pam_std_option(&options, NULL, argc, argv);
310 
311 	PAM_LOG("Options processed");
312 
313 	/* dump output of ssh-agent in ~/.ssh */
314 	retval = pam_get_data(pamh, "ssh_passwd_entry", (const void **)&pwd);
315 	if (retval != PAM_SUCCESS)
316 		PAM_RETURN(retval);
317 
318 	PAM_LOG("Got ssh_passwd_entry");
319 
320 	/* use the tty or X display name in the filename */
321 	retval = pam_get_item(pamh, PAM_TTY, (const void **)&tty);
322 	if (retval != PAM_SUCCESS)
323 		PAM_RETURN(retval);
324 
325 	PAM_LOG("Got TTY");
326 
327 	if (gethostname(hname, sizeof hname) == 0) {
328 		if (asprintf(&env_file, "%s/.ssh/agent-%s%s%s",
329 		    pwd->pw_dir, hname, *tty == ':' ? "" : ":", tty)
330 		    == -1) {
331 			syslog(LOG_CRIT, "%s: %m", MODULE_NAME);
332 			PAM_RETURN(PAM_SERVICE_ERR);
333 		}
334 	}
335 	else if (asprintf(&env_file, "%s/.ssh/agent-%s", pwd->pw_dir,
336 	    tty) == -1) {
337 		syslog(LOG_CRIT, "%s: %m", MODULE_NAME);
338 		PAM_RETURN(PAM_SERVICE_ERR);
339 	}
340 
341 	PAM_LOG("Got env_file: %s", env_file);
342 
343 	/* save the filename so we can delete the file on session close */
344 	retval = pam_set_data(pamh, "ssh_agent_env", env_file, ssh_cleanup);
345 	if (retval != PAM_SUCCESS) {
346 		free(env_file);
347 		PAM_RETURN(retval);
348 	}
349 
350 	PAM_LOG("Saved env_file");
351 
352 	/* start the agent as the user */
353 	saved_uid = geteuid();
354 	seteuid(pwd->pw_uid);
355 	env_fp = fopen(env_file, "w");
356 	if (env_fp != NULL)
357 		chmod(env_file, S_IRUSR);
358 	pipe = popen(SSH_AGENT, "r");
359 	seteuid(saved_uid);
360 	if (!pipe) {
361 		syslog(LOG_ERR, "%s: %s: %m", MODULE_NAME, SSH_AGENT);
362 		if (env_fp)
363 			fclose(env_fp);
364 		PAM_RETURN(PAM_SESSION_ERR);
365 	}
366 
367 	PAM_LOG("Agent started as user");
368 
369 	/*
370 	 * Save environment for application with pam_putenv().
371 	 */
372 	agent_socket = NULL;
373 	while (fgets(env_string, sizeof env_string, pipe)) {
374 		if (env_fp)
375 			fputs(env_string, env_fp);
376 		env_value = strchr(env_string, '=');
377 		if (env_value == NULL) {
378 			env_end = strchr(env_value, ';');
379 			if (env_end != NULL)
380 				continue;
381 		}
382 		*env_end = '\0';
383 		/* pass to the application ... */
384 		retval = pam_putenv(pamh, env_string);
385 		if (retval != PAM_SUCCESS) {
386 			pclose(pipe);
387 			if (env_fp)
388 				fclose(env_fp);
389 			PAM_RETURN(PAM_SERVICE_ERR);
390 		}
391 		*env_value++ = '\0';
392 		if (strcmp(&env_string[strlen(env_string) -
393 		    strlen(ENV_SOCKET_SUFFIX)], ENV_SOCKET_SUFFIX) == 0) {
394 			agent_socket = strdup(env_value);
395 			if (agent_socket == NULL) {
396 				syslog(LOG_CRIT, "%s: %m", MODULE_NAME);
397 				PAM_RETURN(PAM_SERVICE_ERR);
398 			}
399 		}
400 		else if (strcmp(&env_string[strlen(env_string) -
401 		    strlen(ENV_PID_SUFFIX)], ENV_PID_SUFFIX) == 0) {
402 			retval = pam_set_data(pamh, "ssh_agent_pid",
403 			    env_value, ssh_cleanup);
404 			if (retval != PAM_SUCCESS)
405 				PAM_RETURN(retval);
406 		}
407 	}
408 	if (env_fp)
409 		fclose(env_fp);
410 	retval = pclose(pipe);
411 	switch (retval) {
412 	case -1:
413 		syslog(LOG_ERR, "%s: %s: %m", MODULE_NAME, SSH_AGENT);
414 		PAM_RETURN(PAM_SESSION_ERR);
415 	case 0:
416 		break;
417 	case 127:
418 		syslog(LOG_ERR, "%s: cannot execute %s", MODULE_NAME,
419 		    SSH_AGENT);
420 		PAM_RETURN(PAM_SESSION_ERR);
421 	default:
422 		syslog(LOG_ERR, "%s: %s exited %s %d", MODULE_NAME,
423 		    SSH_AGENT, WIFSIGNALED(retval) ? "on signal" :
424 		    "with status", WIFSIGNALED(retval) ? WTERMSIG(retval) :
425 		    WEXITSTATUS(retval));
426 		PAM_RETURN(PAM_SESSION_ERR);
427 	}
428 	if (agent_socket == NULL)
429 		PAM_RETURN(PAM_SESSION_ERR);
430 
431 	PAM_LOG("Environment saved");
432 
433 	/* connect to the agent */
434 	ac = ssh_get_authentication_connection();
435 	if (!ac) {
436 		syslog(LOG_ERR, "%s: %s: %m", MODULE_NAME, agent_socket);
437 		PAM_RETURN(PAM_SESSION_ERR);
438 	}
439 
440 	PAM_LOG("Connected to agent");
441 
442 	/* hand off each private key to the agent */
443 	final = 0;
444 	for (index = 0; ; index++) {
445 		if (!asprintf(&data_name, "ssh_private_key_%d", index)) {
446 			syslog(LOG_CRIT, "%s: %m", MODULE_NAME);
447 			ssh_close_authentication_connection(ac);
448 			PAM_RETURN(PAM_SERVICE_ERR);
449 		}
450 		retval = pam_get_data(pamh, data_name, (const void **)&key);
451 		free(data_name);
452 		if (retval != PAM_SUCCESS)
453 			break;
454 		if (!asprintf(&data_name, "ssh_key_comment_%d", index)) {
455 			syslog(LOG_CRIT, "%s: %m", MODULE_NAME);
456 			ssh_close_authentication_connection(ac);
457 			PAM_RETURN(PAM_SERVICE_ERR);
458 		}
459 		retval = pam_get_data(pamh, data_name, (const void **)&comment);
460 		free(data_name);
461 		if (retval != PAM_SUCCESS)
462 			break;
463 		retval = ssh_add_identity(ac, key, comment);
464 		if (!final)
465 			final = retval;
466 	}
467 	ssh_close_authentication_connection(ac);
468 
469 	PAM_LOG("Keys handed off");
470 
471 	PAM_RETURN(final ? PAM_SUCCESS : PAM_SESSION_ERR);
472 }
473 
474 
475 PAM_EXTERN int
476 pam_sm_close_session(pam_handle_t *pamh, int flags, int argc, const char **argv)
477 {
478 	struct options	 options;	/* module options */
479 	const char	*env_file;	/* ssh-agent environment */
480 	pid_t		 pid;		/* ssh-agent process id */
481 	int	 	 retval;	/* from calls */
482 	const char	*ssh_agent_pid;	/* ssh-agent pid string */
483 
484 	pam_std_option(&options, NULL, argc, argv);
485 
486 	PAM_LOG("Options processed");
487 
488 	/* retrieve environment filename, then remove the file */
489 	retval = pam_get_data(pamh, "ssh_agent_env", (const void **)&env_file);
490 	if (retval != PAM_SUCCESS)
491 		PAM_RETURN(retval);
492 	unlink(env_file);
493 
494 	PAM_LOG("Got ssh_agent_env");
495 
496 	/* retrieve the agent's process id */
497 	retval = pam_get_data(pamh, "ssh_agent_pid", (const void **)&ssh_agent_pid);
498 	if (retval != PAM_SUCCESS)
499 		PAM_RETURN(retval);
500 
501 	PAM_LOG("Got ssh_agent_pid");
502 
503 	/*
504 	 * Kill the agent.  SSH2 from SSH Communications Security does
505 	 * not have a -k option, so we just call kill().
506 	 */
507 	pid = atoi(ssh_agent_pid);
508 	if (pid <= 0)
509 		PAM_RETURN(PAM_SESSION_ERR);
510 	if (kill(pid, SIGTERM) != 0) {
511 		syslog(LOG_ERR, "%s: %s: %m", MODULE_NAME, ssh_agent_pid);
512 		PAM_RETURN(PAM_SESSION_ERR);
513 	}
514 
515 	PAM_LOG("Agent killed");
516 
517 	PAM_RETURN(PAM_SUCCESS);
518 }
519 
520 
521 PAM_MODULE_ENTRY(MODULE_NAME);
522