mirror of https://github.com/sipwise/kamailio.git
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
420 lines
10 KiB
420 lines
10 KiB
/*
|
|
* $Id$
|
|
*
|
|
* LDAP Database Driver for SER
|
|
*
|
|
* Copyright (C) 2008 iptelorg GmbH
|
|
*
|
|
* This file is part of SER, a free SIP server.
|
|
*
|
|
* SER 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 2 of the License, or (at your option) any later
|
|
* version.
|
|
*
|
|
* SER 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, write to the Free Software Foundation, Inc.,
|
|
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
|
*/
|
|
|
|
/** \addtogroup ldap
|
|
* @{
|
|
*/
|
|
|
|
/** \file
|
|
* Implementation of functions related to database commands.
|
|
*/
|
|
|
|
#include "ld_cmd.h"
|
|
#include "ld_fld.h"
|
|
#include "ld_con.h"
|
|
#include "ld_mod.h"
|
|
#include "ld_uri.h"
|
|
#include "ld_cfg.h"
|
|
#include "ld_res.h"
|
|
|
|
#include "../../mem/mem.h"
|
|
#include "../../dprint.h"
|
|
#include "../../ut.h"
|
|
|
|
#include <string.h>
|
|
|
|
|
|
/** Destroys a ld_cmd structure.
|
|
* This function frees all memory used by ld_cmd structure.
|
|
* @param cmd A pointer to generic db_cmd command being freed.
|
|
* @param payload A pointer to ld_cmd structure to be freed.
|
|
*/
|
|
static void ld_cmd_free(db_cmd_t* cmd, struct ld_cmd* payload)
|
|
{
|
|
db_drv_free(&payload->gen);
|
|
if (payload->result) pkg_free(payload->result);
|
|
pkg_free(payload);
|
|
}
|
|
|
|
|
|
static int build_result_array(char*** res, db_cmd_t* cmd)
|
|
{
|
|
struct ld_fld* lfld;
|
|
char** t;
|
|
int i;
|
|
if (cmd->result_count == 0) {
|
|
*res = NULL;
|
|
return 0;
|
|
}
|
|
|
|
t = (char**)pkg_malloc(sizeof(char*) * (cmd->result_count + 1));
|
|
if (t == NULL) {
|
|
ERR("ldap: No memory left\n");
|
|
return -1;
|
|
}
|
|
t[cmd->result_count] = NULL;
|
|
|
|
for(i = 0; i < cmd->result_count; i++) {
|
|
lfld = DB_GET_PAYLOAD(cmd->result + i);
|
|
/* Attribute names are always zero terminated */
|
|
t[i] = lfld->attr.s;
|
|
}
|
|
|
|
*res = t;
|
|
return 0;
|
|
}
|
|
|
|
|
|
int ld_cmd(db_cmd_t* cmd)
|
|
{
|
|
struct ld_cmd* lcmd;
|
|
struct ld_cfg* cfg;
|
|
struct ld_fld* lfld;
|
|
int i, j;
|
|
|
|
lcmd = (struct ld_cmd*)pkg_malloc(sizeof(struct ld_cmd));
|
|
if (lcmd == NULL) {
|
|
ERR("ldap: No memory left\n");
|
|
goto error;
|
|
}
|
|
memset(lcmd, '\0', sizeof(struct ld_cmd));
|
|
if (db_drv_init(&lcmd->gen, ld_cmd_free) < 0) goto error;
|
|
|
|
switch(cmd->type) {
|
|
case DB_PUT:
|
|
case DB_DEL:
|
|
case DB_UPD:
|
|
ERR("ldap: The driver does not support directory modifications yet.\n");
|
|
goto error;
|
|
break;
|
|
|
|
case DB_GET:
|
|
break;
|
|
|
|
case DB_SQL:
|
|
ERR("ldap: The driver does not support raw queries yet.\n");
|
|
goto error;
|
|
}
|
|
|
|
cfg = ld_find_cfg(&cmd->table);
|
|
if (cfg == NULL) {
|
|
ERR("ldap: Cannot find configuration for '%.*s', giving up\n",
|
|
STR_FMT(&cmd->table));
|
|
goto error;
|
|
}
|
|
|
|
lcmd->base = cfg->base.s;
|
|
lcmd->scope = cfg->scope;
|
|
|
|
lcmd->sizelimit = cfg->sizelimit;
|
|
if (cfg->timelimit) {
|
|
lcmd->timelimit.tv_sec = cfg->timelimit;
|
|
lcmd->timelimit.tv_usec = 0;
|
|
}
|
|
|
|
if (cfg->filter.s) {
|
|
lcmd->filter = cfg->filter;
|
|
}
|
|
lcmd->chase_references = cfg->chase_references;
|
|
lcmd->chase_referrals = cfg->chase_referrals;
|
|
|
|
if (ld_resolve_fld(cmd->match, cfg) < 0) goto error;
|
|
if (ld_resolve_fld(cmd->result, cfg) < 0) goto error;
|
|
|
|
/* prepare filter for each result field */
|
|
for(i = 0; !DB_FLD_EMPTY(cmd->result) && !DB_FLD_LAST(cmd->result[i]); i++) {
|
|
int n;
|
|
lfld = DB_GET_PAYLOAD(cmd->result + i);
|
|
lfld->filter = NULL;
|
|
|
|
for(j = 0, n = 0; !DB_FLD_EMPTY(cmd->match) && !DB_FLD_LAST(cmd->match[j]); j++) {
|
|
if (strcmp(cmd->result[i].name, cmd->match[j].name) == 0)
|
|
n++;
|
|
}
|
|
|
|
if (n > 0) {
|
|
lfld->filter = pkg_malloc((n+1)*sizeof(*(lfld->filter)));
|
|
if (!lfld->filter) return -1 /* E_OUT_OF_MEM*/;
|
|
for(j = 0, n = 0; !DB_FLD_EMPTY(cmd->match) && !DB_FLD_LAST(cmd->match[j]); j++) {
|
|
if (strcmp(cmd->result[i].name, cmd->match[j].name) == 0) {
|
|
lfld->filter[n] = cmd->match+j;
|
|
n++;
|
|
}
|
|
}
|
|
lfld->filter[n] = NULL;
|
|
}
|
|
}
|
|
if (build_result_array(&lcmd->result, cmd) < 0) goto error;
|
|
|
|
DB_SET_PAYLOAD(cmd, lcmd);
|
|
return 0;
|
|
|
|
error:
|
|
if (lcmd) {
|
|
DB_SET_PAYLOAD(cmd, NULL);
|
|
db_drv_free(&lcmd->gen);
|
|
if (lcmd->result) pkg_free(lcmd->result);
|
|
pkg_free(lcmd);
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
|
|
int ld_cmd_exec(db_res_t* res, db_cmd_t* cmd)
|
|
{
|
|
db_con_t* con;
|
|
struct ld_res* lres;
|
|
struct ld_cmd* lcmd;
|
|
struct ld_con* lcon;
|
|
char* filter, *err_desc;
|
|
int ret, err;
|
|
LDAPMessage *msg, *resmsg;
|
|
int reconn_cnt;
|
|
int msgid;
|
|
char *oid;
|
|
struct berval *data;
|
|
struct timeval restimeout;
|
|
|
|
filter = NULL;
|
|
err_desc = NULL;
|
|
resmsg = NULL;
|
|
|
|
/* First things first: retrieve connection info from the currently active
|
|
* connection and also mysql payload from the database command
|
|
*/
|
|
con = cmd->ctx->con[db_payload_idx];
|
|
lcmd = DB_GET_PAYLOAD(cmd);
|
|
lcon = DB_GET_PAYLOAD(con);
|
|
|
|
reconn_cnt = ld_reconnect_attempt;
|
|
|
|
if (ld_prepare_ldap_filter(&filter, cmd, &lcmd->filter) < 0) {
|
|
ERR("ldap: Error while building LDAP search filter\n");
|
|
goto error;
|
|
}
|
|
|
|
DBG("ldap: ldap_search(base:'%s', filter:'%s')\n", lcmd->base, filter);
|
|
do {
|
|
if (lcon->flags & LD_CONNECTED) {
|
|
ldap_set_option(lcon->con, LDAP_OPT_DEREF, ((void *)&lcmd->chase_references));
|
|
/* there is alternative method using LDAP_CONTROL_REFERRALS per request but is not well documented */
|
|
ldap_set_option(lcon->con, LDAP_OPT_REFERRALS, lcmd->chase_referrals?LDAP_OPT_ON:LDAP_OPT_OFF);
|
|
|
|
ret = ldap_search_ext(lcon->con, lcmd->base, lcmd->scope, filter,
|
|
lcmd->result, 0, NULL, NULL,
|
|
lcmd->timelimit.tv_sec ? &lcmd->timelimit : NULL,
|
|
lcmd->sizelimit,
|
|
&msgid);
|
|
if (ret != LDAP_SUCCESS) {
|
|
ERR("ldap: Error while searching: %s\n", ldap_err2string(ret));
|
|
goto error;
|
|
}
|
|
|
|
/*
|
|
openldap v2.3 library workaround for unsolicited messages:
|
|
if only unsolicited messages are available then ldap_result of
|
|
v2.3 library waits forever
|
|
*/
|
|
memset(&restimeout, 0, sizeof(restimeout));
|
|
restimeout.tv_sec = 5;
|
|
ret = ldap_result(lcon->con,
|
|
LDAP_RES_ANY,
|
|
LDAP_MSG_ALL,
|
|
&restimeout,
|
|
&resmsg);
|
|
} else {
|
|
/* force it to reconnect */
|
|
ret = -1;
|
|
}
|
|
|
|
if (ret <= 0) {
|
|
ERR("ldap: Error in ldap_search: %s\n", ret < 0 ? ldap_err2string(ret) : "timeout");
|
|
if (ret == LDAP_SERVER_DOWN) {
|
|
lcon->flags &= ~LD_CONNECTED;
|
|
do {
|
|
if (!reconn_cnt) {
|
|
ERR("ldap: maximum reconnection attempt reached! giving up\n");
|
|
goto error;
|
|
}
|
|
reconn_cnt--;
|
|
err = ld_con_connect(con);
|
|
} while (err != 0);
|
|
} else {
|
|
goto error;
|
|
}
|
|
}
|
|
} while (ret <= 0);
|
|
|
|
/* looking for unsolicited messages */
|
|
for (msg = ldap_first_message(lcon->con, resmsg);
|
|
msg != NULL;
|
|
msg = ldap_next_message(lcon->con, msg)) {
|
|
if (ldap_msgtype(msg) == LDAP_RES_EXTENDED) {
|
|
if (ldap_parse_extended_result(lcon->con,
|
|
msg,
|
|
&oid,
|
|
&data,
|
|
0) != LDAP_SUCCESS) {
|
|
ERR("ldap: Error while parsing extended result\n");
|
|
goto error;
|
|
}
|
|
if (oid != NULL) {
|
|
if (strcmp(oid, LDAP_NOTICE_OF_DISCONNECTION) == 0) {
|
|
WARN("ldap: Notice of Disconnection (OID: %s)\n", oid);
|
|
} else {
|
|
WARN("ldap: Unsolicited message received. OID: %s\n", oid);
|
|
}
|
|
ldap_memfree(oid);
|
|
}
|
|
if (data != NULL) {
|
|
WARN("ldap: Unsolicited message data: %.*s\n",
|
|
(int)data->bv_len, data->bv_val);
|
|
ber_bvfree(data);
|
|
}
|
|
}
|
|
}
|
|
|
|
ret = ldap_parse_result(lcon->con, resmsg, &err, NULL, &err_desc, NULL, NULL, 0);
|
|
if (ret != LDAP_SUCCESS) {
|
|
ERR("ldap: Error while reading result status: %s\n",
|
|
ldap_err2string(ret));
|
|
goto error;
|
|
}
|
|
|
|
if (err != LDAP_SUCCESS) {
|
|
ERR("ldap: LDAP server reports error: %s\n", ldap_err2string(err));
|
|
goto error;
|
|
}
|
|
|
|
if (res) {
|
|
lres = DB_GET_PAYLOAD(res);
|
|
lres->msg = resmsg;
|
|
} else if (resmsg) {
|
|
ldap_msgfree(resmsg);
|
|
}
|
|
|
|
if (filter) pkg_free(filter);
|
|
if (err_desc) ldap_memfree(err_desc);
|
|
return 0;
|
|
|
|
error:
|
|
if (filter) pkg_free(filter);
|
|
if (resmsg) ldap_msgfree(resmsg);
|
|
if (err_desc) ldap_memfree(err_desc);
|
|
return -1;
|
|
}
|
|
|
|
|
|
/* Iterate to the next search result in the linked list
|
|
* of messages returned by the LDAP server and convert
|
|
* the field values.
|
|
*/
|
|
static int search_entry(db_res_t* res, int init)
|
|
{
|
|
db_con_t* con;
|
|
struct ld_res* lres;
|
|
struct ld_con* lcon;
|
|
int r;
|
|
lres = DB_GET_PAYLOAD(res);
|
|
/* FIXME */
|
|
con = res->cmd->ctx->con[db_payload_idx];
|
|
lcon = DB_GET_PAYLOAD(con);
|
|
|
|
if (init
|
|
|| !lres->current
|
|
|| ldap_msgtype(lres->current) != LDAP_RES_SEARCH_ENTRY
|
|
/* there is no more value combination result left */
|
|
|| ld_incindex(res->cmd->result)) {
|
|
|
|
do {
|
|
if (init) {
|
|
lres->current = ldap_first_message(lcon->con, lres->msg);
|
|
init = 0;
|
|
}
|
|
else
|
|
lres->current = ldap_next_message(lcon->con, lres->current);
|
|
|
|
while(lres->current) {
|
|
if (ldap_msgtype(lres->current) == LDAP_RES_SEARCH_ENTRY) {
|
|
break;
|
|
}
|
|
lres->current = ldap_next_message(lcon->con, lres->current);
|
|
}
|
|
if (lres->current == NULL) return 1;
|
|
r = ld_ldap2fldinit(res->cmd->result, lcon->con, lres->current);
|
|
} while (r > 0);
|
|
if (r < 0) return -1;
|
|
} else {
|
|
if (ld_ldap2fld(res->cmd->result, lcon->con, lres->current) < 0) return -1;
|
|
}
|
|
|
|
res->cur_rec->fld = res->cmd->result;
|
|
return 0;
|
|
}
|
|
|
|
|
|
int ld_cmd_first(db_res_t* res)
|
|
{
|
|
return search_entry(res, 1);
|
|
}
|
|
|
|
|
|
int ld_cmd_next(db_res_t* res)
|
|
{
|
|
return search_entry(res, 0);
|
|
}
|
|
|
|
#define is_space(c) ((c)==' '||(c)==','||(c)==';'||(c)=='\t'||(c)=='\n'||(c)=='\r'||(c)=='\0')
|
|
|
|
int ld_cmd_setopt(db_cmd_t* cmd, char* optname, va_list ap)
|
|
{
|
|
struct ld_fld* lfld;
|
|
char* val, *c;
|
|
int i;
|
|
|
|
if (!strcasecmp("client_side_filtering", optname)) {
|
|
val = va_arg(ap, char*);
|
|
|
|
for(i = 0; !DB_FLD_EMPTY(cmd->result) && !DB_FLD_LAST(cmd->result[i]); i++) {
|
|
c = val;
|
|
do {
|
|
c = strstr(c, cmd->result[i].name);
|
|
if (c) {
|
|
if ((c == val || is_space(*(c-1))) && is_space(*(c+strlen(cmd->result[i].name)))) {
|
|
lfld = (struct ld_fld*)DB_GET_PAYLOAD(cmd->result + i);
|
|
lfld->client_side_filtering = 1;
|
|
break;
|
|
}
|
|
c += strlen(cmd->result[i].name);
|
|
}
|
|
} while (c != NULL);
|
|
}
|
|
}
|
|
else
|
|
return 1;
|
|
return 0;
|
|
}
|
|
|
|
/** @} */
|