/* Copyright (c) 2004-2018 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 "db-checkpassword.h"
#include <unistd.h>
struct chkpw_auth_request {
char *auth_password;
void (*request_callback)();
int exit_status;
};
struct db_checkpassword {
};
static void
{
}
}
{
}
{
}
}
}
{
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
}
{
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)
{
const char *error;
i_error("Failed to expand checkpassword_path=%s: %s",
}
}
{
if (ret > 0) {
return;
}
if (ret < 0) {
"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) {
"write() failed: %m");
} else {
"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)
{
"Child %s died with signal %d",
} else {
/* shouldn't happen */
"Child %s exited with status=%d",
}
}
struct auth_request *request,
const char *auth_password,
void (*request_callback)())
{
/* <username> \0 <password> \0 timestamp \0 */
if (auth_password != NULL)
if (output_len > CHECKPASSWORD_MAX_REQUEST_LEN) {
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 *
const char *checkpassword_reply_path)
{
db->child_wait =
return db;
}
{
void *key;
}
#endif