freeipa/daemons/ipa-kdb/ipa_kdb_common.c
Alexander Bokovoy 122f968cee ipa-kdb: reduce LDAP operations timeout to 30 seconds
Since LDAP operations used by ipa-kdb driver are synchronous, the
timeout specified here is blocking entire KDC. It is worth reducing the
timeout and since AS REQ processing timeout in KDC is 1 minute, reducing
the timeout for LDAP operations down to 30 seconds allows KDC to
respond promptly in worst case scenario as well.

Fixes: https://pagure.io/freeipa/issue/7217
Reviewed-By: Christian Heimes <cheimes@redhat.com>
Reviewed-By: Robbie Harwood <rharwood@redhat.com>
2018-11-16 16:54:38 -05:00

721 lines
18 KiB
C

/*
* MIT Kerberos KDC database backend for FreeIPA
*
* Authors: Simo Sorce <ssorce@redhat.com>
*
* Copyright (C) 2011 Simo Sorce, Red Hat
* see file 'COPYING' for use and warranty information
*
* 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 "ipa_kdb.h"
#include <unicase.h>
static struct timeval std_timeout = {30, 0};
char *ipadb_filter_escape(const char *input, bool star)
{
char *output;
size_t i = 0;
size_t j = 0;
if (!input) {
return NULL;
}
/* Assume the worst-case. */
output = malloc(strlen(input) * 3 + 1);
if (!output) {
return NULL;
}
while (input[i]) {
switch(input[i]) {
case '*':
if (star) {
output[j++] = '\\';
output[j++] = '2';
output[j++] = 'a';
} else {
output[j++] = '*';
}
break;
case '(':
output[j++] = '\\';
output[j++] = '2';
output[j++] = '8';
break;
case ')':
output[j++] = '\\';
output[j++] = '2';
output[j++] = '9';
break;
case '\\':
output[j++] = '\\';
output[j++] = '5';
output[j++] = 'c';
break;
default:
output[j++] = input[i];
}
i++;
}
output[j] = '\0';
return output;
}
static krb5_error_code ipadb_simple_ldap_to_kerr(int ldap_error)
{
switch (ldap_error) {
case LDAP_SUCCESS:
return 0;
case LDAP_NO_SUCH_OBJECT:
case LDAP_NO_SUCH_ATTRIBUTE:
return KRB5_KDB_NOENTRY;
case LDAP_ALIAS_PROBLEM:
case LDAP_INVALID_DN_SYNTAX:
case LDAP_ALIAS_DEREF_PROBLEM:
case LDAP_UNDEFINED_TYPE:
case LDAP_INAPPROPRIATE_MATCHING:
case LDAP_INVALID_SYNTAX:
case LDAP_NAMING_VIOLATION:
case LDAP_OBJECT_CLASS_VIOLATION:
case LDAP_NO_OBJECT_CLASS_MODS:
return KRB5_KDB_INTERNAL_ERROR;
case LDAP_ALREADY_EXISTS:
case LDAP_NOT_ALLOWED_ON_NONLEAF:
case LDAP_NOT_ALLOWED_ON_RDN:
case LDAP_TIMELIMIT_EXCEEDED:
case LDAP_SIZELIMIT_EXCEEDED:
case LDAP_ADMINLIMIT_EXCEEDED:
case LDAP_STRONG_AUTH_REQUIRED:
case LDAP_CONFIDENTIALITY_REQUIRED:
case LDAP_INAPPROPRIATE_AUTH:
case LDAP_INVALID_CREDENTIALS:
case LDAP_INSUFFICIENT_ACCESS:
case LDAP_BUSY:
case LDAP_UNAVAILABLE:
case LDAP_UNWILLING_TO_PERFORM:
case LDAP_CONSTRAINT_VIOLATION:
case LDAP_TYPE_OR_VALUE_EXISTS:
return KRB5_KDB_CONSTRAINT_VIOLATION;
}
return KRB5_KDB_SERVER_INTERNAL_ERR;
}
static bool ipadb_need_retry(struct ipadb_context *ipactx, int error)
{
switch(error) {
/* connection errors */
case LDAP_SERVER_DOWN:
case LDAP_LOCAL_ERROR:
case LDAP_ENCODING_ERROR:
case LDAP_DECODING_ERROR:
case LDAP_TIMEOUT:
case LDAP_USER_CANCELLED:
case LDAP_PARAM_ERROR:
case LDAP_NO_MEMORY:
case LDAP_CONNECT_ERROR:
case LDAP_NOT_SUPPORTED:
case LDAP_CLIENT_LOOP:
case LDAP_X_CONNECTING:
/* server returned errors */
case LDAP_PROTOCOL_ERROR:
case LDAP_BUSY:
case LDAP_UNAVAILABLE:
case LDAP_UNWILLING_TO_PERFORM:
case LDAP_LOOP_DETECT:
/* prob connection error, try to reconnect */
error = ipadb_get_connection(ipactx);
if (error == 0) {
return true;
}
/* fall through */
default:
break;
}
return false;
}
static int ipadb_check_connection(struct ipadb_context *ipactx)
{
if (ipactx->lcontext == NULL) {
return ipadb_get_connection(ipactx);
}
return 0;
}
krb5_error_code ipadb_simple_search(struct ipadb_context *ipactx,
char *basedn, int scope,
char *filter, char **attrs,
LDAPMessage **res)
{
int ret;
ret = ipadb_check_connection(ipactx);
if (ret != 0)
return ipadb_simple_ldap_to_kerr(ret);
ret = ldap_search_ext_s(ipactx->lcontext, basedn, scope,
filter, attrs, 0, NULL, NULL,
&std_timeout, LDAP_NO_LIMIT,
res);
/* first test if we need to retry to connect */
if (ret != 0 &&
ipadb_need_retry(ipactx, ret)) {
ldap_msgfree(*res);
ret = ldap_search_ext_s(ipactx->lcontext, basedn, scope,
filter, attrs, 0, NULL, NULL,
&std_timeout, LDAP_NO_LIMIT,
res);
}
return ipadb_simple_ldap_to_kerr(ret);
}
krb5_error_code ipadb_simple_delete(struct ipadb_context *ipactx, char *dn)
{
int ret;
ret = ipadb_check_connection(ipactx);
if (ret != 0)
return ipadb_simple_ldap_to_kerr(ret);
ret = ldap_delete_ext_s(ipactx->lcontext, dn, NULL, NULL);
/* first test if we need to retry to connect */
if (ret != 0 &&
ipadb_need_retry(ipactx, ret)) {
ret = ldap_delete_ext_s(ipactx->lcontext, dn, NULL, NULL);
}
return ipadb_simple_ldap_to_kerr(ret);
}
krb5_error_code ipadb_simple_add(struct ipadb_context *ipactx,
char *dn, LDAPMod **mods)
{
int ret;
ret = ipadb_check_connection(ipactx);
if (ret != 0)
return ipadb_simple_ldap_to_kerr(ret);
ret = ldap_add_ext_s(ipactx->lcontext, dn, mods, NULL, NULL);
/* first test if we need to retry to connect */
if (ret != 0 &&
ipadb_need_retry(ipactx, ret)) {
ret = ldap_add_ext_s(ipactx->lcontext, dn, mods, NULL, NULL);
}
return ipadb_simple_ldap_to_kerr(ret);
}
krb5_error_code ipadb_simple_modify(struct ipadb_context *ipactx,
char *dn, LDAPMod **mods)
{
int ret;
ret = ipadb_check_connection(ipactx);
if (ret != 0)
return ipadb_simple_ldap_to_kerr(ret);
ret = ldap_modify_ext_s(ipactx->lcontext, dn, mods, NULL, NULL);
/* first test if we need to retry to connect */
if (ret != 0 &&
ipadb_need_retry(ipactx, ret)) {
ret = ldap_modify_ext_s(ipactx->lcontext, dn, mods, NULL, NULL);
}
return ipadb_simple_ldap_to_kerr(ret);
}
krb5_error_code ipadb_simple_delete_val(struct ipadb_context *ipactx,
char *dn, char *attr, char *value)
{
krb5_error_code kerr;
LDAPMod *mods[2];
mods[0] = calloc(1, sizeof(LDAPMod));
if (!mods[0]) {
return ENOMEM;
}
mods[1] = NULL;
mods[0]->mod_op = LDAP_MOD_DELETE;
mods[0]->mod_type = strdup(attr);
if (!mods[0]->mod_type) {
kerr = ENOMEM;
goto done;
}
mods[0]->mod_values = calloc(2, sizeof(char *));
if (!mods[0]->mod_values) {
kerr = ENOMEM;
goto done;
}
mods[0]->mod_values[0] = strdup(value);
if (!mods[0]->mod_values[0]) {
kerr = ENOMEM;
goto done;
}
kerr = ipadb_simple_modify(ipactx, dn, mods);
done:
ldap_mods_free(mods, 0);
return kerr;
}
krb5_error_code ipadb_deref_search(struct ipadb_context *ipactx,
char *base_dn, int scope,
char *filter,
char **entry_attrs,
char **deref_attr_names,
char **deref_attrs,
LDAPMessage **res)
{
struct berval derefval = { 0, NULL };
LDAPControl *ctrl[2] = { NULL, NULL };
LDAPDerefSpec *ds;
krb5_error_code kerr;
int times;
int ret;
int c, i;
bool retry;
for (c = 0; deref_attr_names[c]; c++) {
/* count */ ;
}
ds = calloc(c+1, sizeof(LDAPDerefSpec));
if (!ds) {
return ENOMEM;
}
for (i = 0; deref_attr_names[i]; i++) {
ds[i].derefAttr = deref_attr_names[i];
ds[i].attributes = deref_attrs;
}
ds[c].derefAttr = NULL;
ret = ldap_create_deref_control_value(ipactx->lcontext, ds, &derefval);
if (ret != LDAP_SUCCESS) {
kerr = ENOMEM;
goto done;
}
ret = ldap_control_create(LDAP_CONTROL_X_DEREF,
1, &derefval, 1, &ctrl[0]);
if (ret != LDAP_SUCCESS) {
kerr = ENOMEM;
goto done;
}
/* retry once if connection errors (tot. max. 2 tries) */
times = 2;
ret = LDAP_SUCCESS;
retry = true;
while (retry) {
times--;
ret = ipadb_check_connection(ipactx);
if (ret != 0)
break;
ret = ldap_search_ext_s(ipactx->lcontext, base_dn,
scope, filter,
entry_attrs, 0,
ctrl, NULL,
&std_timeout, LDAP_NO_LIMIT,
res);
retry = ipadb_need_retry(ipactx, ret) && times > 0;
if (retry) {
/* Free result before next try */
ldap_msgfree(*res);
}
}
kerr = ipadb_simple_ldap_to_kerr(ret);
done:
ldap_control_free(ctrl[0]);
ldap_memfree(derefval.bv_val);
free(ds);
return kerr;
}
/* result extraction */
int ipadb_ldap_attr_to_int(LDAP *lcontext, LDAPMessage *le,
char *attrname, int *result)
{
struct berval **vals;
int ret = ENOENT;
vals = ldap_get_values_len(lcontext, le, attrname);
if (vals) {
*result = atoi(vals[0]->bv_val);
ret = 0;
ldap_value_free_len(vals);
}
return ret;
}
int ipadb_ldap_attr_to_uint32(LDAP *lcontext, LDAPMessage *le,
char *attrname, uint32_t *result)
{
struct berval **vals;
long r;
int ret = ENOENT;
vals = ldap_get_values_len(lcontext, le, attrname);
if (vals) {
r = atol(vals[0]->bv_val);
if (r < 0 || r > (uint32_t)-1) {
ret = EINVAL;
} else {
*result = r;
ret = 0;
}
ldap_value_free_len(vals);
}
return ret;
}
int ipadb_ldap_attr_to_str(LDAP *lcontext, LDAPMessage *le,
char *attrname, char **result)
{
struct berval **vals;
int ret = ENOENT;
vals = ldap_get_values_len(lcontext, le, attrname);
if (vals) {
*result = strndup(vals[0]->bv_val, vals[0]->bv_len);
if (!*result) {
ret = ENOMEM;
} else {
ret = 0;
}
ldap_value_free_len(vals);
}
return ret;
}
int ipadb_ldap_attr_to_strlist(LDAP *lcontext, LDAPMessage *le,
char *attrname, char ***result)
{
struct berval **vals = NULL;
char **strlist = NULL;
int ret;
int i;
vals = ldap_get_values_len(lcontext, le, attrname);
if (!vals) {
return ENOENT;
}
for (i = 0; vals[i]; i++) /* count */ ;
strlist = calloc(i + 1, sizeof(char *));
if (!strlist) {
ret = ENOMEM;
goto fail;
}
for (i = 0; vals[i]; i++) {
strlist[i] = strndup(vals[i]->bv_val, vals[i]->bv_len);
if (!strlist[i]) {
ret = ENOMEM;
goto fail;
}
}
ldap_value_free_len(vals);
*result = strlist;
return 0;
fail:
ldap_value_free_len(vals);
for (i = 0; strlist && strlist[i]; i++) {
free(strlist[i]);
}
free(strlist);
return ret;
}
int ipadb_ldap_attr_to_bool(LDAP *lcontext, LDAPMessage *le,
char *attrname, bool *result)
{
struct berval **vals;
int ret = ENOENT;
vals = ldap_get_values_len(lcontext, le, attrname);
if (vals) {
if (strcasecmp("TRUE", vals[0]->bv_val) == 0) {
*result = true;
ret = 0;
} else if (strcasecmp("FALSE", vals[0]->bv_val) == 0) {
*result = false;
ret = 0;
} else {
ret = EINVAL;
}
ldap_value_free_len(vals);
}
return ret;
}
int ipadb_ldap_attr_to_time_t(LDAP *lcontext, LDAPMessage *le,
char *attrname, time_t *result)
{
struct berval **vals;
char *p;
struct tm stm = { 0 };
int ret = ENOENT;
vals = ldap_get_values_len(lcontext, le, attrname);
if (vals) {
p = strptime(vals[0]->bv_val, "%Y%m%d%H%M%SZ", &stm);
if (p && *p == '\0') {
*result = timegm(&stm);
ret = 0;
} else {
ret = EINVAL;
}
ldap_value_free_len(vals);
}
return ret;
}
int ipadb_ldap_attr_to_krb5_timestamp(LDAP *lcontext, LDAPMessage *le,
char *attrname, krb5_timestamp *result)
{
time_t res_time;
long long res_long;
int ret = ipadb_ldap_attr_to_time_t(lcontext, le,
attrname, &res_time);
if (ret) return ret;
/* this will cast correctly maintaing sign to a 64bit variable */
res_long = res_time;
/* For dates beyond IPAPWD_END_OF_TIME, rest_time might oveflow
* on 32-bit platforms. This does not apply for 64-bit platforms.
* However, since krb5 uses 32-bit time representation, we need
* to limit the result.*/
if (res_long < 0 || res_long > IPAPWD_END_OF_TIME) {
*result = IPAPWD_END_OF_TIME; // 1 Jan 2038, 00:00 GMT
} else {
*result = (krb5_timestamp)res_long;
}
return 0;
}
int ipadb_ldap_attr_has_value(LDAP *lcontext, LDAPMessage *le,
char *attrname, const char *value)
{
struct berval **vals;
int ret = ENOENT;
int i, result;
vals = ldap_get_values_len(lcontext, le, attrname);
if (vals) {
for (i = 0; vals[i]; i++) {
if (ulc_casecmp(vals[i]->bv_val, vals[i]->bv_len,
value, strlen(value),
NULL, NULL, &result) != 0) {
ret = errno;
break;
}
if (result == 0) {
ret = 0;
break;
}
}
ldap_value_free_len(vals);
}
return ret;
}
int ipadb_ldap_deref_results(LDAP *lcontext, LDAPMessage *le,
LDAPDerefRes **results)
{
LDAPControl **ctrls = NULL;
LDAPControl *derefctrl = NULL;
int ret;
ret = ldap_get_entry_controls(lcontext, le, &ctrls);
if (ret != LDAP_SUCCESS) {
return EINVAL;
}
if (!ctrls) {
return ENOENT;
}
derefctrl = ldap_control_find(LDAP_CONTROL_X_DEREF, ctrls, NULL);
if (!derefctrl) {
ret = ENOENT;
goto done;
}
ret = ldap_parse_derefresponse_control(lcontext, derefctrl, results);
if (ret) {
ret = EINVAL;
goto done;
}
ret = 0;
done:
ldap_controls_free(ctrls);
return ret;
}
struct ipadb_multires {
LDAP *lcontext;
LDAPMessage **res;
LDAPMessage *next;
ssize_t cursor;
ssize_t count;
};
krb5_error_code ipadb_multires_init(LDAP *lcontext, struct ipadb_multires **r)
{
*r = malloc(sizeof(struct ipadb_multires));
if (!*r) return ENOMEM;
(*r)->lcontext = lcontext;
(*r)->res = NULL;
(*r)->next = NULL;
(*r)->cursor = -1;
(*r)->count = 0;
return 0;
}
void ipadb_multires_free(struct ipadb_multires *r)
{
if (r != NULL) {
for (int i = 0; i < r->count; i++) {
ldap_msgfree(r->res[i]);
}
free(r);
}
}
LDAPMessage *ipadb_multires_next_entry(struct ipadb_multires *r)
{
if (r->count == 0) return NULL;
if (r->next) {
r->next = ldap_next_entry(r->lcontext, r->next);
}
if (r->next == NULL) {
if (r->cursor >= r->count - 1) {
return NULL;
}
r->cursor++;
r->next = ldap_first_entry(r->lcontext, r->res[r->cursor]);
}
return r->next;
}
krb5_error_code ipadb_multibase_search(struct ipadb_context *ipactx,
char **basedns, int scope,
char *filter, char **attrs,
struct ipadb_multires **res,
bool any)
{
int ret;
ret = ipadb_multires_init(ipactx->lcontext, res);
if (ret != 0) return ret;
ret = ipadb_check_connection(ipactx);
if (ret != 0) {
ipadb_multires_free(*res);
*res = NULL;
return ipadb_simple_ldap_to_kerr(ret);
}
for (int b = 0; basedns[b]; b++) {
LDAPMessage *r;
ret = ldap_search_ext_s(ipactx->lcontext, basedns[b], scope,
filter, attrs, 0, NULL, NULL,
&std_timeout, LDAP_NO_LIMIT, &r);
/* first test if we need to retry to connect */
if (ret != 0 &&
ipadb_need_retry(ipactx, ret)) {
ldap_msgfree(r);
ret = ldap_search_ext_s(ipactx->lcontext, basedns[b], scope,
filter, attrs, 0, NULL, NULL,
&std_timeout, LDAP_NO_LIMIT, &r);
}
if (ret != 0) break;
if (ldap_count_entries(ipactx->lcontext, r) > 0) {
void *tmp = realloc((*res)->res, (((*res)->count + 1) *
sizeof(LDAPMessage *)));
if (tmp == NULL) {
ret = ENOMEM;
break;
}
(*res)->res = tmp;
(*res)->res[(*res)->count] = r;
(*res)->count++;
if (any) break;
}
}
if (ret != 0) {
ipadb_multires_free(*res);
*res = NULL;
}
return ipadb_simple_ldap_to_kerr(ret);
}