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