ld_cmd.c

Go to the documentation of this file.
00001 /*
00002  * $Id$
00003  *
00004  * LDAP Database Driver for SER
00005  *
00006  * Copyright (C) 2008 iptelorg GmbH
00007  *
00008  * This file is part of SER, a free SIP server.
00009  *
00010  * SER is free software; you can redistribute it and/or modify it under the
00011  * terms of the GNU General Public License as published by the Free Software
00012  * Foundation; either version 2 of the License, or (at your option) any later
00013  * version.
00014  *
00015  * SER is distributed in the hope that it will be useful, but WITHOUT ANY
00016  * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
00017  * FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
00018  * details.
00019  *
00020  * You should have received a copy of the GNU General Public License along
00021  * with this program; if not, write to the Free Software Foundation, Inc.,
00022  * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
00023  */
00024 
00033 #include "ld_cmd.h"
00034 #include "ld_fld.h"
00035 #include "ld_con.h"
00036 #include "ld_mod.h"
00037 #include "ld_uri.h"
00038 #include "ld_cfg.h"
00039 #include "ld_res.h"
00040 
00041 #include "../../mem/mem.h"
00042 #include "../../dprint.h"
00043 #include "../../ut.h"
00044 
00045 #include <string.h>
00046 
00047 
00053 static void ld_cmd_free(db_cmd_t* cmd, struct ld_cmd* payload)
00054 {
00055         db_drv_free(&payload->gen);
00056         if (payload->result) pkg_free(payload->result);
00057         pkg_free(payload);
00058 }
00059 
00060 
00061 static int build_result_array(char*** res, db_cmd_t* cmd)
00062 {
00063         struct ld_fld* lfld;
00064         char** t;
00065         int i;
00066         if (cmd->result_count == 0) {
00067                 *res = NULL;
00068                 return 0;
00069         }
00070 
00071         t = (char**)pkg_malloc(sizeof(char*) * (cmd->result_count + 1));
00072         if (t == NULL) {
00073                 ERR("ldap: No memory left\n");
00074                 return -1;
00075         }
00076         t[cmd->result_count] = NULL;
00077 
00078         for(i = 0; i < cmd->result_count; i++) {
00079                 lfld = DB_GET_PAYLOAD(cmd->result + i);
00080                 /* Attribute names are always zero terminated */
00081                 t[i] = lfld->attr.s;
00082         }
00083 
00084         *res = t;
00085         return 0;
00086 }
00087 
00088 
00089 int ld_cmd(db_cmd_t* cmd)
00090 {
00091         struct ld_cmd* lcmd;
00092         struct ld_cfg* cfg;
00093         struct ld_fld* lfld;
00094         int i, j;
00095 
00096         lcmd = (struct ld_cmd*)pkg_malloc(sizeof(struct ld_cmd));
00097         if (lcmd == NULL) {
00098                 ERR("ldap: No memory left\n");
00099                 goto error;
00100         }
00101         memset(lcmd, '\0', sizeof(struct ld_cmd));
00102         if (db_drv_init(&lcmd->gen, ld_cmd_free) < 0) goto error;
00103 
00104         switch(cmd->type) {
00105         case DB_PUT:
00106         case DB_DEL:
00107         case DB_UPD:
00108                 ERR("ldap: The driver does not support directory modifications yet.\n");
00109                 goto error;
00110                 break;
00111 
00112         case DB_GET:
00113                 break;
00114 
00115         case DB_SQL:
00116                 ERR("ldap: The driver does not support raw queries yet.\n");
00117                 goto error;
00118         }
00119 
00120         cfg = ld_find_cfg(&cmd->table);
00121         if (cfg == NULL) {
00122                 ERR("ldap: Cannot find configuration for '%.*s', giving up\n",
00123                         STR_FMT(&cmd->table));
00124                 goto error;
00125         }
00126 
00127         lcmd->base = cfg->base.s;
00128         lcmd->scope = cfg->scope;
00129 
00130         lcmd->sizelimit = cfg->sizelimit;
00131         if (cfg->timelimit) {
00132                 lcmd->timelimit.tv_sec = cfg->timelimit;
00133                 lcmd->timelimit.tv_usec = 0;
00134         }
00135 
00136         if (cfg->filter.s) {
00137                 lcmd->filter = cfg->filter;
00138         }
00139         lcmd->chase_references = cfg->chase_references;
00140         lcmd->chase_referrals = cfg->chase_referrals;
00141 
00142         if (ld_resolve_fld(cmd->match, cfg) < 0) goto error;
00143         if (ld_resolve_fld(cmd->result, cfg) < 0) goto error;
00144 
00145         /* prepare filter for each result field */
00146         for(i = 0; !DB_FLD_EMPTY(cmd->result) && !DB_FLD_LAST(cmd->result[i]); i++) {
00147                 int n;
00148                 lfld = DB_GET_PAYLOAD(cmd->result + i);
00149                 lfld->filter = NULL;
00150         
00151                 for(j = 0, n = 0; !DB_FLD_EMPTY(cmd->match) && !DB_FLD_LAST(cmd->match[j]); j++) {
00152                         if (strcmp(cmd->result[i].name, cmd->match[j].name) == 0)
00153                                 n++;    
00154                 }
00155                 
00156                 if (n > 0) {
00157                         lfld->filter = pkg_malloc((n+1)*sizeof(*(lfld->filter)));
00158                         if (!lfld->filter) return -1 /* E_OUT_OF_MEM*/;
00159                         for(j = 0, n = 0; !DB_FLD_EMPTY(cmd->match) && !DB_FLD_LAST(cmd->match[j]); j++) {
00160                                 if (strcmp(cmd->result[i].name, cmd->match[j].name) == 0) {
00161                                         lfld->filter[n] = cmd->match+j;
00162                                         n++;
00163                                 }
00164                         }
00165                         lfld->filter[n] = NULL;
00166                 }
00167         }
00168         if (build_result_array(&lcmd->result, cmd) < 0) goto error;
00169 
00170         DB_SET_PAYLOAD(cmd, lcmd);
00171         return 0;
00172 
00173  error:
00174         if (lcmd) {
00175                 DB_SET_PAYLOAD(cmd, NULL);
00176                 db_drv_free(&lcmd->gen);
00177                 if (lcmd->result) pkg_free(lcmd->result);
00178                 pkg_free(lcmd);
00179         }
00180         return -1;
00181 }
00182 
00183 
00184 int ld_cmd_exec(db_res_t* res, db_cmd_t* cmd)
00185 {
00186         db_con_t* con;
00187         struct ld_res* lres;
00188         struct ld_cmd* lcmd;
00189         struct ld_con* lcon;
00190         char* filter, *err_desc;
00191         int ret, err;
00192         LDAPMessage *msg, *resmsg;
00193         int reconn_cnt;
00194         int msgid;
00195         char *oid;
00196         struct berval *data;
00197         struct timeval restimeout;
00198 
00199         filter = NULL;
00200         err_desc = NULL;
00201         resmsg = NULL;
00202 
00203         /* First things first: retrieve connection info from the currently active
00204          * connection and also mysql payload from the database command
00205          */
00206         con = cmd->ctx->con[db_payload_idx];
00207         lcmd = DB_GET_PAYLOAD(cmd);
00208         lcon = DB_GET_PAYLOAD(con);
00209         
00210         reconn_cnt = ld_reconnect_attempt;
00211 
00212         if (ld_prepare_ldap_filter(&filter, cmd, &lcmd->filter) < 0) {
00213                 ERR("ldap: Error while building LDAP search filter\n");
00214                 goto error;
00215         }
00216 
00217         DBG("ldap: ldap_search(base:'%s', filter:'%s')\n", lcmd->base, filter);
00218         do {
00219                 if (lcon->flags & LD_CONNECTED) {
00220                         ldap_set_option(lcon->con, LDAP_OPT_DEREF, ((void *)&lcmd->chase_references));
00221                         /* there is alternative method using LDAP_CONTROL_REFERRALS per request but is not well documented */
00222                         ldap_set_option(lcon->con, LDAP_OPT_REFERRALS, lcmd->chase_referrals?LDAP_OPT_ON:LDAP_OPT_OFF);
00223                 
00224                         ret = ldap_search_ext(lcon->con, lcmd->base, lcmd->scope, filter,
00225                                                                   lcmd->result, 0, NULL, NULL,
00226                                                                   lcmd->timelimit.tv_sec ? &lcmd->timelimit : NULL,
00227                                                                   lcmd->sizelimit,
00228                                                                   &msgid);
00229                         if (ret != LDAP_SUCCESS) {
00230                                 ERR("ldap: Error while searching: %s\n", ldap_err2string(ret));
00231                                 goto error;
00232                         }
00233 
00234                         /*
00235                          openldap v2.3 library workaround for unsolicited messages:
00236                          if only unsolicited messages are available then ldap_result of
00237                          v2.3 library waits forever
00238                         */
00239                         memset(&restimeout, 0, sizeof(restimeout));
00240                         restimeout.tv_sec = 5;
00241                         ret = ldap_result(lcon->con,
00242                                                           LDAP_RES_ANY,
00243                                                           LDAP_MSG_ALL,
00244                                                           &restimeout,
00245                                                           &resmsg);
00246                 } else {
00247                         /* force it to reconnect */
00248                         ret = -1;
00249                 }
00250 
00251                 if (ret <= 0) {
00252                         ERR("ldap: Error in ldap_search: %s\n", ret < 0 ? ldap_err2string(ret) : "timeout");
00253                         if (ret == LDAP_SERVER_DOWN) {
00254                                 lcon->flags &= ~LD_CONNECTED;
00255                                 do {
00256                                         if (!reconn_cnt) {
00257                                                 ERR("ldap: maximum reconnection attempt reached! giving up\n");
00258                                                 goto error;
00259                                         }
00260                                         reconn_cnt--;
00261                                         err = ld_con_connect(con);
00262                                 } while (err != 0);
00263                         } else {
00264                                 goto error;
00265                         }
00266                 }
00267         } while (ret <= 0);
00268 
00269         /* looking for unsolicited messages */
00270         for (msg = ldap_first_message(lcon->con, resmsg);
00271                  msg != NULL;
00272                  msg = ldap_next_message(lcon->con, msg)) {
00273                 if (ldap_msgtype(msg) == LDAP_RES_EXTENDED) {
00274                         if (ldap_parse_extended_result(lcon->con,
00275                                                                                    msg,
00276                                                                                    &oid,
00277                                                                                    &data,
00278                                                                                    0) != LDAP_SUCCESS) {
00279                                 ERR("ldap: Error while parsing extended result\n");
00280                                 goto error;
00281                         }
00282                         if (oid != NULL) {
00283                                 if (strcmp(oid, LDAP_NOTICE_OF_DISCONNECTION) == 0) {
00284                                         WARN("ldap: Notice of Disconnection (OID: %s)\n", oid);
00285                                 } else {
00286                                         WARN("ldap: Unsolicited message received. OID: %s\n", oid);
00287                                 }
00288                                 ldap_memfree(oid);
00289                         }
00290                         if (data != NULL) {
00291                                 WARN("ldap: Unsolicited message data: %.*s\n",
00292                                          (int)data->bv_len, data->bv_val);
00293                                 ber_bvfree(data);
00294                         }
00295                 }
00296         }
00297 
00298         ret = ldap_parse_result(lcon->con, resmsg, &err, NULL, &err_desc, NULL, NULL, 0);
00299         if (ret != LDAP_SUCCESS) {
00300                 ERR("ldap: Error while reading result status: %s\n",
00301                         ldap_err2string(ret));
00302                 goto error;
00303         }
00304 
00305         if (err != LDAP_SUCCESS) {
00306                 ERR("ldap: LDAP server reports error: %s\n", ldap_err2string(err));
00307                 goto error;
00308         }
00309 
00310         if (res) {
00311                 lres = DB_GET_PAYLOAD(res);
00312                 lres->msg = resmsg;
00313         } else if (resmsg) {
00314                 ldap_msgfree(resmsg);
00315         }
00316 
00317         if (filter) pkg_free(filter);
00318         if (err_desc) ldap_memfree(err_desc);
00319         return 0;
00320 
00321  error:
00322         if (filter) pkg_free(filter);
00323         if (resmsg) ldap_msgfree(resmsg);
00324         if (err_desc) ldap_memfree(err_desc);
00325         return -1;
00326 }
00327 
00328 
00329 /* Iterate to the next search result in the linked list
00330  * of messages returned by the LDAP server and convert
00331  * the field values.
00332  */
00333 static int search_entry(db_res_t* res, int init)
00334 {
00335         db_con_t* con;
00336         struct ld_res* lres;
00337         struct ld_con* lcon;
00338         int r;
00339         lres = DB_GET_PAYLOAD(res);
00340         /* FIXME */
00341         con = res->cmd->ctx->con[db_payload_idx];
00342         lcon = DB_GET_PAYLOAD(con);
00343 
00344         if (init
00345             || !lres->current
00346             || ldap_msgtype(lres->current) != LDAP_RES_SEARCH_ENTRY
00347             /* there is no more value combination result left */
00348             || ld_incindex(res->cmd->result)) {
00349 
00350                 do {
00351                         if (init) {
00352                                 lres->current = ldap_first_message(lcon->con, lres->msg);
00353                                 init = 0;
00354                         }
00355                         else
00356                                 lres->current = ldap_next_message(lcon->con, lres->current);
00357                         
00358                         while(lres->current) {
00359                                 if (ldap_msgtype(lres->current) == LDAP_RES_SEARCH_ENTRY) {
00360                                         break;
00361                                 }
00362                                 lres->current = ldap_next_message(lcon->con, lres->current);
00363                         }
00364                         if (lres->current == NULL) return 1;
00365                         r = ld_ldap2fldinit(res->cmd->result, lcon->con, lres->current);
00366                 } while (r > 0);
00367                 if (r < 0) return -1;
00368         } else {
00369                 if (ld_ldap2fld(res->cmd->result, lcon->con, lres->current) < 0) return -1;
00370         }
00371 
00372         res->cur_rec->fld = res->cmd->result;
00373         return 0;
00374 }
00375 
00376 
00377 int ld_cmd_first(db_res_t* res)
00378 {
00379         return search_entry(res, 1);
00380 }
00381 
00382 
00383 int ld_cmd_next(db_res_t* res)
00384 {
00385         return search_entry(res, 0);
00386 }
00387 
00388 #define is_space(c) ((c)==' '||(c)==','||(c)==';'||(c)=='\t'||(c)=='\n'||(c)=='\r'||(c)=='\0')
00389 
00390 int ld_cmd_setopt(db_cmd_t* cmd, char* optname, va_list ap)
00391 {
00392         struct ld_fld* lfld;
00393         char* val, *c;
00394         int i;
00395                 
00396         if (!strcasecmp("client_side_filtering", optname)) {
00397                 val = va_arg(ap, char*);
00398 
00399                 for(i = 0; !DB_FLD_EMPTY(cmd->result) && !DB_FLD_LAST(cmd->result[i]); i++) {
00400                         c = val;
00401                         do {
00402                                 c = strstr(c, cmd->result[i].name);
00403                                 if (c) {
00404                                         if ((c == val || is_space(*(c-1))) && is_space(*(c+strlen(cmd->result[i].name)))) {
00405                                                 lfld = (struct ld_fld*)DB_GET_PAYLOAD(cmd->result + i);
00406                                                 lfld->client_side_filtering = 1;
00407                                                 break;
00408                                         }
00409                                         c += strlen(cmd->result[i].name);
00410                                 }
00411                         } while (c != NULL);
00412                 }
00413         }
00414         else
00415                 return 1;
00416         return 0;
00417 }
00418