build: Update config.guess and config.sub
[asterisk/asterisk.git] / res / res_pjsip_config_wizard.c
index 86187ee..3a761a7 100644 (file)
  */
 
 /*** MODULEINFO
+       <depend>pjproject</depend>
        <depend>res_pjsip</depend>
        <support_level>core</support_level>
  ***/
 
 #include "asterisk.h"
 
-ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
-
 #include <regex.h>
 #include <pjsip.h>
 
 #include "asterisk/astobj2.h"
+#include "asterisk/cli.h"
 #include "asterisk/res_pjsip.h"
 #include "asterisk/module.h"
 #include "asterisk/pbx.h"
@@ -63,7 +63,8 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
 
                        <para>For example, the following configuration snippet would create the
                        endpoint, aor, contact, auth and phoneprov objects necessary for a phone to
-                       get phone provisioning information, register, and make and receive calls.</para>
+                       get phone provisioning information, register, and make and receive calls.
+                       A hint is also created in the default context for extension 1000.</para>
                        <para> </para>
 
                        <para>[myphone]</para>
@@ -74,6 +75,8 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
                        <para>accepts_registrations = yes</para>
                        <para>has_phoneprov = yes</para>
                        <para>transport = ipv4</para>
+                       <para>has_hint = yes</para>
+                       <para>hint_exten = 1000</para>
                        <para>inbound_auth/username = testname</para>
                        <para>inbound_auth/password = test password</para>
                        <para>endpoint/allow = ulaw</para>
@@ -82,7 +85,7 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
                        <para>phoneprov/PROFILE = profile1</para>
                        <para> </para>
 
-                       <para>The first 7 items are specific to the wizard.  The rest of the items
+                       <para>The first 8 items are specific to the wizard.  The rest of the items
                        are passed verbatim to the underlying objects.</para>
                        <para> </para>
 
@@ -107,11 +110,19 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
 
                        <para>Of course, any of the items in either example could be placed into
                        templates and shared among wizard objects.</para>
+
+                       <para> </para>
+                       <para>For more information, visit:</para>
+                       <para><literal>https://wiki.asterisk.org/wiki/display/AST/PJSIP+Configuration+Wizard</literal></para>
                </description>
 
                <configFile name="pjsip_wizard.conf">
                        <configObject name="wizard">
                                <synopsis>Provides config wizard.</synopsis>
+                               <description>
+                               <para>For more information, visit:</para>
+                               <para><literal>https://wiki.asterisk.org/wiki/display/AST/PJSIP+Configuration+Wizard</literal></para>
+                               </description>
                                <configOption name="type">
                                        <synopsis>Must be 'wizard'.</synopsis>
                                </configOption>
@@ -128,6 +139,12 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
                                        entry in the list.  If send_registrations is also set, a registration will
                                        also be created for each.</para></description>
                                </configOption>
+                               <configOption name="outbound_proxy">
+                                       <synopsis>Shortcut for specifying proxy on individual objects.</synopsis>
+                                       <description><para>Shortcut for specifying endpoint/outbound_proxy,
+                                       aor/outbound_proxy, and registration/outbound_proxy individually.
+                                       </para></description>
+                               </configOption>
                                <configOption name="sends_auth" default="no">
                                        <synopsis>Send outbound authentication to remote hosts.</synopsis>
                                        <description><para>At least outbound_auth/username is required.</para></description>
@@ -142,6 +159,13 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
                                        be created for each host in the remote _hosts string.  If authentication is required,
                                        sends_auth and an outbound_auth/username must also be supplied.</para></description>
                                </configOption>
+                               <configOption name="sends_line_with_registrations" default="no">
+                                       <synopsis>Sets "line" and "endpoint parameters on registrations.</synopsis>
+                                       <description><para>Setting this to true will cause the wizard to skip the
+                                       creation of an identify object to match incoming requests to the endpoint and
+                                       instead add the line and endpoint parameters to the outbound registration object.
+                                       </para></description>
+                               </configOption>
                                <configOption name="accepts_registrations" default="no">
                                        <synopsis>Accept inbound registration from remote hosts.</synopsis>
                                        <description><para>An AOR with dynamic contacts will be created.  If
@@ -170,6 +194,52 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
                                        The literal <literal>${REMOTE_HOST}</literal> will be substituted with the
                                        appropriate remote_host for each contact.</para></description>
                                </configOption>
