#include <sys/param.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <dirent.h>
#include <pwd.h>
#include <signal.h>
#include <ssh.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 <openssl/dsa.h>
#include <openssl/evp.h>
#include "key.h"
#include "authfd.h"
#include "authfile.h"
#include "log.h"
#include "pam_ssh.h"
void
key_cleanup(pam_handle_t *pamh, void *data, int error_status)
{
if (data)
key_free(data);
}
void
ssh_cleanup(pam_handle_t *pamh, void *data, int error_status)
{
if (data)
free(data);
}
int
auth_via_key(pam_handle_t *pamh, int type, const char *file,
const char *dir, const struct passwd *user, const char *pass)
{
char *comment;
char *data_name;
static int index = 0;
Key *key;
char *path;
int retval;
uid_t saved_uid;
if (!asprintf(&path, "%s/%s", dir, file)) {
syslog(LOG_CRIT, "%s: %m", MODULE_NAME);
return PAM_SERVICE_ERR;
}
saved_uid = geteuid();
seteuid(user->pw_uid);
key = key_load_private_type(type, path, pass, &comment);
free(path);
seteuid(saved_uid);
if (key == NULL)
return PAM_AUTH_ERR;
if (!asprintf(&data_name, "ssh_private_key_%d", index)) {
syslog(LOG_CRIT, "%s: %m", MODULE_NAME);
free(comment);
return PAM_SERVICE_ERR;
}
retval = pam_set_data(pamh, data_name, key, key_cleanup);
free(data_name);
if (retval != PAM_SUCCESS) {
key_free(key);
free(comment);
return retval;
}
if (!asprintf(&data_name, "ssh_key_comment_%d", index)) {
syslog(LOG_CRIT, "%s: %m", MODULE_NAME);
free(comment);
return PAM_SERVICE_ERR;
}
retval = pam_set_data(pamh, data_name, comment, ssh_cleanup);
free(data_name);
if (retval != PAM_SUCCESS) {
free(comment);
return retval;
}
++index;
return PAM_SUCCESS;
}
PAM_EXTERN int
pam_sm_authenticate(pam_handle_t *pamh, int flags, int argc, const char **argv)
{
struct options options;
int authenticated;
char *dotdir;
struct dirent *dotdir_ent;
DIR *dotdir_p;
const char *pass;
struct passwd *pwd;
struct passwd *pwd_keep;
int retval;
int pam_auth_dsa;
int pam_auth_rsa;
const char *user;
pam_std_option(&options, NULL, argc, argv);
PAM_LOG("Options processed");
retval = pam_get_user(pamh, &user, NULL);
if (retval != PAM_SUCCESS)
PAM_RETURN(retval);
pwd = getpwnam(user);
if (pwd == NULL || pwd->pw_dir == NULL)
PAM_RETURN(PAM_AUTH_ERR);
PAM_LOG("Got user: %s", user);
retval = pam_get_pass(pamh, &pass, NEED_PASSPHRASE, &options);
if (retval != PAM_SUCCESS)
PAM_RETURN(retval);
OpenSSL_add_all_algorithms();
PAM_LOG("Got passphrase");
if (!asprintf(&dotdir, "%s/%s", pwd->pw_dir, SSH_CLIENT_DIR)) {
syslog(LOG_CRIT, "%s: %m", MODULE_NAME);
PAM_RETURN(PAM_SERVICE_ERR);
}
pam_auth_dsa = auth_via_key(pamh, KEY_DSA, SSH_CLIENT_ID_DSA, dotdir,
pwd, pass);
pam_auth_rsa = auth_via_key(pamh, KEY_RSA1, SSH_CLIENT_IDENTITY, dotdir,
pwd, pass);
authenticated = 0;
if (pam_auth_dsa == PAM_SUCCESS)
authenticated++;
if (pam_auth_rsa == PAM_SUCCESS)
authenticated++;
PAM_LOG("Done pre-authenticating; got %d", authenticated);
if (!asprintf(&dotdir, "%s/%s", pwd->pw_dir, SSH2_CLIENT_DIR)) {
syslog(LOG_CRIT, "%s: %m", MODULE_NAME);
PAM_RETURN(PAM_SERVICE_ERR);
}
dotdir_p = opendir(dotdir);
while (dotdir_p && (dotdir_ent = readdir(dotdir_p))) {
if (strcmp(&dotdir_ent->d_name[dotdir_ent->d_namlen -
strlen(SSH2_PUB_SUFFIX)], SSH2_PUB_SUFFIX) == 0)
continue;
if (strncmp(dotdir_ent->d_name, SSH2_DSA_PREFIX,
strlen(SSH2_DSA_PREFIX)) == 0)
retval = auth_via_key(pamh, KEY_DSA,
dotdir_ent->d_name, dotdir, pwd, pass);
else if (strncmp(dotdir_ent->d_name, SSH2_RSA_PREFIX,
strlen(SSH2_RSA_PREFIX)) == 0)
retval = auth_via_key(pamh, KEY_RSA,
dotdir_ent->d_name, dotdir, pwd, pass);
else
continue;
authenticated += (retval == PAM_SUCCESS);
}
if (!authenticated) {
PAM_VERBOSE_ERROR("SSH authentication refused");
PAM_RETURN(PAM_AUTH_ERR);
}
PAM_LOG("Done authenticating; got %d", authenticated);
pwd_keep = malloc(sizeof *pwd);
if (pwd_keep == NULL) {
syslog(LOG_CRIT, "%m");
PAM_RETURN(PAM_SERVICE_ERR);
}
memcpy(pwd_keep, pwd, sizeof *pwd_keep);
retval = pam_set_data(pamh, "ssh_passwd_entry", pwd_keep, ssh_cleanup);
if (retval != PAM_SUCCESS) {
free(pwd_keep);
PAM_RETURN(retval);
}
PAM_LOG("Saved ssh_passwd_entry");
PAM_RETURN(PAM_SUCCESS);
}
PAM_EXTERN int
pam_sm_setcred(pam_handle_t *pamh, int flags, int argc, const char **argv)
{
struct options options;
pam_std_option(&options, NULL, argc, argv);
PAM_LOG("Options processed");
PAM_RETURN(PAM_SUCCESS);
}
typedef AuthenticationConnection AC;
PAM_EXTERN int
pam_sm_open_session(pam_handle_t *pamh, int flags, int argc, const char **argv)
{
struct options options;
AC *ac;
char *agent_socket;
char *comment;
char *env_end;
char *env_file;
FILE *env_fp;
char *env_value;
char *data_name;
int final;
int index;
Key *key;
FILE *pipe;
struct passwd *pwd;
int retval;
uid_t saved_uid;
const char *tty;
char hname[MAXHOSTNAMELEN];
char env_string[BUFSIZ];
pam_std_option(&options, NULL, argc, argv);
PAM_LOG("Options processed");
retval = pam_get_data(pamh, "ssh_passwd_entry", (const void **)&pwd);
if (retval != PAM_SUCCESS)
PAM_RETURN(retval);
PAM_LOG("Got ssh_passwd_entry");
retval = pam_get_item(pamh, PAM_TTY, (const void **)&tty);
if (retval != PAM_SUCCESS)
PAM_RETURN(retval);
PAM_LOG("Got TTY");
if (gethostname(hname, sizeof hname) == 0) {
if (asprintf(&env_file, "%s/.ssh/agent-%s%s%s",
pwd->pw_dir, hname, *tty == ':' ? "" : ":", tty)
== -1) {
syslog(LOG_CRIT, "%s: %m", MODULE_NAME);
PAM_RETURN(PAM_SERVICE_ERR);
}
}
else if (asprintf(&env_file, "%s/.ssh/agent-%s", pwd->pw_dir,
tty) == -1) {
syslog(LOG_CRIT, "%s: %m", MODULE_NAME);
PAM_RETURN(PAM_SERVICE_ERR);
}
PAM_LOG("Got env_file: %s", env_file);
retval = pam_set_data(pamh, "ssh_agent_env", env_file, ssh_cleanup);
if (retval != PAM_SUCCESS) {
free(env_file);
PAM_RETURN(retval);
}
PAM_LOG("Saved env_file");
saved_uid = geteuid();
seteuid(pwd->pw_uid);
env_fp = fopen(env_file, "w");
if (env_fp != NULL)
chmod(env_file, S_IRUSR);
pipe = popen(SSH_AGENT, "r");
seteuid(saved_uid);
if (!pipe) {
syslog(LOG_ERR, "%s: %s: %m", MODULE_NAME, SSH_AGENT);
if (env_fp)
fclose(env_fp);
PAM_RETURN(PAM_SESSION_ERR);
}
PAM_LOG("Agent started as user");
agent_socket = NULL;
while (fgets(env_string, sizeof env_string, pipe)) {
if (env_fp)
fputs(env_string, env_fp);
env_value = strchr(env_string, '=');
if (env_value == NULL)
continue;
env_end = strchr(env_value, ';');
if (env_end == NULL)
continue;
*env_end = '\0';
retval = pam_putenv(pamh, env_string);
if (retval != PAM_SUCCESS) {
pclose(pipe);
if (env_fp)
fclose(env_fp);
PAM_RETURN(PAM_SERVICE_ERR);
}
putenv(env_string);
PAM_LOG("Put to environment: %s", env_string);
*env_value++ = '\0';
if (strcmp(&env_string[strlen(env_string) -
strlen(ENV_SOCKET_SUFFIX)], ENV_SOCKET_SUFFIX) == 0) {
agent_socket = strdup(env_value);
if (agent_socket == NULL) {
syslog(LOG_CRIT, "%s: %m", MODULE_NAME);
PAM_RETURN(PAM_SERVICE_ERR);
}
}
else if (strcmp(&env_string[strlen(env_string) -
strlen(ENV_PID_SUFFIX)], ENV_PID_SUFFIX) == 0) {
retval = pam_set_data(pamh, "ssh_agent_pid",
env_value, ssh_cleanup);
if (retval != PAM_SUCCESS)
PAM_RETURN(retval);
PAM_LOG("Environment write successful");
}
}
if (env_fp)
fclose(env_fp);
retval = pclose(pipe);
switch (retval) {
case -1:
syslog(LOG_ERR, "%s: %s: %m", MODULE_NAME, SSH_AGENT);
PAM_RETURN(PAM_SESSION_ERR);
case 0:
break;
case 127:
syslog(LOG_ERR, "%s: cannot execute %s", MODULE_NAME,
SSH_AGENT);
PAM_RETURN(PAM_SESSION_ERR);
default:
syslog(LOG_ERR, "%s: %s exited %s %d", MODULE_NAME,
SSH_AGENT, WIFSIGNALED(retval) ? "on signal" :
"with status", WIFSIGNALED(retval) ? WTERMSIG(retval) :
WEXITSTATUS(retval));
PAM_RETURN(PAM_SESSION_ERR);
}
if (agent_socket == NULL)
PAM_RETURN(PAM_SESSION_ERR);
PAM_LOG("Environment saved");
ac = ssh_get_authentication_connection();
if (!ac) {
syslog(LOG_ERR, "%s: %s: %m", MODULE_NAME, agent_socket);
PAM_RETURN(PAM_SESSION_ERR);
}
PAM_LOG("Connected to agent");
final = 0;
for (index = 0; ; index++) {
if (!asprintf(&data_name, "ssh_private_key_%d", index)) {
syslog(LOG_CRIT, "%s: %m", MODULE_NAME);
ssh_close_authentication_connection(ac);
PAM_RETURN(PAM_SERVICE_ERR);
}
retval = pam_get_data(pamh, data_name, (const void **)&key);
free(data_name);
if (retval != PAM_SUCCESS)
break;
if (!asprintf(&data_name, "ssh_key_comment_%d", index)) {
syslog(LOG_CRIT, "%s: %m", MODULE_NAME);
ssh_close_authentication_connection(ac);
PAM_RETURN(PAM_SERVICE_ERR);
}
retval = pam_get_data(pamh, data_name, (const void **)&comment);
free(data_name);
if (retval != PAM_SUCCESS)
break;
retval = ssh_add_identity(ac, key, comment);
if (!final)
final = retval;
}
ssh_close_authentication_connection(ac);
PAM_LOG("Keys handed off");
PAM_RETURN(final ? PAM_SUCCESS : PAM_SESSION_ERR);
}
PAM_EXTERN int
pam_sm_close_session(pam_handle_t *pamh, int flags, int argc, const char **argv)
{
struct options options;
const char *env_file;
pid_t pid;
int retval;
const char *ssh_agent_pid;
pam_std_option(&options, NULL, argc, argv);
PAM_LOG("Options processed");
retval = pam_get_data(pamh, "ssh_agent_env", (const void **)&env_file);
if (retval != PAM_SUCCESS)
PAM_RETURN(retval);
unlink(env_file);
PAM_LOG("Got ssh_agent_env");
retval = pam_get_data(pamh, "ssh_agent_pid", (const void **)&ssh_agent_pid);
if (retval != PAM_SUCCESS)
PAM_RETURN(retval);
PAM_LOG("Got ssh_agent_pid");
pid = atoi(ssh_agent_pid);
if (pid <= 0)
PAM_RETURN(PAM_SESSION_ERR);
if (kill(pid, SIGTERM) != 0) {
syslog(LOG_ERR, "%s: %s: %m", MODULE_NAME, ssh_agent_pid);
PAM_RETURN(PAM_SESSION_ERR);
}
PAM_LOG("Agent killed");
PAM_RETURN(PAM_SUCCESS);
}
PAM_MODULE_ENTRY("pam_ssh");