res_phoneprov: Refactor phoneprov to allow pluggable config providers
authorGeorge Joseph <george.joseph@fairview5.com>
Thu, 9 Oct 2014 17:46:23 +0000 (17:46 +0000)
committerGeorge Joseph <george.joseph@fairview5.com>
Thu, 9 Oct 2014 17:46:23 +0000 (17:46 +0000)
This patch makes res_phoneprov more modular so other modules (like pjsip)
can provide configuration information instead of res_phoneprov relying solely
on users.conf and sip.conf.  To accomplish this a new ast_phoneprov public API
is now exposed which allows config providers to register themselves, set
defaults (server profile, etc) and add user extensions.

* ast_phoneprov_provider_register registers the provider and provides callbacks
  for loading default settings and loading users.
* ast_phoneprov_provider_unregister clears the defaults and users.
* ast_phoneprov_add_extension should be called once for each user/extension
  by the provider's load_users callback to add them.
* ast_phoneprov_delete_extension deletes one extension.
* ast_phoneprov_delete_extensions deletes all extensions for the provider.

Tested-by: George Joseph
Review: https://reviewboard.asterisk.org/r/3970/
........

Merged revisions 424963 from http://svn.asterisk.org/svn/asterisk/branches/12
........

Merged revisions 424964 from http://svn.asterisk.org/svn/asterisk/branches/13

git-svn-id: https://origsvn.digium.com/svn/asterisk/trunk@424965 65c4cc65-6c06-0410-ace0-fbb531ad65f3

configs/samples/phoneprov.conf.sample
include/asterisk/chanvars.h
include/asterisk/phoneprov.h [new file with mode: 0644]
main/chanvars.c
res/res_phoneprov.c
res/res_phoneprov.exports.in [new file with mode: 0644]

index 17d8b1f..7d14013 100644 (file)
@@ -1,4 +1,7 @@
 [general]
+; This section applies only to the default sip.conf/users.conf config provider
+; embedded in res_phoneprov.  Other providers may provide their own default settings.
+
 ; The default behavior of res_phoneprov will be to set the SERVER template variable to
 ; the IP address that the phone uses to contact the provisioning server and the
 ; SERVER_PORT variable to the bindport setting in sip.conf.  Unless you have a very
@@ -15,7 +18,8 @@ default_profile=polycom ; The default profile to use if none specified in users.
 ; with the provisioning server.  You can define either static files, or dynamically
 ; generated files that can have dynamic names and point to templates that variables
 ; can be substituted into.  You can also set arbitrary variables for the profiles
-; templates to have access to.  Example:
+; templates to have access to.  Profiles are shared across all config providers.
+; Example:
 
 ;[example]
 ;mime_type => application/octet-stream
@@ -25,7 +29,9 @@ default_profile=polycom ; The default profile to use if none specified in users.
 ;setvar => DB_CIDNAME=${ODBC_CID_NAME_LOOKUP(${USERNAME})}
 
 ; Dynamically generated files have a filename registered with variable substitution
-; with variables obtained while reading users.conf.
+; with variables obtained from various config providers.  The default provider
+; embedded in res_phoneprov reads users.conf.  Other providers will have their own
+; sources for the variables and may provide additional variables not listed here.
 
 ; Built in variables and the options in users.conf that they come from
 ;   MAC (macaddress)
index 7ebc64a..3693e2a 100644 (file)
@@ -33,6 +33,8 @@ struct ast_var_t {
 
 AST_LIST_HEAD_NOLOCK(varshead, ast_var_t);
 
+struct varshead *ast_var_list_create(void);
+void ast_var_list_destroy(struct varshead *head);
 #ifdef MALLOC_DEBUG
 struct ast_var_t *_ast_var_assign(const char *name, const char *value, const char *file, int lineno, const char *function);
 #define ast_var_assign(a,b)    _ast_var_assign(a,b,__FILE__,__LINE__,__PRETTY_FUNCTION__)
@@ -43,5 +45,21 @@ void ast_var_delete(struct ast_var_t *var);
 const char *ast_var_name(const struct ast_var_t *var);
 const char *ast_var_full_name(const struct ast_var_t *var);
 const char *ast_var_value(const struct ast_var_t *var);
+char *ast_var_find(const struct varshead *head, const char *name);
+struct varshead *ast_var_list_clone(struct varshead *head);
+
+#define AST_VAR_LIST_TRAVERSE(head, var) AST_LIST_TRAVERSE(head, var, entries)
+
+static inline void AST_VAR_LIST_INSERT_TAIL(struct varshead *head, struct ast_var_t *var) {
+       if (var) {
+               AST_LIST_INSERT_TAIL(head, var, entries);
+       }
+}
+
+static inline void AST_VAR_LIST_INSERT_HEAD(struct varshead *head, struct ast_var_t *var) {
+       if (var) {
+               AST_LIST_INSERT_HEAD(head, var, entries);
+       }
+}
 
 #endif /* _ASTERISK_CHANVARS_H */
diff --git a/include/asterisk/phoneprov.h b/include/asterisk/phoneprov.h
new file mode 100644 (file)
index 0000000..364a574
--- /dev/null
@@ -0,0 +1,119 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2014 - Fairview 5 Engineering, LLC
+ *
+ * George Joseph <george.joseph@fairview5.com>
+ *
+ * See http://www.asterisk.org for more information about
+ * the Asterisk project. Please do not directly contact
+ * any of the maintainers of this project for assistance;
+ * the project provides a web site, mailing lists and IRC
+ * channels for your use.
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE file
+ * at the top of the source tree.
+ */
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#ifndef _ASTERISK_PHONEPROV_H
+#define _ASTERISK_PHONEPROV_H
+
+#include "asterisk.h"
+#include "asterisk/inline_api.h"
+
+enum ast_phoneprov_std_variables {
+       AST_PHONEPROV_STD_MAC = 0,
+       AST_PHONEPROV_STD_PROFILE,
+       AST_PHONEPROV_STD_USERNAME,
+       AST_PHONEPROV_STD_DISPLAY_NAME,
+       AST_PHONEPROV_STD_SECRET,
+       AST_PHONEPROV_STD_LABEL,
+       AST_PHONEPROV_STD_CALLERID,
+       AST_PHONEPROV_STD_TIMEZONE,
+       AST_PHONEPROV_STD_LINENUMBER,
+       AST_PHONEPROV_STD_LINEKEYS,
+       AST_PHONEPROV_STD_SERVER,
+       AST_PHONEPROV_STD_SERVER_PORT,
+       AST_PHONEPROV_STD_SERVER_IFACE,
+       AST_PHONEPROV_STD_VOICEMAIL_EXTEN,
+       AST_PHONEPROV_STD_EXTENSION_LENGTH,
+       AST_PHONEPROV_STD_TZOFFSET,
+       AST_PHONEPROV_STD_DST_ENABLE,
+       AST_PHONEPROV_STD_DST_START_MONTH,
+       AST_PHONEPROV_STD_DST_START_MDAY,
+       AST_PHONEPROV_STD_DST_START_HOUR,
+       AST_PHONEPROV_STD_DST_END_MONTH,
+       AST_PHONEPROV_STD_DST_END_MDAY,
+       AST_PHONEPROV_STD_DST_END_HOUR,
+       AST_PHONEPROV_STD_VAR_LIST_LENGTH,      /* This entry must always be the last in the list */
+};
+
+/*! \brief Lookup table for the standard phoneprov variable names */
+extern const char *ast_phoneprov_std_variable_lookup[];
+
+/*!
+ * \brief Causes the provider to load its users.
+ *
+ * This function is called by phoneprov in response to a
+ * ast_phoneprov_provider_register call by the provider.
+ * It may also be called by phoneprov to request a reload in
+ * response to the res_phoneprov module being reloaded.
+ *
+ * \retval 0 if successful
+ * \retval non-zero if failure
+ */
+typedef int(*ast_phoneprov_load_users_cb)(void);
+
+/*!
+ * \brief Registers a config provider to phoneprov.
+ * \param provider_name The name of the provider
+ * \param load_users Callback that gathers user variables then loads them by
+ * calling ast_phoneprov_add_extension once for each extension.
+ *
+ * \retval 0 if successful
+ * \retval non-zero if failure
+ */
+int ast_phoneprov_provider_register(char *provider_name,
+       ast_phoneprov_load_users_cb load_users);
+
+/*!
+ * \brief Unegisters a config provider from phoneprov and frees its resources.
+ * \param provider_name The name of the provider
+ */
+void ast_phoneprov_provider_unregister(char *provider_name);
+
+/*!
+ * \brief Adds an extension
+ * \param provider_name The name of the provider
+ * \param defaults An ast_vat_t linked list of the extension's variables.
+ * The list is automatically cloned and it must contain at least MACADDRESS
+ * and USERNAME entries.
+ *
+ * \retval 0 if successful
+ * \retval non-zero if failure
+ */
+int ast_phoneprov_add_extension(char *provider_name, struct varshead *vars);
+
+/*!
+ * \brief Deletes an extension
+ * \param provider_name The name of the provider
+ * \param macaddress The mac address of the extension
+ */
+void ast_phoneprov_delete_extension(char *provider_name, char *macaddress);
+
+/*!
+ * \brief Deletes all extensions for this provider
+ * \param provider_name The name of the provider
+ */
+void ast_phoneprov_delete_extensions(char *provider_name);
+
+#endif /* _ASTERISK_PHONEPROV_H */
+
+#ifdef __cplusplus
+}
+#endif
index da7449d..37714e9 100644 (file)
@@ -90,4 +90,67 @@ const char *ast_var_value(const struct ast_var_t *var)
        return (var ? var->value : NULL);
 }
 
+char *ast_var_find(const struct varshead *head, const char *name)
+{
+       struct ast_var_t *var;
+
+       AST_LIST_TRAVERSE(head, var, entries) {
+               if (!strcmp(name, var->name)) {
+                       return var->value;
+               }
+       }
+       return NULL;
+}
+
+struct varshead *ast_var_list_create(void)
+{
+       struct varshead *head;
+
+       head = ast_calloc(1, sizeof(*head));
+       if (!head) {
+               return NULL;
+       }
+       AST_LIST_HEAD_INIT_NOLOCK(head);
+       return head;
+}
+
+void ast_var_list_destroy(struct varshead *head)
+{
+       struct ast_var_t *var;
+
+       if (!head) {
+               return;
+       }
 
+       while ((var = AST_LIST_REMOVE_HEAD(head, entries))) {
+               ast_var_delete(var);
+       }
+
+       ast_free(head);
+}
+
+struct varshead *ast_var_list_clone(struct varshead *head)
+{
+       struct varshead *clone;
+       struct ast_var_t *var, *newvar;
+
+       if (!head) {
+               return NULL;
+       }
+
+       clone = ast_var_list_create();
+       if (!clone) {
+               return NULL;
+       }
+
+       AST_VAR_LIST_TRAVERSE(head, var) {
+               newvar = ast_var_assign(var->name, var->value);
+               if (!newvar) {
+                       ast_var_list_destroy(clone);
+                       return NULL;
+               }
+               AST_VAR_LIST_INSERT_TAIL(clone, newvar);
+       }
+
+       return clone;
+}
index b8b5278..8f71c26 100644 (file)
@@ -2,10 +2,12 @@
  * Asterisk -- An open source telephony toolkit.
  *
  * Copyright (C) 1999 - 2008, Digium, Inc.