+                               <configOption name="has_hint" default="no">
+                                       <synopsis>Create hint and optionally a default application.</synopsis>
+                                       <description><para>Create hint and optionally a default application.</para></description>
+                               </configOption>
+                               <configOption name="hint_context" default="endpoint/context or 'default'">
+                                       <synopsis>The context in which to place hints.</synopsis>
+                                       <description>
+                                       <para>Ignored if <literal>hint_exten</literal> is not specified otherwise specifies the
+                                       context into which the dialplan hints will be placed.  If not specified,
+                                       defaults to the endpoint's context or <literal>default</literal> if that isn't
+                                       found.
+                                       </para></description>
+                               </configOption>
+                               <configOption name="hint_exten">
+                                       <synopsis>Extension to map a PJSIP hint to.</synopsis>
+                                       <description>
+                                       <para>Will create the following entry in <literal>hint_context</literal>:</para>
+                                       <para>   <literal>exten =&gt; &lt;hint_exten&gt;,hint,PJSIP/&lt;wizard_id&gt;</literal></para>
+                                       <para> </para>
+                                       <para>Normal dialplan precedence rules apply so if there's already a hint for
+                                       this extension in <literal>hint_context</literal>, this one will be ignored.
+                                       For more information, visit: </para>
+                                       <para><literal>https://wiki.asterisk.org/wiki/display/AST/PJSIP+Configuration+Wizard</literal></para>
+                                       </description>
+                               </configOption>
+                               <configOption name="hint_application">
+                                       <synopsis>Application to call when 'hint_exten' is dialed.</synopsis>
+                                       <description>
+                                       <para>Ignored if <literal>hint_exten</literal> isn't specified otherwise
+                                       will create the following priority 1 extension in <literal>hint_context</literal>:</para>
+                                       <para>   <literal>exten =&gt; &lt;hint_exten&gt;,1,&lt;hint_application&gt;</literal></para>
+                                       <para> </para>
+                                       <para>You can specify any valid extensions.conf application expression.</para>
+                                       <para>Examples: </para>
+                                       <para>   <literal>Dial(${HINT})</literal></para>
+                                       <para>   <literal>Gosub(stdexten,${EXTEN},1(${HINT}))</literal></para>
+                                       <para> </para>
+                                       <para>Any extensions.conf style variables specified are passed directly to the
+                                       dialplan.</para>
+                                       <para> </para>
+                                       <para>Normal dialplan precedence rules apply so if there's already a priority 1
+                                       application for this specific extension in <literal>hint_context</literal>,
+                                       this one will be ignored. For more information, visit: </para>
+                                       <para><literal>https://wiki.asterisk.org/wiki/display/AST/PJSIP+Configuration+Wizard</literal></para>
+                                       </description>
+                               </configOption>
                                <configOption name="endpoint&#47;*">
                                        <synopsis>Variables to be passed directly to the endpoint.</synopsis>
                                </configOption>
@@ -205,6 +275,8 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
  /*! \brief Defines the maximum number of characters that can be added to a wizard id. */
 #define MAX_ID_SUFFIX 20
 
+#define BASE_REGISTRAR "res_pjsip_config_wizard"
+
 /*! \brief A generic char * vector definition. */
 AST_VECTOR(string_vector, char *);
 
@@ -216,7 +288,7 @@ struct object_type_wizard {
        struct ast_config *last_config;
        char object_type[];
 };
-static AST_VECTOR(object_type_wizards, struct object_type_wizard *) object_type_wizards;
+static AST_VECTOR_RW(object_type_wizards, struct object_type_wizard *) object_type_wizards;
 
 /*! \brief Callbacks for vector deletes */
 #define NOT_EQUALS(a, b) (a != b)
@@ -244,12 +316,15 @@ static struct object_type_wizard *find_wizard(const char *object_type)
 {
        int idx;
 
+       AST_VECTOR_RW_RDLOCK(&object_type_wizards);
        for(idx = 0; idx < AST_VECTOR_SIZE(&object_type_wizards); idx++) {
                struct object_type_wizard *otw = AST_VECTOR_GET(&object_type_wizards, idx);
                if (!strcmp(otw->object_type, object_type)) {
+                       AST_VECTOR_RW_UNLOCK(&object_type_wizards);
                        return otw;
                }
        }
+       AST_VECTOR_RW_UNLOCK(&object_type_wizards);
 
        return NULL;
 }
@@ -274,10 +349,10 @@ static void *create_object(const struct ast_sorcery *sorcery,
        return obj;
 }
 
-/*! \brief Finds a variable in a list and tests it */
+/*! \brief Finds the last variable in a list and tests it */
 static int is_variable_true(struct ast_variable *vars, const char *name)
 {
-       return ast_true(ast_variable_find_in_list(vars, name));
+       return ast_true(ast_variable_find_last_in_list(vars, name));
 }
 
 /*! \brief Appends a variable to the end of an existing list */
