Merge "app_queue: Added reason pause of member"
authorMatt Jordan <mjordan@digium.com>
Mon, 2 Nov 2015 13:59:18 +0000 (07:59 -0600)
committerGerrit Code Review <gerrit2@gerrit.digium.api>
Mon, 2 Nov 2015 13:59:18 +0000 (07:59 -0600)
36 files changed:
CHANGES
Makefile
apps/app_statsd.c
channels/chan_dahdi.c
channels/chan_sip.c
channels/sig_pri.c
configs/samples/pjsip.conf.sample
contrib/ast-db-manage/config/versions/28ce1e718f05_add_fatal_response_interval.py [new file with mode: 0644]
contrib/scripts/autosupport
contrib/scripts/install_prereq
funcs/func_holdintercept.c [new file with mode: 0644]
include/asterisk/cdr.h
include/asterisk/res_pjsip.h
include/asterisk/res_pjsip_cli.h
include/asterisk/res_pjsip_pubsub.h
main/cdr.c
main/codec_builtin.c
main/format_cap.c
main/manager.c
main/strings.c
main/xmldoc.c
res/res_pjsip.c
res/res_pjsip/config_auth.c
res/res_pjsip/config_transport.c
res/res_pjsip/location.c
res/res_pjsip/pjsip_cli.c
res/res_pjsip/pjsip_configuration.c
res/res_pjsip_endpoint_identifier_ip.c
res/res_pjsip_exten_state.c
res/res_pjsip_mwi.c
res/res_pjsip_outbound_registration.c
res/res_pjsip_pubsub.c
res/res_pjsip_pubsub.exports.in
rest-api-templates/api.wiki.mustache
rest-api-templates/asterisk_processor.py
rest-api-templates/res_ari_resource.c.mustache

diff --git a/CHANGES b/CHANGES
index 4cf1043..78d5e6b 100644 (file)
--- a/CHANGES
+++ b/CHANGES
@@ -162,6 +162,13 @@ res_pjsip
    will be used instead. The new SIP resolver provides NAPTR support, improved
    SRV support, and AAAA record support.
 
+res_pjsip_outbound_registration
+-------------------------------
+* A new 'fatal_retry_interval' option has been added to outbound registration.
+  When set (default is zero), and upon receiving a failure response to an
+  outbound registration, registration is retried at the given interval up to
+  'max_retries'.
+
 CEL Backends
 ------------------
 
@@ -190,6 +197,24 @@ Queue
    the queue member was paused.
 
 ------------------------------------------------------------------------------
+--- Functionality changes from Asterisk 13.6.0 to Asterisk 13.7.0 ------------
+------------------------------------------------------------------------------
+
+Dialplan Functions
+------------------
+ * The HOLD_INTERCEPT dialplan function now actually exists in the source tree.
+   While support for the events was added in Asterisk 13.4.0, the function
+   accidentally never made it in. That function is now present, and will cause
+   the 'hold' raised by a channel to be intercepted and converted into an
+   event instead.
+
+
+res_pjsip
+------------------
+ * The ability to use "like" has been added to the pjsip list and show
+   CLI commands.  For instance: CLI> pjsip list endpoints like abc
+
+------------------------------------------------------------------------------
 --- Functionality changes from Asterisk 13.5.0 to Asterisk 13.6.0 ------------
 ------------------------------------------------------------------------------
 
index bfa1ad9..5bc6570 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -124,7 +124,7 @@ empty:=
 space:=$(empty) $(empty)
 ASTTOPDIR:=$(subst $(space),\$(space),$(CURDIR))
 
-# Overwite config files on "make samples"
+# Overwite config files on "make samples" or other config installation targets
 OVERWRITE=y
 
 # Include debug and macro symbols in the executables (-g) and profiling info (-pg)
@@ -651,7 +651,12 @@ install: badshell bininstall datafiles
        @echo " + configuration files (overwriting any      +"
        @echo " + existing config files), run:              +"
        @echo " +                                           +"
-       @echo " +               $(mK) samples               +"
+       @echo " + For generic reference documentation:      +"
+       @echo " +   $(mK) samples                           +"
+       @echo " +                                           +"
+       @echo " + For a sample basic PBX:                   +"
+       @echo " +   $(mK) basic-pbx                         +"
+       @echo " +                                           +"
        @echo " +                                           +"
        @echo " +-----------------  or ---------------------+"
        @echo " +                                           +"
@@ -669,24 +674,14 @@ isntall: install
 
 upgrade: bininstall
 
-# XXX why *.adsi is installed first ?
-adsi:
-       @echo Installing adsi config files...
-       $(INSTALL) -d "$(DESTDIR)$(ASTETCDIR)"
-       @for x in configs/samples/*.adsi; do \
-               dst="$(DESTDIR)$(ASTETCDIR)/`$(BASENAME) $$x`" ; \
-               if [ -f "$${dst}" ] ; then \
-                       echo "Overwriting $$x" ; \
-               else \
-                       echo "Installing $$x" ; \
-               fi ; \
-               $(INSTALL) -m 644 "$$x" "$(DESTDIR)$(ASTETCDIR)/`$(BASENAME) $$x`" ; \
-       done
 
-samples: adsi
-       @echo Installing other config files...
-       @for x in configs/samples/*.sample; do \
-               dst="$(DESTDIR)$(ASTETCDIR)/`$(BASENAME) $$x .sample`" ;        \
+# Install configuration files from the specified directory
+# Parameters:
+#  (1) the configuration directory to install from
+#  (2) the extension to strip off
+define INSTALL_CONFIGS
+       @for x in configs/$(1)/*$(2); do \
+               dst="$(DESTDIR)$(ASTETCDIR)/`$(BASENAME) $$x $(2)`"; \
                if [ -f "$${dst}" ]; then \
                        if [ "$(OVERWRITE)" = "y" ]; then \
                                if cmp -s "$${dst}" "$$x" ; then \
@@ -701,7 +696,7 @@ samples: adsi
                fi ; \
                echo "Installing file $$x"; \
                $(INSTALL) -m 644 "$$x" "$${dst}" ;\
-       done
+       done ; \
        if [ "$(OVERWRITE)" = "y" ]; then \
                echo "Updating asterisk.conf" ; \
                sed -e 's|^astetcdir.*$$|astetcdir => $(ASTETCDIR)|' \
@@ -718,10 +713,28 @@ samples: adsi
                        "$(DESTDIR)$(ASTCONFPATH)" > "$(DESTDIR)$(ASTCONFPATH).tmp" ; \
                $(INSTALL) -m 644 "$(DESTDIR)$(ASTCONFPATH).tmp" "$(DESTDIR)$(ASTCONFPATH)" ; \
                rm -f "$(DESTDIR)$(ASTCONFPATH).tmp" ; \
-       fi ; \
+       fi
+endef
+
+# XXX why *.adsi is installed first ?
+adsi:
+       @echo Installing adsi config files...
+       $(INSTALL) -d "$(DESTDIR)$(ASTETCDIR)"
+       @for x in configs/samples/*.adsi; do \
+               dst="$(DESTDIR)$(ASTETCDIR)/`$(BASENAME) $$x`" ; \
+               if [ -f "$${dst}" ] ; then \
+                       echo "Overwriting $$x" ; \
+               else \
+                       echo "Installing $$x" ; \
+               fi ; \
+               $(INSTALL) -m 644 "$$x" "$(DESTDIR)$(ASTETCDIR)/`$(BASENAME) $$x`" ; \
+       done
+
+samples: adsi
+       @echo Installing other config files...
+       $(call INSTALL_CONFIGS,samples,.sample)
        $(INSTALL) -d "$(DESTDIR)$(ASTSPOOLDIR)/voicemail/default/1234/INBOX"
        build_tools/make_sample_voicemail "$(DESTDIR)/$(ASTDATADIR)" "$(DESTDIR)/$(ASTSPOOLDIR)"
-
        @for x in phoneprov/*; do \
                dst="$(DESTDIR)$(ASTDATADIR)/$$x" ;     \
                if [ -f "$${dst}" ]; then \
@@ -740,6 +753,10 @@ samples: adsi
                $(INSTALL) -m 644 "$$x" "$${dst}" ;\
        done
 
+basic-pbx:
+       @echo Installing basic-pbx config files...
+       $(call INSTALL_CONFIGS,basic-pbx)
+
 webvmail:
        @[ -d "$(DESTDIR)$(HTTP_DOCSDIR)/" ] || ( printf "http docs directory not found.\nUpdate assignment of variable HTTP_DOCSDIR in Makefile!\n" && exit 1 )
        @[ -d "$(DESTDIR)$(HTTP_CGIDIR)" ] || ( printf "cgi-bin directory not found.\nUpdate assignment of variable HTTP_CGIDIR in Makefile!\n" && exit 1 )
@@ -1012,6 +1029,7 @@ endif
 .PHONY: validate-docs
 .PHONY: _clean
 .PHONY: ari-stubs
+.PHONY: basic-pbx
 .PHONY: $(SUBDIRS_INSTALL)
 .PHONY: $(SUBDIRS_DIST_CLEAN)
 .PHONY: $(SUBDIRS_CLEAN)
index cf05419..77e5116 100644 (file)
 
 ASTERISK_REGISTER_FILE()
 
+#include <math.h>
+
 #include "asterisk/module.h"
 #include "asterisk/logger.h"
+#include "asterisk/app.h"
+#include "asterisk/pbx.h"
+#include "asterisk/strings.h"
 
 /*** DOCUMENTATION
-       <application name="Statsd" language="en_US">
+       <application name="StatsD" language="en_US">
                <synopsis>
                        Allow statistics to be passed to the StatsD server from the dialplan.
                </synopsis>
@@ -46,20 +51,180 @@ ASTERISK_REGISTER_FILE()
                        </parameter>
                </syntax>
                <description>
-                       <para>This dialplan application sends statistics to the StatsD server
-                       specified inside of <literal>statsd.conf</literal>.</para>
+                       <para>This dialplan application sends statistics to the StatsD
+                       server specified inside of <literal>statsd.conf</literal>.</para>
                </description>
        </application>
  ***/
 
-static const char app[] = "Statsd";
+static const char app[] = "StatsD";
 
-static int statsd_exec(struct ast_channel *chan, const char *data)
+/*!
+ * \brief Check to ensure the value is within the allowed range.
+ *
+ * \param value The value of the statistic to be sent to StatsD.
+ * \param metric The metric type to be sent to StatsD.
+ *
+ * This function checks to see if the value given to the StatsD daialplan
+ * application is within the allowed range as specified by StatsD. A counter
+ * is the only metric type allowed to be initialized as a negative number.
+ *
+ * \retval zero on success.
+ * \retval 1 on error.
+ */
+static int value_in_range(const char *value, const char *metric)
 {
-       ast_log(LOG_NOTICE, "StatsD application callback is working!\n");
+       double numerical_value = strtod(value, NULL);
+
+       if (!strcmp(metric, "counter")) {
+               if (numerical_value < pow(-2, 63) || numerical_value > pow(2, 63)) {
+                       ast_log(AST_LOG_WARNING, "Value %lf out of range!\n", numerical_value);
+                       return 1;
+               }
+       } else {
+               if (numerical_value < 0 || numerical_value > pow(2, 64)) {
+                       ast_log(AST_LOG_WARNING, "Value %lf out of range!\n", numerical_value);
+                       return 1;
+               }
+       }
+
        return 0;
 }
 
+/*!
+ * \brief Check to ensure the metric type is a valid metric type.
+ *
+ * \param metric The metric type to be sent to StatsD.
+ *
+ * This function checks to see if the metric type given to the StatsD dialplan
+ * is a valid metric type. Metric types are determined by StatsD.
+ *
+ * \retval zero on success.
+ * \retval 1 on error.
+ */
+static int validate_metric(const char *metric)
+{
+       const char *valid_metrics[] = {"gauge","set","timer","counter"};
+       int i;
+
+       if (ast_strlen_zero(metric)) {
+               ast_log(AST_LOG_ERROR, "Missing metric type argument.\n");
+               return 1;
+       }
+
+       for (i = 0; i < ARRAY_LEN(valid_metrics); i++) {
+               if (!strcmp(valid_metrics[i], metric)) {
+                       return 0;
+               }
+       }
+
+       ast_log(AST_LOG_ERROR, "Invalid metric type %s.\n", metric);
+
+       return 1;
+}
+
+/*!
+ * \brief Check to ensure the statistic name is valid.
+ *
+ * \param name The variable name to be sent to StatsD.
+ *
+ * This function checks to see if the statistic name given to the StatsD
+ * dialplan application is valid by ensuring that the name does not have any
+ * invalid characters.
+ *
+ * \retval zero on success.
+ * \retval 1 on error.
+ */
+static int validate_name(const char *name)
+{
+       if (ast_strlen_zero(name) || (strstr(name, "|") != NULL)) {
+               ast_log(AST_LOG_ERROR, "Statistic name %s is missing or contains a pipe (|)"
+                       " character.\n", name);
+               return 1;
+       }
+
+       return 0;
+}
+
+/*!
+ * \brief Check to ensure the value is valid.
+ *
+ * \param value The value of the statistic to be sent to StatsD.
+ * \param metric The metric type to be sent to StatsD.
+ *
+ * This function checks to see if the value given to the StatsD daialplan
+ * application is valid by testing if it is numeric. A plus or minus is only
+ * allowed at the beginning of the value if it is a counter or a gauge.
+ *
+ * \retval zero on success.
+ * \retval 1 on error.
+ */
+static int validate_value(const char *value, const char *metric)
+{
+       const char *actual_value;
+
+       if (ast_strlen_zero(value)) {
+               ast_log(AST_LOG_ERROR, "Missing value argument.\n");
+               return 1;
+       }
+
+       if (!strcmp(metric, "gauge") || !strcmp(metric, "counter")) {
+               if ((value[0] == '+') || (value[0] == '-')) {
+                       actual_value = &value[1];
+                       if (ast_strlen_zero(actual_value)) {
+                               ast_log(AST_LOG_ERROR, "Value argument %s only contains a sign"
+                                       " operator.\n", value);
+                               return 1;
+                       }
+               } else {
+                       actual_value = &value[0];
+               }
+       } else {
+               actual_value = &value[0];
+       }
+
+       if (!isdigit(*actual_value)) {
+               ast_log(AST_LOG_ERROR, "Value of %s is not a valid number!\n", actual_value);
+               return 1;
+       }
+
+       if (value_in_range(actual_value, metric)) {
+               return 1;
+       }
+
+       return 0;
+}
+
+static int statsd_exec(struct ast_channel *chan, const char *data)
+{
+       char *stats;
+
+       AST_DECLARE_APP_ARGS(args,
+                       AST_APP_ARG(metric_type);
+                       AST_APP_ARG(statistic_name);
+                       AST_APP_ARG(value);
+       );
+
+       if (!data) {
+               ast_log(AST_LOG_ERROR, "No parameters were provided. Correct format is "
+                       "StatsD(metric_type,statistic_name,value). All parameters are required.\n");
+               return 1;
+       }
+
+       stats = ast_strdupa(data);
+       AST_STANDARD_APP_ARGS(args, stats);
+
+       /* If any of the validations fail, emit a warning message. */
+       if (validate_metric(args.metric_type) || validate_name(args.statistic_name)
+               || validate_value(args.value, args.metric_type)) {
+               ast_log(AST_LOG_WARNING, "Invalid parameters provided. Correct format is "
+                       "StatsD(metric_type,statistic_name,value). All parameters are required.\n");
+
+               return 1;
+       }
+
+       return 0;
+}
 
 static int unload_module(void)
 {
@@ -71,4 +236,4 @@ static int load_module(void)
        return ast_register_application_xml(app, statsd_exec);
 }
 
