Merge "rtp_engine/res_rtp_asterisk: Fix RTP struct reentrancy crashes."
[asterisk/asterisk.git] / res / res_config_ldap.c
index 1907583..8f24a8d 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Asterisk -- A telephony toolkit for Linux.
+ * Asterisk -- An open source telephony toolkit.
  *
  * Copyright (C) 2005, Oxymium sarl
  * Manuel Guesdon <mguesdon@oxymium.net> - LDAP RealTime Driver Author/Adaptor
 
 /*! \file
  *
- * \brief ldap plugin for portable configuration engine (ARA)
+ * \brief LDAP plugin for portable configuration engine (ARA)
  *
  * \author Mark Spencer <markster@digium.com>
  * \author Manuel Guesdon
  * \author Carl-Einar Thorner <cthorner@voicerd.com>
  * \author Russell Bryant <russell@digium.com>
  *
- * \extref OpenLDAP http://www.openldap.org
+ * OpenLDAP http://www.openldap.org
+ */
+
+/*! \li \ref res_config_ldap.c uses the configuration file \ref res_ldap.conf
+ * \addtogroup configuration_file Configuration Files
+ */
+
+/*! 
+ * \page res_ldap.conf res_ldap.conf
+ * \verbinclude res_ldap.conf.sample
  */
 
 /*** MODULEINFO
        <depend>ldap</depend>
+       <support_level>extended</support_level>
  ***/
 
 #include "asterisk.h"
@@ -43,8 +53,6 @@
 #include <stdio.h>
 #include <ldap.h>
 
-ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
-
 #include "asterisk/channel.h"
 #include "asterisk/logger.h"
 #include "asterisk/config.h"
@@ -82,7 +90,8 @@ struct category_and_metric {
        int var_metric; /*!< For organizing variables (particularly includes and switch statments) within a context */
 };
 
-/*! \brief Table configuration */
+/*! \brief Table configuration 
+ */
 struct ldap_table_config {
        char *table_name;                /*!< table name */
        char *additional_filter;          /*!< additional filter        */
@@ -92,7 +101,8 @@ struct ldap_table_config {
        /* TODO: Make proxies work */
 };
 
-/*! \brief Should be locked before using it */
+/*! \brief Should be locked before using it 
+ */
 static AST_LIST_HEAD_NOLOCK_STATIC(table_configs, ldap_table_config);
 static struct ldap_table_config *base_table_config;
 static struct ldap_table_config *static_table_config;
@@ -101,7 +111,8 @@ static struct ast_cli_entry ldap_cli[] = {
        AST_CLI_DEFINE(realtime_ldap_status, "Shows connection information for the LDAP RealTime driver"),
 };
 
-/*! \brief Create a new table_config */
+/*! \brief Create a new table_config
+ */
 static struct ldap_table_config *table_config_new(const char *table_name)
 {
        struct ldap_table_config *p;
@@ -111,7 +122,7 @@ static struct ldap_table_config *table_config_new(const char *table_name)
 
        if (table_name) {
                if (!(p->table_name = ast_strdup(table_name))) {
-                       free(p);
+                       ast_free(p);
                        return NULL;
                }
        }
@@ -119,8 +130,12 @@ static struct ldap_table_config *table_config_new(const char *table_name)
        return p;
 }
 
-/*! \brief Find a table_config - Should be locked before using it 
- *  \note This function assumes ldap_lock to be locked. */
+/*! \brief Find a table_config
+ *
+ * Should be locked before using it 
+ *
+ *  \note This function assumes ldap_lock to be locked.
+ */
 static struct ldap_table_config *table_config_for_table_name(const char *table_name)
 {
        struct ldap_table_config *c = NULL;
@@ -133,7 +148,8 @@ static struct ldap_table_config *table_config_for_table_name(const char *table_n
        return c;
 }
 
-/*! \brief Find variable by name */
+/*! \brief Find variable by name
+ */
 static struct ast_variable *variable_named(struct ast_variable *var, const char *name)
 {
        for (; var; var = var->next) {
@@ -144,10 +160,10 @@ static struct ast_variable *variable_named(struct ast_variable *var, const char
        return var;
 }
 
-/*! \brief for the semicolon delimiter
-       \param somestr - pointer to a string
-
-       \return number of occurances of the delimiter(semicolon)
+/*! \brief Count  semicolons in string
+ * \param somestr - pointer to a string
+ *
+ * \return number of occurances of the delimiter(semicolon)
  */
 static int semicolon_count_str(const char *somestr)
 {
@@ -159,9 +175,11 @@ static int semicolon_count_str(const char *somestr)
        }
 
        return count;
-} 
+}
 
-/* takes a linked list of \a ast_variable variables, finds the one with the name variable_value
+/* \brief Count semicolons in variables
+ *  
+ * takes a linked list of \a ast_variable variables, finds the one with the name variable_value
  * and returns the number of semicolons in the value for that \a ast_variable
  */
 static int semicolon_count_var(struct ast_variable *var)
@@ -172,12 +190,15 @@ static int semicolon_count_var(struct ast_variable *var)
                return 0;
        }
 
-       ast_debug(2, "LINE(%d) semicolon_count_var: %s\n", __LINE__, var_value->value);
+       ast_debug(2, "semicolon_count_var: %s\n", var_value->value);
 
        return semicolon_count_str(var_value->value);
 }
 
-/*! \brief add attribute to table config - Should be locked before using it */
+/*! \brief add attribute to table config
+ *
+ * Should be locked before using it
+ */
 static void ldap_table_config_add_attribute(struct ldap_table_config *table_config,
        const char *attribute_name, const char *attribute_value)
 {
@@ -197,8 +218,10 @@ static void ldap_table_config_add_attribute(struct ldap_table_config *table_conf
        table_config->attributes = var;
 }
 
-/*! \brief Free table_config 
- *  \note assumes ldap_lock to be locked */
+/*! \brief Free table_config
+ *
+ * \note assumes ldap_lock to be locked
+ */
 static void table_configs_free(void)
 {
        struct ldap_table_config *c;
@@ -213,14 +236,17 @@ static void table_configs_free(void)
                if (c->attributes) {
                        ast_variables_destroy(c->attributes);
                }
-               free(c);
+               ast_free(c);
        }
 
        base_table_config = NULL;
        static_table_config = NULL;
 }
 
