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