summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CHANGES81
-rw-r--r--Makefile3
-rw-r--r--README-SERIOUSLY.bestpractices.txt7
-rw-r--r--UPGRADE.txt6
-rw-r--r--addons/chan_ooh323.c8
-rw-r--r--addons/ooh323c/src/ooSocket.c2
-rw-r--r--apps/Makefile2
-rw-r--r--apps/app_chanspy.c2
-rw-r--r--apps/app_confbridge.c12
-rw-r--r--apps/app_directory.c7
-rw-r--r--apps/app_followme.c2
-rw-r--r--apps/app_minivm.c159
-rw-r--r--apps/app_mixmonitor.c15
-rw-r--r--apps/app_playback.c2
-rw-r--r--apps/app_privacy.c1
-rw-r--r--apps/app_queue.c53
-rw-r--r--apps/app_record.c113
-rw-r--r--apps/app_system.c10
-rw-r--r--apps/app_voicemail.c955
-rw-r--r--bridges/bridge_native_rtp.c697
-rw-r--r--bridges/bridge_simple.c32
-rw-r--r--bridges/bridge_softmix.c8
-rwxr-xr-xbuild_tools/download_externals23
-rwxr-xr-xbuild_tools/list_valid_installed_externals20
-rw-r--r--channels/Makefile2
-rw-r--r--channels/chan_iax2.c4
-rw-r--r--channels/chan_motif.c2
-rw-r--r--channels/chan_pjsip.c108
-rw-r--r--channels/chan_sip.c24
-rw-r--r--channels/chan_unistim.c4
-rw-r--r--channels/iax2/firmware.c13
-rw-r--r--channels/pjsip/dialplan_functions.c204
-rw-r--r--channels/pjsip/include/dialplan_functions.h25
-rw-r--r--channels/sip/dialplan_functions.c3
-rw-r--r--configs/basic-pbx/modules.conf1
-rw-r--r--configs/samples/cdr.conf.sample2
-rw-r--r--configs/samples/config_test.conf.sample8
-rw-r--r--configs/samples/minivm.conf.sample2
-rw-r--r--configs/samples/musiconhold.conf.sample23
-rw-r--r--configs/samples/pjsip.conf.sample16
-rw-r--r--configs/samples/voicemail.conf.sample3
-rw-r--r--configs/samples/xmpp.conf.sample23
-rwxr-xr-xconfigure126
-rw-r--r--configure.ac27
-rw-r--r--contrib/ast-db-manage/config/versions/164abbd708c_add_auto_info_to_endpoint_dtmf_mode.py58
-rw-r--r--contrib/ast-db-manage/config/versions/b83645976fdd_add_dtls_fingerprint_to_ps_endpoints.py38
-rw-r--r--contrib/ast-db-manage/config/versions/d7983954dd96_add_ps_endpoints_notify_early_inuse_.py30
-rw-r--r--contrib/ast-db-manage/config/versions/f3d1c5d38b56_add_prune_on_boot.py31
-rwxr-xr-xcontrib/scripts/install_prereq2
-rw-r--r--formats/format_g719.c17
-rw-r--r--formats/format_g723.c15
-rw-r--r--formats/format_g726.c15
-rw-r--r--formats/format_g729.c18
-rw-r--r--formats/format_gsm.c15
-rw-r--r--formats/format_h263.c15
-rw-r--r--formats/format_h264.c15
-rw-r--r--formats/format_ilbc.c16
-rw-r--r--formats/format_pcm.c20
-rw-r--r--formats/format_siren14.c17
-rw-r--r--formats/format_siren7.c17
-rw-r--r--formats/format_sln.c19
-rw-r--r--formats/format_vox.c17
-rw-r--r--formats/format_wav.c17
-rw-r--r--formats/format_wav_gsm.c17
-rw-r--r--funcs/func_cdr.c2
-rw-r--r--funcs/func_shell.c5
-rw-r--r--include/asterisk/app.h31
-rw-r--r--include/asterisk/ari.h10
-rw-r--r--include/asterisk/bridge_after.h2
-rw-r--r--include/asterisk/bridge_channel.h2
-rw-r--r--include/asterisk/bridge_channel_internal.h11
-rw-r--r--include/asterisk/bridge_technology.h3
-rw-r--r--include/asterisk/config.h11
-rw-r--r--include/asterisk/config_options.h25
-rw-r--r--include/asterisk/core_local.h37
-rw-r--r--include/asterisk/format.h4
-rw-r--r--include/asterisk/format_cache.h5
-rw-r--r--include/asterisk/manager.h2
-rw-r--r--include/asterisk/res_pjsip.h186
-rw-r--r--include/asterisk/res_pjsip_presence_xml.h3
-rw-r--r--include/asterisk/res_pjsip_session.h30
-rw-r--r--include/asterisk/rtp_engine.h11
-rw-r--r--main/Makefile3
-rw-r--r--main/acl.c4
-rw-r--r--main/app.c15
-rw-r--r--main/ast_expr2.c6
-rw-r--r--main/ast_expr2.y6
-rw-r--r--main/asterisk.c102
-rw-r--r--main/bridge.c16
-rw-r--r--main/bridge_after.c30
-rw-r--r--main/bridge_channel.c36
-rw-r--r--main/ccss.c2
-rw-r--r--main/channel.c22
-rw-r--r--main/cli.c4
-rw-r--r--main/codec_builtin.c8
-rw-r--r--main/config.c49
-rw-r--r--main/config_options.c36
-rw-r--r--main/core_local.c54
-rw-r--r--main/data.c4
-rw-r--r--main/format_cache.c8
-rw-r--r--main/http.c13
-rw-r--r--main/json.c1
-rw-r--r--main/libasteriskssl.c24
-rw-r--r--main/manager.c2
-rw-r--r--main/netsock2.c16
-rw-r--r--main/pbx_app.c7
-rw-r--r--main/rtp_engine.c13
-rw-r--r--main/say.c42
-rw-r--r--main/stdtime/localtime.c2
-rw-r--r--main/stun.c4
-rw-r--r--main/tcptls.c4
-rw-r--r--main/utils.c2
-rw-r--r--makeopts.in8
-rw-r--r--res/res_ari_applications.c4
-rw-r--r--res/res_ari_asterisk.c4
-rw-r--r--res/res_ari_bridges.c4
-rw-r--r--res/res_ari_channels.c4
-rw-r--r--res/res_ari_device_states.c4
-rw-r--r--res/res_ari_endpoints.c4
-rw-r--r--res/res_ari_events.c33
-rw-r--r--res/res_ari_mailboxes.c4
-rw-r--r--res/res_ari_playbacks.c4
-rw-r--r--res/res_ari_recordings.c4
-rw-r--r--res/res_ari_sounds.c4
-rw-r--r--res/res_calendar.c2
-rw-r--r--res/res_calendar_icalendar.c2
-rw-r--r--res/res_config_pgsql.c2
-rw-r--r--res/res_corosync.c29
-rw-r--r--res/res_http_post.c19
-rw-r--r--res/res_monitor.c54
-rw-r--r--res/res_musiconhold.c129
-rw-r--r--res/res_pjsip.c102
-rw-r--r--res/res_pjsip/config_transport.c46
-rw-r--r--res/res_pjsip/include/res_pjsip_private.h23
-rw-r--r--res/res_pjsip/location.c62
-rw-r--r--res/res_pjsip/pjsip_configuration.c71
-rw-r--r--res/res_pjsip/pjsip_distributor.c242
-rw-r--r--res/res_pjsip/pjsip_message_ip_updater.c56
-rw-r--r--res/res_pjsip/pjsip_transport_events.c366
-rw-r--r--res/res_pjsip/presence_xml.c25
-rw-r--r--res/res_pjsip_dialog_info_body_generator.c10
-rw-r--r--res/res_pjsip_messaging.c2
-rw-r--r--res/res_pjsip_mwi.c87
-rw-r--r--res/res_pjsip_nat.c12
-rw-r--r--res/res_pjsip_outbound_registration.c136
-rw-r--r--res/res_pjsip_pidf_body_generator.c4
-rw-r--r--res/res_pjsip_pidf_eyebeam_body_supplement.c34
-rw-r--r--res/res_pjsip_pubsub.c8
-rw-r--r--res/res_pjsip_refer.c8
-rw-r--r--res/res_pjsip_registrar.c109
-rw-r--r--res/res_pjsip_sdp_rtp.c50
-rw-r--r--res/res_pjsip_session.c81
-rw-r--r--res/res_pjsip_session.exports.in2
-rw-r--r--res/res_pjsip_t38.c42
-rw-r--r--res/res_pjsip_transport_management.c58
-rw-r--r--res/res_pjsip_transport_websocket.c21
-rw-r--r--res/res_pjsip_xpidf_body_generator.c2
-rw-r--r--res/res_rtp_asterisk.c228
-rw-r--r--res/res_smdi.c10
-rw-r--r--res/res_stasis.c20
-rw-r--r--res/res_stasis_device_state.c4
-rw-r--r--res/res_stasis_snoop.c22
-rw-r--r--res/res_xmpp.c99
-rw-r--r--res/srtp/srtp_compat.h4
-rw-r--r--res/stasis/control.c118
-rw-r--r--rest-api-templates/res_ari_resource.c.mustache35
-rw-r--r--sounds/Makefile7
-rw-r--r--sounds/sounds.xml27
-rw-r--r--tests/test_bridging.c292
-rw-r--r--tests/test_config.c88
-rw-r--r--tests/test_core_format.c5
-rw-r--r--tests/test_taskprocessor.c2
-rw-r--r--third-party/configure.m45
-rw-r--r--third-party/pjproject/Makefile12
-rw-r--r--third-party/pjproject/Makefile.rules7
-rw-r--r--third-party/pjproject/configure.m426
-rw-r--r--third-party/pjproject/patches/0070-Set-PJSIP_INV_SUPPORT_UPDATE-correctly-in-pjsip_inv_.patch29
-rw-r--r--third-party/pjproject/patches/0075-Fixed-2030-Improve-error-handling-in-OpenSSL-socket.patch247
-rw-r--r--third-party/pjproject/patches/config_site.h2
-rw-r--r--utils/astman.c2
-rw-r--r--utils/extconf.c4
181 files changed, 5775 insertions, 1661 deletions
diff --git a/CHANGES b/CHANGES
index a951b2ca3..a8623e064 100644
--- a/CHANGES
+++ b/CHANGES
@@ -9,9 +9,68 @@
==============================================================================
------------------------------------------------------------------------------
+--- Functionality changes from Asterisk 13.17.0 to Asterisk 13.18.0 ----------
+------------------------------------------------------------------------------
+
+Core
+------------------
+ * VP9 is now a supported passthrough video codec and it can be used by
+ specifying "vp9" in the allow line.
+
+Build System
+------------------
+ * A '--with-download-cache' option is now available which is equivalent to
+ setting '--with-sounds-cache' and '--with-externals-cache' to the same
+ value. The download cache can also be set via the AST_DOWNLOAD_CACHE
+ environment variable.
+
+res_pjsip
+------------------
+ * The "external_media_address" on transports is now resolved using dnsmgr and
+ when dnsmgr refreshes are enabled will be automatically updated with the new
+ IP address of a given hostname.
+
+res_musiconhold
+------------------
+ * By default, when res_musiconhold reloads or unloads, it sends a HUP signal
+ to custom applications (and all descendants), waits 100ms, then sends a
+ TERM signal, waits 100ms, then finally sends a KILL signal. An application
+ which is interacting with an external device and/or spawns children of its
+ own may not be able to exit cleanly in the default times, expecially if sent
+ a KILL signal, or if it's children are getting signals directly from
+ res_musiconhoild. To allow extra time, the 'kill_escalation_delay'
+ class option can be used to set the number of milliseconds res_musiconhold
+ waits before escalating kill signals, with the default being the current
+ 100ms. To control to whom the signals are sent, the "kill_method"
+ class option can be set to "process_group" (the default, existing behavior),
+ which sends signals to the application and its descendants directly, or
+ "process" which sends signals only to the application itself.
+
+res_pjsip
+------------------
+ * New dialplan function PJSIP_DTMF_MODE added to get or change the DTMF mode
+ of a channel on a per-call basis.
+
+res_xmpp
+-----------------
+ * OAuth 2.0 authentication is now supported when contacting Google. Follow the
+ instructions in xmpp.conf.sample to retrieve and configure the necessary
+ tokens.
+
+app_queue
+------------------
+ * Add priority to callers in AMI QueueStatus response.
+
+------------------------------------------------------------------------------
--- Functionality changes from Asterisk 13.16.0 to Asterisk 13.17.0 ----------
------------------------------------------------------------------------------
+app_voicemail
+------------------
+ * A new global option "imap_poll_logout" was added to specify whether need to
+ disconnect from the IMAP server after polling of mailboxes.
+ Default: no
+
res_pjsip
------------------
* A new endpoint option "refer_blind_progress" was added to turn off notifying
@@ -21,6 +80,14 @@ res_pjsip
Some SIP phones like Mitel/Aastra or Snom keep the line busy until
receive "200 OK".
+ * A new endpoint option "notify_early_inuse_ringing" was added to control
+ whether to notify dialog-info state 'early' or 'confirmed' on Ringing
+ when already INUSE.
+
+ * The endpoint option 'dtmf_mode' has a new option 'auto_dtmf' added. This
+ mode works similar to 'auto' except uses DTMF INFO as fallback instead of
+ INBAND.
+
res_agi
------------------
* The EAGI() application will now look for a dialplan variable named
@@ -34,6 +101,20 @@ chan_pjsip
function any contact which is considered unreachable due to qualify being
enabled will no longer be called.
+ * The asymmetric_rtp_codec option now also controls whether chan_pjsip will
+ send media as-is without transcoding if the codec has been negotiated in the
+ SDP. If set to "no" then Asterisk will only ever send the preferred codec
+ from the SDP, unless the remote side sends a different codec and we will
+ switch to match.
+
+Build System
+------------------
+ * Added a new PJPROJECT_CONFIGURE_OPTS environment variable which can be used
+ to pass arbitrary options to the bundled pjproject configure.
+
+ * Automatically set the bundled pjproject configure --host and --build
+ options to match those supplied for the asterisk configure.
+
------------------------------------------------------------------------------
--- Functionality changes from Asterisk 13.15.0 to Asterisk 13.16.0 ----------
------------------------------------------------------------------------------
diff --git a/Makefile b/Makefile
index 4f7ce71b1..bb8de1322 100644
--- a/Makefile
+++ b/Makefile
@@ -124,6 +124,9 @@ _ASTLDFLAGS+=$(LDOPTS)
# libxml2 cflags
_ASTCFLAGS+=$(LIBXML2_INCLUDE)
+# BIND_8_COMPAT
+_ASTCFLAGS+=$(BIND8_CFLAGS)
+
#Uncomment this to see all build commands instead of 'quiet' output
#NOISY_BUILD=yes
diff --git a/README-SERIOUSLY.bestpractices.txt b/README-SERIOUSLY.bestpractices.txt
index 108adce8f..b170d2969 100644
--- a/README-SERIOUSLY.bestpractices.txt
+++ b/README-SERIOUSLY.bestpractices.txt
@@ -94,6 +94,13 @@ your ITSP in a place where you didn't expect to allow it. There are a couple of
ways in which you can mitigate this impact: stricter pattern matching, or using
the FILTER() dialplan function.
+The CALLERID(num) and CALLERID(name) values are other commonly used values that
+are sources of data potentially supplied by outside sources. If you use these
+values as parameters to the System(), MixMonitor(), or Monitor() applications
+or the SHELL() dialplan function, you can allow injection of arbitrary operating
+system command execution. The FILTER() dialplan function is available to remove
+dangerous characters from untrusted strings to block the command injection.
+
Strict Pattern Matching
-----------------------
diff --git a/UPGRADE.txt b/UPGRADE.txt
index d023f4088..334fad321 100644
--- a/UPGRADE.txt
+++ b/UPGRADE.txt
@@ -21,6 +21,12 @@
=== UPGRADE-12.txt -- Upgrade info for 11 to 12
===========================================================
+From 13.17.0 to 13.18.0:
+
+Core:
+ - ast_app_parse_timelen now returns an error if it encounters extra characters
+ at the end of the string to be parsed.
+
From 13.15.0 to 13.16.0:
Core:
diff --git a/addons/chan_ooh323.c b/addons/chan_ooh323.c
index b64d43c66..44f339f2c 100644
--- a/addons/chan_ooh323.c
+++ b/addons/chan_ooh323.c
@@ -3181,7 +3181,7 @@ int reload_config(int reload)
static char *handle_cli_ooh323_show_peer(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
{
- char ip_port[30];
+ char ip_port[64];
struct ooh323_peer *prev = NULL, *peer = NULL;
switch (cmd) {
@@ -3212,7 +3212,7 @@ static char *handle_cli_ooh323_show_peer(struct ast_cli_entry *e, int cmd, struc
}
if (peer) {
- sprintf(ip_port, "%s:%d", peer->ip, peer->port);
+ sprintf(ip_port, "%s:%hu", peer->ip, peer->port);
ast_cli(a->fd, "%-15.15s%s\n", "Name: ", peer->name);
ast_cli(a->fd, "%s:%s,%s\n", "FastStart/H.245 Tunneling", peer->faststart?"yes":"no",
peer->h245tunneling?"yes":"no");
@@ -3280,7 +3280,7 @@ static char *handle_cli_ooh323_show_peers(struct ast_cli_entry *e, int cmd, stru
{
struct ooh323_peer *prev = NULL, *peer = NULL;
struct ast_str *codec_buf = ast_str_alloca(AST_FORMAT_CAP_NAMES_LEN);
- char ip_port[30];
+ char ip_port[64];
#define FORMAT "%-15.15s %-15.15s %-23.23s %-s\n"
switch (cmd) {
@@ -3303,7 +3303,7 @@ static char *handle_cli_ooh323_show_peers(struct ast_cli_entry *e, int cmd, stru
peer = peerl.peers;
while (peer) {
ast_mutex_lock(&peer->lock);
- snprintf(ip_port, sizeof(ip_port), "%s:%d", peer->ip, peer->port);
+ snprintf(ip_port, sizeof(ip_port), "%s:%hu", peer->ip, peer->port);
ast_cli(a->fd, FORMAT, peer->name,
peer->accountcode,
ip_port,
diff --git a/addons/ooh323c/src/ooSocket.c b/addons/ooh323c/src/ooSocket.c
index ee02f5206..cbef6bea8 100644
--- a/addons/ooh323c/src/ooSocket.c
+++ b/addons/ooh323c/src/ooSocket.c
@@ -386,7 +386,7 @@ int ooSocketAccept (OOSOCKET socket, OOSOCKET *pNewSocket,
if (*pNewSocket <= 0) return ASN_E_INVSOCKET;
if (destAddr != 0) {
- if ((host = ast_sockaddr_stringify_addr(&addr)) != NULL);
+ if ((host = ast_sockaddr_stringify_addr(&addr)) != NULL)
strncpy(destAddr, host, strlen(host));
}
if (destPort != 0)
diff --git a/apps/Makefile b/apps/Makefile
index 86e5caf5c..87e45bb99 100644
--- a/apps/Makefile
+++ b/apps/Makefile
@@ -30,6 +30,8 @@ include $(ASTTOPDIR)/Makefile.moddir_rules
clean::
rm -f confbridge/*.o confbridge/*.i confbridge/*.gcda confbridge/*.gcno
+app_voicemail.o: _ASTCFLAGS+=$(AST_NO_FORMAT_TRUNCATION)
+
app_confbridge.so: $(subst .c,.o,$(wildcard confbridge/*.c))
$(subst .c,.o,$(wildcard confbridge/*.c)): _ASTCFLAGS+=$(call MOD_ASTCFLAGS,app_confbridge)
diff --git a/apps/app_chanspy.c b/apps/app_chanspy.c
index 2a472bf44..aefe94054 100644
--- a/apps/app_chanspy.c
+++ b/apps/app_chanspy.c
@@ -1425,7 +1425,7 @@ static int extenspy_exec(struct ast_channel *chan, const char *data)
static int dahdiscan_exec(struct ast_channel *chan, const char *data)
{
const char *spec = "DAHDI";
- struct ast_flags flags;
+ struct ast_flags flags = {0};
struct spy_dtmf_options user_options = {
.cycle = '#',
.volume = '\0',
diff --git a/apps/app_confbridge.c b/apps/app_confbridge.c
index c06466018..3ea336a84 100644
--- a/apps/app_confbridge.c
+++ b/apps/app_confbridge.c
@@ -2137,6 +2137,7 @@ static int conf_rec_name(struct confbridge_user *user, const char *conf_name)
}
if (res == -1) {
+ ast_filedelete(user->name_rec_location, NULL);
user->name_rec_location[0] = '\0';
return -1;
}
@@ -2228,6 +2229,7 @@ static int confbridge_exec(struct ast_channel *chan, const char *data)
{
int res = 0, volume_adjustments[2];
int quiet = 0;
+ int async_delete_task_pushed = 0;
char *parse;
const char *b_profile_name = NULL;
const char *u_profile_name = NULL;
@@ -2315,7 +2317,11 @@ static int confbridge_exec(struct ast_channel *chan, const char *data)
if (!quiet &&
(ast_test_flag(&user.u_profile, USER_OPT_ANNOUNCE_JOIN_LEAVE) ||
(ast_test_flag(&user.u_profile, USER_OPT_ANNOUNCE_JOIN_LEAVE_REVIEW)))) {
- conf_rec_name(&user, args.conf_name);
+ if (conf_rec_name(&user, args.conf_name)) {
+ pbx_builtin_setvar_helper(chan, "CONFBRIDGE_RESULT", "FAILED");
+ res = -1; /* Hangup during name recording */
+ goto confbridge_cleanup;
+ }
}
/* menu name */
@@ -2473,6 +2479,7 @@ static int confbridge_exec(struct ast_channel *chan, const char *data)
async_play_sound_file(conference,
conf_get_sound(CONF_SOUND_HAS_LEFT, conference->b_profile.sounds), NULL);
async_delete_name_rec(conference, user.name_rec_location);
+ async_delete_task_pushed = 1;
}
/* play the leave sound */
@@ -2501,6 +2508,9 @@ static int confbridge_exec(struct ast_channel *chan, const char *data)
}
confbridge_cleanup:
+ if (!async_delete_task_pushed && !ast_strlen_zero(user.name_rec_location)) {
+ ast_filedelete(user.name_rec_location, NULL);
+ }
ast_bridge_features_cleanup(&user.features);
conf_bridge_profile_destroy(&user.b_profile);
return res;
diff --git a/apps/app_directory.c b/apps/app_directory.c
index b75e85763..9cfcbf862 100644
--- a/apps/app_directory.c
+++ b/apps/app_directory.c
@@ -511,6 +511,11 @@ static struct ast_config *realtime_directory(char *context)
const char *mailbox = ast_variable_retrieve(rtdata, category, "mailbox");
const char *ctx = ast_variable_retrieve(rtdata, category, "context");
+ if (ast_strlen_zero(mailbox)) {
+ ast_debug(3, "Skipping result with missing or empty mailbox\n");
+ continue;
+ }
+
fullname = ast_variable_retrieve(rtdata, category, "fullname");
hidefromdir = ast_variable_retrieve(rtdata, category, "hidefromdir");
if (ast_true(hidefromdir)) {
@@ -531,7 +536,7 @@ static struct ast_config *realtime_directory(char *context)
/* Does the context exist within the config file? If not, make one */
if (!(cat = ast_category_get(cfg, ctx, NULL))) {
- if (!(cat = ast_category_new(ctx, "", 99999))) {
+ if (!(cat = ast_category_new_dynamic(ctx))) {
ast_log(LOG_WARNING, "Out of memory\n");
ast_config_destroy(cfg);
if (rtdata) {
diff --git a/apps/app_followme.c b/apps/app_followme.c
index 602806b39..5f9e220b5 100644
--- a/apps/app_followme.c
+++ b/apps/app_followme.c
@@ -1527,7 +1527,7 @@ outrun:
}
if (!ast_strlen_zero(targs->namerecloc)) {
int ret;
- char fn[PATH_MAX];
+ char fn[PATH_MAX + sizeof(REC_FORMAT)];
snprintf(fn, sizeof(fn), "%s.%s", targs->namerecloc,
REC_FORMAT);
diff --git a/apps/app_minivm.c b/apps/app_minivm.c
index 1bfcfbbf4..6b1e8bb83 100644
--- a/apps/app_minivm.c
+++ b/apps/app_minivm.c
@@ -1234,6 +1234,8 @@ static const char *ast_str_quote(struct ast_str **buf, ssize_t maxlen, const cha
* \brief Send voicemail with audio file as an attachment */
static int sendmail(struct minivm_template *template, struct minivm_account *vmu, char *cidnum, char *cidname, const char *filename, char *format, int duration, int attach_user_voicemail, enum mvm_messagetype type, const char *counter)
{
+ RAII_VAR(struct ast_str *, str1, ast_str_create(16), ast_free);
+ RAII_VAR(struct ast_str *, str2, ast_str_create(16), ast_free);
FILE *p = NULL;
int pfd;
char email[256] = "";
@@ -1243,20 +1245,18 @@ static int sendmail(struct minivm_template *template, struct minivm_account *vmu
char fname[PATH_MAX];
char dur[PATH_MAX];
char tmp[80] = "/tmp/astmail-XXXXXX";
- char tmp2[PATH_MAX];
- char newtmp[PATH_MAX]; /* Only used with volgain */
+ char mail_cmd_buffer[PATH_MAX];
+ char sox_gain_tmpdir[PATH_MAX] = ""; /* Only used with volgain */
+ char *file_to_delete = NULL, *dir_to_delete = NULL;
struct timeval now;
struct ast_tm tm;
struct minivm_zone *the_zone = NULL;
- struct ast_channel *ast;
- char *finalfilename = "";
- struct ast_str *str1 = ast_str_create(16), *str2 = ast_str_create(16);
+ struct ast_channel *chan = NULL;
char *fromaddress;
char *fromemail;
+ int res;
if (!str1 || !str2) {
- ast_free(str1);
- ast_free(str2);
return -1;
}
@@ -1271,9 +1271,7 @@ static int sendmail(struct minivm_template *template, struct minivm_account *vmu
if (ast_strlen_zero(email)) {
ast_log(LOG_WARNING, "No address to send message to.\n");
- ast_free(str1);
- ast_free(str2);
- return -1;
+ return -1;
}
ast_debug(3, "Sending mail to %s@%s - Using template %s\n", vmu->username, vmu->domain, template->name);
@@ -1281,35 +1279,30 @@ static int sendmail(struct minivm_template *template, struct minivm_account *vmu
if (!strcmp(format, "wav49"))
format = "WAV";
-
/* If we have a gain option, process it now with sox */
if (type == MVM_MESSAGE_EMAIL && (vmu->volgain < -.001 || vmu->volgain > .001) ) {
- char tmpcmd[PATH_MAX];
- int tmpfd;
-
- ast_copy_string(newtmp, "/tmp/XXXXXX", sizeof(newtmp));
- ast_debug(3, "newtmp: %s\n", newtmp);
- tmpfd = mkstemp(newtmp);
- if (tmpfd < 0) {
- ast_log(LOG_WARNING, "Failed to create temporary file for volgain: %d\n", errno);
- ast_free(str1);
- ast_free(str2);
+ char sox_gain_cmd[PATH_MAX];
+
+ ast_copy_string(sox_gain_tmpdir, "/tmp/minivm-gain-XXXXXX", sizeof(sox_gain_tmpdir));
+ ast_debug(3, "sox_gain_tmpdir: %s\n", sox_gain_tmpdir);
+ if (!mkdtemp(sox_gain_tmpdir)) {
+ ast_log(LOG_WARNING, "Failed to create temporary directory for volgain: %d\n", errno);
return -1;
}
- snprintf(tmpcmd, sizeof(tmpcmd), "sox -v %.4f %s.%s %s.%s", vmu->volgain, filename, format, newtmp, format);
- ast_safe_system(tmpcmd);
- close(tmpfd);
- finalfilename = newtmp;
+ snprintf(fname, sizeof(fname), "%s/output.%s", sox_gain_tmpdir, format);
+ snprintf(sox_gain_cmd, sizeof(sox_gain_cmd), "sox -v %.4f %s.%s %s", vmu->volgain, filename, format, fname);
+ ast_safe_system(sox_gain_cmd);
ast_debug(3, "VOLGAIN: Stored at: %s.%s - Level: %.4f - Mailbox: %s\n", filename, format, vmu->volgain, vmu->username);
+
+ /* Mark some things for deletion */
+ file_to_delete = fname;
+ dir_to_delete = sox_gain_tmpdir;
} else {
- finalfilename = ast_strdupa(filename);
+ snprintf(fname, sizeof(fname), "%s.%s", filename, format);
}
- /* Create file name */
- snprintf(fname, sizeof(fname), "%s.%s", finalfilename, format);
-
if (template->attachment)
- ast_debug(1, "Attaching file '%s', format '%s', uservm is '%d'\n", finalfilename, format, attach_user_voicemail);
+ ast_debug(1, "Attaching file '%s', format '%s', uservm is '%d'\n", fname, format, attach_user_voicemail);
/* Make a temporary file instead of piping directly to sendmail, in case the mail
command hangs */
@@ -1324,16 +1317,12 @@ static int sendmail(struct minivm_template *template, struct minivm_account *vmu
}
if (!p) {
ast_log(LOG_WARNING, "Unable to open temporary file '%s'\n", tmp);
- ast_free(str1);
- ast_free(str2);
- return -1;
+ goto out;
}
/* Allocate channel used for chanvar substitution */
- ast = ast_dummy_channel_alloc();
- if (!ast) {
- ast_free(str1);
- ast_free(str2);
- return -1;
+ chan = ast_dummy_channel_alloc();
+ if (!chan) {
+ goto out;
}
snprintf(dur, sizeof(dur), "%d:%02d", duration / 60, duration % 60);
@@ -1361,9 +1350,8 @@ static int sendmail(struct minivm_template *template, struct minivm_account *vmu
/* Set date format for voicemail mail */
ast_strftime(date, sizeof(date), template->dateformat, &tm);
-
/* Populate channel with channel variables for substitution */
- prep_email_sub_vars(ast, vmu, cidnum, cidname, dur, date, counter);
+ prep_email_sub_vars(chan, vmu, cidnum, cidname, dur, date, counter);
/* Find email address to use */
/* If there's a server e-mail address in the account, use that, othterwise template */
@@ -1388,7 +1376,7 @@ static int sendmail(struct minivm_template *template, struct minivm_account *vmu
fprintf(p, "From: Asterisk PBX <%s>\n", who);
} else {
ast_debug(4, "Fromaddress template: %s\n", fromaddress);
- ast_str_substitute_variables(&str1, 0, ast, fromaddress);
+ ast_str_substitute_variables(&str1, 0, chan, fromaddress);
if (check_mime(ast_str_buffer(str1))) {
int first_line = 1;
char *ptr;
@@ -1431,7 +1419,7 @@ static int sendmail(struct minivm_template *template, struct minivm_account *vmu
}
if (!ast_strlen_zero(template->subject)) {
- ast_str_substitute_variables(&str1, 0, ast, template->subject);
+ ast_str_substitute_variables(&str1, 0, chan, template->subject);
if (check_mime(ast_str_buffer(str1))) {
int first_line = 1;
char *ptr;
@@ -1464,7 +1452,7 @@ static int sendmail(struct minivm_template *template, struct minivm_account *vmu
fprintf(p, "--%s\n", bound);
fprintf(p, "Content-Type: text/plain; charset=%s\nContent-Transfer-Encoding: 8bit\n\n", template->charset);
if (!ast_strlen_zero(template->body)) {
- ast_str_substitute_variables(&str1, 0, ast, template->body);
+ ast_str_substitute_variables(&str1, 0, chan, template->body);
ast_debug(3, "Message now: %s\n-----\n", ast_str_buffer(str1));
fprintf(p, "%s\n", ast_str_buffer(str1));
} else {
@@ -1491,14 +1479,45 @@ static int sendmail(struct minivm_template *template, struct minivm_account *vmu
fprintf(p, "\n\n--%s--\n.\n", bound);
}
fclose(p);
- snprintf(tmp2, sizeof(tmp2), "( %s < %s ; rm -f %s ) &", global_mailcmd, tmp, tmp);
- ast_safe_system(tmp2);
- ast_debug(1, "Sent message to %s with command '%s' - %s\n", vmu->email, global_mailcmd, template->attachment ? "(media attachment)" : "");
- ast_debug(3, "Actual command used: %s\n", tmp2);
- ast = ast_channel_unref(ast);
- ast_free(str1);
- ast_free(str2);
- return 0;
+
+ chan = ast_channel_unref(chan);
+
+ if (file_to_delete && dir_to_delete) {
+ /* We can't delete these files ourselves because the mail command will execute in
+ the background and we'll end up deleting them out from under it. */
+ res = snprintf(mail_cmd_buffer, sizeof(mail_cmd_buffer),
+ "( %s < %s ; rm -f %s %s ; rmdir %s ) &",
+ global_mailcmd, tmp, tmp, file_to_delete, dir_to_delete);
+ } else {
+ res = snprintf(mail_cmd_buffer, sizeof(mail_cmd_buffer),
+ "( %s < %s ; rm -f %s ) &",
+ global_mailcmd, tmp, tmp);
+ }
+
+ if (res < sizeof(mail_cmd_buffer)) {
+ file_to_delete = dir_to_delete = NULL;
+ } else {
+ ast_log(LOG_ERROR, "Could not send message, command line too long\n");
+ res = -1;
+ goto out;
+ }
+
+ ast_safe_system(mail_cmd_buffer);
+ ast_debug(1, "Sent message to %s with command '%s'%s\n", vmu->email, global_mailcmd, template->attachment ? " - (media attachment)" : "");
+ ast_debug(3, "Actual command used: %s\n", mail_cmd_buffer);
+
+ res = 0;
+
+out:
+ if (file_to_delete) {
+ unlink(file_to_delete);
+ }
+
+ if (dir_to_delete) {
+ rmdir(dir_to_delete);
+ }
+
+ return res;
}
/*!\internal
@@ -1757,21 +1776,35 @@ static int play_record_review(struct ast_channel *chan, char *playfile, char *re
/*! \brief Run external notification for voicemail message */
static void run_externnotify(struct ast_channel *chan, struct minivm_account *vmu)
{
- char arguments[BUFSIZ];
+ char fquser[AST_MAX_CONTEXT * 2];
+ char *argv[5] = { NULL };
+ struct ast_party_caller *caller;
+ char *cid;
+ int idx;
- if (ast_strlen_zero(vmu->externnotify) && ast_strlen_zero(global_externnotify))
+ if (ast_strlen_zero(vmu->externnotify) && ast_strlen_zero(global_externnotify)) {
return;
+ }
- snprintf(arguments, sizeof(arguments), "%s %s@%s %s %s&",
- ast_strlen_zero(vmu->externnotify) ? global_externnotify : vmu->externnotify,
- vmu->username, vmu->domain,
- (ast_channel_caller(chan)->id.name.valid && ast_channel_caller(chan)->id.name.str)
- ? ast_channel_caller(chan)->id.name.str : "",
- (ast_channel_caller(chan)->id.number.valid && ast_channel_caller(chan)->id.number.str)
- ? ast_channel_caller(chan)->id.number.str : "");
+ snprintf(fquser, sizeof(fquser), "%s@%s", vmu->username, vmu->domain);
- ast_debug(1, "Executing: %s\n", arguments);
- ast_safe_system(arguments);
+ caller = ast_channel_caller(chan);
+ idx = 0;
+ argv[idx++] = ast_strlen_zero(vmu->externnotify) ? global_externnotify : vmu->externnotify;
+ argv[idx++] = fquser;
+ cid = S_COR(caller->id.name.valid, caller->id.name.str, NULL);
+ if (cid) {
+ argv[idx++] = cid;
+ }
+ cid = S_COR(caller->id.number.valid, caller->id.number.str, NULL);
+ if (cid) {
+ argv[idx++] = cid;
+ }
+ argv[idx] = NULL;
+
+ ast_debug(1, "Executing: %s %s %s %s\n",
+ argv[0], argv[1], argv[2] ?: "", argv[3] ?: "");
+ ast_safe_execvp(1, argv[0], argv);
}
/*!\internal
@@ -2253,7 +2286,7 @@ static int minivm_greet_exec(struct ast_channel *chan, const char *data)
char ecodes[16] = "#";
char *tmpptr;
struct minivm_account *vmu;
- char *username = argv[0];
+ char *username;
if (ast_strlen_zero(data)) {
ast_log(LOG_ERROR, "Minivm needs at least an account argument \n");
diff --git a/apps/app_mixmonitor.c b/apps/app_mixmonitor.c
index 979bf2d70..24ce3b6ef 100644
--- a/apps/app_mixmonitor.c
+++ b/apps/app_mixmonitor.c
@@ -138,6 +138,11 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
<para>Will be executed when the recording is over.</para>
<para>Any strings matching <literal>^{X}</literal> will be unescaped to <variable>X</variable>.</para>
<para>All variables will be evaluated at the time MixMonitor is called.</para>
+ <warning><para>Do not use untrusted strings such as <variable>CALLERID(num)</variable>
+ or <variable>CALLERID(name)</variable> as part of the command parameters. You
+ risk a command injection attack executing arbitrary commands if the untrusted
+ strings aren't filtered to remove dangerous characters. See function
+ <variable>FILTER()</variable>.</para></warning>
</parameter>
</syntax>
<description>
@@ -150,6 +155,11 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
<para>Will contain the filename used to record.</para>
</variable>
</variablelist>
+ <warning><para>Do not use untrusted strings such as <variable>CALLERID(num)</variable>
+ or <variable>CALLERID(name)</variable> as part of ANY of the application's
+ parameters. You risk a command injection attack executing arbitrary commands
+ if the untrusted strings aren't filtered to remove dangerous characters. See
+ function <variable>FILTER()</variable>.</para></warning>
</description>
<see-also>
<ref type="application">Monitor</ref>
@@ -224,6 +234,11 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
<para>Will be executed when the recording is over.
Any strings matching <literal>^{X}</literal> will be unescaped to <variable>X</variable>.
All variables will be evaluated at the time MixMonitor is called.</para>
+ <warning><para>Do not use untrusted strings such as <variable>CALLERID(num)</variable>
+ or <variable>CALLERID(name)</variable> as part of the command parameters. You
+ risk a command injection attack executing arbitrary commands if the untrusted
+ strings aren't filtered to remove dangerous characters. See function
+ <variable>FILTER()</variable>.</para></warning>
</parameter>
</syntax>
<description>
diff --git a/apps/app_playback.c b/apps/app_playback.c
index e5df79445..422dd8eff 100644
--- a/apps/app_playback.c
+++ b/apps/app_playback.c
@@ -324,7 +324,7 @@ static int say_date_generic(struct ast_channel *chan, time_t t,
if (format == NULL)
format = "";
- ast_localtime(&when, &tm, NULL);
+ ast_localtime(&when, &tm, timezonename);
snprintf(buf, sizeof(buf), "%s:%s:%04d%02d%02d%02d%02d.%02d-%d-%3d",
prefix,
format,
diff --git a/apps/app_privacy.c b/apps/app_privacy.c
index 0e04df60b..a77bcf7f2 100644
--- a/apps/app_privacy.c
+++ b/apps/app_privacy.c
@@ -40,7 +40,6 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
#include "asterisk/pbx.h"
#include "asterisk/module.h"
#include "asterisk/translate.h"
-#include "asterisk/image.h"
#include "asterisk/callerid.h"
#include "asterisk/app.h"
#include "asterisk/config.h"
diff --git a/apps/app_queue.c b/apps/app_queue.c
index b449263c9..3fed5dcb6 100644
--- a/apps/app_queue.c
+++ b/apps/app_queue.c
@@ -3975,8 +3975,12 @@ static void recalc_holdtime(struct queue_ent *qe, int newholdtime)
/* 2^2 (4) is the filter coefficient; a higher exponent would give old entries more weight */
ao2_lock(qe->parent);
- oldvalue = qe->parent->holdtime;
- qe->parent->holdtime = (((oldvalue << 2) - oldvalue) + newholdtime) >> 2;
+ if ((qe->parent->callscompleted + qe->parent->callsabandoned) == 0) {
+ qe->parent->holdtime = newholdtime;
+ } else {
+ oldvalue = qe->parent->holdtime;
+ qe->parent->holdtime = (((oldvalue << 2) - oldvalue) + newholdtime) >> 2;
+ }
ao2_unlock(qe->parent);
}
@@ -5527,7 +5531,7 @@ static int update_queue(struct call_queue *q, struct member *member, int callcom
if (callcompletedinsl) {
q->callscompletedinsl++;
}
- if (q->callscompletedinsl == 1) {
+ if (q->callscompleted == 1) {
q->talktime = newtalktime;
} else {
/* Calculate talktime using the same exponential average as holdtime code */
@@ -6481,7 +6485,6 @@ static int try_calling(struct queue_ent *qe, struct ast_flags opts, char **opt_a
char oldexten[AST_MAX_EXTENSION]="";
char oldcontext[AST_MAX_CONTEXT]="";
char queuename[256]="";
- char interfacevar[256]="";
struct ast_channel *peer;
struct ast_channel *which;
struct callattempt *lpeer;
@@ -6682,6 +6685,7 @@ static int try_calling(struct queue_ent *qe, struct ast_flags opts, char **opt_a
}
} else { /* peer is valid */
RAII_VAR(struct ast_json *, blob, NULL, ast_json_unref);
+ RAII_VAR(struct ast_str *, interfacevar, ast_str_create(325), ast_free);
/* Ah ha! Someone answered within the desired timeframe. Of course after this
we will always return with -1 so that it is hung up properly after the
conversation. */
@@ -6795,20 +6799,20 @@ static int try_calling(struct queue_ent *qe, struct ast_flags opts, char **opt_a
ao2_lock(qe->parent);
/* if setinterfacevar is defined, make member variables available to the channel */
/* use pbx_builtin_setvar to set a load of variables with one call */
- if (qe->parent->setinterfacevar) {
- snprintf(interfacevar, sizeof(interfacevar), "MEMBERINTERFACE=%s,MEMBERNAME=%s,MEMBERCALLS=%d,MEMBERLASTCALL=%ld,MEMBERPENALTY=%d,MEMBERDYNAMIC=%d,MEMBERREALTIME=%d",
+ if (qe->parent->setinterfacevar && interfacevar) {
+ ast_str_set(&interfacevar, 0, "MEMBERINTERFACE=%s,MEMBERNAME=%s,MEMBERCALLS=%d,MEMBERLASTCALL=%ld,MEMBERPENALTY=%d,MEMBERDYNAMIC=%d,MEMBERREALTIME=%d",
member->interface, member->membername, member->calls, (long)member->lastcall, member->penalty, member->dynamic, member->realtime);
- pbx_builtin_setvar_multiple(qe->chan, interfacevar);
- pbx_builtin_setvar_multiple(peer, interfacevar);
+ pbx_builtin_setvar_multiple(qe->chan, ast_str_buffer(interfacevar));
+ pbx_builtin_setvar_multiple(peer, ast_str_buffer(interfacevar));
}
/* if setqueueentryvar is defined, make queue entry (i.e. the caller) variables available to the channel */
/* use pbx_builtin_setvar to set a load of variables with one call */
- if (qe->parent->setqueueentryvar) {
- snprintf(interfacevar, sizeof(interfacevar), "QEHOLDTIME=%ld,QEORIGINALPOS=%d",
+ if (qe->parent->setqueueentryvar && interfacevar) {
+ ast_str_set(&interfacevar, 0, "QEHOLDTIME=%ld,QEORIGINALPOS=%d",
(long) (time(NULL) - qe->start), qe->opos);
- pbx_builtin_setvar_multiple(qe->chan, interfacevar);
- pbx_builtin_setvar_multiple(peer, interfacevar);
+ pbx_builtin_setvar_multiple(qe->chan, ast_str_buffer(interfacevar));
+ pbx_builtin_setvar_multiple(peer, ast_str_buffer(interfacevar));
}
ao2_unlock(qe->parent);
@@ -7456,12 +7460,10 @@ static int set_member_value(const char *queuename, const char *interface, int pr
static int get_member_penalty(char *queuename, char *interface)
{
int foundqueue = 0, penalty;
- struct call_queue *q, tmpq = {
- .name = queuename,
- };
+ struct call_queue *q;
struct member *mem;
- if ((q = ao2_t_find(queues, &tmpq, OBJ_POINTER, "Search for queue"))) {
+ if ((q = find_load_queue_rt_friendly(queuename))) {
foundqueue = 1;
ao2_lock(q);
if ((mem = interface_exists(q, interface))) {
@@ -8166,6 +8168,9 @@ stop:
} else if (qcontinue) {
reason = QUEUE_CONTINUE;
res = 0;
+ } else if (reason == QUEUE_LEAVEEMPTY) {
+ /* Return back to dialplan, don't hang up */
+ res = 0;
}
} else if (qe.valid_digits) {
ast_queue_log(args.queuename, ast_channel_uniqueid(chan), "NONE", "EXITWITHKEY",
@@ -8209,10 +8214,7 @@ stop:
static int queue_function_var(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len)
{
int res = -1;
- struct call_queue *q, tmpq = {
- .name = data,
- };
-
+ struct call_queue *q;
char interfacevar[256] = "";
float sl = 0;
@@ -8221,7 +8223,7 @@ static int queue_function_var(struct ast_channel *chan, const char *cmd, char *d
return -1;
}
- if ((q = ao2_t_find(queues, &tmpq, OBJ_POINTER, "Find for QUEUE() function"))) {
+ if ((q = find_load_queue_rt_friendly(data))) {
ao2_lock(q);
if (q->setqueuevar) {
sl = 0;
@@ -8540,9 +8542,7 @@ static int queue_function_queuewaitingcount(struct ast_channel *chan, const char
/*! \brief Dialplan function QUEUE_MEMBER_LIST() Get list of members in a specific queue */
static int queue_function_queuememberlist(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len)
{
- struct call_queue *q, tmpq = {
- .name = data,
- };
+ struct call_queue *q;
struct member *m;
/* Ensure an otherwise empty list doesn't return garbage */
@@ -8553,7 +8553,7 @@ static int queue_function_queuememberlist(struct ast_channel *chan, const char *
return -1;
}
- if ((q = ao2_t_find(queues, &tmpq, OBJ_POINTER, "Find for QUEUE_MEMBER_LIST()"))) {
+ if ((q = find_load_queue_rt_friendly(data))) {
int buflen = 0, count = 0;
struct ao2_iterator mem_iter;
@@ -9692,6 +9692,7 @@ static int manager_queues_status(struct mansession *s, const struct message *m)
"ConnectedLineNum: %s\r\n"
"ConnectedLineName: %s\r\n"
"Wait: %ld\r\n"
+ "Priority: %d\r\n"
"%s"
"\r\n",
q->name, pos++, ast_channel_name(qe->chan), ast_channel_uniqueid(qe->chan),
@@ -9699,7 +9700,7 @@ static int manager_queues_status(struct mansession *s, const struct message *m)
S_COR(ast_channel_caller(qe->chan)->id.name.valid, ast_channel_caller(qe->chan)->id.name.str, "unknown"),
S_COR(ast_channel_connected(qe->chan)->id.number.valid, ast_channel_connected(qe->chan)->id.number.str, "unknown"),
S_COR(ast_channel_connected(qe->chan)->id.name.valid, ast_channel_connected(qe->chan)->id.name.str, "unknown"),
- (long) (now - qe->start), idText);
+ (long) (now - qe->start), qe->prio, idText);
++q_items;
}
}
diff --git a/apps/app_record.c b/apps/app_record.c
index 56dc5f47f..104daa51e 100644
--- a/apps/app_record.c
+++ b/apps/app_record.c
@@ -40,6 +40,7 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
#include "asterisk/channel.h"
#include "asterisk/dsp.h" /* use dsp routines for silence detection */
#include "asterisk/format_cache.h"
+#include "asterisk/paths.h"
/*** DOCUMENTATION
<application name="Record" language="en_US">
@@ -104,7 +105,7 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
If the user hangs up during a recording, all data will be lost and the application will terminate.</para>
<variablelist>
<variable name="RECORDED_FILE">
- <para>Will be set to the final filename of the recording.</para>
+ <para>Will be set to the final filename of the recording, without an extension.</para>
</variable>
<variable name="RECORD_STATUS">
<para>This is the final status of the command</para>
@@ -133,10 +134,9 @@ enum {
OPTION_STAR_TERMINATE = (1 << 4),
OPTION_IGNORE_TERMINATE = (1 << 5),
OPTION_KEEP = (1 << 6),
- FLAG_HAS_PERCENT = (1 << 7),
- OPTION_ANY_TERMINATE = (1 << 8),
- OPTION_OPERATOR_EXIT = (1 << 9),
- OPTION_NO_TRUNCATE = (1 << 10),
+ OPTION_ANY_TERMINATE = (1 << 7),
+ OPTION_OPERATOR_EXIT = (1 << 8),
+ OPTION_NO_TRUNCATE = (1 << 9),
};
AST_APP_OPTIONS(app_opts,{
@@ -182,14 +182,47 @@ static int record_dtmf_response(struct ast_channel *chan, struct ast_flags *flag
return 0;
}
+static int create_destination_directory(const char *path)
+{
+ int res;
+ char directory[PATH_MAX], *file_sep;
+
+ if (!(file_sep = strrchr(path, '/'))) {
+ /* No directory to create */
+ return 0;
+ }
+
+ /* Overwrite temporarily */
+ *file_sep = '\0';
+
+ /* Absolute path? */
+ if (path[0] == '/') {
+ res = ast_mkdir(path, 0777);
+ *file_sep = '/';
+ return res;
+ }
+
+ /* Relative path */
+ res = snprintf(directory, sizeof(directory), "%s/sounds/%s",
+ ast_config_AST_DATA_DIR, path);
+
+ *file_sep = '/';
+
+ if (res >= sizeof(directory)) {
+ /* We truncated, so we fail */
+ return -1;
+ }
+
+ return ast_mkdir(directory, 0777);
+}
+
static int record_exec(struct ast_channel *chan, const char *data)
{
int res = 0;
- int count = 0;
char *ext = NULL, *opts[0];
- char *parse, *dir, *file;
+ char *parse;
int i = 0;
- char tmp[256];
+ char tmp[PATH_MAX];
struct ast_filestream *s = NULL;
struct ast_frame *f = NULL;
@@ -229,8 +262,6 @@ static int record_exec(struct ast_channel *chan, const char *data)
ast_app_parse_options(app_opts, &flags, opts, args.options);
if (!ast_strlen_zero(args.filename)) {
- if (strstr(args.filename, "%d"))
- ast_set_flag(&flags, FLAG_HAS_PERCENT);
ext = strrchr(args.filename, '.'); /* to support filename with a . in the filename, not format */
if (!ext)
ext = strchr(args.filename, ':');
@@ -268,38 +299,31 @@ static int record_exec(struct ast_channel *chan, const char *data)
if (ast_test_flag(&flags, OPTION_IGNORE_TERMINATE))
terminator = '\0';
- /* done parsing */
-
- /* these are to allow the use of the %d in the config file for a wild card of sort to
- create a new file with the inputed name scheme */
- if (ast_test_flag(&flags, FLAG_HAS_PERCENT)) {
- AST_DECLARE_APP_ARGS(fname,
- AST_APP_ARG(piece)[100];
- );
- char *tmp2 = ast_strdupa(args.filename);
- char countstring[15];
- int idx;
+ /*
+ If a '%d' is specified as part of the filename, we replace that token with
+ sequentially incrementing numbers until we find a unique filename.
+ */
+ if (strchr(args.filename, '%')) {
+ size_t src, dst, count = 0;
+ size_t src_len = strlen(args.filename);
+ size_t dst_len = sizeof(tmp) - 1;
- /* Separate each piece out by the format specifier */
- AST_NONSTANDARD_APP_ARGS(fname, tmp2, '%');
do {
- int tmplen;
- /* First piece has no leading percent, so it's copied verbatim */
- ast_copy_string(tmp, fname.piece[0], sizeof(tmp));
- tmplen = strlen(tmp);
- for (idx = 1; idx < fname.argc; idx++) {
- if (fname.piece[idx][0] == 'd') {
- /* Substitute the count */
- snprintf(countstring, sizeof(countstring), "%d", count);
- ast_copy_string(tmp + tmplen, countstring, sizeof(tmp) - tmplen);
- tmplen += strlen(countstring);
- } else if (tmplen + 2 < sizeof(tmp)) {
- /* Unknown format specifier - just copy it verbatim */
- tmp[tmplen++] = '%';
- tmp[tmplen++] = fname.piece[idx][0];
+ for (src = 0, dst = 0; src < src_len && dst < dst_len; src++) {
+ if (!strncmp(&args.filename[src], "%d", 2)) {
+ int s = snprintf(&tmp[dst], PATH_MAX - dst, "%zu", count);
+ if (s >= PATH_MAX - dst) {
+ /* We truncated, so we need to bail */
+ ast_log(LOG_WARNING, "Failed to create unique filename from template: %s\n", args.filename);
+ pbx_builtin_setvar_helper(chan, "RECORD_STATUS", "ERROR");
+ return -1;
+ }
+ dst += s;
+ src++;
+ } else {
+ tmp[dst] = args.filename[src];
+ tmp[++dst] = '\0';
}
- /* Copy the remaining portion of the piece */
- ast_copy_string(tmp + tmplen, &(fname.piece[idx][1]), sizeof(tmp) - tmplen);
}
count++;
} while (ast_fileexists(tmp, ext, ast_channel_language(chan)) > 0);
@@ -307,7 +331,6 @@ static int record_exec(struct ast_channel *chan, const char *data)
ast_copy_string(tmp, args.filename, sizeof(tmp));
pbx_builtin_setvar_helper(chan, "RECORDED_FILE", tmp);
- /* end of routine mentioned */
if (ast_channel_state(chan) != AST_STATE_UP) {
if (ast_test_flag(&flags, OPTION_SKIP)) {
@@ -356,11 +379,11 @@ static int record_exec(struct ast_channel *chan, const char *data)
ast_dsp_set_threshold(sildet, ast_dsp_get_threshold_from_settings(THRESHOLD_SILENCE));
}
- /* Create the directory if it does not exist. */
- dir = ast_strdupa(tmp);
- if ((file = strrchr(dir, '/')))
- *file++ = '\0';
- ast_mkdir (dir, 0777);
+ if (create_destination_directory(tmp)) {
+ ast_log(LOG_WARNING, "Could not create directory for file %s\n", args.filename);
+ pbx_builtin_setvar_helper(chan, "RECORD_STATUS", "ERROR");
+ goto out;
+ }
ioflags = ast_test_flag(&flags, OPTION_APPEND) ? O_CREAT|O_APPEND|O_WRONLY : O_CREAT|O_TRUNC|O_WRONLY;
s = ast_writefile(tmp, ext, NULL, ioflags, 0, AST_FILE_MODE);
diff --git a/apps/app_system.c b/apps/app_system.c
index 7fe453de1..e868a07dc 100644
--- a/apps/app_system.c
+++ b/apps/app_system.c
@@ -48,6 +48,11 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
<syntax>
<parameter name="command" required="true">
<para>Command to execute</para>
+ <warning><para>Do not use untrusted strings such as <variable>CALLERID(num)</variable>
+ or <variable>CALLERID(name)</variable> as part of the command parameters. You
+ risk a command injection attack executing arbitrary commands if the untrusted
+ strings aren't filtered to remove dangerous characters. See function
+ <variable>FILTER()</variable>.</para></warning>
</parameter>
</syntax>
<description>
@@ -73,6 +78,11 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
<syntax>
<parameter name="command" required="true">
<para>Command to execute</para>
+ <warning><para>Do not use untrusted strings such as <variable>CALLERID(num)</variable>
+ or <variable>CALLERID(name)</variable> as part of the command parameters. You
+ risk a command injection attack executing arbitrary commands if the untrusted
+ strings aren't filtered to remove dangerous characters. See function
+ <variable>FILTER()</variable>.</para></warning>
</parameter>
</syntax>
<description>
diff --git a/apps/app_voicemail.c b/apps/app_voicemail.c
index f62c7d8c9..f954e278e 100644
--- a/apps/app_voicemail.c
+++ b/apps/app_voicemail.c
@@ -507,6 +507,7 @@ static int imapversion = 1;
static int expungeonhangup = 1;
static int imapgreetings = 0;
+static int imap_poll_logout = 0;
static char delimiter = '\0';
/* mail_open cannot be protected on a stream basis */
@@ -544,6 +545,8 @@ static int imap_retrieve_file (const char *dir, const int msgnum, const char *ma
static int imap_delete_old_greeting (char *dir, struct vm_state *vms);
static void check_quota(struct vm_state *vms, char *mailbox);
static int open_mailbox(struct vm_state *vms, struct ast_vm_user *vmu, int box);
+static void imap_logout(const char *mailbox_id);
+
struct vmstate {
struct vm_state *vms;
AST_LIST_ENTRY(vmstate) list;
@@ -2732,7 +2735,7 @@ static int imap_store_file(const char *dir, const char *mailboxuser, const char
}
if (fread(buf, 1, len, p) != len) {
if (ferror(p)) {
- ast_log(LOG_ERROR, "Error while reading mail file: %s\n");
+ ast_log(LOG_ERROR, "Error while reading mail file: %s\n", tmp);
return -1;
}
}
@@ -3778,12 +3781,12 @@ static SQLHSTMT generic_prepare(struct odbc_obj *obj, void *data)
SQLHSTMT stmt;
res = SQLAllocHandle(SQL_HANDLE_STMT, obj->con, &stmt);
- if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
+ if (!SQL_SUCCEEDED(res)) {
ast_log(AST_LOG_WARNING, "SQL Alloc Handle failed!\n");
return NULL;
}
res = SQLPrepare(stmt, (unsigned char *) gps->sql, SQL_NTS);
- if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
+ if (!SQL_SUCCEEDED(res)) {
ast_log(AST_LOG_WARNING, "SQL Prepare failed![%s]\n", gps->sql);
SQLFreeHandle(SQL_HANDLE_STMT, stmt);
return NULL;
@@ -3825,14 +3828,14 @@ static void odbc_update_msg_id(char *dir, int msg_num, char *msg_id)
* \brief Retrieves a file from an ODBC data store.
* \param dir the path to the file to be retrieved.
* \param msgnum the message number, such as within a mailbox folder.
- *
+ *
* This method is used by the RETRIEVE macro when mailboxes are stored in an ODBC back end.
* The purpose is to get the message from the database store to the local file system, so that the message may be played, or the information file may be read.
*
* The file is looked up by invoking a SQL on the odbc_table (default 'voicemessages') using the dir and msgnum input parameters.
* The output is the message information file with the name msgnum and the extension .txt
* and the message file with the extension of its format, in the directory with base file name of the msgnum.
- *
+ *
* \return 0 on success, -1 on error.
*/
static int retrieve_file(char *dir, int msgnum)
@@ -3845,7 +3848,7 @@ static int retrieve_file(char *dir, int msgnum)
SQLSMALLINT colcount = 0;
SQLHSTMT stmt;
char sql[PATH_MAX];
- char fmt[80]="";
+ char fmt[80] = "";
char *c;
char coltitle[256];
SQLSMALLINT collen;
@@ -3861,144 +3864,139 @@ static int retrieve_file(char *dir, int msgnum)
char msgnums[80];
char *argv[] = { dir, msgnums };
struct generic_prepare_struct gps = { .sql = sql, .argc = 2, .argv = argv };
-
struct odbc_obj *obj;
+
obj = ast_odbc_request_obj(odbc_database, 0);
- if (obj) {
- ast_copy_string(fmt, vmfmts, sizeof(fmt));
- c = strchr(fmt, '|');
- if (c)
- *c = '\0';
- if (!strcasecmp(fmt, "wav49"))
- strcpy(fmt, "WAV");
- snprintf(msgnums, sizeof(msgnums), "%d", msgnum);
- if (msgnum > -1)
- make_file(fn, sizeof(fn), dir, msgnum);
- else
- ast_copy_string(fn, dir, sizeof(fn));
+ if (!obj) {
+ ast_log(AST_LOG_WARNING, "Failed to obtain database object for '%s'!\n", odbc_database);
+ return -1;
+ }
- /* Create the information file */
- snprintf(full_fn, sizeof(full_fn), "%s.txt", fn);
-
- if (!(f = fopen(full_fn, "w+"))) {
- ast_log(AST_LOG_WARNING, "Failed to open/create '%s'\n", full_fn);
- goto yuck;
- }
-
- snprintf(full_fn, sizeof(full_fn), "%s.%s", fn, fmt);
- snprintf(sql, sizeof(sql), "SELECT * FROM %s WHERE dir=? AND msgnum=?", odbc_table);
- stmt = ast_odbc_prepare_and_execute(obj, generic_prepare, &gps);
- if (!stmt) {
- ast_log(AST_LOG_WARNING, "SQL Execute error!\n[%s]\n\n", sql);
- ast_odbc_release_obj(obj);
- goto yuck;
- }
- res = SQLFetch(stmt);
- if (res == SQL_NO_DATA) {
- SQLFreeHandle (SQL_HANDLE_STMT, stmt);
- ast_odbc_release_obj(obj);
- goto yuck;
- } else if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
+ ast_copy_string(fmt, vmfmts, sizeof(fmt));
+ c = strchr(fmt, '|');
+ if (c)
+ *c = '\0';
+ if (!strcasecmp(fmt, "wav49"))
+ strcpy(fmt, "WAV");
+
+ snprintf(msgnums, sizeof(msgnums), "%d", msgnum);
+ if (msgnum > -1)
+ make_file(fn, sizeof(fn), dir, msgnum);
+ else
+ ast_copy_string(fn, dir, sizeof(fn));
+
+ /* Create the information file */
+ snprintf(full_fn, sizeof(full_fn), "%s.txt", fn);
+
+ if (!(f = fopen(full_fn, "w+"))) {
+ ast_log(AST_LOG_WARNING, "Failed to open/create '%s'\n", full_fn);
+ goto bail;
+ }
+
+ snprintf(full_fn, sizeof(full_fn), "%s.%s", fn, fmt);
+ snprintf(sql, sizeof(sql), "SELECT * FROM %s WHERE dir=? AND msgnum=?", odbc_table);
+
+ stmt = ast_odbc_prepare_and_execute(obj, generic_prepare, &gps);
+ if (!stmt) {
+ ast_log(AST_LOG_WARNING, "SQL Execute error!\n[%s]\n\n", sql);
+ goto bail;
+ }
+
+ res = SQLFetch(stmt);
+ if (!SQL_SUCCEEDED(res)) {
+ if (res != SQL_NO_DATA) {
ast_log(AST_LOG_WARNING, "SQL Fetch error!\n[%s]\n\n", sql);
- SQLFreeHandle (SQL_HANDLE_STMT, stmt);
- ast_odbc_release_obj(obj);
- goto yuck;
}
- fd = open(full_fn, O_RDWR | O_CREAT | O_TRUNC, VOICEMAIL_FILE_MODE);
- if (fd < 0) {
- ast_log(AST_LOG_WARNING, "Failed to write '%s': %s\n", full_fn, strerror(errno));
- SQLFreeHandle (SQL_HANDLE_STMT, stmt);
- ast_odbc_release_obj(obj);
- goto yuck;
- }
- res = SQLNumResultCols(stmt, &colcount);
- if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
- ast_log(AST_LOG_WARNING, "SQL Column Count error!\n[%s]\n\n", sql);
- SQLFreeHandle (SQL_HANDLE_STMT, stmt);
- ast_odbc_release_obj(obj);
- goto yuck;
- }
- if (f)
- fprintf(f, "[message]\n");
- for (x = 0; x < colcount; x++) {
- rowdata[0] = '\0';
- colsize = 0;
- collen = sizeof(coltitle);
- res = SQLDescribeCol(stmt, x + 1, (unsigned char *) coltitle, sizeof(coltitle), &collen,
- &datatype, &colsize, &decimaldigits, &nullable);
- if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
- ast_log(AST_LOG_WARNING, "SQL Describe Column error!\n[%s]\n\n", sql);
- SQLFreeHandle (SQL_HANDLE_STMT, stmt);
- ast_odbc_release_obj(obj);
- goto yuck;
- }
- if (!strcasecmp(coltitle, "recording")) {
- off_t offset;
- res = SQLGetData(stmt, x + 1, SQL_BINARY, rowdata, 0, &colsize2);
- fdlen = colsize2;
- if (fd > -1) {
- char tmp[1]="";
- lseek(fd, fdlen - 1, SEEK_SET);
- if (write(fd, tmp, 1) != 1) {
- close(fd);
- fd = -1;
- continue;
- }
- /* Read out in small chunks */
- for (offset = 0; offset < colsize2; offset += CHUNKSIZE) {
- if ((fdm = mmap(NULL, CHUNKSIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, offset)) == MAP_FAILED) {
- ast_log(AST_LOG_WARNING, "Could not mmap the output file: %s (%d)\n", strerror(errno), errno);
- SQLFreeHandle(SQL_HANDLE_STMT, stmt);
- ast_odbc_release_obj(obj);
- goto yuck;
- } else {
- res = SQLGetData(stmt, x + 1, SQL_BINARY, fdm, CHUNKSIZE, NULL);
- munmap(fdm, CHUNKSIZE);
- if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
- ast_log(AST_LOG_WARNING, "SQL Get Data error!\n[%s]\n\n", sql);
- unlink(full_fn);
- SQLFreeHandle(SQL_HANDLE_STMT, stmt);
- ast_odbc_release_obj(obj);
- goto yuck;
- }
- }
+ goto bail_with_handle;
+ }
+
+ fd = open(full_fn, O_RDWR | O_CREAT | O_TRUNC, VOICEMAIL_FILE_MODE);
+ if (fd < 0) {
+ ast_log(AST_LOG_WARNING, "Failed to write '%s': %s\n", full_fn, strerror(errno));
+ goto bail_with_handle;
+ }
+
+ res = SQLNumResultCols(stmt, &colcount);
+ if (!SQL_SUCCEEDED(res)) {
+ ast_log(AST_LOG_WARNING, "SQL Column Count error!\n[%s]\n\n", sql);
+ goto bail_with_handle;
+ }
+
+ fprintf(f, "[message]\n");
+ for (x = 0; x < colcount; x++) {
+ rowdata[0] = '\0';
+ colsize = 0;
+ collen = sizeof(coltitle);
+ res = SQLDescribeCol(stmt, x + 1, (unsigned char *) coltitle, sizeof(coltitle), &collen,
+ &datatype, &colsize, &decimaldigits, &nullable);
+ if (!SQL_SUCCEEDED(res)) {
+ ast_log(AST_LOG_WARNING, "SQL Describe Column error!\n[%s]\n\n", sql);
+ goto bail_with_handle;
+ }
+ if (!strcasecmp(coltitle, "recording")) {
+ off_t offset;
+ res = SQLGetData(stmt, x + 1, SQL_BINARY, rowdata, 0, &colsize2);
+ fdlen = colsize2;
+ if (fd > -1) {
+ char tmp[1] = "";
+ lseek(fd, fdlen - 1, SEEK_SET);
+ if (write(fd, tmp, 1) != 1) {
+ close(fd);
+ fd = -1;
+ continue;
+ }
+ /* Read out in small chunks */
+ for (offset = 0; offset < colsize2; offset += CHUNKSIZE) {
+ if ((fdm = mmap(NULL, CHUNKSIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, offset)) == MAP_FAILED) {
+ ast_log(AST_LOG_WARNING, "Could not mmap the output file: %s (%d)\n", strerror(errno), errno);
+ goto bail_with_handle;
}
- if (truncate(full_fn, fdlen) < 0) {
- ast_log(LOG_WARNING, "Unable to truncate '%s': %s\n", full_fn, strerror(errno));
+ res = SQLGetData(stmt, x + 1, SQL_BINARY, fdm, CHUNKSIZE, NULL);
+ munmap(fdm, CHUNKSIZE);
+ if (!SQL_SUCCEEDED(res)) {
+ ast_log(AST_LOG_WARNING, "SQL Get Data error!\n[%s]\n\n", sql);
+ unlink(full_fn);
+ goto bail_with_handle;
}
}
- } else {
- res = SQLGetData(stmt, x + 1, SQL_CHAR, rowdata, sizeof(rowdata), NULL);
- if ((res == SQL_NULL_DATA) && (!strcasecmp(coltitle, "msg_id"))) {
- char msg_id[MSG_ID_LEN];
- generate_msg_id(msg_id);
- snprintf(rowdata, sizeof(rowdata), "%s", msg_id);
- odbc_update_msg_id(dir, msgnum, msg_id);
- } else if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
- ast_log(AST_LOG_WARNING, "SQL Get Data error! coltitle=%s\n[%s]\n\n", coltitle, sql);
- SQLFreeHandle (SQL_HANDLE_STMT, stmt);
- ast_odbc_release_obj(obj);
- goto yuck;
+ if (truncate(full_fn, fdlen) < 0) {
+ ast_log(LOG_WARNING, "Unable to truncate '%s': %s\n", full_fn, strerror(errno));
}
- if (strcasecmp(coltitle, "msgnum") && strcasecmp(coltitle, "dir") && f)
- fprintf(f, "%s=%s\n", coltitle, rowdata);
+ }
+ } else {
+ res = SQLGetData(stmt, x + 1, SQL_CHAR, rowdata, sizeof(rowdata), NULL);
+ if (res == SQL_NULL_DATA && !strcasecmp(coltitle, "msg_id")) {
+ char msg_id[MSG_ID_LEN];
+ generate_msg_id(msg_id);
+ snprintf(rowdata, sizeof(rowdata), "%s", msg_id);
+ odbc_update_msg_id(dir, msgnum, msg_id);
+ } else if (!SQL_SUCCEEDED(res)) {
+ ast_log(AST_LOG_WARNING, "SQL Get Data error! coltitle=%s\n[%s]\n\n", coltitle, sql);
+ goto bail_with_handle;
+ }
+ if (strcasecmp(coltitle, "msgnum") && strcasecmp(coltitle, "dir")) {
+ fprintf(f, "%s=%s\n", coltitle, rowdata);
}
}
- SQLFreeHandle (SQL_HANDLE_STMT, stmt);
- ast_odbc_release_obj(obj);
- } else
- ast_log(AST_LOG_WARNING, "Failed to obtain database object for '%s'!\n", odbc_database);
-yuck:
+ }
+
+bail_with_handle:
+ SQLFreeHandle(SQL_HANDLE_STMT, stmt);
+
+bail:
if (f)
fclose(f);
if (fd > -1)
close(fd);
+
+ ast_odbc_release_obj(obj);
+
return x - 1;
}
/*!
* \brief Determines the highest message number in use for a given user and mailbox folder.
- * \param vmu
+ * \param vmu
* \param dir the folder the mailbox folder to look for messages. Used to construct the SQL where clause.
*
* This method is used when mailboxes are stored in an ODBC back end.
@@ -4009,58 +4007,61 @@ yuck:
*/
static int last_message_index(struct ast_vm_user *vmu, char *dir)
{
- int x = 0;
+ int x = -1;
int res;
SQLHSTMT stmt;
char sql[PATH_MAX];
char rowdata[20];
char *argv[] = { dir };
struct generic_prepare_struct gps = { .sql = sql, .argc = 1, .argv = argv };
-
struct odbc_obj *obj;
+
obj = ast_odbc_request_obj(odbc_database, 0);
- if (obj) {
- snprintf(sql, sizeof(sql), "SELECT msgnum FROM %s WHERE dir=? order by msgnum desc", odbc_table);
+ if (!obj) {
+ ast_log(AST_LOG_WARNING, "Failed to obtain database object for '%s'!\n", odbc_database);
+ return -1;
+ }
- stmt = ast_odbc_prepare_and_execute(obj, generic_prepare, &gps);
- if (!stmt) {
- ast_log(AST_LOG_WARNING, "SQL Execute error!\n[%s]\n\n", sql);
- ast_odbc_release_obj(obj);
- goto yuck;
- }
- res = SQLFetch(stmt);
- if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
- if (res == SQL_NO_DATA) {
- ast_log(AST_LOG_DEBUG, "Directory '%s' has no messages and therefore no index was retrieved.\n", dir);
- } else {
- ast_log(AST_LOG_WARNING, "SQL Fetch error!\n[%s]\n\n", sql);
- }
+ snprintf(sql, sizeof(sql), "SELECT msgnum FROM %s WHERE dir=? order by msgnum desc", odbc_table);
- SQLFreeHandle (SQL_HANDLE_STMT, stmt);
- ast_odbc_release_obj(obj);
- goto yuck;
- }
- res = SQLGetData(stmt, 1, SQL_CHAR, rowdata, sizeof(rowdata), NULL);
- if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
- ast_log(AST_LOG_WARNING, "SQL Get Data error!\n[%s]\n\n", sql);
- SQLFreeHandle (SQL_HANDLE_STMT, stmt);
- ast_odbc_release_obj(obj);
- goto yuck;
+ stmt = ast_odbc_prepare_and_execute(obj, generic_prepare, &gps);
+ if (!stmt) {
+ ast_log(AST_LOG_WARNING, "SQL Execute error!\n[%s]\n\n", sql);
+ goto bail;
+ }
+
+ res = SQLFetch(stmt);
+ if (!SQL_SUCCEEDED(res)) {
+ if (res == SQL_NO_DATA) {
+ ast_log(AST_LOG_DEBUG, "Directory '%s' has no messages and therefore no index was retrieved.\n", dir);
+ } else {
+ ast_log(AST_LOG_WARNING, "SQL Fetch error!\n[%s]\n\n", sql);
}
- if (sscanf(rowdata, "%30d", &x) != 1)
- ast_log(AST_LOG_WARNING, "Failed to read message index!\n");
- SQLFreeHandle (SQL_HANDLE_STMT, stmt);
- ast_odbc_release_obj(obj);
- return x;
- } else
- ast_log(AST_LOG_WARNING, "Failed to obtain database object for '%s'!\n", odbc_database);
-yuck:
- return x - 1;
+ goto bail_with_handle;
+ }
+
+ res = SQLGetData(stmt, 1, SQL_CHAR, rowdata, sizeof(rowdata), NULL);
+ if (!SQL_SUCCEEDED(res)) {
+ ast_log(AST_LOG_WARNING, "SQL Get Data error!\n[%s]\n\n", sql);
+ goto bail_with_handle;
+ }
+
+ if (sscanf(rowdata, "%30d", &x) != 1) {
+ ast_log(AST_LOG_WARNING, "Failed to read message index!\n");
+ }
+
+bail_with_handle:
+ SQLFreeHandle(SQL_HANDLE_STMT, stmt);
+
+bail:
+ ast_odbc_release_obj(obj);
+
+ return x;
}
/*!
* \brief Determines if the specified message exists.
- * \param dir the folder the mailbox folder to look for messages.
+ * \param dir the folder the mailbox folder to look for messages.
* \param msgnum the message index to query for.
*
* This method is used when mailboxes are stored in an ODBC back end.
@@ -4077,39 +4078,43 @@ static int message_exists(char *dir, int msgnum)
char msgnums[20];
char *argv[] = { dir, msgnums };
struct generic_prepare_struct gps = { .sql = sql, .argc = 2, .argv = argv };
-
struct odbc_obj *obj;
+
obj = ast_odbc_request_obj(odbc_database, 0);
- if (obj) {
- snprintf(msgnums, sizeof(msgnums), "%d", msgnum);
- snprintf(sql, sizeof(sql), "SELECT COUNT(*) FROM %s WHERE dir=? AND msgnum=?", odbc_table);
- stmt = ast_odbc_prepare_and_execute(obj, generic_prepare, &gps);
- if (!stmt) {
- ast_log(AST_LOG_WARNING, "SQL Execute error!\n[%s]\n\n", sql);
- ast_odbc_release_obj(obj);
- goto yuck;
- }
- res = SQLFetch(stmt);
- if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
- ast_log(AST_LOG_WARNING, "SQL Fetch error!\n[%s]\n\n", sql);
- SQLFreeHandle (SQL_HANDLE_STMT, stmt);
- ast_odbc_release_obj(obj);
- goto yuck;
- }
- res = SQLGetData(stmt, 1, SQL_CHAR, rowdata, sizeof(rowdata), NULL);
- if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
- ast_log(AST_LOG_WARNING, "SQL Get Data error!\n[%s]\n\n", sql);
- SQLFreeHandle (SQL_HANDLE_STMT, stmt);
- ast_odbc_release_obj(obj);
- goto yuck;
- }
- if (sscanf(rowdata, "%30d", &x) != 1)
- ast_log(AST_LOG_WARNING, "Failed to read message count!\n");
- SQLFreeHandle (SQL_HANDLE_STMT, stmt);
- ast_odbc_release_obj(obj);
- } else
+ if (!obj) {
ast_log(AST_LOG_WARNING, "Failed to obtain database object for '%s'!\n", odbc_database);
-yuck:
+ return 0;
+ }
+
+ snprintf(msgnums, sizeof(msgnums), "%d", msgnum);
+ snprintf(sql, sizeof(sql), "SELECT COUNT(*) FROM %s WHERE dir=? AND msgnum=?", odbc_table);
+ stmt = ast_odbc_prepare_and_execute(obj, generic_prepare, &gps);
+ if (!stmt) {
+ ast_log(AST_LOG_WARNING, "SQL Execute error!\n[%s]\n\n", sql);
+ goto bail;
+ }
+
+ res = SQLFetch(stmt);
+ if (!SQL_SUCCEEDED(res)) {
+ ast_log(AST_LOG_WARNING, "SQL Fetch error!\n[%s]\n\n", sql);
+ goto bail_with_handle;
+ }
+
+ res = SQLGetData(stmt, 1, SQL_CHAR, rowdata, sizeof(rowdata), NULL);
+ if (!SQL_SUCCEEDED(res)) {
+ ast_log(AST_LOG_WARNING, "SQL Get Data error!\n[%s]\n\n", sql);
+ goto bail_with_handle;
+ }
+
+ if (sscanf(rowdata, "%30d", &x) != 1) {
+ ast_log(AST_LOG_WARNING, "Failed to read message count!\n");
+ }
+
+bail_with_handle:
+ SQLFreeHandle(SQL_HANDLE_STMT, stmt);
+
+bail:
+ ast_odbc_release_obj(obj);
return x;
}
@@ -4124,48 +4129,50 @@ yuck:
*/
static int count_messages(struct ast_vm_user *vmu, char *dir)
{
- int x = 0;
+ int x = -1;
int res;
SQLHSTMT stmt;
char sql[PATH_MAX];
char rowdata[20];
char *argv[] = { dir };
struct generic_prepare_struct gps = { .sql = sql, .argc = 1, .argv = argv };
-
struct odbc_obj *obj;
+
obj = ast_odbc_request_obj(odbc_database, 0);
- if (obj) {
- snprintf(sql, sizeof(sql), "SELECT COUNT(*) FROM %s WHERE dir=?", odbc_table);
- stmt = ast_odbc_prepare_and_execute(obj, generic_prepare, &gps);
- if (!stmt) {
- ast_log(AST_LOG_WARNING, "SQL Execute error!\n[%s]\n\n", sql);
- ast_odbc_release_obj(obj);
- goto yuck;
- }
- res = SQLFetch(stmt);
- if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
- ast_log(AST_LOG_WARNING, "SQL Fetch error!\n[%s]\n\n", sql);
- SQLFreeHandle (SQL_HANDLE_STMT, stmt);
- ast_odbc_release_obj(obj);
- goto yuck;
- }
- res = SQLGetData(stmt, 1, SQL_CHAR, rowdata, sizeof(rowdata), NULL);
- if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
- ast_log(AST_LOG_WARNING, "SQL Get Data error!\n[%s]\n\n", sql);
- SQLFreeHandle (SQL_HANDLE_STMT, stmt);
- ast_odbc_release_obj(obj);
- goto yuck;
- }
- if (sscanf(rowdata, "%30d", &x) != 1)
- ast_log(AST_LOG_WARNING, "Failed to read message count!\n");
- SQLFreeHandle (SQL_HANDLE_STMT, stmt);
- ast_odbc_release_obj(obj);
- return x;
- } else
+ if (!obj) {
ast_log(AST_LOG_WARNING, "Failed to obtain database object for '%s'!\n", odbc_database);
-yuck:
- return x - 1;
+ return -1;
+ }
+
+ snprintf(sql, sizeof(sql), "SELECT COUNT(*) FROM %s WHERE dir=?", odbc_table);
+ stmt = ast_odbc_prepare_and_execute(obj, generic_prepare, &gps);
+ if (!stmt) {
+ ast_log(AST_LOG_WARNING, "SQL Execute error!\n[%s]\n\n", sql);
+ goto bail;
+ }
+ res = SQLFetch(stmt);
+ if (!SQL_SUCCEEDED(res)) {
+ ast_log(AST_LOG_WARNING, "SQL Fetch error!\n[%s]\n\n", sql);
+ goto bail_with_handle;
+ }
+
+ res = SQLGetData(stmt, 1, SQL_CHAR, rowdata, sizeof(rowdata), NULL);
+ if (!SQL_SUCCEEDED(res)) {
+ ast_log(AST_LOG_WARNING, "SQL Get Data error!\n[%s]\n\n", sql);
+ goto bail_with_handle;
+ }
+
+ if (sscanf(rowdata, "%30d", &x) != 1) {
+ ast_log(AST_LOG_WARNING, "Failed to read message count!\n");
+ }
+
+bail_with_handle:
+ SQLFreeHandle(SQL_HANDLE_STMT, stmt);
+
+bail:
+ ast_odbc_release_obj(obj);
+ return x;
}
/*!
@@ -4175,7 +4182,7 @@ yuck:
*
* This method is used when mailboxes are stored in an ODBC back end.
* The specified message is directly deleted from the database 'voicemessages' table.
- *
+ *
* \return the value greater than zero on success to indicate the number of messages, less than zero on error.
*/
static void delete_file(const char *sdir, int smsg)
@@ -4187,21 +4194,25 @@ static void delete_file(const char *sdir, int smsg)
struct generic_prepare_struct gps = { .sql = sql, .argc = 2, .argv = argv };
struct odbc_obj *obj;
- argv[0] = ast_strdupa(sdir);
-
obj = ast_odbc_request_obj(odbc_database, 0);
- if (obj) {
- snprintf(msgnums, sizeof(msgnums), "%d", smsg);
- snprintf(sql, sizeof(sql), "DELETE FROM %s WHERE dir=? AND msgnum=?", odbc_table);
- stmt = ast_odbc_prepare_and_execute(obj, generic_prepare, &gps);
- if (!stmt)
- ast_log(AST_LOG_WARNING, "SQL Execute error!\n[%s]\n\n", sql);
- else
- SQLFreeHandle (SQL_HANDLE_STMT, stmt);
- ast_odbc_release_obj(obj);
- } else
+ if (!obj) {
ast_log(AST_LOG_WARNING, "Failed to obtain database object for '%s'!\n", odbc_database);
- return;
+ return;
+ }
+
+ argv[0] = ast_strdupa(sdir);
+
+ snprintf(msgnums, sizeof(msgnums), "%d", smsg);
+ snprintf(sql, sizeof(sql), "DELETE FROM %s WHERE dir=? AND msgnum=?", odbc_table);
+ stmt = ast_odbc_prepare_and_execute(obj, generic_prepare, &gps);
+ if (!stmt) {
+ ast_log(AST_LOG_WARNING, "SQL Execute error!\n[%s]\n\n", sql);
+ } else {
+ SQLFreeHandle(SQL_HANDLE_STMT, stmt);
+ }
+ ast_odbc_release_obj(obj);
+
+ return;
}
/*!
@@ -4229,19 +4240,22 @@ static void copy_file(char *sdir, int smsg, char *ddir, int dmsg, char *dmailbox
generate_msg_id(msg_id);
delete_file(ddir, dmsg);
obj = ast_odbc_request_obj(odbc_database, 0);
- if (obj) {
- snprintf(msgnums, sizeof(msgnums), "%d", smsg);
- snprintf(msgnumd, sizeof(msgnumd), "%d", dmsg);
- snprintf(sql, sizeof(sql), "INSERT INTO %s (dir, msgnum, msg_id, context, macrocontext, callerid, origtime, duration, recording, flag, mailboxuser, mailboxcontext) SELECT ?,?,?,context,macrocontext,callerid,origtime,duration,recording,flag,?,? FROM %s WHERE dir=? AND msgnum=?", odbc_table, odbc_table);
- stmt = ast_odbc_prepare_and_execute(obj, generic_prepare, &gps);
- if (!stmt)
- ast_log(AST_LOG_WARNING, "SQL Execute error!\n[%s] (You probably don't have MySQL 4.1 or later installed)\n\n", sql);
- else
- SQLFreeHandle(SQL_HANDLE_STMT, stmt);
- ast_odbc_release_obj(obj);
- } else
+ if (!obj) {
ast_log(AST_LOG_WARNING, "Failed to obtain database object for '%s'!\n", odbc_database);
- return;
+ return;
+ }
+
+ snprintf(msgnums, sizeof(msgnums), "%d", smsg);
+ snprintf(msgnumd, sizeof(msgnumd), "%d", dmsg);
+ snprintf(sql, sizeof(sql), "INSERT INTO %s (dir, msgnum, msg_id, context, macrocontext, callerid, origtime, duration, recording, flag, mailboxuser, mailboxcontext) SELECT ?,?,?,context,macrocontext,callerid,origtime,duration,recording,flag,?,? FROM %s WHERE dir=? AND msgnum=?", odbc_table, odbc_table);
+ stmt = ast_odbc_prepare_and_execute(obj, generic_prepare, &gps);
+ if (!stmt)
+ ast_log(AST_LOG_WARNING, "SQL Execute error!\n[%s] (You probably don't have MySQL 4.1 or later installed)\n\n", sql);
+ else
+ SQLFreeHandle(SQL_HANDLE_STMT, stmt);
+ ast_odbc_release_obj(obj);
+
+ return;
}
struct insert_data {
@@ -4270,9 +4284,8 @@ static SQLHSTMT insert_data_cb(struct odbc_obj *obj, void *vdata)
SQLHSTMT stmt;
res = SQLAllocHandle(SQL_HANDLE_STMT, obj->con, &stmt);
- if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
+ if (!SQL_SUCCEEDED(res)) {
ast_log(AST_LOG_WARNING, "SQL Alloc Handle failed!\n");
- SQLFreeHandle(SQL_HANDLE_STMT, stmt);
return NULL;
}
@@ -4292,7 +4305,7 @@ static SQLHSTMT insert_data_cb(struct odbc_obj *obj, void *vdata)
SQLBindParameter(stmt, 13, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(data->category), 0, (void *) data->category, 0, NULL);
}
res = SQLExecDirect(stmt, (unsigned char *) data->sql, SQL_NTS);
- if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
+ if (!SQL_SUCCEEDED(res)) {
ast_log(AST_LOG_WARNING, "SQL Direct Execute failed!\n");
SQLFreeHandle(SQL_HANDLE_STMT, stmt);
return NULL;
@@ -4309,7 +4322,7 @@ static SQLHSTMT insert_data_cb(struct odbc_obj *obj, void *vdata)
* \param msgnum the message index for the message to be stored.
*
* This method is used when mailboxes are stored in an ODBC back end.
- * The message sound file and information file is looked up on the file system.
+ * The message sound file and information file is looked up on the file system.
* A SQL query is invoked to store the message into the (MySQL) database.
*
* \return the zero on success -1 on error.
@@ -4334,7 +4347,9 @@ static int store_file(const char *dir, const char *mailboxuser, const char *mail
struct ast_flags config_flags = { CONFIG_FLAG_NOCACHE };
delete_file(dir, msgnum);
- if (!(obj = ast_odbc_request_obj(odbc_database, 0))) {
+
+ obj = ast_odbc_request_obj(odbc_database, 0);
+ if (!obj) {
ast_log(AST_LOG_WARNING, "Failed to obtain database object for '%s'!\n", odbc_database);
return -1;
}
@@ -4397,25 +4412,25 @@ static int store_file(const char *dir, const char *mailboxuser, const char *mail
ast_log(AST_LOG_WARNING, "Memory map failed for sound file '%s'!\n", full_fn);
res = -1;
break;
- }
+ }
idata.data = fdm;
idata.datalen = idata.indlen = fdlen;
- if (!ast_strlen_zero(idata.category))
- snprintf(sql, sizeof(sql), "INSERT INTO %s (dir,msgnum,recording,context,macrocontext,callerid,origtime,duration,mailboxuser,mailboxcontext,flag,msg_id,category) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?)", odbc_table);
+ if (!ast_strlen_zero(idata.category))
+ snprintf(sql, sizeof(sql), "INSERT INTO %s (dir,msgnum,recording,context,macrocontext,callerid,origtime,duration,mailboxuser,mailboxcontext,flag,msg_id,category) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?)", odbc_table);
else
snprintf(sql, sizeof(sql), "INSERT INTO %s (dir,msgnum,recording,context,macrocontext,callerid,origtime,duration,mailboxuser,mailboxcontext,flag,msg_id) VALUES (?,?,?,?,?,?,?,?,?,?,?,?)", odbc_table);
if ((stmt = ast_odbc_direct_execute(obj, insert_data_cb, &idata))) {
- SQLFreeHandle (SQL_HANDLE_STMT, stmt);
+ SQLFreeHandle(SQL_HANDLE_STMT, stmt);
} else {
ast_log(AST_LOG_WARNING, "SQL Execute error!\n[%s]\n\n", sql);
res = -1;
}
} while (0);
- if (obj) {
- ast_odbc_release_obj(obj);
- }
+
+ ast_odbc_release_obj(obj);
+
if (valid_config(cfg))
ast_config_destroy(cfg);
if (fdm != MAP_FAILED)
@@ -4449,20 +4464,23 @@ static void rename_file(char *sdir, int smsg, char *mailboxuser, char *mailboxco
struct generic_prepare_struct gps = { .sql = sql, .argc = 6, .argv = argv };
delete_file(ddir, dmsg);
+
obj = ast_odbc_request_obj(odbc_database, 0);
- if (obj) {
- snprintf(msgnums, sizeof(msgnums), "%d", smsg);
- snprintf(msgnumd, sizeof(msgnumd), "%d", dmsg);
- snprintf(sql, sizeof(sql), "UPDATE %s SET dir=?, msgnum=?, mailboxuser=?, mailboxcontext=? WHERE dir=? AND msgnum=?", odbc_table);
- stmt = ast_odbc_prepare_and_execute(obj, generic_prepare, &gps);
- if (!stmt)
- ast_log(AST_LOG_WARNING, "SQL Execute error!\n[%s]\n\n", sql);
- else
- SQLFreeHandle(SQL_HANDLE_STMT, stmt);
- ast_odbc_release_obj(obj);
- } else
+ if (!obj) {
ast_log(AST_LOG_WARNING, "Failed to obtain database object for '%s'!\n", odbc_database);
- return;
+ return;
+ }
+
+ snprintf(msgnums, sizeof(msgnums), "%d", smsg);
+ snprintf(msgnumd, sizeof(msgnumd), "%d", dmsg);
+ snprintf(sql, sizeof(sql), "UPDATE %s SET dir=?, msgnum=?, mailboxuser=?, mailboxcontext=? WHERE dir=? AND msgnum=?", odbc_table);
+ stmt = ast_odbc_prepare_and_execute(obj, generic_prepare, &gps);
+ if (!stmt)
+ ast_log(AST_LOG_WARNING, "SQL Execute error!\n[%s]\n\n", sql);
+ else
+ SQLFreeHandle(SQL_HANDLE_STMT, stmt);
+ ast_odbc_release_obj(obj);
+ return;
}
/*!
@@ -5354,55 +5372,95 @@ plain_message:
static int add_email_attachment(FILE *p, struct ast_vm_user *vmu, char *format, char *attach, char *greeting_attachment, char *mailbox, char *bound, char *filename, int last, int msgnum)
{
- char tmpdir[256], newtmp[256];
- char fname[256];
- char tmpcmd[256];
- int tmpfd = -1;
- int soxstatus = 0;
+ char fname[PATH_MAX] = "";
+ char *file_to_delete = NULL, *dir_to_delete = NULL;
+ int res;
/* Eww. We want formats to tell us their own MIME type */
- char *ctype = (!strcasecmp(format, "ogg")) ? "application/" : "audio/x-";
+ char *mime_type = (!strcasecmp(format, "ogg")) ? "application/" : "audio/x-";
+
+ /* This 'while' loop will only execute once. We use it so that we can 'break' */
+ while (vmu->volgain < -.001 || vmu->volgain > .001) {
+ char tmpdir[PATH_MAX];
+ char sox_gain_tmpdir[PATH_MAX];
- if (vmu->volgain < -.001 || vmu->volgain > .001) {
create_dirpath(tmpdir, sizeof(tmpdir), vmu->context, vmu->mailbox, "tmp");
- snprintf(newtmp, sizeof(newtmp), "%s/XXXXXX", tmpdir);
- tmpfd = mkstemp(newtmp);
- chmod(newtmp, VOICEMAIL_FILE_MODE & ~my_umask);
- ast_debug(3, "newtmp: %s\n", newtmp);
- if (tmpfd > -1) {
- snprintf(tmpcmd, sizeof(tmpcmd), "sox -v %.4f %s.%s %s.%s", vmu->volgain, attach, format, newtmp, format);
- if ((soxstatus = ast_safe_system(tmpcmd)) == 0) {
- attach = newtmp;
- ast_debug(3, "VOLGAIN: Stored at: %s.%s - Level: %.4f - Mailbox: %s\n", attach, format, vmu->volgain, mailbox);
+
+ res = snprintf(sox_gain_tmpdir, sizeof(sox_gain_tmpdir), "%s/vm-gain-XXXXXX", tmpdir);
+ if (res >= sizeof(sox_gain_tmpdir)) {
+ ast_log(LOG_ERROR, "Failed to create temporary directory path %s: Out of buffer space\n", tmpdir);
+ break;
+ }
+
+ if (mkdtemp(sox_gain_tmpdir)) {
+ int soxstatus = 0;
+ char sox_gain_cmd[PATH_MAX];
+
+ ast_debug(3, "sox_gain_tmpdir: %s\n", sox_gain_tmpdir);
+
+ /* Save for later */
+ dir_to_delete = sox_gain_tmpdir;
+
+ res = snprintf(fname, sizeof(fname), "%s/output.%s", sox_gain_tmpdir, format);
+ if (res >= sizeof(fname)) {
+ ast_log(LOG_ERROR, "Failed to create filename buffer for %s/output.%s: Too long\n", sox_gain_tmpdir, format);
+ break;
+ }
+
+ res = snprintf(sox_gain_cmd, sizeof(sox_gain_cmd), "sox -v %.4f %s.%s %s",
+ vmu->volgain, attach, format, fname);
+ if (res >= sizeof(sox_gain_cmd)) {
+ ast_log(LOG_ERROR, "Failed to generate sox command, out of buffer space\n");
+ break;
+ }
+
+ soxstatus = ast_safe_system(sox_gain_cmd);
+ if (!soxstatus) {
+ /* Save for later */
+ file_to_delete = fname;
+ ast_debug(3, "VOLGAIN: Stored at: %s - Level: %.4f - Mailbox: %s\n", fname, vmu->volgain, mailbox);
} else {
- ast_log(LOG_WARNING, "Sox failed to re-encode %s.%s: %s (have you installed support for all sox file formats?)\n", attach, format,
- soxstatus == 1 ? "Problem with command line options" : "An error occurred during file processing");
+ ast_log(LOG_WARNING, "Sox failed to re-encode %s: %s (have you installed support for all sox file formats?)\n",
+ fname,
+ soxstatus == 1 ? "Problem with command line options" : "An error occurred during file processing");
ast_log(LOG_WARNING, "Voicemail attachment will have no volume gain.\n");
}
}
+
+ break;
}
+
+ if (!file_to_delete) {
+ res = snprintf(fname, sizeof(fname), "%s.%s", attach, format);
+ if (res >= sizeof(fname)) {
+ ast_log(LOG_ERROR, "Failed to create filename buffer for %s.%s: Too long\n", attach, format);
+ return -1;
+ }
+ }
+
fprintf(p, "--%s" ENDL, bound);
if (msgnum > -1)
- fprintf(p, "Content-Type: %s%s; name=\"%s\"" ENDL, ctype, format, filename);
+ fprintf(p, "Content-Type: %s%s; name=\"%s\"" ENDL, mime_type, format, filename);
else
- fprintf(p, "Content-Type: %s%s; name=\"%s.%s\"" ENDL, ctype, format, greeting_attachment, format);
+ fprintf(p, "Content-Type: %s%s; name=\"%s.%s\"" ENDL, mime_type, format, greeting_attachment, format);
fprintf(p, "Content-Transfer-Encoding: base64" ENDL);
fprintf(p, "Content-Description: Voicemail sound attachment." ENDL);
if (msgnum > -1)
fprintf(p, "Content-Disposition: attachment; filename=\"%s\"" ENDL ENDL, filename);
else
fprintf(p, "Content-Disposition: attachment; filename=\"%s.%s\"" ENDL ENDL, greeting_attachment, format);
- snprintf(fname, sizeof(fname), "%s.%s", attach, format);
base_encode(fname, p);
if (last)
fprintf(p, ENDL ENDL "--%s--" ENDL "." ENDL, bound);
- if (tmpfd > -1) {
- if (soxstatus == 0) {
- unlink(fname);
- }
- close(tmpfd);
- unlink(newtmp);
+
+ if (file_to_delete) {
+ unlink(file_to_delete);
}
+
+ if (dir_to_delete) {
+ rmdir(dir_to_delete);
+ }
+
return 0;
}
@@ -5662,17 +5720,48 @@ static void free_zone(struct vm_zone *z)
}
#ifdef ODBC_STORAGE
-static int inboxcount2(const char *mailbox, int *urgentmsgs, int *newmsgs, int *oldmsgs)
+
+static int count_messages_in_folder(struct odbc_obj *odbc, const char *context, const char *mailbox, const char *folder, int *messages)
{
- int x = -1;
int res;
- SQLHSTMT stmt = NULL;
char sql[PATH_MAX];
char rowdata[20];
+ SQLHSTMT stmt = NULL;
+ struct generic_prepare_struct gps = { .sql = sql, .argc = 0 };
+
+ if (!messages) {
+ return 0;
+ }
+
+ snprintf(sql, sizeof(sql), "SELECT COUNT(*) FROM %s WHERE dir = '%s%s/%s/%s'", odbc_table, VM_SPOOL_DIR, context, mailbox, folder);
+ if (!(stmt = ast_odbc_prepare_and_execute(odbc, generic_prepare, &gps))) {
+ ast_log(LOG_WARNING, "SQL Execute error!\n[%s]\n\n", sql);
+ return 1;
+ }
+ res = SQLFetch(stmt);
+ if (!SQL_SUCCEEDED(res)) {
+ ast_log(LOG_WARNING, "SQL Fetch error!\n[%s]\n\n", sql);
+ SQLFreeHandle(SQL_HANDLE_STMT, stmt);
+ return 1;
+ }
+ res = SQLGetData(stmt, 1, SQL_CHAR, rowdata, sizeof(rowdata), NULL);
+ if (!SQL_SUCCEEDED(res)) {
+ ast_log(LOG_WARNING, "SQL Get Data error!\n[%s]\n\n", sql);
+ SQLFreeHandle(SQL_HANDLE_STMT, stmt);
+ return 1;
+ }
+
+ *messages = atoi(rowdata);
+ SQLFreeHandle(SQL_HANDLE_STMT, stmt);
+
+ return 0;
+}
+
+static int inboxcount2(const char *mailbox, int *urgentmsgs, int *newmsgs, int *oldmsgs)
+{
char tmp[PATH_MAX] = "";
- struct odbc_obj *obj = NULL;
+ struct odbc_obj *obj;
char *context;
- struct generic_prepare_struct gps = { .sql = sql, .argc = 0 };
if (newmsgs)
*newmsgs = 0;
@@ -5714,87 +5803,28 @@ static int inboxcount2(const char *mailbox, int *urgentmsgs, int *newmsgs, int *
} else
context = "default";
- if ((obj = ast_odbc_request_obj(odbc_database, 0))) {
- do {
- if (newmsgs) {
- snprintf(sql, sizeof(sql), "SELECT COUNT(*) FROM %s WHERE dir = '%s%s/%s/%s'", odbc_table, VM_SPOOL_DIR, context, tmp, "INBOX");
- if (!(stmt = ast_odbc_prepare_and_execute(obj, generic_prepare, &gps))) {
- ast_log(AST_LOG_WARNING, "SQL Execute error!\n[%s]\n\n", sql);
- break;
- }
- res = SQLFetch(stmt);
- if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
- ast_log(AST_LOG_WARNING, "SQL Fetch error!\n[%s]\n\n", sql);
- break;
- }
- res = SQLGetData(stmt, 1, SQL_CHAR, rowdata, sizeof(rowdata), NULL);
- if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
- ast_log(AST_LOG_WARNING, "SQL Get Data error!\n[%s]\n\n", sql);
- break;
- }
- *newmsgs = atoi(rowdata);
- SQLFreeHandle (SQL_HANDLE_STMT, stmt);
- }
-
- if (oldmsgs) {
- snprintf(sql, sizeof(sql), "SELECT COUNT(*) FROM %s WHERE dir = '%s%s/%s/%s'", odbc_table, VM_SPOOL_DIR, context, tmp, "Old");
- if (!(stmt = ast_odbc_prepare_and_execute(obj, generic_prepare, &gps))) {
- ast_log(AST_LOG_WARNING, "SQL Execute error!\n[%s]\n\n", sql);
- break;
- }
- res = SQLFetch(stmt);
- if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
- ast_log(AST_LOG_WARNING, "SQL Fetch error!\n[%s]\n\n", sql);
- break;
- }
- res = SQLGetData(stmt, 1, SQL_CHAR, rowdata, sizeof(rowdata), NULL);
- if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
- ast_log(AST_LOG_WARNING, "SQL Get Data error!\n[%s]\n\n", sql);
- break;
- }
- SQLFreeHandle(SQL_HANDLE_STMT, stmt);
- *oldmsgs = atoi(rowdata);
- }
-
- if (urgentmsgs) {
- snprintf(sql, sizeof(sql), "SELECT COUNT(*) FROM %s WHERE dir = '%s%s/%s/%s'", odbc_table, VM_SPOOL_DIR, context, tmp, "Urgent");
- if (!(stmt = ast_odbc_prepare_and_execute(obj, generic_prepare, &gps))) {
- ast_log(LOG_WARNING, "SQL Execute error!\n[%s]\n\n", sql);
- break;
- }
- res = SQLFetch(stmt);
- if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
- ast_log(LOG_WARNING, "SQL Fetch error!\n[%s]\n\n", sql);
- break;
- }
- res = SQLGetData(stmt, 1, SQL_CHAR, rowdata, sizeof(rowdata), NULL);
- if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
- ast_log(LOG_WARNING, "SQL Get Data error!\n[%s]\n\n", sql);
- break;
- }
- *urgentmsgs = atoi(rowdata);
- }
-
- x = 0;
- } while (0);
- } else {
+ obj = ast_odbc_request_obj(odbc_database, 0);
+ if (!obj) {
ast_log(AST_LOG_WARNING, "Failed to obtain database object for '%s'!\n", odbc_database);
+ return -1;
}
- if (stmt) {
- SQLFreeHandle (SQL_HANDLE_STMT, stmt);
- }
- if (obj) {
- ast_odbc_release_obj(obj);
+ if (count_messages_in_folder(obj, context, tmp, "INBOX", newmsgs)
+ || count_messages_in_folder(obj, context, tmp, "Old", oldmsgs)
+ || count_messages_in_folder(obj, context, tmp, "Urgent", urgentmsgs)) {
+ ast_log(AST_LOG_WARNING, "Failed to obtain message count for mailbox %s@%s\n",
+ tmp, context);
}
- return x;
+
+ ast_odbc_release_obj(obj);
+ return 0;
}
/*!
* \brief Gets the number of messages that exist in a mailbox folder.
* \param mailbox_id
* \param folder
- *
+ *
* This method is used when ODBC backend is used.
* \return The number of messages in this mailbox folder (zero or more).
*/
@@ -5821,37 +5851,39 @@ static int messagecount(const char *mailbox_id, const char *folder)
}
obj = ast_odbc_request_obj(odbc_database, 0);
- if (obj) {
- if (!strcmp(folder, "INBOX")) {
- snprintf(sql, sizeof(sql), "SELECT COUNT(*) FROM %s WHERE dir = '%s%s/%s/INBOX' OR dir = '%s%s/%s/Urgent'", odbc_table, VM_SPOOL_DIR, context, mailbox, VM_SPOOL_DIR, context, mailbox);
- } else {
- snprintf(sql, sizeof(sql), "SELECT COUNT(*) FROM %s WHERE dir = '%s%s/%s/%s'", odbc_table, VM_SPOOL_DIR, context, mailbox, folder);
- }
- stmt = ast_odbc_prepare_and_execute(obj, generic_prepare, &gps);
- if (!stmt) {
- ast_log(AST_LOG_WARNING, "SQL Execute error!\n[%s]\n\n", sql);
- goto yuck;
- }
- res = SQLFetch(stmt);
- if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
- ast_log(AST_LOG_WARNING, "SQL Fetch error!\n[%s]\n\n", sql);
- SQLFreeHandle (SQL_HANDLE_STMT, stmt);
- goto yuck;
- }
- res = SQLGetData(stmt, 1, SQL_CHAR, rowdata, sizeof(rowdata), NULL);
- if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
- ast_log(AST_LOG_WARNING, "SQL Get Data error!\n[%s]\n\n", sql);
- SQLFreeHandle (SQL_HANDLE_STMT, stmt);
- goto yuck;
- }
- nummsgs = atoi(rowdata);
- SQLFreeHandle (SQL_HANDLE_STMT, stmt);
- } else
+ if (!obj) {
ast_log(AST_LOG_WARNING, "Failed to obtain database object for '%s'!\n", odbc_database);
+ return 0;
+ }
+
+ if (!strcmp(folder, "INBOX")) {
+ snprintf(sql, sizeof(sql), "SELECT COUNT(*) FROM %s WHERE dir = '%s%s/%s/INBOX' OR dir = '%s%s/%s/Urgent'", odbc_table, VM_SPOOL_DIR, context, mailbox, VM_SPOOL_DIR, context, mailbox);
+ } else {
+ snprintf(sql, sizeof(sql), "SELECT COUNT(*) FROM %s WHERE dir = '%s%s/%s/%s'", odbc_table, VM_SPOOL_DIR, context, mailbox, folder);
+ }
+
+ stmt = ast_odbc_prepare_and_execute(obj, generic_prepare, &gps);
+ if (!stmt) {
+ ast_log(AST_LOG_WARNING, "SQL Execute error!\n[%s]\n\n", sql);
+ goto bail;
+ }
+ res = SQLFetch(stmt);
+ if (!SQL_SUCCEEDED(res)) {
+ ast_log(AST_LOG_WARNING, "SQL Fetch error!\n[%s]\n\n", sql);
+ goto bail_with_handle;
+ }
+ res = SQLGetData(stmt, 1, SQL_CHAR, rowdata, sizeof(rowdata), NULL);
+ if (!SQL_SUCCEEDED(res)) {
+ ast_log(AST_LOG_WARNING, "SQL Get Data error!\n[%s]\n\n", sql);
+ goto bail_with_handle;
+ }
+ nummsgs = atoi(rowdata);
-yuck:
- if (obj)
- ast_odbc_release_obj(obj);
+bail_with_handle:
+ SQLFreeHandle(SQL_HANDLE_STMT, stmt);
+
+bail:
+ ast_odbc_release_obj(obj);
return nummsgs;
}
@@ -6498,6 +6530,7 @@ static int leave_voicemail(struct ast_channel *chan, char *ext, struct leave_vm_
int ausemacro = 0;
int ousemacro = 0;
int ouseexten = 0;
+ int greeting_only = 0;
char tmpdur[16];
char priority[16];
char origtime[16];
@@ -6557,6 +6590,13 @@ static int leave_voicemail(struct ast_channel *chan, char *ext, struct leave_vm_
ast_free(tmp);
return res;
}
+
+ /* If maxmsg is zero, act as a "greetings only" voicemail: Exit successfully without recording */
+ if (vmu->maxmsg == 0) {
+ greeting_only = 1;
+ ast_set_flag(options, OPT_SILENT);
+ }
+
/* Setup pre-file if appropriate */
if (strcmp(vmu->context, "default"))
snprintf(ext_context, sizeof(ext_context), "%s@%s", ext, vmu->context);
@@ -6681,12 +6721,6 @@ static int leave_voicemail(struct ast_channel *chan, char *ext, struct leave_vm_
ast_set_flag(options, OPT_SILENT);
res = 0;
}
- /* If maxmsg is zero, act as a "greetings only" voicemail: Exit successfully without recording */
- if (vmu->maxmsg == 0) {
- ast_debug(3, "Greetings only VM (maxmsg=0), Skipping voicemail recording\n");
- pbx_builtin_setvar_helper(chan, "VMSTATUS", "SUCCESS");
- goto leave_vm_out;
- }
if (!res && !ast_test_flag(options, OPT_SILENT)) {
res = ast_stream_and_wait(chan, INTRO, ecodes);
if (res == '#') {
@@ -6742,6 +6776,13 @@ static int leave_voicemail(struct ast_channel *chan, char *ext, struct leave_vm_
return res;
}
+ if (greeting_only) {
+ ast_debug(3, "Greetings only VM (maxmsg=0), Skipping voicemail recording\n");
+ pbx_builtin_setvar_helper(chan, "VMSTATUS", "SUCCESS");
+ res = 0;
+ goto leave_vm_out;
+ }
+
if (res < 0) {
free_user(vmu);
pbx_builtin_setvar_helper(chan, "VMSTATUS", "FAILED");
@@ -7923,7 +7964,7 @@ static int vm_forwardoptions(struct ast_channel *chan, struct ast_vm_user *vmu,
*duration += prepend_duration;
msg_cat = ast_category_get(msg_cfg, "message", NULL);
- snprintf(duration_buf, 11, "%ld", *duration);
+ snprintf(duration_buf, sizeof(duration_buf), "%ld", *duration);
if (!ast_variable_update(msg_cat, "duration", duration_buf, NULL, 0)) {
ast_config_text_file_save(textfile, msg_cfg, "app_voicemail");
}
@@ -8693,7 +8734,7 @@ static int play_message_duration(struct ast_channel *chan, struct vm_state *vms,
static int play_message(struct ast_channel *chan, struct ast_vm_user *vmu, struct vm_state *vms)
{
int res = 0;
- char filename[256], *cid;
+ char filename[PATH_MAX], *cid;
const char *origtime, *context, *category, *duration, *flag;
struct ast_config *msg_cfg;
struct ast_flags config_flags = { CONFIG_FLAG_NOCACHE };
@@ -10995,7 +11036,7 @@ static int vm_authenticate(struct ast_channel *chan, char *mailbox, int mailbox_
int skipuser, int max_logins, int silent)
{
int useadsi = 0, valid = 0, logretries = 0;
- char password[AST_MAX_EXTENSION]="", *passptr;
+ char password[AST_MAX_EXTENSION], *passptr;
struct ast_vm_user vmus, *vmu = NULL;
/* If ADSI is supported, setup login screen */
@@ -11037,7 +11078,8 @@ static int vm_authenticate(struct ast_channel *chan, char *mailbox, int mailbox_
adsi_password(chan);
if (!ast_strlen_zero(prefix)) {
- char fullusername[80] = "";
+ char fullusername[80];
+
ast_copy_string(fullusername, prefix, sizeof(fullusername));
strncat(fullusername, mailbox, sizeof(fullusername) - 1 - strlen(fullusername));
ast_copy_string(mailbox, fullusername, mailbox_size);
@@ -11095,6 +11137,10 @@ static int vm_authenticate(struct ast_channel *chan, char *mailbox, int mailbox_
free_user(vmu);
return -1;
}
+ if (ast_waitstream(chan, "")) { /* Channel is hung up */
+ free_user(vmu);
+ return -1;
+ }
} else {
if (useadsi)
adsi_login(chan);
@@ -11104,10 +11150,6 @@ static int vm_authenticate(struct ast_channel *chan, char *mailbox, int mailbox_
return -1;
}
}
- if (ast_waitstream(chan, "")) { /* Channel is hung up */
- free_user(vmu);
- return -1;
- }
}
}
if (!valid && (logretries >= max_logins)) {
@@ -12226,6 +12268,9 @@ static int append_mailbox(const char *context, const char *box, const char *data
strcat(mailbox_full, context);
inboxcount2(mailbox_full, &urgent, &new, &old);
+#ifdef IMAP_STORAGE
+ imap_logout(mailbox_full);
+#endif
queue_mwi_event(NULL, mailbox_full, urgent, new, old);
return 0;
@@ -12978,6 +13023,12 @@ static void poll_subscribed_mailbox(struct mwi_sub *mwi_sub)
inboxcount2(mwi_sub->mailbox, &urgent, &new, &old);
+#ifdef IMAP_STORAGE
+ if (imap_poll_logout) {
+ imap_logout(mwi_sub->mailbox);
+ }
+#endif
+
if (urgent != mwi_sub->old_urgent || new != mwi_sub->old_new || old != mwi_sub->old_old) {
mwi_sub->old_urgent = urgent;
mwi_sub->old_new = new;
@@ -13029,6 +13080,55 @@ static void mwi_sub_destroy(struct mwi_sub *mwi_sub)
ast_free(mwi_sub);
}
+#ifdef IMAP_STORAGE
+static void imap_logout(const char *mailbox_id)
+{
+ char *context;
+ char *mailbox;
+ struct ast_vm_user vmus;
+ RAII_VAR(struct ast_vm_user *, vmu, NULL, free_user);
+ struct vm_state *vms = NULL;
+
+ if (ast_strlen_zero(mailbox_id)
+ || separate_mailbox(ast_strdupa(mailbox_id), &mailbox, &context)) {
+ return;
+ }
+
+ memset(&vmus, 0, sizeof(vmus));
+
+ if (!(vmu = find_user(&vmus, context, mailbox)) || vmu->imapuser[0] == '\0') {
+ return;
+ }
+
+ vms = get_vm_state_by_imapuser(vmu->imapuser, 0);
+ if (!vms) {
+ vms = get_vm_state_by_mailbox(mailbox, context, 0);
+ }
+ if (!vms) {
+ return;
+ }
+
+ ast_mutex_lock(&vms->lock);
+ vms->mailstream = mail_close(vms->mailstream);
+ ast_mutex_unlock(&vms->lock);
+
+ vmstate_delete(vms);
+}
+
+static void imap_close_subscribed_mailboxes(void)
+{
+ struct mwi_sub *mwi_sub;
+
+ AST_RWLIST_RDLOCK(&mwi_subs);
+ AST_RWLIST_TRAVERSE(&mwi_subs, mwi_sub, entry) {
+ if (!ast_strlen_zero(mwi_sub->mailbox)) {
+ imap_logout(mwi_sub->mailbox);
+ }
+ }
+ AST_RWLIST_UNLOCK(&mwi_subs);
+}
+#endif
+
static int handle_unsubscribe(void *datap)
{
struct mwi_sub *mwi_sub;
@@ -13040,6 +13140,9 @@ static int handle_unsubscribe(void *datap)
AST_LIST_REMOVE_CURRENT(entry);
/* Don't break here since a duplicate uniqueid
* may have been added as a result of a cache dump. */
+#ifdef IMAP_STORAGE
+ imap_logout(mwi_sub->mailbox);
+#endif
mwi_sub_destroy(mwi_sub);
}
}
@@ -13477,7 +13580,11 @@ static int actual_load_config(int reload, struct ast_config *cfg, struct ast_con
strcpy(listen_control_restart_key, DEFAULT_LISTEN_CONTROL_RESTART_KEY);
strcpy(listen_control_stop_key, DEFAULT_LISTEN_CONTROL_STOP_KEY);
- /* Free all the users structure */
+#ifdef IMAP_STORAGE
+ imap_close_subscribed_mailboxes();
+#endif
+
+ /* Free all the users structure */
free_vm_users();
/* Free all the zones structure */
@@ -13642,6 +13749,11 @@ static int actual_load_config(int reload, struct ast_config *cfg, struct ast_con
} else {
ast_copy_string(greetingfolder, imapfolder, sizeof(greetingfolder));
}
+ if ((val = ast_variable_retrieve(cfg, "general", "imap_poll_logout"))) {
+ imap_poll_logout = ast_true(val);
+ } else {
+ imap_poll_logout = 0;
+ }
/* There is some very unorthodox casting done here. This is due
* to the way c-client handles the argument passed in. It expects a
@@ -14871,6 +14983,9 @@ static int unload_module(void)
ast_unload_realtime("voicemail");
ast_unload_realtime("voicemail_data");
+#ifdef IMAP_STORAGE
+ imap_close_subscribed_mailboxes();
+#endif
free_vm_users();
free_vm_zones();
return res;
diff --git a/bridges/bridge_native_rtp.c b/bridges/bridge_native_rtp.c
index a80ef4c5a..58af24c43 100644
--- a/bridges/bridge_native_rtp.c
+++ b/bridges/bridge_native_rtp.c
@@ -46,76 +46,214 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
#include "asterisk/frame.h"
#include "asterisk/rtp_engine.h"
-/*! \brief Internal structure which contains information about bridged RTP channels */
-struct native_rtp_bridge_data {
+/*! \brief Internal structure which contains bridged RTP channel hook data */
+struct native_rtp_framehook_data {
/*! \brief Framehook used to intercept certain control frames */
int id;
/*! \brief Set when this framehook has been detached */
unsigned int detached;
};
-/*! \brief Internal helper function which gets all RTP information (glue and instances) relating to the given channels */
-static enum ast_rtp_glue_result native_rtp_bridge_get(struct ast_channel *c0, struct ast_channel *c1, struct ast_rtp_glue **glue0,
- struct ast_rtp_glue **glue1, struct ast_rtp_instance **instance0, struct ast_rtp_instance **instance1,
- struct ast_rtp_instance **vinstance0, struct ast_rtp_instance **vinstance1)
+struct rtp_glue_stream {
+ /*! \brief RTP instance */
+ struct ast_rtp_instance *instance;
+ /*! \brief glue result */
+ enum ast_rtp_glue_result result;
+};
+
+struct rtp_glue_data {
+ /*!
+ * \brief glue callbacks
+ *
+ * \note The glue data is considered valid if cb is not NULL.
+ */
+ struct ast_rtp_glue *cb;
+ struct rtp_glue_stream audio;
+ struct rtp_glue_stream video;
+ /*! Combined glue result of both bridge channels. */
+ enum ast_rtp_glue_result result;
+};
+
+/*! \brief Internal structure which contains instance information about bridged RTP channels */
+struct native_rtp_bridge_channel_data {
+ /*! \brief Channel's hook data */
+ struct native_rtp_framehook_data *hook_data;
+ /*!
+ * \brief Glue callbacks to bring remote channel streams back to Asterisk.
+ * \note NULL if channel streams are local.
+ */
+ struct ast_rtp_glue *remote_cb;
+ /*! \brief Channel's cached RTP glue information */
+ struct rtp_glue_data glue;
+};
+
+static void rtp_glue_data_init(struct rtp_glue_data *glue)
{
- enum ast_rtp_glue_result audio_glue0_res;
- enum ast_rtp_glue_result video_glue0_res;
- enum ast_rtp_glue_result audio_glue1_res;
- enum ast_rtp_glue_result video_glue1_res;
+ glue->cb = NULL;
+ glue->audio.instance = NULL;
+ glue->audio.result = AST_RTP_GLUE_RESULT_FORBID;
+ glue->video.instance = NULL;
+ glue->video.result = AST_RTP_GLUE_RESULT_FORBID;
+ glue->result = AST_RTP_GLUE_RESULT_FORBID;
+}
- if (!(*glue0 = ast_rtp_instance_get_glue(ast_channel_tech(c0)->type)) ||
- !(*glue1 = ast_rtp_instance_get_glue(ast_channel_tech(c1)->type))) {
- return AST_RTP_GLUE_RESULT_FORBID;
+static void rtp_glue_data_destroy(struct rtp_glue_data *glue)
+{
+ if (!glue) {
+ return;
}
+ ao2_cleanup(glue->audio.instance);
+ ao2_cleanup(glue->video.instance);
+}
- audio_glue0_res = (*glue0)->get_rtp_info(c0, instance0);
- video_glue0_res = (*glue0)->get_vrtp_info ? (*glue0)->get_vrtp_info(c0, vinstance0) : AST_RTP_GLUE_RESULT_FORBID;
+static void rtp_glue_data_reset(struct rtp_glue_data *glue)
+{
+ rtp_glue_data_destroy(glue);
+ rtp_glue_data_init(glue);
+}
+
+static void native_rtp_bridge_channel_data_free(struct native_rtp_bridge_channel_data *data)
+{
+ ast_debug(2, "Destroying channel tech_pvt data %p\n", data);
+
+ /*
+ * hook_data will probably already have been unreferenced by the framehook detach
+ * and the pointer set to null.
+ */
+ ao2_cleanup(data->hook_data);
- audio_glue1_res = (*glue1)->get_rtp_info(c1, instance1);
- video_glue1_res = (*glue1)->get_vrtp_info ? (*glue1)->get_vrtp_info(c1, vinstance1) : AST_RTP_GLUE_RESULT_FORBID;
+ rtp_glue_data_reset(&data->glue);
+ ast_free(data);
+}
+
+static struct native_rtp_bridge_channel_data *native_rtp_bridge_channel_data_alloc(void)
+{
+ struct native_rtp_bridge_channel_data *data;
+
+ data = ast_calloc(1, sizeof(*data));
+ if (data) {
+ rtp_glue_data_init(&data->glue);
+ }
+ return data;
+}
+
+/*!
+ * \internal
+ * \brief Helper function which gets all RTP information (glue and instances) relating to the given channels
+ *
+ * \retval 0 on success.
+ * \retval -1 on error.
+ */
+static int rtp_glue_data_get(struct ast_channel *c0, struct rtp_glue_data *glue0,
+ struct ast_channel *c1, struct rtp_glue_data *glue1)
+{
+ struct ast_rtp_glue *cb0;
+ struct ast_rtp_glue *cb1;
+ enum ast_rtp_glue_result combined_result;
+
+ cb0 = ast_rtp_instance_get_glue(ast_channel_tech(c0)->type);
+ cb1 = ast_rtp_instance_get_glue(ast_channel_tech(c1)->type);
+ if (!cb0 || !cb1) {
+ /* One or both channels doesn't have any RTP glue registered. */
+ return -1;
+ }
+
+ /* The glue callbacks bump the RTP instance refcounts for us. */
+
+ glue0->cb = cb0;
+ glue0->audio.result = cb0->get_rtp_info(c0, &glue0->audio.instance);
+ glue0->video.result = cb0->get_vrtp_info
+ ? cb0->get_vrtp_info(c0, &glue0->video.instance) : AST_RTP_GLUE_RESULT_FORBID;
+
+ glue1->cb = cb1;
+ glue1->audio.result = cb1->get_rtp_info(c1, &glue1->audio.instance);
+ glue1->video.result = cb1->get_vrtp_info
+ ? cb1->get_vrtp_info(c1, &glue1->video.instance) : AST_RTP_GLUE_RESULT_FORBID;
+
+ /*
+ * Now determine the combined glue result.
+ */
/* Apply any limitations on direct media bridging that may be present */
- if (audio_glue0_res == audio_glue1_res && audio_glue1_res == AST_RTP_GLUE_RESULT_REMOTE) {
- if ((*glue0)->allow_rtp_remote && !((*glue0)->allow_rtp_remote(c0, *instance1))) {
+ if (glue0->audio.result == glue1->audio.result && glue1->audio.result == AST_RTP_GLUE_RESULT_REMOTE) {
+ if (glue0->cb->allow_rtp_remote && !glue0->cb->allow_rtp_remote(c0, glue1->audio.instance)) {
/* If the allow_rtp_remote indicates that remote isn't allowed, revert to local bridge */
- audio_glue0_res = audio_glue1_res = AST_RTP_GLUE_RESULT_LOCAL;
- } else if ((*glue1)->allow_rtp_remote && !((*glue1)->allow_rtp_remote(c1, *instance0))) {
- audio_glue0_res = audio_glue1_res = AST_RTP_GLUE_RESULT_LOCAL;
+ glue0->audio.result = glue1->audio.result = AST_RTP_GLUE_RESULT_LOCAL;
+ } else if (glue1->cb->allow_rtp_remote && !glue1->cb->allow_rtp_remote(c1, glue0->audio.instance)) {
+ glue0->audio.result = glue1->audio.result = AST_RTP_GLUE_RESULT_LOCAL;
}
}
- if (video_glue0_res == video_glue1_res && video_glue1_res == AST_RTP_GLUE_RESULT_REMOTE) {
- if ((*glue0)->allow_vrtp_remote && !((*glue0)->allow_vrtp_remote(c0, *instance1))) {
- /* if the allow_vrtp_remote indicates that remote isn't allowed, revert to local bridge */
- video_glue0_res = video_glue1_res = AST_RTP_GLUE_RESULT_LOCAL;
- } else if ((*glue1)->allow_vrtp_remote && !((*glue1)->allow_vrtp_remote(c1, *instance0))) {
- video_glue0_res = video_glue1_res = AST_RTP_GLUE_RESULT_LOCAL;
+ if (glue0->video.result == glue1->video.result && glue1->video.result == AST_RTP_GLUE_RESULT_REMOTE) {
+ if (glue0->cb->allow_vrtp_remote && !glue0->cb->allow_vrtp_remote(c0, glue1->video.instance)) {
+ /* If the allow_vrtp_remote indicates that remote isn't allowed, revert to local bridge */
+ glue0->video.result = glue1->video.result = AST_RTP_GLUE_RESULT_LOCAL;
+ } else if (glue1->cb->allow_vrtp_remote && !glue1->cb->allow_vrtp_remote(c1, glue0->video.instance)) {
+ glue0->video.result = glue1->video.result = AST_RTP_GLUE_RESULT_LOCAL;
}
}
/* If we are carrying video, and both sides are not going to remotely bridge... fail the native bridge */
- if (video_glue0_res != AST_RTP_GLUE_RESULT_FORBID
- && (audio_glue0_res != AST_RTP_GLUE_RESULT_REMOTE
- || video_glue0_res != AST_RTP_GLUE_RESULT_REMOTE)) {
- audio_glue0_res = AST_RTP_GLUE_RESULT_FORBID;
+ if (glue0->video.result != AST_RTP_GLUE_RESULT_FORBID
+ && (glue0->audio.result != AST_RTP_GLUE_RESULT_REMOTE
+ || glue0->video.result != AST_RTP_GLUE_RESULT_REMOTE)) {
+ glue0->audio.result = AST_RTP_GLUE_RESULT_FORBID;
}
- if (video_glue1_res != AST_RTP_GLUE_RESULT_FORBID
- && (audio_glue1_res != AST_RTP_GLUE_RESULT_REMOTE
- || video_glue1_res != AST_RTP_GLUE_RESULT_REMOTE)) {
- audio_glue1_res = AST_RTP_GLUE_RESULT_FORBID;
+ if (glue1->video.result != AST_RTP_GLUE_RESULT_FORBID
+ && (glue1->audio.result != AST_RTP_GLUE_RESULT_REMOTE
+ || glue1->video.result != AST_RTP_GLUE_RESULT_REMOTE)) {
+ glue1->audio.result = AST_RTP_GLUE_RESULT_FORBID;
}
/* The order of preference is: forbid, local, and remote. */
- if (audio_glue0_res == AST_RTP_GLUE_RESULT_FORBID ||
- audio_glue1_res == AST_RTP_GLUE_RESULT_FORBID) {
+ if (glue0->audio.result == AST_RTP_GLUE_RESULT_FORBID
+ || glue1->audio.result == AST_RTP_GLUE_RESULT_FORBID) {
/* If any sort of bridge is forbidden just completely bail out and go back to generic bridging */
- return AST_RTP_GLUE_RESULT_FORBID;
- } else if (audio_glue0_res == AST_RTP_GLUE_RESULT_LOCAL ||
- audio_glue1_res == AST_RTP_GLUE_RESULT_LOCAL) {
- return AST_RTP_GLUE_RESULT_LOCAL;
+ combined_result = AST_RTP_GLUE_RESULT_FORBID;
+ } else if (glue0->audio.result == AST_RTP_GLUE_RESULT_LOCAL
+ || glue1->audio.result == AST_RTP_GLUE_RESULT_LOCAL) {
+ combined_result = AST_RTP_GLUE_RESULT_LOCAL;
} else {
- return AST_RTP_GLUE_RESULT_REMOTE;
+ combined_result = AST_RTP_GLUE_RESULT_REMOTE;
+ }
+ glue0->result = combined_result;
+ glue1->result = combined_result;
+
+ return 0;
+}
+
+/*!
+ * \internal
+ * \brief Get the current RTP native bridge combined glue result.
+ * \since 15.0.0
+ *
+ * \param c0 First bridge channel
+ * \param c1 Second bridge channel
+ *
+ * \note Both channels must be locked when calling this function.
+ *
+ * \return Current combined glue result.
+ */
+static enum ast_rtp_glue_result rtp_glue_get_current_combined_result(struct ast_channel *c0,
+ struct ast_channel *c1)
+{
+ struct rtp_glue_data glue_a;
+ struct rtp_glue_data glue_b;
+ struct rtp_glue_data *glue0;
+ struct rtp_glue_data *glue1;
+ enum ast_rtp_glue_result combined_result;
+
+ rtp_glue_data_init(&glue_a);
+ glue0 = &glue_a;
+ rtp_glue_data_init(&glue_b);
+ glue1 = &glue_b;
+ if (rtp_glue_data_get(c0, glue0, c1, glue1)) {
+ return AST_RTP_GLUE_RESULT_FORBID;
}
+
+ combined_result = glue0->result;
+ rtp_glue_data_destroy(glue0);
+ rtp_glue_data_destroy(glue1);
+ return combined_result;
}
/*!
@@ -131,52 +269,91 @@ static void native_rtp_bridge_start(struct ast_bridge *bridge, struct ast_channe
{
struct ast_bridge_channel *bc0 = AST_LIST_FIRST(&bridge->channels);
struct ast_bridge_channel *bc1 = AST_LIST_LAST(&bridge->channels);
- enum ast_rtp_glue_result native_type = AST_RTP_GLUE_RESULT_FORBID;
- struct ast_rtp_glue *glue0, *glue1;
- RAII_VAR(struct ast_rtp_instance *, instance0, NULL, ao2_cleanup);
- RAII_VAR(struct ast_rtp_instance *, instance1, NULL, ao2_cleanup);
- RAII_VAR(struct ast_rtp_instance *, vinstance0, NULL, ao2_cleanup);
- RAII_VAR(struct ast_rtp_instance *, vinstance1, NULL, ao2_cleanup);
- RAII_VAR(struct ast_rtp_instance *, tinstance0, NULL, ao2_cleanup);
- RAII_VAR(struct ast_rtp_instance *, tinstance1, NULL, ao2_cleanup);
- RAII_VAR(struct ast_format_cap *, cap0, ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT), ao2_cleanup);
- RAII_VAR(struct ast_format_cap *, cap1, ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT), ao2_cleanup);
+ struct native_rtp_bridge_channel_data *data0;
+ struct native_rtp_bridge_channel_data *data1;
+ struct rtp_glue_data *glue0;
+ struct rtp_glue_data *glue1;
+ struct ast_format_cap *cap0;
+ struct ast_format_cap *cap1;
+ enum ast_rtp_glue_result native_type;
if (bc0 == bc1) {
return;
}
+ data0 = bc0->tech_pvt;
+ data1 = bc1->tech_pvt;
+ if (!data0 || !data1) {
+ /* Not all channels are joined with the bridge tech yet */
+ return;
+ }
+ glue0 = &data0->glue;
+ glue1 = &data1->glue;
ast_channel_lock_both(bc0->chan, bc1->chan);
- if (!bc0->suspended && !bc1->suspended) {
- native_type = native_rtp_bridge_get(bc0->chan, bc1->chan, &glue0, &glue1, &instance0, &instance1, &vinstance0, &vinstance1);
+
+ if (!glue0->cb || !glue1->cb) {
+ /*
+ * Somebody doesn't have glue data so the bridge isn't running
+ *
+ * Actually neither side should have glue data.
+ */
+ ast_assert(!glue0->cb && !glue1->cb);
+
+ if (rtp_glue_data_get(bc0->chan, glue0, bc1->chan, glue1)) {
+ /*
+ * This might happen if one of the channels got masqueraded
+ * at a critical time. It's a bit of a stretch even then
+ * since the channel is in a bridge.
+ */
+ goto done;
+ }
}
+ ast_debug(2, "Bridge '%s'. Tech starting '%s' and '%s' with target '%s'\n",
+ bridge->uniqueid, ast_channel_name(bc0->chan), ast_channel_name(bc1->chan),
+ target ? ast_channel_name(target) : "none");
+
+ native_type = glue0->result;
+
switch (native_type) {
case AST_RTP_GLUE_RESULT_LOCAL:
- if (ast_rtp_instance_get_engine(instance0)->local_bridge) {
- ast_rtp_instance_get_engine(instance0)->local_bridge(instance0, instance1);
+ if (ast_rtp_instance_get_engine(glue0->audio.instance)->local_bridge) {
+ ast_rtp_instance_get_engine(glue0->audio.instance)->local_bridge(glue0->audio.instance, glue1->audio.instance);
}
- if (ast_rtp_instance_get_engine(instance1)->local_bridge) {
- ast_rtp_instance_get_engine(instance1)->local_bridge(instance1, instance0);
+ if (ast_rtp_instance_get_engine(glue1->audio.instance)->local_bridge) {
+ ast_rtp_instance_get_engine(glue1->audio.instance)->local_bridge(glue1->audio.instance, glue0->audio.instance);
}
- ast_rtp_instance_set_bridged(instance0, instance1);
- ast_rtp_instance_set_bridged(instance1, instance0);
+ ast_rtp_instance_set_bridged(glue0->audio.instance, glue1->audio.instance);
+ ast_rtp_instance_set_bridged(glue1->audio.instance, glue0->audio.instance);
ast_verb(4, "Locally RTP bridged '%s' and '%s' in stack\n",
ast_channel_name(bc0->chan), ast_channel_name(bc1->chan));
break;
-
case AST_RTP_GLUE_RESULT_REMOTE:
- if (glue0->get_codec) {
- glue0->get_codec(bc0->chan, cap0);
+ cap0 = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT);
+ cap1 = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT);
+ if (!cap0 || !cap1) {
+ ao2_cleanup(cap0);
+ ao2_cleanup(cap1);
+ break;
+ }
+
+ if (glue0->cb->get_codec) {
+ glue0->cb->get_codec(bc0->chan, cap0);
}
- if (glue1->get_codec) {
- glue1->get_codec(bc1->chan, cap1);
+ if (glue1->cb->get_codec) {
+ glue1->cb->get_codec(bc1->chan, cap1);
}
- /* If we have a target, it's the channel that received the UNHOLD or UPDATE_RTP_PEER frame and was told to resume */
+ /*
+ * If we have a target, it's the channel that received the UNHOLD or
+ * UPDATE_RTP_PEER frame and was told to resume
+ */
if (!target) {
- glue0->update_peer(bc0->chan, instance1, vinstance1, tinstance1, cap1, 0);
- glue1->update_peer(bc1->chan, instance0, vinstance0, tinstance0, cap0, 0);
+ /* Send both channels to remote */
+ data0->remote_cb = glue0->cb;
+ data1->remote_cb = glue1->cb;
+ glue0->cb->update_peer(bc0->chan, glue1->audio.instance, glue1->video.instance, NULL, cap1, 0);
+ glue1->cb->update_peer(bc1->chan, glue0->audio.instance, glue0->video.instance, NULL, cap0, 0);
ast_verb(4, "Remotely bridged '%s' and '%s' - media will flow directly between them\n",
ast_channel_name(bc0->chan), ast_channel_name(bc1->chan));
} else {
@@ -186,51 +363,121 @@ static void native_rtp_bridge_start(struct ast_bridge *bridge, struct ast_channe
* already set up to handle the new media path or will have its own set of updates independent
* of this pass.
*/
+ ast_debug(2, "Bridge '%s'. Sending '%s' back to remote\n",
+ bridge->uniqueid, ast_channel_name(target));
if (bc0->chan == target) {
- glue0->update_peer(bc0->chan, instance1, vinstance1, tinstance1, cap1, 0);
+ data0->remote_cb = glue0->cb;
+ glue0->cb->update_peer(bc0->chan, glue1->audio.instance, glue1->video.instance, NULL, cap1, 0);
} else {
- glue1->update_peer(bc1->chan, instance0, vinstance0, tinstance0, cap0, 0);
+ data1->remote_cb = glue1->cb;
+ glue1->cb->update_peer(bc1->chan, glue0->audio.instance, glue0->video.instance, NULL, cap0, 0);
}
}
+
+ ao2_cleanup(cap0);
+ ao2_cleanup(cap1);
break;
case AST_RTP_GLUE_RESULT_FORBID:
break;
}
+ if (native_type != AST_RTP_GLUE_RESULT_REMOTE) {
+ /* Bring any remaining channels back to us. */
+ if (data0->remote_cb) {
+ ast_debug(2, "Bridge '%s'. Bringing back '%s' to us\n",
+ bridge->uniqueid, ast_channel_name(bc0->chan));
+ data0->remote_cb->update_peer(bc0->chan, NULL, NULL, NULL, NULL, 0);
+ data0->remote_cb = NULL;
+ }
+ if (data1->remote_cb) {
+ ast_debug(2, "Bridge '%s'. Bringing back '%s' to us\n",
+ bridge->uniqueid, ast_channel_name(bc1->chan));
+ data1->remote_cb->update_peer(bc1->chan, NULL, NULL, NULL, NULL, 0);
+ data1->remote_cb = NULL;
+ }
+ }
+
+done:
ast_channel_unlock(bc0->chan);
ast_channel_unlock(bc1->chan);
}
+/*!
+ * \internal
+ * \brief Stop native RTP bridging of two channels
+ *
+ * \param bridge The bridge that had native RTP bridging happening on it
+ * \param target If remote RTP bridging, the channel that is held.
+ *
+ * \note The first channel to leave the bridge triggers the cleanup for both channels
+ */
static void native_rtp_bridge_stop(struct ast_bridge *bridge, struct ast_channel *target)
{
struct ast_bridge_channel *bc0 = AST_LIST_FIRST(&bridge->channels);
struct ast_bridge_channel *bc1 = AST_LIST_LAST(&bridge->channels);
- enum ast_rtp_glue_result native_type;
- struct ast_rtp_glue *glue0, *glue1 = NULL;
- RAII_VAR(struct ast_rtp_instance *, instance0, NULL, ao2_cleanup);
- RAII_VAR(struct ast_rtp_instance *, instance1, NULL, ao2_cleanup);
- RAII_VAR(struct ast_rtp_instance *, vinstance0, NULL, ao2_cleanup);
- RAII_VAR(struct ast_rtp_instance *, vinstance1, NULL, ao2_cleanup);
+ struct native_rtp_bridge_channel_data *data0;
+ struct native_rtp_bridge_channel_data *data1;
+ struct rtp_glue_data *glue0;
+ struct rtp_glue_data *glue1;
if (bc0 == bc1) {
return;
}
+ data0 = bc0->tech_pvt;
+ data1 = bc1->tech_pvt;
+ if (!data0 || !data1) {
+ /* Not all channels are joined with the bridge tech */
+ return;
+ }
+ glue0 = &data0->glue;
+ glue1 = &data1->glue;
+
+ ast_debug(2, "Bridge '%s'. Tech stopping '%s' and '%s' with target '%s'\n",
+ bridge->uniqueid, ast_channel_name(bc0->chan), ast_channel_name(bc1->chan),
+ target ? ast_channel_name(target) : "none");
+
+ if (!glue0->cb || !glue1->cb) {
+ /*
+ * Somebody doesn't have glue data so the bridge isn't running
+ *
+ * Actually neither side should have glue data.
+ */
+ ast_assert(!glue0->cb && !glue1->cb);
+ /* At most one channel can be left at the remote endpoint here. */
+ ast_assert(!data0->remote_cb || !data1->remote_cb);
+
+ /* Bring selected channel streams back to us */
+ if (data0->remote_cb && (!target || target == bc0->chan)) {
+ ast_channel_lock(bc0->chan);
+ ast_debug(2, "Bridge '%s'. Bringing back '%s' to us\n",
+ bridge->uniqueid, ast_channel_name(bc0->chan));
+ data0->remote_cb->update_peer(bc0->chan, NULL, NULL, NULL, NULL, 0);
+ data0->remote_cb = NULL;
+ ast_channel_unlock(bc0->chan);
+ }
+ if (data1->remote_cb && (!target || target == bc1->chan)) {
+ ast_channel_lock(bc1->chan);
+ ast_debug(2, "Bridge '%s'. Bringing back '%s' to us\n",
+ bridge->uniqueid, ast_channel_name(bc1->chan));
+ data1->remote_cb->update_peer(bc1->chan, NULL, NULL, NULL, NULL, 0);
+ data1->remote_cb = NULL;
+ ast_channel_unlock(bc1->chan);
+ }
+ return;
+ }
ast_channel_lock_both(bc0->chan, bc1->chan);
- native_type = native_rtp_bridge_get(bc0->chan, bc1->chan, &glue0, &glue1, &instance0, &instance1, &vinstance0, &vinstance1);
- switch (native_type) {
+ switch (glue0->result) {
case AST_RTP_GLUE_RESULT_LOCAL:
- if (ast_rtp_instance_get_engine(instance0)->local_bridge) {
- ast_rtp_instance_get_engine(instance0)->local_bridge(instance0, NULL);
- }
- if (instance1 && ast_rtp_instance_get_engine(instance1)->local_bridge) {
- ast_rtp_instance_get_engine(instance1)->local_bridge(instance1, NULL);
+ if (ast_rtp_instance_get_engine(glue0->audio.instance)->local_bridge) {
+ ast_rtp_instance_get_engine(glue0->audio.instance)->local_bridge(glue0->audio.instance, NULL);
}
- ast_rtp_instance_set_bridged(instance0, NULL);
- if (instance1) {
- ast_rtp_instance_set_bridged(instance1, NULL);
+ if (ast_rtp_instance_get_engine(glue1->audio.instance)->local_bridge) {
+ ast_rtp_instance_get_engine(glue1->audio.instance)->local_bridge(glue1->audio.instance, NULL);
}
+ ast_rtp_instance_set_bridged(glue0->audio.instance, NULL);
+ ast_rtp_instance_set_bridged(glue1->audio.instance, NULL);
break;
case AST_RTP_GLUE_RESULT_REMOTE:
if (target) {
@@ -238,10 +485,38 @@ static void native_rtp_bridge_stop(struct ast_bridge *bridge, struct ast_channel
* If a target was provided, it is being put on hold and should expect to
* receive media from Asterisk instead of what it was previously connected to.
*/
+ ast_debug(2, "Bridge '%s'. Bringing back '%s' to us\n",
+ bridge->uniqueid, ast_channel_name(target));
if (bc0->chan == target) {
- glue0->update_peer(bc0->chan, NULL, NULL, NULL, NULL, 0);
+ data0->remote_cb = NULL;
+ glue0->cb->update_peer(bc0->chan, NULL, NULL, NULL, NULL, 0);
} else {
- glue1->update_peer(bc1->chan, NULL, NULL, NULL, NULL, 0);
+ data1->remote_cb = NULL;
+ glue1->cb->update_peer(bc1->chan, NULL, NULL, NULL, NULL, 0);
+ }
+ } else {
+ data0->remote_cb = NULL;
+ data1->remote_cb = NULL;
+ /*
+ * XXX We don't want to bring back the channels if we are
+ * switching to T.38. We have received a reinvite on one channel
+ * and we will be sending a reinvite on the other to start T.38.
+ * If we bring the streams back now we confuse the chan_pjsip
+ * channel driver processing the incoming T.38 reinvite with
+ * reinvite glare. I think this is really a bug in chan_pjsip
+ * that this exception case is working around.
+ */
+ if (rtp_glue_get_current_combined_result(bc0->chan, bc1->chan)
+ != AST_RTP_GLUE_RESULT_FORBID) {
+ ast_debug(2, "Bridge '%s'. Bringing back '%s' and '%s' to us\n",
+ bridge->uniqueid, ast_channel_name(bc0->chan),
+ ast_channel_name(bc1->chan));
+ glue0->cb->update_peer(bc0->chan, NULL, NULL, NULL, NULL, 0);
+ glue1->cb->update_peer(bc1->chan, NULL, NULL, NULL, NULL, 0);
+ } else {
+ ast_debug(2, "Bridge '%s'. Skip bringing back '%s' and '%s' to us\n",
+ bridge->uniqueid, ast_channel_name(bc0->chan),
+ ast_channel_name(bc1->chan));
}
}
break;
@@ -249,10 +524,8 @@ static void native_rtp_bridge_stop(struct ast_bridge *bridge, struct ast_channel
break;
}
- if (!target && native_type != AST_RTP_GLUE_RESULT_FORBID) {
- glue0->update_peer(bc0->chan, NULL, NULL, NULL, NULL, 0);
- glue1->update_peer(bc1->chan, NULL, NULL, NULL, NULL, 0);
- }
+ rtp_glue_data_reset(glue0);
+ rtp_glue_data_reset(glue1);
ast_debug(2, "Discontinued RTP bridging of '%s' and '%s' - media will flow through Asterisk core\n",
ast_channel_name(bc0->chan), ast_channel_name(bc1->chan));
@@ -261,13 +534,19 @@ static void native_rtp_bridge_stop(struct ast_bridge *bridge, struct ast_channel
ast_channel_unlock(bc1->chan);
}
-/*! \brief Frame hook that is called to intercept hold/unhold */
-static struct ast_frame *native_rtp_framehook(struct ast_channel *chan, struct ast_frame *f, enum ast_framehook_event event, void *data)
+/*!
+ * \internal
+ * \brief Frame hook that is called to intercept hold/unhold
+ */
+static struct ast_frame *native_rtp_framehook(struct ast_channel *chan,
+ struct ast_frame *f, enum ast_framehook_event event, void *data)
{
- RAII_VAR(struct ast_bridge *, bridge, NULL, ao2_cleanup);
- struct native_rtp_bridge_data *native_data = data;
+ struct ast_bridge *bridge;
+ struct native_rtp_framehook_data *native_data = data;
- if (!f || (event != AST_FRAMEHOOK_EVENT_WRITE)) {
+ if (!f
+ || f->frametype != AST_FRAME_CONTROL
+ || event != AST_FRAMEHOOK_EVENT_WRITE) {
return f;
}
@@ -286,48 +565,64 @@ static struct ast_frame *native_rtp_framehook(struct ast_channel *chan, struct a
ast_channel_unlock(chan);
ast_bridge_lock(bridge);
if (!native_data->detached) {
- if (f->subclass.integer == AST_CONTROL_HOLD) {
+ switch (f->subclass.integer) {
+ case AST_CONTROL_HOLD:
native_rtp_bridge_stop(bridge, chan);
- } else if ((f->subclass.integer == AST_CONTROL_UNHOLD) ||
- (f->subclass.integer == AST_CONTROL_UPDATE_RTP_PEER)) {
+ break;
+ case AST_CONTROL_UNHOLD:
+ case AST_CONTROL_UPDATE_RTP_PEER:
native_rtp_bridge_start(bridge, chan);
+ break;
+ default:
+ break;
}
}
ast_bridge_unlock(bridge);
+ ao2_ref(bridge, -1);
ast_channel_lock(chan);
-
}
return f;
}
-/*! \brief Callback function which informs upstream if we are consuming a frame of a specific type */
+/*!
+ * \internal
+ * \brief Callback function which informs upstream if we are consuming a frame of a specific type
+ */
static int native_rtp_framehook_consume(void *data, enum ast_frame_type type)
{
return (type == AST_FRAME_CONTROL ? 1 : 0);
}
-/*! \brief Internal helper function which checks whether the channels are compatible with our native bridging */
+/*!
+ * \internal
+ * \brief Internal helper function which checks whether a channel is compatible with our native bridging
+ */
static int native_rtp_bridge_capable(struct ast_channel *chan)
{
return !ast_channel_has_hook_requiring_audio(chan);
}
+/*!
+ * \internal
+ * \brief Internal helper function which checks whether both channels are compatible with our native bridging
+ */
static int native_rtp_bridge_compatible_check(struct ast_bridge *bridge, struct ast_bridge_channel *bc0, struct ast_bridge_channel *bc1)
{
enum ast_rtp_glue_result native_type;
- struct ast_rtp_glue *glue0;
- struct ast_rtp_glue *glue1;
- RAII_VAR(struct ast_rtp_instance *, instance0, NULL, ao2_cleanup);
- RAII_VAR(struct ast_rtp_instance *, instance1, NULL, ao2_cleanup);
- RAII_VAR(struct ast_rtp_instance *, vinstance0, NULL, ao2_cleanup);
- RAII_VAR(struct ast_rtp_instance *, vinstance1, NULL, ao2_cleanup);
- RAII_VAR(struct ast_format_cap *, cap0, NULL, ao2_cleanup);
- RAII_VAR(struct ast_format_cap *, cap1, NULL, ao2_cleanup);
int read_ptime0;
int read_ptime1;
int write_ptime0;
int write_ptime1;
+ struct rtp_glue_data glue_a;
+ struct rtp_glue_data glue_b;
+ RAII_VAR(struct ast_format_cap *, cap0, NULL, ao2_cleanup);
+ RAII_VAR(struct ast_format_cap *, cap1, NULL, ao2_cleanup);
+ RAII_VAR(struct rtp_glue_data *, glue0, NULL, rtp_glue_data_destroy);
+ RAII_VAR(struct rtp_glue_data *, glue1, NULL, rtp_glue_data_destroy);
+
+ ast_debug(1, "Bridge '%s'. Checking compatability for channels '%s' and '%s'\n",
+ bridge->uniqueid, ast_channel_name(bc0->chan), ast_channel_name(bc1->chan));
if (!native_rtp_bridge_capable(bc0->chan)) {
ast_debug(1, "Bridge '%s' can not use native RTP bridge as channel '%s' has features which prevent it\n",
@@ -341,8 +636,17 @@ static int native_rtp_bridge_compatible_check(struct ast_bridge *bridge, struct
return 0;
}
- native_type = native_rtp_bridge_get(bc0->chan, bc1->chan, &glue0, &glue1,
- &instance0, &instance1, &vinstance0, &vinstance1);
+ rtp_glue_data_init(&glue_a);
+ glue0 = &glue_a;
+ rtp_glue_data_init(&glue_b);
+ glue1 = &glue_b;
+ if (rtp_glue_data_get(bc0->chan, glue0, bc1->chan, glue1)) {
+ ast_debug(1, "Bridge '%s' can not use native RTP bridge as could not get details\n",
+ bridge->uniqueid);
+ return 0;
+ }
+ native_type = glue0->result;
+
if (native_type == AST_RTP_GLUE_RESULT_FORBID) {
ast_debug(1, "Bridge '%s' can not use native RTP bridge as it was forbidden while getting details\n",
bridge->uniqueid);
@@ -350,25 +654,25 @@ static int native_rtp_bridge_compatible_check(struct ast_bridge *bridge, struct
}
if (ao2_container_count(bc0->features->dtmf_hooks)
- && ast_rtp_instance_dtmf_mode_get(instance0)) {
+ && ast_rtp_instance_dtmf_mode_get(glue0->audio.instance)) {
ast_debug(1, "Bridge '%s' can not use native RTP bridge as channel '%s' has DTMF hooks\n",
bridge->uniqueid, ast_channel_name(bc0->chan));
return 0;
}
if (ao2_container_count(bc1->features->dtmf_hooks)
- && ast_rtp_instance_dtmf_mode_get(instance1)) {
+ && ast_rtp_instance_dtmf_mode_get(glue1->audio.instance)) {
ast_debug(1, "Bridge '%s' can not use native RTP bridge as channel '%s' has DTMF hooks\n",
bridge->uniqueid, ast_channel_name(bc1->chan));
return 0;
}
if (native_type == AST_RTP_GLUE_RESULT_LOCAL
- && (ast_rtp_instance_get_engine(instance0)->local_bridge
- != ast_rtp_instance_get_engine(instance1)->local_bridge
- || (ast_rtp_instance_get_engine(instance0)->dtmf_compatible
- && !ast_rtp_instance_get_engine(instance0)->dtmf_compatible(bc0->chan,
- instance0, bc1->chan, instance1)))) {
+ && (ast_rtp_instance_get_engine(glue0->audio.instance)->local_bridge
+ != ast_rtp_instance_get_engine(glue1->audio.instance)->local_bridge
+ || (ast_rtp_instance_get_engine(glue0->audio.instance)->dtmf_compatible
+ && !ast_rtp_instance_get_engine(glue0->audio.instance)->dtmf_compatible(bc0->chan,
+ glue0->audio.instance, bc1->chan, glue1->audio.instance)))) {
ast_debug(1, "Bridge '%s' can not use local native RTP bridge as local bridge or DTMF is not compatible\n",
bridge->uniqueid);
return 0;
@@ -381,11 +685,11 @@ static int native_rtp_bridge_compatible_check(struct ast_bridge *bridge, struct
}
/* Make sure that codecs match */
- if (glue0->get_codec) {
- glue0->get_codec(bc0->chan, cap0);
+ if (glue0->cb->get_codec) {
+ glue0->cb->get_codec(bc0->chan, cap0);
}
- if (glue1->get_codec) {
- glue1->get_codec(bc1->chan, cap1);
+ if (glue1->cb->get_codec) {
+ glue1->cb->get_codec(bc1->chan, cap1);
}
if (ast_format_cap_count(cap0) != 0
&& ast_format_cap_count(cap1) != 0
@@ -415,6 +719,10 @@ static int native_rtp_bridge_compatible_check(struct ast_bridge *bridge, struct
return 1;
}
+/*!
+ * \internal
+ * \brief Called by the bridge core "compatible' callback
+ */
static int native_rtp_bridge_compatible(struct ast_bridge *bridge)
{
struct ast_bridge_channel *bc0;
@@ -439,10 +747,13 @@ static int native_rtp_bridge_compatible(struct ast_bridge *bridge)
return is_compatible;
}
-/*! \brief Helper function which adds frame hook to bridge channel */
+/*!
+ * \internal
+ * \brief Helper function which adds frame hook to bridge channel
+ */
static int native_rtp_bridge_framehook_attach(struct ast_bridge_channel *bridge_channel)
{
- struct native_rtp_bridge_data *data = ao2_alloc(sizeof(*data), NULL);
+ struct native_rtp_bridge_channel_data *data = bridge_channel->tech_pvt;
static struct ast_framehook_interface hook = {
.version = AST_FRAMEHOOK_INTERFACE_VERSION,
.event_cb = native_rtp_framehook,
@@ -451,45 +762,82 @@ static int native_rtp_bridge_framehook_attach(struct ast_bridge_channel *bridge_
.disable_inheritance = 1,
};
- if (!data) {
+ ast_assert(data->hook_data == NULL);
+ data->hook_data = ao2_alloc_options(sizeof(*data->hook_data), NULL,
+ AO2_ALLOC_OPT_LOCK_NOLOCK);
+ if (!data->hook_data) {
return -1;
}
+ ast_debug(2, "Bridge '%s'. Attaching hook data %p to '%s'\n",
+ bridge_channel->bridge->uniqueid, data, ast_channel_name(bridge_channel->chan));
+
ast_channel_lock(bridge_channel->chan);
- hook.data = ao2_bump(data);
- data->id = ast_framehook_attach(bridge_channel->chan, &hook);
+ /* We're giving 1 ref to the framehook and keeping the one from the alloc for ourselves */
+ hook.data = ao2_bump(data->hook_data);
+ data->hook_data->id = ast_framehook_attach(bridge_channel->chan, &hook);
ast_channel_unlock(bridge_channel->chan);
- if (data->id < 0) {
- /* We need to drop both the reference we hold, and the one the framehook would hold */
- ao2_ref(data, -2);
+ if (data->hook_data->id < 0) {
+ /*
+ * We need to drop both the reference we hold in data,
+ * and the one the framehook would hold.
+ */
+ ao2_ref(data->hook_data, -2);
+ data->hook_data = NULL;
+
return -1;
}
- bridge_channel->tech_pvt = data;
-
return 0;
}
-/*! \brief Helper function which removes frame hook from bridge channel */
+/*!
+ * \internal
+ * \brief Helper function which removes frame hook from bridge channel
+ */
static void native_rtp_bridge_framehook_detach(struct ast_bridge_channel *bridge_channel)
{
- RAII_VAR(struct native_rtp_bridge_data *, data, bridge_channel->tech_pvt, ao2_cleanup);
+ struct native_rtp_bridge_channel_data *data = bridge_channel->tech_pvt;
- if (!data) {
+ if (!data || !data->hook_data) {
return;
}
+ ast_debug(2, "Bridge '%s'. Detaching hook data %p from '%s'\n",
+ bridge_channel->bridge->uniqueid, data->hook_data, ast_channel_name(bridge_channel->chan));
+
ast_channel_lock(bridge_channel->chan);
- ast_framehook_detach(bridge_channel->chan, data->id);
- data->detached = 1;
+ ast_framehook_detach(bridge_channel->chan, data->hook_data->id);
+ data->hook_data->detached = 1;
ast_channel_unlock(bridge_channel->chan);
- bridge_channel->tech_pvt = NULL;
+ ao2_cleanup(data->hook_data);
+ data->hook_data = NULL;
}
+/*!
+ * \internal
+ * \brief Called by the bridge core 'join' callback for each channel joining he bridge
+ */
static int native_rtp_bridge_join(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel)
{
- native_rtp_bridge_framehook_detach(bridge_channel);
+ ast_debug(2, "Bridge '%s'. Channel '%s' is joining bridge tech\n",
+ bridge->uniqueid, ast_channel_name(bridge_channel->chan));
+
+ ast_assert(bridge_channel->tech_pvt == NULL);
+
+ if (bridge_channel->suspended) {
+ /* The channel will rejoin when it is unsuspended */
+ return 0;
+ }
+
+ bridge_channel->tech_pvt = native_rtp_bridge_channel_data_alloc();
+ if (!bridge_channel->tech_pvt) {
+ return -1;
+ }
+
if (native_rtp_bridge_framehook_attach(bridge_channel)) {
+ native_rtp_bridge_channel_data_free(bridge_channel->tech_pvt);
+ bridge_channel->tech_pvt = NULL;
return -1;
}
@@ -497,20 +845,81 @@ static int native_rtp_bridge_join(struct ast_bridge *bridge, struct ast_bridge_c
return 0;
}
+/*!
+ * \internal
+ * \brief Add the channel back into the bridge
+ */
static void native_rtp_bridge_unsuspend(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel)
{
+ ast_debug(2, "Bridge '%s'. Channel '%s' is unsuspended back to bridge tech\n",
+ bridge->uniqueid, ast_channel_name(bridge_channel->chan));
native_rtp_bridge_join(bridge, bridge_channel);
}
+/*!
+ * \internal
+ * \brief Leave the bridge
+ */
static void native_rtp_bridge_leave(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel)
{
+ ast_debug(2, "Bridge '%s'. Channel '%s' is leaving bridge tech\n",
+ bridge->uniqueid, ast_channel_name(bridge_channel->chan));
+
+ if (!bridge_channel->tech_pvt) {
+ return;
+ }
+
native_rtp_bridge_framehook_detach(bridge_channel);
native_rtp_bridge_stop(bridge, NULL);
+
+ native_rtp_bridge_channel_data_free(bridge_channel->tech_pvt);
+ bridge_channel->tech_pvt = NULL;
+}
+
+/*!
+ * \internal
+ * \brief Suspend the channel from the bridge
+ */
+static void native_rtp_bridge_suspend(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel)
+{
+ ast_debug(2, "Bridge '%s'. Channel '%s' is suspending from bridge tech\n",
+ bridge->uniqueid, ast_channel_name(bridge_channel->chan));
+ native_rtp_bridge_leave(bridge, bridge_channel);
}
static int native_rtp_bridge_write(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel, struct ast_frame *frame)
{
- return ast_bridge_queue_everyone_else(bridge, bridge_channel, frame);
+ const struct ast_control_t38_parameters *t38_parameters;
+ int defer = 0;
+
+ if (!ast_bridge_queue_everyone_else(bridge, bridge_channel, frame)) {
+ /* This frame was successfully queued so no need to defer */
+ return 0;
+ }
+
+ /* Depending on the frame defer it so when the next channel joins it receives it */
+ switch (frame->frametype) {
+ case AST_FRAME_CONTROL:
+ switch (frame->subclass.integer) {
+ case AST_CONTROL_T38_PARAMETERS:
+ t38_parameters = frame->data.ptr;
+ switch (t38_parameters->request_response) {
+ case AST_T38_REQUEST_NEGOTIATE:
+ defer = -1;
+ break;
+ default:
+ break;
+ }
+ break;
+ default:
+ break;
+ }
+ break;
+ default:
+ break;
+ }
+
+ return defer;
}
static struct ast_bridge_technology native_rtp_bridge = {
@@ -520,7 +929,7 @@ static struct ast_bridge_technology native_rtp_bridge = {
.join = native_rtp_bridge_join,
.unsuspend = native_rtp_bridge_unsuspend,
.leave = native_rtp_bridge_leave,
- .suspend = native_rtp_bridge_leave,
+ .suspend = native_rtp_bridge_suspend,
.write = native_rtp_bridge_write,
.compatible = native_rtp_bridge_compatible,
};
diff --git a/bridges/bridge_simple.c b/bridges/bridge_simple.c
index 570453500..3e2a73e46 100644
--- a/bridges/bridge_simple.c
+++ b/bridges/bridge_simple.c
@@ -63,7 +63,37 @@ static int simple_bridge_join(struct ast_bridge *bridge, struct ast_bridge_chann
static int simple_bridge_write(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel, struct ast_frame *frame)
{
- return ast_bridge_queue_everyone_else(bridge, bridge_channel, frame);
+ const struct ast_control_t38_parameters *t38_parameters;
+ int defer = 0;
+
+ if (!ast_bridge_queue_everyone_else(bridge, bridge_channel, frame)) {
+ /* This frame was successfully queued so no need to defer */
+ return 0;
+ }
+
+ /* Depending on the frame defer it so when the next channel joins it receives it */
+ switch (frame->frametype) {
+ case AST_FRAME_CONTROL:
+ switch (frame->subclass.integer) {
+ case AST_CONTROL_T38_PARAMETERS:
+ t38_parameters = frame->data.ptr;
+ switch (t38_parameters->request_response) {
+ case AST_T38_REQUEST_NEGOTIATE:
+ defer = -1;
+ break;
+ default:
+ break;
+ }
+ break;
+ default:
+ break;
+ }
+ break;
+ default:
+ break;
+ }
+
+ return defer;
}
static struct ast_bridge_technology simple_bridge = {
diff --git a/bridges/bridge_softmix.c b/bridges/bridge_softmix.c
index 486330af0..f82f35018 100644
--- a/bridges/bridge_softmix.c
+++ b/bridges/bridge_softmix.c
@@ -727,14 +727,10 @@ static int softmix_bridge_write(struct ast_bridge *bridge, struct ast_bridge_cha
res = ast_bridge_queue_everyone_else(bridge, bridge_channel, frame);
break;
case AST_FRAME_VOICE:
- if (bridge_channel) {
- softmix_bridge_write_voice(bridge, bridge_channel, frame);
- }
+ softmix_bridge_write_voice(bridge, bridge_channel, frame);
break;
case AST_FRAME_VIDEO:
- if (bridge_channel) {
- softmix_bridge_write_video(bridge, bridge_channel, frame);
- }
+ softmix_bridge_write_video(bridge, bridge_channel, frame);
break;
case AST_FRAME_CONTROL:
res = softmix_bridge_write_control(bridge, bridge_channel, frame);
diff --git a/build_tools/download_externals b/build_tools/download_externals
index b0a414ed1..efeb6c53c 100755
--- a/build_tools/download_externals
+++ b/build_tools/download_externals
@@ -5,7 +5,24 @@ if [[ ( ${BASH_VERSINFO[0]} == 4 && ${BASH_VERSINFO[1]} > 1 ) || ${BASH_VERSINFO
fi
set -e
+
ASTTOPDIR=${ASTTOPDIR:-.}
+export make=`sed -n -r -e "s/^MAKE\s*=\s*//gp" ${ASTTOPDIR}/makeopts`
+
+getvar() {
+ $make --quiet --no-print-directory -f- <<EOF
+include ${ASTTOPDIR}/makeopts
+all:
+ @echo "\$($1)"
+EOF
+}
+
+XMLSTARLET=`getvar XMLSTARLET`
+ASTMODDIR=`getvar ASTMODDIR`
+cache_dir=`getvar EXTERNALS_CACHE_DIR`
+DOWNLOAD_TO_STDOUT=`getvar DOWNLOAD_TO_STDOUT`
+HOST_CPU=`getvar HOST_CPU`
+INSTALL=`getvar INSTALL`
module_name=${1%%-*}
variant=${1##*-}
@@ -26,20 +43,16 @@ if [[ -z "${tmpdir}" ]] ; then
fi
trap "rm -rf ${tmpdir}" EXIT
-sed -r -e "s/^([^ =]+)\s*=\s*(.*)$/\1=\"\2\"/g" ${ASTTOPDIR}/makeopts >${tmpdir}/makeopts
-source ${tmpdir}/makeopts
if [[ -z "${ASTMODDIR}" ]] ; then
echo "${module_name}: Unable to parse ${ASTTOPDIR}/makeopts."
exit 1
fi
-XMLSTARLET=${XMLSTARLET:-xmlstarlet}
if [[ "${XMLSTARLET}" = ":" ]] ; then
echo "${module_name}: The externals downloader requires xmlstarlet to be installed."
exit 1
fi
-cache_dir="${EXTERNALS_CACHE_DIR}"
if [[ -z ${cache_dir} ]] ; then
cache_dir=${tmpdir}
fi
@@ -187,7 +200,7 @@ if [[ -f ${cache_dir}/${full_name}.manifest.xml ]] ; then
fi
if [[ ${need_download} = 1 ]] ; then
- echo "${full_name}: Downloading ${remote_url}/${tarball}"
+ echo "${full_name}: Downloading ${remote_url}/${tarball} to ${cache_dir}/${tarball}"
${DOWNLOAD_TO_STDOUT} ${remote_url}/${tarball} > ${cache_dir}/${tarball} || {
echo "${full_name}: Unable to fetch ${remote_url}/${tarball}"
exit 1
diff --git a/build_tools/list_valid_installed_externals b/build_tools/list_valid_installed_externals
index 12aff3f95..ed362743c 100755
--- a/build_tools/list_valid_installed_externals
+++ b/build_tools/list_valid_installed_externals
@@ -6,6 +6,23 @@ fi
set -e
ASTTOPDIR=${ASTTOPDIR:-.}
+export make=`sed -n -r -e "s/^MAKE\s*=\s*//gp" ${ASTTOPDIR}/makeopts`
+
+getvar() {
+ $make --quiet --no-print-directory -f- <<EOF
+include ${ASTTOPDIR}/makeopts
+all:
+ @echo "\$($1)"
+EOF
+}
+
+
+XMLSTARLET=`getvar XMLSTARLET`
+ASTMODDIR=`getvar ASTMODDIR`
+cache_dir=`getvar EXTERNALS_CACHE_DIR`
+DOWNLOAD_TO_STDOUT=`getvar DOWNLOAD_TO_STDOUT`
+HOST_CPU=`getvar HOST_CPU`
+INSTALL=`getvar INSTALL`
tmpdir=$(mktemp -d)
if [[ -z "${tmpdir}" ]] ; then
@@ -14,14 +31,11 @@ if [[ -z "${tmpdir}" ]] ; then
fi
trap "rm -rf ${tmpdir}" EXIT
-sed -r -e "s/^([^ =]+)\s*=\s*(.*)$/\1=\"\2\"/g" ${ASTTOPDIR}/makeopts >${tmpdir}/makeopts
-source ${tmpdir}/makeopts
if [[ -z "${ASTMODDIR}" ]] ; then
echo "${module_name}: Unable to parse ${ASTTOPDIR}/makeopts."
exit 1
fi
-XMLSTARLET=${XMLSTARLET:-xmlstarlet}
if [[ "${XMLSTARLET}" = ":" ]] ; then
echo "${module_name}: The externals downloader requires xmlstarlet to be installed."
exit 1
diff --git a/channels/Makefile b/channels/Makefile
index cacfde190..fdc139089 100644
--- a/channels/Makefile
+++ b/channels/Makefile
@@ -49,6 +49,8 @@ CHAN_DAHDI_OBJS= \
chan_dahdi.so: $(CHAN_DAHDI_OBJS)
$(CHAN_DAHDI_OBJS): _ASTCFLAGS+=$(call MOD_ASTCFLAGS,chan_dahdi)
+chan_mgcp.o: _ASTCFLAGS+=$(AST_NO_FORMAT_TRUNCATION)
+
chan_misdn.o: _ASTCFLAGS+=-Imisdn
misdn_config.o: _ASTCFLAGS+=-Imisdn
diff --git a/channels/chan_iax2.c b/channels/chan_iax2.c
index 67552ce4e..f422aae43 100644
--- a/channels/chan_iax2.c
+++ b/channels/chan_iax2.c
@@ -8571,7 +8571,7 @@ static int try_transfer(struct chan_iax2_pvt *pvt, struct iax_ies *ies)
{
int newcall = 0;
struct iax_ie_data ied;
- struct ast_sockaddr new;
+ struct ast_sockaddr new = { {0,} };
memset(&ied, 0, sizeof(ied));
if (!ast_sockaddr_isnull(&ies->apparent_addr)) {
@@ -13084,7 +13084,7 @@ static struct iax2_peer *build_peer(const char *name, struct ast_variable *v, st
ast_free_acl_list(oldacl);
}
- if (!ast_strlen_zero(peer->mailbox)) {
+ if (!ast_strlen_zero(peer->mailbox) && !peer->mwi_event_sub) {
struct stasis_topic *mailbox_specific_topic;
mailbox_specific_topic = ast_mwi_topic(peer->mailbox);
diff --git a/channels/chan_motif.c b/channels/chan_motif.c
index 4bb84c9a1..314103765 100644
--- a/channels/chan_motif.c
+++ b/channels/chan_motif.c
@@ -1907,7 +1907,7 @@ static struct ast_channel *jingle_request(const char *type, struct ast_format_ca
{
RAII_VAR(struct jingle_config *, cfg, ao2_global_obj_ref(globals), ao2_cleanup);
RAII_VAR(struct jingle_endpoint *, endpoint, NULL, ao2_cleanup);
- char *dialed, target[200] = "";
+ char *dialed, target[1024] = "";
struct ast_xmpp_buddy *buddy;
struct jingle_session *session;
struct ast_channel *chan;
diff --git a/channels/chan_pjsip.c b/channels/chan_pjsip.c
index 6eea79368..75dcd7610 100644
--- a/channels/chan_pjsip.c
+++ b/channels/chan_pjsip.c
@@ -727,27 +727,45 @@ static struct ast_frame *chan_pjsip_read(struct ast_channel *ast)
session = channel->session;
- if (ast_format_cap_iscompatible_format(ast_channel_nativeformats(ast), f->subclass.format) == AST_FORMAT_CMP_NOT_EQUAL) {
- ast_debug(1, "Oooh, got a frame with format of %s on channel '%s' when it has not been negotiated\n",
- ast_format_get_name(f->subclass.format), ast_channel_name(ast));
-
- ast_frfree(f);
- return &ast_null_frame;
- }
-
+ /*
+ * Asymmetric RTP only has one native format set at a time.
+ * Therefore we need to update the native format to the current
+ * raw read format BEFORE the native format check
+ */
if (!session->endpoint->asymmetric_rtp_codec &&
ast_format_cmp(ast_channel_rawwriteformat(ast), f->subclass.format) == AST_FORMAT_CMP_NOT_EQUAL) {
- /* For maximum compatibility we ensure that the write format matches that of the received media */
+ struct ast_format_cap *caps;
+
+ /* For maximum compatibility we ensure that the formats match that of the received media */
ast_debug(1, "Oooh, got a frame with format of %s on channel '%s' when we're sending '%s', switching to match\n",
ast_format_get_name(f->subclass.format), ast_channel_name(ast),
ast_format_get_name(ast_channel_rawwriteformat(ast)));
+
+ caps = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT);
+ if (caps) {
+ ast_format_cap_append_from_cap(caps, ast_channel_nativeformats(ast), AST_MEDIA_TYPE_UNKNOWN);
+ ast_format_cap_remove_by_type(caps, AST_MEDIA_TYPE_AUDIO);
+ ast_format_cap_append(caps, f->subclass.format, 0);
+ ast_channel_nativeformats_set(ast, caps);
+ ao2_ref(caps, -1);
+ }
+
ast_set_write_format_path(ast, ast_channel_writeformat(ast), f->subclass.format);
+ ast_set_read_format_path(ast, ast_channel_readformat(ast), f->subclass.format);
if (ast_channel_is_bridged(ast)) {
ast_channel_set_unbridged_nolock(ast, 1);
}
}
+ if (ast_format_cap_iscompatible_format(ast_channel_nativeformats(ast), f->subclass.format) == AST_FORMAT_CMP_NOT_EQUAL) {
+ ast_debug(1, "Oooh, got a frame with format of %s on channel '%s' when it has not been negotiated\n",
+ ast_format_get_name(f->subclass.format), ast_channel_name(ast));
+
+ ast_frfree(f);
+ return &ast_null_frame;
+ }
+
if (session->dsp) {
int dsp_features;
@@ -827,6 +845,8 @@ static int chan_pjsip_write(struct ast_channel *ast, struct ast_frame *frame)
break;
case AST_FRAME_MODEM:
break;
+ case AST_FRAME_CNG:
+ break;
default:
ast_log(LOG_WARNING, "Can't send %u type frames with PJSIP\n", frame->frametype);
break;
@@ -1259,8 +1279,7 @@ static int update_connected_line_information(void *data)
int generate_new_sdp;
method = session->endpoint->id.refresh_method;
- if (session->inv_session->invite_tsx
- && (session->inv_session->options & PJSIP_INV_SUPPORT_UPDATE)) {
+ if (session->inv_session->options & PJSIP_INV_SUPPORT_UPDATE) {
method = AST_SIP_SESSION_REFRESH_METHOD_UPDATE;
}
@@ -1364,7 +1383,8 @@ static int chan_pjsip_indicate(struct ast_channel *ast, int condition, const voi
/* FIXME: Only use this for VP8. Additional work would have to be done to
* fully support other video codecs */
- if (ast_format_cap_iscompatible_format(ast_channel_nativeformats(ast), ast_format_vp8) != AST_FORMAT_CMP_NOT_EQUAL) {
+ if (ast_format_cap_iscompatible_format(ast_channel_nativeformats(ast), ast_format_vp8) != AST_FORMAT_CMP_NOT_EQUAL ||
+ ast_format_cap_iscompatible_format(ast_channel_nativeformats(ast), ast_format_vp9) != AST_FORMAT_CMP_NOT_EQUAL) {
/* FIXME Fake RTP write, this will be sent as an RTCP packet. Ideally the
* RTP engine would provide a way to externally write/schedule RTCP
* packets */
@@ -1688,21 +1708,27 @@ static int chan_pjsip_digit_begin(struct ast_channel *chan, char digit)
struct ast_sip_session_media *media = pvt->media[SIP_MEDIA_AUDIO];
int res = 0;
- switch (channel->session->endpoint->dtmf) {
+ switch (channel->session->dtmf) {
case AST_SIP_DTMF_RFC_4733:
if (!media || !media->rtp) {
return -1;
}
ast_rtp_instance_dtmf_begin(media->rtp, digit);
- break;
+ break;
case AST_SIP_DTMF_AUTO:
- if (!media || !media->rtp || (ast_rtp_instance_dtmf_mode_get(media->rtp) == AST_RTP_DTMF_MODE_INBAND)) {
- return -1;
- }
+ if (!media || !media->rtp || (ast_rtp_instance_dtmf_mode_get(media->rtp) == AST_RTP_DTMF_MODE_INBAND)) {
+ return -1;
+ }
- ast_rtp_instance_dtmf_begin(media->rtp, digit);
- break;
+ ast_rtp_instance_dtmf_begin(media->rtp, digit);
+ break;
+ case AST_SIP_DTMF_AUTO_INFO:
+ if (!media || !media->rtp || (ast_rtp_instance_dtmf_mode_get(media->rtp) == AST_RTP_DTMF_MODE_NONE)) {
+ return -1;
+ }
+ ast_rtp_instance_dtmf_begin(media->rtp, digit);
+ break;
case AST_SIP_DTMF_NONE:
break;
case AST_SIP_DTMF_INBAND:
@@ -1802,7 +1828,21 @@ static int chan_pjsip_digit_end(struct ast_channel *ast, char digit, unsigned in
struct ast_sip_session_media *media = pvt->media[SIP_MEDIA_AUDIO];
int res = 0;
- switch (channel->session->endpoint->dtmf) {
+ switch (channel->session->dtmf) {
+ case AST_SIP_DTMF_AUTO_INFO:
+ {
+ if (!media || !media->rtp) {
+ return -1;
+ }
+ if (ast_rtp_instance_dtmf_mode_get(media->rtp) != AST_RTP_DTMF_MODE_NONE) {
+ ast_debug(3, "Told to send end of digit on Auto-Info channel %s RFC4733 negotiated so using it.\n", ast_channel_name(ast));
+ ast_rtp_instance_dtmf_end_with_duration(media->rtp, digit, duration);
+ break;
+ }
+ /* If RFC_4733 was not negotiated, fail through to the DTMF_INFO processing */
+ ast_debug(3, "Told to send end of digit on Auto-Info channel %s RFC4733 NOT negotiated using INFO instead.\n", ast_channel_name(ast));
+ }
+
case AST_SIP_DTMF_INFO:
{
struct info_dtmf_data *dtmf_data = info_dtmf_data_alloc(channel->session, digit, duration);
@@ -1835,14 +1875,15 @@ static int chan_pjsip_digit_end(struct ast_channel *ast, char digit, unsigned in
}
ast_rtp_instance_dtmf_end_with_duration(media->rtp, digit, duration);
- break;
- case AST_SIP_DTMF_AUTO:
- if (!media || !media->rtp || (ast_rtp_instance_dtmf_mode_get(media->rtp) == AST_RTP_DTMF_MODE_INBAND)) {
- return -1;
- }
+ break;
+ case AST_SIP_DTMF_AUTO:
+ if (!media || !media->rtp || (ast_rtp_instance_dtmf_mode_get(media->rtp) == AST_RTP_DTMF_MODE_INBAND)) {
+ return -1;
+ }
+
+ ast_rtp_instance_dtmf_end_with_duration(media->rtp, digit, duration);
+ break;
- ast_rtp_instance_dtmf_end_with_duration(media->rtp, digit, duration);
- break;
case AST_SIP_DTMF_NONE:
break;
@@ -2599,6 +2640,12 @@ static struct ast_custom_function media_offer_function = {
.write = pjsip_acf_media_offer_write
};
+static struct ast_custom_function dtmf_mode_function = {
+ .name = "PJSIP_DTMF_MODE",
+ .read = pjsip_acf_dtmf_mode_read,
+ .write = pjsip_acf_dtmf_mode_write
+};
+
static struct ast_custom_function session_refresh_function = {
.name = "PJSIP_SEND_SESSION_REFRESH",
.write = pjsip_acf_session_refresh_write,
@@ -2643,6 +2690,11 @@ static int load_module(void)
goto end;
}
+ if (ast_custom_function_register(&dtmf_mode_function)) {
+ ast_log(LOG_WARNING, "Unable to register PJSIP_DTMF_MODE dialplan function\n");
+ goto end;
+ }
+
if (ast_custom_function_register(&session_refresh_function)) {
ast_log(LOG_WARNING, "Unable to register PJSIP_SEND_SESSION_REFRESH dialplan function\n");
goto end;
@@ -2702,6 +2754,7 @@ static int load_module(void)
end:
ao2_cleanup(pjsip_uids_onhold);
pjsip_uids_onhold = NULL;
+ ast_custom_function_unregister(&dtmf_mode_function);
ast_custom_function_unregister(&media_offer_function);
ast_custom_function_unregister(&chan_pjsip_dial_contacts_function);
ast_custom_function_unregister(&session_refresh_function);
@@ -2724,6 +2777,7 @@ static int unload_module(void)
ast_sip_session_unregister_supplement(&chan_pjsip_ack_supplement);
ast_sip_session_unregister_supplement(&call_pickup_supplement);
+ ast_custom_function_unregister(&dtmf_mode_function);
ast_custom_function_unregister(&media_offer_function);
ast_custom_function_unregister(&chan_pjsip_dial_contacts_function);
ast_custom_function_unregister(&session_refresh_function);
diff --git a/channels/chan_sip.c b/channels/chan_sip.c
index 199410328..aa6813fd3 100644
--- a/channels/chan_sip.c
+++ b/channels/chan_sip.c
@@ -2080,7 +2080,7 @@ static int sip_cc_monitor_request_cc(struct ast_cc_monitor *monitor, int *availa
static int construct_pidf_body(enum sip_cc_publish_state state, char *pidf_body, size_t size, const char *presentity)
{
struct ast_str *body = ast_str_alloca(size);
- char tuple_id[32];
+ char tuple_id[64];
generate_random_string(tuple_id, sizeof(tuple_id));
@@ -13512,12 +13512,13 @@ static enum sip_result add_sdp(struct sip_request *resp, struct sip_pvt *p, int
get_our_media_address(p, needvideo, needtext, &addr, &vaddr, &taddr, &dest, &vdest, &tdest);
+ /* We don't use dest here but p->ourip because address in o= field must not change in reINVITE */
snprintf(owner, sizeof(owner), "o=%s %d %d IN %s %s\r\n",
ast_strlen_zero(global_sdpowner) ? "-" : global_sdpowner,
p->sessionid, p->sessionversion,
- (ast_sockaddr_is_ipv6(&dest) && !ast_sockaddr_is_ipv4_mapped(&dest)) ?
+ (ast_sockaddr_is_ipv6(&p->ourip) && !ast_sockaddr_is_ipv4_mapped(&p->ourip)) ?
"IP6" : "IP4",
- ast_sockaddr_stringify_addr_remote(&dest));
+ ast_sockaddr_stringify_addr_remote(&p->ourip));
snprintf(connection, sizeof(connection), "c=IN %s %s\r\n",
(ast_sockaddr_is_ipv6(&dest) && !ast_sockaddr_is_ipv4_mapped(&dest)) ?
@@ -15319,7 +15320,7 @@ static int transmit_cc_notify(struct ast_cc_agent *agent, struct sip_pvt *subscr
{
struct sip_request req;
struct sip_cc_agent_pvt *agent_pvt = agent->private_data;
- char uri[SIPBUFSIZE];
+ char uri[SIPBUFSIZE + sizeof("cc-URI: \r\n") - 1];
char state_str[64];
char subscription_state_hdr[64];
@@ -15336,7 +15337,7 @@ static int transmit_cc_notify(struct ast_cc_agent *agent, struct sip_pvt *subscr
add_header(&req, "Subscription-State", subscription_state_hdr);
if (state == CC_READY) {
generate_uri(subscription, agent_pvt->notify_uri, sizeof(agent_pvt->notify_uri));
- snprintf(uri, sizeof(uri) - 1, "cc-URI: %s\r\n", agent_pvt->notify_uri);
+ snprintf(uri, sizeof(uri), "cc-URI: %s\r\n", agent_pvt->notify_uri);
}
add_content(&req, state_str);
if (state == CC_READY) {
@@ -18568,6 +18569,11 @@ static int get_sip_pvt_from_replaces(const char *callid, const char *totag,
}
}
+ if (!sip_pvt_ptr) {
+ /* return error if sip_pvt was not found */
+ return -1;
+ }
+
/* If we're here sip_pvt_ptr has been copied to *out_pvt, prevent RAII_VAR cleanup */
sip_pvt_ptr = NULL;
@@ -33171,17 +33177,17 @@ static int reload_config(enum channelreloadreason reason)
/* If TCP is running on a different IP than UDP, then add it too */
if (!ast_sockaddr_isnull(&sip_tcp_desc.local_address) &&
- !ast_sockaddr_cmp(&bindaddr, &sip_tcp_desc.local_address)) {
+ ast_sockaddr_cmp_addr(&bindaddr, &sip_tcp_desc.local_address)) {
add_sip_domain(ast_sockaddr_stringify_addr(&sip_tcp_desc.local_address),
SIP_DOMAIN_AUTO, NULL);
}
/* If TLS is running on a different IP than UDP and TCP, then add that too */
if (!ast_sockaddr_isnull(&sip_tls_desc.local_address) &&
- !ast_sockaddr_cmp(&bindaddr, &sip_tls_desc.local_address) &&
- !ast_sockaddr_cmp(&sip_tcp_desc.local_address,
+ ast_sockaddr_cmp_addr(&bindaddr, &sip_tls_desc.local_address) &&
+ ast_sockaddr_cmp_addr(&sip_tcp_desc.local_address,
&sip_tls_desc.local_address)) {
- add_sip_domain(ast_sockaddr_stringify_addr(&sip_tcp_desc.local_address),
+ add_sip_domain(ast_sockaddr_stringify_addr(&sip_tls_desc.local_address),
SIP_DOMAIN_AUTO, NULL);
}
diff --git a/channels/chan_unistim.c b/channels/chan_unistim.c
index 029ce91a3..c3baa8acb 100644
--- a/channels/chan_unistim.c
+++ b/channels/chan_unistim.c
@@ -372,7 +372,7 @@ struct unistim_subchannel {
struct unistim_line {
ast_mutex_t lock;
char name[80]; /*! Like 200 */
- char fullname[80]; /*! Like USTM/200\@black */
+ char fullname[101]; /*! Like USTM/200\@black */
char exten[AST_MAX_EXTENSION]; /*! Extension where to start */
char cid_num[AST_MAX_EXTENSION]; /*! CallerID Number */
char mailbox[AST_MAX_EXTENSION]; /*! Mailbox for MWI */
@@ -3699,7 +3699,7 @@ static void key_select_option(struct unistimsession *pte, char keycode)
#define SELECTCODEC_MSG "Codec number : .."
static void handle_select_codec(struct unistimsession *pte)
{
- char buf[30], buf2[5];
+ char buf[30], buf2[6];
pte->state = STATE_SELECTCODEC;
ast_copy_string(buf, ustmtext("Using codec", pte), sizeof(buf));
diff --git a/channels/iax2/firmware.c b/channels/iax2/firmware.c
index a1ee43550..0286132dd 100644
--- a/channels/iax2/firmware.c
+++ b/channels/iax2/firmware.c
@@ -46,6 +46,8 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
#include "include/firmware.h"
+#define IAX_FIRMWARE_SUBDIR "/firmware/iax"
+
struct iax_firmware {
AST_LIST_ENTRY(iax_firmware) list;
int fd;
@@ -208,7 +210,7 @@ void iax_firmware_reload(void)
struct iax_firmware *cur = NULL;
DIR *fwd;
struct dirent *de;
- char dir[256], fn[256];
+ char fn[PATH_MAX + sizeof(IAX_FIRMWARE_SUBDIR) + sizeof(de->d_name)];
AST_LIST_LOCK(&firmwares);
@@ -218,12 +220,13 @@ void iax_firmware_reload(void)
}
/* Now that we have marked them dead... load new ones */
- snprintf(dir, sizeof(dir), "%s/firmware/iax", ast_config_AST_DATA_DIR);
- fwd = opendir(dir);
+ snprintf(fn, sizeof(fn), "%s%s", ast_config_AST_DATA_DIR, IAX_FIRMWARE_SUBDIR);
+ fwd = opendir(fn);
if (fwd) {
while((de = readdir(fwd))) {
if (de->d_name[0] != '.') {
- snprintf(fn, sizeof(fn), "%s/%s", dir, de->d_name);
+ snprintf(fn, sizeof(fn), "%s%s/%s",
+ ast_config_AST_DATA_DIR, IAX_FIRMWARE_SUBDIR, de->d_name);
if (!try_firmware(fn)) {
ast_verb(2, "Loaded firmware '%s'\n", de->d_name);
}
@@ -231,7 +234,7 @@ void iax_firmware_reload(void)
}
closedir(fwd);
} else {
- ast_log(LOG_WARNING, "Error opening firmware directory '%s': %s\n", dir, strerror(errno));
+ ast_log(LOG_WARNING, "Error opening firmware directory '%s': %s\n", fn, strerror(errno));
}
/* Clean up leftovers */
diff --git a/channels/pjsip/dialplan_functions.c b/channels/pjsip/dialplan_functions.c
index d11636ccc..ae1c265bc 100644
--- a/channels/pjsip/dialplan_functions.c
+++ b/channels/pjsip/dialplan_functions.c
@@ -68,6 +68,18 @@
<ref type="function">PJSIP_SEND_SESSION_REFRESH</ref>
</see-also>
</function>
+<function name="PJSIP_DTMF_MODE" language="en_US">
+ <synopsis>
+ Get or change the DTMF mode for a SIP call.
+ </synopsis>
+ <syntax>
+ </syntax>
+ <description>
+ <para>When read, returns the current DTMF mode</para>
+ <para>When written, sets the current DTMF mode</para>
+ <para>This function uses the same DTMF mode naming as the dtmf_mode configuration option</para>
+ </description>
+</function>
<function name="PJSIP_SEND_SESSION_REFRESH" language="en_US">
<synopsis>
W/O: Initiate a session refresh via an UPDATE or re-INVITE on an established media session
@@ -381,9 +393,15 @@
<enum name="local_uri">
<para>The local URI.</para>
</enum>
+ <enum name="local_tag">
+ <para>Tag in From header</para>
+ </enum>
<enum name="remote_uri">
<para>The remote URI.</para>
</enum>
+ <enum name="remote_tag">
+ <para>Tag in To header</para>
+ </enum>
<enum name="t38state">
<para>The current state of any T.38 fax on this channel.</para>
<enumlist>
@@ -440,6 +458,7 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
#include "asterisk/app.h"
#include "asterisk/channel.h"
#include "asterisk/format.h"
+#include "asterisk/dsp.h"
#include "asterisk/pbx.h"
#include "asterisk/res_pjsip.h"
#include "asterisk/res_pjsip_session.h"
@@ -678,10 +697,18 @@ static int channel_read_pjsip(struct ast_channel *chan, const char *type, const
pjsip_uri_print(PJSIP_URI_IN_FROMTO_HDR, dlg->local.info->uri, buf, buflen);
buf_copy = ast_strdupa(buf);
ast_escape_quoted(buf_copy, buf, buflen);
+ } else if (!strcmp(type, "local_tag")) {
+ ast_copy_pj_str(buf, &dlg->local.info->tag, buflen);
+ buf_copy = ast_strdupa(buf);
+ ast_escape_quoted(buf_copy, buf, buflen);
} else if (!strcmp(type, "remote_uri")) {
pjsip_uri_print(PJSIP_URI_IN_FROMTO_HDR, dlg->remote.info->uri, buf, buflen);
buf_copy = ast_strdupa(buf);
ast_escape_quoted(buf_copy, buf, buflen);
+ } else if (!strcmp(type, "remote_tag")) {
+ ast_copy_pj_str(buf, &dlg->remote.info->tag, buflen);
+ buf_copy = ast_strdupa(buf);
+ ast_escape_quoted(buf_copy, buf, buflen);
} else if (!strcmp(type, "t38state")) {
ast_copy_string(buf, t38state_to_string[channel->session->t38state], buflen);
} else if (!strcmp(type, "local_addr")) {
@@ -929,36 +956,40 @@ int pjsip_acf_dial_contacts_read(struct ast_channel *chan, const char *cmd, char
static int media_offer_read_av(struct ast_sip_session *session, char *buf,
size_t len, enum ast_media_type media_type)
{
- int i, size = 0;
+ int idx;
+ size_t accum = 0;
- for (i = 0; i < ast_format_cap_count(session->req_caps); i++) {
- struct ast_format *fmt = ast_format_cap_get_format(session->req_caps, i);
+ /* Note: buf is not terminated while the string is being built. */
+ for (idx = 0; idx < ast_format_cap_count(session->req_caps); ++idx) {
+ struct ast_format *fmt;
+ size_t size;
+ fmt = ast_format_cap_get_format(session->req_caps, idx);
if (ast_format_get_type(fmt) != media_type) {
ao2_ref(fmt, -1);
continue;
}
- /* add one since we'll include a comma */
+ /* Add one for a comma or terminator */
size = strlen(ast_format_get_name(fmt)) + 1;
if (len < size) {
ao2_ref(fmt, -1);
break;
}
- len -= size;
-
- /* no reason to use strncat here since we have already ensured buf has
- enough space, so strcat can be safely used */
- strcat(buf, ast_format_get_name(fmt));
- strcat(buf, ",");
+ /* Append the format name */
+ strcpy(buf + accum, ast_format_get_name(fmt));/* Safe */
ao2_ref(fmt, -1);
- }
- if (size) {
- /* remove the extra comma */
- buf[strlen(buf) - 1] = '\0';
+ accum += size;
+ len -= size;
+
+ /* The last comma on the built string will be set to the terminator. */
+ buf[accum - 1] = ',';
}
+
+ /* Remove the trailing comma or terminate an empty buffer. */
+ buf[accum ? accum - 1 : 0] = '\0';
return 0;
}
@@ -998,6 +1029,9 @@ int pjsip_acf_media_offer_read(struct ast_channel *chan, const char *cmd, char *
return media_offer_read_av(channel->session, buf, len, AST_MEDIA_TYPE_AUDIO);
} else if (!strcmp(data, "video")) {
return media_offer_read_av(channel->session, buf, len, AST_MEDIA_TYPE_VIDEO);
+ } else {
+ /* Ensure that the buffer is empty */
+ buf[0] = '\0';
}
return 0;
@@ -1032,6 +1066,34 @@ int pjsip_acf_media_offer_write(struct ast_channel *chan, const char *cmd, char
return ast_sip_push_task_synchronous(channel->session->serializer, media_offer_write_av, &mdata);
}
+int pjsip_acf_dtmf_mode_read(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len)
+{
+ struct ast_sip_channel_pvt *channel;
+
+ if (!chan) {
+ ast_log(LOG_WARNING, "No channel was provided to %s function.\n", cmd);
+ return -1;
+ }
+
+ ast_channel_lock(chan);
+ if (strcmp(ast_channel_tech(chan)->type, "PJSIP")) {
+ ast_log(LOG_WARNING, "Cannot call %s on a non-PJSIP channel\n", cmd);
+ ast_channel_unlock(chan);
+ return -1;
+ }
+
+ channel = ast_channel_tech_pvt(chan);
+
+ if (ast_sip_dtmf_to_str(channel->session->dtmf, buf, len) < 0) {
+ ast_log(LOG_WARNING, "Unknown DTMF mode %d on PJSIP channel %s\n", channel->session->dtmf, ast_channel_name(chan));
+ ast_channel_unlock(chan);
+ return -1;
+ }
+
+ ast_channel_unlock(chan);
+ return 0;
+}
+
struct refresh_data {
struct ast_sip_session *session;
enum ast_sip_session_refresh_method method;
@@ -1060,6 +1122,120 @@ static int sip_session_response_cb(struct ast_sip_session *session, pjsip_rx_dat
return 0;
}
+static int dtmf_mode_refresh_cb(void *obj)
+{
+ struct refresh_data *data = obj;
+
+ if (data->session->inv_session->state == PJSIP_INV_STATE_CONFIRMED) {
+ ast_debug(3, "Changing DTMF mode on channel %s after OFFER/ANSWER completion. Sending session refresh\n", ast_channel_name(data->session->channel));
+
+ ast_sip_session_refresh(data->session, NULL, NULL,
+ sip_session_response_cb, data->method, 1);
+ } else if (data->session->inv_session->state == PJSIP_INV_STATE_INCOMING) {
+ ast_debug(3, "Changing DTMF mode on channel %s during OFFER/ANSWER exchange. Updating SDP answer\n", ast_channel_name(data->session->channel));
+ ast_sip_session_regenerate_answer(data->session, NULL);
+ }
+
+ return 0;
+}
+
+int pjsip_acf_dtmf_mode_write(struct ast_channel *chan, const char *cmd, char *data, const char *value)
+{
+ struct ast_sip_channel_pvt *channel;
+ struct chan_pjsip_pvt *pjsip_pvt;
+ int dsp_features = 0;
+ int dtmf = -1;
+ struct refresh_data rdata = {
+ .method = AST_SIP_SESSION_REFRESH_METHOD_INVITE,
+ };
+
+ if (!chan) {
+ ast_log(LOG_WARNING, "No channel was provided to %s function.\n", cmd);
+ return -1;
+ }
+
+ ast_channel_lock(chan);
+ if (strcmp(ast_channel_tech(chan)->type, "PJSIP")) {
+ ast_log(LOG_WARNING, "Cannot call %s on a non-PJSIP channel\n", cmd);
+ ast_channel_unlock(chan);
+ return -1;
+ }
+
+ channel = ast_channel_tech_pvt(chan);
+ rdata.session = channel->session;
+
+ dtmf = ast_sip_str_to_dtmf(value);
+
+ if (dtmf == -1) {
+ ast_log(LOG_WARNING, "Cannot set DTMF mode to '%s' on channel '%s' as value is invalid.\n", value,
+ ast_channel_name(chan));
+ ast_channel_unlock(chan);
+ return -1;
+ }
+
+ if (channel->session->dtmf == dtmf) {
+ /* DTMF mode unchanged, nothing to do! */
+ ast_channel_unlock(chan);
+ return 0;
+ }
+
+ channel->session->dtmf = dtmf;
+
+ pjsip_pvt = channel->pvt;
+ if (pjsip_pvt->media[SIP_MEDIA_AUDIO] && (pjsip_pvt->media[SIP_MEDIA_AUDIO])->rtp) {
+ if (channel->session->dtmf == AST_SIP_DTMF_RFC_4733) {
+ ast_rtp_instance_set_prop((pjsip_pvt->media[SIP_MEDIA_AUDIO])->rtp, AST_RTP_PROPERTY_DTMF, 1);
+ ast_rtp_instance_dtmf_mode_set((pjsip_pvt->media[SIP_MEDIA_AUDIO])->rtp, AST_RTP_DTMF_MODE_RFC2833);
+ } else if (channel->session->dtmf == AST_SIP_DTMF_INFO) {
+ ast_rtp_instance_set_prop((pjsip_pvt->media[SIP_MEDIA_AUDIO])->rtp, AST_RTP_PROPERTY_DTMF, 0);
+ ast_rtp_instance_dtmf_mode_set((pjsip_pvt->media[SIP_MEDIA_AUDIO])->rtp, AST_RTP_DTMF_MODE_NONE);
+ } else if (channel->session->dtmf == AST_SIP_DTMF_INBAND) {
+ ast_rtp_instance_set_prop((pjsip_pvt->media[SIP_MEDIA_AUDIO])->rtp, AST_RTP_PROPERTY_DTMF, 0);
+ ast_rtp_instance_dtmf_mode_set((pjsip_pvt->media[SIP_MEDIA_AUDIO])->rtp, AST_RTP_DTMF_MODE_INBAND);
+ } else if (channel->session->dtmf == AST_SIP_DTMF_NONE) {
+ ast_rtp_instance_set_prop((pjsip_pvt->media[SIP_MEDIA_AUDIO])->rtp, AST_RTP_PROPERTY_DTMF, 0);
+ ast_rtp_instance_dtmf_mode_set((pjsip_pvt->media[SIP_MEDIA_AUDIO])->rtp, AST_RTP_DTMF_MODE_NONE);
+ } else if (channel->session->dtmf == AST_SIP_DTMF_AUTO) {
+ if (ast_rtp_instance_dtmf_mode_get((pjsip_pvt->media[SIP_MEDIA_AUDIO])->rtp) != AST_RTP_DTMF_MODE_RFC2833) {
+ /* no RFC4733 negotiated, enable inband */
+ ast_rtp_instance_dtmf_mode_set((pjsip_pvt->media[SIP_MEDIA_AUDIO])->rtp, AST_RTP_DTMF_MODE_INBAND);
+ }
+ } else if (channel->session->dtmf == AST_SIP_DTMF_AUTO_INFO) {
+ ast_rtp_instance_set_prop((pjsip_pvt->media[SIP_MEDIA_AUDIO])->rtp, AST_RTP_PROPERTY_DTMF, 0);
+ if (ast_rtp_instance_dtmf_mode_get((pjsip_pvt->media[SIP_MEDIA_AUDIO])->rtp) == AST_RTP_DTMF_MODE_INBAND) {
+ /* if inband, switch to INFO */
+ ast_rtp_instance_dtmf_mode_set((pjsip_pvt->media[SIP_MEDIA_AUDIO])->rtp, AST_RTP_DTMF_MODE_NONE);
+ }
+ }
+ }
+
+ if (channel->session->dsp) {
+ dsp_features = ast_dsp_get_features(channel->session->dsp);
+ }
+ if (channel->session->dtmf == AST_SIP_DTMF_INBAND ||
+ channel->session->dtmf == AST_SIP_DTMF_AUTO) {
+ dsp_features |= DSP_FEATURE_DIGIT_DETECT;
+ } else {
+ dsp_features &= ~DSP_FEATURE_DIGIT_DETECT;
+ }
+ if (dsp_features) {
+ if (!channel->session->dsp) {
+ if (!(channel->session->dsp = ast_dsp_new())) {
+ ast_channel_unlock(chan);
+ return 0;
+ }
+ }
+ ast_dsp_set_features(channel->session->dsp, dsp_features);
+ } else if (channel->session->dsp) {
+ ast_dsp_free(channel->session->dsp);
+ channel->session->dsp = NULL;
+ }
+
+ ast_channel_unlock(chan);
+
+ return ast_sip_push_task_synchronous(channel->session->serializer, dtmf_mode_refresh_cb, &rdata);
+}
+
static int refresh_write_cb(void *obj)
{
struct refresh_data *data = obj;
diff --git a/channels/pjsip/include/dialplan_functions.h b/channels/pjsip/include/dialplan_functions.h
index 8b80bfa74..731e91d13 100644
--- a/channels/pjsip/include/dialplan_functions.h
+++ b/channels/pjsip/include/dialplan_functions.h
@@ -48,6 +48,31 @@ int pjsip_acf_channel_read(struct ast_channel *chan, const char *cmd, char *data
int pjsip_acf_media_offer_write(struct ast_channel *chan, const char *cmd, char *data, const char *value);
/*!
+ * \brief PJSIP_DTMF_MODE function read callback
+ * \param chan The channel the function is called on
+ * \param cmd The name of the function
+ * \param data Arguments passed to the function
+ * \param buf Out buffer that should be populated with the data
+ * \param len Size of the buffer
+ *
+ * \retval 0 on success
+ * \retval -1 on failure
+ */
+int pjsip_acf_dtmf_mode_read(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len);
+
+/*!
+ * \brief PJSIP_DTMF_MODE function write callback
+ * \param chan The channel the function is called on
+ * \param cmd The name of the function
+ * \param data Arguments passed to the function
+ * \param value Value to be set by the function
+ *
+ * \retval 0 on success
+ * \retval -1 on failure
+ */
+int pjsip_acf_dtmf_mode_write(struct ast_channel *chan, const char *cmd, char *data, const char *value);
+
+/*!
* \brief PJSIP_MEDIA_OFFER function read callback
* \param chan The channel the function is called on
* \param cmd The name of the function
diff --git a/channels/sip/dialplan_functions.c b/channels/sip/dialplan_functions.c
index b859c601b..af443fd51 100644
--- a/channels/sip/dialplan_functions.c
+++ b/channels/sip/dialplan_functions.c
@@ -486,6 +486,9 @@ done:
dialog_unlink_all(p);
dialog_unref(p, "Destroy test object");
}
+ if (chan) {
+ ast_channel_unref(chan);
+ }
ast_rtp_engine_unregister(&test_engine);
return res;
}
diff --git a/configs/basic-pbx/modules.conf b/configs/basic-pbx/modules.conf
index 7b60125b7..05fcc1488 100644
--- a/configs/basic-pbx/modules.conf
+++ b/configs/basic-pbx/modules.conf
@@ -78,7 +78,6 @@ load = res_pjsip_exten_state.so
load = res_pjsip_header_funcs.so
load = res_pjsip_logger.so
load = res_pjsip_messaging.so
-load = res_pjsip_multihomed.so
load = res_pjsip_mwi_body_generator.so
load = res_pjsip_mwi.so
load = res_pjsip_nat.so
diff --git a/configs/samples/cdr.conf.sample b/configs/samples/cdr.conf.sample
index e175a2a76..1d0af7864 100644
--- a/configs/samples/cdr.conf.sample
+++ b/configs/samples/cdr.conf.sample
@@ -17,7 +17,7 @@
; party. Setting this to "yes" will make calls to extensions that don't answer
; and don't set a B side channel (such as by using the Dial application)
; receive CDR log entries. If this option is set to "no", then those log
-; entries will not be created. Unasnwered Calls which get offered to an
+; entries will not be created. Unanswered Calls which get offered to an
; outgoing line will always receive log entries regardless of this option, and
; that is the intended behaviour.
;unanswered = no
diff --git a/configs/samples/config_test.conf.sample b/configs/samples/config_test.conf.sample
index 2fff45ece..b7cb21292 100644
--- a/configs/samples/config_test.conf.sample
+++ b/configs/samples/config_test.conf.sample
@@ -6,6 +6,10 @@
[global]
intopt=-1
uintopt=1
+timelenopt1=1ms
+timelenopt2=1s
+timelenopt3=1m
+timelenopt4=1h
doubleopt=0.1
sockaddropt=1.2.3.4:1234
boolopt=true
@@ -23,6 +27,10 @@ customopt=yes
[item]
intopt=-1
uintopt=1
+timelenopt1=1
+timelenopt2=1
+timelenopt3=1
+timelenopt4=1
doubleopt=0.1
sockaddropt=1.2.3.4:1234
boolopt=true
diff --git a/configs/samples/minivm.conf.sample b/configs/samples/minivm.conf.sample
index 2df3449d1..79fdbb0e2 100644
--- a/configs/samples/minivm.conf.sample
+++ b/configs/samples/minivm.conf.sample
@@ -51,7 +51,7 @@ silencethreshold=128
; If you need to have an external program, i.e. /usr/bin/myapp called when a
; voicemail is received by the server. The arguments are
;
-; <app> <username@domain> <callerid-number> <callerid-name>
+; <app> <username@domain> <callerid-name> <callerid-number>
;
;externnotify=/usr/bin/myapp
; The character set for voicemail messages can be specified here
diff --git a/configs/samples/musiconhold.conf.sample b/configs/samples/musiconhold.conf.sample
index 8b2202de5..67570ee46 100644
--- a/configs/samples/musiconhold.conf.sample
+++ b/configs/samples/musiconhold.conf.sample
@@ -91,3 +91,26 @@ directory=moh
;mode=custom
;directory=/var/lib/asterisk/mohmp3
;application=/site/sw/bin/madplay -Q -o raw:- --mono -R 8000 -a -12
+
+; By default, when res_musiconhold reloads or unloads, it sends a HUP signal
+; to custom applications (and all descendants), waits 100ms, then sends a
+; TERM signal, waits 100ms, then finally sends a KILL signal. An application
+; which is interacting with an external device and/or spawns children of its
+; own may not be able to exit cleanly in the default times, expecially if sent
+; a KILL signal, or if it's children are getting signals directly from
+; res_musiconhoild. To allow extra time, the 'kill_escalation_delay'
+; class option can be used to set the number of milliseconds res_musiconhold
+; waits before escalating kill signals, with the default being the current
+; 100ms. To control to whom the signals are sent, the "kill_method"
+; class option can be set to "process_group" (the default, existing behavior),
+; which sends signals to the application and its descendants directly, or
+; "process" which sends signals only to the application itself.
+
+;[sox_from_device]
+;mode=custom
+;directory=/var/lib/asterisk/mohmp3
+;application=/usr/bin/sox -q -t alsa -c 2 -r 48000 hw:1 -c 1 -r 8000 -t raw -s -
+; Wait 500ms before escalating kill signals
+;kill_escalation_delay=500
+; Send signals to just the child process instead of all descendants
+;kill_method=process
diff --git a/configs/samples/pjsip.conf.sample b/configs/samples/pjsip.conf.sample
index 494a32a62..f983a87db 100644
--- a/configs/samples/pjsip.conf.sample
+++ b/configs/samples/pjsip.conf.sample
@@ -608,8 +608,15 @@
;direct_media_glare_mitigation=none ; Mitigation of direct media re INVITE
; glare (default: "none")
;direct_media_method=invite ; Direct Media method type (default: "invite")
-;connected_line_method=invite ; Connected line method type (default:
- ; "invite")
+;connected_line_method=invite ; Connected line method type.
+ ; When set to "invite", check the remote's
+ ; Allow header and if UPDATE is allowed, send
+ ; UPDATE instead of INVITE to avoid SDP
+ ; renegotiation. If UPDATE is not Allowed,
+ ; send INVITE.
+ ; If set to "update", send UPDATE regardless
+ ; of what the remote Allows.
+ ; (default: "invite")
;direct_media=yes ; Determines whether media may flow directly between
; endpoints (default: "yes")
;disable_direct_media_on_nat=no ; Disable direct media session refreshes when
@@ -768,6 +775,11 @@
; transfer (default: "yes"). The value "no" is useful
; for some SIP phones (Mitel/Aastra, Snom) which expect
; a sip/frag "200 OK" after REFER has been accepted.
+;notify_early_inuse_ringing = ; Whether to notifies dialog-info 'early'
+ ; on INUSE && RINGING state (default: "no").
+ ; The value "yes" is useful for some SIP phones
+ ; (Cisco SPA) to be able to indicate and pick up
+ ; ringing devices.
;==========================AUTH SECTION OPTIONS=========================
;[auth]
diff --git a/configs/samples/voicemail.conf.sample b/configs/samples/voicemail.conf.sample
index f8221eebe..84e83a344 100644
--- a/configs/samples/voicemail.conf.sample
+++ b/configs/samples/voicemail.conf.sample
@@ -227,6 +227,9 @@ pagerdateformat=%A, %B %d, %Y at %r
;imapclosetimeout=60 ; The TCP close timeout (in seconds)
;imapreadtimeout=60 ; The TCP read timeout (in seconds)
;imapwritetimeout=60 ; The TCP write timeout (in seconds)
+;imap_poll_logout=no ; If pollmailboxes=yes, then specify whether need to
+ ; disconnect from the IMAP server after polling.
+ ; Default: no
; -----------------------------------------------------------------------------
;
diff --git a/configs/samples/xmpp.conf.sample b/configs/samples/xmpp.conf.sample
index dad0f79ef..e3a4be142 100644
--- a/configs/samples/xmpp.conf.sample
+++ b/configs/samples/xmpp.conf.sample
@@ -18,6 +18,29 @@
;pubsub_node=pubsub.astjab.org ; Node to use for publishing events via PubSub
;username=asterisk@astjab.org/asterisk ; Username with optional resource.
;secret=blah ; Password
+;refresh_token=TOKEN_VALUE ; Refresh token issued by Google OAuth 2.0 protocol.
+ ; `secret` must NOT be set if you use OAuth.
+ ; See https://developers.google.com/identity/protocols/OAuth2WebServer
+ ; for more details.
+ ; For test reasons you can obtain one on the page
+ ; https://developers.google.com/oauthplayground/
+ ; 1. Click on Settings icon, check "Use your own OAuth credentials"
+ ; and enter your Client ID and Client Secret (see below).
+ ; 2. Input the scope https://www.googleapis.com/auth/googletalk
+ ; and push "Authorize APIs" button.
+ ; 3. Approve permissions.
+ ; 4. On section "Step 2" push "Exchange authorization code for tokens"
+ ; and get your Refresh token.
+;oauth_clientid=OAUTH_CLIENT_ID_VALUE ; The application's client id to authorize using Google OAuth 2.0 protocol.
+;oauth_secret=OAUTH_SECRET_VALUE ; The application's client secret to authorize using Google OAuth 2.0 protocol.
+ ; 1. Create new Project on the page:
+ ; https://console.cloud.google.com/apis/credentials/oauthclient
+ ; 2. Create new Application ID on the same page with type Web-application.
+ ; In section "Allowed URI redirections" put the path to the corresponding
+ ; script on your site or https://developers.google.com/oauthplayground
+ ; if you would like to obtain refresh_token from users by hand
+ ; (for example, for test reasons).
+ ; 3. Client ID and Client Secret will be shown and available on the same page.
;priority=1 ; Resource priority
;port=5222 ; Port to use defaults to 5222
;usetls=yes ; Use tls or not
diff --git a/configure b/configure
index eed6f452d..8308b9ef6 100755
--- a/configure
+++ b/configure
@@ -697,9 +697,11 @@ PBX_IP_MTU_DISCOVER
PBX_RTLD_NOLOAD
PBX_GLOB_BRACE
PBX_GLOB_NOMAGIC
+BIND8_CFLAGS
AST_RPATH
AST_NATIVE_ARCH
AST_SHADOW_WARNINGS
+AST_NO_FORMAT_TRUNCATION
AST_NO_STRICT_OVERFLOW
AST_FORTIFY_SOURCE
AST_TRAMPOLINES
@@ -1190,6 +1192,7 @@ PJPROJECT_LIB
PBX_PJPROJECT
PJPROJECT_DIR
PJPROJECT_BUNDLED
+PJPROJECT_CONFIGURE_OPTS
AST_C_COMPILER_FAMILY
AST_CLANG_BLOCKS
AST_CLANG_BLOCKS_LIBS
@@ -1197,6 +1200,7 @@ AST_NESTED_FUNCTIONS
AST_CODE_COVERAGE
EXTERNALS_CACHE_DIR
SOUNDS_CACHE_DIR
+AST_DOWNLOAD_CACHE
AST_DEVMODE_STRICT
AST_DEVMODE
NOISY_BUILD
@@ -1326,7 +1330,6 @@ infodir
docdir
oldincludedir
includedir
-runstatedir
localstatedir
sharedstatedir
sysconfdir
@@ -1351,6 +1354,7 @@ ac_user_opts='
enable_option_checking
with_gnu_ld
enable_dev_mode
+with_download_cache
with_sounds_cache
with_externals_cache
enable_coverage
@@ -1451,6 +1455,7 @@ CXX
CXXFLAGS
CCC
CXXCPP
+PJPROJECT_CONFIGURE_OPTS
PKG_CONFIG
PKG_CONFIG_PATH
PKG_CONFIG_LIBDIR
@@ -1506,7 +1511,6 @@ datadir='${datarootdir}'
sysconfdir='${prefix}/etc'
sharedstatedir='${prefix}/com'
localstatedir='${prefix}/var'
-runstatedir='${localstatedir}/run'
includedir='${prefix}/include'
oldincludedir='/usr/include'
docdir='${datarootdir}/doc/${PACKAGE_TARNAME}'
@@ -1759,15 +1763,6 @@ do
| -silent | --silent | --silen | --sile | --sil)
silent=yes ;;
- -runstatedir | --runstatedir | --runstatedi | --runstated \
- | --runstate | --runstat | --runsta | --runst | --runs \
- | --run | --ru | --r)
- ac_prev=runstatedir ;;
- -runstatedir=* | --runstatedir=* | --runstatedi=* | --runstated=* \
- | --runstate=* | --runstat=* | --runsta=* | --runst=* | --runs=* \
- | --run=* | --ru=* | --r=*)
- runstatedir=$ac_optarg ;;
-
-sbindir | --sbindir | --sbindi | --sbind | --sbin | --sbi | --sb)
ac_prev=sbindir ;;
-sbindir=* | --sbindir=* | --sbindi=* | --sbind=* | --sbin=* \
@@ -1905,7 +1900,7 @@ fi
for ac_var in exec_prefix prefix bindir sbindir libexecdir datarootdir \
datadir sysconfdir sharedstatedir localstatedir includedir \
oldincludedir docdir infodir htmldir dvidir pdfdir psdir \
- libdir localedir mandir runstatedir
+ libdir localedir mandir
do
eval ac_val=\$$ac_var
# Remove trailing slashes.
@@ -2058,7 +2053,6 @@ Fine tuning of the installation directories:
--sysconfdir=DIR read-only single-machine data [PREFIX/etc]
--sharedstatedir=DIR modifiable architecture-independent data [PREFIX/com]
--localstatedir=DIR modifiable single-machine data [PREFIX/var]
- --runstatedir=DIR modifiable per-process data [LOCALSTATEDIR/run]
--libdir=DIR object code libraries [EPREFIX/lib]
--includedir=DIR C header files [PREFIX/include]
--oldincludedir=DIR C header files for non-gcc [/usr/include]
@@ -2104,6 +2098,9 @@ Optional Packages:
--with-PACKAGE[=ARG] use PACKAGE [ARG=yes]
--without-PACKAGE do not use PACKAGE (same as --with-PACKAGE=no)
--with-gnu-ld assume the C compiler uses GNU ld [default=no]
+ --with-download-cache=PATH
+ use cached sound AND external module tarfiles in
+ PATH
--with-sounds-cache=PATH
use cached sound tarfiles in PATH
--with-externals-cache=PATH
@@ -2205,6 +2202,8 @@ Some influential environment variables:
CXX C++ compiler command
CXXFLAGS C++ compiler flags
CXXCPP C++ preprocessor
+ PJPROJECT_CONFIGURE_OPTS
+ Additional configure options to pass to bundled pjproject
PKG_CONFIG path to pkg-config utility
PKG_CONFIG_PATH
directories to add to pkg-config's search path
@@ -9022,6 +9021,30 @@ fi
+# Check whether --with-download-cache was given.
+if test "${with_download_cache+set}" = set; then :
+ withval=$with_download_cache;
+ case ${withval} in
+ n|no)
+ unset AST_DOWNLOAD_CACHE
+ ;;
+ *)
+ if test "x${withval}" = "x"; then
+ :
+ else
+ AST_DOWNLOAD_CACHE="${withval}"
+ fi
+ ;;
+ esac
+
+else
+ :
+fi
+
+
+
+
+
# Check whether --with-sounds-cache was given.
if test "${with_sounds_cache+set}" = set; then :
withval=$with_sounds_cache;
@@ -9275,20 +9298,33 @@ $as_echo "configuring" >&6; }
as_fn_error $? "cat is required to build bundled pjproject" "$LINENO" 5
fi
- export TAR PATCH SED NM EXTERNALS_CACHE_DIR DOWNLOAD_TO_STDOUT DOWNLOAD_TIMEOUT DOWNLOAD MD5 CAT
- ${GNU_MAKE} --quiet --no-print-directory -C ${PJPROJECT_DIR} EXTERNALS_CACHE_DIR=${EXTERNALS_CACHE_DIR} configure
+
+ this_host=$(./config.sub $(./config.guess))
+ if test "$build" != "$this_host" ; then
+ PJPROJECT_CONFIGURE_OPTS+=" --build=$build"
+ fi
+ if test "$host" != "$this_host" ; then
+ PJPROJECT_CONFIGURE_OPTS+=" --host=$host"
+ fi
+
+ export TAR PATCH SED NM EXTERNALS_CACHE_DIR AST_DOWNLOAD_CACHE DOWNLOAD_TO_STDOUT DOWNLOAD_TIMEOUT DOWNLOAD MD5 CAT
+ export NOISY_BUILD
+ ${GNU_MAKE} --quiet --no-print-directory -C ${PJPROJECT_DIR} \
+ PJPROJECT_CONFIGURE_OPTS="$PJPROJECT_CONFIGURE_OPTS" \
+ EXTERNALS_CACHE_DIR="${EXTERNALS_CACHE_DIR:-${AST_DOWNLOAD_CACHE}}" \
+ 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 \"${GNU_MAKE} -C ${PJPROJECT_DIR} NOISY_BUILD=yes configure\" to see error details." "$LINENO" 5
+ as_fn_error $? "Re-run the ./configure command with 'NOISY_BUILD=yes' appended to see error details." "$LINENO" 5
fi
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for bundled pjproject" >&5
$as_echo_n "checking for bundled pjproject... " >&6; }
- PJPROJECT_INCLUDE=$(${GNU_MAKE} --quiet --no-print-directory -C ${PJPROJECT_DIR} EXTERNALS_CACHE_DIR=${EXTERNALS_CACHE_DIR} echo_cflags)
+ PJPROJECT_INCLUDE=$(${GNU_MAKE} --quiet --no-print-directory -C ${PJPROJECT_DIR} PJPROJECT_CONFIGURE_OPTS="$PJPROJECT_CONFIGURE_OPTS" EXTERNALS_CACHE_DIR="${EXTERNALS_CACHE_DIR:-${AST_DOWNLOAD_CACHE}}" echo_cflags)
PJPROJECT_CFLAGS="$PJPROJECT_INCLUDE"
PBX_PJPROJECT=1
@@ -14667,7 +14703,7 @@ else
We can't simply define LARGE_OFF_T to be 9223372036854775807,
since some C++ compilers masquerading as C compilers
incorrectly reject 9223372036854775807. */
-#define LARGE_OFF_T ((((off_t) 1 << 31) << 31) - 1 + (((off_t) 1 << 31) << 31))
+#define LARGE_OFF_T (((off_t) 1 << 62) - 1 + ((off_t) 1 << 62))
int off_t_is_large[(LARGE_OFF_T % 2147483629 == 721
&& LARGE_OFF_T % 2147483647 == 1)
? 1 : -1];
@@ -14713,7 +14749,7 @@ else
We can't simply define LARGE_OFF_T to be 9223372036854775807,
since some C++ compilers masquerading as C compilers
incorrectly reject 9223372036854775807. */
-#define LARGE_OFF_T ((((off_t) 1 << 31) << 31) - 1 + (((off_t) 1 << 31) << 31))
+#define LARGE_OFF_T (((off_t) 1 << 62) - 1 + ((off_t) 1 << 62))
int off_t_is_large[(LARGE_OFF_T % 2147483629 == 721
&& LARGE_OFF_T % 2147483647 == 1)
? 1 : -1];
@@ -14737,7 +14773,7 @@ rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
We can't simply define LARGE_OFF_T to be 9223372036854775807,
since some C++ compilers masquerading as C compilers
incorrectly reject 9223372036854775807. */
-#define LARGE_OFF_T ((((off_t) 1 << 31) << 31) - 1 + (((off_t) 1 << 31) << 31))
+#define LARGE_OFF_T (((off_t) 1 << 62) - 1 + ((off_t) 1 << 62))
int off_t_is_large[(LARGE_OFF_T % 2147483629 == 721
&& LARGE_OFF_T % 2147483647 == 1)
? 1 : -1];
@@ -14782,7 +14818,7 @@ else
We can't simply define LARGE_OFF_T to be 9223372036854775807,
since some C++ compilers masquerading as C compilers
incorrectly reject 9223372036854775807. */
-#define LARGE_OFF_T ((((off_t) 1 << 31) << 31) - 1 + (((off_t) 1 << 31) << 31))
+#define LARGE_OFF_T (((off_t) 1 << 62) - 1 + ((off_t) 1 << 62))
int off_t_is_large[(LARGE_OFF_T % 2147483629 == 721
&& LARGE_OFF_T % 2147483647 == 1)
? 1 : -1];
@@ -14806,7 +14842,7 @@ rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
We can't simply define LARGE_OFF_T to be 9223372036854775807,
since some C++ compilers masquerading as C compilers
incorrectly reject 9223372036854775807. */
-#define LARGE_OFF_T ((((off_t) 1 << 31) << 31) - 1 + (((off_t) 1 << 31) << 31))
+#define LARGE_OFF_T (((off_t) 1 << 62) - 1 + ((off_t) 1 << 62))
int off_t_is_large[(LARGE_OFF_T % 2147483629 == 721
&& LARGE_OFF_T % 2147483647 == 1)
? 1 : -1];
@@ -18824,6 +18860,19 @@ $as_echo "no" >&6; }
fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for -Wno-format-truncation" >&5
+$as_echo_n "checking for -Wno-format-truncation... " >&6; }
+if $(${CC} -O2 -Wno-format-truncation -S -o /dev/null -xc /dev/null > /dev/null 2>&1); then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
+$as_echo "yes" >&6; }
+ AST_NO_FORMAT_TRUNCATION=-Wno-format-truncation
+else
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+ AST_NO_FORMAT_TRUNCATION=
+fi
+
+
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for -Wshadow" >&5
$as_echo_n "checking for -Wshadow... " >&6; }
if $(${CC} -Wshadow -S -o /dev/null -xc /dev/null > /dev/null 2>&1); then
@@ -19181,6 +19230,33 @@ fi
rm -f core conftest.err conftest.$ac_objext \
conftest$ac_exeext conftest.$ac_ext
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for BIND_8_COMPAT required" >&5
+$as_echo_n "checking for BIND_8_COMPAT required... " >&6; }
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+#undef BIND_8_COMPAT
+#include <arpa/nameser.h>
+
+int
+main ()
+{
+int x = NXDOMAIN
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_compile "$LINENO"; then :
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+else
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
+$as_echo "yes" >&6; }
+BIND8_CFLAGS=-DBIND_8_COMPAT
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+
+
if test "x${PBX_GLOB_NOMAGIC}" != "x1"; then
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for GLOB_NOMAGIC in glob.h" >&5
@@ -31065,7 +31141,7 @@ if eval \${$as_ac_Lib+:} false; then :
$as_echo_n "(cached) " >&6
else
ac_check_lib_save_LIBS=$LIBS
-LIBS="-lcpg ${pbxlibdir} -lcfg $LIBS"
+LIBS="-lcpg ${pbxlibdir} -lcpg $LIBS"
cat confdefs.h - <<_ACEOF >conftest.$ac_ext
/* end confdefs.h. */
@@ -31107,7 +31183,7 @@ fi
# now check for the header.
if test "${AST_COROSYNC_FOUND}" = "yes"; then
- COROSYNC_LIB="${pbxlibdir} -lcpg -lcfg"
+ COROSYNC_LIB="${pbxlibdir} -lcpg -lcpg"
# if --with-COROSYNC=DIR has been specified, use it.
if test "x${COROSYNC_DIR}" != "x"; then
COROSYNC_INCLUDE="-I${COROSYNC_DIR}/include"
@@ -33205,7 +33281,7 @@ fi
fi
fi
-for ver in 2.0 2.2 2.4 2.6; do
+for ver in 2.0 2.2 2.4 2.6 3.0; do
if test "x${PBX_GMIME}" != "x1" -a "${USE_GMIME}" != "no"; then
diff --git a/configure.ac b/configure.ac
index 120b0f831..6c5f4e3cb 100644
--- a/configure.ac
+++ b/configure.ac
@@ -408,6 +408,7 @@ AC_SUBST(NOISY_BUILD)
AC_SUBST(AST_DEVMODE)
AC_SUBST(AST_DEVMODE_STRICT)
+AST_OPTION_ONLY([download-cache], [AST_DOWNLOAD_CACHE], [cached sound AND external module tarfiles], [])
AST_OPTION_ONLY([sounds-cache], [SOUNDS_CACHE_DIR], [cached sound tarfiles], [])
AST_OPTION_ONLY([externals-cache], [EXTERNALS_CACHE_DIR], [cached external module tarfiles], [])
@@ -1225,6 +1226,16 @@ else
fi
AC_SUBST(AST_NO_STRICT_OVERFLOW)
+AC_MSG_CHECKING(for -Wno-format-truncation)
+if $(${CC} -O2 -Wno-format-truncation -S -o /dev/null -xc /dev/null > /dev/null 2>&1); then
+ AC_MSG_RESULT(yes)
+ AST_NO_FORMAT_TRUNCATION=-Wno-format-truncation
+else
+ AC_MSG_RESULT(no)
+ AST_NO_FORMAT_TRUNCATION=
+fi
+AC_SUBST(AST_NO_FORMAT_TRUNCATION)
+
AC_MSG_CHECKING(for -Wshadow)
if $(${CC} -Wshadow -S -o /dev/null -xc /dev/null > /dev/null 2>&1); then
AC_MSG_RESULT(yes)
@@ -1334,6 +1345,18 @@ AC_LINK_IFELSE(
AC_MSG_RESULT(no)
)
+AC_MSG_CHECKING(for BIND_8_COMPAT required)
+AC_COMPILE_IFELSE([AC_LANG_PROGRAM(
+[[
+#undef BIND_8_COMPAT
+#include <arpa/nameser.h>
+]],
+[[int x = NXDOMAIN]])],
+AC_MSG_RESULT(no),
+AC_MSG_RESULT(yes)
+[BIND8_CFLAGS=-DBIND_8_COMPAT])
+AC_SUBST(BIND8_CFLAGS)
+
AST_C_DEFINE_CHECK([GLOB_NOMAGIC], [GLOB_NOMAGIC], [glob.h])
AST_C_DEFINE_CHECK([GLOB_BRACE], [GLOB_BRACE], [glob.h])
@@ -2351,7 +2374,7 @@ else
AST_EXT_LIB_CHECK([RADIUS], [radiusclient-ng], [rc_read_config], [radiusclient-ng.h])
fi
-AST_EXT_LIB_CHECK([COROSYNC], [cpg], [cpg_join], [corosync/cpg.h], [-lcfg])
+AST_EXT_LIB_CHECK([COROSYNC], [cpg], [cpg_join], [corosync/cpg.h], [-lcpg])
AST_EXT_LIB_CHECK([COROSYNC_CFG_STATE_TRACK], [cfg], [corosync_cfg_state_track], [corosync/cfg.h], [-lcfg])
AST_EXT_LIB_CHECK([SPEEX], [speex], [speex_encode], [speex/speex.h], [-lm])
@@ -2480,7 +2503,7 @@ then
fi
fi
-for ver in 2.0 2.2 2.4 2.6; do
+for ver in 2.0 2.2 2.4 2.6 3.0; do
AST_PKG_CONFIG_CHECK([GMIME], gmime-$ver)
if test "$PBX_GMIME" = 1; then
break;
diff --git a/contrib/ast-db-manage/config/versions/164abbd708c_add_auto_info_to_endpoint_dtmf_mode.py b/contrib/ast-db-manage/config/versions/164abbd708c_add_auto_info_to_endpoint_dtmf_mode.py
new file mode 100644
index 000000000..932773f6d
--- /dev/null
+++ b/contrib/ast-db-manage/config/versions/164abbd708c_add_auto_info_to_endpoint_dtmf_mode.py
@@ -0,0 +1,58 @@
+"""Add auto_info to endpoint dtmf_mode
+
+Revision ID: 164abbd708c
+Revises: 86bb1efa278d
+Create Date: 2017-06-19 13:55:15.354706
+
+"""
+
+# revision identifiers, used by Alembic.
+revision = '164abbd708c'
+down_revision = 'd7983954dd96'
+
+from alembic import op
+import sqlalchemy as sa
+from sqlalchemy.dialects.postgresql import ENUM
+
+OLD_ENUM = ['rfc4733', 'inband', 'info', 'auto']
+NEW_ENUM = ['rfc4733', 'inband', 'info', 'auto', 'auto_info']
+
+old_type = sa.Enum(*OLD_ENUM, name='pjsip_dtmf_mode_values_v2')
+new_type = sa.Enum(*NEW_ENUM, name='pjsip_dtmf_mode_values_v3')
+
+def upgrade():
+ context = op.get_context()
+
+ # Upgrading to this revision WILL clear your directmedia values.
+ if context.bind.dialect.name != 'postgresql':
+ op.alter_column('ps_endpoints', 'dtmf_mode',
+ type_=new_type,
+ existing_type=old_type)
+ else:
+ enum = ENUM('rfc4733', 'inband', 'info', 'auto', 'auto_info',
+ name='pjsip_dtmf_mode_values_v3')
+ enum.create(op.get_bind(), checkfirst=False)
+
+ op.execute('ALTER TABLE ps_endpoints ALTER COLUMN dtmf_mode TYPE'
+ ' pjsip_dtmf_mode_values_v3 USING'
+ ' dtmf_mode::text::pjsip_dtmf_mode_values_v3')
+
+ ENUM(name="pjsip_dtmf_mode_values_v2").drop(op.get_bind(), checkfirst=False)
+
+def downgrade():
+ context = op.get_context()
+
+ if context.bind.dialect.name != 'postgresql':
+ op.alter_column('ps_endpoints', 'dtmf_mode',
+ type_=old_type,
+ existing_type=new_type)
+ else:
+ enum = ENUM('rfc4733', 'inband', 'info', 'auto',
+ name='pjsip_dtmf_mode_values_v2')
+ enum.create(op.get_bind(), checkfirst=False)
+
+ op.execute('ALTER TABLE ps_endpoints ALTER COLUMN dtmf_mode TYPE'
+ ' pjsip_dtmf_mode_values USING'
+ ' dtmf_mode::text::pjsip_dtmf_mode_values_v2')
+
+ ENUM(name="pjsip_dtmf_mode_values_v3").drop(op.get_bind(), checkfirst=False)
diff --git a/contrib/ast-db-manage/config/versions/b83645976fdd_add_dtls_fingerprint_to_ps_endpoints.py b/contrib/ast-db-manage/config/versions/b83645976fdd_add_dtls_fingerprint_to_ps_endpoints.py
new file mode 100644
index 000000000..3d4f74de5
--- /dev/null
+++ b/contrib/ast-db-manage/config/versions/b83645976fdd_add_dtls_fingerprint_to_ps_endpoints.py
@@ -0,0 +1,38 @@
+"""add dtls_fingerprint to ps_endpoints
+
+Revision ID: b83645976fdd
+Revises: f3d1c5d38b56
+Create Date: 2017-08-03 09:01:49.558111
+
+"""
+
+# revision identifiers, used by Alembic.
+revision = 'b83645976fdd'
+down_revision = 'f3d1c5d38b56'
+
+from alembic import op
+import sqlalchemy as sa
+from sqlalchemy.dialects.postgresql import ENUM
+
+SHA_HASH_NAME = 'sha_hash_values'
+SHA_HASH_VALUES = ['SHA-1', 'SHA-256']
+
+def upgrade():
+ context = op.get_context()
+
+ if context.bind.dialect.name == 'postgresql':
+ enum = ENUM(*SHA_HASH_VALUES, name=SHA_HASH_NAME)
+ enum.create(op.get_bind(), checkfirst=False)
+
+ op.add_column('ps_endpoints',
+ sa.Column('dtls_fingerprint', ENUM(*SHA_HASH_VALUES,
+ name=SHA_HASH_NAME, create_type=False)))
+
+def downgrade():
+ context = op.get_context()
+
+ op.drop_column('ps_endpoints', 'dtls_fingerprint')
+
+ if context.bind.dialect.name == 'postgresql':
+ enum = ENUM(*SHA_HASH_VALUES, name=SHA_HASH_NAME)
+ enum.drop(op.get_bind(), checkfirst=False)
diff --git a/contrib/ast-db-manage/config/versions/d7983954dd96_add_ps_endpoints_notify_early_inuse_.py b/contrib/ast-db-manage/config/versions/d7983954dd96_add_ps_endpoints_notify_early_inuse_.py
new file mode 100644
index 000000000..e1dcdd133
--- /dev/null
+++ b/contrib/ast-db-manage/config/versions/d7983954dd96_add_ps_endpoints_notify_early_inuse_.py
@@ -0,0 +1,30 @@
+"""add ps_endpoints.notify_early_inuse_ringing
+
+Revision ID: d7983954dd96
+Revises: 86bb1efa278d
+Create Date: 2017-06-05 15:44:41.152280
+
+"""
+
+# revision identifiers, used by Alembic.
+revision = 'd7983954dd96'
+down_revision = '86bb1efa278d'
+
+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_endpoints', sa.Column('notify_early_inuse_ringing', yesno_values))
+
+def downgrade():
+ op.drop_column('ps_endpoints', 'notify_early_inuse_ringing')
diff --git a/contrib/ast-db-manage/config/versions/f3d1c5d38b56_add_prune_on_boot.py b/contrib/ast-db-manage/config/versions/f3d1c5d38b56_add_prune_on_boot.py
new file mode 100644
index 000000000..759664616
--- /dev/null
+++ b/contrib/ast-db-manage/config/versions/f3d1c5d38b56_add_prune_on_boot.py
@@ -0,0 +1,31 @@
+"""add_prune_on_boot
+
+Revision ID: f3d1c5d38b56
+Revises: 164abbd708c
+Create Date: 2017-08-04 17:31:23.124767
+
+"""
+
+# revision identifiers, used by Alembic.
+revision = 'f3d1c5d38b56'
+down_revision = '164abbd708c'
+
+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_contacts', sa.Column('prune_on_boot', yesno_values))
+
+
+def downgrade():
+ op.drop_column('ps_contacts', 'prune_on_boot')
diff --git a/contrib/scripts/install_prereq b/contrib/scripts/install_prereq
index fb240890b..d69f5527a 100755
--- a/contrib/scripts/install_prereq
+++ b/contrib/scripts/install_prereq
@@ -26,7 +26,7 @@ PACKAGES_DEBIAN="$PACKAGES_DEBIAN libncurses-dev libz-dev libssl-dev libxml2-dev
PACKAGES_DEBIAN="$PACKAGES_DEBIAN libcurl-dev libspeex-dev libspeexdsp-dev libogg-dev libvorbis-dev libasound2-dev portaudio19-dev libcurl4-openssl-dev"
PACKAGES_DEBIAN="$PACKAGES_DEBIAN libpq-dev unixodbc-dev libsqlite0-dev libmysqlclient15-dev libneon27-dev libgmime-dev libusb-dev liblua5.1-0-dev lua5.1"
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 libsnmp-dev libiksemel-dev libcorosync-dev libcpg-dev libcfg-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 automake libsrtp-dev libncurses5-dev python-dev"
PACKAGES_RH="automake bzip2 gcc gcc-c++ patch ncurses-devel openssl-devel libxml2-devel unixODBC-devel libcurl-devel libogg-devel libvorbis-devel speex-devel"
diff --git a/formats/format_g719.c b/formats/format_g719.c
index 667858587..e27822df7 100644
--- a/formats/format_g719.c
+++ b/formats/format_g719.c
@@ -42,20 +42,15 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
static struct ast_frame *g719read(struct ast_filestream *s, int *whennext)
{
- int res;
- /* Send a frame from the file to the appropriate channel */
+ size_t res;
+ /* Send a frame from the file to the appropriate channel */
AST_FRAME_SET_BUFFER(&s->fr, s->buf, AST_FRIENDLY_OFFSET, BUF_SIZE);
if ((res = fread(s->fr.data.ptr, 1, s->fr.datalen, s->f)) != s->fr.datalen) {
- if (feof(s->f)) {
- if (res) {
- ast_debug(3, "Incomplete frame data at end of %s file "
- "(expected %d bytes, read %d)\n",
- ast_format_get_name(s->fr.subclass.format), s->fr.datalen, res);
- }
- } else {
- ast_log(LOG_ERROR, "Error while reading %s file: %s\n",
- ast_format_get_name(s->fr.subclass.format), strerror(errno));
+ if (res) {
+ ast_log(LOG_WARNING, "Short read of %s data (expected %d bytes, read %zu): %s\n",
+ ast_format_get_name(s->fr.subclass.format), s->fr.datalen, res,
+ strerror(errno));
}
return NULL;
}
diff --git a/formats/format_g723.c b/formats/format_g723.c
index 11821246c..9b770336d 100644
--- a/formats/format_g723.c
+++ b/formats/format_g723.c
@@ -42,7 +42,7 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
static struct ast_frame *g723_read(struct ast_filestream *s, int *whennext)
{
unsigned short size;
- int res;
+ size_t res;
int delay;
/* Read the delay for the next packet, and schedule again if necessary */
/* XXX is this ignored ? */
@@ -67,15 +67,10 @@ static struct ast_frame *g723_read(struct ast_filestream *s, int *whennext)
/* Read the data into the buffer */
AST_FRAME_SET_BUFFER(&s->fr, s->buf, AST_FRIENDLY_OFFSET, size);
if ((res = fread(s->fr.data.ptr, 1, s->fr.datalen, s->f)) != s->fr.datalen) {
- if (feof(s->f)) {
- if (res) {
- ast_debug(3, "Incomplete frame data at end of %s file "
- "(expected %d bytes, read %d)\n",
- ast_format_get_name(s->fr.subclass.format), s->fr.datalen, res);
- }
- } else {
- ast_log(LOG_ERROR, "Error while reading %s file: %s\n",
- ast_format_get_name(s->fr.subclass.format), strerror(errno));
+ if (res) {
+ ast_log(LOG_WARNING, "Short read of %s data (expected %d bytes, read %zu): %s\n",
+ ast_format_get_name(s->fr.subclass.format), s->fr.datalen, res,
+ strerror(errno));
}
return NULL;
}
diff --git a/formats/format_g726.c b/formats/format_g726.c
index 94623f441..50b558ec4 100644
--- a/formats/format_g726.c
+++ b/formats/format_g726.c
@@ -119,22 +119,17 @@ static int g726_16_rewrite(struct ast_filestream *s, const char *comment)
static struct ast_frame *g726_read(struct ast_filestream *s, int *whennext)
{
- int res;
+ size_t res;
struct g726_desc *fs = (struct g726_desc *)s->_private;
/* Send a frame from the file to the appropriate channel */
AST_FRAME_SET_BUFFER(&s->fr, s->buf, AST_FRIENDLY_OFFSET, frame_size[fs->rate]);
s->fr.samples = 8 * FRAME_TIME;
if ((res = fread(s->fr.data.ptr, 1, s->fr.datalen, s->f)) != s->fr.datalen) {
- if (feof(s->f)) {
- if (res) {
- ast_debug(3, "Incomplete frame data at end of %s file "
- "(expected %d bytes, read %d)\n",
- ast_format_get_name(s->fr.subclass.format), s->fr.datalen, res);
- }
- } else {
- ast_log(LOG_ERROR, "Error while reading %s file: %s\n",
- ast_format_get_name(s->fr.subclass.format), strerror(errno));
+ if (res) {
+ ast_log(LOG_WARNING, "Short read of %s data (expected %d bytes, read %zu): %s\n",
+ ast_format_get_name(s->fr.subclass.format), s->fr.datalen, res,
+ strerror(errno));
}
return NULL;
}
diff --git a/formats/format_g729.c b/formats/format_g729.c
index 04dfbcfda..35c68bd0c 100644
--- a/formats/format_g729.c
+++ b/formats/format_g729.c
@@ -19,7 +19,7 @@
/*! \file
*
* \brief Save to raw, headerless G729 data.
- * \note This is not an encoder/decoder. The codec fo g729 is only
+ * \note This is not an encoder/decoder. The codec for g729 is only
* available with a commercial license from Digium, due to patent
* restrictions. Check http://www.digium.com for information.
* \arg Extensions: g729
@@ -48,20 +48,16 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
static struct ast_frame *g729_read(struct ast_filestream *s, int *whennext)
{
- int res;
+ size_t res;
+
/* Send a frame from the file to the appropriate channel */
s->fr.samples = G729A_SAMPLES;
AST_FRAME_SET_BUFFER(&s->fr, s->buf, AST_FRIENDLY_OFFSET, BUF_SIZE);
if ((res = fread(s->fr.data.ptr, 1, s->fr.datalen, s->f)) != s->fr.datalen) {
- if (feof(s->f)) {
- if (res) {
- ast_debug(3, "Incomplete frame data at end of %s file "
- "(expected %d bytes, read %d)\n",
- ast_format_get_name(s->fr.subclass.format), s->fr.datalen, res);
- }
- } else {
- ast_log(LOG_ERROR, "Error while reading %s file: %s\n",
- ast_format_get_name(s->fr.subclass.format), strerror(errno));
+ if (res && res != 10) /* XXX what for ? */ {
+ ast_log(LOG_WARNING, "Short read of %s data (expected %d bytes, read %zu): %s\n",
+ ast_format_get_name(s->fr.subclass.format), s->fr.datalen, res,
+ strerror(errno));
}
return NULL;
}
diff --git a/formats/format_gsm.c b/formats/format_gsm.c
index cfc9452ef..783d9553e 100644
--- a/formats/format_gsm.c
+++ b/formats/format_gsm.c
@@ -55,19 +55,14 @@ static const char gsm_silence[] = /* 33 */
static struct ast_frame *gsm_read(struct ast_filestream *s, int *whennext)
{
- int res;
+ size_t res;
AST_FRAME_SET_BUFFER(&(s->fr), s->buf, AST_FRIENDLY_OFFSET, GSM_FRAME_SIZE);
if ((res = fread(s->fr.data.ptr, 1, GSM_FRAME_SIZE, s->f)) != GSM_FRAME_SIZE) {
- if (feof(s->f)) {
- if (res) {
- ast_debug(3, "Incomplete frame data at end of %s file "
- "(expected %d bytes, read %d)\n",
- ast_format_get_name(s->fr.subclass.format), GSM_FRAME_SIZE, res);
- }
- } else {
- ast_log(LOG_ERROR, "Error while reading %s file: %s\n",
- ast_format_get_name(s->fr.subclass.format), strerror(errno));
+ if (res) {
+ ast_log(LOG_WARNING, "Short read of %s data (expected %d bytes, read %zu): %s\n",
+ ast_format_get_name(s->fr.subclass.format), GSM_FRAME_SIZE, res,
+ strerror(errno));
}
return NULL;
}
diff --git a/formats/format_h263.c b/formats/format_h263.c
index 5d59972de..be8e1df7a 100644
--- a/formats/format_h263.c
+++ b/formats/format_h263.c
@@ -69,7 +69,7 @@ static int h263_open(struct ast_filestream *s)
static struct ast_frame *h263_read(struct ast_filestream *s, int *whennext)
{
- int res;
+ size_t res;
uint32_t mark;
unsigned short len;
unsigned int ts;
@@ -87,15 +87,10 @@ static struct ast_frame *h263_read(struct ast_filestream *s, int *whennext)
}
AST_FRAME_SET_BUFFER(&s->fr, s->buf, AST_FRIENDLY_OFFSET, len);
if ((res = fread(s->fr.data.ptr, 1, s->fr.datalen, s->f)) != s->fr.datalen) {
- if (feof(s->f)) {
- if (res) {
- ast_debug(3, "Incomplete frame data at end of %s file "
- "(expected %d bytes, read %d)\n",
- ast_format_get_name(s->fr.subclass.format), s->fr.datalen, res);
- }
- } else {
- ast_log(LOG_ERROR, "Error while reading %s file: %s\n",
- ast_format_get_name(s->fr.subclass.format), strerror(errno));
+ if (res) {
+ ast_log(LOG_WARNING, "Short read of %s data (expected %d bytes, read %zu): %s\n",
+ ast_format_get_name(s->fr.subclass.format), s->fr.datalen, res,
+ strerror(errno));
}
return NULL;
}
diff --git a/formats/format_h264.c b/formats/format_h264.c
index f8906f423..30604004e 100644
--- a/formats/format_h264.c
+++ b/formats/format_h264.c
@@ -61,7 +61,7 @@ static int h264_open(struct ast_filestream *s)
static struct ast_frame *h264_read(struct ast_filestream *s, int *whennext)
{
- int res;
+ size_t res;
int mark = 0;
unsigned short len;
unsigned int ts;
@@ -79,15 +79,10 @@ static struct ast_frame *h264_read(struct ast_filestream *s, int *whennext)
}
AST_FRAME_SET_BUFFER(&s->fr, s->buf, AST_FRIENDLY_OFFSET, len);
if ((res = fread(s->fr.data.ptr, 1, s->fr.datalen, s->f)) != s->fr.datalen) {
- if (feof(s->f)) {
- if (res) {
- ast_debug(3, "Incomplete frame data at end of %s file "
- "(expected %d bytes, read %d)\n",
- ast_format_get_name(s->fr.subclass.format), s->fr.datalen, res);
- }
- } else {
- ast_log(LOG_ERROR, "Error while reading %s file: %s\n",
- ast_format_get_name(s->fr.subclass.format), strerror(errno));
+ if (res) {
+ ast_log(LOG_WARNING, "Short read of %s data (expected %d bytes, read %zu): %s\n",
+ ast_format_get_name(s->fr.subclass.format), s->fr.datalen, res,
+ strerror(errno));
}
return NULL;
}
diff --git a/formats/format_ilbc.c b/formats/format_ilbc.c
index 6e06ef335..d4fbe96e7 100644
--- a/formats/format_ilbc.c
+++ b/formats/format_ilbc.c
@@ -47,19 +47,15 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
static struct ast_frame *ilbc_read(struct ast_filestream *s, int *whennext)
{
- int res;
+ size_t res;
+
/* Send a frame from the file to the appropriate channel */
AST_FRAME_SET_BUFFER(&s->fr, s->buf, AST_FRIENDLY_OFFSET, ILBC_BUF_SIZE);
if ((res = fread(s->fr.data.ptr, 1, s->fr.datalen, s->f)) != s->fr.datalen) {
- if (feof(s->f)) {
- if (res) {
- ast_debug(3, "Incomplete frame data at end of %s file "
- "(expected %d bytes, read %d)\n",
- ast_format_get_name(s->fr.subclass.format), s->fr.datalen, res);
- }
- } else {
- ast_log(LOG_ERROR, "Error while reading %s file: %s\n",
- ast_format_get_name(s->fr.subclass.format), strerror(errno));
+ if (res) {
+ ast_log(LOG_WARNING, "Short read of %s data (expected %d bytes, read %zu): %s\n",
+ ast_format_get_name(s->fr.subclass.format), s->fr.datalen, res,
+ strerror(errno));
}
return NULL;
}
diff --git a/formats/format_pcm.c b/formats/format_pcm.c
index bd7cf7776..7b0648234 100644
--- a/formats/format_pcm.c
+++ b/formats/format_pcm.c
@@ -80,21 +80,15 @@ static int pcma_rewrite(struct ast_filestream *s, const char *comment)
static struct ast_frame *pcm_read(struct ast_filestream *s, int *whennext)
{
- int res;
-
- /* Send a frame from the file to the appropriate channel */
+ size_t res;
+ /* Send a frame from the file to the appropriate channel */
AST_FRAME_SET_BUFFER(&s->fr, s->buf, AST_FRIENDLY_OFFSET, BUF_SIZE);
- if ((res = fread(s->fr.data.ptr, 1, s->fr.datalen, s->f)) != s->fr.datalen) {
- if (feof(s->f)) {
- if (res) {
- ast_debug(3, "Incomplete frame data at end of %s file "
- "(expected %d bytes, read %d)\n",
- ast_format_get_name(s->fr.subclass.format), s->fr.datalen, res);
- }
- } else {
- ast_log(LOG_ERROR, "Error while reading %s file: %s\n",
- ast_format_get_name(s->fr.subclass.format), strerror(errno));
+ if ((res = fread(s->fr.data.ptr, 1, s->fr.datalen, s->f)) < 1) {
+ if (res) {
+ ast_log(LOG_WARNING, "Short read of %s data (expected %d bytes, read %zu): %s\n",
+ ast_format_get_name(s->fr.subclass.format), s->fr.datalen, res,
+ strerror(errno));
}
return NULL;
}
diff --git a/formats/format_siren14.c b/formats/format_siren14.c
index 5aaa1f130..3e42bef9a 100644
--- a/formats/format_siren14.c
+++ b/formats/format_siren14.c
@@ -42,20 +42,15 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
static struct ast_frame *siren14read(struct ast_filestream *s, int *whennext)
{
- int res;
- /* Send a frame from the file to the appropriate channel */
+ size_t res;
+ /* Send a frame from the file to the appropriate channel */
AST_FRAME_SET_BUFFER(&s->fr, s->buf, AST_FRIENDLY_OFFSET, BUF_SIZE);
if ((res = fread(s->fr.data.ptr, 1, s->fr.datalen, s->f)) != s->fr.datalen) {
- if (feof(s->f)) {
- if (res) {
- ast_debug(3, "Incomplete frame data at end of %s file "
- "(expected %d bytes, read %d)\n",
- ast_format_get_name(s->fr.subclass.format), s->fr.datalen, res);
- }
- } else {
- ast_log(LOG_ERROR, "Error while reading %s file: %s\n",
- ast_format_get_name(s->fr.subclass.format), strerror(errno));
+ if (res) {
+ ast_log(LOG_WARNING, "Short read of %s data (expected %d bytes, read %zu): %s\n",
+ ast_format_get_name(s->fr.subclass.format), s->fr.datalen, res,
+ strerror(errno));
}
return NULL;
}
diff --git a/formats/format_siren7.c b/formats/format_siren7.c
index 87e1372b3..f1bde0012 100644
--- a/formats/format_siren7.c
+++ b/formats/format_siren7.c
@@ -42,20 +42,15 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
static struct ast_frame *siren7read(struct ast_filestream *s, int *whennext)
{
- int res;
- /* Send a frame from the file to the appropriate channel */
+ size_t res;
+ /* Send a frame from the file to the appropriate channel */
AST_FRAME_SET_BUFFER(&s->fr, s->buf, AST_FRIENDLY_OFFSET, BUF_SIZE);
if ((res = fread(s->fr.data.ptr, 1, s->fr.datalen, s->f)) != s->fr.datalen) {
- if (feof(s->f)) {
- if (res) {
- ast_debug(3, "Incomplete frame data at end of %s file "
- "(expected %d bytes, read %d)\n",
- ast_format_get_name(s->fr.subclass.format), s->fr.datalen, res);
- }
- } else {
- ast_log(LOG_ERROR, "Error while reading %s file: %s\n",
- ast_format_get_name(s->fr.subclass.format), strerror(errno));
+ if (res) {
+ ast_log(LOG_WARNING, "Short read of %s data (expected %d bytes, read %zu): %s\n",
+ ast_format_get_name(s->fr.subclass.format), s->fr.datalen, res,
+ strerror(errno));
}
return NULL;
}
diff --git a/formats/format_sln.c b/formats/format_sln.c
index 2f4cc57ea..48bad8ae7 100644
--- a/formats/format_sln.c
+++ b/formats/format_sln.c
@@ -36,20 +36,15 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
static struct ast_frame *generic_read(struct ast_filestream *s, int *whennext, unsigned int buf_size)
{
- int res;
- /* Send a frame from the file to the appropriate channel */
+ size_t res;
+ /* Send a frame from the file to the appropriate channel */
AST_FRAME_SET_BUFFER(&s->fr, s->buf, AST_FRIENDLY_OFFSET, buf_size);
- if ((res = fread(s->fr.data.ptr, 1, s->fr.datalen, s->f)) != s->fr.datalen) {
- if (feof(s->f)) {
- if (res) {
- ast_debug(3, "Incomplete frame data at end of %s file "
- "(expected %d bytes, read %d)\n",
- ast_format_get_name(s->fr.subclass.format), s->fr.datalen, res);
- }
- } else {
- ast_log(LOG_ERROR, "Error while reading %s file: %s\n",
- ast_format_get_name(s->fr.subclass.format), strerror(errno));
+ if ((res = fread(s->fr.data.ptr, 1, s->fr.datalen, s->f)) < 1) {
+ if (res) {
+ ast_log(LOG_WARNING, "Short read of %s data (expected %d bytes, read %zu): %s\n",
+ ast_format_get_name(s->fr.subclass.format), s->fr.datalen, res,
+ strerror(errno));
}
return NULL;
}
diff --git a/formats/format_vox.c b/formats/format_vox.c
index 26d4169e1..813dabf21 100644
--- a/formats/format_vox.c
+++ b/formats/format_vox.c
@@ -42,20 +42,15 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
static struct ast_frame *vox_read(struct ast_filestream *s, int *whennext)
{
- int res;
+ size_t res;
/* Send a frame from the file to the appropriate channel */
AST_FRAME_SET_BUFFER(&s->fr, s->buf, AST_FRIENDLY_OFFSET, BUF_SIZE);
- if ((res = fread(s->fr.data.ptr, 1, s->fr.datalen, s->f)) != s->fr.datalen) {
- if (feof(s->f)) {
- if (res) {
- ast_debug(3, "Incomplete frame data at end of %s file "
- "(expected %d bytes, read %d)\n",
- ast_format_get_name(s->fr.subclass.format), s->fr.datalen, res);
- }
- } else {
- ast_log(LOG_ERROR, "Error while reading %s file: %s\n",
- ast_format_get_name(s->fr.subclass.format), strerror(errno));
+ if ((res = fread(s->fr.data.ptr, 1, s->fr.datalen, s->f)) < 1) {
+ if (res) {
+ ast_log(LOG_WARNING, "Short read of %s data (expected %d bytes, read %zu): %s\n",
+ ast_format_get_name(s->fr.subclass.format), s->fr.datalen, res,
+ strerror(errno));
}
return NULL;
}
diff --git a/formats/format_wav.c b/formats/format_wav.c
index 2903992ab..cead61c5b 100644
--- a/formats/format_wav.c
+++ b/formats/format_wav.c
@@ -371,7 +371,7 @@ static void wav_close(struct ast_filestream *s)
static struct ast_frame *wav_read(struct ast_filestream *s, int *whennext)
{
- int res;
+ size_t res;
int samples; /* actual samples read */
#if __BYTE_ORDER == __BIG_ENDIAN
int x;
@@ -393,16 +393,11 @@ static struct ast_frame *wav_read(struct ast_filestream *s, int *whennext)
/* ast_debug(1, "here: %d, maxlen: %d, bytes: %d\n", here, s->maxlen, bytes); */
AST_FRAME_SET_BUFFER(&s->fr, s->buf, AST_FRIENDLY_OFFSET, bytes);
- if ((res = fread(s->fr.data.ptr, 1, s->fr.datalen, s->f)) != s->fr.datalen) {
- if (feof(s->f)) {
- if (res) {
- ast_debug(3, "Incomplete frame data at end of %s file "
- "(expected %d bytes, read %d)\n",
- ast_format_get_name(s->fr.subclass.format), s->fr.datalen, res);
- }
- } else {
- ast_log(LOG_ERROR, "Error while reading %s file: %s\n",
- ast_format_get_name(s->fr.subclass.format), strerror(errno));
+ if ((res = fread(s->fr.data.ptr, 1, s->fr.datalen, s->f)) == 0) {
+ if (res) {
+ ast_log(LOG_WARNING, "Short read of %s data (expected %d bytes, read %zu): %s\n",
+ ast_format_get_name(s->fr.subclass.format), s->fr.datalen, res,
+ strerror(errno));
}
return NULL;
}
diff --git a/formats/format_wav_gsm.c b/formats/format_wav_gsm.c
index 2f80a9a46..423dfe48a 100644
--- a/formats/format_wav_gsm.c
+++ b/formats/format_wav_gsm.c
@@ -421,18 +421,13 @@ static struct ast_frame *wav_read(struct ast_filestream *s, int *whennext)
} else {
/* read and convert */
unsigned char msdata[MSGSM_FRAME_SIZE];
- int res;
-
+ size_t res;
+
if ((res = fread(msdata, 1, MSGSM_FRAME_SIZE, s->f)) != MSGSM_FRAME_SIZE) {
- if (feof(s->f)) {
- if (res) {
- ast_debug(3, "Incomplete frame data at end of %s file "
- "(expected %d bytes, read %d)\n",
- ast_format_get_name(s->fr.subclass.format), MSGSM_FRAME_SIZE, res);
- }
- } else {
- ast_log(LOG_ERROR, "Error while reading %s file: %s\n",
- ast_format_get_name(s->fr.subclass.format), strerror(errno));
+ if (res && res != 1) {
+ ast_log(LOG_WARNING, "Short read of %s data (expected %d bytes, read %zu): %s\n",
+ ast_format_get_name(s->fr.subclass.format), MSGSM_FRAME_SIZE, res,
+ strerror(errno));
}
return NULL;
}
diff --git a/funcs/func_cdr.c b/funcs/func_cdr.c
index 76d468bb8..f70485746 100644
--- a/funcs/func_cdr.c
+++ b/funcs/func_cdr.c
@@ -282,7 +282,7 @@ static void cdr_read_callback(void *data, struct stasis_subscription *sub, struc
if (ast_strlen_zero(ast_channel_name(payload->chan))) {
/* Format request on a dummy channel */
- ast_cdr_format_var(ast_channel_cdr(payload->chan), args.variable, &value, tempbuf, sizeof(tempbuf), 0);
+ ast_cdr_format_var(ast_channel_cdr(payload->chan), args.variable, &value, tempbuf, sizeof(tempbuf), ast_test_flag(&flags, OPT_UNPARSED));
if (ast_strlen_zero(value)) {
return;
}
diff --git a/funcs/func_shell.c b/funcs/func_shell.c
index e403efc2e..79b7f9940 100644
--- a/funcs/func_shell.c
+++ b/funcs/func_shell.c
@@ -84,6 +84,11 @@ static int shell_helper(struct ast_channel *chan, const char *cmd, char *data,
<syntax>
<parameter name="command" required="true">
<para>The command that the shell should execute.</para>
+ <warning><para>Do not use untrusted strings such as <variable>CALLERID(num)</variable>
+ or <variable>CALLERID(name)</variable> as part of the command parameters. You
+ risk a command injection attack executing arbitrary commands if the untrusted
+ strings aren't filtered to remove dangerous characters. See function
+ <variable>FILTER()</variable>.</para></warning>
</parameter>
</syntax>
<description>
diff --git a/include/asterisk/app.h b/include/asterisk/app.h
index 86336e32b..5b10b1c1c 100644
--- a/include/asterisk/app.h
+++ b/include/asterisk/app.h
@@ -871,9 +871,34 @@ int ast_vm_test_destroy_user(const char *context, const char *mailbox);
int ast_vm_test_create_user(const char *context, const char *mailbox);
#endif
-/*! \brief Safely spawn an external program while closing file descriptors
- \note This replaces the \b system call in all Asterisk modules
-*/
+/*!
+ * \brief Safely spawn an external program while closing file descriptors
+ *
+ * \note This replaces the \b execvp call in all Asterisk modules
+ *
+ * \param dualfork Non-zero to simulate running the program in the
+ * background by forking twice. The option provides similar
+ * functionality to the '&' in the OS shell command "cmd &". The
+ * option allows Asterisk to run a reaper loop to watch the first fork
+ * which immediately exits after spaning the second fork. The actual
+ * program is run in the second fork.
+ * \param file execvp(file, argv) file parameter
+ * \param argv execvp(file, argv) argv parameter
+ */
+int ast_safe_execvp(int dualfork, const char *file, char *const argv[]);
+
+/*!
+ * \brief Safely spawn an OS shell command while closing file descriptors
+ *
+ * \note This replaces the \b system call in all Asterisk modules
+ *
+ * \param s - OS shell command string to execute.
+ *
+ * \warning Command injection can happen using this call if the passed
+ * in string is created using untrusted data from an external source.
+ * It is best not to use untrusted data. However, the caller could
+ * filter out dangerous characters to avoid command injection.
+ */
int ast_safe_system(const char *s);
/*!
diff --git a/include/asterisk/ari.h b/include/asterisk/ari.h
index cad9b32c5..f83df0469 100644
--- a/include/asterisk/ari.h
+++ b/include/asterisk/ari.h
@@ -266,4 +266,14 @@ void ast_ari_response_created(struct ast_ari_response *response,
*/
void ast_ari_response_alloc_failed(struct ast_ari_response *response);
+/*! \brief Determines whether the res_ari module is loaded */
+#define CHECK_ARI_MODULE_LOADED() \
+ do { \
+ if (!ast_module_check("res_ari.so") \
+ || !ast_ari_oom_json()) { \
+ return AST_MODULE_LOAD_DECLINE; \
+ } \
+ } while(0)
+
+
#endif /* _ASTERISK_ARI_H */
diff --git a/include/asterisk/bridge_after.h b/include/asterisk/bridge_after.h
index 53f30b9ad..045168571 100644
--- a/include/asterisk/bridge_after.h
+++ b/include/asterisk/bridge_after.h
@@ -45,6 +45,8 @@ enum ast_bridge_after_cb_reason {
AST_BRIDGE_AFTER_CB_REASON_DEPART,
/*! Was explicitly removed by external code. */
AST_BRIDGE_AFTER_CB_REASON_REMOVED,
+ /*! The channel failed to enter the bridge. */
+ AST_BRIDGE_AFTER_CB_REASON_IMPART_FAILED,
};
/*!
diff --git a/include/asterisk/bridge_channel.h b/include/asterisk/bridge_channel.h
index 55c2b3a76..a16695e07 100644
--- a/include/asterisk/bridge_channel.h
+++ b/include/asterisk/bridge_channel.h
@@ -145,6 +145,8 @@ struct ast_bridge_channel {
AST_LIST_ENTRY(ast_bridge_channel) entry;
/*! Queue of outgoing frames to the channel. */
AST_LIST_HEAD_NOLOCK(, ast_frame) wr_queue;
+ /*! Queue of deferred frames, queued onto channel when other party joins. */
+ AST_LIST_HEAD_NOLOCK(, ast_frame) deferred_queue;
/*! Pipe to alert thread when frames are put into the wr_queue. */
int alert_pipe[2];
/*!
diff --git a/include/asterisk/bridge_channel_internal.h b/include/asterisk/bridge_channel_internal.h
index fb8e781e8..ba71e9fc4 100644
--- a/include/asterisk/bridge_channel_internal.h
+++ b/include/asterisk/bridge_channel_internal.h
@@ -98,6 +98,17 @@ void bridge_channel_settle_owed_events(struct ast_bridge *orig_bridge, struct as
/*!
* \internal
+ * \brief Queue any deferred frames on the channel.
+ * \since 13.17.0
+ *
+ * \param bridge_channel Channel that the deferred frames should be pulled from and queued to.
+ *
+ * \return Nothing
+ */
+void bridge_channel_queue_deferred_frames(struct ast_bridge_channel *bridge_channel);
+
+/*!
+ * \internal
* \brief Push the bridge channel into its specified bridge.
* \since 12.0.0
*
diff --git a/include/asterisk/bridge_technology.h b/include/asterisk/bridge_technology.h
index 402b54e98..5add4551f 100644
--- a/include/asterisk/bridge_technology.h
+++ b/include/asterisk/bridge_technology.h
@@ -156,6 +156,9 @@ struct ast_bridge_technology {
* \retval -1 Frame needs to be deferred.
*
* \note On entry, bridge is already locked.
+ *
+ * \note Deferred frames will be automatically queued onto the channel when another
+ * channel joins the bridge.
*/
int (*write)(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel, struct ast_frame *frame);
/*!
diff --git a/include/asterisk/config.h b/include/asterisk/config.h
index 4dc473cd7..c61cee5d2 100644
--- a/include/asterisk/config.h
+++ b/include/asterisk/config.h
@@ -1086,6 +1086,11 @@ enum ast_parse_flags {
PARSE_UINT16 = 0x0005,
#endif
+ /* Returns an int processed by ast_app_parse_timelen.
+ * The first argument is an enum ast_timelen value (required).
+ */
+ PARSE_TIMELEN = 0x0006,
+
/* Returns a struct ast_sockaddr, with optional default value
* (passed by reference) and port handling (accept, ignore,
* require, forbid). The format is 'ipaddress[:port]'. IPv6 address
@@ -1152,6 +1157,12 @@ enum ast_parse_flags {
* returns 1, b unchanged
* ast_parse_arg("12", PARSE_UINT32|PARSE_IN_RANGE|PARSE_RANGE_DEFAULTS, &a, 1, 10);
* returns 1, a = 10
+ * ast_parse_arg("223", PARSE_TIMELEN|PARSE_IN_RANGE, &a, TIMELEN_SECONDS, -1000, 1000);
+ * returns 0, a = 1000
+ * ast_parse_arg("223", PARSE_TIMELEN|PARSE_IN_RANGE, &a, TIMELEN_SECONDS, -1000, 250000);
+ * returns 0, a = 223000
+ * ast_parse_arg("223", PARSE_TIMELEN|PARSE_IN_RANGE|PARSE_DEFAULT, &a, TIMELEN_SECONDS, 9999, -1000, 250000);
+ * returns 0, a = 9999
* ast_parse_arg("www.foo.biz:44", PARSE_INADDR, &sa);
* returns 0, sa contains address and port
* ast_parse_arg("www.foo.biz", PARSE_INADDR|PARSE_PORT_REQUIRE, &sa);
diff --git a/include/asterisk/config_options.h b/include/asterisk/config_options.h
index 30d0c9142..f4c3db188 100644
--- a/include/asterisk/config_options.h
+++ b/include/asterisk/config_options.h
@@ -467,6 +467,31 @@ enum aco_option_type {
* {endcode}
*/
OPT_YESNO_T,
+
+ /*! \brief Type for default option handler for time length signed integers
+ *
+ * \note aco_option_register flags:
+ * See flags available for use with the PARSE_TIMELEN type for the ast_parse_arg function
+ * aco_option_register varargs:
+ * FLDSET macro with the field of type int
+ * The remaining varargs for should be arguments compatible with the varargs for the
+ * ast_parse_arg function with the PARSE_TIMELEN type and the flags passed in the
+ * aco_option_register flags parameter.
+ *
+ * \note In most situations, it is preferable to not pass the PARSE_DEFAULT flag. If a config
+ * contains an invalid value, it is better to let the config loading fail with warnings so that
+ * the problem is fixed by the administrator.
+ *
+ * Example:
+ * struct test_item {
+ * int timelen;
+ * };
+ * {code}
+ * aco_option_register(&cfg_info, "timelen", ACO_EXACT, my_types, "3", OPT_TIMELEN_T, PARSE_IN_RANGE, FLDSET(struct test_item, intopt), TIMELEN_MILLISECONDS, -10, 10);
+ * {endcode}
+ */
+ OPT_TIMELEN_T,
+
};
/*! \brief A callback function for handling a particular option
diff --git a/include/asterisk/core_local.h b/include/asterisk/core_local.h
index 8557072c6..7d6698336 100644
--- a/include/asterisk/core_local.h
+++ b/include/asterisk/core_local.h
@@ -45,6 +45,8 @@ 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.
*
+ * \deprecated - *DO NOT USE* Please use ast_local_lock_all2 instead.
+ *
* \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.
@@ -60,9 +62,28 @@ void ast_local_lock_all(struct ast_channel *chan, struct ast_channel **outchan,
struct ast_channel **outowner);
/*!
+ * \brief Add a reference to the local channel's private tech, lock the local channel's
+ * private base, and add references and lock both sides of the local channel.
+ *
+ * \note None of these locks should be held prior to calling this function.
+ * \note To undo this process call ast_local_unlock_all2.
+ *
+ * \since 13.17.0, 14.6.0
+ *
+ * \param chan Must be a local channel
+ * \param tech_pvt [out] channel's private tech (ref and lock added)
+ * \param base_chan [out] One side of the local channel (ref and lock added)
+ * \param base_owner [out] Other side of the local channel (ref and lock added)
+ */
+void ast_local_lock_all2(struct ast_channel *chan, void **tech_pvt,
+ struct ast_channel **base_chan, struct ast_channel **base_owner);
+
+/*!
* \brief Unlock the "chan" and "owner" channels on the base private structure
* as well as the base private structure itself.
*
+ * \deprecated - *DO NOT USE* Please use ast_local_unlock_all2 instead.
+ *
* \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.
@@ -74,6 +95,22 @@ void ast_local_lock_all(struct ast_channel *chan, struct ast_channel **outchan,
void ast_local_unlock_all(struct ast_channel *chan);
/*!
+ * \brief Remove a reference to the given local channel's private tech, unlock the given
+ * local channel's private base, and remove references and unlock both sides of
+ * given the local channel.
+ *
+ * \note This function should be used in conjunction with ast_local_lock_all2.
+ *
+ * \since 13.17.0, 14.6.0
+ *
+ * \param tech_pvt channel's private tech (ref and lock removed)
+ * \param base_chan One side of the local channel (ref and lock removed)
+ * \param base_owner Other side of the local channel (ref and lock removed)
+ */
+void ast_local_unlock_all2(void *tech_pvt, struct ast_channel *base_chan,
+ struct ast_channel *base_owner);
+
+/*!
* \brief Get the other local channel in the pair.
* \since 12.0.0
*
diff --git a/include/asterisk/format.h b/include/asterisk/format.h
index 368e4104d..3b48af80d 100644
--- a/include/asterisk/format.h
+++ b/include/asterisk/format.h
@@ -32,7 +32,7 @@ struct ast_format;
/*! \brief Format comparison results */
enum ast_format_cmp_res {
- /*! Both formats are equivalent to eachother */
+ /*! Both formats are equivalent to each other */
AST_FORMAT_CMP_EQUAL = 0,
/*! Both formats are completely different and not the same in any way */
AST_FORMAT_CMP_NOT_EQUAL,
@@ -110,7 +110,7 @@ struct ast_format_interface {
struct ast_format *(* const format_parse_sdp_fmtp)(const struct ast_format *format, const char *attributes);
/*!
- * \brief Generate SDP attribute information from an ast_format_attr structure.
+ * \brief Generate SDP attribute information from an ast_format structure.
*
* \param format The format containing attributes
* \param payload The payload number to place into the fmtp line
diff --git a/include/asterisk/format_cache.h b/include/asterisk/format_cache.h
index ff03bb4aa..d716cea6c 100644
--- a/include/asterisk/format_cache.h
+++ b/include/asterisk/format_cache.h
@@ -184,6 +184,11 @@ extern struct ast_format *ast_format_mp4;
extern struct ast_format *ast_format_vp8;
/*!
+ * \brief Built-in cached vp9 format.
+ */
+extern struct ast_format *ast_format_vp9;
+
+/*!
* \brief Built-in cached jpeg format.
*/
extern struct ast_format *ast_format_jpeg;
diff --git a/include/asterisk/manager.h b/include/asterisk/manager.h
index afd9ca148..a9e960c74 100644
--- a/include/asterisk/manager.h
+++ b/include/asterisk/manager.h
@@ -54,7 +54,7 @@
- \ref manager.c Main manager code file
*/
-#define AMI_VERSION "2.10.0"
+#define AMI_VERSION "2.10.1"
#define DEFAULT_MANAGER_PORT 5038 /* Default port for Asterisk management via TCP */
#define DEFAULT_MANAGER_TLS_PORT 5039 /* Default port for Asterisk management via TCP */
diff --git a/include/asterisk/res_pjsip.h b/include/asterisk/res_pjsip.h
index d4bd52c8f..70b56aefa 100644
--- a/include/asterisk/res_pjsip.h
+++ b/include/asterisk/res_pjsip.h
@@ -98,22 +98,41 @@ struct ast_sip_transport_state {
*/
pj_ssl_cipher ciphers[SIP_TLS_MAX_CIPHERS];
/*!
- * Optional local network information, used for NAT purposes
+ * Optional local network information, used for NAT purposes.
+ * "deny" (set) means that it's in the local network. Use the
+ * ast_sip_transport_is_nonlocal and ast_sip_transport_is_local
+ * macro's.
* \since 13.8.0
*/
struct ast_ha *localnet;
/*!
- * DNS manager for refreshing the external address
+ * DNS manager for refreshing the external signaling address
* \since 13.8.0
*/
- struct ast_dnsmgr_entry *external_address_refresher;
+ struct ast_dnsmgr_entry *external_signaling_address_refresher;
/*!
- * Optional external address information
+ * Optional external signaling address information
* \since 13.8.0
*/
- struct ast_sockaddr external_address;
+ struct ast_sockaddr external_signaling_address;
+ /*!
+ * DNS manager for refreshing the external media address
+ * \since 13.18.0
+ */
+ struct ast_dnsmgr_entry *external_media_address_refresher;
+ /*!
+ * Optional external signaling address information
+ * \since 13.18.0
+ */
+ struct ast_sockaddr external_media_address;
};
+#define ast_sip_transport_is_nonlocal(transport_state, addr) \
+ (!transport_state->localnet || ast_apply_ha(transport_state->localnet, addr) == AST_SENSE_ALLOW)
+
+#define ast_sip_transport_is_local(transport_state, addr) \
+ (transport_state->localnet && ast_apply_ha(transport_state->localnet, addr) != AST_SENSE_ALLOW)
+
/*
* \brief Transport to bind to
*/
@@ -260,6 +279,8 @@ struct ast_sip_contact {
AST_STRING_FIELD_EXTENDED(call_id);
/*! The name of the endpoint that added the contact */
AST_STRING_FIELD_EXTENDED(endpoint_name);
+ /*! If true delete the contact on Asterisk restart/boot */
+ int prune_on_boot;
};
#define CONTACT_STATUS "contact_status"
@@ -363,6 +384,8 @@ enum ast_sip_dtmf_mode {
AST_SIP_DTMF_INFO,
/*! Use SIP 4733 if supported by the other side or INBAND if not */
AST_SIP_DTMF_AUTO,
+ /*! Use SIP 4733 if supported by the other side or INFO DTMF (blech) if not */
+ AST_SIP_DTMF_AUTO_INFO,
};
/*!
@@ -763,6 +786,8 @@ struct ast_sip_endpoint {
unsigned int allow_overlap;
/*! Whether to notifies all the progress details on blind transfer */
unsigned int refer_blind_progress;
+ /*! Whether to notifies dialog-info 'early' on INUSE && RINGING state */
+ unsigned int notify_early_inuse_ringing;
};
/*! URI parameter for symmetric transport */
@@ -1197,6 +1222,9 @@ struct ast_sip_contact *ast_sip_location_retrieve_contact(const char *contact_na
* \param expiration_time Optional expiration time of the contact
* \param path_info Path information
* \param user_agent User-Agent header from REGISTER request
+ * \param via_addr
+ * \param via_port
+ * \param call_id
* \param endpoint The endpoint that resulted in the contact being added
*
* \retval -1 failure
@@ -1220,6 +1248,9 @@ int ast_sip_location_add_contact(struct ast_sip_aor *aor, const char *uri,
* \param expiration_time Optional expiration time of the contact
* \param path_info Path information
* \param user_agent User-Agent header from REGISTER request
+ * \param via_addr
+ * \param via_port
+ * \param call_id
* \param endpoint The endpoint that resulted in the contact being added
*
* \retval -1 failure
@@ -1234,6 +1265,31 @@ int ast_sip_location_add_contact_nolock(struct ast_sip_aor *aor, const char *uri
struct ast_sip_endpoint *endpoint);
/*!
+ * \brief Create a new contact for an AOR without locking the AOR
+ * \since 13.18.0
+ *
+ * \param aor Pointer to the AOR
+ * \param uri Full contact URI
+ * \param expiration_time Optional expiration time of the contact
+ * \param path_info Path information
+ * \param user_agent User-Agent header from REGISTER request
+ * \param via_addr
+ * \param via_port
+ * \param call_id
+ * \param prune_on_boot Non-zero if the contact cannot survive a restart/boot.
+ * \param endpoint The endpoint that resulted in the contact being added
+ *
+ * \return The created contact or NULL on failure.
+ *
+ * \warning
+ * This function should only be called if you already hold a named write lock on the aor.
+ */
+struct ast_sip_contact *ast_sip_location_create_contact(struct ast_sip_aor *aor,
+ const char *uri, struct timeval expiration_time, const char *path_info,
+ const char *user_agent, const char *via_addr, int via_port, const char *call_id,
+ int prune_on_boot, struct ast_sip_endpoint *endpoint);
+
+/*!
* \brief Update a contact
*
* \param contact New contact object with details
@@ -1254,6 +1310,12 @@ int ast_sip_location_update_contact(struct ast_sip_contact *contact);
int ast_sip_location_delete_contact(struct ast_sip_contact *contact);
/*!
+ * \brief Prune the prune_on_boot contacts
+ * \since 13.18.0
+ */
+void ast_sip_location_prune_boot_contacts(void);
+
+/*!
* \brief Callback called when an outbound request with authentication credentials is to be sent in dialog
*
* This callback will have the created request on it. The callback's purpose is to do any extra
@@ -2902,4 +2964,118 @@ int ast_sip_set_tpselector_from_ep_or_uri(const struct ast_sip_endpoint *endpoin
int ast_sip_dlg_set_transport(const struct ast_sip_endpoint *endpoint, pjsip_dialog *dlg,
pjsip_tpselector *selector);
+/*!
+ * \brief Convert the DTMF mode enum value into a string
+ * \since 13.18.0
+ *
+ * \param dtmf the dtmf mode
+ * \param buf Buffer to receive dtmf mode string
+ * \param buf_len Buffer length
+ *
+ * \retval 0 Success
+ * \retval -1 Failure
+ *
+ */
+int ast_sip_dtmf_to_str(const enum ast_sip_dtmf_mode dtmf,
+ char *buf, size_t buf_len);
+
+/*!
+ * \brief Convert the DTMF mode name into an enum
+ * \since 13.18.0
+ *
+ * \param dtmf_mode dtmf mode as a string
+ *
+ * \retval >= 0 The enum value
+ * \retval -1 Failure
+ *
+ */
+int ast_sip_str_to_dtmf(const char *dtmf_mode);
+
+/*!
+ * \brief Transport shutdown monitor callback.
+ * \since 13.18.0
+ *
+ * \param data User data to know what to do when transport shuts down.
+ *
+ * \note The callback does not need to care that data is an ao2 object.
+ *
+ * \return Nothing
+ */
+typedef void (*ast_transport_monitor_shutdown_cb)(void *data);
+
+enum ast_transport_monitor_reg {
+ /*! \brief Successfully registered the transport monitor */
+ AST_TRANSPORT_MONITOR_REG_SUCCESS,
+ /*! \brief Replaced the already existing transport monitor with new one. */
+ AST_TRANSPORT_MONITOR_REG_REPLACED,
+ /*!
+ * \brief Transport not found to monitor.
+ * \note Transport is either already shutdown or is not reliable.
+ */
+ AST_TRANSPORT_MONITOR_REG_NOT_FOUND,
+ /*! \brief Error while registering transport monitor. */
+ AST_TRANSPORT_MONITOR_REG_FAILED,
+};
+
+/*!
+ * \brief Register a reliable transport shutdown monitor callback.
+ * \since 13.18.0
+ *
+ * \param transport Transport to monitor for shutdown.
+ * \param cb Who to call when transport is shutdown.
+ * \param ao2_data Data to pass with the callback.
+ *
+ * \return enum ast_transport_monitor_reg
+ */
+enum ast_transport_monitor_reg ast_sip_transport_monitor_register(pjsip_transport *transport,
+ ast_transport_monitor_shutdown_cb cb, void *ao2_data);
+
+/*!
+ * \brief Unregister a reliable transport shutdown monitor callback.
+ * \since 13.18.0
+ *
+ * \param transport Transport to monitor for shutdown.
+ * \param cb Who to call when transport is shutdown.
+ *
+ * \return Nothing
+ */
+void ast_sip_transport_monitor_unregister(pjsip_transport *transport, ast_transport_monitor_shutdown_cb cb);
+
+/*!
+ * \brief Unregister monitor callback from all reliable transports.
+ * \since 13.18.0
+ *
+ * \param cb Who to call when a transport is shutdown.
+ *
+ * \return Nothing
+ */
+void ast_sip_transport_monitor_unregister_all(ast_transport_monitor_shutdown_cb cb);
+
+/*! Transport state notification registration element. */
+struct ast_sip_tpmgr_state_callback {
+ /*! PJPROJECT transport state notification callback */
+ pjsip_tp_state_callback cb;
+ AST_LIST_ENTRY(ast_sip_tpmgr_state_callback) node;
+};
+
+/*!
+ * \brief Register a transport state notification callback element.
+ * \since 13.18.0
+ *
+ * \param element What we are registering.
+ *
+ * \return Nothing
+ */
+void ast_sip_transport_state_register(struct ast_sip_tpmgr_state_callback *element);
+
+/*!
+ * \brief Unregister a transport state notification callback element.
+ * \since 13.18.0
+ *
+ * \param element What we are unregistering.
+ *
+ * \return Nothing
+ */
+void ast_sip_transport_state_unregister(struct ast_sip_tpmgr_state_callback *element);
+
#endif /* _RES_PJSIP_H */
diff --git a/include/asterisk/res_pjsip_presence_xml.h b/include/asterisk/res_pjsip_presence_xml.h
index deed0901e..55b79ad6e 100644
--- a/include/asterisk/res_pjsip_presence_xml.h
+++ b/include/asterisk/res_pjsip_presence_xml.h
@@ -69,7 +69,8 @@ void ast_sip_sanitize_xml(const char *input, char *output, size_t len);
* \param[out] local_state
*/
void ast_sip_presence_exten_state_to_str(int state, char **statestring,
- char **pidfstate, char **pidfnote, enum ast_sip_pidf_state *local_state);
+ char **pidfstate, char **pidfnote, enum ast_sip_pidf_state *local_state,
+ unsigned int notify_early_inuse_ringing);
/*!
* \brief Create XML attribute
diff --git a/include/asterisk/res_pjsip_session.h b/include/asterisk/res_pjsip_session.h
index 5e8eb3acc..7992fd996 100644
--- a/include/asterisk/res_pjsip_session.h
+++ b/include/asterisk/res_pjsip_session.h
@@ -153,6 +153,12 @@ struct ast_sip_session {
struct ast_sip_aor *aor;
/*! From header saved at invite creation */
pjsip_fromto_hdr *saved_from_hdr;
+ /*! Whether the end of the session should be deferred */
+ unsigned int defer_end:1;
+ /*! Session end (remote hangup) requested while termination deferred */
+ unsigned int ended_while_deferred:1;
+ /*! DTMF mode to use with this session, from endpoint but can change */
+ enum ast_sip_dtmf_mode dtmf;
};
typedef int (*ast_sip_session_request_creation_cb)(struct ast_sip_session *session, pjsip_tx_data *tdata);
@@ -482,6 +488,13 @@ int ast_sip_session_defer_termination(struct ast_sip_session *session);
void ast_sip_session_defer_termination_cancel(struct ast_sip_session *session);
/*!
+ * \brief End the session if it had been previously deferred
+ *
+ * \param session The session to end if it had been deferred
+ */
+void ast_sip_session_end_if_deferred(struct ast_sip_session *session);
+
+/*!
* \brief Register an SDP handler
*
* An SDP handler is responsible for parsing incoming SDP streams and ensuring that
@@ -611,6 +624,23 @@ int ast_sip_session_refresh(struct ast_sip_session *session,
int generate_new_sdp);
/*!
+ * \brief Regenerate SDP Answer
+ *
+ * This method is used when an SDP offer has been received but an SDP answer
+ * has not been sent yet. It requests that a new local SDP be created and
+ * set as the SDP answer. As with any outgoing request in res_pjsip_session,
+ * this will call into registered supplements in case they wish to add anything.
+ *
+ * \param session The session on which the answer will be updated
+ * \param on_sdp_creation Callback called when SDP is created
+ * \param generate_new_sdp Boolean to indicate if a new SDP should be created
+ * \retval 0 Successfully updated the SDP answer
+ * \retval -1 Failure to updated the SDP answer
+ */
+int ast_sip_session_regenerate_answer(struct ast_sip_session *session,
+ ast_sip_session_sdp_creation_cb on_sdp_creation);
+
+/*!
* \brief Send a SIP response
*
* This will send the SIP response specified in tdata and
diff --git a/include/asterisk/rtp_engine.h b/include/asterisk/rtp_engine.h
index e8f3d78b4..0b29f3485 100644
--- a/include/asterisk/rtp_engine.h
+++ b/include/asterisk/rtp_engine.h
@@ -114,6 +114,8 @@ enum ast_rtp_property {
AST_RTP_PROPERTY_STUN,
/*! Enable RTCP support */
AST_RTP_PROPERTY_RTCP,
+ /*! Enable Asymmetric RTP Codecs */
+ AST_RTP_PROPERTY_ASYMMETRIC_CODEC,
/*!
* \brief Maximum number of RTP properties supported
@@ -624,12 +626,13 @@ struct ast_rtp_glue {
/*!
* \brief Used to prevent two channels from remotely bridging audio rtp if the channel tech has a
* reason for prohibiting it based on qualities that need to be compared from both channels.
- * \note This function may be NULL for a given channel driver. This should be accounted for and if that is the case, function this is not used.
+ * \note This function may be NULL for a given channel driver. This should be accounted for and if that is the case, this function is not used.
*/
int (*allow_rtp_remote)(struct ast_channel *chan1, struct ast_rtp_instance *instance);
/*!
* \brief Callback for retrieving the RTP instance carrying video
* \note This function increases the reference count on the returned RTP instance.
+ * \note This function may be NULL for a given channel driver. This should be accounted for and if that is the case, this function is not used.
*/
enum ast_rtp_glue_result (*get_vrtp_info)(struct ast_channel *chan, struct ast_rtp_instance **instance);
/*!
@@ -642,11 +645,15 @@ struct ast_rtp_glue {
/*!
* \brief Callback for retrieving the RTP instance carrying text
* \note This function increases the reference count on the returned RTP instance.
+ * \note This function may be NULL for a given channel driver. This should be accounted for and if that is the case, this function is not used.
*/
enum ast_rtp_glue_result (*get_trtp_info)(struct ast_channel *chan, struct ast_rtp_instance **instance);
/*! Callback for updating the destination that the remote side should send RTP to */
int (*update_peer)(struct ast_channel *chan, struct ast_rtp_instance *instance, struct ast_rtp_instance *vinstance, struct ast_rtp_instance *tinstance, const struct ast_format_cap *cap, int nat_active);
- /*! Callback for retrieving codecs that the channel can do. Result returned in result_cap. */
+ /*!
+ * \brief Callback for retrieving codecs that the channel can do. Result returned in result_cap.
+ * \note This function may be NULL for a given channel driver. This should be accounted for and if that is the case, this function is not used.
+ */
void (*get_codec)(struct ast_channel *chan, struct ast_format_cap *result_cap);
/*! Linked list information */
AST_RWLIST_ENTRY(ast_rtp_glue) entry;
diff --git a/main/Makefile b/main/Makefile
index bbddb03fd..18f102506 100644
--- a/main/Makefile
+++ b/main/Makefile
@@ -140,6 +140,7 @@ endif
$(CMD_PREFIX) rm $@.fix
ast_expr2f.o: _ASTCFLAGS+=-Wno-unused
+cdr.o: _ASTCFLAGS+=$(AST_NO_FORMAT_TRUNCATION)
testexpr2: ast_expr2f.c ast_expr2.c ast_expr2.h
$(CC) -g -c -Iinclude -DSTANDALONE ast_expr2f.c
@@ -314,7 +315,7 @@ endif
endif
-tcptls.o: _ASTCFLAGS+=$(OPENSSL_INCLUDE)
+tcptls.o: _ASTCFLAGS+=$(OPENSSL_INCLUDE) -Wno-deprecated-declarations
$(MAIN_TGT): $(OBJS) $(ASTSSL_LIB) $(ASTPJ_LIB) $(LIBEDIT_OBJ)
@$(CC) -c -o buildinfo.o $(_ASTCFLAGS) buildinfo.c $(ASTCFLAGS)
diff --git a/main/acl.c b/main/acl.c
index 9820e8bef..94a242af2 100644
--- a/main/acl.c
+++ b/main/acl.c
@@ -739,8 +739,8 @@ enum ast_acl_sense ast_apply_ha(const struct ast_ha *ha, const struct ast_sockad
char iabuf[INET_ADDRSTRLEN];
char iabuf2[INET_ADDRSTRLEN];
/* DEBUG */
- ast_copy_string(iabuf, ast_inet_ntoa(sin->sin_addr), sizeof(iabuf));
- ast_copy_string(iabuf2, ast_inet_ntoa(ha->netaddr), sizeof(iabuf2));
+ ast_copy_string(iabuf, ast_sockaddr_stringify(addr), sizeof(iabuf));
+ ast_copy_string(iabuf2, ast_sockaddr_stringify(&current_ha->addr), sizeof(iabuf2));
ast_debug(1, "##### Testing %s with %s\n", iabuf, iabuf2);
#endif
if (ast_sockaddr_is_ipv4(&current_ha->addr)) {
diff --git a/main/app.c b/main/app.c
index ee7cef26b..8ea6f82d9 100644
--- a/main/app.c
+++ b/main/app.c
@@ -1113,6 +1113,8 @@ static int control_streamfile(struct ast_channel *chan,
if (!strcasecmp(end, ":end")) {
*end = '\0';
end++;
+ } else {
+ end = NULL;
}
}
@@ -3069,19 +3071,32 @@ int ast_app_parse_timelen(const char *timestr, int *result, enum ast_timelen uni
case 'h':
case 'H':
unit = TIMELEN_HOURS;
+ if (u[1] != '\0') {
+ return -1;
+ }
break;
case 's':
case 'S':
unit = TIMELEN_SECONDS;
+ if (u[1] != '\0') {
+ return -1;
+ }
break;
case 'm':
case 'M':
if (toupper(u[1]) == 'S') {
unit = TIMELEN_MILLISECONDS;
+ if (u[2] != '\0') {
+ return -1;
+ }
} else if (u[1] == '\0') {
unit = TIMELEN_MINUTES;
+ } else {
+ return -1;
}
break;
+ default:
+ return -1;
}
}
diff --git a/main/ast_expr2.c b/main/ast_expr2.c
index a3c715ac1..1b866facf 100644
--- a/main/ast_expr2.c
+++ b/main/ast_expr2.c
@@ -2637,13 +2637,11 @@ to_string (struct val *vp)
if (vp->type == AST_EXPR_string || vp->type == AST_EXPR_numeric_string)
return;
- tmp = malloc ((size_t)25);
- if (tmp == NULL) {
- ast_log(LOG_WARNING,"malloc() failed\n");
+ if (asprintf(&tmp, FP___PRINTF, vp->u.i) == -1) {
+ ast_log(LOG_WARNING, "asprintf() failed\n");
return;
}
- sprintf(tmp, FP___PRINTF, vp->u.i);
vp->type = AST_EXPR_string;
vp->u.s = tmp;
}
diff --git a/main/ast_expr2.y b/main/ast_expr2.y
index 4f6087773..7163a7132 100644
--- a/main/ast_expr2.y
+++ b/main/ast_expr2.y
@@ -630,13 +630,11 @@ to_string (struct val *vp)
if (vp->type == AST_EXPR_string || vp->type == AST_EXPR_numeric_string)
return;
- tmp = malloc ((size_t)25);
- if (tmp == NULL) {
- ast_log(LOG_WARNING,"malloc() failed\n");
+ if (asprintf(&tmp, FP___PRINTF, vp->u.i) == -1) {
+ ast_log(LOG_WARNING, "asprintf() failed\n");
return;
}
- sprintf(tmp, FP___PRINTF, vp->u.i);
vp->type = AST_EXPR_string;
vp->u.s = tmp;
}
diff --git a/main/asterisk.c b/main/asterisk.c
index e2562764a..0818cfbc3 100644
--- a/main/asterisk.c
+++ b/main/asterisk.c
@@ -386,6 +386,9 @@ static void ast_el_write_default_histfile(void);
static void asterisk_daemon(int isroot, const char *runuser, const char *rungroup);
+#define DEFAULT_MONITOR_DIR DEFAULT_SPOOL_DIR "/monitor"
+#define DEFAULT_RECORDING_DIR DEFAULT_SPOOL_DIR "/recording"
+
struct _cfg_paths {
char config_dir[PATH_MAX];
char module_dir[PATH_MAX];
@@ -1283,11 +1286,10 @@ void ast_unreplace_sigchld(void)
ast_mutex_unlock(&safe_system_lock);
}
-int ast_safe_system(const char *s)
+/*! \brief fork and perform other preparations for spawning applications */
+static pid_t safe_exec_prep(int dualfork)
{
pid_t pid;
- int res;
- int status;
#if defined(HAVE_WORKING_FORK) || defined(HAVE_WORKING_VFORK)
ast_replace_sigchld();
@@ -1309,35 +1311,101 @@ int ast_safe_system(const char *s)
cap_free(cap);
#endif
#ifdef HAVE_WORKING_FORK
- if (ast_opt_high_priority)
+ if (ast_opt_high_priority) {
ast_set_priority(0);
+ }
/* Close file descriptors and launch system command */
ast_close_fds_above_n(STDERR_FILENO);
#endif
- execl("/bin/sh", "/bin/sh", "-c", s, (char *) NULL);
- _exit(1);
- } else if (pid > 0) {
+ if (dualfork) {
+#ifdef HAVE_WORKING_FORK
+ pid = fork();
+#else
+ pid = vfork();
+#endif
+ if (pid < 0) {
+ /* Second fork failed. */
+ /* No logger available. */
+ _exit(1);
+ }
+
+ if (pid > 0) {
+ /* This is the first fork, exit so the reaper finishes right away. */
+ _exit(0);
+ }
+
+ /* This is the second fork. The first fork will exit immediately so
+ * Asterisk doesn't have to wait for completion.
+ * ast_safe_system("cmd &") would run in the background, but the '&'
+ * cannot be added with ast_safe_execvp, so we have to double fork.
+ */
+ }
+ }
+
+ if (pid < 0) {
+ ast_log(LOG_WARNING, "Fork failed: %s\n", strerror(errno));
+ }
+#else
+ ast_log(LOG_WARNING, "Fork failed: %s\n", strerror(ENOTSUP));
+ pid = -1;
+#endif
+
+ return pid;
+}
+
+/*! \brief wait for spawned application to complete and unreplace sigchld */
+static int safe_exec_wait(pid_t pid)
+{
+ int res = -1;
+
+#if defined(HAVE_WORKING_FORK) || defined(HAVE_WORKING_VFORK)
+ if (pid > 0) {
for (;;) {
+ int status;
+
res = waitpid(pid, &status, 0);
if (res > -1) {
res = WIFEXITED(status) ? WEXITSTATUS(status) : -1;
break;
- } else if (errno != EINTR)
+ }
+ if (errno != EINTR) {
break;
+ }
}
- } else {
- ast_log(LOG_WARNING, "Fork failed: %s\n", strerror(errno));
- res = -1;
}
ast_unreplace_sigchld();
-#else /* !defined(HAVE_WORKING_FORK) && !defined(HAVE_WORKING_VFORK) */
- res = -1;
#endif
return res;
}
+int ast_safe_execvp(int dualfork, const char *file, char *const argv[])
+{
+ pid_t pid = safe_exec_prep(dualfork);
+
+ if (pid == 0) {
+ execvp(file, argv);
+ _exit(1);
+ /* noreturn from _exit */
+ }
+
+ return safe_exec_wait(pid);
+}
+
+int ast_safe_system(const char *s)
+{
+ pid_t pid = safe_exec_prep(0);
+
+ if (pid == 0) {
+ execl("/bin/sh", "/bin/sh", "-c", s, (char *) NULL);
+ _exit(1);
+ /* noreturn from _exit */
+ }
+
+ return safe_exec_wait(pid);
+}
+
/*!
* \brief enable or disable a logging level to a specified console
*/
@@ -3654,8 +3722,8 @@ static void ast_readconfig(void)
ast_copy_string(cfg_paths.config_dir, DEFAULT_CONFIG_DIR, sizeof(cfg_paths.config_dir));
ast_copy_string(cfg_paths.spool_dir, DEFAULT_SPOOL_DIR, sizeof(cfg_paths.spool_dir));
ast_copy_string(cfg_paths.module_dir, DEFAULT_MODULE_DIR, sizeof(cfg_paths.module_dir));
- snprintf(cfg_paths.monitor_dir, sizeof(cfg_paths.monitor_dir), "%s/monitor", cfg_paths.spool_dir);
- snprintf(cfg_paths.recording_dir, sizeof(cfg_paths.recording_dir), "%s/recording", cfg_paths.spool_dir);
+ ast_copy_string(cfg_paths.monitor_dir, DEFAULT_MONITOR_DIR, sizeof(cfg_paths.monitor_dir));
+ ast_copy_string(cfg_paths.recording_dir, DEFAULT_RECORDING_DIR, sizeof(cfg_paths.recording_dir));
ast_copy_string(cfg_paths.var_dir, DEFAULT_VAR_DIR, sizeof(cfg_paths.var_dir));
ast_copy_string(cfg_paths.data_dir, DEFAULT_DATA_DIR, sizeof(cfg_paths.data_dir));
ast_copy_string(cfg_paths.log_dir, DEFAULT_LOG_DIR, sizeof(cfg_paths.log_dir));
@@ -3811,7 +3879,9 @@ static void ast_readconfig(void)
/* Set the maximum amount of open files */
} else if (!strcasecmp(v->name, "maxfiles")) {
ast_option_maxfiles = atoi(v->value);
- set_ulimit(ast_option_maxfiles);
+ if (!ast_opt_remote) {
+ set_ulimit(ast_option_maxfiles);
+ }
/* What user to run as */
} else if (!strcasecmp(v->name, "runuser")) {
ast_copy_string(cfg_paths.run_user, v->value, sizeof(cfg_paths.run_user));
diff --git a/main/bridge.c b/main/bridge.c
index 7f6fbbef9..f689b297f 100644
--- a/main/bridge.c
+++ b/main/bridge.c
@@ -471,6 +471,7 @@ static void bridge_complete_join(struct ast_bridge *bridge)
}
AST_LIST_TRAVERSE(&bridge->channels, bridge_channel, entry) {
+ bridge_channel_queue_deferred_frames(bridge_channel);
if (!bridge_channel->just_joined) {
continue;
}
@@ -1741,12 +1742,13 @@ join_exit:;
static void *bridge_channel_depart_thread(void *data)
{
struct ast_bridge_channel *bridge_channel = data;
+ int res = 0;
if (bridge_channel->callid) {
ast_callid_threadassoc_add(bridge_channel->callid);
}
- bridge_channel_internal_join(bridge_channel);
+ res = bridge_channel_internal_join(bridge_channel);
/*
* cleanup
@@ -1758,7 +1760,8 @@ static void *bridge_channel_depart_thread(void *data)
ast_bridge_features_destroy(bridge_channel->features);
bridge_channel->features = NULL;
- ast_bridge_discard_after_callback(bridge_channel->chan, AST_BRIDGE_AFTER_CB_REASON_DEPART);
+ ast_bridge_discard_after_callback(bridge_channel->chan,
+ res ? AST_BRIDGE_AFTER_CB_REASON_IMPART_FAILED : AST_BRIDGE_AFTER_CB_REASON_DEPART);
/* If join failed there will be impart threads waiting. */
bridge_channel_impart_signal(bridge_channel->chan);
ast_bridge_discard_after_goto(bridge_channel->chan);
@@ -4240,14 +4243,15 @@ static enum ast_transfer_result attended_transfer_bridge(struct ast_channel *cha
BRIDGE_LOCK_ONE_OR_BOTH(bridge1, bridge2);
if (bridge2) {
+ void *tech;
struct ast_channel *locals[2];
/* Have to lock everything just in case a hangup comes in early */
- ast_local_lock_all(local_chan, &locals[0], &locals[1]);
+ ast_local_lock_all2(local_chan, &tech, &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);
+ ast_local_unlock_all2(tech, locals[0], locals[1]);
ao2_cleanup(local_chan);
return AST_BRIDGE_TRANSFER_FAIL;
}
@@ -4258,7 +4262,7 @@ static enum ast_transfer_result attended_transfer_bridge(struct ast_channel *cha
}
ast_attended_transfer_message_add_link(transfer_msg, locals);
- ast_local_unlock_all(local_chan);
+ ast_local_unlock_all2(tech, locals[0], locals[1]);
} else {
ast_attended_transfer_message_add_app(transfer_msg, app, local_chan);
}
@@ -4786,7 +4790,7 @@ enum ast_transfer_result ast_bridge_transfer_attended(struct ast_channel *to_tra
res = AST_BRIDGE_TRANSFER_SUCCESS;
end:
- if (res == AST_BRIDGE_TRANSFER_SUCCESS && hangup_target) {
+ if ((res == AST_BRIDGE_TRANSFER_SUCCESS && hangup_target) || res == AST_BRIDGE_TRANSFER_FAIL) {
ast_softhangup(to_transfer_target, AST_SOFTHANGUP_DEV);
}
diff --git a/main/bridge_after.c b/main/bridge_after.c
index 1208b57b8..813510528 100644
--- a/main/bridge_after.c
+++ b/main/bridge_after.c
@@ -295,23 +295,23 @@ int ast_bridge_set_after_callback(struct ast_channel *chan, ast_bridge_after_cb
return 0;
}
-const char *reason_strings[] = {
- [AST_BRIDGE_AFTER_CB_REASON_DESTROY] = "Channel destroyed (hungup)",
- [AST_BRIDGE_AFTER_CB_REASON_REPLACED] = "Callback was replaced",
- [AST_BRIDGE_AFTER_CB_REASON_MASQUERADE] = "Channel masqueraded",
- [AST_BRIDGE_AFTER_CB_REASON_DEPART] = "Channel was departed from bridge",
- [AST_BRIDGE_AFTER_CB_REASON_REMOVED] = "Callback was removed",
-};
-
const char *ast_bridge_after_cb_reason_string(enum ast_bridge_after_cb_reason reason)
{
- if (reason < AST_BRIDGE_AFTER_CB_REASON_DESTROY
- || AST_BRIDGE_AFTER_CB_REASON_REMOVED < reason
- || !reason_strings[reason]) {
- return "Unknown";
- }
-
- return reason_strings[reason];
+ switch (reason) {
+ case AST_BRIDGE_AFTER_CB_REASON_DESTROY:
+ return "Channel destroyed (hungup)";
+ case AST_BRIDGE_AFTER_CB_REASON_REPLACED:
+ return "Callback was replaced";
+ case AST_BRIDGE_AFTER_CB_REASON_MASQUERADE:
+ return "Channel masqueraded";
+ case AST_BRIDGE_AFTER_CB_REASON_DEPART:
+ return "Channel was departed from bridge";
+ case AST_BRIDGE_AFTER_CB_REASON_REMOVED:
+ return "Callback was removed";
+ case AST_BRIDGE_AFTER_CB_REASON_IMPART_FAILED:
+ return "Channel failed joining the bridge";
+ }
+ return "Unknown";
}
struct after_bridge_goto_ds {
diff --git a/main/bridge_channel.c b/main/bridge_channel.c
index eba5ae40a..0af688ad4 100644
--- a/main/bridge_channel.c
+++ b/main/bridge_channel.c
@@ -639,18 +639,21 @@ 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;
+ int deferred;
ast_assert(frame->frametype != AST_FRAME_BRIDGE_ACTION_SYNC);
ast_bridge_channel_lock_bridge(bridge_channel);
-/*
- * XXX need to implement a deferred write queue for when there
- * is no peer channel in the bridge (yet or it was kicked).
- *
- * The tech decides if a frame needs to be pushed back for deferral.
- * simple_bridge/native_bridge are likely the only techs that will do this.
- */
- bridge_channel->bridge->technology->write(bridge_channel->bridge, bridge_channel, frame);
+
+ deferred = bridge_channel->bridge->technology->write(bridge_channel->bridge, bridge_channel, frame);
+ if (deferred) {
+ struct ast_frame *dup;
+
+ dup = ast_frdup(frame);
+ if (dup) {
+ AST_LIST_INSERT_HEAD(&bridge_channel->deferred_queue, dup, frame_list);
+ }
+ }
/* Remember any owed events to the bridge. */
switch (frame->frametype) {
@@ -754,6 +757,18 @@ void bridge_channel_settle_owed_events(struct ast_bridge *orig_bridge, struct as
}
}
+void bridge_channel_queue_deferred_frames(struct ast_bridge_channel *bridge_channel)
+{
+ struct ast_frame *frame;
+
+ ast_channel_lock(bridge_channel->chan);
+ while ((frame = AST_LIST_REMOVE_HEAD(&bridge_channel->deferred_queue, frame_list))) {
+ ast_queue_frame_head(bridge_channel->chan, frame);
+ ast_frfree(frame);
+ }
+ ast_channel_unlock(bridge_channel->chan);
+}
+
/*!
* \internal
* \brief Suspend a channel from a bridge.
@@ -2854,6 +2869,11 @@ static void bridge_channel_destroy(void *obj)
}
ast_alertpipe_close(bridge_channel->alert_pipe);
+ /* Flush any unhandled deferred_queue frames. */
+ while ((fr = AST_LIST_REMOVE_HEAD(&bridge_channel->deferred_queue, frame_list))) {
+ ast_frfree(fr);
+ }
+
ast_cond_destroy(&bridge_channel->cond);
ao2_cleanup(bridge_channel->write_format);
diff --git a/main/ccss.c b/main/ccss.c
index 002b9a3dd..506775196 100644
--- a/main/ccss.c
+++ b/main/ccss.c
@@ -3552,7 +3552,7 @@ struct ast_cc_monitor *ast_cc_get_monitor_by_recall_core_id(const int core_id, c
*/
static void cc_unique_append(struct ast_str **str, const char *dialstring)
{
- char dialstring_search[AST_CHANNEL_NAME];
+ char dialstring_search[AST_CHANNEL_NAME + 1];
if (ast_strlen_zero(dialstring)) {
/* No dialstring to append. */
diff --git a/main/channel.c b/main/channel.c
index e1ee516a2..c6c035f39 100644
--- a/main/channel.c
+++ b/main/channel.c
@@ -1001,6 +1001,9 @@ __ast_channel_alloc_ap(int needqueue, int state, const char *cid_num, const char
* the world know of its existance
*/
ast_channel_stage_snapshot_done(tmp);
+
+ ast_debug(1, "Channel %p '%s' allocated\n", tmp, ast_channel_name(tmp));
+
return tmp;
}
@@ -2227,6 +2230,8 @@ static void ast_channel_destructor(void *obj)
char device_name[AST_CHANNEL_NAME];
struct ast_callid *callid;
+ ast_debug(1, "Channel %p '%s' destroying\n", chan, ast_channel_name(chan));
+
/* Stop monitoring */
if (ast_channel_monitor(chan)) {
ast_channel_monitor(chan)->stop(chan, 0);
@@ -2672,6 +2677,9 @@ void ast_hangup(struct ast_channel *chan)
return;
}
+ ast_debug(1, "Channel %p '%s' hanging up. Refs: %d\n", chan, ast_channel_name(chan),
+ ao2_ref(chan, 0));
+
ast_autoservice_stop(chan);
ast_channel_lock(chan);
@@ -2731,7 +2739,6 @@ void ast_hangup(struct ast_channel *chan)
ast_assert(ast_test_flag(ast_channel_flags(chan), AST_FLAG_BLOCKING) == 0);
}
- ast_debug(1, "Hanging up channel '%s'\n", ast_channel_name(chan));
if (ast_channel_tech(chan)->hangup) {
ast_channel_tech(chan)->hangup(chan);
}
@@ -3550,8 +3557,12 @@ int ast_waitfordigit_full(struct ast_channel *c, int timeout_ms, int audiofd, in
} else if (rchan) {
int res;
struct ast_frame *f = ast_read(c);
- if (!f)
+
+ if (!f) {
+ ast_channel_clear_flag(c, AST_FLAG_END_DTMF_ONLY);
+
return -1;
+ }
switch (f->frametype) {
case AST_FRAME_DTMF_BEGIN:
@@ -10822,7 +10833,7 @@ static const struct ast_datastore_info *suppress_get_datastore_information(enum
int ast_channel_suppress(struct ast_channel *chan, unsigned int direction, enum ast_frame_type frametype)
{
- RAII_VAR(struct suppress_data *, suppress, NULL, ao2_cleanup);
+ struct suppress_data *suppress;
const struct ast_datastore_info *datastore_info = NULL;
struct ast_datastore *datastore = NULL;
struct ast_framehook_interface interface = {
@@ -10858,6 +10869,7 @@ int ast_channel_suppress(struct ast_channel *chan, unsigned int direction, enum
if (framehook_id < 0) {
/* Hook attach failed. Get rid of the evidence. */
ast_log(LOG_WARNING, "Failed to attach framehook while attempting to suppress a stream.\n");
+ ao2_ref(suppress, -1);
return -1;
}
@@ -10869,11 +10881,11 @@ int ast_channel_suppress(struct ast_channel *chan, unsigned int direction, enum
if (!(datastore = ast_datastore_alloc(datastore_info, NULL))) {
ast_log(LOG_WARNING, "Failed to allocate datastore while attempting to suppress a stream.\n");
ast_framehook_detach(chan, framehook_id);
+ ao2_ref(suppress, -1);
return -1;
}
- /* and another ref for the datastore */
- ao2_ref(suppress, +1);
+ /* the ref provided by the allocation is taken by the datastore */
datastore->data = suppress;
ast_channel_datastore_add(chan, datastore);
diff --git a/main/cli.c b/main/cli.c
index 1af917776..e9ed709d0 100644
--- a/main/cli.c
+++ b/main/cli.c
@@ -374,7 +374,7 @@ static char *complete_number(const char *partial, unsigned int min, unsigned int
int i, count = 0;
unsigned int prospective[2];
unsigned int part = strtoul(partial, NULL, 10);
- char next[12];
+ char next[13];
if (part < min || part > max) {
return NULL;
@@ -1031,7 +1031,7 @@ static char *handle_chanlist(struct ast_cli_entry *e, int cmd, struct ast_cli_ar
it_chans = ao2_iterator_init(channels, 0);
for (; (msg = ao2_iterator_next(&it_chans)); ao2_ref(msg, -1)) {
struct ast_channel_snapshot *cs = stasis_message_data(msg);
- char durbuf[10] = "-";
+ char durbuf[16] = "-";
if (!count) {
if ((concise || verbose) && !ast_tvzero(cs->creationtime)) {
diff --git a/main/codec_builtin.c b/main/codec_builtin.c
index 5fdfa7e12..9ba33ee35 100644
--- a/main/codec_builtin.c
+++ b/main/codec_builtin.c
@@ -783,6 +783,13 @@ static struct ast_codec vp8 = {
.sample_rate = 1000,
};
+static struct ast_codec vp9 = {
+ .name = "vp9",
+ .description = "VP9 video",
+ .type = AST_MEDIA_TYPE_VIDEO,
+ .sample_rate = 1000,
+};
+
static struct ast_codec t140red = {
.name = "red",
.description = "T.140 Realtime Text with redundancy",
@@ -922,6 +929,7 @@ int ast_codec_builtin_init(void)
res |= CODEC_REGISTER_AND_CACHE(h264);
res |= CODEC_REGISTER_AND_CACHE(mpeg4);
res |= CODEC_REGISTER_AND_CACHE(vp8);
+ res |= CODEC_REGISTER_AND_CACHE(vp9);
res |= CODEC_REGISTER_AND_CACHE(t140red);
res |= CODEC_REGISTER_AND_CACHE(t140);
res |= CODEC_REGISTER_AND_CACHE(none);
diff --git a/main/config.c b/main/config.c
index b81a9f6a2..9be758173 100644
--- a/main/config.c
+++ b/main/config.c
@@ -3743,6 +3743,55 @@ uint32_done:
break;
}
+ case PARSE_TIMELEN:
+ {
+ int x = 0;
+ int *result = p_result;
+ int def = result ? *result : 0;
+ int high = INT_MAX;
+ int low = INT_MIN;
+ enum ast_timelen defunit;
+
+ defunit = va_arg(ap, enum ast_timelen);
+ /* optional arguments: default value and/or (low, high) */
+ if (flags & PARSE_DEFAULT) {
+ def = va_arg(ap, int);
+ }
+ if (flags & (PARSE_IN_RANGE | PARSE_OUT_RANGE)) {
+ low = va_arg(ap, int);
+ high = va_arg(ap, int);
+ }
+ if (ast_strlen_zero(arg)) {
+ error = 1;
+ goto timelen_done;
+ }
+ error = ast_app_parse_timelen(arg, &x, defunit);
+ if (error || x < INT_MIN || x > INT_MAX) {
+ /* Parse error, or type out of int bounds */
+ error = 1;
+ goto timelen_done;
+ }
+ error = (x < low) || (x > high);
+ if (flags & PARSE_RANGE_DEFAULTS) {
+ if (x < low) {
+ def = low;
+ } else if (x > high) {
+ def = high;
+ }
+ }
+ if (flags & PARSE_OUT_RANGE) {
+ error = !error;
+ }
+timelen_done:
+ if (result) {
+ *result = error ? def : x;
+ }
+
+ ast_debug(3, "extract timelen from [%s] in [%d, %d] gives [%d](%d)\n",
+ arg, low, high, result ? *result : x, error);
+ break;
+ }
+
case PARSE_DOUBLE:
{
double *result = p_result;
diff --git a/main/config_options.c b/main/config_options.c
index 81d0ff9a0..40fae161c 100644
--- a/main/config_options.c
+++ b/main/config_options.c
@@ -36,6 +36,7 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
#include "asterisk/config_options.h"
#include "asterisk/stringfields.h"
#include "asterisk/acl.h"
+#include "asterisk/app.h"
#include "asterisk/frame.h"
#include "asterisk/xmldoc.h"
#include "asterisk/cli.h"
@@ -120,6 +121,7 @@ static void config_option_destroy(void *obj)
static int int_handler_fn(const struct aco_option *opt, struct ast_variable *var, void *obj);
static int uint_handler_fn(const struct aco_option *opt, struct ast_variable *var, void *obj);
+static int timelen_handler_fn(const struct aco_option *opt, struct ast_variable *var, void *obj);
static int double_handler_fn(const struct aco_option *opt, struct ast_variable *var, void *obj);
static int sockaddr_handler_fn(const struct aco_option *opt, struct ast_variable *var, void *obj);
static int stringfield_handler_fn(const struct aco_option *opt, struct ast_variable *var, void *obj);
@@ -153,6 +155,7 @@ static aco_option_handler ast_config_option_default_handler(enum aco_option_type
case OPT_SOCKADDR_T: return sockaddr_handler_fn;
case OPT_STRINGFIELD_T: return stringfield_handler_fn;
case OPT_UINT_T: return uint_handler_fn;
+ case OPT_TIMELEN_T: return timelen_handler_fn;
case OPT_CUSTOM_T: return NULL;
}
@@ -1380,6 +1383,39 @@ static int uint_handler_fn(const struct aco_option *opt, struct ast_variable *va
return res;
}
+/*! \brief Default option handler for timelen signed integers
+ * \note For a description of the opt->flags and opt->args values, see the documentation for
+ * enum aco_option_type in config_options.h
+ */
+static int timelen_handler_fn(const struct aco_option *opt, struct ast_variable *var, void *obj)
+{
+ int *field = (int *)(obj + opt->args[0]);
+ unsigned int flags = PARSE_TIMELEN | opt->flags;
+ int res = 0;
+ if (opt->flags & PARSE_IN_RANGE) {
+ if (opt->flags & PARSE_DEFAULT) {
+ res = ast_parse_arg(var->value, flags, field, (enum ast_timelen) opt->args[1], (int) opt->args[2], (int) opt->args[3], opt->args[4]);
+ } else {
+ res = ast_parse_arg(var->value, flags, field, (enum ast_timelen) opt->args[1], (int) opt->args[2], (int) opt->args[3]);
+ }
+ if (res) {
+ if (opt->flags & PARSE_RANGE_DEFAULTS) {
+ ast_log(LOG_WARNING, "Failed to set %s=%s. Set to %d instead due to range limit (%d, %d)\n", var->name, var->value, *field, (int) opt->args[2], (int) opt->args[3]);
+ res = 0;
+ } else if (opt->flags & PARSE_DEFAULT) {
+ ast_log(LOG_WARNING, "Failed to set %s=%s, Set to default value %d instead.\n", var->name, var->value, *field);
+ res = 0;
+ }
+ }
+ } else if ((opt->flags & PARSE_DEFAULT) && ast_parse_arg(var->value, flags, field, (enum ast_timelen) opt->args[1], (int) opt->args[2])) {
+ ast_log(LOG_WARNING, "Attempted to set %s=%s, but set it to %d instead due to default)\n", var->name, var->value, *field);
+ } else {
+ res = ast_parse_arg(var->value, flags, field, (enum ast_timelen) opt->args[1]);
+ }
+
+ return res;
+}
+
/*! \brief Default option handler for doubles
* \note For a description of the opt->flags and opt->args values, see the documentation for
* enum aco_option_type in config_options.h
diff --git a/main/core_local.c b/main/core_local.c
index 1b8ebf6f1..a5918f525 100644
--- a/main/core_local.c
+++ b/main/core_local.c
@@ -235,17 +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)
+void ast_local_lock_all2(struct ast_channel *chan, void **tech_pvt,
+ struct ast_channel **base_chan, struct ast_channel **base_owner)
{
struct local_pvt *p = ast_channel_tech_pvt(chan);
- *outchan = NULL;
- *outowner = NULL;
+ *tech_pvt = NULL;
+ *base_chan = NULL;
+ *base_owner = NULL;
if (p) {
- ao2_ref(p, 1);
- ast_unreal_lock_all(&p->base, outchan, outowner);
+ *tech_pvt = ao2_bump(p);
+ ast_unreal_lock_all(&p->base, base_chan, base_owner);
+ }
+}
+
+void ast_local_lock_all(struct ast_channel *chan, struct ast_channel **outchan,
+ struct ast_channel **outowner)
+{
+ void *tech_pvt;
+ ast_local_lock_all2(chan, &tech_pvt, outchan, outowner);
+}
+
+void ast_local_unlock_all2(void *tech_pvt, struct ast_channel *base_chan,
+ struct ast_channel *base_owner)
+{
+ if (base_chan) {
+ ast_channel_unlock(base_chan);
+ ast_channel_unref(base_chan);
+ }
+
+ if (base_owner) {
+ ast_channel_unlock(base_owner);
+ ast_channel_unref(base_owner);
+ }
+
+ if (tech_pvt) {
+ struct local_pvt *p = tech_pvt;
+ ao2_unlock(&p->base);
+ ao2_ref(tech_pvt, -1);
}
}
@@ -259,19 +287,7 @@ void ast_local_unlock_all(struct ast_channel *chan)
}
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);
+ ast_local_unlock_all2(p, base->chan, base->owner);
}
struct ast_channel *ast_local_get_peer(struct ast_channel *ast)
diff --git a/main/data.c b/main/data.c
index 33a7c040f..59729492c 100644
--- a/main/data.c
+++ b/main/data.c
@@ -3145,6 +3145,10 @@ int ast_data_add_codecs(struct ast_data *root, const char *node_name, struct ast
return -1;
}
+ if (!cap) {
+ return 0;
+ }
+
count = ast_format_cap_count(cap);
for (i = 0; i < count; ++i) {
struct ast_format *fmt;
diff --git a/main/format_cache.c b/main/format_cache.c
index 74ebfe8d5..00563e899 100644
--- a/main/format_cache.c
+++ b/main/format_cache.c
@@ -193,6 +193,11 @@ struct ast_format *ast_format_mp4;
struct ast_format *ast_format_vp8;
/*!
+ * \brief Built-in cached vp9 format.
+ */
+struct ast_format *ast_format_vp9;
+
+/*!
* \brief Built-in cached jpeg format.
*/
struct ast_format *ast_format_jpeg;
@@ -336,6 +341,7 @@ static void format_cache_shutdown(void)
ao2_replace(ast_format_h264, NULL);
ao2_replace(ast_format_mp4, NULL);
ao2_replace(ast_format_vp8, NULL);
+ ao2_replace(ast_format_vp9, NULL);
ao2_replace(ast_format_t140_red, NULL);
ao2_replace(ast_format_t140, NULL);
ao2_replace(ast_format_none, NULL);
@@ -432,6 +438,8 @@ static void set_cached_format(const char *name, struct ast_format *format)
ao2_replace(ast_format_mp4, format);
} else if (!strcmp(name, "vp8")) {
ao2_replace(ast_format_vp8, format);
+ } else if (!strcmp(name, "vp9")) {
+ ao2_replace(ast_format_vp9, format);
} else if (!strcmp(name, "red")) {
ao2_replace(ast_format_t140_red, format);
} else if (!strcmp(name, "t140")) {
diff --git a/main/http.c b/main/http.c
index cccc60b81..40f7b521f 100644
--- a/main/http.c
+++ b/main/http.c
@@ -505,7 +505,7 @@ void ast_http_send(struct ast_tcptls_session_instance *ser,
}
/* send http header */
- fprintf(ser->f,
+ if (fprintf(ser->f,
"HTTP/1.1 %d %s\r\n"
"%s"
"Date: %s\r\n"
@@ -521,17 +521,20 @@ void ast_http_send(struct ast_tcptls_session_instance *ser,
static_content ? "" : "Cache-Control: no-cache, no-store\r\n",
http_header ? ast_str_buffer(http_header) : "",
content_length
- );
+ ) <= 0) {
+ ast_debug(1, "fprintf() failed: %s\n", strerror(errno));
+ close_connection = 1;
+ }
/* send content */
- if (method != AST_HTTP_HEAD || status_code >= 400) {
+ if (!close_connection && (method != AST_HTTP_HEAD || status_code >= 400)) {
if (out && ast_str_strlen(out)) {
/*
* NOTE: Because ser->f is a non-standard FILE *, fwrite() will probably not
* behave exactly as documented.
*/
if (fwrite(ast_str_buffer(out), ast_str_strlen(out), 1, ser->f) != 1) {
- ast_log(LOG_ERROR, "fwrite() failed: %s\n", strerror(errno));
+ ast_debug(1, "fwrite() failed: %s\n", strerror(errno));
close_connection = 1;
}
}
@@ -546,7 +549,7 @@ void ast_http_send(struct ast_tcptls_session_instance *ser,
* behave exactly as documented.
*/
if (fwrite(buf, len, 1, ser->f) != 1) {
- ast_log(LOG_WARNING, "fwrite() failed: %s\n", strerror(errno));
+ ast_debug(1, "fwrite() failed: %s\n", strerror(errno));
close_connection = 1;
break;
}
diff --git a/main/json.c b/main/json.c
index 9f42f0adb..f45d585c1 100644
--- a/main/json.c
+++ b/main/json.c
@@ -825,6 +825,7 @@ struct ast_json *ast_json_vpack(char const *format, va_list ap)
ast_log(LOG_ERROR,
"Error building JSON from '%s': %s.\n",
format, error.text);
+ ast_log_backtrace();
}
}
return r;
diff --git a/main/libasteriskssl.c b/main/libasteriskssl.c
index 9dea3df0b..a89f19125 100644
--- a/main/libasteriskssl.c
+++ b/main/libasteriskssl.c
@@ -31,20 +31,21 @@
ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
+#include "asterisk/_private.h" /* ast_ssl_init() */
+
#ifdef HAVE_OPENSSL
#include <openssl/ssl.h>
#include <openssl/err.h>
#endif
-#include <dlfcn.h>
+#if defined(HAVE_OPENSSL) && \
+ !defined(OPENSSL_VERSION_NUMBER) || OPENSSL_VERSION_NUMBER < 0x10100000L || defined(LIBRESSL_VERSION_NUMBER)
-#include "asterisk/_private.h" /* ast_ssl_init() */
+#include <dlfcn.h>
#include "asterisk/utils.h"
#include "asterisk/lock.h"
-#ifdef HAVE_OPENSSL
-
#define get_OpenSSL_function(func) do { real_##func = dlsym(RTLD_NEXT, __stringify(func)); } while(0)
static int startup_complete;
@@ -74,7 +75,6 @@ static void ssl_lock(int mode, int n, const char *file, int line)
}
}
-#if !defined(OPENSSL_VERSION_NUMBER) || OPENSSL_VERSION_NUMBER < 0x10100000L
int SSL_library_init(void)
{
#if defined(AST_DEVMODE)
@@ -116,9 +116,6 @@ void ERR_free_strings(void)
{
/* we can't allow this to be called, ever */
}
-#endif /* !defined(OPENSSL_VERSION_NUMBER) || OPENSSL_VERSION_NUMBER < 0x10100000L */
-
-#endif /* HAVE_OPENSSL */
/*!
* \internal
@@ -128,8 +125,6 @@ void ERR_free_strings(void)
*/
int ast_ssl_init(void)
{
-#if defined(HAVE_OPENSSL) && defined(OPENSSL_VERSION_NUMBER) && \
- OPENSSL_VERSION_NUMBER < 0x10100000L
unsigned int i;
int (*real_SSL_library_init)(void);
void (*real_CRYPTO_set_id_callback)(unsigned long (*)(void));
@@ -194,7 +189,14 @@ int ast_ssl_init(void)
startup_complete = 1;
-#endif /* HAVE_OPENSSL and its version < 1.1 */
return 0;
}
+#else
+
+int ast_ssl_init(void)
+{
+ return 0;
+}
+
+#endif
diff --git a/main/manager.c b/main/manager.c
index 2f16f0470..17057b37f 100644
--- a/main/manager.c
+++ b/main/manager.c
@@ -6081,7 +6081,7 @@ static int action_coreshowchannels(struct mansession *s, const struct message *m
for (; (msg = ao2_iterator_next(&it_chans)); ao2_ref(msg, -1)) {
struct ast_channel_snapshot *cs = stasis_message_data(msg);
struct ast_str *built = ast_manager_build_channel_state_string_prefix(cs, "");
- char durbuf[10] = "";
+ char durbuf[16] = "";
if (!built) {
continue;
diff --git a/main/netsock2.c b/main/netsock2.c
index 83538b5f4..73595fe84 100644
--- a/main/netsock2.c
+++ b/main/netsock2.c
@@ -477,8 +477,12 @@ uint32_t ast_sockaddr_ipv4(const struct ast_sockaddr *addr)
int ast_sockaddr_is_ipv4(const struct ast_sockaddr *addr)
{
- return addr->ss.ss_family == AF_INET &&
- addr->len == sizeof(struct sockaddr_in);
+ /*
+ * Test addr->len first to be tolerant of an ast_sockaddr_setnull()
+ * addr. In that case addr->len might be the only value initialized.
+ */
+ return addr->len == sizeof(struct sockaddr_in)
+ && addr->ss.ss_family == AF_INET;
}
int ast_sockaddr_is_ipv4_mapped(const struct ast_sockaddr *addr)
@@ -500,8 +504,12 @@ int ast_sockaddr_is_ipv6_link_local(const struct ast_sockaddr *addr)
int ast_sockaddr_is_ipv6(const struct ast_sockaddr *addr)
{
- return addr->ss.ss_family == AF_INET6 &&
- addr->len == sizeof(struct sockaddr_in6);
+ /*
+ * Test addr->len first to be tolerant of an ast_sockaddr_setnull()
+ * addr. In that case addr->len might be the only value initialized.
+ */
+ return addr->len == sizeof(struct sockaddr_in6)
+ && addr->ss.ss_family == AF_INET6;
}
int ast_sockaddr_is_any(const struct ast_sockaddr *addr)
diff --git a/main/pbx_app.c b/main/pbx_app.c
index 1d90dac87..1ce77122c 100644
--- a/main/pbx_app.c
+++ b/main/pbx_app.c
@@ -396,6 +396,11 @@ int ast_unregister_application(const char *app)
struct ast_app *cur;
int cmp;
+ /* Anticipate need for conlock in unreference_cached_app(), in order to avoid
+ * possible deadlock with pbx_extension_helper()/pbx_findapp()
+ */
+ ast_rdlock_contexts();
+
AST_RWLIST_WRLOCK(&apps);
AST_RWLIST_TRAVERSE_SAFE_BEGIN(&apps, cur, list) {
cmp = strcasecmp(app, cur->name);
@@ -418,6 +423,8 @@ int ast_unregister_application(const char *app)
AST_RWLIST_TRAVERSE_SAFE_END;
AST_RWLIST_UNLOCK(&apps);
+ ast_unlock_contexts();
+
return cur ? 0 : -1;
}
diff --git a/main/rtp_engine.c b/main/rtp_engine.c
index 33770877c..d82bc4980 100644
--- a/main/rtp_engine.c
+++ b/main/rtp_engine.c
@@ -2105,35 +2105,35 @@ int ast_rtp_dtls_cfg_parse(struct ast_rtp_dtls_cfg *dtls_cfg, const char *name,
return -1;
}
} else if (!strcasecmp(name, "dtlscertfile")) {
- ast_free(dtls_cfg->certfile);
if (!ast_strlen_zero(value) && !ast_file_is_readable(value)) {
ast_log(LOG_ERROR, "%s file %s does not exist or is not readable\n", name, value);
return -1;
}
+ ast_free(dtls_cfg->certfile);
dtls_cfg->certfile = ast_strdup(value);
} else if (!strcasecmp(name, "dtlsprivatekey")) {
- ast_free(dtls_cfg->pvtfile);
if (!ast_strlen_zero(value) && !ast_file_is_readable(value)) {
ast_log(LOG_ERROR, "%s file %s does not exist or is not readable\n", name, value);
return -1;
}
+ ast_free(dtls_cfg->pvtfile);
dtls_cfg->pvtfile = ast_strdup(value);
} else if (!strcasecmp(name, "dtlscipher")) {
ast_free(dtls_cfg->cipher);
dtls_cfg->cipher = ast_strdup(value);
} else if (!strcasecmp(name, "dtlscafile")) {
- ast_free(dtls_cfg->cafile);
if (!ast_strlen_zero(value) && !ast_file_is_readable(value)) {
ast_log(LOG_ERROR, "%s file %s does not exist or is not readable\n", name, value);
return -1;
}
+ ast_free(dtls_cfg->cafile);
dtls_cfg->cafile = ast_strdup(value);
} else if (!strcasecmp(name, "dtlscapath") || !strcasecmp(name, "dtlscadir")) {
- ast_free(dtls_cfg->capath);
if (!ast_strlen_zero(value) && !ast_file_is_readable(value)) {
ast_log(LOG_ERROR, "%s file %s does not exist or is not readable\n", name, value);
return -1;
}
+ ast_free(dtls_cfg->capath);
dtls_cfg->capath = ast_strdup(value);
} else if (!strcasecmp(name, "dtlssetup")) {
if (!strcasecmp(value, "active")) {
@@ -2695,9 +2695,10 @@ int ast_rtp_engine_init(void)
set_next_mime_type(ast_format_siren7, 0, "audio", "G7221", 16000);
set_next_mime_type(ast_format_siren14, 0, "audio", "G7221", 32000);
set_next_mime_type(ast_format_g719, 0, "audio", "G719", 48000);
- /* Opus and VP8 */
+ /* Opus, VP8, and VP9 */
set_next_mime_type(ast_format_opus, 0, "audio", "opus", 48000);
set_next_mime_type(ast_format_vp8, 0, "video", "VP8", 90000);
+ set_next_mime_type(ast_format_vp9, 0, "video", "VP9", 90000);
/* Define the static rtp payload mappings */
add_static_payload(0, ast_format_ulaw, 0);
@@ -2730,6 +2731,8 @@ int ast_rtp_engine_init(void)
add_static_payload(104, ast_format_mp4, 0);
add_static_payload(105, ast_format_t140_red, 0); /* Real time text chat (with redundancy encoding) */
add_static_payload(106, ast_format_t140, 0); /* Real time text chat */
+ add_static_payload(108, ast_format_vp9, 0);
+
add_static_payload(110, ast_format_speex, 0);
add_static_payload(111, ast_format_g726, 0);
add_static_payload(112, ast_format_g726_aal2, 0);
diff --git a/main/say.c b/main/say.c
index 16241944c..48bd0ee5c 100644
--- a/main/say.c
+++ b/main/say.c
@@ -4082,9 +4082,9 @@ int ast_say_date_with_format_da(struct ast_channel *chan, time_t t, const char *
}
if (!res && next_item(&format[offset + 1]) == 'S') { /* minutes only if seconds follow */
if (tm.tm_min == 1) {
- res = wait_file(chan, ints, "digits/minute", lang);
+ res = wait_file(chan, ints, "minute", lang);
} else {
- res = wait_file(chan, ints, "digits/minutes", lang);
+ res = wait_file(chan, ints, "minutes", lang);
}
}
break;
@@ -4158,7 +4158,7 @@ int ast_say_date_with_format_da(struct ast_channel *chan, time_t t, const char *
if (!res) {
res = ast_say_number(chan, tm.tm_sec, ints, lang, "f");
if (!res) {
- res = wait_file(chan, ints, "digits/seconds", lang);
+ res = wait_file(chan, ints, "seconds", lang);
}
}
break;
@@ -4285,9 +4285,9 @@ int ast_say_date_with_format_de(struct ast_channel *chan, time_t t, const char *
if (!res && next_item(&format[offset + 1]) == 'S') { /* minutes only if seconds follow */
if (tm.tm_min == 1) {
- res = wait_file(chan, ints, "digits/minute", lang);
+ res = wait_file(chan, ints, "minute", lang);
} else {
- res = wait_file(chan, ints, "digits/minutes", lang);
+ res = wait_file(chan, ints, "minutes", lang);
}
}
break;
@@ -4361,7 +4361,7 @@ int ast_say_date_with_format_de(struct ast_channel *chan, time_t t, const char *
if (!res) {
res = ast_say_number(chan, tm.tm_sec, ints, lang, "f");
if (!res) {
- res = wait_file(chan, ints, tm.tm_sec == 1 ? "digits/second" : "digits/seconds", lang);
+ res = wait_file(chan, ints, tm.tm_sec == 1 ? "second" : "seconds", lang);
}
}
break;
@@ -5081,7 +5081,7 @@ int ast_say_date_with_format_fr(struct ast_channel *chan, time_t t, const char *
/* Seconds */
res = ast_say_number(chan, tm.tm_sec, ints, lang, (char * ) NULL);
if (!res) {
- res = wait_file(chan, ints, "digits/second", lang);
+ res = wait_file(chan, ints, "second", lang);
}
break;
case 'T':
@@ -5732,9 +5732,9 @@ int ast_say_date_with_format_pl(struct ast_channel *chan, time_t thetime, const
one = tm.tm_sec % 10;
if (one > 1 && one < 5 && ten != 1)
- res = wait_file(chan, ints, "digits/seconds", lang);
+ res = wait_file(chan, ints, "seconds", lang);
else
- res = wait_file(chan, ints, "digits/second", lang);
+ res = wait_file(chan, ints, "second", lang);
}
}
}
@@ -5898,9 +5898,9 @@ int ast_say_date_with_format_pt(struct ast_channel *chan, time_t t, const char *
res = ast_say_number(chan, tm.tm_min, ints, lang, NULL);
if (!res) {
if (tm.tm_min > 1) {
- res = wait_file(chan, ints, "digits/minutes", lang);
+ res = wait_file(chan, ints, "minutes", lang);
} else {
- res = wait_file(chan, ints, "digits/minute", lang);
+ res = wait_file(chan, ints, "minute", lang);
}
}
} else {
@@ -5996,9 +5996,9 @@ int ast_say_date_with_format_pt(struct ast_channel *chan, time_t t, const char *
res = ast_say_number(chan, tm.tm_sec, ints, lang, NULL);
if (!res) {
if (tm.tm_sec > 1) {
- res = wait_file(chan, ints, "digits/seconds", lang);
+ res = wait_file(chan, ints, "seconds", lang);
} else {
- res = wait_file(chan, ints, "digits/second", lang);
+ res = wait_file(chan, ints, "second", lang);
}
}
} else {
@@ -6212,7 +6212,7 @@ int ast_say_date_with_format_zh(struct ast_channel *chan, time_t t, const char *
}
}
if (!res) {
- res = wait_file(chan, ints, "digits/minute", lang);
+ res = wait_file(chan, ints, "minute", lang);
}
break;
case 'P':
@@ -6296,7 +6296,7 @@ int ast_say_date_with_format_zh(struct ast_channel *chan, time_t t, const char *
}
}
if (!res) {
- res = wait_file(chan, ints, "digits/second", lang);
+ res = wait_file(chan, ints, "second", lang);
}
break;
case 'T':
@@ -6451,7 +6451,7 @@ int ast_say_time_hu(struct ast_channel *chan, time_t t, const char *ints, const
if (tm.tm_min > 0) {
res = ast_say_number(chan, tm.tm_min, ints, lang, "f");
if (!res)
- res = ast_streamfile(chan, "digits/minute", lang);
+ res = ast_streamfile(chan, "minute", lang);
}
return res;
}
@@ -6546,9 +6546,9 @@ int ast_say_time_pt_BR(struct ast_channel *chan, time_t t, const char *ints, con
res = ast_say_number(chan, tm.tm_min, ints, lang, (char *) NULL);
if (!res) {
if (tm.tm_min > 1)
- res = wait_file(chan, ints, "digits/minutes", lang);
+ res = wait_file(chan, ints, "minutes", lang);
else
- res = wait_file(chan, ints, "digits/minute", lang);
+ res = wait_file(chan, ints, "minute", lang);
}
}
return res;
@@ -6608,7 +6608,7 @@ int ast_say_time_zh(struct ast_channel *chan, time_t t, const char *ints, const
if (!res)
res = ast_say_number(chan, tm.tm_min, ints, lang, (char *) NULL);
if (!res)
- res = ast_streamfile(chan, "digits/minute", lang);
+ res = ast_streamfile(chan, "minute", lang);
if (!res)
res = ast_waitstream(chan, ints);
return res;
@@ -7031,7 +7031,7 @@ int ast_say_datetime_zh(struct ast_channel *chan, time_t t, const char *ints, co
if (!res)
res = ast_say_number(chan, tm.tm_min, ints, lang, (char *) NULL);
if (!res)
- res = ast_streamfile(chan, "digits/minute", lang);
+ res = ast_streamfile(chan, "minute", lang);
if (!res)
res = ast_waitstream(chan, ints);
return res;
@@ -7909,7 +7909,7 @@ static int ast_say_date_with_format_gr(struct ast_channel *chan, time_t t, const
if (!res)
res = ast_say_number_full_gr(chan, tm.tm_sec, ints, lang, -1, -1);
if (!res)
- ast_copy_string(nextmsg, "digits/seconds", sizeof(nextmsg));
+ ast_copy_string(nextmsg, "seconds", sizeof(nextmsg));
res = wait_file(chan, ints, nextmsg, lang);
break;
case 'T':
diff --git a/main/stdtime/localtime.c b/main/stdtime/localtime.c
index 9cdf614d5..5b5526e6f 100644
--- a/main/stdtime/localtime.c
+++ b/main/stdtime/localtime.c
@@ -2436,7 +2436,7 @@ static const char *store_by_locale(locale_t prevlocale)
cur = NULL;
AST_LIST_LOCK(&localelist);
for (x = 0; x < 10000; x++) {
- char name[5];
+ char name[6];
snprintf(name, sizeof(name), "%04d", x);
if (!find_by_name(name)) {
if ((cur = ast_calloc(1, sizeof(*cur) + strlen(name) + 1))) {
diff --git a/main/stun.c b/main/stun.c
index fe1afbab7..6ebb2acf4 100644
--- a/main/stun.c
+++ b/main/stun.c
@@ -345,6 +345,8 @@ int ast_stun_handle_packet(int s, struct sockaddr_in *src, unsigned char *data,
if (st.username) {
append_attr_string(&attr, STUN_USERNAME, st.username, &resplen, &respleft);
snprintf(combined, sizeof(combined), "%16s%16s", st.username + 16, st.username);
+ } else {
+ combined[0] = '\0';
}
append_attr_address(&attr, STUN_MAPPED_ADDRESS, src, &resplen, &respleft);
@@ -400,8 +402,6 @@ int ast_stun_request(int s, struct sockaddr_in *dst,
stun_req_id(req);
reqlen = 0;
reqleft = sizeof(req_buf) - sizeof(struct stun_header);
- req->msgtype = 0;
- req->msglen = 0;
attr = (struct stun_attr *) req->ies;
if (username) {
append_attr_string(&attr, STUN_USERNAME, username, &reqlen, &reqleft);
diff --git a/main/tcptls.c b/main/tcptls.c
index 7e09e6661..bc2d64bd4 100644
--- a/main/tcptls.c
+++ b/main/tcptls.c
@@ -447,13 +447,13 @@ static int tcptls_stream_close(void *cookie)
ERR_error_string(sslerr, err), ssl_error_to_string(sslerr, res));
}
-#if defined(OPENSSL_VERSION_NUMBER) && OPENSSL_VERSION_NUMBER >= 0x10100000L
+#if defined(OPENSSL_VERSION_NUMBER) && OPENSSL_VERSION_NUMBER >= 0x10100000L && !defined(LIBRESSL_VERSION_NUMBER)
if (!SSL_is_server(stream->ssl)) {
#else
if (!stream->ssl->server) {
#endif
/* For client threads, ensure that the error stack is cleared */
-#if !defined(OPENSSL_VERSION_NUMBER) || OPENSSL_VERSION_NUMBER < 0x10100000L
+#if !defined(OPENSSL_VERSION_NUMBER) || OPENSSL_VERSION_NUMBER < 0x10100000L || defined(LIBRESSL_VERSION_NUMBER)
#if defined(OPENSSL_VERSION_NUMBER) && OPENSSL_VERSION_NUMBER >= 0x10000000L
ERR_remove_thread_state(NULL);
#else
diff --git a/main/utils.c b/main/utils.c
index b31db5982..0824a373a 100644
--- a/main/utils.c
+++ b/main/utils.c
@@ -1530,7 +1530,7 @@ char *ast_strsep(char **iss, const char sep, uint32_t flags)
int found = 0;
char stack[8];
- if (iss == NULL || *iss == '\0') {
+ if (ast_strlen_zero(st)) {
return NULL;
}
diff --git a/makeopts.in b/makeopts.in
index 56bf11ef7..4baefa298 100644
--- a/makeopts.in
+++ b/makeopts.in
@@ -29,8 +29,9 @@ FETCH=@FETCH@
DOWNLOAD=@DOWNLOAD@
DOWNLOAD_TO_STDOUT=@DOWNLOAD_TO_STDOUT@
DOWNLOAD_TIMEOUT=@DOWNLOAD_TIMEOUT@
-SOUNDS_CACHE_DIR=@SOUNDS_CACHE_DIR@
-EXTERNALS_CACHE_DIR=@EXTERNALS_CACHE_DIR@
+AST_DOWNLOAD_CACHE=@AST_DOWNLOAD_CACHE@
+SOUNDS_CACHE_DIR=$(or @SOUNDS_CACHE_DIR@,${AST_DOWNLOAD_CACHE})
+EXTERNALS_CACHE_DIR=$(or @EXTERNALS_CACHE_DIR@,${AST_DOWNLOAD_CACHE})
RUBBER=@RUBBER@
CATDVI=@CATDVI@
KPATHSEA=@KPATHSEA@
@@ -62,6 +63,8 @@ HOST_OS=@HOST_OS@
OSARCH=@OSARCH@
OSREV=@PBX_OSREV@
+BIND8_CFLAGS=@BIND8_CFLAGS@
+
PTHREAD_CFLAGS=@PTHREAD_CFLAGS@
PTHREAD_LIBS=@PTHREAD_LIBS@
@@ -114,6 +117,7 @@ AST_ASTERISKSSL=@AST_ASTERISKSSL@
AST_DECLARATION_AFTER_STATEMENT=@AST_DECLARATION_AFTER_STATEMENT@
AST_TRAMPOLINES=@AST_TRAMPOLINES@
AST_NO_STRICT_OVERFLOW=@AST_NO_STRICT_OVERFLOW@
+AST_NO_FORMAT_TRUNCATION=@AST_NO_FORMAT_TRUNCATION@
AST_SHADOW_WARNINGS=@AST_SHADOW_WARNINGS@
AST_NESTED_FUNCTIONS=@AST_NESTED_FUNCTIONS@
AST_CLANG_BLOCKS=@AST_CLANG_BLOCKS@
diff --git a/res/res_ari_applications.c b/res/res_ari_applications.c
index cb12e84c8..290719d36 100644
--- a/res/res_ari_applications.c
+++ b/res/res_ari_applications.c
@@ -502,6 +502,10 @@ static int unload_module(void)
static int load_module(void)
{
int res = 0;
+
+ CHECK_ARI_MODULE_LOADED();
+
+
stasis_app_ref();
res |= ast_ari_add_handler(&applications);
if (res) {
diff --git a/res/res_ari_asterisk.c b/res/res_ari_asterisk.c
index 1a574aaaf..73e4d0ce3 100644
--- a/res/res_ari_asterisk.c
+++ b/res/res_ari_asterisk.c
@@ -1223,6 +1223,10 @@ static int unload_module(void)
static int load_module(void)
{
int res = 0;
+
+ CHECK_ARI_MODULE_LOADED();
+
+
stasis_app_ref();
res |= ast_ari_add_handler(&asterisk);
if (res) {
diff --git a/res/res_ari_bridges.c b/res/res_ari_bridges.c
index 69d4d6ed5..b92333095 100644
--- a/res/res_ari_bridges.c
+++ b/res/res_ari_bridges.c
@@ -1415,6 +1415,10 @@ static int unload_module(void)
static int load_module(void)
{
int res = 0;
+
+ CHECK_ARI_MODULE_LOADED();
+
+
stasis_app_ref();
res |= ast_ari_add_handler(&bridges);
if (res) {
diff --git a/res/res_ari_channels.c b/res/res_ari_channels.c
index f59f20634..621767980 100644
--- a/res/res_ari_channels.c
+++ b/res/res_ari_channels.c
@@ -2479,6 +2479,10 @@ static int unload_module(void)
static int load_module(void)
{
int res = 0;
+
+ CHECK_ARI_MODULE_LOADED();
+
+
stasis_app_ref();
res |= ast_ari_add_handler(&channels);
if (res) {
diff --git a/res/res_ari_device_states.c b/res/res_ari_device_states.c
index a3711e6eb..fe1817d5d 100644
--- a/res/res_ari_device_states.c
+++ b/res/res_ari_device_states.c
@@ -333,6 +333,10 @@ static int unload_module(void)
static int load_module(void)
{
int res = 0;
+
+ CHECK_ARI_MODULE_LOADED();
+
+
stasis_app_ref();
res |= ast_ari_add_handler(&deviceStates);
if (res) {
diff --git a/res/res_ari_endpoints.c b/res/res_ari_endpoints.c
index 43d255898..a46b0dc61 100644
--- a/res/res_ari_endpoints.c
+++ b/res/res_ari_endpoints.c
@@ -457,6 +457,10 @@ static int unload_module(void)
static int load_module(void)
{
int res = 0;
+
+ CHECK_ARI_MODULE_LOADED();
+
+
stasis_app_ref();
res |= ast_ari_add_handler(&endpoints);
if (res) {
diff --git a/res/res_ari_events.c b/res/res_ari_events.c
index fd208c57b..b6a44d9b9 100644
--- a/res/res_ari_events.c
+++ b/res/res_ari_events.c
@@ -430,22 +430,29 @@ static int unload_module(void)
static int load_module(void)
{
int res = 0;
- struct ast_websocket_protocol *protocol;
- events.ws_server = ast_websocket_server_create();
- if (!events.ws_server) {
- return AST_MODULE_LOAD_DECLINE;
- }
+ CHECK_ARI_MODULE_LOADED();
- protocol = ast_websocket_sub_protocol_alloc("ari");
- if (!protocol) {
- ao2_ref(events.ws_server, -1);
- events.ws_server = NULL;
- return AST_MODULE_LOAD_DECLINE;
+ /* This is scoped to not conflict with CHECK_ARI_MODULE_LOADED */
+ {
+ struct ast_websocket_protocol *protocol;
+
+ events.ws_server = ast_websocket_server_create();
+ if (!events.ws_server) {
+ return AST_MODULE_LOAD_DECLINE;
+ }
+
+ protocol = ast_websocket_sub_protocol_alloc("ari");
+ if (!protocol) {
+ ao2_ref(events.ws_server, -1);
+ events.ws_server = NULL;
+ return AST_MODULE_LOAD_DECLINE;
+ }
+ protocol->session_attempted = ast_ari_events_event_websocket_ws_attempted_cb;
+ protocol->session_established = ast_ari_events_event_websocket_ws_established_cb;
+ res |= ast_websocket_server_add_protocol2(events.ws_server, protocol);
}
- protocol->session_attempted = ast_ari_events_event_websocket_ws_attempted_cb;
- protocol->session_established = ast_ari_events_event_websocket_ws_established_cb;
- res |= ast_websocket_server_add_protocol2(events.ws_server, protocol);
+
stasis_app_ref();
res |= ast_ari_add_handler(&events);
if (res) {
diff --git a/res/res_ari_mailboxes.c b/res/res_ari_mailboxes.c
index f85541cf0..600ecfd48 100644
--- a/res/res_ari_mailboxes.c
+++ b/res/res_ari_mailboxes.c
@@ -339,6 +339,10 @@ static int unload_module(void)
static int load_module(void)
{
int res = 0;
+
+ CHECK_ARI_MODULE_LOADED();
+
+
stasis_app_ref();
res |= ast_ari_add_handler(&mailboxes);
if (res) {
diff --git a/res/res_ari_playbacks.c b/res/res_ari_playbacks.c
index 25e211c55..106463b5b 100644
--- a/res/res_ari_playbacks.c
+++ b/res/res_ari_playbacks.c
@@ -291,6 +291,10 @@ static int unload_module(void)
static int load_module(void)
{
int res = 0;
+
+ CHECK_ARI_MODULE_LOADED();
+
+
stasis_app_ref();
res |= ast_ari_add_handler(&playbacks);
if (res) {
diff --git a/res/res_ari_recordings.c b/res/res_ari_recordings.c
index 29720a84a..c43148d83 100644
--- a/res/res_ari_recordings.c
+++ b/res/res_ari_recordings.c
@@ -807,6 +807,10 @@ static int unload_module(void)
static int load_module(void)
{
int res = 0;
+
+ CHECK_ARI_MODULE_LOADED();
+
+
stasis_app_ref();
res |= ast_ari_add_handler(&recordings);
if (res) {
diff --git a/res/res_ari_sounds.c b/res/res_ari_sounds.c
index 6d09d2cbe..e58ecd1cf 100644
--- a/res/res_ari_sounds.c
+++ b/res/res_ari_sounds.c
@@ -221,6 +221,10 @@ static int unload_module(void)
static int load_module(void)
{
int res = 0;
+
+ CHECK_ARI_MODULE_LOADED();
+
+
stasis_app_ref();
res |= ast_ari_add_handler(&sounds);
if (res) {
diff --git a/res/res_calendar.c b/res/res_calendar.c
index 3725c9435..298970a92 100644
--- a/res/res_calendar.c
+++ b/res/res_calendar.c
@@ -735,7 +735,7 @@ static void *do_notify(void *data)
struct ast_channel *chan = NULL;
struct ast_variable *itervar;
char *tech, *dest;
- char buf[8];
+ char buf[33];
struct ast_format_cap *caps;
tech = ast_strdupa(event->owner->notify_channel);
diff --git a/res/res_calendar_icalendar.c b/res/res_calendar_icalendar.c
index 8ac905174..a6ce62708 100644
--- a/res/res_calendar_icalendar.c
+++ b/res/res_calendar_icalendar.c
@@ -335,7 +335,7 @@ static void icalendar_add_event(icalcomponent *comp, struct icaltime_span *span,
start_time = icaltime_current_time_with_zone(icaltimezone_get_utc_timezone());
end_time = icaltime_current_time_with_zone(icaltimezone_get_utc_timezone());
end_time.second += pvt->owner->timeframe * 60;
- icaltime_normalize(end_time);
+ end_time = icaltime_normalize(end_time);
for (iter = icalcomponent_get_first_component(pvt->data, ICAL_VEVENT_COMPONENT);
iter;
diff --git a/res/res_config_pgsql.c b/res/res_config_pgsql.c
index e74b73036..b0a24c464 100644
--- a/res/res_config_pgsql.c
+++ b/res/res_config_pgsql.c
@@ -1329,7 +1329,7 @@ static int require_pgsql(const char *database, const char *tablename, va_list ap
/* Size is minimum length; make it at least 50% greater,
* just to be sure, because PostgreSQL doesn't support
* resizing columns. */
- snprintf(fieldtype, sizeof(fieldtype), "CHAR(%d)",
+ snprintf(fieldtype, sizeof(fieldtype), "CHAR(%hhu)",
size < 15 ? size * 2 :
(size * 3 / 2 > 255) ? 255 : size * 3 / 2);
} else if (type == RQ_INTEGER1 || type == RQ_UINTEGER1 || type == RQ_INTEGER2) {
diff --git a/res/res_corosync.c b/res/res_corosync.c
index 6bbbc34b9..ce94e4151 100644
--- a/res/res_corosync.c
+++ b/res/res_corosync.c
@@ -79,6 +79,15 @@ struct corosync_node {
struct ast_sockaddr addr;
};
+/*! \brief Corosync ipc dispatch/request and reply size */
+#define COROSYNC_IPC_BUFFER_SIZE (8192 * 128)
+
+/*! \brief Version of pthread_create to ensure stack is large enough */
+#define corosync_pthread_create_background(a, b, c, d) \
+ ast_pthread_create_stack(a, b, c, d, \
+ (AST_BACKGROUND_STACKSIZE + (3 * COROSYNC_IPC_BUFFER_SIZE)), \
+ __FILE__, __FUNCTION__, __LINE__, #c)
+
static struct corosync_node *corosync_node_alloc(struct ast_event *event)
{
struct corosync_node *node;
@@ -810,10 +819,21 @@ static char *corosync_show_members(struct ast_cli_entry *e, int cmd, struct ast_
for (i = 1, cs_err = cpg_iteration_next(cpg_iter, &cpg_desc);
cs_err == CS_OK;
cs_err = cpg_iteration_next(cpg_iter, &cpg_desc), i++) {
+#ifdef HAVE_COROSYNC_CFG_STATE_TRACK
corosync_cfg_node_address_t addrs[8];
int num_addrs = 0;
unsigned int j;
+#endif
+
+ ast_cli(a->fd, "=== Node %u\n", i);
+ ast_cli(a->fd, "=== --> Group: %s\n", cpg_desc.group.value);
+#ifdef HAVE_COROSYNC_CFG_STATE_TRACK
+ /*
+ * Corosync 2.x cfg lib needs to allocate 1M on stack after calling
+ * corosync_cfg_get_node_addrs. netconsole thread has allocated only 0.5M
+ * resulting in crash.
+ */
cs_err = corosync_cfg_get_node_addrs(cfg_handle, cpg_desc.nodeid,
ARRAY_LEN(addrs), &num_addrs, addrs);
if (cs_err != CS_OK) {
@@ -821,9 +841,6 @@ static char *corosync_show_members(struct ast_cli_entry *e, int cmd, struct ast_
continue;
}
- ast_cli(a->fd, "=== Node %u\n", i);
- ast_cli(a->fd, "=== --> Group: %s\n", cpg_desc.group.value);
-
for (j = 0; j < num_addrs; j++) {
struct sockaddr *sa = (struct sockaddr *) addrs[j].address;
size_t sa_len = (size_t) addrs[j].address_length;
@@ -833,7 +850,9 @@ static char *corosync_show_members(struct ast_cli_entry *e, int cmd, struct ast_
ast_cli(a->fd, "=== --> Address %u: %s\n", j + 1, buf);
}
-
+#else
+ ast_cli(a->fd, "=== --> Nodeid: %"PRIu32"\n", cpg_desc.nodeid);
+#endif
}
ast_cli(a->fd, "===\n"
@@ -1159,7 +1178,7 @@ static int load_module(void)
goto failed;
}
- if (ast_pthread_create_background(&dispatch_thread.id, NULL,
+ if (corosync_pthread_create_background(&dispatch_thread.id, NULL,
dispatch_thread_handler, NULL)) {
ast_log(LOG_ERROR, "Error starting CPG dispatch thread.\n");
goto failed;
diff --git a/res/res_http_post.c b/res/res_http_post.c
index 2ee792af9..3e1ed03f6 100644
--- a/res/res_http_post.c
+++ b/res/res_http_post.c
@@ -57,6 +57,9 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
#ifdef GMIME_TYPE_CONTENT_TYPE
#define AST_GMIME_VER_24
#endif
+#if GMIME_MAJOR_VERSION >= 3
+#define AST_GMIME_VER_30
+#endif
/* just a little structure to hold callback info for gmime */
struct mime_cbinfo {
@@ -86,7 +89,11 @@ static void post_raw(GMimePart *part, const char *post_dir, const char *fn)
stream = g_mime_stream_fs_new(fd);
+#ifdef AST_GMIME_VER_30
+ content = g_mime_part_get_content(part);
+#else
content = g_mime_part_get_content_object(part);
+#endif
g_mime_data_wrapper_write_to_stream(content, stream);
g_mime_stream_flush(stream);
@@ -109,7 +116,11 @@ static GMimeMessage *parse_message(FILE *f)
g_object_unref(stream);
- message = g_mime_parser_construct_message(parser);
+ message = g_mime_parser_construct_message(parser
+#ifdef AST_GMIME_VER_30
+ , NULL
+#endif
+ );
g_object_unref(parser);
@@ -488,7 +499,11 @@ static int reload(void)
static int load_module(void)
{
- g_mime_init(0);
+ g_mime_init(
+#ifndef AST_GMIME_VER_30
+ 0
+#endif
+ );
__ast_http_post_load(0);
diff --git a/res/res_monitor.c b/res/res_monitor.c
index ebf98439c..c4ee674f9 100644
--- a/res/res_monitor.c
+++ b/res/res_monitor.c
@@ -62,17 +62,17 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
<syntax>
<parameter name="file_format" argsep=":">
<argument name="file_format" required="true">
- <para>optional, if not set, defaults to <literal>wav</literal></para>
+ <para>Optional. If not set, defaults to <literal>wav</literal></para>
</argument>
<argument name="urlbase" />
</parameter>
<parameter name="fname_base">
- <para>if set, changes the filename used to the one specified.</para>
+ <para>If set, changes the filename used to the one specified.</para>
</parameter>
<parameter name="options">
<optionlist>
<option name="m">
- <para>when the recording ends mix the two leg files into one and
+ <para>When the recording ends mix the two leg files into one and
delete the two leg files. If the variable <variable>MONITOR_EXEC</variable>
is set, the application referenced in it will be executed instead of
soxmix/sox and the raw leg files will NOT be deleted automatically.
@@ -83,6 +83,13 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
will be passed on as additional arguments to <variable>MONITOR_EXEC</variable>.
Both <variable>MONITOR_EXEC</variable> and the Mix flag can be set from the
administrator interface.</para>
+ <warning><para>Do not use untrusted strings such as
+ <variable>CALLERID(num)</variable> or <variable>CALLERID(name)</variable>
+ as part of <variable>MONITOR_EXEC</variable> or
+ <variable>MONITOR_EXEC_ARGS</variable>. You risk a command injection
+ attack executing arbitrary commands if the untrusted strings aren't
+ filtered to remove dangerous characters. See function
+ <variable>FILTER()</variable>.</para></warning>
</option>
<option name="b">
<para>Don't begin recording unless a call is bridged to another channel.</para>
@@ -460,7 +467,7 @@ int AST_OPTIONAL_API_NAME(ast_monitor_stop)(struct ast_channel *chan, int need_l
LOCK_IF_NEEDED(chan, need_lock);
if (ast_channel_monitor(chan)) {
- char filename[ FILENAME_MAX ];
+ RAII_VAR(struct ast_str *, tmp, ast_str_create(1024), ast_free);
if (ast_channel_monitor(chan)->read_stream) {
ast_closestream(ast_channel_monitor(chan)->read_stream);
@@ -469,31 +476,29 @@ int AST_OPTIONAL_API_NAME(ast_monitor_stop)(struct ast_channel *chan, int need_l
ast_closestream(ast_channel_monitor(chan)->write_stream);
}
- if (ast_channel_monitor(chan)->filename_changed && !ast_strlen_zero(ast_channel_monitor(chan)->filename_base)) {
+ if (tmp && ast_channel_monitor(chan)->filename_changed && !ast_strlen_zero(ast_channel_monitor(chan)->filename_base)) {
if (ast_fileexists(ast_channel_monitor(chan)->read_filename,NULL,NULL) > 0) {
- snprintf(filename, FILENAME_MAX, "%s-in", ast_channel_monitor(chan)->filename_base);
- if (ast_fileexists(filename, NULL, NULL) > 0) {
- ast_filedelete(filename, NULL);
+ ast_str_set(&tmp, 0, "%s-in", ast_channel_monitor(chan)->filename_base);
+ if (ast_fileexists(ast_str_buffer(tmp), NULL, NULL) > 0) {
+ ast_filedelete(ast_str_buffer(tmp), NULL);
}
- ast_filerename(ast_channel_monitor(chan)->read_filename, filename, ast_channel_monitor(chan)->format);
+ ast_filerename(ast_channel_monitor(chan)->read_filename, ast_str_buffer(tmp), ast_channel_monitor(chan)->format);
} else {
ast_log(LOG_WARNING, "File %s not found\n", ast_channel_monitor(chan)->read_filename);
}
- if (ast_fileexists(ast_channel_monitor(chan)->write_filename,NULL,NULL) > 0) {
- snprintf(filename, FILENAME_MAX, "%s-out", ast_channel_monitor(chan)->filename_base);
- if (ast_fileexists(filename, NULL, NULL) > 0) {
- ast_filedelete(filename, NULL);
+ if (tmp && ast_fileexists(ast_channel_monitor(chan)->write_filename,NULL,NULL) > 0) {
+ ast_str_set(&tmp, 0, "%s-out", ast_channel_monitor(chan)->filename_base);
+ if (ast_fileexists(ast_str_buffer(tmp), NULL, NULL) > 0) {
+ ast_filedelete(ast_str_buffer(tmp), NULL);
}
- ast_filerename(ast_channel_monitor(chan)->write_filename, filename, ast_channel_monitor(chan)->format);
+ ast_filerename(ast_channel_monitor(chan)->write_filename, ast_str_buffer(tmp), ast_channel_monitor(chan)->format);
} else {
ast_log(LOG_WARNING, "File %s not found\n", ast_channel_monitor(chan)->write_filename);
}
}
- if (ast_channel_monitor(chan)->joinfiles && !ast_strlen_zero(ast_channel_monitor(chan)->filename_base)) {
- char tmp[1024];
- char tmp2[1024];
+ if (tmp && ast_channel_monitor(chan)->joinfiles && !ast_strlen_zero(ast_channel_monitor(chan)->filename_base)) {
const char *format = !strcasecmp(ast_channel_monitor(chan)->format,"wav49") ? "WAV" : ast_channel_monitor(chan)->format;
char *fname_base = ast_channel_monitor(chan)->filename_base;
const char *execute, *execute_args;
@@ -514,16 +519,17 @@ int AST_OPTIONAL_API_NAME(ast_monitor_stop)(struct ast_channel *chan, int need_l
if (ast_strlen_zero(execute_args)) {
execute_args = "";
}
-
- snprintf(tmp, sizeof(tmp), "%s \"%s-in.%s\" \"%s-out.%s\" \"%s.%s\" %s &",
+
+ ast_str_set(&tmp, 0, delfiles ? "( " : "");
+ ast_str_append(&tmp, 0, "%s \"%s-in.%s\" \"%s-out.%s\" \"%s.%s\" %s &",
execute, fname_base, format, fname_base, format, fname_base, format,execute_args);
if (delfiles) {
- snprintf(tmp2,sizeof(tmp2), "( %s& rm -f \"%s-\"* ) &",tmp, fname_base); /* remove legs when done mixing */
- ast_copy_string(tmp, tmp2, sizeof(tmp));
+ /* remove legs when done mixing */
+ ast_str_append(&tmp, 0, "& rm -f \"%s-\"* ) &", fname_base);
}
- ast_debug(1,"monitor executing %s\n",tmp);
- if (ast_safe_system(tmp) == -1)
- ast_log(LOG_WARNING, "Execute of %s failed.\n",tmp);
+ ast_debug(1,"monitor executing %s\n", ast_str_buffer(tmp));
+ if (ast_safe_system(ast_str_buffer(tmp)) == -1)
+ ast_log(LOG_WARNING, "Execute of %s failed.\n", ast_str_buffer(tmp));
}
if (!ast_strlen_zero(ast_channel_monitor(chan)->beep_id)) {
diff --git a/res/res_musiconhold.c b/res/res_musiconhold.c
index c52c96428..d79151676 100644
--- a/res/res_musiconhold.c
+++ b/res/res_musiconhold.c
@@ -158,6 +158,11 @@ struct moh_files_state {
static struct ast_flags global_flags[1] = {{0}}; /*!< global MOH_ flags */
+enum kill_methods {
+ KILL_METHOD_PROCESS_GROUP = 0,
+ KILL_METHOD_PROCESS
+};
+
struct mohclass {
char name[MAX_MUSICCLASS];
char dir[256];
@@ -178,6 +183,10 @@ struct mohclass {
int pid;
time_t start;
pthread_t thread;
+ /*! Millisecond delay between kill attempts */
+ size_t kill_delay;
+ /*! Kill method */
+ enum kill_methods kill_method;
/*! Source of audio */
int srcfd;
/*! Generic timer */
@@ -678,6 +687,51 @@ static int spawn_mp3(struct mohclass *class)
return fds[0];
}
+static int killer(pid_t pid, int signum, enum kill_methods kill_method)
+{
+ switch (kill_method) {
+ case KILL_METHOD_PROCESS_GROUP:
+ return killpg(pid, signum);
+ case KILL_METHOD_PROCESS:
+ return kill(pid, signum);
+ }
+
+ return -1;
+}
+
+static void killpid(int pid, size_t delay, enum kill_methods kill_method)
+{
+ if (killer(pid, SIGHUP, kill_method) < 0) {
+ if (errno == ESRCH) {
+ return;
+ }
+ ast_log(LOG_WARNING, "Unable to send a SIGHUP to MOH process '%d'?!!: %s\n", pid, strerror(errno));
+ } else {
+ ast_debug(1, "Sent HUP to pid %d%s\n", pid,
+ kill_method == KILL_METHOD_PROCESS_GROUP ? " and all children" : " only");
+ }
+ usleep(delay);
+ if (killer(pid, SIGTERM, kill_method) < 0) {
+ if (errno == ESRCH) {
+ return;
+ }
+ ast_log(LOG_WARNING, "Unable to terminate MOH process '%d'?!!: %s\n", pid, strerror(errno));
+ } else {
+ ast_debug(1, "Sent TERM to pid %d%s\n", pid,
+ kill_method == KILL_METHOD_PROCESS_GROUP ? " and all children" : " only");
+ }
+ usleep(delay);
+ if (killer(pid, SIGKILL, kill_method) < 0) {
+ if (errno == ESRCH) {
+ return;
+ }
+ ast_log(LOG_WARNING, "Unable to kill MOH process '%d'?!!: %s\n", pid, strerror(errno));
+ } else {
+ ast_debug(1, "Sent KILL to pid %d%s\n", pid,
+ kill_method == KILL_METHOD_PROCESS_GROUP ? " and all children" : " only");
+ }
+}
+
static void *monmp3thread(void *data)
{
#define MOH_MS_INTERVAL 100
@@ -753,28 +807,7 @@ static void *monmp3thread(void *data)
class->srcfd = -1;
pthread_testcancel();
if (class->pid > 1) {
- do {
- if (killpg(class->pid, SIGHUP) < 0) {
- if (errno == ESRCH) {
- break;
- }
- ast_log(LOG_WARNING, "Unable to send a SIGHUP to MOH process?!!: %s\n", strerror(errno));
- }
- usleep(100000);
- if (killpg(class->pid, SIGTERM) < 0) {
- if (errno == ESRCH) {
- break;
- }
- ast_log(LOG_WARNING, "Unable to terminate MOH process?!!: %s\n", strerror(errno));
- }
- usleep(100000);
- if (killpg(class->pid, SIGKILL) < 0) {
- if (errno == ESRCH) {
- break;
- }
- ast_log(LOG_WARNING, "Unable to kill MOH process?!!: %s\n", strerror(errno));
- }
- } while (0);
+ killpid(class->pid, class->kill_delay, class->kill_method);
class->pid = 0;
}
} else {
@@ -1328,6 +1361,7 @@ static struct mohclass *_moh_class_malloc(const char *file, int line, const char
)) {
class->format = ao2_bump(ast_format_slin);
class->srcfd = -1;
+ class->kill_delay = 100000;
}
return class;
@@ -1600,44 +1634,22 @@ static void moh_class_destructor(void *obj)
if (class->pid > 1) {
char buff[8192];
- int bytes, tbytes = 0, stime = 0, pid = 0;
+ int bytes, tbytes = 0, stime = 0;
ast_debug(1, "killing %d!\n", class->pid);
stime = time(NULL) + 2;
- pid = class->pid;
- class->pid = 0;
-
- /* Back when this was just mpg123, SIGKILL was fine. Now we need
- * to give the process a reason and time enough to kill off its
- * children. */
- do {
- if (killpg(pid, SIGHUP) < 0) {
- ast_log(LOG_WARNING, "Unable to send a SIGHUP to MOH process?!!: %s\n", strerror(errno));
- }
- usleep(100000);
- if (killpg(pid, SIGTERM) < 0) {
- if (errno == ESRCH) {
- break;
- }
- ast_log(LOG_WARNING, "Unable to terminate MOH process?!!: %s\n", strerror(errno));
- }
- usleep(100000);
- if (killpg(pid, SIGKILL) < 0) {
- if (errno == ESRCH) {
- break;
- }
- ast_log(LOG_WARNING, "Unable to kill MOH process?!!: %s\n", strerror(errno));
- }
- } while (0);
+ killpid(class->pid, class->kill_delay, class->kill_method);
while ((ast_wait_for_input(class->srcfd, 100) > 0) &&
(bytes = read(class->srcfd, buff, 8192)) && time(NULL) < stime) {
tbytes = tbytes + bytes;
}
- ast_debug(1, "mpg123 pid %d and child died after %d bytes read\n", pid, tbytes);
+ ast_debug(1, "mpg123 pid %d and child died after %d bytes read\n",
+ class->pid, tbytes);
+ class->pid = 0;
close(class->srcfd);
class->srcfd = -1;
}
@@ -1765,6 +1777,22 @@ static int load_moh_classes(int reload)
ast_log(LOG_WARNING, "Unknown format '%s' -- defaulting to SLIN\n", var->value);
class->format = ao2_bump(ast_format_slin);
}
+ } else if (!strcasecmp(var->name, "kill_escalation_delay")) {
+ if (sscanf(var->value, "%zu", &class->kill_delay) == 1) {
+ class->kill_delay *= 1000;
+ } else {
+ ast_log(LOG_WARNING, "kill_escalation_delay '%s' is invalid. Setting to 100ms\n", var->value);
+ class->kill_delay = 100000;
+ }
+ } else if (!strcasecmp(var->name, "kill_method")) {
+ if (!strcasecmp(var->value, "process")) {
+ class->kill_method = KILL_METHOD_PROCESS;
+ } else if (!strcasecmp(var->value, "process_group")){
+ class->kill_method = KILL_METHOD_PROCESS_GROUP;
+ } else {
+ ast_log(LOG_WARNING, "kill_method '%s' is invalid. Setting to 'process_group'\n", var->value);
+ class->kill_method = KILL_METHOD_PROCESS_GROUP;
+ }
}
}
@@ -1899,6 +1927,9 @@ static char *handle_cli_moh_show_classes(struct ast_cli_entry *e, int cmd, struc
ast_cli(a->fd, "\tDirectory: %s\n", S_OR(class->dir, "<none>"));
if (ast_test_flag(class, MOH_CUSTOM)) {
ast_cli(a->fd, "\tApplication: %s\n", S_OR(class->args, "<none>"));
+ ast_cli(a->fd, "\tKill Escalation Delay: %zu ms\n", class->kill_delay / 1000);
+ ast_cli(a->fd, "\tKill Method: %s\n",
+ class->kill_method == KILL_METHOD_PROCESS ? "process" : "process_group");
}
if (strcasecmp(class->mode, "files")) {
ast_cli(a->fd, "\tFormat: %s\n", ast_format_get_name(class->format));
diff --git a/res/res_pjsip.c b/res/res_pjsip.c
index 6f1c19e08..2db0668c3 100644
--- a/res/res_pjsip.c
+++ b/res/res_pjsip.c
@@ -193,11 +193,18 @@
<description>
<para>Method used when updating connected line information.</para>
<enumlist>
- <enum name="invite" />
+ <enum name="invite">
+ <para>When set to <literal>invite</literal>, check the remote's Allow header and
+ if UPDATE is allowed, send UPDATE instead of INVITE to avoid SDP
+ renegotiation. If UPDATE is not Allowed, send INVITE.</para>
+ </enum>
<enum name="reinvite">
<para>Alias for the <literal>invite</literal> value.</para>
</enum>
- <enum name="update" />
+ <enum name="update">
+ <para>If set to <literal>update</literal>, send UPDATE regardless of what the remote
+ Allows. </para>
+ </enum>
</enumlist>
</description>
</configOption>
@@ -229,6 +236,9 @@
<enum name="auto">
<para>DTMF is sent as RFC 4733 if the other side supports it or as INBAND if not.</para>
</enum>
+ <enum name="auto_info">
+ <para>DTMF is sent as RFC 4733 if the other side supports it or as SIP INFO if not.</para>
+ </enum>
</enumlist>
</description>
</configOption>
@@ -357,9 +367,12 @@
<configOption name="rewrite_contact">
<synopsis>Allow Contact header to be rewritten with the source IP address-port</synopsis>
<description><para>
- On inbound SIP messages from this endpoint, the Contact header or an appropriate Record-Route
- header will be changed to have the source IP address and port. This option does not affect
- outbound messages sent to this endpoint.
+ On inbound SIP messages from this endpoint, the Contact header or an
+ appropriate Record-Route header will be changed to have the source IP
+ address and port. This option does not affect outbound messages sent to
+ this endpoint. This option helps servers communicate with endpoints
+ that are behind NATs. This option also helps reuse reliable transport
+ connections such as TCP and TLS.
</para></description>
</configOption>
<configOption name="rtp_ipv6" default="no">
@@ -965,6 +978,13 @@
will not send the progress details, but immediately will send "200 OK".
</para></description>
</configOption>
+ <configOption name="notify_early_inuse_ringing" default="no">
+ <synopsis>Whether to notifies dialog-info 'early' on InUse&amp;Ringing state</synopsis>
+ <description><para>
+ Control whether dialog-info subscriptions get 'early' state
+ on Ringing when already INUSE.
+ </para></description>
+ </configOption>
</configObject>
<configObject name="auth">
<synopsis>Authentication type</synopsis>
@@ -1307,6 +1327,13 @@
in incoming SIP REGISTER requests and is not intended to be configured manually.
</para></description>
</configOption>
+ <configOption name="prune_on_boot">
+ <synopsis>A contact that cannot survive a restart/boot.</synopsis>
+ <description><para>
+ The option is set if the incoming SIP REGISTER contact is rewritten
+ on a reliable transport and is not intended to be configured manually.
+ </para></description>
+ </configOption>
</configObject>
<configObject name="aor">
<synopsis>The configuration for a location of an endpoint</synopsis>
@@ -3058,6 +3085,14 @@ pjsip_dialog *ast_sip_create_dialog_uac(const struct ast_sip_endpoint *endpoint,
/* Update the dialog with the new local URI, we do it afterwards so we can use the dialog pool for construction */
pj_strdup_with_null(dlg->pool, &dlg->local.info_str, &local_uri);
dlg->local.info->uri = pjsip_parse_uri(dlg->pool, dlg->local.info_str.ptr, dlg->local.info_str.slen, 0);
+ if (!dlg->local.info->uri) {
+ ast_log(LOG_ERROR,
+ "Could not parse URI '%s' for endpoint '%s'\n",
+ dlg->local.info_str.ptr, ast_sorcery_object_get_id(endpoint));
+ dlg->sess_count--;
+ pjsip_dlg_terminate(dlg);
+ return NULL;
+ }
dlg->local.contact = pjsip_parse_hdr(dlg->pool, &HCONTACT, local_uri.ptr, local_uri.slen, NULL);
@@ -4372,6 +4407,56 @@ const char *ast_sip_get_host_ip_string(int af)
return NULL;
}
+int ast_sip_dtmf_to_str(const enum ast_sip_dtmf_mode dtmf,
+ char *buf, size_t buf_len)
+{
+ switch (dtmf) {
+ case AST_SIP_DTMF_NONE:
+ ast_copy_string(buf, "none", buf_len);
+ break;
+ case AST_SIP_DTMF_RFC_4733:
+ ast_copy_string(buf, "rfc4733", buf_len);
+ break;
+ case AST_SIP_DTMF_INBAND:
+ ast_copy_string(buf, "inband", buf_len);
+ break;
+ case AST_SIP_DTMF_INFO:
+ ast_copy_string(buf, "info", buf_len);
+ break;
+ case AST_SIP_DTMF_AUTO:
+ ast_copy_string(buf, "auto", buf_len);
+ break;
+ case AST_SIP_DTMF_AUTO_INFO:
+ ast_copy_string(buf, "auto_info", buf_len);
+ break;
+ default:
+ buf[0] = '\0';
+ return -1;
+ }
+ return 0;
+}
+
+int ast_sip_str_to_dtmf(const char * dtmf_mode)
+{
+ int result = -1;
+
+ if (!strcasecmp(dtmf_mode, "info")) {
+ result = AST_SIP_DTMF_INFO;
+ } else if (!strcasecmp(dtmf_mode, "rfc4733")) {
+ result = AST_SIP_DTMF_RFC_4733;
+ } else if (!strcasecmp(dtmf_mode, "inband")) {
+ result = AST_SIP_DTMF_INBAND;
+ } else if (!strcasecmp(dtmf_mode, "none")) {
+ result = AST_SIP_DTMF_NONE;
+ } else if (!strcasecmp(dtmf_mode, "auto")) {
+ result = AST_SIP_DTMF_AUTO;
+ } else if (!strcasecmp(dtmf_mode, "auto_info")) {
+ result = AST_SIP_DTMF_AUTO_INFO;
+ }
+
+ return result;
+}
+
/*!
* \brief Set name and number information on an identity header.
*
@@ -4502,6 +4587,7 @@ static int unload_pjsip(void *data)
ast_sip_destroy_system();
ast_sip_destroy_global_headers();
internal_sip_unregister_service(&supplement_module);
+ ast_sip_destroy_transport_events();
}
if (monitor_thread) {
@@ -4580,7 +4666,6 @@ static int load_pjsip(void)
return AST_MODULE_LOAD_SUCCESS;
error:
- unload_pjsip(NULL);
return AST_MODULE_LOAD_DECLINE;
}
@@ -4646,6 +4731,11 @@ static int load_module(void)
goto error;
}
+ if (ast_sip_initialize_transport_events()) {
+ ast_log(LOG_ERROR, "Failed to initialize SIP transport monitor. Aborting load\n");
+ goto error;
+ }
+
ast_sip_initialize_dns();
ast_sip_initialize_global_headers();
diff --git a/res/res_pjsip/config_transport.c b/res/res_pjsip/config_transport.c
index 62bc9d67d..0c804b82a 100644
--- a/res/res_pjsip/config_transport.c
+++ b/res/res_pjsip/config_transport.c
@@ -248,8 +248,11 @@ static int destroy_sip_transport_state(void *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->external_signaling_address_refresher) {
+ ast_dnsmgr_release(transport_state->external_signaling_address_refresher);
+ }
+ if (transport_state->external_media_address_refresher) {
+ ast_dnsmgr_release(transport_state->external_media_address_refresher);
}
if (transport_state->transport) {
pjsip_transport_shutdown(transport_state->transport);
@@ -399,8 +402,8 @@ static void copy_state_to_transport(struct ast_sip_transport *transport)
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));
+ transport->external_address_refresher = transport->state->external_signaling_address_refresher;
+ memcpy(&transport->external_address, &transport->state->external_signaling_address, sizeof(transport->external_signaling_address));
}
static int has_state_changed(struct ast_sip_transport_state *a, struct ast_sip_transport_state *b)
@@ -421,7 +424,11 @@ static int has_state_changed(struct ast_sip_transport_state *a, struct ast_sip_t
return -1;
}
- if (ast_sockaddr_cmp(&a->external_address, &b->external_address)) {
+ if (ast_sockaddr_cmp(&a->external_signaling_address, &b->external_signaling_address)) {
+ return -1;
+ }
+
+ if (ast_sockaddr_cmp(&a->external_media_address, &b->external_media_address)) {
return -1;
}
@@ -515,24 +522,41 @@ static int transport_apply(const struct ast_sorcery *sorcery, void *obj)
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 */
+ /* Now that we know what address family we can set up a dnsmgr refresh for the external addresses if present */
if (!ast_strlen_zero(transport->external_signaling_address)) {
if (temp_state->state->host.addr.sa_family == pj_AF_INET()) {
- temp_state->state->external_address.ss.ss_family = AF_INET;
+ temp_state->state->external_signaling_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;
+ temp_state->state->external_signaling_address.ss.ss_family = AF_INET6;
} else {
ast_log(LOG_ERROR, "Unknown address family for transport '%s', could not get external signaling address\n",
transport_id);
return -1;
}
- if (ast_dnsmgr_lookup(transport->external_signaling_address, &temp_state->state->external_address, &temp_state->state->external_address_refresher, NULL) < 0) {
+ if (ast_dnsmgr_lookup(transport->external_signaling_address, &temp_state->state->external_signaling_address, &temp_state->state->external_signaling_address_refresher, NULL) < 0) {
ast_log(LOG_ERROR, "Could not create dnsmgr for external signaling address on '%s'\n", transport_id);
return -1;
}
}
+ if (!ast_strlen_zero(transport->external_media_address)) {
+ if (temp_state->state->host.addr.sa_family == pj_AF_INET()) {
+ temp_state->state->external_media_address.ss.ss_family = AF_INET;
+ } else if (temp_state->state->host.addr.sa_family == pj_AF_INET6()) {
+ temp_state->state->external_media_address.ss.ss_family = AF_INET6;
+ } else {
+ ast_log(LOG_ERROR, "Unknown address family for transport '%s', could not get external media address\n",
+ transport_id);
+ return -1;
+ }
+
+ if (ast_dnsmgr_lookup(transport->external_media_address, &temp_state->state->external_media_address, &temp_state->state->external_media_address_refresher, NULL) < 0) {
+ ast_log(LOG_ERROR, "Could not create dnsmgr for external media address on '%s'\n", transport_id);
+ return -1;
+ }
+ }
+
if (transport->type == AST_TRANSPORT_UDP) {
for (i = 0; i < BIND_TRIES && res != PJ_SUCCESS; i++) {
@@ -1103,7 +1127,9 @@ static int transport_localnet_handler(const struct aco_option *opt, struct ast_v
return 0;
}
- if (!(state->localnet = ast_append_ha("d", var->value, state->localnet, &error))) {
+ /* We use only the ast_apply_ha() which defaults to ALLOW
+ * ("permit"), so we add DENY rules. */
+ if (!(state->localnet = ast_append_ha("deny", var->value, state->localnet, &error))) {
return -1;
}
diff --git a/res/res_pjsip/include/res_pjsip_private.h b/res/res_pjsip/include/res_pjsip_private.h
index 11ad12c45..5766325b2 100644
--- a/res/res_pjsip/include/res_pjsip_private.h
+++ b/res/res_pjsip/include/res_pjsip_private.h
@@ -135,6 +135,29 @@ void ast_sip_destroy_distributor(void);
/*!
* \internal
+ * \brief Initialize the transport events notify module
+ * \since 13.18.0
+ *
+ * The transport events notify module is responsible for monitoring
+ * when transports die and calling any registered callbacks when that
+ * happens. It also manages any PJPROJECT transport state callbacks
+ * registered to it so the callbacks be more dynamic allowing module
+ * loading/unloading.
+ *
+ * \retval -1 Failure
+ * \retval 0 Success
+ */
+int ast_sip_initialize_transport_events(void);
+
+/*!
+ * \internal
+ * \brief Destruct the transport events notify module.
+ * \since 13.18.0
+ */
+void ast_sip_destroy_transport_events(void);
+
+/*!
+ * \internal
* \brief Initialize global type on a sorcery instance
*
* \retval -1 failure
diff --git a/res/res_pjsip/location.c b/res/res_pjsip/location.c
index 05e19f53a..ddde5c47e 100644
--- a/res/res_pjsip/location.c
+++ b/res/res_pjsip/location.c
@@ -356,13 +356,12 @@ struct ast_sip_contact *ast_sip_location_retrieve_contact(const char *contact_na
return ast_sorcery_retrieve_by_id(ast_sip_get_sorcery(), "contact", contact_name);
}
-int ast_sip_location_add_contact_nolock(struct ast_sip_aor *aor, const char *uri,
- struct timeval expiration_time, const char *path_info, const char *user_agent,
- const char *via_addr, int via_port, const char *call_id,
- struct ast_sip_endpoint *endpoint)
+struct ast_sip_contact *ast_sip_location_create_contact(struct ast_sip_aor *aor,
+ const char *uri, struct timeval expiration_time, const char *path_info,
+ const char *user_agent, const char *via_addr, int via_port, const char *call_id,
+ int prune_on_boot, struct ast_sip_endpoint *endpoint)
{
struct ast_sip_contact *contact;
- int res;
char name[MAX_OBJECT_FIELD * 2 + 3];
char hash[33];
@@ -371,7 +370,7 @@ int ast_sip_location_add_contact_nolock(struct ast_sip_aor *aor, const char *uri
contact = ast_sorcery_alloc(ast_sip_get_sorcery(), "contact", name);
if (!contact) {
- return -1;
+ return NULL;
}
ast_string_field_set(contact, uri, uri);
@@ -405,14 +404,30 @@ int ast_sip_location_add_contact_nolock(struct ast_sip_aor *aor, const char *uri
}
contact->endpoint = ao2_bump(endpoint);
-
if (endpoint) {
ast_string_field_set(contact, endpoint_name, ast_sorcery_object_get_id(endpoint));
}
- res = ast_sorcery_create(ast_sip_get_sorcery(), contact);
- ao2_ref(contact, -1);
- return res;
+ contact->prune_on_boot = prune_on_boot;
+
+ if (ast_sorcery_create(ast_sip_get_sorcery(), contact)) {
+ ao2_ref(contact, -1);
+ return NULL;
+ }
+ return contact;
+}
+
+int ast_sip_location_add_contact_nolock(struct ast_sip_aor *aor, const char *uri,
+ struct timeval expiration_time, const char *path_info, const char *user_agent,
+ const char *via_addr, int via_port, const char *call_id,
+ struct ast_sip_endpoint *endpoint)
+{
+ struct ast_sip_contact *contact;
+
+ contact = ast_sip_location_create_contact(aor, uri, expiration_time, path_info,
+ user_agent, via_addr, via_port, call_id, 0, endpoint);
+ ao2_cleanup(contact);
+ return contact ? 0 : -1;
}
int ast_sip_location_add_contact(struct ast_sip_aor *aor, const char *uri,
@@ -448,6 +463,32 @@ int ast_sip_location_delete_contact(struct ast_sip_contact *contact)
return ast_sorcery_delete(ast_sip_get_sorcery(), contact);
}
+static int prune_boot_contacts_cb(void *obj, void *arg, int flags)
+{
+ struct ast_sip_contact *contact = obj;
+
+ if (contact->prune_on_boot
+ && !strcmp(contact->reg_server, ast_config_AST_SYSTEM_NAME ?: "")) {
+ ast_verb(3, "Removed contact '%s' from AOR '%s' due to system boot\n",
+ contact->uri, contact->aor);
+ ast_sip_location_delete_contact(contact);
+ }
+
+ return 0;
+}
+
+void ast_sip_location_prune_boot_contacts(void)
+{
+ struct ao2_container *contacts;
+
+ contacts = ast_sorcery_retrieve_by_fields(ast_sip_get_sorcery(), "contact",
+ AST_RETRIEVE_FLAG_MULTIPLE | AST_RETRIEVE_FLAG_ALL, NULL);
+ if (contacts) {
+ ao2_callback(contacts, 0, prune_boot_contacts_cb, NULL);
+ ao2_ref(contacts, -1);
+ }
+}
+
/*! \brief Custom handler for translating from a string timeval to actual structure */
static int expiration_str2struct(const struct aco_option *opt, struct ast_variable *var, void *obj)
{
@@ -1228,6 +1269,7 @@ int ast_sip_initialize_sorcery_location(void)
ast_sorcery_object_field_register(sorcery, "contact", "via_addr", "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct ast_sip_contact, via_addr));
ast_sorcery_object_field_register(sorcery, "contact", "via_port", "0", OPT_UINT_T, 0, FLDSET(struct ast_sip_contact, via_port));
ast_sorcery_object_field_register(sorcery, "contact", "call_id", "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct ast_sip_contact, call_id));
+ ast_sorcery_object_field_register(sorcery, "contact", "prune_on_boot", "no", OPT_YESNO_T, 1, FLDSET(struct ast_sip_contact, prune_on_boot));
ast_sorcery_object_field_register(sorcery, "aor", "type", "", OPT_NOOP_T, 0, 0);
ast_sorcery_object_field_register(sorcery, "aor", "minimum_expiration", "60", OPT_UINT_T, 0, FLDSET(struct ast_sip_aor, minimum_expiration));
diff --git a/res/res_pjsip/pjsip_configuration.c b/res/res_pjsip/pjsip_configuration.c
index 77e31abe5..27dadb178 100644
--- a/res/res_pjsip/pjsip_configuration.c
+++ b/res/res_pjsip/pjsip_configuration.c
@@ -368,42 +368,29 @@ static int contact_acl_to_str(const void *obj, const intptr_t *args, char **buf)
static int dtmf_handler(const struct aco_option *opt, struct ast_variable *var, void *obj)
{
struct ast_sip_endpoint *endpoint = obj;
+ enum ast_sip_dtmf_mode dtmf = ast_sip_str_to_dtmf(var->value);
- if (!strcasecmp(var->value, "rfc4733")) {
- endpoint->dtmf = AST_SIP_DTMF_RFC_4733;
- } else if (!strcasecmp(var->value, "inband")) {
- endpoint->dtmf = AST_SIP_DTMF_INBAND;
- } else if (!strcasecmp(var->value, "info")) {
- endpoint->dtmf = AST_SIP_DTMF_INFO;
- } else if (!strcasecmp(var->value, "auto")) {
- endpoint->dtmf = AST_SIP_DTMF_AUTO;
- } else if (!strcasecmp(var->value, "none")) {
- endpoint->dtmf = AST_SIP_DTMF_NONE;
- } else {
+ if (dtmf == -1) {
return -1;
}
+ endpoint->dtmf = dtmf;
return 0;
}
static int dtmf_to_str(const void *obj, const intptr_t *args, char **buf)
{
const struct ast_sip_endpoint *endpoint = obj;
+ char dtmf_str[20];
+ int result = -1;
- switch (endpoint->dtmf) {
- case AST_SIP_DTMF_RFC_4733 :
- *buf = "rfc4733"; break;
- case AST_SIP_DTMF_INBAND :
- *buf = "inband"; break;
- case AST_SIP_DTMF_INFO :
- *buf = "info"; break;
- case AST_SIP_DTMF_AUTO :
- *buf = "auto"; break;
- default:
- *buf = "none";
- }
+ result = ast_sip_dtmf_to_str(endpoint->dtmf, dtmf_str, sizeof(dtmf_str));
- *buf = ast_strdup(*buf);
+ if (result == 0) {
+ *buf = ast_strdup(dtmf_str);
+ } else {
+ *buf = ast_strdup("none");
+ }
return 0;
}
@@ -1145,6 +1132,37 @@ static int tos_video_to_str(const void *obj, const intptr_t *args, char **buf)
return 0;
}
+static int from_user_handler(const struct aco_option *opt,
+ struct ast_variable *var, void *obj)
+{
+ struct ast_sip_endpoint *endpoint = obj;
+ /* Valid non-alphanumeric characters for URI */
+ char *valid_uri_marks = "-_.!~*`()";
+ const char *val;
+
+ for (val = var->value; *val; val++) {
+ if (!strchr(valid_uri_marks, *val) && !isdigit(*val) && !isalpha(*val)) {
+ ast_log(LOG_ERROR, "Error configuring endpoint '%s' - '%s' field "
+ "contains invalid character '%c'\n",
+ ast_sorcery_object_get_id(endpoint), var->name, *val);
+ return -1;
+ }
+ }
+
+ ast_string_field_set(endpoint, fromuser, var->value);
+
+ return 0;
+}
+
+static int from_user_to_str(const void *obj, const intptr_t *args, char **buf)
+{
+ const struct ast_sip_endpoint *endpoint = obj;
+
+ *buf = ast_strdup(endpoint->fromuser);
+
+ return 0;
+}
+
static int set_var_handler(const struct aco_option *opt,
struct ast_variable *var, void *obj)
{
@@ -1909,7 +1927,7 @@ int ast_res_pjsip_initialize_configuration(const struct ast_module_info *ast_mod
ast_sorcery_object_field_register(sip_sorcery, "endpoint", "cos_video", "0", OPT_UINT_T, 0, FLDSET(struct ast_sip_endpoint, media.cos_video));
ast_sorcery_object_field_register(sip_sorcery, "endpoint", "allow_subscribe", "yes", OPT_BOOL_T, 1, FLDSET(struct ast_sip_endpoint, subscription.allow));
ast_sorcery_object_field_register(sip_sorcery, "endpoint", "sub_min_expiry", "0", OPT_UINT_T, 0, FLDSET(struct ast_sip_endpoint, subscription.minexpiry));
- ast_sorcery_object_field_register(sip_sorcery, "endpoint", "from_user", "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct ast_sip_endpoint, fromuser));
+ ast_sorcery_object_field_register_custom(sip_sorcery, "endpoint", "from_user", "", from_user_handler, from_user_to_str, NULL, 0, 0);
ast_sorcery_object_field_register(sip_sorcery, "endpoint", "from_domain", "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct ast_sip_endpoint, fromdomain));
ast_sorcery_object_field_register(sip_sorcery, "endpoint", "mwi_from_user", "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct ast_sip_endpoint, subscription.mwi.fromuser));
ast_sorcery_object_field_register(sip_sorcery, "endpoint", "rtp_engine", "asterisk", OPT_STRINGFIELD_T, 0, STRFLDSET(struct ast_sip_endpoint, media.rtp.engine));
@@ -1941,6 +1959,7 @@ int ast_res_pjsip_initialize_configuration(const struct ast_module_info *ast_mod
ast_sorcery_object_field_register(sip_sorcery, "endpoint", "rtcp_mux", "no", OPT_BOOL_T, 1, FLDSET(struct ast_sip_endpoint, rtcp_mux));
ast_sorcery_object_field_register(sip_sorcery, "endpoint", "allow_overlap", "yes", OPT_BOOL_T, 1, FLDSET(struct ast_sip_endpoint, allow_overlap));
ast_sorcery_object_field_register(sip_sorcery, "endpoint", "refer_blind_progress", "yes", OPT_BOOL_T, 1, FLDSET(struct ast_sip_endpoint, refer_blind_progress));
+ ast_sorcery_object_field_register(sip_sorcery, "endpoint", "notify_early_inuse_ringing", "no", OPT_BOOL_T, 1, FLDSET(struct ast_sip_endpoint, notify_early_inuse_ringing));
if (ast_sip_initialize_sorcery_transport()) {
ast_log(LOG_ERROR, "Failed to register SIP transport support with sorcery\n");
@@ -2003,6 +2022,8 @@ int ast_res_pjsip_initialize_configuration(const struct ast_module_info *ast_mod
load_all_endpoints();
+ ast_sip_location_prune_boot_contacts();
+
return 0;
}
diff --git a/res/res_pjsip/pjsip_distributor.c b/res/res_pjsip/pjsip_distributor.c
index dadde2577..3f245eea0 100644
--- a/res/res_pjsip/pjsip_distributor.c
+++ b/res/res_pjsip/pjsip_distributor.c
@@ -140,62 +140,189 @@ static struct ast_taskprocessor *find_request_serializer(pjsip_rx_data *rdata)
/*! Dialog-specific information the distributor uses */
struct distributor_dialog_data {
+ /*! dialog_associations ao2 container key */
+ pjsip_dialog *dlg;
/*! Serializer to distribute tasks to for this dialog */
struct ast_taskprocessor *serializer;
/*! Endpoint associated with this dialog */
struct ast_sip_endpoint *endpoint;
};
+#define DIALOG_ASSOCIATIONS_BUCKETS 251
+
+static struct ao2_container *dialog_associations;
+
/*!
* \internal
+ * \brief Compute a hash value on an arbitrary buffer.
+ * \since 13.17.0
*
- * \note Call this with the dialog locked
+ * \param[in] pos The buffer to add to the hash
+ * \param[in] len The buffer length to add to the hash
+ * \param[in] hash The hash value to add to
+ *
+ * \details
+ * This version of the function is for when you need to compute a
+ * hash of more than one buffer.
+ *
+ * This famous hash algorithm was written by Dan Bernstein and is
+ * commonly used.
+ *
+ * \sa http://www.cse.yorku.ca/~oz/hash.html
*/
-static struct distributor_dialog_data *distributor_dialog_data_alloc(pjsip_dialog *dlg)
+static int buf_hash_add(const char *pos, size_t len, int hash)
{
- struct distributor_dialog_data *dist;
+ while (len--) {
+ hash = hash * 33 ^ *pos++;
+ }
+
+ return hash;
+}
+
+/*!
+ * \internal
+ * \brief Compute a hash value on an arbitrary buffer.
+ * \since 13.17.0
+ *
+ * \param[in] pos The buffer to add to the hash
+ * \param[in] len The buffer length to add to the hash
+ *
+ * \details
+ * This version of the function is for when you need to compute a
+ * hash of more than one buffer.
+ *
+ * This famous hash algorithm was written by Dan Bernstein and is
+ * commonly used.
+ *
+ * \sa http://www.cse.yorku.ca/~oz/hash.html
+ */
+static int buf_hash(const char *pos, size_t len)
+{
+ return buf_hash_add(pos, len, 5381);
+}
- dist = PJ_POOL_ZALLOC_T(dlg->pool, struct distributor_dialog_data);
- pjsip_dlg_set_mod_data(dlg, distributor_mod.id, dist);
+static int dialog_associations_hash(const void *obj, int flags)
+{
+ const struct distributor_dialog_data *object;
+ union {
+ const pjsip_dialog *dlg;
+ const char buf[sizeof(pjsip_dialog *)];
+ } key;
- return dist;
+ switch (flags & OBJ_SEARCH_MASK) {
+ case OBJ_SEARCH_KEY:
+ key.dlg = obj;
+ break;
+ case OBJ_SEARCH_OBJECT:
+ object = obj;
+ key.dlg = object->dlg;
+ break;
+ default:
+ /* Hash can only work on something with a full key. */
+ ast_assert(0);
+ return 0;
+ }
+ return ast_str_hash_restrict(buf_hash(key.buf, sizeof(key.buf)));
+}
+
+static int dialog_associations_cmp(void *obj, void *arg, int flags)
+{
+ const struct distributor_dialog_data *object_left = obj;
+ const struct distributor_dialog_data *object_right = arg;
+ const pjsip_dialog *right_key = arg;
+ int cmp = 0;
+
+ switch (flags & OBJ_SEARCH_MASK) {
+ case OBJ_SEARCH_OBJECT:
+ right_key = object_right->dlg;
+ /* Fall through */
+ case OBJ_SEARCH_KEY:
+ if (object_left->dlg == right_key) {
+ cmp = CMP_MATCH;
+ }
+ break;
+ case OBJ_SEARCH_PARTIAL_KEY:
+ /* There is no such thing for this container. */
+ ast_assert(0);
+ break;
+ default:
+ cmp = 0;
+ break;
+ }
+ return cmp;
}
void ast_sip_dialog_set_serializer(pjsip_dialog *dlg, struct ast_taskprocessor *serializer)
{
struct distributor_dialog_data *dist;
- SCOPED_LOCK(lock, dlg, pjsip_dlg_inc_lock, pjsip_dlg_dec_lock);
- dist = pjsip_dlg_get_mod_data(dlg, distributor_mod.id);
+ ao2_wrlock(dialog_associations);
+ dist = ao2_find(dialog_associations, dlg, OBJ_SEARCH_KEY | OBJ_NOLOCK);
if (!dist) {
- dist = distributor_dialog_data_alloc(dlg);
+ if (serializer) {
+ dist = ao2_alloc(sizeof(*dist), NULL);
+ if (dist) {
+ dist->dlg = dlg;
+ dist->serializer = serializer;
+ ao2_link_flags(dialog_associations, dist, OBJ_NOLOCK);
+ ao2_ref(dist, -1);
+ }
+ }
+ } else {
+ ao2_lock(dist);
+ dist->serializer = serializer;
+ if (!dist->serializer && !dist->endpoint) {
+ ao2_unlink_flags(dialog_associations, dist, OBJ_NOLOCK);
+ }
+ ao2_unlock(dist);
+ ao2_ref(dist, -1);
}
- dist->serializer = serializer;
+ ao2_unlock(dialog_associations);
}
void ast_sip_dialog_set_endpoint(pjsip_dialog *dlg, struct ast_sip_endpoint *endpoint)
{
struct distributor_dialog_data *dist;
- SCOPED_LOCK(lock, dlg, pjsip_dlg_inc_lock, pjsip_dlg_dec_lock);
- dist = pjsip_dlg_get_mod_data(dlg, distributor_mod.id);
+ ao2_wrlock(dialog_associations);
+ dist = ao2_find(dialog_associations, dlg, OBJ_SEARCH_KEY | OBJ_NOLOCK);
if (!dist) {
- dist = distributor_dialog_data_alloc(dlg);
+ if (endpoint) {
+ dist = ao2_alloc(sizeof(*dist), NULL);
+ if (dist) {
+ dist->dlg = dlg;
+ dist->endpoint = endpoint;
+ ao2_link_flags(dialog_associations, dist, OBJ_NOLOCK);
+ ao2_ref(dist, -1);
+ }
+ }
+ } else {
+ ao2_lock(dist);
+ dist->endpoint = endpoint;
+ if (!dist->serializer && !dist->endpoint) {
+ ao2_unlink_flags(dialog_associations, dist, OBJ_NOLOCK);
+ }
+ ao2_unlock(dist);
+ ao2_ref(dist, -1);
}
- dist->endpoint = endpoint;
+ ao2_unlock(dialog_associations);
}
struct ast_sip_endpoint *ast_sip_dialog_get_endpoint(pjsip_dialog *dlg)
{
struct distributor_dialog_data *dist;
- SCOPED_LOCK(lock, dlg, pjsip_dlg_inc_lock, pjsip_dlg_dec_lock);
+ struct ast_sip_endpoint *endpoint;
- dist = pjsip_dlg_get_mod_data(dlg, distributor_mod.id);
- if (!dist || !dist->endpoint) {
- return NULL;
+ dist = ao2_find(dialog_associations, dlg, OBJ_SEARCH_KEY);
+ if (dist) {
+ ao2_lock(dist);
+ endpoint = ao2_bump(dist->endpoint);
+ ao2_unlock(dist);
+ ao2_ref(dist, -1);
+ } else {
+ endpoint = NULL;
}
- ao2_ref(dist->endpoint, +1);
- return dist->endpoint;
+ return endpoint;
}
static pjsip_dialog *find_dialog(pjsip_rx_data *rdata)
@@ -227,7 +354,7 @@ static pjsip_dialog *find_dialog(pjsip_rx_data *rdata)
pjsip_method_cmp(&rdata->msg_info.msg->line.req.method, &pjsip_cancel_method) ||
rdata->msg_info.to->tag.slen != 0) {
dlg = pjsip_ua_find_dialog(&rdata->msg_info.cid->id, local_tag,
- remote_tag, PJ_TRUE);
+ remote_tag, PJ_FALSE);
if (dlg) {
return dlg;
}
@@ -265,11 +392,6 @@ static pjsip_dialog *find_dialog(pjsip_rx_data *rdata)
pj_mutex_unlock(tsx->mutex);
#endif
- if (!dlg) {
- return NULL;
- }
-
- pjsip_dlg_inc_lock(dlg);
return dlg;
}
@@ -292,16 +414,7 @@ static pjsip_dialog *find_dialog(pjsip_rx_data *rdata)
*/
static int pjstr_hash_add(pj_str_t *str, int hash)
{
- size_t len;
- const char *pos;
-
- len = pj_strlen(str);
- pos = pj_strbuf(str);
- while (len--) {
- hash = hash * 33 ^ *pos++;
- }
-
- return hash;
+ return buf_hash_add(pj_strbuf(str), pj_strlen(str), hash);
}
/*!
@@ -340,7 +453,7 @@ struct ast_taskprocessor *ast_sip_get_distributor_serializer(pjsip_rx_data *rdat
/* Compute the hash from the SIP message call-id and remote-tag */
hash = pjstr_hash(&rdata->msg_info.cid->id);
hash = pjstr_hash_add(remote_tag, hash);
- hash = abs(hash);
+ hash = ast_str_hash_restrict(hash);
serializer = ao2_bump(distributor_pool[hash % ARRAY_LEN(distributor_pool)]);
if (serializer) {
@@ -375,17 +488,18 @@ static pj_bool_t distributor(pjsip_rx_data *rdata)
dlg = find_dialog(rdata);
if (dlg) {
- ast_debug(3, "Searching for serializer on dialog %s for %s\n",
+ ast_debug(3, "Searching for serializer associated with dialog %s for %s\n",
dlg->obj_name, pjsip_rx_data_get_info(rdata));
- dist = pjsip_dlg_get_mod_data(dlg, distributor_mod.id);
+ dist = ao2_find(dialog_associations, dlg, OBJ_SEARCH_KEY);
if (dist) {
+ ao2_lock(dist);
serializer = ao2_bump(dist->serializer);
+ ao2_unlock(dist);
if (serializer) {
- ast_debug(3, "Found serializer %s on dialog %s\n",
+ ast_debug(3, "Found serializer %s associated with dialog %s\n",
ast_taskprocessor_name(serializer), dlg->obj_name);
}
}
- pjsip_dlg_dec_lock(dlg);
}
if (serializer) {
@@ -407,6 +521,7 @@ static pj_bool_t distributor(pjsip_rx_data *rdata)
/* 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);
+ ao2_cleanup(dist);
return PJ_TRUE;
} else {
if (ast_taskprocessor_alert_get()) {
@@ -421,6 +536,7 @@ static pj_bool_t distributor(pjsip_rx_data *rdata)
*/
ast_debug(3, "Taskprocessor overload alert: Ignoring '%s'.\n",
pjsip_rx_data_get_info(rdata));
+ ao2_cleanup(dist);
return PJ_TRUE;
}
@@ -428,10 +544,17 @@ static pj_bool_t distributor(pjsip_rx_data *rdata)
serializer = ast_sip_get_distributor_serializer(rdata);
}
- pjsip_rx_data_clone(rdata, 0, &clone);
+ if (pjsip_rx_data_clone(rdata, 0, &clone) != PJ_SUCCESS) {
+ ast_taskprocessor_unreference(serializer);
+ ao2_cleanup(dist);
+ return PJ_TRUE;
+ }
if (dist) {
+ ao2_lock(dist);
clone->endpt_info.mod_data[endpoint_mod.id] = ao2_bump(dist->endpoint);
+ ao2_unlock(dist);
+ ao2_cleanup(dist);
}
if (ast_sip_push_task(serializer, distribute, clone)) {
@@ -827,7 +950,7 @@ static int suspects_compare(void *obj, void *arg, int flags)
/* Fall through */
case OBJ_SEARCH_KEY:
if (strcmp(object_left->src_name, right_key) == 0) {
- cmp = CMP_MATCH | CMP_STOP;
+ cmp = CMP_MATCH;
}
break;
case OBJ_SEARCH_PARTIAL_KEY:
@@ -842,15 +965,25 @@ static int suspects_compare(void *obj, void *arg, int flags)
return cmp;
}
-static int suspects_hash(const void *obj, int flags) {
- const struct unidentified_request *object_left = obj;
+static int suspects_hash(const void *obj, int flags)
+{
+ const struct unidentified_request *object;
+ const char *key;
- if (flags & OBJ_SEARCH_OBJECT) {
- return ast_str_hash(object_left->src_name);
- } else if (flags & OBJ_SEARCH_KEY) {
- return ast_str_hash(obj);
+ switch (flags & OBJ_SEARCH_MASK) {
+ case OBJ_SEARCH_KEY:
+ key = obj;
+ break;
+ case OBJ_SEARCH_OBJECT:
+ object = obj;
+ key = object->src_name;
+ break;
+ default:
+ /* Hash can only work on something with a full key. */
+ ast_assert(0);
+ return 0;
}
- return -1;
+ return ast_str_hash(key);
}
static struct ao2_container *cli_unid_get_container(const char *regex)
@@ -1068,6 +1201,14 @@ int ast_sip_initialize_distributor(void)
return -1;
}
+ dialog_associations = ao2_container_alloc_hash(AO2_ALLOC_OPT_LOCK_RWLOCK, 0,
+ DIALOG_ASSOCIATIONS_BUCKETS, dialog_associations_hash, NULL,
+ dialog_associations_cmp);
+ if (!dialog_associations) {
+ ast_sip_destroy_distributor();
+ return -1;
+ }
+
if (distributor_pool_setup()) {
ast_sip_destroy_distributor();
return -1;
@@ -1146,5 +1287,6 @@ void ast_sip_destroy_distributor(void)
distributor_pool_shutdown();
+ ao2_cleanup(dialog_associations);
ao2_cleanup(unidentified_requests);
}
diff --git a/res/res_pjsip/pjsip_message_ip_updater.c b/res/res_pjsip/pjsip_message_ip_updater.c
index 2d074640a..099ecaa66 100644
--- a/res/res_pjsip/pjsip_message_ip_updater.c
+++ b/res/res_pjsip/pjsip_message_ip_updater.c
@@ -153,7 +153,16 @@ static int multihomed_rewrite_sdp(struct pjmedia_sdp_session *sdp)
return 0;
}
-static void sanitize_tdata(pjsip_tx_data *tdata)
+#define is_sip_uri(uri) \
+ (PJSIP_URI_SCHEME_IS_SIP(uri) || PJSIP_URI_SCHEME_IS_SIPS(uri))
+
+#ifdef AST_DEVMODE
+#define FUNC_ATTRS __attribute__ ((noinline))
+#else
+#define FUNC_ATTRS
+#endif
+
+static void FUNC_ATTRS sanitize_tdata(pjsip_tx_data *tdata)
{
static const pj_str_t x_name = { AST_SIP_X_AST_TXP, AST_SIP_X_AST_TXP_LEN };
pjsip_param *x_transport;
@@ -161,29 +170,50 @@ static void sanitize_tdata(pjsip_tx_data *tdata)
pjsip_fromto_hdr *fromto;
pjsip_contact_hdr *contact;
pjsip_hdr *hdr;
+#ifdef AST_DEVMODE
+ char hdrbuf[512];
+ int hdrbuf_len;
+#endif
if (tdata->msg->type == PJSIP_REQUEST_MSG) {
- uri = pjsip_uri_get_uri(tdata->msg->line.req.uri);
- x_transport = pjsip_param_find(&uri->other_param, &x_name);
- if (x_transport) {
- pj_list_erase(x_transport);
+ if (is_sip_uri(tdata->msg->line.req.uri)) {
+ uri = pjsip_uri_get_uri(tdata->msg->line.req.uri);
+#ifdef AST_DEVMODE
+ hdrbuf_len = pjsip_uri_print(PJSIP_URI_IN_REQ_URI, uri, hdrbuf, 512);
+ ast_debug(2, "Sanitizing Request: %s\n", hdrbuf);
+#endif
+ while ((x_transport = pjsip_param_find(&uri->other_param, &x_name))) {
+ pj_list_erase(x_transport);
+ }
}
}
for (hdr = tdata->msg->hdr.next; hdr != &tdata->msg->hdr; hdr = hdr->next) {
if (hdr->type == PJSIP_H_TO || hdr->type == PJSIP_H_FROM) {
fromto = (pjsip_fromto_hdr *) hdr;
- uri = pjsip_uri_get_uri(fromto->uri);
- x_transport = pjsip_param_find(&uri->other_param, &x_name);
- if (x_transport) {
- pj_list_erase(x_transport);
+ if (is_sip_uri(fromto->uri)) {
+ uri = pjsip_uri_get_uri(fromto->uri);
+#ifdef AST_DEVMODE
+ hdrbuf_len = pjsip_uri_print(PJSIP_URI_IN_FROMTO_HDR, uri, hdrbuf, 512);
+ hdrbuf[hdrbuf_len] = '\0';
+ ast_debug(2, "Sanitizing From/To: %s\n", hdrbuf);
+#endif
+ while ((x_transport = pjsip_param_find(&uri->other_param, &x_name))) {
+ pj_list_erase(x_transport);
+ }
}
} else if (hdr->type == PJSIP_H_CONTACT) {
contact = (pjsip_contact_hdr *) hdr;
- uri = pjsip_uri_get_uri(contact->uri);
- x_transport = pjsip_param_find(&uri->other_param, &x_name);
- if (x_transport) {
- pj_list_erase(x_transport);
+ if (is_sip_uri(contact->uri)) {
+ uri = pjsip_uri_get_uri(contact->uri);
+#ifdef AST_DEVMODE
+ hdrbuf_len = pjsip_uri_print(PJSIP_URI_IN_CONTACT_HDR, uri, hdrbuf, 512);
+ hdrbuf[hdrbuf_len] = '\0';
+ ast_debug(2, "Sanitizing Contact: %s\n", hdrbuf);
+#endif
+ while ((x_transport = pjsip_param_find(&uri->other_param, &x_name))) {
+ pj_list_erase(x_transport);
+ }
}
}
}
diff --git a/res/res_pjsip/pjsip_transport_events.c b/res/res_pjsip/pjsip_transport_events.c
new file mode 100644
index 000000000..0f57303ba
--- /dev/null
+++ b/res/res_pjsip/pjsip_transport_events.c
@@ -0,0 +1,366 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2017, Digium Inc.
+ *
+ * Richard Mudgett <rmudgett@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 Manages the global transport event notification callbacks.
+ *
+ * \author Richard Mudgett <rmudgett@digium.com>
+ * See Also:
+ *
+ * \arg \ref AstCREDITS
+ */
+
+
+#include "asterisk.h"
+
+#include "asterisk/res_pjsip.h"
+#include "include/res_pjsip_private.h"
+#include "asterisk/linkedlists.h"
+#include "asterisk/vector.h"
+
+/* ------------------------------------------------------------------- */
+
+/*! \brief Number of buckets for monitored active transports */
+#define ACTIVE_TRANSPORTS_BUCKETS 127
+
+/*! Who to notify when transport shuts down. */
+struct transport_monitor_notifier {
+ /*! Who to call when transport shuts down. */
+ ast_transport_monitor_shutdown_cb cb;
+ /*! ao2 data object to pass to callback. */
+ void *data;
+};
+
+/*! \brief Structure for transport to be monitored */
+struct transport_monitor {
+ /*! \brief The underlying PJSIP transport */
+ pjsip_transport *transport;
+ /*! Who is interested in when this transport shuts down. */
+ AST_VECTOR(, struct transport_monitor_notifier) monitors;
+};
+
+/*! \brief Global container of active reliable transports */
+static AO2_GLOBAL_OBJ_STATIC(active_transports);
+
+/*! \brief Existing transport events callback that we need to invoke */
+static pjsip_tp_state_callback tpmgr_state_callback;
+
+/*! List of registered transport state callbacks. */
+static AST_RWLIST_HEAD(, ast_sip_tpmgr_state_callback) transport_state_list;
+
+
+/*! \brief Hashing function for struct transport_monitor */
+AO2_STRING_FIELD_HASH_FN(transport_monitor, transport->obj_name);
+
+/*! \brief Comparison function for struct transport_monitor */
+AO2_STRING_FIELD_CMP_FN(transport_monitor, transport->obj_name);
+
+static const char *transport_state2str(pjsip_transport_state state)
+{
+ const char *name;
+
+ switch (state) {
+ case PJSIP_TP_STATE_CONNECTED:
+ name = "CONNECTED";
+ break;
+ case PJSIP_TP_STATE_DISCONNECTED:
+ name = "DISCONNECTED";
+ break;
+ case PJSIP_TP_STATE_SHUTDOWN:
+ name = "SHUTDOWN";
+ break;
+ case PJSIP_TP_STATE_DESTROY:
+ name = "DESTROY";
+ break;
+ default:
+ /*
+ * We have to have a default case because the enum is
+ * defined by a third-party library.
+ */
+ ast_assert(0);
+ name = "<unknown>";
+ break;
+ }
+ return name;
+}
+
+static void transport_monitor_dtor(void *vdoomed)
+{
+ struct transport_monitor *monitored = vdoomed;
+ int idx;
+
+ for (idx = AST_VECTOR_SIZE(&monitored->monitors); idx--;) {
+ struct transport_monitor_notifier *notifier;
+
+ notifier = AST_VECTOR_GET_ADDR(&monitored->monitors, idx);
+ ao2_cleanup(notifier->data);
+ }
+ AST_VECTOR_FREE(&monitored->monitors);
+}
+
+/*! \brief Callback invoked when transport state changes occur */
+static void transport_state_callback(pjsip_transport *transport,
+ pjsip_transport_state state, const pjsip_transport_state_info *info)
+{
+ struct ao2_container *transports;
+
+ /* We only care about monitoring reliable transports */
+ if (PJSIP_TRANSPORT_IS_RELIABLE(transport)
+ && (transports = ao2_global_obj_ref(active_transports))) {
+ struct transport_monitor *monitored;
+
+ ast_debug(3, "Reliable transport '%s' state:%s\n",
+ transport->obj_name, transport_state2str(state));
+ switch (state) {
+ case PJSIP_TP_STATE_CONNECTED:
+ monitored = ao2_alloc_options(sizeof(*monitored),
+ transport_monitor_dtor, AO2_ALLOC_OPT_LOCK_NOLOCK);
+ if (!monitored) {
+ break;
+ }
+ monitored->transport = transport;
+ if (AST_VECTOR_INIT(&monitored->monitors, 2)) {
+ ao2_ref(monitored, -1);
+ break;
+ }
+
+ ao2_link(transports, monitored);
+ ao2_ref(monitored, -1);
+ break;
+ case PJSIP_TP_STATE_DISCONNECTED:
+ if (!transport->is_shutdown) {
+ pjsip_transport_shutdown(transport);
+ }
+ break;
+ case PJSIP_TP_STATE_SHUTDOWN:
+ /*
+ * Set shutdown flag early so we can force a new transport to be
+ * created if a monitor callback needs to reestablish a link.
+ * PJPROJECT sets the flag after this routine returns even though
+ * it has already called the transport's shutdown routine.
+ */
+ transport->is_shutdown = PJ_TRUE;
+
+ monitored = ao2_find(transports, transport->obj_name,
+ OBJ_SEARCH_KEY | OBJ_UNLINK);
+ if (monitored) {
+ int idx;
+
+ for (idx = AST_VECTOR_SIZE(&monitored->monitors); idx--;) {
+ struct transport_monitor_notifier *notifier;
+
+ notifier = AST_VECTOR_GET_ADDR(&monitored->monitors, idx);
+ notifier->cb(notifier->data);
+ }
+ ao2_ref(monitored, -1);
+ }
+ break;
+ default:
+ break;
+ }
+
+ ao2_ref(transports, -1);
+ }
+
+ /* Loop over other transport state callbacks registered with us. */
+ if (!AST_LIST_EMPTY(&transport_state_list)) {
+ struct ast_sip_tpmgr_state_callback *tpmgr_notifier;
+
+ AST_RWLIST_RDLOCK(&transport_state_list);
+ AST_LIST_TRAVERSE(&transport_state_list, tpmgr_notifier, node) {
+ tpmgr_notifier->cb(transport, state, info);
+ }
+ AST_RWLIST_UNLOCK(&transport_state_list);
+ }
+
+ /* Forward to the old state callback if present */
+ if (tpmgr_state_callback) {
+ tpmgr_state_callback(transport, state, info);
+ }
+}
+
+static int transport_monitor_unregister_all(void *obj, void *arg, int flags)
+{
+ struct transport_monitor *monitored = obj;
+ ast_transport_monitor_shutdown_cb cb = arg;
+ int idx;
+
+ for (idx = AST_VECTOR_SIZE(&monitored->monitors); idx--;) {
+ struct transport_monitor_notifier *notifier;
+
+ notifier = AST_VECTOR_GET_ADDR(&monitored->monitors, idx);
+ if (notifier->cb == cb) {
+ ao2_cleanup(notifier->data);
+ AST_VECTOR_REMOVE_UNORDERED(&monitored->monitors, idx);
+ break;
+ }
+ }
+ return 0;
+}
+
+void ast_sip_transport_monitor_unregister_all(ast_transport_monitor_shutdown_cb cb)
+{
+ struct ao2_container *transports;
+
+ transports = ao2_global_obj_ref(active_transports);
+ if (!transports) {
+ return;
+ }
+ ao2_callback(transports, OBJ_MULTIPLE | OBJ_NODATA, transport_monitor_unregister_all,
+ cb);
+ ao2_ref(transports, -1);
+}
+
+void ast_sip_transport_monitor_unregister(pjsip_transport *transport, ast_transport_monitor_shutdown_cb cb)
+{
+ struct ao2_container *transports;
+ struct transport_monitor *monitored;
+
+ transports = ao2_global_obj_ref(active_transports);
+ if (!transports) {
+ return;
+ }
+
+ ao2_lock(transports);
+ monitored = ao2_find(transports, transport->obj_name, OBJ_SEARCH_KEY | OBJ_NOLOCK);
+ if (monitored) {
+ int idx;
+
+ for (idx = AST_VECTOR_SIZE(&monitored->monitors); idx--;) {
+ struct transport_monitor_notifier *notifier;
+
+ notifier = AST_VECTOR_GET_ADDR(&monitored->monitors, idx);
+ if (notifier->cb == cb) {
+ ao2_cleanup(notifier->data);
+ AST_VECTOR_REMOVE_UNORDERED(&monitored->monitors, idx);
+ break;
+ }
+ }
+ ao2_ref(monitored, -1);
+ }
+ ao2_unlock(transports);
+ ao2_ref(transports, -1);
+}
+
+enum ast_transport_monitor_reg ast_sip_transport_monitor_register(pjsip_transport *transport,
+ ast_transport_monitor_shutdown_cb cb, void *ao2_data)
+{
+ struct ao2_container *transports;
+ struct transport_monitor *monitored;
+ enum ast_transport_monitor_reg res = AST_TRANSPORT_MONITOR_REG_NOT_FOUND;
+
+ transports = ao2_global_obj_ref(active_transports);
+ if (!transports) {
+ return res;
+ }
+
+ ao2_lock(transports);
+ monitored = ao2_find(transports, transport->obj_name, OBJ_SEARCH_KEY | OBJ_NOLOCK);
+ if (monitored) {
+ int idx;
+ struct transport_monitor_notifier new_monitor;
+
+ /* Check if the callback monitor already exists */
+ for (idx = AST_VECTOR_SIZE(&monitored->monitors); idx--;) {
+ struct transport_monitor_notifier *notifier;
+
+ notifier = AST_VECTOR_GET_ADDR(&monitored->monitors, idx);
+ if (notifier->cb == cb) {
+ /* The monitor is already in the vector replace with new ao2_data. */
+ ao2_replace(notifier->data, ao2_data);
+ res = AST_TRANSPORT_MONITOR_REG_REPLACED;
+ goto register_done;
+ }
+ }
+
+ /* Add new monitor to vector */
+ new_monitor.cb = cb;
+ new_monitor.data = ao2_bump(ao2_data);
+ if (AST_VECTOR_APPEND(&monitored->monitors, new_monitor)) {
+ ao2_cleanup(ao2_data);
+ res = AST_TRANSPORT_MONITOR_REG_FAILED;
+ }
+
+register_done:
+ ao2_ref(monitored, -1);
+ }
+ ao2_unlock(transports);
+ ao2_ref(transports, -1);
+ return res;
+}
+
+void ast_sip_transport_state_unregister(struct ast_sip_tpmgr_state_callback *element)
+{
+ AST_RWLIST_WRLOCK(&transport_state_list);
+ AST_LIST_REMOVE(&transport_state_list, element, node);
+ AST_RWLIST_UNLOCK(&transport_state_list);
+}
+
+void ast_sip_transport_state_register(struct ast_sip_tpmgr_state_callback *element)
+{
+ struct ast_sip_tpmgr_state_callback *tpmgr_notifier;
+
+ AST_RWLIST_WRLOCK(&transport_state_list);
+ AST_LIST_TRAVERSE(&transport_state_list, tpmgr_notifier, node) {
+ if (element == tpmgr_notifier) {
+ /* Already registered. */
+ AST_RWLIST_UNLOCK(&transport_state_list);
+ return;
+ }
+ }
+ AST_LIST_INSERT_HEAD(&transport_state_list, element, node);
+ AST_RWLIST_UNLOCK(&transport_state_list);
+}
+
+void ast_sip_destroy_transport_events(void)
+{
+ pjsip_tpmgr *tpmgr;
+
+ tpmgr = pjsip_endpt_get_tpmgr(ast_sip_get_pjsip_endpoint());
+ if (tpmgr) {
+ pjsip_tpmgr_set_state_cb(tpmgr, tpmgr_state_callback);
+ }
+
+ ao2_global_obj_release(active_transports);
+}
+
+int ast_sip_initialize_transport_events(void)
+{
+ pjsip_tpmgr *tpmgr;
+ struct ao2_container *transports;
+
+ tpmgr = pjsip_endpt_get_tpmgr(ast_sip_get_pjsip_endpoint());
+ if (!tpmgr) {
+ return -1;
+ }
+
+ transports = ao2_container_alloc_hash(AO2_ALLOC_OPT_LOCK_MUTEX, 0,
+ ACTIVE_TRANSPORTS_BUCKETS, transport_monitor_hash_fn, NULL,
+ transport_monitor_cmp_fn);
+ if (!transports) {
+ return -1;
+ }
+ ao2_global_obj_replace_unref(active_transports, transports);
+ ao2_ref(transports, -1);
+
+ tpmgr_state_callback = pjsip_tpmgr_get_state_cb(tpmgr);
+ pjsip_tpmgr_set_state_cb(tpmgr, &transport_state_callback);
+
+ return 0;
+}
diff --git a/res/res_pjsip/presence_xml.c b/res/res_pjsip/presence_xml.c
index c991a0d68..3cea79e98 100644
--- a/res/res_pjsip/presence_xml.c
+++ b/res/res_pjsip/presence_xml.c
@@ -82,43 +82,48 @@ void ast_sip_sanitize_xml(const char *input, char *output, size_t len)
}
void ast_sip_presence_exten_state_to_str(int state, char **statestring, char **pidfstate,
- char **pidfnote, enum ast_sip_pidf_state *local_state)
+ char **pidfnote, enum ast_sip_pidf_state *local_state,
+ unsigned int notify_early_inuse_ringing)
{
switch (state) {
case AST_EXTENSION_RINGING:
*statestring = "early";
*local_state = NOTIFY_INUSE;
- *pidfstate = "busy";
+ *pidfstate = "on-the-phone";
*pidfnote = "Ringing";
break;
case (AST_EXTENSION_INUSE | AST_EXTENSION_RINGING):
- *statestring = "confirmed";
+ if (notify_early_inuse_ringing) {
+ *statestring = "early";
+ } else {
+ *statestring = "confirmed";
+ }
*local_state = NOTIFY_INUSE;
- *pidfstate = "busy";
+ *pidfstate = "on-the-phone";
*pidfnote = "Ringing";
break;
case AST_EXTENSION_INUSE:
*statestring = "confirmed";
*local_state = NOTIFY_INUSE;
- *pidfstate = "busy";
+ *pidfstate = "on-the-phone";
*pidfnote = "On the phone";
break;
case AST_EXTENSION_BUSY:
*statestring = "confirmed";
- *local_state = NOTIFY_CLOSED;
- *pidfstate = "busy";
+ *local_state = NOTIFY_INUSE;
+ *pidfstate = "on-the-phone";
*pidfnote = "On the phone";
break;
case AST_EXTENSION_UNAVAILABLE:
*statestring = "terminated";
*local_state = NOTIFY_CLOSED;
- *pidfstate = "away";
+ *pidfstate = "--";
*pidfnote = "Unavailable";
break;
case AST_EXTENSION_ONHOLD:
*statestring = "confirmed";
- *local_state = NOTIFY_CLOSED;
- *pidfstate = "busy";
+ *local_state = NOTIFY_INUSE;
+ *pidfstate = "on-the-phone";
*pidfnote = "On hold";
break;
case AST_EXTENSION_NOT_INUSE:
diff --git a/res/res_pjsip_dialog_info_body_generator.c b/res/res_pjsip_dialog_info_body_generator.c
index b21b70fb1..fa3d710e5 100644
--- a/res/res_pjsip_dialog_info_body_generator.c
+++ b/res/res_pjsip_dialog_info_body_generator.c
@@ -107,6 +107,8 @@ static int dialog_info_generate_body_content(void *body, void *data)
enum ast_sip_pidf_state local_state;
unsigned int version;
char version_str[32], sanitized[PJSIP_MAX_URL_SIZE];
+ struct ast_sip_endpoint *endpoint = NULL;
+ unsigned int notify_early_inuse_ringing = 0;
if (!local || !state_data->sub) {
return -1;
@@ -120,8 +122,12 @@ static int dialog_info_generate_body_content(void *body, void *data)
stripped = ast_strip_quoted(local, "<", ">");
ast_sip_sanitize_xml(stripped, sanitized, sizeof(sanitized));
+ if (state_data->sub && (endpoint = ast_sip_subscription_get_endpoint(state_data->sub))) {
+ notify_early_inuse_ringing = endpoint->notify_early_inuse_ringing;
+ ao2_cleanup(endpoint);
+ }
ast_sip_presence_exten_state_to_str(state_data->exten_state, &statestring,
- &pidfstate, &pidfnote, &local_state);
+ &pidfstate, &pidfnote, &local_state, notify_early_inuse_ringing);
ast_sip_presence_xml_create_attr(state_data->pool, dialog_info, "xmlns", "urn:ietf:params:xml:ns:dialog-info");
@@ -133,7 +139,7 @@ static int dialog_info_generate_body_content(void *body, void *data)
dialog = ast_sip_presence_xml_create_node(state_data->pool, dialog_info, "dialog");
ast_sip_presence_xml_create_attr(state_data->pool, dialog, "id", state_data->exten);
- if (state_data->exten_state == AST_EXTENSION_RINGING) {
+ if (!ast_strlen_zero(statestring) && !strcmp(statestring, "early")) {
ast_sip_presence_xml_create_attr(state_data->pool, dialog, "direction", "recipient");
}
diff --git a/res/res_pjsip_messaging.c b/res/res_pjsip_messaging.c
index 8b465e007..2f01a0f47 100644
--- a/res/res_pjsip_messaging.c
+++ b/res/res_pjsip_messaging.c
@@ -512,7 +512,7 @@ static enum pjsip_status_code rx_data_to_ast_msg(pjsip_rx_data *rdata, struct as
buf[size] = '\0';
res |= ast_msg_set_from(msg, "%s", buf);
- field = pj_sockaddr_print(&rdata->pkt_info.src_addr, buf, sizeof(buf) - 1, 1);
+ field = pj_sockaddr_print(&rdata->pkt_info.src_addr, buf, sizeof(buf) - 1, 3);
res |= ast_msg_set_var(msg, "PJSIP_RECVADDR", field);
switch (rdata->tp_info.transport->key.type) {
diff --git a/res/res_pjsip_mwi.c b/res/res_pjsip_mwi.c
index 5ae2af5d3..05eee782e 100644
--- a/res/res_pjsip_mwi.c
+++ b/res/res_pjsip_mwi.c
@@ -1102,6 +1102,13 @@ static int create_mwi_subscriptions_for_endpoint(void *obj, void *arg, int flags
}
if (endpoint->subscription.mwi.aggregate) {
+ const char *endpoint_id = ast_sorcery_object_get_id(endpoint);
+
+ /* Check if subscription exists */
+ aggregate_sub = ao2_find(unsolicited_mwi, endpoint_id, OBJ_SEARCH_KEY | OBJ_NOLOCK);
+ if (aggregate_sub) {
+ return 0;
+ }
aggregate_sub = mwi_subscription_alloc(endpoint, 0, NULL);
if (!aggregate_sub) {
return 0;
@@ -1113,7 +1120,9 @@ static int create_mwi_subscriptions_for_endpoint(void *obj, void *arg, int flags
struct mwi_subscription *sub;
struct mwi_stasis_subscription *mwi_stasis_sub;
- if (ast_strlen_zero(mailbox)) {
+ /* check if subscription exists */
+ if (ast_strlen_zero(mailbox) ||
+ (!aggregate_sub && endpoint_receives_unsolicited_mwi_for_mailbox(endpoint, mailbox))) {
continue;
}
@@ -1189,31 +1198,79 @@ static int send_contact_notify(void *obj, void *arg, int flags)
return 0;
}
-/*! \brief Function called when a contact is updated */
-static void mwi_contact_updated(const void *object)
+/*! \brief Create mwi subscriptions and notify */
+static void mwi_contact_changed(const struct ast_sip_contact *contact)
{
- char *id = ast_strdupa(ast_sorcery_object_get_id(object)), *aor = NULL;
+ char *id = ast_strdupa(ast_sorcery_object_get_id(contact));
+ char *aor = NULL;
+ struct ast_sip_endpoint *endpoint = NULL;
- aor = strsep(&id, ";@");
+ if (contact->endpoint) {
+ endpoint = ao2_bump(contact->endpoint);
+ } else {
+ if (!ast_strlen_zero(contact->endpoint_name)) {
+ endpoint = ast_sorcery_retrieve_by_id(ast_sip_get_sorcery(), "endpoint", contact->endpoint_name);
+ }
+ }
+ if (!endpoint || ast_strlen_zero(endpoint->subscription.mwi.mailboxes)) {
+ ao2_cleanup(endpoint);
+ return;
+ }
+
+ ao2_lock(unsolicited_mwi);
+ create_mwi_subscriptions_for_endpoint(endpoint, NULL, 0);
+ ao2_unlock(unsolicited_mwi);
+ ao2_cleanup(endpoint);
+
+ aor = strsep(&id, ";@");
ao2_callback(unsolicited_mwi, OBJ_NODATA, send_contact_notify, aor);
}
+/*! \brief Function called when a contact is updated */
+static void mwi_contact_updated(const void *object)
+{
+ mwi_contact_changed(object);
+}
+
/*! \brief Function called when a contact is added */
static void mwi_contact_added(const void *object)
{
+ mwi_contact_changed(object);
+}
+
+/*! \brief Function called when a contact is deleted */
+static void mwi_contact_deleted(const void *object)
+{
const struct ast_sip_contact *contact = object;
struct ao2_iterator *mwi_subs;
struct mwi_subscription *mwi_sub;
- const char *endpoint_id = ast_sorcery_object_get_id(contact->endpoint);
+ struct ast_sip_endpoint *endpoint = NULL;
+ struct ast_sip_contact *found_contact;
- if (ast_strlen_zero(contact->endpoint->subscription.mwi.mailboxes)) {
+ if (contact->endpoint) {
+ endpoint = ao2_bump(contact->endpoint);
+ } else {
+ if (!ast_strlen_zero(contact->endpoint_name)) {
+ endpoint = ast_sorcery_retrieve_by_id(ast_sip_get_sorcery(), "endpoint", contact->endpoint_name);
+ }
+ }
+
+ if (!endpoint || ast_strlen_zero(endpoint->subscription.mwi.mailboxes)) {
+ ao2_cleanup(endpoint);
return;
}
- ao2_lock(unsolicited_mwi);
+ /* Check if there is another contact */
+ found_contact = ast_sip_location_retrieve_contact_from_aor_list(endpoint->aors);
+ ao2_cleanup(endpoint);
+ if (found_contact) {
+ ao2_cleanup(found_contact);
+ return;
+ }
- mwi_subs = ao2_find(unsolicited_mwi, endpoint_id,
+ ao2_lock(unsolicited_mwi);
+ mwi_subs = ao2_find(unsolicited_mwi, contact->endpoint_name,
OBJ_SEARCH_KEY | OBJ_MULTIPLE | OBJ_NOLOCK | OBJ_UNLINK);
if (mwi_subs) {
for (; (mwi_sub = ao2_iterator_next(mwi_subs)); ao2_cleanup(mwi_sub)) {
@@ -1221,18 +1278,14 @@ static void mwi_contact_added(const void *object)
}
ao2_iterator_destroy(mwi_subs);
}
-
- create_mwi_subscriptions_for_endpoint(contact->endpoint, NULL, 0);
-
ao2_unlock(unsolicited_mwi);
-
- mwi_contact_updated(object);
}
/*! \brief Observer for contacts so unsolicited MWI is sent when a contact changes */
static const struct ast_sorcery_observer mwi_contact_observer = {
.created = mwi_contact_added,
.updated = mwi_contact_updated,
+ .deleted = mwi_contact_deleted,
};
/*! \brief Task invoked to send initial MWI NOTIFY for unsolicited */
@@ -1278,7 +1331,9 @@ static struct ast_sorcery_observer global_observer = {
static int reload(void)
{
- create_mwi_subscriptions();
+ if (!ast_sip_get_mwi_disable_initial_unsolicited()) {
+ create_mwi_subscriptions();
+ }
return 0;
}
@@ -1301,13 +1356,13 @@ static int load_module(void)
ast_sip_unregister_subscription_handler(&mwi_handler);
return AST_MODULE_LOAD_DECLINE;
}
- create_mwi_subscriptions();
ast_sorcery_observer_add(ast_sip_get_sorcery(), "contact", &mwi_contact_observer);
ast_sorcery_observer_add(ast_sip_get_sorcery(), "global", &global_observer);
ast_sorcery_reload_object(ast_sip_get_sorcery(), "global");
if (!ast_sip_get_mwi_disable_initial_unsolicited()) {
+ create_mwi_subscriptions();
if (ast_test_flag(&ast_options, AST_OPT_FLAG_FULLY_BOOTED)) {
ast_sip_push_task(NULL, send_initial_notify_all, NULL);
} else {
diff --git a/res/res_pjsip_nat.c b/res/res_pjsip_nat.c
index a0ce2a9d1..370004a3a 100644
--- a/res/res_pjsip_nat.c
+++ b/res/res_pjsip_nat.c
@@ -35,6 +35,7 @@
static void rewrite_uri(pjsip_rx_data *rdata, pjsip_sip_uri *uri)
{
pj_cstr(&uri->host, rdata->pkt_info.src_name);
+ uri->port = rdata->pkt_info.src_port;
if (!strcasecmp("WSS", rdata->tp_info.transport->type_name)) {
/* WSS is special, we don't want to overwrite the URI at all as it needs to be ws */
} else if (strcasecmp("udp", rdata->tp_info.transport->type_name)) {
@@ -42,7 +43,6 @@ static void rewrite_uri(pjsip_rx_data *rdata, pjsip_sip_uri *uri)
} else {
uri->transport_param.slen = 0;
}
- uri->port = rdata->pkt_info.src_port;
}
static int rewrite_route_set(pjsip_rx_data *rdata, pjsip_dialog *dlg)
@@ -165,7 +165,7 @@ static int find_transport_state_in_use(void *obj, void *arg, int flags)
((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;
+ return CMP_MATCH;
}
return 0;
@@ -267,16 +267,16 @@ 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_state->localnet, &addr) != AST_SENSE_ALLOW) {
+ if (ast_sip_transport_is_local(transport_state, &addr)) {
ast_debug(5, "Request is being sent to local address, skipping NAT manipulation\n");
return PJ_SUCCESS;
}
}
- if (!ast_sockaddr_isnull(&transport_state->external_address)) {
+ if (!ast_sockaddr_isnull(&transport_state->external_signaling_address)) {
/* 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_state->external_address));
+ pj_strdup2(tdata->pool, &uri->host, ast_sockaddr_stringify_host(&transport_state->external_signaling_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);
@@ -285,7 +285,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_state->external_address));
+ pj_strdup2(tdata->pool, &via->sent_by.host, ast_sockaddr_stringify_host(&transport_state->external_signaling_address));
if (transport->external_signaling_port) {
via->sent_by.port = transport->external_signaling_port;
}
diff --git a/res/res_pjsip_outbound_registration.c b/res/res_pjsip_outbound_registration.c
index 0a65e6e1d..76d756d9e 100644
--- a/res/res_pjsip_outbound_registration.c
+++ b/res/res_pjsip_outbound_registration.c
@@ -358,6 +358,8 @@ struct sip_outbound_registration_client_state {
unsigned int auth_attempted:1;
/*! \brief The name of the transport to be used for the registration */
char *transport_name;
+ /*! \brief The name of the registration sorcery object */
+ char *registration_name;
};
/*! \brief Outbound registration state information (persists for lifetime that registration should exist) */
@@ -459,7 +461,7 @@ static int line_identify_relationship(void *obj, void *arg, int flags)
struct sip_outbound_registration_state *state = obj;
pjsip_param *line = arg;
- return !pj_strcmp2(&line->value, state->client_state->line) ? CMP_MATCH | CMP_STOP : 0;
+ return !pj_strcmp2(&line->value, state->client_state->line) ? CMP_MATCH : 0;
}
static struct pjsip_param *get_uri_option_line(const void *uri)
@@ -559,20 +561,21 @@ static int handle_client_registration(void *data)
{
RAII_VAR(struct sip_outbound_registration_client_state *, client_state, data, ao2_cleanup);
pjsip_tx_data *tdata;
- pjsip_regc_info info;
- char server_uri[PJSIP_MAX_URL_SIZE];
- char client_uri[PJSIP_MAX_URL_SIZE];
if (client_state->status == SIP_REGISTRATION_STOPPED
|| pjsip_regc_register(client_state->client, PJ_FALSE, &tdata) != PJ_SUCCESS) {
return 0;
}
- pjsip_regc_get_info(client_state->client, &info);
- ast_copy_pj_str(server_uri, &info.server_uri, sizeof(server_uri));
- ast_copy_pj_str(client_uri, &info.client_uri, sizeof(client_uri));
- ast_debug(1, "Outbound REGISTER attempt %u to '%s' with client '%s'\n",
- client_state->retries + 1, server_uri, client_uri);
+ if (DEBUG_ATLEAST(1)) {
+ pjsip_regc_info info;
+
+ pjsip_regc_get_info(client_state->client, &info);
+ ast_log(LOG_DEBUG, "Outbound REGISTER attempt %u to '%.*s' with client '%.*s'\n",
+ client_state->retries + 1,
+ (int) info.server_uri.slen, info.server_uri.ptr,
+ (int) info.client_uri.slen, info.client_uri.ptr);
+ }
if (client_state->support_path) {
pjsip_supported_hdr *hdr;
@@ -795,6 +798,82 @@ static void schedule_retry(struct registration_response *response, unsigned int
}
}
+static int reregister_immediately_cb(void *obj)
+{
+ struct sip_outbound_registration_state *state = obj;
+
+ if (state->client_state->status != SIP_REGISTRATION_REGISTERED) {
+ ao2_ref(state, -1);
+ return 0;
+ }
+
+ if (DEBUG_ATLEAST(1)) {
+ pjsip_regc_info info;
+
+ pjsip_regc_get_info(state->client_state->client, &info);
+ ast_log(LOG_DEBUG,
+ "Outbound registration transport to server '%.*s' from client '%.*s' shutdown\n",
+ (int) info.server_uri.slen, info.server_uri.ptr,
+ (int) info.client_uri.slen, info.client_uri.ptr);
+ }
+
+ cancel_registration(state->client_state);
+
+ ao2_ref(state->client_state, +1);
+ handle_client_registration(state->client_state);
+
+ ao2_ref(state, -1);
+ return 0;
+}
+
+/*!
+ * \internal
+ * \brief The reliable transport we registered using has shutdown.
+ * \since 13.18.0
+ *
+ * \param obj What is needed to initiate a reregister attempt.
+ *
+ * \return Nothing
+ */
+static void registration_transport_shutdown_cb(void *obj)
+{
+ const char *registration_name = obj;
+ struct sip_outbound_registration_state *state;
+
+ state = get_state(registration_name);
+ if (!state) {
+ /* Registration no longer exists or shutting down. */
+ return;
+ }
+ if (ast_sip_push_task(state->client_state->serializer, reregister_immediately_cb, state)) {
+ ao2_ref(state, -1);
+ }
+}
+
+static void registration_transport_monitor_setup(pjsip_transport *transport, const char *registration_name)
+{
+ char *monitor;
+
+ if (!PJSIP_TRANSPORT_IS_RELIABLE(transport)) {
+ return;
+ }
+ monitor = ao2_alloc_options(strlen(registration_name) + 1, NULL,
+ AO2_ALLOC_OPT_LOCK_NOLOCK);
+ if (!monitor) {
+ return;
+ }
+ strcpy(monitor, registration_name);/* Safe */
+
+ /*
+ * We'll ignore if the transport has already been shutdown before we
+ * register the monitor. We might get into a message spamming infinite
+ * loop of registration, shutdown, reregistration...
+ */
+ ast_sip_transport_monitor_register(transport, registration_transport_shutdown_cb,
+ monitor);
+ ao2_ref(monitor, -1);
+}
+
/*! \brief Callback function for handling a response to a registration attempt */
static int handle_registration_response(void *data)
{
@@ -863,9 +942,15 @@ static int handle_registration_response(void *data)
next_registration_round = 0;
}
schedule_registration(response->client_state, next_registration_round);
+
+ /* See if we should monitor for transport shutdown */
+ registration_transport_monitor_setup(response->rdata->tp_info.transport,
+ response->client_state->registration_name);
} else {
ast_debug(1, "Outbound unregistration to '%s' with client '%s' successful\n", server_uri, client_uri);
update_client_state_status(response->client_state, SIP_REGISTRATION_UNREGISTERED);
+ ast_sip_transport_monitor_unregister(response->rdata->tp_info.transport,
+ registration_transport_shutdown_cb);
}
} else if (response->client_state->destroy) {
/* We need to deal with the pending destruction instead. */
@@ -988,7 +1073,8 @@ static void sip_outbound_registration_state_destroy(void *obj)
struct sip_outbound_registration_state *state = obj;
ast_debug(3, "Destroying registration state for registration to server '%s' from client '%s'\n",
- state->registration->server_uri, state->registration->client_uri);
+ state->registration ? state->registration->server_uri : "",
+ state->registration ? state->registration->client_uri : "");
ao2_cleanup(state->registration);
if (!state->client_state) {
@@ -1007,12 +1093,13 @@ 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));
ast_taskprocessor_unreference(client_state->serializer);
+ ast_free(client_state->transport_name);
+ ast_free(client_state->registration_name);
}
/*! \brief Allocator function for registration state */
@@ -1032,6 +1119,23 @@ static struct sip_outbound_registration_state *sip_outbound_registration_state_a
return NULL;
}
+ 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);
+ state->client_state->registration_name =
+ ast_strdup(ast_sorcery_object_get_id(registration));
+
+ 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(state->client_state->status));
+
+ if (!state->client_state->transport_name
+ || !state->client_state->registration_name) {
+ ao2_cleanup(state);
+ return NULL;
+ }
+
/* Create name with seq number appended. */
ast_taskprocessor_build_name(tps_name, sizeof(tps_name), "pjsip/outreg/%s",
ast_sorcery_object_get_id(registration));
@@ -1042,14 +1146,6 @@ static struct sip_outbound_registration_state *sip_outbound_registration_state_a
ao2_cleanup(state);
return NULL;
}
- 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,
- sip_outbound_registration_status_str(state->client_state->status));
state->registration = ao2_bump(registration);
return state;
@@ -2053,6 +2149,8 @@ static int unload_module(void)
ao2_global_obj_release(current_states);
+ ast_sip_transport_monitor_unregister_all(registration_transport_shutdown_cb);
+
/* Wait for registration serializers to get destroyed. */
ast_debug(2, "Waiting for registration transactions to complete for unload.\n");
remaining = ast_serializer_shutdown_group_join(shutdown_group, MAX_UNLOAD_TIMEOUT_TIME);
diff --git a/res/res_pjsip_pidf_body_generator.c b/res/res_pjsip_pidf_body_generator.c
index d3be8c131..4daff964d 100644
--- a/res/res_pjsip_pidf_body_generator.c
+++ b/res/res_pjsip_pidf_body_generator.c
@@ -58,7 +58,7 @@ static int pidf_generate_body_content(void *body, void *data)
struct ast_sip_exten_state_data *state_data = data;
ast_sip_presence_exten_state_to_str(state_data->exten_state, &statestring,
- &pidfstate, &pidfnote, &local_state);
+ &pidfstate, &pidfnote, &local_state, 0);
if (!pjpidf_pres_add_note(state_data->pool, pres, pj_cstr(&note, pidfnote))) {
ast_log(LOG_WARNING, "Unable to add note to PIDF presence\n");
@@ -75,7 +75,7 @@ static int pidf_generate_body_content(void *body, void *data)
pjpidf_tuple_set_contact(state_data->pool, tuple, pj_cstr(&contact, sanitized));
pjpidf_tuple_set_contact_prio(state_data->pool, tuple, pj_cstr(&priority, "1"));
pjpidf_status_set_basic_open(pjpidf_tuple_get_status(tuple),
- local_state == NOTIFY_OPEN);
+ local_state == NOTIFY_OPEN || local_state == NOTIFY_INUSE);
return 0;
}
diff --git a/res/res_pjsip_pidf_eyebeam_body_supplement.c b/res/res_pjsip_pidf_eyebeam_body_supplement.c
index cd590c3d3..0200a4654 100644
--- a/res/res_pjsip_pidf_eyebeam_body_supplement.c
+++ b/res/res_pjsip_pidf_eyebeam_body_supplement.c
@@ -46,30 +46,28 @@
*/
static void add_eyebeam(pj_pool_t *pool, pj_xml_node *node, const char *pidfstate)
{
- static const char *XMLNS_PP = "xmlns:pp";
- static const char *XMLNS_PERSON = "urn:ietf:params:xml:ns:pidf:person";
+ static const char *XMLNS_DM_PREFIX = "xmlns:dm";
+ static const char *XMLNS_DM = "urn:ietf:params:xml:ns:pidf:data-model";
- static const char *XMLNS_ES = "xmlns:es";
- static const char *XMLNS_RPID_STATUS = "urn:ietf:params:xml:ns:pidf:rpid:status:rpid-status";
+ static const char *XMLNS_RPID_PREFIX = "xmlns:rpid";
+ static const char *XMLNS_RPID = "urn:ietf:params:xml:ns:pidf:rpid";
- static const char *XMLNS_EP = "xmlns:ep";
- static const char *XMLNS_RPID_PERSON = "urn:ietf:params:xml:ns:pidf:rpid:rpid-person";
-
- pj_xml_node *person = ast_sip_presence_xml_create_node(pool, node, "pp:person");
- pj_xml_node *status = ast_sip_presence_xml_create_node(pool, person, "status");
+ pj_xml_node *person = ast_sip_presence_xml_create_node(pool, node, "dm:person");
if (pidfstate[0] != '-') {
- pj_xml_node *activities = ast_sip_presence_xml_create_node(pool, status, "ep:activities");
- size_t str_size = sizeof("ep:") + strlen(pidfstate);
+ pj_xml_node *activities = ast_sip_presence_xml_create_node(pool, person, "rpid:activities");
+ size_t str_size = sizeof("rpid:") + strlen(pidfstate);
+ char *act_str = ast_alloca(str_size);
+
+ /* Safe */
+ strcpy(act_str, "rpid:");
+ strcat(act_str, pidfstate);
- activities->content.ptr = pj_pool_alloc(pool, str_size);
- activities->content.slen = pj_ansi_snprintf(activities->content.ptr, str_size,
- "ep:%s", pidfstate);
+ ast_sip_presence_xml_create_node(pool, activities, act_str);
}
- ast_sip_presence_xml_create_attr(pool, node, XMLNS_PP, XMLNS_PERSON);
- ast_sip_presence_xml_create_attr(pool, node, XMLNS_ES, XMLNS_RPID_STATUS);
- ast_sip_presence_xml_create_attr(pool, node, XMLNS_EP, XMLNS_RPID_PERSON);
+ ast_sip_presence_xml_create_attr(pool, node, XMLNS_DM_PREFIX, XMLNS_DM);
+ ast_sip_presence_xml_create_attr(pool, node, XMLNS_RPID_PREFIX, XMLNS_RPID);
}
static int pidf_supplement_body(void *body, void *data)
@@ -80,7 +78,7 @@ static int pidf_supplement_body(void *body, void *data)
enum ast_sip_pidf_state local_state;
ast_sip_presence_exten_state_to_str(state_data->exten_state, &statestring,
- &pidfstate, &pidfnote, &local_state);
+ &pidfstate, &pidfnote, &local_state, 0);
add_eyebeam(state_data->pool, pres, pidfstate);
return 0;
diff --git a/res/res_pjsip_pubsub.c b/res/res_pjsip_pubsub.c
index fbb1ad4e8..c62bddd56 100644
--- a/res/res_pjsip_pubsub.c
+++ b/res/res_pjsip_pubsub.c
@@ -1108,7 +1108,9 @@ static void remove_subscription(struct sip_subscription_tree *obj)
static void destroy_subscription(struct ast_sip_subscription *sub)
{
ast_debug(3, "Destroying SIP subscription from '%s->%s'\n",
- ast_sorcery_object_get_id(sub->tree->endpoint), sub->resource);
+ sub->tree->endpoint ? ast_sorcery_object_get_id(sub->tree->endpoint) : "Unknown",
+ sub->resource);
+
ast_free(sub->body_text);
AST_VECTOR_FREE(&sub->children);
@@ -1265,14 +1267,14 @@ static void subscription_tree_destructor(void *obj)
sub_tree->endpoint ? ast_sorcery_object_get_id(sub_tree->endpoint) : "Unknown",
sub_tree->root ? sub_tree->root->resource : "Unknown");
- ao2_cleanup(sub_tree->endpoint);
-
destroy_subscriptions(sub_tree->root);
if (sub_tree->dlg) {
ast_sip_push_task_synchronous(sub_tree->serializer, subscription_unreference_dialog, sub_tree);
}
+ ao2_cleanup(sub_tree->endpoint);
+
ast_taskprocessor_unreference(sub_tree->serializer);
ast_module_unref(ast_module_info->self);
}
diff --git a/res/res_pjsip_refer.c b/res/res_pjsip_refer.c
index 111c4b2a1..ff8bc6a66 100644
--- a/res/res_pjsip_refer.c
+++ b/res/res_pjsip_refer.c
@@ -543,6 +543,7 @@ static int refer_attended_task(void *data)
}
}
+ ast_sip_session_end_if_deferred(attended->transferer);
if (response != 200) {
if (!ast_sip_push_task(attended->transferer->serializer,
defer_termination_cancel, attended->transferer)) {
@@ -772,6 +773,7 @@ static int refer_incoming_attended_request(struct ast_sip_session *session, pjsi
/* Push it to the other session, which will have both channels with minimal locking */
if (ast_sip_push_task(other_session->serializer, refer_attended_task, attended)) {
+ ast_sip_session_end_if_deferred(session);
ast_sip_session_defer_termination_cancel(session);
ao2_cleanup(attended);
return 500;
@@ -810,9 +812,12 @@ static int refer_incoming_attended_request(struct ast_sip_session *session, pjsi
response = xfer_response_code2sip(ast_bridge_transfer_blind(1, session->channel,
"external_replaces", context, refer_blind_callback, &refer));
+
+ ast_sip_session_end_if_deferred(session);
if (response != 200) {
ast_sip_session_defer_termination_cancel(session);
}
+
return response;
}
}
@@ -865,9 +870,12 @@ static int refer_incoming_blind_request(struct ast_sip_session *session, pjsip_r
response = xfer_response_code2sip(ast_bridge_transfer_blind(1, session->channel,
exten, context, refer_blind_callback, &refer));
+
+ ast_sip_session_end_if_deferred(session);
if (response != 200) {
ast_sip_session_defer_termination_cancel(session);
}
+
return response;
}
diff --git a/res/res_pjsip_registrar.c b/res/res_pjsip_registrar.c
index d54bffa0c..48b48356c 100644
--- a/res/res_pjsip_registrar.c
+++ b/res/res_pjsip_registrar.c
@@ -123,7 +123,7 @@ static int registrar_find_contact(void *obj, void *arg, int flags)
const struct registrar_contact_details *details = arg;
pjsip_uri *contact_uri = pjsip_parse_uri(details->pool, (char*)contact->uri, strlen(contact->uri), 0);
- return (pjsip_uri_cmp(PJSIP_URI_IN_CONTACT_HDR, details->uri, contact_uri) == PJ_SUCCESS) ? CMP_MATCH | CMP_STOP : 0;
+ return (pjsip_uri_cmp(PJSIP_URI_IN_CONTACT_HDR, details->uri, contact_uri) == PJ_SUCCESS) ? CMP_MATCH : 0;
}
/*! \brief Internal function which validates provided Contact headers to confirm that they are acceptable, and returns number of contacts */
@@ -310,6 +310,47 @@ static int registrar_validate_path(pjsip_rx_data *rdata, struct ast_sip_aor *aor
return -1;
}
+/*! Transport monitor for incoming REGISTER contacts */
+struct contact_transport_monitor {
+ /*!
+ * \brief Sorcery contact name to remove on transport shutdown
+ * \note Stored after aor_name in space reserved when struct allocated.
+ */
+ char *contact_name;
+ /*! AOR name the contact is associated */
+ char aor_name[0];
+};
+
+static void register_contact_transport_shutdown_cb(void *data)
+{
+ struct contact_transport_monitor *monitor = data;
+ struct ast_sip_contact *contact;
+ struct ast_named_lock *lock;
+
+ lock = ast_named_lock_get(AST_NAMED_LOCK_TYPE_MUTEX, "aor", monitor->aor_name);
+ if (!lock) {
+ return;
+ }
+
+ ao2_lock(lock);
+ contact = ast_sip_location_retrieve_contact(monitor->contact_name);
+ if (contact) {
+ ast_sip_location_delete_contact(contact);
+ ast_verb(3, "Removed contact '%s' from AOR '%s' due to transport shutdown\n",
+ contact->uri, monitor->aor_name);
+ ast_test_suite_event_notify("AOR_CONTACT_REMOVED",
+ "Contact: %s\r\n"
+ "AOR: %s\r\n"
+ "UserAgent: %s",
+ contact->uri,
+ monitor->aor_name,
+ contact->user_agent);
+ ao2_ref(contact, -1);
+ }
+ ao2_unlock(lock);
+ ast_named_lock_put(lock);
+}
+
static int register_aor_core(pjsip_rx_data *rdata,
struct ast_sip_endpoint *endpoint,
struct ast_sip_aor *aor,
@@ -419,6 +460,9 @@ static int register_aor_core(pjsip_rx_data *rdata,
pjsip_uri_print(PJSIP_URI_IN_CONTACT_HDR, details.uri, contact_uri, sizeof(contact_uri));
if (!(contact = ao2_callback(contacts, OBJ_UNLINK, registrar_find_contact, &details))) {
+ int prune_on_boot = 0;
+ pj_str_t host_name;
+
/* If they are actually trying to delete a contact that does not exist... be forgiving */
if (!expiration) {
ast_verb(3, "Attempted to remove non-existent contact '%s' from AOR '%s' by request\n",
@@ -426,14 +470,68 @@ static int register_aor_core(pjsip_rx_data *rdata,
continue;
}
- if (ast_sip_location_add_contact_nolock(aor, contact_uri, ast_tvadd(ast_tvnow(),
- ast_samp2tv(expiration, 1)), path_str ? ast_str_buffer(path_str) : NULL,
- user_agent, via_addr, via_port, call_id, endpoint)) {
+ /* Determine if the contact cannot survive a restart/boot. */
+ if (details.uri->port == rdata->pkt_info.src_port
+ && !pj_strcmp(&details.uri->host,
+ pj_cstr(&host_name, rdata->pkt_info.src_name))
+ /* We have already checked if the URI scheme is sip: or sips: */
+ && PJSIP_TRANSPORT_IS_RELIABLE(rdata->tp_info.transport)) {
+ pj_str_t type_name;
+
+ /* Determine the transport parameter value */
+ if (!strcasecmp("WSS", rdata->tp_info.transport->type_name)) {
+ /* WSS is special, as it needs to be ws. */
+ pj_cstr(&type_name, "ws");
+ } else {
+ pj_cstr(&type_name, rdata->tp_info.transport->type_name);
+ }
+
+ if (!pj_stricmp(&details.uri->transport_param, &type_name)
+ && (endpoint->nat.rewrite_contact
+ /* Websockets are always rewritten */
+ || !pj_stricmp(&details.uri->transport_param,
+ pj_cstr(&type_name, "ws")))) {
+ /*
+ * The contact was rewritten to the reliable transport's
+ * source address. Disconnecting the transport for any
+ * reason invalidates the contact.
+ */
+ prune_on_boot = 1;
+ }
+ }
+
+ contact = ast_sip_location_create_contact(aor, contact_uri,
+ ast_tvadd(ast_tvnow(), ast_samp2tv(expiration, 1)),
+ path_str ? ast_str_buffer(path_str) : NULL,
+ user_agent, via_addr, via_port, call_id, prune_on_boot, endpoint);
+ if (!contact) {
ast_log(LOG_ERROR, "Unable to bind contact '%s' to AOR '%s'\n",
- contact_uri, aor_name);
+ contact_uri, aor_name);
continue;
}
+ if (prune_on_boot) {
+ const char *contact_name;
+ struct contact_transport_monitor *monitor;
+
+ /*
+ * Monitor the transport in case it gets disconnected because
+ * the contact won't be valid anymore if that happens.
+ */
+ contact_name = ast_sorcery_object_get_id(contact);
+ monitor = ao2_alloc_options(sizeof(*monitor) + 2 + strlen(aor_name)
+ + strlen(contact_name), NULL, AO2_ALLOC_OPT_LOCK_NOLOCK);
+ if (monitor) {
+ strcpy(monitor->aor_name, aor_name);/* Safe */
+ monitor->contact_name = monitor->aor_name + strlen(aor_name) + 1;
+ strcpy(monitor->contact_name, contact_name);/* Safe */
+
+ ast_sip_transport_monitor_register(rdata->tp_info.transport,
+ register_contact_transport_shutdown_cb, monitor);
+ ao2_ref(monitor, -1);
+ }
+ }
+
ast_verb(3, "Added contact '%s' to AOR '%s' with expiration of %d seconds\n",
contact_uri, aor_name, expiration);
ast_test_suite_event_notify("AOR_CONTACT_ADDED",
@@ -893,6 +991,7 @@ static int unload_module(void)
ast_manager_unregister(AMI_SHOW_REGISTRATIONS);
ast_manager_unregister(AMI_SHOW_REGISTRATION_CONTACT_STATUSES);
ast_sip_unregister_service(&registrar_module);
+ ast_sip_transport_monitor_unregister_all(register_contact_transport_shutdown_cb);
return 0;
}
diff --git a/res/res_pjsip_sdp_rtp.c b/res/res_pjsip_sdp_rtp.c
index cafbd52ec..b8ae8c185 100644
--- a/res/res_pjsip_sdp_rtp.c
+++ b/res/res_pjsip_sdp_rtp.c
@@ -241,15 +241,16 @@ static int create_rtp(struct ast_sip_session *session, struct ast_sip_session_me
}
ast_rtp_instance_set_prop(session_media->rtp, AST_RTP_PROPERTY_NAT, session->endpoint->media.rtp.symmetric);
+ ast_rtp_instance_set_prop(session_media->rtp, AST_RTP_PROPERTY_ASYMMETRIC_CODEC, session->endpoint->asymmetric_rtp_codec);
if (!session->endpoint->media.rtp.ice_support && (ice = ast_rtp_instance_get_ice(session_media->rtp))) {
ice->stop(session_media->rtp);
}
- if (session->endpoint->dtmf == AST_SIP_DTMF_RFC_4733 || session->endpoint->dtmf == AST_SIP_DTMF_AUTO) {
+ if (session->dtmf == AST_SIP_DTMF_RFC_4733 || session->dtmf == AST_SIP_DTMF_AUTO || session->dtmf == AST_SIP_DTMF_AUTO_INFO) {
ast_rtp_instance_dtmf_mode_set(session_media->rtp, AST_RTP_DTMF_MODE_RFC2833);
ast_rtp_instance_set_prop(session_media->rtp, AST_RTP_PROPERTY_DTMF, 1);
- } else if (session->endpoint->dtmf == AST_SIP_DTMF_INBAND) {
+ } else if (session->dtmf == AST_SIP_DTMF_INBAND) {
ast_rtp_instance_dtmf_mode_set(session_media->rtp, AST_RTP_DTMF_MODE_INBAND);
}
@@ -269,7 +270,7 @@ static int create_rtp(struct ast_sip_session *session, struct ast_sip_session_me
}
static void get_codecs(struct ast_sip_session *session, const struct pjmedia_sdp_media *stream, struct ast_rtp_codecs *codecs,
- struct ast_sip_session_media *session_media)
+ struct ast_sip_session_media *session_media)
{
pjmedia_sdp_attr *attr;
pjmedia_sdp_rtpmap *rtpmap;
@@ -332,9 +333,19 @@ static void get_codecs(struct ast_sip_session *session, const struct pjmedia_sdp
}
}
}
- if (!tel_event && (session->endpoint->dtmf == AST_SIP_DTMF_AUTO)) {
+ if (!tel_event && (session->dtmf == AST_SIP_DTMF_AUTO)) {
ast_rtp_instance_dtmf_mode_set(session_media->rtp, AST_RTP_DTMF_MODE_INBAND);
}
+
+ if (session->dtmf == AST_SIP_DTMF_AUTO_INFO) {
+ if (tel_event) {
+ ast_rtp_instance_dtmf_mode_set(session_media->rtp, AST_RTP_DTMF_MODE_RFC2833);
+ } else {
+ ast_rtp_instance_dtmf_mode_set(session_media->rtp, AST_RTP_DTMF_MODE_NONE);
+ }
+ }
+
+
/* Get the packetization, if it exists */
if ((attr = pjmedia_sdp_media_find_attr2(stream, "ptime", NULL))) {
unsigned long framing = pj_strtoul(pj_strltrim(&attr->value));
@@ -401,7 +412,24 @@ static int set_caps(struct ast_sip_session *session, struct ast_sip_session_medi
ast_format_cap_append_from_cap(caps, ast_channel_nativeformats(session->channel),
AST_MEDIA_TYPE_UNKNOWN);
ast_format_cap_remove_by_type(caps, media_type);
- ast_format_cap_append_from_cap(caps, joint, media_type);
+
+ /*
+ * If we don't allow the sending codec to be changed on our side
+ * then get the best codec from the joint capabilities of the media
+ * type and use only that. This ensures the core won't start sending
+ * out a format that we aren't currently sending.
+ */
+ if (!session->endpoint->asymmetric_rtp_codec) {
+ struct ast_format *best;
+
+ best = ast_format_cap_get_best_by_type(joint, media_type);
+ if (best) {
+ ast_format_cap_append(caps, best, ast_format_cap_get_framing(joint));
+ ao2_ref(best, -1);
+ }
+ } else {
+ ast_format_cap_append_from_cap(caps, joint, media_type);
+ }
/*
* Apply the new formats to the channel, potentially changing
@@ -412,7 +440,8 @@ static int set_caps(struct ast_sip_session *session, struct ast_sip_session_medi
ast_set_read_format(session->channel, ast_channel_readformat(session->channel));
ast_set_write_format(session->channel, ast_channel_writeformat(session->channel));
}
- if ((session->endpoint->dtmf == AST_SIP_DTMF_AUTO)
+
+ if ( ((session->dtmf == AST_SIP_DTMF_AUTO) || (session->dtmf == AST_SIP_DTMF_AUTO_INFO) )
&& (ast_rtp_instance_dtmf_mode_get(session_media->rtp) == AST_RTP_DTMF_MODE_RFC2833)
&& (session->dsp)) {
dsp_features = ast_dsp_get_features(session->dsp);
@@ -1132,7 +1161,7 @@ static int create_outgoing_sdp_stream(struct ast_sip_session *session, struct as
pj_str_t stmp;
pjmedia_sdp_attr *attr;
int index = 0;
- int noncodec = (session->endpoint->dtmf == AST_SIP_DTMF_RFC_4733 || session->endpoint->dtmf == AST_SIP_DTMF_AUTO) ? AST_RTP_DTMF : 0;
+ int noncodec = (session->dtmf == AST_SIP_DTMF_RFC_4733 || session->dtmf == AST_SIP_DTMF_AUTO || session->dtmf == AST_SIP_DTMF_AUTO_INFO) ? AST_RTP_DTMF : 0;
int min_packet_size = 0, max_packet_size = 0;
int rtp_code;
RAII_VAR(struct ast_format_cap *, caps, NULL, ao2_cleanup);
@@ -1488,12 +1517,11 @@ 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 (transport_state->localnet
- && ast_apply_ha(transport_state->localnet, &addr) == AST_SENSE_ALLOW) {
+ if (ast_sip_transport_is_local(transport_state, &addr)) {
return;
}
- ast_debug(5, "Setting media address to %s\n", transport->external_media_address);
- pj_strdup2(tdata->pool, &stream->conn->addr, transport->external_media_address);
+ ast_debug(5, "Setting media address to %s\n", ast_sockaddr_stringify_host(&transport_state->external_media_address));
+ pj_strdup2(tdata->pool, &stream->conn->addr, ast_sockaddr_stringify_host(&transport_state->external_media_address));
}
/*! \brief Function which stops the RTP instance */
diff --git a/res/res_pjsip_session.c b/res/res_pjsip_session.c
index 1fec089f8..ab6fce2c8 100644
--- a/res/res_pjsip_session.c
+++ b/res/res_pjsip_session.c
@@ -968,6 +968,46 @@ int ast_sip_session_refresh(struct ast_sip_session *session,
return 0;
}
+int ast_sip_session_regenerate_answer(struct ast_sip_session *session,
+ ast_sip_session_sdp_creation_cb on_sdp_creation)
+{
+ pjsip_inv_session *inv_session = session->inv_session;
+ pjmedia_sdp_session *new_answer = NULL;
+ const pjmedia_sdp_session *previous_offer = NULL;
+
+ /* The SDP answer can only be regenerated if it is still pending to be sent */
+ if (!inv_session->neg || (pjmedia_sdp_neg_get_state(inv_session->neg) != PJMEDIA_SDP_NEG_STATE_REMOTE_OFFER &&
+ pjmedia_sdp_neg_get_state(inv_session->neg) != PJMEDIA_SDP_NEG_STATE_WAIT_NEGO)) {
+ ast_log(LOG_WARNING, "Requested to regenerate local SDP answer for channel '%s' but negotiation in state '%s'\n",
+ ast_channel_name(session->channel), pjmedia_sdp_neg_state_str(pjmedia_sdp_neg_get_state(inv_session->neg)));
+ return -1;
+ }
+
+ pjmedia_sdp_neg_get_neg_remote(inv_session->neg, &previous_offer);
+ if (pjmedia_sdp_neg_get_state(inv_session->neg) == PJMEDIA_SDP_NEG_STATE_WAIT_NEGO) {
+ /* Transition the SDP negotiator back to when it received the remote offer */
+ pjmedia_sdp_neg_negotiate(inv_session->pool, inv_session->neg, 0);
+ pjmedia_sdp_neg_set_remote_offer(inv_session->pool, inv_session->neg, previous_offer);
+ }
+
+ new_answer = create_local_sdp(inv_session, session, previous_offer);
+ if (!new_answer) {
+ ast_log(LOG_WARNING, "Could not create a new local SDP answer for channel '%s'\n",
+ ast_channel_name(session->channel));
+ return -1;
+ }
+
+ if (on_sdp_creation) {
+ if (on_sdp_creation(session, new_answer)) {
+ return -1;
+ }
+ }
+
+ pjsip_inv_set_sdp_answer(inv_session, new_answer);
+
+ return 0;
+}
+
void ast_sip_session_send_response(struct ast_sip_session *session, pjsip_tx_data *tdata)
{
handle_outgoing_response(session, tdata);
@@ -1481,6 +1521,8 @@ struct ast_sip_session *ast_sip_session_alloc(struct ast_sip_endpoint *endpoint,
session->contact = ao2_bump(contact);
session->inv_session = inv_session;
+ session->dtmf = endpoint->dtmf;
+
if (add_supplements(session)) {
/* Release the ref held by session->inv_session */
ao2_ref(session, -1);
@@ -1903,6 +1945,9 @@ int ast_sip_session_defer_termination(struct ast_sip_session *session)
session->defer_terminate = 1;
+ session->defer_end = 1;
+ session->ended_while_deferred = 0;
+
session->scheduled_termination.id = 0;
ao2_ref(session, +1);
session->scheduled_termination.user_data = session;
@@ -1940,6 +1985,7 @@ void ast_sip_session_defer_termination_cancel(struct ast_sip_session *session)
/* Already canceled or timer fired. */
return;
}
+
session->defer_terminate = 0;
if (session->terminate_while_deferred) {
@@ -1951,6 +1997,22 @@ void ast_sip_session_defer_termination_cancel(struct ast_sip_session *session)
sip_session_defer_termination_stop_timer(session);
}
+void ast_sip_session_end_if_deferred(struct ast_sip_session *session)
+{
+ if (!session->defer_end) {
+ return;
+ }
+
+ session->defer_end = 0;
+
+ if (session->ended_while_deferred) {
+ /* Complete the session end started by the remote hangup. */
+ ast_debug(3, "Ending session (%p) after being deferred\n", session);
+ session->ended_while_deferred = 0;
+ session_end(session);
+ }
+}
+
struct ast_sip_session *ast_sip_dialog_get_session(pjsip_dialog *dlg)
{
pjsip_inv_session *inv_session = pjsip_dlg_get_inv_session(dlg);
@@ -2532,6 +2594,11 @@ static int session_end(void *vsession)
iter->session_end(session);
}
}
+
+ /* Release any media resources. */
+ ao2_cleanup(session->media);
+ session->media = NULL;
+
return 0;
}
@@ -2636,6 +2703,12 @@ static void session_inv_on_state_changed(pjsip_inv_session *inv, pjsip_event *e)
}
if (inv->state == PJSIP_INV_STATE_DISCONNECTED) {
+ if (session->defer_end) {
+ ast_debug(3, "Deferring session (%p) end\n", session);
+ session->ended_while_deferred = 1;
+ return;
+ }
+
if (ast_sip_push_task(session->serializer, session_end, session)) {
/* Do it anyway even though this is not the right thread. */
session_end(session);
@@ -3122,11 +3195,9 @@ 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 (!transport_state->localnet
- || (transport_state->localnet
- && ast_apply_ha(transport_state->localnet, &addr) == AST_SENSE_ALLOW)) {
- ast_debug(5, "Setting external media address to %s\n", transport->external_media_address);
- pj_strdup2(tdata->pool, &sdp->conn->addr, transport->external_media_address);
+ if (ast_sip_transport_is_nonlocal(transport_state, &addr)) {
+ ast_debug(5, "Setting external media address to %s\n", ast_sockaddr_stringify_host(&transport_state->external_media_address));
+ pj_strdup2(tdata->pool, &sdp->conn->addr, ast_sockaddr_stringify_host(&transport_state->external_media_address));
}
}
diff --git a/res/res_pjsip_session.exports.in b/res/res_pjsip_session.exports.in
index a39485e66..5bc0bf40e 100644
--- a/res/res_pjsip_session.exports.in
+++ b/res/res_pjsip_session.exports.in
@@ -3,6 +3,7 @@
LINKER_SYMBOL_PREFIXast_sip_session_terminate;
LINKER_SYMBOL_PREFIXast_sip_session_defer_termination;
LINKER_SYMBOL_PREFIXast_sip_session_defer_termination_cancel;
+ LINKER_SYMBOL_PREFIXast_sip_session_end_if_deferred;
LINKER_SYMBOL_PREFIXast_sip_session_register_sdp_handler;
LINKER_SYMBOL_PREFIXast_sip_session_unregister_sdp_handler;
LINKER_SYMBOL_PREFIXast_sip_session_register_supplement;
@@ -13,6 +14,7 @@
LINKER_SYMBOL_PREFIXast_sip_session_remove_datastore;
LINKER_SYMBOL_PREFIXast_sip_session_get_identity;
LINKER_SYMBOL_PREFIXast_sip_session_refresh;
+ LINKER_SYMBOL_PREFIXast_sip_session_regenerate_answer;
LINKER_SYMBOL_PREFIXast_sip_session_send_response;
LINKER_SYMBOL_PREFIXast_sip_session_send_request;
LINKER_SYMBOL_PREFIXast_sip_session_create_invite;
diff --git a/res/res_pjsip_t38.c b/res/res_pjsip_t38.c
index 0ce6474e3..58da6a080 100644
--- a/res/res_pjsip_t38.c
+++ b/res/res_pjsip_t38.c
@@ -278,7 +278,7 @@ static int t38_reinvite_sdp_cb(struct ast_sip_session *session, pjmedia_sdp_sess
/* Move the image media stream to the front and have it as the only stream, pjmedia will fill in
* dummy streams for the rest
*/
- for (stream = 0; stream < sdp->media_count++; ++stream) {
+ for (stream = 0; stream < sdp->media_count; ++stream) {
if (!pj_strcmp2(&sdp->media[stream]->desc.media, "image")) {
sdp->media[0] = sdp->media[stream];
sdp->media_count = 1;
@@ -294,21 +294,22 @@ static int t38_reinvite_response_cb(struct ast_sip_session *session, pjsip_rx_da
{
struct pjsip_status_line status = rdata->msg_info.msg->line.status;
struct t38_state *state;
- RAII_VAR(struct ast_sip_session_media *, session_media, NULL, ao2_cleanup);
+ struct ast_sip_session_media *session_media = NULL;
if (status.code == 100) {
return 0;
}
- if (!(state = t38_state_get_or_alloc(session)) ||
+ if (!session->channel || !(state = t38_state_get_or_alloc(session)) ||
!(session_media = ao2_find(session->media, "image", OBJ_KEY))) {
ast_log(LOG_WARNING, "Received response to T.38 re-invite on '%s' but state unavailable\n",
- ast_channel_name(session->channel));
+ session->channel ? ast_channel_name(session->channel) : "unknown channel");
return 0;
}
t38_change_state(session, session_media, state, (status.code == 200) ? T38_ENABLED : T38_REJECTED);
+ ao2_cleanup(session_media);
return 0;
}
@@ -403,16 +404,21 @@ static int t38_interpret_parameters(void *obj)
static struct ast_frame *t38_framehook_write(struct ast_channel *chan,
struct ast_sip_session *session, struct ast_frame *f)
{
- if (f->frametype == AST_FRAME_CONTROL && f->subclass.integer == AST_CONTROL_T38_PARAMETERS &&
- session->endpoint->media.t38.enabled) {
- struct t38_parameters_task_data *data = t38_parameters_task_data_alloc(session, f);
+ if (f->frametype == AST_FRAME_CONTROL && f->subclass.integer == AST_CONTROL_T38_PARAMETERS) {
+ if (session->endpoint->media.t38.enabled) {
+ struct t38_parameters_task_data *data = t38_parameters_task_data_alloc(session, f);
- if (!data) {
- return f;
- }
+ if (!data) {
+ return f;
+ }
- if (ast_sip_push_task(session->serializer, t38_interpret_parameters, data)) {
- ao2_ref(data, -1);
+ if (ast_sip_push_task(session->serializer, t38_interpret_parameters, data)) {
+ ao2_ref(data, -1);
+ }
+ } else {
+ struct ast_control_t38_parameters parameters = { .request_response = AST_T38_REFUSED, };
+ ast_debug(2, "T.38 support not enabled, rejecting T.38 control packet\n");
+ ast_queue_control_data(session->channel, AST_CONTROL_T38_PARAMETERS, &parameters, sizeof(parameters));
}
} else if (f->frametype == AST_FRAME_MODEM) {
struct ast_sip_session_media *session_media;
@@ -503,10 +509,7 @@ static void t38_attach_framehook(struct ast_sip_session *session)
return;
}
- /* Only attach the framehook if t38 is enabled for the endpoint */
- if (!session->endpoint->media.t38.enabled) {
- return;
- }
+ /* Always attach the framehook so we can quickly reject */
ast_channel_lock(session->channel);
@@ -879,12 +882,11 @@ 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 (transport_state->localnet
- && ast_apply_ha(transport_state->localnet, &addr) == AST_SENSE_ALLOW) {
+ if (ast_sip_transport_is_local(transport_state, &addr)) {
return;
}
- ast_debug(5, "Setting media address to %s\n", transport->external_media_address);
- pj_strdup2(tdata->pool, &stream->conn->addr, transport->external_media_address);
+ ast_debug(5, "Setting media address to %s\n", ast_sockaddr_stringify_host(&transport_state->external_media_address));
+ pj_strdup2(tdata->pool, &stream->conn->addr, ast_sockaddr_stringify_host(&transport_state->external_media_address));
}
/*! \brief Function which destroys the UDPTL instance when session ends */
diff --git a/res/res_pjsip_transport_management.c b/res/res_pjsip_transport_management.c
index 3e129dc5f..eb92eb7a5 100644
--- a/res/res_pjsip_transport_management.c
+++ b/res/res_pjsip_transport_management.c
@@ -33,8 +33,8 @@
#include "asterisk/module.h"
#include "asterisk/astobj2.h"
-/*! \brief Number of buckets for keepalive transports */
-#define TRANSPORTS_BUCKETS 53
+/*! \brief Number of buckets for monitored transports */
+#define TRANSPORTS_BUCKETS 127
#define IDLE_TIMEOUT (pjsip_cfg()->tsx.td)
@@ -53,9 +53,6 @@ static pthread_t keepalive_thread = AST_PTHREADT_NULL;
/*! \brief The global interval at which to send keepalives */
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 monitored */
struct monitored_transport {
/*! \brief The underlying PJSIP transport */
@@ -114,7 +111,7 @@ AST_THREADSTORAGE(desc_storage);
static int idle_sched_cb(const void *data)
{
- struct monitored_transport *keepalive = (struct monitored_transport *) data;
+ struct monitored_transport *monitored = (struct monitored_transport *) data;
if (!pj_thread_is_registered()) {
pj_thread_t *thread;
@@ -123,7 +120,7 @@ static int idle_sched_cb(const void *data)
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);
+ ao2_ref(monitored, -1);
return 0;
}
@@ -132,22 +129,22 @@ static int idle_sched_cb(const void *data)
pj_thread_register("Transport Monitor", *desc, &thread);
}
- if (!keepalive->sip_received) {
+ if (!monitored->sip_received) {
ast_log(LOG_NOTICE, "Shutting down transport '%s' since no request was received in %d seconds\n",
- keepalive->transport->info, IDLE_TIMEOUT / 1000);
- pjsip_transport_shutdown(keepalive->transport);
+ monitored->transport->info, IDLE_TIMEOUT / 1000);
+ pjsip_transport_shutdown(monitored->transport);
}
- ao2_ref(keepalive, -1);
+ ao2_ref(monitored, -1);
return 0;
}
/*! \brief Destructor for keepalive transport */
static void monitored_transport_destroy(void *obj)
{
- struct monitored_transport *keepalive = obj;
+ struct monitored_transport *monitored = obj;
- pjsip_transport_dec_ref(keepalive->transport);
+ pjsip_transport_dec_ref(monitored->transport);
}
/*! \brief Callback invoked when transport changes occur */
@@ -178,14 +175,13 @@ static void monitored_transport_state_callback(pjsip_transport *transport, pjsip
/* Let the scheduler inherit the reference from allocation */
if (ast_sched_add_variable(sched, IDLE_TIMEOUT, idle_sched_cb, monitored, 1) < 0) {
/* Uh Oh. Could not schedule the idle check. Kill the transport. */
- ao2_unlink(transports, monitored);
- ao2_ref(monitored, -1);
pjsip_transport_shutdown(transport);
+ } else {
+ /* monitored ref successfully passed to idle_sched_cb() */
+ break;
}
- } else {
- /* No scheduled task, so get rid of the allocation reference */
- ao2_ref(monitored, -1);
}
+ ao2_ref(monitored, -1);
break;
case PJSIP_TP_STATE_SHUTDOWN:
case PJSIP_TP_STATE_DISCONNECTED:
@@ -197,13 +193,12 @@ static void monitored_transport_state_callback(pjsip_transport *transport, pjsip
ao2_ref(transports, -1);
}
-
- /* Forward to the old state callback if present */
- if (tpmgr_state_callback) {
- tpmgr_state_callback(transport, state, info);
- }
}
+struct ast_sip_tpmgr_state_callback monitored_transport_reg = {
+ monitored_transport_state_callback,
+};
+
/*! \brief Hashing function for monitored transport */
static int monitored_transport_hash_fn(const void *obj, int flags)
{
@@ -327,16 +322,9 @@ static pjsip_module idle_monitor_module = {
static int load_module(void)
{
struct ao2_container *transports;
- pjsip_tpmgr *tpmgr;
CHECK_PJSIP_MODULE_LOADED();
- 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");
- return AST_MODULE_LOAD_DECLINE;
- }
-
transports = ao2_container_alloc(TRANSPORTS_BUCKETS, monitored_transport_hash_fn,
monitored_transport_cmp_fn);
if (!transports) {
@@ -363,8 +351,7 @@ static int load_module(void)
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_sip_transport_state_register(&monitored_transport_reg);
ast_sorcery_observer_add(ast_sip_get_sorcery(), "global", &keepalive_global_observer);
ast_sorcery_reload_object(ast_sip_get_sorcery(), "global");
@@ -375,8 +362,6 @@ static int load_module(void)
static int unload_module(void)
{
- pjsip_tpmgr *tpmgr;
-
if (keepalive_interval) {
keepalive_interval = 0;
if (keepalive_thread != AST_PTHREADT_NULL) {
@@ -388,10 +373,7 @@ static int unload_module(void)
ast_sorcery_observer_remove(ast_sip_get_sorcery(), "global", &keepalive_global_observer);
- tpmgr = pjsip_endpt_get_tpmgr(ast_sip_get_pjsip_endpoint());
- if (tpmgr) {
- pjsip_tpmgr_set_state_cb(tpmgr, tpmgr_state_callback);
- }
+ ast_sip_transport_state_unregister(&monitored_transport_reg);
ast_sip_unregister_service(&idle_monitor_module);
diff --git a/res/res_pjsip_transport_websocket.c b/res/res_pjsip_transport_websocket.c
index 1b9d616de..c04594fee 100644
--- a/res/res_pjsip_transport_websocket.c
+++ b/res/res_pjsip_transport_websocket.c
@@ -145,6 +145,7 @@ static int transport_create(void *data)
{
struct transport_create_data *create_data = data;
struct ws_transport *newtransport = NULL;
+ pjsip_tp_state_callback state_cb;
pjsip_endpoint *endpt = ast_sip_get_pjsip_endpoint();
struct pjsip_tpmgr *tpmgr = pjsip_endpt_get_tpmgr(endpt);
@@ -161,6 +162,10 @@ static int transport_create(void *data)
goto on_error;
}
+ /* Give websocket transport a unique name for its lifetime */
+ snprintf(newtransport->transport.obj_name, PJ_MAX_OBJ_NAME, "ws%p",
+ &newtransport->transport);
+
newtransport->transport.endpt = endpt;
if (!(pool = pjsip_endpt_create_pool(endpt, "ws", 512, 512))) {
@@ -219,6 +224,7 @@ static int transport_create(void *data)
newtransport->transport.flag = pjsip_transport_get_flag_from_type((pjsip_transport_type_e)newtransport->transport.key.type);
newtransport->transport.info = (char *)pj_pool_alloc(newtransport->transport.pool, 64);
+ newtransport->transport.dir = PJSIP_TP_DIR_INCOMING;
newtransport->transport.tpmgr = tpmgr;
newtransport->transport.send_msg = &ws_send_msg;
newtransport->transport.destroy = &ws_destroy;
@@ -242,6 +248,16 @@ static int transport_create(void *data)
}
create_data->transport = newtransport;
+
+ /* Notify application of transport state */
+ state_cb = pjsip_tpmgr_get_state_cb(newtransport->transport.tpmgr);
+ if (state_cb) {
+ pjsip_transport_state_info state_info;
+
+ memset(&state_info, 0, sizeof(state_info));
+ state_cb(&newtransport->transport, PJSIP_TP_STATE_CONNECTED, &state_info);
+ }
+
return 0;
on_error:
@@ -302,10 +318,14 @@ static int get_write_timeout(void)
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);
+ if (!transport) {
+ continue;
+ }
ast_debug(5, "Found %s transport with write timeout: %d\n",
transport->type == AST_TRANSPORT_WS ? "WS" : "WSS",
transport->write_timeout);
@@ -361,6 +381,7 @@ static void websocket_cb(struct ast_websocket *session, struct ast_variable *par
if (ast_sip_push_task_synchronous(serializer, transport_create, &create_data)) {
ast_log(LOG_ERROR, "Could not create WebSocket transport.\n");
+ ast_taskprocessor_unreference(serializer);
ast_websocket_unref(session);
return;
}
diff --git a/res/res_pjsip_xpidf_body_generator.c b/res/res_pjsip_xpidf_body_generator.c
index 298235cbc..0977159ee 100644
--- a/res/res_pjsip_xpidf_body_generator.c
+++ b/res/res_pjsip_xpidf_body_generator.c
@@ -63,7 +63,7 @@ static int xpidf_generate_body_content(void *body, void *data)
pj_xml_node *msnsubstatus;
ast_sip_presence_exten_state_to_str(state_data->exten_state, &statestring,
- &pidfstate, &pidfnote, &local_state);
+ &pidfstate, &pidfnote, &local_state, 0);
ast_sip_presence_xml_find_node_attr(state_data->pool, pres, "atom", "id",
&atom, &attr);
diff --git a/res/res_rtp_asterisk.c b/res/res_rtp_asterisk.c
index f75482f7f..77027aafe 100644
--- a/res/res_rtp_asterisk.c
+++ b/res/res_rtp_asterisk.c
@@ -218,8 +218,9 @@ static AST_RWLIST_HEAD_STATIC(host_candidates, ast_ice_host_candidate);
/*! \brief RTP learning mode tracking information */
struct rtp_learning_info {
- int max_seq; /*!< The highest sequence number received */
- int packets; /*!< The number of remaining packets before the source is accepted */
+ int max_seq; /*!< The highest sequence number received */
+ int packets; /*!< The number of remaining packets before the source is accepted */
+ struct timeval received; /*!< The time of the last received packet */
};
#ifdef HAVE_OPENSSL_SRTP
@@ -302,6 +303,7 @@ struct ast_rtp {
void *data;
struct ast_rtcp *rtcp;
struct ast_rtp *bridged; /*!< Who we are Packet bridged to */
+ unsigned int asymmetric_codec; /*!< Indicate if asymmetric send/receive codecs are allowed */
enum strict_rtp_state strict_rtp_state; /*!< Current state that strict RTP protection is in */
struct ast_sockaddr strict_rtp_address; /*!< Remote address information for strict RTP purposes */
@@ -311,7 +313,6 @@ struct ast_rtp {
* but these are in place to keep learning mode sequence values sealed from their normal counterparts.
*/
struct rtp_learning_info rtp_source_learn; /* Learning mode track for the expected RTP source */
- struct rtp_learning_info alt_source_learn; /* Learning mode tracking for a new RTP source after one has been chosen */
struct rtp_red *red;
@@ -319,6 +320,7 @@ struct ast_rtp {
ast_cond_t cond; /*!< ICE/TURN condition for signaling */
struct ice_wrap *ice; /*!< ao2 wrapped ICE session */
+ enum ast_rtp_ice_role role; /*!< Our role in ICE negotiation */
pj_turn_sock *turn_rtp; /*!< RTP TURN relay */
pj_turn_sock *turn_rtcp; /*!< RTCP TURN relay */
pj_turn_state_t turn_state; /*!< Current state of the TURN relay session */
@@ -677,11 +679,41 @@ static void ice_wrap_dtor(void *vdoomed)
}
}
+static void ast2pj_rtp_ice_role(enum ast_rtp_ice_role ast_role, enum pj_ice_sess_role *pj_role)
+{
+ switch (ast_role) {
+ case AST_RTP_ICE_ROLE_CONTROLLED:
+ *pj_role = PJ_ICE_SESS_ROLE_CONTROLLED;
+ break;
+ case AST_RTP_ICE_ROLE_CONTROLLING:
+ *pj_role = PJ_ICE_SESS_ROLE_CONTROLLING;
+ break;
+ }
+}
+
+static void pj2ast_rtp_ice_role(enum pj_ice_sess_role pj_role, enum ast_rtp_ice_role *ast_role)
+{
+ switch (pj_role) {
+ case PJ_ICE_SESS_ROLE_CONTROLLED:
+ *ast_role = AST_RTP_ICE_ROLE_CONTROLLED;
+ return;
+ case PJ_ICE_SESS_ROLE_CONTROLLING:
+ *ast_role = AST_RTP_ICE_ROLE_CONTROLLING;
+ return;
+ case PJ_ICE_SESS_ROLE_UNKNOWN:
+ /* Don't change anything */
+ return;
+ default:
+ /* If we aren't explicitly handling something, it's a bug */
+ ast_assert(0);
+ return;
+ }
+}
+
/*! \pre instance is locked */
static int ice_reset_session(struct ast_rtp_instance *instance)
{
struct ast_rtp *rtp = ast_rtp_instance_get_data(instance);
- pj_ice_sess_role role = rtp->ice->real_ice->role;
int res;
ast_debug(3, "Resetting ICE for RTP instance '%p'\n", instance);
@@ -693,7 +725,9 @@ static int ice_reset_session(struct ast_rtp_instance *instance)
ast_debug(3, "Recreating ICE session %s (%d) for RTP instance '%p'\n", ast_sockaddr_stringify(&rtp->ice_original_rtp_addr), rtp->ice_port, instance);
res = ice_create(instance, &rtp->ice_original_rtp_addr, rtp->ice_port, 1);
if (!res) {
- /* Preserve the role that the old ICE session used */
+ /* Use the current expected role for the ICE session */
+ enum pj_ice_sess_role role = PJ_ICE_SESS_ROLE_UNKNOWN;
+ ast2pj_rtp_ice_role(rtp->role, &role);
pj_ice_sess_change_role(rtp->ice->real_ice, role);
}
@@ -765,6 +799,8 @@ static void ast_rtp_ice_start(struct ast_rtp_instance *instance)
ast_debug(3, "Proposed == active candidates for RTP instance '%p'\n", instance);
ao2_cleanup(rtp->ice_proposed_remote_candidates);
rtp->ice_proposed_remote_candidates = NULL;
+ /* If this ICE session is being preserved then go back to the role it currently is */
+ pj2ast_rtp_ice_role(rtp->ice->real_ice->role, &rtp->role);
return;
}
@@ -938,10 +974,7 @@ static void ast_rtp_ice_set_role(struct ast_rtp_instance *instance, enum ast_rtp
return;
}
- pj_thread_register_check();
-
- pj_ice_sess_change_role(rtp->ice->real_ice, role == AST_RTP_ICE_ROLE_CONTROLLED ?
- PJ_ICE_SESS_ROLE_CONTROLLED : PJ_ICE_SESS_ROLE_CONTROLLING);
+ rtp->role = role;
}
/*! \pre instance is locked */
@@ -1291,6 +1324,8 @@ static void ast_rtp_ice_turn_request(struct ast_rtp_instance *instance, enum ast
pj_turn_session_info info;
struct ast_sockaddr local, loop;
pj_status_t status;
+ pj_turn_sock_cfg turn_sock_cfg;
+ struct ice_wrap *ice;
ast_rtp_instance_get_local_address(instance, &local);
if (ast_sockaddr_is_ipv4(&local)) {
@@ -1353,11 +1388,20 @@ static void ast_rtp_ice_turn_request(struct ast_rtp_instance *instance, enum ast
pj_stun_config_init(&stun_config, &cachingpool.factory, 0, rtp->ioqueue->ioqueue, rtp->ioqueue->timerheap);
+ /* Use ICE session group lock for TURN session to avoid deadlock */
+ pj_turn_sock_cfg_default(&turn_sock_cfg);
+ ice = rtp->ice;
+ if (ice) {
+ turn_sock_cfg.grp_lock = ice->real_ice->grp_lock;
+ ao2_ref(ice, +1);
+ }
+
/* Release the instance lock to avoid deadlock with PJPROJECT group lock */
ao2_unlock(instance);
status = pj_turn_sock_create(&stun_config,
ast_sockaddr_is_ipv4(&addr) ? pj_AF_INET() : pj_AF_INET6(), conn_type,
- turn_cb, NULL, instance, turn_sock);
+ turn_cb, &turn_sock_cfg, instance, turn_sock);
+ ao2_cleanup(ice);
if (status != PJ_SUCCESS) {
ast_log(LOG_WARNING, "Could not create a TURN client socket\n");
ao2_lock(instance);
@@ -2522,6 +2566,17 @@ static int __rtp_recvfrom(struct ast_rtp_instance *instance, void *buf, size_t s
return -1;
}
if (!rtp->passthrough) {
+ /* If a unidirectional ICE negotiation occurs then lock on to the source of the
+ * ICE traffic and use it as the target. This will occur if the remote side only
+ * wants to receive media but never send to us.
+ */
+ if (!rtp->ice_active_remote_candidates && !rtp->ice_proposed_remote_candidates) {
+ if (rtcp) {
+ ast_sockaddr_copy(&rtp->rtcp->them, sa);
+ } else {
+ ast_rtp_instance_set_remote_address(instance, sa);
+ }
+ }
return 0;
}
rtp->passthrough = 0;
@@ -2565,17 +2620,22 @@ static int __rtp_sendto(struct ast_rtp_instance *instance, void *buf, size_t siz
#ifdef HAVE_PJPROJECT
if (rtp->ice) {
+ enum ast_rtp_ice_component_type component = rtcp ? AST_RTP_ICE_COMPONENT_RTCP : AST_RTP_ICE_COMPONENT_RTP;
pj_status_t status;
struct ice_wrap *ice;
+ /* If RTCP is sharing the same socket then use the same component */
+ if (rtcp && rtp->rtcp->s == rtp->s) {
+ component = AST_RTP_ICE_COMPONENT_RTP;
+ }
+
pj_thread_register_check();
/* Release the instance lock to avoid deadlock with PJPROJECT group lock */
ice = rtp->ice;
ao2_ref(ice, +1);
ao2_unlock(instance);
- status = pj_ice_sess_send_data(ice->real_ice,
- rtcp ? AST_RTP_ICE_COMPONENT_RTCP : AST_RTP_ICE_COMPONENT_RTP, temp, len);
+ status = pj_ice_sess_send_data(ice->real_ice, component, temp, len);
ao2_ref(ice, -1);
ao2_lock(instance);
if (status == PJ_SUCCESS) {
@@ -2695,6 +2755,7 @@ static void rtp_learning_seq_init(struct rtp_learning_info *info, uint16_t seq)
{
info->max_seq = seq - 1;
info->packets = learning_min_sequential;
+ memset(&info->received, 0, sizeof(info->received));
}
/*!
@@ -2709,6 +2770,13 @@ static void rtp_learning_seq_init(struct rtp_learning_info *info, uint16_t seq)
*/
static int rtp_learning_rtp_seq_update(struct rtp_learning_info *info, uint16_t seq)
{
+ if (!ast_tvzero(info->received) && ast_tvdiff_ms(ast_tvnow(), info->received) < 5) {
+ /* During the probation period the minimum amount of media we'll accept is
+ * 10ms so give a reasonable 5ms buffer just in case we get it sporadically.
+ */
+ return 1;
+ }
+
if (seq == info->max_seq + 1) {
/* packet is in sequence */
info->packets--;
@@ -2717,6 +2785,7 @@ static int rtp_learning_rtp_seq_update(struct rtp_learning_info *info, uint16_t
info->packets = learning_min_sequential - 1;
}
info->max_seq = seq;
+ info->received = ast_tvnow();
return (info->packets == 0);
}
@@ -2991,10 +3060,9 @@ static int ast_rtp_new(struct ast_rtp_instance *instance,
/* Set default parameters on the newly created RTP structure */
rtp->ssrc = ast_random();
rtp->seqno = ast_random() & 0x7fff;
- rtp->strict_rtp_state = (strictrtp ? STRICT_RTP_LEARN : STRICT_RTP_OPEN);
+ rtp->strict_rtp_state = (strictrtp ? STRICT_RTP_CLOSED : STRICT_RTP_OPEN);
if (strictrtp) {
rtp_learning_seq_init(&rtp->rtp_source_learn, (uint16_t)rtp->seqno);
- rtp_learning_seq_init(&rtp->alt_source_learn, (uint16_t)rtp->seqno);
}
/* Create a new socket for us to listen on and use */
@@ -3443,7 +3511,7 @@ static void ast_rtp_change_source(struct ast_rtp_instance *instance)
ast_debug(3, "Changing ssrc for SRTP from %u to %u\n", rtp->ssrc, ssrc);
res_srtp->change_source(srtp, rtp->ssrc, ssrc);
if (rtcp_srtp != srtp) {
- res_srtp->change_source(srtp, rtp->ssrc, ssrc);
+ res_srtp->change_source(rtcp_srtp, rtp->ssrc, ssrc);
}
}
@@ -4535,17 +4603,6 @@ static struct ast_frame *ast_rtcp_interpret(struct ast_rtp_instance *instance, c
packetwords = size / 4;
- if (ast_rtp_instance_get_prop(instance, AST_RTP_PROPERTY_NAT)) {
- /* Send to whoever sent to us */
- if (ast_sockaddr_cmp(&rtp->rtcp->them, addr)) {
- ast_sockaddr_copy(&rtp->rtcp->them, addr);
- if (rtpdebug) {
- ast_debug(0, "RTCP NAT: Got RTCP from other end. Now sending to address %s\n",
- ast_sockaddr_stringify(&rtp->rtcp->them));
- }
- }
- }
-
ast_debug(1, "Got RTCP report of %zu bytes\n", size);
while (position < packetwords) {
@@ -4574,6 +4631,25 @@ static struct ast_frame *ast_rtcp_interpret(struct ast_rtp_instance *instance, c
return &ast_null_frame;
}
+ if ((rtp->strict_rtp_state != STRICT_RTP_OPEN) && (rtcp_report->ssrc != rtp->themssrc)) {
+ /* Skip over this RTCP record as it does not contain the correct SSRC */
+ position += (length + 1);
+ ast_debug(1, "%p -- Received RTCP report from %s, dropping due to strict RTP protection. Received SSRC '%u' but expected '%u'\n",
+ rtp, ast_sockaddr_stringify(addr), rtcp_report->ssrc, rtp->themssrc);
+ continue;
+ }
+
+ if (ast_rtp_instance_get_prop(instance, AST_RTP_PROPERTY_NAT)) {
+ /* Send to whoever sent to us */
+ if (ast_sockaddr_cmp(&rtp->rtcp->them, addr)) {
+ ast_sockaddr_copy(&rtp->rtcp->them, addr);
+ if (rtpdebug) {
+ ast_debug(0, "RTCP NAT: Got RTCP from other end. Now sending to address %s\n",
+ ast_sockaddr_stringify(&rtp->rtcp->them));
+ }
+ }
+ }
+
if (rtcp_debug_test_addr(addr)) {
ast_verbose("\n\nGot RTCP from %s\n",
ast_sockaddr_stringify(addr));
@@ -4795,9 +4871,6 @@ static int bridge_p2p_rtp_write(struct ast_rtp_instance *instance,
return -1;
}
- rtp->rxcount++;
- rtp->rxoctetcount += (len - hdrlen);
-
/* If the payload coming in is not one of the negotiated ones then send it to the core, this will cause formats to change and the bridge to break */
if (ast_rtp_codecs_find_payload_code(ast_rtp_instance_get_codecs(instance1), bridged_payload) == -1) {
ast_debug(1, "Unsupported payload type received \n");
@@ -4820,6 +4893,23 @@ static int bridge_p2p_rtp_write(struct ast_rtp_instance *instance,
return -1;
}
+
+ ao2_replace(rtp->lastrxformat, payload_type->format);
+ ao2_replace(bridged->lasttxformat, payload_type->format);
+
+ /*
+ * If bridged peer has already received rtp, perform the asymmetric codec check
+ * if that feature has been activated
+ */
+ if (!bridged->asymmetric_codec && bridged->lastrxformat != ast_format_none) {
+ if (ast_format_cmp(bridged->lasttxformat, bridged->lastrxformat) == AST_FORMAT_CMP_NOT_EQUAL) {
+ ast_debug(1, "Asymmetric RTP codecs detected (TX: %s, RX: %s) sending frame to core\n",
+ ast_format_get_name(bridged->lasttxformat),
+ ast_format_get_name(bridged->lastrxformat));
+ return -1;
+ }
+ }
+
/* If the marker bit has been explicitly set turn it on */
if (ast_test_flag(rtp, FLAG_NEED_MARKER_BIT)) {
mark = 1;
@@ -4980,39 +5070,37 @@ static struct ast_frame *ast_rtp_read(struct ast_rtp_instance *instance, int rtc
return &ast_null_frame;
}
+ /* If the version is not what we expected by this point then just drop the packet */
+ if (version != 2) {
+ return &ast_null_frame;
+ }
+
/* If strict RTP protection is enabled see if we need to learn the remote address or if we need to drop the packet */
if (rtp->strict_rtp_state == STRICT_RTP_LEARN) {
- ast_debug(1, "%p -- Probation learning mode pass with source address %s\n", rtp, ast_sockaddr_stringify(&addr));
- /* For now, we always copy the address. */
- ast_sockaddr_copy(&rtp->strict_rtp_address, &addr);
-
- /* Send the rtp and the seqno from header to rtp_learning_rtp_seq_update to see whether we can exit or not*/
- if (rtp_learning_rtp_seq_update(&rtp->rtp_source_learn, seqno)) {
- ast_debug(1, "%p -- Probation at seq %d with %d to go; discarding frame\n",
- rtp, rtp->rtp_source_learn.max_seq, rtp->rtp_source_learn.packets);
- return &ast_null_frame;
- }
-
- ast_verb(4, "%p -- Probation passed - setting RTP source address to %s\n", rtp, ast_sockaddr_stringify(&addr));
- rtp->strict_rtp_state = STRICT_RTP_CLOSED;
- }
- if (rtp->strict_rtp_state == STRICT_RTP_CLOSED) {
if (!ast_sockaddr_cmp(&rtp->strict_rtp_address, &addr)) {
- /* Always reset the alternate learning source */
- rtp_learning_seq_init(&rtp->alt_source_learn, seqno);
+ /* We are learning a new address but have received traffic from the existing address,
+ * accept it but reset the current learning for the new source so it only takes over
+ * once sufficient traffic has been received. */
+ rtp_learning_seq_init(&rtp->rtp_source_learn, seqno);
} else {
/* Start trying to learn from the new address. If we pass a probationary period with
* it, that means we've stopped getting RTP from the original source and we should
* switch to it.
*/
- if (rtp_learning_rtp_seq_update(&rtp->alt_source_learn, seqno)) {
+ if (rtp_learning_rtp_seq_update(&rtp->rtp_source_learn, seqno)) {
ast_debug(1, "%p -- Received RTP packet from %s, dropping due to strict RTP protection. Will switch to it in %d packets\n",
- rtp, ast_sockaddr_stringify(&addr), rtp->alt_source_learn.packets);
+ rtp, ast_sockaddr_stringify(&addr), rtp->rtp_source_learn.packets);
return &ast_null_frame;
}
- ast_verb(4, "%p -- Switching RTP source address to %s\n", rtp, ast_sockaddr_stringify(&addr));
ast_sockaddr_copy(&rtp->strict_rtp_address, &addr);
+
+ ast_verb(4, "%p -- Probation passed - setting RTP source address to %s\n", rtp, ast_sockaddr_stringify(&addr));
+ rtp->strict_rtp_state = STRICT_RTP_CLOSED;
}
+ } else if (rtp->strict_rtp_state == STRICT_RTP_CLOSED && ast_sockaddr_cmp(&rtp->strict_rtp_address, &addr)) {
+ ast_debug(1, "%p -- Received RTP packet from %s, dropping due to strict RTP protection.\n",
+ rtp, ast_sockaddr_stringify(&addr));
+ return &ast_null_frame;
}
/* If symmetric RTP is enabled see if the remote side is not what we expected and change where we are sending audio */
@@ -5033,18 +5121,6 @@ static struct ast_frame *ast_rtp_read(struct ast_rtp_instance *instance, int rtc
}
}
- /* If we are directly bridged to another instance send the audio directly out */
- instance1 = ast_rtp_instance_get_bridged(instance);
- if (instance1
- && !bridge_p2p_rtp_write(instance, instance1, rtpheader, res, hdrlen)) {
- return &ast_null_frame;
- }
-
- /* If the version is not what we expected by this point then just drop the packet */
- if (version != 2) {
- return &ast_null_frame;
- }
-
/* Pull out the various other fields we will need */
payloadtype = (seqno & 0x7f0000) >> 16;
padding = seqno & (1 << 29);
@@ -5144,6 +5220,28 @@ static struct ast_frame *ast_rtp_read(struct ast_rtp_instance *instance, int rtc
rtp->themssrc = ntohl(rtpheader[2]); /* Record their SSRC to put in future RR */
}
+
+ /* If we are directly bridged to another instance send the audio directly out,
+ * but only after updating core information about the received traffic so that
+ * outgoing RTCP reflects it.
+ */
+ instance1 = ast_rtp_instance_get_bridged(instance);
+ if (instance1
+ && !bridge_p2p_rtp_write(instance, instance1, rtpheader, res, hdrlen)) {
+ struct timeval rxtime;
+ struct ast_frame *f;
+
+ /* Update statistics for jitter so they are correct in RTCP */
+ calc_rxstamp(&rxtime, rtp, timestamp, mark);
+
+ /* When doing P2P we don't need to raise any frames about SSRC change to the core */
+ while ((f = AST_LIST_REMOVE_HEAD(&frames, frame_list)) != NULL) {
+ ast_frfree(f);
+ }
+
+ return &ast_null_frame;
+ }
+
if (rtp_debug_test_addr(&addr)) {
ast_verbose("Got RTP packet from %s (type %-2.2d, seq %-6.6u, ts %-6.6u, len %-6.6d)\n",
ast_sockaddr_stringify(&addr),
@@ -5472,6 +5570,8 @@ static void ast_rtp_prop_set(struct ast_rtp_instance *instance, enum ast_rtp_pro
rtp->rtcp = NULL;
}
}
+ } else if (property == AST_RTP_PROPERTY_ASYMMETRIC_CODEC) {
+ rtp->asymmetric_codec = value;
}
}
@@ -5518,7 +5618,11 @@ static void ast_rtp_remote_address_set(struct ast_rtp_instance *instance, struct
rtp->rxseqno = 0;
- if (strictrtp && rtp->strict_rtp_state != STRICT_RTP_OPEN) {
+ if (strictrtp && rtp->strict_rtp_state != STRICT_RTP_OPEN && !ast_sockaddr_isnull(addr) &&
+ ast_sockaddr_cmp(addr, &rtp->strict_rtp_address)) {
+ /* We only need to learn a new strict source address if we've been told the source is
+ * changing to something different.
+ */
rtp->strict_rtp_state = STRICT_RTP_LEARN;
rtp_learning_seq_init(&rtp->rtp_source_learn, rtp->seqno);
}
diff --git a/res/res_smdi.c b/res/res_smdi.c
index f4804c7cb..9a40227f2 100644
--- a/res/res_smdi.c
+++ b/res/res_smdi.c
@@ -610,13 +610,12 @@ static void *smdi_read(void *iface_p)
ast_debug(1, "Read a 'D' ... it's an MD message.\n");
- if (!(md_msg = ast_calloc(1, sizeof(*md_msg)))) {
+ md_msg = ao2_alloc(sizeof(*md_msg), NULL);
+ if (!md_msg) {
ao2_ref(iface, -1);
return NULL;
}
- md_msg = ao2_alloc(sizeof(*md_msg), NULL);
-
/* read the message desk number */
for (i = 0; i < sizeof(md_msg->mesg_desk_num) - 1; i++) {
md_msg->mesg_desk_num[i] = fgetc(iface->file);
@@ -712,13 +711,12 @@ static void *smdi_read(void *iface_p)
ast_debug(1, "Read a 'W', it's an MWI message. (No more debug coming for MWI messages)\n");
- if (!(mwi_msg = ast_calloc(1, sizeof(*mwi_msg)))) {
+ mwi_msg = ao2_alloc(sizeof(*mwi_msg), NULL);
+ if (!mwi_msg) {
ao2_ref(iface, -1);
return NULL;
}
- mwi_msg = ao2_alloc(sizeof(*mwi_msg), NULL);
-
/* discard the 'I' (from 'MWI') */
fgetc(iface->file);
diff --git a/res/res_stasis.c b/res/res_stasis.c
index 9d7bc4c24..b1cea3ad5 100644
--- a/res/res_stasis.c
+++ b/res/res_stasis.c
@@ -1057,8 +1057,18 @@ static void channel_stolen_cb(void *data, struct ast_channel *old_chan, struct a
{
struct stasis_app_control *control;
- /* find control */
- control = ao2_callback(app_controls, 0, masq_match_cb, old_chan);
+ /*
+ * At this point, old_chan is the channel pointer that is in Stasis() and
+ * has the unknown channel's name in it while new_chan is the channel pointer
+ * that is not in Stasis(), but has the guts of the channel that Stasis() knows
+ * about.
+ *
+ * Find and unlink control since the channel has a new name/uniqueid
+ * and its hash has changed. Since the channel is leaving stasis don't
+ * bother putting it back into the container. Nobody is going to
+ * remove it from the container later.
+ */
+ control = ao2_callback(app_controls, OBJ_UNLINK, masq_match_cb, old_chan);
if (!control) {
ast_log(LOG_ERROR, "Could not find control for masqueraded channel\n");
return;
@@ -1099,8 +1109,10 @@ static void channel_replaced_cb(void *data, struct ast_channel *old_chan, struct
return;
}
- /* find, unlink, and relink control since the channel has a new name and
- * its hash has likely changed */
+ /*
+ * Find, unlink, and relink control since the channel has a new
+ * name/uniqueid and its hash has changed.
+ */
control = ao2_callback(app_controls, OBJ_UNLINK, masq_match_cb, new_chan);
if (!control) {
ast_log(LOG_ERROR, "Could not find control for masquerading channel\n");
diff --git a/res/res_stasis_device_state.c b/res/res_stasis_device_state.c
index 29e75660c..51101dd7b 100644
--- a/res/res_stasis_device_state.c
+++ b/res/res_stasis_device_state.c
@@ -108,7 +108,6 @@ static int device_state_subscriptions_cmp(void *obj, void *arg, int flags)
static void device_state_subscription_destroy(void *obj)
{
struct device_state_subscription *sub = obj;
- sub->sub = stasis_unsubscribe_and_join(sub->sub);
ast_string_field_free_memory(sub);
}
@@ -154,6 +153,9 @@ static struct device_state_subscription *find_device_state_subscription(
static void remove_device_state_subscription(
struct device_state_subscription *sub)
{
+ if (sub->sub) {
+ sub->sub = stasis_unsubscribe_and_join(sub->sub);
+ }
ao2_unlink_flags(device_state_subscriptions, sub, OBJ_NOLOCK);
}
diff --git a/res/res_stasis_snoop.c b/res/res_stasis_snoop.c
index abdef6e46..da66894f6 100644
--- a/res/res_stasis_snoop.c
+++ b/res/res_stasis_snoop.c
@@ -74,6 +74,8 @@ struct stasis_app_snoop {
unsigned int whisper_active:1;
/*! \brief Uniqueid of the channel this snoop is snooping on */
char uniqueid[AST_MAX_UNIQUEID];
+ /*! \brief A frame of silence to use when the audiohook returns null */
+ struct ast_frame silence;
};
/*! \brief Destructor for snoop structure */
@@ -93,6 +95,11 @@ static void snoop_destroy(void *obj)
ast_audiohook_destroy(&snoop->whisper);
}
+ if (snoop->silence.data.ptr) {
+ ast_free(snoop->silence.data.ptr);
+ snoop->silence.data.ptr = NULL;
+ }
+
ast_free(snoop->app);
ast_channel_cleanup(snoop->chan);
@@ -199,7 +206,7 @@ static struct ast_frame *snoop_read(struct ast_channel *chan)
frame = ast_audiohook_read_frame(&snoop->spy, snoop->spy_samples, snoop->spy_direction, snoop->spy_format);
ast_audiohook_unlock(&snoop->spy);
- return frame ? frame : &ast_null_frame;
+ return frame ? frame : &snoop->silence;
}
/*! \brief Callback function for hanging up a Snoop channel */
@@ -385,6 +392,19 @@ struct ast_channel *stasis_app_control_snoop(struct ast_channel *chan,
snoop->spy_samples = ast_format_get_sample_rate(snoop->spy_format) / (1000 / SNOOP_INTERVAL);
snoop->spy_active = 1;
+
+ snoop->silence.frametype = AST_FRAME_VOICE,
+ snoop->silence.datalen = snoop->spy_samples * sizeof(uint16_t),
+ snoop->silence.samples = snoop->spy_samples,
+ snoop->silence.mallocd = 0,
+ snoop->silence.offset = 0,
+ snoop->silence.src = __PRETTY_FUNCTION__,
+ snoop->silence.subclass.format = snoop->spy_format,
+ snoop->silence.data.ptr = ast_calloc(snoop->spy_samples, sizeof(uint16_t));
+ if (!snoop->silence.data.ptr) {
+ ast_hangup(snoop->chan);
+ return NULL;
+ }
}
/* If whispering is enabled set up the audiohook */
diff --git a/res/res_xmpp.c b/res/res_xmpp.c
index 95d3cc009..d8487f5ba 100644
--- a/res/res_xmpp.c
+++ b/res/res_xmpp.c
@@ -61,6 +61,7 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
#include "asterisk/manager.h"
#include "asterisk/cli.h"
#include "asterisk/config_options.h"
+#include "asterisk/json.h"
/*** DOCUMENTATION
<application name="JabberSend" language="en_US" module="res_xmpp">
@@ -323,6 +324,15 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
<configOption name="secret">
<synopsis>XMPP password</synopsis>
</configOption>
+ <configOption name="refresh_token">
+ <synopsis>Google OAuth 2.0 refresh token</synopsis>
+ </configOption>
+ <configOption name="oauth_clientid">
+ <synopsis>Google OAuth 2.0 application's client id</synopsis>
+ </configOption>
+ <configOption name="oauth_secret">
+ <synopsis>Google OAuth 2.0 application's secret</synopsis>
+ </configOption>
<configOption name="serverhost">
<synopsis>Route to server, e.g. talk.google.com</synopsis>
</configOption>
@@ -461,6 +471,9 @@ struct ast_xmpp_client_config {
AST_STRING_FIELD(name); /*!< Name of the client connection */
AST_STRING_FIELD(user); /*!< Username to use for authentication */
AST_STRING_FIELD(password); /*!< Password to use for authentication */
+ AST_STRING_FIELD(refresh_token); /*!< Refresh token to use for OAuth authentication */
+ AST_STRING_FIELD(oauth_clientid); /*!< Client ID to use for OAuth authentication */
+ AST_STRING_FIELD(oauth_secret); /*!< Secret to use for OAuth authentication */
AST_STRING_FIELD(server); /*!< Server hostname */
AST_STRING_FIELD(statusmsg); /*!< Status message for presence */
AST_STRING_FIELD(pubsubnode); /*!< Pubsub node */
@@ -529,6 +542,7 @@ static ast_cond_t message_received_condition;
static ast_mutex_t messagelock;
static int xmpp_client_config_post_apply(void *obj, void *arg, int flags);
+static int fetch_access_token(struct ast_xmpp_client_config *cfg);
/*! \brief Destructor function for configuration */
static void ast_xmpp_client_config_destructor(void *obj)
@@ -761,12 +775,16 @@ static int xmpp_config_prelink(void *newitem)
if (ast_strlen_zero(clientcfg->user)) {
ast_log(LOG_ERROR, "No user specified on client '%s'\n", clientcfg->name);
return -1;
- } else if (ast_strlen_zero(clientcfg->password)) {
- ast_log(LOG_ERROR, "No password specified on client '%s'\n", clientcfg->name);
+ } else if (ast_strlen_zero(clientcfg->password) && ast_strlen_zero(clientcfg->refresh_token)) {
+ ast_log(LOG_ERROR, "No password or refresh_token specified on client '%s'\n", clientcfg->name);
return -1;
} else if (ast_strlen_zero(clientcfg->server)) {
ast_log(LOG_ERROR, "No server specified on client '%s'\n", clientcfg->name);
return -1;
+ } else if (!ast_strlen_zero(clientcfg->refresh_token) &&
+ (ast_strlen_zero(clientcfg->oauth_clientid) || ast_strlen_zero(clientcfg->oauth_secret))) {
+ ast_log(LOG_ERROR, "No oauth_clientid or oauth_secret specified, so client '%s' can't be used\n", clientcfg->name);
+ return -1;
}
/* If this is a new connection force a reconnect */
@@ -778,6 +796,9 @@ static int xmpp_config_prelink(void *newitem)
/* If any configuration options are changing that would require reconnecting set the bit so we will do so if possible */
if (strcmp(clientcfg->user, oldclientcfg->user) ||
strcmp(clientcfg->password, oldclientcfg->password) ||
+ strcmp(clientcfg->refresh_token, oldclientcfg->refresh_token) ||
+ strcmp(clientcfg->oauth_clientid, oldclientcfg->oauth_clientid) ||
+ strcmp(clientcfg->oauth_secret, oldclientcfg->oauth_secret) ||
strcmp(clientcfg->server, oldclientcfg->server) ||
(clientcfg->port != oldclientcfg->port) ||
(ast_test_flag(&clientcfg->flags, XMPP_COMPONENT) != ast_test_flag(&oldclientcfg->flags, XMPP_COMPONENT)) ||
@@ -2786,7 +2807,13 @@ static int xmpp_client_authenticate_sasl(struct ast_xmpp_client *client, struct
}
iks_insert_attrib(auth, "xmlns", IKS_NS_XMPP_SASL);
- iks_insert_attrib(auth, "mechanism", "PLAIN");
+ if (!ast_strlen_zero(cfg->refresh_token)) {
+ iks_insert_attrib(auth, "mechanism", "X-OAUTH2");
+ iks_insert_attrib(auth, "auth:service", "oauth2");
+ iks_insert_attrib(auth, "xmlns:auth", "http://www.google.com/talk/protocol/auth");
+ } else {
+ iks_insert_attrib(auth, "mechanism", "PLAIN");
+ }
if (strchr(client->jid->user, '/')) {
char *user = ast_strdupa(client->jid->user);
@@ -3285,28 +3312,28 @@ static int xmpp_ping_request(struct ast_xmpp_client *client, const char *to, con
{
iks *iq, *ping;
int res;
-
+
ast_debug(2, "JABBER: Sending Keep-Alive Ping for client '%s'\n", client->name);
if (!(iq = iks_new("iq")) || !(ping = iks_new("ping"))) {
iks_delete(iq);
return -1;
}
-
+
iks_insert_attrib(iq, "type", "get");
iks_insert_attrib(iq, "to", to);
iks_insert_attrib(iq, "from", from);
-
+
ast_xmpp_client_lock(client);
iks_insert_attrib(iq, "id", client->mid);
ast_xmpp_increment_mid(client->mid);
ast_xmpp_client_unlock(client);
-
+
iks_insert_attrib(ping, "xmlns", "urn:xmpp:ping");
iks_insert_node(iq, ping);
-
+
res = ast_xmpp_client_send(client, iq);
-
+
iks_delete(ping);
iks_delete(iq);
@@ -3627,6 +3654,13 @@ static int xmpp_client_reconnect(struct ast_xmpp_client *client)
return -1;
}
+ if (!ast_strlen_zero(clientcfg->refresh_token)) {
+ ast_debug(2, "Obtaining OAuth access token for client '%s'\n", client->name);
+ if (fetch_access_token(clientcfg)) {
+ return -1;
+ }
+ }
+
ast_xmpp_client_disconnect(client);
client->timeout = 50;
@@ -3643,7 +3677,7 @@ static int xmpp_client_reconnect(struct ast_xmpp_client *client)
/* Set socket timeout options */
setsockopt(iks_fd(client->parser), SOL_SOCKET, SO_RCVTIMEO, (char *)&tv,sizeof(struct timeval));
-
+
if (res == IKS_NET_NOCONN) {
ast_log(LOG_ERROR, "No XMPP connection available when trying to connect client '%s'\n", client->name);
return -1;
@@ -3728,7 +3762,7 @@ static int xmpp_client_receive(struct ast_xmpp_client *client, unsigned int time
/* Log the message here, because iksemel's logHook is
unaccessible */
xmpp_log_hook(client, buf, len, 1);
-
+
if(buf[0] == ' ') {
ast_debug(1, "JABBER: Detected Google Keep Alive. "
"Sending out Ping request for client '%s'\n", client->name);
@@ -3869,6 +3903,42 @@ static int xmpp_client_config_merge_buddies(void *obj, void *arg, int flags)
return 1;
}
+static int fetch_access_token(struct ast_xmpp_client_config *cfg)
+{
+ RAII_VAR(char *, cmd, NULL, ast_free);
+ char cBuf[1024] = "";
+ const char *url = "https://www.googleapis.com/oauth2/v3/token";
+ struct ast_json_error error;
+ RAII_VAR(struct ast_json *, jobj, NULL, ast_json_unref);
+
+ ast_asprintf(&cmd, "CURL(%s,client_id=%s&client_secret=%s&refresh_token=%s&grant_type=refresh_token)",
+ url, cfg->oauth_clientid, cfg->oauth_secret, cfg->refresh_token);
+
+ ast_debug(2, "Performing OAuth 2.0 authentication for client '%s' using command: %s\n",
+ cfg->name, cmd);
+
+ if (ast_func_read(NULL, cmd, cBuf, sizeof(cBuf) - 1)) {
+ ast_log(LOG_ERROR, "CURL is unavailable. This is required for OAuth 2.0 authentication of XMPP client '%s'. Please ensure it is loaded.\n",
+ cfg->name);
+ return -1;
+ }
+
+ ast_debug(2, "OAuth 2.0 authentication for client '%s' returned: %s\n", cfg->name, cBuf);
+
+ jobj = ast_json_load_string(cBuf, &error);
+ if (jobj) {
+ const char *token = ast_json_string_get(ast_json_object_get(jobj, "access_token"));
+ if (token) {
+ ast_string_field_set(cfg, password, token);
+ return 0;
+ }
+ }
+
+ ast_log(LOG_ERROR, "An error occurred while performing OAuth 2.0 authentication for client '%s': %s\n", cfg->name, cBuf);
+
+ return -1;
+}
+
static int xmpp_client_config_post_apply(void *obj, void *arg, int flags)
{
struct ast_xmpp_client_config *cfg = obj;
@@ -4622,8 +4692,8 @@ static int client_buddy_handler(const struct aco_option *opt, struct ast_variabl
* 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
+ * 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.
*/
static int load_module(void)
@@ -4641,6 +4711,9 @@ static int load_module(void)
aco_option_register(&cfg_info, "username", ACO_EXACT, client_options, NULL, OPT_STRINGFIELD_T, 0, STRFLDSET(struct ast_xmpp_client_config, user));
aco_option_register(&cfg_info, "secret", ACO_EXACT, client_options, NULL, OPT_STRINGFIELD_T, 0, STRFLDSET(struct ast_xmpp_client_config, password));
+ aco_option_register(&cfg_info, "refresh_token", ACO_EXACT, client_options, NULL, OPT_STRINGFIELD_T, 0, STRFLDSET(struct ast_xmpp_client_config, refresh_token));
+ aco_option_register(&cfg_info, "oauth_clientid", ACO_EXACT, client_options, NULL, OPT_STRINGFIELD_T, 0, STRFLDSET(struct ast_xmpp_client_config, oauth_clientid));
+ aco_option_register(&cfg_info, "oauth_secret", ACO_EXACT, client_options, NULL, OPT_STRINGFIELD_T, 0, STRFLDSET(struct ast_xmpp_client_config, oauth_secret));
aco_option_register(&cfg_info, "serverhost", ACO_EXACT, client_options, NULL, OPT_STRINGFIELD_T, 0, STRFLDSET(struct ast_xmpp_client_config, server));
aco_option_register(&cfg_info, "statusmessage", ACO_EXACT, client_options, "Online and Available", OPT_STRINGFIELD_T, 0, STRFLDSET(struct ast_xmpp_client_config, statusmsg));
aco_option_register(&cfg_info, "pubsub_node", ACO_EXACT, client_options, NULL, OPT_STRINGFIELD_T, 0, STRFLDSET(struct ast_xmpp_client_config, pubsubnode));
diff --git a/res/srtp/srtp_compat.h b/res/srtp/srtp_compat.h
index 4ab39f318..bf4208244 100644
--- a/res/srtp/srtp_compat.h
+++ b/res/srtp/srtp_compat.h
@@ -5,7 +5,11 @@
#define crypto_policy_t srtp_crypto_policy_t
+#if defined(SRTP_AES_ICM_128)
+#define AES_128_ICM SRTP_AES_ICM_128
+#else
#define AES_128_ICM SRTP_AES_ICM
+#endif
#define HMAC_SHA1 SRTP_HMAC_SHA1
#define err_status_t srtp_err_status_t
diff --git a/res/stasis/control.c b/res/stasis/control.c
index b2b076b73..7e8ea91ef 100644
--- a/res/stasis/control.c
+++ b/res/stasis/control.c
@@ -927,14 +927,21 @@ static int bridge_channel_depart(struct stasis_app_control *control,
return 0;
}
-static void bridge_after_cb(struct ast_channel *chan, void *data)
+static void internal_bridge_after_cb(struct ast_channel *chan, void *data,
+ enum ast_bridge_after_cb_reason reason)
{
struct stasis_app_control *control = data;
SCOPED_AO2LOCK(lock, control);
struct ast_bridge_channel *bridge_channel;
- ast_debug(3, "%s, %s: Channel leaving bridge\n",
- ast_channel_uniqueid(chan), control->bridge->uniqueid);
+ ast_debug(3, "%s, %s: %s\n",
+ ast_channel_uniqueid(chan), control->bridge ? control->bridge->uniqueid : "unknown",
+ ast_bridge_after_cb_reason_string(reason));
+
+ if (reason == AST_BRIDGE_AFTER_CB_REASON_IMPART_FAILED) {
+ /* The impart actually failed so control->bridge isn't valid. */
+ control->bridge = NULL;
+ }
ast_assert(chan == control->channel);
@@ -942,18 +949,21 @@ static void bridge_after_cb(struct ast_channel *chan, void *data)
ast_channel_pbx_set(control->channel, control->pbx);
control->pbx = NULL;
- app_unsubscribe_bridge(control->app, control->bridge);
+ if (control->bridge) {
+ app_unsubscribe_bridge(control->app, control->bridge);
- /* No longer in the bridge */
- control->bridge = NULL;
+ /* No longer in the bridge */
+ control->bridge = NULL;
- /* Get the bridge channel so we don't depart from the wrong bridge */
- ast_channel_lock(chan);
- bridge_channel = ast_channel_get_bridge_channel(chan);
- ast_channel_unlock(chan);
+ /* Get the bridge channel so we don't depart from the wrong bridge */
+ ast_channel_lock(chan);
+ bridge_channel = ast_channel_get_bridge_channel(chan);
+ ast_channel_unlock(chan);
+
+ /* Depart this channel from the bridge using the command queue if possible */
+ stasis_app_send_command_async(control, bridge_channel_depart, bridge_channel, __ao2_cleanup);
+ }
- /* Depart this channel from the bridge using the command queue if possible */
- stasis_app_send_command_async(control, bridge_channel_depart, bridge_channel, __ao2_cleanup);
if (stasis_app_channel_is_stasis_end_published(chan)) {
/* The channel has had a StasisEnd published on it, but until now had remained in
* the bridging system. This means that the channel moved from a Stasis bridge to a
@@ -971,12 +981,19 @@ static void bridge_after_cb(struct ast_channel *chan, void *data)
}
}
+static void bridge_after_cb(struct ast_channel *chan, void *data)
+{
+ struct stasis_app_control *control = data;
+
+ internal_bridge_after_cb(control->channel, data, AST_BRIDGE_AFTER_CB_REASON_DEPART);
+}
+
static void bridge_after_cb_failed(enum ast_bridge_after_cb_reason reason,
void *data)
{
struct stasis_app_control *control = data;
- bridge_after_cb(control->channel, data);
+ internal_bridge_after_cb(control->channel, data, reason);
ast_debug(3, " reason: %s\n",
ast_bridge_after_cb_reason_string(reason));
@@ -1014,42 +1031,53 @@ int control_swap_channel_in_bridge(struct stasis_app_control *control, struct as
return -1;
}
- {
- /* pbx and bridge are modified by the bridging impart thread.
- * It shouldn't happen concurrently, but we still need to lock
- * for the memory fence.
- */
- SCOPED_AO2LOCK(lock, control);
+ ao2_lock(control);
- /* Ensure the controlling application is subscribed early enough
- * to receive the ChannelEnteredBridge message. This works in concert
- * with the subscription handled in the Stasis application execution
- * loop */
- app_subscribe_bridge(control->app, bridge);
-
- /* Save off the channel's PBX */
- ast_assert(control->pbx == NULL);
- if (!control->pbx) {
- control->pbx = ast_channel_pbx(chan);
- ast_channel_pbx_set(chan, NULL);
- }
+ /* Ensure the controlling application is subscribed early enough
+ * to receive the ChannelEnteredBridge message. This works in concert
+ * with the subscription handled in the Stasis application execution
+ * loop */
+ app_subscribe_bridge(control->app, bridge);
- res = ast_bridge_impart(bridge,
- chan,
- swap,
- NULL, /* features */
- AST_BRIDGE_IMPART_CHAN_DEPARTABLE);
- if (res != 0) {
- ast_log(LOG_ERROR, "Error adding channel to bridge\n");
- ast_channel_pbx_set(chan, control->pbx);
- control->pbx = NULL;
- return -1;
- }
+ /* Save off the channel's PBX */
+ ast_assert(control->pbx == NULL);
+ if (!control->pbx) {
+ control->pbx = ast_channel_pbx(chan);
+ ast_channel_pbx_set(chan, NULL);
+ }
- ast_assert(stasis_app_get_bridge(control) == NULL);
- control->bridge = bridge;
+ ast_assert(stasis_app_get_bridge(control) == NULL);
+ /* We need to set control->bridge here since bridge_after_cb may be run
+ * before ast_bridge_impart returns. bridge_after_cb gets a reason
+ * code so it can tell if the bridge is actually valid or not.
+ */
+ control->bridge = bridge;
+
+ /* We can't be holding the control lock while impart is running
+ * or we could create a deadlock with bridge_after_cb which also
+ * tries to lock control.
+ */
+ ao2_unlock(control);
+ res = ast_bridge_impart(bridge,
+ chan,
+ swap,
+ NULL, /* features */
+ AST_BRIDGE_IMPART_CHAN_DEPARTABLE);
+ if (res != 0) {
+ /* ast_bridge_impart failed before it could spawn the depart
+ * thread. The callbacks aren't called in this case.
+ * The impart could still fail even if ast_bridge_impart returned
+ * ok but that's handled by bridge_after_cb.
+ */
+ ast_log(LOG_ERROR, "Error adding channel to bridge\n");
+ ao2_lock(control);
+ ast_channel_pbx_set(chan, control->pbx);
+ control->pbx = NULL;
+ control->bridge = NULL;
+ ao2_unlock(control);
}
- return 0;
+
+ return res;
}
int control_add_channel_to_bridge(struct stasis_app_control *control, struct ast_channel *chan, void *data)
diff --git a/rest-api-templates/res_ari_resource.c.mustache b/rest-api-templates/res_ari_resource.c.mustache
index 921b007be..b4d80101e 100644
--- a/rest-api-templates/res_ari_resource.c.mustache
+++ b/rest-api-templates/res_ari_resource.c.mustache
@@ -262,30 +262,37 @@ static int unload_module(void)
static int load_module(void)
{
int res = 0;
+
+ CHECK_ARI_MODULE_LOADED();
+
{{#apis}}
{{#operations}}
{{#has_websocket}}
- struct ast_websocket_protocol *protocol;
+ /* This is scoped to not conflict with CHECK_ARI_MODULE_LOADED */
+ {
+ struct ast_websocket_protocol *protocol;
- {{full_name}}.ws_server = ast_websocket_server_create();
- if (!{{full_name}}.ws_server) {
- return AST_MODULE_LOAD_DECLINE;
- }
+ {{full_name}}.ws_server = ast_websocket_server_create();
+ if (!{{full_name}}.ws_server) {
+ return AST_MODULE_LOAD_DECLINE;
+ }
- protocol = ast_websocket_sub_protocol_alloc("{{websocket_protocol}}");
- if (!protocol) {
- ao2_ref({{full_name}}.ws_server, -1);
- {{full_name}}.ws_server = NULL;
- return AST_MODULE_LOAD_DECLINE;
- }
- protocol->session_attempted = ast_ari_{{c_name}}_{{c_nickname}}_ws_attempted_cb;
- protocol->session_established = ast_ari_{{c_name}}_{{c_nickname}}_ws_established_cb;
+ protocol = ast_websocket_sub_protocol_alloc("{{websocket_protocol}}");
+ if (!protocol) {
+ ao2_ref({{full_name}}.ws_server, -1);
+ {{full_name}}.ws_server = NULL;
+ return AST_MODULE_LOAD_DECLINE;
+ }
+ protocol->session_attempted = ast_ari_{{c_name}}_{{c_nickname}}_ws_attempted_cb;
+ protocol->session_established = ast_ari_{{c_name}}_{{c_nickname}}_ws_established_cb;
{{/has_websocket}}
{{#is_websocket}}
- res |= ast_websocket_server_add_protocol2({{full_name}}.ws_server, protocol);
+ res |= ast_websocket_server_add_protocol2({{full_name}}.ws_server, protocol);
+ }
{{/is_websocket}}
{{/operations}}
{{/apis}}
+
stasis_app_ref();
res |= ast_ari_add_handler(&{{root_full_name}});
if (res) {
diff --git a/sounds/Makefile b/sounds/Makefile
index 84d0f45c0..381776f72 100644
--- a/sounds/Makefile
+++ b/sounds/Makefile
@@ -19,13 +19,14 @@ CMD_PREFIX?=@
SOUNDS_DIR:=$(DESTDIR)$(ASTDATADIR)/sounds
SOUNDS_CACHE_DIR?=
MOH_DIR:=$(DESTDIR)$(ASTDATADIR)/moh
-CORE_SOUNDS_VERSION:=1.5
-EXTRA_SOUNDS_VERSION:=1.5
+CORE_SOUNDS_VERSION:=1.6
+EXTRA_SOUNDS_VERSION:=1.5.1
MOH_VERSION:=2.03
SOUNDS_URL:=http://downloads.asterisk.org/pub/telephony/sounds/releases
MCS:=$(subst -EN-,-en-,$(MENUSELECT_CORE_SOUNDS))
MCS:=$(subst -EN_AU-,-en_AU-,$(MCS))
MCS:=$(subst -EN_GB-,-en_GB-,$(MCS))
+MCS:=$(subst -EN_NZ-,-en_NZ-,$(MCS))
MCS:=$(subst -FR-,-fr-,$(MCS))
MCS:=$(subst -ES-,-es-,$(MCS))
MCS:=$(subst -RU-,-ru-,$(MCS))
@@ -144,6 +145,8 @@ $(eval $(call sound_format_lang_rule,$(SOUNDS_DIR),core-sounds,en_AU,$(CORE_SOUN
$(eval $(call sound_format_lang_rule,$(SOUNDS_DIR),core-sounds,en_GB,$(CORE_SOUNDS_VERSION)))
+$(eval $(call sound_format_lang_rule,$(SOUNDS_DIR),core-sounds,en_NZ,$(CORE_SOUNDS_VERSION)))
+
$(eval $(call sound_format_lang_rule,$(SOUNDS_DIR),core-sounds,es,$(CORE_SOUNDS_VERSION)))
$(eval $(call sound_format_lang_rule,$(SOUNDS_DIR),core-sounds,fr,$(CORE_SOUNDS_VERSION)))
diff --git a/sounds/sounds.xml b/sounds/sounds.xml
index 547be4b4a..2d996c57e 100644
--- a/sounds/sounds.xml
+++ b/sounds/sounds.xml
@@ -81,6 +81,33 @@
<member name="CORE-SOUNDS-EN_GB-SIREN14" displayname="English (British Accent), G.722.1C (Siren14) format">
<support_level>core</support_level>
</member>
+ <member name="CORE-SOUNDS-EN_NZ-WAV" displayname="English (New Zealand Accent), WAV format">
+ <support_level>core</support_level>
+ </member>
+ <member name="CORE-SOUNDS-EN_NZ-ULAW" displayname="English (New Zealand Accent), mu-Law format">
+ <support_level>core</support_level>
+ </member>
+ <member name="CORE-SOUNDS-EN_NZ-ALAW" displayname="English (New Zealand Accent), a-Law format">
+ <support_level>core</support_level>
+ </member>
+ <member name="CORE-SOUNDS-EN_NZ-GSM" displayname="English (New Zealand Accent), GSM format">
+ <support_level>core</support_level>
+ </member>
+ <member name="CORE-SOUNDS-EN_NZ-G729" displayname="English (New Zealand Accent), G.729 format">
+ <support_level>core</support_level>
+ </member>
+ <member name="CORE-SOUNDS-EN_NZ-G722" displayname="English (New Zealand Accent), G.722 format">
+ <support_level>core</support_level>
+ </member>
+ <member name="CORE-SOUNDS-EN_NZ-SLN16" displayname="English (New Zealand Accent), Signed-linear 16kHz format">
+ <support_level>core</support_level>
+ </member>
+ <member name="CORE-SOUNDS-EN_NZ-SIREN7" displayname="English (New Zealand Accent), G.722.1 (Siren7) format">
+ <support_level>core</support_level>
+ </member>
+ <member name="CORE-SOUNDS-EN_NZ-SIREN14" displayname="English (New Zealand Accent), G.722.1C (Siren14) format">
+ <support_level>core</support_level>
+ </member>
<member name="CORE-SOUNDS-ES-WAV" displayname="Spanish, WAV format">
<support_level>core</support_level>
</member>
diff --git a/tests/test_bridging.c b/tests/test_bridging.c
new file mode 100644
index 000000000..08a2fcc1f
--- /dev/null
+++ b/tests/test_bridging.c
@@ -0,0 +1,292 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2017, Digium, Inc.
+ *
+ * Joshua Colp <jcolp@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 Bridging unit tests
+ *
+ * \author Joshua Colp <jcolp@digium.com>
+ *
+ */
+
+/*** MODULEINFO
+ <depend>TEST_FRAMEWORK</depend>
+ <support_level>core</support_level>
+ ***/
+
+#include "asterisk.h"
+
+ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
+
+#include "asterisk/module.h"
+#include "asterisk/test.h"
+#include "asterisk/channel.h"
+#include "asterisk/time.h"
+#include "asterisk/bridge.h"
+#include "asterisk/bridge_basic.h"
+#include "asterisk/features.h"
+#include "asterisk/format_cache.h"
+
+#define TEST_CATEGORY "/main/bridging/"
+
+#define CHANNEL_TECH_NAME "BridgingTestChannel"
+
+#define TEST_CHANNEL_FORMAT ast_format_slin
+
+/*! \brief A private structure for the test channel */
+struct test_bridging_chan_pvt {
+ /* \brief The expected indication */
+ int condition;
+ /*! \brief The number of indicated things */
+ unsigned int indicated;
+};
+
+/*! \brief Callback function for when a frame is written to a channel */
+static int test_bridging_chan_indicate(struct ast_channel *chan, int condition, const void *data, size_t datalen)
+{
+ struct test_bridging_chan_pvt *test_pvt = ast_channel_tech_pvt(chan);
+
+ if (condition == test_pvt->condition) {
+ test_pvt->indicated++;
+ }
+
+ return 0;
+}
+
+/*! \brief Callback function for when a channel is hung up */
+static int test_bridging_chan_hangup(struct ast_channel *chan)
+{
+ struct test_bridging_chan_pvt *test_pvt = ast_channel_tech_pvt(chan);
+
+ ast_free(test_pvt);
+ ast_channel_tech_pvt_set(chan, NULL);
+
+ return 0;
+}
+
+/*! \brief A channel technology used for the unit tests */
+static struct ast_channel_tech test_bridging_chan_tech = {
+ .type = CHANNEL_TECH_NAME,
+ .description = "Mock channel technology for bridge tests",
+ .indicate = test_bridging_chan_indicate,
+ .hangup = test_bridging_chan_hangup,
+ .properties = AST_CHAN_TP_INTERNAL,
+};
+
+static void test_nanosleep(int secs, long nanosecs)
+{
+ struct timespec sleep_time = {secs, nanosecs};
+
+ while ((nanosleep(&sleep_time, &sleep_time) == -1) && (errno == EINTR)) {
+ }
+}
+
+/*! \brief Wait until a channel is bridged */
+static void wait_for_bridged(struct ast_channel *channel)
+{
+ ast_channel_lock(channel);
+ while (!ast_channel_is_bridged(channel)) {
+ ast_channel_unlock(channel);
+ test_nanosleep(0, 1000000);
+ ast_channel_lock(channel);
+ }
+ ast_channel_unlock(channel);
+}
+
+/*! \brief Wait until a channel is not bridged */
+static void wait_for_unbridged(struct ast_channel *channel)
+{
+ ast_channel_lock(channel);
+ while (ast_channel_is_bridged(channel)) {
+ ast_channel_unlock(channel);
+ test_nanosleep(0, 1000000);
+ ast_channel_lock(channel);
+ }
+ ast_channel_unlock(channel);
+}
+
+/*! \brief Wait until a channel has no frames on its read queue */
+static void wait_for_empty_queue(struct ast_channel *channel)
+{
+ ast_channel_lock(channel);
+ while (!AST_LIST_EMPTY(ast_channel_readq(channel))) {
+ ast_channel_unlock(channel);
+ test_nanosleep(0, 1000000);
+ ast_channel_lock(channel);
+ }
+ ast_channel_unlock(channel);
+}
+
+/*! \brief Create a \ref test_bridging_chan_tech for Alice. */
+#define START_ALICE(channel, pvt) START_CHANNEL(channel, pvt, "Alice", "100")
+
+/*! \brief Create a \ref test_bridging_chan_tech for Bob. */
+#define START_BOB(channel, pvt) START_CHANNEL(channel, pvt, "Bob", "200")
+
+#define START_CHANNEL(channel, pvt, name, number) do { \
+ channel = ast_channel_alloc(0, AST_STATE_UP, number, name, number, number, \
+ "default", NULL, NULL, 0, CHANNEL_TECH_NAME "/" name); \
+ pvt = ast_calloc(1, sizeof(*pvt)); \
+ ast_channel_tech_pvt_set(channel, pvt); \
+ ast_channel_nativeformats_set(channel, test_bridging_chan_tech.capabilities); \
+ ast_channel_set_rawwriteformat(channel, TEST_CHANNEL_FORMAT); \
+ ast_channel_set_rawreadformat(channel, TEST_CHANNEL_FORMAT); \
+ ast_channel_set_writeformat(channel, TEST_CHANNEL_FORMAT); \
+ ast_channel_set_readformat(channel, TEST_CHANNEL_FORMAT); \
+ ast_channel_unlock(channel); \
+ } while (0)
+
+/*! \brief Hang up a test channel safely */
+#define HANGUP_CHANNEL(channel) do { \
+ ao2_ref(channel, +1); \
+ ast_hangup((channel)); \
+ ao2_cleanup(channel); \
+ channel = NULL; \
+ } while (0)
+
+static void safe_channel_release(struct ast_channel *chan)
+{
+ if (!chan) {
+ return;
+ }
+ ast_channel_release(chan);
+}
+
+static void safe_bridge_destroy(struct ast_bridge *bridge)
+{
+ if (!bridge) {
+ return;
+ }
+ ast_bridge_destroy(bridge, 0);
+}
+
+static void stream_periodic_frames(struct ast_channel *chan, int ms, int interval_ms)
+{
+ long nanosecs;
+
+ ast_assert(chan != NULL);
+ ast_assert(0 < ms);
+ ast_assert(0 < interval_ms);
+
+ nanosecs = interval_ms * 1000000L;
+ while (0 < ms) {
+ ast_queue_frame(chan, &ast_null_frame);
+
+ if (interval_ms < ms) {
+ ms -= interval_ms;
+ } else {
+ nanosecs = ms * 1000000L;
+ ms = 0;
+ }
+ test_nanosleep(0, nanosecs);
+ }
+}
+
+AST_TEST_DEFINE(test_bridging_deferred_queue)
+{
+ RAII_VAR(struct ast_channel *, chan_alice, NULL, safe_channel_release);
+ struct test_bridging_chan_pvt *alice_pvt;
+ struct ast_control_t38_parameters t38_parameters = {
+ .request_response = AST_T38_REQUEST_NEGOTIATE,
+ };
+ struct ast_frame frame = {
+ .frametype = AST_FRAME_CONTROL,
+ .subclass.integer = AST_CONTROL_T38_PARAMETERS,
+ .data.ptr = &t38_parameters,
+ .datalen = sizeof(t38_parameters),
+ };
+ RAII_VAR(struct ast_channel *, chan_bob, NULL, safe_channel_release);
+ struct test_bridging_chan_pvt *bob_pvt;
+ RAII_VAR(struct ast_bridge *, bridge1, NULL, safe_bridge_destroy);
+
+ switch (cmd) {
+ case TEST_INIT:
+ info->name = __func__;
+ info->category = TEST_CATEGORY;
+ info->summary = "Test that deferred frames from a channel in a bridge get written";
+ info->description =
+ "This test creates two channels, queues a deferrable frame on one, places it into\n"
+ "a bridge, confirms the frame was read by the bridge, adds the second channel to the\n"
+ "bridge, and makes sure the deferred frame is written to it.";
+ return AST_TEST_NOT_RUN;
+ case TEST_EXECUTE:
+ break;
+ }
+
+ /* Create the bridges */
+ bridge1 = ast_bridge_basic_new();
+ ast_test_validate(test, bridge1 != NULL);
+
+ /* Create channels that will go into the bridge */
+ START_ALICE(chan_alice, alice_pvt);
+ START_BOB(chan_bob, bob_pvt);
+ bob_pvt->condition = AST_CONTROL_T38_PARAMETERS;
+
+ /* Bridge alice and wait for the frame to be deferred */
+ ast_test_validate(test, !ast_bridge_impart(bridge1, chan_alice, NULL, NULL, AST_BRIDGE_IMPART_CHAN_DEPARTABLE));
+ wait_for_bridged(chan_alice);
+ ast_queue_frame(chan_alice, &frame);
+ wait_for_empty_queue(chan_alice);
+
+ /* Bridge bob for a second so it can receive the deferred T.38 request negotiate frame */
+ ast_test_validate(test, !ast_bridge_impart(bridge1, chan_bob, NULL, NULL, AST_BRIDGE_IMPART_CHAN_DEPARTABLE));
+ wait_for_bridged(chan_bob);
+ stream_periodic_frames(chan_alice, 1000, 20);
+ ast_test_validate(test, !ast_bridge_depart(chan_bob));
+ wait_for_unbridged(chan_bob);
+
+ /* Ensure that we received the expected indications while it was in there (request to negotiate, and to terminate) */
+ ast_test_validate(test, bob_pvt->indicated == 2);
+
+ /* Now remove alice since we are done */
+ ast_test_validate(test, !ast_bridge_depart(chan_alice));
+ wait_for_unbridged(chan_alice);
+
+ /* Hangup the channels */
+ HANGUP_CHANNEL(chan_alice);
+ HANGUP_CHANNEL(chan_bob);
+
+ return AST_TEST_PASS;
+}
+
+static int unload_module(void)
+{
+ AST_TEST_UNREGISTER(test_bridging_deferred_queue);
+
+ ast_channel_unregister(&test_bridging_chan_tech);
+ ao2_cleanup(test_bridging_chan_tech.capabilities);
+ test_bridging_chan_tech.capabilities = NULL;
+
+ return 0;
+}
+
+static int load_module(void)
+{
+ test_bridging_chan_tech.capabilities = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT);
+ if (!test_bridging_chan_tech.capabilities) {
+ return AST_MODULE_LOAD_DECLINE;
+ }
+ ast_format_cap_append(test_bridging_chan_tech.capabilities, TEST_CHANNEL_FORMAT, 0);
+ ast_channel_register(&test_bridging_chan_tech);
+
+ AST_TEST_REGISTER(test_bridging_deferred_queue);
+
+ return AST_MODULE_LOAD_SUCCESS;
+}
+
+AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Bridging Unit Tests");
diff --git a/tests/test_config.c b/tests/test_config.c
index fd14908b6..8fb473535 100644
--- a/tests/test_config.c
+++ b/tests/test_config.c
@@ -43,6 +43,7 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$");
#include "asterisk/config_options.h"
#include "asterisk/netsock2.h"
#include "asterisk/acl.h"
+#include "asterisk/app.h"
#include "asterisk/pbx.h"
#include "asterisk/frame.h"
#include "asterisk/utils.h"
@@ -1039,6 +1040,7 @@ AST_TEST_DEFINE(config_hook)
res = AST_TEST_PASS;
out:
+ ast_config_hook_unregister("test_hook");
delete_config_file();
return res;
}
@@ -1082,6 +1084,13 @@ enum {
ast_test_status_update(test, "ast_parse_arg double failed with %f != %f\n", *r, e); \
ret = AST_TEST_FAIL; \
} \
+ } else if (((flags) & PARSE_TYPE) == PARSE_TIMELEN) { \
+ int *r = (int *) (void *) result; \
+ int e = (int) expected_result; \
+ if (*r != e) { \
+ ast_test_status_update(test, "ast_parse_arg timelen failed with %d != %d\n", *r, e); \
+ ret = AST_TEST_FAIL; \
+ } \
} \
} \
*(result) = DEFAULTVAL; \
@@ -1092,6 +1101,7 @@ AST_TEST_DEFINE(ast_parse_arg_test)
int ret = AST_TEST_PASS;
int32_t int32_t_val = DEFAULTVAL;
uint32_t uint32_t_val = DEFAULTVAL;
+ int timelen_val = DEFAULTVAL;
double double_val = DEFAULTVAL;
switch (cmd) {
@@ -1224,6 +1234,60 @@ AST_TEST_DEFINE(ast_parse_arg_test)
TEST_PARSE(" -123", EXPECT_FAIL, DEFAULTVAL, PARSE_UINT32, &uint32_t_val);
+ /* timelen testing */
+ TEST_PARSE("123", EXPECT_SUCCEED, 123, PARSE_TIMELEN, &timelen_val, TIMELEN_MILLISECONDS);
+ TEST_PARSE("-123", EXPECT_SUCCEED, -123, PARSE_TIMELEN, &timelen_val, TIMELEN_MILLISECONDS);
+ TEST_PARSE("0", EXPECT_SUCCEED, 0, PARSE_TIMELEN, &timelen_val, TIMELEN_MILLISECONDS);
+ TEST_PARSE("not a number", EXPECT_FAIL, DEFAULTVAL, PARSE_TIMELEN, &timelen_val, TIMELEN_MILLISECONDS);
+ TEST_PARSE("7not a number", EXPECT_FAIL, DEFAULTVAL, PARSE_TIMELEN, &timelen_val, TIMELEN_MILLISECONDS);
+
+ TEST_PARSE("123s", EXPECT_SUCCEED, 123000, PARSE_TIMELEN, &timelen_val, TIMELEN_MILLISECONDS);
+ TEST_PARSE("-123s", EXPECT_SUCCEED, -123000, PARSE_TIMELEN, &timelen_val, TIMELEN_MILLISECONDS);
+ TEST_PARSE("1m", EXPECT_SUCCEED, 60000, PARSE_TIMELEN, &timelen_val, TIMELEN_MILLISECONDS);
+ TEST_PARSE("1", EXPECT_SUCCEED, 60000, PARSE_TIMELEN, &timelen_val, TIMELEN_MINUTES);
+ TEST_PARSE("1h", EXPECT_SUCCEED, 3600000, PARSE_TIMELEN, &timelen_val, TIMELEN_MILLISECONDS);
+ TEST_PARSE("1", EXPECT_SUCCEED, 3600000, PARSE_TIMELEN, &timelen_val, TIMELEN_HOURS);
+
+ TEST_PARSE("123", EXPECT_SUCCEED, 123, PARSE_TIMELEN | PARSE_DEFAULT, &timelen_val, TIMELEN_MILLISECONDS, 7);
+ TEST_PARSE("-123", EXPECT_SUCCEED, -123, PARSE_TIMELEN | PARSE_DEFAULT, &timelen_val, TIMELEN_MILLISECONDS, 7);
+ TEST_PARSE("0", EXPECT_SUCCEED, 0, PARSE_TIMELEN | PARSE_DEFAULT, &timelen_val, TIMELEN_MILLISECONDS, 7);
+ TEST_PARSE("not a number", EXPECT_FAIL, 7, PARSE_TIMELEN | PARSE_DEFAULT, &timelen_val, TIMELEN_MILLISECONDS, 7);
+ TEST_PARSE("7not a number", EXPECT_FAIL, 7, PARSE_TIMELEN | PARSE_DEFAULT, &timelen_val, TIMELEN_MILLISECONDS, 7);
+
+ TEST_PARSE("123", EXPECT_SUCCEED, 123, PARSE_TIMELEN | PARSE_IN_RANGE, &timelen_val, TIMELEN_MILLISECONDS, 0, 200);
+ TEST_PARSE("-123", EXPECT_SUCCEED, -123, PARSE_TIMELEN | PARSE_IN_RANGE, &timelen_val, TIMELEN_MILLISECONDS, -200, 100);
+ TEST_PARSE("0", EXPECT_SUCCEED, 0, PARSE_TIMELEN | PARSE_IN_RANGE, &timelen_val, TIMELEN_MILLISECONDS, -1, 0);
+ TEST_PARSE("123", EXPECT_FAIL, DEFAULTVAL, PARSE_TIMELEN | PARSE_IN_RANGE, &timelen_val, TIMELEN_MILLISECONDS, 0, 122);
+ TEST_PARSE("-123", EXPECT_FAIL, DEFAULTVAL, PARSE_TIMELEN | PARSE_IN_RANGE, &timelen_val, TIMELEN_MILLISECONDS, -122, 100);
+ TEST_PARSE("0", EXPECT_FAIL, DEFAULTVAL, PARSE_TIMELEN | PARSE_IN_RANGE, &timelen_val, TIMELEN_MILLISECONDS, 1, 100);
+ TEST_PARSE("not a number", EXPECT_FAIL, DEFAULTVAL, PARSE_TIMELEN | PARSE_IN_RANGE, &timelen_val, TIMELEN_MILLISECONDS, INT_MIN, INT_MAX);
+ TEST_PARSE("7not a number", EXPECT_FAIL, DEFAULTVAL, PARSE_TIMELEN | PARSE_IN_RANGE, &timelen_val, TIMELEN_MILLISECONDS, INT_MIN, INT_MAX);
+ TEST_PARSE("123", EXPECT_FAIL, DEFAULTVAL, PARSE_TIMELEN | PARSE_OUT_RANGE, &timelen_val, TIMELEN_MILLISECONDS, 0, 200);
+ TEST_PARSE("-123", EXPECT_FAIL, DEFAULTVAL, PARSE_TIMELEN | PARSE_OUT_RANGE, &timelen_val, TIMELEN_MILLISECONDS, -200, 100);
+ TEST_PARSE("0", EXPECT_FAIL, DEFAULTVAL, PARSE_TIMELEN | PARSE_OUT_RANGE, &timelen_val, TIMELEN_MILLISECONDS, -1, 0);
+ TEST_PARSE("123", EXPECT_SUCCEED, 123, PARSE_TIMELEN | PARSE_OUT_RANGE, &timelen_val, TIMELEN_MILLISECONDS, 0, 122);
+ TEST_PARSE("-123", EXPECT_SUCCEED, -123, PARSE_TIMELEN | PARSE_OUT_RANGE, &timelen_val, TIMELEN_MILLISECONDS, -122, 100);
+ TEST_PARSE("0", EXPECT_SUCCEED, 0, PARSE_TIMELEN | PARSE_OUT_RANGE, &timelen_val, TIMELEN_MILLISECONDS, 1, 100);
+ TEST_PARSE("not a number", EXPECT_FAIL, DEFAULTVAL, PARSE_TIMELEN | PARSE_OUT_RANGE, &timelen_val, TIMELEN_MILLISECONDS, INT_MIN, INT_MAX);
+ TEST_PARSE("7not a number", EXPECT_FAIL, DEFAULTVAL, PARSE_TIMELEN | PARSE_OUT_RANGE, &timelen_val, TIMELEN_MILLISECONDS, INT_MIN, INT_MAX);
+
+ TEST_PARSE("123", EXPECT_SUCCEED, 123, PARSE_TIMELEN | PARSE_DEFAULT | PARSE_IN_RANGE, &timelen_val, TIMELEN_MILLISECONDS, 7, 0, 200);
+ TEST_PARSE("-123", EXPECT_SUCCEED, -123, PARSE_TIMELEN | PARSE_DEFAULT | PARSE_IN_RANGE, &timelen_val, TIMELEN_MILLISECONDS, 7, -200, 100);
+ TEST_PARSE("0", EXPECT_SUCCEED, 0, PARSE_TIMELEN | PARSE_DEFAULT | PARSE_IN_RANGE, &timelen_val, TIMELEN_MILLISECONDS, 7, -1, 0);
+ TEST_PARSE("123", EXPECT_FAIL, 7, PARSE_TIMELEN | PARSE_DEFAULT | PARSE_IN_RANGE, &timelen_val, TIMELEN_MILLISECONDS, 7, 0, 122);
+ TEST_PARSE("-123", EXPECT_FAIL, 7, PARSE_TIMELEN | PARSE_DEFAULT | PARSE_IN_RANGE, &timelen_val, TIMELEN_MILLISECONDS, 7, -122, 100);
+ TEST_PARSE("0", EXPECT_FAIL, 7, PARSE_TIMELEN | PARSE_DEFAULT | PARSE_IN_RANGE, &timelen_val, TIMELEN_MILLISECONDS, 7, 1, 100);
+ TEST_PARSE("not a number", EXPECT_FAIL, 7, PARSE_TIMELEN | PARSE_DEFAULT | PARSE_IN_RANGE, &timelen_val, TIMELEN_MILLISECONDS, 7, INT_MIN, INT_MAX);
+ TEST_PARSE("7not a number", EXPECT_FAIL, 7, PARSE_TIMELEN | PARSE_DEFAULT | PARSE_IN_RANGE, &timelen_val, TIMELEN_MILLISECONDS, 7, INT_MIN, INT_MAX);
+ TEST_PARSE("123", EXPECT_FAIL, 7, PARSE_TIMELEN | PARSE_DEFAULT | PARSE_OUT_RANGE, &timelen_val, TIMELEN_MILLISECONDS, 7, 0, 200);
+ TEST_PARSE("-123", EXPECT_FAIL, 7, PARSE_TIMELEN | PARSE_DEFAULT | PARSE_OUT_RANGE, &timelen_val, TIMELEN_MILLISECONDS, 7, -200, 100);
+ TEST_PARSE("0", EXPECT_FAIL, 7, PARSE_TIMELEN | PARSE_DEFAULT | PARSE_OUT_RANGE, &timelen_val, TIMELEN_MILLISECONDS, 7, -1, 0);
+ TEST_PARSE("123", EXPECT_SUCCEED, 123, PARSE_TIMELEN | PARSE_DEFAULT | PARSE_OUT_RANGE, &timelen_val, TIMELEN_MILLISECONDS, 7, 0, 122);
+ TEST_PARSE("-123", EXPECT_SUCCEED, -123, PARSE_TIMELEN | PARSE_DEFAULT | PARSE_OUT_RANGE, &timelen_val, TIMELEN_MILLISECONDS, 7, -122, 100);
+ TEST_PARSE("0", EXPECT_SUCCEED, 0, PARSE_TIMELEN | PARSE_DEFAULT | PARSE_OUT_RANGE, &timelen_val, TIMELEN_MILLISECONDS, 7, 1, 100);
+ TEST_PARSE("not a number", EXPECT_FAIL, 7, PARSE_TIMELEN | PARSE_DEFAULT | PARSE_OUT_RANGE, &timelen_val, TIMELEN_MILLISECONDS, 7, INT_MIN, INT_MAX);
+ TEST_PARSE("7not a number", EXPECT_FAIL, 7, PARSE_TIMELEN | PARSE_DEFAULT | PARSE_OUT_RANGE, &timelen_val, TIMELEN_MILLISECONDS, 7, INT_MIN, INT_MAX);
+
/* double testing */
TEST_PARSE("123", EXPECT_SUCCEED, 123, PARSE_DOUBLE, &double_val);
TEST_PARSE("123.123", EXPECT_SUCCEED, 123.123, PARSE_DOUBLE, &double_val);
@@ -1283,6 +1347,10 @@ struct test_item {
);
int32_t intopt;
uint32_t uintopt;
+ int timelenopt1;
+ int timelenopt2;
+ int timelenopt3;
+ int timelenopt4;
unsigned int flags;
double doubleopt;
struct ast_sockaddr sockaddropt;
@@ -1437,6 +1505,8 @@ AST_TEST_DEFINE(config_options_test)
#define INT_CONFIG "-1"
#define UINT_DEFAULT "2"
#define UINT_CONFIG "1"
+#define TIMELEN_DEFAULT "2"
+#define TIMELEN_CONFIG "1"
#define DOUBLE_DEFAULT "1.1"
#define DOUBLE_CONFIG "0.1"
#define SOCKADDR_DEFAULT "4.3.2.1:4321"
@@ -1471,6 +1541,10 @@ AST_TEST_DEFINE(config_options_test)
/* Register all options */
aco_option_register(&cfg_info, "intopt", ACO_EXACT, config_test_conf.types, INT_DEFAULT, OPT_INT_T, 0, FLDSET(struct test_item, intopt));
aco_option_register(&cfg_info, "uintopt", ACO_EXACT, config_test_conf.types, UINT_DEFAULT, OPT_UINT_T, 0, FLDSET(struct test_item, uintopt));
+ aco_option_register(&cfg_info, "timelenopt1", ACO_EXACT, config_test_conf.types, TIMELEN_DEFAULT, OPT_TIMELEN_T, 0, FLDSET(struct test_item, timelenopt1), TIMELEN_MILLISECONDS);
+ aco_option_register(&cfg_info, "timelenopt2", ACO_EXACT, config_test_conf.types, TIMELEN_DEFAULT, OPT_TIMELEN_T, 0, FLDSET(struct test_item, timelenopt2), TIMELEN_SECONDS);
+ aco_option_register(&cfg_info, "timelenopt3", ACO_EXACT, config_test_conf.types, TIMELEN_DEFAULT, OPT_TIMELEN_T, 0, FLDSET(struct test_item, timelenopt3), TIMELEN_MINUTES);
+ aco_option_register(&cfg_info, "timelenopt4", ACO_EXACT, config_test_conf.types, TIMELEN_DEFAULT, OPT_TIMELEN_T, 0, FLDSET(struct test_item, timelenopt4), TIMELEN_HOURS);
aco_option_register(&cfg_info, "doubleopt", ACO_EXACT, config_test_conf.types, DOUBLE_DEFAULT, OPT_DOUBLE_T, 0, FLDSET(struct test_item, doubleopt));
aco_option_register(&cfg_info, "sockaddropt", ACO_EXACT, config_test_conf.types, SOCKADDR_DEFAULT, OPT_SOCKADDR_T, 0, FLDSET(struct test_item, sockaddropt));
aco_option_register(&cfg_info, "boolopt", ACO_EXACT, config_test_conf.types, BOOL_DEFAULT, OPT_BOOL_T, 1, FLDSET(struct test_item, boolopt));
@@ -1492,6 +1566,14 @@ AST_TEST_DEFINE(config_options_test)
ast_parse_arg(INT_DEFAULT, PARSE_INT32, &defaults.intopt);
ast_parse_arg(INT_CONFIG, PARSE_INT32, &configs.intopt);
+ ast_parse_arg(TIMELEN_DEFAULT, PARSE_TIMELEN, &defaults.timelenopt1, TIMELEN_MILLISECONDS);
+ ast_parse_arg(TIMELEN_CONFIG, PARSE_TIMELEN, &configs.timelenopt1, TIMELEN_MILLISECONDS);
+ ast_parse_arg(TIMELEN_DEFAULT, PARSE_TIMELEN, &defaults.timelenopt2, TIMELEN_SECONDS);
+ ast_parse_arg(TIMELEN_CONFIG, PARSE_TIMELEN, &configs.timelenopt2, TIMELEN_SECONDS);
+ ast_parse_arg(TIMELEN_DEFAULT, PARSE_TIMELEN, &defaults.timelenopt3, TIMELEN_MINUTES);
+ ast_parse_arg(TIMELEN_CONFIG, PARSE_TIMELEN, &configs.timelenopt3, TIMELEN_MINUTES);
+ ast_parse_arg(TIMELEN_DEFAULT, PARSE_TIMELEN, &defaults.timelenopt4, TIMELEN_HOURS);
+ ast_parse_arg(TIMELEN_CONFIG, PARSE_TIMELEN, &configs.timelenopt4, TIMELEN_HOURS);
ast_parse_arg(UINT_DEFAULT, PARSE_UINT32, &defaults.uintopt);
ast_parse_arg(UINT_CONFIG, PARSE_UINT32, &configs.uintopt);
ast_parse_arg(DOUBLE_DEFAULT, PARSE_DOUBLE, &defaults.doubleopt);
@@ -1553,6 +1635,10 @@ AST_TEST_DEFINE(config_options_test)
NOT_EQUAL_FAIL(intopt, "%d");
NOT_EQUAL_FAIL(uintopt, "%u");
+ NOT_EQUAL_FAIL(timelenopt1, "%d");
+ NOT_EQUAL_FAIL(timelenopt2, "%d");
+ NOT_EQUAL_FAIL(timelenopt3, "%d");
+ NOT_EQUAL_FAIL(timelenopt4, "%d");
NOT_EQUAL_FAIL(boolopt, "%d");
NOT_EQUAL_FAIL(flags, "%u");
NOT_EQUAL_FAIL(customopt, "%d");
@@ -1592,6 +1678,8 @@ AST_TEST_DEFINE(config_options_test)
configs.codeccapopt = NULL;
ast_string_field_free_memory(&defaults);
ast_string_field_free_memory(&configs);
+ aco_info_destroy(&cfg_info);
+ ao2_global_obj_release(global_obj);
return res;
}
diff --git a/tests/test_core_format.c b/tests/test_core_format.c
index a3819c6d0..e4199dbed 100644
--- a/tests/test_core_format.c
+++ b/tests/test_core_format.c
@@ -860,6 +860,7 @@ AST_TEST_DEFINE(format_attribute_set_without_interface)
{
RAII_VAR(struct ast_codec *, codec, NULL, ao2_cleanup);
RAII_VAR(struct ast_format *, format, NULL, ao2_cleanup);
+ struct ast_format *attr_set;
switch (cmd) {
case TEST_INIT:
@@ -885,10 +886,12 @@ AST_TEST_DEFINE(format_attribute_set_without_interface)
return AST_TEST_FAIL;
}
- if (!ast_format_attribute_set(format, "bees", "cool")) {
+ attr_set = ast_format_attribute_set(format, "bees", "cool");
+ if (!attr_set) {
ast_test_status_update(test, "Successfully set an attribute on a format without an interface\n");
return AST_TEST_FAIL;
}
+ ao2_cleanup(attr_set);
return AST_TEST_PASS;
}
diff --git a/tests/test_taskprocessor.c b/tests/test_taskprocessor.c
index be48f9248..ad2074cb8 100644
--- a/tests/test_taskprocessor.c
+++ b/tests/test_taskprocessor.c
@@ -677,7 +677,7 @@ AST_TEST_DEFINE(taskprocessor_push_local)
{
RAII_VAR(struct ast_taskprocessor *, tps, NULL,
ast_taskprocessor_unreference);
- struct task_data *task_data;
+ RAII_VAR(struct task_data *, task_data, NULL, ao2_cleanup);
int local_data;
int res;
diff --git a/third-party/configure.m4 b/third-party/configure.m4
index 635446638..55b72daf9 100644
--- a/third-party/configure.m4
+++ b/third-party/configure.m4
@@ -1,4 +1,7 @@
-
+#
+# If this file is changed, be sure to run ASTTOPDIR/bootstrap.sh
+# before committing.
+#
AC_DEFUN([THIRD_PARTY_CONFIGURE],
[
diff --git a/third-party/pjproject/Makefile b/third-party/pjproject/Makefile
index e691f2242..7a42edcde 100644
--- a/third-party/pjproject/Makefile
+++ b/third-party/pjproject/Makefile
@@ -86,6 +86,11 @@ SHELL_ECHO_PREFIX := echo '[pjproject] '
_all: $(TARGETS)
+define tarball_exists
+ (if [ -f $(TARBALL) -a -f $(PJMD5SUM) ] ; then exit 0 ;\
+ else exit 1; fi; )
+endef
+
define verify_tarball
($(SHELL_ECHO_PREFIX) Verifying $(TARBALL) &&\
tarball_sum=$$($(CAT) $(TARBALL) | $(MD5) | $(SED) -n -r -e "s/^([^ ]+)\s+.*/\1/gp") ;\
@@ -97,7 +102,7 @@ endef
define download_from_pjproject
($(SHELL_ECHO_PREFIX) Downloading $(TARBALL_URL) to $(TARBALL) ;\
$(DOWNLOAD_TO_STDOUT) $(call DOWNLOAD_TIMEOUT,5,60) $(TARBALL_URL) > $(TARBALL) &&\
- $(SHELL_ECHO_PREFIX) Downloading $(PJPROJECT_URL)/MD5SUM to $(PJMD5SUM) &&\
+ $(SHELL_ECHO_PREFIX) Downloading $(PJPROJECT_URL)/MD5SUM.TXT to $(PJMD5SUM) &&\
$(DOWNLOAD_TO_STDOUT) $(call DOWNLOAD_TIMEOUT,5,60) $(PJPROJECT_URL)/MD5SUM.TXT > $(PJMD5SUM) &&\
$(verify_tarball))
endef
@@ -111,11 +116,12 @@ TARBALL_URL = $(PJPROJECT_URL)/$(TARBALL_FILE)
PJMD5SUM = $(patsubst %.tar.bz2,%.md5,$(TARBALL))
$(TARBALL): ../versions.mak
- $(CMD_PREFIX) $(download_from_pjproject) || (rm -rf $@ ;\
+ $(CMD_PREFIX) ($(tarball_exists) && $(verify_tarball) && touch $@) || (rm -rf $@ ;\
+ $(download_from_pjproject)) || (rm -rf $@ ;\
$(SHELL_ECHO_PREFIX) Retrying download ; $(download_from_pjproject))
source/.unpacked: $(DOWNLOAD_DIR)/pjproject-$(PJPROJECT_VERSION).tar.bz2
- ($(verify_tarball)) || (rm -rf $@ ;\
+ $(CMD_PREFIX) $(verify_tarball) || (rm -rf $@ ;\
$(SHELL_ECHO_PREFIX) Retrying download ; $(download_from_pjproject))
$(ECHO_PREFIX) Unpacking $<
-@rm -rf source pjproject-* >/dev/null 2>&1
diff --git a/third-party/pjproject/Makefile.rules b/third-party/pjproject/Makefile.rules
index c0be1cbdf..acd766218 100644
--- a/third-party/pjproject/Makefile.rules
+++ b/third-party/pjproject/Makefile.rules
@@ -1,8 +1,11 @@
-PJPROJECT_URL = http://www.pjsip.org/release/$(PJPROJECT_VERSION)
+PJPROJECT_URL ?= https://raw.githubusercontent.com/asterisk/third-party/master/pjproject/$(PJPROJECT_VERSION)
+# PJPROJECT_CONFIGURE_OPTS could come from the command line or could be
+# set/modified by configure.m4 if the build or host tuples aren't the same
+# as the current build environment (cross-compile).
# Even though we're not installing pjproject, we're setting prefix to /opt/pjproject to be safe
-PJPROJECT_CONFIG_OPTS = --prefix=/opt/pjproject \
+PJPROJECT_CONFIG_OPTS = $(PJPROJECT_CONFIGURE_OPTS) --prefix=/opt/pjproject \
--disable-speex-codec \
--disable-speex-aec \
--disable-speex-aec \
diff --git a/third-party/pjproject/configure.m4 b/third-party/pjproject/configure.m4
index a5e9fca60..2d3353476 100644
--- a/third-party/pjproject/configure.m4
+++ b/third-party/pjproject/configure.m4
@@ -1,3 +1,8 @@
+#
+# If this file is changed, be sure to run ASTTOPDIR/bootstrap.sh
+# before committing.
+#
+
AC_DEFUN([_PJPROJECT_CONFIGURE],
[
if test "${ac_mandatory_list#*PJPROJECT*}" != "$ac_mandatory_list" ; then
@@ -35,17 +40,30 @@ AC_DEFUN([_PJPROJECT_CONFIGURE],
AC_MSG_ERROR(cat is required to build bundled pjproject)
fi
- export TAR PATCH SED NM EXTERNALS_CACHE_DIR DOWNLOAD_TO_STDOUT DOWNLOAD_TIMEOUT DOWNLOAD MD5 CAT
- ${GNU_MAKE} --quiet --no-print-directory -C ${PJPROJECT_DIR} EXTERNALS_CACHE_DIR=${EXTERNALS_CACHE_DIR} configure
+ AC_ARG_VAR([PJPROJECT_CONFIGURE_OPTS],[Additional configure options to pass to bundled pjproject])
+ this_host=$(./config.sub $(./config.guess))
+ if test "$build" != "$this_host" ; then
+ PJPROJECT_CONFIGURE_OPTS+=" --build=$build"
+ fi
+ if test "$host" != "$this_host" ; then
+ PJPROJECT_CONFIGURE_OPTS+=" --host=$host"
+ fi
+
+ export TAR PATCH SED NM EXTERNALS_CACHE_DIR AST_DOWNLOAD_CACHE DOWNLOAD_TO_STDOUT DOWNLOAD_TIMEOUT DOWNLOAD MD5 CAT
+ export NOISY_BUILD
+ ${GNU_MAKE} --quiet --no-print-directory -C ${PJPROJECT_DIR} \
+ PJPROJECT_CONFIGURE_OPTS="$PJPROJECT_CONFIGURE_OPTS" \
+ EXTERNALS_CACHE_DIR="${EXTERNALS_CACHE_DIR:-${AST_DOWNLOAD_CACHE}}" \
+ configure
if test $? -ne 0 ; then
AC_MSG_RESULT(failed)
AC_MSG_NOTICE(Unable to configure ${PJPROJECT_DIR})
- AC_MSG_ERROR(Run "${GNU_MAKE} -C ${PJPROJECT_DIR} NOISY_BUILD=yes configure" to see error details.)
+ AC_MSG_ERROR(Re-run the ./configure command with 'NOISY_BUILD=yes' appended to see error details.)
fi
AC_MSG_CHECKING(for bundled pjproject)
- PJPROJECT_INCLUDE=$(${GNU_MAKE} --quiet --no-print-directory -C ${PJPROJECT_DIR} EXTERNALS_CACHE_DIR=${EXTERNALS_CACHE_DIR} echo_cflags)
+ PJPROJECT_INCLUDE=$(${GNU_MAKE} --quiet --no-print-directory -C ${PJPROJECT_DIR} PJPROJECT_CONFIGURE_OPTS="$PJPROJECT_CONFIGURE_OPTS" EXTERNALS_CACHE_DIR="${EXTERNALS_CACHE_DIR:-${AST_DOWNLOAD_CACHE}}" echo_cflags)
PJPROJECT_CFLAGS="$PJPROJECT_INCLUDE"
PBX_PJPROJECT=1
diff --git a/third-party/pjproject/patches/0070-Set-PJSIP_INV_SUPPORT_UPDATE-correctly-in-pjsip_inv_.patch b/third-party/pjproject/patches/0070-Set-PJSIP_INV_SUPPORT_UPDATE-correctly-in-pjsip_inv_.patch
new file mode 100644
index 000000000..9238e3ec9
--- /dev/null
+++ b/third-party/pjproject/patches/0070-Set-PJSIP_INV_SUPPORT_UPDATE-correctly-in-pjsip_inv_.patch
@@ -0,0 +1,29 @@
+From 1193681959816effa121c4470748d5faa3a59272 Mon Sep 17 00:00:00 2001
+From: George Joseph <gjoseph@digium.com>
+Date: Thu, 29 Jun 2017 13:42:10 -0600
+Subject: [PATCH] Set PJSIP_INV_SUPPORT_UPDATE correctly in
+ pjsip_inv_verify_request3
+
+pjsip_inv_verify_request3 was setting rem_options when UPDATE was
+detected in the Allow header. That's just an internal variable and
+doesn't go anywhere. It's '*options' that needs to be set.
+---
+ pjsip/src/pjsip-ua/sip_inv.c | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/pjsip/src/pjsip-ua/sip_inv.c b/pjsip/src/pjsip-ua/sip_inv.c
+index fbc8ebe..6db7e6b 100644
+--- a/pjsip/src/pjsip-ua/sip_inv.c
++++ b/pjsip/src/pjsip-ua/sip_inv.c
+@@ -1237,7 +1237,7 @@ PJ_DEF(pj_status_t) pjsip_inv_verify_request3(pjsip_rx_data *rdata,
+
+ if (i != allow->count) {
+ /* UPDATE is present in Allow */
+- rem_option |= PJSIP_INV_SUPPORT_UPDATE;
++ *options |= PJSIP_INV_SUPPORT_UPDATE;
+ }
+
+ }
+--
+2.9.4
+
diff --git a/third-party/pjproject/patches/0075-Fixed-2030-Improve-error-handling-in-OpenSSL-socket.patch b/third-party/pjproject/patches/0075-Fixed-2030-Improve-error-handling-in-OpenSSL-socket.patch
new file mode 100644
index 000000000..1e7035d93
--- /dev/null
+++ b/third-party/pjproject/patches/0075-Fixed-2030-Improve-error-handling-in-OpenSSL-socket.patch
@@ -0,0 +1,247 @@
+From 96c06899d95eaf01d05561554b21e8c63baa7129 Mon Sep 17 00:00:00 2001
+From: ming <ming@localhost>
+Date: Thu, 27 Jul 2017 06:07:54 +0000
+Subject: [PATCH 75/76] Fixed #2030: Improve error handling in OpenSSL socket
+
+---
+ pjlib/src/pj/ssl_sock_ossl.c | 173 ++++++++++++++++++++++++++++++++++++++-----
+ 1 file changed, 156 insertions(+), 17 deletions(-)
+
+diff --git a/pjlib/src/pj/ssl_sock_ossl.c b/pjlib/src/pj/ssl_sock_ossl.c
+index c466b3c..b8175e1 100644
+--- a/pjlib/src/pj/ssl_sock_ossl.c
++++ b/pjlib/src/pj/ssl_sock_ossl.c
+@@ -298,14 +298,104 @@ static pj_status_t flush_delayed_send(pj_ssl_sock_t *ssock);
+ /* Expected maximum value of reason component in OpenSSL error code */
+ #define MAX_OSSL_ERR_REASON 1200
+
+-static pj_status_t STATUS_FROM_SSL_ERR(pj_ssl_sock_t *ssock,
+- unsigned long err)
++
++static char *SSLErrorString (int err)
+ {
+- pj_status_t status;
++ switch (err) {
++ case SSL_ERROR_NONE:
++ return "SSL_ERROR_NONE";
++ case SSL_ERROR_ZERO_RETURN:
++ return "SSL_ERROR_ZERO_RETURN";
++ case SSL_ERROR_WANT_READ:
++ return "SSL_ERROR_WANT_READ";
++ case SSL_ERROR_WANT_WRITE:
++ return "SSL_ERROR_WANT_WRITE";
++ case SSL_ERROR_WANT_CONNECT:
++ return "SSL_ERROR_WANT_CONNECT";
++ case SSL_ERROR_WANT_ACCEPT:
++ return "SSL_ERROR_WANT_ACCEPT";
++ case SSL_ERROR_WANT_X509_LOOKUP:
++ return "SSL_ERROR_WANT_X509_LOOKUP";
++ case SSL_ERROR_SYSCALL:
++ return "SSL_ERROR_SYSCALL";
++ case SSL_ERROR_SSL:
++ return "SSL_ERROR_SSL";
++ default:
++ return "SSL_ERROR_UNKNOWN";
++ }
++}
+
+- /* General SSL error, dig more from OpenSSL error queue */
+- if (err == SSL_ERROR_SSL)
+- err = ERR_get_error();
++#define ERROR_LOG(msg, err) \
++ PJ_LOG(2,("SSL", "%s (%s): Level: %d err: <%lu> <%s-%s-%s> len: %d", \
++ msg, action, level, err, \
++ (ERR_lib_error_string(err)? ERR_lib_error_string(err): "???"), \
++ (ERR_func_error_string(err)? ERR_func_error_string(err):"???"),\
++ (ERR_reason_error_string(err)? \
++ ERR_reason_error_string(err): "???"), len));
++
++static void SSLLogErrors(char * action, int ret, int ssl_err, int len)
++{
++ char *ssl_err_str = SSLErrorString(ssl_err);
++
++ if (!action) {
++ action = "UNKNOWN";
++ }
++
++ switch (ssl_err) {
++ case SSL_ERROR_SYSCALL:
++ {
++ unsigned long err2 = ERR_get_error();
++ if (err2) {
++ int level = 0;
++ while (err2) {
++ ERROR_LOG("SSL_ERROR_SYSCALL", err2);
++ level++;
++ err2 = ERR_get_error();
++ }
++ } else if (ret == 0) {
++ /* An EOF was observed that violates the protocol */
++
++ /* The TLS/SSL handshake was not successful but was shut down
++ * controlled and by the specifications of the TLS/SSL protocol.
++ */
++ } else if (ret == -1) {
++ /* BIO error - look for more info in errno... */
++ char errStr[250] = "";
++ strerror_r(errno, errStr, sizeof(errStr));
++ /* for now - continue logging these if they occur.... */
++ PJ_LOG(4,("SSL", "BIO error, SSL_ERROR_SYSCALL (%s): "
++ "errno: <%d> <%s> len: %d",
++ action, errno, errStr, len));
++ } else {
++ /* ret!=0 & ret!=-1 & nothing on error stack - is this valid??? */
++ PJ_LOG(2,("SSL", "SSL_ERROR_SYSCALL (%s) ret: %d len: %d",
++ action, ret, len));
++ }
++ break;
++ }
++ case SSL_ERROR_SSL:
++ {
++ unsigned long err2 = ERR_get_error();
++ int level = 0;
++
++ while (err2) {
++ ERROR_LOG("SSL_ERROR_SSL", err2);
++ level++;
++ err2 = ERR_get_error();
++ }
++ break;
++ }
++ default:
++ PJ_LOG(2,("SSL", "%lu [%s] (%s) ret: %d len: %d",
++ ssl_err, ssl_err_str, action, ret, len));
++ break;
++ }
++}
++
++
++static pj_status_t GET_STATUS_FROM_SSL_ERR(unsigned long err)
++{
++ pj_status_t status;
+
+ /* OpenSSL error range is much wider than PJLIB errno space, so
+ * if it exceeds the space, only the error reason will be kept.
+@@ -317,13 +407,49 @@ static pj_status_t STATUS_FROM_SSL_ERR(pj_ssl_sock_t *ssock,
+ status = ERR_GET_REASON(err);
+
+ status += PJ_SSL_ERRNO_START;
+- ssock->last_err = err;
+ return status;
+ }
+
++/* err contains ERR_get_error() status */
++static pj_status_t STATUS_FROM_SSL_ERR(char *action, pj_ssl_sock_t *ssock,
++ unsigned long err)
++{
++ int level = 0;
++ int len = 0; //dummy
++
++ ERROR_LOG("STATUS_FROM_SSL_ERR", err);
++ level++;
++
++ /* General SSL error, dig more from OpenSSL error queue */
++ if (err == SSL_ERROR_SSL) {
++ err = ERR_get_error();
++ ERROR_LOG("STATUS_FROM_SSL_ERR", err);
++ }
++
++ ssock->last_err = err;
++ return GET_STATUS_FROM_SSL_ERR(err);
++}
++
++/* err contains SSL_get_error() status */
++static pj_status_t STATUS_FROM_SSL_ERR2(char *action, pj_ssl_sock_t *ssock,
++ int ret, int err, int len)
++{
++ unsigned long ssl_err = err;
++
++ if (err == SSL_ERROR_SSL) {
++ ssl_err = ERR_peek_error();
++ }
++
++ /* Dig for more from OpenSSL error queue */
++ SSLLogErrors(action, ret, err, len);
++
++ ssock->last_err = ssl_err;
++ return GET_STATUS_FROM_SSL_ERR(ssl_err);
++}
++
+ static pj_status_t GET_SSL_STATUS(pj_ssl_sock_t *ssock)
+ {
+- return STATUS_FROM_SSL_ERR(ssock, ERR_get_error());
++ return STATUS_FROM_SSL_ERR("status", ssock, ERR_get_error());
+ }
+
+
+@@ -1514,7 +1640,7 @@ static pj_bool_t on_handshake_complete(pj_ssl_sock_t *ssock,
+ unsigned long err;
+ err = ERR_get_error();
+ if (err != SSL_ERROR_NONE)
+- status = STATUS_FROM_SSL_ERR(ssock, err);
++ status = STATUS_FROM_SSL_ERR("connecting", ssock, err);
+ }
+ reset_ssl_sock_state(ssock);
+ }
+@@ -1833,11 +1959,11 @@ static pj_status_t do_handshake(pj_ssl_sock_t *ssock)
+ }
+
+ if (err < 0) {
+- err = SSL_get_error(ssock->ossl_ssl, err);
+- if (err != SSL_ERROR_NONE && err != SSL_ERROR_WANT_READ)
++ int err2 = SSL_get_error(ssock->ossl_ssl, err);
++ if (err2 != SSL_ERROR_NONE && err2 != SSL_ERROR_WANT_READ)
+ {
+ /* Handshake fails */
+- status = STATUS_FROM_SSL_ERR(ssock, err);
++ status = STATUS_FROM_SSL_ERR2("Handshake", ssock, err, err2, 0);
+ return status;
+ }
+ }
+@@ -1913,6 +2039,7 @@ static pj_bool_t asock_on_data_read (pj_activesock_t *asock,
+ read_data_t *buf = *(OFFSET_OF_READ_DATA_PTR(ssock, data));
+ void *data_ = (pj_int8_t*)buf->data + buf->len;
+ int size_ = (int)(ssock->read_size - buf->len);
++ int len = size_;
+
+ /* SSL_read() may write some data to BIO write when re-negotiation
+ * is on progress, so let's protect it with write mutex.
+@@ -1965,10 +2092,22 @@ static pj_bool_t asock_on_data_read (pj_activesock_t *asock,
+ */
+ if (err != SSL_ERROR_NONE && err != SSL_ERROR_WANT_READ)
+ {
+- /* Reset SSL socket state, then return PJ_FALSE */
+- status = STATUS_FROM_SSL_ERR(ssock, err);
+- reset_ssl_sock_state(ssock);
+- goto on_error;
++ if (err == SSL_ERROR_SYSCALL && size_ == -1 &&
++ ERR_peek_error() == 0 && errno == 0)
++ {
++ status = STATUS_FROM_SSL_ERR2("Read", ssock, size_,
++ err, len);
++ PJ_LOG(4,("SSL", "SSL_read() = -1, with "
++ "SSL_ERROR_SYSCALL, no SSL error, "
++ "and errno = 0 - skip BIO error"));
++ /* Ignore these errors */
++ } else {
++ /* Reset SSL socket state, then return PJ_FALSE */
++ status = STATUS_FROM_SSL_ERR2("Read", ssock, size_,
++ err, len);
++ reset_ssl_sock_state(ssock);
++ goto on_error;
++ }
+ }
+
+ status = do_handshake(ssock);
+@@ -2856,7 +2995,7 @@ static pj_status_t ssl_write(pj_ssl_sock_t *ssock,
+ status = PJ_EBUSY;
+ } else {
+ /* Some problem occured */
+- status = STATUS_FROM_SSL_ERR(ssock, err);
++ status = STATUS_FROM_SSL_ERR2("Write", ssock, nwritten, err, size);
+ }
+ } else {
+ /* nwritten < *size, shouldn't happen, unless write BIO cannot hold
+--
+2.9.4
+
diff --git a/third-party/pjproject/patches/config_site.h b/third-party/pjproject/patches/config_site.h
index a345734b0..561b3a231 100644
--- a/third-party/pjproject/patches/config_site.h
+++ b/third-party/pjproject/patches/config_site.h
@@ -68,7 +68,7 @@
Enabling it will result in SEGFAULTS when URIs containing escape sequences are encountered.
*/
#undef PJSIP_UNESCAPE_IN_PLACE
-#define PJSIP_MAX_PKT_LEN 6000
+#define PJSIP_MAX_PKT_LEN 32000
#undef PJ_TODO
#define PJ_TODO(x)
diff --git a/utils/astman.c b/utils/astman.c
index 315b3b065..9e0373c7d 100644
--- a/utils/astman.c
+++ b/utils/astman.c
@@ -289,7 +289,7 @@ static void rebuild_channels(newtComponent c)
{
void *prev = NULL;
struct ast_chan *chan;
- char tmpn[42];
+ char tmpn[sizeof(chan->name) + sizeof(chan->callerid) + 3 - 1];
char tmp[256];
int x=0;
prev = newtListboxGetCurrent(c);
diff --git a/utils/extconf.c b/utils/extconf.c
index 7989bcd2c..837971ba2 100644
--- a/utils/extconf.c
+++ b/utils/extconf.c
@@ -4590,6 +4590,10 @@ static struct ast_exten *pbx_find_extension(struct ast_channel *chan,
struct ast_exten *e, *eroot;
struct ast_include *i;
+ if (!context) {
+ return NULL;
+ }
+
/* Initialize status if appropriate */
if (q->stacklen == 0) {
q->status = STATUS_NO_CONTEXT;