@@ -328,6 +403,134 @@ static struct ast_variable *get_object_variables(struct ast_variable *vars, char
        return return_vars;
 }
 
+/* Don't call while holding context locks. */
+static int delete_extens(const char *context, const char *exten)
+{
+       struct pbx_find_info find_info = { .stacklen = 0 }; /* the rest is reset in pbx_find_extension */
+
+       if (pbx_find_extension(NULL, NULL, &find_info, context, exten, PRIORITY_HINT, NULL, NULL, E_MATCH)) {
+               ast_context_remove_extension(context, exten, PRIORITY_HINT, BASE_REGISTRAR);
+       }
+
+       if (pbx_find_extension(NULL, NULL, &find_info, context, exten, 1, NULL, NULL, E_MATCH)) {
+               ast_context_remove_extension(context, exten, 1, BASE_REGISTRAR);
+       }
+
+       return 0;
+}
+
+static int add_extension(struct ast_context *context, const char *exten,
+       int priority, const char *application)
+{
+       struct pbx_find_info find_info = { .stacklen = 0 }; /* the rest is reset in pbx_find_extension */
+       struct ast_exten *existing_exten;
+       char *data = NULL;
+       char *app = NULL;
+       void *free_ptr = NULL;
+       char *paren;
+       const char *context_name;
+
+       if (!context || ast_strlen_zero(exten) || ast_strlen_zero(application)) {
+               return -1;
+       }
+
+       /* The incoming application has to be split into the app name and the
+        * arguments (data).  The app name can be any storage type as add_extension
+        * copies it into its own buffer.  Data however, needs to be dynamically
+        * allocated and a free function provided.
+        */
+
+       paren = strchr(application, '(');
+       if (!paren) {
+               app = (char *)application;
+       } else {
+               app = ast_strdupa(application);
+               app[paren - application] = '\0';
+               data = ast_strdup(paren + 1);
+               if (!data) {
+                       return -1;
+               }
+               data[strlen(data) - 1] = '\0';
+               free_ptr = ast_free_ptr;
+               if (ast_strlen_zero(app) || ast_strlen_zero(data)) {
+                       ast_free(data);
+                       return -1;
+               }
+       }
+
+       /* Don't disturb existing, exact-match, entries. */
+       context_name = ast_get_context_name(context);
+       if ((existing_exten = pbx_find_extension(NULL, NULL, &find_info, context_name, exten,
+               priority, NULL, NULL, E_MATCH))) {
+               const char *existing_app = ast_get_extension_app(existing_exten);
+               const char *existing_data = ast_get_extension_app_data(existing_exten);
+               if (!strcmp(existing_app, app)
+                       && !strcmp(existing_data ? existing_data : "", data ? data : "")) {
+                       ast_free(data);
+                       return 0;
+               }
+
+               ast_context_remove_extension2(context, exten, priority, BASE_REGISTRAR, 1);
+       }
+
+       if (ast_add_extension2_nolock(context, 0, exten, priority, NULL, NULL,
+                       app, data, free_ptr, BASE_REGISTRAR, NULL, 0)) {
+               ast_free(data);
+               return -1;
+       }
+
+       return 0;
+}
+
+static int add_hints(const char *context, const char *exten, const char *application, const char *id)
+{
+       struct ast_context *hint_context;
+       char *hint_device;
+
+       hint_device = ast_alloca(strlen("PJSIP/") + strlen(id) + 1);
+       sprintf(hint_device, "PJSIP/%s", id);
+
+       /* We need the contexts list locked to safely be able to both read and lock the specific context within */
+       if (ast_wrlock_contexts()) {
+               ast_log(LOG_ERROR, "Failed to lock the contexts list.\n");
+               return -1;
+       }
+
+       if (!(hint_context = ast_context_find_or_create(NULL, NULL, context, BASE_REGISTRAR))) {
+               ast_log(LOG_ERROR, "Unable to find or create hint context '%s'\n", context);
+               if (ast_unlock_contexts()) {
+                       ast_assert(0);
+               }
+               return -1;
+       }
+
+       /* Transfer the all-contexts lock to the specific context */
+       if (ast_wrlock_context(hint_context)) {
+               ast_unlock_contexts();
+               ast_log(LOG_ERROR, "failed to obtain write lock on context\n");
+               return -1;
+       }
+       ast_unlock_contexts();
+
+       if (add_extension(hint_context, exten, PRIORITY_HINT, hint_device)) {
+               ast_log(LOG_ERROR, "Failed to add hint '%s@%s' to the PBX.\n",
+                       exten, context);
+       }
+
+       if (!ast_strlen_zero(application)) {
+               if (add_extension(hint_context, exten, 1, application)) {
+                       ast_log(LOG_ERROR, "Failed to add hint '%s@%s' to the PBX.\n",
+                               exten, context);
+               }
+       } else {
+               ast_context_remove_extension2(hint_context, exten, 1, BASE_REGISTRAR, 1);
+       }
+
+       ast_unlock_context(hint_context);
+
+       return 0;
+}
+
 static int handle_auth(const struct ast_sorcery *sorcery, struct object_type_wizard *otw,
        struct ast_category *wiz, char *direction)
 {
@@ -351,7 +554,7 @@ static int handle_auth(const struct ast_sorcery *sorcery, struct object_type_wiz
        }
 
        if (is_variable_true(wizvars, test_variable)) {
-               if (!ast_variable_find_in_list(vars, "username")) {
+               if (!ast_variable_find_last_in_list(vars, "username")) {
                        ast_log(LOG_ERROR,
                                "Wizard '%s' must have '%s_auth/username' if it %s.\n", id, direction, test_variable);
                        return -1;
@@ -369,7 +572,7 @@ static int handle_auth(const struct ast_sorcery *sorcery, struct object_type_wiz
        variable_list_append_return(&vars, "@pjsip_wizard", id);
 
        /* If the user set auth_type, don't override it. */
-       if (!ast_variable_find_in_list(vars, "auth_type")) {
+       if (!ast_variable_find_last_in_list(vars, "auth_type")) {
                variable_list_append_return(&vars, "auth_type", "userpass");
        }
 
@@ -405,14 +608,19 @@ static int handle_aor(const struct ast_sorcery *sorcery, struct object_type_wiza
        struct ast_sorcery_object *obj = NULL;
        const char *id = ast_category_get_name(wiz);
        const char *contact_pattern;
+       const char *outbound_proxy = ast_variable_find_last_in_list(wizvars, "outbound_proxy");
        int host_count = AST_VECTOR_SIZE(remote_hosts_vector);
        RAII_VAR(struct ast_variable *, vars, get_object_variables(wizvars, "aor/"), ast_variables_destroy);
 
        variable_list_append(&vars, "@pjsip_wizard", id);
 
+       if (!ast_strlen_zero(outbound_proxy)) {
+               variable_list_append_return(&vars, "outbound_proxy", outbound_proxy);
+       }
+
        /* If the user explicitly specified an aor/contact, don't use remote hosts. */
-       if (!ast_variable_find_in_list(vars, "contact")) {
-               if (!(contact_pattern = ast_variable_find_in_list(wizvars, "contact_pattern"))) {
+       if (!ast_variable_find_last_in_list(vars, "contact")) {
+               if (!(contact_pattern = ast_variable_find_last_in_list(wizvars, "contact_pattern"))) {
                        contact_pattern = "sip:${REMOTE_HOST}";
                }
 
@@ -457,13 +665,35 @@ static int handle_endpoint(const struct ast_sorcery *sorcery, struct object_type
        struct ast_variable *wizvars = ast_category_first(wiz);
        struct ast_sorcery_object *obj = NULL;
        const char *id = ast_category_get_name(wiz);
-       const char *transport = ast_variable_find_in_list(wizvars, "transport");
+       const char *outbound_proxy = ast_variable_find_last_in_list(wizvars, "outbound_proxy");
+       const char *transport = ast_variable_find_last_in_list(wizvars, "transport");
+       const char *hint_context = hint_context = ast_variable_find_last_in_list(wizvars, "hint_context");
+       const char *hint_exten = ast_variable_find_last_in_list(wizvars, "hint_exten");
+       const char *hint_application= ast_variable_find_last_in_list(wizvars, "hint_application");
        char new_id[strlen(id) + MAX_ID_SUFFIX];
        RAII_VAR(struct ast_variable *, vars, get_object_variables(wizvars, "endpoint/"), ast_variables_destroy);
 
        variable_list_append_return(&vars, "@pjsip_wizard", id);
        variable_list_append_return(&vars, "aors", id);
 
+       if (!ast_strlen_zero(outbound_proxy)) {
+               variable_list_append_return(&vars, "outbound_proxy", outbound_proxy);
+       }
+
+       if (ast_strlen_zero(hint_context)) {
+               hint_context = ast_variable_find_last_in_list(vars, "context");
+       }
+
+       if (ast_strlen_zero(hint_context)) {
+               hint_context = "default";
+       }
+
+       if (!ast_strlen_zero(hint_exten)) {
+               /* These are added so we can find and delete the hints when the endpoint gets deleted */
+               variable_list_append_return(&vars, "@hint_context", hint_context);
+               variable_list_append_return(&vars, "@hint_exten", hint_exten);
+       }
+
        if (!ast_strlen_zero(transport)) {
                variable_list_append_return(&vars, "transport", transport);
        }
@@ -488,6 +718,14 @@ static int handle_endpoint(const struct ast_sorcery *sorcery, struct object_type
        }
        ao2_ref(obj, -1);
 
+       if (!ast_strlen_zero(hint_exten)) {
+               if (is_variable_true(wizvars, "has_hint")) {
+                       add_hints(hint_context, hint_exten, hint_application, id);
+               } else {
+                       delete_extens(hint_context, hint_exten);
+               }
+       }
+
        return 0;
 }
 
@@ -504,8 +742,9 @@ static int handle_identify(const struct ast_sorcery *sorcery, struct object_type
 
        snprintf(new_id, sizeof(new_id), "%s-identify", id);
 
-       /* If accepting registrations, we don't need an identify. */
-       if (is_variable_true(wizvars, "accepts_registrations")) {
+       /* If accepting registrations or we're sending line, we don't need an identify. */
+       if (is_variable_true(wizvars, "accepts_registrations")
+               || is_variable_true(wizvars, "sends_line_with_registrations")) {
                /* If one exists, delete it. */
                obj = otw->wizard->retrieve_id(sorcery, otw->wizard_data, "identify", new_id);
                if (obj) {
@@ -524,7 +763,7 @@ static int handle_identify(const struct ast_sorcery *sorcery, struct object_type
        variable_list_append_return(&vars, "endpoint", id);
        variable_list_append_return(&vars, "@pjsip_wizard", id);
 
-       if (!ast_variable_find_in_list(vars, "match")) {
+       if (!ast_variable_find_last_in_list(vars, "match")) {
                for (host_counter = 0; host_counter < host_count; host_counter++) {
                        char *rhost = AST_VECTOR_GET(remote_hosts_vector, host_counter);
                        char host[strlen(rhost) + 1];
@@ -574,7 +813,7 @@ static int handle_phoneprov(const struct ast_sorcery *sorcery, struct object_typ
                return 0;
        }
 
-       if (!ast_variable_find_in_list(wizvars, "phoneprov/MAC")) {
+       if (!ast_variable_find_last_in_list(wizvars, "phoneprov/MAC")) {
                ast_log(LOG_ERROR,
                        "Wizard '%s' must have 'phoneprov/MAC' if it has_phoneprov.\n", id);
                return -1;
@@ -600,6 +839,14 @@ static int delete_existing_cb(void *obj, void *arg, int flags)
 {
        struct object_type_wizard *otw = arg;
 
+       if (!strcmp(otw->object_type, "endpoint")) {
+               const char *context = ast_sorcery_object_get_extended(obj, "hint_context");
+               const char *exten = ast_sorcery_object_get_extended(obj, "hint_exten");
+               if (!ast_strlen_zero(context) && !ast_strlen_zero(exten)) {
+                       delete_extens(context, exten);
+               }
+       }
+
        otw->wizard->delete(otw->sorcery, otw->wizard_data, obj);
 
        return CMP_MATCH;
@@ -613,7 +860,8 @@ static int handle_registrations(const struct ast_sorcery *sorcery, struct object
        const char *id = ast_category_get_name(wiz);
        const char *server_uri_pattern;
        const char *client_uri_pattern;
-       const char *transport = ast_variable_find_in_list(wizvars, "transport");
+       const char *outbound_proxy = ast_variable_find_last_in_list(wizvars, "outbound_proxy");
+       const char *transport = ast_variable_find_last_in_list(wizvars, "transport");
        const char *username;
        char new_id[strlen(id) + MAX_ID_SUFFIX];
        int host_count = AST_VECTOR_SIZE(remote_hosts_vector);
@@ -632,6 +880,10 @@ static int handle_registrations(const struct ast_sorcery *sorcery, struct object
                return -1;
        }
 
+       if (!ast_strlen_zero(outbound_proxy)) {
+               variable_list_append_return(&vars, "outbound_proxy", outbound_proxy);
+       }
+
        otw->wizard->retrieve_multiple(sorcery, otw->wizard_data, "registration", existing, search);
        ast_variables_destroy(search);
 
@@ -650,16 +902,16 @@ static int handle_registrations(const struct ast_sorcery *sorcery, struct object
 
        variable_list_append_return(&vars, "@pjsip_wizard", id);
 
-       if (!(server_uri_pattern = ast_variable_find_in_list(wizvars, "server_uri_pattern"))) {
+       if (!(server_uri_pattern = ast_variable_find_last_in_list(wizvars, "server_uri_pattern"))) {
                server_uri_pattern = "sip:${REMOTE_HOST}";
        }
 
-       if (!(client_uri_pattern = ast_variable_find_in_list(wizvars, "client_uri_pattern"))) {
+       if (!(client_uri_pattern = ast_variable_find_last_in_list(wizvars, "client_uri_pattern"))) {
                client_uri_pattern = "sip:${USERNAME}@${REMOTE_HOST}";
        }
 
        if(is_variable_true(wizvars, "sends_auth")) {
-               username = ast_variable_find_in_list(wizvars, "outbound_auth/username");
+               username = ast_variable_find_last_in_list(wizvars, "outbound_auth/username");
        } else {
                username = id;
        }
@@ -702,6 +954,11 @@ static int handle_registrations(const struct ast_sorcery *sorcery, struct object
                        variable_list_append_return(&registration_vars, "transport", transport);
                }
 
+               if (is_variable_true(wizvars, "sends_line_with_registrations")) {
+                       variable_list_append_return(&registration_vars, "line", "yes");
+                       variable_list_append_return(&registration_vars, "endpoint", id);
+               }
+
                snprintf(new_id, sizeof(new_id), "%s-reg-%d", id, host_counter);
 
                obj = create_object(sorcery, new_id, "registration", registration_vars);
@@ -737,14 +994,17 @@ static int wizard_apply_handler(const struct ast_sorcery *sorcery, struct object
        int rc = -1;
 
        AST_VECTOR_INIT(&remote_hosts_vector, 16);
-       remote_hosts = ast_variable_find_in_list(wizvars, "remote_hosts");
+       remote_hosts = ast_variable_find_last_in_list(wizvars, "remote_hosts");
 
        if (!ast_strlen_zero(remote_hosts)) {
                char *host;
                char *hosts = ast_strdupa(remote_hosts);
 
                while ((host = ast_strsep(&hosts, ',', AST_STRSEP_TRIM))) {
-                       AST_VECTOR_APPEND(&remote_hosts_vector, ast_strdup(host));
+                       host = ast_strdup(host);
+                       if (host && AST_VECTOR_APPEND(&remote_hosts_vector, host)) {
+                               ast_free(host);
+                       }
                }
        }
 
@@ -764,7 +1024,7 @@ static int wizard_apply_handler(const struct ast_sorcery *sorcery, struct object
                rc = handle_registrations(sorcery, otw, wiz, &remote_hosts_vector);
        }
 
-       AST_VECTOR_REMOVE_CMP_UNORDERED(&remote_hosts_vector, NULL, NOT_EQUALS, ast_free);
+       AST_VECTOR_REMOVE_ALL_CMP_UNORDERED(&remote_hosts_vector, NULL, NOT_EQUALS, ast_free);
        AST_VECTOR_FREE(&remote_hosts_vector);
 
        ast_debug(4, "%s handler complete.  rc: %d\n", otw->object_type, rc);
@@ -776,6 +1036,7 @@ static int wizard_apply_handler(const struct ast_sorcery *sorcery, struct object
  * Everything below are the sorcery observers.
  */
 static void instance_created_observer(const char *name, struct ast_sorcery *sorcery);
+static void instance_destroying_observer(const char *name, struct ast_sorcery *sorcery);
 static void object_type_loaded_observer(const char *name,
        const struct ast_sorcery *sorcery, const char *object_type, int reloaded);
 static void wizard_mapped_observer(const char *name, struct ast_sorcery *sorcery,
@@ -786,6 +1047,7 @@ static void object_type_registered_observer(const char *name,
 
 const static struct ast_sorcery_global_observer global_observer = {
        .instance_created = instance_created_observer,
+       .instance_destroying = instance_destroying_observer,
 };
 
 struct ast_sorcery_instance_observer observer = {
@@ -909,13 +1171,22 @@ static void wizard_mapped_observer(const char *name, struct ast_sorcery *sorcery
        /* We're only interested in memory wizards with the pjsip_wizard tag. */
        if (wizard_args && !strcmp(wizard_args, "pjsip_wizard")) {
                otw = ast_malloc(sizeof(*otw) + strlen(object_type) + 1);
+               if (!otw) {
+                       return;
+               }
+
                otw->sorcery = sorcery;
                otw->wizard = wizard;
                otw->wizard_data = wizard_data;
                otw->last_config = NULL;
                strcpy(otw->object_type, object_type); /* Safe */
-               AST_VECTOR_APPEND(&object_type_wizards, otw);
-               ast_debug(1, "Wizard mapped for object_type '%s'\n", object_type);
+               AST_VECTOR_RW_WRLOCK(&object_type_wizards);
+               if (AST_VECTOR_APPEND(&object_type_wizards, otw)) {
+                       ast_free(otw);
+               } else {
+                       ast_debug(1, "Wizard mapped for object_type '%s'\n", object_type);
+               }
+               AST_VECTOR_RW_UNLOCK(&object_type_wizards);
        }
 }
 
@@ -929,76 +1200,150 @@ static void object_type_registered_observer(const char *name,
 }
 
 /*! \brief When the res_pjsip instance is created, add an observer to it and initialize the wizard vector.
- * Since you can't unload res_pjsip, this will only ever be called once.
+ * Also, bump the module's ref count so it can't be unloaded before the sorcery instance is
+ * destroyed.
  */
 static void instance_created_observer(const char *name, struct ast_sorcery *sorcery)
 {
        if (strcmp(name, "res_pjsip")) {
                return;
        }
-
+       ast_module_ref(ast_module_info->self);
        ast_sorcery_instance_observer_add(sorcery, &observer);
 }
 
-static int load_module(void)
+/*! \brief When the res_pjsip instance is destroyed, remove the observer
+ * and unref the module.  This should then allow this module to unload cleanly.
+ */
+static void instance_destroying_observer(const char *name, struct ast_sorcery *sorcery)
 {
-       struct ast_sorcery *sorcery = NULL;
-       int i;
+       if (strcmp(name, "res_pjsip")) {
+               return;
+       }
 
-       AST_VECTOR_INIT(&object_type_wizards, 12);
-       ast_sorcery_global_observer_add(&global_observer);
+       ast_sorcery_instance_observer_remove(sorcery, &observer);
+       ast_module_unref(ast_module_info->self);
+}
 
-       /* If this module is loading AFTER res_pjsip, we need to manually add the instance observer
-        * and map the wizards because the observers will never get triggered.
-        * The we neeed to schedule a reload.
-        */
-       if (ast_module_check("res_pjsip.so") && ast_sip_get_pjsip_endpoint()) {
-               sorcery = ast_sip_get_sorcery();
-               if (sorcery) {
-                       /* Clean up and add the observer. */
-                       ast_sorcery_instance_observer_remove(sorcery, &observer);
-                       ast_sorcery_instance_observer_add(sorcery, &observer);
-
-                       for (i = 0; object_types[i]; i++) {
-                               ast_sorcery_apply_wizard_mapping(sorcery, object_types[i], "memory",
-                                       "pjsip_wizard", 0);
-                       }
+static char *handle_export_primitives(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+       struct ast_sorcery *sorcery;
+       int idx;
+       FILE *f = NULL;
+       const char *fn = NULL;
+
+       switch (cmd) {
+       case CLI_INIT:
+               e->command = "pjsip export config_wizard primitives [to]";
+               e->usage =
+                       "Usage: pjsip export config_wizard primitives [ to <filename ]\n"
+                       "       Export the config_wizard objects as pjsip primitives to\n"
+                       "       the console or to <filename>\n";
+               return NULL;
+       case CLI_GENERATE:
+               return NULL;
+       }
 
-                       ast_module_reload("res_pjsip.so");
+       if (a->argc > 5) {
+               char date[256]="";
+               time_t t;
+               fn = a->argv[5];
+
+               time(&t);
+               ast_copy_string(date, ctime(&t), sizeof(date));
+               f = fopen(fn, "w");
+               if (!f) {
+                       ast_log(LOG_ERROR, "Unable to write %s (%s)\n", fn, strerror(errno));
+                       return CLI_FAILURE;
                }
+
+               fprintf(f, ";!\n");
+               fprintf(f, ";! Automatically generated configuration file\n");
+               fprintf(f, ";! Filename: %s\n", fn);
+               fprintf(f, ";! Generator: %s\n", "'pjsip export config_wizard primitives'");
+               fprintf(f, ";! Creation Date: %s", date);
+               fprintf(f, ";!\n");
        }
 
-       return AST_MODULE_LOAD_SUCCESS;
-}
+       sorcery = ast_sip_get_sorcery();
 
-static int unload_module(void)
-{
-       struct object_type_wizard *otw;
-       int i;
-
-       ast_sorcery_global_observer_remove(&global_observer);
+       AST_VECTOR_RW_RDLOCK(&object_type_wizards);
+       for(idx = 0; idx < AST_VECTOR_SIZE(&object_type_wizards); idx++) {
+               struct object_type_wizard *otw = AST_VECTOR_GET(&object_type_wizards, idx);
+               struct ao2_container *container;
+               struct ao2_iterator i;
+               void *o;
 
-       for (i = 0; object_types[i]; i++) {
-               RAII_VAR(struct ao2_container *, existing,
-                       ao2_container_alloc_list(AO2_ALLOC_OPT_LOCK_NOLOCK, 0, NULL, NULL), ao2_cleanup);
+               container = ast_sorcery_retrieve_by_fields(sorcery, otw->object_type, AST_RETRIEVE_FLAG_MULTIPLE, NULL);
+               if (!container) {
+                       continue;
+               }
 
-               otw = find_wizard(object_types[i]);
-               if (otw->sorcery) {
-                       ast_sorcery_instance_observer_remove(otw->sorcery, &observer);
+               i = ao2_iterator_init(container, 0);
+               while ((o = ao2_iterator_next(&i))) {
+                       struct ast_variable *vars;
+                       struct ast_variable *v;
+
+                       vars = ast_sorcery_objectset_create(sorcery, o);
+                       if (vars && ast_variable_find_in_list(vars, "@pjsip_wizard")) {
+                               if (f) {
+                                       fprintf(f, "\n[%s]\ntype = %s\n", ast_sorcery_object_get_id(o), otw->object_type);
+                               } else {
+                                       ast_cli(a->fd, "\n[%s]\ntype = %s\n", ast_sorcery_object_get_id(o), otw->object_type);
+                               }
+                               for (v = vars; v; v = v->next) {
+                                       if (!ast_strlen_zero(v->value)) {
+                                               if (f) {
+                                                       fprintf(f, "%s = %s\n", v->name, v->value);
+                                               } else {
+                                                       ast_cli(a->fd, "%s = %s\n", v->name, v->value);
+                                               }
+                                       }
+                               }
+                       }
+                       ast_variables_destroy(vars);
+                       ao2_ref(o, -1);
                }
+               ao2_iterator_destroy(&i);
+               ao2_cleanup(container);
+       }
+       AST_VECTOR_RW_UNLOCK(&object_type_wizards);
 
-               otw->wizard->retrieve_multiple(otw->sorcery, otw->wizard_data, object_types[i], existing, NULL);
-               ao2_callback(existing, OBJ_NODATA | OBJ_UNLINK | OBJ_MULTIPLE, delete_existing_cb, otw);
+       if (f) {
+               fclose(f);
+               ast_cli(a->fd, "Wrote configuration to %s\n", fn);
        }
 
-       AST_VECTOR_REMOVE_CMP_UNORDERED(&object_type_wizards, NULL, NOT_EQUALS, OTW_DELETE_CB);
-       AST_VECTOR_FREE(&object_type_wizards);
+
+       return CLI_SUCCESS;
+}
+
+static struct ast_cli_entry config_wizard_cli[] = {
+       AST_CLI_DEFINE(handle_export_primitives, "Export config wizard primitives"),
+};
+
+static int load_module(void)
+{
+       AST_VECTOR_RW_INIT(&object_type_wizards, 12);
+       ast_sorcery_global_observer_add(&global_observer);
+       ast_cli_register_multiple(config_wizard_cli, ARRAY_LEN(config_wizard_cli));
+
+       return AST_MODULE_LOAD_SUCCESS;
+}
+
+static int unload_module(void)
+{
+       ast_cli_unregister_multiple(config_wizard_cli, ARRAY_LEN(config_wizard_cli));
+       ast_sorcery_global_observer_remove(&global_observer);
+       AST_VECTOR_REMOVE_ALL_CMP_UNORDERED(&object_type_wizards, NULL, NOT_EQUALS, OTW_DELETE_CB);
+       AST_VECTOR_RW_FREE(&object_type_wizards);
 
        return 0;
 }
 
 AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_GLOBAL_SYMBOLS | AST_MODFLAG_LOAD_ORDER, "PJSIP Config Wizard",
-               .load = load_module,
-               .unload = unload_module,
-               .load_pri = AST_MODPRI_REALTIME_DRIVER,
-               );
+       .support_level = AST_MODULE_SUPPORT_CORE,
+       .load = load_module,
+       .unload = unload_module,
+       .load_pri = AST_MODPRI_REALTIME_DRIVER,
+);