blob: 49c81820ed187166647dde8a13084e92031ab7fd [file] [log] [blame]
/*
* pam_duo.c
*
* Copyright (c) 2010 Duo Security
* All rights reserved, all wrongs reversed.
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include <sys/param.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <ctype.h>
#include <errno.h>
#include <grp.h>
#include <limits.h>
#include <netdb.h>
#include <pwd.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <syslog.h>
#include <unistd.h>
/* These #defines must be present according to PAM documentation. */
#define PAM_SM_AUTH
#define PAM_SM_ACCOUNT
#define PAM_SM_SESSION
#define PAM_SM_PASSWORD
/* NetBSD PAM b0rkage (gnat 39313) */
#ifdef __NetBSD__
#define NO_STATIC_MODULES
#endif
#ifdef HAVE_SECURITY_PAM_APPL_H
#include <security/pam_appl.h>
#endif
#ifdef HAVE_SECURITY_PAM_MODULES_H
#include <security/pam_modules.h>
#endif
#ifdef HAVE_SECURITY_PAM_EXT_H
#include <security/pam_ext.h> /* Linux-PAM */
#endif
/* OpenGroup RFC86.0 and XSSO specify no "const" on arguments */
#if defined(__LINUX_PAM__) || defined(OPENPAM)
# define duopam_const const /* LinuxPAM, OpenPAM */
#else
# define duopam_const /* Solaris, HP-UX, AIX */
#endif
#include "duo.h"
#include "groupaccess.h"
#include "pam_extra.h"
#ifndef PAM_EXTERN
#define PAM_EXTERN
#endif
#ifndef DUO_PRIVSEP_USER
# define DUO_PRIVSEP_USER "duo"
#endif
#define DUO_CONF DUO_CONF_DIR "/pam_duo.conf"
#define MAX_RETRIES 3
#define MAX_GROUPS 256
enum {
DUO_FAIL_SAFE = 0,
DUO_FAIL_SECURE,
};
int debug = 0;
struct duo_config {
char *ikey;
char *skey;
char *host;
char *cafile;
char *groups[MAX_GROUPS];
int groups_cnt;
int failmode; /* Duo failure handling: DUO_FAIL_* */
int pushinfo;
int noverify;
};
static void
_syslog(int priority, const char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
if (debug) {
fprintf(stderr, "[%d] ", priority);
vfprintf(stderr, fmt, ap);
fputs("\n", stderr);
} else {
vsyslog(priority, fmt, ap);
}
va_end(ap);
}
static int
__ini_handler(void *u, const char *section, const char *name, const char *val)
{
struct duo_config *cfg = (struct duo_config *)u;
char *buf, *p;
if (strcmp(name, "ikey") == 0) {
cfg->ikey = strdup(val);
} else if (strcmp(name, "skey") == 0) {
cfg->skey = strdup(val);
} else if (strcmp(name, "host") == 0) {
cfg->host = strdup(val);
} else if (strcmp(name, "cafile") == 0) {
cfg->cafile = strdup(val);
} else if (strcmp(name, "groups") == 0 || strcmp(name, "group") == 0) {
if ((buf = strdup(val)) == NULL) {
_syslog(LOG_ERR, "Out of memory parsing groups");
return (0);
}
for (p = strtok(buf, " "); p != NULL; p = strtok(NULL, " ")) {
if (cfg->groups_cnt >= MAX_GROUPS) {
_syslog(LOG_ERR, "Exceeded max %d groups",
MAX_GROUPS);
cfg->groups_cnt = 0;
free(buf);
return (0);
}
cfg->groups[cfg->groups_cnt++] = p;
}
} else if (strcmp(name, "failmode") == 0) {
if (strcmp(val, "secure") == 0) {
cfg->failmode = DUO_FAIL_SECURE;
} else if (strcmp(val, "safe") == 0) {
cfg->failmode = DUO_FAIL_SAFE;
} else {
_syslog(LOG_ERR, "Invalid failmode: '%s'", val);
return (0);
}
} else if (strcmp(name, "pushinfo") == 0) {
if (strcmp(val, "yes") == 0 || strcmp(val, "true") == 0 ||
strcmp(val, "on") == 0 || strcmp(val, "1") == 0) {
cfg->pushinfo = 1;
}
} else if (strcmp(name, "noverify") == 0) {
if (strcmp(val, "yes") == 0 || strcmp(val, "true") == 0 ||
strcmp(val, "on") == 0 || strcmp(val, "1") == 0) {
cfg->noverify = 1;
}
} else {
_syslog(LOG_ERR, "Invalid pam_duo option: '%s'", name);
return (0);
}
return (1);
}
static void
__duo_status(void *arg, const char *msg)
{
pam_info((pam_handle_t *)arg, "%s", msg);
}
static char *
__duo_prompt(void *arg, const char *prompt, char *buf, size_t bufsz)
{
char *p;
if (pam_prompt((pam_handle_t *)arg, PAM_PROMPT_ECHO_ON, &p,
"%s", prompt) != PAM_SUCCESS) {
return (NULL);
}
strlcpy(buf, p, bufsz);
free(p);
return (buf);
}
static void
_log(int priority, const char *msg,
const char *user, const char *ip, const char *err)
{
char buf[512];
int i, n;
n = snprintf(buf, sizeof(buf), "%s", msg);
if (user != NULL &&
(i = snprintf(buf + n, sizeof(buf) - n, " for '%s'", user)) > 0) {
n += i;
}
if (ip != NULL &&
(i = snprintf(buf + n, sizeof(buf) - n, " from %s", ip)) > 0) {
n += i;
}
if (err != NULL &&
(i = snprintf(buf + n, sizeof(buf) - n, ": %s", err)) > 0) {
n += i;
}
_syslog(priority, "%s", buf);
}
PAM_EXTERN int
pam_sm_authenticate(pam_handle_t *pamh, int pam_flags,
int argc, const char *argv[])
{
struct duo_config cfg;
struct passwd *pw;
duo_t *duo;
duo_code_t code;
duopam_const char *config, *cmd, *ip, *p, *service, *user;
int i, flags, pam_err;
memset(&cfg, 0, sizeof(cfg));
cfg.failmode = DUO_FAIL_SAFE;
/* Check user */
if ((pam_err = pam_get_user(pamh, &user, NULL)) != PAM_SUCCESS ||
(pw = getpwnam(user)) == NULL) {
return (PAM_USER_UNKNOWN);
}
/* Parse configuration */
config = DUO_CONF;
for (i = 0; i < argc; i++) {
if (strncmp("conf=", argv[i], 5) == 0) {
config = argv[i] + 5;
} else if (strcmp("debug", argv[i]) == 0) {
debug = 1;
} else {
_syslog(LOG_ERR, "Invalid pam_duo option: '%s'",
argv[i]);
return (PAM_SERVICE_ERR);
}
}
i = duo_parse_config(config, __ini_handler, &cfg);
if (i == -2) {
_syslog(LOG_ERR, "%s must be readable only by user 'root'",
config);
return (PAM_SERVICE_ERR);
} else if (i == -1) {
_syslog(LOG_ERR, "Couldn't open %s: %s",
config, strerror(errno));
return (PAM_SERVICE_ERR);
} else if (i > 0) {
_syslog(LOG_ERR, "Parse error in %s, line %d", config, i);
return (PAM_SERVICE_ERR);
} else if (!cfg.host || !cfg.host[0] ||
!cfg.skey || !cfg.skey[0] || !cfg.ikey || !cfg.ikey[0]) {
_syslog(LOG_ERR, "Missing host, ikey, or skey in %s", config);
return (PAM_SERVICE_ERR);
}
/* Check group membership */
if (cfg.groups_cnt > 0) {
int matched = 0;
if (ga_init(pw->pw_name, pw->pw_gid) < 0) {
_log(LOG_ERR, "Couldn't get groups",
pw->pw_name, NULL, strerror(errno));
return (PAM_SERVICE_ERR);
}
for (i = 0; i < cfg.groups_cnt; i++) {
if (ga_match_pattern_list(cfg.groups[i])) {
matched = 1;
break;
}
}
ga_free();
/* User in configured groups for Duo auth? */
if (!matched)
return (PAM_SUCCESS);
}
/*
* XXX - Disable incremental status reporting for sshd :-(
* OpenSSH accumulates PAM_TEXT_INFO from modules to send in
* an SSH_MSG_USERAUTH_BANNER post-auth, not real-time!
*/
flags = 0;
cmd = NULL;
if (pam_get_item(pamh, PAM_SERVICE, (duopam_const void **)
(duopam_const void *)&service) == PAM_SUCCESS) {
if (strcmp(service, "sshd") == 0) {
flags |= DUO_FLAG_SYNC;
} else if (strcmp(service, "sudo") == 0) {
cmd = getenv("SUDO_COMMAND");
}
}
ip = NULL;
pam_get_item(pamh, PAM_RHOST,
(duopam_const void **)(duopam_const void *)&ip);
/* Try Duo auth */
if ((duo = duo_open(cfg.host, cfg.ikey, cfg.skey,
"pam_duo/" PACKAGE_VERSION,
cfg.noverify ? "" : cfg.cafile)) == NULL) {
_log(LOG_ERR, "Couldn't open Duo API handle", user, ip, NULL);
return (PAM_SERVICE_ERR);
}
duo_set_conv_funcs(duo, __duo_prompt, __duo_status, pamh);
pam_err = PAM_SERVICE_ERR;
for (i = 0; i < MAX_RETRIES; i++) {
code = duo_login(duo, user, ip, flags,
cfg.pushinfo ? cmd : NULL);
if (code == DUO_FAIL) {
_log(LOG_WARNING, "Failed Duo login",
user, ip, duo_geterr(duo));
if ((flags & DUO_FLAG_SYNC) == 0) {
pam_info(pamh, "%s", "");
}
/* Keep going */
continue;
}
/* Terminal conditions */
if (code == DUO_OK) {
if ((p = duo_geterr(duo)) != NULL) {
_log(LOG_WARNING, "Skipped Duo login",
user, ip, p);
} else {
_log(LOG_INFO, "Successful Duo login",
user, ip, NULL);
}
pam_err = PAM_SUCCESS;
} else if (code == DUO_ABORT) {
_log(LOG_WARNING, "Aborted Duo login",
user, ip, duo_geterr(duo));
pam_err = PAM_ABORT;
} else if (cfg.failmode == DUO_FAIL_SAFE &&
(code == DUO_CONN_ERROR ||
code == DUO_CLIENT_ERROR || code == DUO_SERVER_ERROR)) {
_log(LOG_WARNING, "Failsafe Duo login",
user, ip, duo_geterr(duo));
pam_err = PAM_SUCCESS;
} else {
_log(LOG_ERR, "Error in Duo login",
user, ip, duo_geterr(duo));
pam_err = PAM_SERVICE_ERR;
}
break;
}
if (i == MAX_RETRIES) {
pam_err = PAM_MAXTRIES;
}
duo_close(duo);
return (pam_err);
}
PAM_EXTERN int
pam_sm_setcred(pam_handle_t *pamh, int flags,
int argc, const char *argv[])
{
return (PAM_SUCCESS);
}
PAM_EXTERN int
pam_sm_acct_mgmt(pam_handle_t *pamh, int flags,
int argc, const char *argv[])
{
return (PAM_SUCCESS);
}
PAM_EXTERN int
pam_sm_open_session(pam_handle_t *pamh, int flags,
int argc, const char *argv[])
{
return (PAM_SUCCESS);
}
PAM_EXTERN int
pam_sm_close_session(pam_handle_t *pamh, int flags,
int argc, const char *argv[])
{
return (PAM_SUCCESS);
}
PAM_EXTERN int
pam_sm_chauthtok(pam_handle_t *pamh, int flags,
int argc, const char *argv[])
{
return (PAM_SERVICE_ERR);
}
#ifdef PAM_MODULE_ENTRY
PAM_MODULE_ENTRY("pam_duo");
#endif