+ * Copyright (C) 2014, Fairview 5 Engineering, LLC
  *
  * Mark Spencer <markster@digium.com>
  * Matthew Brooks <mbrooks@digium.com>
  * Terry Wilson <twilson@digium.com>
+ * George Joseph <george.joseph@fairview5.com>
  *
  * See http://www.asterisk.org for more information about
  * the Asterisk project. Please do not directly contact
@@ -24,7 +26,8 @@
  *
  * \author Matthew Brooks <mbrooks@digium.com>
  * \author Terry Wilson <twilson@digium.com>
- */
+ * \author George Joseph <george.joseph@fairview5.com>
+  */
 
 /*! \li \ref res_phoneprov.c uses the configuration file \ref phoneprov.conf and \ref users.conf and \ref sip.conf
  * \addtogroup configuration_file Configuration Files
@@ -39,6 +42,8 @@
        <support_level>extended</support_level>
  ***/
 
+#define AST_API_MODULE
+
 #include "asterisk.h"
 
 #include <sys/ioctl.h>
@@ -65,12 +70,15 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
 #include "asterisk/acl.h"
 #include "asterisk/astobj2.h"
 #include "asterisk/ast_version.h"
+#include "asterisk/phoneprov.h"
 
 #ifdef LOW_MEMORY
+#define MAX_PROVIDER_BUCKETS 1
 #define MAX_PROFILE_BUCKETS 1
 #define MAX_ROUTE_BUCKETS 1
 #define MAX_USER_BUCKETS 1
 #else
+#define MAX_PROVIDER_BUCKETS 17
 #define MAX_PROFILE_BUCKETS 17
 #define MAX_ROUTE_BUCKETS 563
 #define MAX_USER_BUCKETS 563
@@ -85,7 +93,7 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
                </synopsis>
                <syntax>
                        <parameter name="mac" required="true" />
-                       <parameter name="template" required="true" />
+                       <parameter name="template_file" required="true" />
                </syntax>
                <description>
                        <para>Output the specified template for each extension associated with the specified MAC address.</para>
@@ -109,41 +117,162 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
        </function>
  ***/
 
-/*! \brief for use in lookup_iface */
-static struct in_addr __ourip = { .s_addr = 0x00000000, };
+/*!
+ * \brief Creates a hash function for a structure string field.
+ * \param fname The name to use for the function
+ * \param stype The structure type
+ * \param field The field in the structure to hash
+ *
+ * SIMPLE_HASH_FN(mystruct, myfield) will produce a function
+ * named mystruct_hash_fn which hashes mystruct->myfield.
+ */
+#define SIMPLE_HASH_FN(fname, stype, field) \
+static int fname(const void *obj, const int flags) \
+{ \
+       const struct stype *provider = obj; \
+       const char *key; \
+       switch (flags & OBJ_SEARCH_MASK) { \
+       case OBJ_SEARCH_KEY: \
+               key = obj; \
+               break; \
+       case OBJ_SEARCH_OBJECT: \
+               provider = obj; \
+               key = provider->field; \
+               break; \
+       default: \
+               ast_assert(0); \
+               return 0; \
+       } \
+       return ast_str_hash(key); \
+}
+
+/*!
+ * \brief Creates a compare function for a structure string field.
+ * \param fname The name to use for the function
+ * \param stype The structure type
+ * \param field The field in the structure to compare
+ *
+ * SIMPLE_CMP_FN(mystruct, myfield) will produce a function
+ * named mystruct_cmp_fn which compares mystruct->myfield.
+ */
+#define SIMPLE_CMP_FN(fname, stype, field) \
+static int fname(void *obj, void *arg, int flags) \
+{ \
+       const struct stype *object_left = obj, *object_right = arg; \
+       const char *right_key = arg; \
+       int cmp; \
+       switch (flags & OBJ_SEARCH_MASK) { \
+       case OBJ_SEARCH_OBJECT: \
+               right_key = object_right->field; \
+       case OBJ_SEARCH_KEY: \
+               cmp = strcmp(object_left->field, right_key); \
+               break; \
+       case OBJ_SEARCH_PARTIAL_KEY: \
+               cmp = strncmp(object_left->field, right_key, strlen(right_key)); \
+               break; \
+       default: \
+               cmp = 0; \
+               break; \
+       } \
+       if (cmp) { \
+               return 0; \
+       } \
+       return CMP_MATCH; \
+}
+
+const char *ast_phoneprov_std_variable_lookup[] = {
+       [AST_PHONEPROV_STD_MAC] = "MAC",
+       [AST_PHONEPROV_STD_PROFILE] = "PROFILE",
+       [AST_PHONEPROV_STD_USERNAME] = "USERNAME",
+       [AST_PHONEPROV_STD_DISPLAY_NAME] = "DISPLAY_NAME",
+       [AST_PHONEPROV_STD_SECRET] = "SECRET",
+       [AST_PHONEPROV_STD_LABEL] = "LABEL",
+       [AST_PHONEPROV_STD_CALLERID] = "CALLERID",
+       [AST_PHONEPROV_STD_TIMEZONE] = "TIMEZONE",
+       [AST_PHONEPROV_STD_LINENUMBER] = "LINE",
+       [AST_PHONEPROV_STD_LINEKEYS] = "LINEKEYS",
+       [AST_PHONEPROV_STD_SERVER] = "SERVER",
+       [AST_PHONEPROV_STD_SERVER_PORT] = "SERVER_PORT",
+       [AST_PHONEPROV_STD_SERVER_IFACE] = "SERVER_IFACE",
+       [AST_PHONEPROV_STD_VOICEMAIL_EXTEN] = "VOICEMAIL_EXTEN",
+       [AST_PHONEPROV_STD_EXTENSION_LENGTH] = "EXTENSION_LENGTH",
+       [AST_PHONEPROV_STD_TZOFFSET] = "TZOFFSET",
+       [AST_PHONEPROV_STD_DST_ENABLE] = "DST_ENABLE",
+       [AST_PHONEPROV_STD_DST_START_MONTH] = "DST_START_MONTH",
+       [AST_PHONEPROV_STD_DST_START_MDAY] = "DST_START_MDAY",
+       [AST_PHONEPROV_STD_DST_START_HOUR] = "DST_START_HOUR",
+       [AST_PHONEPROV_STD_DST_END_MONTH] = "DST_END_MONTH",
+       [AST_PHONEPROV_STD_DST_END_MDAY] = "DST_END_MDAY",
+       [AST_PHONEPROV_STD_DST_END_HOUR] = "DST_END_HOUR",
+};
+
+/* Translate the standard variables to their users.conf equivalents. */
+const char *pp_user_lookup[] = {
+       [AST_PHONEPROV_STD_MAC] = "macaddress",
+       [AST_PHONEPROV_STD_PROFILE] = "profile",
+       [AST_PHONEPROV_STD_USERNAME] = "username",
+       [AST_PHONEPROV_STD_DISPLAY_NAME] = "fullname",
+       [AST_PHONEPROV_STD_SECRET] = "secret",
+       [AST_PHONEPROV_STD_LABEL] = "label",
+       [AST_PHONEPROV_STD_CALLERID] = "cid_number",
+       [AST_PHONEPROV_STD_TIMEZONE] = "timezone",
+       [AST_PHONEPROV_STD_LINENUMBER] = "linenumber",
+       [AST_PHONEPROV_STD_LINEKEYS] = "linekeys",
+       [AST_PHONEPROV_STD_SERVER] = NULL,
+       [AST_PHONEPROV_STD_SERVER_PORT] = NULL,
+       [AST_PHONEPROV_STD_SERVER_IFACE] = NULL,
+       [AST_PHONEPROV_STD_VOICEMAIL_EXTEN] = "vmexten",
+       [AST_PHONEPROV_STD_EXTENSION_LENGTH] = "localextenlength",
+       [AST_PHONEPROV_STD_TZOFFSET] = NULL,
+       [AST_PHONEPROV_STD_DST_ENABLE] = NULL,
+       [AST_PHONEPROV_STD_DST_START_MONTH] = NULL,
+       [AST_PHONEPROV_STD_DST_START_MDAY] = NULL,
+       [AST_PHONEPROV_STD_DST_START_HOUR] = NULL,
+       [AST_PHONEPROV_STD_DST_END_MONTH] = NULL,
+       [AST_PHONEPROV_STD_DST_END_MDAY] = NULL,
+       [AST_PHONEPROV_STD_DST_END_HOUR] = NULL,
+};
 
