db-checkpassword.c revision a74bc46dde2a422052458587b4336757d6b62227
/* Copyright (c) 2004-2013 Dovecot authors, see the included COPYING file */
#include "auth-common.h"
#if defined(PASSDB_CHECKPASSWORD) || defined(USERDB_CHECKPASSWORD)
#include "lib-signals.h"
#include "array.h"
#include "buffer.h"
#include "str.h"
#include "ioloop.h"
#include "hash.h"
#include "execv-const.h"
#include "env-util.h"
#include "safe-memset.h"
#include "strescape.h"
#include "child-wait.h"
#include "var-expand.h"
#include "db-checkpassword.h"
#include <stdlib.h>
#include <unistd.h>
#define CHECKPASSWORD_MAX_REQUEST_LEN 512
struct chkpw_auth_request {
struct db_checkpassword *db;
struct auth_request *request;
char *auth_password;
void (*request_callback)();
unsigned int output_pos, output_len;
int exit_status;
unsigned int exited:1;
};
struct db_checkpassword {
struct child_wait *child_wait;
};
static void
{
const struct auth_field *field;
}
}
{
i_error("checkpassword: close() failed: %m");
}
i_error("checkpassword: close() failed: %m");
}
}
{
}
}
}
{
const char *const *extra_fields;
}
{
}
static void
{
switch (request->exit_status) {
/* vpopmail exit codes: */
case 3: /* password fail / vpopmail user not found */
case 12: /* null user name given */
case 13: /* null password given */
case 15: /* user has no password */
case 21: /* system user not found */
case 22: /* system user shadow entry not found */
case 23: /* system password fail */
/* standard checkpassword exit codes: */
case 1:
/* (1 is additionally defined in vpopmail for
"Login failed (status=%d)",
break;
case 0:
"Received no input");
break;
}
break;
case 2:
/* checkpassword is called with wrong parameters? unlikely */
"Child %s exited with status 2 (tried to use "
"userdb-only checkpassword program for passdb?)",
break;
case 111:
/* temporary problem, treat as internal error */
default:
/* whatever error.. */
"Child %s exited with status %d",
break;
}
}
static void
{
switch (request->exit_status) {
case 3:
/* User does not exist. */
"User unknown");
break;
case 2:
/* This is intentionally not 0. checkpassword-reply exits with
2 on success when AUTHORIZED is set. */
"Received no input");
break;
}
break;
default:
/* whatever error... */
"Child %s exited with status %d",
break;
}
}
static void
{
/* the process must have exited, and the input fd must have closed */
return;
else
}
{
const struct var_expand_table *tab;
unsigned int i;
/* avoid keeping passwords in environment .. just in case
an attacker might find it from there. environment is no
longer world-readable in modern OSes, but maybe the attacker
could be running with the same UID. of course then the
attacker could usually ptrace() the process, except that is
disabled on some secured systems. so, although I find it
highly unlikely anyone could actually attack Dovecot this
way in a real system, be safe just in case. besides, lets
try to keep at least minimally compatible with the
checkpassword API. */
}
}
}
{
/* Besides passing the standard username and password in a
pipe, also pass some other possibly interesting information
/* FIXME: for backwards compatibility only,
remove some day */
}
/* FIXME: for backwards compatibility only,
remove some day */
}
if (request->local_port != 0) {
request->local_port));
}
if (request->remote_port != 0) {
request->remote_port));
}
}
/* extra fields could come from master db */
}
}
static const char *
const char *checkpassword_reply_path)
{
}
{
unsigned char buf[1024];
if (ret > 0) {
return;
}
if (ret < 0) {
"checkpassword", "read() failed: %m");
"LF characters in checkpassword reply");
} else {
}
}
{
/* Send: username \0 password \0 timestamp \0.
Must be 512 bytes or less. The "timestamp" parameter is actually
useful only for APOP authentication. We don't support it, so
keep it empty */
const unsigned char *data;
} else {
}
/* already checked this */
if (ret <= 0) {
if (ret < 0) {
"checkpassword", "write() failed: %m");
} else {
"checkpassword", "write() returned 0");
}
return;
}
return;
/* finished sending the data */
i_error("checkpassword: close() failed: %m");
}
static void ATTR_NORETURN
{
/* fd 3 is used to send the username+password for the script
fd 4 is used to communicate with checkpassword-reply */
"dup2() failed: %m");
exit(111);
}
if (!authenticate) {
authorization, so we need to signalize the
checkpassword program that the password shall be
ignored by setting AUTHORIZED. This needs a
special checkpassword program which knows how to
handle this. */
env_put("AUTHORIZED=1");
/* passdb credentials lookup */
env_put("CREDENTIALS_LOOKUP=1");
}
}
/* very simple argument splitting. */
}
struct db_checkpassword *db)
{
struct chkpw_auth_request *request =
"Child %s died with signal %d",
"checkpassword", "exit_status=%d",
} else {
/* shouldn't happen */
"Child %s exited with status=%d",
}
}
struct auth_request *request,
const char *auth_password,
void (*request_callback)())
{
struct chkpw_auth_request *chkpw_auth_request;
unsigned int output_len;
/* <username> \0 <password> \0 timestamp \0 */
if (auth_password != NULL)
if (output_len > CHECKPASSWORD_MAX_REQUEST_LEN) {
"Username+password combination too long (%u bytes)",
return;
}
fd_in[0] = -1;
"pipe() failed: %m");
if (fd_in[0] != -1) {
i_close_fd(&fd_in[0]);
}
return;
}
if (pid == -1) {
"fork() failed: %m");
i_close_fd(&fd_in[0]);
i_close_fd(&fd_out[0]);
return;
}
if (pid == 0) {
/* child */
i_close_fd(&fd_in[0]);
auth_password != NULL);
/* not reached */
}
"close(fd_in[1]) failed: %m");
}
"close(fd_out[0]) failed: %m");
}
}
struct db_checkpassword *
db_checkpassword_init(const char *checkpassword_path,
const char *checkpassword_reply_path)
{
struct db_checkpassword *db;
db->child_wait =
return db;
}
{
struct hash_iterate_context *iter;
void *key;
struct chkpw_auth_request *request;
}
#endif