passdb-pam.c revision 25757faf029c369a8318349dafe952e2358df1d8
5b64d5d44892834ba97f003080f3467299b7c5c5jeff.schenk/*
5b64d5d44892834ba97f003080f3467299b7c5c5jeff.schenk Based on auth_pam.c from popa3d by Solar Designer <solar@openwall.com>.
5b64d5d44892834ba97f003080f3467299b7c5c5jeff.schenk
5b64d5d44892834ba97f003080f3467299b7c5c5jeff.schenk You're allowed to do whatever you like with this software (including
5b64d5d44892834ba97f003080f3467299b7c5c5jeff.schenk re-distribution in source and/or binary form, with or without
5b64d5d44892834ba97f003080f3467299b7c5c5jeff.schenk modification), provided that credit is given where it is due and any
5b64d5d44892834ba97f003080f3467299b7c5c5jeff.schenk modified versions are marked as such. There's absolutely no warranty.
5b64d5d44892834ba97f003080f3467299b7c5c5jeff.schenk*/
5b64d5d44892834ba97f003080f3467299b7c5c5jeff.schenk
5b64d5d44892834ba97f003080f3467299b7c5c5jeff.schenk#include "config.h"
5b64d5d44892834ba97f003080f3467299b7c5c5jeff.schenk#undef HAVE_CONFIG_H
5b64d5d44892834ba97f003080f3467299b7c5c5jeff.schenk
5b64d5d44892834ba97f003080f3467299b7c5c5jeff.schenk#ifdef PASSDB_PAM
5b64d5d44892834ba97f003080f3467299b7c5c5jeff.schenk
5b64d5d44892834ba97f003080f3467299b7c5c5jeff.schenk#include "common.h"
5b64d5d44892834ba97f003080f3467299b7c5c5jeff.schenk#include "passdb.h"
5b64d5d44892834ba97f003080f3467299b7c5c5jeff.schenk#include "mycrypt.h"
5b64d5d44892834ba97f003080f3467299b7c5c5jeff.schenk#include "safe-memset.h"
5b64d5d44892834ba97f003080f3467299b7c5c5jeff.schenk
5b64d5d44892834ba97f003080f3467299b7c5c5jeff.schenk#include <stdlib.h>
5b64d5d44892834ba97f003080f3467299b7c5c5jeff.schenk#ifdef HAVE_SECURITY_PAM_APPL_H
5b64d5d44892834ba97f003080f3467299b7c5c5jeff.schenk# include <security/pam_appl.h>
5b64d5d44892834ba97f003080f3467299b7c5c5jeff.schenk#elif defined(HAVE_PAM_PAM_APPL_H)
5b64d5d44892834ba97f003080f3467299b7c5c5jeff.schenk# include <pam/pam_appl.h>
5b64d5d44892834ba97f003080f3467299b7c5c5jeff.schenk#endif
5b64d5d44892834ba97f003080f3467299b7c5c5jeff.schenk
5b64d5d44892834ba97f003080f3467299b7c5c5jeff.schenk#if !defined(_SECURITY_PAM_APPL_H) && !defined(LINUX_PAM)
5b64d5d44892834ba97f003080f3467299b7c5c5jeff.schenk/* Sun's PAM doesn't use const. we use a bit dirty hack to check it.
5b64d5d44892834ba97f003080f3467299b7c5c5jeff.schenk Originally it was just __sun__ check, but HP/UX also uses Sun's PAM
5b64d5d44892834ba97f003080f3467299b7c5c5jeff.schenk so I thought this might work better. */
5b64d5d44892834ba97f003080f3467299b7c5c5jeff.schenk# define linux_const
5b64d5d44892834ba97f003080f3467299b7c5c5jeff.schenk#else
5b64d5d44892834ba97f003080f3467299b7c5c5jeff.schenk# define linux_const const
5b64d5d44892834ba97f003080f3467299b7c5c5jeff.schenk#endif
5b64d5d44892834ba97f003080f3467299b7c5c5jeff.schenktypedef linux_const void *pam_item_t;
5b64d5d44892834ba97f003080f3467299b7c5c5jeff.schenk
5b64d5d44892834ba97f003080f3467299b7c5c5jeff.schenk#ifdef AUTH_PAM_USERPASS
5b64d5d44892834ba97f003080f3467299b7c5c5jeff.schenk# include <security/pam_client.h>
5b64d5d44892834ba97f003080f3467299b7c5c5jeff.schenk
5b64d5d44892834ba97f003080f3467299b7c5c5jeff.schenk# ifndef PAM_BP_RCONTROL
5b64d5d44892834ba97f003080f3467299b7c5c5jeff.schenk/* Linux-PAM prior to 0.74 */
5b64d5d44892834ba97f003080f3467299b7c5c5jeff.schenk# define PAM_BP_RCONTROL PAM_BP_CONTROL
5b64d5d44892834ba97f003080f3467299b7c5c5jeff.schenk# define PAM_BP_WDATA PAM_BP_DATA
5b64d5d44892834ba97f003080f3467299b7c5c5jeff.schenk# define PAM_BP_RDATA PAM_BP_DATA
5b64d5d44892834ba97f003080f3467299b7c5c5jeff.schenk# endif
5b64d5d44892834ba97f003080f3467299b7c5c5jeff.schenk
5b64d5d44892834ba97f003080f3467299b7c5c5jeff.schenk# define USERPASS_AGENT_ID "userpass"
5b64d5d44892834ba97f003080f3467299b7c5c5jeff.schenk# define USERPASS_AGENT_ID_LENGTH 8
5b64d5d44892834ba97f003080f3467299b7c5c5jeff.schenk
5b64d5d44892834ba97f003080f3467299b7c5c5jeff.schenk# define USERPASS_USER_MASK 0x03
5b64d5d44892834ba97f003080f3467299b7c5c5jeff.schenk# define USERPASS_USER_REQUIRED 1
5b64d5d44892834ba97f003080f3467299b7c5c5jeff.schenk# define USERPASS_USER_KNOWN 2
5b64d5d44892834ba97f003080f3467299b7c5c5jeff.schenk# define USERPASS_USER_FIXED 3
5b64d5d44892834ba97f003080f3467299b7c5c5jeff.schenk#endif
5b64d5d44892834ba97f003080f3467299b7c5c5jeff.schenk
dff2cc5646d4437ab9e0cb1dcb59da65462a5938jeff.schenkstruct pam_userpass {
5b64d5d44892834ba97f003080f3467299b7c5c5jeff.schenk const char *user;
dff2cc5646d4437ab9e0cb1dcb59da65462a5938jeff.schenk const char *pass;
5b64d5d44892834ba97f003080f3467299b7c5c5jeff.schenk};
5b64d5d44892834ba97f003080f3467299b7c5c5jeff.schenk
5b64d5d44892834ba97f003080f3467299b7c5c5jeff.schenkstatic char *service_name;
dff2cc5646d4437ab9e0cb1dcb59da65462a5938jeff.schenk
5b64d5d44892834ba97f003080f3467299b7c5c5jeff.schenkstatic int pam_userpass_conv(int num_msg, linux_const struct pam_message **msg,
5b64d5d44892834ba97f003080f3467299b7c5c5jeff.schenk struct pam_response **resp, void *appdata_ptr)
dff2cc5646d4437ab9e0cb1dcb59da65462a5938jeff.schenk{
5b64d5d44892834ba97f003080f3467299b7c5c5jeff.schenk /* @UNSAFE */
dff2cc5646d4437ab9e0cb1dcb59da65462a5938jeff.schenk struct pam_userpass *userpass = (struct pam_userpass *) appdata_ptr;
5b64d5d44892834ba97f003080f3467299b7c5c5jeff.schenk#ifdef AUTH_PAM_USERPASS
5b64d5d44892834ba97f003080f3467299b7c5c5jeff.schenk pamc_bp_t prompt;
const char *input;
char *output;
char flags;
size_t userlen, passlen;
if (num_msg != 1 || msg[0]->msg_style != PAM_BINARY_PROMPT)
return PAM_CONV_ERR;
prompt = (pamc_bp_t)msg[0]->msg;
input = PAM_BP_RDATA(prompt);
if (PAM_BP_RCONTROL(prompt) != PAM_BPC_SELECT ||
strncmp(input, USERPASS_AGENT_ID "/", USERPASS_AGENT_ID_LENGTH + 1))
return PAM_CONV_ERR;
flags = input[USERPASS_AGENT_ID_LENGTH + 1];
input += USERPASS_AGENT_ID_LENGTH + 1 + 1;
if ((flags & USERPASS_USER_MASK) == USERPASS_USER_FIXED &&
strcmp(input, userpass->user))
return PAM_CONV_AGAIN;
if (!(*resp = malloc(sizeof(struct pam_response))))
return PAM_CONV_ERR;
userlen = strlen(userpass->user);
passlen = strlen(userpass->pass);
prompt = NULL;
PAM_BP_RENEW(&prompt, PAM_BPC_DONE, userlen + 1 + passlen);
output = PAM_BP_WDATA(prompt);
memcpy(output, userpass->user, userlen + 1);
memcpy(output + userlen + 1, userpass->pass, passlen);
(*resp)[0].resp_retcode = 0;
(*resp)[0].resp = (char *)prompt;
#else
char *string;
int i;
if (!(*resp = malloc(num_msg * sizeof(struct pam_response))))
return PAM_CONV_ERR;
for (i = 0; i < num_msg; i++) {
switch (msg[i]->msg_style) {
case PAM_PROMPT_ECHO_ON:
string = strdup(userpass->user);
if (string == NULL)
i_fatal("Out of memory");
break;
case PAM_PROMPT_ECHO_OFF:
string = strdup(userpass->pass);
if (string == NULL)
i_fatal("Out of memory");
break;
case PAM_ERROR_MSG:
case PAM_TEXT_INFO:
string = NULL;
break;
default:
while (--i >= 0) {
if ((*resp)[i].resp == NULL)
continue;
safe_memset((*resp)[i].resp, 0,
strlen((*resp)[i].resp));
free((*resp)[i].resp);
(*resp)[i].resp = NULL;
}
free(*resp);
*resp = NULL;
return PAM_CONV_ERR;
}
(*resp)[i].resp_retcode = PAM_SUCCESS;
(*resp)[i].resp = string;
}
#endif
return PAM_SUCCESS;
}
static int pam_auth(pam_handle_t *pamh, const char *user)
{
char *item;
int status;
if ((status = pam_authenticate(pamh, 0)) != PAM_SUCCESS) {
if (verbose) {
i_info("PAM: pam_authenticate(%s) failed: %s",
user, pam_strerror(pamh, status));
}
return status;
}
#ifdef HAVE_PAM_SETCRED
if ((status = pam_setcred(pamh, PAM_ESTABLISH_CRED)) != PAM_SUCCESS) {
if (verbose) {
i_info("PAM: pam_setcred(%s) failed: %s",
user, pam_strerror(pamh, status));
}
return status;
}
#endif
if ((status = pam_acct_mgmt(pamh, 0)) != PAM_SUCCESS) {
if (verbose) {
i_info("PAM: pam_acct_mgmt(%s) failed: %s",
user, pam_strerror(pamh, status));
}
return status;
}
status = pam_get_item(pamh, PAM_USER, (linux_const void **)&item);
if (status != PAM_SUCCESS) {
if (verbose) {
i_info("PAM: pam_get_item(%s) failed: %s",
user, pam_strerror(pamh, status));
}
return status;
}
return PAM_SUCCESS;
}
static enum passdb_result
pam_verify_plain(const char *user, const char *realm, const char *password)
{
pam_handle_t *pamh;
struct pam_userpass userpass;
struct pam_conv conv;
int status, status2;
if (realm != NULL)
user = t_strconcat(user, "@", realm, NULL);
conv.conv = pam_userpass_conv;
conv.appdata_ptr = &userpass;
userpass.user = user;
userpass.pass = password;
status = pam_start(service_name, user, &conv, &pamh);
if (status != PAM_SUCCESS) {
if (verbose) {
i_info("PAM: pam_start(%s) failed: %s",
user, pam_strerror(pamh, status));
}
return PASSDB_RESULT_INTERNAL_FAILURE;
}
status = pam_auth(pamh, user);
if ((status2 = pam_end(pamh, status)) != PAM_SUCCESS) {
i_error("pam_end(%s) failed: %s",
user, pam_strerror(pamh, status2));
return PASSDB_RESULT_INTERNAL_FAILURE;
}
/* FIXME: check for PASSDB_RESULT_UNKNOWN_USER somehow */
return status == PAM_SUCCESS ? PASSDB_RESULT_OK :
PASSDB_RESULT_PASSWORD_MISMATCH;
}
static void pam_init(const char *args)
{
service_name = i_strdup(*args != '\0' ? args : "imap");
}
static void pam_deinit(void)
{
i_free(service_name);
}
struct passdb_module passdb_pam = {
pam_init,
pam_deinit,
pam_verify_plain,
NULL
};
#endif