db-ldap.c revision 803197abb1cc0e81abb668c026c22394bfef820d
/* Copyright (c) 2003-2012 Dovecot authors, see the included COPYING file */
#include "auth-common.h"
#if defined(BUILTIN_LDAP) || defined(PLUGIN_BUILD)
#include "net.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_value {
const char **values;
bool used;
};
struct db_ldap_result_iterate_context {
struct auth_request *auth_request;
unsigned int attr_idx;
/* attribute name => value */
const char *val_1_arr[2];
};
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;
/* connecting may call db_ldap_connect_finish(), which gets us back
here. so do the connection before checking the request queue. */
if (db_ldap_connect(conn) < 0)
return FALSE;
/* no non-pending requests */
return FALSE;
}
/* wait until server has replied to some requests */
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(base=%s filter=%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 ATTR_NULL(1)
{
int ret;
if (ret != LDAP_SUCCESS) {
i_fatal("LDAP: Can't set option %s to %s: %s",
}
}
static void ATTR_NULL(1)
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(). */
}
}
}
struct ldap_field_find_context {
};
static const char *
{
char *ldap_attr;
if (*data != '\0') {
}
return NULL;
}
const char *skip_attr)
{
static struct var_expand_func_table var_funcs_table[] = {
{ "ldap", db_ldap_field_find },
};
struct ldap_field_find_context ctx;
struct ldap_field *field;
unsigned int i;
if (*attrlist == '\0')
return;
/* allow spaces here so "foo=1, bar=2" works */
if (p == NULL)
else {
}
if (*ldap_attr == '\0') {
/* =foo static value */
templ = "";
}
} else {
*templ++ = '\0';
str_truncate(tmp_str, 0);
var_funcs_table, &ctx);
/* backwards compatibility:
attr=name=prefix means same as
attr=name=prefix%$ when %vars are missing */
"%$", NULL);
}
}
if (*name == '\0')
if (*ldap_attr != '\0')
}
}
}
static const struct var_expand_table *
const char *ldap_value)
{
struct var_expand_table *table;
unsigned int count = 1;
&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);
}
}
static bool
const char *attr)
{
const struct ldap_field *field;
return FALSE;
return TRUE;
}
}
return FALSE;
}
static void
{
struct db_ldap_value *ldap_value;
unsigned int i, count;
count = 0;
} else {
}
for (i = 0; i < count; i++)
if (count == 0)
else {
for (i = 1; i < count; i++) {
ldap_value->values[0]);
}
}
}
}
}
struct db_ldap_result_iterate_context *
struct auth_request *auth_request,
{
struct db_ldap_result_iterate_context *ctx;
return ctx;
}
{
struct db_ldap_value *ldap_value;
if (ldap_value == NULL) {
/* ldap attribute wasn't requested */
return "";
}
/* no value for ldap attribute */
return "";
}
"Multiple values found for '%s', using value '%s'",
}
return ldap_value->values[0];
}
static const char *const *
const struct ldap_field *field,
struct db_ldap_value *ldap_value)
{
static struct var_expand_func_table var_funcs_table[] = {
{ "ldap", db_ldap_field_expand },
};
const struct var_expand_table *var_table;
const char *const *values;
if (ldap_value != NULL)
else {
/* LDAP attribute doesn't exist */
}
/* use the LDAP attribute's value */
} else {
/* template */
/* ldapAttr=key=template%$, but ldapAttr doesn't
exist. */
return values;
}
"Multiple values found for '%s', "
"using value '%s'",
}
/* do this lookup separately for each expansion, because:
1) the values are allocated from data stack
2) if "user" field is updated, we want %u/%n/%d updated
(and less importantly the same for other variables) */
values[0]);
else
}
return values;
}
const char **name_r,
const char *const **values_r)
{
const struct ldap_field *field;
struct db_ldap_value *ldap_value;
return FALSE;
if (ldap_value != NULL)
/* no values. don't confuse the caller with this reply. */
}
return TRUE;
}
static void
{
struct hash_iterate_context *iter;
char *name;
struct db_ldap_value *value;
unsigned int orig_len, unused_count = 0;
if (orig_len == 0) {
"no fields returned by the server");
return;
}
unused_count++;
}
}
if (unused_count == 0)
else {
}
}
{
}
struct ldap_connection *conn)
{
}
{
struct ldap_connection *conn;
return conn;
}
return NULL;
}
{
struct ldap_connection *conn;
/* see if it already exists */
if (userdb)
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