xref: /freebsd/lib/libpam/modules/pam_ssh/pam_ssh.c (revision 77a0943ded95b9e6438f7db70c4a28e4d93946d4)
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 	env_swap(self, 0);
182 	SLIST_FOREACH(p, &self->e_head, ee_entries) {
183 		free(p->ee_env);
184 		free(p);
185 	}
186 	if (self->e_committed)
187 		free(self->e_environ_new);
188 	free(self);
189 }
190 
191 
192 void
193 env_cleanup(pam_handle_t *pamh, void *data, int error_status)
194 {
195 	if (data)
196 		env_destroy(data);
197 }
198 
199 
200 typedef struct passwd PASSWD;
201 
202 PAM_EXTERN int
203 pam_sm_authenticate(
204 	pam_handle_t	 *pamh,
205 	int		  flags,
206 	int		  argc,
207 	const char	**argv)
208 {
209 	char		*comment_priv;		/* on private key */
210 	char		*comment_pub;		/* on public key */
211 	char		*identity;		/* user's identity file */
212 	Key		 key;			/* user's private key */
213 	int		 options;		/* module options */
214 	const char	*pass;			/* passphrase */
215 	char		*prompt;		/* passphrase prompt */
216 	Key		 public_key;		/* user's public key */
217 	const PASSWD	*pwent;			/* user's passwd entry */
218 	PASSWD		*pwent_keep;		/* our own copy */
219 	int		 retval;		/* from calls */
220 	uid_t		 saved_uid;		/* caller's uid */
221 	const char	*user;			/* username */
222 
223 	options = 0;
224 	while (argc--)
225 		pam_std_option(&options, *argv++);
226 	if ((retval = pam_get_user(pamh, &user, NULL)) != PAM_SUCCESS)
227 		return retval;
228 	if (!((pwent = getpwnam(user)) && pwent->pw_dir)) {
229 		/* delay? */
230 		return PAM_AUTH_ERR;
231 	}
232 	/* locate the user's private key file */
233 	if (!asprintf(&identity, "%s/%s", pwent->pw_dir,
234 	    SSH_CLIENT_IDENTITY)) {
235 		syslog(LOG_CRIT, "%s: %m", MODULE_NAME);
236 		return PAM_SERVICE_ERR;
237 	}
238 	/*
239 	 * Fail unless we can load the public key.  Change to the
240 	 * owner's UID to appease load_public_key().
241 	 */
242 	key.type = KEY_RSA;
243 	key.rsa = RSA_new();
244 	public_key.type = KEY_RSA;
245 	public_key.rsa = RSA_new();
246 	saved_uid = getuid();
247 	(void)setreuid(pwent->pw_uid, saved_uid);
248 	retval = load_public_key(identity, &public_key, &comment_pub);
249 	(void)setuid(saved_uid);
250 	if (!retval) {
251 		free(identity);
252 		return PAM_AUTH_ERR;
253 	}
254 	RSA_free(public_key.rsa);
255 	/* build the passphrase prompt */
256 	retval = asprintf(&prompt, NEED_PASSPHRASE, identity, comment_pub);
257 	free(comment_pub);
258 	if (!retval) {
259 		syslog(LOG_CRIT, "%s: %m", MODULE_NAME);
260 		free(identity);
261 		return PAM_SERVICE_ERR;
262 	}
263 	/* pass prompt message to application and receive passphrase */
264 	retval = pam_get_pass(pamh, &pass, prompt, options);
265 	free(prompt);
266 	if (retval != PAM_SUCCESS) {
267 		free(identity);
268 		return retval;
269 	}
270 	/*
271 	 * Try to decrypt the private key with the passphrase provided.
272 	 * If success, the user is authenticated.
273 	 */
274 	(void)setreuid(pwent->pw_uid, saved_uid);
275 	retval = load_private_key(identity, pass, &key, &comment_priv);
276 	free(identity);
277 	(void)setuid(saved_uid);
278 	if (!retval)
279 		return PAM_AUTH_ERR;
280 	/*
281 	 * Save the key and comment to pass to ssh-agent in the session
282 	 * phase.
283 	 */
284 	if ((retval = pam_set_data(pamh, "ssh_private_key", key.rsa,
285 	    rsa_cleanup)) != PAM_SUCCESS) {
286 		RSA_free(key.rsa);
287 		free(comment_priv);
288 		return retval;
289 	}
290 	if ((retval = pam_set_data(pamh, "ssh_key_comment", comment_priv,
291 	    ssh_cleanup)) != PAM_SUCCESS) {
292 		free(comment_priv);
293 		return retval;
294 	}
295 	/*
296 	 * Copy the passwd entry (in case successive calls are made)
297 	 * and save it for the session phase.
298 	 */
299 	if (!(pwent_keep = malloc(sizeof *pwent))) {
300 		syslog(LOG_CRIT, "%m");
301 		return PAM_SERVICE_ERR;
302 	}
303 	(void)memcpy(pwent_keep, pwent, sizeof *pwent_keep);
304 	if ((retval = pam_set_data(pamh, "ssh_passwd_entry", pwent_keep,
305 	    ssh_cleanup)) != PAM_SUCCESS) {
306 		free(pwent_keep);
307 		return retval;
308 	}
309 	return PAM_SUCCESS;
310 }
311 
312 
313 PAM_EXTERN int
314 pam_sm_setcred(
315 	pam_handle_t	 *pamh,
316 	int		  flags,
317 	int		  argc,
318 	const char	**argv)
319 {
320 	return PAM_SUCCESS;
321 }
322 
323 
324 typedef AuthenticationConnection AC;
325 
326 PAM_EXTERN int
327 pam_sm_open_session(
328 	pam_handle_t	 *pamh,
329 	int		  flags,
330 	int		  argc,
331 	const char	**argv)
332 {
333 	AC		*ac;			/* to ssh-agent */
334 	char		*comment;		/* on private key */
335 	char		*env_end;		/* end of env */
336 	char		*env_file;		/* to store env */
337 	FILE		*env_fp;		/* env_file handle */
338 	Key		 key;			/* user's private key */
339 	FILE		*pipe;			/* ssh-agent handle */
340 	const PASSWD	*pwent;			/* user's passwd entry */
341 	int		 retval;		/* from calls */
342 	uid_t		 saved_uid;		/* caller's uid */
343 	ENV		*ssh_env;		/* env handle */
344 	const char	*tty;			/* tty or display name */
345 	char		 hname[MAXHOSTNAMELEN];	/* local hostname */
346 	char		 parse[BUFSIZ];		/* commands output */
347 
348 	/* dump output of ssh-agent in ~/.ssh */
349 	if ((retval = pam_get_data(pamh, "ssh_passwd_entry",
350 	    (const void **)&pwent)) != PAM_SUCCESS)
351 		return retval;
352 	/* use the tty or X display name in the filename */
353 	if ((retval = pam_get_item(pamh, PAM_TTY, (const void **)&tty))
354 	    != PAM_SUCCESS)
355 		return retval;
356 	if (*tty == ':' && gethostname(hname, sizeof hname) == 0) {
357 		if (asprintf(&env_file, "%s/.ssh/agent-%s%s",
358 		    pwent->pw_dir, hname, tty) == -1) {
359 			syslog(LOG_CRIT, "%s: %m", MODULE_NAME);
360 			return PAM_SERVICE_ERR;
361 		}
362 	} else if (asprintf(&env_file, "%s/.ssh/agent-%s", pwent->pw_dir,
363 	    tty) == -1) {
364 		syslog(LOG_CRIT, "%s: %m", MODULE_NAME);
365 		return PAM_SERVICE_ERR;
366 	}
367 	/* save the filename so we can delete the file on session close */
368 	if ((retval = pam_set_data(pamh, "ssh_agent_env", env_file,
369 	    ssh_cleanup)) != PAM_SUCCESS) {
370 		free(env_file);
371 		return retval;
372 	}
373 	/* start the agent as the user */
374 	saved_uid = geteuid();
375 	(void)seteuid(pwent->pw_uid);
376 	env_fp = fopen(env_file, "w");
377 	pipe = popen(PATH_SSH_AGENT, "r");
378 	(void)seteuid(saved_uid);
379 	if (!pipe) {
380 		syslog(LOG_ERR, "%s: %s: %m", MODULE_NAME, PATH_SSH_AGENT);
381 		if (env_fp)
382 			(void)fclose(env_fp);
383 		return PAM_SESSION_ERR;
384 	}
385 	if (!(ssh_env = env_new()))
386 		return PAM_SESSION_ERR;
387 	if ((retval = pam_set_data(pamh, "ssh_env_handle", ssh_env,
388 	    env_cleanup)) != PAM_SUCCESS)
389 		return retval;
390 	while (fgets(parse, sizeof parse, pipe)) {
391 		if (env_fp)
392 			(void)fputs(parse, env_fp);
393 		/*
394 		 * Save environment for application with pam_putenv()
395 		 * but also with env_* functions for our own call to
396 		 * ssh_get_authentication_connection().
397 		 */
398 		if (strchr(parse, '=') && (env_end = strchr(parse, ';'))) {
399 			*env_end = '\0';
400 			/* pass to the application ... */
401 			if (!((retval = pam_putenv(pamh, parse)) ==
402 			    PAM_SUCCESS)) {
403 				(void)pclose(pipe);
404 				if (env_fp)
405 					(void)fclose(env_fp);
406 				env_destroy(ssh_env);
407 				return PAM_SERVICE_ERR;
408 			}
409 			env_put(ssh_env, parse);
410 		}
411 	}
412 	if (env_fp)
413 		(void)fclose(env_fp);
414 	switch (retval = pclose(pipe)) {
415 	case -1:
416 		syslog(LOG_ERR, "%s: %s: %m", MODULE_NAME, PATH_SSH_AGENT);
417 		env_destroy(ssh_env);
418 		return PAM_SESSION_ERR;
419 	case 0:
420 		break;
421 	case 127:
422 		syslog(LOG_ERR, "%s: cannot execute %s", MODULE_NAME,
423 		    PATH_SSH_AGENT);
424 		env_destroy(ssh_env);
425 		return PAM_SESSION_ERR;
426 	default:
427 		syslog(LOG_ERR, "%s: %s exited with status %d",
428 		    MODULE_NAME, PATH_SSH_AGENT, WEXITSTATUS(retval));
429 		env_destroy(ssh_env);
430 		return PAM_SESSION_ERR;
431 	}
432 	key.type = KEY_RSA;
433 	/* connect to the agent and hand off the private key */
434 	if ((retval = pam_get_data(pamh, "ssh_private_key",
435 	    (const void **)&key.rsa)) != PAM_SUCCESS ||
436 	    (retval = pam_get_data(pamh, "ssh_key_comment",
437 	    (const void **)&comment)) != PAM_SUCCESS ||
438 	    (retval = env_commit(ssh_env)) != PAM_SUCCESS) {
439 		env_destroy(ssh_env);
440 		return retval;
441 	}
442 	if (!(ac = ssh_get_authentication_connection())) {
443 		syslog(LOG_ERR, "%s: could not connect to agent",
444 		    MODULE_NAME);
445 		env_destroy(ssh_env);
446 		return PAM_SESSION_ERR;
447 	}
448 	retval = ssh_add_identity(ac, &key, comment);
449 	ssh_close_authentication_connection(ac);
450 	env_swap(ssh_env, 0);
451 	return retval ? PAM_SUCCESS : PAM_SESSION_ERR;
452 }
453 
454 
455 PAM_EXTERN int
456 pam_sm_close_session(
457 	pam_handle_t	 *pamh,
458 	int		  flags,
459 	int		  argc,
460 	const char	**argv)
461 {
462 	const char	*env_file;	/* ssh-agent environment */
463 	int	 	 retval;	/* from calls */
464 	ENV		*ssh_env;	/* env handle */
465 
466 	if ((retval = pam_get_data(pamh, "ssh_env_handle",
467 	    (const void **)&ssh_env)) != PAM_SUCCESS)
468 		return retval;
469 	env_swap(ssh_env, 1);
470 	/* kill the agent */
471 	retval = system(PATH_SSH_AGENT " -k");
472 	env_destroy(ssh_env);
473 	switch (retval) {
474 	case -1:
475 		syslog(LOG_ERR, "%s: %s -k: %m", MODULE_NAME,
476 		    PATH_SSH_AGENT);
477 		return PAM_SESSION_ERR;
478 	case 0:
479 		break;
480 	case 127:
481 		syslog(LOG_ERR, "%s: cannot execute %s -k", MODULE_NAME,
482 		    PATH_SSH_AGENT);
483 		return PAM_SESSION_ERR;
484 	default:
485 		syslog(LOG_ERR, "%s: %s -k exited with status %d",
486 		    MODULE_NAME, PATH_SSH_AGENT, WEXITSTATUS(retval));
487 		return PAM_SESSION_ERR;
488 	}
489 	/* retrieve environment filename, then remove the file */
490 	if ((retval = pam_get_data(pamh, "ssh_agent_env",
491 	    (const void **)&env_file)) != PAM_SUCCESS)
492 		return retval;
493 	(void)unlink(env_file);
494 	return PAM_SUCCESS;
495 }
496 
497 
498 PAM_MODULE_ENTRY(MODULE_NAME);
499