-AST_MODULE_INFO_STANDARD_EXTENDED(ASTERISK_GPL_KEY, "Statsd Dialplan Application");
\ No newline at end of file
+AST_MODULE_INFO_STANDARD_EXTENDED(ASTERISK_GPL_KEY, "StatsD Dialplan Application");
index c17e4f8..697c720 100644 (file)
@@ -18832,6 +18832,11 @@ static int process_dahdi(struct dahdi_chan_conf *confp, const char *cat, struct
                                        cadence_is_ok = 0;
                                }
 
+                               /* This check is only needed to satisfy the compiler that element_count can't cause an out of bounds */
+                               if (element_count >= ARRAY_LEN(c)) {
+                                       element_count = ARRAY_LEN(c) - 1;
+                               }
+
                                /* Ring cadences cannot be negative */
                                for (i = 0; i < element_count; i++) {
                                        if (c[i] == 0) {
index 3fdc3ca..f0d4de5 100644 (file)
@@ -11095,7 +11095,7 @@ static int process_sdp_a_audio(const char *a, struct sip_pvt *p, struct ast_rtp_
 
                if (framing && p->autoframing) {
                        ast_debug(1, "Setting framing to %ld\n", framing);
-                       ast_rtp_codecs_set_framing(ast_rtp_instance_get_codecs(p->rtp), framing);
+                       ast_format_cap_set_framing(p->caps, framing);
                }
                found = TRUE;
        } else if (sscanf(a, "rtpmap: %30u %127[^/]/%30u", &codec, mimeSubtype, &sample_rate) == 3) {
@@ -13332,7 +13332,7 @@ static enum sip_result add_sdp(struct sip_request *resp, struct sip_pvt *p, int
                }
 
                /* Finally our remaining audio/video codecs */
-               for (x = 0; x < ast_format_cap_count(p->caps); x++) {
+               for (x = 0; ast_test_flag(&p->flags[0], SIP_OUTGOING) && x < ast_format_cap_count(p->caps); x++) {
                        tmp_fmt = ast_format_cap_get_format(p->caps, x);
 
                        if (ast_format_cap_iscompatible_format(alreadysent, tmp_fmt) != AST_FORMAT_CMP_NOT_EQUAL) {
@@ -13384,6 +13384,11 @@ static enum sip_result add_sdp(struct sip_request *resp, struct sip_pvt *p, int
                        ast_str_append(&a_audio, 0, "a=maxptime:%d\r\n", max_audio_packet_size);
                }
 
+               if (!ast_test_flag(&p->flags[0], SIP_OUTGOING)) {
+                       ast_debug(1, "Setting framing on incoming call: %u\n", min_audio_packet_size);
+                       ast_rtp_codecs_set_framing(ast_rtp_instance_get_codecs(p->rtp), min_audio_packet_size);
+               }
+
                if (!doing_directmedia) {
                        if (ast_test_flag(&p->flags[2], SIP_PAGE3_ICE_SUPPORT)) {
                                add_ice_to_sdp(p->rtp, &a_audio);
@@ -13676,10 +13681,6 @@ static int transmit_response_with_sdp(struct sip_pvt *p, const char *msg, const
                add_cc_call_info_to_response(p, &resp);
        }
        if (p->rtp) {
-               if (!p->autoframing && !ast_test_flag(&p->flags[0], SIP_OUTGOING)) {
-                       ast_debug(1, "Setting framing from config on incoming call\n");
-                       ast_rtp_codecs_set_framing(ast_rtp_instance_get_codecs(p->rtp), ast_format_cap_get_framing(p->caps));
-               }
                ast_rtp_instance_activate(p->rtp);
                try_suggested_sip_codec(p);
                if (p->t38.state == T38_ENABLED) {
index b009c45..1b228af 100644 (file)
@@ -4680,7 +4680,7 @@ static void sig_pri_handle_subcmds(struct sig_pri_span *pri, int chanpos, int ev
                                        f.frametype = AST_FRAME_TEXT;
                                        f.subclass.integer = 0;
                                        f.offset = 0;
-                                       f.data.ptr = &subcmd->u.display.text;
+                                       f.data.ptr = (void *)&subcmd->u.display.text;
                                        f.datalen = subcmd->u.display.length + 1;
                                        ast_queue_frame(owner, &f);
                                        ast_channel_unlock(owner);
index 06befb4..9302fb2 100644 (file)
                         ; registration is unsuccessful (default: "60")
 ;forbidden_retry_interval=0     ; Interval used when receiving a 403 Forbidden
                                 ; response (default: "0")
+;fatal_retry_interval=0 ; Interval used when receiving a fatal response.
+                        ; (default: "0") A fatal response is any permanent
+                        ; failure (non-temporary 4xx, 5xx, 6xx) response
+                        ; received from the registrar. NOTE - if also set
+                        ; the 'forbidden_retry_interval' takes precedence
+                        ; over this one when a 403 is received. Also, if
+                        ; 'auth_rejection_permanent' equals 'yes' a 401 and
+                        ; 407 become subject to this retry interval.
 ;server_uri=    ; SIP URI of the server to register against (default: "")
 ;transport=     ; Transport used for outbound authentication (default: "")
 ;type=  ; Must be of type registration (default: "")
diff --git a/contrib/ast-db-manage/config/versions/28ce1e718f05_add_fatal_response_interval.py b/contrib/ast-db-manage/config/versions/28ce1e718f05_add_fatal_response_interval.py
new file mode 100644 (file)
index 0000000..8c499ae
--- /dev/null
@@ -0,0 +1,22 @@
+"""add fatal_response_interval
+
+Revision ID: 28ce1e718f05
+Revises: 154177371065
+Create Date: 2015-10-20 17:57:45.560585
+
+"""
+
+# revision identifiers, used by Alembic.
+revision = '28ce1e718f05'
+down_revision = '154177371065'
+
+from alembic import op
+import sqlalchemy as sa
+
+
+def upgrade():
+    op.add_column('ps_registrations', sa.Column('fatal_retry_interval', sa.Integer))
+
+
+def downgrade():
+    op.drop_column('ps_registrations', 'fatal_retry_interval')
index 4d5ab59..e41215f 100644 (file)
@@ -74,7 +74,7 @@ if [ $MYUID -ne  0 ]; then
 fi
 
 SUPPORTED_MODULES="wcb4xxp wct4xxp wctc4xxp wctdm wctdm24xxp wcte11xp wcte12xp"
-RELATED_MODULES="$SUPPORTED_MODULES dahdi_dummy dahdi_transcode dahdi_vpmadt032_loader zaptel ztdummy zttranscode";
+RELATED_MODULES="$SUPPORTED_MODULES dahdi_dummy dahdi_transcode dahdi_vpmadt032_loader";
 
 OUTPUT_FILE="${FILE_PREFIX}${OUTPUT_FILE}"
 TARBALL_OUTPUT_FILE="${FILE_PREFIX}${TARBALL_OUTPUT_FILE}"
@@ -152,18 +152,16 @@ echo >> $OUTPUT;
 echo >> $OUTPUT;
 echo -n "."
 
-# Check for loaded Zaptel/DAHDI modules
-for module in dahdi zaptel; do
-  if [ -d /sys/module/$module ]; then
-    echo "------------------" >> $OUTPUT
-    echo "$module version:" >> $OUTPUT
-    echo "------------------" >> $OUTPUT
-    echo "/sys/module/$module/version: " >> $OUTPUT
-    cat /sys/module/$module/version 2> /dev/null >> $OUTPUT
-    echo "" >> $OUTPUT;
-    echo -n "."
-  fi
-done
+
+if [ -d /sys/module/dahdi ]; then
+  echo "------------------" >> $OUTPUT
+  echo "dahdi version:" >> $OUTPUT
+  echo "------------------" >> $OUTPUT
+  echo "/sys/module/dahdi/version: " >> $OUTPUT
+  cat /sys/module/dahdi/version 2> /dev/null >> $OUTPUT
+  echo "" >> $OUTPUT;
+  echo -n "."
+fi
 
 echo "------------------" >> $OUTPUT;
 echo "DAHDI TOOLS : dahdi_cfg --help" >> $OUTPUT;
@@ -188,13 +186,16 @@ asterisk -V >> $OUTPUT;
 echo >> $OUTPUT;
 # Add check to see if asterisk is running.
 if [ -e /var/run/asterisk.ctl ] || [ -e /var/run/asterisk/asterisk.ctl ]; then
-  for command in "show version" "core show version" "pri show version" "dahdi show version" "transcoder show" \
-      "core show uptime" "pri show spans" "misdn show stacks" "zap show channels" "dahdi show status" "dahdi show channels" \
-      "dahdi show channel 1" "core show channels" "skype show version" "skype show licenses" "skype show users" \
-      "skype show hostid" "show g729" "g729 show version" "g729 show licenses" "g729 show hostid" "fax show version" \
-      "fax show licenses" "fax show hostid" "fax show stats" "digium_phones show version" "digium_phones show alerts" \
-                       "digium_phones show applications" "digium_phones show firmwares" "digium_phones show lines" "digium_phones show networks" \
-                       "digium_phones show phones" "digium_phones show sessions" "digium_phones show settings" "digium_phones show translations" ;
+  for command in "core show version" "pri show version" "dahdi show version" "core show translation" \
+      "core show uptime" "core show settings" "core show sysinfo" "core show channels" \
+      "pri show spans" "dahdi show status" "dahdi show channels" "dahdi show channel 1" \
+      "pjsip show endpoints" "pjsip show registrations" "pjsip list channels" \
+      "sip show peers" "sip show registry" "sip show channels" "sip show subscriptions" "sip show settings" \
+      "show g729" "g729 show version" "g729 show licenses" "g729 show hostid" \
+      "digium_phones show version" "digium_phones show alerts" "digium_phones show applications" \
+      "digium_phones show firmwares" "digium_phones show lines" "digium_phones show networks" \
+                       "digium_phones show phones" "digium_phones show sessions" "digium_phones show settings" \
+      "digium_phones show translations" ;
        do
     echo "asterisk -rx \"$command\"" >> $OUTPUT;
     asterisk -rx "$command" >> $OUTPUT;
@@ -317,19 +318,17 @@ echo >> $OUTPUT;
 echo -n "."
 
 echo "----------------------------" >> $OUTPUT;
-echo "CAT OF DAHDI/ZAPTEL CHANNELS : cat /proc/dahdi/" >> $OUTPUT;
+echo "CAT OF DAHDI CHANNELS : cat /proc/dahdi/" >> $OUTPUT;
 echo "----------------------------" >> $OUTPUT;
-for tech in dahdi zaptel zap; do
-  if [ -d /proc/$tech/ ]; then
-    for file in $(ls /proc/$tech/ 2> /dev/null); do
-      echo "----------------------------" >> $OUTPUT;
-      echo "/proc/$tech/$file:" >> $OUTPUT;
-      cat /proc/$tech/$file >> $OUTPUT;
-      echo >> $OUTPUT;
-      echo -n "."
-    done
-  fi
-done
+if [ -d /proc/dahdi/ ]; then
+  for file in $(ls /proc/dahdi/ 2> /dev/null); do
+    echo "----------------------------" >> $OUTPUT;
+    echo "/proc/dahdi/$file:" >> $OUTPUT;
+    cat /proc/dahdi/$file >> $OUTPUT;
+    echo >> $OUTPUT;
+    echo -n "."
+  done
+fi
 echo >> $OUTPUT;
 
 echo "------------------" >> $OUTPUT;
@@ -371,7 +370,7 @@ for mod in $SUPPORTED_MODULES; do
 done
 
 echo "------------------------" >> $OUTPUT;
-echo "DAHDI/ZAPTEL MODULE INFO : modinfo" >> $OUTPUT;
+echo "DAHDI MODULE INFO : modinfo" >> $OUTPUT;
 echo "------------------------" >> $OUTPUT;
 for file in $(ls /lib/modules/$(uname -r)/dahdi/*.ko \
   /lib/modules/$(uname -r)/dahdi/*/*.ko\
@@ -518,26 +517,9 @@ collect_config_backup()
     TAR_FILES="$TAR_FILES /etc/dahdi*"
   fi
 
-  # Check for Zaptel Module configuration
-  if [ -f /etc/sysconfig/zaptel ]; then
-    TAR_FILES="$TAR_FILES /etc/sysconfig/zaptel*"
-  fi
-
-  # Check for Zaptel Module configuration (alternate location)
-  if [ -f /etc/default/zaptel ]; then
-    TAR_FILES="$TAR_FILES /etc/default/zaptel*"
-  fi
-
-  # Grab the dahdi/zaptel init scripts, in case they have been modified
-  for driver in dahdi zaptel; do
-    if [ -f /etc/init.d/$driver ]; then
-      TAR_FILES="$TAR_FILES /etc/init.d/$driver"
-    fi
-  done
-
-  # Check for zaptel.conf
-  if [ -f /etc/zaptel.conf ]; then
-    TAR_FILES="$TAR_FILES /etc/zaptel*"
+  # Grab the dahdi init scripts, in case they have been modified
+  if [ -f /etc/init.d/dahdi ]; then
+    TAR_FILES="$TAR_FILES /etc/init.d/dahdi"
   fi
 
   # Check for fxotune.conf
@@ -567,6 +549,9 @@ collect_config_backup()
   if [ -f /var/log/messages ]; then
     TAR_FILES="$TAR_FILES /var/log/messages"
   fi
+  if [ -f /var/log/syslog ]; then
+    TAR_FILES="$TAR_FILES /var/log/syslog"
+  fi
 
   [ -f $TARBALL_OUTPUT ] && rm -rf $TARBALL_OUTPUT
   [ -f $TARBALL_OUTPUT.gz ] && rm -rf $TARBALL_OUTPUT.gz
@@ -616,8 +601,8 @@ else
   echo "pci listing, dmesg, running processes, and kernel version"
   echo
   echo "2. A backup of elements of your configuration such as:"
-  echo "asterisk config files, license files, loaded dahdi/zaptel module"
-  echo "parameters, and other asterisk/dahdi/zaptel related files."
+  echo "asterisk config files, license files, loaded dahdi module"
+  echo "parameters, and other asterisk/dahdi related files."
   echo
   echo "Collect this information [y/n] ? "
   read files;
index 17cbf03..06eb05e 100755 (executable)
@@ -101,6 +101,7 @@ handle_debian() {
                apt-get install aptitude
        fi
        extra_packs=`check_installed_debs $PACKAGES_DEBIAN`
+       $testcmd aptitude update
        $testcmd aptitude install -y $extra_packs
 }
 
diff --git a/funcs/func_holdintercept.c b/funcs/func_holdintercept.c
new file mode 100644 (file)
index 0000000..3e348c1
--- /dev/null
@@ -0,0 +1,236 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2015, Digium, Inc.
+ *
+ * Matt Jordan <mjordan@digium.com>
+ *
+ * See http://www.asterisk.org for more information about
+ * the Asterisk project. Please do not directly contact
+ * any of the maintainers of this project for assistance;
+ * the project provides a web site, mailing lists and IRC
+ * channels for your use.
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE file
+ * at the top of the source tree.
+ */
+
+/*! \file
+ *
+ * \brief Function that intercepts HOLD frames from channels and raises events
+ *
+ * \author Matt Jordan <mjordan@digium.com>
+ *
+ * \ingroup functions
+ */
+
+/*** MODULEINFO
+       <support_level>core</support_level>
+ ***/
+
+#include "asterisk.h"
+
+ASTERISK_REGISTER_FILE()
+
+#include "asterisk/module.h"
+#include "asterisk/channel.h"
+#include "asterisk/pbx.h"
+#include "asterisk/app.h"
+#include "asterisk/frame.h"
+#include "asterisk/stasis.h"
+#include "asterisk/stasis_channels.h"
+
+/*** DOCUMENTATION
+       <function name="HOLD_INTERCEPT" language="en_US">
+               <synopsis>
+                       Intercepts hold frames on a channel and raises an event instead of passing the frame on
+               </synopsis>
+               <syntax>
+                       <parameter name="action" required="true">
+                               <optionlist>
+                                       <option name="remove">
+                                               <para>W/O. Removes the hold interception function.</para>
+                                       </option>
+                                       <option name="set">
+                                               <para>W/O. Enable hold interception on the channel. When
+                                               enabled, the channel will intercept any hold action that
+                                               is signalled from the device, and instead simply raise an
+                                               event (AMI/ARI) indicating that the channel wanted to put other
+                                               parties on hold.</para>
+                                       </option>
+                               </optionlist>
+                       </parameter>
+               </syntax>
+       </function>
+***/
+
+/*! \brief Private data structure used with the function's datastore */
+struct hold_intercept_data {
+       int framehook_id;
+};
+
+/*! \brief The channel datastore the function uses to store state */
+static const struct ast_datastore_info hold_intercept_datastore = {
+       .type = "hold_intercept",
+};
+
+/*! \internal \brief Disable hold interception on the channel */
+static int remove_hold_intercept(struct ast_channel *chan)
+{
+       struct ast_datastore *datastore = NULL;
+       struct hold_intercept_data *data;
+       SCOPED_CHANNELLOCK(chan_lock, chan);
+
+       datastore = ast_channel_datastore_find(chan, &hold_intercept_datastore, NULL);
+       if (!datastore) {
+               ast_log(AST_LOG_WARNING, "Cannot remove HOLD_INTERCEPT from %s: HOLD_INTERCEPT not currently enabled\n",
+                       ast_channel_name(chan));
+               return -1;
+       }
+       data = datastore->data;
+
+       if (ast_framehook_detach(chan, data->framehook_id)) {
+               ast_log(AST_LOG_WARNING, "Failed to remove HOLD_INTERCEPT framehook from channel %s\n",
+                       ast_channel_name(chan));
+               return -1;
+       }
+
+       if (ast_channel_datastore_remove(chan, datastore)) {
+               ast_log(AST_LOG_WARNING, "Failed to remove HOLD_INTERCEPT datastore from channel %s\n",
+                       ast_channel_name(chan));
+               return -1;
+       }
+       ast_datastore_free(datastore);
+
+       return 0;
+}
+
+/*! \brief Frame hook that is called to intercept hold/unhold */
+static struct ast_frame *hold_intercept_framehook(struct ast_channel *chan,
+       struct ast_frame *f, enum ast_framehook_event event, void *data)
+{
+       int frame_type;
+
+       if (!f || (event != AST_FRAMEHOOK_EVENT_WRITE)) {
+               return f;
+       }
+
+       if (f->frametype != AST_FRAME_CONTROL) {
+               return f;
+       }
+
+       frame_type = f->subclass.integer;
+       if (frame_type != AST_CONTROL_HOLD && frame_type != AST_CONTROL_UNHOLD) {
+               return f;
+       }
+
+       /* Munch munch */
+       ast_frfree(f);
+       f = &ast_null_frame;
+
+       ast_channel_publish_cached_blob(chan,
+               frame_type == AST_CONTROL_HOLD ? ast_channel_hold_type() : ast_channel_unhold_type(),
+               NULL);
+
+       return f;
+}
+
+/*! \brief Callback function which informs upstream if we are consuming a frame of a specific type */
+static int hold_intercept_framehook_consume(void *data, enum ast_frame_type type)
+{
+       return (type == AST_FRAME_CONTROL ? 1 : 0);
+}
+
+/*! \internal \brief Enable hold interception on the channel */
+static int set_hold_intercept(struct ast_channel *chan)
+{
+       struct ast_datastore *datastore;
+       struct hold_intercept_data *data;
+       static struct ast_framehook_interface hold_framehook_interface = {
+               .version = AST_FRAMEHOOK_INTERFACE_VERSION,
+               .event_cb = hold_intercept_framehook,
+               .consume_cb = hold_intercept_framehook_consume,
+               .disable_inheritance = 1,
+       };
+       SCOPED_CHANNELLOCK(chan_lock, chan);
+
+       datastore = ast_channel_datastore_find(chan, &hold_intercept_datastore, NULL);
+       if (datastore) {
+               ast_log(AST_LOG_WARNING, "HOLD_INTERCEPT already set on '%s'\n",
+                       ast_channel_name(chan));
+               return 0;
+       }
+
+       datastore = ast_datastore_alloc(&hold_intercept_datastore, NULL);
+       if (!datastore) {
+               return -1;
+       }
+
+       data = ast_calloc(1, sizeof(*data));
+       if (!data) {
+               ast_datastore_free(datastore);
+               return -1;
+       }
+
+       data->framehook_id = ast_framehook_attach(chan, &hold_framehook_interface);
+       if (data->framehook_id < 0) {
+               ast_log(AST_LOG_WARNING, "Failed to attach HOLD_INTERCEPT framehook to '%s'\n",
+                       ast_channel_name(chan));
+               ast_datastore_free(datastore);
+               ast_free(data);
+               return -1;
+       }
+       datastore->data = data;
+
+       ast_channel_datastore_add(chan, datastore);
+
+       return 0;
+}
+
+/*! \internal \brief HOLD_INTERCEPT write function callback */
+static int hold_intercept_fn_write(struct ast_channel *chan, const char *function,
+       char *data, const char *value)
+{
+       int res;
+
+       if (!chan) {
+               return -1;
+       }
+
+       if (ast_strlen_zero(data)) {
+               ast_log(AST_LOG_WARNING, "HOLD_INTERCEPT requires an argument\n");
+               return -1;
+       }
+
+       if (!strcasecmp(data, "set")) {
+               res = set_hold_intercept(chan);
+       } else if (!strcasecmp(data, "remove")) {
+               res = remove_hold_intercept(chan);
+       } else {
+               ast_log(AST_LOG_WARNING, "HOLD_INTERCEPT: unknown option %s\n", data);
+               res = -1;
+       }
+
+       return res;
+}
+
+/*! \brief Definition of the HOLD_INTERCEPT function */
+static struct ast_custom_function hold_intercept_function = {
+       .name = "HOLD_INTERCEPT",
+       .write = hold_intercept_fn_write,
+};
+
+/*! \internal \brief Unload the module */
+static int unload_module(void)
+{
+       return ast_custom_function_unregister(&hold_intercept_function);
+}
+
+/*! \internal \brief Load the module */
+static int load_module(void)
+{
+       return ast_custom_function_register(&hold_intercept_function) ? AST_MODULE_LOAD_FAILURE : AST_MODULE_LOAD_SUCCESS;
+}
+
+AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Hold interception dialplan function");
index 5654c38..f752f7f 100644 (file)
@@ -536,6 +536,36 @@ int ast_cdr_backend_suspend(const char *name);
 int ast_cdr_backend_unsuspend(const char *name);
 
 /*!
+ * \brief Register a CDR modifier
+ * \param name name associated with the particular CDR modifier
+ * \param desc description of the CDR modifier
+ * \param be function pointer to a CDR modifier
+ *
+ * Used to register a Call Detail Record modifier.
+ *
+ * This gives modules a chance to modify CDR fields before they are dispatched
+ * to registered backends (odbc, syslog, etc).
+ *
+ * \note The *modified* CDR will be passed to **all** registered backends for
+ * logging. For instance, if cdr_manager changes the CDR data, cdr_adaptive_odbc
+ * will also get the modified CDR.
+ *
+ * \retval 0 on success.
+ * \retval -1 on error
+ */
+int ast_cdr_modifier_register(const char *name, const char *desc, ast_cdrbe be);
+
+/*!
+ * \brief Unregister a CDR modifier
+ * \param name name of CDR modifier to unregister
+ * Unregisters a CDR modifier by its name
+ *
+ * \retval 0 The modifier unregistered successfully
+ * \retval -1 The modifier could not be unregistered at this time
+ */
+int ast_cdr_modifier_unregister(const char *name);
+
+/*!
  * \brief Disposition to a string
  * \param disposition input binary form
  * Converts the binary form of a disposition to string form.
index 29edbd2..f159a57 100644 (file)
@@ -1180,6 +1180,11 @@ int ast_sip_push_task(struct ast_taskprocessor *serializer, int (*sip_task)(void
  * cause a deadlock. If you are in a SIP servant thread, just call your function
  * in-line.
  *
+ * \warning \b Never hold locks that may be acquired by a SIP servant thread when
+ * calling this function. Doing so may cause a deadlock if all SIP servant threads
+ * are blocked waiting to acquire the lock while the thread holding the lock is
+ * waiting for a free SIP servant thread.
+ *
  * \param serializer The SIP serializer to which the task belongs. May be NULL.
  * \param sip_task The task to execute
  * \param task_data The parameter to pass to the task when it executes
index 44979b7..c253521 100644 (file)
@@ -61,7 +61,7 @@ struct ast_sip_cli_formatter_entry {
        /*! The callback used to print the details of the object. */
        ao2_callback_fn *print_body;
        /*! The function used to retrieve a container of all objects of this type. */
-       struct ao2_container *(* get_container)(void);
+       struct ao2_container *(* get_container)(const char *regex);
        /*! The function used to iterate over a container of objects. */
        int (* iterate)(void *container, ao2_callback_fn callback, void *args);
        /*! The function used to retrieve a specific object from it's container. */
index afa0d69..c9b66dc 100644 (file)
@@ -684,6 +684,15 @@ const char *ast_sip_subscription_get_body_type(struct ast_sip_subscription *sub)
  */
 const char *ast_sip_subscription_get_body_subtype(struct ast_sip_subscription *sub);
 
+/*!
+ * \since 13.6.0
+ * \brief Alert the pubsub core that the subscription is ready for destruction
+ *
+ * \param sub The subscription that is complete
+ * \return Nothing
+ */
+void ast_sip_subscription_destroy(struct ast_sip_subscription *sub);
+
 /*! \brief Determines whether the res_pjsip_pubsub module is loaded */
 #define CHECK_PJSIP_PUBSUB_MODULE_LOADED()                     \
        do {                                                    \
index 8d7f53f..5e24075 100644 (file)
@@ -297,6 +297,9 @@ struct cdr_beitem {
 /*! \brief List of registered backends */
 static AST_RWLIST_HEAD_STATIC(be_list, cdr_beitem);
 
+/*! \brief List of registered modifiers */
+static AST_RWLIST_HEAD_STATIC(mo_list, cdr_beitem);
+
 /*! \brief Queued CDR waiting to be batched */
 struct cdr_batch_item {
        struct ast_cdr *cdr;
@@ -2678,7 +2681,7 @@ int ast_cdr_backend_unsuspend(const char *name)
        return success;
 }
 
-int ast_cdr_register(const char *name, const char *desc, ast_cdrbe be)
+static int cdr_generic_register(struct be_list *generic_list, const char *name, const char *desc, ast_cdrbe be)
 {
        struct cdr_beitem *i = NULL;
 
@@ -2690,11 +2693,11 @@ int ast_cdr_register(const char *name, const char *desc, ast_cdrbe be)
                return -1;
        }
 
-       AST_RWLIST_WRLOCK(&be_list);
-       AST_RWLIST_TRAVERSE(&be_list, i, list) {
+       AST_RWLIST_WRLOCK(generic_list);
+       AST_RWLIST_TRAVERSE(generic_list, i, list) {
                if (!strcasecmp(name, i->name)) {
                        ast_log(LOG_WARNING, "Already have a CDR backend called '%s'\n", name);
-                       AST_RWLIST_UNLOCK(&be_list);
+                       AST_RWLIST_UNLOCK(generic_list);
                        return -1;
                }
        }
@@ -2706,40 +2709,50 @@ int ast_cdr_register(const char *name, const char *desc, ast_cdrbe be)
        ast_copy_string(i->name, name, sizeof(i->name));
        ast_copy_string(i->desc, desc, sizeof(i->desc));
 
-       AST_RWLIST_INSERT_HEAD(&be_list, i, list);
-       AST_RWLIST_UNLOCK(&be_list);
+       AST_RWLIST_INSERT_HEAD(generic_list, i, list);
+       AST_RWLIST_UNLOCK(generic_list);
 
        return 0;
 }
 
-int ast_cdr_unregister(const char *name)
+int ast_cdr_register(const char *name, const char *desc, ast_cdrbe be)
+{
+       return cdr_generic_register(&be_list, name, desc, be);
+}
+
+int ast_cdr_modifier_register(const char *name, const char *desc, ast_cdrbe be)
+{
+       return cdr_generic_register((struct be_list *)&mo_list, name, desc, be);
+}
+
+static int ast_cdr_generic_unregister(struct be_list *generic_list, const char *name)
 {
        struct cdr_beitem *match = NULL;
        int active_count;
 
-       AST_RWLIST_WRLOCK(&be_list);
-       AST_RWLIST_TRAVERSE(&be_list, match, list) {
+       AST_RWLIST_WRLOCK(generic_list);
+       AST_RWLIST_TRAVERSE(generic_list, match, list) {
                if (!strcasecmp(name, match->name)) {
                        break;
                }
        }
 
        if (!match) {
-               AST_RWLIST_UNLOCK(&be_list);
+               AST_RWLIST_UNLOCK(generic_list);
                return 0;
        }
 
        active_count = ao2_container_count(active_cdrs_by_channel);
 
        if (!match->suspended && active_count != 0) {
-               AST_RWLIST_UNLOCK(&be_list);
+               AST_RWLIST_UNLOCK(generic_list);
                ast_log(AST_LOG_WARNING, "Unable to unregister CDR backend %s; %d CDRs are still active\n",
                        name, active_count);
                return -1;
        }
 
-       AST_RWLIST_REMOVE(&be_list, match, list);
-       AST_RWLIST_UNLOCK(&be_list);
+       AST_RWLIST_REMOVE(generic_list, match, list);
+       AST_RWLIST_UNLOCK(generic_list);
 
        ast_verb(2, "Unregistered '%s' CDR backend\n", name);
        ast_free(match);
@@ -2747,6 +2760,16 @@ int ast_cdr_unregister(const char *name)
        return 0;
 }
 
+int ast_cdr_unregister(const char *name)
+{
+       return ast_cdr_generic_unregister(&be_list, name);
+}
+
+int ast_cdr_modifier_unregister(const char *name)
+{
+       return ast_cdr_generic_unregister((struct be_list *)&mo_list, name);
+}
+
 struct ast_cdr *ast_cdr_dup(struct ast_cdr *cdr)
 {
        struct ast_cdr *newcdr;
@@ -3262,6 +3285,13 @@ static void post_cdr(struct ast_cdr *cdr)
                        continue;
                }
 
+               /* Modify CDR's */
+               AST_RWLIST_RDLOCK(&mo_list);
+               AST_RWLIST_TRAVERSE(&mo_list, i, list) {
+                       i->be(cdr);
+               }
+               AST_RWLIST_UNLOCK(&mo_list);
+
                if (ast_test_flag(cdr, AST_CDR_FLAG_DISABLE)) {
                        continue;
                }
index 4be635e..f86a8df 100644 (file)
@@ -594,7 +594,7 @@ static struct ast_codec ilbc = {
        .type = AST_MEDIA_TYPE_AUDIO,
        .sample_rate = 8000,
        .minimum_ms = 30,
-       .maximum_ms = 30,
+       .maximum_ms = 300,
        .default_ms = 30,
        .minimum_bytes = 50,
        .samples_count = ilbc_samples,
index 224fe33..d486d5d 100644 (file)
@@ -93,14 +93,27 @@ static void format_cap_destroy(void *obj)
        AST_VECTOR_FREE(&cap->preference_order);
 }
 
-static inline void format_cap_init(struct ast_format_cap *cap, enum ast_format_cap_flags flags)
+/*
+ * \brief Initialize values on an ast_format_cap
+ *
+ * \param cap ast_format_cap to initialize
+ * \param flags Unused.
+ * \retval 0 Success
+ * \retval -1 Failure
+ */
+static inline int format_cap_init(struct ast_format_cap *cap, enum ast_format_cap_flags flags)
 {
-       AST_VECTOR_INIT(&cap->formats, 0);
+       if (AST_VECTOR_INIT(&cap->formats, 0)) {
+               return -1;
+       }
 
        /* TODO: Look at common usage of this and determine a good starting point */
-       AST_VECTOR_INIT(&cap->preference_order, 5);
+       if (AST_VECTOR_INIT(&cap->preference_order, 5)) {
+               return -1;
+       }
 
        cap->framing = UINT_MAX;
+       return 0;
 }
 
 struct ast_format_cap *__ast_format_cap_alloc(enum ast_format_cap_flags flags,
@@ -114,7 +127,10 @@ struct ast_format_cap *__ast_format_cap_alloc(enum ast_format_cap_flags flags,
                return NULL;
        }
 
-       format_cap_init(cap, flags);
+       if (format_cap_init(cap, flags)) {
+               ao2_ref(cap, -1);
+               return NULL;
+       }
 
        return cap;
 }
index 2ea6fae..6e9ae00 100644 (file)
@@ -2819,6 +2819,7 @@ AST_THREADSTORAGE(userevent_buf);
  */
 void astman_append(struct mansession *s, const char *fmt, ...)
 {
+       int res;
        va_list ap;
        struct ast_str *buf;
 
@@ -2827,8 +2828,11 @@ void astman_append(struct mansession *s, const char *fmt, ...)
        }
 
        va_start(ap, fmt);
-       ast_str_set_va(&buf, 0, fmt, ap);
+       res = ast_str_set_va(&buf, 0, fmt, ap);
        va_end(ap);
+       if (res == AST_DYNSTR_BUILD_FAILED) {
+               return;
+       }
 
        if (s->f != NULL || s->session->f != NULL) {
                send_string(s, ast_str_buffer(buf));
@@ -2888,6 +2892,7 @@ void astman_send_error(struct mansession *s, const struct message *m, char *erro
 
 void astman_send_error_va(struct mansession *s, const struct message *m, const char *fmt, ...)
 {
+       int res;
        va_list ap;
        struct ast_str *buf;
        char *msg;
@@ -2897,8 +2902,11 @@ void astman_send_error_va(struct mansession *s, const struct message *m, const c
        }
 
        va_start(ap, fmt);
-       ast_str_set_va(&buf, 0, fmt, ap);
+       res = ast_str_set_va(&buf, 0, fmt, ap);
        va_end(ap);
+       if (res == AST_DYNSTR_BUILD_FAILED) {
+               return;
+       }
 
        /* astman_append will use the same underlying buffer, so copy the message out
         * before sending the response */
index 7aaff79..495011e 100644 (file)
@@ -60,55 +60,78 @@ int __ast_str_helper(struct ast_str **buf, ssize_t max_len,
        int append, const char *fmt, va_list ap)
 #endif
 {
-       int res, need;
+       int res;
+       int added;
+       int need;
        int offset = (append && (*buf)->__AST_STR_LEN) ? (*buf)->__AST_STR_USED : 0;
        va_list aq;
 
+       if (max_len < 0) {
+               max_len = (*buf)->__AST_STR_LEN;        /* don't exceed the allocated space */
+       }
+
        do {
-               if (max_len < 0) {
-                       max_len = (*buf)->__AST_STR_LEN;        /* don't exceed the allocated space */
-               }
-               /*
-                * Ask vsnprintf how much space we need. Remember that vsnprintf
-                * does not count the final <code>'\\0'</code> so we must add 1.
-                */
                va_copy(aq, ap);
                res = vsnprintf((*buf)->__AST_STR_STR + offset, (*buf)->__AST_STR_LEN - offset, fmt, aq);
+               va_end(aq);
+
+               if (res < 0) {
+                       /*
+                        * vsnprintf write to string failed.
+                        * I don't think this is possible with a memory buffer.
+                        */
+                       res = AST_DYNSTR_BUILD_FAILED;
+                       added = 0;
+                       break;
+               }
 
-               need = res + offset + 1;
                /*
-                * If there is not enough space and we are below the max length,
-                * reallocate the buffer and return a message telling to retry.
+                * vsnprintf returns how much space we used or would need.
+                * Remember that vsnprintf does not count the nil terminator
+                * so we must add 1.
                 */
-               if (need > (*buf)->__AST_STR_LEN && (max_len == 0 || (*buf)->__AST_STR_LEN < max_len) ) {
-                       int len = (int)(*buf)->__AST_STR_LEN;
-                       if (max_len && max_len < need) {        /* truncate as needed */
-                               need = max_len;
-                       } else if (max_len == 0) {      /* if unbounded, give more room for next time */
-                               need += 16 + need / 4;
-                       }
-                       if (
+               added = res;
+               need = offset + added + 1;
+               if (need <= (*buf)->__AST_STR_LEN
+                       || (max_len && max_len <= (*buf)->__AST_STR_LEN)) {
+                       /*
+                        * There was enough room for the string or we are not
+                        * allowed to try growing the string buffer.
+                        */
+                       break;
+               }
+
+               /* Reallocate the buffer and try again. */
+               if (max_len == 0) {
+                       /* unbounded, give more room for next time */
+                       need += 16 + need / 4;
+               } else if (max_len < need) {
+                       /* truncate as needed */
+                       need = max_len;
+               }
+
+               if (
 #if (defined(MALLOC_DEBUG) && !defined(STANDALONE))
-                                       _ast_str_make_space(buf, need, file, lineno, function)
+                       _ast_str_make_space(buf, need, file, lineno, function)
 #else
-                                       ast_str_make_space(buf, need)
+                       ast_str_make_space(buf, need)
 #endif
-                               ) {
-                               ast_log_safe(LOG_VERBOSE, "failed to extend from %d to %d\n", len, need);
-                               va_end(aq);
-                               return AST_DYNSTR_BUILD_FAILED;
-                       }
-                       (*buf)->__AST_STR_STR[offset] = '\0';   /* Truncate the partial write. */
+                       ) {
+                       ast_log_safe(LOG_VERBOSE, "failed to extend from %d to %d\n",
+                               (int) (*buf)->__AST_STR_LEN, need);
 
-                       /* Restart va_copy before calling vsnprintf() again. */
-                       va_end(aq);
-                       continue;
+                       res = AST_DYNSTR_BUILD_FAILED;
+                       break;
                }
-               va_end(aq);
-               break;
        } while (1);
-       /* update space used, keep in mind the truncation */
-       (*buf)->__AST_STR_USED = (res + offset > (*buf)->__AST_STR_LEN) ? (*buf)->__AST_STR_LEN - 1: res + offset;
+
+       /* Update space used, keep in mind truncation may be necessary. */
+       (*buf)->__AST_STR_USED = ((*buf)->__AST_STR_LEN <= offset + added)
+               ? (*buf)->__AST_STR_LEN - 1
+               : offset + added;
+
+       /* Ensure that the string is terminated. */
+       (*buf)->__AST_STR_STR[(*buf)->__AST_STR_USED] = '\0';
 
        return res;
 }
index 399a7be..86c3f65 100644 (file)
@@ -2646,14 +2646,18 @@ struct ast_xml_xpath_results *__attribute__((format(printf, 1, 2))) ast_xmldoc_q
        struct documentation_tree *doctree;
        RAII_VAR(struct ast_str *, xpath_str, ast_str_create(128), ast_free);
        va_list ap;
+       int res;
 
        if (!xpath_str) {
                return NULL;
        }
 
        va_start(ap, fmt);
-       ast_str_set_va(&xpath_str, 0, fmt, ap);
+       res = ast_str_set_va(&xpath_str, 0, fmt, ap);
        va_end(ap);
+       if (res == AST_DYNSTR_BUILD_FAILED) {
+               return NULL;
+       }
 
        AST_RWLIST_RDLOCK(&xmldoc_tree);
        AST_LIST_TRAVERSE(&xmldoc_tree, doctree, entry) {
index 5fc5990..cdaed4e 100644 (file)
@@ -2559,6 +2559,8 @@ pjsip_dialog *ast_sip_create_dialog_uac(const struct ast_sip_endpoint *endpoint,
 
                pj_strdup2_with_null(dlg->pool, &tmp, outbound_proxy);
                if (!(route = pjsip_parse_hdr(dlg->pool, &ROUTE_HNAME, tmp.ptr, tmp.slen, NULL))) {
+                       ast_log(LOG_ERROR, "Could not create dialog to endpoint '%s' as outbound proxy URI '%s' is not valid\n",
+                               ast_sorcery_object_get_id(endpoint), outbound_proxy);
                        dlg->sess_count--;
                        pjsip_dlg_terminate(dlg);
                        return NULL;
@@ -2752,6 +2754,7 @@ static int create_out_of_dialog_request(const pjsip_method *method, struct ast_s
        pj_str_t from;
        pj_pool_t *pool;
        pjsip_tpselector selector = { .type = PJSIP_TPSELECTOR_NONE, };
+       pjsip_uri *sip_uri;
 
        if (ast_strlen_zero(uri)) {
                if (!endpoint && (!contact || ast_strlen_zero(contact->uri))) {
@@ -2788,6 +2791,16 @@ static int create_out_of_dialog_request(const pjsip_method *method, struct ast_s
                return -1;
        }
 
+       sip_uri = pjsip_parse_uri(pool, remote_uri.ptr, remote_uri.slen, 0);
+       if (!sip_uri || (!PJSIP_URI_SCHEME_IS_SIP(sip_uri) && !PJSIP_URI_SCHEME_IS_SIPS(sip_uri))) {
+               ast_log(LOG_ERROR, "Unable to create outbound %.*s request to endpoint %s as URI '%s' is not valid\n",
+                       (int) pj_strlen(&method->name), pj_strbuf(&method->name),
+                       endpoint ? ast_sorcery_object_get_id(endpoint) : "<none>",
+                       pj_strbuf(&remote_uri));
+               pjsip_endpt_release_pool(ast_sip_get_pjsip_endpoint(), pool);
+               return -1;
+       }
+
        if (sip_dialog_create_from(pool, &from, endpoint ? endpoint->fromuser : NULL,
                                endpoint ? endpoint->fromdomain : NULL, &remote_uri, &selector)) {
                ast_log(LOG_ERROR, "Unable to create From header for %.*s request to endpoint %s\n",
@@ -2812,8 +2825,9 @@ static int create_out_of_dialog_request(const pjsip_method *method, struct ast_s
        /* If an outbound proxy is specified on the endpoint apply it to this request */
        if (endpoint && !ast_strlen_zero(endpoint->outbound_proxy) &&
                ast_sip_set_outbound_proxy((*tdata), endpoint->outbound_proxy)) {
-               ast_log(LOG_ERROR, "Unable to apply outbound proxy on request %.*s to endpoint %s\n",
-                       (int) pj_strlen(&method->name), pj_strbuf(&method->name), ast_sorcery_object_get_id(endpoint));
+               ast_log(LOG_ERROR, "Unable to apply outbound proxy on request %.*s to endpoint %s as outbound proxy URI '%s' is not valid\n",
+                       (int) pj_strlen(&method->name), pj_strbuf(&method->name), ast_sorcery_object_get_id(endpoint),
+                       endpoint->outbound_proxy);
                pjsip_endpt_release_pool(ast_sip_get_pjsip_endpoint(), pool);
                return -1;
        }
index 773889c..9160e67 100644 (file)
@@ -195,13 +195,12 @@ static struct ast_sip_endpoint_formatter endpoint_auth_formatter = {
        .format_ami = format_ami_endpoint_auth
 };
 
-static struct ao2_container *cli_get_container(void)
+static struct ao2_container *cli_get_container(const char *regex)
 {
        RAII_VAR(struct ao2_container *, container, NULL, ao2_cleanup);
        struct ao2_container *s_container;
 
-       container = ast_sorcery_retrieve_by_fields(ast_sip_get_sorcery(), "auth",
-               AST_RETRIEVE_FLAG_MULTIPLE | AST_RETRIEVE_FLAG_ALL, NULL);
+       container = ast_sorcery_retrieve_by_regex(ast_sip_get_sorcery(), "auth", regex);
        if (!container) {
                return NULL;
        }
@@ -272,12 +271,14 @@ static int cli_print_body(void *obj, void *arg, int flags)
 static struct ast_cli_entry cli_commands[] = {
        AST_CLI_DEFINE(ast_sip_cli_traverse_objects, "List PJSIP Auths",
                .command = "pjsip list auths",
-               .usage = "Usage: pjsip list auths\n"
-                                "       List the configured PJSIP Auths\n"),
+               .usage = "Usage: pjsip list auths [ like <pattern> ]\n"
+                               "       List the configured PJSIP Auths\n"
+                               "       Optional regular expression pattern is used to filter the list.\n"),
        AST_CLI_DEFINE(ast_sip_cli_traverse_objects, "Show PJSIP Auths",
                .command = "pjsip show auths",
-               .usage = "Usage: pjsip show auths\n"
-                                "       Show the configured PJSIP Auths\n"),
+               .usage = "Usage: pjsip show auths [ like <pattern> ]\n"
+                               "       Show the configured PJSIP Auths\n"
+                               "       Optional regular expression pattern is used to filter the list.\n"),
        AST_CLI_DEFINE(ast_sip_cli_traverse_objects, "Show PJSIP Auth",
                .command = "pjsip show auth",
                .usage = "Usage: pjsip show auth <id>\n"
index 73030b1..e998661 100644 (file)
@@ -633,13 +633,13 @@ static int tos_to_str(const void *obj, const intptr_t *args, char **buf)
        return 0;
 }
 
-static struct ao2_container *cli_get_container(void)
+static struct ao2_container *cli_get_container(const char *regex)
 {
        RAII_VAR(struct ao2_container *, container, NULL, ao2_cleanup);
        struct ao2_container *s_container;
 
-       container = ast_sorcery_retrieve_by_fields(ast_sip_get_sorcery(), "transport",
-               AST_RETRIEVE_FLAG_MULTIPLE | AST_RETRIEVE_FLAG_ALL, NULL);
+       container = ast_sorcery_retrieve_by_regex(ast_sip_get_sorcery(), "transport",
+               regex);
        if (!container) {
                return NULL;
        }
@@ -720,12 +720,14 @@ static struct ast_cli_entry cli_commands[] = {
        AST_CLI_DEFINE(handle_pjsip_list_ciphers, "List available OpenSSL cipher names"),
        AST_CLI_DEFINE(ast_sip_cli_traverse_objects, "List PJSIP Transports",
                .command = "pjsip list transports",
-               .usage = "Usage: pjsip list transports\n"
-                                "       List the configured PJSIP Transports\n"),
+               .usage = "Usage: pjsip list transports [ like <pattern> ]\n"
+                               "       List the configured PJSIP Transports\n"
+                               "       Optional regular expression pattern is used to filter the list.\n"),
        AST_CLI_DEFINE(ast_sip_cli_traverse_objects, "Show PJSIP Transports",
                .command = "pjsip show transports",
-               .usage = "Usage: pjsip show transports\n"
-                                "       Show the configured PJSIP Transport\n"),
+               .usage = "Usage: pjsip show transports [ like <pattern> ]\n"
+                               "       Show the configured PJSIP Transport\n"
+                               "       Optional regular expression pattern is used to filter the list.\n"),
        AST_CLI_DEFINE(ast_sip_cli_traverse_objects, "Show PJSIP Transport",
                .command = "pjsip show transport",
                .usage = "Usage: pjsip show transport <id>\n"
index 9625f04..b6f88d2 100644 (file)
@@ -319,32 +319,6 @@ static int expiration_struct2str(const void *obj, const intptr_t *args, char **b
        return (ast_asprintf(buf, "%ld", contact->expiration_time.tv_sec) < 0) ? -1 : 0;
 }
 
-/*! \brief Helper function which validates a permanent contact */
-static int permanent_contact_validate(void *data)
-{
-       const char *value = data;
-       pj_pool_t *pool;
-       pj_str_t contact_uri;
-       static const pj_str_t HCONTACT = { "Contact", 7 };
-       pjsip_contact_hdr *contact_hdr;
-       int rc = 0;
-
-       pool = pjsip_endpt_create_pool(ast_sip_get_pjsip_endpoint(), "Permanent Contact Validation", 256, 256);
-       if (!pool) {
-               return -1;
-       }
-
-       pj_strdup2_with_null(pool, &contact_uri, value);
-       if (!(contact_hdr = pjsip_parse_hdr(pool, &HCONTACT, contact_uri.ptr, contact_uri.slen, NULL))
-               || !(PJSIP_URI_SCHEME_IS_SIP(contact_hdr->uri)
-                       || PJSIP_URI_SCHEME_IS_SIPS(contact_hdr->uri))) {
-               rc = -1;
-       }
-
-       pjsip_endpt_release_pool(ast_sip_get_pjsip_endpoint(), pool);
-       return rc;
-}
-
 static int permanent_uri_sort_fn(const void *obj_left, const void *obj_right, int flags)
 {
        const struct ast_sip_contact *object_left = obj_left;
@@ -393,12 +367,6 @@ static int permanent_uri_handler(const struct aco_option *opt, struct ast_variab
                struct ast_sip_contact_status *status;
                char contact_id[strlen(aor_id) + strlen(contact_uri) + 2 + 1];
 
-               if (ast_sip_push_task_synchronous(NULL, permanent_contact_validate, contact_uri)) {
-                       ast_log(LOG_ERROR, "Permanent URI on aor '%s' with contact '%s' failed to parse\n",
-                               ast_sorcery_object_get_id(aor), contact_uri);
-                       return -1;
-               }
-
                if (!aor->permanent_contacts) {
                        aor->permanent_contacts = ao2_container_alloc_list(AO2_ALLOC_OPT_LOCK_NOLOCK,
                                AO2_CONTAINER_ALLOC_OPT_DUPS_REJECT, permanent_uri_sort_fn, NULL);
@@ -626,13 +594,12 @@ struct ast_sip_endpoint_formatter endpoint_aor_formatter = {
        .format_ami = format_ami_endpoint_aor
 };
 
-static struct ao2_container *cli_aor_get_container(void)
+static struct ao2_container *cli_aor_get_container(const char *regex)
 {
        RAII_VAR(struct ao2_container *, container, NULL, ao2_cleanup);
        struct ao2_container *s_container;
 
-       container = ast_sorcery_retrieve_by_fields(ast_sip_get_sorcery(), "aor",
-               AST_RETRIEVE_FLAG_MULTIPLE | AST_RETRIEVE_FLAG_ALL, NULL);
+       container = ast_sorcery_retrieve_by_regex(ast_sip_get_sorcery(), "aor", regex);
        if (!container) {
                return NULL;
        }
@@ -730,12 +697,25 @@ static int cli_contact_iterate(void *container, ao2_callback_fn callback, void *
        return ast_sip_for_each_contact(container, callback, args);
 }
 
-static struct ao2_container *cli_contact_get_container(void)
+static int cli_filter_contacts(void *obj, void *arg, int flags)
+{
+       struct ast_sip_contact_wrapper *wrapper = obj;
+       regex_t *regexbuf = arg;
+
+       if (!regexec(regexbuf, wrapper->contact_id, 0, NULL, 0)) {
+               return 0;
+       }
+
+       return CMP_MATCH;
+}
+
+static struct ao2_container *cli_contact_get_container(const char *regex)
 {
        RAII_VAR(struct ao2_container *, parent_container, NULL, ao2_cleanup);
        struct ao2_container *child_container;
+       regex_t regexbuf;
 
-       parent_container =  cli_aor_get_container();
+       parent_container =  cli_aor_get_container("");
        if (!parent_container) {
                return NULL;
        }
@@ -748,12 +728,23 @@ static struct ao2_container *cli_contact_get_container(void)
 
        ao2_callback(parent_container, OBJ_NODATA, cli_aor_gather_contacts, child_container);
 
+       if (!ast_strlen_zero(regex)) {
+               if (regcomp(&regexbuf, regex, REG_EXTENDED | REG_NOSUB)) {
+                       ao2_ref(child_container, -1);
+                       return NULL;
+               }
+               ao2_callback(child_container, OBJ_UNLINK | OBJ_MULTIPLE | OBJ_NODATA, cli_filter_contacts, &regexbuf);
+               regfree(&regexbuf);
+       }
+
        return child_container;
 }
 
 static void *cli_contact_retrieve_by_id(const char *id)
 {
-       return ao2_find(cli_contact_get_container(), id, OBJ_KEY | OBJ_NOLOCK);
+       RAII_VAR(struct ao2_container *, container, cli_contact_get_container(""), ao2_cleanup);
+
+       return ao2_find(container, id, OBJ_KEY | OBJ_NOLOCK);
 }
 
 static int cli_contact_print_header(void *obj, void *arg, int flags)
@@ -890,12 +881,14 @@ static int cli_aor_print_body(void *obj, void *arg, int flags)
 static struct ast_cli_entry cli_commands[] = {
        AST_CLI_DEFINE(ast_sip_cli_traverse_objects, "List PJSIP Aors",
                .command = "pjsip list aors",
-               .usage = "Usage: pjsip list aors\n"
-                                "       List the configured PJSIP Aors\n"),
+               .usage = "Usage: pjsip list aors [ like <pattern> ]\n"
+                               "       List the configured PJSIP Aors\n"
+                               "       Optional regular expression pattern is used to filter the list.\n"),
        AST_CLI_DEFINE(ast_sip_cli_traverse_objects, "Show PJSIP Aors",
                .command = "pjsip show aors",
-               .usage = "Usage: pjsip show aors\n"
-                                "       Show the configured PJSIP Aors\n"),
+               .usage = "Usage: pjsip show aors [ like <pattern> ]\n"
+                               "       Show the configured PJSIP Aors\n"
+                               "       Optional regular expression pattern is used to filter the list.\n"),
        AST_CLI_DEFINE(ast_sip_cli_traverse_objects, "Show PJSIP Aor",
                .command = "pjsip show aor",
                .usage = "Usage: pjsip show aor <id>\n"
@@ -903,12 +896,14 @@ static struct ast_cli_entry cli_commands[] = {
 
        AST_CLI_DEFINE(ast_sip_cli_traverse_objects, "List PJSIP Contacts",
                .command = "pjsip list contacts",
-               .usage = "Usage: pjsip list contacts\n"
-                                "       List the configured PJSIP contacts\n"),
+               .usage = "Usage: pjsip list contacts [ like <pattern> ]\n"
+                               "       List the configured PJSIP contacts\n"
+                               "       Optional regular expression pattern is used to filter the list.\n"),
        AST_CLI_DEFINE(ast_sip_cli_traverse_objects, "Show PJSIP Contacts",
                .command = "pjsip show contacts",
-               .usage = "Usage: pjsip show contacts\n"
-                                "       Show the configured PJSIP contacts\n"),
+               .usage = "Usage: pjsip show contacts [ like <pattern> ]\n"
+                               "       Show the configured PJSIP contacts\n"
+                               "       Optional regular expression pattern is used to filter the list.\n"),
        AST_CLI_DEFINE(ast_sip_cli_traverse_objects, "Show PJSIP Contact",
                .command = "pjsip show contact",
                .usage = "Usage: pjsip show contact\n"
index 16df3f5..bbd0ac4 100644 (file)
@@ -125,6 +125,7 @@ char *ast_sip_cli_traverse_objects(struct ast_cli_entry *e, int cmd, struct ast_
        const char *cmd2;
        const char *object_id;
        char formatter_type[64];
+       const char *regex;
 
        struct ast_sip_cli_context context = {
                .indent_level = 0,
@@ -162,6 +163,18 @@ char *ast_sip_cli_traverse_objects(struct ast_cli_entry *e, int cmd, struct ast_
                is_container = 1;
        }
 
+       if (cmd != CLI_GENERATE
+               && is_container
+               && a->argc >= 4
+               && strcmp(object_id, "like") == 0) {
+               if (ast_strlen_zero(a->argv[4])) {
+                       return CLI_SHOWUSAGE;
+               }
+               regex = a->argv[4];
+       } else {
+               regex = "";
+       }
+
        if (cmd == CLI_GENERATE
                && (is_container
                        || a->argc > 4
@@ -187,7 +200,7 @@ char *ast_sip_cli_traverse_objects(struct ast_cli_entry *e, int cmd, struct ast_
                " =========================================================================================\n\n");
 
        if (is_container || cmd == CLI_GENERATE) {
-               container = formatter_entry->get_container();
+               container = formatter_entry->get_container(regex);
                if (!container) {
                        ast_cli(a->fd, "No container returned for object type %s.\n",
                                formatter_type);
index cfb17cd..aa753f4 100644 (file)
@@ -1084,29 +1084,6 @@ static struct ast_endpoint *persistent_endpoint_find_or_create(const struct ast_
        return persistent->endpoint;
 }
 
-/*! \brief Helper function which validates an outbound proxy */
-static int outbound_proxy_validate(void *data)
-{
-       const char *proxy = data;
-       pj_pool_t *pool;
-       pj_str_t tmp;
-       static const pj_str_t ROUTE_HNAME = { "Route", 5 };
-
-       pool = pjsip_endpt_create_pool(ast_sip_get_pjsip_endpoint(), "Outbound Proxy Validation", 256, 256);
-       if (!pool) {
-               return -1;
-       }
-
-       pj_strdup2_with_null(pool, &tmp, proxy);
-       if (!pjsip_parse_hdr(pool, &ROUTE_HNAME, tmp.ptr, tmp.slen, NULL)) {
-               pjsip_endpt_release_pool(ast_sip_get_pjsip_endpoint(), pool);
-               return -1;
-       }
-
-       pjsip_endpt_release_pool(ast_sip_get_pjsip_endpoint(), pool);
-       return 0;
-}
-
 /*! \brief Callback function for when an object is finalized */
 static int sip_endpoint_apply_handler(const struct ast_sorcery *sorcery, void *obj)
 {
@@ -1116,12 +1093,7 @@ static int sip_endpoint_apply_handler(const struct ast_sorcery *sorcery, void *o
                return -1;
        }
 
-       if (!ast_strlen_zero(endpoint->outbound_proxy) &&
-               ast_sip_push_task_synchronous(NULL, outbound_proxy_validate, (char*)endpoint->outbound_proxy)) {
-               ast_log(LOG_ERROR, "Invalid outbound proxy '%s' specified on endpoint '%s'\n",
-                       endpoint->outbound_proxy, ast_sorcery_object_get_id(endpoint));
-               return -1;
-       } else if (endpoint->extensions.timer.min_se < 90) {
+       if (endpoint->extensions.timer.min_se < 90) {
                ast_log(LOG_ERROR, "Session timer minimum expires time must be 90 or greater on endpoint '%s'\n",
                        ast_sorcery_object_get_id(endpoint));
                return -1;
@@ -1423,13 +1395,12 @@ static int ami_show_endpoints(struct mansession *s, const struct message *m)
        return 0;
 }
 
-static struct ao2_container *cli_endpoint_get_container(void)
+static struct ao2_container *cli_endpoint_get_container(const char *regex)
 {
        RAII_VAR(struct ao2_container *, container, NULL, ao2_cleanup);
        struct ao2_container *s_container;
 
-       container = ast_sorcery_retrieve_by_fields(sip_sorcery, "endpoint",
-               AST_RETRIEVE_FLAG_MULTIPLE | AST_RETRIEVE_FLAG_ALL, NULL);
+       container = ast_sorcery_retrieve_by_regex(sip_sorcery, "endpoint", regex);
        if (!container) {
                return NULL;
        }
@@ -1543,12 +1514,26 @@ static int cli_endpoint_gather_channels(void *obj, void *arg, int flags)
        return 0;
 }
 
-static struct ao2_container *cli_channel_get_container(void)
+static int cli_filter_channels(void *obj, void *arg, int flags)
+{
+       struct ast_channel_snapshot *channel = obj;
+       regex_t *regexbuf = arg;
+
+       if (!regexec(regexbuf, channel->name, 0, NULL, 0)
+               || !regexec(regexbuf, channel->appl, 0, NULL, 0)) {
+               return 0;
+       }
+
+       return CMP_MATCH;
+}
+
+static struct ao2_container *cli_channel_get_container(const char *regex)
 {
        RAII_VAR(struct ao2_container *, parent_container, NULL, ao2_cleanup);
        struct ao2_container *child_container;
+       regex_t regexbuf;
 
-       parent_container = cli_endpoint_get_container();
+       parent_container = cli_endpoint_get_container("");
        if (!parent_container) {
                return NULL;
        }
@@ -1560,6 +1545,15 @@ static struct ao2_container *cli_channel_get_container(void)
 
        ao2_callback(parent_container, OBJ_NODATA, cli_endpoint_gather_channels, child_container);
 
+       if (!ast_strlen_zero(regex)) {
+               if (regcomp(&regexbuf, regex, REG_EXTENDED | REG_NOSUB)) {
+                       ao2_ref(child_container, -1);
+                       return NULL;
+               }
+               ao2_callback(child_container, OBJ_UNLINK | OBJ_MULTIPLE | OBJ_NODATA, cli_filter_channels, &regexbuf);
+               regfree(&regexbuf);
+       }
+
        return child_container;
 }
 
@@ -1572,7 +1566,7 @@ static const char *cli_channel_get_id(const void *obj)
 
 static void *cli_channel_retrieve_by_id(const char *id)
 {
-       RAII_VAR(struct ao2_container *, container, cli_channel_get_container(), ao2_cleanup);
+       RAII_VAR(struct ao2_container *, container, cli_channel_get_container(""), ao2_cleanup);
 
        return ao2_find(container, id, OBJ_KEY | OBJ_NOLOCK);
 }
@@ -1774,12 +1768,14 @@ static int cli_endpoint_print_body(void *obj, void *arg, int flags)
 static struct ast_cli_entry cli_commands[] = {
        AST_CLI_DEFINE(ast_sip_cli_traverse_objects, "List PJSIP Channels",
                .command = "pjsip list channels",
-               .usage = "Usage: pjsip list channels\n"
-                                "       List the active PJSIP channels\n"),
+               .usage = "Usage: pjsip list channels [ like <pattern> ]\n"
+                               "       List the active PJSIP channels\n"
+                               "       Optional regular expression pattern is used to filter the list.\n"),
        AST_CLI_DEFINE(ast_sip_cli_traverse_objects, "Show PJSIP Channels",
                .command = "pjsip show channels",
-               .usage = "Usage: pjsip show channels\n"
-                                "       List(detailed) the active PJSIP channels\n"),
+               .usage = "Usage: pjsip show channels [ like <pattern> ]\n"
+                               "       List(detailed) the active PJSIP channels\n"
+                               "       Optional regular expression pattern is used to filter the list.\n"),
        AST_CLI_DEFINE(ast_sip_cli_traverse_objects, "Show PJSIP Channel",
                .command = "pjsip show channel",
                .usage = "Usage: pjsip show channel\n"
@@ -1787,12 +1783,14 @@ static struct ast_cli_entry cli_commands[] = {
 
        AST_CLI_DEFINE(ast_sip_cli_traverse_objects, "List PJSIP Endpoints",
                .command = "pjsip list endpoints",
-               .usage = "Usage: pjsip list endpoints\n"
-                                "       List the configured PJSIP endpoints\n"),
+               .usage = "Usage: pjsip list endpoints [ like <pattern> ]\n"
+                               "       List the configured PJSIP endpoints\n"
+                               "       Optional regular expression pattern is used to filter the list.\n"),
        AST_CLI_DEFINE(ast_sip_cli_traverse_objects, "Show PJSIP Endpoints",
                .command = "pjsip show endpoints",
-               .usage = "Usage: pjsip show endpoints\n"
-                                "       List(detailed) the configured PJSIP endpoints\n"),
+               .usage = "Usage: pjsip show endpoints [ like <pattern> ]\n"
+                               "       List(detailed) the configured PJSIP endpoints\n"
+                               "       Optional regular expression pattern is used to filter the list.\n"),
        AST_CLI_DEFINE(ast_sip_cli_traverse_objects, "Show PJSIP Endpoint",
                .command = "pjsip show endpoint",
                .usage = "Usage: pjsip show endpoint <id>\n"
index bbf3407..39d7573 100644 (file)
@@ -298,13 +298,6 @@ struct ast_sip_endpoint_formatter endpoint_identify_formatter = {
        .format_ami = format_ami_endpoint_identify
 };
 
-static int cli_populate_container(void *obj, void *arg, int flags)
-{
-       ao2_link(arg, obj);
-
-       return 0;
-}
-
 static int cli_iterator(void *container, ao2_callback_fn callback, void *args)
 {
        const struct ast_sip_endpoint *endpoint = container;
@@ -328,47 +321,28 @@ static int cli_iterator(void *container, ao2_callback_fn callback, void *args)
        return 0;
 }
 
-static int cli_endpoint_gather_identifies(void *obj, void *arg, int flags)
+static struct ao2_container *cli_get_container(const char *regex)
 {
-       struct ast_sip_endpoint *endpoint = obj;
-       struct ao2_container *container = arg;
-
-       cli_iterator(endpoint, cli_populate_container, container);
-
-       return 0;
-}
+       RAII_VAR(struct ao2_container *, container, NULL, ao2_cleanup);
+       struct ao2_container *s_container;
 
-static struct ao2_container *cli_get_container(void)
-{
-       RAII_VAR(struct ao2_container *, parent_container, NULL, ao2_cleanup);
-       RAII_VAR(struct ao2_container *, s_parent_container, NULL, ao2_cleanup);
-       struct ao2_container *child_container;
-
-       parent_container =  ast_sorcery_retrieve_by_fields(ast_sip_get_sorcery(), "endpoint",
-               AST_RETRIEVE_FLAG_MULTIPLE | AST_RETRIEVE_FLAG_ALL, NULL);
-       if (!parent_container) {
+       container =  ast_sorcery_retrieve_by_regex(ast_sip_get_sorcery(), "identify", regex);
+       if (!container) {
                return NULL;
        }
 
-       s_parent_container = ao2_container_alloc_list(AO2_ALLOC_OPT_LOCK_NOLOCK, 0,
+       s_container = ao2_container_alloc_list(AO2_ALLOC_OPT_LOCK_NOLOCK, 0,
                ast_sorcery_object_id_sort, ast_sorcery_object_id_compare);
-       if (!s_parent_container) {
-               return NULL;
-       }
-
-       if (ao2_container_dup(s_parent_container, parent_container, 0)) {
+       if (!s_container) {
                return NULL;
        }
 
-       child_container = ao2_container_alloc_list(AO2_ALLOC_OPT_LOCK_NOLOCK, 0,
-               ast_sorcery_object_id_sort, ast_sorcery_object_id_compare);
-       if (!child_container) {
+       if (ao2_container_dup(s_container, container, 0)) {
+               ao2_ref(s_container, -1);
                return NULL;
        }
 
-       ao2_callback(s_parent_container, OBJ_NODATA, cli_endpoint_gather_identifies, child_container);
-
-       return child_container;
+       return s_container;
 }
 
 static void *cli_retrieve_by_id(const char *id)
@@ -461,12 +435,14 @@ static char *my_cli_traverse_objects(struct ast_cli_entry *e, int cmd,
 static struct ast_cli_entry cli_identify[] = {
 AST_CLI_DEFINE(my_cli_traverse_objects, "List PJSIP Identifies",
        .command = "pjsip list identifies",
-       .usage = "Usage: pjsip list identifies\n"
-       "       List the configured PJSIP Identifies\n"),
+       .usage = "Usage: pjsip list identifies [ like <pattern> ]\n"
+       "       List the configured PJSIP Identifies\n"
+       "       Optional regular expression pattern is used to filter the list.\n"),
 AST_CLI_DEFINE(my_cli_traverse_objects, "Show PJSIP Identifies",
        .command = "pjsip show identifies",
-       .usage = "Usage: pjsip show identifies\n"
-       "       Show the configured PJSIP Identifies\n"),
+       .usage = "Usage: pjsip show identifies [ like <pattern> ]\n"
+       "       Show the configured PJSIP Identifies\n"
+       "       Optional regular expression pattern is used to filter the list.\n"),
 AST_CLI_DEFINE(my_cli_traverse_objects, "Show PJSIP Identify",
        .command = "pjsip show identify",
        .usage = "Usage: pjsip show identify <id>\n"
index a8a11bc..27d16bd 100644 (file)
@@ -115,7 +115,7 @@ static void exten_state_subscription_destructor(void *obj)
        struct exten_state_subscription *sub = obj;
 
        ast_free(sub->user_agent);
-       ao2_cleanup(sub->sip_sub);
+       ast_sip_subscription_destroy(sub->sip_sub);
        ast_taskprocessor_unreference(sub->serializer);
 }
 
@@ -160,7 +160,7 @@ static struct exten_state_subscription *exten_state_subscription_alloc(
                return NULL;
        }
 
-       exten_state_sub->sip_sub = ao2_bump(sip_sub);
+       exten_state_sub->sip_sub = sip_sub;
 
        /* We keep our own reference to the serializer as there is no guarantee in state_changed
         * that the subscription tree is still valid when it is called. This can occur when
index 8923d3b..f6600dd 100644 (file)
@@ -204,7 +204,9 @@ static void mwi_subscription_destructor(void *obj)
        struct mwi_subscription *sub = obj;
 
        ast_debug(3, "Destroying MWI subscription for endpoint %s\n", sub->id);
-       ao2_cleanup(sub->sip_sub);
+       if (sub->is_solicited) {
+               ast_sip_subscription_destroy(sub->sip_sub);
+       }
        ao2_cleanup(sub->stasis_subs);
        ast_free(sub->aors);
 }
@@ -233,7 +235,7 @@ static struct mwi_subscription *mwi_subscription_alloc(struct ast_sip_endpoint *
         * state not being updated on the device
         */
        if (is_solicited) {
-               sub->sip_sub = ao2_bump(sip_sub);
+               sub->sip_sub = sip_sub;
        }
 
        sub->stasis_subs = ao2_container_alloc(STASIS_BUCKETS, stasis_sub_hash, stasis_sub_cmp);
index 6bdba56..7ff5f16 100644 (file)
                                                buggy registrars.
                                        </para></description>
                                </configOption>
+                               <configOption name="fatal_retry_interval" default="0">
+                                       <synopsis>Interval used when receiving a Fatal response.</synopsis>
+                                       <description><para>
+                                               If a fatal response is received, chan_pjsip will wait
+                                               <replaceable>fatal_retry_interval</replaceable> seconds before
+                                               attempting registration again. If 0 is specified, chan_pjsip will not
+                                               retry after receiving a fatal (non-temporary 4xx, 5xx, 6xx) response.
+                                               Setting this to a non-zero value may go against a "SHOULD NOT" in RFC3261,
+                                               but can be used to work around buggy registrars.</para>
+                                               <note><para>if also set the <replaceable>forbidden_retry_interval</replaceable>
+                                               takes precedence over this one when a 403 is received.
+                                               Also, if <replaceable>auth_rejection_permanent</replaceable> equals 'yes' then
+                                               a 401 and 407 become subject to this retry interval.</para></note>
+                                       </description>
+                               </configOption>
                                <configOption name="server_uri">
                                        <synopsis>SIP URI of the server to register against</synopsis>
                                        <description><para>
@@ -277,6 +292,8 @@ struct sip_outbound_registration {
        unsigned int retry_interval;
        /*! \brief Interval at which retries should occur for permanent responses */
        unsigned int forbidden_retry_interval;
+       /*! \brief Interval at which retries should occur for all permanent responses */
+       unsigned int fatal_retry_interval;
        /*! \brief Treat authentication challenges that we cannot handle as permanent failures */
        unsigned int auth_rejection_permanent;
        /*! \brief Maximum number of retries permitted */
@@ -312,6 +329,8 @@ struct sip_outbound_registration_client_state {
        unsigned int retry_interval;
        /*! \brief Interval at which retries should occur for permanent responses */
        unsigned int forbidden_retry_interval;
+       /*! \brief Interval at which retries should occur for all permanent responses */
+       unsigned int fatal_retry_interval;
        /*! \brief Treat authentication challenges that we cannot handle as permanent failures */
        unsigned int auth_rejection_permanent;
        /*! \brief Determines whether SIP Path support should be advertised */
@@ -799,6 +818,14 @@ static int handle_registration_response(void *data)
                        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 '%u' seconds\n",
                                server_uri, client_uri, response->client_state->forbidden_retry_interval);
+               } else if (response->client_state->fatal_retry_interval
+                          && response->client_state->retries < response->client_state->max_retries) {
+                       /* Some kind of fatal failure response received, so retry according to configured interval */
+                       response->client_state->status = SIP_REGISTRATION_REJECTED_TEMPORARY;
+                       response->client_state->retries++;
+                       schedule_registration(response->client_state, response->client_state->fatal_retry_interval);
+                       ast_log(LOG_WARNING, "'%d' fatal response received from '%s' on registration attempt to '%s', retrying in '%u' seconds\n",
+                               response->code, server_uri, client_uri, response->client_state->fatal_retry_interval);
                } else {
                        /* Finally if there's no hope of registering give up */
                        response->client_state->status = SIP_REGISTRATION_REJECTED_PERMANENT;
@@ -1186,6 +1213,7 @@ static int sip_outbound_registration_perform(void *data)
        }
        state->client_state->retry_interval = registration->retry_interval;
        state->client_state->forbidden_retry_interval = registration->forbidden_retry_interval;
+       state->client_state->fatal_retry_interval = registration->fatal_retry_interval;
        state->client_state->max_retries = registration->max_retries;
        state->client_state->retries = 0;
        state->client_state->support_path = registration->support_path;
@@ -1611,11 +1639,12 @@ static int ami_show_outbound_registrations(struct mansession *s,
        return 0;
 }
 
-static struct ao2_container *cli_get_container(void)
+static struct ao2_container *cli_get_container(const char *regex)
 {
-       RAII_VAR(struct ao2_container *, container, get_registrations(), ao2_cleanup);
+       RAII_VAR(struct ao2_container *, container, NULL, ao2_cleanup);
        struct ao2_container *s_container;
 
+       container = ast_sorcery_retrieve_by_regex(ast_sip_get_sorcery(), "registration", regex);
        if (!container) {
                return NULL;
        }
@@ -1719,12 +1748,14 @@ static struct ast_cli_entry cli_outbound_registration[] = {
        AST_CLI_DEFINE(cli_register, "Registers an outbound registration target"),
        AST_CLI_DEFINE(my_cli_traverse_objects, "List PJSIP Registrations",
                .command = "pjsip list registrations",
-               .usage = "Usage: pjsip list registrations\n"
-                                "       List the configured PJSIP Registrations\n"),
+               .usage = "Usage: pjsip list registrations [ like <pattern> ]\n"
+                               "       List the configured PJSIP Registrations\n"
+                               "       Optional regular expression pattern is used to filter the list.\n"),
        AST_CLI_DEFINE(my_cli_traverse_objects, "Show PJSIP Registrations",
                .command = "pjsip show registrations",
-               .usage = "Usage: pjsip show registrations\n"
-                                "       Show the configured PJSIP Registrations\n"),
+               .usage = "Usage: pjsip show registrations [ like <pattern> ]\n"
+                               "       Show the configured PJSIP Registrations\n"
+                               "       Optional regular expression pattern is used to filter the list.\n"),
        AST_CLI_DEFINE(my_cli_traverse_objects, "Show PJSIP Registration",
                .command = "pjsip show registration",
                .usage = "Usage: pjsip show registration <id>\n"
@@ -1909,6 +1940,7 @@ static int load_module(void)
        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", "fatal_retry_interval", "0", OPT_UINT_T, 0, FLDSET(struct sip_outbound_registration, fatal_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, outbound_auths_to_str, outbound_auths_to_var_list, 0, 0);
index 7d84b46..bbf2128 100644 (file)
@@ -411,6 +411,8 @@ struct sip_subscription_tree {
        int is_list;
        /*! Next item in the list */
        AST_LIST_ENTRY(sip_subscription_tree) next;
+       /*! Indicates that a NOTIFY is currently being sent on the SIP subscription */
+       int last_notify;
 };
 
 /*!
@@ -596,6 +598,7 @@ static void subscription_persistence_remove(struct sip_subscription_tree *sub_tr
 
        ast_sorcery_delete(ast_sip_get_sorcery(), sub_tree->persistence);
        ao2_ref(sub_tree->persistence, -1);
+       sub_tree->persistence = NULL;
 }
 
 
@@ -1020,26 +1023,6 @@ static int datastore_cmp(void *obj, void *arg, int flags)
        return strcmp(datastore1->uid, uid2) ? 0 : CMP_MATCH | CMP_STOP;
 }
 
-static int subscription_remove_serializer(void *obj)
-{
-       struct sip_subscription_tree *sub_tree = obj;
-
-       /* This is why we keep the dialog on the subscription. When the subscription
-        * is destroyed, there is no guarantee that the underlying dialog is ready
-        * to be destroyed. Furthermore, there's no guarantee in the opposite direction
-        * either. The dialog could be destroyed before our subscription is. We fix
-        * this problem by keeping a reference to the dialog until it is time to
-        * destroy the subscription. We need to have the dialog available when the
-        * subscription is destroyed so that we can guarantee that our attempt to
-        * remove the serializer will be successful.
-        */
-       ast_sip_dialog_set_serializer(sub_tree->dlg, NULL);
-       ast_sip_dialog_set_endpoint(sub_tree->dlg, NULL);
-       pjsip_dlg_dec_session(sub_tree->dlg, &pubsub_module);
-
-       return 0;
-}
-
 static void add_subscription(struct sip_subscription_tree *obj)
 {
        SCOPED_LOCK(lock, &subscriptions, AST_RWLIST_WRLOCK, AST_RWLIST_UNLOCK);
@@ -1063,14 +1046,32 @@ static void remove_subscription(struct sip_subscription_tree *obj)
        AST_RWLIST_TRAVERSE_SAFE_END;
 }
 
-static void subscription_destructor(void *obj)
+static void destroy_subscription(struct ast_sip_subscription *sub)
 {
-       struct ast_sip_subscription *sub = obj;
-
        ast_debug(3, "Destroying SIP subscription to resource %s\n", sub->resource);
        ast_free(sub->body_text);
 
+       AST_VECTOR_FREE(&sub->children);
        ao2_cleanup(sub->datastores);
+       ast_free(sub);
+}
+
+static void destroy_subscriptions(struct ast_sip_subscription *root)
+{
+       int i;
+
+       if (!root) {
+               return;
+       }
+
+       for (i = 0; i < AST_VECTOR_SIZE(&root->children); ++i) {
+               struct ast_sip_subscription *child;
+
+               child = AST_VECTOR_GET(&root->children, i);
+               destroy_subscriptions(child);
+       }
+
+       destroy_subscription(root);
 }
 
 static struct ast_sip_subscription *allocate_subscription(const struct ast_sip_subscription_handler *handler,
@@ -1079,7 +1080,7 @@ static struct ast_sip_subscription *allocate_subscription(const struct ast_sip_s
        struct ast_sip_subscription *sub;
        pjsip_sip_uri *contact_uri;
 
-       sub = ao2_alloc(sizeof(*sub) + strlen(resource) + 1, subscription_destructor);
+       sub = ast_calloc(1, sizeof(*sub) + strlen(resource) + 1);
        if (!sub) {
                return NULL;
        }
@@ -1087,13 +1088,13 @@ static struct ast_sip_subscription *allocate_subscription(const struct ast_sip_s
 
        sub->datastores = ao2_container_alloc(DATASTORE_BUCKETS, datastore_hash, datastore_cmp);
        if (!sub->datastores) {
-               ao2_ref(sub, -1);
+               destroy_subscription(sub);
                return NULL;
        }
 
        sub->body_text = ast_str_create(128);
        if (!sub->body_text) {
-               ao2_ref(sub, -1);
+               destroy_subscription(sub);
                return NULL;
        }
 
@@ -1104,7 +1105,7 @@ static struct ast_sip_subscription *allocate_subscription(const struct ast_sip_s
 
        sub->handler = handler;
        sub->subscription_state = PJSIP_EVSUB_STATE_ACTIVE;
-       sub->tree = tree;
+       sub->tree = ao2_bump(tree);
 
        return sub;
 }
@@ -1132,6 +1133,7 @@ static struct ast_sip_subscription *create_virtual_subscriptions(const struct as
 
        sub->full_state = current->full_state;
        sub->body_generator = generator;
+       AST_VECTOR_INIT(&sub->children, AST_VECTOR_SIZE(&current->children));
 
        for (i = 0; i < AST_VECTOR_SIZE(&current->children); ++i) {
                struct ast_sip_subscription *child;
@@ -1166,7 +1168,6 @@ static void shutdown_subscriptions(struct ast_sip_subscription *sub)
        if (AST_VECTOR_SIZE(&sub->children) > 0) {
                for (i = 0; i < AST_VECTOR_SIZE(&sub->children); ++i) {
                        shutdown_subscriptions(AST_VECTOR_GET(&sub->children, i));
-                       ao2_cleanup(AST_VECTOR_GET(&sub->children, i));
                }
                return;
        }
@@ -1176,37 +1177,58 @@ static void shutdown_subscriptions(struct ast_sip_subscription *sub)
                sub->handler->subscription_shutdown(sub);
        }
 }
+static int subscription_unreference_dialog(void *obj)
+{
+       struct sip_subscription_tree *sub_tree = obj;
+
+       /* This is why we keep the dialog on the subscription. When the subscription
+        * is destroyed, there is no guarantee that the underlying dialog is ready
+        * to be destroyed. Furthermore, there's no guarantee in the opposite direction
+        * either. The dialog could be destroyed before our subscription is. We fix
+        * this problem by keeping a reference to the dialog until it is time to
+        * destroy the subscription. We need to have the dialog available when the
+        * subscription is destroyed so that we can guarantee that our attempt to
+        * remove the serializer will be successful.
+        */
+       pjsip_dlg_dec_session(sub_tree->dlg, &pubsub_module);
+       sub_tree->dlg = NULL;
+
+       return 0;
+}
 
 static void subscription_tree_destructor(void *obj)
 {
        struct sip_subscription_tree *sub_tree = obj;
 
+       ast_debug(3, "Destroying subscription tree %p\n", sub_tree);
+
        remove_subscription(sub_tree);
 
-       subscription_persistence_remove(sub_tree);
        ao2_cleanup(sub_tree->endpoint);
 
+       destroy_subscriptions(sub_tree->root);
+
        if (sub_tree->dlg) {
-               ast_sip_push_task_synchronous(NULL, subscription_remove_serializer, sub_tree);
+               ast_sip_push_task_synchronous(sub_tree->serializer, subscription_unreference_dialog, sub_tree);
        }
 
-       shutdown_subscriptions(sub_tree->root);
-       ao2_cleanup(sub_tree->root);
-
        ast_taskprocessor_unreference(sub_tree->serializer);
        ast_module_unref(ast_module_info->self);
 }
 
+void ast_sip_subscription_destroy(struct ast_sip_subscription *sub)
+{
+       ast_debug(3, "Removing subscription %p reference to subscription tree %p\n", sub, sub->tree);
+       ao2_cleanup(sub->tree);
+}
+
 static void subscription_setup_dialog(struct sip_subscription_tree *sub_tree, pjsip_dialog *dlg)
 {
-       /* We keep a reference to the dialog until our subscription is destroyed. See
-        * the subscription_destructor for more details
-        */
-       pjsip_dlg_inc_session(dlg, &pubsub_module);
        sub_tree->dlg = dlg;
        ast_sip_dialog_set_serializer(dlg, sub_tree->serializer);
        ast_sip_dialog_set_endpoint(dlg, sub_tree->endpoint);
        pjsip_evsub_set_mod_data(sub_tree->evsub, pubsub_module.id, sub_tree);
+       pjsip_dlg_inc_session(dlg, &pubsub_module);
 }
 
 static struct sip_subscription_tree *allocate_subscription_tree(struct ast_sip_endpoint *endpoint)
@@ -1497,7 +1519,11 @@ static void sip_subscription_to_ami(struct sip_subscription_tree *sub_tree,
        ast_str_append(buf, 0, "Endpoint: %s\r\n",
                       ast_sorcery_object_get_id(sub_tree->endpoint));
 
-       ast_copy_pj_str(str, &sub_tree->dlg->call_id->id, sizeof(str));
+       if (sub_tree->dlg) {
+               ast_copy_pj_str(str, &sub_tree->dlg->call_id->id, sizeof(str));
+       } else {
+               ast_copy_string(str, "<unknown>", sizeof(str));
+       }
        ast_str_append(buf, 0, "Callid: %s\r\n", str);
 
        ast_str_append(buf, 0, "State: %s\r\n", pjsip_evsub_get_state_name(sub_tree->evsub));
@@ -1518,10 +1544,12 @@ static void sip_subscription_to_ami(struct sip_subscription_tree *sub_tree,
 
 void *ast_sip_subscription_get_header(const struct ast_sip_subscription *sub, const char *header)
 {
-       pjsip_dialog *dlg = sub->tree->dlg;
-       pjsip_msg *msg = ast_sip_mod_data_get(dlg->mod_data, pubsub_module.id, MOD_DATA_MSG);
+       pjsip_dialog *dlg;
+       pjsip_msg *msg;
        pj_str_t name;
 
+       dlg = sub->tree->dlg;
+       msg = ast_sip_mod_data_get(dlg->mod_data, pubsub_module.id, MOD_DATA_MSG);
        pj_cstr(&name, header);
 
        return pjsip_msg_find_hdr_by_name(msg, &name, NULL);
@@ -1654,6 +1682,7 @@ static int sip_subscription_send_request(struct sip_subscription_tree *sub_tree,
 #ifdef TEST_FRAMEWORK
        struct ast_sip_endpoint *endpoint = sub_tree->endpoint;
 #endif
+       pjsip_evsub *evsub = sub_tree->evsub;
        int res;
 
        if (allocate_tdata_buffer(tdata)) {
@@ -1661,13 +1690,13 @@ static int sip_subscription_send_request(struct sip_subscription_tree *sub_tree,
                return -1;
        }
 
-       res = pjsip_evsub_send_request(sub_tree->evsub, tdata) == PJ_SUCCESS ? 0 : -1;
+       res = pjsip_evsub_send_request(evsub, tdata) == PJ_SUCCESS ? 0 : -1;
        subscription_persistence_update(sub_tree, NULL);
 
        ast_test_suite_event_notify("SUBSCRIPTION_STATE_SET",
                "StateText: %s\r\n"
                "Endpoint: %s\r\n",
-               pjsip_evsub_get_state_name(sub_tree->evsub),
+               pjsip_evsub_get_state_name(evsub),
                ast_sorcery_object_get_id(endpoint));
 
        return res;
@@ -2075,6 +2104,8 @@ static pjsip_require_hdr *create_require_eventlist(pj_pool_t *pool)
 /*!
  * \brief Send a NOTIFY request to a subscriber
  *
+ * \pre sub_tree->dlg is locked
+ *
  * \param sub_tree The subscription tree representing the subscription
  * \param force_full_state If true, ignore resource list settings and send full resource list state.
  * \retval 0 Success
@@ -2107,6 +2138,9 @@ static int send_notify(struct sip_subscription_tree *sub_tree, unsigned int forc
                pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr *) require);
        }
 
+       if (sub_tree->root->subscription_state == PJSIP_EVSUB_STATE_TERMINATED) {
+               sub_tree->last_notify = 1;
+       }
        if (sip_subscription_send_request(sub_tree, tdata)) {
                return -1;
        }
@@ -2119,13 +2153,16 @@ static int send_notify(struct sip_subscription_tree *sub_tree, unsigned int forc
 static int serialized_send_notify(void *userdata)
 {
        struct sip_subscription_tree *sub_tree = userdata;
+       pjsip_dialog *dlg = sub_tree->dlg;
 
+       pjsip_dlg_inc_lock(dlg);
        /* It's possible that between when the notification was scheduled
         * and now, that a new SUBSCRIBE arrived, requiring full state to be
         * sent out in an immediate NOTIFY. If that has happened, we need to
         * bail out here instead of sending the batched NOTIFY.
         */
        if (!sub_tree->send_scheduled_notify) {
+               pjsip_dlg_dec_lock(dlg);
                ao2_cleanup(sub_tree);
                return 0;
        }
@@ -2135,6 +2172,7 @@ static int serialized_send_notify(void *userdata)
                        "Resource: %s",
                        sub_tree->root->resource);
        sub_tree->notify_sched_id = -1;
+       pjsip_dlg_dec_lock(dlg);
        ao2_cleanup(sub_tree);
        return 0;
 }
@@ -2167,8 +2205,19 @@ static int schedule_notification(struct sip_subscription_tree *sub_tree)
 int ast_sip_subscription_notify(struct ast_sip_subscription *sub, struct ast_sip_body_data *notify_data,
                int terminate)
 {
+       int res;
+       pjsip_dialog *dlg = sub->tree->dlg;
+
+       pjsip_dlg_inc_lock(dlg);
+
+       if (!sub->tree->evsub) {
+               pjsip_dlg_dec_lock(dlg);
+               return 0;
+       }
+
        if (ast_sip_pubsub_generate_body_content(ast_sip_subscription_get_body_type(sub),
                                ast_sip_subscription_get_body_subtype(sub), notify_data, &sub->body_text)) {
+               pjsip_dlg_dec_lock(dlg);
                return -1;
        }
 
@@ -2178,9 +2227,8 @@ int ast_sip_subscription_notify(struct ast_sip_subscription *sub, struct ast_sip
        }
 
        if (sub->tree->notification_batch_interval) {
-               return schedule_notification(sub->tree);
+               res = schedule_notification(sub->tree);
        } else {
-               int res;
                /* See the note in pubsub_on_rx_refresh() for why sub->tree is refbumped here */
                ao2_ref(sub->tree, +1);
                res = send_notify(sub->tree, 0);
@@ -2188,9 +2236,10 @@ int ast_sip_subscription_notify(struct ast_sip_subscription *sub, struct ast_sip
                                "Resource: %s",
                                sub->tree->root->resource);
                ao2_ref(sub->tree, -1);
-
-               return res;
        }
+
+       pjsip_dlg_dec_lock(dlg);
+       return res;
 }
 
 void ast_sip_subscription_get_local_uri(struct ast_sip_subscription *sub, char *buf, size_t size)
@@ -2200,7 +2249,9 @@ void ast_sip_subscription_get_local_uri(struct ast_sip_subscription *sub, char *
 
 void ast_sip_subscription_get_remote_uri(struct ast_sip_subscription *sub, char *buf, size_t size)
 {
-       pjsip_dialog *dlg = sub->tree->dlg;
+       pjsip_dialog *dlg;
+
+       dlg = sub->tree->dlg;
        ast_copy_pj_str(buf, &dlg->remote.info_str, size);
 }
 
@@ -3139,10 +3190,88 @@ static pj_bool_t pubsub_on_rx_request(pjsip_rx_data *rdata)
        return PJ_FALSE;
 }
 
+static void set_state_terminated(struct ast_sip_subscription *sub)
+{
+       int i;
+
+       sub->subscription_state = PJSIP_EVSUB_STATE_TERMINATED;
+       for (i = 0; i < AST_VECTOR_SIZE(&sub->children); ++i) {
+               set_state_terminated(AST_VECTOR_GET(&sub->children, i));
+       }
+}
+
+/* XXX This function and serialized_pubsub_on_rx_refresh are nearly identical */
+static int serialized_pubsub_on_server_timeout(void *userdata)
+{
+       struct sip_subscription_tree *sub_tree = userdata;
+       pjsip_dialog *dlg = sub_tree->dlg;
+
+       pjsip_dlg_inc_lock(dlg);
+       if (!sub_tree->evsub) {
+               pjsip_dlg_dec_lock(dlg);
+               return 0;
+       }
+       set_state_terminated(sub_tree->root);
+       send_notify(sub_tree, 1);
+       ast_test_suite_event_notify("SUBSCRIPTION_TERMINATED",
+                       "Resource: %s",
+                       sub_tree->root->resource);
+
+       pjsip_dlg_dec_lock(dlg);
+       ao2_cleanup(sub_tree);
+       return 0;
+}
+
+/*!
+ * \brief PJSIP callback when underlying SIP subscription changes state
+ *
+ * This callback is a bit of a mess, because it's not always called when
+ * you might expect it to be, and it can be called multiple times for the
+ * same state.
+ *
+ * For instance, this function is not called at all when an incoming SUBSCRIBE
+ * arrives to refresh a subscription. That makes sense in a way, since the
+ * subscription state has not made a change; it was active and remains active.
+ *
+ * However, if an incoming SUBSCRIBE arrives to end a subscription, then this
+ * will be called into once upon receiving the SUBSCRIBE (after the call to
+ * pubsub_on_rx_refresh) and again when sending a NOTIFY to end the subscription.
+ * In both cases, the apparent state of the subscription is "terminated".
+ *
+ * However, the double-terminated state changes don't happen in all cases. For
+ * instance, if a subscription expires, then the only time this callback is
+ * called is when we send the NOTIFY to end the subscription.
+ *
+ * As far as state changes are concerned, we only ever care about transitions
+ * to the "terminated" state. The action we take here is dependent on the
+ * conditions behind why the state change to "terminated" occurred. If the
+ * state change has occurred because we are sending a NOTIFY to end the
+ * subscription, we consider this to be the final hurrah of the subscription
+ * and take measures to start shutting things down. If the state change to
+ * terminated occurs for a different reason (e.g. transaction timeout,
+ * incoming SUBSCRIBE to end the subscription), then we push a task to
+ * send out a NOTIFY. When that NOTIFY is sent, this callback will be
+ * called again and we will actually shut down the subscription. The
+ * subscription tree's last_notify field let's us know if this is being
+ * called as a result of a terminating NOTIFY or not.
+ *
+ * There is no guarantee that this function will be called from a serializer
+ * thread since it can be called due to a transaction timeout. Therefore
+ * synchronization primitives are necessary to ensure that no operations
+ * step on each others' toes. The dialog lock is always held when this
+ * callback is called, so we ensure that relevant structures that may
+ * be touched in this function are always protected by the dialog lock
+ * elsewhere as well. The dialog lock in particular protects
+ *
+ * \li The subscription tree's last_notify field
+ * \li The subscription tree's evsub pointer
+ */
 static void pubsub_on_evsub_state(pjsip_evsub *evsub, pjsip_event *event)
 {
        struct sip_subscription_tree *sub_tree;
 
+       ast_debug(3, "on_evsub_state called with state %s\n", pjsip_evsub_get_state_name(evsub));
+
        if (pjsip_evsub_get_state(evsub) != PJSIP_EVSUB_STATE_TERMINATED) {
                return;
        }
@@ -3152,21 +3281,63 @@ static void pubsub_on_evsub_state(pjsip_evsub *evsub, pjsip_event *event)
                return;
        }
 
-       ao2_cleanup(sub_tree);
+       if (!sub_tree->last_notify) {
+               if (ast_sip_push_task(sub_tree->serializer, serialized_pubsub_on_server_timeout, ao2_bump(sub_tree))) {
+                       ast_log(LOG_ERROR, "Failed to push task to send final NOTIFY.\n");
+                       ao2_ref(sub_tree, -1);
+               } else {
+                       return;
+               }
+       }
 
        pjsip_evsub_set_mod_data(evsub, pubsub_module.id, NULL);
+       sub_tree->evsub = NULL;
+       ast_sip_dialog_set_serializer(sub_tree->dlg, NULL);
+       ast_sip_dialog_set_endpoint(sub_tree->dlg, NULL);
+       subscription_persistence_remove(sub_tree);
+       shutdown_subscriptions(sub_tree->root);
+
+       /* Remove evsub's reference to the sub_tree */
+       ao2_ref(sub_tree, -1);
 }
 
-static void set_state_terminated(struct ast_sip_subscription *sub)
+static int serialized_pubsub_on_rx_refresh(void *userdata)
 {
-       int i;
+       struct sip_subscription_tree *sub_tree = userdata;
+       pjsip_dialog *dlg = sub_tree->dlg;
 
-       sub->subscription_state = PJSIP_EVSUB_STATE_TERMINATED;
-       for (i = 0; i < AST_VECTOR_SIZE(&sub->children); ++i) {
-               set_state_terminated(AST_VECTOR_GET(&sub->children, i));
+       pjsip_dlg_inc_lock(dlg);
+       if (!sub_tree->evsub) {
+               pjsip_dlg_dec_lock(dlg);
+               return 0;
+       }
+
+       if (pjsip_evsub_get_state(sub_tree->evsub) == PJSIP_EVSUB_STATE_TERMINATED) {
+               set_state_terminated(sub_tree->root);
        }
+
+       send_notify(sub_tree, 1);
+
+       ast_test_suite_event_notify(sub_tree->root->subscription_state == PJSIP_EVSUB_STATE_TERMINATED ?
+                       "SUBSCRIPTION_TERMINATED" : "SUBSCRIPTION_REFRESHED",
+                       "Resource: %s", sub_tree->root->resource);
+
+       pjsip_dlg_dec_lock(dlg);
+       ao2_cleanup(sub_tree);
+       return 0;
 }
 
+/*!
+ * \brief Called whenever an in-dialog SUBSCRIBE is received
+ *
+ * This includes both SUBSCRIBE requests that actually refresh the subscription
+ * as well as SUBSCRIBE requests that end the subscription.
+ *
+ * In the case where the SUBSCRIBE is actually refreshing the subscription we
+ * push a task to send an appropriate NOTIFY request. In the case where the
+ * SUBSCRIBE is ending the subscription, we let the pubsub_on_evsub_state
+ * callback take care of sending the terminal NOTIFY request instead.
+ */
 static void pubsub_on_rx_refresh(pjsip_evsub *evsub, pjsip_rx_data *rdata,
                int *p_st_code, pj_str_t **p_st_text, pjsip_hdr *res_hdr, pjsip_msg_body **p_body)
 {
@@ -3177,31 +3348,19 @@ static void pubsub_on_rx_refresh(pjsip_evsub *evsub, pjsip_rx_data *rdata,
                return;
        }
 
-       /* If sending a NOTIFY to terminate a subscription, then pubsub_on_evsub_state()
-        * will be called when we send the NOTIFY, and that will result in dropping the
-        * refcount of sub_tree by one, and possibly destroying the sub_tree. We need to
-        * hold a reference to the sub_tree until this function returns so that we don't
-        * try to read from or write to freed memory by accident
+       /* PJSIP will set the evsub's state to terminated before calling into this function
+        * if the Expires value of the incoming SUBSCRIBE is 0.
         */
-       ao2_ref(sub_tree, +1);
-
-       if (pjsip_evsub_get_state(evsub) == PJSIP_EVSUB_STATE_TERMINATED) {
-               set_state_terminated(sub_tree->root);
-       }
-
-       if (send_notify(sub_tree, 1)) {
-               *p_st_code = 500;
+       if (pjsip_evsub_get_state(sub_tree->evsub) != PJSIP_EVSUB_STATE_TERMINATED) {
+               if (ast_sip_push_task(sub_tree->serializer, serialized_pubsub_on_rx_refresh, ao2_bump(sub_tree))) {
+                       /* If we can't push the NOTIFY refreshing task...we'll just go with it. */
+                       ao2_ref(sub_tree, -1);
+               }
        }
 
-       ast_test_suite_event_notify(sub_tree->root->subscription_state == PJSIP_EVSUB_STATE_TERMINATED ?
-                       "SUBSCRIPTION_TERMINATED" : "SUBSCRIPTION_REFRESHED",
-                       "Resource: %s", sub_tree->root->resource);
-
        if (sub_tree->is_list) {
                pj_list_insert_before(res_hdr, create_require_eventlist(rdata->tp_info.pool));
        }
-
-       ao2_ref(sub_tree, -1);
 }
 
 static void pubsub_on_rx_notify(pjsip_evsub *evsub, pjsip_rx_data *rdata, int *p_st_code,
@@ -3239,31 +3398,24 @@ static void pubsub_on_client_refresh(pjsip_evsub *evsub)
        ast_sip_push_task(sub_tree->serializer, serialized_pubsub_on_client_refresh, sub_tree);
 }
 
-static int serialized_pubsub_on_server_timeout(void *userdata)
-{
-       struct sip_subscription_tree *sub_tree = userdata;
-
-       set_state_terminated(sub_tree->root);
-       send_notify(sub_tree, 1);
-       ast_test_suite_event_notify("SUBSCRIPTION_TERMINATED",
-                       "Resource: %s",
-                       sub_tree->root->resource);
-
-       ao2_cleanup(sub_tree);
-       return 0;
-}
-
 static void pubsub_on_server_timeout(pjsip_evsub *evsub)
 {
-       struct sip_subscription_tree *sub_tree = pjsip_evsub_get_mod_data(evsub, pubsub_module.id);
 
+       struct sip_subscription_tree *sub_tree = pjsip_evsub_get_mod_data(evsub, pubsub_module.id);
        if (!sub_tree) {
-               /* if a subscription has been terminated and the subscription
-                  timeout/expires is less than the time it takes for all pending
-                  transactions to end then the subscription timer will not have
-                  been canceled yet and sub will be null, so do nothing since
-                  the subscription has already been terminated. */
-               return;
+               /* PJSIP does not terminate the server timeout timer when a SUBSCRIBE
+                * with Expires: 0 arrives to end a subscription, nor does it terminate
+                * this timer when we send a NOTIFY request in response to receiving such
+                * a SUBSCRIBE. PJSIP does not stop the server timeout timer until the
+                * NOTIFY transaction has finished (either through receiving a response
+                * or through a transaction timeout).
+                *
+                * Therefore, it is possible that we can be told that a server timeout
+                * occurred after we already thought that the subscription had been
+                * terminated. In such a case, we will have already removed the sub_tree
+                * from the evsub's mod_data array.
+                */
+        return;
        }
 
        ao2_ref(sub_tree, +1);
index 58702d6..6616524 100644 (file)
@@ -38,6 +38,7 @@
                LINKER_SYMBOL_PREFIXast_sip_subscription_get_remote_uri;
                LINKER_SYMBOL_PREFIXast_sip_subscription_get_header;
                LINKER_SYMBOL_PREFIXast_sip_subscription_is_terminated;
+               LINKER_SYMBOL_PREFIXast_sip_subscription_destroy;
        local:
                *;
 };
index 73aa244..0a54a64 100644 (file)
@@ -67,7 +67,7 @@ h3. Header parameters
 
 h3. Error Responses
 {{#error_responses}}
-* {{code}} - {{{reason}}}
+* {{code}} - {{{wiki_reason}}}
 {{/error_responses}}
 {{/has_error_responses}}
 {{/operations}}
index ab8a8af..68a6799 100644 (file)
@@ -199,6 +199,8 @@ class AsteriskProcessor(SwaggerPostProcessor):
             raise SwaggerError("Summary should end with .", context)
         operation.wiki_summary = wikify(operation.summary or "")
         operation.wiki_notes = wikify(operation.notes or "")
+        for error_response in operation.error_responses:
+            error_response.wiki_reason = wikify(error_response.reason or "")
         operation.parse_body = (operation.body_parameter or operation.has_query_parameters) and True
 
     def process_parameter(self, parameter, context):
index 36ca035..23f2a52 100644 (file)
@@ -291,6 +291,7 @@ static int unload_module(void)
 {{#has_websocket}}
        ao2_cleanup({{full_name}}.ws_server);
        {{full_name}}.ws_server = NULL;
+       ast_ari_websocket_events_event_websocket_dtor();
 {{/has_websocket}}
 {{/apis}}
        stasis_app_unref();