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