From cc595f7353c4b2c5fc71223a8b1ded601e068e04 Mon Sep 17 00:00:00 2001 From: George Joseph Date: Thu, 9 Oct 2014 17:46:23 +0000 Subject: res_phoneprov: Refactor phoneprov to allow pluggable config providers 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 | 10 +- include/asterisk/chanvars.h | 18 + include/asterisk/phoneprov.h | 119 +++ main/chanvars.c | 63 ++ res/res_phoneprov.c | 1461 ++++++++++++++++++++------------- res/res_phoneprov.exports.in | 6 + 6 files changed, 1099 insertions(+), 578 deletions(-) create mode 100644 include/asterisk/phoneprov.h create mode 100644 res/res_phoneprov.exports.in diff --git a/configs/samples/phoneprov.conf.sample b/configs/samples/phoneprov.conf.sample index 17d8b1f49..7d14013b2 100644 --- a/configs/samples/phoneprov.conf.sample +++ b/configs/samples/phoneprov.conf.sample @@ -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) diff --git a/include/asterisk/chanvars.h b/include/asterisk/chanvars.h index 7ebc64a9d..3693e2a3a 100644 --- a/include/asterisk/chanvars.h +++ b/include/asterisk/chanvars.h @@ -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 index 000000000..364a574be --- /dev/null +++ b/include/asterisk/phoneprov.h @@ -0,0 +1,119 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 2014 - Fairview 5 Engineering, LLC + * + * George Joseph + * + * 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 diff --git a/main/chanvars.c b/main/chanvars.c index da7449dc6..37714e9a2 100644 --- a/main/chanvars.c +++ b/main/chanvars.c @@ -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; +} diff --git a/res/res_phoneprov.c b/res/res_phoneprov.c index b8b52787d..8f71c2609 100644 --- a/res/res_phoneprov.c +++ b/res/res_phoneprov.c @@ -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 * Matthew Brooks * Terry Wilson + * George Joseph * * See http://www.asterisk.org for more information about * the Asterisk project. Please do not directly contact @@ -24,7 +26,8 @@ * * \author Matthew Brooks * \author Terry Wilson - */ + * \author George Joseph + */ /*! \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 @@ extended ***/ +#define AST_API_MODULE + #include "asterisk.h" #include @@ -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$") - + Output the specified template for each extension associated with the specified MAC address. @@ -109,41 +117,162 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$") ***/ -/*! \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 index 000000000..83561471a --- /dev/null +++ b/res/res_phoneprov.exports.in @@ -0,0 +1,6 @@ +{ + global: + LINKER_SYMBOL_PREFIXast_phoneprov_*; + local: + *; +}; -- cgit v1.2.3