-/*! \brief Convert variable name to ldap attribute name - Should be locked before using it */
+/*! \brief Convert variable name to ldap attribute name
+ *
+ * \note Should be locked before using it
+ */
 static const char *convert_attribute_name_to_ldap(struct ldap_table_config *table_config,
        const char *attribute_name)
 {
@@ -246,7 +272,9 @@ static const char *convert_attribute_name_to_ldap(struct ldap_table_config *tabl
 }
 
 /*! \brief Convert ldap attribute name to variable name 
-       \note Should be locked before using it */
+ *
+ * \note Should be locked before using it
+ */
 static const char *convert_attribute_name_from_ldap(struct ldap_table_config *table_config,
                                                    const char *attribute_name)
 {
@@ -272,8 +300,8 @@ static const char *convert_attribute_name_from_ldap(struct ldap_table_config *ta
 }
 
 /*! \brief Get variables from ldap entry attributes 
-       \note Should be locked before using it
-       \return a linked list of ast_variable variables.
+ * \note Should be locked before using it
+ * \return a linked list of ast_variable variables.
  */
 static struct ast_variable *realtime_ldap_entry_to_var(struct ldap_table_config *table_config,
        LDAPMessage *ldap_entry)
@@ -302,7 +330,7 @@ static struct ast_variable *realtime_ldap_entry_to_var(struct ldap_table_config
                        for (v = values; *v; v++) {
                                value = *v;
                                valptr = value->bv_val;
-                               ast_debug(2, "LINE(%d) attribute_name: %s LDAP value: %s\n", __LINE__, attribute_name, valptr);
+                               ast_debug(2, "attribute_name: %s LDAP value: %s\n", attribute_name, valptr);
                                if (is_realmed_password_attribute) {
                                        if (!strncasecmp(valptr, "{md5}", 5)) {
                                                valptr += 5;
@@ -356,7 +384,7 @@ static struct ast_variable *realtime_ldap_entry_to_var(struct ldap_table_config
  * The results are freed outside this function so is the \a vars array.
  *     
  * \return \a vars - an array of ast_variable variables terminated with a null.
- **/
+ */
 static struct ast_variable **realtime_ldap_result_to_vars(struct ldap_table_config *table_config,
        LDAPMessage *ldap_result_msg, unsigned int *entries_count_ptr)
 {
@@ -373,7 +401,8 @@ static struct ast_variable **realtime_ldap_result_to_vars(struct ldap_table_conf
        int delim_tot_count = 0;
        int delim_count = 0;
 
-       /* First find the total count */
+       /* \breif First find the total count
+        */
        ldap_entry = ldap_first_entry(ldapConn, ldap_result_msg);
 
        for (tot_count = 0; ldap_entry; tot_count++) { 
@@ -387,31 +416,32 @@ static struct ast_variable **realtime_ldap_result_to_vars(struct ldap_table_conf
                *entries_count_ptr = tot_count;
        }
 
-       /* Now that we have the total count we allocate space and create the variables
+       /*! \note Now that we have the total count we allocate space and create the variables
         * Remember that each element in vars is a linked list that points to realtime variable.
         * If the we are dealing with a static realtime variable we create a new element in the \a vars array for each delimited
         * value in \a variable_value; otherwise, we keep \a vars static and increase the length of the linked list of variables in the array element.
-        * This memory must be freed outside of this function. */
-       vars = ast_calloc(sizeof(struct ast_variable *), tot_count + 1);
+        * This memory must be freed outside of this function.
+        */
+       vars = ast_calloc(tot_count + 1, sizeof(struct ast_variable *));
 
        ldap_entry = ldap_first_entry(ldapConn, ldap_result_msg);
 
        i = 0;
 
-       /* For each static realtime variable we may create several entries in the \a vars array if it's delimited */
-       for (entry_index = 0; ldap_entry; ) { 
+       /* \brief For each static realtime variable we may create several entries in the \a vars array if it's delimited
+        */
+       for (entry_index = 0; ldap_entry; ) {
                int pos = 0;
                delim_value = NULL;
                delim_tot_count = 0;
                delim_count = 0;
-               
+
                do { /* while delim_count */
 
                        /* Starting new static var */
                        char *ldap_attribute_name = ldap_first_attribute(ldapConn, ldap_entry, &ber);
                        struct berval *value;
                        while (ldap_attribute_name) {
-                       
                                const char *attribute_name = convert_attribute_name_from_ldap(table_config, ldap_attribute_name);
                                int is_realmed_password_attribute = strcasecmp(attribute_name, "md5secret") == 0;
                                struct berval **values = NULL;
@@ -431,28 +461,28 @@ static struct ast_variable **realtime_ldap_result_to_vars(struct ldap_table_conf
                                                        ast_debug(2, "md5: %s\n", valptr);
                                                }
                                                if (valptr) {
-                                                       if (delim_value == NULL && !is_realmed_password_attribute 
+                                                       if (delim_value == NULL && !is_realmed_password_attribute
                                                                && (static_table_config != table_config || strcmp(attribute_name, "variable_value") == 0)) {
 
                                                                delim_value = ast_strdup(valptr);
 
                                                                if ((delim_tot_count = semicolon_count_str(delim_value)) > 0) {
-                                                                       ast_debug(4, "LINE(%d) is delimited %d times: %s\n", __LINE__, delim_tot_count, delim_value);
+                                                                       ast_debug(4, "is delimited %d times: %s\n", delim_tot_count, delim_value);
                                                                        is_delimited = 1;
                                                                }
                                                        }
 
-                                                       if (is_delimited != 0 && !is_realmed_password_attribute 
+                                                       if (is_delimited != 0 && !is_realmed_password_attribute
                                                                && (static_table_config != table_config || strcmp(attribute_name, "variable_value") == 0) ) {
                                                                /* for non-Static RealTime, first */
 
                                                                for (i = pos; !ast_strlen_zero(valptr + i); i++) {
-                                                                       ast_debug(4, "LINE(%d) DELIM pos: %d i: %d\n", __LINE__, pos, i);
+                                                                       ast_debug(4, "DELIM pos: %d i: %d\n", pos, i);
                                                                        if (delim_value[i] == ';') {
                                                                                delim_value[i] = '\0';
 
-                                                                               ast_debug(2, "LINE(%d) DELIM - attribute_name: %s value: %s pos: %d\n", __LINE__, attribute_name, &delim_value[pos], pos);
-                                                       
+                                                                               ast_debug(2, "DELIM - attribute_name: %s value: %s pos: %d\n", attribute_name, &delim_value[pos], pos);
+
                                                                                if (prev) {
                                                                                        prev->next = ast_variable_new(attribute_name, &delim_value[pos], table_config->table_name);
                                                                                        if (prev->next) {
@@ -469,9 +499,9 @@ static struct ast_variable **realtime_ldap_result_to_vars(struct ldap_table_conf
                                                                        }
                                                                }
                                                                if (ast_strlen_zero(valptr + i)) {
-                                                                       ast_debug(4, "LINE(%d) DELIM pos: %d i: %d delim_count: %d\n", __LINE__, pos, i, delim_count);
+                                                                       ast_debug(4, "DELIM pos: %d i: %d delim_count: %d\n", pos, i, delim_count);
                                                                        /* Last delimited value */
-                                                                       ast_debug(4, "LINE(%d) DELIM - attribute_name: %s value: %s pos: %d\n", __LINE__, attribute_name, &delim_value[pos], pos);
+                                                                       ast_debug(4, "DELIM - attribute_name: %s value: %s pos: %d\n", attribute_name, &delim_value[pos], pos);
                                                                        if (prev) {
                                                                                prev->next = ast_variable_new(attribute_name, &delim_value[pos], table_config->table_name);
                                                                                if (prev->next) {
@@ -484,17 +514,17 @@ static struct ast_variable **realtime_ldap_result_to_vars(struct ldap_table_conf
                                                                        is_delimited = 0;
                                                                        pos = 0;
                                                                }
-                                                               free(delim_value);
+                                                               ast_free(delim_value);
                                                                delim_value = NULL;
-                                                               
-                                                               ast_debug(4, "LINE(%d) DELIM pos: %d i: %d\n", __LINE__, pos, i);
+
+                                                               ast_debug(4, "DELIM pos: %d i: %d\n", pos, i);
                                                        } else {
                                                                /* not delimited */
                                                                if (delim_value) {
-                                                                       free(delim_value);
+                                                                       ast_free(delim_value);
                                                                        delim_value = NULL;
                                                                }
-                                                               ast_debug(2, "LINE(%d) attribute_name: %s value: %s\n", __LINE__, attribute_name, valptr);
+                                                               ast_debug(2, "attribute_name: %s value: %s\n", attribute_name, valptr);
 
                                                                if (prev) {
                                                                        prev->next = ast_variable_new(attribute_name, valptr, table_config->table_name);
@@ -518,7 +548,7 @@ static struct ast_variable **realtime_ldap_result_to_vars(struct ldap_table_conf
                                        const struct ast_variable *tmpdebug = variable_named(var, "variable_name");
                                        const struct ast_variable *tmpdebug2 = variable_named(var, "variable_value");
                                        if (tmpdebug && tmpdebug2) {
-                                               ast_debug(3, "LINE(%d) Added to vars - %s = %s\n", __LINE__, tmpdebug->value, tmpdebug2->value);
+                                               ast_debug(3, "Added to vars - %s = %s\n", tmpdebug->value, tmpdebug2->value);
                                        }
                                }
                                vars[entry_index++] = var;
@@ -529,8 +559,8 @@ static struct ast_variable **realtime_ldap_result_to_vars(struct ldap_table_conf
                } while (delim_count <= delim_tot_count && static_table_config == table_config);
 
                if (static_table_config != table_config) {
-                       ast_debug(3, "LINE(%d) Added to vars - non static\n", __LINE__);
-                               
+                       ast_debug(3, "Added to vars - non static\n");
+
                        vars[entry_index++] = var;
                        prev = NULL;
                }
@@ -541,14 +571,19 @@ static struct ast_variable **realtime_ldap_result_to_vars(struct ldap_table_conf
 }
 
 
-/*! \brief Check if we have a connection error */
+/*! \brief Check if we have a connection error
+ */
 static int is_ldap_connect_error(int err)
 {
        return (err == LDAP_SERVER_DOWN || err == LDAP_TIMEOUT || err == LDAP_CONNECT_ERROR);
 }
 
-/*! \brief Get LDAP entry by dn and return attributes as variables  - Should be locked before using it 
-       This is used for setting the default values of an object(i.e., with accountBaseDN)
+/*! \brief Get LDAP entry by dn and return attributes as variables
+ *
+ * Should be locked before using it 
+ *
+ * This is used for setting the default values of an object
+ * i.e., with accountBaseDN
 */
 static struct ast_variable *ldap_loadentry(struct ldap_table_config *table_config,
                                           const char *dn)
@@ -609,22 +644,23 @@ static struct ast_variable *ldap_loadentry(struct ldap_table_config *table_confi
                /* Chopping \a vars down to one variable */
                if (vars != NULL) {
                        struct ast_variable **p = vars;
-                       p++;
-                       var = *p;
-                       while (var) {
-                               ast_variables_destroy(var);
-                               p++;
+
+                       /* Only take the first one. */
+                       var = *vars;
+
+                       /* Destroy the rest. */
+                       while (*++p) {
+                               ast_variables_destroy(*p);
                        }
-                       vars = ast_realloc(vars, sizeof(struct ast_variable *));
+                       ast_free(vars);
                }
 
-               var = *vars;
-
                return var;
        }
 }
 
-/*! \note caller should free returned pointer */
+/*! \note caller should free returned pointer
+ */
 static char *substituted(struct ast_channel *channel, const char *string)
 {
 #define MAXRESULT      2048
@@ -638,7 +674,8 @@ static char *substituted(struct ast_channel *channel, const char *string)
        return ret_string;
 }
 
-/*! \note caller should free returned pointer */
+/*! \note caller should free returned pointer
+ */
 static char *cleaned_basedn(struct ast_channel *channel, const char *basedn)
 {
        char *cbasedn = NULL;
@@ -666,7 +703,8 @@ static char *cleaned_basedn(struct ast_channel *channel, const char *basedn)
 }
 
 /*! \brief Replace \<search\> by \<by\> in string. 
-       \note No check is done on string allocated size ! */
+ * \note No check is done on string allocated size !
+ */
 static int replace_string_in_string(char *string, const char *search, const char *by)
 {
        int search_len = strlen(search);
@@ -689,7 +727,8 @@ static int replace_string_in_string(char *string, const char *search, const char
        return replaced;
 }
 
-/*! \brief Append a name=value filter string. The filter string can grow. */
+/*! \brief Append a name=value filter string. The filter string can grow. 
+ */
 static void append_var_and_value_to_filter(struct ast_str **filter,
        struct ldap_table_config *table_config,
        const char *name, const char *value)
@@ -715,20 +754,60 @@ static void append_var_and_value_to_filter(struct ast_str **filter,
        ast_str_append(filter, 0, "(%s=%s)", name, value);
 }
 
+/*!
+ * \internal
+ * \brief Create an LDAP filter using search fields
+ *
+ * \param config the \c ldap_table_config for this search
+ * \param fields the \c ast_variable criteria to include
+ *
+ * \returns an \c ast_str pointer on success, NULL otherwise.
+ */
+static struct ast_str *create_lookup_filter(struct ldap_table_config *config, const struct ast_variable *fields)
+{
+       struct ast_str *filter;
+       const struct ast_variable *field;
+
+       filter = ast_str_create(80);
+       if (!filter) {
+               return NULL;
+       }
+
+       /*
+        * Create the filter with the table additional filter and the
+        * parameter/value pairs we were given
+        */
+       ast_str_append(&filter, 0, "(&");
+       if (config && config->additional_filter) {
+               ast_str_append(&filter, 0, "%s", config->additional_filter);
+       }
+       if (config != base_table_config
+               && base_table_config
+               && base_table_config->additional_filter) {
+               ast_str_append(&filter, 0, "%s", base_table_config->additional_filter);
+       }
+       /* Append the lookup fields */
+       for (field = fields; field; field = field->next) {
+               append_var_and_value_to_filter(&filter, config, field->name, field->value);
+       }
+       ast_str_append(&filter, 0, ")");
+
+       return filter;
+}
+
 /*! \brief LDAP base function 
  * \return a null terminated array of ast_variable (one per entry) or NULL if no entry is found or if an error occured
  * caller should free the returned array and ast_variables
  * \param entries_count_ptr is a pointer to found entries count (can be NULL)
  * \param basedn is the base DN
  * \param table_name is the table_name (used dor attribute convertion and additional filter)
- * \param ap contains null terminated list of pairs name/value
+ * \param fields contains list of pairs name/value
 */
 static struct ast_variable **realtime_ldap_base_ap(unsigned int *entries_count_ptr,
-       const char *basedn, const char *table_name, va_list ap)
+       const char *basedn, const char *table_name, const struct ast_variable *fields)
 {
        struct ast_variable **vars = NULL;
-       const char *newparam = NULL;
-       const char *newval = NULL;
+       const struct ast_variable *field = fields;
        struct ldap_table_config *table_config = NULL;
        char *clean_basedn = cleaned_basedn(NULL, basedn);
        struct ast_str *filter = NULL;
@@ -742,20 +821,9 @@ static struct ast_variable **realtime_ldap_base_ap(unsigned int *entries_count_p
                return NULL;
        } 
 
-       if (!(filter = ast_str_create(80))) {
-               ast_log(LOG_ERROR, "Can't initialize data structures.n");
-               ast_free(clean_basedn);
-               return NULL;
-       }
-
-       /* Get the first parameter and first value in our list of passed paramater/value pairs  */
-       newparam = va_arg(ap, const char *);
-       newval = va_arg(ap, const char *);
-
-       if (!newparam || !newval) {
+       if (!field) {
                ast_log(LOG_ERROR, "Realtime retrieval requires at least 1 parameter"
                        " and 1 value to search on.\n");
-               ast_free(filter);
                ast_free(clean_basedn);
                return NULL;
        }
@@ -765,7 +833,6 @@ static struct ast_variable **realtime_ldap_base_ap(unsigned int *entries_count_p
        /* We now have our complete statement; Lets connect to the server and execute it.  */
        if (!ldap_reconnect()) {
                ast_mutex_unlock(&ldap_lock);
-               ast_free(filter);
                ast_free(clean_basedn);
                return NULL;
        }
@@ -774,38 +841,24 @@ static struct ast_variable **realtime_ldap_base_ap(unsigned int *entries_count_p
        if (!table_config) {
                ast_log(LOG_WARNING, "No table named '%s'.\n", table_name);
                ast_mutex_unlock(&ldap_lock);
-               ast_free(filter);
                ast_free(clean_basedn);
                return NULL;
        }
 
-       ast_str_append(&filter, 0, "(&");
-
-       if (table_config && table_config->additional_filter) {
-               ast_str_append(&filter, 0, "%s", table_config->additional_filter);
-       }
-       if (table_config != base_table_config && base_table_config && 
-               base_table_config->additional_filter) {
-               ast_str_append(&filter, 0, "%s", base_table_config->additional_filter);
+       filter = create_lookup_filter(table_config, fields);
+       if (!filter) {
+               ast_mutex_unlock(&ldap_lock);
+               ast_free(clean_basedn);
+               return NULL;
        }
 
-       /* Create the first part of the query using the first parameter/value pairs we just extracted */
-       /*   If there is only 1 set, then we have our query. Otherwise, loop thru the list and concat */
-
-       append_var_and_value_to_filter(&filter, table_config, newparam, newval);
-       while ((newparam = va_arg(ap, const char *))) {
-               newval = va_arg(ap, const char *);
-               append_var_and_value_to_filter(&filter, table_config, newparam, newval);
-       }
-       ast_str_append(&filter, 0, ")");
-
        do {
                /* freeing ldap_result further down */
                result = ldap_search_ext_s(ldapConn, clean_basedn,
                                  LDAP_SCOPE_SUBTREE, ast_str_buffer(filter), NULL, 0, NULL, NULL, NULL, LDAP_NO_LIMIT,
                                  &ldap_result_msg);
                if (result != LDAP_SUCCESS && is_ldap_connect_error(result)) {
-                       ast_log(LOG_DEBUG, "Failed to query directory. Try %d/10\n", tries + 1);
+                       ast_debug(1, "Failed to query directory. Try %d/10\n", tries + 1);
                        if (++tries < 10) {
                                usleep(1);
                                if (ldapConn) {
@@ -834,7 +887,8 @@ static struct ast_variable **realtime_ldap_base_ap(unsigned int *entries_count_p
 
                ldap_msgfree(ldap_result_msg);
 
-               /* TODO: get the default variables from the accountBaseDN, not implemented with delimited values */
+               /*! \TODO get the default variables from the accountBaseDN, not implemented with delimited values
+                */
                if (vars) {
                        struct ast_variable **p = vars;
                        while (*p) {
@@ -864,6 +918,11 @@ static struct ast_variable **realtime_ldap_base_ap(unsigned int *entries_count_p
                                                                ast_variables_destroy(base_var);
                                                                base_var = next;
                                                        } else {
+                                                               /*!
+                                                                * \todo XXX The interactions with base_var and append_var may
+                                                                * cause a memory leak of base_var nodes.  Also the append_var
+                                                                * list and base_var list may get cross linked.
+                                                                */
                                                                if (append_var) {
                                                                        base_var->next = append_var;
                                                                } else {
@@ -886,46 +945,68 @@ static struct ast_variable **realtime_ldap_base_ap(unsigned int *entries_count_p
                }
        }
 
-       if (filter) {
-               ast_free(filter);
-       }
-
-       if (clean_basedn) {
-               ast_free(clean_basedn);
-       }
+       ast_free(filter);
+       ast_free(clean_basedn);
 
        ast_mutex_unlock(&ldap_lock);
 
        return vars;
 }
 
-/*! \brief same as realtime_ldap_base_ap but take variable arguments count list */
+static struct ast_variable *realtime_arguments_to_fields(va_list ap)
+{
+       struct ast_variable *fields = NULL;
+       const char *newparam, *newval;
+
+       while ((newparam = va_arg(ap, const char *))) {
+               struct ast_variable *field;
+
+               newval = va_arg(ap, const char *);
+               if (!(field = ast_variable_new(newparam, newval, ""))) {
+                       ast_variables_destroy(fields);
+                       return NULL;
+               }
+
+               field->next = fields;
+               fields = field;
+       }
+
+       return fields;
+}
+
+/*! \brief same as realtime_ldap_base_ap but take variable arguments count list
+ */
 static struct ast_variable **realtime_ldap_base(unsigned int *entries_count_ptr,
        const char *basedn, const char *table_name, ...)
 {
+       RAII_VAR(struct ast_variable *, fields, NULL, ast_variables_destroy);
        struct ast_variable **vars = NULL;
        va_list ap;
 
        va_start(ap, table_name);
-       vars = realtime_ldap_base_ap(entries_count_ptr, basedn, table_name, ap);
+       fields = realtime_arguments_to_fields(ap);
        va_end(ap);
 
+       vars = realtime_ldap_base_ap(entries_count_ptr, basedn, table_name, fields);
+
        return vars;
 }
 
 /*! \brief See Asterisk doc
-*
-* For Realtime Dynamic(i.e., switch, queues, and directory) -- I think
-*/
+ *
+ * For Realtime Dynamic(i.e., switch, queues, and directory)
+ */
 static struct ast_variable *realtime_ldap(const char *basedn,
-                                         const char *table_name, va_list ap)
+                                         const char *table_name, const struct ast_variable *fields)
 {
-       struct ast_variable **vars = realtime_ldap_base_ap(NULL, basedn, table_name, ap);
+       struct ast_variable **vars = realtime_ldap_base_ap(NULL, basedn, table_name, fields);
        struct ast_variable *var = NULL;
 
        if (vars) {
                struct ast_variable *last_var = NULL;
                struct ast_variable **p = vars;
+
+               /* Chain the vars array of lists into one list to return. */
                while (*p) {
                        if (last_var) {
                                while (last_var->next) {
@@ -938,35 +1019,32 @@ static struct ast_variable *realtime_ldap(const char *basedn,
                        }
                        p++;
                }
-               free(vars);
+               ast_free(vars);
        }
        return var;
 }
 
 /*! \brief See Asterisk doc
-*
-* this function will be called for the switch statment if no match is found with the realtime_ldap function(i.e. it is a failover);
-* however, the ast_load_realtime wil match on wildcharacters also depending on what the mode is set to
-* this is an area of asterisk that could do with a lot of modification
-* I think this function returns Realtime dynamic objects
-*/
+ *
+ * this function will be called for the switch statment if no match is found with the realtime_ldap function(i.e. it is a failover);
+ * however, the ast_load_realtime wil match on wildcharacters also depending on what the mode is set to
+ * this is an area of asterisk that could do with a lot of modification
+ * I think this function returns Realtime dynamic objects
+ */
 static struct ast_config *realtime_multi_ldap(const char *basedn,
-      const char *table_name, va_list ap)
+      const char *table_name, const struct ast_variable *fields)
 {
        char *op;
        const char *initfield = NULL;
-       const char *newparam, *newval;
        struct ast_variable **vars =
-               realtime_ldap_base_ap(NULL, basedn, table_name, ap);
+               realtime_ldap_base_ap(NULL, basedn, table_name, fields);
        struct ast_config *cfg = NULL;
 
-       newparam = va_arg(ap, const char *);
-       newval = va_arg(ap, const char *);
-       if (!newparam || !newval) {
+       if (!fields) {
            ast_log(LOG_WARNING, "realtime retrieval requires at least 1 parameter and 1 value to search on.\n");
            return NULL;
        }
-       initfield = ast_strdupa(newparam);
+       initfield = ast_strdupa(fields->name);
        if ((op = strchr(initfield, ' '))) {
                *op = '\0';
        }
@@ -979,10 +1057,8 @@ static struct ast_config *realtime_multi_ldap(const char *basedn,
                        struct ast_variable **p = vars;
 
                        while (*p) {
-                               struct ast_category *cat = NULL;
-                               cat = ast_category_new("", table_name, -1);
+                               struct ast_category *cat = ast_category_new_anonymous();
                                if (!cat) {
-                                       ast_log(LOG_ERROR, "Unable to create a new category!\n");
                                        break;
                                } else {
                                        struct ast_variable *var = *p;
@@ -1000,14 +1076,13 @@ static struct ast_config *realtime_multi_ldap(const char *basedn,
                                p++;
                        }
                }
-               free(vars);
+               ast_free(vars);
        }
        return cfg;
 
 }
 
-/*! 
- * \brief Sorting alogrithm for qsort to find the order of the variables \a a and \a b
+/*! \brief Sorting alogrithm for qsort to find the order of the variables \a a and \a b
  * \param a pointer to category_and_metric struct
  * \param b pointer to category_and_metric struct
  *
@@ -1037,13 +1112,13 @@ static int compare_categories(const void *a, const void *b)
        return 0;
 }
 
-/*! \brief See Asterisk doc
+/*! \brief See Asterisk Realtime Documentation
  *
-*      This is for Static Realtime (again: I think...)
-*      
-*      load the configuration stuff for the .conf files
-*      called on a reload
-*/
+ * This is for Static Realtime
+ *     
+ * load the configuration stuff for the .conf files
+ * called on a reload
+ */
 static struct ast_config *config_ldap(const char *basedn, const char *table_name,
        const char *file, struct ast_config *cfg, struct ast_flags config_flags, const char *sugg_incl, const char *who_asked)
 {
@@ -1069,11 +1144,11 @@ static struct ast_config *config_ldap(const char *basedn, const char *table_name
                return NULL;
        }
 
-       /*!\note Since the items come back in random order, they need to be sorted
+       /*! \note Since the items come back in random order, they need to be sorted
         * first, and since the data could easily exceed stack size, this is
         * allocated from the heap.
         */
-       if (!(categories = ast_calloc(sizeof(*categories), vars_count))) {
+       if (!(categories = ast_calloc(vars_count, sizeof(*categories)))) {
                return NULL;
        }
 
@@ -1084,11 +1159,6 @@ static struct ast_config *config_ldap(const char *basedn, const char *table_name
                struct ast_variable *var_val = variable_named(*p, "variable_value");
                struct ast_variable *var_metric = variable_named(*p, "var_metric");
                struct ast_variable *dn = variable_named(*p, "dn");
-                       
-               ast_debug(3, "category: %s\n", category->value);
-               ast_debug(3, "var_name: %s\n", var_name->value);
-               ast_debug(3, "var_val: %s\n", var_val->value);
-               ast_debug(3, "cat_metric: %s\n", cat_metric->value);
 
                if (!category) {
                        ast_log(LOG_ERROR, "No category name in entry '%s'  for file '%s'.\n",
@@ -1115,6 +1185,12 @@ static struct ast_config *config_ldap(const char *basedn, const char *table_name
                        categories[vars_count].var_metric = atoi(var_metric->value);
                        vars_count++;
                }
+
+               ast_debug(3, "category: %s\n", category->value);
+               ast_debug(3, "var_name: %s\n", var_name->value);
+               ast_debug(3, "var_val: %s\n", var_val->value);
+               ast_debug(3, "cat_metric: %s\n", cat_metric->value);
+
        }
 
        qsort(categories, vars_count, sizeof(*categories), compare_categories);
@@ -1131,7 +1207,7 @@ static struct ast_config *config_ldap(const char *basedn, const char *table_name
                if (!last_category || strcmp(last_category, categories[i].name) ||
                        last_category_metric != categories[i].metric) {
 
-                       cur_cat = ast_category_new(categories[i].name, table_name, -1);
+                       cur_cat = ast_category_new_dynamic(categories[i].name);
                        if (!cur_cat) {
                                break;
                        }
@@ -1153,218 +1229,285 @@ static struct ast_config *config_ldap(const char *basedn, const char *table_name
        return cfg;
 }
 
-/* \brief Function to update a set of values in ldap static mode
-*/
-static int update_ldap(const char *basedn, const char *table_name, const char *attribute,
-       const char *lookup, va_list ap)
+/*!
+ * \internal
+ * \brief Create an LDAP modification structure (LDAPMod)
+ *
+ * \param attribute the name of the LDAP attribute to modify
+ * \param new_value the new value of the LDAP attribute
+ *
+ * \returns an LDAPMod * if successful, NULL otherwise.
+ */
+static LDAPMod *ldap_mod_create(const char *attribute, const char *new_value)
 {
-       int error = 0;
-       LDAPMessage *ldap_entry = NULL;
-       LDAPMod **ldap_mods;
-       const char *newparam = NULL;
-       const char *newval = NULL;
-       char *dn;
-       int num_entries = 0;
-       int i = 0;
-       int mods_size = 0;
-       int mod_exists = 0;
-       struct ldap_table_config *table_config = NULL;
-       char *clean_basedn = NULL;
-       struct ast_str *filter = NULL;
-       int tries = 0;
-       int result = 0;
-       LDAPMessage *ldap_result_msg = NULL;
+       LDAPMod *mod;
+       char *type;
 
-       if (!table_name) {
-               ast_log(LOG_ERROR, "No table_name specified.\n");
-               return -1;
-       } 
+       mod = ldap_memcalloc(1, sizeof(LDAPMod));
+       type = ldap_strdup(attribute);
 
-       if (!(filter = ast_str_create(80))) {
-               return -1;
+       if (!(mod && type)) {
+               ast_log(LOG_ERROR, "Memory allocation failure creating LDAP modification\n");
+               ldap_memfree(type);
+               ldap_memfree(mod);
+               return NULL;
        }
 
-       if (!attribute || !lookup) {
-               ast_log(LOG_WARNING, "LINE(%d): search parameters are empty.\n", __LINE__);
-               return -1;
-       }
-       ast_mutex_lock(&ldap_lock);
+       mod->mod_type = type;
 
-       /* We now have our complete statement; Lets connect to the server and execute it.  */
-       if (!ldap_reconnect()) {
-               ast_mutex_unlock(&ldap_lock);
-               return -1;
-       }
+       if (strlen(new_value)) {
+               char **values, *value;
+               values = ldap_memcalloc(2, sizeof(char *));
+               value = ldap_strdup(new_value);
 
-       table_config = table_config_for_table_name(table_name);
-       if (!table_config) {
-               ast_log(LOG_ERROR, "No table named '%s'.\n", table_name);
-               ast_mutex_unlock(&ldap_lock);
-               return -1;
+               if (!(values && value)) {
+                       ast_log(LOG_ERROR, "Memory allocation failure creating LDAP modification\n");
+                       ldap_memfree(value);
+                       ldap_memfree(values);
+                       ldap_memfree(type);
+                       ldap_memfree(mod);
+                       return NULL;
+               }
+
+               mod->mod_op = LDAP_MOD_REPLACE;
+               mod->mod_values = values;
+               mod->mod_values[0] = value;
+       } else {
+               mod->mod_op = LDAP_MOD_DELETE;
        }
 
-       clean_basedn = cleaned_basedn(NULL, basedn);
+       return mod;
+}
 
-       /* Create the filter with the table additional filter and the parameter/value pairs we were given */
-       ast_str_append(&filter, 0, "(&");
-       if (table_config && table_config->additional_filter) {
-               ast_str_append(&filter, 0, "%s", table_config->additional_filter);
-       }
-       if (table_config != base_table_config && base_table_config && base_table_config->additional_filter) {
-               ast_str_append(&filter, 0, "%s", base_table_config->additional_filter);
+/*!
+ * \internal
+ * \brief Append a value to an existing LDAP modification structure
+ *
+ * \param src the LDAPMod to update
+ * \param new_value the new value to append to the LDAPMod
+ *
+ * \returns the \c src original passed in if successful, NULL otherwise.
+ */
+static LDAPMod *ldap_mod_append(LDAPMod *src, const char *new_value)
+{
+       char *new_buffer;
+
+       if (src->mod_op != LDAP_MOD_REPLACE) {
+               return src;
        }
-       append_var_and_value_to_filter(&filter, table_config, attribute, lookup);
-       ast_str_append(&filter, 0, ")");
 
-       /* Create the modification array with the parameter/value pairs we were given, 
-        * if there are several parameters with the same name, we collect them into 
-        * one parameter/value pair and delimit them with a semicolon */
-       newparam = va_arg(ap, const char *);
-       newparam = convert_attribute_name_to_ldap(table_config, newparam);
-       newval = va_arg(ap, const char *);
-       if (!newparam || !newval) {
-               ast_log(LOG_WARNING, "LINE(%d): need at least one parameter to modify.\n", __LINE__);
-               return -1;
+       new_buffer = ldap_memrealloc(
+                       src->mod_values[0],
+                       strlen(src->mod_values[0]) + strlen(new_value) + sizeof(";"));
+
+       if (!new_buffer) {
+               ast_log(LOG_ERROR, "Memory allocation failure creating LDAP modification\n");
+               return NULL;
        }
 
-       mods_size = 2; /* one for the first param/value pair and one for the the terminating NULL */
-       ldap_mods = ast_calloc(sizeof(LDAPMod *), mods_size);
-       ldap_mods[0] = ast_calloc(1, sizeof(LDAPMod));
+       strcat(new_buffer, ";");
+       strcat(new_buffer, new_value);
 
-       ldap_mods[0]->mod_op = LDAP_MOD_REPLACE;
-       ldap_mods[0]->mod_type = ast_strdup(newparam);
+       src->mod_values[0] = new_buffer;
 
-       ldap_mods[0]->mod_values = ast_calloc(sizeof(char *), 2);
-       ldap_mods[0]->mod_values[0] = ast_strdup(newval);
+       return src;
+}
 
-       while ((newparam = va_arg(ap, const char *))) {
-               newparam = convert_attribute_name_to_ldap(table_config, newparam);
-               newval = va_arg(ap, const char *);
-               mod_exists = 0;
-
-               for (i = 0; i < mods_size - 1; i++) {
-                       if (ldap_mods[i]&& !strcmp(ldap_mods[i]->mod_type, newparam)) {
-                               /* We have the parameter allready, adding the value as a semicolon delimited value */
-                               ldap_mods[i]->mod_values[0] = ast_realloc(ldap_mods[i]->mod_values[0], sizeof(char) * (strlen(ldap_mods[i]->mod_values[0]) + strlen(newval) + 2));
-                               strcat(ldap_mods[i]->mod_values[0], ";");
-                               strcat(ldap_mods[i]->mod_values[0], newval);
-                               mod_exists = 1; 
-                               break;
-                       }
-               }
+/*!
+ * \internal
+ * \brief Duplicates an LDAP modification structure
+ *
+ * \param src the LDAPMod to duplicate
+ *
+ * \returns a deep copy of \c src if successful, NULL otherwise.
+ */
+static LDAPMod *ldap_mod_duplicate(const LDAPMod *src)
+{
+       LDAPMod *mod;
+       char *type, **values = NULL;
+
+       mod = ldap_memcalloc(1, sizeof(LDAPMod));
+       type = ldap_strdup(src->mod_type);
 
-               /* create new mod */
-               if (!mod_exists) {
-                       mods_size++;
-                       ldap_mods = ast_realloc(ldap_mods, sizeof(LDAPMod *) * mods_size);
-                       ldap_mods[mods_size - 1] = NULL;
-                       
-                       ldap_mods[mods_size - 2] = ast_calloc(1, sizeof(LDAPMod));
+       if (!(mod && type)) {
+               ast_log(LOG_ERROR, "Memory allocation failure creating LDAP modification\n");
+               ldap_memfree(type);
+               ldap_memfree(mod);
+               return NULL;
+       }
 
-                       ldap_mods[mods_size - 2]->mod_type = ast_calloc(sizeof(char), strlen(newparam) + 1);
-                       strcpy(ldap_mods[mods_size - 2]->mod_type, newparam);
+       if (src->mod_op == LDAP_MOD_REPLACE) {
+               char *value;
 
-                       if (strlen(newval) == 0) {
-                               ldap_mods[mods_size - 2]->mod_op = LDAP_MOD_DELETE;
-                       } else {
-                               ldap_mods[mods_size - 2]->mod_op = LDAP_MOD_REPLACE;
+               values = ldap_memcalloc(2, sizeof(char *));
+               value = ldap_strdup(src->mod_values[0]);
 
-                               ldap_mods[mods_size - 2]->mod_values = ast_calloc(sizeof(char *), 2);
-                               ldap_mods[mods_size - 2]->mod_values[0] = ast_calloc(sizeof(char), strlen(newval) + 1);
-                               strcpy(ldap_mods[mods_size - 2]->mod_values[0], newval);
-                       }
+               if (!(values && value)) {
+                       ast_log(LOG_ERROR, "Memory allocation failure creating LDAP modification\n");
+                       ldap_memfree(value);
+                       ldap_memfree(values);
+                       ldap_memfree(type);
+                       ldap_memfree(mod);
+                       return NULL;
                }
+
+               values[0] = value;
        }
-       /* freeing ldap_mods further down */
 
-       do {
-               /* freeing ldap_result further down */
-               result = ldap_search_ext_s(ldapConn, clean_basedn,
-                                 LDAP_SCOPE_SUBTREE, ast_str_buffer(filter), NULL, 0, NULL, NULL, NULL, LDAP_NO_LIMIT,
-                                 &ldap_result_msg);
-               if (result != LDAP_SUCCESS && is_ldap_connect_error(result)) {
-                       ast_log(LOG_WARNING, "Failed to query directory. Try %d/3\n", tries + 1);
-                       tries++;
-                       if (tries < 3) {
-                               usleep(500000L * tries);
-                               if (ldapConn) {
-                                       ldap_unbind_ext_s(ldapConn, NULL, NULL);
-                                       ldapConn = NULL;
-                               }
-                               if (!ldap_reconnect())
-                                       break;
-                       }
+       mod->mod_op = src->mod_op;
+       mod->mod_type = type;
+       mod->mod_values = values;
+       return mod;
+}
+
+/*!
+ * \internal
+ * \brief Search for an existing LDAP modification structure
+ *
+ * \param modifications a NULL terminated array of LDAP modification structures
+ * \param lookup the attribute name to search for
+ *
+ * \returns an LDAPMod * if successful, NULL otherwise.
+ */
+static LDAPMod *ldap_mod_find(LDAPMod **modifications, const char *lookup)
+{
+       size_t i;
+       for (i = 0; modifications[i]; i++) {
+               if (modifications[i]->mod_op == LDAP_MOD_REPLACE &&
+                       !strcasecmp(modifications[i]->mod_type, lookup)) {
+                       return modifications[i];
                }
-       } while (result != LDAP_SUCCESS && tries < 3 && is_ldap_connect_error(result));
+       }
+       return NULL;
+}
 
-       if (result != LDAP_SUCCESS) {
-               ast_log(LOG_WARNING, "Failed to query directory. Error: %s.\n", ldap_err2string(result));
-               ast_log(LOG_WARNING, "Query: %s\n", ast_str_buffer(filter));
+/*!
+ * \internal
+ * \brief Determine if an LDAP entry has the specified attribute
+ *
+ * \param entry the LDAP entry to examine
+ * \param lookup the attribute name to search for
+ *
+ * \returns 1 if the attribute was found, 0 otherwise.
+ */
+static int ldap_entry_has_attribute(LDAPMessage *entry, const char *lookup)
+{
+       BerElement *ber = NULL;
+       char *attribute;
 
-               ast_mutex_unlock(&ldap_lock);
-               free(filter);
-               free(clean_basedn);
-               ldap_msgfree(ldap_result_msg);
-               ldap_mods_free(ldap_mods, 0);
-               return -1;
+       attribute = ldap_first_attribute(ldapConn, entry, &ber);
+       while (attribute) {
+               if (!strcasecmp(attribute, lookup)) {
+                       ldap_memfree(attribute);
+                       ber_free(ber, 0);
+                       return 1;
+               }
+               ldap_memfree(attribute);
+               attribute = ldap_next_attribute(ldapConn, entry, ber);
        }
-       /* Ready to update */
-       if ((num_entries = ldap_count_entries(ldapConn, ldap_result_msg)) > 0) {
-               ast_debug(3, "LINE(%d) Modifying %s=%s hits: %d\n", __LINE__, attribute, lookup, num_entries);
-               for (i = 0; option_debug > 2 && i < mods_size - 1; i++) {
-                       if (ldap_mods[i]->mod_op != LDAP_MOD_DELETE) {
-                               ast_debug(3, "LINE(%d) %s=%s \n", __LINE__, ldap_mods[i]->mod_type, ldap_mods[i]->mod_values[0]);
-                       } else {
-                               ast_debug(3, "LINE(%d) deleting %s \n", __LINE__, ldap_mods[i]->mod_type);
-                       }
+       ber_free(ber, 0);
+       return 0;
+}
+
+/*!
+ * \internal
+ * \brief Remove LDAP_MOD_DELETE modifications that will not succeed
+ *
+ * \details
+ * A LDAP_MOD_DELETE operation will fail if the LDAP entry does not already have
+ * the corresponding attribute. Because we may be updating multiple LDAP entries
+ * in a single call to update_ldap(), we may need our own copy of the
+ * modifications array for each one.
+ *
+ * \note
+ * This function dynamically allocates memory. If it returns a non-NULL pointer,
+ * it is up to the caller to free it with ldap_mods_free()
+ *
+ * \returns an LDAPMod * if modifications needed to be removed, NULL otherwise.
+ */
+static LDAPMod **massage_mods_for_entry(LDAPMessage *entry, LDAPMod **mods)
+{
+       size_t k, i, remove_count;
+       LDAPMod **copies;
+
+       for (i = remove_count = 0; mods[i]; i++) {
+               if (mods[i]->mod_op == LDAP_MOD_DELETE
+                       && !ldap_entry_has_attribute(entry, mods[i]->mod_type)) {
+                       remove_count++;
                }
-               ldap_entry = ldap_first_entry(ldapConn, ldap_result_msg);
+       }
+
+       if (!remove_count) {
+               return NULL;
+       }
 
-               for (i = 0; ldap_entry; i++) { 
-                       dn = ldap_get_dn(ldapConn, ldap_entry);
-                       if ((error = ldap_modify_ext_s(ldapConn, dn, ldap_mods, NULL, NULL)) != LDAP_SUCCESS)  {
-                               ast_log(LOG_ERROR, "Couldn't modify '%s'='%s', dn:%s because %s\n",
-                                               attribute, lookup, dn, ldap_err2string(error));
+       copies = ldap_memcalloc(i - remove_count + 1, sizeof(LDAPMod *));
+       if (!copies) {
+               ast_log(LOG_ERROR, "Memory allocation failure massaging LDAP modification\n");
+               return NULL;
+       }
+
+       for (i = k = 0; mods[i]; i++) {
+               if (mods[i]->mod_op != LDAP_MOD_DELETE
+                       || ldap_entry_has_attribute(entry, mods[i]->mod_type)) {
+                       copies[k] = ldap_mod_duplicate(mods[i]);
+                       if (!copies[k]) {
+                               ast_log(LOG_ERROR, "Memory allocation failure massaging LDAP modification\n");
+                               ldap_mods_free(copies, 1);
+                               return NULL;
                        }
-                       ldap_memfree(dn);
-                       ldap_entry = ldap_next_entry(ldapConn, ldap_entry);
+                       k++;
+               } else {
+                       ast_debug(3, "Skipping %s deletion because it doesn't exist\n",
+                                       mods[i]->mod_type);
                }
        }
 
-       ast_mutex_unlock(&ldap_lock);
-       ast_free(filter);
-       ast_free(clean_basedn);
-       ldap_msgfree(ldap_result_msg);
-       ldap_mods_free(ldap_mods, 0);
-       return num_entries;
+       return copies;
 }
 
-static int update2_ldap(const char *basedn, const char *table_name, va_list ap)
+/*!
+ * \internal
+ * \brief Count the number of variables in an ast_variables list
+ *
+ * \param vars the list of variables to count
+ *
+ * \returns the number of variables in the specified list
+ */
+static size_t variables_count(const struct ast_variable *vars)
 {
-       int error = 0;
-       LDAPMessage *ldap_entry = NULL;
-       LDAPMod **ldap_mods;
-       const char *newparam = NULL;
-       const char *newval = NULL;
-       char *dn;
-       int num_entries = 0;
-       int i = 0;
-       int mods_size = 0;
-       int mod_exists = 0;
+       const struct ast_variable *var;
+       size_t count = 0;
+       for (var = vars; var; var = var->next) {
+               count++;
+       }
+       return count;
+}
+
+static int update2_ldap(const char *basedn, const char *table_name, const struct ast_variable *lookup_fields, const struct ast_variable *update_fields)
+{
+       const struct ast_variable *field;
        struct ldap_table_config *table_config = NULL;
        char *clean_basedn = NULL;
        struct ast_str *filter = NULL;
+       int search_result = 0;
+       int res = -1;
        int tries = 0;
-       int result = 0;
+       size_t update_count, update_index, entry_count;
+
+       LDAPMessage *ldap_entry = NULL;
+       LDAPMod **modifications;
        LDAPMessage *ldap_result_msg = NULL;
 
        if (!table_name) {
                ast_log(LOG_ERROR, "No table_name specified.\n");
-               return -1;
-       } 
+               return res;
+       }
 
-       if (!(filter = ast_str_create(80))) {
-               return -1;
+       update_count = variables_count(update_fields);
+       if (!update_count) {
+               ast_log(LOG_WARNING, "Need at least one parameter to modify.\n");
+               return res;
        }
 
        ast_mutex_lock(&ldap_lock);
@@ -1372,103 +1515,40 @@ static int update2_ldap(const char *basedn, const char *table_name, va_list ap)
        /* We now have our complete statement; Lets connect to the server and execute it.  */
        if (!ldap_reconnect()) {
                ast_mutex_unlock(&ldap_lock);
-               ast_free(filter);
-               return -1;
+               return res;
        }
 
        table_config = table_config_for_table_name(table_name);
        if (!table_config) {
                ast_log(LOG_ERROR, "No table named '%s'.\n", table_name);
                ast_mutex_unlock(&ldap_lock);
-               ast_free(filter);
-               return -1;
+               return res;
        }
 
        clean_basedn = cleaned_basedn(NULL, basedn);
 
-       /* Create the filter with the table additional filter and the parameter/value pairs we were given */
-       ast_str_append(&filter, 0, "(&");
-       if (table_config && table_config->additional_filter) {
-               ast_str_append(&filter, 0, "%s", table_config->additional_filter);
-       }
-       if (table_config != base_table_config && base_table_config
-               && base_table_config->additional_filter) {
-               ast_str_append(&filter, 0, "%s", base_table_config->additional_filter);
-       }
-
-       /* Get multiple lookup keyfields and values */
-       while ((newparam = va_arg(ap, const char *))) {
-               newval = va_arg(ap, const char *);
-               append_var_and_value_to_filter(&filter, table_config, newparam, newval);
-       }
-       ast_str_append(&filter, 0, ")");
-
-       /* Create the modification array with the parameter/value pairs we were given, 
-        * if there are several parameters with the same name, we collect them into 
-        * one parameter/value pair and delimit them with a semicolon */
-       newparam = va_arg(ap, const char *);
-       newparam = convert_attribute_name_to_ldap(table_config, newparam);
-       newval = va_arg(ap, const char *);
-       if (!newparam || !newval) {
-               ast_log(LOG_WARNING, "LINE(%d): need at least one parameter to modify.\n", __LINE__);
-               ast_free(filter);
+       filter = create_lookup_filter(table_config, lookup_fields);
+       if (!filter) {
+               ast_mutex_unlock(&ldap_lock);
                ast_free(clean_basedn);
-               return -1;
+               return res;
        }
 
-       mods_size = 2; /* one for the first param/value pair and one for the the terminating NULL */
-       ldap_mods = ast_calloc(sizeof(LDAPMod *), mods_size);
-       ldap_mods[0] = ast_calloc(1, sizeof(LDAPMod));
-
-       ldap_mods[0]->mod_op = LDAP_MOD_REPLACE;
-       ldap_mods[0]->mod_type = ast_calloc(sizeof(char), strlen(newparam) + 1);
-       strcpy(ldap_mods[0]->mod_type, newparam);
-
-       ldap_mods[0]->mod_values = ast_calloc(sizeof(char), 2);
-       ldap_mods[0]->mod_values[0] = ast_calloc(sizeof(char), strlen(newval) + 1);
-       strcpy(ldap_mods[0]->mod_values[0], newval);
-
-       while ((newparam = va_arg(ap, const char *))) {
-               newparam = convert_attribute_name_to_ldap(table_config, newparam);
-               newval = va_arg(ap, const char *);
-               mod_exists = 0;
-
-               for (i = 0; i < mods_size - 1; i++) {
-                       if (ldap_mods[i]&& !strcmp(ldap_mods[i]->mod_type, newparam)) {
-                               /* We have the parameter allready, adding the value as a semicolon delimited value */
-                               ldap_mods[i]->mod_values[0] = ast_realloc(ldap_mods[i]->mod_values[0], sizeof(char) * (strlen(ldap_mods[i]->mod_values[0]) + strlen(newval) + 2));
-                               strcat(ldap_mods[i]->mod_values[0], ";");
-                               strcat(ldap_mods[i]->mod_values[0], newval);
-                               mod_exists = 1; 
-                               break;
-                       }
-               }
-
-               /* create new mod */
-               if (!mod_exists) {
-                       mods_size++;
-                       ldap_mods = ast_realloc(ldap_mods, sizeof(LDAPMod *) * mods_size);
-                       ldap_mods[mods_size - 1] = NULL;
-                       ldap_mods[mods_size - 2] = ast_calloc(1, sizeof(LDAPMod));
-
-                       ldap_mods[mods_size - 2]->mod_op = LDAP_MOD_REPLACE;
-
-                       ldap_mods[mods_size - 2]->mod_type = ast_calloc(sizeof(char), strlen(newparam) + 1);
-                       strcpy(ldap_mods[mods_size - 2]->mod_type, newparam);
-
-                       ldap_mods[mods_size - 2]->mod_values = ast_calloc(sizeof(char *), 2);
-                       ldap_mods[mods_size - 2]->mod_values[0] = ast_calloc(sizeof(char), strlen(newval) + 1);
-                       strcpy(ldap_mods[mods_size - 2]->mod_values[0], newval);
-               }
-       }
-       /* freeing ldap_mods further down */
+       /*
+        * Find LDAP records that match our lookup filter. If there are none, then
+        * we don't go through the hassle of building our modifications list.
+        */
 
        do {
-               /* freeing ldap_result further down */
-               result = ldap_search_ext_s(ldapConn, clean_basedn,
-                                 LDAP_SCOPE_SUBTREE, ast_str_buffer(filter), NULL, 0, NULL, NULL, NULL, LDAP_NO_LIMIT,
-                                 &ldap_result_msg);
-               if (result != LDAP_SUCCESS && is_ldap_connect_error(result)) {
+               search_result = ldap_search_ext_s(
+                               ldapConn,
+                               clean_basedn,
+                               LDAP_SCOPE_SUBTREE,
+                               ast_str_buffer(filter),
+                               NULL, 0, NULL, NULL, NULL,
+                               LDAP_NO_LIMIT,
+                               &ldap_result_msg);
+               if (search_result != LDAP_SUCCESS && is_ldap_connect_error(search_result)) {
                        ast_log(LOG_WARNING, "Failed to query directory. Try %d/3\n", tries + 1);
                        tries++;
                        if (tries < 3) {
@@ -1482,47 +1562,128 @@ static int update2_ldap(const char *basedn, const char *table_name, va_list ap)
                                }
                        }
                }
-       } while (result != LDAP_SUCCESS && tries < 3 && is_ldap_connect_error(result));
+       } while (search_result != LDAP_SUCCESS && tries < 3 && is_ldap_connect_error(search_result));
 
-       if (result != LDAP_SUCCESS) {
-               ast_log(LOG_WARNING, "Failed to query directory. Error: %s.\n", ldap_err2string(result));
+       if (search_result != LDAP_SUCCESS) {
+               ast_log(LOG_WARNING, "Failed to query directory. Error: %s.\n", ldap_err2string(search_result));
                ast_log(LOG_WARNING, "Query: %s\n", ast_str_buffer(filter));
+               goto early_bailout;
+       }
 
-               ast_mutex_unlock(&ldap_lock);
-               ast_free(filter);
-               ast_free(clean_basedn);
-               ldap_msgfree(ldap_result_msg);
-               ldap_mods_free(ldap_mods, 0);
-               return -1;
+       entry_count = ldap_count_entries(ldapConn, ldap_result_msg);
+       if (!entry_count) {
+               /* Nothing found, nothing to update */
+               res = 0;
+               goto early_bailout;
        }
-       /* Ready to update */
-       if ((num_entries = ldap_count_entries(ldapConn, ldap_result_msg)) > 0) {
-               for (i = 0; option_debug > 2 && i < mods_size - 1; i++) {
-                       ast_debug(3, "LINE(%d) %s=%s \n", __LINE__, ldap_mods[i]->mod_type, ldap_mods[i]->mod_values[0]);
-               }
 
-               ldap_entry = ldap_first_entry(ldapConn, ldap_result_msg);
+       /* We need to NULL terminate, so we allocate one more than we need */
+       modifications = ldap_memcalloc(update_count + 1, sizeof(LDAPMod *));
+       if (!modifications) {
+               ast_log(LOG_ERROR, "Memory allocation failure\n");
+               goto early_bailout;
+       }
 
-               for (i = 0; ldap_entry; i++) { 
-                       dn = ldap_get_dn(ldapConn, ldap_entry);
-                       if ((error = ldap_modify_ext_s(ldapConn, dn, ldap_mods, NULL, NULL)) != LDAP_SUCCESS)  {
-                               ast_log(LOG_ERROR, "Couldn't modify dn:%s because %s", dn, ldap_err2string(error));
+       /*
+        * Create the modification array with the parameter/value pairs we were given,
+        * if there are several parameters with the same name, we collect them into
+        * one parameter/value pair and delimit them with a semicolon
+        */
+       for (field = update_fields, update_index = 0; field; field = field->next) {
+               LDAPMod *mod;
+
+               const char *ldap_attribute_name = convert_attribute_name_to_ldap(
+                               table_config,
+                               field->name);
+
+               /* See if we already have it */
+               mod = ldap_mod_find(modifications, ldap_attribute_name);
+               if (mod) {
+                       mod = ldap_mod_append(mod, field->value);
+                       if (!mod) {
+                               goto late_bailout;
                        }
-                       ldap_memfree(dn);
-                       ldap_entry = ldap_next_entry(ldapConn, ldap_entry);
+               } else {
+                       mod = ldap_mod_create(ldap_attribute_name, field->value);
+                       if (!mod) {
+                               goto late_bailout;
+                       }
+                       modifications[update_index++] = mod;
                }
        }
 
-       ast_mutex_unlock(&ldap_lock);
-       if (filter) {
-               ast_free(filter);
+       /* Ready to update */
+       ast_debug(3, "Modifying %zu matched entries\n", entry_count);
+       if (option_debug > 2) {
+               size_t i;
+               for (i = 0; modifications[i]; i++) {
+                       if (modifications[i]->mod_op != LDAP_MOD_DELETE) {
+                               ast_debug(3, "%s => %s\n", modifications[i]->mod_type,
+                                               modifications[i]->mod_values[0]);
+                       } else {
+                               ast_debug(3, "deleting %s\n", modifications[i]->mod_type);
+                       }
+               }
        }
-       if (clean_basedn) {
-               ast_free(clean_basedn);
+
+       for (ldap_entry = ldap_first_entry(ldapConn, ldap_result_msg);
+               ldap_entry;
+               ldap_entry = ldap_next_entry(ldapConn, ldap_entry)) {
+               int error;
+               LDAPMod **massaged, **working;
+
+               char *dn = ldap_get_dn(ldapConn, ldap_entry);
+               if (!dn) {
+                       ast_log(LOG_ERROR, "Memory allocation failure\n");
+                       goto late_bailout;
+               }
+
+               working = modifications;
+
+               massaged = massage_mods_for_entry(ldap_entry, modifications);
+               if (massaged) {
+                       /* Did we massage everything out of the list? */
+                       if (!massaged[0]) {
+                               ast_debug(3, "Nothing left to modify - skipping\n");
+                               ldap_mods_free(massaged, 1);
+                               ldap_memfree(dn);
+                               continue;
+                       }
+                       working = massaged;
+               }
+
+               if ((error = ldap_modify_ext_s(ldapConn, dn, working, NULL, NULL)) != LDAP_SUCCESS)  {
+                       ast_log(LOG_ERROR, "Couldn't modify dn:%s because %s", dn, ldap_err2string(error));
+               }
+
+               if (massaged) {
+                       ldap_mods_free(massaged, 1);
+               }
+
+               ldap_memfree(dn);
        }
+
+       res = entry_count;
+
+late_bailout:
+       ldap_mods_free(modifications, 1);
+
+early_bailout:
        ldap_msgfree(ldap_result_msg);
-       ldap_mods_free(ldap_mods, 0);
-       return num_entries;
+       ast_free(filter);
+       ast_free(clean_basedn);
+       ast_mutex_unlock(&ldap_lock);
+
+       return res;
+}
+
+static int update_ldap(const char *basedn, const char *table_name, const char *attribute, const char *lookup, const struct ast_variable *fields)
+{
+       int res;
+       struct ast_variable *lookup_fields = ast_variable_new(attribute, lookup, "");
+       res = update2_ldap(basedn, table_name, lookup_fields, fields);
+       ast_variables_destroy(lookup_fields);
+       return res;
 }
 
 static struct ast_config_engine ldap_engine = {
@@ -1534,6 +1695,19 @@ static struct ast_config_engine ldap_engine = {
        .update2_func = update2_ldap,
 };
 
+/*!
+ * \brief Load the module
+ *
+ * Module loading including tests for configuration or dependencies.
+ * This function can return AST_MODULE_LOAD_FAILURE, AST_MODULE_LOAD_DECLINE,
+ * or AST_MODULE_LOAD_SUCCESS. If a dependency or environment variable fails
+ * tests return AST_MODULE_LOAD_FAILURE. If the module can not load the 
+ * configuration file or other non-critical problem return 
+ * AST_MODULE_LOAD_DECLINE. On success return AST_MODULE_LOAD_SUCCESS.
+ *
+ * \todo Don't error or warn on a default install. If the config is
+ * default we should not attempt to connect to a server. -lathama
+ */
 static int load_module(void)
 {
        if (parse_config() < 0) {
@@ -1556,6 +1730,9 @@ static int load_module(void)
        return 0;
 }
 
+/*! \brief Unload Module
+ *
+ */
 static int unload_module(void)
 {
        /* Aquire control before doing anything to the module itself. */
@@ -1577,6 +1754,8 @@ static int unload_module(void)
        return 0;
 }
 
+/*! \breif Relod Module
+ */
 static int reload(void)
 {
        /* Aquire control before doing anything to the module itself. */
@@ -1605,7 +1784,23 @@ static int reload(void)
        return 0;
 }
 
-/*! \brief parse the configuration file */
+static int config_can_be_inherited(const char *key)
+{
+       int i;
+       static const char * const config[] = {
+               "basedn", "host", "pass", "port", "protocol", "url", "user", "version", NULL
+       };
+
+       for (i = 0; config[i]; i++) {
+               if (!strcasecmp(key, config[i])) {
+                       return 0;
+               }
+       }
+       return 1;
+}
+
+/*! \brief parse the configuration file
+ */
 static int parse_config(void)
 {
        struct ast_config *config;
@@ -1694,7 +1889,9 @@ static int parse_config(void)
                                if (!strcasecmp(var->name, "additionalFilter")) {
                                        table_config->additional_filter = ast_strdup(var->value);
                                } else {
-                                       ldap_table_config_add_attribute(table_config, var->name, var->value);
+                                       if (!is_general || config_can_be_inherited(var->name)) {
+                                               ldap_table_config_add_attribute(table_config, var->name, var->value);
+                                       }
                                }
                        }
                }
@@ -1753,9 +1950,14 @@ static int ldap_reconnect(void)
        }
 }
 
+/*! \brief Realtime Status
+ *
+ */
 static char *realtime_ldap_status(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
 {
-       char status[256], credentials[100] = "";
+       char status[256];
+       char credentials[100] = "";
+       char buf[362]; /* 256+100+" for "+NULL */
        int ctimesec = time(NULL) - connect_time;
 
        switch (cmd) {
@@ -1778,30 +1980,17 @@ static char *realtime_ldap_status(struct ast_cli_entry *e, int cmd, struct ast_c
        if (!ast_strlen_zero(user))
                snprintf(credentials, sizeof(credentials), " with username %s", user);
 
-       if (ctimesec > 31536000) {
-               ast_cli(a->fd, "%s%s for %d years, %d days, %d hours, %d minutes, %d seconds.\n",
-                               status, credentials, ctimesec / 31536000,
-                               (ctimesec % 31536000) / 86400, (ctimesec % 86400) / 3600,
-                               (ctimesec % 3600) / 60, ctimesec % 60);
-       } else if (ctimesec > 86400) {
-               ast_cli(a->fd, "%s%s for %d days, %d hours, %d minutes, %d seconds.\n",
-                               status, credentials, ctimesec / 86400, (ctimesec % 86400) / 3600,
-                               (ctimesec % 3600) / 60, ctimesec % 60);
-       } else if (ctimesec > 3600) {
-               ast_cli(a->fd, "%s%s for %d hours, %d minutes, %d seconds.\n",
-                               status, credentials, ctimesec / 3600, (ctimesec % 3600) / 60,
-                               ctimesec % 60);
-       } else if (ctimesec > 60) {
-               ast_cli(a->fd, "%s%s for %d minutes, %d seconds.\n", status, credentials,
-                                       ctimesec / 60, ctimesec % 60);
-       } else {
-               ast_cli(a->fd, "%s%s for %d seconds.\n", status, credentials, ctimesec);
-       }
+       snprintf(buf, sizeof(buf), "%s%s for ", status, credentials);
+       ast_cli_print_timestr_fromseconds(a->fd, ctimesec, buf);
 
        return CLI_SUCCESS;
 }
 
+/*! \brief Module Information
+ *
+ */
 AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_LOAD_ORDER, "LDAP realtime interface",
+       .support_level = AST_MODULE_SUPPORT_EXTENDED,
        .load = load_module,
        .unload = unload_module,
        .reload = reload,