[Pkg-voip-commits] [asterisk] 04/08: Imported Upstream version 13.8.2~dfsg

tzafrir at debian.org tzafrir at debian.org
Sun Apr 24 13:49:08 UTC 2016


This is an automated email from the git hooks/post-receive script.

tzafrir pushed a commit to branch master
in repository asterisk.

commit 81287ef787b1331949f9e70b5bc436c33a187291
Author: Tzafrir Cohen <tzafrir at debian.org>
Date:   Thu Apr 21 16:16:38 2016 +0300

    Imported Upstream version 13.8.2~dfsg
---
 .gitignore                                         |    36 -
 .version                                           |     2 +-
 CHANGES                                            |   152 +
 ChangeLog                                          |  3397 ++++-
 Makefile                                           |    23 +-
 Makefile.rules                                     |    10 +-
 UPGRADE.txt                                        |    17 +
 addons/.gitignore                                  |     1 -
 addons/res_config_mysql.c                          |     6 +-
 agi/.gitignore                                     |     3 -
 apps/app_amd.c                                     |    16 +-
 apps/app_chanspy.c                                 |     8 +-
 apps/app_confbridge.c                              |   245 +-
 apps/app_dial.c                                    |    18 +
 apps/app_fax.c                                     |     4 +-
 apps/app_meetme.c                                  |     2 +-
 apps/app_minivm.c                                  |    14 +-
 apps/app_mixmonitor.c                              |     8 +-
 apps/app_queue.c                                   |    94 +-
 apps/app_stasis.c                                  |    12 +-
 apps/app_voicemail.c                               |    45 +-
 apps/confbridge/conf_state_multi_marked.c          |    12 +-
 asterisk-13.7.0-rc2-summary.html                   |    46 -
 asterisk-13.7.0-rc2-summary.txt                    |   149 -
 asterisk-13.8.2-summary.html                       |    27 +
 asterisk-13.8.2-summary.txt                        |   120 +
 build_tools/.gitignore                             |     1 -
 build_tools/cflags.xml                             |    15 +-
 build_tools/make_check_alembic                     |    29 +
 build_tools/menuselect-deps.in                     |     4 +
 cel/cel_radius.c                                   |     4 +-
 channels/chan_dahdi.c                              |     2 +-
 channels/chan_misdn.c                              |     4 +-
 channels/chan_motif.c                              |     1 -
 channels/chan_pjsip.c                              |   119 +-
 channels/chan_sip.c                                |  1721 ++-
 channels/misdn_config.c                            |     2 +-
 channels/pjsip/dialplan_functions.c                |     2 +-
 channels/sig_analog.c                              |     6 +-
 channels/sip/dialplan_functions.c                  |     2 +-
 channels/sip/include/dialog.h                      |    26 +-
 channels/sip/include/sip.h                         |     4 +-
 configs/basic-pbx/modules.conf                     |     1 +
 configs/samples/amd.conf.sample                    |    30 +-
 configs/samples/confbridge.conf.sample             |    10 +-
 configs/samples/features.conf.sample               |     2 +-
 configs/samples/http.conf.sample                   |    20 +
 configs/samples/pjproject.conf.sample              |    28 +
 configs/samples/pjsip.conf.sample                  |    22 +
 configs/samples/rtp.conf.sample                    |    27 +
 configure                                          |  1026 +-
 configure.ac                                       |   150 +-
 contrib/ast-db-manage/config/env.py                |     3 +-
 .../versions/10aedae86a32_add_outgoing_enum_va.py  |    10 +-
 .../versions/136885b81223_add_regcontext_to_pj.py  |    21 +
 .../versions/154177371065_add_default_from_user.py |     3 +-
 .../1758e8bbf6b_increase_useragent_column_size.py  |     6 +-
 .../189a235b3fd7_add_keep_alive_interval.py        |     3 +-
 .../versions/1d50859ed02e_create_accountcode.py    |     3 +-
 .../21e526ad3040_add_pjsip_debug_option.py         |     3 +-
 .../versions/23530d604b96_add_rpid_immediate.py    |     3 +-
 ...0fa5_add_bind_rtp_to_media_address_to_pjsip.py} |    17 +-
 .../26f10cadc157_add_pjsip_timeout_options.py      |     5 +-
 .../versions/28b8e71e541f_add_g726_non_standard.py |     3 +-
 .../28ce1e718f05_add_fatal_response_interval.py    |     3 +-
 .../2d078ec071b7_increaes_contact_column_size.py   |     6 +-
 ...930b41b3_add_pjsip_endpoint_options_for_12_1.py |    31 +-
 .../versions/31cd4f4891ec_add_auto_dtmf_mode.py    |    16 +-
 ...1a3bf4143e_add_user_eq_phone_option_to_pjsip.py |     3 +-
 .../3855ee4e5f85_add_missing_pjsip_options.py      |     6 +-
 ...cc0b5bc2c9_add_allow_reload_to_ps_transports.py |    26 +
 .../versions/423f34ad36e2_fix_pjsip_qualify_ti.py  |    26 +
 .../versions/43956d550a44_add_tables_for_pjsip.py  |    13 +
 ...f47c6c44_add_pjsip_endpoint_identifier_order.py |     3 +-
 .../461d7d691209_add_pjsip_qualify_timeout.py      |     9 +-
 .../versions/498357a710ae_add_rtp_keepalive.py     |     3 +-
 .../versions/4c573e7135bd_fix_tos_field_types.py   |    54 +-
 .../config/versions/4da0c5f79a9c_create_tables.py  |    14 +-
 .../5139253c0423_make_q_member_uniqueid_autoinc.py |    33 +-
 .../51f8cb66540e_add_further_dtls_options.py       |     5 +-
 .../5950038a6ead_fix_pjsip_verifiy_typo.py         |     6 +-
 ...41e0b5e89_add_pjsip_max_initial_qualify_time.py |     3 +-
 ...c44d5a908_add_missing_columns_to_sys_and_reg.py |    36 +
 .../e96a0b8071c_increase_pjsip_column_size.py      |    33 +-
 ...f2a_add_media_encryption_optimistic_to_pjsip.py |     3 +-
 contrib/docker/Dockerfile.asterisk                 |    19 +
 contrib/docker/Dockerfile.packager                 |     9 +
 contrib/docker/README.md                           |    39 +
 contrib/docker/make-package.sh                     |    72 +
 contrib/realtime/mssql/mssql_cdr.sql               |     4 +-
 contrib/realtime/mssql/mssql_config.sql            |   216 +-
 contrib/realtime/mssql/mssql_voicemail.sql         |    10 +-
 contrib/realtime/mysql/mysql_cdr.sql               |     2 +-
 contrib/realtime/mysql/mysql_config.sql            |   132 +-
 contrib/realtime/mysql/mysql_voicemail.sql         |     6 +-
 contrib/realtime/oracle/oracle_cdr.sql             |    10 +-
 contrib/realtime/oracle/oracle_config.sql          |   220 +-
 contrib/realtime/oracle/oracle_voicemail.sql       |    16 +-
 contrib/realtime/postgresql/postgresql_cdr.sql     |     2 +-
 contrib/realtime/postgresql/postgresql_config.sql  |   170 +-
 .../realtime/postgresql/postgresql_voicemail.sql   |     6 +-
 contrib/scripts/autosupport                        |    12 +-
 contrib/scripts/install_prereq                     |    15 +-
 doc/.gitignore                                     |     4 -
 funcs/func_callerid.c                              |    21 +-
 funcs/func_cdr.c                                   |    41 +-
 funcs/func_channel.c                               |    24 +
 funcs/func_iconv.c                                 |     3 +-
 funcs/func_odbc.c                                  |     1 +
 include/asterisk.h                                 |    20 +
 include/asterisk/.gitignore                        |     3 -
 include/asterisk/_private.h                        |     7 +
 include/asterisk/app.h                             |     2 +
 include/asterisk/autochan.h                        |    20 +
 include/asterisk/autoconfig.h.in                   |    30 +-
 include/asterisk/bridge_channel.h                  |     2 +
 include/asterisk/bridge_channel_internal.h         |    23 +-
 include/asterisk/channel.h                         |    36 +
 include/asterisk/config.h                          |    11 +
 include/asterisk/core_local.h                      |    32 +
 include/asterisk/features_config.h                 |    15 +
 include/asterisk/http_websocket.h                  |     4 +-
 include/asterisk/module.h                          |     2 +-
 include/asterisk/res_odbc.h                        |    78 +-
 include/asterisk/res_odbc_transaction.h            |    54 +
 include/asterisk/res_pjproject.h                   |    96 +
 include/asterisk/res_pjsip.h                       |   216 +-
 include/asterisk/res_pjsip_session.h               |     2 +
 include/asterisk/rtp_engine.h                      |     6 +-
 include/asterisk/select.h                          |     4 +-
 include/asterisk/stasis_cache_pattern.h            |    19 +
 include/asterisk/strings.h                         |    16 +-
 include/asterisk/taskprocessor.h                   |    27 +
 include/asterisk/tcptls.h                          |    10 +-
 include/asterisk/time.h                            |    10 +-
 main/.gitignore                                    |     4 -
 main/Makefile                                      |   121 +-
 main/app.c                                         |    14 +-
 main/asterisk.c                                    |    47 +-
 main/astmm.c                                       |   107 +-
 main/autochan.c                                    |     6 +-
 main/bridge.c                                      |    27 +-
 main/bridge_basic.c                                |   108 +-
 main/bridge_channel.c                              |    79 +-
 main/callerid.c                                    |    13 +-
 main/ccss.c                                        |     2 +-
 main/cdr.c                                         |     8 +-
 main/cel.c                                         |     2 +-
 main/channel.c                                     |   138 +-
 main/channel_internal_api.c                        |    34 +-
 main/config.c                                      |     2 +
 main/config_options.c                              |    29 +-
 main/core_local.c                                  |    39 +
 main/devicestate.c                                 |    16 +-
 main/editline/.gitignore                           |    13 -
 main/endpoints.c                                   |    20 +-
 main/features_config.c                             |    15 +
 main/format_cap.c                                  |     7 +-
 main/http.c                                        |     7 +-
 main/libasteriskpj.c                               |    52 +
 main/loader.c                                      |   120 +-
 main/logger.c                                      |     2 +-
 main/manager.c                                     |    59 +-
 main/message.c                                     |     2 +-
 main/pbx.c                                         | 12711 +++++++------------
 main/pbx_app.c                                     |   510 +
 main/pbx_builtins.c                                |  1438 +++
 main/pbx_functions.c                               |   723 ++
 main/pbx_hangup_handler.c                          |   300 +
 main/pbx_private.h                                 |    46 +
 main/pbx_switch.c                                  |   133 +
 main/pbx_timing.c                                  |   294 +
 main/pbx_variables.c                               |  1180 ++
 main/sched.c                                       |    45 +-
 main/sorcery.c                                     |   109 +-
 main/stasis.c                                      |    19 +-
 main/stasis_cache_pattern.c                        |    34 +-
 main/stdtime/localtime.c                           |     5 +-
 main/taskprocessor.c                               |   167 +-
 main/tcptls.c                                      |    35 +-
 main/udptl.c                                       |    15 +-
 main/utils.c                                       |     2 +-
 makeopts.in                                        |     3 +
 menuselect/.gitignore                              |     7 -
 menuselect/menuselect.c                            |    12 +
 menuselect/menuselect.h                            |     2 +
 menuselect/menuselect_curses.c                     |    73 +-
 pbx/pbx_dundi.c                                    |    21 +-
 res/Makefile                                       |     1 +
 res/ael/.gitignore                                 |     1 -
 res/res_calendar.c                                 |     2 +
 res/res_config_sqlite3.c                           |    16 +-
 res/res_crypto.c                                   |     8 +-
 res/res_http_websocket.c                           |    33 +-
 res/res_jabber.exports.in                          |    16 -
 res/res_musiconhold.c                              |     3 +-
 res/res_mwi_external.c                             |    13 +-
 res/res_odbc.c                                     |  1179 +-
 res/res_odbc.exports.in                            |    17 +-
 res/res_odbc_transaction.c                         |   529 +
 res/res_odbc_transaction.exports.in                |     6 +
 res/res_phoneprov.c                                |    14 +-
 res/res_pjproject.c                                |   458 +
 res/res_pjproject.exports.in                       |     6 +
 res/res_pjsip.c                                    |   425 +-
 res/res_pjsip/config_global.c                      |    22 +
 res/res_pjsip/config_transport.c                   |   854 +-
 res/res_pjsip/include/res_pjsip_private.h          |     7 +
 res/res_pjsip/location.c                           |    88 +-
 res/res_pjsip/pjsip_configuration.c                |    40 +-
 res/res_pjsip/pjsip_distributor.c                  |    27 +-
 res/res_pjsip/pjsip_options.c                      |    11 +-
 res/res_pjsip/presence_xml.c                       |     8 +-
 res/res_pjsip_caller_id.c                          |   119 +-
 res/res_pjsip_config_wizard.c                      |   111 +-
 res/res_pjsip_diversion.c                          |    99 +-
 res/res_pjsip_dtmf_info.c                          |    12 +-
 res/res_pjsip_endpoint_identifier_anonymous.c      |    20 +-
 res/res_pjsip_endpoint_identifier_ip.c             |     6 +-
 res/res_pjsip_endpoint_identifier_user.c           |    22 +-
 res/res_pjsip_history.c                            |  1352 ++
 res/res_pjsip_log_forwarder.c                      |   125 -
 res/res_pjsip_messaging.c                          |     8 +-
 res/res_pjsip_multihomed.c                         |    20 +-
 res/res_pjsip_mwi.c                                |    34 +-
 res/res_pjsip_nat.c                                |    39 +-
 res/res_pjsip_notify.c                             |     2 +-
 res/res_pjsip_outbound_publish.c                   |     2 +-
 res/res_pjsip_outbound_registration.c              |    48 +-
 res/res_pjsip_path.c                               |     6 +-
 res/res_pjsip_pubsub.c                             |    52 +-
 res/res_pjsip_refer.c                              |    46 +-
 res/res_pjsip_registrar.c                          |    45 +-
 res/res_pjsip_sdp_rtp.c                            |    14 +-
 res/res_pjsip_send_to_voicemail.c                  |    16 +-
 res/res_pjsip_session.c                            |   143 +-
 res/res_pjsip_t38.c                                |    46 +-
 ...eepalive.c => res_pjsip_transport_management.c} |   205 +-
 res/res_pjsip_transport_websocket.c                |    40 +-
 res/res_rtp_asterisk.c                             |   111 +-
 res/res_sorcery_memory.c                           |    16 +-
 res/res_sorcery_memory_cache.c                     |     6 +-
 res/res_sorcery_realtime.c                         |    12 +-
 res/res_stasis_device_state.c                      |    10 +-
 res/res_statsd.exports.in                          |     5 +-
 res/res_xmpp.c                                     |    16 +-
 res/stasis/control.c                               |   107 +-
 sounds/Makefile                                    |     4 +-
 tests/test_dlinklists.c                            |    54 +-
 tests/test_sched.c                                 |   104 +
 tests/test_sorcery_memory_cache_thrash.c           |     9 +
 tests/test_sorcery_realtime.c                      |    10 +-
 tests/test_stasis_endpoints.c                      |     3 +-
 tests/test_threadpool.c                            |    46 +-
 third-party/Makefile                               |    21 +
 third-party/Makefile.rules                         |    36 +
 third-party/pjproject/Makefile                     |   145 +
 third-party/pjproject/Makefile.rules               |     7 +
 third-party/pjproject/apply_patches                |    39 +
 third-party/pjproject/configure.m4                 |    47 +
 .../patches/0001-2.4.5-fix-for-tls-async-ops.patch |   224 +
 ...ls-and-transaction-log-levels-from-1-to-3.patch |    70 +
 ...001-ioqueue-Enable-epoll-in-aconfigure.ac.patch |    80 +
 ...rt-Search-for-transport-even-if-listener-.patch |   114 +
 third-party/pjproject/patches/config_site.h        |    34 +
 third-party/pjproject/patches/user.mak             |     2 +
 third-party/versions.mak                           |     2 +
 utils/.gitignore                                   |    25 -
 268 files changed, 24243 insertions(+), 12445 deletions(-)

diff --git a/.gitignore b/.gitignore
deleted file mode 100644
index 0281e10..0000000
--- a/.gitignore
+++ /dev/null
@@ -1,36 +0,0 @@
-# git ls-files --others --exclude-from=.git/info/exclude
-# Lines that start with '#' are comments.
-# For a project mostly in C, the following would be a good set of
-# exclude patterns (uncomment them if you want to use them):
-# *.[oa]
-# *~
-
-# See .gitignore in subdirectories for more ignored files
-
-*~
-*.[oadi]
-*.gz
-*.ii
-*.oo
-*.eo
-*.so
-*.exports
-*.moduleinfo
-*.makeopts
-*.makedeps
-.lastclean
-aclocal.m4
-autom4te.cache
-config.log
-config.status
-defaults.h
-makeopts
-makeopts.embed_rules
-menuselect-tree
-*.sha1
-*.pyc
-*.gcno
-*.gcda
-latex
-doxygen.log
-
diff --git a/.version b/.version
index 51e739d..f65295b 100644
--- a/.version
+++ b/.version
@@ -1 +1 @@
-13.7.0-rc2
\ No newline at end of file
+13.8.2
\ No newline at end of file
diff --git a/CHANGES b/CHANGES
index 40dbfab..29b1b76 100644
--- a/CHANGES
+++ b/CHANGES
@@ -9,6 +9,158 @@
 ==============================================================================
 
 ------------------------------------------------------------------------------
+--- Functionality changes from Asterisk 13.7.0 to Asterisk 13.8.0 ------------
+------------------------------------------------------------------------------
+
+res_pjsip_caller_id
+------------------
+ * Per RFC3325, the 'From' header is now anonymized on outgoing calls when
+   caller id presentation is prohibited.
+
+res_pjsip_config_wizard
+------------------
+ * A new command (pjsip export config_wizard primitives) has been added that
+   will export all the pjsip objects it created to the console or a file
+   suitable for reuse in a pjsip.conf file.
+
+Build System
+------------------
+ * To help insure that Asterisk is compiled and run with the same known
+   version of pjproject, a new option (--with-pjproject-bundled) has been
+   added to ./configure.  When specified, the version of pjproject specified
+   in third-party/versions.mak will be downloaded and configured.  When you
+   make Asterisk, the build process will also automatically build pjproject
+   and Asterisk will be statically linked to it.  Once a particular version
+   of pjproject is configured and built, it won't be configured or built
+   again unless you run a 'make distclean'.
+
+   To facilitate testing, when 'make install' is run, the pjsua and pjsystest
+   utilities and the pjproject python bindings will be installed in
+   ASTDATADIR/third-party/pjproject.
+
+   The default behavior remains building with the shared pjproject
+   installation, if any.
+
+app_confbridge
+------------------
+ * Added CONFBRIDGE_INFO(muted,) for querying the muted conference state.
+
+ * Added Muted header to AMI ConfbridgeListRooms action response list events
+   to indicate the muted conference state.
+
+ * Added Muted column to CLI "confbridge list" output to indicate the muted
+   conference state and made the locked column a yes/no value instead of a
+   locked/unlocked value.
+
+REDIRECTING(reason)
+------------------
+ * The REDIRECTING(reason) value is now treated consistently between
+   chan_sip and chan_pjsip.
+
+   Both channel drivers match incoming reason values with values documented
+   by REDIRECTING(reason) and values documented by RFC5806 regardless of
+   whether they are quoted or not.  RFC5806 values are mapped to the
+   equivalent REDIRECTING(reason) documented value and is set in
+   REDIRECTING(reason).  e.g., an incoming RFC5806 'unconditional' value or a
+   quoted string version ('"unconditional"') is converted to
+   REDIRECTING(reason)'s 'cfu' value.  The user's dialplan only needs to deal
+   with 'cfu' instead of any of the aliases.
+
+   The incoming 480 response reason text supported by chan_sip checks for
+   known reason values and if not matched then puts quotes around the reason
+   string and assigns that to REDIRECTING(reason).
+
+   Both channel drivers send outgoing known REDIRECTING(reason) values as the
+   unquoted RFC5806 equivalent.  User custom values are either sent as is or
+   with added quotes if SIP doesn't allow a character within the value as
+   part of a RFC3261 Section 25.1 token.  Note that there are still
+   limitations on what characters can be put in a custom user value.  e.g.,
+   embedding quotes in the middle of the reason string is just going to cause
+   you grief.
+
+ * Setting a REDIRECTING(reason) value now recognizes RFC5806 aliases.
+   e.g., Setting REDIRECTING(reason) to 'unconditional' is converted to the
+   'cfu' value.
+
+res_pjproject
+------------------
+ * This module is the successor of res_pjsip_log_forwarder.  As well as
+   handling the log forwarding (which now displays as 'pjproject:0' instead
+   of 'pjsip:0'), it also adds a 'pjproject show buildopts' command to the CLI.
+   This displays the compiled-in options of the pjproject installation
+   Asterisk is currently running against.
+
+ * Another feature of this module is the ability to map pjproject log levels
+   to Asterisk log levels, or to suppress the pjproject log messages
+   altogether.  Many of the messages emitted by pjproject itself are the result
+   of errors which Asterisk will ultimately handle so the messages can be
+   misleading or just noise.  A new config file (pjproject.conf) has been added
+   to configure the mapping and a new CLI command (pjproject show log mappings)
+   has been added to display the mappings currently in use.
+
+res_pjsip
+------------------
+ * Transports are now reloadable.  In testing, no in-progress calls were
+   disrupted if the ip address or port weren't changed, but the possibility
+   still exists.  To make sure there are no unintentional drops, a new option
+   'allow_reload', which defaults to 'no' has been added to transport.  If
+   left at the default, changes to the particular transport will be ignored.
+   If set to 'yes', changes (if any) will be applied.
+
+ * Added new global option (regcontext) to pjsip. When set, Asterisk will
+   dynamically create and destroy a NoOp priority 1 extension
+   for a given endpoint who registers or unregisters with us.
+
+res_pjsip_history
+------------------
+ * A new module, res_pjsip_history, has been added that provides SIP history
+   viewing/filtering from the CLI. The module is intended to be used on systems
+   with busy SIP traffic, where existing forms of viewing SIP messages - such
+   as the res_pjsip_logger - may be inadequate. The module provides two new
+   CLI commands:
+   - 'pjsip set history {on|off|clear}' - this enables/disables SIP history
+     capturing, as well as clears an existing history capture. Note that SIP
+     packets captured are stored in memory until cleared. As a result, the
+     history capture should only be used for debugging/viewing purposes, and
+     should *NOT* be left permanently enabled on a system.
+   - 'pjsip show history' - displays the captured SIP history. When invoked
+     with no options, the entire captured history is displayed. Two options
+     are available:
+     -- 'entry <num>' - display a detailed view of a single SIP message in
+        the history
+     -- 'where ...' - filter the history based on some expression. For more
+        information on filtering, view the current CLI help for the
+        'pjsip show history' command.
+
+Voicemail
+------------------
+ * app_voicemail and res_mwi_external can now be built together.  The default
+   remains to build app_voicemail and not res_mwi_external but if they are
+   both built, the load order will cause res_mwi_external to load first and
+   app_voicemail will be skipped.  Use 'preload=app_voicemail.so' in
+   modules.conf to force app_voicemail to be the voicemail provider.
+
+Queue
+-------------------
+ * Added field ReasonPause on QueueMemberStatus if set when paused, the reason
+   the queue member was paused.
+
+res_pjsip_sdp_rtp
+------------------
+ * A new option (bind_rtp_to_media_address) has been added to endpoint which
+   will cause res_pjsip_sdp_rtp to actually bind the RTP instance to the
+   media_address as well as using it in the SDP.  If set, RTP packets will now
+   originate from the media address instead of the operating system's "primary"
+   ip address.
+
+res_rtp_asterisk
+------------------
+ * A new configuration section - ice_host_candidates - has been added to
+   rtp.conf, allowing automatically discovered ICE host candidates to be
+   overriden. This allows an Asterisk server behind a 1:1 NAT to send its
+   external IP as a host candidate rather than relying on STUN to discover it.
+
+------------------------------------------------------------------------------
 --- Functionality changes from Asterisk 13.6.0 to Asterisk 13.7.0 ------------
 ------------------------------------------------------------------------------
 
diff --git a/ChangeLog b/ChangeLog
index 496e64f..4fda324 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,75 +1,3319 @@
-2015-12-18 18:18 +0000  Asterisk Development Team <asteriskteam at digium.com>
+2016-04-20 10:46 +0000  Asterisk Development Team <asteriskteam at digium.com>
 
-	* asterisk 13.7.0-rc2 Released.
+	* asterisk 13.8.2 Released.
 
-2015-12-18 12:18 +0000 [a93a5387d4]  Kevin Harwell <kharwell at lunkwill>
+2016-04-20 05:45 +0000 [26d67ce885]  Joshua Colp <jcolp at digium.com>
 
-	* Release summaries: Add summaries for 13.7.0-rc2
+	* Release summaries: Remove previous versions
+
+2016-04-20 05:45 +0000 [d9909232ed]  Joshua Colp <jcolp at digium.com>
+
+	* .version: Update for 13.8.2
+
+2016-04-20 05:45 +0000 [fc57bb9b15]  Joshua Colp <jcolp at digium.com>
+
+	* .lastclean: Update for 13.8.2
+
+2016-04-20 05:45 +0000 [ac04474f38]  Joshua Colp <jcolp at digium.com>
+
+	* realtime: Add database scripts for 13.8.2
+
+2016-04-18 17:00 +0000 [91a3e1184f]  Mark Michelson <mmichelson at digium.com>
+
+	* res_pjsip_registrar: Fix bad memory-ness with user_agent.
+
+	  Recent changes to the PJSIP registrar resulted in tests failing due to
+	  missing AOR_CONTACT_ADDED test events. The reason for this was that the
+	  user_agent string had junk values in it, resulting in being unable to
+	  generate the event.
+
+	  I'm going to be honest here, I have no idea why this was happening. Here
+	  are the steps needed for the user_agent variable to get messed up:
+	  * REGISTER is received
+	  * First contact in the REGISTER results in a contact being removed
+	  * Second contact in the REGISTER results in a contact being added
+	  * The contact, AOR, expiration, and user agent all have to be passed as
+	    format parameters to the creation of a string. Any subset of those
+	    parameters would not be enough to cause the problem.
+
+	  Looking into what was happening, the thing that struck me as odd was
+	  that the user_agent variable was meant to be set to the value of the
+	  User-Agent SIP header in the incoming REGISTER. However, when removing a
+	  contact, the user_agent variable would be set (via ast_strdupa inside a
+	  loop) to the stored contact's user_agent. This means that the
+	  user_agent's value would be incorrect when attempting to process further
+	  contacts in the incoming REGISTER.
+
+	  The fix here is to use a different variable for the stored user agent
+	  when removing a contact. Correcting the behavior to be correct also
+	  means the memory usage is less weird, and the issue no longer occurs.
+
+	  ASTERISK-25929 #close
+	  Reported by Joshua Colp
+
+	  Change-Id: I7cd24c86a38dec69ebcc94150614bc25f46b8c08
+	  (cherry picked from commit f436b9ab111f1ff57c6dd3970051f123b42c1103)
+
+2016-04-18 13:41 +0000 [70e25ced60]  Joshua Colp <jcolp at digium.com>
+
+	* res_pjsip_transport_management: Allow unload to occur.
+
+	  At shutdown it is possible for modules to be unloaded that wouldn't
+	  normally be unloaded. This allows the environment to be cleaned up.
+
+	  The res_pjsip_transport_management module did not have the unload
+	  logic in it to clean itself up causing the res_pjsip module to not
+	  get unloaded. As a result the res_pjsip monitor thread kept going
+	  processing traffic and timers when it shouldn't.
+
+	  Change-Id: Ic8cadee131e3b2c436a81d3ae8bb5775999ae00a
+	  (cherry picked from commit 49bfdc9ac029e0ef17cd8e85d8d7b7731387a34e)
+
+2016-04-18 12:12 +0000 [856931edc2]  Mark Michelson <mmichelson at digium.com>
+
+	* PJSIP: Remove PJSIP parsing functions from uri length validation.
+
+	  The PJSIP parsing functions provide a nice concise way to check the
+	  length of a hostname in a SIP URI. The problem is that in order to use
+	  those parsing functions, it's required to use them from a thread that
+	  has registered with PJLib.
+
+	  On startup, when parsing AOR configuration, the permanent URI handler
+	  may not be run from a PJLib-registered thread. Specifically, this could
+	  happen when Asterisk was started in daemon mode rather than
+	  console-mode. If PJProject were compiled with assertions enabled, then
+	  this would cause Asterisk to crash on startup.
+
+	  The solution presented here is to do our own parsing of the contact URI
+	  in order to ensure that the hostname in the URI is not too long. The
+	  parsing does not attempt to perform a full SIP URI parse/validation,
+	  since the hostname in the URI is what is important.
+
+	  ASTERISK-25928 #close
+	  Reported by Joshua Colp
+
+	  Change-Id: Ic3d6c20ff3502507c17244a8b7e2ca761dc7fb60
+	  (cherry picked from commit efae187217750e322cd6167705a33f888b631427)
+
+2016-04-14 20:26 +0000  Asterisk Development Team <asteriskteam at digium.com>
+
+	* asterisk 13.8.1 Released.
+
+2016-04-14 15:26 +0000 [18e6f12e83]  Kevin Harwell <kharwell at digium.com>
+
+	* Release summaries: Remove previous versions
+
+2016-04-14 15:26 +0000 [625c07711a]  Kevin Harwell <kharwell at digium.com>
+
+	* .version: Update for 13.8.1
+
+2016-04-14 15:26 +0000 [584f1fb3c7]  Kevin Harwell <kharwell at digium.com>
+
+	* .lastclean: Update for 13.8.1
+
+2016-04-14 15:26 +0000 [1e37a63379]  Kevin Harwell <kharwell at digium.com>
+
+	* realtime: Add database scripts for 13.8.1
+
+2016-04-14 13:49 +0000 [dcf1b3c098]  Mark Michelson <mmichelson at digium.com>
+
+	* transport management: Register thread with PJProject.
+
+	  The scheduler thread that kills idle TCP connections was not registering
+	  with PJProject properly and causing assertions if PJProject was built in
+	  debug mode.
+
+	  This change registers the thread with PJProject the first time that the
+	  scheduler callback executes.
+
+	  AST-2016-005
+
+	  Change-Id: I5f7a37e2c80726a99afe9dc2a4a69bdedf661283
+
+2016-03-08 12:12 +0000 [efafbb1319]  Mark Michelson <mmichelson at digium.com>
+
+	* res_pjsip_transport_management: Kill idle TCP connections.
+
+	  "Idle" here means that someone connects to us and does not send a SIP
+	  request. PJProject will not automatically time out such connections, so
+	  it's up to Asterisk to do it instead.
+
+	  When we receive an incoming TCP connection, we will start a timer
+	  (equivalent to transaction timer D) waiting to receive an incoming
+	  request. If we do not receive a request in that timeframe, then we will
+	  shut down the TCP connection.
+
+	  ASTERISK-25796 #close
+	  Reported by George Joseph
+
+	  AST-2016-005
+
+	  Change-Id: I7b0d303e5d140d0ccaf2f7af562071e3d1130ac6
+
+2016-03-08 10:52 +0000 [159f639770]  Mark Michelson <mmichelson at digium.com>
+
+	* Rename res_pjsip_keepalive res_pjsip_transport_management
+
+	  ASTERISK-25796
+	  Reported by George Joseph
+
+	  AST-2016-005
+
+	  Change-Id: Id322a05f927392293570599730050bc677d99433
+
+2016-04-14 07:15 +0000 [c164ff004d]  Mark Michelson <mmichelson at digium.com>
+
+	* AST-2016-004: Fix crash on REGISTER with long URI.
+
+	  Due to some ignored return values, Asterisk could crash if processing an
+	  incoming REGISTER whose contact URI was above a certain length.
+
+	  ASTERISK-25707 #close
+	  Reported by George Joseph
+
+	  Patches:
+	  	0001-res_pjsip-Validate-that-URIs-don-t-exceed-pjproject-.patch
+
+	  AST-2016-004
+
+	  Change-Id: I0ed3898fe7ab10121b76c8c79046692de3a1be55
+
+2016-03-29 19:39 +0000  Asterisk Development Team <asteriskteam at digium.com>
+
+	* asterisk 13.8.0 Released.
+
+2016-03-29 14:39 +0000 [0f885f0076]  Mark Michelson <mmichelson at digium.com>
+
+	* Release summaries: Add summaries for 13.8.0
+
+2016-03-29 14:34 +0000 [a1fa37aebd]  Mark Michelson <mmichelson at digium.com>
+
+	* Release summaries: Remove previous versions
+
+2016-03-29 14:34 +0000 [e7de5fd439]  Mark Michelson <mmichelson at digium.com>
+
+	* .version: Update for 13.8.0
+
+2016-03-29 14:34 +0000 [8baf813848]  Mark Michelson <mmichelson at digium.com>
+
+	* .lastclean: Update for 13.8.0
+
+2016-03-29 14:34 +0000 [42469df205]  Mark Michelson <mmichelson at digium.com>
+
+	* realtime: Add database scripts for 13.8.0
+
+2016-03-22 18:31 +0000  Asterisk Development Team <asteriskteam at digium.com>
+
+	* asterisk 13.8.0-rc1 Released.
+
+2016-03-22 13:26 +0000 [a698424678]  Mark Michelson <mmichelson at lunkwill>
+
+	* Release summaries: Add summaries for 13.8.0-rc1
+
+2016-03-22 13:21 +0000 [e395a0b973]  Mark Michelson <mmichelson at lunkwill>
+
+	* .version: Update for 13.8.0-rc1
+
+2016-03-22 13:21 +0000 [38a86b2dbf]  Mark Michelson <mmichelson at lunkwill>
+
+	* .lastclean: Update for 13.8.0-rc1
+
+2016-03-22 13:21 +0000 [e0c8c8bf4a]  Mark Michelson <mmichelson at lunkwill>
+
+	* realtime: Add database scripts for 13.8.0-rc1
+
+2016-03-18 14:31 +0000 [6a40520fe9]  Kevin Harwell <kharwell at digium.com>
+
+	* chan_pjsip: ref leak when checking direct_media_glare
+
+	  Fix the reference leak introduced in the following commit:
+
+	  9444ddadf8525d1ce66a1faf1db97f9f6c265ca4
+
+	  ASTERISK-25849
+
+	  Change-Id: I5cfefd5ee6c1c3a1715c050330aaa10e4d2a5e85
+2016-03-16 12:37 +0000 [9444ddadf8]  Kevin Harwell <kharwell at digium.com>
+
+	* chan_pjsip: transfers with direct media reinvite has wrong address/port
+
+	  During a transfer involving direct media a race occurs between when the
+	  transferer channel is swapped out, initiating rtp changes/updates, and the
+	  subsequent reinvites.
+
+	  When Alice, after speaking with Charlie (Bob is on hold), connects Bob and
+	  Charlie invites are sent to each in order to establish the call between them.
+	  Bob is taken off hold and Charlie is told to have his media flow through
+	  Asterisk. However, if before those invites go out the bridge updates Bob's
+	  and/or Charlie's rtp information with direct media data (i.e. address, port)
+	  then the invite(s) will contain the remote data in the SDP instead of the
+	  Asterisk data.
+
+	  The race occurs in the native bridge glue code when updating the peer. The
+	  direct_media_address can get set twice before sending out the first invite
+	  during call connection. This can happen because the checking/setting of the
+	  direct_media_address happened in one thread while the sending of the invite(s)
+	  happened in another thread.
+
+	  This fix removes the race condition by moving the checking/setting of the
+	  direct_media_address to be in the same thread as the sending of the invites(s).
+	  This serializes the checking/setting and sending so they can no longer happen
+	  out of order.
+
+	  ASTERISK-25849 #close
+
+	  Change-Id: Idfea590175e74f401929a601dba0c91ca1a7f873
+
+2015-10-19 07:11 +0000 [88240f98d9]  Rodrigo Ramírez Norambuena <a at rodrigoramirez.com>
+
+	* install_prereq: Update repositories before install on Debian systems
+
+	  When to install packages the indexed local is more old of the
+	  version of software on the repository they have been upgraded by security
+	  update then get the package will give 404 not found.
+
+	  The patch prevent by update local index to repository for aptitude before
+	  install.
+
+	  ASTERISK-25495 #close
+
+	  Reporte by: Rodrigo Ramírez Norambuena
+
+	  Change-Id: I645959e553aac542805ced394cac2dca964051fa
+	  (cherry picked from commit 88f3dbaec9509bfba8bc1de7799aa0dc65304bb5)
+
+2015-06-03 20:12 +0000 [efcf9a96db]  Rodrigo Ramírez Norambuena <decipher.hk at gmail.com>
+
+	* install_prereq: Check if is installed aptitude otherwise to install.
+
+	  If in Debian or system based, dont have aptitude installed the script do
+	  nothing. This patch checked if aptitude  installed, if not installed.
+
+	  Also, if execute script with all packages installed yet, the script not show
+	  nothing and return exit 1 because the command 'grep' get nothing from pipe from
+	  'awk'.
+
+	  ASTERISK-25113 #close
+	  Reported By: Rodrigo Ramírez Norambuena <decipher.hk at gmail.com>
+
+	  Change-Id: Iebdff55805d3917166e5e08e0a1e2176f36ff27f
+	  (cherry picked from commit 6737ded0581a9e1256bdfe30c1d747e7ca93f8b3)
+
+2016-03-03 04:43 +0000 [2b1b8e382a]  Sergio Medina Toledo <lumasepa at gmail.com>
+
+	* res_pjsip_refer.c: Fix seg fault in process of Refer-to header.
+
+	  The "Refer-to" header of an incoming REFER request is parsed by
+	  pjsip_parse_uri().  That function requires the URI parameter to be NULL
+	  terminated.  Unfortunately, the previous code added the NULL terminator by
+	  overwriting memory that may not be safe.  The overwritten memory results
+	  could be benign, memory corruption, or a segmentation fault.  Now the URI
+	  is NULL terminated safely by copying the URI to a new chunk of memory with
+	  the correct size to be NULL terminated.
+
+	  ASTERISK-25814 #close
+
+	  Change-Id: I32565496684a5a49c3278fce06474b8c94b37342
+
+2016-03-11 12:22 +0000 [de04308ae4]  Richard Mudgett <rmudgett at digium.com>
+
+	* chan_sip.c: Fix mwi resub deadlock potential.
+
+	  This patch is part of a series to resolve deadlocks in chan_sip.c.
+
+	  Stopping a scheduled event can result in a deadlock if the scheduled event
+	  is running when you try to stop the event.  If you hold a lock needed by
+	  the scheduled event while trying to stop the scheduled event then a
+	  deadlock can happen.  The general strategy for resolving the deadlock
+	  potential is to push the actual starting and stopping of the scheduled
+	  events off onto the scheduler/do_monitor() thread by scheduling an
+	  immediate one shot scheduled event.  Some restructuring may be needed
+	  because the code may assume that the start/stop of the scheduled events is
+	  immediate.
+
+	  ASTERISK-25023 #close
+
+	  Change-Id: I96d429c57a48861fd8bde63dd93db4e92dc3adb6
+
+2016-03-10 17:01 +0000 [5f6627a8a4]  Richard Mudgett <rmudgett at digium.com>
+
+	* chan_sip.c: Fix registration timeout and expire deadlock potential.
+
+	  This patch is part of a series to resolve deadlocks in chan_sip.c.
+
+	  Stopping a scheduled event can result in a deadlock if the scheduled event
+	  is running when you try to stop the event.  If you hold a lock needed by
+	  the scheduled event while trying to stop the scheduled event then a
+	  deadlock can happen.  The general strategy for resolving the deadlock
+	  potential is to push the actual starting and stopping of the scheduled
+	  events off onto the scheduler/do_monitor() thread by scheduling an
+	  immediate one shot scheduled event.  Some restructuring may be needed
+	  because the code may assume that the start/stop of the scheduled events is
+	  immediate.
+
+	  ASTERISK-25023
+
+	  Change-Id: I2e40de89efc8ae6e8850771d089ca44bc604b508
+
+2016-03-10 12:17 +0000 [32bd7a64f9]  Richard Mudgett <rmudgett at digium.com>
+
+	* chan_sip.c: Fix t38id deadlock potential.
+
+	  This patch is part of a series to resolve deadlocks in chan_sip.c.
+
+	  Stopping a scheduled event can result in a deadlock if the scheduled event
+	  is running when you try to stop the event.  If you hold a lock needed by
+	  the scheduled event while trying to stop the scheduled event then a
+	  deadlock can happen.  The general strategy for resolving the deadlock
+	  potential is to push the actual starting and stopping of the scheduled
+	  events off onto the scheduler/do_monitor() thread by scheduling an
+	  immediate one shot scheduled event.  Some restructuring may be needed
+	  because the code may assume that the start/stop of the scheduled events is
+	  immediate.
+
+	  ASTERISK-25023
+
+	  Change-Id: If595e4456cd059d7171880c7f354e844c21b5f5f
+
+2016-03-09 16:34 +0000 [43556b800b]  Richard Mudgett <rmudgett at digium.com>
+
+	* chan_sip.c: Fix reinviteid deadlock potential.
+
+	  This patch is part of a series to resolve deadlocks in chan_sip.c.
+
+	  Stopping a scheduled event can result in a deadlock if the scheduled event
+	  is running when you try to stop the event.  If you hold a lock needed by
+	  the scheduled event while trying to stop the scheduled event then a
+	  deadlock can happen.  The general strategy for resolving the deadlock
+	  potential is to push the actual starting and stopping of the scheduled
+	  events off onto the scheduler/do_monitor() thread by scheduling an
+	  immediate one shot scheduled event.  Some restructuring may be needed
+	  because the code may assume that the start/stop of the scheduled events is
+	  immediate.
+
+	  ASTERISK-25023
+
+	  Change-Id: I9c11b9d597468f63916c99e1dabff9f4a46f84c1
+
+2016-03-09 16:32 +0000 [38c1cdab2c]  Richard Mudgett <rmudgett at digium.com>
+
+	* chan_sip.c: Fix packet retransid deadlock potential.
+
+	  This patch is part of a series to resolve deadlocks in chan_sip.c.
+
+	  Stopping a scheduled event can result in a deadlock if the scheduled event
+	  is running when you try to stop the event.  If you hold a lock needed by
+	  the scheduled event while trying to stop the scheduled event then a
+	  deadlock can happen.  The general strategy for resolving the deadlock
+	  potential is to push the actual starting and stopping of the scheduled
+	  events off onto the scheduler/do_monitor() thread by scheduling an
+	  immediate one shot scheduled event.  Some restructuring may be needed
+	  because the code may assume that the start/stop of the scheduled events is
+	  immediate.
+
+	  * Fix retrans_pkt() to call check_pendings() with both the owner channel
+	  and the private objects locked as required.
+
+	  * Refactor dialog retransmission packet list to safely remove packet
+	  nodes.  The list nodes are now ao2 objects.  The list has a ref and the
+	  scheduled entry has a ref.
+
+	  ASTERISK-25023
+
+	  Change-Id: I50926d81be53f4cd3d572a3292cd25f563f59641
+
+2016-03-09 16:26 +0000 [e4ad55c888]  Richard Mudgett <rmudgett at digium.com>
+
+	* chan_sip.c: Fix waitid deadlock potential.
+
+	  This patch is part of a series to resolve deadlocks in chan_sip.c.
+
+	  Stopping a scheduled event can result in a deadlock if the scheduled event
+	  is running when you try to stop the event.  If you hold a lock needed by
+	  the scheduled event while trying to stop the scheduled event then a
+	  deadlock can happen.  The general strategy for resolving the deadlock
+	  potential is to push the actual starting and stopping of the scheduled
+	  events off onto the scheduler/do_monitor() thread by scheduling an
+	  immediate one shot scheduled event.  Some restructuring may be needed
+	  because the code may assume that the start/stop of the scheduled events is
+	  immediate.
+
+	  * Made always run check_pendings() under the scheduler thread so scheduler
+	  ids can be checked safely.
+
+	  ASTERISK-25023
+
+	  Change-Id: Ia834d6edd5bdb47c163e4ecf884428a4a8b17d52
+
+2016-03-08 15:08 +0000 [98d5669c28]  Richard Mudgett <rmudgett at digium.com>
+
+	* chan_sip.c: Fix session timers deadlock potential.
+
+	  This patch is part of a series to resolve deadlocks in chan_sip.c.
+
+	  Stopping a scheduled event can result in a deadlock if the scheduled event
+	  is running when you try to stop the event.  If you hold a lock needed by
+	  the scheduled event while trying to stop the scheduled event then a
+	  deadlock can happen.  The general strategy for resolving the deadlock
+	  potential is to push the actual starting and stopping of the scheduled
+	  events off onto the scheduler/do_monitor() thread by scheduling an
+	  immediate one shot scheduled event.  Some restructuring may be needed
+	  because the code may assume that the start/stop of the scheduled events is
+	  immediate.
+
+	  ASTERISK-25023
+
+	  Change-Id: I6d65269151ba95e0d8fe4e9e611881cde2ab4900
+
+2016-03-07 13:21 +0000 [9cb8f73226]  Richard Mudgett <rmudgett at digium.com>
+
+	* chan_sip.c: Fix autokillid deadlock potential.
+
+	  This patch is part of a series to resolve deadlocks in chan_sip.c.
+
+	  Stopping a scheduled event can result in a deadlock if the scheduled event
+	  is running when you try to stop the event.  If you hold a lock needed by
+	  the scheduled event while trying to stop the scheduled event then a
+	  deadlock can happen.  The general strategy for resolving the deadlock
+	  potential is to push the actual starting and stopping of the scheduled
+	  events off onto the scheduler/do_monitor() thread by scheduling an
+	  immediate one shot scheduled event.  Some restructuring may be needed
+	  because the code may assume that the start/stop of the scheduled events is
+	  immediate.
+
+	  * Fix clearing autokillid in __sip_autodestruct() even though we could
+	  reschedule.
+
+	  ASTERISK-25023
+
+	  Change-Id: I450580dbf26e2e3952ee6628c735b001565c368f
+
+2016-03-07 18:28 +0000 [c5c7f48a15]  Richard Mudgett <rmudgett at digium.com>
+
+	* chan_sip.c: Fix provisional_keepalive_sched_id deadlock.
+
+	  This patch is part of a series to resolve deadlocks in chan_sip.c.
+
+	  Stopping a scheduled event can result in a deadlock if the scheduled event
+	  is running when you try to stop the event.  If you hold a lock needed by
+	  the scheduled event while trying to stop the scheduled event then a
+	  deadlock can happen.  The general strategy for resolving the deadlock
+	  potential is to push the actual starting and stopping of the scheduled
+	  events off onto the scheduler/do_monitor() thread by scheduling an
+	  immediate one shot scheduled event.  Some restructuring may be needed
+	  because the code may assume that the start/stop of the scheduled events is
+	  immediate.
+
+	  ASTERISK-25023
+
+	  Change-Id: I98a694fd42bc81436c83aa92de03226e6e4e3f48
+
+2016-03-09 11:22 +0000 [f959d84dfd]  Richard Mudgett <rmudgett at digium.com>
+
+	* chan_sip.c: Adjust how dialog_unlink_all() stops scheduled events.
+
+	  This patch is part of a series to resolve deadlocks in chan_sip.c.
+
+	  * Make dialog_unlink_all() unschedule all items at once in the sched
+	  thread.
+
+	  ASTERISK-25023
+
+	  Change-Id: I7743072fb228836e8228b72f6dc46c8cc50b3fb4
+
+2016-03-10 21:54 +0000 [5f3225ddcc]  Richard Mudgett <rmudgett at digium.com>
+
+	* chan_sip.c: Clear scheduled immediate events on unload.
+
+	  This patch is part of a series to resolve deadlocks in chan_sip.c.
+
+	  The reordering of chan_sip's shutdown is to handle any immediate events
+	  that get put onto the scheduler so resources aren't leaked.  The typical
+	  immediate events at this time are going to be concerned with stopping
+	  other scheduled events.
+
+	  ASTERISK-25023
+
+	  Change-Id: I3f6540717634f6f2e84d8531a054976f2bbb9d20
+
+2016-03-15 14:51 +0000 [7a74971771]  Richard Mudgett <rmudgett at digium.com>
+
+	* sip/dialplan_functions.c: Fix /channels/chan_sip/test_sip_rtpqos crash.
+
+	  This patch is part of a series to resolve deadlocks in chan_sip.c.
+
+	  Delaying destruction of the chan_sip sip_pvt structures caused the
+	  /channels/chan_sip/test_sip_rtpqos unit test to crash.  That test
+	  registers a special test ast_rtp_engine with the rtp engine module.  When
+	  the unit test completes it cleans up by unregistering the test
+	  ast_rtp_engine and exits.  Since the delayed destruction of the sip_pvt
+	  happens after the unit test returns, the destructor tries to call the rtp
+	  engine destroy callback of the test ast_rtp_engine auto variable which no
+	  longer exists on the stack.
+
+	  * Change the test ast_rtp_engine auto variable to a static variable.  Now
+	  the variable can still exist after the unit test exits so the delayed
+	  sip_pvt destruction can complete successfully.
+
+	  ASTERISK-25023
+
+	  Change-Id: I61e34a12d425189ef7e96fc69ae14993f82f3f13
+
+2016-03-15 13:31 +0000 [d2c09ed73b]  Andrew Nagy <andrew.nagy at the159.com>
+
+	* app_stasis: Don't hang up if app is not registered
+
+	  This prevents pbx_core from hanging up the channel if the app isn't
+	  registered.
+
+	  ASTERISK-25846 #close
+
+	  Change-Id: I63216a61f30706d5362bc0906b50b6f0544aebce
+2016-03-07 15:50 +0000 [b2d2906445]  Richard Mudgett <rmudgett at digium.com>
+
+	* sched.c: Ensure oldest expiring entry runs first.
+
+	  This patch is part of a series to resolve deadlocks in chan_sip.c.
+
+	  * Updated sched unit test to check new behavior.
+
+	  ASTERISK-25023
+
+	  Change-Id: Ib69437327b3cda5e14c4238d9ff91b2531b34ef3
+
+2016-03-04 18:25 +0000 [9ae21b510f]  Richard Mudgett <rmudgett at digium.com>
+
+	* chan_sip.c: Made sip_reinvite_retry() call sip_pvt_lock_full().
+
+	  Change-Id: I90f04208a089f95488a2460185a8dbc3f6acca12
+
+2016-03-07 18:56 +0000 [56bcb97a3c]  Richard Mudgett <rmudgett at digium.com>
+
+	* chan_sip.c: Simplify sip_pvt destructor call levels.
+
+	  Remove destructor calling destroy_it calling really_destroy_it
+	  for no benefit.  Just make the destructor the really_destroy_it
+	  function.
+
+	  Change-Id: Idea0d47b27dd74f2488db75bcc7f353d8fdc614a
+
+2016-03-14 08:59 +0000 [677a65fcbb]  Joshua Colp <jcolp at digium.com>
+
+	* build: Add configure check for proto field of PJSIP TLS transport setting.
+
+	  Older versions of PJSIP do not have the proto field on the TLS transport
+	  setting structure. This change adds a configure check so even if it is
+	  not present we will still be able to build.
+
+	  Change-Id: Ibf3f47befb91ed1b8194bf63888baa6fee05aba9
+
+2016-03-12 16:02 +0000 [32f0a3d52a]  gtjoseph <george.joseph at fairview5.com>
+
+	* build_system:  Split COMPILE_DOUBLE from DONT_OPTIMIZE
+
+	  I can't ever recall actually needing the intermediate files or the checking
+	  that a double compile produces.  What I CAN remember is every DONT_OPTIMIZE
+	  build needing 3 invocations of gcc instead of 1 just to do the checks and
+	  produce those intermediate files.
+
+	  Having said that, Richard pointed out that the reason for the double compile
+	  was that there were cases in the past where a submitted patch failed to compile
+	  because the submitter never tried it with the optimizations turned on.
+
+	  To get the best of both worlds, COMPILE_DOUBLE has been split into its own
+	  option.  If DONT_OPTIMIZE is turned on, COMPILE_DOUBLE will also be selected
+	  BUT you can then turn it off if all you need are the debugging symbols.  This
+	  way you have to make an informed decision about disabling COMPILE_DOUBLE.
+
+	  To allow COMPILE_DOUBLE to be both auto-selected and turned off, a new feature
+	  was added to menuselect.  The <use> element can now contain an "autoselect"
+	  attribute which will turn the used member on but not create a hard dependency.
+	  The cflags.xml implementation for COMPILE_DOUBLE looks like this...
+
+	  <member name="DONT_OPTIMIZE" displayname="Disable Optimizations ...">
+	  	<use autoselect="yes">COMPILE_DOUBLE</use>
+	  	<support_level>core</support_level>
+	  </member>
+	  <member name="COMPILE_DOUBLE" displayname="Pre-compile with ...>
+	  	<depend>DONT_OPTIMIZE</depend>
+	  	<support_level>core</support_level>
+	  </member>
+
+	  When DONT_OPTIMIZE is turned on, COMPILE_DOUBLE is turned on because
+	  of the use.
+	  When DONT_OPTIMIZE is turned off, COMPILE_DOUBLE is turned off because
+	  of the depend.
+	  When COMPILE_DOUBLE is turned on, DONT_OPTIMIZE is turned on because
+	  of the depend.
+	  When COMPILE_DOUBLE is turned off, DONT_OPTIMIZE is left as is because
+	  it only uses COMPILE_DOUBLE, it doesn't depend on it.
+
+	  I also made a few tweaks to the ncurses implementation to move things
+	  left a bit to allow longer descriptions.
+
+	  Change-Id: Id49ca930ac4b5ec4fc2d8141979ad888da7b1611
+
+2016-03-10 13:09 +0000 [38499e7125]  gtjoseph <george.joseph at fairview5.com>
+
+	* pjproject:  Pass (dont_)optimize flags to pjproject and fix pjsua
+
+	  The pjproject Makefile now uses the Asterisk optimization flags which
+	  are determined by the setting of the DONT_OPTMIZE menuselect flag.
+	  The Makefile was also restructured so a change to the top level
+	  menuselect.makeopts will result in a rebuild of pjproject.
+
+	  Also, "--disable-resample" was removed from the pjproject configure
+	  options.  Without resample, pjsua (which is used by the testsuite)
+	  can't make audio calls.  When it can't, it segfaults.
+
+	  Change-Id: I24b0a4d0872acef00ed89b3c527a713ee4c2ccd4
+
+2016-03-11 16:03 +0000 [336cae73cc]  Walter Doekes <walter+asterisk at wjd.nu>
+
+	* app_chanspy: Fix occasional deadlock with ChanSpy and Local channels.
+
+	  Channel masquerading had a conflict with autochannel locking.
+
+	  When locking autochannel->channel, the channel is fetched from the
+	  autochannel and then locked. During the fetch, the autochannel -- which
+	  has no locks itself -- can be modified by someone who owns the channel
+	  lock. That means that the value of autochan->channel cannot be trusted
+	  until you hold the lock.
+
+	  In practice, this caused problems with Local channels getting
+	  masqueraded away while the ChanSpy attempted to get info from that
+	  channel. The old channel which was about to get removed got locked, but
+	  the new (replaced) channel got unlocked (no-op). Because the replaced
+	  channel was now locked (and would never get unlocked), it couldn't get
+	  removed from the channel list in a timely manner, and would now cause
+	  deadlocks when iterating over the channel list.
+
+	  This change checks the autochannel after locking the channel for changes
+	  to the autochannel. If the channel had been changed, the lock is
+	  reobtained on the new channel.
+
+	  In theory it seems possible that after this fix, the lock attempt on the
+	  old (wrong) channel can be on an already destroyed lock, maybe causing
+	  a crash. But that hasn't been observed in the wild and is harder induce
+	  than the current deadlock.
+
+	  Thanks go to Filip Frank for suggesting a fix similar to this and
+	  especially to IRC user hexanol for pointing out why this deadlock was
+	  possible and testing this fix. And to Richard for catching my rookie
+	  while loop mistake ;)
+
+	  ASTERISK-25321 #close
+
+	  Change-Id: I293ae0014e531cd0e675c3f02d1d118a98683def
+
+2016-03-07 21:34 +0000 [875d5e9872]  gtjoseph <george.joseph at fairview5.com>
+
+	* pjproject_bundled: Remove --with-external-pa from configure options.
+
+	  Not sure why it was there in the first place as we already specify
+	  --disable-sound.
+
+	  Change-Id: Ia80a40e8b1e1acc287955ab11ba1fbd0c7d4cff9
+
+2016-03-06 14:38 +0000 [530cff5f5f]  gtjoseph <george.joseph at fairview5.com>
+
+	* res_pjsip:  Strip spaces from items parsed from comma-separated lists
+
+	  Configurations like "aors = a, b, c" were either ignoring everything after "a"
+	  or trying to look up " b".  Same for mailboxes,  ciphers, contacts and a few
+	  others.
+
+	  To fix, all the strsep(&copy, ",") calls have been wrapped in ast_strip.  To
+	  facilitate this, ast_strip, ast_skip_blanks and ast_skip_nonblanks were
+	  updated to handle null pointers.
+
+	  In some cases, an ast_strlen_zero() test was added to skip consecutive commas.
+
+	  There was also an attempt to ast_free an ast_strdupa'd string in
+	  ast_sip_for_each_aor which was causing a SEGV.  I removed it.
+
+	  Although this issue was reported for realtime, the issue was in the res_pjsip
+	  modules so all config mechanisms were affected.
+
+	  ASTERISK-25829 #close
+	  Reported-by: Mateusz Kowalski
+
+	  Change-Id: I0b22a2cf22a7c1c50d4ecacbfa540155bec0e7a2
+
+2016-03-04 20:37 +0000 [3c8076a83b]  gtjoseph <george.joseph at fairview5.com>
+
+	* install_prereq: Add packages for bundled pjproject
+
+	  RedHat/CentOS needs python-devel
+	  Debian/Ubuntu needs automake, libsrtp-dev and python-dev
+
+	  Ubuntu also needed libncurses5-dev for cmenuselect so while not
+	  needed for pjproject, I adedd it anyway.
+
+	  Change-Id: Idf5fa16e2d87c687439621507e122cb9461d7089
+
+2016-02-24 17:25 +0000 [27f32cd0a6]  gtjoseph <george.joseph at fairview5.com>
+
+	* res_pjsip_caller_id: Anonymize 'From' when caller id presentation is prohibited
+
+	  Per RFC3325, the 'From' header is now anonymized on outgoing calls when
+	  caller id presentation is prohibited.
+
+	  TID = trust_id_outbound
+	  PRO = Set(CALLERID(pres)=prohib)
+	  USR = endpoint/from_user
+	  DOM = endpoint/from_domain
+	  PAI = YES(privacy=off), NO(not sent), PRI(privacy=full) (assumes send_pai=yes)
+
+	  Conditions          |Result
+	  --------------------|----------------------------------------------------
+	  TID PRO USR DOM     |PAI    FROM
+	  --------------------|----------------------------------------------------
+	  Y   Y   abc def.ghi |PRI    "Anonymous" <sip:abc at def.ghi>
+	  Y   Y   abc         |PRI    "Anonymous" <sip:abc at anonymous.invalid>
+	  Y   Y       def.ghi |PRI    "Anonymous" <sip:anonymous at def.ghi>
+	  Y   Y               |PRI    "Anonymous" <sip:anonymous at anonymous.invalid>
+
+	  Y   N   abc def.ghi |YES    <sip:abc at def.ghi>
+	  Y   N   abc         |YES    <sip:abc@<ip_address>>
+	  Y   N       def.ghi |YES    "Caller Name" <sip:<caller_exten>@def.ghi>
+	  Y   N               |YES    "Caller Name" <sip:<caller_exten>@<ip_address>>
+
+	  N   Y   abc def.ghi |NO     "Anonymous" <sip:abc at def.ghi>
+	  N   Y   abc         |NO     "Anonymous" <sip:abc at anonymous.invalid>
+	  N   Y       def.ghi |NO     "Anonymous" <sip:anonymous at def.ghi>
+	  N   Y               |NO     "Anonymous" <sip:anonymous at anonymous.invalid>
+
+	  N   N   abc def.ghi |YES    <sip:abc at def.ghi>
+	  N   N   abc         |YES    <sip:abc@<ip_address>>
+	  N   N       def.ghi |YES    "Caller Name" <sip:<caller_exten>@def.ghi>
+	  N   N               |YES    "Caller Name" <sip:<caller_exten>@<ip_address>>
+
+	  ASTERISK-25791 #close
+	  Reported-by: Anthony Messina
+
+	  Change-Id: I2c82a5ca1413c2c00fb62ea95b0ae8e97af54dc9
+
+2016-03-03 17:34 +0000 [7cf7b0a4f9]  gtjoseph <george.joseph at fairview5.com>
+
+	* third_party/Makefile.rules:  Replace unsupported != operator with $(shell ...)
+
+	  Apparently the != operator is fairly new so I've replaced it with
+	  the old $(shell ...) syntax.
+
+	  Change-Id: I16b2e1878a4f91e7e9740abd427f9639f933c479
+	  Reported-by: Richard Mudgett
+2016-01-23 15:50 +0000 [53f57001f2]  gtjoseph <george.joseph at fairview5.com>
+
+	* loader: Retry dlopen when loading fails
+
+	  Although we use the RTLD_LAZY flag when calling dlopen
+	  the first time on a module, this only defers resolution
+	  for function calls.  Pointer references to functions are
+	  determined at link time so dlopen expects them to be there.
+	  Since we don't cross-module link, pointers to functions
+	  in other modules won't be available and dlopen will fail.
+
+	  Doing a "hardened" build also causes problems because it
+	  typically sets "-z now" on the ld command line which
+	  overrides RTLD_LAZY at run time.
+
+	  If the failing module isn't a GLOBAL_SYMBOLS module, then
+	  dlopen will be called again after all the GLOBAL_SYMBOLS
+	  modules have been loaded and they'll eventually resolve.
+
+	  If the calling module IS a GLOBAL_SYMBOLS module itself
+	  and a third module depends on it, then there's an issue
+	  because the second time through the dlopen loop,
+	  GLOBAL_SYMBOLS modules aren't given any special treatment
+	  and since the order in which dlopen is called isn't
+	  deterministic, the dependent may again be tried before the
+	  module it needs is loaded.
+
+	  Simple solution:  Save modules that fail load_resource
+	  because of a dlopen error in a list and retry them
+	  immediately after the first pass. Keep retrying until
+	  the failed list is empty or we reach a #defined max
+	  retries. Error messages are suppressed until the final
+	  pass which also gets rid of those confusing error messages
+	  about module failures that are later corrected.
+
+	  Change-Id: Iddae1d97cd2f00b94e61662447432765755f64bb
+
+2016-03-01 16:18 +0000 [40d9e9e238]  Kevin Harwell <kharwell at digium.com>
+
+	* bridge.c: Crash during attended transfer when missing a local channel half
+
+	  It's possible for the transferer channel to get hung up early during the
+	  attended transfer process. For instance, a phone may send a "bye" immediately
+	  upon receiving a sip notify that contains a sip frag 100 (I'm looking at you
+	  Jitsi). When this occurs a race begins between the transferer being hung up
+	  and completion of the transfer code.
+
+	  If the channel hangs up too early during a transfer involving stasis bridging
+	  for instance, then when the created local channel goes to look up its swap
+	  channel (and associated datastore) it can't find it (since it is no longer in
+	  the bridge) thus it fails to enter the stasis application. Consequently, the
+	  created local channel(s) hang up as well. If the timing is just right then the
+	  bridging code attempts to add the message link with missing local channel(s).
+	  Hence the crash.
+
+	  Unfortunately, there is no great way to solve the problem of the unexpected
+	  "bye". While we can't guarantee we won't receive an early hangup, and in this
+	  case still fail to enter the stasis application, we can make it so asterisk
+	  does not crash.
+
+	  This patch does just that by locking the local channel structure, checking
+	  that the local channel's peer has not been lost, and then continuing. This
+	  keeps the local channel's peer from being ripped out from underneath it by
+	  the local/unreal hangup code while attempting to set the stasis message link.
+
+	  ASTERISK-25771
+
+	  Change-Id: Ie6d6061e34c7c95f07116fffac9a09e5d225c880
+
+2016-03-01 18:08 +0000 [ff3da61c35]  Kevin Harwell <kharwell at digium.com>
+
+	* res_pjsip_refer.c: Delay sending the initial SIP Notify with frag 100
+
+	  During the transfer process, some phones (okay it was the Jitsi softphone,
+	  but maybe others are out there) send a "bye" immediately after receiving a
+	  SIP Notify. When a "bye" is received early for some types of transfers the
+	  transferer channel may no longer be available during late stage transfer
+	  processing.
+
+	  For instance, during an attended transfer involving stasis bridging at one
+	  point the created local channel looks for an associated swap channel in
+	  order to retrieve the stasis application name. If the transferer has hung
+	  up then the local channel will fail to find it. The local channel then has
+	  no way to know which stasis app to enter, so it fails and hangs up as well.
+	  Thus the transfer does not complete as expected.
+
+	  This patch delays the sending of the initial notify in order to give the
+	  transfer process enough time to gather the necessary data for a successful
+	  transfer.
+
+	  ASTERISK-25771
+
+	  Change-Id: I09cfc9a5d6ed4c007bc70625e0972b470393bf16
+
+2016-03-03 08:26 +0000 [26b8f2692e]  Joshua Colp <jcolp at digium.com>
+
+	* res_pjsip_dtmf_info: NULL terminate the message body.
+
+	  PJSIP does not ensure that when printing the message body the
+	  buffer will be NULL terminated. This is problematic when searching
+	  for the signal and duration values of the DTMF.
+
+	  This change ensures the buffer is always NULL terminated.
+
+	  Change-Id: I52653a1a60c93092d06af31a27408d569cc98968
+
+2016-03-01 20:03 +0000 [86d6e44cc1]  gtjoseph <george.joseph at fairview5.com>
+
+	* alembic: Fix downgrade and tweak for sqlite
+
+	  Downgrade had a few issues.  First there was an errant 'update' statement in
+	  add_auto_dtmf_mode that looks like it was a copy/paste error.  Second, we
+	  weren't cleaning up the ENUMs so subsequent upgrades on postgres failed
+	  because the types already existed.
+
+	  For sqlite...  sqlite doesn't support ALTER or DROP COLUMN directly.
+	  Fortunately alembic batch_operations takes care of this for us if we
+	  use it so the alter and drops were converted to use batch operations.
+
+	  Here's an example downgrade:
+
+	      with op.batch_alter_table('ps_endpoints') as batch_op:
+	          batch_op.drop_column('tos_audio')
+	          batch_op.drop_column('tos_video')
+	          batch_op.add_column(sa.Column('tos_audio', yesno_values))
+	          batch_op.add_column(sa.Column('tos_video', yesno_values))
+	          batch_op.drop_column('cos_audio')
+	          batch_op.drop_column('cos_video')
+	          batch_op.add_column(sa.Column('cos_audio', yesno_values))
+	          batch_op.add_column(sa.Column('cos_video', yesno_values))
+
+	      with op.batch_alter_table('ps_transports') as batch_op:
+	          batch_op.drop_column('tos')
+	          batch_op.add_column(sa.Column('tos', yesno_values))
+	      # Can't cast integers to YESNO_VALUES, so dropping and adding is required
+	          batch_op.drop_column('cos')
+	          batch_op.add_column(sa.Column('cos', yesno_values))
+
+	  Upgrades from base to head and downgrades from head to base were tested
+	  repeatedly for postgresql, mysql/mariadb, and sqlite3.
+
+	  Change-Id: I862b0739eb3fd45ec3412dcc13c2340e1b7baef8
+
+2016-03-02 15:55 +0000 [6f0d7ce9db]  gtjoseph <george.joseph at fairview5.com>
+
+	* config_transport:  Fix objects returned by ast_sip_get_transport_states
+
+	  ast_sip_get_transport_states was returning a container of internal_state
+	  objects instead of ast_sip_transport_state objects.  This was causing
+	  transport lookups to fail, most noticably in res_pjsip_nat, which
+	  couldn't find the correct external addresses.  This was causing contacts
+	  to go out with internal ip addresses.
+
+	  ASTERISK-25830 #close
+	  Reported-by: Sean Bright
+
+	  Change-Id: I1aee6a2fd46c42e8dd0af72498d17de459ac750e
+
+2016-03-02 11:17 +0000 [1ea7a5a774]  Scott Griepentrog <scott at griepentrog.com>
+
+	* CHAOS: cleanup possible null vars on msg alloc failure
+
+	  In message.c, if msg_alloc fails to init the string field,
+	  vars may be null, so use a null tolerant cleanup.
+
+	  In res_pjsip_messaging.c, if msg_data_create fails, mdata
+	  will be null, so use a null tolerant cleanup.
+
+	  ASTERISK-25323
+
+	  Change-Id: Ic2d55c2c3750d5616e2a05ea92a19c717507ff56
+
+2016-03-02 09:34 +0000 [3c37c7071f]  Scott Griepentrog <scott at griepentrog.com>
+
+	* CHAOS: prevent crash on failed strdup
+
+	  This patch avoids crashing on a null pointer
+	  if the strdup() allocation fails.
+
+	  ASTERISK-25323
+
+	  Change-Id: I3f67434820ba53b53663efd6cbb42749f4f6c0f5
+
+2016-02-29 18:11 +0000 [9633be9d25]  Richard Mudgett <rmudgett at digium.com>
+
+	* func_callerid.c: Update REDIRECTING reason documentation.
+
+	  Change-Id: I6e8d39b0711110a4bceafa652e58b30465e28386
+
+2016-02-26 18:57 +0000 [4165ea7778]  Richard Mudgett <rmudgett at digium.com>
+
+	* SIP diversion: Fix REDIRECTING(reason) value inconsistencies.
+
+	  Previous chan_sip behavior:
+
+	  Before this patch chan_sip would always strip any quotes from an incoming
+	  reason and pass that value up as the REDIRECTING(reason).  For an outgoing
+	  reason value, chan_sip would check the value against known values and
+	  quote any it didn't recognize.  Incoming 480 response message reason text
+	  was just assigned to the REDIRECTING(reason).
+
+	  Previous chan_pjsip behavior:
+
+	  Before this patch chan_pjsip would always pass the incoming reason value
+	  up as the REDIRECTING(reason).  For an outgoing reason value, chan_pjsip
+	  would send the reason value as passed down.
+
+	  With this patch:
+
+	  Both channel drivers match incoming reason values with values documented
+	  by REDIRECTING(reason) and values documented by RFC5806 regardless of
+	  whether they are quoted or not.  RFC5806 values are mapped to the
+	  equivalent REDIRECTING(reason) documented value and is set in
+	  REDIRECTING(reason).  e.g., an incoming RFC5806 'unconditional' value or a
+	  quoted string version ('"unconditional"') is converted to
+	  REDIRECTING(reason)'s 'cfu' value.  The user's dialplan only needs to deal
+	  with 'cfu' instead of any of the aliases.
+
+	  The incoming 480 response reason text supported by chan_sip checks for
+	  known reason values and if not matched then puts quotes around the reason
+	  string and assigns that to REDIRECTING(reason).
+
+	  Both channel drivers send outgoing known REDIRECTING(reason) values as the
+	  unquoted RFC5806 equivalent.  User custom values are either sent as is or
+	  with added quotes if SIP doesn't allow a character within the value as
+	  part of a RFC3261 Section 25.1 token.  Note that there are still
+	  limitations on what characters can be put in a custom user value.  e.g.,
+	  embedding quotes in the middle of the reason string is silly and just
+	  going to cause you grief.
+
+	  * Setting a REDIRECTING(reason) value now recognizes RFC5806 aliases.
+	  e.g., Setting REDIRECTING(reason) to 'unconditional' is converted to the
+	  'cfu' value.
+
+	  * Added missing malloc() NULL return check in res_pjsip_diversion.c
+	  set_redirecting_reason().
+
+	  * Fixed potential read from a stale pointer in res_pjsip_diversion.c
+	  add_diversion_header().  The reason string needed to be copied into the
+	  tdata memory pool to ensure that the string would always be available.
+	  Otherwise, if the reason string returned by reason_code_to_str() was a
+	  user's reason string then the string could be freed later by another
+	  thread.
+
+	  Change-Id: Ifba83d23a195a9f64d55b9c681d2e62476b68a87
+
+2016-02-26 18:54 +0000 [41f4af4ce5]  Richard Mudgett <rmudgett at digium.com>
+
+	* res_pjsip_send_to_voicemail.c: Allow either quoted or not send_to_vm reason.
+
+	  Change-Id: Id6350b3c7d4ec8df7ec89863566645e2b0f441fd
+
+2016-02-29 20:41 +0000 [4c5998ff55]  Richard Mudgett <rmudgett at digium.com>
+
+	* res_pjsip_send_to_voicemail.c: Fix off-nominal double channel unref.
+
+	  * Fix double unref of other_party channel in off nominal path.
+
+	  * This is unlikely to be a real problem.  However, for safety,
+	  in handle_incoming_request() keep the datastore ref with the
+	  other_party channel ref until we are finished with the other_party
+	  channel.
+
+	  Change-Id: I78f22547bf0bb99fb20814ceab75952bd857f821
+
+2016-01-18 21:54 +0000 [b59956a875]  gtjoseph <george.joseph at fairview5.com>
+
+	* build-system: Allow building with static pjproject
+
+	  Background here:
+	  http://lists.digium.com/pipermail/asterisk-dev/2016-January/075266.html
+
+	  From CHANGES:
+	   * To help insure that Asterisk is compiled and run with the same known
+	     version of pjproject, a new option (--with-pjproject-bundled) has been
+	     added to ./configure.  When specified, the version of pjproject specified
+	     in third-party/versions.mak will be downloaded and configured.  When you
+	     make Asterisk, the build process will also automatically build pjproject
+	     and Asterisk will be statically linked to it.  Once a particular version
+	     of pjproject is configured and built, it won't be configured or built
+	     again unless you run a 'make distclean'.
+
+	     To facilitate testing, when 'make install' is run, the pjsua and pjsystest
+	     utilities and the pjproject python bindings will be installed in
+	     ASTDATADIR/third-party/pjproject.
+
+	     The default behavior remains building with the shared pjproject
+	     installation, if any.
+
+	  Building:
+
+	     All you have to do is include the --with-pjproject-bundled option on
+	     the ./configure command line (and remove any existing --with-pjproject
+	     option if specified).  Everything else is automatic.
+
+	  Behind the scenes:
+
+	     The top-level Makefile was modified to include 'third-party' in the
+	     list of MOD_SUBDIRS.
+
+	     The third-party directory was created to contain any third party
+	     packages that may be needed in the future.  Its Makefile automatically
+	     iterates over any subdirectories passing on targets.
+
+	     The third-party/pjproject directory was created to house the pjproject
+	     source distribution.  Its Makefile contains targets to download, patch
+	     configure, generate dependencies, compile libs, apps and python bindings,
+	     sanitized build.mak and generate a symbols list.
+
+	     When bootstrap.sh is run, it automatically includes the configure.m4
+	     file in third-party/pjproject.  This file has a macro to download and
+	     conifgure pjproject and get and set PJPROJECT_INCLUDE, PJPROJECT_DIR
+	     and PJPROJECT_BUNDLED.  It also tests for the capabilities like
+	     PJ_TRANSACTION_GRP_LOCK by parsing preprocessor output as opposed to
+	     trying to compile.  Of course, bootstrap.sh is only run once and the
+	     configure file is incldued in the patch.
+
+	     When configure is run with the new options, the macro in configure.m4
+	     triggers the download, patch, conifgure and tests.  No compilation is
+	     performed at this time.  The downloaded tarball is cached in /tmp so
+	     it doesn't get downloaded again on a distclean.
+
+	     When make is run in the top-level Asterisk source directory, it will
+	     automatically descend all the subdirectories in third_party just as it
+	     does for addons, apps, etc.  The top-level Makefile makes sure that
+	     the 'third-party' is built before 'main' so that dependencies from the
+	     other directories are built first.
+
+	     When main does build, a new shared library (libasteriskpj) is created that
+	     links statically to the pjproject .a files and exports all their symbols.
+	     The asterisk binary links to that, just as it does with libasteriskssl.
+
+	     When Asterisk is installed, the pjsua and pjsystest apps, and the pjproject
+	     python bindings are installed in ASTDATADIR/third-party/pjproject.  This
+	     will facilitate testing, including running the testsuite which will be
+	     updated to check that directory for the pjsua module ahead of the system
+	     python library.
+
+	  Modules should continue to depend on pjproject if they use pjproject APIs
+	  directly.  They should not care about the implementation.  No changes to any
+	  res_pjsip modules were made.
+
+	  Change-Id: Ia7a60c28c2e9ba9537c5570f933c1ebcb20a3103
+
+2016-02-22 16:59 +0000 [18a323e542]  Richard Mudgett <rmudgett at digium.com>
+
+	* chan_sip.c: Fix T.38 issues caused by leaving a bridge.
+
+	  chan_sip could not handle AST_T38_TERMINATED frames being sent to it when
+	  the channel left the bridge.  The action resulted in overlapping outgoing
+	  reINVITEs.  The testsuite tests/fax/sip/directmedia_reinvite_t38 was not
+	  happy.
+
+	  * Force T.38 to be remembered as locally bridged.  Now when the channel
+	  leaves the native RTP bridge after T.38, the channel remembers that it has
+	  already reINVITEed the media back to Asterisk.  It just needs to terminate
+	  T.38 when the AST_T38_TERMINATED arrives.
+
+	  * Prevent redundant AST_T38_TERMINATED from causing problems.  Redundant
+	  AST_T38_TERMINATED frames could cause overlapping outgoing reINVITEs if
+	  they happen before the T.38 state changes to disabled.  Now the T.38 state
+	  is set to disabled before the reINVITE is sent.
+
+	  ASTERISK-25582 #close
+
+	  Change-Id: I53f5c6ce7d90b3f322a942af1a9bcab6d967b7ce
+
+2016-02-18 18:27 +0000 [263a39f2cc]  Richard Mudgett <rmudgett at digium.com>
+
+	* res_pjsip_t38.c: Back out part of an earlier fix attempt.
+
+	  This backs out item 4 of the 4875e5ac32f5ccad51add6a4216947bfb385245d
+	  commit.  Item 4 added the t38_bye_supplement.  Unfortunately, the frame
+	  that it puts into the bridge may or may not be processed by the time the
+	  bridged peer is kicked out of the bridge.  If it is processed then all is
+	  well.  However, if it is not processed then that channel is stuck in fax
+	  mode until it hangs up or maybe if it joins another bridge for T.38
+	  faxing.
+
+	  ASTERISK-25582
+
+	  Change-Id: Ib20a03ecadf1bf8a0dcadfadf6c2f2e60919a9f7
+
+2016-02-22 13:54 +0000 [221422be50]  Richard Mudgett <rmudgett at digium.com>
+
+	* bridge core: Add owed T.38 terminate when channel leaves a bridge.
+
+	  The channel is now going to get T.38 terminated when it leaves the
+	  bridging system and the bridged peers are going to get T.38 terminated as
+	  well.
+
+	  ASTERISK-25582
+
+	  Change-Id: I77a9205979910210e3068e1ddff400dbf35c4ca7
+
+2016-02-19 16:01 +0000 [0a5bc64491]  Richard Mudgett <rmudgett at digium.com>
+
+	* channel api: Create is_t38_active accessor functions.
+
+	  ASTERISK-25582
+
+	  Change-Id: I69451920b122de7ee18d15bb231c80ea7067a22b
+
+2016-02-19 19:06 +0000 [513638a5f4]  Richard Mudgett <rmudgett at digium.com>
+
+	* bridge_channel: Don't settle owed events on an optimization.
+
+	  Local channel optimization could cause DTMF digits to be duplicated.
+	  Pending DTMF end events would be posted to a bridge when the local channel
+	  optimizes out and is replaced by the channel further down the chain.  When
+	  the real digit ends, the channel would get another DTMF end posted to the
+	  bridge.
+
+	  A -- LocalA;1/n -- LocalA;2/n -- LocalB;1 -- LocalB;2 -- B
+
+	  1) LocalA has the /n flag to prevent optimization.
+	  2) B is sending DTMF to A through the local channel chain.
+	  3) When LocalB optimizes out it can move B to the position of LocalB;1
+	  4) Without this patch, when B swaps with LocalB;1 then LocalB;1 would
+	  settle an owed DTMF end to the bridge toward LocalA;2.
+	  5) When B finally ends its DTMF it sends the DTMF end down the chain.
+	  6) Without this patch, A would hear the DTMF digit end when LocalB
+	  optimizes out and when B ends the original digit.
+
+	  ASTERISK-25582
+
+	  Change-Id: I1bbd28b8b399c0fb54985a5747f330a4cd2aa251
+
+2016-02-22 12:15 +0000 [7c4495cb70]  Richard Mudgett <rmudgett at digium.com>
+
+	* channel.c: Route all control frames to a channel through the same code.
+
+	  Frame hooks can conceivably return a control frame in exchange for an
+	  audio frame inside ast_write().  Those returned control frames were not
+	  handled quite the same as if they were sent to ast_indicate().  Now it
+	  doesn't matter if you use ast_write() to send an AST_FRAME_CONTROL to a
+	  channel or ast_indicate().
+
+	  ASTERISK-25582
+
+	  Change-Id: I5775f41421aca2b510128198e9b827bf9169629b
+
+2016-02-25 15:13 +0000 [48d713a832]  gtjoseph <george.joseph at fairview5.com>
+
+	* sorcery:  Refactor create, update and delete to better deal with caches
+
+	  The ast_sorcery_create, update and delete function have been refactored
+	  to better deal with caches and errors.
+
+	  The action is now called on all non-caching wizards first. If ANY succeed,
+	  the action is called on all caching wizards and the observers are notified.
+	  This way we don't put something in the cache (or update or delete) before
+	  knowing the action was performed in at least 1 backend and we only call the
+	  observers once even if there were multiple writable backends.
+
+	  ast_sorcery_create was never adding to caches in the first place which
+	  was preventing contacts from getting added to a memory_cache when they
+	  were created.  In turn this was causing memory_cache to emit errors if
+	  the contact was deleted before being retrieved (which would have
+	  populated the cache).
+
+	  ASTERISK-25811 #close
+	  Reported-by: Ross Beer
+
+	  Change-Id: Id5596ce691685a79886e57b0865888458d6e7b46
+2016-02-25 15:39 +0000 [ee947d4a7a]  gtjoseph <george.joseph at fairview5.com>
+
+	* res_pjsip_mwi:  Turn some NOTICEs and WARNINGs into debug 1s.
+
+	  There are a few cases where we're emitting notices or warnings
+	  for things that really need neither, like a client retrying to subscribe
+	  to mwi when they're not conifgured for it.  They get a 404 so there's no
+	  need for non-debug messages.
+
+	  Change-Id: I05e38a7ff6c2f2521146f4be6a79731b9864e61f
+2016-02-25 14:17 +0000 [6e70e8ccdb]  gtjoseph <george.joseph at fairview5.com>
+
+	* res_sorcery_memory_cache:  Fix SEGV in some CLI commands
+
+	  A few of the CLI commands weren't checking for enough arguments
+	  and were SEGVing.
+
+	  Change-Id: Ie6494132ad2fe54b4f014bcdc112a37c36a9b413
+
+2016-02-25 10:29 +0000 [4417f64d83]  Leif Madsen <leif at leifmadsen.com>
+
+	* Add initial support to build Docker images
+
+	  This work-in-progress is the first step to being able to reliably
+	  build Asterisk containers from the Asterisk source. I'm submitting
+	  this based on feedback gained at AstriDevCon 2015.
+
+	  Information about how to use this is provided in contrib/docker/README.md
+	  and will result in a local Asterisk container being built right from
+	  your source. I believe this can eventually be automated via
+	  hub.docker.com.
+
+	  Change-Id: Ifa070706d40e56755797097b6ed72c1e243bd0d1
+
+2016-02-22 19:31 +0000 [e7a6abbbd3]  Richard Mudgett <rmudgett at digium.com>
+
+	* rtp_engine.h: Remove extraneous semicolons.
+
+	  Change-Id: Ib462633d396fa941379dfef648dcd2245e350084
+
+2016-02-23 14:57 +0000 [6656afffa0]  Richard Mudgett <rmudgett at digium.com>
+
+	* chan_sip.c: Suppress T.38 SDP c= line if addr is the same.
+
+	  Use the correct comparison function since we only care if the address
+	  without the port is the same.
+
+	  Change-Id: Ibf6c485f843a1be6dee58a47b33d81a7a8cbe3b0
+
+2016-02-16 08:14 +0000 [ea9deff996]  Christof Lauber <christof.lauber at annax.ch>
+
+	* res_config_sqlite3: Fix crashes when reading peers from sqlite3 tables
+
+	  Introduced realloaction of ast_str buf in sqlite3_escape functions in case
+	  the returned buffer from threadstorage was actually too small.
+
+	  Change-Id: I3c5eb43aaade93ee457943daddc651781954c445
+
+2016-02-11 11:01 +0000 [d2a1457e0b]  gtjoseph <george.joseph at fairview5.com>
+
+	* res_pjsip/config_transport: Allow reloading transports.
+
+	  The 'reload' mechanism actually involves closing the underlying
+	  socket and calling the appropriate udp, tcp or tls start functions
+	  again.  Only outbound_registration, pubsub and session needed work
+	  to reset the transport before sending requests to insure that the
+	  pjsip transport didn't get pulled out from under them.
+
+	  In my testing, no calls were dropped when a transport was changed
+	  for any of the 3 transport types even if ip addresses or ports were
+	  changed. To be on the safe side however, a new transport option was
+	  added (allow_reload) which defaults to 'no'.  Unless it's explicitly
+	  set to 'yes' for a transport, changes to that transport will be ignored
+	  on a reload of res_pjsip.  This should preserve the current behavior.
+
+	  Change-Id: I5e759850e25958117d4c02f62ceb7244d7ec9edf
+
+2016-02-07 17:34 +0000 [6b921f706d]  gtjoseph <george.joseph at fairview5.com>
+
+	* res_pjproject:  Add ability to map pjproject log levels to Asterisk log levels
+
+	  Warnings and errors in the pjproject libraries are generally handled by
+	  Asterisk.  In many cases, Asterisk wouldn't even consider them to be warnings
+	  or errors so the messages emitted by pjproject directly are either superfluous
+	  or misleading.  A good exampe of this are the level-0 errors pjproject emits
+	  when it can't open a TCP/TLS socket to a client to send an OPTIONS.  We don't
+	  consider a failure to qualify a UDP client an "ERROR", why should a TCP/TLS
+	  client be treated any differently?
+
+	  A config file for res_pjproject has bene added (pjproject.conf) and a new
+	  log_mappings object allows mapping pjproject levels to Asterisk levels
+	  (or nothing).  The defaults if no pjproject.conf file is found are the same
+	  as those that were hard-coded into res_pjproject initially: 0,1 = LOG_ERROR,
+	  2 = LOG_WARNING, 3,4,5 = LOG_DEBUG<level>
+
+	  Change-Id: Iba7bb349c70397586889b8f45b8c3d6c6c8c3898
+
+2016-02-18 10:55 +0000 [f295088764]  Alexei Gradinari <alex2grad at gmail.com>
+
+	* res_pjsip_outbound_publish: Fix processing 412 response
+
+	  When Asterisk receives a 412 (Conditional Request Failed) response
+	  it has to recreate publish session.
+	  There is bug in res_pjsip_outbound_publish.c
+	  The function sip_outbound_publish_client_alloc is called with wrong object
+	  while processing 412 (Conditional Request Failed) response.
+	  This patch fixes it.
+
+	  ASTERISK-25229 #close
+
+	  Change-Id: I3b62f2debf6bb1e5817cde7b13ea39ef2bf14359
+2016-02-18 11:15 +0000 [f1f79812c1]  Mark Michelson <mmichelson at digium.com>
+
+	* Fix failing threadpool_auto_increment test.
+
+	  The threadpool_auto_increment test fails infrequently for a couple of
+	  reasons
+	  * The threadpool listener was notified of fewer tasks being pushed than
+	    were actually pushed
+	  * The "was_empty" flag was set to an unexpected value.
+
+	  The problem is that the test pushes three tasks into the threadpool.
+	  Test expects the threadpool to essentially gather those three tasks, and
+	  then distribute those to the threadpool threads. It also expects that as
+	  the tasks are pushed in, the threadpool listener is alerted immediately
+	  that the tasks have been pushed. In reality, a task can be distributed
+	  to the threadpool threads quicker than expected, meaning that the
+	  threadpool has already emptied by the time each subsequent task is
+	  pushed. In addition, the internal threadpool queue can be delayed so
+	  that the threadpool listener is not alerted that a task has been pushed
+	  even after the task has been executed.
+
+	  From the test's point of view, there's no way to be able to predict
+	  exactly the order that task execution/listener notifications will occur,
+	  and there is no way to know which listener notifications will indicate
+	  that the threadpool was previously empty.
+
+	  For this reason, the test has been updated to only check the things it
+	  can check. It ensures that all tasks get executed, that the threads go
+	  idle after the tasks are executed, and that the listener is told the
+	  proper number of tasks that were pushed.
+
+	  Change-Id: I7673120d74adad64ae6894594a606e102d9a1f2c
+
+2016-02-16 23:37 +0000 [79dc5e2f00]  Rodrigo Ramírez Norambuena <a at rodrigoramirez.com>
+
+	* app_queue: fix Calculate talktime when is first call answered
+
+	  Fix calculate of average time for talktime is wrong when is completed the
+	  first call beacuse the time for talked would be that call.
+
+	  ASTERISK-25800 #close
+
+	  Change-Id: I94f79028935913cd9174b090b52bb300b91b9492
+
+2016-02-17 13:30 +0000 [5a3a857dd6]  Richard Mudgett <rmudgett at digium.com>
+
+	* cel.c: Fix mismatch in ast_cel_track_event() return type.
+
+	  The return type of ast_cel_track_event() is not large enough to return all
+	  64 potential bits of the event enable mask.  Fortunately, the defined CEL
+	  events do not really need all 64 bits and the return value is only used to
+	  determine if the requested CEL event is enabled.
+
+	  * Made the ast_cel_track_event() return 0 or 1 only so the return value
+	  can fit inside an int type instead of zero or a truncated 64 bit non-zero
+	  value.
+
+	  Change-Id: I783d932320db11a95c7bf7636a72b6fe2566904c
+
+2016-02-16 16:37 +0000 [87ab65c557]  gtjoseph <george.joseph at fairview5.com>
+
+	* res_odbc: Fix exports.in for missing symbols
+
+	  res_odbc.exports.in was missing a few symbols.
+	  Changed to wildcards.
+
+	  Change-Id: Ieadd76df24e43ea92577f651d478a0f7b742c30c
+
+2016-02-16 12:20 +0000 [c0f3062031]  gtjoseph <george.joseph at fairview5.com>
+
+	* res_statsd:  Fix exports.in for missing symbols
+
+	  res_statsd.export.in was missing the _va variations of the log
+	  functions causing Asterisk to crash in res_pjsip if OPTIONAL_API
+	  wasn't enabled.
+
+	  ASTERISK-25727 #close
+	  Reported-by: Gergely Dömsödi
+
+	  Change-Id: I395729f9f51bdd33c5ca757f5f96ebedad74077b
+
+2016-02-15 21:31 +0000 [5e848dae7b]  gtjoseph <george.joseph at fairview5.com>
+
+	* res_pjsip_config_wizard:  Add command to export primitive objects
+
+	  A new command (pjsip export config_wizard primitives) has been added that
+	  will export all the pjsip objects it created to the console or a file
+	  suitable for reuse in a pjsip.conf file.
+
+	  ASTERISK-24919 #close
+	  Reported-by: Ray Crumrine
+
+	  Change-Id: Ica2a5f494244b4f8345b0437b16d06aa0484452b
+
+2016-02-15 15:37 +0000 [34c64707d1]  gtjoseph <george.joseph at fairview5.com>
+
+	* res_pjsip_caller_id: Fix segfault when replacing rpid or pai header
+
+	  If the PJSIP_HEADER dialplan function adds a PAI or RPID header and send_rpid
+	  or send_pai is set, res_pjsip_caller_id attemps to retrieve, parse and modify
+	  the header added by the dialplan function.  Since the header added by the
+	  dialplan function is generic string, there are no virtual functions to parse
+	  the uri and we get a segfault when we try.  Since the modify, was really only
+	  an overwrite, we now just delete the old header if it was type PJSIP_H_OTHER
+	  and recreate it.
+
+	  This raises a question for another time though:  What should happen with
+	  duplicate headers?  Right now res_pjsip_header_funcs doesn't check for dups
+	  so if it's session supplement is loaded after res_pjsip_caller_id's (or any
+	  other module that adds headers), there'll be dups in the message.
+
+	  ASTERISK-25337 #close
+
+	  Change-Id: I5e296b52d30f106b822c0eb27c4c2b0e0f71c7fa
+
+2016-02-15 13:08 +0000 [ebe167f792]  Mark Michelson <mmichelson at digium.com>
+
+	* Fix creation race of contact_status structures.
+
+	  It is possible when processing a SIP REGISTER request to have two
+	  threads end up creating contact_status structures in sorcery.
+	  contact_status is created using a "find or create" function. If two
+	  threads call into this at the same time, each thread will fail to find
+	  an existing contact_status, and so both will end up creating a new
+	  contact status.
+
+	  During testing, we would see sporadic failures because the
+	  PJSIP_CONTACT() dialplan function would operate on a different
+	  contact_status than what had been updated by res_pjsip/pjsip_options.
+
+	  The fix here is two-fold:
+	  1) The "find or create" function for contact_status now has a lock
+	  around the entire operation. This way, if two threads attempt the
+	  operation simultaneously, the first to get there will create the object,
+	  and the second will find the object created by the first thread.
+
+	  2) res_sorcery_memory has had its create callback updated so that it
+	  will not allow for objects with duplicate IDs to be created.
+
+	  Change-Id: I55b1460ff1eb0af0a3697b82d7c2bac9f6af5b97
+
+2016-02-15 12:52 +0000 [1c4f2a920d]  Joshua Colp <jcolp at digium.com>
+
+	* res_pjsip_pubsub: Move where the subscription is stored to after initialized.
+
+	  A problem arose when testing the AMI subscription listing actions where it
+	  was possible for a subscription that had not been fully initialized to be
+	  listed. This was problematic as the underlying listing code would crash.
+
+	  This change makes it so the subscription tree is fully set up before it is
+	  added to the list of subscriptions. This ensures that when the listing actions
+	  get the subscription it is valid.
+
+	  ASTERISK-25738 #close
+
+	  Change-Id: Iace2b13641c31bbcc0d43a39f99aba1f340c0f48
+
+2015-02-20 20:51 +0000 [ac00c6bc2d]  Corey Farrell <git at cfware.com>
+
+	* main/asterisk.c: Reverse #if statement in listener() to fix code folding.
+
+	  listener() opens the same code block in two places (#if and #else).  This
+	  confuses some folding editors causing it to think that an extra code block
+	  was opened.  Folding in 'geany' causes all code after listener() to be
+	  folded as if it were part of that procedure.
+
+	  ASTERISK-24813 #close
+
+	  Change-Id: I4b8c766e6c91e327dd445e8c18f8a6f268acd961
+
+2016-02-09 17:34 +0000 [b1b797e0e7]  gtjoseph <george.joseph at fairview5.com>
+
+	* res_pjsip:  Refactor load_module/unload_module
+
+	  load_module was just too hairy with every step having to clean up all
+	  previous steps on failure.
+
+	  Some of the pjproject init calls have now been moved to a separate
+	  load_pjsip function and the unload_pjsip function was enhanced to clean
+	  up everything if an error happened at any stage of the load process.
+
+	  In the process, a bunch of missing pj_shutdowns, serializer_pool_shutdowns
+	  and ast_threadpool_shutdowns were also corrected.
+
+	  Change-Id: I5eec711b437c35b56605ed99537ebbb30463b302
+
+2016-02-09 22:42 +0000 [20e9792fbc]  Badalyan Vyacheslav <slavon.net at gmail.com>
+
+	* Resources/res_phoneprov: fix memory leak and heap-use-after-free
+
+	  * heap-use-after-free happens when we free "cfg"
+	  but then use "value" which refers to it
+
+	  * A memory leak occurs because in some cases
+	  it is not released "defaults"
+
+	  ASTERISK-25721 #close
+	  Reported by: Badalyan Vyacheslav
+	  Tested by: Badalyan Vyacheslav
+
+	  Change-Id: I3807d3f4726df6864430ec144cf6265d3f538469
+
+2016-02-11 11:21 +0000 [962a9d61f8]  Etienne Lessard (license #6394)
+
+	* func_iconv: Ensure output strings are properly terminated.
+
+	  ASTERISK-25272 #close
+	  Reported by: Etienne Lessard
+	  patches:
+	   AST-25272.patch submitted by Etienne Lessard (license #6394)
+
+	  Change-Id: Id75ad202300960a1e91afe15e319d992936ecc17
+
+2016-02-10 16:16 +0000 [c1bf014ea0]  gtjoseph <george.joseph at fairview5.com>
+
+	* res_pjsip:  Handle pjsip_dlg_create_uas deprecation
+
+	  Pjproject has deprecated pjsip_dlg_create_uas in 2.5 and replaced it with
+	  pjsip_dlg_create_uas_and_inc_lock which, as the name implies, automatically
+	  increments the lock on the returned dialog.  To account for this, configure.ac
+	  now detects the presence of pjsip_dlg_create_uas_and_inc_lock and res_pjsip.c
+	  has an #ifdef HAVE_PJSIP_DLG_CREATE_UAS_AND_INC_LOCK to decide whether to use
+	  the original call or the new one.  If the new one was used, the ref count is
+	  decremented before returning.
+
+	  ASTERISK-25751 #close
+	  Reported-by Josh Colp
+
+	  Change-Id: I1be776b94761df03bd0693bc7795a75682615ca8
+
+2016-02-09 23:40 +0000 [bd07b6f0dd]  Badalyan Vyacheslav <slavon.net at gmail.com>
+
+	* Build: Added testing compiler to support the system sanitizes
+
+	  In older versions of the compiler was not sanitizes.
+	  Compilers other than GCC can not support the Usan and TSAN
+	  or have other options for *FLAGS.
+
+	  ASTERISK-25767 #close
+	  Reported by: Badalyan Vyacheslav
+	  Tested by: Badalyan Vyacheslav
+
+	  Change-Id: Iefce6608221fa87884b82ae3cb5649b7b1804916
+
+2016-02-09 20:57 +0000 [e9e896abd1]  Badalyan Vyacheslav <v.badalyan at open-bs.ru>
+
+	* Build: Fix menuselect USAN conflicts
+
+	  USAN can be used together with other sanitizers.
+
+	  Reported by: Badalyan Vyacheslav
+	  Tested by: Badalyan Vyacheslav
+
+	  Change-Id: I3bffa350d70965c3026651dba3a12414d0aaa45f
+
+2016-02-09 14:21 +0000 [93e8ed0154]  Corey Farrell <git at cfware.com>
+
+	* Simplify and fix conditional in FD_SET.
+
+	  FD_SET contains a conditional statement to protect against buffer
+	  overruns.  The statement was overly complicated and prevented use
+	  of the last array element of ast_fdset.  We now just verify the fd
+	  is less than ast_FDMAX.
+
+	  Change-Id: I41895c0b497b052aef5bf49d75c817c48b326f40
+
+2016-02-09 07:11 +0000 [a7c8d4cd6b]  Joshua Colp <jcolp at digium.com>
+
+	* tests/test_sorcery_memory_cache_thrash: Improve termination process.
+
+	  When terminating the threads thrashing a sorcery memory cache each
+	  would be told to stop and then we would wait on them. During at
+	  least one thrashing test this was problematic due to the specific
+	  usage pattern in use. It would take some time for termination of the
+	  thread to occur.
+
+	  This would occur due to contention between the threads retrieving
+	  and the threads updating the cache. As the retrieving threads are
+	  given priority it may be some time before the updating threads
+	  are able to proceed.
+
+	  This change makes it so all threads are told to stop and then each
+	  are joined to ensure they stop. This way all the threads should
+	  stop at around the same time instead of waiting for one to stop,
+	  the next to stop, then the next, and so on. As a result of this
+	  the execution time for each thrash test is much closer to their
+	  expected value than previously seen as well.
+
+	  Change-Id: I04a53470b0ea4170b8819180b0bd7475f3642827
+2016-01-29 17:56 +0000 [2451d4e455]  gtjoseph <george.joseph at fairview5.com>
+
+	* res_pjsip:  Fix infinite recursion when loading transports from realtime
+
+	  Attempting to load a transport from realtime was forcing asterisk into an
+	  infinite recursion loop.  The first thing transport_apply did was to do a
+	  sorcery retrieve by id for an existing transport of the same name. For files,
+	  this just returns the previous object from res_sorcery_config's internal
+	  container, if any.  For realtime, the res_sourcery_realtime driver looks in the
+	  database and finds the existing row but now it has to rehydrate it into a
+	  sorcery object which means calling... transport_apply.  And so it goes.
+
+	  The main issue with loading from realtime (apart from the loop) was that
+	  transport stores structures and pointers directly in the ast_sip_transport
+	  structure instead of the separate ast_transport_state structure.  This patch
+	  separates those items into the ast_sip_transport_state structure.  The pattern
+	  is roughly the same as res_pjsip_outbound_registration.
+
+	  Although all current usages of ast_sip_transport and ast_sip_transport_state
+	  were modified to use the new ast_sip_get_transport_state API, the original
+	  items are left in ast_sip_transport and kept updated to maintain ABI
+	  compatability for third-party modules.  They are marked as deprecated and
+	  noted that they're now in ast_sip_transport_state.
+
+	  ASTERISK-25606 #close
+	  Reported-by: Martin Moučka
+
+	  Change-Id: Ic7a836ea8e786e8def51fe3f8cce855ea54f5f19
+
+2016-01-25 17:36 +0000 [6f978fbfe5]  Richard Mudgett <rmudgett at digium.com>
+
+	* app_confbridge: Only use b_profile options from the conference.
+
+	  A user cannot set new bridge options after the conference is created by
+	  the first user.  Attempting to do so is documented as undefined behavior.
+
+	  This patch ensures that the bridge profile options used are from the
+	  conference and not what a subsequent user may have tried to set.
+
+	  Change-Id: I1b6383eba654679e5739d5a8de98199cf074a266
+
+2016-02-05 10:29 +0000 [ec8fd6714d]  gtjoseph <george.joseph at fairview5.com>
+
+	* chan_misdn: Fix a few issues causing compile errors
+
+	  Change-Id: I54b48c24d7ca88ed80496fdfd142d08772a7ab98
+
+2016-02-04 16:17 +0000 [6a799cd78f]  Mark Michelson <mmichelson at digium.com>
+
+	* Check for OpenSSL defines before trying to use them.
+
+	  The SSL_OP_NO_TLSv1_1 and SSL_OP_NO_TLSv1_2 defines did not exist prior
+	  to OpenSSL version 1.0.1. A recent commit attempts to, by default, set
+	  these options, which can cause problems on systems with older OpenSSL
+	  installations.
+
+	  This commit adds a configure script check for those defines and will not
+	  attempt to make use of those if they do not exist. We will print a
+	  warning urging the user to upgrade their OpenSSL installation if those
+	  defines are not present.
+
+	  Change-Id: I6a2eb9a43fd0738b404d8f6f2cf4b5c22d9d752d
+2016-02-03 14:25 +0000 [953d1cc11a]  gtjoseph <george.joseph at fairview5.com>
+
+	* pjsip/alembic:  Add missing columns to system and registration
+
+	  ps_systems needed disable_tcp_switch
+	  ps_registrations needed line and endpoint
+
+	  ASTERISK-25737 #close
+
+	  Change-Id: Iaf9c2d69e62243d9fa53104c28c5339c47d4ac19
+
+2016-02-04 11:39 +0000 [23829b3253]  Mark Michelson <mmichelson at digium.com>
+
+	* res_stasis_device_state: Fix refcounting error.
+
+	  Device state subscription lifetimes were governed by when the
+	  subscription was established and unsubscribed from. However, it is
+	  possible that at the time of unsubscription, there could be device state
+	  events still in flight. When those device state events occur, the device
+	  state callback could attempt to dereference a freed pointer. Crash.
+
+	  This change ensures that the lifetime of the device state subscription
+	  does not end until the underlying stasis subscription has confirmed that
+	  its final message has been sent.
+
+	  Change-Id: I25a0f1472894c1a562252fb7129671478e25e9b2
+
+2016-01-27 10:44 +0000 [4e8e6d3922]  Sean Bright <sean.bright at gmail.com>
+
+	* res_rtp_asterisk: Allow ICE host candidates to be overriden
+
+	  During ICE negotiation the IPs of the local interfaces are sent to the remote
+	  peer as host candidates. In many cases Asterisk is behind a static one-to-one
+	  NAT, so these host addresses will be internal IP addresses.
+
+	  To help in hiding the topology of the internal network, this patch adds the
+	  ability to override the host candidates by matching them against a
+	  user-defined list of replacements.
+
+	  Change-Id: I1c9541af97b83a4c690c8150d19bf7202c8bff1f
+
+2015-12-07 12:46 +0000 [c6b1b2b1c8]  Richard Mudgett <rmudgett at digium.com>
+
+	* AST-2016-003 udptl.c: Fix uninitialized values.
+
+	  Sending UDPTL packets to Asterisk with the right amount of missing
+	  sequence numbers and enough redundant 0-length IFP packets, can make
+	  Asterisk crash.
+
+	  ASTERISK-25603 #close
+	  Reported by: Walter Doekes
+
+	  ASTERISK-25742 #close
+	  Reported by: Torrey Searle
+
+	  Change-Id: I97df8375041be986f3f266ac1946a538023a5255
+2016-02-03 12:05 +0000 [f8acadde2c]  Joshua Colp <jcolp at digium.com>
+
+	* AST-2016-001 http: Provide greater control of TLS and set modern defaults.
+
+	  This change exposes the configuration of various aspects of the TLS
+	  support and sets the default to the modern standards.
+
+	  The TLS cipher is now set to the best values according to the
+	  Mozilla OpSec team, different TLS versions can now be disabled, and
+	  the cipher order can be forced to be that of the server instead of
+	  the client.
+
+	  ASTERISK-24972 #close
+
+	  Change-Id: I0a10f2883f7559af5e48dee0901251dbf30d45b8
+2015-09-28 17:07 +0000 [3c81a052c8]  Richard Mudgett <rmudgett at digium.com>
+
+	* AST-2016-002 chan_sip.c: Fix retransmission timeout integer overflow.
+
+	  Setting the sip.conf timert1 value to a value higher than 1245 can cause
+	  an integer overflow and result in large retransmit timeout times.  These
+	  large timeout times hold system file descriptors hostage and can cause the
+	  system to run out of file descriptors.
+
+	  NOTE: The default sip.conf timert1 value is 500 which does not expose the
+	  vulnerability.
+
+	  * The overflow is now detected and the previous timeout time is
+	  calculated.
+
+	  ASTERISK-25397 #close
+	  Reported by: Alexander Traud
+
+	  Change-Id: Ia7231f2f415af1cbf90b923e001b9219cff46290
+2016-02-03 14:07 +0000 [2a6ee8caeb]  gtjoseph <george.joseph at fairview5.com>
+
+	* logging: Remove/fix some message annoyances
+
+	  test_dlinklists doesn't need to NOTICE everyone that every macro worked.
+
+	  res_phoneprov doesn't need to VERBOSE everyone that a phoneprov extension or
+	  provider was registered.
+
+	  res_odbc was missing a newline at the end of one message.
+
+	  Change-Id: I6c06361518ef3711821795e535acd439782a995e
+
+2016-02-02 10:52 +0000 [32fc784284]  Alexei Gradinari License #5691
+
+	* res_sorcery_realtime: Fix regex regression.
+
+	  A regression was introduced where searching for realtime PJSIP objects
+	  by regex by starting the regex with a leading "^" would cause no items
+	  to be returned.
+
+	  This was due to a change which attempted to drop the requirement for a
+	  leading "^" to be present due to how some CLI commands formulate their
+	  regexes. However, the change, rather than simply eliminating the
+	  requirement, caused any regexes that did begin with "^" to end up not
+	  returning the expected results.
+
+	  This change fixes the problem by inspecting the regex and formulating
+	  the realtime query differently depending on if it begins with "^".
+
+	  ASTERISK-25702 #close
+	  Reported by Nic Colledge
+
+	  Patches:
+	      realtime_retrieve_regex.patch submitted by Alexei Gradinari License #5691
+
+	  Change-Id: I055df608a6e6a10732044fa737a9fe8dca602693
+
+2016-02-02 04:05 +0000 [0405c31756]  Karsten Wemheuer <kwe-digium at iptam.com>
+
+	* res_xmpp: Does not connect in component mode
+
+	  The module res_xmpp does not accept usernames in the form used in component
+	  mode (XEP-0114). In component mode there is no @something in the name.
+	  In component mode the connection is now not dropped anymore.
+
+	  If the xmpp server sends out a "stream" tag before handshake is finished,
+	  the connection gets dropped in res_xmpp. Now this tag will be ignored and
+	  the connection will be established.
+
+	  After connecting there will be an exchange of presence states. This does
+	  not work as expected in component mode. The responsible function
+	  "xmpp_pak_presence" is left before the states get sent out. Sending
+	  presence states in component mode is now moved to the top of the function.
+
+	  ASTERISK-25735 #close
+
+	  Change-Id: I70e036f931c3124ebb2ad1e56f93ed35cfdd9d5c
+2016-02-01 13:04 +0000 [8804d0973c]  gtjoseph <george.joseph at fairview5.com>
+
+	* build_system:  Fix some warnings highlighted by clang
+
+	  Fix some warnings found with clang.
+
+	  Change-Id: I5195b6189b148c2ee3ed4a19d015a6d4ef3e77bd
+
+2016-02-01 13:16 +0000 [109b0aff6b]  gtjoseph <george.joseph at fairview5.com>
+
+	* res/Makefile: Fix bug in "clean" target for ari
+
+	  The "clean" target was attempting to clean res/ari from inside
+	  the res directory which doesn't remove anything.  Removed the res/
+	  prefix.
+
+	  Change-Id: Ib1a518d54efa81b9fd5a42742d43cc3767435bf6
+
+2016-01-31 20:13 +0000 [a85fab7c44]  gtjoseph <george.joseph at fairview5.com>
+
+	* pjsip/alembic: Fix definition of qualify_timeout
+
+	  A recent commit set qualify_timeout to Decimal which isn't supported.
+	  This path corrects it to Float.
+
+	  Change-Id: I038f5274ba8cb60f8518a5845ce448d49306aadf
+
+2016-01-29 07:39 +0000 [aa9348ab9a]  Stefan Engström <stefanen at kth.se>
+
+	* chan_sip.c: AMI & CLI notify methods get different values of asterisk's own ip.
+
+	  When I ask asterisk to send a SIP NOTIFY message to a sip peer using either a)
+	  AMI action: SIPnotify or b) cli command: sip notify <cmd> <peer>, I expect
+	  asterisk to include the same value for its own ip in both cases a) and b),
+	  but it seems a) produces a contact header like Contact:
+	  <sip:asterisk at 192.168.1.227:8060> whereas b) produces a contact header like
+	  <sip:asterisk at 127.0.0.1:8060>. 0.0.0.0:8060 is my udpbindaddr in sip.conf
+
+	  My guess is that manager_sipnotify should call
+	  ast_sip_ouraddrfor(&p->sa, &p->ourip, p) the same way sip_cli_notify does,
+	  because after applying this patch, both cases a) and b) produce
+	  the contact header that I expect: <sip:asterisk at 192.168.1.227:8060>
+
+	  Reported by: Stefan Engström
+	  Tested by: Stefan Engström
+
+	  Change-Id: I86af5e209db64aab82c25417de6c768fb645f476
+2015-12-23 15:07 +0000 [65bd4fcc3f]  Mark Michelson <mmichelson at digium.com>
+
+	* res_odbc: Remove connection management
+
+	  Asterisk by default will create a single database connection and share
+	  it among all threads that attempt to access the database. In previous
+	  versions of Asterisk, this was tolerable, because the most used channel
+	  driver, chan_sip, mostly accessed the database from a single thread.
+	  With PJSIP, however, many threads may be attempting to perform database
+	  operations, and there is the potential for many more database accesses,
+	  meaning the concurrency is a horrible bottleneck if only one connection
+	  is shared.
+
+	  Asterisk has a connection pooling facility built into it, but the
+	  implementation has flaws. For one, there is a strict limit on the number
+	  of simultaneous connections that could be made to the database. Anything
+	  beyond the maximum would result in a failed operation. Attempting to
+	  predict what the maximum should be is nearly impossible even for someone
+	  intimately familiar with Asterisk's threading model. In addition, use of
+	  transactions in the dialplan can cause some severe bugs if connection
+	  pooling is enabled.
+
+	  This commit seeks to fix the concurrency problem by removing all
+	  connection management code from Asterisk and leaving that to the
+	  underlying unixODBC code instead. Now, Asterisk does not share a single
+	  connection, nor does it try to maintain a connection pool. Instead, all
+	  Asterisk ever does is request a connection from unixODBC and allow
+	  unixODBC to either allocate those connections or retrieve them from a
+	  pool.
+
+	  Doing this has a bit of a ripple effect. For one, since connections are
+	  not long-lived objects, several of the safeguards that previously
+	  existed have been removed. We don't have to worry about trying to use a
+	  connection that has gone stale. In every case, when we request a
+	  connection, it has just been made and we don't need to perform any
+	  sanity checks to be sure it's still active.
+
+	  Another major player affected by this change is transactions.
+	  Transactions and their respective connections were so tightly coupled
+	  that it was almost pornographic. This code change moves
+	  transaction-related code to its own file separate from the core ODBC
+	  functionality. This way, the core of ODBC does not even have to know
+	  that transactions exist.
+
+	  In making this large change, I had to look at a lot of code and
+	  understand it. When making this change, I discovered several places
+	  where the behavior is definitely not ideal, but it seemed outside the
+	  scope of this change to be fixing it. Instead, any place where I saw
+	  some sort of room for improvement has had a XXX comment added explaining
+	  what could be altered to improve it.
+
+	  Change-Id: I37a84def5ea4ddf93868ce8105f39de078297fbf
+
+2016-01-28 12:44 +0000 [2a9e623ff9]  Richard Mudgett <rmudgett at digium.com>
+
+	* config_options.c: Fix warning message wording.
+
+	  Change-Id: I915ea437936320393afde0e7552cf0a980a6b2e4
+
+2016-01-25 17:34 +0000 [ed3c9c1512]  Richard Mudgett <rmudgett at digium.com>
+
+	* app_confbridge.c: Replace inlined code with existing function.
+
+	  Change-Id: Ida5594e9f8d7c1fc18eeb733a11f8fb96326da51
+
+2016-01-25 16:05 +0000 [1d0abf86e7]  Richard Mudgett <rmudgett at digium.com>
+
+	* app_confbridge: Add ability to get the muted conference state.
+
+	  * Added CONFBRIDGE_INFO(muted,) for querying the muted conference state.
+
+	  * Added Muted header to AMI ConfbridgeListRooms action response list
+	  events to indicate the muted conference state.
+
+	  * Added Muted column to CLI "confbridge list" output to indicate the muted
+	  conference state and made the locked column a yes/no value instead of a
+	  locked/unlocked value.
+
+	  ASTERISK-20987
+	  Reported by: hristo
+
+	  Change-Id: I4076bd8ea1c23a3afd4f5833e9291b49a0c448b1
+
+2016-01-26 17:59 +0000 [f0d40afa69]  Richard Mudgett <rmudgett at digium.com>
+
+	* app_confbridge.c: Update CONFBRIDGE and CONFBRIDGE_INFO documentation.
+
+	  Change-Id: Ic1f9e22ba1f2ff3b3f5cb017c5ddcd9bd48eccc7
+
+2016-01-25 15:48 +0000 [3e51e5c7fd]  Richard Mudgett <rmudgett at digium.com>
+
+	* app_confbridge: Make non-admin users join a muted conference muted.
+
+	  ASTERISK-20987 #close
+	  Reported by: hristo
+
+	  Change-Id: Ic61a2b524ab3a4cfadf227fc6b3506527bc03f38
+
+2016-01-27 13:02 +0000 [9da18af992]  gtjoseph <george.joseph at fairview5.com>
+
+	* res_pjsip:  Add res_pjproject dependency to UPGRADE.txt and samples
+
+	  Since res_pjsip now depends on res_pjproject, this is now mentioned
+	  in UPGRADE.txt and the basic-pbx modules.conf has been updated.
+
+	  Change-Id: I42826597d5e10f08e518208860c44c96e52f1b2d
+2016-01-27 10:29 +0000 [aee8448bc2]  gtjoseph <george.joseph at fairview5.com>
+
+	* build_system: Prevent goals needing makeopts from running when it's missing
+
+	  The Makefile only optionally includes makeopts so when goals like uninstall that
+	  dont depend on anything else are run after a distclean, rules like
+	  'rm -f "$(DESTDIR)$(ASTMODDIR)/"*' get run as 'rm -f ""/*' which attempts
+	  to remove everything in the root directory.
+
+	  Although there's a rule defined for makeopts which prints a message and does
+	  an 'exit 1', since '-include makepopts' was specified (with the -), the exit
+	  was ignored letting the rest of the rules run.
+
+	  This patch makes makeopts required unless the goal has the string 'clean' in it.
+
+	  ASTERISK-25730 #close
+	  Reported-by: George Joseph
+
+	  Change-Id: I1bce59a7ea4f48e7a468e22b2abbb13c63417ac7
+
+2016-01-25 09:35 +0000 [f22074e5d9]  Joshua Colp <jcolp at digium.com>
+
+	* config: Allow options to register when documentation is unavailable.
+
+	  The config options framework is strict in that configuration options must
+	  be documented unless XML documentation support is not available. In
+	  practice this is useful as it ensures documentation exists however in
+	  off-nominal cases this can cause strange problems.
+
+	  If it is expected that a config option has a non-zero or non-empty
+	  default value but the config option documentation is unavailable
+	  this reasonable expectation will not be met. This can cause obscure
+	  crashes and weirdness depending on how the code handles it.
+
+	  This change tweaks the behavior to ensure that the config option
+	  is still allowed to register, apply default values, and be set when
+	  devmode is not enabled. If devmode is enabled then the option can
+	  NOT be set.
+
+	  This also does not remove the initial documentation error message that
+	  is output on load when registering the configuration option.
+
+	  ASTERISK-25725 #close
+
+	  Change-Id: Iec42fca6b35f31326c33fcdc25473f6fd7bc8af8
+
+2016-01-25 10:23 +0000 [4a3275abb9]  Mark Michelson <mmichelson at digium.com>
+
+	* Stasis: Use custom structure when setting variables.
+
+	  A recent change to queue channel variable setting to the Stasis control
+	  queue caused a regression. When setting channel variables, it is
+	  possible to give a NULL channel variable value in order to unset the
+	  variable (i.e. remove it from the channel variable list). The change
+	  introduced a call to ast_variable_new(), which is not tolerant of NULL
+	  channel variable values.
+
+	  This new change switches from using ast_variable to using a custom
+	  channel variable struct that is lighter weight and NULL value-tolerant.
+
+	  Change-Id: I784d7beaaa3c036ea936d103e7caf0bb1562162d
+
+2016-01-25 16:56 +0000 [b2c8a99f9e]  Rusty Newton <rnewton at digium.com>
+
+	* sounds/Makefile: Incremented core and extra sounds versions to 1.5
+
+	  Core and extra sounds 1.5 was recently released! The tarballs contain
+	  change descriptions however I figure more people will see this one so
+	  I'll try to be a bit detailed. Approximately 60 sounds were moved from Extra
+	  to Core for en, en_GB, fr and added for languages that didn't already
+	  have Extra sound sets (it,ja,ru).
+
+	  In addition all of the English and Russian sounds have been completely
+	  re-recorded.
+
+	  Sounds moved and added:
+	  activated,added,all-circuits-busy-now,astcc-followed-by-pound
+	  at-tone-time-exactly,call-forwarding,call-fwd-no-ans,call-fwd-on-busy
+	  ,call-fwd-unconditional,calling,call-waiting,cancelled,
+	  cannot-complete-as-dialed,check-number-dial-again,conf-full,de-activated
+	  ,disabled,do-not-disturb,enabled,enter-num-blacklist,entr-num-rmv-blklist
+	  ,extension,feature-not-avail-line,for,from-unknown-caller,goodbye,hello
+	  ,if-correct-press,im-sorry,info-about-last-call,is,is-in-use,is-set-to
+	  ,location,number,number-not-answering,num-was-successfully,one-moment-please
+	  ,please-try-again,pls-hold-while-try,pls-try-call-later,pm-invalid-option
+	  ,privacy-to-blacklist-last-caller,removed,simul-call-limit-reached
+	  ,something-terribly-wrong,sorry,sorry-youre-having-problems,speed-dial
+	  ,speed-dial-empty,telephone-number,time,to-call-this-number,to-extension
+	  ,to-listen-to-it,to-rerecord-it,unidentified-no-callback,with,you-entered
+	  ,your
+
+	  There were also a few random fixes here and there to file names for a few
+	  of the languages.
+
+	  ASTERISK-25068 #close
+
+	  Change-Id: I2b594344ec585d7dfd922b40c1af43b1508828b3
+2016-01-25 16:51 +0000 [8261bda1bf]  Mark Michelson <mmichelson at digium.com>
+
+	* res_pjsip_pubsub: Prevent crash from AMI command on freed subscription.
+
+	  A test recently uncovered that running an ill-timed AMI command to show
+	  inbound subscriptions could cause a crash since Asterisk will try to
+	  operate on a freed subscription.
+
+	  The fix for this is to remove the subscription tree from the list of
+	  subscriptions at the time that we are sending our final NOTIFY request
+	  out. This way, as the subscription is in the process of dying, it is
+	  inaccessible from AMI.
+
+	  Change-Id: Ic0239003d8d73e04c47c12dd2a7e23867e5b5b23
+
+2016-01-25 11:03 +0000 [a6823bb0c4]  Corey Farrell <git at cfware.com>
+
+	* chan_sip: Fix buffer overrun in sip_sipredirect.
+
+	  sip_sipredirect uses sscanf to copy up to 256 characters to a stacked buffer
+	  of 256 characters.  This patch reduces the copy to 255 characters to leave
+	  room for the string null terminator.
+
+	  ASTERISK-25722 #close
+
+	  Change-Id: Id6c3a629a609e94153287512c59aa1923e8a03ab
+
+2016-01-22 15:08 +0000 [1003c2eb05]  Mark Michelson <mmichelson at digium.com>
+
+	* Stasis: Fix potential memory leak of control data.
+
+	  When queuing tasks onto the Stasis control queue, you can pass an
+	  arbitrary data pointer and a function to free that data. All ARI
+	  commands that use the Stasis control queue made the assumption that the
+	  destructor function would be called in all paths, whether the task was
+	  queued successfully or not. However, this was not correct. If a task was
+	  queued onto a control structure that was already completed, the
+	  allocated data would not be freed properly.
+
+	  This patch corrects this by making sure that all return paths call the
+	  data destructor.
+
+	  Change-Id: Ibf06522094f8e5c4cce652537dc5d7222b1c4fcb
+
+2016-01-21 10:58 +0000 [eedd77fda0]  Mark Michelson <mmichelson at digium.com>
+
+	* Stasis: Use control queue to prevent crash.
+
+	  A crash occurred when attempting to set a channel variable on a channel
+	  that had already been hung up. This is because there is a small window
+	  between when a control is grabbed and when the channel variable is set
+	  that the channel can be hung up.
+
+	  The fix here is to queue the setting of the channel variable onto the
+	  control queue. This way, the manipulation of the channel happens in a
+	  thread where it is safe to be done.
+
+	  In this change, I also noticed that the setting of bridge roles on
+	  channels was being done outside of the control queue, so I also changed
+	  those operations to be done in the control queue.
+
+	  ASTERISK-25709 #close
+	  Reported by Mark Michelson
+
+	  Change-Id: I2a0a4d51bce6fba6f1d9954e40935e42f366ea78
+
+2016-01-22 11:48 +0000 [1c95b211a0]  Richard Mudgett <rmudgett at digium.com>
+
+	* logger.c: Fix buffer overrun found by address sanitizer.
+
+	  The null terminator of the tail struct member was not being allocated
+	  when no logger.conf config file is installed.
+
+	  ASTERISK-25714 #close
+	  Reported by: Badalian Vyacheslav
+
+	  Change-Id: I45770fdd08af39506a3bc33ba279c4f16e047a30
+
+2016-01-21 16:40 +0000 [6ff945ab87]  Corey Farrell <git at cfware.com>
+
+	* Build System: Add support for checking alembic branches.
+
+	  * Add 'check-alembic' target to root Makefile.
+	  * Create build_tools/make_check_alembic to do the actual checks.
+
+	  ASTERISK-25685
+
+	  Change-Id: Ibb3cae7d1202ac23dc70b0f3b5801571ad46b004
+
+2016-01-19 18:20 +0000 [02035212de]  Richard Mudgett <rmudgett at digium.com>
+
+	* res/res_pjsip/presence_xml.c: Add missing 2nd call presence state case.
+
+	  ASTERISK-25712 #close
+	  Reported by: Richard Mudgett
+
+	  Change-Id: I70634df24f8c6c3a2c66c45af61d021e4999253f
+
+2016-01-18 03:49 +0000 [c68c66c61f]  Diederik de Groot <ddegroot at talon.nl>
+
+	* main/asterisk.c: ast_el_read_char
+
+	  Make sure buf[res] is not accessed at res=-1 (buffer underrun).
+	  Address Sanitizer will complain about this quite loudly.
+
+	  ASTERISK-24801 #close
+
+	  Change-Id: Ifcd7f691310815a31756b76067c56fba299d3ae9
+
+2016-01-13 16:49 +0000 [f87c3275cc]  Richard Mudgett <rmudgett at digium.com>
+
+	* res_pjsip: Add CLI "pjsip dump endpt [details]"
+
+	  Dump the res_pjsip endpt internals.
+
+	  In non-developer mode we will not document or make easily accessible the
+	  "details" option even though it is still available.  The user has to know
+	  it exists to use it.  Presumably they would also be aware of the potential
+	  crash warning below.
+
+	  Warning: PJPROJECT documents that the function used by this CLI command
+	  may cause a crash when asking for details because it tries to access all
+	  active memory pools.
+
+	  Change-Id: If2d98a3641c9873364d1daaad971376311aef3cb
+
+2016-01-18 17:16 +0000 [46b2de55f9]  Matt Jordan <mjordan at digium.com>
+
+	* funcs/func_cdr: Correctly report high precision values for duration and billsec
+
+	  When CDRs were refactored, func_cdr's ability to report high precision values
+	  for duration and billsec (the 'f' option) was broken. This was due to func_cdr
+	  incorrectly interpreting the duration/billsec values provided by the CDR engine
+	  in milliseconds, as opposed to seconds. Since the CDR engine only provides
+	  duration and billsec in seconds, and does not expose either attribute with
+	  sufficient precision to merely pass back the underlying value, this patch fixes
+	  the bug by re-calculating duration and billsec with microsecond precision based
+	  on the start/answer/end times on the CDR.
+
+	  ASTERISK-25179 #close
+
+	  Change-Id: I8bc63822b496537a5bf80baf6102c06206bee841
+
+2016-01-18 19:20 +0000 [137fe5ae01]  gtjoseph <george.joseph at fairview5.com>
+
+	* res_pjproject:  Add module providing pjproject logging and utils
+
+	  res_pjsip_log_forwarder has been renamed to res_pjproject
+	  and enhanced as follows:
+
+	  As a follow-on to the recent 'Add CLI "pjsip show buildopts"' patch,
+	  a new ast_pjproject_get_buildopt function has been added.  It
+	  allows the caller to get the value of one of the buildopts.
+
+	  The initial use case is retrieving the runtime value of
+	  PJ_MAX_HOSTNAME to insure we don't send a hostname greater
+	  than pjproject can handle.  Since it can differ between
+	  the version of pjproject that Asterisk was compiled against
+	  and the version of pjproject that Asterisk is running against,
+	  we can't use the PJ_MAX_HOSTNAME macro directly in Asterisk
+	  source code.
+
+	  Change-Id: Iab6e82fec3d7cf00c1cf6185c42be3e7569dee1e
+
+2016-01-19 17:15 +0000 [b5c13c1545]  Joshua Colp <jcolp at digium.com>
+
+	* test_threadpool: Wait for each task to complete and fix memory leak.
+
+	  This change makes the thread_timeout_thrash unit test wait for
+	  each task to complete. This fixes the problem where the test would
+	  prematurely end when all threads were gone and a new one had to be
+	  started to handle the last task. It also increases the thrasing as
+	  it is now more likely for each task to encounter the above scenario.
+
+	  This also fixes a memory leak where the data for each task was not
+	  being freed.
+
+	  ASTERISK-25611 #close
+
+	  Change-Id: I5017d621a4dc911f509074c16229b86bff2fb3c6
+
+2016-01-18 19:44 +0000 [0ab89182d9]  Richard Mudgett <rmudgett at digium.com>
+
+	* taskprocessor.c: Increase CLI "core ping taskprocessor" timeout.
+
+	  Change-Id: I4892d6acbb580d6c207d006341eaf5e0f8f2a029
+
+2016-01-18 19:43 +0000 [a2a8ea3330]  Richard Mudgett <rmudgett at digium.com>
+
+	* taskprocessor.c: Fix some taskprocessor unrefs.
+
+	  You have to call ast_taskprocessor_unref() outside of the taskprocessor
+	  implementation code.  Taskprocessor use since v12 has become more
+	  transient than just the singleton uses in earlier versions.
+
+	  Change-Id: If7675299924c0cc65f2a43a85254e6f06f2d61bb
+
+2016-01-19 13:44 +0000 [d604a9afc8]  Richard Mudgett <rmudgett at digium.com>
+
+	* Fix alembic branches on v13.
+
+	  Change-Id: I313449b609ede18ad1e1763a655dd23b9210a8e0
+
+2016-01-18 18:45 +0000 [a0c79f3a4f]  gtjoseph <george.joseph at fairview5.com>
+
+	* pjsip_loging_refactor: Rename res_pjsip_log_forwarder to res_pjproject
+
+	  Change-Id: I5387821f29e5caa0cba0b7d62b0fc0d341e7e20b
+
+2016-01-14 09:26 +0000 [018ccf680b]  Rusty Newton <rnewton at digium.com>
+
+	* func_channel: Add help text for undocumented CHANNEL function arguments
+
+	  Adding help text documentation for:
+	  * hangupsource
+	  * appname
+	  * appdata
+	  * exten
+	  * context
+	  * channame
+	  * uniqueid
+	  * linkedid
+
+	  ASTERISK-24097 #close
+	  Reported by: Steven T. Wheeler
+	  Tested by: Rusty Newton
+
+	  Change-Id: Ib94b00568b0433987df87d5b67ea529b5905754d
+
+2016-01-16 13:18 +0000 [5644bca9f9]  Daniel Journo <dan at keshercommunications.com>
+
+	* Update version number in features.conf.sample
+
+	  Update the version number in the comments from Asterisk 12 to Asterisk 12+
+
+	  Change-Id: Ie692ac8cda3c993c3bf10f27f51a1cca3317ec7b
+
+2016-01-15 19:52 +0000 [3f5f30cf82]  Corey Farrell <git at cfware.com>
+
+	* main/config: Clean config maps on shutdown.
+
+	  ASTERISK-25700 #close
+
+	  Change-Id: I096da84f9c62c6095f68bcf98eac4b7c7868e808
+
+2016-01-14 14:42 +0000 [660fedecb7]  Kevin Harwell <kharwell at digium.com>
+
+	* bridge_basic: don't cache xferfailsound during an attended transfer
+
+	  The xferfailsound was read from the channel at the beginning of the transfer,
+	  and that value is "cached" for the duration of the transfer. Therefore, changing
+	  the xferfailsound on the channel using the FEATURE() dialplan function does
+	  nothing once the transfer is under way.
+
+	  This makes it so the transfer code instead gets the xferfailsound configuration
+	  options from the channel when it is actually going to be used.
+
+	  This patch also fixes a potential memory leak of the props object as well as
+	  making sure the condition variable gets initialized before being destroyed.
+
+	  ASTERISK-25696 #close
+
+	  Change-Id: Ic726b0f54ef588bd9c9c67f4b0e4d787934f85e4
+
+2015-07-10 10:37 +0000 [9cda1de34d]  Richard Mudgett <rmudgett at digium.com>
+
+	* taskprocessor.c: Simplify ast_taskprocessor_get() return code.
+
+	  Change-Id: Id5bd18ef1f60ef8be453e677e98478298358a9d1
+
+2016-01-13 18:20 +0000 [a79af2b312]  Richard Mudgett <rmudgett at digium.com>
+
+	* astmm.c: Add more stats to CLI "memory show" commands.
+
+	  * Add freed regions totals to allocations and summary.
+
+	  * Add totals for all allocations and not just the selected allocations.
+
+	  Change-Id: I61d5a5112617b0733097f2545a3006a344b4032a
+
+2016-01-14 16:00 +0000 [83feb7db3b]  Kevin Harwell <kharwell at digium.com>
+
+	* bridge_basic: don't play an attended transfer fail sound after target hangs up
+
+	  If the attended transfer destination answers (picks call up or goes to
+	  voicemail) and then hangs up on the transferer then transferer hears the
+	  fail sound.
+
+	  This patch makes it so the fail sound is not played when the transfer
+	  destination/target hangs up after answering.
+
+	  ASTERISK-25697 #close
+
+	  Change-Id: I97f142fe4fc2805d1a24b7c16143069dc03d9ded
+
+2016-01-14 13:22 +0000 [935d641f3b]  Mark Michelson <mmichelson at digium.com>
+
+	* Remove res/ari/* content during 'make clean'.
+
+	  'make clean' and 'make distclean' can leave behind .o files in the
+	  res/ari/ directory. One observed consequence of this is that running
+	  Asterisk with MALLOC_DEBUG can cause Asterisk to crash immediately on
+	  startup sometimes.
+
+	  By ensuring that we are making a clean build, we can be sure that stale
+	  files are not being included in the build and causing problems when
+	  build options should have caused files to be re-built.
+
+	  ASTERISK-25683 #close
+	  Reported by yaron nahum
+
+	  Change-Id: I1f48baa904d2468eddeefb42ee68a56af7adc7b7
+
+2016-01-13 15:58 +0000 [46f21df302]  Daniel Journo <dan at keshercommunications.com>
+
+	* pjsip/alembic:  Fix qualify_timeout column definition
+
+	  Corrects the qualify_timeout column type from Integer to Decimal
+
+	  ASTERISK-25686 #close
+	  Reported-by: Marcelo Terres
+
+	  Change-Id: I757d0e3c011ee9be6cd5abd48bc92441a405d3c8
+
+2016-01-12 11:14 +0000 [32b29d7b02]  Joshua Colp <jcolp at digium.com>
+
+	* app: Queue hangup if channel is hung up during sub or macro execution.
+
+	  This issue was exposed when executing a connected line subroutine.
+	  When connected or redirected subroutines or macros are executed it is
+	  expected that the underlying applications and logic invoked are fast
+	  and do not consume frames. In practice this constraint is not enforced
+	  and if not adhered to will cause channels to continue when they shouldn't.
+	  This is because each caller of the connected or redirected logic does not
+	  check whether the channel has been hung up on return. As a result the
+	  the hung up channel continues.
+
+	  This change makes it so when the API to execute a subroutine or
+	  macro is invoked the channel is checked to determine if it has hung up.
+	  If it has then a hangup is queued again so the caller will see it
+	  and stop.
+
+	  ASTERISK-25690 #close
+
+	  Change-Id: I1f9a8ceb1487df0389f0d346ce0f6dcbcaf476ea
+
+2016-01-13 07:20 +0000 [e7cfda0b38]  Sean Bright <sean.bright at gmail.com>
+
+	* res_musiconhold: Prevent multiple simultaneous reloads.
+
+	  There are two ways in which the reload() function in res_musiconhold can be
+	  called from the CLI:
+
+	    * module reload res_musiconhold.so
+	    * moh reload
+
+	  In the former case, the module loader holds a lock that prevents multiple
+	  concurrent calls, but in the latter there is no such protection.
+
+	  This patch changes the 'moh reload' CLI command to invoke the module loader
+	  directly, rather than call reload() explicitly.
+
+	  ASTERISK-25687 #close
+
+	  Change-Id: I408968b4c8932864411b7f9ad88cfdc7b9ba711c
+2016-01-12 14:25 +0000 [5586abc957]  Richard Mudgett <rmudgett at digium.com>
+
+	* res_pjsip_log_forwarder.c: Add CLI "pjsip show buildopts".
+
+	  PJPROJECT has a function available to dump the compile time
+	  options used when building the library.
+
+	  * Add CLI "pjsip show buildopts" command.
+
+	  * Update contrib/scripts/autosupport to get pjproject information.
+
+	  Change-Id: Id93a6a916d765b2a2e5a1aeb54caaf83206be748
+
+2016-01-12 10:36 +0000 [4cd58c3b20]  Mark Michelson <mmichelson at digium.com>
+
+	* res_sorcery_realtime: Remove leading ^ requirement.
+
+	  res_sorcery_realtime's search-by-regex callback performed a check to
+	  ensure that the passed-in regex began with a caret (^). If it did not,
+	  then no results would be returned.
+
+	  This callback only started to become used when "like" support was added
+	  to PJSIP CLI commands. The CLI command for listing objects would pass an
+	  empty regex ("") to the sorcery backend if no "like" statement was
+	  present. For most sorcery backends, this resulted in returning all
+	  objects. However, for realtime, this resulted in returning no objects.
+
+	  This commit seeks to fix the regression by removing the requirement from
+	  res_sorcery_realtime for the passed-in-regex to begin with a caret.
+
+	  ASTERISK-25689 #close
+	  Reported by Marcelo Terres
+
+	  Change-Id: I22b4dc5d7f3f11bb29ac2e42ef94682e9bab3b20
+
+2016-01-07 11:57 +0000 [219c204a41]  gtjoseph <george.joseph at fairview5.com>
+
+	* pjsip_sdp_rtp:  Add option endpoint/bind_rtp_to_media_address
+
+	  On a system with multiple ip addresses in the same subnet, if a
+	  transport is bound to a specific ip address and endpoint/media_address
+	   is set, the SIP/SDP will have the correct address in all fields but
+	  the rtp stream MAY still originate from one of the other ip addresses,
+	  most probably the "primary" ip address.  This happens because
+	   res_pjsip_sdp_rtp/create_rtp always calls ast_instance_new with
+	  the "all" ip address (0.0.0.0 or ::).
+
+	  The new option causes res_pjsip_sdp_rtp/create_rtp to call
+	  ast_rtp_instance_new with the endpoint's media_address (if specified)
+	  instead of the "all" address.  This causes the packets to originate from
+	  the specified address.
+
+	  ASTERISK-25632
+	  ASTERISK-25637
+	  Reported-by: Olivier Krief
+	  Reported-by: Dan Journo
+
+	  Change-Id: I3dfaa079e54ba7fb7c4fd1f5f7bd9509bbf8bd88
+
+2016-01-10 16:22 +0000 [22801a06ee]  Daniel Journo <dan at keshercommunications.com>
+
+	* pjsip:  Add option global/regcontext
+
+	  Added new global option (regcontext) to pjsip. When set, Asterisk will
+	  dynamically create and destroy a NoOp priority 1 extension
+	  for a given endpoint who registers or unregisters with us.
+
+	  ASTERISK-25670 #close
+	  Reported-by: Daniel Journo
+
+	  Change-Id: Ib1530c5b45340625805c057f8ff1fb240a43ea62
+
+2016-01-08 15:22 +0000 [1600ebca7d]  Kevin Harwell <kharwell at digium.com>
+
+	* pbx: Deadlock between contexts container and context_merge locks
+
+	  Recent changes (ASTERISK-25394 commit 2bd27d12223fe33b58c453965ed5c6ed3af7c4f5)
+	  introduced the possibility of a deadlock. Due to the mentioned modifications
+	  ast_change_hints now needs to keep both merge/delete and state callbacks from
+	  occurring while it executes. Unfortunately, sometimes ast_change_hints can be
+	  called with the contexts container locked. When this happens it's possible for
+	  another thread to grab the context_merge_lock before the thread calling into
+	  ast_change_hints does and then try to obtain the contexts container lock. This
+	  of course causes a deadlock between the two threads. The thread calling into
+	  ast_change_hints waits for the other thread to release context_merge_lock and
+	  the other thread is waiting on that one to release the contexts container lock.
+
+	  Unfortunately, there is not a great way to fix this problem. When hints change,
+	  the subsequent state callbacks cannot run at the same time as a merge/delete,
+	  nor when the usual state callbacks do. This patch alleviates the problem by
+	  having those particular callbacks (the ones run after a hint change) occur in a
+	  serialized task. By moving the context_merge_lock to a task it can now safely be
+	  attempted or held without a deadlock occurring.
+
+	  ASTERISK-25640 #close
+	  Reported by: Krzysztof Trempala
+
+	  Change-Id: If2210ea241afd1585dc2594c16faff84579bf302
+
+2016-01-10 17:08 +0000 [0fc3dad965]  Corey Farrell <git at cfware.com>
+
+	* devicestate: Cleanup engine thread during graceful shutdown.
+
+	  ASTERISK-25681 #close
+
+	  Change-Id: I64337c70f0ebd8c77f70792042684607c950c8f1
+
+2016-01-10 13:51 +0000 [f34dd10495]  Corey Farrell <git at cfware.com>
+
+	* manager: Cleanup manager_channelvars during shutdown.
+
+	  ASTERISK-25680 #close
+
+	  Change-Id: I3251d781cbc3f48a6a7e1b969ac4983f552b2446
+
+2016-01-10 13:27 +0000 [1d3a1167fc]  Corey Farrell <git at cfware.com>
+
+	* res_calendar: Cleanup scheduler context at unload.
+
+	  ASTERISK-25679 #close
+
+	  Change-Id: I839159bf6882cccc1b23494c7aa2bc2a2624613f
+
+2016-01-08 11:49 +0000 [3a160cdbf6]  Joshua Colp <jcolp at digium.com>
+
+	* res_rtp_asterisk: Revert DTLS negotiation changes.
+
+	  Due to locking issues within pjnath these changes are being
+	  reverted until pjnath can be changed.
+
+	  ASTERISK-25645
+
+	  Revert "res_rtp_asterisk.c: Fix DTLS negotiation delays."
+
+	  This reverts commit 24ae124e4f7310cfa64c187b944b2ffc060da28d.
+
+	  Change-Id: I2986cfb2c43dc14455c1bcaf92c3804f9da49705
+
+	  Revert "res_rtp_asterisk: Resolve further timing issues with DTLS negotiation"
+
+	  This reverts commit 965a0eee46d24321f74c244e23c5a5f45e67e12b.
+
+	  Change-Id: Ie68fafde27dad4b03cb7a1e27ce2a8502c3f7bbe
+
+2016-01-09 17:57 +0000 [4b10fc9173]  gtjoseph <george.joseph at fairview5.com>
+
+	* Revert "pjsip_location: Delete contact_status object when contact is deleted"
+
+	  This reverts commit 0a9941de9d24093b5ff44096d1d7406f29d11e45.
+
+	  Matt,
+
+	  This patch causes another problem and should not have been needed.
+	  Before this patch, persistent_endpoint_contact_deleted_observer WAS
+	  deleting the contact_status when ast_sip_location_delete_contact was
+	  called.  By deleting it yourself in ast_sip_location_delete_contact
+	  it was gone before the observer could run and the observer therefore
+	  was throwing an error and not sending stasis/AMI/statsd messages.
+
+	  So, I don't think this was the cause of your original issue.  I also
+	  had verified the contact AMI and statsd lifecycle and it was working.
+	  I'll double check now though.
+
+	  ASTERISK-25675
+	  Reported-by: Daniel Journo
+
+	  Change-Id: Ib586a6b7f90acb641b0c410f659743ab90e84f1a
+
+2016-01-09 18:04 +0000 [79b4309881]  Corey Farrell <git at cfware.com>
+
+	* pbx_dundi: Run cleanup on failed load.
+
+	  During failed startup of pbx_dundi no cleanup was performed.  Add a call
+	  to unload_module before returning AST_MODULE_LOAD_DECLINE.
+
+	  ASTERISK-25677 #close
+
+	  Change-Id: I8ffa226fda4365ee7068ac1f464473f1a4ebbb29
+
+2016-01-09 13:28 +0000 [a5406b1f9e]  Corey Farrell <git at cfware.com>
+
+	* res_crypto: Perform cleanup at shutdown.
+
+	  This change causes res_crypto to unregister CLI at shutdown while still
+	  preventing the module from being unloaded.
+
+	  ASTERISK-25673 #close
+
+	  Change-Id: Ie5d57338dc2752abfc0dd05d0eec86413f2304fc
+
+2016-01-06 19:10 +0000 [cf8e7a580b]  Richard Mudgett <rmudgett at digium.com>
+
+	* res_pjsip: Create human friendly serializer names.
+
+	  PJSIP name formats:
+	  pjsip/aor/<aor>-<seq> -- registrar thread pool serializer
+	  pjsip/default-<seq> -- default thread pool serializer
+	  pjsip/messaging -- messaging thread pool serializer
+	  pjsip/outreg/<registration>-<seq> -- outbound registration thread pool
+	  serializer
+	  pjsip/pubsub/<endpoint>-<seq> -- pubsub thread pool serializer
+	  pjsip/refer/<endpoint>-<seq> -- REFER thread pool serializer
+	  pjsip/session/<endpoint>-<seq> -- session thread pool serializer
+	  pjsip/websocket-<seq> -- websocket thread pool serializer
+
+	  Change-Id: Iff9df8da3ddae1132cb2ef65f64df0c465c5e084
+
+2016-01-06 19:09 +0000 [4276f185f0]  Richard Mudgett <rmudgett at digium.com>
+
+	* Sorcery: Create human friendly serializer names.
+
+	  Sorcery name formats:
+	  sorcery/<type>-<seq> -- Sorcery thread pool serializer
+
+	  Change-Id: Idc2e5d3dbab15c825b97c38c028319a0d2315c47
+
+2016-01-06 19:09 +0000 [f02ac1b7f9]  Richard Mudgett <rmudgett at digium.com>
+
+	* Stasis: Create human friendly taskprocessor/serializer names.
+
+	  Stasis name formats:
+	  subm:<topic>-<seq> -- Stasis subscription mailbox task processor
+	  subp:<topic>-<seq> -- Stasis subscription thread pool serializer
+
+	  Change-Id: Id19234b306e3594530bb040bc95d977f18ac7bfd
+
+2016-01-07 16:15 +0000 [ec1f1c6742]  Richard Mudgett <rmudgett at digium.com>
+
+	* taskprocessor.c: New API for human friendly taskprocessor names.
+
+	  * Add new API call to get a sequence number for use in human friendly
+	  taskprocessor names.
+
+	  * Add new API call to create a taskprocessor name in a given buffer and
+	  append a sequence number.
+
+	  Change-Id: Iac458f05b45232315ed64aa31b1df05b875537a9
+
+2016-01-06 17:19 +0000 [d8bc3e0c8b]  Richard Mudgett <rmudgett at digium.com>
+
+	* taskprocessor.c: Fix CLI "core show taskprocessors" output format.
 
-2015-12-18 12:18 +0000 [8e201b742a]  Kevin Harwell <kharwell at lunkwill.digium.internal>
+	  Update the CLI "core show taskprocessors" output format to not be
+	  distorted because UUID names are longer than previously used taskprocessor
+	  names.
 
-	* Release summaries: Remove previous versions
+	  Change-Id: I1a5c82ce3e8f765a0627796aba87f8f7be077601
 
-2015-12-18 12:18 +0000 [5a164c70f2]  Kevin Harwell <kharwell at lunkwill>
+2016-01-07 21:07 +0000 [2c4b7502de]  Richard Mudgett <rmudgett at digium.com>
 
-	* .version: Update for 13.7.0-rc2
+	* taskprocessor.c: Fix CLI "core show taskprocessors" unref.
 
-2015-12-18 12:18 +0000 [e039eca0a7]  Kevin Harwell <kharwell at lunkwill>
+	  Change-Id: I1d9f4e532caa6dfabe034745dd16d06134efdce5
 
-	* .lastclean: Update for 13.7.0-rc2
+2016-01-07 20:44 +0000 [3b33ac7a46]  Richard Mudgett <rmudgett at digium.com>
 
-2015-12-18 12:18 +0000 [bfe2eb8751]  Kevin Harwell <kharwell at lunkwill>
+	* taskprocessor.c: Sort CLI "core show taskprocessors" output.
 
-	* realtime: Add database scripts for 13.7.0-rc2
+	  Change-Id: I71e7bf57c7b908c8b8c71f1816348ed7c5a5d51e
 
-2015-12-16 11:25 +0000 [805297783d]  Mark Michelson <mmichelson at digium.com>
+2016-01-06 19:00 +0000 [0fc32c4dd3]  Richard Mudgett <rmudgett at digium.com>
 
-	* Alembic: Add PJSIP global keep_alive_interval.
+	* ccss.c: Replace space in taskprocessor name.
 
-	  The keep_alive_interval option was added about a year ago, but no
-	  alembic revision was created to add the appropriate column to the
-	  database.
+	  The CLI "core ping taskprocessor" command does not work very
+	  well with taskprocessor names that have spaces in them.  You
+	  have to put quotes around the name so using tab completion
+	  becomes awkward.
 
-	  This commit fixes the problem and adds the column. This was discovered
-	  by running the testsuite with automatic conversion to realtime enabled.
+	  Change-Id: I29e806dd0a8a0256f4e2e0a7ab88c9e19ab0eda0
 
-	  Change-Id: If3ef92a7c4f4844d08f8aae170d2178aec5c4c1a
+2016-01-05 16:54 +0000 [0e0c24ad78]  Richard Mudgett <rmudgett at digium.com>
 
-2015-12-16 11:28 +0000 [63df9bb560]  Mark Michelson <mmichelson at digium.com>
+	* taskprocessor.c: Add CLI "core ping taskprocessor" missing unlock.
 
-	* Alembic: Increase column size of PJSIP AOR "contact".
+	  Change-Id: I78247e0faf978bf850b5ba4e9f4933ab3c59d17b
 
-	  When running the PJSIP AMI "show_endpoint" test with automatic
-	  conversion to realtime, the test would fail. This was because the AOR
-	  "contact" column was sized at 40, and the configured contact was larger
-	  than that.
+2016-01-07 03:33 +0000 [0f79c8839b]  Diederik de Groot <ddegroot at talon.nl>
 
-	  This commit increases the size of the contact column to 255 characters.
+	* main: Use ast_strdup instead of strdup
 
-	  Change-Id: Ia65bc7fd37699b7c0eaef9629a1a31eab9a24ba1
+	  Fix compile error in main/utils.c because strdup was used in dummy_start
 
-2015-12-14 12:04 +0000 [acd19d5f1f]  Joshua Colp <jcolp at digium.com>
+	  Change-Id: Id61a6cf4f3cbf235450441e10e7da101a6335793
 
-	* json: Audit ast_json_* usage for thread safety.
+2016-01-07 03:21 +0000 [4285dee778]  Diederik de Groot <ddegroot at talon.nl>
 
-	  The JSON library Asterisk uses, jansson, is not thread
-	  safe for us in a few ways. To help with this wrappers for JSON
-	  object reference count increasing and decreasing were added
-	  which use a global lock to ensure they don't clobber over
-	  each other. This does not extend to reference count manipulation
-	  within the jansson library itself. This means you can't safely
-	  use the object borrowing specifier (O) in ast_json_pack and
-	  you can't share JSON instances between objects.
+	* include/asterisk/time.h: Renamed global declaration:tv
 
-	  This change removes uses of the O specifier and replaces them
-	  with the o specifier and an explicit ast_json_ref. Some cases
-	  of instance sharing have also been removed.
+	  Renamed global declaration:tv to dummy_tv_var_for_types,
+	  which would oltherwise cause 'shadow' warnings when 'tv'
+	  was declared as a local variable elsewhere.
 
-	  ASTERISK-25601 #close
+	  Added comment to note that dummy_tv_var_for_types is never
+	  really exported and only used as a place holder.
 
-	  Change-Id: I06550d8b0cc1bfeb56cab580a4e608ae4f1ec7d1
+	  ASTERISK-25627 #close
+
+	  Change-Id: I9a6e17995006584f3627efe8988e3f8aa0f5dc28
+
+2016-01-07 15:37 +0000 [96094feab6]  Mark Michelson <mmichelson at digium.com>
+
+	* PJSIP: Prevent deadlock due to dialog/transaction lock inversion.
+
+	  A deadlock was observed where the monitor thread was stuck, therefore
+	  resulting in no incoming SIP traffic being processed.
+
+	  The problem occurred when two 200 OK responses arrived in response to a
+	  terminating NOTIFY request sent from Asterisk. The first 200 OK was
+	  dispatched to a threadpool worker, who locked the corresponding
+	  transaction. The second 200 OK arrived, resulting in the monitor thread
+	  locking the dialog. At this point, the two threads are at odds, because
+	  the monitor thread attempts to lock the transaction, and the threadpool
+	  thread loops attempting to try to lock the dialog.
+
+	  In this case, the fix is to not have the monitor thread attempt to hold
+	  both the dialog and transaction locks at the same time. Instead, we
+	  release the dialog lock before attempting to lock the transaction.
+
+	  There have also been some debug messages added to the process in an
+	  attempt to make it more clear what is going on in the process.
+
+	  ASTERISK-25668 #close
+	  Reported by Mark Michelson
+
+	  Change-Id: I4db0705f1403737b4360e33a8e6276805d086d4a
+
+2016-01-07 09:39 +0000 [52e9de0016]  Corey Farrell <git at cfware.com>
+
+	* ast_format_cap_append_by_type: Resolve codec reference leak.
+
+	  This resolves a reference leak caused by ASTERISK-25535.  The pointer
+	  returned by ast_format_get_codec is saved so it can be released.
+
+	  ASTERISK-25664 #close
+
+	  Change-Id: If9941b1bf4320b2c59056546d6bce9422726d1ec
+
+2016-01-04 04:26 +0000 [86eae38d7e]  Aaron An <anjb at ti-net.com.cn>
+
+	* cel/cel_radius: Fix wrong pointer.
+
+	  The macro ADD_VENDOR_CODE defined in the cel_radius.c should use the parameter
+	  y not the address of y.
+
+	  I capture the radius UDP packet via tcpdump, and the AV pairs are not correct,
+	  then i review the source code and compare it with cdr/cdr_radius.c. Fix it and
+	   it works.
+
+	  ASTERISK-25647 #close
+	  Reported by: Aaron An
+	  Tested by: Aaron An
+
+	  Change-Id: I72889bccd8fde120d47aa659edc0e7e6d4d019f0
+
+2016-01-05 14:52 +0000 [881dc862e0]  gtjoseph <george.joseph at fairview5.com>
+
+	* asterisk.h: Add ASTERISK_REGISTER_FILE macro
+
+	  The 11/13 branches and master use 2 different file version macros. 11/13
+	  uses ASTERISK_FILE_VERSION but master uses ASTERISK_REGISTER_FILE. This
+	  means a new file added to 11/13 can't just be cherry-picked to master
+	  because the macro has to be changed.
+
+	  To make cherry-picking possible, ASTERISK_REGISTER_FILE was added
+	  to asterisk.h as a simple alias for ASTERISK_FILE_VERSION(__FILE__, NULL)
+	  The "$Revision$" tag doesn't do anything since Asterisk moved to git so
+	  just passing NULL as the verison works fine.  asterisk.h was also
+	  annotated to deprecate ASTERISK_FILE_VERSION and suggest using
+	  ASTERISK_REGISTER_FILE for all new files.
+
+	  Finally, 2 recent file additions, pbx_builtins.c and pbx_functions.c,
+	  were modified to use the new macro to make sure it actually worked.
+	  'core show file version' showed the correct output.
+
+	  Change-Id: I5867ed898818d26ee49bb6e5c7d4c1a45d4789a5
+
+2016-01-05 11:06 +0000 [d228b62fd4]  gtjoseph <george.joseph at fairview5.com>
+
+	* stasis_cache_pattern:  Backport to 13
+
+	  Somehow stasis_cache_pattern got out of sync between 13 and master
+	  and it was causing duplicate channel message issues in 13 when
+	  related to a specific endpoint. I.E. from statsd,
+	  'endpoints.PJSIP.1174.channels 0|g' was being emitted twice.
+
+	  Backporting stasis_cache_pattern from master to 13 solved
+	  the issue and running the unit and testsuite tests confirmed
+	  that no new ones were created.
+
+	  ASTERISK-25317 #close
+
+	  Change-Id: Ia8707462f62d15eed14541c37f332a7bbbceb548
+2016-01-04 20:23 +0000 [e462f0063f]  Corey Farrell <git at cfware.com>
+
+	* main/pbx: Move hangup handler routines to pbx_hangup_handler.c.
+
+	  This is the sixth patch in a series meant to reduce the bulk of pbx.c.
+	  This moves hangup handler management functions to their own source.
+
+	  Change-Id: Ib25a75aa57fc7d5c4294479e5cc46775912fb104
+
+2016-01-04 19:46 +0000 [ab191d124c]  Corey Farrell <git at cfware.com>
+
+	* main/pbx: Move dialplan application management routines to pbx_app.c.
+
+	  This is the sixth patch in a series meant to reduce the bulk of pbx.c.
+	  This moves dialplan application management functions to their own source.
+
+	  Change-Id: I444c10fb90a3cdf9f3047605d6a8aad49c22c44c
+
+2016-01-04 18:20 +0000 [09a9b93896]  Corey Farrell <git at cfware.com>
+
+	* main/pbx: Move switch routines to pbx_switch.c.
+
+	  This is the fifth patch in a series meant to reduce the bulk of pbx.c.
+	  This moves ast_switch functions to their own source.
+
+	  Change-Id: Ic2592a18a5c4d8a3c2dcf9786c9a6f650a8c628e
+
+2016-01-04 18:00 +0000 [c608274a39]  Corey Farrell <git at cfware.com>
+
+	* main/pbx: Move timing routines to pbx_timing.c.
+
+	  This is the fourth patch in a series meant to reduce the bulk of pbx.c.
+	  This moves pbx timing functions to their own source.
+
+	  Change-Id: I05c45186cb11edfc901e95f6be4e6a8abf129cd6
+
+2015-12-29 04:31 +0000 [338a8ffed6]  Martin Tomec <tomec.martin at gmail.com>
+
+	* app_queue: Add member flag "in_call" to prevent reading wrong lastcall time
+
+	  Member lastcall time is updated later than member status. There was chance to
+	  check wrapuptime for available member with wrong (old) lastcall time.
+	  New boolean flag "in_call" is set to true right before connecting call, and
+	  reset to false after update of lastcall time. Members with "in_call" set to true
+	  are treat as unavailable.
+
+	  ASTERISK-19820 #close
+
+	  Change-Id: I1923230cf9859ee51563a8ed420a0628b4d2e500
+
+2015-12-28 17:23 +0000 [e13719bff1]  Rodrigo Ramírez Norambuena <a at rodrigoramirez.com>
+
+	* app_queue: Added reason pause of member
+
+	  In app_queue added value Paused Reason on QueueMemberStatus when a member
+	  on queue is paused and the reason was set.
+
+	  ASTERISK-25480 #close
+	  Reporte by: Rodrigo Ramírez Norambuena
+
+	  Change-Id: Ia5db503482f50764c15e2020196c785f59d4a68e
+
+2015-12-30 10:49 +0000 [4ec85a9f07]  gtjoseph <george.joseph at fairview5.com>
+
+	* voicemail: Move app_voicemail / res_mwi_external conflict to runtime
+
+	  The menuselect conflict between app_voicemail and res_mwi_external
+	  makes it hard to package 1 version of Asterisk.  There no actual
+	  build dependencies between the 2 so moving this check to runtime
+	  seems like a better solution.
+
+	  The ast_vm_register and ast_vm_greeter_register functions in app.c
+	  were modified to return AST_MODULE_LOAD_DECLINE instead of -1 if there
+	  is already a voicemail module registered. The modules' load_module
+	  functions were then modified to return DECLINE instead of -1 to the
+	  loader.  Since -1 is interpreted by the loader as AST_MODULE_LOAD_FAILURE,
+	  the modules were incorrectly causing Asterisk to stop so this needed
+	  to be cleaned up anyway.
+
+	  Now you can build both and use modules.conf to decide which voicemail
+	  implementation to load.
+
+	  The default menuselect options still build app_voicemail and not
+	  res_mwi_external but if both ARE built, res_mwi_external will load
+	  first and become the voicemail provider unless modules.conf rules
+	  prevent it.  This is noted in CHANGES.
+
+	  Change-Id: I7d98d4e8a3b87b8df9e51c2608f0da6ddfb89247
+
+2016-01-04 16:22 +0000 [7fdcfd7724]  Corey Farrell <git at cfware.com>
+
+	* main/pbx: Move variable routines to pbx_variables.c.
+
+	  This is the third patch in a series meant to reduce the bulk of pbx.c.
+	  This moves channel and global variable routines to their own source.
+
+	  Change-Id: Ibe8fb4647db11598591d443a99e3f99200a56bc6
+
+2015-12-04 17:22 +0000 [80a8b2a4cd]  Richard Mudgett <rmudgett at digium.com>
+
+	* app_dial: Immediately exit dial if the caller is already hung up.
+
+	  If a caller hangs up before dial is executed within an AGI then the AGI
+	  has likely eaten all queued frames before executing the dial in DeadAGI
+	  mode.  With the caller hung up and no pending frames from the caller's
+	  read queue, dial would not know that the call has hung up until a called
+	  channel answers.  It is rather annoying to whoever just answered the
+	  non-existent call.
+
+	  Dial should not continue execution in DeadAGI mode, hangup handlers, or
+	  the h exten.
+
+	  * Added a check early in dial to abort dialing if the caller has hungup.
+
+	  ASTERISK-25307 #close
+	  Reported by: David Cunningham
+
+	  Change-Id: Icd1bc0764726ef8c809f76743ca008d0f102f418
+
+2016-01-02 10:26 +0000 [1087b0c6ed]  Matt Jordan <mjordan at digium.com>
+
+	* main/cdr: Allow setting properties on a finalized CDR if it is the last one
+
+	  Prior to this patch, we explicitly disallowed setting any properties on a
+	  finalized CDR. This seemed like a good idea at the time; in practice, it was
+	  more restrictive.
+
+	  There are weird and strange scenarios where setting a property on a finalized
+	  CDR is definitely wrong. For example, we may Fork a CDR, finalizing the
+	  previous one, then change a property. In said case, the old CDR is supposed
+	  to now be 'immutable' (so to speak), and should not be updated. From the
+	  perspective of the code, a forked CDR that is finalized is just finalized.
+	  Hence why we decided these should not be updated.
+
+	  In practice, it is much more common to want to set a property on a CDR in
+	  the h extension or in a hangup handler. Disallowing a common scenario to make
+	  an esoteric behaviour work isn't good. This patch fixes this by allowing
+	  callers to set a property IF we are the last CDR in the chain. This preserves
+	  the finalized CDR if it was forked, while allowing the more common case to
+	  function.
+
+	  ASTERISK-25458 #close
+
+	  Change-Id: Icf3553c607b9f561152a41e6d8381d594ccdf4b9
+
+2016-01-02 10:23 +0000 [1f23e65b89]  Matt Jordan <mjordan at digium.com>
+
+	* main/cdr: Set the end time on a CDR if endbeforehexten is Yes
+
+	  Prior to this patch, the CDR engine attempted to set the end time on a CDR
+	  that was executing hangup logic and with endbeforehexten set to Yes by
+	  calling a function that inspects the properties on the Party A snapshot to
+	  determine if we are ready to set the end time. That always failed. This is
+	  because a Party A snapshot is not updated for CDRs that are executing hangup
+	  logic with endbeforehexten=Yes.
+
+	  Instead of calling a function that looks at the Party A snapshot, we just
+	  simply set the end time on the CDR. This is safe to call multiple times, and is
+	  safe to call at this point as we know that (a) we are executing hangup logic,
+	  and (b) we are supposed to set the end time at this point.
+
+	  ASTERISK-25458
+
+	  Change-Id: I0c27b493861f9c13c43addbbb21257f79047a3b3
+
+2015-12-30 20:51 +0000 [2ffade4574]  Corey Farrell <git at cfware.com>
+
+	* main/pbx: Move custom function routines to pbx_functions.c.
+
+	  This is the second patch in a series meant to reduce the bulk of pbx.c.
+	  This moves custom function management routines to their own source.
+
+	  Change-Id: I34a6190282f781cdbbd3ce9d3adeac3c3805e177
+
+2015-12-28 19:18 +0000 [20b8474f20]  gtjoseph <george.joseph at fairview5.com>
+
+	* main/pbx: Move pbx_builtin dialplan applications to pbx_builtins.c
+
+	  We joked about splitting pbx.c into multiple files but this first step was
+	  fairly easy.  All of the pbx_builtin dialplan applications have been moved
+	  into pbx_builtins.c and a new pbx_private.h file was added. load_pbx_builtins()
+	  is called by asterisk.c just after load_pbx().
+
+	  A few functions were renamed and are cross-exposed between the 2 source files.
+
+	  Change-Id: I87066be3dbf7f5822942ac1449d98cc43fc7561a
 
-2015-12-05 10:01 +0000 [11ff4e6b3f]  Joshua Colp <jcolp at digium.com>
+2015-12-24 20:26 +0000 [e4a566918a]  Matt Jordan <mjordan at digium.com>
+
+	* tests/test_stasis_endpoints: Remove expected duplicate events
+
+	  The cache_clear test was written to expect duplicate Stasis messages
+	  sent from the technology endpoint to the all caching topic. This patch
+	  fixes the test to no longer expect these duplicate messages.
+
+	  ASTERISK-25137
+
+	  Change-Id: I58075d70d6cdf42e792e0fb63ba624720bfce981
+
+2015-12-28 14:02 +0000 [a280400758]  Joshua Colp <jcolp at digium.com>
+
+	* test_time: Provide a timeout when waiting.
+
+	  The test_timezone_watch unit test is written to expect a
+	  condition to be signaled when the inotify daemon thread runs.
+	  There exists a small window where the test_timezone_watch
+	  thread can signal the inotify daemon thread while it is not
+	  reading on the underlying file descriptor. If this occurs
+	  the test_timezone_watch thread will wait indefinitely for a
+	  signal that will never arrive.
+
+	  This change adds a timeout to the condition so it will return
+	  regardless after a period of time.
+
+	  Change-Id: Ifed981879df6de3d93acd3ee0a70f92546517390
+
+2015-05-27 13:22 +0000 [3a1c4885be]  gtjoseph <george.joseph at fairview5.com>
+
+	* endpoint/stasis: Eliminate duplicate events on endpoint status change
+
+	  When an endpoint is created, its messages are forwarded to both the tech
+	  endpoint topic and the all endpoints topic. This is done so that various
+	  parties interested in endpoint messages can subscribe to just the tech
+	  endpoint and receive all messages associated with that particular technology,
+	  as opposed to subscribing to the all endpoints topic. Unfortunately, when the
+	  tech endpoint is created, it also forwards all of its messages to the all
+	  topic. This results in duplicate messages whenever an endpoint publishes its
+	  messages.
+
+	  This patch resolves the duplicate message issue by creating a new function
+	  for Stasis caching topics, stasis_cp_sink_create. In most respects, this acts
+	  as a normal caching topic, save that it no longer forwards messages it receives
+	  to the all endpoints topic. This allows it to act as an aggregation "sink",
+	  while preserving the necessary caching behaviour.
+
+	  ASTERISK-25137 #close
+	  Reported-by: Vitezslav Novy
+
+	  ASTERISK-25116 #close
+	  Reported-by: George Joseph <george.joseph at fairview5.com>
+	  Tested-by: George Joseph <george.joseph at fairview5.com>
+
+	  Change-Id: Ie47784adfb973ab0063e59fc18f390d7dd26d17b
+2015-12-24 22:19 +0000 [136c537695]  Dade Brandon <dade at xencall.com>
+
+	* res_http_websocket.c: prevent avoidable disconnections caused by write errors
+
+	  Updated ast_websocket_write to encode the entire frame in to one
+	  write operation, to ensure that we don't end up with a situation
+	  where the websocket header has been sent, while the body can not
+	  be written.
+
+	  Previous to August's patch in commit b9bd3c14, certain network
+	  conditions could cause the header to be written, and then the
+	  sub-sequent body to fail - which would cause the next successful
+	  write to contain a new header, and a new body (resulting in
+	  the peer receiving two headers - the second of which would be
+	  read as part of the body for the first header).
+
+	  This was patched to have both write operations individually fail
+	  by closing the websocket.
+
+	  In a case available to the submitter of this patch, the same
+	  body which would consistently fail to write, would succeed
+	  if written at the same time as the header.
+
+	  This update merges the two operations in to one, adds debug messages
+	  indicating the reason for a websocket connection being closed during
+	  a write operation, and clarifies some variable names for code legibility.
+
+	  Change-Id: I4db7a586af1c7a57184c31d3d55bf146f1a40598
+
+2015-12-27 22:38 +0000 [f2efbb5d75]  Corey Farrell <git at cfware.com>
+
+	* Remove res_jabber file that was left behind.
+
+	  Change-Id: I9d88fac0394d5bbaff0900a2ee911c4e4478846b
+
+2015-12-13 13:09 +0000 [dde7f3c1c4]  Matt Jordan <mjordan at digium.com>
+
+	* res_pjsip_history: Add a module that provides PJSIP history for debugging
+
+	  This patch adds a new module, res_pjsip_history, that provides a slightly
+	  better way of debugging SIP message traffic on a busy Asterisk system. The
+	  existing mechanisms all rely on passively dumping a SIP message to the CLI.
+	  While this is perfectly fine for logging purposes and well controlled
+	  environments, on many installations, the amount of SIP messages Asterisk
+	  receives will quickly swamp the CLI. This makes it difficult to view/capture
+	  those messages that you want to diagnose in real time.
+
+	  This patch provides another way of handling this. When enabled, the module
+	  will store SIP message traffic in memory. This traffic can then be queried
+	  at leisure.
+
+	  In order to make the querying useful, a CLI command has been implemented,
+	  'pjsip show history', that supports a basic expression syntax similar to
+	  SQL or other query languages. A small number of useful fields have been
+	  added in this initial patch; additional fields can easily be added in
+	  later improvements. Those fields are:
+	   - number: The entry index in the history
+	   - timestamp: The time the message was recieved
+	   - addr: The source/destination address of the message
+	   - sip.msg.request.method: The request method
+	   - sip.msg.call-id: The Call-ID header
+
+	  Note - this is a resurrection of the module initially proposed on Review Board
+	  here: https://reviewboard.asterisk.org/r/4053/
+
+	  Change-Id: I39bd74ce998e99ad5ebc0aab3e84df3a150f8e36
+
+2015-12-25 09:56 +0000 [be050f2638]  Dade Brandon <dade at xencall.com>
+
+	* chan_sip.c: fix websocket_write_timeout default value
+
+	  websocket_write_timeout was not being set to its default value
+	  during sip config reload, which meant that prior to this commit,
+	  1) the default value of 100 was not used, unless an invalid value
+	  (or 1) was specified in sip.conf for websocket_write_timeout, and
+	  2) if the websocket_write_timeout directive was removed from sip.conf
+	  without a full restart of asterisk, then the previous value would
+	  continue to be used indefinitely.
+
+	  This essentially lead to a 0ms write timeout (the first write attempt
+	  in ast_careful_fwrite must have succeeded) in websocket write requests
+	  from chan_sip, unless websocket_write_timeout was explicitely set in sip.conf.
+
+	  Changes to websocket_write_timeout still only apply to new websocket
+	  sessions, after the sip reload -- timeouts on existing sessions are
+	  not adjusted during sip reload.
+
+	  Change-Id: Ibed3816ed29cc354af6564c5ab3e75eab72cb953
+
+2015-12-23 17:40 +0000 [b3024cad10]  Richard Mudgett <rmudgett at digium.com>
+
+	* bridge_basic.c: Fix GOTO_ON_BLINDXFR
+
+	  Use of GOTO_ON_BLINDXFR would not work at all.  The target location would
+	  never be executed by the transferring channel.
+
+	  * Made feature_blind_transfer() call ast_bridge_set_after_go_on() with
+	  valid context, exten, and priority parameters from the transferring
+	  channel.
+
+	  * Renamed some feature_blind_transfer() local variables for clarity.
+
+	  ASTERISK-25641 #close
+	  Reported by Dmitry Melekhov
+
+	  Change-Id: I19bead9ffdc4aee8d58c654ca05a198da1e4b7ac
+
+2015-12-24 12:19 +0000 [0a9941de9d]  Matt Jordan <mjordan at digium.com>
+
+	* res/res_pjsip_location: Delete contact_status object when contact is deleted
+
+	  In 450579e908, a change was made that removed the deletion of the
+	  'contact_status' object when a 'contact' object is deleted in sorcery.
+	  This unfortunately means that the 'contact_status' object persists, even when
+	  something has explicitly removed a contact. The result is that the state of
+	  the contact will not be regenerated if that contact is re-created, and the
+	  stale state will be reported/used for that contact. It also results in
+	  no ContactStatusChanged events being generated for either ARI or AMI.
+
+	  This patch restores the deletion logic that was removed. Doing so now
+	  results in the expected events being generated again.
+
+	  Change-Id: I28789a112e845072308b5b34522690e3faf58f07
+
+2015-12-24 10:18 +0000 [1e24a0ca8a]  Kevin Harwell <kharwell at digium.com>
+
+	* res_rtp_asterisk: rtp->ice check not wrapped in HAVE_PJPROJECT ifdef
+
+	  Change-Id: I19b49112e1b630bd04e859f14ccf96f8ebd6b151
+
+2015-12-20 21:33 +0000 [1d3d20dd68]  Dade Brandon <dade at xencall.com>
+
+	* app_amd: Correct documentation to reflect functionality
+
+	  Update documentation to reflect that maximum_number_of_words
+	  has functionality inconsistent with the variable name (and inconsistent
+	  with prior documentation.)
+
+	  Update documentation for silence_threshold, which previously implied
+	  that it was measuring time, rather than noise averages in the sample.
+
+	  Update the comments in amd.conf.sample.
+
+	  ASTERISK-25639 #close
+	  Change-Id: I4b1451e5dc9cb3cb06d59b6ab872f5275ba79093
+
+2015-12-17 19:05 +0000 [965a0eee46]  Dade Brandon <dade at xencall.com>
+
+	* res_rtp_asterisk: Resolve further timing issues with DTLS negotiation
+
+	  Resolves an edge case dtls negotiation delay for certain networks which
+	  somehow manage to drop the rtcp side's packet when these are both sent
+	  ast_rtp_remote_address_set, causing it to have to time-out and restart
+	  the handshake.
+
+	  Move dtls pending bio flush in to it's own function, and call it from
+	  ast_rtp_on_ice_complete, when we're rtp->ice, rather than when
+	  ast_rtp_remote_address_set.
+
+	  Keep the existing flush from the recent change to res_rtp_remote_address_set
+	  if ice is not being used.
+
+	  ASTERISK-25614 #close
+	  Reported-by: XenCALL
+	  Tested by: XenCALL
+
+	  Change-Id: Ie2caedbdee1783159f375589b6fd3845c8577ba5
+
+2015-12-18 09:54 +0000 [ae428d8460]  Carlos Oliva <carlos.oliva at invoxcontact.com>
+
+	* app_queue: update RT members when the 1st call joins a queue with no agents
+
+	  If a call enters on a queue and the members on that queue are updated in
+	  realtime (ex: using mysql inserting a new agent) the queue members are
+	  never refreshed and the call will stay in the queue until other event occurs.
+	  This happens only if this is the first call of the queue and there is no
+	  agents servicing.
+	  This patch prevent this issue, ensuring realtime members are updated if
+	  there is one call in the queue and no available agents
+
+	  ASTERISK-25442 #close
+
+	  Change-Id: If1e036d013a5c1d8b0bf60d71d48fe98694a8682
+
+2015-12-05 10:01 +0000 [59d5bb0613]  Joshua Colp <jcolp at digium.com>
 
 	* res_sorcery_memory_cache: Add support for a full backend cache.
 
@@ -82,9 +3326,8 @@
 	  ASTERISK-25625 #close
 
 	  Change-Id: Ie2993487e9c19de563413ad5561c7403b48caab5
-	  (cherry picked from commit 59d5bb0613810418f2a618b9a6dee5bcfd45767e)
 
-2015-12-17 10:25 +0000 [9bc1e49325]  Joshua Colp <jcolp at digium.com>
+2015-12-17 10:25 +0000 [0cefcabd58]  Joshua Colp <jcolp at digium.com>
 
 	* rtp_engine: Ignore empty filenames in DTLS configuration.
 
@@ -94,7 +3337,7 @@
 
 	  Change-Id: Ib761dc235638a3fb701df337952f831fc3e69539
 
-2015-12-17 08:10 +0000 [c78eb1e82b]  Joshua Colp <jcolp at digium.com>
+2015-12-17 08:10 +0000 [158a0a5422]  Joshua Colp <jcolp at digium.com>
 
 	* chan_sip: Enable WebSocket support by default.
 
@@ -104,6 +3347,66 @@
 
 	  Change-Id: Icb02bbcad47b11a795c14ce20a9bf29649a54423
 
+2015-12-14 12:04 +0000 [a9d6fc571d]  Joshua Colp <jcolp at digium.com>
+
+	* json: Audit ast_json_* usage for thread safety.
+
+	  The JSON library Asterisk uses, jansson, is not thread
+	  safe for us in a few ways. To help with this wrappers for JSON
+	  object reference count increasing and decreasing were added
+	  which use a global lock to ensure they don't clobber over
+	  each other. This does not extend to reference count manipulation
+	  within the jansson library itself. This means you can't safely
+	  use the object borrowing specifier (O) in ast_json_pack and
+	  you can't share JSON instances between objects.
+
+	  This change removes uses of the O specifier and replaces them
+	  with the o specifier and an explicit ast_json_ref. Some cases
+	  of instance sharing have also been removed.
+
+	  ASTERISK-25601 #close
+
+	  Change-Id: I06550d8b0cc1bfeb56cab580a4e608ae4f1ec7d1
+
+2015-12-16 11:28 +0000 [53bd5a539a]  Mark Michelson <mmichelson at digium.com>
+
+	* Alembic: Increase column size of PJSIP AOR "contact".
+
+	  When running the PJSIP AMI "show_endpoint" test with automatic
+	  conversion to realtime, the test would fail. This was because the AOR
+	  "contact" column was sized at 40, and the configured contact was larger
+	  than that.
+
+	  This commit increases the size of the contact column to 255 characters.
+
+	  Change-Id: Ia65bc7fd37699b7c0eaef9629a1a31eab9a24ba1
+
+2015-12-16 11:25 +0000 [da17dc4d75]  Mark Michelson <mmichelson at digium.com>
+
+	* Alembic: Add PJSIP global keep_alive_interval.
+
+	  The keep_alive_interval option was added about a year ago, but no
+	  alembic revision was created to add the appropriate column to the
+	  database.
+
+	  This commit fixes the problem and adds the column. This was discovered
+	  by running the testsuite with automatic conversion to realtime enabled.
+
+	  Change-Id: If3ef92a7c4f4844d08f8aae170d2178aec5c4c1a
+
+2015-12-08 13:04 +0000 [fe8011cc50]  sungtae kim <pchero21 at gmail.com>
+
+	* AMI: Fixed OriginateResponse message
+
+	  When the asterisk sending OriginateResponse message,
+	  it doesn't set the "Uniqueid".
+	  And it didn't support correct response message for
+	  Application originate.
+
+	  ASTERISK-25624 #close
+
+	  Change-Id: I26f54f677ccfb0b7cfd4967a844a1657fd69b74d
+
 2015-12-15 18:01 +0000  Asterisk Development Team <asteriskteam at digium.com>
 
 	* asterisk 13.7.0-rc1 Released.
diff --git a/Makefile b/Makefile
index d4ae9fd..a717f4a 100644
--- a/Makefile
+++ b/Makefile
@@ -100,7 +100,10 @@ export LDCONFIG
 export LDCONFIG_FLAGS
 export PYTHON
 
--include makeopts
+# makeopts is required unless the goal is clean or distclean
+ifeq ($(findstring clean,$(MAKECMDGOALS)),)
+include makeopts
+endif
 
 # start the primary CFLAGS and LDFLAGS with any that were provided
 # to the configure script
@@ -247,7 +250,7 @@ endif
 
 _ASTCFLAGS+=$(OPTIONS)
 
-MOD_SUBDIRS:=channels pbx apps codecs formats cdr cel bridges funcs tests main res addons $(LOCAL_MOD_SUBDIRS)
+MOD_SUBDIRS:=third-party channels pbx apps codecs formats cdr cel bridges funcs tests main res addons $(LOCAL_MOD_SUBDIRS)
 OTHER_SUBDIRS:=utils agi contrib
 SUBDIRS:=$(OTHER_SUBDIRS) $(MOD_SUBDIRS)
 SUBDIRS_INSTALL:=$(SUBDIRS:%=%-install)
@@ -375,8 +378,10 @@ ifeq ($(findstring $(OSARCH), mingw32 cygwin ),)
     # directories containing them must be completed before the main Asterisk
     # binary can be built.
     # If MENUSELECT_EMBED is empty, we don't need this and allow 'main' to be
-    # be built without building all dependencies first.
+    # be built with only third_party first.
 main: $(filter-out main,$(MOD_SUBDIRS))
+  else
+main: third-party
   endif
 else
     # Windows: we need to build main (i.e. the asterisk dll) first,
@@ -566,7 +571,8 @@ INSTALLDIRS="$(ASTLIBDIR)" "$(ASTMODDIR)" "$(ASTSBINDIR)" "$(ASTETCDIR)" "$(ASTV
 	"$(ASTDATADIR)/documentation/thirdparty" "$(ASTDATADIR)/firmware" \
 	"$(ASTDATADIR)/firmware/iax" "$(ASTDATADIR)/images" "$(ASTDATADIR)/keys" \
 	"$(ASTDATADIR)/phoneprov" "$(ASTDATADIR)/rest-api" "$(ASTDATADIR)/static-http" \
-	"$(ASTDATADIR)/sounds" "$(ASTDATADIR)/moh" "$(ASTMANDIR)/man8" "$(AGI_DIR)" "$(ASTDBDIR)"
+	"$(ASTDATADIR)/sounds" "$(ASTDATADIR)/moh" "$(ASTMANDIR)/man8" "$(AGI_DIR)" "$(ASTDBDIR)" \
+	"$(ASTDATADIR)/third-party"
 
 installdirs:
 	@for i in $(INSTALLDIRS); do \
@@ -609,7 +615,7 @@ ifeq ($(HAVE_DAHDI),1)
 endif
 
 $(SUBDIRS_INSTALL):
-	+ at DESTDIR="$(DESTDIR)" ASTSBINDIR="$(ASTSBINDIR)" $(SUBMAKE) -C $(@:-install=) install
+	+ at DESTDIR="$(DESTDIR)" ASTSBINDIR="$(ASTSBINDIR)" ASTDATADIR="$(ASTDATADIR)" $(SUBMAKE) -C $(@:-install=) install
 
 NEWMODS:=$(foreach d,$(MOD_SUBDIRS),$(notdir $(wildcard $(d)/*.so)))
 OLDMODS=$(filter-out $(NEWMODS) $(notdir $(DESTDIR)$(ASTMODDIR)),$(notdir $(wildcard $(DESTDIR)$(ASTMODDIR)/*.so)))
@@ -887,7 +893,7 @@ sounds:
 	@[ -f "$(DESTDIR)$(ASTDBDIR)/astdb.sqlite3" ] || [ ! -f "$(DESTDIR)$(ASTDBDIR)/astdb" ] || [ ! -f menuselect.makeopts ] || grep -q MENUSELECT_UTILS=.*astdb2sqlite3 menuselect.makeopts || (sed -i.orig -e's/MENUSELECT_UTILS=\(.*\)/MENUSELECT_UTILS=\1 astdb2sqlite3/' menuselect.makeopts && echo "Updating menuselect.makeopts to include astdb2sqlite3" && echo "Original version backed up to menuselect.makeopts.orig")
 
 $(SUBDIRS_UNINSTALL):
-	+@$(SUBMAKE) -C $(@:-uninstall=) uninstall
+	+ at DESTDIR="$(DESTDIR)" ASTSBINDIR="$(ASTSBINDIR)" ASTDATADIR="$(ASTDATADIR)" $(SUBMAKE) -C $(@:-uninstall=) uninstall
 
 main-binuninstall:
 	+ at DESTDIR="$(DESTDIR)" ASTSBINDIR="$(ASTSBINDIR)" ASTLIBDIR="$(ASTLIBDIR)" $(SUBMAKE) -C main binuninstall
@@ -1010,6 +1016,10 @@ else
 		rest-api/resources.json .
 endif
 
+check-alembic: makeopts
+	@find contrib/ast-db-manage/ -name '*.pyc' -delete
+	@ALEMBIC=$(ALEMBIC) build_tools/make_check_alembic config cdr voicemail >&2
+
 .PHONY: menuselect
 .PHONY: main
 .PHONY: sounds
@@ -1031,6 +1041,7 @@ endif
 .PHONY: _clean
 .PHONY: ari-stubs
 .PHONY: basic-pbx
+.PHONY: check-alembic
 .PHONY: $(SUBDIRS_INSTALL)
 .PHONY: $(SUBDIRS_DIST_CLEAN)
 .PHONY: $(SUBDIRS_CLEAN)
diff --git a/Makefile.rules b/Makefile.rules
index 1031f2d..a22f19c 100644
--- a/Makefile.rules
+++ b/Makefile.rules
@@ -103,13 +103,15 @@ CC_LIBS=$(PTHREAD_LIBS) $(LIBS)
 CXX_LIBS=$(PTHREAD_LIBS) $(LIBS)
 
 # determine whether to double-compile so that the optimizer can report code path problems
-# this is only done when developer mode and DONT_OPTIMIZE are both enabled
-# in that case, we run the preprocessor to produce a .i or .ii file from the source
+# In this case, we run the preprocessor to produce a .i or .ii file from the source
 # code, then compile once with optimizer enabled (and the output to /dev/null),
 # and if that doesn't fail then compile again with optimizer disabled
-ifeq ($(findstring DONT_OPTIMIZE,$(MENUSELECT_CFLAGS))$(AST_DEVMODE),DONT_OPTIMIZEyes)
+
+ifeq ($(findstring COMPILE_DOUBLE,$(MENUSELECT_CFLAGS)),COMPILE_DOUBLE)
 COMPILE_DOUBLE=yes
-else
+endif
+
+ifeq ($(findstring DONT_OPTIMIZE,$(MENUSELECT_CFLAGS))$(AST_DEVMODE),)
 _ASTCFLAGS+=$(AST_FORTIFY_SOURCE)
 endif
 
diff --git a/UPGRADE.txt b/UPGRADE.txt
index 27ca0e6..471350c 100644
--- a/UPGRADE.txt
+++ b/UPGRADE.txt
@@ -21,6 +21,23 @@
 === UPGRADE-12.txt  -- Upgrade info for 11 to 12
 ===========================================================
 
+From 13.7.0 to 13.8.0:
+
+res_pjsip:
+ - res_pjsip now depends on res_pjproject.  If autoload=no in modules.conf,
+   res_pjproject must be explicitly loaded or res_pjsip and all of its
+   dependents will fail to load.
+
+REDIRECTING(reason):
+ - See the CHANGES file for a description of the behavior change.
+
+ODBC:
+ - Connection pooling/sharing has been completely removed from Asterisk
+   in favor of letting ODBC take care of it instead. It is strongly
+   recommended that you enable connection pooling in unixODBC. As a result
+   of this, the "pooling", "shared_connection", "limit", and "idlecheck"
+   options in res_odbc.conf are deprecated and provide no function.
+
 From 13.5.0 to 13.6.0:
 
 ARI:
diff --git a/addons/.gitignore b/addons/.gitignore
deleted file mode 100644
index 663e668..0000000
--- a/addons/.gitignore
+++ /dev/null
@@ -1 +0,0 @@
-mp3
diff --git a/addons/res_config_mysql.c b/addons/res_config_mysql.c
index 6e6224b..be5d002 100644
--- a/addons/res_config_mysql.c
+++ b/addons/res_config_mysql.c
@@ -1518,7 +1518,7 @@ static int load_mysql_config(struct ast_config *config, const char *category, st
 	ast_debug(1, "MySQL RealTime database name: %s\n", conn->name);
 	ast_debug(1, "MySQL RealTime user: %s\n", conn->user);
 	ast_debug(1, "MySQL RealTime password: %s\n", conn->pass);
-	if(conn->charset)
+	if(!ast_strlen_zero(conn->charset))
 		ast_debug(1, "MySQL RealTime charset: %s\n", conn->charset);
 
 	return 1;
@@ -1533,13 +1533,13 @@ static int mysql_reconnect(struct mysql_conn *conn)
 	/* mutex lock should have been locked before calling this function. */
 
 reconnect_tryagain:
-	if ((!conn->connected) && (!ast_strlen_zero(conn->host) || conn->sock) && !ast_strlen_zero(conn->user) && !ast_strlen_zero(conn->name)) {
+	if ((!conn->connected) && (!ast_strlen_zero(conn->host) || !ast_strlen_zero(conn->sock)) && !ast_strlen_zero(conn->user) && !ast_strlen_zero(conn->name)) {
 		if (!mysql_init(&conn->handle)) {
 			ast_log(LOG_WARNING, "MySQL RealTime: Insufficient memory to allocate MySQL resource.\n");
 			conn->connected = 0;
 			return 0;
 		}
-		if(conn->charset && strlen(conn->charset) > 2){
+		if(strlen(conn->charset) > 2){
 			char set_names[255];
 			char statement[512];
 			snprintf(set_names, sizeof(set_names), "SET NAMES %s", conn->charset);
diff --git a/agi/.gitignore b/agi/.gitignore
deleted file mode 100644
index 9b2a4e2..0000000
--- a/agi/.gitignore
+++ /dev/null
@@ -1,3 +0,0 @@
-eagi-sphinx-test
-eagi-test
-strcompat.c
diff --git a/apps/app_amd.c b/apps/app_amd.c
index 68d1008..9a2e5f3 100644
--- a/apps/app_amd.c
+++ b/apps/app_amd.c
@@ -62,19 +62,19 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
 		<syntax>
 			<parameter name="initialSilence" required="false">
 				<para>Is maximum initial silence duration before greeting.</para>
-				<para>If this is exceeded set as MACHINE</para>
+				<para>If this is exceeded, the result is detection as a MACHINE</para>
 			</parameter>
 			<parameter name="greeting" required="false">
 				<para>is the maximum length of a greeting.</para>
-				<para>If this is exceeded set as MACHINE</para>
+				<para>If this is exceeded, the result is detection as a MACHINE</para>
 			</parameter>
 			<parameter name="afterGreetingSilence" required="false">
 				<para>Is the silence after detecting a greeting.</para>
-				<para>If this is exceeded set as HUMAN</para>
+				<para>If this is exceeded, the result is detection as a HUMAN</para>
 			</parameter>
 			<parameter name="totalAnalysis Time" required="false">
 				<para>Is the maximum time allowed for the algorithm</para>
-				<para>to decide HUMAN or MACHINE</para>
+				<para>to decide on whether the audio represents a HUMAN, or a MACHINE</para>
 			</parameter>
 			<parameter name="miniumWordLength" required="false">
 				<para>Is the minimum duration of Voice considered to be a word</para>
@@ -85,14 +85,14 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
 			</parameter>
 			<parameter name="maximumNumberOfWords" required="false">
 				<para>Is the maximum number of words in a greeting</para>
-				<para>If this is exceeded set as MACHINE</para>
+				<para>If this is REACHED, then the result is detection as a MACHINE</para>
 			</parameter>
 			<parameter name="silenceThreshold" required="false">
-				<para>How long do we consider silence</para>
+				<para>What is the average level of noise from 0 to 32767 which if not exceeded, should be considered silence?</para>
 			</parameter>
 			<parameter name="maximumWordLength" required="false">
 				<para>Is the maximum duration of a word to accept.</para>
-				<para>If exceeded set as MACHINE</para>
+				<para>If exceeded, then the result is detection as a MACHINE</para>
 			</parameter>
 		</syntax>
 		<description>
@@ -130,7 +130,7 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
 					</value>
 					<value name="MAXWORDS">
 						Word Count - maximum number of words.
-					</value>	
+					</value>
 				</variable>
 			</variablelist>
 		</description>
diff --git a/apps/app_chanspy.c b/apps/app_chanspy.c
index 9080594..19675fb 100644
--- a/apps/app_chanspy.c
+++ b/apps/app_chanspy.c
@@ -598,12 +598,12 @@ static int attach_barge(struct ast_autochan *spyee_autochan,
 		return -1;
 	}
 
-	ast_channel_lock(internal_bridge_autochan->chan);
+	ast_autochan_channel_lock(internal_bridge_autochan);
 	if (start_spying(internal_bridge_autochan, spyer_name, bridge_whisper_audiohook)) {
 		ast_log(LOG_WARNING, "Unable to attach barge audiohook on spyee '%s'. Barge mode disabled.\n", name);
 		retval = -1;
 	}
-	ast_channel_unlock(internal_bridge_autochan->chan);
+	ast_autochan_channel_unlock(internal_bridge_autochan);
 
 	*spyee_bridge_autochan = internal_bridge_autochan;
 
@@ -632,9 +632,9 @@ static int channel_spy(struct ast_channel *chan, struct ast_autochan *spyee_auto
 	spyer_name = ast_strdupa(ast_channel_name(chan));
 	ast_channel_unlock(chan);
 
-	ast_channel_lock(spyee_autochan->chan);
+	ast_autochan_channel_lock(spyee_autochan);
 	name = ast_strdupa(ast_channel_name(spyee_autochan->chan));
-	ast_channel_unlock(spyee_autochan->chan);
+	ast_autochan_channel_unlock(spyee_autochan);
 
 	ast_verb(2, "Spying on channel %s\n", name);
 	publish_chanspy_message(chan, spyee_autochan->chan, 1);
diff --git a/apps/app_confbridge.c b/apps/app_confbridge.c
index 5976d39..55b7b12 100644
--- a/apps/app_confbridge.c
+++ b/apps/app_confbridge.c
@@ -130,32 +130,51 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
 	</application>
 	<function name="CONFBRIDGE" language="en_US">
 		<synopsis>
-			Set a custom dynamic bridge, user, or menu profile on a channel for the ConfBridge application using the same options defined in confbridge.conf.
+			Set a custom dynamic bridge, user, or menu profile on a channel for the
+			ConfBridge application using the same options available in confbridge.conf.
 		</synopsis>
 		<syntax>
 			<parameter name="type" required="true">
-				<para>Type refers to which type of profile the option belongs too.  Type can be <literal>bridge</literal>, <literal>user</literal>, or
-				<literal>menu</literal>.</para>
+				<para>To what type of conference profile the option applies.</para>
+				<enumlist>
+					<enum name="bridge"></enum>
+					<enum name="menu"></enum>
+					<enum name="user"></enum>
+				</enumlist>
 			</parameter>
 			<parameter name="option" required="true">
-				<para>Option refers to <filename>confbridge.conf</filename> option that is being set dynamically on this channel, or
-				<literal>clear</literal> to remove already applied options from the channel.</para>
+				<para>Option refers to a <filename>confbridge.conf</filename> option
+				that is being set dynamically on this channel, or <literal>clear</literal>
+				to remove already applied profile options from the channel.</para>
 			</parameter>
 		</syntax>
 		<description>
+			<para>A custom profile uses the default profile type settings defined in
+			<filename>confbridge.conf</filename> as defaults if the profile template
+			is not explicitly specified first.</para>
+			<para>For <literal>bridge</literal> profiles the default template is <literal>default_bridge</literal>.</para>
+			<para>For <literal>menu</literal> profiles the default template is <literal>default_menu</literal>.</para>
+			<para>For <literal>user</literal> profiles the default template is <literal>default_user</literal>.</para>
 			<para>---- Example 1 ----</para>
-			<para>In this example the custom set user profile on this channel will automatically be used by the ConfBridge app.</para>
-			<para>exten => 1,1,Answer() </para>
-			<para>exten => 1,n,Set(CONFBRIDGE(user,announce_join_leave)=yes)</para>
-			<para>exten => 1,n,Set(CONFBRIDGE(user,startmuted)=yes)</para>
-			<para>exten => 1,n,ConfBridge(1) </para>
+			<para>In this example the custom user profile set on the channel will
+			automatically be used by the ConfBridge application.</para>
+			<para>exten => 1,1,Answer()</para>
+			<para>; In this example the effect of the following line is</para>
+			<para>; implied:</para>
+			<para>; same => n,Set(CONFBRIDGE(user,template)=default_user)</para>
+			<para>same => n,Set(CONFBRIDGE(user,announce_join_leave)=yes)</para>
+			<para>same => n,Set(CONFBRIDGE(user,startmuted)=yes)</para>
+			<para>same => n,ConfBridge(1) </para>
 			<para>---- Example 2 ----</para>
-			<para>This example shows how to use a predefined user or bridge profile in confbridge.conf as a template for a dynamic profile. Here we make a admin/marked user out of the default_user profile that is already defined in confbridge.conf.</para>
-			<para>exten => 1,1,Answer() </para>
-			<para>exten => 1,n,Set(CONFBRIDGE(user,template)=default_user)</para>
-			<para>exten => 1,n,Set(CONFBRIDGE(user,admin)=yes)</para>
-			<para>exten => 1,n,Set(CONFBRIDGE(user,marked)=yes)</para>
-			<para>exten => 1,n,ConfBridge(1)</para>
+			<para>This example shows how to use a predefined user profile in
+			<filename>confbridge.conf</filename> as a template for a dynamic profile.
+			Here we make an admin/marked user out of the <literal>my_user</literal>
+			profile that you define in <filename>confbridge.conf</filename>.</para>
+			<para>exten => 1,1,Answer()</para>
+			<para>same => n,Set(CONFBRIDGE(user,template)=my_user)</para>
+			<para>same => n,Set(CONFBRIDGE(user,admin)=yes)</para>
+			<para>same => n,Set(CONFBRIDGE(user,marked)=yes)</para>
+			<para>same => n,ConfBridge(1)</para>
 		</description>
 	</function>
 	<function name="CONFBRIDGE_INFO" language="en_US">
@@ -164,14 +183,32 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
 		</synopsis>
 		<syntax>
 			<parameter name="type" required="true">
-				<para>Type can be <literal>parties</literal>, <literal>admins</literal>, <literal>marked</literal>, or <literal>locked</literal>.</para>
+				<para>What conference information is requested.</para>
+				<enumlist>
+					<enum name="admins">
+						<para>Get the number of admin users in the conference.</para>
+					</enum>
+					<enum name="locked">
+						<para>Determine if the conference is locked. (0 or 1)</para>
+					</enum>
+					<enum name="marked">
+						<para>Get the number of marked users in the conference.</para>
+					</enum>
+					<enum name="muted">
+						<para>Determine if the conference is muted. (0 or 1)</para>
+					</enum>
+					<enum name="parties">
+						<para>Get the number of users in the conference.</para>
+					</enum>
+				</enumlist>
 			</parameter>
 			<parameter name="conf" required="true">
-				<para>Conf refers to the name of the conference being referenced.</para>
+				<para>The name of the conference being referenced.</para>
 			</parameter>
 		</syntax>
 		<description>
-			<para>This function returns a non-negative integer for valid conference identifiers (0 or 1 for <literal>locked</literal>) and "" for invalid conference identifiers.</para>
+			<para>This function returns a non-negative integer for valid conference
+			names and an empty string for invalid conference names.</para>
 		</description>
 	</function>
 	<manager name="ConfbridgeList" language="en_US">
@@ -1045,10 +1082,35 @@ void conf_update_user_mute(struct confbridge_user *user)
 		"Conference: %s\r\n"
 		"Channel: %s",
 		mute_effective ? "muted" : "unmuted",
-		user->b_profile.name,
+		user->conference->b_profile.name,
 		ast_channel_name(user->chan));
 }
 
+/*
+ * \internal
+ * \brief Mute/unmute a single user.
+ */
+static void generic_mute_unmute_user(struct confbridge_conference *conference, struct confbridge_user *user, int mute)
+{
+	/* Set user level mute request. */
+	user->muted = mute ? 1 : 0;
+
+	conf_update_user_mute(user);
+	ast_test_suite_event_notify("CONF_MUTE",
+		"Message: participant %s %s\r\n"
+		"Conference: %s\r\n"
+		"Channel: %s",
+		ast_channel_name(user->chan),
+		mute ? "muted" : "unmuted",
+		conference->b_profile.name,
+		ast_channel_name(user->chan));
+	if (mute) {
+		send_mute_event(user, conference);
+	} else {
+		send_unmute_event(user, conference);
+	}
+}
+
 void conf_moh_stop(struct confbridge_user *user)
 {
 	user->playing_moh = 0;
@@ -1141,7 +1203,7 @@ int conf_handle_inactive_waitmarked(struct confbridge_user *user)
 {
 	/* If we have not been quieted play back that they are waiting for the leader */
 	if (!ast_test_flag(&user->u_profile, USER_OPT_QUIET) && play_prompt_to_user(user,
-			conf_get_sound(CONF_SOUND_WAIT_FOR_LEADER, user->b_profile.sounds))) {
+			conf_get_sound(CONF_SOUND_WAIT_FOR_LEADER, user->conference->b_profile.sounds))) {
 		/* user hungup while the sound was playing */
 		return -1;
 	}
@@ -1153,7 +1215,7 @@ int conf_handle_only_unmarked(struct confbridge_user *user)
 	/* If audio prompts have not been quieted or this prompt quieted play it on out */
 	if (!ast_test_flag(&user->u_profile, USER_OPT_QUIET | USER_OPT_NOONLYPERSON)) {
 		if (play_prompt_to_user(user,
-			conf_get_sound(CONF_SOUND_ONLY_PERSON, user->b_profile.sounds))) {
+			conf_get_sound(CONF_SOUND_ONLY_PERSON, user->conference->b_profile.sounds))) {
 			/* user hungup while the sound was playing */
 			return -1;
 		}
@@ -1227,11 +1289,11 @@ static struct confbridge_conference *join_conference_bridge(const char *conferen
 	/* When finding a conference bridge that already exists make sure that it is not locked, and if so that we are not an admin */
 	if (conference && (max_members_reached || conference->locked) && !ast_test_flag(&user->u_profile, USER_OPT_ADMIN)) {
 		ao2_unlock(conference_bridges);
-		ao2_ref(conference, -1);
 		ast_debug(1, "Conference '%s' is locked and caller is not an admin\n", conference_name);
 		ast_stream_and_wait(user->chan,
-				conf_get_sound(CONF_SOUND_LOCKED, user->b_profile.sounds),
-				"");
+			conf_get_sound(CONF_SOUND_LOCKED, conference->b_profile.sounds),
+			"");
+		ao2_ref(conference, -1);
 		return NULL;
 	}
 
@@ -1265,7 +1327,6 @@ static struct confbridge_conference *join_conference_bridge(const char *conferen
 			app, conference_name, NULL);
 		if (!conference->bridge) {
 			ao2_ref(conference, -1);
-			conference = NULL;
 			ao2_unlock(conference_bridges);
 			ast_log(LOG_ERROR, "Conference '%s' mixing bridge could not be created.\n", conference_name);
 			return NULL;
@@ -1283,7 +1344,6 @@ static struct confbridge_conference *join_conference_bridge(const char *conferen
 		/* Link it into the conference bridges container */
 		if (!ao2_link(conference_bridges, conference)) {
 			ao2_ref(conference, -1);
-			conference = NULL;
 			ao2_unlock(conference_bridges);
 			ast_log(LOG_ERROR,
 				"Conference '%s' could not be added to the conferences list.\n", conference_name);
@@ -1310,6 +1370,13 @@ static struct confbridge_conference *join_conference_bridge(const char *conferen
 
 	ao2_lock(conference);
 
+	/* Determine if the new user should join the conference muted. */
+	if (ast_test_flag(&user->u_profile, USER_OPT_STARTMUTED)
+		|| (!ast_test_flag(&user->u_profile, USER_OPT_ADMIN) && conference->muted)) {
+		/* Set user level mute request. */
+		user->muted = 1;
+	}
+
 	/*
 	 * Suspend any MOH until the user actually joins the bridge of
 	 * the conference.  This way any pre-join file playback does not
@@ -1321,6 +1388,7 @@ static struct confbridge_conference *join_conference_bridge(const char *conferen
 		/* Invalid event, nothing was done, so we don't want to process a leave. */
 		ao2_unlock(conference);
 		ao2_ref(conference, -1);
+		user->conference = NULL;
 		return NULL;
 	}
 
@@ -1512,7 +1580,12 @@ static int conf_get_pin(struct ast_channel *chan, struct confbridge_user *user)
 	const char *pin = user->u_profile.pin;
 	char *tmp = pin_guess;
 	int i, res;
-	unsigned int len = MAX_PIN ;
+	unsigned int len = MAX_PIN;
+
+	/*
+	 * NOTE: We have not joined a conference yet so we have to use
+	 * the bridge profile requested by the user.
+	 */
 
 	/* give them three tries to get the pin right */
 	for (i = 0; i < 3; i++) {
@@ -1727,12 +1800,6 @@ static int confbridge_exec(struct ast_channel *chan, const char *data)
 		}
 	}
 
-	/* If the caller should be joined already muted, set the flag before we join. */
-	if (ast_test_flag(&user.u_profile, USER_OPT_STARTMUTED)) {
-		/* Set user level mute request. */
-		user.muted = 1;
-	}
-
 	/* Look for a conference bridge matching the provided name */
 	if (!(conference = join_conference_bridge(args.conf_name, &user))) {
 		pbx_builtin_setvar_helper(chan, "CONFBRIDGE_RESULT", "FAILED");
@@ -1770,13 +1837,13 @@ static int confbridge_exec(struct ast_channel *chan, const char *data)
 		ast_autoservice_start(chan);
 		play_sound_file(conference, user.name_rec_location);
 		play_sound_file(conference,
-			conf_get_sound(CONF_SOUND_HAS_JOINED, user.b_profile.sounds));
+			conf_get_sound(CONF_SOUND_HAS_JOINED, conference->b_profile.sounds));
 		ast_autoservice_stop(chan);
 	}
 
 	/* Play the Join sound to both the conference and the user entering. */
 	if (!quiet) {
-		const char *join_sound = conf_get_sound(CONF_SOUND_JOIN, user.b_profile.sounds);
+		const char *join_sound = conf_get_sound(CONF_SOUND_JOIN, conference->b_profile.sounds);
 
 		ast_stream_and_wait(chan, join_sound, "");
 		ast_autoservice_start(chan);
@@ -1835,29 +1902,29 @@ static int confbridge_exec(struct ast_channel *chan, const char *data)
 		ast_autoservice_start(chan);
 		play_sound_file(conference, user.name_rec_location);
 		play_sound_file(conference,
-			conf_get_sound(CONF_SOUND_HAS_LEFT, user.b_profile.sounds));
+			conf_get_sound(CONF_SOUND_HAS_LEFT, conference->b_profile.sounds));
 		ast_autoservice_stop(chan);
 	}
 
 	/* play the leave sound */
 	if (!quiet) {
-		const char *leave_sound = conf_get_sound(CONF_SOUND_LEAVE, user.b_profile.sounds);
+		const char *leave_sound = conf_get_sound(CONF_SOUND_LEAVE, conference->b_profile.sounds);
 		ast_autoservice_start(chan);
 		play_sound_file(conference, leave_sound);
 		ast_autoservice_stop(chan);
 	}
 
-	/* Easy as pie, depart this channel from the conference bridge */
-	leave_conference(&user);
-	conference = NULL;
-
 	/* If the user was kicked from the conference play back the audio prompt for it */
 	if (!quiet && user.kicked) {
 		res = ast_stream_and_wait(chan,
-			conf_get_sound(CONF_SOUND_KICKED, user.b_profile.sounds),
+			conf_get_sound(CONF_SOUND_KICKED, conference->b_profile.sounds),
 			"");
 	}
 
+	/* Easy as pie, depart this channel from the conference bridge */
+	leave_conference(&user);
+	conference = NULL;
+
 	/* Restore volume adjustments to previous values in case they were changed */
 	if (volume_adjustments[0]) {
 		ast_audiohook_volume_set(chan, AST_AUDIOHOOK_DIRECTION_READ, volume_adjustments[0]);
@@ -1884,26 +1951,11 @@ static int action_toggle_mute(struct confbridge_conference *conference,
 
 	/* Toggle user level mute request. */
 	mute = !user->muted;
-	user->muted = mute;
+	generic_mute_unmute_user(conference, user, mute);
 
-	conf_update_user_mute(user);
-	ast_test_suite_event_notify("CONF_MUTE",
-		"Message: participant %s %s\r\n"
-		"Conference: %s\r\n"
-		"Channel: %s",
-		ast_channel_name(user->chan),
-		mute ? "muted" : "unmuted",
-		user->b_profile.name,
-		ast_channel_name(user->chan));
-	if (mute) {
-		send_mute_event(user, conference);
-	} else {
-		send_unmute_event(user, conference);
-	}
-
-	return play_file(bridge_channel, NULL, (mute ?
-		conf_get_sound(CONF_SOUND_MUTED, user->b_profile.sounds) :
-		conf_get_sound(CONF_SOUND_UNMUTED, user->b_profile.sounds))) < 0;
+	return play_file(bridge_channel, NULL,
+		conf_get_sound(mute ? CONF_SOUND_MUTED : CONF_SOUND_UNMUTED,
+			conference->b_profile.sounds)) < 0;
 }
 
 static int action_toggle_mute_participants(struct confbridge_conference *conference, struct confbridge_user *user)
@@ -1928,8 +1980,9 @@ static int action_toggle_mute_participants(struct confbridge_conference *confere
 
 	ao2_unlock(conference);
 
-	sound_to_play = conf_get_sound((mute ? CONF_SOUND_PARTICIPANTS_MUTED : CONF_SOUND_PARTICIPANTS_UNMUTED),
-		user->b_profile.sounds);
+	sound_to_play = conf_get_sound(
+		mute ? CONF_SOUND_PARTICIPANTS_MUTED : CONF_SOUND_PARTICIPANTS_UNMUTED,
+		conference->b_profile.sounds);
 
 	/* The host needs to hear it seperately, as they don't get the audio from play_sound_helper */
 	ast_stream_and_wait(user->chan, sound_to_play, "");
@@ -2036,7 +2089,7 @@ static int action_kick_last(struct confbridge_conference *conference,
 
 	if (!isadmin) {
 		play_file(bridge_channel, NULL,
-			  conf_get_sound(CONF_SOUND_ERROR_MENU, user->b_profile.sounds));
+			conf_get_sound(CONF_SOUND_ERROR_MENU, conference->b_profile.sounds));
 		ast_log(LOG_WARNING, "Only admin users can use the kick_last menu action. Channel %s of conf %s is not an admin.\n",
 			ast_channel_name(bridge_channel->chan),
 			conference->name);
@@ -2048,7 +2101,7 @@ static int action_kick_last(struct confbridge_conference *conference,
 		|| (ast_test_flag(&last_user->u_profile, USER_OPT_ADMIN))) {
 		ao2_unlock(conference);
 		play_file(bridge_channel, NULL,
-			  conf_get_sound(CONF_SOUND_ERROR_MENU, user->b_profile.sounds));
+			conf_get_sound(CONF_SOUND_ERROR_MENU, conference->b_profile.sounds));
 	} else if (last_user && !last_user->kicked) {
 		last_user->kicked = 1;
 		pbx_builtin_setvar_helper(last_user->chan, "CONFBRIDGE_RESULT", "KICKED");
@@ -2177,9 +2230,9 @@ static int execute_menu_entry(struct confbridge_conference *conference,
 			}
 			conference->locked = (!conference->locked ? 1 : 0);
 			res |= play_file(bridge_channel, NULL,
-				(conference->locked ?
-				conf_get_sound(CONF_SOUND_LOCKED_NOW, user->b_profile.sounds) :
-				conf_get_sound(CONF_SOUND_UNLOCKED_NOW, user->b_profile.sounds))) < 0;
+				conf_get_sound(
+					conference->locked ? CONF_SOUND_LOCKED_NOW : CONF_SOUND_UNLOCKED_NOW,
+					conference->b_profile.sounds)) < 0;
 			break;
 		case MENU_ACTION_ADMIN_KICK_LAST:
 			res |= action_kick_last(conference, bridge_channel, user);
@@ -2410,7 +2463,7 @@ static void handle_cli_confbridge_list_item(struct ast_cli_args *a, struct confb
 		ast_channel_name(user->chan),
 		flag_str,
 		user->u_profile.name,
-		user->b_profile.name,
+		user->conference->b_profile.name,
 		user->menu_name,
 		S_COR(ast_channel_caller(user->chan)->id.number.valid,
 			ast_channel_caller(user->chan)->id.number.str, "<unknown>"));
@@ -2448,11 +2501,16 @@ static char *handle_cli_confbridge_list(struct ast_cli_entry *e, int cmd, struct
 	if (a->argc == 2) {
 		struct ao2_iterator iter;
 
-		ast_cli(a->fd, "Conference Bridge Name           Users  Marked Locked?\n");
-		ast_cli(a->fd, "================================ ====== ====== ========\n");
+		ast_cli(a->fd, "Conference Bridge Name           Users  Marked Locked Muted\n");
+		ast_cli(a->fd, "================================ ====== ====== ====== =====\n");
 		iter = ao2_iterator_init(conference_bridges, 0);
 		while ((conference = ao2_iterator_next(&iter))) {
-			ast_cli(a->fd, "%-32s %6u %6u %s\n", conference->name, conference->activeusers + conference->waitingusers, conference->markedusers, (conference->locked ? "locked" : "unlocked"));
+			ast_cli(a->fd, "%-32s %6u %6u %-6s %s\n",
+				conference->name,
+				conference->activeusers + conference->waitingusers,
+				conference->markedusers,
+				AST_CLI_YESNO(conference->locked),
+				AST_CLI_YESNO(conference->muted));
 			ao2_ref(conference, -1);
 		}
 		ao2_iterator_destroy(&iter);
@@ -2509,30 +2567,6 @@ static int generic_lock_unlock_helper(int lock, const char *conference_name)
 }
 
 /* \internal
- * \brief Mute/unmute a single user.
- */
-static void generic_mute_unmute_user(struct confbridge_conference *conference, struct confbridge_user *user, int mute)
-{
-	/* Set user level mute request. */
-	user->muted = mute ? 1 : 0;
-
-	conf_update_user_mute(user);
-	ast_test_suite_event_notify("CONF_MUTE",
-		"Message: participant %s %s\r\n"
-		"Conference: %s\r\n"
-		"Channel: %s",
-		ast_channel_name(user->chan),
-		mute ? "muted" : "unmuted",
-		conference->b_profile.name,
-		ast_channel_name(user->chan));
-	if (mute) {
-		send_mute_event(user, conference);
-	} else {
-		send_unmute_event(user, conference);
-	}
-}
-
-/* \internal
  * \brief finds a conference user by channel name and mutes/unmutes them.
  *
  * \retval 0 success
@@ -2942,12 +2976,14 @@ static int action_confbridgelistrooms(struct mansession *s, const struct message
 		"Parties: %u\r\n"
 		"Marked: %u\r\n"
 		"Locked: %s\r\n"
+		"Muted: %s\r\n"
 		"\r\n",
 		id_text,
 		conference->name,
 		conference->activeusers + conference->waitingusers,
 		conference->markedusers,
-		conference->locked ? "Yes" : "No");
+		AST_YESNO(conference->locked),
+		AST_YESNO(conference->muted));
 		ao2_unlock(conference);
 
 		ao2_ref(conference, -1);
@@ -3218,30 +3254,31 @@ static int func_confbridge_info(struct ast_channel *chan, const char *cmd, char
 
 	/* get the correct count for the type requested */
 	ao2_lock(conference);
-	if (!strncasecmp(args.type, "parties", 7)) {
+	if (!strcasecmp(args.type, "parties")) {
 		AST_LIST_TRAVERSE(&conference->active_list, user, list) {
 			count++;
 		}
 		AST_LIST_TRAVERSE(&conference->waiting_list, user, list) {
 			count++;
 		}
-	} else if (!strncasecmp(args.type, "admins", 6)) {
+	} else if (!strcasecmp(args.type, "admins")) {
 		AST_LIST_TRAVERSE(&conference->active_list, user, list) {
 			if (ast_test_flag(&user->u_profile, USER_OPT_ADMIN)) {
 				count++;
 			}
 		}
-	} else if (!strncasecmp(args.type, "marked", 6)) {
+	} else if (!strcasecmp(args.type, "marked")) {
 		AST_LIST_TRAVERSE(&conference->active_list, user, list) {
 			if (ast_test_flag(&user->u_profile, USER_OPT_MARKEDUSER)) {
 				count++;
 			}
 		}
-	} else if (!strncasecmp(args.type, "locked", 6)) {
+	} else if (!strcasecmp(args.type, "locked")) {
 		count = conference->locked;
+	} else if (!strcasecmp(args.type, "muted")) {
+		count = conference->muted;
 	} else {
-		ast_log(LOG_ERROR, "Invalid keyword '%s' passed to CONFBRIDGE_INFO.  Should be one of: "
-			"parties, admins, marked, or locked.\n", args.type);
+		ast_log(LOG_ERROR, "Invalid keyword '%s' passed to CONFBRIDGE_INFO.\n", args.type);
 	}
 	snprintf(buf, len, "%d", count);
 	ao2_unlock(conference);
diff --git a/apps/app_dial.c b/apps/app_dial.c
index d65dcae..bc4f8a5 100644
--- a/apps/app_dial.c
+++ b/apps/app_dial.c
@@ -2151,6 +2151,24 @@ static int dial_exec_full(struct ast_channel *chan, const char *data, struct ast
 		return -1;
 	}
 
+	if (ast_check_hangup_locked(chan)) {
+		/*
+		 * Caller hung up before we could dial.  If dial is executed
+		 * within an AGI then the AGI has likely eaten all queued
+		 * frames before executing the dial in DeadAGI mode.  With
+		 * the caller hung up and no pending frames from the caller's
+		 * read queue, dial would not know that the call has hung up
+		 * until a called channel answers.  It is rather annoying to
+		 * whoever just answered the non-existent call.
+		 *
+		 * Dial should not continue execution in DeadAGI mode, hangup
+		 * handlers, or the h exten.
+		 */
+		ast_verb(3, "Caller hung up before dial.\n");
+		pbx_builtin_setvar_helper(chan, "DIALSTATUS", "CANCEL");
+		return -1;
+	}
+
 	parse = ast_strdupa(data);
 
 	AST_STANDARD_APP_ARGS(args, parse);
diff --git a/apps/app_fax.c b/apps/app_fax.c
index 7822a34..88aa6ad 100644
--- a/apps/app_fax.c
+++ b/apps/app_fax.c
@@ -356,8 +356,8 @@ static int fax_generator_generate(struct ast_channel *chan, void *data, int len,
 }
 
 static struct ast_generator generator = {
-	alloc:		fax_generator_alloc,
-	generate: 	fax_generator_generate,
+	.alloc = fax_generator_alloc,
+	.generate = fax_generator_generate,
 };
 
 
diff --git a/apps/app_meetme.c b/apps/app_meetme.c
index b2131ff..7903b1d 100644
--- a/apps/app_meetme.c
+++ b/apps/app_meetme.c
@@ -2936,7 +2936,7 @@ static void meetme_menu_admin(enum menu_modes *menu_mode, int *dtmf, struct ast_
 		tweak_talk_volume(user, VOL_UP);
 		break;
 	default:
-		menu_mode = MENU_DISABLED;
+		*menu_mode = MENU_DISABLED;
 		/* Play an error message! */
 		if (!ast_streamfile(chan, "conf-errormenu", ast_channel_language(chan))) {
 			ast_waitstream(chan, "");
diff --git a/apps/app_minivm.c b/apps/app_minivm.c
index 45d04d8..fb7c22a 100644
--- a/apps/app_minivm.c
+++ b/apps/app_minivm.c
@@ -2998,10 +2998,10 @@ static char *handle_minivm_list_templates(struct ast_cli_entry *e, int cmd, stru
 	ast_cli(a->fd, HVLT_OUTPUT_FORMAT, "-------------", "-------", "------", "------------", "-------");
 	AST_LIST_TRAVERSE(&message_templates, this, list) {
 		ast_cli(a->fd, HVLT_OUTPUT_FORMAT, this->name, 
-			this->charset ? this->charset : "-", 
-			this->locale ? this->locale : "-",
+			S_OR(this->charset, "-"),
+			S_OR(this->locale, "-"),
 			this->attachment ? "Yes" : "No",
-			this->subject ? this->subject : "-");
+			S_OR(this->subject, "-"));
 		count++;
 	}
 	AST_LIST_UNLOCK(&message_templates);
@@ -3069,10 +3069,10 @@ static char *handle_minivm_show_users(struct ast_cli_entry *e, int cmd, struct a
 		if ((a->argc == 3) || ((a->argc == 5) && !strcmp(a->argv[4], vmu->domain))) {
 			count++;
 			snprintf(tmp, sizeof(tmp), "%s@%s", vmu->username, vmu->domain);
-			ast_cli(a->fd, HMSU_OUTPUT_FORMAT, tmp, vmu->etemplate ? vmu->etemplate : "-", 
-				vmu->ptemplate ? vmu->ptemplate : "-",
-				vmu->zonetag ? vmu->zonetag : "-", 
-				vmu->attachfmt ? vmu->attachfmt : "-",
+			ast_cli(a->fd, HMSU_OUTPUT_FORMAT, tmp, S_OR(vmu->etemplate, "-"),
+				S_OR(vmu->ptemplate, "-"),
+				S_OR(vmu->zonetag, "-"),
+				S_OR(vmu->attachfmt, "-"),
 				vmu->fullname);
 		}
 	}
diff --git a/apps/app_mixmonitor.c b/apps/app_mixmonitor.c
index e6f4053..3515f4b 100644
--- a/apps/app_mixmonitor.c
+++ b/apps/app_mixmonitor.c
@@ -729,11 +729,11 @@ static void *mixmonitor_thread(void *obj)
 
 	ast_audiohook_unlock(&mixmonitor->audiohook);
 
-	ast_channel_lock(mixmonitor->autochan->chan);
+	ast_autochan_channel_lock(mixmonitor->autochan);
 	if (ast_test_flag(mixmonitor, MUXFLAG_BEEP_STOP)) {
 		ast_stream_and_wait(mixmonitor->autochan->chan, "beep", "");
 	}
-	ast_channel_unlock(mixmonitor->autochan->chan);
+	ast_autochan_channel_unlock(mixmonitor->autochan);
 
 	ast_autochan_destroy(mixmonitor->autochan);
 
@@ -805,11 +805,11 @@ static int setup_mixmonitor_ds(struct mixmonitor *mixmonitor, struct ast_channel
 		return -1;
 	}
 
-	ast_channel_lock(mixmonitor->autochan->chan);
+	ast_autochan_channel_lock(mixmonitor->autochan);
 	if (ast_test_flag(mixmonitor, MUXFLAG_BEEP_START)) {
 		ast_stream_and_wait(mixmonitor->autochan->chan, "beep", "");
 	}
-	ast_channel_unlock(mixmonitor->autochan->chan);
+	ast_autochan_channel_unlock(mixmonitor->autochan);
 
 	mixmonitor_ds->samp_rate = 8000;
 	mixmonitor_ds->audiohook = &mixmonitor->audiohook;
diff --git a/apps/app_queue.c b/apps/app_queue.c
index 5a8dcd2..939a0e2 100644
--- a/apps/app_queue.c
+++ b/apps/app_queue.c
@@ -982,6 +982,13 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
 				<parameter name="LastCall">
 					<para>The time this member last took a call, expressed in seconds since 00:00, Jan 1, 1970 UTC.</para>
 				</parameter>
+				<parameter name="InCall">
+					<para>Set to 1 if member is in call. Set to 0 after LastCall time is updated.</para>
+					<enumlist>
+						<enum name="0"/>
+						<enum name="1"/>
+					</enumlist>
+				</parameter>
 				<parameter name="Status">
 					<para>The numeric device state status of the queue member.</para>
 					<enumlist>
@@ -1002,6 +1009,9 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
 						<enum name="1"/>
 					</enumlist>
 				</parameter>
+				<parameter name="PausedReason">
+					<para>If set when paused, the reason the queue member was paused.</para>
+				</parameter>
 				<parameter name="Ringinuse">
 					<enumlist>
 						<enum name="0"/>
@@ -1493,8 +1503,10 @@ struct member {
 	int realtime;                        /*!< Is this member realtime? */
 	int status;                          /*!< Status of queue member */
 	int paused;                          /*!< Are we paused (not accepting calls)? */
+	char reason_paused[80];              /*!< Reason of paused if member is paused */
 	int queuepos;                        /*!< In what order (pertains to certain strategies) should this member be called? */
 	time_t lastcall;                     /*!< When last successful call was hungup */
+	unsigned int in_call:1;              /*!< True if member is still in call. (so lastcall is not actual) */
 	struct call_queue *lastqueue;	     /*!< Last queue we received a call */
 	unsigned int dead:1;                 /*!< Used to detect members deleted in realtime */
 	unsigned int delme:1;                /*!< Flag to delete entry on reload */
@@ -2155,7 +2167,7 @@ static void queue_publish_member_blob(struct stasis_message_type *type, struct a
 
 static struct ast_json *queue_member_blob_create(struct call_queue *q, struct member *mem)
 {
-	return ast_json_pack("{s: s, s: s, s: s, s: s, s: s, s: i, s: i, s: i, s: i, s: i, s: i}",
+	return ast_json_pack("{s: s, s: s, s: s, s: s, s: s, s: i, s: i, s: i, s: i, s: i, s: i, s: s, s: i}",
 		"Queue", q->name,
 		"MemberName", mem->membername,
 		"Interface", mem->interface,
@@ -2164,8 +2176,10 @@ static struct ast_json *queue_member_blob_create(struct call_queue *q, struct me
 		"Penalty", mem->penalty,
 		"CallsTaken", mem->calls,
 		"LastCall", (int)mem->lastcall,
+		"InCall", mem->in_call,
 		"Status", mem->status,
 		"Paused", mem->paused,
+		"PausedReason", mem->reason_paused,
 		"Ringinuse", mem->ringinuse);
 }
 
@@ -2226,6 +2240,10 @@ static int get_member_status(struct call_queue *q, int max_penalty, int min_pena
 			if (member->paused && (conditions & QUEUE_EMPTY_PAUSED)) {
 				ast_debug(4, "%s is unavailable because he is paused'\n", member->membername);
 				break;
+			} else if ((conditions & QUEUE_EMPTY_WRAPUP) && member->in_call && q->wrapuptime) {
+				ast_debug(4, "%s is unavailable because still in call, so we can`t check "
+					"wrapuptime (%d)\n", member->membername, q->wrapuptime);
+				break;
 			} else if ((conditions & QUEUE_EMPTY_WRAPUP) && member->lastcall && q->wrapuptime && (time(NULL) - q->wrapuptime < member->lastcall)) {
 				ast_debug(4, "%s is unavailable because it has only been %d seconds since his last call (wrapup time is %d)\n", member->membername, (int) (time(NULL) - member->lastcall), q->wrapuptime);
 				break;
@@ -2293,6 +2311,9 @@ static int is_member_available(struct call_queue *q, struct member *mem)
 	}
 
 	/* Let wrapuptimes override device state availability */
+	if (q->wrapuptime && mem->in_call) {
+		available = 0; /* member is still in call, cant check wrapuptime to lastcall time */
+	}
 	if (mem->lastcall && q->wrapuptime && (time(NULL) - q->wrapuptime < mem->lastcall)) {
 		available = 0;
 	}
@@ -2645,6 +2666,7 @@ static void clear_queue(struct call_queue *q)
 		while ((mem = ao2_iterator_next(&mem_iter))) {
 			mem->calls = 0;
 			mem->lastcall = 0;
+			mem->in_call = 0;
 			ao2_ref(mem, -1);
 		}
 		ao2_iterator_destroy(&mem_iter);
@@ -4142,6 +4164,12 @@ static int can_ring_entry(struct queue_ent *qe, struct callattempt *call)
 		return 0;
 	}
 
+	if (call->member->in_call && call->lastqueue->wrapuptime) {
+		ast_debug(1, "%s is in call, so not available (wrapuptime %d)\n",
+			call->interface, call->lastqueue->wrapuptime);
+		return 0;
+	}
+
 	if ((call->lastqueue && call->lastqueue->wrapuptime && (time(NULL) - call->lastcall < call->lastqueue->wrapuptime))
 		|| (!call->lastqueue && qe->parent->wrapuptime && (time(NULL) - call->lastcall < qe->parent->wrapuptime))) {
 		ast_debug(1, "Wrapuptime not yet expired on queue %s for %s\n",
@@ -5191,6 +5219,11 @@ static int is_our_turn(struct queue_ent *qe)
 		res = 0;
 	}
 
+	/* Update realtime members if this is the first call and number of avalable members is 0 */
+	if (avl == 0 && qe->pos == 1) {
+		update_realtime_members(qe->parent);
+	}
+
 	return res;
 }
 
@@ -5359,6 +5392,9 @@ static int update_queue(struct call_queue *q, struct member *member, int callcom
 				time(&mem->lastcall);
 				mem->calls++;
 				mem->lastqueue = q;
+				mem->in_call = 0;
+				ast_debug(4, "Marked member %s as NOT in_call. Lastcall time: %ld \n",
+					mem->membername, (long)mem->lastcall);
 				ao2_ref(mem, -1);
 			}
 			ao2_unlock(qtmp);
@@ -5370,6 +5406,9 @@ static int update_queue(struct call_queue *q, struct member *member, int callcom
 		time(&member->lastcall);
 		member->calls++;
 		member->lastqueue = q;
+		member->in_call = 0;
+		ast_debug(4, "Marked member %s as NOT in_call. Lastcall time: %ld \n",
+			member->membername, (long)member->lastcall);
 		ao2_unlock(q);
 	}
 	ao2_lock(q);
@@ -5377,9 +5416,13 @@ static int update_queue(struct call_queue *q, struct member *member, int callcom
 	if (callcompletedinsl) {
 		q->callscompletedinsl++;
 	}
-	/* Calculate talktime using the same exponential average as holdtime code*/
-	oldtalktime = q->talktime;
-	q->talktime = (((oldtalktime << 2) - oldtalktime) + newtalktime) >> 2;
+	if (q->callscompletedinsl == 1) {
+		q->talktime = newtalktime;
+	} else {
+		/* Calculate talktime using the same exponential average as holdtime code */
+		oldtalktime = q->talktime;
+		q->talktime = (((oldtalktime << 2) - oldtalktime) + newtalktime) >> 2;
+	}
 	ao2_unlock(q);
 	return 0;
 }
@@ -6348,6 +6391,9 @@ static int try_calling(struct queue_ent *qe, struct ast_flags opts, char **opt_a
 	int callcompletedinsl;
 	struct ao2_iterator memi;
 	struct queue_end_bridge *queue_end_bridge = NULL;
+	struct ao2_iterator queue_iter; /* to iterate through all queues (for shared_lastcall)*/
+	struct member *mem;
+	struct call_queue *queuetmp;
 
 	memset(&bridge_config, 0, sizeof(bridge_config));
 	tmpid[0] = 0;
@@ -6773,6 +6819,28 @@ static int try_calling(struct queue_ent *qe, struct ast_flags opts, char **opt_a
 			}
 		}
 		qe->handled++;
+
+		/** mark member as "in_call" in all queues */
+		if (shared_lastcall) {
+			queue_iter = ao2_iterator_init(queues, 0);
+			while ((queuetmp = ao2_t_iterator_next(&queue_iter, "Iterate through queues"))) {
+				ao2_lock(queuetmp);
+				if ((mem = ao2_find(queuetmp->members, member, OBJ_POINTER))) {
+					mem->in_call = 1;
+					ast_debug(4, "Marked member %s as in_call \n", mem->membername);
+					ao2_ref(mem, -1);
+				}
+				ao2_unlock(queuetmp);
+				queue_t_unref(queuetmp, "Done with iterator");
+			}
+			ao2_iterator_destroy(&queue_iter);
+		} else {
+			ao2_lock(qe->parent);
+			member->in_call = 1;
+			ast_debug(4, "Marked member %s as in_call \n", member->membername);
+			ao2_unlock(qe->parent);
+		}
+
 		ast_queue_log(queuename, ast_channel_uniqueid(qe->chan), member->membername, "CONNECT", "%ld|%s|%ld", (long) (time(NULL) - qe->start), ast_channel_uniqueid(peer),
 													(long)(orig - to > 0 ? (orig - to) / 1000 : 0));
 
@@ -7050,6 +7118,14 @@ static void set_queue_member_pause(struct call_queue *q, struct member *mem, con
 	}
 
 	mem->paused = paused;
+	if (paused) {
+		if (!ast_strlen_zero(reason)) {
+			ast_copy_string(mem->reason_paused, reason, sizeof(mem->reason_paused));
+		}
+	} else {
+		ast_copy_string(mem->reason_paused, "", sizeof(mem->reason_paused));
+	}
+
 	ast_devstate_changed(mem->paused ? QUEUE_PAUSED_DEVSTATE : QUEUE_UNPAUSED_DEVSTATE,
 		AST_DEVSTATE_CACHABLE, "Queue:%s_pause_%s", q->name, mem->interface);
 
@@ -9135,10 +9211,11 @@ static char *__queues_show(struct mansession *s, int fd, int argc, const char *
 
 				ast_str_append(&out, 0, " (ringinuse %s)", mem->ringinuse ? "enabled" : "disabled");
 
-				ast_str_append(&out, 0, "%s%s%s%s%s%s%s%s%s (%s%s%s)",
+				ast_str_append(&out, 0, "%s%s%s%s%s%s%s%s%s%s%s%s (%s%s%s)",
 					mem->dynamic ? ast_term_color(COLOR_CYAN, COLOR_BLACK) : "", mem->dynamic ? " (dynamic)" : "", ast_term_reset(),
 					mem->realtime ? ast_term_color(COLOR_MAGENTA, COLOR_BLACK) : "", mem->realtime ? " (realtime)" : "", ast_term_reset(),
 					mem->paused ? ast_term_color(COLOR_BROWN, COLOR_BLACK) : "", mem->paused ? " (paused)" : "", ast_term_reset(),
+					mem->in_call ? ast_term_color(COLOR_BROWN, COLOR_BLACK) : "", mem->in_call ? " (in call)" : "", ast_term_reset(),
 					ast_term_color(
 						mem->status == AST_DEVICE_UNAVAILABLE || mem->status == AST_DEVICE_UNKNOWN ?
 							COLOR_RED : COLOR_GREEN, COLOR_BLACK),
@@ -9506,12 +9583,15 @@ static int manager_queues_status(struct mansession *s, const struct message *m)
 						"Penalty: %d\r\n"
 						"CallsTaken: %d\r\n"
 						"LastCall: %d\r\n"
+						"InCall: %d\r\n"
 						"Status: %d\r\n"
 						"Paused: %d\r\n"
+						"PausedReason: %s\r\n"
 						"%s"
 						"\r\n",
 						q->name, mem->membername, mem->interface, mem->state_interface, mem->dynamic ? "dynamic" : "static",
-						mem->penalty, mem->calls, (int)mem->lastcall, mem->status, mem->paused, idText);
+						mem->penalty, mem->calls, (int)mem->lastcall, mem->in_call, mem->status,
+						mem->paused, mem->reason_paused, idText);
 					++q_items;
 				}
 				ao2_ref(mem, -1);
@@ -9665,7 +9745,7 @@ static int manager_pause_queue_member(struct mansession *s, const struct message
 	interface = astman_get_header(m, "Interface");
 	paused_s = astman_get_header(m, "Paused");
 	queuename = astman_get_header(m, "Queue");      /* Optional - if not supplied, pause the given Interface in all queues */
-	reason = astman_get_header(m, "Reason");        /* Optional - Only used for logging purposes */
+	reason = astman_get_header(m, "Reason");        /* Optional */
 
 	if (ast_strlen_zero(interface) || ast_strlen_zero(paused_s)) {
 		astman_send_error(s, m, "Need 'Interface' and 'Paused' parameters.");
diff --git a/apps/app_stasis.c b/apps/app_stasis.c
index 4f53aff..1e5b567 100644
--- a/apps/app_stasis.c
+++ b/apps/app_stasis.c
@@ -110,10 +110,16 @@ static int app_exec(struct ast_channel *chan, const char *data)
 		                      args.app_argv);
 	}
 
-	if (ret == -1) {
-	    pbx_builtin_setvar_helper(chan, "STASISSTATUS", "FAILED");
+	if (ret) {
+		/* set ret to 0 so pbx_core doesnt hangup the channel */
+		if (!ast_check_hangup(chan)) {
+			ret = 0;
+		} else {
+			ret = -1;
+		}
+		pbx_builtin_setvar_helper(chan, "STASISSTATUS", "FAILED");
 	} else {
-	    pbx_builtin_setvar_helper(chan, "STASISSTATUS", "SUCCESS");
+		pbx_builtin_setvar_helper(chan, "STASISSTATUS", "SUCCESS");
 	}
 
 	return ret;
diff --git a/apps/app_voicemail.c b/apps/app_voicemail.c
index 19bd896..0755c44 100644
--- a/apps/app_voicemail.c
+++ b/apps/app_voicemail.c
@@ -48,7 +48,6 @@
 
 /*** MODULEINFO
 	<defaultenabled>yes</defaultenabled>
-	<conflict>res_mwi_external</conflict>
 	<use type="module">res_adsi</use>
 	<use type="module">res_smdi</use>
 	<support_level>core</support_level>
@@ -14702,10 +14701,14 @@ static int unload_module(void)
  *
  * Module loading including tests for configuration or dependencies.
  * This function can return AST_MODULE_LOAD_FAILURE, AST_MODULE_LOAD_DECLINE,
- * or AST_MODULE_LOAD_SUCCESS. If a dependency or environment variable fails
- * tests return AST_MODULE_LOAD_FAILURE. If the module can not load the 
- * configuration file or other non-critical problem return 
- * AST_MODULE_LOAD_DECLINE. On success return AST_MODULE_LOAD_SUCCESS.
+ * or AST_MODULE_LOAD_SUCCESS.
+ *
+ * If a dependency, allocation or environment variable fails tests, return AST_MODULE_LOAD_FAILURE.
+ *
+ * If the module can't load the configuration file, can't register as a provider or
+ * has another issue not fatal to Asterisk itself, return AST_MODULE_LOAD_DECLINE.
+ *
+ * On success return AST_MODULE_LOAD_SUCCESS.
  */
 static int load_module(void)
 {
@@ -14714,7 +14717,7 @@ static int load_module(void)
 	umask(my_umask);
 
 	if (!(inprocess_container = ao2_container_alloc(573, inprocess_hash_fn, inprocess_cmp_fn))) {
-		return AST_MODULE_LOAD_DECLINE;
+		return AST_MODULE_LOAD_FAILURE;
 	}
 
 	/* compute the location of the voicemail spool directory */
@@ -14724,8 +14727,10 @@ static int load_module(void)
 		ast_log(AST_LOG_WARNING, "failed to reference mwi subscription taskprocessor.  MWI will not work\n");
 	}
 
-	if ((res = load_config(0)))
-		return res;
+	if ((res = load_config(0))) {
+		unload_module();
+		return AST_MODULE_LOAD_DECLINE;
+	}
 
 	res = ast_register_application_xml(app, vm_exec);
 	res |= ast_register_application_xml(app2, vm_execmain);
@@ -14746,10 +14751,26 @@ static int load_module(void)
 	res |= AST_TEST_REGISTER(test_voicemail_vm_info);
 #endif
 
-	res |= ast_vm_register(&vm_table);
-	res |= ast_vm_greeter_register(&vm_greeter_table);
 	if (res) {
-		return res;
+		ast_log(LOG_ERROR, "Failure registering applications, functions or tests\n");
+		unload_module();
+		return AST_MODULE_LOAD_DECLINE;
+	}
+
+	/* ast_vm_register may return DECLINE if another module registered for vm */
+	res = ast_vm_register(&vm_table);
+	if (res) {
+		ast_log(LOG_ERROR, "Failure registering as a voicemail provider\n");
+		unload_module();
+		return AST_MODULE_LOAD_DECLINE;
+	}
+
+	/* ast_vm_greeter_register may return DECLINE if another module registered as a greeter */
+	res = ast_vm_greeter_register(&vm_greeter_table);
+	if (res) {
+		ast_log(LOG_ERROR, "Failure registering as a greeter provider\n");
+		unload_module();
+		return AST_MODULE_LOAD_DECLINE;
 	}
 
 	ast_cli_register_multiple(cli_voicemail, ARRAY_LEN(cli_voicemail));
@@ -14762,7 +14783,7 @@ static int load_module(void)
 	ast_realtime_require_field("voicemail", "uniqueid", RQ_UINTEGER3, 11, "password", RQ_CHAR, 10, SENTINEL);
 	ast_realtime_require_field("voicemail_data", "filename", RQ_CHAR, 30, "duration", RQ_UINTEGER3, 5, SENTINEL);
 
-	return res;
+	return AST_MODULE_LOAD_SUCCESS;
 }
 
 static int dialout(struct ast_channel *chan, struct ast_vm_user *vmu, char *num, char *outgoing_context) 
diff --git a/apps/confbridge/conf_state_multi_marked.c b/apps/confbridge/conf_state_multi_marked.c
index 5d977f7..fabe99b 100644
--- a/apps/confbridge/conf_state_multi_marked.c
+++ b/apps/confbridge/conf_state_multi_marked.c
@@ -163,7 +163,7 @@ static void leave_marked(struct confbridge_user *user)
 			ao2_unlock(user->conference);
 			ast_autoservice_start(user->chan);
 			play_sound_file(user->conference,
-				conf_get_sound(CONF_SOUND_LEADER_HAS_LEFT, user->b_profile.sounds));
+				conf_get_sound(CONF_SOUND_LEADER_HAS_LEFT, user->conference->b_profile.sounds));
 			ast_autoservice_stop(user->chan);
 			ao2_lock(user->conference);
 		}
@@ -182,14 +182,14 @@ static void leave_marked(struct confbridge_user *user)
 	}
 }
 
-static int post_join_play_begin(struct confbridge_user *cbu)
+static int post_join_play_begin(struct confbridge_user *user)
 {
 	int res;
 
-	ast_autoservice_start(cbu->chan);
-	res = play_sound_file(cbu->conference,
-		conf_get_sound(CONF_SOUND_BEGIN, cbu->b_profile.sounds));
-	ast_autoservice_stop(cbu->chan);
+	ast_autoservice_start(user->chan);
+	res = play_sound_file(user->conference,
+		conf_get_sound(CONF_SOUND_BEGIN, user->conference->b_profile.sounds));
+	ast_autoservice_stop(user->chan);
 	return res;
 }
 
diff --git a/asterisk-13.7.0-rc2-summary.html b/asterisk-13.7.0-rc2-summary.html
deleted file mode 100644
index 1863eba..0000000
--- a/asterisk-13.7.0-rc2-summary.html
+++ /dev/null
@@ -1,46 +0,0 @@
-<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"><html xmlns="http://www.w3.org/1999/xhtml"><title>Release Summary - asterisk-13.7.0-rc2</title><h1 align="center"><a name="top">Release Summary</a></h1><h3 align="center">asterisk-13.7.0-rc2</h3><h3 align="center">Date: 2015-12-18</h3><h3 align="center"><asteriskteam at digium.com></h3><hr><h2 align="center">Table of Contents</h2><ol>
-<li><a href="#summary">Summary</a></li>
-<li><a href="#contributors">Contributors</a></li>
-<li><a href="#closed_issues">Closed Issues</a></li>
-<li><a href="#commits">Other Changes</a></li>
-<li><a href="#diffstat">Diffstat</a></li>
-</ol><hr><a name="summary"><h2 align="center">Summary</h2></a><center><a href="#top">[Back to Top]</a></center><p>This release is a point release of an existing major version. The changes included were made to address problems that have been identified in this release series, or are minor, backwards compatible new features or improvements. Users should be able to safely upgrade to this version if this release series is already in use. Users considering upgrading from a previous version a [...]
-<tr><th width="33%">Coders</th><th width="33%">Testers</th><th width="33%">Reporters</th></tr>
-<tr valign="top"><td width="33%">4 Joshua Colp <jcolp at digium.com><br/>3 Kevin Harwell <kharwell at lunkwill><br/>2 Mark Michelson <mmichelson at digium.com><br/>1 Kevin Harwell <kharwell at lunkwill.digium.internal><br/></td><td width="33%"><td width="33%">2 Joshua Colp <jcolp at digium.com><br/></td></tr>
-</table><hr><a name="closed_issues"><h2 align="center">Closed Issues</h2></a><center><a href="#top">[Back to Top]</a></center><p>This is a list of all issues from the issue tracker that were closed by changes that went into this release.</p><h3>Bug</h3><h4>Category: Core/General</h4><a href="https://issues.asterisk.org/jira/browse/ASTERISK-25601">ASTERISK-25601</a>: json: Audit reference usage and thread safety<br/>Reported by: Joshua Colp<ul>
-<li><a href="https://code.asterisk.org/code/changelog/asterisk?cs=acd19d5f1f84b5c10acc2828b5e73cecfa1ed6ba">[acd19d5f1f]</a> Joshua Colp -- json: Audit ast_json_* usage for thread safety.</li>
-</ul><br><h4>Category: Core/Sorcery</h4><a href="https://issues.asterisk.org/jira/browse/ASTERISK-25625">ASTERISK-25625</a>: res_sorcery_memory_cache: Add full backend caching<br/>Reported by: Joshua Colp<ul>
-<li><a href="https://code.asterisk.org/code/changelog/asterisk?cs=11ff4e6b3fb6ef9b1470d348e1f43090fbe17a17">[11ff4e6b3f]</a> Joshua Colp -- res_sorcery_memory_cache: Add support for a full backend cache.</li>
-</ul><br><hr><a name="commits"><h2 align="center">Commits Not Associated with an Issue</h2></a><center><a href="#top">[Back to Top]</a></center><p>This is a list of all changes that went into this release that did not reference a JIRA issue.</p><table width="100%" border="1">
-<tr><th>Revision</th><th>Author</th><th>Summary</th></tr>
-<tr><td><a href="https://code.asterisk.org/code/changelog/asterisk?cs=8e201b742a804ff003d86e7c8566856210d02026">8e201b742a</a></td><td>Kevin Harwell</td><td>Release summaries: Remove previous versions</td></tr>
-<tr><td><a href="https://code.asterisk.org/code/changelog/asterisk?cs=5a164c70f2dadcdcf325ccf326efc6edc2315d1b">5a164c70f2</a></td><td>Kevin Harwell</td><td>.version: Update for 13.7.0-rc2</td></tr>
-<tr><td><a href="https://code.asterisk.org/code/changelog/asterisk?cs=e039eca0a7ee4589b64cc8dbd0d18700eed7a31f">e039eca0a7</a></td><td>Kevin Harwell</td><td>.lastclean: Update for 13.7.0-rc2</td></tr>
-<tr><td><a href="https://code.asterisk.org/code/changelog/asterisk?cs=bfe2eb875168864d243dfbed2d4b184e72001110">bfe2eb8751</a></td><td>Kevin Harwell</td><td>realtime: Add database scripts for 13.7.0-rc2</td></tr>
-<tr><td><a href="https://code.asterisk.org/code/changelog/asterisk?cs=805297783dc96976c5806d371f1618a5a3901ff4">805297783d</a></td><td>Mark Michelson</td><td>Alembic: Add PJSIP global keep_alive_interval.</td></tr>
-<tr><td><a href="https://code.asterisk.org/code/changelog/asterisk?cs=63df9bb56001a29765a7e85702019144380581cf">63df9bb560</a></td><td>Mark Michelson</td><td>Alembic: Increase column size of PJSIP AOR "contact".</td></tr>
-<tr><td><a href="https://code.asterisk.org/code/changelog/asterisk?cs=9bc1e49325c0319418c1bbd8ee8e04f31946ef8c">9bc1e49325</a></td><td>Joshua Colp</td><td>rtp_engine: Ignore empty filenames in DTLS configuration.</td></tr>
-<tr><td><a href="https://code.asterisk.org/code/changelog/asterisk?cs=c78eb1e82bc6b92ced080df5766b3fbd7dedeb72">c78eb1e82b</a></td><td>Joshua Colp</td><td>chan_sip: Enable WebSocket support by default.</td></tr>
-</table><hr><a name="diffstat"><h2 align="center">Diffstat Results</h2></a><center><a href="#top">[Back to Top]</a></center><p>This is a summary of the changes to the source code that went into this release that was generated using the diffstat utility.</p><pre>asterisk-13.7.0-rc1-summary.html                                                     |  373 ---
-asterisk-13.7.0-rc1-summary.txt                                                      |  967 ---------
-b/.version                                                                           |    2
-b/CHANGES                                                                            |    8
-b/channels/chan_sip.c                                                                |    1
-b/contrib/ast-db-manage/config/versions/189a235b3fd7_add_keep_alive_interval.py      |   22
-b/contrib/ast-db-manage/config/versions/2d078ec071b7_increaes_contact_column_size.py |   22
-b/contrib/realtime/mssql/mssql_config.sql                                            |   14
-b/contrib/realtime/mysql/mysql_config.sql                                            |   12
-b/contrib/realtime/oracle/oracle_config.sql                                          |   14
-b/contrib/realtime/postgresql/postgresql_config.sql                                  |   10
-b/main/aoc.c                                                                         |   20
-b/main/loader.c                                                                      |    4
-b/main/rtp_engine.c                                                                  |   22
-b/main/sorcery.c                                                                     |    6
-b/main/stasis.c                                                                      |    4
-b/main/stasis_channels.c                                                             |   24
-b/res/res_fax.c                                                                      |    4
-b/res/res_pjsip_endpoint_identifier_ip.c                                             |    2
-b/res/res_sorcery_memory_cache.c                                                     | 1040 +++++++++-
-b/res/res_stasis.c                                                                   |    6
-b/res/res_stasis_playback.c                                                          |    2
-22 files changed, 1136 insertions(+), 1443 deletions(-)</pre><br></html>
\ No newline at end of file
diff --git a/asterisk-13.7.0-rc2-summary.txt b/asterisk-13.7.0-rc2-summary.txt
deleted file mode 100644
index d24ff2c..0000000
--- a/asterisk-13.7.0-rc2-summary.txt
+++ /dev/null
@@ -1,149 +0,0 @@
-                                Release Summary
-
-                              asterisk-13.7.0-rc2
-
-                                Date: 2015-12-18
-
-                           <asteriskteam at digium.com>
-
-     ----------------------------------------------------------------------
-
-                               Table of Contents
-
-    1. Summary
-    2. Contributors
-    3. Closed Issues
-    4. Other Changes
-    5. Diffstat
-
-     ----------------------------------------------------------------------
-
-                                    Summary
-
-                                 [Back to Top]
-
-   This release is a point release of an existing major version. The changes
-   included were made to address problems that have been identified in this
-   release series, or are minor, backwards compatible new features or
-   improvements. Users should be able to safely upgrade to this version if
-   this release series is already in use. Users considering upgrading from a
-   previous version are strongly encouraged to review the UPGRADE.txt
-   document as well as the CHANGES document for information about upgrading
-   to this release series.
-
-   The data in this summary reflects changes that have been made since the
-   previous release, asterisk-13.7.0-rc1.
-
-     ----------------------------------------------------------------------
-
-                                  Contributors
-
-                                 [Back to Top]
-
-   This table lists the people who have submitted code, those that have
-   tested patches, as well as those that reported issues on the issue tracker
-   that were resolved in this release. For coders, the number is how many of
-   their patches (of any size) were committed into this release. For testers,
-   the number is the number of times their name was listed as assisting with
-   testing a patch. Finally, for reporters, the number is the number of
-   issues that they reported that were affected by commits that went into
-   this release.
-
-   Coders                   Testers                  Reporters                
-   4 Joshua Colp                                     2 Joshua Colp            
-   3 Kevin Harwell          
-   2 Mark Michelson         
-   1 Kevin Harwell          
-
-     ----------------------------------------------------------------------
-
-                                 Closed Issues
-
-                                 [Back to Top]
-
-   This is a list of all issues from the issue tracker that were closed by
-   changes that went into this release.
-
-  Bug
-
-    Category: Core/General
-
-   ASTERISK-25601: json: Audit reference usage and thread safety
-   Reported by: Joshua Colp
-     * [acd19d5f1f] Joshua Colp -- json: Audit ast_json_* usage for thread
-       safety.
-
-    Category: Core/Sorcery
-
-   ASTERISK-25625: res_sorcery_memory_cache: Add full backend caching
-   Reported by: Joshua Colp
-     * [11ff4e6b3f] Joshua Colp -- res_sorcery_memory_cache: Add support for
-       a full backend cache.
-
-     ----------------------------------------------------------------------
-
-                      Commits Not Associated with an Issue
-
-                                 [Back to Top]
-
-   This is a list of all changes that went into this release that did not
-   reference a JIRA issue.
-
-   +------------------------------------------------------------------------+
-   | Revision   | Author         | Summary                                  |
-   |------------+----------------+------------------------------------------|
-   | 8e201b742a | Kevin Harwell  | Release summaries: Remove previous       |
-   |            |                | versions                                 |
-   |------------+----------------+------------------------------------------|
-   | 5a164c70f2 | Kevin Harwell  | .version: Update for 13.7.0-rc2          |
-   |------------+----------------+------------------------------------------|
-   | e039eca0a7 | Kevin Harwell  | .lastclean: Update for 13.7.0-rc2        |
-   |------------+----------------+------------------------------------------|
-   | bfe2eb8751 | Kevin Harwell  | realtime: Add database scripts for       |
-   |            |                | 13.7.0-rc2                               |
-   |------------+----------------+------------------------------------------|
-   | 805297783d | Mark Michelson | Alembic: Add PJSIP global                |
-   |            |                | keep_alive_interval.                     |
-   |------------+----------------+------------------------------------------|
-   | 63df9bb560 | Mark Michelson | Alembic: Increase column size of PJSIP   |
-   |            |                | AOR "contact".                           |
-   |------------+----------------+------------------------------------------|
-   | 9bc1e49325 | Joshua Colp    | rtp_engine: Ignore empty filenames in    |
-   |            |                | DTLS configuration.                      |
-   |------------+----------------+------------------------------------------|
-   | c78eb1e82b | Joshua Colp    | chan_sip: Enable WebSocket support by    |
-   |            |                | default.                                 |
-   +------------------------------------------------------------------------+
-
-     ----------------------------------------------------------------------
-
-                                Diffstat Results
-
-                                 [Back to Top]
-
-   This is a summary of the changes to the source code that went into this
-   release that was generated using the diffstat utility.
-
- asterisk-13.7.0-rc1-summary.html                                                     |  373 ---
- asterisk-13.7.0-rc1-summary.txt                                                      |  967 ---------
- b/.version                                                                           |    2
- b/CHANGES                                                                            |    8
- b/channels/chan_sip.c                                                                |    1
- b/contrib/ast-db-manage/config/versions/189a235b3fd7_add_keep_alive_interval.py      |   22
- b/contrib/ast-db-manage/config/versions/2d078ec071b7_increaes_contact_column_size.py |   22
- b/contrib/realtime/mssql/mssql_config.sql                                            |   14
- b/contrib/realtime/mysql/mysql_config.sql                                            |   12
- b/contrib/realtime/oracle/oracle_config.sql                                          |   14
- b/contrib/realtime/postgresql/postgresql_config.sql                                  |   10
- b/main/aoc.c                                                                         |   20
- b/main/loader.c                                                                      |    4
- b/main/rtp_engine.c                                                                  |   22
- b/main/sorcery.c                                                                     |    6
- b/main/stasis.c                                                                      |    4
- b/main/stasis_channels.c                                                             |   24
- b/res/res_fax.c                                                                      |    4
- b/res/res_pjsip_endpoint_identifier_ip.c                                             |    2
- b/res/res_sorcery_memory_cache.c                                                     | 1040 +++++++++-
- b/res/res_stasis.c                                                                   |    6
- b/res/res_stasis_playback.c                                                          |    2
- 22 files changed, 1136 insertions(+), 1443 deletions(-)
diff --git a/asterisk-13.8.2-summary.html b/asterisk-13.8.2-summary.html
new file mode 100644
index 0000000..7904a9d
--- /dev/null
+++ b/asterisk-13.8.2-summary.html
@@ -0,0 +1,27 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"><html xmlns="http://www.w3.org/1999/xhtml"><title>Release Summary - asterisk-13.8.2</title><h1 align="center"><a name="top">Release Summary</a></h1><h3 align="center">asterisk-13.8.2</h3><h3 align="center">Date: 2016-04-20</h3><h3 align="center"><asteriskteam at digium.com></h3><hr><h2 align="center">Table of Contents</h2><ol>
+<li><a href="#summary">Summary</a></li>
+<li><a href="#contributors">Contributors</a></li>
+<li><a href="#closed_issues">Closed Issues</a></li>
+<li><a href="#commits">Other Changes</a></li>
+<li><a href="#diffstat">Diffstat</a></li>
+</ol><hr><a name="summary"><h2 align="center">Summary</h2></a><center><a href="#top">[Back to Top]</a></center><p>This release is a point release of an existing major version. The changes included were made to address problems that have been identified in this release series, or are minor, backwards compatible new features or improvements. Users should be able to safely upgrade to this version if this release series is already in use. Users considering upgrading from a previous version a [...]
+<tr><th width="33%">Coders</th><th width="33%">Testers</th><th width="33%">Reporters</th></tr>
+<tr valign="top"><td width="33%">5 Joshua Colp <jcolp at digium.com><br/>2 Mark Michelson <mmichelson at digium.com><br/></td><td width="33%"><td width="33%">2 Joshua Colp <jcolp at digium.com><br/>2 Joshua Colp<br/></td></tr>
+</table><hr><a name="closed_issues"><h2 align="center">Closed Issues</h2></a><center><a href="#top">[Back to Top]</a></center><p>This is a list of all issues from the issue tracker that were closed by changes that went into this release.</p><h3>Bug</h3><h4>Category: Resources/res_pjsip</h4><a href="https://issues.asterisk.org/jira/browse/ASTERISK-25928">ASTERISK-25928</a>: res_pjsip: URI validation done outside of PJSIP thread<br/>Reported by: Joshua Colp<ul>
+<li><a href="https://code.asterisk.org/code/changelog/asterisk?cs=856931edc2bff4e6b25f2adf1a9b8ab2ee1a3adc">[856931edc2]</a> Mark Michelson -- PJSIP: Remove PJSIP parsing functions from uri length validation.</li>
+</ul><br><h4>Category: Resources/res_pjsip_registrar</h4><a href="https://issues.asterisk.org/jira/browse/ASTERISK-25929">ASTERISK-25929</a>: res_pjsip_registrar: AOR_CONTACT_ADDED events not raised<br/>Reported by: Joshua Colp<ul>
+<li><a href="https://code.asterisk.org/code/changelog/asterisk?cs=91a3e1184f6d00c8274f9e707a01e2f9d997dada">[91a3e1184f]</a> Mark Michelson -- res_pjsip_registrar: Fix bad memory-ness with user_agent.</li>
+</ul><br><hr><a name="commits"><h2 align="center">Commits Not Associated with an Issue</h2></a><center><a href="#top">[Back to Top]</a></center><p>This is a list of all changes that went into this release that did not reference a JIRA issue.</p><table width="100%" border="1">
+<tr><th>Revision</th><th>Author</th><th>Summary</th></tr>
+<tr><td><a href="https://code.asterisk.org/code/changelog/asterisk?cs=26d67ce88513040c2a6275b986830426c21429c3">26d67ce885</a></td><td>Joshua Colp</td><td>Release summaries: Remove previous versions</td></tr>
+<tr><td><a href="https://code.asterisk.org/code/changelog/asterisk?cs=d9909232ed734dfff19b62d2b0ec44b484184e53">d9909232ed</a></td><td>Joshua Colp</td><td>.version: Update for 13.8.2</td></tr>
+<tr><td><a href="https://code.asterisk.org/code/changelog/asterisk?cs=fc57bb9b15f242bb62686375905de3f7aefc9193">fc57bb9b15</a></td><td>Joshua Colp</td><td>.lastclean: Update for 13.8.2</td></tr>
+<tr><td><a href="https://code.asterisk.org/code/changelog/asterisk?cs=ac04474f383120f8b9b8bfe4ef42df835c7521d3">ac04474f38</a></td><td>Joshua Colp</td><td>realtime: Add database scripts for 13.8.2</td></tr>
+<tr><td><a href="https://code.asterisk.org/code/changelog/asterisk?cs=70e25ced60c7fa2b3ad5fe7b2d27cbf5ac1af12b">70e25ced60</a></td><td>Joshua Colp</td><td>res_pjsip_transport_management: Allow unload to occur.</td></tr>
+</table><hr><a name="diffstat"><h2 align="center">Diffstat Results</h2></a><center><a href="#top">[Back to Top]</a></center><p>This is a summary of the changes to the source code that went into this release that was generated using the diffstat utility.</p><pre>asterisk-13.8.1-summary.html           |   33 --------
+asterisk-13.8.1-summary.txt            |  128 ---------------------------------
+b/.version                             |    2
+b/res/res_pjsip/location.c             |   56 ++++++++++----
+b/res/res_pjsip_registrar.c            |    3
+b/res/res_pjsip_transport_management.c |   17 +++-
+6 files changed, 58 insertions(+), 181 deletions(-)</pre><br></html>
\ No newline at end of file
diff --git a/asterisk-13.8.2-summary.txt b/asterisk-13.8.2-summary.txt
new file mode 100644
index 0000000..1b45600
--- /dev/null
+++ b/asterisk-13.8.2-summary.txt
@@ -0,0 +1,120 @@
+                                Release Summary
+
+                                asterisk-13.8.2
+
+                                Date: 2016-04-20
+
+                           <asteriskteam at digium.com>
+
+     ----------------------------------------------------------------------
+
+                               Table of Contents
+
+    1. Summary
+    2. Contributors
+    3. Closed Issues
+    4. Other Changes
+    5. Diffstat
+
+     ----------------------------------------------------------------------
+
+                                    Summary
+
+                                 [Back to Top]
+
+   This release is a point release of an existing major version. The changes
+   included were made to address problems that have been identified in this
+   release series, or are minor, backwards compatible new features or
+   improvements. Users should be able to safely upgrade to this version if
+   this release series is already in use. Users considering upgrading from a
+   previous version are strongly encouraged to review the UPGRADE.txt
+   document as well as the CHANGES document for information about upgrading
+   to this release series.
+
+   The data in this summary reflects changes that have been made since the
+   previous release, asterisk-13.8.1.
+
+     ----------------------------------------------------------------------
+
+                                  Contributors
+
+                                 [Back to Top]
+
+   This table lists the people who have submitted code, those that have
+   tested patches, as well as those that reported issues on the issue tracker
+   that were resolved in this release. For coders, the number is how many of
+   their patches (of any size) were committed into this release. For testers,
+   the number is the number of times their name was listed as assisting with
+   testing a patch. Finally, for reporters, the number is the number of
+   issues that they reported that were affected by commits that went into
+   this release.
+
+   Coders                   Testers                  Reporters                
+   5 Joshua Colp                                     2 Joshua Colp            
+   2 Mark Michelson                                  2 Joshua Colp            
+
+     ----------------------------------------------------------------------
+
+                                 Closed Issues
+
+                                 [Back to Top]
+
+   This is a list of all issues from the issue tracker that were closed by
+   changes that went into this release.
+
+  Bug
+
+    Category: Resources/res_pjsip
+
+   ASTERISK-25928: res_pjsip: URI validation done outside of PJSIP thread
+   Reported by: Joshua Colp
+     * [856931edc2] Mark Michelson -- PJSIP: Remove PJSIP parsing functions
+       from uri length validation.
+
+    Category: Resources/res_pjsip_registrar
+
+   ASTERISK-25929: res_pjsip_registrar: AOR_CONTACT_ADDED events not raised
+   Reported by: Joshua Colp
+     * [91a3e1184f] Mark Michelson -- res_pjsip_registrar: Fix bad
+       memory-ness with user_agent.
+
+     ----------------------------------------------------------------------
+
+                      Commits Not Associated with an Issue
+
+                                 [Back to Top]
+
+   This is a list of all changes that went into this release that did not
+   reference a JIRA issue.
+
+   +------------------------------------------------------------------------+
+   | Revision   | Author      | Summary                                     |
+   |------------+-------------+---------------------------------------------|
+   | 26d67ce885 | Joshua Colp | Release summaries: Remove previous versions |
+   |------------+-------------+---------------------------------------------|
+   | d9909232ed | Joshua Colp | .version: Update for 13.8.2                 |
+   |------------+-------------+---------------------------------------------|
+   | fc57bb9b15 | Joshua Colp | .lastclean: Update for 13.8.2               |
+   |------------+-------------+---------------------------------------------|
+   | ac04474f38 | Joshua Colp | realtime: Add database scripts for 13.8.2   |
+   |------------+-------------+---------------------------------------------|
+   | 70e25ced60 | Joshua Colp | res_pjsip_transport_management: Allow       |
+   |            |             | unload to occur.                            |
+   +------------------------------------------------------------------------+
+
+     ----------------------------------------------------------------------
+
+                                Diffstat Results
+
+                                 [Back to Top]
+
+   This is a summary of the changes to the source code that went into this
+   release that was generated using the diffstat utility.
+
+ asterisk-13.8.1-summary.html           |   33 --------
+ asterisk-13.8.1-summary.txt            |  128 ---------------------------------
+ b/.version                             |    2
+ b/res/res_pjsip/location.c             |   56 ++++++++++----
+ b/res/res_pjsip_registrar.c            |    3
+ b/res/res_pjsip_transport_management.c |   17 +++-
+ 6 files changed, 58 insertions(+), 181 deletions(-)
diff --git a/build_tools/.gitignore b/build_tools/.gitignore
deleted file mode 100644
index c60a0df..0000000
--- a/build_tools/.gitignore
+++ /dev/null
@@ -1 +0,0 @@
-menuselect-deps
diff --git a/build_tools/cflags.xml b/build_tools/cflags.xml
index d11fb22..d3c3ebe 100644
--- a/build_tools/cflags.xml
+++ b/build_tools/cflags.xml
@@ -1,5 +1,10 @@
 <category name="MENUSELECT_CFLAGS" displayname="Compiler Flags" positive_output="yes" remove_on_change=".lastclean">
 		<member name="DONT_OPTIMIZE" displayname="Disable Optimizations by the Compiler">
+			<use autoselect="yes">COMPILE_DOUBLE</use>
+			<support_level>core</support_level>
+		</member>
+		<member name="COMPILE_DOUBLE" displayname="Pre-compile with optimizations to detect errors, then discard and recompile with DONT_OPTIMIZE.  Creates intermediate .i files">
+			<depend>DONT_OPTIMIZE</depend>
 			<support_level>core</support_level>
 		</member>
 		<member name="DEBUG_THREADS" displayname="Enable Thread Debugging">
@@ -85,32 +90,30 @@
 			<support_level>core</support_level>
 		</member>
 		<member name="ADDRESS_SANITIZER" displayname="Address Sanitizer">
+			<depend>HAVE_ADDRESS_SANITIZER</depend>
 			<support_level>extended</support_level>
 			<conflict>THREAD_SANITIZER</conflict>
 			<conflict>LEAK_SANITIZER</conflict>
-			<conflict>UNDEFINED_SANITIZER</conflict>
 			<conflict>MALLOC_DEBUG</conflict>
 			<conflict>DEBUG_CHAOS</conflict>
 		</member>
 		<member name="THREAD_SANITIZER" displayname="Thread Sanitizer">
+			<depend>HAVE_THREAD_SANITIZER</depend>
 			<support_level>extended</support_level>
 			<conflict>ADDRESS_SANITIZER</conflict>
 			<conflict>LEAK_SANITIZER</conflict>
-			<conflict>UNDEFINED_SANITIZER</conflict>
 		</member>
 		<member name="LEAK_SANITIZER" displayname="Leak Sanitizer">
+			<depend>HAVE_LEAK_SANITIZER</depend>
 			<support_level>extended</support_level>
 			<conflict>ADDRESS_SANITIZER</conflict>
 			<conflict>THREAD_SANITIZER</conflict>
-			<conflict>UNDEFINED_SANITIZER</conflict>
 			<conflict>MALLOC_DEBUG</conflict>
 			<conflict>DEBUG_CHAOS</conflict>
 		</member>
 		<member name="UNDEFINED_SANITIZER" displayname="Undefined Behavior Sanitizer">
+			<depend>HAVE_UNDEFINED_SANITIZER</depend>
 			<support_level>extended</support_level>
-			<conflict>ADDRESS_SANITIZER</conflict>
-			<conflict>THREAD_SANITIZER</conflict>
-			<conflict>LEAK_SANITIZER</conflict>
 		</member>
 		<member name="BUSYDETECT_TONEONLY" displayname="Enable additional comparision of only the tone duration not the silence part">
 			<conflict>BUSYDETECT_COMPARE_TONE_AND_SILENCE</conflict>
diff --git a/build_tools/make_check_alembic b/build_tools/make_check_alembic
new file mode 100755
index 0000000..ecc5fc1
--- /dev/null
+++ b/build_tools/make_check_alembic
@@ -0,0 +1,29 @@
+#!/bin/sh
+if [ -z "$ALEMBIC" -o ! -d contrib/ast-db-manage ]; then
+	echo "Run 'make check-alembic' to use this script" >&2
+	exit 1
+fi
+
+if [ "$ALEMBIC" = ":" ]; then
+	echo "Install alembic and re-run configure before using this target."
+	exit 1
+fi
+
+cd contrib/ast-db-manage
+
+FOUNDERROR=
+for id in "$@"; do
+	if [ -n "$($ALEMBIC -c ${id}.ini.sample branches)" ]; then
+		echo "Alembic branches exist for $id - details follow:"
+		# This second run is needed to display the errors because
+		# formatting was lost in the first execution.
+		$ALEMBIC -c ${id}.ini.sample branches
+		# Display all errors before reporting failure to Make.
+		FOUNDERROR=yes
+	fi
+done
+
+if [ -n "$FOUNDERROR" ]; then
+	# One or more failures.
+	exit 1
+fi
diff --git a/build_tools/menuselect-deps.in b/build_tools/menuselect-deps.in
index a77530b..e5413f1 100644
--- a/build_tools/menuselect-deps.in
+++ b/build_tools/menuselect-deps.in
@@ -72,3 +72,7 @@ WINARCH=@PBX_WINARCH@
 ZLIB=@PBX_ZLIB@
 TIMERFD=@PBX_TIMERFD@
 NATIVE_ARCH=@AST_NATIVE_ARCH@
+HAVE_ADDRESS_SANITIZER=@AST_ADDRESS_SANITIZER@
+HAVE_LEAK_SANITIZER=@AST_LEAK_SANITIZER@
+HAVE_THREAD_SANITIZER=@AST_THREAD_SANITIZER@
+HAVE_UNDEFINED_SANITIZER=@AST_UNDEFINED_SANITIZER@
diff --git a/cel/cel_radius.c b/cel/cel_radius.c
index 79caf22..7b41887 100644
--- a/cel/cel_radius.c
+++ b/cel/cel_radius.c
@@ -95,7 +95,7 @@ static rc_handle *rh = NULL;
 
 #define RADIUS_BACKEND_NAME "CEL Radius Logging"
 
-#define ADD_VENDOR_CODE(x,y) (rc_avpair_add(rh, send, x, &y, strlen(y), VENDOR_CODE))
+#define ADD_VENDOR_CODE(x,y) (rc_avpair_add(rh, send, x, (void *)y, strlen(y), VENDOR_CODE))
 
 static int build_radius_record(VALUE_PAIR **send, struct ast_cel_event_record *record)
 {
@@ -176,7 +176,7 @@ static int build_radius_record(VALUE_PAIR **send, struct ast_cel_event_record *r
 	/* Setting Acct-Session-Id & User-Name attributes for proper generation
 	   of Acct-Unique-Session-Id on server side */
 	/* Channel */
-	if (!rc_avpair_add(rh, send, PW_USER_NAME, &record->channel_name,
+	if (!rc_avpair_add(rh, send, PW_USER_NAME, (void *)record->channel_name,
 			strlen(record->channel_name), 0)) {
 		return -1;
 	}
diff --git a/channels/chan_dahdi.c b/channels/chan_dahdi.c
index ef0ad65..e9d0b5b 100644
--- a/channels/chan_dahdi.c
+++ b/channels/chan_dahdi.c
@@ -4705,7 +4705,7 @@ static int drc_sample(int sample, float drc)
 	neg = (sample < 0 ? -1 : 1);
 	steep = drc*sample;
 	shallow = neg*(max-max/drc)+(float)sample/drc;
-	if (abs(steep) < abs(shallow)) {
+	if (fabsf(steep) < fabsf(shallow)) {
 		sample = steep;
 	}
 	else {
diff --git a/channels/chan_misdn.c b/channels/chan_misdn.c
index ae8bcd7..d1e5567 100644
--- a/channels/chan_misdn.c
+++ b/channels/chan_misdn.c
@@ -369,8 +369,8 @@ struct hold_info {
 	int channel;
 };
 
-#define chan_list_ref(obj, debug) (ao2_t_ref((obj), +1, (debug)), (obj))
-#define chan_list_unref(obj, debug) (ao2_t_ref((obj), -1, (debug)), NULL)
+#define chan_list_ref(obj, debug) ao2_t_ref((obj), +1, (debug))
+#define chan_list_unref(obj, debug) ao2_t_ref((obj), -1, (debug))
 
 /*!
  * \brief Channel call record structure
diff --git a/channels/chan_motif.c b/channels/chan_motif.c
index 24575fa..640976f 100644
--- a/channels/chan_motif.c
+++ b/channels/chan_motif.c
@@ -320,7 +320,6 @@ struct jingle_session {
 	struct ast_callid *callid;            /*!< Bound session call-id */
 };
 
-static const char desc[] = "Motif Jingle Channel";
 static const char channel_type[] = "Motif";
 
 struct jingle_config {
diff --git a/channels/chan_pjsip.c b/channels/chan_pjsip.c
index 0a8d1bc..07887af 100644
--- a/channels/chan_pjsip.c
+++ b/channels/chan_pjsip.c
@@ -223,17 +223,6 @@ static void chan_pjsip_get_codec(struct ast_channel *chan, struct ast_format_cap
 	ast_format_cap_append_from_cap(result, channel->session->endpoint->media.codecs, AST_MEDIA_TYPE_UNKNOWN);
 }
 
-static int send_direct_media_request(void *data)
-{
-	struct ast_sip_session *session = data;
-	int res;
-
-	res = ast_sip_session_refresh(session, NULL, NULL, NULL,
-		session->endpoint->media.direct_media.method, 1);
-	ao2_ref(session, -1);
-	return res;
-}
-
 /*! \brief Destructor function for \ref transport_info_data */
 static void transport_info_destroy(void *obj)
 {
@@ -302,6 +291,84 @@ static int check_for_rtp_changes(struct ast_channel *chan, struct ast_rtp_instan
 	return changed;
 }
 
+struct rtp_direct_media_data {
+	struct ast_channel *chan;
+	struct ast_rtp_instance *rtp;
+	struct ast_rtp_instance *vrtp;
+	struct ast_format_cap *cap;
+	struct ast_sip_session *session;
+};
+
+static void rtp_direct_media_data_destroy(void *data)
+{
+	struct rtp_direct_media_data *cdata = data;
+
+	ao2_cleanup(cdata->session);
+	ao2_cleanup(cdata->cap);
+	ao2_cleanup(cdata->vrtp);
+	ao2_cleanup(cdata->rtp);
+	ao2_cleanup(cdata->chan);
+}
+
+static struct rtp_direct_media_data *rtp_direct_media_data_create(
+	struct ast_channel *chan, struct ast_rtp_instance *rtp, struct ast_rtp_instance *vrtp,
+	const struct ast_format_cap *cap, struct ast_sip_session *session)
+{
+	struct rtp_direct_media_data *cdata = ao2_alloc(sizeof(*cdata), rtp_direct_media_data_destroy);
+
+	if (!cdata) {
+		return NULL;
+	}
+
+	cdata->chan = ao2_bump(chan);
+	cdata->rtp = ao2_bump(rtp);
+	cdata->vrtp = ao2_bump(vrtp);
+	cdata->cap = ao2_bump((struct ast_format_cap *)cap);
+	cdata->session = ao2_bump(session);
+
+	return cdata;
+}
+
+static int send_direct_media_request(void *data)
+{
+	struct rtp_direct_media_data *cdata = data;
+	struct ast_sip_channel_pvt *channel = ast_channel_tech_pvt(cdata->chan);
+	struct chan_pjsip_pvt *pvt = channel->pvt;
+	int changed = 0;
+	int res = 0;
+
+	if (pvt->media[SIP_MEDIA_AUDIO]) {
+		changed |= check_for_rtp_changes(
+			cdata->chan, cdata->rtp, pvt->media[SIP_MEDIA_AUDIO], 1);
+	}
+	if (pvt->media[SIP_MEDIA_VIDEO]) {
+		changed |= check_for_rtp_changes(
+			cdata->chan, cdata->vrtp, pvt->media[SIP_MEDIA_VIDEO], 3);
+	}
+
+	if (direct_media_mitigate_glare(cdata->session)) {
+		ast_debug(4, "Disregarding setting RTP on %s: mitigating re-INVITE glare\n", ast_channel_name(cdata->chan));
+		ao2_ref(cdata, -1);
+		return 0;
+	}
+
+	if (cdata->cap && ast_format_cap_count(cdata->cap) &&
+	    !ast_format_cap_identical(cdata->session->direct_media_cap, cdata->cap)) {
+		ast_format_cap_remove_by_type(cdata->session->direct_media_cap, AST_MEDIA_TYPE_UNKNOWN);
+		ast_format_cap_append_from_cap(cdata->session->direct_media_cap, cdata->cap, AST_MEDIA_TYPE_UNKNOWN);
+		changed = 1;
+	}
+
+	if (changed) {
+		ast_debug(4, "RTP changed on %s; initiating direct media update\n", ast_channel_name(cdata->chan));
+		res = ast_sip_session_refresh(cdata->session, NULL, NULL, NULL,
+			cdata->session->endpoint->media.direct_media.method, 1);
+	}
+
+	ao2_ref(cdata, -1);
+	return res;
+}
+
 /*! \brief Function called by RTP engine to change where the remote party should send media */
 static int chan_pjsip_set_rtp_peer(struct ast_channel *chan,
 		struct ast_rtp_instance *rtp,
@@ -311,9 +378,8 @@ static int chan_pjsip_set_rtp_peer(struct ast_channel *chan,
 		int nat_active)
 {
 	struct ast_sip_channel_pvt *channel = ast_channel_tech_pvt(chan);
-	struct chan_pjsip_pvt *pvt = channel->pvt;
 	struct ast_sip_session *session = channel->session;
-	int changed = 0;
+	struct rtp_direct_media_data *cdata;
 
 	/* Don't try to do any direct media shenanigans on early bridges */
 	if ((rtp || vrtp || tpeer) && !ast_channel_is_bridged(chan)) {
@@ -326,31 +392,14 @@ static int chan_pjsip_set_rtp_peer(struct ast_channel *chan,
 		return 0;
 	}
 
-	if (pvt->media[SIP_MEDIA_AUDIO]) {
-		changed |= check_for_rtp_changes(chan, rtp, pvt->media[SIP_MEDIA_AUDIO], 1);
-	}
-	if (pvt->media[SIP_MEDIA_VIDEO]) {
-		changed |= check_for_rtp_changes(chan, vrtp, pvt->media[SIP_MEDIA_VIDEO], 3);
-	}
-
-	if (direct_media_mitigate_glare(session)) {
-		ast_debug(4, "Disregarding setting RTP on %s: mitigating re-INVITE glare\n", ast_channel_name(chan));
+	cdata = rtp_direct_media_data_create(chan, rtp, vrtp, cap, session);
+	if (!cdata) {
 		return 0;
 	}
 
-	if (cap && ast_format_cap_count(cap) && !ast_format_cap_identical(session->direct_media_cap, cap)) {
-		ast_format_cap_remove_by_type(session->direct_media_cap, AST_MEDIA_TYPE_UNKNOWN);
-		ast_format_cap_append_from_cap(session->direct_media_cap, cap, AST_MEDIA_TYPE_UNKNOWN);
-		changed = 1;
-	}
-
-	if (changed) {
-		ao2_ref(session, +1);
-
-		ast_debug(4, "RTP changed on %s; initiating direct media update\n", ast_channel_name(chan));
-		if (ast_sip_push_task(session->serializer, send_direct_media_request, session)) {
-			ao2_cleanup(session);
-		}
+	if (ast_sip_push_task(session->serializer, send_direct_media_request, cdata)) {
+		ast_log(LOG_ERROR, "Unable to send direct media request for channel %s\n", ast_channel_name(chan));
+		ao2_ref(cdata, -1);
 	}
 
 	return 0;
diff --git a/channels/chan_sip.c b/channels/chan_sip.c
index 500aeb7..9e7872b 100644
--- a/channels/chan_sip.c
+++ b/channels/chan_sip.c
@@ -710,7 +710,7 @@ static const struct  cfsip_methods {
  */
 static const struct sip_reasons {
 	enum AST_REDIRECTING_REASON code;
-	char * const text;
+	const char *text;
 } sip_reason_table[] = {
 	{ AST_REDIRECTING_REASON_UNKNOWN, "unknown" },
 	{ AST_REDIRECTING_REASON_USER_BUSY, "user-busy" },
@@ -723,8 +723,8 @@ static const struct sip_reasons {
 	{ AST_REDIRECTING_REASON_FOLLOW_ME, "follow-me" },
 	{ AST_REDIRECTING_REASON_OUT_OF_ORDER, "out-of-service" },
 	{ AST_REDIRECTING_REASON_AWAY, "away" },
-	{ AST_REDIRECTING_REASON_CALL_FWD_DTE, "unknown"},
-	{ AST_REDIRECTING_REASON_SEND_TO_VM, "send_to_vm"},
+	{ AST_REDIRECTING_REASON_CALL_FWD_DTE, "cf_dte" },		/* Non-standard */
+	{ AST_REDIRECTING_REASON_SEND_TO_VM, "send_to_vm" },	/* Non-standard */
 };
 
 
@@ -1372,7 +1372,6 @@ static void ast_sip_ouraddrfor(const struct ast_sockaddr *them, struct ast_socka
 static void sip_registry_destroy(void *reg);
 static int sip_register(const char *value, int lineno);
 static const char *regstate2str(enum sipregistrystate regstate) attribute_const;
-static int sip_reregister(const void *data);
 static int __sip_do_register(struct sip_registry *r);
 static int sip_reg_timeout(const void *data);
 static void sip_send_all_registers(void);
@@ -1486,7 +1485,6 @@ static void change_t38_state(struct sip_pvt *p, int state);
 
 /*------ Session-Timers functions --------- */
 static void proc_422_rsp(struct sip_pvt *p, struct sip_request *rsp);
-static int  proc_session_timer(const void *vp);
 static void stop_session_timer(struct sip_pvt *p);
 static void start_session_timer(struct sip_pvt *p);
 static void restart_session_timer(struct sip_pvt *p);
@@ -1503,11 +1501,16 @@ static int sip_set_rtp_peer(struct ast_channel *chan, struct ast_rtp_instance *i
 
 /*!--- SIP MWI Subscription support */
 static int sip_subscribe_mwi(const char *value, int lineno);
-static void sip_subscribe_mwi_destroy(void *data);
 static void sip_send_all_mwi_subscriptions(void);
-static int sip_subscribe_mwi_do(const void *data);
 static int __sip_subscribe_mwi_do(struct sip_subscription_mwi *mwi);
 
+/* Scheduler id start/stop/reschedule functions. */
+static void stop_provisional_keepalive(struct sip_pvt *pvt);
+static void do_stop_session_timer(struct sip_pvt *pvt);
+static void stop_reinvite_retry(struct sip_pvt *pvt);
+static void stop_retrans_pkt(struct sip_pkt *pkt);
+static void stop_t38_abort_timer(struct sip_pvt *pvt);
+
 /*! \brief Definition of this channel for PBX channel registration */
 struct ast_channel_tech sip_tech = {
 	.type = "SIP",
@@ -2370,52 +2373,54 @@ static int map_s_x(const struct _map_x_s *table, const char *s, int errorvalue)
 	return errorvalue;
 }
 
-static enum AST_REDIRECTING_REASON sip_reason_str_to_code(const char *text)
+/*!
+ * \internal
+ * \brief Determine if the given string is a SIP token.
+ * \since 13.8.0
+ *
+ * \param str String to determine if is a SIP token.
+ *
+ * \note A token is defined by RFC3261 Section 25.1
+ *
+ * \return Non-zero if the string is a SIP token.
+ */
+static int sip_is_token(const char *str)
 {
-	enum AST_REDIRECTING_REASON ast = AST_REDIRECTING_REASON_UNKNOWN;
-	int i;
+	int is_token;
 
-	for (i = 0; i < ARRAY_LEN(sip_reason_table); ++i) {
-		if (!strcasecmp(text, sip_reason_table[i].text)) {
-			ast = sip_reason_table[i].code;
+	if (ast_strlen_zero(str)) {
+		/* An empty string is not a token. */
+		return 0;
+	}
+
+	is_token = 1;
+	do {
+		if (!isalnum(*str)
+			&& !strchr("-.!%*_+`'~", *str)) {
+			/* The character is not allowed in a token. */
+			is_token = 0;
 			break;
 		}
-	}
+	} while (*++str);
 
-	return ast;
+	return is_token;
 }
 
-static const char *sip_reason_code_to_str(struct ast_party_redirecting_reason *reason, int *table_lookup)
+static const char *sip_reason_code_to_str(struct ast_party_redirecting_reason *reason)
 {
-	int code = reason->code;
+	int idx;
+	int code;
 
-	/* If there's a specific string set, then we just
-	 * use it.
-	 */
+	/* use specific string if given */
 	if (!ast_strlen_zero(reason->str)) {
-		/* If we care about whether this can be found in
-		 * the table, then we need to check about that.
-		 */
-		if (table_lookup) {
-			/* If the string is literally "unknown" then don't bother with the lookup
-			 * because it can lead to a false negative.
-			 */
-			if (!strcasecmp(reason->str, "unknown") ||
-					sip_reason_str_to_code(reason->str) != AST_REDIRECTING_REASON_UNKNOWN) {
-				*table_lookup = TRUE;
-			} else {
-				*table_lookup = FALSE;
-			}
-		}
 		return reason->str;
 	}
 
-	if (table_lookup) {
-		*table_lookup = TRUE;
-	}
-
-	if (code >= 0 && code < ARRAY_LEN(sip_reason_table)) {
-		return sip_reason_table[code].text;
+	code = reason->code;
+	for (idx = 0; idx < ARRAY_LEN(sip_reason_table); ++idx) {
+		if (code == sip_reason_table[idx].code) {
+			return sip_reason_table[idx].text;
+		}
 	}
 
 	return "unknown";
@@ -3238,6 +3243,58 @@ static void ref_proxy(struct sip_pvt *pvt, struct sip_proxy *proxy)
 	}
 }
 
+static void do_dialog_unlink_sched_items(struct sip_pvt *dialog)
+{
+	struct sip_pkt *cp;
+
+	/* remove all current packets in this dialog */
+	sip_pvt_lock(dialog);
+	while ((cp = dialog->packets)) {
+		/* Unlink and destroy the packet object. */
+		dialog->packets = dialog->packets->next;
+		AST_SCHED_DEL_UNREF(sched, cp->retransid,
+			ao2_t_ref(cp, -1, "Stop scheduled packet retransmission"));
+		ao2_t_ref(cp, -1, "Packet retransmission list");
+	}
+	sip_pvt_unlock(dialog);
+
+	AST_SCHED_DEL_UNREF(sched, dialog->waitid,
+		dialog_unref(dialog, "Stop scheduled waitid"));
+
+	AST_SCHED_DEL_UNREF(sched, dialog->initid,
+		dialog_unref(dialog, "Stop scheduled initid"));
+
+	AST_SCHED_DEL_UNREF(sched, dialog->reinviteid,
+		dialog_unref(dialog, "Stop scheduled reinviteid"));
+
+	AST_SCHED_DEL_UNREF(sched, dialog->autokillid,
+		dialog_unref(dialog, "Stop scheduled autokillid"));
+
+	AST_SCHED_DEL_UNREF(sched, dialog->request_queue_sched_id,
+		dialog_unref(dialog, "Stop scheduled request_queue_sched_id"));
+
+	AST_SCHED_DEL_UNREF(sched, dialog->provisional_keepalive_sched_id,
+		dialog_unref(dialog, "Stop scheduled provisional keepalive"));
+
+	AST_SCHED_DEL_UNREF(sched, dialog->t38id,
+		dialog_unref(dialog, "Stop scheduled t38id"));
+
+	if (dialog->stimer) {
+		dialog->stimer->st_active = FALSE;
+		do_stop_session_timer(dialog);
+	}
+}
+
+/* Run by the sched thread. */
+static int __dialog_unlink_sched_items(const void *data)
+{
+	struct sip_pvt *dialog = (void *) data;
+
+	do_dialog_unlink_sched_items(dialog);
+	dialog_unref(dialog, "Stop scheduled items for unlink action");
+	return 0;
+}
+
 /*!
  * \brief Unlink a dialog from the dialogs container, as well as any other places
  * that it may be currently stored.
@@ -3247,7 +3304,6 @@ static void ref_proxy(struct sip_pvt *pvt, struct sip_proxy *proxy)
  */
 void dialog_unlink_all(struct sip_pvt *dialog)
 {
-	struct sip_pkt *cp;
 	struct ast_channel *owner;
 
 	dialog_ref(dialog, "Let's bump the count in the unlink so it doesn't accidentally become dead before we are done");
@@ -3285,41 +3341,14 @@ void dialog_unlink_all(struct sip_pvt *dialog)
 		dialog->relatedpeer->call = dialog_unref(dialog->relatedpeer->call, "unset the relatedpeer->call field in tandem with relatedpeer field itself");
 	}
 
-	/* remove all current packets in this dialog */
-	while((cp = dialog->packets)) {
-		dialog->packets = dialog->packets->next;
-		AST_SCHED_DEL(sched, cp->retransid);
-		dialog_unref(cp->owner, "remove all current packets in this dialog, and the pointer to the dialog too as part of __sip_destroy");
-		if (cp->data) {
-			ast_free(cp->data);
-		}
-		ast_free(cp);
-	}
-
-	AST_SCHED_DEL_UNREF(sched, dialog->waitid, dialog_unref(dialog, "when you delete the waitid sched, you should dec the refcount for the stored dialog ptr"));
-
-	AST_SCHED_DEL_UNREF(sched, dialog->initid, dialog_unref(dialog, "when you delete the initid sched, you should dec the refcount for the stored dialog ptr"));
-
-	if (dialog->reinviteid > -1) {
-		AST_SCHED_DEL_UNREF(sched, dialog->reinviteid, dialog_unref(dialog, "clear ref for reinvite_timeout"));
-	}
-
-	if (dialog->autokillid > -1) {
-		AST_SCHED_DEL_UNREF(sched, dialog->autokillid, dialog_unref(dialog, "when you delete the autokillid sched, you should dec the refcount for the stored dialog ptr"));
-	}
-
-	if (dialog->request_queue_sched_id > -1) {
-		AST_SCHED_DEL_UNREF(sched, dialog->request_queue_sched_id, dialog_unref(dialog, "when you delete the request_queue_sched_id sched, you should dec the refcount for the stored dialog ptr"));
-	}
-
-	AST_SCHED_DEL_UNREF(sched, dialog->provisional_keepalive_sched_id, dialog_unref(dialog, "when you delete the provisional_keepalive_sched_id, you should dec the refcount for the stored dialog ptr"));
-
-	if (dialog->t38id > -1) {
-		AST_SCHED_DEL_UNREF(sched, dialog->t38id, dialog_unref(dialog, "when you delete the t38id sched, you should dec the refcount for the stored dialog ptr"));
-	}
-
-	if (dialog->stimer) {
-		stop_session_timer(dialog);
+	dialog_ref(dialog, "Stop scheduled items for unlink action");
+	if (ast_sched_add(sched, 0, __dialog_unlink_sched_items, dialog) < 0) {
+		/*
+		 * Uh Oh.  Fall back to unscheduling things immediately
+		 * despite the potential deadlock risk.
+		 */
+		dialog_unref(dialog, "Failed to schedule stop scheduled items for unlink action");
+		do_dialog_unlink_sched_items(dialog);
 	}
 
 	dialog_unref(dialog, "Let's unbump the count in the unlink so the poor pvt can disappear if it is time");
@@ -3920,10 +3949,17 @@ static void append_history_full(struct sip_pvt *p, const char *fmt, ...)
 	return;
 }
 
-/*! \brief Retransmit SIP message if no answer (Called from scheduler) */
+/*!
+ * \brief Retransmit SIP message if no answer
+ *
+ * \note Run by the sched thread.
+ */
 static int retrans_pkt(const void *data)
 {
-	struct sip_pkt *pkt = (struct sip_pkt *)data, *prev, *cur = NULL;
+	struct sip_pkt *pkt = (struct sip_pkt *) data;
+	struct sip_pkt *prev;
+	struct sip_pkt *cur;
+	struct ast_channel *owner_chan;
 	int reschedule = DEFAULT_RETRANS;
 	int xmitres = 0;
 	/* how many ms until retrans timeout is reached */
@@ -3965,6 +4001,13 @@ static int retrans_pkt(const void *data)
 			}
 
 			/* For non-invites, a maximum of 4 secs */
+			if (INT_MAX / pkt->timer_a < pkt->timer_t1) {
+				/*
+				 * Uh Oh, we will have an integer overflow.
+				 * Recalculate previous timeout time instead.
+				 */
+				pkt->timer_a = pkt->timer_a / 2;
+			}
 			siptimer_a = pkt->timer_t1 * pkt->timer_a;	/* Double each time */
 			if (pkt->method != SIP_INVITE && siptimer_a > 4000) {
 				siptimer_a = 4000;
@@ -3981,6 +4024,7 @@ static int retrans_pkt(const void *data)
 
 		if (sip_debug_test_pvt(pkt->owner)) {
 			const struct ast_sockaddr *dst = sip_real_dst(pkt->owner);
+
 			ast_verbose("Retransmitting #%d (%s) to %s:\n%s\n---\n",
 				pkt->retrans, sip_nat_mode(pkt->owner),
 				ast_sockaddr_stringify(dst),
@@ -4001,7 +4045,7 @@ static int retrans_pkt(const void *data)
 				reschedule = diff;
 			}
 			sip_pvt_unlock(pkt->owner);
-			return  reschedule;
+			return reschedule;
 		}
 	}
 
@@ -4031,16 +4075,11 @@ static int retrans_pkt(const void *data)
 		append_history(pkt->owner, "MaxRetries", "%s", pkt->is_fatal ? "(Critical)" : "(Non-critical)");
 	}
 
+	sip_pvt_unlock(pkt->owner);	/* SIP_PVT, not channel */
+	owner_chan = sip_pvt_lock_full(pkt->owner);
+
 	if (pkt->is_fatal) {
-		while(pkt->owner->owner && ast_channel_trylock(pkt->owner->owner)) {
-			sip_pvt_unlock(pkt->owner);	/* SIP_PVT, not channel */
-			usleep(1);
-			sip_pvt_lock(pkt->owner);
-		}
-		if (pkt->owner->owner && !ast_channel_hangupcause(pkt->owner->owner)) {
-			ast_channel_hangupcause_set(pkt->owner->owner, AST_CAUSE_NO_USER_RESPONSE);
-		}
-		if (pkt->owner->owner) {
+		if (owner_chan) {
 			ast_log(LOG_WARNING, "Hanging up call %s - no reply to our critical packet (see https://wiki.asterisk.org/wiki/display/AST/SIP+Retransmissions).\n", pkt->owner->callid);
 
 			if (pkt->is_resp &&
@@ -4061,8 +4100,10 @@ static int retrans_pkt(const void *data)
 				/* there is nothing left to do, mark the dialog as gone */
 				sip_alreadygone(pkt->owner);
 			}
-			ast_queue_hangup_with_cause(pkt->owner->owner, AST_CAUSE_NO_USER_RESPONSE);
-			ast_channel_unlock(pkt->owner->owner);
+			if (!ast_channel_hangupcause(owner_chan)) {
+				ast_channel_hangupcause_set(owner_chan, AST_CAUSE_NO_USER_RESPONSE);
+			}
+			ast_queue_hangup_with_cause(owner_chan, AST_CAUSE_NO_USER_RESPONSE);
 		} else {
 			/* If no channel owner, destroy now */
 
@@ -4080,38 +4121,68 @@ static int retrans_pkt(const void *data)
 	       check_pendings(pkt->owner);
 	}
 
+	if (owner_chan) {
+		ast_channel_unlock(owner_chan);
+		ast_channel_unref(owner_chan);
+	}
+
 	if (pkt->method == SIP_BYE) {
 		/* We're not getting answers on SIP BYE's.  Tear down the call anyway. */
 		sip_alreadygone(pkt->owner);
-		if (pkt->owner->owner) {
-			ast_channel_unlock(pkt->owner->owner);
-		}
 		append_history(pkt->owner, "ByeFailure", "Remote peer doesn't respond to bye. Destroying call anyway.");
 		pvt_set_needdestroy(pkt->owner, "no response to BYE");
 	}
 
-	/* Remove the packet */
+	/* Unlink and destroy the packet object. */
 	for (prev = NULL, cur = pkt->owner->packets; cur; prev = cur, cur = cur->next) {
 		if (cur == pkt) {
+			/* Unlink the node from the list. */
 			UNLINK(cur, pkt->owner->packets, prev);
-			sip_pvt_unlock(pkt->owner);
-			if (pkt->owner) {
-				pkt->owner = dialog_unref(pkt->owner,"pkt is being freed, its dialog ref is dead now");
-			}
-			if (pkt->data) {
-				ast_free(pkt->data);
-			}
-			pkt->data = NULL;
-			ast_free(pkt);
-			return 0;
+			ao2_t_ref(pkt, -1, "Packet retransmission list (retransmission complete)");
+			break;
 		}
 	}
-	/* error case */
-	ast_log(LOG_WARNING, "Weird, couldn't find packet owner!\n");
+
+	/*
+	 * If the object was not in the list then we were in the process of
+	 * stopping retransmisions while we were sending this retransmission.
+	 */
+
 	sip_pvt_unlock(pkt->owner);
+	ao2_t_ref(pkt, -1, "Scheduled packet retransmission complete");
 	return 0;
 }
 
+/* Run by the sched thread. */
+static int __stop_retrans_pkt(const void *data)
+{
+	struct sip_pkt *pkt = (void *) data;
+
+	AST_SCHED_DEL_UNREF(sched, pkt->retransid,
+		ao2_t_ref(pkt, -1, "Stop scheduled packet retransmission"));
+	ao2_t_ref(pkt, -1, "Stop packet retransmission action");
+	return 0;
+}
+
+static void stop_retrans_pkt(struct sip_pkt *pkt)
+{
+	ao2_t_ref(pkt, +1, "Stop packet retransmission action");
+	if (ast_sched_add(sched, 0, __stop_retrans_pkt, pkt) < 0) {
+		/* Uh Oh.  Expect bad behavior. */
+		ao2_t_ref(pkt, -1, "Failed to schedule stop packet retransmission action");
+	}
+}
+
+static void sip_pkt_dtor(void *vdoomed)
+{
+	struct sip_pkt *pkt = (void *) vdoomed;
+
+	if (pkt->owner) {
+		dialog_unref(pkt->owner, "Retransmission packet is being destroyed");
+	}
+	ast_free(pkt->data);
+}
+
 /*!
  * \internal
  * \brief Transmit packet with retransmits
@@ -4142,12 +4213,14 @@ static enum sip_result __sip_reliable_xmit(struct sip_pvt *p, uint32_t seqno, in
 		}
 	}
 
-	if (!(pkt = ast_calloc(1, sizeof(*pkt)))) {
+	pkt = ao2_alloc_options(sizeof(*pkt), sip_pkt_dtor, AO2_ALLOC_OPT_LOCK_NOLOCK);
+	if (!pkt) {
 		return AST_FAILURE;
 	}
 	/* copy data, add a terminator and save length */
-	if (!(pkt->data = ast_str_create(ast_str_strlen(data)))) {
-		ast_free(pkt);
+	pkt->data = ast_str_create(ast_str_strlen(data));
+	if (!pkt->data) {
+		ao2_t_ref(pkt, -1, "Failed to initialize");
 		return AST_FAILURE;
 	}
 	ast_str_set(&pkt->data, 0, "%s%s", ast_str_buffer(data), "\0");
@@ -4157,8 +4230,11 @@ static enum sip_result __sip_reliable_xmit(struct sip_pvt *p, uint32_t seqno, in
 	pkt->is_resp = resp;
 	pkt->is_fatal = fatal;
 	pkt->owner = dialog_ref(p, "__sip_reliable_xmit: setting pkt->owner");
+
+	/* The retransmission list owns a pkt ref */
 	pkt->next = p->packets;
 	p->packets = pkt;	/* Add it to the queue */
+
 	if (resp) {
 		/* Parse out the response code */
 		if (sscanf(ast_str_buffer(pkt->data), "SIP/2.0 %30u", &respid) == 1) {
@@ -4166,7 +4242,6 @@ static enum sip_result __sip_reliable_xmit(struct sip_pvt *p, uint32_t seqno, in
 		}
 	}
 	pkt->timer_t1 = p->timer_t1;	/* Set SIP timer T1 */
-	pkt->retransid = -1;
 	if (pkt->timer_t1) {
 		siptimer_a = pkt->timer_t1;
 	}
@@ -4175,7 +4250,12 @@ static enum sip_result __sip_reliable_xmit(struct sip_pvt *p, uint32_t seqno, in
 	pkt->retrans_stop_time = 64 * (pkt->timer_t1 ? pkt->timer_t1 : DEFAULT_TIMER_T1); /* time in ms after pkt->time_sent to stop retransmission */
 
 	/* Schedule retransmission */
-	AST_SCHED_REPLACE_VARIABLE(pkt->retransid, sched, siptimer_a, retrans_pkt, pkt, 1);
+	ao2_t_ref(pkt, +1, "Schedule packet retransmission");
+	pkt->retransid = ast_sched_add_variable(sched, siptimer_a, retrans_pkt, pkt, 1);
+	if (pkt->retransid < 0) {
+		ao2_t_ref(pkt, -1, "Failed to schedule packet retransmission");
+	}
+
 	if (sipdebug) {
 		ast_debug(4, "*** SIP TIMER: Initializing retransmit timer on packet: Id  #%d\n", pkt->retransid);
 	}
@@ -4185,11 +4265,11 @@ static enum sip_result __sip_reliable_xmit(struct sip_pvt *p, uint32_t seqno, in
 	if (xmitres == XMIT_ERROR) {	/* Serious network trouble, no need to try again */
 		append_history(pkt->owner, "XmitErr", "%s", pkt->is_fatal ? "(Critical)" : "(Non-critical)");
 		ast_log(LOG_ERROR, "Serious Network Trouble; __sip_xmit returns error for pkt data\n");
-		AST_SCHED_DEL(sched, pkt->retransid);
+
+		/* Unlink and destroy the packet object. */
 		p->packets = pkt->next;
-		pkt->owner = dialog_unref(pkt->owner,"pkt is being freed, its dialog ref is dead now");
-		ast_free(pkt->data);
-		ast_free(pkt);
+		stop_retrans_pkt(pkt);
+		ao2_t_ref(pkt, -1, "Packet retransmission list");
 		return AST_FAILURE;
 	} else {
 		/* This is odd, but since the retrans timer starts at 500ms and the do_monitor thread
@@ -4216,6 +4296,7 @@ static int __sip_autodestruct(const void *data)
 	/* If this is a subscription, tell the phone that we got a timeout */
 	if (p->subscribed && p->subscribed != MWI_NOTIFICATION && p->subscribed != CALL_COMPLETION) {
 		struct state_notify_data data = { 0, };
+
 		data.state = AST_EXTENSION_DEACTIVATED;
 
 		transmit_state_notify(p, &data, 1, TRUE);	/* Send last notification */
@@ -4229,6 +4310,7 @@ static int __sip_autodestruct(const void *data)
 	if (p->packets) {
 		if (!p->needdestroy) {
 			char method_str[31];
+
 			ast_debug(3, "Re-scheduled destruction of SIP call %s\n", p->callid ? p->callid : "<unknown>");
 			append_history(p, "ReliableXmit", "timeout");
 			if (sscanf(p->lastmsg, "Tx: %30s", method_str) == 1 || sscanf(p->lastmsg, "Rx: %30s", method_str) == 1) {
@@ -4243,66 +4325,118 @@ static int __sip_autodestruct(const void *data)
 		}
 	}
 
-	/* Reset schedule ID */
-	p->autokillid = -1;
-
 	/*
 	 * Lock both the pvt and the channel safely so that we can queue up a frame.
 	 */
 	owner = sip_pvt_lock_full(p);
 	if (owner) {
-		ast_log(LOG_WARNING, "Autodestruct on dialog '%s' with owner %s in place (Method: %s). Rescheduling destruction for 10000 ms\n", p->callid, ast_channel_name(owner), sip_methods[p->method].text);
+		ast_log(LOG_WARNING,
+			"Autodestruct on dialog '%s' with owner %s in place (Method: %s). Rescheduling destruction for 10000 ms\n",
+			p->callid, ast_channel_name(owner), sip_methods[p->method].text);
 		ast_queue_hangup_with_cause(owner, AST_CAUSE_PROTOCOL_ERROR);
 		ast_channel_unlock(owner);
 		ast_channel_unref(owner);
 		sip_pvt_unlock(p);
 		return 10000;
-	} else if (p->refer && !p->alreadygone) {
+	}
+
+	/* Reset schedule ID */
+	p->autokillid = -1;
+
+	if (p->refer && !p->alreadygone) {
 		ast_debug(3, "Finally hanging up channel after transfer: %s\n", p->callid);
 		stop_media_flows(p);
 		transmit_request_with_auth(p, SIP_BYE, 0, XMIT_RELIABLE, 1);
 		append_history(p, "ReferBYE", "Sending BYE on transferer call leg %s", p->callid);
 		sip_scheddestroy(p, DEFAULT_TRANS_TIMEOUT);
+		sip_pvt_unlock(p);
 	} else {
 		append_history(p, "AutoDestroy", "%s", p->callid);
 		ast_debug(3, "Auto destroying SIP dialog '%s'\n", p->callid);
 		sip_pvt_unlock(p);
 		dialog_unlink_all(p); /* once it's unlinked and unrefd everywhere, it'll be freed automagically */
-		sip_pvt_lock(p);
-		/* dialog_unref(p, "unref dialog-- no other matching conditions"); -- unlink all now should finish off the dialog's references and free it. */
-		/* sip_destroy(p); */		/* Go ahead and destroy dialog. All attempts to recover is done */
-		/* sip_destroy also absorbs the reference */
 	}
 
-	sip_pvt_unlock(p);
+	dialog_unref(p, "autokillid complete");
+
+	return 0;
+}
+
+static void do_cancel_destroy(struct sip_pvt *pvt)
+{
+	if (-1 < pvt->autokillid) {
+		append_history(pvt, "CancelDestroy", "");
+		AST_SCHED_DEL_UNREF(sched, pvt->autokillid,
+			dialog_unref(pvt, "Stop scheduled autokillid"));
+	}
+}
 
-	dialog_unref(p, "The ref to a dialog passed to this sched callback is going out of scope; unref it.");
+/* Run by the sched thread. */
+static int __sip_cancel_destroy(const void *data)
+{
+	struct sip_pvt *pvt = (void *) data;
 
+	sip_pvt_lock(pvt);
+	do_cancel_destroy(pvt);
+	sip_pvt_unlock(pvt);
+	dialog_unref(pvt, "Cancel destroy action");
 	return 0;
 }
 
-/*! \brief Schedule final destruction of SIP dialog.  This can not be canceled.
- *  This function is used to keep a dialog around for a period of time in order
- *  to properly respond to any retransmits. */
-void sip_scheddestroy_final(struct sip_pvt *p, int ms)
+void sip_cancel_destroy(struct sip_pvt *pvt)
 {
-	if (p->final_destruction_scheduled) {
-		return; /* already set final destruction */
+	if (pvt->final_destruction_scheduled) {
+		return;
 	}
 
-	sip_scheddestroy(p, ms);
-	if (p->autokillid != -1) {
-		p->final_destruction_scheduled = 1;
+	dialog_ref(pvt, "Cancel destroy action");
+	if (ast_sched_add(sched, 0, __sip_cancel_destroy, pvt) < 0) {
+		/* Uh Oh.  Expect bad behavior. */
+		dialog_unref(pvt, "Failed to schedule cancel destroy action");
+		ast_log(LOG_WARNING, "Unable to cancel SIP destruction.  Expect bad things.\n");
 	}
 }
 
-/*! \brief Schedule destruction of SIP dialog */
-void sip_scheddestroy(struct sip_pvt *p, int ms)
+struct sip_scheddestroy_data {
+	struct sip_pvt *pvt;
+	int ms;
+};
+
+/* Run by the sched thread. */
+static int __sip_scheddestroy(const void *data)
 {
-	if (p->final_destruction_scheduled) {
-		return; /* already set final destruction */
+	struct sip_scheddestroy_data *sched_data = (void *) data;
+	struct sip_pvt *pvt = sched_data->pvt;
+	int ms = sched_data->ms;
+
+	ast_free(sched_data);
+
+	sip_pvt_lock(pvt);
+	do_cancel_destroy(pvt);
+
+	if (pvt->do_history) {
+		append_history(pvt, "SchedDestroy", "%d ms", ms);
+	}
+
+	dialog_ref(pvt, "Schedule autokillid");
+	pvt->autokillid = ast_sched_add(sched, ms, __sip_autodestruct, pvt);
+	if (pvt->autokillid < 0) {
+		/* Uh Oh.  Expect bad behavior. */
+		dialog_unref(pvt, "Failed to schedule autokillid");
 	}
 
+	if (pvt->stimer) {
+		stop_session_timer(pvt);
+	}
+	sip_pvt_unlock(pvt);
+	dialog_unref(pvt, "Destroy action");
+	return 0;
+}
+
+static int sip_scheddestroy_full(struct sip_pvt *p, int ms)
+{
+	struct sip_scheddestroy_data *sched_data;
+
 	if (ms < 0) {
 		if (p->timer_t1 == 0) {
 			p->timer_t1 = global_t1;	/* Set timer T1 if not set (RFC 3261) */
@@ -4313,37 +4447,44 @@ void sip_scheddestroy(struct sip_pvt *p, int ms)
 		ms = p->timer_t1 * 64;
 	}
 	if (sip_debug_test_pvt(p)) {
-		ast_verbose("Scheduling destruction of SIP dialog '%s' in %d ms (Method: %s)\n", p->callid, ms, sip_methods[p->method].text);
-	}
-	if (sip_cancel_destroy(p)) {
-		ast_log(LOG_WARNING, "Unable to cancel SIP destruction.  Expect bad things.\n");
+		ast_verbose("Scheduling destruction of SIP dialog '%s' in %d ms (Method: %s)\n",
+			p->callid, ms, sip_methods[p->method].text);
 	}
 
-	if (p->do_history) {
-		append_history(p, "SchedDestroy", "%d ms", ms);
+	sched_data = ast_malloc(sizeof(*sched_data));
+	if (!sched_data) {
+		/* Uh Oh.  Expect bad behavior. */
+		return -1;
 	}
-	p->autokillid = ast_sched_add(sched, ms, __sip_autodestruct, dialog_ref(p, "setting ref as passing into ast_sched_add for __sip_autodestruct"));
+	sched_data->pvt = p;
+	sched_data->ms = ms;
+	dialog_ref(p, "Destroy action");
+	if (ast_sched_add(sched, 0, __sip_scheddestroy, sched_data) < 0) {
+		/* Uh Oh.  Expect bad behavior. */
+		dialog_unref(p, "Failed to schedule destroy action");
+		ast_free(sched_data);
+		return -1;
+	}
+	return 0;
+}
 
-	if (p->stimer && p->stimer->st_active == TRUE && p->stimer->st_schedid > -1) {
-		stop_session_timer(p);
+void sip_scheddestroy(struct sip_pvt *p, int ms)
+{
+	if (p->final_destruction_scheduled) {
+		return; /* already set final destruction */
 	}
+	sip_scheddestroy_full(p, ms);
 }
 
-/*! \brief Cancel destruction of SIP dialog.
- * Be careful as this also absorbs the reference - if you call it
- * from within the scheduler, this might be the last reference.
- */
-int sip_cancel_destroy(struct sip_pvt *p)
+void sip_scheddestroy_final(struct sip_pvt *p, int ms)
 {
 	if (p->final_destruction_scheduled) {
-		return 0;
+		return; /* already set final destruction */
 	}
 
-	if (p->autokillid > -1) {
-		append_history(p, "CancelDestroy", "");
-		AST_SCHED_DEL_UNREF(sched, p->autokillid, dialog_unref(p, "remove ref for autokillid"));
+	if (!sip_scheddestroy_full(p, ms)) {
+		p->final_destruction_scheduled = 1;
 	}
-	return 0;
 }
 
 /*! \brief Acknowledges receipt of a packet and stops retransmission
@@ -4378,33 +4519,11 @@ int __sip_ack(struct sip_pvt *p, uint32_t seqno, int resp, int sipmethod)
 				if (sipdebug)
 					ast_debug(4, "** SIP TIMER: Cancelling retransmit of packet (reply received) Retransid #%d\n", cur->retransid);
 			}
-			/* This odd section is designed to thwart a
-			 * race condition in the packet scheduler. There are
-			 * two conditions under which deleting the packet from the
-			 * scheduler can fail.
-			 *
-			 * 1. The packet has been removed from the scheduler because retransmission
-			 * is being attempted. The problem is that if the packet is currently attempting
-			 * retransmission and we are at this point in the code, then that MUST mean
-			 * that retrans_pkt is waiting on p's lock. Therefore we will relinquish the
-			 * lock temporarily to allow retransmission.
-			 *
-			 * 2. The packet has reached its maximum number of retransmissions and has
-			 * been permanently removed from the packet scheduler. If this is the case, then
-			 * the packet's retransid will be set to -1. The atomicity of the setting and checking
-			 * of the retransid to -1 is ensured since in both cases p's lock is held.
-			 */
-			while (cur->retransid > -1 && ast_sched_del(sched, cur->retransid)) {
-				sip_pvt_unlock(p);
-				usleep(1);
-				sip_pvt_lock(p);
-			}
+
+			/* Unlink and destroy the packet object. */
 			UNLINK(cur, p->packets, prev);
-			dialog_unref(cur->owner, "unref pkt cur->owner dialog from sip ack before freeing pkt");
-			if (cur->data) {
-				ast_free(cur->data);
-			}
-			ast_free(cur);
+			stop_retrans_pkt(cur);
+			ao2_t_ref(cur, -1, "Packet retransmission list");
 			break;
 		}
 	}
@@ -4445,7 +4564,7 @@ int __sip_semi_ack(struct sip_pvt *p, uint32_t seqno, int resp, int sipmethod)
 				if (sipdebug)
 					ast_debug(4, "*** SIP TIMER: Cancelling retransmission #%d - %s (got response)\n", cur->retransid, sip_methods[sipmethod].text);
 			}
-			AST_SCHED_DEL(sched, cur->retransid);
+			stop_retrans_pkt(cur);
 			res = TRUE;
 			break;
 		}
@@ -4471,25 +4590,14 @@ static void add_blank(struct sip_request *req)
 	}
 }
 
+/* Run by the sched thread. */
 static int send_provisional_keepalive_full(struct sip_pvt *pvt, int with_sdp)
 {
 	const char *msg = NULL;
 	struct ast_channel *chan;
 	int res = 0;
-	int old_sched_id = pvt->provisional_keepalive_sched_id;
 
 	chan = sip_pvt_lock_full(pvt);
-	/* Check that nothing has changed while we were waiting for the lock */
-	if (old_sched_id != pvt->provisional_keepalive_sched_id) {
-		/* Keepalive has been cancelled or rescheduled, clean up and leave */
-		if (chan) {
-			ast_channel_unlock(chan);
-			chan = ast_channel_unref(chan);
-		}
-		sip_pvt_unlock(pvt);
-		dialog_unref(pvt, "dialog ref for provisional keepalive");
-		return 0;
-	}
 
 	if (!pvt->last_provisional || !strncasecmp(pvt->last_provisional, "100", 3)) {
 		msg = "183 Session Progress";
@@ -4502,25 +4610,23 @@ static int send_provisional_keepalive_full(struct sip_pvt *pvt, int with_sdp)
 			transmit_response(pvt, S_OR(msg, pvt->last_provisional), &pvt->initreq);
 		}
 		res = PROVIS_KEEPALIVE_TIMEOUT;
+	} else {
+		pvt->provisional_keepalive_sched_id = -1;
 	}
 
+	sip_pvt_unlock(pvt);
 	if (chan) {
 		ast_channel_unlock(chan);
-		chan = ast_channel_unref(chan);
-	}
-
-	if (!res) {
-		pvt->provisional_keepalive_sched_id = -1;
+		ast_channel_unref(chan);
 	}
 
-	sip_pvt_unlock(pvt);
-
 	if (!res) {
-		dialog_unref(pvt, "dialog ref for provisional keepalive");
+		dialog_unref(pvt, "Schedule provisional keepalive complete");
 	}
 	return res;
 }
 
+/* Run by the sched thread. */
 static int send_provisional_keepalive(const void *data)
 {
 	struct sip_pvt *pvt = (struct sip_pvt *) data;
@@ -4528,6 +4634,7 @@ static int send_provisional_keepalive(const void *data)
 	return send_provisional_keepalive_full(pvt, 0);
 }
 
+/* Run by the sched thread. */
 static int send_provisional_keepalive_with_sdp(const void *data)
 {
 	struct sip_pvt *pvt = (void *) data;
@@ -4535,12 +4642,73 @@ static int send_provisional_keepalive_with_sdp(const void *data)
 	return send_provisional_keepalive_full(pvt, 1);
 }
 
+/* Run by the sched thread. */
+static int __update_provisional_keepalive_full(struct sip_pvt *pvt, int with_sdp)
+{
+	AST_SCHED_DEL_UNREF(sched, pvt->provisional_keepalive_sched_id,
+		dialog_unref(pvt, "Stop scheduled provisional keepalive for update"));
+
+	sip_pvt_lock(pvt);
+	if (pvt->invitestate < INV_COMPLETED) {
+		/* Provisional keepalive is still needed. */
+		dialog_ref(pvt, "Schedule provisional keepalive");
+		pvt->provisional_keepalive_sched_id = ast_sched_add(sched, PROVIS_KEEPALIVE_TIMEOUT,
+			with_sdp ? send_provisional_keepalive_with_sdp : send_provisional_keepalive,
+			pvt);
+		if (pvt->provisional_keepalive_sched_id < 0) {
+			dialog_unref(pvt, "Failed to schedule provisional keepalive");
+		}
+	}
+	sip_pvt_unlock(pvt);
+
+	dialog_unref(pvt, "Update provisional keepalive action");
+	return 0;
+}
+
+/* Run by the sched thread. */
+static int __update_provisional_keepalive(const void *data)
+{
+	struct sip_pvt *pvt = (void *) data;
+
+	return __update_provisional_keepalive_full(pvt, 0);
+}
+
+/* Run by the sched thread. */
+static int __update_provisional_keepalive_with_sdp(const void *data)
+{
+	struct sip_pvt *pvt = (void *) data;
+
+	return __update_provisional_keepalive_full(pvt, 1);
+}
+
 static void update_provisional_keepalive(struct sip_pvt *pvt, int with_sdp)
 {
-	AST_SCHED_DEL_UNREF(sched, pvt->provisional_keepalive_sched_id, dialog_unref(pvt, "when you delete the provisional_keepalive_sched_id, you should dec the refcount for the stored dialog ptr"));
+	dialog_ref(pvt, "Update provisional keepalive action");
+	if (ast_sched_add(sched, 0,
+		with_sdp ? __update_provisional_keepalive_with_sdp : __update_provisional_keepalive,
+		pvt) < 0) {
+		dialog_unref(pvt, "Failed to schedule update provisional keepalive action");
+	}
+}
+
+/* Run by the sched thread. */
+static int __stop_provisional_keepalive(const void *data)
+{
+	struct sip_pvt *pvt = (void *) data;
 
-	pvt->provisional_keepalive_sched_id = ast_sched_add(sched, PROVIS_KEEPALIVE_TIMEOUT,
-		with_sdp ? send_provisional_keepalive_with_sdp : send_provisional_keepalive, dialog_ref(pvt, "Increment refcount to pass dialog pointer to sched callback"));
+	AST_SCHED_DEL_UNREF(sched, pvt->provisional_keepalive_sched_id,
+		dialog_unref(pvt, "Stop scheduled provisional keepalive"));
+	dialog_unref(pvt, "Stop provisional keepalive action");
+	return 0;
+}
+
+static void stop_provisional_keepalive(struct sip_pvt *pvt)
+{
+	dialog_ref(pvt, "Stop provisional keepalive action");
+	if (ast_sched_add(sched, 0, __stop_provisional_keepalive, pvt) < 0) {
+		/* Uh Oh.  Expect bad behavior. */
+		dialog_unref(pvt, "Failed to schedule stop provisional keepalive action");
+	}
 }
 
 static void add_required_respheader(struct sip_request *req)
@@ -4596,7 +4764,7 @@ static int send_response(struct sip_pvt *p, struct sip_request *req, enum xmitty
 
 	/* If we are sending a final response to an INVITE, stop retransmitting provisional responses */
 	if (p->initreq.method == SIP_INVITE && reliable == XMIT_CRITICAL) {
-		AST_SCHED_DEL_UNREF(sched, p->provisional_keepalive_sched_id, dialog_unref(p, "when you delete the provisional_keepalive_sched_id, you should dec the refcount for the stored dialog ptr"));
+		stop_provisional_keepalive(p);
 	}
 
 	res = (reliable) ?
@@ -6333,8 +6501,6 @@ static void sip_registry_destroy(void *obj)
 		reg->call = dialog_unref(reg->call, "unref reg->call");
 		/* reg->call = sip_destroy(reg->call); */
 	}
-	AST_SCHED_DEL(sched, reg->expire);
-	AST_SCHED_DEL(sched, reg->timeout);
 
 	ast_string_field_free_memory(reg);
 }
@@ -6343,12 +6509,12 @@ static void sip_registry_destroy(void *obj)
 static void sip_subscribe_mwi_destroy(void *data)
 {
 	struct sip_subscription_mwi *mwi = data;
+
 	if (mwi->call) {
 		mwi->call->mwi = NULL;
 		mwi->call = dialog_unref(mwi->call, "sip_subscription_mwi destruction");
 	}
 
-	AST_SCHED_DEL(sched, mwi->resub);
 	ast_string_field_free_memory(mwi);
 }
 
@@ -6362,18 +6528,17 @@ static void offered_media_list_destroy(struct sip_pvt *p)
 	}
 }
 
-/*! \brief Execute destruction of SIP dialog structure, release memory */
-void __sip_destroy(struct sip_pvt *p, int lockowner, int lockdialoglist)
+/*! \brief ao2 destructor for SIP dialog structure */
+static void sip_pvt_dtor(void *vdoomed)
 {
+	struct sip_pvt *p = vdoomed;
 	struct sip_request *req;
 
+	ast_debug(3, "Destroying SIP dialog %s\n", p->callid);
+
 	/* Destroy Session-Timers if allocated */
- 	if (p->stimer) {
-		p->stimer->quit_flag = 1;
-		stop_session_timer(p);
-		ast_free(p->stimer);
-		p->stimer = NULL;
-	}
+	ast_free(p->stimer);
+	p->stimer = NULL;
 
 	if (sip_debug_test_pvt(p))
 		ast_verbose("Really destroying SIP dialog '%s' Method: %s\n", p->callid, sip_methods[p->method].text);
@@ -6385,14 +6550,12 @@ void __sip_destroy(struct sip_pvt *p, int lockowner, int lockdialoglist)
 
 	/* Unlink us from the owner if we have one */
 	if (p->owner) {
-		if (lockowner)
-			ast_channel_lock(p->owner);
+		ast_channel_lock(p->owner);
 		ast_debug(1, "Detaching from %s\n", ast_channel_name(p->owner));
 		ast_channel_tech_pvt_set(p->owner, NULL);
 		/* Make sure that the channel knows its backend is going away */
 		ast_channel_softhangup_internal_flag_add(p->owner, AST_SOFTHANGUP_DEV);
-		if (lockowner)
-			ast_channel_unlock(p->owner);
+		ast_channel_unlock(p->owner);
 		/* Give the channel a chance to react before deallocation */
 		usleep(1);
 	}
@@ -6702,24 +6865,6 @@ static int update_call_counter(struct sip_pvt *fup, int event)
 	return 0;
 }
 
-
-static void sip_destroy_fn(void *p)
-{
-	sip_destroy(p);
-}
-
-/*! \brief Destroy SIP call structure.
- * Make it return NULL so the caller can do things like
- *	foo = sip_destroy(foo);
- * and reduce the chance of bugs due to dangling pointers.
- */
-struct sip_pvt *sip_destroy(struct sip_pvt *p)
-{
-	ast_debug(3, "Destroying SIP dialog %s\n", p->callid);
-	__sip_destroy(p, TRUE, TRUE);
-	return NULL;
-}
-
 /*! \brief Convert SIP hangup causes to Asterisk hangup causes */
 int hangup_sip2cause(int cause)
 {
@@ -6892,21 +7037,44 @@ const char *hangup_cause2sip(int cause)
 	return 0;
 }
 
+/* Run by the sched thread. */
 static int reinvite_timeout(const void *data)
 {
 	struct sip_pvt *dialog = (struct sip_pvt *) data;
-	struct ast_channel *owner = sip_pvt_lock_full(dialog);
+	struct ast_channel *owner;
+
+	owner = sip_pvt_lock_full(dialog);
 	dialog->reinviteid = -1;
 	check_pendings(dialog);
 	if (owner) {
 		ast_channel_unlock(owner);
 		ast_channel_unref(owner);
 	}
-	ao2_unlock(dialog);
-	dialog_unref(dialog, "unref for reinvite timeout");
+	sip_pvt_unlock(dialog);
+	dialog_unref(dialog, "reinviteid complete");
+	return 0;
+}
+
+/* Run by the sched thread. */
+static int __stop_reinviteid(const void *data)
+{
+	struct sip_pvt *pvt = (void *) data;
+
+	AST_SCHED_DEL_UNREF(sched, pvt->reinviteid,
+		dialog_unref(pvt, "Stop scheduled reinviteid"));
+	dialog_unref(pvt, "Stop reinviteid action");
 	return 0;
 }
 
+static void stop_reinviteid(struct sip_pvt *pvt)
+{
+	dialog_ref(pvt, "Stop reinviteid action");
+	if (ast_sched_add(sched, 0, __stop_reinviteid, pvt) < 0) {
+		/* Uh Oh.  Expect bad behavior. */
+		dialog_unref(pvt, "Failed to schedule stop reinviteid action");
+	}
+}
+
 /*! \brief  sip_hangup: Hangup SIP call
  * Part of PBX interface, called from ast_hangup */
 static int sip_hangup(struct ast_channel *ast)
@@ -7021,7 +7189,8 @@ static int sip_hangup(struct ast_channel *ast)
 				}
 			} else {	/* Incoming call, not up */
 				const char *res;
-				AST_SCHED_DEL_UNREF(sched, p->provisional_keepalive_sched_id, dialog_unref(p, "when you delete the provisional_keepalive_sched_id, you should dec the refcount for the stored dialog ptr"));
+
+				stop_provisional_keepalive(p);
 				if (p->hangupcause && (res = hangup_cause2sip(p->hangupcause)))
 					transmit_response_reliable(p, res, &p->initreq);
 				else
@@ -7029,7 +7198,7 @@ static int sip_hangup(struct ast_channel *ast)
 				p->invitestate = INV_TERMINATED;
 			}
 		} else {	/* Call is in UP state, send BYE */
-			if (p->stimer->st_active == TRUE) {
+			if (p->stimer) {
 				stop_session_timer(p);
 			}
 
@@ -7089,16 +7258,20 @@ static int sip_hangup(struct ast_channel *ast)
 				   but we can't send one while we have "INVITE" outstanding. */
 				ast_set_flag(&p->flags[0], SIP_PENDINGBYE);
 				ast_clear_flag(&p->flags[0], SIP_NEEDREINVITE);
-				AST_SCHED_DEL_UNREF(sched, p->waitid, dialog_unref(p, "when you delete the waitid sched, you should dec the refcount for the stored dialog ptr"));
-				if (sip_cancel_destroy(p)) {
-					ast_log(LOG_WARNING, "Unable to cancel SIP destruction.  Expect bad things.\n");
-				}
+				stop_reinvite_retry(p);
+				sip_cancel_destroy(p);
+
 				/* If we have an ongoing reinvite, there is a chance that we have gotten a provisional
 				 * response, but something weird has happened and we will never receive a final response.
 				 * So, just in case, check for pending actions after a bit of time to trigger the pending
 				 * bye that we are setting above */
 				if (p->ongoing_reinvite && p->reinviteid < 0) {
-					p->reinviteid = ast_sched_add(sched, 32 * p->timer_t1, reinvite_timeout, dialog_ref(p, "ref for reinvite_timeout"));
+					p->reinviteid = ast_sched_add(sched, 32 * p->timer_t1,
+						reinvite_timeout, dialog_ref(p, "Schedule reinviteid"));
+					if (p->reinviteid < 0) {
+						/* Uh Oh.  Expect bad behavior. */
+						dialog_unref(p, "Failed to schedule reinviteid");
+					}
 				}
 			}
 		}
@@ -7203,8 +7376,8 @@ static int sip_answer(struct ast_channel *ast)
 		ast_set_flag(&p->flags[1], SIP_PAGE2_DIALOG_ESTABLISHED);
 		/* RFC says the session timer starts counting on 200,
 		 * not on INVITE. */
-		if (p->stimer->st_active == TRUE) {
-			start_session_timer(p);
+		if (p->stimer) {
+			restart_session_timer(p);
 		}
 	}
 	sip_pvt_unlock(p);
@@ -7467,13 +7640,13 @@ static int interpret_t38_parameters(struct sip_pvt *p, const struct ast_control_
 		/* Negotiation can not take place without a valid max_ifp value. */
 		if (!parameters->max_ifp) {
 			if (p->t38.state == T38_PEER_REINVITE) {
-				AST_SCHED_DEL_UNREF(sched, p->t38id, dialog_unref(p, "when you delete the t38id sched, you should dec the refcount for the stored dialog ptr"));
+				stop_t38_abort_timer(p);
 				transmit_response_reliable(p, "488 Not acceptable here", &p->initreq);
 			}
 			change_t38_state(p, T38_REJECTED);
 			break;
 		} else if (p->t38.state == T38_PEER_REINVITE) {
-			AST_SCHED_DEL_UNREF(sched, p->t38id, dialog_unref(p, "when you delete the t38id sched, you should dec the refcount for the stored dialog ptr"));
+			stop_t38_abort_timer(p);
 			p->t38.our_parms = *parameters;
 			/* modify our parameters to conform to the peer's parameters,
 			 * based on the rules in the ITU T.38 recommendation
@@ -7507,17 +7680,19 @@ static int interpret_t38_parameters(struct sip_pvt *p, const struct ast_control_
 	case AST_T38_REFUSED:
 	case AST_T38_REQUEST_TERMINATE:         /* Shutdown T38 */
 		if (p->t38.state == T38_PEER_REINVITE) {
-			AST_SCHED_DEL_UNREF(sched, p->t38id, dialog_unref(p, "when you delete the t38id sched, you should dec the refcount for the stored dialog ptr"));
+			stop_t38_abort_timer(p);
 			change_t38_state(p, T38_REJECTED);
 			transmit_response_reliable(p, "488 Not acceptable here", &p->initreq);
-		} else if (p->t38.state == T38_ENABLED)
+		} else if (p->t38.state == T38_ENABLED) {
+			change_t38_state(p, T38_DISABLED);
 			transmit_reinvite_with_sdp(p, FALSE, FALSE);
+		}
 		break;
 	case AST_T38_REQUEST_PARMS: {		/* Application wants remote's parameters re-sent */
 		struct ast_control_t38_parameters parameters = p->t38.their_parms;
 
 		if (p->t38.state == T38_PEER_REINVITE) {
-			AST_SCHED_DEL_UNREF(sched, p->t38id, dialog_unref(p, "when you delete the t38id sched, you should dec the refcount for the stored dialog ptr"));
+			stop_t38_abort_timer(p);
 			parameters.max_ifp = ast_udptl_get_far_max_ifp(p->udptl);
 			parameters.request_response = AST_T38_REQUEST_NEGOTIATE;
 			if (p->owner) {
@@ -8594,13 +8769,13 @@ static struct sip_st_dlg* sip_st_alloc(struct sip_pvt *const p)
 		return p->stimer;
 	}
 
-	if (!(stp = ast_calloc(1, sizeof(struct sip_st_dlg))))
+	if (!(stp = ast_calloc(1, sizeof(struct sip_st_dlg)))) {
 		return NULL;
+	}
+	stp->st_schedid = -1;           /* Session-Timers ast_sched scheduler id */
 
 	p->stimer = stp;
 
-	stp->st_schedid = -1;           /* Session-Timers ast_sched scheduler id */
-
 	return p->stimer;
 }
 
@@ -8623,7 +8798,7 @@ struct sip_pvt *__sip_alloc(ast_string_field callid, struct ast_sockaddr *addr,
 {
 	struct sip_pvt *p;
 
-	p = __ao2_alloc_debug(sizeof(*p), sip_destroy_fn,
+	p = __ao2_alloc_debug(sizeof(*p), sip_pvt_dtor,
 		AO2_ALLOC_OPT_LOCK_MUTEX, "allocate a dialog(pvt) struct",
 		file, line, func, 1);
 	if (!p) {
@@ -9079,7 +9254,7 @@ static void forked_invite_init(struct sip_request *req, const char *new_theirtag
  * \post pvt is locked
  * \post pvt->owner is locked and its reference count is increased (if pvt->owner is not NULL)
  *
- * \returns a pointer to the locked and reffed pvt->owner channel if it exists.
+ * \return a pointer to the locked and reffed pvt->owner channel if it exists.
  */
 static struct ast_channel *sip_pvt_lock_full(struct sip_pvt *pvt)
 {
@@ -10669,6 +10844,10 @@ static int process_sdp(struct sip_pvt *p, struct sip_request *req, int t38action
 		} else if (udptlportno > 0) {
 			if (debug)
 				ast_verbose("Got T.38 Re-invite without audio. Keeping RTP active during T.38 session.\n");
+
+			/* Force media to go through us for T.38. */
+			memset(&p->redirip, 0, sizeof(p->redirip));
+
 			/* Prevent audio RTCP reads */
 			if (p->owner) {
 				ast_channel_set_fd(p->owner, 1, -1);
@@ -13439,7 +13618,7 @@ static enum sip_result add_sdp(struct sip_request *resp, struct sip_pvt *p, int
 
 		ast_str_append(&m_modem, 0, "m=image %d udptl t38\r\n", ast_sockaddr_port(&udptldest));
 
-		if (ast_sockaddr_cmp(&udptldest, &dest)) {
+		if (ast_sockaddr_cmp_addr(&udptldest, &dest)) {
 			ast_str_append(&m_modem, 0, "c=IN %s %s\r\n",
 					(ast_sockaddr_is_ipv6(&udptldest) && !ast_sockaddr_is_ipv4_mapped(&udptldest)) ?
 					"IP6" : "IP4", ast_sockaddr_stringify_addr_remote(&udptldest));
@@ -13780,6 +13959,29 @@ static int transmit_reinvite_with_sdp(struct sip_pvt *p, int t38version, int old
 {
 	struct sip_request req;
 
+	if (t38version) {
+		/* Force media to go through us for T.38. */
+		memset(&p->redirip, 0, sizeof(p->redirip));
+	}
+	if (p->rtp) {
+		if (t38version) {
+			/* Silence RTCP while audio RTP is inactive */
+			ast_rtp_instance_set_prop(p->rtp, AST_RTP_PROPERTY_RTCP, 0);
+			if (p->owner) {
+				/* Prevent audio RTCP reads */
+				ast_channel_set_fd(p->owner, 1, -1);
+			}
+		} else if (ast_sockaddr_isnull(&p->redirip)) {
+			/* Enable RTCP since it will be inactive if we're coming back
+			 * with this reinvite */
+			ast_rtp_instance_set_prop(p->rtp, AST_RTP_PROPERTY_RTCP, 1);
+			if (p->owner) {
+				/* Enable audio RTCP reads */
+				ast_channel_set_fd(p->owner, 1, ast_rtp_instance_fd(p->rtp, 1));
+			}
+		}
+	}
+
 	reqprep(&req, p, ast_test_flag(&p->flags[0], SIP_REINVITE_UPDATE) ?  SIP_UPDATE : SIP_INVITE, 0, 1);
 
 	add_header(&req, "Allow", ALLOWED_METHODS);
@@ -14182,7 +14384,7 @@ static void add_diversion(struct sip_request *req, struct sip_pvt *pvt)
 {
 	struct ast_party_id diverting_from;
 	const char *reason;
-	int found_in_table;
+	const char *quote_str;
 	char header_text[256];
 	char encoded_number[SIPBUFSIZE/2];
 
@@ -14207,17 +14409,18 @@ static void add_diversion(struct sip_request *req, struct sip_pvt *pvt)
 		ast_copy_string(encoded_number, diverting_from.number.str, sizeof(encoded_number));
 	}
 
-	reason = sip_reason_code_to_str(&ast_channel_redirecting(pvt->owner)->reason, &found_in_table);
+	reason = sip_reason_code_to_str(&ast_channel_redirecting(pvt->owner)->reason);
+
+	/* Reason is either already quoted or it is a token to not need quotes added. */
+	quote_str = *reason == '\"' || sip_is_token(reason) ? "" : "\"";
 
 	/* We at least have a number to place in the Diversion header, which is enough */
 	if (!diverting_from.name.valid
 		|| ast_strlen_zero(diverting_from.name.str)) {
 		snprintf(header_text, sizeof(header_text), "<sip:%s@%s>;reason=%s%s%s",
-				encoded_number,
-				ast_sockaddr_stringify_host_remote(&pvt->ourip),
-				found_in_table ? "" : "\"",
-				reason,
-				found_in_table ? "" : "\"");
+			encoded_number,
+			ast_sockaddr_stringify_host_remote(&pvt->ourip),
+			quote_str, reason, quote_str);
 	} else {
 		char escaped_name[SIPBUFSIZE/2];
 		if (sip_cfg.pedanticsipchecking) {
@@ -14226,12 +14429,10 @@ static void add_diversion(struct sip_request *req, struct sip_pvt *pvt)
 			ast_copy_string(escaped_name, diverting_from.name.str, sizeof(escaped_name));
 		}
 		snprintf(header_text, sizeof(header_text), "\"%s\" <sip:%s@%s>;reason=%s%s%s",
-				escaped_name,
-				encoded_number,
-				ast_sockaddr_stringify_host_remote(&pvt->ourip),
-				found_in_table ? "" : "\"",
-				reason,
-				found_in_table ? "" : "\"");
+			escaped_name,
+			encoded_number,
+			ast_sockaddr_stringify_host_remote(&pvt->ourip),
+			quote_str, reason, quote_str);
 	}
 
 	add_header(req, "Diversion", header_text);
@@ -14442,22 +14643,96 @@ static int transmit_invite(struct sip_pvt *p, int sipmethod, int sdp, int init,
 	return send_request(p, &req, init ? XMIT_CRITICAL : XMIT_RELIABLE, p->ocseq);
 }
 
-/*! \brief Send a subscription or resubscription for MWI */
+/*!
+ * \brief Send a subscription or resubscription for MWI
+ *
+ * \note Run by the sched thread.
+ */
 static int sip_subscribe_mwi_do(const void *data)
 {
-	struct sip_subscription_mwi *mwi = (struct sip_subscription_mwi*)data;
-
-	if (!mwi) {
-		return -1;
-	}
+	struct sip_subscription_mwi *mwi = (struct sip_subscription_mwi *) data;
 
 	mwi->resub = -1;
 	__sip_subscribe_mwi_do(mwi);
-	ao2_t_ref(mwi, -1, "unref mwi to balance ast_sched_add");
+	ao2_t_ref(mwi, -1, "Scheduled mwi resub complete");
+
+	return 0;
+}
+
+/* Run by the sched thread. */
+static int __shutdown_mwi_subscription(const void *data)
+{
+	struct sip_subscription_mwi *mwi = (void *) data;
+
+	AST_SCHED_DEL_UNREF(sched, mwi->resub,
+		ao2_t_ref(mwi, -1, "Stop scheduled mwi resub"));
+
+	if (mwi->dnsmgr) {
+		ast_dnsmgr_release(mwi->dnsmgr);
+		mwi->dnsmgr = NULL;
+		ao2_t_ref(mwi, -1, "dnsmgr release");
+	}
+
+	ao2_t_ref(mwi, -1, "Shutdown MWI subscription action");
+	return 0;
+}
+
+static void shutdown_mwi_subscription(struct sip_subscription_mwi *mwi)
+{
+	ao2_t_ref(mwi, +1, "Shutdown MWI subscription action");
+	if (ast_sched_add(sched, 0, __shutdown_mwi_subscription, mwi) < 0) {
+		/* Uh Oh.  Expect bad behavior. */
+		ao2_t_ref(mwi, -1, "Failed to schedule shutdown MWI subscription action");
+	}
+}
+
+struct mwi_subscription_data {
+	struct sip_subscription_mwi *mwi;
+	int ms;
+};
+
+/* Run by the sched thread. */
+static int __start_mwi_subscription(const void *data)
+{
+	struct mwi_subscription_data *sched_data = (void *) data;
+	struct sip_subscription_mwi *mwi = sched_data->mwi;
+	int ms = sched_data->ms;
+
+	ast_free(sched_data);
+
+	AST_SCHED_DEL_UNREF(sched, mwi->resub,
+		ao2_t_ref(mwi, -1, "Stop scheduled mwi resub"));
 
+	ao2_t_ref(mwi, +1, "Schedule mwi resub");
+	mwi->resub = ast_sched_add(sched, ms, sip_subscribe_mwi_do, mwi);
+	if (mwi->resub < 0) {
+		/* Uh Oh.  Expect bad behavior. */
+		ao2_t_ref(mwi, -1, "Failed to schedule mwi resub");
+	}
+
+	ao2_t_ref(mwi, -1, "Start MWI subscription action");
 	return 0;
 }
 
+static void start_mwi_subscription(struct sip_subscription_mwi *mwi, int ms)
+{
+	struct mwi_subscription_data *sched_data;
+
+	sched_data = ast_malloc(sizeof(*sched_data));
+	if (!sched_data) {
+		/* Uh Oh.  Expect bad behavior. */
+		return;
+	}
+	sched_data->mwi = mwi;
+	sched_data->ms = ms;
+	ao2_t_ref(mwi, +1, "Start MWI subscription action");
+	if (ast_sched_add(sched, 0, __start_mwi_subscription, sched_data) < 0) {
+		/* Uh Oh.  Expect bad behavior. */
+		ao2_t_ref(mwi, -1, "Failed to schedule start MWI subscription action");
+		ast_free(sched_data);
+	}
+}
+
 static void on_dns_update_registry(struct ast_sockaddr *old, struct ast_sockaddr *new, void *data)
 {
 	struct sip_registry *reg = data;
@@ -15159,6 +15434,12 @@ static int manager_sipnotify(struct mansession *s, const struct message *m)
 		}
 	}
 
+	/* Now that we have the peer's address, set our ip and change callid */
+	ast_sip_ouraddrfor(&p->sa, &p->ourip, p);
+	build_via(p);
+
+	change_callid_pvt(p, NULL);
+
 	sip_scheddestroy(p, SIP_TRANS_TIMEOUT);
 	transmit_invite(p, SIP_NOTIFY, 0, 2, NULL);
 	dialog_unref(p, "bump down the count of p since we're done with it.");
@@ -15257,7 +15538,7 @@ static const struct _map_x_s regstatestrings[] = {
 	{ REG_STATE_AUTHSENT, "Auth. Sent"},
 	{ REG_STATE_REGISTERED, "Registered"},
 	{ REG_STATE_REJECTED, "Rejected"},
-	{ REG_STATE_TIMEOUT, "Timeout"},
+	{ REG_STATE_TIMEOUT, "Registered"},/* Hidden state.  We are renewing registration. */
 	{ REG_STATE_NOAUTH, "No Authentication"},
 	{ -1, NULL } /* terminator */
 };
@@ -15273,22 +15554,22 @@ static void sip_publish_registry(const char *username, const char *domain, const
 	ast_system_publish_registry("SIP", username, domain, status, NULL);
 }
 
-/*! \brief Update registration with SIP Proxy.
+/*!
+ * \brief Update registration with SIP Proxy.
+ *
+ * \details
  * Called from the scheduler when the previous registration expires,
  * so we don't have to cancel the pending event.
  * We assume the reference so the sip_registry is valid, since it
  * is stored in the scheduled event anyways.
+ *
+ * \note Run by the sched thread.
  */
 static int sip_reregister(const void *data)
 {
 	/* if we are here, we know that we need to reregister. */
 	struct sip_registry *r = (struct sip_registry *) data;
 
-	/* if we couldn't get a reference to the registry object, punt */
-	if (!r) {
-		return 0;
-	}
-
 	if (r->call && r->call->do_history) {
 		append_history(r->call, "RegistryRenew", "Account: %s@%s", r->username, r->hostname);
 	}
@@ -15300,8 +15581,25 @@ static int sip_reregister(const void *data)
 
 	r->expire = -1;
 	r->expiry = r->configured_expiry;
+	switch (r->regstate) {
+	case REG_STATE_UNREGISTERED:
+	case REG_STATE_REGSENT:
+	case REG_STATE_AUTHSENT:
+		break;
+	case REG_STATE_REJECTED:
+	case REG_STATE_NOAUTH:
+	case REG_STATE_FAILED:
+		/* Restarting registration as unregistered */
+		r->regstate = REG_STATE_UNREGISTERED;
+		break;
+	case REG_STATE_TIMEOUT:
+	case REG_STATE_REGISTERED:
+		/* Registration needs to be renewed. */
+		r->regstate = REG_STATE_TIMEOUT;
+		break;
+	}
 	__sip_do_register(r);
-	ao2_t_ref(r, -1, "unref the re-register scheduled event");
+	ao2_t_ref(r, -1, "Scheduled reregister timeout complete");
 	return 0;
 }
 
@@ -15316,21 +15614,83 @@ static int __sip_do_register(struct sip_registry *r)
 	return res;
 }
 
-/*! \brief Registration timeout, register again
+struct reregister_data {
+	struct sip_registry *reg;
+	int ms;
+};
+
+/* Run by the sched thread. */
+static int __start_reregister_timeout(const void *data)
+{
+	struct reregister_data *sched_data = (void *) data;
+	struct sip_registry *reg = sched_data->reg;
+	int ms = sched_data->ms;
+
+	ast_free(sched_data);
+
+	AST_SCHED_DEL_UNREF(sched, reg->expire,
+		ao2_t_ref(reg, -1, "Stop scheduled reregister timeout"));
+
+	ao2_t_ref(reg, +1, "Schedule reregister timeout");
+	reg->expire = ast_sched_add(sched, ms, sip_reregister, reg);
+	if (reg->expire < 0) {
+		/* Uh Oh.  Expect bad behavior. */
+		ao2_t_ref(reg, -1, "Failed to schedule reregister timeout");
+	}
+
+	ao2_t_ref(reg, -1, "Start reregister timeout action");
+	return 0;
+}
+
+static void start_reregister_timeout(struct sip_registry *reg, int ms)
+{
+	struct reregister_data *sched_data;
+
+	sched_data = ast_malloc(sizeof(*sched_data));
+	if (!sched_data) {
+		/* Uh Oh.  Expect bad behavior. */
+		return;
+	}
+	sched_data->reg = reg;
+	sched_data->ms = ms;
+	ao2_t_ref(reg, +1, "Start reregister timeout action");
+	if (ast_sched_add(sched, 0, __start_reregister_timeout, sched_data) < 0) {
+		/* Uh Oh.  Expect bad behavior. */
+		ao2_t_ref(reg, -1, "Failed to schedule start reregister timeout action");
+		ast_free(sched_data);
+	}
+}
+
+/*!
+ * \brief Registration request timeout, register again
+ *
+ * \details
  * Registered as a timeout handler during transmit_register(),
  * to retransmit the packet if a reply does not come back.
- * This is called by the scheduler so the event is not pending anymore when
+ *
+ * \note This is called by the scheduler so the event is not pending anymore when
  * we are called.
+ *
+ * \note Run by the sched thread.
  */
 static int sip_reg_timeout(const void *data)
 {
-
-	/* if we are here, our registration timed out, so we'll just do it over */
 	struct sip_registry *r = (struct sip_registry *)data; /* the ref count should have been bumped when the sched item was added */
 	struct sip_pvt *p;
 
-	/* if we couldn't get a reference to the registry object, punt */
-	if (!r) {
+	switch (r->regstate) {
+	case REG_STATE_UNREGISTERED:
+	case REG_STATE_REGSENT:
+	case REG_STATE_AUTHSENT:
+	case REG_STATE_TIMEOUT:
+		break;
+	default:
+		/*
+		 * Registration completed because we got a request response
+		 * and we couldn't stop the scheduled entry in time.
+		 */
+		r->timeout = -1;
+		ao2_t_ref(r, -1, "Scheduled register timeout completed early");
 		return 0;
 	}
 
@@ -15372,10 +15732,60 @@ static int sip_reg_timeout(const void *data)
 		ast_log(LOG_NOTICE, "   -- Registration for '%s@%s' timed out, trying again (Attempt #%d)\n", r->username, r->hostname, r->regattempts);
 	}
 	sip_publish_registry(r->username, r->hostname, regstate2str(r->regstate));
-	ao2_t_ref(r, -1, "unreffing registry_unref r");
+	ao2_t_ref(r, -1, "Scheduled register timeout complete");
+	return 0;
+}
+
+/* Run by the sched thread. */
+static int __stop_register_timeout(const void *data)
+{
+	struct sip_registry *reg = (struct sip_registry *) data;
+
+	AST_SCHED_DEL_UNREF(sched, reg->timeout,
+		ao2_t_ref(reg, -1, "Stop scheduled register timeout"));
+	ao2_t_ref(reg, -1, "Stop register timeout action");
 	return 0;
 }
 
+static void stop_register_timeout(struct sip_registry *reg)
+{
+	ao2_t_ref(reg, +1, "Stop register timeout action");
+	if (ast_sched_add(sched, 0, __stop_register_timeout, reg) < 0) {
+		/* Uh Oh.  Expect bad behavior. */
+		ao2_t_ref(reg, -1, "Failed to schedule stop register timeout action");
+	}
+}
+
+/* Run by the sched thread. */
+static int __start_register_timeout(const void *data)
+{
+	struct sip_registry *reg = (struct sip_registry *) data;
+
+	AST_SCHED_DEL_UNREF(sched, reg->timeout,
+		ao2_t_ref(reg, -1, "Stop scheduled register timeout"));
+
+	ao2_t_ref(reg, +1, "Schedule register timeout");
+	reg->timeout = ast_sched_add(sched, global_reg_timeout * 1000, sip_reg_timeout, reg);
+	if (reg->timeout < 0) {
+		/* Uh Oh.  Expect bad behavior. */
+		ao2_t_ref(reg, -1, "Failed to schedule register timeout");
+	}
+	ast_debug(1, "Scheduled a registration timeout for %s id  #%d \n",
+		reg->hostname, reg->timeout);
+
+	ao2_t_ref(reg, -1, "Start register timeout action");
+	return 0;
+}
+
+static void start_register_timeout(struct sip_registry *reg)
+{
+	ao2_t_ref(reg, +1, "Start register timeout action");
+	if (ast_sched_add(sched, 0, __start_register_timeout, reg) < 0) {
+		/* Uh Oh.  Expect bad behavior. */
+		ao2_t_ref(reg, -1, "Failed to schedule start register timeout action");
+	}
+}
+
 static const char *sip_sanitized_host(const char *host)
 {
 	struct ast_sockaddr addr = { { 0, 0, }, };
@@ -15489,16 +15899,9 @@ static int transmit_register(struct sip_registry *r, int sipmethod, const char *
 			 * probably DNS.  We need to reschedule a registration try */
 			dialog_unlink_all(p);
 			p = dialog_unref(p, "unref dialog after unlink_all");
-			if (r->timeout > -1) {
-				AST_SCHED_REPLACE_UNREF(r->timeout, sched, global_reg_timeout * 1000, sip_reg_timeout, r,
-										ao2_t_ref(_data, -1, "del for REPLACE of registry ptr"),
-										ao2_t_ref(r, -1, "object ptr dec when SCHED_REPLACE add failed"),
-										ao2_t_ref(r, +1, "add for REPLACE registry ptr"));
-				ast_log(LOG_WARNING, "Still have a registration timeout for %s@%s (create_addr() error), %d\n", r->username, r->hostname, r->timeout);
-			} else {
-				r->timeout = ast_sched_add(sched, global_reg_timeout * 1000, sip_reg_timeout, ao2_t_bump(r, "add for REPLACE registry ptr"));
-				ast_log(LOG_WARNING, "Probably a DNS error for registration to %s@%s, trying REGISTER again (after %d seconds)\n", r->username, r->hostname, global_reg_timeout);
-			}
+			ast_log(LOG_WARNING, "Probably a DNS error for registration to %s@%s, trying REGISTER again (after %d seconds)\n",
+				r->username, r->hostname, global_reg_timeout);
+			start_register_timeout(r);
 			r->regattempts++;
 			return 0;
 		}
@@ -15561,14 +15964,7 @@ static int transmit_register(struct sip_registry *r, int sipmethod, const char *
 
 	/* set up a timeout */
 	if (auth == NULL)  {
-		if (r->timeout > -1) {
-			ast_log(LOG_WARNING, "Still have a registration timeout, #%d - deleting it\n", r->timeout);
-		}
-		AST_SCHED_REPLACE_UNREF(r->timeout, sched, global_reg_timeout * 1000, sip_reg_timeout, r,
-								ao2_t_ref(_data, -1, "reg ptr unrefed from del in SCHED_REPLACE"),
-								ao2_t_ref(r, -1, "reg ptr unrefed from add failure in SCHED_REPLACE"),
-								ao2_t_ref(r, +1, "reg ptr reffed from add in SCHED_REPLACE"));
-		ast_debug(1, "Scheduled a registration timeout for %s id  #%d \n", r->hostname, r->timeout);
+		start_register_timeout(r);
 	}
 
 	snprintf(from, sizeof(from), "<sip:%s@%s>;tag=%s", r->username, S_OR(r->regdomain, sip_sanitized_host(p->tohost)), p->tag);
@@ -16835,6 +17231,7 @@ static void acl_change_event_stasis_unsubscribe(void)
 	acl_change_sub = stasis_unsubscribe_and_join(acl_change_sub);
 }
 
+/* Run by the sched thread. */
 static int network_change_sched_cb(const void *data)
 {
 	network_change_sched_id = -1;
@@ -17224,8 +17621,7 @@ static enum check_auth_result register_verify(struct sip_pvt *p, struct ast_sock
 			ast_copy_flags(&p->flags[0], &peer->flags[0], SIP_NAT_FORCE_RPORT);
 
 			if (!(res = check_auth(p, req, peer->name, peer->secret, peer->md5secret, SIP_REGISTER, uri2, XMIT_UNRELIABLE))) {
-				if (sip_cancel_destroy(p))
-					ast_log(LOG_WARNING, "Unable to cancel SIP destruction.  Expect bad things.\n");
+				sip_cancel_destroy(p);
 
 				if (check_request_transport(peer, req)) {
 					ast_set_flag(&p->flags[0], SIP_PENDINGBYE);
@@ -17279,8 +17675,7 @@ static enum check_auth_result register_verify(struct sip_pvt *p, struct ast_sock
 				ao2_t_link(peers_by_ip, peer, "link peer into peers-by-ip table");
 			}
 			ao2_lock(peer);
-			if (sip_cancel_destroy(p))
-				ast_log(LOG_WARNING, "Unable to cancel SIP destruction.  Expect bad things.\n");
+			sip_cancel_destroy(p);
 			switch (parse_register_contact(p, peer, req)) {
 			case PARSE_REGISTER_DENIED:
 				ast_log(LOG_WARNING, "Registration denied because of contact ACL\n");
@@ -17654,6 +18049,9 @@ static int get_rdnis(struct sip_pvt *p, struct sip_request *oreq, char **name, c
 	char *params, *reason_param = NULL;
 	struct sip_request *req;
 
+	ast_assert(reason_code != NULL);
+	ast_assert(reason_str != NULL);
+
 	req = oreq ? oreq : &p->initreq;
 
 	ast_copy_string(tmp, sip_get_header(req, "Diversion"), sizeof(tmp));
@@ -17687,16 +18085,6 @@ static int get_rdnis(struct sip_pvt *p, struct sip_request *oreq, char **name, c
 			if ((end = strchr(reason_param, ';'))) {
 				*end = '\0';
 			}
-			/* Remove enclosing double-quotes */
-			if (*reason_param == '"')
-				reason_param = ast_strip_quoted(reason_param, "\"", "\"");
-			if (!ast_strlen_zero(reason_param)) {
-				sip_set_redirstr(p, reason_param);
-				if (p->owner) {
-					pbx_builtin_setvar_helper(p->owner, "__PRIREDIRECTREASON", p->redircause);
-					pbx_builtin_setvar_helper(p->owner, "__SIPREDIRECTREASON", reason_param);
-				}
-			}
 		}
 	}
 
@@ -17728,12 +18116,27 @@ static int get_rdnis(struct sip_pvt *p, struct sip_request *oreq, char **name, c
 	}
 
 	if (!ast_strlen_zero(reason_param)) {
-		if (reason_code) {
-			*reason_code = sip_reason_str_to_code(reason_param);
+		*reason_str = ast_strdup(reason_param);
+
+		/* Remove any enclosing double-quotes */
+		if (*reason_param == '"') {
+			reason_param = ast_strip_quoted(reason_param, "\"", "\"");
 		}
 
-		if (reason_str) {
-			*reason_str = ast_strdup(reason_param);
+		*reason_code = ast_redirecting_reason_parse(reason_param);
+		if (*reason_code < 0) {
+			*reason_code = AST_REDIRECTING_REASON_UNKNOWN;
+		} else {
+			ast_free(*reason_str);
+			*reason_str = ast_strdup("");
+		}
+
+		if (!ast_strlen_zero(reason_param)) {
+			sip_set_redirstr(p, reason_param);
+			if (p->owner) {
+				pbx_builtin_setvar_helper(p->owner, "__PRIREDIRECTREASON", p->redircause);
+				pbx_builtin_setvar_helper(p->owner, "__SIPREDIRECTREASON", reason_param);
+			}
 		}
 	}
 
@@ -22608,10 +23011,11 @@ static void change_redirecting_information(struct sip_pvt *p, struct sip_request
 		redirecting->to.name.str = redirecting_to_name;
 	}
 	redirecting->reason.code = reason;
+	ast_free(redirecting->reason.str);
+	redirecting->reason.str = reason_str;
 	if (reason_str) {
-		ast_debug(3, "Got redirecting reason %s\n", reason_str);
-		ast_free(redirecting->reason.str);
-		redirecting->reason.str = reason_str;
+		ast_debug(3, "Got redirecting reason %s\n", ast_strlen_zero(reason_str)
+			? sip_reason_code_to_str(&redirecting->reason) : reason_str);
 	}
 }
 
@@ -22731,10 +23135,13 @@ static void parse_moved_contact(struct sip_pvt *p, struct sip_request *req, char
 	}
 }
 
-/*! \brief Check pending actions on SIP call
+/*!
+ * \brief Check pending actions on SIP call
  *
  * \note both sip_pvt and sip_pvt's owner channel (if present)
  *  must be locked for this function.
+ *
+ * \note Run by the sched thread.
  */
 static void check_pendings(struct sip_pvt *p)
 {
@@ -22742,13 +23149,14 @@ static void check_pendings(struct sip_pvt *p)
 		if (p->reinviteid > -1) {
 			/* Outstanding p->reinviteid timeout, so wait... */
 			return;
-		} else if (p->invitestate == INV_PROCEEDING || p->invitestate == INV_EARLY_MEDIA) {
+		}
+		if (p->invitestate == INV_PROCEEDING || p->invitestate == INV_EARLY_MEDIA) {
 			/* if we can't BYE, then this is really a pending CANCEL */
 			p->invitestate = INV_CANCELLED;
 			transmit_request(p, SIP_CANCEL, p->lastinvite, XMIT_RELIABLE, FALSE);
 			/* If the cancel occurred on an initial invite, cancel the pending BYE */
 			if (!ast_test_flag(&p->flags[1], SIP_PAGE2_DIALOG_ESTABLISHED)) {
-				ast_clear_flag(&p->flags[0], SIP_PENDINGBYE);
+				ast_clear_flag(&p->flags[0], SIP_PENDINGBYE | SIP_NEEDREINVITE);
 			}
 			/* Actually don't destroy us yet, wait for the 487 on our original
 			   INVITE, but do set an autodestruct just in case we never get it. */
@@ -22764,12 +23172,16 @@ static void check_pendings(struct sip_pvt *p)
 			}
 			/* Perhaps there is an SD change INVITE outstanding */
 			transmit_request_with_auth(p, SIP_BYE, 0, XMIT_RELIABLE, TRUE);
-			ast_clear_flag(&p->flags[0], SIP_PENDINGBYE);
+			ast_clear_flag(&p->flags[0], SIP_PENDINGBYE | SIP_NEEDREINVITE);
 		}
 		sip_scheddestroy(p, DEFAULT_TRANS_TIMEOUT);
 	} else if (ast_test_flag(&p->flags[0], SIP_NEEDREINVITE)) {
 		/* if we can't REINVITE, hold it for later */
-		if (p->pendinginvite || p->invitestate == INV_CALLING || p->invitestate == INV_PROCEEDING || p->invitestate == INV_EARLY_MEDIA || p->waitid > -1) {
+		if (p->pendinginvite
+			|| p->invitestate == INV_CALLING
+			|| p->invitestate == INV_PROCEEDING
+			|| p->invitestate == INV_EARLY_MEDIA
+			|| p->waitid > -1) {
 			ast_debug(2, "NOT Sending pending reinvite (yet) on '%s'\n", p->callid);
 		} else {
 			ast_debug(2, "Sending pending reinvite on '%s'\n", p->callid);
@@ -22780,32 +23192,77 @@ static void check_pendings(struct sip_pvt *p)
 	}
 }
 
-/*! \brief Reset the NEEDREINVITE flag after waiting when we get 491 on a Re-invite
-	to avoid race conditions between asterisk servers.
-	Called from the scheduler.
-*/
+/* Run by the sched thread. */
+static int __sched_check_pendings(const void *data)
+{
+	struct sip_pvt *pvt = (void *) data;
+	struct ast_channel *owner;
+
+	owner = sip_pvt_lock_full(pvt);
+	check_pendings(pvt);
+	if (owner) {
+		ast_channel_unlock(owner);
+		ast_channel_unref(owner);
+	}
+	sip_pvt_unlock(pvt);
+
+	dialog_unref(pvt, "Check pending actions action");
+	return 0;
+}
+
+static void sched_check_pendings(struct sip_pvt *pvt)
+{
+	dialog_ref(pvt, "Check pending actions action");
+	if (ast_sched_add(sched, 0, __sched_check_pendings, pvt) < 0) {
+		/* Uh Oh.  Expect bad behavior. */
+		dialog_unref(pvt, "Failed to schedule check pending actions action");
+	}
+}
+
+/*!
+ * \brief Reset the NEEDREINVITE flag after waiting when we get 491 on a Re-invite
+ * to avoid race conditions between asterisk servers.
+ *
+ * \note Run by the sched thread.
+ */
 static int sip_reinvite_retry(const void *data)
 {
 	struct sip_pvt *p = (struct sip_pvt *) data;
 	struct ast_channel *owner;
 
-	sip_pvt_lock(p); /* called from schedule thread which requires a lock */
-	while ((owner = p->owner) && ast_channel_trylock(owner)) {
-		sip_pvt_unlock(p);
-		usleep(1);
-		sip_pvt_lock(p);
-	}
+	owner = sip_pvt_lock_full(p);
 	ast_set_flag(&p->flags[0], SIP_NEEDREINVITE);
 	p->waitid = -1;
 	check_pendings(p);
 	sip_pvt_unlock(p);
 	if (owner) {
 		ast_channel_unlock(owner);
+		ast_channel_unref(owner);
 	}
-	dialog_unref(p, "unref the dialog ptr from sip_reinvite_retry, because it held a dialog ptr");
+	dialog_unref(p, "Schedule waitid complete");
 	return 0;
 }
 
+/* Run by the sched thread. */
+static int __stop_reinvite_retry(const void *data)
+{
+	struct sip_pvt *pvt = (void *) data;
+
+	AST_SCHED_DEL_UNREF(sched, pvt->waitid,
+		dialog_unref(pvt, "Stop scheduled waitid"));
+	dialog_unref(pvt, "Stop reinvite retry action");
+	return 0;
+}
+
+static void stop_reinvite_retry(struct sip_pvt *pvt)
+{
+	dialog_ref(pvt, "Stop reinvite retry action");
+	if (ast_sched_add(sched, 0, __stop_reinvite_retry, pvt) < 0) {
+		/* Uh Oh.  Expect bad behavior. */
+		dialog_unref(pvt, "Failed to schedule stop reinvite retry action");
+	}
+}
+
 /*!
  * \brief Handle authentication challenge for SIP UPDATE
  *
@@ -23007,9 +23464,7 @@ static void handle_response_invite(struct sip_pvt *p, int resp, const char *rest
 
 	if ((resp >= 200 && reinvite)) {
 		p->ongoing_reinvite = 0;
-		if (p->reinviteid > -1) {
-			AST_SCHED_DEL_UNREF(sched, p->reinviteid, dialog_unref(p, "unref dialog for reinvite timeout because of a final response"));
-		}
+		stop_reinviteid(p);
 	}
 
 	/* Final response, clear out pending invite */
@@ -23027,16 +23482,16 @@ static void handle_response_invite(struct sip_pvt *p, int resp, const char *rest
 	switch (resp) {
 	case 100:	/* Trying */
 	case 101:	/* Dialog establishment */
-		if (!req->ignore && p->invitestate != INV_CANCELLED && sip_cancel_destroy(p)) {
-			ast_log(LOG_WARNING, "Unable to cancel SIP destruction.  Expect bad things.\n");
+		if (!req->ignore && p->invitestate != INV_CANCELLED) {
+			sip_cancel_destroy(p);
 		}
-		check_pendings(p);
+		sched_check_pendings(p);
 		break;
 
 	case 180:	/* 180 Ringing */
 	case 182:       /* 182 Queued */
-		if (!req->ignore && p->invitestate != INV_CANCELLED && sip_cancel_destroy(p)) {
-			ast_log(LOG_WARNING, "Unable to cancel SIP destruction.  Expect bad things.\n");
+		if (!req->ignore && p->invitestate != INV_CANCELLED) {
+			sip_cancel_destroy(p);
 		}
 		/* Store Route-set from provisional SIP responses so
 		 * early-dialog request can be routed properly
@@ -23088,12 +23543,13 @@ static void handle_response_invite(struct sip_pvt *p, int resp, const char *rest
 			}
 			ast_rtp_instance_activate(p->rtp);
 		}
-		check_pendings(p);
+		sched_check_pendings(p);
 		break;
 
 	case 181:	/* Call Is Being Forwarded */
-		if (!req->ignore && (p->invitestate != INV_CANCELLED) && sip_cancel_destroy(p))
-			ast_log(LOG_WARNING, "Unable to cancel SIP destruction.  Expect bad things.\n");
+		if (!req->ignore && p->invitestate != INV_CANCELLED) {
+			sip_cancel_destroy(p);
+		}
 		/* Store Route-set from provisional SIP responses so
 		 * early-dialog request can be routed properly
 		 * */
@@ -23120,12 +23576,12 @@ static void handle_response_invite(struct sip_pvt *p, int resp, const char *rest
 			ast_party_redirecting_free(&redirecting);
 			sip_handle_cc(p, req, AST_CC_CCNR);
 		}
-		check_pendings(p);
+		sched_check_pendings(p);
 		break;
 
 	case 183:	/* Session progress */
-		if (!req->ignore && (p->invitestate != INV_CANCELLED) && sip_cancel_destroy(p)) {
-			ast_log(LOG_WARNING, "Unable to cancel SIP destruction.  Expect bad things.\n");
+		if (!req->ignore && p->invitestate != INV_CANCELLED) {
+			sip_cancel_destroy(p);
 		}
 		/* Store Route-set from provisional SIP responses so
 		 * early-dialog request can be routed properly
@@ -23181,12 +23637,12 @@ static void handle_response_invite(struct sip_pvt *p, int resp, const char *rest
 				ast_queue_control(p->owner, AST_CONTROL_RINGING);
 			}
 		}
-		check_pendings(p);
+		sched_check_pendings(p);
 		break;
 
 	case 200:	/* 200 OK on invite - someone's answering our call */
-		if (!req->ignore && (p->invitestate != INV_CANCELLED) && sip_cancel_destroy(p)) {
-			ast_log(LOG_WARNING, "Unable to cancel SIP destruction.  Expect bad things.\n");
+		if (!req->ignore && p->invitestate != INV_CANCELLED) {
+			sip_cancel_destroy(p);
 		}
 		p->authtries = 0;
 		if (find_sdp(req)) {
@@ -23331,7 +23787,7 @@ static void handle_response_invite(struct sip_pvt *p, int resp, const char *rest
 		p->invitestate = INV_TERMINATED;
 		ast_set_flag(&p->flags[1], SIP_PAGE2_DIALOG_ESTABLISHED);
 		xmitres = transmit_request(p, SIP_ACK, seqno, XMIT_UNRELIABLE, TRUE);
-		check_pendings(p);
+		sched_check_pendings(p);
 		break;
 
 	case 407: /* Proxy authentication */
@@ -23411,14 +23867,22 @@ static void handle_response_invite(struct sip_pvt *p, int resp, const char *rest
 		if (p->owner && !req->ignore) {
 			struct ast_party_redirecting redirecting;
 			struct ast_set_party_redirecting update_redirecting;
+			char *quoted_rest = ast_alloca(strlen(rest) + 3);
+
 			ast_party_redirecting_set_init(&redirecting, ast_channel_redirecting(p->owner));
 			memset(&update_redirecting, 0, sizeof(update_redirecting));
 
-			redirecting.reason.code = sip_reason_str_to_code(rest);
-			redirecting.reason.str = ast_strdup(rest);
+			redirecting.reason.code = ast_redirecting_reason_parse(rest);
+			if (redirecting.reason.code < 0) {
+				sprintf(quoted_rest, "\"%s\"", rest);/* Safe */
+
+				redirecting.reason.code = AST_REDIRECTING_REASON_UNKNOWN;
+				redirecting.reason.str = quoted_rest;
+			} else {
+				redirecting.reason.str = "";
+			}
 
 			ast_channel_queue_redirecting_update(p->owner, &redirecting, &update_redirecting);
-			ast_party_redirecting_free(&redirecting);
 
 			ast_queue_control(p->owner, AST_CONTROL_BUSY);
 		}
@@ -23435,7 +23899,7 @@ static void handle_response_invite(struct sip_pvt *p, int resp, const char *rest
 			update_call_counter(p, DEC_CALL_LIMIT);
 			append_history(p, "Hangup", "Got 487 on CANCEL request from us on call without owner. Killing this dialog.");
 		}
-		check_pendings(p);
+		sched_check_pendings(p);
 		sip_scheddestroy(p, DEFAULT_TRANS_TIMEOUT);
 		break;
 	case 415: /* Unsupported media type */
@@ -23467,6 +23931,7 @@ static void handle_response_invite(struct sip_pvt *p, int resp, const char *rest
 				/* Reset the flag after a while
 				 */
 				int wait;
+
 				/* RFC 3261, if owner of call, wait between 2.1 to 4 seconds,
 				 * if not owner of call, wait 0 to 2 seconds */
 				if (p->outgoing_call) {
@@ -23474,7 +23939,12 @@ static void handle_response_invite(struct sip_pvt *p, int resp, const char *rest
 				} else {
 					wait = ast_random() % 2000;
 				}
-				p->waitid = ast_sched_add(sched, wait, sip_reinvite_retry, dialog_ref(p, "passing dialog ptr into sched structure based on waitid for sip_reinvite_retry."));
+				dialog_ref(p, "Schedule waitid for sip_reinvite_retry.");
+				p->waitid = ast_sched_add(sched, wait, sip_reinvite_retry, p);
+				if (p->waitid < 0) {
+					/* Uh Oh.  Expect bad behavior. */
+					dialog_ref(p, "Failed to schedule waitid");
+				}
 				ast_debug(2, "Reinvite race. Scheduled sip_reinvite_retry in %d secs in handle_response_invite (waitid %d, dialog '%s')\n",
 						wait, p->waitid, p->callid);
 			}
@@ -23589,9 +24059,7 @@ static void handle_response_subscribe(struct sip_pvt *p, int resp, const char *r
 			p->options = NULL;
 		}
 		p->mwi->subscribed = 1;
-		if ((p->mwi->resub = ast_sched_add(sched, mwi_expiry * 1000, sip_subscribe_mwi_do, ao2_t_bump(p->mwi, "mwi ast_sched_add"))) < 0) {
-			ao2_t_ref(p->mwi, -1, "mwi ast_sched_add < 0");
-		}
+		start_mwi_subscription(p->mwi, mwi_expiry * 1000);
 		break;
 	case 401:
 	case 407:
@@ -23762,8 +24230,8 @@ static int handle_response_register(struct sip_pvt *p, int resp, const char *res
 			break;
 		}
 		ast_log(LOG_WARNING, "Forbidden - wrong password on authentication for REGISTER for '%s' to '%s'\n", p->registry->username, p->registry->hostname);
-		AST_SCHED_DEL_UNREF(sched, r->timeout, ao2_t_ref(r, -1, "reg ptr unref from handle_response_register 403"));
 		r->regstate = REG_STATE_NOAUTH;
+		stop_register_timeout(r);
 		sip_publish_registry(r->username, r->hostname, regstate2str(r->regstate));
 		pvt_set_needdestroy(p, "received 403 response");
 		break;
@@ -23773,8 +24241,8 @@ static int handle_response_register(struct sip_pvt *p, int resp, const char *res
 		if (r->call)
 			r->call = dialog_unref(r->call, "unsetting registry->call pointer-- case 404");
 		r->regstate = REG_STATE_REJECTED;
+		stop_register_timeout(r);
 		sip_publish_registry(r->username, r->hostname, regstate2str(r->regstate));
-		AST_SCHED_DEL_UNREF(sched, r->timeout, ao2_t_ref(r, -1, "reg ptr unref from handle_response_register 404"));
 		break;
 	case 407:	/* Proxy auth */
 		if (p->authtries == MAX_AUTHTRIES || do_register_auth(p, req, resp)) {
@@ -23793,7 +24261,6 @@ static int handle_response_register(struct sip_pvt *p, int resp, const char *res
 	case 423:	/* Interval too brief */
 		r->expiry = atoi(sip_get_header(req, "Min-Expires"));
 		ast_log(LOG_WARNING, "Got 423 Interval too brief for service %s@%s, minimum is %d seconds\n", p->registry->username, p->registry->hostname, r->expiry);
-		AST_SCHED_DEL_UNREF(sched, r->timeout, ao2_t_ref(r, -1, "reg ptr unref from handle_response_register 423"));
 		if (r->call) {
 			r->call = dialog_unref(r->call, "unsetting registry->call pointer-- case 423");
 			pvt_set_needdestroy(p, "received 423 response");
@@ -23802,6 +24269,7 @@ static int handle_response_register(struct sip_pvt *p, int resp, const char *res
 			ast_log(LOG_WARNING, "Required expiration time from %s@%s is too high, giving up\n", p->registry->username, p->registry->hostname);
 			r->expiry = r->configured_expiry;
 			r->regstate = REG_STATE_REJECTED;
+			stop_register_timeout(r);
 		} else {
 			r->regstate = REG_STATE_UNREGISTERED;
 			transmit_register(r, SIP_REGISTER, NULL, NULL);
@@ -23817,8 +24285,8 @@ static int handle_response_register(struct sip_pvt *p, int resp, const char *res
 		if (r->call)
 			r->call = dialog_unref(r->call, "unsetting registry->call pointer-- case 4xx");
 		r->regstate = REG_STATE_REJECTED;
+		stop_register_timeout(r);
 		sip_publish_registry(r->username, r->hostname, regstate2str(r->regstate));
-		AST_SCHED_DEL_UNREF(sched, r->timeout, ao2_t_ref(r, -1, "reg ptr unref from handle_response_register 479"));
 		break;
 	case 200:	/* 200 OK */
 		if (!r) {
@@ -23827,15 +24295,15 @@ static int handle_response_register(struct sip_pvt *p, int resp, const char *res
 			return 0;
 		}
 
-		r->regstate = REG_STATE_REGISTERED;
-		r->regtime = ast_tvnow();		/* Reset time of last successful registration */
-		sip_publish_registry(r->username, r->hostname, regstate2str(r->regstate));
-		r->regattempts = 0;
 		ast_debug(1, "Registration successful\n");
 		if (r->timeout > -1) {
 			ast_debug(1, "Cancelling timeout %d\n", r->timeout);
 		}
-		AST_SCHED_DEL_UNREF(sched, r->timeout, ao2_t_ref(r, -1, "reg ptr unref from handle_response_register 200"));
+		r->regstate = REG_STATE_REGISTERED;
+		stop_register_timeout(r);
+		r->regtime = ast_tvnow();		/* Reset time of last successful registration */
+		sip_publish_registry(r->username, r->hostname, regstate2str(r->regstate));
+		r->regattempts = 0;
 		if (r->call)
 			r->call = dialog_unref(r->call, "unsetting registry->call pointer-- case 200");
 		ao2_t_replace(p->registry, NULL, "unref registry entry p->registry");
@@ -23889,10 +24357,7 @@ static int handle_response_register(struct sip_pvt *p, int resp, const char *res
 		r->refresh= (int) expires_ms / 1000;
 
 		/* Schedule re-registration before we expire */
-		AST_SCHED_REPLACE_UNREF(r->expire, sched, expires_ms, sip_reregister, r,
-								ao2_t_ref(_data, -1, "unref in REPLACE del fail"),
-								ao2_t_ref(r, -1, "unref in REPLACE add fail"),
-								ao2_t_ref(r, +1, "The Addition side of REPLACE"));
+		start_reregister_timeout(r, expires_ms);
 	}
 	return 1;
 }
@@ -24467,8 +24932,9 @@ static void handle_response(struct sip_pvt *p, int resp, const char *rest, struc
 				}
 			} else if ((resp >= 100) && (resp < 200)) {
 				if (sipmethod == SIP_INVITE) {
-					if (!req->ignore && sip_cancel_destroy(p))
-						ast_log(LOG_WARNING, "Unable to cancel SIP destruction.  Expect bad things.\n");
+					if (!req->ignore) {
+						sip_cancel_destroy(p);
+					}
 					if (find_sdp(req))
 						process_sdp(p, req, SDP_T38_NONE);
 					if (p->owner) {
@@ -24534,8 +25000,9 @@ static void handle_response(struct sip_pvt *p, int resp, const char *rest, struc
 		default:	/* Errors without handlers */
 			if ((resp >= 100) && (resp < 200)) {
 				if (sipmethod == SIP_INVITE) {	/* re-invite */
-					if (!req->ignore && sip_cancel_destroy(p))
-						ast_log(LOG_WARNING, "Unable to cancel SIP destruction.  Expect bad things.\n");
+					if (!req->ignore) {
+						sip_cancel_destroy(p);
+					}
 				}
 			} else if ((resp >= 200) && (resp < 300)) { /* on any unrecognized 2XX response do the following */
 				if (sipmethod == SIP_INVITE) {
@@ -24552,10 +25019,10 @@ static void handle_response(struct sip_pvt *p, int resp, const char *rest, struc
 				case 502: /* Bad gateway */
 				case 503: /* Service Unavailable */
 				case 504: /* Server timeout */
-
 					/* re-invite failed */
-					if (sipmethod == SIP_INVITE && sip_cancel_destroy(p))
-						ast_log(LOG_WARNING, "Unable to cancel SIP destruction.  Expect bad things.\n");
+					if (sipmethod == SIP_INVITE) {
+						sip_cancel_destroy(p);
+					}
 					break;
 				}
 			}
@@ -25012,27 +25479,89 @@ static int do_magic_pickup(struct ast_channel *channel, const char *extension, c
 	return 0;
 }
 
-/*! \brief Called to deny a T38 reinvite if the core does not respond to our request */
+/*!
+ * \brief Called to deny a T38 reinvite if the core does not respond to our request
+ *
+ * \note Run by the sched thread.
+ */
 static int sip_t38_abort(const void *data)
 {
-	struct sip_pvt *p = (struct sip_pvt *) data;
+	struct sip_pvt *pvt = (struct sip_pvt *) data;
+	struct ast_channel *owner;
 
-	sip_pvt_lock(p);
-	/* an application may have taken ownership of the T.38 negotiation on this
-	 * channel while we were waiting to grab the lock... if it did, the scheduler
-	 * id will have been reset to -1, which is our indication that we do *not*
-	 * want to abort the negotiation process
+	owner = sip_pvt_lock_full(pvt);
+	pvt->t38id = -1;
+
+	/*
+	 * An application may have taken ownership of the T.38 negotiation
+	 * on the channel while we were waiting to grab the lock.  If it
+	 * did, the T.38 state will have been changed.  This is our
+	 * indication that we do *not* want to abort the negotiation
+	 * process.
 	 */
-	if (p->t38id != -1) {
-		change_t38_state(p, T38_REJECTED);
-		transmit_response_reliable(p, "488 Not acceptable here", &p->initreq);
-		p->t38id = -1;
-		dialog_unref(p, "unref the dialog ptr from sip_t38_abort, because it held a dialog ptr");
+	if (pvt->t38.state == T38_PEER_REINVITE) {
+		/* Still waiting for a response on timeout so reject the offer. */
+		change_t38_state(pvt, T38_REJECTED);
+		transmit_response_reliable(pvt, "488 Not acceptable here", &pvt->initreq);
 	}
-	sip_pvt_unlock(p);
+
+	if (owner) {
+		ast_channel_unlock(owner);
+		ast_channel_unref(owner);
+	}
+	sip_pvt_unlock(pvt);
+	dialog_unref(pvt, "t38id complete");
+	return 0;
+}
+
+/* Run by the sched thread. */
+static int __stop_t38_abort_timer(const void *data)
+{
+	struct sip_pvt *pvt = (void *) data;
+
+	AST_SCHED_DEL_UNREF(sched, pvt->t38id,
+		dialog_unref(pvt, "Stop scheduled t38id"));
+	dialog_unref(pvt, "Stop t38id action");
+	return 0;
+}
+
+static void stop_t38_abort_timer(struct sip_pvt *pvt)
+{
+	dialog_ref(pvt, "Stop t38id action");
+	if (ast_sched_add(sched, 0, __stop_t38_abort_timer, pvt) < 0) {
+		/* Uh Oh.  Expect bad behavior. */
+		dialog_unref(pvt, "Failed to schedule stop t38id action");
+	}
+}
+
+/* Run by the sched thread. */
+static int __start_t38_abort_timer(const void *data)
+{
+	struct sip_pvt *pvt = (void *) data;
+
+	AST_SCHED_DEL_UNREF(sched, pvt->t38id,
+		dialog_unref(pvt, "Stop scheduled t38id"));
+
+	dialog_ref(pvt, "Schedule t38id");
+	pvt->t38id = ast_sched_add(sched, 5000, sip_t38_abort, pvt);
+	if (pvt->t38id < 0) {
+		/* Uh Oh.  Expect bad behavior. */
+		dialog_unref(pvt, "Failed to schedule t38id");
+	}
+
+	dialog_unref(pvt, "Start t38id action");
 	return 0;
 }
 
+static void start_t38_abort_timer(struct sip_pvt *pvt)
+{
+	dialog_ref(pvt, "Start t38id action");
+	if (ast_sched_add(sched, 0, __start_t38_abort_timer, pvt) < 0) {
+		/* Uh Oh.  Expect bad behavior. */
+		dialog_unref(pvt, "Failed to schedule start t38id action");
+	}
+}
+
 /*!
  * \brief bare-bones support for SIP UPDATE
  *
@@ -25503,8 +26032,8 @@ static int handle_request_invite(struct sip_pvt *p, struct sip_request *req, str
 	if (!req->ignore) {
 		int newcall = (p->initreq.headers ? TRUE : FALSE);
 
-		if (sip_cancel_destroy(p))
-			ast_log(LOG_WARNING, "Unable to cancel SIP destruction.  Expect bad things.\n");
+		sip_cancel_destroy(p);
+
 		/* This also counts as a pending invite */
 		p->pendinginvite = seqno;
 		check_via(p, req);
@@ -25773,7 +26302,7 @@ static int handle_request_invite(struct sip_pvt *p, struct sip_request *req, str
 	/* Check if OLI/ANI-II is present in From: */
 	parse_oli(req, p->owner);
 
-	if (reinvite && p->stimer->st_active == TRUE) {
+	if (reinvite && p->stimer) {
 		restart_session_timer(p);
 	}
 
@@ -25915,11 +26444,7 @@ static int handle_request_invite(struct sip_pvt *p, struct sip_request *req, str
 			transmit_response(p, "100 Trying", req);
 
 			if (p->t38.state == T38_PEER_REINVITE) {
-				if (p->t38id > -1) {
-					/* reset t38 abort timer */
-					AST_SCHED_DEL_UNREF(sched, p->t38id, dialog_unref(p, "remove ref for t38id"));
-				}
-				p->t38id = ast_sched_add(sched, 5000, sip_t38_abort, dialog_ref(p, "passing dialog ptr into sched structure based on t38id for sip_t38_abort."));
+				start_t38_abort_timer(p);
 			} else if (p->t38.state == T38_ENABLED) {
 				ast_set_flag(&p->flags[1], SIP_PAGE2_DIALOG_ESTABLISHED);
 				transmit_response_with_t38_sdp(p, "200 OK", req, (reinvite ? XMIT_RELIABLE : (req->ignore ?  XMIT_UNRELIABLE : XMIT_CRITICAL)));
@@ -26468,13 +26993,10 @@ static int handle_request_cancel(struct sip_pvt *p, struct sip_request *req)
 		 */
 		for (pkt = p->packets, prev_pkt = NULL; pkt; prev_pkt = pkt, pkt = pkt->next) {
 			if (pkt->seqno == p->lastinvite && pkt->response_code == 487) {
-				AST_SCHED_DEL(sched, pkt->retransid);
+				/* Unlink and destroy the packet object. */
 				UNLINK(pkt, p->packets, prev_pkt);
-				dialog_unref(pkt->owner, "unref packet->owner from dialog");
-				if (pkt->data) {
-					ast_free(pkt->data);
-				}
-				ast_free(pkt);
+				stop_retrans_pkt(pkt);
+				ao2_t_ref(pkt, -1, "Packet retransmission list");
 				break;
 			}
 		}
@@ -26696,7 +27218,7 @@ static int handle_request_bye(struct sip_pvt *p, struct sip_request *req)
 
 	/* Destroy any pending invites so we won't try to do another
 	 * scheduled reINVITE. */
-	AST_SCHED_DEL_UNREF(sched, p->waitid, dialog_unref(p, "decrement refcount from sip_destroy because waitid won't be scheduled"));
+	stop_reinvite_retry(p);
 
 	return 1;
 }
@@ -27775,10 +28297,13 @@ static int handle_request_subscribe(struct sip_pvt *p, struct sip_request *req,
 						action, p->exten, p->context, p->username);
 			}
 		}
-		if (p->autokillid > -1 && sip_cancel_destroy(p))	/* Remove subscription expiry for renewals */
-			ast_log(LOG_WARNING, "Unable to cancel SIP destruction.  Expect bad things.\n");
-		if (p->expiry > 0)
-			sip_scheddestroy(p, (p->expiry + 10) * 1000);	/* Set timer for destruction of call at expiration */
+
+		/* Remove subscription expiry for renewals */
+		sip_cancel_destroy(p);
+		if (p->expiry > 0) {
+			/* Set timer for destruction of call at expiration */
+			sip_scheddestroy(p, (p->expiry + 10) * 1000);
+		}
 
 		if (p->subscribed == MWI_NOTIFICATION) {
 			ast_set_flag(&p->flags[1], SIP_PAGE2_DIALOG_ESTABLISHED);
@@ -28225,7 +28750,7 @@ static int handle_incoming(struct sip_pvt *p, struct sip_request *req, struct as
 					ast_queue_control(p->owner, AST_CONTROL_SRCCHANGE);
 				}
 			}
-			check_pendings(p);
+			sched_check_pendings(p);
 		} else if (p->glareinvite == seqno) {
 			/* handle ack for the 491 pending sent for glareinvite */
 			p->glareinvite = 0;
@@ -28992,41 +29517,110 @@ static void acl_change_stasis_cb(void *data, struct stasis_subscription *sub,
 	restart_monitor();
 }
 
-/*! \brief Session-Timers: Restart session timer */
-static void restart_session_timer(struct sip_pvt *p)
+/*!
+ * \brief Session-Timers: Process session refresh timeout event
+ *
+ * \note Run by the sched thread.
+ */
+static int proc_session_timer(const void *vp)
 {
-	if (p->stimer->st_active == TRUE) {
-		ast_debug(2, "Session timer stopped: %d - %s\n", p->stimer->st_schedid, p->callid);
-		AST_SCHED_DEL_UNREF(sched, p->stimer->st_schedid,
-				dialog_unref(p, "Removing session timer ref"));
-		start_session_timer(p);
+	struct sip_pvt *p = (struct sip_pvt *) vp;
+	struct sip_st_dlg *stimer = p->stimer;
+	int res = 0;
+
+	ast_assert(stimer != NULL);
+
+	ast_debug(2, "Session timer expired: %d - %s\n", stimer->st_schedid, p->callid);
+
+	if (!p->owner) {
+		goto return_unref;
 	}
-}
 
+	if ((stimer->st_active != TRUE) || (ast_channel_state(p->owner) != AST_STATE_UP)) {
+		goto return_unref;
+	}
 
-/*! \brief Session-Timers: Stop session timer */
-static void stop_session_timer(struct sip_pvt *p)
+	if (stimer->st_ref == SESSION_TIMER_REFRESHER_US) {
+		res = 1;
+		if (T38_ENABLED == p->t38.state) {
+			transmit_reinvite_with_sdp(p, TRUE, TRUE);
+		} else {
+			transmit_reinvite_with_sdp(p, FALSE, TRUE);
+		}
+	} else {
+		struct ast_channel *owner;
+
+		ast_log(LOG_WARNING, "Session-Timer expired - %s\n", p->callid);
+
+		owner = sip_pvt_lock_full(p);
+		if (owner) {
+			send_session_timeout(owner, "SIPSessionTimer");
+			ast_softhangup_nolock(owner, AST_SOFTHANGUP_DEV);
+			ast_channel_unlock(owner);
+			ast_channel_unref(owner);
+		}
+		sip_pvt_unlock(p);
+	}
+
+return_unref:
+	if (!res) {
+		/* Session timer processing is no longer needed. */
+		ast_debug(2, "Session timer stopped: %d - %s\n",
+			stimer->st_schedid, p->callid);
+		/* Don't pass go, don't collect $200.. we are the scheduled
+		 * callback. We can rip ourself out here. */
+		stimer->st_schedid = -1;
+		stimer->st_active = FALSE;
+
+		/* If we are not asking to be rescheduled, then we need to release our
+		 * reference to the dialog. */
+		dialog_unref(p, "Session timer st_schedid complete");
+	}
+
+	return res;
+}
+
+static void do_stop_session_timer(struct sip_pvt *pvt)
 {
-	if (p->stimer->st_active == TRUE) {
-		p->stimer->st_active = FALSE;
-		ast_debug(2, "Session timer stopped: %d - %s\n", p->stimer->st_schedid, p->callid);
-		AST_SCHED_DEL_UNREF(sched, p->stimer->st_schedid,
-				dialog_unref(p, "removing session timer ref"));
+	struct sip_st_dlg *stimer = pvt->stimer;
+
+	if (-1 < stimer->st_schedid) {
+		ast_debug(2, "Session timer stopped: %d - %s\n",
+			stimer->st_schedid, pvt->callid);
+		AST_SCHED_DEL_UNREF(sched, stimer->st_schedid,
+			dialog_unref(pvt, "Stop scheduled session timer st_schedid"));
 	}
 }
 
+/* Run by the sched thread. */
+static int __stop_session_timer(const void *data)
+{
+	struct sip_pvt *pvt = (void *) data;
 
-/*! \brief Session-Timers: Start session timer */
-static void start_session_timer(struct sip_pvt *p)
+	do_stop_session_timer(pvt);
+	dialog_unref(pvt, "Stop session timer action");
+	return 0;
+}
+
+/*! \brief Session-Timers: Stop session timer */
+static void stop_session_timer(struct sip_pvt *pvt)
 {
-	unsigned int timeout_ms;
+	struct sip_st_dlg *stimer = pvt->stimer;
 
-	if (p->stimer->st_schedid > -1) {
-		/* in the event a timer is already going, stop it */
-		ast_debug(2, "Session timer stopped: %d - %s\n", p->stimer->st_schedid, p->callid);
-		AST_SCHED_DEL_UNREF(sched, p->stimer->st_schedid,
-			dialog_unref(p, "unref stimer->st_schedid from dialog"));
+	stimer->st_active = FALSE;
+	dialog_ref(pvt, "Stop session timer action");
+	if (ast_sched_add(sched, 0, __stop_session_timer, pvt) < 0) {
+		/* Uh Oh.  Expect bad behavior. */
+		dialog_unref(pvt, "Failed to schedule stop session timer action");
 	}
+}
+
+/* Run by the sched thread. */
+static int __start_session_timer(const void *data)
+{
+	struct sip_pvt *pvt = (void *) data;
+	struct sip_st_dlg *stimer = pvt->stimer;
+	unsigned int timeout_ms;
 
 	/*
 	 * RFC 4028 Section 10
@@ -29037,97 +29631,50 @@ static void start_session_timer(struct sip_pvt *p)
 	 * interval is RECOMMENDED.
 	 */
 
-	timeout_ms = (1000 * p->stimer->st_interval);
-	if (p->stimer->st_ref == SESSION_TIMER_REFRESHER_US) {
+	timeout_ms = (1000 * stimer->st_interval);
+	if (stimer->st_ref == SESSION_TIMER_REFRESHER_US) {
 		timeout_ms /= 2;
 	} else {
 		timeout_ms -= MIN(timeout_ms / 3, 32000);
 	}
 
-	p->stimer->st_schedid = ast_sched_add(sched, timeout_ms, proc_session_timer,
-			dialog_ref(p, "adding session timer ref"));
+	/* in the event a timer is already going, stop it */
+	do_stop_session_timer(pvt);
 
-	if (p->stimer->st_schedid < 0) {
-		dialog_unref(p, "removing session timer ref");
-		ast_log(LOG_ERROR, "ast_sched_add failed - %s\n", p->callid);
+	dialog_ref(pvt, "Schedule session timer st_schedid");
+	stimer->st_schedid = ast_sched_add(sched, timeout_ms, proc_session_timer, pvt);
+	if (stimer->st_schedid < 0) {
+		dialog_unref(pvt, "Failed to schedule session timer st_schedid");
 	} else {
-		p->stimer->st_active = TRUE;
-		ast_debug(2, "Session timer started: %d - %s %ums\n", p->stimer->st_schedid, p->callid, timeout_ms);
+		ast_debug(2, "Session timer started: %d - %s %ums\n",
+			stimer->st_schedid, pvt->callid, timeout_ms);
 	}
-}
 
+	dialog_unref(pvt, "Start session timer action");
+	return 0;
+}
 
-/*! \brief Session-Timers: Process session refresh timeout event */
-static int proc_session_timer(const void *vp)
+/*! \brief Session-Timers: Start session timer */
+static void start_session_timer(struct sip_pvt *pvt)
 {
-	struct sip_pvt *p = (struct sip_pvt *) vp;
-	int res = 0;
-
-	if (!p->stimer) {
-		ast_log(LOG_WARNING, "Null stimer in proc_session_timer - %s\n", p->callid);
-		goto return_unref;
-	}
-
-	ast_debug(2, "Session timer expired: %d - %s\n", p->stimer->st_schedid, p->callid);
-
-	if (!p->owner) {
-		goto return_unref;
-	}
-
-	if ((p->stimer->st_active != TRUE) || (ast_channel_state(p->owner) != AST_STATE_UP)) {
-		goto return_unref;
-	}
-
-	if (p->stimer->st_ref == SESSION_TIMER_REFRESHER_US) {
-		res = 1;
-		if (T38_ENABLED == p->t38.state) {
-			transmit_reinvite_with_sdp(p, TRUE, TRUE);
-		} else {
-			transmit_reinvite_with_sdp(p, FALSE, TRUE);
-		}
-	} else {
-		if (p->stimer->quit_flag) {
-			goto return_unref;
-		}
-		ast_log(LOG_WARNING, "Session-Timer expired - %s\n", p->callid);
-		sip_pvt_lock(p);
-		while (p->owner && ast_channel_trylock(p->owner)) {
-			sip_pvt_unlock(p);
-			usleep(1);
-			if (p->stimer && p->stimer->quit_flag) {
-				goto return_unref;
-			}
-			sip_pvt_lock(p);
-		}
+	struct sip_st_dlg *stimer = pvt->stimer;
 
-		send_session_timeout(p->owner, "SIPSessionTimer");
-		ast_softhangup_nolock(p->owner, AST_SOFTHANGUP_DEV);
-		ast_channel_unlock(p->owner);
-		sip_pvt_unlock(p);
+	stimer->st_active = TRUE;
+	dialog_ref(pvt, "Start session timer action");
+	if (ast_sched_add(sched, 0, __start_session_timer, pvt) < 0) {
+		/* Uh Oh.  Expect bad behavior. */
+		dialog_unref(pvt, "Failed to schedule start session timer action");
 	}
+}
 
-return_unref:
-	if (!res) {
-		/* An error occurred.  Stop session timer processing */
-		if (p->stimer) {
-			ast_debug(2, "Session timer stopped: %d - %s\n", p->stimer->st_schedid, p->callid);
-			/* Don't pass go, don't collect $200.. we are the scheduled
-			 * callback. We can rip ourself out here. */
-			p->stimer->st_schedid = -1;
-			/* Calling stop_session_timer is nice for consistent debug
-			 * logs. */
-			stop_session_timer(p);
-		}
-
-		/* If we are not asking to be rescheduled, then we need to release our
-		 * reference to the dialog. */
-		dialog_unref(p, "removing session timer ref");
+/*! \brief Session-Timers: Restart session timer */
+static void restart_session_timer(struct sip_pvt *p)
+{
+	if (p->stimer->st_active == TRUE) {
+		start_session_timer(p);
 	}
-
-	return res;
 }
 
-
 /*! \brief Session-Timers: Function for parsing Min-SE header */
 int parse_minse (const char *p_hdrval, int *const p_interval)
 {
@@ -31239,9 +31786,11 @@ static void display_nat_warning(const char *cat, int reason, struct ast_flags *f
 	}
 }
 
-static int cleanup_registration(void *obj, void *arg, int flags)
+/* Run by the sched thread. */
+static int __cleanup_registration(const void *data)
 {
-	struct sip_registry *reg = obj;
+	struct sip_registry *reg = (struct sip_registry *) data;
+
 	ao2_lock(reg);
 
 	if (reg->call) {
@@ -31250,12 +31799,12 @@ static int cleanup_registration(void *obj, void *arg, int flags)
 		dialog_unlink_all(reg->call);
 		reg->call = dialog_unref(reg->call, "remove iterator->call from registry traversal");
 	}
-	if (reg->expire > -1) {
-		AST_SCHED_DEL_UNREF(sched, reg->expire, ao2_t_ref(reg, -1, "reg ptr unref from reload config"));
-	}
-	if (reg->timeout > -1) {
-		AST_SCHED_DEL_UNREF(sched, reg->timeout, ao2_t_ref(reg, -1, "reg ptr unref from reload config"));
-	}
+
+	AST_SCHED_DEL_UNREF(sched, reg->expire,
+		ao2_t_ref(reg, -1, "Stop scheduled reregister timeout"));
+	AST_SCHED_DEL_UNREF(sched, reg->timeout,
+		ao2_t_ref(reg, -1, "Stop scheduled register timeout"));
+
 	if (reg->dnsmgr) {
 		ast_dnsmgr_release(reg->dnsmgr);
 		reg->dnsmgr = NULL;
@@ -31263,6 +31812,21 @@ static int cleanup_registration(void *obj, void *arg, int flags)
 	}
 
 	ao2_unlock(reg);
+
+	ao2_t_ref(reg, -1, "cleanup_registration action");
+	return 0;
+}
+
+static int cleanup_registration(void *obj, void *arg, int flags)
+{
+	struct sip_registry *reg = obj;
+
+	ao2_t_ref(reg, +1, "cleanup_registration action");
+	if (ast_sched_add(sched, 0, __cleanup_registration, reg) < 0) {
+		/* Uh Oh.  Expect bad behavior. */
+		ao2_t_ref(reg, -1, "Failed to schedule cleanup_registration action");
+	}
+
 	return CMP_MATCH;
 }
 
@@ -31465,6 +32029,7 @@ static int reload_config(enum channelreloadreason reason)
 	global_dynamic_exclude_static = 0;	/* Exclude static peers */
 	sip_cfg.tcp_enabled = FALSE;
 	sip_cfg.websocket_enabled = TRUE;
+	sip_cfg.websocket_write_timeout = AST_DEFAULT_WEBSOCKET_WRITE_TIMEOUT;
 
 	/* Session-Timers */
 	global_st_mode = SESSION_TIMER_MODE_ACCEPT;
@@ -32669,14 +33234,6 @@ static int sip_set_rtp_peer(struct ast_channel *chan, struct ast_rtp_instance *i
 	} else if (!ast_sockaddr_isnull(&p->redirip)) {
 		memset(&p->redirip, 0, sizeof(p->redirip));
 		changed = 1;
-
-		if (p->rtp) {
-			/* Enable RTCP since it will be inactive if we're coming back
-			 * from a reinvite */
-			ast_rtp_instance_set_prop(p->rtp, AST_RTP_PROPERTY_RTCP, 1);
-			/* Enable audio RTCP reads */
-			ast_channel_set_fd(chan, 1, ast_rtp_instance_fd(p->rtp, 1));
-		}
 	}
 
 	if (vinstance) {
@@ -32953,8 +33510,8 @@ static int sip_sipredirect(struct sip_pvt *p, const char *dest)
 
 			memset(ldomain, 0, sizeof(ldomain));
 			local_to_header++;
-			/* This is okey because lhost and lport are as big as tmp */
-			sscanf(local_to_header, "%256[^<>; ]", ldomain);
+			/* Will copy no more than 255 chars plus null terminator. */
+			sscanf(local_to_header, "%255[^<>; ]", ldomain);
 			if (ast_strlen_zero(ldomain)) {
 				ast_log(LOG_ERROR, "Can't find the host address\n");
 				return 0;
@@ -33063,10 +33620,7 @@ static void sip_send_all_registers(void)
 	while ((iterator = ao2_t_iterator_next(&iter, "sip_send_all_registers iter"))) {
 		ao2_lock(iterator);
 		ms += regspacing;
-		AST_SCHED_REPLACE_UNREF(iterator->expire, sched, ms, sip_reregister, iterator,
-								ao2_t_ref(_data, -1, "REPLACE sched del decs the refcount"),
-								ao2_t_ref(iterator, -1, "REPLACE sched add failure decs the refcount"),
-								ao2_t_ref(iterator, +1, "REPLACE sched add incs the refcount"));
+		start_reregister_timeout(iterator, ms);
 		ao2_unlock(iterator);
 		ao2_t_ref(iterator, -1, "sip_send_all_registers iter");
 	}
@@ -33077,18 +33631,12 @@ static void sip_send_all_registers(void)
 static void sip_send_all_mwi_subscriptions(void)
 {
 	struct ao2_iterator iter;
-	struct sip_subscription_mwi *iterator;
+	struct sip_subscription_mwi *mwi;
 
 	iter = ao2_iterator_init(subscription_mwi_list, 0);
-	while ((iterator = ao2_t_iterator_next(&iter, "sip_send_all_mwi_subscriptions iter"))) {
-		ao2_lock(iterator);
-		AST_SCHED_DEL(sched, iterator->resub);
-		ao2_t_ref(iterator, +1, "mwi added to schedule");
-		if ((iterator->resub = ast_sched_add(sched, 1, sip_subscribe_mwi_do, iterator)) < 0) {
-			ao2_t_ref(iterator, -1, "mwi failed to schedule");
-		}
-		ao2_unlock(iterator);
-		ao2_t_ref(iterator, -1, "sip_send_all_mwi_subscriptions iter");
+	while ((mwi = ao2_t_iterator_next(&iter, "sip_send_all_mwi_subscriptions iter"))) {
+		start_mwi_subscription(mwi, 1);
+		ao2_t_ref(mwi, -1, "sip_send_all_mwi_subscriptions iter");
 	}
 	ao2_iterator_destroy(&iter);
 }
@@ -34746,6 +35294,20 @@ static int unload_module(void)
 		ast_mutex_unlock(&monlock);
 	}
 
+	cleanup_all_regs();
+
+	{
+		struct ao2_iterator iter;
+		struct sip_subscription_mwi *mwi;
+
+		iter = ao2_iterator_init(subscription_mwi_list, 0);
+		while ((mwi = ao2_t_iterator_next(&iter, "unload_module iter"))) {
+			shutdown_mwi_subscription(mwi);
+			ao2_t_ref(mwi, -1, "unload_module iter");
+		}
+		ao2_iterator_destroy(&iter);
+	}
+
 	/* Destroy all the dialogs and free their memory */
 	i = ao2_iterator_init(dialogs, 0);
 	while ((p = ao2_t_iterator_next(&i, "iterate thru dialogs"))) {
@@ -34754,6 +35316,13 @@ static int unload_module(void)
 	}
 	ao2_iterator_destroy(&i);
 
+	/*
+	 * Since the monitor thread runs the scheduled events and we
+	 * just stopped the monitor thread above, we have to run any
+	 * pending scheduled immediate events in this thread.
+	 */
+	ast_sched_runq(sched);
+
 	/* Free memory for local network address mask */
 	ast_free_ha(localaddr);
 
@@ -34773,28 +35342,6 @@ static int unload_module(void)
 	ast_free(default_tls_cfg.cafile);
 	ast_free(default_tls_cfg.capath);
 
-	cleanup_all_regs();
-	ao2_cleanup(registry_list);
-
-	{
-		struct ao2_iterator iter;
-		struct sip_subscription_mwi *iterator;
-
-		iter = ao2_iterator_init(subscription_mwi_list, 0);
-		while ((iterator = ao2_t_iterator_next(&iter, "unload_module iter"))) {
-			ao2_lock(iterator);
-			if (iterator->dnsmgr) {
-				ast_dnsmgr_release(iterator->dnsmgr);
-				iterator->dnsmgr = NULL;
-				ao2_t_ref(iterator, -1, "dnsmgr release");
-			}
-			ao2_unlock(iterator);
-			ao2_t_ref(iterator, -1, "unload_module iter");
-		}
-		ao2_iterator_destroy(&iter);
-	}
-	ao2_cleanup(subscription_mwi_list);
-
 	/*
 	 * Wait awhile for the TCP/TLS thread container to become empty.
 	 *
@@ -34810,6 +35357,9 @@ static int unload_module(void)
 		ast_debug(2, "TCP/TLS thread container did not become empty :(\n");
 	}
 
+	ao2_cleanup(registry_list);
+	ao2_cleanup(subscription_mwi_list);
+
 	ao2_t_global_obj_release(g_bogus_peer, "Release the bogus peer.");
 
 	ao2_t_cleanup(peers, "unref the peers table");
@@ -34829,6 +35379,7 @@ static int unload_module(void)
 	close(sipsock);
 	io_context_destroy(io);
 	ast_sched_context_destroy(sched);
+	sched = NULL;
 	ast_context_destroy_by_name(used_context, "SIP");
 	ast_unload_realtime("sipregs");
 	ast_unload_realtime("sippeers");
diff --git a/channels/misdn_config.c b/channels/misdn_config.c
index 32f92c3..a3ea315 100644
--- a/channels/misdn_config.c
+++ b/channels/misdn_config.c
@@ -698,7 +698,7 @@ void misdn_cfg_get_desc (enum misdn_cfg_elements elem, void *buf, int bufsize, v
 	else if ((elem > MISDN_GEN_FIRST) && (elem < MISDN_GEN_LAST))
 		spec = (struct misdn_cfg_spec *)gen_spec;
 
-	if (!spec || !spec[place].desc)
+	if (!spec)
 		memset(buf, 0, 1);
 	else {
 		ast_copy_string(buf, spec[place].desc, bufsize);
diff --git a/channels/pjsip/dialplan_functions.c b/channels/pjsip/dialplan_functions.c
index eb6def6..b566503 100644
--- a/channels/pjsip/dialplan_functions.c
+++ b/channels/pjsip/dialplan_functions.c
@@ -819,7 +819,7 @@ int pjsip_acf_dial_contacts_read(struct ast_channel *chan, const char *cmd, char
 		return -1;
 	}
 
-	while ((aor_name = strsep(&rest, ","))) {
+	while ((aor_name = ast_strip(strsep(&rest, ",")))) {
 		RAII_VAR(struct ast_sip_aor *, aor, ast_sip_location_retrieve_aor(aor_name), ao2_cleanup);
 		RAII_VAR(struct ao2_container *, contacts, NULL, ao2_cleanup);
 		struct ao2_iterator it_contacts;
diff --git a/channels/sig_analog.c b/channels/sig_analog.c
index 1d44a29..467d548 100644
--- a/channels/sig_analog.c
+++ b/channels/sig_analog.c
@@ -70,7 +70,7 @@ static char analog_defaultozz[64] = "";
 
 static const struct {
 	enum analog_sigtype sigtype;
-	const char const *name;
+	const char *name;
 } sigtypes[] = {
 	{ ANALOG_SIG_FXOLS, "fxo_ls" },
 	{ ANALOG_SIG_FXOKS, "fxo_ks" },
@@ -97,7 +97,7 @@ static const struct {
 
 static const struct {
 	unsigned int cid_type;
-	const char const *name;
+	const char *name;
 } cidtypes[] = {
 	{ CID_SIG_BELL,   "bell" },
 	{ CID_SIG_V23,    "v23" },
@@ -2671,7 +2671,7 @@ static struct ast_frame *__analog_handle_event(struct analog_pvt *p, struct ast_
 {
 	int res, x;
 	int mysig;
-	enum analog_sub idx;
+	int idx;
 	char *c;
 	pthread_t threadid;
 	struct ast_channel *chan;
diff --git a/channels/sip/dialplan_functions.c b/channels/sip/dialplan_functions.c
index 5499db8..608f456 100644
--- a/channels/sip/dialplan_functions.c
+++ b/channels/sip/dialplan_functions.c
@@ -273,7 +273,7 @@ static int test_sip_rtpqos_1_get_stat(struct ast_rtp_instance *instance, struct
 AST_TEST_DEFINE(test_sip_rtpqos_1)
 {
 	int i, res = AST_TEST_PASS;
-	struct ast_rtp_engine test_engine = {
+	static struct ast_rtp_engine test_engine = {
 		.name = "test",
 		.new = test_sip_rtpqos_1_new,
 		.destroy = test_sip_rtpqos_1_destroy,
diff --git a/channels/sip/include/dialog.h b/channels/sip/include/dialog.h
index 582841d..291e3a6 100644
--- a/channels/sip/include/dialog.h
+++ b/channels/sip/include/dialog.h
@@ -39,23 +39,23 @@ struct sip_pvt *__sip_alloc(ast_string_field callid, struct ast_sockaddr *sin,
 #define sip_alloc(callid, addr, useglobal_nat, intended_method, req, logger_callid) \
 	__sip_alloc(callid, addr, useglobal_nat, intended_method, req, logger_callid, __FILE__, __LINE__, __PRETTY_FUNCTION__)
 
+/*!
+ * \brief Schedule final destruction of SIP dialog.
+ *
+ * \note This cannot be canceled.
+ *
+ * \details
+ * This function is used to keep a dialog around for a period of time in order
+ * to properly respond to any retransmits.
+ */
 void sip_scheddestroy_final(struct sip_pvt *p, int ms);
+
+/*! \brief Schedule destruction of SIP dialog */
 void sip_scheddestroy(struct sip_pvt *p, int ms);
-int sip_cancel_destroy(struct sip_pvt *p);
 
-/*! \brief Destroy SIP call structure.
- * Make it return NULL so the caller can do things like
- *	foo = sip_destroy(foo);
- * and reduce the chance of bugs due to dangling pointers.
- */
-struct sip_pvt *sip_destroy(struct sip_pvt *p);
+/*! \brief Cancel destruction of SIP dialog. */
+void sip_cancel_destroy(struct sip_pvt *pvt);
 
-/*! \brief Destroy SIP call structure.
- * Make it return NULL so the caller can do things like
- *	foo = sip_destroy(foo);
- * and reduce the chance of bugs due to dangling pointers.
- */
-void __sip_destroy(struct sip_pvt *p, int lockowner, int lockdialoglist);
 /*!
  * \brief Unlink a dialog from the dialogs container, as well as any other places
  * that it may be currently stored.
diff --git a/channels/sip/include/sip.h b/channels/sip/include/sip.h
index 82f208c..3e68321 100644
--- a/channels/sip/include/sip.h
+++ b/channels/sip/include/sip.h
@@ -547,8 +547,7 @@ enum sipregistrystate {
 		 * recover (not sure how correctly).
 		 */
 
-	REG_STATE_TIMEOUT,	/*!< Registration timed out
-		* \note XXX unused */
+	REG_STATE_TIMEOUT,	/*!< Registration about to expire, renewing registration */
 
 	REG_STATE_NOAUTH,	/*!< We have no accepted credentials
 		 * \note fatal - no chance to proceed */
@@ -952,7 +951,6 @@ struct sip_st_dlg {
 	int st_cached_max_se;              /*!< Session-Timers cached Session-Expires */
 	enum st_mode st_cached_mode;       /*!< Session-Timers cached M.O. */
 	enum st_refresher st_cached_ref;   /*!< Session-Timers session refresher */
-	unsigned char quit_flag:1;         /*!< Stop trying to lock; just quit */
 };
 
 
diff --git a/configs/basic-pbx/modules.conf b/configs/basic-pbx/modules.conf
index db727b7..3561537 100644
--- a/configs/basic-pbx/modules.conf
+++ b/configs/basic-pbx/modules.conf
@@ -66,6 +66,7 @@ load = pbx_functions.so
 
 load = res_hep_pjsip.so
 load = res_musiconhold.so
+load = res_pjproject.so
 load = res_pjsip_acl.so
 load = res_pjsip_authenticator_digest.so
 load = res_pjsip_caller_id.so
diff --git a/configs/samples/amd.conf.sample b/configs/samples/amd.conf.sample
index d7323ec..84b391c 100644
--- a/configs/samples/amd.conf.sample
+++ b/configs/samples/amd.conf.sample
@@ -3,17 +3,29 @@
 ;
 
 [general]
+total_analysis_time = 5000	; Maximum time allowed for the algorithm to decide
+				; on whether the audio represents a HUMAN, or a MACHINE
+silence_threshold = 256		; If the average level of noise in a sample does not reach
+				; this value, from a scale of 0 to 32767, then we will consider
+				; it to be silence.
+
+; Greeting ;
 initial_silence = 2500		; Maximum silence duration before the greeting.
-				; If exceeded then MACHINE.
-greeting = 1500			; Maximum length of a greeting. If exceeded then MACHINE.
+				; If exceeded, then the result is detection as a MACHINE.
 after_greeting_silence = 800	; Silence after detecting a greeting.
-				; If exceeded then HUMAN
-total_analysis_time = 5000	; Maximum time allowed for the algorithm to decide
-				; on a HUMAN or MACHINE
+				; If exceeded, then the result is detection as a HUMAN
+greeting = 1500			; Maximum length of a greeting. If exceeded, then the
+				; result is detection as a MACHINE.
+
+; Word detection ;
 min_word_length = 100		; Minimum duration of Voice to considered as a word
+maximum_word_length = 5000  	; Maximum duration of a single Voice utterance allowed.
 between_words_silence = 50	; Minimum duration of silence after a word to consider
 				; the audio what follows as a new word
-maximum_number_of_words = 3	; Maximum number of words in the greeting.
-				; If exceeded then MACHINE
-maximum_word_length = 5000      ; Maximum duration of a single Voice utterance allowed.
-silence_threshold = 256
+
+maximum_number_of_words = 3	; Maximum number of words in the greeting
+				; If REACHED, then the result is detection as a MACHINE
+				; WARNING: Releases prior to January 1 2016 documented
+				; maximum_number_of_words as 'if exceeded, then MACHINE',
+				; which did not reflect the true functionality.  In Asterisk 14,
+				; this functionality will change to reflect the variables' name.
diff --git a/configs/samples/confbridge.conf.sample b/configs/samples/confbridge.conf.sample
index 0419001..d0bdd6f 100644
--- a/configs/samples/confbridge.conf.sample
+++ b/configs/samples/confbridge.conf.sample
@@ -334,10 +334,12 @@ type=bridge
                               ; upon release of the video src.
 
 ; admin_toggle_mute_participants ; This action allows an administrator to toggle the mute
-                                 ; state for all non-admins within a conference.  All
-                                 ; admin users are unaffected by this option.  Note that all
-                                 ; users, regardless of their admin status, are notified
-                                 ; that the conference is muted.
+                                 ; state for all non-admins within a conference.
+                                 ; Subsequent non-admins joining a muted conference will
+                                 ; start muted.  All admin users are unaffected by this
+                                 ; option.  Note that all users, regardless of their admin
+                                 ; status, are notified that the conference is muted when
+                                 ; the state is toggled.
 
 ; participant_count        ; This action plays back the number of participants currently
                            ; in a conference
diff --git a/configs/samples/features.conf.sample b/configs/samples/features.conf.sample
index aeb25c0..223d693 100644
--- a/configs/samples/features.conf.sample
+++ b/configs/samples/features.conf.sample
@@ -2,7 +2,7 @@
 ; Sample Call Features (transfer, monitor/mixmonitor, etc) configuration
 ;
 
-; Asterisk 12 Note - All parking lot configuration is now done in res_parking.conf
+; Note: From Asterisk 12 - All parking lot configuration is now done in res_parking.conf
 
 [general]
 ;transferdigittimeout => 3      ; Number of seconds to wait between digits when transferring a call
diff --git a/configs/samples/http.conf.sample b/configs/samples/http.conf.sample
index 1d23a67..342dff4 100644
--- a/configs/samples/http.conf.sample
+++ b/configs/samples/http.conf.sample
@@ -90,6 +90,26 @@ bindaddr=127.0.0.1
 ; private in same .pem file.
 ; openssl req -new -x509 -days 365 -nodes -out /tmp/foo.pem -keyout /tmp/foo.pem
 ;
+; tlscipher=                             ; The list of allowed ciphers
+;                                        ; if none are specified the following cipher
+;                                        ; list will be used instead:
+; ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:
+; ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:
+; kEDH+AESGCM:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:
+; ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:
+; ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:
+; DHE-DSS-AES128-SHA256:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA:DHE-RSA-AES256-SHA:
+; AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:
+; AES:CAMELLIA:DES-CBC3-SHA:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!MD5:!PSK:!aECDH:
+; !EDH-DSS-DES-CBC3-SHA:!EDH-RSA-DES-CBC3-SHA:!KRB5-DES-CBC3-SHA
+;
+; tlsdisablev1=yes                ; Disable TLSv1 support - if not set this defaults to "yes"
+; tlsdisablev11=yes               ; Disable TLSv1.1 support - if not set this defaults to "no"
+; tlsdisablev12=yes               ; Disable TLSv1.2 support - if not set this defaults to "no"
+;
+; tlsservercipherorder=yes        ; Use the server preference order instead of the client order
+;                                 ; Defaults to "yes"
+;
 ; The post_mappings section maps URLs to real paths on the filesystem.  If a
 ; POST is done from within an authenticated manager session to one of the
 ; configured POST mappings, then any files in the POST will be placed in the
diff --git a/configs/samples/pjproject.conf.sample b/configs/samples/pjproject.conf.sample
new file mode 100644
index 0000000..97af734
--- /dev/null
+++ b/configs/samples/pjproject.conf.sample
@@ -0,0 +1,28 @@
+; Common pjproject options
+;
+
+;========================LOG_MAPPINGS SECTION OPTIONS===============================
+;[log_mappings]
+;  SYNOPSIS: Provides pjproject to Asterisk log level mappings.
+;  NOTES: The name of this section in the pjproject.conf configuration file must
+;         remain log_mappings or the configuration will not be applied.
+;         The defaults mentioned below only apply if this file or the 'log_mappings'
+;         object can'tbe found.  If the object is found, there are no defaults. If
+;         you don't specify an entry, nothing will be logged for that level.
+;
+;asterisk_error =    ; A comma separated list of pjproject log levels to map to
+                     ; Asterisk errors.
+                     ; (default: "0,1")
+;asterisk_warning =  ; A comma separated list of pjproject log levels to map to
+                     ; Asterisk warnings.
+                     ; (default: "2")
+;asterisk_notice =   ; A comma separated list of pjproject log levels to map to
+                     ; Asterisk notices.
+                     ; (default: "")
+;asterisk_verbose =  ; A comma separated list of pjproject log levels to map to
+                     ; Asterisk verbose.
+                     ; (default: "")
+;asterisk_debug =    ; A comma separated list of pjproject log levels to map to
+                     ; Asterisk debug
+                     ; (default: "3,4,5")
+;type=               ; Must be of type log_mappings (default: "")
diff --git a/configs/samples/pjsip.conf.sample b/configs/samples/pjsip.conf.sample
index 859635c..ebbd199 100644
--- a/configs/samples/pjsip.conf.sample
+++ b/configs/samples/pjsip.conf.sample
@@ -615,6 +615,9 @@
 ;disallow=      ; Media Codec s to disallow (default: "")
 ;dtmf_mode=rfc4733      ; DTMF mode (default: "rfc4733")
 ;media_address=         ; IP address used in SDP for media handling (default: "")
+;bind_rtp_to_media_address=     ; Bind the RTP session to the media_address.
+                                ; This causes all RTP packets to be sent from
+                                ; the specified address. (default: "no")
 ;force_rport=yes        ; Force use of return port (default: "yes")
 ;ice_support=no ; Enable the ICE mechanism to help traverse NAT (default: "no")
 ;identify_by=username   ; Way s for Endpoint to be identified (default:
@@ -807,6 +810,12 @@
                                 ; clients are slow to process the received
                                 ; information. Value is in milliseconds; default
                                 ; is 100 ms.
+;allow_reload=no    ; Although transports can now be reloaded, that may not be
+                    ; desirable because of the slight possibility of dropped
+                    ; calls. To make sure there are no unintentional drops, if
+                    ; this option is set to 'no' (the default) changes to the
+                    ; particular transport will be ignored. If set to 'yes',
+                    ; changes (if any) will be applied.
 
 ;==========================AOR SECTION OPTIONS=========================
 ;[aor]
@@ -885,6 +894,10 @@
                             ; startup that qualifies should be attempted on all
                             ; contacts.  If greater than the qualify_frequency
                             ; for an aor, qualify_frequency will be used instead.
+; If regcontext is specified, Asterisk will dynamically create and destroy a
+; NoOp priority 1 extension for a given endpoint who registers or unregisters
+; with us. The extension added is the name of the endpoint.
+;regcontext=sipregistrations
 
 ; MODULE PROVIDING BELOW SECTION(S): res_pjsip_acl
 ;==========================ACL SECTION OPTIONS=========================
@@ -933,6 +946,15 @@
                         ; 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: "")
+;line=          ; When enabled this option will cause a 'line' parameter to be
+                ; added to the Contact header placed into the outgoing
+                ; registration request. If the remote server sends a call
+                ; this line parameter will be used to establish a relationship
+                ; to the outbound registration, ultimately causing the
+                ; configured endpoint to be used (default: "no")
+;endpoint=      ; When line support is enabled this configured endpoint name
+                ; is used for incoming calls that are related to the outbound
+                ; registration (default: "")
 ;type=  ; Must be of type registration (default: "")
 
 
diff --git a/configs/samples/rtp.conf.sample b/configs/samples/rtp.conf.sample
index c22acaa..2ef5dd2 100644
--- a/configs/samples/rtp.conf.sample
+++ b/configs/samples/rtp.conf.sample
@@ -58,3 +58,30 @@ rtpend=20000
 ;
 ; Password used to authenticate with TURN relay server.
 ; turnpassword=
+;
+[ice_host_candidates]
+;
+; When Asterisk is behind a static one-to-one NAT and ICE is in use, ICE will
+; expose the server's internal IP address as one of the host candidates.
+; Although using STUN (see the 'stunaddr' configuration option) will provide a
+; publicly accessible IP, the internal IP will still be sent to the remote
+; peer. To help hide the topology of your internal network, you can override
+; the host candidates that Asterisk will send to the remote peer.
+;
+; IMPORTANT: Only use this functionality when your Asterisk server is behind a
+; one-to-one NAT and you know what you're doing. If you do define anything
+; here, you almost certainly will NOT want to specify 'stunaddr' or 'turnaddr'
+; above.
+;
+; The format for these overrides is:
+;
+;    <local address> => <advertised address>
+;
+; The following will replace 192.168.1.10 with 1.2.3.4 during ICE
+; negotiation:
+;
+;192.168.1.10 => 1.2.3.4
+;
+; You can define an override for more than 1 interface if you have a multihomed
+; server. Any local interface that is not matched will be passed through
+; unaltered. Both IPv4 and IPv6 addresses are supported.
diff --git a/configure b/configure
index a0468b9..f2f0f78 100755
--- a/configure
+++ b/configure
@@ -651,6 +651,8 @@ PBX_MSG_NOSIGNAL
 PBX_IXJUSER
 GMIME_LIBS
 GMIME_CFLAGS
+PBX_SSL_OP_NO_TLSV1_2
+PBX_SSL_OP_NO_TLSV1_1
 OPENH323_BUILD
 OPENH323_SUFFIX
 OPENH323_LIBDIR
@@ -695,6 +697,10 @@ AST_TRAMPOLINES
 AST_DECLARATION_AFTER_STATEMENT
 GC_LDFLAGS
 GC_CFLAGS
+AST_UNDEFINED_SANITIZER
+AST_LEAK_SANITIZER
+AST_THREAD_SANITIZER
+AST_ADDRESS_SANITIZER
 PBX_PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP
 PBX_PTHREAD_RWLOCK_INITIALIZER
 AST_ASTERISKSSL
@@ -901,6 +907,18 @@ PBX_PORTAUDIO
 PORTAUDIO_DIR
 PORTAUDIO_INCLUDE
 PORTAUDIO_LIB
+PBX_POPT
+POPT_DIR
+POPT_INCLUDE
+POPT_LIB
+PBX_PJSIP_TLS_TRANSPORT_PROTO
+PJSIP_TLS_TRANSPORT_PROTO_DIR
+PJSIP_TLS_TRANSPORT_PROTO_INCLUDE
+PJSIP_TLS_TRANSPORT_PROTO_LIB
+PBX_PJSIP_EXTERNAL_RESOLVER
+PJSIP_EXTERNAL_RESOLVER_DIR
+PJSIP_EXTERNAL_RESOLVER_INCLUDE
+PJSIP_EXTERNAL_RESOLVER_LIB
 PBX_PJ_SSL_CERT_LOAD_FROM_FILES2
 PJ_SSL_CERT_LOAD_FROM_FILES2_DIR
 PJ_SSL_CERT_LOAD_FROM_FILES2_INCLUDE
@@ -917,10 +935,11 @@ PBX_PJ_TRANSACTION_GRP_LOCK
 PJ_TRANSACTION_GRP_LOCK_DIR
 PJ_TRANSACTION_GRP_LOCK_INCLUDE
 PJ_TRANSACTION_GRP_LOCK_LIB
-PBX_POPT
-POPT_DIR
-POPT_INCLUDE
-POPT_LIB
+PBX_PJSIP_DLG_CREATE_UAS_AND_INC_LOCK
+PJSIP_DLG_CREATE_UAS_AND_INC_LOCK_DIR
+PJSIP_DLG_CREATE_UAS_AND_INC_LOCK_INCLUDE
+PJSIP_DLG_CREATE_UAS_AND_INC_LOCK_LIB
+PJPROJECT_BUNDLED
 PBX_PJPROJECT
 PJPROJECT_DIR
 PJPROJECT_INCLUDE
@@ -1162,6 +1181,7 @@ SHA1SUM
 LDCONFIG
 DOWNLOAD
 FETCH
+ALEMBIC
 GIT
 XMLSTARLET
 XMLLINT
@@ -1339,6 +1359,7 @@ with_osptk
 with_oss
 with_postgres
 with_pjproject
+with_pjproject_bundled
 with_popt
 with_portaudio
 with_pri
@@ -2077,6 +2098,8 @@ Optional Packages:
   --with-oss=PATH         use Open Sound System files in PATH
   --with-postgres=PATH    use PostgreSQL files in PATH
   --with-pjproject=PATH   use PJPROJECT files in PATH
+  --with-pjproject-bundled
+                          Use bundled pjproject libraries
   --with-popt=PATH        use popt files in PATH
   --with-portaudio=PATH   use PortAudio files in PATH
   --with-pri=PATH         use ISDN PRI files in PATH
@@ -7465,6 +7488,47 @@ $as_echo "no" >&6; }
 fi
 
 
+# Extract the first word of "alembic", so it can be a program name with args.
+set dummy alembic; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_path_ALEMBIC+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  case $ALEMBIC in
+  [\\/]* | ?:[\\/]*)
+  ac_cv_path_ALEMBIC="$ALEMBIC" # Let the user override the test with a path.
+  ;;
+  *)
+  as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+  IFS=$as_save_IFS
+  test -z "$as_dir" && as_dir=.
+    for ac_exec_ext in '' $ac_executable_extensions; do
+  if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+    ac_cv_path_ALEMBIC="$as_dir/$ac_word$ac_exec_ext"
+    $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+    break 2
+  fi
+done
+  done
+IFS=$as_save_IFS
+
+  test -z "$ac_cv_path_ALEMBIC" && ac_cv_path_ALEMBIC=":"
+  ;;
+esac
+fi
+ALEMBIC=$ac_cv_path_ALEMBIC
+if test -n "$ALEMBIC"; then
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ALEMBIC" >&5
+$as_echo "$ALEMBIC" >&6; }
+else
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+
 if test "${WGET}" != ":" ; then
   DOWNLOAD=${WGET}
 else if test "${CURL}" != ":" ; then
@@ -10349,6 +10413,7 @@ fi
 
 
 
+
     PJPROJECT_DESCRIP="PJPROJECT"
     PJPROJECT_OPTION="pjproject"
     PBX_PJPROJECT=0
@@ -10380,33 +10445,36 @@ fi
 
 
 
+PJPROJECT_BUNDLED=no
 
-    POPT_DESCRIP="popt"
-    POPT_OPTION="popt"
-    PBX_POPT=0
 
-# Check whether --with-popt was given.
-if test "${with_popt+set}" = set; then :
-  withval=$with_popt;
-	case ${withval} in
-	n|no)
-	USE_POPT=no
-	# -1 is a magic value used by menuselect to know that the package
-	# was disabled, other than 'not found'
-	PBX_POPT=-1
-	;;
-	y|ye|yes)
-	ac_mandatory_list="${ac_mandatory_list} POPT"
-	;;
-	*)
-	POPT_DIR="${withval}"
-	ac_mandatory_list="${ac_mandatory_list} POPT"
-	;;
+
+# Check whether --with-pjproject-bundled was given.
+if test "${with_pjproject_bundled+set}" = set; then :
+  withval=$with_pjproject_bundled; case "${enableval}" in
+	      n|no) PJPROJECT_BUNDLED=no ;;
+	      *) PJPROJECT_BUNDLED=yes ;;
 	esac
+fi
+
+
+
+if test "$PJPROJECT_BUNDLED" = "yes" -a "${ac_mandatory_list#*PJPROJECT*}" != "$ac_mandatory_list" ; then
+   as_fn_error $? "--with-pjproject and --with-pjproject-bundled can't both be specified" "$LINENO" 5
+fi
 
+if test "$PJPROJECT_BUNDLED" = "yes" ; then
+   ac_mandatory_list="$ac_mandatory_list PJPROJECT"
+   PJPROJECT_DIR="${ac_top_build_prefix}third-party/pjproject"
 fi
 
 
+PJSIP_DLG_CREATE_UAS_AND_INC_LOCK_DESCRIP="PJSIP Dialog Create UAS with Incremented Lock"
+PJSIP_DLG_CREATE_UAS_AND_INC_LOCK_OPTION=pjsip
+PJSIP_DLG_CREATE_UAS_AND_INC_LOCK_DIR=${PJPROJECT_DIR}
+
+PBX_PJSIP_DLG_CREATE_UAS_AND_INC_LOCK=0
+
 
 
 
@@ -10461,6 +10529,63 @@ PBX_PJ_SSL_CERT_LOAD_FROM_FILES2=0
 
 
 
+PJSIP_EXTERNAL_RESOLVER_DESCRIP="PJSIP External Resolver Support"
+PJSIP_EXTERNAL_RESOLVER_OPTION=pjsip
+PJSIP_EXTERNAL_RESOLVER_DIR=${PJPROJECT_DIR}
+
+PBX_PJSIP_EXTERNAL_RESOLVER=0
+
+
+
+
+
+
+
+PJSIP_TLS_TRANSPORT_PROTO_DESCRIP="PJSIP TLS Transport proto field support"
+PJSIP_TLS_TRANSPORT_PROTO_OPTION=pjsip
+PJSIP_TLS_TRANSPORT_PROTO_DIR=${PJPROJECT_DIR}
+
+PBX_PJSIP_TLS_TRANSPORT_PROTO=0
+
+
+
+
+
+
+
+
+    POPT_DESCRIP="popt"
+    POPT_OPTION="popt"
+    PBX_POPT=0
+
+# Check whether --with-popt was given.
+if test "${with_popt+set}" = set; then :
+  withval=$with_popt;
+	case ${withval} in
+	n|no)
+	USE_POPT=no
+	# -1 is a magic value used by menuselect to know that the package
+	# was disabled, other than 'not found'
+	PBX_POPT=-1
+	;;
+	y|ye|yes)
+	ac_mandatory_list="${ac_mandatory_list} POPT"
+	;;
+	*)
+	POPT_DIR="${withval}"
+	ac_mandatory_list="${ac_mandatory_list} POPT"
+	;;
+	esac
+
+fi
+
+
+
+
+
+
+
+
     PORTAUDIO_DESCRIP="PortAudio"
     PORTAUDIO_OPTION="portaudio"
     PBX_PORTAUDIO=0
@@ -17393,6 +17518,134 @@ CFLAGS="$saved_CFLAGS"
 
 
 
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for -fsanitize=address support" >&5
+$as_echo_n "checking for -fsanitize=address support... " >&6; }
+saved_sanitize_CFLAGS="${CFLAGS}"
+saved_sanitize_LDFLAGS="${LDFLAGS}"
+CFLAGS="-fsanitize=address -fno-omit-frame-pointer"
+LDFLAGS="-fsanitize=address"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+
+int
+main ()
+{
+int x = 1;
+  ;
+  return 0;
+}
+_ACEOF
+if ac_fn_c_try_compile "$LINENO"; then :
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
+$as_echo "yes" >&6; }
+	AST_ADDRESS_SANITIZER=1
+else
+  AST_ADDRESS_SANITIZER=
+	{ $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+CFLAGS="${saved_sanitize_CFLAGS}"
+LDFLAGS="${saved_sanitize_LDFLAGS}"
+
+
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for -fsanitize=thread support" >&5
+$as_echo_n "checking for -fsanitize=thread support... " >&6; }
+saved_sanitize_CFLAGS="${CFLAGS}"
+saved_sanitize_LDFLAGS="${LDFLAGS}"
+CFLAGS="-fno-omit-frame-pointer -pie -fPIE -fsanitize=thread"
+LDFLAGS="-fsanitize=thread -pie -fPIE"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+
+int
+main ()
+{
+int x = 1;
+  ;
+  return 0;
+}
+_ACEOF
+if ac_fn_c_try_compile "$LINENO"; then :
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
+$as_echo "yes" >&6; }
+	AST_THREAD_SANITIZER=1
+else
+  AST_THREAD_SANITIZER=
+	{ $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+CFLAGS="${saved_sanitize_CFLAGS}"
+LDFLAGS="${saved_sanitize_LDFLAGS}"
+
+
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for -fsanitize=leak support" >&5
+$as_echo_n "checking for -fsanitize=leak support... " >&6; }
+saved_sanitize_CFLAGS="${CFLAGS}"
+saved_sanitize_LDFLAGS="${LDFLAGS}"
+CFLAGS="-fno-omit-frame-pointer -fsanitize=leak"
+LDFLAGS="-fsanitize=leak"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+
+int
+main ()
+{
+int x = 1;
+  ;
+  return 0;
+}
+_ACEOF
+if ac_fn_c_try_compile "$LINENO"; then :
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
+$as_echo "yes" >&6; }
+	AST_LEAK_SANITIZER=1
+else
+  AST_LEAK_SANITIZER=
+	{ $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+CFLAGS="${saved_sanitize_CFLAGS}"
+LDFLAGS="${saved_sanitize_LDFLAGS}"
+
+
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for -fsanitize=undefined support" >&5
+$as_echo_n "checking for -fsanitize=undefined support... " >&6; }
+saved_sanitize_CFLAGS="${CFLAGS}"
+saved_sanitize_LDFLAGS="${LDFLAGS}"
+CFLAGS="-fno-omit-frame-pointer -fsanitize=undefined"
+LDFLAGS="-fsanitize=undefined"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+
+int
+main ()
+{
+int x = 1;
+  ;
+  return 0;
+}
+_ACEOF
+if ac_fn_c_try_compile "$LINENO"; then :
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
+$as_echo "yes" >&6; }
+	AST_UNDEFINED_SANITIZER=1
+else
+  AST_UNDEFINED_SANITIZER=
+	{ $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+CFLAGS="${saved_sanitize_CFLAGS}"
+LDFLAGS="${saved_sanitize_LDFLAGS}"
+
+
 { $as_echo "$as_me:${as_lineno-$LINENO}: checking for -ffunction-sections support" >&5
 $as_echo_n "checking for -ffunction-sections support... " >&6; }
 saved_CFLAGS="${CFLAGS}"
@@ -23961,63 +24214,304 @@ $as_echo "$as_me: *** including --without-postgres" >&6;}
    fi
 fi
 
+if test "$USE_PJPROJECT" != "no" ; then
+   if test "$PJPROJECT_BUNDLED" = "yes" ; then
 
-   if test "x${PBX_PJPROJECT}" != "x1" -a "${USE_PJPROJECT}" != "no"; then
 
-pkg_failed=no
-{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for PJPROJECT" >&5
-$as_echo_n "checking for PJPROJECT... " >&6; }
+	{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for embedded pjproject (may have to download)" >&5
+$as_echo_n "checking for embedded pjproject (may have to download)... " >&6; }
+	{ $as_echo "$as_me:${as_lineno-$LINENO}: result: configuring" >&5
+$as_echo "configuring" >&6; }
+	make --quiet --no-print-directory -C $PJPROJECT_DIR configure
+	if test $? -ne 0 ; then
+		{ $as_echo "$as_me:${as_lineno-$LINENO}: result: failed" >&5
+$as_echo "failed" >&6; }
+		{ $as_echo "$as_me:${as_lineno-$LINENO}: Unable to configure $PJPROJECT_DIR" >&5
+$as_echo "$as_me: Unable to configure $PJPROJECT_DIR" >&6;}
+		as_fn_error $? "Run \"make -C $PJPROJECT_DIR NOISY_BUILD=yes configure\" to see error details." "$LINENO" 5
+	fi
 
-if test -n "$PJPROJECT_CFLAGS"; then
-    pkg_cv_PJPROJECT_CFLAGS="$PJPROJECT_CFLAGS"
- elif test -n "$PKG_CONFIG"; then
-    if test -n "$PKG_CONFIG" && \
-    { { $as_echo "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"libpjproject\""; } >&5
-  ($PKG_CONFIG --exists --print-errors "libpjproject") 2>&5
-  ac_status=$?
-  $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
-  test $ac_status = 0; }; then
-  pkg_cv_PJPROJECT_CFLAGS=`$PKG_CONFIG --cflags "libpjproject" 2>/dev/null`
-		      test "x$?" != "x0" && pkg_failed=yes
-else
-  pkg_failed=yes
-fi
- else
-    pkg_failed=untried
-fi
-if test -n "$PJPROJECT_LIBS"; then
-    pkg_cv_PJPROJECT_LIBS="$PJPROJECT_LIBS"
- elif test -n "$PKG_CONFIG"; then
-    if test -n "$PKG_CONFIG" && \
-    { { $as_echo "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"libpjproject\""; } >&5
-  ($PKG_CONFIG --exists --print-errors "libpjproject") 2>&5
-  ac_status=$?
-  $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
-  test $ac_status = 0; }; then
-  pkg_cv_PJPROJECT_LIBS=`$PKG_CONFIG --libs "libpjproject" 2>/dev/null`
-		      test "x$?" != "x0" && pkg_failed=yes
-else
-  pkg_failed=yes
-fi
- else
-    pkg_failed=untried
-fi
+	PJPROJECT_INCLUDE=$(make --quiet --no-print-directory -C $PJPROJECT_DIR echo_cflags)
+	PJPROJECT_CFLAGS="$PJPROJECT_INCLUDE"
+	PBX_PJPROJECT=1
+	PJPROJECT_BUNDLED=yes
 
+$as_echo "#define HAVE_PJPROJECT 1" >>confdefs.h
 
 
-if test $pkg_failed = yes; then
-   	{ $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
-$as_echo "no" >&6; }
+$as_echo "#define HAVE_PJPROJECT_BUNDLED 1" >>confdefs.h
 
-if $PKG_CONFIG --atleast-pkgconfig-version 0.20; then
-        _pkg_short_errors_supported=yes
-else
-        _pkg_short_errors_supported=no
-fi
-        if test $_pkg_short_errors_supported = yes; then
-	        PJPROJECT_PKG_ERRORS=`$PKG_CONFIG --short-errors --print-errors --cflags --libs "libpjproject" 2>&1`
-        else
-	        PJPROJECT_PKG_ERRORS=`$PKG_CONFIG --print-errors --cflags --libs "libpjproject" 2>&1`
+	{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for embedded pjproject" >&5
+$as_echo_n "checking for embedded pjproject... " >&6; }
+	{ $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
+$as_echo "yes" >&6; }
+
+
+	PJSIP_DLG_CREATE_UAS_AND_INC_LOCK_INCLUDE="$PJPROJECT_INCLUDE"
+    { $as_echo "$as_me:${as_lineno-$LINENO}: checking for pjsip_dlg_create_uas_and_inc_lock declared in pjsip.h" >&5
+$as_echo_n "checking for pjsip_dlg_create_uas_and_inc_lock declared in pjsip.h... " >&6; }
+
+	saved_cpp="$CPPFLAGS"
+	CPPFLAGS="$PJPROJECT_INCLUDE"
+	cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+#include <pjsip.h>
+
+_ACEOF
+if (eval "$ac_cpp conftest.$ac_ext") 2>&5 |
+  $EGREP "pjsip_dlg_create_uas_and_inc_lock" >/dev/null 2>&1; then :
+
+		{ $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
+$as_echo "yes" >&6; }
+		PBX_PJSIP_DLG_CREATE_UAS_AND_INC_LOCK=1
+
+$as_echo "#define HAVE_PJSIP_DLG_CREATE_UAS_AND_INC_LOCK 1" >>confdefs.h
+
+
+else
+
+		{ $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+
+fi
+rm -f conftest*
+
+
+	CPPGLAGS="$saved_cpp"
+	PJSIP_DLG_CREATE_UAS_AND_INC_LOCK_INCLUDE="$PJPROJECT_INCLUDE"
+
+
+	PJ_TRANSACTION_GRP_LOCK_INCLUDE="$PJPROJECT_INCLUDE"
+    { $as_echo "$as_me:${as_lineno-$LINENO}: checking for pjsip_tsx_create_uac2 declared in pjsip.h" >&5
+$as_echo_n "checking for pjsip_tsx_create_uac2 declared in pjsip.h... " >&6; }
+
+	saved_cpp="$CPPFLAGS"
+	CPPFLAGS="$PJPROJECT_INCLUDE"
+	cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+#include <pjsip.h>
+
+_ACEOF
+if (eval "$ac_cpp conftest.$ac_ext") 2>&5 |
+  $EGREP "pjsip_tsx_create_uac2" >/dev/null 2>&1; then :
+
+		{ $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
+$as_echo "yes" >&6; }
+		PBX_PJ_TRANSACTION_GRP_LOCK=1
+
+$as_echo "#define HAVE_PJ_TRANSACTION_GRP_LOCK 1" >>confdefs.h
+
+
+else
+
+		{ $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+
+fi
+rm -f conftest*
+
+
+	CPPGLAGS="$saved_cpp"
+	PJ_TRANSACTION_GRP_LOCK_INCLUDE="$PJPROJECT_INCLUDE"
+
+
+	PJSIP_REPLACE_MEDIA_STREAM_INCLUDE="$PJPROJECT_INCLUDE"
+    { $as_echo "$as_me:${as_lineno-$LINENO}: checking for PJMEDIA_SDP_NEG_ALLOW_MEDIA_CHANGE declared in pjmedia.h" >&5
+$as_echo_n "checking for PJMEDIA_SDP_NEG_ALLOW_MEDIA_CHANGE declared in pjmedia.h... " >&6; }
+
+	saved_cpp="$CPPFLAGS"
+	CPPFLAGS="$PJPROJECT_INCLUDE"
+	cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+#include <pjmedia.h>
+
+_ACEOF
+if (eval "$ac_cpp conftest.$ac_ext") 2>&5 |
+  $EGREP "PJMEDIA_SDP_NEG_ALLOW_MEDIA_CHANGE" >/dev/null 2>&1; then :
+
+		{ $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
+$as_echo "yes" >&6; }
+		PBX_PJSIP_REPLACE_MEDIA_STREAM=1
+
+$as_echo "#define HAVE_PJSIP_REPLACE_MEDIA_STREAM 1" >>confdefs.h
+
+
+else
+
+		{ $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+
+fi
+rm -f conftest*
+
+
+	CPPGLAGS="$saved_cpp"
+	PJSIP_REPLACE_MEDIA_STREAM_INCLUDE="$PJPROJECT_INCLUDE"
+
+
+	PJSIP_GET_DEST_INFO_INCLUDE="$PJPROJECT_INCLUDE"
+    { $as_echo "$as_me:${as_lineno-$LINENO}: checking for pjsip_get_dest_info declared in pjsip.h" >&5
+$as_echo_n "checking for pjsip_get_dest_info declared in pjsip.h... " >&6; }
+
+	saved_cpp="$CPPFLAGS"
+	CPPFLAGS="$PJPROJECT_INCLUDE"
+	cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+#include <pjsip.h>
+
+_ACEOF
+if (eval "$ac_cpp conftest.$ac_ext") 2>&5 |
+  $EGREP "pjsip_get_dest_info" >/dev/null 2>&1; then :
+
+		{ $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
+$as_echo "yes" >&6; }
+		PBX_PJSIP_GET_DEST_INFO=1
+
+$as_echo "#define HAVE_PJSIP_GET_DEST_INFO 1" >>confdefs.h
+
+
+else
+
+		{ $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+
+fi
+rm -f conftest*
+
+
+	CPPGLAGS="$saved_cpp"
+	PJSIP_GET_DEST_INFO_INCLUDE="$PJPROJECT_INCLUDE"
+
+
+	PJ_SSL_CERT_LOAD_FROM_FILES2_INCLUDE="$PJPROJECT_INCLUDE"
+    { $as_echo "$as_me:${as_lineno-$LINENO}: checking for pj_ssl_cert_load_from_files2 declared in pjlib.h" >&5
+$as_echo_n "checking for pj_ssl_cert_load_from_files2 declared in pjlib.h... " >&6; }
+
+	saved_cpp="$CPPFLAGS"
+	CPPFLAGS="$PJPROJECT_INCLUDE"
+	cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+#include <pjlib.h>
+
+_ACEOF
+if (eval "$ac_cpp conftest.$ac_ext") 2>&5 |
+  $EGREP "pj_ssl_cert_load_from_files2" >/dev/null 2>&1; then :
+
+		{ $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
+$as_echo "yes" >&6; }
+		PBX_PJ_SSL_CERT_LOAD_FROM_FILES2=1
+
+$as_echo "#define HAVE_PJ_SSL_CERT_LOAD_FROM_FILES2 1" >>confdefs.h
+
+
+else
+
+		{ $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+
+fi
+rm -f conftest*
+
+
+	CPPGLAGS="$saved_cpp"
+	PJ_SSL_CERT_LOAD_FROM_FILES2_INCLUDE="$PJPROJECT_INCLUDE"
+
+
+	PJSIP_EXTERNAL_RESOLVER_INCLUDE="$PJPROJECT_INCLUDE"
+    { $as_echo "$as_me:${as_lineno-$LINENO}: checking for pjsip_endpt_set_ext_resolver declared in pjsip.h" >&5
+$as_echo_n "checking for pjsip_endpt_set_ext_resolver declared in pjsip.h... " >&6; }
+
+	saved_cpp="$CPPFLAGS"
+	CPPFLAGS="$PJPROJECT_INCLUDE"
+	cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+#include <pjsip.h>
+
+_ACEOF
+if (eval "$ac_cpp conftest.$ac_ext") 2>&5 |
+  $EGREP "pjsip_endpt_set_ext_resolver" >/dev/null 2>&1; then :
+
+		{ $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
+$as_echo "yes" >&6; }
+		PBX_PJSIP_EXTERNAL_RESOLVER=1
+
+$as_echo "#define HAVE_PJSIP_EXTERNAL_RESOLVER 1" >>confdefs.h
+
+
+else
+
+		{ $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+
+fi
+rm -f conftest*
+
+
+	CPPGLAGS="$saved_cpp"
+	PJSIP_EXTERNAL_RESOLVER_INCLUDE="$PJPROJECT_INCLUDE"
+
+
+$as_echo "#define HAVE_PJSIP_TLS_TRANSPORT_PROTO 1" >>confdefs.h
+
+
+   else
+
+   if test "x${PBX_PJPROJECT}" != "x1" -a "${USE_PJPROJECT}" != "no"; then
+
+pkg_failed=no
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for PJPROJECT" >&5
+$as_echo_n "checking for PJPROJECT... " >&6; }
+
+if test -n "$PJPROJECT_CFLAGS"; then
+    pkg_cv_PJPROJECT_CFLAGS="$PJPROJECT_CFLAGS"
+ elif test -n "$PKG_CONFIG"; then
+    if test -n "$PKG_CONFIG" && \
+    { { $as_echo "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"libpjproject\""; } >&5
+  ($PKG_CONFIG --exists --print-errors "libpjproject") 2>&5
+  ac_status=$?
+  $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
+  test $ac_status = 0; }; then
+  pkg_cv_PJPROJECT_CFLAGS=`$PKG_CONFIG --cflags "libpjproject" 2>/dev/null`
+		      test "x$?" != "x0" && pkg_failed=yes
+else
+  pkg_failed=yes
+fi
+ else
+    pkg_failed=untried
+fi
+if test -n "$PJPROJECT_LIBS"; then
+    pkg_cv_PJPROJECT_LIBS="$PJPROJECT_LIBS"
+ elif test -n "$PKG_CONFIG"; then
+    if test -n "$PKG_CONFIG" && \
+    { { $as_echo "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"libpjproject\""; } >&5
+  ($PKG_CONFIG --exists --print-errors "libpjproject") 2>&5
+  ac_status=$?
+  $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
+  test $ac_status = 0; }; then
+  pkg_cv_PJPROJECT_LIBS=`$PKG_CONFIG --libs "libpjproject" 2>/dev/null`
+		      test "x$?" != "x0" && pkg_failed=yes
+else
+  pkg_failed=yes
+fi
+ else
+    pkg_failed=untried
+fi
+
+
+
+if test $pkg_failed = yes; then
+   	{ $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+
+if $PKG_CONFIG --atleast-pkgconfig-version 0.20; then
+        _pkg_short_errors_supported=yes
+else
+        _pkg_short_errors_supported=no
+fi
+        if test $_pkg_short_errors_supported = yes; then
+	        PJPROJECT_PKG_ERRORS=`$PKG_CONFIG --short-errors --print-errors --cflags --libs "libpjproject" 2>&1`
+        else
+	        PJPROJECT_PKG_ERRORS=`$PKG_CONFIG --print-errors --cflags --libs "libpjproject" 2>&1`
         fi
 	# Put the nasty error message in config.log where it belongs
 	echo "$PJPROJECT_PKG_ERRORS" >&5
@@ -24051,6 +24545,110 @@ fi
 
 
 
+if test "x${PBX_PJSIP_DLG_CREATE_UAS_AND_INC_LOCK}" != "x1" -a "${USE_PJSIP_DLG_CREATE_UAS_AND_INC_LOCK}" != "no"; then
+   pbxlibdir=""
+   # if --with-PJSIP_DLG_CREATE_UAS_AND_INC_LOCK=DIR has been specified, use it.
+   if test "x${PJSIP_DLG_CREATE_UAS_AND_INC_LOCK_DIR}" != "x"; then
+      if test -d ${PJSIP_DLG_CREATE_UAS_AND_INC_LOCK_DIR}/lib; then
+         pbxlibdir="-L${PJSIP_DLG_CREATE_UAS_AND_INC_LOCK_DIR}/lib"
+      else
+         pbxlibdir="-L${PJSIP_DLG_CREATE_UAS_AND_INC_LOCK_DIR}"
+      fi
+   fi
+   pbxfuncname="pjsip_dlg_create_uas_and_inc_lock"
+   if test "x${pbxfuncname}" = "x" ; then   # empty lib, assume only headers
+      AST_PJSIP_DLG_CREATE_UAS_AND_INC_LOCK_FOUND=yes
+   else
+      ast_ext_lib_check_save_CFLAGS="${CFLAGS}"
+      CFLAGS="${CFLAGS} $PJPROJECT_CFLAGS"
+      as_ac_Lib=`$as_echo "ac_cv_lib_pjsip_${pbxfuncname}" | $as_tr_sh`
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for ${pbxfuncname} in -lpjsip" >&5
+$as_echo_n "checking for ${pbxfuncname} in -lpjsip... " >&6; }
+if eval \${$as_ac_Lib+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  ac_check_lib_save_LIBS=$LIBS
+LIBS="-lpjsip ${pbxlibdir} $PJPROJECT_LIBS $LIBS"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+
+/* Override any GCC internal prototype to avoid an error.
+   Use char because int might match the return type of a GCC
+   builtin and then its argument prototype would still apply.  */
+#ifdef __cplusplus
+extern "C"
+#endif
+char ${pbxfuncname} ();
+int
+main ()
+{
+return ${pbxfuncname} ();
+  ;
+  return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+  eval "$as_ac_Lib=yes"
+else
+  eval "$as_ac_Lib=no"
+fi
+rm -f core conftest.err conftest.$ac_objext \
+    conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
+fi
+eval ac_res=\$$as_ac_Lib
+	       { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5
+$as_echo "$ac_res" >&6; }
+if eval test \"x\$"$as_ac_Lib"\" = x"yes"; then :
+  AST_PJSIP_DLG_CREATE_UAS_AND_INC_LOCK_FOUND=yes
+else
+  AST_PJSIP_DLG_CREATE_UAS_AND_INC_LOCK_FOUND=no
+fi
+
+      CFLAGS="${ast_ext_lib_check_save_CFLAGS}"
+   fi
+
+   # now check for the header.
+   if test "${AST_PJSIP_DLG_CREATE_UAS_AND_INC_LOCK_FOUND}" = "yes"; then
+      PJSIP_DLG_CREATE_UAS_AND_INC_LOCK_LIB="${pbxlibdir} -lpjsip $PJPROJECT_LIBS"
+      # if --with-PJSIP_DLG_CREATE_UAS_AND_INC_LOCK=DIR has been specified, use it.
+      if test "x${PJSIP_DLG_CREATE_UAS_AND_INC_LOCK_DIR}" != "x"; then
+         PJSIP_DLG_CREATE_UAS_AND_INC_LOCK_INCLUDE="-I${PJSIP_DLG_CREATE_UAS_AND_INC_LOCK_DIR}/include"
+      fi
+      PJSIP_DLG_CREATE_UAS_AND_INC_LOCK_INCLUDE="${PJSIP_DLG_CREATE_UAS_AND_INC_LOCK_INCLUDE} $PJPROJECT_CFLAGS"
+      if test "xpjsip.h" = "x" ; then	# no header, assume found
+         PJSIP_DLG_CREATE_UAS_AND_INC_LOCK_HEADER_FOUND="1"
+      else				# check for the header
+         ast_ext_lib_check_saved_CPPFLAGS="${CPPFLAGS}"
+         CPPFLAGS="${CPPFLAGS} ${PJSIP_DLG_CREATE_UAS_AND_INC_LOCK_INCLUDE}"
+         ac_fn_c_check_header_mongrel "$LINENO" "pjsip.h" "ac_cv_header_pjsip_h" "$ac_includes_default"
+if test "x$ac_cv_header_pjsip_h" = xyes; then :
+  PJSIP_DLG_CREATE_UAS_AND_INC_LOCK_HEADER_FOUND=1
+else
+  PJSIP_DLG_CREATE_UAS_AND_INC_LOCK_HEADER_FOUND=0
+fi
+
+
+         CPPFLAGS="${ast_ext_lib_check_saved_CPPFLAGS}"
+      fi
+      if test "x${PJSIP_DLG_CREATE_UAS_AND_INC_LOCK_HEADER_FOUND}" = "x0" ; then
+         PJSIP_DLG_CREATE_UAS_AND_INC_LOCK_LIB=""
+         PJSIP_DLG_CREATE_UAS_AND_INC_LOCK_INCLUDE=""
+      else
+         if test "x${pbxfuncname}" = "x" ; then		# only checking headers -> no library
+            PJSIP_DLG_CREATE_UAS_AND_INC_LOCK_LIB=""
+         fi
+         PBX_PJSIP_DLG_CREATE_UAS_AND_INC_LOCK=1
+         cat >>confdefs.h <<_ACEOF
+#define HAVE_PJSIP_DLG_CREATE_UAS_AND_INC_LOCK 1
+_ACEOF
+
+      fi
+   fi
+fi
+
+
+
 if test "x${PBX_PJ_TRANSACTION_GRP_LOCK}" != "x1" -a "${USE_PJ_TRANSACTION_GRP_LOCK}" != "no"; then
    pbxlibdir=""
    # if --with-PJ_TRANSACTION_GRP_LOCK=DIR has been specified, use it.
@@ -24074,7 +24672,7 @@ if eval \${$as_ac_Lib+:} false; then :
   $as_echo_n "(cached) " >&6
 else
   ac_check_lib_save_LIBS=$LIBS
-LIBS="-lpjsip ${pbxlibdir} $PJPROJECT_LIBS $LIBS"
+LIBS="-lpjsip ${pbxlibdir} $PJPROJECT_LIB $LIBS"
 cat confdefs.h - <<_ACEOF >conftest.$ac_ext
 /* end confdefs.h.  */
 
@@ -24116,7 +24714,7 @@ fi
 
    # now check for the header.
    if test "${AST_PJ_TRANSACTION_GRP_LOCK_FOUND}" = "yes"; then
-      PJ_TRANSACTION_GRP_LOCK_LIB="${pbxlibdir} -lpjsip $PJPROJECT_LIBS"
+      PJ_TRANSACTION_GRP_LOCK_LIB="${pbxlibdir} -lpjsip $PJPROJECT_LIB"
       # if --with-PJ_TRANSACTION_GRP_LOCK=DIR has been specified, use it.
       if test "x${PJ_TRANSACTION_GRP_LOCK_DIR}" != "x"; then
          PJ_TRANSACTION_GRP_LOCK_INCLUDE="-I${PJ_TRANSACTION_GRP_LOCK_DIR}/include"
@@ -24155,10 +24753,10 @@ fi
 
 
 
-saved_cppflags="${CPPFLAGS}"
-saved_libs="${LIBS}"
-CPPFLAGS="${CPPFLAGS} ${PJPROJECT_CFLAGS}"
-LIBS="${LIBS} ${PJPROJECT_LIBS}"
+      saved_cppflags="${CPPFLAGS}"
+      saved_libs="${LIBS}"
+      CPPFLAGS="${CPPFLAGS} ${PJPROJECT_CFLAGS}"
+      LIBS="${LIBS} ${PJPROJECT_LIB}"
 
     if test "x${PBX_PJSIP_REPLACE_MEDIA_STREAM}" != "x1" -a "${USE_PJSIP_REPLACE_MEDIA_STREAM}" != "no"; then
         if test "x" != "x"; then
@@ -24204,8 +24802,8 @@ rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
 	CPPFLAGS="${saved_cppflags}"
     fi
 
-LIBS="${saved_libs}"
-CPPFLAGS="${saved_cppflags}"
+      LIBS="${saved_libs}"
+      CPPFLAGS="${saved_cppflags}"
 
 
 if test "x${PBX_PJSIP_GET_DEST_INFO}" != "x1" -a "${USE_PJSIP_GET_DEST_INFO}" != "no"; then
@@ -24231,7 +24829,7 @@ if eval \${$as_ac_Lib+:} false; then :
   $as_echo_n "(cached) " >&6
 else
   ac_check_lib_save_LIBS=$LIBS
-LIBS="-lpjsip ${pbxlibdir} $PJPROJECT_LIBS $LIBS"
+LIBS="-lpjsip ${pbxlibdir} $PJPROJECT_LIB $LIBS"
 cat confdefs.h - <<_ACEOF >conftest.$ac_ext
 /* end confdefs.h.  */
 
@@ -24273,7 +24871,7 @@ fi
 
    # now check for the header.
    if test "${AST_PJSIP_GET_DEST_INFO_FOUND}" = "yes"; then
-      PJSIP_GET_DEST_INFO_LIB="${pbxlibdir} -lpjsip $PJPROJECT_LIBS"
+      PJSIP_GET_DEST_INFO_LIB="${pbxlibdir} -lpjsip $PJPROJECT_LIB"
       # if --with-PJSIP_GET_DEST_INFO=DIR has been specified, use it.
       if test "x${PJSIP_GET_DEST_INFO_DIR}" != "x"; then
          PJSIP_GET_DEST_INFO_INCLUDE="-I${PJSIP_GET_DEST_INFO_DIR}/include"
@@ -24322,7 +24920,7 @@ if test "x${PBX_PJ_SSL_CERT_LOAD_FROM_FILES2}" != "x1" -a "${USE_PJ_SSL_CERT_LOA
          pbxlibdir="-L${PJ_SSL_CERT_LOAD_FROM_FILES2_DIR}"
       fi
    fi
-   pbxfuncname="pj_ssl_cert_load_from_files2"
+   pbxfuncname="pjsip/include/pjsip/sip_util.h"
    if test "x${pbxfuncname}" = "x" ; then   # empty lib, assume only headers
       AST_PJ_SSL_CERT_LOAD_FROM_FILES2_FOUND=yes
    else
@@ -24335,7 +24933,7 @@ if eval \${$as_ac_Lib+:} false; then :
   $as_echo_n "(cached) " >&6
 else
   ac_check_lib_save_LIBS=$LIBS
-LIBS="-lpj ${pbxlibdir} $PJPROJECT_LIBS $LIBS"
+LIBS="-lpj ${pbxlibdir} $PJPROJECT_LIB $LIBS"
 cat confdefs.h - <<_ACEOF >conftest.$ac_ext
 /* end confdefs.h.  */
 
@@ -24377,7 +24975,7 @@ fi
 
    # now check for the header.
    if test "${AST_PJ_SSL_CERT_LOAD_FROM_FILES2_FOUND}" = "yes"; then
-      PJ_SSL_CERT_LOAD_FROM_FILES2_LIB="${pbxlibdir} -lpj $PJPROJECT_LIBS"
+      PJ_SSL_CERT_LOAD_FROM_FILES2_LIB="${pbxlibdir} -lpj $PJPROJECT_LIB"
       # if --with-PJ_SSL_CERT_LOAD_FROM_FILES2=DIR has been specified, use it.
       if test "x${PJ_SSL_CERT_LOAD_FROM_FILES2_DIR}" != "x"; then
          PJ_SSL_CERT_LOAD_FROM_FILES2_INCLUDE="-I${PJ_SSL_CERT_LOAD_FROM_FILES2_DIR}/include"
@@ -24416,6 +25014,164 @@ fi
 
 
 
+if test "x${PBX_PJSIP_EXTERNAL_RESOLVER}" != "x1" -a "${USE_PJSIP_EXTERNAL_RESOLVER}" != "no"; then
+   pbxlibdir=""
+   # if --with-PJSIP_EXTERNAL_RESOLVER=DIR has been specified, use it.
+   if test "x${PJSIP_EXTERNAL_RESOLVER_DIR}" != "x"; then
+      if test -d ${PJSIP_EXTERNAL_RESOLVER_DIR}/lib; then
+         pbxlibdir="-L${PJSIP_EXTERNAL_RESOLVER_DIR}/lib"
+      else
+         pbxlibdir="-L${PJSIP_EXTERNAL_RESOLVER_DIR}"
+      fi
+   fi
+   pbxfuncname="pjsip_endpt_set_ext_resolver"
+   if test "x${pbxfuncname}" = "x" ; then   # empty lib, assume only headers
+      AST_PJSIP_EXTERNAL_RESOLVER_FOUND=yes
+   else
+      ast_ext_lib_check_save_CFLAGS="${CFLAGS}"
+      CFLAGS="${CFLAGS} $PJPROJECT_CFLAGS"
+      as_ac_Lib=`$as_echo "ac_cv_lib_pjsip_${pbxfuncname}" | $as_tr_sh`
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for ${pbxfuncname} in -lpjsip" >&5
+$as_echo_n "checking for ${pbxfuncname} in -lpjsip... " >&6; }
+if eval \${$as_ac_Lib+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  ac_check_lib_save_LIBS=$LIBS
+LIBS="-lpjsip ${pbxlibdir} $PJPROJECT_LIBS $LIBS"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+
+/* Override any GCC internal prototype to avoid an error.
+   Use char because int might match the return type of a GCC
+   builtin and then its argument prototype would still apply.  */
+#ifdef __cplusplus
+extern "C"
+#endif
+char ${pbxfuncname} ();
+int
+main ()
+{
+return ${pbxfuncname} ();
+  ;
+  return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+  eval "$as_ac_Lib=yes"
+else
+  eval "$as_ac_Lib=no"
+fi
+rm -f core conftest.err conftest.$ac_objext \
+    conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
+fi
+eval ac_res=\$$as_ac_Lib
+	       { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5
+$as_echo "$ac_res" >&6; }
+if eval test \"x\$"$as_ac_Lib"\" = x"yes"; then :
+  AST_PJSIP_EXTERNAL_RESOLVER_FOUND=yes
+else
+  AST_PJSIP_EXTERNAL_RESOLVER_FOUND=no
+fi
+
+      CFLAGS="${ast_ext_lib_check_save_CFLAGS}"
+   fi
+
+   # now check for the header.
+   if test "${AST_PJSIP_EXTERNAL_RESOLVER_FOUND}" = "yes"; then
+      PJSIP_EXTERNAL_RESOLVER_LIB="${pbxlibdir} -lpjsip $PJPROJECT_LIBS"
+      # if --with-PJSIP_EXTERNAL_RESOLVER=DIR has been specified, use it.
+      if test "x${PJSIP_EXTERNAL_RESOLVER_DIR}" != "x"; then
+         PJSIP_EXTERNAL_RESOLVER_INCLUDE="-I${PJSIP_EXTERNAL_RESOLVER_DIR}/include"
+      fi
+      PJSIP_EXTERNAL_RESOLVER_INCLUDE="${PJSIP_EXTERNAL_RESOLVER_INCLUDE} $PJPROJECT_CFLAGS"
+      if test "xpjsip.h" = "x" ; then	# no header, assume found
+         PJSIP_EXTERNAL_RESOLVER_HEADER_FOUND="1"
+      else				# check for the header
+         ast_ext_lib_check_saved_CPPFLAGS="${CPPFLAGS}"
+         CPPFLAGS="${CPPFLAGS} ${PJSIP_EXTERNAL_RESOLVER_INCLUDE}"
+         ac_fn_c_check_header_mongrel "$LINENO" "pjsip.h" "ac_cv_header_pjsip_h" "$ac_includes_default"
+if test "x$ac_cv_header_pjsip_h" = xyes; then :
+  PJSIP_EXTERNAL_RESOLVER_HEADER_FOUND=1
+else
+  PJSIP_EXTERNAL_RESOLVER_HEADER_FOUND=0
+fi
+
+
+         CPPFLAGS="${ast_ext_lib_check_saved_CPPFLAGS}"
+      fi
+      if test "x${PJSIP_EXTERNAL_RESOLVER_HEADER_FOUND}" = "x0" ; then
+         PJSIP_EXTERNAL_RESOLVER_LIB=""
+         PJSIP_EXTERNAL_RESOLVER_INCLUDE=""
+      else
+         if test "x${pbxfuncname}" = "x" ; then		# only checking headers -> no library
+            PJSIP_EXTERNAL_RESOLVER_LIB=""
+         fi
+         PBX_PJSIP_EXTERNAL_RESOLVER=1
+         cat >>confdefs.h <<_ACEOF
+#define HAVE_PJSIP_EXTERNAL_RESOLVER 1
+_ACEOF
+
+      fi
+   fi
+fi
+
+
+
+      saved_cppflags="${CPPFLAGS}"
+      saved_libs="${LIBS}"
+      CPPFLAGS="${CPPFLAGS} ${PJPROJECT_CFLAGS}"
+      LIBS="${LIBS} ${PJPROJECT_LIB}"
+
+    if test "x${PBX_PJSIP_TLS_TRANSPORT_PROTO}" != "x1" -a "${USE_PJSIP_TLS_TRANSPORT_PROTO}" != "no"; then
+        if test "x" != "x"; then
+            { $as_echo "$as_me:${as_lineno-$LINENO}: checking for " >&5
+$as_echo_n "checking for ... " >&6; }
+	else
+            { $as_echo "$as_me:${as_lineno-$LINENO}: checking if \"struct pjsip_tls_setting setting; int proto; proto = setting.proto;\" compiles using pjsip.h" >&5
+$as_echo_n "checking if \"struct pjsip_tls_setting setting; int proto; proto = setting.proto;\" compiles using pjsip.h... " >&6; }
+	fi
+	saved_cppflags="${CPPFLAGS}"
+	if test "x${PJSIP_TLS_TRANSPORT_PROTO_DIR}" != "x"; then
+	    PJSIP_TLS_TRANSPORT_PROTO_INCLUDE="-I${PJSIP_TLS_TRANSPORT_PROTO_DIR}/include"
+	fi
+	CPPFLAGS="${CPPFLAGS} ${PJSIP_TLS_TRANSPORT_PROTO_INCLUDE}"
+
+	cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+ #include <pjsip.h>
+int
+main ()
+{
+ struct pjsip_tls_setting setting; int proto; proto = setting.proto;;
+
+  ;
+  return 0;
+}
+_ACEOF
+if ac_fn_c_try_compile "$LINENO"; then :
+     { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
+$as_echo "yes" >&6; }
+		PBX_PJSIP_TLS_TRANSPORT_PROTO=1
+
+$as_echo "#define HAVE_PJSIP_TLS_TRANSPORT_PROTO 1" >>confdefs.h
+
+
+
+else
+         { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+	CPPFLAGS="${saved_cppflags}"
+    fi
+
+      LIBS="${saved_libs}"
+      CPPFLAGS="${saved_cppflags}"
+   fi
+fi
+
 
 if test "x${PBX_POPT}" != "x1" -a "${USE_POPT}" != "no"; then
    pbxlibdir=""
@@ -30329,6 +31085,102 @@ rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
 
 fi
 
+if test "$PBX_OPENSSL" = "1";
+then
+
+    if test "x${PBX_SSL_OP_NO_TLSV1_1}" != "x1"; then
+	{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for SSL_OP_NO_TLSv1_1 in openssl/ssl.h" >&5
+$as_echo_n "checking for SSL_OP_NO_TLSv1_1 in openssl/ssl.h... " >&6; }
+	saved_cppflags="${CPPFLAGS}"
+	if test "x${SSL_OP_NO_TLSV1_1_DIR}" != "x"; then
+	    SSL_OP_NO_TLSV1_1_INCLUDE="-I${SSL_OP_NO_TLSV1_1_DIR}/include"
+	fi
+	CPPFLAGS="${CPPFLAGS} ${SSL_OP_NO_TLSV1_1_INCLUDE}"
+
+	cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+ #include <openssl/ssl.h>
+int
+main ()
+{
+#if defined(SSL_OP_NO_TLSv1_1)
+				int foo = 0;
+			        #else
+			        int foo = bar;
+			        #endif
+				0
+
+  ;
+  return 0;
+}
+_ACEOF
+if ac_fn_c_try_compile "$LINENO"; then :
+     { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
+$as_echo "yes" >&6; }
+		PBX_SSL_OP_NO_TLSV1_1=1
+
+$as_echo "#define HAVE_SSL_OP_NO_TLSV1_1 1" >>confdefs.h
+
+
+
+else
+     { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+	CPPFLAGS="${saved_cppflags}"
+    fi
+
+
+
+    if test "x${PBX_SSL_OP_NO_TLSV1_2}" != "x1"; then
+	{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for SSL_OP_NO_TLSv1_2 in openssl/ssl.h" >&5
+$as_echo_n "checking for SSL_OP_NO_TLSv1_2 in openssl/ssl.h... " >&6; }
+	saved_cppflags="${CPPFLAGS}"
+	if test "x${SSL_OP_NO_TLSV1_2_DIR}" != "x"; then
+	    SSL_OP_NO_TLSV1_2_INCLUDE="-I${SSL_OP_NO_TLSV1_2_DIR}/include"
+	fi
+	CPPFLAGS="${CPPFLAGS} ${SSL_OP_NO_TLSV1_2_INCLUDE}"
+
+	cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+ #include <openssl/ssl.h>
+int
+main ()
+{
+#if defined(SSL_OP_NO_TLSv1_2)
+				int foo = 0;
+			        #else
+			        int foo = bar;
+			        #endif
+				0
+
+  ;
+  return 0;
+}
+_ACEOF
+if ac_fn_c_try_compile "$LINENO"; then :
+     { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
+$as_echo "yes" >&6; }
+		PBX_SSL_OP_NO_TLSV1_2=1
+
+$as_echo "#define HAVE_SSL_OP_NO_TLSV1_2 1" >>confdefs.h
+
+
+
+else
+     { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+	CPPFLAGS="${saved_cppflags}"
+    fi
+
+
+fi
+
 
 if test "x${PBX_SRTP}" != "x1" -a "${USE_SRTP}" != "no"; then
    pbxlibdir=""
diff --git a/configure.ac b/configure.ac
index 3292809..d0a1dec 100644
--- a/configure.ac
+++ b/configure.ac
@@ -283,6 +283,7 @@ AC_PATH_PROG([KPATHSEA], [kpsewhich], :)
 AC_PATH_PROG([XMLLINT], [xmllint], :)
 AC_PATH_PROG([XMLSTARLET], [xmlstarlet], :)
 AC_PATH_PROG([GIT], [git], :)
+AC_PATH_PROG([ALEMBIC], [alembic], :)
 if test "${WGET}" != ":" ; then
   DOWNLOAD=${WGET}
 else if test "${CURL}" != ":" ; then
@@ -456,12 +457,38 @@ AST_EXT_LIB_SETUP([OPUS], [Opus], [opus])
 AST_EXT_LIB_SETUP([OSPTK], [OSP Toolkit], [osptk])
 AST_EXT_LIB_SETUP([OSS], [Open Sound System], [oss])
 AST_EXT_LIB_SETUP([PGSQL], [PostgreSQL], [postgres])
+
 AST_EXT_LIB_SETUP([PJPROJECT], [PJPROJECT], [pjproject])
-AST_EXT_LIB_SETUP([POPT], [popt], [popt])
+PJPROJECT_BUNDLED=no
+AH_TEMPLATE(m4_bpatsubst([[HAVE_PJPROJECT_BUNDLED]], [(.*)]), [Define to 1 when using the bundled pjproject.])
+
+AC_ARG_WITH([pjproject-bundled],
+	[AS_HELP_STRING([--with-pjproject-bundled],
+		[Use bundled pjproject libraries])],
+	[case "${enableval}" in
+	      n|no) PJPROJECT_BUNDLED=no ;;
+	      *) PJPROJECT_BUNDLED=yes ;;
+	esac])
+AC_SUBST(PJPROJECT_BUNDLED)
+
+if test "$PJPROJECT_BUNDLED" = "yes" -a "${ac_mandatory_list#*PJPROJECT*}" != "$ac_mandatory_list" ; then
+   AC_MSG_ERROR(--with-pjproject and --with-pjproject-bundled can't both be specified)
+fi
+
+if test "$PJPROJECT_BUNDLED" = "yes" ; then
+   ac_mandatory_list="$ac_mandatory_list PJPROJECT"
+   PJPROJECT_DIR="${ac_top_build_prefix}third-party/pjproject"
+fi
+
+AST_EXT_LIB_SETUP_OPTIONAL([PJSIP_DLG_CREATE_UAS_AND_INC_LOCK], [PJSIP Dialog Create UAS with Incremented Lock], [PJPROJECT], [pjsip])
 AST_EXT_LIB_SETUP_OPTIONAL([PJ_TRANSACTION_GRP_LOCK], [PJSIP Transaction Group Lock Support], [PJPROJECT], [pjsip])
 AST_EXT_LIB_SETUP_OPTIONAL([PJSIP_REPLACE_MEDIA_STREAM], [PJSIP Media Stream Replacement Support], [PJPROJECT], [pjsip])
 AST_EXT_LIB_SETUP_OPTIONAL([PJSIP_GET_DEST_INFO], [pjsip_get_dest_info support], [PJPROJECT], [pjsip])
 AST_EXT_LIB_SETUP_OPTIONAL([PJ_SSL_CERT_LOAD_FROM_FILES2], [pj_ssl_cert_load_from_files2 support], [PJPROJECT], [pjsip])
+AST_EXT_LIB_SETUP_OPTIONAL([PJSIP_EXTERNAL_RESOLVER], [PJSIP External Resolver Support], [PJPROJECT], [pjsip])
+AST_EXT_LIB_SETUP_OPTIONAL([PJSIP_TLS_TRANSPORT_PROTO], [PJSIP TLS Transport proto field support], [PJPROJECT], [pjsip])
+
+AST_EXT_LIB_SETUP([POPT], [popt], [popt])
 AST_EXT_LIB_SETUP([PORTAUDIO], [PortAudio], [portaudio])
 AST_EXT_LIB_SETUP([PRI], [ISDN PRI], [pri])
 AST_EXT_LIB_SETUP_OPTIONAL([PRI_SETUP_ACK_INBAND], [ISDN PRI progress inband ie in SETUP ACK], [PRI], [pri])
@@ -1048,6 +1075,70 @@ AST_GCC_ATTRIBUTE(may_alias)
 AST_GCC_ATTRIBUTE(constructor)
 AST_GCC_ATTRIBUTE(destructor)
 
+AC_MSG_CHECKING(for -fsanitize=address support)
+saved_sanitize_CFLAGS="${CFLAGS}"
+saved_sanitize_LDFLAGS="${LDFLAGS}"
+CFLAGS="-fsanitize=address -fno-omit-frame-pointer"
+LDFLAGS="-fsanitize=address"
+AC_COMPILE_IFELSE(
+	[AC_LANG_PROGRAM([], [int x = 1;])],
+	AC_MSG_RESULT(yes)
+	[AST_ADDRESS_SANITIZER=1],
+	[AST_ADDRESS_SANITIZER=]
+	AC_MSG_RESULT(no)
+)
+CFLAGS="${saved_sanitize_CFLAGS}"
+LDFLAGS="${saved_sanitize_LDFLAGS}"
+AC_SUBST(AST_ADDRESS_SANITIZER)
+
+AC_MSG_CHECKING(for -fsanitize=thread support)
+saved_sanitize_CFLAGS="${CFLAGS}"
+saved_sanitize_LDFLAGS="${LDFLAGS}"
+CFLAGS="-fno-omit-frame-pointer -pie -fPIE -fsanitize=thread"
+LDFLAGS="-fsanitize=thread -pie -fPIE"
+AC_COMPILE_IFELSE(
+	[AC_LANG_PROGRAM([], [int x = 1;])],
+	AC_MSG_RESULT(yes)
+	[AST_THREAD_SANITIZER=1],
+	[AST_THREAD_SANITIZER=]
+	AC_MSG_RESULT(no)
+)
+CFLAGS="${saved_sanitize_CFLAGS}"
+LDFLAGS="${saved_sanitize_LDFLAGS}"
+AC_SUBST(AST_THREAD_SANITIZER)
+
+AC_MSG_CHECKING(for -fsanitize=leak support)
+saved_sanitize_CFLAGS="${CFLAGS}"
+saved_sanitize_LDFLAGS="${LDFLAGS}"
+CFLAGS="-fno-omit-frame-pointer -fsanitize=leak"
+LDFLAGS="-fsanitize=leak"
+AC_COMPILE_IFELSE(
+	[AC_LANG_PROGRAM([], [int x = 1;])],
+	AC_MSG_RESULT(yes)
+	[AST_LEAK_SANITIZER=1],
+	[AST_LEAK_SANITIZER=]
+	AC_MSG_RESULT(no)
+)
+CFLAGS="${saved_sanitize_CFLAGS}"
+LDFLAGS="${saved_sanitize_LDFLAGS}"
+AC_SUBST(AST_LEAK_SANITIZER)
+
+AC_MSG_CHECKING(for -fsanitize=undefined support)
+saved_sanitize_CFLAGS="${CFLAGS}"
+saved_sanitize_LDFLAGS="${LDFLAGS}"
+CFLAGS="-fno-omit-frame-pointer -fsanitize=undefined"
+LDFLAGS="-fsanitize=undefined"
+AC_COMPILE_IFELSE(
+	[AC_LANG_PROGRAM([], [int x = 1;])],
+	AC_MSG_RESULT(yes)
+	[AST_UNDEFINED_SANITIZER=1],
+	[AST_UNDEFINED_SANITIZER=]
+	AC_MSG_RESULT(no)
+)
+CFLAGS="${saved_sanitize_CFLAGS}"
+LDFLAGS="${saved_sanitize_LDFLAGS}"
+AC_SUBST(AST_UNDEFINED_SANITIZER)
+
 AC_MSG_CHECKING(for -ffunction-sections support)
 saved_CFLAGS="${CFLAGS}"
 CFLAGS="${CFLAGS} -ffunction-sections"
@@ -2075,20 +2166,37 @@ if test "${PG_CONFIG}" != No; then
    fi
 fi
 
-AST_PKG_CONFIG_CHECK([PJPROJECT], [libpjproject])
-
-AST_EXT_LIB_CHECK([PJ_TRANSACTION_GRP_LOCK], [pjsip], [pjsip_tsx_create_uac2], [pjsip.h], [$PJPROJECT_LIBS], [$PJPROJECT_CFLAGS])
-
-saved_cppflags="${CPPFLAGS}"
-saved_libs="${LIBS}"
-CPPFLAGS="${CPPFLAGS} ${PJPROJECT_CFLAGS}"
-LIBS="${LIBS} ${PJPROJECT_LIBS}"
-AST_C_COMPILE_CHECK([PJSIP_REPLACE_MEDIA_STREAM], [pjmedia_mod_offer_flag flag = PJMEDIA_SDP_NEG_ALLOW_MEDIA_CHANGE], [pjmedia.h])
-LIBS="${saved_libs}"
-CPPFLAGS="${saved_cppflags}"
-
-AST_EXT_LIB_CHECK([PJSIP_GET_DEST_INFO], [pjsip], [pjsip_get_dest_info], [pjsip.h], [$PJPROJECT_LIBS], [$PJPROJECT_CFLAGS])
-AST_EXT_LIB_CHECK([PJ_SSL_CERT_LOAD_FROM_FILES2], [pj], [pj_ssl_cert_load_from_files2], [pjlib.h], [$PJPROJECT_LIBS], [$PJPROJECT_CFLAGS])
+if test "$USE_PJPROJECT" != "no" ; then
+   if test "$PJPROJECT_BUNDLED" = "yes" ; then
+       AC_CONFIG_MACRO_DIR(third-party/pjproject)
+       PJPROJECT_CONFIGURE([$PJPROJECT_DIR])
+   else
+      AST_PKG_CONFIG_CHECK([PJPROJECT], [libpjproject])
+
+      AST_EXT_LIB_CHECK([PJSIP_DLG_CREATE_UAS_AND_INC_LOCK], [pjsip], [pjsip_dlg_create_uas_and_inc_lock], [pjsip.h], [$PJPROJECT_LIBS], [$PJPROJECT_CFLAGS])
+      AST_EXT_LIB_CHECK([PJ_TRANSACTION_GRP_LOCK], [pjsip], [pjsip_tsx_create_uac2], [pjsip.h], [$PJPROJECT_LIB], [$PJPROJECT_CFLAGS])
+
+      saved_cppflags="${CPPFLAGS}"
+      saved_libs="${LIBS}"
+      CPPFLAGS="${CPPFLAGS} ${PJPROJECT_CFLAGS}"
+      LIBS="${LIBS} ${PJPROJECT_LIB}"
+      AST_C_COMPILE_CHECK([PJSIP_REPLACE_MEDIA_STREAM], [pjmedia_mod_offer_flag flag = PJMEDIA_SDP_NEG_ALLOW_MEDIA_CHANGE], [pjmedia.h])
+      LIBS="${saved_libs}"
+      CPPFLAGS="${saved_cppflags}"
+
+      AST_EXT_LIB_CHECK([PJSIP_GET_DEST_INFO], [pjsip], [pjsip_get_dest_info], [pjsip.h], [$PJPROJECT_LIB], [$PJPROJECT_CFLAGS])
+      AST_EXT_LIB_CHECK([PJ_SSL_CERT_LOAD_FROM_FILES2], [pj], [pjsip/include/pjsip/sip_util.h], [pjlib.h], [$PJPROJECT_LIB], [$PJPROJECT_CFLAGS])
+      AST_EXT_LIB_CHECK([PJSIP_EXTERNAL_RESOLVER], [pjsip], [pjsip_endpt_set_ext_resolver], [pjsip.h], [$PJPROJECT_LIBS], [$PJPROJECT_CFLAGS])
+
+      saved_cppflags="${CPPFLAGS}"
+      saved_libs="${LIBS}"
+      CPPFLAGS="${CPPFLAGS} ${PJPROJECT_CFLAGS}"
+      LIBS="${LIBS} ${PJPROJECT_LIB}"
+      AST_C_COMPILE_CHECK([PJSIP_TLS_TRANSPORT_PROTO], [struct pjsip_tls_setting setting; int proto; proto = setting.proto;], [pjsip.h])
+      LIBS="${saved_libs}"
+      CPPFLAGS="${saved_cppflags}"
+   fi
+fi
 
 AST_EXT_LIB_CHECK([POPT], [popt], [poptStrerror], [popt.h])
 
@@ -2252,11 +2360,11 @@ AC_CHECK_FUNC([crypt], [SYSCRYPT=true], [SYSCRYPT=""])
 if test "x$LIBCRYPT_LIB" != "x" ; then
     CRYPT_LIB="$LIBCRYPT_LIB"
     CRYPT_INCLUDE="$LIBCRYPT_INCLUDE"
-    AC_DEFINE([HAVE_CRYPT], [1], [Define to 1 if you have the `crypt' function.])
+    AC_DEFINE([HAVE_CRYPT], [1], [Define to 1 if you have the 'crypt' function.])
 elif test "x$SYSCRYPT" != "x" ; then
     CRYPT_LIB=""
     CRYPT_INCLUDE=""
-    AC_DEFINE([HAVE_CRYPT], [1], [Define to 1 if you have the `crypt' function.])
+    AC_DEFINE([HAVE_CRYPT], [1], [Define to 1 if you have the 'crypt' function.])
 fi
 
 AC_SUBST(CRYPT_LIB)
@@ -2264,7 +2372,7 @@ AC_SUBST(CRYPT_INCLUDE)
 
 # Find crypt_r support
 AC_CHECK_LIB([crypt], [crypt_r],
-    [AC_DEFINE([HAVE_CRYPT_R], [1], [Define to 1 if you have the `crypt_r' function.])])
+    [AC_DEFINE([HAVE_CRYPT_R], [1], [Define to 1 if you have the 'crypt_r' function.])])
 
 AST_EXT_LIB_CHECK([CRYPTO], [crypto], [AES_encrypt], [openssl/aes.h])
 
@@ -2289,6 +2397,12 @@ then
         AST_C_DECLARE_CHECK([OPENSSL_ECDH_AUTO], [SSL_CTX_set_ecdh_auto], [openssl/ssl.h])
 fi
 
+if test "$PBX_OPENSSL" = "1";
+then
+        AST_C_DEFINE_CHECK([SSL_OP_NO_TLSV1_1], [SSL_OP_NO_TLSv1_1], [openssl/ssl.h])
+        AST_C_DEFINE_CHECK([SSL_OP_NO_TLSV1_2], [SSL_OP_NO_TLSv1_2], [openssl/ssl.h])
+fi
+
 AST_EXT_LIB_CHECK([SRTP], [srtp], [srtp_init], [srtp/srtp.h])
 
 if test "$PBX_SRTP" = "1";
diff --git a/contrib/ast-db-manage/config/env.py b/contrib/ast-db-manage/config/env.py
index 6740d59..4118da0 100755
--- a/contrib/ast-db-manage/config/env.py
+++ b/contrib/ast-db-manage/config/env.py
@@ -58,7 +58,8 @@ def run_migrations_online():
     connection = engine.connect()
     context.configure(
                 connection=connection,
-                target_metadata=target_metadata
+                target_metadata=target_metadata,
+                render_as_batch=True
                 )
 
     try:
diff --git a/contrib/ast-db-manage/config/versions/10aedae86a32_add_outgoing_enum_va.py b/contrib/ast-db-manage/config/versions/10aedae86a32_add_outgoing_enum_va.py
index b4ea71c..cc9f0e0 100755
--- a/contrib/ast-db-manage/config/versions/10aedae86a32_add_outgoing_enum_va.py
+++ b/contrib/ast-db-manage/config/versions/10aedae86a32_add_outgoing_enum_va.py
@@ -45,7 +45,10 @@ def upgrade():
     context = op.get_context()
 
     # Upgrading to this revision WILL clear your directmedia values.
-    if context.bind.dialect.name != 'postgresql':
+    if context.bind.dialect.name == 'sqlite':
+        with op.batch_alter_table('sippeers') as batch_op:
+            batch_op.alter_column('directmedia', type_=new_type)
+    elif context.bind.dialect.name != 'postgresql':
         op.alter_column('sippeers', 'directmedia',
                         type_=new_type,
                         existing_type=old_type)
@@ -66,7 +69,10 @@ def downgrade():
     op.execute(tcr.update().where(tcr.c.directmedia==u'outgoing')
                .values(directmedia=None))
 
-    if context.bind.dialect.name != 'postgresql':
+    if context.bind.dialect.name == 'sqlite':
+        with op.batch_alter_table('sippeers') as batch_op:
+            batch_op.alter_column('directmedia', type_=old_type)
+    elif context.bind.dialect.name != 'postgresql':
         op.alter_column('sippeers', 'directmedia',
                         type_=old_type,
                         existing_type=new_type)
diff --git a/contrib/ast-db-manage/config/versions/136885b81223_add_regcontext_to_pj.py b/contrib/ast-db-manage/config/versions/136885b81223_add_regcontext_to_pj.py
new file mode 100644
index 0000000..22fd6c7
--- /dev/null
+++ b/contrib/ast-db-manage/config/versions/136885b81223_add_regcontext_to_pj.py
@@ -0,0 +1,21 @@
+"""add regcontext to pjsip
+
+Revision ID: 136885b81223
+Revises: 26d7f3bf0fa5
+Create Date: 2016-01-11 22:32:45.470522
+
+"""
+
+# revision identifiers, used by Alembic.
+revision = '136885b81223'
+down_revision = '26d7f3bf0fa5'
+
+from alembic import op
+import sqlalchemy as sa
+
+def upgrade():
+    op.add_column('ps_globals', sa.Column('regcontext', sa.String(80)))
+
+def downgrade():
+    with op.batch_alter_table('ps_globals') as batch_op:
+        batch_op.drop_column('regcontext')
diff --git a/contrib/ast-db-manage/config/versions/154177371065_add_default_from_user.py b/contrib/ast-db-manage/config/versions/154177371065_add_default_from_user.py
index 7e6cf99..6c5f808 100644
--- a/contrib/ast-db-manage/config/versions/154177371065_add_default_from_user.py
+++ b/contrib/ast-db-manage/config/versions/154177371065_add_default_from_user.py
@@ -19,4 +19,5 @@ def upgrade():
 
 
 def downgrade():
-    op.drop_column('ps_globals', 'default_from_user')
+    with op.batch_alter_table('ps_globals') as batch_op:
+        batch_op.drop_column('default_from_user')
diff --git a/contrib/ast-db-manage/config/versions/1758e8bbf6b_increase_useragent_column_size.py b/contrib/ast-db-manage/config/versions/1758e8bbf6b_increase_useragent_column_size.py
index 215726f..c16cff9 100755
--- a/contrib/ast-db-manage/config/versions/1758e8bbf6b_increase_useragent_column_size.py
+++ b/contrib/ast-db-manage/config/versions/1758e8bbf6b_increase_useragent_column_size.py
@@ -33,9 +33,11 @@ import sqlalchemy as sa
 
 
 def upgrade():
-    op.alter_column('sippeers', 'useragent', type_=sa.String(255))
+    with op.batch_alter_table('sippeers') as batch_op:
+        batch_op.alter_column('useragent', type_=sa.String(255))
 
 
 def downgrade():
-    op.alter_column('sippeers', 'useragent', type_=sa.String(20))
+    with op.batch_alter_table('sippeers') as batch_op:
+        batch_op.alter_column('useragent', type_=sa.String(20))
 
diff --git a/contrib/ast-db-manage/config/versions/189a235b3fd7_add_keep_alive_interval.py b/contrib/ast-db-manage/config/versions/189a235b3fd7_add_keep_alive_interval.py
index aa52171..da22f26 100644
--- a/contrib/ast-db-manage/config/versions/189a235b3fd7_add_keep_alive_interval.py
+++ b/contrib/ast-db-manage/config/versions/189a235b3fd7_add_keep_alive_interval.py
@@ -19,4 +19,5 @@ def upgrade():
 
 
 def downgrade():
-    op.drop_column('ps_globals', 'keep_alive_interval')
+    with op.batch_alter_table('ps_globals') as batch_op:
+        batch_op.drop_column('keep_alive_interval')
diff --git a/contrib/ast-db-manage/config/versions/1d50859ed02e_create_accountcode.py b/contrib/ast-db-manage/config/versions/1d50859ed02e_create_accountcode.py
index eb20001..4d520fc 100644
--- a/contrib/ast-db-manage/config/versions/1d50859ed02e_create_accountcode.py
+++ b/contrib/ast-db-manage/config/versions/1d50859ed02e_create_accountcode.py
@@ -17,4 +17,5 @@ def upgrade():
     op.add_column('ps_endpoints', sa.Column('accountcode', sa.String(20)))
 
 def downgrade():
-    op.drop_column('ps_endpoints', 'accountcode')
+    with op.batch_alter_table('ps_endpoints') as batch_op:
+        batch_op.drop_column('accountcode')
diff --git a/contrib/ast-db-manage/config/versions/21e526ad3040_add_pjsip_debug_option.py b/contrib/ast-db-manage/config/versions/21e526ad3040_add_pjsip_debug_option.py
index 2adca62..8b77eb7 100755
--- a/contrib/ast-db-manage/config/versions/21e526ad3040_add_pjsip_debug_option.py
+++ b/contrib/ast-db-manage/config/versions/21e526ad3040_add_pjsip_debug_option.py
@@ -18,4 +18,5 @@ def upgrade():
     op.add_column('ps_globals', sa.Column('debug', sa.String(40)))
 
 def downgrade():
-    op.drop_column('ps_globals', 'debug')
+    with op.batch_alter_table('ps_globals') as batch_op:
+        batch_op.drop_column('debug')
diff --git a/contrib/ast-db-manage/config/versions/23530d604b96_add_rpid_immediate.py b/contrib/ast-db-manage/config/versions/23530d604b96_add_rpid_immediate.py
index dc0c01c..8ca63f1 100755
--- a/contrib/ast-db-manage/config/versions/23530d604b96_add_rpid_immediate.py
+++ b/contrib/ast-db-manage/config/versions/23530d604b96_add_rpid_immediate.py
@@ -45,4 +45,5 @@ def upgrade():
     op.add_column('ps_endpoints', sa.Column('rpid_immediate', yesno_values))
 
 def downgrade():
-    op.drop_column('ps_endpoints', 'rpid_immediate')
+    with op.batch_alter_table('ps_endpoints') as batch_op:
+        batch_op.drop_column('rpid_immediate')
diff --git a/contrib/ast-db-manage/config/versions/eb88a14f2a_add_media_encryption_optimistic_to_pjsip.py b/contrib/ast-db-manage/config/versions/26d7f3bf0fa5_add_bind_rtp_to_media_address_to_pjsip.py
similarity index 57%
copy from contrib/ast-db-manage/config/versions/eb88a14f2a_add_media_encryption_optimistic_to_pjsip.py
copy to contrib/ast-db-manage/config/versions/26d7f3bf0fa5_add_bind_rtp_to_media_address_to_pjsip.py
index 2d96b37..1199d0c 100644
--- a/contrib/ast-db-manage/config/versions/eb88a14f2a_add_media_encryption_optimistic_to_pjsip.py
+++ b/contrib/ast-db-manage/config/versions/26d7f3bf0fa5_add_bind_rtp_to_media_address_to_pjsip.py
@@ -1,14 +1,14 @@
-"""add media encryption optimistic to pjsip
+"""add bind_rtp_to_media_address to pjsip
 
-Revision ID: eb88a14f2a
-Revises: 10aedae86a32
-Create Date: 2014-11-19 07:08:55.423018
+Revision ID: 26d7f3bf0fa5
+Revises: 2d078ec071b7
+Create Date: 2016-01-07 12:23:42.894400
 
 """
 
 # revision identifiers, used by Alembic.
-revision = 'eb88a14f2a'
-down_revision = '10aedae86a32'
+revision = '26d7f3bf0fa5'
+down_revision = '2d078ec071b7'
 
 from alembic import op
 import sqlalchemy as sa
@@ -24,8 +24,9 @@ def upgrade():
     # type to get around "already created" issue - works okay with mysql
     yesno_values = ENUM(*YESNO_VALUES, name=YESNO_NAME, create_type=False)
 
-    op.add_column('ps_endpoints', sa.Column('media_encryption_optimistic', yesno_values))
+    op.add_column('ps_endpoints', sa.Column('bind_rtp_to_media_address', yesno_values))
 
 
 def downgrade():
-    op.drop_column('ps_endpoints', 'media_encryption_optimistic')
+    with op.batch_alter_table('ps_endpoints') as batch_op:
+        batch_op.drop_column('bind_rtp_to_media_address')
diff --git a/contrib/ast-db-manage/config/versions/26f10cadc157_add_pjsip_timeout_options.py b/contrib/ast-db-manage/config/versions/26f10cadc157_add_pjsip_timeout_options.py
index 8972d80..2a792d3 100644
--- a/contrib/ast-db-manage/config/versions/26f10cadc157_add_pjsip_timeout_options.py
+++ b/contrib/ast-db-manage/config/versions/26f10cadc157_add_pjsip_timeout_options.py
@@ -20,5 +20,6 @@ def upgrade():
 
 
 def downgrade():
-    op.drop_column('ps_endpoints', 'rtp_timeout')
-    op.drop_column('ps_endpoints', 'rtp_timeout_hold')
+    with op.batch_alter_table('ps_endpoints') as batch_op:
+        batch_op.drop_column('rtp_timeout')
+        batch_op.drop_column('rtp_timeout_hold')
diff --git a/contrib/ast-db-manage/config/versions/28b8e71e541f_add_g726_non_standard.py b/contrib/ast-db-manage/config/versions/28b8e71e541f_add_g726_non_standard.py
index ad36bd9..09056d6 100644
--- a/contrib/ast-db-manage/config/versions/28b8e71e541f_add_g726_non_standard.py
+++ b/contrib/ast-db-manage/config/versions/28b8e71e541f_add_g726_non_standard.py
@@ -27,4 +27,5 @@ def upgrade():
 
 
 def downgrade():
-    op.drop_column('ps_endpoints', 'g726_non_standard')
+    with op.batch_alter_table('ps_endpoints') as batch_op:
+        batch_op.drop_column('g726_non_standard')
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
index 8c499ae..8e05a62 100644
--- 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
@@ -19,4 +19,5 @@ def upgrade():
 
 
 def downgrade():
-    op.drop_column('ps_registrations', 'fatal_retry_interval')
+    with op.batch_alter_table('ps_registrations') as batch_op:
+        batch_op.drop_column('fatal_retry_interval')
diff --git a/contrib/ast-db-manage/config/versions/2d078ec071b7_increaes_contact_column_size.py b/contrib/ast-db-manage/config/versions/2d078ec071b7_increaes_contact_column_size.py
index 2ade86f..9f98750 100644
--- a/contrib/ast-db-manage/config/versions/2d078ec071b7_increaes_contact_column_size.py
+++ b/contrib/ast-db-manage/config/versions/2d078ec071b7_increaes_contact_column_size.py
@@ -15,8 +15,10 @@ import sqlalchemy as sa
 
 
 def upgrade():
-    op.alter_column('ps_aors', 'contact', type_=sa.String(255))
+    with op.batch_alter_table('ps_aors') as batch_op:
+        batch_op.alter_column('contact', type_=sa.String(255))
 
 
 def downgrade():
-    op.alter_column('ps_aors', 'contact', type_=sa.String(40))
+    with op.batch_alter_table('ps_aors') as batch_op:
+        batch_op.alter_column('contact', type_=sa.String(40))
diff --git a/contrib/ast-db-manage/config/versions/2fc7930b41b3_add_pjsip_endpoint_options_for_12_1.py b/contrib/ast-db-manage/config/versions/2fc7930b41b3_add_pjsip_endpoint_options_for_12_1.py
index 564897e..102265e 100755
--- a/contrib/ast-db-manage/config/versions/2fc7930b41b3_add_pjsip_endpoint_options_for_12_1.py
+++ b/contrib/ast-db-manage/config/versions/2fc7930b41b3_add_pjsip_endpoint_options_for_12_1.py
@@ -120,15 +120,15 @@ def upgrade():
     op.create_index('ps_registrations_id', 'ps_registrations', ['id'])
 
     ########################## add columns ###########################
-
+    with op.batch_alter_table('ps_endpoints') as batch_op:
     # new columns for endpoints
-    op.add_column('ps_endpoints', sa.Column('media_address', sa.String(40)))
-    op.add_column('ps_endpoints', sa.Column('redirect_method',
+        batch_op.add_column(sa.Column('media_address', sa.String(40)))
+        batch_op.add_column(sa.Column('redirect_method',
                                             pjsip_redirect_method_values))
-    op.add_column('ps_endpoints', sa.Column('set_var', sa.Text()))
+        batch_op.add_column(sa.Column('set_var', sa.Text()))
 
     # rename mwi_fromuser to mwi_from_user
-    op.alter_column('ps_endpoints', 'mwi_fromuser',
+        batch_op.alter_column('mwi_fromuser',
                     new_column_name='mwi_from_user',
                     existing_type=sa.String(40))
 
@@ -144,20 +144,23 @@ def upgrade():
 def downgrade():
     ########################## drop columns ##########################
 
-    op.drop_column('ps_aors', 'support_path')
-    op.drop_column('ps_aors', 'outbound_proxy')
-    op.drop_column('ps_aors', 'maximum_expiration')
+    with op.batch_alter_table('ps_aors') as batch_op:
+        batch_op.drop_column('support_path')
+        batch_op.drop_column('outbound_proxy')
+        batch_op.drop_column('maximum_expiration')
 
-    op.drop_column('ps_contacts', 'path')
-    op.drop_column('ps_contacts', 'outbound_proxy')
+    with op.batch_alter_table('ps_contacts') as batch_op:
+        batch_op.drop_column('path')
+        batch_op.drop_column('outbound_proxy')
 
-    op.alter_column('ps_endpoints', 'mwi_from_user',
+    with op.batch_alter_table('ps_endpoints') as batch_op:
+        batch_op.alter_column('mwi_from_user',
                     new_column_name='mwi_fromuser',
                     existing_type=sa.String(40))
 
-    op.drop_column('ps_endpoints', 'set_var')
-    op.drop_column('ps_endpoints', 'redirect_method')
-    op.drop_column('ps_endpoints', 'media_address')
+        batch_op.drop_column('set_var')
+        batch_op.drop_column('redirect_method')
+        batch_op.drop_column('media_address')
 
     ########################## drop tables ###########################
 
diff --git a/contrib/ast-db-manage/config/versions/31cd4f4891ec_add_auto_dtmf_mode.py b/contrib/ast-db-manage/config/versions/31cd4f4891ec_add_auto_dtmf_mode.py
index 0d84390..b1a9f8b 100644
--- a/contrib/ast-db-manage/config/versions/31cd4f4891ec_add_auto_dtmf_mode.py
+++ b/contrib/ast-db-manage/config/versions/31cd4f4891ec_add_auto_dtmf_mode.py
@@ -20,14 +20,14 @@ NEW_ENUM = ['rfc4733', 'inband', 'info', 'auto']
 old_type = sa.Enum(*OLD_ENUM, name='pjsip_dtmf_mode_values')
 new_type = sa.Enum(*NEW_ENUM, name='pjsip_dtmf_mode_values_v2')
 
-tcr = sa.sql.table('ps_endpoints', sa.Column('dtmf_mode', new_type,
-                   nullable=True))
-
 def upgrade():
     context = op.get_context()
 
     # Upgrading to this revision WILL clear your directmedia values.
-    if context.bind.dialect.name != 'postgresql':
+    if context.bind.dialect.name == 'sqlite':
+        with op.batch_alter_table('ps_endpoints') as batch_op:
+            batch_op.alter_column('dtmf_mode', type_=new_type)
+    elif context.bind.dialect.name != 'postgresql':
         op.alter_column('ps_endpoints', 'dtmf_mode',
                         type_=new_type,
                         existing_type=old_type)
@@ -45,10 +45,10 @@ def upgrade():
 def downgrade():
     context = op.get_context()
 
-    op.execute(tcr.update().where(tcr.c.directmedia==u'outgoing')
-               .values(directmedia=None))
-
-    if context.bind.dialect.name != 'postgresql':
+    if context.bind.dialect.name == 'sqlite':
+        with op.batch_alter_table('ps_endpoints') as batch_op:
+            batch_op.alter_column('dtmf_mode', type_=old_type)
+    elif context.bind.dialect.name != 'postgresql':
         op.alter_column('ps_endpoints', 'dtmf_mode',
                         type_=old_type,
                         existing_type=new_type)
diff --git a/contrib/ast-db-manage/config/versions/371a3bf4143e_add_user_eq_phone_option_to_pjsip.py b/contrib/ast-db-manage/config/versions/371a3bf4143e_add_user_eq_phone_option_to_pjsip.py
index ea2b291..bb65874 100644
--- a/contrib/ast-db-manage/config/versions/371a3bf4143e_add_user_eq_phone_option_to_pjsip.py
+++ b/contrib/ast-db-manage/config/versions/371a3bf4143e_add_user_eq_phone_option_to_pjsip.py
@@ -27,4 +27,5 @@ def upgrade():
     op.add_column('ps_endpoints', sa.Column('user_eq_phone', yesno_values))
 
 def downgrade():
-    op.drop_column('ps_endpoints', 'user_eq_phone')
+    with op.batch_alter_table('ps_endpoints') as batch_op:
+        batch_op.drop_column('user_eq_phone')
diff --git a/contrib/ast-db-manage/config/versions/3855ee4e5f85_add_missing_pjsip_options.py b/contrib/ast-db-manage/config/versions/3855ee4e5f85_add_missing_pjsip_options.py
index afc1beb..08457a9 100644
--- a/contrib/ast-db-manage/config/versions/3855ee4e5f85_add_missing_pjsip_options.py
+++ b/contrib/ast-db-manage/config/versions/3855ee4e5f85_add_missing_pjsip_options.py
@@ -20,5 +20,7 @@ def upgrade():
 
 
 def downgrade():
-    op.drop_column('ps_contacts', 'user_agent')
-    op.drop_column('ps_endpoints', 'message_context')
+    with op.batch_alter_table('ps_contacts') as batch_op:
+        batch_op.drop_column('user_agent')
+    with op.batch_alter_table('ps_endpoints') as batch_op:
+        batch_op.drop_column('message_context')
diff --git a/contrib/ast-db-manage/config/versions/3bcc0b5bc2c9_add_allow_reload_to_ps_transports.py b/contrib/ast-db-manage/config/versions/3bcc0b5bc2c9_add_allow_reload_to_ps_transports.py
new file mode 100644
index 0000000..7f2c579
--- /dev/null
+++ b/contrib/ast-db-manage/config/versions/3bcc0b5bc2c9_add_allow_reload_to_ps_transports.py
@@ -0,0 +1,26 @@
+"""Add allow_reload to ps_transports
+
+Revision ID: 3bcc0b5bc2c9
+Revises: dbc44d5a908
+Create Date: 2016-02-05 17:43:39.183785
+
+"""
+
+# revision identifiers, used by Alembic.
+revision = '3bcc0b5bc2c9'
+down_revision = 'dbc44d5a908'
+
+from alembic import op
+import sqlalchemy as sa
+from sqlalchemy.dialects.postgresql import ENUM
+
+YESNO_NAME = 'yesno_values'
+YESNO_VALUES = ['yes', 'no']
+
+def upgrade():
+    yesno_values = ENUM(*YESNO_VALUES, name=YESNO_NAME, create_type=False)
+    op.add_column('ps_transports', sa.Column('allow_reload', yesno_values))
+
+def downgrade():
+    with op.batch_alter_table('ps_transports') as batch_op:
+        batch_op.drop_column('allow_reload')
diff --git a/contrib/ast-db-manage/config/versions/423f34ad36e2_fix_pjsip_qualify_ti.py b/contrib/ast-db-manage/config/versions/423f34ad36e2_fix_pjsip_qualify_ti.py
new file mode 100644
index 0000000..bcdfdc5
--- /dev/null
+++ b/contrib/ast-db-manage/config/versions/423f34ad36e2_fix_pjsip_qualify_ti.py
@@ -0,0 +1,26 @@
+"""fix pjsip qualify timeout
+
+Revision ID: 423f34ad36e2
+Revises: 136885b81223
+Create Date: 2016-01-13 21:49:21.557734
+
+"""
+
+# revision identifiers, used by Alembic.
+revision = '423f34ad36e2'
+down_revision = '136885b81223'
+
+from alembic import op
+import sqlalchemy as sa
+
+def upgrade():
+    with op.batch_alter_table('ps_aors') as batch_op:
+        batch_op.alter_column('qualify_timeout', type_=sa.Float)
+    with op.batch_alter_table('ps_contacts') as batch_op:
+        batch_op.alter_column('qualify_timeout', type_=sa.Float)
+
+def downgrade():
+    with op.batch_alter_table('ps_aors') as batch_op:
+        batch_op.alter_column('qualify_timeout', type_=sa.Integer)
+    with op.batch_alter_table('ps_contacts') as batch_op:
+        batch_op.alter_column('qualify_timeout', type_=sa.Integer)
diff --git a/contrib/ast-db-manage/config/versions/43956d550a44_add_tables_for_pjsip.py b/contrib/ast-db-manage/config/versions/43956d550a44_add_tables_for_pjsip.py
index 0c4d9c8..140fe5b 100755
--- a/contrib/ast-db-manage/config/versions/43956d550a44_add_tables_for_pjsip.py
+++ b/contrib/ast-db-manage/config/versions/43956d550a44_add_tables_for_pjsip.py
@@ -12,6 +12,7 @@ down_revision = '4da0c5f79a9c'
 
 from alembic import op
 import sqlalchemy as sa
+from sqlalchemy.dialects.postgresql import ENUM
 
 
 YESNO_VALUES = ['yes', 'no']
@@ -181,9 +182,21 @@ def upgrade():
 
 
 def downgrade():
+    context = op.get_context()
+
     op.drop_table('ps_endpoints')
     op.drop_table('ps_auths')
     op.drop_table('ps_aors')
     op.drop_table('ps_contacts')
     op.drop_table('ps_domain_aliases')
     op.drop_table('ps_endpoint_id_ips')
+
+    enums = ['yesno_values',
+             'pjsip_100rel_values','pjsip_auth_type_values','pjsip_cid_privacy_values',
+             'pjsip_connected_line_method_values','pjsip_direct_media_glare_mitigation_values',
+             'pjsip_dtls_setup_values','pjsip_dtmf_mode_values','pjsip_identify_by_values',
+             'pjsip_media_encryption_values','pjsip_t38udptl_ec_values','pjsip_timer_values']
+
+    if context.bind.dialect.name == 'postgresql':
+        for e in enums:
+            ENUM(name=e).drop(op.get_bind(), checkfirst=False)
diff --git a/contrib/ast-db-manage/config/versions/45e3f47c6c44_add_pjsip_endpoint_identifier_order.py b/contrib/ast-db-manage/config/versions/45e3f47c6c44_add_pjsip_endpoint_identifier_order.py
index 213da92..ffc9cb9 100644
--- a/contrib/ast-db-manage/config/versions/45e3f47c6c44_add_pjsip_endpoint_identifier_order.py
+++ b/contrib/ast-db-manage/config/versions/45e3f47c6c44_add_pjsip_endpoint_identifier_order.py
@@ -18,4 +18,5 @@ def upgrade():
     op.add_column('ps_globals', sa.Column('endpoint_identifier_order', sa.String(40)))
 
 def downgrade():
-    op.drop_column('ps_globals', 'endpoint_identifier_order')
+    with op.batch_alter_table('ps_globals') as batch_op:
+        batch_op.drop_column('endpoint_identifier_order')
diff --git a/contrib/ast-db-manage/config/versions/461d7d691209_add_pjsip_qualify_timeout.py b/contrib/ast-db-manage/config/versions/461d7d691209_add_pjsip_qualify_timeout.py
index 9600c04..ec8a904 100644
--- a/contrib/ast-db-manage/config/versions/461d7d691209_add_pjsip_qualify_timeout.py
+++ b/contrib/ast-db-manage/config/versions/461d7d691209_add_pjsip_qualify_timeout.py
@@ -16,10 +16,9 @@ import sqlalchemy as sa
 def upgrade():
     op.add_column('ps_aors', sa.Column('qualify_timeout', sa.Integer))
     op.add_column('ps_contacts', sa.Column('qualify_timeout', sa.Integer))
-    pass
-
 
 def downgrade():
-    op.drop_column('ps_aors', 'qualify_timeout')
-    op.drop_column('ps_contacts', 'qualify_timeout')
-    pass
+    with op.batch_alter_table('ps_aors') as batch_op:
+        batch_op.drop_column('qualify_timeout')
+    with op.batch_alter_table('ps_contacts') as batch_op:
+        batch_op.drop_column('qualify_timeout')
diff --git a/contrib/ast-db-manage/config/versions/498357a710ae_add_rtp_keepalive.py b/contrib/ast-db-manage/config/versions/498357a710ae_add_rtp_keepalive.py
index 5a4f470..3ad2650 100644
--- a/contrib/ast-db-manage/config/versions/498357a710ae_add_rtp_keepalive.py
+++ b/contrib/ast-db-manage/config/versions/498357a710ae_add_rtp_keepalive.py
@@ -19,4 +19,5 @@ def upgrade():
 
 
 def downgrade():
-    op.drop_column('ps_endpoints', 'rtp_keepalive')
+    with op.batch_alter_table('ps_endpoints') as batch_op:
+        batch_op.drop_column('rtp_keepalive')
diff --git a/contrib/ast-db-manage/config/versions/4c573e7135bd_fix_tos_field_types.py b/contrib/ast-db-manage/config/versions/4c573e7135bd_fix_tos_field_types.py
index aefddd1..d9bbf89 100755
--- a/contrib/ast-db-manage/config/versions/4c573e7135bd_fix_tos_field_types.py
+++ b/contrib/ast-db-manage/config/versions/4c573e7135bd_fix_tos_field_types.py
@@ -19,43 +19,43 @@ YESNO_NAME = 'yesno_values'
 YESNO_VALUES = ['yes', 'no']
 
 def upgrade():
-    op.alter_column('ps_endpoints', 'tos_audio',
+    with op.batch_alter_table('ps_endpoints') as batch_op:
+        batch_op.alter_column('tos_audio',
                     type_=sa.String(10))
-    op.alter_column('ps_endpoints', 'tos_video',
+        batch_op.alter_column('tos_video',
                     type_=sa.String(10))
-    op.alter_column('ps_transports', 'tos',
+        batch_op.drop_column('cos_audio')
+        batch_op.drop_column('cos_video')
+        batch_op.add_column(sa.Column('cos_audio', sa.Integer))
+        batch_op.add_column(sa.Column('cos_video', sa.Integer))
+
+    with op.batch_alter_table('ps_transports') as batch_op:
+        batch_op.alter_column('tos',
                     type_=sa.String(10))
 
     # Can't cast YENO_VALUES to Integers, so dropping and adding is required
-    op.drop_column('ps_endpoints', 'cos_audio')
-    op.drop_column('ps_endpoints', 'cos_video')
-    op.drop_column('ps_transports', 'cos')
-
-    op.add_column('ps_endpoints', sa.Column('cos_audio', sa.Integer))
-    op.add_column('ps_endpoints', sa.Column('cos_video', sa.Integer))
-    op.add_column('ps_transports', sa.Column('cos', sa.Integer))
-    pass
+        batch_op.drop_column('cos')
 
+        batch_op.add_column(sa.Column('cos', sa.Integer))
 
 def downgrade():
 
     yesno_values = ENUM(*YESNO_VALUES, name=YESNO_NAME, create_type=False)
 
     # Can't cast string to YESNO_VALUES, so dropping and adding is required
-    op.drop_column('ps_endpoints', 'tos_audio')
-    op.drop_column('ps_endpoints', 'tos_video')
-    op.drop_column('ps_transports', 'tos')
-
-    op.add_column('ps_endpoints', sa.Column('tos_audio', yesno_values))
-    op.add_column('ps_endpoints', sa.Column('tos_video', yesno_values))
-    op.add_column('ps_transports', sa.Column('tos', yesno_values))
-
+    with op.batch_alter_table('ps_endpoints') as batch_op:
+        batch_op.drop_column('tos_audio')
+        batch_op.drop_column('tos_video')
+        batch_op.add_column(sa.Column('tos_audio', yesno_values))
+        batch_op.add_column(sa.Column('tos_video', yesno_values))
+        batch_op.drop_column('cos_audio')
+        batch_op.drop_column('cos_video')
+        batch_op.add_column(sa.Column('cos_audio', yesno_values))
+        batch_op.add_column(sa.Column('cos_video', yesno_values))
+
+    with op.batch_alter_table('ps_transports') as batch_op:
+        batch_op.drop_column('tos')
+        batch_op.add_column(sa.Column('tos', yesno_values))
     # Can't cast integers to YESNO_VALUES, so dropping and adding is required
-    op.drop_column('ps_endpoints', 'cos_audio')
-    op.drop_column('ps_endpoints', 'cos_video')
-    op.drop_column('ps_transports', 'cos')
-
-    op.add_column('ps_endpoints', sa.Column('cos_audio', yesno_values))
-    op.add_column('ps_endpoints', sa.Column('cos_video', yesno_values))
-    op.add_column('ps_transports', sa.Column('cos', yesno_values))
-    pass
+        batch_op.drop_column('cos')
+        batch_op.add_column(sa.Column('cos', yesno_values))
diff --git a/contrib/ast-db-manage/config/versions/4da0c5f79a9c_create_tables.py b/contrib/ast-db-manage/config/versions/4da0c5f79a9c_create_tables.py
index ffaff92..01c40ac 100755
--- a/contrib/ast-db-manage/config/versions/4da0c5f79a9c_create_tables.py
+++ b/contrib/ast-db-manage/config/versions/4da0c5f79a9c_create_tables.py
@@ -30,7 +30,7 @@ down_revision = None
 
 from alembic import op
 import sqlalchemy as sa
-
+from sqlalchemy.dialects.postgresql import ENUM
 
 YESNO_VALUES = ['yes', 'no']
 TYPE_VALUES = ['friend', 'user', 'peer']
@@ -323,8 +323,20 @@ def upgrade():
 
 
 def downgrade():
+    context = op.get_context()
+
     op.drop_table('sippeers')
     op.drop_table('iaxfriends')
     op.drop_table('voicemail')
     op.drop_table('meetme')
     op.drop_table('musiconhold')
+
+    enums = ['type_values', 'yes_no_values',
+             'sip_transport_values','sip_dtmfmode_values','sip_directmedia_values',
+             'sip_progressinband_values','sip_session_timers_values','sip_session_refresher_values',
+             'sip_callingpres_values','iax_requirecalltoken_values','iax_encryption_values',
+             'iax_transfer_values','moh_mode_values']
+
+    if context.bind.dialect.name == 'postgresql':
+        for e in enums:
+            ENUM(name=e).drop(op.get_bind(), checkfirst=False)
diff --git a/contrib/ast-db-manage/config/versions/5139253c0423_make_q_member_uniqueid_autoinc.py b/contrib/ast-db-manage/config/versions/5139253c0423_make_q_member_uniqueid_autoinc.py
index 6bcaa9a..632f4c4 100755
--- a/contrib/ast-db-manage/config/versions/5139253c0423_make_q_member_uniqueid_autoinc.py
+++ b/contrib/ast-db-manage/config/versions/5139253c0423_make_q_member_uniqueid_autoinc.py
@@ -33,28 +33,31 @@ import sqlalchemy as sa
 
 
 def upgrade():
+    context = op.get_context()
     # Was unable to find a way to use op.alter_column() to add the unique
     # index property.
-    op.drop_column('queue_members', 'uniqueid')
-    op.add_column(
-        'queue_members',
-        sa.Column(
-            name='uniqueid', type_=sa.Integer, nullable=False,
-            unique=True))
+    if context.bind.dialect.name == 'sqlite':
+        with op.batch_alter_table('queue_members') as batch_op:
+            batch_op.create_primary_key('queue_members_pj', columns='uniqueid')
+    else:
+        op.drop_column('queue_members', 'uniqueid')
+        op.add_column(
+                      'queue_members',
+                      sa.Column(
+                                name='uniqueid', type_=sa.Integer, nullable=False,
+                                unique=True))
     # The postgres backend does not like the autoincrement needed for
     # mysql here.  It is just the backend that is giving a warning and
     # not the database itself.
-    op.alter_column(
-        table_name='queue_members', column_name='uniqueid',
-        existing_type=sa.Integer, existing_nullable=False,
-        autoincrement=True)
+        op.alter_column(
+                        table_name='queue_members', column_name='uniqueid',
+                        existing_type=sa.Integer, existing_nullable=False,
+                        autoincrement=True)
 
 
 def downgrade():
     # Was unable to find a way to use op.alter_column() to remove the
     # unique index property.
-    op.drop_column('queue_members', 'uniqueid')
-    op.add_column(
-        'queue_members',
-        sa.Column(name='uniqueid', type_=sa.String(80), nullable=False))
-
+    with op.batch_alter_table('queue_members') as batch_op:
+        batch_op.drop_column('uniqueid')
+        batch_op.add_column(sa.Column(name='uniqueid', type_=sa.String(80), nullable=False))
diff --git a/contrib/ast-db-manage/config/versions/51f8cb66540e_add_further_dtls_options.py b/contrib/ast-db-manage/config/versions/51f8cb66540e_add_further_dtls_options.py
index c2dacda..8d0f68f 100644
--- a/contrib/ast-db-manage/config/versions/51f8cb66540e_add_further_dtls_options.py
+++ b/contrib/ast-db-manage/config/versions/51f8cb66540e_add_further_dtls_options.py
@@ -28,5 +28,6 @@ def upgrade():
     op.add_column('ps_endpoints', sa.Column('media_use_received_transport', yesno_values))
 
 def downgrade():
-    op.drop_column('ps_endpoints', 'force_avp')
-    op.drop_column('ps_endpoints', 'media_use_received_transport')
+    with op.batch_alter_table('ps_endpoints') as batch_op:
+        batch_op.drop_column('force_avp')
+        batch_op.drop_column('media_use_received_transport')
diff --git a/contrib/ast-db-manage/config/versions/5950038a6ead_fix_pjsip_verifiy_typo.py b/contrib/ast-db-manage/config/versions/5950038a6ead_fix_pjsip_verifiy_typo.py
index 28ebc8b..ace5444 100644
--- a/contrib/ast-db-manage/config/versions/5950038a6ead_fix_pjsip_verifiy_typo.py
+++ b/contrib/ast-db-manage/config/versions/5950038a6ead_fix_pjsip_verifiy_typo.py
@@ -19,11 +19,13 @@ YESNO_VALUES = ['yes', 'no']
 
 def upgrade():
     yesno_values = ENUM(*YESNO_VALUES, name=YESNO_NAME, create_type=False)
-    op.alter_column('ps_transports', 'verifiy_server', type_=yesno_values,
+    with op.batch_alter_table('ps_transports') as batch_op:
+        batch_op.alter_column('verifiy_server', type_=yesno_values,
                     new_column_name='verify_server')
 
 
 def downgrade():
     yesno_values = ENUM(*YESNO_VALUES, name=YESNO_NAME, create_type=False)
-    op.alter_column('ps_transports', 'verify_server', type_=yesno_values,
+    with op.batch_alter_table('ps_transports') as batch_op:
+        batch_op.alter_column('verify_server', type_=yesno_values,
                     new_column_name='verifiy_server')
diff --git a/contrib/ast-db-manage/config/versions/a541e0b5e89_add_pjsip_max_initial_qualify_time.py b/contrib/ast-db-manage/config/versions/a541e0b5e89_add_pjsip_max_initial_qualify_time.py
index 0ffd784..dc06d84 100644
--- a/contrib/ast-db-manage/config/versions/a541e0b5e89_add_pjsip_max_initial_qualify_time.py
+++ b/contrib/ast-db-manage/config/versions/a541e0b5e89_add_pjsip_max_initial_qualify_time.py
@@ -17,4 +17,5 @@ def upgrade():
     op.add_column('ps_globals', sa.Column('max_initial_qualify_time', sa.Integer))
 
 def downgrade():
-    op.drop_column('ps_globals', 'max_initial_qualify_time')
+    with op.batch_alter_table('ps_globals') as batch_op:
+        batch_op.drop_column('max_initial_qualify_time')
diff --git a/contrib/ast-db-manage/config/versions/dbc44d5a908_add_missing_columns_to_sys_and_reg.py b/contrib/ast-db-manage/config/versions/dbc44d5a908_add_missing_columns_to_sys_and_reg.py
new file mode 100644
index 0000000..b4502eb
--- /dev/null
+++ b/contrib/ast-db-manage/config/versions/dbc44d5a908_add_missing_columns_to_sys_and_reg.py
@@ -0,0 +1,36 @@
+"""Add missing columns to system and registration
+
+Revision ID: dbc44d5a908
+Revises: 423f34ad36e2
+Create Date: 2016-02-03 13:15:15.083043
+
+"""
+
+# revision identifiers, used by Alembic.
+revision = 'dbc44d5a908'
+down_revision = '423f34ad36e2'
+
+from alembic import op
+import sqlalchemy as sa
+from sqlalchemy.dialects.postgresql import ENUM
+
+YESNO_NAME = 'yesno_values'
+YESNO_VALUES = ['yes', 'no']
+
+def upgrade():
+    ############################# Enums ##############################
+
+    # yesno_values have already been created, so use postgres enum object
+    # type to get around "already created" issue - works okay with mysql
+    yesno_values = ENUM(*YESNO_VALUES, name=YESNO_NAME, create_type=False)
+
+    op.add_column('ps_systems', sa.Column('disable_tcp_switch', yesno_values))
+    op.add_column('ps_registrations', sa.Column('line', yesno_values))
+    op.add_column('ps_registrations', sa.Column('endpoint', sa.String(40)))
+
+def downgrade():
+    with op.batch_alter_table('ps_systems') as batch_op:
+        batch_op.drop_column('disable_tcp_switch')
+    with op.batch_alter_table('ps_registrations') as batch_op:
+        batch_op.drop_column('line')
+        batch_op.drop_column('endpoint')
diff --git a/contrib/ast-db-manage/config/versions/e96a0b8071c_increase_pjsip_column_size.py b/contrib/ast-db-manage/config/versions/e96a0b8071c_increase_pjsip_column_size.py
index d386ded..f25c298 100644
--- a/contrib/ast-db-manage/config/versions/e96a0b8071c_increase_pjsip_column_size.py
+++ b/contrib/ast-db-manage/config/versions/e96a0b8071c_increase_pjsip_column_size.py
@@ -15,25 +15,28 @@ import sqlalchemy as sa
 
 
 def upgrade():
-    op.alter_column('ps_globals', 'user_agent', type_=sa.String(255))
+    with op.batch_alter_table('ps_globals') as batch_op:
+        batch_op.alter_column('user_agent', type_=sa.String(255))
 
-    op.alter_column('ps_contacts', 'id', type_=sa.String(255))
-    op.alter_column('ps_contacts', 'uri', type_=sa.String(255))
-    op.alter_column('ps_contacts', 'user_agent', type_=sa.String(255))
+    with op.batch_alter_table('ps_contacts') as batch_op:
+        batch_op.alter_column('id', type_=sa.String(255))
+        batch_op.alter_column('uri', type_=sa.String(255))
+        batch_op.alter_column('user_agent', type_=sa.String(255))
 
-    op.alter_column('ps_registrations', 'client_uri', type_=sa.String(255))
-    op.alter_column('ps_registrations', 'server_uri', type_=sa.String(255))
+    with op.batch_alter_table('ps_registrations') as batch_op:
+        batch_op.alter_column('client_uri', type_=sa.String(255))
+        batch_op.alter_column('server_uri', type_=sa.String(255))
 
 
 def downgrade():
-    op.alter_column('ps_registrations', 'server_uri', type_=sa.String(40))
-    op.alter_column('ps_registrations', 'client_uri', type_=sa.String(40))
-
-    op.alter_column('ps_contacts', 'user_agent', type_=sa.String(40))
-    op.alter_column('ps_contacts', 'uri', type_=sa.String(40))
-    op.alter_column('ps_contacts', 'id', type_=sa.String(40))
-
-    op.alter_column('ps_globals', 'user_agent', type_=sa.String(40))
-
+    with op.batch_alter_table('ps_globals') as batch_op:
+        batch_op.alter_column('user_agent', type_=sa.String(40))
 
+    with op.batch_alter_table('ps_contacts') as batch_op:
+        batch_op.alter_column('id', type_=sa.String(40))
+        batch_op.alter_column('uri', type_=sa.String(40))
+        batch_op.alter_column('user_agent', type_=sa.String(40))
 
+    with op.batch_alter_table('ps_registrations') as batch_op:
+        batch_op.alter_column('client_uri', type_=sa.String(40))
+        batch_op.alter_column('server_uri', type_=sa.String(40))
diff --git a/contrib/ast-db-manage/config/versions/eb88a14f2a_add_media_encryption_optimistic_to_pjsip.py b/contrib/ast-db-manage/config/versions/eb88a14f2a_add_media_encryption_optimistic_to_pjsip.py
index 2d96b37..5e50df2 100644
--- a/contrib/ast-db-manage/config/versions/eb88a14f2a_add_media_encryption_optimistic_to_pjsip.py
+++ b/contrib/ast-db-manage/config/versions/eb88a14f2a_add_media_encryption_optimistic_to_pjsip.py
@@ -28,4 +28,5 @@ def upgrade():
 
 
 def downgrade():
-    op.drop_column('ps_endpoints', 'media_encryption_optimistic')
+    with op.batch_alter_table('ps_endpoints') as batch_op:
+        batch_op.drop_column('media_encryption_optimistic')
diff --git a/contrib/docker/Dockerfile.asterisk b/contrib/docker/Dockerfile.asterisk
new file mode 100644
index 0000000..41bf435
--- /dev/null
+++ b/contrib/docker/Dockerfile.asterisk
@@ -0,0 +1,19 @@
+# Version 0.0.3
+FROM centos:7
+MAINTAINER Leif Madsen <leif at leifmadsen.com>
+ENV REFRESHED_AT 2016-02-25
+ENV STARTDIR /tmp
+ENV RPMPATH ./out
+
+# copy is required because you can't mount volumes during build
+COPY $RPMPATH/*.rpm $STARTDIR
+
+# install dependencies and Asterisk RPM
+RUN yum install epel-release -y && \
+    yum install -y *.rpm && \
+    yum clean all && \
+    yum autoremove -y && \
+    /sbin/ldconfig
+
+ENTRYPOINT ["/usr/sbin/asterisk"]
+CMD ["-c", "-vvvv", "-g"]
diff --git a/contrib/docker/Dockerfile.packager b/contrib/docker/Dockerfile.packager
new file mode 100644
index 0000000..3588210
--- /dev/null
+++ b/contrib/docker/Dockerfile.packager
@@ -0,0 +1,9 @@
+FROM alanfranz/fwd-centos-7:latest
+MAINTAINER Leif Madsen <leif at leifmadsen.com>
+ENV REFRESHED_AT 2016-02-25
+ADD contrib/scripts/install_prereq /tmp/install_prereq
+RUN yum clean metadata && \
+    yum -y update && \
+    yum install epel-release -y && \
+    yum clean all &&\
+    /tmp/install_prereq install
diff --git a/contrib/docker/README.md b/contrib/docker/README.md
new file mode 100644
index 0000000..2a9bd66
--- /dev/null
+++ b/contrib/docker/README.md
@@ -0,0 +1,39 @@
+# Building Asterisk into a Docker Container Image
+The following set of steps should leave you with a Docker container that
+is relatively small, built from your local checked out source, and even
+provides you with a nice little RPM too!
+
+## Build the package container image
+Build the package container image. This uses FPM[1] so no `spec` files and
+such are necessary.
+```
+docker build --pull -f contrib/docker/Dockerfile.packager -t asterisk-build .
+```
+
+## Build your Asterisk RPM from source
+Build the Asterisk RPM from the resulting container image.
+```
+docker run -ti \
+    -v $(pwd):/application:ro \
+    -v $(pwd)/out:/build \
+    -w /application asterisk-build \
+    /application/contrib/docker/make-package.sh 13.6.0
+```
+> **NOTE**: If you need to build this on a system that has SElinux enabled
+> you'll need to use the following command instead:
+> ```
+> docker run -ti \
+>     -v $(pwd):/application:Z \
+>     -v $(pwd)/out:/build:Z \
+>     -w /application asterisk-build \
+>     /application/contrib/docker/make-package.sh 13.6.0
+> ```
+
+## Create your Asterisk container image
+Now create your own Asterisk container image from the resulting RPM.
+```
+docker build --rm -t madsen/asterisk:13.6.0-1 -f contrib/docker/Dockerfile.asterisk .
+```
+
+# References
+[1] https://github.com/jordansissel/fpm
diff --git a/contrib/docker/make-package.sh b/contrib/docker/make-package.sh
new file mode 100755
index 0000000..261df60
--- /dev/null
+++ b/contrib/docker/make-package.sh
@@ -0,0 +1,72 @@
+#!/bin/bash
+# This script intended to be run from the packager container. Please see the
+# README.md file for more information on how this script is used.
+#
+set -ex
+[ -n "$1" ]
+mkdir -p /opt
+
+# move into the application directory where Asterisk source exists
+cd /application
+
+# strip the source of any Git-isms
+rsync -av --exclude='.git' . /tmp/application
+
+# move to the build directory and build Asterisk
+cd /tmp/application
+./configure
+cd menuselect
+make menuselect
+cd ..
+make menuselect-tree
+
+menuselect/menuselect --check-deps menuselect.makeopts
+
+# Do not include sound files. You should be mounting these from and external
+# volume.
+sed -i -e 's/MENUSELECT_MOH=.*$/MENUSELECT_MOH=/' menuselect.makeopts
+sed -i -e 's/MENUSELECT_CORE_SOUNDS=.*$/MENUSELECT_CORE_SOUNDS=/' menuselect.makeopts
+
+# Build it!
+make all install DESTDIR=/tmp/installdir
+
+rm -rf /tmp/application
+cd /build
+
+# Use the Fine Package Management system to build us an RPM without all that
+# reeking effort.
+fpm -t rpm -s dir -n asterisk-custom --version "$1" \
+    --depends libedit \
+    --depends libxslt \
+    --depends jansson \
+    --depends pjproject \
+    --depends openssl \
+    --depends libxml2 \
+    --depends unixODBC \
+    --depends libcurl \
+    --depends libogg \
+    --depends libvorbis \
+    --depends speex \
+    --depends spandsp \
+    --depends freetds \
+    --depends net-snmp \
+    --depends iksemel \
+    --depends corosynclib \
+    --depends newt \
+    --depends lua \
+    --depends sqlite \
+    --depends freetds \
+    --depends radiusclient-ng \
+    --depends postgresql \
+    --depends neon \
+    --depends libical \
+    --depends openldap \
+    --depends sqlite2 \
+    --depends mysql \
+    --depends bluez \
+    --depends gsm \
+    --depends libuuid \
+    --depends libsrtp \
+    -C /tmp/installdir etc usr var
+
+chown -R --reference /application/contrib/docker/make-package.sh .
diff --git a/contrib/realtime/mssql/mssql_cdr.sql b/contrib/realtime/mssql/mssql_cdr.sql
index dcd433f..d342515 100644
--- a/contrib/realtime/mssql/mssql_cdr.sql
+++ b/contrib/realtime/mssql/mssql_cdr.sql
@@ -6,7 +6,7 @@ CREATE TABLE alembic_version (
 
 GO
 
--- Running upgrade None -> 210693f3123d
+-- Running upgrade  -> 210693f3123d
 
 CREATE TABLE cdr (
     accountcode VARCHAR(20) NULL, 
@@ -40,3 +40,5 @@ GO
 
 COMMIT;
 
+GO
+
diff --git a/contrib/realtime/mssql/mssql_config.sql b/contrib/realtime/mssql/mssql_config.sql
index 29ca157..7402db5 100644
--- a/contrib/realtime/mssql/mssql_config.sql
+++ b/contrib/realtime/mssql/mssql_config.sql
@@ -6,7 +6,7 @@ CREATE TABLE alembic_version (
 
 GO
 
--- Running upgrade None -> 4da0c5f79a9c
+-- Running upgrade  -> 4da0c5f79a9c
 
 CREATE TABLE sippeers (
     id INTEGER NOT NULL IDENTITY(1,1), 
@@ -336,6 +336,10 @@ CREATE TABLE musiconhold (
 
 GO
 
+INSERT INTO alembic_version (version_num) VALUES ('4da0c5f79a9c');
+
+GO
+
 -- Running upgrade 4da0c5f79a9c -> 43956d550a44
 
 CREATE TABLE ps_endpoints (
@@ -540,6 +544,10 @@ CREATE INDEX ps_endpoint_id_ips_id ON ps_endpoint_id_ips (id);
 
 GO
 
+UPDATE alembic_version SET version_num='43956d550a44' WHERE alembic_version.version_num = '4da0c5f79a9c';
+
+GO
+
 -- Running upgrade 43956d550a44 -> 581a4264e537
 
 CREATE TABLE extensions (
@@ -555,6 +563,10 @@ CREATE TABLE extensions (
 
 GO
 
+UPDATE alembic_version SET version_num='581a4264e537' WHERE alembic_version.version_num = '43956d550a44';
+
+GO
+
 -- Running upgrade 581a4264e537 -> 2fc7930b41b3
 
 CREATE TABLE ps_systems (
@@ -696,12 +708,20 @@ ALTER TABLE ps_aors ADD CONSTRAINT yesno_values CHECK (support_path IN ('yes', '
 
 GO
 
+UPDATE alembic_version SET version_num='2fc7930b41b3' WHERE alembic_version.version_num = '581a4264e537';
+
+GO
+
 -- Running upgrade 2fc7930b41b3 -> 21e526ad3040
 
 ALTER TABLE ps_globals ADD debug VARCHAR(40) NULL;
 
 GO
 
+UPDATE alembic_version SET version_num='21e526ad3040' WHERE alembic_version.version_num = '2fc7930b41b3';
+
+GO
+
 -- Running upgrade 21e526ad3040 -> 28887f25a46f
 
 CREATE TABLE queues (
@@ -792,6 +812,10 @@ CREATE TABLE queue_members (
 
 GO
 
+UPDATE alembic_version SET version_num='28887f25a46f' WHERE alembic_version.version_num = '21e526ad3040';
+
+GO
+
 -- Running upgrade 28887f25a46f -> 4c573e7135bd
 
 ALTER TABLE ps_endpoints ALTER COLUMN tos_audio VARCHAR(10);
@@ -802,27 +826,27 @@ ALTER TABLE ps_endpoints ALTER COLUMN tos_video VARCHAR(10);
 
 GO
 
-ALTER TABLE ps_transports ALTER COLUMN tos VARCHAR(10);
+ALTER TABLE ps_endpoints DROP COLUMN cos_audio;
 
 GO
 
-ALTER TABLE ps_endpoints DROP COLUMN cos_audio;
+ALTER TABLE ps_endpoints DROP COLUMN cos_video;
 
 GO
 
-ALTER TABLE ps_endpoints DROP COLUMN cos_video;
+ALTER TABLE ps_endpoints ADD cos_audio INTEGER NULL;
 
 GO
 
-ALTER TABLE ps_transports DROP COLUMN cos;
+ALTER TABLE ps_endpoints ADD cos_video INTEGER NULL;
 
 GO
 
-ALTER TABLE ps_endpoints ADD cos_audio INTEGER NULL;
+ALTER TABLE ps_transports ALTER COLUMN tos VARCHAR(10);
 
 GO
 
-ALTER TABLE ps_endpoints ADD cos_video INTEGER NULL;
+ALTER TABLE ps_transports DROP COLUMN cos;
 
 GO
 
@@ -830,6 +854,10 @@ ALTER TABLE ps_transports ADD cos INTEGER NULL;
 
 GO
 
+UPDATE alembic_version SET version_num='4c573e7135bd' WHERE alembic_version.version_num = '28887f25a46f';
+
+GO
+
 -- Running upgrade 4c573e7135bd -> 3855ee4e5f85
 
 ALTER TABLE ps_endpoints ADD message_context VARCHAR(40) NULL;
@@ -840,6 +868,10 @@ ALTER TABLE ps_contacts ADD user_agent VARCHAR(40) NULL;
 
 GO
 
+UPDATE alembic_version SET version_num='3855ee4e5f85' WHERE alembic_version.version_num = '4c573e7135bd';
+
+GO
+
 -- Running upgrade 3855ee4e5f85 -> e96a0b8071c
 
 ALTER TABLE ps_globals ALTER COLUMN user_agent VARCHAR(255);
@@ -866,6 +898,10 @@ ALTER TABLE ps_registrations ALTER COLUMN server_uri VARCHAR(255);
 
 GO
 
+UPDATE alembic_version SET version_num='e96a0b8071c' WHERE alembic_version.version_num = '3855ee4e5f85';
+
+GO
+
 -- Running upgrade e96a0b8071c -> c6d929b23a8
 
 CREATE TABLE ps_subscription_persistence (
@@ -889,6 +925,10 @@ CREATE INDEX ps_subscription_persistence_id ON ps_subscription_persistence (id);
 
 GO
 
+UPDATE alembic_version SET version_num='c6d929b23a8' WHERE alembic_version.version_num = 'e96a0b8071c';
+
+GO
+
 -- Running upgrade c6d929b23a8 -> 51f8cb66540e
 
 ALTER TABLE ps_endpoints ADD force_avp VARCHAR(3) NULL;
@@ -907,18 +947,30 @@ ALTER TABLE ps_endpoints ADD CONSTRAINT yesno_values CHECK (media_use_received_t
 
 GO
 
+UPDATE alembic_version SET version_num='51f8cb66540e' WHERE alembic_version.version_num = 'c6d929b23a8';
+
+GO
+
 -- Running upgrade 51f8cb66540e -> 1d50859ed02e
 
 ALTER TABLE ps_endpoints ADD accountcode VARCHAR(20) NULL;
 
 GO
 
+UPDATE alembic_version SET version_num='1d50859ed02e' WHERE alembic_version.version_num = '51f8cb66540e';
+
+GO
+
 -- Running upgrade 1d50859ed02e -> 1758e8bbf6b
 
 ALTER TABLE sippeers ALTER COLUMN useragent VARCHAR(255);
 
 GO
 
+UPDATE alembic_version SET version_num='1758e8bbf6b' WHERE alembic_version.version_num = '1d50859ed02e';
+
+GO
+
 -- Running upgrade 1758e8bbf6b -> 5139253c0423
 
 ALTER TABLE queue_members DROP COLUMN uniqueid;
@@ -933,6 +985,10 @@ ALTER TABLE queue_members ADD UNIQUE (uniqueid);
 
 GO
 
+UPDATE alembic_version SET version_num='5139253c0423' WHERE alembic_version.version_num = '1758e8bbf6b';
+
+GO
+
 -- Running upgrade 5139253c0423 -> d39508cb8d8
 
 CREATE TABLE queue_rules (
@@ -944,6 +1000,10 @@ CREATE TABLE queue_rules (
 
 GO
 
+UPDATE alembic_version SET version_num='d39508cb8d8' WHERE alembic_version.version_num = '5139253c0423';
+
+GO
+
 -- Running upgrade d39508cb8d8 -> 5950038a6ead
 
 ALTER TABLE ps_transports ALTER COLUMN verifiy_server VARCHAR(3);
@@ -958,6 +1018,10 @@ ALTER TABLE ps_transports ADD CONSTRAINT yesno_values CHECK (verifiy_server IN (
 
 GO
 
+UPDATE alembic_version SET version_num='5950038a6ead' WHERE alembic_version.version_num = 'd39508cb8d8';
+
+GO
+
 -- Running upgrade 5950038a6ead -> 10aedae86a32
 
 ALTER TABLE sippeers DROP CONSTRAINT sip_directmedia_values;
@@ -972,6 +1036,10 @@ ALTER TABLE sippeers ADD CONSTRAINT sip_directmedia_values_v2 CHECK (directmedia
 
 GO
 
+UPDATE alembic_version SET version_num='10aedae86a32' WHERE alembic_version.version_num = '5950038a6ead';
+
+GO
+
 -- Running upgrade 10aedae86a32 -> eb88a14f2a
 
 ALTER TABLE ps_endpoints ADD media_encryption_optimistic VARCHAR(3) NULL;
@@ -982,6 +1050,10 @@ ALTER TABLE ps_endpoints ADD CONSTRAINT yesno_values CHECK (media_encryption_opt
 
 GO
 
+UPDATE alembic_version SET version_num='eb88a14f2a' WHERE alembic_version.version_num = '10aedae86a32';
+
+GO
+
 -- Running upgrade eb88a14f2a -> 371a3bf4143e
 
 ALTER TABLE ps_endpoints ADD user_eq_phone VARCHAR(3) NULL;
@@ -992,12 +1064,20 @@ ALTER TABLE ps_endpoints ADD CONSTRAINT yesno_values CHECK (user_eq_phone IN ('y
 
 GO
 
+UPDATE alembic_version SET version_num='371a3bf4143e' WHERE alembic_version.version_num = 'eb88a14f2a';
+
+GO
+
 -- Running upgrade 371a3bf4143e -> 45e3f47c6c44
 
 ALTER TABLE ps_globals ADD endpoint_identifier_order VARCHAR(40) NULL;
 
 GO
 
+UPDATE alembic_version SET version_num='45e3f47c6c44' WHERE alembic_version.version_num = '371a3bf4143e';
+
+GO
+
 -- Running upgrade 45e3f47c6c44 -> 23530d604b96
 
 ALTER TABLE ps_endpoints ADD rpid_immediate VARCHAR(3) NULL;
@@ -1008,6 +1088,10 @@ ALTER TABLE ps_endpoints ADD CONSTRAINT yesno_values CHECK (rpid_immediate IN ('
 
 GO
 
+UPDATE alembic_version SET version_num='23530d604b96' WHERE alembic_version.version_num = '45e3f47c6c44';
+
+GO
+
 -- Running upgrade 23530d604b96 -> 31cd4f4891ec
 
 ALTER TABLE ps_endpoints DROP CONSTRAINT pjsip_dtmf_mode_values;
@@ -1022,6 +1106,10 @@ ALTER TABLE ps_endpoints ADD CONSTRAINT pjsip_dtmf_mode_values_v2 CHECK (dtmf_mo
 
 GO
 
+UPDATE alembic_version SET version_num='31cd4f4891ec' WHERE alembic_version.version_num = '23530d604b96';
+
+GO
+
 -- Running upgrade 31cd4f4891ec -> 461d7d691209
 
 ALTER TABLE ps_aors ADD qualify_timeout INTEGER NULL;
@@ -1032,12 +1120,20 @@ ALTER TABLE ps_contacts ADD qualify_timeout INTEGER NULL;
 
 GO
 
+UPDATE alembic_version SET version_num='461d7d691209' WHERE alembic_version.version_num = '31cd4f4891ec';
+
+GO
+
 -- Running upgrade 461d7d691209 -> a541e0b5e89
 
 ALTER TABLE ps_globals ADD max_initial_qualify_time INTEGER NULL;
 
 GO
 
+UPDATE alembic_version SET version_num='a541e0b5e89' WHERE alembic_version.version_num = '461d7d691209';
+
+GO
+
 -- Running upgrade a541e0b5e89 -> 28b8e71e541f
 
 ALTER TABLE ps_endpoints ADD g726_non_standard VARCHAR(3) NULL;
@@ -1048,12 +1144,20 @@ ALTER TABLE ps_endpoints ADD CONSTRAINT yesno_values CHECK (g726_non_standard IN
 
 GO
 
+UPDATE alembic_version SET version_num='28b8e71e541f' WHERE alembic_version.version_num = 'a541e0b5e89';
+
+GO
+
 -- Running upgrade 28b8e71e541f -> 498357a710ae
 
 ALTER TABLE ps_endpoints ADD rtp_keepalive INTEGER NULL;
 
 GO
 
+UPDATE alembic_version SET version_num='498357a710ae' WHERE alembic_version.version_num = '28b8e71e541f';
+
+GO
+
 -- Running upgrade 498357a710ae -> 26f10cadc157
 
 ALTER TABLE ps_endpoints ADD rtp_timeout INTEGER NULL;
@@ -1064,33 +1168,129 @@ ALTER TABLE ps_endpoints ADD rtp_timeout_hold INTEGER NULL;
 
 GO
 
+UPDATE alembic_version SET version_num='26f10cadc157' WHERE alembic_version.version_num = '498357a710ae';
+
+GO
+
 -- Running upgrade 26f10cadc157 -> 154177371065
 
 ALTER TABLE ps_globals ADD default_from_user VARCHAR(80) NULL;
 
 GO
 
+UPDATE alembic_version SET version_num='154177371065' WHERE alembic_version.version_num = '26f10cadc157';
+
+GO
+
 -- Running upgrade 154177371065 -> 28ce1e718f05
 
 ALTER TABLE ps_registrations ADD fatal_retry_interval INTEGER NULL;
 
 GO
 
+UPDATE alembic_version SET version_num='28ce1e718f05' WHERE alembic_version.version_num = '154177371065';
+
+GO
+
 -- Running upgrade 28ce1e718f05 -> 189a235b3fd7
 
 ALTER TABLE ps_globals ADD keep_alive_interval INTEGER NULL;
 
 GO
 
+UPDATE alembic_version SET version_num='189a235b3fd7' WHERE alembic_version.version_num = '28ce1e718f05';
+
+GO
+
 -- Running upgrade 189a235b3fd7 -> 2d078ec071b7
 
 ALTER TABLE ps_aors ALTER COLUMN contact VARCHAR(255);
 
 GO
 
-INSERT INTO alembic_version (version_num) VALUES ('2d078ec071b7');
+UPDATE alembic_version SET version_num='2d078ec071b7' WHERE alembic_version.version_num = '189a235b3fd7';
+
+GO
+
+-- Running upgrade 2d078ec071b7 -> 26d7f3bf0fa5
+
+ALTER TABLE ps_endpoints ADD bind_rtp_to_media_address VARCHAR(3) NULL;
+
+GO
+
+ALTER TABLE ps_endpoints ADD CONSTRAINT yesno_values CHECK (bind_rtp_to_media_address IN ('yes', 'no'));
+
+GO
+
+UPDATE alembic_version SET version_num='26d7f3bf0fa5' WHERE alembic_version.version_num = '2d078ec071b7';
+
+GO
+
+-- Running upgrade 26d7f3bf0fa5 -> 136885b81223
+
+ALTER TABLE ps_globals ADD regcontext VARCHAR(80) NULL;
+
+GO
+
+UPDATE alembic_version SET version_num='136885b81223' WHERE alembic_version.version_num = '26d7f3bf0fa5';
+
+GO
+
+-- Running upgrade 136885b81223 -> 423f34ad36e2
+
+ALTER TABLE ps_aors ALTER COLUMN qualify_timeout FLOAT;
+
+GO
+
+ALTER TABLE ps_contacts ALTER COLUMN qualify_timeout FLOAT;
+
+GO
+
+UPDATE alembic_version SET version_num='423f34ad36e2' WHERE alembic_version.version_num = '136885b81223';
+
+GO
+
+-- Running upgrade 423f34ad36e2 -> dbc44d5a908
+
+ALTER TABLE ps_systems ADD disable_tcp_switch VARCHAR(3) NULL;
+
+GO
+
+ALTER TABLE ps_systems ADD CONSTRAINT yesno_values CHECK (disable_tcp_switch IN ('yes', 'no'));
+
+GO
+
+ALTER TABLE ps_registrations ADD line VARCHAR(3) NULL;
+
+GO
+
+ALTER TABLE ps_registrations ADD CONSTRAINT yesno_values CHECK (line IN ('yes', 'no'));
+
+GO
+
+ALTER TABLE ps_registrations ADD endpoint VARCHAR(40) NULL;
+
+GO
+
+UPDATE alembic_version SET version_num='dbc44d5a908' WHERE alembic_version.version_num = '423f34ad36e2';
+
+GO
+
+-- Running upgrade dbc44d5a908 -> 3bcc0b5bc2c9
+
+ALTER TABLE ps_transports ADD allow_reload VARCHAR(3) NULL;
+
+GO
+
+ALTER TABLE ps_transports ADD CONSTRAINT yesno_values CHECK (allow_reload IN ('yes', 'no'));
+
+GO
+
+UPDATE alembic_version SET version_num='3bcc0b5bc2c9' WHERE alembic_version.version_num = 'dbc44d5a908';
 
 GO
 
 COMMIT;
 
+GO
+
diff --git a/contrib/realtime/mssql/mssql_voicemail.sql b/contrib/realtime/mssql/mssql_voicemail.sql
index 815d24e..75cf56a 100644
--- a/contrib/realtime/mssql/mssql_voicemail.sql
+++ b/contrib/realtime/mssql/mssql_voicemail.sql
@@ -6,7 +6,7 @@ CREATE TABLE alembic_version (
 
 GO
 
--- Running upgrade None -> a2e9769475e
+-- Running upgrade  -> a2e9769475e
 
 CREATE TABLE voicemail_messages (
     dir VARCHAR(255) NOT NULL, 
@@ -34,15 +34,21 @@ CREATE INDEX voicemail_messages_dir ON voicemail_messages (dir);
 
 GO
 
+INSERT INTO alembic_version (version_num) VALUES ('a2e9769475e');
+
+GO
+
 -- Running upgrade a2e9769475e -> 39428242f7f5
 
 ALTER TABLE voicemail_messages ALTER COLUMN recording IMAGE;
 
 GO
 
-INSERT INTO alembic_version (version_num) VALUES ('39428242f7f5');
+UPDATE alembic_version SET version_num='39428242f7f5' WHERE alembic_version.version_num = 'a2e9769475e';
 
 GO
 
 COMMIT;
 
+GO
+
diff --git a/contrib/realtime/mysql/mysql_cdr.sql b/contrib/realtime/mysql/mysql_cdr.sql
index 95d2282..972f69a 100644
--- a/contrib/realtime/mysql/mysql_cdr.sql
+++ b/contrib/realtime/mysql/mysql_cdr.sql
@@ -2,7 +2,7 @@ CREATE TABLE alembic_version (
     version_num VARCHAR(32) NOT NULL
 );
 
--- Running upgrade None -> 210693f3123d
+-- Running upgrade  -> 210693f3123d
 
 CREATE TABLE cdr (
     accountcode VARCHAR(20), 
diff --git a/contrib/realtime/mysql/mysql_config.sql b/contrib/realtime/mysql/mysql_config.sql
index 7204f06..b52cf94 100644
--- a/contrib/realtime/mysql/mysql_config.sql
+++ b/contrib/realtime/mysql/mysql_config.sql
@@ -2,7 +2,7 @@ CREATE TABLE alembic_version (
     version_num VARCHAR(32) NOT NULL
 );
 
--- Running upgrade None -> 4da0c5f79a9c
+-- Running upgrade  -> 4da0c5f79a9c
 
 CREATE TABLE sippeers (
     id INTEGER NOT NULL AUTO_INCREMENT, 
@@ -387,7 +387,7 @@ CREATE TABLE ps_endpoint_id_ips (
 
 CREATE INDEX ps_endpoint_id_ips_id ON ps_endpoint_id_ips (id);
 
-UPDATE alembic_version SET version_num='43956d550a44';
+UPDATE alembic_version SET version_num='43956d550a44' WHERE alembic_version.version_num = '4da0c5f79a9c';
 
 -- Running upgrade 43956d550a44 -> 581a4264e537
 
@@ -402,7 +402,7 @@ CREATE TABLE extensions (
     UNIQUE (id)
 );
 
-UPDATE alembic_version SET version_num='581a4264e537';
+UPDATE alembic_version SET version_num='581a4264e537' WHERE alembic_version.version_num = '43956d550a44';
 
 -- Running upgrade 581a4264e537 -> 2fc7930b41b3
 
@@ -493,13 +493,13 @@ ALTER TABLE ps_aors ADD COLUMN outbound_proxy VARCHAR(40);
 
 ALTER TABLE ps_aors ADD COLUMN support_path ENUM('yes','no');
 
-UPDATE alembic_version SET version_num='2fc7930b41b3';
+UPDATE alembic_version SET version_num='2fc7930b41b3' WHERE alembic_version.version_num = '581a4264e537';
 
 -- Running upgrade 2fc7930b41b3 -> 21e526ad3040
 
 ALTER TABLE ps_globals ADD COLUMN debug VARCHAR(40);
 
-UPDATE alembic_version SET version_num='21e526ad3040';
+UPDATE alembic_version SET version_num='21e526ad3040' WHERE alembic_version.version_num = '2fc7930b41b3';
 
 -- Running upgrade 21e526ad3040 -> 28887f25a46f
 
@@ -573,29 +573,29 @@ CREATE TABLE queue_members (
     PRIMARY KEY (queue_name, interface)
 );
 
-UPDATE alembic_version SET version_num='28887f25a46f';
+UPDATE alembic_version SET version_num='28887f25a46f' WHERE alembic_version.version_num = '21e526ad3040';
 
 -- Running upgrade 28887f25a46f -> 4c573e7135bd
 
-ALTER TABLE ps_endpoints CHANGE tos_audio tos_audio VARCHAR(10) NULL;
+ALTER TABLE ps_endpoints MODIFY tos_audio VARCHAR(10) NULL;
 
-ALTER TABLE ps_endpoints CHANGE tos_video tos_video VARCHAR(10) NULL;
-
-ALTER TABLE ps_transports CHANGE tos tos VARCHAR(10) NULL;
+ALTER TABLE ps_endpoints MODIFY tos_video VARCHAR(10) NULL;
 
 ALTER TABLE ps_endpoints DROP COLUMN cos_audio;
 
 ALTER TABLE ps_endpoints DROP COLUMN cos_video;
 
-ALTER TABLE ps_transports DROP COLUMN cos;
-
 ALTER TABLE ps_endpoints ADD COLUMN cos_audio INTEGER;
 
 ALTER TABLE ps_endpoints ADD COLUMN cos_video INTEGER;
 
+ALTER TABLE ps_transports MODIFY tos VARCHAR(10) NULL;
+
+ALTER TABLE ps_transports DROP COLUMN cos;
+
 ALTER TABLE ps_transports ADD COLUMN cos INTEGER;
 
-UPDATE alembic_version SET version_num='4c573e7135bd';
+UPDATE alembic_version SET version_num='4c573e7135bd' WHERE alembic_version.version_num = '28887f25a46f';
 
 -- Running upgrade 4c573e7135bd -> 3855ee4e5f85
 
@@ -603,23 +603,23 @@ ALTER TABLE ps_endpoints ADD COLUMN message_context VARCHAR(40);
 
 ALTER TABLE ps_contacts ADD COLUMN user_agent VARCHAR(40);
 
-UPDATE alembic_version SET version_num='3855ee4e5f85';
+UPDATE alembic_version SET version_num='3855ee4e5f85' WHERE alembic_version.version_num = '4c573e7135bd';
 
 -- Running upgrade 3855ee4e5f85 -> e96a0b8071c
 
-ALTER TABLE ps_globals CHANGE user_agent user_agent VARCHAR(255) NULL;
+ALTER TABLE ps_globals MODIFY user_agent VARCHAR(255) NULL;
 
-ALTER TABLE ps_contacts CHANGE id id VARCHAR(255) NULL;
+ALTER TABLE ps_contacts MODIFY id VARCHAR(255) NULL;
 
-ALTER TABLE ps_contacts CHANGE uri uri VARCHAR(255) NULL;
+ALTER TABLE ps_contacts MODIFY uri VARCHAR(255) NULL;
 
-ALTER TABLE ps_contacts CHANGE user_agent user_agent VARCHAR(255) NULL;
+ALTER TABLE ps_contacts MODIFY user_agent VARCHAR(255) NULL;
 
-ALTER TABLE ps_registrations CHANGE client_uri client_uri VARCHAR(255) NULL;
+ALTER TABLE ps_registrations MODIFY client_uri VARCHAR(255) NULL;
 
-ALTER TABLE ps_registrations CHANGE server_uri server_uri VARCHAR(255) NULL;
+ALTER TABLE ps_registrations MODIFY server_uri VARCHAR(255) NULL;
 
-UPDATE alembic_version SET version_num='e96a0b8071c';
+UPDATE alembic_version SET version_num='e96a0b8071c' WHERE alembic_version.version_num = '3855ee4e5f85';
 
 -- Running upgrade e96a0b8071c -> c6d929b23a8
 
@@ -640,7 +640,7 @@ CREATE TABLE ps_subscription_persistence (
 
 CREATE INDEX ps_subscription_persistence_id ON ps_subscription_persistence (id);
 
-UPDATE alembic_version SET version_num='c6d929b23a8';
+UPDATE alembic_version SET version_num='c6d929b23a8' WHERE alembic_version.version_num = 'e96a0b8071c';
 
 -- Running upgrade c6d929b23a8 -> 51f8cb66540e
 
@@ -648,19 +648,19 @@ ALTER TABLE ps_endpoints ADD COLUMN force_avp ENUM('yes','no');
 
 ALTER TABLE ps_endpoints ADD COLUMN media_use_received_transport ENUM('yes','no');
 
-UPDATE alembic_version SET version_num='51f8cb66540e';
+UPDATE alembic_version SET version_num='51f8cb66540e' WHERE alembic_version.version_num = 'c6d929b23a8';
 
 -- Running upgrade 51f8cb66540e -> 1d50859ed02e
 
 ALTER TABLE ps_endpoints ADD COLUMN accountcode VARCHAR(20);
 
-UPDATE alembic_version SET version_num='1d50859ed02e';
+UPDATE alembic_version SET version_num='1d50859ed02e' WHERE alembic_version.version_num = '51f8cb66540e';
 
 -- Running upgrade 1d50859ed02e -> 1758e8bbf6b
 
-ALTER TABLE sippeers CHANGE useragent useragent VARCHAR(255) NULL;
+ALTER TABLE sippeers MODIFY useragent VARCHAR(255) NULL;
 
-UPDATE alembic_version SET version_num='1758e8bbf6b';
+UPDATE alembic_version SET version_num='1758e8bbf6b' WHERE alembic_version.version_num = '1d50859ed02e';
 
 -- Running upgrade 1758e8bbf6b -> 5139253c0423
 
@@ -670,9 +670,9 @@ ALTER TABLE queue_members ADD COLUMN uniqueid INTEGER NOT NULL;
 
 ALTER TABLE queue_members ADD UNIQUE (uniqueid);
 
-ALTER TABLE queue_members CHANGE uniqueid uniqueid INTEGER NOT NULL AUTO_INCREMENT;
+ALTER TABLE queue_members MODIFY uniqueid INTEGER NOT NULL AUTO_INCREMENT;
 
-UPDATE alembic_version SET version_num='5139253c0423';
+UPDATE alembic_version SET version_num='5139253c0423' WHERE alembic_version.version_num = '1758e8bbf6b';
 
 -- Running upgrade 5139253c0423 -> d39508cb8d8
 
@@ -683,49 +683,49 @@ CREATE TABLE queue_rules (
     max_penalty VARCHAR(32) NOT NULL
 );
 
-UPDATE alembic_version SET version_num='d39508cb8d8';
+UPDATE alembic_version SET version_num='d39508cb8d8' WHERE alembic_version.version_num = '5139253c0423';
 
 -- Running upgrade d39508cb8d8 -> 5950038a6ead
 
 ALTER TABLE ps_transports CHANGE verifiy_server verify_server ENUM('yes','no') NULL;
 
-UPDATE alembic_version SET version_num='5950038a6ead';
+UPDATE alembic_version SET version_num='5950038a6ead' WHERE alembic_version.version_num = 'd39508cb8d8';
 
 -- Running upgrade 5950038a6ead -> 10aedae86a32
 
-ALTER TABLE sippeers CHANGE directmedia directmedia ENUM('yes','no','nonat','update','outgoing') NULL;
+ALTER TABLE sippeers MODIFY directmedia ENUM('yes','no','nonat','update','outgoing') NULL;
 
-UPDATE alembic_version SET version_num='10aedae86a32';
+UPDATE alembic_version SET version_num='10aedae86a32' WHERE alembic_version.version_num = '5950038a6ead';
 
 -- Running upgrade 10aedae86a32 -> eb88a14f2a
 
 ALTER TABLE ps_endpoints ADD COLUMN media_encryption_optimistic ENUM('yes','no');
 
-UPDATE alembic_version SET version_num='eb88a14f2a';
+UPDATE alembic_version SET version_num='eb88a14f2a' WHERE alembic_version.version_num = '10aedae86a32';
 
 -- Running upgrade eb88a14f2a -> 371a3bf4143e
 
 ALTER TABLE ps_endpoints ADD COLUMN user_eq_phone ENUM('yes','no');
 
-UPDATE alembic_version SET version_num='371a3bf4143e';
+UPDATE alembic_version SET version_num='371a3bf4143e' WHERE alembic_version.version_num = 'eb88a14f2a';
 
 -- Running upgrade 371a3bf4143e -> 45e3f47c6c44
 
 ALTER TABLE ps_globals ADD COLUMN endpoint_identifier_order VARCHAR(40);
 
-UPDATE alembic_version SET version_num='45e3f47c6c44';
+UPDATE alembic_version SET version_num='45e3f47c6c44' WHERE alembic_version.version_num = '371a3bf4143e';
 
 -- Running upgrade 45e3f47c6c44 -> 23530d604b96
 
 ALTER TABLE ps_endpoints ADD COLUMN rpid_immediate ENUM('yes','no');
 
-UPDATE alembic_version SET version_num='23530d604b96';
+UPDATE alembic_version SET version_num='23530d604b96' WHERE alembic_version.version_num = '45e3f47c6c44';
 
 -- Running upgrade 23530d604b96 -> 31cd4f4891ec
 
-ALTER TABLE ps_endpoints CHANGE dtmf_mode dtmf_mode ENUM('rfc4733','inband','info','auto') NULL;
+ALTER TABLE ps_endpoints MODIFY dtmf_mode ENUM('rfc4733','inband','info','auto') NULL;
 
-UPDATE alembic_version SET version_num='31cd4f4891ec';
+UPDATE alembic_version SET version_num='31cd4f4891ec' WHERE alembic_version.version_num = '23530d604b96';
 
 -- Running upgrade 31cd4f4891ec -> 461d7d691209
 
@@ -733,25 +733,25 @@ ALTER TABLE ps_aors ADD COLUMN qualify_timeout INTEGER;
 
 ALTER TABLE ps_contacts ADD COLUMN qualify_timeout INTEGER;
 
-UPDATE alembic_version SET version_num='461d7d691209';
+UPDATE alembic_version SET version_num='461d7d691209' WHERE alembic_version.version_num = '31cd4f4891ec';
 
 -- Running upgrade 461d7d691209 -> a541e0b5e89
 
 ALTER TABLE ps_globals ADD COLUMN max_initial_qualify_time INTEGER;
 
-UPDATE alembic_version SET version_num='a541e0b5e89';
+UPDATE alembic_version SET version_num='a541e0b5e89' WHERE alembic_version.version_num = '461d7d691209';
 
 -- Running upgrade a541e0b5e89 -> 28b8e71e541f
 
 ALTER TABLE ps_endpoints ADD COLUMN g726_non_standard ENUM('yes','no');
 
-UPDATE alembic_version SET version_num='28b8e71e541f';
+UPDATE alembic_version SET version_num='28b8e71e541f' WHERE alembic_version.version_num = 'a541e0b5e89';
 
 -- Running upgrade 28b8e71e541f -> 498357a710ae
 
 ALTER TABLE ps_endpoints ADD COLUMN rtp_keepalive INTEGER;
 
-UPDATE alembic_version SET version_num='498357a710ae';
+UPDATE alembic_version SET version_num='498357a710ae' WHERE alembic_version.version_num = '28b8e71e541f';
 
 -- Running upgrade 498357a710ae -> 26f10cadc157
 
@@ -759,29 +759,65 @@ ALTER TABLE ps_endpoints ADD COLUMN rtp_timeout INTEGER;
 
 ALTER TABLE ps_endpoints ADD COLUMN rtp_timeout_hold INTEGER;
 
-UPDATE alembic_version SET version_num='26f10cadc157';
+UPDATE alembic_version SET version_num='26f10cadc157' WHERE alembic_version.version_num = '498357a710ae';
 
 -- Running upgrade 26f10cadc157 -> 154177371065
 
 ALTER TABLE ps_globals ADD COLUMN default_from_user VARCHAR(80);
 
-UPDATE alembic_version SET version_num='154177371065';
+UPDATE alembic_version SET version_num='154177371065' WHERE alembic_version.version_num = '26f10cadc157';
 
 -- Running upgrade 154177371065 -> 28ce1e718f05
 
 ALTER TABLE ps_registrations ADD COLUMN fatal_retry_interval INTEGER;
 
-UPDATE alembic_version SET version_num='28ce1e718f05';
+UPDATE alembic_version SET version_num='28ce1e718f05' WHERE alembic_version.version_num = '154177371065';
 
 -- Running upgrade 28ce1e718f05 -> 189a235b3fd7
 
 ALTER TABLE ps_globals ADD COLUMN keep_alive_interval INTEGER;
 
-UPDATE alembic_version SET version_num='189a235b3fd7';
+UPDATE alembic_version SET version_num='189a235b3fd7' WHERE alembic_version.version_num = '28ce1e718f05';
 
 -- Running upgrade 189a235b3fd7 -> 2d078ec071b7
 
-ALTER TABLE ps_aors CHANGE contact contact VARCHAR(255) NULL;
+ALTER TABLE ps_aors MODIFY contact VARCHAR(255) NULL;
+
+UPDATE alembic_version SET version_num='2d078ec071b7' WHERE alembic_version.version_num = '189a235b3fd7';
+
+-- Running upgrade 2d078ec071b7 -> 26d7f3bf0fa5
+
+ALTER TABLE ps_endpoints ADD COLUMN bind_rtp_to_media_address ENUM('yes','no');
+
+UPDATE alembic_version SET version_num='26d7f3bf0fa5' WHERE alembic_version.version_num = '2d078ec071b7';
+
+-- Running upgrade 26d7f3bf0fa5 -> 136885b81223
+
+ALTER TABLE ps_globals ADD COLUMN regcontext VARCHAR(80);
+
+UPDATE alembic_version SET version_num='136885b81223' WHERE alembic_version.version_num = '26d7f3bf0fa5';
+
+-- Running upgrade 136885b81223 -> 423f34ad36e2
+
+ALTER TABLE ps_aors MODIFY qualify_timeout FLOAT NULL;
+
+ALTER TABLE ps_contacts MODIFY qualify_timeout FLOAT NULL;
+
+UPDATE alembic_version SET version_num='423f34ad36e2' WHERE alembic_version.version_num = '136885b81223';
+
+-- Running upgrade 423f34ad36e2 -> dbc44d5a908
+
+ALTER TABLE ps_systems ADD COLUMN disable_tcp_switch ENUM('yes','no');
+
+ALTER TABLE ps_registrations ADD COLUMN line ENUM('yes','no');
+
+ALTER TABLE ps_registrations ADD COLUMN endpoint VARCHAR(40);
+
+UPDATE alembic_version SET version_num='dbc44d5a908' WHERE alembic_version.version_num = '423f34ad36e2';
+
+-- Running upgrade dbc44d5a908 -> 3bcc0b5bc2c9
+
+ALTER TABLE ps_transports ADD COLUMN allow_reload ENUM('yes','no');
 
-UPDATE alembic_version SET version_num='2d078ec071b7';
+UPDATE alembic_version SET version_num='3bcc0b5bc2c9' WHERE alembic_version.version_num = 'dbc44d5a908';
 
diff --git a/contrib/realtime/mysql/mysql_voicemail.sql b/contrib/realtime/mysql/mysql_voicemail.sql
index ff5b620..1ee25c2 100644
--- a/contrib/realtime/mysql/mysql_voicemail.sql
+++ b/contrib/realtime/mysql/mysql_voicemail.sql
@@ -2,7 +2,7 @@ CREATE TABLE alembic_version (
     version_num VARCHAR(32) NOT NULL
 );
 
--- Running upgrade None -> a2e9769475e
+-- Running upgrade  -> a2e9769475e
 
 CREATE TABLE voicemail_messages (
     dir VARCHAR(255) NOT NULL, 
@@ -28,7 +28,7 @@ INSERT INTO alembic_version (version_num) VALUES ('a2e9769475e');
 
 -- Running upgrade a2e9769475e -> 39428242f7f5
 
-ALTER TABLE voicemail_messages CHANGE recording recording BLOB(4294967295) NULL;
+ALTER TABLE voicemail_messages MODIFY recording BLOB(4294967295) NULL;
 
-UPDATE alembic_version SET version_num='39428242f7f5';
+UPDATE alembic_version SET version_num='39428242f7f5' WHERE alembic_version.version_num = 'a2e9769475e';
 
diff --git a/contrib/realtime/oracle/oracle_cdr.sql b/contrib/realtime/oracle/oracle_cdr.sql
index 66302fc..81d599a 100644
--- a/contrib/realtime/oracle/oracle_cdr.sql
+++ b/contrib/realtime/oracle/oracle_cdr.sql
@@ -1,14 +1,10 @@
-SET TRANSACTION READ WRITE
-
-/
-
 CREATE TABLE alembic_version (
     version_num VARCHAR2(32 CHAR) NOT NULL
 )
 
 /
 
--- Running upgrade None -> 210693f3123d
+-- Running upgrade  -> 210693f3123d
 
 CREATE TABLE cdr (
     accountcode VARCHAR2(20 CHAR), 
@@ -40,7 +36,3 @@ INSERT INTO alembic_version (version_num) VALUES ('210693f3123d')
 
 /
 
-COMMIT
-
-/
-
diff --git a/contrib/realtime/oracle/oracle_config.sql b/contrib/realtime/oracle/oracle_config.sql
index f179ca6..5e89c15 100644
--- a/contrib/realtime/oracle/oracle_config.sql
+++ b/contrib/realtime/oracle/oracle_config.sql
@@ -1,14 +1,10 @@
-SET TRANSACTION READ WRITE
-
-/
-
 CREATE TABLE alembic_version (
     version_num VARCHAR2(32 CHAR) NOT NULL
 )
 
 /
 
--- Running upgrade None -> 4da0c5f79a9c
+-- Running upgrade  -> 4da0c5f79a9c
 
 CREATE TABLE sippeers (
     id INTEGER NOT NULL, 
@@ -338,6 +334,10 @@ CREATE TABLE musiconhold (
 
 /
 
+INSERT INTO alembic_version (version_num) VALUES ('4da0c5f79a9c')
+
+/
+
 -- Running upgrade 4da0c5f79a9c -> 43956d550a44
 
 CREATE TABLE ps_endpoints (
@@ -374,7 +374,7 @@ CREATE TABLE ps_endpoints (
     callerid VARCHAR2(40 CHAR), 
     callerid_privacy VARCHAR(23 CHAR), 
     callerid_tag VARCHAR2(40 CHAR), 
-    100rel VARCHAR(8 CHAR), 
+    "100rel" VARCHAR(8 CHAR), 
     aggregate_mwi VARCHAR(3 CHAR), 
     trust_id_inbound VARCHAR(3 CHAR), 
     trust_id_outbound VARCHAR(3 CHAR), 
@@ -438,7 +438,7 @@ CREATE TABLE ps_endpoints (
     CONSTRAINT yesno_values CHECK (send_rpid IN ('yes', 'no')), 
     CONSTRAINT pjsip_timer_values CHECK (timers IN ('forced', 'no', 'required', 'yes')), 
     CONSTRAINT pjsip_cid_privacy_values CHECK (callerid_privacy IN ('allowed_not_screened', 'allowed_passed_screened', 'allowed_failed_screened', 'allowed', 'prohib_not_screened', 'prohib_passed_screened', 'prohib_failed_screened', 'prohib', 'unavailable')), 
-    CONSTRAINT pjsip_100rel_values CHECK (100rel IN ('no', 'required', 'yes')), 
+    CONSTRAINT pjsip_100rel_values CHECK ("100rel" IN ('no', 'required', 'yes')), 
     CONSTRAINT yesno_values CHECK (aggregate_mwi IN ('yes', 'no')), 
     CONSTRAINT yesno_values CHECK (trust_id_inbound IN ('yes', 'no')), 
     CONSTRAINT yesno_values CHECK (trust_id_outbound IN ('yes', 'no')), 
@@ -542,6 +542,10 @@ CREATE INDEX ps_endpoint_id_ips_id ON ps_endpoint_id_ips (id)
 
 /
 
+UPDATE alembic_version SET version_num='43956d550a44' WHERE alembic_version.version_num = '4da0c5f79a9c'
+
+/
+
 -- Running upgrade 43956d550a44 -> 581a4264e537
 
 CREATE TABLE extensions (
@@ -557,6 +561,10 @@ CREATE TABLE extensions (
 
 /
 
+UPDATE alembic_version SET version_num='581a4264e537' WHERE alembic_version.version_num = '43956d550a44'
+
+/
+
 -- Running upgrade 581a4264e537 -> 2fc7930b41b3
 
 CREATE TABLE ps_systems (
@@ -698,12 +706,20 @@ ALTER TABLE ps_aors ADD CONSTRAINT yesno_values CHECK (support_path IN ('yes', '
 
 /
 
+UPDATE alembic_version SET version_num='2fc7930b41b3' WHERE alembic_version.version_num = '581a4264e537'
+
+/
+
 -- Running upgrade 2fc7930b41b3 -> 21e526ad3040
 
 ALTER TABLE ps_globals ADD debug VARCHAR2(40 CHAR)
 
 /
 
+UPDATE alembic_version SET version_num='21e526ad3040' WHERE alembic_version.version_num = '2fc7930b41b3'
+
+/
+
 -- Running upgrade 21e526ad3040 -> 28887f25a46f
 
 CREATE TABLE queues (
@@ -794,6 +810,10 @@ CREATE TABLE queue_members (
 
 /
 
+UPDATE alembic_version SET version_num='28887f25a46f' WHERE alembic_version.version_num = '21e526ad3040'
+
+/
+
 -- Running upgrade 28887f25a46f -> 4c573e7135bd
 
 ALTER TABLE ps_endpoints MODIFY tos_audio VARCHAR2(10 CHAR)
@@ -804,27 +824,27 @@ ALTER TABLE ps_endpoints MODIFY tos_video VARCHAR2(10 CHAR)
 
 /
 
-ALTER TABLE ps_transports MODIFY tos VARCHAR2(10 CHAR)
+ALTER TABLE ps_endpoints DROP COLUMN cos_audio
 
 /
 
-ALTER TABLE ps_endpoints DROP COLUMN cos_audio
+ALTER TABLE ps_endpoints DROP COLUMN cos_video
 
 /
 
-ALTER TABLE ps_endpoints DROP COLUMN cos_video
+ALTER TABLE ps_endpoints ADD cos_audio INTEGER
 
 /
 
-ALTER TABLE ps_transports DROP COLUMN cos
+ALTER TABLE ps_endpoints ADD cos_video INTEGER
 
 /
 
-ALTER TABLE ps_endpoints ADD cos_audio INTEGER
+ALTER TABLE ps_transports MODIFY tos VARCHAR2(10 CHAR)
 
 /
 
-ALTER TABLE ps_endpoints ADD cos_video INTEGER
+ALTER TABLE ps_transports DROP COLUMN cos
 
 /
 
@@ -832,6 +852,10 @@ ALTER TABLE ps_transports ADD cos INTEGER
 
 /
 
+UPDATE alembic_version SET version_num='4c573e7135bd' WHERE alembic_version.version_num = '28887f25a46f'
+
+/
+
 -- Running upgrade 4c573e7135bd -> 3855ee4e5f85
 
 ALTER TABLE ps_endpoints ADD message_context VARCHAR2(40 CHAR)
@@ -842,6 +866,10 @@ ALTER TABLE ps_contacts ADD user_agent VARCHAR2(40 CHAR)
 
 /
 
+UPDATE alembic_version SET version_num='3855ee4e5f85' WHERE alembic_version.version_num = '4c573e7135bd'
+
+/
+
 -- Running upgrade 3855ee4e5f85 -> e96a0b8071c
 
 ALTER TABLE ps_globals MODIFY user_agent VARCHAR2(255 CHAR)
@@ -868,6 +896,10 @@ ALTER TABLE ps_registrations MODIFY server_uri VARCHAR2(255 CHAR)
 
 /
 
+UPDATE alembic_version SET version_num='e96a0b8071c' WHERE alembic_version.version_num = '3855ee4e5f85'
+
+/
+
 -- Running upgrade e96a0b8071c -> c6d929b23a8
 
 CREATE TABLE ps_subscription_persistence (
@@ -891,6 +923,10 @@ CREATE INDEX ps_subscription_persistence_id ON ps_subscription_persistence (id)
 
 /
 
+UPDATE alembic_version SET version_num='c6d929b23a8' WHERE alembic_version.version_num = 'e96a0b8071c'
+
+/
+
 -- Running upgrade c6d929b23a8 -> 51f8cb66540e
 
 ALTER TABLE ps_endpoints ADD force_avp VARCHAR(3 CHAR)
@@ -909,18 +945,30 @@ ALTER TABLE ps_endpoints ADD CONSTRAINT yesno_values CHECK (media_use_received_t
 
 /
 
+UPDATE alembic_version SET version_num='51f8cb66540e' WHERE alembic_version.version_num = 'c6d929b23a8'
+
+/
+
 -- Running upgrade 51f8cb66540e -> 1d50859ed02e
 
 ALTER TABLE ps_endpoints ADD accountcode VARCHAR2(20 CHAR)
 
 /
 
+UPDATE alembic_version SET version_num='1d50859ed02e' WHERE alembic_version.version_num = '51f8cb66540e'
+
+/
+
 -- Running upgrade 1d50859ed02e -> 1758e8bbf6b
 
 ALTER TABLE sippeers MODIFY useragent VARCHAR2(255 CHAR)
 
 /
 
+UPDATE alembic_version SET version_num='1758e8bbf6b' WHERE alembic_version.version_num = '1d50859ed02e'
+
+/
+
 -- Running upgrade 1758e8bbf6b -> 5139253c0423
 
 ALTER TABLE queue_members DROP COLUMN uniqueid
@@ -935,6 +983,10 @@ ALTER TABLE queue_members ADD UNIQUE (uniqueid)
 
 /
 
+UPDATE alembic_version SET version_num='5139253c0423' WHERE alembic_version.version_num = '1758e8bbf6b'
+
+/
+
 -- Running upgrade 5139253c0423 -> d39508cb8d8
 
 CREATE TABLE queue_rules (
@@ -946,6 +998,10 @@ CREATE TABLE queue_rules (
 
 /
 
+UPDATE alembic_version SET version_num='d39508cb8d8' WHERE alembic_version.version_num = '5139253c0423'
+
+/
+
 -- Running upgrade d39508cb8d8 -> 5950038a6ead
 
 ALTER TABLE ps_transports MODIFY verifiy_server VARCHAR(3 CHAR)
@@ -960,6 +1016,10 @@ ALTER TABLE ps_transports ADD CONSTRAINT yesno_values CHECK (verifiy_server IN (
 
 /
 
+UPDATE alembic_version SET version_num='5950038a6ead' WHERE alembic_version.version_num = 'd39508cb8d8'
+
+/
+
 -- Running upgrade 5950038a6ead -> 10aedae86a32
 
 ALTER TABLE sippeers DROP CONSTRAINT sip_directmedia_values
@@ -974,6 +1034,10 @@ ALTER TABLE sippeers ADD CONSTRAINT sip_directmedia_values_v2 CHECK (directmedia
 
 /
 
+UPDATE alembic_version SET version_num='10aedae86a32' WHERE alembic_version.version_num = '5950038a6ead'
+
+/
+
 -- Running upgrade 10aedae86a32 -> eb88a14f2a
 
 ALTER TABLE ps_endpoints ADD media_encryption_optimistic VARCHAR(3 CHAR)
@@ -984,6 +1048,10 @@ ALTER TABLE ps_endpoints ADD CONSTRAINT yesno_values CHECK (media_encryption_opt
 
 /
 
+UPDATE alembic_version SET version_num='eb88a14f2a' WHERE alembic_version.version_num = '10aedae86a32'
+
+/
+
 -- Running upgrade eb88a14f2a -> 371a3bf4143e
 
 ALTER TABLE ps_endpoints ADD user_eq_phone VARCHAR(3 CHAR)
@@ -994,12 +1062,20 @@ ALTER TABLE ps_endpoints ADD CONSTRAINT yesno_values CHECK (user_eq_phone IN ('y
 
 /
 
+UPDATE alembic_version SET version_num='371a3bf4143e' WHERE alembic_version.version_num = 'eb88a14f2a'
+
+/
+
 -- Running upgrade 371a3bf4143e -> 45e3f47c6c44
 
 ALTER TABLE ps_globals ADD endpoint_identifier_order VARCHAR2(40 CHAR)
 
 /
 
+UPDATE alembic_version SET version_num='45e3f47c6c44' WHERE alembic_version.version_num = '371a3bf4143e'
+
+/
+
 -- Running upgrade 45e3f47c6c44 -> 23530d604b96
 
 ALTER TABLE ps_endpoints ADD rpid_immediate VARCHAR(3 CHAR)
@@ -1010,6 +1086,10 @@ ALTER TABLE ps_endpoints ADD CONSTRAINT yesno_values CHECK (rpid_immediate IN ('
 
 /
 
+UPDATE alembic_version SET version_num='23530d604b96' WHERE alembic_version.version_num = '45e3f47c6c44'
+
+/
+
 -- Running upgrade 23530d604b96 -> 31cd4f4891ec
 
 ALTER TABLE ps_endpoints DROP CONSTRAINT pjsip_dtmf_mode_values
@@ -1024,6 +1104,10 @@ ALTER TABLE ps_endpoints ADD CONSTRAINT pjsip_dtmf_mode_values_v2 CHECK (dtmf_mo
 
 /
 
+UPDATE alembic_version SET version_num='31cd4f4891ec' WHERE alembic_version.version_num = '23530d604b96'
+
+/
+
 -- Running upgrade 31cd4f4891ec -> 461d7d691209
 
 ALTER TABLE ps_aors ADD qualify_timeout INTEGER
@@ -1034,12 +1118,20 @@ ALTER TABLE ps_contacts ADD qualify_timeout INTEGER
 
 /
 
+UPDATE alembic_version SET version_num='461d7d691209' WHERE alembic_version.version_num = '31cd4f4891ec'
+
+/
+
 -- Running upgrade 461d7d691209 -> a541e0b5e89
 
 ALTER TABLE ps_globals ADD max_initial_qualify_time INTEGER
 
 /
 
+UPDATE alembic_version SET version_num='a541e0b5e89' WHERE alembic_version.version_num = '461d7d691209'
+
+/
+
 -- Running upgrade a541e0b5e89 -> 28b8e71e541f
 
 ALTER TABLE ps_endpoints ADD g726_non_standard VARCHAR(3 CHAR)
@@ -1050,12 +1142,20 @@ ALTER TABLE ps_endpoints ADD CONSTRAINT yesno_values CHECK (g726_non_standard IN
 
 /
 
+UPDATE alembic_version SET version_num='28b8e71e541f' WHERE alembic_version.version_num = 'a541e0b5e89'
+
+/
+
 -- Running upgrade 28b8e71e541f -> 498357a710ae
 
 ALTER TABLE ps_endpoints ADD rtp_keepalive INTEGER
 
 /
 
+UPDATE alembic_version SET version_num='498357a710ae' WHERE alembic_version.version_num = '28b8e71e541f'
+
+/
+
 -- Running upgrade 498357a710ae -> 26f10cadc157
 
 ALTER TABLE ps_endpoints ADD rtp_timeout INTEGER
@@ -1066,35 +1166,125 @@ ALTER TABLE ps_endpoints ADD rtp_timeout_hold INTEGER
 
 /
 
+UPDATE alembic_version SET version_num='26f10cadc157' WHERE alembic_version.version_num = '498357a710ae'
+
+/
+
 -- Running upgrade 26f10cadc157 -> 154177371065
 
 ALTER TABLE ps_globals ADD default_from_user VARCHAR2(80 CHAR)
 
 /
 
+UPDATE alembic_version SET version_num='154177371065' WHERE alembic_version.version_num = '26f10cadc157'
+
+/
+
 -- Running upgrade 154177371065 -> 28ce1e718f05
 
 ALTER TABLE ps_registrations ADD fatal_retry_interval INTEGER
 
 /
 
+UPDATE alembic_version SET version_num='28ce1e718f05' WHERE alembic_version.version_num = '154177371065'
+
+/
+
 -- Running upgrade 28ce1e718f05 -> 189a235b3fd7
 
 ALTER TABLE ps_globals ADD keep_alive_interval INTEGER
 
 /
 
+UPDATE alembic_version SET version_num='189a235b3fd7' WHERE alembic_version.version_num = '28ce1e718f05'
+
+/
+
 -- Running upgrade 189a235b3fd7 -> 2d078ec071b7
 
 ALTER TABLE ps_aors MODIFY contact VARCHAR2(255 CHAR)
 
 /
 
-INSERT INTO alembic_version (version_num) VALUES ('2d078ec071b7')
+UPDATE alembic_version SET version_num='2d078ec071b7' WHERE alembic_version.version_num = '189a235b3fd7'
+
+/
+
+-- Running upgrade 2d078ec071b7 -> 26d7f3bf0fa5
+
+ALTER TABLE ps_endpoints ADD bind_rtp_to_media_address VARCHAR(3 CHAR)
+
+/
+
+ALTER TABLE ps_endpoints ADD CONSTRAINT yesno_values CHECK (bind_rtp_to_media_address IN ('yes', 'no'))
+
+/
+
+UPDATE alembic_version SET version_num='26d7f3bf0fa5' WHERE alembic_version.version_num = '2d078ec071b7'
+
+/
+
+-- Running upgrade 26d7f3bf0fa5 -> 136885b81223
+
+ALTER TABLE ps_globals ADD regcontext VARCHAR2(80 CHAR)
+
+/
+
+UPDATE alembic_version SET version_num='136885b81223' WHERE alembic_version.version_num = '26d7f3bf0fa5'
+
+/
+
+-- Running upgrade 136885b81223 -> 423f34ad36e2
+
+ALTER TABLE ps_aors MODIFY qualify_timeout FLOAT
+
+/
+
+ALTER TABLE ps_contacts MODIFY qualify_timeout FLOAT
+
+/
+
+UPDATE alembic_version SET version_num='423f34ad36e2' WHERE alembic_version.version_num = '136885b81223'
+
+/
+
+-- Running upgrade 423f34ad36e2 -> dbc44d5a908
+
+ALTER TABLE ps_systems ADD disable_tcp_switch VARCHAR(3 CHAR)
+
+/
+
+ALTER TABLE ps_systems ADD CONSTRAINT yesno_values CHECK (disable_tcp_switch IN ('yes', 'no'))
+
+/
+
+ALTER TABLE ps_registrations ADD line VARCHAR(3 CHAR)
+
+/
+
+ALTER TABLE ps_registrations ADD CONSTRAINT yesno_values CHECK (line IN ('yes', 'no'))
+
+/
+
+ALTER TABLE ps_registrations ADD endpoint VARCHAR2(40 CHAR)
+
+/
+
+UPDATE alembic_version SET version_num='dbc44d5a908' WHERE alembic_version.version_num = '423f34ad36e2'
+
+/
+
+-- Running upgrade dbc44d5a908 -> 3bcc0b5bc2c9
+
+ALTER TABLE ps_transports ADD allow_reload VARCHAR(3 CHAR)
+
+/
+
+ALTER TABLE ps_transports ADD CONSTRAINT yesno_values CHECK (allow_reload IN ('yes', 'no'))
 
 /
 
-COMMIT
+UPDATE alembic_version SET version_num='3bcc0b5bc2c9' WHERE alembic_version.version_num = 'dbc44d5a908'
 
 /
 
diff --git a/contrib/realtime/oracle/oracle_voicemail.sql b/contrib/realtime/oracle/oracle_voicemail.sql
index 2c663e7..12133ea 100644
--- a/contrib/realtime/oracle/oracle_voicemail.sql
+++ b/contrib/realtime/oracle/oracle_voicemail.sql
@@ -1,14 +1,10 @@
-SET TRANSACTION READ WRITE
-
-/
-
 CREATE TABLE alembic_version (
     version_num VARCHAR2(32 CHAR) NOT NULL
 )
 
 /
 
--- Running upgrade None -> a2e9769475e
+-- Running upgrade  -> a2e9769475e
 
 CREATE TABLE voicemail_messages (
     dir VARCHAR2(255 CHAR) NOT NULL, 
@@ -36,17 +32,17 @@ CREATE INDEX voicemail_messages_dir ON voicemail_messages (dir)
 
 /
 
--- Running upgrade a2e9769475e -> 39428242f7f5
-
-ALTER TABLE voicemail_messages MODIFY recording BLOB
+INSERT INTO alembic_version (version_num) VALUES ('a2e9769475e')
 
 /
 
-INSERT INTO alembic_version (version_num) VALUES ('39428242f7f5')
+-- Running upgrade a2e9769475e -> 39428242f7f5
+
+ALTER TABLE voicemail_messages MODIFY recording BLOB
 
 /
 
-COMMIT
+UPDATE alembic_version SET version_num='39428242f7f5' WHERE alembic_version.version_num = 'a2e9769475e'
 
 /
 
diff --git a/contrib/realtime/postgresql/postgresql_cdr.sql b/contrib/realtime/postgresql/postgresql_cdr.sql
index 8aa1d97..5f1ffa4 100644
--- a/contrib/realtime/postgresql/postgresql_cdr.sql
+++ b/contrib/realtime/postgresql/postgresql_cdr.sql
@@ -4,7 +4,7 @@ CREATE TABLE alembic_version (
     version_num VARCHAR(32) NOT NULL
 );
 
--- Running upgrade None -> 210693f3123d
+-- Running upgrade  -> 210693f3123d
 
 CREATE TABLE cdr (
     accountcode VARCHAR(20), 
diff --git a/contrib/realtime/postgresql/postgresql_config.sql b/contrib/realtime/postgresql/postgresql_config.sql
index 344b2a3..813fa14 100644
--- a/contrib/realtime/postgresql/postgresql_config.sql
+++ b/contrib/realtime/postgresql/postgresql_config.sql
@@ -4,25 +4,25 @@ CREATE TABLE alembic_version (
     version_num VARCHAR(32) NOT NULL
 );
 
--- Running upgrade None -> 4da0c5f79a9c
+-- Running upgrade  -> 4da0c5f79a9c
 
-CREATE TYPE type_values AS ENUM ('friend','user','peer');
+CREATE TYPE type_values AS ENUM ('friend', 'user', 'peer');
 
-CREATE TYPE sip_transport_values AS ENUM ('udp','tcp','tls','ws','wss','udp,tcp','tcp,udp');
+CREATE TYPE sip_transport_values AS ENUM ('udp', 'tcp', 'tls', 'ws', 'wss', 'udp,tcp', 'tcp,udp');
 
-CREATE TYPE sip_dtmfmode_values AS ENUM ('rfc2833','info','shortinfo','inband','auto');
+CREATE TYPE sip_dtmfmode_values AS ENUM ('rfc2833', 'info', 'shortinfo', 'inband', 'auto');
 
-CREATE TYPE sip_directmedia_values AS ENUM ('yes','no','nonat','update');
+CREATE TYPE sip_directmedia_values AS ENUM ('yes', 'no', 'nonat', 'update');
 
-CREATE TYPE yes_no_values AS ENUM ('yes','no');
+CREATE TYPE yes_no_values AS ENUM ('yes', 'no');
 
-CREATE TYPE sip_progressinband_values AS ENUM ('yes','no','never');
+CREATE TYPE sip_progressinband_values AS ENUM ('yes', 'no', 'never');
 
-CREATE TYPE sip_session_timers_values AS ENUM ('accept','refuse','originate');
+CREATE TYPE sip_session_timers_values AS ENUM ('accept', 'refuse', 'originate');
 
-CREATE TYPE sip_session_refresher_values AS ENUM ('uac','uas');
+CREATE TYPE sip_session_refresher_values AS ENUM ('uac', 'uas');
 
-CREATE TYPE sip_callingpres_values AS ENUM ('allowed_not_screened','allowed_passed_screen','allowed_failed_screen','allowed','prohib_not_screened','prohib_passed_screen','prohib_failed_screen','prohib');
+CREATE TYPE sip_callingpres_values AS ENUM ('allowed_not_screened', 'allowed_passed_screen', 'allowed_failed_screen', 'allowed', 'prohib_not_screened', 'prohib_passed_screen', 'prohib_failed_screen', 'prohib');
 
 CREATE TABLE sippeers (
     id SERIAL NOT NULL, 
@@ -126,11 +126,11 @@ CREATE INDEX sippeers_ipaddr_port ON sippeers (ipaddr, port);
 
 CREATE INDEX sippeers_host_port ON sippeers (host, port);
 
-CREATE TYPE iax_requirecalltoken_values AS ENUM ('yes','no','auto');
+CREATE TYPE iax_requirecalltoken_values AS ENUM ('yes', 'no', 'auto');
 
-CREATE TYPE iax_encryption_values AS ENUM ('yes','no','aes128');
+CREATE TYPE iax_encryption_values AS ENUM ('yes', 'no', 'aes128');
 
-CREATE TYPE iax_transfer_values AS ENUM ('yes','no','mediaonly');
+CREATE TYPE iax_transfer_values AS ENUM ('yes', 'no', 'mediaonly');
 
 CREATE TABLE iaxfriends (
     id SERIAL NOT NULL, 
@@ -257,7 +257,7 @@ CREATE TABLE meetme (
 
 CREATE INDEX meetme_confno_start_end ON meetme (confno, starttime, endtime);
 
-CREATE TYPE moh_mode_values AS ENUM ('custom','files','mp3nb','quietmp3nb','quietmp3');
+CREATE TYPE moh_mode_values AS ENUM ('custom', 'files', 'mp3nb', 'quietmp3nb', 'quietmp3');
 
 CREATE TABLE musiconhold (
     name VARCHAR(80) NOT NULL, 
@@ -271,29 +271,31 @@ CREATE TABLE musiconhold (
     PRIMARY KEY (name)
 );
 
+INSERT INTO alembic_version (version_num) VALUES ('4da0c5f79a9c');
+
 -- Running upgrade 4da0c5f79a9c -> 43956d550a44
 
-CREATE TYPE yesno_values AS ENUM ('yes','no');
+CREATE TYPE yesno_values AS ENUM ('yes', 'no');
 
-CREATE TYPE pjsip_connected_line_method_values AS ENUM ('invite','reinvite','update');
+CREATE TYPE pjsip_connected_line_method_values AS ENUM ('invite', 'reinvite', 'update');
 
-CREATE TYPE pjsip_direct_media_glare_mitigation_values AS ENUM ('none','outgoing','incoming');
+CREATE TYPE pjsip_direct_media_glare_mitigation_values AS ENUM ('none', 'outgoing', 'incoming');
 
-CREATE TYPE pjsip_dtmf_mode_values AS ENUM ('rfc4733','inband','info');
+CREATE TYPE pjsip_dtmf_mode_values AS ENUM ('rfc4733', 'inband', 'info');
 
 CREATE TYPE pjsip_identify_by_values AS ENUM ('username');
 
-CREATE TYPE pjsip_timer_values AS ENUM ('forced','no','required','yes');
+CREATE TYPE pjsip_timer_values AS ENUM ('forced', 'no', 'required', 'yes');
 
-CREATE TYPE pjsip_cid_privacy_values AS ENUM ('allowed_not_screened','allowed_passed_screened','allowed_failed_screened','allowed','prohib_not_screened','prohib_passed_screened','prohib_failed_screened','prohib','unavailable');
+CREATE TYPE pjsip_cid_privacy_values AS ENUM ('allowed_not_screened', 'allowed_passed_screened', 'allowed_failed_screened', 'allowed', 'prohib_not_screened', 'prohib_passed_screened', 'prohib_failed_screened', 'prohib', 'unavailable');
 
-CREATE TYPE pjsip_100rel_values AS ENUM ('no','required','yes');
+CREATE TYPE pjsip_100rel_values AS ENUM ('no', 'required', 'yes');
 
-CREATE TYPE pjsip_media_encryption_values AS ENUM ('no','sdes','dtls');
+CREATE TYPE pjsip_media_encryption_values AS ENUM ('no', 'sdes', 'dtls');
 
-CREATE TYPE pjsip_t38udptl_ec_values AS ENUM ('none','fec','redundancy');
+CREATE TYPE pjsip_t38udptl_ec_values AS ENUM ('none', 'fec', 'redundancy');
 
-CREATE TYPE pjsip_dtls_setup_values AS ENUM ('active','passive','actpass');
+CREATE TYPE pjsip_dtls_setup_values AS ENUM ('active', 'passive', 'actpass');
 
 CREATE TABLE ps_endpoints (
     id VARCHAR(40) NOT NULL, 
@@ -380,7 +382,7 @@ CREATE TABLE ps_endpoints (
 
 CREATE INDEX ps_endpoints_id ON ps_endpoints (id);
 
-CREATE TYPE pjsip_auth_type_values AS ENUM ('md5','userpass');
+CREATE TYPE pjsip_auth_type_values AS ENUM ('md5', 'userpass');
 
 CREATE TABLE ps_auths (
     id VARCHAR(40) NOT NULL, 
@@ -437,6 +439,8 @@ CREATE TABLE ps_endpoint_id_ips (
 
 CREATE INDEX ps_endpoint_id_ips_id ON ps_endpoint_id_ips (id);
 
+UPDATE alembic_version SET version_num='43956d550a44' WHERE alembic_version.version_num = '4da0c5f79a9c';
+
 -- Running upgrade 43956d550a44 -> 581a4264e537
 
 CREATE TABLE extensions (
@@ -450,9 +454,11 @@ CREATE TABLE extensions (
     UNIQUE (id)
 );
 
+UPDATE alembic_version SET version_num='581a4264e537' WHERE alembic_version.version_num = '43956d550a44';
+
 -- Running upgrade 581a4264e537 -> 2fc7930b41b3
 
-CREATE TYPE pjsip_redirect_method_values AS ENUM ('user','uri_core','uri_pjsip');
+CREATE TYPE pjsip_redirect_method_values AS ENUM ('user', 'uri_core', 'uri_pjsip');
 
 CREATE TABLE ps_systems (
     id VARCHAR(40) NOT NULL, 
@@ -478,9 +484,9 @@ CREATE TABLE ps_globals (
 
 CREATE INDEX ps_globals_id ON ps_globals (id);
 
-CREATE TYPE pjsip_transport_method_values AS ENUM ('default','unspecified','tlsv1','sslv2','sslv3','sslv23');
+CREATE TYPE pjsip_transport_method_values AS ENUM ('default', 'unspecified', 'tlsv1', 'sslv2', 'sslv3', 'sslv23');
 
-CREATE TYPE pjsip_transport_protocol_values AS ENUM ('udp','tcp','tls','ws','wss');
+CREATE TYPE pjsip_transport_protocol_values AS ENUM ('udp', 'tcp', 'tls', 'ws', 'wss');
 
 CREATE TABLE ps_transports (
     id VARCHAR(40) NOT NULL, 
@@ -545,15 +551,19 @@ ALTER TABLE ps_aors ADD COLUMN outbound_proxy VARCHAR(40);
 
 ALTER TABLE ps_aors ADD COLUMN support_path yesno_values;
 
+UPDATE alembic_version SET version_num='2fc7930b41b3' WHERE alembic_version.version_num = '581a4264e537';
+
 -- Running upgrade 2fc7930b41b3 -> 21e526ad3040
 
 ALTER TABLE ps_globals ADD COLUMN debug VARCHAR(40);
 
+UPDATE alembic_version SET version_num='21e526ad3040' WHERE alembic_version.version_num = '2fc7930b41b3';
+
 -- Running upgrade 21e526ad3040 -> 28887f25a46f
 
-CREATE TYPE queue_autopause_values AS ENUM ('yes','no','all');
+CREATE TYPE queue_autopause_values AS ENUM ('yes', 'no', 'all');
 
-CREATE TYPE queue_strategy_values AS ENUM ('ringall','leastrecent','fewestcalls','random','rrmemory','linear','wrandom','rrordered');
+CREATE TYPE queue_strategy_values AS ENUM ('ringall', 'leastrecent', 'fewestcalls', 'random', 'rrmemory', 'linear', 'wrandom', 'rrordered');
 
 CREATE TABLE queues (
     name VARCHAR(128) NOT NULL, 
@@ -625,32 +635,38 @@ CREATE TABLE queue_members (
     PRIMARY KEY (queue_name, interface)
 );
 
+UPDATE alembic_version SET version_num='28887f25a46f' WHERE alembic_version.version_num = '21e526ad3040';
+
 -- Running upgrade 28887f25a46f -> 4c573e7135bd
 
 ALTER TABLE ps_endpoints ALTER COLUMN tos_audio TYPE VARCHAR(10);
 
 ALTER TABLE ps_endpoints ALTER COLUMN tos_video TYPE VARCHAR(10);
 
-ALTER TABLE ps_transports ALTER COLUMN tos TYPE VARCHAR(10);
-
 ALTER TABLE ps_endpoints DROP COLUMN cos_audio;
 
 ALTER TABLE ps_endpoints DROP COLUMN cos_video;
 
-ALTER TABLE ps_transports DROP COLUMN cos;
-
 ALTER TABLE ps_endpoints ADD COLUMN cos_audio INTEGER;
 
 ALTER TABLE ps_endpoints ADD COLUMN cos_video INTEGER;
 
+ALTER TABLE ps_transports ALTER COLUMN tos TYPE VARCHAR(10);
+
+ALTER TABLE ps_transports DROP COLUMN cos;
+
 ALTER TABLE ps_transports ADD COLUMN cos INTEGER;
 
+UPDATE alembic_version SET version_num='4c573e7135bd' WHERE alembic_version.version_num = '28887f25a46f';
+
 -- Running upgrade 4c573e7135bd -> 3855ee4e5f85
 
 ALTER TABLE ps_endpoints ADD COLUMN message_context VARCHAR(40);
 
 ALTER TABLE ps_contacts ADD COLUMN user_agent VARCHAR(40);
 
+UPDATE alembic_version SET version_num='3855ee4e5f85' WHERE alembic_version.version_num = '4c573e7135bd';
+
 -- Running upgrade 3855ee4e5f85 -> e96a0b8071c
 
 ALTER TABLE ps_globals ALTER COLUMN user_agent TYPE VARCHAR(255);
@@ -665,6 +681,8 @@ ALTER TABLE ps_registrations ALTER COLUMN client_uri TYPE VARCHAR(255);
 
 ALTER TABLE ps_registrations ALTER COLUMN server_uri TYPE VARCHAR(255);
 
+UPDATE alembic_version SET version_num='e96a0b8071c' WHERE alembic_version.version_num = '3855ee4e5f85';
+
 -- Running upgrade e96a0b8071c -> c6d929b23a8
 
 CREATE TABLE ps_subscription_persistence (
@@ -684,20 +702,28 @@ CREATE TABLE ps_subscription_persistence (
 
 CREATE INDEX ps_subscription_persistence_id ON ps_subscription_persistence (id);
 
+UPDATE alembic_version SET version_num='c6d929b23a8' WHERE alembic_version.version_num = 'e96a0b8071c';
+
 -- Running upgrade c6d929b23a8 -> 51f8cb66540e
 
 ALTER TABLE ps_endpoints ADD COLUMN force_avp yesno_values;
 
 ALTER TABLE ps_endpoints ADD COLUMN media_use_received_transport yesno_values;
 
+UPDATE alembic_version SET version_num='51f8cb66540e' WHERE alembic_version.version_num = 'c6d929b23a8';
+
 -- Running upgrade 51f8cb66540e -> 1d50859ed02e
 
 ALTER TABLE ps_endpoints ADD COLUMN accountcode VARCHAR(20);
 
+UPDATE alembic_version SET version_num='1d50859ed02e' WHERE alembic_version.version_num = '51f8cb66540e';
+
 -- Running upgrade 1d50859ed02e -> 1758e8bbf6b
 
 ALTER TABLE sippeers ALTER COLUMN useragent TYPE VARCHAR(255);
 
+UPDATE alembic_version SET version_num='1758e8bbf6b' WHERE alembic_version.version_num = '1d50859ed02e';
+
 -- Running upgrade 1758e8bbf6b -> 5139253c0423
 
 ALTER TABLE queue_members DROP COLUMN uniqueid;
@@ -706,6 +732,8 @@ ALTER TABLE queue_members ADD COLUMN uniqueid INTEGER NOT NULL;
 
 ALTER TABLE queue_members ADD UNIQUE (uniqueid);
 
+UPDATE alembic_version SET version_num='5139253c0423' WHERE alembic_version.version_num = '1758e8bbf6b';
+
 -- Running upgrade 5139253c0423 -> d39508cb8d8
 
 CREATE TABLE queue_rules (
@@ -715,85 +743,153 @@ CREATE TABLE queue_rules (
     max_penalty VARCHAR(32) NOT NULL
 );
 
+UPDATE alembic_version SET version_num='d39508cb8d8' WHERE alembic_version.version_num = '5139253c0423';
+
 -- Running upgrade d39508cb8d8 -> 5950038a6ead
 
 ALTER TABLE ps_transports ALTER COLUMN verifiy_server TYPE yesno_values;
 
 ALTER TABLE ps_transports RENAME verifiy_server TO verify_server;
 
+UPDATE alembic_version SET version_num='5950038a6ead' WHERE alembic_version.version_num = 'd39508cb8d8';
+
 -- Running upgrade 5950038a6ead -> 10aedae86a32
 
-CREATE TYPE sip_directmedia_values_v2 AS ENUM ('yes','no','nonat','update','outgoing');
+CREATE TYPE sip_directmedia_values_v2 AS ENUM ('yes', 'no', 'nonat', 'update', 'outgoing');
 
 ALTER TABLE sippeers ALTER COLUMN directmedia TYPE sip_directmedia_values_v2 USING directmedia::text::sip_directmedia_values_v2;
 
 DROP TYPE sip_directmedia_values;
 
+UPDATE alembic_version SET version_num='10aedae86a32' WHERE alembic_version.version_num = '5950038a6ead';
+
 -- Running upgrade 10aedae86a32 -> eb88a14f2a
 
 ALTER TABLE ps_endpoints ADD COLUMN media_encryption_optimistic yesno_values;
 
+UPDATE alembic_version SET version_num='eb88a14f2a' WHERE alembic_version.version_num = '10aedae86a32';
+
 -- Running upgrade eb88a14f2a -> 371a3bf4143e
 
 ALTER TABLE ps_endpoints ADD COLUMN user_eq_phone yesno_values;
 
+UPDATE alembic_version SET version_num='371a3bf4143e' WHERE alembic_version.version_num = 'eb88a14f2a';
+
 -- Running upgrade 371a3bf4143e -> 45e3f47c6c44
 
 ALTER TABLE ps_globals ADD COLUMN endpoint_identifier_order VARCHAR(40);
 
+UPDATE alembic_version SET version_num='45e3f47c6c44' WHERE alembic_version.version_num = '371a3bf4143e';
+
 -- Running upgrade 45e3f47c6c44 -> 23530d604b96
 
 ALTER TABLE ps_endpoints ADD COLUMN rpid_immediate yesno_values;
 
+UPDATE alembic_version SET version_num='23530d604b96' WHERE alembic_version.version_num = '45e3f47c6c44';
+
 -- Running upgrade 23530d604b96 -> 31cd4f4891ec
 
-CREATE TYPE pjsip_dtmf_mode_values_v2 AS ENUM ('rfc4733','inband','info','auto');
+CREATE TYPE pjsip_dtmf_mode_values_v2 AS ENUM ('rfc4733', 'inband', 'info', 'auto');
 
 ALTER TABLE ps_endpoints ALTER COLUMN dtmf_mode TYPE pjsip_dtmf_mode_values_v2 USING dtmf_mode::text::pjsip_dtmf_mode_values_v2;
 
 DROP TYPE pjsip_dtmf_mode_values;
 
+UPDATE alembic_version SET version_num='31cd4f4891ec' WHERE alembic_version.version_num = '23530d604b96';
+
 -- Running upgrade 31cd4f4891ec -> 461d7d691209
 
 ALTER TABLE ps_aors ADD COLUMN qualify_timeout INTEGER;
 
 ALTER TABLE ps_contacts ADD COLUMN qualify_timeout INTEGER;
 
+UPDATE alembic_version SET version_num='461d7d691209' WHERE alembic_version.version_num = '31cd4f4891ec';
+
 -- Running upgrade 461d7d691209 -> a541e0b5e89
 
 ALTER TABLE ps_globals ADD COLUMN max_initial_qualify_time INTEGER;
 
+UPDATE alembic_version SET version_num='a541e0b5e89' WHERE alembic_version.version_num = '461d7d691209';
+
 -- Running upgrade a541e0b5e89 -> 28b8e71e541f
 
 ALTER TABLE ps_endpoints ADD COLUMN g726_non_standard yesno_values;
 
+UPDATE alembic_version SET version_num='28b8e71e541f' WHERE alembic_version.version_num = 'a541e0b5e89';
+
 -- Running upgrade 28b8e71e541f -> 498357a710ae
 
 ALTER TABLE ps_endpoints ADD COLUMN rtp_keepalive INTEGER;
 
+UPDATE alembic_version SET version_num='498357a710ae' WHERE alembic_version.version_num = '28b8e71e541f';
+
 -- Running upgrade 498357a710ae -> 26f10cadc157
 
 ALTER TABLE ps_endpoints ADD COLUMN rtp_timeout INTEGER;
 
 ALTER TABLE ps_endpoints ADD COLUMN rtp_timeout_hold INTEGER;
 
+UPDATE alembic_version SET version_num='26f10cadc157' WHERE alembic_version.version_num = '498357a710ae';
+
 -- Running upgrade 26f10cadc157 -> 154177371065
 
 ALTER TABLE ps_globals ADD COLUMN default_from_user VARCHAR(80);
 
+UPDATE alembic_version SET version_num='154177371065' WHERE alembic_version.version_num = '26f10cadc157';
+
 -- Running upgrade 154177371065 -> 28ce1e718f05
 
 ALTER TABLE ps_registrations ADD COLUMN fatal_retry_interval INTEGER;
 
+UPDATE alembic_version SET version_num='28ce1e718f05' WHERE alembic_version.version_num = '154177371065';
+
 -- Running upgrade 28ce1e718f05 -> 189a235b3fd7
 
 ALTER TABLE ps_globals ADD COLUMN keep_alive_interval INTEGER;
 
+UPDATE alembic_version SET version_num='189a235b3fd7' WHERE alembic_version.version_num = '28ce1e718f05';
+
 -- Running upgrade 189a235b3fd7 -> 2d078ec071b7
 
 ALTER TABLE ps_aors ALTER COLUMN contact TYPE VARCHAR(255);
 
-INSERT INTO alembic_version (version_num) VALUES ('2d078ec071b7');
+UPDATE alembic_version SET version_num='2d078ec071b7' WHERE alembic_version.version_num = '189a235b3fd7';
+
+-- Running upgrade 2d078ec071b7 -> 26d7f3bf0fa5
+
+ALTER TABLE ps_endpoints ADD COLUMN bind_rtp_to_media_address yesno_values;
+
+UPDATE alembic_version SET version_num='26d7f3bf0fa5' WHERE alembic_version.version_num = '2d078ec071b7';
+
+-- Running upgrade 26d7f3bf0fa5 -> 136885b81223
+
+ALTER TABLE ps_globals ADD COLUMN regcontext VARCHAR(80);
+
+UPDATE alembic_version SET version_num='136885b81223' WHERE alembic_version.version_num = '26d7f3bf0fa5';
+
+-- Running upgrade 136885b81223 -> 423f34ad36e2
+
+ALTER TABLE ps_aors ALTER COLUMN qualify_timeout TYPE FLOAT;
+
+ALTER TABLE ps_contacts ALTER COLUMN qualify_timeout TYPE FLOAT;
+
+UPDATE alembic_version SET version_num='423f34ad36e2' WHERE alembic_version.version_num = '136885b81223';
+
+-- Running upgrade 423f34ad36e2 -> dbc44d5a908
+
+ALTER TABLE ps_systems ADD COLUMN disable_tcp_switch yesno_values;
+
+ALTER TABLE ps_registrations ADD COLUMN line yesno_values;
+
+ALTER TABLE ps_registrations ADD COLUMN endpoint VARCHAR(40);
+
+UPDATE alembic_version SET version_num='dbc44d5a908' WHERE alembic_version.version_num = '423f34ad36e2';
+
+-- Running upgrade dbc44d5a908 -> 3bcc0b5bc2c9
+
+ALTER TABLE ps_transports ADD COLUMN allow_reload yesno_values;
+
+UPDATE alembic_version SET version_num='3bcc0b5bc2c9' WHERE alembic_version.version_num = 'dbc44d5a908';
 
 COMMIT;
 
diff --git a/contrib/realtime/postgresql/postgresql_voicemail.sql b/contrib/realtime/postgresql/postgresql_voicemail.sql
index 20caf39..f925784 100644
--- a/contrib/realtime/postgresql/postgresql_voicemail.sql
+++ b/contrib/realtime/postgresql/postgresql_voicemail.sql
@@ -4,7 +4,7 @@ CREATE TABLE alembic_version (
     version_num VARCHAR(32) NOT NULL
 );
 
--- Running upgrade None -> a2e9769475e
+-- Running upgrade  -> a2e9769475e
 
 CREATE TABLE voicemail_messages (
     dir VARCHAR(255) NOT NULL, 
@@ -26,11 +26,13 @@ ALTER TABLE voicemail_messages ADD CONSTRAINT voicemail_messages_dir_msgnum PRIM
 
 CREATE INDEX voicemail_messages_dir ON voicemail_messages (dir);
 
+INSERT INTO alembic_version (version_num) VALUES ('a2e9769475e');
+
 -- Running upgrade a2e9769475e -> 39428242f7f5
 
 ALTER TABLE voicemail_messages ALTER COLUMN recording TYPE BYTEA;
 
-INSERT INTO alembic_version (version_num) VALUES ('39428242f7f5');
+UPDATE alembic_version SET version_num='39428242f7f5' WHERE alembic_version.version_num = 'a2e9769475e';
 
 COMMIT;
 
diff --git a/contrib/scripts/autosupport b/contrib/scripts/autosupport
old mode 100644
new mode 100755
index e41215f..3e0213a
--- a/contrib/scripts/autosupport
+++ b/contrib/scripts/autosupport
@@ -3,7 +3,7 @@
 # Autosupport Version 2.1.0
 # Collect support information
 #
-# Copyright (C) 2005-2013, Digium, Inc.
+# Copyright (C) 2005-2016, Digium, Inc.
 #
 # Written by John Bigelow (support at digium.com)
 #            Charles Moye (cmoye at digium.com)
@@ -38,7 +38,7 @@ then
   -h | --help)
     echo
     echo "Digium autosupport script"
-    echo "Copyright (C) 2005-2013, Digium, Inc."
+    echo "Copyright (C) 2005-2016, Digium, Inc."
     echo "Licensed under the terms of the GNU General Public License"
     echo
     echo "usage: autosupport [prefix]"
@@ -186,7 +186,9 @@ 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 "core show version" "pri show version" "dahdi show version" "core show translation" \
+  for command in "core show version" "pri show version" "dahdi show version" \
+      "pjsip show version" "pjsip show buildopts" \
+      "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" \
@@ -194,9 +196,9 @@ if [ -e /var/run/asterisk.ctl ] || [ -e /var/run/asterisk/asterisk.ctl ]; then
       "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 phones" "digium_phones show sessions" "digium_phones show settings" \
       "digium_phones show translations" ;
-	do
+  do
     echo "asterisk -rx \"$command\"" >> $OUTPUT;
     asterisk -rx "$command" >> $OUTPUT;
     echo >> $OUTPUT;
diff --git a/contrib/scripts/install_prereq b/contrib/scripts/install_prereq
index 4639478..afad1f7 100755
--- a/contrib/scripts/install_prereq
+++ b/contrib/scripts/install_prereq
@@ -28,12 +28,12 @@ PACKAGES_DEBIAN="$PACKAGES_DEBIAN libpq-dev unixodbc-dev libsqlite0-dev libmysql
 PACKAGES_DEBIAN="$PACKAGES_DEBIAN libopenh323-dev libvpb-dev libgtk2.0-dev libmysqlclient-dev libbluetooth-dev libradiusclient-ng-dev freetds-dev"
 PACKAGES_DEBIAN="$PACKAGES_DEBIAN libsnmp-dev libiksemel-dev libcorosync-dev libnewt-dev libpopt-dev libical-dev libspandsp-dev libjack-dev"
 PACKAGES_DEBIAN="$PACKAGES_DEBIAN libresample-dev libc-client-dev binutils-dev libsrtp-dev libgsm1-dev libedit-dev doxygen libjansson-dev libldap-dev"
-PACKAGES_DEBIAN="$PACKAGES_DEBIAN subversion git libxslt1-dev"
+PACKAGES_DEBIAN="$PACKAGES_DEBIAN subversion git libxslt1-dev automake libsrtp-dev libncurses5-dev python-dev"
 PACKAGES_RH="automake gcc gcc-c++ ncurses-devel openssl-devel libxml2-devel unixODBC-devel libcurl-devel libogg-devel libvorbis-devel speex-devel"
 PACKAGES_RH="$PACKAGES_RH spandsp-devel freetds-devel net-snmp-devel iksemel-devel corosynclib-devel newt-devel popt-devel libtool-ltdl-devel lua-devel"
 PACKAGES_RH="$PACKAGES_RH sqlite-devel libsqlite3x-devel radiusclient-ng-devel portaudio-devel postgresql-devel libresample-devel neon-devel libical-devel"
 PACKAGES_RH="$PACKAGES_RH openldap-devel gmime22-devel sqlite2-devel mysql-devel bluez-libs-devel jack-audio-connection-kit-devel gsm-devel libedit-devel libuuid-devel"
-PACKAGES_RH="$PACKAGES_RH jansson-devel libsrtp-devel pjproject-devel subversion git libxslt-devel"
+PACKAGES_RH="$PACKAGES_RH jansson-devel libsrtp-devel pjproject-devel subversion git libxslt-devel python-devel"
 
 PACKAGES_OBSD="popt gmake wget libxml libogg libvorbis curl iksemel spandsp speex iodbc freetds-0.63p1-msdblib mysql-client gmime sqlite sqlite3 jack libxslt"
 
@@ -70,9 +70,10 @@ check_installed_debs() {
 	do
 		tocheck="${tocheck} ^${pack}$"
 	done
-	aptitude -F '%c %p' search ${tocheck} 2>/dev/null \
-		| awk '/^p/{print $2}' \
-		| grep -v ':i386$'
+	pkgs=$(aptitude -F '%c %p' search ${tocheck} 2>/dev/null | awk '/^p/{print $2}')
+	if ! [ ${#pkgs} -eq 0 ]; then
+		echo $pkgs | grep -v ':i386$'
+	fi
 }
 
 # parsing the output of yum is close to impossible.
@@ -96,7 +97,11 @@ check_installed_pkgs() {
 }
 
 handle_debian() {
+	if ! [ -x "$(command -v aptitude)" ]; then
+		apt-get install aptitude
+	fi
 	extra_packs=`check_installed_debs $PACKAGES_DEBIAN`
+	$testcmd aptitude update
 	$testcmd aptitude install -y $extra_packs
 }
 
diff --git a/doc/.gitignore b/doc/.gitignore
deleted file mode 100644
index 3461c58..0000000
--- a/doc/.gitignore
+++ /dev/null
@@ -1,4 +0,0 @@
-core-en_US.xml
-rest-api
-api
-asterisk-ng-doxygen
diff --git a/funcs/func_callerid.c b/funcs/func_callerid.c
index 621a6f2..4db985e 100644
--- a/funcs/func_callerid.c
+++ b/funcs/func_callerid.c
@@ -373,22 +373,27 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
 			fields get/set a combined value for the corresponding
 			<replaceable>...-name-pres</replaceable> and <replaceable>...-num-pres</replaceable>
 			fields.</para>
-			<para>The allowable values for the <replaceable>reason</replaceable>
+			<para>The recognized values for the <replaceable>reason</replaceable>
 			and <replaceable>orig-reason</replaceable> fields are the following:</para>
 			<enumlist>
-				<enum name = "unknown"><para>Unknown</para></enum>
+				<enum name = "away"><para>Callee is Away</para></enum>
+				<enum name = "cf_dte"><para>Call Forwarding By The Called DTE</para></enum>
 				<enum name = "cfb"><para>Call Forwarding Busy</para></enum>
 				<enum name = "cfnr"><para>Call Forwarding No Reply</para></enum>
-				<enum name = "unavailable"><para>Callee is Unavailable</para></enum>
-				<enum name = "time_of_day"><para>Time of Day</para></enum>
-				<enum name = "dnd"><para>Do Not Disturb</para></enum>
+				<enum name = "cfu"><para>Call Forwarding Unconditional</para></enum>
 				<enum name = "deflection"><para>Call Deflection</para></enum>
+				<enum name = "dnd"><para>Do Not Disturb</para></enum>
 				<enum name = "follow_me"><para>Follow Me</para></enum>
 				<enum name = "out_of_order"><para>Called DTE Out-Of-Order</para></enum>
-				<enum name = "away"><para>Callee is Away</para></enum>
-				<enum name = "cf_dte"><para>Call Forwarding By The Called DTE</para></enum>
-				<enum name = "cfu"><para>Call Forwarding Unconditional</para></enum>
+				<enum name = "send_to_vm"><para>Send the call to voicemail</para></enum>
+				<enum name = "time_of_day"><para>Time of Day</para></enum>
+				<enum name = "unavailable"><para>Callee is Unavailable</para></enum>
+				<enum name = "unknown"><para>Unknown</para></enum>
 			</enumlist>
+			<note><para>You can set a user defined reason string that SIP can
+			send/receive instead.  The user defined reason string my need to be
+			quoted depending upon SIP or the peer's requirements.  These strings
+			are treated as unknown by the non-SIP channel drivers.</para></note>
 			<para>The allowable values for the <replaceable>xxx-name-charset</replaceable>
 			field are the following:</para>
 			<enumlist>
diff --git a/funcs/func_cdr.c b/funcs/func_cdr.c
index 95d047e..dc86593 100644
--- a/funcs/func_cdr.c
+++ b/funcs/func_cdr.c
@@ -221,6 +221,26 @@ STASIS_MESSAGE_TYPE_DEFN_LOCAL(cdr_read_message_type);
 STASIS_MESSAGE_TYPE_DEFN_LOCAL(cdr_write_message_type);
 STASIS_MESSAGE_TYPE_DEFN_LOCAL(cdr_prop_write_message_type);
 
+static struct timeval cdr_retrieve_time(struct ast_channel *chan, const char *time_name)
+{
+	struct timeval time;
+	char *value = NULL;
+	char tempbuf[128];
+
+	if (ast_strlen_zero(ast_channel_name(chan))) {
+		/* Format request on a dummy channel */
+		ast_cdr_format_var(ast_channel_cdr(chan), time_name, &value, tempbuf, sizeof(tempbuf), 1);
+	} else {
+		ast_cdr_getvar(ast_channel_name(chan), time_name, tempbuf, sizeof(tempbuf));
+	}
+
+	if (sscanf(tempbuf, "%ld.%ld", &time.tv_sec, &time.tv_usec) != 2) {
+		ast_log(AST_LOG_WARNING, "Failed to fully extract '%s' from CDR\n", time_name);
+	}
+
+	return time;
+}
+
 static void cdr_read_callback(void *data, struct stasis_subscription *sub, struct stasis_message *message)
 {
 	struct cdr_func_payload *payload = stasis_message_data(message);
@@ -268,16 +288,21 @@ static void cdr_read_callback(void *data, struct stasis_subscription *sub, struc
 
 	if (ast_test_flag(&flags, OPT_FLOAT)
 		&& (!strcasecmp("billsec", args.variable) || !strcasecmp("duration", args.variable))) {
-		long ms;
-		double dtime;
+		struct timeval start = cdr_retrieve_time(payload->chan, !strcasecmp("billsec", args.variable) ? "answer" : "start");
+		struct timeval finish = cdr_retrieve_time(payload->chan, "end");
+		double delta;
 
-		if (sscanf(tempbuf, "%30ld", &ms) != 1) {
-			ast_log(AST_LOG_WARNING, "Unable to parse %s (%s) from the CDR for channel %s\n",
-				args.variable, tempbuf, ast_channel_name(payload->chan));
-			return;
+		if (ast_tvzero(finish)) {
+			finish = ast_tvnow();
 		}
-		dtime = (double)(ms / 1000.0);
-		snprintf(tempbuf, sizeof(tempbuf), "%lf", dtime);
+
+		if (ast_tvzero(start)) {
+			delta = 0.0;
+		} else {
+			delta = (double)(ast_tvdiff_us(finish, start) / 1000000.0);
+		}
+		snprintf(tempbuf, sizeof(tempbuf), "%lf", delta);
+
 	} else if (!ast_test_flag(&flags, OPT_UNPARSED)) {
 		if (!strcasecmp("start", args.variable)
 			|| !strcasecmp("end", args.variable)
diff --git a/funcs/func_channel.c b/funcs/func_channel.c
index 0f59bb5..deecda6 100644
--- a/funcs/func_channel.c
+++ b/funcs/func_channel.c
@@ -205,6 +205,30 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
 						<para>R/W whether or not context tracing is enabled, only available
 						<emphasis>if CHANNEL_TRACE is defined</emphasis>.</para>
 					</enum>
+					<enum name="hangupsource">
+						<para>R/W returns the channel responsible for hangup.</para>
+					</enum>
+					<enum name="appname">
+						<para>R/O returns the internal application name.</para>
+					</enum>
+					<enum name="appdata">
+						<para>R/O returns the application data if available.</para>
+					</enum>
+					<enum name="exten">
+						<para>R/O returns the extension for an outbound channel.</para>
+					</enum>
+					<enum name="context">
+						<para>R/O returns the context for an outbound channel.</para>
+					</enum>
+					<enum name="channame">
+						<para>R/O returns the channel name for an outbound channel.</para>
+					</enum>
+					<enum name="uniqueid">
+						<para>R/O returns the channel uniqueid.</para>
+					</enum>
+					<enum name="linkedid">
+						<para>R/O returns the linkedid if available, otherwise returns the uniqueid.</para>
+					</enum>
 				</enumlist>
 				<para><emphasis>chan_sip</emphasis> provides the following additional options:</para>
 				<enumlist>
diff --git a/funcs/func_iconv.c b/funcs/func_iconv.c
index c3d0286..0a8e57d 100644
--- a/funcs/func_iconv.c
+++ b/funcs/func_iconv.c
@@ -83,7 +83,7 @@ static int iconv_read(struct ast_channel *chan, const char *cmd, char *arguments
 		AST_APP_ARG(text);
 	);
 	iconv_t cd;
-	size_t incount, outcount = len;
+	size_t incount, outcount = len - 1;
 	char *parse;
 
 	if (ast_strlen_zero(arguments)) {
@@ -120,6 +120,7 @@ static int iconv_read(struct ast_channel *chan, const char *cmd, char *arguments
 		else
 			ast_log(LOG_WARNING,  "Iconv: error %d: %s.\n", errno, strerror(errno));
 	}
+	*buf = '\0';
 	iconv_close(cd);
 
 	return 0;
diff --git a/funcs/func_odbc.c b/funcs/func_odbc.c
index 088cd5a..0af3fd1 100644
--- a/funcs/func_odbc.c
+++ b/funcs/func_odbc.c
@@ -42,6 +42,7 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
 #include "asterisk/pbx.h"
 #include "asterisk/config.h"
 #include "asterisk/res_odbc.h"
+#include "asterisk/res_odbc_transaction.h"
 #include "asterisk/app.h"
 #include "asterisk/cli.h"
 #include "asterisk/strings.h"
diff --git a/include/asterisk.h b/include/asterisk.h
index c501c44..873ed5c 100644
--- a/include/asterisk.h
+++ b/include/asterisk.h
@@ -222,6 +222,9 @@ char *ast_complete_source_filename(const char *partial, int n);
  * SVN from modifying them in this file; under normal circumstances they would
  * not be present and SVN would expand the Revision keyword into the file's
  * revision number.
+ *
+ * \deprecated All new files should use ASTERISK_REGISTER_FILE instead.
+ * \version 11.22.0 deprecated
  */
 #ifdef MTX_PROFILE
 #define	HAVE_MTX_PROFILE	/* used in lock.h */
@@ -251,6 +254,23 @@ char *ast_complete_source_filename(const char *partial, int n);
 #define ASTERISK_FILE_VERSION(file, x)
 #endif /* LOW_MEMORY */
 
+/*!
+ * \since 11.22.0
+ * \brief Register/unregister a source code file with the core.
+ *
+ * This macro will place a file-scope constructor and destructor into the
+ * source of the module using it; this will cause the file to be
+ * registered with the Asterisk core (and unregistered) at the appropriate
+ * times.
+ *
+ * Example:
+ *
+ * \code
+ * ASTERISK_REGISTER_FILE()
+ * \endcode
+ */
+#define ASTERISK_REGISTER_FILE() ASTERISK_FILE_VERSION(__FILE__, NULL)
+
 #if !defined(LOW_MEMORY)
 /*!
  * \brief support for event profiling
diff --git a/include/asterisk/.gitignore b/include/asterisk/.gitignore
deleted file mode 100644
index ae33b3c..0000000
--- a/include/asterisk/.gitignore
+++ /dev/null
@@ -1,3 +0,0 @@
-autoconfig.h
-build.h
-buildopts.h
diff --git a/include/asterisk/_private.h b/include/asterisk/_private.h
index d49de17..2148a20 100644
--- a/include/asterisk/_private.h
+++ b/include/asterisk/_private.h
@@ -17,6 +17,12 @@
 
 int load_modules(unsigned int);		/*!< Provided by loader.c */
 int load_pbx(void);			/*!< Provided by pbx.c */
+int load_pbx_builtins(void);	/*!< Provided by pbx_builtins.c */
+int load_pbx_functions_cli(void);	/*!< Provided by pbx_functions.c */
+int load_pbx_variables(void);	/*!< Provided by pbx_variables.c */
+int load_pbx_switch(void);		/*!< Provided by pbx_switch.c */
+int load_pbx_app(void);		/*!< Provided by pbx_app.c */
+int load_pbx_hangup_handler(void);	/*!< Provided by pbx_hangup_handler.c */
 int init_logger(void);			/*!< Provided by logger.c */
 void close_logger(void);		/*!< Provided by logger.c */
 void logger_queue_start(void);		/*!< Provided by logger.c */
@@ -46,6 +52,7 @@ void ast_stun_init(void);               /*!< Provided by stun.c */
 int ast_cel_engine_init(void);		/*!< Provided by cel.c */
 int ast_cel_engine_reload(void);	/*!< Provided by cel.c */
 int ast_ssl_init(void);                 /*!< Provided by ssl.c */
+int ast_pj_init(void);                 /*!< Provided by libasteriskpj.c */
 int ast_test_init(void);            /*!< Provided by test.c */
 int ast_msg_init(void);             /*!< Provided by message.c */
 void ast_msg_shutdown(void);        /*!< Provided by message.c */
diff --git a/include/asterisk/app.h b/include/asterisk/app.h
index 6171dd4..86336e3 100644
--- a/include/asterisk/app.h
+++ b/include/asterisk/app.h
@@ -580,6 +580,7 @@ int ast_vm_is_registered(void);
  *
  * \retval 0 on success.
  * \retval -1 on error.
+ * \retval AST_MODULE_LOAD_DECLINE if there's already another provider registered.
  */
 int __ast_vm_register(const struct ast_vm_functions *vm_table, struct ast_module *module);
 
@@ -648,6 +649,7 @@ int ast_vm_greeter_is_registered(void);
  *
  * \retval 0 on success.
  * \retval -1 on error.
+ * \retval AST_MODULE_LOAD_DECLINE if there's already another greeter registered.
  */
 int __ast_vm_greeter_register(const struct ast_vm_greeter_functions *vm_table, struct ast_module *module);
 
diff --git a/include/asterisk/autochan.h b/include/asterisk/autochan.h
index a0981b7..319c203 100644
--- a/include/asterisk/autochan.h
+++ b/include/asterisk/autochan.h
@@ -56,8 +56,28 @@ struct ast_autochan {
  * to save off the pointer using ast_channel_ref and to unref the channel when you
  * are finished with the pointer. If you do not do this and a masquerade occurs on
  * the channel, then it is possible that your saved pointer will become invalid.
+ *
+ * 3. If you want to lock the autochan->chan channel, be sure to use
+ * ast_autochan_channel_lock and ast_autochan_channel_unlock. An attempt to lock
+ * the autochan->chan directly may result in it being changed after you've
+ * retrieved the value of chan, but before you've had a chance to lock it.
+ * First when chan is locked, the autochan structure is guaranteed to keep the
+ * same channel.
  */
 
+#define ast_autochan_channel_lock(autochan) \
+	do { \
+		struct ast_channel *autochan_chan = autochan->chan; \
+		ast_channel_lock(autochan_chan); \
+		if (autochan->chan == autochan_chan) { \
+			break; \
+		} \
+		ast_channel_unlock(autochan_chan); \
+	} while (1)
+
+#define ast_autochan_channel_unlock(autochan) \
+	ast_channel_unlock(autochan->chan)
+
 /*!
  * \brief set up a new ast_autochan structure
  *
diff --git a/include/asterisk/autoconfig.h.in b/include/asterisk/autoconfig.h.in
index 542f5d2..8078098 100644
--- a/include/asterisk/autoconfig.h.in
+++ b/include/asterisk/autoconfig.h.in
@@ -155,13 +155,13 @@
 /* Define to 1 if you have the `cosl' function. */
 #undef HAVE_COSL
 
-/* Define to 1 if you have the `crypt' function. */
+/* Define to 1 if you have the 'crypt' function. */
 #undef HAVE_CRYPT
 
 /* Define to 1 if you have the OpenSSL Cryptography library. */
 #undef HAVE_CRYPTO
 
-/* Define to 1 if you have the `crypt_r' function. */
+/* Define to 1 if you have the 'crypt_r' function. */
 #undef HAVE_CRYPT_R
 
 /* Define to 1 if you have a functional curl library. */
@@ -581,18 +581,28 @@
 /* Define if your system has the PJPROJECT libraries. */
 #undef HAVE_PJPROJECT
 
-/* Define to 1 if PJPROJECT has the pjsip_get_dest_info support feature. */
+/* Define if your system has PJPROJECT_BUNDLED */
+#undef HAVE_PJPROJECT_BUNDLED
+
+/* Define if your system has pjsip_dlg_create_uas_and_inc_lock declared. */
+#undef HAVE_PJSIP_DLG_CREATE_UAS_AND_INC_LOCK
+
+/* Define if your system has pjsip_endpt_set_ext_resolver declared. */
+#undef HAVE_PJSIP_EXTERNAL_RESOLVER
+
+/* Define if your system has pjsip_get_dest_info declared. */
 #undef HAVE_PJSIP_GET_DEST_INFO
 
 /* Define if your system has the PJSIP_REPLACE_MEDIA_STREAM headers. */
 #undef HAVE_PJSIP_REPLACE_MEDIA_STREAM
 
-/* Define to 1 if PJPROJECT has the pj_ssl_cert_load_from_files2 support
-   feature. */
+/* Define if your system has the PJSIP_TLS_TRANSPORT_PROTO headers. */
+#undef HAVE_PJSIP_TLS_TRANSPORT_PROTO
+
+/* Define if your system has pj_ssl_cert_load_from_files2 declared. */
 #undef HAVE_PJ_SSL_CERT_LOAD_FROM_FILES2
 
-/* Define to 1 if PJPROJECT has the PJSIP Transaction Group Lock Support
-   feature. */
+/* Define if your system has pjsip_tsx_create_uac2 declared. */
 #undef HAVE_PJ_TRANSACTION_GRP_LOCK
 
 /* Define to 1 if your system defines IP_PKTINFO. */
@@ -826,6 +836,12 @@
 /* Define to 1 if you have the ISDN SS7 library. */
 #undef HAVE_SS7
 
+/* Define if your system has the SSL_OP_NO_TLSV1_1 headers. */
+#undef HAVE_SSL_OP_NO_TLSV1_1
+
+/* Define if your system has the SSL_OP_NO_TLSV1_2 headers. */
+#undef HAVE_SSL_OP_NO_TLSV1_2
+
 /* Define to 1 if `stat' has the bug that it succeeds when given the
    zero-length file name argument. */
 #undef HAVE_STAT_EMPTY_STRING_BUG
diff --git a/include/asterisk/bridge_channel.h b/include/asterisk/bridge_channel.h
index 03fe30e..55c2b3a 100644
--- a/include/asterisk/bridge_channel.h
+++ b/include/asterisk/bridge_channel.h
@@ -170,6 +170,8 @@ struct ast_bridge_channel {
 		/*! Collected DTMF digits for DTMF hooks. */
 		char collected[MAXIMUM_DTMF_FEATURE_STRING];
 	} dtmf_hook_state;
+	/*! Non-zero if a T.38 session terminate is owed to the bridge. */
+	char owed_t38_terminate;
 };
 
 /*!
diff --git a/include/asterisk/bridge_channel_internal.h b/include/asterisk/bridge_channel_internal.h
index e3fb73d..7f7d5a8 100644
--- a/include/asterisk/bridge_channel_internal.h
+++ b/include/asterisk/bridge_channel_internal.h
@@ -84,7 +84,7 @@ struct ast_bridge_channel *bridge_channel_internal_alloc(struct ast_bridge *brid
 
 /*!
  * \internal
- * \brief Clear owed events by the channel to the original bridge.
+ * \brief Settle owed events by the channel to the original bridge.
  * \since 12.0.0
  *
  * \param orig_bridge Original bridge the channel was in before leaving.
@@ -118,6 +118,27 @@ int bridge_channel_internal_push(struct ast_bridge_channel *bridge_channel);
 
 /*!
  * \internal
+ * \brief Push the bridge channel into its specified bridge.
+ * \since 13.8.0
+ *
+ * \param bridge_channel Channel to push.
+ * \param optimized non-zero if the push with swap is for an optimization.
+ *
+ * \note A ref is not held by bridge_channel->swap when calling because the
+ * push with swap happens immediately.
+ *
+ * \note On entry, bridge_channel->bridge is already locked.
+ *
+ * \retval 0 on success.
+ * \retval -1 on failure.  The channel did not get pushed.
+ *
+ * \note On failure the caller must call
+ * ast_bridge_features_remove(bridge_channel->features, AST_BRIDGE_HOOK_REMOVE_ON_PULL);
+ */
+int bridge_channel_internal_push_full(struct ast_bridge_channel *bridge_channel, int optimized);
+
+/*!
+ * \internal
  * \brief Pull the bridge channel out of its current bridge.
  * \since 12.0.0
  *
diff --git a/include/asterisk/channel.h b/include/asterisk/channel.h
index fe669ef..4cc210d 100644
--- a/include/asterisk/channel.h
+++ b/include/asterisk/channel.h
@@ -1616,6 +1616,42 @@ void ast_channel_set_unbridged(struct ast_channel *chan, int value);
 void ast_channel_set_unbridged_nolock(struct ast_channel *chan, int value);
 
 /*!
+ * \brief This function will check if T.38 is active on the channel.
+ *
+ * \param chan Channel on which to check the unbridge_eval flag
+ *
+ * \return Returns 0 if the flag is down or 1 if the flag is up.
+ */
+int ast_channel_is_t38_active(struct ast_channel *chan);
+
+/*!
+ * \brief ast_channel_is_t38_active variant. Use this if the channel
+ *         is already locked prior to calling.
+ *
+ * \param chan Channel on which to check the is_t38_active flag
+ *
+ * \return Returns 0 if the flag is down or 1 if the flag is up.
+ */
+int ast_channel_is_t38_active_nolock(struct ast_channel *chan);
+
+/*!
+ * \brief Sets the is_t38_active flag
+ *
+ * \param chan Which channel is having its is_t38_active value set
+ * \param is_t38_active Non-zero if T.38 is active
+ */
+void ast_channel_set_is_t38_active(struct ast_channel *chan, int is_t38_active);
+
+/*!
+ * \brief Variant of ast_channel_set_is_t38_active. Use this if the channel
+ *         is already locked prior to calling.
+ *
+ * \param chan Which channel is having its is_t38_active value set
+ * \param is_t38_active Non-zero if T.38 is active
+ */
+void ast_channel_set_is_t38_active_nolock(struct ast_channel *chan, int is_t38_active);
+
+/*!
  * \brief Lock the given channel, then request softhangup on the channel with the given causecode
  * \param chan channel on which to hang up
  * \param causecode cause code to use (Zero if don't use cause code)
diff --git a/include/asterisk/config.h b/include/asterisk/config.h
index 8a375e5..d0bcae6 100644
--- a/include/asterisk/config.h
+++ b/include/asterisk/config.h
@@ -495,6 +495,17 @@ int ast_unload_realtime(const char *family);
  *
  * \note You should use the constant SENTINEL to terminate arguments, in
  * order to preserve cross-platform compatibility.
+ *
+ * TODO The return value of this function is routinely ignored. Ignoring
+ * the return value means that it's mostly pointless to be calling this.
+ * You'll see some warning messages potentially, but that's it.
+ *
+ * XXX This function is super useful for detecting configuration problems
+ * early, but unfortunately, the latest in configuration management, sorcery,
+ * doesn't work well with this. Users of sorcery are familiar with the fields
+ * they will need to write but don't know if realtime is being used. Sorcery
+ * knows what storage mechanism is being used but has no high-level knowledge
+ * of what sort of data is going to be written.
  */
 int ast_realtime_require_field(const char *family, ...) attribute_sentinel;
 
diff --git a/include/asterisk/core_local.h b/include/asterisk/core_local.h
index 491112d..8557072 100644
--- a/include/asterisk/core_local.h
+++ b/include/asterisk/core_local.h
@@ -42,6 +42,38 @@ struct stasis_message_type;
 /* ------------------------------------------------------------------- */
 
 /*!
+ * \brief Lock the "chan" and "owner" channels (and return them) on the base
+ *        private structure as well as the base private structure itself.
+ *
+ * \note This also adds references to each of the above mentioned elements and
+ *       also the underlying private local structure.
+ * \note None of these locks should be held prior to calling this function.
+ * \note To undo this process call ast_local_unlock_all.
+ *
+ * \since 13.8.0
+ *
+ * \param chan Must be a local channel
+ * \param outchan The local channel's "chan" channel
+ * \param outowner The local channel's "owner" channel
+ */
+void ast_local_lock_all(struct ast_channel *chan, struct ast_channel **outchan,
+			struct ast_channel **outowner);
+
+/*!
+ * \brief Unlock the "chan" and "owner" channels on the base private structure
+ *        as well as the base private structure itself.
+ *
+ * \note This also removes references to each of the above mentioned elements and
+ *       also the underlying private local structure.
+ * \note This function should be used in conjunction with ast_local_lock_all.
+ *
+ * \since 13.8.0
+ *
+ * \param chan Must be a local channel
+ */
+void ast_local_unlock_all(struct ast_channel *chan);
+
+/*!
  * \brief Get the other local channel in the pair.
  * \since 12.0.0
  *
diff --git a/include/asterisk/features_config.h b/include/asterisk/features_config.h
index b15759b..baaff18 100644
--- a/include/asterisk/features_config.h
+++ b/include/asterisk/features_config.h
@@ -102,6 +102,21 @@ struct ast_features_xfer_config {
 struct ast_features_xfer_config *ast_get_chan_features_xfer_config(struct ast_channel *chan);
 
 /*!
+ * \brief Get the transfer configuration option xferfailsound
+ *
+ * \note The channel should be locked before calling this function.
+ * \note The returned value has to be freed.
+ *
+ * If no channel is provided, then option is pulled from the global
+ * transfer configuration.
+ *
+ * \param chan The channel to get configuration options for
+ * \retval NULL Failed to get configuration
+ * \retval non-NULL The xferfailsound
+ */
+char *ast_get_chan_features_xferfailsound(struct ast_channel *chan);
+
+/*!
  * \brief Configuration relating to call pickup
  */
 struct ast_features_pickup_config {
diff --git a/include/asterisk/http_websocket.h b/include/asterisk/http_websocket.h
index 5adc089..d8e7d06 100644
--- a/include/asterisk/http_websocket.h
+++ b/include/asterisk/http_websocket.h
@@ -265,12 +265,12 @@ AST_OPTIONAL_API(int, ast_websocket_read_string,
  * \param session Pointer to the WebSocket session
  * \param opcode WebSocket operation code to place in the frame
  * \param payload Optional pointer to a payload to add to the frame
- * \param actual_length Length of the payload (0 if no payload)
+ * \param payload_size Length of the payload (0 if no payload)
  *
  * \retval 0 if successfully written
  * \retval -1 if error occurred
  */
-AST_OPTIONAL_API(int, ast_websocket_write, (struct ast_websocket *session, enum ast_websocket_opcode opcode, char *payload, uint64_t actual_length), { errno = ENOSYS; return -1;});
+AST_OPTIONAL_API(int, ast_websocket_write, (struct ast_websocket *session, enum ast_websocket_opcode opcode, char *payload, uint64_t payload_size), { errno = ENOSYS; return -1;});
 
 /*!
  * \brief Construct and transmit a WebSocket frame containing string data.
diff --git a/include/asterisk/module.h b/include/asterisk/module.h
index d201aac..35ee8bb 100644
--- a/include/asterisk/module.h
+++ b/include/asterisk/module.h
@@ -68,7 +68,7 @@ enum ast_module_unload_mode {
 enum ast_module_load_result {
 	AST_MODULE_LOAD_SUCCESS = 0,    /*!< Module loaded and configured */
 	AST_MODULE_LOAD_DECLINE = 1,    /*!< Module is not configured */
-	AST_MODULE_LOAD_SKIP = 2,       /*!< Module was skipped for some reason */
+	AST_MODULE_LOAD_SKIP = 2,       /*!< Module was skipped for some reason (For loader.c use only. Should never be returned by modules)*/
 	AST_MODULE_LOAD_PRIORITY = 3,   /*!< Module is not loaded yet, but is added to prioity heap */
 	AST_MODULE_LOAD_FAILURE = -1,   /*!< Module could not be loaded properly */
 };
diff --git a/include/asterisk/res_odbc.h b/include/asterisk/res_odbc.h
index 7d9d4a1..8c7b549 100644
--- a/include/asterisk/res_odbc.h
+++ b/include/asterisk/res_odbc.h
@@ -46,16 +46,11 @@ enum {
 struct odbc_obj {
 	SQLHDBC  con;                   /*!< ODBC Connection Handle */
 	struct odbc_class *parent;      /*!< Information about the connection is protected */
-	struct timeval last_used;       /*!< Used by idlecheck to determine if the connection should be renegotiated */
 #ifdef DEBUG_THREADS
 	char file[80];
 	char function[80];
 	int lineno;
 #endif
-	unsigned int used:1;            /*!< Is this connection currently in use? */
-	unsigned int up:1;
-	unsigned int tx:1;              /*!< Should this connection be unshared, regardless of the class setting? */
-	struct odbc_txn_frame *txf;     /*!< Reference back to the transaction frame, if applicable */
 	AST_LIST_ENTRY(odbc_obj) list;
 };
 
@@ -102,39 +97,29 @@ int ast_odbc_smart_execute(struct odbc_obj *obj, SQLHSTMT stmt) __attribute__((d
 
 /*!
  * \brief Retrieves a connected ODBC object
- * \param name The name of the ODBC class for which a connection is needed.
- * \param flags One or more of the following flags:
- *	\li RES_ODBC_SANITY_CHECK Whether to ensure that a connection is valid before returning the handle.  Usually unnecessary.
- *	\li RES_ODBC_INDEPENDENT_CONNECTION Return a handle which is independent from all others.  Usually used when starting a transaction.
- *	\li RES_ODBC_CONNECTED Only return a connected handle.  Intended for use with peers which use idlecheck, which are checked periodically for reachability.
- * \param  file, function, lineno
  *
- * \return ODBC object
- * \retval NULL if there is no connection available with the requested name.
+ * \deprecated
  *
- * Connection classes may, in fact, contain multiple connection handles.  If
- * the connection is pooled, then each connection will be dedicated to the
- * thread which requests it.  Note that all connections should be released
- * when the thread is done by calling ast_odbc_release_obj(), below.
+ * This is only around for backwards-compatibility with older versions of Asterisk.
  */
 struct odbc_obj *_ast_odbc_request_obj2(const char *name, struct ast_flags flags, const char *file, const char *function, int lineno);
+
+/*!
+ * \brief Get a ODBC connection object
+ *
+ * The "check" parameter is leftover from an earlier implementation where database connections
+ * were cached by res_odbc. Since connections are managed by unixODBC now, this parameter is
+ * only kept around for API compatibility.
+ *
+ * \param name The name of the res_odbc.conf section describing the database to connect to
+ * \param check unused
+ * \return A connection to the database. Call ast_odbc_release_obj() when finished.
+ */
 struct odbc_obj *_ast_odbc_request_obj(const char *name, int check, const char *file, const char *function, int lineno);
 
 #define ast_odbc_request_obj2(a, b)	_ast_odbc_request_obj2(a, b, __FILE__, __PRETTY_FUNCTION__, __LINE__)
 #define ast_odbc_request_obj(a, b)	_ast_odbc_request_obj(a, b, __FILE__, __PRETTY_FUNCTION__, __LINE__)
 
-/*!
- * \brief Retrieve a stored ODBC object, if a transaction has been started.
- * \param chan Channel associated with the transaction.
- * \param objname Name of the database handle.  This name corresponds to the name passed
- * to \see ast_odbc_request_obj2 (or formerly, to ast_odbc_request_obj).  Note that the
- * existence of this parameter name explicitly allows for multiple transactions to be open
- * at once, albeit to different databases.
- * \retval A stored ODBC object, if a transaction was already started.
- * \retval NULL, if no transaction yet exists.
- */
-struct odbc_obj *ast_odbc_retrieve_transaction_obj(struct ast_channel *chan, const char *objname);
-
 /*! 
  * \brief Releases an ODBC object previously allocated by ast_odbc_request_obj()
  * \param obj The ODBC object
@@ -223,4 +208,39 @@ int ast_odbc_clear_cache(const char *database, const char *tablename);
  */
 SQLRETURN ast_odbc_ast_str_SQLGetData(struct ast_str **buf, int pmaxlen, SQLHSTMT StatementHandle, SQLUSMALLINT ColumnNumber, SQLSMALLINT TargetType, SQLLEN *StrLen_or_Ind);
 
+/*!
+ * \brief Shortcut for printing errors to logs after a failed SQL operation.
+ *
+ * \param handle_type The type of SQL handle on which to gather diagnostics
+ * \param handle The SQL handle to gather diagnostics from
+ * \param operation The name of the failed operation.
+ * \return The error string that was printed to the logs
+ */
+struct ast_str *ast_odbc_print_errors(SQLSMALLINT handle_type, SQLHANDLE handle, const char *operation);
+
+/*!
+ * \brief Get the transaction isolation setting for an ODBC class
+ */
+unsigned int ast_odbc_class_get_isolation(struct odbc_class *class);
+
+/*!
+ * \brief Get the transaction forcecommit setting for an ODBC class
+ */
+unsigned int ast_odbc_class_get_forcecommit(struct odbc_class *class);
+
+/*!
+ * \brief Get the name of an ODBC class.
+ */
+const char *ast_odbc_class_get_name(struct odbc_class *class);
+
+/*!
+ * \brief Convert from textual transaction isolation values to their numeric constants
+ */
+int ast_odbc_text2isolation(const char *txt);
+
+/*!
+ * \brief Convert from numeric transaction isolation values to their textual counterparts
+ */
+const char *ast_odbc_isolation2text(int iso);
+
 #endif /* _ASTERISK_RES_ODBC_H */
diff --git a/include/asterisk/res_odbc_transaction.h b/include/asterisk/res_odbc_transaction.h
new file mode 100644
index 0000000..b0f9316
--- /dev/null
+++ b/include/asterisk/res_odbc_transaction.h
@@ -0,0 +1,54 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2016, Digium, Inc.
+ *
+ * Mark Michelson <mmichelson at 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.
+ */
+
+#ifndef RES_ODBC_TRANSACTION_H
+#define RES_ODBC_TRANSACTION_H
+
+/*!
+ * \brief
+ *
+ * Retrieve an ODBC transaction connection with the given ODBC class name.
+ *
+ * \note The name passed here is *not* the name of the transaction but the name of the
+ * ODBC class defined in res_odbc.conf.
+ *
+ * \note Do not call ast_odbc_release_obj() on the retrieved connection. Calling this function
+ * does not make you the owner of the connection.
+ *
+ * XXX This function is majorly flawed because it ignores properties of transactions and simply
+ * finds one that corresponds to the given DSN. The problem here is that transactions have names
+ * and they maintain which transaction is "active" for operations like transaction creation,
+ * commit, and rollback. However, when it comes to intermediary operations to be made on the
+ * transactions, all that is ignored. It means that if a channel has created multiple transactions
+ * for the same DSN, it's a crapshoot which of those transactions the operation will be performed
+ * on. This can potentially lead to baffling errors under the right circumstances.
+ *
+ * XXX The semantics of this function make for writing some awkward code. If you use func_odbc as
+ * an example, it has to first try to retrieve a transactional connection, then failing that, create
+ * a non-transactional connection. The result is that it has to remember which type of connection it's
+ * using and know whether to release the connection when completed or not. It would be much better
+ * if callers did not have to jump through such hoops.
+ *
+ * \param chan Channel on which the ODBC transaction was created
+ * \param objname The name of the ODBC class configured in res_odbc.conf
+ * \retval NULL Transaction connection could not be found.
+ * \retval non-NULL A transactional connection
+ */
+struct odbc_obj *ast_odbc_retrieve_transaction_obj(struct ast_channel *chan, const char *objname);
+
+#endif /* RES_ODBC_TRANSACTION_H */
diff --git a/include/asterisk/res_pjproject.h b/include/asterisk/res_pjproject.h
new file mode 100644
index 0000000..8828b34
--- /dev/null
+++ b/include/asterisk/res_pjproject.h
@@ -0,0 +1,96 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2016, Fairview 5 Engineering, LLC
+ *
+ * George Joseph <george.joseph at fairview5.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.
+ */
+
+#ifndef _RES_PJPROJECT_H
+#define _RES_PJPROJECT_H
+
+/*! \brief Determines whether the res_pjproject module is loaded */
+#define CHECK_PJPROJECT_MODULE_LOADED()                 \
+	do {                                                \
+		if (!ast_module_check("res_pjproject.so")) {    \
+			return AST_MODULE_LOAD_DECLINE;             \
+		}                                               \
+	} while(0)
+
+/*!
+ * \brief Retrieve a pjproject build option
+ *
+ * \param option The build option requested
+ * \param format_string A scanf-style format string to parse the option value into
+ * \param ... Pointers to variables to receive the values parsed
+ *
+ * \retval The number of values parsed
+ *
+ * \since 13.8.0
+ *
+ * \note The option requested must be from those returned by pj_dump_config()
+ * which can be displayed with the 'pjsip show buildopts' CLI command.
+ *
+ *   <b>Sample Usage:</b>
+ *   \code
+ *
+ *   int max_hostname;
+ *
+ *   ast_sip_get_pjproject_buildopt("PJ_MAX_HOSTNAME", "%d", &max_hostname);
+ *
+ *   \endcode
+ *
+ */
+int ast_pjproject_get_buildopt(char *option, char *format_string, ...) __attribute__((format(scanf, 2, 3)));
+
+/*!
+ * \brief Begin PJPROJECT log interception for CLI output.
+ * \since 13.8.0
+ *
+ * \param fd CLI file descriptior to send intercepted output.
+ *
+ * \note ast_pjproject_log_intercept_begin() and
+ * ast_pjproject_log_intercept_end() must always be called
+ * in pairs.
+ *
+ * \return Nothing
+ */
+void ast_pjproject_log_intercept_begin(int fd);
+
+/*!
+ * \brief End PJPROJECT log interception for CLI output.
+ * \since 13.8.0
+ *
+ * \note ast_pjproject_log_intercept_begin() and
+ * ast_pjproject_log_intercept_end() must always be called
+ * in pairs.
+ *
+ * \return Nothing
+ */
+void ast_pjproject_log_intercept_end(void);
+
+/*!
+ * \brief Increment the res_pjproject reference count.
+ *
+ * This ensures graceful shutdown happens in the proper order.
+ */
+void ast_pjproject_ref(void);
+
+/*!
+ * \brief Decrement the res_pjproject reference count.
+ *
+ * This ensures graceful shutdown happens in the proper order.
+ */
+void ast_pjproject_unref(void);
+
+#endif /* _RES_PJPROJECT_H */
diff --git a/include/asterisk/res_pjsip.h b/include/asterisk/res_pjsip.h
index 6ca56bd..3901cf7 100644
--- a/include/asterisk/res_pjsip.h
+++ b/include/asterisk/res_pjsip.h
@@ -54,34 +54,59 @@ struct pjsip_tpfactory;
 struct pjsip_tls_setting;
 struct pjsip_tpselector;
 
+/*! \brief Maximum number of ciphers supported for a TLS transport */
+#define SIP_TLS_MAX_CIPHERS 64
+
 /*!
  * \brief Structure for SIP transport information
  */
 struct ast_sip_transport_state {
 	/*! \brief Transport itself */
 	struct pjsip_transport *transport;
-
 	/*! \brief Transport factory */
 	struct pjsip_tpfactory *factory;
+	/*!
+	 * Transport id
+	 * \since 13.8.0
+	 */
+	char *id;
+	/*!
+	 * Transport type
+	 * \since 13.8.0
+	 */
+	enum ast_transport type;
+	/*!
+	 * Address and port to bind to
+	 * \since 13.8.0
+	 */
+	pj_sockaddr host;
+	/*!
+	 * TLS settings
+	 * \since 13.8.0
+	 */
+	pjsip_tls_setting tls;
+	/*!
+	 * Configured TLS ciphers
+	 * \since 13.8.0
+	 */
+	pj_ssl_cipher ciphers[SIP_TLS_MAX_CIPHERS];
+	/*!
+	 * Optional local network information, used for NAT purposes
+	 * \since 13.8.0
+	 */
+	struct ast_ha *localnet;
+	/*!
+	 * DNS manager for refreshing the external address
+	 * \since 13.8.0
+	 */
+	struct ast_dnsmgr_entry *external_address_refresher;
+	/*!
+	 * Optional external address information
+	 * \since 13.8.0
+	 */
+	struct ast_sockaddr external_address;
 };
 
-#define SIP_SORCERY_DOMAIN_ALIAS_TYPE "domain_alias"
-
-/*!
- * Details about a SIP domain alias
- */
-struct ast_sip_domain_alias {
-	/*! Sorcery object details */
-	SORCERY_OBJECT(details);
-	AST_DECLARE_STRING_FIELDS(
-		/*! Domain to be aliased to */
-		AST_STRING_FIELD(domain);
-	);
-};
-
-/*! \brief Maximum number of ciphers supported for a TLS transport */
-#define SIP_TLS_MAX_CIPHERS 64
-
 /*
  * \brief Transport to bind to
  */
@@ -108,23 +133,51 @@ struct ast_sip_transport {
 		);
 	/*! Type of transport */
 	enum ast_transport type;
-	/*! Address and port to bind to */
+	/*!
+	 * \deprecated Moved to ast_sip_transport_state
+	 * \version 13.8.0 deprecated
+	 * Address and port to bind to
+	 */
 	pj_sockaddr host;
 	/*! Number of simultaneous asynchronous operations */
 	unsigned int async_operations;
 	/*! Optional external port for signaling */
 	unsigned int external_signaling_port;
-	/*! TLS settings */
+	/*!
+	 * \deprecated Moved to ast_sip_transport_state
+	 * \version 13.7.1 deprecated
+	 * TLS settings
+	 */
 	pjsip_tls_setting tls;
-	/*! Configured TLS ciphers */
+	/*!
+	 * \deprecated Moved to ast_sip_transport_state
+	 * \version 13.7.1 deprecated
+	 * Configured TLS ciphers
+	 */
 	pj_ssl_cipher ciphers[SIP_TLS_MAX_CIPHERS];
-	/*! Optional local network information, used for NAT purposes */
+	/*!
+	 * \deprecated Moved to ast_sip_transport_state
+	 * \version 13.7.1 deprecated
+	 * Optional local network information, used for NAT purposes
+	 */
 	struct ast_ha *localnet;
-	/*! DNS manager for refreshing the external address */
+	/*!
+	 * \deprecated Moved to ast_sip_transport_state
+	 * \version 13.7.1 deprecated
+	 * DNS manager for refreshing the external address
+	 */
 	struct ast_dnsmgr_entry *external_address_refresher;
-	/*! Optional external address information */
+	/*!
+	 * \deprecated Moved to ast_sip_transport_state
+	 * \version 13.7.1 deprecated
+	 * Optional external address information
+	 */
 	struct ast_sockaddr external_address;
-	/*! Transport state information */
+	/*!
+	 * \deprecated
+	 * \version 13.7.1 deprecated
+	 * Transport state information
+	 */
 	struct ast_sip_transport_state *state;
 	/*! QOS DSCP TOS bits */
 	unsigned int tos;
@@ -132,6 +185,22 @@ struct ast_sip_transport {
 	unsigned int cos;
 	/*! Write timeout */
 	int write_timeout;
+	/*! Allow reload */
+	int allow_reload;
+};
+
+#define SIP_SORCERY_DOMAIN_ALIAS_TYPE "domain_alias"
+
+/*!
+ * Details about a SIP domain alias
+ */
+struct ast_sip_domain_alias {
+	/*! Sorcery object details */
+	SORCERY_OBJECT(details);
+	AST_DECLARE_STRING_FIELDS(
+		/*! Domain to be aliased to */
+		AST_STRING_FIELD(domain);
+	);
 };
 
 /*!
@@ -571,6 +640,8 @@ struct ast_sip_endpoint_media_configuration {
 	unsigned int cos_video;
 	/*! Is g.726 packed in a non standard way */
 	unsigned int g726_non_standard;
+	/*! Bind the RTP instance to the media_address */
+	unsigned int bind_rtp_to_media_address;
 };
 
 /*!
@@ -1124,6 +1195,21 @@ struct ast_sip_endpoint *ast_sip_get_artificial_endpoint(void);
  */
 struct ast_taskprocessor *ast_sip_create_serializer(void);
 
+/*!
+ * \brief Create a new serializer for SIP tasks
+ * \since 13.8.0
+ *
+ * See \ref ast_threadpool_serializer for more information on serializers.
+ * SIP creates serializers so that tasks operating on similar data will run
+ * in sequence.
+ *
+ * \param name Name of the serializer. (must be unique)
+ *
+ * \retval NULL Failure
+ * \retval non-NULL Newly-created serializer
+ */
+struct ast_taskprocessor *ast_sip_create_serializer_named(const char *name);
+
 struct ast_serializer_shutdown_group;
 
 /*!
@@ -1142,6 +1228,22 @@ struct ast_serializer_shutdown_group;
 struct ast_taskprocessor *ast_sip_create_serializer_group(struct ast_serializer_shutdown_group *shutdown_group);
 
 /*!
+ * \brief Create a new serializer for SIP tasks
+ * \since 13.8.0
+ *
+ * See \ref ast_threadpool_serializer for more information on serializers.
+ * SIP creates serializers so that tasks operating on similar data will run
+ * in sequence.
+ *
+ * \param name Name of the serializer. (must be unique)
+ * \param shutdown_group Group shutdown controller. (NULL if no group association)
+ *
+ * \retval NULL Failure
+ * \retval non-NULL Newly-created serializer
+ */
+struct ast_taskprocessor *ast_sip_create_serializer_group_named(const char *name, struct ast_serializer_shutdown_group *shutdown_group);
+
+/*!
  * \brief Set a serializer on a SIP dialog so requests and responses are automatically serialized
  *
  * Passing a NULL serializer is a way to remove a serializer from a dialog.
@@ -2042,6 +2144,17 @@ void ast_sip_unregister_supplement(struct ast_sip_supplement *supplement);
 char *ast_sip_get_debug(void);
 
 /*!
+ * \brief Retrieve the global regcontext setting.
+ *
+ * \since 13.8.0
+ *
+ * \note returned string needs to be de-allocated by caller.
+ *
+ * \retval the global regcontext setting
+ */
+char *ast_sip_get_regcontext(void);
+
+/*!
  * \brief Retrieve the global endpoint_identifier_order setting.
  *
  * Specifies the order by which endpoint identifiers should be regarded.
@@ -2128,4 +2241,57 @@ const char *ast_sip_get_host_ip_string(int af);
  */
 long ast_sip_threadpool_queue_size(void);
 
+/*!
+ * \brief Retrieve transport state
+ * \since 13.7.1
+ *
+ * @param transport_id
+ * @returns transport_state
+ *
+ * \note ao2_cleanup(...) or ao2_ref(...,  -1) must be called on the returned object
+ */
+struct ast_sip_transport_state *ast_sip_get_transport_state(const char *transport_id);
+
+/*!
+ * \brief Retrieves all transport states
+ * \since 13.7.1
+ *
+ * @returns ao2_container
+ *
+ * \note ao2_cleanup(...) or ao2_ref(...,  -1) must be called on the returned object
+ */
+struct ao2_container *ast_sip_get_transport_states(void);
+
+/*!
+ * \brief Sets pjsip_tpselector from ast_sip_transport
+ * \since 13.8.0
+ *
+ * \param transport The transport to be used
+ * \param selector The selector to be populated
+ * \retval 0 success
+ * \retval -1 failure
+ */
+int ast_sip_set_tpselector_from_transport(const struct ast_sip_transport *transport, pjsip_tpselector *selector);
+
+/*!
+ * \brief Sets pjsip_tpselector from ast_sip_transport
+ * \since 13.8.0
+ *
+ * \param transport_name The name of the transport to be used
+ * \param selector The selector to be populated
+ * \retval 0 success
+ * \retval -1 failure
+ */
+int ast_sip_set_tpselector_from_transport_name(const char *transport_name, pjsip_tpselector *selector);
+
+/*!
+ * \brief Set name and number information on an identity header.
+ *
+ * \param pool Memory pool to use for string duplication
+ * \param id_hdr A From, P-Asserted-Identity, or Remote-Party-ID header to modify
+ * \param id The identity information to apply to the header
+ */
+void ast_sip_modify_id_header(pj_pool_t *pool, pjsip_fromto_hdr *id_hdr,
+	const struct ast_party_id *id);
+
 #endif /* _RES_PJSIP_H */
diff --git a/include/asterisk/res_pjsip_session.h b/include/asterisk/res_pjsip_session.h
index ddc87b0..75d37ac 100644
--- a/include/asterisk/res_pjsip_session.h
+++ b/include/asterisk/res_pjsip_session.h
@@ -149,6 +149,8 @@ struct ast_sip_session {
 	enum ast_sip_session_t38state t38state;
 	/*! The AOR associated with this session */
 	struct ast_sip_aor *aor;
+	/*! From header saved at invite creation */
+	pjsip_fromto_hdr *saved_from_hdr;
 };
 
 typedef int (*ast_sip_session_request_creation_cb)(struct ast_sip_session *session, pjsip_tx_data *tdata);
diff --git a/include/asterisk/rtp_engine.h b/include/asterisk/rtp_engine.h
index 80183ed..c79554b 100644
--- a/include/asterisk/rtp_engine.h
+++ b/include/asterisk/rtp_engine.h
@@ -929,7 +929,7 @@ int ast_rtp_instance_set_requested_target_address(struct ast_rtp_instance *insta
  * \since 1.8
  */
 #define ast_rtp_instance_set_remote_address(instance, address) \
-	ast_rtp_instance_set_requested_target_address((instance), (address));
+	ast_rtp_instance_set_requested_target_address((instance), (address))
 
 /*!
  * \brief Set the address that we are expecting to receive RTP on
@@ -1041,7 +1041,7 @@ void ast_rtp_instance_get_requested_target_address(struct ast_rtp_instance *inst
  * \since 1.8
  */
 #define ast_rtp_instance_get_remote_address(instance, address) \
-	ast_rtp_instance_get_incoming_source_address((instance), (address));
+	ast_rtp_instance_get_incoming_source_address((instance), (address))
 
 /*!
  * \brief Get the requested target address of the remote endpoint and
@@ -1077,7 +1077,7 @@ int ast_rtp_instance_get_and_cmp_requested_target_address(struct ast_rtp_instanc
  * \since 1.8
  */
 #define ast_rtp_instance_get_and_cmp_remote_address(instance, address) \
-	ast_rtp_instance_get_and_cmp_requested_target_address((instance), (address));
+	ast_rtp_instance_get_and_cmp_requested_target_address((instance), (address))
 
 /*!
  * \brief Set the value of an RTP instance extended property
diff --git a/include/asterisk/select.h b/include/asterisk/select.h
index d6d34fb..d6f9d32 100644
--- a/include/asterisk/select.h
+++ b/include/asterisk/select.h
@@ -58,9 +58,7 @@ typedef struct {
 #define FD_SET(fd, fds) \
 	do { \
 		TYPEOF_FD_SET_FDS_BITS *bytes = (TYPEOF_FD_SET_FDS_BITS *) fds; \
-		/* 32bit: FD / 32 + ((FD + 1) % 32 ? 1 : 0) < 1024 */ \
-		/* 64bit: FD / 64 + ((FD + 1) % 64 ? 1 : 0) < 512 */ \
-		if (fd / _bitsize(*bytes) + ((fd + 1) % _bitsize(*bytes) ? 1 : 0) < sizeof(*(fds)) / SIZEOF_FD_SET_FDS_BITS) { \
+		if (fd < ast_FDMAX) { \
 			bytes[fd / _bitsize(*bytes)] |= ((TYPEOF_FD_SET_FDS_BITS) 1) << (fd % _bitsize(*bytes)); \
 		} else { \
 			fprintf(stderr, "FD %d exceeds the maximum size of ast_fdset!\n", fd); \
diff --git a/include/asterisk/stasis_cache_pattern.h b/include/asterisk/stasis_cache_pattern.h
index 2ea643e..e61d3e9 100644
--- a/include/asterisk/stasis_cache_pattern.h
+++ b/include/asterisk/stasis_cache_pattern.h
@@ -109,6 +109,8 @@ struct stasis_cp_single;
 /*!
  * \brief Create the 'one' side of the cache pattern.
  *
+ * Create the 'one' and forward to all's topic and topic_cached.
+ *
  * Dispose of using stasis_cp_single_unsubscribe().
  *
  * \param all Corresponding all side.
@@ -119,6 +121,23 @@ struct stasis_cp_single *stasis_cp_single_create(struct stasis_cp_all *all,
 	const char *name);
 
 /*!
+ * \brief Create a sink in the cache pattern
+ *
+ * Create the 'one' but do not automatically forward to the all's topic.
+ * This is useful when aggregating other topic's messages created with
+ * \c stasis_cp_single_create in another caching topic without replicating
+ * those messages in the all's topics.
+ *
+ * Dispose of using stasis_cp_single_unsubscribe().
+ *
+ * \param all Corresponding all side.
+ * \param name Base name for the topics.
+ * \return One side instance
+ */
+struct stasis_cp_single *stasis_cp_sink_create(struct stasis_cp_all *all,
+	const char *name);
+
+/*!
  * \brief Stops caching and forwarding messages.
  *
  * \param one One side of the cache pattern.
diff --git a/include/asterisk/strings.h b/include/asterisk/strings.h
index af5ae6c..3701b53 100644
--- a/include/asterisk/strings.h
+++ b/include/asterisk/strings.h
@@ -145,8 +145,12 @@ static int force_inline attribute_pure ast_ends_with(const char *str, const char
 AST_INLINE_API(
 char * attribute_pure ast_skip_blanks(const char *str),
 {
-	while (*str && ((unsigned char) *str) < 33)
-		str++;
+	if (str) {
+		while (*str && ((unsigned char) *str) < 33) {
+			str++;
+		}
+	}
+
 	return (char *) str;
 }
 )
@@ -184,8 +188,12 @@ char *ast_trim_blanks(char *str),
 AST_INLINE_API(
 char * attribute_pure ast_skip_nonblanks(const char *str),
 {
-	while (*str && ((unsigned char) *str) > 32)
-		str++;
+	if (str) {
+		while (*str && ((unsigned char) *str) > 32) {
+			str++;
+		}
+	}
+
 	return (char *) str;
 }
 )
diff --git a/include/asterisk/taskprocessor.h b/include/asterisk/taskprocessor.h
index 6ebf072..af3ce74 100644
--- a/include/asterisk/taskprocessor.h
+++ b/include/asterisk/taskprocessor.h
@@ -56,6 +56,9 @@
 
 struct ast_taskprocessor;
 
+/*! \brief Suggested maximum taskprocessor name length (less null terminator). */
+#define AST_TASKPROCESSOR_MAX_NAME	45
+
 #define AST_TASKPROCESSOR_HIGH_WATER_LEVEL 500
 
 /*!
@@ -259,6 +262,30 @@ int ast_taskprocessor_execute(struct ast_taskprocessor *tps);
 int ast_taskprocessor_is_task(struct ast_taskprocessor *tps);
 
 /*!
+ * \brief Get the next sequence number to create a human friendly taskprocessor name.
+ * \since 13.8.0
+ *
+ * \return Sequence number for use in creating human friendly taskprocessor names.
+ */
+unsigned int ast_taskprocessor_seq_num(void);
+
+/*!
+ * \brief Build a taskprocessor name with a sequence number on the end.
+ * \since 13.8.0
+ *
+ * \param buf Where to put the built taskprocessor name.
+ * \param size How large is buf including null terminator.
+ * \param format printf format to create the non-sequenced part of the name.
+ *
+ * \note The user supplied part of the taskprocessor name is truncated
+ * to allow the full sequence number to be appended within the supplied
+ * buffer size.
+ *
+ * \return Nothing
+ */
+void __attribute__((format(printf, 3, 4))) ast_taskprocessor_build_name(char *buf, unsigned int size, const char *format, ...);
+
+/*!
  * \brief Return the name of the taskprocessor singleton
  * \since 1.6.1
  */
diff --git a/include/asterisk/tcptls.h b/include/asterisk/tcptls.h
index 0e8d9d0..e1a632c 100644
--- a/include/asterisk/tcptls.h
+++ b/include/asterisk/tcptls.h
@@ -86,7 +86,15 @@ enum ast_ssl_flags {
 	/*! Use SSLv3 for outgoing client connections */
 	AST_SSL_SSLV3_CLIENT = (1 << 4),
 	/*! Use TLSv1 for outgoing client connections */
-	AST_SSL_TLSV1_CLIENT = (1 << 5)
+	AST_SSL_TLSV1_CLIENT = (1 << 5),
+	/*! Use server cipher order instead of the client order */
+	AST_SSL_SERVER_CIPHER_ORDER = (1 << 6),
+	/*! Disable TLSv1 support */
+	AST_SSL_DISABLE_TLSV1 = (1 << 7),
+	/*! Disable TLSv1.1 support */
+	AST_SSL_DISABLE_TLSV11 = (1 << 8),
+	/*! Disable TLSv1.2 support */
+	AST_SSL_DISABLE_TLSV12 = (1 << 9),
 };
 
 struct ast_tls_config {
diff --git a/include/asterisk/time.h b/include/asterisk/time.h
index f2382df..408325b 100644
--- a/include/asterisk/time.h
+++ b/include/asterisk/time.h
@@ -31,10 +31,12 @@
 
 /* We have to let the compiler learn what types to use for the elements of a
    struct timeval since on linux, it's time_t and suseconds_t, but on *BSD,
-   they are just a long. */
-extern struct timeval tv;
-typedef typeof(tv.tv_sec) ast_time_t;
-typedef typeof(tv.tv_usec) ast_suseconds_t;
+   they are just a long.
+   note:dummy_tv_var_for_types never actually gets exported, only used as
+   local place holder. */
+extern struct timeval dummy_tv_var_for_types;
+typedef typeof(dummy_tv_var_for_types.tv_sec) ast_time_t;
+typedef typeof(dummy_tv_var_for_types.tv_usec) ast_suseconds_t;
 
 /*!
  * \brief Computes the difference (in seconds) between two \c struct \c timeval instances.
diff --git a/main/.gitignore b/main/.gitignore
deleted file mode 100644
index 3ff4656..0000000
--- a/main/.gitignore
+++ /dev/null
@@ -1,4 +0,0 @@
-asterisk
-libasteriskssl.so.1
-libasteriskssl.dylib
-version.c
diff --git a/main/Makefile b/main/Makefile
index 2c4b576..70b2a39 100644
--- a/main/Makefile
+++ b/main/Makefile
@@ -22,6 +22,9 @@ SRC:=$(wildcard *.c) ast_expr2.c ast_expr2f.c
 ifeq ($(AST_ASTERISKSSL),yes)
 SRC:=$(filter-out libasteriskssl.c,$(SRC))
 endif
+ifeq ($(PJPROJECT_BUNDLED),yes)
+SRC:=$(filter-out libasteriskpj.c,$(SRC))
+endif
 OBJSFILTER=fskmodem_int.o fskmodem_float.o cygload.o buildinfo.o
 OBJS=$(filter-out $(OBJSFILTER),$(SRC:.c=.o))
 
@@ -200,7 +203,7 @@ ASTSSL_LDLIBS=-L. -lasteriskssl
 ifeq ($(findstring darwin,$(OSARCH)),) # not Darwin
 ASTSSL_LIB:=libasteriskssl.so
 
-$(ASTSSL_LIB).$(ASTSSL_SO_VERSION): _ASTLDFLAGS+=-Wl,-soname=$(ASTSSL_LIB).$(ASTSSL_SO_VERSION)
+$(ASTSSL_LIB).$(ASTSSL_SO_VERSION): _ASTLDFLAGS+=-Wl,-soname=$(ASTSSL_LIB)
 $(ASTSSL_LIB).$(ASTSSL_SO_VERSION): _ASTCFLAGS+=-fPIC -DAST_MODULE=\"asteriskssl\"
 $(ASTSSL_LIB).$(ASTSSL_SO_VERSION): LIBS+=$(ASTSSL_LIBS)
 ifeq ($(GNU_LD),1)
@@ -218,12 +221,14 @@ ifeq ($(GNU_LD),1)
 endif
 	$(ECHO_PREFIX) echo "   [LD] $^ -> $@"
 	$(CMD_PREFIX) $(CC) $(STATIC_BUILD) -o $@ $(CC_LDFLAGS_SO) $^ $(CC_LIBS)
-ifneq ($(LDCONFIG),)
-	$(LDCONFIG) $(LDCONFIG_FLAGS) .
-endif
 
 $(ASTSSL_LIB): $(ASTSSL_LIB).$(ASTSSL_SO_VERSION)
-	$(LN) -sf $< $@
+	$(ECHO_PREFIX) echo "   [LN] $< -> $@"
+ifneq ($(LDCONFIG),)
+	$(CMD_PREFIX) $(LDCONFIG) $(LDCONFIG_FLAGS) . 2>/dev/null
+else
+	$(CMD_PREFIX) $(LN) -sf $< $@
+endif
 
 else # Darwin
 ASTSSL_LIB:=libasteriskssl.dylib
@@ -243,12 +248,92 @@ endif
 
 endif
 
+libasteriskpj.o: _ASTCFLAGS+=$(PJPROJECT_INCLUDE)
+
+ifeq ($(PJPROJECT_BUNDLED),yes)
+
+ASTPJ_SO_VERSION=2
+ASTPJ_LDLIBS=-L. -lasteriskpj
+
+-include $(ASTTOPDIR)/$(PJPROJECT_DIR)/build.mak
+
+PJPROJECT_LDLIBS := \
+-Wl,--whole-archive \
+$(PJSUA_LIB_LDLIB) \
+$(PJSIP_UA_LDLIB) \
+$(PJSIP_SIMPLE_LDLIB) \
+$(PJSIP_LDLIB) \
+$(PJNATH_LDLIB) \
+$(PJMEDIA_CODEC_LDLIB) \
+$(PJMEDIA_VIDEODEV_LDLIB) \
+$(PJMEDIA_AUDIODEV_LDLIB) \
+$(PJMEDIA_LDLIB) \
+$(PJLIB_UTIL_LDLIB) \
+$(PJLIB_LDLIB) \
+-Wl,--no-whole-archive \
+$(APP_THIRD_PARTY_LIBS) \
+$(APP_THIRD_PARTY_EXT)
+
+ifeq ($(findstring darwin,$(OSARCH)),) # not Darwin
+ASTPJ_LIB:=libasteriskpj.so
+
+libasteriskpj.exports: $(ASTTOPDIR)/$(PJPROJECT_DIR)/pjproject.symbols
+	$(ECHO_PREFIX) echo "   [GENERATE] libasteriskpj.exports"
+ifeq ($(GNU_LD),1)
+	$(CMD_PREFIX) echo -e "{\n\tglobal:" > libasteriskpj.exports
+	$(CMD_PREFIX) sed -r -e "s/.*/\t\t$(LINKER_SYMBOL_PREFIX)&;/" $(ASTTOPDIR)/$(PJPROJECT_DIR)/pjproject.symbols >> libasteriskpj.exports
+	$(CMD_PREFIX) echo -e "\t\t$(LINKER_SYMBOL_PREFIX)ast_pj_init;\n" >> libasteriskpj.exports
+	$(CMD_PREFIX) echo -e "\tlocal:\n\t\t*;\n};" >> libasteriskpj.exports
+endif
+
+$(ASTPJ_LIB).$(ASTPJ_SO_VERSION): _ASTLDFLAGS+=-Wl,-soname=$(ASTPJ_LIB) $(PJ_LDFLAGS)
+$(ASTPJ_LIB).$(ASTPJ_SO_VERSION): _ASTCFLAGS+=-fPIC -DAST_MODULE=\"asteriskpj\" $(PJ_CFLAGS)
+$(ASTPJ_LIB).$(ASTPJ_SO_VERSION): LIBS+=$(PJPROJECT_LDLIBS) -lssl -lcrypto -luuid -lm -lrt -lpthread
+ifeq ($(GNU_LD),1)
+    $(ASTPJ_LIB).$(ASTPJ_SO_VERSION): SO_SUPPRESS_SYMBOLS=-Wl,--version-script,libasteriskpj.exports,--warn-common
+endif
+$(ASTPJ_LIB).$(ASTPJ_SO_VERSION): SOLINK=$(DYLINK)
+
+# These rules are duplicated from $(ASTTOPDIR)/Makefile.rules because the library name
+# being built does not match the "%.so" pattern; there are also additional steps
+# required to build a proper shared library (as opposed to the 'loadable module'
+# type that are built by the standard rules)
+$(ASTPJ_LIB).$(ASTPJ_SO_VERSION): libasteriskpj.o libasteriskpj.exports
+	$(ECHO_PREFIX) echo "   [LD] $< -> $@"
+	$(CMD_PREFIX) $(CC) $(STATIC_BUILD) -o $@ $(CC_LDFLAGS_SO) $< $(CC_LIBS)
+
+$(ASTPJ_LIB): $(ASTPJ_LIB).$(ASTPJ_SO_VERSION)
+	$(ECHO_PREFIX) echo "   [LN] $< -> $@"
+ifneq ($(LDCONFIG),)
+	$(CMD_PREFIX) $(LDCONFIG) $(LDCONFIG_FLAGS) . 2>/dev/null
+else
+	$(CMD_PREFIX) $(LN) -sf $< $@
+endif
+
+else # Darwin
+ASTPJ_LIB:=libasteriskpj.dylib
+
+# -install_name allows library to be found if installed somewhere other than
+# /lib or /usr/lib
+$(ASTPJ_LIB): _ASTLDFLAGS+=-dynamiclib -install_name $(ASTLIBDIR)/$(ASTPJ_LIB) $(PJ_LDFLAGS)
+$(ASTPJ_LIB): _ASTCFLAGS+=-fPIC -DAST_MODULE=\"asteriskpj\" $(PJ_CFLAGS)
+$(ASTPJ_LIB): LIBS+=$(PJPROJECT_LIBS)  -lssl -lcrypto -luuid -lm -lrt -lpthread
+$(ASTPJ_LIB): SOLINK=$(DYLINK)
+
+# Special rules for building a shared library (not a dynamically loadable module)
+$(ASTPJ_LIB): libasteriskpj.o
+	$(ECHO_PREFIX) echo "   [LD] $^ -> $@"
+	$(CMD_PREFIX) $(CC) $(STATIC_BUILD) -o $@ $(CC_LDFLAGS_SO) $^ $(CC_LIBS)
+endif
+
+endif
+
 tcptls.o: _ASTCFLAGS+=$(OPENSSL_INCLUDE)
 
-$(MAIN_TGT): $(OBJS) $(ASTSSL_LIB) $(LIBEDIT_OBJ) $(AST_EMBED_LDSCRIPTS)
+$(MAIN_TGT): $(OBJS) $(ASTSSL_LIB) $(ASTPJ_LIB) $(LIBEDIT_OBJ) $(AST_EMBED_LDSCRIPTS)
 	@$(CC) -c -o buildinfo.o $(_ASTCFLAGS) buildinfo.c $(ASTCFLAGS)
 	$(ECHO_PREFIX) echo "   [LD] $(OBJS) $(LIBEDIT_OBJ) $(AST_EMBED_LDSCRIPTS) -> $@"
-	$(CMD_PREFIX) $(CXX) $(STATIC_BUILD) -o $@ $(ASTLINK) $(AST_EMBED_LDFLAGS) $(_ASTLDFLAGS) $(ASTLDFLAGS) $(OBJS) $(ASTSSL_LDLIBS) $(LIBEDIT_OBJ) $(AST_EMBED_LDSCRIPTS) buildinfo.o $(AST_LIBS) $(AST_EMBED_LIBS) $(GMIMELDFLAGS) $(LIBEDIT_LIB)
+	$(CMD_PREFIX) $(CXX) $(STATIC_BUILD) -o $@ $(ASTLINK) $(AST_EMBED_LDFLAGS) $(_ASTLDFLAGS) $(ASTLDFLAGS) $(OBJS) $(ASTSSL_LDLIBS) $(ASTPJ_LDLIBS) $(LIBEDIT_OBJ) $(AST_EMBED_LDSCRIPTS) buildinfo.o $(AST_LIBS) $(AST_EMBED_LIBS) $(GMIMELDFLAGS) $(LIBEDIT_LIB)
 
 ifeq ($(GNU_LD),1)
 $(MAIN_TGT): asterisk.exports
@@ -265,16 +350,29 @@ ifeq ($(findstring darwin,$(OSARCH)),) # not Darwin
 else # Darwin
 	$(INSTALL) -m 755 $(ASTSSL_LIB) "$(DESTDIR)$(ASTLIBDIR)/"
 endif
+endif
+ifeq ($(PJPROJECT_BUNDLED),yes)
+ifeq ($(findstring darwin,$(OSARCH)),) # not Darwin
+	$(INSTALL) -m 755 $(ASTPJ_LIB).$(ASTPJ_SO_VERSION) "$(DESTDIR)$(ASTLIBDIR)/"
+	$(LN) -sf $(ASTPJ_LIB).$(ASTPJ_SO_VERSION) "$(DESTDIR)$(ASTLIBDIR)/$(ASTPJ_LIB)"
+else # Darwin
+	$(INSTALL) -m 755 $(ASTPJ_LIB) "$(DESTDIR)$(ASTLIBDIR)/"
+endif
+endif
 ifneq ($(LDCONFIG),)
 	$(LDCONFIG) $(LDCONFIG_FLAGS) "$(DESTDIR)$(ASTLIBDIR)/"
 endif
-endif
 	$(LN) -sf asterisk "$(DESTDIR)$(ASTSBINDIR)/rasterisk"
 
 binuninstall:
 	rm -f "$(DESTDIR)$(ASTSBINDIR)/$(MAIN_TGT)"
 	rm -f "$(DESTDIR)$(ASTSBINDIR)/rasterisk"
+ifneq ($(ASTSSL_LIB).$(ASTSSL_SO_VERSION),.)
 	rm -f "$(DESTDIR)$(ASTLIBDIR)/$(ASTSSL_LIB).$(ASTSSL_SO_VERSION)"
+endif
+ifneq ($(ASTPJ_LIB).$(ASTPJ_SO_VERSION),.)
+	rm -f "$(DESTDIR)$(ASTLIBDIR)/$(ASTPJ_LIB).$(ASTPJ_SO_VERSION)"
+endif
 ifneq ($(LDCONFIG),)
 	$(LDCONFIG) $(LDCONFIG_FLAGS) "$(DESTDIR)$(ASTLIBDIR)/"
 endif
@@ -284,7 +382,12 @@ clean::
 ifeq ($(AST_ASTERISKSSL),yes)
 	rm -f $(ASTSSL_LIB) $(ASTSSL_LIB).*
 endif
-	rm -f asterisk.exports libasteriskssl.exports
+	rm -f libasteriskpj.o
+	rm -f libasteriskpj.so* libasteriskpj.dynlib
+	rm -f .libasteriskpj*
+
+	rm -f asterisk.exports libasteriskssl.exports libasteriskpj.exports
 	@if [ -f editline/Makefile ]; then $(MAKE) -C editline distclean ; fi
 	@$(MAKE) -C stdtime clean
 	rm -f libresample/src/*.o
+	rm -f *.tmp
diff --git a/main/app.c b/main/app.c
index 8c33b91..a89e5b0 100644
--- a/main/app.c
+++ b/main/app.c
@@ -357,6 +357,11 @@ int ast_app_exec_macro(struct ast_channel *autoservice_chan, struct ast_channel
 	if (autoservice_chan) {
 		ast_autoservice_stop(autoservice_chan);
 	}
+
+	if (ast_check_hangup_locked(macro_chan)) {
+		ast_queue_hangup(macro_chan);
+	}
+
 	return res;
 }
 
@@ -433,6 +438,11 @@ int ast_app_exec_sub(struct ast_channel *autoservice_chan, struct ast_channel *s
 	if (autoservice_chan) {
 		ast_autoservice_stop(autoservice_chan);
 	}
+
+	if (!ignore_hangup && ast_check_hangup_locked(sub_chan)) {
+		ast_queue_hangup(sub_chan);
+	}
+
 	return res;
 }
 
@@ -494,7 +504,7 @@ int __ast_vm_register(const struct ast_vm_functions *vm_table, struct ast_module
 	if (table) {
 		ast_log(LOG_WARNING, "Voicemail provider already registered by %s.\n",
 			table->module_name);
-		return -1;
+		return AST_MODULE_LOAD_DECLINE;
 	}
 
 	table = ao2_alloc_options(sizeof(*table), NULL, AO2_ALLOC_OPT_LOCK_NOLOCK);
@@ -605,7 +615,7 @@ int __ast_vm_greeter_register(const struct ast_vm_greeter_functions *vm_table, s
 	if (table) {
 		ast_log(LOG_WARNING, "Voicemail greeter provider already registered by %s.\n",
 			table->module_name);
-		return -1;
+		return AST_MODULE_LOAD_DECLINE;
 	}
 
 	table = ao2_alloc_options(sizeof(*table), NULL, AO2_ALLOC_OPT_LOCK_NOLOCK);
diff --git a/main/asterisk.c b/main/asterisk.c
index 3f16caf..3985149 100644
--- a/main/asterisk.c
+++ b/main/asterisk.c
@@ -1608,15 +1608,14 @@ static void *listener(void *unused)
 			if (errno != EINTR)
 				ast_log(LOG_WARNING, "Accept returned %d: %s\n", s, strerror(errno));
 		} else {
-#if !defined(SO_PASSCRED)
-			{
-#else
+#if defined(SO_PASSCRED)
 			int sckopt = 1;
 			/* turn on socket credentials passing. */
 			if (setsockopt(s, SOL_SOCKET, SO_PASSCRED, &sckopt, sizeof(sckopt)) < 0) {
 				ast_log(LOG_WARNING, "Unable to turn on socket credentials passing\n");
-			} else {
+			} else
 #endif
+			{
 				for (x = 0; x < AST_MAX_CONNECTS; x++) {
 					if (consoles[x].fd >= 0) {
 						continue;
@@ -2808,11 +2807,12 @@ static int ast_el_read_char(EditLine *editline, char *cp)
 
 			console_print(buf, 0);
 
-			if ((res < EL_BUF_SIZE - 1) && ((buf[res-1] == '\n') || (buf[res-2] == '\n'))) {
+			if ((res < EL_BUF_SIZE - 1) && ((buf[res-1] == '\n') || (res >= 2 && buf[res-2] == '\n'))) {
 				*cp = CC_REFRESH;
 				return(1);
-			} else
+			} else {
 				lastpos = 1;
+			}
 		}
 	}
 
@@ -4530,6 +4530,11 @@ static void asterisk_daemon(int isroot, const char *runuser, const char *rungrou
 		exit(1);
 	}
 
+	if (ast_pj_init()) {
+		printf("Failed: ast_pj_init\n%s", term_quit());
+		exit(1);
+	}
+
 	if (app_init()) {
 		printf("App core initialization failed.\n%s", term_quit());
 		exit(1);
@@ -4643,6 +4648,36 @@ static void asterisk_daemon(int isroot, const char *runuser, const char *rungrou
 		exit(1);
 	}
 
+	if (load_pbx_builtins()) {
+		printf("Failed: load_pbx_builtins\n%s", term_quit());
+		exit(1);
+	}
+
+	if (load_pbx_functions_cli()) {
+		printf("Failed: load_pbx_functions_cli\n%s", term_quit());
+		exit(1);
+	}
+
+	if (load_pbx_variables()) {
+		printf("Failed: load_pbx_variables\n%s", term_quit());
+		exit(1);
+	}
+
+	if (load_pbx_switch()) {
+		printf("Failed: load_pbx_switch\n%s", term_quit());
+		exit(1);
+	}
+
+	if (load_pbx_app()) {
+		printf("Failed: load_pbx_app\n%s", term_quit());
+		exit(1);
+	}
+
+	if (load_pbx_hangup_handler()) {
+		printf("Failed: load_pbx_hangup_handler\n%s", term_quit());
+		exit(1);
+	}
+
 	if (ast_local_init()) {
 		printf("Failed: ast_local_init\n%s", term_quit());
 		exit(1);
diff --git a/main/astmm.c b/main/astmm.c
index 5812174..3317784 100644
--- a/main/astmm.c
+++ b/main/astmm.c
@@ -672,6 +672,32 @@ int __ast_vasprintf(char **strp, const char *fmt, va_list ap, const char *file,
 	return size;
 }
 
+/*!
+ * \internal
+ * \brief Count the number of bytes in the specified freed region.
+ *
+ * \param freed Already freed region blocks storage.
+ *
+ * \note reglock must be locked before calling.
+ *
+ * \return Number of bytes in freed region.
+ */
+static size_t freed_regions_size(struct ast_freed_regions *freed)
+{
+	size_t total_len = 0;
+	int idx;
+	struct ast_region *old;
+
+	for (idx = 0; idx < ARRAY_LEN(freed->regions); ++idx) {
+		old = freed->regions[idx];
+		if (old) {
+			total_len += old->len;
+		}
+	}
+
+	return total_len;
+}
+
 static char *handle_memory_atexit_list(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
 {
 	switch (cmd) {
@@ -774,12 +800,54 @@ static char *handle_memory_atexit_summary(struct ast_cli_entry *e, int cmd, stru
 	return CLI_SUCCESS;
 }
 
+/*!
+ * \internal
+ * \brief Common summary output at the end of the memory show commands.
+ *
+ * \param fd CLI output file descriptor.
+ * \param whales_len Accumulated size of free large allocations.
+ * \param minnows_len Accumulated size of free small allocations.
+ * \param total_len Accumulated size of all current allocations.
+ * \param selected_len Accumulated size of the selected allocations.
+ * \param cache_len Accumulated size of the allocations that are part of a cache.
+ * \param count Number of selected allocations.
+ *
+ * \return Nothing
+ */
+static void print_memory_show_common_stats(int fd,
+	unsigned int whales_len,
+	unsigned int minnows_len,
+	unsigned int total_len,
+	unsigned int selected_len,
+	unsigned int cache_len,
+	unsigned int count)
+{
+	if (cache_len) {
+		ast_cli(fd, "%10u bytes allocated (%u in caches) in %u selected allocations\n\n",
+			selected_len, cache_len, count);
+	} else {
+		ast_cli(fd, "%10u bytes allocated in %u selected allocations\n\n",
+			selected_len, count);
+	}
+
+	ast_cli(fd, "%10u bytes in all allocations\n", total_len);
+	ast_cli(fd, "%10u bytes in deferred free large allocations\n", whales_len);
+	ast_cli(fd, "%10u bytes in deferred free small allocations\n", minnows_len);
+	ast_cli(fd, "%10u bytes in deferred free allocations\n",
+		whales_len + minnows_len);
+	ast_cli(fd, "%10u bytes in all allocations and deferred free allocations\n",
+		total_len + whales_len + minnows_len);
+}
+
 static char *handle_memory_show_allocations(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
 {
 	const char *fn = NULL;
 	struct ast_region *reg;
 	unsigned int idx;
-	unsigned int len = 0;
+	unsigned int whales_len;
+	unsigned int minnows_len;
+	unsigned int total_len = 0;
+	unsigned int selected_len = 0;
 	unsigned int cache_len = 0;
 	unsigned int count = 0;
 
@@ -813,6 +881,7 @@ static char *handle_memory_show_allocations(struct ast_cli_entry *e, int cmd, st
 	ast_mutex_lock(&reglock);
 	for (idx = 0; idx < ARRAY_LEN(regions); ++idx) {
 		for (reg = regions[idx]; reg; reg = AST_LIST_NEXT(reg, node)) {
+			total_len += reg->len;
 			if (fn && strcasecmp(fn, reg->file)) {
 				continue;
 			}
@@ -823,21 +892,21 @@ static char *handle_memory_show_allocations(struct ast_cli_entry *e, int cmd, st
 				(unsigned int) reg->len, reg->cache ? " (cache)" : "",
 				reg->func, reg->lineno, reg->file);
 
-			len += reg->len;
+			selected_len += reg->len;
 			if (reg->cache) {
 				cache_len += reg->len;
 			}
 			++count;
 		}
 	}
+
+	whales_len = freed_regions_size(&whales);
+	minnows_len = freed_regions_size(&minnows);
 	ast_mutex_unlock(&reglock);
 
-	if (cache_len) {
-		ast_cli(a->fd, "%u bytes allocated (%u in caches) in %u allocations\n",
-			len, cache_len, count);
-	} else {
-		ast_cli(a->fd, "%u bytes allocated in %u allocations\n", len, count);
-	}
+	print_memory_show_common_stats(a->fd,
+		whales_len, minnows_len, total_len,
+		selected_len, cache_len, count);
 
 	return CLI_SUCCESS;
 }
@@ -850,7 +919,10 @@ static char *handle_memory_show_summary(struct ast_cli_entry *e, int cmd, struct
 	int idx;
 	int cmp;
 	struct ast_region *reg;
-	unsigned int len = 0;
+	unsigned int whales_len;
+	unsigned int minnows_len;
+	unsigned int total_len = 0;
+	unsigned int selected_len = 0;
 	unsigned int cache_len = 0;
 	unsigned int count = 0;
 	struct file_summary {
@@ -868,7 +940,7 @@ static char *handle_memory_show_summary(struct ast_cli_entry *e, int cmd, struct
 		e->usage =
 			"Usage: memory show summary [<file>]\n"
 			"       Summarizes heap memory allocations by file, or optionally\n"
-			"       by line, if a file is specified.\n";
+			"       by line if a file is specified.\n";
 		return NULL;
 	case CLI_GENERATE:
 		return NULL;
@@ -883,6 +955,7 @@ static char *handle_memory_show_summary(struct ast_cli_entry *e, int cmd, struct
 	ast_mutex_lock(&reglock);
 	for (idx = 0; idx < ARRAY_LEN(regions); ++idx) {
 		for (reg = regions[idx]; reg; reg = AST_LIST_NEXT(reg, node)) {
+			total_len += reg->len;
 			if (fn) {
 				if (strcasecmp(fn, reg->file)) {
 					continue;
@@ -941,11 +1014,14 @@ static char *handle_memory_show_summary(struct ast_cli_entry *e, int cmd, struct
 			++cur->count;
 		}
 	}
+
+	whales_len = freed_regions_size(&whales);
+	minnows_len = freed_regions_size(&minnows);
 	ast_mutex_unlock(&reglock);
 
 	/* Dump the whole list */
 	for (cur = list; cur; cur = cur->next) {
-		len += cur->len;
+		selected_len += cur->len;
 		cache_len += cur->cache_len;
 		count += cur->count;
 		if (cur->cache_len) {
@@ -967,12 +1043,9 @@ static char *handle_memory_show_summary(struct ast_cli_entry *e, int cmd, struct
 		}
 	}
 
-	if (cache_len) {
-		ast_cli(a->fd, "%u bytes allocated (%u in caches) in %u allocations\n",
-			len, cache_len, count);
-	} else {
-		ast_cli(a->fd, "%u bytes allocated in %u allocations\n", len, count);
-	}
+	print_memory_show_common_stats(a->fd,
+		whales_len, minnows_len, total_len,
+		selected_len, cache_len, count);
 
 	return CLI_SUCCESS;
 }
diff --git a/main/autochan.c b/main/autochan.c
index 7da249a..c1e8b83 100644
--- a/main/autochan.c
+++ b/main/autochan.c
@@ -51,7 +51,7 @@ struct ast_autochan *ast_autochan_setup(struct ast_channel *chan)
 
 	autochan->chan = ast_channel_ref(chan);
 
-	ast_channel_lock(autochan->chan);
+	ast_channel_lock(autochan->chan); /* autochan is still private, no need for ast_autochan_channel_lock() */
 	AST_LIST_INSERT_TAIL(ast_channel_autochans(autochan->chan), autochan, list);
 	ast_channel_unlock(autochan->chan);
 
@@ -64,7 +64,7 @@ void ast_autochan_destroy(struct ast_autochan *autochan)
 {
 	struct ast_autochan *autochan_iter;
 
-	ast_channel_lock(autochan->chan);
+	ast_autochan_channel_lock(autochan);
 	AST_LIST_TRAVERSE_SAFE_BEGIN(ast_channel_autochans(autochan->chan), autochan_iter, list) {
 		if (autochan_iter == autochan) {
 			AST_LIST_REMOVE_CURRENT(list);
@@ -73,7 +73,7 @@ void ast_autochan_destroy(struct ast_autochan *autochan)
 		}
 	}
 	AST_LIST_TRAVERSE_SAFE_END;
-	ast_channel_unlock(autochan->chan);
+	ast_autochan_channel_unlock(autochan);
 
 	autochan->chan = ast_channel_unref(autochan->chan);
 
diff --git a/main/bridge.c b/main/bridge.c
index b5c5951..fd83cfb 100644
--- a/main/bridge.c
+++ b/main/bridge.c
@@ -2187,7 +2187,7 @@ int bridge_do_move(struct ast_bridge *dst_bridge, struct ast_bridge_channel *bri
 
 	bridge_channel_moving(bridge_channel, orig_bridge, dst_bridge);
 
-	if (bridge_channel_internal_push(bridge_channel)) {
+	if (bridge_channel_internal_push_full(bridge_channel, optimized)) {
 		/* Try to put the channel back into the original bridge. */
 		ast_bridge_features_remove(bridge_channel->features,
 			AST_BRIDGE_HOOK_REMOVE_ON_PULL);
@@ -2200,7 +2200,6 @@ int bridge_do_move(struct ast_bridge *dst_bridge, struct ast_bridge_channel *bri
 					AST_BRIDGE_HOOK_REMOVE_ON_PULL);
 				ast_bridge_channel_leave_bridge(bridge_channel,
 					BRIDGE_CHANNEL_STATE_END_NO_DISSOLVE, bridge_channel->bridge->cause);
-				bridge_channel_settle_owed_events(orig_bridge, bridge_channel);
 			}
 		} else {
 			ast_bridge_channel_leave_bridge(bridge_channel,
@@ -2208,7 +2207,7 @@ int bridge_do_move(struct ast_bridge *dst_bridge, struct ast_bridge_channel *bri
 			bridge_channel_settle_owed_events(orig_bridge, bridge_channel);
 		}
 		res = -1;
-	} else {
+	} else if (!optimized) {
 		bridge_channel_settle_owed_events(orig_bridge, bridge_channel);
 	}
 
@@ -4037,19 +4036,25 @@ static enum ast_transfer_result attended_transfer_bridge(struct ast_channel *cha
 	BRIDGE_LOCK_ONE_OR_BOTH(bridge1, bridge2);
 
 	if (bridge2) {
-		RAII_VAR(struct ast_channel *, local_chan2, NULL, ao2_cleanup);
 		struct ast_channel *locals[2];
 
-		ast_channel_lock(local_chan);
-		local_chan2 = ast_local_get_peer(local_chan);
-		ast_channel_unlock(local_chan);
-
-		ast_assert(local_chan2 != NULL);
+		/* Have to lock everything just in case a hangup comes in early */
+		ast_local_lock_all(local_chan, &locals[0], &locals[1]);
+		if (!locals[0] || !locals[1]) {
+			ast_log(LOG_ERROR, "Transfer failed probably due to an early hangup - "
+				"missing other half of '%s'\n", ast_channel_name(local_chan));
+			ast_local_unlock_all(local_chan);
+			ao2_cleanup(local_chan);
+			return AST_BRIDGE_TRANSFER_FAIL;
+		}
 
-		locals[0] = local_chan;
-		locals[1] = local_chan2;
+		/* Make sure the peer is properly set */
+		if (local_chan != locals[0]) {
+			SWAP(locals[0], locals[1]);
+		}
 
 		ast_attended_transfer_message_add_link(transfer_msg, locals);
+		ast_local_unlock_all(local_chan);
 	} else {
 		ast_attended_transfer_message_add_app(transfer_msg, app, local_chan);
 	}
diff --git a/main/bridge_basic.c b/main/bridge_basic.c
index 3bbd173..c4cf2a0 100644
--- a/main/bridge_basic.c
+++ b/main/bridge_basic.c
@@ -1296,8 +1296,6 @@ struct attended_transfer_properties {
 		AST_STRING_FIELD(exten);
 		/*! Context of transfer target */
 		AST_STRING_FIELD(context);
-		/*! Sound to play on failure */
-		AST_STRING_FIELD(failsound);
 		/*! Sound to play when transfer completes */
 		AST_STRING_FIELD(xfersound);
 		/*! The channel technology of the transferer channel */
@@ -1421,12 +1419,21 @@ static struct attended_transfer_properties *attended_transfer_properties_alloc(
 	struct ast_flags *transferer_features;
 
 	props = ao2_alloc(sizeof(*props), attended_transfer_properties_destructor);
-	if (!props || ast_string_field_init(props, 64)) {
+	if (!props) {
+		ast_log(LOG_ERROR, "Unable to create props - channel %s, context %s\n",
+			ast_channel_name(transferer), context);
 		return NULL;
 	}
 
 	ast_cond_init(&props->cond, NULL);
 
+	if (ast_string_field_init(props, 64)) {
+		ast_log(LOG_ERROR, "Unable to initialize prop fields - channel %s, context %s\n",
+			ast_channel_name(transferer), context);
+		ao2_ref(props, -1);
+		return NULL;
+	}
+
 	props->target_framehook_id = -1;
 	props->transferer = ast_channel_ref(transferer);
 
@@ -1447,7 +1454,6 @@ static struct attended_transfer_properties *attended_transfer_properties_alloc(
 	props->atxfernoanswertimeout = xfer_cfg->atxfernoanswertimeout;
 	props->atxferloopdelay = xfer_cfg->atxferloopdelay;
 	ast_string_field_set(props, context, get_transfer_context(transferer, context));
-	ast_string_field_set(props, failsound, xfer_cfg->xferfailsound);
 	ast_string_field_set(props, xfersound, xfer_cfg->xfersound);
 	ao2_ref(xfer_cfg, -1);
 
@@ -1708,6 +1714,44 @@ static void play_sound(struct ast_channel *chan, const char *sound)
 }
 
 /*!
+ * \brief Helper method to play a fail sound on a channel in a bridge
+ *
+ * \param chan The channel to play the fail sound to
+ */
+static void play_failsound(struct ast_channel *chan)
+{
+	char *sound;
+
+	ast_channel_lock(chan);
+	sound = ast_get_chan_features_xferfailsound(chan);
+	ast_channel_unlock(chan);
+
+	if (sound) {
+		play_sound(chan, sound);
+		ast_free(sound);
+	}
+}
+
+/*!
+ * \brief Helper method to stream a fail sound on a channel
+ *
+ * \param chan The channel to stream the fail sound to
+ */
+static void stream_failsound(struct ast_channel *chan)
+{
+	char *sound;
+
+	ast_channel_lock(chan);
+	sound = ast_get_chan_features_xferfailsound(chan);
+	ast_channel_unlock(chan);
+
+	if (sound) {
+		ast_stream_and_wait(chan, sound, AST_DIGIT_NONE);
+		ast_free(sound);
+	}
+}
+
+/*!
  * \brief Helper method to place a channel in a bridge on hold
  */
 static void hold(struct ast_channel *chan)
@@ -2049,7 +2093,7 @@ static enum attended_transfer_state calling_target_exit(struct attended_transfer
 {
 	switch (stimulus) {
 	case STIMULUS_TRANSFEREE_HANGUP:
-		play_sound(props->transferer, props->failsound);
+		play_failsound(props->transferer);
 		publish_transfer_fail(props);
 		return TRANSFER_FAIL;
 	case STIMULUS_DTMF_ATXFER_COMPLETE:
@@ -2061,7 +2105,7 @@ static enum attended_transfer_state calling_target_exit(struct attended_transfer
 	case STIMULUS_TRANSFER_TARGET_HANGUP:
 	case STIMULUS_TIMEOUT:
 	case STIMULUS_DTMF_ATXFER_ABORT:
-		play_sound(props->transferer, props->failsound);
+		play_failsound(props->transferer);
 		return TRANSFER_REBRIDGE;
 	case STIMULUS_DTMF_ATXFER_THREEWAY:
 		bridge_unhold(props->transferee_bridge);
@@ -2090,7 +2134,7 @@ static enum attended_transfer_state hesitant_exit(struct attended_transfer_prope
 {
 	switch (stimulus) {
 	case STIMULUS_TRANSFEREE_HANGUP:
-		play_sound(props->transferer, props->failsound);
+		play_failsound(props->transferer);
 		publish_transfer_fail(props);
 		return TRANSFER_FAIL;
 	case STIMULUS_DTMF_ATXFER_COMPLETE:
@@ -2101,7 +2145,7 @@ static enum attended_transfer_state hesitant_exit(struct attended_transfer_prope
 	case STIMULUS_TRANSFER_TARGET_HANGUP:
 	case STIMULUS_TIMEOUT:
 	case STIMULUS_DTMF_ATXFER_ABORT:
-		play_sound(props->transferer, props->failsound);
+		play_failsound(props->transferer);
 		return TRANSFER_RESUME;
 	case STIMULUS_DTMF_ATXFER_THREEWAY:
 		return TRANSFER_THREEWAY;
@@ -2163,7 +2207,7 @@ static enum attended_transfer_state consulting_exit(struct attended_transfer_pro
 		 * a sound to the transferer to indicate the transferee is gone.
 		 */
 		bridge_basic_change_personality(props->target_bridge, BRIDGE_BASIC_PERSONALITY_NORMAL, NULL);
-		play_sound(props->transferer, props->failsound);
+		play_failsound(props->transferer);
 		ast_bridge_merge_inhibit(props->target_bridge, -1);
 		/* These next two lines are here to ensure that our reference to the target bridge
 		 * is cleaned up properly and that the target bridge is not destroyed when the
@@ -2178,8 +2222,9 @@ static enum attended_transfer_state consulting_exit(struct attended_transfer_pro
 		bridge_unhold(props->transferee_bridge);
 		return TRANSFER_COMPLETE;
 	case STIMULUS_TRANSFER_TARGET_HANGUP:
+		return TRANSFER_REBRIDGE;
 	case STIMULUS_DTMF_ATXFER_ABORT:
-		play_sound(props->transferer, props->failsound);
+		play_failsound(props->transferer);
 		return TRANSFER_REBRIDGE;
 	case STIMULUS_DTMF_ATXFER_THREEWAY:
 		bridge_unhold(props->transferee_bridge);
@@ -2211,7 +2256,7 @@ static enum attended_transfer_state double_checking_exit(struct attended_transfe
 {
 	switch (stimulus) {
 	case STIMULUS_TRANSFEREE_HANGUP:
-		play_sound(props->transferer, props->failsound);
+		play_failsound(props->transferer);
 		publish_transfer_fail(props);
 		return TRANSFER_FAIL;
 	case STIMULUS_TRANSFERER_HANGUP:
@@ -2221,7 +2266,7 @@ static enum attended_transfer_state double_checking_exit(struct attended_transfe
 		return TRANSFER_COMPLETE;
 	case STIMULUS_TRANSFER_TARGET_HANGUP:
 	case STIMULUS_DTMF_ATXFER_ABORT:
-		play_sound(props->transferer, props->failsound);
+		play_failsound(props->transferer);
 		return TRANSFER_RESUME;
 	case STIMULUS_DTMF_ATXFER_THREEWAY:
 		bridge_unhold(props->target_bridge);
@@ -3297,7 +3342,7 @@ static int feature_attended_transfer(struct ast_bridge_channel *bridge_channel,
 	props->transfer_target = dial_transfer(bridge_channel->chan, destination);
 	if (!props->transfer_target) {
 		ast_log(LOG_ERROR, "Unable to request outbound channel for attended transfer target.\n");
-		ast_stream_and_wait(props->transferer, props->failsound, AST_DIGIT_NONE);
+		stream_failsound(props->transferer);
 		ast_bridge_channel_write_unhold(bridge_channel);
 		attended_transfer_properties_shutdown(props);
 		return 0;
@@ -3308,7 +3353,7 @@ static int feature_attended_transfer(struct ast_bridge_channel *bridge_channel,
 	props->target_bridge = ast_bridge_basic_new();
 	if (!props->target_bridge) {
 		ast_log(LOG_ERROR, "Unable to create bridge for attended transfer target.\n");
-		ast_stream_and_wait(props->transferer, props->failsound, AST_DIGIT_NONE);
+		stream_failsound(props->transferer);
 		ast_bridge_channel_write_unhold(bridge_channel);
 		ast_hangup(props->transfer_target);
 		props->transfer_target = NULL;
@@ -3319,7 +3364,7 @@ static int feature_attended_transfer(struct ast_bridge_channel *bridge_channel,
 
 	if (attach_framehook(props, props->transfer_target)) {
 		ast_log(LOG_ERROR, "Unable to attach framehook to transfer target.\n");
-		ast_stream_and_wait(props->transferer, props->failsound, AST_DIGIT_NONE);
+		stream_failsound(props->transferer);
 		ast_bridge_channel_write_unhold(bridge_channel);
 		ast_hangup(props->transfer_target);
 		props->transfer_target = NULL;
@@ -3334,7 +3379,7 @@ static int feature_attended_transfer(struct ast_bridge_channel *bridge_channel,
 
 	if (ast_call(props->transfer_target, destination, 0)) {
 		ast_log(LOG_ERROR, "Unable to place outbound call to transfer target.\n");
-		ast_stream_and_wait(bridge_channel->chan, props->failsound, AST_DIGIT_NONE);
+		stream_failsound(props->transferer);
 		ast_bridge_channel_write_unhold(bridge_channel);
 		ast_hangup(props->transfer_target);
 		props->transfer_target = NULL;
@@ -3350,7 +3395,7 @@ static int feature_attended_transfer(struct ast_bridge_channel *bridge_channel,
 	if (ast_bridge_impart(props->target_bridge, props->transfer_target, NULL, NULL,
 		AST_BRIDGE_IMPART_CHAN_INDEPENDENT)) {
 		ast_log(LOG_ERROR, "Unable to place transfer target into bridge.\n");
-		ast_stream_and_wait(bridge_channel->chan, props->failsound, AST_DIGIT_NONE);
+		stream_failsound(props->transferer);
 		ast_bridge_channel_write_unhold(bridge_channel);
 		ast_hangup(props->transfer_target);
 		props->transfer_target = NULL;
@@ -3360,7 +3405,7 @@ static int feature_attended_transfer(struct ast_bridge_channel *bridge_channel,
 
 	if (ast_pthread_create_detached(&thread, NULL, attended_transfer_monitor_thread, props)) {
 		ast_log(LOG_ERROR, "Unable to create monitoring thread for attended transfer.\n");
-		ast_stream_and_wait(bridge_channel->chan, props->failsound, AST_DIGIT_NONE);
+		stream_failsound(props->transferer);
 		ast_bridge_channel_write_unhold(bridge_channel);
 		attended_transfer_properties_shutdown(props);
 		return 0;
@@ -3385,35 +3430,46 @@ static void blind_transfer_cb(struct ast_channel *new_channel, struct transfer_c
 /*! \brief Internal built in feature for blind transfers */
 static int feature_blind_transfer(struct ast_bridge_channel *bridge_channel, void *hook_pvt)
 {
-	char exten[AST_MAX_EXTENSION] = "";
+	char xfer_exten[AST_MAX_EXTENSION] = "";
 	struct ast_bridge_features_blind_transfer *blind_transfer = hook_pvt;
-	const char *context;
+	const char *xfer_context;
 	char *goto_on_blindxfr;
 
 	ast_bridge_channel_write_hold(bridge_channel, NULL);
 
 	ast_channel_lock(bridge_channel->chan);
-	context = ast_strdupa(get_transfer_context(bridge_channel->chan,
+	xfer_context = ast_strdupa(get_transfer_context(bridge_channel->chan,
 		blind_transfer ? blind_transfer->context : NULL));
 	goto_on_blindxfr = ast_strdupa(S_OR(pbx_builtin_getvar_helper(bridge_channel->chan,
 		"GOTO_ON_BLINDXFR"), ""));
 	ast_channel_unlock(bridge_channel->chan);
 
 	/* Grab the extension to transfer to */
-	if (grab_transfer(bridge_channel->chan, exten, sizeof(exten), context)) {
+	if (grab_transfer(bridge_channel->chan, xfer_exten, sizeof(xfer_exten), xfer_context)) {
 		ast_bridge_channel_write_unhold(bridge_channel);
 		return 0;
 	}
 
 	if (!ast_strlen_zero(goto_on_blindxfr)) {
+		const char *chan_context;
+		const char *chan_exten;
+		int chan_priority;
+
 		ast_debug(1, "After transfer, transferer %s goes to %s\n",
 				ast_channel_name(bridge_channel->chan), goto_on_blindxfr);
-		ast_bridge_set_after_go_on(bridge_channel->chan, NULL, NULL, 0, goto_on_blindxfr);
+
+		ast_channel_lock(bridge_channel->chan);
+		chan_context = ast_strdupa(ast_channel_context(bridge_channel->chan));
+		chan_exten = ast_strdupa(ast_channel_exten(bridge_channel->chan));
+		chan_priority = ast_channel_priority(bridge_channel->chan);
+		ast_channel_unlock(bridge_channel->chan);
+		ast_bridge_set_after_go_on(bridge_channel->chan,
+			chan_context, chan_exten, chan_priority, goto_on_blindxfr);
 	}
 
-	if (ast_bridge_transfer_blind(0, bridge_channel->chan, exten, context, blind_transfer_cb,
-			bridge_channel->chan) != AST_BRIDGE_TRANSFER_SUCCESS &&
-			!ast_strlen_zero(goto_on_blindxfr)) {
+	if (ast_bridge_transfer_blind(0, bridge_channel->chan, xfer_exten, xfer_context,
+		blind_transfer_cb, bridge_channel->chan) != AST_BRIDGE_TRANSFER_SUCCESS
+		&& !ast_strlen_zero(goto_on_blindxfr)) {
 		ast_bridge_discard_after_goto(bridge_channel->chan);
 	}
 
diff --git a/main/bridge_channel.c b/main/bridge_channel.c
index 3874e50..c9262a8 100644
--- a/main/bridge_channel.c
+++ b/main/bridge_channel.c
@@ -637,6 +637,8 @@ void ast_bridge_channel_kick(struct ast_bridge_channel *bridge_channel, int caus
  */
 static int bridge_channel_write_frame(struct ast_bridge_channel *bridge_channel, struct ast_frame *frame)
 {
+	const struct ast_control_t38_parameters *t38_parameters;
+
 	ast_assert(frame->frametype != AST_FRAME_BRIDGE_ACTION_SYNC);
 
 	ast_bridge_channel_lock_bridge(bridge_channel);
@@ -663,6 +665,27 @@ static int bridge_channel_write_frame(struct ast_bridge_channel *bridge_channel,
 		 * We explicitly will not remember HOLD/UNHOLD frames because
 		 * things like attended transfers will handle them.
 		 */
+		switch (frame->subclass.integer) {
+		case AST_CONTROL_T38_PARAMETERS:
+			t38_parameters = frame->data.ptr;
+			switch (t38_parameters->request_response) {
+			case AST_T38_REQUEST_NEGOTIATE:
+			case AST_T38_NEGOTIATED:
+				bridge_channel->owed_t38_terminate = 1;
+				break;
+			case AST_T38_REQUEST_TERMINATE:
+			case AST_T38_TERMINATED:
+			case AST_T38_REFUSED:
+				bridge_channel->owed_t38_terminate = 0;
+				break;
+			default:
+				break;
+			}
+			break;
+		default:
+			break;
+		}
+		break;
 	default:
 		break;
 	}
@@ -675,6 +698,23 @@ static int bridge_channel_write_frame(struct ast_bridge_channel *bridge_channel,
 	return 0;
 }
 
+/*!
+ * \internal
+ * \brief Cancel owed events by the channel to the bridge.
+ * \since 13.8.0
+ *
+ * \param bridge_channel Channel that owes events to the bridge.
+ *
+ * \note On entry, the bridge_channel->bridge is already locked.
+ *
+ * \return Nothing
+ */
+static void bridge_channel_cancel_owed_events(struct ast_bridge_channel *bridge_channel)
+{
+	bridge_channel->owed.dtmf_digit = '\0';
+	bridge_channel->owed_t38_terminate = 0;
+}
+
 void bridge_channel_settle_owed_events(struct ast_bridge *orig_bridge, struct ast_bridge_channel *bridge_channel)
 {
 	if (bridge_channel->owed.dtmf_digit) {
@@ -694,6 +734,23 @@ void bridge_channel_settle_owed_events(struct ast_bridge *orig_bridge, struct as
 		bridge_channel->owed.dtmf_digit = '\0';
 		orig_bridge->technology->write(orig_bridge, NULL, &frame);
 	}
+	if (bridge_channel->owed_t38_terminate) {
+		struct ast_control_t38_parameters t38_parameters = {
+			.request_response = AST_T38_TERMINATED,
+		};
+		struct ast_frame frame = {
+			.frametype = AST_FRAME_CONTROL,
+			.subclass.integer = AST_CONTROL_T38_PARAMETERS,
+			.data.ptr = &t38_parameters,
+			.datalen = sizeof(t38_parameters),
+			.src = "Bridge channel owed T.38 terminate",
+		};
+
+		ast_debug(1, "T.38 terminate simulated to bridge %s because %s left.\n",
+			orig_bridge->uniqueid, ast_channel_name(bridge_channel->chan));
+		bridge_channel->owed_t38_terminate = 0;
+		orig_bridge->technology->write(orig_bridge, NULL, &frame);
+	}
 }
 
 /*!
@@ -2037,7 +2094,7 @@ void bridge_channel_internal_pull(struct ast_bridge_channel *bridge_channel)
 	ast_bridge_publish_leave(bridge, bridge_channel->chan);
 }
 
-int bridge_channel_internal_push(struct ast_bridge_channel *bridge_channel)
+int bridge_channel_internal_push_full(struct ast_bridge_channel *bridge_channel, int optimized)
 {
 	struct ast_bridge *bridge = bridge_channel->bridge;
 	struct ast_bridge_channel *swap;
@@ -2073,6 +2130,9 @@ int bridge_channel_internal_push(struct ast_bridge_channel *bridge_channel)
 		/* This flag is cleared so the act of this channel leaving does not cause it to dissolve if need be */
 		ast_clear_flag(&bridge->feature_flags, AST_BRIDGE_FLAG_DISSOLVE_EMPTY);
 
+		if (optimized) {
+			bridge_channel_cancel_owed_events(swap);
+		}
 		ast_bridge_channel_leave_bridge(swap, BRIDGE_CHANNEL_STATE_END_NO_DISSOLVE, 0);
 		bridge_channel_internal_pull(swap);
 
@@ -2112,6 +2172,11 @@ int bridge_channel_internal_push(struct ast_bridge_channel *bridge_channel)
 	return 0;
 }
 
+int bridge_channel_internal_push(struct ast_bridge_channel *bridge_channel)
+{
+	return bridge_channel_internal_push_full(bridge_channel, 0);
+}
+
 /*!
  * \internal
  * \brief Handle bridge channel control frame action.
@@ -2712,6 +2777,18 @@ int bridge_channel_internal_join(struct ast_bridge_channel *bridge_channel,
 			ast_channel_sending_dtmf_tv(bridge_channel->chan), "bridge end");
 	}
 
+	/* Complete any T.38 session before exiting the bridge. */
+	if (ast_channel_is_t38_active(bridge_channel->chan)) {
+		struct ast_control_t38_parameters t38_parameters = {
+			.request_response = AST_T38_TERMINATED,
+		};
+
+		ast_debug(1, "Channel %s simulating T.38 terminate for bridge end.\n",
+			ast_channel_name(bridge_channel->chan));
+		ast_indicate_data(bridge_channel->chan, AST_CONTROL_T38_PARAMETERS,
+			&t38_parameters, sizeof(t38_parameters));
+	}
+
 	/* Indicate a source change since this channel is leaving the bridge system. */
 	ast_indicate(bridge_channel->chan, AST_CONTROL_SRCCHANGE);
 
diff --git a/main/callerid.c b/main/callerid.c
index a99dafc..a9864da 100644
--- a/main/callerid.c
+++ b/main/callerid.c
@@ -1209,7 +1209,16 @@ static const struct ast_value_translation redirecting_reason_types[] = {
 	{ AST_REDIRECTING_REASON_OUT_OF_ORDER,   "out_of_order", "Called DTE Out-Of-Order" },
 	{ AST_REDIRECTING_REASON_AWAY,           "away",         "Callee is Away" },
 	{ AST_REDIRECTING_REASON_CALL_FWD_DTE,   "cf_dte",       "Call Forwarding By The Called DTE" },
-	{ AST_REDIRECTING_REASON_SEND_TO_VM,     "send_to_vm",   "Call is being redirected to user's voicemail"},
+	{ AST_REDIRECTING_REASON_SEND_TO_VM,     "send_to_vm",   "Call is being redirected to user's voicemail" },
+
+	/* Convenience SIP aliases.  Alias descriptions are not used. */
+	{ AST_REDIRECTING_REASON_USER_BUSY,      "user-busy" },
+	{ AST_REDIRECTING_REASON_NO_ANSWER,      "no-answer" },
+	{ AST_REDIRECTING_REASON_UNCONDITIONAL,  "unconditional" },
+	{ AST_REDIRECTING_REASON_TIME_OF_DAY,    "time-of-day" },
+	{ AST_REDIRECTING_REASON_DO_NOT_DISTURB, "do-not-disturb" },
+	{ AST_REDIRECTING_REASON_FOLLOW_ME,      "follow-me" },
+	{ AST_REDIRECTING_REASON_OUT_OF_ORDER,   "out-of-service" },
 /* *INDENT-ON* */
 };
 
@@ -1232,7 +1241,7 @@ const char *ast_redirecting_reason_describe(int data)
 
 	for (index = 0; index < ARRAY_LEN(redirecting_reason_types); ++index) {
 		if (redirecting_reason_types[index].value == data) {
-			return redirecting_reason_types[index].description;
+			return redirecting_reason_types[index].description ?: "Redirecting reason alias-bug";
 		}
 	}
 
diff --git a/main/ccss.c b/main/ccss.c
index 0605810..f39eed9 100644
--- a/main/ccss.c
+++ b/main/ccss.c
@@ -4663,7 +4663,7 @@ int ast_cc_init(void)
 					"Create generic monitor container"))) {
 		return -1;
 	}
-	if (!(cc_core_taskprocessor = ast_taskprocessor_get("CCSS core", TPS_REF_DEFAULT))) {
+	if (!(cc_core_taskprocessor = ast_taskprocessor_get("CCSS_core", TPS_REF_DEFAULT))) {
 		return -1;
 	}
 	if (!(cc_sched_context = ast_sched_context_create())) {
diff --git a/main/cdr.c b/main/cdr.c
index 07c0466..7795a65 100644
--- a/main/cdr.c
+++ b/main/cdr.c
@@ -1356,10 +1356,10 @@ static int base_process_party_a(struct cdr_object *cdr, struct ast_channel_snaps
 
 	ast_assert(strcasecmp(snapshot->name, cdr->party_a.snapshot->name) == 0);
 
-	/* Ignore any snapshots from a dead or dying channel */
+	/* Finalize the CDR if we're in hangup logic and we're set to do so */
 	if (ast_test_flag(&snapshot->softhangup_flags, AST_SOFTHANGUP_HANGUP_EXEC)
-			&& ast_test_flag(&mod_cfg->general->settings, CDR_END_BEFORE_H_EXTEN)) {
-		cdr_object_check_party_a_hangup(cdr);
+		&& ast_test_flag(&mod_cfg->general->settings, CDR_END_BEFORE_H_EXTEN)) {
+		cdr_object_finalize(cdr);
 		return 0;
 	}
 
@@ -2953,7 +2953,7 @@ int ast_cdr_setvar(const char *channel_name, const char *name, const char *value
 		for (it_cdr = cdr; it_cdr; it_cdr = it_cdr->next) {
 			struct varshead *headp = NULL;
 
-			if (it_cdr->fn_table == &finalized_state_fn_table) {
+			if (it_cdr->fn_table == &finalized_state_fn_table && it_cdr->next != NULL) {
 				continue;
 			}
 			if (!strcasecmp(channel_name, it_cdr->party_a.snapshot->name)) {
diff --git a/main/cel.c b/main/cel.c
index 0c1e37b..d9fcc5f 100644
--- a/main/cel.c
+++ b/main/cel.c
@@ -541,7 +541,7 @@ static int ast_cel_track_event(enum ast_cel_event_type et)
 		return 0;
 	}
 
-	return (cfg->general->events & ((int64_t) 1 << et));
+	return (cfg->general->events & ((int64_t) 1 << et)) ? 1 : 0;
 }
 
 static int events_handler(const struct aco_option *opt, struct ast_variable *var, void *obj)
diff --git a/main/channel.c b/main/channel.c
index 6507ef9..4ed1f8b 100644
--- a/main/channel.c
+++ b/main/channel.c
@@ -4473,67 +4473,47 @@ static int indicate_redirecting(struct ast_channel *chan, const void *data, size
 	return res ? -1 : 0;
 }
 
-int ast_indicate_data(struct ast_channel *chan, int _condition,
-		const void *data, size_t datalen)
+static int indicate_data_internal(struct ast_channel *chan, int _condition, const void *data, size_t datalen)
 {
 	/* By using an enum, we'll get compiler warnings for values not handled
 	 * in switch statements. */
 	enum ast_control_frame_type condition = _condition;
 	struct ast_tone_zone_sound *ts = NULL;
+	const struct ast_control_t38_parameters *t38_parameters;
 	int res;
-	/* this frame is used by framehooks. if it is set, we must free it at the end of this function */
-	struct ast_frame *awesome_frame = NULL;
-
-	ast_channel_lock(chan);
-
-	/* Don't bother if the channel is about to go away, anyway. */
-	if ((ast_test_flag(ast_channel_flags(chan), AST_FLAG_ZOMBIE)
-			|| (ast_check_hangup(chan) && !ast_channel_is_leaving_bridge(chan)))
-		&& condition != AST_CONTROL_MASQUERADE_NOTIFY) {
-		res = -1;
-		goto indicate_cleanup;
-	}
-
-	if (!ast_framehook_list_is_empty(ast_channel_framehooks(chan))) {
-		/* Do framehooks now, do it, go, go now */
-		struct ast_frame frame = {
-			.frametype = AST_FRAME_CONTROL,
-			.subclass.integer = condition,
-			.data.ptr = (void *) data, /* this cast from const is only okay because we do the ast_frdup below */
-			.datalen = datalen
-		};
-
-		/* we have now committed to freeing this frame */
-		awesome_frame = ast_frdup(&frame);
-
-		/* who knows what we will get back! the anticipation is killing me. */
-		if (!(awesome_frame = ast_framehook_list_write_event(ast_channel_framehooks(chan), awesome_frame))
-			|| awesome_frame->frametype != AST_FRAME_CONTROL) {
-			res = 0;
-			goto indicate_cleanup;
-		}
-
-		condition = awesome_frame->subclass.integer;
-		data = awesome_frame->data.ptr;
-		datalen = awesome_frame->datalen;
-	}
 
 	switch (condition) {
 	case AST_CONTROL_CONNECTED_LINE:
 		if (indicate_connected_line(chan, data, datalen)) {
 			res = 0;
-			goto indicate_cleanup;
+			return res;
 		}
 		break;
 	case AST_CONTROL_REDIRECTING:
 		if (indicate_redirecting(chan, data, datalen)) {
 			res = 0;
-			goto indicate_cleanup;
+			return res;
 		}
 		break;
 	case AST_CONTROL_HOLD:
 	case AST_CONTROL_UNHOLD:
-		ast_channel_hold_state_set(chan, condition);
+		ast_channel_hold_state_set(chan, _condition);
+		break;
+	case AST_CONTROL_T38_PARAMETERS:
+		t38_parameters = data;
+		switch (t38_parameters->request_response) {
+		case AST_T38_REQUEST_NEGOTIATE:
+		case AST_T38_NEGOTIATED:
+			ast_channel_set_is_t38_active_nolock(chan, 1);
+			break;
+		case AST_T38_REQUEST_TERMINATE:
+		case AST_T38_TERMINATED:
+		case AST_T38_REFUSED:
+			ast_channel_set_is_t38_active_nolock(chan, 0);
+			break;
+		default:
+			break;
+		}
 		break;
 	default:
 		break;
@@ -4541,7 +4521,7 @@ int ast_indicate_data(struct ast_channel *chan, int _condition,
 
 	if (is_visible_indication(condition)) {
 		/* A new visible indication is requested. */
-		ast_channel_visible_indication_set(chan, condition);
+		ast_channel_visible_indication_set(chan, _condition);
 	} else if (condition == AST_CONTROL_UNHOLD || _condition < 0) {
 		/* Visible indication is cleared/stopped. */
 		ast_channel_visible_indication_set(chan, 0);
@@ -4549,7 +4529,7 @@ int ast_indicate_data(struct ast_channel *chan, int _condition,
 
 	if (ast_channel_tech(chan)->indicate) {
 		/* See if the channel driver can handle this condition. */
-		res = ast_channel_tech(chan)->indicate(chan, condition, data, datalen);
+		res = ast_channel_tech(chan)->indicate(chan, _condition, data, datalen);
 	} else {
 		res = -1;
 	}
@@ -4557,7 +4537,7 @@ int ast_indicate_data(struct ast_channel *chan, int _condition,
 	if (!res) {
 		/* The channel driver successfully handled this indication */
 		res = 0;
-		goto indicate_cleanup;
+		return res;
 	}
 
 	/* The channel driver does not support this indication, let's fake
@@ -4570,7 +4550,7 @@ int ast_indicate_data(struct ast_channel *chan, int _condition,
 		/* Stop any tones that are playing */
 		ast_playtones_stop(chan);
 		res = 0;
-		goto indicate_cleanup;
+		return res;
 	}
 
 	/* Handle conditions that we have tones for. */
@@ -4578,7 +4558,7 @@ int ast_indicate_data(struct ast_channel *chan, int _condition,
 	case _XXX_AST_CONTROL_T38:
 		/* deprecated T.38 control frame */
 		res = -1;
-		goto indicate_cleanup;
+		return res;
 	case AST_CONTROL_T38_PARAMETERS:
 		/* there is no way to provide 'default' behavior for these
 		 * control frames, so we need to return failure, but there
@@ -4587,7 +4567,7 @@ int ast_indicate_data(struct ast_channel *chan, int _condition,
 		 * so just return right now. in addition, we want to return
 		 * whatever value the channel driver returned, in case it
 		 * has some meaning.*/
-		goto indicate_cleanup;
+		return res;
 	case AST_CONTROL_RINGING:
 		ts = ast_get_indication_tone(ast_channel_zone(chan), "ring");
 		/* It is common practice for channel drivers to return -1 if trying
@@ -4670,6 +4650,53 @@ int ast_indicate_data(struct ast_channel *chan, int _condition,
 		ast_log(LOG_WARNING, "Unable to handle indication %u for '%s'\n", condition, ast_channel_name(chan));
 	}
 
+	return res;
+}
+
+int ast_indicate_data(struct ast_channel *chan, int _condition, const void *data, size_t datalen)
+{
+	int res;
+	/* this frame is used by framehooks. if it is set, we must free it at the end of this function */
+	struct ast_frame *awesome_frame = NULL;
+
+	ast_channel_lock(chan);
+
+	/* Don't bother if the channel is about to go away, anyway. */
+	if ((ast_test_flag(ast_channel_flags(chan), AST_FLAG_ZOMBIE)
+			|| (ast_check_hangup(chan) && !ast_channel_is_leaving_bridge(chan)))
+		&& _condition != AST_CONTROL_MASQUERADE_NOTIFY) {
+		res = -1;
+		goto indicate_cleanup;
+	}
+
+	if (!ast_framehook_list_is_empty(ast_channel_framehooks(chan))) {
+		/* Do framehooks now, do it, go, go now */
+		struct ast_frame frame = {
+			.frametype = AST_FRAME_CONTROL,
+			.subclass.integer = _condition,
+			.data.ptr = (void *) data, /* this cast from const is only okay because we do the ast_frdup below */
+			.datalen = datalen
+		};
+
+		/* we have now committed to freeing this frame */
+		awesome_frame = ast_frdup(&frame);
+
+		/* who knows what we will get back! the anticipation is killing me. */
+		awesome_frame = ast_framehook_list_write_event(ast_channel_framehooks(chan),
+			awesome_frame);
+		if (!awesome_frame
+			|| awesome_frame->frametype != AST_FRAME_CONTROL) {
+			res = 0;
+			goto indicate_cleanup;
+		}
+
+		_condition = awesome_frame->subclass.integer;
+		data = awesome_frame->data.ptr;
+		datalen = awesome_frame->datalen;
+	}
+
+	res = indicate_data_internal(chan, _condition, data, datalen);
+
 indicate_cleanup:
 	ast_channel_unlock(chan);
 	if (awesome_frame) {
@@ -5012,10 +5039,15 @@ int ast_write(struct ast_channel *chan, struct ast_frame *fr)
 				res = ast_senddigit_end(chan, fr->subclass.integer, fr->len);
 				ast_channel_lock(chan);
 				CHECK_BLOCKING(chan);
-			} else if (fr->frametype == AST_FRAME_CONTROL && fr->subclass.integer == AST_CONTROL_UNHOLD) {
-				/* This is a side case where Echo is basically being called and the person put themselves on hold and took themselves off hold */
-				res = (ast_channel_tech(chan)->indicate == NULL) ? 0 :
-					ast_channel_tech(chan)->indicate(chan, fr->subclass.integer, fr->data.ptr, fr->datalen);
+			} else if (fr->frametype == AST_FRAME_CONTROL
+				&& fr->subclass.integer == AST_CONTROL_UNHOLD) {
+				/*
+				 * This is a side case where Echo is basically being called
+				 * and the person put themselves on hold and took themselves
+				 * off hold.
+				 */
+				indicate_data_internal(chan, fr->subclass.integer, fr->data.ptr,
+					fr->datalen);
 			}
 			res = 0;	/* XXX explain, why 0 ? */
 			goto done;
@@ -5027,8 +5059,8 @@ int ast_write(struct ast_channel *chan, struct ast_frame *fr)
 	CHECK_BLOCKING(chan);
 	switch (fr->frametype) {
 	case AST_FRAME_CONTROL:
-		res = (ast_channel_tech(chan)->indicate == NULL) ? 0 :
-			ast_channel_tech(chan)->indicate(chan, fr->subclass.integer, fr->data.ptr, fr->datalen);
+		indicate_data_internal(chan, fr->subclass.integer, fr->data.ptr, fr->datalen);
+		res = 0;
 		break;
 	case AST_FRAME_DTMF_BEGIN:
 		if (ast_channel_audiohooks(chan)) {
diff --git a/main/channel_internal_api.c b/main/channel_internal_api.c
index db5f3c0..51d49c2 100644
--- a/main/channel_internal_api.c
+++ b/main/channel_internal_api.c
@@ -173,8 +173,6 @@ struct ast_channel {
 							 *   See \arg \ref AstFileDesc */
 	int softhangup;				/*!< Whether or not we have been hung up...  Do not set this value
 							 *   directly, use ast_softhangup() */
-	int unbridged;              /*!< If non-zero, the bridge core needs to re-evaluate the current
-	                                 bridging technology which is in use by this channel's bridge. */
 	int fdno;					/*!< Which fd had an event detected on */
 	int streamid;					/*!< For streaming playback, the schedule ID */
 	int vstreamid;					/*!< For streaming video playback, the schedule ID */
@@ -216,6 +214,9 @@ struct ast_channel {
 	char exten[AST_MAX_EXTENSION];			/*!< Dialplan: Current extension number */
 	char macrocontext[AST_MAX_CONTEXT];		/*!< Macro: Current non-macro context. See app_macro.c */
 	char macroexten[AST_MAX_EXTENSION];		/*!< Macro: Current non-macro extension. See app_macro.c */
+	char unbridged;							/*!< non-zero if the bridge core needs to re-evaluate the current
+											 bridging technology which is in use by this channel's bridge. */
+	char is_t38_active;						/*!< non-zero if T.38 is active on this channel. */
 	char dtmf_digit_to_emulate;			/*!< Digit being emulated */
 	char sending_dtmf_digit;			/*!< Digit this channel is currently sending out. (zero if not sending) */
 	struct timeval sending_dtmf_tv;		/*!< The time this channel started sending the current digit. (Invalid if sending_dtmf_digit is zero.) */
@@ -1152,7 +1153,7 @@ int ast_channel_unbridged(struct ast_channel *chan)
 
 void ast_channel_set_unbridged_nolock(struct ast_channel *chan, int value)
 {
-	chan->unbridged = value;
+	chan->unbridged = !!value;
 	ast_queue_frame(chan, &ast_null_frame);
 }
 
@@ -1163,6 +1164,33 @@ void ast_channel_set_unbridged(struct ast_channel *chan, int value)
 	ast_channel_unlock(chan);
 }
 
+int ast_channel_is_t38_active_nolock(struct ast_channel *chan)
+{
+	return chan->is_t38_active;
+}
+
+int ast_channel_is_t38_active(struct ast_channel *chan)
+{
+	int res;
+
+	ast_channel_lock(chan);
+	res = ast_channel_is_t38_active_nolock(chan);
+	ast_channel_unlock(chan);
+	return res;
+}
+
+void ast_channel_set_is_t38_active_nolock(struct ast_channel *chan, int is_t38_active)
+{
+	chan->is_t38_active = !!is_t38_active;
+}
+
+void ast_channel_set_is_t38_active(struct ast_channel *chan, int is_t38_active)
+{
+	ast_channel_lock(chan);
+	ast_channel_set_is_t38_active_nolock(chan, is_t38_active);
+	ast_channel_unlock(chan);
+}
+
 void ast_channel_callid_cleanup(struct ast_channel *chan)
 {
 	if (chan->callid) {
diff --git a/main/config.c b/main/config.c
index eefc8ca..74f42cf 100644
--- a/main/config.c
+++ b/main/config.c
@@ -3863,6 +3863,8 @@ static void config_shutdown(void)
 
 	ast_cli_unregister_multiple(cli_config, ARRAY_LEN(cli_config));
 
+	clear_config_maps();
+
 	ao2_cleanup(cfg_hooks);
 	cfg_hooks = NULL;
 }
diff --git a/main/config_options.c b/main/config_options.c
index 0c706ac..5da310f 100644
--- a/main/config_options.c
+++ b/main/config_options.c
@@ -71,6 +71,9 @@ struct aco_option {
 	aco_option_handler handler;
 	unsigned int flags;
 	unsigned int no_doc:1;
+#ifdef AST_DEVMODE
+	unsigned int doc_unavailable:1;
+#endif
 	unsigned char deprecated:1;
 	size_t argc;
 	intptr_t args[0];
@@ -183,18 +186,20 @@ static int link_option_to_types(struct aco_info *info, struct aco_type **types,
 			ast_log(LOG_ERROR, "Attempting to register option using uninitialized type\n");
 			return -1;
 		}
-		if (!ao2_link(type->internal->opts, opt)
-#ifdef AST_XML_DOCS
-				|| (!info->hidden &&
-					!opt->no_doc &&
-					xmldoc_update_config_option(types, info->module, opt->name, type->name, opt->default_val, opt->match_type == ACO_REGEX, opt->type))
-#endif /* AST_XML_DOCS */
-		) {
+		if (!ao2_link(type->internal->opts, opt)) {
 			do {
 				ao2_unlink(types[idx - 1]->internal->opts, opt);
 			} while (--idx);
 			return -1;
 		}
+#ifdef AST_XML_DOCS
+		if (!info->hidden && !opt->no_doc &&
+			xmldoc_update_config_option(types, info->module, opt->name, type->name, opt->default_val, opt->match_type == ACO_REGEX, opt->type)) {
+#ifdef AST_DEVMODE
+			opt->doc_unavailable = 1;
+#endif
+#endif
+		}
 	}
 	/* The container(s) should hold the only ref to opt */
 	ao2_ref(opt, -1);
@@ -716,6 +721,14 @@ int aco_process_var(struct aco_type *type, const char *cat, struct ast_variable
 		ast_log(LOG_ERROR, "BUG! Somehow a config option for %s/%s was created with no handler!\n", cat, var->name);
 		return -1;
 	}
+
+#ifdef AST_DEVMODE
+	if (opt->doc_unavailable) {
+		ast_log(LOG_ERROR, "Config option '%s' of type '%s' is not completely documented and can not be set\n", var->name, type->name);
+		return -1;
+	}
+#endif
+
 	if (opt->handler(opt, var, obj)) {
 		ast_log(LOG_ERROR, "Error parsing %s=%s at line %d of %s\n", var->name, var->value, var->lineno, var->file);
 		return -1;
@@ -1050,7 +1063,7 @@ static int xmldoc_update_config_option(struct aco_type **types, const char *modu
 	}
 
 	if (!(option = ast_xml_xpath_get_first_result(results))) {
-		ast_log(LOG_WARNING, "Could obtain results for option '%s' with type '%s' in module '%s'\n", name, object_name, module);
+		ast_log(LOG_WARNING, "Could not obtain results for option '%s' with type '%s' in module '%s'\n", name, object_name, module);
 		return XMLDOC_STRICT ? -1 : 0;
 	}
 	ast_xml_set_attribute(option, "regex", regex ? "true" : "false");
diff --git a/main/core_local.c b/main/core_local.c
index 10bd839..1b8ebf6 100644
--- a/main/core_local.c
+++ b/main/core_local.c
@@ -235,6 +235,45 @@ struct local_pvt {
 	char exten[AST_MAX_EXTENSION];
 };
 
+void ast_local_lock_all(struct ast_channel *chan, struct ast_channel **outchan,
+			struct ast_channel **outowner)
+{
+	struct local_pvt *p = ast_channel_tech_pvt(chan);
+
+	*outchan = NULL;
+	*outowner = NULL;
+
+	if (p) {
+		ao2_ref(p, 1);
+		ast_unreal_lock_all(&p->base, outchan, outowner);
+	}
+}
+
+void ast_local_unlock_all(struct ast_channel *chan)
+{
+	struct local_pvt *p = ast_channel_tech_pvt(chan);
+	struct ast_unreal_pvt *base;
+
+	if (!p) {
+		return;
+	}
+
+	base = &p->base;
+
+	if (base->owner) {
+		ast_channel_unlock(base->owner);
+		ast_channel_unref(base->owner);
+	}
+
+	if (base->chan) {
+		ast_channel_unlock(base->chan);
+		ast_channel_unref(base->chan);
+	}
+
+	ao2_unlock(base);
+	ao2_ref(p, -1);
+}
+
 struct ast_channel *ast_local_get_peer(struct ast_channel *ast)
 {
 	struct local_pvt *p = ast_channel_tech_pvt(ast);
diff --git a/main/devicestate.c b/main/devicestate.c
index 2983ee9..faba144 100644
--- a/main/devicestate.c
+++ b/main/devicestate.c
@@ -214,6 +214,7 @@ static pthread_t change_thread = AST_PTHREADT_NULL;
 
 /*! \brief Flag for the queue */
 static ast_cond_t change_pending;
+static volatile int shuttingdown;
 
 struct stasis_subscription *devstate_message_sub;
 
@@ -548,7 +549,7 @@ static void *do_devstate_changes(void *data)
 {
 	struct state_change *next, *current;
 
-	for (;;) {
+	while (!shuttingdown) {
 		/* This basically pops off any state change entries, resets the list back to NULL, unlocks, and processes each state change */
 		AST_LIST_LOCK(&state_changes);
 		if (AST_LIST_EMPTY(&state_changes))
@@ -626,6 +627,18 @@ static void devstate_change_cb(void *data, struct stasis_subscription *sub, stru
 		device_state->cachable, NULL);
 }
 
+static void device_state_engine_cleanup(void)
+{
+	shuttingdown = 1;
+	AST_LIST_LOCK(&state_changes);
+	ast_cond_signal(&change_pending);
+	AST_LIST_UNLOCK(&state_changes);
+
+	if (change_thread != AST_PTHREADT_NULL) {
+		pthread_join(change_thread, NULL);
+	}
+}
+
 /*! \brief Initialize the device state engine in separate thread */
 int ast_device_state_engine_init(void)
 {
@@ -634,6 +647,7 @@ int ast_device_state_engine_init(void)
 		ast_log(LOG_ERROR, "Unable to start device state change thread.\n");
 		return -1;
 	}
+	ast_register_cleanup(device_state_engine_cleanup);
 
 	return 0;
 }
diff --git a/main/editline/.gitignore b/main/editline/.gitignore
deleted file mode 100644
index d3bb06b..0000000
--- a/main/editline/.gitignore
+++ /dev/null
@@ -1,13 +0,0 @@
-*.o_a
-Makefile
-common.h
-config.cache
-config.h
-editline.c
-emacs.h
-fcns.c
-fcns.h
-help.c
-help.h
-makelist
-vi.h
diff --git a/main/endpoints.c b/main/endpoints.c
index 0155adf..80e7f87 100644
--- a/main/endpoints.c
+++ b/main/endpoints.c
@@ -303,13 +303,14 @@ static struct ast_endpoint *endpoint_internal_create(const char *tech, const cha
 		return NULL;
 	}
 
-	endpoint->topics = stasis_cp_single_create(ast_endpoint_cache_all(),
-		endpoint->id);
-	if (!endpoint->topics) {
-		return NULL;
-	}
-
 	if (!ast_strlen_zero(resource)) {
+
+		endpoint->topics = stasis_cp_single_create(ast_endpoint_cache_all(),
+			endpoint->id);
+		if (!endpoint->topics) {
+			return NULL;
+		}
+
 		endpoint->router = stasis_message_router_create_pool(ast_endpoint_topic(endpoint));
 		if (!endpoint->router) {
 			return NULL;
@@ -325,9 +326,16 @@ static struct ast_endpoint *endpoint_internal_create(const char *tech, const cha
 
 		endpoint->tech_forward = stasis_forward_all(stasis_cp_single_topic(endpoint->topics),
 			stasis_cp_single_topic(tech_endpoint->topics));
+
 		endpoint_publish_snapshot(endpoint);
 		ao2_link(endpoints, endpoint);
 	} else {
+		endpoint->topics = stasis_cp_sink_create(ast_endpoint_cache_all(),
+			endpoint->id);
+		if (!endpoint->topics) {
+			return NULL;
+		}
+
 		ao2_link(tech_endpoints, endpoint);
 	}
 
diff --git a/main/features_config.c b/main/features_config.c
index 9fed53b..9126a86 100644
--- a/main/features_config.c
+++ b/main/features_config.c
@@ -1158,6 +1158,21 @@ struct ast_features_xfer_config *ast_get_chan_features_xfer_config(struct ast_ch
 	return cfg->global->xfer;
 }
 
+char *ast_get_chan_features_xferfailsound(struct ast_channel *chan)
+{
+	char *res;
+	struct ast_features_xfer_config *cfg = ast_get_chan_features_xfer_config(chan);
+
+	if (!cfg) {
+		return NULL;
+	}
+
+	res = ast_strdup(cfg->xferfailsound);
+	ao2_ref(cfg, -1);
+
+	return res;
+}
+
 struct ast_features_pickup_config *ast_get_chan_features_pickup_config(struct ast_channel *chan)
 {
 	RAII_VAR(struct features_config *, cfg, NULL, ao2_cleanup);
diff --git a/main/format_cap.c b/main/format_cap.c
index 8d6046a..17ae18c 100644
--- a/main/format_cap.c
+++ b/main/format_cap.c
@@ -252,6 +252,7 @@ int ast_format_cap_append_by_type(struct ast_format_cap *cap, enum ast_media_typ
 
 	for (id = 1; id < ast_codec_get_max(); ++id) {
 		struct ast_codec *codec = ast_codec_get_by_id(id);
+		struct ast_codec *codec2 = NULL;
 		struct ast_format *format;
 		int res;
 
@@ -272,10 +273,14 @@ int ast_format_cap_append_by_type(struct ast_format_cap *cap, enum ast_media_typ
 			continue;
 		}
 
-		if (!format || (codec != ast_format_get_codec(format))) {
+		if (format) {
+			codec2 = ast_format_get_codec(format);
+		}
+		if (codec != codec2) {
 			ao2_cleanup(format);
 			format = ast_format_create(codec);
 		}
+		ao2_cleanup(codec2);
 		ao2_ref(codec, -1);
 
 		if (!format) {
diff --git a/main/http.c b/main/http.c
index 26e218b..c343cb2 100644
--- a/main/http.c
+++ b/main/http.c
@@ -2102,10 +2102,13 @@ static int __ast_http_load(int reload)
 	}
 	http_tls_cfg.pvtfile = ast_strdup("");
 
+	/* Apply modern intermediate settings according to the Mozilla OpSec team as of July 30th, 2015 but disable TLSv1 */
+	ast_set_flag(&http_tls_cfg.flags, AST_SSL_DISABLE_TLSV1 | AST_SSL_SERVER_CIPHER_ORDER);
+
 	if (http_tls_cfg.cipher) {
 		ast_free(http_tls_cfg.cipher);
 	}
-	http_tls_cfg.cipher = ast_strdup("");
+	http_tls_cfg.cipher = ast_strdup("ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:kEDH+AESGCM:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA256:DHE-RSA-AES256-SHA256:DHE- [...]
 
 	AST_RWLIST_WRLOCK(&uri_redirects);
 	while ((redirect = AST_RWLIST_REMOVE_HEAD(&uri_redirects, entry))) {
@@ -2131,8 +2134,6 @@ static int __ast_http_load(int reload)
 			&& strcasecmp(v->name, "tlsdontverifyserver")
 			&& strcasecmp(v->name, "tlsclientmethod")
 			&& strcasecmp(v->name, "sslclientmethod")
-			&& strcasecmp(v->name, "tlscipher")
-			&& strcasecmp(v->name, "sslcipher")
 			&& !ast_tls_read_conf(&http_tls_cfg, &https_desc, v->name, v->value)) {
 			continue;
 		}
diff --git a/main/libasteriskpj.c b/main/libasteriskpj.c
new file mode 100644
index 0000000..aed0ec8
--- /dev/null
+++ b/main/libasteriskpj.c
@@ -0,0 +1,52 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2009-2012, Digium, Inc.
+ * Copyright (C) 2015, Fairview 5 Engineering, LLC
+ *
+ * Russell Bryant <russell at digium.com>
+ * George Joseph <george.joseph at fairview5.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 Loader stub for static pjproject libraries
+ *
+ * \author George Joseph <george.joseph at fairview5.com>
+ */
+
+/*** MODULEINFO
+	<support_level>core</support_level>
+ ***/
+
+#include "asterisk.h"
+
+ASTERISK_REGISTER_FILE()
+
+#ifdef HAVE_PJPROJECT
+#include <pjlib.h>
+#endif
+
+#include "asterisk/_private.h" /* ast_pj_init() */
+
+/*!
+ * \internal
+ * \brief Initialize static pjproject implementation
+ */
+int ast_pj_init(void)
+{
+#ifdef HAVE_PJPROJECT_BUNDLED
+	pj_init();
+#endif
+	return 0;
+}
diff --git a/main/loader.c b/main/loader.c
index 2b3bd1f..f660a62 100644
--- a/main/loader.c
+++ b/main/loader.c
@@ -519,9 +519,11 @@ static void unload_dynamic_module(struct ast_module *mod)
 #endif
 }
 
-static enum ast_module_load_result load_resource(const char *resource_name, unsigned int global_symbols_only, struct ast_heap *resource_heap, int required);
+static enum ast_module_load_result load_resource(const char *resource_name, unsigned int global_symbols_only, unsigned int suppress_logging, struct ast_heap *resource_heap, int required);
 
-static struct ast_module *load_dynamic_module(const char *resource_in, unsigned int global_symbols_only, struct ast_heap *resource_heap)
+#define MODULE_LOCAL_ONLY (void *)-1
+
+static struct ast_module *load_dynamic_module(const char *resource_in, unsigned int global_symbols_only, unsigned int suppress_logging, struct ast_heap *resource_heap)
 {
 	char fn[PATH_MAX] = "";
 	void *lib = NULL;
@@ -549,8 +551,10 @@ static struct ast_module *load_dynamic_module(const char *resource_in, unsigned
 	if (missing_so)
 		strcat(resource_being_loaded->resource, ".so");
 
-	if (!(lib = dlopen(fn, RTLD_LAZY | RTLD_LOCAL))) {
-		ast_log(LOG_WARNING, "Error loading module '%s': %s\n", resource_in, dlerror());
+	if (!(lib = dlopen(fn, RTLD_LAZY | RTLD_GLOBAL))) {
+		if (!suppress_logging) {
+			ast_log(LOG_WARNING, "Error loading module '%s': %s\n", resource_in, dlerror());
+		}
 		ast_free(resource_being_loaded);
 		return NULL;
 	}
@@ -577,7 +581,7 @@ static struct ast_module *load_dynamic_module(const char *resource_in, unsigned
 	   and this one does not, then close it and return */
 	if (global_symbols_only && !wants_global) {
 		logged_dlclose(resource_in, lib);
-		return NULL;
+		return MODULE_LOCAL_ONLY;
 	}
 
 	logged_dlclose(resource_in, lib);
@@ -1059,7 +1063,7 @@ static enum ast_module_load_result start_resource(struct ast_module *mod)
  *
  *  If the ast_heap is not provided, the module's load function will be executed
  *  immediately */
-static enum ast_module_load_result load_resource(const char *resource_name, unsigned int global_symbols_only, struct ast_heap *resource_heap, int required)
+static enum ast_module_load_result load_resource(const char *resource_name, unsigned int global_symbols_only, unsigned int suppress_logging, struct ast_heap *resource_heap, int required)
 {
 	struct ast_module *mod;
 	enum ast_module_load_result res = AST_MODULE_LOAD_SUCCESS;
@@ -1073,14 +1077,15 @@ static enum ast_module_load_result load_resource(const char *resource_name, unsi
 			return AST_MODULE_LOAD_SKIP;
 	} else {
 #ifdef LOADABLE_MODULES
-		if (!(mod = load_dynamic_module(resource_name, global_symbols_only, resource_heap))) {
-			/* don't generate a warning message during load_modules() */
+		mod = load_dynamic_module(resource_name, global_symbols_only, suppress_logging, resource_heap);
+		if (mod == MODULE_LOCAL_ONLY) {
+				return AST_MODULE_LOAD_SKIP;
+		}
+		if (!mod) {
 			if (!global_symbols_only) {
 				ast_log(LOG_WARNING, "Module '%s' could not be loaded.\n", resource_name);
-				return required ? AST_MODULE_LOAD_FAILURE : AST_MODULE_LOAD_DECLINE;
-			} else {
-				return required ? AST_MODULE_LOAD_FAILURE : AST_MODULE_LOAD_SKIP;
 			}
+			return required ? AST_MODULE_LOAD_FAILURE : AST_MODULE_LOAD_DECLINE;
 		}
 #else
 		ast_log(LOG_WARNING, "Module support is not available. Module '%s' could not be loaded.\n", resource_name);
@@ -1117,7 +1122,7 @@ int ast_load_resource(const char *resource_name)
 {
 	int res;
 	AST_DLLIST_LOCK(&module_list);
-	res = load_resource(resource_name, 0, NULL, 0);
+	res = load_resource(resource_name, 0, 0, NULL, 0);
 	if (!res) {
 		ast_test_suite_event_notify("MODULE_LOAD", "Message: %s", resource_name);
 	}
@@ -1174,6 +1179,8 @@ static int mod_load_cmp(void *a, void *b)
 	return b_pri - a_pri;
 }
 
+AST_LIST_HEAD_NOLOCK(load_retries, load_order_entry);
+
 /*! loads modules in order by load_pri, updates mod_count
 	\return -1 on failure to load module, -2 on failure to load required module, otherwise 0
 */
@@ -1182,8 +1189,13 @@ static int load_resource_list(struct load_order *load_order, unsigned int global
 	struct ast_heap *resource_heap;
 	struct load_order_entry *order;
 	struct ast_module *mod;
+	struct load_retries load_retries;
 	int count = 0;
 	int res = 0;
+	int i = 0;
+#define LOAD_RETRIES 4
+
+	AST_LIST_HEAD_INIT_NOLOCK(&load_retries);
 
 	if(!(resource_heap = ast_heap_create(8, mod_load_cmp, -1))) {
 		return -1;
@@ -1191,21 +1203,34 @@ static int load_resource_list(struct load_order *load_order, unsigned int global
 
 	/* first, add find and add modules to heap */
 	AST_LIST_TRAVERSE_SAFE_BEGIN(load_order, order, entry) {
-		switch (load_resource(order->resource, global_symbols, resource_heap, order->required)) {
+		enum ast_module_load_result lres;
+
+		/* Suppress log messages unless this is the last pass */
+		lres = load_resource(order->resource, global_symbols, 1, resource_heap, order->required);
+		ast_debug(3, "PASS 0: %-46s %d %d\n", order->resource, lres, global_symbols);
+		switch (lres) {
 		case AST_MODULE_LOAD_SUCCESS:
+			/* We're supplying a heap so SUCCESS isn't possible but we still have to test for it. */
+			break;
+		case AST_MODULE_LOAD_FAILURE:
 		case AST_MODULE_LOAD_DECLINE:
+			/*
+			 * DECLINE or FAILURE means there was an issue with dlopen or module_register
+			 * which might be retryable.  LOAD_FAILURE only happens for required modules
+			 * but we're still going to retry.  We need to remove the entry from the
+			 * load_order list and add it to the load_retries list.
+			 */
 			AST_LIST_REMOVE_CURRENT(entry);
-			ast_free(order->resource);
-			ast_free(order);
+			AST_LIST_INSERT_TAIL(&load_retries, order, entry);
 			break;
-		case AST_MODULE_LOAD_FAILURE:
-			ast_log(LOG_ERROR, "*** Failed to load module %s - %s\n", order->resource, order->required ? "Required" : "Not required");
-			fprintf(stderr, "*** Failed to load module %s - %s\n", order->resource, order->required ? "Required" : "Not required");
-			res = order->required ? -2 : -1;
-			goto done;
 		case AST_MODULE_LOAD_SKIP:
+			/*
+			 * SKIP means that dlopen worked but global_symbols was set and this module doesn't qualify.
+			 * Leave it in load_order for the next call of load_resource_list.
+			 */
 			break;
 		case AST_MODULE_LOAD_PRIORITY:
+			/* load_resource worked and the module was added to the priority heap */
 			AST_LIST_REMOVE_CURRENT(entry);
 			ast_free(order->resource);
 			ast_free(order);
@@ -1214,9 +1239,56 @@ static int load_resource_list(struct load_order *load_order, unsigned int global
 	}
 	AST_LIST_TRAVERSE_SAFE_END;
 
+	/* Retry the failures until the list is empty or we reach LOAD_RETRIES */
+	for (i = 0; !AST_LIST_EMPTY(&load_retries) && i < LOAD_RETRIES; i++) {
+		AST_LIST_TRAVERSE_SAFE_BEGIN(&load_retries, order, entry) {
+			enum ast_module_load_result lres;
+
+			/* Suppress log messages unless this is the last pass */
+			lres = load_resource(order->resource, global_symbols, (i < LOAD_RETRIES - 1), resource_heap, order->required);
+			ast_debug(3, "PASS %d %-46s %d %d\n", i + 1, order->resource, lres, global_symbols);
+			switch (lres) {
+			/* These are all retryable. */
+			case AST_MODULE_LOAD_SUCCESS:
+			case AST_MODULE_LOAD_DECLINE:
+				break;
+			case AST_MODULE_LOAD_FAILURE:
+				/* LOAD_FAILURE only happens for required modules */
+				if (i == LOAD_RETRIES - 1) {
+					/* This was the last chance to load a required module*/
+					ast_log(LOG_ERROR, "*** Failed to load module %s - Required\n", order->resource);
+					fprintf(stderr, "*** Failed to load module %s - Required\n", order->resource);
+					res =  -2;
+					goto done;
+				}
+				break;;
+			case AST_MODULE_LOAD_SKIP:
+				/*
+				 * SKIP means that dlopen worked but global_symbols was set and this module
+				 * doesn't qualify.  Put it back in load_order for the next call of
+				 * load_resource_list.
+				 */
+				AST_LIST_REMOVE_CURRENT(entry);
+				AST_LIST_INSERT_TAIL(load_order, order, entry);
+				break;
+			case AST_MODULE_LOAD_PRIORITY:
+				/* load_resource worked and the module was added to the priority heap */
+				AST_LIST_REMOVE_CURRENT(entry);
+				ast_free(order->resource);
+				ast_free(order);
+				break;
+			}
+		}
+		AST_LIST_TRAVERSE_SAFE_END;
+	}
+
 	/* second remove modules from heap sorted by priority */
 	while ((mod = ast_heap_pop(resource_heap))) {
-		switch (start_resource(mod)) {
+		enum ast_module_load_result lres;
+
+		lres = start_resource(mod);
+		ast_debug(3, "START: %-46s %d %d\n", mod->resource, lres, global_symbols);
+		switch (lres) {
 		case AST_MODULE_LOAD_SUCCESS:
 			count++;
 		case AST_MODULE_LOAD_DECLINE:
@@ -1231,6 +1303,12 @@ static int load_resource_list(struct load_order *load_order, unsigned int global
 	}
 
 done:
+
+	while ((order = AST_LIST_REMOVE_HEAD(&load_retries, entry))) {
+		ast_free(order->resource);
+		ast_free(order);
+	}
+
 	if (mod_count) {
 		*mod_count += count;
 	}
diff --git a/main/logger.c b/main/logger.c
index fb9e8ed..46d9cbb 100644
--- a/main/logger.c
+++ b/main/logger.c
@@ -464,7 +464,7 @@ static int init_logger_chain(int locked, const char *altconf)
 
 	/* If no config file, we're fine, set default options. */
 	if (!cfg) {
-		if (!(chan = ast_calloc(1, sizeof(*chan)))) {
+		if (!(chan = ast_calloc(1, sizeof(*chan) + 1))) {
 			fprintf(stderr, "Failed to initialize default logging\n");
 			return -1;
 		}
diff --git a/main/manager.c b/main/manager.c
index 99c5502..de00381 100644
--- a/main/manager.c
+++ b/main/manager.c
@@ -651,6 +651,8 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
 				<parameter name="Channel"/>
 				<parameter name="Context"/>
 				<parameter name="Exten"/>
+				<parameter name="Application"/>
+				<parameter name="Data"/>
 				<parameter name="Reason"/>
 				<parameter name="Uniqueid"/>
 				<parameter name="CallerIDNum"/>
@@ -2332,7 +2334,7 @@ static char *handle_showmanager(struct ast_cli_entry *e, int cmd, struct ast_cli
 		"        write perm: %s\n"
 		"   displayconnects: %s\n"
 		"allowmultiplelogin: %s\n",
-		(user->username ? user->username : "(N/A)"),
+		S_OR(user->username, "(N/A)"),
 		(user->secret ? "<Set>" : "(N/A)"),
 		((user->acl && !ast_acl_list_is_empty(user->acl)) ? "yes" : "no"),
 		user_authority_to_str(user->readperm, &rauthority),
@@ -4969,22 +4971,43 @@ static void *fast_originate(void *data)
 	}
 	/* Tell the manager what happened with the channel */
 	chans[0] = chan;
-	ast_manager_event_multichan(EVENT_FLAG_CALL, "OriginateResponse", chan ? 1 : 0, chans,
-		"%s"
-		"Response: %s\r\n"
-		"Channel: %s\r\n"
-		"Context: %s\r\n"
-		"Exten: %s\r\n"
-		"Reason: %d\r\n"
-		"Uniqueid: %s\r\n"
-		"CallerIDNum: %s\r\n"
-		"CallerIDName: %s\r\n",
-		in->idtext, res ? "Failure" : "Success",
-		chan ? ast_channel_name(chan) : requested_channel, in->context, in->exten, reason,
-		chan ? ast_channel_uniqueid(chan) : "<null>",
-		S_OR(in->cid_num, "<unknown>"),
-		S_OR(in->cid_name, "<unknown>")
-		);
+	if (!ast_strlen_zero(in->app)) {
+		ast_manager_event_multichan(EVENT_FLAG_CALL, "OriginateResponse", chan ? 1 : 0, chans,
+			"%s"
+			"Response: %s\r\n"
+			"Channel: %s\r\n"
+			"Application: %s\r\n"
+			"Data: %s\r\n"
+			"Reason: %d\r\n"
+			"Uniqueid: %s\r\n"
+			"CallerIDNum: %s\r\n"
+			"CallerIDName: %s\r\n",
+			in->idtext, res ? "Failure" : "Success",
+			chan ? ast_channel_name(chan) : requested_channel,
+			in->app, in->appdata, reason,
+			chan ? ast_channel_uniqueid(chan) : S_OR(in->channelid, "<unknown>"),
+			S_OR(in->cid_num, "<unknown>"),
+			S_OR(in->cid_name, "<unknown>")
+			);
+	} else {
+		ast_manager_event_multichan(EVENT_FLAG_CALL, "OriginateResponse", chan ? 1 : 0, chans,
+			"%s"
+			"Response: %s\r\n"
+			"Channel: %s\r\n"
+			"Context: %s\r\n"
+			"Exten: %s\r\n"
+			"Reason: %d\r\n"
+			"Uniqueid: %s\r\n"
+			"CallerIDNum: %s\r\n"
+			"CallerIDName: %s\r\n",
+			in->idtext, res ? "Failure" : "Success",
+			chan ? ast_channel_name(chan) : requested_channel,
+			in->context, in->exten, reason,
+			chan ? ast_channel_uniqueid(chan) : S_OR(in->channelid, "<unknown>"),
+			S_OR(in->cid_num, "<unknown>"),
+			S_OR(in->cid_name, "<unknown>")
+			);
+	}
 
 	/* Locked and ref'd by ast_pbx_outgoing_exten or ast_pbx_outgoing_app */
 	if (chan) {
@@ -8489,6 +8512,8 @@ static void manager_shutdown(void)
 		manager_free_user(user);
 	}
 	acl_change_stasis_unsubscribe();
+
+	ast_free(manager_channelvars);
 }
 
 
diff --git a/main/message.c b/main/message.c
index 01a1c9b..c15975b 100644
--- a/main/message.c
+++ b/main/message.c
@@ -398,7 +398,7 @@ static void msg_destructor(void *obj)
 	struct ast_msg *msg = obj;
 
 	ast_string_field_free_memory(msg);
-	ao2_ref(msg->vars, -1);
+	ao2_cleanup(msg->vars);
 }
 
 struct ast_msg *ast_msg_alloc(void)
diff --git a/main/pbx.c b/main/pbx.c
index be00328..b166978 100644
--- a/main/pbx.c
+++ b/main/pbx.c
@@ -73,6 +73,7 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
 #include "asterisk/stasis_channels.h"
 #include "asterisk/dial.h"
 #include "asterisk/vector.h"
+#include "pbx_private.h"
 
 /*!
  * \note I M P O R T A N T :
@@ -96,637 +97,6 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
  */
 
 /*** DOCUMENTATION
-	<application name="Answer" language="en_US">
-		<synopsis>
-			Answer a channel if ringing.
-		</synopsis>
-		<syntax>
-			<parameter name="delay">
-				<para>Asterisk will wait this number of milliseconds before returning to
-				the dialplan after answering the call.</para>
-			</parameter>
-		</syntax>
-		<description>
-			<para>If the call has not been answered, this application will
-			answer it. Otherwise, it has no effect on the call.</para>
-		</description>
-		<see-also>
-			<ref type="application">Hangup</ref>
-		</see-also>
-	</application>
-	<application name="BackGround" language="en_US">
-		<synopsis>
-			Play an audio file while waiting for digits of an extension to go to.
-		</synopsis>
-		<syntax>
-			<parameter name="filenames" required="true" argsep="&">
-				<argument name="filename1" required="true" />
-				<argument name="filename2" multiple="true" />
-			</parameter>
-			<parameter name="options">
-				<optionlist>
-					<option name="s">
-						<para>Causes the playback of the message to be skipped
-						if the channel is not in the <literal>up</literal> state (i.e. it
-						hasn't been answered yet). If this happens, the
-						application will return immediately.</para>
-					</option>
-					<option name="n">
-						<para>Don't answer the channel before playing the files.</para>
-					</option>
-					<option name="m">
-						<para>Only break if a digit hit matches a one digit
-						extension in the destination context.</para>
-					</option>
-				</optionlist>
-			</parameter>
-			<parameter name="langoverride">
-				<para>Explicitly specifies which language to attempt to use for the requested sound files.</para>
-			</parameter>
-			<parameter name="context">
-				<para>This is the dialplan context that this application will use when exiting
-				to a dialed extension.</para>
-			</parameter>
-		</syntax>
-		<description>
-			<para>This application will play the given list of files <emphasis>(do not put extension)</emphasis>
-			while waiting for an extension to be dialed by the calling channel. To continue waiting
-			for digits after this application has finished playing files, the <literal>WaitExten</literal>
-			application should be used.</para>
-			<para>If one of the requested sound files does not exist, call processing will be terminated.</para>
-			<para>This application sets the following channel variable upon completion:</para>
-			<variablelist>
-				<variable name="BACKGROUNDSTATUS">
-					<para>The status of the background attempt as a text string.</para>
-					<value name="SUCCESS" />
-					<value name="FAILED" />
-				</variable>
-			</variablelist>
-		</description>
-		<see-also>
-			<ref type="application">ControlPlayback</ref>
-			<ref type="application">WaitExten</ref>
-			<ref type="application">BackgroundDetect</ref>
-			<ref type="function">TIMEOUT</ref>
-		</see-also>
-	</application>
-	<application name="Busy" language="en_US">
-		<synopsis>
-			Indicate the Busy condition.
-		</synopsis>
-		<syntax>
-			<parameter name="timeout">
-				<para>If specified, the calling channel will be hung up after the specified number of seconds.
-				Otherwise, this application will wait until the calling channel hangs up.</para>
-			</parameter>
-		</syntax>
-		<description>
-			<para>This application will indicate the busy condition to the calling channel.</para>
-		</description>
-		<see-also>
-			<ref type="application">Congestion</ref>
-			<ref type="application">Progress</ref>
-			<ref type="application">Playtones</ref>
-			<ref type="application">Hangup</ref>
-		</see-also>
-	</application>
-	<application name="Congestion" language="en_US">
-		<synopsis>
-			Indicate the Congestion condition.
-		</synopsis>
-		<syntax>
-			<parameter name="timeout">
-				<para>If specified, the calling channel will be hung up after the specified number of seconds.
-				Otherwise, this application will wait until the calling channel hangs up.</para>
-			</parameter>
-		</syntax>
-		<description>
-			<para>This application will indicate the congestion condition to the calling channel.</para>
-		</description>
-		<see-also>
-			<ref type="application">Busy</ref>
-			<ref type="application">Progress</ref>
-			<ref type="application">Playtones</ref>
-			<ref type="application">Hangup</ref>
-		</see-also>
-	</application>
-	<application name="ExecIfTime" language="en_US">
-		<synopsis>
-			Conditional application execution based on the current time.
-		</synopsis>
-		<syntax argsep="?">
-			<parameter name="day_condition" required="true">
-				<argument name="times" required="true" />
-				<argument name="weekdays" required="true" />
-				<argument name="mdays" required="true" />
-				<argument name="months" required="true" />
-				<argument name="timezone" required="false" />
-			</parameter>
-			<parameter name="appname" required="true" hasparams="optional">
-				<argument name="appargs" required="true" />
-			</parameter>
-		</syntax>
-		<description>
-			<para>This application will execute the specified dialplan application, with optional
-			arguments, if the current time matches the given time specification.</para>
-		</description>
-		<see-also>
-			<ref type="application">Exec</ref>
-			<ref type="application">ExecIf</ref>
-			<ref type="application">TryExec</ref>
-			<ref type="application">GotoIfTime</ref>
-		</see-also>
-	</application>
-	<application name="Goto" language="en_US">
-		<synopsis>
-			Jump to a particular priority, extension, or context.
-		</synopsis>
-		<syntax>
-			<parameter name="context" />
-			<parameter name="extensions" />
-			<parameter name="priority" required="true" />
-		</syntax>
-		<description>
-			<para>This application will set the current context, extension, and priority in the channel structure.
-			After it completes, the pbx engine will continue dialplan execution at the specified location.
-			If no specific <replaceable>extension</replaceable>, or <replaceable>extension</replaceable> and
-			<replaceable>context</replaceable>, are specified, then this application will
-			just set the specified <replaceable>priority</replaceable> of the current extension.</para>
-			<para>At least a <replaceable>priority</replaceable> is required as an argument, or the goto will
-			return a <literal>-1</literal>,	and the channel and call will be terminated.</para>
-			<para>If the location that is put into the channel information is bogus, and asterisk cannot
-			find that location in the dialplan, then the execution engine will try to find and execute the code in
-			the <literal>i</literal> (invalid) extension in the current context. If that does not exist, it will try to execute the
-			<literal>h</literal> extension. If neither the <literal>h</literal> nor <literal>i</literal> extensions
-			have been defined, the channel is hung up, and the execution of instructions on the channel is terminated.
-			What this means is that, for example, you specify a context that does not exist, then
-			it will not be possible to find the <literal>h</literal> or <literal>i</literal> extensions,
-			and the call will terminate!</para>
-		</description>
-		<see-also>
-			<ref type="application">GotoIf</ref>
-			<ref type="application">GotoIfTime</ref>
-			<ref type="application">Gosub</ref>
-			<ref type="application">Macro</ref>
-		</see-also>
-	</application>
-	<application name="GotoIf" language="en_US">
-		<synopsis>
-			Conditional goto.
-		</synopsis>
-		<syntax argsep="?">
-			<parameter name="condition" required="true" />
-			<parameter name="destination" required="true" argsep=":">
-				<argument name="labeliftrue">
-					<para>Continue at <replaceable>labeliftrue</replaceable> if the condition is true.
-					Takes the form similar to Goto() of [[context,]extension,]priority.</para>
-				</argument>
-				<argument name="labeliffalse">
-					<para>Continue at <replaceable>labeliffalse</replaceable> if the condition is false.
-					Takes the form similar to Goto() of [[context,]extension,]priority.</para>
-				</argument>
-			</parameter>
-		</syntax>
-		<description>
-			<para>This application will set the current context, extension, and priority in the channel structure
-			based on the evaluation of the given condition. After this application completes, the
-			pbx engine will continue dialplan execution at the specified location in the dialplan.
-			The labels are specified with the same syntax as used within the Goto application.
-			If the label chosen by the condition is omitted, no jump is performed, and the execution passes to the
-			next instruction. If the target location is bogus, and does not exist, the execution engine will try
-			to find and execute the code in the <literal>i</literal> (invalid) extension in the current context.
-			If that does not exist, it will try to execute the <literal>h</literal> extension.
-			If neither the <literal>h</literal> nor <literal>i</literal> extensions have been defined,
-			the channel is hung up, and the execution of instructions on the channel is terminated.
-			Remember that this command can set the current context, and if the context specified
-			does not exist, then it will not be able to find any 'h' or 'i' extensions there, and
-			the channel and call will both be terminated!.</para>
-		</description>
-		<see-also>
-			<ref type="application">Goto</ref>
-			<ref type="application">GotoIfTime</ref>
-			<ref type="application">GosubIf</ref>
-			<ref type="application">MacroIf</ref>
-		</see-also>
-	</application>
-	<application name="GotoIfTime" language="en_US">
-		<synopsis>
-			Conditional Goto based on the current time.
-		</synopsis>
-		<syntax argsep="?">
-			<parameter name="condition" required="true">
-				<argument name="times" required="true" />
-				<argument name="weekdays" required="true" />
-				<argument name="mdays" required="true" />
-				<argument name="months" required="true" />
-				<argument name="timezone" required="false" />
-			</parameter>
-			<parameter name="destination" required="true" argsep=":">
-				<argument name="labeliftrue">
-					<para>Continue at <replaceable>labeliftrue</replaceable> if the condition is true.
-					Takes the form similar to Goto() of [[context,]extension,]priority.</para>
-				</argument>
-				<argument name="labeliffalse">
-					<para>Continue at <replaceable>labeliffalse</replaceable> if the condition is false.
-					Takes the form similar to Goto() of [[context,]extension,]priority.</para>
-				</argument>
-			</parameter>
-		</syntax>
-		<description>
-			<para>This application will set the context, extension, and priority in the channel structure
-			based on the evaluation of the given time specification. After this application completes,
-			the pbx engine will continue dialplan execution at the specified location in the dialplan.
-			If the current time is within the given time specification, the channel will continue at
-			<replaceable>labeliftrue</replaceable>. Otherwise the channel will continue at <replaceable>labeliffalse</replaceable>.
-			If the label chosen by the condition is omitted, no jump is performed, and execution passes to the next
-			instruction. If the target jump location is bogus, the same actions would be taken as for <literal>Goto</literal>.
-			Further information on the time specification can be found in examples
-			illustrating how to do time-based context includes in the dialplan.</para>
-		</description>
-		<see-also>
-			<ref type="application">GotoIf</ref>
-			<ref type="application">Goto</ref>
-			<ref type="function">IFTIME</ref>
-			<ref type="function">TESTTIME</ref>
-		</see-also>
-	</application>
-	<application name="ImportVar" language="en_US">
-		<synopsis>
-			Import a variable from a channel into a new variable.
-		</synopsis>
-		<syntax argsep="=">
-			<parameter name="newvar" required="true" />
-			<parameter name="vardata" required="true">
-				<argument name="channelname" required="true" />
-				<argument name="variable" required="true" />
-			</parameter>
-		</syntax>
-		<description>
-			<para>This application imports a <replaceable>variable</replaceable> from the specified
-			<replaceable>channel</replaceable> (as opposed to the current one) and stores it as a variable
-			(<replaceable>newvar</replaceable>) in the current channel (the channel that is calling this
-			application). Variables created by this application have the same inheritance properties as those
-			created with the <literal>Set</literal> application.</para>
-		</description>
-		<see-also>
-			<ref type="application">Set</ref>
-		</see-also>
-	</application>
-	<application name="Hangup" language="en_US">
-		<synopsis>
-			Hang up the calling channel.
-		</synopsis>
-		<syntax>
-			<parameter name="causecode">
-				<para>If a <replaceable>causecode</replaceable> is given the channel's
-				hangup cause will be set to the given value.</para>
-			</parameter>
-		</syntax>
-		<description>
-			<para>This application will hang up the calling channel.</para>
-		</description>
-		<see-also>
-			<ref type="application">Answer</ref>
-			<ref type="application">Busy</ref>
-			<ref type="application">Congestion</ref>
-		</see-also>
-	</application>
-	<application name="Incomplete" language="en_US">
-		<synopsis>
-			Returns AST_PBX_INCOMPLETE value.
-		</synopsis>
-		<syntax>
-			<parameter name="n">
-				<para>If specified, then Incomplete will not attempt to answer the channel first.</para>
-				<note><para>Most channel types need to be in Answer state in order to receive DTMF.</para></note>
-			</parameter>
-		</syntax>
-		<description>
-			<para>Signals the PBX routines that the previous matched extension is incomplete
-			and that further input should be allowed before matching can be considered
-			to be complete.  Can be used within a pattern match when certain criteria warrants
-			a longer match.</para>
-		</description>
-	</application>
-	<application name="NoOp" language="en_US">
-		<synopsis>
-			Do Nothing (No Operation).
-		</synopsis>
-		<syntax>
-			<parameter name="text">
-				<para>Any text provided can be viewed at the Asterisk CLI.</para>
-			</parameter>
-		</syntax>
-		<description>
-			<para>This application does nothing. However, it is useful for debugging purposes.</para>
-			<para>This method can be used to see the evaluations of variables or functions without having any effect.</para>
-		</description>
-		<see-also>
-			<ref type="application">Verbose</ref>
-			<ref type="application">Log</ref>
-		</see-also>
-	</application>
-	<application name="Proceeding" language="en_US">
-		<synopsis>
-			Indicate proceeding.
-		</synopsis>
-		<syntax />
-		<description>
-			<para>This application will request that a proceeding message be provided to the calling channel.</para>
-		</description>
-	</application>
-	<application name="Progress" language="en_US">
-		<synopsis>
-			Indicate progress.
-		</synopsis>
-		<syntax />
-		<description>
-			<para>This application will request that in-band progress information be provided to the calling channel.</para>
-		</description>
-		<see-also>
-			<ref type="application">Busy</ref>
-			<ref type="application">Congestion</ref>
-			<ref type="application">Ringing</ref>
-			<ref type="application">Playtones</ref>
-		</see-also>
-	</application>
-	<application name="RaiseException" language="en_US">
-		<synopsis>
-			Handle an exceptional condition.
-		</synopsis>
-		<syntax>
-			<parameter name="reason" required="true" />
-		</syntax>
-		<description>
-			<para>This application will jump to the <literal>e</literal> extension in the current context, setting the
-			dialplan function EXCEPTION(). If the <literal>e</literal> extension does not exist, the call will hangup.</para>
-		</description>
-		<see-also>
-			<ref type="function">Exception</ref>
-		</see-also>
-	</application>
-	<application name="Ringing" language="en_US">
-		<synopsis>
-			Indicate ringing tone.
-		</synopsis>
-		<syntax />
-		<description>
-			<para>This application will request that the channel indicate a ringing tone to the user.</para>
-		</description>
-		<see-also>
-			<ref type="application">Busy</ref>
-			<ref type="application">Congestion</ref>
-			<ref type="application">Progress</ref>
-			<ref type="application">Playtones</ref>
-		</see-also>
-	</application>
-	<application name="SayAlpha" language="en_US">
-		<synopsis>
-			Say Alpha.
-		</synopsis>
-		<syntax>
-			<parameter name="string" required="true" />
-		</syntax>
-		<description>
-			<para>This application will play the sounds that correspond to the letters
-			of the given <replaceable>string</replaceable>. If the channel variable
-			<variable>SAY_DTMF_INTERRUPT</variable> is set to 'true' (case insensitive),
-			then this application will react to DTMF in the	same way as
-			<literal>Background</literal>.</para>
-		</description>
-		<see-also>
-			<ref type="application">SayDigits</ref>
-			<ref type="application">SayNumber</ref>
-			<ref type="application">SayPhonetic</ref>
-			<ref type="function">CHANNEL</ref>
-		</see-also>
-	</application>
-	<application name="SayAlphaCase" language="en_US">
-		<synopsis>
-			Say Alpha.
-		</synopsis>
-		<syntax>
-			<parameter name="casetype" required="true" >
-				<enumlist>
-					<enum name="a">
-						<para>Case sensitive (all) pronunciation.
-						(Ex: SayAlphaCase(a,aBc); - lowercase a uppercase b lowercase c).</para>
-					</enum>
-					<enum name="l">
-						<para>Case sensitive (lower) pronunciation.
-						(Ex: SayAlphaCase(l,aBc); - lowercase a b lowercase c).</para>
-					</enum>
-					<enum name="n">
-						<para>Case insensitive pronunciation. Equivalent to SayAlpha.
-						(Ex: SayAlphaCase(n,aBc) - a b c).</para>
-					</enum>
-					<enum name="u">
-						<para>Case sensitive (upper) pronunciation.
-						(Ex: SayAlphaCase(u,aBc); - a uppercase b c).</para>
-					</enum>
-				</enumlist>
-			</parameter>
-			<parameter name="string" required="true" />
-		</syntax>
-		<description>
-			<para>This application will play the sounds that correspond to the letters of the
-			given <replaceable>string</replaceable>.  Optionally, a <replaceable>casetype</replaceable> may be
-			specified.  This will be used for case-insensitive or case-sensitive pronunciations. If the channel
-			variable <variable>SAY_DTMF_INTERRUPT</variable> is set to 'true' (case insensitive), then this
-			application will react to DTMF in the same way as <literal>Background</literal>.</para>
-		</description>
-		<see-also>
-			<ref type="application">SayDigits</ref>
-			<ref type="application">SayNumber</ref>
-			<ref type="application">SayPhonetic</ref>
-			<ref type="application">SayAlpha</ref>
-			<ref type="function">CHANNEL</ref>
-		</see-also>
-	</application>
-	<application name="SayDigits" language="en_US">
-		<synopsis>
-			Say Digits.
-		</synopsis>
-		<syntax>
-			<parameter name="digits" required="true" />
-		</syntax>
-		<description>
-			<para>This application will play the sounds that correspond to the digits of
-			the given number. This will use the language that is currently set for the channel.
-			If the channel variable <variable>SAY_DTMF_INTERRUPT</variable> is set to 'true'
-			(case insensitive), then this application will react to DTMF in the same way as
-			<literal>Background</literal>.</para>
-		</description>
-		<see-also>
-			<ref type="application">SayAlpha</ref>
-			<ref type="application">SayNumber</ref>
-			<ref type="application">SayPhonetic</ref>
-			<ref type="function">CHANNEL</ref>
-		</see-also>
-	</application>
-	<application name="SayNumber" language="en_US">
-		<synopsis>
-			Say Number.
-		</synopsis>
-		<syntax>
-			<parameter name="digits" required="true" />
-			<parameter name="gender" />
-		</syntax>
-		<description>
-			<para>This application will play the sounds that correspond to the given
-			<replaceable>digits</replaceable>. Optionally, a <replaceable>gender</replaceable> may be
-			specified. This will use the language that is currently set for the channel. See the CHANNEL()
-			function for more information on setting the language for the channel. If the channel variable
-			<variable>SAY_DTMF_INTERRUPT</variable> is set to 'true' (case insensitive), then this
-			application will react to DTMF in the same way as <literal>Background</literal>.</para>
-		</description>
-		<see-also>
-			<ref type="application">SayAlpha</ref>
-			<ref type="application">SayDigits</ref>
-			<ref type="application">SayPhonetic</ref>
-			<ref type="function">CHANNEL</ref>
-		</see-also>
-	</application>
-	<application name="SayPhonetic" language="en_US">
-		<synopsis>
-			Say Phonetic.
-		</synopsis>
-		<syntax>
-			<parameter name="string" required="true" />
-		</syntax>
-		<description>
-			<para>This application will play the sounds from the phonetic alphabet that correspond to the
-			letters in the given <replaceable>string</replaceable>. If the channel variable
-			<variable>SAY_DTMF_INTERRUPT</variable> is set to 'true' (case insensitive), then this
-			application will react to DTMF in the same way as <literal>Background</literal>.</para>
-		</description>
-		<see-also>
-			<ref type="application">SayAlpha</ref>
-			<ref type="application">SayDigits</ref>
-			<ref type="application">SayNumber</ref>
-		</see-also>
-	</application>
-	<application name="Set" language="en_US">
-		<synopsis>
-			Set channel variable or function value.
-		</synopsis>
-		<syntax argsep="=">
-			<parameter name="name" required="true" />
-			<parameter name="value" required="true" />
-		</syntax>
-		<description>
-			<para>This function can be used to set the value of channel variables or dialplan functions.
-			When setting variables, if the variable name is prefixed with <literal>_</literal>,
-			the variable will be inherited into channels created from the current channel.
-			If the variable name is prefixed with <literal>__</literal>, the variable will be
-			inherited into channels created from the current channel and all children channels.</para>
-			<note><para>If (and only if), in <filename>/etc/asterisk/asterisk.conf</filename>, you have
-			a <literal>[compat]</literal> category, and you have <literal>app_set = 1.4</literal> under that, then
-			the behavior of this app changes, and strips surrounding quotes from the right hand side as
-			it did previously in 1.4.
-			The advantages of not stripping out quoting, and not caring about the separator characters (comma and vertical bar)
-			were sufficient to make these changes in 1.6. Confusion about how many backslashes would be needed to properly
-			protect separators and quotes in various database access strings has been greatly
-			reduced by these changes.</para></note>
-		</description>
-		<see-also>
-			<ref type="application">MSet</ref>
-			<ref type="function">GLOBAL</ref>
-			<ref type="function">SET</ref>
-			<ref type="function">ENV</ref>
-		</see-also>
-	</application>
-	<application name="MSet" language="en_US">
-		<synopsis>
-			Set channel variable(s) or function value(s).
-		</synopsis>
-		<syntax>
-			<parameter name="set1" required="true" argsep="=">
-				<argument name="name1" required="true" />
-				<argument name="value1" required="true" />
-			</parameter>
-			<parameter name="set2" multiple="true" argsep="=">
-				<argument name="name2" required="true" />
-				<argument name="value2" required="true" />
-			</parameter>
-		</syntax>
-		<description>
-			<para>This function can be used to set the value of channel variables or dialplan functions.
-			When setting variables, if the variable name is prefixed with <literal>_</literal>,
-			the variable will be inherited into channels created from the current channel
-			If the variable name is prefixed with <literal>__</literal>, the variable will be
-			inherited into channels created from the current channel and all children channels.
-			MSet behaves in a similar fashion to the way Set worked in 1.2/1.4 and is thus
-			prone to doing things that you may not expect. For example, it strips surrounding
-			double-quotes from the right-hand side (value). If you need to put a separator
-			character (comma or vert-bar), you will need to escape them by inserting a backslash
-			before them. Avoid its use if possible.</para>
-		</description>
-		<see-also>
-			<ref type="application">Set</ref>
-		</see-also>
-	</application>
-	<application name="SetAMAFlags" language="en_US">
-		<synopsis>
-			Set the AMA Flags.
-		</synopsis>
-		<syntax>
-			<parameter name="flag" />
-		</syntax>
-		<description>
-			<para>This application will set the channel's AMA Flags for billing purposes.</para>
-			<warning><para>This application is deprecated. Please use the CHANNEL function instead.</para></warning>
-		</description>
-		<see-also>
-			<ref type="function">CDR</ref>
-			<ref type="function">CHANNEL</ref>
-		</see-also>
-	</application>
-	<application name="Wait" language="en_US">
-		<synopsis>
-			Waits for some time.
-		</synopsis>
-		<syntax>
-			<parameter name="seconds" required="true">
-				<para>Can be passed with fractions of a second. For example, <literal>1.5</literal> will ask the
-				application to wait for 1.5 seconds.</para>
-			</parameter>
-		</syntax>
-		<description>
-			<para>This application waits for a specified number of <replaceable>seconds</replaceable>.</para>
-		</description>
-	</application>
-	<application name="WaitExten" language="en_US">
-		<synopsis>
-			Waits for an extension to be entered.
-		</synopsis>
-		<syntax>
-			<parameter name="seconds">
-				<para>Can be passed with fractions of a second. For example, <literal>1.5</literal> will ask the
-				application to wait for 1.5 seconds.</para>
-			</parameter>
-			<parameter name="options">
-				<optionlist>
-					<option name="m">
-						<para>Provide music on hold to the caller while waiting for an extension.</para>
-						<argument name="x">
-							<para>Specify the class for music on hold. <emphasis>CHANNEL(musicclass) will
-							be used instead if set</emphasis></para>
-						</argument>
-					</option>
-				</optionlist>
-			</parameter>
-		</syntax>
-		<description>
-			<para>This application waits for the user to enter a new extension for a specified number
-			of <replaceable>seconds</replaceable>.</para>
-			<xi:include xpointer="xpointer(/docs/application[@name='Macro']/description/warning[2])" />
-		</description>
-		<see-also>
-			<ref type="application">Background</ref>
-			<ref type="function">TIMEOUT</ref>
-		</see-also>
-	</application>
 	<function name="EXCEPTION" language="en_US">
 		<synopsis>
 			Retrieve the details of the current dialplan exception.
@@ -850,48 +220,15 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
 
 #define SWITCH_DATA_LENGTH 256
 
-#define VAR_BUF_SIZE 4096
-
 #define	VAR_NORMAL		1
 #define	VAR_SOFTTRAN	2
 #define	VAR_HARDTRAN	3
 
-#define BACKGROUND_SKIP		(1 << 0)
-#define BACKGROUND_NOANSWER	(1 << 1)
-#define BACKGROUND_MATCHEXTEN	(1 << 2)
-#define BACKGROUND_PLAYBACK	(1 << 3)
-
-AST_APP_OPTIONS(background_opts, {
-	AST_APP_OPTION('s', BACKGROUND_SKIP),
-	AST_APP_OPTION('n', BACKGROUND_NOANSWER),
-	AST_APP_OPTION('m', BACKGROUND_MATCHEXTEN),
-	AST_APP_OPTION('p', BACKGROUND_PLAYBACK),
-});
-
-#define WAITEXTEN_MOH		(1 << 0)
-#define WAITEXTEN_DIALTONE	(1 << 1)
-
-AST_APP_OPTIONS(waitexten_opts, {
-	AST_APP_OPTION_ARG('m', WAITEXTEN_MOH, 0),
-	AST_APP_OPTION_ARG('d', WAITEXTEN_DIALTONE, 0),
-});
-
 struct ast_context;
 struct ast_app;
 
 AST_THREADSTORAGE(switch_data);
 AST_THREADSTORAGE(extensionstate_buf);
-/*!
- * \brief A thread local indicating whether the current thread can run
- * 'dangerous' dialplan functions.
- */
-AST_THREADSTORAGE(thread_inhibit_escalations_tl);
-
-/*!
- * \brief Set to true (non-zero) to globally allow all dangerous dialplan
- * functions to run.
- */
-static int live_dangerously;
 
 /*!
    \brief ast_exten: An extension
@@ -985,24 +322,6 @@ struct ast_context {
 	char name[0];				/*!< Name of the context */
 };
 
-/*! \brief ast_app: A registered application */
-struct ast_app {
-	int (*execute)(struct ast_channel *chan, const char *data);
-	AST_DECLARE_STRING_FIELDS(
-		AST_STRING_FIELD(synopsis);     /*!< Synopsis text for 'show applications' */
-		AST_STRING_FIELD(description);  /*!< Description (help text) for 'show application <name>' */
-		AST_STRING_FIELD(syntax);       /*!< Syntax text for 'core show applications' */
-		AST_STRING_FIELD(arguments);    /*!< Arguments description */
-		AST_STRING_FIELD(seealso);      /*!< See also */
-	);
-#ifdef AST_XML_DOCS
-	enum ast_doc_src docsrc;		/*!< Where the documentation come from. */
-#endif
-	AST_RWLIST_ENTRY(ast_app) list;		/*!< Next app in list */
-	struct ast_module *module;		/*!< Module this app belongs to */
-	char name[0];				/*!< Name of the application */
-};
-
 /*! \brief ast_state_cb: An extension state notify register item */
 struct ast_state_cb {
 	/*! Watcher ID returned when registered. */
@@ -1051,6 +370,8 @@ struct ast_hint {
 	AST_VECTOR(, char *) devices; /*!< Devices associated with the hint */
 };
 
+STASIS_MESSAGE_TYPE_DEFN_LOCAL(hint_change_message_type);
+
 #define HINTDEVICE_DATA_LENGTH 16
 AST_THREADSTORAGE(hintdevice_data);
 
@@ -1263,34 +584,10 @@ struct pbx_exception {
 	int priority;				/*!< Priority associated with this exception */
 };
 
-static int pbx_builtin_answer(struct ast_channel *, const char *);
-static int pbx_builtin_goto(struct ast_channel *, const char *);
-static int pbx_builtin_hangup(struct ast_channel *, const char *);
-static int pbx_builtin_background(struct ast_channel *, const char *);
-static int pbx_builtin_wait(struct ast_channel *, const char *);
-static int pbx_builtin_waitexten(struct ast_channel *, const char *);
-static int pbx_builtin_incomplete(struct ast_channel *, const char *);
-static int pbx_builtin_setamaflags(struct ast_channel *, const char *);
-static int pbx_builtin_ringing(struct ast_channel *, const char *);
-static int pbx_builtin_proceeding(struct ast_channel *, const char *);
-static int pbx_builtin_progress(struct ast_channel *, const char *);
-static int pbx_builtin_congestion(struct ast_channel *, const char *);
-static int pbx_builtin_busy(struct ast_channel *, const char *);
-static int pbx_builtin_noop(struct ast_channel *, const char *);
-static int pbx_builtin_gotoif(struct ast_channel *, const char *);
-static int pbx_builtin_gotoiftime(struct ast_channel *, const char *);
-static int pbx_builtin_execiftime(struct ast_channel *, const char *);
-static int pbx_builtin_saynumber(struct ast_channel *, const char *);
-static int pbx_builtin_saydigits(struct ast_channel *, const char *);
-static int pbx_builtin_saycharacters(struct ast_channel *, const char *);
-static int pbx_builtin_saycharacters_case(struct ast_channel *, const char *);
-static int pbx_builtin_sayphonetic(struct ast_channel *, const char *);
 static int matchcid(const char *cidpattern, const char *callerid);
 #ifdef NEED_DEBUG
 static void log_match_char_tree(struct match_char *node, char *prefix); /* for use anywhere */
 #endif
-static int pbx_builtin_importvar(struct ast_channel *, const char *);
-static void set_ext_pri(struct ast_channel *c, const char *exten, int pri);
 static void new_find_extension(const char *str, struct scoreboard *score,
 		struct match_char *tree, int length, int spec, const char *callerid,
 		const char *label, enum ext_match_t action);
@@ -1418,10 +715,6 @@ static unsigned int hashtab_hash_labels(const void *obj)
 	return ast_hashtab_hash_string(S_OR(ac->label, ""));
 }
 
-
-AST_RWLOCK_DEFINE_STATIC(globalslock);
-static struct varshead globals = AST_LIST_HEAD_NOLOCK_INIT_VALUE;
-
 static int autofallthrough = 1;
 static int extenpatternmatchnew = 0;
 static char *overrideswitch = NULL;
@@ -1435,50 +728,6 @@ AST_MUTEX_DEFINE_STATIC(maxcalllock);
 static int countcalls;
 static int totalcalls;
 
-/*!
- * \brief Registered functions container.
- *
- * It is sorted by function name.
- */
-static AST_RWLIST_HEAD_STATIC(acf_root, ast_custom_function);
-
-/*! \brief Declaration of builtin applications */
-static struct pbx_builtin {
-	char name[AST_MAX_APP];
-	int (*execute)(struct ast_channel *chan, const char *data);
-} builtins[] =
-{
-	/* These applications are built into the PBX core and do not
-	   need separate modules */
-
-	{ "Answer",         pbx_builtin_answer },
-	{ "BackGround",     pbx_builtin_background },
-	{ "Busy",           pbx_builtin_busy },
-	{ "Congestion",     pbx_builtin_congestion },
-	{ "ExecIfTime",     pbx_builtin_execiftime },
-	{ "Goto",           pbx_builtin_goto },
-	{ "GotoIf",         pbx_builtin_gotoif },
-	{ "GotoIfTime",     pbx_builtin_gotoiftime },
-	{ "ImportVar",      pbx_builtin_importvar },
-	{ "Hangup",         pbx_builtin_hangup },
-	{ "Incomplete",     pbx_builtin_incomplete },
-	{ "NoOp",           pbx_builtin_noop },
-	{ "Proceeding",     pbx_builtin_proceeding },
-	{ "Progress",       pbx_builtin_progress },
-	{ "RaiseException", pbx_builtin_raise_exception },
-	{ "Ringing",        pbx_builtin_ringing },
-	{ "SayAlpha",       pbx_builtin_saycharacters },
-	{ "SayAlphaCase",   pbx_builtin_saycharacters_case },
-	{ "SayDigits",      pbx_builtin_saydigits },
-	{ "SayNumber",      pbx_builtin_saynumber },
-	{ "SayPhonetic",    pbx_builtin_sayphonetic },
-	{ "Set",            pbx_builtin_setvar },
-	{ "MSet",           pbx_builtin_setvar_multiple },
-	{ "SetAMAFlags",    pbx_builtin_setamaflags },
-	{ "Wait",           pbx_builtin_wait },
-	{ "WaitExten",      pbx_builtin_waitexten }
-};
-
 static struct ast_context *contexts;
 static struct ast_hashtab *contexts_table = NULL;
 
@@ -1495,15 +744,6 @@ AST_MUTEX_DEFINE_STATIC(conlock);
  */
 AST_MUTEX_DEFINE_STATIC(context_merge_lock);
 
-/*!
- * \brief Registered applications container.
- *
- * It is sorted by application name.
- */
-static AST_RWLIST_HEAD_STATIC(apps, ast_app);
-
-static AST_RWLIST_HEAD_STATIC(switches, ast_switch);
-
 static int stateid = 1;
 /*!
  * \note When holding this container's lock, do _not_ do
@@ -1693,90 +933,10 @@ int check_contexts(char *file, int line )
 }
 #endif
 
-/*
-   \note This function is special. It saves the stack so that no matter
-   how many times it is called, it returns to the same place */
-int pbx_exec(struct ast_channel *c,	/*!< Channel */
-	     struct ast_app *app,	/*!< Application */
-	     const char *data)		/*!< Data for execution */
+static inline int include_valid(struct ast_include *i)
 {
-	int res;
-	struct ast_module_user *u = NULL;
-	const char *saved_c_appl;
-	const char *saved_c_data;
-
-	/* save channel values */
-	saved_c_appl= ast_channel_appl(c);
-	saved_c_data= ast_channel_data(c);
-
-	ast_channel_lock(c);
-	ast_channel_appl_set(c, app->name);
-	ast_channel_data_set(c, data);
-	ast_channel_publish_snapshot(c);
-	ast_channel_unlock(c);
-
-	if (app->module)
-		u = __ast_module_user_add(app->module, c);
-	res = app->execute(c, S_OR(data, ""));
-	if (app->module && u)
-		__ast_module_user_remove(app->module, u);
-	/* restore channel values */
-	ast_channel_appl_set(c, saved_c_appl);
-	ast_channel_data_set(c, saved_c_data);
-	return res;
-}
-
-static struct ast_app *pbx_findapp_nolock(const char *name)
-{
-	struct ast_app *cur;
-	int cmp;
-
-	AST_RWLIST_TRAVERSE(&apps, cur, list) {
-		cmp = strcasecmp(name, cur->name);
-		if (cmp > 0) {
-			continue;
-		}
-		if (!cmp) {
-			/* Found it. */
-			break;
-		}
-		/* Not in container. */
-		cur = NULL;
-		break;
-	}
-
-	return cur;
-}
-
-struct ast_app *pbx_findapp(const char *app)
-{
-	struct ast_app *ret;
-
-	AST_RWLIST_RDLOCK(&apps);
-	ret = pbx_findapp_nolock(app);
-	AST_RWLIST_UNLOCK(&apps);
-
-	return ret;
-}
-
-static struct ast_switch *pbx_findswitch(const char *sw)
-{
-	struct ast_switch *asw;
-
-	AST_RWLIST_RDLOCK(&switches);
-	AST_RWLIST_TRAVERSE(&switches, asw, list) {
-		if (!strcasecmp(asw->name, sw))
-			break;
-	}
-	AST_RWLIST_UNLOCK(&switches);
-
-	return asw;
-}
-
-static inline int include_valid(struct ast_include *i)
-{
-	if (!i->hastime)
-		return 1;
+	if (!i->hastime)
+		return 1;
 
 	return ast_check_timing(&(i->timing));
 }
@@ -3514,291 +2674,6 @@ struct ast_exten *pbx_find_extension(struct ast_channel *chan,
 	return NULL;
 }
 
-/*!
- * \brief extract offset:length from variable name.
- * \return 1 if there is a offset:length part, which is
- * trimmed off (values go into variables)
- */
-static int parse_variable_name(char *var, int *offset, int *length, int *isfunc)
-{
-	int parens = 0;
-
-	*offset = 0;
-	*length = INT_MAX;
-	*isfunc = 0;
-	for (; *var; var++) {
-		if (*var == '(') {
-			(*isfunc)++;
-			parens++;
-		} else if (*var == ')') {
-			parens--;
-		} else if (*var == ':' && parens == 0) {
-			*var++ = '\0';
-			sscanf(var, "%30d:%30d", offset, length);
-			return 1; /* offset:length valid */
-		}
-	}
-	return 0;
-}
-
-/*!
- *\brief takes a substring. It is ok to call with value == workspace.
- * \param value
- * \param offset < 0 means start from the end of the string and set the beginning
- *   to be that many characters back.
- * \param length is the length of the substring, a value less than 0 means to leave
- * that many off the end.
- * \param workspace
- * \param workspace_len
- * Always return a copy in workspace.
- */
-static char *substring(const char *value, int offset, int length, char *workspace, size_t workspace_len)
-{
-	char *ret = workspace;
-	int lr;	/* length of the input string after the copy */
-
-	ast_copy_string(workspace, value, workspace_len); /* always make a copy */
-
-	lr = strlen(ret); /* compute length after copy, so we never go out of the workspace */
-
-	/* Quick check if no need to do anything */
-	if (offset == 0 && length >= lr)	/* take the whole string */
-		return ret;
-
-	if (offset < 0)	{	/* translate negative offset into positive ones */
-		offset = lr + offset;
-		if (offset < 0) /* If the negative offset was greater than the length of the string, just start at the beginning */
-			offset = 0;
-	}
-
-	/* too large offset result in empty string so we know what to return */
-	if (offset >= lr)
-		return ret + lr;	/* the final '\0' */
-
-	ret += offset;		/* move to the start position */
-	if (length >= 0 && length < lr - offset)	/* truncate if necessary */
-		ret[length] = '\0';
-	else if (length < 0) {
-		if (lr > offset - length) /* After we remove from the front and from the rear, is there anything left? */
-			ret[lr + length - offset] = '\0';
-		else
-			ret[0] = '\0';
-	}
-
-	return ret;
-}
-
-static const char *ast_str_substring(struct ast_str *value, int offset, int length)
-{
-	int lr;	/* length of the input string after the copy */
-
-	lr = ast_str_strlen(value); /* compute length after copy, so we never go out of the workspace */
-
-	/* Quick check if no need to do anything */
-	if (offset == 0 && length >= lr)	/* take the whole string */
-		return ast_str_buffer(value);
-
-	if (offset < 0)	{	/* translate negative offset into positive ones */
-		offset = lr + offset;
-		if (offset < 0) /* If the negative offset was greater than the length of the string, just start at the beginning */
-			offset = 0;
-	}
-
-	/* too large offset result in empty string so we know what to return */
-	if (offset >= lr) {
-		ast_str_reset(value);
-		return ast_str_buffer(value);
-	}
-
-	if (offset > 0) {
-		/* Go ahead and chop off the beginning */
-		memmove(ast_str_buffer(value), ast_str_buffer(value) + offset, ast_str_strlen(value) - offset + 1);
-		lr -= offset;
-	}
-
-	if (length >= 0 && length < lr) {	/* truncate if necessary */
-		char *tmp = ast_str_buffer(value);
-		tmp[length] = '\0';
-		ast_str_update(value);
-	} else if (length < 0) {
-		if (lr > -length) { /* After we remove from the front and from the rear, is there anything left? */
-			char *tmp = ast_str_buffer(value);
-			tmp[lr + length] = '\0';
-			ast_str_update(value);
-		} else {
-			ast_str_reset(value);
-		}
-	} else {
-		/* Nothing to do, but update the buffer length */
-		ast_str_update(value);
-	}
-
-	return ast_str_buffer(value);
-}
-
-/*! \brief  Support for Asterisk built-in variables in the dialplan
-
-\note	See also
-	- \ref AstVar	Channel variables
-	- \ref AstCauses The HANGUPCAUSE variable
- */
-void pbx_retrieve_variable(struct ast_channel *c, const char *var, char **ret, char *workspace, int workspacelen, struct varshead *headp)
-{
-	struct ast_str *str = ast_str_create(16);
-	const char *cret;
-
-	cret = ast_str_retrieve_variable(&str, 0, c, headp, var);
-	ast_copy_string(workspace, ast_str_buffer(str), workspacelen);
-	*ret = cret ? workspace : NULL;
-	ast_free(str);
-}
-
-const char *ast_str_retrieve_variable(struct ast_str **str, ssize_t maxlen, struct ast_channel *c, struct varshead *headp, const char *var)
-{
-	const char not_found = '\0';
-	char *tmpvar;
-	const char *ret;
-	const char *s;	/* the result */
-	int offset, length;
-	int i, need_substring;
-	struct varshead *places[2] = { headp, &globals };	/* list of places where we may look */
-	char workspace[20];
-
-	if (c) {
-		ast_channel_lock(c);
-		places[0] = ast_channel_varshead(c);
-	}
-	/*
-	 * Make a copy of var because parse_variable_name() modifies the string.
-	 * Then if called directly, we might need to run substring() on the result;
-	 * remember this for later in 'need_substring', 'offset' and 'length'
-	 */
-	tmpvar = ast_strdupa(var);	/* parse_variable_name modifies the string */
-	need_substring = parse_variable_name(tmpvar, &offset, &length, &i /* ignored */);
-
-	/*
-	 * Look first into predefined variables, then into variable lists.
-	 * Variable 's' points to the result, according to the following rules:
-	 * s == &not_found (set at the beginning) means that we did not find a
-	 *	matching variable and need to look into more places.
-	 * If s != &not_found, s is a valid result string as follows:
-	 * s = NULL if the variable does not have a value;
-	 *	you typically do this when looking for an unset predefined variable.
-	 * s = workspace if the result has been assembled there;
-	 *	typically done when the result is built e.g. with an snprintf(),
-	 *	so we don't need to do an additional copy.
-	 * s != workspace in case we have a string, that needs to be copied
-	 *	(the ast_copy_string is done once for all at the end).
-	 *	Typically done when the result is already available in some string.
-	 */
-	s = &not_found;	/* default value */
-	if (c) {	/* This group requires a valid channel */
-		/* Names with common parts are looked up a piece at a time using strncmp. */
-		if (!strncmp(var, "CALL", 4)) {
-			if (!strncmp(var + 4, "ING", 3)) {
-				if (!strcmp(var + 7, "PRES")) {			/* CALLINGPRES */
-					ast_str_set(str, maxlen, "%d",
-						ast_party_id_presentation(&ast_channel_caller(c)->id));
-					s = ast_str_buffer(*str);
-				} else if (!strcmp(var + 7, "ANI2")) {		/* CALLINGANI2 */
-					ast_str_set(str, maxlen, "%d", ast_channel_caller(c)->ani2);
-					s = ast_str_buffer(*str);
-				} else if (!strcmp(var + 7, "TON")) {		/* CALLINGTON */
-					ast_str_set(str, maxlen, "%d", ast_channel_caller(c)->id.number.plan);
-					s = ast_str_buffer(*str);
-				} else if (!strcmp(var + 7, "TNS")) {		/* CALLINGTNS */
-					ast_str_set(str, maxlen, "%d", ast_channel_dialed(c)->transit_network_select);
-					s = ast_str_buffer(*str);
-				}
-			}
-		} else if (!strcmp(var, "HINT")) {
-			s = ast_str_get_hint(str, maxlen, NULL, 0, c, ast_channel_context(c), ast_channel_exten(c)) ? ast_str_buffer(*str) : NULL;
-		} else if (!strcmp(var, "HINTNAME")) {
-			s = ast_str_get_hint(NULL, 0, str, maxlen, c, ast_channel_context(c), ast_channel_exten(c)) ? ast_str_buffer(*str) : NULL;
-		} else if (!strcmp(var, "EXTEN")) {
-			s = ast_channel_exten(c);
-		} else if (!strcmp(var, "CONTEXT")) {
-			s = ast_channel_context(c);
-		} else if (!strcmp(var, "PRIORITY")) {
-			ast_str_set(str, maxlen, "%d", ast_channel_priority(c));
-			s = ast_str_buffer(*str);
-		} else if (!strcmp(var, "CHANNEL")) {
-			s = ast_channel_name(c);
-		} else if (!strcmp(var, "UNIQUEID")) {
-			s = ast_channel_uniqueid(c);
-		} else if (!strcmp(var, "HANGUPCAUSE")) {
-			ast_str_set(str, maxlen, "%d", ast_channel_hangupcause(c));
-			s = ast_str_buffer(*str);
-		}
-	}
-	if (s == &not_found) { /* look for more */
-		if (!strcmp(var, "EPOCH")) {
-			ast_str_set(str, maxlen, "%d", (int) time(NULL));
-			s = ast_str_buffer(*str);
-		} else if (!strcmp(var, "SYSTEMNAME")) {
-			s = ast_config_AST_SYSTEM_NAME;
-		} else if (!strcmp(var, "ASTETCDIR")) {
-			s = ast_config_AST_CONFIG_DIR;
-		} else if (!strcmp(var, "ASTMODDIR")) {
-			s = ast_config_AST_MODULE_DIR;
-		} else if (!strcmp(var, "ASTVARLIBDIR")) {
-			s = ast_config_AST_VAR_DIR;
-		} else if (!strcmp(var, "ASTDBDIR")) {
-			s = ast_config_AST_DB;
-		} else if (!strcmp(var, "ASTKEYDIR")) {
-			s = ast_config_AST_KEY_DIR;
-		} else if (!strcmp(var, "ASTDATADIR")) {
-			s = ast_config_AST_DATA_DIR;
-		} else if (!strcmp(var, "ASTAGIDIR")) {
-			s = ast_config_AST_AGI_DIR;
-		} else if (!strcmp(var, "ASTSPOOLDIR")) {
-			s = ast_config_AST_SPOOL_DIR;
-		} else if (!strcmp(var, "ASTRUNDIR")) {
-			s = ast_config_AST_RUN_DIR;
-		} else if (!strcmp(var, "ASTLOGDIR")) {
-			s = ast_config_AST_LOG_DIR;
-		} else if (!strcmp(var, "ENTITYID")) {
-			ast_eid_to_str(workspace, sizeof(workspace), &ast_eid_default);
-			s = workspace;
-		}
-	}
-	/* if not found, look into chanvars or global vars */
-	for (i = 0; s == &not_found && i < ARRAY_LEN(places); i++) {
-		struct ast_var_t *variables;
-		if (!places[i])
-			continue;
-		if (places[i] == &globals)
-			ast_rwlock_rdlock(&globalslock);
-		AST_LIST_TRAVERSE(places[i], variables, entries) {
-			if (!strcmp(ast_var_name(variables), var)) {
-				s = ast_var_value(variables);
-				break;
-			}
-		}
-		if (places[i] == &globals)
-			ast_rwlock_unlock(&globalslock);
-	}
-	if (s == &not_found || s == NULL) {
-		ast_debug(5, "Result of '%s' is NULL\n", var);
-		ret = NULL;
-	} else {
-		ast_debug(5, "Result of '%s' is '%s'\n", var, s);
-		if (s != ast_str_buffer(*str)) {
-			ast_str_set(str, maxlen, "%s", s);
-		}
-		ret = ast_str_buffer(*str);
-		if (need_substring) {
-			ret = ast_str_substring(*str, offset, length);
-			ast_debug(2, "Final result of '%s' is '%s'\n", var, ret);
-		}
-	}
-
-	if (c) {
-		ast_channel_unlock(c);
-	}
-	return ret;
-}
-
 static void exception_store_free(void *data)
 {
 	struct pbx_exception *exception = data;
@@ -3822,7 +2697,7 @@ static const struct ast_datastore_info exception_store_info = {
  * \retval 0 on success.
  * \retval -1 on error.
  */
-static int raise_exception(struct ast_channel *chan, const char *reason, int priority)
+int raise_exception(struct ast_channel *chan, const char *reason, int priority)
 {
 	struct ast_datastore *ds = ast_channel_datastore_find(chan, &exception_store_info, NULL);
 	struct pbx_exception *exception = NULL;
@@ -3848,12 +2723,6 @@ static int raise_exception(struct ast_channel *chan, const char *reason, int pri
 	return 0;
 }
 
-int pbx_builtin_raise_exception(struct ast_channel *chan, const char *reason)
-{
-	/* Priority will become 1, next time through the AUTOLOOP */
-	return raise_exception(chan, reason, 0);
-}
-
 static int acf_exception_read(struct ast_channel *chan, const char *name, char *data, char *buf, size_t buflen)
 {
 	struct ast_datastore *ds = ast_channel_datastore_find(chan, &exception_store_info, NULL);
@@ -3879,8218 +2748,5262 @@ static struct ast_custom_function exception_function = {
 	.read = acf_exception_read,
 };
 
-static char *handle_show_functions(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+/*!
+ * \brief The return value depends on the action:
+ *
+ * E_MATCH, E_CANMATCH, E_MATCHMORE require a real match,
+ *	and return 0 on failure, -1 on match;
+ * E_FINDLABEL maps the label to a priority, and returns
+ *	the priority on success, ... XXX
+ * E_SPAWN, spawn an application,
+ *
+ * \retval 0 on success.
+ * \retval  -1 on failure.
+ *
+ * \note The channel is auto-serviced in this function, because doing an extension
+ * match may block for a long time.  For example, if the lookup has to use a network
+ * dialplan switch, such as DUNDi or IAX2, it may take a while.  However, the channel
+ * auto-service code will queue up any important signalling frames to be processed
+ * after this is done.
+ */
+static int pbx_extension_helper(struct ast_channel *c, struct ast_context *con,
+  const char *context, const char *exten, int priority,
+  const char *label, const char *callerid, enum ext_match_t action, int *found, int combined_find_spawn)
 {
-	struct ast_custom_function *acf;
-	int count_acf = 0;
-	int like = 0;
-
-	switch (cmd) {
-	case CLI_INIT:
-		e->command = "core show functions [like]";
-		e->usage =
-			"Usage: core show functions [like <text>]\n"
-			"       List builtin functions, optionally only those matching a given string\n";
-		return NULL;
-	case CLI_GENERATE:
-		return NULL;
-	}
-
-	if (a->argc == 5 && (!strcmp(a->argv[3], "like")) ) {
-		like = 1;
-	} else if (a->argc != 3) {
-		return CLI_SHOWUSAGE;
-	}
+	struct ast_exten *e;
+	struct ast_app *app;
+	char *substitute = NULL;
+	int res;
+	struct pbx_find_info q = { .stacklen = 0 }; /* the rest is reset in pbx_find_extension */
+	char passdata[EXT_DATA_SIZE];
+	int matching_action = (action == E_MATCH || action == E_CANMATCH || action == E_MATCHMORE);
 
-	ast_cli(a->fd, "%s Custom Functions:\n--------------------------------------------------------------------------------\n", like ? "Matching" : "Installed");
+	ast_rdlock_contexts();
+	if (found)
+		*found = 0;
 
-	AST_RWLIST_RDLOCK(&acf_root);
-	AST_RWLIST_TRAVERSE(&acf_root, acf, acflist) {
-		if (!like || strstr(acf->name, a->argv[4])) {
-			count_acf++;
-			ast_cli(a->fd, "%-20.20s  %-35.35s  %s\n",
-				S_OR(acf->name, ""),
-				S_OR(acf->syntax, ""),
-				S_OR(acf->synopsis, ""));
+	e = pbx_find_extension(c, con, &q, context, exten, priority, label, callerid, action);
+	if (e) {
+		if (found)
+			*found = 1;
+		if (matching_action) {
+			ast_unlock_contexts();
+			return -1;	/* success, we found it */
+		} else if (action == E_FINDLABEL) { /* map the label to a priority */
+			res = e->priority;
+			ast_unlock_contexts();
+			return res;	/* the priority we were looking for */
+		} else {	/* spawn */
+			if (!e->cached_app)
+				e->cached_app = pbx_findapp(e->app);
+			app = e->cached_app;
+			if (ast_strlen_zero(e->data)) {
+				*passdata = '\0';
+			} else {
+				const char *tmp;
+				if ((!(tmp = strchr(e->data, '$'))) || (!strstr(tmp, "${") && !strstr(tmp, "$["))) {
+					/* no variables to substitute, copy on through */
+					ast_copy_string(passdata, e->data, sizeof(passdata));
+				} else {
+					/* save e->data on stack for later processing after lock released */
+					substitute = ast_strdupa(e->data);
+				}
+			}
+			ast_unlock_contexts();
+			if (!app) {
+				ast_log(LOG_WARNING, "No application '%s' for extension (%s, %s, %d)\n", e->app, context, exten, priority);
+				return -1;
+			}
+			if (ast_channel_context(c) != context)
+				ast_channel_context_set(c, context);
+			if (ast_channel_exten(c) != exten)
+				ast_channel_exten_set(c, exten);
+			ast_channel_priority_set(c, priority);
+			if (substitute) {
+				pbx_substitute_variables_helper(c, substitute, passdata, sizeof(passdata)-1);
+			}
+			ast_debug(1, "Launching '%s'\n", app_name(app));
+			if (VERBOSITY_ATLEAST(3)) {
+				ast_verb(3, "Executing [%s@%s:%d] " COLORIZE_FMT "(\"" COLORIZE_FMT "\", \"" COLORIZE_FMT "\") %s\n",
+					exten, context, priority,
+					COLORIZE(COLOR_BRCYAN, 0, app_name(app)),
+					COLORIZE(COLOR_BRMAGENTA, 0, ast_channel_name(c)),
+					COLORIZE(COLOR_BRMAGENTA, 0, passdata),
+					"in new stack");
+			}
+			return pbx_exec(c, app, passdata);	/* 0 on success, -1 on failure */
+		}
+	} else if (q.swo) {	/* not found here, but in another switch */
+		if (found)
+			*found = 1;
+		ast_unlock_contexts();
+		if (matching_action) {
+			return -1;
+		} else {
+			if (!q.swo->exec) {
+				ast_log(LOG_WARNING, "No execution engine for switch %s\n", q.swo->name);
+				res = -1;
+			}
+			return q.swo->exec(c, q.foundcontext ? q.foundcontext : context, exten, priority, callerid, q.data);
+		}
+	} else {	/* not found anywhere, see what happened */
+		ast_unlock_contexts();
+		/* Using S_OR here because Solaris doesn't like NULL being passed to ast_log */
+		switch (q.status) {
+		case STATUS_NO_CONTEXT:
+			if (!matching_action && !combined_find_spawn)
+				ast_log(LOG_NOTICE, "Cannot find extension context '%s'\n", S_OR(context, ""));
+			break;
+		case STATUS_NO_EXTENSION:
+			if (!matching_action && !combined_find_spawn)
+				ast_log(LOG_NOTICE, "Cannot find extension '%s' in context '%s'\n", exten, S_OR(context, ""));
+			break;
+		case STATUS_NO_PRIORITY:
+			if (!matching_action && !combined_find_spawn)
+				ast_log(LOG_NOTICE, "No such priority %d in extension '%s' in context '%s'\n", priority, exten, S_OR(context, ""));
+			break;
+		case STATUS_NO_LABEL:
+			if (context && !combined_find_spawn)
+				ast_log(LOG_NOTICE, "No such label '%s' in extension '%s' in context '%s'\n", label, exten, S_OR(context, ""));
+			break;
+		default:
+			ast_debug(1, "Shouldn't happen!\n");
 		}
-	}
-	AST_RWLIST_UNLOCK(&acf_root);
-
-	ast_cli(a->fd, "%d %scustom functions installed.\n", count_acf, like ? "matching " : "");
 
-	return CLI_SUCCESS;
+		return (matching_action) ? 0 : -1;
+	}
 }
 
-static char *complete_functions(const char *word, int pos, int state)
+/*! \brief Find hint for given extension in context */
+static struct ast_exten *ast_hint_extension_nolock(struct ast_channel *c, const char *context, const char *exten)
 {
-	struct ast_custom_function *cur;
-	char *ret = NULL;
-	int which = 0;
-	int wordlen;
-	int cmp;
+	struct pbx_find_info q = { .stacklen = 0 }; /* the rest is set in pbx_find_context */
+	return pbx_find_extension(c, NULL, &q, context, exten, PRIORITY_HINT, NULL, "", E_MATCH);
+}
 
-	if (pos != 3) {
-		return NULL;
-	}
+static struct ast_exten *ast_hint_extension(struct ast_channel *c, const char *context, const char *exten)
+{
+	struct ast_exten *e;
+	ast_rdlock_contexts();
+	e = ast_hint_extension_nolock(c, context, exten);
+	ast_unlock_contexts();
+	return e;
+}
 
-	wordlen = strlen(word);
-	AST_RWLIST_RDLOCK(&acf_root);
-	AST_RWLIST_TRAVERSE(&acf_root, cur, acflist) {
-		/*
-		 * Do a case-insensitive search for convenience in this
-		 * 'complete' function.
-		 *
-		 * We must search the entire container because the functions are
-		 * sorted and normally found case sensitively.
-		 */
-		cmp = strncasecmp(word, cur->name, wordlen);
-		if (!cmp) {
-			/* Found match. */
-			if (++which <= state) {
-				/* Not enough matches. */
-				continue;
-			}
-			ret = ast_strdup(cur->name);
-			break;
-		}
+enum ast_extension_states ast_devstate_to_extenstate(enum ast_device_state devstate)
+{
+	switch (devstate) {
+	case AST_DEVICE_ONHOLD:
+		return AST_EXTENSION_ONHOLD;
+	case AST_DEVICE_BUSY:
+		return AST_EXTENSION_BUSY;
+	case AST_DEVICE_UNKNOWN:
+		return AST_EXTENSION_NOT_INUSE;
+	case AST_DEVICE_UNAVAILABLE:
+	case AST_DEVICE_INVALID:
+		return AST_EXTENSION_UNAVAILABLE;
+	case AST_DEVICE_RINGINUSE:
+		return (AST_EXTENSION_INUSE | AST_EXTENSION_RINGING);
+	case AST_DEVICE_RINGING:
+		return AST_EXTENSION_RINGING;
+	case AST_DEVICE_INUSE:
+		return AST_EXTENSION_INUSE;
+	case AST_DEVICE_NOT_INUSE:
+		return AST_EXTENSION_NOT_INUSE;
+	case AST_DEVICE_TOTAL: /* not a device state, included for completeness */
+		break;
 	}
-	AST_RWLIST_UNLOCK(&acf_root);
 
-	return ret;
+	return AST_EXTENSION_NOT_INUSE;
 }
 
-static char *handle_show_function(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+/*!
+ * \internal
+ * \brief Parse out the presence portion of the hint string
+ */
+static char *parse_hint_presence(struct ast_str *hint_args)
 {
-	struct ast_custom_function *acf;
-	/* Maximum number of characters added by terminal coloring is 22 */
-	char infotitle[64 + AST_MAX_APP + 22], syntitle[40], destitle[40], argtitle[40], seealsotitle[40];
-	char info[64 + AST_MAX_APP], *synopsis = NULL, *description = NULL, *seealso = NULL;
-	char stxtitle[40], *syntax = NULL, *arguments = NULL;
-	int syntax_size, description_size, synopsis_size, arguments_size, seealso_size;
+	char *copy = ast_strdupa(ast_str_buffer(hint_args));
+	char *tmp = "";
 
-	switch (cmd) {
-	case CLI_INIT:
-		e->command = "core show function";
-		e->usage =
-			"Usage: core show function <function>\n"
-			"       Describe a particular dialplan function.\n";
+	if ((tmp = strrchr(copy, ','))) {
+		*tmp = '\0';
+		tmp++;
+	} else {
 		return NULL;
-	case CLI_GENERATE:
-		return complete_functions(a->word, a->pos, a->n);
 	}
+	ast_str_set(&hint_args, 0, "%s", tmp);
+	return ast_str_buffer(hint_args);
+}
 
-	if (a->argc != 4) {
-		return CLI_SHOWUSAGE;
-	}
+/*!
+ * \internal
+ * \brief Parse out the device portion of the hint string
+ */
+static char *parse_hint_device(struct ast_str *hint_args)
+{
+	char *copy = ast_strdupa(ast_str_buffer(hint_args));
+	char *tmp;
 
-	if (!(acf = ast_custom_function_find(a->argv[3]))) {
-		ast_cli(a->fd, "No function by that name registered.\n");
-		return CLI_FAILURE;
+	if ((tmp = strrchr(copy, ','))) {
+		*tmp = '\0';
 	}
 
-	syntax_size = strlen(S_OR(acf->syntax, "Not Available")) + AST_TERM_MAX_ESCAPE_CHARS;
-	if (!(syntax = ast_malloc(syntax_size))) {
-		ast_cli(a->fd, "Memory allocation failure!\n");
-		return CLI_FAILURE;
-	}
+	ast_str_set(&hint_args, 0, "%s", copy);
+	return ast_str_buffer(hint_args);
+}
 
-	snprintf(info, sizeof(info), "\n  -= Info about function '%s' =- \n\n", acf->name);
-	term_color(infotitle, info, COLOR_MAGENTA, 0, sizeof(infotitle));
-	term_color(syntitle, "[Synopsis]\n", COLOR_MAGENTA, 0, 40);
-	term_color(destitle, "[Description]\n", COLOR_MAGENTA, 0, 40);
-	term_color(stxtitle, "[Syntax]\n", COLOR_MAGENTA, 0, 40);
-	term_color(argtitle, "[Arguments]\n", COLOR_MAGENTA, 0, 40);
-	term_color(seealsotitle, "[See Also]\n", COLOR_MAGENTA, 0, 40);
-	term_color(syntax, S_OR(acf->syntax, "Not available"), COLOR_CYAN, 0, syntax_size);
-#ifdef AST_XML_DOCS
-	if (acf->docsrc == AST_XML_DOC) {
-		arguments = ast_xmldoc_printable(S_OR(acf->arguments, "Not available"), 1);
-		synopsis = ast_xmldoc_printable(S_OR(acf->synopsis, "Not available"), 1);
-		description = ast_xmldoc_printable(S_OR(acf->desc, "Not available"), 1);
-		seealso = ast_xmldoc_printable(S_OR(acf->seealso, "Not available"), 1);
-	} else
-#endif
-	{
-		synopsis_size = strlen(S_OR(acf->synopsis, "Not Available")) + AST_TERM_MAX_ESCAPE_CHARS;
-		synopsis = ast_malloc(synopsis_size);
+static void device_state_info_dt(void *obj)
+{
+	struct ast_device_state_info *info = obj;
 
-		description_size = strlen(S_OR(acf->desc, "Not Available")) + AST_TERM_MAX_ESCAPE_CHARS;
-		description = ast_malloc(description_size);
+	ao2_cleanup(info->causing_channel);
+}
 
-		arguments_size = strlen(S_OR(acf->arguments, "Not Available")) + AST_TERM_MAX_ESCAPE_CHARS;
-		arguments = ast_malloc(arguments_size);
+static struct ao2_container *alloc_device_state_info(void)
+{
+	return ao2_container_alloc_options(AO2_ALLOC_OPT_LOCK_NOLOCK, 1, NULL, NULL);
+}
 
-		seealso_size = strlen(S_OR(acf->seealso, "Not Available")) + AST_TERM_MAX_ESCAPE_CHARS;
-		seealso = ast_malloc(seealso_size);
+static int ast_extension_state3(struct ast_str *hint_app, struct ao2_container *device_state_info)
+{
+	char *cur;
+	char *rest;
+	struct ast_devstate_aggregate agg;
 
-		/* check allocated memory. */
-		if (!synopsis || !description || !arguments || !seealso) {
-			ast_free(synopsis);
-			ast_free(description);
-			ast_free(arguments);
-			ast_free(seealso);
-			ast_free(syntax);
-			return CLI_FAILURE;
-		}
+	/* One or more devices separated with a & character */
+	rest = parse_hint_device(hint_app);
 
-		term_color(arguments, S_OR(acf->arguments, "Not available"), COLOR_CYAN, 0, arguments_size);
-		term_color(synopsis, S_OR(acf->synopsis, "Not available"), COLOR_CYAN, 0, synopsis_size);
-		term_color(description, S_OR(acf->desc, "Not available"), COLOR_CYAN, 0, description_size);
-		term_color(seealso, S_OR(acf->seealso, "Not available"), COLOR_CYAN, 0, seealso_size);
-	}
+	ast_devstate_aggregate_init(&agg);
+	while ((cur = strsep(&rest, "&"))) {
+		enum ast_device_state state = ast_device_state(cur);
 
-	ast_cli(a->fd, "%s%s%s\n\n%s%s\n\n%s%s\n\n%s%s\n\n%s%s\n",
-			infotitle, syntitle, synopsis, destitle, description,
-			stxtitle, syntax, argtitle, arguments, seealsotitle, seealso);
+		ast_devstate_aggregate_add(&agg, state);
+		if (device_state_info) {
+			struct ast_device_state_info *obj;
 
-	ast_free(arguments);
-	ast_free(synopsis);
-	ast_free(description);
-	ast_free(seealso);
-	ast_free(syntax);
+			obj = ao2_alloc_options(sizeof(*obj) + strlen(cur), device_state_info_dt, AO2_ALLOC_OPT_LOCK_NOLOCK);
+			/* if failed we cannot add this device */
+			if (obj) {
+				obj->device_state = state;
+				strcpy(obj->device_name, cur);
+				ao2_link(device_state_info, obj);
+				ao2_ref(obj, -1);
+			}
+		}
+	}
 
-	return CLI_SUCCESS;
+	return ast_devstate_to_extenstate(ast_devstate_aggregate_result(&agg));
 }
 
-static struct ast_custom_function *ast_custom_function_find_nolock(const char *name)
+/*! \brief Check state of extension by using hints */
+static int ast_extension_state2(struct ast_exten *e, struct ao2_container *device_state_info)
 {
-	struct ast_custom_function *cur;
-	int cmp;
+	struct ast_str *hint_app = ast_str_thread_get(&extensionstate_buf, 32);
 
-	AST_RWLIST_TRAVERSE(&acf_root, cur, acflist) {
-		cmp = strcmp(name, cur->name);
-		if (cmp > 0) {
-			continue;
-		}
-		if (!cmp) {
-			/* Found it. */
-			break;
-		}
-		/* Not in container. */
-		cur = NULL;
-		break;
+	if (!e || !hint_app) {
+		return -1;
 	}
 
-	return cur;
+	ast_str_set(&hint_app, 0, "%s", ast_get_extension_app(e));
+	return ast_extension_state3(hint_app, device_state_info);
 }
 
-struct ast_custom_function *ast_custom_function_find(const char *name)
+/*! \brief Return extension_state as string */
+const char *ast_extension_state2str(int extension_state)
 {
-	struct ast_custom_function *acf;
-
-	AST_RWLIST_RDLOCK(&acf_root);
-	acf = ast_custom_function_find_nolock(name);
-	AST_RWLIST_UNLOCK(&acf_root);
+	int i;
 
-	return acf;
+	for (i = 0; (i < ARRAY_LEN(extension_states)); i++) {
+		if (extension_states[i].extension_state == extension_state)
+			return extension_states[i].text;
+	}
+	return "Unknown";
 }
 
-int ast_custom_function_unregister(struct ast_custom_function *acf)
+/*!
+ * \internal
+ * \brief Check extension state for an extension by using hint
+ */
+static int internal_extension_state_extended(struct ast_channel *c, const char *context, const char *exten,
+	struct ao2_container *device_state_info)
 {
-	struct ast_custom_function *cur;
+	struct ast_exten *e;
 
-	if (!acf) {
-		return -1;
+	if (!(e = ast_hint_extension(c, context, exten))) {  /* Do we have a hint for this extension ? */
+		return -1;                   /* No hint, return -1 */
 	}
 
-	AST_RWLIST_WRLOCK(&acf_root);
-	if ((cur = AST_RWLIST_REMOVE(&acf_root, acf, acflist))) {
-#ifdef AST_XML_DOCS
-		if (cur->docsrc == AST_XML_DOC) {
-			ast_string_field_free_memory(acf);
+	if (e->exten[0] == '_') {
+		/* Create this hint on-the-fly */
+		ast_add_extension(e->parent->name, 0, exten, e->priority, e->label,
+			e->matchcid ? e->cidmatch : NULL, e->app, ast_strdup(e->data), ast_free_ptr,
+			e->registrar);
+		if (!(e = ast_hint_extension(c, context, exten))) {
+			/* Improbable, but not impossible */
+			return -1;
 		}
-#endif
-		ast_verb(2, "Unregistered custom function %s\n", cur->name);
 	}
-	AST_RWLIST_UNLOCK(&acf_root);
-
-	return cur ? 0 : -1;
-}
 
-/*!
- * \brief Returns true if given custom function escalates privileges on read.
- *
- * \param acf Custom function to query.
- * \return True (non-zero) if reads escalate privileges.
- * \return False (zero) if reads just read.
- */
-static int read_escalates(const struct ast_custom_function *acf) {
-	return acf->read_escalates;
+	return ast_extension_state2(e, device_state_info);  /* Check all devices in the hint */
 }
 
-/*!
- * \brief Returns true if given custom function escalates privileges on write.
- *
- * \param acf Custom function to query.
- * \return True (non-zero) if writes escalate privileges.
- * \return False (zero) if writes just write.
- */
-static int write_escalates(const struct ast_custom_function *acf) {
-	return acf->write_escalates;
+/*! \brief Check extension state for an extension by using hint */
+int ast_extension_state(struct ast_channel *c, const char *context, const char *exten)
+{
+	return internal_extension_state_extended(c, context, exten, NULL);
 }
 
-/*! \internal
- *  \brief Retrieve the XML documentation of a specified ast_custom_function,
- *         and populate ast_custom_function string fields.
- *  \param acf ast_custom_function structure with empty 'desc' and 'synopsis'
- *             but with a function 'name'.
- *  \retval -1 On error.
- *  \retval 0 On succes.
- */
-static int acf_retrieve_docs(struct ast_custom_function *acf)
+/*! \brief Check extended extension state for an extension by using hint */
+int ast_extension_state_extended(struct ast_channel *c, const char *context, const char *exten,
+	struct ao2_container **device_state_info)
 {
-#ifdef AST_XML_DOCS
-	char *tmpxml;
+	struct ao2_container *container = NULL;
+	int ret;
 
-	/* Let's try to find it in the Documentation XML */
-	if (!ast_strlen_zero(acf->desc) || !ast_strlen_zero(acf->synopsis)) {
-		return 0;
+	if (device_state_info) {
+		container = alloc_device_state_info();
 	}
 
-	if (ast_string_field_init(acf, 128)) {
-		return -1;
+	ret = internal_extension_state_extended(c, context, exten, container);
+	if (ret < 0 && container) {
+		ao2_ref(container, -1);
+		container = NULL;
 	}
 
-	/* load synopsis */
-	tmpxml = ast_xmldoc_build_synopsis("function", acf->name, ast_module_name(acf->mod));
-	ast_string_field_set(acf, synopsis, tmpxml);
-	ast_free(tmpxml);
-
-	/* load description */
-	tmpxml = ast_xmldoc_build_description("function", acf->name, ast_module_name(acf->mod));
-	ast_string_field_set(acf, desc, tmpxml);
-	ast_free(tmpxml);
-
-	/* load syntax */
-	tmpxml = ast_xmldoc_build_syntax("function", acf->name, ast_module_name(acf->mod));
-	ast_string_field_set(acf, syntax, tmpxml);
-	ast_free(tmpxml);
-
-	/* load arguments */
-	tmpxml = ast_xmldoc_build_arguments("function", acf->name, ast_module_name(acf->mod));
-	ast_string_field_set(acf, arguments, tmpxml);
-	ast_free(tmpxml);
-
-	/* load seealso */
-	tmpxml = ast_xmldoc_build_seealso("function", acf->name, ast_module_name(acf->mod));
-	ast_string_field_set(acf, seealso, tmpxml);
-	ast_free(tmpxml);
-
-	acf->docsrc = AST_XML_DOC;
-#endif
+	if (device_state_info) {
+		get_device_state_causing_channels(container);
+		*device_state_info = container;
+	}
 
-	return 0;
+	return ret;
 }
 
-int __ast_custom_function_register(struct ast_custom_function *acf, struct ast_module *mod)
+static int extension_presence_state_helper(struct ast_exten *e, char **subtype, char **message)
 {
-	struct ast_custom_function *cur;
+	struct ast_str *hint_app = ast_str_thread_get(&extensionstate_buf, 32);
+	char *presence_provider;
+	const char *app;
 
-	if (!acf) {
+	if (!e || !hint_app) {
 		return -1;
 	}
 
-	acf->mod = mod;
-#ifdef AST_XML_DOCS
-	acf->docsrc = AST_STATIC_DOC;
-#endif
-
-	if (acf_retrieve_docs(acf)) {
+	app = ast_get_extension_app(e);
+	if (ast_strlen_zero(app)) {
 		return -1;
 	}
 
-	AST_RWLIST_WRLOCK(&acf_root);
-
-	cur = ast_custom_function_find_nolock(acf->name);
-	if (cur) {
-		ast_log(LOG_ERROR, "Function %s already registered.\n", acf->name);
-		AST_RWLIST_UNLOCK(&acf_root);
-		return -1;
-	}
+	ast_str_set(&hint_app, 0, "%s", app);
+	presence_provider = parse_hint_presence(hint_app);
 
-	/* Store in alphabetical order */
-	AST_RWLIST_TRAVERSE_SAFE_BEGIN(&acf_root, cur, acflist) {
-		if (strcmp(acf->name, cur->name) < 0) {
-			AST_RWLIST_INSERT_BEFORE_CURRENT(acf, acflist);
-			break;
-		}
-	}
-	AST_RWLIST_TRAVERSE_SAFE_END;
-	if (!cur) {
-		AST_RWLIST_INSERT_TAIL(&acf_root, acf, acflist);
+	if (ast_strlen_zero(presence_provider)) {
+		/* No presence string in the hint */
+		return 0;
 	}
 
-	AST_RWLIST_UNLOCK(&acf_root);
-
-	ast_verb(2, "Registered custom function '" COLORIZE_FMT "'\n", COLORIZE(COLOR_BRCYAN, 0, acf->name));
-
-	return 0;
+	return ast_presence_state(presence_provider, subtype, message);
 }
 
-int __ast_custom_function_register_escalating(struct ast_custom_function *acf, enum ast_custom_function_escalation escalation, struct ast_module *mod)
+int ast_hint_presence_state(struct ast_channel *c, const char *context, const char *exten, char **subtype, char **message)
 {
-	int res;
+	struct ast_exten *e;
 
-	res = __ast_custom_function_register(acf, mod);
-	if (res != 0) {
-		return -1;
+	if (!(e = ast_hint_extension(c, context, exten))) {  /* Do we have a hint for this extension ? */
+		return -1;                   /* No hint, return -1 */
 	}
 
-	switch (escalation) {
-	case AST_CFE_NONE:
-		break;
-	case AST_CFE_READ:
-		acf->read_escalates = 1;
-		break;
-	case AST_CFE_WRITE:
-		acf->write_escalates = 1;
-		break;
-	case AST_CFE_BOTH:
-		acf->read_escalates = 1;
-		acf->write_escalates = 1;
-		break;
+	if (e->exten[0] == '_') {
+		/* Create this hint on-the-fly */
+		ast_add_extension(e->parent->name, 0, exten, e->priority, e->label,
+			e->matchcid ? e->cidmatch : NULL, e->app, ast_strdup(e->data), ast_free_ptr,
+			e->registrar);
+		if (!(e = ast_hint_extension(c, context, exten))) {
+			/* Improbable, but not impossible */
+			return -1;
+		}
 	}
 
-	return 0;
+	return extension_presence_state_helper(e, subtype, message);
 }
 
-/*! \brief return a pointer to the arguments of the function,
- * and terminates the function name with '\\0'
- */
-static char *func_args(char *function)
+static int execute_state_callback(ast_state_cb_type cb,
+	const char *context,
+	const char *exten,
+	void *data,
+	enum ast_state_cb_update_reason reason,
+	struct ast_hint *hint,
+	struct ao2_container *device_state_info)
 {
-	char *args = strchr(function, '(');
+	int res = 0;
+	struct ast_state_cb_info info = { 0, };
 
-	if (!args) {
-		ast_log(LOG_WARNING, "Function '%s' doesn't contain parentheses.  Assuming null argument.\n", function);
-	} else {
-		char *p;
-		*args++ = '\0';
-		if ((p = strrchr(args, ')'))) {
-			*p = '\0';
-		} else {
-			ast_log(LOG_WARNING, "Can't find trailing parenthesis for function '%s(%s'?\n", function, args);
-		}
-	}
-	return args;
-}
-
-void pbx_live_dangerously(int new_live_dangerously)
-{
-	if (new_live_dangerously && !live_dangerously) {
-		ast_log(LOG_WARNING, "Privilege escalation protection disabled!\n"
-			"See https://wiki.asterisk.org/wiki/x/1gKfAQ for more details.\n");
-	}
+	info.reason = reason;
 
-	if (!new_live_dangerously && live_dangerously) {
-		ast_log(LOG_NOTICE, "Privilege escalation protection enabled.\n");
+	/* Copy over current hint data */
+	if (hint) {
+		ao2_lock(hint);
+		info.exten_state = hint->laststate;
+		info.device_state_info = device_state_info;
+		info.presence_state = hint->last_presence_state;
+		if (!(ast_strlen_zero(hint->last_presence_subtype))) {
+			info.presence_subtype = ast_strdupa(hint->last_presence_subtype);
+		} else {
+			info.presence_subtype = "";
+		}
+		if (!(ast_strlen_zero(hint->last_presence_message))) {
+			info.presence_message = ast_strdupa(hint->last_presence_message);
+		} else {
+			info.presence_message = "";
+		}
+		ao2_unlock(hint);
+	} else {
+		info.exten_state = AST_EXTENSION_REMOVED;
 	}
-	live_dangerously = new_live_dangerously;
-}
 
-int ast_thread_inhibit_escalations(void)
-{
-	int *thread_inhibit_escalations;
-
-	thread_inhibit_escalations = ast_threadstorage_get(
-		&thread_inhibit_escalations_tl, sizeof(*thread_inhibit_escalations));
-
-	if (thread_inhibit_escalations == NULL) {
-		ast_log(LOG_ERROR, "Error inhibiting privilege escalations for current thread\n");
-		return -1;
-	}
+	/* NOTE: The casts will not be needed for v10 and later */
+	res = cb((char *) context, (char *) exten, &info, data);
 
-	*thread_inhibit_escalations = 1;
-	return 0;
+	return res;
 }
 
 /*!
- * \brief Indicates whether the current thread inhibits the execution of
- * dangerous functions.
+ * /internal
+ * /brief Identify a channel for every device which is supposedly responsible for the device state.
  *
- * \return True (non-zero) if dangerous function execution is inhibited.
- * \return False (zero) if dangerous function execution is allowed.
+ * Especially when the device is ringing, the oldest ringing channel is chosen.
+ * For all other cases the first encountered channel in the specific state is chosen.
  */
-static int thread_inhibits_escalations(void)
+static void get_device_state_causing_channels(struct ao2_container *c)
 {
-	int *thread_inhibit_escalations;
-
-	thread_inhibit_escalations = ast_threadstorage_get(
-		&thread_inhibit_escalations_tl, sizeof(*thread_inhibit_escalations));
+	struct ao2_iterator iter;
+	struct ast_device_state_info *info;
+	struct ast_channel *chan;
 
-	if (thread_inhibit_escalations == NULL) {
-		ast_log(LOG_ERROR, "Error checking thread's ability to run dangerous functions\n");
-		/* On error, assume that we are inhibiting */
-		return 1;
+	if (!c || !ao2_container_count(c)) {
+		return;
 	}
+	iter = ao2_iterator_init(c, 0);
+	for (; (info = ao2_iterator_next(&iter)); ao2_ref(info, -1)) {
+		enum ast_channel_state search_state = 0; /* prevent false uninit warning */
+		char match[AST_CHANNEL_NAME];
+		struct ast_channel_iterator *chan_iter;
+		struct timeval chantime = {0, }; /* prevent false uninit warning */
+
+		switch (info->device_state) {
+		case AST_DEVICE_RINGING:
+		case AST_DEVICE_RINGINUSE:
+			/* find ringing channel */
+			search_state = AST_STATE_RINGING;
+			break;
+		case AST_DEVICE_BUSY:
+			/* find busy channel */
+			search_state = AST_STATE_BUSY;
+			break;
+		case AST_DEVICE_ONHOLD:
+		case AST_DEVICE_INUSE:
+			/* find up channel */
+			search_state = AST_STATE_UP;
+			break;
+		case AST_DEVICE_UNKNOWN:
+		case AST_DEVICE_NOT_INUSE:
+		case AST_DEVICE_INVALID:
+		case AST_DEVICE_UNAVAILABLE:
+		case AST_DEVICE_TOTAL /* not a state */:
+			/* no channels are of interest */
+			continue;
+		}
 
-	return *thread_inhibit_escalations;
+		/* iterate over all channels of the device */
+	        snprintf(match, sizeof(match), "%s-", info->device_name);
+		chan_iter = ast_channel_iterator_by_name_new(match, strlen(match));
+		for (; (chan = ast_channel_iterator_next(chan_iter)); ast_channel_unref(chan)) {
+			ast_channel_lock(chan);
+			/* this channel's state doesn't match */
+			if (search_state != ast_channel_state(chan)) {
+				ast_channel_unlock(chan);
+				continue;
+			}
+			/* any non-ringing channel will fit */
+			if (search_state != AST_STATE_RINGING) {
+				ast_channel_unlock(chan);
+				info->causing_channel = chan; /* is kept ref'd! */
+				break;
+			}
+			/* but we need the oldest ringing channel of the device to match with undirected pickup */
+			if (!info->causing_channel) {
+				chantime = ast_channel_creationtime(chan);
+				ast_channel_ref(chan); /* must ref it! */
+				info->causing_channel = chan;
+			} else if (ast_tvcmp(ast_channel_creationtime(chan), chantime) < 0) {
+				chantime = ast_channel_creationtime(chan);
+				ast_channel_unref(info->causing_channel);
+				ast_channel_ref(chan); /* must ref it! */
+				info->causing_channel = chan;
+			}
+			ast_channel_unlock(chan);
+		}
+		ast_channel_iterator_destroy(chan_iter);
+	}
+	ao2_iterator_destroy(&iter);
 }
 
-/*!
- * \brief Determines whether execution of a custom function's read function
- * is allowed.
- *
- * \param acfptr Custom function to check
- * \return True (non-zero) if reading is allowed.
- * \return False (zero) if reading is not allowed.
- */
-static int is_read_allowed(struct ast_custom_function *acfptr)
+static void device_state_notify_callbacks(struct ast_hint *hint, struct ast_str **hint_app)
 {
-	if (!acfptr) {
-		return 1;
-	}
+	struct ao2_iterator cb_iter;
+	struct ast_state_cb *state_cb;
+	int state, same_state;
+	struct ao2_container *device_state_info;
+	int first_extended_cb_call = 1;
+	char context_name[AST_MAX_CONTEXT];
+	char exten_name[AST_MAX_EXTENSION];
 
-	if (!read_escalates(acfptr)) {
-		return 1;
+	ao2_lock(hint);
+	if (!hint->exten) {
+		/* The extension has already been destroyed */
+		ao2_unlock(hint);
+		return;
 	}
 
-	if (!thread_inhibits_escalations()) {
-		return 1;
-	}
+	/*
+	 * Save off strings in case the hint extension gets destroyed
+	 * while we are notifying the watchers.
+	 */
+	ast_copy_string(context_name,
+			ast_get_context_name(ast_get_extension_context(hint->exten)),
+			sizeof(context_name));
+	ast_copy_string(exten_name, ast_get_extension_name(hint->exten),
+			sizeof(exten_name));
+	ast_str_set(hint_app, 0, "%s", ast_get_extension_app(hint->exten));
+	ao2_unlock(hint);
 
-	if (live_dangerously) {
-		/* Global setting overrides the thread's preference */
-		ast_debug(2, "Reading %s from a dangerous context\n",
-			acfptr->name);
-		return 1;
-	}
+	/*
+	 * Get device state for this hint.
+	 *
+	 * NOTE: We cannot hold any locks while determining the hint
+	 * device state or notifying the watchers without causing a
+	 * deadlock.  (conlock, hints, and hint)
+	 */
 
-	/* We have no reason to allow this function to execute */
-	return 0;
-}
+	/* Make a container so state3 can fill it if we wish.
+	 * If that failed we simply do not provide the extended state info.
+	 */
+	device_state_info = alloc_device_state_info();
 
-/*!
- * \brief Determines whether execution of a custom function's write function
- * is allowed.
- *
- * \param acfptr Custom function to check
- * \return True (non-zero) if writing is allowed.
- * \return False (zero) if writing is not allowed.
- */
-static int is_write_allowed(struct ast_custom_function *acfptr)
-{
-	if (!acfptr) {
-		return 1;
+	state = ast_extension_state3(*hint_app, device_state_info);
+	if ((same_state = state == hint->laststate) && (~state & AST_EXTENSION_RINGING)) {
+		ao2_cleanup(device_state_info);
+		return;
 	}
 
-	if (!write_escalates(acfptr)) {
-		return 1;
-	}
+	/* Device state changed since last check - notify the watchers. */
+	hint->laststate = state;	/* record we saw the change */
 
-	if (!thread_inhibits_escalations()) {
-		return 1;
+	/* For general callbacks */
+	cb_iter = ao2_iterator_init(statecbs, 0);
+	for (; !same_state && (state_cb = ao2_iterator_next(&cb_iter)); ao2_ref(state_cb, -1)) {
+		execute_state_callback(state_cb->change_cb,
+				       context_name,
+				       exten_name,
+				       state_cb->data,
+				       AST_HINT_UPDATE_DEVICE,
+				       hint,
+				       NULL);
 	}
+	ao2_iterator_destroy(&cb_iter);
 
-	if (live_dangerously) {
-		/* Global setting overrides the thread's preference */
-		ast_debug(2, "Writing %s from a dangerous context\n",
-			acfptr->name);
-		return 1;
+	/* For extension callbacks */
+	/* extended callbacks are called when the state changed or when AST_STATE_RINGING is
+	 * included. Normal callbacks are only called when the state changed.
+	 */
+	cb_iter = ao2_iterator_init(hint->callbacks, 0);
+	for (; (state_cb = ao2_iterator_next(&cb_iter)); ao2_ref(state_cb, -1)) {
+		if (state_cb->extended && first_extended_cb_call) {
+			/* Fill detailed device_state_info now that we know it is used by extd. callback */
+			first_extended_cb_call = 0;
+			get_device_state_causing_channels(device_state_info);
+		}
+		if (state_cb->extended || !same_state) {
+			execute_state_callback(state_cb->change_cb,
+					       context_name,
+					       exten_name,
+					       state_cb->data,
+					       AST_HINT_UPDATE_DEVICE,
+					       hint,
+					       state_cb->extended ? device_state_info : NULL);
+		}
 	}
+	ao2_iterator_destroy(&cb_iter);
 
-	/* We have no reason to allow this function to execute */
-	return 0;
+	ao2_cleanup(device_state_info);
 }
 
-int ast_func_read(struct ast_channel *chan, const char *function, char *workspace, size_t len)
+static void presence_state_notify_callbacks(
+	struct stasis_message *msg, struct ast_hint *hint, struct ast_str **hint_app,
+	struct ast_presence_state_message *presence_state)
 {
-	char *copy = ast_strdupa(function);
-	char *args = func_args(copy);
-	struct ast_custom_function *acfptr = ast_custom_function_find(copy);
-	int res;
-	struct ast_module_user *u = NULL;
-
-	if (acfptr == NULL) {
-		ast_log(LOG_ERROR, "Function %s not registered\n", copy);
-	} else if (!acfptr->read && !acfptr->read2) {
-		ast_log(LOG_ERROR, "Function %s cannot be read\n", copy);
-	} else if (!is_read_allowed(acfptr)) {
-		ast_log(LOG_ERROR, "Dangerous function %s read blocked\n", copy);
-	} else if (acfptr->read) {
-		if (acfptr->mod) {
-			u = __ast_module_user_add(acfptr->mod, chan);
-		}
-		res = acfptr->read(chan, copy, args, workspace, len);
-		if (acfptr->mod && u) {
-			__ast_module_user_remove(acfptr->mod, u);
-		}
-		return res;
-	} else {
-		struct ast_str *str = ast_str_create(16);
-		if (acfptr->mod) {
-			u = __ast_module_user_add(acfptr->mod, chan);
-		}
-		res = acfptr->read2(chan, copy, args, &str, 0);
-		if (acfptr->mod && u) {
-			__ast_module_user_remove(acfptr->mod, u);
-		}
-		ast_copy_string(workspace, ast_str_buffer(str), len > ast_str_size(str) ? ast_str_size(str) : len);
-		ast_free(str);
-		return res;
+	struct ao2_iterator cb_iter;
+	struct ast_state_cb *state_cb;
+	char context_name[AST_MAX_CONTEXT];
+	char exten_name[AST_MAX_EXTENSION];
+
+	ao2_lock(hint);
+
+	if (!hint->exten) {
+		/* The extension has already been destroyed */
+		ao2_unlock(hint);
+		return;
 	}
-	return -1;
-}
 
-int ast_func_read2(struct ast_channel *chan, const char *function, struct ast_str **str, ssize_t maxlen)
-{
-	char *copy = ast_strdupa(function);
-	char *args = func_args(copy);
-	struct ast_custom_function *acfptr = ast_custom_function_find(copy);
-	int res;
-	struct ast_module_user *u = NULL;
-
-	if (acfptr == NULL) {
-		ast_log(LOG_ERROR, "Function %s not registered\n", copy);
-	} else if (!acfptr->read && !acfptr->read2) {
-		ast_log(LOG_ERROR, "Function %s cannot be read\n", copy);
-	} else if (!is_read_allowed(acfptr)) {
-		ast_log(LOG_ERROR, "Dangerous function %s read blocked\n", copy);
-	} else {
-		if (acfptr->mod) {
-			u = __ast_module_user_add(acfptr->mod, chan);
+	if (hint_change_message_type() != stasis_message_type(msg)) {
+		const char *app;
+		char *parse;
+
+		/* Does this hint monitor the device that changed state? */
+		app = ast_get_extension_app(hint->exten);
+		if (ast_strlen_zero(app)) {
+			/* The hint does not monitor presence at all. */
+			ao2_unlock(hint);
+			return;
 		}
-		ast_str_reset(*str);
-		if (acfptr->read2) {
-			/* ast_str enabled */
-			res = acfptr->read2(chan, copy, args, str, maxlen);
-		} else {
-			/* Legacy function pointer, allocate buffer for result */
-			int maxsize = ast_str_size(*str);
-			if (maxlen > -1) {
-				if (maxlen == 0) {
-					if (acfptr->read_max) {
-						maxsize = acfptr->read_max;
-					} else {
-						maxsize = VAR_BUF_SIZE;
-					}
-				} else {
-					maxsize = maxlen;
-				}
-				ast_str_make_space(str, maxsize);
-			}
-			res = acfptr->read(chan, copy, args, ast_str_buffer(*str), maxsize);
+
+		ast_str_set(hint_app, 0, "%s", app);
+		parse = parse_hint_presence(*hint_app);
+		if (ast_strlen_zero(parse)) {
+			ao2_unlock(hint);
+			return;
 		}
-		if (acfptr->mod && u) {
-			__ast_module_user_remove(acfptr->mod, u);
+		if (strcasecmp(parse, presence_state->provider)) {
+			/* The hint does not monitor the presence provider. */
+			ao2_unlock(hint);
+			return;
 		}
-		return res;
 	}
-	return -1;
-}
-
-int ast_func_write(struct ast_channel *chan, const char *function, const char *value)
-{
-	char *copy = ast_strdupa(function);
-	char *args = func_args(copy);
-	struct ast_custom_function *acfptr = ast_custom_function_find(copy);
 
-	if (acfptr == NULL) {
-		ast_log(LOG_ERROR, "Function %s not registered\n", copy);
-	} else if (!acfptr->write) {
-		ast_log(LOG_ERROR, "Function %s cannot be written to\n", copy);
-	} else if (!is_write_allowed(acfptr)) {
-		ast_log(LOG_ERROR, "Dangerous function %s write blocked\n", copy);
-	} else {
-		int res;
-		struct ast_module_user *u = NULL;
-		if (acfptr->mod)
-			u = __ast_module_user_add(acfptr->mod, chan);
-		res = acfptr->write(chan, copy, args, value);
-		if (acfptr->mod && u)
-			__ast_module_user_remove(acfptr->mod, u);
-		return res;
+	/*
+	 * Save off strings in case the hint extension gets destroyed
+	 * while we are notifying the watchers.
+	 */
+	ast_copy_string(context_name,
+			ast_get_context_name(ast_get_extension_context(hint->exten)),
+			sizeof(context_name));
+	ast_copy_string(exten_name, ast_get_extension_name(hint->exten),
+			sizeof(exten_name));
+	ast_str_set(hint_app, 0, "%s", ast_get_extension_app(hint->exten));
+
+	/* Check to see if update is necessary */
+	if ((hint->last_presence_state == presence_state->state) &&
+	    ((hint->last_presence_subtype && presence_state->subtype &&
+	      !strcmp(hint->last_presence_subtype, presence_state->subtype)) ||
+	     (!hint->last_presence_subtype && !presence_state->subtype)) &&
+	    ((hint->last_presence_message && presence_state->message &&
+	      !strcmp(hint->last_presence_message, presence_state->message)) ||
+	     (!hint->last_presence_message && !presence_state->message))) {
+		/* this update is the same as the last, do nothing */
+		ao2_unlock(hint);
+		return;
 	}
 
-	return -1;
-}
-
-void ast_str_substitute_variables_full(struct ast_str **buf, ssize_t maxlen, struct ast_channel *c, struct varshead *headp, const char *templ, size_t *used)
-{
-	/* Substitutes variables into buf, based on string templ */
-	char *cp4 = NULL;
-	const char *whereweare;
-	int orig_size = 0;
-	int offset, offset2, isfunction;
-	const char *nextvar, *nextexp, *nextthing;
-	const char *vars, *vare;
-	char *finalvars;
-	int pos, brackets, needsub, len;
-	struct ast_str *substr1 = ast_str_create(16), *substr2 = NULL, *substr3 = ast_str_create(16);
-
-	ast_str_reset(*buf);
-	whereweare = templ;
-	while (!ast_strlen_zero(whereweare)) {
-		/* reset our buffer */
-		ast_str_reset(substr3);
-
-		/* Assume we're copying the whole remaining string */
-		pos = strlen(whereweare);
-		nextvar = NULL;
-		nextexp = NULL;
-		nextthing = strchr(whereweare, '$');
-		if (nextthing) {
-			switch (nextthing[1]) {
-			case '{':
-				nextvar = nextthing;
-				pos = nextvar - whereweare;
-				break;
-			case '[':
-				nextexp = nextthing;
-				pos = nextexp - whereweare;
-				break;
-			default:
-				pos = 1;
-			}
-		}
-
-		if (pos) {
-			/* Copy that many bytes */
-			ast_str_append_substr(buf, maxlen, whereweare, pos);
-
-			templ += pos;
-			whereweare += pos;
-		}
-
-		if (nextvar) {
-			/* We have a variable.  Find the start and end, and determine
-			   if we are going to have to recursively call ourselves on the
-			   contents */
-			vars = vare = nextvar + 2;
-			brackets = 1;
-			needsub = 0;
-
-			/* Find the end of it */
-			while (brackets && *vare) {
-				if ((vare[0] == '$') && (vare[1] == '{')) {
-					needsub++;
-				} else if (vare[0] == '{') {
-					brackets++;
-				} else if (vare[0] == '}') {
-					brackets--;
-				} else if ((vare[0] == '$') && (vare[1] == '['))
-					needsub++;
-				vare++;
-			}
-			if (brackets)
-				ast_log(LOG_WARNING, "Error in extension logic (missing '}')\n");
-			len = vare - vars - 1;
+	/* update new values */
+	ast_free(hint->last_presence_subtype);
+	ast_free(hint->last_presence_message);
+	hint->last_presence_state = presence_state->state;
+	hint->last_presence_subtype = presence_state->subtype ? ast_strdup(presence_state->subtype) : NULL;
+	hint->last_presence_message = presence_state->message ? ast_strdup(presence_state->message) : NULL;
 
-			/* Skip totally over variable string */
-			whereweare += (len + 3);
+	/*
+	 * NOTE: We cannot hold any locks while notifying
+	 * the watchers without causing a deadlock.
+	 * (conlock, hints, and hint)
+	 */
+	ao2_unlock(hint);
 
-			/* Store variable name (and truncate) */
-			ast_str_set_substr(&substr1, 0, vars, len);
-			ast_debug(5, "Evaluating '%s' (from '%s' len %d)\n", ast_str_buffer(substr1), vars, len);
+	/* For general callbacks */
+	cb_iter = ao2_iterator_init(statecbs, 0);
+	for (; (state_cb = ao2_iterator_next(&cb_iter)); ao2_ref(state_cb, -1)) {
+		execute_state_callback(state_cb->change_cb,
+				       context_name,
+				       exten_name,
+				       state_cb->data,
+				       AST_HINT_UPDATE_PRESENCE,
+				       hint,
+				       NULL);
+	}
+	ao2_iterator_destroy(&cb_iter);
 
-			/* Substitute if necessary */
-			if (needsub) {
-				size_t my_used;
+	/* For extension callbacks */
+	cb_iter = ao2_iterator_init(hint->callbacks, 0);
+	for (; (state_cb = ao2_iterator_next(&cb_iter)); ao2_cleanup(state_cb)) {
+		execute_state_callback(state_cb->change_cb,
+				       context_name,
+				       exten_name,
+				       state_cb->data,
+				       AST_HINT_UPDATE_PRESENCE,
+				       hint,
+				       NULL);
+	}
+	ao2_iterator_destroy(&cb_iter);
+}
 
-				if (!substr2) {
-					substr2 = ast_str_create(16);
-				}
-				ast_str_substitute_variables_full(&substr2, 0, c, headp, ast_str_buffer(substr1), &my_used);
-				finalvars = ast_str_buffer(substr2);
-			} else {
-				finalvars = ast_str_buffer(substr1);
-			}
+static int handle_hint_change_message_type(struct stasis_message *msg, enum ast_state_cb_update_reason reason)
+{
+	struct ast_hint *hint;
+	struct ast_str *hint_app;
 
-			parse_variable_name(finalvars, &offset, &offset2, &isfunction);
-			if (isfunction) {
-				/* Evaluate function */
-				if (c || !headp) {
-					cp4 = ast_func_read2(c, finalvars, &substr3, 0) ? NULL : ast_str_buffer(substr3);
-				} else {
-					struct varshead old;
-					struct ast_channel *bogus = ast_dummy_channel_alloc();
-					if (bogus) {
-						memcpy(&old, ast_channel_varshead(bogus), sizeof(old));
-						memcpy(ast_channel_varshead(bogus), headp, sizeof(*ast_channel_varshead(bogus)));
-						cp4 = ast_func_read2(c, finalvars, &substr3, 0) ? NULL : ast_str_buffer(substr3);
-						/* Don't deallocate the varshead that was passed in */
-						memcpy(ast_channel_varshead(bogus), &old, sizeof(*ast_channel_varshead(bogus)));
-						ast_channel_unref(bogus);
-					} else {
-						ast_log(LOG_ERROR, "Unable to allocate bogus channel for variable substitution.  Function results may be blank.\n");
-					}
-				}
-				ast_debug(2, "Function %s result is '%s'\n", finalvars, cp4 ? cp4 : "(null)");
-			} else {
-				/* Retrieve variable value */
-				ast_str_retrieve_variable(&substr3, 0, c, headp, finalvars);
-				cp4 = ast_str_buffer(substr3);
-			}
-			if (cp4) {
-				ast_str_substring(substr3, offset, offset2);
-				ast_str_append(buf, maxlen, "%s", ast_str_buffer(substr3));
-			}
-		} else if (nextexp) {
-			/* We have an expression.  Find the start and end, and determine
-			   if we are going to have to recursively call ourselves on the
-			   contents */
-			vars = vare = nextexp + 2;
-			brackets = 1;
-			needsub = 0;
-
-			/* Find the end of it */
-			while (brackets && *vare) {
-				if ((vare[0] == '$') && (vare[1] == '[')) {
-					needsub++;
-					brackets++;
-					vare++;
-				} else if (vare[0] == '[') {
-					brackets++;
-				} else if (vare[0] == ']') {
-					brackets--;
-				} else if ((vare[0] == '$') && (vare[1] == '{')) {
-					needsub++;
-					vare++;
-				}
-				vare++;
-			}
-			if (brackets)
-				ast_log(LOG_WARNING, "Error in extension logic (missing ']')\n");
-			len = vare - vars - 1;
+	if (hint_change_message_type() != stasis_message_type(msg)) {
+		return 0;
+	}
 
-			/* Skip totally over expression */
-			whereweare += (len + 3);
+	if (!(hint_app = ast_str_create(1024))) {
+		return -1;
+	}
 
-			/* Store variable name (and truncate) */
-			ast_str_set_substr(&substr1, 0, vars, len);
+	hint = stasis_message_data(msg);
 
-			/* Substitute if necessary */
-			if (needsub) {
-				size_t my_used;
+	if (reason == AST_HINT_UPDATE_DEVICE) {
+		device_state_notify_callbacks(hint, &hint_app);
+	} else if (reason == AST_HINT_UPDATE_PRESENCE) {
+		char *presence_subtype = NULL;
+		char *presence_message = NULL;
+		int state;
 
-				if (!substr2) {
-					substr2 = ast_str_create(16);
-				}
-				ast_str_substitute_variables_full(&substr2, 0, c, headp, ast_str_buffer(substr1), &my_used);
-				finalvars = ast_str_buffer(substr2);
-			} else {
-				finalvars = ast_str_buffer(substr1);
-			}
+		state = extension_presence_state_helper(
+			hint->exten, &presence_subtype, &presence_message);
 
-			if (ast_str_expr(&substr3, 0, c, finalvars)) {
-				ast_debug(2, "Expression result is '%s'\n", ast_str_buffer(substr3));
-			}
-			ast_str_append(buf, maxlen, "%s", ast_str_buffer(substr3));
+		{
+			struct ast_presence_state_message presence_state = {
+				.state = state > 0 ? state : AST_PRESENCE_INVALID,
+				.subtype = presence_subtype,
+				.message = presence_message
+			};
+			presence_state_notify_callbacks(msg, hint, &hint_app, &presence_state);
 		}
+
+		ast_free(presence_subtype);
+		ast_free(presence_message);
 	}
-	*used = ast_str_strlen(*buf) - orig_size;
-	ast_free(substr1);
-	ast_free(substr2);
-	ast_free(substr3);
-}
 
-void ast_str_substitute_variables(struct ast_str **buf, ssize_t maxlen, struct ast_channel *chan, const char *templ)
-{
-	size_t used;
-	ast_str_substitute_variables_full(buf, maxlen, chan, NULL, templ, &used);
+	ast_free(hint_app);
+	return 1;
 }
 
-void ast_str_substitute_variables_varshead(struct ast_str **buf, ssize_t maxlen, struct varshead *headp, const char *templ)
+static void device_state_cb(void *unused, struct stasis_subscription *sub, struct stasis_message *msg)
 {
-	size_t used;
-	ast_str_substitute_variables_full(buf, maxlen, NULL, headp, templ, &used);
+	struct ast_device_state_message *dev_state;
+	struct ast_str *hint_app;
+	struct ast_hintdevice *device;
+	struct ast_hintdevice *cmpdevice;
+	struct ao2_iterator *dev_iter;
+
+	if (handle_hint_change_message_type(msg, AST_HINT_UPDATE_DEVICE)) {
+		return;
+	}
+
+	if (ast_device_state_message_type() != stasis_message_type(msg)) {
+		return;
+	}
+
+	dev_state = stasis_message_data(msg);
+	if (dev_state->eid) {
+		/* ignore non-aggregate states */
+		return;
+	}
+
+	if (ao2_container_count(hintdevices) == 0) {
+		/* There are no hints monitoring devices. */
+		return;
+	}
+
+	hint_app = ast_str_create(1024);
+	if (!hint_app) {
+		return;
+	}
+
+	cmpdevice = ast_alloca(sizeof(*cmpdevice) + strlen(dev_state->device));
+	strcpy(cmpdevice->hintdevice, dev_state->device);
+
+	ast_mutex_lock(&context_merge_lock);/* Hold off ast_merge_contexts_and_delete */
+	dev_iter = ao2_t_callback(hintdevices,
+		OBJ_SEARCH_OBJECT | OBJ_MULTIPLE,
+		hintdevice_cmp_multiple,
+		cmpdevice,
+		"find devices in container");
+	if (!dev_iter) {
+		ast_mutex_unlock(&context_merge_lock);
+		ast_free(hint_app);
+		return;
+	}
+
+	for (; (device = ao2_iterator_next(dev_iter)); ao2_t_ref(device, -1, "Next device")) {
+		if (device->hint) {
+			device_state_notify_callbacks(device->hint, &hint_app);
+		}
+	}
+	ast_mutex_unlock(&context_merge_lock);
+
+	ao2_iterator_destroy(dev_iter);
+	ast_free(hint_app);
+	return;
 }
 
-void pbx_substitute_variables_helper_full(struct ast_channel *c, struct varshead *headp, const char *cp1, char *cp2, int count, size_t *used)
+/*!
+ * \internal
+ * \brief Destroy the given state callback object.
+ *
+ * \param doomed State callback to destroy.
+ *
+ * \return Nothing
+ */
+static void destroy_state_cb(void *doomed)
 {
-	/* Substitutes variables into cp2, based on string cp1, cp2 NO LONGER NEEDS TO BE ZEROED OUT!!!!  */
-	char *cp4 = NULL;
-	const char *whereweare, *orig_cp2 = cp2;
-	int length, offset, offset2, isfunction;
-	char *workspace = NULL;
-	char *ltmp = NULL, *var = NULL;
-	char *nextvar, *nextexp, *nextthing;
-	char *vars, *vare;
-	int pos, brackets, needsub, len;
+	struct ast_state_cb *state_cb = doomed;
 
-	*cp2 = 0; /* just in case nothing ends up there */
-	whereweare = cp1;
-	while (!ast_strlen_zero(whereweare) && count) {
-		/* Assume we're copying the whole remaining string */
-		pos = strlen(whereweare);
-		nextvar = NULL;
-		nextexp = NULL;
-		nextthing = strchr(whereweare, '$');
-		if (nextthing) {
-			switch (nextthing[1]) {
-			case '{':
-				nextvar = nextthing;
-				pos = nextvar - whereweare;
-				break;
-			case '[':
-				nextexp = nextthing;
-				pos = nextexp - whereweare;
-				break;
-			default:
-				pos = 1;
-			}
+	if (state_cb->destroy_cb) {
+		state_cb->destroy_cb(state_cb->id, state_cb->data);
+	}
+}
+
+/*!
+ * \internal
+ * \brief Add watcher for extension states with destructor
+ */
+static int extension_state_add_destroy(const char *context, const char *exten,
+	ast_state_cb_type change_cb, ast_state_cb_destroy_type destroy_cb, void *data, int extended)
+{
+	struct ast_hint *hint;
+	struct ast_state_cb *state_cb;
+	struct ast_exten *e;
+	int id;
+
+	/* If there's no context and extension:  add callback to statecbs list */
+	if (!context && !exten) {
+		/* Prevent multiple adds from adding the same change_cb at the same time. */
+		ao2_lock(statecbs);
+
+		/* Remove any existing change_cb. */
+		ao2_find(statecbs, change_cb, OBJ_UNLINK | OBJ_NODATA);
+
+		/* Now insert the change_cb */
+		if (!(state_cb = ao2_alloc(sizeof(*state_cb), destroy_state_cb))) {
+			ao2_unlock(statecbs);
+			return -1;
 		}
+		state_cb->id = 0;
+		state_cb->change_cb = change_cb;
+		state_cb->destroy_cb = destroy_cb;
+		state_cb->data = data;
+		state_cb->extended = extended;
+		ao2_link(statecbs, state_cb);
 
-		if (pos) {
-			/* Can't copy more than 'count' bytes */
-			if (pos > count)
-				pos = count;
-
-			/* Copy that many bytes */
-			memcpy(cp2, whereweare, pos);
-
-			count -= pos;
-			cp2 += pos;
-			whereweare += pos;
-			*cp2 = 0;
-		}
-
-		if (nextvar) {
-			/* We have a variable.  Find the start and end, and determine
-			   if we are going to have to recursively call ourselves on the
-			   contents */
-			vars = vare = nextvar + 2;
-			brackets = 1;
-			needsub = 0;
-
-			/* Find the end of it */
-			while (brackets && *vare) {
-				if ((vare[0] == '$') && (vare[1] == '{')) {
-					needsub++;
-				} else if (vare[0] == '{') {
-					brackets++;
-				} else if (vare[0] == '}') {
-					brackets--;
-				} else if ((vare[0] == '$') && (vare[1] == '['))
-					needsub++;
-				vare++;
-			}
-			if (brackets)
-				ast_log(LOG_WARNING, "Error in extension logic (missing '}')\n");
-			len = vare - vars - 1;
+		ao2_ref(state_cb, -1);
+		ao2_unlock(statecbs);
+		return 0;
+	}
+
+	if (!context || !exten)
+		return -1;
+
+	/* This callback type is for only one hint, so get the hint */
+	e = ast_hint_extension(NULL, context, exten);
+	if (!e) {
+		return -1;
+	}
+
+	/* If this is a pattern, dynamically create a new extension for this
+	 * particular match.  Note that this will only happen once for each
+	 * individual extension, because the pattern will no longer match first.
+	 */
+	if (e->exten[0] == '_') {
+		ast_add_extension(e->parent->name, 0, exten, e->priority, e->label,
+			e->matchcid ? e->cidmatch : NULL, e->app, ast_strdup(e->data), ast_free_ptr,
+			e->registrar);
+		e = ast_hint_extension(NULL, context, exten);
+		if (!e || e->exten[0] == '_') {
+			return -1;
+		}
+	}
 
-			/* Skip totally over variable string */
-			whereweare += (len + 3);
+	/* Find the hint in the hints container */
+	ao2_lock(hints);/* Locked to hold off ast_merge_contexts_and_delete */
+	hint = ao2_find(hints, e, 0);
+	if (!hint) {
+		ao2_unlock(hints);
+		return -1;
+	}
 
-			if (!var)
-				var = ast_alloca(VAR_BUF_SIZE);
+	/* Now insert the callback in the callback list  */
+	if (!(state_cb = ao2_alloc(sizeof(*state_cb), destroy_state_cb))) {
+		ao2_ref(hint, -1);
+		ao2_unlock(hints);
+		return -1;
+	}
+	do {
+		id = stateid++;		/* Unique ID for this callback */
+		/* Do not allow id to ever be -1 or 0. */
+	} while (id == -1 || id == 0);
+	state_cb->id = id;
+	state_cb->change_cb = change_cb;	/* Pointer to callback routine */
+	state_cb->destroy_cb = destroy_cb;
+	state_cb->data = data;		/* Data for the callback */
+	state_cb->extended = extended;
+	ao2_link(hint->callbacks, state_cb);
 
-			/* Store variable name (and truncate) */
-			ast_copy_string(var, vars, len + 1);
+	ao2_ref(state_cb, -1);
+	ao2_ref(hint, -1);
+	ao2_unlock(hints);
 
-			/* Substitute if necessary */
-			if (needsub) {
-				size_t my_used;
+	return id;
+}
 
-				if (!ltmp) {
-					ltmp = ast_alloca(VAR_BUF_SIZE);
-				}
-				pbx_substitute_variables_helper_full(c, headp, var, ltmp, VAR_BUF_SIZE - 1, &my_used);
-				vars = ltmp;
-			} else {
-				vars = var;
-			}
+/*! \brief Add watcher for extension states with destructor */
+int ast_extension_state_add_destroy(const char *context, const char *exten,
+	ast_state_cb_type change_cb, ast_state_cb_destroy_type destroy_cb, void *data)
+{
+	return extension_state_add_destroy(context, exten, change_cb, destroy_cb, data, 0);
+}
 
-			if (!workspace)
-				workspace = ast_alloca(VAR_BUF_SIZE);
-
-			workspace[0] = '\0';
-
-			parse_variable_name(vars, &offset, &offset2, &isfunction);
-			if (isfunction) {
-				/* Evaluate function */
-				if (c || !headp)
-					cp4 = ast_func_read(c, vars, workspace, VAR_BUF_SIZE) ? NULL : workspace;
-				else {
-					struct varshead old;
-					struct ast_channel *c = ast_dummy_channel_alloc();
-					if (c) {
-						memcpy(&old, ast_channel_varshead(c), sizeof(old));
-						memcpy(ast_channel_varshead(c), headp, sizeof(*ast_channel_varshead(c)));
-						cp4 = ast_func_read(c, vars, workspace, VAR_BUF_SIZE) ? NULL : workspace;
-						/* Don't deallocate the varshead that was passed in */
-						memcpy(ast_channel_varshead(c), &old, sizeof(*ast_channel_varshead(c)));
-						c = ast_channel_unref(c);
-					} else {
-						ast_log(LOG_ERROR, "Unable to allocate bogus channel for variable substitution.  Function results may be blank.\n");
-					}
-				}
-				ast_debug(2, "Function %s result is '%s'\n", vars, cp4 ? cp4 : "(null)");
-			} else {
-				/* Retrieve variable value */
-				pbx_retrieve_variable(c, vars, &cp4, workspace, VAR_BUF_SIZE, headp);
-			}
-			if (cp4) {
-				cp4 = substring(cp4, offset, offset2, workspace, VAR_BUF_SIZE);
-
-				length = strlen(cp4);
-				if (length > count)
-					length = count;
-				memcpy(cp2, cp4, length);
-				count -= length;
-				cp2 += length;
-				*cp2 = 0;
-			}
-		} else if (nextexp) {
-			/* We have an expression.  Find the start and end, and determine
-			   if we are going to have to recursively call ourselves on the
-			   contents */
-			vars = vare = nextexp + 2;
-			brackets = 1;
-			needsub = 0;
-
-			/* Find the end of it */
-			while (brackets && *vare) {
-				if ((vare[0] == '$') && (vare[1] == '[')) {
-					needsub++;
-					brackets++;
-					vare++;
-				} else if (vare[0] == '[') {
-					brackets++;
-				} else if (vare[0] == ']') {
-					brackets--;
-				} else if ((vare[0] == '$') && (vare[1] == '{')) {
-					needsub++;
-					vare++;
-				}
-				vare++;
-			}
-			if (brackets)
-				ast_log(LOG_WARNING, "Error in extension logic (missing ']')\n");
-			len = vare - vars - 1;
+/*! \brief Add watcher for extension states */
+int ast_extension_state_add(const char *context, const char *exten,
+	ast_state_cb_type change_cb, void *data)
+{
+	return extension_state_add_destroy(context, exten, change_cb, NULL, data, 0);
+}
+
+/*! \brief Add watcher for extended extension states with destructor */
+int ast_extension_state_add_destroy_extended(const char *context, const char *exten,
+	ast_state_cb_type change_cb, ast_state_cb_destroy_type destroy_cb, void *data)
+{
+	return extension_state_add_destroy(context, exten, change_cb, destroy_cb, data, 1);
+}
 
-			/* Skip totally over expression */
-			whereweare += (len + 3);
+/*! \brief Add watcher for extended extension states */
+int ast_extension_state_add_extended(const char *context, const char *exten,
+	ast_state_cb_type change_cb, void *data)
+{
+	return extension_state_add_destroy(context, exten, change_cb, NULL, data, 1);
+}
 
-			if (!var)
-				var = ast_alloca(VAR_BUF_SIZE);
+/*! \brief Find Hint by callback id */
+static int find_hint_by_cb_id(void *obj, void *arg, int flags)
+{
+	struct ast_state_cb *state_cb;
+	const struct ast_hint *hint = obj;
+	int *id = arg;
 
-			/* Store variable name (and truncate) */
-			ast_copy_string(var, vars, len + 1);
+	if ((state_cb = ao2_find(hint->callbacks, id, 0))) {
+		ao2_ref(state_cb, -1);
+		return CMP_MATCH | CMP_STOP;
+	}
 
-			/* Substitute if necessary */
-			if (needsub) {
-				size_t my_used;
+	return 0;
+}
 
-				if (!ltmp) {
-					ltmp = ast_alloca(VAR_BUF_SIZE);
-				}
-				pbx_substitute_variables_helper_full(c, headp, var, ltmp, VAR_BUF_SIZE - 1, &my_used);
-				vars = ltmp;
-			} else {
-				vars = var;
-			}
+/*! \brief  ast_extension_state_del: Remove a watcher from the callback list */
+int ast_extension_state_del(int id, ast_state_cb_type change_cb)
+{
+	struct ast_state_cb *p_cur;
+	int ret = -1;
 
-			length = ast_expr(vars, cp2, count, c);
+	if (!id) {	/* id == 0 is a callback without extension */
+		if (!change_cb) {
+			return ret;
+		}
+		p_cur = ao2_find(statecbs, change_cb, OBJ_UNLINK);
+		if (p_cur) {
+			ret = 0;
+			ao2_ref(p_cur, -1);
+		}
+	} else { /* callback with extension, find the callback based on ID */
+		struct ast_hint *hint;
 
-			if (length) {
-				ast_debug(1, "Expression result is '%s'\n", cp2);
-				count -= length;
-				cp2 += length;
-				*cp2 = 0;
+		ao2_lock(hints);/* Locked to hold off ast_merge_contexts_and_delete */
+		hint = ao2_callback(hints, 0, find_hint_by_cb_id, &id);
+		if (hint) {
+			p_cur = ao2_find(hint->callbacks, &id, OBJ_UNLINK);
+			if (p_cur) {
+				ret = 0;
+				ao2_ref(p_cur, -1);
 			}
+			ao2_ref(hint, -1);
 		}
+		ao2_unlock(hints);
 	}
-	*used = cp2 - orig_cp2;
-}
 
-void pbx_substitute_variables_helper(struct ast_channel *c, const char *cp1, char *cp2, int count)
-{
-	size_t used;
-	pbx_substitute_variables_helper_full(c, (c) ? ast_channel_varshead(c) : NULL, cp1, cp2, count, &used);
+	return ret;
 }
 
-void pbx_substitute_variables_varshead(struct varshead *headp, const char *cp1, char *cp2, int count)
+static int hint_id_cmp(void *obj, void *arg, int flags)
 {
-	size_t used;
-	pbx_substitute_variables_helper_full(NULL, headp, cp1, cp2, count, &used);
+	const struct ast_state_cb *cb = obj;
+	int *id = arg;
+
+	return (cb->id == *id) ? CMP_MATCH | CMP_STOP : 0;
 }
 
 /*!
- * \brief The return value depends on the action:
- *
- * E_MATCH, E_CANMATCH, E_MATCHMORE require a real match,
- *	and return 0 on failure, -1 on match;
- * E_FINDLABEL maps the label to a priority, and returns
- *	the priority on success, ... XXX
- * E_SPAWN, spawn an application,
+ * \internal
+ * \brief Destroy the given hint object.
  *
- * \retval 0 on success.
- * \retval  -1 on failure.
+ * \param obj Hint to destroy.
  *
- * \note The channel is auto-serviced in this function, because doing an extension
- * match may block for a long time.  For example, if the lookup has to use a network
- * dialplan switch, such as DUNDi or IAX2, it may take a while.  However, the channel
- * auto-service code will queue up any important signalling frames to be processed
- * after this is done.
+ * \return Nothing
  */
-static int pbx_extension_helper(struct ast_channel *c, struct ast_context *con,
-  const char *context, const char *exten, int priority,
-  const char *label, const char *callerid, enum ext_match_t action, int *found, int combined_find_spawn)
+static void destroy_hint(void *obj)
 {
-	struct ast_exten *e;
-	struct ast_app *app;
-	char *substitute = NULL;
-	int res;
-	struct pbx_find_info q = { .stacklen = 0 }; /* the rest is reset in pbx_find_extension */
-	char passdata[EXT_DATA_SIZE];
-	int matching_action = (action == E_MATCH || action == E_CANMATCH || action == E_MATCHMORE);
+	struct ast_hint *hint = obj;
+	int i;
 
-	ast_rdlock_contexts();
-	if (found)
-		*found = 0;
+	if (hint->callbacks) {
+		struct ast_state_cb *state_cb;
+		const char *context_name;
+		const char *exten_name;
 
-	e = pbx_find_extension(c, con, &q, context, exten, priority, label, callerid, action);
-	if (e) {
-		if (found)
-			*found = 1;
-		if (matching_action) {
-			ast_unlock_contexts();
-			return -1;	/* success, we found it */
-		} else if (action == E_FINDLABEL) { /* map the label to a priority */
-			res = e->priority;
-			ast_unlock_contexts();
-			return res;	/* the priority we were looking for */
-		} else {	/* spawn */
-			if (!e->cached_app)
-				e->cached_app = pbx_findapp(e->app);
-			app = e->cached_app;
-			if (ast_strlen_zero(e->data)) {
-				*passdata = '\0';
-			} else {
-				const char *tmp;
-				if ((!(tmp = strchr(e->data, '$'))) || (!strstr(tmp, "${") && !strstr(tmp, "$["))) {
-					/* no variables to substitute, copy on through */
-					ast_copy_string(passdata, e->data, sizeof(passdata));
-				} else {
-					/* save e->data on stack for later processing after lock released */
-					substitute = ast_strdupa(e->data);
-				}
-			}
-			ast_unlock_contexts();
-			if (!app) {
-				ast_log(LOG_WARNING, "No application '%s' for extension (%s, %s, %d)\n", e->app, context, exten, priority);
-				return -1;
-			}
-			if (ast_channel_context(c) != context)
-				ast_channel_context_set(c, context);
-			if (ast_channel_exten(c) != exten)
-				ast_channel_exten_set(c, exten);
-			ast_channel_priority_set(c, priority);
-			if (substitute) {
-				pbx_substitute_variables_helper(c, substitute, passdata, sizeof(passdata)-1);
-			}
-			ast_debug(1, "Launching '%s'\n", app->name);
-			if (VERBOSITY_ATLEAST(3)) {
-				ast_verb(3, "Executing [%s@%s:%d] " COLORIZE_FMT "(\"" COLORIZE_FMT "\", \"" COLORIZE_FMT "\") %s\n",
-					exten, context, priority,
-					COLORIZE(COLOR_BRCYAN, 0, app->name),
-					COLORIZE(COLOR_BRMAGENTA, 0, ast_channel_name(c)),
-					COLORIZE(COLOR_BRMAGENTA, 0, passdata),
-					"in new stack");
-			}
-			return pbx_exec(c, app, passdata);	/* 0 on success, -1 on failure */
-		}
-	} else if (q.swo) {	/* not found here, but in another switch */
-		if (found)
-			*found = 1;
-		ast_unlock_contexts();
-		if (matching_action) {
-			return -1;
+		if (hint->exten) {
+			context_name = ast_get_context_name(ast_get_extension_context(hint->exten));
+			exten_name = ast_get_extension_name(hint->exten);
+			hint->exten = NULL;
 		} else {
-			if (!q.swo->exec) {
-				ast_log(LOG_WARNING, "No execution engine for switch %s\n", q.swo->name);
-				res = -1;
-			}
-			return q.swo->exec(c, q.foundcontext ? q.foundcontext : context, exten, priority, callerid, q.data);
+			/* The extension has already been destroyed */
+			context_name = hint->context_name;
+			exten_name = hint->exten_name;
 		}
-	} else {	/* not found anywhere, see what happened */
-		ast_unlock_contexts();
-		/* Using S_OR here because Solaris doesn't like NULL being passed to ast_log */
-		switch (q.status) {
-		case STATUS_NO_CONTEXT:
-			if (!matching_action && !combined_find_spawn)
-				ast_log(LOG_NOTICE, "Cannot find extension context '%s'\n", S_OR(context, ""));
-			break;
-		case STATUS_NO_EXTENSION:
-			if (!matching_action && !combined_find_spawn)
-				ast_log(LOG_NOTICE, "Cannot find extension '%s' in context '%s'\n", exten, S_OR(context, ""));
-			break;
-		case STATUS_NO_PRIORITY:
-			if (!matching_action && !combined_find_spawn)
-				ast_log(LOG_NOTICE, "No such priority %d in extension '%s' in context '%s'\n", priority, exten, S_OR(context, ""));
-			break;
-		case STATUS_NO_LABEL:
-			if (context && !combined_find_spawn)
-				ast_log(LOG_NOTICE, "No such label '%s' in extension '%s' in context '%s'\n", label, exten, S_OR(context, ""));
-			break;
-		default:
-			ast_debug(1, "Shouldn't happen!\n");
+		hint->laststate = AST_EXTENSION_DEACTIVATED;
+		while ((state_cb = ao2_callback(hint->callbacks, OBJ_UNLINK, NULL, NULL))) {
+			/* Notify with -1 and remove all callbacks */
+			execute_state_callback(state_cb->change_cb,
+				context_name,
+				exten_name,
+				state_cb->data,
+				AST_HINT_UPDATE_DEVICE,
+				hint,
+				NULL);
+			ao2_ref(state_cb, -1);
 		}
-
-		return (matching_action) ? 0 : -1;
+		ao2_ref(hint->callbacks, -1);
 	}
-}
-
-/*! \brief Find hint for given extension in context */
-static struct ast_exten *ast_hint_extension_nolock(struct ast_channel *c, const char *context, const char *exten)
-{
-	struct pbx_find_info q = { .stacklen = 0 }; /* the rest is set in pbx_find_context */
-	return pbx_find_extension(c, NULL, &q, context, exten, PRIORITY_HINT, NULL, "", E_MATCH);
-}
-
-static struct ast_exten *ast_hint_extension(struct ast_channel *c, const char *context, const char *exten)
-{
-	struct ast_exten *e;
-	ast_rdlock_contexts();
-	e = ast_hint_extension_nolock(c, context, exten);
-	ast_unlock_contexts();
-	return e;
-}
 
-enum ast_extension_states ast_devstate_to_extenstate(enum ast_device_state devstate)
-{
-	switch (devstate) {
-	case AST_DEVICE_ONHOLD:
-		return AST_EXTENSION_ONHOLD;
-	case AST_DEVICE_BUSY:
-		return AST_EXTENSION_BUSY;
-	case AST_DEVICE_UNKNOWN:
-		return AST_EXTENSION_NOT_INUSE;
-	case AST_DEVICE_UNAVAILABLE:
-	case AST_DEVICE_INVALID:
-		return AST_EXTENSION_UNAVAILABLE;
-	case AST_DEVICE_RINGINUSE:
-		return (AST_EXTENSION_INUSE | AST_EXTENSION_RINGING);
-	case AST_DEVICE_RINGING:
-		return AST_EXTENSION_RINGING;
-	case AST_DEVICE_INUSE:
-		return AST_EXTENSION_INUSE;
-	case AST_DEVICE_NOT_INUSE:
-		return AST_EXTENSION_NOT_INUSE;
-	case AST_DEVICE_TOTAL: /* not a device state, included for completeness */
-		break;
+	for (i = 0; i < AST_VECTOR_SIZE(&hint->devices); i++) {
+		char *device = AST_VECTOR_GET(&hint->devices, i);
+		ast_free(device);
 	}
-
-	return AST_EXTENSION_NOT_INUSE;
+	AST_VECTOR_FREE(&hint->devices);
+	ast_free(hint->last_presence_subtype);
+	ast_free(hint->last_presence_message);
 }
 
-/*!
- * \internal
- * \brief Parse out the presence portion of the hint string
- */
-static char *parse_hint_presence(struct ast_str *hint_args)
+/*! \brief Remove hint from extension */
+static int ast_remove_hint(struct ast_exten *e)
 {
-	char *copy = ast_strdupa(ast_str_buffer(hint_args));
-	char *tmp = "";
+	/* Cleanup the Notifys if hint is removed */
+	struct ast_hint *hint;
 
-	if ((tmp = strrchr(copy, ','))) {
-		*tmp = '\0';
-		tmp++;
-	} else {
-		return NULL;
+	if (!e) {
+		return -1;
 	}
-	ast_str_set(&hint_args, 0, "%s", tmp);
-	return ast_str_buffer(hint_args);
-}
-
-/*!
- * \internal
- * \brief Parse out the device portion of the hint string
- */
-static char *parse_hint_device(struct ast_str *hint_args)
-{
-	char *copy = ast_strdupa(ast_str_buffer(hint_args));
-	char *tmp;
 
-	if ((tmp = strrchr(copy, ','))) {
-		*tmp = '\0';
+	hint = ao2_find(hints, e, OBJ_UNLINK);
+	if (!hint) {
+		return -1;
 	}
 
-	ast_str_set(&hint_args, 0, "%s", copy);
-	return ast_str_buffer(hint_args);
-}
+	remove_hintdevice(hint);
 
-static void device_state_info_dt(void *obj)
-{
-	struct ast_device_state_info *info = obj;
+	/*
+	 * The extension is being destroyed so we must save some
+	 * information to notify that the extension is deactivated.
+	 */
+	ao2_lock(hint);
+	ast_copy_string(hint->context_name,
+		ast_get_context_name(ast_get_extension_context(hint->exten)),
+		sizeof(hint->context_name));
+	ast_copy_string(hint->exten_name, ast_get_extension_name(hint->exten),
+		sizeof(hint->exten_name));
+	hint->exten = NULL;
+	ao2_unlock(hint);
 
-	ao2_cleanup(info->causing_channel);
-}
+	ao2_ref(hint, -1);
 
-static struct ao2_container *alloc_device_state_info(void)
-{
-	return ao2_container_alloc_options(AO2_ALLOC_OPT_LOCK_NOLOCK, 1, NULL, NULL);
+	return 0;
 }
 
-static int ast_extension_state3(struct ast_str *hint_app, struct ao2_container *device_state_info)
+/*! \brief Add hint to hint list, check initial extension state */
+static int ast_add_hint(struct ast_exten *e)
 {
-	char *cur;
-	char *rest;
-	struct ast_devstate_aggregate agg;
-
-	/* One or more devices separated with a & character */
-	rest = parse_hint_device(hint_app);
+	struct ast_hint *hint_new;
+	struct ast_hint *hint_found;
+	char *message = NULL;
+	char *subtype = NULL;
+	int presence_state;
 
-	ast_devstate_aggregate_init(&agg);
-	while ((cur = strsep(&rest, "&"))) {
-		enum ast_device_state state = ast_device_state(cur);
+	if (!e) {
+		return -1;
+	}
 
-		ast_devstate_aggregate_add(&agg, state);
-		if (device_state_info) {
-			struct ast_device_state_info *obj;
+	/*
+	 * We must create the hint we wish to add before determining if
+	 * it is already in the hints container to avoid possible
+	 * deadlock when getting the current extension state.
+	 */
+	hint_new = ao2_alloc(sizeof(*hint_new), destroy_hint);
+	if (!hint_new) {
+		return -1;
+	}
+	AST_VECTOR_INIT(&hint_new->devices, 8);
 
-			obj = ao2_alloc_options(sizeof(*obj) + strlen(cur), device_state_info_dt, AO2_ALLOC_OPT_LOCK_NOLOCK);
-			/* if failed we cannot add this device */
-			if (obj) {
-				obj->device_state = state;
-				strcpy(obj->device_name, cur);
-				ao2_link(device_state_info, obj);
-				ao2_ref(obj, -1);
-			}
+	/* Initialize new hint. */
+	hint_new->callbacks = ao2_container_alloc(1, NULL, hint_id_cmp);
+	if (!hint_new->callbacks) {
+		ao2_ref(hint_new, -1);
+		return -1;
+	}
+	hint_new->exten = e;
+	if (strstr(e->app, "${") && e->exten[0] == '_') {
+		/* The hint is dynamic and hasn't been evaluted yet */
+		hint_new->laststate = AST_DEVICE_INVALID;
+		hint_new->last_presence_state = AST_PRESENCE_INVALID;
+	} else {
+		hint_new->laststate = ast_extension_state2(e, NULL);
+		if ((presence_state = extension_presence_state_helper(e, &subtype, &message)) > 0) {
+			hint_new->last_presence_state = presence_state;
+			hint_new->last_presence_subtype = subtype;
+			hint_new->last_presence_message = message;
+			message = subtype = NULL;
 		}
 	}
 
-	return ast_devstate_to_extenstate(ast_devstate_aggregate_result(&agg));
-}
-
-/*! \brief Check state of extension by using hints */
-static int ast_extension_state2(struct ast_exten *e, struct ao2_container *device_state_info)
-{
-	struct ast_str *hint_app = ast_str_thread_get(&extensionstate_buf, 32);
+	/* Prevent multiple add hints from adding the same hint at the same time. */
+	ao2_lock(hints);
 
-	if (!e || !hint_app) {
+	/* Search if hint exists, do nothing */
+	hint_found = ao2_find(hints, e, 0);
+	if (hint_found) {
+		ao2_ref(hint_found, -1);
+		ao2_unlock(hints);
+		ao2_ref(hint_new, -1);
+		ast_debug(2, "HINTS: Not re-adding existing hint %s: %s\n",
+			ast_get_extension_name(e), ast_get_extension_app(e));
 		return -1;
 	}
 
-	ast_str_set(&hint_app, 0, "%s", ast_get_extension_app(e));
-	return ast_extension_state3(hint_app, device_state_info);
-}
-
-/*! \brief Return extension_state as string */
-const char *ast_extension_state2str(int extension_state)
-{
-	int i;
-
-	for (i = 0; (i < ARRAY_LEN(extension_states)); i++) {
-		if (extension_states[i].extension_state == extension_state)
-			return extension_states[i].text;
+	/* Add new hint to the hints container */
+	ast_debug(2, "HINTS: Adding hint %s: %s\n",
+		ast_get_extension_name(e), ast_get_extension_app(e));
+	ao2_link(hints, hint_new);
+	if (add_hintdevice(hint_new, ast_get_extension_app(e))) {
+		ast_log(LOG_WARNING, "Could not add devices for hint: %s@%s.\n",
+			ast_get_extension_name(e),
+			ast_get_context_name(ast_get_extension_context(e)));
 	}
-	return "Unknown";
-}
-
-/*!
- * \internal
- * \brief Check extension state for an extension by using hint
- */
-static int internal_extension_state_extended(struct ast_channel *c, const char *context, const char *exten,
-	struct ao2_container *device_state_info)
-{
-	struct ast_exten *e;
 
-	if (!(e = ast_hint_extension(c, context, exten))) {  /* Do we have a hint for this extension ? */
-		return -1;                   /* No hint, return -1 */
-	}
+	/* if not dynamic */
+	if (!(strstr(e->app, "${") && e->exten[0] == '_')) {
+		struct ast_state_cb *state_cb;
+		struct ao2_iterator cb_iter;
 
-	if (e->exten[0] == '_') {
-		/* Create this hint on-the-fly */
-		ast_add_extension(e->parent->name, 0, exten, e->priority, e->label,
-			e->matchcid ? e->cidmatch : NULL, e->app, ast_strdup(e->data), ast_free_ptr,
-			e->registrar);
-		if (!(e = ast_hint_extension(c, context, exten))) {
-			/* Improbable, but not impossible */
-			return -1;
+		/* For general callbacks */
+		cb_iter = ao2_iterator_init(statecbs, 0);
+		for (; (state_cb = ao2_iterator_next(&cb_iter)); ao2_ref(state_cb, -1)) {
+			execute_state_callback(state_cb->change_cb,
+					ast_get_context_name(ast_get_extension_context(e)),
+					ast_get_extension_name(e),
+					state_cb->data,
+					AST_HINT_UPDATE_DEVICE,
+					hint_new,
+					NULL);
 		}
+		ao2_iterator_destroy(&cb_iter);
 	}
+	ao2_unlock(hints);
+	ao2_ref(hint_new, -1);
 
-	return ast_extension_state2(e, device_state_info);  /* Check all devices in the hint */
-}
-
-/*! \brief Check extension state for an extension by using hint */
-int ast_extension_state(struct ast_channel *c, const char *context, const char *exten)
-{
-	return internal_extension_state_extended(c, context, exten, NULL);
+	return 0;
 }
 
-/*! \brief Check extended extension state for an extension by using hint */
-int ast_extension_state_extended(struct ast_channel *c, const char *context, const char *exten,
-	struct ao2_container **device_state_info)
+/*! \brief Publish a hint changed event  */
+static int publish_hint_change(struct ast_hint *hint, struct ast_exten *ne)
 {
-	struct ao2_container *container = NULL;
-	int ret;
+	struct stasis_message *message;
 
-	if (device_state_info) {
-		container = alloc_device_state_info();
+	if (!hint_change_message_type()) {
+		return -1;
 	}
 
-	ret = internal_extension_state_extended(c, context, exten, container);
-	if (ret < 0 && container) {
-		ao2_ref(container, -1);
-		container = NULL;
+	/* The message is going to be published to two topics so the hint needs two refs */
+	if (!(message = stasis_message_create(hint_change_message_type(), ao2_bump(hint)))) {
+		ao2_ref(hint, -1);
+		return -1;
 	}
 
-	if (device_state_info) {
-		get_device_state_causing_channels(container);
-		*device_state_info = container;
-	}
+	stasis_publish(ast_device_state_topic_all(), message);
+	stasis_publish(ast_presence_state_topic_all(), message);
 
-	return ret;
+	ao2_ref(message, -1);
+
+	return 0;
 }
 
-static int extension_presence_state_helper(struct ast_exten *e, char **subtype, char **message)
+/*! \brief Change hint for an extension */
+static int ast_change_hint(struct ast_exten *oe, struct ast_exten *ne)
 {
-	struct ast_str *hint_app = ast_str_thread_get(&extensionstate_buf, 32);
-	char *presence_provider;
-	const char *app;
+	struct ast_hint *hint;
 
-	if (!e || !hint_app) {
+	if (!oe || !ne) {
 		return -1;
 	}
 
-	app = ast_get_extension_app(e);
-	if (ast_strlen_zero(app)) {
+	ao2_lock(hints);/* Locked to hold off others while we move the hint around. */
+
+	/*
+	 * Unlink the hint from the hints container as the extension
+	 * name (which is the hash value) could change.
+	 */
+	hint = ao2_find(hints, oe, OBJ_UNLINK);
+	if (!hint) {
+		ao2_unlock(hints);
+		ast_mutex_unlock(&context_merge_lock);
 		return -1;
 	}
 
-	ast_str_set(&hint_app, 0, "%s", app);
-	presence_provider = parse_hint_presence(hint_app);
+	remove_hintdevice(hint);
 
-	if (ast_strlen_zero(presence_provider)) {
-		/* No presence string in the hint */
-		return 0;
+	/* Update the hint and put it back in the hints container. */
+	ao2_lock(hint);
+	hint->exten = ne;
+
+	ao2_unlock(hint);
+
+	ao2_link(hints, hint);
+	if (add_hintdevice(hint, ast_get_extension_app(ne))) {
+		ast_log(LOG_WARNING, "Could not add devices for hint: %s@%s.\n",
+			ast_get_extension_name(ne),
+			ast_get_context_name(ast_get_extension_context(ne)));
 	}
+	ao2_unlock(hints);
 
-	return ast_presence_state(presence_provider, subtype, message);
+	publish_hint_change(hint, ne);
+
+	ao2_ref(hint, -1);
+
+	return 0;
 }
 
-int ast_hint_presence_state(struct ast_channel *c, const char *context, const char *exten, char **subtype, char **message)
+/*! \brief Get hint for channel */
+int ast_get_hint(char *hint, int hintsize, char *name, int namesize, struct ast_channel *c, const char *context, const char *exten)
 {
-	struct ast_exten *e;
-
-	if (!(e = ast_hint_extension(c, context, exten))) {  /* Do we have a hint for this extension ? */
-		return -1;                   /* No hint, return -1 */
-	}
+	struct ast_exten *e = ast_hint_extension(c, context, exten);
 
-	if (e->exten[0] == '_') {
-		/* Create this hint on-the-fly */
-		ast_add_extension(e->parent->name, 0, exten, e->priority, e->label,
-			e->matchcid ? e->cidmatch : NULL, e->app, ast_strdup(e->data), ast_free_ptr,
-			e->registrar);
-		if (!(e = ast_hint_extension(c, context, exten))) {
-			/* Improbable, but not impossible */
-			return -1;
+	if (e) {
+		if (hint)
+			ast_copy_string(hint, ast_get_extension_app(e), hintsize);
+		if (name) {
+			const char *tmp = ast_get_extension_app_data(e);
+			if (tmp)
+				ast_copy_string(name, tmp, namesize);
 		}
+		return -1;
 	}
-
-	return extension_presence_state_helper(e, subtype, message);
+	return 0;
 }
 
-static int execute_state_callback(ast_state_cb_type cb,
-	const char *context,
-	const char *exten,
-	void *data,
-	enum ast_state_cb_update_reason reason,
-	struct ast_hint *hint,
-	struct ao2_container *device_state_info)
+/*! \brief Get hint for channel */
+int ast_str_get_hint(struct ast_str **hint, ssize_t hintsize, struct ast_str **name, ssize_t namesize, struct ast_channel *c, const char *context, const char *exten)
 {
-	int res = 0;
-	struct ast_state_cb_info info = { 0, };
+	struct ast_exten *e = ast_hint_extension(c, context, exten);
 
-	info.reason = reason;
+	if (!e) {
+		return 0;
+	}
 
-	/* Copy over current hint data */
 	if (hint) {
-		ao2_lock(hint);
-		info.exten_state = hint->laststate;
-		info.device_state_info = device_state_info;
-		info.presence_state = hint->last_presence_state;
-		if (!(ast_strlen_zero(hint->last_presence_subtype))) {
-			info.presence_subtype = ast_strdupa(hint->last_presence_subtype);
-		} else {
-			info.presence_subtype = "";
-		}
-		if (!(ast_strlen_zero(hint->last_presence_message))) {
-			info.presence_message = ast_strdupa(hint->last_presence_message);
-		} else {
-			info.presence_message = "";
+		ast_str_set(hint, hintsize, "%s", ast_get_extension_app(e));
+	}
+	if (name) {
+		const char *tmp = ast_get_extension_app_data(e);
+		if (tmp) {
+			ast_str_set(name, namesize, "%s", tmp);
 		}
-		ao2_unlock(hint);
-	} else {
-		info.exten_state = AST_EXTENSION_REMOVED;
 	}
+	return -1;
+}
 
-	/* NOTE: The casts will not be needed for v10 and later */
-	res = cb((char *) context, (char *) exten, &info, data);
-
-	return res;
+int ast_exists_extension(struct ast_channel *c, const char *context, const char *exten, int priority, const char *callerid)
+{
+	return pbx_extension_helper(c, NULL, context, exten, priority, NULL, callerid, E_MATCH, 0, 0);
 }
 
-/*!
- * /internal
- * /brief Identify a channel for every device which is supposedly responsible for the device state.
- *
- * Especially when the device is ringing, the oldest ringing channel is chosen.
- * For all other cases the first encountered channel in the specific state is chosen.
- */
-static void get_device_state_causing_channels(struct ao2_container *c)
+int ast_findlabel_extension(struct ast_channel *c, const char *context, const char *exten, const char *label, const char *callerid)
 {
-	struct ao2_iterator iter;
-	struct ast_device_state_info *info;
-	struct ast_channel *chan;
-
-	if (!c || !ao2_container_count(c)) {
-		return;
-	}
-	iter = ao2_iterator_init(c, 0);
-	for (; (info = ao2_iterator_next(&iter)); ao2_ref(info, -1)) {
-		enum ast_channel_state search_state = 0; /* prevent false uninit warning */
-		char match[AST_CHANNEL_NAME];
-		struct ast_channel_iterator *chan_iter;
-		struct timeval chantime = {0, }; /* prevent false uninit warning */
-
-		switch (info->device_state) {
-		case AST_DEVICE_RINGING:
-		case AST_DEVICE_RINGINUSE:
-			/* find ringing channel */
-			search_state = AST_STATE_RINGING;
-			break;
-		case AST_DEVICE_BUSY:
-			/* find busy channel */
-			search_state = AST_STATE_BUSY;
-			break;
-		case AST_DEVICE_ONHOLD:
-		case AST_DEVICE_INUSE:
-			/* find up channel */
-			search_state = AST_STATE_UP;
-			break;
-		case AST_DEVICE_UNKNOWN:
-		case AST_DEVICE_NOT_INUSE:
-		case AST_DEVICE_INVALID:
-		case AST_DEVICE_UNAVAILABLE:
-		case AST_DEVICE_TOTAL /* not a state */:
-			/* no channels are of interest */
-			continue;
-		}
+	return pbx_extension_helper(c, NULL, context, exten, 0, label, callerid, E_FINDLABEL, 0, 0);
+}
 
-		/* iterate over all channels of the device */
-	        snprintf(match, sizeof(match), "%s-", info->device_name);
-		chan_iter = ast_channel_iterator_by_name_new(match, strlen(match));
-		for (; (chan = ast_channel_iterator_next(chan_iter)); ast_channel_unref(chan)) {
-			ast_channel_lock(chan);
-			/* this channel's state doesn't match */
-			if (search_state != ast_channel_state(chan)) {
-				ast_channel_unlock(chan);
-				continue;
-			}
-			/* any non-ringing channel will fit */
-			if (search_state != AST_STATE_RINGING) {
-				ast_channel_unlock(chan);
-				info->causing_channel = chan; /* is kept ref'd! */
-				break;
-			}
-			/* but we need the oldest ringing channel of the device to match with undirected pickup */
-			if (!info->causing_channel) {
-				chantime = ast_channel_creationtime(chan);
-				ast_channel_ref(chan); /* must ref it! */
-				info->causing_channel = chan;
-			} else if (ast_tvcmp(ast_channel_creationtime(chan), chantime) < 0) {
-				chantime = ast_channel_creationtime(chan);
-				ast_channel_unref(info->causing_channel);
-				ast_channel_ref(chan); /* must ref it! */
-				info->causing_channel = chan;
-			}
-			ast_channel_unlock(chan);
-		}
-		ast_channel_iterator_destroy(chan_iter);
-	}
-	ao2_iterator_destroy(&iter);
+int ast_findlabel_extension2(struct ast_channel *c, struct ast_context *con, const char *exten, const char *label, const char *callerid)
+{
+	return pbx_extension_helper(c, con, NULL, exten, 0, label, callerid, E_FINDLABEL, 0, 0);
 }
 
-static void device_state_cb(void *unused, struct stasis_subscription *sub, struct stasis_message *msg)
+int ast_canmatch_extension(struct ast_channel *c, const char *context, const char *exten, int priority, const char *callerid)
 {
-	struct ast_device_state_message *dev_state;
-	struct ast_hint *hint;
-	struct ast_str *hint_app;
-	struct ast_hintdevice *device;
-	struct ast_hintdevice *cmpdevice;
-	struct ao2_iterator *dev_iter;
-	struct ao2_iterator cb_iter;
-	char context_name[AST_MAX_CONTEXT];
-	char exten_name[AST_MAX_EXTENSION];
+	return pbx_extension_helper(c, NULL, context, exten, priority, NULL, callerid, E_CANMATCH, 0, 0);
+}
 
-	if (ast_device_state_message_type() != stasis_message_type(msg)) {
-		return;
-	}
+int ast_matchmore_extension(struct ast_channel *c, const char *context, const char *exten, int priority, const char *callerid)
+{
+	return pbx_extension_helper(c, NULL, context, exten, priority, NULL, callerid, E_MATCHMORE, 0, 0);
+}
 
-	dev_state = stasis_message_data(msg);
-	if (dev_state->eid) {
-		/* ignore non-aggregate states */
-		return;
-	}
+int ast_spawn_extension(struct ast_channel *c, const char *context, const char *exten, int priority, const char *callerid, int *found, int combined_find_spawn)
+{
+	return pbx_extension_helper(c, NULL, context, exten, priority, NULL, callerid, E_SPAWN, found, combined_find_spawn);
+}
 
-	if (ao2_container_count(hintdevices) == 0) {
-		/* There are no hints monitoring devices. */
-		return;
-	}
+void ast_pbx_h_exten_run(struct ast_channel *chan, const char *context)
+{
+	int autoloopflag;
+	int found;
+	int spawn_error;
 
-	hint_app = ast_str_create(1024);
-	if (!hint_app) {
-		return;
-	}
+	ast_channel_lock(chan);
 
-	cmpdevice = ast_alloca(sizeof(*cmpdevice) + strlen(dev_state->device));
-	strcpy(cmpdevice->hintdevice, dev_state->device);
+	/*
+	 * Make sure that the channel is marked as hungup since we are
+	 * going to run the h exten on it.
+	 */
+	ast_softhangup_nolock(chan, AST_SOFTHANGUP_HANGUP_EXEC);
 
-	ast_mutex_lock(&context_merge_lock);/* Hold off ast_merge_contexts_and_delete */
-	dev_iter = ao2_t_callback(hintdevices,
-		OBJ_SEARCH_OBJECT | OBJ_MULTIPLE,
-		hintdevice_cmp_multiple,
-		cmpdevice,
-		"find devices in container");
-	if (!dev_iter) {
-		ast_mutex_unlock(&context_merge_lock);
-		ast_free(hint_app);
-		return;
+	/* Set h exten location */
+	if (context != ast_channel_context(chan)) {
+		ast_channel_context_set(chan, context);
 	}
+	ast_channel_exten_set(chan, "h");
+	ast_channel_priority_set(chan, 1);
 
-	for (; (device = ao2_iterator_next(dev_iter)); ao2_t_ref(device, -1, "Next device")) {
-		struct ast_state_cb *state_cb;
-		int state;
-		int same_state;
-		struct ao2_container *device_state_info;
-		int first_extended_cb_call = 1;
-
-		if (!device->hint) {
-			/* Should never happen. */
-			continue;
-		}
-		hint = device->hint;
-
-		ao2_lock(hint);
-		if (!hint->exten) {
-			/* The extension has already been destroyed */
-			ao2_unlock(hint);
-			continue;
-		}
-
-		/*
-		 * Save off strings in case the hint extension gets destroyed
-		 * while we are notifying the watchers.
-		 */
-		ast_copy_string(context_name,
-			ast_get_context_name(ast_get_extension_context(hint->exten)),
-			sizeof(context_name));
-		ast_copy_string(exten_name, ast_get_extension_name(hint->exten),
-			sizeof(exten_name));
-		ast_str_set(&hint_app, 0, "%s", ast_get_extension_app(hint->exten));
-		ao2_unlock(hint);
-
-		/*
-		 * Get device state for this hint.
-		 *
-		 * NOTE: We cannot hold any locks while determining the hint
-		 * device state or notifying the watchers without causing a
-		 * deadlock.  (conlock, hints, and hint)
-		 */
-		/* Make a container so state3 can fill it if we wish.
-		 * If that failed we simply do not provide the extended state info.
-		 */
-		device_state_info = alloc_device_state_info();
-		state = ast_extension_state3(hint_app, device_state_info);
-		if ((same_state = state == hint->laststate) && (~state & AST_EXTENSION_RINGING)) {
-			ao2_cleanup(device_state_info);
-			continue;
-		}
-
-		/* Device state changed since last check - notify the watchers. */
-		hint->laststate = state;	/* record we saw the change */
+	/* Save autoloop flag */
+	autoloopflag = ast_test_flag(ast_channel_flags(chan), AST_FLAG_IN_AUTOLOOP);
+	ast_set_flag(ast_channel_flags(chan), AST_FLAG_IN_AUTOLOOP);
+	ast_channel_unlock(chan);
 
-		/* For general callbacks */
-		cb_iter = ao2_iterator_init(statecbs, 0);
-		for (; !same_state && (state_cb = ao2_iterator_next(&cb_iter)); ao2_ref(state_cb, -1)) {
-			execute_state_callback(state_cb->change_cb,
-				context_name,
-				exten_name,
-				state_cb->data,
-				AST_HINT_UPDATE_DEVICE,
-				hint,
-				NULL);
-		}
-		ao2_iterator_destroy(&cb_iter);
+	for (;;) {
+		spawn_error = ast_spawn_extension(chan, ast_channel_context(chan),
+			ast_channel_exten(chan), ast_channel_priority(chan),
+			S_COR(ast_channel_caller(chan)->id.number.valid,
+				ast_channel_caller(chan)->id.number.str, NULL), &found, 1);
 
-		/* For extension callbacks */
-		/* extended callbacks are called when the state changed or when AST_STATE_RINGING is
-		 * included. Normal callbacks are only called when the state changed.
-		 */
-		cb_iter = ao2_iterator_init(hint->callbacks, 0);
-		for (; (state_cb = ao2_iterator_next(&cb_iter)); ao2_ref(state_cb, -1)) {
-			if (state_cb->extended && first_extended_cb_call) {
-				/* Fill detailed device_state_info now that we know it is used by extd. callback */
-				first_extended_cb_call = 0;
-				get_device_state_causing_channels(device_state_info);
-			}
-			if (state_cb->extended || !same_state) {
-				execute_state_callback(state_cb->change_cb,
-					context_name,
-					exten_name,
-					state_cb->data,
-					AST_HINT_UPDATE_DEVICE,
-					hint,
-					state_cb->extended ? device_state_info : NULL);
-			}
+		ast_channel_lock(chan);
+		if (spawn_error) {
+			/* The code after the loop needs the channel locked. */
+			break;
 		}
-		ao2_iterator_destroy(&cb_iter);
-
-		ao2_cleanup(device_state_info);
+		ast_channel_priority_set(chan, ast_channel_priority(chan) + 1);
+		ast_channel_unlock(chan);
+	}
+	if (found && spawn_error) {
+		/* Something bad happened, or a hangup has been requested. */
+		ast_debug(1, "Spawn extension (%s,%s,%d) exited non-zero on '%s'\n",
+			ast_channel_context(chan), ast_channel_exten(chan),
+			ast_channel_priority(chan), ast_channel_name(chan));
+		ast_verb(2, "Spawn extension (%s, %s, %d) exited non-zero on '%s'\n",
+			ast_channel_context(chan), ast_channel_exten(chan),
+			ast_channel_priority(chan), ast_channel_name(chan));
 	}
-	ast_mutex_unlock(&context_merge_lock);
 
-	ao2_iterator_destroy(dev_iter);
-	ast_free(hint_app);
-	return;
+	/* An "h" exten has been run, so indicate that one has been run. */
+	ast_set_flag(ast_channel_flags(chan), AST_FLAG_BRIDGE_HANGUP_RUN);
+
+	/* Restore autoloop flag */
+	ast_set2_flag(ast_channel_flags(chan), autoloopflag, AST_FLAG_IN_AUTOLOOP);
+	ast_channel_unlock(chan);
 }
 
-/*!
- * \internal
- * \brief Destroy the given state callback object.
- *
- * \param doomed State callback to destroy.
- *
- * \return Nothing
- */
-static void destroy_state_cb(void *doomed)
+/*! helper function to set extension and priority */
+void set_ext_pri(struct ast_channel *c, const char *exten, int pri)
 {
-	struct ast_state_cb *state_cb = doomed;
-
-	if (state_cb->destroy_cb) {
-		state_cb->destroy_cb(state_cb->id, state_cb->data);
-	}
+	ast_channel_lock(c);
+	ast_channel_exten_set(c, exten);
+	ast_channel_priority_set(c, pri);
+	ast_channel_unlock(c);
 }
 
 /*!
- * \internal
- * \brief Add watcher for extension states with destructor
- */
-static int extension_state_add_destroy(const char *context, const char *exten,
-	ast_state_cb_type change_cb, ast_state_cb_destroy_type destroy_cb, void *data, int extended)
+ * \brief collect digits from the channel into the buffer.
+ * \param c, buf, buflen, pos
+ * \param waittime is in milliseconds
+ * \retval 0 on timeout or done.
+ * \retval -1 on error.
+*/
+static int collect_digits(struct ast_channel *c, int waittime, char *buf, int buflen, int pos)
 {
-	struct ast_hint *hint;
-	struct ast_state_cb *state_cb;
-	struct ast_exten *e;
-	int id;
-
-	/* If there's no context and extension:  add callback to statecbs list */
-	if (!context && !exten) {
-		/* Prevent multiple adds from adding the same change_cb at the same time. */
-		ao2_lock(statecbs);
-
-		/* Remove any existing change_cb. */
-		ao2_find(statecbs, change_cb, OBJ_UNLINK | OBJ_NODATA);
+	int digit;
 
-		/* Now insert the change_cb */
-		if (!(state_cb = ao2_alloc(sizeof(*state_cb), destroy_state_cb))) {
-			ao2_unlock(statecbs);
-			return -1;
+	buf[pos] = '\0';	/* make sure it is properly terminated */
+	while (ast_matchmore_extension(c, ast_channel_context(c), buf, 1,
+		S_COR(ast_channel_caller(c)->id.number.valid, ast_channel_caller(c)->id.number.str, NULL))) {
+		/* As long as we're willing to wait, and as long as it's not defined,
+		   keep reading digits until we can't possibly get a right answer anymore.  */
+		digit = ast_waitfordigit(c, waittime);
+		if (ast_channel_softhangup_internal_flag(c) & AST_SOFTHANGUP_ASYNCGOTO) {
+			ast_channel_clear_softhangup(c, AST_SOFTHANGUP_ASYNCGOTO);
+		} else {
+			if (!digit)	/* No entry */
+				break;
+			if (digit < 0)	/* Error, maybe a  hangup */
+				return -1;
+			if (pos < buflen - 1) {	/* XXX maybe error otherwise ? */
+				buf[pos++] = digit;
+				buf[pos] = '\0';
+			}
+			waittime = ast_channel_pbx(c)->dtimeoutms;
 		}
-		state_cb->id = 0;
-		state_cb->change_cb = change_cb;
-		state_cb->destroy_cb = destroy_cb;
-		state_cb->data = data;
-		state_cb->extended = extended;
-		ao2_link(statecbs, state_cb);
-
-		ao2_ref(state_cb, -1);
-		ao2_unlock(statecbs);
-		return 0;
 	}
+	return 0;
+}
 
-	if (!context || !exten)
-		return -1;
+static enum ast_pbx_result __ast_pbx_run(struct ast_channel *c,
+		struct ast_pbx_args *args)
+{
+	int found = 0;	/* set if we find at least one match */
+	int res = 0;
+	int autoloopflag;
+	int error = 0;		/* set an error conditions */
+	struct ast_pbx *pbx;
+	struct ast_callid *callid;
 
-	/* This callback type is for only one hint, so get the hint */
-	e = ast_hint_extension(NULL, context, exten);
-	if (!e) {
-		return -1;
+	/* A little initial setup here */
+	if (ast_channel_pbx(c)) {
+		ast_log(LOG_WARNING, "%s already has PBX structure??\n", ast_channel_name(c));
+		/* XXX and now what ? */
+		ast_free(ast_channel_pbx(c));
+	}
+	if (!(pbx = ast_calloc(1, sizeof(*pbx)))) {
+		return AST_PBX_FAILED;
 	}
 
-	/* If this is a pattern, dynamically create a new extension for this
-	 * particular match.  Note that this will only happen once for each
-	 * individual extension, because the pattern will no longer match first.
-	 */
-	if (e->exten[0] == '_') {
-		ast_add_extension(e->parent->name, 0, exten, e->priority, e->label,
-			e->matchcid ? e->cidmatch : NULL, e->app, ast_strdup(e->data), ast_free_ptr,
-			e->registrar);
-		e = ast_hint_extension(NULL, context, exten);
-		if (!e || e->exten[0] == '_') {
-			return -1;
+	callid = ast_read_threadstorage_callid();
+	/* If the thread isn't already associated with a callid, we should create that association. */
+	if (!callid) {
+		/* Associate new PBX thread with the channel call id if it is availble.
+		 * If not, create a new one instead.
+		 */
+		callid = ast_channel_callid(c);
+		if (!callid) {
+			callid = ast_create_callid();
+			if (callid) {
+				ast_channel_lock(c);
+				ast_channel_callid_set(c, callid);
+				ast_channel_unlock(c);
+			}
 		}
+		ast_callid_threadassoc_add(callid);
+		callid = ast_callid_unref(callid);
+	} else {
+		/* Nothing to do here, The thread is already bound to a callid.  Let's just get rid of the reference. */
+		ast_callid_unref(callid);
 	}
 
-	/* Find the hint in the hints container */
-	ao2_lock(hints);/* Locked to hold off ast_merge_contexts_and_delete */
-	hint = ao2_find(hints, e, 0);
-	if (!hint) {
-		ao2_unlock(hints);
-		return -1;
-	}
+	ast_channel_pbx_set(c, pbx);
+	/* Set reasonable defaults */
+	ast_channel_pbx(c)->rtimeoutms = 10000;
+	ast_channel_pbx(c)->dtimeoutms = 5000;
 
-	/* Now insert the callback in the callback list  */
-	if (!(state_cb = ao2_alloc(sizeof(*state_cb), destroy_state_cb))) {
-		ao2_ref(hint, -1);
-		ao2_unlock(hints);
-		return -1;
+	autoloopflag = ast_test_flag(ast_channel_flags(c), AST_FLAG_IN_AUTOLOOP);	/* save value to restore at the end */
+	ast_set_flag(ast_channel_flags(c), AST_FLAG_IN_AUTOLOOP);
+
+	if (ast_strlen_zero(ast_channel_exten(c))) {
+		/* If not successful fall back to 's' - but only if there is no given exten  */
+		ast_verb(2, "Starting %s at %s,%s,%d failed so falling back to exten 's'\n", ast_channel_name(c), ast_channel_context(c), ast_channel_exten(c), ast_channel_priority(c));
+		/* XXX the original code used the existing priority in the call to
+		 * ast_exists_extension(), and reset it to 1 afterwards.
+		 * I believe the correct thing is to set it to 1 immediately.
+		*/
+		set_ext_pri(c, "s", 1);
 	}
-	do {
-		id = stateid++;		/* Unique ID for this callback */
-		/* Do not allow id to ever be -1 or 0. */
-	} while (id == -1 || id == 0);
-	state_cb->id = id;
-	state_cb->change_cb = change_cb;	/* Pointer to callback routine */
-	state_cb->destroy_cb = destroy_cb;
-	state_cb->data = data;		/* Data for the callback */
-	state_cb->extended = extended;
-	ao2_link(hint->callbacks, state_cb);
 
-	ao2_ref(state_cb, -1);
-	ao2_ref(hint, -1);
-	ao2_unlock(hints);
+	for (;;) {
+		char dst_exten[256];	/* buffer to accumulate digits */
+		int pos = 0;		/* XXX should check bounds */
+		int digit = 0;
+		int invalid = 0;
+		int timeout = 0;
 
-	return id;
-}
+		/* No digits pressed yet */
+		dst_exten[pos] = '\0';
 
-/*! \brief Add watcher for extension states with destructor */
-int ast_extension_state_add_destroy(const char *context, const char *exten,
-	ast_state_cb_type change_cb, ast_state_cb_destroy_type destroy_cb, void *data)
-{
-	return extension_state_add_destroy(context, exten, change_cb, destroy_cb, data, 0);
-}
+		/* loop on priorities in this context/exten */
+		while (!(res = ast_spawn_extension(c, ast_channel_context(c), ast_channel_exten(c), ast_channel_priority(c),
+			S_COR(ast_channel_caller(c)->id.number.valid, ast_channel_caller(c)->id.number.str, NULL),
+			&found, 1))) {
 
-/*! \brief Add watcher for extension states */
-int ast_extension_state_add(const char *context, const char *exten,
-	ast_state_cb_type change_cb, void *data)
-{
-	return extension_state_add_destroy(context, exten, change_cb, NULL, data, 0);
-}
+			if (!ast_check_hangup(c)) {
+				ast_channel_priority_set(c, ast_channel_priority(c) + 1);
+				continue;
+			}
 
-/*! \brief Add watcher for extended extension states with destructor */
-int ast_extension_state_add_destroy_extended(const char *context, const char *exten,
-	ast_state_cb_type change_cb, ast_state_cb_destroy_type destroy_cb, void *data)
-{
-	return extension_state_add_destroy(context, exten, change_cb, destroy_cb, data, 1);
-}
+			/* Check softhangup flags. */
+			if (ast_channel_softhangup_internal_flag(c) & AST_SOFTHANGUP_ASYNCGOTO) {
+				ast_channel_clear_softhangup(c, AST_SOFTHANGUP_ASYNCGOTO);
+				continue;
+			}
+			if (ast_channel_softhangup_internal_flag(c) & AST_SOFTHANGUP_TIMEOUT) {
+				if (ast_exists_extension(c, ast_channel_context(c), "T", 1,
+					S_COR(ast_channel_caller(c)->id.number.valid, ast_channel_caller(c)->id.number.str, NULL))) {
+					set_ext_pri(c, "T", 1);
+					/* If the AbsoluteTimeout is not reset to 0, we'll get an infinite loop */
+					memset(ast_channel_whentohangup(c), 0, sizeof(*ast_channel_whentohangup(c)));
+					ast_channel_clear_softhangup(c, AST_SOFTHANGUP_TIMEOUT);
+					continue;
+				} else if (ast_exists_extension(c, ast_channel_context(c), "e", 1,
+					S_COR(ast_channel_caller(c)->id.number.valid, ast_channel_caller(c)->id.number.str, NULL))) {
+					raise_exception(c, "ABSOLUTETIMEOUT", 1);
+					/* If the AbsoluteTimeout is not reset to 0, we'll get an infinite loop */
+					memset(ast_channel_whentohangup(c), 0, sizeof(*ast_channel_whentohangup(c)));
+					ast_channel_clear_softhangup(c, AST_SOFTHANGUP_TIMEOUT);
+					continue;
+				}
 
-/*! \brief Add watcher for extended extension states */
-int ast_extension_state_add_extended(const char *context, const char *exten,
-	ast_state_cb_type change_cb, void *data)
-{
-	return extension_state_add_destroy(context, exten, change_cb, NULL, data, 1);
-}
+				/* Call timed out with no special extension to jump to. */
+				error = 1;
+				break;
+			}
+			ast_debug(1, "Extension %s, priority %d returned normally even though call was hung up\n",
+				ast_channel_exten(c), ast_channel_priority(c));
+			error = 1;
+			break;
+		} /* end while  - from here on we can use 'break' to go out */
+		if (found && res) {
+			/* Something bad happened, or a hangup has been requested. */
+			if (strchr("0123456789ABCDEF*#", res)) {
+				ast_debug(1, "Oooh, got something to jump out with ('%c')!\n", res);
+				pos = 0;
+				dst_exten[pos++] = digit = res;
+				dst_exten[pos] = '\0';
+			} else if (res == AST_PBX_INCOMPLETE) {
+				ast_debug(1, "Spawn extension (%s,%s,%d) exited INCOMPLETE on '%s'\n", ast_channel_context(c), ast_channel_exten(c), ast_channel_priority(c), ast_channel_name(c));
+				ast_verb(2, "Spawn extension (%s, %s, %d) exited INCOMPLETE on '%s'\n", ast_channel_context(c), ast_channel_exten(c), ast_channel_priority(c), ast_channel_name(c));
 
-/*! \brief Find Hint by callback id */
-static int find_hint_by_cb_id(void *obj, void *arg, int flags)
-{
-	struct ast_state_cb *state_cb;
-	const struct ast_hint *hint = obj;
-	int *id = arg;
+				/* Don't cycle on incomplete - this will happen if the only extension that matches is our "incomplete" extension */
+				if (!ast_matchmore_extension(c, ast_channel_context(c), ast_channel_exten(c), 1,
+					S_COR(ast_channel_caller(c)->id.number.valid, ast_channel_caller(c)->id.number.str, NULL))) {
+					invalid = 1;
+				} else {
+					ast_copy_string(dst_exten, ast_channel_exten(c), sizeof(dst_exten));
+					digit = 1;
+					pos = strlen(dst_exten);
+				}
+			} else {
+				ast_debug(1, "Spawn extension (%s,%s,%d) exited non-zero on '%s'\n", ast_channel_context(c), ast_channel_exten(c), ast_channel_priority(c), ast_channel_name(c));
+				ast_verb(2, "Spawn extension (%s, %s, %d) exited non-zero on '%s'\n", ast_channel_context(c), ast_channel_exten(c), ast_channel_priority(c), ast_channel_name(c));
 
-	if ((state_cb = ao2_find(hint->callbacks, id, 0))) {
-		ao2_ref(state_cb, -1);
-		return CMP_MATCH | CMP_STOP;
-	}
+				if ((res == AST_PBX_ERROR)
+					&& ast_exists_extension(c, ast_channel_context(c), "e", 1,
+						S_COR(ast_channel_caller(c)->id.number.valid, ast_channel_caller(c)->id.number.str, NULL))) {
+					/* if we are already on the 'e' exten, don't jump to it again */
+					if (!strcmp(ast_channel_exten(c), "e")) {
+						ast_verb(2, "Spawn extension (%s, %s, %d) exited ERROR while already on 'e' exten on '%s'\n", ast_channel_context(c), ast_channel_exten(c), ast_channel_priority(c), ast_channel_name(c));
+						error = 1;
+					} else {
+						raise_exception(c, "ERROR", 1);
+						continue;
+					}
+				}
 
-	return 0;
-}
+				if (ast_channel_softhangup_internal_flag(c) & AST_SOFTHANGUP_ASYNCGOTO) {
+					ast_channel_clear_softhangup(c, AST_SOFTHANGUP_ASYNCGOTO);
+					continue;
+				}
+				if (ast_channel_softhangup_internal_flag(c) & AST_SOFTHANGUP_TIMEOUT) {
+					if (ast_exists_extension(c, ast_channel_context(c), "T", 1,
+						S_COR(ast_channel_caller(c)->id.number.valid, ast_channel_caller(c)->id.number.str, NULL))) {
+						set_ext_pri(c, "T", 1);
+						/* If the AbsoluteTimeout is not reset to 0, we'll get an infinite loop */
+						memset(ast_channel_whentohangup(c), 0, sizeof(*ast_channel_whentohangup(c)));
+						ast_channel_clear_softhangup(c, AST_SOFTHANGUP_TIMEOUT);
+						continue;
+					} else if (ast_exists_extension(c, ast_channel_context(c), "e", 1,
+						S_COR(ast_channel_caller(c)->id.number.valid, ast_channel_caller(c)->id.number.str, NULL))) {
+						raise_exception(c, "ABSOLUTETIMEOUT", 1);
+						/* If the AbsoluteTimeout is not reset to 0, we'll get an infinite loop */
+						memset(ast_channel_whentohangup(c), 0, sizeof(*ast_channel_whentohangup(c)));
+						ast_channel_clear_softhangup(c, AST_SOFTHANGUP_TIMEOUT);
+						continue;
+					}
+					/* Call timed out with no special extension to jump to. */
+				}
+				error = 1;
+				break;
+			}
+		}
+		if (error)
+			break;
 
-/*! \brief  ast_extension_state_del: Remove a watcher from the callback list */
-int ast_extension_state_del(int id, ast_state_cb_type change_cb)
-{
-	struct ast_state_cb *p_cur;
-	int ret = -1;
+		/*!\note
+		 * We get here on a failure of some kind:  non-existing extension or
+		 * hangup.  We have options, here.  We can either catch the failure
+		 * and continue, or we can drop out entirely. */
 
-	if (!id) {	/* id == 0 is a callback without extension */
-		if (!change_cb) {
-			return ret;
-		}
-		p_cur = ao2_find(statecbs, change_cb, OBJ_UNLINK);
-		if (p_cur) {
-			ret = 0;
-			ao2_ref(p_cur, -1);
-		}
-	} else { /* callback with extension, find the callback based on ID */
-		struct ast_hint *hint;
+		if (invalid
+			|| (ast_strlen_zero(dst_exten) &&
+				!ast_exists_extension(c, ast_channel_context(c), ast_channel_exten(c), 1,
+				S_COR(ast_channel_caller(c)->id.number.valid, ast_channel_caller(c)->id.number.str, NULL)))) {
+			/*!\note
+			 * If there is no match at priority 1, it is not a valid extension anymore.
+			 * Try to continue at "i" (for invalid) or "e" (for exception) or exit if
+			 * neither exist.
+			 */
+			if (ast_exists_extension(c, ast_channel_context(c), "i", 1,
+				S_COR(ast_channel_caller(c)->id.number.valid, ast_channel_caller(c)->id.number.str, NULL))) {
+				ast_verb(3, "Channel '%s' sent to invalid extension: context,exten,priority=%s,%s,%d\n",
+					ast_channel_name(c), ast_channel_context(c), ast_channel_exten(c), ast_channel_priority(c));
+				pbx_builtin_setvar_helper(c, "INVALID_EXTEN", ast_channel_exten(c));
+				set_ext_pri(c, "i", 1);
+			} else if (ast_exists_extension(c, ast_channel_context(c), "e", 1,
+				S_COR(ast_channel_caller(c)->id.number.valid, ast_channel_caller(c)->id.number.str, NULL))) {
+				raise_exception(c, "INVALID", 1);
+			} else {
+				ast_log(LOG_WARNING, "Channel '%s' sent to invalid extension but no invalid handler: context,exten,priority=%s,%s,%d\n",
+					ast_channel_name(c), ast_channel_context(c), ast_channel_exten(c), ast_channel_priority(c));
+				error = 1; /* we know what to do with it */
+				break;
+			}
+		} else if (ast_channel_softhangup_internal_flag(c) & AST_SOFTHANGUP_TIMEOUT) {
+			/* If we get this far with AST_SOFTHANGUP_TIMEOUT, then we know that the "T" extension is next. */
+			ast_channel_clear_softhangup(c, AST_SOFTHANGUP_TIMEOUT);
+		} else {	/* keypress received, get more digits for a full extension */
+			int waittime = 0;
+			if (digit)
+				waittime = ast_channel_pbx(c)->dtimeoutms;
+			else if (!autofallthrough)
+				waittime = ast_channel_pbx(c)->rtimeoutms;
+			if (!waittime) {
+				const char *status = pbx_builtin_getvar_helper(c, "DIALSTATUS");
+				if (!status)
+					status = "UNKNOWN";
+				ast_verb(3, "Auto fallthrough, channel '%s' status is '%s'\n", ast_channel_name(c), status);
+				if (!strcasecmp(status, "CONGESTION"))
+					res = indicate_congestion(c, "10");
+				else if (!strcasecmp(status, "CHANUNAVAIL"))
+					res = indicate_congestion(c, "10");
+				else if (!strcasecmp(status, "BUSY"))
+					res = indicate_busy(c, "10");
+				error = 1; /* XXX disable message */
+				break;	/* exit from the 'for' loop */
+			}
 
-		ao2_lock(hints);/* Locked to hold off ast_merge_contexts_and_delete */
-		hint = ao2_callback(hints, 0, find_hint_by_cb_id, &id);
-		if (hint) {
-			p_cur = ao2_find(hint->callbacks, &id, OBJ_UNLINK);
-			if (p_cur) {
-				ret = 0;
-				ao2_ref(p_cur, -1);
+			if (collect_digits(c, waittime, dst_exten, sizeof(dst_exten), pos))
+				break;
+			if (res == AST_PBX_INCOMPLETE && ast_strlen_zero(&dst_exten[pos]))
+				timeout = 1;
+			if (!timeout
+				&& ast_exists_extension(c, ast_channel_context(c), dst_exten, 1,
+					S_COR(ast_channel_caller(c)->id.number.valid, ast_channel_caller(c)->id.number.str, NULL))) { /* Prepare the next cycle */
+				set_ext_pri(c, dst_exten, 1);
+			} else {
+				/* No such extension */
+				if (!timeout && !ast_strlen_zero(dst_exten)) {
+					/* An invalid extension */
+					if (ast_exists_extension(c, ast_channel_context(c), "i", 1,
+						S_COR(ast_channel_caller(c)->id.number.valid, ast_channel_caller(c)->id.number.str, NULL))) {
+						ast_verb(3, "Invalid extension '%s' in context '%s' on %s\n", dst_exten, ast_channel_context(c), ast_channel_name(c));
+						pbx_builtin_setvar_helper(c, "INVALID_EXTEN", dst_exten);
+						set_ext_pri(c, "i", 1);
+					} else if (ast_exists_extension(c, ast_channel_context(c), "e", 1,
+						S_COR(ast_channel_caller(c)->id.number.valid, ast_channel_caller(c)->id.number.str, NULL))) {
+						raise_exception(c, "INVALID", 1);
+					} else {
+						ast_log(LOG_WARNING,
+							"Invalid extension '%s', but no rule 'i' or 'e' in context '%s'\n",
+							dst_exten, ast_channel_context(c));
+						found = 1; /* XXX disable message */
+						break;
+					}
+				} else {
+					/* A simple timeout */
+					if (ast_exists_extension(c, ast_channel_context(c), "t", 1,
+						S_COR(ast_channel_caller(c)->id.number.valid, ast_channel_caller(c)->id.number.str, NULL))) {
+						ast_verb(3, "Timeout on %s\n", ast_channel_name(c));
+						set_ext_pri(c, "t", 1);
+					} else if (ast_exists_extension(c, ast_channel_context(c), "e", 1,
+						S_COR(ast_channel_caller(c)->id.number.valid, ast_channel_caller(c)->id.number.str, NULL))) {
+						raise_exception(c, "RESPONSETIMEOUT", 1);
+					} else {
+						ast_log(LOG_WARNING,
+							"Timeout, but no rule 't' or 'e' in context '%s'\n",
+							ast_channel_context(c));
+						found = 1; /* XXX disable message */
+						break;
+					}
+				}
 			}
-			ao2_ref(hint, -1);
 		}
-		ao2_unlock(hints);
 	}
 
-	return ret;
-}
+	if (!found && !error) {
+		ast_log(LOG_WARNING, "Don't know what to do with '%s'\n", ast_channel_name(c));
+	}
 
-static int hint_id_cmp(void *obj, void *arg, int flags)
-{
-	const struct ast_state_cb *cb = obj;
-	int *id = arg;
+	if (!args || !args->no_hangup_chan) {
+		ast_softhangup(c, AST_SOFTHANGUP_APPUNLOAD);
+		if (!ast_test_flag(ast_channel_flags(c), AST_FLAG_BRIDGE_HANGUP_RUN)
+			&& ast_exists_extension(c, ast_channel_context(c), "h", 1,
+				S_COR(ast_channel_caller(c)->id.number.valid,
+					ast_channel_caller(c)->id.number.str, NULL))) {
+			ast_pbx_h_exten_run(c, ast_channel_context(c));
+		}
+		ast_pbx_hangup_handler_run(c);
+	}
 
-	return (cb->id == *id) ? CMP_MATCH | CMP_STOP : 0;
+	ast_set2_flag(ast_channel_flags(c), autoloopflag, AST_FLAG_IN_AUTOLOOP);
+	ast_clear_flag(ast_channel_flags(c), AST_FLAG_BRIDGE_HANGUP_RUN); /* from one round to the next, make sure this gets cleared */
+	pbx_destroy(ast_channel_pbx(c));
+	ast_channel_pbx_set(c, NULL);
+
+	if (!args || !args->no_hangup_chan) {
+		ast_hangup(c);
+	}
+
+	return AST_PBX_SUCCESS;
 }
 
 /*!
- * \internal
- * \brief Destroy the given hint object.
- *
- * \param obj Hint to destroy.
- *
- * \return Nothing
- */
-static void destroy_hint(void *obj)
+ * \brief Increase call count for channel
+ * \retval 0 on success
+ * \retval non-zero if a configured limit (maxcalls, maxload, minmemfree) was reached
+*/
+static int increase_call_count(const struct ast_channel *c)
 {
-	struct ast_hint *hint = obj;
-	int i;
-
-	if (hint->callbacks) {
-		struct ast_state_cb *state_cb;
-		const char *context_name;
-		const char *exten_name;
+	int failed = 0;
+	double curloadavg;
+#if defined(HAVE_SYSINFO)
+	long curfreemem;
+	struct sysinfo sys_info;
+#endif
 
-		if (hint->exten) {
-			context_name = ast_get_context_name(ast_get_extension_context(hint->exten));
-			exten_name = ast_get_extension_name(hint->exten);
-			hint->exten = NULL;
-		} else {
-			/* The extension has already been destroyed */
-			context_name = hint->context_name;
-			exten_name = hint->exten_name;
-		}
-		hint->laststate = AST_EXTENSION_DEACTIVATED;
-		while ((state_cb = ao2_callback(hint->callbacks, OBJ_UNLINK, NULL, NULL))) {
-			/* Notify with -1 and remove all callbacks */
-			execute_state_callback(state_cb->change_cb,
-				context_name,
-				exten_name,
-				state_cb->data,
-				AST_HINT_UPDATE_DEVICE,
-				hint,
-				NULL);
-			ao2_ref(state_cb, -1);
+	ast_mutex_lock(&maxcalllock);
+	if (ast_option_maxcalls) {
+		if (countcalls >= ast_option_maxcalls) {
+			ast_log(LOG_WARNING, "Maximum call limit of %d calls exceeded by '%s'!\n", ast_option_maxcalls, ast_channel_name(c));
+			failed = -1;
 		}
-		ao2_ref(hint->callbacks, -1);
 	}
-
-	for (i = 0; i < AST_VECTOR_SIZE(&hint->devices); i++) {
-		char *device = AST_VECTOR_GET(&hint->devices, i);
-		ast_free(device);
+	if (ast_option_maxload) {
+		getloadavg(&curloadavg, 1);
+		if (curloadavg >= ast_option_maxload) {
+			ast_log(LOG_WARNING, "Maximum loadavg limit of %f load exceeded by '%s' (currently %f)!\n", ast_option_maxload, ast_channel_name(c), curloadavg);
+			failed = -1;
+		}
 	}
-	AST_VECTOR_FREE(&hint->devices);
-	ast_free(hint->last_presence_subtype);
-	ast_free(hint->last_presence_message);
-}
-
-/*! \brief Remove hint from extension */
-static int ast_remove_hint(struct ast_exten *e)
-{
-	/* Cleanup the Notifys if hint is removed */
-	struct ast_hint *hint;
-
-	if (!e) {
-		return -1;
+#if defined(HAVE_SYSINFO)
+	if (option_minmemfree) {
+		if (!sysinfo(&sys_info)) {
+			/* make sure that the free system memory is above the configured low watermark
+			 * convert the amount of freeram from mem_units to MB */
+			curfreemem = sys_info.freeram * sys_info.mem_unit;
+			curfreemem /= 1024 * 1024;
+			if (curfreemem < option_minmemfree) {
+				ast_log(LOG_WARNING, "Available system memory (~%ldMB) is below the configured low watermark (%ldMB)\n", curfreemem, option_minmemfree);
+				failed = -1;
+			}
+		}
 	}
+#endif
 
-	hint = ao2_find(hints, e, OBJ_UNLINK);
-	if (!hint) {
-		return -1;
+	if (!failed) {
+		countcalls++;
+		totalcalls++;
 	}
+	ast_mutex_unlock(&maxcalllock);
 
-	remove_hintdevice(hint);
+	return failed;
+}
 
-	/*
-	 * The extension is being destroyed so we must save some
-	 * information to notify that the extension is deactivated.
-	 */
-	ao2_lock(hint);
-	ast_copy_string(hint->context_name,
-		ast_get_context_name(ast_get_extension_context(hint->exten)),
-		sizeof(hint->context_name));
-	ast_copy_string(hint->exten_name, ast_get_extension_name(hint->exten),
-		sizeof(hint->exten_name));
-	hint->exten = NULL;
-	ao2_unlock(hint);
+static void decrease_call_count(void)
+{
+	ast_mutex_lock(&maxcalllock);
+	if (countcalls > 0)
+		countcalls--;
+	ast_mutex_unlock(&maxcalllock);
+}
 
-	ao2_ref(hint, -1);
+static void destroy_exten(struct ast_exten *e)
+{
+	if (e->priority == PRIORITY_HINT)
+		ast_remove_hint(e);
 
-	return 0;
+	if (e->peer_table)
+		ast_hashtab_destroy(e->peer_table,0);
+	if (e->peer_label_table)
+		ast_hashtab_destroy(e->peer_label_table, 0);
+	if (e->datad)
+		e->datad(e->data);
+	ast_free(e);
 }
 
-/*! \brief Add hint to hint list, check initial extension state */
-static int ast_add_hint(struct ast_exten *e)
+static void *pbx_thread(void *data)
 {
-	struct ast_hint *hint_new;
-	struct ast_hint *hint_found;
-	char *message = NULL;
-	char *subtype = NULL;
-	int presence_state;
+	/* Oh joyeous kernel, we're a new thread, with nothing to do but
+	   answer this channel and get it going.
+	*/
+	/* NOTE:
+	   The launcher of this function _MUST_ increment 'countcalls'
+	   before invoking the function; it will be decremented when the
+	   PBX has finished running on the channel
+	 */
+	struct ast_channel *c = data;
 
-	if (!e) {
-		return -1;
-	}
+	__ast_pbx_run(c, NULL);
+	decrease_call_count();
 
-	/*
-	 * We must create the hint we wish to add before determining if
-	 * it is already in the hints container to avoid possible
-	 * deadlock when getting the current extension state.
-	 */
-	hint_new = ao2_alloc(sizeof(*hint_new), destroy_hint);
-	if (!hint_new) {
-		return -1;
-	}
-	AST_VECTOR_INIT(&hint_new->devices, 8);
+	pthread_exit(NULL);
 
-	/* Initialize new hint. */
-	hint_new->callbacks = ao2_container_alloc(1, NULL, hint_id_cmp);
-	if (!hint_new->callbacks) {
-		ao2_ref(hint_new, -1);
-		return -1;
-	}
-	hint_new->exten = e;
-	if (strstr(e->app, "${") && e->exten[0] == '_') {
-		/* The hint is dynamic and hasn't been evaluted yet */
-		hint_new->laststate = AST_DEVICE_INVALID;
-		hint_new->last_presence_state = AST_PRESENCE_INVALID;
-	} else {
-		hint_new->laststate = ast_extension_state2(e, NULL);
-		if ((presence_state = extension_presence_state_helper(e, &subtype, &message)) > 0) {
-			hint_new->last_presence_state = presence_state;
-			hint_new->last_presence_subtype = subtype;
-			hint_new->last_presence_message = message;
-			message = subtype = NULL;
-		}
-	}
+	return NULL;
+}
 
-	/* Prevent multiple add hints from adding the same hint at the same time. */
-	ao2_lock(hints);
+enum ast_pbx_result ast_pbx_start(struct ast_channel *c)
+{
+	pthread_t t;
 
-	/* Search if hint exists, do nothing */
-	hint_found = ao2_find(hints, e, 0);
-	if (hint_found) {
-		ao2_ref(hint_found, -1);
-		ao2_unlock(hints);
-		ao2_ref(hint_new, -1);
-		ast_debug(2, "HINTS: Not re-adding existing hint %s: %s\n",
-			ast_get_extension_name(e), ast_get_extension_app(e));
-		return -1;
+	if (!c) {
+		ast_log(LOG_WARNING, "Asked to start thread on NULL channel?\n");
+		return AST_PBX_FAILED;
 	}
 
-	/* Add new hint to the hints container */
-	ast_debug(2, "HINTS: Adding hint %s: %s\n",
-		ast_get_extension_name(e), ast_get_extension_app(e));
-	ao2_link(hints, hint_new);
-	if (add_hintdevice(hint_new, ast_get_extension_app(e))) {
-		ast_log(LOG_WARNING, "Could not add devices for hint: %s@%s.\n",
-			ast_get_extension_name(e),
-			ast_get_context_name(ast_get_extension_context(e)));
+	if (!ast_test_flag(&ast_options, AST_OPT_FLAG_FULLY_BOOTED)) {
+		ast_log(LOG_WARNING, "PBX requires Asterisk to be fully booted\n");
+		return AST_PBX_FAILED;
 	}
 
-	/* if not dynamic */
-	if (!(strstr(e->app, "${") && e->exten[0] == '_')) {
-		struct ast_state_cb *state_cb;
-		struct ao2_iterator cb_iter;
+	if (increase_call_count(c))
+		return AST_PBX_CALL_LIMIT;
 
-		/* For general callbacks */
-		cb_iter = ao2_iterator_init(statecbs, 0);
-		for (; (state_cb = ao2_iterator_next(&cb_iter)); ao2_ref(state_cb, -1)) {
-			execute_state_callback(state_cb->change_cb,
-					ast_get_context_name(ast_get_extension_context(e)),
-					ast_get_extension_name(e),
-					state_cb->data,
-					AST_HINT_UPDATE_DEVICE,
-					hint_new,
-					NULL);
-		}
-		ao2_iterator_destroy(&cb_iter);
+	/* Start a new thread, and get something handling this channel. */
+	if (ast_pthread_create_detached(&t, NULL, pbx_thread, c)) {
+		ast_log(LOG_WARNING, "Failed to create new channel thread\n");
+		decrease_call_count();
+		return AST_PBX_FAILED;
 	}
-	ao2_unlock(hints);
-	ao2_ref(hint_new, -1);
 
-	return 0;
+	return AST_PBX_SUCCESS;
 }
 
-/*! \brief Change hint for an extension */
-static int ast_change_hint(struct ast_exten *oe, struct ast_exten *ne)
+enum ast_pbx_result ast_pbx_run_args(struct ast_channel *c, struct ast_pbx_args *args)
 {
-	struct ast_str *hint_app;
-	struct ast_hint *hint;
-	int previous_device_state;
-	char *previous_message = NULL;
-	char *message = NULL;
-	char *previous_subtype = NULL;
-	char *subtype = NULL;
-	int previous_presence_state;
-	int presence_state;
-	int presence_state_changed = 0;
+	enum ast_pbx_result res = AST_PBX_SUCCESS;
 
-	if (!oe || !ne) {
-		return -1;
+	if (!ast_test_flag(&ast_options, AST_OPT_FLAG_FULLY_BOOTED)) {
+		ast_log(LOG_WARNING, "PBX requires Asterisk to be fully booted\n");
+		return AST_PBX_FAILED;
 	}
 
-	hint_app = ast_str_create(1024);
-	if (!hint_app) {
-		return -1;
+	if (increase_call_count(c)) {
+		return AST_PBX_CALL_LIMIT;
 	}
 
-	ast_mutex_lock(&context_merge_lock); /* Hold off ast_merge_contexts_and_delete and state changes */
-
-	ao2_lock(hints);/* Locked to hold off others while we move the hint around. */
+	res = __ast_pbx_run(c, args);
 
-	/*
-	 * Unlink the hint from the hints container as the extension
-	 * name (which is the hash value) could change.
-	 */
-	hint = ao2_find(hints, oe, OBJ_UNLINK);
-	if (!hint) {
-		ao2_unlock(hints);
-		ast_mutex_unlock(&context_merge_lock);
-		ast_free(hint_app);
-		return -1;
-	}
+	decrease_call_count();
 
-	remove_hintdevice(hint);
+	return res;
+}
 
-	/* Update the hint and put it back in the hints container. */
-	ao2_lock(hint);
-	hint->exten = ne;
+enum ast_pbx_result ast_pbx_run(struct ast_channel *c)
+{
+	return ast_pbx_run_args(c, NULL);
+}
 
-	/* Store the previous states so we know whether we need to notify state callbacks */
-	previous_device_state = hint->laststate;
-	previous_presence_state = hint->last_presence_state;
-	previous_message = hint->last_presence_message;
-	previous_subtype = hint->last_presence_subtype;
+int ast_active_calls(void)
+{
+	return countcalls;
+}
 
-	/* Update the saved device and presence state with the new extension */
-	hint->laststate = ast_extension_state2(ne, NULL);
-	hint->last_presence_state = AST_PRESENCE_INVALID;
-	hint->last_presence_subtype = NULL;
-	hint->last_presence_message = NULL;
+int ast_processed_calls(void)
+{
+	return totalcalls;
+}
 
-	presence_state = extension_presence_state_helper(ne, &subtype, &message);
-	if (presence_state > 0) {
-		hint->last_presence_state = presence_state;
-		hint->last_presence_subtype = subtype;
-		hint->last_presence_message = message;
-	}
+int pbx_set_autofallthrough(int newval)
+{
+	int oldval = autofallthrough;
+	autofallthrough = newval;
+	return oldval;
+}
 
-	ao2_unlock(hint);
+int pbx_set_extenpatternmatchnew(int newval)
+{
+	int oldval = extenpatternmatchnew;
+	extenpatternmatchnew = newval;
+	return oldval;
+}
 
-	ao2_link(hints, hint);
-	if (add_hintdevice(hint, ast_get_extension_app(ne))) {
-		ast_log(LOG_WARNING, "Could not add devices for hint: %s@%s.\n",
-			ast_get_extension_name(ne),
-			ast_get_context_name(ast_get_extension_context(ne)));
+void pbx_set_overrideswitch(const char *newval)
+{
+	if (overrideswitch) {
+		ast_free(overrideswitch);
 	}
+	if (!ast_strlen_zero(newval)) {
+		overrideswitch = ast_strdup(newval);
+	} else {
+		overrideswitch = NULL;
+	}
+}
 
-	ao2_unlock(hints);
-
-	/* Locking for state callbacks is respected here and only the context_merge_lock lock is
-	 * held during the state callback invocation. This will stop the normal state callback
-	 * thread from being able to handle incoming state changes if they occur.
-	 */
+/*!
+ * \brief lookup for a context with a given name,
+ * \retval found context or NULL if not found.
+ */
+static struct ast_context *find_context(const char *context)
+{
+	struct fake_context item;
 
-	/* Determine if presence state has changed due to the change of the hint extension */
-	if ((hint->last_presence_state != previous_presence_state) ||
-		strcmp(S_OR(hint->last_presence_subtype, ""), S_OR(previous_subtype, "")) ||
-		strcmp(S_OR(hint->last_presence_message, ""), S_OR(previous_message, ""))) {
-		presence_state_changed = 1;
-	}
+	ast_copy_string(item.name, context, sizeof(item.name));
 
-	/* Notify any existing state callbacks if the device or presence state has changed */
-	if ((hint->laststate != previous_device_state) || presence_state_changed) {
-		struct ao2_iterator cb_iter;
-		struct ast_state_cb *state_cb;
-		struct ao2_container *device_state_info;
-		int first_extended_cb_call = 1;
+	return ast_hashtab_lookup(contexts_table, &item);
+}
 
-		/* For general callbacks */
-		cb_iter = ao2_iterator_init(statecbs, 0);
-		for (; (state_cb = ao2_iterator_next(&cb_iter)); ao2_ref(state_cb, -1)) {
-			/* Unlike the normal state callbacks since something has explicitly provided us this extension
-			 * it will remain valid and unchanged for the lifetime of this function invocation.
-			 */
-			if (hint->laststate != previous_device_state) {
-				execute_state_callback(state_cb->change_cb,
-					ast_get_context_name(ast_get_extension_context(ne)),
-					ast_get_extension_name(ne),
-					state_cb->data,
-					AST_HINT_UPDATE_DEVICE,
-					hint,
-					NULL);
-			}
-			if (presence_state_changed) {
-				execute_state_callback(state_cb->change_cb,
-					ast_get_context_name(ast_get_extension_context(ne)),
-					ast_get_extension_name(ne),
-					state_cb->data,
-					AST_HINT_UPDATE_PRESENCE,
-					hint,
-					NULL);
-			}
-		}
-		ao2_iterator_destroy(&cb_iter);
+/*!
+ * \brief lookup for a context with a given name,
+ * \retval with conlock held if found.
+ * \retval NULL if not found.
+ */
+static struct ast_context *find_context_locked(const char *context)
+{
+	struct ast_context *c;
+	struct fake_context item;
 
-		ast_str_set(&hint_app, 0, "%s", ast_get_extension_app(ne));
+	ast_copy_string(item.name, context, sizeof(item.name));
 
-		device_state_info = alloc_device_state_info();
-		ast_extension_state3(hint_app, device_state_info);
+	ast_rdlock_contexts();
+	c = ast_hashtab_lookup(contexts_table, &item);
+	if (!c) {
+		ast_unlock_contexts();
+	}
 
-		/* For extension callbacks */
-		cb_iter = ao2_iterator_init(hint->callbacks, 0);
-		for (; (state_cb = ao2_iterator_next(&cb_iter)); ao2_ref(state_cb, -1)) {
-			if (hint->laststate != previous_device_state) {
-				if (state_cb->extended && first_extended_cb_call) {
-				/* Fill detailed device_state_info now that we know it is used by extd. callback */
-					first_extended_cb_call = 0;
-					get_device_state_causing_channels(device_state_info);
-				}
-				execute_state_callback(state_cb->change_cb,
-					ast_get_context_name(ast_get_extension_context(ne)),
-					ast_get_extension_name(ne),
-					state_cb->data,
-					AST_HINT_UPDATE_DEVICE,
-					hint,
-					state_cb->extended ? device_state_info : NULL);
-			}
-			if (presence_state_changed) {
-				execute_state_callback(state_cb->change_cb,
-					ast_get_context_name(ast_get_extension_context(ne)),
-					ast_get_extension_name(ne),
-					state_cb->data,
-					AST_HINT_UPDATE_PRESENCE,
-					hint,
-					NULL);
-			}
-		}
-		ao2_iterator_destroy(&cb_iter);
+	return c;
+}
 
-		ao2_cleanup(device_state_info);
+/*!
+ * \brief Remove included contexts.
+ * This function locks contexts list by &conlist, search for the right context
+ * structure, leave context list locked and call ast_context_remove_include2
+ * which removes include, unlock contexts list and return ...
+ */
+int ast_context_remove_include(const char *context, const char *include, const char *registrar)
+{
+	int ret = -1;
+	struct ast_context *c;
+
+	c = find_context_locked(context);
+	if (c) {
+		/* found, remove include from this context ... */
+		ret = ast_context_remove_include2(c, include, registrar);
+		ast_unlock_contexts();
 	}
+	return ret;
+}
 
-	ao2_ref(hint, -1);
+/*!
+ * \brief Locks context, remove included contexts, unlocks context.
+ * When we call this function, &conlock lock must be locked, because when
+ * we giving *con argument, some process can remove/change this context
+ * and after that there can be segfault.
+ *
+ * \retval 0 on success.
+ * \retval -1 on failure.
+ */
+int ast_context_remove_include2(struct ast_context *con, const char *include, const char *registrar)
+{
+	struct ast_include *i, *pi = NULL;
+	int ret = -1;
 
-	ast_mutex_unlock(&context_merge_lock);
+	ast_wrlock_context(con);
 
-	ast_free(hint_app);
-	ast_free(previous_message);
-	ast_free(previous_subtype);
+	/* find our include */
+	for (i = con->includes; i; pi = i, i = i->next) {
+		if (!strcmp(i->name, include) &&
+				(!registrar || !strcmp(i->registrar, registrar))) {
+			/* remove from list */
+			ast_verb(3, "Removing inclusion of context '%s' in context '%s; registrar=%s'\n", include, ast_get_context_name(con), registrar);
+			if (pi)
+				pi->next = i->next;
+			else
+				con->includes = i->next;
+			/* free include and return */
+			ast_destroy_timing(&(i->timing));
+			ast_free(i);
+			ret = 0;
+			break;
+		}
+	}
 
-	return 0;
-}
+	ast_unlock_context(con);
 
+	return ret;
+}
 
-/*! \brief Get hint for channel */
-int ast_get_hint(char *hint, int hintsize, char *name, int namesize, struct ast_channel *c, const char *context, const char *exten)
+/*!
+ * \note This function locks contexts list by &conlist, search for the rigt context
+ * structure, leave context list locked and call ast_context_remove_switch2
+ * which removes switch, unlock contexts list and return ...
+ */
+int ast_context_remove_switch(const char *context, const char *sw, const char *data, const char *registrar)
 {
-	struct ast_exten *e = ast_hint_extension(c, context, exten);
+	int ret = -1; /* default error return */
+	struct ast_context *c;
 
-	if (e) {
-		if (hint)
-			ast_copy_string(hint, ast_get_extension_app(e), hintsize);
-		if (name) {
-			const char *tmp = ast_get_extension_app_data(e);
-			if (tmp)
-				ast_copy_string(name, tmp, namesize);
-		}
-		return -1;
+	c = find_context_locked(context);
+	if (c) {
+		/* remove switch from this context ... */
+		ret = ast_context_remove_switch2(c, sw, data, registrar);
+		ast_unlock_contexts();
 	}
-	return 0;
+	return ret;
 }
 
-/*! \brief Get hint for channel */
-int ast_str_get_hint(struct ast_str **hint, ssize_t hintsize, struct ast_str **name, ssize_t namesize, struct ast_channel *c, const char *context, const char *exten)
+/*!
+ * \brief This function locks given context, removes switch, unlock context and
+ * return.
+ * \note When we call this function, &conlock lock must be locked, because when
+ * we giving *con argument, some process can remove/change this context
+ * and after that there can be segfault.
+ *
+ */
+int ast_context_remove_switch2(struct ast_context *con, const char *sw, const char *data, const char *registrar)
 {
-	struct ast_exten *e = ast_hint_extension(c, context, exten);
+	struct ast_sw *i;
+	int ret = -1;
 
-	if (!e) {
-		return 0;
-	}
+	ast_wrlock_context(con);
 
-	if (hint) {
-		ast_str_set(hint, hintsize, "%s", ast_get_extension_app(e));
-	}
-	if (name) {
-		const char *tmp = ast_get_extension_app_data(e);
-		if (tmp) {
-			ast_str_set(name, namesize, "%s", tmp);
+	/* walk switches */
+	AST_LIST_TRAVERSE_SAFE_BEGIN(&con->alts, i, list) {
+		if (!strcmp(i->name, sw) && !strcmp(i->data, data) &&
+			(!registrar || !strcmp(i->registrar, registrar))) {
+			/* found, remove from list */
+			ast_verb(3, "Removing switch '%s' from context '%s; registrar=%s'\n", sw, ast_get_context_name(con), registrar);
+			AST_LIST_REMOVE_CURRENT(list);
+			ast_free(i); /* free switch and return */
+			ret = 0;
+			break;
 		}
 	}
-	return -1;
-}
+	AST_LIST_TRAVERSE_SAFE_END;
 
-int ast_exists_extension(struct ast_channel *c, const char *context, const char *exten, int priority, const char *callerid)
-{
-	return pbx_extension_helper(c, NULL, context, exten, priority, NULL, callerid, E_MATCH, 0, 0);
-}
+	ast_unlock_context(con);
 
-int ast_findlabel_extension(struct ast_channel *c, const char *context, const char *exten, const char *label, const char *callerid)
-{
-	return pbx_extension_helper(c, NULL, context, exten, 0, label, callerid, E_FINDLABEL, 0, 0);
+	return ret;
 }
 
-int ast_findlabel_extension2(struct ast_channel *c, struct ast_context *con, const char *exten, const char *label, const char *callerid)
+/*! \note This function will lock conlock. */
+int ast_context_remove_extension(const char *context, const char *extension, int priority, const char *registrar)
 {
-	return pbx_extension_helper(c, con, NULL, exten, 0, label, callerid, E_FINDLABEL, 0, 0);
+	return ast_context_remove_extension_callerid(context, extension, priority, NULL, AST_EXT_MATCHCID_ANY, registrar);
 }
 
-int ast_canmatch_extension(struct ast_channel *c, const char *context, const char *exten, int priority, const char *callerid)
+int ast_context_remove_extension_callerid(const char *context, const char *extension, int priority, const char *callerid, int matchcallerid, const char *registrar)
 {
-	return pbx_extension_helper(c, NULL, context, exten, priority, NULL, callerid, E_CANMATCH, 0, 0);
-}
+	int ret = -1; /* default error return */
+	struct ast_context *c;
 
-int ast_matchmore_extension(struct ast_channel *c, const char *context, const char *exten, int priority, const char *callerid)
-{
-	return pbx_extension_helper(c, NULL, context, exten, priority, NULL, callerid, E_MATCHMORE, 0, 0);
+	c = find_context_locked(context);
+	if (c) { /* ... remove extension ... */
+		ret = ast_context_remove_extension_callerid2(c, extension, priority, callerid,
+			matchcallerid, registrar, 0);
+		ast_unlock_contexts();
+	}
+
+	return ret;
 }
 
-int ast_spawn_extension(struct ast_channel *c, const char *context, const char *exten, int priority, const char *callerid, int *found, int combined_find_spawn)
+/*!
+ * \brief This functionc locks given context, search for the right extension and
+ * fires out all peer in this extensions with given priority. If priority
+ * is set to 0, all peers are removed. After that, unlock context and
+ * return.
+ * \note When do you want to call this function, make sure that &conlock is locked,
+ * because some process can handle with your *con context before you lock
+ * it.
+ *
+ */
+int ast_context_remove_extension2(struct ast_context *con, const char *extension, int priority, const char *registrar, int already_locked)
 {
-	return pbx_extension_helper(c, NULL, context, exten, priority, NULL, callerid, E_SPAWN, found, combined_find_spawn);
+	return ast_context_remove_extension_callerid2(con, extension, priority, NULL, AST_EXT_MATCHCID_ANY, registrar, already_locked);
 }
 
-void ast_pbx_h_exten_run(struct ast_channel *chan, const char *context)
+int ast_context_remove_extension_callerid2(struct ast_context *con, const char *extension, int priority, const char *callerid, int matchcallerid, const char *registrar, int already_locked)
 {
-	int autoloopflag;
-	int found;
-	int spawn_error;
-
-	ast_channel_lock(chan);
-
-	/*
-	 * Make sure that the channel is marked as hungup since we are
-	 * going to run the h exten on it.
-	 */
-	ast_softhangup_nolock(chan, AST_SOFTHANGUP_HANGUP_EXEC);
-
-	/* Set h exten location */
-	if (context != ast_channel_context(chan)) {
-		ast_channel_context_set(chan, context);
-	}
-	ast_channel_exten_set(chan, "h");
-	ast_channel_priority_set(chan, 1);
+	struct ast_exten *exten, *prev_exten = NULL;
+	struct ast_exten *peer;
+	struct ast_exten ex, *exten2, *exten3;
+	char dummy_name[1024];
+	struct ast_exten *previous_peer = NULL;
+	struct ast_exten *next_peer = NULL;
+	int found = 0;
 
-	/* Save autoloop flag */
-	autoloopflag = ast_test_flag(ast_channel_flags(chan), AST_FLAG_IN_AUTOLOOP);
-	ast_set_flag(ast_channel_flags(chan), AST_FLAG_IN_AUTOLOOP);
-	ast_channel_unlock(chan);
+	if (!already_locked)
+		ast_wrlock_context(con);
 
-	for (;;) {
-		spawn_error = ast_spawn_extension(chan, ast_channel_context(chan),
-			ast_channel_exten(chan), ast_channel_priority(chan),
-			S_COR(ast_channel_caller(chan)->id.number.valid,
-				ast_channel_caller(chan)->id.number.str, NULL), &found, 1);
-
-		ast_channel_lock(chan);
-		if (spawn_error) {
-			/* The code after the loop needs the channel locked. */
-			break;
-		}
-		ast_channel_priority_set(chan, ast_channel_priority(chan) + 1);
-		ast_channel_unlock(chan);
-	}
-	if (found && spawn_error) {
-		/* Something bad happened, or a hangup has been requested. */
-		ast_debug(1, "Spawn extension (%s,%s,%d) exited non-zero on '%s'\n",
-			ast_channel_context(chan), ast_channel_exten(chan),
-			ast_channel_priority(chan), ast_channel_name(chan));
-		ast_verb(2, "Spawn extension (%s, %s, %d) exited non-zero on '%s'\n",
-			ast_channel_context(chan), ast_channel_exten(chan),
-			ast_channel_priority(chan), ast_channel_name(chan));
-	}
-
-	/* An "h" exten has been run, so indicate that one has been run. */
-	ast_set_flag(ast_channel_flags(chan), AST_FLAG_BRIDGE_HANGUP_RUN);
-
-	/* Restore autoloop flag */
-	ast_set2_flag(ast_channel_flags(chan), autoloopflag, AST_FLAG_IN_AUTOLOOP);
-	ast_channel_unlock(chan);
-}
+#ifdef NEED_DEBUG
+	ast_verb(3,"Removing %s/%s/%d%s%s from trees, registrar=%s\n", con->name, extension, priority, matchcallerid ? "/" : "", matchcallerid ? callerid : "", registrar);
+#endif
+#ifdef CONTEXT_DEBUG
+	check_contexts(__FILE__, __LINE__);
+#endif
+	/* find this particular extension */
+	ex.exten = dummy_name;
+	ex.matchcid = matchcallerid;
+	ex.cidmatch = callerid;
+	ast_copy_string(dummy_name, extension, sizeof(dummy_name));
+	exten = ast_hashtab_lookup(con->root_table, &ex);
+	if (exten) {
+		if (priority == 0) {
+			exten2 = ast_hashtab_remove_this_object(con->root_table, exten);
+			if (!exten2)
+				ast_log(LOG_ERROR,"Trying to delete the exten %s from context %s, but could not remove from the root_table\n", extension, con->name);
+			if (con->pattern_tree) {
+				struct match_char *x = add_exten_to_pattern_tree(con, exten, 1);
 
-/*!
- * \internal
- * \brief Publish a hangup handler related message to \ref stasis
- */
-static void publish_hangup_handler_message(const char *action, struct ast_channel *chan, const char *handler)
-{
-	RAII_VAR(struct ast_json *, blob, NULL, ast_json_unref);
+				if (x->exten) { /* this test for safety purposes */
+					x->deleted = 1; /* with this marked as deleted, it will never show up in the scoreboard, and therefore never be found */
+					x->exten = 0; /* get rid of what will become a bad pointer */
+				} else {
+					ast_log(LOG_WARNING,"Trying to delete an exten from a context, but the pattern tree node returned isn't a full extension\n");
+				}
+			}
+		} else {
+			ex.priority = priority;
+			exten2 = ast_hashtab_lookup(exten->peer_table, &ex);
+			if (exten2) {
+				if (exten2->label) { /* if this exten has a label, remove that, too */
+					exten3 = ast_hashtab_remove_this_object(exten->peer_label_table,exten2);
+					if (!exten3)
+						ast_log(LOG_ERROR,"Did not remove this priority label (%d/%s) from the peer_label_table of context %s, extension %s!\n", priority, exten2->label, con->name, exten2->exten);
+				}
 
-	blob = ast_json_pack("{s: s, s: s}",
-			"type", action,
-			"handler", S_OR(handler, ""));
-	if (!blob) {
-		return;
+				exten3 = ast_hashtab_remove_this_object(exten->peer_table, exten2);
+				if (!exten3)
+					ast_log(LOG_ERROR,"Did not remove this priority (%d) from the peer_table of context %s, extension %s!\n", priority, con->name, exten2->exten);
+				if (exten2 == exten && exten2->peer) {
+					exten2 = ast_hashtab_remove_this_object(con->root_table, exten);
+					ast_hashtab_insert_immediate(con->root_table, exten2->peer);
+				}
+				if (ast_hashtab_size(exten->peer_table) == 0) {
+					/* well, if the last priority of an exten is to be removed,
+					   then, the extension is removed, too! */
+					exten3 = ast_hashtab_remove_this_object(con->root_table, exten);
+					if (!exten3)
+						ast_log(LOG_ERROR,"Did not remove this exten (%s) from the context root_table (%s) (priority %d)\n", exten->exten, con->name, priority);
+					if (con->pattern_tree) {
+						struct match_char *x = add_exten_to_pattern_tree(con, exten, 1);
+						if (x->exten) { /* this test for safety purposes */
+							x->deleted = 1; /* with this marked as deleted, it will never show up in the scoreboard, and therefore never be found */
+							x->exten = 0; /* get rid of what will become a bad pointer */
+						}
+					}
+				}
+			} else {
+				ast_log(LOG_ERROR,"Could not find priority %d of exten %s in context %s!\n",
+						priority, exten->exten, con->name);
+			}
+		}
+	} else {
+		/* hmmm? this exten is not in this pattern tree? */
+		ast_log(LOG_WARNING,"Cannot find extension %s in root_table in context %s\n",
+				extension, con->name);
 	}
-
-	ast_channel_publish_blob(chan, ast_channel_hangup_handler_type(), blob);
-}
-
-int ast_pbx_hangup_handler_run(struct ast_channel *chan)
-{
-	struct ast_hangup_handler_list *handlers;
-	struct ast_hangup_handler *h_handler;
-
-	ast_channel_lock(chan);
-	handlers = ast_channel_hangup_handlers(chan);
-	if (AST_LIST_EMPTY(handlers)) {
-		ast_channel_unlock(chan);
-		return 0;
+#ifdef NEED_DEBUG
+	if (con->pattern_tree) {
+		ast_log(LOG_NOTICE,"match char tree after exten removal:\n");
+		log_match_char_tree(con->pattern_tree, " ");
 	}
+#endif
 
-	/*
-	 * Make sure that the channel is marked as hungup since we are
-	 * going to run the hangup handlers on it.
-	 */
-	ast_softhangup_nolock(chan, AST_SOFTHANGUP_HANGUP_EXEC);
-
-	for (;;) {
-		handlers = ast_channel_hangup_handlers(chan);
-		h_handler = AST_LIST_REMOVE_HEAD(handlers, node);
-		if (!h_handler) {
+	/* scan the extension list to find first matching extension-registrar */
+	for (exten = con->root; exten; prev_exten = exten, exten = exten->next) {
+		if (!strcmp(exten->exten, extension) &&
+			(!registrar || !strcmp(exten->registrar, registrar)) &&
+			(!matchcallerid || (!ast_strlen_zero(callerid) && !ast_strlen_zero(exten->cidmatch) && !strcmp(exten->cidmatch, callerid)) || (ast_strlen_zero(callerid) && ast_strlen_zero(exten->cidmatch))))
 			break;
-		}
-
-		publish_hangup_handler_message("run", chan, h_handler->args);
-		ast_channel_unlock(chan);
-
-		ast_app_exec_sub(NULL, chan, h_handler->args, 1);
-		ast_free(h_handler);
-
-		ast_channel_lock(chan);
 	}
-	ast_channel_unlock(chan);
-	return 1;
-}
+	if (!exten) {
+		/* we can't find right extension */
+		if (!already_locked)
+			ast_unlock_context(con);
+		return -1;
+	}
 
-void ast_pbx_hangup_handler_init(struct ast_channel *chan)
-{
-	struct ast_hangup_handler_list *handlers;
+	/* scan the priority list to remove extension with exten->priority == priority */
+	for (peer = exten, next_peer = exten->peer ? exten->peer : exten->next;
+		 peer && !strcmp(peer->exten, extension) &&
+			(!callerid || (!matchcallerid && !peer->matchcid) || (matchcallerid && peer->matchcid && !strcmp(peer->cidmatch, callerid))) ;
+			peer = next_peer, next_peer = next_peer ? (next_peer->peer ? next_peer->peer : next_peer->next) : NULL) {
 
-	handlers = ast_channel_hangup_handlers(chan);
-	AST_LIST_HEAD_INIT_NOLOCK(handlers);
-}
+		if ((priority == 0 || peer->priority == priority) &&
+				(!registrar || !strcmp(peer->registrar, registrar) )) {
+			found = 1;
 
-void ast_pbx_hangup_handler_destroy(struct ast_channel *chan)
-{
-	struct ast_hangup_handler_list *handlers;
-	struct ast_hangup_handler *h_handler;
+			/* we are first priority extension? */
+			if (!previous_peer) {
+				/*
+				 * We are first in the priority chain, so must update the extension chain.
+				 * The next node is either the next priority or the next extension
+				 */
+				struct ast_exten *next_node = peer->peer ? peer->peer : peer->next;
+				if (peer->peer) {
+					/* move the peer_table and peer_label_table down to the next peer, if
+					   it is there */
+					peer->peer->peer_table = peer->peer_table;
+					peer->peer->peer_label_table = peer->peer_label_table;
+					peer->peer_table = NULL;
+					peer->peer_label_table = NULL;
+				}
+				if (!prev_exten) {	/* change the root... */
+					con->root = next_node;
+				} else {
+					prev_exten->next = next_node; /* unlink */
+				}
+				if (peer->peer)	{ /* update the new head of the pri list */
+					peer->peer->next = peer->next;
+				}
+			} else { /* easy, we are not first priority in extension */
+				previous_peer->peer = peer->peer;
+			}
 
-	ast_channel_lock(chan);
 
-	/* Get rid of each of the hangup handlers on the channel */
-	handlers = ast_channel_hangup_handlers(chan);
-	while ((h_handler = AST_LIST_REMOVE_HEAD(handlers, node))) {
-		ast_free(h_handler);
+			/* now, free whole priority extension */
+			destroy_exten(peer);
+		} else {
+			previous_peer = peer;
+		}
 	}
-
-	ast_channel_unlock(chan);
+	if (!already_locked)
+		ast_unlock_context(con);
+	return found ? 0 : -1;
 }
 
-int ast_pbx_hangup_handler_pop(struct ast_channel *chan)
-{
-	struct ast_hangup_handler_list *handlers;
-	struct ast_hangup_handler *h_handler;
-
-	ast_channel_lock(chan);
-	handlers = ast_channel_hangup_handlers(chan);
-	h_handler = AST_LIST_REMOVE_HEAD(handlers, node);
-	if (h_handler) {
-		publish_hangup_handler_message("pop", chan, h_handler->args);
-	}
-	ast_channel_unlock(chan);
-	if (h_handler) {
-		ast_free(h_handler);
-		return 1;
-	}
-	return 0;
-}
 
-void ast_pbx_hangup_handler_push(struct ast_channel *chan, const char *handler)
+/*!
+ * \note This function locks contexts list by &conlist, searches for the right context
+ * structure, and locks the macrolock mutex in that context.
+ * macrolock is used to limit a macro to be executed by one call at a time.
+ * \param context The context
+ */
+int ast_context_lockmacro(const char *context)
 {
-	struct ast_hangup_handler_list *handlers;
-	struct ast_hangup_handler *h_handler;
-	const char *expanded_handler;
+	struct ast_context *c;
+	int ret = -1;
 
-	if (ast_strlen_zero(handler)) {
-		return;
-	}
+	c = find_context_locked(context);
+	if (c) {
+		ast_unlock_contexts();
 
-	expanded_handler = ast_app_expand_sub_args(chan, handler);
-	if (!expanded_handler) {
-		return;
-	}
-	h_handler = ast_malloc(sizeof(*h_handler) + 1 + strlen(expanded_handler));
-	if (!h_handler) {
-		ast_free((char *) expanded_handler);
-		return;
+		/* if we found context, lock macrolock */
+		ret = ast_mutex_lock(&c->macrolock);
 	}
-	strcpy(h_handler->args, expanded_handler);/* Safe */
-	ast_free((char *) expanded_handler);
-
-	ast_channel_lock(chan);
 
-	handlers = ast_channel_hangup_handlers(chan);
-	AST_LIST_INSERT_HEAD(handlers, h_handler, node);
-	publish_hangup_handler_message("push", chan, h_handler->args);
-	ast_channel_unlock(chan);
+	return ret;
 }
 
-#define HANDLER_FORMAT	"%-30s %s\n"
-
 /*!
- * \internal
- * \brief CLI output the hangup handler headers.
- * \since 11.0
- *
- * \param fd CLI file descriptor to use.
- *
- * \return Nothing
+ * \note This function locks contexts list by &conlist, searches for the right context
+ * structure, and unlocks the macrolock mutex in that context.
+ * macrolock is used to limit a macro to be executed by one call at a time.
+ * \param context The context
  */
-static void ast_pbx_hangup_handler_headers(int fd)
+int ast_context_unlockmacro(const char *context)
 {
-	ast_cli(fd, HANDLER_FORMAT, "Channel", "Handler");
-}
+	struct ast_context *c;
+	int ret = -1;
 
-/*!
- * \internal
- * \brief CLI output the channel hangup handlers.
- * \since 11.0
- *
- * \param fd CLI file descriptor to use.
- * \param chan Channel to show hangup handlers.
- *
- * \return Nothing
- */
-static void ast_pbx_hangup_handler_show(int fd, struct ast_channel *chan)
-{
-	struct ast_hangup_handler_list *handlers;
-	struct ast_hangup_handler *h_handler;
-	int first = 1;
+	c = find_context_locked(context);
+	if (c) {
+		ast_unlock_contexts();
 
-	ast_channel_lock(chan);
-	handlers = ast_channel_hangup_handlers(chan);
-	AST_LIST_TRAVERSE(handlers, h_handler, node) {
-		ast_cli(fd, HANDLER_FORMAT, first ? ast_channel_name(chan) : "", h_handler->args);
-		first = 0;
+		/* if we found context, unlock macrolock */
+		ret = ast_mutex_unlock(&c->macrolock);
 	}
-	ast_channel_unlock(chan);
+
+	return ret;
 }
 
 /*
- * \brief 'show hanguphandlers <channel>' CLI command implementation function...
+ * Help for CLI commands ...
  */
-static char *handle_show_hangup_channel(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
-{
-	struct ast_channel *chan;
 
-	switch (cmd) {
-	case CLI_INIT:
-		e->command = "core show hanguphandlers";
-		e->usage =
-			"Usage: core show hanguphandlers <channel>\n"
-			"       Show hangup handlers of a specified channel.\n";
-		return NULL;
+/*! \brief  handle_show_hints: CLI support for listing registered dial plan hints */
+static char *handle_show_hints(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+	struct ast_hint *hint;
+	int num = 0;
+	int watchers;
+	struct ao2_iterator i;
+	char buf[AST_MAX_EXTENSION+AST_MAX_CONTEXT+2];
+
+	switch (cmd) {
+	case CLI_INIT:
+		e->command = "core show hints";
+		e->usage =
+			"Usage: core show hints\n"
+			"       List registered hints.\n"
+			"       Hint details are shown in five columns. In order from left to right, they are:\n"
+			"       1. Hint extension URI.\n"
+			"       2. List of mapped device or presence state identifiers.\n"
+			"       3. Current extension state. The aggregate of mapped device states.\n"
+			"       4. Current presence state for the mapped presence state provider.\n"
+			"       5. Watchers - number of subscriptions and other entities watching this hint.\n";
+		return NULL;
 	case CLI_GENERATE:
-		return ast_complete_channels(a->line, a->word, a->pos, a->n, e->args);
+		return NULL;
 	}
 
-	if (a->argc < 4) {
-		return CLI_SHOWUSAGE;
+	if (ao2_container_count(hints) == 0) {
+		ast_cli(a->fd, "There are no registered dialplan hints\n");
+		return CLI_SUCCESS;
 	}
+	/* ... we have hints ... */
+	ast_cli(a->fd, "\n    -= Registered Asterisk Dial Plan Hints =-\n");
 
-	chan = ast_channel_get_by_name(a->argv[3]);
-	if (!chan) {
-		ast_cli(a->fd, "Channel does not exist.\n");
-		return CLI_FAILURE;
-	}
+	i = ao2_iterator_init(hints, 0);
+	for (; (hint = ao2_iterator_next(&i)); ao2_ref(hint, -1)) {
+		ao2_lock(hint);
+		if (!hint->exten) {
+			/* The extension has already been destroyed */
+			ao2_unlock(hint);
+			continue;
+		}
+		watchers = ao2_container_count(hint->callbacks);
+		snprintf(buf, sizeof(buf), "%s@%s",
+			ast_get_extension_name(hint->exten),
+			ast_get_context_name(ast_get_extension_context(hint->exten)));
 
-	ast_pbx_hangup_handler_headers(a->fd);
-	ast_pbx_hangup_handler_show(a->fd, chan);
+		ast_cli(a->fd, "%-20.20s: %-20.20s  State:%-15.15s Presence:%-15.15s Watchers %2d\n",
+			buf,
+			ast_get_extension_app(hint->exten),
+			ast_extension_state2str(hint->laststate),
+			ast_presence_state2str(hint->last_presence_state),
+			watchers);
 
-	ast_channel_unref(chan);
+		ao2_unlock(hint);
+		num++;
+	}
+	ao2_iterator_destroy(&i);
 
+	ast_cli(a->fd, "----------------\n");
+	ast_cli(a->fd, "- %d hints registered\n", num);
 	return CLI_SUCCESS;
 }
 
-/*
- * \brief 'show hanguphandlers all' CLI command implementation function...
- */
-static char *handle_show_hangup_all(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+/*! \brief autocomplete for CLI command 'core show hint' */
+static char *complete_core_show_hint(const char *line, const char *word, int pos, int state)
 {
-	struct ast_channel_iterator *iter;
-	struct ast_channel *chan;
+	struct ast_hint *hint;
+	char *ret = NULL;
+	int which = 0;
+	int wordlen;
+	struct ao2_iterator i;
+
+	if (pos != 3)
+		return NULL;
+
+	wordlen = strlen(word);
+
+	/* walk through all hints */
+	i = ao2_iterator_init(hints, 0);
+	for (; (hint = ao2_iterator_next(&i)); ao2_ref(hint, -1)) {
+		ao2_lock(hint);
+		if (!hint->exten) {
+			/* The extension has already been destroyed */
+			ao2_unlock(hint);
+			continue;
+		}
+		if (!strncasecmp(word, ast_get_extension_name(hint->exten), wordlen) && ++which > state) {
+			ret = ast_strdup(ast_get_extension_name(hint->exten));
+			ao2_unlock(hint);
+			ao2_ref(hint, -1);
+			break;
+		}
+		ao2_unlock(hint);
+	}
+	ao2_iterator_destroy(&i);
+
+	return ret;
+}
+
+/*! \brief  handle_show_hint: CLI support for listing registered dial plan hint */
+static char *handle_show_hint(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+	struct ast_hint *hint;
+	int watchers;
+	int num = 0, extenlen;
+	struct ao2_iterator i;
+	char buf[AST_MAX_EXTENSION+AST_MAX_CONTEXT+2];
 
 	switch (cmd) {
 	case CLI_INIT:
-		e->command = "core show hanguphandlers all";
+		e->command = "core show hint";
 		e->usage =
-			"Usage: core show hanguphandlers all\n"
-			"       Show hangup handlers for all channels.\n";
+			"Usage: core show hint <exten>\n"
+			"       List registered hint.\n"
+			"       Hint details are shown in five columns. In order from left to right, they are:\n"
+			"       1. Hint extension URI.\n"
+			"       2. List of mapped device or presence state identifiers.\n"
+			"       3. Current extension state. The aggregate of mapped device states.\n"
+			"       4. Current presence state for the mapped presence state provider.\n"
+			"       5. Watchers - number of subscriptions and other entities watching this hint.\n";
 		return NULL;
 	case CLI_GENERATE:
-		return ast_complete_channels(a->line, a->word, a->pos, a->n, e->args);
+		return complete_core_show_hint(a->line, a->word, a->pos, a->n);
 	}
 
-	if (a->argc < 4) {
+	if (a->argc < 4)
 		return CLI_SHOWUSAGE;
-	}
 
-	iter = ast_channel_iterator_all_new();
-	if (!iter) {
-		return CLI_FAILURE;
+	if (ao2_container_count(hints) == 0) {
+		ast_cli(a->fd, "There are no registered dialplan hints\n");
+		return CLI_SUCCESS;
 	}
 
-	ast_pbx_hangup_handler_headers(a->fd);
-	for (; (chan = ast_channel_iterator_next(iter)); ast_channel_unref(chan)) {
-		ast_pbx_hangup_handler_show(a->fd, chan);
+	extenlen = strlen(a->argv[3]);
+	i = ao2_iterator_init(hints, 0);
+	for (; (hint = ao2_iterator_next(&i)); ao2_ref(hint, -1)) {
+		ao2_lock(hint);
+		if (!hint->exten) {
+			/* The extension has already been destroyed */
+			ao2_unlock(hint);
+			continue;
+		}
+		if (!strncasecmp(ast_get_extension_name(hint->exten), a->argv[3], extenlen)) {
+			watchers = ao2_container_count(hint->callbacks);
+			sprintf(buf, "%s@%s",
+				ast_get_extension_name(hint->exten),
+				ast_get_context_name(ast_get_extension_context(hint->exten)));
+			ast_cli(a->fd, "%-20.20s: %-20.20s  State:%-15.15s Presence:%-15.15s Watchers %2d\n",
+				buf,
+				ast_get_extension_app(hint->exten),
+				ast_extension_state2str(hint->laststate), 
+				ast_presence_state2str(hint->last_presence_state), 
+				watchers);
+			num++;
+		}
+		ao2_unlock(hint);
 	}
-	ast_channel_iterator_destroy(iter);
-
+	ao2_iterator_destroy(&i);
+	if (!num)
+		ast_cli(a->fd, "No hints matching extension %s\n", a->argv[3]);
+	else
+		ast_cli(a->fd, "%d hint%s matching extension %s\n", num, (num!=1 ? "s":""), a->argv[3]);
 	return CLI_SUCCESS;
 }
 
-/*! helper function to set extension and priority */
-static void set_ext_pri(struct ast_channel *c, const char *exten, int pri)
+#if 0
+/* This code can be used to test if the system survives running out of memory.
+ * It might be an idea to put this in only if ENABLE_AUTODESTRUCT_TESTS is enabled.
+ *
+ * If you want to test this, these Linux sysctl flags might be appropriate:
+ *   vm.overcommit_memory = 2
+ *   vm.swappiness = 0
+ *
+ * <@Corydon76-home> I envision 'core eat disk space' and 'core eat file descriptors' now
+ * <@mjordan> egads
+ * <@mjordan> it's literally the 'big red' auto-destruct button
+ * <@mjordan> if you were wondering who even builds such a thing.... well, now you know
+ * ...
+ * <@Corydon76-home> What about if they lived only if you defined TEST_FRAMEWORK?  Shouldn't have those on production machines
+ * <@mjordan> I think accompanied with an update to one of our README files that "no, really, TEST_FRAMEWORK isn't for you", I'd be fine
+ */
+static char *handle_eat_memory(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
 {
-	ast_channel_lock(c);
-	ast_channel_exten_set(c, exten);
-	ast_channel_priority_set(c, pri);
-	ast_channel_unlock(c);
-}
+	void **blocks;
+	int blocks_pos = 0;
+	const int blocks_max = 50000;
+	long long int allocated = 0;
+	int sizes[] = {
+		100 * 1024 * 1024,
+		100 * 1024,
+		2 * 1024,
+		400,
+		0
+	};
+	int i;
 
-/*!
- * \brief collect digits from the channel into the buffer.
- * \param c, buf, buflen, pos
- * \param waittime is in milliseconds
- * \retval 0 on timeout or done.
- * \retval -1 on error.
-*/
-static int collect_digits(struct ast_channel *c, int waittime, char *buf, int buflen, int pos)
-{
-	int digit;
+	switch (cmd) {
+	case CLI_INIT:
+		/* To do: add method to free memory again? 5 minutes? */
+		e->command = "core eat memory";
+		e->usage =
+			"Usage: core eat memory\n"
+			"       Eats all available memory so you can test if the system survives\n";
+		return NULL;
+	case CLI_GENERATE:
+		return NULL;
+	}
 
-	buf[pos] = '\0';	/* make sure it is properly terminated */
-	while (ast_matchmore_extension(c, ast_channel_context(c), buf, 1,
-		S_COR(ast_channel_caller(c)->id.number.valid, ast_channel_caller(c)->id.number.str, NULL))) {
-		/* As long as we're willing to wait, and as long as it's not defined,
-		   keep reading digits until we can't possibly get a right answer anymore.  */
-		digit = ast_waitfordigit(c, waittime);
-		if (ast_channel_softhangup_internal_flag(c) & AST_SOFTHANGUP_ASYNCGOTO) {
-			ast_channel_clear_softhangup(c, AST_SOFTHANGUP_ASYNCGOTO);
-		} else {
-			if (!digit)	/* No entry */
+	blocks = ast_malloc(sizeof(void*) * blocks_max);
+	if (!blocks) {
+		ast_log(LOG_ERROR, "Already out of mem?\n");
+		return CLI_SUCCESS;
+	}
+
+	for (i = 0; sizes[i]; ++i) {
+		int alloc_size = sizes[i];
+		ast_log(LOG_WARNING, "Allocating %d sized blocks (got %d blocks already)\n", alloc_size, blocks_pos);
+		while (1) {
+			void *block;
+			if (blocks_pos >= blocks_max) {
+				ast_log(LOG_ERROR, "Memory buffer too small? Run me again :)\n");
 				break;
-			if (digit < 0)	/* Error, maybe a  hangup */
-				return -1;
-			if (pos < buflen - 1) {	/* XXX maybe error otherwise ? */
-				buf[pos++] = digit;
-				buf[pos] = '\0';
 			}
-			waittime = ast_channel_pbx(c)->dtimeoutms;
+
+			block = ast_malloc(alloc_size);
+			if (!block) {
+				break;
+			}
+
+			blocks[blocks_pos++] = block;
+			allocated += alloc_size;
 		}
 	}
-	return 0;
+
+	/* No freeing of the mem! */
+	ast_log(LOG_WARNING, "Allocated %lld bytes total!\n", allocated);
+	return CLI_SUCCESS;
 }
+#endif
 
-static enum ast_pbx_result __ast_pbx_run(struct ast_channel *c,
-		struct ast_pbx_args *args)
+/*
+ * 'show dialplan' CLI command implementation functions ...
+ */
+static char *complete_show_dialplan_context(const char *line, const char *word, int pos,
+	int state)
 {
-	int found = 0;	/* set if we find at least one match */
-	int res = 0;
-	int autoloopflag;
-	int error = 0;		/* set an error conditions */
-	struct ast_pbx *pbx;
-	struct ast_callid *callid;
+	struct ast_context *c = NULL;
+	char *ret = NULL;
+	int which = 0;
+	int wordlen;
 
-	/* A little initial setup here */
-	if (ast_channel_pbx(c)) {
-		ast_log(LOG_WARNING, "%s already has PBX structure??\n", ast_channel_name(c));
-		/* XXX and now what ? */
-		ast_free(ast_channel_pbx(c));
-	}
-	if (!(pbx = ast_calloc(1, sizeof(*pbx)))) {
-		return AST_PBX_FAILED;
-	}
+	/* we are do completion of [exten@]context on second position only */
+	if (pos != 2)
+		return NULL;
 
-	callid = ast_read_threadstorage_callid();
-	/* If the thread isn't already associated with a callid, we should create that association. */
-	if (!callid) {
-		/* Associate new PBX thread with the channel call id if it is availble.
-		 * If not, create a new one instead.
-		 */
-		callid = ast_channel_callid(c);
-		if (!callid) {
-			callid = ast_create_callid();
-			if (callid) {
-				ast_channel_lock(c);
-				ast_channel_callid_set(c, callid);
-				ast_channel_unlock(c);
-			}
+	ast_rdlock_contexts();
+
+	wordlen = strlen(word);
+
+	/* walk through all contexts and return the n-th match */
+	while ( (c = ast_walk_contexts(c)) ) {
+		if (!strncasecmp(word, ast_get_context_name(c), wordlen) && ++which > state) {
+			ret = ast_strdup(ast_get_context_name(c));
+			break;
 		}
-		ast_callid_threadassoc_add(callid);
-		callid = ast_callid_unref(callid);
-	} else {
-		/* Nothing to do here, The thread is already bound to a callid.  Let's just get rid of the reference. */
-		ast_callid_unref(callid);
 	}
 
-	ast_channel_pbx_set(c, pbx);
-	/* Set reasonable defaults */
-	ast_channel_pbx(c)->rtimeoutms = 10000;
-	ast_channel_pbx(c)->dtimeoutms = 5000;
+	ast_unlock_contexts();
 
-	autoloopflag = ast_test_flag(ast_channel_flags(c), AST_FLAG_IN_AUTOLOOP);	/* save value to restore at the end */
-	ast_set_flag(ast_channel_flags(c), AST_FLAG_IN_AUTOLOOP);
+	return ret;
+}
 
-	if (ast_strlen_zero(ast_channel_exten(c))) {
-		/* If not successful fall back to 's' - but only if there is no given exten  */
-		ast_verb(2, "Starting %s at %s,%s,%d failed so falling back to exten 's'\n", ast_channel_name(c), ast_channel_context(c), ast_channel_exten(c), ast_channel_priority(c));
-		/* XXX the original code used the existing priority in the call to
-		 * ast_exists_extension(), and reset it to 1 afterwards.
-		 * I believe the correct thing is to set it to 1 immediately.
-		*/
-		set_ext_pri(c, "s", 1);
+/*! \brief Counters for the show dialplan manager command */
+struct dialplan_counters {
+	int total_items;
+	int total_context;
+	int total_exten;
+	int total_prio;
+	int context_existence;
+	int extension_existence;
+};
+
+/*! \brief helper function to print an extension */
+static void print_ext(struct ast_exten *e, char * buf, int buflen)
+{
+	int prio = ast_get_extension_priority(e);
+	if (prio == PRIORITY_HINT) {
+		snprintf(buf, buflen, "hint: %s",
+			ast_get_extension_app(e));
+	} else {
+		snprintf(buf, buflen, "%d. %s(%s)",
+			prio, ast_get_extension_app(e),
+			(!ast_strlen_zero(ast_get_extension_app_data(e)) ? (char *)ast_get_extension_app_data(e) : ""));
 	}
+}
 
-	for (;;) {
-		char dst_exten[256];	/* buffer to accumulate digits */
-		int pos = 0;		/* XXX should check bounds */
-		int digit = 0;
-		int invalid = 0;
-		int timeout = 0;
+/* XXX not verified */
+static int show_dialplan_helper(int fd, const char *context, const char *exten, struct dialplan_counters *dpc, struct ast_include *rinclude, int includecount, const char *includes[])
+{
+	struct ast_context *c = NULL;
+	int res = 0, old_total_exten = dpc->total_exten;
 
-		/* No digits pressed yet */
-		dst_exten[pos] = '\0';
+	ast_rdlock_contexts();
 
-		/* loop on priorities in this context/exten */
-		while (!(res = ast_spawn_extension(c, ast_channel_context(c), ast_channel_exten(c), ast_channel_priority(c),
-			S_COR(ast_channel_caller(c)->id.number.valid, ast_channel_caller(c)->id.number.str, NULL),
-			&found, 1))) {
+	/* walk all contexts ... */
+	while ( (c = ast_walk_contexts(c)) ) {
+		struct ast_exten *e;
+		struct ast_include *i;
+		struct ast_ignorepat *ip;
+#ifndef LOW_MEMORY
+		char buf[1024], buf2[1024];
+#else
+		char buf[256], buf2[256];
+#endif
+		int context_info_printed = 0;
 
-			if (!ast_check_hangup(c)) {
-				ast_channel_priority_set(c, ast_channel_priority(c) + 1);
-				continue;
-			}
+		if (context && strcmp(ast_get_context_name(c), context))
+			continue;	/* skip this one, name doesn't match */
 
-			/* Check softhangup flags. */
-			if (ast_channel_softhangup_internal_flag(c) & AST_SOFTHANGUP_ASYNCGOTO) {
-				ast_channel_clear_softhangup(c, AST_SOFTHANGUP_ASYNCGOTO);
-				continue;
-			}
-			if (ast_channel_softhangup_internal_flag(c) & AST_SOFTHANGUP_TIMEOUT) {
-				if (ast_exists_extension(c, ast_channel_context(c), "T", 1,
-					S_COR(ast_channel_caller(c)->id.number.valid, ast_channel_caller(c)->id.number.str, NULL))) {
-					set_ext_pri(c, "T", 1);
-					/* If the AbsoluteTimeout is not reset to 0, we'll get an infinite loop */
-					memset(ast_channel_whentohangup(c), 0, sizeof(*ast_channel_whentohangup(c)));
-					ast_channel_clear_softhangup(c, AST_SOFTHANGUP_TIMEOUT);
-					continue;
-				} else if (ast_exists_extension(c, ast_channel_context(c), "e", 1,
-					S_COR(ast_channel_caller(c)->id.number.valid, ast_channel_caller(c)->id.number.str, NULL))) {
-					raise_exception(c, "ABSOLUTETIMEOUT", 1);
-					/* If the AbsoluteTimeout is not reset to 0, we'll get an infinite loop */
-					memset(ast_channel_whentohangup(c), 0, sizeof(*ast_channel_whentohangup(c)));
-					ast_channel_clear_softhangup(c, AST_SOFTHANGUP_TIMEOUT);
-					continue;
-				}
+		dpc->context_existence = 1;
 
-				/* Call timed out with no special extension to jump to. */
-				error = 1;
-				break;
-			}
-			ast_debug(1, "Extension %s, priority %d returned normally even though call was hung up\n",
-				ast_channel_exten(c), ast_channel_priority(c));
-			error = 1;
-			break;
-		} /* end while  - from here on we can use 'break' to go out */
-		if (found && res) {
-			/* Something bad happened, or a hangup has been requested. */
-			if (strchr("0123456789ABCDEF*#", res)) {
-				ast_debug(1, "Oooh, got something to jump out with ('%c')!\n", res);
-				pos = 0;
-				dst_exten[pos++] = digit = res;
-				dst_exten[pos] = '\0';
-			} else if (res == AST_PBX_INCOMPLETE) {
-				ast_debug(1, "Spawn extension (%s,%s,%d) exited INCOMPLETE on '%s'\n", ast_channel_context(c), ast_channel_exten(c), ast_channel_priority(c), ast_channel_name(c));
-				ast_verb(2, "Spawn extension (%s, %s, %d) exited INCOMPLETE on '%s'\n", ast_channel_context(c), ast_channel_exten(c), ast_channel_priority(c), ast_channel_name(c));
+		ast_rdlock_context(c);
 
-				/* Don't cycle on incomplete - this will happen if the only extension that matches is our "incomplete" extension */
-				if (!ast_matchmore_extension(c, ast_channel_context(c), ast_channel_exten(c), 1,
-					S_COR(ast_channel_caller(c)->id.number.valid, ast_channel_caller(c)->id.number.str, NULL))) {
-					invalid = 1;
-				} else {
-					ast_copy_string(dst_exten, ast_channel_exten(c), sizeof(dst_exten));
-					digit = 1;
-					pos = strlen(dst_exten);
-				}
-			} else {
-				ast_debug(1, "Spawn extension (%s,%s,%d) exited non-zero on '%s'\n", ast_channel_context(c), ast_channel_exten(c), ast_channel_priority(c), ast_channel_name(c));
-				ast_verb(2, "Spawn extension (%s, %s, %d) exited non-zero on '%s'\n", ast_channel_context(c), ast_channel_exten(c), ast_channel_priority(c), ast_channel_name(c));
+		/* are we looking for exten too? if yes, we print context
+		 * only if we find our extension.
+		 * Otherwise print context even if empty ?
+		 * XXX i am not sure how the rinclude is handled.
+		 * I think it ought to go inside.
+		 */
+		if (!exten) {
+			dpc->total_context++;
+			ast_cli(fd, "[ Context '%s' created by '%s' ]\n",
+				ast_get_context_name(c), ast_get_context_registrar(c));
+			context_info_printed = 1;
+		}
 
-				if ((res == AST_PBX_ERROR)
-					&& ast_exists_extension(c, ast_channel_context(c), "e", 1,
-						S_COR(ast_channel_caller(c)->id.number.valid, ast_channel_caller(c)->id.number.str, NULL))) {
-					/* if we are already on the 'e' exten, don't jump to it again */
-					if (!strcmp(ast_channel_exten(c), "e")) {
-						ast_verb(2, "Spawn extension (%s, %s, %d) exited ERROR while already on 'e' exten on '%s'\n", ast_channel_context(c), ast_channel_exten(c), ast_channel_priority(c), ast_channel_name(c));
-						error = 1;
-					} else {
-						raise_exception(c, "ERROR", 1);
-						continue;
-					}
-				}
+		/* walk extensions ... */
+		e = NULL;
+		while ( (e = ast_walk_context_extensions(c, e)) ) {
+			struct ast_exten *p;
 
-				if (ast_channel_softhangup_internal_flag(c) & AST_SOFTHANGUP_ASYNCGOTO) {
-					ast_channel_clear_softhangup(c, AST_SOFTHANGUP_ASYNCGOTO);
-					continue;
-				}
-				if (ast_channel_softhangup_internal_flag(c) & AST_SOFTHANGUP_TIMEOUT) {
-					if (ast_exists_extension(c, ast_channel_context(c), "T", 1,
-						S_COR(ast_channel_caller(c)->id.number.valid, ast_channel_caller(c)->id.number.str, NULL))) {
-						set_ext_pri(c, "T", 1);
-						/* If the AbsoluteTimeout is not reset to 0, we'll get an infinite loop */
-						memset(ast_channel_whentohangup(c), 0, sizeof(*ast_channel_whentohangup(c)));
-						ast_channel_clear_softhangup(c, AST_SOFTHANGUP_TIMEOUT);
-						continue;
-					} else if (ast_exists_extension(c, ast_channel_context(c), "e", 1,
-						S_COR(ast_channel_caller(c)->id.number.valid, ast_channel_caller(c)->id.number.str, NULL))) {
-						raise_exception(c, "ABSOLUTETIMEOUT", 1);
-						/* If the AbsoluteTimeout is not reset to 0, we'll get an infinite loop */
-						memset(ast_channel_whentohangup(c), 0, sizeof(*ast_channel_whentohangup(c)));
-						ast_channel_clear_softhangup(c, AST_SOFTHANGUP_TIMEOUT);
-						continue;
-					}
-					/* Call timed out with no special extension to jump to. */
+			if (exten && !ast_extension_match(ast_get_extension_name(e), exten))
+				continue;	/* skip, extension match failed */
+
+			dpc->extension_existence = 1;
+
+			/* may we print context info? */
+			if (!context_info_printed) {
+				dpc->total_context++;
+				if (rinclude) { /* TODO Print more info about rinclude */
+					ast_cli(fd, "[ Included context '%s' created by '%s' ]\n",
+						ast_get_context_name(c), ast_get_context_registrar(c));
+				} else {
+					ast_cli(fd, "[ Context '%s' created by '%s' ]\n",
+						ast_get_context_name(c), ast_get_context_registrar(c));
 				}
-				error = 1;
-				break;
+				context_info_printed = 1;
 			}
-		}
-		if (error)
-			break;
+			dpc->total_prio++;
 
-		/*!\note
-		 * We get here on a failure of some kind:  non-existing extension or
-		 * hangup.  We have options, here.  We can either catch the failure
-		 * and continue, or we can drop out entirely. */
+			/* write extension name and first peer */
+			if (e->matchcid == AST_EXT_MATCHCID_ON)
+				snprintf(buf, sizeof(buf), "'%s' (CID match '%s') => ", ast_get_extension_name(e), e->cidmatch);
+			else
+				snprintf(buf, sizeof(buf), "'%s' =>", ast_get_extension_name(e));
 
-		if (invalid
-			|| (ast_strlen_zero(dst_exten) &&
-				!ast_exists_extension(c, ast_channel_context(c), ast_channel_exten(c), 1,
-				S_COR(ast_channel_caller(c)->id.number.valid, ast_channel_caller(c)->id.number.str, NULL)))) {
-			/*!\note
-			 * If there is no match at priority 1, it is not a valid extension anymore.
-			 * Try to continue at "i" (for invalid) or "e" (for exception) or exit if
-			 * neither exist.
-			 */
-			if (ast_exists_extension(c, ast_channel_context(c), "i", 1,
-				S_COR(ast_channel_caller(c)->id.number.valid, ast_channel_caller(c)->id.number.str, NULL))) {
-				ast_verb(3, "Channel '%s' sent to invalid extension: context,exten,priority=%s,%s,%d\n",
-					ast_channel_name(c), ast_channel_context(c), ast_channel_exten(c), ast_channel_priority(c));
-				pbx_builtin_setvar_helper(c, "INVALID_EXTEN", ast_channel_exten(c));
-				set_ext_pri(c, "i", 1);
-			} else if (ast_exists_extension(c, ast_channel_context(c), "e", 1,
-				S_COR(ast_channel_caller(c)->id.number.valid, ast_channel_caller(c)->id.number.str, NULL))) {
-				raise_exception(c, "INVALID", 1);
-			} else {
-				ast_log(LOG_WARNING, "Channel '%s' sent to invalid extension but no invalid handler: context,exten,priority=%s,%s,%d\n",
-					ast_channel_name(c), ast_channel_context(c), ast_channel_exten(c), ast_channel_priority(c));
-				error = 1; /* we know what to do with it */
-				break;
-			}
-		} else if (ast_channel_softhangup_internal_flag(c) & AST_SOFTHANGUP_TIMEOUT) {
-			/* If we get this far with AST_SOFTHANGUP_TIMEOUT, then we know that the "T" extension is next. */
-			ast_channel_clear_softhangup(c, AST_SOFTHANGUP_TIMEOUT);
-		} else {	/* keypress received, get more digits for a full extension */
-			int waittime = 0;
-			if (digit)
-				waittime = ast_channel_pbx(c)->dtimeoutms;
-			else if (!autofallthrough)
-				waittime = ast_channel_pbx(c)->rtimeoutms;
-			if (!waittime) {
-				const char *status = pbx_builtin_getvar_helper(c, "DIALSTATUS");
-				if (!status)
-					status = "UNKNOWN";
-				ast_verb(3, "Auto fallthrough, channel '%s' status is '%s'\n", ast_channel_name(c), status);
-				if (!strcasecmp(status, "CONGESTION"))
-					res = pbx_builtin_congestion(c, "10");
-				else if (!strcasecmp(status, "CHANUNAVAIL"))
-					res = pbx_builtin_congestion(c, "10");
-				else if (!strcasecmp(status, "BUSY"))
-					res = pbx_builtin_busy(c, "10");
-				error = 1; /* XXX disable message */
-				break;	/* exit from the 'for' loop */
+			print_ext(e, buf2, sizeof(buf2));
+
+			ast_cli(fd, "  %-17s %-45s [%s]\n", buf, buf2,
+				ast_get_extension_registrar(e));
+
+			dpc->total_exten++;
+			/* walk next extension peers */
+			p = e;	/* skip the first one, we already got it */
+			while ( (p = ast_walk_extension_priorities(e, p)) ) {
+				const char *el = ast_get_extension_label(p);
+				dpc->total_prio++;
+				if (el)
+					snprintf(buf, sizeof(buf), "   [%s]", el);
+				else
+					buf[0] = '\0';
+				print_ext(p, buf2, sizeof(buf2));
+
+				ast_cli(fd,"  %-17s %-45s [%s]\n", buf, buf2,
+					ast_get_extension_registrar(p));
 			}
+		}
 
-			if (collect_digits(c, waittime, dst_exten, sizeof(dst_exten), pos))
-				break;
-			if (res == AST_PBX_INCOMPLETE && ast_strlen_zero(&dst_exten[pos]))
-				timeout = 1;
-			if (!timeout
-				&& ast_exists_extension(c, ast_channel_context(c), dst_exten, 1,
-					S_COR(ast_channel_caller(c)->id.number.valid, ast_channel_caller(c)->id.number.str, NULL))) { /* Prepare the next cycle */
-				set_ext_pri(c, dst_exten, 1);
-			} else {
-				/* No such extension */
-				if (!timeout && !ast_strlen_zero(dst_exten)) {
-					/* An invalid extension */
-					if (ast_exists_extension(c, ast_channel_context(c), "i", 1,
-						S_COR(ast_channel_caller(c)->id.number.valid, ast_channel_caller(c)->id.number.str, NULL))) {
-						ast_verb(3, "Invalid extension '%s' in context '%s' on %s\n", dst_exten, ast_channel_context(c), ast_channel_name(c));
-						pbx_builtin_setvar_helper(c, "INVALID_EXTEN", dst_exten);
-						set_ext_pri(c, "i", 1);
-					} else if (ast_exists_extension(c, ast_channel_context(c), "e", 1,
-						S_COR(ast_channel_caller(c)->id.number.valid, ast_channel_caller(c)->id.number.str, NULL))) {
-						raise_exception(c, "INVALID", 1);
-					} else {
-						ast_log(LOG_WARNING,
-							"Invalid extension '%s', but no rule 'i' or 'e' in context '%s'\n",
-							dst_exten, ast_channel_context(c));
-						found = 1; /* XXX disable message */
-						break;
-					}
+		/* walk included and write info ... */
+		i = NULL;
+		while ( (i = ast_walk_context_includes(c, i)) ) {
+			snprintf(buf, sizeof(buf), "'%s'", ast_get_include_name(i));
+			if (exten) {
+				/* Check all includes for the requested extension */
+				if (includecount >= AST_PBX_MAX_STACK) {
+					ast_log(LOG_WARNING, "Maximum include depth exceeded!\n");
 				} else {
-					/* A simple timeout */
-					if (ast_exists_extension(c, ast_channel_context(c), "t", 1,
-						S_COR(ast_channel_caller(c)->id.number.valid, ast_channel_caller(c)->id.number.str, NULL))) {
-						ast_verb(3, "Timeout on %s\n", ast_channel_name(c));
-						set_ext_pri(c, "t", 1);
-					} else if (ast_exists_extension(c, ast_channel_context(c), "e", 1,
-						S_COR(ast_channel_caller(c)->id.number.valid, ast_channel_caller(c)->id.number.str, NULL))) {
-						raise_exception(c, "RESPONSETIMEOUT", 1);
+					int dupe = 0;
+					int x;
+					for (x = 0; x < includecount; x++) {
+						if (!strcasecmp(includes[x], ast_get_include_name(i))) {
+							dupe++;
+							break;
+						}
+					}
+					if (!dupe) {
+						includes[includecount] = ast_get_include_name(i);
+						show_dialplan_helper(fd, ast_get_include_name(i), exten, dpc, i, includecount + 1, includes);
 					} else {
-						ast_log(LOG_WARNING,
-							"Timeout, but no rule 't' or 'e' in context '%s'\n",
-							ast_channel_context(c));
-						found = 1; /* XXX disable message */
-						break;
+						ast_log(LOG_WARNING, "Avoiding circular include of %s within %s\n", ast_get_include_name(i), context);
 					}
 				}
+			} else {
+				ast_cli(fd, "  Include =>        %-45s [%s]\n",
+					buf, ast_get_include_registrar(i));
 			}
 		}
-	}
-
-	if (!found && !error) {
-		ast_log(LOG_WARNING, "Don't know what to do with '%s'\n", ast_channel_name(c));
-	}
 
-	if (!args || !args->no_hangup_chan) {
-		ast_softhangup(c, AST_SOFTHANGUP_APPUNLOAD);
-		if (!ast_test_flag(ast_channel_flags(c), AST_FLAG_BRIDGE_HANGUP_RUN)
-			&& ast_exists_extension(c, ast_channel_context(c), "h", 1,
-				S_COR(ast_channel_caller(c)->id.number.valid,
-					ast_channel_caller(c)->id.number.str, NULL))) {
-			ast_pbx_h_exten_run(c, ast_channel_context(c));
+		/* walk ignore patterns and write info ... */
+		ip = NULL;
+		while ( (ip = ast_walk_context_ignorepats(c, ip)) ) {
+			const char *ipname = ast_get_ignorepat_name(ip);
+			char ignorepat[AST_MAX_EXTENSION];
+			snprintf(buf, sizeof(buf), "'%s'", ipname);
+			snprintf(ignorepat, sizeof(ignorepat), "_%s.", ipname);
+			if (!exten || ast_extension_match(ignorepat, exten)) {
+				ast_cli(fd, "  Ignore pattern => %-45s [%s]\n",
+					buf, ast_get_ignorepat_registrar(ip));
+			}
+		}
+		if (!rinclude) {
+			struct ast_sw *sw = NULL;
+			while ( (sw = ast_walk_context_switches(c, sw)) ) {
+				snprintf(buf, sizeof(buf), "'%s/%s'",
+					ast_get_switch_name(sw),
+					ast_get_switch_data(sw));
+				ast_cli(fd, "  Alt. Switch =>    %-45s [%s]\n",
+					buf, ast_get_switch_registrar(sw));
+			}
 		}
-		ast_pbx_hangup_handler_run(c);
-	}
 
-	ast_set2_flag(ast_channel_flags(c), autoloopflag, AST_FLAG_IN_AUTOLOOP);
-	ast_clear_flag(ast_channel_flags(c), AST_FLAG_BRIDGE_HANGUP_RUN); /* from one round to the next, make sure this gets cleared */
-	pbx_destroy(ast_channel_pbx(c));
-	ast_channel_pbx_set(c, NULL);
+		ast_unlock_context(c);
 
-	if (!args || !args->no_hangup_chan) {
-		ast_hangup(c);
+		/* if we print something in context, make an empty line */
+		if (context_info_printed)
+			ast_cli(fd, "\n");
 	}
+	ast_unlock_contexts();
 
-	return AST_PBX_SUCCESS;
+	return (dpc->total_exten == old_total_exten) ? -1 : res;
 }
 
-/*!
- * \brief Increase call count for channel
- * \retval 0 on success
- * \retval non-zero if a configured limit (maxcalls, maxload, minmemfree) was reached
-*/
-static int increase_call_count(const struct ast_channel *c)
+static int show_debug_helper(int fd, const char *context, const char *exten, struct dialplan_counters *dpc, struct ast_include *rinclude, int includecount, const char *includes[])
 {
-	int failed = 0;
-	double curloadavg;
-#if defined(HAVE_SYSINFO)
-	long curfreemem;
-	struct sysinfo sys_info;
-#endif
-
-	ast_mutex_lock(&maxcalllock);
-	if (ast_option_maxcalls) {
-		if (countcalls >= ast_option_maxcalls) {
-			ast_log(LOG_WARNING, "Maximum call limit of %d calls exceeded by '%s'!\n", ast_option_maxcalls, ast_channel_name(c));
-			failed = -1;
-		}
-	}
-	if (ast_option_maxload) {
-		getloadavg(&curloadavg, 1);
-		if (curloadavg >= ast_option_maxload) {
-			ast_log(LOG_WARNING, "Maximum loadavg limit of %f load exceeded by '%s' (currently %f)!\n", ast_option_maxload, ast_channel_name(c), curloadavg);
-			failed = -1;
-		}
-	}
-#if defined(HAVE_SYSINFO)
-	if (option_minmemfree) {
-		if (!sysinfo(&sys_info)) {
-			/* make sure that the free system memory is above the configured low watermark
-			 * convert the amount of freeram from mem_units to MB */
-			curfreemem = sys_info.freeram * sys_info.mem_unit;
-			curfreemem /= 1024 * 1024;
-			if (curfreemem < option_minmemfree) {
-				ast_log(LOG_WARNING, "Available system memory (~%ldMB) is below the configured low watermark (%ldMB)\n", curfreemem, option_minmemfree);
-				failed = -1;
-			}
-		}
-	}
-#endif
+	struct ast_context *c = NULL;
+	int res = 0, old_total_exten = dpc->total_exten;
 
-	if (!failed) {
-		countcalls++;
-		totalcalls++;
-	}
-	ast_mutex_unlock(&maxcalllock);
+	ast_cli(fd,"\n     In-mem exten Trie for Fast Extension Pattern Matching:\n\n");
 
-	return failed;
-}
+	ast_cli(fd,"\n           Explanation: Node Contents Format = <char(s) to match>:<pattern?>:<specif>:[matched extension]\n");
+	ast_cli(fd,    "                        Where <char(s) to match> is a set of chars, any one of which should match the current character\n");
+	ast_cli(fd,    "                              <pattern?>: Y if this a pattern match (eg. _XZN[5-7]), N otherwise\n");
+	ast_cli(fd,    "                              <specif>: an assigned 'exactness' number for this matching char. The lower the number, the more exact the match\n");
+	ast_cli(fd,    "                              [matched exten]: If all chars matched to this point, which extension this matches. In form: EXTEN:<exten string>\n");
+	ast_cli(fd,    "                        In general, you match a trie node to a string character, from left to right. All possible matching chars\n");
+	ast_cli(fd,    "                        are in a string vertically, separated by an unbroken string of '+' characters.\n\n");
+	ast_rdlock_contexts();
 
-static void decrease_call_count(void)
-{
-	ast_mutex_lock(&maxcalllock);
-	if (countcalls > 0)
-		countcalls--;
-	ast_mutex_unlock(&maxcalllock);
-}
+	/* walk all contexts ... */
+	while ( (c = ast_walk_contexts(c)) ) {
+		int context_info_printed = 0;
 
-static void destroy_exten(struct ast_exten *e)
-{
-	if (e->priority == PRIORITY_HINT)
-		ast_remove_hint(e);
+		if (context && strcmp(ast_get_context_name(c), context))
+			continue;	/* skip this one, name doesn't match */
 
-	if (e->peer_table)
-		ast_hashtab_destroy(e->peer_table,0);
-	if (e->peer_label_table)
-		ast_hashtab_destroy(e->peer_label_table, 0);
-	if (e->datad)
-		e->datad(e->data);
-	ast_free(e);
-}
+		dpc->context_existence = 1;
 
-static void *pbx_thread(void *data)
-{
-	/* Oh joyeous kernel, we're a new thread, with nothing to do but
-	   answer this channel and get it going.
-	*/
-	/* NOTE:
-	   The launcher of this function _MUST_ increment 'countcalls'
-	   before invoking the function; it will be decremented when the
-	   PBX has finished running on the channel
-	 */
-	struct ast_channel *c = data;
+		if (!c->pattern_tree) {
+			/* Ignore check_return warning from Coverity for ast_exists_extension below */
+			ast_exists_extension(NULL, c->name, "s", 1, ""); /* do this to force the trie to built, if it is not already */
+		}
 
-	__ast_pbx_run(c, NULL);
-	decrease_call_count();
+		ast_rdlock_context(c);
 
-	pthread_exit(NULL);
+		dpc->total_context++;
+		ast_cli(fd, "[ Context '%s' created by '%s' ]\n",
+			ast_get_context_name(c), ast_get_context_registrar(c));
+		context_info_printed = 1;
 
-	return NULL;
-}
+		if (c->pattern_tree)
+		{
+			cli_match_char_tree(c->pattern_tree, " ", fd);
+		} else {
+			ast_cli(fd,"\n     No Pattern Trie present. Perhaps the context is empty...or there is trouble...\n\n");
+		}
 
-enum ast_pbx_result ast_pbx_start(struct ast_channel *c)
-{
-	pthread_t t;
+		ast_unlock_context(c);
 
-	if (!c) {
-		ast_log(LOG_WARNING, "Asked to start thread on NULL channel?\n");
-		return AST_PBX_FAILED;
+		/* if we print something in context, make an empty line */
+		if (context_info_printed)
+			ast_cli(fd, "\n");
 	}
+	ast_unlock_contexts();
 
-	if (!ast_test_flag(&ast_options, AST_OPT_FLAG_FULLY_BOOTED)) {
-		ast_log(LOG_WARNING, "PBX requires Asterisk to be fully booted\n");
-		return AST_PBX_FAILED;
-	}
+	return (dpc->total_exten == old_total_exten) ? -1 : res;
+}
 
-	if (increase_call_count(c))
-		return AST_PBX_CALL_LIMIT;
+static char *handle_show_dialplan(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+	char *exten = NULL, *context = NULL;
+	/* Variables used for different counters */
+	struct dialplan_counters counters;
+	const char *incstack[AST_PBX_MAX_STACK];
 
-	/* Start a new thread, and get something handling this channel. */
-	if (ast_pthread_create_detached(&t, NULL, pbx_thread, c)) {
-		ast_log(LOG_WARNING, "Failed to create new channel thread\n");
-		decrease_call_count();
-		return AST_PBX_FAILED;
+	switch (cmd) {
+	case CLI_INIT:
+		e->command = "dialplan show";
+		e->usage =
+			"Usage: dialplan show [[exten@]context]\n"
+			"       Show dialplan\n";
+		return NULL;
+	case CLI_GENERATE:
+		return complete_show_dialplan_context(a->line, a->word, a->pos, a->n);
 	}
 
-	return AST_PBX_SUCCESS;
-}
+	memset(&counters, 0, sizeof(counters));
 
-enum ast_pbx_result ast_pbx_run_args(struct ast_channel *c, struct ast_pbx_args *args)
-{
-	enum ast_pbx_result res = AST_PBX_SUCCESS;
+	if (a->argc != 2 && a->argc != 3)
+		return CLI_SHOWUSAGE;
 
-	if (!ast_test_flag(&ast_options, AST_OPT_FLAG_FULLY_BOOTED)) {
-		ast_log(LOG_WARNING, "PBX requires Asterisk to be fully booted\n");
-		return AST_PBX_FAILED;
+	/* we obtain [exten@]context? if yes, split them ... */
+	if (a->argc == 3) {
+		if (strchr(a->argv[2], '@')) {	/* split into exten & context */
+			context = ast_strdupa(a->argv[2]);
+			exten = strsep(&context, "@");
+			/* change empty strings to NULL */
+			if (ast_strlen_zero(exten))
+				exten = NULL;
+		} else { /* no '@' char, only context given */
+			context = ast_strdupa(a->argv[2]);
+		}
+		if (ast_strlen_zero(context))
+			context = NULL;
 	}
+	/* else Show complete dial plan, context and exten are NULL */
+	show_dialplan_helper(a->fd, context, exten, &counters, NULL, 0, incstack);
 
-	if (increase_call_count(c)) {
-		return AST_PBX_CALL_LIMIT;
+	/* check for input failure and throw some error messages */
+	if (context && !counters.context_existence) {
+		ast_cli(a->fd, "There is no existence of '%s' context\n", context);
+		return CLI_FAILURE;
 	}
 
-	res = __ast_pbx_run(c, args);
-
-	decrease_call_count();
+	if (exten && !counters.extension_existence) {
+		if (context)
+			ast_cli(a->fd, "There is no existence of %s@%s extension\n",
+				exten, context);
+		else
+			ast_cli(a->fd,
+				"There is no existence of '%s' extension in all contexts\n",
+				exten);
+		return CLI_FAILURE;
+	}
 
-	return res;
-}
+	ast_cli(a->fd,"-= %d %s (%d %s) in %d %s. =-\n",
+				counters.total_exten, counters.total_exten == 1 ? "extension" : "extensions",
+				counters.total_prio, counters.total_prio == 1 ? "priority" : "priorities",
+				counters.total_context, counters.total_context == 1 ? "context" : "contexts");
 
-enum ast_pbx_result ast_pbx_run(struct ast_channel *c)
-{
-	return ast_pbx_run_args(c, NULL);
+	/* everything ok */
+	return CLI_SUCCESS;
 }
 
-int ast_active_calls(void)
+/*! \brief Send ack once */
+static char *handle_debug_dialplan(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
 {
-	return countcalls;
-}
+	char *exten = NULL, *context = NULL;
+	/* Variables used for different counters */
+	struct dialplan_counters counters;
+	const char *incstack[AST_PBX_MAX_STACK];
 
-int ast_processed_calls(void)
-{
-	return totalcalls;
-}
+	switch (cmd) {
+	case CLI_INIT:
+		e->command = "dialplan debug";
+		e->usage =
+			"Usage: dialplan debug [context]\n"
+			"       Show dialplan context Trie(s). Usually only useful to folks debugging the deep internals of the fast pattern matcher\n";
+		return NULL;
+	case CLI_GENERATE:
+		return complete_show_dialplan_context(a->line, a->word, a->pos, a->n);
+	}
 
-int pbx_set_autofallthrough(int newval)
-{
-	int oldval = autofallthrough;
-	autofallthrough = newval;
-	return oldval;
-}
+	memset(&counters, 0, sizeof(counters));
 
-int pbx_set_extenpatternmatchnew(int newval)
-{
-	int oldval = extenpatternmatchnew;
-	extenpatternmatchnew = newval;
-	return oldval;
-}
+	if (a->argc != 2 && a->argc != 3)
+		return CLI_SHOWUSAGE;
 
-void pbx_set_overrideswitch(const char *newval)
-{
-	if (overrideswitch) {
-		ast_free(overrideswitch);
+	/* we obtain [exten@]context? if yes, split them ... */
+	/* note: we ignore the exten totally here .... */
+	if (a->argc == 3) {
+		if (strchr(a->argv[2], '@')) {	/* split into exten & context */
+			context = ast_strdupa(a->argv[2]);
+			exten = strsep(&context, "@");
+			/* change empty strings to NULL */
+			if (ast_strlen_zero(exten))
+				exten = NULL;
+		} else { /* no '@' char, only context given */
+			context = ast_strdupa(a->argv[2]);
+		}
+		if (ast_strlen_zero(context))
+			context = NULL;
 	}
-	if (!ast_strlen_zero(newval)) {
-		overrideswitch = ast_strdup(newval);
-	} else {
-		overrideswitch = NULL;
+	/* else Show complete dial plan, context and exten are NULL */
+	show_debug_helper(a->fd, context, exten, &counters, NULL, 0, incstack);
+
+	/* check for input failure and throw some error messages */
+	if (context && !counters.context_existence) {
+		ast_cli(a->fd, "There is no existence of '%s' context\n", context);
+		return CLI_FAILURE;
 	}
-}
 
-/*!
- * \brief lookup for a context with a given name,
- * \retval found context or NULL if not found.
- */
-static struct ast_context *find_context(const char *context)
-{
-	struct fake_context item;
 
-	ast_copy_string(item.name, context, sizeof(item.name));
+	ast_cli(a->fd,"-= %d %s. =-\n",
+			counters.total_context, counters.total_context == 1 ? "context" : "contexts");
 
-	return ast_hashtab_lookup(contexts_table, &item);
+	/* everything ok */
+	return CLI_SUCCESS;
 }
 
-/*!
- * \brief lookup for a context with a given name,
- * \retval with conlock held if found.
- * \retval NULL if not found.
- */
-static struct ast_context *find_context_locked(const char *context)
+/*! \brief Send ack once */
+static void manager_dpsendack(struct mansession *s, const struct message *m)
 {
-	struct ast_context *c;
-	struct fake_context item;
-
-	ast_copy_string(item.name, context, sizeof(item.name));
-
-	ast_rdlock_contexts();
-	c = ast_hashtab_lookup(contexts_table, &item);
-	if (!c) {
-		ast_unlock_contexts();
-	}
-
-	return c;
+	astman_send_listack(s, m, "DialPlan list will follow", "start");
 }
 
-/*!
- * \brief Remove included contexts.
- * This function locks contexts list by &conlist, search for the right context
- * structure, leave context list locked and call ast_context_remove_include2
- * which removes include, unlock contexts list and return ...
+/*! \brief Show dialplan extensions
+ * XXX this function is similar but not exactly the same as the CLI's
+ * show dialplan. Must check whether the difference is intentional or not.
  */
-int ast_context_remove_include(const char *context, const char *include, const char *registrar)
+static int manager_show_dialplan_helper(struct mansession *s, const struct message *m,
+					const char *actionidtext, const char *context,
+					const char *exten, struct dialplan_counters *dpc,
+					struct ast_include *rinclude)
 {
-	int ret = -1;
 	struct ast_context *c;
+	int res = 0, old_total_exten = dpc->total_exten;
 
-	c = find_context_locked(context);
-	if (c) {
-		/* found, remove include from this context ... */
-		ret = ast_context_remove_include2(c, include, registrar);
-		ast_unlock_contexts();
-	}
-	return ret;
-}
+	if (ast_strlen_zero(exten))
+		exten = NULL;
+	if (ast_strlen_zero(context))
+		context = NULL;
 
-/*!
- * \brief Locks context, remove included contexts, unlocks context.
- * When we call this function, &conlock lock must be locked, because when
- * we giving *con argument, some process can remove/change this context
- * and after that there can be segfault.
- *
- * \retval 0 on success.
- * \retval -1 on failure.
- */
-int ast_context_remove_include2(struct ast_context *con, const char *include, const char *registrar)
-{
-	struct ast_include *i, *pi = NULL;
-	int ret = -1;
-
-	ast_wrlock_context(con);
+	ast_debug(3, "manager_show_dialplan: Context: -%s- Extension: -%s-\n", context, exten);
 
-	/* find our include */
-	for (i = con->includes; i; pi = i, i = i->next) {
-		if (!strcmp(i->name, include) &&
-				(!registrar || !strcmp(i->registrar, registrar))) {
-			/* remove from list */
-			ast_verb(3, "Removing inclusion of context '%s' in context '%s; registrar=%s'\n", include, ast_get_context_name(con), registrar);
-			if (pi)
-				pi->next = i->next;
-			else
-				con->includes = i->next;
-			/* free include and return */
-			ast_destroy_timing(&(i->timing));
-			ast_free(i);
-			ret = 0;
-			break;
-		}
+	/* try to lock contexts */
+	if (ast_rdlock_contexts()) {
+		astman_send_error(s, m, "Failed to lock contexts");
+		ast_log(LOG_WARNING, "Failed to lock contexts list for manager: listdialplan\n");
+		return -1;
 	}
 
-	ast_unlock_context(con);
-
-	return ret;
-}
-
-/*!
- * \note This function locks contexts list by &conlist, search for the rigt context
- * structure, leave context list locked and call ast_context_remove_switch2
- * which removes switch, unlock contexts list and return ...
- */
-int ast_context_remove_switch(const char *context, const char *sw, const char *data, const char *registrar)
-{
-	int ret = -1; /* default error return */
-	struct ast_context *c;
+	c = NULL;		/* walk all contexts ... */
+	while ( (c = ast_walk_contexts(c)) ) {
+		struct ast_exten *e;
+		struct ast_include *i;
+		struct ast_ignorepat *ip;
 
-	c = find_context_locked(context);
-	if (c) {
-		/* remove switch from this context ... */
-		ret = ast_context_remove_switch2(c, sw, data, registrar);
-		ast_unlock_contexts();
-	}
-	return ret;
-}
+		if (context && strcmp(ast_get_context_name(c), context) != 0)
+			continue;	/* not the name we want */
 
-/*!
- * \brief This function locks given context, removes switch, unlock context and
- * return.
- * \note When we call this function, &conlock lock must be locked, because when
- * we giving *con argument, some process can remove/change this context
- * and after that there can be segfault.
- *
- */
-int ast_context_remove_switch2(struct ast_context *con, const char *sw, const char *data, const char *registrar)
-{
-	struct ast_sw *i;
-	int ret = -1;
+		dpc->context_existence = 1;
+		dpc->total_context++;
 
-	ast_wrlock_context(con);
+		ast_debug(3, "manager_show_dialplan: Found Context: %s \n", ast_get_context_name(c));
 
-	/* walk switches */
-	AST_LIST_TRAVERSE_SAFE_BEGIN(&con->alts, i, list) {
-		if (!strcmp(i->name, sw) && !strcmp(i->data, data) &&
-			(!registrar || !strcmp(i->registrar, registrar))) {
-			/* found, remove from list */
-			ast_verb(3, "Removing switch '%s' from context '%s; registrar=%s'\n", sw, ast_get_context_name(con), registrar);
-			AST_LIST_REMOVE_CURRENT(list);
-			ast_free(i); /* free switch and return */
-			ret = 0;
-			break;
+		if (ast_rdlock_context(c)) {	/* failed to lock */
+			ast_debug(3, "manager_show_dialplan: Failed to lock context\n");
+			continue;
 		}
-	}
-	AST_LIST_TRAVERSE_SAFE_END;
 
-	ast_unlock_context(con);
-
-	return ret;
-}
-
-/*! \note This function will lock conlock. */
-int ast_context_remove_extension(const char *context, const char *extension, int priority, const char *registrar)
-{
-	return ast_context_remove_extension_callerid(context, extension, priority, NULL, AST_EXT_MATCHCID_ANY, registrar);
-}
-
-int ast_context_remove_extension_callerid(const char *context, const char *extension, int priority, const char *callerid, int matchcallerid, const char *registrar)
-{
-	int ret = -1; /* default error return */
-	struct ast_context *c;
+		/* XXX note- an empty context is not printed */
+		e = NULL;		/* walk extensions in context  */
+		while ( (e = ast_walk_context_extensions(c, e)) ) {
+			struct ast_exten *p;
 
-	c = find_context_locked(context);
-	if (c) { /* ... remove extension ... */
-		ret = ast_context_remove_extension_callerid2(c, extension, priority, callerid,
-			matchcallerid, registrar, 0);
-		ast_unlock_contexts();
-	}
+			/* looking for extension? is this our extension? */
+			if (exten && !ast_extension_match(ast_get_extension_name(e), exten)) {
+				/* not the one we are looking for, continue */
+				ast_debug(3, "manager_show_dialplan: Skipping extension %s\n", ast_get_extension_name(e));
+				continue;
+			}
+			ast_debug(3, "manager_show_dialplan: Found Extension: %s \n", ast_get_extension_name(e));
 
-	return ret;
-}
+			dpc->extension_existence = 1;
 
-/*!
- * \brief This functionc locks given context, search for the right extension and
- * fires out all peer in this extensions with given priority. If priority
- * is set to 0, all peers are removed. After that, unlock context and
- * return.
- * \note When do you want to call this function, make sure that &conlock is locked,
- * because some process can handle with your *con context before you lock
- * it.
- *
- */
-int ast_context_remove_extension2(struct ast_context *con, const char *extension, int priority, const char *registrar, int already_locked)
-{
-	return ast_context_remove_extension_callerid2(con, extension, priority, NULL, AST_EXT_MATCHCID_ANY, registrar, already_locked);
-}
+			dpc->total_exten++;
 
-int ast_context_remove_extension_callerid2(struct ast_context *con, const char *extension, int priority, const char *callerid, int matchcallerid, const char *registrar, int already_locked)
-{
-	struct ast_exten *exten, *prev_exten = NULL;
-	struct ast_exten *peer;
-	struct ast_exten ex, *exten2, *exten3;
-	char dummy_name[1024];
-	struct ast_exten *previous_peer = NULL;
-	struct ast_exten *next_peer = NULL;
-	int found = 0;
+			p = NULL;		/* walk next extension peers */
+			while ( (p = ast_walk_extension_priorities(e, p)) ) {
+				int prio = ast_get_extension_priority(p);
 
-	if (!already_locked)
-		ast_wrlock_context(con);
+				dpc->total_prio++;
+				if (!dpc->total_items++)
+					manager_dpsendack(s, m);
+				astman_append(s, "Event: ListDialplan\r\n%s", actionidtext);
+				astman_append(s, "Context: %s\r\nExtension: %s\r\n", ast_get_context_name(c), ast_get_extension_name(e) );
 
-#ifdef NEED_DEBUG
-	ast_verb(3,"Removing %s/%s/%d%s%s from trees, registrar=%s\n", con->name, extension, priority, matchcallerid ? "/" : "", matchcallerid ? callerid : "", registrar);
-#endif
-#ifdef CONTEXT_DEBUG
-	check_contexts(__FILE__, __LINE__);
-#endif
-	/* find this particular extension */
-	ex.exten = dummy_name;
-	ex.matchcid = matchcallerid;
-	ex.cidmatch = callerid;
-	ast_copy_string(dummy_name, extension, sizeof(dummy_name));
-	exten = ast_hashtab_lookup(con->root_table, &ex);
-	if (exten) {
-		if (priority == 0) {
-			exten2 = ast_hashtab_remove_this_object(con->root_table, exten);
-			if (!exten2)
-				ast_log(LOG_ERROR,"Trying to delete the exten %s from context %s, but could not remove from the root_table\n", extension, con->name);
-			if (con->pattern_tree) {
-				struct match_char *x = add_exten_to_pattern_tree(con, exten, 1);
+				/* XXX maybe make this conditional, if p != e ? */
+				if (ast_get_extension_label(p))
+					astman_append(s, "ExtensionLabel: %s\r\n", ast_get_extension_label(p));
 
-				if (x->exten) { /* this test for safety purposes */
-					x->deleted = 1; /* with this marked as deleted, it will never show up in the scoreboard, and therefore never be found */
-					x->exten = 0; /* get rid of what will become a bad pointer */
+				if (prio == PRIORITY_HINT) {
+					astman_append(s, "Priority: hint\r\nApplication: %s\r\n", ast_get_extension_app(p));
 				} else {
-					ast_log(LOG_WARNING,"Trying to delete an exten from a context, but the pattern tree node returned isn't a full extension\n");
+					astman_append(s, "Priority: %d\r\nApplication: %s\r\nAppData: %s\r\n", prio, ast_get_extension_app(p), (char *) ast_get_extension_app_data(p));
 				}
+				astman_append(s, "Registrar: %s\r\n\r\n", ast_get_extension_registrar(e));
 			}
-		} else {
-			ex.priority = priority;
-			exten2 = ast_hashtab_lookup(exten->peer_table, &ex);
-			if (exten2) {
-				if (exten2->label) { /* if this exten has a label, remove that, too */
-					exten3 = ast_hashtab_remove_this_object(exten->peer_label_table,exten2);
-					if (!exten3)
-						ast_log(LOG_ERROR,"Did not remove this priority label (%d/%s) from the peer_label_table of context %s, extension %s!\n", priority, exten2->label, con->name, exten2->exten);
-				}
+		}
 
-				exten3 = ast_hashtab_remove_this_object(exten->peer_table, exten2);
-				if (!exten3)
-					ast_log(LOG_ERROR,"Did not remove this priority (%d) from the peer_table of context %s, extension %s!\n", priority, con->name, exten2->exten);
-				if (exten2 == exten && exten2->peer) {
-					exten2 = ast_hashtab_remove_this_object(con->root_table, exten);
-					ast_hashtab_insert_immediate(con->root_table, exten2->peer);
-				}
-				if (ast_hashtab_size(exten->peer_table) == 0) {
-					/* well, if the last priority of an exten is to be removed,
-					   then, the extension is removed, too! */
-					exten3 = ast_hashtab_remove_this_object(con->root_table, exten);
-					if (!exten3)
-						ast_log(LOG_ERROR,"Did not remove this exten (%s) from the context root_table (%s) (priority %d)\n", exten->exten, con->name, priority);
-					if (con->pattern_tree) {
-						struct match_char *x = add_exten_to_pattern_tree(con, exten, 1);
-						if (x->exten) { /* this test for safety purposes */
-							x->deleted = 1; /* with this marked as deleted, it will never show up in the scoreboard, and therefore never be found */
-							x->exten = 0; /* get rid of what will become a bad pointer */
-						}
-					}
-				}
+		i = NULL;		/* walk included and write info ... */
+		while ( (i = ast_walk_context_includes(c, i)) ) {
+			if (exten) {
+				/* Check all includes for the requested extension */
+				manager_show_dialplan_helper(s, m, actionidtext, ast_get_include_name(i), exten, dpc, i);
 			} else {
-				ast_log(LOG_ERROR,"Could not find priority %d of exten %s in context %s!\n",
-						priority, exten->exten, con->name);
+				if (!dpc->total_items++)
+					manager_dpsendack(s, m);
+				astman_append(s, "Event: ListDialplan\r\n%s", actionidtext);
+				astman_append(s, "Context: %s\r\nIncludeContext: %s\r\nRegistrar: %s\r\n", ast_get_context_name(c), ast_get_include_name(i), ast_get_include_registrar(i));
+				astman_append(s, "\r\n");
+				ast_debug(3, "manager_show_dialplan: Found Included context: %s \n", ast_get_include_name(i));
 			}
 		}
-	} else {
-		/* hmmm? this exten is not in this pattern tree? */
-		ast_log(LOG_WARNING,"Cannot find extension %s in root_table in context %s\n",
-				extension, con->name);
-	}
-#ifdef NEED_DEBUG
-	if (con->pattern_tree) {
-		ast_log(LOG_NOTICE,"match char tree after exten removal:\n");
-		log_match_char_tree(con->pattern_tree, " ");
-	}
-#endif
 
-	/* scan the extension list to find first matching extension-registrar */
-	for (exten = con->root; exten; prev_exten = exten, exten = exten->next) {
-		if (!strcmp(exten->exten, extension) &&
-			(!registrar || !strcmp(exten->registrar, registrar)) &&
-			(!matchcallerid || (!ast_strlen_zero(callerid) && !ast_strlen_zero(exten->cidmatch) && !strcmp(exten->cidmatch, callerid)) || (ast_strlen_zero(callerid) && ast_strlen_zero(exten->cidmatch))))
-			break;
-	}
-	if (!exten) {
-		/* we can't find right extension */
-		if (!already_locked)
-			ast_unlock_context(con);
-		return -1;
-	}
-
-	/* scan the priority list to remove extension with exten->priority == priority */
-	for (peer = exten, next_peer = exten->peer ? exten->peer : exten->next;
-		 peer && !strcmp(peer->exten, extension) &&
-			(!callerid || (!matchcallerid && !peer->matchcid) || (matchcallerid && peer->matchcid && !strcmp(peer->cidmatch, callerid))) ;
-			peer = next_peer, next_peer = next_peer ? (next_peer->peer ? next_peer->peer : next_peer->next) : NULL) {
-
-		if ((priority == 0 || peer->priority == priority) &&
-				(!registrar || !strcmp(peer->registrar, registrar) )) {
-			found = 1;
+		ip = NULL;	/* walk ignore patterns and write info ... */
+		while ( (ip = ast_walk_context_ignorepats(c, ip)) ) {
+			const char *ipname = ast_get_ignorepat_name(ip);
+			char ignorepat[AST_MAX_EXTENSION];
 
-			/* we are first priority extension? */
-			if (!previous_peer) {
-				/*
-				 * We are first in the priority chain, so must update the extension chain.
-				 * The next node is either the next priority or the next extension
-				 */
-				struct ast_exten *next_node = peer->peer ? peer->peer : peer->next;
-				if (peer->peer) {
-					/* move the peer_table and peer_label_table down to the next peer, if
-					   it is there */
-					peer->peer->peer_table = peer->peer_table;
-					peer->peer->peer_label_table = peer->peer_label_table;
-					peer->peer_table = NULL;
-					peer->peer_label_table = NULL;
-				}
-				if (!prev_exten) {	/* change the root... */
-					con->root = next_node;
-				} else {
-					prev_exten->next = next_node; /* unlink */
-				}
-				if (peer->peer)	{ /* update the new head of the pri list */
-					peer->peer->next = peer->next;
-				}
-			} else { /* easy, we are not first priority in extension */
-				previous_peer->peer = peer->peer;
+			snprintf(ignorepat, sizeof(ignorepat), "_%s.", ipname);
+			if (!exten || ast_extension_match(ignorepat, exten)) {
+				if (!dpc->total_items++)
+					manager_dpsendack(s, m);
+				astman_append(s, "Event: ListDialplan\r\n%s", actionidtext);
+				astman_append(s, "Context: %s\r\nIgnorePattern: %s\r\nRegistrar: %s\r\n", ast_get_context_name(c), ipname, ast_get_ignorepat_registrar(ip));
+				astman_append(s, "\r\n");
+			}
+		}
+		if (!rinclude) {
+			struct ast_sw *sw = NULL;
+			while ( (sw = ast_walk_context_switches(c, sw)) ) {
+				if (!dpc->total_items++)
+					manager_dpsendack(s, m);
+				astman_append(s, "Event: ListDialplan\r\n%s", actionidtext);
+				astman_append(s, "Context: %s\r\nSwitch: %s/%s\r\nRegistrar: %s\r\n", ast_get_context_name(c), ast_get_switch_name(sw), ast_get_switch_data(sw), ast_get_switch_registrar(sw));
+				astman_append(s, "\r\n");
+				ast_debug(3, "manager_show_dialplan: Found Switch : %s \n", ast_get_switch_name(sw));
 			}
-
-
-			/* now, free whole priority extension */
-			destroy_exten(peer);
-		} else {
-			previous_peer = peer;
 		}
-	}
-	if (!already_locked)
-		ast_unlock_context(con);
-	return found ? 0 : -1;
-}
-
-
-/*!
- * \note This function locks contexts list by &conlist, searches for the right context
- * structure, and locks the macrolock mutex in that context.
- * macrolock is used to limit a macro to be executed by one call at a time.
- * \param context The context
- */
-int ast_context_lockmacro(const char *context)
-{
-	struct ast_context *c;
-	int ret = -1;
-
-	c = find_context_locked(context);
-	if (c) {
-		ast_unlock_contexts();
 
-		/* if we found context, lock macrolock */
-		ret = ast_mutex_lock(&c->macrolock);
+		ast_unlock_context(c);
 	}
+	ast_unlock_contexts();
 
-	return ret;
+	if (dpc->total_exten == old_total_exten) {
+		ast_debug(3, "manager_show_dialplan: Found nothing new\n");
+		/* Nothing new under the sun */
+		return -1;
+	} else {
+		return res;
+	}
 }
 
-/*!
- * \note This function locks contexts list by &conlist, searches for the right context
- * structure, and unlocks the macrolock mutex in that context.
- * macrolock is used to limit a macro to be executed by one call at a time.
- * \param context The context
- */
-int ast_context_unlockmacro(const char *context)
+/*! \brief  Manager listing of dial plan */
+static int manager_show_dialplan(struct mansession *s, const struct message *m)
 {
-	struct ast_context *c;
-	int ret = -1;
-
-	c = find_context_locked(context);
-	if (c) {
-		ast_unlock_contexts();
+	const char *exten, *context;
+	const char *id = astman_get_header(m, "ActionID");
+	char idtext[256];
 
-		/* if we found context, unlock macrolock */
-		ret = ast_mutex_unlock(&c->macrolock);
-	}
+	/* Variables used for different counters */
+	struct dialplan_counters counters;
 
-	return ret;
-}
+	if (!ast_strlen_zero(id))
+		snprintf(idtext, sizeof(idtext), "ActionID: %s\r\n", id);
+	else
+		idtext[0] = '\0';
 
-/*! \brief Dynamically register a new dial plan application */
-int ast_register_application2(const char *app, int (*execute)(struct ast_channel *, const char *), const char *synopsis, const char *description, void *mod)
-{
-	struct ast_app *tmp;
-	struct ast_app *cur;
-	int length;
-#ifdef AST_XML_DOCS
-	char *tmpxml;
-#endif
+	memset(&counters, 0, sizeof(counters));
 
-	AST_RWLIST_WRLOCK(&apps);
-	cur = pbx_findapp_nolock(app);
-	if (cur) {
-		ast_log(LOG_WARNING, "Already have an application '%s'\n", app);
-		AST_RWLIST_UNLOCK(&apps);
-		return -1;
-	}
+	exten = astman_get_header(m, "Extension");
+	context = astman_get_header(m, "Context");
 
-	length = sizeof(*tmp) + strlen(app) + 1;
+	manager_show_dialplan_helper(s, m, idtext, context, exten, &counters, NULL);
 
-	if (!(tmp = ast_calloc(1, length))) {
-		AST_RWLIST_UNLOCK(&apps);
-		return -1;
-	}
+	if (!ast_strlen_zero(context) && !counters.context_existence) {
+		char errorbuf[BUFSIZ];
 
-	if (ast_string_field_init(tmp, 128)) {
-		AST_RWLIST_UNLOCK(&apps);
-		ast_free(tmp);
-		return -1;
+		snprintf(errorbuf, sizeof(errorbuf), "Did not find context %s", context);
+		astman_send_error(s, m, errorbuf);
+		return 0;
 	}
+	if (!ast_strlen_zero(exten) && !counters.extension_existence) {
+		char errorbuf[BUFSIZ];
 
-	strcpy(tmp->name, app);
-	tmp->execute = execute;
-	tmp->module = mod;
-
-#ifdef AST_XML_DOCS
-	/* Try to lookup the docs in our XML documentation database */
-	if (ast_strlen_zero(synopsis) && ast_strlen_zero(description)) {
-		/* load synopsis */
-		tmpxml = ast_xmldoc_build_synopsis("application", app, ast_module_name(tmp->module));
-		ast_string_field_set(tmp, synopsis, tmpxml);
-		ast_free(tmpxml);
-
-		/* load description */
-		tmpxml = ast_xmldoc_build_description("application", app, ast_module_name(tmp->module));
-		ast_string_field_set(tmp, description, tmpxml);
-		ast_free(tmpxml);
-
-		/* load syntax */
-		tmpxml = ast_xmldoc_build_syntax("application", app, ast_module_name(tmp->module));
-		ast_string_field_set(tmp, syntax, tmpxml);
-		ast_free(tmpxml);
-
-		/* load arguments */
-		tmpxml = ast_xmldoc_build_arguments("application", app, ast_module_name(tmp->module));
-		ast_string_field_set(tmp, arguments, tmpxml);
-		ast_free(tmpxml);
-
-		/* load seealso */
-		tmpxml = ast_xmldoc_build_seealso("application", app, ast_module_name(tmp->module));
-		ast_string_field_set(tmp, seealso, tmpxml);
-		ast_free(tmpxml);
-		tmp->docsrc = AST_XML_DOC;
-	} else {
-#endif
-		ast_string_field_set(tmp, synopsis, synopsis);
-		ast_string_field_set(tmp, description, description);
-#ifdef AST_XML_DOCS
-		tmp->docsrc = AST_STATIC_DOC;
+		if (!ast_strlen_zero(context))
+			snprintf(errorbuf, sizeof(errorbuf), "Did not find extension %s@%s", exten, context);
+		else
+			snprintf(errorbuf, sizeof(errorbuf), "Did not find extension %s in any context", exten);
+		astman_send_error(s, m, errorbuf);
+		return 0;
 	}
-#endif
 
-	/* Store in alphabetical order */
-	AST_RWLIST_TRAVERSE_SAFE_BEGIN(&apps, cur, list) {
-		if (strcasecmp(tmp->name, cur->name) < 0) {
-			AST_RWLIST_INSERT_BEFORE_CURRENT(tmp, list);
-			break;
-		}
+	if (!counters.total_items) {
+		manager_dpsendack(s, m);
 	}
-	AST_RWLIST_TRAVERSE_SAFE_END;
-	if (!cur)
-		AST_RWLIST_INSERT_TAIL(&apps, tmp, list);
 
-	ast_verb(2, "Registered application '" COLORIZE_FMT "'\n", COLORIZE(COLOR_BRCYAN, 0, tmp->name));
-
-	AST_RWLIST_UNLOCK(&apps);
+	astman_send_list_complete_start(s, m, "ShowDialPlanComplete", counters.total_items);
+	astman_append(s,
+		"ListExtensions: %d\r\n"
+		"ListPriorities: %d\r\n"
+		"ListContexts: %d\r\n",
+		counters.total_exten, counters.total_prio, counters.total_context);
+	astman_send_list_complete_end(s);
 
+	/* everything ok */
 	return 0;
 }
 
-/*
- * Append to the list. We don't have a tail pointer because we need
- * to scan the list anyways to check for duplicates during insertion.
- */
-int ast_register_switch(struct ast_switch *sw)
+#ifdef AST_DEVMODE
+static char *handle_show_device2extenstate(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
 {
-	struct ast_switch *tmp;
+	struct ast_devstate_aggregate agg;
+	int i, j, exten, combined;
 
-	AST_RWLIST_WRLOCK(&switches);
-	AST_RWLIST_TRAVERSE(&switches, tmp, list) {
-		if (!strcasecmp(tmp->name, sw->name)) {
-			AST_RWLIST_UNLOCK(&switches);
-			ast_log(LOG_WARNING, "Switch '%s' already found\n", sw->name);
-			return -1;
+	switch (cmd) {
+	case CLI_INIT:
+		e->command = "core show device2extenstate";
+		e->usage =
+			"Usage: core show device2extenstate\n"
+			"       Lists device state to extension state combinations.\n";
+	case CLI_GENERATE:
+		return NULL;
+	}
+	for (i = 0; i < AST_DEVICE_TOTAL; i++) {
+		for (j = 0; j < AST_DEVICE_TOTAL; j++) {
+			ast_devstate_aggregate_init(&agg);
+			ast_devstate_aggregate_add(&agg, i);
+			ast_devstate_aggregate_add(&agg, j);
+			combined = ast_devstate_aggregate_result(&agg);
+			exten = ast_devstate_to_extenstate(combined);
+			ast_cli(a->fd, "\n Exten:%14s  CombinedDevice:%12s  Dev1:%12s  Dev2:%12s", ast_extension_state2str(exten), ast_devstate_str(combined), ast_devstate_str(j), ast_devstate_str(i));
 		}
 	}
-	AST_RWLIST_INSERT_TAIL(&switches, sw, list);
-	AST_RWLIST_UNLOCK(&switches);
-
-	return 0;
-}
-
-void ast_unregister_switch(struct ast_switch *sw)
-{
-	AST_RWLIST_WRLOCK(&switches);
-	AST_RWLIST_REMOVE(&switches, sw, list);
-	AST_RWLIST_UNLOCK(&switches);
+	ast_cli(a->fd, "\n");
+	return CLI_SUCCESS;
 }
-
-/*
- * Help for CLI commands ...
- */
-
-static void print_app_docs(struct ast_app *aa, int fd)
-{
-#ifdef AST_XML_DOCS
-	char *synopsis = NULL, *description = NULL, *arguments = NULL, *seealso = NULL;
-	if (aa->docsrc == AST_XML_DOC) {
-		synopsis = ast_xmldoc_printable(S_OR(aa->synopsis, "Not available"), 1);
-		description = ast_xmldoc_printable(S_OR(aa->description, "Not available"), 1);
-		arguments = ast_xmldoc_printable(S_OR(aa->arguments, "Not available"), 1);
-		seealso = ast_xmldoc_printable(S_OR(aa->seealso, "Not available"), 1);
-		if (!synopsis || !description || !arguments || !seealso) {
-			goto free_docs;
-		}
-		ast_cli(fd, "\n"
-			"%s  -= Info about application '%s' =- %s\n\n"
-			COLORIZE_FMT "\n"
-			"%s\n\n"
-			COLORIZE_FMT "\n"
-			"%s\n\n"
-			COLORIZE_FMT "\n"
-			"%s%s%s\n\n"
-			COLORIZE_FMT "\n"
-			"%s\n\n"
-			COLORIZE_FMT "\n"
-			"%s\n",
-			ast_term_color(COLOR_MAGENTA, 0), aa->name, ast_term_reset(),
-			COLORIZE(COLOR_MAGENTA, 0, "[Synopsis]"), synopsis,
-			COLORIZE(COLOR_MAGENTA, 0, "[Description]"), description,
-			COLORIZE(COLOR_MAGENTA, 0, "[Syntax]"),
-				ast_term_color(COLOR_CYAN, 0), S_OR(aa->syntax, "Not available"), ast_term_reset(),
-			COLORIZE(COLOR_MAGENTA, 0, "[Arguments]"), arguments,
-			COLORIZE(COLOR_MAGENTA, 0, "[See Also]"), seealso);
-free_docs:
-		ast_free(synopsis);
-		ast_free(description);
-		ast_free(arguments);
-		ast_free(seealso);
-	} else
 #endif
-	{
-		ast_cli(fd, "\n"
-			"%s  -= Info about application '%s' =- %s\n\n"
-			COLORIZE_FMT "\n"
-			COLORIZE_FMT "\n\n"
-			COLORIZE_FMT "\n"
-			COLORIZE_FMT "\n\n"
-			COLORIZE_FMT "\n"
-			COLORIZE_FMT "\n\n"
-			COLORIZE_FMT "\n"
-			COLORIZE_FMT "\n\n"
-			COLORIZE_FMT "\n"
-			COLORIZE_FMT "\n",
-			ast_term_color(COLOR_MAGENTA, 0), aa->name, ast_term_reset(),
-			COLORIZE(COLOR_MAGENTA, 0, "[Synopsis]"),
-			COLORIZE(COLOR_CYAN, 0, S_OR(aa->synopsis, "Not available")),
-			COLORIZE(COLOR_MAGENTA, 0, "[Description]"),
-			COLORIZE(COLOR_CYAN, 0, S_OR(aa->description, "Not available")),
-			COLORIZE(COLOR_MAGENTA, 0, "[Syntax]"),
-			COLORIZE(COLOR_CYAN, 0, S_OR(aa->syntax, "Not available")),
-			COLORIZE(COLOR_MAGENTA, 0, "[Arguments]"),
-			COLORIZE(COLOR_CYAN, 0, S_OR(aa->arguments, "Not available")),
-			COLORIZE(COLOR_MAGENTA, 0, "[See Also]"),
-			COLORIZE(COLOR_CYAN, 0, S_OR(aa->seealso, "Not available")));
-	}
-}
 
-/*
- * \brief 'show application' CLI command implementation function...
- */
-static char *handle_show_application(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+static char *handle_set_extenpatternmatchnew(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
 {
-	struct ast_app *aa;
-	int app, no_registered_app = 1;
+	int oldval = 0;
 
 	switch (cmd) {
 	case CLI_INIT:
-		e->command = "core show application";
+		e->command = "dialplan set extenpatternmatchnew true";
 		e->usage =
-			"Usage: core show application <application> [<application> [<application> [...]]]\n"
-			"       Describes a particular application.\n";
+			"Usage: dialplan set extenpatternmatchnew true|false\n"
+			"       Use the NEW extension pattern matching algorithm, true or false.\n";
 		return NULL;
 	case CLI_GENERATE:
-		/*
-		 * There is a possibility to show informations about more than one
-		 * application at one time. You can type 'show application Dial Echo' and
-		 * you will see informations about these two applications ...
-		 */
-		return ast_complete_applications(a->line, a->word, a->n);
+		return NULL;
 	}
 
-	if (a->argc < 4) {
+	if (a->argc != 4)
 		return CLI_SHOWUSAGE;
-	}
 
-	AST_RWLIST_RDLOCK(&apps);
-	AST_RWLIST_TRAVERSE(&apps, aa, list) {
-		/* Check for each app that was supplied as an argument */
-		for (app = 3; app < a->argc; app++) {
-			if (strcasecmp(aa->name, a->argv[app])) {
-				continue;
-			}
-
-			/* We found it! */
-			no_registered_app = 0;
-
-			print_app_docs(aa, a->fd);
-		}
-	}
-	AST_RWLIST_UNLOCK(&apps);
+	oldval =  pbx_set_extenpatternmatchnew(1);
 
-	/* we found at least one app? no? */
-	if (no_registered_app) {
-		ast_cli(a->fd, "Your application(s) is (are) not registered\n");
-		return CLI_FAILURE;
-	}
+	if (oldval)
+		ast_cli(a->fd, "\n    -- Still using the NEW pattern match algorithm for extension names in the dialplan.\n");
+	else
+		ast_cli(a->fd, "\n    -- Switched to using the NEW pattern match algorithm for extension names in the dialplan.\n");
 
 	return CLI_SUCCESS;
 }
 
-/*! \brief  handle_show_hints: CLI support for listing registered dial plan hints */
-static char *handle_show_hints(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+static char *handle_unset_extenpatternmatchnew(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
 {
-	struct ast_hint *hint;
-	int num = 0;
-	int watchers;
-	struct ao2_iterator i;
-	char buf[AST_MAX_EXTENSION+AST_MAX_CONTEXT+2];
+	int oldval = 0;
 
 	switch (cmd) {
 	case CLI_INIT:
-		e->command = "core show hints";
+		e->command = "dialplan set extenpatternmatchnew false";
 		e->usage =
-			"Usage: core show hints\n"
-			"       List registered hints.\n"
-			"       Hint details are shown in five columns. In order from left to right, they are:\n"
-			"       1. Hint extension URI.\n"
-			"       2. List of mapped device or presence state identifiers.\n"
-			"       3. Current extension state. The aggregate of mapped device states.\n"
-			"       4. Current presence state for the mapped presence state provider.\n"
-			"       5. Watchers - number of subscriptions and other entities watching this hint.\n";
+			"Usage: dialplan set extenpatternmatchnew true|false\n"
+			"       Use the NEW extension pattern matching algorithm, true or false.\n";
 		return NULL;
 	case CLI_GENERATE:
 		return NULL;
 	}
 
-	if (ao2_container_count(hints) == 0) {
-		ast_cli(a->fd, "There are no registered dialplan hints\n");
-		return CLI_SUCCESS;
-	}
-	/* ... we have hints ... */
-	ast_cli(a->fd, "\n    -= Registered Asterisk Dial Plan Hints =-\n");
-
-	i = ao2_iterator_init(hints, 0);
-	for (; (hint = ao2_iterator_next(&i)); ao2_ref(hint, -1)) {
-		ao2_lock(hint);
-		if (!hint->exten) {
-			/* The extension has already been destroyed */
-			ao2_unlock(hint);
-			continue;
-		}
-		watchers = ao2_container_count(hint->callbacks);
-		snprintf(buf, sizeof(buf), "%s@%s",
-			ast_get_extension_name(hint->exten),
-			ast_get_context_name(ast_get_extension_context(hint->exten)));
+	if (a->argc != 4)
+		return CLI_SHOWUSAGE;
 
-		ast_cli(a->fd, "%-20.20s: %-20.20s  State:%-15.15s Presence:%-15.15s Watchers %2d\n",
-			buf,
-			ast_get_extension_app(hint->exten),
-			ast_extension_state2str(hint->laststate),
-			ast_presence_state2str(hint->last_presence_state),
-			watchers);
+	oldval =  pbx_set_extenpatternmatchnew(0);
 
-		ao2_unlock(hint);
-		num++;
-	}
-	ao2_iterator_destroy(&i);
+	if (!oldval)
+		ast_cli(a->fd, "\n    -- Still using the OLD pattern match algorithm for extension names in the dialplan.\n");
+	else
+		ast_cli(a->fd, "\n    -- Switched to using the OLD pattern match algorithm for extension names in the dialplan.\n");
 
-	ast_cli(a->fd, "----------------\n");
-	ast_cli(a->fd, "- %d hints registered\n", num);
 	return CLI_SUCCESS;
 }
 
-/*! \brief autocomplete for CLI command 'core show hint' */
-static char *complete_core_show_hint(const char *line, const char *word, int pos, int state)
-{
-	struct ast_hint *hint;
-	char *ret = NULL;
-	int which = 0;
-	int wordlen;
-	struct ao2_iterator i;
-
-	if (pos != 3)
-		return NULL;
+/*
+ * CLI entries for upper commands ...
+ */
+static struct ast_cli_entry pbx_cli[] = {
+#if 0
+	AST_CLI_DEFINE(handle_eat_memory, "Eats all available memory"),
+#endif
+	AST_CLI_DEFINE(handle_show_hints, "Show dialplan hints"),
+	AST_CLI_DEFINE(handle_show_hint, "Show dialplan hint"),
+#ifdef AST_DEVMODE
+	AST_CLI_DEFINE(handle_show_device2extenstate, "Show expected exten state from multiple device states"),
+#endif
+	AST_CLI_DEFINE(handle_show_dialplan, "Show dialplan"),
+	AST_CLI_DEFINE(handle_debug_dialplan, "Show fast extension pattern matching data structures"),
+	AST_CLI_DEFINE(handle_unset_extenpatternmatchnew, "Use the Old extension pattern matching algorithm."),
+	AST_CLI_DEFINE(handle_set_extenpatternmatchnew, "Use the New extension pattern matching algorithm."),
+};
 
-	wordlen = strlen(word);
+void unreference_cached_app(struct ast_app *app)
+{
+	struct ast_context *context = NULL;
+	struct ast_exten *eroot = NULL, *e = NULL;
 
-	/* walk through all hints */
-	i = ao2_iterator_init(hints, 0);
-	for (; (hint = ao2_iterator_next(&i)); ao2_ref(hint, -1)) {
-		ao2_lock(hint);
-		if (!hint->exten) {
-			/* The extension has already been destroyed */
-			ao2_unlock(hint);
-			continue;
-		}
-		if (!strncasecmp(word, ast_get_extension_name(hint->exten), wordlen) && ++which > state) {
-			ret = ast_strdup(ast_get_extension_name(hint->exten));
-			ao2_unlock(hint);
-			ao2_ref(hint, -1);
-			break;
+	ast_rdlock_contexts();
+	while ((context = ast_walk_contexts(context))) {
+		while ((eroot = ast_walk_context_extensions(context, eroot))) {
+			while ((e = ast_walk_extension_priorities(eroot, e))) {
+				if (e->cached_app == app)
+					e->cached_app = NULL;
+			}
 		}
-		ao2_unlock(hint);
 	}
-	ao2_iterator_destroy(&i);
+	ast_unlock_contexts();
 
-	return ret;
+	return;
 }
 
-/*! \brief  handle_show_hint: CLI support for listing registered dial plan hint */
-static char *handle_show_hint(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+struct ast_context *ast_context_find_or_create(struct ast_context **extcontexts, struct ast_hashtab *exttable, const char *name, const char *registrar)
 {
-	struct ast_hint *hint;
-	int watchers;
-	int num = 0, extenlen;
-	struct ao2_iterator i;
-	char buf[AST_MAX_EXTENSION+AST_MAX_CONTEXT+2];
-
-	switch (cmd) {
-	case CLI_INIT:
-		e->command = "core show hint";
-		e->usage =
-			"Usage: core show hint <exten>\n"
-			"       List registered hint.\n"
-			"       Hint details are shown in five columns. In order from left to right, they are:\n"
-			"       1. Hint extension URI.\n"
-			"       2. List of mapped device or presence state identifiers.\n"
-			"       3. Current extension state. The aggregate of mapped device states.\n"
-			"       4. Current presence state for the mapped presence state provider.\n"
-			"       5. Watchers - number of subscriptions and other entities watching this hint.\n";
-		return NULL;
-	case CLI_GENERATE:
-		return complete_core_show_hint(a->line, a->word, a->pos, a->n);
-	}
-
-	if (a->argc < 4)
-		return CLI_SHOWUSAGE;
+	struct ast_context *tmp, **local_contexts;
+	struct fake_context search;
+	int length = sizeof(struct ast_context) + strlen(name) + 1;
 
-	if (ao2_container_count(hints) == 0) {
-		ast_cli(a->fd, "There are no registered dialplan hints\n");
-		return CLI_SUCCESS;
+	if (!contexts_table) {
+		/* Protect creation of contexts_table from reentrancy. */
+		ast_wrlock_contexts();
+		if (!contexts_table) {
+			contexts_table = ast_hashtab_create(17,
+				ast_hashtab_compare_contexts,
+				ast_hashtab_resize_java,
+				ast_hashtab_newsize_java,
+				ast_hashtab_hash_contexts,
+				0);
+		}
+		ast_unlock_contexts();
 	}
 
-	extenlen = strlen(a->argv[3]);
-	i = ao2_iterator_init(hints, 0);
-	for (; (hint = ao2_iterator_next(&i)); ao2_ref(hint, -1)) {
-		ao2_lock(hint);
-		if (!hint->exten) {
-			/* The extension has already been destroyed */
-			ao2_unlock(hint);
-			continue;
+	ast_copy_string(search.name, name, sizeof(search.name));
+	if (!extcontexts) {
+		ast_rdlock_contexts();
+		local_contexts = &contexts;
+		tmp = ast_hashtab_lookup(contexts_table, &search);
+		if (tmp) {
+			tmp->refcount++;
+			ast_unlock_contexts();
+			return tmp;
 		}
-		if (!strncasecmp(ast_get_extension_name(hint->exten), a->argv[3], extenlen)) {
-			watchers = ao2_container_count(hint->callbacks);
-			sprintf(buf, "%s@%s",
-				ast_get_extension_name(hint->exten),
-				ast_get_context_name(ast_get_extension_context(hint->exten)));
-			ast_cli(a->fd, "%-20.20s: %-20.20s  State:%-15.15s Presence:%-15.15s Watchers %2d\n",
-				buf,
-				ast_get_extension_app(hint->exten),
-				ast_extension_state2str(hint->laststate), 
-				ast_presence_state2str(hint->last_presence_state), 
-				watchers);
-			num++;
+	} else { /* local contexts just in a linked list; search there for the new context; slow, linear search, but not frequent */
+		local_contexts = extcontexts;
+		tmp = ast_hashtab_lookup(exttable, &search);
+		if (tmp) {
+			tmp->refcount++;
+			return tmp;
 		}
-		ao2_unlock(hint);
 	}
-	ao2_iterator_destroy(&i);
-	if (!num)
-		ast_cli(a->fd, "No hints matching extension %s\n", a->argv[3]);
-	else
-		ast_cli(a->fd, "%d hint%s matching extension %s\n", num, (num!=1 ? "s":""), a->argv[3]);
-	return CLI_SUCCESS;
-}
-
 
-/*! \brief  handle_show_switches: CLI support for listing registered dial plan switches */
-static char *handle_show_switches(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
-{
-	struct ast_switch *sw;
-
-	switch (cmd) {
-	case CLI_INIT:
-		e->command = "core show switches";
-		e->usage =
-			"Usage: core show switches\n"
-			"       List registered switches\n";
-		return NULL;
-	case CLI_GENERATE:
+	if ((tmp = ast_calloc(1, length))) {
+		ast_rwlock_init(&tmp->lock);
+		ast_mutex_init(&tmp->macrolock);
+		strcpy(tmp->name, name);
+		tmp->root = NULL;
+		tmp->root_table = NULL;
+		tmp->registrar = ast_strdup(registrar);
+		tmp->includes = NULL;
+		tmp->ignorepats = NULL;
+		tmp->refcount = 1;
+	} else {
+		ast_log(LOG_ERROR, "Danger! We failed to allocate a context for %s!\n", name);
+		if (!extcontexts) {
+			ast_unlock_contexts();
+		}
 		return NULL;
 	}
 
-	AST_RWLIST_RDLOCK(&switches);
+	if (!extcontexts) {
+		tmp->next = *local_contexts;
+		*local_contexts = tmp;
+		ast_hashtab_insert_safe(contexts_table, tmp); /*put this context into the tree */
+		ast_unlock_contexts();
+		ast_verb(3, "Registered extension context '%s'; registrar: %s\n", tmp->name, registrar);
+	} else {
+		tmp->next = *local_contexts;
+		if (exttable)
+			ast_hashtab_insert_immediate(exttable, tmp); /*put this context into the tree */
 
-	if (AST_RWLIST_EMPTY(&switches)) {
-		AST_RWLIST_UNLOCK(&switches);
-		ast_cli(a->fd, "There are no registered alternative switches\n");
-		return CLI_SUCCESS;
+		*local_contexts = tmp;
+		ast_verb(3, "Registered extension context '%s'; registrar: %s\n", tmp->name, registrar);
 	}
+	return tmp;
+}
 
-	ast_cli(a->fd, "\n    -= Registered Asterisk Alternative Switches =-\n");
-	AST_RWLIST_TRAVERSE(&switches, sw, list)
-		ast_cli(a->fd, "%s: %s\n", sw->name, sw->description);
+void __ast_context_destroy(struct ast_context *list, struct ast_hashtab *contexttab, struct ast_context *con, const char *registrar);
 
-	AST_RWLIST_UNLOCK(&switches);
+struct store_hint {
+	char *context;
+	char *exten;
+	AST_LIST_HEAD_NOLOCK(, ast_state_cb) callbacks;
+	int laststate;
+	int last_presence_state;
+	char *last_presence_subtype;
+	char *last_presence_message;
 
-	return CLI_SUCCESS;
-}
+	AST_LIST_ENTRY(store_hint) list;
+	char data[1];
+};
 
-#if 0
-/* This code can be used to test if the system survives running out of memory.
- * It might be an idea to put this in only if ENABLE_AUTODESTRUCT_TESTS is enabled.
- *
- * If you want to test this, these Linux sysctl flags might be appropriate:
- *   vm.overcommit_memory = 2
- *   vm.swappiness = 0
- *
- * <@Corydon76-home> I envision 'core eat disk space' and 'core eat file descriptors' now
- * <@mjordan> egads
- * <@mjordan> it's literally the 'big red' auto-destruct button
- * <@mjordan> if you were wondering who even builds such a thing.... well, now you know
- * ...
- * <@Corydon76-home> What about if they lived only if you defined TEST_FRAMEWORK?  Shouldn't have those on production machines
- * <@mjordan> I think accompanied with an update to one of our README files that "no, really, TEST_FRAMEWORK isn't for you", I'd be fine
- */
-static char *handle_eat_memory(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+AST_LIST_HEAD_NOLOCK(store_hints, store_hint);
+
+static void context_merge_incls_swits_igps_other_registrars(struct ast_context *new, struct ast_context *old, const char *registrar)
 {
-	void **blocks;
-	int blocks_pos = 0;
-	const int blocks_max = 50000;
-	long long int allocated = 0;
-	int sizes[] = {
-		100 * 1024 * 1024,
-		100 * 1024,
-		2 * 1024,
-		400,
-		0
-	};
-	int i;
+	struct ast_include *i;
+	struct ast_ignorepat *ip;
+	struct ast_sw *sw;
 
-	switch (cmd) {
-	case CLI_INIT:
-		/* To do: add method to free memory again? 5 minutes? */
-		e->command = "core eat memory";
-		e->usage =
-			"Usage: core eat memory\n"
-			"       Eats all available memory so you can test if the system survives\n";
-		return NULL;
-	case CLI_GENERATE:
-		return NULL;
+	ast_verb(3, "merging incls/swits/igpats from old(%s) to new(%s) context, registrar = %s\n", ast_get_context_name(old), ast_get_context_name(new), registrar);
+	/* copy in the includes, switches, and ignorepats */
+	/* walk through includes */
+	for (i = NULL; (i = ast_walk_context_includes(old, i)) ; ) {
+		if (strcmp(ast_get_include_registrar(i), registrar) == 0)
+			continue; /* not mine */
+		ast_context_add_include2(new, ast_get_include_name(i), ast_get_include_registrar(i));
 	}
 
-	blocks = ast_malloc(sizeof(void*) * blocks_max);
-	if (!blocks) {
-		ast_log(LOG_ERROR, "Already out of mem?\n");
-		return CLI_SUCCESS;
+	/* walk through switches */
+	for (sw = NULL; (sw = ast_walk_context_switches(old, sw)) ; ) {
+		if (strcmp(ast_get_switch_registrar(sw), registrar) == 0)
+			continue; /* not mine */
+		ast_context_add_switch2(new, ast_get_switch_name(sw), ast_get_switch_data(sw), ast_get_switch_eval(sw), ast_get_switch_registrar(sw));
 	}
 
-	for (i = 0; sizes[i]; ++i) {
-		int alloc_size = sizes[i];
-		ast_log(LOG_WARNING, "Allocating %d sized blocks (got %d blocks already)\n", alloc_size, blocks_pos);
-		while (1) {
-			void *block;
-			if (blocks_pos >= blocks_max) {
-				ast_log(LOG_ERROR, "Memory buffer too small? Run me again :)\n");
-				break;
-			}
-
-			block = ast_malloc(alloc_size);
-			if (!block) {
-				break;
-			}
-
-			blocks[blocks_pos++] = block;
-			allocated += alloc_size;
-		}
+	/* walk thru ignorepats ... */
+	for (ip = NULL; (ip = ast_walk_context_ignorepats(old, ip)); ) {
+		if (strcmp(ast_get_ignorepat_registrar(ip), registrar) == 0)
+			continue; /* not mine */
+		ast_context_add_ignorepat2(new, ast_get_ignorepat_name(ip), ast_get_ignorepat_registrar(ip));
 	}
-
-	/* No freeing of the mem! */
-	ast_log(LOG_WARNING, "Allocated %lld bytes total!\n", allocated);
-	return CLI_SUCCESS;
 }
-#endif
 
-static char *handle_show_applications(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+
+/* the purpose of this routine is to duplicate a context, with all its substructure,
+   except for any extens that have a matching registrar */
+static void context_merge(struct ast_context **extcontexts, struct ast_hashtab *exttable, struct ast_context *context, const char *registrar)
 {
-	struct ast_app *aa;
-	int like = 0, describing = 0;
-	int total_match = 0;    /* Number of matches in like clause */
-	int total_apps = 0;     /* Number of apps registered */
-	static const char * const choices[] = { "like", "describing", NULL };
+	struct ast_context *new = ast_hashtab_lookup(exttable, context); /* is there a match in the new set? */
+	struct ast_exten *exten_item, *prio_item, *new_exten_item, *new_prio_item;
+	struct ast_hashtab_iter *exten_iter;
+	struct ast_hashtab_iter *prio_iter;
+	int insert_count = 0;
+	int first = 1;
 
-	switch (cmd) {
-	case CLI_INIT:
-		e->command = "core show applications [like|describing]";
-		e->usage =
-			"Usage: core show applications [{like|describing} <text>]\n"
-			"       List applications which are currently available.\n"
-			"       If 'like', <text> will be a substring of the app name\n"
-			"       If 'describing', <text> will be a substring of the description\n";
-		return NULL;
-	case CLI_GENERATE:
-		return (a->pos != 3) ? NULL : ast_cli_complete(a->word, choices, a->n);
-	}
+	/* We'll traverse all the extensions/prios, and see which are not registrar'd with
+	   the current registrar, and copy them to the new context. If the new context does not
+	   exist, we'll create it "on demand". If no items are in this context to copy, then we'll
+	   only create the empty matching context if the old one meets the criteria */
 
-	AST_RWLIST_RDLOCK(&apps);
+	if (context->root_table) {
+		exten_iter = ast_hashtab_start_traversal(context->root_table);
+		while ((exten_item=ast_hashtab_next(exten_iter))) {
+			if (new) {
+				new_exten_item = ast_hashtab_lookup(new->root_table, exten_item);
+			} else {
+				new_exten_item = NULL;
+			}
+			prio_iter = ast_hashtab_start_traversal(exten_item->peer_table);
+			while ((prio_item=ast_hashtab_next(prio_iter))) {
+				int res1;
+				char *dupdstr;
 
-	if (AST_RWLIST_EMPTY(&apps)) {
-		ast_cli(a->fd, "There are no registered applications\n");
-		AST_RWLIST_UNLOCK(&apps);
-		return CLI_SUCCESS;
-	}
+				if (new_exten_item) {
+					new_prio_item = ast_hashtab_lookup(new_exten_item->peer_table, prio_item);
+				} else {
+					new_prio_item = NULL;
+				}
+				if (strcmp(prio_item->registrar,registrar) == 0) {
+					continue;
+				}
+				/* make sure the new context exists, so we have somewhere to stick this exten/prio */
+				if (!new) {
+					new = ast_context_find_or_create(extcontexts, exttable, context->name, prio_item->registrar); /* a new context created via priority from a different context in the old dialplan, gets its registrar from the prio's registrar */
+				}
 
-	/* core list applications like <keyword> */
-	if ((a->argc == 5) && (!strcmp(a->argv[3], "like"))) {
-		like = 1;
-	} else if ((a->argc > 4) && (!strcmp(a->argv[3], "describing"))) {
-		describing = 1;
-	}
+				/* copy in the includes, switches, and ignorepats */
+				if (first) { /* but, only need to do this once */
+					context_merge_incls_swits_igps_other_registrars(new, context, registrar);
+					first = 0;
+				}
 
-	/* core list applications describing <keyword1> [<keyword2>] [...] */
-	if ((!like) && (!describing)) {
-		ast_cli(a->fd, "    -= Registered Asterisk Applications =-\n");
-	} else {
-		ast_cli(a->fd, "    -= Matching Asterisk Applications =-\n");
-	}
+				if (!new) {
+					ast_log(LOG_ERROR,"Could not allocate a new context for %s in merge_and_delete! Danger!\n", context->name);
+					ast_hashtab_end_traversal(prio_iter);
+					ast_hashtab_end_traversal(exten_iter);
+					return; /* no sense continuing. */
+				}
+				/* we will not replace existing entries in the new context with stuff from the old context.
+				   but, if this is because of some sort of registrar conflict, we ought to say something... */
 
-	AST_RWLIST_TRAVERSE(&apps, aa, list) {
-		int printapp = 0;
-		total_apps++;
-		if (like) {
-			if (strcasestr(aa->name, a->argv[4])) {
-				printapp = 1;
-				total_match++;
-			}
-		} else if (describing) {
-			if (aa->description) {
-				/* Match all words on command line */
-				int i;
-				printapp = 1;
-				for (i = 4; i < a->argc; i++) {
-					if (!strcasestr(aa->description, a->argv[i])) {
-						printapp = 0;
-					} else {
-						total_match++;
-					}
+				dupdstr = ast_strdup(prio_item->data);
+
+				res1 = ast_add_extension2(new, 0, prio_item->exten, prio_item->priority, prio_item->label,
+										  prio_item->matchcid ? prio_item->cidmatch : NULL, prio_item->app, dupdstr, ast_free_ptr, prio_item->registrar);
+				if (!res1 && new_exten_item && new_prio_item){
+					ast_verb(3,"Dropping old dialplan item %s/%s/%d [%s(%s)] (registrar=%s) due to conflict with new dialplan\n",
+							context->name, prio_item->exten, prio_item->priority, prio_item->app, (char*)prio_item->data, prio_item->registrar);
+				} else {
+					/* we do NOT pass the priority data from the old to the new -- we pass a copy of it, so no changes to the current dialplan take place,
+					 and no double frees take place, either! */
+					insert_count++;
 				}
 			}
-		} else {
-			printapp = 1;
-		}
-
-		if (printapp) {
-			ast_cli(a->fd,"  %20s: %s\n", aa->name, aa->synopsis ? aa->synopsis : "<Synopsis not available>");
+			ast_hashtab_end_traversal(prio_iter);
 		}
-	}
-	if ((!like) && (!describing)) {
-		ast_cli(a->fd, "    -= %d Applications Registered =-\n",total_apps);
-	} else {
-		ast_cli(a->fd, "    -= %d Applications Matching =-\n",total_match);
+		ast_hashtab_end_traversal(exten_iter);
+	} else if (new) {
+		/* If the context existed but had no extensions, we still want to merge
+		 * the includes, switches and ignore patterns.
+		 */
+		context_merge_incls_swits_igps_other_registrars(new, context, registrar);
 	}
 
-	AST_RWLIST_UNLOCK(&apps);
+	if (!insert_count && !new && (strcmp(context->registrar, registrar) != 0 ||
+		  (strcmp(context->registrar, registrar) == 0 && context->refcount > 1))) {
+		/* we could have given it the registrar of the other module who incremented the refcount,
+		   but that's not available, so we give it the registrar we know about */
+		new = ast_context_find_or_create(extcontexts, exttable, context->name, context->registrar);
 
-	return CLI_SUCCESS;
+		/* copy in the includes, switches, and ignorepats */
+		context_merge_incls_swits_igps_other_registrars(new, context, registrar);
+	}
 }
 
-/*
- * 'show dialplan' CLI command implementation functions ...
- */
-static char *complete_show_dialplan_context(const char *line, const char *word, int pos,
-	int state)
+
+/* XXX this does not check that multiple contexts are merged */
+void ast_merge_contexts_and_delete(struct ast_context **extcontexts, struct ast_hashtab *exttable, const char *registrar)
 {
-	struct ast_context *c = NULL;
-	char *ret = NULL;
-	int which = 0;
-	int wordlen;
+	double ft;
+	struct ast_context *tmp;
+	struct ast_context *oldcontextslist;
+	struct ast_hashtab *oldtable;
+	struct store_hints hints_stored = AST_LIST_HEAD_NOLOCK_INIT_VALUE;
+	struct store_hints hints_removed = AST_LIST_HEAD_NOLOCK_INIT_VALUE;
+	struct store_hint *saved_hint;
+	struct ast_hint *hint;
+	struct ast_exten *exten;
+	int length;
+	struct ast_state_cb *thiscb;
+	struct ast_hashtab_iter *iter;
+	struct ao2_iterator i;
+	struct timeval begintime;
+	struct timeval writelocktime;
+	struct timeval endlocktime;
+	struct timeval enddeltime;
 
-	/* we are do completion of [exten@]context on second position only */
-	if (pos != 2)
-		return NULL;
+	/*
+	 * It is very important that this function hold the hints
+	 * container lock _and_ the conlock during its operation; not
+	 * only do we need to ensure that the list of contexts and
+	 * extensions does not change, but also that no hint callbacks
+	 * (watchers) are added or removed during the merge/delete
+	 * process
+	 *
+	 * In addition, the locks _must_ be taken in this order, because
+	 * there are already other code paths that use this order
+	 */
 
-	ast_rdlock_contexts();
+	begintime = ast_tvnow();
+	ast_mutex_lock(&context_merge_lock);/* Serialize ast_merge_contexts_and_delete */
+	ast_wrlock_contexts();
 
-	wordlen = strlen(word);
+	if (!contexts_table) {
+		/* Well, that's odd. There are no contexts. */
+		contexts_table = exttable;
+		contexts = *extcontexts;
+		ast_unlock_contexts();
+		ast_mutex_unlock(&context_merge_lock);
+		return;
+	}
 
-	/* walk through all contexts and return the n-th match */
-	while ( (c = ast_walk_contexts(c)) ) {
-		if (!strncasecmp(word, ast_get_context_name(c), wordlen) && ++which > state) {
-			ret = ast_strdup(ast_get_context_name(c));
-			break;
-		}
+	iter = ast_hashtab_start_traversal(contexts_table);
+	while ((tmp = ast_hashtab_next(iter))) {
+		context_merge(extcontexts, exttable, tmp, registrar);
 	}
+	ast_hashtab_end_traversal(iter);
 
-	ast_unlock_contexts();
+	ao2_lock(hints);
+	writelocktime = ast_tvnow();
 
-	return ret;
-}
+	/* preserve all watchers for hints */
+	i = ao2_iterator_init(hints, AO2_ITERATOR_DONTLOCK);
+	for (; (hint = ao2_iterator_next(&i)); ao2_ref(hint, -1)) {
+		if (ao2_container_count(hint->callbacks)) {
+			ao2_lock(hint);
+			if (!hint->exten) {
+				/* The extension has already been destroyed. (Should never happen here) */
+				ao2_unlock(hint);
+				continue;
+			}
 
-/*! \brief Counters for the show dialplan manager command */
-struct dialplan_counters {
-	int total_items;
-	int total_context;
-	int total_exten;
-	int total_prio;
-	int context_existence;
-	int extension_existence;
-};
+			length = strlen(hint->exten->exten) + strlen(hint->exten->parent->name) + 2
+				+ sizeof(*saved_hint);
+			if (!(saved_hint = ast_calloc(1, length))) {
+				ao2_unlock(hint);
+				continue;
+			}
 
-/*! \brief helper function to print an extension */
-static void print_ext(struct ast_exten *e, char * buf, int buflen)
-{
-	int prio = ast_get_extension_priority(e);
-	if (prio == PRIORITY_HINT) {
-		snprintf(buf, buflen, "hint: %s",
-			ast_get_extension_app(e));
-	} else {
-		snprintf(buf, buflen, "%d. %s(%s)",
-			prio, ast_get_extension_app(e),
-			(!ast_strlen_zero(ast_get_extension_app_data(e)) ? (char *)ast_get_extension_app_data(e) : ""));
+			/* This removes all the callbacks from the hint into saved_hint. */
+			while ((thiscb = ao2_callback(hint->callbacks, OBJ_UNLINK, NULL, NULL))) {
+				AST_LIST_INSERT_TAIL(&saved_hint->callbacks, thiscb, entry);
+				/*
+				 * We intentionally do not unref thiscb to account for the
+				 * non-ao2 reference in saved_hint->callbacks
+				 */
+			}
+
+			saved_hint->laststate = hint->laststate;
+			saved_hint->context = saved_hint->data;
+			strcpy(saved_hint->data, hint->exten->parent->name);
+			saved_hint->exten = saved_hint->data + strlen(saved_hint->context) + 1;
+			strcpy(saved_hint->exten, hint->exten->exten);
+			if (hint->last_presence_subtype) {
+				saved_hint->last_presence_subtype = ast_strdup(hint->last_presence_subtype);
+			}
+			if (hint->last_presence_message) {
+				saved_hint->last_presence_message = ast_strdup(hint->last_presence_message);
+			}
+			saved_hint->last_presence_state = hint->last_presence_state;
+			ao2_unlock(hint);
+			AST_LIST_INSERT_HEAD(&hints_stored, saved_hint, list);
+		}
 	}
-}
+	ao2_iterator_destroy(&i);
 
-/* XXX not verified */
-static int show_dialplan_helper(int fd, const char *context, const char *exten, struct dialplan_counters *dpc, struct ast_include *rinclude, int includecount, const char *includes[])
-{
-	struct ast_context *c = NULL;
-	int res = 0, old_total_exten = dpc->total_exten;
+	/* save the old table and list */
+	oldtable = contexts_table;
+	oldcontextslist = contexts;
 
-	ast_rdlock_contexts();
+	/* move in the new table and list */
+	contexts_table = exttable;
+	contexts = *extcontexts;
 
-	/* walk all contexts ... */
-	while ( (c = ast_walk_contexts(c)) ) {
-		struct ast_exten *e;
-		struct ast_include *i;
-		struct ast_ignorepat *ip;
-#ifndef LOW_MEMORY
-		char buf[1024], buf2[1024];
-#else
-		char buf[256], buf2[256];
-#endif
-		int context_info_printed = 0;
+	/*
+	 * Restore the watchers for hints that can be found; notify
+	 * those that cannot be restored.
+	 */
+	while ((saved_hint = AST_LIST_REMOVE_HEAD(&hints_stored, list))) {
+		struct pbx_find_info q = { .stacklen = 0 };
 
-		if (context && strcmp(ast_get_context_name(c), context))
-			continue;	/* skip this one, name doesn't match */
+		exten = pbx_find_extension(NULL, NULL, &q, saved_hint->context, saved_hint->exten,
+			PRIORITY_HINT, NULL, "", E_MATCH);
+		/*
+		 * If this is a pattern, dynamically create a new extension for this
+		 * particular match.  Note that this will only happen once for each
+		 * individual extension, because the pattern will no longer match first.
+		 */
+		if (exten && exten->exten[0] == '_') {
+			ast_add_extension_nolock(exten->parent->name, 0, saved_hint->exten,
+				PRIORITY_HINT, NULL, 0, exten->app, ast_strdup(exten->data), ast_free_ptr,
+				exten->registrar);
+			/* rwlocks are not recursive locks */
+			exten = ast_hint_extension_nolock(NULL, saved_hint->context,
+				saved_hint->exten);
+		}
 
-		dpc->context_existence = 1;
+		/* Find the hint in the hints container */
+		hint = exten ? ao2_find(hints, exten, 0) : NULL;
+		if (!hint) {
+			/*
+			 * Notify watchers of this removed hint later when we aren't
+			 * encumberd by so many locks.
+			 */
+			AST_LIST_INSERT_HEAD(&hints_removed, saved_hint, list);
+		} else {
+			ao2_lock(hint);
+			while ((thiscb = AST_LIST_REMOVE_HEAD(&saved_hint->callbacks, entry))) {
+				ao2_link(hint->callbacks, thiscb);
+				/* Ref that we added when putting into saved_hint->callbacks */
+				ao2_ref(thiscb, -1);
+			}
+			hint->laststate = saved_hint->laststate;
+			hint->last_presence_state = saved_hint->last_presence_state;
+			hint->last_presence_subtype = saved_hint->last_presence_subtype;
+			hint->last_presence_message = saved_hint->last_presence_message;
+			ao2_unlock(hint);
+			ao2_ref(hint, -1);
+			/*
+			 * The free of saved_hint->last_presence_subtype and
+			 * saved_hint->last_presence_message is not necessary here.
+			 */
+			ast_free(saved_hint);
+		}
+	}
 
-		ast_rdlock_context(c);
+	ao2_unlock(hints);
+	ast_unlock_contexts();
 
-		/* are we looking for exten too? if yes, we print context
-		 * only if we find our extension.
-		 * Otherwise print context even if empty ?
-		 * XXX i am not sure how the rinclude is handled.
-		 * I think it ought to go inside.
-		 */
-		if (!exten) {
-			dpc->total_context++;
-			ast_cli(fd, "[ Context '%s' created by '%s' ]\n",
-				ast_get_context_name(c), ast_get_context_registrar(c));
-			context_info_printed = 1;
+	/*
+	 * Notify watchers of all removed hints with the same lock
+	 * environment as device_state_cb().
+	 */
+	while ((saved_hint = AST_LIST_REMOVE_HEAD(&hints_removed, list))) {
+		/* this hint has been removed, notify the watchers */
+		while ((thiscb = AST_LIST_REMOVE_HEAD(&saved_hint->callbacks, entry))) {
+			execute_state_callback(thiscb->change_cb,
+				saved_hint->context,
+				saved_hint->exten,
+				thiscb->data,
+				AST_HINT_UPDATE_DEVICE,
+				NULL,
+				NULL);
+			/* Ref that we added when putting into saved_hint->callbacks */
+			ao2_ref(thiscb, -1);
 		}
+		ast_free(saved_hint->last_presence_subtype);
+		ast_free(saved_hint->last_presence_message);
+		ast_free(saved_hint);
+	}
 
-		/* walk extensions ... */
-		e = NULL;
-		while ( (e = ast_walk_context_extensions(c, e)) ) {
-			struct ast_exten *p;
-
-			if (exten && !ast_extension_match(ast_get_extension_name(e), exten))
-				continue;	/* skip, extension match failed */
+	ast_mutex_unlock(&context_merge_lock);
+	endlocktime = ast_tvnow();
 
-			dpc->extension_existence = 1;
+	/*
+	 * The old list and hashtab no longer are relevant, delete them
+	 * while the rest of asterisk is now freely using the new stuff
+	 * instead.
+	 */
 
-			/* may we print context info? */
-			if (!context_info_printed) {
-				dpc->total_context++;
-				if (rinclude) { /* TODO Print more info about rinclude */
-					ast_cli(fd, "[ Included context '%s' created by '%s' ]\n",
-						ast_get_context_name(c), ast_get_context_registrar(c));
-				} else {
-					ast_cli(fd, "[ Context '%s' created by '%s' ]\n",
-						ast_get_context_name(c), ast_get_context_registrar(c));
-				}
-				context_info_printed = 1;
-			}
-			dpc->total_prio++;
+	ast_hashtab_destroy(oldtable, NULL);
 
-			/* write extension name and first peer */
-			if (e->matchcid == AST_EXT_MATCHCID_ON)
-				snprintf(buf, sizeof(buf), "'%s' (CID match '%s') => ", ast_get_extension_name(e), e->cidmatch);
-			else
-				snprintf(buf, sizeof(buf), "'%s' =>", ast_get_extension_name(e));
+	for (tmp = oldcontextslist; tmp; ) {
+		struct ast_context *next;	/* next starting point */
 
-			print_ext(e, buf2, sizeof(buf2));
+		next = tmp->next;
+		__ast_internal_context_destroy(tmp);
+		tmp = next;
+	}
+	enddeltime = ast_tvnow();
 
-			ast_cli(fd, "  %-17s %-45s [%s]\n", buf, buf2,
-				ast_get_extension_registrar(e));
+	ft = ast_tvdiff_us(writelocktime, begintime);
+	ft /= 1000000.0;
+	ast_verb(3,"Time to scan old dialplan and merge leftovers back into the new: %8.6f sec\n", ft);
 
-			dpc->total_exten++;
-			/* walk next extension peers */
-			p = e;	/* skip the first one, we already got it */
-			while ( (p = ast_walk_extension_priorities(e, p)) ) {
-				const char *el = ast_get_extension_label(p);
-				dpc->total_prio++;
-				if (el)
-					snprintf(buf, sizeof(buf), "   [%s]", el);
-				else
-					buf[0] = '\0';
-				print_ext(p, buf2, sizeof(buf2));
+	ft = ast_tvdiff_us(endlocktime, writelocktime);
+	ft /= 1000000.0;
+	ast_verb(3,"Time to restore hints and swap in new dialplan: %8.6f sec\n", ft);
 
-				ast_cli(fd,"  %-17s %-45s [%s]\n", buf, buf2,
-					ast_get_extension_registrar(p));
-			}
-		}
+	ft = ast_tvdiff_us(enddeltime, endlocktime);
+	ft /= 1000000.0;
+	ast_verb(3,"Time to delete the old dialplan: %8.6f sec\n", ft);
 
-		/* walk included and write info ... */
-		i = NULL;
-		while ( (i = ast_walk_context_includes(c, i)) ) {
-			snprintf(buf, sizeof(buf), "'%s'", ast_get_include_name(i));
-			if (exten) {
-				/* Check all includes for the requested extension */
-				if (includecount >= AST_PBX_MAX_STACK) {
-					ast_log(LOG_WARNING, "Maximum include depth exceeded!\n");
-				} else {
-					int dupe = 0;
-					int x;
-					for (x = 0; x < includecount; x++) {
-						if (!strcasecmp(includes[x], ast_get_include_name(i))) {
-							dupe++;
-							break;
-						}
-					}
-					if (!dupe) {
-						includes[includecount] = ast_get_include_name(i);
-						show_dialplan_helper(fd, ast_get_include_name(i), exten, dpc, i, includecount + 1, includes);
-					} else {
-						ast_log(LOG_WARNING, "Avoiding circular include of %s within %s\n", ast_get_include_name(i), context);
-					}
-				}
-			} else {
-				ast_cli(fd, "  Include =>        %-45s [%s]\n",
-					buf, ast_get_include_registrar(i));
-			}
-		}
+	ft = ast_tvdiff_us(enddeltime, begintime);
+	ft /= 1000000.0;
+	ast_verb(3,"Total time merge_contexts_delete: %8.6f sec\n", ft);
+}
 
-		/* walk ignore patterns and write info ... */
-		ip = NULL;
-		while ( (ip = ast_walk_context_ignorepats(c, ip)) ) {
-			const char *ipname = ast_get_ignorepat_name(ip);
-			char ignorepat[AST_MAX_EXTENSION];
-			snprintf(buf, sizeof(buf), "'%s'", ipname);
-			snprintf(ignorepat, sizeof(ignorepat), "_%s.", ipname);
-			if (!exten || ast_extension_match(ignorepat, exten)) {
-				ast_cli(fd, "  Ignore pattern => %-45s [%s]\n",
-					buf, ast_get_ignorepat_registrar(ip));
-			}
-		}
-		if (!rinclude) {
-			struct ast_sw *sw = NULL;
-			while ( (sw = ast_walk_context_switches(c, sw)) ) {
-				snprintf(buf, sizeof(buf), "'%s/%s'",
-					ast_get_switch_name(sw),
-					ast_get_switch_data(sw));
-				ast_cli(fd, "  Alt. Switch =>    %-45s [%s]\n",
-					buf, ast_get_switch_registrar(sw));
-			}
-		}
-
-		ast_unlock_context(c);
+/*
+ * errno values
+ *  EBUSY  - can't lock
+ *  ENOENT - no existence of context
+ */
+int ast_context_add_include(const char *context, const char *include, const char *registrar)
+{
+	int ret = -1;
+	struct ast_context *c;
 
-		/* if we print something in context, make an empty line */
-		if (context_info_printed)
-			ast_cli(fd, "\n");
+	c = find_context_locked(context);
+	if (c) {
+		ret = ast_context_add_include2(c, include, registrar);
+		ast_unlock_contexts();
 	}
-	ast_unlock_contexts();
-
-	return (dpc->total_exten == old_total_exten) ? -1 : res;
+	return ret;
 }
 
-static int show_debug_helper(int fd, const char *context, const char *exten, struct dialplan_counters *dpc, struct ast_include *rinclude, int includecount, const char *includes[])
+/*
+ * errno values
+ *  ENOMEM - out of memory
+ *  EBUSY  - can't lock
+ *  EEXIST - already included
+ *  EINVAL - there is no existence of context for inclusion
+ */
+int ast_context_add_include2(struct ast_context *con, const char *value,
+	const char *registrar)
 {
-	struct ast_context *c = NULL;
-	int res = 0, old_total_exten = dpc->total_exten;
-
-	ast_cli(fd,"\n     In-mem exten Trie for Fast Extension Pattern Matching:\n\n");
-
-	ast_cli(fd,"\n           Explanation: Node Contents Format = <char(s) to match>:<pattern?>:<specif>:[matched extension]\n");
-	ast_cli(fd,    "                        Where <char(s) to match> is a set of chars, any one of which should match the current character\n");
-	ast_cli(fd,    "                              <pattern?>: Y if this a pattern match (eg. _XZN[5-7]), N otherwise\n");
-	ast_cli(fd,    "                              <specif>: an assigned 'exactness' number for this matching char. The lower the number, the more exact the match\n");
-	ast_cli(fd,    "                              [matched exten]: If all chars matched to this point, which extension this matches. In form: EXTEN:<exten string>\n");
-	ast_cli(fd,    "                        In general, you match a trie node to a string character, from left to right. All possible matching chars\n");
-	ast_cli(fd,    "                        are in a string vertically, separated by an unbroken string of '+' characters.\n\n");
-	ast_rdlock_contexts();
+	struct ast_include *new_include;
+	char *c;
+	struct ast_include *i, *il = NULL; /* include, include_last */
+	int length;
+	char *p;
 
-	/* walk all contexts ... */
-	while ( (c = ast_walk_contexts(c)) ) {
-		int context_info_printed = 0;
+	length = sizeof(struct ast_include);
+	length += 2 * (strlen(value) + 1);
 
-		if (context && strcmp(ast_get_context_name(c), context))
-			continue;	/* skip this one, name doesn't match */
+	/* allocate new include structure ... */
+	if (!(new_include = ast_calloc(1, length)))
+		return -1;
+	/* Fill in this structure. Use 'p' for assignments, as the fields
+	 * in the structure are 'const char *'
+	 */
+	p = new_include->stuff;
+	new_include->name = p;
+	strcpy(p, value);
+	p += strlen(value) + 1;
+	new_include->rname = p;
+	strcpy(p, value);
+	/* Strip off timing info, and process if it is there */
+	if ( (c = strchr(p, ',')) ) {
+		*c++ = '\0';
+		new_include->hastime = ast_build_timing(&(new_include->timing), c);
+	}
+	new_include->next      = NULL;
+	new_include->registrar = registrar;
 
-		dpc->context_existence = 1;
+	ast_wrlock_context(con);
 
-		if (!c->pattern_tree) {
-			/* Ignore check_return warning from Coverity for ast_exists_extension below */
-			ast_exists_extension(NULL, c->name, "s", 1, ""); /* do this to force the trie to built, if it is not already */
+	/* ... go to last include and check if context is already included too... */
+	for (i = con->includes; i; i = i->next) {
+		if (!strcasecmp(i->name, new_include->name)) {
+			ast_destroy_timing(&(new_include->timing));
+			ast_free(new_include);
+			ast_unlock_context(con);
+			errno = EEXIST;
+			return -1;
 		}
+		il = i;
+	}
 
-		ast_rdlock_context(c);
+	/* ... include new context into context list, unlock, return */
+	if (il)
+		il->next = new_include;
+	else
+		con->includes = new_include;
+	ast_verb(3, "Including context '%s' in context '%s'\n", new_include->name, ast_get_context_name(con));
 
-		dpc->total_context++;
-		ast_cli(fd, "[ Context '%s' created by '%s' ]\n",
-			ast_get_context_name(c), ast_get_context_registrar(c));
-		context_info_printed = 1;
+	ast_unlock_context(con);
 
-		if (c->pattern_tree)
-		{
-			cli_match_char_tree(c->pattern_tree, " ", fd);
-		} else {
-			ast_cli(fd,"\n     No Pattern Trie present. Perhaps the context is empty...or there is trouble...\n\n");
-		}
+	return 0;
+}
 
-		ast_unlock_context(c);
+/*
+ * errno values
+ *  EBUSY  - can't lock
+ *  ENOENT - no existence of context
+ */
+int ast_context_add_switch(const char *context, const char *sw, const char *data, int eval, const char *registrar)
+{
+	int ret = -1;
+	struct ast_context *c;
 
-		/* if we print something in context, make an empty line */
-		if (context_info_printed)
-			ast_cli(fd, "\n");
+	c = find_context_locked(context);
+	if (c) { /* found, add switch to this context */
+		ret = ast_context_add_switch2(c, sw, data, eval, registrar);
+		ast_unlock_contexts();
 	}
-	ast_unlock_contexts();
-
-	return (dpc->total_exten == old_total_exten) ? -1 : res;
+	return ret;
 }
 
-static char *handle_show_dialplan(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+/*
+ * errno values
+ *  ENOMEM - out of memory
+ *  EBUSY  - can't lock
+ *  EEXIST - already included
+ *  EINVAL - there is no existence of context for inclusion
+ */
+int ast_context_add_switch2(struct ast_context *con, const char *value,
+	const char *data, int eval, const char *registrar)
 {
-	char *exten = NULL, *context = NULL;
-	/* Variables used for different counters */
-	struct dialplan_counters counters;
-	const char *incstack[AST_PBX_MAX_STACK];
+	struct ast_sw *new_sw;
+	struct ast_sw *i;
+	int length;
+	char *p;
 
-	switch (cmd) {
-	case CLI_INIT:
-		e->command = "dialplan show";
-		e->usage =
-			"Usage: dialplan show [[exten@]context]\n"
-			"       Show dialplan\n";
-		return NULL;
-	case CLI_GENERATE:
-		return complete_show_dialplan_context(a->line, a->word, a->pos, a->n);
-	}
+	length = sizeof(struct ast_sw);
+	length += strlen(value) + 1;
+	if (data)
+		length += strlen(data);
+	length++;
 
-	memset(&counters, 0, sizeof(counters));
+	/* allocate new sw structure ... */
+	if (!(new_sw = ast_calloc(1, length)))
+		return -1;
+	/* ... fill in this structure ... */
+	p = new_sw->stuff;
+	new_sw->name = p;
+	strcpy(new_sw->name, value);
+	p += strlen(value) + 1;
+	new_sw->data = p;
+	if (data) {
+		strcpy(new_sw->data, data);
+		p += strlen(data) + 1;
+	} else {
+		strcpy(new_sw->data, "");
+		p++;
+	}
+	new_sw->eval	  = eval;
+	new_sw->registrar = registrar;
 
-	if (a->argc != 2 && a->argc != 3)
-		return CLI_SHOWUSAGE;
+	/* ... try to lock this context ... */
+	ast_wrlock_context(con);
 
-	/* we obtain [exten@]context? if yes, split them ... */
-	if (a->argc == 3) {
-		if (strchr(a->argv[2], '@')) {	/* split into exten & context */
-			context = ast_strdupa(a->argv[2]);
-			exten = strsep(&context, "@");
-			/* change empty strings to NULL */
-			if (ast_strlen_zero(exten))
-				exten = NULL;
-		} else { /* no '@' char, only context given */
-			context = ast_strdupa(a->argv[2]);
+	/* ... go to last sw and check if context is already swd too... */
+	AST_LIST_TRAVERSE(&con->alts, i, list) {
+		if (!strcasecmp(i->name, new_sw->name) && !strcasecmp(i->data, new_sw->data)) {
+			ast_free(new_sw);
+			ast_unlock_context(con);
+			errno = EEXIST;
+			return -1;
 		}
-		if (ast_strlen_zero(context))
-			context = NULL;
 	}
-	/* else Show complete dial plan, context and exten are NULL */
-	show_dialplan_helper(a->fd, context, exten, &counters, NULL, 0, incstack);
 
-	/* check for input failure and throw some error messages */
-	if (context && !counters.context_existence) {
-		ast_cli(a->fd, "There is no existence of '%s' context\n", context);
-		return CLI_FAILURE;
-	}
+	/* ... sw new context into context list, unlock, return */
+	AST_LIST_INSERT_TAIL(&con->alts, new_sw, list);
 
-	if (exten && !counters.extension_existence) {
-		if (context)
-			ast_cli(a->fd, "There is no existence of %s@%s extension\n",
-				exten, context);
-		else
-			ast_cli(a->fd,
-				"There is no existence of '%s' extension in all contexts\n",
-				exten);
-		return CLI_FAILURE;
-	}
+	ast_verb(3, "Including switch '%s/%s' in context '%s'\n", new_sw->name, new_sw->data, ast_get_context_name(con));
 
-	ast_cli(a->fd,"-= %d %s (%d %s) in %d %s. =-\n",
-				counters.total_exten, counters.total_exten == 1 ? "extension" : "extensions",
-				counters.total_prio, counters.total_prio == 1 ? "priority" : "priorities",
-				counters.total_context, counters.total_context == 1 ? "context" : "contexts");
+	ast_unlock_context(con);
 
-	/* everything ok */
-	return CLI_SUCCESS;
+	return 0;
 }
 
-/*! \brief Send ack once */
-static char *handle_debug_dialplan(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+/*
+ * EBUSY  - can't lock
+ * ENOENT - there is not context existence
+ */
+int ast_context_remove_ignorepat(const char *context, const char *ignorepat, const char *registrar)
 {
-	char *exten = NULL, *context = NULL;
-	/* Variables used for different counters */
-	struct dialplan_counters counters;
-	const char *incstack[AST_PBX_MAX_STACK];
+	int ret = -1;
+	struct ast_context *c;
 
-	switch (cmd) {
-	case CLI_INIT:
-		e->command = "dialplan debug";
-		e->usage =
-			"Usage: dialplan debug [context]\n"
-			"       Show dialplan context Trie(s). Usually only useful to folks debugging the deep internals of the fast pattern matcher\n";
-		return NULL;
-	case CLI_GENERATE:
-		return complete_show_dialplan_context(a->line, a->word, a->pos, a->n);
+	c = find_context_locked(context);
+	if (c) {
+		ret = ast_context_remove_ignorepat2(c, ignorepat, registrar);
+		ast_unlock_contexts();
 	}
+	return ret;
+}
 
-	memset(&counters, 0, sizeof(counters));
+int ast_context_remove_ignorepat2(struct ast_context *con, const char *ignorepat, const char *registrar)
+{
+	struct ast_ignorepat *ip, *ipl = NULL;
 
-	if (a->argc != 2 && a->argc != 3)
-		return CLI_SHOWUSAGE;
+	ast_wrlock_context(con);
 
-	/* we obtain [exten@]context? if yes, split them ... */
-	/* note: we ignore the exten totally here .... */
-	if (a->argc == 3) {
-		if (strchr(a->argv[2], '@')) {	/* split into exten & context */
-			context = ast_strdupa(a->argv[2]);
-			exten = strsep(&context, "@");
-			/* change empty strings to NULL */
-			if (ast_strlen_zero(exten))
-				exten = NULL;
-		} else { /* no '@' char, only context given */
-			context = ast_strdupa(a->argv[2]);
+	for (ip = con->ignorepats; ip; ip = ip->next) {
+		if (!strcmp(ip->pattern, ignorepat) &&
+			(!registrar || (registrar == ip->registrar))) {
+			if (ipl) {
+				ipl->next = ip->next;
+				ast_free(ip);
+			} else {
+				con->ignorepats = ip->next;
+				ast_free(ip);
+			}
+			ast_unlock_context(con);
+			return 0;
 		}
-		if (ast_strlen_zero(context))
-			context = NULL;
+		ipl = ip;
 	}
-	/* else Show complete dial plan, context and exten are NULL */
-	show_debug_helper(a->fd, context, exten, &counters, NULL, 0, incstack);
 
-	/* check for input failure and throw some error messages */
-	if (context && !counters.context_existence) {
-		ast_cli(a->fd, "There is no existence of '%s' context\n", context);
-		return CLI_FAILURE;
-	}
+	ast_unlock_context(con);
+	errno = EINVAL;
+	return -1;
+}
 
+/*
+ * EBUSY - can't lock
+ * ENOENT - there is no existence of context
+ */
+int ast_context_add_ignorepat(const char *context, const char *value, const char *registrar)
+{
+	int ret = -1;
+	struct ast_context *c;
 
-	ast_cli(a->fd,"-= %d %s. =-\n",
-			counters.total_context, counters.total_context == 1 ? "context" : "contexts");
+	c = find_context_locked(context);
+	if (c) {
+		ret = ast_context_add_ignorepat2(c, value, registrar);
+		ast_unlock_contexts();
+	}
+	return ret;
+}
+
+int ast_context_add_ignorepat2(struct ast_context *con, const char *value, const char *registrar)
+{
+	struct ast_ignorepat *ignorepat, *ignorepatc, *ignorepatl = NULL;
+	int length;
+	char *pattern;
+	length = sizeof(struct ast_ignorepat);
+	length += strlen(value) + 1;
+	if (!(ignorepat = ast_calloc(1, length)))
+		return -1;
+	/* The cast to char * is because we need to write the initial value.
+	 * The field is not supposed to be modified otherwise.  Also, gcc 4.2
+	 * sees the cast as dereferencing a type-punned pointer and warns about
+	 * it.  This is the workaround (we're telling gcc, yes, that's really
+	 * what we wanted to do).
+	 */
+	pattern = (char *) ignorepat->pattern;
+	strcpy(pattern, value);
+	ignorepat->next = NULL;
+	ignorepat->registrar = registrar;
+	ast_wrlock_context(con);
+	for (ignorepatc = con->ignorepats; ignorepatc; ignorepatc = ignorepatc->next) {
+		ignorepatl = ignorepatc;
+		if (!strcasecmp(ignorepatc->pattern, value)) {
+			/* Already there */
+			ast_unlock_context(con);
+			ast_free(ignorepat);
+			errno = EEXIST;
+			return -1;
+		}
+	}
+	if (ignorepatl)
+		ignorepatl->next = ignorepat;
+	else
+		con->ignorepats = ignorepat;
+	ast_unlock_context(con);
+	return 0;
 
-	/* everything ok */
-	return CLI_SUCCESS;
 }
 
-/*! \brief Send ack once */
-static void manager_dpsendack(struct mansession *s, const struct message *m)
+int ast_ignore_pattern(const char *context, const char *pattern)
 {
-	astman_send_listack(s, m, "DialPlan list will follow", "start");
+	int ret = 0;
+	struct ast_context *con;
+
+	ast_rdlock_contexts();
+	con = ast_context_find(context);
+	if (con) {
+		struct ast_ignorepat *pat;
+
+		for (pat = con->ignorepats; pat; pat = pat->next) {
+			if (ast_extension_match(pat->pattern, pattern)) {
+				ret = 1;
+				break;
+			}
+		}
+	}
+	ast_unlock_contexts();
+
+	return ret;
 }
 
-/*! \brief Show dialplan extensions
- * XXX this function is similar but not exactly the same as the CLI's
- * show dialplan. Must check whether the difference is intentional or not.
+/*
+ * ast_add_extension_nolock -- use only in situations where the conlock is already held
+ * ENOENT  - no existence of context
+ *
  */
-static int manager_show_dialplan_helper(struct mansession *s, const struct message *m,
-					const char *actionidtext, const char *context,
-					const char *exten, struct dialplan_counters *dpc,
-					struct ast_include *rinclude)
+static int ast_add_extension_nolock(const char *context, int replace, const char *extension,
+	int priority, const char *label, const char *callerid,
+	const char *application, void *data, void (*datad)(void *), const char *registrar)
 {
+	int ret = -1;
 	struct ast_context *c;
-	int res = 0, old_total_exten = dpc->total_exten;
 
-	if (ast_strlen_zero(exten))
-		exten = NULL;
-	if (ast_strlen_zero(context))
-		context = NULL;
+	c = find_context(context);
+	if (c) {
+		ret = ast_add_extension2_lockopt(c, replace, extension, priority, label, callerid,
+			application, data, datad, registrar, 1);
+	}
 
-	ast_debug(3, "manager_show_dialplan: Context: -%s- Extension: -%s-\n", context, exten);
+	return ret;
+}
+/*
+ * EBUSY   - can't lock
+ * ENOENT  - no existence of context
+ *
+ */
+int ast_add_extension(const char *context, int replace, const char *extension,
+	int priority, const char *label, const char *callerid,
+	const char *application, void *data, void (*datad)(void *), const char *registrar)
+{
+	int ret = -1;
+	struct ast_context *c;
 
-	/* try to lock contexts */
-	if (ast_rdlock_contexts()) {
-		astman_send_error(s, m, "Failed to lock contexts");
-		ast_log(LOG_WARNING, "Failed to lock contexts list for manager: listdialplan\n");
-		return -1;
+	c = find_context_locked(context);
+	if (c) {
+		ret = ast_add_extension2(c, replace, extension, priority, label, callerid,
+			application, data, datad, registrar);
+		ast_unlock_contexts();
 	}
 
-	c = NULL;		/* walk all contexts ... */
-	while ( (c = ast_walk_contexts(c)) ) {
-		struct ast_exten *e;
-		struct ast_include *i;
-		struct ast_ignorepat *ip;
-
-		if (context && strcmp(ast_get_context_name(c), context) != 0)
-			continue;	/* not the name we want */
+	return ret;
+}
 
-		dpc->context_existence = 1;
-		dpc->total_context++;
+int ast_explicit_goto(struct ast_channel *chan, const char *context, const char *exten, int priority)
+{
+	if (!chan)
+		return -1;
 
-		ast_debug(3, "manager_show_dialplan: Found Context: %s \n", ast_get_context_name(c));
+	ast_channel_lock(chan);
 
-		if (ast_rdlock_context(c)) {	/* failed to lock */
-			ast_debug(3, "manager_show_dialplan: Failed to lock context\n");
-			continue;
+	if (!ast_strlen_zero(context))
+		ast_channel_context_set(chan, context);
+	if (!ast_strlen_zero(exten))
+		ast_channel_exten_set(chan, exten);
+	if (priority > -1) {
+		/* see flag description in channel.h for explanation */
+		if (ast_test_flag(ast_channel_flags(chan), AST_FLAG_IN_AUTOLOOP)) {
+			--priority;
 		}
+		ast_channel_priority_set(chan, priority);
+	}
 
-		/* XXX note- an empty context is not printed */
-		e = NULL;		/* walk extensions in context  */
-		while ( (e = ast_walk_context_extensions(c, e)) ) {
-			struct ast_exten *p;
-
-			/* looking for extension? is this our extension? */
-			if (exten && !ast_extension_match(ast_get_extension_name(e), exten)) {
-				/* not the one we are looking for, continue */
-				ast_debug(3, "manager_show_dialplan: Skipping extension %s\n", ast_get_extension_name(e));
-				continue;
-			}
-			ast_debug(3, "manager_show_dialplan: Found Extension: %s \n", ast_get_extension_name(e));
-
-			dpc->extension_existence = 1;
+	ast_channel_unlock(chan);
 
-			dpc->total_exten++;
+	return 0;
+}
 
-			p = NULL;		/* walk next extension peers */
-			while ( (p = ast_walk_extension_priorities(e, p)) ) {
-				int prio = ast_get_extension_priority(p);
+int ast_async_goto(struct ast_channel *chan, const char *context, const char *exten, int priority)
+{
+	struct ast_channel *newchan;
 
-				dpc->total_prio++;
-				if (!dpc->total_items++)
-					manager_dpsendack(s, m);
-				astman_append(s, "Event: ListDialplan\r\n%s", actionidtext);
-				astman_append(s, "Context: %s\r\nExtension: %s\r\n", ast_get_context_name(c), ast_get_extension_name(e) );
-
-				/* XXX maybe make this conditional, if p != e ? */
-				if (ast_get_extension_label(p))
-					astman_append(s, "ExtensionLabel: %s\r\n", ast_get_extension_label(p));
-
-				if (prio == PRIORITY_HINT) {
-					astman_append(s, "Priority: hint\r\nApplication: %s\r\n", ast_get_extension_app(p));
-				} else {
-					astman_append(s, "Priority: %d\r\nApplication: %s\r\nAppData: %s\r\n", prio, ast_get_extension_app(p), (char *) ast_get_extension_app_data(p));
-				}
-				astman_append(s, "Registrar: %s\r\n\r\n", ast_get_extension_registrar(e));
-			}
-		}
-
-		i = NULL;		/* walk included and write info ... */
-		while ( (i = ast_walk_context_includes(c, i)) ) {
-			if (exten) {
-				/* Check all includes for the requested extension */
-				manager_show_dialplan_helper(s, m, actionidtext, ast_get_include_name(i), exten, dpc, i);
-			} else {
-				if (!dpc->total_items++)
-					manager_dpsendack(s, m);
-				astman_append(s, "Event: ListDialplan\r\n%s", actionidtext);
-				astman_append(s, "Context: %s\r\nIncludeContext: %s\r\nRegistrar: %s\r\n", ast_get_context_name(c), ast_get_include_name(i), ast_get_include_registrar(i));
-				astman_append(s, "\r\n");
-				ast_debug(3, "manager_show_dialplan: Found Included context: %s \n", ast_get_include_name(i));
-			}
-		}
-
-		ip = NULL;	/* walk ignore patterns and write info ... */
-		while ( (ip = ast_walk_context_ignorepats(c, ip)) ) {
-			const char *ipname = ast_get_ignorepat_name(ip);
-			char ignorepat[AST_MAX_EXTENSION];
-
-			snprintf(ignorepat, sizeof(ignorepat), "_%s.", ipname);
-			if (!exten || ast_extension_match(ignorepat, exten)) {
-				if (!dpc->total_items++)
-					manager_dpsendack(s, m);
-				astman_append(s, "Event: ListDialplan\r\n%s", actionidtext);
-				astman_append(s, "Context: %s\r\nIgnorePattern: %s\r\nRegistrar: %s\r\n", ast_get_context_name(c), ipname, ast_get_ignorepat_registrar(ip));
-				astman_append(s, "\r\n");
-			}
-		}
-		if (!rinclude) {
-			struct ast_sw *sw = NULL;
-			while ( (sw = ast_walk_context_switches(c, sw)) ) {
-				if (!dpc->total_items++)
-					manager_dpsendack(s, m);
-				astman_append(s, "Event: ListDialplan\r\n%s", actionidtext);
-				astman_append(s, "Context: %s\r\nSwitch: %s/%s\r\nRegistrar: %s\r\n", ast_get_context_name(c), ast_get_switch_name(sw), ast_get_switch_data(sw), ast_get_switch_registrar(sw));
-				astman_append(s, "\r\n");
-				ast_debug(3, "manager_show_dialplan: Found Switch : %s \n", ast_get_switch_name(sw));
-			}
+	ast_channel_lock(chan);
+	/* Channels in a bridge or running a PBX can be sent directly to the specified destination */
+	if (ast_channel_is_bridged(chan) || ast_channel_pbx(chan)) {
+		if (ast_test_flag(ast_channel_flags(chan), AST_FLAG_IN_AUTOLOOP)) {
+			priority += 1;
 		}
-
-		ast_unlock_context(c);
-	}
-	ast_unlock_contexts();
-
-	if (dpc->total_exten == old_total_exten) {
-		ast_debug(3, "manager_show_dialplan: Found nothing new\n");
-		/* Nothing new under the sun */
-		return -1;
-	} else {
-		return res;
-	}
-}
-
-/*! \brief  Manager listing of dial plan */
-static int manager_show_dialplan(struct mansession *s, const struct message *m)
-{
-	const char *exten, *context;
-	const char *id = astman_get_header(m, "ActionID");
-	char idtext[256];
-
-	/* Variables used for different counters */
-	struct dialplan_counters counters;
-
-	if (!ast_strlen_zero(id))
-		snprintf(idtext, sizeof(idtext), "ActionID: %s\r\n", id);
-	else
-		idtext[0] = '\0';
-
-	memset(&counters, 0, sizeof(counters));
-
-	exten = astman_get_header(m, "Extension");
-	context = astman_get_header(m, "Context");
-
-	manager_show_dialplan_helper(s, m, idtext, context, exten, &counters, NULL);
-
-	if (!ast_strlen_zero(context) && !counters.context_existence) {
-		char errorbuf[BUFSIZ];
-
-		snprintf(errorbuf, sizeof(errorbuf), "Did not find context %s", context);
-		astman_send_error(s, m, errorbuf);
+		ast_explicit_goto(chan, context, exten, priority);
+		ast_softhangup_nolock(chan, AST_SOFTHANGUP_ASYNCGOTO);
+		ast_channel_unlock(chan);
 		return 0;
 	}
-	if (!ast_strlen_zero(exten) && !counters.extension_existence) {
-		char errorbuf[BUFSIZ];
+	ast_channel_unlock(chan);
 
-		if (!ast_strlen_zero(context))
-			snprintf(errorbuf, sizeof(errorbuf), "Did not find extension %s@%s", exten, context);
-		else
-			snprintf(errorbuf, sizeof(errorbuf), "Did not find extension %s in any context", exten);
-		astman_send_error(s, m, errorbuf);
-		return 0;
+	/* Otherwise, we need to gain control of the channel first */
+	newchan = ast_channel_yank(chan);
+	if (!newchan) {
+		ast_log(LOG_WARNING, "Unable to gain control of channel %s\n", ast_channel_name(chan));
+		return -1;
 	}
-
-	if (!counters.total_items) {
-		manager_dpsendack(s, m);
+	ast_explicit_goto(newchan, context, exten, priority);
+	if (ast_pbx_start(newchan)) {
+		ast_hangup(newchan);
+		ast_log(LOG_WARNING, "Unable to start PBX on %s\n", ast_channel_name(newchan));
+		return -1;
 	}
 
-	astman_send_list_complete_start(s, m, "ShowDialPlanComplete", counters.total_items);
-	astman_append(s,
-		"ListExtensions: %d\r\n"
-		"ListPriorities: %d\r\n"
-		"ListContexts: %d\r\n",
-		counters.total_exten, counters.total_prio, counters.total_context);
-	astman_send_list_complete_end(s);
-
-	/* everything ok */
 	return 0;
 }
 
-/*! \brief CLI support for listing global variables in a parseable way */
-static char *handle_show_globals(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+int ast_async_goto_by_name(const char *channame, const char *context, const char *exten, int priority)
 {
-	int i = 0;
-	struct ast_var_t *newvariable;
-
-	switch (cmd) {
-	case CLI_INIT:
-		e->command = "dialplan show globals";
-		e->usage =
-			"Usage: dialplan show globals\n"
-			"       List current global dialplan variables and their values\n";
-		return NULL;
-	case CLI_GENERATE:
-		return NULL;
-	}
+	struct ast_channel *chan;
+	int res = -1;
 
-	ast_rwlock_rdlock(&globalslock);
-	AST_LIST_TRAVERSE (&globals, newvariable, entries) {
-		i++;
-		ast_cli(a->fd, "   %s=%s\n", ast_var_name(newvariable), ast_var_value(newvariable));
+	if ((chan = ast_channel_get_by_name(channame))) {
+		res = ast_async_goto(chan, context, exten, priority);
+		chan = ast_channel_unref(chan);
 	}
-	ast_rwlock_unlock(&globalslock);
-	ast_cli(a->fd, "\n    -- %d variable(s)\n", i);
 
-	return CLI_SUCCESS;
+	return res;
 }
 
-#ifdef AST_DEVMODE
-static char *handle_show_device2extenstate(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+/*! \brief copy a string skipping whitespace */
+static int ext_strncpy(char *dst, const char *src, int len)
 {
-	struct ast_devstate_aggregate agg;
-	int i, j, exten, combined;
+	int count = 0;
+	int insquares = 0;
 
-	switch (cmd) {
-	case CLI_INIT:
-		e->command = "core show device2extenstate";
-		e->usage =
-			"Usage: core show device2extenstate\n"
-			"       Lists device state to extension state combinations.\n";
-	case CLI_GENERATE:
-		return NULL;
-	}
-	for (i = 0; i < AST_DEVICE_TOTAL; i++) {
-		for (j = 0; j < AST_DEVICE_TOTAL; j++) {
-			ast_devstate_aggregate_init(&agg);
-			ast_devstate_aggregate_add(&agg, i);
-			ast_devstate_aggregate_add(&agg, j);
-			combined = ast_devstate_aggregate_result(&agg);
-			exten = ast_devstate_to_extenstate(combined);
-			ast_cli(a->fd, "\n Exten:%14s  CombinedDevice:%12s  Dev1:%12s  Dev2:%12s", ast_extension_state2str(exten), ast_devstate_str(combined), ast_devstate_str(j), ast_devstate_str(i));
+	while (*src && (count < len - 1)) {
+		if (*src == '[') {
+			insquares = 1;
+		} else if (*src == ']') {
+			insquares = 0;
+		} else if (*src == ' ' && !insquares) {
+			src++;
+			continue;
 		}
+		*dst = *src;
+		dst++;
+		src++;
+		count++;
 	}
-	ast_cli(a->fd, "\n");
-	return CLI_SUCCESS;
+	*dst = '\0';
+
+	return count;
 }
-#endif
 
-/*! \brief CLI support for listing chanvar's variables in a parseable way */
-static char *handle_show_chanvar(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+/*!
+ * \brief add the extension in the priority chain.
+ * \retval 0 on success.
+ * \retval -1 on failure.
+*/
+static int add_priority(struct ast_context *con, struct ast_exten *tmp,
+	struct ast_exten *el, struct ast_exten *e, int replace)
 {
-	struct ast_channel *chan;
-	struct ast_var_t *var;
+	struct ast_exten *ep;
+	struct ast_exten *eh=e;
+	int repeated_label = 0; /* Track if this label is a repeat, assume no. */
 
-	switch (cmd) {
-	case CLI_INIT:
-		e->command = "dialplan show chanvar";
-		e->usage =
-			"Usage: dialplan show chanvar <channel>\n"
-			"       List current channel variables and their values\n";
-		return NULL;
-	case CLI_GENERATE:
-		return ast_complete_channels(a->line, a->word, a->pos, a->n, 3);
+	for (ep = NULL; e ; ep = e, e = e->peer) {
+		if (e->label && tmp->label && e->priority != tmp->priority && !strcmp(e->label, tmp->label)) {
+			if (strcmp(e->exten, tmp->exten)) {
+				ast_log(LOG_WARNING,
+					"Extension '%s' priority %d in '%s', label '%s' already in use at aliased extension '%s' priority %d\n",
+					tmp->exten, tmp->priority, con->name, tmp->label, e->exten, e->priority);
+			} else {
+				ast_log(LOG_WARNING,
+					"Extension '%s' priority %d in '%s', label '%s' already in use at priority %d\n",
+					tmp->exten, tmp->priority, con->name, tmp->label, e->priority);
+			}
+			repeated_label = 1;
+		}
+		if (e->priority >= tmp->priority) {
+			break;
+		}
 	}
 
-	if (a->argc != e->args + 1) {
-		return CLI_SHOWUSAGE;
+	if (repeated_label) {	/* Discard the label since it's a repeat. */
+		tmp->label = NULL;
 	}
 
-	chan = ast_channel_get_by_name(a->argv[e->args]);
-	if (!chan) {
-		ast_cli(a->fd, "Channel '%s' not found\n", a->argv[e->args]);
-		return CLI_FAILURE;
-	}
+	if (!e) {	/* go at the end, and ep is surely set because the list is not empty */
+		ast_hashtab_insert_safe(eh->peer_table, tmp);
 
-	ast_channel_lock(chan);
-	AST_LIST_TRAVERSE(ast_channel_varshead(chan), var, entries) {
-		ast_cli(a->fd, "%s=%s\n", ast_var_name(var), ast_var_value(var));
+		if (tmp->label) {
+			ast_hashtab_insert_safe(eh->peer_label_table, tmp);
+		}
+		ep->peer = tmp;
+		return 0;	/* success */
 	}
-	ast_channel_unlock(chan);
+	if (e->priority == tmp->priority) {
+		/* Can't have something exactly the same.  Is this a
+		   replacement?  If so, replace, otherwise, bonk. */
+		if (!replace) {
+			if (strcmp(e->exten, tmp->exten)) {
+				ast_log(LOG_WARNING,
+					"Unable to register extension '%s' priority %d in '%s', already in use by aliased extension '%s'\n",
+					tmp->exten, tmp->priority, con->name, e->exten);
+			} else {
+				ast_log(LOG_WARNING,
+					"Unable to register extension '%s' priority %d in '%s', already in use\n",
+					tmp->exten, tmp->priority, con->name);
+			}
 
-	ast_channel_unref(chan);
-	return CLI_SUCCESS;
-}
-
-static char *handle_set_global(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
-{
-	switch (cmd) {
-	case CLI_INIT:
-		e->command = "dialplan set global";
-		e->usage =
-			"Usage: dialplan set global <name> <value>\n"
-			"       Set global dialplan variable <name> to <value>\n";
-		return NULL;
-	case CLI_GENERATE:
-		return NULL;
-	}
-
-	if (a->argc != e->args + 2)
-		return CLI_SHOWUSAGE;
-
-	pbx_builtin_setvar_helper(NULL, a->argv[3], a->argv[4]);
-	ast_cli(a->fd, "\n    -- Global variable '%s' set to '%s'\n", a->argv[3], a->argv[4]);
-
-	return CLI_SUCCESS;
-}
-
-static char *handle_set_chanvar(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
-{
-	struct ast_channel *chan;
-	const char *chan_name, *var_name, *var_value;
-
-	switch (cmd) {
-	case CLI_INIT:
-		e->command = "dialplan set chanvar";
-		e->usage =
-			"Usage: dialplan set chanvar <channel> <varname> <value>\n"
-			"       Set channel variable <varname> to <value>\n";
-		return NULL;
-	case CLI_GENERATE:
-		return ast_complete_channels(a->line, a->word, a->pos, a->n, 3);
-	}
-
-	if (a->argc != e->args + 3)
-		return CLI_SHOWUSAGE;
-
-	chan_name = a->argv[e->args];
-	var_name = a->argv[e->args + 1];
-	var_value = a->argv[e->args + 2];
-
-	if (!(chan = ast_channel_get_by_name(chan_name))) {
-		ast_cli(a->fd, "Channel '%s' not found\n", chan_name);
-		return CLI_FAILURE;
-	}
-
-	pbx_builtin_setvar_helper(chan, var_name, var_value);
-
-	chan = ast_channel_unref(chan);
-
-	ast_cli(a->fd, "\n    -- Channel variable '%s' set to '%s' for '%s'\n",  var_name, var_value, chan_name);
-
-	return CLI_SUCCESS;
-}
-
-static char *handle_set_extenpatternmatchnew(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
-{
-	int oldval = 0;
-
-	switch (cmd) {
-	case CLI_INIT:
-		e->command = "dialplan set extenpatternmatchnew true";
-		e->usage =
-			"Usage: dialplan set extenpatternmatchnew true|false\n"
-			"       Use the NEW extension pattern matching algorithm, true or false.\n";
-		return NULL;
-	case CLI_GENERATE:
-		return NULL;
-	}
-
-	if (a->argc != 4)
-		return CLI_SHOWUSAGE;
-
-	oldval =  pbx_set_extenpatternmatchnew(1);
-
-	if (oldval)
-		ast_cli(a->fd, "\n    -- Still using the NEW pattern match algorithm for extension names in the dialplan.\n");
-	else
-		ast_cli(a->fd, "\n    -- Switched to using the NEW pattern match algorithm for extension names in the dialplan.\n");
-
-	return CLI_SUCCESS;
-}
-
-static char *handle_unset_extenpatternmatchnew(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
-{
-	int oldval = 0;
-
-	switch (cmd) {
-	case CLI_INIT:
-		e->command = "dialplan set extenpatternmatchnew false";
-		e->usage =
-			"Usage: dialplan set extenpatternmatchnew true|false\n"
-			"       Use the NEW extension pattern matching algorithm, true or false.\n";
-		return NULL;
-	case CLI_GENERATE:
-		return NULL;
-	}
-
-	if (a->argc != 4)
-		return CLI_SHOWUSAGE;
-
-	oldval =  pbx_set_extenpatternmatchnew(0);
-
-	if (!oldval)
-		ast_cli(a->fd, "\n    -- Still using the OLD pattern match algorithm for extension names in the dialplan.\n");
-	else
-		ast_cli(a->fd, "\n    -- Switched to using the OLD pattern match algorithm for extension names in the dialplan.\n");
-
-	return CLI_SUCCESS;
-}
-
-/*
- * CLI entries for upper commands ...
- */
-static struct ast_cli_entry pbx_cli[] = {
-#if 0
-	AST_CLI_DEFINE(handle_eat_memory, "Eats all available memory"),
-#endif
-	AST_CLI_DEFINE(handle_show_applications, "Shows registered dialplan applications"),
-	AST_CLI_DEFINE(handle_show_functions, "Shows registered dialplan functions"),
-	AST_CLI_DEFINE(handle_show_switches, "Show alternative switches"),
-	AST_CLI_DEFINE(handle_show_hints, "Show dialplan hints"),
-	AST_CLI_DEFINE(handle_show_hint, "Show dialplan hint"),
-	AST_CLI_DEFINE(handle_show_globals, "Show global dialplan variables"),
-#ifdef AST_DEVMODE
-	AST_CLI_DEFINE(handle_show_device2extenstate, "Show expected exten state from multiple device states"),
-#endif
-	AST_CLI_DEFINE(handle_show_chanvar, "Show channel variables"),
-	AST_CLI_DEFINE(handle_show_function, "Describe a specific dialplan function"),
-	AST_CLI_DEFINE(handle_show_hangup_all, "Show hangup handlers of all channels"),
-	AST_CLI_DEFINE(handle_show_hangup_channel, "Show hangup handlers of a specified channel"),
-	AST_CLI_DEFINE(handle_show_application, "Describe a specific dialplan application"),
-	AST_CLI_DEFINE(handle_set_global, "Set global dialplan variable"),
-	AST_CLI_DEFINE(handle_set_chanvar, "Set a channel variable"),
-	AST_CLI_DEFINE(handle_show_dialplan, "Show dialplan"),
-	AST_CLI_DEFINE(handle_debug_dialplan, "Show fast extension pattern matching data structures"),
-	AST_CLI_DEFINE(handle_unset_extenpatternmatchnew, "Use the Old extension pattern matching algorithm."),
-	AST_CLI_DEFINE(handle_set_extenpatternmatchnew, "Use the New extension pattern matching algorithm."),
-};
-
-static void unreference_cached_app(struct ast_app *app)
-{
-	struct ast_context *context = NULL;
-	struct ast_exten *eroot = NULL, *e = NULL;
-
-	ast_rdlock_contexts();
-	while ((context = ast_walk_contexts(context))) {
-		while ((eroot = ast_walk_context_extensions(context, eroot))) {
-			while ((e = ast_walk_extension_priorities(eroot, e))) {
-				if (e->cached_app == app)
-					e->cached_app = NULL;
-			}
-		}
-	}
-	ast_unlock_contexts();
-
-	return;
-}
-
-int ast_unregister_application(const char *app)
-{
-	struct ast_app *cur;
-	int cmp;
-
-	AST_RWLIST_WRLOCK(&apps);
-	AST_RWLIST_TRAVERSE_SAFE_BEGIN(&apps, cur, list) {
-		cmp = strcasecmp(app, cur->name);
-		if (cmp > 0) {
-			continue;
-		}
-		if (!cmp) {
-			/* Found it. */
-			unreference_cached_app(cur);
-			AST_RWLIST_REMOVE_CURRENT(list);
-			ast_verb(2, "Unregistered application '%s'\n", cur->name);
-			ast_string_field_free_memory(cur);
-			ast_free(cur);
-			break;
-		}
-		/* Not in container. */
-		cur = NULL;
-		break;
-	}
-	AST_RWLIST_TRAVERSE_SAFE_END;
-	AST_RWLIST_UNLOCK(&apps);
-
-	return cur ? 0 : -1;
-}
-
-struct ast_context *ast_context_find_or_create(struct ast_context **extcontexts, struct ast_hashtab *exttable, const char *name, const char *registrar)
-{
-	struct ast_context *tmp, **local_contexts;
-	struct fake_context search;
-	int length = sizeof(struct ast_context) + strlen(name) + 1;
-
-	if (!contexts_table) {
-		/* Protect creation of contexts_table from reentrancy. */
-		ast_wrlock_contexts();
-		if (!contexts_table) {
-			contexts_table = ast_hashtab_create(17,
-				ast_hashtab_compare_contexts,
-				ast_hashtab_resize_java,
-				ast_hashtab_newsize_java,
-				ast_hashtab_hash_contexts,
-				0);
-		}
-		ast_unlock_contexts();
-	}
-
-	ast_copy_string(search.name, name, sizeof(search.name));
-	if (!extcontexts) {
-		ast_rdlock_contexts();
-		local_contexts = &contexts;
-		tmp = ast_hashtab_lookup(contexts_table, &search);
-		if (tmp) {
-			tmp->refcount++;
-			ast_unlock_contexts();
-			return tmp;
-		}
-	} else { /* local contexts just in a linked list; search there for the new context; slow, linear search, but not frequent */
-		local_contexts = extcontexts;
-		tmp = ast_hashtab_lookup(exttable, &search);
-		if (tmp) {
-			tmp->refcount++;
-			return tmp;
-		}
-	}
-
-	if ((tmp = ast_calloc(1, length))) {
-		ast_rwlock_init(&tmp->lock);
-		ast_mutex_init(&tmp->macrolock);
-		strcpy(tmp->name, name);
-		tmp->root = NULL;
-		tmp->root_table = NULL;
-		tmp->registrar = ast_strdup(registrar);
-		tmp->includes = NULL;
-		tmp->ignorepats = NULL;
-		tmp->refcount = 1;
-	} else {
-		ast_log(LOG_ERROR, "Danger! We failed to allocate a context for %s!\n", name);
-		if (!extcontexts) {
-			ast_unlock_contexts();
-		}
-		return NULL;
-	}
-
-	if (!extcontexts) {
-		tmp->next = *local_contexts;
-		*local_contexts = tmp;
-		ast_hashtab_insert_safe(contexts_table, tmp); /*put this context into the tree */
-		ast_unlock_contexts();
-		ast_verb(3, "Registered extension context '%s'; registrar: %s\n", tmp->name, registrar);
-	} else {
-		tmp->next = *local_contexts;
-		if (exttable)
-			ast_hashtab_insert_immediate(exttable, tmp); /*put this context into the tree */
-
-		*local_contexts = tmp;
-		ast_verb(3, "Registered extension context '%s'; registrar: %s\n", tmp->name, registrar);
-	}
-	return tmp;
-}
-
-void __ast_context_destroy(struct ast_context *list, struct ast_hashtab *contexttab, struct ast_context *con, const char *registrar);
-
-struct store_hint {
-	char *context;
-	char *exten;
-	AST_LIST_HEAD_NOLOCK(, ast_state_cb) callbacks;
-	int laststate;
-	int last_presence_state;
-	char *last_presence_subtype;
-	char *last_presence_message;
-
-	AST_LIST_ENTRY(store_hint) list;
-	char data[1];
-};
-
-AST_LIST_HEAD_NOLOCK(store_hints, store_hint);
-
-static void context_merge_incls_swits_igps_other_registrars(struct ast_context *new, struct ast_context *old, const char *registrar)
-{
-	struct ast_include *i;
-	struct ast_ignorepat *ip;
-	struct ast_sw *sw;
-
-	ast_verb(3, "merging incls/swits/igpats from old(%s) to new(%s) context, registrar = %s\n", ast_get_context_name(old), ast_get_context_name(new), registrar);
-	/* copy in the includes, switches, and ignorepats */
-	/* walk through includes */
-	for (i = NULL; (i = ast_walk_context_includes(old, i)) ; ) {
-		if (strcmp(ast_get_include_registrar(i), registrar) == 0)
-			continue; /* not mine */
-		ast_context_add_include2(new, ast_get_include_name(i), ast_get_include_registrar(i));
-	}
-
-	/* walk through switches */
-	for (sw = NULL; (sw = ast_walk_context_switches(old, sw)) ; ) {
-		if (strcmp(ast_get_switch_registrar(sw), registrar) == 0)
-			continue; /* not mine */
-		ast_context_add_switch2(new, ast_get_switch_name(sw), ast_get_switch_data(sw), ast_get_switch_eval(sw), ast_get_switch_registrar(sw));
-	}
-
-	/* walk thru ignorepats ... */
-	for (ip = NULL; (ip = ast_walk_context_ignorepats(old, ip)); ) {
-		if (strcmp(ast_get_ignorepat_registrar(ip), registrar) == 0)
-			continue; /* not mine */
-		ast_context_add_ignorepat2(new, ast_get_ignorepat_name(ip), ast_get_ignorepat_registrar(ip));
-	}
-}
-
-
-/* the purpose of this routine is to duplicate a context, with all its substructure,
-   except for any extens that have a matching registrar */
-static void context_merge(struct ast_context **extcontexts, struct ast_hashtab *exttable, struct ast_context *context, const char *registrar)
-{
-	struct ast_context *new = ast_hashtab_lookup(exttable, context); /* is there a match in the new set? */
-	struct ast_exten *exten_item, *prio_item, *new_exten_item, *new_prio_item;
-	struct ast_hashtab_iter *exten_iter;
-	struct ast_hashtab_iter *prio_iter;
-	int insert_count = 0;
-	int first = 1;
-
-	/* We'll traverse all the extensions/prios, and see which are not registrar'd with
-	   the current registrar, and copy them to the new context. If the new context does not
-	   exist, we'll create it "on demand". If no items are in this context to copy, then we'll
-	   only create the empty matching context if the old one meets the criteria */
-
-	if (context->root_table) {
-		exten_iter = ast_hashtab_start_traversal(context->root_table);
-		while ((exten_item=ast_hashtab_next(exten_iter))) {
-			if (new) {
-				new_exten_item = ast_hashtab_lookup(new->root_table, exten_item);
-			} else {
-				new_exten_item = NULL;
-			}
-			prio_iter = ast_hashtab_start_traversal(exten_item->peer_table);
-			while ((prio_item=ast_hashtab_next(prio_iter))) {
-				int res1;
-				char *dupdstr;
-
-				if (new_exten_item) {
-					new_prio_item = ast_hashtab_lookup(new_exten_item->peer_table, prio_item);
-				} else {
-					new_prio_item = NULL;
-				}
-				if (strcmp(prio_item->registrar,registrar) == 0) {
-					continue;
-				}
-				/* make sure the new context exists, so we have somewhere to stick this exten/prio */
-				if (!new) {
-					new = ast_context_find_or_create(extcontexts, exttable, context->name, prio_item->registrar); /* a new context created via priority from a different context in the old dialplan, gets its registrar from the prio's registrar */
-				}
-
-				/* copy in the includes, switches, and ignorepats */
-				if (first) { /* but, only need to do this once */
-					context_merge_incls_swits_igps_other_registrars(new, context, registrar);
-					first = 0;
-				}
-
-				if (!new) {
-					ast_log(LOG_ERROR,"Could not allocate a new context for %s in merge_and_delete! Danger!\n", context->name);
-					ast_hashtab_end_traversal(prio_iter);
-					ast_hashtab_end_traversal(exten_iter);
-					return; /* no sense continuing. */
-				}
-				/* we will not replace existing entries in the new context with stuff from the old context.
-				   but, if this is because of some sort of registrar conflict, we ought to say something... */
-
-				dupdstr = ast_strdup(prio_item->data);
-
-				res1 = ast_add_extension2(new, 0, prio_item->exten, prio_item->priority, prio_item->label,
-										  prio_item->matchcid ? prio_item->cidmatch : NULL, prio_item->app, dupdstr, ast_free_ptr, prio_item->registrar);
-				if (!res1 && new_exten_item && new_prio_item){
-					ast_verb(3,"Dropping old dialplan item %s/%s/%d [%s(%s)] (registrar=%s) due to conflict with new dialplan\n",
-							context->name, prio_item->exten, prio_item->priority, prio_item->app, (char*)prio_item->data, prio_item->registrar);
-				} else {
-					/* we do NOT pass the priority data from the old to the new -- we pass a copy of it, so no changes to the current dialplan take place,
-					 and no double frees take place, either! */
-					insert_count++;
-				}
-			}
-			ast_hashtab_end_traversal(prio_iter);
-		}
-		ast_hashtab_end_traversal(exten_iter);
-	} else if (new) {
-		/* If the context existed but had no extensions, we still want to merge
-		 * the includes, switches and ignore patterns.
-		 */
-		context_merge_incls_swits_igps_other_registrars(new, context, registrar);
-	}
-
-	if (!insert_count && !new && (strcmp(context->registrar, registrar) != 0 ||
-		  (strcmp(context->registrar, registrar) == 0 && context->refcount > 1))) {
-		/* we could have given it the registrar of the other module who incremented the refcount,
-		   but that's not available, so we give it the registrar we know about */
-		new = ast_context_find_or_create(extcontexts, exttable, context->name, context->registrar);
-
-		/* copy in the includes, switches, and ignorepats */
-		context_merge_incls_swits_igps_other_registrars(new, context, registrar);
-	}
-}
-
-
-/* XXX this does not check that multiple contexts are merged */
-void ast_merge_contexts_and_delete(struct ast_context **extcontexts, struct ast_hashtab *exttable, const char *registrar)
-{
-	double ft;
-	struct ast_context *tmp;
-	struct ast_context *oldcontextslist;
-	struct ast_hashtab *oldtable;
-	struct store_hints hints_stored = AST_LIST_HEAD_NOLOCK_INIT_VALUE;
-	struct store_hints hints_removed = AST_LIST_HEAD_NOLOCK_INIT_VALUE;
-	struct store_hint *saved_hint;
-	struct ast_hint *hint;
-	struct ast_exten *exten;
-	int length;
-	struct ast_state_cb *thiscb;
-	struct ast_hashtab_iter *iter;
-	struct ao2_iterator i;
-	struct timeval begintime;
-	struct timeval writelocktime;
-	struct timeval endlocktime;
-	struct timeval enddeltime;
-
-	/*
-	 * It is very important that this function hold the hints
-	 * container lock _and_ the conlock during its operation; not
-	 * only do we need to ensure that the list of contexts and
-	 * extensions does not change, but also that no hint callbacks
-	 * (watchers) are added or removed during the merge/delete
-	 * process
-	 *
-	 * In addition, the locks _must_ be taken in this order, because
-	 * there are already other code paths that use this order
-	 */
-
-	begintime = ast_tvnow();
-	ast_mutex_lock(&context_merge_lock);/* Serialize ast_merge_contexts_and_delete */
-	ast_wrlock_contexts();
-
-	if (!contexts_table) {
-		/* Well, that's odd. There are no contexts. */
-		contexts_table = exttable;
-		contexts = *extcontexts;
-		ast_unlock_contexts();
-		ast_mutex_unlock(&context_merge_lock);
-		return;
-	}
-
-	iter = ast_hashtab_start_traversal(contexts_table);
-	while ((tmp = ast_hashtab_next(iter))) {
-		context_merge(extcontexts, exttable, tmp, registrar);
-	}
-	ast_hashtab_end_traversal(iter);
-
-	ao2_lock(hints);
-	writelocktime = ast_tvnow();
-
-	/* preserve all watchers for hints */
-	i = ao2_iterator_init(hints, AO2_ITERATOR_DONTLOCK);
-	for (; (hint = ao2_iterator_next(&i)); ao2_ref(hint, -1)) {
-		if (ao2_container_count(hint->callbacks)) {
-			ao2_lock(hint);
-			if (!hint->exten) {
-				/* The extension has already been destroyed. (Should never happen here) */
-				ao2_unlock(hint);
-				continue;
-			}
-
-			length = strlen(hint->exten->exten) + strlen(hint->exten->parent->name) + 2
-				+ sizeof(*saved_hint);
-			if (!(saved_hint = ast_calloc(1, length))) {
-				ao2_unlock(hint);
-				continue;
-			}
-
-			/* This removes all the callbacks from the hint into saved_hint. */
-			while ((thiscb = ao2_callback(hint->callbacks, OBJ_UNLINK, NULL, NULL))) {
-				AST_LIST_INSERT_TAIL(&saved_hint->callbacks, thiscb, entry);
-				/*
-				 * We intentionally do not unref thiscb to account for the
-				 * non-ao2 reference in saved_hint->callbacks
-				 */
-			}
-
-			saved_hint->laststate = hint->laststate;
-			saved_hint->context = saved_hint->data;
-			strcpy(saved_hint->data, hint->exten->parent->name);
-			saved_hint->exten = saved_hint->data + strlen(saved_hint->context) + 1;
-			strcpy(saved_hint->exten, hint->exten->exten);
-			if (hint->last_presence_subtype) {
-				saved_hint->last_presence_subtype = ast_strdup(hint->last_presence_subtype);
-			}
-			if (hint->last_presence_message) {
-				saved_hint->last_presence_message = ast_strdup(hint->last_presence_message);
-			}
-			saved_hint->last_presence_state = hint->last_presence_state;
-			ao2_unlock(hint);
-			AST_LIST_INSERT_HEAD(&hints_stored, saved_hint, list);
-		}
-	}
-	ao2_iterator_destroy(&i);
-
-	/* save the old table and list */
-	oldtable = contexts_table;
-	oldcontextslist = contexts;
-
-	/* move in the new table and list */
-	contexts_table = exttable;
-	contexts = *extcontexts;
-
-	/*
-	 * Restore the watchers for hints that can be found; notify
-	 * those that cannot be restored.
-	 */
-	while ((saved_hint = AST_LIST_REMOVE_HEAD(&hints_stored, list))) {
-		struct pbx_find_info q = { .stacklen = 0 };
-
-		exten = pbx_find_extension(NULL, NULL, &q, saved_hint->context, saved_hint->exten,
-			PRIORITY_HINT, NULL, "", E_MATCH);
-		/*
-		 * If this is a pattern, dynamically create a new extension for this
-		 * particular match.  Note that this will only happen once for each
-		 * individual extension, because the pattern will no longer match first.
-		 */
-		if (exten && exten->exten[0] == '_') {
-			ast_add_extension_nolock(exten->parent->name, 0, saved_hint->exten,
-				PRIORITY_HINT, NULL, 0, exten->app, ast_strdup(exten->data), ast_free_ptr,
-				exten->registrar);
-			/* rwlocks are not recursive locks */
-			exten = ast_hint_extension_nolock(NULL, saved_hint->context,
-				saved_hint->exten);
-		}
-
-		/* Find the hint in the hints container */
-		hint = exten ? ao2_find(hints, exten, 0) : NULL;
-		if (!hint) {
-			/*
-			 * Notify watchers of this removed hint later when we aren't
-			 * encumberd by so many locks.
-			 */
-			AST_LIST_INSERT_HEAD(&hints_removed, saved_hint, list);
-		} else {
-			ao2_lock(hint);
-			while ((thiscb = AST_LIST_REMOVE_HEAD(&saved_hint->callbacks, entry))) {
-				ao2_link(hint->callbacks, thiscb);
-				/* Ref that we added when putting into saved_hint->callbacks */
-				ao2_ref(thiscb, -1);
-			}
-			hint->laststate = saved_hint->laststate;
-			hint->last_presence_state = saved_hint->last_presence_state;
-			hint->last_presence_subtype = saved_hint->last_presence_subtype;
-			hint->last_presence_message = saved_hint->last_presence_message;
-			ao2_unlock(hint);
-			ao2_ref(hint, -1);
-			/*
-			 * The free of saved_hint->last_presence_subtype and
-			 * saved_hint->last_presence_message is not necessary here.
-			 */
-			ast_free(saved_hint);
-		}
-	}
-
-	ao2_unlock(hints);
-	ast_unlock_contexts();
-
-	/*
-	 * Notify watchers of all removed hints with the same lock
-	 * environment as device_state_cb().
-	 */
-	while ((saved_hint = AST_LIST_REMOVE_HEAD(&hints_removed, list))) {
-		/* this hint has been removed, notify the watchers */
-		while ((thiscb = AST_LIST_REMOVE_HEAD(&saved_hint->callbacks, entry))) {
-			execute_state_callback(thiscb->change_cb,
-				saved_hint->context,
-				saved_hint->exten,
-				thiscb->data,
-				AST_HINT_UPDATE_DEVICE,
-				NULL,
-				NULL);
-			/* Ref that we added when putting into saved_hint->callbacks */
-			ao2_ref(thiscb, -1);
-		}
-		ast_free(saved_hint->last_presence_subtype);
-		ast_free(saved_hint->last_presence_message);
-		ast_free(saved_hint);
-	}
-
-	ast_mutex_unlock(&context_merge_lock);
-	endlocktime = ast_tvnow();
-
-	/*
-	 * The old list and hashtab no longer are relevant, delete them
-	 * while the rest of asterisk is now freely using the new stuff
-	 * instead.
-	 */
-
-	ast_hashtab_destroy(oldtable, NULL);
-
-	for (tmp = oldcontextslist; tmp; ) {
-		struct ast_context *next;	/* next starting point */
-
-		next = tmp->next;
-		__ast_internal_context_destroy(tmp);
-		tmp = next;
-	}
-	enddeltime = ast_tvnow();
-
-	ft = ast_tvdiff_us(writelocktime, begintime);
-	ft /= 1000000.0;
-	ast_verb(3,"Time to scan old dialplan and merge leftovers back into the new: %8.6f sec\n", ft);
-
-	ft = ast_tvdiff_us(endlocktime, writelocktime);
-	ft /= 1000000.0;
-	ast_verb(3,"Time to restore hints and swap in new dialplan: %8.6f sec\n", ft);
-
-	ft = ast_tvdiff_us(enddeltime, endlocktime);
-	ft /= 1000000.0;
-	ast_verb(3,"Time to delete the old dialplan: %8.6f sec\n", ft);
-
-	ft = ast_tvdiff_us(enddeltime, begintime);
-	ft /= 1000000.0;
-	ast_verb(3,"Total time merge_contexts_delete: %8.6f sec\n", ft);
-}
-
-/*
- * errno values
- *  EBUSY  - can't lock
- *  ENOENT - no existence of context
- */
-int ast_context_add_include(const char *context, const char *include, const char *registrar)
-{
-	int ret = -1;
-	struct ast_context *c;
-
-	c = find_context_locked(context);
-	if (c) {
-		ret = ast_context_add_include2(c, include, registrar);
-		ast_unlock_contexts();
-	}
-	return ret;
-}
-
-/*! \brief Helper for get_range.
- * return the index of the matching entry, starting from 1.
- * If names is not supplied, try numeric values.
- */
-static int lookup_name(const char *s, const char * const names[], int max)
-{
-	int i;
-
-	if (names && *s > '9') {
-		for (i = 0; names[i]; i++) {
-			if (!strcasecmp(s, names[i])) {
-				return i;
-			}
-		}
-	}
-
-	/* Allow months and weekdays to be specified as numbers, as well */
-	if (sscanf(s, "%2d", &i) == 1 && i >= 1 && i <= max) {
-		/* What the array offset would have been: "1" would be at offset 0 */
-		return i - 1;
-	}
-	return -1; /* error return */
-}
-
-/*! \brief helper function to return a range up to max (7, 12, 31 respectively).
- * names, if supplied, is an array of names that should be mapped to numbers.
- */
-static unsigned get_range(char *src, int max, const char * const names[], const char *msg)
-{
-	int start, end; /* start and ending position */
-	unsigned int mask = 0;
-	char *part;
-
-	/* Check for whole range */
-	if (ast_strlen_zero(src) || !strcmp(src, "*")) {
-		return (1 << max) - 1;
-	}
-
-	while ((part = strsep(&src, "&"))) {
-		/* Get start and ending position */
-		char *endpart = strchr(part, '-');
-		if (endpart) {
-			*endpart++ = '\0';
-		}
-		/* Find the start */
-		if ((start = lookup_name(part, names, max)) < 0) {
-			ast_log(LOG_WARNING, "Invalid %s '%s', skipping element\n", msg, part);
-			continue;
-		}
-		if (endpart) { /* find end of range */
-			if ((end = lookup_name(endpart, names, max)) < 0) {
-				ast_log(LOG_WARNING, "Invalid end %s '%s', skipping element\n", msg, endpart);
-				continue;
-			}
-		} else {
-			end = start;
-		}
-		/* Fill the mask. Remember that ranges are cyclic */
-		mask |= (1 << end);   /* initialize with last element */
-		while (start != end) {
-			mask |= (1 << start);
-			if (++start >= max) {
-				start = 0;
-			}
-		}
-	}
-	return mask;
-}
-
-/*! \brief store a bitmask of valid times, one bit each 1 minute */
-static void get_timerange(struct ast_timing *i, char *times)
-{
-	char *endpart, *part;
-	int x;
-	int st_h, st_m;
-	int endh, endm;
-	int minute_start, minute_end;
-
-	/* start disabling all times, fill the fields with 0's, as they may contain garbage */
-	memset(i->minmask, 0, sizeof(i->minmask));
-
-	/* 1-minute per bit */
-	/* Star is all times */
-	if (ast_strlen_zero(times) || !strcmp(times, "*")) {
-		/* 48, because each hour takes 2 integers; 30 bits each */
-		for (x = 0; x < 48; x++) {
-			i->minmask[x] = 0x3fffffff; /* 30 bits */
-		}
-		return;
-	}
-	/* Otherwise expect a range */
-	while ((part = strsep(&times, "&"))) {
-		if (!(endpart = strchr(part, '-'))) {
-			if (sscanf(part, "%2d:%2d", &st_h, &st_m) != 2 || st_h < 0 || st_h > 23 || st_m < 0 || st_m > 59) {
-				ast_log(LOG_WARNING, "%s isn't a valid time.\n", part);
-				continue;
-			}
-			i->minmask[st_h * 2 + (st_m >= 30 ? 1 : 0)] |= (1 << (st_m % 30));
-			continue;
-		}
-		*endpart++ = '\0';
-		/* why skip non digits? Mostly to skip spaces */
-		while (*endpart && !isdigit(*endpart)) {
-			endpart++;
-		}
-		if (!*endpart) {
-			ast_log(LOG_WARNING, "Invalid time range starting with '%s-'.\n", part);
-			continue;
-		}
-		if (sscanf(part, "%2d:%2d", &st_h, &st_m) != 2 || st_h < 0 || st_h > 23 || st_m < 0 || st_m > 59) {
-			ast_log(LOG_WARNING, "'%s' isn't a valid start time.\n", part);
-			continue;
-		}
-		if (sscanf(endpart, "%2d:%2d", &endh, &endm) != 2 || endh < 0 || endh > 23 || endm < 0 || endm > 59) {
-			ast_log(LOG_WARNING, "'%s' isn't a valid end time.\n", endpart);
-			continue;
-		}
-		minute_start = st_h * 60 + st_m;
-		minute_end = endh * 60 + endm;
-		/* Go through the time and enable each appropriate bit */
-		for (x = minute_start; x != minute_end; x = (x + 1) % (24 * 60)) {
-			i->minmask[x / 30] |= (1 << (x % 30));
-		}
-		/* Do the last one */
-		i->minmask[x / 30] |= (1 << (x % 30));
-	}
-	/* All done */
-	return;
-}
-
-static const char * const days[] =
-{
-	"sun",
-	"mon",
-	"tue",
-	"wed",
-	"thu",
-	"fri",
-	"sat",
-	NULL,
-};
-
-static const char * const months[] =
-{
-	"jan",
-	"feb",
-	"mar",
-	"apr",
-	"may",
-	"jun",
-	"jul",
-	"aug",
-	"sep",
-	"oct",
-	"nov",
-	"dec",
-	NULL,
-};
-/*! /brief Build timing
- *
- * /param i info
- * /param info_in
- *
- */
-int ast_build_timing(struct ast_timing *i, const char *info_in)
-{
-	char *info;
-	int j, num_fields, last_sep = -1;
-
-	i->timezone = NULL;
-
-	/* Check for empty just in case */
-	if (ast_strlen_zero(info_in)) {
-		return 0;
-	}
-
-	/* make a copy just in case we were passed a static string */
-	info = ast_strdupa(info_in);
-
-	/* count the number of fields in the timespec */
-	for (j = 0, num_fields = 1; info[j] != '\0'; j++) {
-		if (info[j] == ',') {
-			last_sep = j;
-			num_fields++;
-		}
-	}
-
-	/* save the timezone, if it is specified */
-	if (num_fields == 5) {
-		i->timezone = ast_strdup(info + last_sep + 1);
-	}
-
-	/* Assume everything except time */
-	i->monthmask = 0xfff;	/* 12 bits */
-	i->daymask = 0x7fffffffU; /* 31 bits */
-	i->dowmask = 0x7f; /* 7 bits */
-	/* on each call, use strsep() to move info to the next argument */
-	get_timerange(i, strsep(&info, "|,"));
-	if (info)
-		i->dowmask = get_range(strsep(&info, "|,"), 7, days, "day of week");
-	if (info)
-		i->daymask = get_range(strsep(&info, "|,"), 31, NULL, "day");
-	if (info)
-		i->monthmask = get_range(strsep(&info, "|,"), 12, months, "month");
-	return 1;
-}
-
-int ast_check_timing(const struct ast_timing *i)
-{
-	return ast_check_timing2(i, ast_tvnow());
-}
-
-int ast_check_timing2(const struct ast_timing *i, const struct timeval tv)
-{
-	struct ast_tm tm;
-
-	ast_localtime(&tv, &tm, i->timezone);
-
-	/* If it's not the right month, return */
-	if (!(i->monthmask & (1 << tm.tm_mon)))
-		return 0;
-
-	/* If it's not that time of the month.... */
-	/* Warning, tm_mday has range 1..31! */
-	if (!(i->daymask & (1 << (tm.tm_mday-1))))
-		return 0;
-
-	/* If it's not the right day of the week */
-	if (!(i->dowmask & (1 << tm.tm_wday)))
-		return 0;
-
-	/* Sanity check the hour just to be safe */
-	if ((tm.tm_hour < 0) || (tm.tm_hour > 23)) {
-		ast_log(LOG_WARNING, "Insane time...\n");
-		return 0;
-	}
-
-	/* Now the tough part, we calculate if it fits
-	   in the right time based on min/hour */
-	if (!(i->minmask[tm.tm_hour * 2 + (tm.tm_min >= 30 ? 1 : 0)] & (1 << (tm.tm_min >= 30 ? tm.tm_min - 30 : tm.tm_min))))
-		return 0;
-
-	/* If we got this far, then we're good */
-	return 1;
-}
-
-int ast_destroy_timing(struct ast_timing *i)
-{
-	if (i->timezone) {
-		ast_free(i->timezone);
-		i->timezone = NULL;
-	}
-	return 0;
-}
-/*
- * errno values
- *  ENOMEM - out of memory
- *  EBUSY  - can't lock
- *  EEXIST - already included
- *  EINVAL - there is no existence of context for inclusion
- */
-int ast_context_add_include2(struct ast_context *con, const char *value,
-	const char *registrar)
-{
-	struct ast_include *new_include;
-	char *c;
-	struct ast_include *i, *il = NULL; /* include, include_last */
-	int length;
-	char *p;
-
-	length = sizeof(struct ast_include);
-	length += 2 * (strlen(value) + 1);
-
-	/* allocate new include structure ... */
-	if (!(new_include = ast_calloc(1, length)))
-		return -1;
-	/* Fill in this structure. Use 'p' for assignments, as the fields
-	 * in the structure are 'const char *'
-	 */
-	p = new_include->stuff;
-	new_include->name = p;
-	strcpy(p, value);
-	p += strlen(value) + 1;
-	new_include->rname = p;
-	strcpy(p, value);
-	/* Strip off timing info, and process if it is there */
-	if ( (c = strchr(p, ',')) ) {
-		*c++ = '\0';
-		new_include->hastime = ast_build_timing(&(new_include->timing), c);
-	}
-	new_include->next      = NULL;
-	new_include->registrar = registrar;
-
-	ast_wrlock_context(con);
-
-	/* ... go to last include and check if context is already included too... */
-	for (i = con->includes; i; i = i->next) {
-		if (!strcasecmp(i->name, new_include->name)) {
-			ast_destroy_timing(&(new_include->timing));
-			ast_free(new_include);
-			ast_unlock_context(con);
-			errno = EEXIST;
-			return -1;
-		}
-		il = i;
-	}
-
-	/* ... include new context into context list, unlock, return */
-	if (il)
-		il->next = new_include;
-	else
-		con->includes = new_include;
-	ast_verb(3, "Including context '%s' in context '%s'\n", new_include->name, ast_get_context_name(con));
-
-	ast_unlock_context(con);
-
-	return 0;
-}
-
-/*
- * errno values
- *  EBUSY  - can't lock
- *  ENOENT - no existence of context
- */
-int ast_context_add_switch(const char *context, const char *sw, const char *data, int eval, const char *registrar)
-{
-	int ret = -1;
-	struct ast_context *c;
-
-	c = find_context_locked(context);
-	if (c) { /* found, add switch to this context */
-		ret = ast_context_add_switch2(c, sw, data, eval, registrar);
-		ast_unlock_contexts();
-	}
-	return ret;
-}
-
-/*
- * errno values
- *  ENOMEM - out of memory
- *  EBUSY  - can't lock
- *  EEXIST - already included
- *  EINVAL - there is no existence of context for inclusion
- */
-int ast_context_add_switch2(struct ast_context *con, const char *value,
-	const char *data, int eval, const char *registrar)
-{
-	struct ast_sw *new_sw;
-	struct ast_sw *i;
-	int length;
-	char *p;
-
-	length = sizeof(struct ast_sw);
-	length += strlen(value) + 1;
-	if (data)
-		length += strlen(data);
-	length++;
-
-	/* allocate new sw structure ... */
-	if (!(new_sw = ast_calloc(1, length)))
-		return -1;
-	/* ... fill in this structure ... */
-	p = new_sw->stuff;
-	new_sw->name = p;
-	strcpy(new_sw->name, value);
-	p += strlen(value) + 1;
-	new_sw->data = p;
-	if (data) {
-		strcpy(new_sw->data, data);
-		p += strlen(data) + 1;
-	} else {
-		strcpy(new_sw->data, "");
-		p++;
-	}
-	new_sw->eval	  = eval;
-	new_sw->registrar = registrar;
-
-	/* ... try to lock this context ... */
-	ast_wrlock_context(con);
-
-	/* ... go to last sw and check if context is already swd too... */
-	AST_LIST_TRAVERSE(&con->alts, i, list) {
-		if (!strcasecmp(i->name, new_sw->name) && !strcasecmp(i->data, new_sw->data)) {
-			ast_free(new_sw);
-			ast_unlock_context(con);
-			errno = EEXIST;
-			return -1;
-		}
-	}
-
-	/* ... sw new context into context list, unlock, return */
-	AST_LIST_INSERT_TAIL(&con->alts, new_sw, list);
-
-	ast_verb(3, "Including switch '%s/%s' in context '%s'\n", new_sw->name, new_sw->data, ast_get_context_name(con));
-
-	ast_unlock_context(con);
-
-	return 0;
-}
-
-/*
- * EBUSY  - can't lock
- * ENOENT - there is not context existence
- */
-int ast_context_remove_ignorepat(const char *context, const char *ignorepat, const char *registrar)
-{
-	int ret = -1;
-	struct ast_context *c;
-
-	c = find_context_locked(context);
-	if (c) {
-		ret = ast_context_remove_ignorepat2(c, ignorepat, registrar);
-		ast_unlock_contexts();
-	}
-	return ret;
-}
-
-int ast_context_remove_ignorepat2(struct ast_context *con, const char *ignorepat, const char *registrar)
-{
-	struct ast_ignorepat *ip, *ipl = NULL;
-
-	ast_wrlock_context(con);
-
-	for (ip = con->ignorepats; ip; ip = ip->next) {
-		if (!strcmp(ip->pattern, ignorepat) &&
-			(!registrar || (registrar == ip->registrar))) {
-			if (ipl) {
-				ipl->next = ip->next;
-				ast_free(ip);
-			} else {
-				con->ignorepats = ip->next;
-				ast_free(ip);
-			}
-			ast_unlock_context(con);
-			return 0;
-		}
-		ipl = ip;
-	}
-
-	ast_unlock_context(con);
-	errno = EINVAL;
-	return -1;
-}
-
-/*
- * EBUSY - can't lock
- * ENOENT - there is no existence of context
- */
-int ast_context_add_ignorepat(const char *context, const char *value, const char *registrar)
-{
-	int ret = -1;
-	struct ast_context *c;
-
-	c = find_context_locked(context);
-	if (c) {
-		ret = ast_context_add_ignorepat2(c, value, registrar);
-		ast_unlock_contexts();
-	}
-	return ret;
-}
-
-int ast_context_add_ignorepat2(struct ast_context *con, const char *value, const char *registrar)
-{
-	struct ast_ignorepat *ignorepat, *ignorepatc, *ignorepatl = NULL;
-	int length;
-	char *pattern;
-	length = sizeof(struct ast_ignorepat);
-	length += strlen(value) + 1;
-	if (!(ignorepat = ast_calloc(1, length)))
-		return -1;
-	/* The cast to char * is because we need to write the initial value.
-	 * The field is not supposed to be modified otherwise.  Also, gcc 4.2
-	 * sees the cast as dereferencing a type-punned pointer and warns about
-	 * it.  This is the workaround (we're telling gcc, yes, that's really
-	 * what we wanted to do).
-	 */
-	pattern = (char *) ignorepat->pattern;
-	strcpy(pattern, value);
-	ignorepat->next = NULL;
-	ignorepat->registrar = registrar;
-	ast_wrlock_context(con);
-	for (ignorepatc = con->ignorepats; ignorepatc; ignorepatc = ignorepatc->next) {
-		ignorepatl = ignorepatc;
-		if (!strcasecmp(ignorepatc->pattern, value)) {
-			/* Already there */
-			ast_unlock_context(con);
-			ast_free(ignorepat);
-			errno = EEXIST;
-			return -1;
-		}
-	}
-	if (ignorepatl)
-		ignorepatl->next = ignorepat;
-	else
-		con->ignorepats = ignorepat;
-	ast_unlock_context(con);
-	return 0;
-
-}
-
-int ast_ignore_pattern(const char *context, const char *pattern)
-{
-	int ret = 0;
-	struct ast_context *con;
-
-	ast_rdlock_contexts();
-	con = ast_context_find(context);
-	if (con) {
-		struct ast_ignorepat *pat;
-
-		for (pat = con->ignorepats; pat; pat = pat->next) {
-			if (ast_extension_match(pat->pattern, pattern)) {
-				ret = 1;
-				break;
-			}
-		}
-	}
-	ast_unlock_contexts();
-
-	return ret;
-}
-
-/*
- * ast_add_extension_nolock -- use only in situations where the conlock is already held
- * ENOENT  - no existence of context
- *
- */
-static int ast_add_extension_nolock(const char *context, int replace, const char *extension,
-	int priority, const char *label, const char *callerid,
-	const char *application, void *data, void (*datad)(void *), const char *registrar)
-{
-	int ret = -1;
-	struct ast_context *c;
-
-	c = find_context(context);
-	if (c) {
-		ret = ast_add_extension2_lockopt(c, replace, extension, priority, label, callerid,
-			application, data, datad, registrar, 1);
-	}
-
-	return ret;
-}
-/*
- * EBUSY   - can't lock
- * ENOENT  - no existence of context
- *
- */
-int ast_add_extension(const char *context, int replace, const char *extension,
-	int priority, const char *label, const char *callerid,
-	const char *application, void *data, void (*datad)(void *), const char *registrar)
-{
-	int ret = -1;
-	struct ast_context *c;
-
-	c = find_context_locked(context);
-	if (c) {
-		ret = ast_add_extension2(c, replace, extension, priority, label, callerid,
-			application, data, datad, registrar);
-		ast_unlock_contexts();
-	}
-
-	return ret;
-}
-
-int ast_explicit_goto(struct ast_channel *chan, const char *context, const char *exten, int priority)
-{
-	if (!chan)
-		return -1;
-
-	ast_channel_lock(chan);
-
-	if (!ast_strlen_zero(context))
-		ast_channel_context_set(chan, context);
-	if (!ast_strlen_zero(exten))
-		ast_channel_exten_set(chan, exten);
-	if (priority > -1) {
-		/* see flag description in channel.h for explanation */
-		if (ast_test_flag(ast_channel_flags(chan), AST_FLAG_IN_AUTOLOOP)) {
-			--priority;
-		}
-		ast_channel_priority_set(chan, priority);
-	}
-
-	ast_channel_unlock(chan);
-
-	return 0;
-}
-
-int ast_async_goto(struct ast_channel *chan, const char *context, const char *exten, int priority)
-{
-	struct ast_channel *newchan;
-
-	ast_channel_lock(chan);
-	/* Channels in a bridge or running a PBX can be sent directly to the specified destination */
-	if (ast_channel_is_bridged(chan) || ast_channel_pbx(chan)) {
-		if (ast_test_flag(ast_channel_flags(chan), AST_FLAG_IN_AUTOLOOP)) {
-			priority += 1;
-		}
-		ast_explicit_goto(chan, context, exten, priority);
-		ast_softhangup_nolock(chan, AST_SOFTHANGUP_ASYNCGOTO);
-		ast_channel_unlock(chan);
-		return 0;
-	}
-	ast_channel_unlock(chan);
-
-	/* Otherwise, we need to gain control of the channel first */
-	newchan = ast_channel_yank(chan);
-	if (!newchan) {
-		ast_log(LOG_WARNING, "Unable to gain control of channel %s\n", ast_channel_name(chan));
-		return -1;
-	}
-	ast_explicit_goto(newchan, context, exten, priority);
-	if (ast_pbx_start(newchan)) {
-		ast_hangup(newchan);
-		ast_log(LOG_WARNING, "Unable to start PBX on %s\n", ast_channel_name(newchan));
-		return -1;
-	}
-
-	return 0;
-}
-
-int ast_async_goto_by_name(const char *channame, const char *context, const char *exten, int priority)
-{
-	struct ast_channel *chan;
-	int res = -1;
-
-	if ((chan = ast_channel_get_by_name(channame))) {
-		res = ast_async_goto(chan, context, exten, priority);
-		chan = ast_channel_unref(chan);
-	}
-
-	return res;
-}
-
-/*! \brief copy a string skipping whitespace */
-static int ext_strncpy(char *dst, const char *src, int len)
-{
-	int count = 0;
-	int insquares = 0;
-
-	while (*src && (count < len - 1)) {
-		if (*src == '[') {
-			insquares = 1;
-		} else if (*src == ']') {
-			insquares = 0;
-		} else if (*src == ' ' && !insquares) {
-			src++;
-			continue;
-		}
-		*dst = *src;
-		dst++;
-		src++;
-		count++;
-	}
-	*dst = '\0';
-
-	return count;
-}
-
-/*!
- * \brief add the extension in the priority chain.
- * \retval 0 on success.
- * \retval -1 on failure.
-*/
-static int add_priority(struct ast_context *con, struct ast_exten *tmp,
-	struct ast_exten *el, struct ast_exten *e, int replace)
-{
-	struct ast_exten *ep;
-	struct ast_exten *eh=e;
-	int repeated_label = 0; /* Track if this label is a repeat, assume no. */
-
-	for (ep = NULL; e ; ep = e, e = e->peer) {
-		if (e->label && tmp->label && e->priority != tmp->priority && !strcmp(e->label, tmp->label)) {
-			if (strcmp(e->exten, tmp->exten)) {
-				ast_log(LOG_WARNING,
-					"Extension '%s' priority %d in '%s', label '%s' already in use at aliased extension '%s' priority %d\n",
-					tmp->exten, tmp->priority, con->name, tmp->label, e->exten, e->priority);
-			} else {
-				ast_log(LOG_WARNING,
-					"Extension '%s' priority %d in '%s', label '%s' already in use at priority %d\n",
-					tmp->exten, tmp->priority, con->name, tmp->label, e->priority);
-			}
-			repeated_label = 1;
-		}
-		if (e->priority >= tmp->priority) {
-			break;
-		}
-	}
-
-	if (repeated_label) {	/* Discard the label since it's a repeat. */
-		tmp->label = NULL;
-	}
-
-	if (!e) {	/* go at the end, and ep is surely set because the list is not empty */
-		ast_hashtab_insert_safe(eh->peer_table, tmp);
-
-		if (tmp->label) {
-			ast_hashtab_insert_safe(eh->peer_label_table, tmp);
-		}
-		ep->peer = tmp;
-		return 0;	/* success */
-	}
-	if (e->priority == tmp->priority) {
-		/* Can't have something exactly the same.  Is this a
-		   replacement?  If so, replace, otherwise, bonk. */
-		if (!replace) {
-			if (strcmp(e->exten, tmp->exten)) {
-				ast_log(LOG_WARNING,
-					"Unable to register extension '%s' priority %d in '%s', already in use by aliased extension '%s'\n",
-					tmp->exten, tmp->priority, con->name, e->exten);
-			} else {
-				ast_log(LOG_WARNING,
-					"Unable to register extension '%s' priority %d in '%s', already in use\n",
-					tmp->exten, tmp->priority, con->name);
-			}
-
-			return -1;
-		}
-		/* we are replacing e, so copy the link fields and then update
-		 * whoever pointed to e to point to us
-		 */
-		tmp->next = e->next;	/* not meaningful if we are not first in the peer list */
-		tmp->peer = e->peer;	/* always meaningful */
-		if (ep)	{		/* We're in the peer list, just insert ourselves */
-			ast_hashtab_remove_object_via_lookup(eh->peer_table,e);
-
-			if (e->label) {
-				ast_hashtab_remove_object_via_lookup(eh->peer_label_table,e);
-			}
-
-			ast_hashtab_insert_safe(eh->peer_table,tmp);
-			if (tmp->label) {
-				ast_hashtab_insert_safe(eh->peer_label_table,tmp);
-			}
-
-			ep->peer = tmp;
-		} else if (el) {		/* We're the first extension. Take over e's functions */
-			struct match_char *x = add_exten_to_pattern_tree(con, e, 1);
-			tmp->peer_table = e->peer_table;
-			tmp->peer_label_table = e->peer_label_table;
-			ast_hashtab_remove_object_via_lookup(tmp->peer_table,e);
-			ast_hashtab_insert_safe(tmp->peer_table,tmp);
-			if (e->label) {
-				ast_hashtab_remove_object_via_lookup(tmp->peer_label_table, e);
-			}
-			if (tmp->label) {
-				ast_hashtab_insert_safe(tmp->peer_label_table, tmp);
-			}
-
-			ast_hashtab_remove_object_via_lookup(con->root_table, e);
-			ast_hashtab_insert_safe(con->root_table, tmp);
-			el->next = tmp;
-			/* The pattern trie points to this exten; replace the pointer,
-			   and all will be well */
-			if (x) { /* if the trie isn't formed yet, don't sweat this */
-				if (x->exten) { /* this test for safety purposes */
-					x->exten = tmp; /* replace what would become a bad pointer */
-				} else {
-					ast_log(LOG_ERROR,"Trying to delete an exten from a context, but the pattern tree node returned isn't an extension\n");
-				}
-			}
-		} else {			/* We're the very first extension.  */
-			struct match_char *x = add_exten_to_pattern_tree(con, e, 1);
-			ast_hashtab_remove_object_via_lookup(con->root_table, e);
-			ast_hashtab_insert_safe(con->root_table, tmp);
-			tmp->peer_table = e->peer_table;
-			tmp->peer_label_table = e->peer_label_table;
-			ast_hashtab_remove_object_via_lookup(tmp->peer_table, e);
-			ast_hashtab_insert_safe(tmp->peer_table, tmp);
-			if (e->label) {
-				ast_hashtab_remove_object_via_lookup(tmp->peer_label_table, e);
-			}
-			if (tmp->label) {
-				ast_hashtab_insert_safe(tmp->peer_label_table, tmp);
-			}
-
-			ast_hashtab_remove_object_via_lookup(con->root_table, e);
-			ast_hashtab_insert_safe(con->root_table, tmp);
-			con->root = tmp;
-			/* The pattern trie points to this exten; replace the pointer,
-			   and all will be well */
-			if (x) { /* if the trie isn't formed yet; no problem */
-				if (x->exten) { /* this test for safety purposes */
-					x->exten = tmp; /* replace what would become a bad pointer */
-				} else {
-					ast_log(LOG_ERROR,"Trying to delete an exten from a context, but the pattern tree node returned isn't an extension\n");
-				}
-			}
-		}
-		if (tmp->priority == PRIORITY_HINT)
-			ast_change_hint(e,tmp);
-		/* Destroy the old one */
-		if (e->datad)
-			e->datad(e->data);
-		ast_free(e);
-	} else {	/* Slip ourselves in just before e */
-		tmp->peer = e;
-		tmp->next = e->next;	/* extension chain, or NULL if e is not the first extension */
-		if (ep) {			/* Easy enough, we're just in the peer list */
-			if (tmp->label) {
-				ast_hashtab_insert_safe(eh->peer_label_table, tmp);
-			}
-			ast_hashtab_insert_safe(eh->peer_table, tmp);
-			ep->peer = tmp;
-		} else {			/* we are the first in some peer list, so link in the ext list */
-			tmp->peer_table = e->peer_table;
-			tmp->peer_label_table = e->peer_label_table;
-			e->peer_table = 0;
-			e->peer_label_table = 0;
-			ast_hashtab_insert_safe(tmp->peer_table, tmp);
-			if (tmp->label) {
-				ast_hashtab_insert_safe(tmp->peer_label_table, tmp);
-			}
-			ast_hashtab_remove_object_via_lookup(con->root_table, e);
-			ast_hashtab_insert_safe(con->root_table, tmp);
-			if (el)
-				el->next = tmp;	/* in the middle... */
-			else
-				con->root = tmp; /* ... or at the head */
-			e->next = NULL;	/* e is no more at the head, so e->next must be reset */
-		}
-		/* And immediately return success. */
-		if (tmp->priority == PRIORITY_HINT) {
-			ast_add_hint(tmp);
-		}
-	}
-	return 0;
-}
-
-/*! \brief
- * Main interface to add extensions to the list for out context.
- *
- * We sort extensions in order of matching preference, so that we can
- * stop the search as soon as we find a suitable match.
- * This ordering also takes care of wildcards such as '.' (meaning
- * "one or more of any character") and '!' (which is 'earlymatch',
- * meaning "zero or more of any character" but also impacts the
- * return value from CANMATCH and EARLYMATCH.
- *
- * The extension match rules defined in the devmeeting 2006.05.05 are
- * quite simple: WE SELECT THE LONGEST MATCH.
- * In detail, "longest" means the number of matched characters in
- * the extension. In case of ties (e.g. _XXX and 333) in the length
- * of a pattern, we give priority to entries with the smallest cardinality
- * (e.g, [5-9] comes before [2-8] before the former has only 5 elements,
- * while the latter has 7, etc.
- * In case of same cardinality, the first element in the range counts.
- * If we still have a tie, any final '!' will make this as a possibly
- * less specific pattern.
- *
- * EBUSY - can't lock
- * EEXIST - extension with the same priority exist and no replace is set
- *
- */
-int ast_add_extension2(struct ast_context *con,
-	int replace, const char *extension, int priority, const char *label, const char *callerid,
-	const char *application, void *data, void (*datad)(void *),
-	const char *registrar)
-{
-	return ast_add_extension2_lockopt(con, replace, extension, priority, label, callerid,
-		application, data, datad, registrar, 1);
-}
-
-int ast_add_extension2_nolock(struct ast_context *con,
-	int replace, const char *extension, int priority, const char *label, const char *callerid,
-	const char *application, void *data, void (*datad)(void *),
-	const char *registrar)
-{
-	return ast_add_extension2_lockopt(con, replace, extension, priority, label, callerid,
-		application, data, datad, registrar, 0);
-}
-
-
-/*!
- * \brief Same as ast_add_extension2() but controls the context locking.
- *
- * \details
- * Does all the work of ast_add_extension2, but adds an arg to
- * determine if context locking should be done.
- */
-static int ast_add_extension2_lockopt(struct ast_context *con,
-	int replace, const char *extension, int priority, const char *label, const char *callerid,
-	const char *application, void *data, void (*datad)(void *),
-	const char *registrar, int lock_context)
-{
-	/*
-	 * Sort extensions (or patterns) according to the rules indicated above.
-	 * These are implemented by the function ext_cmp()).
-	 * All priorities for the same ext/pattern/cid are kept in a list,
-	 * using the 'peer' field  as a link field..
-	 */
-	struct ast_exten *tmp, *tmp2, *e, *el = NULL;
-	int res;
-	int length;
-	char *p;
-	char expand_buf[VAR_BUF_SIZE];
-	struct ast_exten dummy_exten = {0};
-	char dummy_name[1024];
-
-	if (ast_strlen_zero(extension)) {
-		ast_log(LOG_ERROR,"You have to be kidding-- add exten '' to context %s? Figure out a name and call me back. Action ignored.\n",
-				con->name);
-		return -1;
-	}
-
-	/* If we are adding a hint evalulate in variables and global variables */
-	if (priority == PRIORITY_HINT && strstr(application, "${") && extension[0] != '_') {
-		struct ast_channel *c = ast_dummy_channel_alloc();
-
-		if (c) {
-			ast_channel_exten_set(c, extension);
-			ast_channel_context_set(c, con->name);
-		}
-		pbx_substitute_variables_helper(c, application, expand_buf, sizeof(expand_buf));
-		application = expand_buf;
-		if (c) {
-			ast_channel_unref(c);
-		}
-	}
-
-	length = sizeof(struct ast_exten);
-	length += strlen(extension) + 1;
-	length += strlen(application) + 1;
-	if (label)
-		length += strlen(label) + 1;
-	if (callerid)
-		length += strlen(callerid) + 1;
-	else
-		length ++;	/* just the '\0' */
-
-	/* Be optimistic:  Build the extension structure first */
-	if (!(tmp = ast_calloc(1, length)))
-		return -1;
-
-	if (ast_strlen_zero(label)) /* let's turn empty labels to a null ptr */
-		label = 0;
-
-	/* use p as dst in assignments, as the fields are const char * */
-	p = tmp->stuff;
-	if (label) {
-		tmp->label = p;
-		strcpy(p, label);
-		p += strlen(label) + 1;
-	}
-	tmp->exten = p;
-	p += ext_strncpy(p, extension, strlen(extension) + 1) + 1;
-	tmp->priority = priority;
-	tmp->cidmatch = p;	/* but use p for assignments below */
-
-	/* Blank callerid and NULL callerid are two SEPARATE things.  Do NOT confuse the two!!! */
-	if (callerid) {
-		p += ext_strncpy(p, callerid, strlen(callerid) + 1) + 1;
-		tmp->matchcid = AST_EXT_MATCHCID_ON;
-	} else {
-		*p++ = '\0';
-		tmp->matchcid = AST_EXT_MATCHCID_OFF;
-	}
-	tmp->app = p;
-	strcpy(p, application);
-	tmp->parent = con;
-	tmp->data = data;
-	tmp->datad = datad;
-	tmp->registrar = registrar;
-
-	if (lock_context) {
-		ast_wrlock_context(con);
-	}
-
-	if (con->pattern_tree) { /* usually, on initial load, the pattern_tree isn't formed until the first find_exten; so if we are adding
-								an extension, and the trie exists, then we need to incrementally add this pattern to it. */
-		ast_copy_string(dummy_name, extension, sizeof(dummy_name));
-		dummy_exten.exten = dummy_name;
-		dummy_exten.matchcid = AST_EXT_MATCHCID_OFF;
-		dummy_exten.cidmatch = 0;
-		tmp2 = ast_hashtab_lookup(con->root_table, &dummy_exten);
-		if (!tmp2) {
-			/* hmmm, not in the trie; */
-			add_exten_to_pattern_tree(con, tmp, 0);
-			ast_hashtab_insert_safe(con->root_table, tmp); /* for the sake of completeness */
-		}
-	}
-	res = 0; /* some compilers will think it is uninitialized otherwise */
-	for (e = con->root; e; el = e, e = e->next) {   /* scan the extension list */
-		res = ext_cmp(e->exten, tmp->exten);
-		if (res == 0) { /* extension match, now look at cidmatch */
-			if (e->matchcid == AST_EXT_MATCHCID_OFF && tmp->matchcid == AST_EXT_MATCHCID_OFF)
-				res = 0;
-			else if (tmp->matchcid == AST_EXT_MATCHCID_ON && e->matchcid == AST_EXT_MATCHCID_OFF)
-				res = 1;
-			else if (e->matchcid == AST_EXT_MATCHCID_ON && tmp->matchcid == AST_EXT_MATCHCID_OFF)
-				res = -1;
-			else
-				res = ext_cmp(e->cidmatch, tmp->cidmatch);
-		}
-		if (res >= 0)
-			break;
-	}
-	if (e && res == 0) { /* exact match, insert in the priority chain */
-		res = add_priority(con, tmp, el, e, replace);
-		if (res < 0) {
-			if (con->pattern_tree) {
-				struct match_char *x = add_exten_to_pattern_tree(con, tmp, 1);
-
-				if (x->exten) {
-					x->deleted = 1;
-					x->exten = 0;
-				}
-
-				ast_hashtab_remove_this_object(con->root_table, tmp);
-			}
-
-			if (tmp->datad) {
-				tmp->datad(tmp->data);
-				/* if you free this, null it out */
-				tmp->data = NULL;
-			}
-
-			ast_free(tmp);
-		}
-		if (lock_context) {
-			ast_unlock_context(con);
-		}
-		if (res < 0) {
-			errno = EEXIST;
-			return -1;
-		}
-	} else {
-		/*
-		 * not an exact match, this is the first entry with this pattern,
-		 * so insert in the main list right before 'e' (if any)
-		 */
-		tmp->next = e;
-		if (el) {  /* there is another exten already in this context */
-			el->next = tmp;
-			tmp->peer_table = ast_hashtab_create(13,
-							hashtab_compare_exten_numbers,
-							ast_hashtab_resize_java,
-							ast_hashtab_newsize_java,
-							hashtab_hash_priority,
-							0);
-			tmp->peer_label_table = ast_hashtab_create(7,
-								hashtab_compare_exten_labels,
-								ast_hashtab_resize_java,
-								ast_hashtab_newsize_java,
-								hashtab_hash_labels,
-								0);
-			if (label) {
-				ast_hashtab_insert_safe(tmp->peer_label_table, tmp);
-			}
-			ast_hashtab_insert_safe(tmp->peer_table, tmp);
-		} else {  /* this is the first exten in this context */
-			if (!con->root_table)
-				con->root_table = ast_hashtab_create(27,
-													hashtab_compare_extens,
-													ast_hashtab_resize_java,
-													ast_hashtab_newsize_java,
-													hashtab_hash_extens,
-													0);
-			con->root = tmp;
-			con->root->peer_table = ast_hashtab_create(13,
-								hashtab_compare_exten_numbers,
-								ast_hashtab_resize_java,
-								ast_hashtab_newsize_java,
-								hashtab_hash_priority,
-								0);
-			con->root->peer_label_table = ast_hashtab_create(7,
-									hashtab_compare_exten_labels,
-									ast_hashtab_resize_java,
-									ast_hashtab_newsize_java,
-									hashtab_hash_labels,
-									0);
-			if (label) {
-				ast_hashtab_insert_safe(con->root->peer_label_table, tmp);
-			}
-			ast_hashtab_insert_safe(con->root->peer_table, tmp);
-
-		}
-		ast_hashtab_insert_safe(con->root_table, tmp);
-		if (lock_context) {
-			ast_unlock_context(con);
-		}
-		if (tmp->priority == PRIORITY_HINT) {
-			ast_add_hint(tmp);
-		}
-	}
-	if (option_debug) {
-		if (tmp->matchcid == AST_EXT_MATCHCID_ON) {
-			ast_debug(1, "Added extension '%s' priority %d (CID match '%s') to %s (%p)\n",
-					  tmp->exten, tmp->priority, tmp->cidmatch, con->name, con);
-		} else {
-			ast_debug(1, "Added extension '%s' priority %d to %s (%p)\n",
-					  tmp->exten, tmp->priority, con->name, con);
-		}
-	}
-
-	if (tmp->matchcid == AST_EXT_MATCHCID_ON) {
-		ast_verb(3, "Added extension '%s' priority %d (CID match '%s') to %s\n",
-				 tmp->exten, tmp->priority, tmp->cidmatch, con->name);
-	} else {
-		ast_verb(3, "Added extension '%s' priority %d to %s\n",
-				 tmp->exten, tmp->priority, con->name);
-	}
-
-	return 0;
-}
-
-/*! \brief Structure which contains information about an outgoing dial */
-struct pbx_outgoing {
-	/*! \brief Dialing structure being used */
-	struct ast_dial *dial;
-	/*! \brief Condition for synchronous dialing */
-	ast_cond_t cond;
-	/*! \brief Application to execute */
-	char app[AST_MAX_APP];
-	/*! \brief Application data to pass to application */
-	char *appdata;
-	/*! \brief Dialplan context */
-	char context[AST_MAX_CONTEXT];
-	/*! \brief Dialplan extension */
-	char exten[AST_MAX_EXTENSION];
-	/*! \brief Dialplan priority */
-	int priority;
-	/*! \brief Result of the dial operation when dialed is set */
-	int dial_res;
-	/*! \brief Set when dialing is completed */
-	unsigned int dialed:1;
-	/*! \brief Set when execution is completed */
-	unsigned int executed:1;
-};
-
-/*! \brief Destructor for outgoing structure */
-static void pbx_outgoing_destroy(void *obj)
-{
-	struct pbx_outgoing *outgoing = obj;
-
-	if (outgoing->dial) {
-		ast_dial_destroy(outgoing->dial);
-	}
-
-	ast_cond_destroy(&outgoing->cond);
-
-	ast_free(outgoing->appdata);
-}
-
-/*! \brief Internal function which dials an outgoing leg and sends it to a provided extension or application */
-static void *pbx_outgoing_exec(void *data)
-{
-	RAII_VAR(struct pbx_outgoing *, outgoing, data, ao2_cleanup);
-	enum ast_dial_result res;
-
-	/* Notify anyone interested that dialing is complete */
-	res = ast_dial_run(outgoing->dial, NULL, 0);
-	ao2_lock(outgoing);
-	outgoing->dial_res = res;
-	outgoing->dialed = 1;
-	ast_cond_signal(&outgoing->cond);
-	ao2_unlock(outgoing);
-
-	/* If the outgoing leg was not answered we can immediately return and go no further */
-	if (res != AST_DIAL_RESULT_ANSWERED) {
-		return NULL;
-	}
-
-	if (!ast_strlen_zero(outgoing->app)) {
-		struct ast_app *app = pbx_findapp(outgoing->app);
-
-		if (app) {
-			ast_verb(4, "Launching %s(%s) on %s\n", outgoing->app, S_OR(outgoing->appdata, ""),
-				ast_channel_name(ast_dial_answered(outgoing->dial)));
-			pbx_exec(ast_dial_answered(outgoing->dial), app, outgoing->appdata);
-		} else {
-			ast_log(LOG_WARNING, "No such application '%s'\n", outgoing->app);
-		}
-	} else {
-		struct ast_channel *answered = ast_dial_answered(outgoing->dial);
-
-		if (!ast_strlen_zero(outgoing->context)) {
-			ast_channel_context_set(answered, outgoing->context);
-		}
-
-		if (!ast_strlen_zero(outgoing->exten)) {
-			ast_channel_exten_set(answered, outgoing->exten);
-		}
-
-		if (outgoing->priority > 0) {
-			ast_channel_priority_set(answered, outgoing->priority);
-		}
-
-		if (ast_pbx_run(answered)) {
-			ast_log(LOG_ERROR, "Failed to start PBX on %s\n", ast_channel_name(answered));
-		} else {
-			/* PBX will have taken care of hanging up, so we steal the answered channel so dial doesn't do it */
-			ast_dial_answered_steal(outgoing->dial);
-		}
-	}
-
-	/* Notify anyone else again that may be interested that execution is complete */
-	ao2_lock(outgoing);
-	outgoing->executed = 1;
-	ast_cond_signal(&outgoing->cond);
-	ao2_unlock(outgoing);
-
-	return NULL;
-}
-
-/*! \brief Internal dialing state callback which causes early media to trigger an answer */
-static void pbx_outgoing_state_callback(struct ast_dial *dial)
-{
-	struct ast_channel *channel;
-
-	if (ast_dial_state(dial) != AST_DIAL_RESULT_PROGRESS) {
-		return;
-	}
-
-	if (!(channel = ast_dial_get_channel(dial, 0))) {
-		return;
-	}
-
-	ast_verb(4, "Treating progress as answer on '%s' due to early media option\n",
-		ast_channel_name(channel));
-
-	ast_queue_control(channel, AST_CONTROL_ANSWER);
-}
-
-/*!
- * \brief Attempt to convert disconnect cause to old originate reason.
- *
- * \todo XXX The old originate reasons need to be trashed and replaced
- * with normal disconnect cause codes if the call was not answered.
- * The internal consumers of the reason values would also need to be
- * updated: app_originate, call files, and AMI OriginateResponse.
- */
-static enum ast_control_frame_type pbx_dial_reason(enum ast_dial_result dial_result, int cause)
-{
-	enum ast_control_frame_type pbx_reason;
-
-	if (dial_result == AST_DIAL_RESULT_ANSWERED) {
-		/* Remote end answered. */
-		pbx_reason = AST_CONTROL_ANSWER;
-	} else if (dial_result == AST_DIAL_RESULT_HANGUP) {
-		/* Caller hungup */
-		pbx_reason = AST_CONTROL_HANGUP;
-	} else {
-		switch (cause) {
-		case AST_CAUSE_USER_BUSY:
-			pbx_reason = AST_CONTROL_BUSY;
-			break;
-		case AST_CAUSE_CALL_REJECTED:
-		case AST_CAUSE_NETWORK_OUT_OF_ORDER:
-		case AST_CAUSE_DESTINATION_OUT_OF_ORDER:
-		case AST_CAUSE_NORMAL_TEMPORARY_FAILURE:
-		case AST_CAUSE_SWITCH_CONGESTION:
-		case AST_CAUSE_NORMAL_CIRCUIT_CONGESTION:
-			pbx_reason = AST_CONTROL_CONGESTION;
-			break;
-		case AST_CAUSE_ANSWERED_ELSEWHERE:
-		case AST_CAUSE_NO_ANSWER:
-			/* Remote end was ringing (but isn't anymore) */
-			pbx_reason = AST_CONTROL_RINGING;
-			break;
-		case AST_CAUSE_UNALLOCATED:
-		default:
-			/* Call Failure (not BUSY, and not NO_ANSWER, maybe Circuit busy or down?) */
-			pbx_reason = 0;
-			break;
-		}
-	}
-
-	return pbx_reason;
-}
-
-static int pbx_outgoing_attempt(const char *type, struct ast_format_cap *cap,
-	const char *addr, int timeout, const char *context, const char *exten, int priority,
-	const char *app, const char *appdata, int *reason, int synchronous,
-	const char *cid_num, const char *cid_name, struct ast_variable *vars,
-	const char *account, struct ast_channel **locked_channel, int early_media,
-	const struct ast_assigned_ids *assignedids)
-{
-	RAII_VAR(struct pbx_outgoing *, outgoing, NULL, ao2_cleanup);
-	struct ast_channel *dialed;
-	pthread_t thread;
-
-	outgoing = ao2_alloc(sizeof(*outgoing), pbx_outgoing_destroy);
-	if (!outgoing) {
-		return -1;
-	}
-	ast_cond_init(&outgoing->cond, NULL);
-
-	if (!ast_strlen_zero(app)) {
-		ast_copy_string(outgoing->app, app, sizeof(outgoing->app));
-		outgoing->appdata = ast_strdup(appdata);
-	} else {
-		ast_copy_string(outgoing->context, context, sizeof(outgoing->context));
-		ast_copy_string(outgoing->exten, exten, sizeof(outgoing->exten));
-		outgoing->priority = priority;
-	}
-
-	if (!(outgoing->dial = ast_dial_create())) {
-		return -1;
-	}
-
-	if (ast_dial_append(outgoing->dial, type, addr, assignedids)) {
-		return -1;
-	}
-
-	ast_dial_set_global_timeout(outgoing->dial, timeout);
-
-	if (ast_dial_prerun(outgoing->dial, NULL, cap)) {
-		if (synchronous && reason) {
-			*reason = pbx_dial_reason(AST_DIAL_RESULT_FAILED,
-				ast_dial_reason(outgoing->dial, 0));
-		}
-		return -1;
-	}
-
-	dialed = ast_dial_get_channel(outgoing->dial, 0);
-	if (!dialed) {
-		return -1;
-	}
-
-	ast_channel_lock(dialed);
-	if (vars) {
-		ast_set_variables(dialed, vars);
-	}
-	if (!ast_strlen_zero(account)) {
-		ast_channel_stage_snapshot(dialed);
-		ast_channel_accountcode_set(dialed, account);
-		ast_channel_peeraccount_set(dialed, account);
-		ast_channel_stage_snapshot_done(dialed);
-	}
-	ast_set_flag(ast_channel_flags(dialed), AST_FLAG_ORIGINATED);
-	ast_channel_unlock(dialed);
-
-	if (!ast_strlen_zero(cid_num) || !ast_strlen_zero(cid_name)) {
-		struct ast_party_connected_line connected;
-
-		/*
-		 * It seems strange to set the CallerID on an outgoing call leg
-		 * to whom we are calling, but this function's callers are doing
-		 * various Originate methods.  This call leg goes to the local
-		 * user.  Once the called party answers, the dialplan needs to
-		 * be able to access the CallerID from the CALLERID function as
-		 * if the called party had placed this call.
-		 */
-		ast_set_callerid(dialed, cid_num, cid_name, cid_num);
-
-		ast_party_connected_line_set_init(&connected, ast_channel_connected(dialed));
-		if (!ast_strlen_zero(cid_num)) {
-			connected.id.number.valid = 1;
-			connected.id.number.str = (char *) cid_num;
-			connected.id.number.presentation = AST_PRES_ALLOWED_USER_NUMBER_NOT_SCREENED;
-		}
-		if (!ast_strlen_zero(cid_name)) {
-			connected.id.name.valid = 1;
-			connected.id.name.str = (char *) cid_name;
-			connected.id.name.presentation = AST_PRES_ALLOWED_USER_NUMBER_NOT_SCREENED;
-		}
-		ast_channel_set_connected_line(dialed, &connected, NULL);
-	}
-
-	if (early_media) {
-		ast_dial_set_state_callback(outgoing->dial, pbx_outgoing_state_callback);
-	}
-
-	if (locked_channel) {
-		/*
-		 * Keep a dialed channel ref since the caller wants
-		 * the channel returned.  We must get the ref before
-		 * spawning off pbx_outgoing_exec().
-		 */
-		ast_channel_ref(dialed);
-		if (!synchronous) {
-			/*
-			 * Lock it now to hold off pbx_outgoing_exec() in case the
-			 * calling function needs the channel state/snapshot before
-			 * dialing actually happens.
-			 */
-			ast_channel_lock(dialed);
-		}
-	}
-
-	ao2_ref(outgoing, +1);
-	if (ast_pthread_create_detached(&thread, NULL, pbx_outgoing_exec, outgoing)) {
-		ast_log(LOG_WARNING, "Unable to spawn dialing thread for '%s/%s'\n", type, addr);
-		ao2_ref(outgoing, -1);
-		if (locked_channel) {
-			if (!synchronous) {
-				ast_channel_unlock(dialed);
-			}
-			ast_channel_unref(dialed);
-		}
-		return -1;
-	}
-
-	if (synchronous) {
-		ao2_lock(outgoing);
-		/* Wait for dialing to complete */
-		while (!outgoing->dialed) {
-			ast_cond_wait(&outgoing->cond, ao2_object_get_lockaddr(outgoing));
-		}
-		if (1 < synchronous
-			&& outgoing->dial_res == AST_DIAL_RESULT_ANSWERED) {
-			/* Wait for execution to complete */
-			while (!outgoing->executed) {
-				ast_cond_wait(&outgoing->cond, ao2_object_get_lockaddr(outgoing));
-			}
-		}
-		ao2_unlock(outgoing);
-
-		/* Determine the outcome of the dialing attempt up to it being answered. */
-		if (reason) {
-			*reason = pbx_dial_reason(outgoing->dial_res,
-				ast_dial_reason(outgoing->dial, 0));
-		}
-
-		if (outgoing->dial_res != AST_DIAL_RESULT_ANSWERED) {
-			/* The dial operation failed. */
-			if (locked_channel) {
-				ast_channel_unref(dialed);
-			}
-			return -1;
-		}
-		if (locked_channel) {
-			ast_channel_lock(dialed);
-		}
-	}
-
-	if (locked_channel) {
-		*locked_channel = dialed;
-	}
-	return 0;
-}
-
-int ast_pbx_outgoing_exten(const char *type, struct ast_format_cap *cap, const char *addr,
-	int timeout, const char *context, const char *exten, int priority, int *reason,
-	int synchronous, const char *cid_num, const char *cid_name, struct ast_variable *vars,
-	const char *account, struct ast_channel **locked_channel, int early_media,
-	const struct ast_assigned_ids *assignedids)
-{
-	int res;
-	int my_reason;
-
-	if (!reason) {
-		reason = &my_reason;
-	}
-	*reason = 0;
-	if (locked_channel) {
-		*locked_channel = NULL;
-	}
-
-	res = pbx_outgoing_attempt(type, cap, addr, timeout, context, exten, priority,
-		NULL, NULL, reason, synchronous, cid_num, cid_name, vars, account, locked_channel,
-		early_media, assignedids);
-
-	if (res < 0 /* Call failed to get connected for some reason. */
-		&& 1 < synchronous
-		&& ast_exists_extension(NULL, context, "failed", 1, NULL)) {
-		struct ast_channel *failed;
-
-		/* We do not have to worry about a locked_channel if dialing failed. */
-		ast_assert(!locked_channel || !*locked_channel);
-
-		/*!
-		 * \todo XXX Not good.  The channel name is not unique if more than
-		 * one originate fails at a time.
-		 */
-		failed = ast_channel_alloc(0, AST_STATE_DOWN, cid_num, cid_name, account,
-			"failed", context, NULL, NULL, 0, "OutgoingSpoolFailed");
-		if (failed) {
-			char failed_reason[12];
-
-			ast_set_variables(failed, vars);
-			snprintf(failed_reason, sizeof(failed_reason), "%d", *reason);
-			pbx_builtin_setvar_helper(failed, "REASON", failed_reason);
-			ast_channel_unlock(failed);
-
-			if (ast_pbx_run(failed)) {
-				ast_log(LOG_ERROR, "Unable to run PBX on '%s'\n",
-					ast_channel_name(failed));
-				ast_hangup(failed);
-			}
-		}
-	}
-
-	return res;
-}
-
-int ast_pbx_outgoing_app(const char *type, struct ast_format_cap *cap, const char *addr,
-	int timeout, const char *app, const char *appdata, int *reason, int synchronous,
-	const char *cid_num, const char *cid_name, struct ast_variable *vars,
-	const char *account, struct ast_channel **locked_channel,
-	const struct ast_assigned_ids *assignedids)
-{
-	if (reason) {
-		*reason = 0;
-	}
-	if (locked_channel) {
-		*locked_channel = NULL;
-	}
-	if (ast_strlen_zero(app)) {
-		return -1;
-	}
-
-	return pbx_outgoing_attempt(type, cap, addr, timeout, NULL, NULL, 0, app, appdata,
-		reason, synchronous, cid_num, cid_name, vars, account, locked_channel, 0,
-		assignedids);
-}
-
-/* this is the guts of destroying a context --
-   freeing up the structure, traversing and destroying the
-   extensions, switches, ignorepats, includes, etc. etc. */
-
-static void __ast_internal_context_destroy( struct ast_context *con)
-{
-	struct ast_include *tmpi;
-	struct ast_sw *sw;
-	struct ast_exten *e, *el, *en;
-	struct ast_ignorepat *ipi;
-	struct ast_context *tmp = con;
-
-	for (tmpi = tmp->includes; tmpi; ) { /* Free includes */
-		struct ast_include *tmpil = tmpi;
-		tmpi = tmpi->next;
-		ast_free(tmpil);
-	}
-	for (ipi = tmp->ignorepats; ipi; ) { /* Free ignorepats */
-		struct ast_ignorepat *ipl = ipi;
-		ipi = ipi->next;
-		ast_free(ipl);
-	}
-	if (tmp->registrar)
-		ast_free(tmp->registrar);
-
-	/* destroy the hash tabs */
-	if (tmp->root_table) {
-		ast_hashtab_destroy(tmp->root_table, 0);
-	}
-	/* and destroy the pattern tree */
-	if (tmp->pattern_tree)
-		destroy_pattern_tree(tmp->pattern_tree);
-
-	while ((sw = AST_LIST_REMOVE_HEAD(&tmp->alts, list)))
-		ast_free(sw);
-	for (e = tmp->root; e;) {
-		for (en = e->peer; en;) {
-			el = en;
-			en = en->peer;
-			destroy_exten(el);
+			return -1;
 		}
-		el = e;
-		e = e->next;
-		destroy_exten(el);
-	}
-	tmp->root = NULL;
-	ast_rwlock_destroy(&tmp->lock);
-	ast_mutex_destroy(&tmp->macrolock);
-	ast_free(tmp);
-}
-
-
-void __ast_context_destroy(struct ast_context *list, struct ast_hashtab *contexttab, struct ast_context *con, const char *registrar)
-{
-	struct ast_context *tmp, *tmpl=NULL;
-	struct ast_exten *exten_item, *prio_item;
+		/* we are replacing e, so copy the link fields and then update
+		 * whoever pointed to e to point to us
+		 */
+		tmp->next = e->next;	/* not meaningful if we are not first in the peer list */
+		tmp->peer = e->peer;	/* always meaningful */
+		if (ep)	{		/* We're in the peer list, just insert ourselves */
+			ast_hashtab_remove_object_via_lookup(eh->peer_table,e);
 
-	for (tmp = list; tmp; ) {
-		struct ast_context *next = NULL;	/* next starting point */
-			/* The following code used to skip forward to the next
-			   context with matching registrar, but this didn't
-			   make sense; individual priorities registrar'd to
-			   the matching registrar could occur in any context! */
-		ast_debug(1, "Investigate ctx %s %s\n", tmp->name, tmp->registrar);
-		if (con) {
-			for (; tmp; tmpl = tmp, tmp = tmp->next) { /* skip to the matching context */
-				ast_debug(1, "check ctx %s %s\n", tmp->name, tmp->registrar);
-				if ( !strcasecmp(tmp->name, con->name) ) {
-					break;	/* found it */
-				}
+			if (e->label) {
+				ast_hashtab_remove_object_via_lookup(eh->peer_label_table,e);
 			}
-		}
 
-		if (!tmp)	/* not found, we are done */
-			break;
-		ast_wrlock_context(tmp);
+			ast_hashtab_insert_safe(eh->peer_table,tmp);
+			if (tmp->label) {
+				ast_hashtab_insert_safe(eh->peer_label_table,tmp);
+			}
 
-		if (registrar) {
-			/* then search thru and remove any extens that match registrar. */
-			struct ast_hashtab_iter *exten_iter;
-			struct ast_hashtab_iter *prio_iter;
-			struct ast_ignorepat *ip, *ipl = NULL, *ipn = NULL;
-			struct ast_include *i, *pi = NULL, *ni = NULL;
-			struct ast_sw *sw = NULL;
+			ep->peer = tmp;
+		} else if (el) {		/* We're the first extension. Take over e's functions */
+			struct match_char *x = add_exten_to_pattern_tree(con, e, 1);
+			tmp->peer_table = e->peer_table;
+			tmp->peer_label_table = e->peer_label_table;
+			ast_hashtab_remove_object_via_lookup(tmp->peer_table,e);
+			ast_hashtab_insert_safe(tmp->peer_table,tmp);
+			if (e->label) {
+				ast_hashtab_remove_object_via_lookup(tmp->peer_label_table, e);
+			}
+			if (tmp->label) {
+				ast_hashtab_insert_safe(tmp->peer_label_table, tmp);
+			}
 
-			/* remove any ignorepats whose registrar matches */
-			for (ip = tmp->ignorepats; ip; ip = ipn) {
-				ipn = ip->next;
-				if (!strcmp(ip->registrar, registrar)) {
-					if (ipl) {
-						ipl->next = ip->next;
-						ast_free(ip);
-						continue; /* don't change ipl */
-					} else {
-						tmp->ignorepats = ip->next;
-						ast_free(ip);
-						continue; /* don't change ipl */
-					}
+			ast_hashtab_remove_object_via_lookup(con->root_table, e);
+			ast_hashtab_insert_safe(con->root_table, tmp);
+			el->next = tmp;
+			/* The pattern trie points to this exten; replace the pointer,
+			   and all will be well */
+			if (x) { /* if the trie isn't formed yet, don't sweat this */
+				if (x->exten) { /* this test for safety purposes */
+					x->exten = tmp; /* replace what would become a bad pointer */
+				} else {
+					ast_log(LOG_ERROR,"Trying to delete an exten from a context, but the pattern tree node returned isn't an extension\n");
 				}
-				ipl = ip;
 			}
-			/* remove any includes whose registrar matches */
-			for (i = tmp->includes; i; i = ni) {
-				ni = i->next;
-				if (strcmp(i->registrar, registrar) == 0) {
-					/* remove from list */
-					if (pi) {
-						pi->next = i->next;
-						/* free include */
-						ast_free(i);
-						continue; /* don't change pi */
-					} else {
-						tmp->includes = i->next;
-						/* free include */
-						ast_free(i);
-						continue; /* don't change pi */
-					}
-				}
-				pi = i;
+		} else {			/* We're the very first extension.  */
+			struct match_char *x = add_exten_to_pattern_tree(con, e, 1);
+			ast_hashtab_remove_object_via_lookup(con->root_table, e);
+			ast_hashtab_insert_safe(con->root_table, tmp);
+			tmp->peer_table = e->peer_table;
+			tmp->peer_label_table = e->peer_label_table;
+			ast_hashtab_remove_object_via_lookup(tmp->peer_table, e);
+			ast_hashtab_insert_safe(tmp->peer_table, tmp);
+			if (e->label) {
+				ast_hashtab_remove_object_via_lookup(tmp->peer_label_table, e);
 			}
-			/* remove any switches whose registrar matches */
-			AST_LIST_TRAVERSE_SAFE_BEGIN(&tmp->alts, sw, list) {
-				if (strcmp(sw->registrar,registrar) == 0) {
-					AST_LIST_REMOVE_CURRENT(list);
-					ast_free(sw);
-				}
+			if (tmp->label) {
+				ast_hashtab_insert_safe(tmp->peer_label_table, tmp);
 			}
-			AST_LIST_TRAVERSE_SAFE_END;
-
-			if (tmp->root_table) { /* it is entirely possible that the context is EMPTY */
-				exten_iter = ast_hashtab_start_traversal(tmp->root_table);
-				while ((exten_item=ast_hashtab_next(exten_iter))) {
-					int end_traversal = 1;
-
-					/*
-					 * If the extension could not be removed from the root_table due to
-					 * a loaded PBX app, it can exist here but have its peer_table be
-					 * destroyed due to a previous pass through this function.
-					 */
-					if (!exten_item->peer_table) {
-						continue;
-					}
 
-					prio_iter = ast_hashtab_start_traversal(exten_item->peer_table);
-					while ((prio_item=ast_hashtab_next(prio_iter))) {
-						char extension[AST_MAX_EXTENSION];
-						char cidmatch[AST_MAX_EXTENSION];
-						if (!prio_item->registrar || strcmp(prio_item->registrar, registrar) != 0) {
-							continue;
-						}
-						ast_verb(3, "Remove %s/%s/%d, registrar=%s; con=%s(%p); con->root=%p\n",
-								 tmp->name, prio_item->exten, prio_item->priority, registrar, con? con->name : "<nil>", con, con? con->root_table: NULL);
-						ast_copy_string(extension, prio_item->exten, sizeof(extension));
-						if (prio_item->cidmatch) {
-							ast_copy_string(cidmatch, prio_item->cidmatch, sizeof(cidmatch));
-						}
-						end_traversal &= ast_context_remove_extension_callerid2(tmp, extension, prio_item->priority, cidmatch, prio_item->matchcid, NULL, 1);
-					}
-					/* Explanation:
-					 * ast_context_remove_extension_callerid2 will destroy the extension that it comes across. This
-					 * destruction includes destroying the exten's peer_table, which we are currently traversing. If
-					 * ast_context_remove_extension_callerid2 ever should return '0' then this means we have destroyed
-					 * the hashtable which we are traversing, and thus calling ast_hashtab_end_traversal will result
-					 * in reading invalid memory. Thus, if we detect that we destroyed the hashtable, then we will simply
-					 * free the iterator
-					 */
-					if (end_traversal) {
-						ast_hashtab_end_traversal(prio_iter);
-					} else {
-						ast_free(prio_iter);
-					}
+			ast_hashtab_remove_object_via_lookup(con->root_table, e);
+			ast_hashtab_insert_safe(con->root_table, tmp);
+			con->root = tmp;
+			/* The pattern trie points to this exten; replace the pointer,
+			   and all will be well */
+			if (x) { /* if the trie isn't formed yet; no problem */
+				if (x->exten) { /* this test for safety purposes */
+					x->exten = tmp; /* replace what would become a bad pointer */
+				} else {
+					ast_log(LOG_ERROR,"Trying to delete an exten from a context, but the pattern tree node returned isn't an extension\n");
 				}
-				ast_hashtab_end_traversal(exten_iter);
 			}
-
-			/* delete the context if it's registrar matches, is empty, has refcount of 1, */
-			/* it's not empty, if it has includes, ignorepats, or switches that are registered from
-			   another registrar. It's not empty if there are any extensions */
-			if (strcmp(tmp->registrar, registrar) == 0 && tmp->refcount < 2 && !tmp->root && !tmp->ignorepats && !tmp->includes && AST_LIST_EMPTY(&tmp->alts)) {
-				ast_debug(1, "delete ctx %s %s\n", tmp->name, tmp->registrar);
-				ast_hashtab_remove_this_object(contexttab, tmp);
-
-				next = tmp->next;
-				if (tmpl)
-					tmpl->next = next;
-				else
-					contexts = next;
-				/* Okay, now we're safe to let it go -- in a sense, we were
-				   ready to let it go as soon as we locked it. */
-				ast_unlock_context(tmp);
-				__ast_internal_context_destroy(tmp);
-			} else {
-				ast_debug(1,"Couldn't delete ctx %s/%s; refc=%d; tmp.root=%p\n", tmp->name, tmp->registrar,
-						  tmp->refcount, tmp->root);
-				ast_unlock_context(tmp);
-				next = tmp->next;
-				tmpl = tmp;
+		}
+		if (tmp->priority == PRIORITY_HINT)
+			ast_change_hint(e,tmp);
+		/* Destroy the old one */
+		if (e->datad)
+			e->datad(e->data);
+		ast_free(e);
+	} else {	/* Slip ourselves in just before e */
+		tmp->peer = e;
+		tmp->next = e->next;	/* extension chain, or NULL if e is not the first extension */
+		if (ep) {			/* Easy enough, we're just in the peer list */
+			if (tmp->label) {
+				ast_hashtab_insert_safe(eh->peer_label_table, tmp);
+			}
+			ast_hashtab_insert_safe(eh->peer_table, tmp);
+			ep->peer = tmp;
+		} else {			/* we are the first in some peer list, so link in the ext list */
+			tmp->peer_table = e->peer_table;
+			tmp->peer_label_table = e->peer_label_table;
+			e->peer_table = 0;
+			e->peer_label_table = 0;
+			ast_hashtab_insert_safe(tmp->peer_table, tmp);
+			if (tmp->label) {
+				ast_hashtab_insert_safe(tmp->peer_label_table, tmp);
 			}
-		} else if (con) {
-			ast_verb(3, "Deleting context %s registrar=%s\n", tmp->name, tmp->registrar);
-			ast_debug(1, "delete ctx %s %s\n", tmp->name, tmp->registrar);
-			ast_hashtab_remove_this_object(contexttab, tmp);
-
-			next = tmp->next;
-			if (tmpl)
-				tmpl->next = next;
+			ast_hashtab_remove_object_via_lookup(con->root_table, e);
+			ast_hashtab_insert_safe(con->root_table, tmp);
+			if (el)
+				el->next = tmp;	/* in the middle... */
 			else
-				contexts = next;
-			/* Okay, now we're safe to let it go -- in a sense, we were
-			   ready to let it go as soon as we locked it. */
-			ast_unlock_context(tmp);
-			__ast_internal_context_destroy(tmp);
+				con->root = tmp; /* ... or at the head */
+			e->next = NULL;	/* e is no more at the head, so e->next must be reset */
+		}
+		/* And immediately return success. */
+		if (tmp->priority == PRIORITY_HINT) {
+			ast_add_hint(tmp);
 		}
-
-		/* if we have a specific match, we are done, otherwise continue */
-		tmp = con ? NULL : next;
 	}
+	return 0;
 }
 
-int ast_context_destroy_by_name(const char *context, const char *registrar)
+/*! \brief
+ * Main interface to add extensions to the list for out context.
+ *
+ * We sort extensions in order of matching preference, so that we can
+ * stop the search as soon as we find a suitable match.
+ * This ordering also takes care of wildcards such as '.' (meaning
+ * "one or more of any character") and '!' (which is 'earlymatch',
+ * meaning "zero or more of any character" but also impacts the
+ * return value from CANMATCH and EARLYMATCH.
+ *
+ * The extension match rules defined in the devmeeting 2006.05.05 are
+ * quite simple: WE SELECT THE LONGEST MATCH.
+ * In detail, "longest" means the number of matched characters in
+ * the extension. In case of ties (e.g. _XXX and 333) in the length
+ * of a pattern, we give priority to entries with the smallest cardinality
+ * (e.g, [5-9] comes before [2-8] before the former has only 5 elements,
+ * while the latter has 7, etc.
+ * In case of same cardinality, the first element in the range counts.
+ * If we still have a tie, any final '!' will make this as a possibly
+ * less specific pattern.
+ *
+ * EBUSY - can't lock
+ * EEXIST - extension with the same priority exist and no replace is set
+ *
+ */
+int ast_add_extension2(struct ast_context *con,
+	int replace, const char *extension, int priority, const char *label, const char *callerid,
+	const char *application, void *data, void (*datad)(void *),
+	const char *registrar)
 {
-	struct ast_context *con;
-	int ret = -1;
-
-	ast_wrlock_contexts();
-	con = ast_context_find(context);
-	if (con) {
-		ast_context_destroy(con, registrar);
-		ret = 0;
-	}
-	ast_unlock_contexts();
-
-	return ret;
+	return ast_add_extension2_lockopt(con, replace, extension, priority, label, callerid,
+		application, data, datad, registrar, 1);
 }
 
-void ast_context_destroy(struct ast_context *con, const char *registrar)
+int ast_add_extension2_nolock(struct ast_context *con,
+	int replace, const char *extension, int priority, const char *label, const char *callerid,
+	const char *application, void *data, void (*datad)(void *),
+	const char *registrar)
 {
-	ast_wrlock_contexts();
-	__ast_context_destroy(contexts, contexts_table, con,registrar);
-	ast_unlock_contexts();
+	return ast_add_extension2_lockopt(con, replace, extension, priority, label, callerid,
+		application, data, datad, registrar, 0);
 }
 
-static void wait_for_hangup(struct ast_channel *chan, const void *data)
-{
-	int res;
-	struct ast_frame *f;
-	double waitsec;
-	int waittime;
-
-	if (ast_strlen_zero(data) || (sscanf(data, "%30lg", &waitsec) != 1) || (waitsec < 0))
-		waitsec = -1;
-	if (waitsec > -1) {
-		waittime = waitsec * 1000.0;
-		ast_safe_sleep(chan, waittime);
-	} else do {
-		res = ast_waitfor(chan, -1);
-		if (res < 0)
-			return;
-		f = ast_read(chan);
-		if (f)
-			ast_frfree(f);
-	} while(f);
-}
 
 /*!
- * \ingroup applications
+ * \brief Same as ast_add_extension2() but controls the context locking.
+ *
+ * \details
+ * Does all the work of ast_add_extension2, but adds an arg to
+ * determine if context locking should be done.
  */
-static int pbx_builtin_proceeding(struct ast_channel *chan, const char *data)
+static int ast_add_extension2_lockopt(struct ast_context *con,
+	int replace, const char *extension, int priority, const char *label, const char *callerid,
+	const char *application, void *data, void (*datad)(void *),
+	const char *registrar, int lock_context)
 {
-	ast_indicate(chan, AST_CONTROL_PROCEEDING);
-	return 0;
-}
+	/*
+	 * Sort extensions (or patterns) according to the rules indicated above.
+	 * These are implemented by the function ext_cmp()).
+	 * All priorities for the same ext/pattern/cid are kept in a list,
+	 * using the 'peer' field  as a link field..
+	 */
+	struct ast_exten *tmp, *tmp2, *e, *el = NULL;
+	int res;
+	int length;
+	char *p;
+	char expand_buf[VAR_BUF_SIZE];
+	struct ast_exten dummy_exten = {0};
+	char dummy_name[1024];
 
-/*!
- * \ingroup applications
- */
-static int pbx_builtin_progress(struct ast_channel *chan, const char *data)
-{
-	ast_indicate(chan, AST_CONTROL_PROGRESS);
-	return 0;
-}
+	if (ast_strlen_zero(extension)) {
+		ast_log(LOG_ERROR,"You have to be kidding-- add exten '' to context %s? Figure out a name and call me back. Action ignored.\n",
+				con->name);
+		return -1;
+	}
 
-/*!
- * \ingroup applications
- */
-static int pbx_builtin_ringing(struct ast_channel *chan, const char *data)
-{
-	ast_indicate(chan, AST_CONTROL_RINGING);
-	return 0;
-}
+	/* If we are adding a hint evalulate in variables and global variables */
+	if (priority == PRIORITY_HINT && strstr(application, "${") && extension[0] != '_') {
+		struct ast_channel *c = ast_dummy_channel_alloc();
 
-/*!
- * \ingroup applications
- */
-static int pbx_builtin_busy(struct ast_channel *chan, const char *data)
-{
-	ast_indicate(chan, AST_CONTROL_BUSY);
-	/* Don't change state of an UP channel, just indicate
-	   busy in audio */
-	ast_channel_lock(chan);
-	if (ast_channel_state(chan) != AST_STATE_UP) {
-		ast_channel_hangupcause_set(chan, AST_CAUSE_BUSY);
-		ast_setstate(chan, AST_STATE_BUSY);
+		if (c) {
+			ast_channel_exten_set(c, extension);
+			ast_channel_context_set(c, con->name);
+		}
+		pbx_substitute_variables_helper(c, application, expand_buf, sizeof(expand_buf));
+		application = expand_buf;
+		if (c) {
+			ast_channel_unref(c);
+		}
 	}
-	ast_channel_unlock(chan);
-	wait_for_hangup(chan, data);
-	return -1;
-}
 
-/*!
- * \ingroup applications
- */
-static int pbx_builtin_congestion(struct ast_channel *chan, const char *data)
-{
-	ast_indicate(chan, AST_CONTROL_CONGESTION);
-	/* Don't change state of an UP channel, just indicate
-	   congestion in audio */
-	ast_channel_lock(chan);
-	if (ast_channel_state(chan) != AST_STATE_UP) {
-		ast_channel_hangupcause_set(chan, AST_CAUSE_CONGESTION);
-		ast_setstate(chan, AST_STATE_BUSY);
+	length = sizeof(struct ast_exten);
+	length += strlen(extension) + 1;
+	length += strlen(application) + 1;
+	if (label)
+		length += strlen(label) + 1;
+	if (callerid)
+		length += strlen(callerid) + 1;
+	else
+		length ++;	/* just the '\0' */
+
+	/* Be optimistic:  Build the extension structure first */
+	if (!(tmp = ast_calloc(1, length)))
+		return -1;
+
+	if (ast_strlen_zero(label)) /* let's turn empty labels to a null ptr */
+		label = 0;
+
+	/* use p as dst in assignments, as the fields are const char * */
+	p = tmp->stuff;
+	if (label) {
+		tmp->label = p;
+		strcpy(p, label);
+		p += strlen(label) + 1;
 	}
-	ast_channel_unlock(chan);
-	wait_for_hangup(chan, data);
-	return -1;
-}
+	tmp->exten = p;
+	p += ext_strncpy(p, extension, strlen(extension) + 1) + 1;
+	tmp->priority = priority;
+	tmp->cidmatch = p;	/* but use p for assignments below */
 
-/*!
- * \ingroup applications
- */
-static int pbx_builtin_answer(struct ast_channel *chan, const char *data)
-{
-	int delay = 0;
-	char *parse;
-	AST_DECLARE_APP_ARGS(args,
-		AST_APP_ARG(delay);
-		AST_APP_ARG(answer_cdr);
-	);
+	/* Blank callerid and NULL callerid are two SEPARATE things.  Do NOT confuse the two!!! */
+	if (callerid) {
+		p += ext_strncpy(p, callerid, strlen(callerid) + 1) + 1;
+		tmp->matchcid = AST_EXT_MATCHCID_ON;
+	} else {
+		*p++ = '\0';
+		tmp->matchcid = AST_EXT_MATCHCID_OFF;
+	}
+	tmp->app = p;
+	strcpy(p, application);
+	tmp->parent = con;
+	tmp->data = data;
+	tmp->datad = datad;
+	tmp->registrar = registrar;
+
+	if (lock_context) {
+		ast_wrlock_context(con);
+	}
 
-	if (ast_strlen_zero(data)) {
-		return __ast_answer(chan, 0);
+	if (con->pattern_tree) { /* usually, on initial load, the pattern_tree isn't formed until the first find_exten; so if we are adding
+								an extension, and the trie exists, then we need to incrementally add this pattern to it. */
+		ast_copy_string(dummy_name, extension, sizeof(dummy_name));
+		dummy_exten.exten = dummy_name;
+		dummy_exten.matchcid = AST_EXT_MATCHCID_OFF;
+		dummy_exten.cidmatch = 0;
+		tmp2 = ast_hashtab_lookup(con->root_table, &dummy_exten);
+		if (!tmp2) {
+			/* hmmm, not in the trie; */
+			add_exten_to_pattern_tree(con, tmp, 0);
+			ast_hashtab_insert_safe(con->root_table, tmp); /* for the sake of completeness */
+		}
+	}
+	res = 0; /* some compilers will think it is uninitialized otherwise */
+	for (e = con->root; e; el = e, e = e->next) {   /* scan the extension list */
+		res = ext_cmp(e->exten, tmp->exten);
+		if (res == 0) { /* extension match, now look at cidmatch */
+			if (e->matchcid == AST_EXT_MATCHCID_OFF && tmp->matchcid == AST_EXT_MATCHCID_OFF)
+				res = 0;
+			else if (tmp->matchcid == AST_EXT_MATCHCID_ON && e->matchcid == AST_EXT_MATCHCID_OFF)
+				res = 1;
+			else if (e->matchcid == AST_EXT_MATCHCID_ON && tmp->matchcid == AST_EXT_MATCHCID_OFF)
+				res = -1;
+			else
+				res = ext_cmp(e->cidmatch, tmp->cidmatch);
+		}
+		if (res >= 0)
+			break;
 	}
+	if (e && res == 0) { /* exact match, insert in the priority chain */
+		res = add_priority(con, tmp, el, e, replace);
+		if (res < 0) {
+			if (con->pattern_tree) {
+				struct match_char *x = add_exten_to_pattern_tree(con, tmp, 1);
+
+				if (x->exten) {
+					x->deleted = 1;
+					x->exten = 0;
+				}
 
-	parse = ast_strdupa(data);
+				ast_hashtab_remove_this_object(con->root_table, tmp);
+			}
 
-	AST_STANDARD_APP_ARGS(args, parse);
+			if (tmp->datad) {
+				tmp->datad(tmp->data);
+				/* if you free this, null it out */
+				tmp->data = NULL;
+			}
 
-	if (!ast_strlen_zero(args.delay) && (ast_channel_state(chan) != AST_STATE_UP))
-		delay = atoi(data);
+			ast_free(tmp);
+		}
+		if (lock_context) {
+			ast_unlock_context(con);
+		}
+		if (res < 0) {
+			errno = EEXIST;
+			return -1;
+		}
+	} else {
+		/*
+		 * not an exact match, this is the first entry with this pattern,
+		 * so insert in the main list right before 'e' (if any)
+		 */
+		tmp->next = e;
+		if (el) {  /* there is another exten already in this context */
+			el->next = tmp;
+			tmp->peer_table = ast_hashtab_create(13,
+							hashtab_compare_exten_numbers,
+							ast_hashtab_resize_java,
+							ast_hashtab_newsize_java,
+							hashtab_hash_priority,
+							0);
+			tmp->peer_label_table = ast_hashtab_create(7,
+								hashtab_compare_exten_labels,
+								ast_hashtab_resize_java,
+								ast_hashtab_newsize_java,
+								hashtab_hash_labels,
+								0);
+			if (label) {
+				ast_hashtab_insert_safe(tmp->peer_label_table, tmp);
+			}
+			ast_hashtab_insert_safe(tmp->peer_table, tmp);
+		} else {  /* this is the first exten in this context */
+			if (!con->root_table)
+				con->root_table = ast_hashtab_create(27,
+													hashtab_compare_extens,
+													ast_hashtab_resize_java,
+													ast_hashtab_newsize_java,
+													hashtab_hash_extens,
+													0);
+			con->root = tmp;
+			con->root->peer_table = ast_hashtab_create(13,
+								hashtab_compare_exten_numbers,
+								ast_hashtab_resize_java,
+								ast_hashtab_newsize_java,
+								hashtab_hash_priority,
+								0);
+			con->root->peer_label_table = ast_hashtab_create(7,
+									hashtab_compare_exten_labels,
+									ast_hashtab_resize_java,
+									ast_hashtab_newsize_java,
+									hashtab_hash_labels,
+									0);
+			if (label) {
+				ast_hashtab_insert_safe(con->root->peer_label_table, tmp);
+			}
+			ast_hashtab_insert_safe(con->root->peer_table, tmp);
 
-	if (delay < 0) {
-		delay = 0;
+		}
+		ast_hashtab_insert_safe(con->root_table, tmp);
+		if (lock_context) {
+			ast_unlock_context(con);
+		}
+		if (tmp->priority == PRIORITY_HINT) {
+			ast_add_hint(tmp);
+		}
+	}
+	if (option_debug) {
+		if (tmp->matchcid == AST_EXT_MATCHCID_ON) {
+			ast_debug(1, "Added extension '%s' priority %d (CID match '%s') to %s (%p)\n",
+					  tmp->exten, tmp->priority, tmp->cidmatch, con->name, con);
+		} else {
+			ast_debug(1, "Added extension '%s' priority %d to %s (%p)\n",
+					  tmp->exten, tmp->priority, con->name, con);
+		}
 	}
 
-	if (!ast_strlen_zero(args.answer_cdr) && !strcasecmp(args.answer_cdr, "nocdr")) {
-		ast_log(AST_LOG_WARNING, "The nocdr option for the Answer application has been removed and is no longer supported.\n");
+	if (tmp->matchcid == AST_EXT_MATCHCID_ON) {
+		ast_verb(3, "Added extension '%s' priority %d (CID match '%s') to %s\n",
+				 tmp->exten, tmp->priority, tmp->cidmatch, con->name);
+	} else {
+		ast_verb(3, "Added extension '%s' priority %d to %s\n",
+				 tmp->exten, tmp->priority, con->name);
 	}
 
-	return __ast_answer(chan, delay);
+	return 0;
 }
 
-static int pbx_builtin_incomplete(struct ast_channel *chan, const char *data)
-{
-	const char *options = data;
-	int answer = 1;
+/*! \brief Structure which contains information about an outgoing dial */
+struct pbx_outgoing {
+	/*! \brief Dialing structure being used */
+	struct ast_dial *dial;
+	/*! \brief Condition for synchronous dialing */
+	ast_cond_t cond;
+	/*! \brief Application to execute */
+	char app[AST_MAX_APP];
+	/*! \brief Application data to pass to application */
+	char *appdata;
+	/*! \brief Dialplan context */
+	char context[AST_MAX_CONTEXT];
+	/*! \brief Dialplan extension */
+	char exten[AST_MAX_EXTENSION];
+	/*! \brief Dialplan priority */
+	int priority;
+	/*! \brief Result of the dial operation when dialed is set */
+	int dial_res;
+	/*! \brief Set when dialing is completed */
+	unsigned int dialed:1;
+	/*! \brief Set when execution is completed */
+	unsigned int executed:1;
+};
 
-	/* Some channels can receive DTMF in unanswered state; some cannot */
-	if (!ast_strlen_zero(options) && strchr(options, 'n')) {
-		answer = 0;
-	}
+/*! \brief Destructor for outgoing structure */
+static void pbx_outgoing_destroy(void *obj)
+{
+	struct pbx_outgoing *outgoing = obj;
 
-	/* If the channel is hungup, stop waiting */
-	if (ast_check_hangup(chan)) {
-		return -1;
-	} else if (ast_channel_state(chan) != AST_STATE_UP && answer) {
-		__ast_answer(chan, 0);
+	if (outgoing->dial) {
+		ast_dial_destroy(outgoing->dial);
 	}
 
-	ast_indicate(chan, AST_CONTROL_INCOMPLETE);
+	ast_cond_destroy(&outgoing->cond);
 
-	return AST_PBX_INCOMPLETE;
+	ast_free(outgoing->appdata);
 }
 
-/*!
- * \ingroup applications
- */
-static int pbx_builtin_setamaflags(struct ast_channel *chan, const char *data)
+/*! \brief Internal function which dials an outgoing leg and sends it to a provided extension or application */
+static void *pbx_outgoing_exec(void *data)
 {
-	ast_log(AST_LOG_WARNING, "The SetAMAFlags application is deprecated. Please use the CHANNEL function instead.\n");
+	RAII_VAR(struct pbx_outgoing *, outgoing, data, ao2_cleanup);
+	enum ast_dial_result res;
 
-	if (ast_strlen_zero(data)) {
-		ast_log(AST_LOG_WARNING, "No parameter passed to SetAMAFlags\n");
-		return 0;
+	/* Notify anyone interested that dialing is complete */
+	res = ast_dial_run(outgoing->dial, NULL, 0);
+	ao2_lock(outgoing);
+	outgoing->dial_res = res;
+	outgoing->dialed = 1;
+	ast_cond_signal(&outgoing->cond);
+	ao2_unlock(outgoing);
+
+	/* If the outgoing leg was not answered we can immediately return and go no further */
+	if (res != AST_DIAL_RESULT_ANSWERED) {
+		return NULL;
 	}
-	/* Copy the AMA Flags as specified */
-	ast_channel_lock(chan);
-	if (isdigit(data[0])) {
-		int amaflags;
-		if (sscanf(data, "%30d", &amaflags) != 1) {
-			ast_log(AST_LOG_WARNING, "Unable to set AMA flags on channel %s\n", ast_channel_name(chan));
-			ast_channel_unlock(chan);
-			return 0;
+
+	if (!ast_strlen_zero(outgoing->app)) {
+		struct ast_app *app = pbx_findapp(outgoing->app);
+
+		if (app) {
+			ast_verb(4, "Launching %s(%s) on %s\n", outgoing->app, S_OR(outgoing->appdata, ""),
+				ast_channel_name(ast_dial_answered(outgoing->dial)));
+			pbx_exec(ast_dial_answered(outgoing->dial), app, outgoing->appdata);
+		} else {
+			ast_log(LOG_WARNING, "No such application '%s'\n", outgoing->app);
 		}
-		ast_channel_amaflags_set(chan, amaflags);
 	} else {
-		ast_channel_amaflags_set(chan, ast_channel_string2amaflag(data));
-	}
-	ast_channel_unlock(chan);
-	return 0;
-}
+		struct ast_channel *answered = ast_dial_answered(outgoing->dial);
 
-/*!
- * \ingroup applications
- */
-static int pbx_builtin_hangup(struct ast_channel *chan, const char *data)
-{
-	int cause;
+		if (!ast_strlen_zero(outgoing->context)) {
+			ast_channel_context_set(answered, outgoing->context);
+		}
 
-	ast_set_hangupsource(chan, "dialplan/builtin", 0);
+		if (!ast_strlen_zero(outgoing->exten)) {
+			ast_channel_exten_set(answered, outgoing->exten);
+		}
 
-	if (!ast_strlen_zero(data)) {
-		cause = ast_str2cause(data);
-		if (cause <= 0) {
-			if (sscanf(data, "%30d", &cause) != 1 || cause <= 0) {
-				ast_log(LOG_WARNING, "Invalid cause given to Hangup(): \"%s\"\n", data);
-				cause = 0;
-			}
+		if (outgoing->priority > 0) {
+			ast_channel_priority_set(answered, outgoing->priority);
 		}
-	} else {
-		cause = 0;
-	}
 
-	ast_channel_lock(chan);
-	if (cause <= 0) {
-		cause = ast_channel_hangupcause(chan);
-		if (cause <= 0) {
-			cause = AST_CAUSE_NORMAL_CLEARING;
+		if (ast_pbx_run(answered)) {
+			ast_log(LOG_ERROR, "Failed to start PBX on %s\n", ast_channel_name(answered));
+		} else {
+			/* PBX will have taken care of hanging up, so we steal the answered channel so dial doesn't do it */
+			ast_dial_answered_steal(outgoing->dial);
 		}
 	}
-	ast_channel_hangupcause_set(chan, cause);
-	ast_softhangup_nolock(chan, AST_SOFTHANGUP_EXPLICIT);
-	ast_channel_unlock(chan);
 
-	return -1;
+	/* Notify anyone else again that may be interested that execution is complete */
+	ao2_lock(outgoing);
+	outgoing->executed = 1;
+	ast_cond_signal(&outgoing->cond);
+	ao2_unlock(outgoing);
+
+	return NULL;
 }
 
-/*!
- * \ingroup functions
- */
-static int testtime_write(struct ast_channel *chan, const char *cmd, char *var, const char *value)
+/*! \brief Internal dialing state callback which causes early media to trigger an answer */
+static void pbx_outgoing_state_callback(struct ast_dial *dial)
 {
-	struct ast_tm tm;
-	struct timeval tv;
-	char *remainder, result[30], timezone[80];
+	struct ast_channel *channel;
 
-	/* Turn off testing? */
-	if (!pbx_checkcondition(value)) {
-		pbx_builtin_setvar_helper(chan, "TESTTIME", NULL);
-		return 0;
+	if (ast_dial_state(dial) != AST_DIAL_RESULT_PROGRESS) {
+		return;
 	}
 
-	/* Parse specified time */
-	if (!(remainder = ast_strptime(value, "%Y/%m/%d %H:%M:%S", &tm))) {
-		return -1;
+	if (!(channel = ast_dial_get_channel(dial, 0))) {
+		return;
 	}
-	sscanf(remainder, "%79s", timezone);
-	tv = ast_mktime(&tm, S_OR(timezone, NULL));
 
-	snprintf(result, sizeof(result), "%ld", (long) tv.tv_sec);
-	pbx_builtin_setvar_helper(chan, "__TESTTIME", result);
-	return 0;
-}
+	ast_verb(4, "Treating progress as answer on '%s' due to early media option\n",
+		ast_channel_name(channel));
 
-static struct ast_custom_function testtime_function = {
-	.name = "TESTTIME",
-	.write = testtime_write,
-};
+	ast_queue_control(channel, AST_CONTROL_ANSWER);
+}
 
 /*!
- * \ingroup applications
+ * \brief Attempt to convert disconnect cause to old originate reason.
+ *
+ * \todo XXX The old originate reasons need to be trashed and replaced
+ * with normal disconnect cause codes if the call was not answered.
+ * The internal consumers of the reason values would also need to be
+ * updated: app_originate, call files, and AMI OriginateResponse.
  */
-static int pbx_builtin_gotoiftime(struct ast_channel *chan, const char *data)
+static enum ast_control_frame_type pbx_dial_reason(enum ast_dial_result dial_result, int cause)
 {
-	char *s, *ts, *branch1, *branch2, *branch;
-	struct ast_timing timing;
-	const char *ctime;
-	struct timeval tv = ast_tvnow();
-	long timesecs;
+	enum ast_control_frame_type pbx_reason;
 
-	if (!chan) {
-		ast_log(LOG_WARNING, "GotoIfTime requires a channel on which to operate\n");
-		return -1;
+	if (dial_result == AST_DIAL_RESULT_ANSWERED) {
+		/* Remote end answered. */
+		pbx_reason = AST_CONTROL_ANSWER;
+	} else if (dial_result == AST_DIAL_RESULT_HANGUP) {
+		/* Caller hungup */
+		pbx_reason = AST_CONTROL_HANGUP;
+	} else {
+		switch (cause) {
+		case AST_CAUSE_USER_BUSY:
+			pbx_reason = AST_CONTROL_BUSY;
+			break;
+		case AST_CAUSE_CALL_REJECTED:
+		case AST_CAUSE_NETWORK_OUT_OF_ORDER:
+		case AST_CAUSE_DESTINATION_OUT_OF_ORDER:
+		case AST_CAUSE_NORMAL_TEMPORARY_FAILURE:
+		case AST_CAUSE_SWITCH_CONGESTION:
+		case AST_CAUSE_NORMAL_CIRCUIT_CONGESTION:
+			pbx_reason = AST_CONTROL_CONGESTION;
+			break;
+		case AST_CAUSE_ANSWERED_ELSEWHERE:
+		case AST_CAUSE_NO_ANSWER:
+			/* Remote end was ringing (but isn't anymore) */
+			pbx_reason = AST_CONTROL_RINGING;
+			break;
+		case AST_CAUSE_UNALLOCATED:
+		default:
+			/* Call Failure (not BUSY, and not NO_ANSWER, maybe Circuit busy or down?) */
+			pbx_reason = 0;
+			break;
+		}
 	}
 
-	if (ast_strlen_zero(data)) {
-		ast_log(LOG_WARNING, "GotoIfTime requires an argument:\n  <time range>,<days of week>,<days of month>,<months>[,<timezone>]?'labeliftrue':'labeliffalse'\n");
-		return -1;
-	}
+	return pbx_reason;
+}
 
-	ts = s = ast_strdupa(data);
+static int pbx_outgoing_attempt(const char *type, struct ast_format_cap *cap,
+	const char *addr, int timeout, const char *context, const char *exten, int priority,
+	const char *app, const char *appdata, int *reason, int synchronous,
+	const char *cid_num, const char *cid_name, struct ast_variable *vars,
+	const char *account, struct ast_channel **locked_channel, int early_media,
+	const struct ast_assigned_ids *assignedids)
+{
+	RAII_VAR(struct pbx_outgoing *, outgoing, NULL, ao2_cleanup);
+	struct ast_channel *dialed;
+	pthread_t thread;
 
-	ast_channel_lock(chan);
-	if ((ctime = pbx_builtin_getvar_helper(chan, "TESTTIME")) && sscanf(ctime, "%ld", &timesecs) == 1) {
-		tv.tv_sec = timesecs;
-	} else if (ctime) {
-		ast_log(LOG_WARNING, "Using current time to evaluate\n");
-		/* Reset when unparseable */
-		pbx_builtin_setvar_helper(chan, "TESTTIME", NULL);
+	outgoing = ao2_alloc(sizeof(*outgoing), pbx_outgoing_destroy);
+	if (!outgoing) {
+		return -1;
 	}
-	ast_channel_unlock(chan);
-
-	/* Separate the Goto path */
-	strsep(&ts, "?");
-	branch1 = strsep(&ts,":");
-	branch2 = strsep(&ts,"");
+	ast_cond_init(&outgoing->cond, NULL);
 
-	/* struct ast_include include contained garbage here, fixed by zeroing it on get_timerange */
-	if (ast_build_timing(&timing, s) && ast_check_timing2(&timing, tv)) {
-		branch = branch1;
+	if (!ast_strlen_zero(app)) {
+		ast_copy_string(outgoing->app, app, sizeof(outgoing->app));
+		outgoing->appdata = ast_strdup(appdata);
 	} else {
-		branch = branch2;
+		ast_copy_string(outgoing->context, context, sizeof(outgoing->context));
+		ast_copy_string(outgoing->exten, exten, sizeof(outgoing->exten));
+		outgoing->priority = priority;
 	}
-	ast_destroy_timing(&timing);
 
-	if (ast_strlen_zero(branch)) {
-		ast_debug(1, "Not taking any branch\n");
-		return 0;
+	if (!(outgoing->dial = ast_dial_create())) {
+		return -1;
 	}
 
-	return pbx_builtin_goto(chan, branch);
-}
-
-/*!
- * \ingroup applications
- */
-static int pbx_builtin_execiftime(struct ast_channel *chan, const char *data)
-{
-	char *s, *appname;
-	struct ast_timing timing;
-	struct ast_app *app;
-	static const char * const usage = "ExecIfTime requires an argument:\n  <time range>,<days of week>,<days of month>,<months>[,<timezone>]?<appname>[(<appargs>)]";
-
-	if (ast_strlen_zero(data)) {
-		ast_log(LOG_WARNING, "%s\n", usage);
+	if (ast_dial_append(outgoing->dial, type, addr, assignedids)) {
 		return -1;
 	}
 
-	appname = ast_strdupa(data);
+	ast_dial_set_global_timeout(outgoing->dial, timeout);
 
-	s = strsep(&appname, "?");	/* Separate the timerange and application name/data */
-	if (!appname) {	/* missing application */
-		ast_log(LOG_WARNING, "%s\n", usage);
+	if (ast_dial_prerun(outgoing->dial, NULL, cap)) {
+		if (synchronous && reason) {
+			*reason = pbx_dial_reason(AST_DIAL_RESULT_FAILED,
+				ast_dial_reason(outgoing->dial, 0));
+		}
 		return -1;
 	}
 
-	if (!ast_build_timing(&timing, s)) {
-		ast_log(LOG_WARNING, "Invalid Time Spec: %s\nCorrect usage: %s\n", s, usage);
-		ast_destroy_timing(&timing);
+	dialed = ast_dial_get_channel(outgoing->dial, 0);
+	if (!dialed) {
 		return -1;
 	}
 
-	if (!ast_check_timing(&timing))	{ /* outside the valid time window, just return */
-		ast_destroy_timing(&timing);
-		return 0;
-	}
-	ast_destroy_timing(&timing);
-
-	/* now split appname(appargs) */
-	if ((s = strchr(appname, '('))) {
-		char *e;
-		*s++ = '\0';
-		if ((e = strrchr(s, ')')))
-			*e = '\0';
-		else
-			ast_log(LOG_WARNING, "Failed to find closing parenthesis\n");
+	ast_channel_lock(dialed);
+	if (vars) {
+		ast_set_variables(dialed, vars);
 	}
-
-
-	if ((app = pbx_findapp(appname))) {
-		return pbx_exec(chan, app, S_OR(s, ""));
-	} else {
-		ast_log(LOG_WARNING, "Cannot locate application %s\n", appname);
-		return -1;
+	if (!ast_strlen_zero(account)) {
+		ast_channel_stage_snapshot(dialed);
+		ast_channel_accountcode_set(dialed, account);
+		ast_channel_peeraccount_set(dialed, account);
+		ast_channel_stage_snapshot_done(dialed);
 	}
-}
-
-/*!
- * \ingroup applications
- */
-static int pbx_builtin_wait(struct ast_channel *chan, const char *data)
-{
-	int ms;
+	ast_set_flag(ast_channel_flags(dialed), AST_FLAG_ORIGINATED);
+	ast_channel_unlock(dialed);
 
-	/* Wait for "n" seconds */
-	if (!ast_app_parse_timelen(data, &ms, TIMELEN_SECONDS) && ms > 0) {
-		return ast_safe_sleep(chan, ms);
-	}
-	return 0;
-}
+	if (!ast_strlen_zero(cid_num) || !ast_strlen_zero(cid_name)) {
+		struct ast_party_connected_line connected;
 
-/*!
- * \ingroup applications
- */
-static int pbx_builtin_waitexten(struct ast_channel *chan, const char *data)
-{
-	int ms, res;
-	struct ast_flags flags = {0};
-	char *opts[1] = { NULL };
-	char *parse;
-	AST_DECLARE_APP_ARGS(args,
-		AST_APP_ARG(timeout);
-		AST_APP_ARG(options);
-	);
+		/*
+		 * It seems strange to set the CallerID on an outgoing call leg
+		 * to whom we are calling, but this function's callers are doing
+		 * various Originate methods.  This call leg goes to the local
+		 * user.  Once the called party answers, the dialplan needs to
+		 * be able to access the CallerID from the CALLERID function as
+		 * if the called party had placed this call.
+		 */
+		ast_set_callerid(dialed, cid_num, cid_name, cid_num);
 
-	if (!ast_strlen_zero(data)) {
-		parse = ast_strdupa(data);
-		AST_STANDARD_APP_ARGS(args, parse);
-	} else
-		memset(&args, 0, sizeof(args));
-
-	if (args.options)
-		ast_app_parse_options(waitexten_opts, &flags, opts, args.options);
-
-	if (ast_test_flag(&flags, WAITEXTEN_MOH) && !opts[0] ) {
-		ast_log(LOG_WARNING, "The 'm' option has been specified for WaitExten without a class.\n");
-	} else if (ast_test_flag(&flags, WAITEXTEN_MOH)) {
-		ast_indicate_data(chan, AST_CONTROL_HOLD, S_OR(opts[0], NULL),
-			!ast_strlen_zero(opts[0]) ? strlen(opts[0]) + 1 : 0);
-	} else if (ast_test_flag(&flags, WAITEXTEN_DIALTONE)) {
-		struct ast_tone_zone_sound *ts = ast_get_indication_tone(ast_channel_zone(chan), "dial");
-		if (ts) {
-			ast_playtones_start(chan, 0, ts->data, 0);
-			ts = ast_tone_zone_sound_unref(ts);
-		} else {
-			ast_tonepair_start(chan, 350, 440, 0, 0);
+		ast_party_connected_line_set_init(&connected, ast_channel_connected(dialed));
+		if (!ast_strlen_zero(cid_num)) {
+			connected.id.number.valid = 1;
+			connected.id.number.str = (char *) cid_num;
+			connected.id.number.presentation = AST_PRES_ALLOWED_USER_NUMBER_NOT_SCREENED;
 		}
-	}
-	/* Wait for "n" seconds */
-	if (!ast_app_parse_timelen(args.timeout, &ms, TIMELEN_SECONDS) && ms > 0) {
-		/* Yay! */
-	} else if (ast_channel_pbx(chan)) {
-		ms = ast_channel_pbx(chan)->rtimeoutms;
-	} else {
-		ms = 10000;
-	}
-
-	res = ast_waitfordigit(chan, ms);
-	if (!res) {
-		if (ast_check_hangup(chan)) {
-			/* Call is hungup for some reason. */
-			res = -1;
-		} else if (ast_exists_extension(chan, ast_channel_context(chan), ast_channel_exten(chan), ast_channel_priority(chan) + 1,
-			S_COR(ast_channel_caller(chan)->id.number.valid, ast_channel_caller(chan)->id.number.str, NULL))) {
-			ast_verb(3, "Timeout on %s, continuing...\n", ast_channel_name(chan));
-		} else if (ast_exists_extension(chan, ast_channel_context(chan), "t", 1,
-			S_COR(ast_channel_caller(chan)->id.number.valid, ast_channel_caller(chan)->id.number.str, NULL))) {
-			ast_verb(3, "Timeout on %s, going to 't'\n", ast_channel_name(chan));
-			set_ext_pri(chan, "t", 0); /* 0 will become 1, next time through the loop */
-		} else if (ast_exists_extension(chan, ast_channel_context(chan), "e", 1,
-			S_COR(ast_channel_caller(chan)->id.number.valid, ast_channel_caller(chan)->id.number.str, NULL))) {
-			raise_exception(chan, "RESPONSETIMEOUT", 0); /* 0 will become 1, next time through the loop */
-		} else {
-			ast_log(LOG_WARNING, "Timeout but no rule 't' or 'e' in context '%s'\n",
-				ast_channel_context(chan));
-			res = -1;
+		if (!ast_strlen_zero(cid_name)) {
+			connected.id.name.valid = 1;
+			connected.id.name.str = (char *) cid_name;
+			connected.id.name.presentation = AST_PRES_ALLOWED_USER_NUMBER_NOT_SCREENED;
 		}
+		ast_channel_set_connected_line(dialed, &connected, NULL);
 	}
 
-	if (ast_test_flag(&flags, WAITEXTEN_MOH))
-		ast_indicate(chan, AST_CONTROL_UNHOLD);
-	else if (ast_test_flag(&flags, WAITEXTEN_DIALTONE))
-		ast_playtones_stop(chan);
-
-	return res;
-}
-
-/*!
- * \ingroup applications
- */
-static int pbx_builtin_background(struct ast_channel *chan, const char *data)
-{
-	int res = 0;
-	int mres = 0;
-	struct ast_flags flags = {0};
-	char *parse, exten[2] = "";
-	AST_DECLARE_APP_ARGS(args,
-		AST_APP_ARG(filename);
-		AST_APP_ARG(options);
-		AST_APP_ARG(lang);
-		AST_APP_ARG(context);
-	);
-
-	if (ast_strlen_zero(data)) {
-		ast_log(LOG_WARNING, "Background requires an argument (filename)\n");
-		return -1;
+	if (early_media) {
+		ast_dial_set_state_callback(outgoing->dial, pbx_outgoing_state_callback);
 	}
 
-	parse = ast_strdupa(data);
-
-	AST_STANDARD_APP_ARGS(args, parse);
-
-	if (ast_strlen_zero(args.lang))
-		args.lang = (char *)ast_channel_language(chan);	/* XXX this is const */
-
-	if (ast_strlen_zero(args.context)) {
-		const char *context;
-		ast_channel_lock(chan);
-		if ((context = pbx_builtin_getvar_helper(chan, "MACRO_CONTEXT"))) {
-			args.context = ast_strdupa(context);
-		} else {
-			args.context = ast_strdupa(ast_channel_context(chan));
+	if (locked_channel) {
+		/*
+		 * Keep a dialed channel ref since the caller wants
+		 * the channel returned.  We must get the ref before
+		 * spawning off pbx_outgoing_exec().
+		 */
+		ast_channel_ref(dialed);
+		if (!synchronous) {
+			/*
+			 * Lock it now to hold off pbx_outgoing_exec() in case the
+			 * calling function needs the channel state/snapshot before
+			 * dialing actually happens.
+			 */
+			ast_channel_lock(dialed);
 		}
-		ast_channel_unlock(chan);
 	}
 
-	if (args.options) {
-		if (!strcasecmp(args.options, "skip"))
-			flags.flags = BACKGROUND_SKIP;
-		else if (!strcasecmp(args.options, "noanswer"))
-			flags.flags = BACKGROUND_NOANSWER;
-		else
-			ast_app_parse_options(background_opts, &flags, NULL, args.options);
+	ao2_ref(outgoing, +1);
+	if (ast_pthread_create_detached(&thread, NULL, pbx_outgoing_exec, outgoing)) {
+		ast_log(LOG_WARNING, "Unable to spawn dialing thread for '%s/%s'\n", type, addr);
+		ao2_ref(outgoing, -1);
+		if (locked_channel) {
+			if (!synchronous) {
+				ast_channel_unlock(dialed);
+			}
+			ast_channel_unref(dialed);
+		}
+		return -1;
 	}
 
-	/* Answer if need be */
-	if (ast_channel_state(chan) != AST_STATE_UP) {
-		if (ast_test_flag(&flags, BACKGROUND_SKIP)) {
-			goto done;
-		} else if (!ast_test_flag(&flags, BACKGROUND_NOANSWER)) {
-			res = ast_answer(chan);
+	if (synchronous) {
+		ao2_lock(outgoing);
+		/* Wait for dialing to complete */
+		while (!outgoing->dialed) {
+			ast_cond_wait(&outgoing->cond, ao2_object_get_lockaddr(outgoing));
 		}
-	}
+		if (1 < synchronous
+			&& outgoing->dial_res == AST_DIAL_RESULT_ANSWERED) {
+			/* Wait for execution to complete */
+			while (!outgoing->executed) {
+				ast_cond_wait(&outgoing->cond, ao2_object_get_lockaddr(outgoing));
+			}
+		}
+		ao2_unlock(outgoing);
 
-	if (!res) {
-		char *back = ast_strip(args.filename);
-		char *front;
+		/* Determine the outcome of the dialing attempt up to it being answered. */
+		if (reason) {
+			*reason = pbx_dial_reason(outgoing->dial_res,
+				ast_dial_reason(outgoing->dial, 0));
+		}
 
-		ast_stopstream(chan);		/* Stop anything playing */
-		/* Stream the list of files */
-		while (!res && (front = strsep(&back, "&")) ) {
-			if ( (res = ast_streamfile(chan, front, args.lang)) ) {
-				ast_log(LOG_WARNING, "ast_streamfile failed on %s for %s\n", ast_channel_name(chan), (char*)data);
-				res = 0;
-				mres = 1;
-				break;
-			}
-			if (ast_test_flag(&flags, BACKGROUND_PLAYBACK)) {
-				res = ast_waitstream(chan, "");
-			} else if (ast_test_flag(&flags, BACKGROUND_MATCHEXTEN)) {
-				res = ast_waitstream_exten(chan, args.context);
-			} else {
-				res = ast_waitstream(chan, AST_DIGIT_ANY);
+		if (outgoing->dial_res != AST_DIAL_RESULT_ANSWERED) {
+			/* The dial operation failed. */
+			if (locked_channel) {
+				ast_channel_unref(dialed);
 			}
-			ast_stopstream(chan);
+			return -1;
+		}
+		if (locked_channel) {
+			ast_channel_lock(dialed);
 		}
 	}
 
-	/*
-	 * If the single digit DTMF is an extension in the specified context, then
-	 * go there and signal no DTMF.  Otherwise, we should exit with that DTMF.
-	 * If we're in Macro, we'll exit and seek that DTMF as the beginning of an
-	 * extension in the Macro's calling context.  If we're not in Macro, then
-	 * we'll simply seek that extension in the calling context.  Previously,
-	 * someone complained about the behavior as it related to the interior of a
-	 * Gosub routine, and the fix (#14011) inadvertently broke FreePBX
-	 * (#14940).  This change should fix both of these situations, but with the
-	 * possible incompatibility that if a single digit extension does not exist
-	 * (but a longer extension COULD have matched), it would have previously
-	 * gone immediately to the "i" extension, but will now need to wait for a
-	 * timeout.
-	 *
-	 * Later, we had to add a flag to disable this workaround, because AGI
-	 * users can EXEC Background and reasonably expect that the DTMF code will
-	 * be returned (see #16434).
-	 */
-	if (!ast_test_flag(ast_channel_flags(chan), AST_FLAG_DISABLE_WORKAROUNDS)
-		&& (exten[0] = res)
-		&& ast_canmatch_extension(chan, args.context, exten, 1,
-			S_COR(ast_channel_caller(chan)->id.number.valid, ast_channel_caller(chan)->id.number.str, NULL))
-		&& !ast_matchmore_extension(chan, args.context, exten, 1,
-			S_COR(ast_channel_caller(chan)->id.number.valid, ast_channel_caller(chan)->id.number.str, NULL))) {
-		char buf[2] = { 0, };
-		snprintf(buf, sizeof(buf), "%c", res);
-		ast_channel_exten_set(chan, buf);
-		ast_channel_context_set(chan, args.context);
-		ast_channel_priority_set(chan, 0);
-		res = 0;
+	if (locked_channel) {
+		*locked_channel = dialed;
 	}
-done:
-	pbx_builtin_setvar_helper(chan, "BACKGROUNDSTATUS", mres ? "FAILED" : "SUCCESS");
-	return res;
-}
-
-/*! Goto
- * \ingroup applications
- */
-static int pbx_builtin_goto(struct ast_channel *chan, const char *data)
-{
-	int res = ast_parseable_goto(chan, data);
-	if (!res)
-		ast_verb(3, "Goto (%s,%s,%d)\n", ast_channel_context(chan), ast_channel_exten(chan), ast_channel_priority(chan) + 1);
-	return res;
+	return 0;
 }
 
-
-int pbx_builtin_serialize_variables(struct ast_channel *chan, struct ast_str **buf)
+int ast_pbx_outgoing_exten(const char *type, struct ast_format_cap *cap, const char *addr,
+	int timeout, const char *context, const char *exten, int priority, int *reason,
+	int synchronous, const char *cid_num, const char *cid_name, struct ast_variable *vars,
+	const char *account, struct ast_channel **locked_channel, int early_media,
+	const struct ast_assigned_ids *assignedids)
 {
-	struct ast_var_t *variables;
-	const char *var, *val;
-	int total = 0;
-
-	if (!chan)
-		return 0;
-
-	ast_str_reset(*buf);
-
-	ast_channel_lock(chan);
+	int res;
+	int my_reason;
 
-	AST_LIST_TRAVERSE(ast_channel_varshead(chan), variables, entries) {
-		if ((var = ast_var_name(variables)) && (val = ast_var_value(variables))
-		   /* && !ast_strlen_zero(var) && !ast_strlen_zero(val) */
-		   ) {
-			if (ast_str_append(buf, 0, "%s=%s\n", var, val) < 0) {
-				ast_log(LOG_ERROR, "Data Buffer Size Exceeded!\n");
-				break;
-			} else
-				total++;
-		} else
-			break;
+	if (!reason) {
+		reason = &my_reason;
+	}
+	*reason = 0;
+	if (locked_channel) {
+		*locked_channel = NULL;
 	}
 
-	ast_channel_unlock(chan);
+	res = pbx_outgoing_attempt(type, cap, addr, timeout, context, exten, priority,
+		NULL, NULL, reason, synchronous, cid_num, cid_name, vars, account, locked_channel,
+		early_media, assignedids);
 
-	return total;
-}
+	if (res < 0 /* Call failed to get connected for some reason. */
+		&& 1 < synchronous
+		&& ast_exists_extension(NULL, context, "failed", 1, NULL)) {
+		struct ast_channel *failed;
 
-const char *pbx_builtin_getvar_helper(struct ast_channel *chan, const char *name)
-{
-	struct ast_var_t *variables;
-	const char *ret = NULL;
-	int i;
-	struct varshead *places[2] = { NULL, &globals };
+		/* We do not have to worry about a locked_channel if dialing failed. */
+		ast_assert(!locked_channel || !*locked_channel);
 
-	if (!name)
-		return NULL;
+		/*!
+		 * \todo XXX Not good.  The channel name is not unique if more than
+		 * one originate fails at a time.
+		 */
+		failed = ast_channel_alloc(0, AST_STATE_DOWN, cid_num, cid_name, account,
+			"failed", context, NULL, NULL, 0, "OutgoingSpoolFailed");
+		if (failed) {
+			char failed_reason[12];
 
-	if (chan) {
-		ast_channel_lock(chan);
-		places[0] = ast_channel_varshead(chan);
-	}
+			ast_set_variables(failed, vars);
+			snprintf(failed_reason, sizeof(failed_reason), "%d", *reason);
+			pbx_builtin_setvar_helper(failed, "REASON", failed_reason);
+			ast_channel_unlock(failed);
 
-	for (i = 0; i < 2; i++) {
-		if (!places[i])
-			continue;
-		if (places[i] == &globals)
-			ast_rwlock_rdlock(&globalslock);
-		AST_LIST_TRAVERSE(places[i], variables, entries) {
-			if (!strcmp(name, ast_var_name(variables))) {
-				ret = ast_var_value(variables);
-				break;
+			if (ast_pbx_run(failed)) {
+				ast_log(LOG_ERROR, "Unable to run PBX on '%s'\n",
+					ast_channel_name(failed));
+				ast_hangup(failed);
 			}
 		}
-		if (places[i] == &globals)
-			ast_rwlock_unlock(&globalslock);
-		if (ret)
-			break;
 	}
 
-	if (chan)
-		ast_channel_unlock(chan);
-
-	return ret;
+	return res;
 }
 
-void pbx_builtin_pushvar_helper(struct ast_channel *chan, const char *name, const char *value)
+int ast_pbx_outgoing_app(const char *type, struct ast_format_cap *cap, const char *addr,
+	int timeout, const char *app, const char *appdata, int *reason, int synchronous,
+	const char *cid_num, const char *cid_name, struct ast_variable *vars,
+	const char *account, struct ast_channel **locked_channel,
+	const struct ast_assigned_ids *assignedids)
 {
-	struct ast_var_t *newvariable;
-	struct varshead *headp;
-
-	if (name[strlen(name)-1] == ')') {
-		char *function = ast_strdupa(name);
-
-		ast_log(LOG_WARNING, "Cannot push a value onto a function\n");
-		ast_func_write(chan, function, value);
-		return;
+	if (reason) {
+		*reason = 0;
 	}
-
-	if (chan) {
-		ast_channel_lock(chan);
-		headp = ast_channel_varshead(chan);
-	} else {
-		ast_rwlock_wrlock(&globalslock);
-		headp = &globals;
+	if (locked_channel) {
+		*locked_channel = NULL;
 	}
-
-	if (value && (newvariable = ast_var_assign(name, value))) {
-		if (headp == &globals)
-			ast_verb(2, "Setting global variable '%s' to '%s'\n", name, value);
-		AST_LIST_INSERT_HEAD(headp, newvariable, entries);
+	if (ast_strlen_zero(app)) {
+		return -1;
 	}
 
-	if (chan)
-		ast_channel_unlock(chan);
-	else
-		ast_rwlock_unlock(&globalslock);
+	return pbx_outgoing_attempt(type, cap, addr, timeout, NULL, NULL, 0, app, appdata,
+		reason, synchronous, cid_num, cid_name, vars, account, locked_channel, 0,
+		assignedids);
 }
 
-int pbx_builtin_setvar_helper(struct ast_channel *chan, const char *name, const char *value)
-{
-	struct ast_var_t *newvariable;
-	struct varshead *headp;
-	const char *nametail = name;
-	/*! True if the old value was not an empty string. */
-	int old_value_existed = 0;
-
-	if (name[strlen(name) - 1] == ')') {
-		char *function = ast_strdupa(name);
+/* this is the guts of destroying a context --
+   freeing up the structure, traversing and destroying the
+   extensions, switches, ignorepats, includes, etc. etc. */
 
-		return ast_func_write(chan, function, value);
-	}
+static void __ast_internal_context_destroy( struct ast_context *con)
+{
+	struct ast_include *tmpi;
+	struct ast_sw *sw;
+	struct ast_exten *e, *el, *en;
+	struct ast_ignorepat *ipi;
+	struct ast_context *tmp = con;
 
-	if (chan) {
-		ast_channel_lock(chan);
-		headp = ast_channel_varshead(chan);
-	} else {
-		ast_rwlock_wrlock(&globalslock);
-		headp = &globals;
+	for (tmpi = tmp->includes; tmpi; ) { /* Free includes */
+		struct ast_include *tmpil = tmpi;
+		tmpi = tmpi->next;
+		ast_free(tmpil);
 	}
-
-	/* For comparison purposes, we have to strip leading underscores */
-	if (*nametail == '_') {
-		nametail++;
-		if (*nametail == '_')
-			nametail++;
+	for (ipi = tmp->ignorepats; ipi; ) { /* Free ignorepats */
+		struct ast_ignorepat *ipl = ipi;
+		ipi = ipi->next;
+		ast_free(ipl);
 	}
+	if (tmp->registrar)
+		ast_free(tmp->registrar);
 
-	AST_LIST_TRAVERSE_SAFE_BEGIN(headp, newvariable, entries) {
-		if (strcmp(ast_var_name(newvariable), nametail) == 0) {
-			/* there is already such a variable, delete it */
-			AST_LIST_REMOVE_CURRENT(entries);
-			old_value_existed = !ast_strlen_zero(ast_var_value(newvariable));
-			ast_var_delete(newvariable);
-			break;
-		}
+	/* destroy the hash tabs */
+	if (tmp->root_table) {
+		ast_hashtab_destroy(tmp->root_table, 0);
 	}
-	AST_LIST_TRAVERSE_SAFE_END;
+	/* and destroy the pattern tree */
+	if (tmp->pattern_tree)
+		destroy_pattern_tree(tmp->pattern_tree);
 
-	if (value && (newvariable = ast_var_assign(name, value))) {
-		if (headp == &globals) {
-			ast_verb(2, "Setting global variable '%s' to '%s'\n", name, value);
+	while ((sw = AST_LIST_REMOVE_HEAD(&tmp->alts, list)))
+		ast_free(sw);
+	for (e = tmp->root; e;) {
+		for (en = e->peer; en;) {
+			el = en;
+			en = en->peer;
+			destroy_exten(el);
 		}
-		AST_LIST_INSERT_HEAD(headp, newvariable, entries);
-		ast_channel_publish_varset(chan, name, value);
-	} else if (old_value_existed) {
-		/* We just deleted a non-empty dialplan variable. */
-		ast_channel_publish_varset(chan, name, "");
+		el = e;
+		e = e->next;
+		destroy_exten(el);
 	}
-
-	if (chan)
-		ast_channel_unlock(chan);
-	else
-		ast_rwlock_unlock(&globalslock);
-	return 0;
+	tmp->root = NULL;
+	ast_rwlock_destroy(&tmp->lock);
+	ast_mutex_destroy(&tmp->macrolock);
+	ast_free(tmp);
 }
 
-int pbx_builtin_setvar(struct ast_channel *chan, const char *data)
-{
-	char *name, *value, *mydata;
-
-	if (ast_strlen_zero(data)) {
-		ast_log(LOG_WARNING, "Set requires one variable name/value pair.\n");
-		return 0;
-	}
-
-	mydata = ast_strdupa(data);
-	name = strsep(&mydata, "=");
-	value = mydata;
-	if (!value) {
-		ast_log(LOG_WARNING, "Set requires an '=' to be a valid assignment.\n");
-		return 0;
-	}
-
-	if (strchr(name, ' ')) {
-		ast_log(LOG_WARNING, "Please avoid unnecessary spaces on variables as it may lead to unexpected results ('%s' set to '%s').\n", name, mydata);
-	}
-
-	pbx_builtin_setvar_helper(chan, name, value);
-
-	return 0;
-}
 
-int pbx_builtin_setvar_multiple(struct ast_channel *chan, const char *vdata)
+void __ast_context_destroy(struct ast_context *list, struct ast_hashtab *contexttab, struct ast_context *con, const char *registrar)
 {
-	char *data;
-	int x;
-	AST_DECLARE_APP_ARGS(args,
-		AST_APP_ARG(pair)[24];
-	);
-	AST_DECLARE_APP_ARGS(pair,
-		AST_APP_ARG(name);
-		AST_APP_ARG(value);
-	);
-
-	if (ast_strlen_zero(vdata)) {
-		ast_log(LOG_WARNING, "MSet requires at least one variable name/value pair.\n");
-		return 0;
-	}
-
-	data = ast_strdupa(vdata);
-	AST_STANDARD_APP_ARGS(args, data);
+	struct ast_context *tmp, *tmpl=NULL;
+	struct ast_exten *exten_item, *prio_item;
 
-	for (x = 0; x < args.argc; x++) {
-		AST_NONSTANDARD_APP_ARGS(pair, args.pair[x], '=');
-		if (pair.argc == 2) {
-			pbx_builtin_setvar_helper(chan, pair.name, pair.value);
-			if (strchr(pair.name, ' '))
-				ast_log(LOG_WARNING, "Please avoid unnecessary spaces on variables as it may lead to unexpected results ('%s' set to '%s').\n", pair.name, pair.value);
-		} else if (!chan) {
-			ast_log(LOG_WARNING, "MSet: ignoring entry '%s' with no '='\n", pair.name);
-		} else {
-			ast_log(LOG_WARNING, "MSet: ignoring entry '%s' with no '=' (in %s@%s:%d\n", pair.name, ast_channel_exten(chan), ast_channel_context(chan), ast_channel_priority(chan));
+	for (tmp = list; tmp; ) {
+		struct ast_context *next = NULL;	/* next starting point */
+			/* The following code used to skip forward to the next
+			   context with matching registrar, but this didn't
+			   make sense; individual priorities registrar'd to
+			   the matching registrar could occur in any context! */
+		ast_debug(1, "Investigate ctx %s %s\n", tmp->name, tmp->registrar);
+		if (con) {
+			for (; tmp; tmpl = tmp, tmp = tmp->next) { /* skip to the matching context */
+				ast_debug(1, "check ctx %s %s\n", tmp->name, tmp->registrar);
+				if ( !strcasecmp(tmp->name, con->name) ) {
+					break;	/* found it */
+				}
+			}
 		}
-	}
 
-	return 0;
-}
+		if (!tmp)	/* not found, we are done */
+			break;
+		ast_wrlock_context(tmp);
 
-int pbx_builtin_importvar(struct ast_channel *chan, const char *data)
-{
-	char *name;
-	char *value;
-	char *channel;
-	char tmp[VAR_BUF_SIZE];
-	static int deprecation_warning = 0;
+		if (registrar) {
+			/* then search thru and remove any extens that match registrar. */
+			struct ast_hashtab_iter *exten_iter;
+			struct ast_hashtab_iter *prio_iter;
+			struct ast_ignorepat *ip, *ipl = NULL, *ipn = NULL;
+			struct ast_include *i, *pi = NULL, *ni = NULL;
+			struct ast_sw *sw = NULL;
 
-	if (ast_strlen_zero(data)) {
-		ast_log(LOG_WARNING, "Ignoring, since there is no variable to set\n");
-		return 0;
-	}
-	tmp[0] = 0;
-	if (!deprecation_warning) {
-		ast_log(LOG_WARNING, "ImportVar is deprecated.  Please use Set(varname=${IMPORT(channel,variable)}) instead.\n");
-		deprecation_warning = 1;
-	}
+			/* remove any ignorepats whose registrar matches */
+			for (ip = tmp->ignorepats; ip; ip = ipn) {
+				ipn = ip->next;
+				if (!strcmp(ip->registrar, registrar)) {
+					if (ipl) {
+						ipl->next = ip->next;
+						ast_free(ip);
+						continue; /* don't change ipl */
+					} else {
+						tmp->ignorepats = ip->next;
+						ast_free(ip);
+						continue; /* don't change ipl */
+					}
+				}
+				ipl = ip;
+			}
+			/* remove any includes whose registrar matches */
+			for (i = tmp->includes; i; i = ni) {
+				ni = i->next;
+				if (strcmp(i->registrar, registrar) == 0) {
+					/* remove from list */
+					if (pi) {
+						pi->next = i->next;
+						/* free include */
+						ast_free(i);
+						continue; /* don't change pi */
+					} else {
+						tmp->includes = i->next;
+						/* free include */
+						ast_free(i);
+						continue; /* don't change pi */
+					}
+				}
+				pi = i;
+			}
+			/* remove any switches whose registrar matches */
+			AST_LIST_TRAVERSE_SAFE_BEGIN(&tmp->alts, sw, list) {
+				if (strcmp(sw->registrar,registrar) == 0) {
+					AST_LIST_REMOVE_CURRENT(list);
+					ast_free(sw);
+				}
+			}
+			AST_LIST_TRAVERSE_SAFE_END;
 
-	value = ast_strdupa(data);
-	name = strsep(&value,"=");
-	channel = strsep(&value,",");
-	if (channel && value && name) { /*! \todo XXX should do !ast_strlen_zero(..) of the args ? */
-		struct ast_channel *chan2 = ast_channel_get_by_name(channel);
-		if (chan2) {
-			char *s = ast_alloca(strlen(value) + 4);
-			sprintf(s, "${%s}", value);
-			pbx_substitute_variables_helper(chan2, s, tmp, sizeof(tmp) - 1);
-			chan2 = ast_channel_unref(chan2);
-		}
-		pbx_builtin_setvar_helper(chan, name, tmp);
-	}
+			if (tmp->root_table) { /* it is entirely possible that the context is EMPTY */
+				exten_iter = ast_hashtab_start_traversal(tmp->root_table);
+				while ((exten_item=ast_hashtab_next(exten_iter))) {
+					int end_traversal = 1;
 
-	return(0);
-}
+					/*
+					 * If the extension could not be removed from the root_table due to
+					 * a loaded PBX app, it can exist here but have its peer_table be
+					 * destroyed due to a previous pass through this function.
+					 */
+					if (!exten_item->peer_table) {
+						continue;
+					}
 
-static int pbx_builtin_noop(struct ast_channel *chan, const char *data)
-{
-	return 0;
-}
+					prio_iter = ast_hashtab_start_traversal(exten_item->peer_table);
+					while ((prio_item=ast_hashtab_next(prio_iter))) {
+						char extension[AST_MAX_EXTENSION];
+						char cidmatch[AST_MAX_EXTENSION];
+						if (!prio_item->registrar || strcmp(prio_item->registrar, registrar) != 0) {
+							continue;
+						}
+						ast_verb(3, "Remove %s/%s/%d, registrar=%s; con=%s(%p); con->root=%p\n",
+								 tmp->name, prio_item->exten, prio_item->priority, registrar, con? con->name : "<nil>", con, con? con->root_table: NULL);
+						ast_copy_string(extension, prio_item->exten, sizeof(extension));
+						if (prio_item->cidmatch) {
+							ast_copy_string(cidmatch, prio_item->cidmatch, sizeof(cidmatch));
+						}
+						end_traversal &= ast_context_remove_extension_callerid2(tmp, extension, prio_item->priority, cidmatch, prio_item->matchcid, NULL, 1);
+					}
+					/* Explanation:
+					 * ast_context_remove_extension_callerid2 will destroy the extension that it comes across. This
+					 * destruction includes destroying the exten's peer_table, which we are currently traversing. If
+					 * ast_context_remove_extension_callerid2 ever should return '0' then this means we have destroyed
+					 * the hashtable which we are traversing, and thus calling ast_hashtab_end_traversal will result
+					 * in reading invalid memory. Thus, if we detect that we destroyed the hashtable, then we will simply
+					 * free the iterator
+					 */
+					if (end_traversal) {
+						ast_hashtab_end_traversal(prio_iter);
+					} else {
+						ast_free(prio_iter);
+					}
+				}
+				ast_hashtab_end_traversal(exten_iter);
+			}
 
-void pbx_builtin_clear_globals(void)
-{
-	struct ast_var_t *vardata;
+			/* delete the context if it's registrar matches, is empty, has refcount of 1, */
+			/* it's not empty, if it has includes, ignorepats, or switches that are registered from
+			   another registrar. It's not empty if there are any extensions */
+			if (strcmp(tmp->registrar, registrar) == 0 && tmp->refcount < 2 && !tmp->root && !tmp->ignorepats && !tmp->includes && AST_LIST_EMPTY(&tmp->alts)) {
+				ast_debug(1, "delete ctx %s %s\n", tmp->name, tmp->registrar);
+				ast_hashtab_remove_this_object(contexttab, tmp);
+
+				next = tmp->next;
+				if (tmpl)
+					tmpl->next = next;
+				else
+					contexts = next;
+				/* Okay, now we're safe to let it go -- in a sense, we were
+				   ready to let it go as soon as we locked it. */
+				ast_unlock_context(tmp);
+				__ast_internal_context_destroy(tmp);
+			} else {
+				ast_debug(1,"Couldn't delete ctx %s/%s; refc=%d; tmp.root=%p\n", tmp->name, tmp->registrar,
+						  tmp->refcount, tmp->root);
+				ast_unlock_context(tmp);
+				next = tmp->next;
+				tmpl = tmp;
+			}
+		} else if (con) {
+			ast_verb(3, "Deleting context %s registrar=%s\n", tmp->name, tmp->registrar);
+			ast_debug(1, "delete ctx %s %s\n", tmp->name, tmp->registrar);
+			ast_hashtab_remove_this_object(contexttab, tmp);
 
-	ast_rwlock_wrlock(&globalslock);
-	while ((vardata = AST_LIST_REMOVE_HEAD(&globals, entries)))
-		ast_var_delete(vardata);
-	ast_rwlock_unlock(&globalslock);
-}
+			next = tmp->next;
+			if (tmpl)
+				tmpl->next = next;
+			else
+				contexts = next;
+			/* Okay, now we're safe to let it go -- in a sense, we were
+			   ready to let it go as soon as we locked it. */
+			ast_unlock_context(tmp);
+			__ast_internal_context_destroy(tmp);
+		}
 
-int pbx_checkcondition(const char *condition)
-{
-	int res;
-	if (ast_strlen_zero(condition)) {                /* NULL or empty strings are false */
-		return 0;
-	} else if (sscanf(condition, "%30d", &res) == 1) { /* Numbers are evaluated for truth */
-		return res;
-	} else {                                         /* Strings are true */
-		return 1;
+		/* if we have a specific match, we are done, otherwise continue */
+		tmp = con ? NULL : next;
 	}
 }
 
-static int pbx_builtin_gotoif(struct ast_channel *chan, const char *data)
+int ast_context_destroy_by_name(const char *context, const char *registrar)
 {
-	char *condition, *branch1, *branch2, *branch;
-	char *stringp;
-
-	if (ast_strlen_zero(data)) {
-		ast_log(LOG_WARNING, "Ignoring, since there is no variable to check\n");
-		return 0;
-	}
-
-	stringp = ast_strdupa(data);
-	condition = strsep(&stringp,"?");
-	branch1 = strsep(&stringp,":");
-	branch2 = strsep(&stringp,"");
-	branch = pbx_checkcondition(condition) ? branch1 : branch2;
+	struct ast_context *con;
+	int ret = -1;
 
-	if (ast_strlen_zero(branch)) {
-		ast_debug(1, "Not taking any branch\n");
-		return 0;
+	ast_wrlock_contexts();
+	con = ast_context_find(context);
+	if (con) {
+		ast_context_destroy(con, registrar);
+		ret = 0;
 	}
+	ast_unlock_contexts();
 
-	return pbx_builtin_goto(chan, branch);
+	return ret;
 }
 
-static int pbx_builtin_saynumber(struct ast_channel *chan, const char *data)
+void ast_context_destroy(struct ast_context *con, const char *registrar)
 {
-	char tmp[256];
-	char *number = tmp;
-	int number_val;
-	char *options;
-	int res;
-	int interrupt = 0;
-	const char *interrupt_string;
-
-	ast_channel_lock(chan);
-	interrupt_string = pbx_builtin_getvar_helper(chan, "SAY_DTMF_INTERRUPT");
-	if (ast_true(interrupt_string)) {
-		interrupt = 1;
-	}
-	ast_channel_unlock(chan);
-
-	if (ast_strlen_zero(data)) {
-		ast_log(LOG_WARNING, "SayNumber requires an argument (number)\n");
-		return -1;
-	}
-	ast_copy_string(tmp, data, sizeof(tmp));
-	strsep(&number, ",");
-
-	if (sscanf(tmp, "%d", &number_val) != 1) {
-		ast_log(LOG_WARNING, "argument '%s' to SayNumber could not be parsed as a number.\n", tmp);
-		return 0;
-	}
-
-	options = strsep(&number, ",");
-	if (options) {
-		if ( strcasecmp(options, "f") && strcasecmp(options, "m") &&
-			strcasecmp(options, "c") && strcasecmp(options, "n") ) {
-			ast_log(LOG_WARNING, "SayNumber gender option is either 'f', 'm', 'c' or 'n'\n");
-			return -1;
-		}
-	}
-
-	res = ast_say_number(chan, number_val, interrupt ? AST_DIGIT_ANY : "", ast_channel_language(chan), options);
-
-	if (res < 0) {
-		ast_log(LOG_WARNING, "We were unable to say the number %s, is it too large?\n", tmp);
-	}
-
-	return interrupt ? res : 0;
+	ast_wrlock_contexts();
+	__ast_context_destroy(contexts, contexts_table, con,registrar);
+	ast_unlock_contexts();
 }
 
-static int pbx_builtin_saydigits(struct ast_channel *chan, const char *data)
+void wait_for_hangup(struct ast_channel *chan, const void *data)
 {
-	int res = 0;
-	int interrupt = 0;
-	const char *interrupt_string;
-
-	ast_channel_lock(chan);
-	interrupt_string = pbx_builtin_getvar_helper(chan, "SAY_DTMF_INTERRUPT");
-	if (ast_true(interrupt_string)) {
-		interrupt = 1;
-	}
-	ast_channel_unlock(chan);
-
-	if (data) {
-		res = ast_say_digit_str(chan, data, interrupt ? AST_DIGIT_ANY : "", ast_channel_language(chan));
-	}
+	int res;
+	struct ast_frame *f;
+	double waitsec;
+	int waittime;
 
-	return res;
+	if (ast_strlen_zero(data) || (sscanf(data, "%30lg", &waitsec) != 1) || (waitsec < 0))
+		waitsec = -1;
+	if (waitsec > -1) {
+		waittime = waitsec * 1000.0;
+		ast_safe_sleep(chan, waittime);
+	} else do {
+		res = ast_waitfor(chan, -1);
+		if (res < 0)
+			return;
+		f = ast_read(chan);
+		if (f)
+			ast_frfree(f);
+	} while(f);
 }
 
-static int pbx_builtin_saycharacters_case(struct ast_channel *chan, const char *data)
+/*!
+ * \ingroup functions
+ */
+static int testtime_write(struct ast_channel *chan, const char *cmd, char *var, const char *value)
 {
-	int res = 0;
-	int sensitivity = 0;
-	char *parse;
-	int interrupt = 0;
-	const char *interrupt_string;
-
-	AST_DECLARE_APP_ARGS(args,
-		AST_APP_ARG(options);
-		AST_APP_ARG(characters);
-	);
-
-	ast_channel_lock(chan);
-	interrupt_string = pbx_builtin_getvar_helper(chan, "SAY_DTMF_INTERRUPT");
-	if (ast_true(interrupt_string)) {
-		interrupt = 1;
-	}
-	ast_channel_unlock(chan);
-
-	if (ast_strlen_zero(data)) {
-		ast_log(LOG_WARNING, "SayAlphaCase requires two arguments (options, characters)\n");
-		return 0;
-	}
-
-	parse = ast_strdupa(data);
-	AST_STANDARD_APP_ARGS(args, parse);
+	struct ast_tm tm;
+	struct timeval tv;
+	char *remainder, result[30], timezone[80];
 
-	if (!args.options || strlen(args.options) != 1) {
-		ast_log(LOG_WARNING, "SayAlphaCase options are mutually exclusive and required\n");
+	/* Turn off testing? */
+	if (!pbx_checkcondition(value)) {
+		pbx_builtin_setvar_helper(chan, "TESTTIME", NULL);
 		return 0;
 	}
 
-	switch (args.options[0]) {
-	case 'a':
-		sensitivity = AST_SAY_CASE_ALL;
-		break;
-	case 'l':
-		sensitivity = AST_SAY_CASE_LOWER;
-		break;
-	case 'n':
-		sensitivity = AST_SAY_CASE_NONE;
-		break;
-	case 'u':
-		sensitivity = AST_SAY_CASE_UPPER;
-		break;
-	default:
-		ast_log(LOG_WARNING, "Invalid option: '%s'\n", args.options);
-		return 0;
+	/* Parse specified time */
+	if (!(remainder = ast_strptime(value, "%Y/%m/%d %H:%M:%S", &tm))) {
+		return -1;
 	}
+	sscanf(remainder, "%79s", timezone);
+	tv = ast_mktime(&tm, S_OR(timezone, NULL));
 
-	res = ast_say_character_str(chan, args.characters, interrupt ? AST_DIGIT_ANY : "", ast_channel_language(chan), sensitivity);
-
-	return res;
+	snprintf(result, sizeof(result), "%ld", (long) tv.tv_sec);
+	pbx_builtin_setvar_helper(chan, "__TESTTIME", result);
+	return 0;
 }
 
-static int pbx_builtin_saycharacters(struct ast_channel *chan, const char *data)
-{
-	int res = 0;
-	int interrupt = 0;
-	const char *interrupt_string;
-
-	ast_channel_lock(chan);
-	interrupt_string = pbx_builtin_getvar_helper(chan, "SAY_DTMF_INTERRUPT");
-	if (ast_true(interrupt_string)) {
-		interrupt = 1;
-	}
-	ast_channel_unlock(chan);
-
-	if (data) {
-		res = ast_say_character_str(chan, data, interrupt ? AST_DIGIT_ANY : "", ast_channel_language(chan), AST_SAY_CASE_NONE);
-	}
-
-	return res;
-}
+static struct ast_custom_function testtime_function = {
+	.name = "TESTTIME",
+	.write = testtime_write,
+};
 
-static int pbx_builtin_sayphonetic(struct ast_channel *chan, const char *data)
+int pbx_checkcondition(const char *condition)
 {
-	int res = 0;
-	int interrupt = 0;
-	const char *interrupt_string;
-
-	ast_channel_lock(chan);
-	interrupt_string = pbx_builtin_getvar_helper(chan, "SAY_DTMF_INTERRUPT");
-	if (ast_true(interrupt_string)) {
-		interrupt = 1;
+	int res;
+	if (ast_strlen_zero(condition)) {                /* NULL or empty strings are false */
+		return 0;
+	} else if (sscanf(condition, "%30d", &res) == 1) { /* Numbers are evaluated for truth */
+		return res;
+	} else {                                         /* Strings are true */
+		return 1;
 	}
-	ast_channel_unlock(chan);
-
-	if (data)
-		res = ast_say_phonetic_str(chan, data, interrupt ? AST_DIGIT_ANY : "", ast_channel_language(chan));
-	return res;
 }
 
 static void presence_state_cb(void *unused, struct stasis_subscription *sub, struct stasis_message *msg)
 {
-	struct ast_presence_state_message *presence_state = stasis_message_data(msg);
 	struct ast_hint *hint;
 	struct ast_str *hint_app = NULL;
 	struct ao2_iterator hint_iter;
-	struct ao2_iterator cb_iter;
-	char context_name[AST_MAX_CONTEXT];
-	char exten_name[AST_MAX_EXTENSION];
+
+	if (handle_hint_change_message_type(msg, AST_HINT_UPDATE_PRESENCE)) {
+		return;
+	}
 
 	if (stasis_message_type(msg) != ast_presence_state_message_type()) {
 		return;
@@ -12104,98 +8017,7 @@ static void presence_state_cb(void *unused, struct stasis_subscription *sub, str
 	ast_mutex_lock(&context_merge_lock);/* Hold off ast_merge_contexts_and_delete */
 	hint_iter = ao2_iterator_init(hints, 0);
 	for (; (hint = ao2_iterator_next(&hint_iter)); ao2_cleanup(hint)) {
-		struct ast_state_cb *state_cb;
-		const char *app;
-		char *parse;
-		ao2_lock(hint);
-
-		if (!hint->exten) {
-			/* The extension has already been destroyed */
-			ao2_unlock(hint);
-			continue;
-		}
-
-		/* Does this hint monitor the device that changed state? */
-		app = ast_get_extension_app(hint->exten);
-		if (ast_strlen_zero(app)) {
-			/* The hint does not monitor presence at all. */
-			ao2_unlock(hint);
-			continue;
-		}
-
-		ast_str_set(&hint_app, 0, "%s", app);
-		parse = parse_hint_presence(hint_app);
-		if (ast_strlen_zero(parse)) {
-			ao2_unlock(hint);
-			continue;
-		}
-		if (strcasecmp(parse, presence_state->provider)) {
-			/* The hint does not monitor the presence provider. */
-			ao2_unlock(hint);
-			continue;
-		}
-
-		/*
-		 * Save off strings in case the hint extension gets destroyed
-		 * while we are notifying the watchers.
-		 */
-		ast_copy_string(context_name,
-			ast_get_context_name(ast_get_extension_context(hint->exten)),
-			sizeof(context_name));
-		ast_copy_string(exten_name, ast_get_extension_name(hint->exten),
-			sizeof(exten_name));
-		ast_str_set(&hint_app, 0, "%s", ast_get_extension_app(hint->exten));
-
-		/* Check to see if update is necessary */
-		if ((hint->last_presence_state == presence_state->state) &&
-			((hint->last_presence_subtype && presence_state->subtype && !strcmp(hint->last_presence_subtype, presence_state->subtype)) || (!hint->last_presence_subtype && !presence_state->subtype)) &&
-			((hint->last_presence_message && presence_state->message && !strcmp(hint->last_presence_message, presence_state->message)) || (!hint->last_presence_message && !presence_state->message))) {
-
-			/* this update is the same as the last, do nothing */
-			ao2_unlock(hint);
-			continue;
-		}
-
-		/* update new values */
-		ast_free(hint->last_presence_subtype);
-		ast_free(hint->last_presence_message);
-		hint->last_presence_state = presence_state->state;
-		hint->last_presence_subtype = presence_state->subtype ? ast_strdup(presence_state->subtype) : NULL;
-		hint->last_presence_message = presence_state->message ? ast_strdup(presence_state->message) : NULL;
-		/*
-		 * (Copied from device_state_cb)
-		 *
-		 * NOTE: We cannot hold any locks while notifying
-		 * the watchers without causing a deadlock.
-		 * (conlock, hints, and hint)
-		 */
-		ao2_unlock(hint);
-
-		/* For general callbacks */
-		cb_iter = ao2_iterator_init(statecbs, 0);
-		for (; (state_cb = ao2_iterator_next(&cb_iter)); ao2_ref(state_cb, -1)) {
-			execute_state_callback(state_cb->change_cb,
-				context_name,
-				exten_name,
-				state_cb->data,
-				AST_HINT_UPDATE_PRESENCE,
-				hint,
-				NULL);
-		}
-		ao2_iterator_destroy(&cb_iter);
-
-		/* For extension callbacks */
-		cb_iter = ao2_iterator_init(hint->callbacks, 0);
-		for (; (state_cb = ao2_iterator_next(&cb_iter)); ao2_cleanup(state_cb)) {
-			execute_state_callback(state_cb->change_cb,
-				context_name,
-				exten_name,
-				state_cb->data,
-				AST_HINT_UPDATE_PRESENCE,
-				hint,
-				NULL);
-		}
-		ao2_iterator_destroy(&cb_iter);
+		presence_state_notify_callbacks(msg, hint, &hint_app, stasis_message_data(msg));
 	}
 	ao2_iterator_destroy(&hint_iter);
 	ast_mutex_unlock(&context_merge_lock);
@@ -12320,15 +8142,9 @@ static int action_extensionstatelist(struct mansession *s, const struct message
  */
 static void unload_pbx(void)
 {
-	int x;
-
 	presence_state_sub = stasis_unsubscribe_and_join(presence_state_sub);
 	device_state_sub = stasis_unsubscribe_and_join(device_state_sub);
 
-	/* Unregister builtin applications */
-	for (x = 0; x < ARRAY_LEN(builtins); x++) {
-		ast_unregister_application(builtins[x].name);
-	}
 	ast_manager_unregister("ShowDialPlan");
 	ast_manager_unregister("ExtensionStateList");
 	ast_cli_unregister_multiple(pbx_cli, ARRAY_LEN(pbx_cli));
@@ -12340,27 +8156,18 @@ static void unload_pbx(void)
 int load_pbx(void)
 {
 	int res = 0;
-	int x;
 
 	ast_register_cleanup(unload_pbx);
 
 	/* Initialize the PBX */
 	ast_verb(1, "Asterisk PBX Core Initializing\n");
 
-	ast_verb(2, "Registering builtin applications and functions:\n");
+	ast_verb(2, "Registering builtin functions:\n");
 	ast_cli_register_multiple(pbx_cli, ARRAY_LEN(pbx_cli));
 	ast_data_register_multiple_core(pbx_data_providers, ARRAY_LEN(pbx_data_providers));
 	__ast_custom_function_register(&exception_function, NULL);
 	__ast_custom_function_register(&testtime_function, NULL);
 
-	/* Register builtin applications */
-	for (x = 0; x < ARRAY_LEN(builtins); x++) {
-		if (ast_register_application2(builtins[x].name, builtins[x].execute, NULL, NULL, NULL)) {
-			ast_log(LOG_ERROR, "Unable to register builtin application '%s'\n", builtins[x].name);
-			return -1;
-		}
-	}
-
 	/* Register manager application */
 	res |= ast_manager_register_xml_core("ShowDialPlan", EVENT_FLAG_CONFIG | EVENT_FLAG_REPORTING, manager_show_dialplan);
 	res |= ast_manager_register_xml_core("ExtensionStateList", EVENT_FLAG_CALL | EVENT_FLAG_REPORTING, action_extensionstatelist);
@@ -12682,37 +8489,6 @@ int ast_async_parseable_goto(struct ast_channel *chan, const char *goto_string)
 	return pbx_parseable_goto(chan, goto_string, 1);
 }
 
-char *ast_complete_applications(const char *line, const char *word, int state)
-{
-	struct ast_app *app;
-	int which = 0;
-	int cmp;
-	char *ret = NULL;
-	size_t wordlen = strlen(word);
-
-	AST_RWLIST_RDLOCK(&apps);
-	AST_RWLIST_TRAVERSE(&apps, app, list) {
-		cmp = strncasecmp(word, app->name, wordlen);
-		if (cmp > 0) {
-			continue;
-		}
-		if (!cmp) {
-			/* Found match. */
-			if (++which <= state) {
-				/* Not enough matches. */
-				continue;
-			}
-			ret = ast_strdup(app->name);
-			break;
-		}
-		/* Not in container. */
-		break;
-	}
-	AST_RWLIST_UNLOCK(&apps);
-
-	return ret;
-}
-
 static int hint_hash(const void *obj, const int flags)
 {
 	const struct ast_hint *hint = obj;
@@ -12755,6 +8531,8 @@ static int statecbs_cmp(void *obj, void *arg, int flags)
  */
 static void pbx_shutdown(void)
 {
+	STASIS_MESSAGE_TYPE_CLEANUP(hint_change_message_type);
+
 	if (hints) {
 		ao2_container_unregister("hints");
 		ao2_ref(hints, -1);
@@ -12773,7 +8551,6 @@ static void pbx_shutdown(void)
 	if (contexts_table) {
 		ast_hashtab_destroy(contexts_table, NULL);
 	}
-	pbx_builtin_clear_globals();
 }
 
 static void print_hints_key(void *v_obj, void *where, ao2_prnt_fn *prnt)
@@ -12826,5 +8603,9 @@ int ast_pbx_init(void)
 
 	ast_register_cleanup(pbx_shutdown);
 
+	if (STASIS_MESSAGE_TYPE_INIT(hint_change_message_type) != 0) {
+		return -1;
+	}
+
 	return (hints && hintdevices && statecbs) ? 0 : -1;
 }
diff --git a/main/pbx_app.c b/main/pbx_app.c
new file mode 100644
index 0000000..bb60b8e
--- /dev/null
+++ b/main/pbx_app.c
@@ -0,0 +1,510 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2016, CFWare, LLC
+ *
+ * Corey Farrell <git at cfware.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 Custom function management routines.
+ *
+ * \author Corey Farrell <git at cfware.com>
+ */
+
+/*** MODULEINFO
+	<support_level>core</support_level>
+ ***/
+
+#include "asterisk.h"
+
+ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
+
+#include "asterisk/_private.h"
+#include "asterisk/cli.h"
+#include "asterisk/linkedlists.h"
+#include "asterisk/module.h"
+#include "asterisk/pbx.h"
+#include "asterisk/stasis_channels.h"
+#include "asterisk/strings.h"
+#include "asterisk/term.h"
+#include "asterisk/utils.h"
+#include "asterisk/xmldoc.h"
+#include "pbx_private.h"
+
+/*! \brief ast_app: A registered application */
+struct ast_app {
+	int (*execute)(struct ast_channel *chan, const char *data);
+	AST_DECLARE_STRING_FIELDS(
+		AST_STRING_FIELD(synopsis);     /*!< Synopsis text for 'show applications' */
+		AST_STRING_FIELD(description);  /*!< Description (help text) for 'show application <name>' */
+		AST_STRING_FIELD(syntax);       /*!< Syntax text for 'core show applications' */
+		AST_STRING_FIELD(arguments);    /*!< Arguments description */
+		AST_STRING_FIELD(seealso);      /*!< See also */
+	);
+#ifdef AST_XML_DOCS
+	enum ast_doc_src docsrc;		/*!< Where the documentation come from. */
+#endif
+	AST_RWLIST_ENTRY(ast_app) list;		/*!< Next app in list */
+	struct ast_module *module;		/*!< Module this app belongs to */
+	char name[0];				/*!< Name of the application */
+};
+
+/*!
+ * \brief Registered applications container.
+ *
+ * It is sorted by application name.
+ */
+static AST_RWLIST_HEAD_STATIC(apps, ast_app);
+
+static struct ast_app *pbx_findapp_nolock(const char *name)
+{
+	struct ast_app *cur;
+	int cmp;
+
+	AST_RWLIST_TRAVERSE(&apps, cur, list) {
+		cmp = strcasecmp(name, cur->name);
+		if (cmp > 0) {
+			continue;
+		}
+		if (!cmp) {
+			/* Found it. */
+			break;
+		}
+		/* Not in container. */
+		cur = NULL;
+		break;
+	}
+
+	return cur;
+}
+
+struct ast_app *pbx_findapp(const char *app)
+{
+	struct ast_app *ret;
+
+	AST_RWLIST_RDLOCK(&apps);
+	ret = pbx_findapp_nolock(app);
+	AST_RWLIST_UNLOCK(&apps);
+
+	return ret;
+}
+
+/*! \brief Dynamically register a new dial plan application */
+int ast_register_application2(const char *app, int (*execute)(struct ast_channel *, const char *), const char *synopsis, const char *description, void *mod)
+{
+	struct ast_app *tmp;
+	struct ast_app *cur;
+	int length;
+#ifdef AST_XML_DOCS
+	char *tmpxml;
+#endif
+
+	AST_RWLIST_WRLOCK(&apps);
+	cur = pbx_findapp_nolock(app);
+	if (cur) {
+		ast_log(LOG_WARNING, "Already have an application '%s'\n", app);
+		AST_RWLIST_UNLOCK(&apps);
+		return -1;
+	}
+
+	length = sizeof(*tmp) + strlen(app) + 1;
+
+	if (!(tmp = ast_calloc(1, length))) {
+		AST_RWLIST_UNLOCK(&apps);
+		return -1;
+	}
+
+	if (ast_string_field_init(tmp, 128)) {
+		AST_RWLIST_UNLOCK(&apps);
+		ast_free(tmp);
+		return -1;
+	}
+
+	strcpy(tmp->name, app);
+	tmp->execute = execute;
+	tmp->module = mod;
+
+#ifdef AST_XML_DOCS
+	/* Try to lookup the docs in our XML documentation database */
+	if (ast_strlen_zero(synopsis) && ast_strlen_zero(description)) {
+		/* load synopsis */
+		tmpxml = ast_xmldoc_build_synopsis("application", app, ast_module_name(tmp->module));
+		ast_string_field_set(tmp, synopsis, tmpxml);
+		ast_free(tmpxml);
+
+		/* load description */
+		tmpxml = ast_xmldoc_build_description("application", app, ast_module_name(tmp->module));
+		ast_string_field_set(tmp, description, tmpxml);
+		ast_free(tmpxml);
+
+		/* load syntax */
+		tmpxml = ast_xmldoc_build_syntax("application", app, ast_module_name(tmp->module));
+		ast_string_field_set(tmp, syntax, tmpxml);
+		ast_free(tmpxml);
+
+		/* load arguments */
+		tmpxml = ast_xmldoc_build_arguments("application", app, ast_module_name(tmp->module));
+		ast_string_field_set(tmp, arguments, tmpxml);
+		ast_free(tmpxml);
+
+		/* load seealso */
+		tmpxml = ast_xmldoc_build_seealso("application", app, ast_module_name(tmp->module));
+		ast_string_field_set(tmp, seealso, tmpxml);
+		ast_free(tmpxml);
+		tmp->docsrc = AST_XML_DOC;
+	} else {
+#endif
+		ast_string_field_set(tmp, synopsis, synopsis);
+		ast_string_field_set(tmp, description, description);
+#ifdef AST_XML_DOCS
+		tmp->docsrc = AST_STATIC_DOC;
+	}
+#endif
+
+	/* Store in alphabetical order */
+	AST_RWLIST_TRAVERSE_SAFE_BEGIN(&apps, cur, list) {
+		if (strcasecmp(tmp->name, cur->name) < 0) {
+			AST_RWLIST_INSERT_BEFORE_CURRENT(tmp, list);
+			break;
+		}
+	}
+	AST_RWLIST_TRAVERSE_SAFE_END;
+	if (!cur)
+		AST_RWLIST_INSERT_TAIL(&apps, tmp, list);
+
+	ast_verb(2, "Registered application '" COLORIZE_FMT "'\n", COLORIZE(COLOR_BRCYAN, 0, tmp->name));
+
+	AST_RWLIST_UNLOCK(&apps);
+
+	return 0;
+}
+
+static void print_app_docs(struct ast_app *aa, int fd)
+{
+#ifdef AST_XML_DOCS
+	char *synopsis = NULL, *description = NULL, *arguments = NULL, *seealso = NULL;
+	if (aa->docsrc == AST_XML_DOC) {
+		synopsis = ast_xmldoc_printable(S_OR(aa->synopsis, "Not available"), 1);
+		description = ast_xmldoc_printable(S_OR(aa->description, "Not available"), 1);
+		arguments = ast_xmldoc_printable(S_OR(aa->arguments, "Not available"), 1);
+		seealso = ast_xmldoc_printable(S_OR(aa->seealso, "Not available"), 1);
+		if (!synopsis || !description || !arguments || !seealso) {
+			goto free_docs;
+		}
+		ast_cli(fd, "\n"
+			"%s  -= Info about application '%s' =- %s\n\n"
+			COLORIZE_FMT "\n"
+			"%s\n\n"
+			COLORIZE_FMT "\n"
+			"%s\n\n"
+			COLORIZE_FMT "\n"
+			"%s%s%s\n\n"
+			COLORIZE_FMT "\n"
+			"%s\n\n"
+			COLORIZE_FMT "\n"
+			"%s\n",
+			ast_term_color(COLOR_MAGENTA, 0), aa->name, ast_term_reset(),
+			COLORIZE(COLOR_MAGENTA, 0, "[Synopsis]"), synopsis,
+			COLORIZE(COLOR_MAGENTA, 0, "[Description]"), description,
+			COLORIZE(COLOR_MAGENTA, 0, "[Syntax]"),
+				ast_term_color(COLOR_CYAN, 0), S_OR(aa->syntax, "Not available"), ast_term_reset(),
+			COLORIZE(COLOR_MAGENTA, 0, "[Arguments]"), arguments,
+			COLORIZE(COLOR_MAGENTA, 0, "[See Also]"), seealso);
+free_docs:
+		ast_free(synopsis);
+		ast_free(description);
+		ast_free(arguments);
+		ast_free(seealso);
+	} else
+#endif
+	{
+		ast_cli(fd, "\n"
+			"%s  -= Info about application '%s' =- %s\n\n"
+			COLORIZE_FMT "\n"
+			COLORIZE_FMT "\n\n"
+			COLORIZE_FMT "\n"
+			COLORIZE_FMT "\n\n"
+			COLORIZE_FMT "\n"
+			COLORIZE_FMT "\n\n"
+			COLORIZE_FMT "\n"
+			COLORIZE_FMT "\n\n"
+			COLORIZE_FMT "\n"
+			COLORIZE_FMT "\n",
+			ast_term_color(COLOR_MAGENTA, 0), aa->name, ast_term_reset(),
+			COLORIZE(COLOR_MAGENTA, 0, "[Synopsis]"),
+			COLORIZE(COLOR_CYAN, 0, S_OR(aa->synopsis, "Not available")),
+			COLORIZE(COLOR_MAGENTA, 0, "[Description]"),
+			COLORIZE(COLOR_CYAN, 0, S_OR(aa->description, "Not available")),
+			COLORIZE(COLOR_MAGENTA, 0, "[Syntax]"),
+			COLORIZE(COLOR_CYAN, 0, S_OR(aa->syntax, "Not available")),
+			COLORIZE(COLOR_MAGENTA, 0, "[Arguments]"),
+			COLORIZE(COLOR_CYAN, 0, S_OR(aa->arguments, "Not available")),
+			COLORIZE(COLOR_MAGENTA, 0, "[See Also]"),
+			COLORIZE(COLOR_CYAN, 0, S_OR(aa->seealso, "Not available")));
+	}
+}
+
+/*
+ * \brief 'show application' CLI command implementation function...
+ */
+static char *handle_show_application(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+	struct ast_app *aa;
+	int app, no_registered_app = 1;
+
+	switch (cmd) {
+	case CLI_INIT:
+		e->command = "core show application";
+		e->usage =
+			"Usage: core show application <application> [<application> [<application> [...]]]\n"
+			"       Describes a particular application.\n";
+		return NULL;
+	case CLI_GENERATE:
+		/*
+		 * There is a possibility to show informations about more than one
+		 * application at one time. You can type 'show application Dial Echo' and
+		 * you will see informations about these two applications ...
+		 */
+		return ast_complete_applications(a->line, a->word, a->n);
+	}
+
+	if (a->argc < 4) {
+		return CLI_SHOWUSAGE;
+	}
+
+	AST_RWLIST_RDLOCK(&apps);
+	AST_RWLIST_TRAVERSE(&apps, aa, list) {
+		/* Check for each app that was supplied as an argument */
+		for (app = 3; app < a->argc; app++) {
+			if (strcasecmp(aa->name, a->argv[app])) {
+				continue;
+			}
+
+			/* We found it! */
+			no_registered_app = 0;
+
+			print_app_docs(aa, a->fd);
+		}
+	}
+	AST_RWLIST_UNLOCK(&apps);
+
+	/* we found at least one app? no? */
+	if (no_registered_app) {
+		ast_cli(a->fd, "Your application(s) is (are) not registered\n");
+		return CLI_FAILURE;
+	}
+
+	return CLI_SUCCESS;
+}
+
+static char *handle_show_applications(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+	struct ast_app *aa;
+	int like = 0, describing = 0;
+	int total_match = 0;    /* Number of matches in like clause */
+	int total_apps = 0;     /* Number of apps registered */
+	static const char * const choices[] = { "like", "describing", NULL };
+
+	switch (cmd) {
+	case CLI_INIT:
+		e->command = "core show applications [like|describing]";
+		e->usage =
+			"Usage: core show applications [{like|describing} <text>]\n"
+			"       List applications which are currently available.\n"
+			"       If 'like', <text> will be a substring of the app name\n"
+			"       If 'describing', <text> will be a substring of the description\n";
+		return NULL;
+	case CLI_GENERATE:
+		return (a->pos != 3) ? NULL : ast_cli_complete(a->word, choices, a->n);
+	}
+
+	AST_RWLIST_RDLOCK(&apps);
+
+	if (AST_RWLIST_EMPTY(&apps)) {
+		ast_cli(a->fd, "There are no registered applications\n");
+		AST_RWLIST_UNLOCK(&apps);
+		return CLI_SUCCESS;
+	}
+
+	/* core list applications like <keyword> */
+	if ((a->argc == 5) && (!strcmp(a->argv[3], "like"))) {
+		like = 1;
+	} else if ((a->argc > 4) && (!strcmp(a->argv[3], "describing"))) {
+		describing = 1;
+	}
+
+	/* core list applications describing <keyword1> [<keyword2>] [...] */
+	if ((!like) && (!describing)) {
+		ast_cli(a->fd, "    -= Registered Asterisk Applications =-\n");
+	} else {
+		ast_cli(a->fd, "    -= Matching Asterisk Applications =-\n");
+	}
+
+	AST_RWLIST_TRAVERSE(&apps, aa, list) {
+		int printapp = 0;
+		total_apps++;
+		if (like) {
+			if (strcasestr(aa->name, a->argv[4])) {
+				printapp = 1;
+				total_match++;
+			}
+		} else if (describing) {
+			if (aa->description) {
+				/* Match all words on command line */
+				int i;
+				printapp = 1;
+				for (i = 4; i < a->argc; i++) {
+					if (!strcasestr(aa->description, a->argv[i])) {
+						printapp = 0;
+					} else {
+						total_match++;
+					}
+				}
+			}
+		} else {
+			printapp = 1;
+		}
+
+		if (printapp) {
+			ast_cli(a->fd,"  %20s: %s\n", aa->name, aa->synopsis ? aa->synopsis : "<Synopsis not available>");
+		}
+	}
+	if ((!like) && (!describing)) {
+		ast_cli(a->fd, "    -= %d Applications Registered =-\n",total_apps);
+	} else {
+		ast_cli(a->fd, "    -= %d Applications Matching =-\n",total_match);
+	}
+
+	AST_RWLIST_UNLOCK(&apps);
+
+	return CLI_SUCCESS;
+}
+
+int ast_unregister_application(const char *app)
+{
+	struct ast_app *cur;
+	int cmp;
+
+	AST_RWLIST_WRLOCK(&apps);
+	AST_RWLIST_TRAVERSE_SAFE_BEGIN(&apps, cur, list) {
+		cmp = strcasecmp(app, cur->name);
+		if (cmp > 0) {
+			continue;
+		}
+		if (!cmp) {
+			/* Found it. */
+			unreference_cached_app(cur);
+			AST_RWLIST_REMOVE_CURRENT(list);
+			ast_verb(2, "Unregistered application '%s'\n", cur->name);
+			ast_string_field_free_memory(cur);
+			ast_free(cur);
+			break;
+		}
+		/* Not in container. */
+		cur = NULL;
+		break;
+	}
+	AST_RWLIST_TRAVERSE_SAFE_END;
+	AST_RWLIST_UNLOCK(&apps);
+
+	return cur ? 0 : -1;
+}
+
+char *ast_complete_applications(const char *line, const char *word, int state)
+{
+	struct ast_app *app;
+	int which = 0;
+	int cmp;
+	char *ret = NULL;
+	size_t wordlen = strlen(word);
+
+	AST_RWLIST_RDLOCK(&apps);
+	AST_RWLIST_TRAVERSE(&apps, app, list) {
+		cmp = strncasecmp(word, app->name, wordlen);
+		if (cmp > 0) {
+			continue;
+		}
+		if (!cmp) {
+			/* Found match. */
+			if (++which <= state) {
+				/* Not enough matches. */
+				continue;
+			}
+			ret = ast_strdup(app->name);
+			break;
+		}
+		/* Not in container. */
+		break;
+	}
+	AST_RWLIST_UNLOCK(&apps);
+
+	return ret;
+}
+
+const char *app_name(struct ast_app *app)
+{
+	return app->name;
+}
+
+/*
+   \note This function is special. It saves the stack so that no matter
+   how many times it is called, it returns to the same place */
+int pbx_exec(struct ast_channel *c,	/*!< Channel */
+	     struct ast_app *app,	/*!< Application */
+	     const char *data)		/*!< Data for execution */
+{
+	int res;
+	struct ast_module_user *u = NULL;
+	const char *saved_c_appl;
+	const char *saved_c_data;
+
+	/* save channel values */
+	saved_c_appl= ast_channel_appl(c);
+	saved_c_data= ast_channel_data(c);
+
+	ast_channel_lock(c);
+	ast_channel_appl_set(c, app->name);
+	ast_channel_data_set(c, data);
+	ast_channel_publish_snapshot(c);
+	ast_channel_unlock(c);
+
+	if (app->module)
+		u = __ast_module_user_add(app->module, c);
+	res = app->execute(c, S_OR(data, ""));
+	if (app->module && u)
+		__ast_module_user_remove(app->module, u);
+	/* restore channel values */
+	ast_channel_appl_set(c, saved_c_appl);
+	ast_channel_data_set(c, saved_c_data);
+	return res;
+}
+
+static struct ast_cli_entry app_cli[] = {
+	AST_CLI_DEFINE(handle_show_applications, "Shows registered dialplan applications"),
+	AST_CLI_DEFINE(handle_show_application, "Describe a specific dialplan application"),
+};
+
+static void unload_pbx_app(void)
+{
+	ast_cli_unregister_multiple(app_cli, ARRAY_LEN(app_cli));
+}
+
+int load_pbx_app(void)
+{
+	ast_cli_register_multiple(app_cli, ARRAY_LEN(app_cli));
+	ast_register_cleanup(unload_pbx_app);
+
+	return 0;
+}
diff --git a/main/pbx_builtins.c b/main/pbx_builtins.c
new file mode 100644
index 0000000..fa15588
--- /dev/null
+++ b/main/pbx_builtins.c
@@ -0,0 +1,1438 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2015 Fairview 5 Engineering, LLC
+ *
+ * George Joseph <george.joseph at fairview5.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 Core PBX builtin routines.
+ *
+ * \author George Joseph <george.joseph at fairview5.com>
+ */
+
+/*** MODULEINFO
+	<support_level>core</support_level>
+ ***/
+
+#include "asterisk.h"
+
+ASTERISK_REGISTER_FILE()
+
+#include "asterisk/_private.h"
+#include "asterisk/pbx.h"
+#include "asterisk/causes.h"
+#include "asterisk/indications.h"
+#include "asterisk/stasis_channels.h"
+#include "asterisk/say.h"
+#include "asterisk/app.h"
+#include "asterisk/module.h"
+#include "pbx_private.h"
+
+/*** DOCUMENTATION
+	<application name="Answer" language="en_US">
+		<synopsis>
+			Answer a channel if ringing.
+		</synopsis>
+		<syntax>
+			<parameter name="delay">
+				<para>Asterisk will wait this number of milliseconds before returning to
+				the dialplan after answering the call.</para>
+			</parameter>
+		</syntax>
+		<description>
+			<para>If the call has not been answered, this application will
+			answer it. Otherwise, it has no effect on the call.</para>
+		</description>
+		<see-also>
+			<ref type="application">Hangup</ref>
+		</see-also>
+	</application>
+	<application name="BackGround" language="en_US">
+		<synopsis>
+			Play an audio file while waiting for digits of an extension to go to.
+		</synopsis>
+		<syntax>
+			<parameter name="filenames" required="true" argsep="&">
+				<argument name="filename1" required="true" />
+				<argument name="filename2" multiple="true" />
+			</parameter>
+			<parameter name="options">
+				<optionlist>
+					<option name="s">
+						<para>Causes the playback of the message to be skipped
+						if the channel is not in the <literal>up</literal> state (i.e. it
+						hasn't been answered yet). If this happens, the
+						application will return immediately.</para>
+					</option>
+					<option name="n">
+						<para>Don't answer the channel before playing the files.</para>
+					</option>
+					<option name="m">
+						<para>Only break if a digit hit matches a one digit
+						extension in the destination context.</para>
+					</option>
+				</optionlist>
+			</parameter>
+			<parameter name="langoverride">
+				<para>Explicitly specifies which language to attempt to use for the requested sound files.</para>
+			</parameter>
+			<parameter name="context">
+				<para>This is the dialplan context that this application will use when exiting
+				to a dialed extension.</para>
+			</parameter>
+		</syntax>
+		<description>
+			<para>This application will play the given list of files <emphasis>(do not put extension)</emphasis>
+			while waiting for an extension to be dialed by the calling channel. To continue waiting
+			for digits after this application has finished playing files, the <literal>WaitExten</literal>
+			application should be used.</para>
+			<para>If one of the requested sound files does not exist, call processing will be terminated.</para>
+			<para>This application sets the following channel variable upon completion:</para>
+			<variablelist>
+				<variable name="BACKGROUNDSTATUS">
+					<para>The status of the background attempt as a text string.</para>
+					<value name="SUCCESS" />
+					<value name="FAILED" />
+				</variable>
+			</variablelist>
+		</description>
+		<see-also>
+			<ref type="application">ControlPlayback</ref>
+			<ref type="application">WaitExten</ref>
+			<ref type="application">BackgroundDetect</ref>
+			<ref type="function">TIMEOUT</ref>
+		</see-also>
+	</application>
+	<application name="Busy" language="en_US">
+		<synopsis>
+			Indicate the Busy condition.
+		</synopsis>
+		<syntax>
+			<parameter name="timeout">
+				<para>If specified, the calling channel will be hung up after the specified number of seconds.
+				Otherwise, this application will wait until the calling channel hangs up.</para>
+			</parameter>
+		</syntax>
+		<description>
+			<para>This application will indicate the busy condition to the calling channel.</para>
+		</description>
+		<see-also>
+			<ref type="application">Congestion</ref>
+			<ref type="application">Progress</ref>
+			<ref type="application">Playtones</ref>
+			<ref type="application">Hangup</ref>
+		</see-also>
+	</application>
+	<application name="Congestion" language="en_US">
+		<synopsis>
+			Indicate the Congestion condition.
+		</synopsis>
+		<syntax>
+			<parameter name="timeout">
+				<para>If specified, the calling channel will be hung up after the specified number of seconds.
+				Otherwise, this application will wait until the calling channel hangs up.</para>
+			</parameter>
+		</syntax>
+		<description>
+			<para>This application will indicate the congestion condition to the calling channel.</para>
+		</description>
+		<see-also>
+			<ref type="application">Busy</ref>
+			<ref type="application">Progress</ref>
+			<ref type="application">Playtones</ref>
+			<ref type="application">Hangup</ref>
+		</see-also>
+	</application>
+	<application name="ExecIfTime" language="en_US">
+		<synopsis>
+			Conditional application execution based on the current time.
+		</synopsis>
+		<syntax argsep="?">
+			<parameter name="day_condition" required="true">
+				<argument name="times" required="true" />
+				<argument name="weekdays" required="true" />
+				<argument name="mdays" required="true" />
+				<argument name="months" required="true" />
+				<argument name="timezone" required="false" />
+			</parameter>
+			<parameter name="appname" required="true" hasparams="optional">
+				<argument name="appargs" required="true" />
+			</parameter>
+		</syntax>
+		<description>
+			<para>This application will execute the specified dialplan application, with optional
+			arguments, if the current time matches the given time specification.</para>
+		</description>
+		<see-also>
+			<ref type="application">Exec</ref>
+			<ref type="application">ExecIf</ref>
+			<ref type="application">TryExec</ref>
+			<ref type="application">GotoIfTime</ref>
+		</see-also>
+	</application>
+	<application name="Goto" language="en_US">
+		<synopsis>
+			Jump to a particular priority, extension, or context.
+		</synopsis>
+		<syntax>
+			<parameter name="context" />
+			<parameter name="extensions" />
+			<parameter name="priority" required="true" />
+		</syntax>
+		<description>
+			<para>This application will set the current context, extension, and priority in the channel structure.
+			After it completes, the pbx engine will continue dialplan execution at the specified location.
+			If no specific <replaceable>extension</replaceable>, or <replaceable>extension</replaceable> and
+			<replaceable>context</replaceable>, are specified, then this application will
+			just set the specified <replaceable>priority</replaceable> of the current extension.</para>
+			<para>At least a <replaceable>priority</replaceable> is required as an argument, or the goto will
+			return a <literal>-1</literal>,	and the channel and call will be terminated.</para>
+			<para>If the location that is put into the channel information is bogus, and asterisk cannot
+			find that location in the dialplan, then the execution engine will try to find and execute the code in
+			the <literal>i</literal> (invalid) extension in the current context. If that does not exist, it will try to execute the
+			<literal>h</literal> extension. If neither the <literal>h</literal> nor <literal>i</literal> extensions
+			have been defined, the channel is hung up, and the execution of instructions on the channel is terminated.
+			What this means is that, for example, you specify a context that does not exist, then
+			it will not be possible to find the <literal>h</literal> or <literal>i</literal> extensions,
+			and the call will terminate!</para>
+		</description>
+		<see-also>
+			<ref type="application">GotoIf</ref>
+			<ref type="application">GotoIfTime</ref>
+			<ref type="application">Gosub</ref>
+			<ref type="application">Macro</ref>
+		</see-also>
+	</application>
+	<application name="GotoIf" language="en_US">
+		<synopsis>
+			Conditional goto.
+		</synopsis>
+		<syntax argsep="?">
+			<parameter name="condition" required="true" />
+			<parameter name="destination" required="true" argsep=":">
+				<argument name="labeliftrue">
+					<para>Continue at <replaceable>labeliftrue</replaceable> if the condition is true.
+					Takes the form similar to Goto() of [[context,]extension,]priority.</para>
+				</argument>
+				<argument name="labeliffalse">
+					<para>Continue at <replaceable>labeliffalse</replaceable> if the condition is false.
+					Takes the form similar to Goto() of [[context,]extension,]priority.</para>
+				</argument>
+			</parameter>
+		</syntax>
+		<description>
+			<para>This application will set the current context, extension, and priority in the channel structure
+			based on the evaluation of the given condition. After this application completes, the
+			pbx engine will continue dialplan execution at the specified location in the dialplan.
+			The labels are specified with the same syntax as used within the Goto application.
+			If the label chosen by the condition is omitted, no jump is performed, and the execution passes to the
+			next instruction. If the target location is bogus, and does not exist, the execution engine will try
+			to find and execute the code in the <literal>i</literal> (invalid) extension in the current context.
+			If that does not exist, it will try to execute the <literal>h</literal> extension.
+			If neither the <literal>h</literal> nor <literal>i</literal> extensions have been defined,
+			the channel is hung up, and the execution of instructions on the channel is terminated.
+			Remember that this command can set the current context, and if the context specified
+			does not exist, then it will not be able to find any 'h' or 'i' extensions there, and
+			the channel and call will both be terminated!.</para>
+		</description>
+		<see-also>
+			<ref type="application">Goto</ref>
+			<ref type="application">GotoIfTime</ref>
+			<ref type="application">GosubIf</ref>
+			<ref type="application">MacroIf</ref>
+		</see-also>
+	</application>
+	<application name="GotoIfTime" language="en_US">
+		<synopsis>
+			Conditional Goto based on the current time.
+		</synopsis>
+		<syntax argsep="?">
+			<parameter name="condition" required="true">
+				<argument name="times" required="true" />
+				<argument name="weekdays" required="true" />
+				<argument name="mdays" required="true" />
+				<argument name="months" required="true" />
+				<argument name="timezone" required="false" />
+			</parameter>
+			<parameter name="destination" required="true" argsep=":">
+				<argument name="labeliftrue">
+					<para>Continue at <replaceable>labeliftrue</replaceable> if the condition is true.
+					Takes the form similar to Goto() of [[context,]extension,]priority.</para>
+				</argument>
+				<argument name="labeliffalse">
+					<para>Continue at <replaceable>labeliffalse</replaceable> if the condition is false.
+					Takes the form similar to Goto() of [[context,]extension,]priority.</para>
+				</argument>
+			</parameter>
+		</syntax>
+		<description>
+			<para>This application will set the context, extension, and priority in the channel structure
+			based on the evaluation of the given time specification. After this application completes,
+			the pbx engine will continue dialplan execution at the specified location in the dialplan.
+			If the current time is within the given time specification, the channel will continue at
+			<replaceable>labeliftrue</replaceable>. Otherwise the channel will continue at <replaceable>labeliffalse</replaceable>.
+			If the label chosen by the condition is omitted, no jump is performed, and execution passes to the next
+			instruction. If the target jump location is bogus, the same actions would be taken as for <literal>Goto</literal>.
+			Further information on the time specification can be found in examples
+			illustrating how to do time-based context includes in the dialplan.</para>
+		</description>
+		<see-also>
+			<ref type="application">GotoIf</ref>
+			<ref type="application">Goto</ref>
+			<ref type="function">IFTIME</ref>
+			<ref type="function">TESTTIME</ref>
+		</see-also>
+	</application>
+	<application name="ImportVar" language="en_US">
+		<synopsis>
+			Import a variable from a channel into a new variable.
+		</synopsis>
+		<syntax argsep="=">
+			<parameter name="newvar" required="true" />
+			<parameter name="vardata" required="true">
+				<argument name="channelname" required="true" />
+				<argument name="variable" required="true" />
+			</parameter>
+		</syntax>
+		<description>
+			<para>This application imports a <replaceable>variable</replaceable> from the specified
+			<replaceable>channel</replaceable> (as opposed to the current one) and stores it as a variable
+			(<replaceable>newvar</replaceable>) in the current channel (the channel that is calling this
+			application). Variables created by this application have the same inheritance properties as those
+			created with the <literal>Set</literal> application.</para>
+		</description>
+		<see-also>
+			<ref type="application">Set</ref>
+		</see-also>
+	</application>
+	<application name="Hangup" language="en_US">
+		<synopsis>
+			Hang up the calling channel.
+		</synopsis>
+		<syntax>
+			<parameter name="causecode">
+				<para>If a <replaceable>causecode</replaceable> is given the channel's
+				hangup cause will be set to the given value.</para>
+			</parameter>
+		</syntax>
+		<description>
+			<para>This application will hang up the calling channel.</para>
+		</description>
+		<see-also>
+			<ref type="application">Answer</ref>
+			<ref type="application">Busy</ref>
+			<ref type="application">Congestion</ref>
+		</see-also>
+	</application>
+	<application name="Incomplete" language="en_US">
+		<synopsis>
+			Returns AST_PBX_INCOMPLETE value.
+		</synopsis>
+		<syntax>
+			<parameter name="n">
+				<para>If specified, then Incomplete will not attempt to answer the channel first.</para>
+				<note><para>Most channel types need to be in Answer state in order to receive DTMF.</para></note>
+			</parameter>
+		</syntax>
+		<description>
+			<para>Signals the PBX routines that the previous matched extension is incomplete
+			and that further input should be allowed before matching can be considered
+			to be complete.  Can be used within a pattern match when certain criteria warrants
+			a longer match.</para>
+		</description>
+	</application>
+	<application name="NoOp" language="en_US">
+		<synopsis>
+			Do Nothing (No Operation).
+		</synopsis>
+		<syntax>
+			<parameter name="text">
+				<para>Any text provided can be viewed at the Asterisk CLI.</para>
+			</parameter>
+		</syntax>
+		<description>
+			<para>This application does nothing. However, it is useful for debugging purposes.</para>
+			<para>This method can be used to see the evaluations of variables or functions without having any effect.</para>
+		</description>
+		<see-also>
+			<ref type="application">Verbose</ref>
+			<ref type="application">Log</ref>
+		</see-also>
+	</application>
+	<application name="Proceeding" language="en_US">
+		<synopsis>
+			Indicate proceeding.
+		</synopsis>
+		<syntax />
+		<description>
+			<para>This application will request that a proceeding message be provided to the calling channel.</para>
+		</description>
+	</application>
+	<application name="Progress" language="en_US">
+		<synopsis>
+			Indicate progress.
+		</synopsis>
+		<syntax />
+		<description>
+			<para>This application will request that in-band progress information be provided to the calling channel.</para>
+		</description>
+		<see-also>
+			<ref type="application">Busy</ref>
+			<ref type="application">Congestion</ref>
+			<ref type="application">Ringing</ref>
+			<ref type="application">Playtones</ref>
+		</see-also>
+	</application>
+	<application name="RaiseException" language="en_US">
+		<synopsis>
+			Handle an exceptional condition.
+		</synopsis>
+		<syntax>
+			<parameter name="reason" required="true" />
+		</syntax>
+		<description>
+			<para>This application will jump to the <literal>e</literal> extension in the current context, setting the
+			dialplan function EXCEPTION(). If the <literal>e</literal> extension does not exist, the call will hangup.</para>
+		</description>
+		<see-also>
+			<ref type="function">Exception</ref>
+		</see-also>
+	</application>
+	<application name="Ringing" language="en_US">
+		<synopsis>
+			Indicate ringing tone.
+		</synopsis>
+		<syntax />
+		<description>
+			<para>This application will request that the channel indicate a ringing tone to the user.</para>
+		</description>
+		<see-also>
+			<ref type="application">Busy</ref>
+			<ref type="application">Congestion</ref>
+			<ref type="application">Progress</ref>
+			<ref type="application">Playtones</ref>
+		</see-also>
+	</application>
+	<application name="SayAlpha" language="en_US">
+		<synopsis>
+			Say Alpha.
+		</synopsis>
+		<syntax>
+			<parameter name="string" required="true" />
+		</syntax>
+		<description>
+			<para>This application will play the sounds that correspond to the letters
+			of the given <replaceable>string</replaceable>. If the channel variable
+			<variable>SAY_DTMF_INTERRUPT</variable> is set to 'true' (case insensitive),
+			then this application will react to DTMF in the	same way as
+			<literal>Background</literal>.</para>
+		</description>
+		<see-also>
+			<ref type="application">SayDigits</ref>
+			<ref type="application">SayNumber</ref>
+			<ref type="application">SayPhonetic</ref>
+			<ref type="function">CHANNEL</ref>
+		</see-also>
+	</application>
+	<application name="SayAlphaCase" language="en_US">
+		<synopsis>
+			Say Alpha.
+		</synopsis>
+		<syntax>
+			<parameter name="casetype" required="true" >
+				<enumlist>
+					<enum name="a">
+						<para>Case sensitive (all) pronunciation.
+						(Ex: SayAlphaCase(a,aBc); - lowercase a uppercase b lowercase c).</para>
+					</enum>
+					<enum name="l">
+						<para>Case sensitive (lower) pronunciation.
+						(Ex: SayAlphaCase(l,aBc); - lowercase a b lowercase c).</para>
+					</enum>
+					<enum name="n">
+						<para>Case insensitive pronunciation. Equivalent to SayAlpha.
+						(Ex: SayAlphaCase(n,aBc) - a b c).</para>
+					</enum>
+					<enum name="u">
+						<para>Case sensitive (upper) pronunciation.
+						(Ex: SayAlphaCase(u,aBc); - a uppercase b c).</para>
+					</enum>
+				</enumlist>
+			</parameter>
+			<parameter name="string" required="true" />
+		</syntax>
+		<description>
+			<para>This application will play the sounds that correspond to the letters of the
+			given <replaceable>string</replaceable>.  Optionally, a <replaceable>casetype</replaceable> may be
+			specified.  This will be used for case-insensitive or case-sensitive pronunciations. If the channel
+			variable <variable>SAY_DTMF_INTERRUPT</variable> is set to 'true' (case insensitive), then this
+			application will react to DTMF in the same way as <literal>Background</literal>.</para>
+		</description>
+		<see-also>
+			<ref type="application">SayDigits</ref>
+			<ref type="application">SayNumber</ref>
+			<ref type="application">SayPhonetic</ref>
+			<ref type="application">SayAlpha</ref>
+			<ref type="function">CHANNEL</ref>
+		</see-also>
+	</application>
+	<application name="SayDigits" language="en_US">
+		<synopsis>
+			Say Digits.
+		</synopsis>
+		<syntax>
+			<parameter name="digits" required="true" />
+		</syntax>
+		<description>
+			<para>This application will play the sounds that correspond to the digits of
+			the given number. This will use the language that is currently set for the channel.
+			If the channel variable <variable>SAY_DTMF_INTERRUPT</variable> is set to 'true'
+			(case insensitive), then this application will react to DTMF in the same way as
+			<literal>Background</literal>.</para>
+		</description>
+		<see-also>
+			<ref type="application">SayAlpha</ref>
+			<ref type="application">SayNumber</ref>
+			<ref type="application">SayPhonetic</ref>
+			<ref type="function">CHANNEL</ref>
+		</see-also>
+	</application>
+	<application name="SayNumber" language="en_US">
+		<synopsis>
+			Say Number.
+		</synopsis>
+		<syntax>
+			<parameter name="digits" required="true" />
+			<parameter name="gender" />
+		</syntax>
+		<description>
+			<para>This application will play the sounds that correspond to the given
+			<replaceable>digits</replaceable>. Optionally, a <replaceable>gender</replaceable> may be
+			specified. This will use the language that is currently set for the channel. See the CHANNEL()
+			function for more information on setting the language for the channel. If the channel variable
+			<variable>SAY_DTMF_INTERRUPT</variable> is set to 'true' (case insensitive), then this
+			application will react to DTMF in the same way as <literal>Background</literal>.</para>
+		</description>
+		<see-also>
+			<ref type="application">SayAlpha</ref>
+			<ref type="application">SayDigits</ref>
+			<ref type="application">SayPhonetic</ref>
+			<ref type="function">CHANNEL</ref>
+		</see-also>
+	</application>
+	<application name="SayPhonetic" language="en_US">
+		<synopsis>
+			Say Phonetic.
+		</synopsis>
+		<syntax>
+			<parameter name="string" required="true" />
+		</syntax>
+		<description>
+			<para>This application will play the sounds from the phonetic alphabet that correspond to the
+			letters in the given <replaceable>string</replaceable>. If the channel variable
+			<variable>SAY_DTMF_INTERRUPT</variable> is set to 'true' (case insensitive), then this
+			application will react to DTMF in the same way as <literal>Background</literal>.</para>
+		</description>
+		<see-also>
+			<ref type="application">SayAlpha</ref>
+			<ref type="application">SayDigits</ref>
+			<ref type="application">SayNumber</ref>
+		</see-also>
+	</application>
+	<application name="SetAMAFlags" language="en_US">
+		<synopsis>
+			Set the AMA Flags.
+		</synopsis>
+		<syntax>
+			<parameter name="flag" />
+		</syntax>
+		<description>
+			<para>This application will set the channel's AMA Flags for billing purposes.</para>
+			<warning><para>This application is deprecated. Please use the CHANNEL function instead.</para></warning>
+		</description>
+		<see-also>
+			<ref type="function">CDR</ref>
+			<ref type="function">CHANNEL</ref>
+		</see-also>
+	</application>
+	<application name="Wait" language="en_US">
+		<synopsis>
+			Waits for some time.
+		</synopsis>
+		<syntax>
+			<parameter name="seconds" required="true">
+				<para>Can be passed with fractions of a second. For example, <literal>1.5</literal> will ask the
+				application to wait for 1.5 seconds.</para>
+			</parameter>
+		</syntax>
+		<description>
+			<para>This application waits for a specified number of <replaceable>seconds</replaceable>.</para>
+		</description>
+	</application>
+	<application name="WaitExten" language="en_US">
+		<synopsis>
+			Waits for an extension to be entered.
+		</synopsis>
+		<syntax>
+			<parameter name="seconds">
+				<para>Can be passed with fractions of a second. For example, <literal>1.5</literal> will ask the
+				application to wait for 1.5 seconds.</para>
+			</parameter>
+			<parameter name="options">
+				<optionlist>
+					<option name="m">
+						<para>Provide music on hold to the caller while waiting for an extension.</para>
+						<argument name="x">
+							<para>Specify the class for music on hold. <emphasis>CHANNEL(musicclass) will
+							be used instead if set</emphasis></para>
+						</argument>
+					</option>
+				</optionlist>
+			</parameter>
+		</syntax>
+		<description>
+			<para>This application waits for the user to enter a new extension for a specified number
+			of <replaceable>seconds</replaceable>.</para>
+			<xi:include xpointer="xpointer(/docs/application[@name='Macro']/description/warning[2])" />
+		</description>
+		<see-also>
+			<ref type="application">Background</ref>
+			<ref type="function">TIMEOUT</ref>
+		</see-also>
+	</application>
+ ***/
+
+#define BACKGROUND_SKIP		(1 << 0)
+#define BACKGROUND_NOANSWER	(1 << 1)
+#define BACKGROUND_MATCHEXTEN	(1 << 2)
+#define BACKGROUND_PLAYBACK	(1 << 3)
+
+AST_APP_OPTIONS(background_opts, {
+	AST_APP_OPTION('s', BACKGROUND_SKIP),
+	AST_APP_OPTION('n', BACKGROUND_NOANSWER),
+	AST_APP_OPTION('m', BACKGROUND_MATCHEXTEN),
+	AST_APP_OPTION('p', BACKGROUND_PLAYBACK),
+});
+
+#define WAITEXTEN_MOH		(1 << 0)
+#define WAITEXTEN_DIALTONE	(1 << 1)
+
+AST_APP_OPTIONS(waitexten_opts, {
+	AST_APP_OPTION_ARG('m', WAITEXTEN_MOH, 0),
+	AST_APP_OPTION_ARG('d', WAITEXTEN_DIALTONE, 0),
+});
+
+int pbx_builtin_raise_exception(struct ast_channel *chan, const char *reason)
+{
+	/* Priority will become 1, next time through the AUTOLOOP */
+	return raise_exception(chan, reason, 0);
+}
+
+/*!
+ * \ingroup applications
+ */
+static int pbx_builtin_proceeding(struct ast_channel *chan, const char *data)
+{
+	ast_indicate(chan, AST_CONTROL_PROCEEDING);
+	return 0;
+}
+
+/*!
+ * \ingroup applications
+ */
+static int pbx_builtin_progress(struct ast_channel *chan, const char *data)
+{
+	ast_indicate(chan, AST_CONTROL_PROGRESS);
+	return 0;
+}
+
+/*!
+ * \ingroup applications
+ */
+static int pbx_builtin_ringing(struct ast_channel *chan, const char *data)
+{
+	ast_indicate(chan, AST_CONTROL_RINGING);
+	return 0;
+}
+
+/*!
+ * \ingroup applications
+ */
+int indicate_busy(struct ast_channel *chan, const char *data)
+{
+	ast_indicate(chan, AST_CONTROL_BUSY);
+	/* Don't change state of an UP channel, just indicate
+	   busy in audio */
+	ast_channel_lock(chan);
+	if (ast_channel_state(chan) != AST_STATE_UP) {
+		ast_channel_hangupcause_set(chan, AST_CAUSE_BUSY);
+		ast_setstate(chan, AST_STATE_BUSY);
+	}
+	ast_channel_unlock(chan);
+	wait_for_hangup(chan, data);
+	return -1;
+}
+
+/*!
+ * \ingroup applications
+ */
+int indicate_congestion(struct ast_channel *chan, const char *data)
+{
+	ast_indicate(chan, AST_CONTROL_CONGESTION);
+	/* Don't change state of an UP channel, just indicate
+	   congestion in audio */
+	ast_channel_lock(chan);
+	if (ast_channel_state(chan) != AST_STATE_UP) {
+		ast_channel_hangupcause_set(chan, AST_CAUSE_CONGESTION);
+		ast_setstate(chan, AST_STATE_BUSY);
+	}
+	ast_channel_unlock(chan);
+	wait_for_hangup(chan, data);
+	return -1;
+}
+
+/*!
+ * \ingroup applications
+ */
+static int pbx_builtin_answer(struct ast_channel *chan, const char *data)
+{
+	int delay = 0;
+	char *parse;
+	AST_DECLARE_APP_ARGS(args,
+		AST_APP_ARG(delay);
+		AST_APP_ARG(answer_cdr);
+	);
+
+	if (ast_strlen_zero(data)) {
+		return __ast_answer(chan, 0);
+	}
+
+	parse = ast_strdupa(data);
+
+	AST_STANDARD_APP_ARGS(args, parse);
+
+	if (!ast_strlen_zero(args.delay) && (ast_channel_state(chan) != AST_STATE_UP))
+		delay = atoi(data);
+
+	if (delay < 0) {
+		delay = 0;
+	}
+
+	if (!ast_strlen_zero(args.answer_cdr) && !strcasecmp(args.answer_cdr, "nocdr")) {
+		ast_log(AST_LOG_WARNING, "The nocdr option for the Answer application has been removed and is no longer supported.\n");
+	}
+
+	return __ast_answer(chan, delay);
+}
+
+static int pbx_builtin_incomplete(struct ast_channel *chan, const char *data)
+{
+	const char *options = data;
+	int answer = 1;
+
+	/* Some channels can receive DTMF in unanswered state; some cannot */
+	if (!ast_strlen_zero(options) && strchr(options, 'n')) {
+		answer = 0;
+	}
+
+	/* If the channel is hungup, stop waiting */
+	if (ast_check_hangup(chan)) {
+		return -1;
+	} else if (ast_channel_state(chan) != AST_STATE_UP && answer) {
+		__ast_answer(chan, 0);
+	}
+
+	ast_indicate(chan, AST_CONTROL_INCOMPLETE);
+
+	return AST_PBX_INCOMPLETE;
+}
+
+/*!
+ * \ingroup applications
+ */
+static int pbx_builtin_setamaflags(struct ast_channel *chan, const char *data)
+{
+	ast_log(AST_LOG_WARNING, "The SetAMAFlags application is deprecated. Please use the CHANNEL function instead.\n");
+
+	if (ast_strlen_zero(data)) {
+		ast_log(AST_LOG_WARNING, "No parameter passed to SetAMAFlags\n");
+		return 0;
+	}
+	/* Copy the AMA Flags as specified */
+	ast_channel_lock(chan);
+	if (isdigit(data[0])) {
+		int amaflags;
+		if (sscanf(data, "%30d", &amaflags) != 1) {
+			ast_log(AST_LOG_WARNING, "Unable to set AMA flags on channel %s\n", ast_channel_name(chan));
+			ast_channel_unlock(chan);
+			return 0;
+		}
+		ast_channel_amaflags_set(chan, amaflags);
+	} else {
+		ast_channel_amaflags_set(chan, ast_channel_string2amaflag(data));
+	}
+	ast_channel_unlock(chan);
+	return 0;
+}
+
+/*!
+ * \ingroup applications
+ */
+static int pbx_builtin_hangup(struct ast_channel *chan, const char *data)
+{
+	int cause;
+
+	ast_set_hangupsource(chan, "dialplan/builtin", 0);
+
+	if (!ast_strlen_zero(data)) {
+		cause = ast_str2cause(data);
+		if (cause <= 0) {
+			if (sscanf(data, "%30d", &cause) != 1 || cause <= 0) {
+				ast_log(LOG_WARNING, "Invalid cause given to Hangup(): \"%s\"\n", data);
+				cause = 0;
+			}
+		}
+	} else {
+		cause = 0;
+	}
+
+	ast_channel_lock(chan);
+	if (cause <= 0) {
+		cause = ast_channel_hangupcause(chan);
+		if (cause <= 0) {
+			cause = AST_CAUSE_NORMAL_CLEARING;
+		}
+	}
+	ast_channel_hangupcause_set(chan, cause);
+	ast_softhangup_nolock(chan, AST_SOFTHANGUP_EXPLICIT);
+	ast_channel_unlock(chan);
+
+	return -1;
+}
+
+/*! Goto
+ * \ingroup applications
+ */
+static int pbx_builtin_goto(struct ast_channel *chan, const char *data)
+{
+	int res = ast_parseable_goto(chan, data);
+	if (!res)
+		ast_verb(3, "Goto (%s,%s,%d)\n", ast_channel_context(chan), ast_channel_exten(chan), ast_channel_priority(chan) + 1);
+	return res;
+}
+
+/*!
+ * \ingroup applications
+ */
+static int pbx_builtin_gotoiftime(struct ast_channel *chan, const char *data)
+{
+	char *s, *ts, *branch1, *branch2, *branch;
+	struct ast_timing timing;
+	const char *ctime;
+	struct timeval tv = ast_tvnow();
+	long timesecs;
+
+	if (!chan) {
+		ast_log(LOG_WARNING, "GotoIfTime requires a channel on which to operate\n");
+		return -1;
+	}
+
+	if (ast_strlen_zero(data)) {
+		ast_log(LOG_WARNING, "GotoIfTime requires an argument:\n  <time range>,<days of week>,<days of month>,<months>[,<timezone>]?'labeliftrue':'labeliffalse'\n");
+		return -1;
+	}
+
+	ts = s = ast_strdupa(data);
+
+	ast_channel_lock(chan);
+	if ((ctime = pbx_builtin_getvar_helper(chan, "TESTTIME")) && sscanf(ctime, "%ld", &timesecs) == 1) {
+		tv.tv_sec = timesecs;
+	} else if (ctime) {
+		ast_log(LOG_WARNING, "Using current time to evaluate\n");
+		/* Reset when unparseable */
+		pbx_builtin_setvar_helper(chan, "TESTTIME", NULL);
+	}
+	ast_channel_unlock(chan);
+
+	/* Separate the Goto path */
+	strsep(&ts, "?");
+	branch1 = strsep(&ts,":");
+	branch2 = strsep(&ts,"");
+
+	/* struct ast_include include contained garbage here, fixed by zeroing it on get_timerange */
+	if (ast_build_timing(&timing, s) && ast_check_timing2(&timing, tv)) {
+		branch = branch1;
+	} else {
+		branch = branch2;
+	}
+	ast_destroy_timing(&timing);
+
+	if (ast_strlen_zero(branch)) {
+		ast_debug(1, "Not taking any branch\n");
+		return 0;
+	}
+
+	return pbx_builtin_goto(chan, branch);
+}
+
+/*!
+ * \ingroup applications
+ */
+static int pbx_builtin_execiftime(struct ast_channel *chan, const char *data)
+{
+	char *s, *appname;
+	struct ast_timing timing;
+	struct ast_app *app;
+	static const char * const usage = "ExecIfTime requires an argument:\n  <time range>,<days of week>,<days of month>,<months>[,<timezone>]?<appname>[(<appargs>)]";
+
+	if (ast_strlen_zero(data)) {
+		ast_log(LOG_WARNING, "%s\n", usage);
+		return -1;
+	}
+
+	appname = ast_strdupa(data);
+
+	s = strsep(&appname, "?");	/* Separate the timerange and application name/data */
+	if (!appname) {	/* missing application */
+		ast_log(LOG_WARNING, "%s\n", usage);
+		return -1;
+	}
+
+	if (!ast_build_timing(&timing, s)) {
+		ast_log(LOG_WARNING, "Invalid Time Spec: %s\nCorrect usage: %s\n", s, usage);
+		ast_destroy_timing(&timing);
+		return -1;
+	}
+
+	if (!ast_check_timing(&timing))	{ /* outside the valid time window, just return */
+		ast_destroy_timing(&timing);
+		return 0;
+	}
+	ast_destroy_timing(&timing);
+
+	/* now split appname(appargs) */
+	if ((s = strchr(appname, '('))) {
+		char *e;
+		*s++ = '\0';
+		if ((e = strrchr(s, ')')))
+			*e = '\0';
+		else
+			ast_log(LOG_WARNING, "Failed to find closing parenthesis\n");
+	}
+
+
+	if ((app = pbx_findapp(appname))) {
+		return pbx_exec(chan, app, S_OR(s, ""));
+	} else {
+		ast_log(LOG_WARNING, "Cannot locate application %s\n", appname);
+		return -1;
+	}
+}
+
+/*!
+ * \ingroup applications
+ */
+static int pbx_builtin_wait(struct ast_channel *chan, const char *data)
+{
+	int ms;
+
+	/* Wait for "n" seconds */
+	if (!ast_app_parse_timelen(data, &ms, TIMELEN_SECONDS) && ms > 0) {
+		return ast_safe_sleep(chan, ms);
+	}
+	return 0;
+}
+
+/*!
+ * \ingroup applications
+ */
+static int pbx_builtin_waitexten(struct ast_channel *chan, const char *data)
+{
+	int ms, res;
+	struct ast_flags flags = {0};
+	char *opts[1] = { NULL };
+	char *parse;
+	AST_DECLARE_APP_ARGS(args,
+		AST_APP_ARG(timeout);
+		AST_APP_ARG(options);
+	);
+
+	if (!ast_strlen_zero(data)) {
+		parse = ast_strdupa(data);
+		AST_STANDARD_APP_ARGS(args, parse);
+	} else
+		memset(&args, 0, sizeof(args));
+
+	if (args.options)
+		ast_app_parse_options(waitexten_opts, &flags, opts, args.options);
+
+	if (ast_test_flag(&flags, WAITEXTEN_MOH) && !opts[0] ) {
+		ast_log(LOG_WARNING, "The 'm' option has been specified for WaitExten without a class.\n");
+	} else if (ast_test_flag(&flags, WAITEXTEN_MOH)) {
+		ast_indicate_data(chan, AST_CONTROL_HOLD, S_OR(opts[0], NULL),
+			!ast_strlen_zero(opts[0]) ? strlen(opts[0]) + 1 : 0);
+	} else if (ast_test_flag(&flags, WAITEXTEN_DIALTONE)) {
+		struct ast_tone_zone_sound *ts = ast_get_indication_tone(ast_channel_zone(chan), "dial");
+		if (ts) {
+			ast_playtones_start(chan, 0, ts->data, 0);
+			ts = ast_tone_zone_sound_unref(ts);
+		} else {
+			ast_tonepair_start(chan, 350, 440, 0, 0);
+		}
+	}
+	/* Wait for "n" seconds */
+	if (!ast_app_parse_timelen(args.timeout, &ms, TIMELEN_SECONDS) && ms > 0) {
+		/* Yay! */
+	} else if (ast_channel_pbx(chan)) {
+		ms = ast_channel_pbx(chan)->rtimeoutms;
+	} else {
+		ms = 10000;
+	}
+
+	res = ast_waitfordigit(chan, ms);
+	if (!res) {
+		if (ast_check_hangup(chan)) {
+			/* Call is hungup for some reason. */
+			res = -1;
+		} else if (ast_exists_extension(chan, ast_channel_context(chan), ast_channel_exten(chan), ast_channel_priority(chan) + 1,
+			S_COR(ast_channel_caller(chan)->id.number.valid, ast_channel_caller(chan)->id.number.str, NULL))) {
+			ast_verb(3, "Timeout on %s, continuing...\n", ast_channel_name(chan));
+		} else if (ast_exists_extension(chan, ast_channel_context(chan), "t", 1,
+			S_COR(ast_channel_caller(chan)->id.number.valid, ast_channel_caller(chan)->id.number.str, NULL))) {
+			ast_verb(3, "Timeout on %s, going to 't'\n", ast_channel_name(chan));
+			set_ext_pri(chan, "t", 0); /* 0 will become 1, next time through the loop */
+		} else if (ast_exists_extension(chan, ast_channel_context(chan), "e", 1,
+			S_COR(ast_channel_caller(chan)->id.number.valid, ast_channel_caller(chan)->id.number.str, NULL))) {
+			raise_exception(chan, "RESPONSETIMEOUT", 0); /* 0 will become 1, next time through the loop */
+		} else {
+			ast_log(LOG_WARNING, "Timeout but no rule 't' or 'e' in context '%s'\n",
+				ast_channel_context(chan));
+			res = -1;
+		}
+	}
+
+	if (ast_test_flag(&flags, WAITEXTEN_MOH))
+		ast_indicate(chan, AST_CONTROL_UNHOLD);
+	else if (ast_test_flag(&flags, WAITEXTEN_DIALTONE))
+		ast_playtones_stop(chan);
+
+	return res;
+}
+
+/*!
+ * \ingroup applications
+ */
+static int pbx_builtin_background(struct ast_channel *chan, const char *data)
+{
+	int res = 0;
+	int mres = 0;
+	struct ast_flags flags = {0};
+	char *parse, exten[2] = "";
+	AST_DECLARE_APP_ARGS(args,
+		AST_APP_ARG(filename);
+		AST_APP_ARG(options);
+		AST_APP_ARG(lang);
+		AST_APP_ARG(context);
+	);
+
+	if (ast_strlen_zero(data)) {
+		ast_log(LOG_WARNING, "Background requires an argument (filename)\n");
+		return -1;
+	}
+
+	parse = ast_strdupa(data);
+
+	AST_STANDARD_APP_ARGS(args, parse);
+
+	if (ast_strlen_zero(args.lang))
+		args.lang = (char *)ast_channel_language(chan);	/* XXX this is const */
+
+	if (ast_strlen_zero(args.context)) {
+		const char *context;
+		ast_channel_lock(chan);
+		if ((context = pbx_builtin_getvar_helper(chan, "MACRO_CONTEXT"))) {
+			args.context = ast_strdupa(context);
+		} else {
+			args.context = ast_strdupa(ast_channel_context(chan));
+		}
+		ast_channel_unlock(chan);
+	}
+
+	if (args.options) {
+		if (!strcasecmp(args.options, "skip"))
+			flags.flags = BACKGROUND_SKIP;
+		else if (!strcasecmp(args.options, "noanswer"))
+			flags.flags = BACKGROUND_NOANSWER;
+		else
+			ast_app_parse_options(background_opts, &flags, NULL, args.options);
+	}
+
+	/* Answer if need be */
+	if (ast_channel_state(chan) != AST_STATE_UP) {
+		if (ast_test_flag(&flags, BACKGROUND_SKIP)) {
+			goto done;
+		} else if (!ast_test_flag(&flags, BACKGROUND_NOANSWER)) {
+			res = ast_answer(chan);
+		}
+	}
+
+	if (!res) {
+		char *back = ast_strip(args.filename);
+		char *front;
+
+		ast_stopstream(chan);		/* Stop anything playing */
+		/* Stream the list of files */
+		while (!res && (front = strsep(&back, "&")) ) {
+			if ( (res = ast_streamfile(chan, front, args.lang)) ) {
+				ast_log(LOG_WARNING, "ast_streamfile failed on %s for %s\n", ast_channel_name(chan), (char*)data);
+				res = 0;
+				mres = 1;
+				break;
+			}
+			if (ast_test_flag(&flags, BACKGROUND_PLAYBACK)) {
+				res = ast_waitstream(chan, "");
+			} else if (ast_test_flag(&flags, BACKGROUND_MATCHEXTEN)) {
+				res = ast_waitstream_exten(chan, args.context);
+			} else {
+				res = ast_waitstream(chan, AST_DIGIT_ANY);
+			}
+			ast_stopstream(chan);
+		}
+	}
+
+	/*
+	 * If the single digit DTMF is an extension in the specified context, then
+	 * go there and signal no DTMF.  Otherwise, we should exit with that DTMF.
+	 * If we're in Macro, we'll exit and seek that DTMF as the beginning of an
+	 * extension in the Macro's calling context.  If we're not in Macro, then
+	 * we'll simply seek that extension in the calling context.  Previously,
+	 * someone complained about the behavior as it related to the interior of a
+	 * Gosub routine, and the fix (#14011) inadvertently broke FreePBX
+	 * (#14940).  This change should fix both of these situations, but with the
+	 * possible incompatibility that if a single digit extension does not exist
+	 * (but a longer extension COULD have matched), it would have previously
+	 * gone immediately to the "i" extension, but will now need to wait for a
+	 * timeout.
+	 *
+	 * Later, we had to add a flag to disable this workaround, because AGI
+	 * users can EXEC Background and reasonably expect that the DTMF code will
+	 * be returned (see #16434).
+	 */
+	if (!ast_test_flag(ast_channel_flags(chan), AST_FLAG_DISABLE_WORKAROUNDS)
+		&& (exten[0] = res)
+		&& ast_canmatch_extension(chan, args.context, exten, 1,
+			S_COR(ast_channel_caller(chan)->id.number.valid, ast_channel_caller(chan)->id.number.str, NULL))
+		&& !ast_matchmore_extension(chan, args.context, exten, 1,
+			S_COR(ast_channel_caller(chan)->id.number.valid, ast_channel_caller(chan)->id.number.str, NULL))) {
+		char buf[2] = { 0, };
+		snprintf(buf, sizeof(buf), "%c", res);
+		ast_channel_exten_set(chan, buf);
+		ast_channel_context_set(chan, args.context);
+		ast_channel_priority_set(chan, 0);
+		res = 0;
+	}
+done:
+	pbx_builtin_setvar_helper(chan, "BACKGROUNDSTATUS", mres ? "FAILED" : "SUCCESS");
+	return res;
+}
+
+static int pbx_builtin_noop(struct ast_channel *chan, const char *data)
+{
+	return 0;
+}
+
+static int pbx_builtin_gotoif(struct ast_channel *chan, const char *data)
+{
+	char *condition, *branch1, *branch2, *branch;
+	char *stringp;
+
+	if (ast_strlen_zero(data)) {
+		ast_log(LOG_WARNING, "Ignoring, since there is no variable to check\n");
+		return 0;
+	}
+
+	stringp = ast_strdupa(data);
+	condition = strsep(&stringp,"?");
+	branch1 = strsep(&stringp,":");
+	branch2 = strsep(&stringp,"");
+	branch = pbx_checkcondition(condition) ? branch1 : branch2;
+
+	if (ast_strlen_zero(branch)) {
+		ast_debug(1, "Not taking any branch\n");
+		return 0;
+	}
+
+	return pbx_builtin_goto(chan, branch);
+}
+
+static int pbx_builtin_saynumber(struct ast_channel *chan, const char *data)
+{
+	char tmp[256];
+	char *number = tmp;
+	int number_val;
+	char *options;
+	int res;
+	int interrupt = 0;
+	const char *interrupt_string;
+
+	ast_channel_lock(chan);
+	interrupt_string = pbx_builtin_getvar_helper(chan, "SAY_DTMF_INTERRUPT");
+	if (ast_true(interrupt_string)) {
+		interrupt = 1;
+	}
+	ast_channel_unlock(chan);
+
+	if (ast_strlen_zero(data)) {
+		ast_log(LOG_WARNING, "SayNumber requires an argument (number)\n");
+		return -1;
+	}
+	ast_copy_string(tmp, data, sizeof(tmp));
+	strsep(&number, ",");
+
+	if (sscanf(tmp, "%d", &number_val) != 1) {
+		ast_log(LOG_WARNING, "argument '%s' to SayNumber could not be parsed as a number.\n", tmp);
+		return 0;
+	}
+
+	options = strsep(&number, ",");
+	if (options) {
+		if ( strcasecmp(options, "f") && strcasecmp(options, "m") &&
+			strcasecmp(options, "c") && strcasecmp(options, "n") ) {
+			ast_log(LOG_WARNING, "SayNumber gender option is either 'f', 'm', 'c' or 'n'\n");
+			return -1;
+		}
+	}
+
+	res = ast_say_number(chan, number_val, interrupt ? AST_DIGIT_ANY : "", ast_channel_language(chan), options);
+
+	if (res < 0) {
+		ast_log(LOG_WARNING, "We were unable to say the number %s, is it too large?\n", tmp);
+	}
+
+	return interrupt ? res : 0;
+}
+
+static int pbx_builtin_saydigits(struct ast_channel *chan, const char *data)
+{
+	int res = 0;
+	int interrupt = 0;
+	const char *interrupt_string;
+
+	ast_channel_lock(chan);
+	interrupt_string = pbx_builtin_getvar_helper(chan, "SAY_DTMF_INTERRUPT");
+	if (ast_true(interrupt_string)) {
+		interrupt = 1;
+	}
+	ast_channel_unlock(chan);
+
+	if (data) {
+		res = ast_say_digit_str(chan, data, interrupt ? AST_DIGIT_ANY : "", ast_channel_language(chan));
+	}
+
+	return res;
+}
+
+static int pbx_builtin_saycharacters_case(struct ast_channel *chan, const char *data)
+{
+	int res = 0;
+	int sensitivity = 0;
+	char *parse;
+	int interrupt = 0;
+	const char *interrupt_string;
+
+	AST_DECLARE_APP_ARGS(args,
+		AST_APP_ARG(options);
+		AST_APP_ARG(characters);
+	);
+
+	ast_channel_lock(chan);
+	interrupt_string = pbx_builtin_getvar_helper(chan, "SAY_DTMF_INTERRUPT");
+	if (ast_true(interrupt_string)) {
+		interrupt = 1;
+	}
+	ast_channel_unlock(chan);
+
+	if (ast_strlen_zero(data)) {
+		ast_log(LOG_WARNING, "SayAlphaCase requires two arguments (options, characters)\n");
+		return 0;
+	}
+
+	parse = ast_strdupa(data);
+	AST_STANDARD_APP_ARGS(args, parse);
+
+	if (!args.options || strlen(args.options) != 1) {
+		ast_log(LOG_WARNING, "SayAlphaCase options are mutually exclusive and required\n");
+		return 0;
+	}
+
+	switch (args.options[0]) {
+	case 'a':
+		sensitivity = AST_SAY_CASE_ALL;
+		break;
+	case 'l':
+		sensitivity = AST_SAY_CASE_LOWER;
+		break;
+	case 'n':
+		sensitivity = AST_SAY_CASE_NONE;
+		break;
+	case 'u':
+		sensitivity = AST_SAY_CASE_UPPER;
+		break;
+	default:
+		ast_log(LOG_WARNING, "Invalid option: '%s'\n", args.options);
+		return 0;
+	}
+
+	res = ast_say_character_str(chan, args.characters, interrupt ? AST_DIGIT_ANY : "", ast_channel_language(chan), sensitivity);
+
+	return res;
+}
+
+static int pbx_builtin_saycharacters(struct ast_channel *chan, const char *data)
+{
+	int res = 0;
+	int interrupt = 0;
+	const char *interrupt_string;
+
+	ast_channel_lock(chan);
+	interrupt_string = pbx_builtin_getvar_helper(chan, "SAY_DTMF_INTERRUPT");
+	if (ast_true(interrupt_string)) {
+		interrupt = 1;
+	}
+	ast_channel_unlock(chan);
+
+	if (data) {
+		res = ast_say_character_str(chan, data, interrupt ? AST_DIGIT_ANY : "", ast_channel_language(chan), AST_SAY_CASE_NONE);
+	}
+
+	return res;
+}
+
+static int pbx_builtin_sayphonetic(struct ast_channel *chan, const char *data)
+{
+	int res = 0;
+	int interrupt = 0;
+	const char *interrupt_string;
+
+	ast_channel_lock(chan);
+	interrupt_string = pbx_builtin_getvar_helper(chan, "SAY_DTMF_INTERRUPT");
+	if (ast_true(interrupt_string)) {
+		interrupt = 1;
+	}
+	ast_channel_unlock(chan);
+
+	if (data)
+		res = ast_say_phonetic_str(chan, data, interrupt ? AST_DIGIT_ANY : "", ast_channel_language(chan));
+	return res;
+}
+
+static int pbx_builtin_importvar(struct ast_channel *chan, const char *data)
+{
+	char *name;
+	char *value;
+	char *channel;
+	char tmp[VAR_BUF_SIZE];
+	static int deprecation_warning = 0;
+
+	if (ast_strlen_zero(data)) {
+		ast_log(LOG_WARNING, "Ignoring, since there is no variable to set\n");
+		return 0;
+	}
+	tmp[0] = 0;
+	if (!deprecation_warning) {
+		ast_log(LOG_WARNING, "ImportVar is deprecated.  Please use Set(varname=${IMPORT(channel,variable)}) instead.\n");
+		deprecation_warning = 1;
+	}
+
+	value = ast_strdupa(data);
+	name = strsep(&value,"=");
+	channel = strsep(&value,",");
+	if (channel && value && name) { /*! \todo XXX should do !ast_strlen_zero(..) of the args ? */
+		struct ast_channel *chan2 = ast_channel_get_by_name(channel);
+		if (chan2) {
+			char *s = ast_alloca(strlen(value) + 4);
+			sprintf(s, "${%s}", value);
+			pbx_substitute_variables_helper(chan2, s, tmp, sizeof(tmp) - 1);
+			chan2 = ast_channel_unref(chan2);
+		}
+		pbx_builtin_setvar_helper(chan, name, tmp);
+	}
+
+	return(0);
+}
+
+/*! \brief Declaration of builtin applications */
+struct pbx_builtin {
+	char name[AST_MAX_APP];
+	int (*execute)(struct ast_channel *chan, const char *data);
+} builtins[] =
+{
+	/* These applications are built into the PBX core and do not
+	   need separate modules */
+
+	{ "Answer",         pbx_builtin_answer },
+	{ "BackGround",     pbx_builtin_background },
+	{ "Busy",           indicate_busy },
+	{ "Congestion",     indicate_congestion },
+	{ "ExecIfTime",     pbx_builtin_execiftime },
+	{ "Goto",           pbx_builtin_goto },
+	{ "GotoIf",         pbx_builtin_gotoif },
+	{ "GotoIfTime",     pbx_builtin_gotoiftime },
+	{ "ImportVar",      pbx_builtin_importvar },
+	{ "Hangup",         pbx_builtin_hangup },
+	{ "Incomplete",     pbx_builtin_incomplete },
+	{ "NoOp",           pbx_builtin_noop },
+	{ "Proceeding",     pbx_builtin_proceeding },
+	{ "Progress",       pbx_builtin_progress },
+	{ "RaiseException", pbx_builtin_raise_exception },
+	{ "Ringing",        pbx_builtin_ringing },
+	{ "SayAlpha",       pbx_builtin_saycharacters },
+	{ "SayAlphaCase",   pbx_builtin_saycharacters_case },
+	{ "SayDigits",      pbx_builtin_saydigits },
+	{ "SayNumber",      pbx_builtin_saynumber },
+	{ "SayPhonetic",    pbx_builtin_sayphonetic },
+	{ "SetAMAFlags",    pbx_builtin_setamaflags },
+	{ "Wait",           pbx_builtin_wait },
+	{ "WaitExten",      pbx_builtin_waitexten }
+};
+
+static void unload_pbx_builtins(void)
+{
+	int x;
+
+	/* Unregister builtin applications */
+	for (x = 0; x < ARRAY_LEN(builtins); x++) {
+		ast_unregister_application(builtins[x].name);
+	}
+}
+
+int load_pbx_builtins(void)
+{
+	int x;
+
+	/* Register builtin applications */
+	for (x = 0; x < ARRAY_LEN(builtins); x++) {
+		if (ast_register_application2(builtins[x].name, builtins[x].execute, NULL, NULL, NULL)) {
+			ast_log(LOG_ERROR, "Unable to register builtin application '%s'\n", builtins[x].name);
+			unload_pbx_builtins();
+			return -1;
+		}
+	}
+
+	ast_register_cleanup(unload_pbx_builtins);
+
+	return 0;
+}
diff --git a/main/pbx_functions.c b/main/pbx_functions.c
new file mode 100644
index 0000000..bc738b0
--- /dev/null
+++ b/main/pbx_functions.c
@@ -0,0 +1,723 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2015, CFWare, LLC
+ *
+ * Corey Farrell <git at cfware.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 Custom function management routines.
+ *
+ * \author Corey Farrell <git at cfware.com>
+ */
+
+/*** MODULEINFO
+	<support_level>core</support_level>
+ ***/
+
+#include "asterisk.h"
+
+ASTERISK_REGISTER_FILE()
+
+#include "asterisk/_private.h"
+#include "asterisk/cli.h"
+#include "asterisk/linkedlists.h"
+#include "asterisk/module.h"
+#include "asterisk/pbx.h"
+#include "asterisk/term.h"
+#include "asterisk/threadstorage.h"
+#include "asterisk/xmldoc.h"
+#include "pbx_private.h"
+
+/*!
+ * \brief A thread local indicating whether the current thread can run
+ * 'dangerous' dialplan functions.
+ */
+AST_THREADSTORAGE(thread_inhibit_escalations_tl);
+
+/*!
+ * \brief Set to true (non-zero) to globally allow all dangerous dialplan
+ * functions to run.
+ */
+static int live_dangerously;
+
+/*!
+ * \brief Registered functions container.
+ *
+ * It is sorted by function name.
+ */
+static AST_RWLIST_HEAD_STATIC(acf_root, ast_custom_function);
+
+static char *handle_show_functions(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+	struct ast_custom_function *acf;
+	int count_acf = 0;
+	int like = 0;
+
+	switch (cmd) {
+	case CLI_INIT:
+		e->command = "core show functions [like]";
+		e->usage =
+			"Usage: core show functions [like <text>]\n"
+			"       List builtin functions, optionally only those matching a given string\n";
+		return NULL;
+	case CLI_GENERATE:
+		return NULL;
+	}
+
+	if (a->argc == 5 && (!strcmp(a->argv[3], "like")) ) {
+		like = 1;
+	} else if (a->argc != 3) {
+		return CLI_SHOWUSAGE;
+	}
+
+	ast_cli(a->fd, "%s Custom Functions:\n"
+		"--------------------------------------------------------------------------------\n",
+		like ? "Matching" : "Installed");
+
+	AST_RWLIST_RDLOCK(&acf_root);
+	AST_RWLIST_TRAVERSE(&acf_root, acf, acflist) {
+		if (!like || strstr(acf->name, a->argv[4])) {
+			count_acf++;
+			ast_cli(a->fd, "%-20.20s  %-35.35s  %s\n",
+				S_OR(acf->name, ""),
+				S_OR(acf->syntax, ""),
+				S_OR(acf->synopsis, ""));
+		}
+	}
+	AST_RWLIST_UNLOCK(&acf_root);
+
+	ast_cli(a->fd, "%d %scustom functions installed.\n", count_acf, like ? "matching " : "");
+
+	return CLI_SUCCESS;
+}
+
+static char *complete_functions(const char *word, int pos, int state)
+{
+	struct ast_custom_function *cur;
+	char *ret = NULL;
+	int which = 0;
+	int wordlen;
+	int cmp;
+
+	if (pos != 3) {
+		return NULL;
+	}
+
+	wordlen = strlen(word);
+	AST_RWLIST_RDLOCK(&acf_root);
+	AST_RWLIST_TRAVERSE(&acf_root, cur, acflist) {
+		/*
+		 * Do a case-insensitive search for convenience in this
+		 * 'complete' function.
+		 *
+		 * We must search the entire container because the functions are
+		 * sorted and normally found case sensitively.
+		 */
+		cmp = strncasecmp(word, cur->name, wordlen);
+		if (!cmp) {
+			/* Found match. */
+			if (++which <= state) {
+				/* Not enough matches. */
+				continue;
+			}
+			ret = ast_strdup(cur->name);
+			break;
+		}
+	}
+	AST_RWLIST_UNLOCK(&acf_root);
+
+	return ret;
+}
+
+static char *handle_show_function(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+	struct ast_custom_function *acf;
+	/* Maximum number of characters added by terminal coloring is 22 */
+	char infotitle[64 + AST_MAX_APP + 22], syntitle[40], destitle[40], argtitle[40], seealsotitle[40];
+	char info[64 + AST_MAX_APP], *synopsis = NULL, *description = NULL, *seealso = NULL;
+	char stxtitle[40], *syntax = NULL, *arguments = NULL;
+	int syntax_size, description_size, synopsis_size, arguments_size, seealso_size;
+
+	switch (cmd) {
+	case CLI_INIT:
+		e->command = "core show function";
+		e->usage =
+			"Usage: core show function <function>\n"
+			"       Describe a particular dialplan function.\n";
+		return NULL;
+	case CLI_GENERATE:
+		return complete_functions(a->word, a->pos, a->n);
+	}
+
+	if (a->argc != 4) {
+		return CLI_SHOWUSAGE;
+	}
+
+	if (!(acf = ast_custom_function_find(a->argv[3]))) {
+		ast_cli(a->fd, "No function by that name registered.\n");
+
+		return CLI_FAILURE;
+	}
+
+	syntax_size = strlen(S_OR(acf->syntax, "Not Available")) + AST_TERM_MAX_ESCAPE_CHARS;
+	syntax = ast_malloc(syntax_size);
+	if (!syntax) {
+		ast_cli(a->fd, "Memory allocation failure!\n");
+
+		return CLI_FAILURE;
+	}
+
+	snprintf(info, sizeof(info), "\n  -= Info about function '%s' =- \n\n", acf->name);
+	term_color(infotitle, info, COLOR_MAGENTA, 0, sizeof(infotitle));
+	term_color(syntitle, "[Synopsis]\n", COLOR_MAGENTA, 0, 40);
+	term_color(destitle, "[Description]\n", COLOR_MAGENTA, 0, 40);
+	term_color(stxtitle, "[Syntax]\n", COLOR_MAGENTA, 0, 40);
+	term_color(argtitle, "[Arguments]\n", COLOR_MAGENTA, 0, 40);
+	term_color(seealsotitle, "[See Also]\n", COLOR_MAGENTA, 0, 40);
+	term_color(syntax, S_OR(acf->syntax, "Not available"), COLOR_CYAN, 0, syntax_size);
+#ifdef AST_XML_DOCS
+	if (acf->docsrc == AST_XML_DOC) {
+		arguments = ast_xmldoc_printable(S_OR(acf->arguments, "Not available"), 1);
+		synopsis = ast_xmldoc_printable(S_OR(acf->synopsis, "Not available"), 1);
+		description = ast_xmldoc_printable(S_OR(acf->desc, "Not available"), 1);
+		seealso = ast_xmldoc_printable(S_OR(acf->seealso, "Not available"), 1);
+	} else
+#endif
+	{
+		synopsis_size = strlen(S_OR(acf->synopsis, "Not Available")) + AST_TERM_MAX_ESCAPE_CHARS;
+		synopsis = ast_malloc(synopsis_size);
+
+		description_size = strlen(S_OR(acf->desc, "Not Available")) + AST_TERM_MAX_ESCAPE_CHARS;
+		description = ast_malloc(description_size);
+
+		arguments_size = strlen(S_OR(acf->arguments, "Not Available")) + AST_TERM_MAX_ESCAPE_CHARS;
+		arguments = ast_malloc(arguments_size);
+
+		seealso_size = strlen(S_OR(acf->seealso, "Not Available")) + AST_TERM_MAX_ESCAPE_CHARS;
+		seealso = ast_malloc(seealso_size);
+
+		/* check allocated memory. */
+		if (!synopsis || !description || !arguments || !seealso) {
+			ast_free(synopsis);
+			ast_free(description);
+			ast_free(arguments);
+			ast_free(seealso);
+			ast_free(syntax);
+
+			return CLI_FAILURE;
+		}
+
+		term_color(arguments, S_OR(acf->arguments, "Not available"), COLOR_CYAN, 0, arguments_size);
+		term_color(synopsis, S_OR(acf->synopsis, "Not available"), COLOR_CYAN, 0, synopsis_size);
+		term_color(description, S_OR(acf->desc, "Not available"), COLOR_CYAN, 0, description_size);
+		term_color(seealso, S_OR(acf->seealso, "Not available"), COLOR_CYAN, 0, seealso_size);
+	}
+
+	ast_cli(a->fd, "%s%s%s\n\n%s%s\n\n%s%s\n\n%s%s\n\n%s%s\n",
+			infotitle, syntitle, synopsis, destitle, description,
+			stxtitle, syntax, argtitle, arguments, seealsotitle, seealso);
+
+	ast_free(arguments);
+	ast_free(synopsis);
+	ast_free(description);
+	ast_free(seealso);
+	ast_free(syntax);
+
+	return CLI_SUCCESS;
+}
+
+static struct ast_custom_function *ast_custom_function_find_nolock(const char *name)
+{
+	struct ast_custom_function *cur;
+	int cmp;
+
+	AST_RWLIST_TRAVERSE(&acf_root, cur, acflist) {
+		cmp = strcmp(name, cur->name);
+		if (cmp > 0) {
+			continue;
+		}
+		if (!cmp) {
+			/* Found it. */
+			break;
+		}
+		/* Not in container. */
+		cur = NULL;
+		break;
+	}
+
+	return cur;
+}
+
+struct ast_custom_function *ast_custom_function_find(const char *name)
+{
+	struct ast_custom_function *acf;
+
+	AST_RWLIST_RDLOCK(&acf_root);
+	acf = ast_custom_function_find_nolock(name);
+	AST_RWLIST_UNLOCK(&acf_root);
+
+	return acf;
+}
+
+int ast_custom_function_unregister(struct ast_custom_function *acf)
+{
+	struct ast_custom_function *cur;
+
+	if (!acf) {
+		return -1;
+	}
+
+	AST_RWLIST_WRLOCK(&acf_root);
+	cur = AST_RWLIST_REMOVE(&acf_root, acf, acflist);
+	if (cur) {
+#ifdef AST_XML_DOCS
+		if (cur->docsrc == AST_XML_DOC) {
+			ast_string_field_free_memory(acf);
+		}
+#endif
+		ast_verb(2, "Unregistered custom function %s\n", cur->name);
+	}
+	AST_RWLIST_UNLOCK(&acf_root);
+
+	return cur ? 0 : -1;
+}
+
+/*!
+ * \brief Returns true if given custom function escalates privileges on read.
+ *
+ * \param acf Custom function to query.
+ * \return True (non-zero) if reads escalate privileges.
+ * \return False (zero) if reads just read.
+ */
+static int read_escalates(const struct ast_custom_function *acf) {
+	return acf->read_escalates;
+}
+
+/*!
+ * \brief Returns true if given custom function escalates privileges on write.
+ *
+ * \param acf Custom function to query.
+ * \return True (non-zero) if writes escalate privileges.
+ * \return False (zero) if writes just write.
+ */
+static int write_escalates(const struct ast_custom_function *acf) {
+	return acf->write_escalates;
+}
+
+/*! \internal
+ *  \brief Retrieve the XML documentation of a specified ast_custom_function,
+ *         and populate ast_custom_function string fields.
+ *  \param acf ast_custom_function structure with empty 'desc' and 'synopsis'
+ *             but with a function 'name'.
+ *  \retval -1 On error.
+ *  \retval 0 On succes.
+ */
+static int acf_retrieve_docs(struct ast_custom_function *acf)
+{
+#ifdef AST_XML_DOCS
+	char *tmpxml;
+
+	/* Let's try to find it in the Documentation XML */
+	if (!ast_strlen_zero(acf->desc) || !ast_strlen_zero(acf->synopsis)) {
+		return 0;
+	}
+
+	if (ast_string_field_init(acf, 128)) {
+		return -1;
+	}
+
+	/* load synopsis */
+	tmpxml = ast_xmldoc_build_synopsis("function", acf->name, ast_module_name(acf->mod));
+	ast_string_field_set(acf, synopsis, tmpxml);
+	ast_free(tmpxml);
+
+	/* load description */
+	tmpxml = ast_xmldoc_build_description("function", acf->name, ast_module_name(acf->mod));
+	ast_string_field_set(acf, desc, tmpxml);
+	ast_free(tmpxml);
+
+	/* load syntax */
+	tmpxml = ast_xmldoc_build_syntax("function", acf->name, ast_module_name(acf->mod));
+	ast_string_field_set(acf, syntax, tmpxml);
+	ast_free(tmpxml);
+
+	/* load arguments */
+	tmpxml = ast_xmldoc_build_arguments("function", acf->name, ast_module_name(acf->mod));
+	ast_string_field_set(acf, arguments, tmpxml);
+	ast_free(tmpxml);
+
+	/* load seealso */
+	tmpxml = ast_xmldoc_build_seealso("function", acf->name, ast_module_name(acf->mod));
+	ast_string_field_set(acf, seealso, tmpxml);
+	ast_free(tmpxml);
+
+	acf->docsrc = AST_XML_DOC;
+#endif
+
+	return 0;
+}
+
+int __ast_custom_function_register(struct ast_custom_function *acf, struct ast_module *mod)
+{
+	struct ast_custom_function *cur;
+
+	if (!acf) {
+		return -1;
+	}
+
+	acf->mod = mod;
+#ifdef AST_XML_DOCS
+	acf->docsrc = AST_STATIC_DOC;
+#endif
+
+	if (acf_retrieve_docs(acf)) {
+		return -1;
+	}
+
+	AST_RWLIST_WRLOCK(&acf_root);
+
+	cur = ast_custom_function_find_nolock(acf->name);
+	if (cur) {
+		ast_log(LOG_ERROR, "Function %s already registered.\n", acf->name);
+		AST_RWLIST_UNLOCK(&acf_root);
+		return -1;
+	}
+
+	/* Store in alphabetical order */
+	AST_RWLIST_TRAVERSE_SAFE_BEGIN(&acf_root, cur, acflist) {
+		if (strcmp(acf->name, cur->name) < 0) {
+			AST_RWLIST_INSERT_BEFORE_CURRENT(acf, acflist);
+			break;
+		}
+	}
+	AST_RWLIST_TRAVERSE_SAFE_END;
+	if (!cur) {
+		AST_RWLIST_INSERT_TAIL(&acf_root, acf, acflist);
+	}
+
+	AST_RWLIST_UNLOCK(&acf_root);
+
+	ast_verb(2, "Registered custom function '" COLORIZE_FMT "'\n", COLORIZE(COLOR_BRCYAN, 0, acf->name));
+
+	return 0;
+}
+
+int __ast_custom_function_register_escalating(struct ast_custom_function *acf, enum ast_custom_function_escalation escalation, struct ast_module *mod)
+{
+	int res;
+
+	res = __ast_custom_function_register(acf, mod);
+	if (res != 0) {
+		return -1;
+	}
+
+	switch (escalation) {
+	case AST_CFE_NONE:
+		break;
+	case AST_CFE_READ:
+		acf->read_escalates = 1;
+		break;
+	case AST_CFE_WRITE:
+		acf->write_escalates = 1;
+		break;
+	case AST_CFE_BOTH:
+		acf->read_escalates = 1;
+		acf->write_escalates = 1;
+		break;
+	}
+
+	return 0;
+}
+
+/*! \brief return a pointer to the arguments of the function,
+ * and terminates the function name with '\\0'
+ */
+static char *func_args(char *function)
+{
+	char *args = strchr(function, '(');
+
+	if (!args) {
+		ast_log(LOG_WARNING, "Function '%s' doesn't contain parentheses.  Assuming null argument.\n", function);
+	} else {
+		char *p;
+		*args++ = '\0';
+		if ((p = strrchr(args, ')'))) {
+			*p = '\0';
+		} else {
+			ast_log(LOG_WARNING, "Can't find trailing parenthesis for function '%s(%s'?\n", function, args);
+		}
+	}
+	return args;
+}
+
+void pbx_live_dangerously(int new_live_dangerously)
+{
+	if (new_live_dangerously && !live_dangerously) {
+		ast_log(LOG_WARNING, "Privilege escalation protection disabled!\n"
+			"See https://wiki.asterisk.org/wiki/x/1gKfAQ for more details.\n");
+	}
+
+	if (!new_live_dangerously && live_dangerously) {
+		ast_log(LOG_NOTICE, "Privilege escalation protection enabled.\n");
+	}
+	live_dangerously = new_live_dangerously;
+}
+
+int ast_thread_inhibit_escalations(void)
+{
+	int *thread_inhibit_escalations;
+
+	thread_inhibit_escalations = ast_threadstorage_get(
+		&thread_inhibit_escalations_tl, sizeof(*thread_inhibit_escalations));
+
+	if (thread_inhibit_escalations == NULL) {
+		ast_log(LOG_ERROR, "Error inhibiting privilege escalations for current thread\n");
+		return -1;
+	}
+
+	*thread_inhibit_escalations = 1;
+	return 0;
+}
+
+/*!
+ * \brief Indicates whether the current thread inhibits the execution of
+ * dangerous functions.
+ *
+ * \return True (non-zero) if dangerous function execution is inhibited.
+ * \return False (zero) if dangerous function execution is allowed.
+ */
+static int thread_inhibits_escalations(void)
+{
+	int *thread_inhibit_escalations;
+
+	thread_inhibit_escalations = ast_threadstorage_get(
+		&thread_inhibit_escalations_tl, sizeof(*thread_inhibit_escalations));
+
+	if (thread_inhibit_escalations == NULL) {
+		ast_log(LOG_ERROR, "Error checking thread's ability to run dangerous functions\n");
+		/* On error, assume that we are inhibiting */
+		return 1;
+	}
+
+	return *thread_inhibit_escalations;
+}
+
+/*!
+ * \brief Determines whether execution of a custom function's read function
+ * is allowed.
+ *
+ * \param acfptr Custom function to check
+ * \return True (non-zero) if reading is allowed.
+ * \return False (zero) if reading is not allowed.
+ */
+static int is_read_allowed(struct ast_custom_function *acfptr)
+{
+	if (!acfptr) {
+		return 1;
+	}
+
+	if (!read_escalates(acfptr)) {
+		return 1;
+	}
+
+	if (!thread_inhibits_escalations()) {
+		return 1;
+	}
+
+	if (live_dangerously) {
+		/* Global setting overrides the thread's preference */
+		ast_debug(2, "Reading %s from a dangerous context\n",
+			acfptr->name);
+		return 1;
+	}
+
+	/* We have no reason to allow this function to execute */
+	return 0;
+}
+
+/*!
+ * \brief Determines whether execution of a custom function's write function
+ * is allowed.
+ *
+ * \param acfptr Custom function to check
+ * \return True (non-zero) if writing is allowed.
+ * \return False (zero) if writing is not allowed.
+ */
+static int is_write_allowed(struct ast_custom_function *acfptr)
+{
+	if (!acfptr) {
+		return 1;
+	}
+
+	if (!write_escalates(acfptr)) {
+		return 1;
+	}
+
+	if (!thread_inhibits_escalations()) {
+		return 1;
+	}
+
+	if (live_dangerously) {
+		/* Global setting overrides the thread's preference */
+		ast_debug(2, "Writing %s from a dangerous context\n",
+			acfptr->name);
+		return 1;
+	}
+
+	/* We have no reason to allow this function to execute */
+	return 0;
+}
+
+int ast_func_read(struct ast_channel *chan, const char *function, char *workspace, size_t len)
+{
+	char *copy = ast_strdupa(function);
+	char *args = func_args(copy);
+	struct ast_custom_function *acfptr = ast_custom_function_find(copy);
+	int res;
+	struct ast_module_user *u = NULL;
+
+	if (acfptr == NULL) {
+		ast_log(LOG_ERROR, "Function %s not registered\n", copy);
+	} else if (!acfptr->read && !acfptr->read2) {
+		ast_log(LOG_ERROR, "Function %s cannot be read\n", copy);
+	} else if (!is_read_allowed(acfptr)) {
+		ast_log(LOG_ERROR, "Dangerous function %s read blocked\n", copy);
+	} else if (acfptr->read) {
+		if (acfptr->mod) {
+			u = __ast_module_user_add(acfptr->mod, chan);
+		}
+		res = acfptr->read(chan, copy, args, workspace, len);
+		if (acfptr->mod && u) {
+			__ast_module_user_remove(acfptr->mod, u);
+		}
+
+		return res;
+	} else {
+		struct ast_str *str = ast_str_create(16);
+
+		if (acfptr->mod) {
+			u = __ast_module_user_add(acfptr->mod, chan);
+		}
+		res = acfptr->read2(chan, copy, args, &str, 0);
+		if (acfptr->mod && u) {
+			__ast_module_user_remove(acfptr->mod, u);
+		}
+		ast_copy_string(workspace, ast_str_buffer(str), len > ast_str_size(str) ? ast_str_size(str) : len);
+		ast_free(str);
+
+		return res;
+	}
+
+	return -1;
+}
+
+int ast_func_read2(struct ast_channel *chan, const char *function, struct ast_str **str, ssize_t maxlen)
+{
+	char *copy = ast_strdupa(function);
+	char *args = func_args(copy);
+	struct ast_custom_function *acfptr = ast_custom_function_find(copy);
+	int res;
+	struct ast_module_user *u = NULL;
+
+	if (acfptr == NULL) {
+		ast_log(LOG_ERROR, "Function %s not registered\n", copy);
+	} else if (!acfptr->read && !acfptr->read2) {
+		ast_log(LOG_ERROR, "Function %s cannot be read\n", copy);
+	} else if (!is_read_allowed(acfptr)) {
+		ast_log(LOG_ERROR, "Dangerous function %s read blocked\n", copy);
+	} else {
+		if (acfptr->mod) {
+			u = __ast_module_user_add(acfptr->mod, chan);
+		}
+		ast_str_reset(*str);
+		if (acfptr->read2) {
+			/* ast_str enabled */
+			res = acfptr->read2(chan, copy, args, str, maxlen);
+		} else {
+			/* Legacy function pointer, allocate buffer for result */
+			int maxsize = ast_str_size(*str);
+
+			if (maxlen > -1) {
+				if (maxlen == 0) {
+					if (acfptr->read_max) {
+						maxsize = acfptr->read_max;
+					} else {
+						maxsize = VAR_BUF_SIZE;
+					}
+				} else {
+					maxsize = maxlen;
+				}
+				ast_str_make_space(str, maxsize);
+			}
+			res = acfptr->read(chan, copy, args, ast_str_buffer(*str), maxsize);
+		}
+		if (acfptr->mod && u) {
+			__ast_module_user_remove(acfptr->mod, u);
+		}
+
+		return res;
+	}
+
+	return -1;
+}
+
+int ast_func_write(struct ast_channel *chan, const char *function, const char *value)
+{
+	char *copy = ast_strdupa(function);
+	char *args = func_args(copy);
+	struct ast_custom_function *acfptr = ast_custom_function_find(copy);
+
+	if (acfptr == NULL) {
+		ast_log(LOG_ERROR, "Function %s not registered\n", copy);
+	} else if (!acfptr->write) {
+		ast_log(LOG_ERROR, "Function %s cannot be written to\n", copy);
+	} else if (!is_write_allowed(acfptr)) {
+		ast_log(LOG_ERROR, "Dangerous function %s write blocked\n", copy);
+	} else {
+		int res;
+		struct ast_module_user *u = NULL;
+
+		if (acfptr->mod) {
+			u = __ast_module_user_add(acfptr->mod, chan);
+		}
+		res = acfptr->write(chan, copy, args, value);
+		if (acfptr->mod && u) {
+			__ast_module_user_remove(acfptr->mod, u);
+		}
+
+		return res;
+	}
+
+	return -1;
+}
+
+static struct ast_cli_entry acf_cli[] = {
+	AST_CLI_DEFINE(handle_show_functions, "Shows registered dialplan functions"),
+	AST_CLI_DEFINE(handle_show_function, "Describe a specific dialplan function"),
+};
+
+static void unload_pbx_functions_cli(void)
+{
+	ast_cli_unregister_multiple(acf_cli, ARRAY_LEN(acf_cli));
+}
+
+int load_pbx_functions_cli(void)
+{
+	ast_cli_register_multiple(acf_cli, ARRAY_LEN(acf_cli));
+	ast_register_cleanup(unload_pbx_functions_cli);
+
+	return 0;
+}
diff --git a/main/pbx_hangup_handler.c b/main/pbx_hangup_handler.c
new file mode 100644
index 0000000..3b82547
--- /dev/null
+++ b/main/pbx_hangup_handler.c
@@ -0,0 +1,300 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2016, CFWare, LLC
+ *
+ * Corey Farrell <git at cfware.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 PBX Hangup Handler management routines.
+ *
+ * \author Corey Farrell <git at cfware.com>
+ */
+
+/*** MODULEINFO
+	<support_level>core</support_level>
+ ***/
+
+#include "asterisk.h"
+
+ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
+
+#include "asterisk/_private.h"
+#include "asterisk/app.h"
+#include "asterisk/cli.h"
+#include "asterisk/linkedlists.h"
+#include "asterisk/pbx.h"
+#include "asterisk/stasis_channels.h"
+#include "asterisk/utils.h"
+
+/*!
+ * \internal
+ * \brief Publish a hangup handler related message to \ref stasis
+ */
+static void publish_hangup_handler_message(const char *action, struct ast_channel *chan, const char *handler)
+{
+	RAII_VAR(struct ast_json *, blob, NULL, ast_json_unref);
+
+	blob = ast_json_pack("{s: s, s: s}",
+			"type", action,
+			"handler", S_OR(handler, ""));
+	if (!blob) {
+		return;
+	}
+
+	ast_channel_publish_blob(chan, ast_channel_hangup_handler_type(), blob);
+}
+
+int ast_pbx_hangup_handler_run(struct ast_channel *chan)
+{
+	struct ast_hangup_handler_list *handlers;
+	struct ast_hangup_handler *h_handler;
+
+	ast_channel_lock(chan);
+	handlers = ast_channel_hangup_handlers(chan);
+	if (AST_LIST_EMPTY(handlers)) {
+		ast_channel_unlock(chan);
+		return 0;
+	}
+
+	/*
+	 * Make sure that the channel is marked as hungup since we are
+	 * going to run the hangup handlers on it.
+	 */
+	ast_softhangup_nolock(chan, AST_SOFTHANGUP_HANGUP_EXEC);
+
+	for (;;) {
+		handlers = ast_channel_hangup_handlers(chan);
+		h_handler = AST_LIST_REMOVE_HEAD(handlers, node);
+		if (!h_handler) {
+			break;
+		}
+
+		publish_hangup_handler_message("run", chan, h_handler->args);
+		ast_channel_unlock(chan);
+
+		ast_app_exec_sub(NULL, chan, h_handler->args, 1);
+		ast_free(h_handler);
+
+		ast_channel_lock(chan);
+	}
+	ast_channel_unlock(chan);
+	return 1;
+}
+
+void ast_pbx_hangup_handler_init(struct ast_channel *chan)
+{
+	struct ast_hangup_handler_list *handlers;
+
+	handlers = ast_channel_hangup_handlers(chan);
+	AST_LIST_HEAD_INIT_NOLOCK(handlers);
+}
+
+void ast_pbx_hangup_handler_destroy(struct ast_channel *chan)
+{
+	struct ast_hangup_handler_list *handlers;
+	struct ast_hangup_handler *h_handler;
+
+	ast_channel_lock(chan);
+
+	/* Get rid of each of the hangup handlers on the channel */
+	handlers = ast_channel_hangup_handlers(chan);
+	while ((h_handler = AST_LIST_REMOVE_HEAD(handlers, node))) {
+		ast_free(h_handler);
+	}
+
+	ast_channel_unlock(chan);
+}
+
+int ast_pbx_hangup_handler_pop(struct ast_channel *chan)
+{
+	struct ast_hangup_handler_list *handlers;
+	struct ast_hangup_handler *h_handler;
+
+	ast_channel_lock(chan);
+	handlers = ast_channel_hangup_handlers(chan);
+	h_handler = AST_LIST_REMOVE_HEAD(handlers, node);
+	if (h_handler) {
+		publish_hangup_handler_message("pop", chan, h_handler->args);
+	}
+	ast_channel_unlock(chan);
+	if (h_handler) {
+		ast_free(h_handler);
+		return 1;
+	}
+	return 0;
+}
+
+void ast_pbx_hangup_handler_push(struct ast_channel *chan, const char *handler)
+{
+	struct ast_hangup_handler_list *handlers;
+	struct ast_hangup_handler *h_handler;
+	const char *expanded_handler;
+
+	if (ast_strlen_zero(handler)) {
+		return;
+	}
+
+	expanded_handler = ast_app_expand_sub_args(chan, handler);
+	if (!expanded_handler) {
+		return;
+	}
+	h_handler = ast_malloc(sizeof(*h_handler) + 1 + strlen(expanded_handler));
+	if (!h_handler) {
+		ast_free((char *) expanded_handler);
+		return;
+	}
+	strcpy(h_handler->args, expanded_handler);/* Safe */
+	ast_free((char *) expanded_handler);
+
+	ast_channel_lock(chan);
+
+	handlers = ast_channel_hangup_handlers(chan);
+	AST_LIST_INSERT_HEAD(handlers, h_handler, node);
+	publish_hangup_handler_message("push", chan, h_handler->args);
+	ast_channel_unlock(chan);
+}
+
+#define HANDLER_FORMAT	"%-30s %s\n"
+
+/*!
+ * \internal
+ * \brief CLI output the hangup handler headers.
+ * \since 11.0
+ *
+ * \param fd CLI file descriptor to use.
+ *
+ * \return Nothing
+ */
+static void ast_pbx_hangup_handler_headers(int fd)
+{
+	ast_cli(fd, HANDLER_FORMAT, "Channel", "Handler");
+}
+
+/*!
+ * \internal
+ * \brief CLI output the channel hangup handlers.
+ * \since 11.0
+ *
+ * \param fd CLI file descriptor to use.
+ * \param chan Channel to show hangup handlers.
+ *
+ * \return Nothing
+ */
+static void ast_pbx_hangup_handler_show(int fd, struct ast_channel *chan)
+{
+	struct ast_hangup_handler_list *handlers;
+	struct ast_hangup_handler *h_handler;
+	int first = 1;
+
+	ast_channel_lock(chan);
+	handlers = ast_channel_hangup_handlers(chan);
+	AST_LIST_TRAVERSE(handlers, h_handler, node) {
+		ast_cli(fd, HANDLER_FORMAT, first ? ast_channel_name(chan) : "", h_handler->args);
+		first = 0;
+	}
+	ast_channel_unlock(chan);
+}
+
+/*
+ * \brief 'show hanguphandlers <channel>' CLI command implementation function...
+ */
+static char *handle_show_hangup_channel(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+	struct ast_channel *chan;
+
+	switch (cmd) {
+	case CLI_INIT:
+		e->command = "core show hanguphandlers";
+		e->usage =
+			"Usage: core show hanguphandlers <channel>\n"
+			"       Show hangup handlers of a specified channel.\n";
+		return NULL;
+	case CLI_GENERATE:
+		return ast_complete_channels(a->line, a->word, a->pos, a->n, e->args);
+	}
+
+	if (a->argc < 4) {
+		return CLI_SHOWUSAGE;
+	}
+
+	chan = ast_channel_get_by_name(a->argv[3]);
+	if (!chan) {
+		ast_cli(a->fd, "Channel does not exist.\n");
+		return CLI_FAILURE;
+	}
+
+	ast_pbx_hangup_handler_headers(a->fd);
+	ast_pbx_hangup_handler_show(a->fd, chan);
+
+	ast_channel_unref(chan);
+
+	return CLI_SUCCESS;
+}
+
+/*
+ * \brief 'show hanguphandlers all' CLI command implementation function...
+ */
+static char *handle_show_hangup_all(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+	struct ast_channel_iterator *iter;
+	struct ast_channel *chan;
+
+	switch (cmd) {
+	case CLI_INIT:
+		e->command = "core show hanguphandlers all";
+		e->usage =
+			"Usage: core show hanguphandlers all\n"
+			"       Show hangup handlers for all channels.\n";
+		return NULL;
+	case CLI_GENERATE:
+		return ast_complete_channels(a->line, a->word, a->pos, a->n, e->args);
+	}
+
+	if (a->argc < 4) {
+		return CLI_SHOWUSAGE;
+	}
+
+	iter = ast_channel_iterator_all_new();
+	if (!iter) {
+		return CLI_FAILURE;
+	}
+
+	ast_pbx_hangup_handler_headers(a->fd);
+	for (; (chan = ast_channel_iterator_next(iter)); ast_channel_unref(chan)) {
+		ast_pbx_hangup_handler_show(a->fd, chan);
+	}
+	ast_channel_iterator_destroy(iter);
+
+	return CLI_SUCCESS;
+}
+
+static struct ast_cli_entry cli[] = {
+	AST_CLI_DEFINE(handle_show_hangup_all, "Show hangup handlers of all channels"),
+	AST_CLI_DEFINE(handle_show_hangup_channel, "Show hangup handlers of a specified channel"),
+};
+
+static void unload_pbx_hangup_handler(void)
+{
+	ast_cli_unregister_multiple(cli, ARRAY_LEN(cli));
+}
+
+int load_pbx_hangup_handler(void)
+{
+	ast_cli_register_multiple(cli, ARRAY_LEN(cli));
+	ast_register_cleanup(unload_pbx_hangup_handler);
+
+	return 0;
+}
diff --git a/main/pbx_private.h b/main/pbx_private.h
new file mode 100644
index 0000000..e091941
--- /dev/null
+++ b/main/pbx_private.h
@@ -0,0 +1,46 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2015 Fairview 5 Engineering, LLC
+ *
+ * George Joseph <george.joseph at fairview5.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 Private include file for pbx
+ */
+
+#ifndef _PBX_PRIVATE_H
+#define _PBX_PRIVATE_H
+
+/*! pbx.c functions needed by pbx_builtins.c */
+int raise_exception(struct ast_channel *chan, const char *reason, int priority);
+void wait_for_hangup(struct ast_channel *chan, const void *data);
+void set_ext_pri(struct ast_channel *c, const char *exten, int pri);
+
+/*! pbx.c function needed by pbx_app.c */
+void unreference_cached_app(struct ast_app *app);
+
+/*! pbx_builtins.c functions needed by pbx.c */
+int indicate_congestion(struct ast_channel *, const char *);
+int indicate_busy(struct ast_channel *, const char *);
+
+/*! pbx_switch.c functions needed by pbx.c */
+struct ast_switch *pbx_findswitch(const char *sw);
+
+/*! pbx_app.c functions needed by pbx.c */
+const char *app_name(struct ast_app *app);
+
+#define VAR_BUF_SIZE 4096
+
+#endif /* _PBX_PRIVATE_H */
diff --git a/main/pbx_switch.c b/main/pbx_switch.c
new file mode 100644
index 0000000..4ac7f8c
--- /dev/null
+++ b/main/pbx_switch.c
@@ -0,0 +1,133 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2016, CFWare, LLC
+ *
+ * Corey Farrell <git at cfware.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 PBX switch routines.
+ *
+ * \author Corey Farrell <git at cfware.com>
+ */
+
+/*** MODULEINFO
+	<support_level>core</support_level>
+ ***/
+
+#include "asterisk.h"
+
+ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
+
+#include "asterisk/_private.h"
+#include "asterisk/cli.h"
+#include "asterisk/linkedlists.h"
+#include "asterisk/pbx.h"
+#include "pbx_private.h"
+
+static AST_RWLIST_HEAD_STATIC(switches, ast_switch);
+
+struct ast_switch *pbx_findswitch(const char *sw)
+{
+	struct ast_switch *asw;
+
+	AST_RWLIST_RDLOCK(&switches);
+	AST_RWLIST_TRAVERSE(&switches, asw, list) {
+		if (!strcasecmp(asw->name, sw))
+			break;
+	}
+	AST_RWLIST_UNLOCK(&switches);
+
+	return asw;
+}
+
+/*
+ * Append to the list. We don't have a tail pointer because we need
+ * to scan the list anyways to check for duplicates during insertion.
+ */
+int ast_register_switch(struct ast_switch *sw)
+{
+	struct ast_switch *tmp;
+
+	AST_RWLIST_WRLOCK(&switches);
+	AST_RWLIST_TRAVERSE(&switches, tmp, list) {
+		if (!strcasecmp(tmp->name, sw->name)) {
+			AST_RWLIST_UNLOCK(&switches);
+			ast_log(LOG_WARNING, "Switch '%s' already found\n", sw->name);
+			return -1;
+		}
+	}
+	AST_RWLIST_INSERT_TAIL(&switches, sw, list);
+	AST_RWLIST_UNLOCK(&switches);
+
+	return 0;
+}
+
+void ast_unregister_switch(struct ast_switch *sw)
+{
+	AST_RWLIST_WRLOCK(&switches);
+	AST_RWLIST_REMOVE(&switches, sw, list);
+	AST_RWLIST_UNLOCK(&switches);
+}
+
+/*! \brief  handle_show_switches: CLI support for listing registered dial plan switches */
+static char *handle_show_switches(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+	struct ast_switch *sw;
+
+	switch (cmd) {
+	case CLI_INIT:
+		e->command = "core show switches";
+		e->usage =
+			"Usage: core show switches\n"
+			"       List registered switches\n";
+		return NULL;
+	case CLI_GENERATE:
+		return NULL;
+	}
+
+	AST_RWLIST_RDLOCK(&switches);
+
+	if (AST_RWLIST_EMPTY(&switches)) {
+		AST_RWLIST_UNLOCK(&switches);
+		ast_cli(a->fd, "There are no registered alternative switches\n");
+		return CLI_SUCCESS;
+	}
+
+	ast_cli(a->fd, "\n    -= Registered Asterisk Alternative Switches =-\n");
+	AST_RWLIST_TRAVERSE(&switches, sw, list)
+		ast_cli(a->fd, "%s: %s\n", sw->name, sw->description);
+
+	AST_RWLIST_UNLOCK(&switches);
+
+	return CLI_SUCCESS;
+}
+
+static struct ast_cli_entry sw_cli[] = {
+	AST_CLI_DEFINE(handle_show_switches, "Show alternative switches"),
+};
+
+static void unload_pbx_switch(void)
+{
+	ast_cli_unregister_multiple(sw_cli, ARRAY_LEN(sw_cli));
+}
+
+int load_pbx_switch(void)
+{
+	ast_cli_register_multiple(sw_cli, ARRAY_LEN(sw_cli));
+	ast_register_cleanup(unload_pbx_switch);
+
+	return 0;
+}
diff --git a/main/pbx_timing.c b/main/pbx_timing.c
new file mode 100644
index 0000000..53529bb
--- /dev/null
+++ b/main/pbx_timing.c
@@ -0,0 +1,294 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2016, CFWare, LLC
+ *
+ * Corey Farrell <git at cfware.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 PBX timing routines.
+ *
+ * \author Corey Farrell <git at cfware.com>
+ */
+
+/*** MODULEINFO
+	<support_level>core</support_level>
+ ***/
+
+#include "asterisk.h"
+
+ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
+
+#include "asterisk/localtime.h"
+#include "asterisk/logger.h"
+#include "asterisk/pbx.h"
+#include "asterisk/strings.h"
+#include "asterisk/utils.h"
+
+/*! \brief Helper for get_range.
+ * return the index of the matching entry, starting from 1.
+ * If names is not supplied, try numeric values.
+ */
+static int lookup_name(const char *s, const char * const names[], int max)
+{
+	int i;
+
+	if (names && *s > '9') {
+		for (i = 0; names[i]; i++) {
+			if (!strcasecmp(s, names[i])) {
+				return i;
+			}
+		}
+	}
+
+	/* Allow months and weekdays to be specified as numbers, as well */
+	if (sscanf(s, "%2d", &i) == 1 && i >= 1 && i <= max) {
+		/* What the array offset would have been: "1" would be at offset 0 */
+		return i - 1;
+	}
+	return -1; /* error return */
+}
+
+/*! \brief helper function to return a range up to max (7, 12, 31 respectively).
+ * names, if supplied, is an array of names that should be mapped to numbers.
+ */
+static unsigned get_range(char *src, int max, const char * const names[], const char *msg)
+{
+	int start, end; /* start and ending position */
+	unsigned int mask = 0;
+	char *part;
+
+	/* Check for whole range */
+	if (ast_strlen_zero(src) || !strcmp(src, "*")) {
+		return (1 << max) - 1;
+	}
+
+	while ((part = strsep(&src, "&"))) {
+		/* Get start and ending position */
+		char *endpart = strchr(part, '-');
+		if (endpart) {
+			*endpart++ = '\0';
+		}
+		/* Find the start */
+		if ((start = lookup_name(part, names, max)) < 0) {
+			ast_log(LOG_WARNING, "Invalid %s '%s', skipping element\n", msg, part);
+			continue;
+		}
+		if (endpart) { /* find end of range */
+			if ((end = lookup_name(endpart, names, max)) < 0) {
+				ast_log(LOG_WARNING, "Invalid end %s '%s', skipping element\n", msg, endpart);
+				continue;
+			}
+		} else {
+			end = start;
+		}
+		/* Fill the mask. Remember that ranges are cyclic */
+		mask |= (1 << end);   /* initialize with last element */
+		while (start != end) {
+			mask |= (1 << start);
+			if (++start >= max) {
+				start = 0;
+			}
+		}
+	}
+	return mask;
+}
+
+/*! \brief store a bitmask of valid times, one bit each 1 minute */
+static void get_timerange(struct ast_timing *i, char *times)
+{
+	char *endpart, *part;
+	int x;
+	int st_h, st_m;
+	int endh, endm;
+	int minute_start, minute_end;
+
+	/* start disabling all times, fill the fields with 0's, as they may contain garbage */
+	memset(i->minmask, 0, sizeof(i->minmask));
+
+	/* 1-minute per bit */
+	/* Star is all times */
+	if (ast_strlen_zero(times) || !strcmp(times, "*")) {
+		/* 48, because each hour takes 2 integers; 30 bits each */
+		for (x = 0; x < 48; x++) {
+			i->minmask[x] = 0x3fffffff; /* 30 bits */
+		}
+		return;
+	}
+	/* Otherwise expect a range */
+	while ((part = strsep(&times, "&"))) {
+		if (!(endpart = strchr(part, '-'))) {
+			if (sscanf(part, "%2d:%2d", &st_h, &st_m) != 2 || st_h < 0 || st_h > 23 || st_m < 0 || st_m > 59) {
+				ast_log(LOG_WARNING, "%s isn't a valid time.\n", part);
+				continue;
+			}
+			i->minmask[st_h * 2 + (st_m >= 30 ? 1 : 0)] |= (1 << (st_m % 30));
+			continue;
+		}
+		*endpart++ = '\0';
+		/* why skip non digits? Mostly to skip spaces */
+		while (*endpart && !isdigit(*endpart)) {
+			endpart++;
+		}
+		if (!*endpart) {
+			ast_log(LOG_WARNING, "Invalid time range starting with '%s-'.\n", part);
+			continue;
+		}
+		if (sscanf(part, "%2d:%2d", &st_h, &st_m) != 2 || st_h < 0 || st_h > 23 || st_m < 0 || st_m > 59) {
+			ast_log(LOG_WARNING, "'%s' isn't a valid start time.\n", part);
+			continue;
+		}
+		if (sscanf(endpart, "%2d:%2d", &endh, &endm) != 2 || endh < 0 || endh > 23 || endm < 0 || endm > 59) {
+			ast_log(LOG_WARNING, "'%s' isn't a valid end time.\n", endpart);
+			continue;
+		}
+		minute_start = st_h * 60 + st_m;
+		minute_end = endh * 60 + endm;
+		/* Go through the time and enable each appropriate bit */
+		for (x = minute_start; x != minute_end; x = (x + 1) % (24 * 60)) {
+			i->minmask[x / 30] |= (1 << (x % 30));
+		}
+		/* Do the last one */
+		i->minmask[x / 30] |= (1 << (x % 30));
+	}
+	/* All done */
+	return;
+}
+
+static const char * const days[] =
+{
+	"sun",
+	"mon",
+	"tue",
+	"wed",
+	"thu",
+	"fri",
+	"sat",
+	NULL,
+};
+
+static const char * const months[] =
+{
+	"jan",
+	"feb",
+	"mar",
+	"apr",
+	"may",
+	"jun",
+	"jul",
+	"aug",
+	"sep",
+	"oct",
+	"nov",
+	"dec",
+	NULL,
+};
+
+/*! /brief Build timing
+ *
+ * /param i info
+ * /param info_in
+ *
+ */
+int ast_build_timing(struct ast_timing *i, const char *info_in)
+{
+	char *info;
+	int j, num_fields, last_sep = -1;
+
+	i->timezone = NULL;
+
+	/* Check for empty just in case */
+	if (ast_strlen_zero(info_in)) {
+		return 0;
+	}
+
+	/* make a copy just in case we were passed a static string */
+	info = ast_strdupa(info_in);
+
+	/* count the number of fields in the timespec */
+	for (j = 0, num_fields = 1; info[j] != '\0'; j++) {
+		if (info[j] == ',') {
+			last_sep = j;
+			num_fields++;
+		}
+	}
+
+	/* save the timezone, if it is specified */
+	if (num_fields == 5) {
+		i->timezone = ast_strdup(info + last_sep + 1);
+	}
+
+	/* Assume everything except time */
+	i->monthmask = 0xfff;	/* 12 bits */
+	i->daymask = 0x7fffffffU; /* 31 bits */
+	i->dowmask = 0x7f; /* 7 bits */
+	/* on each call, use strsep() to move info to the next argument */
+	get_timerange(i, strsep(&info, "|,"));
+	if (info)
+		i->dowmask = get_range(strsep(&info, "|,"), 7, days, "day of week");
+	if (info)
+		i->daymask = get_range(strsep(&info, "|,"), 31, NULL, "day");
+	if (info)
+		i->monthmask = get_range(strsep(&info, "|,"), 12, months, "month");
+	return 1;
+}
+
+int ast_check_timing(const struct ast_timing *i)
+{
+	return ast_check_timing2(i, ast_tvnow());
+}
+
+int ast_check_timing2(const struct ast_timing *i, const struct timeval tv)
+{
+	struct ast_tm tm;
+
+	ast_localtime(&tv, &tm, i->timezone);
+
+	/* If it's not the right month, return */
+	if (!(i->monthmask & (1 << tm.tm_mon)))
+		return 0;
+
+	/* If it's not that time of the month.... */
+	/* Warning, tm_mday has range 1..31! */
+	if (!(i->daymask & (1 << (tm.tm_mday-1))))
+		return 0;
+
+	/* If it's not the right day of the week */
+	if (!(i->dowmask & (1 << tm.tm_wday)))
+		return 0;
+
+	/* Sanity check the hour just to be safe */
+	if ((tm.tm_hour < 0) || (tm.tm_hour > 23)) {
+		ast_log(LOG_WARNING, "Insane time...\n");
+		return 0;
+	}
+
+	/* Now the tough part, we calculate if it fits
+	   in the right time based on min/hour */
+	if (!(i->minmask[tm.tm_hour * 2 + (tm.tm_min >= 30 ? 1 : 0)] & (1 << (tm.tm_min >= 30 ? tm.tm_min - 30 : tm.tm_min))))
+		return 0;
+
+	/* If we got this far, then we're good */
+	return 1;
+}
+
+int ast_destroy_timing(struct ast_timing *i)
+{
+	if (i->timezone) {
+		ast_free(i->timezone);
+		i->timezone = NULL;
+	}
+	return 0;
+}
diff --git a/main/pbx_variables.c b/main/pbx_variables.c
new file mode 100644
index 0000000..16ddbd0
--- /dev/null
+++ b/main/pbx_variables.c
@@ -0,0 +1,1180 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2016, CFWare, LLC
+ *
+ * Corey Farrell <git at cfware.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 PBX variables routines.
+ *
+ * \author Corey Farrell <git at cfware.com>
+ */
+
+/*** MODULEINFO
+	<support_level>core</support_level>
+ ***/
+
+#include "asterisk.h"
+
+ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
+
+#include "asterisk/_private.h"
+#include "asterisk/app.h"
+#include "asterisk/ast_expr.h"
+#include "asterisk/chanvars.h"
+#include "asterisk/cli.h"
+#include "asterisk/linkedlists.h"
+#include "asterisk/lock.h"
+#include "asterisk/module.h"
+#include "asterisk/paths.h"
+#include "asterisk/pbx.h"
+#include "asterisk/stasis_channels.h"
+#include "pbx_private.h"
+
+/*** DOCUMENTATION
+	<application name="Set" language="en_US">
+		<synopsis>
+			Set channel variable or function value.
+		</synopsis>
+		<syntax argsep="=">
+			<parameter name="name" required="true" />
+			<parameter name="value" required="true" />
+		</syntax>
+		<description>
+			<para>This function can be used to set the value of channel variables or dialplan functions.
+			When setting variables, if the variable name is prefixed with <literal>_</literal>,
+			the variable will be inherited into channels created from the current channel.
+			If the variable name is prefixed with <literal>__</literal>, the variable will be
+			inherited into channels created from the current channel and all children channels.</para>
+			<note><para>If (and only if), in <filename>/etc/asterisk/asterisk.conf</filename>, you have
+			a <literal>[compat]</literal> category, and you have <literal>app_set = 1.4</literal> under that, then
+			the behavior of this app changes, and strips surrounding quotes from the right hand side as
+			it did previously in 1.4.
+			The advantages of not stripping out quoting, and not caring about the separator characters (comma and vertical bar)
+			were sufficient to make these changes in 1.6. Confusion about how many backslashes would be needed to properly
+			protect separators and quotes in various database access strings has been greatly
+			reduced by these changes.</para></note>
+		</description>
+		<see-also>
+			<ref type="application">MSet</ref>
+			<ref type="function">GLOBAL</ref>
+			<ref type="function">SET</ref>
+			<ref type="function">ENV</ref>
+		</see-also>
+	</application>
+	<application name="MSet" language="en_US">
+		<synopsis>
+			Set channel variable(s) or function value(s).
+		</synopsis>
+		<syntax>
+			<parameter name="set1" required="true" argsep="=">
+				<argument name="name1" required="true" />
+				<argument name="value1" required="true" />
+			</parameter>
+			<parameter name="set2" multiple="true" argsep="=">
+				<argument name="name2" required="true" />
+				<argument name="value2" required="true" />
+			</parameter>
+		</syntax>
+		<description>
+			<para>This function can be used to set the value of channel variables or dialplan functions.
+			When setting variables, if the variable name is prefixed with <literal>_</literal>,
+			the variable will be inherited into channels created from the current channel
+			If the variable name is prefixed with <literal>__</literal>, the variable will be
+			inherited into channels created from the current channel and all children channels.
+			MSet behaves in a similar fashion to the way Set worked in 1.2/1.4 and is thus
+			prone to doing things that you may not expect. For example, it strips surrounding
+			double-quotes from the right-hand side (value). If you need to put a separator
+			character (comma or vert-bar), you will need to escape them by inserting a backslash
+			before them. Avoid its use if possible.</para>
+		</description>
+		<see-also>
+			<ref type="application">Set</ref>
+		</see-also>
+	</application>
+ ***/
+
+AST_RWLOCK_DEFINE_STATIC(globalslock);
+static struct varshead globals = AST_LIST_HEAD_NOLOCK_INIT_VALUE;
+
+/*!
+ * \brief extract offset:length from variable name.
+ * \return 1 if there is a offset:length part, which is
+ * trimmed off (values go into variables)
+ */
+static int parse_variable_name(char *var, int *offset, int *length, int *isfunc)
+{
+	int parens = 0;
+
+	*offset = 0;
+	*length = INT_MAX;
+	*isfunc = 0;
+	for (; *var; var++) {
+		if (*var == '(') {
+			(*isfunc)++;
+			parens++;
+		} else if (*var == ')') {
+			parens--;
+		} else if (*var == ':' && parens == 0) {
+			*var++ = '\0';
+			sscanf(var, "%30d:%30d", offset, length);
+			return 1; /* offset:length valid */
+		}
+	}
+	return 0;
+}
+
+/*!
+ *\brief takes a substring. It is ok to call with value == workspace.
+ * \param value
+ * \param offset < 0 means start from the end of the string and set the beginning
+ *   to be that many characters back.
+ * \param length is the length of the substring, a value less than 0 means to leave
+ * that many off the end.
+ * \param workspace
+ * \param workspace_len
+ * Always return a copy in workspace.
+ */
+static char *substring(const char *value, int offset, int length, char *workspace, size_t workspace_len)
+{
+	char *ret = workspace;
+	int lr;	/* length of the input string after the copy */
+
+	ast_copy_string(workspace, value, workspace_len); /* always make a copy */
+
+	lr = strlen(ret); /* compute length after copy, so we never go out of the workspace */
+
+	/* Quick check if no need to do anything */
+	if (offset == 0 && length >= lr)	/* take the whole string */
+		return ret;
+
+	if (offset < 0)	{	/* translate negative offset into positive ones */
+		offset = lr + offset;
+		if (offset < 0) /* If the negative offset was greater than the length of the string, just start at the beginning */
+			offset = 0;
+	}
+
+	/* too large offset result in empty string so we know what to return */
+	if (offset >= lr)
+		return ret + lr;	/* the final '\0' */
+
+	ret += offset;		/* move to the start position */
+	if (length >= 0 && length < lr - offset)	/* truncate if necessary */
+		ret[length] = '\0';
+	else if (length < 0) {
+		if (lr > offset - length) /* After we remove from the front and from the rear, is there anything left? */
+			ret[lr + length - offset] = '\0';
+		else
+			ret[0] = '\0';
+	}
+
+	return ret;
+}
+
+static const char *ast_str_substring(struct ast_str *value, int offset, int length)
+{
+	int lr;	/* length of the input string after the copy */
+
+	lr = ast_str_strlen(value); /* compute length after copy, so we never go out of the workspace */
+
+	/* Quick check if no need to do anything */
+	if (offset == 0 && length >= lr)	/* take the whole string */
+		return ast_str_buffer(value);
+
+	if (offset < 0)	{	/* translate negative offset into positive ones */
+		offset = lr + offset;
+		if (offset < 0) /* If the negative offset was greater than the length of the string, just start at the beginning */
+			offset = 0;
+	}
+
+	/* too large offset result in empty string so we know what to return */
+	if (offset >= lr) {
+		ast_str_reset(value);
+		return ast_str_buffer(value);
+	}
+
+	if (offset > 0) {
+		/* Go ahead and chop off the beginning */
+		memmove(ast_str_buffer(value), ast_str_buffer(value) + offset, ast_str_strlen(value) - offset + 1);
+		lr -= offset;
+	}
+
+	if (length >= 0 && length < lr) {	/* truncate if necessary */
+		char *tmp = ast_str_buffer(value);
+		tmp[length] = '\0';
+		ast_str_update(value);
+	} else if (length < 0) {
+		if (lr > -length) { /* After we remove from the front and from the rear, is there anything left? */
+			char *tmp = ast_str_buffer(value);
+			tmp[lr + length] = '\0';
+			ast_str_update(value);
+		} else {
+			ast_str_reset(value);
+		}
+	} else {
+		/* Nothing to do, but update the buffer length */
+		ast_str_update(value);
+	}
+
+	return ast_str_buffer(value);
+}
+
+/*! \brief  Support for Asterisk built-in variables in the dialplan
+
+\note	See also
+	- \ref AstVar	Channel variables
+	- \ref AstCauses The HANGUPCAUSE variable
+ */
+void pbx_retrieve_variable(struct ast_channel *c, const char *var, char **ret, char *workspace, int workspacelen, struct varshead *headp)
+{
+	struct ast_str *str = ast_str_create(16);
+	const char *cret;
+
+	cret = ast_str_retrieve_variable(&str, 0, c, headp, var);
+	ast_copy_string(workspace, ast_str_buffer(str), workspacelen);
+	*ret = cret ? workspace : NULL;
+	ast_free(str);
+}
+
+const char *ast_str_retrieve_variable(struct ast_str **str, ssize_t maxlen, struct ast_channel *c, struct varshead *headp, const char *var)
+{
+	const char not_found = '\0';
+	char *tmpvar;
+	const char *ret;
+	const char *s;	/* the result */
+	int offset, length;
+	int i, need_substring;
+	struct varshead *places[2] = { headp, &globals };	/* list of places where we may look */
+	char workspace[20];
+
+	if (c) {
+		ast_channel_lock(c);
+		places[0] = ast_channel_varshead(c);
+	}
+	/*
+	 * Make a copy of var because parse_variable_name() modifies the string.
+	 * Then if called directly, we might need to run substring() on the result;
+	 * remember this for later in 'need_substring', 'offset' and 'length'
+	 */
+	tmpvar = ast_strdupa(var);	/* parse_variable_name modifies the string */
+	need_substring = parse_variable_name(tmpvar, &offset, &length, &i /* ignored */);
+
+	/*
+	 * Look first into predefined variables, then into variable lists.
+	 * Variable 's' points to the result, according to the following rules:
+	 * s == &not_found (set at the beginning) means that we did not find a
+	 *	matching variable and need to look into more places.
+	 * If s != &not_found, s is a valid result string as follows:
+	 * s = NULL if the variable does not have a value;
+	 *	you typically do this when looking for an unset predefined variable.
+	 * s = workspace if the result has been assembled there;
+	 *	typically done when the result is built e.g. with an snprintf(),
+	 *	so we don't need to do an additional copy.
+	 * s != workspace in case we have a string, that needs to be copied
+	 *	(the ast_copy_string is done once for all at the end).
+	 *	Typically done when the result is already available in some string.
+	 */
+	s = &not_found;	/* default value */
+	if (c) {	/* This group requires a valid channel */
+		/* Names with common parts are looked up a piece at a time using strncmp. */
+		if (!strncmp(var, "CALL", 4)) {
+			if (!strncmp(var + 4, "ING", 3)) {
+				if (!strcmp(var + 7, "PRES")) {			/* CALLINGPRES */
+					ast_str_set(str, maxlen, "%d",
+						ast_party_id_presentation(&ast_channel_caller(c)->id));
+					s = ast_str_buffer(*str);
+				} else if (!strcmp(var + 7, "ANI2")) {		/* CALLINGANI2 */
+					ast_str_set(str, maxlen, "%d", ast_channel_caller(c)->ani2);
+					s = ast_str_buffer(*str);
+				} else if (!strcmp(var + 7, "TON")) {		/* CALLINGTON */
+					ast_str_set(str, maxlen, "%d", ast_channel_caller(c)->id.number.plan);
+					s = ast_str_buffer(*str);
+				} else if (!strcmp(var + 7, "TNS")) {		/* CALLINGTNS */
+					ast_str_set(str, maxlen, "%d", ast_channel_dialed(c)->transit_network_select);
+					s = ast_str_buffer(*str);
+				}
+			}
+		} else if (!strcmp(var, "HINT")) {
+			s = ast_str_get_hint(str, maxlen, NULL, 0, c, ast_channel_context(c), ast_channel_exten(c)) ? ast_str_buffer(*str) : NULL;
+		} else if (!strcmp(var, "HINTNAME")) {
+			s = ast_str_get_hint(NULL, 0, str, maxlen, c, ast_channel_context(c), ast_channel_exten(c)) ? ast_str_buffer(*str) : NULL;
+		} else if (!strcmp(var, "EXTEN")) {
+			s = ast_channel_exten(c);
+		} else if (!strcmp(var, "CONTEXT")) {
+			s = ast_channel_context(c);
+		} else if (!strcmp(var, "PRIORITY")) {
+			ast_str_set(str, maxlen, "%d", ast_channel_priority(c));
+			s = ast_str_buffer(*str);
+		} else if (!strcmp(var, "CHANNEL")) {
+			s = ast_channel_name(c);
+		} else if (!strcmp(var, "UNIQUEID")) {
+			s = ast_channel_uniqueid(c);
+		} else if (!strcmp(var, "HANGUPCAUSE")) {
+			ast_str_set(str, maxlen, "%d", ast_channel_hangupcause(c));
+			s = ast_str_buffer(*str);
+		}
+	}
+	if (s == &not_found) { /* look for more */
+		if (!strcmp(var, "EPOCH")) {
+			ast_str_set(str, maxlen, "%d", (int) time(NULL));
+			s = ast_str_buffer(*str);
+		} else if (!strcmp(var, "SYSTEMNAME")) {
+			s = ast_config_AST_SYSTEM_NAME;
+		} else if (!strcmp(var, "ASTETCDIR")) {
+			s = ast_config_AST_CONFIG_DIR;
+		} else if (!strcmp(var, "ASTMODDIR")) {
+			s = ast_config_AST_MODULE_DIR;
+		} else if (!strcmp(var, "ASTVARLIBDIR")) {
+			s = ast_config_AST_VAR_DIR;
+		} else if (!strcmp(var, "ASTDBDIR")) {
+			s = ast_config_AST_DB;
+		} else if (!strcmp(var, "ASTKEYDIR")) {
+			s = ast_config_AST_KEY_DIR;
+		} else if (!strcmp(var, "ASTDATADIR")) {
+			s = ast_config_AST_DATA_DIR;
+		} else if (!strcmp(var, "ASTAGIDIR")) {
+			s = ast_config_AST_AGI_DIR;
+		} else if (!strcmp(var, "ASTSPOOLDIR")) {
+			s = ast_config_AST_SPOOL_DIR;
+		} else if (!strcmp(var, "ASTRUNDIR")) {
+			s = ast_config_AST_RUN_DIR;
+		} else if (!strcmp(var, "ASTLOGDIR")) {
+			s = ast_config_AST_LOG_DIR;
+		} else if (!strcmp(var, "ENTITYID")) {
+			ast_eid_to_str(workspace, sizeof(workspace), &ast_eid_default);
+			s = workspace;
+		}
+	}
+	/* if not found, look into chanvars or global vars */
+	for (i = 0; s == &not_found && i < ARRAY_LEN(places); i++) {
+		struct ast_var_t *variables;
+		if (!places[i])
+			continue;
+		if (places[i] == &globals)
+			ast_rwlock_rdlock(&globalslock);
+		AST_LIST_TRAVERSE(places[i], variables, entries) {
+			if (!strcmp(ast_var_name(variables), var)) {
+				s = ast_var_value(variables);
+				break;
+			}
+		}
+		if (places[i] == &globals)
+			ast_rwlock_unlock(&globalslock);
+	}
+	if (s == &not_found || s == NULL) {
+		ast_debug(5, "Result of '%s' is NULL\n", var);
+		ret = NULL;
+	} else {
+		ast_debug(5, "Result of '%s' is '%s'\n", var, s);
+		if (s != ast_str_buffer(*str)) {
+			ast_str_set(str, maxlen, "%s", s);
+		}
+		ret = ast_str_buffer(*str);
+		if (need_substring) {
+			ret = ast_str_substring(*str, offset, length);
+			ast_debug(2, "Final result of '%s' is '%s'\n", var, ret);
+		}
+	}
+
+	if (c) {
+		ast_channel_unlock(c);
+	}
+	return ret;
+}
+
+void ast_str_substitute_variables_full(struct ast_str **buf, ssize_t maxlen, struct ast_channel *c, struct varshead *headp, const char *templ, size_t *used)
+{
+	/* Substitutes variables into buf, based on string templ */
+	char *cp4 = NULL;
+	const char *whereweare;
+	int orig_size = 0;
+	int offset, offset2, isfunction;
+	const char *nextvar, *nextexp, *nextthing;
+	const char *vars, *vare;
+	char *finalvars;
+	int pos, brackets, needsub, len;
+	struct ast_str *substr1 = ast_str_create(16), *substr2 = NULL, *substr3 = ast_str_create(16);
+
+	ast_str_reset(*buf);
+	whereweare = templ;
+	while (!ast_strlen_zero(whereweare)) {
+		/* reset our buffer */
+		ast_str_reset(substr3);
+
+		/* Assume we're copying the whole remaining string */
+		pos = strlen(whereweare);
+		nextvar = NULL;
+		nextexp = NULL;
+		nextthing = strchr(whereweare, '$');
+		if (nextthing) {
+			switch (nextthing[1]) {
+			case '{':
+				nextvar = nextthing;
+				pos = nextvar - whereweare;
+				break;
+			case '[':
+				nextexp = nextthing;
+				pos = nextexp - whereweare;
+				break;
+			default:
+				pos = 1;
+			}
+		}
+
+		if (pos) {
+			/* Copy that many bytes */
+			ast_str_append_substr(buf, maxlen, whereweare, pos);
+
+			templ += pos;
+			whereweare += pos;
+		}
+
+		if (nextvar) {
+			/* We have a variable.  Find the start and end, and determine
+			   if we are going to have to recursively call ourselves on the
+			   contents */
+			vars = vare = nextvar + 2;
+			brackets = 1;
+			needsub = 0;
+
+			/* Find the end of it */
+			while (brackets && *vare) {
+				if ((vare[0] == '$') && (vare[1] == '{')) {
+					needsub++;
+				} else if (vare[0] == '{') {
+					brackets++;
+				} else if (vare[0] == '}') {
+					brackets--;
+				} else if ((vare[0] == '$') && (vare[1] == '['))
+					needsub++;
+				vare++;
+			}
+			if (brackets)
+				ast_log(LOG_WARNING, "Error in extension logic (missing '}')\n");
+			len = vare - vars - 1;
+
+			/* Skip totally over variable string */
+			whereweare += (len + 3);
+
+			/* Store variable name (and truncate) */
+			ast_str_set_substr(&substr1, 0, vars, len);
+			ast_debug(5, "Evaluating '%s' (from '%s' len %d)\n", ast_str_buffer(substr1), vars, len);
+
+			/* Substitute if necessary */
+			if (needsub) {
+				size_t my_used;
+
+				if (!substr2) {
+					substr2 = ast_str_create(16);
+				}
+				ast_str_substitute_variables_full(&substr2, 0, c, headp, ast_str_buffer(substr1), &my_used);
+				finalvars = ast_str_buffer(substr2);
+			} else {
+				finalvars = ast_str_buffer(substr1);
+			}
+
+			parse_variable_name(finalvars, &offset, &offset2, &isfunction);
+			if (isfunction) {
+				/* Evaluate function */
+				if (c || !headp) {
+					cp4 = ast_func_read2(c, finalvars, &substr3, 0) ? NULL : ast_str_buffer(substr3);
+				} else {
+					struct varshead old;
+					struct ast_channel *bogus = ast_dummy_channel_alloc();
+					if (bogus) {
+						memcpy(&old, ast_channel_varshead(bogus), sizeof(old));
+						memcpy(ast_channel_varshead(bogus), headp, sizeof(*ast_channel_varshead(bogus)));
+						cp4 = ast_func_read2(c, finalvars, &substr3, 0) ? NULL : ast_str_buffer(substr3);
+						/* Don't deallocate the varshead that was passed in */
+						memcpy(ast_channel_varshead(bogus), &old, sizeof(*ast_channel_varshead(bogus)));
+						ast_channel_unref(bogus);
+					} else {
+						ast_log(LOG_ERROR, "Unable to allocate bogus channel for variable substitution.  Function results may be blank.\n");
+					}
+				}
+				ast_debug(2, "Function %s result is '%s'\n", finalvars, cp4 ? cp4 : "(null)");
+			} else {
+				/* Retrieve variable value */
+				ast_str_retrieve_variable(&substr3, 0, c, headp, finalvars);
+				cp4 = ast_str_buffer(substr3);
+			}
+			if (cp4) {
+				ast_str_substring(substr3, offset, offset2);
+				ast_str_append(buf, maxlen, "%s", ast_str_buffer(substr3));
+			}
+		} else if (nextexp) {
+			/* We have an expression.  Find the start and end, and determine
+			   if we are going to have to recursively call ourselves on the
+			   contents */
+			vars = vare = nextexp + 2;
+			brackets = 1;
+			needsub = 0;
+
+			/* Find the end of it */
+			while (brackets && *vare) {
+				if ((vare[0] == '$') && (vare[1] == '[')) {
+					needsub++;
+					brackets++;
+					vare++;
+				} else if (vare[0] == '[') {
+					brackets++;
+				} else if (vare[0] == ']') {
+					brackets--;
+				} else if ((vare[0] == '$') && (vare[1] == '{')) {
+					needsub++;
+					vare++;
+				}
+				vare++;
+			}
+			if (brackets)
+				ast_log(LOG_WARNING, "Error in extension logic (missing ']')\n");
+			len = vare - vars - 1;
+
+			/* Skip totally over expression */
+			whereweare += (len + 3);
+
+			/* Store variable name (and truncate) */
+			ast_str_set_substr(&substr1, 0, vars, len);
+
+			/* Substitute if necessary */
+			if (needsub) {
+				size_t my_used;
+
+				if (!substr2) {
+					substr2 = ast_str_create(16);
+				}
+				ast_str_substitute_variables_full(&substr2, 0, c, headp, ast_str_buffer(substr1), &my_used);
+				finalvars = ast_str_buffer(substr2);
+			} else {
+				finalvars = ast_str_buffer(substr1);
+			}
+
+			if (ast_str_expr(&substr3, 0, c, finalvars)) {
+				ast_debug(2, "Expression result is '%s'\n", ast_str_buffer(substr3));
+			}
+			ast_str_append(buf, maxlen, "%s", ast_str_buffer(substr3));
+		}
+	}
+	*used = ast_str_strlen(*buf) - orig_size;
+	ast_free(substr1);
+	ast_free(substr2);
+	ast_free(substr3);
+}
+
+void ast_str_substitute_variables(struct ast_str **buf, ssize_t maxlen, struct ast_channel *chan, const char *templ)
+{
+	size_t used;
+	ast_str_substitute_variables_full(buf, maxlen, chan, NULL, templ, &used);
+}
+
+void ast_str_substitute_variables_varshead(struct ast_str **buf, ssize_t maxlen, struct varshead *headp, const char *templ)
+{
+	size_t used;
+	ast_str_substitute_variables_full(buf, maxlen, NULL, headp, templ, &used);
+}
+
+void pbx_substitute_variables_helper_full(struct ast_channel *c, struct varshead *headp, const char *cp1, char *cp2, int count, size_t *used)
+{
+	/* Substitutes variables into cp2, based on string cp1, cp2 NO LONGER NEEDS TO BE ZEROED OUT!!!!  */
+	char *cp4 = NULL;
+	const char *whereweare, *orig_cp2 = cp2;
+	int length, offset, offset2, isfunction;
+	char *workspace = NULL;
+	char *ltmp = NULL, *var = NULL;
+	char *nextvar, *nextexp, *nextthing;
+	char *vars, *vare;
+	int pos, brackets, needsub, len;
+
+	*cp2 = 0; /* just in case nothing ends up there */
+	whereweare = cp1;
+	while (!ast_strlen_zero(whereweare) && count) {
+		/* Assume we're copying the whole remaining string */
+		pos = strlen(whereweare);
+		nextvar = NULL;
+		nextexp = NULL;
+		nextthing = strchr(whereweare, '$');
+		if (nextthing) {
+			switch (nextthing[1]) {
+			case '{':
+				nextvar = nextthing;
+				pos = nextvar - whereweare;
+				break;
+			case '[':
+				nextexp = nextthing;
+				pos = nextexp - whereweare;
+				break;
+			default:
+				pos = 1;
+			}
+		}
+
+		if (pos) {
+			/* Can't copy more than 'count' bytes */
+			if (pos > count)
+				pos = count;
+
+			/* Copy that many bytes */
+			memcpy(cp2, whereweare, pos);
+
+			count -= pos;
+			cp2 += pos;
+			whereweare += pos;
+			*cp2 = 0;
+		}
+
+		if (nextvar) {
+			/* We have a variable.  Find the start and end, and determine
+			   if we are going to have to recursively call ourselves on the
+			   contents */
+			vars = vare = nextvar + 2;
+			brackets = 1;
+			needsub = 0;
+
+			/* Find the end of it */
+			while (brackets && *vare) {
+				if ((vare[0] == '$') && (vare[1] == '{')) {
+					needsub++;
+				} else if (vare[0] == '{') {
+					brackets++;
+				} else if (vare[0] == '}') {
+					brackets--;
+				} else if ((vare[0] == '$') && (vare[1] == '['))
+					needsub++;
+				vare++;
+			}
+			if (brackets)
+				ast_log(LOG_WARNING, "Error in extension logic (missing '}')\n");
+			len = vare - vars - 1;
+
+			/* Skip totally over variable string */
+			whereweare += (len + 3);
+
+			if (!var)
+				var = ast_alloca(VAR_BUF_SIZE);
+
+			/* Store variable name (and truncate) */
+			ast_copy_string(var, vars, len + 1);
+
+			/* Substitute if necessary */
+			if (needsub) {
+				size_t my_used;
+
+				if (!ltmp) {
+					ltmp = ast_alloca(VAR_BUF_SIZE);
+				}
+				pbx_substitute_variables_helper_full(c, headp, var, ltmp, VAR_BUF_SIZE - 1, &my_used);
+				vars = ltmp;
+			} else {
+				vars = var;
+			}
+
+			if (!workspace)
+				workspace = ast_alloca(VAR_BUF_SIZE);
+
+			workspace[0] = '\0';
+
+			parse_variable_name(vars, &offset, &offset2, &isfunction);
+			if (isfunction) {
+				/* Evaluate function */
+				if (c || !headp)
+					cp4 = ast_func_read(c, vars, workspace, VAR_BUF_SIZE) ? NULL : workspace;
+				else {
+					struct varshead old;
+					struct ast_channel *c = ast_dummy_channel_alloc();
+					if (c) {
+						memcpy(&old, ast_channel_varshead(c), sizeof(old));
+						memcpy(ast_channel_varshead(c), headp, sizeof(*ast_channel_varshead(c)));
+						cp4 = ast_func_read(c, vars, workspace, VAR_BUF_SIZE) ? NULL : workspace;
+						/* Don't deallocate the varshead that was passed in */
+						memcpy(ast_channel_varshead(c), &old, sizeof(*ast_channel_varshead(c)));
+						c = ast_channel_unref(c);
+					} else {
+						ast_log(LOG_ERROR, "Unable to allocate bogus channel for variable substitution.  Function results may be blank.\n");
+					}
+				}
+				ast_debug(2, "Function %s result is '%s'\n", vars, cp4 ? cp4 : "(null)");
+			} else {
+				/* Retrieve variable value */
+				pbx_retrieve_variable(c, vars, &cp4, workspace, VAR_BUF_SIZE, headp);
+			}
+			if (cp4) {
+				cp4 = substring(cp4, offset, offset2, workspace, VAR_BUF_SIZE);
+
+				length = strlen(cp4);
+				if (length > count)
+					length = count;
+				memcpy(cp2, cp4, length);
+				count -= length;
+				cp2 += length;
+				*cp2 = 0;
+			}
+		} else if (nextexp) {
+			/* We have an expression.  Find the start and end, and determine
+			   if we are going to have to recursively call ourselves on the
+			   contents */
+			vars = vare = nextexp + 2;
+			brackets = 1;
+			needsub = 0;
+
+			/* Find the end of it */
+			while (brackets && *vare) {
+				if ((vare[0] == '$') && (vare[1] == '[')) {
+					needsub++;
+					brackets++;
+					vare++;
+				} else if (vare[0] == '[') {
+					brackets++;
+				} else if (vare[0] == ']') {
+					brackets--;
+				} else if ((vare[0] == '$') && (vare[1] == '{')) {
+					needsub++;
+					vare++;
+				}
+				vare++;
+			}
+			if (brackets)
+				ast_log(LOG_WARNING, "Error in extension logic (missing ']')\n");
+			len = vare - vars - 1;
+
+			/* Skip totally over expression */
+			whereweare += (len + 3);
+
+			if (!var)
+				var = ast_alloca(VAR_BUF_SIZE);
+
+			/* Store variable name (and truncate) */
+			ast_copy_string(var, vars, len + 1);
+
+			/* Substitute if necessary */
+			if (needsub) {
+				size_t my_used;
+
+				if (!ltmp) {
+					ltmp = ast_alloca(VAR_BUF_SIZE);
+				}
+				pbx_substitute_variables_helper_full(c, headp, var, ltmp, VAR_BUF_SIZE - 1, &my_used);
+				vars = ltmp;
+			} else {
+				vars = var;
+			}
+
+			length = ast_expr(vars, cp2, count, c);
+
+			if (length) {
+				ast_debug(1, "Expression result is '%s'\n", cp2);
+				count -= length;
+				cp2 += length;
+				*cp2 = 0;
+			}
+		}
+	}
+	*used = cp2 - orig_cp2;
+}
+
+void pbx_substitute_variables_helper(struct ast_channel *c, const char *cp1, char *cp2, int count)
+{
+	size_t used;
+	pbx_substitute_variables_helper_full(c, (c) ? ast_channel_varshead(c) : NULL, cp1, cp2, count, &used);
+}
+
+void pbx_substitute_variables_varshead(struct varshead *headp, const char *cp1, char *cp2, int count)
+{
+	size_t used;
+	pbx_substitute_variables_helper_full(NULL, headp, cp1, cp2, count, &used);
+}
+
+/*! \brief CLI support for listing global variables in a parseable way */
+static char *handle_show_globals(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+	int i = 0;
+	struct ast_var_t *newvariable;
+
+	switch (cmd) {
+	case CLI_INIT:
+		e->command = "dialplan show globals";
+		e->usage =
+			"Usage: dialplan show globals\n"
+			"       List current global dialplan variables and their values\n";
+		return NULL;
+	case CLI_GENERATE:
+		return NULL;
+	}
+
+	ast_rwlock_rdlock(&globalslock);
+	AST_LIST_TRAVERSE (&globals, newvariable, entries) {
+		i++;
+		ast_cli(a->fd, "   %s=%s\n", ast_var_name(newvariable), ast_var_value(newvariable));
+	}
+	ast_rwlock_unlock(&globalslock);
+	ast_cli(a->fd, "\n    -- %d variable(s)\n", i);
+
+	return CLI_SUCCESS;
+}
+
+/*! \brief CLI support for listing chanvar's variables in a parseable way */
+static char *handle_show_chanvar(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+	struct ast_channel *chan;
+	struct ast_var_t *var;
+
+	switch (cmd) {
+	case CLI_INIT:
+		e->command = "dialplan show chanvar";
+		e->usage =
+			"Usage: dialplan show chanvar <channel>\n"
+			"       List current channel variables and their values\n";
+		return NULL;
+	case CLI_GENERATE:
+		return ast_complete_channels(a->line, a->word, a->pos, a->n, 3);
+	}
+
+	if (a->argc != e->args + 1) {
+		return CLI_SHOWUSAGE;
+	}
+
+	chan = ast_channel_get_by_name(a->argv[e->args]);
+	if (!chan) {
+		ast_cli(a->fd, "Channel '%s' not found\n", a->argv[e->args]);
+		return CLI_FAILURE;
+	}
+
+	ast_channel_lock(chan);
+	AST_LIST_TRAVERSE(ast_channel_varshead(chan), var, entries) {
+		ast_cli(a->fd, "%s=%s\n", ast_var_name(var), ast_var_value(var));
+	}
+	ast_channel_unlock(chan);
+
+	ast_channel_unref(chan);
+	return CLI_SUCCESS;
+}
+
+static char *handle_set_global(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+	switch (cmd) {
+	case CLI_INIT:
+		e->command = "dialplan set global";
+		e->usage =
+			"Usage: dialplan set global <name> <value>\n"
+			"       Set global dialplan variable <name> to <value>\n";
+		return NULL;
+	case CLI_GENERATE:
+		return NULL;
+	}
+
+	if (a->argc != e->args + 2)
+		return CLI_SHOWUSAGE;
+
+	pbx_builtin_setvar_helper(NULL, a->argv[3], a->argv[4]);
+	ast_cli(a->fd, "\n    -- Global variable '%s' set to '%s'\n", a->argv[3], a->argv[4]);
+
+	return CLI_SUCCESS;
+}
+
+static char *handle_set_chanvar(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+	struct ast_channel *chan;
+	const char *chan_name, *var_name, *var_value;
+
+	switch (cmd) {
+	case CLI_INIT:
+		e->command = "dialplan set chanvar";
+		e->usage =
+			"Usage: dialplan set chanvar <channel> <varname> <value>\n"
+			"       Set channel variable <varname> to <value>\n";
+		return NULL;
+	case CLI_GENERATE:
+		return ast_complete_channels(a->line, a->word, a->pos, a->n, 3);
+	}
+
+	if (a->argc != e->args + 3)
+		return CLI_SHOWUSAGE;
+
+	chan_name = a->argv[e->args];
+	var_name = a->argv[e->args + 1];
+	var_value = a->argv[e->args + 2];
+
+	if (!(chan = ast_channel_get_by_name(chan_name))) {
+		ast_cli(a->fd, "Channel '%s' not found\n", chan_name);
+		return CLI_FAILURE;
+	}
+
+	pbx_builtin_setvar_helper(chan, var_name, var_value);
+
+	chan = ast_channel_unref(chan);
+
+	ast_cli(a->fd, "\n    -- Channel variable '%s' set to '%s' for '%s'\n",  var_name, var_value, chan_name);
+
+	return CLI_SUCCESS;
+}
+
+int pbx_builtin_serialize_variables(struct ast_channel *chan, struct ast_str **buf)
+{
+	struct ast_var_t *variables;
+	const char *var, *val;
+	int total = 0;
+
+	if (!chan)
+		return 0;
+
+	ast_str_reset(*buf);
+
+	ast_channel_lock(chan);
+
+	AST_LIST_TRAVERSE(ast_channel_varshead(chan), variables, entries) {
+		if ((var = ast_var_name(variables)) && (val = ast_var_value(variables))
+		   /* && !ast_strlen_zero(var) && !ast_strlen_zero(val) */
+		   ) {
+			if (ast_str_append(buf, 0, "%s=%s\n", var, val) < 0) {
+				ast_log(LOG_ERROR, "Data Buffer Size Exceeded!\n");
+				break;
+			} else
+				total++;
+		} else
+			break;
+	}
+
+	ast_channel_unlock(chan);
+
+	return total;
+}
+
+const char *pbx_builtin_getvar_helper(struct ast_channel *chan, const char *name)
+{
+	struct ast_var_t *variables;
+	const char *ret = NULL;
+	int i;
+	struct varshead *places[2] = { NULL, &globals };
+
+	if (!name)
+		return NULL;
+
+	if (chan) {
+		ast_channel_lock(chan);
+		places[0] = ast_channel_varshead(chan);
+	}
+
+	for (i = 0; i < 2; i++) {
+		if (!places[i])
+			continue;
+		if (places[i] == &globals)
+			ast_rwlock_rdlock(&globalslock);
+		AST_LIST_TRAVERSE(places[i], variables, entries) {
+			if (!strcmp(name, ast_var_name(variables))) {
+				ret = ast_var_value(variables);
+				break;
+			}
+		}
+		if (places[i] == &globals)
+			ast_rwlock_unlock(&globalslock);
+		if (ret)
+			break;
+	}
+
+	if (chan)
+		ast_channel_unlock(chan);
+
+	return ret;
+}
+
+void pbx_builtin_pushvar_helper(struct ast_channel *chan, const char *name, const char *value)
+{
+	struct ast_var_t *newvariable;
+	struct varshead *headp;
+
+	if (name[strlen(name)-1] == ')') {
+		char *function = ast_strdupa(name);
+
+		ast_log(LOG_WARNING, "Cannot push a value onto a function\n");
+		ast_func_write(chan, function, value);
+		return;
+	}
+
+	if (chan) {
+		ast_channel_lock(chan);
+		headp = ast_channel_varshead(chan);
+	} else {
+		ast_rwlock_wrlock(&globalslock);
+		headp = &globals;
+	}
+
+	if (value && (newvariable = ast_var_assign(name, value))) {
+		if (headp == &globals)
+			ast_verb(2, "Setting global variable '%s' to '%s'\n", name, value);
+		AST_LIST_INSERT_HEAD(headp, newvariable, entries);
+	}
+
+	if (chan)
+		ast_channel_unlock(chan);
+	else
+		ast_rwlock_unlock(&globalslock);
+}
+
+int pbx_builtin_setvar_helper(struct ast_channel *chan, const char *name, const char *value)
+{
+	struct ast_var_t *newvariable;
+	struct varshead *headp;
+	const char *nametail = name;
+	/*! True if the old value was not an empty string. */
+	int old_value_existed = 0;
+
+	if (name[strlen(name) - 1] == ')') {
+		char *function = ast_strdupa(name);
+
+		return ast_func_write(chan, function, value);
+	}
+
+	if (chan) {
+		ast_channel_lock(chan);
+		headp = ast_channel_varshead(chan);
+	} else {
+		ast_rwlock_wrlock(&globalslock);
+		headp = &globals;
+	}
+
+	/* For comparison purposes, we have to strip leading underscores */
+	if (*nametail == '_') {
+		nametail++;
+		if (*nametail == '_')
+			nametail++;
+	}
+
+	AST_LIST_TRAVERSE_SAFE_BEGIN(headp, newvariable, entries) {
+		if (strcmp(ast_var_name(newvariable), nametail) == 0) {
+			/* there is already such a variable, delete it */
+			AST_LIST_REMOVE_CURRENT(entries);
+			old_value_existed = !ast_strlen_zero(ast_var_value(newvariable));
+			ast_var_delete(newvariable);
+			break;
+		}
+	}
+	AST_LIST_TRAVERSE_SAFE_END;
+
+	if (value && (newvariable = ast_var_assign(name, value))) {
+		if (headp == &globals) {
+			ast_verb(2, "Setting global variable '%s' to '%s'\n", name, value);
+		}
+		AST_LIST_INSERT_HEAD(headp, newvariable, entries);
+		ast_channel_publish_varset(chan, name, value);
+	} else if (old_value_existed) {
+		/* We just deleted a non-empty dialplan variable. */
+		ast_channel_publish_varset(chan, name, "");
+	}
+
+	if (chan)
+		ast_channel_unlock(chan);
+	else
+		ast_rwlock_unlock(&globalslock);
+	return 0;
+}
+
+int pbx_builtin_setvar(struct ast_channel *chan, const char *data)
+{
+	char *name, *value, *mydata;
+
+	if (ast_strlen_zero(data)) {
+		ast_log(LOG_WARNING, "Set requires one variable name/value pair.\n");
+		return 0;
+	}
+
+	mydata = ast_strdupa(data);
+	name = strsep(&mydata, "=");
+	value = mydata;
+	if (!value) {
+		ast_log(LOG_WARNING, "Set requires an '=' to be a valid assignment.\n");
+		return 0;
+	}
+
+	if (strchr(name, ' ')) {
+		ast_log(LOG_WARNING, "Please avoid unnecessary spaces on variables as it may lead to unexpected results ('%s' set to '%s').\n", name, mydata);
+	}
+
+	pbx_builtin_setvar_helper(chan, name, value);
+
+	return 0;
+}
+
+int pbx_builtin_setvar_multiple(struct ast_channel *chan, const char *vdata)
+{
+	char *data;
+	int x;
+	AST_DECLARE_APP_ARGS(args,
+		AST_APP_ARG(pair)[24];
+	);
+	AST_DECLARE_APP_ARGS(pair,
+		AST_APP_ARG(name);
+		AST_APP_ARG(value);
+	);
+
+	if (ast_strlen_zero(vdata)) {
+		ast_log(LOG_WARNING, "MSet requires at least one variable name/value pair.\n");
+		return 0;
+	}
+
+	data = ast_strdupa(vdata);
+	AST_STANDARD_APP_ARGS(args, data);
+
+	for (x = 0; x < args.argc; x++) {
+		AST_NONSTANDARD_APP_ARGS(pair, args.pair[x], '=');
+		if (pair.argc == 2) {
+			pbx_builtin_setvar_helper(chan, pair.name, pair.value);
+			if (strchr(pair.name, ' '))
+				ast_log(LOG_WARNING, "Please avoid unnecessary spaces on variables as it may lead to unexpected results ('%s' set to '%s').\n", pair.name, pair.value);
+		} else if (!chan) {
+			ast_log(LOG_WARNING, "MSet: ignoring entry '%s' with no '='\n", pair.name);
+		} else {
+			ast_log(LOG_WARNING, "MSet: ignoring entry '%s' with no '=' (in %s@%s:%d\n", pair.name, ast_channel_exten(chan), ast_channel_context(chan), ast_channel_priority(chan));
+		}
+	}
+
+	return 0;
+}
+
+void pbx_builtin_clear_globals(void)
+{
+	struct ast_var_t *vardata;
+
+	ast_rwlock_wrlock(&globalslock);
+	while ((vardata = AST_LIST_REMOVE_HEAD(&globals, entries)))
+		ast_var_delete(vardata);
+	ast_rwlock_unlock(&globalslock);
+}
+
+static struct ast_cli_entry vars_cli[] = {
+	AST_CLI_DEFINE(handle_show_globals, "Show global dialplan variables"),
+	AST_CLI_DEFINE(handle_show_chanvar, "Show channel variables"),
+	AST_CLI_DEFINE(handle_set_global, "Set global dialplan variable"),
+	AST_CLI_DEFINE(handle_set_chanvar, "Set a channel variable"),
+};
+
+static void unload_pbx_variables(void)
+{
+	ast_cli_unregister_multiple(vars_cli, ARRAY_LEN(vars_cli));
+	ast_unregister_application("Set");
+	ast_unregister_application("MSet");
+	pbx_builtin_clear_globals();
+}
+
+int load_pbx_variables(void)
+{
+	int res = 0;
+
+	res |= ast_cli_register_multiple(vars_cli, ARRAY_LEN(vars_cli));
+	res |= ast_register_application2("Set", pbx_builtin_setvar, NULL, NULL, NULL);
+	res |= ast_register_application2("MSet", pbx_builtin_setvar_multiple, NULL, NULL, NULL);
+	ast_register_cleanup(unload_pbx_variables);
+
+	return res;
+}
diff --git a/main/sched.c b/main/sched.c
index f851670..222c691 100644
--- a/main/sched.c
+++ b/main/sched.c
@@ -83,6 +83,14 @@ struct sched {
 	/*! The ID that has been popped off the scheduler context's queue */
 	struct sched_id *sched_id;
 	struct timeval when;          /*!< Absolute time event should take place */
+	/*!
+	 * \brief Tie breaker in case the when is the same for multiple entries.
+	 *
+	 * \note The oldest expiring entry in the scheduler heap goes first.
+	 * This is possible when multiple events are scheduled to expire at
+	 * the same time by internal coding.
+	 */
+	unsigned int tie_breaker;
 	int resched;                  /*!< When to reschedule */
 	int variable;                 /*!< Use return value from callback to reschedule */
 	const void *data;             /*!< Data */
@@ -107,6 +115,8 @@ struct ast_sched_context {
 	ast_mutex_t lock;
 	unsigned int eventcnt;                  /*!< Number of events processed */
 	unsigned int highwater;					/*!< highest count so far */
+	/*! Next tie breaker in case events expire at the same time. */
+	unsigned int tie_breaker;
 	struct ast_heap *sched_heap;
 	struct sched_thread *sched_thread;
 	/*! The scheduled task that is currently executing */
@@ -213,9 +223,17 @@ int ast_sched_start_thread(struct ast_sched_context *con)
 	return 0;
 }
 
-static int sched_time_cmp(void *a, void *b)
+static int sched_time_cmp(void *va, void *vb)
 {
-	return ast_tvcmp(((struct sched *) b)->when, ((struct sched *) a)->when);
+	struct sched *a = va;
+	struct sched *b = vb;
+	int cmp;
+
+	cmp = ast_tvcmp(b->when, a->when);
+	if (!cmp) {
+		cmp = b->tie_breaker - a->tie_breaker;
+	}
+	return cmp;
 }
 
 struct ast_sched_context *ast_sched_context_create(void)
@@ -442,11 +460,28 @@ int ast_sched_wait(struct ast_sched_context *con)
  */
 static void schedule(struct ast_sched_context *con, struct sched *s)
 {
-	ast_heap_push(con->sched_heap, s);
+	size_t size;
+
+	size = ast_heap_size(con->sched_heap);
 
-	if (ast_heap_size(con->sched_heap) > con->highwater) {
-		con->highwater = ast_heap_size(con->sched_heap);
+	/* Record the largest the scheduler heap became for reporting purposes. */
+	if (con->highwater <= size) {
+		con->highwater = size + 1;
 	}
+
+	/* Determine the tie breaker value for the new entry. */
+	if (size) {
+		++con->tie_breaker;
+	} else {
+		/*
+		 * Restart the sequence for the first entry to make integer
+		 * roll over more unlikely.
+		 */
+		con->tie_breaker = 0;
+	}
+	s->tie_breaker = con->tie_breaker;
+
+	ast_heap_push(con->sched_heap, s);
 }
 
 /*! \brief
diff --git a/main/sorcery.c b/main/sorcery.c
index e78fc5c..3a29cfa 100644
--- a/main/sorcery.c
+++ b/main/sorcery.c
@@ -820,7 +820,7 @@ static struct ast_sorcery_object_type *sorcery_object_type_alloc(const char *typ
 {
 #define INITIAL_WIZARD_VECTOR_SIZE 5
 	struct ast_sorcery_object_type *object_type;
-	char uuid[AST_UUID_STR_LEN];
+	char tps_name[AST_TASKPROCESSOR_MAX_NAME + 1];
 
 	if (!(object_type = ao2_alloc(sizeof(*object_type), sorcery_object_type_destructor))) {
 		return NULL;
@@ -853,12 +853,10 @@ static struct ast_sorcery_object_type *sorcery_object_type_alloc(const char *typ
 		return NULL;
 	}
 
-	if (!ast_uuid_generate_str(uuid, sizeof(uuid))) {
-		ao2_ref(object_type, -1);
-		return NULL;
-	}
+	/* Create name with seq number appended. */
+	ast_taskprocessor_build_name(tps_name, sizeof(tps_name), "sorcery/%s", type);
 
-	if (!(object_type->serializer = ast_threadpool_serializer(uuid, threadpool))) {
+	if (!(object_type->serializer = ast_threadpool_serializer(tps_name, threadpool))) {
 		ao2_ref(object_type, -1);
 		return NULL;
 	}
@@ -1955,7 +1953,12 @@ static int sorcery_wizard_create(void *obj, void *arg, int flags)
 		ast_debug(5, "Sorcery wizard '%s' does not support creation\n", object_wizard->wizard->callbacks.name);
 		return 0;
 	}
-	return (!object_wizard->caching && !object_wizard->wizard->callbacks.create(details->sorcery, object_wizard->data, details->obj)) ? CMP_MATCH | CMP_STOP : 0;
+
+	if (object_wizard->wizard->callbacks.create(details->sorcery, object_wizard->data, details->obj)) {
+		return 0;
+	}
+
+	return CMP_MATCH;
 }
 
 /*! \brief Internal callback function which notifies an individual observer that an object has been created */
@@ -2000,17 +2003,31 @@ int ast_sorcery_create(const struct ast_sorcery *sorcery, void *object)
 	AST_VECTOR_RW_RDLOCK(&object_type->wizards);
 	for (i = 0; i < AST_VECTOR_SIZE(&object_type->wizards); i++) {
 		found_wizard = AST_VECTOR_GET(&object_type->wizards, i);
-		if (sorcery_wizard_create(found_wizard, &sdetails, 0) == (CMP_MATCH | CMP_STOP)) {
+		if (!found_wizard->caching && sorcery_wizard_create(found_wizard, &sdetails, 0) == CMP_MATCH) {
 			object_wizard = found_wizard;
-			if(ao2_container_count(object_type->observers)) {
-				struct sorcery_observer_invocation *invocation = sorcery_observer_invocation_alloc(object_type, object);
+		}
+	}
 
-				if (invocation && ast_taskprocessor_push(object_type->serializer, sorcery_observers_notify_create, invocation)) {
-					ao2_cleanup(invocation);
-				}
+	if (object_wizard) {
+		for (i = 0; i < AST_VECTOR_SIZE(&object_type->wizards); i++) {
+			found_wizard = AST_VECTOR_GET(&object_type->wizards, i);
+			if (found_wizard->caching) {
+				sorcery_wizard_create(found_wizard, &sdetails, 0);
+			}
+		}
+
+		if (ao2_container_count(object_type->observers)) {
+			struct sorcery_observer_invocation *invocation = sorcery_observer_invocation_alloc(
+				object_type, object);
+
+			if (invocation
+				&& ast_taskprocessor_push(object_type->serializer, sorcery_observers_notify_create,
+					invocation)) {
+				ao2_cleanup(invocation);
 			}
 		}
 	}
+
 	AST_VECTOR_RW_UNLOCK(&object_type->wizards);
 
 	return object_wizard ? 0 : -1;
@@ -2050,8 +2067,11 @@ static int sorcery_wizard_update(void *obj, void *arg, int flags)
 		return 0;
 	}
 
-	return (!object_wizard->wizard->callbacks.update(details->sorcery, object_wizard->data, details->obj) &&
-		!object_wizard->caching) ? CMP_MATCH | CMP_STOP : 0;
+	if (object_wizard->wizard->callbacks.update(details->sorcery, object_wizard->data, details->obj)) {
+		return 0;
+	}
+
+	return CMP_MATCH;
 }
 
 int ast_sorcery_update(const struct ast_sorcery *sorcery, void *object)
@@ -2073,17 +2093,31 @@ int ast_sorcery_update(const struct ast_sorcery *sorcery, void *object)
 	AST_VECTOR_RW_RDLOCK(&object_type->wizards);
 	for (i = 0; i < AST_VECTOR_SIZE(&object_type->wizards); i++) {
 		found_wizard = AST_VECTOR_GET(&object_type->wizards, i);
-		if (sorcery_wizard_update(found_wizard, &sdetails, 0) == (CMP_MATCH | CMP_STOP)) {
+		if (!found_wizard->caching && sorcery_wizard_update(found_wizard, &sdetails, 0) == CMP_MATCH) {
 			object_wizard = found_wizard;
-			if (ao2_container_count(object_type->observers)) {
-				struct sorcery_observer_invocation *invocation = sorcery_observer_invocation_alloc(object_type, object);
+		}
+	}
 
-				if (invocation && ast_taskprocessor_push(object_type->serializer, sorcery_observers_notify_update, invocation)) {
-					ao2_cleanup(invocation);
-				}
+	if (object_wizard) {
+		for (i = 0; i < AST_VECTOR_SIZE(&object_type->wizards); i++) {
+			found_wizard = AST_VECTOR_GET(&object_type->wizards, i);
+			if (found_wizard->caching) {
+				sorcery_wizard_update(found_wizard, &sdetails, 0);
+			}
+		}
+
+		if (ao2_container_count(object_type->observers)) {
+			struct sorcery_observer_invocation *invocation = sorcery_observer_invocation_alloc(
+				object_type, object);
+
+			if (invocation
+				&& ast_taskprocessor_push(object_type->serializer, sorcery_observers_notify_update,
+					invocation)) {
+				ao2_cleanup(invocation);
 			}
 		}
 	}
+
 	AST_VECTOR_RW_UNLOCK(&object_type->wizards);
 
 	return object_wizard ? 0 : -1;
@@ -2123,8 +2157,11 @@ static int sorcery_wizard_delete(void *obj, void *arg, int flags)
 		return 0;
 	}
 
-	return (!object_wizard->wizard->callbacks.delete(details->sorcery, object_wizard->data, details->obj) &&
-		!object_wizard->caching) ? CMP_MATCH | CMP_STOP : 0;
+	if (object_wizard->wizard->callbacks.delete(details->sorcery, object_wizard->data, details->obj)) {
+		return 0;
+	}
+
+	return CMP_MATCH;
 }
 
 int ast_sorcery_delete(const struct ast_sorcery *sorcery, void *object)
@@ -2146,17 +2183,31 @@ int ast_sorcery_delete(const struct ast_sorcery *sorcery, void *object)
 	AST_VECTOR_RW_RDLOCK(&object_type->wizards);
 	for (i = 0; i < AST_VECTOR_SIZE(&object_type->wizards); i++) {
 		found_wizard = AST_VECTOR_GET(&object_type->wizards, i);
-		if (sorcery_wizard_delete(found_wizard, &sdetails, 0) == (CMP_MATCH | CMP_STOP)) {
+		if (!found_wizard->caching && sorcery_wizard_delete(found_wizard, &sdetails, 0) == CMP_MATCH) {
 			object_wizard = found_wizard;
-			if (ao2_container_count(object_type->observers)) {
-				struct sorcery_observer_invocation *invocation = sorcery_observer_invocation_alloc(object_type, object);
+		}
+	}
 
-				if (invocation && ast_taskprocessor_push(object_type->serializer, sorcery_observers_notify_delete, invocation)) {
-					ao2_cleanup(invocation);
-				}
+	if (object_wizard) {
+		for (i = 0; i < AST_VECTOR_SIZE(&object_type->wizards); i++) {
+			found_wizard = AST_VECTOR_GET(&object_type->wizards, i);
+			if (found_wizard->caching) {
+				sorcery_wizard_delete(found_wizard, &sdetails, 0);
+			}
+		}
+
+		if (ao2_container_count(object_type->observers)) {
+			struct sorcery_observer_invocation *invocation = sorcery_observer_invocation_alloc(
+				object_type, object);
+
+			if (invocation
+				&& ast_taskprocessor_push(object_type->serializer, sorcery_observers_notify_delete,
+					invocation)) {
+				ao2_cleanup(invocation);
 			}
 		}
 	}
+
 	AST_VECTOR_RW_UNLOCK(&object_type->wizards);
 
 	return object_wizard ? 0 : -1;
diff --git a/main/stasis.c b/main/stasis.c
index 962efc8..4fb6903 100644
--- a/main/stasis.c
+++ b/main/stasis.c
@@ -462,22 +462,29 @@ struct stasis_subscription *internal_stasis_subscribe(
 	}
 
 	/* The ao2 lock is used for join_cond. */
-	sub = ao2_t_alloc(sizeof(*sub), subscription_dtor, topic->name);
+	sub = ao2_t_alloc(sizeof(*sub), subscription_dtor, stasis_topic_name(topic));
 	if (!sub) {
 		return NULL;
 	}
 	ast_uuid_generate_str(sub->uniqueid, sizeof(sub->uniqueid));
 
 	if (needs_mailbox) {
-		/* With a small number of subscribers, a thread-per-sub is
-		 * acceptable. For larger number of subscribers, a thread
+		char tps_name[AST_TASKPROCESSOR_MAX_NAME + 1];
+
+		/* Create name with seq number appended. */
+		ast_taskprocessor_build_name(tps_name, sizeof(tps_name), "sub%c:%s",
+			use_thread_pool ? 'p' : 'm',
+			stasis_topic_name(topic));
+
+		/*
+		 * With a small number of subscribers, a thread-per-sub is
+		 * acceptable. For a large number of subscribers, a thread
 		 * pool should be used.
 		 */
 		if (use_thread_pool) {
-			sub->mailbox = ast_threadpool_serializer(sub->uniqueid, pool);
+			sub->mailbox = ast_threadpool_serializer(tps_name, pool);
 		} else {
-			sub->mailbox = ast_taskprocessor_get(sub->uniqueid,
-				TPS_REF_DEFAULT);
+			sub->mailbox = ast_taskprocessor_get(tps_name, TPS_REF_DEFAULT);
 		}
 		if (!sub->mailbox) {
 			return NULL;
diff --git a/main/stasis_cache_pattern.c b/main/stasis_cache_pattern.c
index 9e3de36..7ccf1c1 100644
--- a/main/stasis_cache_pattern.c
+++ b/main/stasis_cache_pattern.c
@@ -138,20 +138,11 @@ struct stasis_cp_single *stasis_cp_single_create(struct stasis_cp_all *all,
 {
 	RAII_VAR(struct stasis_cp_single *, one, NULL, ao2_cleanup);
 
-	one = ao2_t_alloc(sizeof(*one), one_dtor, name);
+	one = stasis_cp_sink_create(all, name);
 	if (!one) {
 		return NULL;
 	}
 
-	one->topic = stasis_topic_create(name);
-	if (!one->topic) {
-		return NULL;
-	}
-	one->topic_cached = stasis_caching_topic_create(one->topic, all->cache);
-	if (!one->topic_cached) {
-		return NULL;
-	}
-
 	one->forward_topic_to_all = stasis_forward_all(one->topic, all->topic);
 	if (!one->forward_topic_to_all) {
 		return NULL;
@@ -166,6 +157,29 @@ struct stasis_cp_single *stasis_cp_single_create(struct stasis_cp_all *all,
 	return one;
 }
 
+struct stasis_cp_single *stasis_cp_sink_create(struct stasis_cp_all *all,
+	const char *name)
+{
+	RAII_VAR(struct stasis_cp_single *, one, NULL, ao2_cleanup);
+
+	one = ao2_t_alloc(sizeof(*one), one_dtor, name);
+	if (!one) {
+		return NULL;
+	}
+
+	one->topic = stasis_topic_create(name);
+	if (!one->topic) {
+		return NULL;
+	}
+	one->topic_cached = stasis_caching_topic_create(one->topic, all->cache);
+	if (!one->topic_cached) {
+		return NULL;
+	}
+
+	ao2_ref(one, +1);
+	return one;
+}
+
 void stasis_cp_single_unsubscribe(struct stasis_cp_single *one)
 {
 	if (!one) {
diff --git a/main/stdtime/localtime.c b/main/stdtime/localtime.c
index 4b00520..702edbe 100644
--- a/main/stdtime/localtime.c
+++ b/main/stdtime/localtime.c
@@ -796,13 +796,16 @@ static void sstate_free(struct state *p)
 
 void ast_localtime_wakeup_monitor(struct ast_test *info)
 {
+	struct timeval wait_now = ast_tvnow();
+	struct timespec wait_time = { .tv_sec = wait_now.tv_sec + 2, .tv_nsec = wait_now.tv_usec * 1000 };
+
 	if (inotify_thread != AST_PTHREADT_NULL) {
 		AST_LIST_LOCK(&zonelist);
 #ifdef TEST_FRAMEWORK
 		test = info;
 #endif
 		pthread_kill(inotify_thread, SIGURG);
-		ast_cond_wait(&initialization, &(&zonelist)->lock);
+		ast_cond_timedwait(&initialization, &(&zonelist)->lock, &wait_time);
 #ifdef TEST_FRAMEWORK
 		test = NULL;
 #endif
diff --git a/main/taskprocessor.c b/main/taskprocessor.c
index 7c50089..1ba0c8a 100644
--- a/main/taskprocessor.c
+++ b/main/taskprocessor.c
@@ -332,10 +332,11 @@ static void *tps_task_free(struct tps_task *task)
 }
 
 /* taskprocessor tab completion */
-static char *tps_taskprocessor_tab_complete(struct ast_taskprocessor *p, struct ast_cli_args *a)
+static char *tps_taskprocessor_tab_complete(struct ast_cli_args *a)
 {
 	int tklen;
 	int wordnum = 0;
+	struct ast_taskprocessor *p;
 	char *name = NULL;
 	struct ao2_iterator i;
 
@@ -347,10 +348,10 @@ static char *tps_taskprocessor_tab_complete(struct ast_taskprocessor *p, struct
 	while ((p = ao2_iterator_next(&i))) {
 		if (!strncasecmp(a->word, p->name, tklen) && ++wordnum > a->n) {
 			name = ast_strdup(p->name);
-			ao2_ref(p, -1);
+			ast_taskprocessor_unreference(p);
 			break;
 		}
-		ao2_ref(p, -1);
+		ast_taskprocessor_unreference(p);
 	}
 	ao2_iterator_destroy(&i);
 	return name;
@@ -372,7 +373,7 @@ static char *cli_tps_ping(struct ast_cli_entry *e, int cmd, struct ast_cli_args
 	const char *name;
 	struct timeval when;
 	struct timespec ts;
-	struct ast_taskprocessor *tps = NULL;
+	struct ast_taskprocessor *tps;
 
 	switch (cmd) {
 	case CLI_INIT:
@@ -382,7 +383,7 @@ static char *cli_tps_ping(struct ast_cli_entry *e, int cmd, struct ast_cli_args
 			"	Displays the time required for a task to be processed\n";
 		return NULL;
 	case CLI_GENERATE:
-		return tps_taskprocessor_tab_complete(tps, a);
+		return tps_taskprocessor_tab_complete(a);
 	}
 
 	if (a->argc != 4)
@@ -394,24 +395,73 @@ static char *cli_tps_ping(struct ast_cli_entry *e, int cmd, struct ast_cli_args
 		return CLI_SUCCESS;
 	}
 	ast_cli(a->fd, "\npinging %s ...", name);
-	when = ast_tvadd((begin = ast_tvnow()), ast_samp2tv(1000, 1000));
+
+	/*
+	 * Wait up to 5 seconds for a ping reply.
+	 *
+	 * On a very busy system it could take awhile to get a
+	 * ping response from some taskprocessors.
+	 */
+	begin = ast_tvnow();
+	when = ast_tvadd(begin, ast_samp2tv(5000, 1000));
 	ts.tv_sec = when.tv_sec;
 	ts.tv_nsec = when.tv_usec * 1000;
+
 	ast_mutex_lock(&cli_ping_cond_lock);
 	if (ast_taskprocessor_push(tps, tps_ping_handler, 0) < 0) {
+		ast_mutex_unlock(&cli_ping_cond_lock);
 		ast_cli(a->fd, "\nping failed: could not push task to %s\n\n", name);
-		ao2_ref(tps, -1);
+		ast_taskprocessor_unreference(tps);
 		return CLI_FAILURE;
 	}
 	ast_cond_timedwait(&cli_ping_cond, &cli_ping_cond_lock, &ts);
 	ast_mutex_unlock(&cli_ping_cond_lock);
+
 	end = ast_tvnow();
 	delta = ast_tvsub(end, begin);
 	ast_cli(a->fd, "\n\t%24s ping time: %.1ld.%.6ld sec\n\n", name, (long)delta.tv_sec, (long int)delta.tv_usec);
-	ao2_ref(tps, -1);
+	ast_taskprocessor_unreference(tps);
 	return CLI_SUCCESS;
 }
 
+/*!
+ * \internal
+ * \brief Taskprocessor ao2 container sort function.
+ * \since 13.8.0
+ *
+ * \param obj_left pointer to the (user-defined part) of an object.
+ * \param obj_right pointer to the (user-defined part) of an object.
+ * \param flags flags from ao2_callback()
+ *   OBJ_SEARCH_OBJECT - if set, 'obj_right', is an object.
+ *   OBJ_SEARCH_KEY - if set, 'obj_right', is a search key item that is not an object.
+ *   OBJ_SEARCH_PARTIAL_KEY - if set, 'obj_right', is a partial search key item that is not an object.
+ *
+ * \retval <0 if obj_left < obj_right
+ * \retval =0 if obj_left == obj_right
+ * \retval >0 if obj_left > obj_right
+ */
+static int tps_sort_cb(const void *obj_left, const void *obj_right, int flags)
+{
+	const struct ast_taskprocessor *tps_left = obj_left;
+	const struct ast_taskprocessor *tps_right = obj_right;
+	const char *right_key = obj_right;
+	int cmp;
+
+	switch (flags & OBJ_SEARCH_MASK) {
+	default:
+	case OBJ_SEARCH_OBJECT:
+		right_key = tps_right->name;
+		/* Fall through */
+	case OBJ_SEARCH_KEY:
+		cmp = strcasecmp(tps_left->name, right_key);
+		break;
+	case OBJ_SEARCH_PARTIAL_KEY:
+		cmp = strncasecmp(tps_left->name, right_key, strlen(right_key));
+		break;
+	}
+	return cmp;
+}
+
 static char *cli_tps_report(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
 {
 	char name[256];
@@ -419,8 +469,11 @@ static char *cli_tps_report(struct ast_cli_entry *e, int cmd, struct ast_cli_arg
 	unsigned long qsize;
 	unsigned long maxqsize;
 	unsigned long processed;
-	struct ast_taskprocessor *p;
-	struct ao2_iterator i;
+	struct ao2_container *sorted_tps;
+	struct ast_taskprocessor *tps;
+	struct ao2_iterator iter;
+#define FMT_HEADERS		"%-45s %10s %10s %10s\n"
+#define FMT_FIELDS		"%-45s %10lu %10lu %10lu\n"
 
 	switch (cmd) {
 	case CLI_INIT:
@@ -433,22 +486,38 @@ static char *cli_tps_report(struct ast_cli_entry *e, int cmd, struct ast_cli_arg
 		return NULL;
 	}
 
-	if (a->argc != e->args)
+	if (a->argc != e->args) {
 		return CLI_SHOWUSAGE;
+	}
 
-	ast_cli(a->fd, "\n\t+----- Processor -----+--- Processed ---+- In Queue -+- Max Depth -+");
-	i = ao2_iterator_init(tps_singletons, 0);
-	while ((p = ao2_iterator_next(&i))) {
-		ast_copy_string(name, p->name, sizeof(name));
-		qsize = p->tps_queue_size;
-		maxqsize = p->stats->max_qsize;
-		processed = p->stats->_tasks_processed_count;
-		ast_cli(a->fd, "\n%24s   %17lu %12lu %12lu", name, processed, qsize, maxqsize);
-		ao2_ref(p, -1);
+	sorted_tps = ao2_container_alloc_rbtree(AO2_ALLOC_OPT_LOCK_NOLOCK, 0, tps_sort_cb,
+		NULL);
+	if (!sorted_tps
+		|| ao2_container_dup(sorted_tps, tps_singletons, 0)) {
+		ao2_cleanup(sorted_tps);
+		return CLI_FAILURE;
 	}
-	ao2_iterator_destroy(&i);
-	tcount = ao2_container_count(tps_singletons);
-	ast_cli(a->fd, "\n\t+---------------------+-----------------+------------+-------------+\n\t%d taskprocessors\n\n", tcount);
+
+	ast_cli(a->fd, "\n" FMT_HEADERS, "Processor", "Processed", "In Queue", "Max Depth");
+	tcount = 0;
+	iter = ao2_iterator_init(sorted_tps, AO2_ITERATOR_UNLINK);
+	while ((tps = ao2_iterator_next(&iter))) {
+		ast_copy_string(name, tps->name, sizeof(name));
+		qsize = tps->tps_queue_size;
+		if (tps->stats) {
+			maxqsize = tps->stats->max_qsize;
+			processed = tps->stats->_tasks_processed_count;
+		} else {
+			maxqsize = 0;
+			processed = 0;
+		}
+		ast_cli(a->fd, FMT_FIELDS, name, processed, qsize, maxqsize);
+		ast_taskprocessor_unreference(tps);
+		++tcount;
+	}
+	ao2_iterator_destroy(&iter);
+	ast_cli(a->fd, "\n%d taskprocessors\n\n", tcount);
+	ao2_ref(sorted_tps, -1);
 	return CLI_SUCCESS;
 }
 
@@ -605,6 +674,8 @@ static struct ast_taskprocessor *__allocate_taskprocessor(const char *name, stru
 
 	if (!(ao2_link(tps_singletons, p))) {
 		ast_log(LOG_ERROR, "Failed to add taskprocessor '%s' to container\n", p->name);
+		listener->tps = NULL;
+		ao2_ref(p, -1);
 		return NULL;
 	}
 
@@ -655,12 +726,7 @@ struct ast_taskprocessor *ast_taskprocessor_get(const char *name, enum ast_tps_o
 	}
 
 	p = __allocate_taskprocessor(name, listener);
-	if (!p) {
-		ao2_ref(listener, -1);
-		return NULL;
-	}
 
-	/* Unref listener here since the taskprocessor has gained a reference to the listener */
 	ao2_ref(listener, -1);
 	return p;
 }
@@ -793,11 +859,14 @@ int ast_taskprocessor_execute(struct ast_taskprocessor *tps)
 	 */
 	tps->executing = 0;
 	size = ast_taskprocessor_size(tps);
-	/* If we executed a task, bump the stats */
+
+	/* Update the stats */
 	if (tps->stats) {
-		tps->stats->_tasks_processed_count++;
-		if (size > tps->stats->max_qsize) {
-			tps->stats->max_qsize = size;
+		++tps->stats->_tasks_processed_count;
+
+		/* Include the task we just executed as part of the queue size. */
+		if (size >= tps->stats->max_qsize) {
+			tps->stats->max_qsize = size + 1;
 		}
 	}
 	ao2_unlock(tps);
@@ -818,3 +887,37 @@ int ast_taskprocessor_is_task(struct ast_taskprocessor *tps)
 	ao2_unlock(tps);
 	return is_task;
 }
+
+unsigned int ast_taskprocessor_seq_num(void)
+{
+	static int seq_num;
+
+	return (unsigned int) ast_atomic_fetchadd_int(&seq_num, +1);
+}
+
+void ast_taskprocessor_build_name(char *buf, unsigned int size, const char *format, ...)
+{
+	va_list ap;
+	int user_size;
+#define SEQ_STR_SIZE (1 + 8 + 1)	/* Dash plus 8 hex digits plus null terminator */
+
+	ast_assert(buf != NULL);
+	ast_assert(SEQ_STR_SIZE <= size);
+
+	va_start(ap, format);
+	user_size = vsnprintf(buf, size - (SEQ_STR_SIZE - 1), format, ap);
+	va_end(ap);
+	if (user_size < 0) {
+		/*
+		 * Wow!  We got an output error to a memory buffer.
+		 * Assume no user part of name written.
+		 */
+		user_size = 0;
+	} else if (size < user_size + SEQ_STR_SIZE) {
+		/* Truncate user part of name to make sequence number fit. */
+		user_size = size - SEQ_STR_SIZE;
+	}
+
+	/* Append sequence number to end of user name. */
+	snprintf(buf + user_size, SEQ_STR_SIZE, "-%08x", ast_taskprocessor_seq_num());
+}
diff --git a/main/tcptls.c b/main/tcptls.c
index 1b0c26a..34baf9a 100644
--- a/main/tcptls.c
+++ b/main/tcptls.c
@@ -759,7 +759,8 @@ static int __ssl_setup(struct ast_tls_config *cfg, int client)
 	return 0;
 #else
 	int disable_ssl = 0;
- 
+	long ssl_opts = 0;
+
 	if (!cfg->enabled) {
 		return 0;
 	}
@@ -807,11 +808,29 @@ static int __ssl_setup(struct ast_tls_config *cfg, int client)
 	 * them. SSLv23_*_method supports TLSv1+.
 	 */
 	if (disable_ssl) {
-		long ssl_opts;
+		ssl_opts |= SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3;
+	}
 
-		ssl_opts = SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3;
-		SSL_CTX_set_options(cfg->ssl_ctx, ssl_opts);
+	if (ast_test_flag(&cfg->flags, AST_SSL_SERVER_CIPHER_ORDER)) {
+		ssl_opts |= SSL_OP_CIPHER_SERVER_PREFERENCE;
+	}
+
+	if (ast_test_flag(&cfg->flags, AST_SSL_DISABLE_TLSV1)) {
+		ssl_opts |= SSL_OP_NO_TLSv1;
+	}
+#if defined(HAVE_SSL_OP_NO_TLSV1_1) && defined(HAVE_SSL_OP_NO_TLSV1_2)
+	if (ast_test_flag(&cfg->flags, AST_SSL_DISABLE_TLSV11)) {
+		ssl_opts |= SSL_OP_NO_TLSv1_1;
 	}
+	if (ast_test_flag(&cfg->flags, AST_SSL_DISABLE_TLSV12)) {
+		ssl_opts |= SSL_OP_NO_TLSv1_2;
+	}
+#else
+	ast_log(LOG_WARNING, "Your version of OpenSSL leaves you potentially vulnerable "
+			"to the SSL BEAST attack. Please upgrade to OpenSSL 1.0.1 or later\n");
+#endif
+
+	SSL_CTX_set_options(cfg->ssl_ctx, ssl_opts);
 
 	SSL_CTX_set_verify(cfg->ssl_ctx,
 		ast_test_flag(&cfg->flags, AST_SSL_VERIFY_CLIENT) ? SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT : SSL_VERIFY_NONE,
@@ -1164,6 +1183,14 @@ int ast_tls_read_conf(struct ast_tls_config *tls_cfg, struct ast_tcptls_session_
 			ast_clear_flag(&tls_cfg->flags, AST_SSL_TLSV1_CLIENT);
 			ast_clear_flag(&tls_cfg->flags, AST_SSL_SSLV3_CLIENT);
 		}
+	} else if (!strcasecmp(varname, "tlsservercipherorder")) {
+		ast_set2_flag(&tls_cfg->flags, ast_true(value), AST_SSL_SERVER_CIPHER_ORDER);
+	} else if (!strcasecmp(varname, "tlsdisablev1")) {
+		ast_set2_flag(&tls_cfg->flags, ast_true(value), AST_SSL_DISABLE_TLSV1);
+	} else if (!strcasecmp(varname, "tlsdisablev11")) {
+		ast_set2_flag(&tls_cfg->flags, ast_true(value), AST_SSL_DISABLE_TLSV11);
+	} else if (!strcasecmp(varname, "tlsdisablev12")) {
+		ast_set2_flag(&tls_cfg->flags, ast_true(value), AST_SSL_DISABLE_TLSV12);
 	} else {
 		return -1;
 	}
diff --git a/main/udptl.c b/main/udptl.c
index 4e87819..e8410cc 100644
--- a/main/udptl.c
+++ b/main/udptl.c
@@ -305,16 +305,15 @@ static int decode_open_type(uint8_t *buf, unsigned int limit, unsigned int *len,
 	if (decode_length(buf, limit, len, &octet_cnt) != 0)
 		return -1;
 
-	if (octet_cnt > 0) {
-		/* Make sure the buffer contains at least the number of bits requested */
-		if ((*len + octet_cnt) > limit)
-			return -1;
-
-		*p_num_octets = octet_cnt;
-		*p_object = &buf[*len];
-		*len += octet_cnt;
+	/* Make sure the buffer contains at least the number of bits requested */
+	if ((*len + octet_cnt) > limit) {
+		return -1;
 	}
 
+	*p_num_octets = octet_cnt;
+	*p_object = &buf[*len];
+	*len += octet_cnt;
+
 	return 0;
 }
 /*- End of function --------------------------------------------------------*/
diff --git a/main/utils.c b/main/utils.c
index 3a8d46a..0b527f0 100644
--- a/main/utils.c
+++ b/main/utils.c
@@ -1213,7 +1213,7 @@ static void *dummy_start(void *data)
 
 	lock_info->thread_id = pthread_self();
 	lock_info->lwp = ast_get_tid();
-	lock_info->thread_name = strdup(a.name);
+	lock_info->thread_name = ast_strdup(a.name);
 
 	pthread_mutexattr_init(&mutex_attr);
 	pthread_mutexattr_settype(&mutex_attr, AST_MUTEX_KIND);
diff --git a/makeopts.in b/makeopts.in
index 4922c2f..bfc965d 100644
--- a/makeopts.in
+++ b/makeopts.in
@@ -38,6 +38,7 @@ SHA1SUM=@SHA1SUM@
 OPENSSL=@OPENSSL@
 LDCONFIG=@LDCONFIG@
 GIT=@GIT@
+ALEMBIC=@ALEMBIC@
 
 BUILD_PLATFORM=@BUILD_PLATFORM@
 BUILD_CPU=@BUILD_CPU@
@@ -228,8 +229,10 @@ OSS_LIB=@OSS_LIB@ @FFMPEG_LIB@ @SDL_LIB@ @SDL_IMAGE_LIB@ @X11_LIB@
 PGSQL_INCLUDE=@PGSQL_INCLUDE@
 PGSQL_LIB=@PGSQL_LIB@
 
+PJPROJECT_BUNDLED=@PJPROJECT_BUNDLED@
 PJPROJECT_INCLUDE=@PJPROJECT_INCLUDE@
 PJPROJECT_LIB=@PJPROJECT_LIB@
+PJPROJECT_DIR=@PJPROJECT_DIR@
 
 POPT_INCLUDE=@POPT_INCLUDE@
 POPT_LIB=@POPT_LIB@
diff --git a/menuselect/.gitignore b/menuselect/.gitignore
deleted file mode 100644
index ded8d2d..0000000
--- a/menuselect/.gitignore
+++ /dev/null
@@ -1,7 +0,0 @@
-autoconfig.h
-cmenuselect
-config.log
-config.status
-menuselect
-gmenuselect
-nmenuselect
diff --git a/menuselect/menuselect.c b/menuselect/menuselect.c
index f4a826b..6136135 100644
--- a/menuselect/menuselect.c
+++ b/menuselect/menuselect.c
@@ -357,6 +357,10 @@ static int process_xml_ref_node(xmlNode *node, struct member *mem, struct refere
 		}
 	}
 
+	if ((tmp = (const char *) xmlGetProp(node, BAD_CAST "autoselect"))) {
+		ref->autoselect = !strcasecmp(tmp, "yes");
+	}
+
 	tmp = (const char *) xmlNodeGetContent(node);
 
 	if (tmp && !strlen_zero(tmp)) {
@@ -1154,8 +1158,16 @@ unsigned int enable_member(struct member *mem)
 	}
 
 	if ((mem->enabled = can_enable)) {
+		struct reference *use;
+
 		print_debug("Just set %s enabled to %d\n", mem->name, mem->enabled);
 		while (calc_dep_failures(1, 0) || calc_conflict_failures(1, 0));
+
+		AST_LIST_TRAVERSE(&mem->uses, use, list) {
+			if (use->member && use->autoselect && !use->member->enabled) {
+				enable_member(use->member);
+			}
+		}
 	}
 
 	return can_enable;
diff --git a/menuselect/menuselect.h b/menuselect/menuselect.h
index b1d22dd..112f1c8 100644
--- a/menuselect/menuselect.h
+++ b/menuselect/menuselect.h
@@ -43,6 +43,8 @@ struct reference {
 	struct member *member;
 	/*! if this package was found */
 	unsigned char met:1;
+	/*! if this package should be autoselected */
+	unsigned char autoselect:1;
 	/*! for linking */
 	AST_LIST_ENTRY(reference) list;
 };
diff --git a/menuselect/menuselect_curses.c b/menuselect/menuselect_curses.c
index 5afa996..0064592 100644
--- a/menuselect/menuselect_curses.c
+++ b/menuselect/menuselect_curses.c
@@ -158,6 +158,12 @@ static int really_quit(WINDOW *win)
 	return c;
 }
 
+#define MENU_HELP_LEFT_ADJ 16
+#define MAIN_MENU_LEFT_ADJ 20
+#define CAT_MENU_LEFT_ADJ 20
+#define SCROLL_DOWN_LEFT_ADJ 15
+#define MEMBER_INFO_LEFT_ADJ 25
+
 static void draw_main_menu(WINDOW *menu, int curopt)
 {
 	struct category *cat;
@@ -167,42 +173,67 @@ static void draw_main_menu(WINDOW *menu, int curopt)
 	wclear(menu);
 
 	AST_LIST_TRAVERSE(&categories, cat, list) {
-		wmove(menu, i++, max_x / 2 - 10);
-		snprintf(buf, sizeof(buf), " %s", strlen_zero(cat->displayname) ? cat->name : cat->displayname);
+		wmove(menu, i++, max_x / 2 - MAIN_MENU_LEFT_ADJ);
+		snprintf(buf, sizeof(buf), "%s", strlen_zero(cat->displayname) ? cat->name : cat->displayname);
 		waddstr(menu, buf);
 	}
 
-	wmove(menu, curopt, (max_x / 2) - 15);
+	wmove(menu, curopt, (max_x / 2) - MAIN_MENU_LEFT_ADJ - 5);
 	waddstr(menu, "--->");
-	wmove(menu, 0, 0);
+	wmove(menu, curopt, (max_x / 2) - MAIN_MENU_LEFT_ADJ);
 
 	wrefresh(menu);
 }
 
-static void display_mem_info(WINDOW *menu, struct member *mem, int start, int end)
+static void display_mem_info(WINDOW *menu, struct member *mem, int start_y, int end)
 {
 	char buf[64];
 	struct reference *dep;
 	struct reference *con;
 	struct reference *use;
+	int start_x = (max_x / 2 - MEMBER_INFO_LEFT_ADJ);
+	int maxlen = (max_x - start_x);
 
-	wmove(menu, end - start + 2, max_x / 2 - 16);
+	wmove(menu, end - start_y + 1, start_x);
+	wclrtoeol(menu);
+	wmove(menu, end - start_y + 2, start_x);
 	wclrtoeol(menu);
-	wmove(menu, end - start + 3, max_x / 2 - 16);
+	wmove(menu, end - start_y + 3, start_x);
 	wclrtoeol(menu);
-	wmove(menu, end - start + 4, max_x / 2 - 16);
+	wmove(menu, end - start_y + 4, start_x);
 	wclrtoeol(menu);
-	wmove(menu, end - start + 5, max_x / 2 - 16);
+	wmove(menu, end - start_y + 5, start_x);
 	wclrtoeol(menu);
-	wmove(menu, end - start + 6, max_x / 2 - 16);
+	wmove(menu, end - start_y + 6, start_x);
 	wclrtoeol(menu);
 
 	if (mem->displayname) {
-		wmove(menu, end - start + 2, max_x / 2 - 16);
-		waddstr(menu, (char *) mem->displayname);
+		int name_len = strlen(mem->displayname);
+
+		wmove(menu, end - start_y + 1, start_x);
+		if (name_len >  maxlen) {
+			char *last_space;
+			char *line_1 = strdup(mem->displayname);
+
+			if (line_1) {
+				line_1[maxlen] = '\0';
+				last_space = strrchr(line_1, ' ');
+				if (last_space) {
+					*last_space = '\0';
+				}
+				waddstr(menu, line_1);
+				wmove(menu, end - start_y + 2, start_x);
+				waddstr(menu, &mem->displayname[last_space - line_1]);
+				free(line_1);
+			} else {
+				waddstr(menu, (char *) mem->displayname);
+			}
+		} else {
+			waddstr(menu, (char *) mem->displayname);
+		}
 	}
 	if (!AST_LIST_EMPTY(&mem->deps)) {
-		wmove(menu, end - start + 3, max_x / 2 - 16);
+		wmove(menu, end - start_y + 3, start_x);
 		strcpy(buf, "Depends on: ");
 		AST_LIST_TRAVERSE(&mem->deps, dep, list) {
 			strncat(buf, dep->displayname, sizeof(buf) - strlen(buf) - 1);
@@ -213,7 +244,7 @@ static void display_mem_info(WINDOW *menu, struct member *mem, int start, int en
 		waddstr(menu, buf);
 	}
 	if (!AST_LIST_EMPTY(&mem->uses)) {
-		wmove(menu, end - start + 4, max_x / 2 - 16);
+		wmove(menu, end - start_y + 4, start_x);
 		strcpy(buf, "Can use: ");
 		AST_LIST_TRAVERSE(&mem->uses, use, list) {
 			strncat(buf, use->displayname, sizeof(buf) - strlen(buf) - 1);
@@ -224,7 +255,7 @@ static void display_mem_info(WINDOW *menu, struct member *mem, int start, int en
 		waddstr(menu, buf);
 	}
 	if (!AST_LIST_EMPTY(&mem->conflicts)) {
-		wmove(menu, end - start + 5, max_x / 2 - 16);
+		wmove(menu, end - start_y + 5, start_x);
 		strcpy(buf, "Conflicts with: ");
 		AST_LIST_TRAVERSE(&mem->conflicts, con, list) {
 			strncat(buf, con->displayname, sizeof(buf) - strlen(buf) - 1);
@@ -237,7 +268,7 @@ static void display_mem_info(WINDOW *menu, struct member *mem, int start, int en
 
 	if (!mem->is_separator) { /* Separators lack support levels */
 		{ /* support level */
-			wmove(menu, end - start + 6, max_x / 2 - 16);
+			wmove(menu, end - start_y + 6, start_x);
 			snprintf(buf, sizeof(buf), "Support Level: %s", mem->support_level);
 			if (mem->replacement && *mem->replacement) {
 				char buf2[64];
@@ -266,7 +297,7 @@ static void draw_category_menu(WINDOW *menu, struct category *cat, int start, in
 				break;
 			}
 		}
-		wmove(menu, curopt - start, max_x / 2 - 9);
+		wmove(menu, curopt - start, (max_x / 2) - (CAT_MENU_LEFT_ADJ - 1));
 		wrefresh(menu);
 		return;
 	}
@@ -279,7 +310,7 @@ static void draw_category_menu(WINDOW *menu, struct category *cat, int start, in
 			i++;
 			continue;
 		}
-		wmove(menu, j++, max_x / 2 - 10);
+		wmove(menu, j++, max_x / 2 - CAT_MENU_LEFT_ADJ);
 		i++;
 		if ((mem->depsfailed == HARD_FAILURE) || (mem->conflictsfailed == HARD_FAILURE)) {
 			snprintf(buf, sizeof(buf), "XXX %s", mem->name);
@@ -302,11 +333,11 @@ static void draw_category_menu(WINDOW *menu, struct category *cat, int start, in
 	}
 
 	if (flags & SCROLL_DOWN) {
-		wmove(menu, j, max_x / 2 - sizeof(SCROLL_DOWN_INDICATOR) / 2);
+		wmove(menu, j, max_x / 2 - SCROLL_DOWN_LEFT_ADJ);
 		waddstr(menu, SCROLL_DOWN_INDICATOR);
 	}
 
-	wmove(menu, curopt - start, max_x / 2 - 9);
+	wmove(menu, curopt - start, (max_x / 2) - (CAT_MENU_LEFT_ADJ - 1));
 	wrefresh(menu);
 }
 
@@ -465,7 +496,7 @@ static void draw_title_window(WINDOW *title)
 	waddstr(title, (char *) menu_name);
 	wmove(title, 3, (max_x / 2) - (strlen(titlebar) / 2));
 	waddstr(title, titlebar);
-	wmove(title, 5, (max_x / 2) - (strlen(MENU_HELP) / 2));
+	wmove(title, 5, (max_x / 2) - MENU_HELP_LEFT_ADJ);
 	waddstr(title, MENU_HELP);
 	wrefresh(title);
 }
diff --git a/pbx/pbx_dundi.c b/pbx/pbx_dundi.c
index 04da247..51801f4 100644
--- a/pbx/pbx_dundi.c
+++ b/pbx/pbx_dundi.c
@@ -5014,30 +5014,31 @@ static int load_module(void)
 	io = io_context_create();
 	sched = ast_sched_context_create();
 
-	if (!io || !sched)
-		return AST_MODULE_LOAD_DECLINE;
+	if (!io || !sched) {
+		goto declined;
+	}
 
-	if (set_config("dundi.conf", &sin, 0))
-		return AST_MODULE_LOAD_DECLINE;
+	if (set_config("dundi.conf", &sin, 0)) {
+		goto declined;
+	}
 
 	netsocket = socket(AF_INET, SOCK_DGRAM, IPPROTO_IP);
 
 	if (netsocket < 0) {
 		ast_log(LOG_ERROR, "Unable to create network socket: %s\n", strerror(errno));
-		return AST_MODULE_LOAD_DECLINE;
+		goto declined;
 	}
 	if (bind(netsocket, (struct sockaddr *) &sin, sizeof(sin))) {
 		ast_log(LOG_ERROR, "Unable to bind to %s port %d: %s\n",
 			ast_inet_ntoa(sin.sin_addr), ntohs(sin.sin_port), strerror(errno));
-		return AST_MODULE_LOAD_DECLINE;
+		goto declined;
 	}
 
 	ast_set_qos(netsocket, tos, 0, "DUNDi");
 
 	if (start_network_thread()) {
 		ast_log(LOG_ERROR, "Unable to start network thread\n");
-		close(netsocket);
-		return AST_MODULE_LOAD_DECLINE;
+		goto declined;
 	}
 
 	ast_cli_register_multiple(cli_dundi, ARRAY_LEN(cli_dundi));
@@ -5050,6 +5051,10 @@ static int load_module(void)
 	ast_verb(2, "DUNDi Ready and Listening on %s port %d\n", ast_inet_ntoa(sin.sin_addr), ntohs(sin.sin_port));
 
 	return AST_MODULE_LOAD_SUCCESS;
+
+declined:
+	unload_module();
+	return AST_MODULE_LOAD_DECLINE;
 }
 
 AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_DEFAULT, "Distributed Universal Number Discovery (DUNDi)",
diff --git a/res/Makefile b/res/Makefile
index dbab999..d5e0e37 100644
--- a/res/Makefile
+++ b/res/Makefile
@@ -81,6 +81,7 @@ clean::
 	rm -f stasis/*.[oi] stasis/*.gcda stasis/*.gcno
 	rm -f parking/*.[oi] parking/*.gcda parking/*.gcno
 	rm -f stasis_recording/*.[oi] stasis_recording/*.gcda stasis_recording/*.gcno
+	rm -f ari/*.[oi] ari/*.gcda ari/*.gcno
 
 $(if $(filter res_parking,$(EMBEDDED_MODS)),modules.link,res_parking.so): $(subst .c,.o,$(wildcard parking/*.c))
 $(subst .c,.o,$(wildcard parking/*.c)): _ASTCFLAGS+=$(call MOD_ASTCFLAGS,res_parking)
diff --git a/res/ael/.gitignore b/res/ael/.gitignore
deleted file mode 100644
index f39b612..0000000
--- a/res/ael/.gitignore
+++ /dev/null
@@ -1 +0,0 @@
-ael.output
diff --git a/res/res_calendar.c b/res/res_calendar.c
index 5b911ca..2641654 100644
--- a/res/res_calendar.c
+++ b/res/res_calendar.c
@@ -1853,6 +1853,8 @@ static int unload_module(void)
 	ast_mutex_unlock(&refreshlock);
 	pthread_join(refresh_thread, NULL);
 
+	ast_sched_context_destroy(sched);
+
 	AST_LIST_LOCK(&techs);
 	AST_LIST_TRAVERSE_SAFE_BEGIN(&techs, tech, list) {
 		ast_unload_resource(tech->module, 0);
diff --git a/res/res_config_sqlite3.c b/res/res_config_sqlite3.c
index 04b8e37..4c4b820 100644
--- a/res/res_config_sqlite3.c
+++ b/res/res_config_sqlite3.c
@@ -127,8 +127,14 @@ static inline const char *sqlite3_escape_string_helper(struct ast_threadstorage
 	 * add two quotes, and convert NULL pointers to the word "NULL", but we
 	 * don't allow those anyway. Just going to use %q for now. */
 	struct ast_str *buf = ast_str_thread_get(ts, maxlen);
-	char *tmp = ast_str_buffer(buf);
 	char q = ts == &escape_value_buf ? '\'' : '"';
+	char *tmp;
+
+	if (ast_str_size(buf) < maxlen) {
+		/* realloc if buf is too small */
+		ast_str_make_space(&buf, maxlen);
+	}
+	tmp = ast_str_buffer(buf);
 
 	ast_str_reset(buf);
 	*tmp++ = q; /* Initial quote */
@@ -160,9 +166,15 @@ static const char *sqlite3_escape_column_op(const char *param)
 {
 	size_t maxlen = strlen(param) * 2 + sizeof("\"\" =");
 	struct ast_str *buf = ast_str_thread_get(&escape_column_buf, maxlen);
-	char *tmp = ast_str_buffer(buf);
+	char *tmp;
 	int space = 0;
 
+	if (ast_str_size(buf) < maxlen) {
+		/* realloc if buf is too small */
+		ast_str_make_space(&buf, maxlen);
+	}
+	tmp = ast_str_buffer(buf);
+
 	ast_str_reset(buf);
 	*tmp++ = '"';
 	while ((*tmp++ = *param++)) {
diff --git a/res/res_crypto.c b/res/res_crypto.c
index 78b8df2..1683427 100644
--- a/res/res_crypto.c
+++ b/res/res_crypto.c
@@ -652,13 +652,17 @@ static int load_module(void)
 	} else {
 		crypto_load(-1, -1);
 	}
+
+	/* This prevents dlclose from ever running, but allows CLI cleanup at shutdown. */
+	ast_module_shutdown_ref(ast_module_info->self);
 	return AST_MODULE_LOAD_SUCCESS;
 }
 
 static int unload_module(void)
 {
-	/* Can't unload this once we're loaded */
-	return -1;
+	ast_cli_unregister_multiple(cli_crypto, ARRAY_LEN(cli_crypto));
+
+	return 0;
 }
 
 /* needs usecount semantics defined */
diff --git a/res/res_http_websocket.c b/res/res_http_websocket.c
index b6baa3c..e16c23c 100644
--- a/res/res_http_websocket.c
+++ b/res/res_http_websocket.c
@@ -330,18 +330,19 @@ static const char *websocket_opcode2str(enum ast_websocket_opcode opcode)
 }
 
 /*! \brief Write function for websocket traffic */
-int AST_OPTIONAL_API_NAME(ast_websocket_write)(struct ast_websocket *session, enum ast_websocket_opcode opcode, char *payload, uint64_t actual_length)
+int AST_OPTIONAL_API_NAME(ast_websocket_write)(struct ast_websocket *session, enum ast_websocket_opcode opcode, char *payload, uint64_t payload_size)
 {
 	size_t header_size = 2; /* The minimum size of a websocket frame is 2 bytes */
 	char *frame;
 	uint64_t length;
+	uint64_t frame_size;
 
 	ast_debug(3, "Writing websocket %s frame, length %" PRIu64 "\n",
-			websocket_opcode2str(opcode), actual_length);
+			websocket_opcode2str(opcode), payload_size);
 
-	if (actual_length < 126) {
-		length = actual_length;
-	} else if (actual_length < (1 << 16)) {
+	if (payload_size < 126) {
+		length = payload_size;
+	} else if (payload_size < (1 << 16)) {
 		length = 126;
 		/* We need an additional 2 bytes to store the extended length */
 		header_size += 2;
@@ -351,37 +352,37 @@ int AST_OPTIONAL_API_NAME(ast_websocket_write)(struct ast_websocket *session, en
 		header_size += 8;
 	}
 
-	frame = ast_alloca(header_size);
-	memset(frame, 0, header_size);
+	frame_size = header_size + payload_size;
+
+	frame = ast_alloca(frame_size + 1);
+	memset(frame, 0, frame_size + 1);
 
 	frame[0] = opcode | 0x80;
 	frame[1] = length;
 
 	/* Use the additional available bytes to store the length */
 	if (length == 126) {
-		put_unaligned_uint16(&frame[2], htons(actual_length));
+		put_unaligned_uint16(&frame[2], htons(payload_size));
 	} else if (length == 127) {
-		put_unaligned_uint64(&frame[2], htonll(actual_length));
+		put_unaligned_uint64(&frame[2], htonll(payload_size));
 	}
 
+	memcpy(&frame[header_size], payload, payload_size);
+
 	ao2_lock(session);
 	if (session->closing) {
 		ao2_unlock(session);
 		return -1;
 	}
-	if (ast_careful_fwrite(session->f, session->fd, frame, header_size, session->timeout)) {
-		ao2_unlock(session);
-		/* 1011 - server terminating connection due to not being able to fulfill the request */
-		ast_websocket_close(session, 1011);
-		return -1;
-	}
 
-	if (ast_careful_fwrite(session->f, session->fd, payload, actual_length, session->timeout)) {
+	if (ast_careful_fwrite(session->f, session->fd, frame, frame_size, session->timeout)) {
 		ao2_unlock(session);
 		/* 1011 - server terminating connection due to not being able to fulfill the request */
+		ast_debug(1, "Closing WS with 1011 because we can't fulfill a write request\n");
 		ast_websocket_close(session, 1011);
 		return -1;
 	}
+
 	fflush(session->f);
 	ao2_unlock(session);
 
diff --git a/res/res_jabber.exports.in b/res/res_jabber.exports.in
deleted file mode 100644
index 926f242..0000000
--- a/res/res_jabber.exports.in
+++ /dev/null
@@ -1,16 +0,0 @@
-{
-	global:
-		LINKER_SYMBOL_PREFIXast_aji_create_chat;
-		LINKER_SYMBOL_PREFIXast_aji_disconnect;
-		LINKER_SYMBOL_PREFIXast_aji_get_client;
-		LINKER_SYMBOL_PREFIXast_aji_get_clients;
-		LINKER_SYMBOL_PREFIXast_aji_increment_mid;
-		LINKER_SYMBOL_PREFIXast_aji_invite_chat;
-		LINKER_SYMBOL_PREFIXast_aji_join_chat;
-		LINKER_SYMBOL_PREFIXast_aji_send;
-		LINKER_SYMBOL_PREFIXast_aji_send_chat;
-		LINKER_SYMBOL_PREFIXast_aji_client_destroy;
-		LINKER_SYMBOL_PREFIXast_aji_buddy_destroy;
-	local:
-		*;
-};
diff --git a/res/res_musiconhold.c b/res/res_musiconhold.c
index 2ac5081..669fb9b 100644
--- a/res/res_musiconhold.c
+++ b/res/res_musiconhold.c
@@ -1821,7 +1821,8 @@ static char *handle_cli_moh_reload(struct ast_cli_entry *e, int cmd, struct ast_
 	if (a->argc != e->args)
 		return CLI_SHOWUSAGE;
 
-	reload();
+	/* The module loader will prevent concurrent reloads from occurring, so we delegate */
+	ast_module_reload("res_musiconhold");
 
 	return CLI_SUCCESS;
 }
diff --git a/res/res_mwi_external.c b/res/res_mwi_external.c
index e5d8a3d..9ded0d9 100644
--- a/res/res_mwi_external.c
+++ b/res/res_mwi_external.c
@@ -33,7 +33,6 @@
 
 /*** MODULEINFO
 	<defaultenabled>no</defaultenabled>
-	<conflict>app_voicemail</conflict>
 	<support_level>core</support_level>
  ***/
 
@@ -935,12 +934,22 @@ static int unload_module(void)
 
 static int load_module(void)
 {
+	int res;
+
 	if (mwi_sorcery_init()
 		|| ast_sorcery_observer_add(mwi_sorcery, MWI_MAILBOX_TYPE, &mwi_observers)
 #if defined(MWI_DEBUG_CLI)
 		|| ast_cli_register_multiple(mwi_cli, ARRAY_LEN(mwi_cli))
 #endif	/* defined(MWI_DEBUG_CLI) */
-		|| ast_vm_register(&vm_table)) {
+		) {
+		unload_module();
+		return AST_MODULE_LOAD_DECLINE;
+	}
+
+	/* ast_vm_register may return DECLINE if another module registered for vm */
+	res = ast_vm_register(&vm_table);
+	if (res) {
+		ast_log(LOG_ERROR, "Failure registering as a voicemail provider\n");
 		unload_module();
 		return AST_MODULE_LOAD_DECLINE;
 	}
diff --git a/res/res_odbc.c b/res/res_odbc.c
index 171b858..17b7a76 100644
--- a/res/res_odbc.c
+++ b/res/res_odbc.c
@@ -64,66 +64,6 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
 #include "asterisk/threadstorage.h"
 #include "asterisk/data.h"
 
-/*** DOCUMENTATION
-	<function name="ODBC" language="en_US">
-		<synopsis>
-			Controls ODBC transaction properties.
-		</synopsis>
-		<syntax>
-			<parameter name="property" required="true">
-				<enumlist>
-					<enum name="transaction">
-						<para>Gets or sets the active transaction ID.  If set, and the transaction ID does not
-						exist and a <replaceable>database name</replaceable> is specified as an argument, it will be created.</para>
-					</enum>
-					<enum name="forcecommit">
-						<para>Controls whether a transaction will be automatically committed when the channel
-						hangs up.  Defaults to false.  If a <replaceable>transaction ID</replaceable> is specified in the optional argument,
-						the property will be applied to that ID, otherwise to the current active ID.</para>
-					</enum>
-					<enum name="isolation">
-						<para>Controls the data isolation on uncommitted transactions.  May be one of the
-						following: <literal>read_committed</literal>, <literal>read_uncommitted</literal>,
-						<literal>repeatable_read</literal>, or <literal>serializable</literal>.  Defaults to the
-						database setting in <filename>res_odbc.conf</filename> or <literal>read_committed</literal>
-						if not specified.  If a <replaceable>transaction ID</replaceable> is specified as an optional argument, it will be
-						applied to that ID, otherwise the current active ID.</para>
-					</enum>
-				</enumlist>
-			</parameter>
-			<parameter name="argument" required="false" />
-		</syntax>
-		<description>
-			<para>The ODBC() function allows setting several properties to influence how a connected
-			database processes transactions.</para>
-		</description>
-	</function>
-	<application name="ODBC_Commit" language="en_US">
-		<synopsis>
-			Commits a currently open database transaction.
-		</synopsis>
-		<syntax>
-			<parameter name="transaction ID" required="no" />
-		</syntax>
-		<description>
-			<para>Commits the database transaction specified by <replaceable>transaction ID</replaceable>
-			or the current active transaction, if not specified.</para>
-		</description>
-	</application>
-	<application name="ODBC_Rollback" language="en_US">
-		<synopsis>
-			Rollback a currently open database transaction.
-		</synopsis>
-		<syntax>
-			<parameter name="transaction ID" required="no" />
-		</syntax>
-		<description>
-			<para>Rolls back the database transaction specified by <replaceable>transaction ID</replaceable>
-			or the current active transaction, if not specified.</para>
-		</description>
-	</application>
- ***/
-
 struct odbc_class
 {
 	AST_LIST_ENTRY(odbc_class) list;
@@ -133,21 +73,15 @@ struct odbc_class
 	char *password;
 	char *sanitysql;
 	SQLHENV env;
-	unsigned int haspool:1;              /*!< Boolean - TDS databases need this */
 	unsigned int delme:1;                /*!< Purge the class */
 	unsigned int backslash_is_escape:1;  /*!< On this database, the backslash is a native escape sequence */
 	unsigned int forcecommit:1;          /*!< Should uncommitted transactions be auto-committed on handle release? */
 	unsigned int isolation;              /*!< Flags for how the DB should deal with data in other, uncommitted transactions */
-	unsigned int limit;                  /*!< Maximum number of database handles we will allow */
-	int count;                           /*!< Running count of pooled connections */
-	unsigned int idlecheck;              /*!< Recheck the connection if it is idle for this long (in seconds) */
 	unsigned int conntimeout;            /*!< Maximum time the connection process should take */
 	/*! When a connection fails, cache that failure for how long? */
 	struct timeval negative_connection_cache;
 	/*! When a connection fails, when did that last occur? */
 	struct timeval last_negative_connect;
-	/*! List of handles associated with this class */
-	struct ao2_container *obj_container;
 };
 
 static struct ao2_container *class_container;
@@ -157,16 +91,9 @@ static AST_RWLIST_HEAD_STATIC(odbc_tables, odbc_cache_tables);
 static odbc_status odbc_obj_connect(struct odbc_obj *obj);
 static odbc_status odbc_obj_disconnect(struct odbc_obj *obj);
 static int odbc_register_class(struct odbc_class *class, int connect);
-static void odbc_txn_free(void *data);
-static void odbc_release_obj2(struct odbc_obj *obj, struct odbc_txn_frame *tx);
 
 AST_THREADSTORAGE(errors_buf);
 
-static const struct ast_datastore_info txn_info = {
-	.type = "ODBC_Transaction",
-	.destroy = odbc_txn_free,
-};
-
 struct odbc_txn_frame {
 	AST_LIST_ENTRY(odbc_txn_frame) list;
 	struct ast_channel *owner;
@@ -189,13 +116,11 @@ struct odbc_txn_frame {
 	MEMBER(odbc_class, dsn, AST_DATA_STRING)		\
 	MEMBER(odbc_class, username, AST_DATA_STRING)		\
 	MEMBER(odbc_class, password, AST_DATA_PASSWORD)		\
-	MEMBER(odbc_class, limit, AST_DATA_INTEGER)		\
-	MEMBER(odbc_class, count, AST_DATA_INTEGER)		\
 	MEMBER(odbc_class, forcecommit, AST_DATA_BOOLEAN)
 
 AST_DATA_STRUCTURE(odbc_class, DATA_EXPORT_ODBC_CLASS);
 
-static const char *isolation2text(int iso)
+const char *ast_odbc_isolation2text(int iso)
 {
 	if (iso == SQL_TXN_READ_COMMITTED) {
 		return "read_committed";
@@ -210,7 +135,7 @@ static const char *isolation2text(int iso)
 	}
 }
 
-static int text2isolation(const char *txt)
+int ast_odbc_text2isolation(const char *txt)
 {
 	if (strncasecmp(txt, "read_", 5) == 0) {
 		if (strncasecmp(txt + 5, "c", 1) == 0) {
@@ -229,176 +154,6 @@ static int text2isolation(const char *txt)
 	}
 }
 
-static struct odbc_txn_frame *find_transaction(struct ast_channel *chan, struct odbc_obj *obj, const char *name, int active)
-{
-	struct ast_datastore *txn_store;
-	AST_LIST_HEAD(, odbc_txn_frame) *oldlist;
-	struct odbc_txn_frame *txn = NULL;
-
-	if (!chan && obj && obj->txf && obj->txf->owner) {
-		chan = obj->txf->owner;
-	} else if (!chan) {
-		/* No channel == no transaction */
-		return NULL;
-	}
-
-	ast_channel_lock(chan);
-	if ((txn_store = ast_channel_datastore_find(chan, &txn_info, NULL))) {
-		oldlist = txn_store->data;
-	} else {
-		/* Need to create a new datastore */
-		if (!(txn_store = ast_datastore_alloc(&txn_info, NULL))) {
-			ast_log(LOG_ERROR, "Unable to allocate a new datastore.  Cannot create a new transaction.\n");
-			ast_channel_unlock(chan);
-			return NULL;
-		}
-
-		if (!(oldlist = ast_calloc(1, sizeof(*oldlist)))) {
-			ast_log(LOG_ERROR, "Unable to allocate datastore list head.  Cannot create a new transaction.\n");
-			ast_datastore_free(txn_store);
-			ast_channel_unlock(chan);
-			return NULL;
-		}
-
-		txn_store->data = oldlist;
-		AST_LIST_HEAD_INIT(oldlist);
-		ast_channel_datastore_add(chan, txn_store);
-	}
-
-	AST_LIST_LOCK(oldlist);
-	ast_channel_unlock(chan);
-
-	/* Scanning for an object is *fast*.  Scanning for a name is much slower. */
-	if (obj != NULL || active == 1) {
-		AST_LIST_TRAVERSE(oldlist, txn, list) {
-			if (txn->obj == obj || txn->active) {
-				AST_LIST_UNLOCK(oldlist);
-				return txn;
-			}
-		}
-	}
-
-	if (name != NULL) {
-		AST_LIST_TRAVERSE(oldlist, txn, list) {
-			if (!strcasecmp(txn->name, name)) {
-				AST_LIST_UNLOCK(oldlist);
-				return txn;
-			}
-		}
-	}
-
-	/* Nothing found, create one */
-	if (name && obj && (txn = ast_calloc(1, sizeof(*txn) + strlen(name) + 1))) {
-		struct odbc_txn_frame *otxn;
-
-		strcpy(txn->name, name); /* SAFE */
-		txn->obj = obj;
-		txn->isolation = obj->parent->isolation;
-		txn->forcecommit = obj->parent->forcecommit;
-		txn->owner = chan;
-		txn->active = 1;
-
-		/* On creation, the txn becomes active, and all others inactive */
-		AST_LIST_TRAVERSE(oldlist, otxn, list) {
-			otxn->active = 0;
-		}
-		AST_LIST_INSERT_TAIL(oldlist, txn, list);
-
-		obj->txf = txn;
-		obj->tx = 1;
-	}
-	AST_LIST_UNLOCK(oldlist);
-
-	return txn;
-}
-
-static struct odbc_txn_frame *release_transaction(struct odbc_txn_frame *tx)
-{
-	if (!tx) {
-		return NULL;
-	}
-
-	ast_debug(2, "release_transaction(%p) called (tx->obj = %p, tx->obj->txf = %p)\n", tx, tx->obj, tx->obj ? tx->obj->txf : NULL);
-
-	/* If we have an owner, disassociate */
-	if (tx->owner) {
-		struct ast_datastore *txn_store;
-		AST_LIST_HEAD(, odbc_txn_frame) *oldlist;
-
-		ast_channel_lock(tx->owner);
-		if ((txn_store = ast_channel_datastore_find(tx->owner, &txn_info, NULL))) {
-			oldlist = txn_store->data;
-			AST_LIST_LOCK(oldlist);
-			AST_LIST_REMOVE(oldlist, tx, list);
-			AST_LIST_UNLOCK(oldlist);
-		}
-		ast_channel_unlock(tx->owner);
-		tx->owner = NULL;
-	}
-
-	if (tx->obj) {
-		/* If we have any uncommitted transactions, they are handled when we release the object */
-		struct odbc_obj *obj = tx->obj;
-		/* Prevent recursion during destruction */
-		tx->obj->txf = NULL;
-		tx->obj = NULL;
-		odbc_release_obj2(obj, tx);
-	}
-	ast_free(tx);
-	return NULL;
-}
-
-static void odbc_txn_free(void *vdata)
-{
-	struct odbc_txn_frame *tx;
-	AST_LIST_HEAD(, odbc_txn_frame) *oldlist = vdata;
-
-	ast_debug(2, "odbc_txn_free(%p) called\n", vdata);
-
-	AST_LIST_LOCK(oldlist);
-	while ((tx = AST_LIST_REMOVE_HEAD(oldlist, list))) {
-		release_transaction(tx);
-	}
-	AST_LIST_UNLOCK(oldlist);
-	AST_LIST_HEAD_DESTROY(oldlist);
-	ast_free(oldlist);
-}
-
-static int mark_transaction_active(struct ast_channel *chan, struct odbc_txn_frame *tx)
-{
-	struct ast_datastore *txn_store;
-	AST_LIST_HEAD(, odbc_txn_frame) *oldlist;
-	struct odbc_txn_frame *active = NULL, *txn;
-
-	if (!chan && tx && tx->owner) {
-		chan = tx->owner;
-	}
-
-	if (!chan) {
-		return -1;
-	}
-
-	ast_channel_lock(chan);
-	if (!(txn_store = ast_channel_datastore_find(chan, &txn_info, NULL))) {
-		ast_channel_unlock(chan);
-		return -1;
-	}
-
-	oldlist = txn_store->data;
-	AST_LIST_LOCK(oldlist);
-	AST_LIST_TRAVERSE(oldlist, txn, list) {
-		if (txn == tx) {
-			txn->active = 1;
-			active = txn;
-		} else {
-			txn->active = 0;
-		}
-	}
-	AST_LIST_UNLOCK(oldlist);
-	ast_channel_unlock(chan);
-	return active ? 0 : -1;
-}
-
 static void odbc_class_destructor(void *data)
 {
 	struct odbc_class *class = data;
@@ -414,7 +169,6 @@ static void odbc_class_destructor(void *data)
 	if (class->sanitysql) {
 		ast_free(class->sanitysql);
 	}
-	ao2_ref(class->obj_container, -1);
 	SQLFreeHandle(SQL_HANDLE_ENV, class->env);
 }
 
@@ -451,6 +205,21 @@ static void destroy_table_cache(struct odbc_cache_tables *table) {
  * \retval A structure describing the table layout, or NULL, if the table is not found or another error occurs.
  * When a structure is returned, the contained columns list will be
  * rdlock'ed, to ensure that it will be retained in memory.
+ *
+ * XXX This creates a connection and disconnects it. In some situations, the caller of
+ * this function has its own connection and could donate it to this function instead of
+ * needing to create another one.
+ *
+ * XXX The automatic readlock of the columns is awkward. It's done because it's possible for
+ * multiple threads to have references to the table, and the table is not refcounted. Possible
+ * changes here would be
+ * * Eliminate the table cache entirely. The use of ast_odbc_find_table() is generally
+ *   questionable. The only real good use right now is from ast_realtime_require_field() in
+ *   order to make sure the DB has the expected columns in it. Since that is only used sparingly,
+ *   the need to cache tables is questionable. Instead, the table structure can be fetched from
+ *   the DB directly each time, resulting in a single owner of the data.
+ * * Make odbc_cache_tables a refcounted object.
+ *
  * \since 1.6.1
  */
 struct odbc_cache_tables *ast_odbc_find_table(const char *database, const char *tablename)
@@ -460,7 +229,7 @@ struct odbc_cache_tables *ast_odbc_find_table(const char *database, const char *
 	char columnname[80];
 	SQLLEN sqlptr;
 	SQLHSTMT stmt = NULL;
-	int res = 0, error = 0, try = 0;
+	int res = 0, error = 0;
 	struct odbc_obj *obj;
 
 	AST_RWLIST_RDLOCK(&odbc_tables);
@@ -482,27 +251,16 @@ struct odbc_cache_tables *ast_odbc_find_table(const char *database, const char *
 	}
 
 	/* Table structure not already cached; build it now. */
-	ao2_lock(obj);
 	do {
 		res = SQLAllocHandle(SQL_HANDLE_STMT, obj->con, &stmt);
 		if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
-			if (try == 0) {
-				try = 1;
-				ast_odbc_sanity_check(obj);
-				continue;
-			}
 			ast_log(LOG_WARNING, "SQL Alloc Handle failed on connection '%s'!\n", database);
 			break;
 		}
 
 		res = SQLColumns(stmt, NULL, 0, NULL, 0, (unsigned char *)tablename, SQL_NTS, (unsigned char *)"%", SQL_NTS);
 		if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
-			if (try == 0) {
-				try = 1;
-				SQLFreeHandle(SQL_HANDLE_STMT, stmt);
-				ast_odbc_sanity_check(obj);
-				continue;
-			}
+			SQLFreeHandle(SQL_HANDLE_STMT, stmt);
 			ast_log(LOG_ERROR, "Unable to query database columns on connection '%s'.\n", database);
 			break;
 		}
@@ -553,7 +311,6 @@ struct odbc_cache_tables *ast_odbc_find_table(const char *database, const char *
 		AST_RWLIST_RDLOCK(&(tableptr->columns));
 		break;
 	} while (1);
-	ao2_unlock(obj);
 
 	AST_RWLIST_UNLOCK(&odbc_tables);
 
@@ -595,125 +352,52 @@ int ast_odbc_clear_cache(const char *database, const char *tablename)
 
 SQLHSTMT ast_odbc_direct_execute(struct odbc_obj *obj, SQLHSTMT (*exec_cb)(struct odbc_obj *obj, void *data), void *data)
 {
-	int attempt;
 	SQLHSTMT stmt;
 
-	ao2_lock(obj);
-
-	for (attempt = 0; attempt < 2; attempt++) {
-		stmt = exec_cb(obj, data);
-
-		if (stmt) {
-			break;
-		} else if (obj->tx) {
-			ast_log(LOG_WARNING, "Failed to execute, but unable to reconnect, as we're transactional.\n");
-			break;
-		} else if (attempt == 0) {
-			ast_log(LOG_WARNING, "SQL Execute error! Verifying connection to %s [%s]...\n", obj->parent->name, obj->parent->dsn);
-		}
-		if (!ast_odbc_sanity_check(obj)) {
-			break;
-		}
-	}
-
-	ao2_unlock(obj);
+	stmt = exec_cb(obj, data);
 
 	return stmt;
 }
 
 SQLHSTMT ast_odbc_prepare_and_execute(struct odbc_obj *obj, SQLHSTMT (*prepare_cb)(struct odbc_obj *obj, void *data), void *data)
 {
-	int res = 0, i, attempt;
-	SQLINTEGER nativeerror=0, numfields=0;
-	SQLSMALLINT diagbytes=0;
-	unsigned char state[10], diagnostic[256];
+	int res = 0;
 	SQLHSTMT stmt;
 
-	ao2_lock(obj);
-
-	for (attempt = 0; attempt < 2; attempt++) {
-		/* This prepare callback may do more than just prepare -- it may also
-		 * bind parameters, bind results, etc.  The real key, here, is that
-		 * when we disconnect, all handles become invalid for most databases.
-		 * We must therefore redo everything when we establish a new
-		 * connection. */
-		stmt = prepare_cb(obj, data);
-
-		if (stmt) {
-			res = SQLExecute(stmt);
-			if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO) && (res != SQL_NO_DATA)) {
-				if (res == SQL_ERROR) {
-					SQLGetDiagField(SQL_HANDLE_STMT, stmt, 1, SQL_DIAG_NUMBER, &numfields, SQL_IS_INTEGER, &diagbytes);
-					for (i = 0; i < numfields; i++) {
-						SQLGetDiagRec(SQL_HANDLE_STMT, stmt, i + 1, state, &nativeerror, diagnostic, sizeof(diagnostic), &diagbytes);
-						ast_log(LOG_WARNING, "SQL Execute returned an error %d: %s: %s (%d)\n", res, state, diagnostic, diagbytes);
-						if (i > 10) {
-							ast_log(LOG_WARNING, "Oh, that was good.  There are really %d diagnostics?\n", (int)numfields);
-							break;
-						}
-					}
-				}
+	/* This prepare callback may do more than just prepare -- it may also
+	 * bind parameters, bind results, etc.  The real key, here, is that
+	 * when we disconnect, all handles become invalid for most databases.
+	 * We must therefore redo everything when we establish a new
+	 * connection. */
+	stmt = prepare_cb(obj, data);
 
-				if (obj->tx) {
-					ast_log(LOG_WARNING, "SQL Execute error, but unable to reconnect, as we're transactional.\n");
-					break;
-				} else {
-					ast_log(LOG_WARNING, "SQL Execute error %d! Verifying connection to %s [%s]...\n", res, obj->parent->name, obj->parent->dsn);
-					SQLFreeHandle(SQL_HANDLE_STMT, stmt);
-					stmt = NULL;
-
-					obj->up = 0;
-					/*
-					 * While this isn't the best way to try to correct an error, this won't automatically
-					 * fail when the statement handle invalidates.
-					 */
-					if (!ast_odbc_sanity_check(obj)) {
-						break;
-					}
-					continue;
-				}
-			} else {
-				obj->last_used = ast_tvnow();
+	if (stmt) {
+		res = SQLExecute(stmt);
+		if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO) && (res != SQL_NO_DATA)) {
+			if (res == SQL_ERROR) {
+				ast_odbc_print_errors(SQL_HANDLE_STMT, stmt, "SQL Execute");
 			}
-			break;
-		} else if (attempt == 0) {
-			ast_odbc_sanity_check(obj);
+
+			ast_log(LOG_WARNING, "SQL Execute error %d!\n", res);
+			SQLFreeHandle(SQL_HANDLE_STMT, stmt);
+			stmt = NULL;
 		}
 	}
 
-	ao2_unlock(obj);
-
 	return stmt;
 }
 
 int ast_odbc_smart_execute(struct odbc_obj *obj, SQLHSTMT stmt)
 {
-	int res = 0, i;
-	SQLINTEGER nativeerror=0, numfields=0;
-	SQLSMALLINT diagbytes=0;
-	unsigned char state[10], diagnostic[256];
-
-	ao2_lock(obj);
+	int res = 0;
 
 	res = SQLExecute(stmt);
 	if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO) && (res != SQL_NO_DATA)) {
 		if (res == SQL_ERROR) {
-			SQLGetDiagField(SQL_HANDLE_STMT, stmt, 1, SQL_DIAG_NUMBER, &numfields, SQL_IS_INTEGER, &diagbytes);
-			for (i = 0; i < numfields; i++) {
-				SQLGetDiagRec(SQL_HANDLE_STMT, stmt, i + 1, state, &nativeerror, diagnostic, sizeof(diagnostic), &diagbytes);
-				ast_log(LOG_WARNING, "SQL Execute returned an error %d: %s: %s (%d)\n", res, state, diagnostic, diagbytes);
-				if (i > 10) {
-					ast_log(LOG_WARNING, "Oh, that was good.  There are really %d diagnostics?\n", (int)numfields);
-					break;
-				}
-			}
+			ast_odbc_print_errors(SQL_HANDLE_STMT, stmt, "SQL Execute");
 		}
-	} else {
-		obj->last_used = ast_tvnow();
 	}
 
-	ao2_unlock(obj);
-
 	return res;
 }
 
@@ -734,39 +418,47 @@ SQLRETURN ast_odbc_ast_str_SQLGetData(struct ast_str **buf, int pmaxlen, SQLHSTM
 	return res;
 }
 
-int ast_odbc_sanity_check(struct odbc_obj *obj) 
-{
-	char *test_sql = "select 1";
-	SQLHSTMT stmt;
-	int res = 0;
-
-	if (!ast_strlen_zero(obj->parent->sanitysql))
-		test_sql = obj->parent->sanitysql;
-
-	if (obj->up) {
-		res = SQLAllocHandle(SQL_HANDLE_STMT, obj->con, &stmt);
-		if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
-			obj->up = 0;
-		} else {
-			res = SQLPrepare(stmt, (unsigned char *)test_sql, SQL_NTS);
-			if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
-				obj->up = 0;
-			} else {
-				res = SQLExecute(stmt);
-				if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
-					obj->up = 0;
-				}
-			}
+struct ast_str *ast_odbc_print_errors(SQLSMALLINT handle_type, SQLHANDLE handle, const char *operation)
+{
+	struct ast_str *errors = ast_str_thread_get(&errors_buf, 16);
+	SQLINTEGER nativeerror = 0;
+	SQLINTEGER numfields = 0;
+	SQLSMALLINT diagbytes = 0;
+	SQLSMALLINT i;
+	unsigned char state[10];
+	unsigned char diagnostic[256];
+
+	ast_str_reset(errors);
+	SQLGetDiagField(handle_type, handle, 1, SQL_DIAG_NUMBER, &numfields,
+			SQL_IS_INTEGER, &diagbytes);
+	for (i = 0; i < numfields; i++) {
+		SQLGetDiagRec(handle_type, handle, i + 1, state, &nativeerror,
+				diagnostic, sizeof(diagnostic), &diagbytes);
+		ast_str_append(&errors, 0, "%s%s", ast_str_strlen(errors) ? "," : "", state);
+		ast_log(LOG_WARNING, "%s returned an error: %s: %s\n", operation, state, diagnostic);
+		/* XXX Why is this here? */
+		if (i > 10) {
+			ast_log(LOG_WARNING, "Oh, that was good.  There are really %d diagnostics?\n", (int)numfields);
+			break;
 		}
-		SQLFreeHandle (SQL_HANDLE_STMT, stmt);
 	}
 
-	if (!obj->up && !obj->tx) { /* Try to reconnect! */
-		ast_log(LOG_WARNING, "Connection is down attempting to reconnect...\n");
-		odbc_obj_disconnect(obj);
-		odbc_obj_connect(obj);
-	}
-	return obj->up;
+	return errors;
+}
+
+unsigned int ast_odbc_class_get_isolation(struct odbc_class *class)
+{
+	return class->isolation;
+}
+
+unsigned int ast_odbc_class_get_forcecommit(struct odbc_class *class)
+{
+	return class->forcecommit;
+}
+
+const char *ast_odbc_class_get_name(struct odbc_class *class)
+{
+	return class->name;
 }
 
 static int load_odbc_config(void)
@@ -776,9 +468,8 @@ static int load_odbc_config(void)
 	struct ast_variable *v;
 	char *cat;
 	const char *dsn, *username, *password, *sanitysql;
-	int enabled, pooling, limit, bse, conntimeout, forcecommit, isolation;
+	int enabled, bse, conntimeout, forcecommit, isolation;
 	struct timeval ncache = { 0, 0 };
-	unsigned int idlecheck;
 	int preconnect = 0, res = 0;
 	struct ast_flags config_flags = { 0 };
 
@@ -799,33 +490,17 @@ static int load_odbc_config(void)
 			/* Reset all to defaults for each class of odbc connections */
 			dsn = username = password = sanitysql = NULL;
 			enabled = 1;
-			preconnect = idlecheck = 0;
-			pooling = 0;
-			limit = 0;
+			preconnect = 0;
 			bse = 1;
 			conntimeout = 10;
 			forcecommit = 0;
 			isolation = SQL_TXN_READ_COMMITTED;
 			for (v = ast_variable_browse(config, cat); v; v = v->next) {
-				if (!strcasecmp(v->name, "pooling")) {
-					if (ast_true(v->value))
-						pooling = 1;
-				} else if (!strncasecmp(v->name, "share", 5)) {
-					/* "shareconnections" is a little clearer in meaning than "pooling" */
-					if (ast_false(v->value))
-						pooling = 1;
-				} else if (!strcasecmp(v->name, "limit")) {
-					sscanf(v->value, "%30d", &limit);
-					if (ast_true(v->value) && !limit) {
-						ast_log(LOG_WARNING, "Limit should be a number, not a boolean: '%s'.  Setting limit to 1023 for ODBC class '%s'.\n", v->value, cat);
-						limit = 1023;
-					} else if (ast_false(v->value)) {
-						ast_log(LOG_WARNING, "Limit should be a number, not a boolean: '%s'.  Disabling ODBC class '%s'.\n", v->value, cat);
-						enabled = 0;
-						break;
-					}
-				} else if (!strcasecmp(v->name, "idlecheck")) {
-					sscanf(v->value, "%30u", &idlecheck);
+				if (!strcasecmp(v->name, "pooling") ||
+						!strncasecmp(v->name, "share", 5) ||
+						!strcasecmp(v->name, "limit") ||
+						!strcasecmp(v->name, "idlecheck")) {
+					ast_log(LOG_WARNING, "The 'pooling', 'shared_connections', 'limit', and 'idlecheck' options are deprecated. Please see UPGRADE.txt for information\n");
 				} else if (!strcasecmp(v->name, "enabled")) {
 					enabled = ast_true(v->value);
 				} else if (!strcasecmp(v->name, "pre-connect")) {
@@ -859,7 +534,7 @@ static int load_odbc_config(void)
 				} else if (!strcasecmp(v->name, "forcecommit")) {
 					forcecommit = ast_true(v->value);
 				} else if (!strcasecmp(v->name, "isolation")) {
-					if ((isolation = text2isolation(v->value)) == 0) {
+					if ((isolation = ast_odbc_text2isolation(v->value)) == 0) {
 						ast_log(LOG_ERROR, "Unrecognized value for 'isolation': '%s' in section '%s'\n", v->value, cat);
 						isolation = SQL_TXN_READ_COMMITTED;
 					}
@@ -883,22 +558,9 @@ static int load_odbc_config(void)
 					return res;
 				}
 
-				new->obj_container = ao2_container_alloc(1, null_hash_fn, ao2_match_by_addr);
-
-				if (pooling) {
-					new->haspool = pooling;
-					if (limit) {
-						new->limit = limit;
-					} else {
-						ast_log(LOG_WARNING, "Pooling without also setting a limit is pointless.  Changing limit from 0 to 5.\n");
-						new->limit = 5;
-					}
-				}
-
 				new->backslash_is_escape = bse ? 1 : 0;
 				new->forcecommit = forcecommit ? 1 : 0;
 				new->isolation = isolation;
-				new->idlecheck = idlecheck;
 				new->conntimeout = conntimeout;
 				new->negative_connection_cache = ncache;
 
@@ -934,7 +596,6 @@ static char *handle_cli_odbc_show(struct ast_cli_entry *e, int cmd, struct ast_c
 {
 	struct ao2_iterator aoi;
 	struct odbc_class *class;
-	struct odbc_obj *current;
 	int length = 0;
 	int which = 0;
 	char *ret = NULL;
@@ -973,7 +634,6 @@ static char *handle_cli_odbc_show(struct ast_cli_entry *e, int cmd, struct ast_c
 	aoi = ao2_iterator_init(class_container, 0);
 	while ((class = ao2_iterator_next(&aoi))) {
 		if ((a->argc == 2) || (a->argc == 3 && !strcmp(a->argv[2], "all")) || (!strcmp(a->argv[2], class->name))) {
-			int count = 0;
 			char timestr[80];
 			struct ast_tm tm;
 
@@ -981,38 +641,6 @@ static char *handle_cli_odbc_show(struct ast_cli_entry *e, int cmd, struct ast_c
 			ast_strftime(timestr, sizeof(timestr), "%Y-%m-%d %T", &tm);
 			ast_cli(a->fd, "  Name:   %s\n  DSN:    %s\n", class->name, class->dsn);
 			ast_cli(a->fd, "    Last connection attempt: %s\n", timestr);
-
-			if (class->haspool) {
-				struct ao2_iterator aoi2 = ao2_iterator_init(class->obj_container, 0);
-
-				ast_cli(a->fd, "  Pooled: Yes\n  Limit:  %u\n  Connections in use: %d\n", class->limit, class->count);
-
-				while ((current = ao2_iterator_next(&aoi2))) {
-					ao2_lock(current);
-#ifdef DEBUG_THREADS
-					ast_cli(a->fd, "    - Connection %d: %s (%s:%d %s)\n", ++count,
-						current->used ? "in use" :
-						current->up && ast_odbc_sanity_check(current) ? "connected" : "disconnected",
-						current->file, current->lineno, current->function);
-#else
-					ast_cli(a->fd, "    - Connection %d: %s\n", ++count,
-						current->used ? "in use" :
-						current->up && ast_odbc_sanity_check(current) ? "connected" : "disconnected");
-#endif
-					ao2_unlock(current);
-					ao2_ref(current, -1);
-				}
-				ao2_iterator_destroy(&aoi2);
-			} else {
-				/* Should only ever be one of these (unless there are transactions) */
-				struct ao2_iterator aoi2 = ao2_iterator_init(class->obj_container, 0);
-				while ((current = ao2_iterator_next(&aoi2))) {
-					ast_cli(a->fd, "  Pooled: No\n  Connected: %s\n", current->used ? "In use" :
-						current->up && ast_odbc_sanity_check(current) ? "Yes" : "No");
-					ao2_ref(current, -1);
-				}
-				ao2_iterator_destroy(&aoi2);
-			}
 			ast_cli(a->fd, "\n");
 		}
 		ao2_ref(class, -1);
@@ -1048,150 +676,23 @@ static int odbc_register_class(struct odbc_class *class, int preconnect)
 	}
 }
 
-static void odbc_release_obj2(struct odbc_obj *obj, struct odbc_txn_frame *tx)
+void ast_odbc_release_obj(struct odbc_obj *obj)
 {
-	SQLINTEGER nativeerror=0, numfields=0;
-	SQLSMALLINT diagbytes=0, i;
-	unsigned char state[10], diagnostic[256];
-
-	ast_debug(2, "odbc_release_obj2(%p) called (obj->txf = %p)\n", obj, obj->txf);
-	if (tx) {
-		ast_debug(1, "called on a transactional handle with %s\n", tx->forcecommit ? "COMMIT" : "ROLLBACK");
-		if (SQLEndTran(SQL_HANDLE_DBC, obj->con, tx->forcecommit ? SQL_COMMIT : SQL_ROLLBACK) == SQL_ERROR) {
-			/* Handle possible transaction commit failure */
-			SQLGetDiagField(SQL_HANDLE_DBC, obj->con, 1, SQL_DIAG_NUMBER, &numfields, SQL_IS_INTEGER, &diagbytes);
-			for (i = 0; i < numfields; i++) {
-				SQLGetDiagRec(SQL_HANDLE_DBC, obj->con, i + 1, state, &nativeerror, diagnostic, sizeof(diagnostic), &diagbytes);
-				ast_log(LOG_WARNING, "SQLEndTran returned an error: %s: %s\n", state, diagnostic);
-				if (!strcmp((char *)state, "25S02") || !strcmp((char *)state, "08007")) {
-					/* These codes mean that a commit failed and a transaction
-					 * is still active. We must rollback, or things will get
-					 * very, very weird for anybody using the handle next. */
-					SQLEndTran(SQL_HANDLE_DBC, obj->con, SQL_ROLLBACK);
-				}
-				if (i > 10) {
-					ast_log(LOG_WARNING, "Oh, that was good.  There are really %d diagnostics?\n", (int)numfields);
-					break;
-				}
-			}
-		}
-
-		/* Transaction is done, reset autocommit */
-		if (SQLSetConnectAttr(obj->con, SQL_ATTR_AUTOCOMMIT, (void *)SQL_AUTOCOMMIT_ON, 0) == SQL_ERROR) {
-			SQLGetDiagField(SQL_HANDLE_DBC, obj->con, 1, SQL_DIAG_NUMBER, &numfields, SQL_IS_INTEGER, &diagbytes);
-			for (i = 0; i < numfields; i++) {
-				SQLGetDiagRec(SQL_HANDLE_DBC, obj->con, i + 1, state, &nativeerror, diagnostic, sizeof(diagnostic), &diagbytes);
-				ast_log(LOG_WARNING, "SetConnectAttr (Autocommit) returned an error: %s: %s\n", state, diagnostic);
-				if (i > 10) {
-					ast_log(LOG_WARNING, "Oh, that was good.  There are really %d diagnostics?\n", (int)numfields);
-					break;
-				}
-			}
-		}
-	}
+	ast_debug(2, "Releasing ODBC handle %p\n", obj);
 
 #ifdef DEBUG_THREADS
 	obj->file[0] = '\0';
 	obj->function[0] = '\0';
 	obj->lineno = 0;
 #endif
-
-	/* For pooled connections, this frees the connection to be
-	 * reused.  For non-pooled connections, it does nothing. */
-	obj->used = 0;
-	if (obj->txf) {
-		/* Prevent recursion -- transaction is already closed out. */
-		obj->txf->obj = NULL;
-		obj->txf = release_transaction(obj->txf);
-	}
 	ao2_ref(obj, -1);
 }
 
-void ast_odbc_release_obj(struct odbc_obj *obj)
-{
-	struct odbc_txn_frame *tx = find_transaction(NULL, obj, NULL, 0);
-	odbc_release_obj2(obj, tx);
-}
-
 int ast_odbc_backslash_is_escape(struct odbc_obj *obj)
 {
 	return obj->parent->backslash_is_escape;
 }
 
-static int commit_exec(struct ast_channel *chan, const char *data)
-{
-	struct odbc_txn_frame *tx;
-	SQLINTEGER nativeerror=0, numfields=0;
-	SQLSMALLINT diagbytes=0, i;
-	unsigned char state[10], diagnostic[256];
-
-	if (ast_strlen_zero(data)) {
-		tx = find_transaction(chan, NULL, NULL, 1);
-	} else {
-		tx = find_transaction(chan, NULL, data, 0);
-	}
-
-	pbx_builtin_setvar_helper(chan, "COMMIT_RESULT", "OK");
-
-	if (tx) {
-		if (SQLEndTran(SQL_HANDLE_DBC, tx->obj->con, SQL_COMMIT) == SQL_ERROR) {
-			struct ast_str *errors = ast_str_thread_get(&errors_buf, 16);
-			ast_str_reset(errors);
-
-			/* Handle possible transaction commit failure */
-			SQLGetDiagField(SQL_HANDLE_DBC, tx->obj->con, 1, SQL_DIAG_NUMBER, &numfields, SQL_IS_INTEGER, &diagbytes);
-			for (i = 0; i < numfields; i++) {
-				SQLGetDiagRec(SQL_HANDLE_DBC, tx->obj->con, i + 1, state, &nativeerror, diagnostic, sizeof(diagnostic), &diagbytes);
-				ast_str_append(&errors, 0, "%s%s", ast_str_strlen(errors) ? "," : "", state);
-				ast_log(LOG_WARNING, "SQLEndTran returned an error: %s: %s\n", state, diagnostic);
-				if (i > 10) {
-					ast_log(LOG_WARNING, "Oh, that was good.  There are really %d diagnostics?\n", (int)numfields);
-					break;
-				}
-			}
-			pbx_builtin_setvar_helper(chan, "COMMIT_RESULT", ast_str_buffer(errors));
-		}
-	}
-	return 0;
-}
-
-static int rollback_exec(struct ast_channel *chan, const char *data)
-{
-	struct odbc_txn_frame *tx;
-	SQLINTEGER nativeerror=0, numfields=0;
-	SQLSMALLINT diagbytes=0, i;
-	unsigned char state[10], diagnostic[256];
-
-	if (ast_strlen_zero(data)) {
-		tx = find_transaction(chan, NULL, NULL, 1);
-	} else {
-		tx = find_transaction(chan, NULL, data, 0);
-	}
-
-	pbx_builtin_setvar_helper(chan, "ROLLBACK_RESULT", "OK");
-
-	if (tx) {
-		if (SQLEndTran(SQL_HANDLE_DBC, tx->obj->con, SQL_ROLLBACK) == SQL_ERROR) {
-			struct ast_str *errors = ast_str_thread_get(&errors_buf, 16);
-			ast_str_reset(errors);
-
-			/* Handle possible transaction commit failure */
-			SQLGetDiagField(SQL_HANDLE_DBC, tx->obj->con, 1, SQL_DIAG_NUMBER, &numfields, SQL_IS_INTEGER, &diagbytes);
-			for (i = 0; i < numfields; i++) {
-				SQLGetDiagRec(SQL_HANDLE_DBC, tx->obj->con, i + 1, state, &nativeerror, diagnostic, sizeof(diagnostic), &diagbytes);
-				ast_str_append(&errors, 0, "%s%s", ast_str_strlen(errors) ? "," : "", state);
-				ast_log(LOG_WARNING, "SQLEndTran returned an error: %s: %s\n", state, diagnostic);
-				if (i > 10) {
-					ast_log(LOG_WARNING, "Oh, that was good.  There are really %d diagnostics?\n", (int)numfields);
-					break;
-				}
-			}
-			pbx_builtin_setvar_helper(chan, "ROLLBACK_RESULT", ast_str_buffer(errors));
-		}
-	}
-	return 0;
-}
-
 static int aoro2_class_cb(void *obj, void *arg, int flags)
 {
 	struct odbc_class *class = obj;
@@ -1202,275 +703,41 @@ static int aoro2_class_cb(void *obj, void *arg, int flags)
 	return 0;
 }
 
-#define USE_TX (void *)(long)1
-#define NO_TX  (void *)(long)2
-#define EOR_TX (void *)(long)3
-
-static int aoro2_obj_cb(void *vobj, void *arg, int flags)
-{
-	struct odbc_obj *obj = vobj;
-	ao2_lock(obj);
-	if ((arg == NO_TX && !obj->tx) || (arg == EOR_TX && !obj->used) || (arg == USE_TX && obj->tx && !obj->used)) {
-		obj->used = 1;
-		ao2_unlock(obj);
-		return CMP_MATCH | CMP_STOP;
-	}
-	ao2_unlock(obj);
-	return 0;
-}
-
-/* This function should only be called for shared connections. Otherwise, the lack of
- * setting vobj->used breaks EOR_TX searching. For nonshared connections, use
- * aoro2_obj_cb instead. */
-static int aoro2_obj_notx_cb(void *vobj, void *arg, int flags)
-{
-	struct odbc_obj *obj = vobj;
-	if (!obj->tx) {
-		return CMP_MATCH | CMP_STOP;
-	}
-	return 0;
-}
-
 struct odbc_obj *_ast_odbc_request_obj2(const char *name, struct ast_flags flags, const char *file, const char *function, int lineno)
 {
 	struct odbc_obj *obj = NULL;
 	struct odbc_class *class;
-	SQLINTEGER nativeerror=0, numfields=0;
-	SQLSMALLINT diagbytes=0, i;
-	unsigned char state[10], diagnostic[256];
 
 	if (!(class = ao2_callback(class_container, 0, aoro2_class_cb, (char *) name))) {
 		ast_debug(1, "Class '%s' not found!\n", name);
 		return NULL;
 	}
 
-	ast_assert(ao2_ref(class, 0) > 1);
-
-	if (class->haspool) {
-		/* Recycle connections before building another */
-		obj = ao2_callback(class->obj_container, 0, aoro2_obj_cb, EOR_TX);
-
-		if (obj) {
-			ast_assert(ao2_ref(obj, 0) > 1);
-		}
-		if (!obj && (ast_atomic_fetchadd_int(&class->count, +1) < class->limit)) {
-			obj = ao2_alloc(sizeof(*obj), odbc_obj_destructor);
-			if (!obj) {
-				class->count--;
-				ao2_ref(class, -1);
-				ast_debug(3, "Unable to allocate object\n");
-				ast_atomic_fetchadd_int(&class->count, -1);
-				return NULL;
-			}
-			ast_assert(ao2_ref(obj, 0) == 1);
-			/* obj inherits the outstanding reference to class */
-			obj->parent = class;
-			class = NULL;
-			if (odbc_obj_connect(obj) == ODBC_FAIL) {
-				ast_log(LOG_WARNING, "Failed to connect to %s\n", name);
-				ast_assert(ao2_ref(obj->parent, 0) > 0);
-				/* Because it was never within the container, we have to manually decrement the count here */
-				ast_atomic_fetchadd_int(&obj->parent->count, -1);
-				ao2_ref(obj, -1);
-				obj = NULL;
-			} else {
-				obj->used = 1;
-				ao2_link(obj->parent->obj_container, obj);
-			}
-		} else {
-			/* If construction fails due to the limit (or negative timecache), reverse our increment. */
-			if (!obj) {
-				ast_atomic_fetchadd_int(&class->count, -1);
-			}
-			/* Object is not constructed, so delete outstanding reference to class. */
-			ao2_ref(class, -1);
-			class = NULL;
-		}
-
-		if (!obj) {
-			return NULL;
-		}
-
-		ao2_lock(obj);
-
-		if (ast_test_flag(&flags, RES_ODBC_INDEPENDENT_CONNECTION)) {
-			/* Ensure this connection has autocommit turned off. */
-			if (SQLSetConnectAttr(obj->con, SQL_ATTR_AUTOCOMMIT, (void *)SQL_AUTOCOMMIT_OFF, 0) == SQL_ERROR) {
-				SQLGetDiagField(SQL_HANDLE_DBC, obj->con, 1, SQL_DIAG_NUMBER, &numfields, SQL_IS_INTEGER, &diagbytes);
-				for (i = 0; i < numfields; i++) {
-					SQLGetDiagRec(SQL_HANDLE_DBC, obj->con, i + 1, state, &nativeerror, diagnostic, sizeof(diagnostic), &diagbytes);
-					ast_log(LOG_WARNING, "SQLSetConnectAttr (Autocommit) returned an error: %s: %s\n", state, diagnostic);
-					if (i > 10) {
-						ast_log(LOG_WARNING, "Oh, that was good.  There are really %d diagnostics?\n", (int)numfields);
-						break;
-					}
-				}
-			}
-		}
-	} else if (ast_test_flag(&flags, RES_ODBC_INDEPENDENT_CONNECTION)) {
-		/* Non-pooled connections -- but must use a separate connection handle */
-		if (!(obj = ao2_callback(class->obj_container, 0, aoro2_obj_cb, USE_TX))) {
-			ast_debug(1, "Object not found\n");
-			obj = ao2_alloc(sizeof(*obj), odbc_obj_destructor);
-			if (!obj) {
-				ao2_ref(class, -1);
-				ast_debug(3, "Unable to allocate object\n");
-				return NULL;
-			}
-			/* obj inherits the outstanding reference to class */
-			obj->parent = class;
-			class = NULL;
-			if (odbc_obj_connect(obj) == ODBC_FAIL) {
-				ast_log(LOG_WARNING, "Failed to connect to %s\n", name);
-				ao2_ref(obj, -1);
-				obj = NULL;
-			} else {
-				obj->used = 1;
-				ao2_link(obj->parent->obj_container, obj);
-				ast_atomic_fetchadd_int(&obj->parent->count, +1);
-			}
-		}
-
-		if (!obj) {
-			return NULL;
-		}
-
-		ao2_lock(obj);
-
-		if (SQLSetConnectAttr(obj->con, SQL_ATTR_AUTOCOMMIT, (void *)SQL_AUTOCOMMIT_OFF, 0) == SQL_ERROR) {
-			SQLGetDiagField(SQL_HANDLE_DBC, obj->con, 1, SQL_DIAG_NUMBER, &numfields, SQL_IS_INTEGER, &diagbytes);
-			for (i = 0; i < numfields; i++) {
-				SQLGetDiagRec(SQL_HANDLE_DBC, obj->con, i + 1, state, &nativeerror, diagnostic, sizeof(diagnostic), &diagbytes);
-				ast_log(LOG_WARNING, "SetConnectAttr (Autocommit) returned an error: %s: %s\n", state, diagnostic);
-				if (i > 10) {
-					ast_log(LOG_WARNING, "Oh, that was good.  There are really %d diagnostics?\n", (int)numfields);
-					break;
-				}
-			}
-		}
-	} else {
-		/* Non-pooled connection: multiple modules can use the same connection. */
-		if ((obj = ao2_callback(class->obj_container, 0, aoro2_obj_notx_cb, NO_TX))) {
-			/* Object is not constructed, so delete outstanding reference to class. */
-			ast_assert(ao2_ref(class, 0) > 1);
-			ao2_ref(class, -1);
-			class = NULL;
-		} else {
-			/* No entry: build one */
-			if (!(obj = ao2_alloc(sizeof(*obj), odbc_obj_destructor))) {
-				ast_assert(ao2_ref(class, 0) > 1);
-				ao2_ref(class, -1);
-				ast_debug(3, "Unable to allocate object\n");
-				return NULL;
-			}
-			/* obj inherits the outstanding reference to class */
-			obj->parent = class;
-			class = NULL;
-			if (odbc_obj_connect(obj) == ODBC_FAIL) {
-				ast_log(LOG_WARNING, "Failed to connect to %s\n", name);
-				ao2_ref(obj, -1);
-				obj = NULL;
-			} else {
-				ao2_link(obj->parent->obj_container, obj);
-				ast_assert(ao2_ref(obj, 0) > 1);
-			}
-		}
-
-		if (!obj) {
-			return NULL;
-		}
-
-		ao2_lock(obj);
-
-		if (SQLSetConnectAttr(obj->con, SQL_ATTR_AUTOCOMMIT, (void *)SQL_AUTOCOMMIT_ON, 0) == SQL_ERROR) {
-			SQLGetDiagField(SQL_HANDLE_DBC, obj->con, 1, SQL_DIAG_NUMBER, &numfields, SQL_IS_INTEGER, &diagbytes);
-			for (i = 0; i < numfields; i++) {
-				SQLGetDiagRec(SQL_HANDLE_DBC, obj->con, i + 1, state, &nativeerror, diagnostic, sizeof(diagnostic), &diagbytes);
-				ast_log(LOG_WARNING, "SetConnectAttr (Autocommit) returned an error: %s: %s\n", state, diagnostic);
-				if (i > 10) {
-					ast_log(LOG_WARNING, "Oh, that was good.  There are really %d diagnostics?\n", (int)numfields);
-					break;
-				}
-			}
-		}
-	}
-
-	ast_assert(obj != NULL);
-
-	/* Set the isolation property */
-	if (SQLSetConnectAttr(obj->con, SQL_ATTR_TXN_ISOLATION, (void *)(long)obj->parent->isolation, 0) == SQL_ERROR) {
-		SQLGetDiagField(SQL_HANDLE_DBC, obj->con, 1, SQL_DIAG_NUMBER, &numfields, SQL_IS_INTEGER, &diagbytes);
-		for (i = 0; i < numfields; i++) {
-			SQLGetDiagRec(SQL_HANDLE_DBC, obj->con, i + 1, state, &nativeerror, diagnostic, sizeof(diagnostic), &diagbytes);
-			ast_log(LOG_WARNING, "SetConnectAttr (Txn isolation) returned an error: %s: %s\n", state, diagnostic);
-			if (i > 10) {
-				ast_log(LOG_WARNING, "Oh, that was good.  There are really %d diagnostics?\n", (int)numfields);
-				break;
-			}
-		}
-	}
-
-	if (ast_test_flag(&flags, RES_ODBC_CONNECTED) && !obj->up) {
-		odbc_obj_connect(obj);
-	} else if (ast_test_flag(&flags, RES_ODBC_SANITY_CHECK)) {
-		ast_odbc_sanity_check(obj);
-	} else if (obj->parent->idlecheck > 0 && ast_tvdiff_sec(ast_tvnow(), obj->last_used) > obj->parent->idlecheck) {
-		odbc_obj_connect(obj);
+	/* XXX ODBC connection objects do not have shared ownership, so there is no reason
+	 * to use refcounted objects here.
+	 */
+	obj = ao2_alloc(sizeof(*obj), odbc_obj_destructor);
+	/* Inherit reference from the ao2_callback from before */
+	obj->parent = class;
+	if (odbc_obj_connect(obj) == ODBC_FAIL) {
+		ao2_ref(obj, -1);
+		return NULL;
 	}
 
-#ifdef DEBUG_THREADS
-	ast_copy_string(obj->file, file, sizeof(obj->file));
-	ast_copy_string(obj->function, function, sizeof(obj->function));
-	obj->lineno = lineno;
-#endif
-
-	/* We had it locked because of the obj_connects we see here. */
-	ao2_unlock(obj);
-
-	ast_assert(class == NULL);
-
-	ast_assert(ao2_ref(obj, 0) > 1);
 	return obj;
 }
 
 struct odbc_obj *_ast_odbc_request_obj(const char *name, int check, const char *file, const char *function, int lineno)
 {
 	struct ast_flags flags = { check ? RES_ODBC_SANITY_CHECK : 0 };
+	/* XXX New flow means that the "check" parameter doesn't do anything. We're requesting
+	 * a connection from ODBC. We'll either get a new one, which obviously is already connected, or
+	 * we'll get one from the ODBC connection pool. In that case, it will ensure to only give us a
+	 * live connection
+	 */
 	return _ast_odbc_request_obj2(name, flags, file, function, lineno);
 }
 
-struct odbc_obj *ast_odbc_retrieve_transaction_obj(struct ast_channel *chan, const char *objname)
-{
-	struct ast_datastore *txn_store;
-	AST_LIST_HEAD(, odbc_txn_frame) *oldlist;
-	struct odbc_txn_frame *txn = NULL;
-
-	if (!chan) {
-		/* No channel == no transaction */
-		return NULL;
-	}
-
-	ast_channel_lock(chan);
-	if ((txn_store = ast_channel_datastore_find(chan, &txn_info, NULL))) {
-		oldlist = txn_store->data;
-	} else {
-		ast_channel_unlock(chan);
-		return NULL;
-	}
-
-	AST_LIST_LOCK(oldlist);
-	ast_channel_unlock(chan);
-
-	AST_LIST_TRAVERSE(oldlist, txn, list) {
-		if (txn->obj && txn->obj->parent && !strcmp(txn->obj->parent->name, objname)) {
-			AST_LIST_UNLOCK(oldlist);
-			return txn->obj;
-		}
-	}
-	AST_LIST_UNLOCK(oldlist);
-	return NULL;
-}
-
 static odbc_status odbc_obj_disconnect(struct odbc_obj *obj)
 {
 	int res;
@@ -1490,20 +757,19 @@ static odbc_status odbc_obj_disconnect(struct odbc_obj *obj)
 
 	if (obj->parent) {
 		if (res == SQL_SUCCESS || res == SQL_SUCCESS_WITH_INFO) {
-			ast_debug(1, "Disconnected %d from %s [%s]\n", res, obj->parent->name, obj->parent->dsn);
+			ast_debug(3, "Disconnected %d from %s [%s](%p)\n", res, obj->parent->name, obj->parent->dsn, obj);
 		} else {
-			ast_debug(1, "res_odbc: %s [%s] already disconnected\n", obj->parent->name, obj->parent->dsn);
+			ast_debug(3, "res_odbc: %s [%s](%p) already disconnected\n", obj->parent->name, obj->parent->dsn, obj);
 		}
 	}
 
 	if ((res = SQLFreeHandle(SQL_HANDLE_DBC, con)) == SQL_SUCCESS) {
-		ast_debug(1, "Database handle %p deallocated\n", con);
+		ast_debug(3, "Database handle %p (connection %p) deallocated\n", obj, con);
 	} else {
 		SQLGetDiagRec(SQL_HANDLE_DBC, con, 1, state, &err, msg, 100, &mlen);
 		ast_log(LOG_WARNING, "Unable to deallocate database handle %p? %d errno=%d %s\n", con, res, (int)err, msg);
 	}
 
-	obj->up = 0;
 	return ODBC_SUCCESS;
 }
 
@@ -1520,13 +786,8 @@ static odbc_status odbc_obj_connect(struct odbc_obj *obj)
 	SQLHDBC con;
 	long int negative_cache_expiration;
 
-	if (obj->up) {
-		odbc_obj_disconnect(obj);
-		ast_log(LOG_NOTICE, "Re-connecting %s\n", obj->parent->name);
-	} else {
-		ast_assert(obj->con == NULL);
-		ast_log(LOG_NOTICE, "Connecting %s\n", obj->parent->name);
-	}
+	ast_assert(obj->con == NULL);
+	ast_debug(3, "Connecting %s(%p)\n", obj->parent->name, obj);
 
 	/* Dont connect while server is marked as unreachable via negative_connection_cache */
 	negative_cache_expiration = obj->parent->last_negative_connect.tv_sec + obj->parent->negative_connection_cache.tv_sec;
@@ -1564,155 +825,13 @@ static odbc_status odbc_obj_connect(struct odbc_obj *obj)
 		}
 		return ODBC_FAIL;
 	} else {
-		ast_log(LOG_NOTICE, "res_odbc: Connected to %s [%s]\n", obj->parent->name, obj->parent->dsn);
-		obj->up = 1;
-		obj->last_used = ast_tvnow();
+		ast_debug(3, "res_odbc: Connected to %s [%s (%p)]\n", obj->parent->name, obj->parent->dsn, obj);
 	}
 
 	obj->con = con;
 	return ODBC_SUCCESS;
 }
 
-static int acf_transaction_read(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len)
-{
-	AST_DECLARE_APP_ARGS(args,
-		AST_APP_ARG(property);
-		AST_APP_ARG(opt);
-	);
-	struct odbc_txn_frame *tx;
-
-	AST_STANDARD_APP_ARGS(args, data);
-	if (strcasecmp(args.property, "transaction") == 0) {
-		if ((tx = find_transaction(chan, NULL, NULL, 1))) {
-			ast_copy_string(buf, tx->name, len);
-			return 0;
-		}
-	} else if (strcasecmp(args.property, "isolation") == 0) {
-		if (!ast_strlen_zero(args.opt)) {
-			tx = find_transaction(chan, NULL, args.opt, 0);
-		} else {
-			tx = find_transaction(chan, NULL, NULL, 1);
-		}
-		if (tx) {
-			ast_copy_string(buf, isolation2text(tx->isolation), len);
-			return 0;
-		}
-	} else if (strcasecmp(args.property, "forcecommit") == 0) {
-		if (!ast_strlen_zero(args.opt)) {
-			tx = find_transaction(chan, NULL, args.opt, 0);
-		} else {
-			tx = find_transaction(chan, NULL, NULL, 1);
-		}
-		if (tx) {
-			ast_copy_string(buf, tx->forcecommit ? "1" : "0", len);
-			return 0;
-		}
-	}
-	return -1;
-}
-
-static int acf_transaction_write(struct ast_channel *chan, const char *cmd, char *s, const char *value)
-{
-	AST_DECLARE_APP_ARGS(args,
-		AST_APP_ARG(property);
-		AST_APP_ARG(opt);
-	);
-	struct odbc_txn_frame *tx;
-	SQLINTEGER nativeerror=0, numfields=0;
-	SQLSMALLINT diagbytes=0, i;
-	unsigned char state[10], diagnostic[256];
-
-	AST_STANDARD_APP_ARGS(args, s);
-	if (strcasecmp(args.property, "transaction") == 0) {
-		/* Set active transaction */
-		struct odbc_obj *obj;
-		if ((tx = find_transaction(chan, NULL, value, 0))) {
-			mark_transaction_active(chan, tx);
-		} else {
-			/* No such transaction, create one */
-			struct ast_flags flags = { RES_ODBC_INDEPENDENT_CONNECTION };
-			if (ast_strlen_zero(args.opt) || !(obj = ast_odbc_request_obj2(args.opt, flags))) {
-				ast_log(LOG_ERROR, "Could not create transaction: invalid database specification '%s'\n", S_OR(args.opt, ""));
-				pbx_builtin_setvar_helper(chan, "ODBC_RESULT", "INVALID_DB");
-				return -1;
-			}
-			if (!find_transaction(chan, obj, value, 0)) {
-				pbx_builtin_setvar_helper(chan, "ODBC_RESULT", "FAILED_TO_CREATE");
-				return -1;
-			}
-			obj->tx = 1;
-		}
-		pbx_builtin_setvar_helper(chan, "ODBC_RESULT", "OK");
-		return 0;
-	} else if (strcasecmp(args.property, "forcecommit") == 0) {
-		/* Set what happens when an uncommitted transaction ends without explicit Commit or Rollback */
-		if (ast_strlen_zero(args.opt)) {
-			tx = find_transaction(chan, NULL, NULL, 1);
-		} else {
-			tx = find_transaction(chan, NULL, args.opt, 0);
-		}
-		if (!tx) {
-			pbx_builtin_setvar_helper(chan, "ODBC_RESULT", "FAILED_TO_CREATE");
-			return -1;
-		}
-		if (ast_true(value)) {
-			tx->forcecommit = 1;
-		} else if (ast_false(value)) {
-			tx->forcecommit = 0;
-		} else {
-			ast_log(LOG_ERROR, "Invalid value for forcecommit: '%s'\n", S_OR(value, ""));
-			pbx_builtin_setvar_helper(chan, "ODBC_RESULT", "INVALID_VALUE");
-			return -1;
-		}
-
-		pbx_builtin_setvar_helper(chan, "ODBC_RESULT", "OK");
-		return 0;
-	} else if (strcasecmp(args.property, "isolation") == 0) {
-		/* How do uncommitted transactions affect reads? */
-		int isolation = text2isolation(value);
-		if (ast_strlen_zero(args.opt)) {
-			tx = find_transaction(chan, NULL, NULL, 1);
-		} else {
-			tx = find_transaction(chan, NULL, args.opt, 0);
-		}
-		if (!tx) {
-			pbx_builtin_setvar_helper(chan, "ODBC_RESULT", "FAILED_TO_CREATE");
-			return -1;
-		}
-		if (isolation == 0) {
-			pbx_builtin_setvar_helper(chan, "ODBC_RESULT", "INVALID_VALUE");
-			ast_log(LOG_ERROR, "Invalid isolation specification: '%s'\n", S_OR(value, ""));
-		} else if (SQLSetConnectAttr(tx->obj->con, SQL_ATTR_TXN_ISOLATION, (void *)(long)isolation, 0) == SQL_ERROR) {
-			pbx_builtin_setvar_helper(chan, "ODBC_RESULT", "SQL_ERROR");
-			SQLGetDiagField(SQL_HANDLE_DBC, tx->obj->con, 1, SQL_DIAG_NUMBER, &numfields, SQL_IS_INTEGER, &diagbytes);
-			for (i = 0; i < numfields; i++) {
-				SQLGetDiagRec(SQL_HANDLE_DBC, tx->obj->con, i + 1, state, &nativeerror, diagnostic, sizeof(diagnostic), &diagbytes);
-				ast_log(LOG_WARNING, "SetConnectAttr (Txn isolation) returned an error: %s: %s\n", state, diagnostic);
-				if (i > 10) {
-					ast_log(LOG_WARNING, "Oh, that was good.  There are really %d diagnostics?\n", (int)numfields);
-					break;
-				}
-			}
-		} else {
-			pbx_builtin_setvar_helper(chan, "ODBC_RESULT", "OK");
-			tx->isolation = isolation;
-		}
-		return 0;
-	} else {
-		ast_log(LOG_ERROR, "Unknown property: '%s'\n", args.property);
-		return -1;
-	}
-}
-
-static struct ast_custom_function odbc_function = {
-	.name = "ODBC",
-	.read = acf_transaction_read,
-	.write = acf_transaction_write,
-};
-
-static const char * const app_commit = "ODBC_Commit";
-static const char * const app_rollback = "ODBC_Rollback";
-
 /*!
  * \internal
  * \brief Implements the channels provider.
@@ -1720,12 +839,10 @@ static const char * const app_rollback = "ODBC_Rollback";
 static int data_odbc_provider_handler(const struct ast_data_search *search,
 		struct ast_data *root)
 {
-	struct ao2_iterator aoi, aoi2;
+	struct ao2_iterator aoi;
 	struct odbc_class *class;
-	struct odbc_obj *current;
-	struct ast_data *data_odbc_class, *data_odbc_connections, *data_odbc_connection;
+	struct ast_data *data_odbc_class, *data_odbc_connections;
 	struct ast_data *enum_node;
-	int count;
 
 	aoi = ao2_iterator_init(class_container, 0);
 	while ((class = ao2_iterator_next(&aoi))) {
@@ -1737,18 +854,12 @@ static int data_odbc_provider_handler(const struct ast_data_search *search,
 
 		ast_data_add_structure(odbc_class, data_odbc_class, class);
 
-		if (!ao2_container_count(class->obj_container)) {
-			ao2_ref(class, -1);
-			continue;
-		}
-
 		data_odbc_connections = ast_data_add_node(data_odbc_class, "connections");
 		if (!data_odbc_connections) {
 			ao2_ref(class, -1);
 			continue;
 		}
 
-		ast_data_add_bool(data_odbc_class, "shared", !class->haspool);
 		/* isolation */
 		enum_node = ast_data_add_node(data_odbc_class, "isolation");
 		if (!enum_node) {
@@ -1756,30 +867,7 @@ static int data_odbc_provider_handler(const struct ast_data_search *search,
 			continue;
 		}
 		ast_data_add_int(enum_node, "value", class->isolation);
-		ast_data_add_str(enum_node, "text", isolation2text(class->isolation));
-
-		count = 0;
-		aoi2 = ao2_iterator_init(class->obj_container, 0);
-		while ((current = ao2_iterator_next(&aoi2))) {
-			data_odbc_connection = ast_data_add_node(data_odbc_connections, "connection");
-			if (!data_odbc_connection) {
-				ao2_ref(current, -1);
-				continue;
-			}
-
-			ao2_lock(current);
-			ast_data_add_str(data_odbc_connection, "status", current->used ? "in use" :
-					current->up && ast_odbc_sanity_check(current) ? "connected" : "disconnected");
-			ast_data_add_bool(data_odbc_connection, "transactional", current->tx);
-			ao2_unlock(current);
-
-			if (class->haspool) {
-				ast_data_add_int(data_odbc_connection, "number", ++count);
-			}
-
-			ao2_ref(current, -1);
-		}
-		ao2_iterator_destroy(&aoi2);
+		ast_data_add_str(enum_node, "text", ast_odbc_isolation2text(class->isolation));
 		ao2_ref(class, -1);
 
 		if (!ast_data_search_match(search, data_odbc_class)) {
@@ -1807,7 +895,6 @@ static int reload(void)
 {
 	struct odbc_cache_tables *table;
 	struct odbc_class *class;
-	struct odbc_obj *current;
 	struct ao2_iterator aoi = ao2_iterator_init(class_container, 0);
 
 	/* First, mark all to be purged */
@@ -1819,51 +906,12 @@ static int reload(void)
 
 	load_odbc_config();
 
-	/* Purge remaining classes */
-
-	/* Note on how this works; this is a case of circular references, so we
-	 * explicitly do NOT want to use a callback here (or we wind up in
-	 * recursive hell).
-	 *
-	 * 1. Iterate through all the classes.  Note that the classes will currently
-	 * contain two classes of the same name, one of which is marked delme and
-	 * will be purged when all remaining objects of the class are released, and
-	 * the other, which was created above when we re-parsed the config file.
-	 * 2. On each class, there is a reference held by the master container and
-	 * a reference held by each connection object.  There are two cases for
-	 * destruction of the class, noted below.  However, in all cases, all O-refs
-	 * (references to objects) will first be freed, which will cause the C-refs
-	 * (references to classes) to be decremented (but never to 0, because the
-	 * class container still has a reference).
-	 *    a) If the class has outstanding objects, the C-ref by the class
-	 *    container will then be freed, which leaves only C-refs by any
-	 *    outstanding objects.  When the final outstanding object is released
-	 *    (O-refs held by applications and dialplan functions), it will in turn
-	 *    free the final C-ref, causing class destruction.
-	 *    b) If the class has no outstanding objects, when the class container
-	 *    removes the final C-ref, the class will be destroyed.
-	 */
 	aoi = ao2_iterator_init(class_container, 0);
-	while ((class = ao2_iterator_next(&aoi))) { /* C-ref++ (by iterator) */
+	while ((class = ao2_iterator_next(&aoi))) {
 		if (class->delme) {
-			struct ao2_iterator aoi2 = ao2_iterator_init(class->obj_container, 0);
-			while ((current = ao2_iterator_next(&aoi2))) { /* O-ref++ (by iterator) */
-				ao2_unlink(class->obj_container, current); /* unlink O-ref from class (reference handled implicitly) */
-				ao2_ref(current, -1); /* O-ref-- (by iterator) */
-				/* At this point, either
-				 * a) there's an outstanding O-ref, or
-				 * b) the object has already been destroyed.
-				 */
-			}
-			ao2_iterator_destroy(&aoi2);
-			ao2_unlink(class_container, class); /* unlink C-ref from container (reference handled implicitly) */
-			/* At this point, either
-			 * a) there's an outstanding O-ref, which holds an outstanding C-ref, or
-			 * b) the last remaining C-ref is held by the iterator, which will be
-			 * destroyed in the next step.
-			 */
+			ao2_unlink(class_container, class);
 		}
-		ao2_ref(class, -1); /* C-ref-- (by iterator) */
+		ao2_ref(class, -1);
 	}
 	ao2_iterator_destroy(&aoi);
 
@@ -1901,9 +949,6 @@ static int load_module(void)
 		return AST_MODULE_LOAD_DECLINE;
 	ast_cli_register_multiple(cli_odbc, ARRAY_LEN(cli_odbc));
 	ast_data_register_multiple(odbc_providers, ARRAY_LEN(odbc_providers));
-	ast_register_application_xml(app_commit, commit_exec);
-	ast_register_application_xml(app_rollback, rollback_exec);
-	ast_custom_function_register(&odbc_function);
 	ast_log(LOG_NOTICE, "res_odbc loaded.\n");
 	return 0;
 }
diff --git a/res/res_odbc.exports.in b/res/res_odbc.exports.in
index ad674be..dc7c289 100644
--- a/res/res_odbc.exports.in
+++ b/res/res_odbc.exports.in
@@ -1,20 +1,7 @@
 {
 	global:
-		LINKER_SYMBOL_PREFIXast_odbc_ast_str_SQLGetData;
-		LINKER_SYMBOL_PREFIXast_odbc_backslash_is_escape;
-		LINKER_SYMBOL_PREFIXast_odbc_clear_cache;
-		LINKER_SYMBOL_PREFIXast_odbc_direct_execute;
-		LINKER_SYMBOL_PREFIXast_odbc_find_column;
-		LINKER_SYMBOL_PREFIXast_odbc_find_table;
-		LINKER_SYMBOL_PREFIXast_odbc_prepare_and_execute;
-		LINKER_SYMBOL_PREFIXast_odbc_release_obj;
-		LINKER_SYMBOL_PREFIXast_odbc_request_obj;
-		LINKER_SYMBOL_PREFIX_ast_odbc_request_obj;
-		LINKER_SYMBOL_PREFIXast_odbc_request_obj2;
-		LINKER_SYMBOL_PREFIX_ast_odbc_request_obj2;
-		LINKER_SYMBOL_PREFIXast_odbc_retrieve_transaction_obj;
-		LINKER_SYMBOL_PREFIXast_odbc_sanity_check;
-		LINKER_SYMBOL_PREFIXast_odbc_smart_execute;
+		LINKER_SYMBOL_PREFIXast_odbc_*;
+		LINKER_SYMBOL_PREFIX_ast_odbc_*;
 	local:
 		*;
 };
diff --git a/res/res_odbc_transaction.c b/res/res_odbc_transaction.c
new file mode 100644
index 0000000..33800c3
--- /dev/null
+++ b/res/res_odbc_transaction.c
@@ -0,0 +1,529 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2016, Digium, Inc.
+ *
+ * Mark Michelson <mmichelson at 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.
+ */
+
+#include "asterisk.h"
+
+#include "asterisk/res_odbc.h"
+#include "asterisk/res_odbc_transaction.h"
+#include "asterisk/channel.h"
+#include "asterisk/pbx.h"
+#include "asterisk/app.h"
+#include "asterisk/module.h"
+
+/*** MODULEINFO
+	<depend>res_odbc</depend>
+	<support_level>core</support_level>
+ ***/
+
+/*** DOCUMENTATION
+	<function name="ODBC" language="en_US">
+		<synopsis>
+			Controls ODBC transaction properties.
+		</synopsis>
+		<syntax>
+			<parameter name="property" required="true">
+				<enumlist>
+					<enum name="transaction">
+						<para>Gets or sets the active transaction ID.  If set, and the transaction ID does not
+						exist and a <replaceable>database name</replaceable> is specified as an argument, it will be created.</para>
+					</enum>
+					<enum name="forcecommit">
+						<para>Controls whether a transaction will be automatically committed when the channel
+						hangs up.  Defaults to false.  If a <replaceable>transaction ID</replaceable> is specified in the optional argument,
+						the property will be applied to that ID, otherwise to the current active ID.</para>
+					</enum>
+					<enum name="isolation">
+						<para>Controls the data isolation on uncommitted transactions.  May be one of the
+						following: <literal>read_committed</literal>, <literal>read_uncommitted</literal>,
+						<literal>repeatable_read</literal>, or <literal>serializable</literal>.  Defaults to the
+						database setting in <filename>res_odbc.conf</filename> or <literal>read_committed</literal>
+						if not specified.  If a <replaceable>transaction ID</replaceable> is specified as an optional argument, it will be
+						applied to that ID, otherwise the current active ID.</para>
+					</enum>
+				</enumlist>
+			</parameter>
+			<parameter name="argument" required="false" />
+		</syntax>
+		<description>
+			<para>The ODBC() function allows setting several properties to influence how a connected
+			database processes transactions.</para>
+		</description>
+	</function>
+	<application name="ODBC_Commit" language="en_US">
+		<synopsis>
+			Commits a currently open database transaction.
+		</synopsis>
+		<syntax>
+			<parameter name="transaction ID" required="no" />
+		</syntax>
+		<description>
+			<para>Commits the database transaction specified by <replaceable>transaction ID</replaceable>
+			or the current active transaction, if not specified.</para>
+		</description>
+	</application>
+	<application name="ODBC_Rollback" language="en_US">
+		<synopsis>
+			Rollback a currently open database transaction.
+		</synopsis>
+		<syntax>
+			<parameter name="transaction ID" required="no" />
+		</syntax>
+		<description>
+			<para>Rolls back the database transaction specified by <replaceable>transaction ID</replaceable>
+			or the current active transaction, if not specified.</para>
+		</description>
+	</application>
+ ***/
+
+struct odbc_txn_frame {
+	AST_LIST_ENTRY(odbc_txn_frame) list;
+	struct odbc_obj *obj;        /*!< Database handle within which transacted statements are run */
+	/*!\brief Is this record the current active transaction within the channel?
+	 * Note that the active flag is really only necessary for statements which
+	 * are triggered from the dialplan, as there isn't a direct correlation
+	 * between multiple statements.  Applications wishing to use transactions
+	 * may simply perform each statement on the same odbc_obj, which keeps the
+	 * transaction persistent.
+	 */
+	unsigned int active:1;
+	unsigned int forcecommit:1;     /*!< Should uncommitted transactions be auto-committed on handle release? */
+	unsigned int isolation;         /*!< Flags for how the DB should deal with data in other, uncommitted transactions */
+	char name[0];                   /*!< Name of this transaction ID */
+};
+
+static struct odbc_txn_frame *release_transaction(struct odbc_txn_frame *tx);
+
+static void odbc_txn_free(void *vdata)
+{
+	struct odbc_txn_frame *tx;
+	AST_LIST_HEAD(, odbc_txn_frame) *oldlist = vdata;
+
+	ast_debug(2, "odbc_txn_free(%p) called\n", vdata);
+
+	AST_LIST_LOCK(oldlist);
+	while ((tx = AST_LIST_REMOVE_HEAD(oldlist, list))) {
+		release_transaction(tx);
+	}
+	AST_LIST_UNLOCK(oldlist);
+	AST_LIST_HEAD_DESTROY(oldlist);
+	ast_free(oldlist);
+}
+
+static const struct ast_datastore_info txn_info = {
+	.type = "ODBC_Transaction",
+	.destroy = odbc_txn_free,
+};
+
+static struct odbc_txn_frame *create_transaction(struct ast_channel *chan, const char *name, const char *dsn)
+{
+	struct ast_datastore *txn_store;
+	AST_LIST_HEAD(, odbc_txn_frame) *oldlist;
+	struct odbc_txn_frame *txn = NULL;
+	struct odbc_txn_frame *otxn;
+
+	if (ast_strlen_zero(dsn)) {
+		return NULL;
+	}
+
+	ast_channel_lock(chan);
+	if ((txn_store = ast_channel_datastore_find(chan, &txn_info, NULL))) {
+		oldlist = txn_store->data;
+	} else {
+		if (!(txn_store = ast_datastore_alloc(&txn_info, NULL))) {
+			ast_log(LOG_ERROR, "Unable to allocate a new datastore.  Cannot create a new transaction.\n");
+			ast_channel_unlock(chan);
+			return NULL;
+		}
+
+		if (!(oldlist = ast_calloc(1, sizeof(*oldlist)))) {
+			ast_log(LOG_ERROR, "Unable to allocate datastore list head.  Cannot create a new transaction.\n");
+			ast_datastore_free(txn_store);
+			ast_channel_unlock(chan);
+			return NULL;
+		}
+
+		txn_store->data = oldlist;
+		AST_LIST_HEAD_INIT(oldlist);
+		ast_channel_datastore_add(chan, txn_store);
+	}
+	ast_channel_unlock(chan);
+
+	txn = ast_calloc(1, sizeof(*txn) + strlen(name) + 1);
+	if (!txn) {
+		return NULL;
+	}
+
+	strcpy(txn->name, name); /* SAFE */
+	txn->obj = ast_odbc_request_obj(dsn, 0);
+	if (!txn->obj) {
+		ast_free(txn);
+		return NULL;
+	}
+	txn->isolation = ast_odbc_class_get_isolation(txn->obj->parent);
+	txn->forcecommit = ast_odbc_class_get_isolation(txn->obj->parent);
+	txn->active = 1;
+
+	if (SQLSetConnectAttr(txn->obj->con, SQL_ATTR_AUTOCOMMIT, (void *)SQL_AUTOCOMMIT_OFF, 0) == SQL_ERROR) {
+		ast_odbc_print_errors(SQL_HANDLE_DBC, txn->obj->con, "SetConnectAttr (Autocommit)");
+		ast_odbc_release_obj(txn->obj);
+		ast_free(txn);
+		return NULL;
+	}
+
+	/* Set the isolation property */
+	if (SQLSetConnectAttr(txn->obj->con, SQL_ATTR_TXN_ISOLATION, (void *)(long)txn->isolation, 0) == SQL_ERROR) {
+		ast_odbc_print_errors(SQL_HANDLE_DBC, txn->obj->con, "SetConnectAttr");
+		ast_odbc_release_obj(txn->obj);
+		ast_free(txn);
+		return NULL;
+	}
+
+	/* On creation, the txn becomes active, and all others inactive */
+	AST_LIST_LOCK(oldlist);
+	AST_LIST_TRAVERSE(oldlist, otxn, list) {
+		otxn->active = 0;
+	}
+	AST_LIST_INSERT_TAIL(oldlist, txn, list);
+	AST_LIST_UNLOCK(oldlist);
+
+	return txn;
+}
+
+static struct odbc_txn_frame *find_transaction(struct ast_channel *chan, const char *name, int active)
+{
+	struct ast_datastore *txn_store;
+	AST_LIST_HEAD(, odbc_txn_frame) *oldlist;
+	struct odbc_txn_frame *txn = NULL;
+
+	if (!chan || (!active && !name)) {
+		return NULL;
+	}
+
+	ast_channel_lock(chan);
+	txn_store = ast_channel_datastore_find(chan, &txn_info, NULL);
+	ast_channel_unlock(chan);
+
+	if (!txn_store) {
+		/* No datastore? Definitely no transaction then */
+		return NULL;
+	}
+	
+	oldlist = txn_store->data;
+	AST_LIST_LOCK(oldlist);
+
+	AST_LIST_TRAVERSE(oldlist, txn, list) {
+		if (active) {
+			if (txn->active) {
+				break;
+			}
+		} else if (!strcasecmp(txn->name, name)) {
+			break;
+		}
+	}
+	AST_LIST_UNLOCK(oldlist);
+
+	return txn;
+}
+
+static struct odbc_txn_frame *release_transaction(struct odbc_txn_frame *tx)
+{
+	if (!tx) {
+		return NULL;
+	}
+
+	ast_debug(2, "release_transaction(%p) called (tx->obj = %p\n", tx, tx->obj);
+
+	ast_debug(1, "called on a transactional handle with %s\n", tx->forcecommit ? "COMMIT" : "ROLLBACK");
+	if (SQLEndTran(SQL_HANDLE_DBC, tx->obj->con, tx->forcecommit ? SQL_COMMIT : SQL_ROLLBACK) == SQL_ERROR) {
+		ast_odbc_print_errors(SQL_HANDLE_DBC, tx->obj->con, "SQLEndTran");
+	}
+
+	/* Transaction is done, reset autocommit
+	 *
+	 * XXX I'm unsure if this is actually necessary, since we're releasing
+	 * the connection back to unixODBC. However, if unixODBC pooling is enabled,
+	 * it can't hurt to do just in case.
+	 */
+	if (SQLSetConnectAttr(tx->obj->con, SQL_ATTR_AUTOCOMMIT, (void *)SQL_AUTOCOMMIT_ON, 0) == SQL_ERROR) {
+		ast_odbc_print_errors(SQL_HANDLE_DBC, tx->obj->con, "SQLSetAttr");
+	}
+
+	ast_odbc_release_obj(tx->obj);
+	ast_free(tx);
+	return NULL;
+}
+
+static int commit_exec(struct ast_channel *chan, const char *data)
+{
+	struct odbc_txn_frame *tx;
+
+	if (ast_strlen_zero(data)) {
+		tx = find_transaction(chan, NULL, 1);
+	} else {
+		tx = find_transaction(chan, data, 0);
+	}
+
+	/* XXX COMMIT_RESULT is set to OK even if no transaction was found. Very misleading */
+	pbx_builtin_setvar_helper(chan, "COMMIT_RESULT", "OK");
+
+	if (tx) {
+		if (SQLEndTran(SQL_HANDLE_DBC, tx->obj->con, SQL_COMMIT) == SQL_ERROR) {
+			struct ast_str *errors = ast_odbc_print_errors(SQL_HANDLE_DBC, tx->obj->con, "SQLEndTran");
+			pbx_builtin_setvar_helper(chan, "COMMIT_RESULT", ast_str_buffer(errors));
+		}
+	}
+	return 0;
+}
+
+static int rollback_exec(struct ast_channel *chan, const char *data)
+{
+	struct odbc_txn_frame *tx;
+
+	if (ast_strlen_zero(data)) {
+		tx = find_transaction(chan, NULL, 1);
+	} else {
+		tx = find_transaction(chan, data, 0);
+	}
+
+	/* XXX ROLLBACK_RESULT is set to OK even if no transaction was found. Very misleading */
+	pbx_builtin_setvar_helper(chan, "ROLLBACK_RESULT", "OK");
+
+	if (tx) {
+		if (SQLEndTran(SQL_HANDLE_DBC, tx->obj->con, SQL_ROLLBACK) == SQL_ERROR) {
+			struct ast_str *errors = ast_odbc_print_errors(SQL_HANDLE_DBC, tx->obj->con, "SQLEndTran");
+			pbx_builtin_setvar_helper(chan, "ROLLBACK_RESULT", ast_str_buffer(errors));
+		}
+	}
+	return 0;
+}
+
+static int acf_transaction_read(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len)
+{
+	AST_DECLARE_APP_ARGS(args,
+		AST_APP_ARG(property);
+		AST_APP_ARG(opt);
+	);
+	struct odbc_txn_frame *tx;
+
+	AST_STANDARD_APP_ARGS(args, data);
+	if (strcasecmp(args.property, "transaction") == 0) {
+		if ((tx = find_transaction(chan, NULL, 1))) {
+			ast_copy_string(buf, tx->name, len);
+			return 0;
+		}
+	} else if (strcasecmp(args.property, "isolation") == 0) {
+		if (!ast_strlen_zero(args.opt)) {
+			tx = find_transaction(chan, args.opt, 0);
+		} else {
+			tx = find_transaction(chan, NULL, 1);
+		}
+		if (tx) {
+			ast_copy_string(buf, ast_odbc_isolation2text(tx->isolation), len);
+			return 0;
+		}
+	} else if (strcasecmp(args.property, "forcecommit") == 0) {
+		if (!ast_strlen_zero(args.opt)) {
+			tx = find_transaction(chan, args.opt, 0);
+		} else {
+			tx = find_transaction(chan, NULL, 1);
+		}
+		if (tx) {
+			ast_copy_string(buf, tx->forcecommit ? "1" : "0", len);
+			return 0;
+		}
+	}
+	return -1;
+}
+
+/* XXX The idea of "active" transactions is silly and makes things
+ * more prone to error. It would be much better if the transaction
+ * always had to be specified by name so that no implicit behavior
+ * occurred.
+ */
+static int mark_transaction_active(struct ast_channel *chan, struct odbc_txn_frame *tx)
+{
+	struct ast_datastore *txn_store;
+	AST_LIST_HEAD(, odbc_txn_frame) *oldlist;
+	struct odbc_txn_frame *active = NULL, *txn;
+
+	if (!chan) {
+		return -1;
+	}
+
+	ast_channel_lock(chan);
+	if (!(txn_store = ast_channel_datastore_find(chan, &txn_info, NULL))) {
+		ast_channel_unlock(chan);
+		return -1;
+	}
+
+	oldlist = txn_store->data;
+	AST_LIST_LOCK(oldlist);
+	AST_LIST_TRAVERSE(oldlist, txn, list) {
+		if (txn == tx) {
+			txn->active = 1;
+			active = txn;
+		} else {
+			txn->active = 0;
+		}
+	}
+	AST_LIST_UNLOCK(oldlist);
+	ast_channel_unlock(chan);
+	return active ? 0 : -1;
+}
+
+static int acf_transaction_write(struct ast_channel *chan, const char *cmd, char *s, const char *value)
+{
+	AST_DECLARE_APP_ARGS(args,
+		AST_APP_ARG(property);
+		AST_APP_ARG(opt);
+	);
+	struct odbc_txn_frame *tx;
+
+	AST_STANDARD_APP_ARGS(args, s);
+	if (strcasecmp(args.property, "transaction") == 0) {
+		/* Set active transaction */
+		if ((tx = find_transaction(chan, value, 0))) {
+			mark_transaction_active(chan, tx);
+		} else if (!create_transaction(chan, value, args.opt)) {
+			pbx_builtin_setvar_helper(chan, "ODBC_RESULT", "FAILED_TO_CREATE");
+			return -1;
+		}
+		pbx_builtin_setvar_helper(chan, "ODBC_RESULT", "OK");
+		return 0;
+	} else if (strcasecmp(args.property, "forcecommit") == 0) {
+		/* Set what happens when an uncommitted transaction ends without explicit Commit or Rollback */
+		if (ast_strlen_zero(args.opt)) {
+			tx = find_transaction(chan, NULL, 1);
+		} else {
+			tx = find_transaction(chan, args.opt, 0);
+		}
+		if (!tx) {
+			pbx_builtin_setvar_helper(chan, "ODBC_RESULT", "FAILED_TO_CREATE");
+			return -1;
+		}
+		if (ast_true(value)) {
+			tx->forcecommit = 1;
+		} else if (ast_false(value)) {
+			tx->forcecommit = 0;
+		} else {
+			ast_log(LOG_ERROR, "Invalid value for forcecommit: '%s'\n", S_OR(value, ""));
+			pbx_builtin_setvar_helper(chan, "ODBC_RESULT", "INVALID_VALUE");
+			return -1;
+		}
+
+		pbx_builtin_setvar_helper(chan, "ODBC_RESULT", "OK");
+		return 0;
+	} else if (strcasecmp(args.property, "isolation") == 0) {
+		/* How do uncommitted transactions affect reads? */
+		/* XXX This is completely useless. The problem is that setting the isolation here
+		 * does not actually alter the connection. The only time the isolation gets set is
+		 * when the transaction is created. The only way to set isolation is to set it on
+		 * the ODBC class's configuration in res_odbc.conf.
+		 */
+		int isolation = ast_odbc_text2isolation(value);
+		if (ast_strlen_zero(args.opt)) {
+			tx = find_transaction(chan, NULL, 1);
+		} else {
+			tx = find_transaction(chan, args.opt, 0);
+		}
+		if (!tx) {
+			pbx_builtin_setvar_helper(chan, "ODBC_RESULT", "FAILED_TO_CREATE");
+			return -1;
+		}
+		if (isolation == 0) {
+			pbx_builtin_setvar_helper(chan, "ODBC_RESULT", "INVALID_VALUE");
+			ast_log(LOG_ERROR, "Invalid isolation specification: '%s'\n", S_OR(value, ""));
+		} else if (SQLSetConnectAttr(tx->obj->con, SQL_ATTR_TXN_ISOLATION, (void *)(long)isolation, 0) == SQL_ERROR) {
+			pbx_builtin_setvar_helper(chan, "ODBC_RESULT", "SQL_ERROR");
+			ast_odbc_print_errors(SQL_HANDLE_DBC, tx->obj->con, "SetConnectAttr (Txn isolation)");
+		} else {
+			pbx_builtin_setvar_helper(chan, "ODBC_RESULT", "OK");
+			tx->isolation = isolation;
+		}
+		return 0;
+	} else {
+		ast_log(LOG_ERROR, "Unknown property: '%s'\n", args.property);
+		return -1;
+	}
+}
+
+struct odbc_obj *ast_odbc_retrieve_transaction_obj(struct ast_channel *chan, const char *objname)
+{
+	struct ast_datastore *txn_store;
+	AST_LIST_HEAD(, odbc_txn_frame) *oldlist;
+	struct odbc_txn_frame *txn = NULL;
+
+	if (!chan || !objname) {
+		/* No channel == no transaction */
+		return NULL;
+	}
+
+	ast_channel_lock(chan);
+	if ((txn_store = ast_channel_datastore_find(chan, &txn_info, NULL))) {
+		oldlist = txn_store->data;
+	} else {
+		ast_channel_unlock(chan);
+		return NULL;
+	}
+
+	AST_LIST_LOCK(oldlist);
+	ast_channel_unlock(chan);
+
+	AST_LIST_TRAVERSE(oldlist, txn, list) {
+		if (txn->obj && txn->obj->parent && !strcmp(ast_odbc_class_get_name(txn->obj->parent), objname)) {
+			AST_LIST_UNLOCK(oldlist);
+			return txn->obj;
+		}
+	}
+	AST_LIST_UNLOCK(oldlist);
+	return NULL;
+}
+
+static struct ast_custom_function odbc_function = {
+	.name = "ODBC",
+	.read = acf_transaction_read,
+	.write = acf_transaction_write,
+};
+
+static const char * const app_commit = "ODBC_Commit";
+static const char * const app_rollback = "ODBC_Rollback";
+
+/* XXX res_odbc takes the path of disallowing unloads from happening.
+ * It's not a great precedent, but since trying to deal with unloading the module
+ * while transactions are active seems like a huge pain to deal with, we'll go
+ * the same way here.
+ */
+static int unload_module(void)
+{
+	return -1;
+}
+
+static int load_module(void)
+{
+	ast_register_application_xml(app_commit, commit_exec);
+	ast_register_application_xml(app_rollback, rollback_exec);
+	ast_custom_function_register(&odbc_function);
+	return 0;
+}
+
+AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_GLOBAL_SYMBOLS | AST_MODFLAG_LOAD_ORDER, "ODBC transaction resource",
+		.support_level = AST_MODULE_SUPPORT_CORE,
+		.load = load_module,
+		.unload = unload_module,
+		.load_pri = AST_MODPRI_REALTIME_DEPEND,
+	       );
diff --git a/res/res_odbc_transaction.exports.in b/res/res_odbc_transaction.exports.in
new file mode 100644
index 0000000..5b06155
--- /dev/null
+++ b/res/res_odbc_transaction.exports.in
@@ -0,0 +1,6 @@
+{
+	global:
+		LINKER_SYMBOL_PREFIXast_odbc_retrieve_transaction_obj;
+	local:
+		*;
+};
diff --git a/res/res_phoneprov.c b/res/res_phoneprov.c
index 6986d20..df93c5b 100644
--- a/res/res_phoneprov.c
+++ b/res/res_phoneprov.c
@@ -1193,8 +1193,7 @@ static struct ast_http_uri phoneprovuri = {
 
 static struct varshead *get_defaults(void)
 {
-	struct ast_config *phoneprov_cfg;
-	struct ast_config *cfg;
+	struct ast_config *phoneprov_cfg, *cfg = CONFIG_STATUS_FILEINVALID;
 	const char *value;
 	struct ast_variable *v;
 	struct ast_var_t *var;
@@ -1233,10 +1232,12 @@ static struct varshead *get_defaults(void)
 	if (!value) {
 		if ((cfg = ast_config_load("sip.conf", config_flags)) && cfg != CONFIG_STATUS_FILEINVALID) {
 			value = ast_variable_retrieve(cfg, "general", "bindport");
-			ast_config_destroy(cfg);
 		}
 	}
 	var = ast_var_assign(variable_lookup[AST_PHONEPROV_STD_SERVER_PORT], S_OR(value, "5060"));
+	if(cfg && cfg != CONFIG_STATUS_FILEINVALID) {
+		ast_config_destroy(cfg);
+	}
 	AST_VAR_LIST_INSERT_TAIL(defaults, var);
 
 	value = ast_variable_retrieve(phoneprov_cfg, "general", pp_general_lookup[AST_PHONEPROV_STD_PROFILE]);
@@ -1288,6 +1289,7 @@ static int load_users(void)
 	if (!(cfg = ast_config_load("users.conf", config_flags))
 		|| cfg == CONFIG_STATUS_FILEINVALID) {
 		ast_log(LOG_WARNING, "Unable to load users.conf\n");
+		ast_var_list_destroy(defaults);
 		return -1;
 	}
 
@@ -1337,6 +1339,7 @@ static int load_users(void)
 		}
 	}
 	ast_config_destroy(cfg);
+	ast_var_list_destroy(defaults);
 	return 0;
 }
 
@@ -1476,7 +1479,6 @@ static int reload(void)
 	ao2_lock(providers);
 	i = ao2_iterator_init(providers, 0);
 	for(; (provider = ao2_iterator_next(&i)); ao2_ref(provider, -1)) {
-		ast_log(LOG_VERBOSE, "Reloading provider '%s' users.\n", provider->provider_name);
 		if (provider->load_users()) {
 			ast_log(LOG_ERROR, "Unable to load provider '%s' users. Reload aborted.\n", provider->provider_name);
 			continue;
@@ -1553,7 +1555,6 @@ int ast_phoneprov_provider_register(char *provider_name,
 		return -1;
 	}
 
-	ast_log(LOG_VERBOSE, "Registered phoneprov provider '%s'.\n", provider_name);
 	return 0;
 }
 
@@ -1606,7 +1607,6 @@ void ast_phoneprov_provider_unregister(char *provider_name)
 
 	ast_phoneprov_delete_extensions(provider_name);
 	ao2_find(providers, provider_name, OBJ_SEARCH_KEY | OBJ_NODATA | OBJ_UNLINK);
-	ast_log(LOG_VERBOSE, "Unegistered phoneprov provider '%s'.\n", provider_name);
 }
 
 int ast_phoneprov_add_extension(char *provider_name, struct varshead *vars)
@@ -1679,7 +1679,6 @@ int ast_phoneprov_add_extension(char *provider_name, struct varshead *vars)
 			ast_log(LOG_WARNING, "Could not create http routes for '%s' - skipping\n", user->macaddress);
 			return -1;
 		}
-		ast_log(LOG_VERBOSE, "Created %s/%s for provider '%s'.\n", username, mac, provider_name);
 		ao2_link(users, user);
 
 	} else {
@@ -1698,7 +1697,6 @@ int ast_phoneprov_add_extension(char *provider_name, struct varshead *vars)
 			exten = delete_extension(exten);
 			return -1;
 		}
-		ast_log(LOG_VERBOSE, "Added %s/%s for provider '%s'.\n", username, mac, provider_name);
 	}
 
 	return 0;
diff --git a/res/res_pjproject.c b/res/res_pjproject.c
new file mode 100644
index 0000000..f54c371
--- /dev/null
+++ b/res/res_pjproject.c
@@ -0,0 +1,458 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2013, Digium, Inc.
+ *
+ * David M. Lee, II <dlee at 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 Bridge PJPROJECT logging to Asterisk logging.
+ * \author David M. Lee, II <dlee at digium.com>
+ *
+ * PJPROJECT logging doesn't exactly match Asterisk logging, but mapping the two is
+ * not too bad. PJPROJECT log levels are identified by a single int. Limits are
+ * not specified by PJPROJECT, but their implementation used 1 through 6.
+ *
+ * The mapping is as follows:
+ *  - 0: LOG_ERROR
+ *  - 1: LOG_ERROR
+ *  - 2: LOG_WARNING
+ *  - 3 and above: equivalent to ast_debug(level, ...) for res_pjproject.so
+ */
+
+/*** MODULEINFO
+	<depend>pjproject</depend>
+	<support_level>core</support_level>
+ ***/
+
+/*** DOCUMENTATION
+	<configInfo name="res_pjproject" language="en_US">
+		<synopsis>pjproject common configuration</synopsis>
+		<configFile name="pjproject.conf">
+			<configObject name="log_mappings">
+				<synopsis>PJPROJECT to Asterisk Log Level Mapping</synopsis>
+				<description><para>Warnings and errors in the pjproject libraries are generally handled
+					by Asterisk.  In many cases, Asterisk wouldn't even consider them to
+					be warnings or errors so the messages emitted by pjproject directly
+					are either superfluous or misleading.  The 'log_mappings'
+					object allows mapping the pjproject levels to Asterisk levels, or nothing.
+					</para>
+					<note><para>The id of this object, as well as its type, must be
+					'log_mappings' or it won't be found.</para></note>
+				</description>
+				<configOption name="type">
+					<synopsis>Must be of type 'log_mappings'.</synopsis>
+				</configOption>
+				<configOption name="asterisk_error" default="0,1">
+					<synopsis>A comma separated list of pjproject log levels to map to Asterisk LOG_ERROR.</synopsis>
+				</configOption>
+				<configOption name="asterisk_warning" default="2">
+					<synopsis>A comma separated list of pjproject log levels to map to Asterisk LOG_WARNING.</synopsis>
+				</configOption>
+				<configOption name="asterisk_notice" default="">
+					<synopsis>A comma separated list of pjproject log levels to map to Asterisk LOG_NOTICE.</synopsis>
+				</configOption>
+				<configOption name="asterisk_debug" default="3,4,5">
+					<synopsis>A comma separated list of pjproject log levels to map to Asterisk LOG_DEBUG.</synopsis>
+				</configOption>
+				<configOption name="asterisk_verbose" default="">
+					<synopsis>A comma separated list of pjproject log levels to map to Asterisk LOG_VERBOSE.</synopsis>
+				</configOption>
+			</configObject>
+		</configFile>
+	</configInfo>
+ ***/
+
+#include "asterisk.h"
+
+ASTERISK_REGISTER_FILE()
+
+#include <stdarg.h>
+#include <pjlib.h>
+#include <pjsip.h>
+#include <pj/log.h>
+
+#include "asterisk/logger.h"
+#include "asterisk/module.h"
+#include "asterisk/cli.h"
+#include "asterisk/res_pjproject.h"
+#include "asterisk/vector.h"
+#include "asterisk/sorcery.h"
+
+static struct ast_sorcery *pjproject_sorcery;
+static pj_log_func *log_cb_orig;
+static unsigned decor_orig;
+
+static AST_VECTOR(buildopts, char *) buildopts;
+
+/*! Protection from other log intercept instances.  There can be only one at a time. */
+AST_MUTEX_DEFINE_STATIC(pjproject_log_intercept_lock);
+
+struct pjproject_log_intercept_data {
+	pthread_t thread;
+	int fd;
+};
+
+static struct pjproject_log_intercept_data pjproject_log_intercept = {
+	.thread = AST_PTHREADT_NULL,
+	.fd = -1,
+};
+
+struct log_mappings {
+	/*! Sorcery object details */
+	SORCERY_OBJECT(details);
+	/*! These are all comma-separated lists of pjproject log levels */
+	AST_DECLARE_STRING_FIELDS(
+		/*! pjproject log levels mapped to Asterisk ERROR */
+		AST_STRING_FIELD(asterisk_error);
+		/*! pjproject log levels mapped to Asterisk WARNING */
+		AST_STRING_FIELD(asterisk_warning);
+		/*! pjproject log levels mapped to Asterisk NOTICE */
+		AST_STRING_FIELD(asterisk_notice);
+		/*! pjproject log levels mapped to Asterisk VERBOSE */
+		AST_STRING_FIELD(asterisk_verbose);
+		/*! pjproject log levels mapped to Asterisk DEBUG */
+		AST_STRING_FIELD(asterisk_debug);
+	);
+};
+
+static struct log_mappings *default_log_mappings;
+
+static struct log_mappings *get_log_mappings(void)
+{
+	struct log_mappings *mappings;
+
+	mappings = ast_sorcery_retrieve_by_id(pjproject_sorcery, "log_mappings", "log_mappings");
+	if (!mappings) {
+		return ao2_bump(default_log_mappings);
+	}
+
+	return mappings;
+}
+
+#define __LOG_SUPPRESS -1
+
+static int get_log_level(int pj_level)
+{
+	RAII_VAR(struct log_mappings *, mappings, get_log_mappings(), ao2_cleanup);
+	unsigned char l;
+
+	if (!mappings) {
+		return __LOG_ERROR;
+	}
+
+	l = '0' + fmin(pj_level, 9);
+
+	if (strchr(mappings->asterisk_error, l)) {
+		return __LOG_ERROR;
+	} else if (strchr(mappings->asterisk_warning, l)) {
+		return __LOG_WARNING;
+	} else if (strchr(mappings->asterisk_notice, l)) {
+		return __LOG_NOTICE;
+	} else if (strchr(mappings->asterisk_verbose, l)) {
+		return __LOG_VERBOSE;
+	} else if (strchr(mappings->asterisk_debug, l)) {
+		return __LOG_DEBUG;
+	}
+
+	return __LOG_SUPPRESS;
+}
+
+static void log_forwarder(int level, const char *data, int len)
+{
+	int ast_level;
+	/* PJPROJECT doesn't provide much in the way of source info */
+	const char * log_source = "pjproject";
+	int log_line = 0;
+	const char *log_func = "<?>";
+	int mod_level;
+
+	if (pjproject_log_intercept.fd != -1
+		&& pjproject_log_intercept.thread == pthread_self()) {
+		/*
+		 * We are handling a CLI command intercepting PJPROJECT
+		 * log output.
+		 */
+		ast_cli(pjproject_log_intercept.fd, "%s\n", data);
+		return;
+	}
+
+	ast_level = get_log_level(level);
+
+	if (ast_level == __LOG_SUPPRESS) {
+		return;
+	}
+
+	if (ast_level == __LOG_DEBUG) {
+		/* For levels 3 and up, obey the debug level for res_pjproject */
+		mod_level = ast_opt_dbg_module ?
+			ast_debug_get_by_module("res_pjproject") : 0;
+		if (option_debug < level && mod_level < level) {
+			return;
+		}
+	}
+
+	/* PJPROJECT uses indention to indicate function call depth. We'll prepend
+	 * log statements with a tab so they'll have a better shot at lining
+	 * up */
+	ast_log(ast_level, log_source, log_line, log_func, "\t%s\n", data);
+}
+
+static void capture_buildopts_cb(int level, const char *data, int len)
+{
+	if (strstr(data, "Teluu") || strstr(data, "Dumping")) {
+		return;
+	}
+
+	AST_VECTOR_ADD_SORTED(&buildopts, ast_strdup(ast_skip_blanks(data)), strcmp);
+}
+
+#pragma GCC diagnostic ignored "-Wformat-nonliteral"
+int ast_pjproject_get_buildopt(char *option, char *format_string, ...)
+{
+	int res = 0;
+	char *format_temp;
+	int i;
+
+	format_temp = ast_alloca(strlen(option) + strlen(" : ") + strlen(format_string) + 1);
+	sprintf(format_temp, "%s : %s", option, format_string);
+
+	for (i = 0; i < AST_VECTOR_SIZE(&buildopts); i++) {
+		va_list arg_ptr;
+		va_start(arg_ptr, format_string);
+		res = vsscanf(AST_VECTOR_GET(&buildopts, i), format_temp, arg_ptr);
+		va_end(arg_ptr);
+		if (res) {
+			break;
+		}
+	}
+
+	return res;
+}
+#pragma GCC diagnostic warning "-Wformat-nonliteral"
+
+void ast_pjproject_log_intercept_begin(int fd)
+{
+	/* Protect from other CLI instances trying to do this at the same time. */
+	ast_mutex_lock(&pjproject_log_intercept_lock);
+
+	pjproject_log_intercept.thread = pthread_self();
+	pjproject_log_intercept.fd = fd;
+}
+
+void ast_pjproject_log_intercept_end(void)
+{
+	pjproject_log_intercept.fd = -1;
+	pjproject_log_intercept.thread = AST_PTHREADT_NULL;
+
+	ast_mutex_unlock(&pjproject_log_intercept_lock);
+}
+
+void ast_pjproject_ref(void)
+{
+	ast_module_ref(ast_module_info->self);
+}
+
+void ast_pjproject_unref(void)
+{
+	ast_module_unref(ast_module_info->self);
+}
+
+static char *handle_pjproject_show_buildopts(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+	int i;
+
+	switch (cmd) {
+	case CLI_INIT:
+		e->command = "pjproject show buildopts";
+		e->usage =
+			"Usage: pjproject show buildopts\n"
+			"       Show the compile time config of the pjproject that Asterisk is\n"
+			"       running against.\n";
+		return NULL;
+	case CLI_GENERATE:
+		return NULL;
+	}
+
+	ast_cli(a->fd, "PJPROJECT compile time config currently running against:\n");
+
+	for (i = 0; i < AST_VECTOR_SIZE(&buildopts); i++) {
+		ast_cli(a->fd, "%s\n", AST_VECTOR_GET(&buildopts, i));
+	}
+
+	return CLI_SUCCESS;
+}
+
+static void mapping_destroy(void *object)
+{
+	struct log_mappings *mappings = object;
+
+	ast_string_field_free_memory(mappings);
+}
+
+static void *mapping_alloc(const char *name)
+{
+	struct log_mappings *mappings = ast_sorcery_generic_alloc(sizeof(*mappings), mapping_destroy);
+	if (!mappings) {
+		return NULL;
+	}
+	ast_string_field_init(mappings, 128);
+
+	return mappings;
+}
+
+static char *handle_pjproject_show_log_mappings(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+	struct ast_variable *objset;
+	struct ast_variable *i;
+	struct log_mappings *mappings;
+
+	switch (cmd) {
+	case CLI_INIT:
+		e->command = "pjproject show log mappings";
+		e->usage =
+			"Usage: pjproject show log mappings\n"
+			"       Show pjproject to Asterisk log mappings\n";
+		return NULL;
+	case CLI_GENERATE:
+		return NULL;
+	}
+
+	ast_cli(a->fd, "PJPROJECT to Asterisk log mappings:\n");
+	ast_cli(a->fd, "Asterisk Level   : PJPROJECT log levels\n");
+
+	mappings = get_log_mappings();
+	if (!mappings) {
+		ast_log(LOG_ERROR, "Unable to retrieve pjproject log_mappings\n");
+		return CLI_SUCCESS;
+	}
+
+	objset = ast_sorcery_objectset_create(pjproject_sorcery, mappings);
+	if (!objset) {
+		ao2_ref(mappings, -1);
+		return CLI_SUCCESS;
+	}
+
+	for (i = objset; i; i = i->next) {
+		ast_cli(a->fd, "%-16s : %s\n", i->name, i->value);
+	}
+	ast_variables_destroy(objset);
+
+	ao2_ref(mappings, -1);
+	return CLI_SUCCESS;
+}
+
+static struct ast_cli_entry pjproject_cli[] = {
+	AST_CLI_DEFINE(handle_pjproject_show_buildopts, "Show the compiled config of the pjproject in use"),
+	AST_CLI_DEFINE(handle_pjproject_show_log_mappings, "Show pjproject to Asterisk log mappings"),
+};
+
+static int load_module(void)
+{
+	ast_debug(3, "Starting PJPROJECT logging to Asterisk logger\n");
+
+	if (!(pjproject_sorcery = ast_sorcery_open())) {
+		ast_log(LOG_ERROR, "Failed to open SIP sorcery failed to open\n");
+		return AST_MODULE_LOAD_DECLINE;
+	}
+
+	ast_sorcery_apply_default(pjproject_sorcery, "log_mappings", "config", "pjproject.conf,criteria=type=log_mappings");
+	if (ast_sorcery_object_register(pjproject_sorcery, "log_mappings", mapping_alloc, NULL, NULL)) {
+		ast_log(LOG_WARNING, "Failed to register pjproject log_mappings object with sorcery\n");
+		ast_sorcery_unref(pjproject_sorcery);
+		pjproject_sorcery = NULL;
+		return AST_MODULE_LOAD_DECLINE;
+	}
+
+	ast_sorcery_object_field_register(pjproject_sorcery, "log_mappings", "type", "", OPT_NOOP_T, 0, 0);
+	ast_sorcery_object_field_register(pjproject_sorcery, "log_mappings", "asterisk_debug", "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct log_mappings, asterisk_debug));
+	ast_sorcery_object_field_register(pjproject_sorcery, "log_mappings", "asterisk_error", "",  OPT_STRINGFIELD_T, 0, STRFLDSET(struct log_mappings, asterisk_error));
+	ast_sorcery_object_field_register(pjproject_sorcery, "log_mappings", "asterisk_warning", "",  OPT_STRINGFIELD_T, 0, STRFLDSET(struct log_mappings, asterisk_warning));
+	ast_sorcery_object_field_register(pjproject_sorcery, "log_mappings", "asterisk_notice", "",  OPT_STRINGFIELD_T, 0, STRFLDSET(struct log_mappings, asterisk_notice));
+	ast_sorcery_object_field_register(pjproject_sorcery, "log_mappings", "asterisk_verbose", "",  OPT_STRINGFIELD_T, 0, STRFLDSET(struct log_mappings, asterisk_verbose));
+
+	default_log_mappings = ast_sorcery_alloc(pjproject_sorcery, "log_mappings", "log_mappings");
+	if (!default_log_mappings) {
+		ast_log(LOG_ERROR, "Unable to allocate memory for pjproject log_mappings\n");
+		return AST_MODULE_LOAD_DECLINE;
+	}
+	ast_string_field_set(default_log_mappings, asterisk_error, "0,1");
+	ast_string_field_set(default_log_mappings, asterisk_warning, "2");
+	ast_string_field_set(default_log_mappings, asterisk_debug, "3,4,5");
+
+	ast_sorcery_load(pjproject_sorcery);
+
+	pj_init();
+
+	decor_orig = pj_log_get_decor();
+	log_cb_orig = pj_log_get_log_func();
+
+	if (AST_VECTOR_INIT(&buildopts, 64)) {
+		return AST_MODULE_LOAD_DECLINE;
+	}
+
+	/*
+	 * On startup, we want to capture the dump once and store it.
+	 */
+	pj_log_set_log_func(capture_buildopts_cb);
+	pj_log_set_decor(0);
+	pj_dump_config();
+	pj_log_set_decor(PJ_LOG_HAS_SENDER | PJ_LOG_HAS_INDENT);
+	pj_log_set_log_func(log_forwarder);
+
+	ast_cli_register_multiple(pjproject_cli, ARRAY_LEN(pjproject_cli));
+
+	return AST_MODULE_LOAD_SUCCESS;
+}
+
+#define NOT_EQUALS(a, b) (a != b)
+
+static int unload_module(void)
+{
+	ast_cli_unregister_multiple(pjproject_cli, ARRAY_LEN(pjproject_cli));
+	pj_log_set_log_func(log_cb_orig);
+	pj_log_set_decor(decor_orig);
+
+	AST_VECTOR_REMOVE_CMP_UNORDERED(&buildopts, NULL, NOT_EQUALS, ast_free);
+	AST_VECTOR_FREE(&buildopts);
+
+	ast_debug(3, "Stopped PJPROJECT logging to Asterisk logger\n");
+
+	pj_shutdown();
+
+	ao2_cleanup(default_log_mappings);
+	default_log_mappings = NULL;
+
+	ast_sorcery_unref(pjproject_sorcery);
+
+	return 0;
+}
+
+static int reload_module(void)
+{
+	if (pjproject_sorcery) {
+		ast_sorcery_reload(pjproject_sorcery);
+	}
+
+	return AST_MODULE_LOAD_SUCCESS;
+}
+
+AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_GLOBAL_SYMBOLS | AST_MODFLAG_LOAD_ORDER, "PJPROJECT Log and Utility Support",
+	.support_level = AST_MODULE_SUPPORT_CORE,
+	.load = load_module,
+	.unload = unload_module,
+	.reload = reload_module,
+	.load_pri = AST_MODPRI_CHANNEL_DEPEND - 6,
+	);
diff --git a/res/res_pjproject.exports.in b/res/res_pjproject.exports.in
new file mode 100644
index 0000000..f1821a6
--- /dev/null
+++ b/res/res_pjproject.exports.in
@@ -0,0 +1,6 @@
+{
+	global:
+		LINKER_SYMBOL_PREFIXast_pjproject_*;
+	local:
+		*;
+};
diff --git a/res/res_pjsip.c b/res/res_pjsip.c
index 8e99c55..752491c 100644
--- a/res/res_pjsip.c
+++ b/res/res_pjsip.c
@@ -42,9 +42,11 @@
 #include "asterisk/res_pjsip_cli.h"
 #include "asterisk/test.h"
 #include "asterisk/res_pjsip_presence_xml.h"
+#include "asterisk/res_pjproject.h"
 
 /*** MODULEINFO
 	<depend>pjproject</depend>
+	<depend>res_pjproject</depend>
 	<depend>res_sorcery_config</depend>
 	<depend>res_sorcery_memory</depend>
 	<depend>res_sorcery_astdb</depend>
@@ -233,6 +235,14 @@
 					</para></note>
 					</description>
 				</configOption>
+				<configOption name="bind_rtp_to_media_address">
+					<synopsis>Bind the RTP instance to the media_address</synopsis>
+					<description><para>
+						If media_address is specified, this option causes the RTP instance to be bound to the
+						specified ip address which causes the packets to be sent from that address.
+					</para>
+					</description>
+				</configOption>
 				<configOption name="force_rport" default="yes">
 					<synopsis>Force use of return port</synopsis>
 				</configOption>
@@ -1016,6 +1026,14 @@
 						Value is in milliseconds; default is 100 ms.</para>
 					</description>
 				</configOption>
+				<configOption name="allow_reload" default="no">
+					<synopsis>Allow this transport to be reloaded.</synopsis>
+					<description>
+						<para>Allow this transport to be reloaded when res_pjsip is reloaded.
+						This option defaults to "no" because reloading a transport may disrupt
+						in-progress calls.</para>
+					</description>
+				</configOption>
 			</configObject>
 			<configObject name="contact">
 				<synopsis>A way of creating an aliased name to a SIP URI</synopsis>
@@ -1271,6 +1289,10 @@
 				<configOption name="user_agent" default="Asterisk <Asterisk Version>">
 					<synopsis>Value used in User-Agent header for SIP requests and Server header for SIP responses.</synopsis>
 				</configOption>
+				<configOption name="regcontext" default="">
+                                        <synopsis>When set, Asterisk will dynamically create and destroy a NoOp priority 1 extension for a given
+					peer who registers or unregisters with us.</synopsis>
+                                </configOption>
 				<configOption name="default_outbound_endpoint" default="default_outbound_endpoint">
 					<synopsis>Endpoint to use when sending an outbound request to a URI without a specified endpoint.</synopsis>
 				</configOption>
@@ -2208,6 +2230,57 @@ struct ast_sip_endpoint *ast_sip_identify_endpoint(pjsip_rx_data *rdata)
 	return endpoint;
 }
 
+static int do_cli_dump_endpt(void *v_a)
+{
+	struct ast_cli_args *a = v_a;
+
+	ast_pjproject_log_intercept_begin(a->fd);
+	pjsip_endpt_dump(ast_sip_get_pjsip_endpoint(), a->argc == 4 ? PJ_TRUE : PJ_FALSE);
+	ast_pjproject_log_intercept_end();
+
+	return 0;
+}
+
+static char *cli_dump_endpt(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+	switch (cmd) {
+	case CLI_INIT:
+#ifdef AST_DEVMODE
+		e->command = "pjsip dump endpt [details]";
+		e->usage =
+			"Usage: pjsip dump endpt [details]\n"
+			"       Dump the res_pjsip endpt internals.\n"
+			"\n"
+			"Warning: PJPROJECT documents that the function used by this\n"
+			"CLI command may cause a crash when asking for details because\n"
+			"it tries to access all active memory pools.\n";
+#else
+		/*
+		 * In non-developer mode we will not document or make easily accessible
+		 * the details option even though it is still available.  The user has
+		 * to know it exists to use it.  Presumably they would also be aware of
+		 * the potential crash warning.
+		 */
+		e->command = "pjsip dump endpt";
+		e->usage =
+			"Usage: pjsip dump endpt\n"
+			"       Dump the res_pjsip endpt internals.\n";
+#endif /* AST_DEVMODE */
+		return NULL;
+	case CLI_GENERATE:
+		return NULL;
+	}
+
+	if (4 < a->argc
+		|| (a->argc == 4 && strcasecmp(a->argv[3], "details"))) {
+		return CLI_SHOWUSAGE;
+	}
+
+	ast_sip_push_task_synchronous(NULL, do_cli_dump_endpt, a);
+
+	return CLI_SUCCESS;
+}
+
 static char *cli_show_endpoint_identifiers(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
 {
 #define ENDPOINT_IDENTIFIER_FORMAT "%-20.20s\n"
@@ -2271,8 +2344,9 @@ static char *cli_show_settings(struct ast_cli_entry *e, int cmd, struct ast_cli_
 }
 
 static struct ast_cli_entry cli_commands[] = {
-        AST_CLI_DEFINE(cli_show_settings, "Show global and system configuration options"),
-        AST_CLI_DEFINE(cli_show_endpoint_identifiers, "List registered endpoint identifiers")
+	AST_CLI_DEFINE(cli_dump_endpt, "Dump the res_pjsip endpt internals"),
+	AST_CLI_DEFINE(cli_show_settings, "Show global and system configuration options"),
+	AST_CLI_DEFINE(cli_show_endpoint_identifiers, "List registered endpoint identifiers")
 };
 
 AST_RWLIST_HEAD_STATIC(endpoint_formatters, ast_sip_endpoint_formatter);
@@ -2417,29 +2491,23 @@ static int sip_dialog_create_from(pj_pool_t *pool, pj_str_t *from, const char *u
 	return 0;
 }
 
-static int sip_get_tpselector_from_endpoint(const struct ast_sip_endpoint *endpoint, pjsip_tpselector *selector)
+int ast_sip_set_tpselector_from_transport(const struct ast_sip_transport *transport, pjsip_tpselector *selector)
 {
-	RAII_VAR(struct ast_sip_transport *, transport, NULL, ao2_cleanup);
-	const char *transport_name = endpoint->transport;
-
-	if (ast_strlen_zero(transport_name)) {
-		return 0;
-	}
-
-	transport = ast_sorcery_retrieve_by_id(ast_sip_get_sorcery(), "transport", transport_name);
+	RAII_VAR(struct ast_sip_transport_state *, transport_state, NULL, ao2_cleanup);
 
-	if (!transport || !transport->state) {
-		ast_log(LOG_ERROR, "Unable to retrieve PJSIP transport '%s' for endpoint '%s'\n",
-			transport_name, ast_sorcery_object_get_id(endpoint));
+	transport_state = ast_sip_get_transport_state(ast_sorcery_object_get_id(transport));
+	if (!transport_state) {
+		ast_log(LOG_ERROR, "Unable to retrieve PJSIP transport state for '%s'\n",
+			ast_sorcery_object_get_id(transport));
 		return -1;
 	}
 
-	if (transport->state->transport) {
+	if (transport_state->transport) {
 		selector->type = PJSIP_TPSELECTOR_TRANSPORT;
-		selector->u.transport = transport->state->transport;
-	} else if (transport->state->factory) {
+		selector->u.transport = transport_state->transport;
+	} else if (transport_state->factory) {
 		selector->type = PJSIP_TPSELECTOR_LISTENER;
-		selector->u.listener = transport->state->factory;
+		selector->u.listener = transport_state->factory;
 	} else if (transport->type == AST_TRANSPORT_WS || transport->type == AST_TRANSPORT_WSS) {
 		/* The WebSocket transport has no factory as it can not create outgoing connections, so
 		 * even if an endpoint is locked to a WebSocket transport we let the PJSIP logic
@@ -2453,6 +2521,35 @@ static int sip_get_tpselector_from_endpoint(const struct ast_sip_endpoint *endpo
 	return 0;
 }
 
+int ast_sip_set_tpselector_from_transport_name(const char *transport_name, pjsip_tpselector *selector)
+{
+	RAII_VAR(struct ast_sip_transport *, transport, NULL, ao2_cleanup);
+
+	if (ast_strlen_zero(transport_name)) {
+		return 0;
+	}
+
+	transport = ast_sorcery_retrieve_by_id(ast_sip_get_sorcery(), "transport", transport_name);
+	if (!transport) {
+		ast_log(LOG_ERROR, "Unable to retrieve PJSIP transport '%s'\n",
+			transport_name);
+		return -1;
+	}
+
+	return ast_sip_set_tpselector_from_transport(transport, selector);
+}
+
+static int sip_get_tpselector_from_endpoint(const struct ast_sip_endpoint *endpoint, pjsip_tpselector *selector)
+{
+	const char *transport_name = endpoint->transport;
+
+	if (ast_strlen_zero(transport_name)) {
+		return 0;
+	}
+
+	return ast_sip_set_tpselector_from_transport_name(endpoint->transport, selector);
+}
+
 void ast_sip_add_usereqphone(const struct ast_sip_endpoint *endpoint, pj_pool_t *pool, pjsip_uri *uri)
 {
 	pjsip_sip_uri *sip_uri;
@@ -2647,7 +2744,11 @@ pjsip_dialog *ast_sip_create_dialog_uas(const struct ast_sip_endpoint *endpoint,
 			(type != PJSIP_TRANSPORT_UDP && type != PJSIP_TRANSPORT_UDP6) ? ";transport=" : "",
 			(type != PJSIP_TRANSPORT_UDP && type != PJSIP_TRANSPORT_UDP6) ? pjsip_transport_get_type_name(type) : "");
 
+#ifdef HAVE_PJSIP_DLG_CREATE_UAS_AND_INC_LOCK
+	*status = pjsip_dlg_create_uas_and_inc_lock(pjsip_ua_instance(), rdata, &contact, &dlg);
+#else
 	*status = pjsip_dlg_create_uas(pjsip_ua_instance(), rdata, &contact, &dlg);
+#endif
 	if (*status != PJ_SUCCESS) {
 		char err[PJ_ERR_MSG_SIZE];
 
@@ -2660,6 +2761,9 @@ pjsip_dialog *ast_sip_create_dialog_uas(const struct ast_sip_endpoint *endpoint,
 	dlg->sess_count++;
 	pjsip_dlg_set_transport(dlg, &selector);
 	dlg->sess_count--;
+#ifdef HAVE_PJSIP_DLG_CREATE_UAS_AND_INC_LOCK
+	pjsip_dlg_dec_lock(dlg);
+#endif
 
 	return dlg;
 }
@@ -3379,23 +3483,34 @@ int ast_sip_append_body(pjsip_tx_data *tdata, const char *body_text)
 	return 0;
 }
 
+struct ast_taskprocessor *ast_sip_create_serializer_group_named(const char *name, struct ast_serializer_shutdown_group *shutdown_group)
+{
+	return ast_threadpool_serializer_group(name, sip_threadpool, shutdown_group);
+}
+
 struct ast_taskprocessor *ast_sip_create_serializer_group(struct ast_serializer_shutdown_group *shutdown_group)
 {
-	struct ast_taskprocessor *serializer;
-	char name[AST_UUID_STR_LEN];
+	char tps_name[AST_TASKPROCESSOR_MAX_NAME + 1];
 
-	ast_uuid_generate_str(name, sizeof(name));
+	/* Create name with seq number appended. */
+	ast_taskprocessor_build_name(tps_name, sizeof(tps_name), "pjsip-group-serializer");
 
-	serializer = ast_threadpool_serializer_group(name, sip_threadpool, shutdown_group);
-	if (!serializer) {
-		return NULL;
-	}
-	return serializer;
+	return ast_sip_create_serializer_group_named(tps_name, shutdown_group);
+}
+
+struct ast_taskprocessor *ast_sip_create_serializer_named(const char *name)
+{
+	return ast_sip_create_serializer_group_named(name, NULL);
 }
 
 struct ast_taskprocessor *ast_sip_create_serializer(void)
 {
-	return ast_sip_create_serializer_group(NULL);
+	char tps_name[AST_TASKPROCESSOR_MAX_NAME + 1];
+
+	/* Create name with seq number appended. */
+	ast_taskprocessor_build_name(tps_name, sizeof(tps_name), "pjsip-serializer");
+
+	return ast_sip_create_serializer_group_named(tps_name, NULL);
 }
 
 /*!
@@ -3425,10 +3540,14 @@ static void serializer_pool_shutdown(void)
  */
 static int serializer_pool_setup(void)
 {
+	char tps_name[AST_TASKPROCESSOR_MAX_NAME + 1];
 	int idx;
 
 	for (idx = 0; idx < SERIALIZER_POOL_SIZE; ++idx) {
-		serializer_pool[idx] = ast_sip_create_serializer();
+		/* Create name with seq number appended. */
+		ast_taskprocessor_build_name(tps_name, sizeof(tps_name), "pjsip/default");
+
+		serializer_pool[idx] = ast_sip_create_serializer_named(tps_name);
 		if (!serializer_pool[idx]) {
 			serializer_pool_shutdown();
 			return -1;
@@ -3745,6 +3864,35 @@ const char *ast_sip_get_host_ip_string(int af)
 	return NULL;
 }
 
+/*!
+ * \brief Set name and number information on an identity header.
+ *
+ * \param pool Memory pool to use for string duplication
+ * \param id_hdr A From, P-Asserted-Identity, or Remote-Party-ID header to modify
+ * \param id The identity information to apply to the header
+ */
+void ast_sip_modify_id_header(pj_pool_t *pool, pjsip_fromto_hdr *id_hdr, const struct ast_party_id *id)
+{
+	pjsip_name_addr *id_name_addr;
+	pjsip_sip_uri *id_uri;
+
+	id_name_addr = (pjsip_name_addr *) id_hdr->uri;
+	id_uri = pjsip_uri_get_uri(id_name_addr->uri);
+
+	if (id->name.valid) {
+		int name_buf_len = strlen(id->name.str) * 2 + 1;
+		char *name_buf = ast_alloca(name_buf_len);
+
+		ast_escape_quoted(id->name.str, name_buf, name_buf_len);
+		pj_strdup2(pool, &id_name_addr->display, name_buf);
+	}
+
+	if (id->number.valid) {
+		pj_strdup2(pool, &id_uri->user, id->number.str);
+	}
+}
+
+
 static void remove_request_headers(pjsip_endpoint *endpt)
 {
 	const pjsip_hdr *request_headers = pjsip_endpt_get_request_headers(endpt);
@@ -3825,29 +3973,55 @@ static int reload_configuration_task(void *obj)
 	return 0;
 }
 
-static int load_module(void)
+static int unload_pjsip(void *data)
 {
-	/* The third parameter is just copied from
-	 * example code from PJLIB. This can be adjusted
-	 * if necessary.
+	/*
+	 * These calls need the pjsip endpoint and serializer to clean up.
+	 * If they're not set, then there's nothing to clean up anyway.
 	 */
-	pj_status_t status;
-	struct ast_threadpool_options options;
+	if (ast_pjsip_endpoint && serializer_pool[0]) {
+		ast_res_pjsip_cleanup_options_handling();
+		internal_sip_destroy_outbound_authentication();
+		ast_sip_destroy_distributor();
+		ast_res_pjsip_destroy_configuration();
+		ast_sip_destroy_system();
+		ast_sip_destroy_global_headers();
+		internal_sip_unregister_service(&supplement_module);
+	}
 
-	if (pj_init() != PJ_SUCCESS) {
-		return AST_MODULE_LOAD_DECLINE;
+	if (monitor_thread) {
+		stop_monitor_thread();
+		monitor_thread = NULL;
 	}
 
-	if (pjlib_util_init() != PJ_SUCCESS) {
-		pj_shutdown();
-		return AST_MODULE_LOAD_DECLINE;
+	if (memory_pool) {
+		pj_pool_release(memory_pool);
+		memory_pool = NULL;
+	}
+
+	ast_pjsip_endpoint = NULL;
+
+	if (caching_pool.lock) {
+		pj_caching_pool_destroy(&caching_pool);
 	}
 
+	pj_shutdown();
+
+	return 0;
+}
+
+static int load_pjsip(void)
+{
+	pj_status_t status;
+
+	/* The third parameter is just copied from
+	 * example code from PJLIB. This can be adjusted
+	 * if necessary.
+	 */
 	pj_caching_pool_init(&caching_pool, NULL, 1024 * 1024);
 	if (pjsip_endpt_create(&caching_pool.factory, "SIP", &ast_pjsip_endpoint) != PJ_SUCCESS) {
 		ast_log(LOG_ERROR, "Failed to create PJSIP endpoint structure. Aborting load\n");
-		pj_caching_pool_destroy(&caching_pool);
-		return AST_MODULE_LOAD_DECLINE;
+		goto error;
 	}
 
 	/* PJSIP will automatically try to add a Max-Forwards header. Since we want to control that,
@@ -3858,10 +4032,7 @@ static int load_module(void)
 	memory_pool = pj_pool_create(&caching_pool.factory, "SIP", 1024, 1024, NULL);
 	if (!memory_pool) {
 		ast_log(LOG_ERROR, "Failed to create memory pool for SIP. Aborting load\n");
-		pjsip_endpt_destroy(ast_pjsip_endpoint);
-		ast_pjsip_endpoint = NULL;
-		pj_caching_pool_destroy(&caching_pool);
-		return AST_MODULE_LOAD_DECLINE;
+		goto error;
 	}
 
 	if (!pj_gethostip(pj_AF_INET(), &host_ip_ipv4)) {
@@ -3874,119 +4045,84 @@ static int load_module(void)
 		ast_verb(3, "Local IPv6 address determined to be: %s\n", host_ip_ipv6_string);
 	}
 
+	pjsip_tsx_layer_init_module(ast_pjsip_endpoint);
+	pjsip_ua_init_module(ast_pjsip_endpoint, NULL);
+
+	monitor_continue = 1;
+	status = pj_thread_create(memory_pool, "SIP", (pj_thread_proc *) &monitor_thread_exec,
+			NULL, PJ_THREAD_DEFAULT_STACK_SIZE * 2, 0, &monitor_thread);
+	if (status != PJ_SUCCESS) {
+		ast_log(LOG_ERROR, "Failed to start SIP monitor thread. Aborting load\n");
+		goto error;
+	}
+
+	return AST_MODULE_LOAD_SUCCESS;
+
+error:
+	unload_pjsip(NULL);
+	return AST_MODULE_LOAD_DECLINE;
+}
+
+static int load_module(void)
+{
+	struct ast_threadpool_options options;
+
+	CHECK_PJPROJECT_MODULE_LOADED();
+
+	/* pjproject and config_system need to be initialized before all else */
+	if (pj_init() != PJ_SUCCESS) {
+		return AST_MODULE_LOAD_DECLINE;
+	}
+
+	if (pjlib_util_init() != PJ_SUCCESS) {
+		goto error;
+	}
+
 	if (ast_sip_initialize_system()) {
 		ast_log(LOG_ERROR, "Failed to initialize SIP 'system' configuration section. Aborting load\n");
-		pj_pool_release(memory_pool);
-		memory_pool = NULL;
-		pjsip_endpt_destroy(ast_pjsip_endpoint);
-		ast_pjsip_endpoint = NULL;
-		pj_caching_pool_destroy(&caching_pool);
-		return AST_MODULE_LOAD_DECLINE;
+		goto error;
 	}
 
+	/* The serializer needs threadpool and threadpool needs pjproject to be initialized so it's next */
 	sip_get_threadpool_options(&options);
 	options.thread_start = sip_thread_start;
 	sip_threadpool = ast_threadpool_create("SIP", NULL, &options);
 	if (!sip_threadpool) {
-		ast_log(LOG_ERROR, "Failed to create SIP threadpool. Aborting load\n");
-		ast_sip_destroy_system();
-		pj_pool_release(memory_pool);
-		memory_pool = NULL;
-		pjsip_endpt_destroy(ast_pjsip_endpoint);
-		ast_pjsip_endpoint = NULL;
-		pj_caching_pool_destroy(&caching_pool);
-		return AST_MODULE_LOAD_DECLINE;
+		goto error;
 	}
 
 	if (serializer_pool_setup()) {
 		ast_log(LOG_ERROR, "Failed to create SIP serializer pool. Aborting load\n");
-		ast_threadpool_shutdown(sip_threadpool);
-		ast_sip_destroy_system();
-		pj_pool_release(memory_pool);
-		memory_pool = NULL;
-		pjsip_endpt_destroy(ast_pjsip_endpoint);
-		ast_pjsip_endpoint = NULL;
-		pj_caching_pool_destroy(&caching_pool);
-		return AST_MODULE_LOAD_DECLINE;
+		goto error;
 	}
 
-	ast_sip_initialize_dns();
-
-	pjsip_tsx_layer_init_module(ast_pjsip_endpoint);
-	pjsip_ua_init_module(ast_pjsip_endpoint, NULL);
-
-	monitor_continue = 1;
-	status = pj_thread_create(memory_pool, "SIP", (pj_thread_proc *) &monitor_thread_exec,
-			NULL, PJ_THREAD_DEFAULT_STACK_SIZE * 2, 0, &monitor_thread);
-	if (status != PJ_SUCCESS) {
-		ast_log(LOG_ERROR, "Failed to start SIP monitor thread. Aborting load\n");
-		ast_sip_destroy_system();
-		pj_pool_release(memory_pool);
-		memory_pool = NULL;
-		pjsip_endpt_destroy(ast_pjsip_endpoint);
-		ast_pjsip_endpoint = NULL;
-		pj_caching_pool_destroy(&caching_pool);
-		return AST_MODULE_LOAD_DECLINE;
+	/* Now load all the pjproject infrastructure. */
+	if (load_pjsip()) {
+		goto error;
 	}
 
+	ast_sip_initialize_dns();
+
 	ast_sip_initialize_global_headers();
 
 	if (ast_res_pjsip_initialize_configuration(ast_module_info)) {
 		ast_log(LOG_ERROR, "Failed to initialize SIP configuration. Aborting load\n");
-		ast_sip_destroy_global_headers();
-		stop_monitor_thread();
-		ast_sip_destroy_system();
-		pj_pool_release(memory_pool);
-		memory_pool = NULL;
-		pjsip_endpt_destroy(ast_pjsip_endpoint);
-		ast_pjsip_endpoint = NULL;
-		pj_caching_pool_destroy(&caching_pool);
-		return AST_MODULE_LOAD_DECLINE;
+		goto error;
 	}
 
 	if (ast_sip_initialize_distributor()) {
 		ast_log(LOG_ERROR, "Failed to register distributor module. Aborting load\n");
-		ast_res_pjsip_destroy_configuration();
-		ast_sip_destroy_global_headers();
-		stop_monitor_thread();
-		ast_sip_destroy_system();
-		pj_pool_release(memory_pool);
-		memory_pool = NULL;
-		pjsip_endpt_destroy(ast_pjsip_endpoint);
-		ast_pjsip_endpoint = NULL;
-		pj_caching_pool_destroy(&caching_pool);
-		return AST_MODULE_LOAD_DECLINE;
+		goto error;
 	}
 
 	if (internal_sip_register_service(&supplement_module)) {
 		ast_log(LOG_ERROR, "Failed to initialize supplement hooks. Aborting load\n");
-		ast_sip_destroy_distributor();
-		ast_res_pjsip_destroy_configuration();
-		ast_sip_destroy_global_headers();
-		stop_monitor_thread();
-		ast_sip_destroy_system();
-		pj_pool_release(memory_pool);
-		memory_pool = NULL;
-		pjsip_endpt_destroy(ast_pjsip_endpoint);
-		ast_pjsip_endpoint = NULL;
-		pj_caching_pool_destroy(&caching_pool);
-		return AST_MODULE_LOAD_DECLINE;
+		goto error;
 	}
 
 	if (internal_sip_initialize_outbound_authentication()) {
 		ast_log(LOG_ERROR, "Failed to initialize outbound authentication. Aborting load\n");
-		internal_sip_unregister_service(&supplement_module);
-		ast_sip_destroy_distributor();
-		ast_res_pjsip_destroy_configuration();
-		ast_sip_destroy_global_headers();
-		stop_monitor_thread();
-		ast_sip_destroy_system();
-		pj_pool_release(memory_pool);
-		memory_pool = NULL;
-		pjsip_endpt_destroy(ast_pjsip_endpoint);
-		ast_pjsip_endpoint = NULL;
-		pj_caching_pool_destroy(&caching_pool);
-		return AST_MODULE_LOAD_DECLINE;
+		goto error;
 	}
 
 	ast_res_pjsip_init_options_handling(0);
@@ -3995,7 +4131,17 @@ static int load_module(void)
 	AST_TEST_REGISTER(xml_sanitization_end_null);
 	AST_TEST_REGISTER(xml_sanitization_exceeds_buffer);
 
+	ast_pjproject_ref();
+
 	return AST_MODULE_LOAD_SUCCESS;
+
+error:
+	/* These functions all check for NULLs and are safe to call at any time */
+	unload_pjsip(NULL);
+	serializer_pool_shutdown();
+	ast_threadpool_shutdown(sip_threadpool);
+
+	return AST_MODULE_LOAD_DECLINE;
 }
 
 static int reload_module(void)
@@ -4012,33 +4158,11 @@ static int reload_module(void)
 	return 0;
 }
 
-static int unload_pjsip(void *data)
-{
-	ast_cli_unregister_multiple(cli_commands, ARRAY_LEN(cli_commands));
-	ast_res_pjsip_cleanup_options_handling();
-	internal_sip_destroy_outbound_authentication();
-	ast_sip_destroy_distributor();
-	ast_res_pjsip_destroy_configuration();
-	ast_sip_destroy_system();
-	ast_sip_destroy_global_headers();
-	internal_sip_unregister_service(&supplement_module);
-	if (monitor_thread) {
-		stop_monitor_thread();
-	}
-	if (memory_pool) {
-		pj_pool_release(memory_pool);
-		memory_pool = NULL;
-	}
-	ast_pjsip_endpoint = NULL;
-	pj_caching_pool_destroy(&caching_pool);
-	pj_shutdown();
-	return 0;
-}
-
 static int unload_module(void)
 {
 	AST_TEST_UNREGISTER(xml_sanitization_end_null);
 	AST_TEST_UNREGISTER(xml_sanitization_exceeds_buffer);
+	ast_cli_unregister_multiple(cli_commands, ARRAY_LEN(cli_commands));
 
 	/* The thread this is called from cannot call PJSIP/PJLIB functions,
 	 * so we have to push the work to the threadpool to handle
@@ -4048,7 +4172,8 @@ static int unload_module(void)
 	serializer_pool_shutdown();
 	ast_threadpool_shutdown(sip_threadpool);
 
-	ast_sip_destroy_cli();
+	ast_pjproject_unref();
+
 	return 0;
 }
 
diff --git a/res/res_pjsip/config_global.c b/res/res_pjsip/config_global.c
index ef706f0..3d88ffc 100644
--- a/res/res_pjsip/config_global.c
+++ b/res/res_pjsip/config_global.c
@@ -35,6 +35,7 @@
 #define DEFAULT_ENDPOINT_IDENTIFIER_ORDER "ip,username,anonymous"
 #define DEFAULT_MAX_INITIAL_QUALIFY_TIME 0
 #define DEFAULT_FROM_USER "asterisk"
+#define DEFAULT_REGCONTEXT ""
 
 static char default_useragent[256];
 
@@ -42,6 +43,7 @@ struct global_config {
 	SORCERY_OBJECT(details);
 	AST_DECLARE_STRING_FIELDS(
 		AST_STRING_FIELD(useragent);
+		AST_STRING_FIELD(regcontext);
 		AST_STRING_FIELD(default_outbound_endpoint);
 		/*! Debug logging yes|no|host */
 		AST_STRING_FIELD(debug);
@@ -137,6 +139,23 @@ char *ast_sip_get_debug(void)
 	return res;
 }
 
+char *ast_sip_get_regcontext(void)
+{
+        char *res;
+        struct global_config *cfg;
+
+        cfg = get_global_cfg();
+        if (!cfg) {
+                return ast_strdup(DEFAULT_REGCONTEXT);
+        }
+
+        res = ast_strdup(cfg->regcontext);
+        ao2_ref(cfg, -1);
+
+        return res;
+}
+
+
 char *ast_sip_get_endpoint_identifier_order(void)
 {
 	char *res;
@@ -310,6 +329,9 @@ int ast_sip_initialize_sorcery_global(void)
 		OPT_UINT_T, 0, FLDSET(struct global_config, max_initial_qualify_time));
 	ast_sorcery_object_field_register(sorcery, "global", "default_from_user", DEFAULT_FROM_USER,
 		OPT_STRINGFIELD_T, 0, STRFLDSET(struct global_config, default_from_user));
+	ast_sorcery_object_field_register(sorcery, "global", "regcontext", DEFAULT_REGCONTEXT,
+                OPT_STRINGFIELD_T, 0, STRFLDSET(struct global_config, regcontext));
+
 
 	if (ast_sorcery_instance_observer_add(sorcery, &observer_callbacks_global)) {
 		return -1;
diff --git a/res/res_pjsip/config_transport.c b/res/res_pjsip/config_transport.c
index 840824b..d2c0874 100644
--- a/res/res_pjsip/config_transport.c
+++ b/res/res_pjsip/config_transport.c
@@ -18,6 +18,7 @@
 
 #include "asterisk.h"
 
+#include <math.h>
 #include <pjsip.h>
 #include <pjlib.h>
 
@@ -31,6 +32,132 @@
 #include "include/res_pjsip_private.h"
 #include "asterisk/http_websocket.h"
 
+#define MAX_POINTER_STRING 33
+
+/*! \brief Default number of state container buckets */
+#define DEFAULT_STATE_BUCKETS 53
+static struct ao2_container *transport_states;
+
+struct internal_state {
+	char *id;
+	/*! Set if there was a change detected */
+	int change_detected;
+	/*! \brief Transport configuration object */
+	struct ast_sip_transport *transport;
+	/*! \brief Transport state information */
+	struct ast_sip_transport_state *state;
+};
+
+static void temp_state_store_cleanup(void *data)
+{
+	struct ast_sip_transport_state **temp_state = data;
+
+	ao2_cleanup(*temp_state);
+	ast_free(data);
+}
+
+AST_THREADSTORAGE_CUSTOM(temp_state_store, NULL, temp_state_store_cleanup);
+
+/*! \brief hashing function for state objects */
+static int internal_state_hash(const void *obj, const int flags)
+{
+	const struct internal_state *object;
+	const char *key;
+
+	switch (flags & OBJ_SEARCH_MASK) {
+	case OBJ_SEARCH_KEY:
+		key = obj;
+		break;
+	case OBJ_SEARCH_OBJECT:
+		object = obj;
+		key = object->id;
+		break;
+	default:
+		ast_assert(0);
+		return 0;
+	}
+	return ast_str_hash(key);
+}
+
+/*! \brief comparator function for state objects */
+static int internal_state_cmp(void *obj, void *arg, int flags)
+{
+	const struct internal_state *object_left = obj;
+	const struct internal_state *object_right = arg;
+	const char *right_key = arg;
+	int cmp;
+
+	switch (flags & OBJ_SEARCH_MASK) {
+	case OBJ_SEARCH_OBJECT:
+		right_key = object_right->id;
+		/* Fall through */
+	case OBJ_SEARCH_KEY:
+		cmp = strcmp(object_left->id, right_key);
+		break;
+	case OBJ_SEARCH_PARTIAL_KEY:
+		/* Not supported by container. */
+		ast_assert(0);
+		return 0;
+	default:
+		cmp = 0;
+		break;
+	}
+	if (cmp) {
+		return 0;
+	}
+	return CMP_MATCH;
+}
+
+/*! \brief hashing function for state objects */
+static int transport_state_hash(const void *obj, const int flags)
+{
+	const struct ast_sip_transport_state *object;
+	const char *key;
+
+	switch (flags & OBJ_SEARCH_MASK) {
+	case OBJ_SEARCH_KEY:
+		key = obj;
+		break;
+	case OBJ_SEARCH_OBJECT:
+		object = obj;
+		key = object->id;
+		break;
+	default:
+		ast_assert(0);
+		return 0;
+	}
+	return ast_str_hash(key);
+}
+
+/*! \brief comparator function for state objects */
+static int transport_state_cmp(void *obj, void *arg, int flags)
+{
+	const struct ast_sip_transport_state *object_left = obj;
+	const struct ast_sip_transport_state *object_right = arg;
+	const char *right_key = arg;
+	int cmp;
+
+	switch (flags & OBJ_SEARCH_MASK) {
+	case OBJ_SEARCH_OBJECT:
+		right_key = object_right->id;
+		/* Fall through */
+	case OBJ_SEARCH_KEY:
+		cmp = strcmp(object_left->id, right_key);
+		break;
+	case OBJ_SEARCH_PARTIAL_KEY:
+		/* Not supported by container. */
+		ast_assert(0);
+		return 0;
+	default:
+		cmp = 0;
+		break;
+	}
+	if (cmp) {
+		return 0;
+	}
+	return CMP_MATCH;
+}
+
 static int sip_transport_to_ami(const struct ast_sip_transport *transport,
 				struct ast_str **buf)
 {
@@ -75,69 +202,246 @@ struct ast_sip_endpoint_formatter endpoint_transport_formatter = {
 	.format_ami = format_ami_endpoint_transport
 };
 
-static int destroy_transport_state(void *data)
+static void set_qos(struct ast_sip_transport *transport, pj_qos_params *qos)
+{
+	int tos_as_dscp = transport->tos >> 2;
+
+	if (transport->tos) {
+		qos->flags |= PJ_QOS_PARAM_HAS_DSCP;
+		qos->dscp_val = tos_as_dscp;
+	}
+	if (transport->cos) {
+		qos->flags |= PJ_QOS_PARAM_HAS_SO_PRIO;
+		qos->so_prio = transport->cos;
+	}
+}
+
+/*! \brief Destructor for transport */
+static void sip_transport_destroy(void *obj)
+{
+	struct ast_sip_transport *transport = obj;
+
+	ast_string_field_free_memory(transport);
+}
+
+/*! \brief Allocator for transport */
+static void *sip_transport_alloc(const char *name)
 {
-	pjsip_transport *transport = data;
-	pjsip_transport_shutdown(transport);
+	struct ast_sip_transport *transport = ast_sorcery_generic_alloc(sizeof(*transport), sip_transport_destroy);
+
+	if (!transport) {
+		return NULL;
+	}
+
+	if (ast_string_field_init(transport, 256)) {
+		ao2_cleanup(transport);
+		return NULL;
+	}
+
+	return transport;
+}
+
+static int destroy_sip_transport_state(void *data)
+{
+	struct ast_sip_transport_state *transport_state = data;
+
+	ast_free(transport_state->id);
+	ast_free_ha(transport_state->localnet);
+
+	if (transport_state->external_address_refresher) {
+		ast_dnsmgr_release(transport_state->external_address_refresher);
+	}
+	if (transport_state->transport) {
+		pjsip_transport_shutdown(transport_state->transport);
+	}
+
 	return 0;
 }
 
-/*! \brief Destructor for transport state information */
-static void transport_state_destroy(void *obj)
+/*! \brief Destructor for ast_sip_transport state information */
+static void sip_transport_state_destroy(void *obj)
 {
 	struct ast_sip_transport_state *state = obj;
 
-	if (state->transport) {
-		ast_sip_push_task_synchronous(NULL, destroy_transport_state, state->transport);
+	ast_sip_push_task_synchronous(NULL, destroy_sip_transport_state, state);
+}
+
+/*! \brief Destructor for ast_sip_transport state information */
+static void internal_state_destroy(void *obj)
+{
+	struct internal_state *state = obj;
+
+	ast_free(state->id);
+	ao2_cleanup(state->transport);
+	ao2_cleanup(state->state);
+}
+
+static struct internal_state *find_internal_state_by_transport(const struct ast_sip_transport *transport)
+{
+	const char *key = ast_sorcery_object_get_id(transport);
+
+	return ao2_find(transport_states, key, OBJ_SEARCH_KEY | OBJ_NOLOCK);
+}
+
+static struct ast_sip_transport_state *find_state_by_transport(const struct ast_sip_transport *transport)
+{
+	struct internal_state *state;
+
+	state = find_internal_state_by_transport(transport);
+	if (!state) {
+		return NULL;
 	}
+	ao2_bump(state->state);
+	ao2_cleanup(state);
+
+	return state->state;
 }
 
-/*! \brief Destructor for transport */
-static void transport_destroy(void *obj)
+static int remove_temporary_state(void)
 {
-	struct ast_sip_transport *transport = obj;
+	struct ast_sip_transport_state **state;
 
-	ast_string_field_free_memory(transport);
-	ast_free_ha(transport->localnet);
+	state = ast_threadstorage_get(&temp_state_store, sizeof(state));
+	if (!state) {
+		return -1;
+	}
 
-	if (transport->external_address_refresher) {
-		ast_dnsmgr_release(transport->external_address_refresher);
+	ao2_cleanup(*state);
+	*state = NULL;
+	return 0;
+}
+
+static struct ast_sip_transport_state *find_temporary_state(struct ast_sip_transport *transport)
+{
+	struct ast_sip_transport_state **state;
+
+	state = ast_threadstorage_get(&temp_state_store, sizeof(state));
+	if (state && *state) {
+		ao2_ref(*state, +1);
+		return *state;
 	}
 
-	ao2_cleanup(transport->state);
+	return NULL;
 }
 
-/*! \brief Allocator for transport */
-static void *transport_alloc(const char *name)
+static struct internal_state *internal_state_alloc(struct ast_sip_transport *transport)
 {
-	struct ast_sip_transport *transport = ast_sorcery_generic_alloc(sizeof(*transport), transport_destroy);
+	struct internal_state *internal_state;
 
-	if (!transport) {
+	internal_state = ao2_alloc(sizeof(*internal_state), internal_state_destroy);
+	if (!internal_state) {
 		return NULL;
 	}
 
-	if (ast_string_field_init(transport, 256)) {
-		ao2_cleanup(transport);
+	internal_state->id = ast_strdup(ast_sorcery_object_get_id(transport));
+	if (!internal_state->id) {
+		ao2_cleanup(internal_state);
 		return NULL;
 	}
 
-	pjsip_tls_setting_default(&transport->tls);
-	transport->tls.ciphers = transport->ciphers;
+	/* We're transferring the reference from find_temporary_state */
+	internal_state->state = find_temporary_state(transport);
+	if (!internal_state->state) {
+		ao2_cleanup(internal_state);
+		return NULL;
+	}
+	internal_state->transport = ao2_bump(transport);
+	internal_state->transport->state = internal_state->state;
+	remove_temporary_state();
 
-	return transport;
+	return internal_state;
 }
 
-static void set_qos(struct ast_sip_transport *transport, pj_qos_params *qos)
+/*!
+ * \internal
+ * \brief Should only be called by the individual field handlers
+ */
+static struct ast_sip_transport_state *find_or_create_temporary_state(struct ast_sip_transport *transport)
 {
-	int tos_as_dscp = transport->tos >> 2;
+	struct ast_sip_transport_state **state;
+	struct ast_sip_transport_state *new_state;
 
-	if (transport->tos) {
-		qos->flags |= PJ_QOS_PARAM_HAS_DSCP;
-		qos->dscp_val = tos_as_dscp;
+	if ((new_state = find_temporary_state(transport))) {
+		return new_state;
 	}
-	if (transport->cos) {
-		qos->flags |= PJ_QOS_PARAM_HAS_SO_PRIO;
-		qos->so_prio = transport->cos;
+
+	state = ast_threadstorage_get(&temp_state_store, sizeof(state));
+	if (!state || *state) {
+		return NULL;
+	}
+
+	new_state = ao2_alloc(sizeof(**state), sip_transport_state_destroy);
+	if (!new_state) {
+		return NULL;
+	}
+	new_state->id = ast_strdup(ast_sorcery_object_get_id(transport));
+	new_state->type = transport->type;
+
+	pjsip_tls_setting_default(&new_state->tls);
+	new_state->tls.ciphers = new_state->ciphers;
+
+	ao2_ref(new_state, +1);
+	*state = new_state;
+
+	return new_state;
+}
+
+static void copy_state_to_transport(struct ast_sip_transport *transport)
+{
+	ast_assert(transport && transport->state);
+
+	memcpy(&transport->host, &transport->state->host, sizeof(transport->host));
+	memcpy(&transport->tls, &transport->state->tls, sizeof(transport->tls));
+	memcpy(&transport->ciphers, &transport->state->ciphers, sizeof(transport->ciphers));
+	transport->localnet = transport->state->localnet;
+	transport->external_address_refresher = transport->state->external_address_refresher;
+	memcpy(&transport->external_address, &transport->state->external_address, sizeof(transport->external_address));
+}
+
+static int has_state_changed(struct ast_sip_transport_state *a, struct ast_sip_transport_state *b)
+{
+	if (a->type != b->type) {
+		return -1;
+	}
+
+	if (pj_sockaddr_cmp(&a->host, &b->host)) {
+		return -1;
+	}
+
+	if ((a->localnet || b->localnet)
+		&& ((!a->localnet != !b->localnet)
+		|| ast_sockaddr_cmp(&a->localnet->addr, &b->localnet->addr)
+		|| ast_sockaddr_cmp(&a->localnet->netmask, &b->localnet->netmask)))
+	{
+		return -1;
+	}
+
+	if (ast_sockaddr_cmp(&a->external_address, &b->external_address)) {
+		return -1;
+	}
+
+	if (a->tls.method != b->tls.method
+		|| a->tls.ciphers_num != b->tls.ciphers_num
+#ifdef HAVE_PJSIP_TLS_TRANSPORT_PROTO
+		|| a->tls.proto != b->tls.proto
+#endif
+		|| a->tls.verify_client != b->tls.verify_client
+		|| a->tls.verify_server != b->tls.verify_server
+		|| a->tls.require_client_cert != b->tls.require_client_cert) {
+		return -1;
+	}
+
+	if (memcmp(a->ciphers, b->ciphers, sizeof(pj_ssl_cipher) * fmax(a->tls.ciphers_num, b->tls.ciphers_num))) {
+		return -1;
+	}
+
+	return 0;
+}
+
+static void states_cleanup(void *states)
+{
+	if (states) {
+		ao2_unlock(states);
 	}
 }
 
@@ -145,64 +449,109 @@ static void set_qos(struct ast_sip_transport *transport, pj_qos_params *qos)
 static int transport_apply(const struct ast_sorcery *sorcery, void *obj)
 {
 	struct ast_sip_transport *transport = obj;
-	RAII_VAR(struct ast_sip_transport *, existing, ast_sorcery_retrieve_by_id(sorcery, "transport", ast_sorcery_object_get_id(obj)), ao2_cleanup);
+	const char *transport_id = ast_sorcery_object_get_id(obj);
+	RAII_VAR(struct ao2_container *, states, transport_states, states_cleanup);
+	RAII_VAR(struct internal_state *, temp_state, NULL, ao2_cleanup);
+	RAII_VAR(struct internal_state *, perm_state, NULL, ao2_cleanup);
+	RAII_VAR(struct ast_variable *, changes, NULL, ast_variables_destroy);
 	pj_status_t res = -1;
+	int i;
+#define BIND_TRIES 3
+#define BIND_DELAY_US 100000
 
-	if (!existing || !existing->state) {
-		if (!(transport->state = ao2_alloc(sizeof(*transport->state), transport_state_destroy))) {
-			ast_log(LOG_ERROR, "Transport state for '%s' could not be allocated\n", ast_sorcery_object_get_id(obj));
-			return -1;
-		}
-	} else {
-		transport->state = existing->state;
-		ao2_ref(transport->state, +1);
+	if (!states) {
+		return -1;
 	}
 
-	/* Once active a transport can not be reconfigured */
-	if (transport->state->transport || transport->state->factory) {
+	/*
+	 * transport_apply gets called for EVERY retrieval of a transport when using realtime.
+	 * We need to prevent multiple threads from trying to mess with underlying transports
+	 * at the same time.  The container is the only thing we have to lock on.
+	 */
+	ao2_wrlock(states);
+
+	temp_state = internal_state_alloc(transport);
+	if (!temp_state) {
+		ast_log(LOG_ERROR, "Transport '%s' failed to allocate memory\n", transport_id);
 		return -1;
 	}
 
-	if (transport->host.addr.sa_family != PJ_AF_INET && transport->host.addr.sa_family != PJ_AF_INET6) {
-		ast_log(LOG_ERROR, "Transport '%s' could not be started as binding not specified\n", ast_sorcery_object_get_id(obj));
+	perm_state = find_internal_state_by_transport(transport);
+	if (perm_state) {
+		ast_sorcery_diff(sorcery, perm_state->transport, transport, &changes);
+		if (!changes && !has_state_changed(perm_state->state, temp_state->state)) {
+			/* In case someone is using the deprecated fields, reset them */
+			transport->state = perm_state->state;
+			copy_state_to_transport(transport);
+			ao2_replace(perm_state->transport, transport);
+			return 0;
+		}
+
+		if (!transport->allow_reload) {
+			if (!perm_state->change_detected) {
+				perm_state->change_detected = 1;
+				ast_log(LOG_WARNING, "Transport '%s' is not reloadable, maintaining previous values\n", transport_id);
+			}
+			/* In case someone is using the deprecated fields, reset them */
+			transport->state = perm_state->state;
+			copy_state_to_transport(transport);
+			ao2_replace(perm_state->transport, transport);
+			return 0;
+		}
+	}
+
+	if (temp_state->state->host.addr.sa_family != PJ_AF_INET && temp_state->state->host.addr.sa_family != PJ_AF_INET6) {
+		ast_log(LOG_ERROR, "Transport '%s' could not be started as binding not specified\n", transport_id);
 		return -1;
 	}
 
 	/* Set default port if not present */
-	if (!pj_sockaddr_get_port(&transport->host)) {
-		pj_sockaddr_set_port(&transport->host, (transport->type == AST_TRANSPORT_TLS) ? 5061 : 5060);
+	if (!pj_sockaddr_get_port(&temp_state->state->host)) {
+		pj_sockaddr_set_port(&temp_state->state->host, (transport->type == AST_TRANSPORT_TLS) ? 5061 : 5060);
 	}
 
 	/* Now that we know what address family we can set up a dnsmgr refresh for the external media address if present */
 	if (!ast_strlen_zero(transport->external_signaling_address)) {
-		if (transport->host.addr.sa_family == pj_AF_INET()) {
-			transport->external_address.ss.ss_family = AF_INET;
-		} else if (transport->host.addr.sa_family == pj_AF_INET6()) {
-			transport->external_address.ss.ss_family = AF_INET6;
+		if (temp_state->state->host.addr.sa_family == pj_AF_INET()) {
+			temp_state->state->external_address.ss.ss_family = AF_INET;
+		} else if (temp_state->state->host.addr.sa_family == pj_AF_INET6()) {
+			temp_state->state->external_address.ss.ss_family = AF_INET6;
 		} else {
 			ast_log(LOG_ERROR, "Unknown address family for transport '%s', could not get external signaling address\n",
-					ast_sorcery_object_get_id(obj));
+					transport_id);
 			return -1;
 		}
 
-		if (ast_dnsmgr_lookup(transport->external_signaling_address, &transport->external_address, &transport->external_address_refresher, NULL) < 0) {
-			ast_log(LOG_ERROR, "Could not create dnsmgr for external signaling address on '%s'\n", ast_sorcery_object_get_id(obj));
+		if (ast_dnsmgr_lookup(transport->external_signaling_address, &temp_state->state->external_address, &temp_state->state->external_address_refresher, NULL) < 0) {
+			ast_log(LOG_ERROR, "Could not create dnsmgr for external signaling address on '%s'\n", transport_id);
 			return -1;
 		}
 	}
 
 	if (transport->type == AST_TRANSPORT_UDP) {
-		if (transport->host.addr.sa_family == pj_AF_INET()) {
-			res = pjsip_udp_transport_start(ast_sip_get_pjsip_endpoint(), &transport->host.ipv4, NULL, transport->async_operations, &transport->state->transport);
-		} else if (transport->host.addr.sa_family == pj_AF_INET6()) {
-			res = pjsip_udp_transport_start6(ast_sip_get_pjsip_endpoint(), &transport->host.ipv6, NULL, transport->async_operations, &transport->state->transport);
+
+		for (i = 0; i < BIND_TRIES && res != PJ_SUCCESS; i++) {
+			if (perm_state && perm_state->state && perm_state->state->transport) {
+				pjsip_udp_transport_pause(perm_state->state->transport,
+					PJSIP_UDP_TRANSPORT_DESTROY_SOCKET);
+				usleep(BIND_DELAY_US);
+			}
+
+			if (temp_state->state->host.addr.sa_family == pj_AF_INET()) {
+				res = pjsip_udp_transport_start(ast_sip_get_pjsip_endpoint(),
+					&temp_state->state->host.ipv4, NULL, transport->async_operations,
+					&temp_state->state->transport);
+			} else if (temp_state->state->host.addr.sa_family == pj_AF_INET6()) {
+				res = pjsip_udp_transport_start6(ast_sip_get_pjsip_endpoint(),
+					&temp_state->state->host.ipv6, NULL, transport->async_operations,
+					&temp_state->state->transport);
+			}
 		}
 
 		if (res == PJ_SUCCESS && (transport->tos || transport->cos)) {
 			pj_sock_t sock;
 			pj_qos_params qos_params;
-
-			sock = pjsip_udp_transport_get_socket(transport->state->transport);
+			sock = pjsip_udp_transport_get_socket(temp_state->state->transport);
 			pj_sock_get_qos_params(sock, &qos_params);
 			set_qos(transport, &qos_params);
 			pj_sock_set_qos_params(sock, &qos_params);
@@ -210,61 +559,42 @@ static int transport_apply(const struct ast_sorcery *sorcery, void *obj)
 	} else if (transport->type == AST_TRANSPORT_TCP) {
 		pjsip_tcp_transport_cfg cfg;
 
-		pjsip_tcp_transport_cfg_default(&cfg, transport->host.addr.sa_family);
-		cfg.bind_addr = transport->host;
+		pjsip_tcp_transport_cfg_default(&cfg, temp_state->state->host.addr.sa_family);
+		cfg.bind_addr = temp_state->state->host;
 		cfg.async_cnt = transport->async_operations;
 		set_qos(transport, &cfg.qos_params);
 
-		res = pjsip_tcp_transport_start3(ast_sip_get_pjsip_endpoint(), &cfg, &transport->state->factory);
+		for (i = 0; i < BIND_TRIES && res != PJ_SUCCESS; i++) {
+			if (perm_state && perm_state->state && perm_state->state->factory
+				&& perm_state->state->factory->destroy) {
+				perm_state->state->factory->destroy(perm_state->state->factory);
+				usleep(BIND_DELAY_US);
+			}
+
+			res = pjsip_tcp_transport_start3(ast_sip_get_pjsip_endpoint(), &cfg,
+				&temp_state->state->factory);
+		}
 	} else if (transport->type == AST_TRANSPORT_TLS) {
 		if (transport->async_operations > 1 && ast_compare_versions(pj_get_version(), "2.5.0") < 0) {
 			ast_log(LOG_ERROR, "Transport: %s: When protocol=tls and pjproject version < 2.5.0, async_operations can't be > 1\n",
 					ast_sorcery_object_get_id(obj));
 			return -1;
 		}
-		if (!ast_strlen_zero(transport->ca_list_file)) {
-			if (!ast_file_is_readable(transport->ca_list_file)) {
-				ast_log(LOG_ERROR, "Transport: %s: ca_list_file %s is either missing or not readable\n",
-						ast_sorcery_object_get_id(obj), transport->ca_list_file);
-				return -1;
-			}
-		}
-		transport->tls.ca_list_file = pj_str((char*)transport->ca_list_file);
-#ifdef HAVE_PJ_SSL_CERT_LOAD_FROM_FILES2
-		if (!ast_strlen_zero(transport->ca_list_path)) {
-			if (!ast_file_is_readable(transport->ca_list_path)) {
-				ast_log(LOG_ERROR, "Transport: %s: ca_list_path %s is either missing or not readable\n",
-						ast_sorcery_object_get_id(obj), transport->ca_list_path);
-				return -1;
-			}
-		}
-		transport->tls.ca_list_path = pj_str((char*)transport->ca_list_path);
-#else
-		if (!ast_strlen_zero(transport->ca_list_path)) {
-			ast_log(LOG_WARNING, "Asterisk has been built against a version of pjproject that does not "
-					"support the 'ca_list_path' option. Please upgrade to version 2.4 or later.\n");
-		}
-#endif
-		if (!ast_strlen_zero(transport->cert_file)) {
-			if (!ast_file_is_readable(transport->cert_file)) {
-				ast_log(LOG_ERROR, "Transport: %s: cert_file %s is either missing or not readable\n",
-						ast_sorcery_object_get_id(obj), transport->cert_file);
-				return -1;
-			}
-		}
-		transport->tls.cert_file = pj_str((char*)transport->cert_file);
-		if (!ast_strlen_zero(transport->privkey_file)) {
-			if (!ast_file_is_readable(transport->privkey_file)) {
-				ast_log(LOG_ERROR, "Transport: %s: privkey_file %s is either missing or not readable\n",
-						ast_sorcery_object_get_id(obj), transport->privkey_file);
-				return -1;
+
+		temp_state->state->tls.password = pj_str((char*)transport->password);
+		set_qos(transport, &temp_state->state->tls.qos_params);
+
+		for (i = 0; i < BIND_TRIES && res != PJ_SUCCESS; i++) {
+			if (perm_state && perm_state->state && perm_state->state->factory
+				&& perm_state->state->factory->destroy) {
+				perm_state->state->factory->destroy(perm_state->state->factory);
+				usleep(BIND_DELAY_US);
 			}
-		}
-		transport->tls.privkey_file = pj_str((char*)transport->privkey_file);
-		transport->tls.password = pj_str((char*)transport->password);
-		set_qos(transport, &transport->tls.qos_params);
 
-		res = pjsip_tls_transport_start2(ast_sip_get_pjsip_endpoint(), &transport->tls, &transport->host, NULL, transport->async_operations, &transport->state->factory);
+			res = pjsip_tls_transport_start2(ast_sip_get_pjsip_endpoint(), &temp_state->state->tls,
+				&temp_state->state->host, NULL, transport->async_operations,
+				&temp_state->state->factory);
+		}
 	} else if ((transport->type == AST_TRANSPORT_WS) || (transport->type == AST_TRANSPORT_WSS)) {
 		if (transport->cos || transport->tos) {
 			ast_log(LOG_WARNING, "TOS and COS values ignored for websocket transport\n");
@@ -279,6 +609,98 @@ static int transport_apply(const struct ast_sorcery *sorcery, void *obj)
 		ast_log(LOG_ERROR, "Transport '%s' could not be started: %s\n", ast_sorcery_object_get_id(obj), msg);
 		return -1;
 	}
+
+	copy_state_to_transport(transport);
+	if (perm_state) {
+		ao2_unlink_flags(states, perm_state, OBJ_NOLOCK);
+	}
+	ao2_link_flags(states, temp_state, OBJ_NOLOCK);
+
+	return 0;
+}
+
+/*! \brief Custom handler for type just makes sure the state is created */
+static int transport_state_init(const struct aco_option *opt, struct ast_variable *var, void *obj)
+{
+	struct ast_sip_transport *transport = obj;
+	struct ast_sip_transport_state *state = find_or_create_temporary_state(transport);
+
+	ao2_cleanup(state);
+
+	return 0;
+}
+
+/*! \brief Custom handler for TLS method setting */
+static int transport_tls_file_handler(const struct aco_option *opt, struct ast_variable *var, void *obj)
+{
+	struct ast_sip_transport *transport = obj;
+	RAII_VAR(struct ast_sip_transport_state *, state, find_or_create_temporary_state(transport), ao2_cleanup);
+
+	if (!state) {
+		return -1;
+	}
+
+	if (!ast_file_is_readable(var->value)) {
+		ast_log(LOG_ERROR, "Transport: %s: %s %s is either missing or not readable\n",
+			ast_sorcery_object_get_id(obj), var->name, var->value);
+		return -1;
+	}
+
+	if (!strcasecmp(var->name, "ca_list_file")) {
+		state->tls.ca_list_file = pj_str((char*)var->value);
+		ast_string_field_set(transport, ca_list_file, var->value);
+	} else if (!strcasecmp(var->name, "ca_list_path")) {
+#ifdef HAVE_PJ_SSL_CERT_LOAD_FROM_FILES2
+		state->tls.ca_list_path = pj_str((char*)var->value);
+		ast_string_field_set(transport, ca_list_path, var->value);
+#else
+		ast_log(LOG_WARNING, "Asterisk has been built against a version of pjproject that does not "
+				"support the 'ca_list_path' option. Please upgrade to version 2.4 or later.\n");
+#endif
+	} else if (!strcasecmp(var->name, "cert_file")) {
+		state->tls.cert_file = pj_str((char*)var->value);
+		ast_string_field_set(transport, cert_file, var->value);
+	} else if (!strcasecmp(var->name, "priv_key_file")) {
+		state->tls.privkey_file = pj_str((char*)var->value);
+		ast_string_field_set(transport, privkey_file, var->value);
+	}
+
+	return 0;
+}
+
+static int ca_list_file_to_str(const void *obj, const intptr_t *args, char **buf)
+{
+	const struct ast_sip_transport *transport = obj;
+
+	*buf = ast_strdup(transport->ca_list_file);
+
+	return 0;
+}
+
+static int ca_list_path_to_str(const void *obj, const intptr_t *args, char **buf)
+{
+	const struct ast_sip_transport *transport = obj;
+
+	*buf = ast_strdup(transport->ca_list_path);
+
+	return 0;
+}
+
+static int cert_file_to_str(const void *obj, const intptr_t *args, char **buf)
+{
+	const struct ast_sip_transport *transport = obj;
+
+	*buf = ast_strdup(transport->cert_file);
+
+	return 0;
+}
+
+static int privkey_file_to_str(const void *obj, const intptr_t *args, char **buf)
+{
+	const struct ast_sip_transport *transport = obj;
+
+	*buf = ast_strdup(transport->privkey_file);
+
 	return 0;
 }
 
@@ -286,6 +708,11 @@ static int transport_apply(const struct ast_sorcery *sorcery, void *obj)
 static int transport_protocol_handler(const struct aco_option *opt, struct ast_variable *var, void *obj)
 {
 	struct ast_sip_transport *transport = obj;
+	RAII_VAR(struct ast_sip_transport_state *, state, find_or_create_temporary_state(transport), ao2_cleanup);
+
+	if (!state) {
+		return -1;
+	}
 
 	if (!strcasecmp(var->value, "udp")) {
 		transport->type = AST_TRANSPORT_UDP;
@@ -301,6 +728,8 @@ static int transport_protocol_handler(const struct aco_option *opt, struct ast_v
 		return -1;
 	}
 
+	state->type = transport->type;
+
 	return 0;
 }
 
@@ -328,7 +757,14 @@ static int transport_bind_handler(const struct aco_option *opt, struct ast_varia
 {
 	struct ast_sip_transport *transport = obj;
 	pj_str_t buf;
-	int rc = pj_sockaddr_parse(pj_AF_UNSPEC(), 0, pj_cstr(&buf, var->value), &transport->host);
+	int rc;
+	RAII_VAR(struct ast_sip_transport_state *, state, find_or_create_temporary_state(transport), ao2_cleanup);
+
+	if (!state) {
+		return -1;
+	}
+
+	rc = pj_sockaddr_parse(pj_AF_UNSPEC(), 0, pj_cstr(&buf, var->value), &state->host);
 
 	return rc != PJ_SUCCESS ? -1 : 0;
 }
@@ -336,13 +772,18 @@ static int transport_bind_handler(const struct aco_option *opt, struct ast_varia
 static int transport_bind_to_str(const void *obj, const intptr_t *args, char **buf)
 {
 	const struct ast_sip_transport *transport = obj;
+	RAII_VAR(struct ast_sip_transport_state *, state, find_state_by_transport(transport), ao2_cleanup);
+
+	if (!state) {
+		return -1;
+	}
 
 	if (!(*buf = ast_calloc(MAX_OBJECT_FIELD, sizeof(char)))) {
 		return -1;
 	}
 
 	/* include port as well as brackets if IPv6 */
-	pj_sockaddr_print(&transport->host, *buf, MAX_OBJECT_FIELD, 1 | 2);
+	pj_sockaddr_print(&state->host, *buf, MAX_OBJECT_FIELD, 1 | 2);
 
 	return 0;
 }
@@ -351,13 +792,18 @@ static int transport_bind_to_str(const void *obj, const intptr_t *args, char **b
 static int transport_tls_bool_handler(const struct aco_option *opt, struct ast_variable *var, void *obj)
 {
 	struct ast_sip_transport *transport = obj;
+	RAII_VAR(struct ast_sip_transport_state *, state, find_or_create_temporary_state(transport), ao2_cleanup);
+
+	if (!state) {
+		return -1;
+	}
 
 	if (!strcasecmp(var->name, "verify_server")) {
-		transport->tls.verify_server = ast_true(var->value) ? PJ_TRUE : PJ_FALSE;
+		state->tls.verify_server = ast_true(var->value) ? PJ_TRUE : PJ_FALSE;
 	} else if (!strcasecmp(var->name, "verify_client")) {
-		transport->tls.verify_client = ast_true(var->value) ? PJ_TRUE : PJ_FALSE;
+		state->tls.verify_client = ast_true(var->value) ? PJ_TRUE : PJ_FALSE;
 	} else if (!strcasecmp(var->name, "require_client_cert")) {
-		transport->tls.require_client_cert = ast_true(var->value) ? PJ_TRUE : PJ_FALSE;
+		state->tls.require_client_cert = ast_true(var->value) ? PJ_TRUE : PJ_FALSE;
 	} else {
 		return -1;
 	}
@@ -368,21 +814,42 @@ static int transport_tls_bool_handler(const struct aco_option *opt, struct ast_v
 static int verify_server_to_str(const void *obj, const intptr_t *args, char **buf)
 {
 	const struct ast_sip_transport *transport = obj;
-	*buf = ast_strdup(AST_YESNO(transport->tls.verify_server));
+	RAII_VAR(struct ast_sip_transport_state *, state, find_state_by_transport(transport), ao2_cleanup);
+
+	if (!state) {
+		return -1;
+	}
+
+	*buf = ast_strdup(AST_YESNO(state->tls.verify_server));
+
 	return 0;
 }
 
 static int verify_client_to_str(const void *obj, const intptr_t *args, char **buf)
 {
 	const struct ast_sip_transport *transport = obj;
-	*buf = ast_strdup(AST_YESNO(transport->tls.verify_client));
+	RAII_VAR(struct ast_sip_transport_state *, state, find_state_by_transport(transport), ao2_cleanup);
+
+	if (!state) {
+		return -1;
+	}
+
+	*buf = ast_strdup(AST_YESNO(state->tls.verify_client));
+
 	return 0;
 }
 
 static int require_client_cert_to_str(const void *obj, const intptr_t *args, char **buf)
 {
 	const struct ast_sip_transport *transport = obj;
-	*buf = ast_strdup(AST_YESNO(transport->tls.require_client_cert));
+	RAII_VAR(struct ast_sip_transport_state *, state, find_state_by_transport(transport), ao2_cleanup);
+
+	if (!state) {
+		return -1;
+	}
+
+	*buf = ast_strdup(AST_YESNO(state->tls.require_client_cert));
+
 	return 0;
 }
 
@@ -390,19 +857,24 @@ static int require_client_cert_to_str(const void *obj, const intptr_t *args, cha
 static int transport_tls_method_handler(const struct aco_option *opt, struct ast_variable *var, void *obj)
 {
 	struct ast_sip_transport *transport = obj;
+	RAII_VAR(struct ast_sip_transport_state *, state, find_or_create_temporary_state(transport), ao2_cleanup);
+
+	if (!state) {
+		return -1;
+	}
 
 	if (ast_strlen_zero(var->value) || !strcasecmp(var->value, "default")) {
-		transport->tls.method = PJSIP_SSL_DEFAULT_METHOD;
+		state->tls.method = PJSIP_SSL_DEFAULT_METHOD;
 	} else if (!strcasecmp(var->value, "unspecified")) {
-		transport->tls.method = PJSIP_SSL_UNSPECIFIED_METHOD;
+		state->tls.method = PJSIP_SSL_UNSPECIFIED_METHOD;
 	} else if (!strcasecmp(var->value, "tlsv1")) {
-		transport->tls.method = PJSIP_TLSV1_METHOD;
+		state->tls.method = PJSIP_TLSV1_METHOD;
 	} else if (!strcasecmp(var->value, "sslv2")) {
-		transport->tls.method = PJSIP_SSLV2_METHOD;
+		state->tls.method = PJSIP_SSLV2_METHOD;
 	} else if (!strcasecmp(var->value, "sslv3")) {
-		transport->tls.method = PJSIP_SSLV3_METHOD;
+		state->tls.method = PJSIP_SSLV3_METHOD;
 	} else if (!strcasecmp(var->value, "sslv23")) {
-		transport->tls.method = PJSIP_SSLV23_METHOD;
+		state->tls.method = PJSIP_SSLV23_METHOD;
 	} else {
 		return -1;
 	}
@@ -421,9 +893,16 @@ static const char *tls_method_map[] = {
 static int tls_method_to_str(const void *obj, const intptr_t *args, char **buf)
 {
 	const struct ast_sip_transport *transport = obj;
-	if (ARRAY_IN_BOUNDS(transport->tls.method, tls_method_map)) {
-		*buf = ast_strdup(tls_method_map[transport->tls.method]);
+	RAII_VAR(struct ast_sip_transport_state *, state, find_state_by_transport(transport), ao2_cleanup);
+
+	if (!state) {
+		return -1;
+	}
+
+	if (ARRAY_IN_BOUNDS(state->tls.method, tls_method_map)) {
+		*buf = ast_strdup(tls_method_map[state->tls.method]);
 	}
+
 	return 0;
 }
 
@@ -463,7 +942,7 @@ static pj_ssl_cipher cipher_name_to_id(const char *name)
  * \retval 0 on success.
  * \retval -1 on error.
  */
-static int transport_cipher_add(struct ast_sip_transport *transport, const char *name)
+static int transport_cipher_add(struct ast_sip_transport_state *state, const char *name)
 {
 	pj_ssl_cipher cipher;
 	int idx;
@@ -480,13 +959,13 @@ static int transport_cipher_add(struct ast_sip_transport *transport, const char
 	}
 
 	if (pj_ssl_cipher_is_supported(cipher)) {
-		for (idx = transport->tls.ciphers_num; idx--;) {
-			if (transport->ciphers[idx] == cipher) {
+		for (idx = state->tls.ciphers_num; idx--;) {
+			if (state->ciphers[idx] == cipher) {
 				/* The cipher is already in the list. */
 				return 0;
 			}
 		}
-		transport->ciphers[transport->tls.ciphers_num++] = cipher;
+		state->ciphers[state->tls.ciphers_num++] = cipher;
 		return 0;
 	} else {
 		ast_log(LOG_ERROR, "Cipher '%s' is unsupported\n", name);
@@ -501,19 +980,23 @@ static int transport_tls_cipher_handler(const struct aco_option *opt, struct ast
 	char *parse;
 	char *name;
 	int res = 0;
+	RAII_VAR(struct ast_sip_transport_state *, state, find_or_create_temporary_state(transport), ao2_cleanup);
+
+	if (!state) {
+		return -1;
+	}
 
 	parse = ast_strdupa(S_OR(var->value, ""));
-	while ((name = strsep(&parse, ","))) {
-		name = ast_strip(name);
+	while ((name = ast_strip(strsep(&parse, ",")))) {
 		if (ast_strlen_zero(name)) {
 			continue;
 		}
-		if (ARRAY_LEN(transport->ciphers) <= transport->tls.ciphers_num) {
+		if (ARRAY_LEN(state->ciphers) <= state->tls.ciphers_num) {
 			ast_log(LOG_ERROR, "Too many ciphers specified\n");
 			res = -1;
 			break;
 		}
-		res |= transport_cipher_add(transport, name);
+		res |= transport_cipher_add(state, name);
 	}
 	return res ? -1 : 0;
 }
@@ -543,8 +1026,13 @@ static void cipher_to_str(char **buf, const pj_ssl_cipher *ciphers, unsigned int
 static int transport_tls_cipher_to_str(const void *obj, const intptr_t *args, char **buf)
 {
 	const struct ast_sip_transport *transport = obj;
+	RAII_VAR(struct ast_sip_transport_state *, state, find_state_by_transport(transport), ao2_cleanup);
+
+	if (!state) {
+		return -1;
+	}
 
-	cipher_to_str(buf, transport->ciphers, transport->tls.ciphers_num);
+	cipher_to_str(buf, state->ciphers, state->tls.ciphers_num);
 	return *buf ? 0 : -1;
 }
 
@@ -584,14 +1072,19 @@ static int transport_localnet_handler(const struct aco_option *opt, struct ast_v
 {
 	struct ast_sip_transport *transport = obj;
 	int error = 0;
+	RAII_VAR(struct ast_sip_transport_state *, state, find_or_create_temporary_state(transport), ao2_cleanup);
+
+	if (!state) {
+		return -1;
+	}
 
 	if (ast_strlen_zero(var->value)) {
-		ast_free_ha(transport->localnet);
-		transport->localnet = NULL;
+		ast_free_ha(state->localnet);
+		state->localnet = NULL;
 		return 0;
 	}
 
-	if (!(transport->localnet = ast_append_ha("d", var->value, transport->localnet, &error))) {
+	if (!(state->localnet = ast_append_ha("d", var->value, state->localnet, &error))) {
 		return -1;
 	}
 
@@ -601,12 +1094,16 @@ static int transport_localnet_handler(const struct aco_option *opt, struct ast_v
 static int localnet_to_vl(const void *obj, struct ast_variable **fields)
 {
 	const struct ast_sip_transport *transport = obj;
-
 	char str[MAX_OBJECT_FIELD];
 	struct ast_variable *head = NULL;
-	struct ast_ha *ha = transport->localnet;
+	struct ast_ha *ha;
+	RAII_VAR(struct ast_sip_transport_state *, state, find_state_by_transport(transport), ao2_cleanup);
 
-	for (; ha; ha = ha->next) {
+	if (!state) {
+		return -1;
+	}
+
+	for (ha = state->localnet; ha; ha = ha->next) {
 		const char *addr = ast_strdupa(ast_sockaddr_stringify_addr(&ha->addr));
 		snprintf(str, MAX_OBJECT_FIELD, "%s%s/%s", ha->sense == AST_SENSE_ALLOW ? "!" : "",
 			addr, ast_sockaddr_stringify_addr(&ha->netmask));
@@ -625,8 +1122,13 @@ static int localnet_to_str(const void *obj, const intptr_t *args, char **buf)
 {
 	RAII_VAR(struct ast_str *, str, ast_str_create(MAX_OBJECT_FIELD), ast_free);
 	const struct ast_sip_transport *transport = obj;
+	RAII_VAR(struct ast_sip_transport_state *, state, find_state_by_transport(transport), ao2_cleanup);
 
-	ast_ha_join(transport->localnet, &str);
+	if (!state) {
+		return -1;
+	}
+
+	ast_ha_join(state->localnet, &str);
 	*buf = ast_strdup(ast_str_buffer(str));
 	return 0;
 }
@@ -730,10 +1232,15 @@ static int cli_print_body(void *obj, void *arg, int flags)
 	struct ast_sip_transport *transport = obj;
 	struct ast_sip_cli_context *context = arg;
 	char hoststr[PJ_INET6_ADDRSTRLEN];
+	RAII_VAR(struct ast_sip_transport_state *, state, find_state_by_transport(transport), ao2_cleanup);
+
+	if (!state) {
+		return -1;
+	}
 
 	ast_assert(context->output_buffer != NULL);
 
-	pj_sockaddr_print(&transport->host, hoststr, sizeof(hoststr), 3);
+	pj_sockaddr_print(&state->host, hoststr, sizeof(hoststr), 3);
 
 	ast_str_append(&context->output_buffer, 0, "%*s:  %-21s  %6s  %5u  %5u  %s\n",
 		CLI_INDENT_TO_SPACES(context->indent_level), "Transport",
@@ -770,25 +1277,78 @@ static struct ast_cli_entry cli_commands[] = {
 
 static struct ast_sip_cli_formatter_entry *cli_formatter;
 
+struct ast_sip_transport_state *ast_sip_get_transport_state(const char *transport_id)
+{
+	struct internal_state * state = NULL;
+
+	if (!transport_states) {
+		return NULL;
+	}
+
+	state = ao2_find(transport_states, transport_id, OBJ_SEARCH_KEY);
+	if (!state || !state->state) {
+		ao2_cleanup(state);
+		return NULL;
+	}
+
+	ao2_ref(state->state, +1);
+	ao2_ref(state, -1);
+
+	return state->state;
+}
+
+static int populate_transport_states(void *obj, void *arg, int flags)
+{
+	struct internal_state *state = obj;
+	struct ao2_container *container = arg;
+
+	ao2_link(container, state->state);
+
+	return CMP_MATCH;
+}
+
+struct ao2_container *ast_sip_get_transport_states(void)
+{
+	struct ao2_container *states = ao2_container_alloc(DEFAULT_STATE_BUCKETS, transport_state_hash, transport_state_cmp);
+
+	if (!states) {
+		return NULL;
+	}
+
+	ao2_callback(transport_states, OBJ_NODATA | OBJ_MULTIPLE, populate_transport_states, states);
+	return states;
+}
+
 /*! \brief Initialize sorcery with transport support */
 int ast_sip_initialize_sorcery_transport(void)
 {
 	struct ast_sorcery *sorcery = ast_sip_get_sorcery();
+	struct ao2_container *transports = NULL;
+
+	/* Create outbound registration states container. */
+	transport_states = ao2_container_alloc(DEFAULT_STATE_BUCKETS, internal_state_hash, internal_state_cmp);
+	if (!transport_states) {
+		ast_log(LOG_ERROR, "Unable to allocate transport states container\n");
+		return AST_MODULE_LOAD_FAILURE;
+	}
 
 	ast_sorcery_apply_default(sorcery, "transport", "config", "pjsip.conf,criteria=type=transport");
 
-	if (ast_sorcery_object_register_no_reload(sorcery, "transport", transport_alloc, NULL, transport_apply)) {
+	if (ast_sorcery_object_register(sorcery, "transport", sip_transport_alloc, NULL, transport_apply)) {
 		return -1;
 	}
 
-	ast_sorcery_object_field_register(sorcery, "transport", "type", "", OPT_NOOP_T, 0, 0);
+	/* Normally type is a OPT_NOOP_T but we're using it to make sure that state is created */
+	ast_sorcery_object_field_register_custom(sorcery, "transport", "type", "", transport_state_init, NULL, NULL, 0, 0);
 	ast_sorcery_object_field_register_custom(sorcery, "transport", "protocol", "udp", transport_protocol_handler, transport_protocol_to_str, NULL, 0, 0);
 	ast_sorcery_object_field_register_custom(sorcery, "transport", "bind", "", transport_bind_handler, transport_bind_to_str, NULL, 0, 0);
 	ast_sorcery_object_field_register(sorcery, "transport", "async_operations", "1", OPT_UINT_T, 0, FLDSET(struct ast_sip_transport, async_operations));
-	ast_sorcery_object_field_register(sorcery, "transport", "ca_list_file", "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct ast_sip_transport, ca_list_file));
-	ast_sorcery_object_field_register(sorcery, "transport", "ca_list_path", "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct ast_sip_transport, ca_list_path));
-	ast_sorcery_object_field_register(sorcery, "transport", "cert_file", "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct ast_sip_transport, cert_file));
-	ast_sorcery_object_field_register(sorcery, "transport", "priv_key_file", "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct ast_sip_transport, privkey_file));
+
+	ast_sorcery_object_field_register_custom(sorcery, "transport", "ca_list_file", "", transport_tls_file_handler, ca_list_file_to_str, NULL, 0, 0);
+	ast_sorcery_object_field_register_custom(sorcery, "transport", "ca_list_path", "", transport_tls_file_handler, ca_list_path_to_str, NULL, 0, 0);
+	ast_sorcery_object_field_register_custom(sorcery, "transport", "cert_file", "", transport_tls_file_handler, cert_file_to_str, NULL, 0, 0);
+	ast_sorcery_object_field_register_custom(sorcery, "transport", "priv_key_file", "", transport_tls_file_handler, privkey_file_to_str, NULL, 0, 0);
+
 	ast_sorcery_object_field_register(sorcery, "transport", "password", "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct ast_sip_transport, password));
 	ast_sorcery_object_field_register(sorcery, "transport", "external_signaling_address", "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct ast_sip_transport, external_signaling_address));
 	ast_sorcery_object_field_register(sorcery, "transport", "external_signaling_port", "0", OPT_UINT_T, PARSE_IN_RANGE, FLDSET(struct ast_sip_transport, external_signaling_port), 0, 65535);
@@ -803,6 +1363,7 @@ int ast_sip_initialize_sorcery_transport(void)
 	ast_sorcery_object_field_register_custom(sorcery, "transport", "tos", "0", transport_tos_handler, tos_to_str, NULL, 0, 0);
 	ast_sorcery_object_field_register(sorcery, "transport", "cos", "0", OPT_UINT_T, 0, FLDSET(struct ast_sip_transport, cos));
 	ast_sorcery_object_field_register(sorcery, "transport", "websocket_write_timeout", AST_DEFAULT_WEBSOCKET_WRITE_TIMEOUT_STR, OPT_INT_T, PARSE_IN_RANGE, FLDSET(struct ast_sip_transport, write_timeout), 1, INT_MAX);
+	ast_sorcery_object_field_register(sorcery, "transport", "allow_reload", "no", OPT_BOOL_T, 1, FLDSET(struct ast_sip_transport, allow_reload));
 
 	internal_sip_register_endpoint_formatter(&endpoint_transport_formatter);
 
@@ -822,6 +1383,10 @@ int ast_sip_initialize_sorcery_transport(void)
 	ast_sip_register_cli_formatter(cli_formatter);
 	ast_cli_register_multiple(cli_commands, ARRAY_LEN(cli_commands));
 
+	/* trigger load of transports from realtime by trying to revrieve them all */
+	transports = ast_sorcery_retrieve_by_fields(sorcery, "transport", AST_RETRIEVE_FLAG_ALL | AST_RETRIEVE_FLAG_MULTIPLE, NULL);
+	ao2_cleanup(transports);
+
 	return 0;
 }
 
@@ -832,5 +1397,8 @@ int ast_sip_destroy_sorcery_transport(void)
 
 	internal_sip_unregister_endpoint_formatter(&endpoint_transport_formatter);
 
+	ao2_ref(transport_states, -1);
+	transport_states = NULL;
+
 	return 0;
 }
diff --git a/res/res_pjsip/include/res_pjsip_private.h b/res/res_pjsip/include/res_pjsip_private.h
index c1f7e23..72a4387 100644
--- a/res/res_pjsip/include/res_pjsip_private.h
+++ b/res/res_pjsip/include/res_pjsip_private.h
@@ -318,4 +318,11 @@ int internal_sip_unregister_endpoint_formatter(struct ast_sip_endpoint_formatter
  * \brief Finds or creates contact_status for a contact
  */
 struct ast_sip_contact_status *ast_res_pjsip_find_or_create_contact_status(const struct ast_sip_contact *contact);
+
+/*!
+ * \internal
+ * \brief Validate that the uri meets pjproject length restrictions
+ */
+int ast_sip_validate_uri_length(const char *uri);
+
 #endif /* RES_PJSIP_PRIVATE_H_ */
diff --git a/res/res_pjsip/location.c b/res/res_pjsip/location.c
index 2908f6f..7ff72e4 100644
--- a/res/res_pjsip/location.c
+++ b/res/res_pjsip/location.c
@@ -28,6 +28,11 @@
 #include "asterisk/res_pjsip_cli.h"
 #include "asterisk/statsd.h"
 
+#include "asterisk/res_pjproject.h"
+
+static int pj_max_hostname = PJ_MAX_HOSTNAME;
+static int pjsip_max_url_size = PJSIP_MAX_URL_SIZE;
+
 /*! \brief Destructor for AOR */
 static void aor_destroy(void *obj)
 {
@@ -210,7 +215,7 @@ void ast_sip_location_retrieve_contact_and_aor_from_list(const char *aor_list, s
 	*aor = NULL;
 	*contact = NULL;
 
-	while ((aor_name = strsep(&rest, ","))) {
+	while ((aor_name = ast_strip(strsep(&rest, ",")))) {
 		*aor = ast_sip_location_retrieve_aor(aor_name);
 
 		if (!(*aor)) {
@@ -369,6 +374,69 @@ static int permanent_uri_sort_fn(const void *obj_left, const void *obj_right, in
 	return cmp;
 }
 
+int ast_sip_validate_uri_length(const char *contact_uri)
+{
+	int max_length = pj_max_hostname - 1;
+	char *contact = ast_strdupa(contact_uri);
+	char *host;
+	char *at;
+	int theres_a_port = 0;
+
+	if (strlen(contact_uri) > pjsip_max_url_size - 1) {
+		return -1;
+	}
+
+	contact = ast_strip_quoted(contact, "<", ">");
+
+	if (!strncasecmp(contact, "sip:", 4)) {
+		host = contact + 4;
+	} else if (!strncasecmp(contact, "sips:", 5)) {
+		host = contact + 5;
+	} else {
+		/* Not a SIP URI */
+		return -1;
+	}
+
+	at = strchr(contact, '@');
+	if (at) {
+		/* sip[s]:user at host */
+		host = at + 1;
+	}
+
+	if (host[0] == '[') {
+		/* Host is an IPv6 address. Just get up to the matching bracket */
+		char *close_bracket;
+
+		close_bracket = strchr(host, ']');
+		if (!close_bracket) {
+			return -1;
+		}
+		close_bracket++;
+		if (*close_bracket == ':') {
+			theres_a_port = 1;
+		}
+		*close_bracket = '\0';
+	} else {
+		/* uri parameters could contain ';' so trim them off first */
+		host = strsep(&host, ";?");
+		/* Host is FQDN or IPv4 address. Need to find closing delimiter */
+		if (strchr(host, ':')) {
+			theres_a_port = 1;
+			host = strsep(&host, ":");
+		}
+	}
+
+	if (!theres_a_port) {
+		max_length -= strlen("_sips.tcp.");
+	}
+
+	if (strlen(host) > max_length) {
+		return -1;
+	}
+
+	return 0;
+}
+
 /*! \brief Custom handler for permanent URIs */
 static int permanent_uri_handler(const struct aco_option *opt, struct ast_variable *var, void *obj)
 {
@@ -382,12 +450,21 @@ static int permanent_uri_handler(const struct aco_option *opt, struct ast_variab
 	}
 
 	contacts = ast_strdupa(var->value);
-	while ((contact_uri = strsep(&contacts, ","))) {
+	while ((contact_uri = ast_strip(strsep(&contacts, ",")))) {
 		struct ast_sip_contact *contact;
 		struct ast_sip_contact_status *status;
 		char hash[33];
 		char contact_id[strlen(aor_id) + sizeof(hash) + 2];
 
+		if (ast_strlen_zero(contact_uri)) {
+			continue;
+		}
+
+		if (ast_sip_validate_uri_length(contact_uri)) {
+			ast_log(LOG_ERROR, "Contact uri or hostname length exceeds pjproject limit: %s\n", 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);
@@ -447,7 +524,7 @@ int ast_sip_for_each_aor(const char *aors, ao2_callback_fn on_aor, void *arg)
 	}
 
 	copy = ast_strdupa(aors);
-	while ((name = strsep(&copy, ","))) {
+	while ((name = ast_strip(strsep(&copy, ",")))) {
 		RAII_VAR(struct ast_sip_aor *, aor,
 			 ast_sip_location_retrieve_aor(name), ao2_cleanup);
 
@@ -459,7 +536,6 @@ int ast_sip_for_each_aor(const char *aors, ao2_callback_fn on_aor, void *arg)
 			return -1;
 		}
 	}
-	ast_free(copy);
 	return 0;
 }
 
@@ -958,6 +1034,10 @@ int ast_sip_initialize_sorcery_location(void)
 	struct ast_sorcery *sorcery = ast_sip_get_sorcery();
 	int i;
 
+	ast_pjproject_get_buildopt("PJ_MAX_HOSTNAME", "%d", &pj_max_hostname);
+	/* As of pjproject 2.4.5, PJSIP_MAX_URL_SIZE isn't exposed yet but we try anyway. */
+	ast_pjproject_get_buildopt("PJSIP_MAX_URL_SIZE", "%d", &pjsip_max_url_size);
+
 	ast_sorcery_apply_default(sorcery, "contact", "astdb", "registrar");
 	ast_sorcery_apply_default(sorcery, "aor", "config", "pjsip.conf,criteria=type=aor");
 
diff --git a/res/res_pjsip/pjsip_configuration.c b/res/res_pjsip/pjsip_configuration.c
index 746a457..ebd6212 100644
--- a/res/res_pjsip/pjsip_configuration.c
+++ b/res/res_pjsip/pjsip_configuration.c
@@ -21,6 +21,7 @@
 #include "asterisk/callerid.h"
 #include "asterisk/test.h"
 #include "asterisk/statsd.h"
+#include "asterisk/pbx.h"
 
 /*! \brief Number of buckets for persistent endpoint information */
 #define PERSISTENT_BUCKETS 53
@@ -68,6 +69,7 @@ static int persistent_endpoint_update_state(void *obj, void *arg, int flags)
 	struct ao2_iterator i;
 	struct ast_sip_contact *contact;
 	enum ast_endpoint_state state = AST_ENDPOINT_OFFLINE;
+	char *regcontext;
 
 	if (status) {
 		char rtt[32];
@@ -116,16 +118,37 @@ static int persistent_endpoint_update_state(void *obj, void *arg, int flags)
 		return 0;
 	}
 
+	regcontext = ast_sip_get_regcontext();
+
 	if (state == AST_ENDPOINT_ONLINE) {
 		ast_endpoint_set_state(endpoint, AST_ENDPOINT_ONLINE);
 		blob = ast_json_pack("{s: s}", "peer_status", "Reachable");
+
+		if (!ast_strlen_zero(regcontext)) {
+			if (!ast_exists_extension(NULL, regcontext, ast_endpoint_get_resource(endpoint), 1, NULL)) {
+				ast_add_extension(regcontext, 1, ast_endpoint_get_resource(endpoint), 1, NULL, NULL,
+					"Noop", ast_strdup(ast_endpoint_get_resource(endpoint)), ast_free_ptr, "SIP");
+			}
+		}
+
 		ast_verb(1, "Endpoint %s is now Reachable\n", ast_endpoint_get_resource(endpoint));
 	} else {
 		ast_endpoint_set_state(endpoint, AST_ENDPOINT_OFFLINE);
 		blob = ast_json_pack("{s: s}", "peer_status", "Unreachable");
+
+		if (!ast_strlen_zero(regcontext)) {
+			struct pbx_find_info q = { .stacklen = 0 };
+
+			if (pbx_find_extension(NULL, NULL, &q, regcontext, ast_endpoint_get_resource(endpoint), 1, NULL, "", E_MATCH)) {
+				ast_context_remove_extension(regcontext, ast_endpoint_get_resource(endpoint), 1, NULL);
+			}
+		}
+
 		ast_verb(1, "Endpoint %s is now Unreachable\n", ast_endpoint_get_resource(endpoint));
 	}
 
+	ast_free(regcontext);
+
 	ast_endpoint_blob_publish(endpoint, ast_endpoint_state_type(), blob);
 	ast_json_unref(blob);
 	ast_devstate_changed(AST_DEVICE_UNKNOWN, AST_DEVSTATE_CACHABLE, "PJSIP/%s", ast_endpoint_get_resource(endpoint));
@@ -390,7 +413,7 @@ int ast_sip_auth_vector_init(struct ast_sip_auth_vector *auths, const char *valu
 		return -1;
 	}
 
-	while ((val = strsep(&auth_names, ","))) {
+	while ((val = ast_strip(strsep(&auth_names, ",")))) {
 		if (ast_strlen_zero(val)) {
 			continue;
 		}
@@ -457,7 +480,11 @@ static int ident_handler(const struct aco_option *opt, struct ast_variable *var,
 	char *idents = ast_strdupa(var->value);
 	char *val;
 
-	while ((val = strsep(&idents, ","))) {
+	while ((val = ast_strip(strsep(&idents, ",")))) {
+		if (ast_strlen_zero(val)) {
+			continue;
+		}
+
 		if (!strcasecmp(val, "username")) {
 			endpoint->ident_method |= AST_SIP_ENDPOINT_IDENTIFY_BY_USERNAME;
 		} else {
@@ -1850,6 +1877,7 @@ int ast_res_pjsip_initialize_configuration(const struct ast_module_info *ast_mod
 	ast_sorcery_object_field_register_custom(sip_sorcery, "endpoint", "outbound_auth", "", outbound_auth_handler, outbound_auths_to_str, NULL, 0, 0);
 	ast_sorcery_object_field_register(sip_sorcery, "endpoint", "aors", "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct ast_sip_endpoint, aors));
 	ast_sorcery_object_field_register(sip_sorcery, "endpoint", "media_address", "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct ast_sip_endpoint, media.address));
+	ast_sorcery_object_field_register(sip_sorcery, "endpoint", "bind_rtp_to_media_address", "no", OPT_BOOL_T, 1, STRFLDSET(struct ast_sip_endpoint, media.bind_rtp_to_media_address));
 	ast_sorcery_object_field_register_custom(sip_sorcery, "endpoint", "identify_by", "username", ident_handler, ident_to_str, NULL, 0, 0);
 	ast_sorcery_object_field_register(sip_sorcery, "endpoint", "direct_media", "yes", OPT_BOOL_T, 1, FLDSET(struct ast_sip_endpoint, media.direct_media.enabled));
 	ast_sorcery_object_field_register_custom(sip_sorcery, "endpoint", "direct_media_method", "invite", direct_media_method_handler, direct_media_method_to_str, NULL, 0, 0);
@@ -2002,18 +2030,24 @@ int ast_res_pjsip_initialize_configuration(const struct ast_module_info *ast_mod
 
 void ast_res_pjsip_destroy_configuration(void)
 {
+	if (!sip_sorcery) {
+		return;
+	}
+
 	ast_sorcery_observer_remove(sip_sorcery, CONTACT_STATUS, &state_contact_status_observer);
 	ast_sorcery_observer_remove(sip_sorcery, "contact", &state_contact_observer);
 	ast_sip_destroy_sorcery_global();
 	ast_sip_destroy_sorcery_location();
 	ast_sip_destroy_sorcery_auth();
 	ast_sip_destroy_sorcery_transport();
+	ast_sorcery_unref(sip_sorcery);
+	sip_sorcery = NULL;
 	ast_manager_unregister(AMI_SHOW_ENDPOINT);
 	ast_manager_unregister(AMI_SHOW_ENDPOINTS);
 	ast_cli_unregister_multiple(cli_commands, ARRAY_LEN(cli_commands));
 	ast_sip_unregister_cli_formatter(endpoint_formatter);
 	ast_sip_unregister_cli_formatter(channel_formatter);
-	ast_sorcery_unref(sip_sorcery);
+	ast_sip_destroy_cli();
 	ao2_cleanup(persistent_endpoints);
 }
 
diff --git a/res/res_pjsip/pjsip_distributor.c b/res/res_pjsip/pjsip_distributor.c
index 0e0e90f..834ca10 100644
--- a/res/res_pjsip/pjsip_distributor.c
+++ b/res/res_pjsip/pjsip_distributor.c
@@ -105,6 +105,10 @@ static struct ast_taskprocessor *find_request_serializer(pjsip_rx_data *rdata)
 		serializer_name = tsx->last_tx->mod_data[distributor_mod.id];
 		if (!ast_strlen_zero(serializer_name)) {
 			serializer = ast_taskprocessor_get(serializer_name, TPS_REF_IF_EXISTS);
+			if (serializer) {
+				ast_debug(3, "Found serializer %s on transaction %s\n",
+						serializer_name, tsx->obj_name);
+			}
 		}
 	}
 
@@ -253,27 +257,34 @@ static pj_bool_t distributor(pjsip_rx_data *rdata)
 	pjsip_dialog *dlg = find_dialog(rdata);
 	struct distributor_dialog_data *dist = NULL;
 	struct ast_taskprocessor *serializer = NULL;
-	struct ast_taskprocessor *req_serializer = NULL;
 	pjsip_rx_data *clone;
 
 	if (dlg) {
+		ast_debug(3, "Searching for serializer on dialog %s for %s\n",
+				dlg->obj_name, rdata->msg_info.info);
 		dist = pjsip_dlg_get_mod_data(dlg, distributor_mod.id);
 		if (dist) {
-			serializer = dist->serializer;
+			serializer = ao2_bump(dist->serializer);
+			if (serializer) {
+				ast_debug(3, "Found serializer %s on dialog %s\n",
+						ast_taskprocessor_name(serializer), dlg->obj_name);
+			}
 		}
+		pjsip_dlg_dec_lock(dlg);
 	}
 
 	if (serializer) {
 		/* We have a serializer so we know where to send the message. */
 	} else if (rdata->msg_info.msg->type == PJSIP_RESPONSE_MSG) {
-		req_serializer = find_request_serializer(rdata);
-		serializer = req_serializer;
+		ast_debug(3, "No dialog serializer for response %s. Using request transaction as basis\n",
+				rdata->msg_info.info);
+		serializer = find_request_serializer(rdata);
 	} else if (!pjsip_method_cmp(&rdata->msg_info.msg->line.req.method, &pjsip_cancel_method)
 		|| !pjsip_method_cmp(&rdata->msg_info.msg->line.req.method, &pjsip_bye_method)) {
 		/* We have a BYE or CANCEL request without a serializer. */
 		pjsip_endpt_respond_stateless(ast_sip_get_pjsip_endpoint(), rdata,
 			PJSIP_SC_CALL_TSX_DOES_NOT_EXIST, NULL, NULL, NULL);
-		goto end;
+		return PJ_TRUE;
 	}
 
 	pjsip_rx_data_clone(rdata, 0, &clone);
@@ -296,11 +307,7 @@ static pj_bool_t distributor(pjsip_rx_data *rdata)
 		ast_sip_push_task(serializer, distribute, clone);
 	}
 
-end:
-	if (dlg) {
-		pjsip_dlg_dec_lock(dlg);
-	}
-	ast_taskprocessor_unreference(req_serializer);
+	ast_taskprocessor_unreference(serializer);
 
 	return PJ_TRUE;
 }
diff --git a/res/res_pjsip/pjsip_options.c b/res/res_pjsip/pjsip_options.c
index 8b75a5f..de551dc 100644
--- a/res/res_pjsip/pjsip_options.c
+++ b/res/res_pjsip/pjsip_options.c
@@ -105,6 +105,8 @@ static void *contact_status_alloc(const char *name)
 	return status;
 }
 
+AST_MUTEX_DEFINE_STATIC(creation_lock);
+
 /*!
  * \brief Retrieve a ast_sip_contact_status object from sorcery creating
  *        one if not found.
@@ -112,6 +114,7 @@ static void *contact_status_alloc(const char *name)
 struct ast_sip_contact_status *ast_res_pjsip_find_or_create_contact_status(const struct ast_sip_contact *contact)
 {
 	struct ast_sip_contact_status *status;
+	SCOPED_MUTEX(lock, &creation_lock);
 
 	status = ast_sorcery_retrieve_by_id(ast_sip_get_sorcery(), CONTACT_STATUS,
 		ast_sorcery_object_get_id(contact));
@@ -278,7 +281,7 @@ static int on_endpoint(void *obj, void *arg, int flags)
 	}
 
 	aors = ast_strdupa(endpoint->aors);
-	while ((aor_name = strsep(&aors, ","))) {
+	while ((aor_name = ast_strip(strsep(&aors, ",")))) {
 		struct ast_sip_aor *aor;
 		struct ao2_container *contacts;
 
@@ -803,7 +806,7 @@ static int cli_qualify_contacts(void *data)
 	}
 
 	aors = ast_strdupa(endpoint->aors);
-	while ((aor_name = strsep(&aors, ","))) {
+	while ((aor_name = ast_strip(strsep(&aors, ",")))) {
 		struct ast_sip_aor *aor;
 		struct ao2_container *contacts;
 
@@ -907,7 +910,7 @@ static int ami_sip_qualify(struct mansession *s, const struct message *m)
 	}
 
 	aors = ast_strdupa(endpoint->aors);
-	while ((aor_name = strsep(&aors, ","))) {
+	while ((aor_name = ast_strip(strsep(&aors, ",")))) {
 		struct ast_sip_aor *aor;
 		struct ao2_container *contacts;
 
@@ -1095,7 +1098,7 @@ static int qualify_and_schedule_all_cb(void *obj, void *arg, int flags)
 	}
 
 	aors = ast_strdupa(endpoint->aors);
-	while ((aor_name = strsep(&aors, ","))) {
+	while ((aor_name = ast_strip(strsep(&aors, ",")))) {
 		struct ast_sip_aor *aor;
 		struct ao2_container *contacts;
 
diff --git a/res/res_pjsip/presence_xml.c b/res/res_pjsip/presence_xml.c
index b98ea02..c991a0d 100644
--- a/res/res_pjsip/presence_xml.c
+++ b/res/res_pjsip/presence_xml.c
@@ -91,6 +91,12 @@ void ast_sip_presence_exten_state_to_str(int state, char **statestring, char **p
 		*pidfstate = "busy";
 		*pidfnote = "Ringing";
 		break;
+	case (AST_EXTENSION_INUSE | AST_EXTENSION_RINGING):
+		*statestring = "confirmed";
+		*local_state = NOTIFY_INUSE;
+		*pidfstate = "busy";
+		*pidfnote = "Ringing";
+		break;
 	case AST_EXTENSION_INUSE:
 		*statestring = "confirmed";
 		*local_state = NOTIFY_INUSE;
@@ -121,7 +127,7 @@ void ast_sip_presence_exten_state_to_str(int state, char **statestring, char **p
 		*statestring = "terminated";
 		*local_state = NOTIFY_OPEN;
 		*pidfstate = "--";
-		*pidfnote ="Ready";
+		*pidfnote = "Ready";
 		break;
 	}
 }
diff --git a/res/res_pjsip_caller_id.c b/res/res_pjsip_caller_id.c
index f1908a7..9af2a8a 100644
--- a/res/res_pjsip_caller_id.c
+++ b/res/res_pjsip_caller_id.c
@@ -398,49 +398,18 @@ static void caller_id_incoming_response(struct ast_sip_session *session, pjsip_r
 
 /*!
  * \internal
- * \brief Set name and number information on an identity header.
- * \param pool Memory pool to use for string duplication
- * \param id_hdr A From, P-Asserted-Identity, or Remote-Party-ID header to modify
- * \param id The identity information to apply to the header
- */
-static void modify_id_header(pj_pool_t *pool, pjsip_fromto_hdr *id_hdr, const struct ast_party_id *id)
-{
-	pjsip_name_addr *id_name_addr;
-	pjsip_sip_uri *id_uri;
-
-	id_name_addr = (pjsip_name_addr *) id_hdr->uri;
-	id_uri = pjsip_uri_get_uri(id_name_addr->uri);
-
-	if (id->name.valid) {
-		int name_buf_len = strlen(id->name.str) * 2 + 1;
-		char *name_buf = ast_alloca(name_buf_len);
-
-		ast_escape_quoted(id->name.str, name_buf, name_buf_len);
-		pj_strdup2(pool, &id_name_addr->display, name_buf);
-	}
-
-	if (id->number.valid) {
-		pj_strdup2(pool, &id_uri->user, id->number.str);
-	}
-}
-
-/*!
- * \internal
  * \brief Create an identity header for an outgoing message
  * \param hdr_name The name of the header to create
  * \param tdata The message to place the header on
  * \param id The identification information for the new header
  * \return newly-created header
  */
-static pjsip_fromto_hdr *create_new_id_hdr(const pj_str_t *hdr_name, pjsip_tx_data *tdata, const struct ast_party_id *id)
+static pjsip_fromto_hdr *create_new_id_hdr(const pj_str_t *hdr_name, pjsip_fromto_hdr *base, pjsip_tx_data *tdata, const struct ast_party_id *id)
 {
 	pjsip_fromto_hdr *id_hdr;
-	pjsip_fromto_hdr *base;
 	pjsip_name_addr *id_name_addr;
 	pjsip_sip_uri *id_uri;
 
-	base = tdata->msg->type == PJSIP_REQUEST_MSG ? PJSIP_MSG_FROM_HDR(tdata->msg) :
-		PJSIP_MSG_TO_HDR(tdata->msg);
 	id_hdr = pjsip_from_hdr_create(tdata->pool);
 	id_hdr->type = PJSIP_H_OTHER;
 	pj_strdup(tdata->pool, &id_hdr->name, hdr_name);
@@ -500,9 +469,10 @@ static void add_privacy_header(pjsip_tx_data *tdata, const struct ast_party_id *
  * \param tdata The message to add the header to
  * \param id The identification information used to populate the header
  */
-static void add_pai_header(pjsip_tx_data *tdata, const struct ast_party_id *id)
+static void add_pai_header(const struct ast_sip_session *session, pjsip_tx_data *tdata, const struct ast_party_id *id)
 {
 	static const pj_str_t pj_pai_name = { "P-Asserted-Identity", 19 };
+	pjsip_fromto_hdr *base;
 	pjsip_fromto_hdr *pai_hdr;
 	pjsip_fromto_hdr *old_pai;
 
@@ -511,12 +481,28 @@ static void add_pai_header(pjsip_tx_data *tdata, const struct ast_party_id *id)
 	 */
 	old_pai = pjsip_msg_find_hdr_by_name(tdata->msg, &pj_pai_name, NULL);
 	if (old_pai) {
-		modify_id_header(tdata->pool, old_pai, id);
-		add_privacy_header(tdata, id);
-		return;
+		/* If type is OTHER, then the existing header was most likely
+		 * added by the PJSIP_HEADER dial plan function as a simple
+		 * name/value pair.  We can't pass this to modify_id_header because
+		 * there are no virtual functions to get the uri.  We could parse
+		 * it into a pjsip_fromto_hdr but it isn't worth it since
+		 * modify_id_header is just going to overwrite the name and number
+		 * anyway.  We'll just remove it from the header list instead
+		 * and create a new one.
+		 */
+		if (old_pai->type == PJSIP_H_OTHER) {
+			pj_list_erase(old_pai);
+		} else {
+			ast_sip_modify_id_header(tdata->pool, old_pai, id);
+			add_privacy_header(tdata, id);
+			return;
+		}
 	}
 
-	pai_hdr = create_new_id_hdr(&pj_pai_name, tdata, id);
+	base = tdata->msg->type == PJSIP_REQUEST_MSG ? session->saved_from_hdr :
+		PJSIP_MSG_TO_HDR(tdata->msg);
+
+	pai_hdr = create_new_id_hdr(&pj_pai_name, base, tdata, id);
 	if (!pai_hdr) {
 		return;
 	}
@@ -589,9 +575,10 @@ static void add_privacy_params(pjsip_tx_data *tdata, pjsip_fromto_hdr *hdr, cons
  * \param tdata The message to add the header to
  * \param id The identification information used to populate the header
  */
-static void add_rpid_header(pjsip_tx_data *tdata, const struct ast_party_id *id)
+static void add_rpid_header(const struct ast_sip_session *session, pjsip_tx_data *tdata, const struct ast_party_id *id)
 {
 	static const pj_str_t pj_rpid_name = { "Remote-Party-ID", 15 };
+	pjsip_fromto_hdr *base;
 	pjsip_fromto_hdr *rpid_hdr;
 	pjsip_fromto_hdr *old_rpid;
 
@@ -600,12 +587,28 @@ static void add_rpid_header(pjsip_tx_data *tdata, const struct ast_party_id *id)
 	 */
 	old_rpid = pjsip_msg_find_hdr_by_name(tdata->msg, &pj_rpid_name, NULL);
 	if (old_rpid) {
-		modify_id_header(tdata->pool, old_rpid, id);
-		add_privacy_params(tdata, old_rpid, id);
-		return;
+		/* If type is OTHER, then the existing header was most likely
+		 * added by the PJSIP_HEADER dial plan function as a simple
+		 * name/value pair.  We can't pass this to modify_id_header because
+		 * there are no virtual functions to get the uri.  We could parse
+		 * it into a pjsip_fromto_hdr but it isn't worth it since
+		 * modify_id_header is just going to overwrite the name and number
+		 * anyway.  We'll just remove it from the header list instead
+		 * and create a new one.
+		 */
+		if (old_rpid->type == PJSIP_H_OTHER) {
+			pj_list_erase(old_rpid);
+		} else {
+			ast_sip_modify_id_header(tdata->pool, old_rpid, id);
+			add_privacy_params(tdata, old_rpid, id);
+			return;
+		}
 	}
 
-	rpid_hdr = create_new_id_hdr(&pj_rpid_name, tdata, id);
+	base = tdata->msg->type == PJSIP_REQUEST_MSG ? session->saved_from_hdr :
+		PJSIP_MSG_TO_HDR(tdata->msg);
+
+	rpid_hdr = create_new_id_hdr(&pj_rpid_name, base, tdata, id);
 	if (!rpid_hdr) {
 		return;
 	}
@@ -632,10 +635,10 @@ static void add_id_headers(const struct ast_sip_session *session, pjsip_tx_data
 		return;
 	}
 	if (session->endpoint->id.send_pai) {
-		add_pai_header(tdata, id);
+		add_pai_header(session, tdata, id);
 	}
 	if (session->endpoint->id.send_rpid) {
-		add_rpid_header(tdata, id);
+		add_rpid_header(session, tdata, id);
 	}
 }
 
@@ -643,10 +646,9 @@ static void add_id_headers(const struct ast_sip_session *session, pjsip_tx_data
  * \internal
  * \brief Session supplement callback for outgoing INVITE requests
  *
- * For an initial INVITE request, we may change the From header to appropriately
- * reflect the identity information. On all INVITEs (initial and reinvite) we may
- * add other identity headers such as P-Asserted-Identity and Remote-Party-ID based
- * on configuration and privacy settings
+ * On all INVITEs (initial and reinvite) we may add other identity headers
+ * such as P-Asserted-Identity and Remote-Party-ID based on configuration
+ * and privacy settings
  *
  * \param session The session on which the INVITE will be sent
  * \param tdata The outbound INVITE request
@@ -660,33 +662,12 @@ static void caller_id_outgoing_request(struct ast_sip_session *session, pjsip_tx
 		return;
 	}
 
-	/* Must do a deep copy unless we hold the channel lock the entire time. */
 	ast_party_id_init(&connected_id);
 	ast_channel_lock(session->channel);
 	effective_id = ast_channel_connected_effective_id(session->channel);
 	ast_party_id_copy(&connected_id, &effective_id);
 	ast_channel_unlock(session->channel);
 
-	if (session->inv_session->state < PJSIP_INV_STATE_CONFIRMED) {
-		/* Only change the From header on the initial outbound INVITE. Switching it
-		 * mid-call might confuse some UAs.
-		 */
-		pjsip_fromto_hdr *from;
-		pjsip_dialog *dlg;
-
-		from = pjsip_msg_find_hdr(tdata->msg, PJSIP_H_FROM, tdata->msg->hdr.next);
-		dlg = session->inv_session->dlg;
-
-		if (ast_strlen_zero(session->endpoint->fromuser)
-			&& (session->endpoint->id.trust_outbound
-				|| (ast_party_id_presentation(&connected_id) & AST_PRES_RESTRICTION) == AST_PRES_ALLOWED)) {
-			modify_id_header(tdata->pool, from, &connected_id);
-			modify_id_header(dlg->pool, dlg->local.info, &connected_id);
-		}
-
-		ast_sip_add_usereqphone(session->endpoint, tdata->pool, from->uri);
-		ast_sip_add_usereqphone(session->endpoint, dlg->pool, dlg->local.info->uri);
-	}
 	add_id_headers(session, tdata, &connected_id);
 	ast_party_id_free(&connected_id);
 }
diff --git a/res/res_pjsip_config_wizard.c b/res/res_pjsip_config_wizard.c
index 39d3c3f..e263437 100644
--- a/res/res_pjsip_config_wizard.c
+++ b/res/res_pjsip_config_wizard.c
@@ -45,6 +45,7 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
 #include <pjsip.h>
 
 #include "asterisk/astobj2.h"
+#include "asterisk/cli.h"
 #include "asterisk/res_pjsip.h"
 #include "asterisk/module.h"
 #include "asterisk/pbx.h"
@@ -276,7 +277,7 @@ struct object_type_wizard {
 	struct ast_config *last_config;
 	char object_type[];
 };
-static AST_VECTOR(object_type_wizards, struct object_type_wizard *) object_type_wizards;
+static AST_VECTOR_RW(object_type_wizards, struct object_type_wizard *) object_type_wizards;
 
 /*! \brief Callbacks for vector deletes */
 #define NOT_EQUALS(a, b) (a != b)
@@ -304,12 +305,15 @@ static struct object_type_wizard *find_wizard(const char *object_type)
 {
 	int idx;
 
+	AST_VECTOR_RW_RDLOCK(&object_type_wizards);
 	for(idx = 0; idx < AST_VECTOR_SIZE(&object_type_wizards); idx++) {
 		struct object_type_wizard *otw = AST_VECTOR_GET(&object_type_wizards, idx);
 		if (!strcmp(otw->object_type, object_type)) {
+			AST_VECTOR_RW_UNLOCK(&object_type_wizards);
 			return otw;
 		}
 	}
+	AST_VECTOR_RW_UNLOCK(&object_type_wizards);
 
 	return NULL;
 }
@@ -1137,7 +1141,9 @@ static void wizard_mapped_observer(const char *name, struct ast_sorcery *sorcery
 		otw->wizard_data = wizard_data;
 		otw->last_config = NULL;
 		strcpy(otw->object_type, object_type); /* Safe */
+		AST_VECTOR_RW_WRLOCK(&object_type_wizards);
 		AST_VECTOR_APPEND(&object_type_wizards, otw);
+		AST_VECTOR_RW_UNLOCK(&object_type_wizards);
 		ast_debug(1, "Wizard mapped for object_type '%s'\n", object_type);
 	}
 }
@@ -1177,19 +1183,118 @@ static void instance_destroying_observer(const char *name, struct ast_sorcery *s
 	ast_module_unref(ast_module_info->self);
 }
 
+static char *handle_export_primitives(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+	struct ast_sorcery *sorcery;
+	int idx;
+	FILE *f = NULL;
+	const char *fn = NULL;
+
+	switch (cmd) {
+	case CLI_INIT:
+		e->command = "pjsip export config_wizard primitives [to]";
+		e->usage =
+			"Usage: pjsip export config_wizard primitives [ to <filename ]\n"
+			"       Export the config_wizard objects as pjsip primitives to\n"
+			"       the console or to <filename>\n";
+		return NULL;
+	case CLI_GENERATE:
+		return NULL;
+	}
+
+	if (a->argc > 5) {
+		char date[256]="";
+		time_t t;
+		fn = a->argv[5];
+
+		time(&t);
+		ast_copy_string(date, ctime(&t), sizeof(date));
+		f = fopen(fn, "w");
+		if (!f) {
+			ast_log(LOG_ERROR, "Unable to write %s (%s)\n", fn, strerror(errno));
+			return CLI_FAILURE;
+		}
+
+		fprintf(f, ";!\n");
+		fprintf(f, ";! Automatically generated configuration file\n");
+		fprintf(f, ";! Filename: %s\n", fn);
+		fprintf(f, ";! Generator: %s\n", "'pjsip export config_wizard primitives'");
+		fprintf(f, ";! Creation Date: %s", date);
+		fprintf(f, ";!\n");
+	}
+
+	sorcery = ast_sip_get_sorcery();
+
+	AST_VECTOR_RW_RDLOCK(&object_type_wizards);
+	for(idx = 0; idx < AST_VECTOR_SIZE(&object_type_wizards); idx++) {
+		struct object_type_wizard *otw = AST_VECTOR_GET(&object_type_wizards, idx);
+		struct ao2_container *container;
+		struct ao2_iterator i;
+		void *o;
+
+		container = ast_sorcery_retrieve_by_fields(sorcery, otw->object_type, AST_RETRIEVE_FLAG_MULTIPLE, NULL);
+		if (!container) {
+			continue;
+		}
+
+		i = ao2_iterator_init(container, 0);
+		while ((o = ao2_iterator_next(&i))) {
+			struct ast_variable *vars;
+			struct ast_variable *v;
+
+			vars = ast_sorcery_objectset_create(sorcery, o);
+			if (vars && ast_variable_find_in_list(vars, "@pjsip_wizard")) {
+				if (f) {
+					fprintf(f, "\n[%s]\ntype = %s\n", ast_sorcery_object_get_id(o), otw->object_type);
+				} else {
+					ast_cli(a->fd, "\n[%s]\ntype = %s\n", ast_sorcery_object_get_id(o), otw->object_type);
+				}
+				for (v = vars; v; v = v->next) {
+					if (!ast_strlen_zero(v->value)) {
+						if (f) {
+							fprintf(f, "%s = %s\n", v->name, v->value);
+						} else {
+							ast_cli(a->fd, "%s = %s\n", v->name, v->value);
+						}
+					}
+				}
+			}
+			ast_variables_destroy(vars);
+			ao2_ref(o, -1);
+		}
+		ao2_iterator_destroy(&i);
+		ao2_cleanup(container);
+	}
+	AST_VECTOR_RW_UNLOCK(&object_type_wizards);
+
+	if (f) {
+		fclose(f);
+		ast_cli(a->fd, "Wrote configuration to %s\n", fn);
+	}
+
+
+	return CLI_SUCCESS;
+}
+
+static struct ast_cli_entry config_wizard_cli[] = {
+	AST_CLI_DEFINE(handle_export_primitives, "Export config wizard primitives"),
+};
+
 static int load_module(void)
 {
-	AST_VECTOR_INIT(&object_type_wizards, 12);
+	AST_VECTOR_RW_INIT(&object_type_wizards, 12);
 	ast_sorcery_global_observer_add(&global_observer);
+	ast_cli_register_multiple(config_wizard_cli, ARRAY_LEN(config_wizard_cli));
 
 	return AST_MODULE_LOAD_SUCCESS;
 }
 
 static int unload_module(void)
 {
+	ast_cli_unregister_multiple(config_wizard_cli, ARRAY_LEN(config_wizard_cli));
 	ast_sorcery_global_observer_remove(&global_observer);
 	AST_VECTOR_REMOVE_CMP_UNORDERED(&object_type_wizards, NULL, NOT_EQUALS, OTW_DELETE_CB);
-	AST_VECTOR_FREE(&object_type_wizards);
+	AST_VECTOR_RW_FREE(&object_type_wizards);
 
 	return 0;
 }
diff --git a/res/res_pjsip_diversion.c b/res/res_pjsip_diversion.c
index 4d9aca4..41e6c82 100644
--- a/res/res_pjsip_diversion.c
+++ b/res/res_pjsip_diversion.c
@@ -37,6 +37,39 @@
 
 static const pj_str_t diversion_name = { "Diversion", 9 };
 
+/*!
+ * \internal
+ * \brief Determine if the given string is a SIP token.
+ * \since 13.8.0
+ *
+ * \param str String to determine if is a SIP token.
+ *
+ * \note A token is defined by RFC3261 Section 25.1
+ *
+ * \return Non-zero if the string is a SIP token.
+ */
+static int sip_is_token(const char *str)
+{
+	int is_token;
+
+	if (ast_strlen_zero(str)) {
+		/* An empty string is not a token. */
+		return 0;
+	}
+
+	is_token = 1;
+	do {
+		if (!isalnum(*str)
+			&& !strchr("-.!%*_+`'~", *str)) {
+			/* The character is not allowed in a token. */
+			is_token = 0;
+			break;
+		}
+	} while (*++str);
+
+	return is_token;
+}
+
 /*! \brief Diversion header reasons
  *
  * The core defines a bunch of constants used to define
@@ -46,7 +79,7 @@ static const pj_str_t diversion_name = { "Diversion", 9 };
  */
 static const struct reasons {
 	enum AST_REDIRECTING_REASON code;
-	char *const text;
+	const char *text;
 } reason_table[] = {
 	{ AST_REDIRECTING_REASON_UNKNOWN, "unknown" },
 	{ AST_REDIRECTING_REASON_USER_BUSY, "user-busy" },
@@ -59,39 +92,28 @@ static const struct reasons {
 	{ AST_REDIRECTING_REASON_FOLLOW_ME, "follow-me" },
 	{ AST_REDIRECTING_REASON_OUT_OF_ORDER, "out-of-service" },
 	{ AST_REDIRECTING_REASON_AWAY, "away" },
-	{ AST_REDIRECTING_REASON_CALL_FWD_DTE, "unknown"},
-	{ AST_REDIRECTING_REASON_SEND_TO_VM, "send_to_vm"},
+	{ AST_REDIRECTING_REASON_CALL_FWD_DTE, "cf_dte" },		/* Non-standard */
+	{ AST_REDIRECTING_REASON_SEND_TO_VM, "send_to_vm" },	/* Non-standard */
 };
 
 static const char *reason_code_to_str(const struct ast_party_redirecting_reason *reason)
 {
-	int code = reason->code;
+	int idx;
+	int code;
 
 	/* use specific string if given */
 	if (!ast_strlen_zero(reason->str)) {
 		return reason->str;
 	}
 
-	if (code >= 0 && code < ARRAY_LEN(reason_table)) {
-		return reason_table[code].text;
-	}
-
-	return "unknown";
-}
-
-static enum AST_REDIRECTING_REASON reason_str_to_code(const char *text)
-{
-	enum AST_REDIRECTING_REASON code = AST_REDIRECTING_REASON_UNKNOWN;
-	int i;
-
-	for (i = 0; i < ARRAY_LEN(reason_table); ++i) {
-		if (!strcasecmp(text, reason_table[i].text)) {
-			code = reason_table[i].code;
-			break;
+	code = reason->code;
+	for (idx = 0; idx < ARRAY_LEN(reason_table); ++idx) {
+		if (code == reason_table[idx].code) {
+			return reason_table[idx].text;
 		}
 	}
 
-	return code;
+	return "unknown";
 }
 
 static pjsip_fromto_hdr *get_diversion_header(pjsip_rx_data *rdata)
@@ -159,13 +181,31 @@ static void set_redirecting_reason(pjsip_fromto_hdr *hdr,
 {
 	static const pj_str_t reason_name = { "reason", 6 };
 	pjsip_param *reason = pjsip_param_find(&hdr->other_param, &reason_name);
+	char *reason_str;
 
 	if (!reason) {
 		return;
 	}
 
 	set_redirecting_value(&data->str, &reason->value);
-	data->code = reason_str_to_code(data->str);
+	if (!data->str) {
+		/* Oops, allocation failure */
+		return;
+	}
+	reason_str = ast_strdupa(data->str);
+
+	/* Remove any enclosing double-quotes */
+	if (*reason_str == '"') {
+		reason_str = ast_strip_quoted(reason_str, "\"", "\"");
+	}
+
+	data->code = ast_redirecting_reason_parse(reason_str);
+	if (data->code < 0) {
+		data->code = AST_REDIRECTING_REASON_UNKNOWN;
+	} else {
+		ast_free(data->str);
+		data->str = ast_strdup("");
+	}
 }
 
 static void set_redirecting(struct ast_sip_session *session,
@@ -251,6 +291,9 @@ static void add_diversion_header(pjsip_tx_data *tdata, struct ast_party_redirect
 	pjsip_sip_uri *uri;
 	pjsip_param *param;
 	pjsip_fromto_hdr *old_hdr;
+	const char *reason_str;
+	const char *quote_str;
+	char *reason_buf;
 
 	struct ast_party_id *id = &data->from;
 	pjsip_uri *base = PJSIP_MSG_FROM_HDR(tdata->msg)->uri;
@@ -272,7 +315,17 @@ static void add_diversion_header(pjsip_tx_data *tdata, struct ast_party_redirect
 
 	param = PJ_POOL_ALLOC_T(tdata->pool, pjsip_param);
 	param->name = pj_str("reason");
-	param->value = pj_str((char*)reason_code_to_str(&data->reason));
+
+	reason_str = reason_code_to_str(&data->reason);
+
+	/* Reason is either already quoted or it is a token to not need quotes added. */
+	quote_str = *reason_str == '\"' || sip_is_token(reason_str) ? "" : "\"";
+
+	reason_buf = pj_pool_alloc(tdata->pool, strlen(reason_str) + 3);
+	sprintf(reason_buf, "%s%s%s", quote_str, reason_str, quote_str);/* Safe */
+
+	param->value = pj_str(reason_buf);
+
 	pj_list_insert_before(&hdr->other_param, param);
 
 	hdr->uri = (pjsip_uri *) name_addr;
diff --git a/res/res_pjsip_dtmf_info.c b/res/res_pjsip_dtmf_info.c
index 7b52250..ede515d 100644
--- a/res/res_pjsip_dtmf_info.c
+++ b/res/res_pjsip_dtmf_info.c
@@ -82,14 +82,13 @@ static char get_event(const char *c)
 static int dtmf_info_incoming_request(struct ast_sip_session *session, struct pjsip_rx_data *rdata)
 {
 	pjsip_msg_body *body = rdata->msg_info.msg->body;
-	char buf[body ? body->len : 0];
+	char buf[body ? body->len + 1 : 1];
 	char *cur = buf;
 	char *line;
-
 	char event = '\0';
 	unsigned int duration = 100;
-
 	char is_dtmf;
+	int res;
 
 	if (!session->channel) {
 		return 0;
@@ -107,7 +106,12 @@ static int dtmf_info_incoming_request(struct ast_sip_session *session, struct pj
 		return 0;
 	}
 
-	body->print_body(body, buf, body->len);
+	res = body->print_body(body, buf, body->len);
+	if (res < 0) {
+		send_response(session, rdata, 500);
+		return 0;
+	}
+	buf[res] = '\0';
 
 	if (is_dtmf) {
 		/* directly use what is in the message body */
diff --git a/res/res_pjsip_endpoint_identifier_anonymous.c b/res/res_pjsip_endpoint_identifier_anonymous.c
index 274c055..b39c863 100644
--- a/res/res_pjsip_endpoint_identifier_anonymous.c
+++ b/res/res_pjsip_endpoint_identifier_anonymous.c
@@ -42,14 +42,14 @@ static int get_endpoint_details(pjsip_rx_data *rdata, char *domain, size_t domai
 	return 0;
 }
 
-static int find_transport_in_use(void *obj, void *arg, int flags)
+static int find_transport_state_in_use(void *obj, void *arg, int flags)
 {
-	struct ast_sip_transport *transport = obj;
+	struct ast_sip_transport_state *transport_state = obj;
 	pjsip_rx_data *rdata = arg;
 
-	if ((transport->state->transport == rdata->tp_info.transport) ||
-		(transport->state->factory && !pj_strcmp(&transport->state->factory->addr_name.host, &rdata->tp_info.transport->local_name.host) &&
-			transport->state->factory->addr_name.port == rdata->tp_info.transport->local_name.port)) {
+	if (transport_state && ((transport_state->transport == rdata->tp_info.transport) ||
+		(transport_state->factory && !pj_strcmp(&transport_state->factory->addr_name.host, &rdata->tp_info.transport->local_name.host) &&
+			transport_state->factory->addr_name.port == rdata->tp_info.transport->local_name.port))) {
 		return CMP_MATCH | CMP_STOP;
 	}
 
@@ -61,7 +61,8 @@ static struct ast_sip_endpoint *anonymous_identify(pjsip_rx_data *rdata)
 	char domain_name[64], id[AST_UUID_STR_LEN];
 	struct ast_sip_endpoint *endpoint;
 	RAII_VAR(struct ast_sip_domain_alias *, alias, NULL, ao2_cleanup);
-	RAII_VAR(struct ao2_container *, transports, NULL, ao2_cleanup);
+	RAII_VAR(struct ao2_container *, transport_states, NULL, ao2_cleanup);
+	RAII_VAR(struct ast_sip_transport_state *, transport_state, NULL, ao2_cleanup);
 	RAII_VAR(struct ast_sip_transport *, transport, NULL, ao2_cleanup);
 
 	if (get_endpoint_details(rdata, domain_name, sizeof(domain_name))) {
@@ -83,9 +84,10 @@ static struct ast_sip_endpoint *anonymous_identify(pjsip_rx_data *rdata)
 	}
 
 	/* See if the transport this came in on has a provided domain */
-	if ((transports = ast_sorcery_retrieve_by_fields(ast_sip_get_sorcery(), "transport", AST_RETRIEVE_FLAG_MULTIPLE | AST_RETRIEVE_FLAG_ALL, NULL)) &&
-		(transport = ao2_callback(transports, 0, find_transport_in_use, rdata)) &&
-		!ast_strlen_zero(transport->domain)) {
+	if ((transport_states = ast_sip_get_transport_states())
+		&& (transport_state = ao2_callback(transport_states, 0, find_transport_state_in_use, rdata))
+		&& (transport = ast_sorcery_retrieve_by_id(ast_sip_get_sorcery(), "transport", transport_state->id))
+		&& !ast_strlen_zero(transport->domain)) {
 		snprintf(id, sizeof(id), "anonymous@%s", transport->domain);
 		if ((endpoint = ast_sorcery_retrieve_by_id(ast_sip_get_sorcery(), "endpoint", id))) {
 			goto done;
diff --git a/res/res_pjsip_endpoint_identifier_ip.c b/res/res_pjsip_endpoint_identifier_ip.c
index 11559ad..7f4858a 100644
--- a/res/res_pjsip_endpoint_identifier_ip.c
+++ b/res/res_pjsip_endpoint_identifier_ip.c
@@ -164,11 +164,15 @@ static int ip_identify_match_handler(const struct aco_option *opt, struct ast_va
 		return 0;
 	}
 
-	while ((current_string = strsep(&input_string, ","))) {
+	while ((current_string = ast_strip(strsep(&input_string, ",")))) {
 		struct ast_sockaddr *addrs;
 		int num_addrs = 0, error = 0, i;
 		char *mask = strrchr(current_string, '/');
 
+		if (ast_strlen_zero(current_string)) {
+			continue;
+		}
+
 		if (mask) {
 			identify->matches = ast_append_ha("d", current_string, identify->matches, &error);
 
diff --git a/res/res_pjsip_endpoint_identifier_user.c b/res/res_pjsip_endpoint_identifier_user.c
index 5abf879..aa6d398 100644
--- a/res/res_pjsip_endpoint_identifier_user.c
+++ b/res/res_pjsip_endpoint_identifier_user.c
@@ -42,14 +42,14 @@ static int get_endpoint_details(pjsip_rx_data *rdata, char *endpoint, size_t end
 	return 0;
 }
 
-static int find_transport_in_use(void *obj, void *arg, int flags)
+static int find_transport_state_in_use(void *obj, void *arg, int flags)
 {
-	struct ast_sip_transport *transport = obj;
+	struct ast_sip_transport_state *transport_state = obj;
 	pjsip_rx_data *rdata = arg;
 
-	if ((transport->state->transport == rdata->tp_info.transport) ||
-		(transport->state->factory && !pj_strcmp(&transport->state->factory->addr_name.host, &rdata->tp_info.transport->local_name.host) &&
-			transport->state->factory->addr_name.port == rdata->tp_info.transport->local_name.port)) {
+	if (transport_state && ((transport_state->transport == rdata->tp_info.transport) ||
+		(transport_state->factory && !pj_strcmp(&transport_state->factory->addr_name.host, &rdata->tp_info.transport->local_name.host) &&
+			transport_state->factory->addr_name.port == rdata->tp_info.transport->local_name.port))) {
 		return CMP_MATCH | CMP_STOP;
 	}
 
@@ -61,7 +61,8 @@ static struct ast_sip_endpoint *username_identify(pjsip_rx_data *rdata)
 	char endpoint_name[64], domain_name[64], id[AST_UUID_STR_LEN];
 	struct ast_sip_endpoint *endpoint;
 	RAII_VAR(struct ast_sip_domain_alias *, alias, NULL, ao2_cleanup);
-	RAII_VAR(struct ao2_container *, transports, NULL, ao2_cleanup);
+	RAII_VAR(struct ao2_container *, transport_states, NULL, ao2_cleanup);
+	RAII_VAR(struct ast_sip_transport_state *, transport_state, NULL, ao2_cleanup);
 	RAII_VAR(struct ast_sip_transport *, transport, NULL, ao2_cleanup);
 
 	if (get_endpoint_details(rdata, endpoint_name, sizeof(endpoint_name), domain_name, sizeof(domain_name))) {
@@ -83,10 +84,11 @@ static struct ast_sip_endpoint *username_identify(pjsip_rx_data *rdata)
 	}
 
 	/* See if the transport this came in on has a provided domain */
-	if ((transports = ast_sorcery_retrieve_by_fields(ast_sip_get_sorcery(), "transport", AST_RETRIEVE_FLAG_MULTIPLE | AST_RETRIEVE_FLAG_ALL, NULL)) &&
-		(transport = ao2_callback(transports, 0, find_transport_in_use, rdata)) &&
-		!ast_strlen_zero(transport->domain)) {
-		snprintf(id, sizeof(id), "%s@%s", endpoint_name, transport->domain);
+	if ((transport_states = ast_sip_get_transport_states())
+		&& (transport_state = ao2_callback(transport_states, 0, find_transport_state_in_use, rdata))
+		&& (transport = ast_sorcery_retrieve_by_id(ast_sip_get_sorcery(), "transport", transport_state->id))
+		&& !ast_strlen_zero(transport->domain)) {
+		snprintf(id, sizeof(id), "anonymous@%s", transport->domain);
 		if ((endpoint = ast_sorcery_retrieve_by_id(ast_sip_get_sorcery(), "endpoint", id))) {
 			goto done;
 		}
diff --git a/res/res_pjsip_history.c b/res/res_pjsip_history.c
new file mode 100644
index 0000000..ea5f5e8
--- /dev/null
+++ b/res/res_pjsip_history.c
@@ -0,0 +1,1352 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 1999 - 2015, Digium, Inc.
+ *
+ * Matt Jordan <mjordan at 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 PJSIP History
+ *
+ * \author Matt Jordan <mjordan at digium.com>
+ *
+ */
+
+/*** MODULEINFO
+	<depend>pjproject</depend>
+	<depend>res_pjsip</depend>
+	<support_level>extended</support_level>
+ ***/
+
+#include "asterisk.h"
+
+ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
+
+#include <pjsip.h>
+#include <regex.h>
+
+#include "asterisk/res_pjsip.h"
+#include "asterisk/module.h"
+#include "asterisk/logger.h"
+#include "asterisk/cli.h"
+#include "asterisk/netsock2.h"
+#include "asterisk/vector.h"
+#include "asterisk/lock.h"
+
+#define HISTORY_INITIAL_SIZE 256
+
+/*! \brief Pool factory used by pjlib to allocate memory. */
+static pj_caching_pool cachingpool;
+
+/*! \brief Whether or not we are storing history */
+static int enabled;
+
+/*! \brief Packet count */
+static int packet_number;
+
+/*! \brief An item in the history */
+struct pjsip_history_entry {
+	/*! \brief Packet number */
+	int number;
+	/*! \brief Whether or not we transmitted the packet */
+	int transmitted;
+	/*! \brief Time the packet was transmitted/received */
+	struct timeval timestamp;
+	/*! \brief Source address */
+	pj_sockaddr_in src;
+	/*! \brief Destination address */
+	pj_sockaddr_in dst;
+	/*! \brief Memory pool used to allocate \c msg */
+	pj_pool_t *pool;
+	/*! \brief The actual SIP message */
+	pjsip_msg *msg;
+};
+
+/*! \brief Mutex that protects \ref vector_history */
+AST_MUTEX_DEFINE_STATIC(history_lock);
+
+/*! \brief The one and only history that we've captured */
+static AST_VECTOR(vector_history_t, struct pjsip_history_entry *) vector_history;
+
+struct expression_token;
+
+/*! \brief An operator that we understand in an expression */
+struct operator {
+	/*! \brief Our operator's symbol */
+	const char *symbol;
+	/*! \brief Precedence of the symbol */
+	int precedence;
+	/*! \brief Non-zero if the operator is evaluated right-to-left */
+	int right_to_left;
+	/*! \brief Number of operands the operator takes */
+	int operands;
+	/*!
+	 * \brief Evaluation function for unary operators
+	 *
+	 * \param op The operator being evaluated
+	 * \param type The type of value contained in \c operand
+	 * \param operand A pointer to the value to evaluate
+	 *
+	 * \retval -1 error
+	 * \retval 0 evaluation is False
+	 * \retval 1 evaluation is True
+	 */
+	int (* const evaluate_unary)(struct operator *op, enum aco_option_type type, void *operand);
+	/*!
+	 * \brief Evaluation function for binary operators
+	 *
+	 * \param op The operator being evaluated
+	 * \param type The type of value contained in \c op_left
+	 * \param op_left A pointer to the value to evaluate (a result or extracted from an entry)
+	 * \param op_right The expression token containing the other value (a result or user-provided)
+	 *
+	 * \retval -1 error
+	 * \retval 0 evaluation is False
+	 * \retval 1 evaluation is True
+	 */
+	int (* const evaluate)(struct operator *op, enum aco_option_type type, void *op_left, struct expression_token *op_right);
+};
+
+/*! \brief A field that we understand and can perform operations on */
+struct allowed_field {
+	/*! \brief The representation of the field */
+	const char *symbol;
+	/*! \brief The type /c get_field returns */
+	enum aco_option_type return_type;
+	/*!
+	 * \brief Function that returns the field from a pjsip_history_entry
+	 *
+	 * Note that the function must return a pointer to the location in
+	 * \c pjsip_history_entry - no memory should be allocated as the caller
+	 * will not dispose of any
+	 */
+	void *(* const get_field)(struct pjsip_history_entry *entry);
+};
+
+/*! \brief The type of token that has been parsed out of an expression */
+enum expression_token_type {
+	/*! The \c expression_token contains a field */
+	TOKEN_TYPE_FIELD,
+	/*! The \c expression_token contains an operator */
+	TOKEN_TYPE_OPERATOR,
+	/*! The \c expression_token contains a previous result */
+	TOKEN_TYPE_RESULT
+};
+
+/*! \brief A token in the expression or an evaluated part of the expression */
+struct expression_token {
+	/*! \brief The next expression token in the queue */
+	struct expression_token *next;
+	/*! \brief The type of value stored in the expression token */
+	enum expression_token_type token_type;
+	/*! \brief An operator that evaluates expressions */
+	struct operator *op;
+	/*! \brief The result of an evaluated expression */
+	int result;
+	/*! \brief The field in the expression */
+	char field[];
+};
+
+/*!
+ * \brief Operator callback for determining equality
+ */
+static int evaluate_equal(struct operator *op, enum aco_option_type type, void *op_left, struct expression_token *op_right)
+{
+	switch (type) {
+	case OPT_BOOL_T:
+	case OPT_BOOLFLAG_T:
+	case OPT_INT_T:
+	case OPT_UINT_T:
+	{
+		int right;
+
+		if (sscanf(op_right->field, "%30d", &right) != 1) {
+			ast_log(LOG_WARNING, "Unable to extract field '%s': not an integer\n", op_right->field);
+			return -1;
+		}
+		return (*(int *)op_left) == right;
+	}
+	case OPT_DOUBLE_T:
+	{
+		double right;
+
+		if (sscanf(op_right->field, "%lf", &right) != 1) {
+			ast_log(LOG_WARNING, "Unable to extract field '%s': not a double\n", op_right->field);
+			return -1;
+		}
+		return (*(double *)op_left) == right;
+	}
+	case OPT_CHAR_ARRAY_T:
+	case OPT_STRINGFIELD_T:
+		/* In our case, we operate on pj_str_t */
+		return pj_strcmp2(op_left, op_right->field) == 0;
+	case OPT_NOOP_T:
+	/* Used for timeval */
+	{
+		struct timeval right = { 0, };
+
+		if (sscanf(op_right->field, "%ld", &right.tv_sec) != 1) {
+			ast_log(LOG_WARNING, "Unable to extract field '%s': not a timestamp\n", op_right->field);
+			return -1;
+		}
+
+		return ast_tvcmp(*(struct timeval *)op_left, right) == 0;
+	}
+	case OPT_SOCKADDR_T:
+	/* In our case, we operate only on pj_sockaddr_t */
+	{
+		pj_sockaddr right;
+		pj_str_t str_right;
+
+		pj_cstr(&str_right, op_right->field);
+		if (pj_sockaddr_parse(pj_AF_UNSPEC(), 0, &str_right, &right) != PJ_SUCCESS) {
+			ast_log(LOG_WARNING, "Unable to convert field '%s': not an IPv4 or IPv6 address\n", op_right->field);
+			return -1;
+		}
+
+		return pj_sockaddr_cmp(op_left, &right) == 0;
+	}
+	default:
+		ast_log(LOG_WARNING, "Cannot evaluate field '%s': invalid type for operator '%s'\n",
+			op_right->field, op->symbol);
+	}
+
+	return -1;
+}
+
+/*!
+ * \brief Operator callback for determining inequality
+ */
+static int evaluate_not_equal(struct operator *op, enum aco_option_type type, void *op_left, struct expression_token *op_right)
+{
+	return !evaluate_equal(op, type, op_left, op_right);
+}
+
+/*
+ * \brief Operator callback for determining if one operand is less than another
+ */
+static int evaluate_less_than(struct operator *op, enum aco_option_type type, void *op_left, struct expression_token *op_right)
+{
+	switch (type) {
+	case OPT_BOOL_T:
+	case OPT_BOOLFLAG_T:
+	case OPT_INT_T:
+	case OPT_UINT_T:
+	{
+		int right;
+
+		if (sscanf(op_right->field, "%30d", &right) != 1) {
+			ast_log(LOG_WARNING, "Unable to extract field '%s': not an integer\n", op_right->field);
+			return -1;
+		}
+		return (*(int *)op_left) < right;
+	}
+	case OPT_DOUBLE_T:
+	{
+		double right;
+
+		if (sscanf(op_right->field, "%lf", &right) != 1) {
+			ast_log(LOG_WARNING, "Unable to extract field '%s': not a double\n", op_right->field);
+			return -1;
+		}
+		return (*(double *)op_left) < right;
+	}
+	case OPT_NOOP_T:
+	/* Used for timeval */
+	{
+		struct timeval right = { 0, };
+
+		if (sscanf(op_right->field, "%ld", &right.tv_sec) != 1) {
+			ast_log(LOG_WARNING, "Unable to extract field '%s': not a timestamp\n", op_right->field);
+			return -1;
+		}
+
+		return ast_tvcmp(*(struct timeval *)op_left, right) == -1;
+	}
+	default:
+		ast_log(LOG_WARNING, "Cannot evaluate field '%s': invalid type for operator '%s'\n",
+			op_right->field, op->symbol);
+	}
+
+	return -1;
+}
+
+/*
+ * \brief Operator callback for determining if one operand is greater than another
+ */
+static int evaluate_greater_than(struct operator *op, enum aco_option_type type, void *op_left, struct expression_token *op_right)
+{
+	switch (type) {
+	case OPT_BOOL_T:
+	case OPT_BOOLFLAG_T:
+	case OPT_INT_T:
+	case OPT_UINT_T:
+	{
+		int right;
+
+		if (sscanf(op_right->field, "%30d", &right) != 1) {
+			ast_log(LOG_WARNING, "Unable to extract field '%s': not an integer\n", op_right->field);
+			return -1;
+		}
+		return (*(int *)op_left) > right;
+	}
+	case OPT_DOUBLE_T:
+	{
+		double right;
+
+		if (sscanf(op_right->field, "%lf", &right) != 1) {
+			ast_log(LOG_WARNING, "Unable to extract field '%s': not a double\n", op_right->field);
+			return -1;
+		}
+		return (*(double *)op_left) > right;
+	}
+	case OPT_NOOP_T:
+	/* Used for timeval */
+	{
+		struct timeval right = { 0, };
+
+		if (sscanf(op_right->field, "%ld", &right.tv_sec) != 1) {
+			ast_log(LOG_WARNING, "Unable to extract field '%s': not a timestamp\n", op_right->field);
+			return -1;
+		}
+
+		return ast_tvcmp(*(struct timeval *)op_left, right) == 1;
+	}
+	default:
+		ast_log(LOG_WARNING, "Cannot evaluate field '%s': invalid type for operator '%s'\n",
+			op_right->field, op->symbol);
+	}
+
+	return -1;
+}
+
+/*
+ * \brief Operator callback for determining if one operand is less than or equal to another
+ */
+static int evaluate_less_than_or_equal(struct operator *op, enum aco_option_type type, void *op_left, struct expression_token *op_right)
+{
+	return !evaluate_greater_than(op, type, op_left, op_right);
+}
+
+/*
+ * \brief Operator callback for determining if one operand is greater than or equal to another
+ */
+static int evaluate_greater_than_or_equal(struct operator *op, enum aco_option_type type, void *op_left, struct expression_token *op_right)
+{
+	return !evaluate_less_than(op, type, op_left, op_right);
+}
+
+/*
+ * \brief Operator callback for determining logical NOT
+ */
+static int evaluate_not(struct operator *op, enum aco_option_type type, void *operand)
+{
+	switch (type) {
+	case OPT_BOOL_T:
+	case OPT_BOOLFLAG_T:
+	case OPT_INT_T:
+	case OPT_UINT_T:
+		return !(*(int *)operand);
+	default:
+		ast_log(LOG_WARNING, "Cannot evaluate: invalid operand type for operator '%s'\n", op->symbol);
+	}
+
+	return -1;
+}
+
+/*
+ * \brief Operator callback for determining logical AND
+ */
+static int evaluate_and(struct operator *op, enum aco_option_type type, void *op_left, struct expression_token *op_right)
+{
+	switch (type) {
+	case OPT_BOOL_T:
+	case OPT_BOOLFLAG_T:
+	case OPT_INT_T:
+	case OPT_UINT_T:
+		return (*(int *)op_left && op_right->result);
+	default:
+		ast_log(LOG_WARNING, "Cannot evaluate: invalid operand type for operator '%s'\n", op->symbol);
+	}
+
+	return -1;
+}
+
+/*
+ * \brief Operator callback for determining logical OR
+ */
+static int evaluate_or(struct operator *op, enum aco_option_type type, void *op_left, struct expression_token *op_right)
+{
+	switch (type) {
+	case OPT_BOOL_T:
+	case OPT_BOOLFLAG_T:
+	case OPT_INT_T:
+	case OPT_UINT_T:
+		return (*(int *)op_left || op_right->result);
+	default:
+		ast_log(LOG_WARNING, "Cannot evaluate: invalid operand type for operator '%s'\n", op->symbol);
+	}
+
+	return -1;
+}
+
+/*
+ * \brief Operator callback for regex 'like'
+ */
+static int evaluate_like(struct operator *op, enum aco_option_type type, void *op_left, struct expression_token *op_right)
+{
+	switch (type) {
+	case OPT_CHAR_ARRAY_T:
+	case OPT_STRINGFIELD_T:
+	/* In our case, we operate on pj_str_t */
+	{
+		int result;
+		regex_t regexbuf;
+		char buf[pj_strlen(op_left) + 1];
+
+		ast_copy_pj_str(buf, op_left, pj_strlen(op_left));
+		if (regcomp(&regexbuf, op_right->field, REG_EXTENDED | REG_NOSUB)) {
+			ast_log(LOG_WARNING, "Failed to compile '%s' into a regular expression\n", op_right->field);
+			return -1;
+		}
+
+		result = (regexec(&regexbuf, buf, 0, NULL, 0) == 0);
+		regfree(&regexbuf);
+
+		return result;
+	}
+	default:
+		ast_log(LOG_WARNING, "Cannot evaluate: invalid operand type for operator '%s'\n", op->symbol);
+	}
+
+	return -1;
+}
+
+/*!
+ * \brief Operator token for a left parenthesis.
+ *
+ * While this is used by the shunting-yard algorithm implementation,
+ * it should never appear in the resulting RPN queue of expression tokens
+ */
+static struct operator left_paren = {
+	.symbol = "(",
+	.precedence = 15
+};
+
+/*!
+ * \brief Our allowed operations
+ */
+static struct operator allowed_operators[] = {
+	{ .symbol = "=", .precedence = 7, .operands = 2, .evaluate = evaluate_equal, },
+	{ .symbol = "==", .precedence = 7, .operands = 2, .evaluate = evaluate_equal, },
+	{ .symbol = "!=", .precedence = 7, .operands = 2, .evaluate = evaluate_not_equal, },
+	{ .symbol = "<", .precedence = 6, .operands = 2, .evaluate = evaluate_less_than, },
+	{ .symbol = ">", .precedence = 6, .operands = 2, .evaluate = evaluate_greater_than, },
+	{ .symbol = "<=", .precedence = 6, .operands = 2, .evaluate = evaluate_less_than_or_equal, },
+	{ .symbol = ">=", .precedence = 6, .operands = 2, .evaluate = evaluate_greater_than_or_equal, },
+	{ .symbol = "!", .precedence = 2, .operands = 1, .right_to_left = 1, .evaluate_unary = evaluate_not, },
+	{ .symbol = "&&", .precedence = 11, .operands = 2, .evaluate = evaluate_and, },
+	{ .symbol = "||", .precedence = 12, .operands = 2, .evaluate = evaluate_or, },
+	{ .symbol = "like", .precedence = 7, .operands = 2, .evaluate = evaluate_like, },
+	{ .symbol = "and", .precedence = 11, .operands = 2, .evaluate = evaluate_and, },
+	{ .symbol = "or", .precedence = 11, .operands = 2, .evaluate = evaluate_or, },
+	{ .symbol = "not", .precedence = 2, .operands = 1, .right_to_left = 1, .evaluate_unary = evaluate_not, },
+};
+
+/*! \brief Callback to retrieve the entry index number */
+static void *entry_get_number(struct pjsip_history_entry *entry)
+{
+	return &entry->number;
+}
+
+/*! \brief Callback to retrieve the entry's timestamp */
+static void *entry_get_timestamp(struct pjsip_history_entry *entry)
+{
+	return &entry->timestamp;
+}
+
+/*! \brief Callback to retrieve the entry's destination address */
+static void *entry_get_addr(struct pjsip_history_entry *entry)
+{
+	if (entry->transmitted) {
+		return &entry->dst;
+	} else {
+		return &entry->src;
+	}
+}
+
+/*! \brief Callback to retrieve the entry's SIP request method type */
+static void *entry_get_sip_msg_request_method(struct pjsip_history_entry *entry)
+{
+	if (entry->msg->type != PJSIP_REQUEST_MSG) {
+		return NULL;
+	}
+
+	return &entry->msg->line.req.method.name;
+}
+
+/*! \brief Callback to retrieve the entry's SIP Call-ID header */
+static void *entry_get_sip_msg_call_id(struct pjsip_history_entry *entry)
+{
+	pjsip_cid_hdr *cid_hdr;
+
+	cid_hdr = PJSIP_MSG_CID_HDR(entry->msg);
+
+	return &cid_hdr->id;
+}
+
+/*! \brief The fields we allow */
+static struct allowed_field allowed_fields[] = {
+	{ .symbol = "number", .return_type = OPT_INT_T, .get_field = entry_get_number, },
+	/* We co-op the NOOP type here for timeval */
+	{ .symbol = "timestamp", .return_type = OPT_NOOP_T, .get_field = entry_get_timestamp, },
+	{ .symbol = "addr", .return_type = OPT_SOCKADDR_T, .get_field = entry_get_addr, },
+	{ .symbol = "sip.msg.request.method", .return_type = OPT_CHAR_ARRAY_T, .get_field = entry_get_sip_msg_request_method, },
+	{ .symbol = "sip.msg.call-id", .return_type = OPT_CHAR_ARRAY_T, .get_field = entry_get_sip_msg_call_id, },
+};
+
+/*! \brief Free an expression token and all others it references */
+static struct expression_token *expression_token_free(struct expression_token *token)
+{
+	struct expression_token *it_token;
+
+	it_token = token;
+	while (it_token) {
+		struct expression_token *prev = it_token;
+
+		it_token = it_token->next;
+		ast_free(prev);
+	}
+
+	return NULL;
+}
+
+/*!
+ * \brief Allocate an expression token
+ *
+ * \param token_type The type of token in the expression
+ * \param value The value/operator/result to pack into the token
+ *
+ * \retval NULL on failure
+ * \retval \c expression_token on success
+ */
+static struct expression_token *expression_token_alloc(enum expression_token_type token_type, void *value)
+{
+	struct expression_token *token;
+
+	switch (token_type) {
+	case TOKEN_TYPE_RESULT:
+	case TOKEN_TYPE_OPERATOR:
+		token = ast_calloc(1, sizeof(*token));
+		break;
+	case TOKEN_TYPE_FIELD:
+		token = ast_calloc(1, sizeof(*token) + strlen((const char *)value) + 1);
+		break;
+	default:
+		ast_assert(0);
+		return NULL;
+	}
+
+	if (!token) {
+		return NULL;
+	}
+	token->token_type = token_type;
+
+	switch (token_type) {
+	case TOKEN_TYPE_RESULT:
+		token->result = *(int *)value;
+		break;
+	case TOKEN_TYPE_OPERATOR:
+		token->op = value;
+		break;
+	case TOKEN_TYPE_FIELD:
+		strcpy(token->field, value); /* safe */
+		break;
+	default:
+		ast_assert(0);
+	}
+
+	return token;
+}
+
+/*! \brief Determine if the expression token matches a field in \c allowed_fields */
+static struct allowed_field *get_allowed_field(struct expression_token *token)
+{
+	int i;
+
+	ast_assert(token->token_type == TOKEN_TYPE_FIELD);
+
+	for (i = 0; i < ARRAY_LEN(allowed_fields); i++) {
+		if (strcasecmp(allowed_fields[i].symbol, token->field)) {
+			continue;
+		}
+
+		return &allowed_fields[i];
+	}
+
+	return NULL;
+}
+
+/*! \brief AO2 destructor for \c pjsip_history_entry */
+static void pjsip_history_entry_dtor(void *obj)
+{
+	struct pjsip_history_entry *entry = obj;
+
+	if (entry->pool) {
+		pj_pool_release(entry->pool);
+		entry->pool = NULL;
+	}
+}
+
+/*!
+ * \brief Create a \c pjsip_history_entry AO2 object
+ *
+ * \param msg The PJSIP message that this history entry wraps
+ *
+ * \retval An AO2 \c pjsip_history_entry object on success
+ * \retval NULL on failure
+ */
+static struct pjsip_history_entry *pjsip_history_entry_alloc(pjsip_msg *msg)
+{
+	struct pjsip_history_entry *entry;
+
+	entry = ao2_alloc_options(sizeof(*entry), pjsip_history_entry_dtor, AO2_ALLOC_OPT_LOCK_NOLOCK);
+	if (!entry) {
+		return NULL;
+	}
+	entry->number = ast_atomic_fetchadd_int(&packet_number, 1);
+	entry->timestamp = ast_tvnow();
+	entry->timestamp.tv_usec = 0;
+
+	entry->pool = pj_pool_create(&cachingpool.factory, NULL, PJSIP_POOL_RDATA_LEN,
+	                             PJSIP_POOL_RDATA_INC, NULL);
+	if (!entry->pool) {
+		ao2_ref(entry, -1);
+		return NULL;
+	}
+
+	entry->msg = pjsip_msg_clone(entry->pool, msg);
+	if (!entry->msg) {
+		ao2_ref(entry, -1);
+		return NULL;
+	}
+
+	return entry;
+}
+
+/*! \brief PJSIP callback when a SIP message is transmitted */
+static pj_status_t history_on_tx_msg(pjsip_tx_data *tdata)
+{
+	struct pjsip_history_entry *entry;
+
+	if (!enabled) {
+		return PJ_SUCCESS;
+	}
+
+	entry = pjsip_history_entry_alloc(tdata->msg);
+	if (!entry) {
+		return PJ_SUCCESS;
+	}
+	entry->transmitted = 1;
+	pj_sockaddr_cp(&entry->src, &tdata->tp_info.transport->local_addr);
+	pj_sockaddr_cp(&entry->dst, &tdata->tp_info.dst_addr);
+
+	ast_mutex_lock(&history_lock);
+	AST_VECTOR_APPEND(&vector_history, entry);
+	ast_mutex_unlock(&history_lock);
+
+	return PJ_SUCCESS;
+}
+
+/*! \brief PJSIP callback when a SIP message is received */
+static pj_bool_t history_on_rx_msg(pjsip_rx_data *rdata)
+{
+	struct pjsip_history_entry *entry;
+
+	if (!enabled) {
+		return PJ_FALSE;
+	}
+
+	if (!rdata->msg_info.msg) {
+		return PJ_FALSE;
+	}
+
+	entry = pjsip_history_entry_alloc(rdata->msg_info.msg);
+	if (!entry) {
+		return PJ_FALSE;
+	}
+
+	if (rdata->tp_info.transport->addr_len) {
+		pj_sockaddr_cp(&entry->dst, &rdata->tp_info.transport->local_addr);
+	}
+
+	if (rdata->pkt_info.src_addr_len) {
+		pj_sockaddr_cp(&entry->src, &rdata->pkt_info.src_addr);
+	}
+
+	ast_mutex_lock(&history_lock);
+	AST_VECTOR_APPEND(&vector_history, entry);
+	ast_mutex_unlock(&history_lock);
+
+	return PJ_FALSE;
+}
+
+/*! \brief Vector callback that releases the reference for the entry in a history vector */
+static void clear_history_entry_cb(struct pjsip_history_entry *entry)
+{
+	ao2_ref(entry, -1);
+}
+
+/*!
+ * \brief Remove all entries from \ref vector_history
+ *
+ * This must be called from a registered PJSIP thread
+ */
+static int clear_history_entries(void *obj)
+{
+	ast_mutex_lock(&history_lock);
+	AST_VECTOR_RESET(&vector_history, clear_history_entry_cb);
+	packet_number = 0;
+	ast_mutex_unlock(&history_lock);
+
+	return 0;
+}
+
+/*!
+ * \brief Build a reverse polish notation expression queue
+ *
+ * This function is an implementation of the Shunting-Yard Algorithm. It takes
+ * a user provided infix-notation expression and converts it into a reverse
+ * polish notation expression, which is a queue of tokens that can be easily
+ * parsed.
+ *
+ * \params a The CLI arguments provided by the User, containing the infix expression
+ *
+ * \retval NULL error
+ * \retval expression_token A 'queue' of expression tokens in RPN
+ */
+static struct expression_token *build_expression_queue(struct ast_cli_args *a)
+{
+	AST_VECTOR(, struct operator *) operators; /* A stack of saved operators */
+	struct expression_token *output = NULL;    /* The output queue */
+	struct expression_token *head = NULL;      /* Pointer to the head of /c output */
+	int i;
+
+#define APPEND_TO_OUTPUT(output, token) do { \
+	if ((output)) { \
+		(output)->next = (token); \
+		(output) = (token); \
+	} else { \
+		(output) = (token); \
+		head = (output); \
+	} \
+} while (0)
+
+	if (AST_VECTOR_INIT(&operators, 8)) {
+		return NULL;
+	}
+
+	for (i = 4; i < a->argc; i++) {
+		struct expression_token *out_token;
+		char *token = ast_strdupa(a->argv[i]);
+		int j;
+
+		/* Strip off and append any left parentheses */
+		if (token[0] == '(') {
+			AST_VECTOR_APPEND(&operators, &left_paren);
+			if (!token[1]) {
+				continue;
+			}
+			token = &token[1];
+		}
+
+		/* Handle the case where the token is an operator */
+		for (j = 0; j < ARRAY_LEN(allowed_operators); j++) {
+			int k;
+
+			if (strcasecmp(token, allowed_operators[j].symbol)) {
+				continue;
+			}
+
+			for (k = AST_VECTOR_SIZE(&operators) - 1; k >= 0; k--) {
+				struct operator *top = AST_VECTOR_GET(&operators, k);
+
+				/* Remove and push queued up operators, if they are of
+				 * less precedence than this operator
+				 */
+				if ((allowed_operators[j].right_to_left && allowed_operators[j].precedence >= top->precedence)
+					|| (!allowed_operators[j].right_to_left && allowed_operators[j].precedence > top->precedence)) {
+
+					if (!(out_token = expression_token_alloc(TOKEN_TYPE_OPERATOR, top))) {
+						goto error;
+					}
+					APPEND_TO_OUTPUT(output, out_token);
+					AST_VECTOR_REMOVE(&operators, k, 1);
+				}
+			}
+
+			AST_VECTOR_APPEND(&operators, &allowed_operators[j]);
+			token = NULL;
+			break;
+		}
+
+		/* Token was an operator; continue to next token */
+		if (!token) {
+			continue;
+		}
+
+		/* Handle a right parentheses either by itself or as part of the token.
+		 * If part of the token, push the token onto the output queue first
+		 */
+		if (token[0] == ')' || token[strlen(token) - 1] == ')') {
+
+			if (token[strlen(token) - 1] == ')') {
+				token[strlen(token) - 1] = '\0';
+
+				if (!(out_token = expression_token_alloc(TOKEN_TYPE_FIELD, token))) {
+					goto error;
+				}
+				APPEND_TO_OUTPUT(output, out_token);
+				token = NULL;
+			}
+
+			for (j = AST_VECTOR_SIZE(&operators) - 1; j >= 0; j--) {
+				struct operator *top = AST_VECTOR_GET(&operators, j);
+
+				AST_VECTOR_REMOVE(&operators, j, 1);
+				if (top == &left_paren) {
+					break;
+				}
+
+				if (!(out_token = expression_token_alloc(TOKEN_TYPE_OPERATOR, top))) {
+					goto error;
+				}
+				APPEND_TO_OUTPUT(output, out_token);
+			}
+		}
+
+		/* Just a plain token, push to the output queue */
+		if (token) {
+			if (!(out_token = expression_token_alloc(TOKEN_TYPE_FIELD, token))) {
+				goto error;
+			}
+			APPEND_TO_OUTPUT(output, out_token);
+		}
+	}
+
+	/* Remove any non-applied operators that remain, applying them
+	 * to the output queue
+	 */
+	for (i = AST_VECTOR_SIZE(&operators) - 1; i >= 0; i--) {
+		struct operator *top = AST_VECTOR_GET(&operators, i);
+		struct expression_token *out_token;
+
+		AST_VECTOR_REMOVE(&operators, i, 1);
+		if (top == &left_paren) {
+			ast_log(LOG_WARNING, "Unbalanced '(' parentheses in expression!\n");
+			continue;
+		}
+
+		if (!(out_token = expression_token_alloc(TOKEN_TYPE_OPERATOR, top))) {
+			goto error;
+		}
+		APPEND_TO_OUTPUT(output, out_token);
+	}
+
+	AST_VECTOR_FREE(&operators);
+	return head;
+
+error:
+	AST_VECTOR_FREE(&operators);
+	expression_token_free(output);
+	return NULL;
+}
+
+/*!
+ * \brief Evaluate a single entry in this history using a RPN expression
+ *
+ * \param entry The entry in the history to evaluate
+ * \param queue The RPN expression
+ *
+ * \retval 0 The expression evaluated FALSE on \c entry
+ * \retval 1 The expression evaluated TRUE on \c entry
+ * \retval -1 The expression errored
+ */
+static int evaluate_history_entry(struct pjsip_history_entry *entry, struct expression_token *queue)
+{
+	AST_VECTOR(, struct expression_token *) stack; /* Our stack of results and operands */
+	struct expression_token *it_queue;
+	struct expression_token *final;
+	int result;
+	int i;
+
+	if (AST_VECTOR_INIT(&stack, 16)) {
+		return -1;
+	}
+
+	for (it_queue = queue; it_queue; it_queue = it_queue->next) {
+		struct expression_token *op_one;
+		struct expression_token *op_two = NULL;
+		struct expression_token *result;
+		int res = 0;
+
+		/* If this is not an operator, push it to the stack */
+		if (!it_queue->op) {
+			AST_VECTOR_APPEND(&stack, it_queue);
+			continue;
+		}
+
+		if (AST_VECTOR_SIZE(&stack) < it_queue->op->operands) {
+			ast_log(LOG_WARNING, "Unable to evaluate expression operator '%s': not enough operands\n",
+				it_queue->op->symbol);
+			goto error;
+		}
+
+		if (it_queue->op->operands == 1) {
+			/* Unary operators currently consist only of 'not', which can only act
+			 * upon an evaluated condition result.
+			 */
+			ast_assert(it_queue->op->evaluate_unary != NULL);
+
+			op_one = AST_VECTOR_REMOVE(&stack, AST_VECTOR_SIZE(&stack) - 1, 1);
+			if (op_one->token_type != TOKEN_TYPE_RESULT) {
+				ast_log(LOG_WARNING, "Unable to evaluate '%s': operand is not the result of an operation\n",
+					it_queue->op->symbol);
+				goto error;
+			}
+
+			res = it_queue->op->evaluate_unary(it_queue->op, OPT_INT_T, &op_one->result) == 0 ? 0 : 1;
+		} else if (it_queue->op->operands == 2) {
+			struct allowed_field *field;
+			enum aco_option_type type;
+			void *value;
+
+			ast_assert(it_queue->op->evaluate != NULL);
+
+			op_one = AST_VECTOR_REMOVE(&stack, AST_VECTOR_SIZE(&stack) - 1, 1);
+			op_two = AST_VECTOR_REMOVE(&stack, AST_VECTOR_SIZE(&stack) - 1, 1);
+
+			/* If operand two is a field, then it must be a field we recognize. */
+			if (op_two->token_type == TOKEN_TYPE_FIELD) {
+				field = get_allowed_field(op_two);
+				if (!field) {
+					ast_log(LOG_WARNING, "Unknown or unrecognized field: %s\n", op_two->field);
+					goto error;
+				}
+
+				type = field->return_type;
+				value = field->get_field(entry);
+			} else if (op_two->token_type == TOKEN_TYPE_RESULT) {
+				type = OPT_INT_T;
+				value = &op_two->result;
+			} else {
+				ast_log(LOG_WARNING, "Attempting to evaluate an operator: %s\n", op_two->op->symbol);
+				goto error;
+			}
+
+			if (value) {
+				res = it_queue->op->evaluate(it_queue->op, type, value, op_one) == 0 ? 0 : 1;
+			} else {
+				res = 0;
+			}
+		} else {
+			ast_log(LOG_WARNING, "Operator '%s' has an invalid number of operands\n", it_queue->op->symbol);
+			ast_assert(0);
+			goto error;
+		}
+
+		/* Results are temporary; clean used ones up */
+		if (op_one && op_one->token_type == TOKEN_TYPE_RESULT) {
+			ast_free(op_one);
+		}
+		if (op_two && op_two->token_type == TOKEN_TYPE_RESULT) {
+			ast_free(op_two);
+		}
+
+		/* Push the result onto the stack */
+		result = expression_token_alloc(TOKEN_TYPE_RESULT, &res);
+		if (!result) {
+			goto error;
+		}
+		AST_VECTOR_APPEND(&stack, result);
+	}
+
+	/*
+	 * When the evaluation is complete, we must have:
+	 *  - A single result remaining on the stack
+	 *  - An actual result
+	 */
+	if (AST_VECTOR_SIZE(&stack) != 1) {
+		ast_log(LOG_WARNING, "Expression was unbalanced: %zu results remained after evaluation\n",
+			AST_VECTOR_SIZE(&stack));
+		goto error;
+	}
+
+	final = AST_VECTOR_GET(&stack, 0);
+	if (final->token_type != TOKEN_TYPE_RESULT) {
+		ast_log(LOG_WARNING, "Expression did not create a usable result\n");
+		goto error;
+	}
+	result = final->result;
+	ast_free(final);
+
+	return result;
+
+error:
+	/* Clean out any remaining result expression tokens */
+	for (i = 0; i < AST_VECTOR_SIZE(&stack); i++) {
+		struct expression_token *failed_token = AST_VECTOR_GET(&stack, i);
+
+		if (failed_token->token_type == TOKEN_TYPE_RESULT) {
+			ast_free(failed_token);
+		}
+	}
+	AST_VECTOR_FREE(&stack);
+	return -1;
+}
+
+/*!
+ * \brief Create a filtered history based on a user provided expression
+ *
+ * \param a The CLI arguments containing the expression
+ *
+ * \retval NULL on error
+ * \retval A vector containing the filtered history on success
+ */
+static struct vector_history_t *filter_history(struct ast_cli_args *a)
+{
+	struct vector_history_t *output;
+	struct expression_token *queue;
+	int i;
+
+	output = ast_malloc(sizeof(*output));
+	if (!output) {
+		return NULL;
+	}
+
+	if (AST_VECTOR_INIT(output, HISTORY_INITIAL_SIZE / 2)) {
+		ast_free(output);
+		return NULL;
+	}
+
+	queue = build_expression_queue(a);
+	if (!queue) {
+		return NULL;
+	}
+
+	ast_mutex_lock(&history_lock);
+	for (i = 0; i < AST_VECTOR_SIZE(&vector_history); i++) {
+		struct pjsip_history_entry *entry = AST_VECTOR_GET(&vector_history, i);
+		int res;
+
+		res = evaluate_history_entry(entry, queue);
+		if (res == -1) {
+			/* Error in expression evaluation; bail */
+			ast_mutex_unlock(&history_lock);
+			AST_VECTOR_RESET(output, clear_history_entry_cb);
+			AST_VECTOR_FREE(output);
+			ast_free(output);
+			expression_token_free(queue);
+			return NULL;
+		} else if (!res) {
+			continue;
+		} else {
+			AST_VECTOR_APPEND(output, ao2_bump(entry));
+		}
+	}
+	ast_mutex_unlock(&history_lock);
+
+	expression_token_free(queue);
+
+	return output;
+}
+
+/*! \brief Print a detailed view of a single entry in the history to the CLI */
+static void display_single_entry(struct ast_cli_args *a, struct pjsip_history_entry *entry)
+{
+	char addr[64];
+	char *buf;
+
+	buf = ast_calloc(1, PJSIP_MAX_PKT_LEN * sizeof(char));
+	if (!buf) {
+		return;
+	}
+
+	if (pjsip_msg_print(entry->msg, buf, PJSIP_MAX_PKT_LEN) == -1) {
+		ast_log(LOG_WARNING, "Unable to print SIP message %d: packet too large!\n", entry->number);
+		ast_free(buf);
+		return;
+	}
+
+	if (entry->transmitted) {
+		pj_sockaddr_print(&entry->dst, addr, sizeof(addr), 3);
+	} else {
+		pj_sockaddr_print(&entry->src, addr, sizeof(addr), 3);
+	}
+
+	ast_cli(a->fd, "<--- History Entry %d %s %s at %-10.10ld --->\n",
+		entry->number,
+		entry->transmitted ? "Sent to" : "Received from",
+		addr,
+		entry->timestamp.tv_sec);
+	ast_cli(a->fd, "%s\n", buf);
+
+	ast_free(buf);
+}
+
+/*! \brief Print a list of the entries to the CLI */
+static void display_entry_list(struct ast_cli_args *a, struct vector_history_t *vec)
+{
+	int i;
+
+	ast_cli(a->fd, "%-5.5s %-10.10s %-30.30s %-35.35s\n",
+		"No.",
+		"Timestamp",
+		"(Dir) Address",
+		"SIP Message");
+	ast_cli(a->fd, "===== ========== ============================== ===================================\n");
+
+	for (i = 0; i < AST_VECTOR_SIZE(vec); i++) {
+		struct pjsip_history_entry *entry;
+		char addr[64];
+		char line[256];
+
+		entry = AST_VECTOR_GET(vec, i);
+
+		if (entry->transmitted) {
+			pj_sockaddr_print(&entry->dst, addr, sizeof(addr), 3);
+		} else {
+			pj_sockaddr_print(&entry->src, addr, sizeof(addr), 3);
+		}
+
+		if (entry->msg->type == PJSIP_REQUEST_MSG) {
+			char uri[128];
+
+			pjsip_uri_print(PJSIP_URI_IN_REQ_URI, entry->msg->line.req.uri, uri, sizeof(uri));
+			snprintf(line, sizeof(line), "%.*s %s SIP/2.0",
+				(int)pj_strlen(&entry->msg->line.req.method.name),
+				pj_strbuf(&entry->msg->line.req.method.name),
+				uri);
+		} else {
+			snprintf(line, sizeof(line), "SIP/2.0 %u %.*s",
+				entry->msg->line.status.code,
+				(int)pj_strlen(&entry->msg->line.status.reason),
+				pj_strbuf(&entry->msg->line.status.reason));
+		}
+
+		ast_cli(a->fd, "%-5.5d %-10.10ld %-5.5s %-24.24s %s\n",
+			entry->number,
+			entry->timestamp.tv_sec,
+			entry->transmitted ? "* ==>" : "* <==",
+			addr,
+			line);
+	}
+}
+
+/*! \brief Cleanup routine for a history vector, serviced on a registered PJSIP thread */
+static int safe_vector_cleanup(void *obj)
+{
+	struct vector_history_t *vec = obj;
+
+	AST_VECTOR_RESET(vec, clear_history_entry_cb);
+	AST_VECTOR_FREE(vec);
+	ast_free(vec);
+
+	return 0;
+}
+
+static char *pjsip_show_history(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+	struct vector_history_t *vec = &vector_history;
+	struct pjsip_history_entry *entry = NULL;
+
+	if (cmd == CLI_INIT) {
+		e->command = "pjsip show history";
+		e->usage =
+			"Usage: pjsip show history [entry <num>|where [...]]\n"
+			"       Displays the currently collected history or an\n"
+			"       entry within the history.\n\n"
+			"       * Running the command with no options will display\n"
+			"         the entire history.\n"
+			"       * Providing 'entry <num>' will display the full\n"
+			"         detail of a particular entry in this history.\n"
+			"       * Providing 'where ...' will allow for filtering\n"
+			"         the history. The history can be filtered using\n"
+			"         any of the following fields:\n"
+			"         - number: The history entry number\n"
+			"         - timestamp: The time associated with the history entry\n"
+			"         - addr: The source/destination address of the SIP message\n"
+			"         - sip.msg.request.method: The request method type\n"
+			"         - sip.msg.call-id: The Call-ID header of the SIP message\n"
+			"\n"
+			"         When filtering, standard Boolean operators can be used,\n"
+			"         as well as 'like' for regexs.\n"
+			"\n"
+			"         Example:\n"
+			"         'pjsip show history where number > 5 and (addr = \"192.168.0.3:5060\" or addr = \"192.168.0.5:5060\")'\n";
+		return NULL;
+	} else if (cmd == CLI_GENERATE) {
+		return NULL;
+	}
+
+	if (a->argc > 3) {
+		if (!strcasecmp(a->argv[3], "entry") && a->argc == 5) {
+			int num;
+
+			if (sscanf(a->argv[4], "%30d", &num) != 1) {
+				ast_cli(a->fd, "'%s' is not a valid entry number\n", a->argv[4]);
+				return CLI_FAILURE;
+			}
+
+			/* Get the entry at the provided position */
+			ast_mutex_lock(&history_lock);
+			if (num >= AST_VECTOR_SIZE(&vector_history) || num < 0) {
+				ast_cli(a->fd, "Entry '%d' does not exist\n", num);
+				ast_mutex_unlock(&history_lock);
+				return CLI_FAILURE;
+			}
+			entry = ao2_bump(AST_VECTOR_GET(&vector_history, num));
+			ast_mutex_unlock(&history_lock);
+		} else if (!strcasecmp(a->argv[3], "where")) {
+			vec = filter_history(a);
+			if (!vec) {
+				return CLI_FAILURE;
+			}
+		} else {
+			return CLI_SHOWUSAGE;
+		}
+	}
+
+	if (AST_VECTOR_SIZE(vec) == 1) {
+		if (vec == &vector_history) {
+			ast_mutex_lock(&history_lock);
+		}
+		entry = ao2_bump(AST_VECTOR_GET(vec, 0));
+		if (vec == &vector_history) {
+			ast_mutex_lock(&history_lock);
+		}
+	}
+
+	if (entry) {
+		display_single_entry(a, entry);
+	} else {
+		if (vec == &vector_history) {
+			ast_mutex_lock(&history_lock);
+		}
+
+		display_entry_list(a, vec);
+
+		if (vec == &vector_history) {
+			ast_mutex_unlock(&history_lock);
+		}
+	}
+
+	if (vec != &vector_history) {
+		ast_sip_push_task(NULL, safe_vector_cleanup, vec);
+	}
+	ao2_cleanup(entry);
+
+	return CLI_SUCCESS;
+}
+
+static char *pjsip_set_history(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+	const char *what;
+
+	if (cmd == CLI_INIT) {
+		e->command = "pjsip set history {on|off|clear}";
+		e->usage =
+			"Usage: pjsip set history {on|off|clear}\n"
+			"       Enables/disables/clears the PJSIP history.\n\n"
+			"       Enabling the history will start recording transmitted/received\n"
+			"       packets. Disabling the history will stop recording, but keep\n"
+			"       the already received packets. Clearing the history will wipe\n"
+			"       the received packets from memory.\n\n"
+			"       As the PJSIP history is maintained in memory, and includes\n"
+			"       all received/transmitted requests and responses, it should\n"
+			"       only be enabled for debugging purposes, and cleared when done.\n";
+		return NULL;
+	} else if (cmd == CLI_GENERATE) {
+		return NULL;
+	}
+
+	what = a->argv[e->args - 1];	/* Guaranteed to exist */
+
+	if (a->argc == e->args) {
+		if (!strcasecmp(what, "on")) {
+			enabled = 1;
+			ast_cli(a->fd, "PJSIP History enabled\n");
+			return CLI_SUCCESS;
+		} else if (!strcasecmp(what, "off")) {
+			enabled = 0;
+			ast_cli(a->fd, "PJSIP History disabled\n");
+			return CLI_SUCCESS;
+		} else if (!strcasecmp(what, "clear")) {
+			ast_sip_push_task(NULL, clear_history_entries, NULL);
+			ast_cli(a->fd, "PJSIP History cleared\n");
+			return CLI_SUCCESS;
+		}
+	}
+
+	return CLI_SHOWUSAGE;
+}
+
+static pjsip_module logging_module = {
+	.name = { "History Module", 14 },
+	.priority = 0,
+	.on_rx_request = history_on_rx_msg,
+	.on_rx_response = history_on_rx_msg,
+	.on_tx_request = history_on_tx_msg,
+	.on_tx_response = history_on_tx_msg,
+};
+
+static struct ast_cli_entry cli_pjsip[] = {
+	AST_CLI_DEFINE(pjsip_set_history, "Enable/Disable PJSIP History"),
+	AST_CLI_DEFINE(pjsip_show_history, "Display PJSIP History"),
+};
+
+static int load_module(void)
+{
+	CHECK_PJSIP_MODULE_LOADED();
+
+	pj_caching_pool_init(&cachingpool, &pj_pool_factory_default_policy, 0);
+
+	AST_VECTOR_INIT(&vector_history, HISTORY_INITIAL_SIZE);
+
+	ast_sip_register_service(&logging_module);
+	ast_cli_register_multiple(cli_pjsip, ARRAY_LEN(cli_pjsip));
+
+	return AST_MODULE_LOAD_SUCCESS;
+}
+
+static int unload_module(void)
+{
+	ast_cli_unregister_multiple(cli_pjsip, ARRAY_LEN(cli_pjsip));
+	ast_sip_unregister_service(&logging_module);
+
+	ast_sip_push_task_synchronous(NULL, clear_history_entries, NULL);
+	AST_VECTOR_FREE(&vector_history);
+
+	pj_caching_pool_destroy(&cachingpool);
+
+	return 0;
+}
+
+AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_LOAD_ORDER, "PJSIP History",
+		.support_level = AST_MODULE_SUPPORT_EXTENDED,
+		.load = load_module,
+		.unload = unload_module,
+		.load_pri = AST_MODPRI_APP_DEPEND,
+	);
diff --git a/res/res_pjsip_log_forwarder.c b/res/res_pjsip_log_forwarder.c
deleted file mode 100644
index 7b095bb..0000000
--- a/res/res_pjsip_log_forwarder.c
+++ /dev/null
@@ -1,125 +0,0 @@
-/*
- * Asterisk -- An open source telephony toolkit.
- *
- * Copyright (C) 2013, Digium, Inc.
- *
- * David M. Lee, II <dlee at 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 Bridge PJSIP logging to Asterisk logging.
- * \author David M. Lee, II <dlee at digium.com>
- *
- * PJSIP logging doesn't exactly match Asterisk logging, but mapping the two is
- * not too bad. PJSIP log levels are identified by a single int. Limits are
- * not specified by PJSIP, but their implementation used 1 through 6.
- *
- * The mapping is as follows:
- *  - 0: LOG_ERROR
- *  - 1: LOG_ERROR
- *  - 2: LOG_WARNING
- *  - 3 and above: equivalent to ast_debug(level, ...) for res_pjsip.so
- */
-
-/*** MODULEINFO
-	<depend>pjproject</depend>
-	<support_level>core</support_level>
- ***/
-
-#include "asterisk.h"
-
-ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
-
-#include <pjsip.h>
-#include <pj/log.h>
-
-#include "asterisk/logger.h"
-#include "asterisk/module.h"
-
-static pj_log_func *log_cb_orig;
-static unsigned decor_orig;
-
-static void log_cb(int level, const char *data, int len)
-{
-	int ast_level;
-	/* PJSIP doesn't provide much in the way of source info */
-	const char * log_source = "pjsip";
-	int log_line = 0;
-	const char *log_func = "<?>";
-	int mod_level;
-
-	/* Lower number indicates higher importance */
-	switch (level) {
-	case 0: /* level zero indicates fatal error, according to docs */
-	case 1: /* 1 seems to be used for errors */
-		ast_level = __LOG_ERROR;
-		break;
-	case 2: /* 2 seems to be used for warnings and errors */
-		ast_level = __LOG_WARNING;
-		break;
-	default:
-		ast_level = __LOG_DEBUG;
-
-		/* For levels 3 and up, obey the debug level for res_pjsip */
-		mod_level = ast_opt_dbg_module ?
-			ast_debug_get_by_module("res_pjsip") : 0;
-		if (option_debug < level && mod_level < level) {
-			return;
-		}
-		break;
-	}
-
-	/* PJSIP uses indention to indicate function call depth. We'll prepend
-	 * log statements with a tab so they'll have a better shot at lining
-	 * up */
-	ast_log(ast_level, log_source, log_line, log_func, "\t%s\n", data);
-}
-
-static int load_module(void)
-{
-	pj_init();
-
-	decor_orig = pj_log_get_decor();
-	log_cb_orig = pj_log_get_log_func();
-
-	ast_debug(3, "Forwarding PJSIP logger to Asterisk logger\n");
-	/* SENDER prepends the source to the log message. This could be a
-	 * filename, object reference, or simply a string
-	 *
-	 * INDENT is assumed to be on by most log statements in PJSIP itself.
-	 */
-	pj_log_set_decor(PJ_LOG_HAS_SENDER | PJ_LOG_HAS_INDENT);
-	pj_log_set_log_func(log_cb);
-
-	return AST_MODULE_LOAD_SUCCESS;
-}
-
-static int unload_module(void)
-{
-	pj_log_set_log_func(log_cb_orig);
-	pj_log_set_decor(decor_orig);
-
-	pj_shutdown();
-
-	return 0;
-}
-
-/* While we don't really export global symbols, we want to load before other
- * modules that do */
-AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_GLOBAL_SYMBOLS | AST_MODFLAG_LOAD_ORDER, "PJSIP Log Forwarder",
-	.support_level = AST_MODULE_SUPPORT_CORE,
-	.load = load_module,
-	.unload = unload_module,
-	.load_pri = AST_MODPRI_CHANNEL_DEPEND - 6,
-	);
diff --git a/res/res_pjsip_messaging.c b/res/res_pjsip_messaging.c
index dab70ca..5962232 100644
--- a/res/res_pjsip_messaging.c
+++ b/res/res_pjsip_messaging.c
@@ -530,6 +530,10 @@ static struct msg_data* msg_data_create(const struct ast_msg *msg, const char *t
 	/* Make sure we start with sip: */
 	mdata->to = ast_begins_with(to, "sip:") ? ast_strdup(++to) : ast_strdup(to - 3);
 	mdata->from = ast_strdup(from);
+	if (!mdata->to || !mdata->from) {
+		ao2_ref(mdata, -1);
+		return NULL;
+	}
 
 	/* sometimes from can still contain the tag at this point, so remove it */
 	if ((tag = strchr(mdata->from, ';'))) {
@@ -597,7 +601,7 @@ static int sip_msg_send(const struct ast_msg *msg, const char *to, const char *f
 
 	if (!(mdata = msg_data_create(msg, to, from)) ||
 	    ast_sip_push_task(message_serializer, msg_send, mdata)) {
-		ao2_ref(mdata, -1);
+		ao2_cleanup(mdata);
 		return -1;
 	}
 	return 0;
@@ -758,7 +762,7 @@ static int load_module(void)
 		return AST_MODULE_LOAD_DECLINE;
 	}
 
-	message_serializer = ast_sip_create_serializer();
+	message_serializer = ast_sip_create_serializer_named("pjsip/messaging");
 	if (!message_serializer) {
 		ast_sip_unregister_service(&messaging_module);
 		ast_msg_tech_unregister(&msg_tech);
diff --git a/res/res_pjsip_multihomed.c b/res/res_pjsip_multihomed.c
index 437a1cb..745bc37 100644
--- a/res/res_pjsip_multihomed.c
+++ b/res/res_pjsip_multihomed.c
@@ -33,30 +33,28 @@
 /*! \brief Helper function which returns a UDP transport bound to the given address and port */
 static pjsip_transport *multihomed_get_udp_transport(pj_str_t *address, int port)
 {
-	struct ao2_container *transports = ast_sorcery_retrieve_by_fields(ast_sip_get_sorcery(), "transport",
-		AST_RETRIEVE_FLAG_MULTIPLE | AST_RETRIEVE_FLAG_ALL, NULL);
-	struct ast_sip_transport *transport;
+	struct ao2_container *transport_states = ast_sip_get_transport_states();
+	struct ast_sip_transport_state *transport_state;
 	struct ao2_iterator iter;
 	pjsip_transport *sip_transport = NULL;
 
-	if (!transports) {
+	if (!transport_states) {
 		return NULL;
 	}
 
-	for (iter = ao2_iterator_init(transports, 0); (transport = ao2_iterator_next(&iter)); ao2_ref(transport, -1)) {
-		if ((transport->type != AST_TRANSPORT_UDP) ||
-			(pj_strcmp(&transport->state->transport->local_name.host, address)) ||
-			(transport->state->transport->local_name.port != port)) {
+	for (iter = ao2_iterator_init(transport_states, 0); (transport_state = ao2_iterator_next(&iter)); ao2_ref(transport_state, -1)) {
+		if (transport_state && ((transport_state->type != AST_TRANSPORT_UDP) ||
+			(pj_strcmp(&transport_state->transport->local_name.host, address)) ||
+			(transport_state->transport->local_name.port != port))) {
 			continue;
 		}
 
-		sip_transport = transport->state->transport;
-		ao2_ref(transport, -1);
+		sip_transport = transport_state->transport;
 		break;
 	}
 	ao2_iterator_destroy(&iter);
 
-	ao2_ref(transports, -1);
+	ao2_ref(transport_states, -1);
 
 	return sip_transport;
 }
diff --git a/res/res_pjsip_mwi.c b/res/res_pjsip_mwi.c
index f349324..7bec758 100644
--- a/res/res_pjsip_mwi.c
+++ b/res/res_pjsip_mwi.c
@@ -432,7 +432,7 @@ static void send_unsolicited_mwi_notify(struct mwi_subscription *sub,
 	ast_debug(5, "Sending unsolicited MWI NOTIFY to endpoint %s, new messages: %d, old messages: %d\n",
 			sub->id, counter->new_msgs, counter->old_msgs);
 
-	while ((aor_name = strsep(&endpoint_aors, ","))) {
+	while ((aor_name = ast_strip(strsep(&endpoint_aors, ",")))) {
 		RAII_VAR(struct ast_sip_aor *, aor, ast_sip_location_retrieve_aor(aor_name), ao2_cleanup);
 		RAII_VAR(struct ao2_container *, contacts, NULL, ao2_cleanup);
 		struct unsolicited_mwi_data mwi_data = {
@@ -448,7 +448,7 @@ static void send_unsolicited_mwi_notify(struct mwi_subscription *sub,
 
 		contacts = ast_sip_location_retrieve_aor_contacts(aor);
 		if (!contacts || (ao2_container_count(contacts) == 0)) {
-			ast_log(LOG_NOTICE, "No contacts bound to AOR %s. Cannot send unsolicited MWI until a contact registers.\n", aor_name);
+			ast_debug(1, "No contacts bound to AOR %s. Cannot send unsolicited MWI until a contact registers.\n", aor_name);
 			continue;
 		}
 
@@ -598,9 +598,13 @@ static int mwi_validate_for_aor(void *obj, void *arg, int flags)
 	}
 
 	mailboxes = ast_strdupa(aor->mailboxes);
-	while ((mailbox = strsep(&mailboxes, ","))) {
+	while ((mailbox = ast_strip(strsep(&mailboxes, ",")))) {
+		if (ast_strlen_zero(mailbox)) {
+			continue;
+		}
+
 		if (endpoint_receives_unsolicited_mwi_for_mailbox(endpoint, mailbox)) {
-			ast_log(LOG_NOTICE, "Endpoint '%s' already configured for unsolicited MWI for mailbox '%s'. "
+			ast_debug(1, "Endpoint '%s' already configured for unsolicited MWI for mailbox '%s'. "
 					"Denying MWI subscription to %s\n", ast_sorcery_object_get_id(endpoint), mailbox,
 					ast_sorcery_object_get_id(aor));
 			return -1;
@@ -622,9 +626,13 @@ static int mwi_on_aor(void *obj, void *arg, int flags)
 	}
 
 	mailboxes = ast_strdupa(aor->mailboxes);
-	while ((mailbox = strsep(&mailboxes, ","))) {
+	while ((mailbox = ast_strip(strsep(&mailboxes, ",")))) {
 		struct mwi_stasis_subscription *mwi_stasis_sub;
 
+		if (ast_strlen_zero(mailbox)) {
+			continue;
+		}
+
 		mwi_stasis_sub = mwi_stasis_subscription_alloc(mailbox, sub);
 		if (!mwi_stasis_sub) {
 			continue;
@@ -710,13 +718,13 @@ static int mwi_new_subscribe(struct ast_sip_endpoint *endpoint,
 
 	aor = ast_sip_location_retrieve_aor(resource);
 	if (!aor) {
-		ast_log(LOG_WARNING, "Unable to locate aor %s. MWI subscription failed.\n",
+		ast_debug(1, "Unable to locate aor %s. MWI subscription failed.\n",
 			resource);
 		return 404;
 	}
 
 	if (ast_strlen_zero(aor->mailboxes)) {
-		ast_log(LOG_NOTICE, "AOR %s has no configured mailboxes. MWI subscription failed.\n",
+		ast_debug(1, "AOR %s has no configured mailboxes. MWI subscription failed.\n",
 			resource);
 		return 404;
 	}
@@ -890,7 +898,7 @@ static int create_mwi_subscriptions_for_endpoint(void *obj, void *arg, int flags
 
 	endpoint_aors = ast_strdupa(endpoint->aors);
 
-	while ((aor_name = strsep(&endpoint_aors, ","))) {
+	while ((aor_name = ast_strip(strsep(&endpoint_aors, ",")))) {
 		RAII_VAR(struct ast_sip_aor *, aor, ast_sip_location_retrieve_aor(aor_name), ao2_cleanup);
 
 		if (!aor) {
@@ -921,11 +929,15 @@ static int create_mwi_subscriptions_for_endpoint(void *obj, void *arg, int flags
 	}
 
 	mailboxes = ast_strdupa(endpoint->subscription.mwi.mailboxes);
-	while ((mailbox = strsep(&mailboxes, ","))) {
-		struct mwi_subscription *sub = aggregate_sub ?:
-			mwi_subscription_alloc(endpoint, 0, NULL);
+	while ((mailbox = ast_strip(strsep(&mailboxes, ",")))) {
+		struct mwi_subscription *sub;
 		struct mwi_stasis_subscription *mwi_stasis_sub;
 
+		if (ast_strlen_zero(mailbox)) {
+			continue;
+		}
+
+		sub = aggregate_sub ?: mwi_subscription_alloc(endpoint, 0, NULL);
 		mwi_stasis_sub = mwi_stasis_subscription_alloc(mailbox, sub);
 		if (mwi_stasis_sub) {
 			ao2_link(sub->stasis_subs, mwi_stasis_sub);
diff --git a/res/res_pjsip_nat.c b/res/res_pjsip_nat.c
index a2eb6ad..59ef71c 100644
--- a/res/res_pjsip_nat.c
+++ b/res/res_pjsip_nat.c
@@ -150,19 +150,19 @@ struct request_transport_details {
 };
 
 /*! \brief Callback function for finding the transport the request is going out on */
-static int find_transport_in_use(void *obj, void *arg, int flags)
+static int find_transport_state_in_use(void *obj, void *arg, int flags)
 {
-	struct ast_sip_transport *transport = obj;
+	struct ast_sip_transport_state *transport_state = obj;
 	struct request_transport_details *details = arg;
 
 	/* If an explicit transport or factory matches then this is what is in use, if we are unavailable
 	 * to compare based on that we make sure that the type is the same and the source IP address/port are the same
 	 */
-	if ((details->transport && details->transport == transport->state->transport) ||
-		(details->factory && details->factory == transport->state->factory) ||
-		((details->type == transport->type) && (transport->state->factory) &&
-			!pj_strcmp(&transport->state->factory->addr_name.host, &details->local_address) &&
-			transport->state->factory->addr_name.port == details->local_port)) {
+	if (transport_state && ((details->transport && details->transport == transport_state->transport) ||
+		(details->factory && details->factory == transport_state->factory) ||
+		((details->type == transport_state->type) && (transport_state->factory) &&
+			!pj_strcmp(&transport_state->factory->addr_name.host, &details->local_address) &&
+			transport_state->factory->addr_name.port == details->local_port))) {
 		return CMP_MATCH | CMP_STOP;
 	}
 
@@ -204,8 +204,9 @@ static int nat_invoke_hook(void *obj, void *arg, int flags)
 
 static pj_status_t nat_on_tx_message(pjsip_tx_data *tdata)
 {
-	RAII_VAR(struct ao2_container *, transports, NULL, ao2_cleanup);
+	RAII_VAR(struct ao2_container *, transport_states, NULL, ao2_cleanup);
 	RAII_VAR(struct ast_sip_transport *, transport, NULL, ao2_cleanup);
+	RAII_VAR(struct ast_sip_transport_state *, transport_state, NULL, ao2_cleanup);
 	struct request_transport_details details = { 0, };
 	pjsip_via_hdr *via = NULL;
 	struct ast_sockaddr addr = { { 0, } };
@@ -247,9 +248,19 @@ static pj_status_t nat_on_tx_message(pjsip_tx_data *tdata)
 		}
 	}
 
-	if (!(transports = ast_sorcery_retrieve_by_fields(ast_sip_get_sorcery(), "transport", AST_RETRIEVE_FLAG_MULTIPLE | AST_RETRIEVE_FLAG_ALL, NULL)) ||
-		!(transport = ao2_callback(transports, 0, find_transport_in_use, &details)) || !transport->localnet ||
-		ast_sockaddr_isnull(&transport->external_address)) {
+	if (!(transport_states = ast_sip_get_transport_states())) {
+		return PJ_SUCCESS;
+	}
+
+	if (!(transport_state = ao2_callback(transport_states, 0, find_transport_state_in_use, &details))) {
+		return PJ_SUCCESS;
+	}
+
+	if (!(transport = ast_sorcery_retrieve_by_id(ast_sip_get_sorcery(), "transport", transport_state->id))) {
+		return PJ_SUCCESS;
+	}
+
+	if ( !transport_state->localnet || 	ast_sockaddr_isnull(&transport_state->external_address)) {
 		return PJ_SUCCESS;
 	}
 
@@ -257,13 +268,13 @@ static pj_status_t nat_on_tx_message(pjsip_tx_data *tdata)
 	ast_sockaddr_set_port(&addr, tdata->tp_info.dst_port);
 
 	/* See if where we are sending this request is local or not, and if not that we can get a Contact URI to modify */
-	if (ast_apply_ha(transport->localnet, &addr) != AST_SENSE_ALLOW) {
+	if (ast_apply_ha(transport_state->localnet, &addr) != AST_SENSE_ALLOW) {
 		return PJ_SUCCESS;
 	}
 
 	/* Update the contact header with the external address */
 	if (uri || (uri = nat_get_contact_sip_uri(tdata))) {
-		pj_strdup2(tdata->pool, &uri->host, ast_sockaddr_stringify_host(&transport->external_address));
+		pj_strdup2(tdata->pool, &uri->host, ast_sockaddr_stringify_host(&transport_state->external_address));
 		if (transport->external_signaling_port) {
 			uri->port = transport->external_signaling_port;
 			ast_debug(4, "Re-wrote Contact URI port to %d\n", uri->port);
@@ -272,7 +283,7 @@ static pj_status_t nat_on_tx_message(pjsip_tx_data *tdata)
 
 	/* Update the via header if relevant */
 	if ((tdata->msg->type == PJSIP_REQUEST_MSG) && (via || (via = pjsip_msg_find_hdr(tdata->msg, PJSIP_H_VIA, NULL)))) {
-		pj_strdup2(tdata->pool, &via->sent_by.host, ast_sockaddr_stringify_host(&transport->external_address));
+		pj_strdup2(tdata->pool, &via->sent_by.host, ast_sockaddr_stringify_host(&transport_state->external_address));
 		if (transport->external_signaling_port) {
 			via->sent_by.port = transport->external_signaling_port;
 		}
diff --git a/res/res_pjsip_notify.c b/res/res_pjsip_notify.c
index 96367cf..8258b38 100644
--- a/res/res_pjsip_notify.c
+++ b/res/res_pjsip_notify.c
@@ -615,7 +615,7 @@ static int notify_endpoint(void *obj)
 
 	aors = ast_strdupa(data->endpoint->aors);
 
-	while ((aor_name = strsep(&aors, ","))) {
+	while ((aor_name = ast_strip(strsep(&aors, ",")))) {
 		RAII_VAR(struct ast_sip_aor *, aor,
 			 ast_sip_location_retrieve_aor(aor_name), ao2_cleanup);
 		RAII_VAR(struct ao2_container *, contacts, NULL, ao2_cleanup);
diff --git a/res/res_pjsip_outbound_publish.c b/res/res_pjsip_outbound_publish.c
index 8b6f6e4..c16ced3 100644
--- a/res/res_pjsip_outbound_publish.c
+++ b/res/res_pjsip_outbound_publish.c
@@ -891,7 +891,7 @@ static void sip_outbound_publish_callback(struct pjsip_publishc_cbparam *param)
 		pjsip_publishc_destroy(client->client);
 		client->client = NULL;
 
-		if (sip_outbound_publish_client_alloc(publish)) {
+		if (sip_outbound_publish_client_alloc(client)) {
 			ast_log(LOG_ERROR, "Failed to create a new outbound publish client for '%s' on 412 response\n",
 				ast_sorcery_object_get_id(publish));
 			goto end;
diff --git a/res/res_pjsip_outbound_registration.c b/res/res_pjsip_outbound_registration.c
index 4ba5748..8a40cce 100644
--- a/res/res_pjsip_outbound_registration.c
+++ b/res/res_pjsip_outbound_registration.c
@@ -346,6 +346,8 @@ struct sip_outbound_registration_client_state {
 	unsigned int destroy:1;
 	/*! \brief Non-zero if we have attempted sending a REGISTER with authentication */
 	unsigned int auth_attempted:1;
+	/*! \brief The name of the transport to be used for the registration */
+	char *transport_name;
 };
 
 /*! \brief Outbound registration state information (persists for lifetime that registration should exist) */
@@ -508,6 +510,7 @@ static pj_status_t registration_client_send(struct sip_outbound_registration_cli
 {
 	pj_status_t status;
 	int *callback_invoked;
+	pjsip_tpselector selector = { .type = PJSIP_TPSELECTOR_NONE, };
 
 	callback_invoked = ast_threadstorage_get(&register_callback_invoked, sizeof(int));
 	if (!callback_invoked) {
@@ -517,6 +520,13 @@ static pj_status_t registration_client_send(struct sip_outbound_registration_cli
 
 	/* Due to the message going out the callback may now be invoked, so bump the count */
 	ao2_ref(client_state, +1);
+	/*
+	 * Set the transport in case transports were reloaded.
+	 * When pjproject removes the extraneous error messages produced,
+	 * we can check status and only set the transport and resend if there was an error
+	 */
+	ast_sip_set_tpselector_from_transport_name(client_state->transport_name, &selector);
+	pjsip_regc_set_transport(client_state->client, &selector);
 	status = pjsip_regc_send(client_state->client, tdata);
 
 	/* If the attempt to send the message failed and the callback was not invoked we need to
@@ -966,6 +976,7 @@ static void sip_outbound_registration_client_state_destroy(void *obj)
 {
 	struct sip_outbound_registration_client_state *client_state = obj;
 
+	ast_free(client_state->transport_name);
 	ast_statsd_log_string("PJSIP.registrations.count", AST_STATSD_GAUGE, "-1", 1.0);
 	ast_statsd_log_string_va("PJSIP.registrations.state.%s", AST_STATSD_GAUGE, "-1", 1.0,
 		sip_outbound_registration_status_str(client_state->status));
@@ -977,6 +988,7 @@ static void sip_outbound_registration_client_state_destroy(void *obj)
 static struct sip_outbound_registration_state *sip_outbound_registration_state_alloc(struct sip_outbound_registration *registration)
 {
 	struct sip_outbound_registration_state *state;
+	char tps_name[AST_TASKPROCESSOR_MAX_NAME + 1];
 
 	state = ao2_alloc(sizeof(*state), sip_outbound_registration_state_destroy);
 	if (!state) {
@@ -989,7 +1001,12 @@ static struct sip_outbound_registration_state *sip_outbound_registration_state_a
 		return NULL;
 	}
 
-	state->client_state->serializer = ast_sip_create_serializer_group(shutdown_group);
+	/* Create name with seq number appended. */
+	ast_taskprocessor_build_name(tps_name, sizeof(tps_name), "pjsip/outreg/%s",
+		ast_sorcery_object_get_id(registration));
+
+	state->client_state->serializer = ast_sip_create_serializer_group_named(tps_name,
+		shutdown_group);
 	if (!state->client_state->serializer) {
 		ao2_cleanup(state);
 		return NULL;
@@ -997,6 +1014,7 @@ static struct sip_outbound_registration_state *sip_outbound_registration_state_a
 	state->client_state->status = SIP_REGISTRATION_UNREGISTERED;
 	state->client_state->timer.user_data = state->client_state;
 	state->client_state->timer.cb = sip_outbound_registration_timer_cb;
+	state->client_state->transport_name = ast_strdup(registration->transport);
 
 	ast_statsd_log_string("PJSIP.registrations.count", AST_STATSD_GAUGE, "+1", 1.0);
 	ast_statsd_log_string_va("PJSIP.registrations.state.%s", AST_STATSD_GAUGE, "+1", 1.0,
@@ -1165,25 +1183,6 @@ static int sip_outbound_registration_regc_alloc(void *data)
 
 	pjsip_endpt_release_pool(ast_sip_get_pjsip_endpoint(), pool);
 
-	if (!ast_strlen_zero(registration->transport)) {
-		RAII_VAR(struct ast_sip_transport *, transport, ast_sorcery_retrieve_by_id(ast_sip_get_sorcery(), "transport", registration->transport), ao2_cleanup);
-
-		if (!transport || !transport->state) {
-			ast_log(LOG_ERROR, "Unable to retrieve PJSIP transport '%s' "
-				" for outbound registration", registration->transport);
-			return -1;
-		}
-
-		if (transport->state->transport) {
-			selector.type = PJSIP_TPSELECTOR_TRANSPORT;
-			selector.u.transport = transport->state->transport;
-		} else if (transport->state->factory) {
-			selector.type = PJSIP_TPSELECTOR_LISTENER;
-			selector.u.listener = transport->state->factory;
-		} else {
-			return -1;
-		}
-	}
 
 	ast_assert(state->client_state->client == NULL);
 	if (pjsip_regc_create(ast_sip_get_pjsip_endpoint(), state->client_state,
@@ -1192,6 +1191,7 @@ static int sip_outbound_registration_regc_alloc(void *data)
 		return -1;
 	}
 
+	ast_sip_set_tpselector_from_transport_name(registration->transport, &selector);
 	pjsip_regc_set_transport(state->client_state->client, &selector);
 
 	if (!ast_strlen_zero(registration->outbound_proxy)) {
@@ -1290,10 +1290,18 @@ static int sip_outbound_registration_apply(const struct ast_sorcery *sorcery, vo
 		ast_log(LOG_ERROR, "No server URI specified on outbound registration '%s'\n",
 			ast_sorcery_object_get_id(applied));
 		return -1;
+	} else if (ast_sip_validate_uri_length(applied->server_uri)) {
+			ast_log(LOG_ERROR, "Server URI or hostname length exceeds pjpropject limit '%s'\n",
+				ast_sorcery_object_get_id(applied));
+			return -1;
 	} else if (ast_strlen_zero(applied->client_uri)) {
 		ast_log(LOG_ERROR, "No client URI specified on outbound registration '%s'\n",
 			ast_sorcery_object_get_id(applied));
 		return -1;
+	} else if (ast_sip_validate_uri_length(applied->client_uri)) {
+			ast_log(LOG_ERROR, "Client URI or hostname length exceeds pjpropject limit '%s'\n",
+				ast_sorcery_object_get_id(applied));
+			return -1;
 	} else if (applied->line && ast_strlen_zero(applied->endpoint)) {
 		ast_log(LOG_ERROR, "Line support has been enabled on outbound registration '%s' without providing an endpoint\n",
 			ast_sorcery_object_get_id(applied));
diff --git a/res/res_pjsip_path.c b/res/res_pjsip_path.c
index d0ee5a4..47d6a79 100644
--- a/res/res_pjsip_path.c
+++ b/res/res_pjsip_path.c
@@ -53,9 +53,13 @@ static struct ast_sip_aor *find_aor(struct ast_sip_endpoint *endpoint, pjsip_uri
 	configured_aors = ast_strdupa(endpoint->aors);
 
 	/* Iterate the configured AORs to see if the user or the user+domain match */
-	while ((aor_name = strsep(&configured_aors, ","))) {
+	while ((aor_name = ast_strip(strsep(&configured_aors, ",")))) {
 		struct ast_sip_domain_alias *alias = NULL;
 
+		if (ast_strlen_zero(aor_name)) {
+			continue;
+		}
+
 		if (!pj_strcmp2(&sip_uri->user, aor_name)) {
 			break;
 		}
diff --git a/res/res_pjsip_pubsub.c b/res/res_pjsip_pubsub.c
index 99376b1..d859b76 100644
--- a/res/res_pjsip_pubsub.c
+++ b/res/res_pjsip_pubsub.c
@@ -1202,8 +1202,6 @@ static void subscription_tree_destructor(void *obj)
 
 	ast_debug(3, "Destroying subscription tree %p\n", sub_tree);
 
-	remove_subscription(sub_tree);
-
 	ao2_cleanup(sub_tree->endpoint);
 
 	destroy_subscriptions(sub_tree->root);
@@ -1234,6 +1232,7 @@ static void subscription_setup_dialog(struct sip_subscription_tree *sub_tree, pj
 static struct sip_subscription_tree *allocate_subscription_tree(struct ast_sip_endpoint *endpoint)
 {
 	struct sip_subscription_tree *sub_tree;
+	char tps_name[AST_TASKPROCESSOR_MAX_NAME + 1];
 
 	sub_tree = ao2_alloc(sizeof *sub_tree, subscription_tree_destructor);
 	if (!sub_tree) {
@@ -1242,7 +1241,11 @@ static struct sip_subscription_tree *allocate_subscription_tree(struct ast_sip_e
 
 	ast_module_ref(ast_module_info->self);
 
-	sub_tree->serializer = ast_sip_create_serializer();
+	/* Create name with seq number appended. */
+	ast_taskprocessor_build_name(tps_name, sizeof(tps_name), "pjsip/pubsub/%s",
+		ast_sorcery_object_get_id(endpoint));
+
+	sub_tree->serializer = ast_sip_create_serializer_named(tps_name);
 	if (!sub_tree->serializer) {
 		ao2_ref(sub_tree, -1);
 		return NULL;
@@ -1251,7 +1254,6 @@ static struct sip_subscription_tree *allocate_subscription_tree(struct ast_sip_e
 	sub_tree->endpoint = ao2_bump(endpoint);
 	sub_tree->notify_sched_id = -1;
 
-	add_subscription(sub_tree);
 	return sub_tree;
 }
 
@@ -1325,6 +1327,8 @@ static struct sip_subscription_tree *create_subscription_tree(const struct ast_s
 		sub_tree->is_list = 1;
 	}
 
+	add_subscription(sub_tree);
+
 	return sub_tree;
 }
 
@@ -1555,6 +1559,28 @@ void *ast_sip_subscription_get_header(const struct ast_sip_subscription *sub, co
 	return pjsip_msg_find_hdr_by_name(msg, &name, NULL);
 }
 
+/*!
+ * \internal
+ * \brief Wrapper for pjsip_evsub_send_request
+ *
+ * This function (re)sets the transport before sending to catch cases
+ * where the transport might have changed.
+ *
+ * If pjproject gives us the ability to resend, we'll only reset the transport
+ * if PJSIP_ETPNOTAVAIL is returned from send.
+ *
+ * \returns pj_status_t
+ */
+static pj_status_t internal_pjsip_evsub_send_request(struct sip_subscription_tree *sub_tree, pjsip_tx_data *tdata)
+{
+	pjsip_tpselector selector = { .type = PJSIP_TPSELECTOR_NONE, };
+
+	ast_sip_set_tpselector_from_transport_name(sub_tree->endpoint->transport, &selector);
+	pjsip_dlg_set_transport(sub_tree->dlg, &selector);
+
+	return pjsip_evsub_send_request(sub_tree->evsub, tdata);
+}
+
 /* XXX This function is not used. */
 struct ast_sip_subscription *ast_sip_create_subscription(const struct ast_sip_subscription_handler *handler,
 		struct ast_sip_endpoint *endpoint, const char *resource)
@@ -1602,7 +1628,7 @@ struct ast_sip_subscription *ast_sip_create_subscription(const struct ast_sip_su
 	evsub = sub_tree->evsub;
 
 	if (pjsip_evsub_initiate(evsub, NULL, -1, &tdata) == PJ_SUCCESS) {
-		pjsip_evsub_send_request(evsub, tdata);
+		internal_pjsip_evsub_send_request(sub_tree, tdata);
 	} else {
 		/* pjsip_evsub_terminate will result in pubsub_on_evsub_state,
 		 * being called and terminating the subscription. Therefore, we don't
@@ -1613,6 +1639,8 @@ struct ast_sip_subscription *ast_sip_create_subscription(const struct ast_sip_su
 		return NULL;
 	}
 
+	add_subscription(sub_tree);
+
 	return sub;
 }
 
@@ -1681,8 +1709,8 @@ 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;
+#endif
 	int res;
 
 	if (allocate_tdata_buffer(tdata)) {
@@ -1690,7 +1718,8 @@ static int sip_subscription_send_request(struct sip_subscription_tree *sub_tree,
 		return -1;
 	}
 
-	res = pjsip_evsub_send_request(evsub, tdata) == PJ_SUCCESS ? 0 : -1;
+	res = internal_pjsip_evsub_send_request(sub_tree, tdata);
+
 	subscription_persistence_update(sub_tree, NULL);
 
 	ast_test_suite_event_notify("SUBSCRIPTION_STATE_SET",
@@ -1699,7 +1728,7 @@ static int sip_subscription_send_request(struct sip_subscription_tree *sub_tree,
 		pjsip_evsub_get_state_name(evsub),
 		ast_sorcery_object_get_id(endpoint));
 
-	return res;
+	return (res == PJ_SUCCESS ? 0 : -1);
 }
 
 /*!
@@ -3290,6 +3319,7 @@ static void pubsub_on_evsub_state(pjsip_evsub *evsub, pjsip_event *event)
 		}
 	}
 
+	remove_subscription(sub_tree);
 	pjsip_evsub_set_mod_data(evsub, pubsub_module.id, NULL);
 	sub_tree->evsub = NULL;
 	ast_sip_dialog_set_serializer(sub_tree->dlg, NULL);
@@ -3624,7 +3654,11 @@ static int list_item_handler(const struct aco_option *opt,
 	char *items = ast_strdupa(var->value);
 	char *item;
 
-	while ((item = strsep(&items, ","))) {
+	while ((item = ast_strip(strsep(&items, ",")))) {
+		if (ast_strlen_zero(item)) {
+			continue;
+		}
+
 		if (item_in_vector(list, item)) {
 			ast_log(LOG_WARNING, "Ignoring duplicated list item '%s'\n", item);
 			continue;
diff --git a/res/res_pjsip_refer.c b/res/res_pjsip_refer.c
index 4896a00..f3af65c 100644
--- a/res/res_pjsip_refer.c
+++ b/res/res_pjsip_refer.c
@@ -59,6 +59,8 @@ struct refer_progress {
 	struct transfer_channel_data *transfer_data;
 	/*! \brief Uniqueid of transferee channel */
 	char *transferee;
+	/*! \brief Non-zero if the 100 notify has been sent */
+	int sent_100;
 };
 
 /*! \brief REFER Progress notification structure */
@@ -133,6 +135,18 @@ static int refer_progress_notify(void *data)
 		notification->progress->sub = NULL;
 	}
 
+	/* Send a deferred initial 100 Trying SIP frag NOTIFY if we haven't already. */
+	if (!notification->progress->sent_100) {
+		notification->progress->sent_100 = 1;
+		if (notification->response != 100) {
+			ast_debug(3, "Sending initial 100 Trying NOTIFY for progress monitor '%p'\n",
+				notification->progress);
+			if (pjsip_xfer_notify(sub, PJSIP_EVSUB_STATE_ACTIVE, 100, NULL, &tdata) == PJ_SUCCESS) {
+				pjsip_xfer_send_request(sub, tdata);
+			}
+		}
+	}
+
 	ast_debug(3, "Sending NOTIFY with response '%d' and state '%u' on subscription '%p' and progress monitor '%p'\n",
 		notification->response, notification->state, sub, notification->progress);
 
@@ -340,8 +354,8 @@ static int refer_progress_alloc(struct ast_sip_session *session, pjsip_rx_data *
 	const pj_str_t str_refer_sub = { "Refer-Sub", 9 };
 	pjsip_generic_string_hdr *refer_sub = NULL;
 	const pj_str_t str_true = { "true", 4 };
-	pjsip_tx_data *tdata;
 	pjsip_hdr hdr_list;
+	char tps_name[AST_TASKPROCESSOR_MAX_NAME + 1];
 
 	*progress = NULL;
 
@@ -363,7 +377,11 @@ static int refer_progress_alloc(struct ast_sip_session *session, pjsip_rx_data *
 	/* To prevent a potential deadlock we need the dialog so we can lock/unlock */
 	(*progress)->dlg = session->inv_session->dlg;
 
-	if (!((*progress)->serializer = ast_sip_create_serializer())) {
+	/* Create name with seq number appended. */
+	ast_taskprocessor_build_name(tps_name, sizeof(tps_name), "pjsip/refer/%s",
+		ast_sorcery_object_get_id(session->endpoint));
+
+	if (!((*progress)->serializer = ast_sip_create_serializer_named(tps_name))) {
 		goto error;
 	}
 
@@ -387,12 +405,6 @@ static int refer_progress_alloc(struct ast_sip_session *session, pjsip_rx_data *
 	ast_debug(3, "Accepting REFER request for progress monitor '%p'\n", *progress);
 	pjsip_xfer_accept((*progress)->sub, rdata, 202, &hdr_list);
 
-	/* Send initial NOTIFY Request */
-	ast_debug(3, "Sending initial 100 Trying NOTIFY for progress monitor '%p'\n", *progress);
-	if (pjsip_xfer_notify((*progress)->sub, PJSIP_EVSUB_STATE_ACTIVE, 100, NULL, &tdata) == PJ_SUCCESS) {
-		pjsip_xfer_send_request((*progress)->sub, tdata);
-	}
-
 	return 0;
 
 error:
@@ -973,6 +985,7 @@ static int refer_incoming_refer_request(struct ast_sip_session *session, struct
 {
 	pjsip_generic_string_hdr *refer_to;
 	char *uri;
+	size_t uri_size;
 	pjsip_uri *target;
 	pjsip_sip_uri *target_uri;
 	RAII_VAR(struct refer_progress *, progress, NULL, ao2_cleanup);
@@ -1006,20 +1019,19 @@ static int refer_incoming_refer_request(struct ast_sip_session *session, struct
 		return 0;
 	}
 
-	/* This is done on purpose (and is safe) - it's done so that the value passed to
-	 * pjsip_parse_uri is NULL terminated as required
+	/* The ast_copy_pj_str to uri is needed because it puts the NULL terminator to the uri
+	 * as pjsip_parse_uri require a NULL terminated uri
 	 */
-	uri = refer_to->hvalue.ptr;
-	uri[refer_to->hvalue.slen] = '\0';
 
-	target = pjsip_parse_uri(rdata->tp_info.pool, refer_to->hvalue.ptr, refer_to->hvalue.slen, 0);
+	uri_size = pj_strlen(&refer_to->hvalue) + 1;
+	uri = ast_alloca(uri_size);
+	ast_copy_pj_str(uri, &refer_to->hvalue, uri_size);
+
+	target = pjsip_parse_uri(rdata->tp_info.pool, uri, uri_size - 1, 0);
+
 	if (!target
 		|| (!PJSIP_URI_SCHEME_IS_SIP(target)
 			&& !PJSIP_URI_SCHEME_IS_SIPS(target))) {
-		size_t uri_size = pj_strlen(&refer_to->hvalue) + 1;
-		char *uri = ast_alloca(uri_size);
-
-		ast_copy_pj_str(uri, &refer_to->hvalue, uri_size);
 
 		pjsip_dlg_respond(session->inv_session->dlg, rdata, 400, NULL, NULL, NULL);
 		ast_debug(3, "Received a REFER without a parseable Refer-To ('%s') on channel '%s' from endpoint '%s'\n",
diff --git a/res/res_pjsip_registrar.c b/res/res_pjsip_registrar.c
index fed4393..1ee0d5f 100644
--- a/res/res_pjsip_registrar.c
+++ b/res/res_pjsip_registrar.c
@@ -18,6 +18,7 @@
 
 /*** MODULEINFO
 	<depend>pjproject</depend>
+	<depend>res_pjproject</depend>
 	<depend>res_pjsip</depend>
 	<support_level>core</support_level>
  ***/
@@ -32,6 +33,7 @@
 #include "asterisk/test.h"
 #include "asterisk/taskprocessor.h"
 #include "asterisk/manager.h"
+#include "asterisk/res_pjproject.h"
 #include "res_pjsip/include/res_pjsip_private.h"
 
 /*** DOCUMENTATION
@@ -51,6 +53,9 @@
 	</manager>
  ***/
 
+static int pj_max_hostname = PJ_MAX_HOSTNAME;
+static int pjsip_max_url_size = PJSIP_MAX_URL_SIZE;
+
 /*! \brief Internal function which returns the expiration time for a contact */
 static int registrar_get_expiration(const struct ast_sip_aor *aor, const pjsip_contact_hdr *contact, const pjsip_rx_data *rdata)
 {
@@ -85,7 +90,7 @@ struct registrar_contact_details {
 	/*! \brief Pool used for parsing URI */
 	pj_pool_t *pool;
 	/*! \brief URI being looked for */
-	pjsip_uri *uri;
+	pjsip_sip_uri *uri;
 };
 
 /*! \brief Callback function for finding a contact */
@@ -113,6 +118,7 @@ static int registrar_validate_contacts(const pjsip_rx_data *rdata, struct ao2_co
 	while ((contact = (pjsip_contact_hdr *) pjsip_msg_find_hdr(rdata->msg_info.msg, PJSIP_H_CONTACT, contact->next))) {
 		int expiration = registrar_get_expiration(aor, contact, rdata);
 		RAII_VAR(struct ast_sip_contact *, existing, NULL, ao2_cleanup);
+		char contact_uri[pjsip_max_url_size];
 
 		if (contact->star) {
 			/* The expiration MUST be 0 when a '*' contact is used and there must be no other contact */
@@ -134,6 +140,19 @@ static int registrar_validate_contacts(const pjsip_rx_data *rdata, struct ao2_co
 
 		details.uri = pjsip_uri_get_uri(contact->uri);
 
+		/* pjsip_uri_print returns -1 if there's not enough room in the buffer */
+		if (pjsip_uri_print(PJSIP_URI_IN_CONTACT_HDR, details.uri, contact_uri, sizeof(contact_uri)) < 0) {
+			/* If the total length of the uri is greater than pjproject can handle, go no further */
+			pjsip_endpt_release_pool(ast_sip_get_pjsip_endpoint(), details.pool);
+			return -1;
+		}
+
+		if (details.uri->host.slen >= pj_max_hostname) {
+			/* If the length of the hostname is greater than pjproject can handle, go no further */
+			pjsip_endpt_release_pool(ast_sip_get_pjsip_endpoint(), details.pool);
+			return -1;
+		}
+
 		/* Determine if this is an add, update, or delete for policy enforcement purposes */
 		if (!(existing = ao2_callback(contacts, 0, registrar_find_contact, &details))) {
 			if (expiration) {
@@ -231,6 +250,7 @@ static void serializer_destroy(void *obj)
 
 static struct serializer *serializer_create(const char *aor_name)
 {
+	char tps_name[AST_TASKPROCESSOR_MAX_NAME + 1];
 	size_t size = strlen(aor_name) + 1;
 	struct serializer *ser = ao2_alloc(
 		sizeof(*ser) + size, serializer_destroy);
@@ -239,7 +259,11 @@ static struct serializer *serializer_create(const char *aor_name)
 		return NULL;
 	}
 
-	if (!(ser->serializer = ast_sip_create_serializer())) {
+	/* Create name with seq number appended. */
+	ast_taskprocessor_build_name(tps_name, sizeof(tps_name), "pjsip/aor/%s",
+		aor_name);
+
+	if (!(ser->serializer = ast_sip_create_serializer_named(tps_name))) {
 		ao2_ref(ser, -1);
 		return NULL;
 	}
@@ -472,7 +496,7 @@ static int rx_task(void *data)
 	/* Iterate each provided Contact header and add, update, or delete */
 	while ((contact_hdr = pjsip_msg_find_hdr(task_data->rdata->msg_info.msg, PJSIP_H_CONTACT, contact_hdr ? contact_hdr->next : NULL))) {
 		int expiration;
-		char contact_uri[PJSIP_MAX_URL_SIZE];
+		char contact_uri[pjsip_max_url_size];
 		RAII_VAR(struct ast_sip_contact *, contact, NULL, ao2_cleanup);
 
 		if (contact_hdr->star) {
@@ -557,7 +581,6 @@ static int rx_task(void *data)
 			ao2_cleanup(contact_update);
 		} else {
 			/* We want to report the user agent that was actually in the removed contact */
-			user_agent = ast_strdupa(contact->user_agent);
 			ast_sip_location_delete_contact(contact);
 			ast_verb(3, "Removed contact '%s' from AOR '%s' due to request\n", contact_uri, aor_name);
 			ast_test_suite_event_notify("AOR_CONTACT_REMOVED",
@@ -566,7 +589,7 @@ static int rx_task(void *data)
 					"UserAgent: %s",
 					contact_uri,
 					aor_name,
-					user_agent);
+					contact->user_agent);
 		}
 	}
 
@@ -646,9 +669,13 @@ static pj_bool_t registrar_on_rx_request(struct pjsip_rx_data *rdata)
 	configured_aors = ast_strdupa(endpoint->aors);
 
 	/* Iterate the configured AORs to see if the user or the user+domain match */
-	while ((aor_name = strsep(&configured_aors, ","))) {
+	while ((aor_name = ast_strip(strsep(&configured_aors, ",")))) {
 		struct ast_sip_domain_alias *alias = NULL;
 
+		if (ast_strlen_zero(aor_name)) {
+			continue;
+		}
+
 		if (!pj_strcmp2(&uri->user, aor_name)) {
 			break;
 		}
@@ -791,6 +818,12 @@ static int load_module(void)
 {
 	const pj_str_t STR_REGISTER = { "REGISTER", 8 };
 
+	CHECK_PJPROJECT_MODULE_LOADED();
+
+	ast_pjproject_get_buildopt("PJ_MAX_HOSTNAME", "%d", &pj_max_hostname);
+	/* As of pjproject 2.4.5, PJSIP_MAX_URL_SIZE isn't exposed yet but we try anyway. */
+	ast_pjproject_get_buildopt("PJSIP_MAX_URL_SIZE", "%d", &pjsip_max_url_size);
+
 	CHECK_PJSIP_MODULE_LOADED();
 
 	if (!(serializers = ao2_container_alloc(
diff --git a/res/res_pjsip_sdp_rtp.c b/res/res_pjsip_sdp_rtp.c
index f733ea4..18a7f3f 100644
--- a/res/res_pjsip_sdp_rtp.c
+++ b/res/res_pjsip_sdp_rtp.c
@@ -175,8 +175,15 @@ static int rtp_check_timeout(const void *data)
 static int create_rtp(struct ast_sip_session *session, struct ast_sip_session_media *session_media, unsigned int ipv6)
 {
 	struct ast_rtp_engine_ice *ice;
+	struct ast_sockaddr temp_media_address;
+	struct ast_sockaddr *media_address =  ipv6 ? &address_ipv6 : &address_ipv4;
 
-	if (!(session_media->rtp = ast_rtp_instance_new(session->endpoint->media.rtp.engine, sched, ipv6 ? &address_ipv6 : &address_ipv4, NULL))) {
+	if (session->endpoint->media.bind_rtp_to_media_address && !ast_strlen_zero(session->endpoint->media.address)) {
+		ast_sockaddr_parse(&temp_media_address, session->endpoint->media.address, 0);
+		media_address = &temp_media_address;
+	}
+
+	if (!(session_media->rtp = ast_rtp_instance_new(session->endpoint->media.rtp.engine, sched, media_address, NULL))) {
 		ast_log(LOG_ERROR, "Unable to create RTP instance using RTP engine '%s'\n", session->endpoint->media.rtp.engine);
 		return -1;
 	}
@@ -1343,11 +1350,12 @@ static int apply_negotiated_sdp_stream(struct ast_sip_session *session, struct a
 /*! \brief Function which updates the media stream with external media address, if applicable */
 static void change_outgoing_sdp_stream_media_address(pjsip_tx_data *tdata, struct pjmedia_sdp_media *stream, struct ast_sip_transport *transport)
 {
+	RAII_VAR(struct ast_sip_transport_state *, transport_state, ast_sip_get_transport_state(ast_sorcery_object_get_id(transport)), ao2_cleanup);
 	char host[NI_MAXHOST];
 	struct ast_sockaddr addr = { { 0, } };
 
 	/* If the stream has been rejected there will be no connection line */
-	if (!stream->conn) {
+	if (!stream->conn || !transport_state) {
 		return;
 	}
 
@@ -1355,7 +1363,7 @@ static void change_outgoing_sdp_stream_media_address(pjsip_tx_data *tdata, struc
 	ast_sockaddr_parse(&addr, host, PARSE_PORT_FORBID);
 
 	/* Is the address within the SDP inside the same network? */
-	if (ast_apply_ha(transport->localnet, &addr) == AST_SENSE_ALLOW) {
+	if (ast_apply_ha(transport_state->localnet, &addr) == AST_SENSE_ALLOW) {
 		return;
 	}
 
diff --git a/res/res_pjsip_send_to_voicemail.c b/res/res_pjsip_send_to_voicemail.c
index 3a57aea..12d6b74 100644
--- a/res/res_pjsip_send_to_voicemail.c
+++ b/res/res_pjsip_send_to_voicemail.c
@@ -47,7 +47,8 @@
 #define SEND_TO_VM_HEADER_VALUE "feature_send_to_vm"
 
 #define SEND_TO_VM_REDIRECT "REDIRECTING(reason)"
-#define SEND_TO_VM_REDIRECT_VALUE "\"send_to_vm\""
+#define SEND_TO_VM_REDIRECT_VALUE "send_to_vm"
+#define SEND_TO_VM_REDIRECT_QUOTED_VALUE "\"" SEND_TO_VM_REDIRECT_VALUE "\""
 
 static void send_response(struct ast_sip_session *session, int code, struct pjsip_rx_data *rdata)
 {
@@ -102,9 +103,13 @@ static int has_diversion_reason(pjsip_rx_data *rdata)
 	pjsip_param *reason;
 	pjsip_fromto_hdr *hdr = get_diversion_header(rdata);
 
-	return hdr &&
-		(reason = get_diversion_reason(hdr)) &&
-		!pj_stricmp2(&reason->value, SEND_TO_VM_REDIRECT_VALUE);
+	if (!hdr) {
+		return 0;
+	}
+	reason = get_diversion_reason(hdr);
+	return reason
+		&& (!pj_stricmp2(&reason->value, SEND_TO_VM_REDIRECT_QUOTED_VALUE)
+			|| !pj_stricmp2(&reason->value, SEND_TO_VM_REDIRECT_VALUE));
 }
 
 static int has_call_feature(pjsip_rx_data *rdata)
@@ -160,12 +165,10 @@ static int handle_incoming_request(struct ast_sip_session *session, struct pjsip
 	sip_session_datastore->data = other_party;
 
 	if (ast_sip_session_add_datastore(session, sip_session_datastore)) {
-		ast_channel_unref(other_party);
 		ao2_ref(sip_session_datastore, -1);
 		send_response(session, 500, rdata);
 		return -1;
 	}
-	ao2_ref(sip_session_datastore, -1);
 
 	if (has_feature) {
 		pbx_builtin_setvar_helper(other_party, SEND_TO_VM_HEADER,
@@ -177,6 +180,7 @@ static int handle_incoming_request(struct ast_sip_session *session, struct pjsip
 					  SEND_TO_VM_REDIRECT_VALUE);
 	}
 
+	ao2_ref(sip_session_datastore, -1);
 	return 0;
 }
 
diff --git a/res/res_pjsip_session.c b/res/res_pjsip_session.c
index 6044ceb..3b91f58 100644
--- a/res/res_pjsip_session.c
+++ b/res/res_pjsip_session.c
@@ -30,6 +30,7 @@
 
 #include "asterisk/res_pjsip.h"
 #include "asterisk/res_pjsip_session.h"
+#include "asterisk/callerid.h"
 #include "asterisk/datastore.h"
 #include "asterisk/module.h"
 #include "asterisk/logger.h"
@@ -800,6 +801,75 @@ static pjmedia_sdp_session *generate_session_refresh_sdp(struct ast_sip_session
 	return create_local_sdp(inv_session, session, previous_sdp);
 }
 
+static void set_from_header(struct ast_sip_session *session)
+{
+	struct ast_party_id effective_id;
+	struct ast_party_id connected_id;
+	pj_pool_t *dlg_pool;
+	pjsip_fromto_hdr *dlg_info;
+	pjsip_name_addr *dlg_info_name_addr;
+	pjsip_sip_uri *dlg_info_uri;
+	int restricted;
+
+	if (!session->channel || session->saved_from_hdr) {
+		return;
+	}
+
+	/* We need to save off connected_id for RPID/PAI generation */
+	ast_party_id_init(&connected_id);
+	ast_channel_lock(session->channel);
+	effective_id = ast_channel_connected_effective_id(session->channel);
+	ast_party_id_copy(&connected_id, &effective_id);
+	ast_channel_unlock(session->channel);
+
+	restricted =
+		((ast_party_id_presentation(&connected_id) & AST_PRES_RESTRICTION) != AST_PRES_ALLOWED);
+
+	/* Now set up dlg->local.info so pjsip can correctly generate From */
+
+	dlg_pool = session->inv_session->dlg->pool;
+	dlg_info = session->inv_session->dlg->local.info;
+	dlg_info_name_addr = (pjsip_name_addr *) dlg_info->uri;
+	dlg_info_uri = pjsip_uri_get_uri(dlg_info_name_addr);
+
+	if (session->endpoint->id.trust_outbound || !restricted) {
+		ast_sip_modify_id_header(dlg_pool, dlg_info, &connected_id);
+	}
+
+	ast_party_id_free(&connected_id);
+
+	if (!ast_strlen_zero(session->endpoint->fromuser)) {
+		dlg_info_name_addr->display.ptr = NULL;
+		dlg_info_name_addr->display.slen = 0;
+		pj_strdup2(dlg_pool, &dlg_info_uri->user, session->endpoint->fromuser);
+	}
+
+	if (!ast_strlen_zero(session->endpoint->fromdomain)) {
+		pj_strdup2(dlg_pool, &dlg_info_uri->host, session->endpoint->fromdomain);
+	}
+
+	ast_sip_add_usereqphone(session->endpoint, dlg_pool, dlg_info->uri);
+
+	/* We need to save off the non-anonymized From for RPID/PAI generation (for domain) */
+	session->saved_from_hdr = pjsip_hdr_clone(dlg_pool, dlg_info);
+
+	/* In chan_sip, fromuser and fromdomain trump restricted so we only
+	 * anonymize if they're not set.
+	 */
+	if (restricted) {
+		/* fromuser doesn't provide a display name so we always set it */
+		pj_strdup2(dlg_pool, &dlg_info_name_addr->display, "Anonymous");
+
+		if (ast_strlen_zero(session->endpoint->fromuser)) {
+			pj_strdup2(dlg_pool, &dlg_info_uri->user, "anonymous");
+		}
+
+		if (ast_strlen_zero(session->endpoint->fromdomain)) {
+			pj_strdup2(dlg_pool, &dlg_info_uri->host, "anonymous.invalid");
+		}
+	}
+}
+
 int ast_sip_session_refresh(struct ast_sip_session *session,
 		ast_sip_session_request_creation_cb on_request_creation,
 		ast_sip_session_sdp_creation_cb on_sdp_creation,
@@ -867,6 +937,12 @@ int ast_sip_session_refresh(struct ast_sip_session *session,
 		}
 	}
 
+	/*
+	 * We MUST call set_from_header() before pjsip_inv_(reinvite|update).  If we don't, the
+	 * From in the reINVITE/UPDATE will be wrong but the rest of the messages will be OK.
+	 */
+	set_from_header(session);
+
 	if (method == AST_SIP_SESSION_REFRESH_METHOD_INVITE) {
 		if (pjsip_inv_reinvite(inv_session, NULL, new_sdp, &tdata)) {
 			ast_log(LOG_WARNING, "Failed to create reinvite properly.\n");
@@ -888,10 +964,32 @@ int ast_sip_session_refresh(struct ast_sip_session *session,
 	return 0;
 }
 
+/*!
+ * \internal
+ * \brief Wrapper for pjsip_inv_send_msg
+ *
+ * This function (re)sets the transport before sending to catch cases
+ * where the transport might have changed.
+ *
+ * If pjproject gives us the ability to resend, we'll only reset the transport
+ * if PJSIP_ETPNOTAVAIL is returned from send.
+ *
+ * \returns pj_status_t
+ */
+static pj_status_t internal_pjsip_inv_send_msg(pjsip_inv_session *inv, const char *transport_name, pjsip_tx_data *tdata)
+{
+	pjsip_tpselector selector = { .type = PJSIP_TPSELECTOR_NONE, };
+
+	ast_sip_set_tpselector_from_transport_name(transport_name, &selector);
+	pjsip_dlg_set_transport(inv->dlg, &selector);
+
+	return pjsip_inv_send_msg(inv, tdata);
+}
+
 void ast_sip_session_send_response(struct ast_sip_session *session, pjsip_tx_data *tdata)
 {
 	handle_outgoing_response(session, tdata);
-	pjsip_inv_send_msg(session->inv_session, tdata);
+	internal_pjsip_inv_send_msg(session->inv_session, session->endpoint->transport, tdata);
 	return;
 }
 
@@ -1060,6 +1158,7 @@ static pjsip_module session_reinvite_module = {
 	.on_rx_request = session_reinvite_on_rx_request,
 };
 
+
 void ast_sip_session_send_request_with_cb(struct ast_sip_session *session, pjsip_tx_data *tdata,
 		ast_sip_session_response_cb on_response)
 {
@@ -1073,21 +1172,9 @@ void ast_sip_session_send_request_with_cb(struct ast_sip_session *session, pjsip
 	ast_sip_mod_data_set(tdata->pool, tdata->mod_data, session_module.id,
 			     MOD_DATA_ON_RESPONSE, on_response);
 
-	if (!ast_strlen_zero(session->endpoint->fromuser) ||
-		!ast_strlen_zero(session->endpoint->fromdomain)) {
-		pjsip_fromto_hdr *from = pjsip_msg_find_hdr(tdata->msg, PJSIP_H_FROM, tdata->msg->hdr.next);
-		pjsip_sip_uri *uri = pjsip_uri_get_uri(from->uri);
-
-		if (!ast_strlen_zero(session->endpoint->fromuser)) {
-			pj_strdup2(tdata->pool, &uri->user, session->endpoint->fromuser);
-		}
-		if (!ast_strlen_zero(session->endpoint->fromdomain)) {
-			pj_strdup2(tdata->pool, &uri->host, session->endpoint->fromdomain);
-		}
-	}
-
 	handle_outgoing_request(session, tdata);
-	pjsip_inv_send_msg(session->inv_session, tdata);
+	internal_pjsip_inv_send_msg(session->inv_session, session->endpoint->transport, tdata);
+
 	return;
 }
 
@@ -1110,9 +1197,17 @@ int ast_sip_session_create_invite(struct ast_sip_session *session, pjsip_tx_data
 #ifdef PJMEDIA_SDP_NEG_ANSWER_MULTIPLE_CODECS
 	pjmedia_sdp_neg_set_answer_multiple_codecs(session->inv_session->neg, PJ_TRUE);
 #endif
+
+	/*
+	 * We MUST call set_from_header() before pjsip_inv_invite.  If we don't, the
+	 * From in the initial INVITE will be wrong but the rest of the messages will be OK.
+	 */
+	set_from_header(session);
+
 	if (pjsip_inv_invite(session->inv_session, tdata) != PJ_SUCCESS) {
 		return -1;
 	}
+
 	return 0;
 }
 
@@ -1266,6 +1361,7 @@ struct ast_sip_session *ast_sip_session_alloc(struct ast_sip_endpoint *endpoint,
 	RAII_VAR(struct ast_sip_session *, session, NULL, ao2_cleanup);
 	struct ast_sip_session_supplement *iter;
 	int dsp_features = 0;
+	char tps_name[AST_TASKPROCESSOR_MAX_NAME + 1];
 
 	session = ao2_alloc(sizeof(*session), session_destructor);
 	if (!session) {
@@ -1286,7 +1382,11 @@ struct ast_sip_session *ast_sip_session_alloc(struct ast_sip_endpoint *endpoint,
 	/* fill session->media with available types */
 	ao2_callback(sdp_handlers, OBJ_NODATA, add_session_media, session);
 
-	session->serializer = ast_sip_create_serializer();
+	/* Create name with seq number appended. */
+	ast_taskprocessor_build_name(tps_name, sizeof(tps_name), "pjsip/session/%s",
+		ast_sorcery_object_get_id(endpoint));
+
+	session->serializer = ast_sip_create_serializer_named(tps_name);
 	if (!session->serializer) {
 		return NULL;
 	}
@@ -1847,7 +1947,7 @@ static pjsip_inv_session *pre_session_setup(pjsip_rx_data *rdata, const struct a
 		if (pjsip_inv_initial_answer(inv_session, rdata, 500, NULL, NULL, &tdata) != PJ_SUCCESS) {
 			pjsip_inv_terminate(inv_session, 500, PJ_FALSE);
 		}
-		pjsip_inv_send_msg(inv_session, tdata);
+		internal_pjsip_inv_send_msg(inv_session, endpoint->transport, tdata);
 		return NULL;
 	}
 	return inv_session;
@@ -2000,7 +2100,7 @@ static void handle_new_invite_request(pjsip_rx_data *rdata)
 		if (pjsip_inv_initial_answer(inv_session, rdata, 500, NULL, NULL, &tdata) == PJ_SUCCESS) {
 			pjsip_inv_terminate(inv_session, 500, PJ_FALSE);
 		} else {
-			pjsip_inv_send_msg(inv_session, tdata);
+			internal_pjsip_inv_send_msg(inv_session, endpoint->transport, tdata);
 		}
 		return;
 	}
@@ -2010,7 +2110,7 @@ static void handle_new_invite_request(pjsip_rx_data *rdata)
 		if (pjsip_inv_initial_answer(inv_session, rdata, 500, NULL, NULL, &tdata) == PJ_SUCCESS) {
 			pjsip_inv_terminate(inv_session, 500, PJ_FALSE);
 		} else {
-			pjsip_inv_send_msg(inv_session, tdata);
+			internal_pjsip_inv_send_msg(inv_session, endpoint->transport, tdata);
 		}
 		ao2_cleanup(invite);
 	}
@@ -2768,13 +2868,14 @@ static pjsip_inv_callback inv_callback = {
 /*! \brief Hook for modifying outgoing messages with SDP to contain the proper address information */
 static void session_outgoing_nat_hook(pjsip_tx_data *tdata, struct ast_sip_transport *transport)
 {
+	RAII_VAR(struct ast_sip_transport_state *, transport_state, ast_sip_get_transport_state(ast_sorcery_object_get_id(transport)), ao2_cleanup);
 	struct ast_sip_nat_hook *hook = ast_sip_mod_data_get(
 		tdata->mod_data, session_module.id, MOD_DATA_NAT_HOOK);
 	struct pjmedia_sdp_session *sdp;
 	int stream;
 
 	/* SDP produced by us directly will never be multipart */
-	if (hook || !tdata->msg->body || pj_stricmp2(&tdata->msg->body->content_type.type, "application") ||
+	if (!transport_state || hook || !tdata->msg->body || pj_stricmp2(&tdata->msg->body->content_type.type, "application") ||
 		pj_stricmp2(&tdata->msg->body->content_type.subtype, "sdp") || ast_strlen_zero(transport->external_media_address)) {
 		return;
 	}
@@ -2788,7 +2889,7 @@ static void session_outgoing_nat_hook(pjsip_tx_data *tdata, struct ast_sip_trans
 		ast_copy_pj_str(host, &sdp->conn->addr, sizeof(host));
 		ast_sockaddr_parse(&addr, host, PARSE_PORT_FORBID);
 
-		if (ast_apply_ha(transport->localnet, &addr) != AST_SENSE_ALLOW) {
+		if (ast_apply_ha(transport_state->localnet, &addr) != AST_SENSE_ALLOW) {
 			pj_strdup2(tdata->pool, &sdp->conn->addr, transport->external_media_address);
 		}
 	}
diff --git a/res/res_pjsip_t38.c b/res/res_pjsip_t38.c
index 2c544bb..14207d9 100644
--- a/res/res_pjsip_t38.c
+++ b/res/res_pjsip_t38.c
@@ -570,41 +570,6 @@ static struct ast_sip_session_supplement t38_supplement = {
 	.outgoing_request = t38_outgoing_invite_request,
 };
 
-static int t38_incoming_bye_request(struct ast_sip_session *session, struct pjsip_rx_data *rdata)
-{
-	struct ast_datastore *datastore;
-	struct ast_sip_session_media *session_media;
-
-	if (!session->channel) {
-		return 0;
-	}
-
-	datastore = ast_sip_session_get_datastore(session, "t38");
-	if (!datastore) {
-		return 0;
-	}
-
-	session_media = ao2_find(session->media, "image", OBJ_KEY);
-	if (!session_media) {
-		ao2_ref(datastore, -1);
-		return 0;
-	}
-
-	t38_change_state(session, session_media, datastore->data, T38_REJECTED);
-
-	ao2_ref(datastore, -1);
-	ao2_ref(session_media, -1);
-
-	return 0;
-}
-
-/*! \brief Supplement for handling a remote termination of T.38 state */
-static struct ast_sip_session_supplement t38_bye_supplement = {
-	.method = "BYE",
-	.priority = AST_SIP_SUPPLEMENT_PRIORITY_CHANNEL + 1,
-	.incoming_request = t38_incoming_bye_request,
-};
-
 /*! \brief Parse a T.38 image stream and store the attribute information */
 static void t38_interpret_sdp(struct t38_state *state, struct ast_sip_session *session, struct ast_sip_session_media *session_media,
 	const struct pjmedia_sdp_media *stream)
@@ -890,11 +855,12 @@ static int apply_negotiated_sdp_stream(struct ast_sip_session *session, struct a
 /*! \brief Function which updates the media stream with external media address, if applicable */
 static void change_outgoing_sdp_stream_media_address(pjsip_tx_data *tdata, struct pjmedia_sdp_media *stream, struct ast_sip_transport *transport)
 {
+	RAII_VAR(struct ast_sip_transport_state *, transport_state, ast_sip_get_transport_state(ast_sorcery_object_get_id(transport)), ao2_cleanup);
 	char host[NI_MAXHOST];
 	struct ast_sockaddr addr = { { 0, } };
 
 	/* If the stream has been rejected there will be no connection line */
-	if (!stream->conn) {
+	if (!stream->conn || !transport_state) {
 		return;
 	}
 
@@ -902,7 +868,7 @@ static void change_outgoing_sdp_stream_media_address(pjsip_tx_data *tdata, struc
 	ast_sockaddr_parse(&addr, host, PARSE_PORT_FORBID);
 
 	/* Is the address within the SDP inside the same network? */
-	if (ast_apply_ha(transport->localnet, &addr) == AST_SENSE_ALLOW) {
+	if (ast_apply_ha(transport_state->localnet, &addr) == AST_SENSE_ALLOW) {
 		return;
 	}
 
@@ -934,7 +900,6 @@ static int unload_module(void)
 {
 	ast_sip_session_unregister_sdp_handler(&image_sdp_handler, "image");
 	ast_sip_session_unregister_supplement(&t38_supplement);
-	ast_sip_session_unregister_supplement(&t38_bye_supplement);
 
 	return 0;
 }
@@ -961,11 +926,6 @@ static int load_module(void)
 		goto end;
 	}
 
-	if (ast_sip_session_register_supplement(&t38_bye_supplement)) {
-		ast_log(LOG_ERROR, "Unable to register T.38 BYE session supplement\n");
-		goto end;
-	}
-
 	if (ast_sip_session_register_sdp_handler(&image_sdp_handler, "image")) {
 		ast_log(LOG_ERROR, "Unable to register SDP handler for image stream type\n");
 		goto end;
diff --git a/res/res_pjsip_keepalive.c b/res/res_pjsip_transport_management.c
similarity index 55%
rename from res/res_pjsip_keepalive.c
rename to res/res_pjsip_transport_management.c
index b854fc9..afd94eb 100644
--- a/res/res_pjsip_keepalive.c
+++ b/res/res_pjsip_transport_management.c
@@ -24,6 +24,8 @@
 
 #include "asterisk.h"
 
+#include <signal.h>
+
 #include <pjsip.h>
 #include <pjsip_ua.h>
 
@@ -32,7 +34,9 @@
 #include "asterisk/astobj2.h"
 
 /*! \brief Number of buckets for keepalive transports */
-#define KEEPALIVE_TRANSPORTS_BUCKETS 53
+#define TRANSPORTS_BUCKETS 53
+
+#define IDLE_TIMEOUT (pjsip_cfg()->tsx.td)
 
 /*! \brief The keep alive packet to send */
 static const pj_str_t keepalive_packet = { "\r\n\r\n", 4 };
@@ -40,6 +44,9 @@ static const pj_str_t keepalive_packet = { "\r\n\r\n", 4 };
 /*! \brief Global container of active transports */
 static struct ao2_container *transports;
 
+/*! \brief Scheduler context for timing out connections with no data received */
+static struct ast_sched_context *sched;
+
 /*! \brief Thread keeping things alive */
 static pthread_t keepalive_thread = AST_PTHREADT_NULL;
 
@@ -49,24 +56,26 @@ static unsigned int keepalive_interval;
 /*! \brief Existing transport manager callback that we need to invoke */
 static pjsip_tp_state_callback tpmgr_state_callback;
 
-/*! \brief Structure for transport to be kept alive */
-struct keepalive_transport {
+/*! \brief Structure for transport to be monitored */
+struct monitored_transport {
 	/*! \brief The underlying PJSIP transport */
 	pjsip_transport *transport;
+	/*! \brief Non-zero if a PJSIP request was received */
+	int sip_received;
 };
 
 /*! \brief Callback function to send keepalive */
 static int keepalive_transport_cb(void *obj, void *arg, int flags)
 {
-	struct keepalive_transport *keepalive = obj;
+	struct monitored_transport *monitored = obj;
 	pjsip_tpselector selector = {
 		.type = PJSIP_TPSELECTOR_TRANSPORT,
-		.u.transport = keepalive->transport,
+		.u.transport = monitored->transport,
 	};
 
 	pjsip_tpmgr_send_raw(pjsip_endpt_get_tpmgr(ast_sip_get_pjsip_endpoint()),
-		keepalive->transport->key.type, &selector, NULL, keepalive_packet.ptr, keepalive_packet.slen,
-		&keepalive->transport->key.rem_addr, pj_sockaddr_get_len(&keepalive->transport->key.rem_addr),
+		monitored->transport->key.type, &selector, NULL, keepalive_packet.ptr, keepalive_packet.slen,
+		&monitored->transport->key.rem_addr, pj_sockaddr_get_len(&monitored->transport->key.rem_addr),
 		NULL, NULL);
 
 	return 0;
@@ -86,7 +95,7 @@ static void *keepalive_transport_thread(void *data)
 	/* Once loaded this module just keeps on going as it is unsafe to stop and change the underlying
 	 * callback for the transport manager.
 	 */
-	while (1) {
+	while (keepalive_interval) {
 		sleep(keepalive_interval);
 		ao2_callback(transports, OBJ_NODATA, keepalive_transport_cb, NULL);
 	}
@@ -94,32 +103,78 @@ static void *keepalive_transport_thread(void *data)
 	return NULL;
 }
 
+AST_THREADSTORAGE(desc_storage);
+
+static int idle_sched_cb(const void *data)
+{
+	struct monitored_transport *keepalive = (struct monitored_transport *) data;
+	int sip_received = ast_atomic_fetchadd_int(&keepalive->sip_received, 0);
+
+	if (!pj_thread_is_registered()) {
+		pj_thread_t *thread;
+		pj_thread_desc *desc;
+
+		desc = ast_threadstorage_get(&desc_storage, sizeof(pj_thread_desc));
+		if (!desc) {
+			ast_log(LOG_ERROR, "Could not get thread desc from thread-local storage.\n");
+			ao2_ref(keepalive, -1);
+			return 0;
+		}
+
+		pj_bzero(*desc, sizeof(*desc));
+
+		pj_thread_register("Transport Monitor", *desc, &thread);
+	}
+
+	if (!sip_received) {
+		ast_log(LOG_NOTICE, "Shutting down transport '%s' since no request was received in %d seconds\n",
+				keepalive->transport->info, IDLE_TIMEOUT);
+		pjsip_transport_shutdown(keepalive->transport);
+	}
+
+	ao2_ref(keepalive, -1);
+	return 0;
+}
+
 /*! \brief Destructor for keepalive transport */
-static void keepalive_transport_destroy(void *obj)
+static void monitored_transport_destroy(void *obj)
 {
-	struct keepalive_transport *keepalive = obj;
+	struct monitored_transport *keepalive = obj;
 
 	pjsip_transport_dec_ref(keepalive->transport);
 }
 
 /*! \brief Callback invoked when transport changes occur */
-static void keepalive_transport_state_callback(pjsip_transport *transport, pjsip_transport_state state,
+static void monitored_transport_state_callback(pjsip_transport *transport, pjsip_transport_state state,
 	const pjsip_transport_state_info *info)
 {
-	/* We only care about connection-oriented transports */
-	if (transport->flag & PJSIP_TRANSPORT_RELIABLE) {
-		struct keepalive_transport *keepalive;
+	/* We only care about reliable transports */
+	if (PJSIP_TRANSPORT_IS_RELIABLE(transport) &&
+			(transport->dir == PJSIP_TP_DIR_INCOMING || keepalive_interval)) {
+		struct monitored_transport *monitored;
 
 		switch (state) {
 		case PJSIP_TP_STATE_CONNECTED:
-			keepalive = ao2_alloc(sizeof(*keepalive), keepalive_transport_destroy);
-			if (keepalive) {
-				keepalive->transport = transport;
-				pjsip_transport_add_ref(keepalive->transport);
-				ao2_link(transports, keepalive);
-				ao2_ref(keepalive, -1);
+			monitored = ao2_alloc(sizeof(*monitored), monitored_transport_destroy);
+			if (!monitored) {
+				break;
+			}
+			monitored->transport = transport;
+			pjsip_transport_add_ref(monitored->transport);
+			ao2_link(transports, monitored);
+			if (transport->dir == PJSIP_TP_DIR_INCOMING) {
+				/* Let the scheduler inherit the reference from allocation */
+				if (ast_sched_add_variable(sched, IDLE_TIMEOUT, idle_sched_cb, monitored, 1) < 0) {
+					ao2_unlink(transports, monitored);
+					ao2_ref(monitored, -1);
+					pjsip_transport_shutdown(transport);
+				}
+			} else {
+				/* No scheduled task, so get rid of the allocation reference */
+				ao2_ref(monitored, -1);
 			}
 			break;
+		case PJSIP_TP_STATE_SHUTDOWN:
 		case PJSIP_TP_STATE_DISCONNECTED:
 			ao2_find(transports, transport->obj_name, OBJ_SEARCH_KEY | OBJ_NODATA | OBJ_UNLINK);
 			break;
@@ -134,10 +189,10 @@ static void keepalive_transport_state_callback(pjsip_transport *transport, pjsip
 	}
 }
 
-/*! \brief Hashing function for keepalive transport */
-static int keepalive_transport_hash_fn(const void *obj, int flags)
+/*! \brief Hashing function for monitored transport */
+static int monitored_transport_hash_fn(const void *obj, int flags)
 {
-	const struct keepalive_transport *object;
+	const struct monitored_transport *object;
 	const char *key;
 
 	switch (flags & OBJ_SEARCH_MASK) {
@@ -156,11 +211,11 @@ static int keepalive_transport_hash_fn(const void *obj, int flags)
 	return ast_str_hash(key);
 }
 
-/*! \brief Comparison function for keepalive transport */
-static int keepalive_transport_cmp_fn(void *obj, void *arg, int flags)
+/*! \brief Comparison function for monitored transport */
+static int monitored_transport_cmp_fn(void *obj, void *arg, int flags)
 {
-	const struct keepalive_transport *object_left = obj;
-	const struct keepalive_transport *object_right = arg;
+	const struct monitored_transport *object_left = obj;
+	const struct monitored_transport *object_right = arg;
 	const char *right_key = arg;
 	int cmp;
 
@@ -193,7 +248,6 @@ static int keepalive_transport_cmp_fn(void *obj, void *arg, int flags)
 static void keepalive_global_loaded(const char *object_type)
 {
 	unsigned int new_interval = ast_sip_get_keep_alive_interval();
-	pjsip_tpmgr *tpmgr;
 
 	if (new_interval) {
 		keepalive_interval = new_interval;
@@ -209,28 +263,11 @@ static void keepalive_global_loaded(const char *object_type)
 		return;
 	}
 
-	transports = ao2_container_alloc(KEEPALIVE_TRANSPORTS_BUCKETS, keepalive_transport_hash_fn,
-		keepalive_transport_cmp_fn);
-	if (!transports) {
-		ast_log(LOG_ERROR, "Could not create container for transports to perform keepalive on.\n");
-		return;
-	}
-
-	tpmgr = pjsip_endpt_get_tpmgr(ast_sip_get_pjsip_endpoint());
-	if (!tpmgr) {
-		ast_log(LOG_ERROR, "No transport manager to attach keepalive functionality to.\n");
-		ao2_ref(transports, -1);
-		return;
-	}
-
 	if (ast_pthread_create(&keepalive_thread, NULL, keepalive_transport_thread, NULL)) {
 		ast_log(LOG_ERROR, "Could not create thread for sending keepalive messages.\n");
 		ao2_ref(transports, -1);
 		return;
 	}
-
-	tpmgr_state_callback = pjsip_tpmgr_get_state_cb(tpmgr);
-	pjsip_tpmgr_set_state_cb(tpmgr, &keepalive_transport_state_callback);
 }
 
 /*! \brief Observer which is used to update our interval when the global setting changes */
@@ -238,10 +275,72 @@ static struct ast_sorcery_observer keepalive_global_observer = {
 	.loaded = keepalive_global_loaded,
 };
 
+/*!
+ * \brief
+ * On incoming TCP connections, when we receive a SIP request, we mark that we have
+ * received a valid SIP request. This way, we will not shut the transport down for
+ * idleness
+ */
+static pj_bool_t idle_monitor_on_rx_request(pjsip_rx_data *rdata)
+{
+	struct monitored_transport *idle_trans;
+
+	idle_trans = ao2_find(transports, rdata->tp_info.transport->obj_name, OBJ_SEARCH_KEY);
+	if (!idle_trans) {
+		return PJ_FALSE;
+	}
+
+	ast_atomic_fetchadd_int(&idle_trans->sip_received, +1);
+	ao2_ref(idle_trans, -1);
+
+	return PJ_FALSE;
+}
+
+static pjsip_module idle_monitor_module = {
+	.name = {"idle monitor module", 19},
+	.priority = PJSIP_MOD_PRIORITY_TRANSPORT_LAYER + 3,
+	.on_rx_request = idle_monitor_on_rx_request,
+};
+
 static int load_module(void)
 {
+	pjsip_tpmgr *tpmgr;
+
 	CHECK_PJSIP_MODULE_LOADED();
 
+	transports = ao2_container_alloc(TRANSPORTS_BUCKETS, monitored_transport_hash_fn,
+		monitored_transport_cmp_fn);
+	if (!transports) {
+		ast_log(LOG_ERROR, "Could not create container for transports to perform keepalive on.\n");
+		return AST_MODULE_LOAD_DECLINE;
+	}
+
+	tpmgr = pjsip_endpt_get_tpmgr(ast_sip_get_pjsip_endpoint());
+	if (!tpmgr) {
+		ast_log(LOG_ERROR, "No transport manager to attach keepalive functionality to.\n");
+		ao2_ref(transports, -1);
+		return AST_MODULE_LOAD_DECLINE;
+	}
+
+	sched = ast_sched_context_create();
+	if (!sched) {
+		ast_log(LOG_ERROR, "Failed to create keepalive scheduler context.\n");
+		ao2_ref(transports, -1);
+		return AST_MODULE_LOAD_DECLINE;
+	}
+
+	if (ast_sched_start_thread(sched)) {
+		ast_log(LOG_ERROR, "Failed to start keepalive scheduler thread\n");
+		ast_sched_context_destroy(sched);
+		ao2_ref(transports, -1);
+		return AST_MODULE_LOAD_DECLINE;
+	}
+
+	ast_sip_register_service(&idle_monitor_module);
+
+	tpmgr_state_callback = pjsip_tpmgr_get_state_cb(tpmgr);
+	pjsip_tpmgr_set_state_cb(tpmgr, &monitored_transport_state_callback);
+
 	ast_sorcery_observer_add(ast_sip_get_sorcery(), "global", &keepalive_global_observer);
 	ast_sorcery_reload_object(ast_sip_get_sorcery(), "global");
 	ast_module_shutdown_ref(ast_module_info->self);
@@ -250,7 +349,19 @@ static int load_module(void)
 
 static int unload_module(void)
 {
-	/* This will never get called */
+	pjsip_tpmgr *tpmgr = pjsip_endpt_get_tpmgr(ast_sip_get_pjsip_endpoint());
+
+	if (keepalive_interval) {
+		keepalive_interval = 0;
+		pthread_kill(keepalive_thread, SIGURG);
+		pthread_join(keepalive_thread, NULL);
+	}
+
+	ast_sched_context_destroy(sched);
+	ao2_ref(transports, -1);
+
+	ast_sip_unregister_service(&idle_monitor_module);
+	pjsip_tpmgr_set_state_cb(tpmgr, tpmgr_state_callback);
 	return 0;
 }
 
@@ -260,7 +371,7 @@ static int reload_module(void)
 	return 0;
 }
 
-AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_LOAD_ORDER, "PJSIP Stateful Connection Keepalive Support",
+AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_LOAD_ORDER, "PJSIP Reliable Transport Management",
 		.support_level = AST_MODULE_SUPPORT_CORE,
 		.load = load_module,
 		.reload = reload_module,
diff --git a/res/res_pjsip_transport_websocket.c b/res/res_pjsip_transport_websocket.c
index a49eade..4181828 100644
--- a/res/res_pjsip_transport_websocket.c
+++ b/res/res_pjsip_transport_websocket.c
@@ -275,24 +275,27 @@ static int transport_read(void *data)
 static int get_write_timeout(void)
 {
 	int write_timeout = -1;
-	struct ao2_container *transports;
+	struct ao2_container *transport_states;
 
-	transports = ast_sorcery_retrieve_by_fields(ast_sip_get_sorcery(), "transport", AST_RETRIEVE_FLAG_ALL, NULL);
+	transport_states = ast_sip_get_transport_states();
 
-	if (transports) {
-		struct ao2_iterator it_transports = ao2_iterator_init(transports, 0);
-		struct ast_sip_transport *transport;
+	if (transport_states) {
+		struct ao2_iterator it_transport_states = ao2_iterator_init(transport_states, 0);
+		struct ast_sip_transport_state *transport_state;
 
-		for (; (transport = ao2_iterator_next(&it_transports)); ao2_cleanup(transport)) {
-			if (transport->type != AST_TRANSPORT_WS && transport->type != AST_TRANSPORT_WSS) {
+		for (; (transport_state = ao2_iterator_next(&it_transport_states)); ao2_cleanup(transport_state)) {
+			struct ast_sip_transport *transport;
+			if (transport_state->type != AST_TRANSPORT_WS && transport_state->type != AST_TRANSPORT_WSS) {
 				continue;
 			}
+			transport = ast_sorcery_retrieve_by_id(ast_sip_get_sorcery(), "transport", transport_state->id);
 			ast_debug(5, "Found %s transport with write timeout: %d\n",
 				transport->type == AST_TRANSPORT_WS ? "WS" : "WSS",
 				transport->write_timeout);
 			write_timeout = MAX(write_timeout, transport->write_timeout);
 		}
-		ao2_cleanup(transports);
+		ao2_iterator_destroy(&it_transport_states);
+		ao2_cleanup(transport_states);
 	}
 
 	if (write_timeout < 0) {
@@ -303,14 +306,22 @@ static int get_write_timeout(void)
 	return write_timeout;
 }
 
-/*!
- \brief WebSocket connection handler.
- */
+static struct ast_taskprocessor *create_websocket_serializer(void)
+{
+	char tps_name[AST_TASKPROCESSOR_MAX_NAME + 1];
+
+	/* Create name with seq number appended. */
+	ast_taskprocessor_build_name(tps_name, sizeof(tps_name), "pjsip/websocket");
+
+	return ast_sip_create_serializer_named(tps_name);
+}
+
+/*! \brief WebSocket connection handler. */
 static void websocket_cb(struct ast_websocket *session, struct ast_variable *parameters, struct ast_variable *headers)
 {
-	struct ast_taskprocessor *serializer = NULL;
+	struct ast_taskprocessor *serializer;
 	struct transport_create_data create_data;
-	struct ws_transport *transport = NULL;
+	struct ws_transport *transport;
 	struct transport_read_data read_data;
 
 	if (ast_websocket_set_nonblock(session)) {
@@ -323,7 +334,8 @@ static void websocket_cb(struct ast_websocket *session, struct ast_variable *par
 		return;
 	}
 
-	if (!(serializer = ast_sip_create_serializer())) {
+	serializer = create_websocket_serializer();
+	if (!serializer) {
 		ast_websocket_unref(session);
 		return;
 	}
diff --git a/res/res_rtp_asterisk.c b/res/res_rtp_asterisk.c
index 3dfaf87..611920e 100644
--- a/res/res_rtp_asterisk.c
+++ b/res/res_rtp_asterisk.c
@@ -182,6 +182,16 @@ struct ast_rtp_ioqueue_thread {
 /*! \brief List of ioqueue threads */
 static AST_LIST_HEAD_STATIC(ioqueues, ast_rtp_ioqueue_thread);
 
+/*! \brief Structure which contains ICE host candidate mapping information */
+struct ast_ice_host_candidate {
+	pj_sockaddr local;
+	pj_sockaddr advertised;
+	AST_RWLIST_ENTRY(ast_ice_host_candidate) next;
+};
+
+/*! \brief List of ICE host candidate mappings */
+static AST_RWLIST_HEAD_STATIC(host_candidates, ast_ice_host_candidate);
+
 #endif
 
 #define FLAG_3389_WARNING               (1 << 0)
@@ -451,6 +461,38 @@ static void dtls_srtp_stop_timeout_timer(struct ast_rtp_instance *instance, stru
 static int __rtp_sendto(struct ast_rtp_instance *instance, void *buf, size_t size, int flags, struct ast_sockaddr *sa, int rtcp, int *ice, int use_srtp);
 
 #ifdef HAVE_PJPROJECT
+/*! \brief Helper function which clears the ICE host candidate mapping */
+static void host_candidate_overrides_clear(void)
+{
+	struct ast_ice_host_candidate *candidate;
+
+	AST_RWLIST_WRLOCK(&host_candidates);
+	AST_RWLIST_TRAVERSE_SAFE_BEGIN(&host_candidates, candidate, next) {
+		AST_RWLIST_REMOVE_CURRENT(next);
+		ast_free(candidate);
+	}
+	AST_RWLIST_TRAVERSE_SAFE_END;
+	AST_RWLIST_UNLOCK(&host_candidates);
+}
+
+/*! \brief Applies the ICE host candidate mapping */
+static void host_candidate_overrides_apply(unsigned int count, pj_sockaddr addrs[])
+{
+	int pos;
+	struct ast_ice_host_candidate *candidate;
+
+	AST_RWLIST_RDLOCK(&host_candidates);
+	for (pos = 0; pos < count; pos++) {
+		AST_LIST_TRAVERSE(&host_candidates, candidate, next) {
+			if (!pj_sockaddr_cmp(&candidate->local, &addrs[pos])) {
+				pj_sockaddr_copy_addr(&addrs[pos], &candidate->advertised);
+				break;
+			}
+		}
+	}
+	AST_RWLIST_UNLOCK(&host_candidates);
+}
+
 /*! \brief Helper function which updates an ast_sockaddr with the candidate used for the component */
 static void update_address_with_ice_candidate(struct ast_rtp *rtp, enum ast_rtp_ice_component_type component,
 	struct ast_sockaddr *cand_address)
@@ -2118,8 +2160,6 @@ static int __rtp_recvfrom(struct ast_rtp_instance *instance, void *buf, size_t s
 			SSL_set_accept_state(dtls->ssl);
 		}
 
-		ast_mutex_lock(&dtls->lock);
-
 		dtls_srtp_check_pending(instance, rtp, rtcp);
 
 		BIO_write(dtls->read_bio, buf, len);
@@ -2130,7 +2170,6 @@ static int __rtp_recvfrom(struct ast_rtp_instance *instance, void *buf, size_t s
 			unsigned long error = ERR_get_error();
 			ast_log(LOG_ERROR, "DTLS failure occurred on RTP instance '%p' due to reason '%s', terminating\n",
 				instance, ERR_reason_error_string(error));
-			ast_mutex_unlock(&dtls->lock);
 			return -1;
 		}
 
@@ -2148,8 +2187,6 @@ static int __rtp_recvfrom(struct ast_rtp_instance *instance, void *buf, size_t s
 			dtls_srtp_start_timeout_timer(instance, rtp, rtcp);
 		}
 
-		ast_mutex_unlock(&dtls->lock);
-
 		return res;
 	}
 #endif
@@ -2373,6 +2410,8 @@ static void rtp_add_candidates_to_ice(struct ast_rtp_instance *instance, struct
 		pj_enum_ip_interface(pj_AF_INET6(), &count, address);
 	}
 
+	host_candidate_overrides_apply(count, address);
+
 	for (pos = 0; pos < count; pos++) {
 		pj_sockaddr_set_port(&address[pos], port);
 		ast_rtp_ice_add_cand(rtp, component, transport, PJ_ICE_CAND_TYPE_HOST, 65535, &address[pos], &address[pos], NULL,
@@ -4822,9 +4861,6 @@ static int ast_rtp_fd(struct ast_rtp_instance *instance, int rtcp)
 static void ast_rtp_remote_address_set(struct ast_rtp_instance *instance, struct ast_sockaddr *addr)
 {
 	struct ast_rtp *rtp = ast_rtp_instance_get_data(instance);
-#ifdef HAVE_OPENSSL_SRTP
-	struct dtls_details *dtls;
-#endif
 
 	if (rtp->rtcp) {
 		ast_debug(1, "Setting RTCP address on RTP instance '%p'\n", instance);
@@ -4842,28 +4878,6 @@ static void ast_rtp_remote_address_set(struct ast_rtp_instance *instance, struct
 		rtp_learning_seq_init(&rtp->rtp_source_learn, rtp->seqno);
 	}
 
-#ifdef HAVE_OPENSSL_SRTP
-	/* Trigger pending outbound DTLS packets received before the address was set.  Avoid unnecessary locking
-	 * by checking if we're passive. Without this, we only send the pending packets once a new SSL packet is
-	 * received in __rtp_recvfrom.
-	 */
-	dtls = &rtp->dtls;
-	if (dtls->dtls_setup == AST_RTP_DTLS_SETUP_PASSIVE) {
-		ast_mutex_lock(&dtls->lock);
-		dtls_srtp_check_pending(instance, rtp, 0);
-		ast_mutex_unlock(&dtls->lock);
-	}
-
-	if (rtp->rtcp) {
-		dtls = &rtp->rtcp->dtls;
-		if (dtls->dtls_setup == AST_RTP_DTLS_SETUP_PASSIVE) {
-			ast_mutex_lock(&dtls->lock);
-			dtls_srtp_check_pending(instance, rtp, 1);
-			ast_mutex_unlock(&dtls->lock);
-		}
-	}
-#endif
-
 	return;
 }
 
@@ -5287,6 +5301,11 @@ static int rtp_reload(int reload)
 	const char *s;
 	struct ast_flags config_flags = { reload ? CONFIG_FLAG_FILEUNCHANGED : 0 };
 
+#ifdef HAVE_PJPROJECT
+	struct ast_variable *var;
+	struct ast_ice_host_candidate *candidate;
+#endif
+
 	cfg = ast_config_load2("rtp.conf", "rtp", config_flags);
 	if (cfg == CONFIG_STATUS_FILEMISSING || cfg == CONFIG_STATUS_FILEUNCHANGED || cfg == CONFIG_STATUS_FILEINVALID) {
 		return 0;
@@ -5313,6 +5332,7 @@ static int rtp_reload(int reload)
 	turnaddr = pj_str(NULL);
 	turnusername = pj_str(NULL);
 	turnpassword = pj_str(NULL);
+	host_candidate_overrides_clear();
 #endif
 
 	if (cfg) {
@@ -5392,6 +5412,36 @@ static int rtp_reload(int reload)
 		if ((s = ast_variable_retrieve(cfg, "general", "turnpassword"))) {
 			pj_strdup2_with_null(pool, &turnpassword, s);
 		}
+
+		AST_RWLIST_WRLOCK(&host_candidates);
+		for (var = ast_variable_browse(cfg, "ice_host_candidates"); var; var = var->next) {
+			struct ast_sockaddr local_addr, advertised_addr;
+			pj_str_t address;
+
+			ast_sockaddr_setnull(&local_addr);
+			ast_sockaddr_setnull(&advertised_addr);
+
+			if (ast_parse_arg(var->name, PARSE_ADDR | PARSE_PORT_IGNORE, &local_addr)) {
+				ast_log(LOG_WARNING, "Invalid local ICE host address: %s\n", var->name);
+				continue;
+			}
+
+			if (ast_parse_arg(var->value, PARSE_ADDR | PARSE_PORT_IGNORE, &advertised_addr)) {
+				ast_log(LOG_WARNING, "Invalid advertised ICE host address: %s\n", var->value);
+				continue;
+			}
+
+			if (!(candidate = ast_calloc(1, sizeof(*candidate)))) {
+				ast_log(LOG_ERROR, "Failed to allocate ICE host candidate mapping.\n");
+				break;
+			}
+
+			pj_sockaddr_parse(pj_AF_UNSPEC(), 0, pj_cstr(&address, ast_sockaddr_stringify(&local_addr)), &candidate->local);
+			pj_sockaddr_parse(pj_AF_UNSPEC(), 0, pj_cstr(&address, ast_sockaddr_stringify(&advertised_addr)), &candidate->advertised);
+
+			AST_RWLIST_INSERT_TAIL(&host_candidates, candidate, next);
+		}
+		AST_RWLIST_UNLOCK(&host_candidates);
 #endif
 		ast_config_destroy(cfg);
 	}
@@ -5494,6 +5544,7 @@ static int unload_module(void)
 	ast_cli_unregister_multiple(cli_rtp, ARRAY_LEN(cli_rtp));
 
 #ifdef HAVE_PJPROJECT
+	host_candidate_overrides_clear();
 	pj_thread_register_check();
 	rtp_terminate_pjproject();
 #endif
diff --git a/res/res_sorcery_memory.c b/res/res_sorcery_memory.c
index 7d398c2..45bde26 100644
--- a/res/res_sorcery_memory.c
+++ b/res/res_sorcery_memory.c
@@ -98,7 +98,21 @@ static int sorcery_memory_cmp(void *obj, void *arg, int flags)
 
 static int sorcery_memory_create(const struct ast_sorcery *sorcery, void *data, void *object)
 {
-	ao2_link(data, object);
+	void *existing;
+
+	ao2_lock(data);
+
+	existing = ao2_find(data, ast_sorcery_object_get_id(object), OBJ_KEY | OBJ_NOLOCK);
+	if (existing) {
+		ao2_ref(existing, -1);
+		ao2_unlock(data);
+		return -1;
+	}
+
+	ao2_link_flags(data, object, OBJ_NOLOCK);
+
+	ao2_unlock(data);
+
 	return 0;
 }
 
diff --git a/res/res_sorcery_memory_cache.c b/res/res_sorcery_memory_cache.c
index 99db0ce..f2ed5d5 100644
--- a/res/res_sorcery_memory_cache.c
+++ b/res/res_sorcery_memory_cache.c
@@ -1830,7 +1830,7 @@ static char *sorcery_memory_cache_expire(struct ast_cli_entry *e, int cmd, struc
 		}
 	}
 
-	if (a->argc > 6) {
+	if (a->argc < 5 || a->argc > 6) {
 		return CLI_SHOWUSAGE;
 	}
 
@@ -1884,7 +1884,7 @@ static char *sorcery_memory_cache_stale(struct ast_cli_entry *e, int cmd, struct
 		}
 	}
 
-	if (a->argc > 6) {
+	if (a->argc < 5 || a->argc > 6) {
 		return CLI_SHOWUSAGE;
 	}
 
@@ -1943,7 +1943,7 @@ static char *sorcery_memory_cache_populate(struct ast_cli_entry *e, int cmd, str
 		}
 	}
 
-	if (a->argc > 5) {
+	if (a->argc != 5) {
 		return CLI_SHOWUSAGE;
 	}
 
diff --git a/res/res_sorcery_realtime.c b/res/res_sorcery_realtime.c
index 3412b92..b16069b 100644
--- a/res/res_sorcery_realtime.c
+++ b/res/res_sorcery_realtime.c
@@ -218,16 +218,16 @@ static void sorcery_realtime_retrieve_multiple(const struct ast_sorcery *sorcery
 
 static void sorcery_realtime_retrieve_regex(const struct ast_sorcery *sorcery, void *data, const char *type, struct ao2_container *objects, const char *regex)
 {
-	char field[strlen(UUID_FIELD) + 6], value[strlen(regex) + 2];
+	char field[strlen(UUID_FIELD) + 6], value[strlen(regex) + 3];
 	RAII_VAR(struct ast_variable *, fields, NULL, ast_variables_destroy);
 
 	/* The realtime API provides no direct ability to do regex so for now we support a limited subset using pattern matching */
-	if (regex[0] != '^') {
-		return;
-	}
-
 	snprintf(field, sizeof(field), "%s LIKE", UUID_FIELD);
-	snprintf(value, sizeof(value), "%s%%", regex + 1);
+	if (regex[0] == '^') {
+		snprintf(value, sizeof(value), "%s%%", regex + 1);
+	} else {
+		snprintf(value, sizeof(value), "%%%s%%", regex);
+	}
 
 	if (!(fields = ast_variable_new(field, value, ""))) {
 		return;
diff --git a/res/res_stasis_device_state.c b/res/res_stasis_device_state.c
index c0b6859..be082dc 100644
--- a/res/res_stasis_device_state.c
+++ b/res/res_stasis_device_state.c
@@ -303,6 +303,12 @@ static void device_state_cb(void *data, struct stasis_subscription *sub,
 {
 	struct ast_device_state_message *device_state;
 
+	if (stasis_subscription_final_message(sub, msg)) {
+		/* Remove stasis subscription's reference to device_state_subscription */
+		ao2_ref(data, -1);
+		return;
+	}
+
 	if (ast_device_state_message_type() != stasis_message_type(msg)) {
 		return;
 	}
@@ -365,10 +371,12 @@ static int subscribe_device_state(struct stasis_app *app, void *obj)
 
 	ast_debug(3, "Subscribing to device %s\n", sub->device_name);
 
-	sub->sub = stasis_subscribe_pool(topic, device_state_cb, sub);
+	sub->sub = stasis_subscribe_pool(topic, device_state_cb, ao2_bump(sub));
 	if (!sub->sub) {
 		ast_log(LOG_ERROR, "Unable to subscribe to device %s\n",
 			sub->device_name);
+		/* Reference we added when attempting to stasis_subscribe_pool */
+		ao2_ref(sub, -1);
 		return -1;
 	}
 
diff --git a/res/res_statsd.exports.in b/res/res_statsd.exports.in
index d4a79c1..ca28b16 100644
--- a/res/res_statsd.exports.in
+++ b/res/res_statsd.exports.in
@@ -1,9 +1,6 @@
 {
 	global:
-		LINKER_SYMBOL_PREFIX*ast_statsd_log;
-		LINKER_SYMBOL_PREFIX*ast_statsd_log_full;
-		LINKER_SYMBOL_PREFIX*ast_statsd_log_sample;
-		LINKER_SYMBOL_PREFIX*ast_statsd_log_string;
+		LINKER_SYMBOL_PREFIXast_statsd_log*;
 	local:
 		*;
 };
diff --git a/res/res_xmpp.c b/res/res_xmpp.c
index d979143..ed35cd1 100644
--- a/res/res_xmpp.c
+++ b/res/res_xmpp.c
@@ -3130,6 +3130,10 @@ done:
 /*! \brief Internal function called when we authenticated as a component */
 static int xmpp_component_authenticating(struct ast_xmpp_client *client, struct ast_xmpp_client_config *cfg, int type, iks *node)
 {
+	if (!strcmp(iks_name(node), "stream:features")) {
+		return 0;
+	}
+
 	if (strcmp(iks_name(node), "handshake")) {
 		ast_log(LOG_ERROR, "Failed to authenticate component '%s'\n", client->name);
 		return -1;
@@ -3305,6 +3309,11 @@ static int xmpp_pak_presence(struct ast_xmpp_client *client, struct ast_xmpp_cli
 	int status = pak->show ? pak->show : STATUS_DISAPPEAR;
 	enum ast_device_state state = AST_DEVICE_UNAVAILABLE;
 
+	/* If this is a component presence probe request answer immediately with our presence status */
+	if (ast_test_flag(&cfg->flags, XMPP_COMPONENT) && !ast_strlen_zero(type) && !strcasecmp(type, "probe")) {
+		xmpp_client_set_presence(client, pak->from->full, iks_find_attrib(pak->x, "to"), cfg->status, cfg->statusmsg);
+	}
+
 	/* If no resource is available this is a general buddy presence update, which we will ignore */
 	if (!pak->from->resource) {
 		return 0;
@@ -3319,11 +3328,6 @@ static int xmpp_pak_presence(struct ast_xmpp_client *client, struct ast_xmpp_cli
 		return 0;
 	}
 
-	/* If this is a component presence probe request answer immediately with our presence status */
-	if (ast_test_flag(&cfg->flags, XMPP_COMPONENT) && !ast_strlen_zero(type) && !strcasecmp(type, "probe")) {
-		xmpp_client_set_presence(client, pak->from->full, iks_find_attrib(pak->x, "to"), cfg->status, cfg->statusmsg);
-	}
-
 	ao2_lock(buddy->resources);
 
 	if (!(resource = ao2_callback(buddy->resources, OBJ_NOLOCK, xmpp_resource_cmp, pak->from->resource))) {
@@ -3864,7 +3868,7 @@ static int xmpp_client_config_post_apply(void *obj, void *arg, int flags)
 			cfg->client->jid = iks_id_new(cfg->client->stack, cfg->user);
 		}
 
-		if (!cfg->client->jid || ast_strlen_zero(cfg->client->jid->user)) {
+		if (!cfg->client->jid || (ast_strlen_zero(cfg->client->jid->user) && !ast_test_flag(&cfg->flags, XMPP_COMPONENT))) {
 			ast_log(LOG_ERROR, "Jabber identity '%s' could not be created for client '%s' - client not active\n", cfg->user, cfg->name);
 			return -1;
 		}
diff --git a/res/stasis/control.c b/res/stasis/control.c
index 87362df..ebb7e01 100644
--- a/res/stasis/control.c
+++ b/res/stasis/control.c
@@ -355,14 +355,39 @@ int stasis_app_control_dial(struct stasis_app_control *control, const char *endp
 	return 0;
 }
 
+static int app_control_add_role(struct stasis_app_control *control,
+		struct ast_channel *chan, void *data)
+{
+	char *role = data;
+
+	return ast_channel_add_bridge_role(chan, role);
+}
+
 int stasis_app_control_add_role(struct stasis_app_control *control, const char *role)
 {
-	return ast_channel_add_bridge_role(control->channel, role);
+	char *role_dup;
+
+	role_dup = ast_strdup(role);
+	if (!role_dup) {
+		return -1;
+	}
+
+	stasis_app_send_command_async(control, app_control_add_role, role_dup, ast_free_ptr);
+
+	return 0;
+}
+
+static int app_control_clear_roles(struct stasis_app_control *control,
+		struct ast_channel *chan, void *data)
+{
+	ast_channel_clear_bridge_roles(chan);
+
+	return 0;
 }
 
 void stasis_app_control_clear_roles(struct stasis_app_control *control)
 {
-	ast_channel_clear_bridge_roles(control->channel);
+	stasis_app_send_command_async(control, app_control_clear_roles, NULL, NULL);
 }
 
 int control_command_count(struct stasis_app_control *control)
@@ -598,9 +623,69 @@ int stasis_app_control_unmute(struct stasis_app_control *control, unsigned int d
 	return 0;
 }
 
+/*!
+ * \brief structure for queuing ARI channel variable setting
+ *
+ * It may seem weird to define this custom structure given that we already have
+ * ast_var_t and ast_variable defined elsewhere. The problem with those is that
+ * they are not tolerant of NULL channel variable value pointers. In fact, in both
+ * cases, the best they could do is to have a zero-length variable value. However,
+ * when un-setting a channel variable, it is important to pass a NULL value, not
+ * a zero-length string.
+ */
+struct chanvar {
+	/*! Name of variable to set/unset */
+	char *name;
+	/*! Value of variable to set. If unsetting, this will be NULL */
+	char *value;
+};
+
+static void free_chanvar(void *data)
+{
+	struct chanvar *var = data;
+
+	ast_free(var->name);
+	ast_free(var->value);
+	ast_free(var);
+}
+
+static int app_control_set_channel_var(struct stasis_app_control *control,
+	struct ast_channel *chan, void *data)
+{
+	struct chanvar *var = data;
+
+	pbx_builtin_setvar_helper(control->channel, var->name, var->value);
+
+	return 0;
+}
+
 int stasis_app_control_set_channel_var(struct stasis_app_control *control, const char *variable, const char *value)
 {
-	return pbx_builtin_setvar_helper(control->channel, variable, value);
+	struct chanvar *var;
+
+	var = ast_calloc(1, sizeof(*var));
+	if (!var) {
+		return -1;
+	}
+
+	var->name = ast_strdup(variable);
+	if (!var->name) {
+		free_chanvar(var);
+		return -1;
+	}
+
+	/* It's kosher for value to be NULL. It means the variable is being unset */
+	if (value) {
+		var->value = ast_strdup(value);
+		if (!var->value) {
+			free_chanvar(var);
+			return -1;
+		}
+	}
+
+	stasis_app_send_command_async(control, app_control_set_channel_var, var, free_chanvar);
+
+	return 0;
 }
 
 static int app_control_hold(struct stasis_app_control *control,
@@ -746,6 +831,14 @@ static int app_send_command_on_condition(struct stasis_app_control *control,
 	RAII_VAR(struct stasis_app_command *, command, NULL, ao2_cleanup);
 
 	if (control == NULL || control->is_done) {
+		/* If exec_command_on_condition fails, it calls the data_destructor.
+		 * In order to provide consistent behavior, we'll also call the data_destructor
+		 * on this error path. This way, callers never have to call the
+		 * data_destructor themselves.
+		 */
+		if (data_destructor) {
+			data_destructor(data);
+		}
 		return -1;
 	}
 
@@ -771,6 +864,14 @@ int stasis_app_send_command_async(struct stasis_app_control *control,
 	RAII_VAR(struct stasis_app_command *, command, NULL, ao2_cleanup);
 
 	if (control == NULL || control->is_done) {
+		/* If exec_command fails, it calls the data_destructor. In order to
+		 * provide consistent behavior, we'll also call the data_destructor
+		 * on this error path. This way, callers never have to call the
+		 * data_destructor themselves.
+		 */
+		if (data_destructor) {
+			data_destructor(data);
+		}
 		return -1;
 	}
 
diff --git a/sounds/Makefile b/sounds/Makefile
index a726ece..84d0f45 100644
--- a/sounds/Makefile
+++ b/sounds/Makefile
@@ -19,8 +19,8 @@ CMD_PREFIX?=@
 SOUNDS_DIR:=$(DESTDIR)$(ASTDATADIR)/sounds
 SOUNDS_CACHE_DIR?=
 MOH_DIR:=$(DESTDIR)$(ASTDATADIR)/moh
-CORE_SOUNDS_VERSION:=1.4.27
-EXTRA_SOUNDS_VERSION:=1.4.15
+CORE_SOUNDS_VERSION:=1.5
+EXTRA_SOUNDS_VERSION:=1.5
 MOH_VERSION:=2.03
 SOUNDS_URL:=http://downloads.asterisk.org/pub/telephony/sounds/releases
 MCS:=$(subst -EN-,-en-,$(MENUSELECT_CORE_SOUNDS))
diff --git a/tests/test_dlinklists.c b/tests/test_dlinklists.c
index 197dd75..81760ac 100644
--- a/tests/test_dlinklists.c
+++ b/tests/test_dlinklists.c
@@ -70,7 +70,7 @@ static void print_list(struct test_container *x, char *expect)
 			strcat(buff," <=> ");
 	}
 	
-	ast_log(LOG_NOTICE,"Got: %s  [expect %s]\n", buff, expect);
+	ast_debug(1,"Got: %s  [expect %s]\n", buff, expect);
 }
 
 static void print_list_backwards(struct test_container *x, char *expect)
@@ -84,7 +84,7 @@ static void print_list_backwards(struct test_container *x, char *expect)
 			strcat(buff," <=> ");
 	}
 	
-	ast_log(LOG_NOTICE,"Got: %s  [expect %s]\n", buff, expect);
+	ast_debug(1,"Got: %s  [expect %s]\n", buff, expect);
 }
 
 static struct test_container *make_cont(void)
@@ -180,7 +180,7 @@ static void dll_tests(void)
 	struct test1 *d;
 	struct test1 *e;
 	
-	ast_log(LOG_NOTICE,"Test AST_DLLIST_INSERT_HEAD, AST_DLLIST_TRAVERSE, AST_DLLIST_TRAVERSE_BACKWARDS_SAFE_BEGIN, AST_DLLIST_TRAVERSE_BACKWARDS_SAFE_END\n");
+	ast_debug(1,"Test AST_DLLIST_INSERT_HEAD, AST_DLLIST_TRAVERSE, AST_DLLIST_TRAVERSE_BACKWARDS_SAFE_BEGIN, AST_DLLIST_TRAVERSE_BACKWARDS_SAFE_END\n");
 	tc = make_cont();
 	a = make_test1("A");
 	b = make_test1("B");
@@ -197,9 +197,9 @@ static void dll_tests(void)
 	tc = make_cont();
 
 	if (AST_DLLIST_EMPTY(&tc->entries))
-		ast_log(LOG_NOTICE,"Test AST_DLLIST_EMPTY....OK\n");
+		ast_debug(1,"Test AST_DLLIST_EMPTY....OK\n");
 	else
-		ast_log(LOG_NOTICE,"Test AST_DLLIST_EMPTY....PROBLEM!!\n");
+		ast_log(LOG_ERROR,"Test AST_DLLIST_EMPTY....PROBLEM!!\n");
 
 
 	a = make_test1("A");
@@ -207,7 +207,7 @@ static void dll_tests(void)
 	c = make_test1("C");
 	d = make_test1("D");
 	
-	ast_log(LOG_NOTICE,"Test AST_DLLIST_INSERT_TAIL\n");
+	ast_debug(1,"Test AST_DLLIST_INSERT_TAIL\n");
 	AST_DLLIST_INSERT_TAIL(&tc->entries, a, list);
 	AST_DLLIST_INSERT_TAIL(&tc->entries, b, list);
 	AST_DLLIST_INSERT_TAIL(&tc->entries, c, list);
@@ -215,24 +215,24 @@ static void dll_tests(void)
 	print_list(tc, "A <=> B <=> C <=> D");
 
 	if (AST_DLLIST_FIRST(&tc->entries) == a)
-		ast_log(LOG_NOTICE,"Test AST_DLLIST_FIRST....OK\n");
+		ast_debug(1,"Test AST_DLLIST_FIRST....OK\n");
 	else
-		ast_log(LOG_NOTICE,"Test AST_DLLIST_FIRST....PROBLEM\n");
+		ast_log(LOG_ERROR,"Test AST_DLLIST_FIRST....PROBLEM\n");
 
 	if (AST_DLLIST_LAST(&tc->entries) == d)
-		ast_log(LOG_NOTICE,"Test AST_DLLIST_LAST....OK\n");
+		ast_debug(1,"Test AST_DLLIST_LAST....OK\n");
 	else
-		ast_log(LOG_NOTICE,"Test AST_DLLIST_LAST....PROBLEM\n");
+		ast_log(LOG_ERROR,"Test AST_DLLIST_LAST....PROBLEM\n");
 
 	if (AST_DLLIST_NEXT(a,list) == b)
-		ast_log(LOG_NOTICE,"Test AST_DLLIST_NEXT....OK\n");
+		ast_debug(1,"Test AST_DLLIST_NEXT....OK\n");
 	else
-		ast_log(LOG_NOTICE,"Test AST_DLLIST_NEXT....PROBLEM\n");
+		ast_log(LOG_ERROR,"Test AST_DLLIST_NEXT....PROBLEM\n");
 
 	if (AST_DLLIST_PREV(d,list) == c)
-		ast_log(LOG_NOTICE,"Test AST_DLLIST_PREV....OK\n");
+		ast_debug(1,"Test AST_DLLIST_PREV....OK\n");
 	else
-		ast_log(LOG_NOTICE,"Test AST_DLLIST_PREV....PROBLEM\n");
+		ast_log(LOG_ERROR,"Test AST_DLLIST_PREV....PROBLEM\n");
 
 	destroy_test_container(tc);
 
@@ -243,35 +243,35 @@ static void dll_tests(void)
 	c = make_test1("C");
 	d = make_test1("D");
 
-	ast_log(LOG_NOTICE,"Test AST_DLLIST_INSERT_AFTER, AST_DLLIST_TRAVERSE_BACKWARDS\n");
+	ast_debug(1,"Test AST_DLLIST_INSERT_AFTER, AST_DLLIST_TRAVERSE_BACKWARDS\n");
 	AST_DLLIST_INSERT_HEAD(&tc->entries, a, list);
 	AST_DLLIST_INSERT_AFTER(&tc->entries, a, b, list);
 	AST_DLLIST_INSERT_AFTER(&tc->entries, b, c, list);
 	AST_DLLIST_INSERT_AFTER(&tc->entries, c, d, list);
 	print_list_backwards(tc, "D <=> C <=> B <=> A");
 
-	ast_log(LOG_NOTICE,"Test AST_DLLIST_REMOVE_HEAD\n");
+	ast_debug(1,"Test AST_DLLIST_REMOVE_HEAD\n");
 	AST_DLLIST_REMOVE_HEAD(&tc->entries, list);
 	print_list_backwards(tc, "D <=> C <=> B");
-	ast_log(LOG_NOTICE,"Test AST_DLLIST_REMOVE_HEAD\n");
+	ast_debug(1,"Test AST_DLLIST_REMOVE_HEAD\n");
 	AST_DLLIST_REMOVE_HEAD(&tc->entries, list);
 	print_list_backwards(tc, "D <=> C");
-	ast_log(LOG_NOTICE,"Test AST_DLLIST_REMOVE_HEAD\n");
+	ast_debug(1,"Test AST_DLLIST_REMOVE_HEAD\n");
 	AST_DLLIST_REMOVE_HEAD(&tc->entries, list);
 	print_list_backwards(tc, "D");
 	AST_DLLIST_REMOVE_HEAD(&tc->entries, list);
 
 	if (AST_DLLIST_EMPTY(&tc->entries))
-		ast_log(LOG_NOTICE,"Test AST_DLLIST_REMOVE_HEAD....OK\n");
+		ast_debug(1,"Test AST_DLLIST_REMOVE_HEAD....OK\n");
 	else
-		ast_log(LOG_NOTICE,"Test AST_DLLIST_REMOVE_HEAD....PROBLEM!!\n");
+		ast_log(LOG_ERROR,"Test AST_DLLIST_REMOVE_HEAD....PROBLEM!!\n");
 
 	AST_DLLIST_INSERT_HEAD(&tc->entries, a, list);
 	AST_DLLIST_INSERT_AFTER(&tc->entries, a, b, list);
 	AST_DLLIST_INSERT_AFTER(&tc->entries, b, c, list);
 	AST_DLLIST_INSERT_AFTER(&tc->entries, c, d, list);
 
-	ast_log(LOG_NOTICE,"Test AST_DLLIST_REMOVE\n");
+	ast_debug(1,"Test AST_DLLIST_REMOVE\n");
 	AST_DLLIST_REMOVE(&tc->entries, c, list);
 	print_list(tc, "A <=> B <=> D");
 	AST_DLLIST_REMOVE(&tc->entries, a, list);
@@ -281,9 +281,9 @@ static void dll_tests(void)
 	AST_DLLIST_REMOVE(&tc->entries, b, list);
 	
 	if (AST_DLLIST_EMPTY(&tc->entries))
-		ast_log(LOG_NOTICE,"Test AST_DLLIST_REMOVE....OK\n");
+		ast_debug(1,"Test AST_DLLIST_REMOVE....OK\n");
 	else
-		ast_log(LOG_NOTICE,"Test AST_DLLIST_REMOVE....PROBLEM!!\n");
+		ast_log(LOG_ERROR,"Test AST_DLLIST_REMOVE....PROBLEM!!\n");
 
 	AST_DLLIST_INSERT_HEAD(&tc->entries, a, list);
 	AST_DLLIST_INSERT_AFTER(&tc->entries, a, b, list);
@@ -295,11 +295,11 @@ static void dll_tests(void)
 	}
 	AST_DLLIST_TRAVERSE_SAFE_END;
 	if (AST_DLLIST_EMPTY(&tc->entries))
-		ast_log(LOG_NOTICE,"Test AST_DLLIST_REMOVE_CURRENT... OK\n");
+		ast_debug(1,"Test AST_DLLIST_REMOVE_CURRENT... OK\n");
 	else
-		ast_log(LOG_NOTICE,"Test AST_DLLIST_REMOVE_CURRENT... PROBLEM\n");
+		ast_log(LOG_ERROR,"Test AST_DLLIST_REMOVE_CURRENT... PROBLEM\n");
 	
-	ast_log(LOG_NOTICE,"Test AST_DLLIST_MOVE_CURRENT, AST_DLLIST_INSERT_BEFORE_CURRENT\n");
+	ast_debug(1,"Test AST_DLLIST_MOVE_CURRENT, AST_DLLIST_INSERT_BEFORE_CURRENT\n");
 	AST_DLLIST_INSERT_HEAD(&tc->entries, a, list);
 	AST_DLLIST_INSERT_AFTER(&tc->entries, a, b, list);
 	AST_DLLIST_INSERT_AFTER(&tc->entries, b, c, list);
@@ -325,7 +325,7 @@ static void dll_tests(void)
 	c = make_test1("C");
 	d = make_test1("D");
 
-	ast_log(LOG_NOTICE,"Test: AST_DLLIST_MOVE_CURRENT_BACKWARDS and AST_DLLIST_INSERT_BEFORE_CURRENT_BACKWARDS\n");
+	ast_debug(1,"Test: AST_DLLIST_MOVE_CURRENT_BACKWARDS and AST_DLLIST_INSERT_BEFORE_CURRENT_BACKWARDS\n");
 	AST_DLLIST_INSERT_HEAD(&tc->entries, a, list);
 	AST_DLLIST_INSERT_AFTER(&tc->entries, a, b, list);
 	AST_DLLIST_INSERT_AFTER(&tc->entries, b, c, list);
diff --git a/tests/test_sched.c b/tests/test_sched.c
index 5ad2f5d..9b0118b 100644
--- a/tests/test_sched.c
+++ b/tests/test_sched.c
@@ -45,6 +45,67 @@ static int sched_cb(const void *data)
 	return 0;
 }
 
+static int order_check;
+static int order_check_failed;
+
+static void sched_order_check(struct ast_test *test, int order)
+{
+	++order_check;
+	if (order_check != order) {
+		ast_test_status_update(test, "Unexpected execution order: expected:%d got:%d\n",
+			order, order_check);
+		order_check_failed = 1;
+	}
+}
+
+static int sched_order_1_cb(const void *data)
+{
+	sched_order_check((void *) data, 1);
+	return 0;
+}
+
+static int sched_order_2_cb(const void *data)
+{
+	sched_order_check((void *) data, 2);
+	return 0;
+}
+
+static int sched_order_3_cb(const void *data)
+{
+	sched_order_check((void *) data, 3);
+	return 0;
+}
+
+static int sched_order_4_cb(const void *data)
+{
+	sched_order_check((void *) data, 4);
+	return 0;
+}
+
+static int sched_order_5_cb(const void *data)
+{
+	sched_order_check((void *) data, 5);
+	return 0;
+}
+
+static int sched_order_6_cb(const void *data)
+{
+	sched_order_check((void *) data, 6);
+	return 0;
+}
+
+static int sched_order_7_cb(const void *data)
+{
+	sched_order_check((void *) data, 7);
+	return 0;
+}
+
+static int sched_order_8_cb(const void *data)
+{
+	sched_order_check((void *) data, 8);
+	return 0;
+}
+
 AST_TEST_DEFINE(sched_test_order)
 {
 	struct ast_sched_context *con;
@@ -152,6 +213,49 @@ AST_TEST_DEFINE(sched_test_order)
 		goto return_cleanup;
 	}
 
+	/*
+	 * Schedule immediate and delayed entries to check the order
+	 * that they get executed.  They must get executed at the
+	 * time they expire in the order they were added.
+	 */
+#define DELAYED_SAME_EXPIRE		300 /* ms */
+	ast_test_validate_cleanup(test, -1 < ast_sched_add(con, DELAYED_SAME_EXPIRE, sched_order_1_cb, test), res, return_cleanup);
+	ast_test_validate_cleanup(test, -1 < ast_sched_add(con, 0, sched_order_1_cb, test), res, return_cleanup);
+	ast_test_validate_cleanup(test, -1 < ast_sched_add(con, DELAYED_SAME_EXPIRE, sched_order_2_cb, test), res, return_cleanup);
+	ast_test_validate_cleanup(test, -1 < ast_sched_add(con, 0, sched_order_2_cb, test), res, return_cleanup);
+	ast_test_validate_cleanup(test, -1 < ast_sched_add(con, DELAYED_SAME_EXPIRE, sched_order_3_cb, test), res, return_cleanup);
+	ast_test_validate_cleanup(test, -1 < ast_sched_add(con, 0, sched_order_3_cb, test), res, return_cleanup);
+	ast_test_validate_cleanup(test, -1 < ast_sched_add(con, DELAYED_SAME_EXPIRE, sched_order_4_cb, test), res, return_cleanup);
+	ast_test_validate_cleanup(test, -1 < ast_sched_add(con, 0, sched_order_4_cb, test), res, return_cleanup);
+	ast_test_validate_cleanup(test, -1 < ast_sched_add(con, DELAYED_SAME_EXPIRE, sched_order_5_cb, test), res, return_cleanup);
+	ast_test_validate_cleanup(test, -1 < ast_sched_add(con, 0, sched_order_5_cb, test), res, return_cleanup);
+	ast_test_validate_cleanup(test, -1 < ast_sched_add(con, DELAYED_SAME_EXPIRE, sched_order_6_cb, test), res, return_cleanup);
+	ast_test_validate_cleanup(test, -1 < ast_sched_add(con, 0, sched_order_6_cb, test), res, return_cleanup);
+	ast_test_validate_cleanup(test, -1 < ast_sched_add(con, DELAYED_SAME_EXPIRE, sched_order_7_cb, test), res, return_cleanup);
+	ast_test_validate_cleanup(test, -1 < ast_sched_add(con, 0, sched_order_7_cb, test), res, return_cleanup);
+	ast_test_validate_cleanup(test, -1 < ast_sched_add(con, DELAYED_SAME_EXPIRE, sched_order_8_cb, test), res, return_cleanup);
+
+	/* Check order of scheduled immediate entries. */
+	order_check = 0;
+	order_check_failed = 0;
+	usleep(50 * 1000);/* Ensure that all the immediate entries are ready to expire */
+	ast_test_validate_cleanup(test, 7 == ast_sched_runq(con), res, return_cleanup);
+	ast_test_validate_cleanup(test, !order_check_failed, res, return_cleanup);
+
+	/* Check order of scheduled entries expiring at the same time. */
+	order_check = 0;
+	order_check_failed = 0;
+	usleep((DELAYED_SAME_EXPIRE + 50) * 1000);/* Ensure that all the delayed entries are ready to expire */
+	ast_test_validate_cleanup(test, 8 == ast_sched_runq(con), res, return_cleanup);
+	ast_test_validate_cleanup(test, !order_check_failed, res, return_cleanup);
+
+	if ((wait = ast_sched_wait(con)) != -1) {
+		ast_test_status_update(test,
+				"ast_sched_wait() should have returned -1, returned '%d'\n",
+				wait);
+		goto return_cleanup;
+	}
+
 	res = AST_TEST_PASS;
 
 return_cleanup:
diff --git a/tests/test_sorcery_memory_cache_thrash.c b/tests/test_sorcery_memory_cache_thrash.c
index 2d18317..c0d25fe 100644
--- a/tests/test_sorcery_memory_cache_thrash.c
+++ b/tests/test_sorcery_memory_cache_thrash.c
@@ -302,6 +302,15 @@ static void sorcery_memory_cache_thrash_stop(struct sorcery_memory_cache_thrash
 		}
 
 		thread->stop = 1;
+	}
+
+	for (idx = 0; idx < AST_VECTOR_SIZE(&thrash->threads); ++idx) {
+		struct sorcery_memory_cache_thrash_thread *thread;
+
+		thread = AST_VECTOR_GET(&thrash->threads, idx);
+		if (thread->thread == AST_PTHREADT_NULL) {
+			continue;
+		}
 
 		pthread_join(thread->thread, NULL);
 
diff --git a/tests/test_sorcery_realtime.c b/tests/test_sorcery_realtime.c
index 3791c7c..eaee9ff 100644
--- a/tests/test_sorcery_realtime.c
+++ b/tests/test_sorcery_realtime.c
@@ -67,17 +67,13 @@ static int realtime_is_object_matching(const char *object_id, const struct ast_v
 
 		/* If we are doing a pattern matching we need to remove the LIKE from the name */
 		if ((like = strstr(name, " LIKE"))) {
-			char *pattern, *field_value = ast_strdupa(field->value);
+			char *field_value = ast_strdupa(field->value);
 
 			*like = '\0';
 
 			value = ast_strdupa(ast_variable_retrieve(realtime_objects, object_id, name));
 
-			if (!(pattern = strchr(field_value, '%'))) {
-				return 0;
-			}
-
-			*pattern = '\0';
+			field_value = ast_strip_quoted(field_value, "%", "%");
 
 			if (strncmp(value, field_value, strlen(field_value))) {
 				return 0;
@@ -567,7 +563,7 @@ AST_TEST_DEFINE(object_retrieve_regex)
 		return AST_TEST_FAIL;
 	}
 
-	if (!(objects = ast_sorcery_retrieve_by_regex(sorcery, "test", "^blah-"))) {
+	if (!(objects = ast_sorcery_retrieve_by_regex(sorcery, "test", "blah-"))) {
 		ast_test_status_update(test, "Failed to retrieve a container of objects\n");
 		return AST_TEST_FAIL;
 	} else if (ao2_container_count(objects) != 2) {
diff --git a/tests/test_stasis_endpoints.c b/tests/test_stasis_endpoints.c
index 7ac5291..17e2d05 100644
--- a/tests/test_stasis_endpoints.c
+++ b/tests/test_stasis_endpoints.c
@@ -185,9 +185,10 @@ AST_TEST_DEFINE(cache_clear)
 
 	/* Note: there's a few messages between the creation and the clear.
 	 * Wait for all of them... */
-	message_index = stasis_message_sink_wait_for(sink, message_index + 4,
+	message_index = stasis_message_sink_wait_for(sink, message_index + 2,
 		cache_update, __func__, STASIS_SINK_DEFAULT_WAIT);
 	ast_test_validate(test, 0 <= message_index);
+
 	/* Now we should have a cache removal entry */
 	msg = sink->messages[message_index];
 	type = stasis_message_type(msg);
diff --git a/tests/test_threadpool.c b/tests/test_threadpool.c
index 42181a2..d8acf26 100644
--- a/tests/test_threadpool.c
+++ b/tests/test_threadpool.c
@@ -636,6 +636,14 @@ AST_TEST_DEFINE(threadpool_thread_timeout_thrash)
 		ast_mutex_unlock(&tld->lock);
 
 		ast_threadpool_push(pool, simple_task, std);
+
+		res = wait_for_completion(test, std);
+
+		ast_free(std);
+
+		if (res == AST_TEST_FAIL) {
+			goto end;
+		}
 	}
 
 	res = wait_until_thread_state(test, tld, 0, 0);
@@ -913,6 +921,41 @@ end:
 	return res;
 }
 
+static enum ast_test_result_state wait_until_thread_state_task_pushed(struct ast_test *test,
+		struct test_listener_data *tld, int num_active, int num_idle, int num_tasks)
+{
+	enum ast_test_result_state res = AST_TEST_PASS;
+	struct timeval start;
+	struct timespec end;
+
+	res = wait_until_thread_state(test, tld, num_active, num_idle);
+	if (res == AST_TEST_FAIL) {
+		return res;
+	}
+
+	start = ast_tvnow();
+	end.tv_sec = start.tv_sec + 5;
+	end.tv_nsec = start.tv_usec * 1000;
+
+	ast_mutex_lock(&tld->lock);
+
+	while (tld->num_tasks != num_tasks) {
+		if (ast_cond_timedwait(&tld->cond, &tld->lock, &end) == ETIMEDOUT) {
+			break;
+		}
+	}
+
+	if (tld->num_tasks != num_tasks) {
+		ast_test_status_update(test, "Number of tasks pushed %d does not match expected %d\n",
+				tld->num_tasks, num_tasks);
+		res = AST_TEST_FAIL;
+	}
+
+	ast_mutex_unlock(&tld->lock);
+
+	return res;
+}
+
 AST_TEST_DEFINE(threadpool_auto_increment)
 {
 	struct ast_threadpool *pool = NULL;
@@ -1013,11 +1056,10 @@ AST_TEST_DEFINE(threadpool_auto_increment)
 		goto end;
 	}
 
-	res = wait_until_thread_state(test, tld, 0, 3);
+	res = wait_until_thread_state_task_pushed(test, tld, 0, 3, 4);
 	if (res == AST_TEST_FAIL) {
 		goto end;
 	}
-	res = listener_check(test, listener, 1, 0, 4, 0, 3, 1);
 
 end:
 	ast_threadpool_shutdown(pool);
diff --git a/third-party/Makefile b/third-party/Makefile
new file mode 100644
index 0000000..0aca21e
--- /dev/null
+++ b/third-party/Makefile
@@ -0,0 +1,21 @@
+
+include Makefile.rules
+
+TP_SUBDIRS := pjproject
+# Sub directories that contain special install/uninstall targets must be explicitly listed
+# to prevent accidentally running the package's default install target.
+TP_INSTALL_SUBDIRS := pjproject
+
+.PHONY: all dist-clean distclean install clean moduleinfo makeopts uninstall __embed_libs __embed_ldscript __embed_ldflags $(TP_SUBDIRS)
+
+override MAKECMDGOALS?=all
+
+MAKECMDGOALS:=$(subst dist-clean,distclean,$(MAKECMDGOALS))
+MAKECMDGOALS:=$(subst tpclean,clean,$(MAKECMDGOALS))
+
+all distclean dist-clean install tpclean : $(TP_SUBDIRS)
+install uninstall: $(TP_INSTALL_SUBDIRS)
+
+$(TP_SUBDIRS):
+	+$(CMD_PREFIX) $(SUBMAKE) -C $@ $(MAKECMDGOALS)
+
diff --git a/third-party/Makefile.rules b/third-party/Makefile.rules
new file mode 100644
index 0000000..e633e0e
--- /dev/null
+++ b/third-party/Makefile.rules
@@ -0,0 +1,36 @@
+
+ifeq ($(NOISY_BUILD),)
+SUBMAKE?=$(MAKE) --quiet --no-print-directory
+ECHO_PREFIX?=@
+CMD_PREFIX?=@
+QUIET_CONFIGURE=-q
+REALLY_QUIET=&>/dev/null
+else
+SUBMAKE?=$(MAKE)
+ECHO_PREFIX?=@\#
+CMD_PREFIX?=
+QUIET_CONFIGURE=
+REALLY_QUIET=
+endif
+
+DOWNLOAD := $(shell which wget 2>/dev/null)
+DOWNLOAD := $(if $(DOWNLOAD),$(DOWNLOAD) -O- ,)
+
+ifeq ($(DOWNLOAD),)
+DOWNLOAD := $(shell which curl 2>/dev/null)
+DOWNLOAD := $(if $(DOWNLOAD), $(DOWNLOAD) -L ,)
+endif
+
+ifeq ($(DOWNLOAD),)
+DOWNLOAD := echo "No download program available" ; exit 1;
+endif
+
+export SUBMAKE
+export ECHO_PREFIX
+export CMD_PREFIX
+export QUIET_CONFIGURE
+export REALLY_QUIET
+export ASTTOPDIR
+export ASTSBINDIR
+export DESTDIR
+export ASTDATADIR
diff --git a/third-party/pjproject/Makefile b/third-party/pjproject/Makefile
new file mode 100644
index 0000000..5810a65
--- /dev/null
+++ b/third-party/pjproject/Makefile
@@ -0,0 +1,145 @@
+.SUFFIXES:
+.PHONY: _all all _install install clean distclean echo_cflags configure
+
+include ../versions.mak
+
+SPECIAL_TARGETS :=
+
+ifneq ($(findstring configure,$(MAKECMDGOALS))$(findstring echo_cflags,$(MAKECMDGOALS)),)
+# Run from $(ASTTOPDIR)/configure
+    SPECIAL_TARGETS += configure
+    include ../Makefile.rules
+    include Makefile.rules
+endif
+
+ifeq ($(findstring echo_cflags,$(MAKECMDGOALS)),echo_cflags)
+    -include build.mak
+    ECHO_PREFIX=@\#
+endif
+
+ifeq ($(findstring clean,$(MAKECMDGOALS)),clean)
+# clean or distclean
+    SPECIAL_TARGETS += clean
+    include ../Makefile.rules
+    include Makefile.rules
+endif
+
+ifeq ($(SPECIAL_TARGETS),)
+# Run locally or from $(ASTTOPDIR)/Makefile.  All include files should be present
+    ifeq ($(wildcard ../../makeopts),)
+        $(error ASTTOPDIR/configure hasn't been run)
+    endif
+    include ../../makeopts
+
+    ifeq ($(PJPROJECT_BUNDLED),yes)
+        -include ../../menuselect.makeopts
+        include ../Makefile.rules
+
+        all: _all
+        install: _install
+
+        include ../../Makefile.rules
+        include Makefile.rules
+        include build.mak
+        CF := $(filter-out -W%,$(CC_CFLAGS))
+        CF := $(filter-out -I%,$(CF))
+        export CFLAGS += $(CF)
+        export LDFLAGS += $(CC_LDFLAGS)
+    else
+        all install:
+    endif
+endif
+
+ECHO_PREFIX := $(ECHO_PREFIX) echo '[pjproject] '
+
+ifndef $(TMPDIR)
+    ifneq ($(wildcard /tmp),)
+        TMPDIR=/tmp
+    else
+        TMPDIR=.
+    endif
+endif
+
+$(TMPDIR)/pjproject-$(PJPROJECT_VERSION).tar.bz2 : ../versions.mak
+	$(ECHO_PREFIX) Downloading $@ with $(DOWNLOAD)
+	$(CMD_PREFIX) $(DOWNLOAD) $(PJPROJECT_URL)/$(@F) > $@
+
+source/.unpacked: $(TMPDIR)/pjproject-$(PJPROJECT_VERSION).tar.bz2
+	$(ECHO_PREFIX) Unpacking $<
+	- at rm -rf source &>/dev/null
+	- at mkdir source &>/dev/null
+	$(CMD_PREFIX) tar --strip-components=1 -C source -xjf $<
+	$(ECHO_PREFIX) Applying patches
+	$(CMD_PREFIX) ./apply_patches $(QUIET_CONFIGURE) ./patches ./source
+	- at touch source/.unpacked
+
+source/user.mak: source/.unpacked ./patches/user.mak
+	$(ECHO_PREFIX) Applying user.mak
+	$(CMD_PREFIX) cp -f ./patches/user.mak ./source/
+
+source/pjlib/include/pj/config_site.h: source/.unpacked ./patches/config_site.h
+	$(ECHO_PREFIX) Applying config_site.h
+	$(CMD_PREFIX) cp -f ./patches/config_site.h ./source/pjlib/include/pj/
+
+build.mak: source/.unpacked source/pjlib/include/pj/config_site.h source/user.mak Makefile.rules
+	$(ECHO_PREFIX) Configuring with $(PJPROJECT_CONFIG_OPTS)
+	$(CMD_PREFIX) (cd source ; autoconf aconfigure.ac > aconfigure && ./aconfigure $(QUIET_CONFIGURE) $(PJPROJECT_CONFIG_OPTS))
+	@sed -r -e "/prefix|export PJ_SHARED_LIBRARIES|MACHINE_NAME|OS_NAME|HOST_NAME|CC_NAME|CROSS_COMPILE|LINUX_POLL/d" source/build.mak > build.mak
+
+configure: build.mak
+
+echo_cflags: build.mak
+	@echo $(PJ_CFLAGS)
+
+source/pjlib/build/.pjlib-$(TARGET_NAME).depend: build.mak
+	$(ECHO_PREFIX) "Making dependencies"
+	+$(CMD_PREFIX) $(SUBMAKE) -C source dep
+
+
+menuselect: ../../menuselect.makeopts ../../makeopts
+	-$(CMD_PREFIX) test -d source && ($(SUBMAKE) -C source clean ; find source -name *.a -delete ; rm -rf source/pjsip-apps/src/python/build) || :
+	-$(CMD_PREFIX) rm -rf pjproject.symbols
+
+
+source/pjlib/lib/libpj-$(TARGET_NAME).a: menuselect source/pjlib/build/.pjlib-$(TARGET_NAME).depend
+	$(ECHO_PREFIX) Compiling libs
+	+$(CMD_PREFIX) $(SUBMAKE) -C source lib $(REALLY_QUIET)
+
+pjproject.symbols: source/pjlib/lib/libpj-$(TARGET_NAME).a
+	$(ECHO_PREFIX) Generating symbols
+	$(CMD_PREFIX) nm -Pog $(PJ_LIB_FILES) | sed -n -r -e "s/.+: ([pP][jJ][^ ]+) .+/\1/gp" | sort -u > pjproject.symbols
+
+source/pjsip-apps/bin/pjsua-$(TARGET_NAME): source/pjlib/lib/libpj-$(TARGET_NAME).a
+	$(ECHO_PREFIX) Compiling apps
+	$(CMD_PREFIX) $(SUBMAKE) -C source/pjsip-apps/build pjsua pjsystest $(REALLY_QUIET)
+
+source/pjsip-apps/src/python/build/_pjsua.so: source/pjlib/lib/libpj-$(TARGET_NAME).a
+	$(ECHO_PREFIX) Compiling python bindings
+	$(CMD_PREFIX) (cd source/pjsip-apps/src/python ; python setup.py build --build-platlib=./build $(REALLY_QUIET))
+
+
+_all: pjproject.symbols source/pjsip-apps/bin/pjsua-$(TARGET_NAME) source/pjsip-apps/src/python/build/_pjsua.so
+
+_install: _all
+	$(ECHO_PREFIX) Installing apps and python bindings
+	@if [ ! -d "$(DESTDIR)$(ASTDATADIR)/third-party/pjproject" ]; then \
+		$(INSTALL) -d "$(DESTDIR)$(ASTDATADIR)/third-party/pjproject"; \
+	fi;
+	$(CMD_PREFIX) $(INSTALL) -m 755 source/pjsip-apps/bin/pjsua-$(TARGET_NAME) "$(DESTDIR)$(ASTDATADIR)/third-party/pjproject/pjsua"
+	$(CMD_PREFIX) $(INSTALL) -m 755 source/pjsip-apps/bin/pjsystest-$(TARGET_NAME) "$(DESTDIR)$(ASTDATADIR)/third-party/pjproject/pjsystest"
+	$(CMD_PREFIX) $(INSTALL) -m 755 source/pjsip-apps/src/python/build/_pjsua.so "$(DESTDIR)$(ASTDATADIR)/third-party/pjproject/"
+	$(CMD_PREFIX) $(INSTALL) -m 644 source/pjsip-apps/src/python/build/pjsua.py "$(DESTDIR)$(ASTDATADIR)/third-party/pjproject/"
+
+uninstall:
+	$(ECHO_PREFIX) Uninstalling apps and python bindings
+	$(CMD_PREFIX) rm -rf "$(DESTDIR)$(ASTDATADIR)/third-party/pjproject"
+
+clean:
+	$(ECHO_PREFIX) Cleaning
+	-$(CMD_PREFIX) test -d source && ($(SUBMAKE) -C source clean ; find source -name *.a -delete ; rm -rf source/pjsip-apps/src/python/build) || :
+	-$(CMD_PREFIX) rm -rf pjproject.symbols
+
+distclean:
+	$(ECHO_PREFIX) Distcleaning
+	-$(CMD_PREFIX) rm -rf source pjproject.symbols pjproject-*.tar.bz2 build.mak
+
diff --git a/third-party/pjproject/Makefile.rules b/third-party/pjproject/Makefile.rules
new file mode 100644
index 0000000..f39629b
--- /dev/null
+++ b/third-party/pjproject/Makefile.rules
@@ -0,0 +1,7 @@
+PJPROJECT_URL = http://www.pjsip.org/release/$(PJPROJECT_VERSION)
+
+# Even though we're not installing pjproject, we're setting prefix to /opt/pjproject to be safe
+PJPROJECT_CONFIG_OPTS = --prefix=/opt/pjproject --with-external-speex --with-external-gsm --with-external-srtp \
+	--with-external-pa --disable-video --disable-v4l2 --disable-sound \
+	--disable-opencore-amr --disable-ilbc-codec --without-libyuv --disable-g7221-codec \
+	--enable-epoll
diff --git a/third-party/pjproject/apply_patches b/third-party/pjproject/apply_patches
new file mode 100755
index 0000000..1b72d14
--- /dev/null
+++ b/third-party/pjproject/apply_patches
@@ -0,0 +1,39 @@
+#!/bin/bash
+
+if [ "$1" = "-q" ] ; then
+	quiet=1
+	shift
+fi
+
+patchdir=${1:?You must supply a patches directory}
+sourcedir=${2?:You must supply a source directory}
+
+patchdir=`readlink -f $patchdir`
+sourcedir=`readlink -f $sourcedir`
+
+if [ ! -d "$patchdir" ] ; then
+	echo "$patchdir is not a directory" >&2
+	exit 1
+fi
+
+if [ ! -d "$sourcedir" ] ; then
+	echo "$sourcedir is not a directory"  >&2
+	exit 1
+fi
+
+if [ ! "$(ls -A $patchdir/*.patch 2>/dev/null)" ] ; then
+	echo "No patches in $patchdir"  >&2
+	exit 0
+fi
+
+for patchfile in $patchdir/*.patch ; do
+	patch -d $sourcedir -p1 -s -r- -f -N --dry-run -i "$patchfile" || (echo "Patchfile $(basename $patchfile) failed to apply >&2" ; exit 1) || exit 1
+done
+
+for patchfile in "$patchdir"/*.patch ; do
+	[ -z $quiet ] && echo "Applying patch $(basename $patchfile)"
+	patch -d "$sourcedir" -p1 -s -i "$patchfile" || exit 1
+done
+
+exit 0
+
diff --git a/third-party/pjproject/configure.m4 b/third-party/pjproject/configure.m4
new file mode 100644
index 0000000..7432227
--- /dev/null
+++ b/third-party/pjproject/configure.m4
@@ -0,0 +1,47 @@
+AC_DEFUN([PJPROJECT_SYMBOL_CHECK],
+[
+	$1_INCLUDE="$PJPROJECT_INCLUDE"
+    AC_MSG_CHECKING([for $2 declared in $3])
+
+	saved_cpp="$CPPFLAGS"
+	CPPFLAGS="$PJPROJECT_INCLUDE"
+	AC_EGREP_HEADER($2, $3, [
+		AC_MSG_RESULT(yes)
+		PBX_$1=1
+		AC_DEFINE([HAVE_$1], 1, [Define if your system has $2 declared.])
+	], [
+		AC_MSG_RESULT(no)
+	])
+
+	CPPGLAGS="$saved_cpp"
+	$1_INCLUDE="$PJPROJECT_INCLUDE"
+])
+
+AC_DEFUN([PJPROJECT_CONFIGURE],
+[
+	AC_MSG_CHECKING(for embedded pjproject (may have to download))
+	AC_MSG_RESULT(configuring)
+	make --quiet --no-print-directory -C $1 configure
+	if test $? -ne 0 ; then
+		AC_MSG_RESULT(failed)
+		AC_MSG_NOTICE(Unable to configure $1)
+		AC_MSG_ERROR(Run "make -C $1 NOISY_BUILD=yes configure" to see error details.)
+	fi
+
+	PJPROJECT_INCLUDE=$(make --quiet --no-print-directory -C $1 echo_cflags)
+	PJPROJECT_CFLAGS="$PJPROJECT_INCLUDE"
+	PBX_PJPROJECT=1
+	PJPROJECT_BUNDLED=yes
+	AC_DEFINE([HAVE_PJPROJECT], 1, [Define if your system has PJPROJECT])
+	AC_DEFINE([HAVE_PJPROJECT_BUNDLED], 1, [Define if your system has PJPROJECT_BUNDLED])
+	AC_MSG_CHECKING(for embedded pjproject)
+	AC_MSG_RESULT(yes)
+
+	PJPROJECT_SYMBOL_CHECK([PJSIP_DLG_CREATE_UAS_AND_INC_LOCK], [pjsip_dlg_create_uas_and_inc_lock], [pjsip.h])
+	PJPROJECT_SYMBOL_CHECK([PJ_TRANSACTION_GRP_LOCK], [pjsip_tsx_create_uac2], [pjsip.h])
+	PJPROJECT_SYMBOL_CHECK([PJSIP_REPLACE_MEDIA_STREAM], [PJMEDIA_SDP_NEG_ALLOW_MEDIA_CHANGE], [pjmedia.h])
+	PJPROJECT_SYMBOL_CHECK([PJSIP_GET_DEST_INFO], [pjsip_get_dest_info], [pjsip.h])
+	PJPROJECT_SYMBOL_CHECK([PJ_SSL_CERT_LOAD_FROM_FILES2], [pj_ssl_cert_load_from_files2], [pjlib.h])
+	PJPROJECT_SYMBOL_CHECK([PJSIP_EXTERNAL_RESOLVER], [pjsip_endpt_set_ext_resolver], [pjsip.h])
+	AC_DEFINE([HAVE_PJSIP_TLS_TRANSPORT_PROTO], 1, [Define if your system has PJSIP_TLS_TRANSPORT_PROTO])
+])
diff --git a/third-party/pjproject/patches/0001-2.4.5-fix-for-tls-async-ops.patch b/third-party/pjproject/patches/0001-2.4.5-fix-for-tls-async-ops.patch
new file mode 100644
index 0000000..33fc8ea
--- /dev/null
+++ b/third-party/pjproject/patches/0001-2.4.5-fix-for-tls-async-ops.patch
@@ -0,0 +1,224 @@
+diff --git a/pjlib/include/pj/ssl_sock.h b/pjlib/include/pj/ssl_sock.h
+index 1682bda..a69af32 100644
+--- a/pjlib/include/pj/ssl_sock.h
++++ b/pjlib/include/pj/ssl_sock.h
+@@ -864,6 +864,18 @@ PJ_DECL(void) pj_ssl_sock_param_default(pj_ssl_sock_param *param);
+ 
+ 
+ /**
++ * Duplicate pj_ssl_sock_param.
++ *
++ * @param pool	Pool to allocate memory.
++ * @param dst	Destination parameter.
++ * @param src	Source parameter.
++ */
++PJ_DECL(void) pj_ssl_sock_param_copy(pj_pool_t *pool, 
++				     pj_ssl_sock_param *dst,
++				     const pj_ssl_sock_param *src);
++
++
++/**
+  * Create secure socket instance.
+  *
+  * @param pool		The pool for allocating secure socket instance.
+@@ -1115,6 +1127,30 @@ PJ_DECL(pj_status_t) pj_ssl_sock_start_accept(pj_ssl_sock_t *ssock,
+ 
+ 
+ /**
++ * Same as #pj_ssl_sock_start_accept(), but application can provide
++ * a secure socket parameter, which will be used to create a new secure
++ * socket reported in \a on_accept_complete() callback when there is
++ * an incoming connection.
++ *
++ * @param ssock		The secure socket.
++ * @param pool		Pool used to allocate some internal data for the
++ *			operation.
++ * @param localaddr	Local address to bind on.
++ * @param addr_len	Length of buffer containing local address.
++ * @param newsock_param	Secure socket parameter for new accepted sockets.
++ *
++ * @return		PJ_SUCCESS if the operation has been successful,
++ *			or the appropriate error code on failure.
++ */
++PJ_DECL(pj_status_t)
++pj_ssl_sock_start_accept2(pj_ssl_sock_t *ssock,
++			  pj_pool_t *pool,
++			  const pj_sockaddr_t *local_addr,
++			  int addr_len,
++			  const pj_ssl_sock_param *newsock_param);
++
++
++/**
+  * Starts asynchronous socket connect() operation and SSL/TLS handshaking 
+  * for this socket. Once the connection is done (either successfully or not),
+  * the \a on_connect_complete() callback will be called.
+diff --git a/pjlib/src/pj/ssl_sock_common.c b/pjlib/src/pj/ssl_sock_common.c
+index 913efee..717ab1d 100644
+--- a/pjlib/src/pj/ssl_sock_common.c
++++ b/pjlib/src/pj/ssl_sock_common.c
+@@ -19,6 +19,7 @@
+ #include <pj/ssl_sock.h>
+ #include <pj/assert.h>
+ #include <pj/errno.h>
++#include <pj/pool.h>
+ #include <pj/string.h>
+ 
+ /*
+@@ -48,6 +49,31 @@ PJ_DEF(void) pj_ssl_sock_param_default(pj_ssl_sock_param *param)
+ }
+ 
+ 
++/*
++ * Duplicate SSL socket parameter.
++ */
++PJ_DEF(void) pj_ssl_sock_param_copy( pj_pool_t *pool, 
++				     pj_ssl_sock_param *dst,
++				     const pj_ssl_sock_param *src)
++{
++    /* Init secure socket param */
++    pj_memcpy(dst, src, sizeof(*dst));
++    if (src->ciphers_num > 0) {
++	unsigned i;
++	dst->ciphers = (pj_ssl_cipher*)
++			pj_pool_calloc(pool, src->ciphers_num, 
++				       sizeof(pj_ssl_cipher));
++	for (i = 0; i < src->ciphers_num; ++i)
++	    dst->ciphers[i] = src->ciphers[i];
++    }
++
++    if (src->server_name.slen) {
++        /* Server name must be null-terminated */
++        pj_strdup_with_null(pool, &dst->server_name, &src->server_name);
++    }
++}
++
++
+ PJ_DEF(pj_status_t) pj_ssl_cert_get_verify_status_strings(
+ 						pj_uint32_t verify_status, 
+ 						const char *error_strings[],
+diff --git a/pjlib/src/pj/ssl_sock_ossl.c b/pjlib/src/pj/ssl_sock_ossl.c
+index 40a5a1e..6a701b7 100644
+--- a/pjlib/src/pj/ssl_sock_ossl.c
++++ b/pjlib/src/pj/ssl_sock_ossl.c
+@@ -141,6 +141,7 @@ struct pj_ssl_sock_t
+     pj_pool_t		 *pool;
+     pj_ssl_sock_t	 *parent;
+     pj_ssl_sock_param	  param;
++    pj_ssl_sock_param	  newsock_param;
+     pj_ssl_cert_t	 *cert;
+     
+     pj_ssl_cert_info	  local_cert_info;
+@@ -1757,11 +1758,9 @@ static pj_bool_t asock_on_accept_complete (pj_activesock_t *asock,
+     unsigned i;
+     pj_status_t status;
+ 
+-    PJ_UNUSED_ARG(src_addr_len);
+-
+     /* Create new SSL socket instance */
+-    status = pj_ssl_sock_create(ssock_parent->pool, &ssock_parent->param,
+-				&ssock);
++    status = pj_ssl_sock_create(ssock_parent->pool,
++    				&ssock_parent->newsock_param, &ssock);
+     if (status != PJ_SUCCESS)
+ 	goto on_return;
+ 
+@@ -2183,20 +2182,8 @@ PJ_DEF(pj_status_t) pj_ssl_sock_create (pj_pool_t *pool,
+ 	return status;
+ 
+     /* Init secure socket param */
+-    ssock->param = *param;
++    pj_ssl_sock_param_copy(pool, &ssock->param, param);
+     ssock->param.read_buffer_size = ((ssock->param.read_buffer_size+7)>>3)<<3;
+-    if (param->ciphers_num > 0) {
+-	unsigned i;
+-	ssock->param.ciphers = (pj_ssl_cipher*)
+-			       pj_pool_calloc(pool, param->ciphers_num, 
+-					      sizeof(pj_ssl_cipher));
+-	for (i = 0; i < param->ciphers_num; ++i)
+-	    ssock->param.ciphers[i] = param->ciphers[i];
+-    }
+-
+-    /* Server name must be null-terminated */
+-    pj_strdup_with_null(pool, &ssock->param.server_name, 
+-			&param->server_name);
+ 
+     /* Finally */
+     *p_ssock = ssock;
+@@ -2617,12 +2604,36 @@ PJ_DEF(pj_status_t) pj_ssl_sock_start_accept (pj_ssl_sock_t *ssock,
+ 					      const pj_sockaddr_t *localaddr,
+ 					      int addr_len)
+ {
++    return pj_ssl_sock_start_accept2(ssock, pool, localaddr, addr_len,
++    				     &ssock->param);
++}
++
++
++/**
++ * Same as #pj_ssl_sock_start_accept(), but application provides parameter
++ * for new accepted secure sockets.
++ */
++PJ_DEF(pj_status_t)
++pj_ssl_sock_start_accept2(pj_ssl_sock_t *ssock,
++			  pj_pool_t *pool,
++			  const pj_sockaddr_t *localaddr,
++			  int addr_len,
++			  const pj_ssl_sock_param *newsock_param)
++{
+     pj_activesock_cb asock_cb;
+     pj_activesock_cfg asock_cfg;
+     pj_status_t status;
+ 
+     PJ_ASSERT_RETURN(ssock && pool && localaddr && addr_len, PJ_EINVAL);
+ 
++    /* Verify new socket parameters */
++    if (newsock_param->grp_lock != ssock->param.grp_lock ||
++        newsock_param->sock_af != ssock->param.sock_af ||
++        newsock_param->sock_type != ssock->param.sock_type)
++    {
++        return PJ_EINVAL;
++    }
++
+     /* Create socket */
+     status = pj_sock_socket(ssock->param.sock_af, ssock->param.sock_type, 0, 
+ 			    &ssock->sock);
+@@ -2691,6 +2702,7 @@ PJ_DEF(pj_status_t) pj_ssl_sock_start_accept (pj_ssl_sock_t *ssock,
+ 	goto on_error;
+ 
+     /* Start accepting */
++    pj_ssl_sock_param_copy(pool, &ssock->newsock_param, newsock_param);
+     status = pj_activesock_start_accept(ssock->asock, pool);
+     if (status != PJ_SUCCESS)
+ 	goto on_error;
+diff --git a/pjsip/src/pjsip/sip_transport_tls.c b/pjsip/src/pjsip/sip_transport_tls.c
+index a9e95fb..91d99a7 100644
+--- a/pjsip/src/pjsip/sip_transport_tls.c
++++ b/pjsip/src/pjsip/sip_transport_tls.c
+@@ -314,7 +314,7 @@ PJ_DEF(pj_status_t) pjsip_tls_transport_start2( pjsip_endpoint *endpt,
+     int af, sip_ssl_method;
+     pj_uint32_t sip_ssl_proto;
+     struct tls_listener *listener;
+-    pj_ssl_sock_param ssock_param;
++    pj_ssl_sock_param ssock_param, newsock_param;
+     pj_sockaddr *listener_addr;
+     pj_bool_t has_listener;
+     pj_status_t status;
+@@ -473,9 +473,14 @@ PJ_DEF(pj_status_t) pjsip_tls_transport_start2( pjsip_endpoint *endpt,
+      */
+     has_listener = PJ_FALSE;
+ 
+-    status = pj_ssl_sock_start_accept(listener->ssock, pool, 
++    pj_memcpy(&newsock_param, &ssock_param, sizeof(newsock_param));
++    newsock_param.async_cnt = 1;
++    newsock_param.cb.on_data_read = &on_data_read;
++    newsock_param.cb.on_data_sent = &on_data_sent;
++    status = pj_ssl_sock_start_accept2(listener->ssock, pool, 
+ 			  (pj_sockaddr_t*)listener_addr, 
+-			  pj_sockaddr_get_len((pj_sockaddr_t*)listener_addr));
++			  pj_sockaddr_get_len((pj_sockaddr_t*)listener_addr),
++			  &newsock_param);
+     if (status == PJ_SUCCESS || status == PJ_EPENDING) {
+ 	pj_ssl_sock_info info;
+ 	has_listener = PJ_TRUE;
+-- 
+cgit v0.11.2
+
diff --git a/third-party/pjproject/patches/0001-Bump-tcp-tls-and-transaction-log-levels-from-1-to-3.patch b/third-party/pjproject/patches/0001-Bump-tcp-tls-and-transaction-log-levels-from-1-to-3.patch
new file mode 100644
index 0000000..9873abf
--- /dev/null
+++ b/third-party/pjproject/patches/0001-Bump-tcp-tls-and-transaction-log-levels-from-1-to-3.patch
@@ -0,0 +1,70 @@
+From a147b72df1ec150c1d733e882225db86142fb339 Mon Sep 17 00:00:00 2001
+From: George Joseph <george.joseph at fairview5.com>
+Date: Sun, 21 Feb 2016 10:01:53 -0700
+Subject: [PATCH] Bump tcp/tls and transaction log levels from 1 to 3
+
+sip_transport_tcp, sip_transport_tls and sip_transaction are printing messages
+at log level 1 or 2 for things that are transient, recoverable, possibly
+expected, or are handled with return codes. A good example of this is if we're
+trying to send an OPTIONS message to a TCP client that has disappeared.  Both
+sip_transport_tcp and sip_transaction are printing "connection refused"
+messages because the remote client isn't listening.  This is generally expected
+behavior and it should be up to the app caller to determine if an error message
+is warranted.
+---
+ pjsip/src/pjsip/sip_transaction.c   | 4 ++--
+ pjsip/src/pjsip/sip_transport_tcp.c | 2 +-
+ pjsip/src/pjsip/sip_transport_tls.c | 2 +-
+ 3 files changed, 4 insertions(+), 4 deletions(-)
+
+diff --git a/pjsip/src/pjsip/sip_transaction.c b/pjsip/src/pjsip/sip_transaction.c
+index 46bd971..1b4fdb7 100644
+--- a/pjsip/src/pjsip/sip_transaction.c
++++ b/pjsip/src/pjsip/sip_transaction.c
+@@ -1898,7 +1898,7 @@ static void send_msg_callback( pjsip_send_state *send_state,
+ 
+ 	    err =pj_strerror((pj_status_t)-sent, errmsg, sizeof(errmsg));
+ 
+-	    PJ_LOG(2,(tsx->obj_name,
++	    PJ_LOG(3,(tsx->obj_name,
+ 		      "Failed to send %s! err=%d (%s)",
+ 		      pjsip_tx_data_get_info(send_state->tdata), -sent,
+ 		      errmsg));
+@@ -1938,7 +1938,7 @@ static void send_msg_callback( pjsip_send_state *send_state,
+ 	    }
+ 
+ 	} else {
+-	    PJ_PERROR(2,(tsx->obj_name, (pj_status_t)-sent,
++	    PJ_PERROR(3,(tsx->obj_name, (pj_status_t)-sent,
+ 		         "Temporary failure in sending %s, "
+ 		         "will try next server",
+ 		         pjsip_tx_data_get_info(send_state->tdata)));
+diff --git a/pjsip/src/pjsip/sip_transport_tcp.c b/pjsip/src/pjsip/sip_transport_tcp.c
+index 222cb13..1bbb324 100644
+--- a/pjsip/src/pjsip/sip_transport_tcp.c
++++ b/pjsip/src/pjsip/sip_transport_tcp.c
+@@ -164,7 +164,7 @@ static void tcp_perror(const char *sender, const char *title,
+ 
+     pj_strerror(status, errmsg, sizeof(errmsg));
+ 
+-    PJ_LOG(1,(sender, "%s: %s [code=%d]", title, errmsg, status));
++    PJ_LOG(3,(sender, "%s: %s [code=%d]", title, errmsg, status));
+ }
+ 
+ 
+diff --git a/pjsip/src/pjsip/sip_transport_tls.c b/pjsip/src/pjsip/sip_transport_tls.c
+index 617d7f5..a83ac32 100644
+--- a/pjsip/src/pjsip/sip_transport_tls.c
++++ b/pjsip/src/pjsip/sip_transport_tls.c
+@@ -170,7 +170,7 @@ static void tls_perror(const char *sender, const char *title,
+ 
+     pj_strerror(status, errmsg, sizeof(errmsg));
+ 
+-    PJ_LOG(1,(sender, "%s: %s [code=%d]", title, errmsg, status));
++    PJ_LOG(3,(sender, "%s: %s [code=%d]", title, errmsg, status));
+ }
+ 
+ 
+-- 
+2.5.0
+
diff --git a/third-party/pjproject/patches/0001-ioqueue-Enable-epoll-in-aconfigure.ac.patch b/third-party/pjproject/patches/0001-ioqueue-Enable-epoll-in-aconfigure.ac.patch
new file mode 100644
index 0000000..36b6c65
--- /dev/null
+++ b/third-party/pjproject/patches/0001-ioqueue-Enable-epoll-in-aconfigure.ac.patch
@@ -0,0 +1,80 @@
+From b5c0bc905911f75e08987e6833075481fe16dab2 Mon Sep 17 00:00:00 2001
+From: George Joseph <george.joseph at fairview5.com>
+Date: Mon, 22 Feb 2016 13:05:59 -0700
+Subject: [PATCH] ioqueue:  Enable epoll in aconfigure.ac
+
+Although the --enable-epoll option was being accepted, the result
+was always forced to select.  This patch updates aconfigure.ac
+to properly set the value of ac_linux_poll if --enable-epoll is
+specified.
+---
+ README.txt                           |  1 +
+ aconfigure                           | 11 +++++++----
+ aconfigure.ac                        |  7 +++++--
+ pjlib/include/pj/compat/os_auto.h.in |  3 +++
+ 4 files changed, 16 insertions(+), 6 deletions(-)
+
+diff --git a/README.txt b/README.txt
+index bc45da8..48415fd 100644
+--- a/README.txt
++++ b/README.txt
+@@ -463,6 +463,7 @@ Using Default Settings
+    $ ./configure --help
+    ...
+    Optional Features:
++   --enable-epoll           Use epoll on Linux instead of select
+    --disable-floating-point	Disable floating point where possible
+    --disable-sound 		Exclude sound (i.e. use null sound)
+    --disable-small-filter 	Exclude small filter in resampling
+diff --git a/aconfigure.ac b/aconfigure.ac
+index 2f71abb..3e88124 100644
+--- a/aconfigure.ac
++++ b/aconfigure.ac
+@@ -410,6 +410,7 @@ dnl ######################
+ dnl # ioqueue selection
+ dnl # 
+ AC_SUBST(ac_os_objs)
++AC_SUBST(ac_linux_poll)
+ AC_MSG_CHECKING([ioqueue backend])
+ AC_ARG_ENABLE(epoll,
+ 	      AC_HELP_STRING([--enable-epoll],
+@@ -417,10 +418,13 @@ AC_ARG_ENABLE(epoll,
+ 	      [
+ 		ac_os_objs=ioqueue_epoll.o
+ 		AC_MSG_RESULT([/dev/epoll])
++		AC_DEFINE(PJ_HAS_LINUX_EPOLL,1)
++		ac_linux_poll=epoll
+ 	      ],
+ 	      [
+ 		ac_os_objs=ioqueue_select.o
+-	        AC_MSG_RESULT([select()]) 
++		AC_MSG_RESULT([select()])
++		ac_linux_poll=select
+ 	      ])
+ 
+ AC_SUBST(ac_shared_libraries)
+@@ -1879,7 +1883,6 @@ esac
+ 
+ 
+ AC_SUBST(target)
+-AC_SUBST(ac_linux_poll,select)
+ AC_SUBST(ac_host,unix)
+ AC_SUBST(ac_main_obj)
+ case $target in
+diff --git a/pjlib/include/pj/compat/os_auto.h.in b/pjlib/include/pj/compat/os_auto.h.in
+index 77980d3..c8e73b2 100644
+--- a/pjlib/include/pj/compat/os_auto.h.in
++++ b/pjlib/include/pj/compat/os_auto.h.in
+@@ -128,6 +128,9 @@
+  */
+ #undef PJ_SELECT_NEEDS_NFDS
+ 
++/* Was Linux epoll support enabled */
++#undef PJ_HAS_LINUX_EPOLL
++
+ /* Is errno a good way to retrieve OS errors?
+  */
+ #undef PJ_HAS_ERRNO_VAR
+-- 
+2.5.0
+
diff --git a/third-party/pjproject/patches/0001-sip_transport-Search-for-transport-even-if-listener-.patch b/third-party/pjproject/patches/0001-sip_transport-Search-for-transport-even-if-listener-.patch
new file mode 100644
index 0000000..001912c
--- /dev/null
+++ b/third-party/pjproject/patches/0001-sip_transport-Search-for-transport-even-if-listener-.patch
@@ -0,0 +1,114 @@
+From 552194179eb6deae8326eb0fef446e69240ea41b Mon Sep 17 00:00:00 2001
+From: George Joseph <george.joseph at fairview5.com>
+Date: Fri, 19 Feb 2016 17:05:53 -0700
+Subject: [PATCH] sip_transport:  Search for transport even if listener was
+ specified.
+
+If a listener was specified when calling pjsip_tpmgr_acquire_transport2,
+a new transport was always created instead of using an existing one.  This
+caused several issues mostly related to the remote end not expecting a new
+connection.  I.E.  A TCP client who registered to a server is not going to
+be listening for connections coming back from the server and refuses the
+connection.
+
+Now when pjsip_tpmgr_acquire_transport2 is called with a listener, the
+registry is still searched for an existing transport and the listener
+is used as a factory only if no existing transport can be found.
+---
+ pjsip/src/pjsip/sip_transport.c | 68 ++++++++++++++++++++---------------------
+ 1 file changed, 34 insertions(+), 34 deletions(-)
+
+diff --git a/pjsip/src/pjsip/sip_transport.c b/pjsip/src/pjsip/sip_transport.c
+index 0410324..620b9c0 100644
+--- a/pjsip/src/pjsip/sip_transport.c
++++ b/pjsip/src/pjsip/sip_transport.c
+@@ -1999,29 +1999,11 @@ PJ_DEF(pj_status_t) pjsip_tpmgr_acquire_transport2(pjsip_tpmgr *mgr,
+ 
+ 	TRACE_((THIS_FILE, "Transport %s acquired", seltp->obj_name));
+ 	return PJ_SUCCESS;
+-
+-
+-    } else if (sel && sel->type == PJSIP_TPSELECTOR_LISTENER &&
+-	       sel->u.listener)
+-    {
+-	/* Application has requested that a specific listener is to
+-	 * be used. In this case, skip transport hash table lookup.
+-	 */
+-
+-	/* Verify that the listener type matches the destination type */
+-	if (sel->u.listener->type != type) {
+-	    pj_lock_release(mgr->lock);
+-	    return PJSIP_ETPNOTSUITABLE;
+-	}
+-
+-	/* We'll use this listener to create transport */
+-	factory = sel->u.listener;
+-
+     } else {
+ 
+ 	/*
+ 	 * This is the "normal" flow, where application doesn't specify
+-	 * specific transport/listener to be used to send message to.
++	 * specific transport to be used to send message to.
+ 	 * In this case, lookup the transport from the hash table.
+ 	 */
+ 	pjsip_transport_key key;
+@@ -2081,22 +2063,40 @@ PJ_DEF(pj_status_t) pjsip_tpmgr_acquire_transport2(pjsip_tpmgr *mgr,
+ 	    return PJ_SUCCESS;
+ 	}
+ 
+-	/*
+-	 * Transport not found!
+-	 * Find factory that can create such transport.
+-	 */
+-	factory = mgr->factory_list.next;
+-	while (factory != &mgr->factory_list) {
+-	    if (factory->type == type)
+-		break;
+-	    factory = factory->next;
+-	}
++	if (sel && sel->type == PJSIP_TPSELECTOR_LISTENER &&
++		sel->u.listener)
++		{
++		/* Application has requested that a specific listener is to
++		 * be used.
++		 */
++
++		/* Verify that the listener type matches the destination type */
++		if (sel->u.listener->type != type) {
++			pj_lock_release(mgr->lock);
++			return PJSIP_ETPNOTSUITABLE;
++		}
+ 
+-	if (factory == &mgr->factory_list) {
+-	    /* No factory can create the transport! */
+-	    pj_lock_release(mgr->lock);
+-	    TRACE_((THIS_FILE, "No suitable factory was found either"));
+-	    return PJSIP_EUNSUPTRANSPORT;
++		/* We'll use this listener to create transport */
++		factory = sel->u.listener;
++
++	} else {
++		/*
++		 * Transport not found!
++		 * Find factory that can create such transport.
++		 */
++		factory = mgr->factory_list.next;
++		while (factory != &mgr->factory_list) {
++			if (factory->type == type)
++				break;
++			factory = factory->next;
++		}
++
++		if (factory == &mgr->factory_list) {
++			/* No factory can create the transport! */
++			pj_lock_release(mgr->lock);
++			TRACE_((THIS_FILE, "No suitable factory was found either"));
++			return PJSIP_EUNSUPTRANSPORT;
++		}
+ 	}
+     }
+ 
+-- 
+2.5.0
+
diff --git a/third-party/pjproject/patches/config_site.h b/third-party/pjproject/patches/config_site.h
new file mode 100644
index 0000000..544c1e8
--- /dev/null
+++ b/third-party/pjproject/patches/config_site.h
@@ -0,0 +1,34 @@
+/*
+ * Asterisk config_site.h
+ */
+
+#include <sys/select.h>
+
+#define PJ_HAS_IPV6 1
+#define NDEBUG 1
+#define PJ_MAX_HOSTNAME (256)
+#define PJSIP_MAX_URL_SIZE (512)
+#ifdef PJ_HAS_LINUX_EPOLL
+#define PJ_IOQUEUE_MAX_HANDLES	(5000)
+#else
+#define PJ_IOQUEUE_MAX_HANDLES	(FD_SETSIZE)
+#endif
+#define PJ_IOQUEUE_HAS_SAFE_UNREG 1
+#define PJ_IOQUEUE_MAX_EVENTS_IN_SINGLE_POLL (16)
+
+#define PJ_SCANNER_USE_BITWISE	0
+#define PJ_OS_HAS_CHECK_STACK	0
+#define PJ_LOG_MAX_LEVEL		3
+#define PJ_ENABLE_EXTRA_CHECK	0
+#define PJSIP_MAX_TSX_COUNT		((64*1024)-1)
+#define PJSIP_MAX_DIALOG_COUNT	((64*1024)-1)
+#define PJSIP_UDP_SO_SNDBUF_SIZE	(512*1024)
+#define PJSIP_UDP_SO_RCVBUF_SIZE	(512*1024)
+#define PJ_DEBUG			0
+#define PJSIP_SAFE_MODULE		0
+#define PJ_HAS_STRICMP_ALNUM		0
+#define PJ_HASH_USE_OWN_TOLOWER		1
+#define PJSIP_UNESCAPE_IN_PLACE		1
+
+#undef PJ_TODO
+#define PJ_TODO(x)
diff --git a/third-party/pjproject/patches/user.mak b/third-party/pjproject/patches/user.mak
new file mode 100644
index 0000000..31579d1
--- /dev/null
+++ b/third-party/pjproject/patches/user.mak
@@ -0,0 +1,2 @@
+
+CFLAGS += -fPIC -Wno-unused-but-set-variable -Wno-unused-variable -Wno-unused-label -Wno-unused-function -Wno-strict-aliasing
diff --git a/third-party/versions.mak b/third-party/versions.mak
new file mode 100644
index 0000000..7b8b59c
--- /dev/null
+++ b/third-party/versions.mak
@@ -0,0 +1,2 @@
+
+PJPROJECT_VERSION = 2.4.5
diff --git a/utils/.gitignore b/utils/.gitignore
deleted file mode 100644
index 8e95c8d..0000000
--- a/utils/.gitignore
+++ /dev/null
@@ -1,25 +0,0 @@
-aelbison.c
-aelparse
-aelparse.c
-ast_expr2.c
-ast_expr2f.c
-astman
-astcanary
-astdb2bdb
-astdb2sqlite3
-check_expr
-check_expr2
-check_expr2.dSYM/
-conf2ael
-db1-ast/libdb1.a
-hashtab.c
-lock.c
-md5.c
-muted
-pbx_ael.c
-pval.c
-smsq
-stereorize
-strcompat.c
-streamplayer
-threadstorage.c

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-voip/asterisk.git



More information about the Pkg-voip-commits mailing list