chan_sip: Allow Asterisk to retry after 403 on register
[asterisk/asterisk.git] / res / res_pjsip_outbound_registration.c
index 6d9368e..4a8b46a 100644 (file)
@@ -30,6 +30,8 @@
 #include "asterisk/res_pjsip.h"
 #include "asterisk/module.h"
 #include "asterisk/taskprocessor.h"
+#include "asterisk/cli.h"
+#include "asterisk/stasis_system.h"
 
 /*** DOCUMENTATION
        <configInfo name="res_pjsip_outbound_registration" language="en_US">
                                </configOption>
                                <configOption name="client_uri">
                                        <synopsis>Client SIP URI used when attemping outbound registration</synopsis>
+                                       <description><para>
+                                               This is the address-of-record for the outbound registration (i.e. the URI in
+                                               the To header of the REGISTER).</para>
+                                               <para>For registration with an ITSP, the client SIP URI may need to consist of
+                                               an account name or number and the provider's hostname for their registrar, e.g.
+                                               client_uri=1234567890@example.com. This may differ between providers.</para>
+                                               <para>For registration to generic registrars, the client SIP URI will depend
+                                               on networking specifics and configuration of the registrar.
+                                       </para></description>
                                </configOption>
                                <configOption name="contact_user">
                                        <synopsis>Contact User to use in request</synopsis>
                                <configOption name="retry_interval" default="60">
                                        <synopsis>Interval in seconds between retries if outbound registration is unsuccessful</synopsis>
                                </configOption>
+                               <configOption name="forbidden_retry_interval" default="0">
+                                       <synopsis>Interval used when receiving a 403 Forbidden response.</synopsis>
+                                       <description><para>
+                                               If a 403 Forbidden is received, chan_pjsip will wait
+                                               <replaceable>forbidden_retry_interval</replaceable> seconds before
+                                               attempting registration again. If 0 is specified, chan_pjsip will not
+                                               retry after receiving a 403 Forbidden response. Setting this to a non-zero
+                                               value goes against a "SHOULD NOT" in RFC3261, but can be used to work around
+                                               buggy registrars.
+                                       </para></description>
+                               </configOption>
                                <configOption name="server_uri">
                                        <synopsis>SIP URI of the server to register against</synopsis>
+                                       <description><para>
+                                               This is the URI at which to find the registrar to send the outbound REGISTER. This URI
+                                               is used as the request URI of the outbound REGISTER request from Asterisk.</para>
+                                               <para>For registration with an ITSP, the setting may often be just the domain of
+                                               the registrar, e.g. sip:sip.example.com.
+                                       </para></description>
                                </configOption>
                                <configOption name="transport">
                                        <synopsis>Transport used for outbound authentication</synopsis>
                        </configObject>
                </configFile>
        </configInfo>
+       <manager name="PJSIPUnregister" language="en_US">
+               <synopsis>
+                       Unregister an outbound registration.
+               </synopsis>
+               <syntax>
+                       <xi:include xpointer="xpointer(/docs/manager[@name='Login']/syntax/parameter[@name='ActionID'])" />
+                       <parameter name="Registration" required="true">
+                               <para>The outbound registration to unregister.</para>
+                       </parameter>
+               </syntax>
+       </manager>
  ***/
 
 /*! \brief Amount of buffer time (in seconds) before expiration that we re-register at */
@@ -109,6 +148,14 @@ enum sip_outbound_registration_status {
        SIP_REGISTRATION_STOPPED,
 };
 
