/*
Authors:
Sumit Bose <sbose@redhat.com>
Copyright (C) 2009 Red Hat
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "config.h"
#include "util/util.h"
#include "util/sss_sockets.h"
#include "util/sss_ldap.h"
#include "providers/ldap/sdap.h"
const char* sss_ldap_err2string(int err)
{
if (IS_SSSD_ERROR(err)) {
return sss_strerror(err);
} else {
return ldap_err2string(err);
}
}
int sss_ldap_get_diagnostic_msg(TALLOC_CTX *mem_ctx, LDAP *ld, char **_errmsg)
{
char *errmsg = NULL;
int optret;
optret = ldap_get_option(ld, SDAP_DIAGNOSTIC_MESSAGE, (void*)&errmsg);
if (optret != LDAP_SUCCESS) {
return EINVAL;
}
*_errmsg = talloc_strdup(mem_ctx, errmsg ? errmsg : "unknown error");
ldap_memfree(errmsg);
if (*_errmsg == NULL) {
return ENOMEM;
}
return EOK;
}
int sss_ldap_control_create(const char *oid, int iscritical,
struct berval *value, int dupval,
LDAPControl **ctrlp)
{
#ifdef HAVE_LDAP_CONTROL_CREATE
return ldap_control_create(oid, iscritical, value, dupval, ctrlp);
#else
LDAPControl *lc = NULL;
if (oid == NULL || ctrlp == NULL) {
return LDAP_PARAM_ERROR;
}
lc = calloc(sizeof(LDAPControl), 1);
if (lc == NULL) {
return LDAP_NO_MEMORY;
}
lc->ldctl_oid = strdup(oid);
if (lc->ldctl_oid == NULL) {
free(lc);
return LDAP_NO_MEMORY;
}
if (value != NULL && value->bv_val != NULL) {
if (dupval == 0) {
lc->ldctl_value = *value;
} else {
ber_dupbv(&lc->ldctl_value, value);
if (lc->ldctl_value.bv_val == NULL) {
free(lc->ldctl_oid);
free(lc);
return LDAP_NO_MEMORY;
}
}
}
lc->ldctl_iscritical = iscritical;
*ctrlp = lc;
return LDAP_SUCCESS;
#endif
}
#ifdef HAVE_LDAP_INIT_FD
#define LDAP_PROTO_TCP 1 /* ldap:// */
#define LDAP_PROTO_UDP 2 /* reserved */
#define LDAP_PROTO_IPC 3 /* ldapi:// */
#define LDAP_PROTO_EXT 4 /* user-defined socket/sockbuf */
extern int ldap_init_fd(ber_socket_t fd, int proto, const char *url, LDAP **ld);
static void sss_ldap_init_sys_connect_done(struct tevent_req *subreq);
#endif
struct sss_ldap_init_state {
LDAP *ldap;
int sd;
const char *uri;
};
static int sss_ldap_init_state_destructor(void *data)
{
struct sss_ldap_init_state *state = (struct sss_ldap_init_state *)data;
if (state->ldap) {
DEBUG(SSSDBG_TRACE_FUNC,
"calling ldap_unbind_ext for ldap:[%p] sd:[%d]\n",
state->ldap, state->sd);
ldap_unbind_ext(state->ldap, NULL, NULL);
}
if (state->sd != -1) {
DEBUG(SSSDBG_TRACE_FUNC, "closing socket [%d]\n", state->sd);
close(state->sd);
state->sd = -1;
}
return 0;
}
struct tevent_req *sss_ldap_init_send(TALLOC_CTX *mem_ctx,
struct tevent_context *ev,
const char *uri,
struct sockaddr_storage *addr,
int addr_len, int timeout)
{
int ret = EOK;
struct tevent_req *req;
struct sss_ldap_init_state *state;
req = tevent_req_create(mem_ctx, &state, struct sss_ldap_init_state);
if (req == NULL) {
DEBUG(SSSDBG_CRIT_FAILURE, "tevent_req_create failed.\n");
return NULL;
}
talloc_set_destructor((TALLOC_CTX *)state, sss_ldap_init_state_destructor);
state->ldap = NULL;
state->sd = -1;
state->uri = uri;
#ifdef HAVE_LDAP_INIT_FD
struct tevent_req *subreq;
subreq = sssd_async_socket_init_send(state, ev, addr, addr_len, timeout);
if (subreq == NULL) {
ret = ENOMEM;
DEBUG(SSSDBG_CRIT_FAILURE, "sssd_async_socket_init_send failed.\n");
goto fail;
}
tevent_req_set_callback(subreq, sss_ldap_init_sys_connect_done, req);
return req;
fail:
tevent_req_error(req, ret);
#else
DEBUG(SSSDBG_MINOR_FAILURE, "ldap_init_fd not available, "
"will use ldap_initialize with uri [%s].\n", uri);
ret = ldap_initialize(&state->ldap, uri);
if (ret == LDAP_SUCCESS) {
tevent_req_done(req);
} else {
DEBUG(SSSDBG_CRIT_FAILURE,
"ldap_initialize failed [%s].\n", sss_ldap_err2string(ret));
if (ret == LDAP_SERVER_DOWN) {
tevent_req_error(req, ETIMEDOUT);
} else {
tevent_req_error(req, EIO);
}
}
#endif
tevent_req_post(req, ev);
return req;
}
#ifdef HAVE_LDAP_INIT_FD
static void sss_ldap_init_sys_connect_done(struct tevent_req *subreq)
{
struct tevent_req *req = tevent_req_callback_data(subreq,
struct tevent_req);
struct sss_ldap_init_state *state = tevent_req_data(req,
struct sss_ldap_init_state);
char *tlserr;
int ret;
int lret;
int optret;
ret = sssd_async_socket_init_recv(subreq, &state->sd);
talloc_zfree(subreq);
if (ret != EOK) {
DEBUG(SSSDBG_CRIT_FAILURE,
"sssd_async_socket_init request failed: [%d]: %s.\n",
ret, sss_strerror(ret));
goto fail;
}
/* Initialize LDAP handler */
lret = ldap_init_fd(state->sd, LDAP_PROTO_TCP, state->uri, &state->ldap);
if (lret != LDAP_SUCCESS) {
DEBUG(SSSDBG_CRIT_FAILURE,
"ldap_init_fd failed: %s. [%d][%s]\n",
sss_ldap_err2string(lret), state->sd, state->uri);
ret = lret == LDAP_SERVER_DOWN ? ETIMEDOUT : EIO;
goto fail;
}
if (ldap_is_ldaps_url(state->uri)) {
lret = ldap_install_tls(state->ldap);
if (lret != LDAP_SUCCESS) {
if (lret == LDAP_LOCAL_ERROR) {
DEBUG(SSSDBG_FUNC_DATA, "TLS/SSL already in place.\n");
} else {
optret = sss_ldap_get_diagnostic_msg(state, state->ldap,
&tlserr);
if (optret == LDAP_SUCCESS) {
DEBUG(SSSDBG_CRIT_FAILURE,
"ldap_install_tls failed: [%s] [%s]\n",
sss_ldap_err2string(lret), tlserr);
sss_log(SSS_LOG_ERR,
"Could not start TLS encryption. %s", tlserr);
} else {
DEBUG(SSSDBG_CRIT_FAILURE,
"ldap_install_tls failed: [%s]\n",
sss_ldap_err2string(lret));
sss_log(SSS_LOG_ERR, "Could not start TLS encryption. "
"Check for certificate issues.");
}
ret = EIO;
goto fail;
}
}
}
tevent_req_done(req);
return;
fail:
tevent_req_error(req, ret);
}
#endif
int sss_ldap_init_recv(struct tevent_req *req, LDAP **ldap, int *sd)
{
struct sss_ldap_init_state *state = tevent_req_data(req,
struct sss_ldap_init_state);
TEVENT_REQ_RETURN_ON_ERROR(req);
/* Everything went well therefore we do not want to release resources */
talloc_set_destructor(state, NULL);
*ldap = state->ldap;
*sd = state->sd;
return EOK;
}
/*
* _filter will contain combined filters from all possible search bases
* or NULL if it should be empty
*/
bool sss_ldap_dn_in_search_bases_len(TALLOC_CTX *mem_ctx,
const char *dn,
struct sdap_search_base **search_bases,
char **_filter,
int *_match_len)
{
struct sdap_search_base *base;
int basedn_len, dn_len;
int len_diff;
int i, j;
bool base_confirmed = false;
bool comma_found = false;
bool backslash_found = false;
char *filter = NULL;
bool ret = false;
int match_len;
if (dn == NULL) {
DEBUG(SSSDBG_FUNC_DATA, "dn is NULL\n");
ret = false;
goto done;
}
if (search_bases == NULL) {
DEBUG(SSSDBG_FUNC_DATA, "search_bases is NULL\n");
ret = false;
goto done;
}
dn_len = strlen(dn);
for (i = 0; search_bases[i] != NULL; i++) {
base = search_bases[i];
basedn_len = strlen(base->basedn);
if (basedn_len > dn_len) {
continue;
}
len_diff = dn_len - basedn_len;
base_confirmed = (strncasecmp(&dn[len_diff], base->basedn, basedn_len) == 0);
if (!base_confirmed) {
continue;
}
match_len = basedn_len;
switch (base->scope) {
case LDAP_SCOPE_BASE:
/* dn > base? */
if (len_diff != 0) {
continue;
}
break;
case LDAP_SCOPE_ONELEVEL:
if (len_diff == 0) {
/* Base object doesn't belong to scope=one
* search */
continue;
}
comma_found = false;
for (j = 0; j < len_diff - 1; j++) { /* ignore comma before base */
if (dn[j] == '\\') {
backslash_found = true;
} else if (dn[j] == ',' && !backslash_found) {
comma_found = true;
break;
} else {
backslash_found = false;
}
}
/* it has at least one more level */
if (comma_found) {
continue;
}
break;
case LDAP_SCOPE_SUBTREE:
/* dn length >= base dn length && base_confirmed == true */
break;
default:
DEBUG(SSSDBG_FUNC_DATA, "Unsupported scope: %d\n", base->scope);
continue;
}
/*
* If we get here, the dn is valid.
* If no filter is set, than return true immediately.
* Append filter otherwise.
*/
ret = true;
if (_match_len) {
*_match_len = match_len;
}
if (base->filter == NULL || _filter == NULL) {
goto done;
} else {
filter = talloc_strdup_append(filter, base->filter);
if (filter == NULL) {
DEBUG(SSSDBG_CRIT_FAILURE, "talloc_strdup_append() failed\n");
ret = false;
goto done;
}
}
}
if (_filter != NULL) {
if (filter != NULL) {
*_filter = talloc_asprintf(mem_ctx, "(|%s)", filter);
if (*_filter == NULL) {
DEBUG(SSSDBG_CRIT_FAILURE,
"talloc_asprintf_append() failed\n");
ret = false;
goto done;
}
} else {
*_filter = NULL;
}
}
done:
talloc_free(filter);
return ret;
}
bool sss_ldap_dn_in_search_bases(TALLOC_CTX *mem_ctx,
const char *dn,
struct sdap_search_base **search_bases,
char **_filter)
{
return sss_ldap_dn_in_search_bases_len(mem_ctx, dn, search_bases, _filter,
NULL);
}
char *sss_ldap_encode_ndr_uint32(TALLOC_CTX *mem_ctx, uint32_t flags)
{
char hex[9]; /* 4 bytes in hex + terminating zero */
errno_t ret;
ret = snprintf(hex, 9, "%08x", flags);
if (ret != 8) {
return NULL;
}
return talloc_asprintf(mem_ctx, "\\%c%c\\%c%c\\%c%c\\%c%c",
hex[6], hex[7], hex[4], hex[5],
hex[2], hex[3], hex[0], hex[1]);
}