/* Copyright (c) 2003-2018 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 "time-util.h"
#include "env-util.h"
#include "var-expand.h"
#include "settings.h"
#include "userdb.h"
#include "db-ldap.h"
#include <stddef.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
#endif
#ifndef LDAP_SASL_QUIET
#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
#endif
struct db_ldap_result {
int refcount;
};
struct db_ldap_value {
const char **values;
bool used;
};
struct db_ldap_result_iterate_context {
unsigned int attr_idx;
/* attribute name => value */
bool skip_null_values;
bool iter_dn_values;
};
struct db_ldap_sasl_bind_context {
const char *authcid;
const char *passwd;
const char *realm;
const char *authzid;
};
{ 0, NULL, 0 }
};
.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",
};
struct db_ldap_result_iterate_context *
struct ldap_request_search *ldap_request,
bool iter_dn_values);
{
else
return -1;
return 0;
}
{
else
return -1;
return 0;
}
#ifdef OPENLDAP_TLS_OPTIONS
{
else
return -1;
return 0;
}
#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:
case LDAP_OPERATIONS_ERROR:
default:
/* connection problems */
return 0;
}
}
struct ldap_request *request)
{
(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 *)request;
"ldap_search(%s) parsing failed: %s",
if (ldap_handle_error(conn) < 0) {
/* broken request, remove it */
return 0;
}
return -1;
}
return 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 void
{
unsigned int count;
if (count == 0)
return;
"Connection appears to be hanging, reconnecting");
}
}
struct ldap_request *request)
{
(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;
}
struct db_ldap_result *res)
{
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;
}
struct ldap_request_search *request,
struct db_ldap_result *res)
{
"Multiple values found for '%s', "
}
continue;
/* In future we could also support LDAP URLs here */
values[0]);
}
}
return 0;
}
struct ldap_field_find_subquery_context {
const char *name;
};
static int
const char **value_r,
const char **error_r ATTR_UNUSED)
{
char *ldap_attr;
const char *p;
if (*data != '\0') {
data, p);
}
}
return 1;
}
static int
struct ldap_request_search *request,
struct ldap_request_named_result *named_res)
{
const char *p, *error;
char *name;
}
/* get the attributes names into array (ldapAttr@name -> ldapAttr) */
str_truncate(tmp_str, 0);
"Failed to expand subquery %s: %s",
return -1;
}
} else {
if (p != NULL &&
field->ldap_attr_name, p);
}
}
}
"ldap_search(dn=%s) failed: %s",
return -1;
}
return 0;
}
struct db_ldap_result *res)
{
return -1;
} else {
return -1;
}
return 0;
}
struct ldap_request_search *request,
struct db_ldap_result *res)
{
/* see if we need to do more LDAP queries */
if (!field->value_is_dn)
continue;
}
return -1;
} else {
}
/* send the next LDAP query */
named_res) < 0)
return -1;
return 1;
}
/* dn field wasn't returned, skip this */
}
return 0;
}
static bool
struct db_ldap_result *res)
{
int ret;
bool final_result;
} else {
case LDAP_RES_SEARCH_ENTRY:
case LDAP_RES_SEARCH_RESULT:
break;
/* we're going to ignore this */
return FALSE;
default:
i_error("LDAP: Reply with unexpected type %d",
return TRUE;
}
}
ret = LDAP_SUCCESS;
} else {
final_result = TRUE;
}
/* LDAP_NO_SUCH_OBJECT is returned for nonexistent base */
/* handle search failures here */
(struct ldap_request_search *)request;
"ldap_search(base=%s filter=%s) failed: %s",
} else {
"ldap_search(base=%s) failed: %s",
}
}
/* expand any @results */
if (!final_result) {
"LDAP search returned multiple entries");
} else {
/* wait for finish */
return FALSE;
}
} else {
if (ret > 0) {
/* more LDAP queries left */
return FALSE;
}
if (ret < 0)
}
}
/* wait for the final reply */
return TRUE;
}
if (final_result) {
conn->pending_count--;
}
T_BEGIN {
} T_END;
if (idx > 0) {
/* see if there are timed out requests */
TRUE, "Request lost");
}
return TRUE;
}
{
}
}
static void
{
(struct ldap_request_search *)request;
}
}
}
}
static void
{
unsigned int idx;
int msgid;
return;
}
return;
}
/* request is allocated from auth_request's pool */
}
{
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 %s: Initial binding to LDAP server timed out",
conn->config_path);
}
#ifdef HAVE_LDAP_SASL
{
int ret;
/* There doesn't seem to be a way to do SASL binding
asynchronously.. */
sasl_interact, &context);
return -1;
return 0;
}
#else
{
i_unreached(); /* already checked at init */
return -1;
}
#endif
{
int msgid;
if (msgid == -1) {
/* lost connection, close it */
}
return -1;
}
return 0;
}
{
if (db_ldap_bind_sasl(conn) < 0)
return -1;
} else {
if (db_ldap_bind_simple(conn) < 0)
return -1;
}
return 0;
}
{
int ret;
/* get the connection's fd */
if (ret != LDAP_SUCCESS) {
i_fatal("LDAP %s: Can't get connection fd: %s",
}
/* Solaris LDAP library seems to be broken */
i_fatal("LDAP %s: Buggy LDAP library returned wrong fd: %d",
}
}
static void ATTR_NULL(1)
{
int ret;
if (ret != LDAP_SUCCESS) {
i_fatal("LDAP %s: Can't set option %s to %s: %s",
}
}
static void ATTR_NULL(1)
{
}
{
#ifdef OPENLDAP_TLS_OPTIONS
}
#else
i_fatal("LDAP %s: tls_* settings aren't supported by your LDAP library - they must not be set",
conn->config_path);
}
#endif
}
{
unsigned int ldap_version;
int value;
#ifdef LDAP_OPT_NETWORK_TIMEOUT
int ret;
if (ret != LDAP_SUCCESS) {
i_fatal("LDAP %s: Can't set network-timeout: %s",
}
#endif
#ifdef LDAP_OPT_DEBUG_LEVEL
}
#endif
}
{
int ret;
#ifdef LDAP_HAVE_INITIALIZE
if (ret != LDAP_SUCCESS) {
i_fatal("LDAP %s: ldap_initialize() failed with uris %s: %s",
}
#else
i_unreached(); /* already checked at init */
#endif
} else {
i_fatal("LDAP %s: ldap_init() failed with hosts: %s",
}
}
}
{
int debug_level;
bool debug;
int ret;
debug = debug_level > 0;
return 0;
if (debug) {
}
if (conn->delayed_connect) {
}
#ifdef LDAP_HAVE_START_TLS_S
if (ret != LDAP_SUCCESS) {
if (ret == LDAP_OPERATIONS_ERROR &&
i_fatal("LDAP %s: Don't use both tls=yes "
}
i_error("LDAP %s: ldap_start_tls_s() failed: %s",
return -1;
}
#else
i_unreached(); /* already checked at init */
#endif
}
if (db_ldap_bind(conn) < 0)
return -1;
if (debug) {
}
}
return 0;
}
{
(void)db_ldap_connect(conn);
}
{
if (conn->delayed_connect)
return;
}
{
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 int
const char **value_r,
const char **error_r ATTR_UNUSED)
{
char *ldap_attr;
if (*data != '\0') {
}
return 1;
}
const char *skip_attr)
{
{ "ldap", db_ldap_field_find },
{ "ldap_ptr", db_ldap_field_find },
};
unsigned int i;
if (*attrlist == '\0')
return;
/* allow spaces here so "foo=1, bar=2" works */
if (p == NULL)
else if (attr_data[0] == '@') {
ldap_attr = "";
} else {
}
if (*ldap_attr == '\0') {
/* =foo static value */
templ = "";
}
} else {
*templ++ = '\0';
str_truncate(tmp_str, 0);
/* This var_expand_with_funcs call fills the
* ldap_field_find_context in ctx, but the
* resulting string_t is not used, and the
* return value or error_r is not checked since
* it gives errors for non-ldap variable
* expansions. */
}
/* backwards compatibility:
attr=name=prefix means same as
attr=name=prefix%$ when %vars are missing */
"%$", NULL);
}
}
if (*name == '\0')
if (name[0] == '@') {
/* @name=ldapField */
name++;
/* !ldapAttr */
name = "";
ldap_attr++;
}
if (*ldap_attr != '\0' &&
/* root request's attribute */
}
}
}
}
static const struct var_expand_table *
const char *ldap_value)
{
&count);
return table;
}
#define IS_LDAP_ESCAPED_CHAR(c) \
{
for (const char *p = str; *p != '\0'; p++) {
if (IS_LDAP_ESCAPED_CHAR(*p)) {
}
str_append_c(ret, *p);
}
}
static bool
const char *attr)
{
return FALSE;
return TRUE;
}
}
return FALSE;
}
static void
const char *suffix)
{
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 ldap_request_search *ldap_request,
bool iter_dn_values)
{
const char *suffix;
}
}
}
return ctx;
}
struct db_ldap_result_iterate_context *
struct ldap_request_search *ldap_request,
{
}
{
const char *p;
if (p == NULL)
return "";
else {
/* default value given */
return p;
}
}
static int
{
if (ldap_value == NULL) {
/* requested ldap attribute wasn't returned at all */
return 1;
}
/* no value for ldap attribute */
return 1;
}
"Multiple values found for '%s', using value '%s'",
}
return 1;
}
static int
{
int ret;
i_unreached();
if (field_name[0] == '\0') {
*value_r = "";
return 1;
}
}
static int
{
return 1;
}
{ "ldap", db_ldap_field_expand },
{ "ldap_ptr", db_ldap_field_ptr_expand },
{ "ldap_dn", db_ldap_field_dn_expand },
};
static const char *const *
const struct ldap_field *field,
struct db_ldap_value *ldap_value)
{
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) */
"Failed to expand template %s: %s",
}
}
return values;
}
const char **name_r,
const char *const **values_r)
{
unsigned int pos;
const char *error;
do {
return FALSE;
if (ldap_value != NULL)
else {
/* expand %variables also for LDAP name fields. we'll use the
same ctx->var, which may already contain the value. */
}
}
/* no values. don't confuse the caller with this reply. */
}
return TRUE;
}
static void
{
char *name;
unsigned int unused_count = 0;
return;
if (orig_len == 0) {
"no fields returned by the server");
return;
}
unused_count++;
}
}
if (unused_count == 0)
else {
}
}
{
}
struct ldap_connection *conn)
{
}
{
return conn;
}
return NULL;
}
{
/* see if it already exists */
if (userdb)
return conn;
}
if (*config_path == '\0')
i_fatal("LDAP: Configuration file path not given");
#ifndef LDAP_HAVE_INITIALIZE
i_fatal("LDAP %s: uris set, but Dovecot compiled without support for LDAP uris "
"(ldap_initialize() not supported by LDAP library)", config_path);
}
#endif
#ifndef LDAP_HAVE_START_TLS_S
#endif
#ifndef HAVE_LDAP_SASL
#endif
}
#ifdef OPENLDAP_TLS_OPTIONS
i_fatal("LDAP %s: Unknown tls_require_cert value '%s'",
}
#endif
i_fatal("LDAP %s: 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