diff options
Diffstat (limited to 'crypto/openssh/pam_ssh/pam_ssh.c')
-rw-r--r-- | crypto/openssh/pam_ssh/pam_ssh.c | 489 |
1 files changed, 489 insertions, 0 deletions
diff --git a/crypto/openssh/pam_ssh/pam_ssh.c b/crypto/openssh/pam_ssh/pam_ssh.c new file mode 100644 index 0000000..9392b9e --- /dev/null +++ b/crypto/openssh/pam_ssh/pam_ssh.c @@ -0,0 +1,489 @@ +/*- + * Copyright (c) 1999 Andrew J. Korty + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD$ + * + */ + + +#include <sys/param.h> +#include <sys/queue.h> + +#include <fcntl.h> +#include <paths.h> +#include <pwd.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#define PAM_SM_AUTH +#define PAM_SM_SESSION +#include <security/pam_modules.h> +#include <security/pam_mod_misc.h> + +#include "includes.h" +#include "rsa.h" +#include "ssh.h" +#include "authfd.h" + +#define MODULE_NAME "pam_ssh" +#define NEED_PASSPHRASE "Need passphrase for %s (%s).\nEnter passphrase: " +#define PATH_SSH_AGENT "__PREFIX__/bin/ssh-agent" + + +void +rsa_cleanup(pam_handle_t *pamh, void *data, int error_status) +{ + if (data) + RSA_free(data); +} + + +void +ssh_cleanup(pam_handle_t *pamh, void *data, int error_status) +{ + if (data) + free(data); +} + + +/* + * The following set of functions allow the module to manipulate the + * environment without calling the putenv() or setenv() stdlib functions. + * At least one version of these functions, on the first call, copies + * the environment into dynamically-allocated memory and then augments + * it. On subsequent calls, the realloc() call is used to grow the + * previously allocated buffer. Problems arise when the "environ" + * variable is changed to point to static memory after putenv()/setenv() + * have been called. + * + * We don't use putenv() or setenv() in case the application subsequently + * manipulates environ, (e.g., to clear the environment by pointing + * environ at an array of one element equal to NULL). + */ + +SLIST_HEAD(env_head, env_entry); + +struct env_entry { + char *ee_env; + SLIST_ENTRY(env_entry) ee_entries; +}; + +typedef struct env { + char **e_environ_orig; + char **e_environ_new; + int e_count; + struct env_head e_head; + int e_committed; +} ENV; + +extern char **environ; + + +static ENV * +env_new(void) +{ + ENV *self; + + if (!(self = malloc(sizeof (ENV)))) { + syslog(LOG_CRIT, "%m"); + return NULL; + } + SLIST_INIT(&self->e_head); + self->e_count = 0; + self->e_committed = 0; + return self; +} + + +static int +env_put(ENV *self, char *s) +{ + struct env_entry *env; + + if (!(env = malloc(sizeof (struct env_entry))) || + !(env->ee_env = strdup(s))) { + syslog(LOG_CRIT, "%m"); + return PAM_SERVICE_ERR; + } + SLIST_INSERT_HEAD(&self->e_head, env, ee_entries); + ++self->e_count; + return PAM_SUCCESS; +} + + +static void +env_swap(ENV *self, int which) +{ + environ = which ? self->e_environ_new : self->e_environ_orig; +} + + +static int +env_commit(ENV *self) +{ + int n; + struct env_entry *p; + char **v; + + for (v = environ, n = 0; v && *v; v++, n++) + ; + if (!(v = malloc((n + self->e_count + 1) * sizeof (char *)))) { + syslog(LOG_CRIT, "%m"); + return PAM_SERVICE_ERR; + } + self->e_committed = 1; + (void)memcpy(v, environ, n * sizeof (char *)); + SLIST_FOREACH(p, &self->e_head, ee_entries) + v[n++] = p->ee_env; + v[n] = NULL; + self->e_environ_orig = environ; + self->e_environ_new = v; + env_swap(self, 1); + return PAM_SUCCESS; +} + + +static void +env_destroy(ENV *self) +{ + struct env_entry *p; + + env_swap(self, 0); + SLIST_FOREACH(p, &self->e_head, ee_entries) { + free(p->ee_env); + free(p); + } + if (self->e_committed) + free(self->e_environ_new); + free(self); +} + + +void +env_cleanup(pam_handle_t *pamh, void *data, int error_status) +{ + if (data) + env_destroy(data); +} + + +typedef struct passwd PASSWD; + +PAM_EXTERN int +pam_sm_authenticate( + pam_handle_t *pamh, + int flags, + int argc, + const char **argv) +{ + char *comment_priv; /* on private key */ + char *comment_pub; /* on public key */ + char *identity; /* user's identity file */ + RSA *key; /* user's private key */ + int options; /* module options */ + const char *pass; /* passphrase */ + char *prompt; /* passphrase prompt */ + RSA *public_key; /* user's public key */ + const PASSWD *pwent; /* user's passwd entry */ + PASSWD *pwent_keep; /* our own copy */ + int retval; /* from calls */ + uid_t saved_uid; /* caller's uid */ + const char *user; /* username */ + + options = 0; + while (argc--) + pam_std_option(&options, *argv++); + if ((retval = pam_get_user(pamh, &user, NULL)) != PAM_SUCCESS) + return retval; + if (!((pwent = getpwnam(user)) && pwent->pw_dir)) { + /* delay? */ + return PAM_AUTH_ERR; + } + /* locate the user's private key file */ + if (!asprintf(&identity, "%s/%s", pwent->pw_dir, + SSH_CLIENT_IDENTITY)) { + syslog(LOG_CRIT, "%s: %m", MODULE_NAME); + return PAM_SERVICE_ERR; + } + /* + * Fail unless we can load the public key. Change to the + * owner's UID to appease load_public_key(). + */ + key = RSA_new(); + public_key = RSA_new(); + saved_uid = getuid(); + (void)setreuid(pwent->pw_uid, saved_uid); + retval = load_public_key(identity, public_key, &comment_pub); + (void)setuid(saved_uid); + if (!retval) { + free(identity); + return PAM_AUTH_ERR; + } + RSA_free(public_key); + /* build the passphrase prompt */ + retval = asprintf(&prompt, NEED_PASSPHRASE, identity, comment_pub); + free(comment_pub); + if (!retval) { + syslog(LOG_CRIT, "%s: %m", MODULE_NAME); + free(identity); + return PAM_SERVICE_ERR; + } + /* pass prompt message to application and receive passphrase */ + retval = pam_get_pass(pamh, &pass, prompt, options); + free(prompt); + if (retval != PAM_SUCCESS) { + free(identity); + return retval; + } + /* + * Try to decrypt the private key with the passphrase provided. + * If success, the user is authenticated. + */ + (void)setreuid(pwent->pw_uid, saved_uid); + retval = load_private_key(identity, pass, key, &comment_priv); + free(identity); + (void)setuid(saved_uid); + if (!retval) + return PAM_AUTH_ERR; + /* + * Save the key and comment to pass to ssh-agent in the session + * phase. + */ + if ((retval = pam_set_data(pamh, "ssh_private_key", key, + rsa_cleanup)) != PAM_SUCCESS) { + RSA_free(key); + free(comment_priv); + return retval; + } + if ((retval = pam_set_data(pamh, "ssh_key_comment", comment_priv, + ssh_cleanup)) != PAM_SUCCESS) { + free(comment_priv); + return retval; + } + /* + * Copy the passwd entry (in case successive calls are made) + * and save it for the session phase. + */ + if (!(pwent_keep = malloc(sizeof *pwent))) { + syslog(LOG_CRIT, "%m"); + return PAM_SERVICE_ERR; + } + (void)memcpy(pwent_keep, pwent, sizeof *pwent_keep); + if ((retval = pam_set_data(pamh, "ssh_passwd_entry", pwent_keep, + ssh_cleanup)) != PAM_SUCCESS) { + free(pwent_keep); + return retval; + } + return PAM_SUCCESS; +} + + +PAM_EXTERN int +pam_sm_setcred( + pam_handle_t *pamh, + int flags, + int argc, + const char **argv) +{ + return PAM_SUCCESS; +} + + +typedef AuthenticationConnection AC; + +PAM_EXTERN int +pam_sm_open_session( + pam_handle_t *pamh, + int flags, + int argc, + const char **argv) +{ + AC *ac; /* to ssh-agent */ + char *comment; /* on private key */ + char *env_end; /* end of env */ + char *env_file; /* to store env */ + FILE *env_fp; /* env_file handle */ + RSA *key; /* user's private key */ + FILE *pipe; /* ssh-agent handle */ + const PASSWD *pwent; /* user's passwd entry */ + int retval; /* from calls */ + uid_t saved_uid; /* caller's uid */ + ENV *ssh_env; /* env handle */ + const char *tty; /* tty or display name */ + char hname[MAXHOSTNAMELEN]; /* local hostname */ + char parse[BUFSIZ]; /* commands output */ + + /* dump output of ssh-agent in ~/.ssh */ + if ((retval = pam_get_data(pamh, "ssh_passwd_entry", + (const void **)&pwent)) != PAM_SUCCESS) + return retval; + /* use the tty or X display name in the filename */ + if ((retval = pam_get_item(pamh, PAM_TTY, (const void **)&tty)) + != PAM_SUCCESS) + return retval; + if (*tty == ':' && gethostname(hname, sizeof hname) == 0) { + if (asprintf(&env_file, "%s/.ssh/agent-%s%s", + pwent->pw_dir, hname, tty) == -1) { + syslog(LOG_CRIT, "%s: %m", MODULE_NAME); + return PAM_SERVICE_ERR; + } + } else if (asprintf(&env_file, "%s/.ssh/agent-%s", pwent->pw_dir, + tty) == -1) { + syslog(LOG_CRIT, "%s: %m", MODULE_NAME); + return PAM_SERVICE_ERR; + } + /* save the filename so we can delete the file on session close */ + if ((retval = pam_set_data(pamh, "ssh_agent_env", env_file, + ssh_cleanup)) != PAM_SUCCESS) { + free(env_file); + return retval; + } + /* start the agent as the user */ + saved_uid = geteuid(); + (void)seteuid(pwent->pw_uid); + env_fp = fopen(env_file, "w"); + pipe = popen(PATH_SSH_AGENT, "r"); + (void)seteuid(saved_uid); + if (!pipe) { + syslog(LOG_ERR, "%s: %s: %m", MODULE_NAME, PATH_SSH_AGENT); + if (env_fp) + (void)fclose(env_fp); + return PAM_SESSION_ERR; + } + if (!(ssh_env = env_new())) + return PAM_SESSION_ERR; + if ((retval = pam_set_data(pamh, "ssh_env_handle", ssh_env, + env_cleanup)) != PAM_SUCCESS) + return retval; + while (fgets(parse, sizeof parse, pipe)) { + if (env_fp) + (void)fputs(parse, env_fp); + /* + * Save environment for application with pam_putenv() + * but also with env_* functions for our own call to + * ssh_get_authentication_connection(). + */ + if (strchr(parse, '=') && (env_end = strchr(parse, ';'))) { + *env_end = '\0'; + /* pass to the application ... */ + if (!((retval = pam_putenv(pamh, parse)) == + PAM_SUCCESS)) { + (void)pclose(pipe); + if (env_fp) + (void)fclose(env_fp); + env_destroy(ssh_env); + return PAM_SERVICE_ERR; + } + env_put(ssh_env, parse); + } + } + if (env_fp) + (void)fclose(env_fp); + switch (retval = pclose(pipe)) { + case -1: + syslog(LOG_ERR, "%s: %s: %m", MODULE_NAME, PATH_SSH_AGENT); + env_destroy(ssh_env); + return PAM_SESSION_ERR; + case 0: + break; + case 127: + syslog(LOG_ERR, "%s: cannot execute %s", MODULE_NAME, + PATH_SSH_AGENT); + env_destroy(ssh_env); + return PAM_SESSION_ERR; + default: + syslog(LOG_ERR, "%s: %s exited with status %d", + MODULE_NAME, PATH_SSH_AGENT, WEXITSTATUS(retval)); + env_destroy(ssh_env); + return PAM_SESSION_ERR; + } + /* connect to the agent and hand off the private key */ + if ((retval = pam_get_data(pamh, "ssh_private_key", + (const void **)&key)) != PAM_SUCCESS || + (retval = pam_get_data(pamh, "ssh_key_comment", + (const void **)&comment)) != PAM_SUCCESS || + (retval = env_commit(ssh_env)) != PAM_SUCCESS) { + env_destroy(ssh_env); + return retval; + } + if (!(ac = ssh_get_authentication_connection())) { + syslog(LOG_ERR, "%s: could not connect to agent", + MODULE_NAME); + env_destroy(ssh_env); + return PAM_SESSION_ERR; + } + retval = ssh_add_identity(ac, key, comment); + ssh_close_authentication_connection(ac); + env_swap(ssh_env, 0); + return retval ? PAM_SUCCESS : PAM_SESSION_ERR; +} + + +PAM_EXTERN int +pam_sm_close_session( + pam_handle_t *pamh, + int flags, + int argc, + const char **argv) +{ + const char *env_file; /* ssh-agent environment */ + int retval; /* from calls */ + ENV *ssh_env; /* env handle */ + + if ((retval = pam_get_data(pamh, "ssh_env_handle", + (const void **)&ssh_env)) != PAM_SUCCESS) + return retval; + env_swap(ssh_env, 1); + /* kill the agent */ + retval = system(PATH_SSH_AGENT " -k"); + env_destroy(ssh_env); + switch (retval) { + case -1: + syslog(LOG_ERR, "%s: %s -k: %m", MODULE_NAME, + PATH_SSH_AGENT); + return PAM_SESSION_ERR; + case 0: + break; + case 127: + syslog(LOG_ERR, "%s: cannot execute %s -k", MODULE_NAME, + PATH_SSH_AGENT); + return PAM_SESSION_ERR; + default: + syslog(LOG_ERR, "%s: %s -k exited with status %d", + MODULE_NAME, PATH_SSH_AGENT, WEXITSTATUS(retval)); + return PAM_SESSION_ERR; + } + /* retrieve environment filename, then remove the file */ + if ((retval = pam_get_data(pamh, "ssh_agent_env", + (const void **)&env_file)) != PAM_SUCCESS) + return retval; + (void)unlink(env_file); + return PAM_SUCCESS; +} + + +PAM_MODULE_ENTRY(MODULE_NAME); |