-/* \note This enum and the pp_variable_list must be in the same order or
- * bad things happen! */
-enum pp_variables {
-       PP_MACADDRESS,
-       PP_USERNAME,
-       PP_FULLNAME,
-       PP_SECRET,
-       PP_LABEL,
-       PP_CALLERID,
-       PP_TIMEZONE,
-       PP_LINENUMBER,
-       PP_LINEKEYS,
-       PP_VAR_LIST_LENGTH,     /* This entry must always be the last in the list */
+/* Translate the standard variables to their phoneprov.conf [general] equivalents. */
+const char *pp_general_lookup[] = {
+       [AST_PHONEPROV_STD_MAC] = NULL,
+       [AST_PHONEPROV_STD_PROFILE] = "default_profile",
+       [AST_PHONEPROV_STD_USERNAME] = NULL,
+       [AST_PHONEPROV_STD_DISPLAY_NAME] = NULL,
+       [AST_PHONEPROV_STD_SECRET] = NULL,
+       [AST_PHONEPROV_STD_LABEL] = NULL,
+       [AST_PHONEPROV_STD_CALLERID] = NULL,
+       [AST_PHONEPROV_STD_TIMEZONE] = NULL,
+       [AST_PHONEPROV_STD_LINENUMBER] = NULL,
+       [AST_PHONEPROV_STD_LINEKEYS] = NULL,
+       [AST_PHONEPROV_STD_SERVER] = "serveraddr",
+       [AST_PHONEPROV_STD_SERVER_PORT] = "serverport",
+       [AST_PHONEPROV_STD_SERVER_IFACE] = "serveriface",
+       [AST_PHONEPROV_STD_VOICEMAIL_EXTEN] = NULL,
+       [AST_PHONEPROV_STD_EXTENSION_LENGTH] = NULL,
+       [AST_PHONEPROV_STD_TZOFFSET] = NULL,
+       [AST_PHONEPROV_STD_DST_ENABLE] = NULL,
+       [AST_PHONEPROV_STD_DST_START_MONTH] = NULL,
+       [AST_PHONEPROV_STD_DST_START_MDAY] = NULL,
+       [AST_PHONEPROV_STD_DST_START_HOUR] = NULL,
+       [AST_PHONEPROV_STD_DST_END_MONTH] = NULL,
+       [AST_PHONEPROV_STD_DST_END_MDAY] = NULL,
+       [AST_PHONEPROV_STD_DST_END_HOUR] = NULL,
 };
 
-/*! \brief Lookup table to translate between users.conf property names and
- * variables for use in phoneprov templates */
-static const struct pp_variable_lookup {
-       enum pp_variables id;
-       const char * const user_var;
-       const char * const template_var;
-} pp_variable_list[] = {
-       { PP_MACADDRESS, "macaddress", "MAC" },
-       { PP_USERNAME, "username", "USERNAME" },
-       { PP_FULLNAME, "fullname", "DISPLAY_NAME" },
-       { PP_SECRET, "secret", "SECRET" },
-       { PP_LABEL, "label", "LABEL" },
-       { PP_CALLERID, "cid_number", "CALLERID" },
-       { PP_TIMEZONE, "timezone", "TIMEZONE" },
-       { PP_LINENUMBER, "linenumber", "LINE" },
-       { PP_LINEKEYS, "linekeys", "LINEKEYS" },
+/*! \brief for use in lookup_iface */
+static struct in_addr __ourip = { .s_addr = 0x00000000, };
+
+/*! \brief structure to hold config providers */
+struct phoneprov_provider {
+       AST_DECLARE_STRING_FIELDS(
+               AST_STRING_FIELD(provider_name);
+       );
+       ast_phoneprov_load_users_cb load_users;
 };
+struct ao2_container *providers;
+SIMPLE_HASH_FN(phoneprov_provider_hash_fn, phoneprov_provider, provider_name)
+SIMPLE_CMP_FN(phoneprov_provider_cmp_fn, phoneprov_provider, provider_name)
 
 /*! \brief structure to hold file data */
 struct phoneprov_file {
@@ -155,6 +284,16 @@ struct phoneprov_file {
        AST_LIST_ENTRY(phoneprov_file) entry;
 };
 
+/*! \brief structure to hold extensions */
+struct extension {
+       AST_DECLARE_STRING_FIELDS(
+               AST_STRING_FIELD(name);
+       );
+       int index;
+       struct varshead *headp; /*!< List of variables to substitute into templates */
+       AST_LIST_ENTRY(extension) entry;
+};
+
 /*! \brief structure to hold phone profiles read from phoneprov.conf */
 struct phone_profile {
        AST_DECLARE_STRING_FIELDS(
@@ -166,24 +305,22 @@ struct phone_profile {
        AST_LIST_HEAD_NOLOCK(, phoneprov_file) static_files;    /*!< List of static files */
        AST_LIST_HEAD_NOLOCK(, phoneprov_file) dynamic_files;   /*!< List of dynamic files */
 };
-
-struct extension {
-       AST_DECLARE_STRING_FIELDS(
-               AST_STRING_FIELD(name);
-       );
-       int index;
-       struct varshead *headp; /*!< List of variables to substitute into templates */
-       AST_LIST_ENTRY(extension) entry;
-};
+struct ao2_container *profiles;
+SIMPLE_HASH_FN(phone_profile_hash_fn, phone_profile, name)
+SIMPLE_CMP_FN(phone_profile_cmp_fn, phone_profile, name)
 
 /*! \brief structure to hold users read from users.conf */
 struct user {
        AST_DECLARE_STRING_FIELDS(
                AST_STRING_FIELD(macaddress);   /*!< Mac address of user's phone */
+               AST_STRING_FIELD(provider_name);        /*!< Name of the provider who registered this mac */
        );
        struct phone_profile *profile;  /*!< Profile the phone belongs to */
        AST_LIST_HEAD_NOLOCK(, extension) extensions;
 };
+struct ao2_container *users;
+SIMPLE_HASH_FN(user_hash_fn, user, macaddress)
+SIMPLE_CMP_FN(user_cmp_fn, user, macaddress)
 
 /*! \brief structure to hold http routes (valid URIs, and the files they link to) */
 struct http_route {
@@ -193,19 +330,13 @@ struct http_route {
        struct phoneprov_file *file;    /*!< The file that links to the URI */
        struct user *user;      /*!< The user that has variables to substitute into the file
                                                 * NULL in the case of a static route */
+       struct phone_profile *profile;
 };
+struct ao2_container *http_routes;
+SIMPLE_HASH_FN(http_route_hash_fn, http_route, uri)
+SIMPLE_CMP_FN(http_route_cmp_fn, http_route, uri)
 
-static struct ao2_container *profiles;
-static struct ao2_container *http_routes;
-static struct ao2_container *users;
-
-static char global_server[80] = "";    /*!< Server to substitute into templates */
-static char global_serverport[6] = ""; /*!< Server port to substitute into templates */
-static char global_default_profile[80] = "";   /*!< Default profile to use if one isn't specified */
-
-/*! \brief List of global variables currently available: VOICEMAIL_EXTEN, EXTENSION_LENGTH */
-static struct varshead global_variables;
-static ast_mutex_t globals_lock;
+#define SIPUSERS_PROVIDER_NAME "sipusers"
 
 /* iface is the interface (e.g. eth0); address is the return value */
 static int lookup_iface(const char *iface, struct in_addr *address)
@@ -238,35 +369,25 @@ static int lookup_iface(const char *iface, struct in_addr *address)
        }
 }
 
-static struct phone_profile *unref_profile(struct phone_profile *prof)
-{
-       ao2_ref(prof, -1);
-
-       return NULL;
-}
-
-/*! \brief Return a phone profile looked up by name */
-static struct phone_profile *find_profile(const char *name)
+static struct phoneprov_provider *find_provider(char *name)
 {
-       struct phone_profile tmp = {
-               .name = name,
-       };
-
-       return ao2_find(profiles, &tmp, OBJ_POINTER);
+       return ao2_find(providers, name, OBJ_SEARCH_KEY);
 }
 
-static int profile_hash_fn(const void *obj, const int flags)
+/*! \brief Delete all providers */
+static void delete_providers(void)
 {
-       const struct phone_profile *profile = obj;
+       if (!providers) {
+               return;
+       }
 
-       return ast_str_case_hash(profile->name);
+       ao2_callback(providers, OBJ_UNLINK | OBJ_NODATA | OBJ_MULTIPLE, NULL, NULL);
 }
 
-static int profile_cmp_fn(void *obj, void *arg, int flags)
+static void provider_destructor(void *obj)
 {
-       const struct phone_profile *profile1 = obj, *profile2 = arg;
-
-       return !strcasecmp(profile1->name, profile2->name) ? CMP_MATCH | CMP_STOP : 0;
+       struct phoneprov_provider *provider = obj;
+       ast_string_field_free_memory(provider);
 }
 
 static void delete_file(struct phoneprov_file *file)
@@ -275,53 +396,6 @@ static void delete_file(struct phoneprov_file *file)
        ast_free(file);
 }
 
-static void profile_destructor(void *obj)
-{
-       struct phone_profile *profile = obj;
-       struct phoneprov_file *file;
-       struct ast_var_t *var;
-
-       while ((file = AST_LIST_REMOVE_HEAD(&profile->static_files, entry)))
-               delete_file(file);
-
-       while ((file = AST_LIST_REMOVE_HEAD(&profile->dynamic_files, entry)))
-               delete_file(file);
-
-       while ((var = AST_LIST_REMOVE_HEAD(profile->headp, entries)))
-               ast_var_delete(var);
-
-       ast_free(profile->headp);
-       ast_string_field_free_memory(profile);
-}
-
-static struct http_route *unref_route(struct http_route *route)
-{
-       ao2_ref(route, -1);
-
-       return NULL;
-}
-
-static int routes_hash_fn(const void *obj, const int flags)
-{
-       const struct http_route *route = obj;
-
-       return ast_str_case_hash(route->uri);
-}
-
-static int routes_cmp_fn(void *obj, void *arg, int flags)
-{
-       const struct http_route *route1 = obj, *route2 = arg;
-
-       return !strcasecmp(route1->uri, route2->uri) ? CMP_MATCH | CMP_STOP : 0;
-}
-
-static void route_destructor(void *obj)
-{
-       struct http_route *route = obj;
-
-       ast_string_field_free_memory(route);
-}
-
 /*! \brief Read a TEXT file into a string and return the length */
 static int load_file(const char *filename, char **ret)
 {
@@ -365,189 +439,66 @@ static void set_timezone_variables(struct varshead *headp, const char *zone)
        struct ast_tm tm_info;
        int tzoffset;
        char buffer[21];
-       struct ast_var_t *var;
        struct timeval when;
 
        time(&utc_time);
        ast_get_dst_info(&utc_time, &dstenable, &dststart, &dstend, &tzoffset, zone);
        snprintf(buffer, sizeof(buffer), "%d", tzoffset);
-       var = ast_var_assign("TZOFFSET", buffer);
-       if (var)
-               AST_LIST_INSERT_TAIL(headp, var, entries);
+       AST_VAR_LIST_INSERT_TAIL(headp, ast_var_assign("TZOFFSET", buffer));
 
-       if (!dstenable)
+       if (!dstenable) {
                return;
+       }
 
-       if ((var = ast_var_assign("DST_ENABLE", "1")))
-               AST_LIST_INSERT_TAIL(headp, var, entries);
+       AST_VAR_LIST_INSERT_TAIL(headp, ast_var_assign("DST_ENABLE", "1"));
 
        when.tv_sec = dststart;
        ast_localtime(&when, &tm_info, zone);
 
        snprintf(buffer, sizeof(buffer), "%d", tm_info.tm_mon+1);
-       if ((var = ast_var_assign("DST_START_MONTH", buffer)))
-               AST_LIST_INSERT_TAIL(headp, var, entries);
+       AST_VAR_LIST_INSERT_TAIL(headp, ast_var_assign("DST_START_MONTH", buffer));
 
        snprintf(buffer, sizeof(buffer), "%d", tm_info.tm_mday);
-       if ((var = ast_var_assign("DST_START_MDAY", buffer)))
-               AST_LIST_INSERT_TAIL(headp, var, entries);
+       AST_VAR_LIST_INSERT_TAIL(headp, ast_var_assign("DST_START_MDAY", buffer));
 
        snprintf(buffer, sizeof(buffer), "%d", tm_info.tm_hour);
-       if ((var = ast_var_assign("DST_START_HOUR", buffer)))
-               AST_LIST_INSERT_TAIL(headp, var, entries);
+       AST_VAR_LIST_INSERT_TAIL(headp, ast_var_assign("DST_START_HOUR", buffer));
 
        when.tv_sec = dstend;
        ast_localtime(&when, &tm_info, zone);
 
        snprintf(buffer, sizeof(buffer), "%d", tm_info.tm_mon + 1);
-       if ((var = ast_var_assign("DST_END_MONTH", buffer)))
-               AST_LIST_INSERT_TAIL(headp, var, entries);
+       AST_VAR_LIST_INSERT_TAIL(headp, ast_var_assign("DST_END_MONTH", buffer));
 
        snprintf(buffer, sizeof(buffer), "%d", tm_info.tm_mday);
-       if ((var = ast_var_assign("DST_END_MDAY", buffer)))
-               AST_LIST_INSERT_TAIL(headp, var, entries);
+       AST_VAR_LIST_INSERT_TAIL(headp, ast_var_assign("DST_END_MDAY", buffer));
 
        snprintf(buffer, sizeof(buffer), "%d", tm_info.tm_hour);
-       if ((var = ast_var_assign("DST_END_HOUR", buffer)))
-               AST_LIST_INSERT_TAIL(headp, var, entries);
+       AST_VAR_LIST_INSERT_TAIL(headp, ast_var_assign("DST_END_HOUR", buffer));
 }
 
-/*! \brief Callback that is executed everytime an http request is received by this module */
-static int phoneprov_callback(struct ast_tcptls_session_instance *ser, const struct ast_http_uri *urih, const char *uri, enum ast_http_method method, struct ast_variable *get_vars, struct ast_variable *headers)
+static struct http_route *unref_route(struct http_route *route)
 {
-       struct http_route *route;
-       struct http_route search_route = {
-               .uri = uri,
-       };
-       struct ast_str *result;
-       char path[PATH_MAX];
-       char *file = NULL;
-       int len;
-       int fd;
-       struct ast_str *http_header;
-
-       if (method != AST_HTTP_GET && method != AST_HTTP_HEAD) {
-               ast_http_error(ser, 501, "Not Implemented", "Attempt to use unimplemented / unsupported method");
-               return 0;
-       }
-
-       if (!(route = ao2_find(http_routes, &search_route, OBJ_POINTER))) {
-               goto out404;
-       }
-
-       snprintf(path, sizeof(path), "%s/phoneprov/%s", ast_config_AST_DATA_DIR, route->file->template);
-
-       if (!route->user) { /* Static file */
-
-               fd = open(path, O_RDONLY);
-               if (fd < 0) {
-                       goto out500;
-               }
-
-               len = lseek(fd, 0, SEEK_END);
-               lseek(fd, 0, SEEK_SET);
-               if (len < 0) {
-                       ast_log(LOG_WARNING, "Could not load file: %s (%d)\n", path, len);
-                       close(fd);
-                       goto out500;
-               }
-
-               http_header = ast_str_create(80);
-               ast_str_set(&http_header, 0, "Content-type: %s\r\n",
-                       route->file->mime_type);
-
-               ast_http_send(ser, method, 200, NULL, http_header, NULL, fd, 0);
-
-               close(fd);
-               route = unref_route(route);
-               return 0;
-       } else { /* Dynamic file */
-               struct ast_str *tmp;
-
-               len = load_file(path, &file);
-               if (len < 0) {
-                       ast_log(LOG_WARNING, "Could not load file: %s (%d)\n", path, len);
-                       if (file) {
-                               ast_free(file);
-                       }
-
-                       goto out500;
-               }
-
-               if (!file) {
-                       goto out500;
-               }
-
-               if (!(tmp = ast_str_create(len))) {
-                       if (file) {
-                               ast_free(file);
-                       }
-
-                       goto out500;
-               }
-
-               /* Unless we are overridden by serveriface or serveraddr, we set the SERVER variable to
-                * the IP address we are listening on that the phone contacted for this config file */
-               if (ast_strlen_zero(global_server)) {
-                       union {
-                               struct sockaddr sa;
-                               struct sockaddr_in sa_in;
-                       } name;
-                       socklen_t namelen = sizeof(name.sa);
-                       int res;
-
-                       if ((res = getsockname(ser->fd, &name.sa, &namelen))) {
-                               ast_log(LOG_WARNING, "Could not get server IP, breakage likely.\n");
-                       } else {
-                               struct ast_var_t *var;
-                               struct extension *exten_iter;
-
-                               if ((var = ast_var_assign("SERVER", ast_inet_ntoa(name.sa_in.sin_addr)))) {
-                                       AST_LIST_TRAVERSE(&route->user->extensions, exten_iter, entry) {
-                                               AST_LIST_INSERT_TAIL(exten_iter->headp, var, entries);
-                                       }
-                               }
-                       }
-               }
-
-               ast_str_substitute_variables_varshead(&tmp, 0, AST_LIST_FIRST(&route->user->extensions)->headp, file);
-
-               if (file) {
-                       ast_free(file);
-               }
-
-               http_header = ast_str_create(80);
-               ast_str_set(&http_header, 0, "Content-type: %s\r\n",
-                       route->file->mime_type);
+       ao2_cleanup(route);
 
-               if (!(result = ast_str_create(512))) {
-                       ast_log(LOG_ERROR, "Could not create result string!\n");
-                       if (tmp) {
-                               ast_free(tmp);
-                       }
-                       ast_free(http_header);
-                       goto out500;
-               }
-               ast_str_append(&result, 0, "%s", ast_str_buffer(tmp)); 
+       return NULL;
+}
 
-               ast_http_send(ser, method, 200, NULL, http_header, result, 0, 0);
-               if (tmp) {
-                       ast_free(tmp);
-               }
+static void route_destructor(void *obj)
+{
+       struct http_route *route = obj;
 
-               route = unref_route(route);
+       ast_string_field_free_memory(route);
+}
 
-               return 0;
+/*! \brief Delete all http routes, freeing their memory */
+static void delete_routes(void)
+{
+       if (!http_routes) {
+               return;
        }
 
-out404:
-       ast_http_error(ser, 404, "Not Found", "Nothing to see here.  Move along.");
-       return 0;
-
-out500:
-       route = unref_route(route);
-       ast_http_error(ser, 500, "Internal Error", "An internal error has occured.");
-       return 0;
+       ao2_callback(http_routes, OBJ_UNLINK | OBJ_NODATA | OBJ_MULTIPLE, NULL, NULL);
 }
 
 /*! \brief Build a route structure and add it to the list of available http routes
@@ -555,7 +506,7 @@ out500:
        \param user User to link to the route (NULL means static route)
        \param uri URI of the route
 */
-static void build_route(struct phoneprov_file *pp_file, struct user *user, char *uri)
+static void build_route(struct phoneprov_file *pp_file, struct phone_profile *profile, struct user *user, char *uri)
 {
        struct http_route *route;
 
@@ -572,44 +523,88 @@ static void build_route(struct phoneprov_file *pp_file, struct user *user, char
        ast_string_field_set(route, uri, S_OR(uri, pp_file->format));
        route->user = user;
        route->file = pp_file;
+       route->profile = profile;
 
        ao2_link(http_routes, route);
 
        route = unref_route(route);
 }
 
-/*! \brief Build a phone profile and add it to the list of phone profiles
-       \param name the name of the profile
-       \param v ast_variable from parsing phoneprov.conf
-*/
-static void build_profile(const char *name, struct ast_variable *v)
+static struct phone_profile *unref_profile(struct phone_profile *prof)
 {
-       struct phone_profile *profile;
+       ao2_cleanup(prof);
+
+       return NULL;
+}
+
+/*! \brief Return a phone profile looked up by name */
+static struct phone_profile *find_profile(const char *name)
+{
+       return ao2_find(profiles, name, OBJ_SEARCH_KEY);
+}
+
+static void profile_destructor(void *obj)
+{
+       struct phone_profile *profile = obj;
+       struct phoneprov_file *file;
        struct ast_var_t *var;
 
-       if (!(profile = ao2_alloc(sizeof(*profile), profile_destructor))) {
-               return;
+       while ((file = AST_LIST_REMOVE_HEAD(&profile->static_files, entry))) {
+               delete_file(file);
        }
 
-       if (ast_string_field_init(profile, 32)) {
-               profile = unref_profile(profile);
-               return;
+       while ((file = AST_LIST_REMOVE_HEAD(&profile->dynamic_files, entry))) {
+               delete_file(file);
        }
 
-       if (!(profile->headp = ast_calloc(1, sizeof(*profile->headp)))) {
-               profile = unref_profile(profile);
-               return;
+       while ((var = AST_LIST_REMOVE_HEAD(profile->headp, entries))) {
+               ast_var_delete(var);
        }
 
-       AST_LIST_HEAD_INIT_NOLOCK(&profile->static_files);
-       AST_LIST_HEAD_INIT_NOLOCK(&profile->dynamic_files);
-
-       ast_string_field_set(profile, name, name);
+       ast_free(profile->headp);
+       ast_string_field_free_memory(profile);
+}
+
+/*! \brief Delete all phone profiles, freeing their memory */
+static void delete_profiles(void)
+{
+       if (!profiles) {
+               return;
+       }
+
+       ao2_callback(profiles, OBJ_UNLINK | OBJ_NODATA | OBJ_MULTIPLE, NULL, NULL);
+}
+
+/*! \brief Build a phone profile and add it to the list of phone profiles
+       \param name the name of the profile
+       \param v ast_variable from parsing phoneprov.conf
+*/
+static void build_profile(const char *name, struct ast_variable *v)
+{
+       struct phone_profile *profile;
+
+       if (!(profile = ao2_alloc(sizeof(*profile), profile_destructor))) {
+               return;
+       }
+
+       if (ast_string_field_init(profile, 32)) {
+               profile = unref_profile(profile);
+               return;
+       }
+
+       if (!(profile->headp = ast_var_list_create())) {
+               profile = unref_profile(profile);
+               return;
+       }
+
+       AST_LIST_HEAD_INIT_NOLOCK(&profile->static_files);
+       AST_LIST_HEAD_INIT_NOLOCK(&profile->dynamic_files);
+
+       ast_string_field_set(profile, name, name);
        for (; v; v = v->next) {
                if (!strcasecmp(v->name, "mime_type")) {
                        ast_string_field_set(profile, default_mime_type, v->value);
                } else if (!strcasecmp(v->name, "setvar")) {
-                       struct ast_var_t *variable;
                        char *value_copy = ast_strdupa(v->value);
 
                        AST_DECLARE_APP_ARGS(args,
@@ -625,8 +620,7 @@ static void build_profile(const char *name, struct ast_variable *v)
                                args.varval = ast_strip(args.varval);
                                if (ast_strlen_zero(args.varname) || ast_strlen_zero(args.varval))
                                        break;
-                               if ((variable = ast_var_assign(args.varname, args.varval)))
-                                       AST_LIST_INSERT_TAIL(profile->headp, variable, entries);
+                               AST_VAR_LIST_INSERT_TAIL(profile->headp, ast_var_assign(args.varname, args.varval));
                        } while (0);
                } else if (!strcasecmp(v->name, "staticdir")) {
                        ast_string_field_set(profile, staticdir, v->value);
@@ -664,7 +658,7 @@ static void build_profile(const char *name, struct ast_variable *v)
                                ast_string_field_build(pp_file, template, "%s%s", profile->staticdir, args.filename);
                                AST_LIST_INSERT_TAIL(&profile->static_files, pp_file, entry);
                                /* Add a route for the static files, as their filenames won't change per-user */
-                               build_route(pp_file, NULL, NULL);
+                               build_route(pp_file, profile, NULL, NULL);
                        } else {
                                ast_string_field_set(pp_file, format, v->name);
                                ast_string_field_set(pp_file, template, args.filename);
@@ -673,18 +667,6 @@ static void build_profile(const char *name, struct ast_variable *v)
                }
        }
 
-       /* Append the global variables to the variables list for this profile.
-        * This is for convenience later, when we need to provide a single
-        * variable list for use in substitution. */
-       ast_mutex_lock(&globals_lock);
-       AST_LIST_TRAVERSE(&global_variables, var, entries) {
-               struct ast_var_t *new_var;
-               if ((new_var = ast_var_assign(var->name, var->value))) {
-                       AST_LIST_INSERT_TAIL(profile->headp, new_var, entries);
-               }
-       }
-       ast_mutex_unlock(&globals_lock);
-
        ao2_link(profiles, profile);
 
        profile = unref_profile(profile);
@@ -692,24 +674,17 @@ static void build_profile(const char *name, struct ast_variable *v)
 
 static struct extension *delete_extension(struct extension *exten)
 {
-       struct ast_var_t *var;
-       while ((var = AST_LIST_REMOVE_HEAD(exten->headp, entries))) {
-               ast_var_delete(var);
-       }
-       ast_free(exten->headp);
+       ast_var_list_destroy(exten->headp);
        ast_string_field_free_memory(exten);
-
        ast_free(exten);
 
        return NULL;
 }
 
-static struct extension *build_extension(struct ast_config *cfg, const char *name)
+static struct extension *build_extension(const char *name, struct varshead *vars)
 {
        struct extension *exten;
-       struct ast_var_t *var;
        const char *tmp;
-       int i;
 
        if (!(exten = ast_calloc_with_stringfields(1, struct extension, 32))) {
                return NULL;
@@ -717,56 +692,36 @@ static struct extension *build_extension(struct ast_config *cfg, const char *nam
 
        ast_string_field_set(exten, name, name);
 
-       if (!(exten->headp = ast_calloc(1, sizeof(*exten->headp)))) {
-               ast_free(exten);
-               exten = NULL;
+       exten->headp = ast_var_list_clone(vars);
+       if (!exten->headp) {
+               ast_log(LOG_ERROR, "Unable to clone variables for extension '%s'\n", name);
+               delete_extension(exten);
                return NULL;
        }
 
-       for (i = 0; i < PP_VAR_LIST_LENGTH; i++) {
-               tmp = ast_variable_retrieve(cfg, name, pp_variable_list[i].user_var);
-
-               /* If we didn't get a USERNAME variable, set it to the user->name */
-               if (i == PP_USERNAME && !tmp) {
-                       if ((var = ast_var_assign(pp_variable_list[PP_USERNAME].template_var, exten->name))) {
-                               AST_LIST_INSERT_TAIL(exten->headp, var, entries);
-                       }
-                       continue;
-               } else if (i == PP_TIMEZONE) {
-                       /* perfectly ok if tmp is NULL, will set variables based on server's time zone */
-                       set_timezone_variables(exten->headp, tmp);
-               } else if (i == PP_LINENUMBER) {
-                       if (!tmp) {
-                               tmp = "1";
-                       }
-                       exten->index = atoi(tmp);
-               } else if (i == PP_LINEKEYS) {
-                       if (!tmp) {
-                               tmp = "1";
-                       }
-               }
-
-               if (tmp && (var = ast_var_assign(pp_variable_list[i].template_var, tmp))) {
-                       AST_LIST_INSERT_TAIL(exten->headp, var, entries);
-               }
+       tmp = ast_var_find(exten->headp, ast_phoneprov_std_variable_lookup[AST_PHONEPROV_STD_LINENUMBER]);
+       if (!tmp) {
+               AST_VAR_LIST_INSERT_TAIL(exten->headp,
+                       ast_var_assign(ast_phoneprov_std_variable_lookup[AST_PHONEPROV_STD_LINENUMBER], "1"));
+               exten->index = 1;
+       } else {
+               sscanf(tmp, "%d", &exten->index);
        }
 
-       if (!ast_strlen_zero(global_server)) {
-               if ((var = ast_var_assign("SERVER", global_server)))
-                       AST_LIST_INSERT_TAIL(exten->headp, var, entries);
+       if (!ast_var_find(exten->headp, ast_phoneprov_std_variable_lookup[AST_PHONEPROV_STD_LINEKEYS])) {
+               AST_VAR_LIST_INSERT_TAIL(exten->headp,
+                       ast_var_assign(ast_phoneprov_std_variable_lookup[AST_PHONEPROV_STD_LINEKEYS], "1"));
        }
 
-       if (!ast_strlen_zero(global_serverport)) {
-               if ((var = ast_var_assign("SERVER_PORT", global_serverport)))
-                       AST_LIST_INSERT_TAIL(exten->headp, var, entries);
-       }
+       set_timezone_variables(exten->headp,
+               ast_var_find(vars, ast_phoneprov_std_variable_lookup[AST_PHONEPROV_STD_TIMEZONE]));
 
        return exten;
 }
 
 static struct user *unref_user(struct user *user)
 {
-       ao2_ref(user, -1);
+       ao2_cleanup(user);
 
        return NULL;
 }
@@ -774,25 +729,19 @@ static struct user *unref_user(struct user *user)
 /*! \brief Return a user looked up by name */
 static struct user *find_user(const char *macaddress)
 {
-       struct user tmp = {
-               .macaddress = macaddress,
-       };
-
-       return ao2_find(users, &tmp, OBJ_POINTER);
-}
-
-static int users_hash_fn(const void *obj, const int flags)
-{
-       const struct user *user = obj;
-
-       return ast_str_case_hash(user->macaddress);
+       return ao2_find(users, macaddress, OBJ_SEARCH_KEY);
 }
 
-static int users_cmp_fn(void *obj, void *arg, int flags)
+static int routes_delete_cb(void *obj, void *arg, int flags)
 {
-       const struct user *user1 = obj, *user2 = arg;
+       struct http_route *route = obj;
+       struct user *user = route->user;
+       char *macaddress = arg;
 
-       return !strcasecmp(user1->macaddress, user2->macaddress) ? CMP_MATCH | CMP_STOP : 0;
+       if (user && !strcmp(user->macaddress, macaddress)) {
+               return CMP_MATCH;
+       }
+       return 0;
 }
 
 /*! \brief Free all memory associated with a user */
@@ -809,41 +758,39 @@ static void user_destructor(void *obj)
                user->profile = unref_profile(user->profile);
        }
 
+       ao2_callback(http_routes, OBJ_UNLINK | OBJ_NODATA | OBJ_MULTIPLE, routes_delete_cb, (void *)user->macaddress);
+
        ast_string_field_free_memory(user);
 }
 
 /*! \brief Delete all users */
 static void delete_users(void)
 {
-       struct ao2_iterator i;
-       struct user *user;
-
-       i = ao2_iterator_init(users, 0);
-       while ((user = ao2_iterator_next(&i))) {
-               ao2_unlink(users, user);
-               user = unref_user(user);
+       if (!users) {
+               return;
        }
-       ao2_iterator_destroy(&i);
+
+       ao2_callback(users, OBJ_UNLINK | OBJ_NODATA | OBJ_MULTIPLE, NULL, NULL);
 }
 
 /*! \brief Build and return a user structure based on gathered config data */
-static struct user *build_user(const char *mac, struct phone_profile *profile)
+static struct user *build_user(const char *mac, struct phone_profile *profile, char *provider_name)
 {
        struct user *user;
 
        if (!(user = ao2_alloc(sizeof(*user), user_destructor))) {
-               profile = unref_profile(profile);
                return NULL;
        }
 
-       if (ast_string_field_init(user, 32)) {
-               profile = unref_profile(profile);
+       if (ast_string_field_init(user, 64)) {
                user = unref_user(user);
                return NULL;
        }
 
        ast_string_field_set(user, macaddress, mac);
-       user->profile = profile; /* already ref counted by find_profile */
+       ast_string_field_set(user, provider_name, provider_name);
+       user->profile = profile;
+       ao2_ref(profile, 1);
 
        return user;
 }
@@ -851,7 +798,7 @@ static struct user *build_user(const char *mac, struct phone_profile *profile)
 /*! \brief Add an extension to a user ordered by index/linenumber */
 static int add_user_extension(struct user *user, struct extension *exten)
 {
-       struct ast_var_t *var;
+       struct ast_var_t *pvar, *var2;
        struct ast_str *str = ast_str_create(16);
 
        if (!str) {
@@ -860,15 +807,16 @@ static int add_user_extension(struct user *user, struct extension *exten)
 
        /* Append profile variables here, and substitute variables on profile
         * setvars, so that we can use user specific variables in them */
-       AST_LIST_TRAVERSE(user->profile->headp, var, entries) {
-               struct ast_var_t *var2;
+       AST_VAR_LIST_TRAVERSE(user->profile->headp, pvar) {
+               if (ast_var_find(exten->headp, pvar->name)) {
+                       continue;
+               }
 
-               ast_str_substitute_variables_varshead(&str, 0, exten->headp, var->value);
-               if ((var2 = ast_var_assign(var->name, ast_str_buffer(str)))) {
-                       AST_LIST_INSERT_TAIL(exten->headp, var2, entries);
+               ast_str_substitute_variables_varshead(&str, 0, exten->headp, pvar->value);
+               if ((var2 = ast_var_assign(pvar->name, ast_str_buffer(str)))) {
+                       AST_VAR_LIST_INSERT_TAIL(exten->headp, var2);
                }
        }
-
        ast_free(str);
 
        if (AST_LIST_EMPTY(&user->extensions)) {
@@ -904,187 +852,146 @@ static int build_user_routes(struct user *user)
 
        AST_LIST_TRAVERSE(&user->profile->dynamic_files, pp_file, entry) {
                ast_str_substitute_variables_varshead(&str, 0, AST_LIST_FIRST(&user->extensions)->headp, pp_file->format);
-               build_route(pp_file, user, ast_str_buffer(str));
+               build_route(pp_file, user->profile, user, ast_str_buffer(str));
        }
 
        ast_free(str);
        return 0;
 }
 
-/* \brief Parse config files and create appropriate structures */
-static int set_config(void)
+/*! \brief Callback that is executed everytime an http request is received by this module */
+static int phoneprov_callback(struct ast_tcptls_session_instance *ser, const struct ast_http_uri *urih, const char *uri, enum ast_http_method method, struct ast_variable *get_vars, struct ast_variable *headers)
 {
-       struct ast_config *cfg, *phoneprov_cfg;
-       char *cat;
-       struct ast_variable *v;
-       struct ast_flags config_flags = { 0 };
-       struct ast_var_t *var;
-
-       /* Try to grab the port from sip.conf.  If we don't get it here, we'll set it
-        * to whatever is set in phoneprov.conf or default to 5060 */
-       if ((cfg = ast_config_load("sip.conf", config_flags)) && cfg != CONFIG_STATUS_FILEINVALID) {
-               ast_copy_string(global_serverport, S_OR(ast_variable_retrieve(cfg, "general", "bindport"), "5060"), sizeof(global_serverport));
-               ast_config_destroy(cfg);
-       }
+       struct http_route *route;
+       struct ast_str *result;
+       char path[PATH_MAX];
+       char *file = NULL;
+       char *server;
+       int len;
+       int fd;
+       struct ast_str *http_header;
 
-       if (!(cfg = ast_config_load("users.conf", config_flags)) || cfg == CONFIG_STATUS_FILEINVALID) {
-               ast_log(LOG_WARNING, "Unable to load users.conf\n");
+       if (method != AST_HTTP_GET && method != AST_HTTP_HEAD) {
+               ast_http_error(ser, 501, "Not Implemented", "Attempt to use unimplemented / unsupported method");
                return 0;
        }
 
-       /* Go ahead and load global variables from users.conf so we can append to profiles */
-       for (v = ast_variable_browse(cfg, "general"); v; v = v->next) {
-               if (!strcasecmp(v->name, "vmexten")) {
-                       if ((var = ast_var_assign("VOICEMAIL_EXTEN", v->value))) {
-                               ast_mutex_lock(&globals_lock);
-                               AST_LIST_INSERT_TAIL(&global_variables, var, entries);
-                               ast_mutex_unlock(&globals_lock);
-                       }
-               }
-               if (!strcasecmp(v->name, "localextenlength")) {
-                       if ((var = ast_var_assign("EXTENSION_LENGTH", v->value)))
-                               ast_mutex_lock(&globals_lock);
-                               AST_LIST_INSERT_TAIL(&global_variables, var, entries);
-                               ast_mutex_unlock(&globals_lock);
-               }
-       }
-
-       if (!(phoneprov_cfg = ast_config_load("phoneprov.conf", config_flags)) || phoneprov_cfg == CONFIG_STATUS_FILEINVALID) {
-               ast_log(LOG_ERROR, "Unable to load config phoneprov.conf\n");
-               ast_config_destroy(cfg);
-               return -1;
+       if (!(route = ao2_find(http_routes, uri, OBJ_SEARCH_KEY))) {
+               goto out404;
        }
 
-       cat = NULL;
-       while ((cat = ast_category_browse(phoneprov_cfg, cat))) {
-               if (!strcasecmp(cat, "general")) {
-                       for (v = ast_variable_browse(phoneprov_cfg, cat); v; v = v->next) {
-                               if (!strcasecmp(v->name, "serveraddr"))
-                                       ast_copy_string(global_server, v->value, sizeof(global_server));
-                               else if (!strcasecmp(v->name, "serveriface")) {
-                                       struct in_addr addr;
-                                       lookup_iface(v->value, &addr);
-                                       ast_copy_string(global_server, ast_inet_ntoa(addr), sizeof(global_server));
-                               } else if (!strcasecmp(v->name, "serverport"))
-                                       ast_copy_string(global_serverport, v->value, sizeof(global_serverport));
-                               else if (!strcasecmp(v->name, "default_profile"))
-                                       ast_copy_string(global_default_profile, v->value, sizeof(global_default_profile));
-                       }
-               } else
-                       build_profile(cat, ast_variable_browse(phoneprov_cfg, cat));
-       }
+       snprintf(path, sizeof(path), "%s/phoneprov/%s", ast_config_AST_DATA_DIR, route->file->template);
 
-       ast_config_destroy(phoneprov_cfg);
+       if (!route->user) { /* Static file */
 
-       cat = NULL;
-       while ((cat = ast_category_browse(cfg, cat))) {
-               const char *tmp, *mac;
-               struct user *user;
-               struct phone_profile *profile;
-               struct extension *exten;
+               fd = open(path, O_RDONLY);
+               if (fd < 0) {
+                       goto out500;
+               }
 
-               if (!strcasecmp(cat, "general")) {
-                       continue;
+               len = lseek(fd, 0, SEEK_END);
+               lseek(fd, 0, SEEK_SET);
+               if (len < 0) {
+                       ast_log(LOG_WARNING, "Could not load file: %s (%d)\n", path, len);
+                       close(fd);
+                       goto out500;
                }
 
-               if (!strcasecmp(cat, "authentication"))
-                       continue;
+               http_header = ast_str_create(80);
+               ast_str_set(&http_header, 0, "Content-type: %s\r\n",
+                       route->file->mime_type);
 
-               if (!((tmp = ast_variable_retrieve(cfg, cat, "autoprov")) && ast_true(tmp)))
-                       continue;
+               ast_http_send(ser, method, 200, NULL, http_header, NULL, fd, 0);
 
-               if (!(mac = ast_variable_retrieve(cfg, cat, "macaddress"))) {
-                       ast_log(LOG_WARNING, "autoprov set for %s, but no mac address - skipping.\n", cat);
-                       continue;
+               close(fd);
+               route = unref_route(route);
+               return 0;
+       } else { /* Dynamic file */
+               struct ast_str *tmp;
+
+               len = load_file(path, &file);
+               if (len < 0) {
+                       ast_log(LOG_WARNING, "Could not load file: %s (%d)\n", path, len);
+                       if (file) {
+                               ast_free(file);
+                       }
+
+                       goto out500;
                }
 
-               tmp = S_OR(ast_variable_retrieve(cfg, cat, "profile"), global_default_profile);
-               if (ast_strlen_zero(tmp)) {
-                       ast_log(LOG_WARNING, "No profile for user [%s] with mac '%s' - skipping\n", cat, mac);
-                       continue;
+               if (!file) {
+                       goto out500;
                }
 
-               if (!(user = find_user(mac))) {
-                       if (!(profile = find_profile(tmp))) {
-                               ast_log(LOG_WARNING, "Could not look up profile '%s' - skipping.\n", tmp);
-                               continue;
+               if (!(tmp = ast_str_create(len))) {
+                       if (file) {
+                               ast_free(file);
                        }
 
-                       if (!(user = build_user(mac, profile))) {
-                               ast_log(LOG_WARNING, "Could not create user for '%s' - skipping\n", mac);
-                               continue;
-                       }
+                       goto out500;
+               }
 
-                       if (!(exten = build_extension(cfg, cat))) {
-                               ast_log(LOG_WARNING, "Could not create extension for %s - skipping\n", user->macaddress);
-                               user = unref_user(user);
-                               continue;
-                       }
+               /* Unless we are overridden by serveriface or serveraddr, we set the SERVER variable to
+                * the IP address we are listening on that the phone contacted for this config file */
 
-                       if (add_user_extension(user, exten)) {
-                               ast_log(LOG_WARNING, "Could not add extension '%s' to user '%s'\n", exten->name, user->macaddress);
-                               user = unref_user(user);
-                               exten = delete_extension(exten);
-                               continue;
-                       }
+               server = ast_var_find(AST_LIST_FIRST(&route->user->extensions)->headp,
+                       ast_phoneprov_std_variable_lookup[AST_PHONEPROV_STD_SERVER]);
 
-                       if (build_user_routes(user)) {
-                               ast_log(LOG_WARNING, "Could not create http routes for %s - skipping\n", user->macaddress);
-                               user = unref_user(user);
-                               continue;
-                       }
+               if (!server) {
+                       union {
+                               struct sockaddr sa;
+                               struct sockaddr_in sa_in;
+                       } name;
+                       socklen_t namelen = sizeof(name.sa);
+                       int res;
 
-                       ao2_link(users, user);
-                       user = unref_user(user);
-               } else {
-                       if (!(exten = build_extension(cfg, cat))) {
-                               ast_log(LOG_WARNING, "Could not create extension for %s - skipping\n", user->macaddress);
-                               user = unref_user(user);
-                               continue;
-                       }
+                       if ((res = getsockname(ser->fd, &name.sa, &namelen))) {
+                               ast_log(LOG_WARNING, "Could not get server IP, breakage likely.\n");
+                       } else {
+                               struct extension *exten_iter;
+                               const char *newserver = ast_inet_ntoa(name.sa_in.sin_addr);
 
-                       if (add_user_extension(user, exten)) {
-                               ast_log(LOG_WARNING, "Could not add extension '%s' to user '%s'\n", exten->name, user->macaddress);
-                               user = unref_user(user);
-                               exten = delete_extension(exten);
-                               continue;
+                               AST_LIST_TRAVERSE(&route->user->extensions, exten_iter, entry) {
+                                       AST_VAR_LIST_INSERT_TAIL(exten_iter->headp,
+                                               ast_var_assign(ast_phoneprov_std_variable_lookup[AST_PHONEPROV_STD_SERVER], newserver));
+                               }
                        }
-
-                       user = unref_user(user);
                }
-       }
 
-       ast_config_destroy(cfg);
+               ast_str_substitute_variables_varshead(&tmp, 0, AST_LIST_FIRST(&route->user->extensions)->headp, file);
 
-       return 0;
-}
+               ast_free(file);
 
-/*! \brief Delete all http routes, freeing their memory */
-static void delete_routes(void)
-{
-       struct ao2_iterator i;
-       struct http_route *route;
+               http_header = ast_str_create(80);
+               ast_str_set(&http_header, 0, "Content-type: %s\r\n",
+                       route->file->mime_type);
+
+               if (!(result = ast_str_create(512))) {
+                       ast_log(LOG_ERROR, "Could not create result string!\n");
+                       if (tmp) {
+                               ast_free(tmp);
+                       }
+                       ast_free(http_header);
+                       goto out500;
+               }
+               ast_str_append(&result, 0, "%s", ast_str_buffer(tmp));
+
+               ast_http_send(ser, method, 200, NULL, http_header, result, 0, 0);
+               ast_free(tmp);
 
-       i = ao2_iterator_init(http_routes, 0);
-       while ((route = ao2_iterator_next(&i))) {
-               ao2_unlink(http_routes, route);
                route = unref_route(route);
+
+               return 0;
        }
-       ao2_iterator_destroy(&i);
-}
 
-/*! \brief Delete all phone profiles, freeing their memory */
-static void delete_profiles(void)
-{
-       struct ao2_iterator i;
-       struct phone_profile *profile;
+out404:
+       ast_http_error(ser, 404, "Not Found", uri);
+       return 0;
 
-       i = ao2_iterator_init(profiles, 0);
-       while ((profile = ao2_iterator_next(&i))) {
-               ao2_unlink(profiles, profile);
-               profile = unref_profile(profile);
-       }
-       ao2_iterator_destroy(&i);
+out500:
+       route = unref_route(route);
+       ast_http_error(ser, 500, "Internal Error", "An internal error has occured.");
+       return 0;
 }
 
 /*! \brief A dialplan function that can be used to print a string for each phoneprov user */
@@ -1222,13 +1129,27 @@ static struct ast_custom_function pp_each_extension_function = {
        .read2 = pp_each_extension_read2,
 };
 
+#define FORMATS "%-20.20s %-40.40s  %-30.30s\n"
+#define FORMATD "%-20.20s %-20.20s %-40.40s  %-30.30s\n"
+static int route_list_cb(void *obj, void *arg, void *data, int flags)
+{
+       int fd = *(int *)arg;
+       struct http_route *route = obj;
+
+       if (data && route->user) {
+               ast_cli(fd, FORMATD, route->user->provider_name, route->profile->name, route->uri, route->file->template);
+       }
+       if (!data && !route->user) {
+               ast_cli(fd, FORMATS, route->profile->name, route->uri, route->file->template);
+       }
+
+       return CMP_MATCH;
+}
+
 /*! \brief CLI command to list static and dynamic routes */
 static char *handle_show_routes(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
 {
-#define FORMAT "%-40.40s  %-30.30s\n"
-       struct ao2_iterator i;
-       struct http_route *route;
-
+       int fd = a->fd;
        switch(cmd) {
        case CLI_INIT:
                e->command = "phoneprov show routes";
@@ -1243,25 +1164,14 @@ static char *handle_show_routes(struct ast_cli_entry *e, int cmd, struct ast_cli
        /* This currently iterates over routes twice, but it is the only place I've needed
         * to really separate static an dynamic routes, so I've just left it this way. */
        ast_cli(a->fd, "Static routes\n\n");
-       ast_cli(a->fd, FORMAT, "Relative URI", "Physical location");
-       i = ao2_iterator_init(http_routes, 0);
-       while ((route = ao2_iterator_next(&i))) {
-               if (!route->user)
-                       ast_cli(a->fd, FORMAT, route->uri, route->file->template);
-               route = unref_route(route);
-       }
-       ao2_iterator_destroy(&i);
+       ast_cli(a->fd, FORMATS, "Profile", "Relative URI", "Physical location");
+
+       ao2_callback_data(http_routes, OBJ_NODATA | OBJ_MULTIPLE, route_list_cb, &fd, NULL);
 
        ast_cli(a->fd, "\nDynamic routes\n\n");
-       ast_cli(a->fd, FORMAT, "Relative URI", "Template");
+       ast_cli(a->fd, FORMATD, "Provider", "Profile", "Relative URI", "Template");
 
-       i = ao2_iterator_init(http_routes, 0);
-       while ((route = ao2_iterator_next(&i))) {
-               if (route->user)
-                       ast_cli(a->fd, FORMAT, route->uri, route->file->template);
-               route = unref_route(route);
-       }
-       ao2_iterator_destroy(&i);
+       ao2_callback_data(http_routes, OBJ_NODATA | OBJ_MULTIPLE, route_list_cb, &fd, (void *)1);
 
        return CLI_SUCCESS;
 }
@@ -1279,86 +1189,485 @@ static struct ast_http_uri phoneprovuri = {
        .key = __FILE__,
 };
 
+static struct varshead *get_defaults(void)
+{
+       struct ast_config *phoneprov_cfg;
+       struct ast_config *cfg;
+       const char *value;
+       struct ast_variable *v;
+       struct ast_var_t *var;
+       struct ast_flags config_flags = { 0 };
+       struct varshead *defaults = ast_var_list_create();
+
+       if (!defaults) {
+               ast_log(LOG_ERROR, "Unable to create default var list.\n");
+               return NULL;
+       }
+
+       if (!(phoneprov_cfg = ast_config_load("phoneprov.conf", config_flags))
+               || phoneprov_cfg == CONFIG_STATUS_FILEINVALID) {
+               ast_log(LOG_ERROR, "Unable to load config phoneprov.conf\n");
+               ast_var_list_destroy(defaults);
+               return NULL;
+       }
+
+       value = ast_variable_retrieve(phoneprov_cfg, "general", pp_general_lookup[AST_PHONEPROV_STD_SERVER]);
+       if (!value) {
+               struct in_addr addr;
+               value = ast_variable_retrieve(phoneprov_cfg, "general", pp_general_lookup[AST_PHONEPROV_STD_SERVER_IFACE]);
+               if (value) {
+                       lookup_iface(value, &addr);
+                       value = ast_inet_ntoa(addr);
+               }
+       }
+       if (value) {
+               var = ast_var_assign(ast_phoneprov_std_variable_lookup[AST_PHONEPROV_STD_SERVER], value);
+               AST_VAR_LIST_INSERT_TAIL(defaults, var);
+       } else {
+               ast_log(LOG_WARNING, "Unable to find a valid server address or name.\n");
+       }
+
+       value = ast_variable_retrieve(phoneprov_cfg, "general", pp_general_lookup[AST_PHONEPROV_STD_SERVER_PORT]);
+       if (!value) {
+               if ((cfg = ast_config_load("sip.conf", config_flags)) && cfg != CONFIG_STATUS_FILEINVALID) {
+                       value = ast_variable_retrieve(cfg, "general", "bindport");
+                       ast_config_destroy(cfg);
+               }
+       }
+       var = ast_var_assign(ast_phoneprov_std_variable_lookup[AST_PHONEPROV_STD_SERVER_PORT], S_OR(value, "5060"));
+       AST_VAR_LIST_INSERT_TAIL(defaults, var);
+
+       value = ast_variable_retrieve(phoneprov_cfg, "general", pp_general_lookup[AST_PHONEPROV_STD_PROFILE]);
+       if (!value) {
+               ast_log(LOG_ERROR, "Unable to load default profile.\n");
+               ast_config_destroy(phoneprov_cfg);
+               ast_var_list_destroy(defaults);
+               return NULL;
+       }
+       var = ast_var_assign(ast_phoneprov_std_variable_lookup[AST_PHONEPROV_STD_PROFILE], value);
+       AST_VAR_LIST_INSERT_TAIL(defaults, var);
+       ast_config_destroy(phoneprov_cfg);
+
+       if (!(cfg = ast_config_load("users.conf", config_flags)) || cfg == CONFIG_STATUS_FILEINVALID) {
+               ast_log(LOG_ERROR, "Unable to load users.conf\n");
+               ast_var_list_destroy(defaults);
+               return NULL;
+       }
+
+       /* Go ahead and load global variables from users.conf so we can append to profiles */
+       for (v = ast_variable_browse(cfg, "general"); v; v = v->next) {
+               if (!strcasecmp(v->name, pp_user_lookup[AST_PHONEPROV_STD_VOICEMAIL_EXTEN])) {
+                       var = ast_var_assign(ast_phoneprov_std_variable_lookup[AST_PHONEPROV_STD_VOICEMAIL_EXTEN], v->value);
+                       AST_VAR_LIST_INSERT_TAIL(defaults, var);
+               }
+               if (!strcasecmp(v->name, pp_user_lookup[AST_PHONEPROV_STD_EXTENSION_LENGTH])) {
+                       var = ast_var_assign(ast_phoneprov_std_variable_lookup[AST_PHONEPROV_STD_EXTENSION_LENGTH], v->value);
+                       AST_VAR_LIST_INSERT_TAIL(defaults, var);
+               }
+       }
+       ast_config_destroy(cfg);
+
+       return defaults;
+}
+
+static int load_users(void)
+{
+       struct ast_config *cfg;
+       char *cat;
+       const char *value;
+       struct ast_flags config_flags = { 0 };
+       struct varshead *defaults = get_defaults();
+
+       if (!defaults) {
+               ast_log(LOG_WARNING, "Unable to load default variables.\n");
+               return -1;
+       }
+
+       if (!(cfg = ast_config_load("users.conf", config_flags))
+               || cfg == CONFIG_STATUS_FILEINVALID) {
+               ast_log(LOG_WARNING, "Unable to load users.conf\n");
+               return -1;
+       }
+
+       cat = NULL;
+       while ((cat = ast_category_browse(cfg, cat))) {
+               const char *tmp;
+               int i;
+               struct ast_var_t *varx;
+               struct ast_var_t *vard;
+
+               if (strcasecmp(cat, "general") && strcasecmp(cat, "authentication")) {
+                       struct varshead *variables = ast_var_list_create();
+
+                       if (!((tmp = ast_variable_retrieve(cfg, cat, "autoprov")) && ast_true(tmp))) {
+                               ast_var_list_destroy(variables);
+                               continue;
+                       }
+
+                       /* Transfer the standard variables */
+                       for (i = 0; i < AST_PHONEPROV_STD_VAR_LIST_LENGTH; i++) {
+                               if (pp_user_lookup[i]) {
+                                       value = ast_variable_retrieve(cfg, cat, pp_user_lookup[i]);
+                                       if (value) {
+                                               varx = ast_var_assign(ast_phoneprov_std_variable_lookup[i],
+                                                       value);
+                                               AST_VAR_LIST_INSERT_TAIL(variables, varx);
+                                       }
+                               }
+                       }
+
+                       if (!ast_var_find(variables, ast_phoneprov_std_variable_lookup[AST_PHONEPROV_STD_MAC])) {
+                               ast_log(LOG_WARNING, "autoprov set for %s, but no mac address - skipping.\n", cat);
+                               ast_var_list_destroy(variables);
+                               continue;
+                       }
+
+                       /* Apply defaults */
+                       AST_VAR_LIST_TRAVERSE(defaults, vard) {
+                               if (ast_var_find(variables, vard->name)) {
+                                       continue;
+                               }
+                               varx = ast_var_assign(vard->name, vard->value);
+                               AST_VAR_LIST_INSERT_TAIL(variables, varx);
+                       }
+
+                       ast_phoneprov_add_extension(SIPUSERS_PROVIDER_NAME, variables);
+               }
+       }
+       ast_config_destroy(cfg);
+       return 0;
+}
+
+static int load_common(void)
+{
+       struct ast_config *phoneprov_cfg;
+       struct ast_flags config_flags = { 0 };
+       char *cat;
+
+       if (!(phoneprov_cfg = ast_config_load("phoneprov.conf", config_flags))
+               || phoneprov_cfg == CONFIG_STATUS_FILEINVALID) {
+               ast_log(LOG_ERROR, "Unable to load config phoneprov.conf\n");
+               return -1;
+       }
+
+       cat = NULL;
+       while ((cat = ast_category_browse(phoneprov_cfg, cat))) {
+               if (!strcasecmp(cat, "general")) {
+                       continue;
+               }
+               build_profile(cat, ast_variable_browse(phoneprov_cfg, cat));
+       }
+       ast_config_destroy(phoneprov_cfg);
+
+       return 0;
+}
 /*!
  * \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 
+ * 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.
  */
 static int load_module(void)
 {
-       profiles = ao2_container_alloc(MAX_PROFILE_BUCKETS, profile_hash_fn, profile_cmp_fn);
+       profiles = ao2_container_alloc(MAX_PROFILE_BUCKETS, phone_profile_hash_fn, phone_profile_cmp_fn);
+       if (!profiles) {
+               ast_log(LOG_ERROR, "Unable to allocate profiles container.\n");
+               return -1;
+       }
+
+       http_routes = ao2_container_alloc(MAX_ROUTE_BUCKETS, http_route_hash_fn, http_route_cmp_fn);
+       if (!http_routes) {
+               ast_log(LOG_ERROR, "Unable to allocate routes container.\n");
+               goto error;
+       }
+
+       if (load_common()) {
+               ast_log(LOG_ERROR, "Unable to load provisioning profiles.\n");
+               goto error;
+       }
+
+       users = ao2_container_alloc(MAX_USER_BUCKETS, user_hash_fn, user_cmp_fn);
+       if (!users) {
+               ast_log(LOG_ERROR, "Unable to allocate users container.\n");
+               goto error;
+       }
 
-       http_routes = ao2_container_alloc(MAX_ROUTE_BUCKETS, routes_hash_fn, routes_cmp_fn);
+       providers = ao2_container_alloc(MAX_PROVIDER_BUCKETS, phoneprov_provider_hash_fn, phoneprov_provider_cmp_fn);
+       if (!providers) {
+               ast_log(LOG_ERROR, "Unable to allocate providers container.\n");
+               goto error;
+       }
 
-       users = ao2_container_alloc(MAX_USER_BUCKETS, users_hash_fn, users_cmp_fn);
+       /* Register ourselves as the provider for sip.conf/users.conf */
+       if (ast_phoneprov_provider_register(SIPUSERS_PROVIDER_NAME, load_users)) {
+               ast_log(LOG_WARNING, "Unable register sip/users config provider.  Others may succeed.\n");
+       }
 
-       AST_LIST_HEAD_INIT_NOLOCK(&global_variables);
-       ast_mutex_init(&globals_lock);
+       ast_http_uri_link(&phoneprovuri);
 
        ast_custom_function_register(&pp_each_user_function);
        ast_custom_function_register(&pp_each_extension_function);
        ast_cli_register_multiple(pp_cli, ARRAY_LEN(pp_cli));
 
-       set_config();
-       ast_http_uri_link(&phoneprovuri);
-
        return 0;
+
+error:
+       delete_profiles();
+       ao2_cleanup(profiles);
+       delete_routes();
+       ao2_cleanup(http_routes);
+       delete_users();
+       ao2_cleanup(users);
+       delete_providers();
+       ao2_cleanup(providers);
+       return -1;
+
 }
 
 static int unload_module(void)
 {
-       struct ast_var_t *var;
-
        ast_http_uri_unlink(&phoneprovuri);
        ast_custom_function_unregister(&pp_each_user_function);
        ast_custom_function_unregister(&pp_each_extension_function);
        ast_cli_unregister_multiple(pp_cli, ARRAY_LEN(pp_cli));
 
+       /* This cleans up the sip.conf/users.conf provider (called specifically for clarity) */
+       ast_phoneprov_provider_unregister(SIPUSERS_PROVIDER_NAME);
+
+       /* This cleans up the framework which also cleans up the providers. */
+       delete_profiles();
+       ao2_cleanup(profiles);
        delete_routes();
+       ao2_cleanup(http_routes);
        delete_users();
-       delete_profiles();
-       ao2_ref(profiles, -1);
-       ao2_ref(http_routes, -1);
-       ao2_ref(users, -1);
-
-       ast_mutex_lock(&globals_lock);
-       while ((var = AST_LIST_REMOVE_HEAD(&global_variables, entries))) {
-               ast_var_delete(var);
-       }
-       ast_mutex_unlock(&globals_lock);
-
-       ast_mutex_destroy(&globals_lock);
+       ao2_cleanup(users);
+       delete_providers();
+       ao2_cleanup(providers);
 
        return 0;
 }
 
 static int reload(void)
 {
-       struct ast_var_t *var;
+       struct ao2_iterator i;
+       struct phoneprov_provider *provider;
 
+       /* Clean everything except the providers */
        delete_routes();
        delete_users();
        delete_profiles();
 
-       ast_mutex_lock(&globals_lock);
-       while ((var = AST_LIST_REMOVE_HEAD(&global_variables, entries))) {
-               ast_var_delete(var);
+       /* Reload the profiles */
+       if (load_common()) {
+               ast_log(LOG_ERROR, "Unable to reload provisioning profiles.\n");
+               return -1;
        }
-       ast_mutex_unlock(&globals_lock);
 
-       set_config();
+       /* For each provider, reload the users */
+       ao2_lock(providers);
+       i = ao2_iterator_init(providers, 0);
+       for(; (provider = ao2_iterator_next(&i)); ao2_ref(provider, -1)) {
+               ast_log(LOG_VERBOSE, "Reloading provider '%s' users.\n", provider->provider_name);
+               if (provider->load_users()) {
+                       ast_log(LOG_ERROR, "Unable to load provider '%s' users. Reload aborted.\n", provider->provider_name);
+                       continue;
+               }
+       }
+       ao2_iterator_destroy(&i);
+       ao2_unlock(providers);
 
        return 0;
 }
 
-AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_DEFAULT, "HTTP Phone Provisioning",
+AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_DEFAULT | AST_MODFLAG_GLOBAL_SYMBOLS, "HTTP Phone Provisioning",
                .support_level = AST_MODULE_SUPPORT_EXTENDED,
                .load = load_module,
                .unload = unload_module,
                .reload = reload,
        );
+
+/****  Public API for register/unregister, set defaults, and add extension. ****/
+
+int ast_phoneprov_provider_register(char *provider_name,
+       ast_phoneprov_load_users_cb load_users)
+{
+       struct phoneprov_provider *provider;
+
+       if (ast_strlen_zero(provider_name)) {
+               ast_log(LOG_ERROR, "Provider name can't be empty.\n");
+               return -1;
+       }
+
+       provider = find_provider(provider_name);
+       if (provider) {
+               ast_log(LOG_ERROR, "There is already a provider registered named '%s'.\n", provider_name);
+               ao2_ref(provider, -1);
+               return -1;
+       }
+
+       provider = ao2_alloc(sizeof(struct phoneprov_provider), provider_destructor);
+       if (!provider) {
+               ast_log(LOG_ERROR, "Unable to allocate sufficient memory for provider '%s'.\n", provider_name);
+               return -1;
+       }
+
+       if (ast_string_field_init(provider, 32)) {
+               ao2_ref(provider, -1);
+               ast_log(LOG_ERROR, "Unable to allocate sufficient memory for provider '%s' stringfields.\n", provider_name);
+               return -1;
+       }
+
+       ast_string_field_set(provider, provider_name, provider_name);
+       provider->load_users = load_users;
+
+       ao2_link(providers, provider);
+       ao2_ref(provider, -1);
+
+       if (provider->load_users()) {
+               ast_log(LOG_ERROR, "Unable to load provider '%s' users. Register aborted.\n", provider_name);
+               ast_phoneprov_provider_unregister(provider_name);
+               return -1;
+       }
+
+       ast_log(LOG_VERBOSE, "Registered phoneprov provider '%s'.\n", provider_name);
+       return 0;
+}
+
+static int extensions_delete_cb(void *obj, void *arg, int flags)
+{
+       char *provider_name = arg;
+       struct user *user = obj;
+       if (strcmp(user->provider_name, provider_name)) {
+               return 0;
+       }
+       return CMP_MATCH;
+}
+
+static int extension_delete_cb(void *obj, void *arg, void *data, int flags)
+{
+       struct user *user = obj;
+       char *provider_name = data;
+       char *macaddress = arg;
+
+       if (!strcmp(user->provider_name, provider_name) && !strcasecmp(user->macaddress, macaddress)) {
+               return CMP_MATCH;
+       }
+       return 0;
+}
+
+void ast_phoneprov_delete_extension(char *provider_name, char *macaddress)
+{
+       ao2_callback_data(users, OBJ_UNLINK | OBJ_NODATA | OBJ_MULTIPLE | OBJ_SEARCH_KEY,
+               extension_delete_cb, macaddress, provider_name);
+}
+
+void ast_phoneprov_delete_extensions(char *provider_name)
+{
+       ao2_callback(users, OBJ_UNLINK | OBJ_NODATA | OBJ_MULTIPLE, extensions_delete_cb, provider_name);
+}
+
+void ast_phoneprov_provider_unregister(char *provider_name)
+{
+       ast_phoneprov_delete_extensions(provider_name);
+       ao2_find(providers, provider_name, OBJ_SEARCH_KEY | OBJ_NODATA | OBJ_UNLINK);
+       ast_log(LOG_VERBOSE, "Unegistered phoneprov provider '%s'.\n", provider_name);
+}
+
+int ast_phoneprov_add_extension(char *provider_name, struct varshead *vars)
+{
+       RAII_VAR(struct phoneprov_provider *, provider, NULL, ao2_cleanup);
+       RAII_VAR(struct user *, user, NULL, ao2_cleanup);
+       RAII_VAR(struct phone_profile *, profile, NULL, ao2_cleanup);
+       struct extension *exten;
+       char *profile_name;
+       char *mac;
+       char *username;
+
+       if (ast_strlen_zero(provider_name)) {
+               ast_log(LOG_ERROR, "Provider name can't be empty.\n");
+               return -1;
+       }
+       if (!vars) {
+               ast_log(LOG_ERROR, "Variable list can't be empty.\n");
+               return -1;
+       }
+
+       username = ast_var_find(vars, ast_phoneprov_std_variable_lookup[AST_PHONEPROV_STD_USERNAME]);
+       if (!username) {
+               ast_log(LOG_ERROR, "Extension name can't be empty.\n");
+               return -1;
+       }
+
+       mac = ast_var_find(vars, ast_phoneprov_std_variable_lookup[AST_PHONEPROV_STD_MAC]);
+       if (!mac) {
+               ast_log(LOG_ERROR, "MAC Address can't be empty.\n");
+               return -1;
+       }
+
+       provider = find_provider(provider_name);
+       if (!provider) {
+               ast_log(LOG_ERROR, "Provider '%s' wasn't found in the registry.\n", provider_name);
+               return -1;
+       }
+
+       profile_name = ast_var_find(vars,
+               ast_phoneprov_std_variable_lookup[AST_PHONEPROV_STD_PROFILE]);
+       if (!profile_name) {
+               ast_log(LOG_ERROR, "No profile could be found for user '%s' - skipping.\n", username);
+               return -1;
+       }
+       if (!(profile = find_profile(profile_name))) {
+               ast_log(LOG_ERROR, "Could not look up profile '%s' - skipping.\n", profile_name);
+               return -1;
+       }
+
+       if (!(user = find_user(mac))) {
+
+               if (!(user = build_user(mac, profile, provider_name))) {
+                       ast_log(LOG_ERROR, "Could not create user for '%s' - skipping\n", mac);
+                       return -1;
+               }
+
+               if (!(exten = build_extension(username, vars))) {
+                       ast_log(LOG_ERROR, "Could not create extension for '%s' - skipping\n", user->macaddress);
+                       return -1;
+               }
+
+               if (add_user_extension(user, exten)) {
+                       ast_log(LOG_WARNING, "Could not add extension '%s' to user '%s'\n", exten->name, user->macaddress);
+                       exten = delete_extension(exten);
+                       return -1;
+               }
+
+               if (build_user_routes(user)) {
+                       ast_log(LOG_WARNING, "Could not create http routes for '%s' - skipping\n", user->macaddress);
+                       return -1;
+               }
+               ast_log(LOG_VERBOSE, "Created %s/%s for provider '%s'.\n", username, mac, provider_name);
+               ao2_link(users, user);
+
+       } else {
+               if (strcmp(provider_name, user->provider_name)) {
+                       ast_log(LOG_ERROR, "MAC address '%s' was already added by provider '%s' - skipping\n", user->macaddress, user->provider_name);
+                       return -1;
+               }
+
+               if (!(exten = build_extension(username, vars))) {
+                       ast_log(LOG_ERROR, "Could not create extension for '%s' - skipping\n", user->macaddress);
+                       return -1;
+               }
+
+               if (add_user_extension(user, exten)) {
+                       ast_log(LOG_WARNING, "Could not add extension '%s' to user '%s'\n", exten->name, user->macaddress);
+                       exten = delete_extension(exten);
+                       return -1;
+               }
+               ast_log(LOG_VERBOSE, "Added %s/%s for provider '%s'.\n", username, mac, provider_name);
+       }
+
+       return 0;
+}
diff --git a/res/res_phoneprov.exports.in b/res/res_phoneprov.exports.in
new file mode 100644 (file)
index 0000000..8356147
--- /dev/null
@@ -0,0 +1,6 @@
+{
+       global:
+               LINKER_SYMBOL_PREFIXast_phoneprov_*;
+       local:
+               *;
+};