From c6837c236fd1eca33246e634e90a0526d12efc4b Mon Sep 17 00:00:00 2001 From: George Joseph Date: Thu, 9 Oct 2014 20:55:50 +0000 Subject: [PATCH] res_pjsip_phoneprov_provider: Provides pjsip integration with res_phoneprov This module allows res_pjsip to integrate with res_phoneprov. It handles the pjsip 'phoneprov' object type. Tested-by: George Joseph Review: https://reviewboard.asterisk.org/r/3976/ ........ Merged revisions 425007 from http://svn.asterisk.org/svn/asterisk/branches/12 ........ Merged revisions 425008 from http://svn.asterisk.org/svn/asterisk/branches/13 git-svn-id: https://origsvn.digium.com/svn/asterisk/trunk@425009 65c4cc65-6c06-0410-ace0-fbb531ad65f3 --- configs/samples/pjsip.conf.sample | 114 ++++++++ res/res_pjsip_phoneprov_provider.c | 421 +++++++++++++++++++++++++++++ 2 files changed, 535 insertions(+) create mode 100644 res/res_pjsip_phoneprov_provider.c diff --git a/configs/samples/pjsip.conf.sample b/configs/samples/pjsip.conf.sample index f5fd75e0db..9f995e48c3 100644 --- a/configs/samples/pjsip.conf.sample +++ b/configs/samples/pjsip.conf.sample @@ -71,6 +71,8 @@ ; * Defines a permission list or references one stored in acl.conf ; * Registration "registration" ; * Contains information about an outbound SIP registration +; * Phone Provisioning "phoneprov" +; * Contains information needed by res_phoneprov for autoprovisioning ; The following sections show example configurations for various scenarios. ; Most require a couple or more configuration types configured in concert. @@ -480,6 +482,104 @@ ;NOTIFY requests being too large to send, consider breaking your lists into ;sub-lists. +;============EXAMPLE PHONEPROV CONFIGURATION================================ + +; Before configuring provisioning here, see the documentation for res_phoneprov +; and configure phoneprov.conf appropriately. + +; For each user to be autoprovisioned, a [phoneprov] configuration section +; must be created. At a minimum, the 'type', 'PROFILE' and 'MAC' variables must +; be set. All other variables are optional. +; Example: + +;[1000] +;type=phoneprov ; must be specified as 'phoneprov' +;endpoint=1000 ; Required only if automatic setting of + ; USERNAME, SECRET, DISPLAY_NAME and CALLERID + ; are needed. +;PROFILE=digium ; required +;MAC=deadbeef4dad ; required +;SERVER=myserver.example.com ; A standard variable +;TIMEZONE=America/Denver ; A standard variable +;MYVAR=somevalue ; A user confdigured variable + +; If the phoneprov sections have common variables, it is best to create a +; phoneprov template. The example below will produce the same configuration +; as the one specified above except that MYVAR will be overridden for +; the specific user. +; Example: + +;[phoneprov_defaults](!) +;type=phoneprov ; must be specified as 'phoneprov' +;PROFILE=digium ; required +;SERVER=myserver.example.com ; A standard variable +;TIMEZONE=America/Denver ; A standard variable +;MYVAR=somevalue ; A user configured variable + +;[1000](phoneprov_defaults) +;endpoint=1000 ; Required only if automatic setting of + ; USERNAME, SECRET, DISPLAY_NAME and CALLERID + ; are needed. +;MAC=deadbeef4dad ; required +;MYVAR=someOTHERvalue ; A user confdigured variable + +; To have USERNAME and SECRET automatically set, the endpoint +; specified here must in turn have an outbound_auth section defined. + +; Fuller example: + +;[1000] +;type=endpoint +;outbound_auth=1000-auth +;callerid=My Name <8005551212> +;transport=transport-udp-nat + +;[1000-auth] +;type=auth +;auth_type=userpass +;username=myname +;password=mysecret + +;[phoneprov_defaults](!) +;type=phoneprov ; must be specified as 'phoneprov' +;PROFILE=someprofile ; required +;SERVER=myserver.example.com ; A standard variable +;TIMEZONE=America/Denver ; A standard variable +;MYVAR=somevalue ; A user configured variable + +;[1000](phoneprov_defaults) +;endpoint=1000 ; Required only if automatic setting of + ; USERNAME, SECRET, DISPLAY_NAME and CALLERID + ; are needed. +;MAC=deadbeef4dad ; required +;MYVAR=someUSERvalue ; A user confdigured variable +;LABEL=1000 ; A standard variable + +; The previous sections would produce a template substitution map as follows: + +;MAC=deadbeef4dad ;added by pp1000 +;USERNAME=myname ;automatically added by 1000-auth username +;SECRET=mysecret ;automatically added by 1000-auth password +;PROFILE=someprofile ;added by defaults +;SERVER=myserver.example.com ;added by defaults +;SERVER_PORT=5060 ;added by defaults +;MYVAR=someUSERvalue ;added by defaults but overdidden by user +;CALLERID=8005551212 ;automatically added by 1000 callerid +;DISPLAY_NAME=My Name ;automatically added by 1000 callerid +;TIMEZONE=America/Denver ;added by defaults +;TZOFFSET=252100 ;automatically calculated by res_phoneprov +;DST_ENABLE=1 ;automatically calculated by res_phoneprov +;DST_START_MONTH=3 ;automatically calculated by res_phoneprov +;DST_START_MDAY=9 ;automatically calculated by res_phoneprov +;DST_START_HOUR=3 ;automatically calculated by res_phoneprov +;DST_END_MONTH=11 ;automatically calculated by res_phoneprov +;DST_END_MDAY=2 ;automatically calculated by res_phoneprov +;DST_END_HOUR=1 ;automatically calculated by res_phoneprov +;ENDPOINT_ID=1000 ;automatically added by this module +;AUTH_ID=1000-auth ;automatically added by this module +;TRANSPORT_ID=transport-udp-nat ;automatically added by this module +;LABEL=1000 ;added by user + ; MODULE PROVIDING BELOW SECTION(S): res_pjsip ;==========================ENDPOINT SECTION OPTIONS========================= ;[endpoint] @@ -800,3 +900,17 @@ ;endpoint= ; Name of Endpoint (default: "") ;match= ; IP addresses or networks to match against (default: "") ;type= ; Must be of type identify (default: "") + + + + +;========================PHONEPROV_USER SECTION OPTIONS======================= +;[phoneprov] +; SYNOPSIS: Contains variables for autoprovisioning each user +;endpoint= ; The endpoint from which to gather username, secret, etc. (default: "") +;PROFILE= ; The name of a profile configured in phoneprov.conf (default: "") +;MAC= ; The mac address for this user (default: "") +;OTHERVAR= ; Any other name value pair to be used in templates (default: "") + ; Common variables include LINE, LINEKEYS, etc. + ; See phoneprov.conf.sample for others. +;type= ; Must be of type phoneprov (default: "") diff --git a/res/res_pjsip_phoneprov_provider.c b/res/res_pjsip_phoneprov_provider.c new file mode 100644 index 0000000000..b09e4594d7 --- /dev/null +++ b/res/res_pjsip_phoneprov_provider.c @@ -0,0 +1,421 @@ +/* + * 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. + */ + +/*! \file + * + * \brief PJSIP Phoneprov Configuration Provider + * + * \author George Joseph + */ + +/*! \li \ref res_pjsip_phoneprov_provider.c uses the configuration file \ref pjsip.conf + * \addtogroup configuration_file Configuration Files + */ + +/*! + * \page pjsip.conf pjsip.conf + * \verbinclude pjsip.conf.sample + */ + +/*** MODULEINFO + res_pjsip + res_phoneprov + extended + ***/ + +#include "asterisk.h" + +#include + +#include "asterisk/res_pjsip.h" +#include "asterisk/module.h" +#include "asterisk/sorcery.h" +#include "asterisk/phoneprov.h" +#include "res_pjsip/include/res_pjsip_private.h" + +/*** DOCUMENTATION + + Module that integrates res_pjsip with res_phoneprov. + + PJSIP Phoneprov Provider + + This module creates the integration between res_pjsip and + res_phoneprov. + + Each user to be integrated requires a phoneprov + section defined in pjsip.conf. Each section identifies + the endpoint associated with the user and any other name/value pairs to be passed + on to res_phoneprov's template substitution. Only MAC and + PROFILE variables are required. Any other variables + supplied will be passed through. + + Example: + [1000] + type = phoneprovr + endpoint = ep1000 + MAC = deadbeef4dad + PROFILE = grandstream2 + LINEKEYS = 2 + LINE = 1 + OTHERVAR = othervalue + + The following variables are automatically defined if an endpoint + is defined for the user: + + Source: The user_name defined in the first auth reference + in the endpoint. + Source: The user_pass defined in the first auth reference + in the endpoint. + Source: The number part of the callerid defined in + the endpoint. + Source: The name part of the callerid defined in + the endpoint. + Source: The id of the phoneprov section. + + + In addition to the standard variables, the following are also automatically defined: + + Source: The id of the endpoint. + Source: The id of the transport used by the endpoint. + Source: The id of the auth used by the endpoint. + + + All other template substitution variables must be explicitly defined in the + phoneprov_default or phoneprov sections. + + + + + Provides variables for each user. + + Must be of type 'phoneprov'. + + + The endpoint from which variables will be retrieved. + + + The mac address for this user. (required) + + + The phoneprov profile to use for this user. (required) + + + Other name/value pairs to be passed through for use in templates. + + + + + ***/ + +static struct ast_sorcery *sorcery; + +/*! \brief Structure for a phoneprov object */ +struct phoneprov { + SORCERY_OBJECT(details); + struct varshead *vars; +}; + +/*! \brief Destructor function for phoneprov */ +static void phoneprov_destroy(void *obj) +{ + struct phoneprov *pp = obj; + + ast_var_list_destroy(pp->vars); +} + +/*! \brief Allocator for phoneprov */ +static void *phoneprov_alloc(const char *name) +{ + struct phoneprov *pp = ast_sorcery_generic_alloc(sizeof(*pp), phoneprov_destroy); + + if (!pp || !(pp->vars = ast_var_list_create())) { + ast_log(LOG_ERROR, "Unable to allocate memory for phoneprov structure %s\n", + name); + ao2_cleanup(pp); + return NULL; + } + + return pp; +} + +/*! \brief Helper that creates an ast_var_t and inserts it into the list */ +static int assign_and_insert(const char *name, const char *value, struct varshead *vars) +{ + struct ast_var_t *var; + + if (ast_strlen_zero(name) || !vars) { + return -1; + } + + /* Just ignore if the value is NULL or empty */ + if (ast_strlen_zero(value)) { + return 0; + } + + var = ast_var_assign(name, value); + if (!var) { + ast_log(LOG_ERROR, "Could not allocate variable memory for variable.\n"); + return -1; + } + AST_VAR_LIST_INSERT_TAIL(vars, var); + + return 0; +} + +/*! \brief Adds a config name/value pair to the phoneprov object */ +static int aco_handler(const struct aco_option *opt, struct ast_variable *var, void *obj) +{ + struct phoneprov *pp = obj; + + return assign_and_insert(var->name, var->value, pp->vars); +} + +/*! \brief Converts the phoneprov varlist to an ast_variable list */ +static int fields_handler(const void *obj, struct ast_variable **fields) +{ + const struct phoneprov *pp = obj; + struct ast_var_t *pvar; + struct ast_variable *head = NULL; + struct ast_variable *tail = NULL; + struct ast_variable *var; + + AST_VAR_LIST_TRAVERSE(pp->vars, pvar) { + var = ast_variable_new(pvar->name, pvar->value, NULL); + if (!var) { + ast_variables_destroy(head); + return -1; + } + if (!head) { + head = var; + tail = var; + continue; + } + tail->next = var; + tail = var; + } + + *fields = head; + + return 0; +} + +static int load_endpoint(const char *id, const char *endpoint_name, struct varshead *vars, + char *port_string) +{ + struct ast_sip_auth *auth; + RAII_VAR(struct ast_sip_endpoint *, endpoint, NULL, ao2_cleanup); + RAII_VAR(struct ast_sip_transport *, transport, NULL, ao2_cleanup); + const char *auth_name; + + *port_string = '\0'; + + /* We need to use res_pjsip's sorcery instance instead of our own to + * get endpoint and auth. + */ + endpoint = ast_sorcery_retrieve_by_id(ast_sip_get_sorcery(), "endpoint", + endpoint_name); + if (!endpoint) { + ast_log(LOG_ERROR, "phoneprov %s contained invalid endpoint %s.\n", id, + endpoint_name); + return -1; + } + + assign_and_insert("ENDPOINT_ID", endpoint_name, vars); + assign_and_insert("TRANSPORT_ID", endpoint->transport, vars); + + if (endpoint->id.self.number.valid && !ast_strlen_zero(endpoint->id.self.number.str)) { + assign_and_insert(ast_phoneprov_std_variable_lookup[AST_PHONEPROV_STD_CALLERID], + endpoint->id.self.number.str, vars); + } + + if (endpoint->id.self.name.valid && !ast_strlen_zero(endpoint->id.self.name.str)) { + assign_and_insert( + ast_phoneprov_std_variable_lookup[AST_PHONEPROV_STD_DISPLAY_NAME], + endpoint->id.self.name.str, vars); + } + + transport = ast_sorcery_retrieve_by_id(ast_sip_get_sorcery(), "transport", + endpoint->transport); + if (!transport) { + ast_log(LOG_ERROR, "Endpoint %s contained invalid transport %s.\n", endpoint_name, + endpoint->transport); + return -1; + } + snprintf(port_string, 6, "%d", pj_sockaddr_get_port(&transport->host)); + + if (!endpoint->inbound_auths.num) { + return 0; + } + auth_name = endpoint->inbound_auths.names[0]; + + auth = ast_sorcery_retrieve_by_id(ast_sip_get_sorcery(), "auth", auth_name); + if (!auth) { + ast_log(LOG_ERROR, "phoneprov %s contained invalid auth %s.\n", id, auth_name); + return -1; + } + + assign_and_insert("AUTH_ID", auth_name, vars); + assign_and_insert(ast_phoneprov_std_variable_lookup[AST_PHONEPROV_STD_USERNAME], + auth->auth_user, vars); + assign_and_insert(ast_phoneprov_std_variable_lookup[AST_PHONEPROV_STD_SECRET], + auth->auth_pass, vars); + ao2_ref(auth, -1); + + return 0; +} + +/*! \brief Callback that loads the users from phoneprov sections */ +static int load_users(void) +{ + struct phoneprov *pp; + struct ao2_container *c; + struct ao2_iterator i; + int user_count = 0; + char port_string[6]; + + c = ast_sorcery_retrieve_by_regex(sorcery, "phoneprov", ""); + if (!c) { + ast_log(LOG_ERROR, "Retrieve by regex failed to allocate a container.\n"); + return -1; + } + if (ao2_container_count(c) == 0) { + ast_log(LOG_ERROR, "Unable to find any phoneprov users.\n"); + ao2_cleanup(c); + return -1; + } + + i = ao2_iterator_init(c, 0); + while ((pp = ao2_iterator_next(&i))) { + const char *endpoint_name; + const char *id = ast_sorcery_object_get_id(pp); + + endpoint_name = ast_var_find(pp->vars, "endpoint"); + if (endpoint_name) { + if (load_endpoint(id, endpoint_name, pp->vars, port_string)) { + goto cleanup; + } + } + + if (!ast_var_find(pp->vars, + ast_phoneprov_std_variable_lookup[AST_PHONEPROV_STD_USERNAME])) { + assign_and_insert( + ast_phoneprov_std_variable_lookup[AST_PHONEPROV_STD_USERNAME], id, + pp->vars); + } + + if (!ast_var_find(pp->vars, + ast_phoneprov_std_variable_lookup[AST_PHONEPROV_STD_LABEL])) { + assign_and_insert(ast_phoneprov_std_variable_lookup[AST_PHONEPROV_STD_LABEL], + id, pp->vars); + } + + if (!ast_var_find(pp->vars, + ast_phoneprov_std_variable_lookup[AST_PHONEPROV_STD_SERVER_PORT])) { + assign_and_insert("SERVER_PORT", S_OR(port_string, "5060"), pp->vars); + } + + if (!ast_var_find(pp->vars, + ast_phoneprov_std_variable_lookup[AST_PHONEPROV_STD_PROFILE])) { + ast_log(LOG_ERROR, "phoneprov %s didn't contain a PROFILE entry.\n", id); + } else if (!ast_phoneprov_add_extension(AST_MODULE, pp->vars)) { + user_count++; + } + ao2_ref(pp, -1); + } + +cleanup: + ao2_iterator_destroy(&i); + ao2_cleanup(pp); + ao2_cleanup(c); + + return user_count > 0 ? 0 : -1; +} + +/*! \brief Callback that validates the phoneprov object */ +static int users_apply_handler(const struct ast_sorcery *sorcery, void *obj) +{ + struct phoneprov *pp = obj; + const char *id = ast_sorcery_object_get_id(pp); + + if (!ast_var_find(pp->vars, + ast_phoneprov_std_variable_lookup[AST_PHONEPROV_STD_MAC])) { + ast_log(LOG_ERROR, "phoneprov %s must contain a MAC entry.\n", id); + return -1; + } + + if (!ast_var_find(pp->vars, + ast_phoneprov_std_variable_lookup[AST_PHONEPROV_STD_PROFILE])) { + ast_log(LOG_ERROR, "phoneprov %s must contain a PROFILE entry.\n", id); + return -1; + } + + return 0; +} + +static int load_module(void) +{ + if (!(sorcery = ast_sorcery_open())) { + ast_log(LOG_ERROR, "Unable to open a sorcery instance.\n"); + return AST_MODULE_LOAD_DECLINE; + } + + if (ast_sorcery_apply_default(sorcery, "phoneprov", "config", + "pjsip.conf,criteria=type=phoneprov")) { + ast_log(LOG_ERROR, "Unable to register object phoneprov.\n"); + ast_sorcery_unref(sorcery); + return AST_MODULE_LOAD_DECLINE; + } + + ast_sorcery_object_register(sorcery, "phoneprov", phoneprov_alloc, NULL, + users_apply_handler); + ast_sorcery_object_field_register(sorcery, "phoneprov", "type", "", OPT_NOOP_T, 0, 0); + ast_sorcery_object_fields_register(sorcery, "phoneprov", "^", aco_handler, + fields_handler); + ast_sorcery_reload_object(sorcery, "phoneprov"); + + if (ast_phoneprov_provider_register(AST_MODULE, load_users)) { + ast_log(LOG_ERROR, "Unable to register pjsip phoneprov provider.\n"); + ast_sorcery_unref(sorcery); + return AST_MODULE_LOAD_DECLINE; + } + + return AST_MODULE_LOAD_SUCCESS; +} + +static int unload_module(void) +{ + ast_phoneprov_provider_unregister(AST_MODULE); + ast_sorcery_unref(sorcery); + + return 0; +} + +static int reload_module(void) +{ + unload_module(); + load_module(); + + return 0; +} + +AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_LOAD_ORDER, "PJSIP Phoneprov Provider", + .load = load_module, + .reload = reload_module, + .unload = unload_module, + .load_pri = AST_MODPRI_APP_DEPEND, + );