db-ldap.c revision 31088625f59b7359d70845d81ea9e3dd8a24eb63
/* Copyright (c) 2003-2011 Dovecot authors, see the included COPYING file */
#include "auth-common.h"
#if defined(BUILTIN_LDAP) || defined(PLUGIN_BUILD)
#include "network.h"
#include "ioloop.h"
#include "array.h"
#include "hash.h"
#include "aqueue.h"
#include "str.h"
#include "env-util.h"
#include "var-expand.h"
#include "settings.h"
#include "userdb.h"
#include "db-ldap.h"
#include <stddef.h>
#include <stdlib.h>
#include <unistd.h>
#define HAVE_LDAP_SASL
#ifdef HAVE_SASL_SASL_H
#elif defined (HAVE_SASL_H)
# include <sasl.h>
#else
#endif
#ifdef LDAP_OPT_X_TLS
# define OPENLDAP_TLS_OPTIONS
#endif
#if SASL_VERSION_MAJOR < 2
#endif
#ifndef LDAP_SASL_QUIET
# define LDAP_SASL_QUIET 0 /* Doesn't exist in Solaris LDAP */
#endif
/* Older versions may require calling ldap_result() twice */
#if LDAP_VENDOR_VERSION <= 20112
# define OPENLDAP_ASYNC_WORKAROUND
#endif
/* Solaris LDAP library doesn't have LDAP_OPT_SUCCESS */
#ifndef LDAP_OPT_SUCCESS
# define LDAP_OPT_SUCCESS LDAP_SUCCESS
#endif
struct db_ldap_result_iterate_context {
struct ldap_connection *conn;
struct auth_request *auth_request;
struct hash_table *attr_map;
struct var_expand_table *var_table;
const char *const *static_attrs;
};
struct db_ldap_sasl_bind_context {
const char *authcid;
const char *passwd;
const char *realm;
const char *authzid;
};
static struct setting_def setting_defs[] = {
{ 0, NULL, 0 }
};
static struct ldap_settings default_ldap_settings = {
.auth_bind_userdn = NULL,
.sasl_realm = NULL,
.sasl_authz_id = NULL,
.tls_ca_cert_file = NULL,
.tls_ca_cert_dir = NULL,
.tls_cert_file = NULL,
.tls_key_file = NULL,
.tls_cipher_suite = NULL,
.tls_require_cert = NULL,
.deref = "never",
.scope = "subtree",
.ldap_version = 3,
.debug_level = "0",
.ldaprc_path = "",
.user_attrs = "homeDirectory=home,uidNumber=uid,gidNumber=gid",
.user_filter = "(&(objectClass=posixAccount)(uid=%u))",
.pass_attrs = "uid=user,userPassword=password",
.pass_filter = "(&(objectClass=posixAccount)(uid=%u))",
.iterate_attrs = "uid=user",
.iterate_filter = "(objectClass=posixAccount)",
.default_pass_scheme = "crypt"
};
{
return LDAP_DEREF_NEVER;
return LDAP_DEREF_SEARCHING;
return LDAP_DEREF_FINDING;
return LDAP_DEREF_ALWAYS;
}
{
return LDAP_SCOPE_BASE;
return LDAP_SCOPE_ONELEVEL;
return LDAP_SCOPE_SUBTREE;
}
#ifdef OPENLDAP_TLS_OPTIONS
static int tls_require_cert2str(const char *str)
{
return LDAP_OPT_X_TLS_NEVER;
return LDAP_OPT_X_TLS_HARD;
return LDAP_OPT_X_TLS_DEMAND;
return LDAP_OPT_X_TLS_ALLOW;
return LDAP_OPT_X_TLS_TRY;
}
#endif
{
if (ret != LDAP_SUCCESS) {
i_error("LDAP: Can't get error number: %s",
return LDAP_UNAVAILABLE;
}
return err;
}
{
const char *ret;
}
return ret;
}
{
if (db_ldap_connect(conn) < 0)
}
{
switch (err) {
case LDAP_SUCCESS:
i_unreached();
case LDAP_SIZELIMIT_EXCEEDED:
case LDAP_TIMELIMIT_EXCEEDED:
case LDAP_NO_SUCH_ATTRIBUTE:
case LDAP_UNDEFINED_TYPE:
case LDAP_INVALID_SYNTAX:
case LDAP_NO_SUCH_OBJECT:
case LDAP_ALIAS_PROBLEM:
case LDAP_INVALID_DN_SYNTAX:
case LDAP_IS_LEAF:
case LDAP_ALIAS_DEREF_PROBLEM:
case LDAP_FILTER_ERROR:
/* invalid input */
return -1;
case LDAP_SERVER_DOWN:
case LDAP_TIMEOUT:
case LDAP_UNAVAILABLE:
case LDAP_BUSY:
#ifdef LDAP_CONNECT_ERROR
case LDAP_CONNECT_ERROR:
#endif
case LDAP_LOCAL_ERROR:
case LDAP_INVALID_CREDENTIALS:
default:
/* connection problems */
return 0;
}
}
struct ldap_request *request)
{
struct ldap_request_bind *brequest =
(struct ldap_request_bind *)request;
"ldap_bind(%s) failed: %s",
if (ldap_handle_error(conn) < 0) {
/* broken request, remove it */
return 0;
}
return -1;
}
return 1;
}
struct ldap_request *request)
{
struct ldap_request_search *srequest =
(struct ldap_request_search *)request;
"ldap_search(%s) parsing failed: %s",
if (ldap_handle_error(conn) < 0) {
/* broken request, remove it */
return 0;
}
return -1;
}
return 1;
}
{
int ret = -1;
/* no non-pending requests */
return FALSE;
}
/* wait until server has replied to some requests */
return FALSE;
}
if (db_ldap_connect(conn) < 0)
return FALSE;
conn->pending_count));
if (conn->pending_count > 0 &&
/* we can't do binds until all existing requests are finished */
return FALSE;
}
switch (conn->conn_state) {
case LDAP_CONN_STATE_BINDING:
/* wait until we're in bound state */
return FALSE;
break;
/* bind to default dn first */
(void)db_ldap_bind(conn);
return FALSE;
/* we can do anything in this state */
break;
}
case LDAP_REQUEST_TYPE_BIND:
break;
case LDAP_REQUEST_TYPE_SEARCH:
break;
}
if (ret > 0) {
/* success */
conn->pending_count++;
return TRUE;
} else if (ret < 0) {
/* disconnected */
return FALSE;
} else {
/* broken request, remove from queue */
return TRUE;
}
}
static bool
{
struct ldap_request *const *first_requestp;
unsigned int count;
if (count == 0)
return TRUE;
"Connection appears to be hanging, reconnecting");
return TRUE;
}
return TRUE;
}
struct ldap_request *request)
{
return;
}
(void)db_ldap_request_queue_next(conn);
}
{
if (ret == LDAP_SERVER_DOWN) {
i_error("LDAP: Can't connect to server: %s",
return -1;
}
if (ret != LDAP_SUCCESS) {
i_error("LDAP: binding failed (dn %s): %s",
return -1;
}
while (db_ldap_request_queue_next(conn))
;
return 0;
}
{
int ret;
/* lost connection, close it */
}
}
unsigned int max_count,
unsigned int timeout_secs,
{
break;
/* timed out, abort */
conn->pending_count--;
}
if (error) {
"%s", reason);
} else {
"%s", reason);
}
max_count--;
}
}
static struct ldap_request *
unsigned int *idx_r)
{
unsigned int i, count;
if (count == 0)
return NULL;
for (i = 0; i < count; i++) {
*idx_r = i;
return request;
}
break;
}
return NULL;
}
static void
{
struct ldap_request *request;
unsigned int idx;
return;
}
return;
}
} else {
switch (ldap_msgtype(res)) {
case LDAP_RES_SEARCH_ENTRY:
case LDAP_RES_SEARCH_RESULT:
break;
/* we're going to ignore this */
return;
default:
i_error("LDAP: Reply with unexpected type %d",
ldap_msgtype(res));
return;
}
}
ret = LDAP_SUCCESS;
else {
conn->pending_count--;
}
/* handle search failures here */
struct ldap_request_search *srequest =
(struct ldap_request_search *)request;
"ldap_search(%s) failed: %s",
}
T_BEGIN {
} T_END;
if (idx > 0) {
/* see if there are timed out requests */
TRUE, "Request lost");
}
}
{
int ret;
do {
return;
#ifdef OPENLDAP_ASYNC_WORKAROUND
if (ret == 0) {
/* try again, there may be another in buffer */
}
#endif
if (ret <= 0)
break;
if (ret > 0) {
/* input disabled, continue once it's enabled */
} else if (ret == 0) {
/* send more requests */
while (db_ldap_request_queue_next(conn))
;
i_error("LDAP: Connection lost to LDAP server, reconnecting");
} else {
/* server probably disconnected an idle connection. don't
reconnect until the next request comes. */
}
}
#ifdef HAVE_LDAP_SASL
static int
{
const char *str;
case SASL_CB_GETREALM:
break;
case SASL_CB_AUTHNAME:
break;
case SASL_CB_USER:
break;
case SASL_CB_PASS:
break;
default:
break;
}
}
}
return LDAP_SUCCESS;
}
#endif
{
i_error("LDAP: Initial binding to LDAP server timed out");
}
{
int msgid;
if (msgid == -1) {
/* lost connection, close it */
}
return -1;
}
return 0;
}
{
int ret;
/* get the connection's fd */
if (ret != LDAP_SUCCESS) {
i_fatal("LDAP: Can't get connection fd: %s",
}
/* Solaris LDAP library seems to be broken */
i_fatal("LDAP: Buggy LDAP library returned wrong fd: %d",
}
}
static void
{
int ret;
if (ret != LDAP_SUCCESS) {
i_fatal("LDAP: Can't set option %s to %s: %s",
}
}
static void
const char *optname)
{
}
{
return;
#ifdef OPENLDAP_TLS_OPTIONS
}
#else
i_warning("LDAP: tls_* settings ignored, "
"your LDAP library doesn't seem to support them");
#endif
}
{
unsigned int ldap_version;
int value;
#ifdef LDAP_OPT_DEBUG_LEVEL
if (value != 0) {
}
#endif
i_fatal("LDAP: sasl_bind=yes requires ldap_version=3");
i_fatal("LDAP: tls=yes requires ldap_version=3");
}
}
{
int ret;
return 0;
#ifdef LDAP_HAVE_INITIALIZE
#else
i_fatal("LDAP: Your LDAP library doesn't support "
"'uris' setting, use 'hosts' instead.");
#endif
} else
i_fatal("LDAP: ldap_init() failed with hosts: %s",
}
#ifdef LDAP_HAVE_START_TLS_S
if (ret != LDAP_SUCCESS) {
if (ret == LDAP_OPERATIONS_ERROR &&
i_fatal("LDAP: Don't use both tls=yes "
"and ldaps URI");
}
i_error("LDAP: ldap_start_tls_s() failed: %s",
return -1;
}
#else
i_error("LDAP: Your LDAP library doesn't support TLS");
return -1;
#endif
}
#ifdef HAVE_LDAP_SASL
struct db_ldap_sasl_bind_context context;
/* There doesn't seem to be a way to do SASL binding
asynchronously.. */
sasl_interact, &context);
return -1;
#else
i_fatal("LDAP: sasl_bind=yes but no SASL support compiled in");
#endif
} else {
if (db_ldap_bind(conn) < 0)
return -1;
}
return 0;
}
{
if (!enable) {
} else {
}
}
}
{
"Aborting (timeout), we're not connected to LDAP server");
/* no requests left, remove this timeout handler */
}
}
{
unsigned int i;
if (conn->pending_count != 0) {
for (i = 0; i < conn->pending_count; i++) {
}
conn->pending_count = 0;
}
}
/* the fd may have already been closed before ldap_unbind(),
so we'll have to use io_remove_closed(). */
}
}
}
const char *skip_attr)
{
unsigned int i, j, size;
if (*attrlist == '\0')
return;
/* @UNSAFE */
for (i = j = 0; i < size; i++) {
/* allow spaces here so "foo=1, bar=2" works */
if (p == NULL)
else if (p != attr_data) {
} else {
/* =<static key>=<static value> */
if (str_len(static_data) > 0)
continue;
}
if (*name != '\0' &&
i_fatal("ldap: LDAP attribute '%s' used multiple times. This is currently unsupported.",
name);
}
(*attr_names_r)[j++] = name;
}
}
if (str_len(static_data) > 0) {
}
}
struct var_expand_table *
{
struct var_expand_table *table;
unsigned int count;
count++;
return table;
}
#define IS_LDAP_ESCAPED_CHAR(c) \
((c) == '*' || (c) == '(' || (c) == ')' || (c) == '\\')
const char *ldap_escape(const char *str,
{
const char *p;
for (p = str; *p != '\0'; p++) {
if (IS_LDAP_ESCAPED_CHAR(*p))
break;
}
if (*p == '\0')
return str;
for (; *p != '\0'; p++) {
if (IS_LDAP_ESCAPED_CHAR(*p))
str_append_c(ret, *p);
}
}
struct db_ldap_result_iterate_context *
struct auth_request *auth_request,
struct hash_table *attr_map)
{
struct db_ldap_result_iterate_context *ctx;
const char *static_data;
if (static_data != NULL) {
const struct var_expand_table *table;
}
return ctx;
}
static void
{
} else {
"no fields returned by the server");
}
}
}
static void
{
}
return;
/* we want to use variables */
ctx->auth_request);
}
}
}
static void
{
unsigned int i;
"Multiple values found for '%s', "
}
}
/* no debugging */
} else {
}
}
{
const char *p;
return TRUE;
}
}
}
if (p == NULL) {
} else {
}
/* make _next() return correct values */
ctx->static_attrs++;
return TRUE;
}
return FALSE;
}
const char **name_r,
const char *const **values_r)
{
if (!db_ldap_result_int_next(ctx))
return FALSE;
/* we can use only one value with templates */
} else {
}
return TRUE;
}
struct ldap_connection *conn)
{
}
{
struct ldap_connection *conn;
return conn;
}
return NULL;
}
{
struct ldap_connection *conn;
const char *str;
/* see if it already exists */
return conn;
}
if (*config_path == '\0')
i_fatal("LDAP: Configuration file path not given");
i_fatal("LDAP: No base given");
i_fatal("LDAP: No uris or hosts set");
#ifndef LDAP_HAVE_INITIALIZE
i_fatal("LDAP: Dovecot compiled without support for LDAP uris "
"(ldap_initialize not found)");
}
#endif
i_fatal("LDAP: Multiple different ldaprc_path "
"settings not allowed (%s and %s)",
}
}
return conn;
}
{
struct ldap_connection **p;
return;
if (*p == conn) {
break;
}
}
}
#ifndef BUILTIN_LDAP
/* Building a plugin */
extern struct passdb_module_interface passdb_ldap_plugin;
extern struct userdb_module_interface userdb_ldap_plugin;
void authdb_ldap_init(void);
void authdb_ldap_deinit(void);
void authdb_ldap_init(void)
{
}
void authdb_ldap_deinit(void)
{
}
#endif
#endif