2 * Copyright (c) 1999, 2000 Andrew J. Korty
4 * Copyright (c) 2001,2002 Networks Associates Technology, Inc.
7 * Portions of this software were developed for the FreeBSD Project by
8 * ThinkSec AS and NAI Labs, the Security Research Division of Network
9 * Associates, Inc. under DARPA/SPAWAR contract N66001-01-C-8035
10 * ("CBOSS"), as part of the DARPA CHATS research program.
12 * Redistribution and use in source and binary forms, with or without
13 * modification, are permitted provided that the following conditions
15 * 1. Redistributions of source code must retain the above copyright
16 * notice, this list of conditions and the following disclaimer.
17 * 2. Redistributions in binary form must reproduce the above copyright
18 * notice, this list of conditions and the following disclaimer in the
19 * documentation and/or other materials provided with the distribution.
20 * 3. The name of the author may not be used to endorse or promote
21 * products derived from this software without specific prior written
24 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
25 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
26 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
27 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
28 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
29 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
30 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
31 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
32 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
33 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
36 * $Id: pam_ssh.c,v 1.23 2001/08/20 01:44:02 akorty Exp $
37 * $FreeBSD: src/lib/libpam/modules/pam_ssh/pam_ssh.c,v 1.28.2.4 2002/07/12 09:24:56 des Exp $
38 * $DragonFly: src/lib/libpam/modules/pam_ssh/Attic/pam_ssh.c,v 1.2 2003/06/17 04:26:50 dillon Exp $
41 #include <sys/param.h>
54 #define PAM_SM_SESSION
56 #include <security/pam_appl.h>
57 #include <security/pam_modules.h>
58 #include <security/pam_mod_misc.h>
60 #include <openssl/dsa.h>
61 #include <openssl/evp.h>
70 PAM_OPT_KEYFILES = PAM_OPT_STD_MAX
73 static struct opttab other_options[] = {
74 { "keyfiles", PAM_OPT_KEYFILES },
78 static void key_cleanup(pam_handle_t *, void *, int);
79 static void ssh_cleanup(pam_handle_t *, void *, int);
82 * Generic cleanup function for OpenSSH "Key" type.
86 key_cleanup(pam_handle_t *pamh __unused, void *data, int err __unused)
94 * Generic PAM cleanup function for this module.
98 ssh_cleanup(pam_handle_t *pamh __unused, void *data, int err __unused)
106 * Authenticate a user's key by trying to decrypt it with the password
107 * provided. The key and its comment are then stored for later
108 * retrieval by the session phase. An increasing index is embedded in
109 * the PAM variable names so this function may be called multiple times
114 auth_via_key(pam_handle_t *pamh, const char *file, const char *dir,
115 const struct passwd *user, const char *pass, struct options *options)
117 char *comment; /* private key comment */
118 char *data_name; /* PAM state */
119 static int key_idx = 0; /* for saved keys */
120 Key *key; /* user's key */
121 char *path; /* to key files */
122 int retval; /* from calls */
124 /* locate the user's private key file */
126 if (!asprintf(&path, "%s/%s", dir, file)) {
128 return (PAM_SERVICE_ERR);
131 /* Try to decrypt the private key with the passphrase provided. If
132 success, the user is authenticated. */
135 if ((retval = openpam_borrow_cred(pamh, user)) != PAM_SUCCESS)
137 key = key_load_private(path, pass, &comment);
138 openpam_restore_cred(pamh);
141 comment = strdup(file);
144 return (PAM_AUTH_ERR);
147 /* save the key and comment to pass to ssh-agent in the session
150 if (!asprintf(&data_name, "ssh_private_key_%d", key_idx)) {
153 return (PAM_SERVICE_ERR);
155 retval = pam_set_data(pamh, data_name, key, key_cleanup);
157 if (retval != PAM_SUCCESS) {
162 if (!asprintf(&data_name, "ssh_key_comment_%d", key_idx)) {
165 return (PAM_SERVICE_ERR);
167 retval = pam_set_data(pamh, data_name, comment, ssh_cleanup);
169 if (retval != PAM_SUCCESS) {
175 return (PAM_SUCCESS);
180 * Add the keys stored by auth_via_key() to the agent connected to the
185 add_keys(pam_handle_t *pamh, struct options *options)
187 AuthenticationConnection *ac; /* connection to ssh-agent */
188 char *comment; /* private key comment */
189 char *data_name; /* PAM state */
190 int final; /* final return value */
191 int key_idx; /* for saved keys */
192 Key *key; /* user's private key */
193 int retval; /* from calls */
196 * Connect to the agent.
198 * XXX Because ssh_get_authentication_connection() gets the
199 * XXX agent parameters from the environment, we have to
200 * XXX temporarily replace the environment with the PAM
201 * XXX environment list. This is a hack.
204 extern char **environ;
208 if ((environ = pam_getenvlist(pamh)) == NULL) {
211 return (PAM_BUF_ERR);
213 ac = ssh_get_authentication_connection();
214 for (evp = environ; *evp; evp++)
221 return (PAM_SESSION_ERR);
224 /* hand off each private key to the agent */
227 for (key_idx = 0; ; key_idx++) {
228 if (!asprintf(&data_name, "ssh_private_key_%d", key_idx)) {
230 ssh_close_authentication_connection(ac);
231 return (PAM_SERVICE_ERR);
233 retval = pam_get_data(pamh, data_name, (const void **)&key);
235 if (retval != PAM_SUCCESS)
237 if (!asprintf(&data_name, "ssh_key_comment_%d", key_idx)) {
239 ssh_close_authentication_connection(ac);
240 return (PAM_SERVICE_ERR);
242 retval = pam_get_data(pamh, data_name,
243 (const void **)&comment);
245 if (retval != PAM_SUCCESS)
247 retval = ssh_add_identity(ac, key, comment);
251 ssh_close_authentication_connection(ac);
253 return (final ? PAM_SUCCESS : PAM_SESSION_ERR);
258 pam_sm_authenticate(pam_handle_t *pamh, int flags __unused,
259 int argc __unused, const char *argv[] __unused)
261 struct options options;
262 int authenticated; /* user authenticated? */
263 char *dotdir; /* .ssh dir name */
264 char *file; /* current key file */
265 char *kfspec; /* list of key files to add */
267 const char *pass; /* passphrase */
268 const struct passwd *pwent; /* user's passwd entry */
269 struct passwd *pwent_keep; /* our own copy */
270 int retval; /* from calls */
271 const char *user; /* username */
273 pam_std_option(&options, other_options, argc, argv);
275 if (!pam_test_option(&options, PAM_OPT_KEYFILES, &kfspec)) {
276 kfspec = DEF_KEYFILES;
279 if ((retval = pam_get_user(pamh, &user, NULL)) != PAM_SUCCESS)
281 if (user == NULL || (pwent = getpwnam(user)) == NULL ||
282 pwent->pw_dir == NULL || pwent->pw_dir[0] == '\0')
283 return (PAM_AUTH_ERR);
285 /* pass prompt message to application and receive passphrase */
287 retval = pam_get_pass(pamh, &pass, NEED_PASSPHRASE, &options);
288 if (retval != PAM_SUCCESS)
291 OpenSSL_add_all_algorithms(); /* required for DSA */
293 /* any key will authenticate us, but if we can decrypt all of the
294 specified keys, we'll do so here so we can cache them in the
297 if (!asprintf(&dotdir, "%s/%s", pwent->pw_dir, SSH_CLIENT_DIR)) {
299 return (PAM_SERVICE_ERR);
302 keyfiles = strdup(kfspec);
303 for (file = strtok(keyfiles, SEP_KEYFILES); file;
304 file = strtok(NULL, SEP_KEYFILES))
305 if (auth_via_key(pamh, file, dotdir, pwent, pass, &options) ==
311 return (PAM_AUTH_ERR);
313 /* copy the passwd entry (in case successive calls are made) and
314 save it for the session phase */
316 if (!(pwent_keep = malloc(sizeof *pwent))) {
318 return (PAM_SERVICE_ERR);
320 (void) memcpy(pwent_keep, pwent, sizeof *pwent_keep);
321 if ((retval = pam_set_data(pamh, "ssh_passwd_entry", pwent_keep,
322 ssh_cleanup)) != PAM_SUCCESS) {
327 return (PAM_SUCCESS);
332 pam_sm_setcred(pam_handle_t *pamh __unused, int flags __unused,
333 int argc __unused, const char *argv[] __unused)
336 return (PAM_SUCCESS);
341 pam_sm_open_session(pam_handle_t *pamh, int flags __unused,
342 int argc __unused, const char *argv[] __unused)
344 struct options options;
345 char *agent_socket; /* agent socket */
346 char *env_end; /* end of env */
347 FILE *env_read; /* env data source */
348 char env_string[BUFSIZ]; /* environment string */
349 char *env_value; /* envariable value */
350 int env_write; /* env file descriptor */
351 char hname[MAXHOSTNAMELEN]; /* local hostname */
352 int no_link; /* link per-agent file? */
353 char *per_agent; /* to store env */
354 char *per_session; /* per-session filename */
355 char *agent_pid; /* agent pid */
356 const struct passwd *pwent; /* user's passwd entry */
357 int retval; /* from calls */
358 int start_agent; /* start agent? */
359 const char *tty; /* tty or display name */
361 pam_std_option(&options, other_options, argc, argv);
363 /* dump output of ssh-agent in ~/.ssh */
364 if ((retval = pam_get_data(pamh, "ssh_passwd_entry",
365 (const void **)&pwent)) != PAM_SUCCESS)
369 * Use reference counts to limit agents to one per user per host.
371 * Technique: Create an environment file containing
372 * information about the agent. Only one file is created, but
373 * it may be given many names. One name is given for the
374 * agent itself, agent-<host>. Another name is given for each
375 * session, agent-<host>-<display> or agent-<host>-<tty>. We
376 * delete the per-session filename on session close, and when
377 * the link count goes to unity on the per-agent file, we
378 * delete the file and kill the agent.
381 /* the per-agent file contains just the hostname */
383 (void) gethostname(hname, sizeof hname);
384 if (asprintf(&per_agent, "%s/.ssh/agent-%s", pwent->pw_dir, hname)
387 return (PAM_SERVICE_ERR);
390 /* save the per-agent filename in case we want to delete it on
393 if ((retval = pam_set_data(pamh, "ssh_agent_env_agent", per_agent,
394 ssh_cleanup)) != PAM_SUCCESS) {
399 /* take on the user's privileges for writing files and starting the
402 if ((retval = openpam_borrow_cred(pamh, pwent)) != PAM_SUCCESS)
405 /* Try to create the per-agent file or open it for reading if it
406 exists. If we can't do either, we won't try to link a
407 per-session filename later. Start the agent if we can't open
408 the file for reading. */
410 env_write = no_link = 0;
412 if ((env_write = open(per_agent, O_CREAT | O_EXCL | O_WRONLY,
413 S_IRUSR)) < 0 && !(env_read = fopen(per_agent, "r")))
417 openpam_restore_cred(pamh);
420 env_read = popen(SSH_AGENT, "r");
421 openpam_restore_cred(pamh);
423 PAM_LOG( "%s: %m", SSH_AGENT);
425 (void) close(env_write);
426 return (PAM_SESSION_ERR);
430 /* save environment for application with pam_putenv() */
433 while (fgets(env_string, sizeof env_string, env_read)) {
435 /* parse environment definitions */
438 (void) write(env_write, env_string,
440 if (!(env_value = strchr(env_string, '=')) ||
441 !(env_end = strchr(env_value, ';')))
445 /* pass to the application */
447 if (!((retval = pam_putenv(pamh, env_string)) ==
450 (void) pclose(env_read);
452 (void) fclose(env_read);
454 (void) close(env_write);
457 return (PAM_SERVICE_ERR);
462 /* save the agent socket so we can connect to it and add
463 the keys as well as the PID so we can kill the agent on
466 if (strcmp(&env_string[strlen(env_string) -
467 strlen(ENV_SOCKET_SUFFIX)], ENV_SOCKET_SUFFIX) == 0 &&
468 !(agent_socket = strdup(env_value))) {
471 (void) pclose(env_read);
473 (void) fclose(env_read);
475 (void) close(env_write);
478 return (PAM_SERVICE_ERR);
479 } else if (strcmp(&env_string[strlen(env_string) -
480 strlen(ENV_PID_SUFFIX)], ENV_PID_SUFFIX) == 0 &&
481 ((agent_pid = strdup(env_value)) == NULL ||
482 (retval = pam_set_data(pamh, "ssh_agent_pid",
483 agent_pid, ssh_cleanup)) != PAM_SUCCESS)) {
485 (void) pclose(env_read);
487 (void) fclose(env_read);
489 (void) close(env_write);
499 (void) close(env_write);
502 switch (retval = pclose(env_read)) {
504 PAM_LOG( "%s: %m", SSH_AGENT);
507 return (PAM_SESSION_ERR);
511 PAM_LOG( "cannot execute %s",
515 return (PAM_SESSION_ERR);
517 PAM_LOG( "%s exited %s %d",
518 SSH_AGENT, WIFSIGNALED(retval) ? "on signal" :
519 "with status", WIFSIGNALED(retval) ?
520 WTERMSIG(retval) : WEXITSTATUS(retval));
523 return (PAM_SESSION_ERR);
526 (void) fclose(env_read);
529 return (PAM_SESSION_ERR);
531 if (start_agent && (retval = add_keys(pamh, &options))
536 /* if we couldn't access the per-agent file, don't link a
537 per-session filename to it */
540 return (PAM_SUCCESS);
542 /* the per-session file contains the display name or tty name as
543 well as the hostname */
545 if ((retval = pam_get_item(pamh, PAM_TTY, (const void **)&tty))
548 if (asprintf(&per_session, "%s/.ssh/agent-%s-%s", pwent->pw_dir,
551 return (PAM_SERVICE_ERR);
554 /* save the per-session filename so we can delete it on session
557 if ((retval = pam_set_data(pamh, "ssh_agent_env_session",
558 per_session, ssh_cleanup)) != PAM_SUCCESS) {
563 (void) unlink(per_session); /* remove cruft */
564 (void) link(per_agent, per_session);
566 return (PAM_SUCCESS);
571 pam_sm_close_session(pam_handle_t *pamh, int flags __unused,
572 int argc __unused, const char *argv[] __unused)
574 struct options options;
575 const char *env_file; /* ssh-agent environment */
576 pid_t pid; /* ssh-agent process id */
577 int retval; /* from calls */
578 const char *ssh_agent_pid; /* ssh-agent pid string */
579 struct stat sb; /* to check st_nlink */
581 pam_std_option(&options, other_options, argc, argv);
583 if ((retval = pam_get_data(pamh, "ssh_agent_env_session",
584 (const void **)&env_file)) == PAM_SUCCESS && env_file)
585 (void) unlink(env_file);
587 /* Retrieve per-agent filename and check link count. If it's
588 greater than unity, other sessions are still using this
591 if ((retval = pam_get_data(pamh, "ssh_agent_env_agent",
592 (const void **)&env_file)) == PAM_SUCCESS && env_file &&
593 stat(env_file, &sb) == 0) {
595 return (PAM_SUCCESS);
596 (void) unlink(env_file);
599 /* retrieve the agent's process id */
601 if ((retval = pam_get_data(pamh, "ssh_agent_pid",
602 (const void **)&ssh_agent_pid)) != PAM_SUCCESS)
605 /* Kill the agent. SSH's ssh-agent does not have a -k option, so
608 pid = atoi(ssh_agent_pid);
610 return (PAM_SESSION_ERR);
611 if (kill(pid, SIGTERM) != 0) {
612 PAM_LOG( "%s: %m", ssh_agent_pid);
613 return (PAM_SESSION_ERR);
616 return (PAM_SUCCESS);
619 PAM_MODULE_ENTRY(MODULE_NAME);