+static const char *sip_outbound_registration_status_str[] = {
+       [SIP_REGISTRATION_UNREGISTERED] = "Unregistered",
+       [SIP_REGISTRATION_REGISTERED] = "Registered",
+       [SIP_REGISTRATION_REJECTED_TEMPORARY] = "Rejected",
+       [SIP_REGISTRATION_REJECTED_PERMANENT] = "Rejected",
+       [SIP_REGISTRATION_STOPPED] = "Stopped",
+};
+
 /*! \brief Outbound registration client state information (persists for lifetime of regc) */
 struct sip_outbound_registration_client_state {
        /*! \brief Current status of this registration */
@@ -123,6 +170,8 @@ struct sip_outbound_registration_client_state {
        unsigned int max_retries;
        /*! \brief Interval at which retries should occur for temporal responses */
        unsigned int retry_interval;
+       /*! \brief Interval at which retries should occur for permanent responses */
+       unsigned int forbidden_retry_interval;
        /*! \brief Treat authentication challenges that we cannot handle as permanent failures */
        unsigned int auth_rejection_permanent;
        /*! \brief Serializer for stuff and things */
@@ -162,6 +211,8 @@ struct sip_outbound_registration {
        unsigned int expiration;
        /*! \brief Interval at which retries should occur for temporal responses */
        unsigned int retry_interval;
+       /*! \brief Interval at which retries should occur for permanent responses */
+       unsigned int forbidden_retry_interval;
        /*! \brief Treat authentication challenges that we cannot handle as permanent failures */
        unsigned int auth_rejection_permanent;
        /*! \brief Maximum number of retries permitted */
@@ -188,6 +239,8 @@ static int handle_client_registration(void *data)
 {
        RAII_VAR(struct sip_outbound_registration_client_state *, client_state, data, ao2_cleanup);
        pjsip_tx_data *tdata;
+       pjsip_regc_info info;
+       char server_uri[PJSIP_MAX_URL_SIZE], client_uri[PJSIP_MAX_URL_SIZE];
 
        cancel_registration(client_state);
 
@@ -196,11 +249,16 @@ static int handle_client_registration(void *data)
                return 0;
        }
 
+       pjsip_regc_get_info(client_state->client, &info);
+       ast_copy_pj_str(server_uri, &info.server_uri, sizeof(server_uri));
+       ast_copy_pj_str(client_uri, &info.client_uri, sizeof(client_uri));
+       ast_debug(3, "REGISTER attempt %d to '%s' with client '%s'\n",
+                 client_state->retries + 1, server_uri, client_uri);
+
        /* Due to the registration the callback may now get called, so bump the ref count */
        ao2_ref(client_state, +1);
        if (pjsip_regc_send(client_state->client, tdata) != PJ_SUCCESS) {
                ao2_ref(client_state, -1);
-               pjsip_tx_data_dec_ref(tdata);
        }
 
        return 0;
@@ -209,7 +267,7 @@ static int handle_client_registration(void *data)
 /*! \brief Timer callback function, used just for registrations */
 static void sip_outbound_registration_timer_cb(pj_timer_heap_t *timer_heap, struct pj_timer_entry *entry)
 {
-       RAII_VAR(struct sip_outbound_registration_client_state *, client_state, entry->user_data, ao2_cleanup);
+       struct sip_outbound_registration_client_state *client_state = entry->user_data;
 
        ao2_ref(client_state, +1);
        if (ast_sip_push_task(client_state->serializer, handle_client_registration, client_state)) {
@@ -333,7 +391,10 @@ static int handle_registration_response(void *data)
                pjsip_tx_data *tdata;
                if (!ast_sip_create_request_with_auth(&response->client_state->outbound_auths,
                                response->rdata, response->tsx, &tdata)) {
-                       pjsip_regc_send(response->client_state->client, tdata);
+                       ao2_ref(response->client_state, +1);
+                       if (pjsip_regc_send(response->client_state->client, tdata) != PJ_SUCCESS) {
+                               ao2_cleanup(response->client_state);
+                       }
                        return 0;
                }
                /* Otherwise, fall through so the failure is processed appropriately */
@@ -341,6 +402,7 @@ static int handle_registration_response(void *data)
 
        if (PJSIP_IS_STATUS_IN_CLASS(response->code, 200)) {
                /* If the registration went fine simply reschedule registration for the future */
+               ast_debug(1, "Outbound registration to '%s' with client '%s' successful\n", server_uri, client_uri);
                response->client_state->status = SIP_REGISTRATION_REGISTERED;
                response->client_state->retries = 0;
                schedule_registration(response->client_state, response->expiration - REREGISTER_BUFFER_TIME);
@@ -365,12 +427,25 @@ static int handle_registration_response(void *data)
                                response->code, server_uri, client_uri, response->client_state->retry_interval);
                }
        } else {
-               /* Finally if there's no hope of registering give up */
-               response->client_state->status = SIP_REGISTRATION_REJECTED_PERMANENT;
-               ast_log(LOG_WARNING, "Fatal response '%d' received from '%s' on registration attempt to '%s', stopping outbound registration\n",
-                       response->code, server_uri, client_uri);
+               if (response->code == 403
+                       && response->client_state->forbidden_retry_interval
+                       && response->client_state->retries < response->client_state->max_retries) {
+                       /* A forbidden response retry interval is configured and there are retries remaining */
+                       response->client_state->status = SIP_REGISTRATION_REJECTED_TEMPORARY;
+                       response->client_state->retries++;
+                       schedule_registration(response->client_state, response->client_state->forbidden_retry_interval);
+                       ast_log(LOG_WARNING, "403 Forbidden fatal response received from '%s' on registration attempt to '%s', retrying in '%d' seconds\n",
+                               server_uri, client_uri, response->client_state->forbidden_retry_interval);
+               } else {
+                       /* Finally if there's no hope of registering give up */
+                       response->client_state->status = SIP_REGISTRATION_REJECTED_PERMANENT;
+                       ast_log(LOG_WARNING, "Fatal response '%d' received from '%s' on registration attempt to '%s', stopping outbound registration\n",
+                               response->code, server_uri, client_uri);
+               }
        }
 
+       ast_system_publish_registry("PJSIP", client_uri, server_uri, sip_outbound_registration_status_str[response->client_state->status], NULL);
+
        /* If deferred destruction is in use see if we need to destroy now */
        if (response->client_state->destroy) {
                handle_client_state_destruction(response->client_state);
@@ -388,6 +463,7 @@ static void sip_outbound_registration_response_cb(struct pjsip_regc_cbparam *par
        response->code = param->code;
        response->expiration = param->expiration;
        response->client_state = client_state;
+       ao2_ref(response->client_state, +1);
 
        if (param->rdata) {
                struct pjsip_retry_after_hdr *retry_after = pjsip_msg_find_hdr(param->rdata->msg_info.msg, PJSIP_H_RETRY_AFTER, NULL);
@@ -397,8 +473,6 @@ static void sip_outbound_registration_response_cb(struct pjsip_regc_cbparam *par
                pjsip_rx_data_clone(param->rdata, 0, &response->rdata);
        }
 
-       ao2_ref(response->client_state, +1);
-
        if (ast_sip_push_task(client_state->serializer, handle_registration_response, response)) {
                ast_log(LOG_WARNING, "Failed to pass incoming registration response to threadpool\n");
                ao2_cleanup(response);
@@ -578,6 +652,8 @@ static int sip_outbound_registration_regc_alloc(void *data)
                RAII_VAR(struct ast_sip_transport *, transport, ast_sorcery_retrieve_by_id(ast_sip_get_sorcery(), "transport", registration->transport), ao2_cleanup);
 
                if (!transport || !transport->state) {
+                       ast_log(LOG_ERROR, "Unable to retrieve PJSIP transport '%s' "
+                               " for outbound registration", registration->transport);
                        return -1;
                }
 
@@ -666,6 +742,7 @@ static int sip_outbound_registration_perform(void *data)
        }
        registration->state->client_state->outbound_auths.num = registration->outbound_auths.num;
        registration->state->client_state->retry_interval = registration->retry_interval;
+       registration->state->client_state->forbidden_retry_interval = registration->forbidden_retry_interval;
        registration->state->client_state->max_retries = registration->max_retries;
        registration->state->client_state->retries = 0;
 
@@ -704,6 +781,147 @@ static int outbound_auth_handler(const struct aco_option *opt, struct ast_variab
        return ast_sip_auth_array_init(&registration->outbound_auths, var->value);
 }
 
+static struct sip_outbound_registration *retrieve_registration(const char *registration_name)
+{
+       return ast_sorcery_retrieve_by_id(
+               ast_sip_get_sorcery(),
+               "registration",
+               registration_name);
+}
+
+static int unregister_task(void *obj)
+{
+       RAII_VAR(struct sip_outbound_registration*, registration, obj, ao2_cleanup);
+       struct pjsip_regc *client = registration->state->client_state->client;
+       pjsip_tx_data *tdata;
+
+       if (pjsip_regc_unregister(client, &tdata) != PJ_SUCCESS) {
+               return 0;
+       }
+
+       ao2_ref(registration->state->client_state, +1);
+       if (pjsip_regc_send(client, tdata) != PJ_SUCCESS) {
+               ao2_cleanup(registration->state->client_state);
+       }
+
+       return 0;
+}
+
+static int queue_unregister(struct sip_outbound_registration *registration)
+{
+       ao2_ref(registration, +1);
+       if (ast_sip_push_task(registration->state->client_state->serializer, unregister_task, registration)) {
+               ao2_cleanup(registration);
+               return -1;
+       }
+       return 0;
+}
+
+static char *cli_complete_registration(const char *line, const char *word,
+int pos, int state)
+{
+       char *result = NULL;
+       int wordlen;
+       int which = 0;
+       struct sip_outbound_registration *registration;
+       RAII_VAR(struct ao2_container *, registrations, NULL, ao2_cleanup);
+       struct ao2_iterator i;
+
+       if (pos != 3) {
+               return NULL;
+       }
+
+       wordlen = strlen(word);
+       registrations = ast_sorcery_retrieve_by_fields(ast_sip_get_sorcery(), "registration",
+               AST_RETRIEVE_FLAG_MULTIPLE | AST_RETRIEVE_FLAG_ALL, NULL);
+       if (!registrations) {
+               return NULL;
+       }
+
+       i = ao2_iterator_init(registrations, 0);
+       while ((registration = ao2_iterator_next(&i))) {
+               const char *name = ast_sorcery_object_get_id(registration);
+               if (!strncasecmp(word, name, wordlen) && ++which > state) {
+                       result = ast_strdup(name);
+               }
+
+               ao2_cleanup(registration);
+               if (result) {
+                       break;
+               }
+       }
+       ao2_iterator_destroy(&i);
+       return result;
+}
+
+static char *cli_unregister(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+       RAII_VAR(struct sip_outbound_registration *, registration, NULL, ao2_cleanup);
+       const char *registration_name;
+
+       switch (cmd) {
+       case CLI_INIT:
+               e->command = "pjsip send unregister";
+               e->usage =
+                       "Usage: pjsip send unregister <registration>\n"
+                       "       Send a SIP REGISTER request to the specified outbound "
+                       "registration with an expiration of 0. This will cause the contact "
+                       "added by this registration to be removed on the remote system.\n";
+               return NULL;
+       case CLI_GENERATE:
+               return cli_complete_registration(a->line, a->word, a->pos, a->n);
+       }
+
+       if (a->argc != 4) {
+               return CLI_SHOWUSAGE;
+       }
+
+       registration_name = a->argv[3];
+
+       registration = retrieve_registration(registration_name);
+       if (!registration) {
+               ast_cli(a->fd, "Unable to retrieve registration %s\n", registration_name);
+               return CLI_FAILURE;
+       }
+
+       if (queue_unregister(registration)) {
+               ast_cli(a->fd, "Failed to queue unregistration");
+               return 0;
+       }
+
+       return CLI_SUCCESS;
+}
+
+static int ami_unregister(struct mansession *s, const struct message *m)
+{
+       const char *registration_name = astman_get_header(m, "Registration");
+       RAII_VAR(struct sip_outbound_registration *, registration, NULL, ao2_cleanup);
+
+       if (ast_strlen_zero(registration_name)) {
+               astman_send_error(s, m, "Registration parameter missing.");
+               return 0;
+       }
+
+       registration = retrieve_registration(registration_name);
+       if (!registration) {
+               astman_send_error(s, m, "Unable to retrieve registration entry\n");
+               return 0;
+       }
+
+
+       if (queue_unregister(registration)) {
+               astman_send_ack(s, m, "Failed to queue unregistration");
+               return 0;
+       }
+
+       astman_send_ack(s, m, "Unregistration sent");
+       return 0;
+}
+
+static struct ast_cli_entry cli_outbound_registration[] = {
+       AST_CLI_DEFINE(cli_unregister, "Send a REGISTER request to an outbound registration target with a expiration of 0")
+};
+
 static int load_module(void)
 {
        ast_sorcery_apply_default(ast_sip_get_sorcery(), "registration", "config", "pjsip.conf,criteria=type=registration");
@@ -720,12 +938,15 @@ static int load_module(void)
        ast_sorcery_object_field_register(ast_sip_get_sorcery(), "registration", "outbound_proxy", "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct sip_outbound_registration, outbound_proxy));
        ast_sorcery_object_field_register(ast_sip_get_sorcery(), "registration", "expiration", "3600", OPT_UINT_T, 0, FLDSET(struct sip_outbound_registration, expiration));
        ast_sorcery_object_field_register(ast_sip_get_sorcery(), "registration", "retry_interval", "60", OPT_UINT_T, 0, FLDSET(struct sip_outbound_registration, retry_interval));
+       ast_sorcery_object_field_register(ast_sip_get_sorcery(), "registration", "forbidden_retry_interval", "0", OPT_UINT_T, 0, FLDSET(struct sip_outbound_registration, forbidden_retry_interval));
        ast_sorcery_object_field_register(ast_sip_get_sorcery(), "registration", "max_retries", "10", OPT_UINT_T, 0, FLDSET(struct sip_outbound_registration, max_retries));
        ast_sorcery_object_field_register(ast_sip_get_sorcery(), "registration", "auth_rejection_permanent", "yes", OPT_BOOL_T, 1, FLDSET(struct sip_outbound_registration, auth_rejection_permanent));
        ast_sorcery_object_field_register_custom(ast_sip_get_sorcery(), "registration", "outbound_auth", "", outbound_auth_handler, NULL, 0, 0);
        ast_sorcery_reload_object(ast_sip_get_sorcery(), "registration");
        sip_outbound_registration_perform_all();
 
+       ast_cli_register_multiple(cli_outbound_registration, ARRAY_LEN(cli_outbound_registration));
+       ast_manager_register_xml("PJSIPUnregister", EVENT_FLAG_SYSTEM | EVENT_FLAG_REPORTING, ami_unregister);
        return AST_MODULE_LOAD_SUCCESS;
 }
 
@@ -738,6 +959,8 @@ static int reload_module(void)
 
 static int unload_module(void)
 {
+       ast_cli_unregister_multiple(cli_outbound_registration, ARRAY_LEN(cli_outbound_registration));
+       ast_manager_unregister("PJSIPUnregister");
        return 0;
 }