/*
* One-time password login PAM module
*
* Markus Kuhn
* Steven Murdoch
*
* Interface documentation:
*
* http://www.kernel.org/pub/linux/libs/pam/Linux-PAM-html/pam_modules.html
* http://www.cl.cam.ac.uk/~mgk25/otpw.html
*
* Inspired by pam_pwdfile.c by Charl P. Botha
* and pam_unix/support.c (part of the standard PAM distribution)
*
*/
#include
#include
#include
#include
#include
#include
#include
#define PAM_SM_AUTH
#define PAM_SM_SESSION
#include
#ifdef OPENPAM
#include
#endif
#include "otpw.h"
#define D(a) if (debug) { a; }
#define MODULE_NAME "pam_otpw"
/*
* Output logging information to syslog
*
* pamh pointer to the PAM handle
* priority, format, ... passed on to (v)syslog
*
* (based on _log_err in pam_unix/support.c)
*/
void log_message(int priority, pam_handle_t *pamh, const char *format, ...)
{
char *service = NULL;
char logname[80];
va_list args;
if (pamh)
pam_get_item(pamh, PAM_SERVICE, (const void **) &service);
if (!service)
service = "";
snprintf(logname, sizeof(logname), "%s(" MODULE_NAME ")", service);
va_start(args, format);
openlog(logname, LOG_CONS | LOG_PID, LOG_AUTH);
vsyslog(priority, format, args); /* from BSD, not POSIX */
va_end(args);
closelog();
}
/*
* Wrapper around conversation function (a callback function provided by
* the PAM application to interact with the user)
*
* (based on converse in pam_unix/support.c)
*/
static int converse(pam_handle_t *pamh, int nargs,
struct pam_message **message,
struct pam_response **response,
int debug)
{
int retval;
struct pam_conv *conv;
/* get pointer to conversation function */
retval = pam_get_item(pamh, PAM_CONV, (const void **) &conv);
if (retval != PAM_SUCCESS) {
log_message(LOG_ERR, pamh, "no conversation function: %s",
pam_strerror(pamh, retval));
return retval;
}
D(log_message(LOG_DEBUG, pamh, "calling conversation function"));
/* call conversation function */
retval = conv->conv(nargs, (const struct pam_message **) message,
response, conv->appdata_ptr);
D(log_message(LOG_DEBUG, pamh, "conversation function returned %d", retval));
if (retval != PAM_SUCCESS) {
log_message(LOG_WARNING, pamh, "conversation function failed: %s",
pam_strerror(pamh, retval));
}
return retval; /* propagate error status */
}
/* we register cleanup() to be called when the app calls pam_end(),
* to make sure that otpw_verify() gets a chance to remove locks */
static void cleanup(pam_handle_t *pamh, void *data, int err)
{
int debug = ((struct challenge *) data)->flags & OTPW_DEBUG;
D(log_message(LOG_DEBUG, pamh,"cleanup() called, data=%p, err=%d",
data, err));
if (((struct challenge *) data)->passwords)
otpw_verify((struct challenge *) data, "entryaborted");
free(data);
}
/*
* Issue password prompt with challenge and receive response from user
*
* (based on _set_auth_tok from pam_pwdfile.c, originally based
* on pam_unix/support.c but that no longer seems to exist)
*/
static int get_response(pam_handle_t *pamh, char *challenge, int debug)
{
int retval;
volatile char *p;
struct pam_message msg, *pmsg[1];
struct pam_response *resp;
char message[81];
/* format password prompt */
snprintf(message, sizeof(message), "Password %s: ", challenge);
/* set up conversation call */
pmsg[0] = &msg;
msg.msg_style = PAM_PROMPT_ECHO_OFF;
msg.msg = message;
resp = NULL;
/* call conversation function */
if ((retval = converse(pamh, 1, pmsg, &resp, debug)) != PAM_SUCCESS) {
/* converse has already output a warning log message here */
return retval;
}
/* error handling (just to be safe) */
if (!resp) {
log_message(LOG_WARNING, pamh, "get_response(): resp==NULL");
return PAM_CONV_ERR;
}
if (!resp[0].resp) {
log_message(LOG_WARNING, pamh, "get_response(): resp[0].resp==NULL");
free(resp);
return PAM_CONV_ERR;
}
/* store response as PAM item */
pam_set_item(pamh, PAM_AUTHTOK, resp[0].resp);
/* sanitize and free buffer */
for (p = resp[0].resp; *p; p++)
*p = 0;
free(resp[0].resp);
free(resp);
return PAM_SUCCESS;
}
/*
* Display a notice (err==0) or error message (err==1) to the user
*/
static int display_notice(pam_handle_t *pamh, int err, int debug,
char *format, ...)
{
int retval;
struct pam_message msg, *pmsg[1];
struct pam_response *resp;
char message[1024];
va_list args;
/* format message */
va_start(args, format);
vsnprintf(message, sizeof(message), format, args);
va_end(args);
/* set up conversation call */
pmsg[0] = &msg;
msg.msg_style = err ? PAM_ERROR_MSG : PAM_TEXT_INFO /* PAM_TEXT_INFO */;
msg.msg = message;
resp = NULL;
/* call conversation function */
if ((retval = converse(pamh, 1, pmsg, &resp, debug)) != PAM_SUCCESS) {
/* converse has already output a warning log message here */
return retval;
}
/* memory wants to be free */
if (resp) {
if (resp[0].resp)
free(resp[0].resp);
free(resp);
}
return PAM_SUCCESS;
}
/* provided entry point for auth service */
PAM_EXTERN int pam_sm_authenticate(pam_handle_t *pamh, int flags,
int argc, const char **argv)
{
int retval;
const char *username;
char *password;
struct otpw_pwdbuf *user;
struct challenge *ch = NULL;
int i, debug = 0, otpw_flags = 0;
/* parse option flags */
for (i = 0; i < argc; i++) {
if (!strcmp(argv[i], "debug")) {
debug = 1;
otpw_flags |= OTPW_DEBUG;
} else if (!strcmp(argv[i], "nolock")) {
otpw_flags |= OTPW_NOLOCK;
}
}
D(log_message(LOG_DEBUG, pamh, "pam_sm_authenticate called, flags=%d",
flags));
/* get user name */
retval = pam_get_user(pamh, &username, "login: ");
#ifdef OPENPAM
if (retval == PAM_CONV_ERR)
return PAM_CONV_ERR;
#else
if (retval == PAM_CONV_AGAIN)
return PAM_INCOMPLETE;
#endif
else if (retval != PAM_SUCCESS) {
log_message(LOG_NOTICE, pamh, "no username provided");
return PAM_USER_UNKNOWN;
}
/* DEBUG */
D(log_message(LOG_DEBUG, pamh, "username is %s", username));
D(log_message(LOG_DEBUG, pamh, "uid=%d, euid=%d, gid=%d, egid=%d",
getuid(), geteuid(), getgid(), getegid()));
/* consult POSIX password database (to find homedir, etc.) */
otpw_getpwnam(username, &user);
if (!user) {
log_message(LOG_NOTICE, pamh, "username not found");
return PAM_USER_UNKNOWN;
}
/*
* Make sure that otpw_verify() is always called to clean up locks,
* even if the connection is aborted while we are in get_response()
* or something else goes wrong.
*/
ch = calloc(1, sizeof(struct challenge));
if (!ch)
return PAM_AUTHINFO_UNAVAIL;
retval = pam_set_data(pamh, MODULE_NAME":ch", ch, cleanup);
if (retval != PAM_SUCCESS) {
log_message(LOG_ERR, pamh, "pam_set_data() failed");
return PAM_AUTHINFO_UNAVAIL;
}
/* check whether a pseudo-user for owning OTPW files exist */
otpw_set_pseudouser(&otpw_pseudouser);
/* prepare OTPW challenge */
otpw_prepare(ch, &user->pwd, otpw_flags);
free(user);
if (otpw_pseudouser) {
free(otpw_pseudouser);
otpw_pseudouser = NULL;
}
D(log_message(LOG_DEBUG, pamh, "challenge: %s", ch->challenge));
if (ch->passwords < 1) {
/* it seems OTPW might not have been set up or has exhausted keys,
perhaps explain here in info msg how to "man otpw-gen" */
log_message(LOG_NOTICE, pamh, "OTPW not set up for user %s", username);
return PAM_AUTHINFO_UNAVAIL;
}
/* Issue challenge, get response */
retval = get_response(pamh, ch->challenge, debug);
if (retval != PAM_SUCCESS) {
log_message(LOG_ERR, pamh,"get_response() failed: %s",
pam_strerror(pamh, retval));
return PAM_AUTHINFO_UNAVAIL;
}
retval = pam_get_item(pamh, PAM_AUTHTOK, (void *)&password);
if (retval != PAM_SUCCESS) {
log_message(LOG_ERR, pamh, "auth token not found");
return PAM_AUTHINFO_UNAVAIL;
}
if (!password) {
/* NULL passwords are checked in get_response so this
* point in the code should never be reached */
log_message(LOG_ERR, pamh, "password==NULL (should never happen)");
return PAM_AUTHINFO_UNAVAIL;
}
/* verify response */
retval = otpw_verify(ch, password);
if (retval == OTPW_OK) {
D(log_message(LOG_DEBUG, pamh, "password matches"));
return PAM_SUCCESS;
} else if (retval == OTPW_WRONG) {
log_message(LOG_NOTICE, pamh, "incorrect password from user %s", username);
return PAM_AUTH_ERR;
}
log_message(LOG_ERR, pamh, "OTPW error %d for user %s", retval, username);
return PAM_AUTHINFO_UNAVAIL;
}
/* another expected entry point */
PAM_EXTERN int pam_sm_setcred(pam_handle_t *pamh, int flags,
int argc, const char **argv)
{
(void) pamh;
(void) flags;
(void) argc;
(void) argv;
/* NOP */
return PAM_SUCCESS;
}
/* this is called after the user has logged in */
PAM_EXTERN int pam_sm_open_session(pam_handle_t *pamh, int flags,
int argc, const char **argv)
{
struct challenge *ch = NULL;
int retval;
int i, debug = 0;
/* parse option flags */
for (i = 0; i < argc; i++) {
if (!strcmp(argv[i], "debug"))
debug = 1;
}
D(log_message(LOG_DEBUG, pamh, "pam_sm_open_session called, flags=%d",
flags));
retval = pam_get_data(pamh, MODULE_NAME":ch", (const void **) &ch);
if (retval != PAM_SUCCESS || !ch) {
log_message(LOG_ERR, pamh, "pam_get_data() failed");
return PAM_SESSION_ERR;
}
if (!(flags & PAM_SILENT) && ch->entries >= 0) {
display_notice(pamh, 0, debug,
"Remaining one-time passwords: %d of %d%s",
ch->remaining, ch->entries,
(ch->remaining < ch->entries/2) || (ch->remaining < 20) ?
" (time to print new ones with otpw-gen)" : "");
}
return PAM_SUCCESS;
}
/* another expected entry point */
PAM_EXTERN int pam_sm_close_session(pam_handle_t *pamh, int flags,
int argc, const char **argv)
{
(void) pamh;
(void) flags;
(void) argc;
(void) argv;
/* NOP */
return PAM_SUCCESS;
}
#ifdef PAM_STATIC
struct pam_module _pam_listfile_modstruct = {
MODULE_NAME,
pam_sm_authenticate,
pam_sm_setcred,
NULL,
NULL,
NULL,
NULL
};
#endif