xref: /freebsd/lib/libpam/modules/pam_ssh/pam_ssh.c (revision 5521ff5a4d1929056e7ffc982fac3341ca54df7c)
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/queue.h>
33 
34 #include <fcntl.h>
35 #include <paths.h>
36 #include <pwd.h>
37 #include <stdio.h>
38 #include <stdlib.h>
39 #include <string.h>
40 #include <unistd.h>
41 
42 #define	PAM_SM_AUTH
43 #define	PAM_SM_SESSION
44 #include <security/pam_modules.h>
45 #include <security/pam_mod_misc.h>
46 
47 #include <openssl/dsa.h>
48 
49 #include "includes.h"
50 #include "rsa.h"
51 #include "key.h"
52 #include "ssh.h"
53 #include "authfd.h"
54 #include "authfile.h"
55 
56 #define	MODULE_NAME	"pam_ssh"
57 #define	NEED_PASSPHRASE	"Need passphrase for %s (%s).\nEnter passphrase: "
58 #define	PATH_SSH_AGENT	"/usr/bin/ssh-agent"
59 
60 
61 void
62 rsa_cleanup(pam_handle_t *pamh, void *data, int error_status)
63 {
64 	if (data)
65 		RSA_free(data);
66 }
67 
68 
69 void
70 ssh_cleanup(pam_handle_t *pamh, void *data, int error_status)
71 {
72 	if (data)
73 		free(data);
74 }
75 
76 
77 /*
78  * The following set of functions allow the module to manipulate the
79  * environment without calling the putenv() or setenv() stdlib functions.
80  * At least one version of these functions, on the first call, copies
81  * the environment into dynamically-allocated memory and then augments
82  * it.  On subsequent calls, the realloc() call is used to grow the
83  * previously allocated buffer.  Problems arise when the "environ"
84  * variable is changed to point to static memory after putenv()/setenv()
85  * have been called.
86  *
87  * We don't use putenv() or setenv() in case the application subsequently
88  * manipulates environ, (e.g., to clear the environment by pointing
89  * environ at an array of one element equal to NULL).
90  */
91 
92 SLIST_HEAD(env_head, env_entry);
93 
94 struct env_entry {
95 	char			*ee_env;
96 	SLIST_ENTRY(env_entry)	 ee_entries;
97 };
98 
99 typedef struct env {
100 	char		**e_environ_orig;
101 	char		**e_environ_new;
102 	int		  e_count;
103 	struct env_head	  e_head;
104 	int		  e_committed;
105 } ENV;
106 
107 extern char **environ;
108 
109 
110 static ENV *
111 env_new(void)
112 {
113 	ENV	*self;
114 
115 	if (!(self = malloc(sizeof (ENV)))) {
116 		syslog(LOG_CRIT, "%m");
117 		return NULL;
118 	}
119 	SLIST_INIT(&self->e_head);
120 	self->e_count = 0;
121 	self->e_committed = 0;
122 	return self;
123 }
124 
125 
126 static int
127 env_put(ENV *self, char *s)
128 {
129 	struct env_entry	*env;
130 
131 	if (!(env = malloc(sizeof (struct env_entry))) ||
132 	    !(env->ee_env = strdup(s))) {
133 		syslog(LOG_CRIT, "%m");
134 		return PAM_SERVICE_ERR;
135 	}
136 	SLIST_INSERT_HEAD(&self->e_head, env, ee_entries);
137 	++self->e_count;
138 	return PAM_SUCCESS;
139 }
140 
141 
142 static void
143 env_swap(ENV *self, int which)
144 {
145 	environ = which ? self->e_environ_new : self->e_environ_orig;
146 }
147 
148 
149 static int
150 env_commit(ENV *self)
151 {
152 	int			  n;
153 	struct env_entry	 *p;
154 	char 			**v;
155 
156 	for (v = environ, n = 0; v && *v; v++, n++)
157 		;
158 	if (!(v = malloc((n + self->e_count + 1) * sizeof (char *)))) {
159 		syslog(LOG_CRIT, "%m");
160 		return PAM_SERVICE_ERR;
161 	}
162 	self->e_committed = 1;
163 	(void)memcpy(v, environ, n * sizeof (char *));
164 	SLIST_FOREACH(p, &self->e_head, ee_entries)
165 		v[n++] = p->ee_env;
166 	v[n] = NULL;
167 	self->e_environ_orig = environ;
168 	self->e_environ_new = v;
169 	env_swap(self, 1);
170 	return PAM_SUCCESS;
171 }
172 
173 
174 static void
175 env_destroy(ENV *self)
176 {
177 	struct env_entry	 *p;
178 
179 	if (self->e_committed)
180 		env_swap(self, 0);
181 	SLIST_FOREACH(p, &self->e_head, ee_entries) {
182 		free(p->ee_env);
183 		free(p);
184 	}
185 	if (self->e_committed)
186 		free(self->e_environ_new);
187 	free(self);
188 }
189 
190 
191 void
192 env_cleanup(pam_handle_t *pamh, void *data, int error_status)
193 {
194 	if (data)
195 		env_destroy(data);
196 }
197 
198 
199 typedef struct passwd PASSWD;
200 
201 PAM_EXTERN int
202 pam_sm_authenticate(
203 	pam_handle_t	 *pamh,
204 	int		  flags,
205 	int		  argc,
206 	const char	**argv)
207 {
208 	char		*comment_priv;		/* on private key */
209 	char		*comment_pub;		/* on public key */
210 	char		*identity;		/* user's identity file */
211 	Key		 key;			/* user's private key */
212 	int		 options;		/* module options */
213 	const char	*pass;			/* passphrase */
214 	char		*prompt;		/* passphrase prompt */
215 	Key		 public_key;		/* user's public key */
216 	const PASSWD	*pwent;			/* user's passwd entry */
217 	PASSWD		*pwent_keep;		/* our own copy */
218 	int		 retval;		/* from calls */
219 	uid_t		 saved_uid;		/* caller's uid */
220 	const char	*user;			/* username */
221 
222 	options = 0;
223 	while (argc--)
224 		pam_std_option(&options, *argv++);
225 	if ((retval = pam_get_user(pamh, &user, NULL)) != PAM_SUCCESS)
226 		return retval;
227 	if (!((pwent = getpwnam(user)) && pwent->pw_dir)) {
228 		/* delay? */
229 		return PAM_AUTH_ERR;
230 	}
231 	/* locate the user's private key file */
232 	if (!asprintf(&identity, "%s/%s", pwent->pw_dir,
233 	    SSH_CLIENT_IDENTITY)) {
234 		syslog(LOG_CRIT, "%s: %m", MODULE_NAME);
235 		return PAM_SERVICE_ERR;
236 	}
237 	/*
238 	 * Fail unless we can load the public key.  Change to the
239 	 * owner's UID to appease load_public_key().
240 	 */
241 	key.type = KEY_RSA;
242 	key.rsa = RSA_new();
243 	public_key.type = KEY_RSA;
244 	public_key.rsa = RSA_new();
245 	saved_uid = getuid();
246 	(void)setreuid(pwent->pw_uid, saved_uid);
247 	retval = load_public_key(identity, &public_key, &comment_pub);
248 	(void)setuid(saved_uid);
249 	if (!retval) {
250 		free(identity);
251 		return PAM_AUTH_ERR;
252 	}
253 	RSA_free(public_key.rsa);
254 	/* build the passphrase prompt */
255 	retval = asprintf(&prompt, NEED_PASSPHRASE, identity, comment_pub);
256 	free(comment_pub);
257 	if (!retval) {
258 		syslog(LOG_CRIT, "%s: %m", MODULE_NAME);
259 		free(identity);
260 		return PAM_SERVICE_ERR;
261 	}
262 	/* pass prompt message to application and receive passphrase */
263 	retval = pam_get_pass(pamh, &pass, prompt, options);
264 	free(prompt);
265 	if (retval != PAM_SUCCESS) {
266 		free(identity);
267 		return retval;
268 	}
269 	/*
270 	 * Try to decrypt the private key with the passphrase provided.
271 	 * If success, the user is authenticated.
272 	 */
273 	(void)setreuid(pwent->pw_uid, saved_uid);
274 	retval = load_private_key(identity, pass, &key, &comment_priv);
275 	free(identity);
276 	(void)setuid(saved_uid);
277 	if (!retval)
278 		return PAM_AUTH_ERR;
279 	/*
280 	 * Save the key and comment to pass to ssh-agent in the session
281 	 * phase.
282 	 */
283 	if ((retval = pam_set_data(pamh, "ssh_private_key", key.rsa,
284 	    rsa_cleanup)) != PAM_SUCCESS) {
285 		RSA_free(key.rsa);
286 		free(comment_priv);
287 		return retval;
288 	}
289 	if ((retval = pam_set_data(pamh, "ssh_key_comment", comment_priv,
290 	    ssh_cleanup)) != PAM_SUCCESS) {
291 		free(comment_priv);
292 		return retval;
293 	}
294 	/*
295 	 * Copy the passwd entry (in case successive calls are made)
296 	 * and save it for the session phase.
297 	 */
298 	if (!(pwent_keep = malloc(sizeof *pwent))) {
299 		syslog(LOG_CRIT, "%m");
300 		return PAM_SERVICE_ERR;
301 	}
302 	(void)memcpy(pwent_keep, pwent, sizeof *pwent_keep);
303 	if ((retval = pam_set_data(pamh, "ssh_passwd_entry", pwent_keep,
304 	    ssh_cleanup)) != PAM_SUCCESS) {
305 		free(pwent_keep);
306 		return retval;
307 	}
308 	return PAM_SUCCESS;
309 }
310 
311 
312 PAM_EXTERN int
313 pam_sm_setcred(
314 	pam_handle_t	 *pamh,
315 	int		  flags,
316 	int		  argc,
317 	const char	**argv)
318 {
319 	return PAM_SUCCESS;
320 }
321 
322 
323 typedef AuthenticationConnection AC;
324 
325 PAM_EXTERN int
326 pam_sm_open_session(
327 	pam_handle_t	 *pamh,
328 	int		  flags,
329 	int		  argc,
330 	const char	**argv)
331 {
332 	AC		*ac;			/* to ssh-agent */
333 	char		*comment;		/* on private key */
334 	char		*env_end;		/* end of env */
335 	char		*env_file;		/* to store env */
336 	FILE		*env_fp;		/* env_file handle */
337 	Key		 key;			/* user's private key */
338 	FILE		*pipe;			/* ssh-agent handle */
339 	const PASSWD	*pwent;			/* user's passwd entry */
340 	int		 retval;		/* from calls */
341 	uid_t		 saved_uid;		/* caller's uid */
342 	ENV		*ssh_env;		/* env handle */
343 	const char	*tty;			/* tty or display name */
344 	char		 hname[MAXHOSTNAMELEN];	/* local hostname */
345 	char		 parse[BUFSIZ];		/* commands output */
346 
347 	/* dump output of ssh-agent in ~/.ssh */
348 	if ((retval = pam_get_data(pamh, "ssh_passwd_entry",
349 	    (const void **)&pwent)) != PAM_SUCCESS)
350 		return retval;
351 	/* use the tty or X display name in the filename */
352 	if ((retval = pam_get_item(pamh, PAM_TTY, (const void **)&tty))
353 	    != PAM_SUCCESS)
354 		return retval;
355 	if (*tty == ':' && gethostname(hname, sizeof hname) == 0) {
356 		if (asprintf(&env_file, "%s/.ssh/agent-%s%s",
357 		    pwent->pw_dir, hname, tty) == -1) {
358 			syslog(LOG_CRIT, "%s: %m", MODULE_NAME);
359 			return PAM_SERVICE_ERR;
360 		}
361 	} else if (asprintf(&env_file, "%s/.ssh/agent-%s", pwent->pw_dir,
362 	    tty) == -1) {
363 		syslog(LOG_CRIT, "%s: %m", MODULE_NAME);
364 		return PAM_SERVICE_ERR;
365 	}
366 	/* save the filename so we can delete the file on session close */
367 	if ((retval = pam_set_data(pamh, "ssh_agent_env", env_file,
368 	    ssh_cleanup)) != PAM_SUCCESS) {
369 		free(env_file);
370 		return retval;
371 	}
372 	/* start the agent as the user */
373 	saved_uid = geteuid();
374 	(void)seteuid(pwent->pw_uid);
375 	env_fp = fopen(env_file, "w");
376 	pipe = popen(PATH_SSH_AGENT, "r");
377 	(void)seteuid(saved_uid);
378 	if (!pipe) {
379 		syslog(LOG_ERR, "%s: %s: %m", MODULE_NAME, PATH_SSH_AGENT);
380 		if (env_fp)
381 			(void)fclose(env_fp);
382 		return PAM_SESSION_ERR;
383 	}
384 	if (!(ssh_env = env_new()))
385 		return PAM_SESSION_ERR;
386 	if ((retval = pam_set_data(pamh, "ssh_env_handle", ssh_env,
387 	    env_cleanup)) != PAM_SUCCESS)
388 		return retval;
389 	while (fgets(parse, sizeof parse, pipe)) {
390 		if (env_fp)
391 			(void)fputs(parse, env_fp);
392 		/*
393 		 * Save environment for application with pam_putenv()
394 		 * but also with env_* functions for our own call to
395 		 * ssh_get_authentication_connection().
396 		 */
397 		if (strchr(parse, '=') && (env_end = strchr(parse, ';'))) {
398 			*env_end = '\0';
399 			/* pass to the application ... */
400 			if (!((retval = pam_putenv(pamh, parse)) ==
401 			    PAM_SUCCESS)) {
402 				(void)pclose(pipe);
403 				if (env_fp)
404 					(void)fclose(env_fp);
405 				env_destroy(ssh_env);
406 				return PAM_SERVICE_ERR;
407 			}
408 			env_put(ssh_env, parse);
409 		}
410 	}
411 	if (env_fp)
412 		(void)fclose(env_fp);
413 	switch (retval = pclose(pipe)) {
414 	case -1:
415 		syslog(LOG_ERR, "%s: %s: %m", MODULE_NAME, PATH_SSH_AGENT);
416 		env_destroy(ssh_env);
417 		return PAM_SESSION_ERR;
418 	case 0:
419 		break;
420 	case 127:
421 		syslog(LOG_ERR, "%s: cannot execute %s", MODULE_NAME,
422 		    PATH_SSH_AGENT);
423 		env_destroy(ssh_env);
424 		return PAM_SESSION_ERR;
425 	default:
426 		syslog(LOG_ERR, "%s: %s exited with status %d",
427 		    MODULE_NAME, PATH_SSH_AGENT, WEXITSTATUS(retval));
428 		env_destroy(ssh_env);
429 		return PAM_SESSION_ERR;
430 	}
431 	key.type = KEY_RSA;
432 	/* connect to the agent and hand off the private key */
433 	if ((retval = pam_get_data(pamh, "ssh_private_key",
434 	    (const void **)&key.rsa)) != PAM_SUCCESS ||
435 	    (retval = pam_get_data(pamh, "ssh_key_comment",
436 	    (const void **)&comment)) != PAM_SUCCESS ||
437 	    (retval = env_commit(ssh_env)) != PAM_SUCCESS) {
438 		env_destroy(ssh_env);
439 		return retval;
440 	}
441 	if (!(ac = ssh_get_authentication_connection())) {
442 		syslog(LOG_ERR, "%s: could not connect to agent",
443 		    MODULE_NAME);
444 		env_destroy(ssh_env);
445 		return PAM_SESSION_ERR;
446 	}
447 	retval = ssh_add_identity(ac, &key, comment);
448 	ssh_close_authentication_connection(ac);
449 	env_swap(ssh_env, 0);
450 	return retval ? PAM_SUCCESS : PAM_SESSION_ERR;
451 }
452 
453 
454 PAM_EXTERN int
455 pam_sm_close_session(
456 	pam_handle_t	 *pamh,
457 	int		  flags,
458 	int		  argc,
459 	const char	**argv)
460 {
461 	const char	*env_file;	/* ssh-agent environment */
462 	int	 	 retval;	/* from calls */
463 	ENV		*ssh_env;	/* env handle */
464 
465 	if ((retval = pam_get_data(pamh, "ssh_env_handle",
466 	    (const void **)&ssh_env)) != PAM_SUCCESS)
467 		return retval;
468 	env_swap(ssh_env, 1);
469 	/* kill the agent */
470 	retval = system(PATH_SSH_AGENT " -k");
471 	env_destroy(ssh_env);
472 	switch (retval) {
473 	case -1:
474 		syslog(LOG_ERR, "%s: %s -k: %m", MODULE_NAME,
475 		    PATH_SSH_AGENT);
476 		return PAM_SESSION_ERR;
477 	case 0:
478 		break;
479 	case 127:
480 		syslog(LOG_ERR, "%s: cannot execute %s -k", MODULE_NAME,
481 		    PATH_SSH_AGENT);
482 		return PAM_SESSION_ERR;
483 	default:
484 		syslog(LOG_ERR, "%s: %s -k exited with status %d",
485 		    MODULE_NAME, PATH_SSH_AGENT, WEXITSTATUS(retval));
486 		return PAM_SESSION_ERR;
487 	}
488 	/* retrieve environment filename, then remove the file */
489 	if ((retval = pam_get_data(pamh, "ssh_agent_env",
490 	    (const void **)&env_file)) != PAM_SUCCESS)
491 		return retval;
492 	(void)unlink(env_file);
493 	return PAM_SUCCESS;
494 }
495 
496 
497 PAM_MODULE_ENTRY(MODULE_NAME);
498