diff options
181 files changed, 5775 insertions, 1661 deletions
@@ -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 ---------- ------------------------------------------------------------------------------ @@ -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 @@ -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(¤t_ha->addr), sizeof(iabuf2)); ast_debug(1, "##### Testing %s with %s\n", iabuf, iabuf2); #endif if (ast_sockaddr_is_ipv4(¤t_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&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(¬e, 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(®istrar_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, ¶meters, 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; |