passdb-pam.c revision 871ec63cffdd0d7e4d0d85ac39f3806ea27bc8b2
/*
Based on auth_pam.c from popa3d by Solar Designer <solar@openwall.com>.
You're allowed to do whatever you like with this software (including
modification), provided that credit is given where it is due and any
modified versions are marked as such. There's absolutely no warranty.
*/
#include "common.h"
#ifdef PASSDB_PAM
#include "lib-signals.h"
#include "buffer.h"
#include "ioloop.h"
#include "hash.h"
#include "str.h"
#include "var-expand.h"
#include "network.h"
#include "passdb.h"
#include "safe-memset.h"
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#ifdef HAVE_SECURITY_PAM_APPL_H
# include <security/pam_appl.h>
#elif defined(HAVE_PAM_PAM_APPL_H)
# include <pam/pam_appl.h>
#endif
/* Sun's PAM doesn't use const. we use a bit dirty hack to check it.
so I thought this might work better. */
# define linux_const
#else
# define linux_const const
#endif
typedef linux_const void *pam_item_t;
#ifdef AUTH_PAM_USERPASS
# include <security/pam_client.h>
# ifndef PAM_BP_RCONTROL
/* Linux-PAM prior to 0.74 */
# define PAM_BP_RCONTROL PAM_BP_CONTROL
# define PAM_BP_WDATA PAM_BP_DATA
# define PAM_BP_RDATA PAM_BP_DATA
# endif
# define USERPASS_AGENT_ID "userpass"
# define USERPASS_AGENT_ID_LENGTH 8
# define USERPASS_USER_MASK 0x03
# define USERPASS_USER_REQUIRED 1
# define USERPASS_USER_KNOWN 2
# define USERPASS_USER_FIXED 3
#endif
struct pam_passdb_module {
struct passdb_module module;
bool pam_setcred, pam_session;
const char *service_name, *pam_cache_key;
};
struct pam_auth_request {
int refcount;
int fd;
struct auth_request *request;
};
struct pam_userpass {
const char *user;
const char *pass;
};
static struct hash_table *pam_requests;
{
/* @UNSAFE */
#ifdef AUTH_PAM_USERPASS
const char *input;
char *output;
char flags;
return PAM_CONV_ERR;
return PAM_CONV_ERR;
return PAM_CONV_AGAIN;
return PAM_CONV_ERR;
(*resp)[0].resp_retcode = 0;
#else
char *string;
int i;
return PAM_CONV_ERR;
for (i = 0; i < num_msg; i++) {
case PAM_PROMPT_ECHO_ON:
break;
case PAM_PROMPT_ECHO_OFF:
break;
case PAM_ERROR_MSG:
case PAM_TEXT_INFO:
break;
default:
while (--i >= 0) {
continue;
}
return PAM_CONV_ERR;
}
}
#endif
return PAM_SUCCESS;
}
{
void *item;
int status;
return status;
}
#ifdef HAVE_PAM_SETCRED
if (module->pam_setcred) {
PAM_SUCCESS) {
return status;
}
}
#endif
return status;
}
if (module->pam_session) {
*error = t_strdup_printf(
"pam_open_session() failed: %s",
return status;
}
*error = t_strdup_printf(
"pam_close_session() failed: %s",
return status;
}
}
/* FIXME: this doesn't actually work since we're in the child
process.. */
if (status != PAM_SUCCESS) {
return status;
}
return PAM_SUCCESS;
}
static enum passdb_result
{
struct pam_userpass userpass;
enum passdb_result result;
const char *str;
if (status != PAM_SUCCESS) {
} else {
/* Set some PAM items. They shouldn't fail, and we don't really
care if they do. */
/* TTY is needed by eg. pam_access module */
switch (status) {
case PAM_SUCCESS:
break;
case PAM_USER_UNKNOWN:
break;
case PAM_NEW_AUTHTOK_REQD:
case PAM_ACCT_EXPIRED:
break;
default:
break;
}
} else {
}
}
if (worker) {
/* blocking=yes code path in auth worker */
return result;
}
/* Don't send larger writes than what would block. truncated error
message isn't that bad.. */
if (ret < 0)
i_error("write() failed: %m");
else {
}
}
return result;
}
{
enum passdb_result result;
/* POSIX guarantees that writing PIPE_BUF bytes or less to pipes is
atomic. We rely on that. */
if (ret < 0) {
"read() from child process failed: %m");
} else if (ret == 0) {
/* it died */
"Child process died");
"Child process returned only %d bytes", (int)ret);
} else {
/* error message included */
if (result == PASSDB_RESULT_INTERNAL_FAILURE) {
} else {
}
}
}
"close(child input) failed: %m");
}
}
void *context __attr_unused__)
{
struct pam_auth_request *request;
int status;
/* FIXME: if we ever do some other kind of forking, this needs fixing */
if (pid == -1) {
i_error("waitpid() failed: %m");
return;
}
i_error("PAM: Unknown child %s exited with status %d",
continue;
}
if (WIFSIGNALED(status)) {
i_error("PAM: Child %s died with signal %d",
i_error("PAM: Child %s exited unexpectedly with "
}
}
}
static void
{
struct pam_auth_request *pam_auth_request;
enum passdb_result result;
const char *service;
int fd[2];
if (worker) {
/* blocking=yes code path in auth worker */
return;
}
return;
}
if (pid == -1) {
return;
}
if (pid == 0) {
_exit(0);
}
"close(fd[1]) failed: %m");
}
}
static struct passdb_module *
{
struct pam_passdb_module *module;
const char *const *t_args;
int i;
t_push();
/* -session for backwards compatibility */
t_args[i] + 10);
/* for backwards compatibility */
if (*t_args[i] != '\0') {
t_args[i]);
}
} else {
}
}
t_pop();
}
{
struct hash_iterate_context *iter;
continue;
"PAM child process %s timed out, killing it",
i_error("PAM: kill(%s) failed: %m",
}
}
}
const char *args __attr_unused__)
{
if (pam_requests != NULL)
i_fatal("Can't support more than one PAM passdb");
/* we're caching the password by using directly the plaintext password
given by the auth mechanism */
}
}
{
timeout_remove(&to);
}
}
struct passdb_module_interface passdb_pam = {
"pam",
NULL,
};
#endif