summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CHANGES46
-rw-r--r--apps/app_confbridge.c15
-rw-r--r--apps/app_queue.c152
-rw-r--r--apps/app_talkdetect.c2
-rw-r--r--apps/app_voicemail.c113
-rw-r--r--apps/confbridge/conf_chan_announce.c1
-rw-r--r--apps/confbridge/conf_config_parser.c19
-rw-r--r--apps/confbridge/include/confbridge.h1
-rw-r--r--bridges/bridge_softmix.c13
-rw-r--r--channels/chan_sip.c36
-rw-r--r--configs/basic-pbx/asterisk.conf22
-rw-r--r--configs/samples/confbridge.conf.sample2
-rw-r--r--configs/samples/pjsip.conf.sample45
-rw-r--r--configs/samples/sip.conf.sample1
-rwxr-xr-xconfigure107
-rw-r--r--configure.ac4
-rw-r--r--contrib/ast-db-manage/config/versions/65eb22eb195_add_unidentified_request_options_to_.py27
-rw-r--r--contrib/ast-db-manage/config/versions/81b01a191a46_pjsip_add_contact_reg_server.py25
-rw-r--r--contrib/ast-db-manage/config/versions/8d478ab86e29_pjsip_add_disable_multi_domain.py31
-rwxr-xr-xcontrib/scripts/install_prereq36
-rw-r--r--funcs/func_odbc.c396
-rw-r--r--include/asterisk/astobj2.h134
-rw-r--r--include/asterisk/autoconfig.h.in3
-rw-r--r--include/asterisk/bridge_channel_internal.h38
-rw-r--r--include/asterisk/bridge_technology.h3
-rw-r--r--include/asterisk/features.h9
-rw-r--r--include/asterisk/res_pjsip.h272
-rw-r--r--include/asterisk/res_pjsip_pubsub.h4
-rw-r--r--include/asterisk/stasis.h24
-rw-r--r--main/Makefile12
-rw-r--r--main/asterisk.exports.in2
-rw-r--r--main/bridge.c222
-rw-r--r--main/bridge_channel.c31
-rw-r--r--main/config.c34
-rw-r--r--main/config_options.c2
-rw-r--r--main/core_unreal.c1
-rw-r--r--main/features.c1
-rw-r--r--main/file.c24
-rw-r--r--main/format_cap.c2
-rw-r--r--main/lock.c16
-rw-r--r--main/manager.c202
-rw-r--r--main/manager_channels.c24
-rw-r--r--main/stasis_endpoints.c7
-rw-r--r--main/stasis_message.c35
-rw-r--r--makeopts.in3
-rw-r--r--res/res_agi.c40
-rw-r--r--res/res_fax.c54
-rw-r--r--res/res_pjsip.c100
-rw-r--r--res/res_pjsip/config_global.c77
-rw-r--r--res/res_pjsip/include/res_pjsip_private.h19
-rw-r--r--res/res_pjsip/location.c64
-rw-r--r--res/res_pjsip/pjsip_configuration.c56
-rw-r--r--res/res_pjsip/pjsip_distributor.c358
-rw-r--r--res/res_pjsip/pjsip_options.c15
-rw-r--r--res/res_pjsip/pjsip_scheduler.c495
-rw-r--r--res/res_pjsip_authenticator_digest.c28
-rw-r--r--res/res_pjsip_caller_id.c6
-rw-r--r--res/res_pjsip_endpoint_identifier_anonymous.c38
-rw-r--r--res/res_pjsip_endpoint_identifier_ip.c2
-rw-r--r--res/res_pjsip_endpoint_identifier_user.c145
-rw-r--r--res/res_pjsip_exten_state.c8
-rw-r--r--res/res_pjsip_mwi.c10
-rw-r--r--res/res_pjsip_outbound_publish.c2
-rw-r--r--res/res_pjsip_publish_asterisk.c8
-rw-r--r--res/res_pjsip_pubsub.c43
-rw-r--r--res/res_pjsip_registrar.c138
-rw-r--r--res/res_pjsip_transport_management.c18
-rw-r--r--res/stasis/control.c14
-rw-r--r--res/stasis/control.h18
-rw-r--r--res/stasis/stasis_bridge.c61
-rw-r--r--tests/test_channel_feature_hooks.c15
-rw-r--r--tests/test_config.c75
-rw-r--r--tests/test_message.c8
-rw-r--r--tests/test_res_pjsip_scheduler.c400
-rw-r--r--third-party/pjproject/Makefile23
-rw-r--r--third-party/pjproject/Makefile.rules11
-rwxr-xr-xthird-party/pjproject/apply_patches4
-rw-r--r--third-party/pjproject/configure.m46
-rw-r--r--third-party/pjproject/patches/0001-pjsip-apps-src-python-setup.py-Take-make-from-the-en.patch51
-rw-r--r--third-party/pjproject/patches/0001-sip_parser.c-Remove-wholesale-strip-from-parse_param.patch55
-rw-r--r--third-party/pjproject/patches/config_site.h6
81 files changed, 4033 insertions, 637 deletions
diff --git a/CHANGES b/CHANGES
index 382c4a42b..965b1b4be 100644
--- a/CHANGES
+++ b/CHANGES
@@ -9,9 +9,41 @@
==============================================================================
------------------------------------------------------------------------------
+--- Functionality changes from Asterisk 13.9.0 to Asterisk 13.10.0 -----------
+------------------------------------------------------------------------------
+
+res_fax
+------------------
+ * Added FAXMODE variable to let dialplan know what fax transport was used.
+ FAXMODE variable is set to either "audio" or "T38".
+
+res_pjsip
+------------------
+ * Added new status Updated to AMI event ContactStatus on update registration
+
+ * Added "reg_server" to contacts.
+ If the Asterisk system name is set in asterisk.conf, it will be stored
+ into the "reg_server" field in the ps_contacts table to facilitate
+ multi-server setups.
+
+app_confbridge
+------------------
+ * Added a bridge profile option called regcontext that allows you to
+ dynamically register the conference bridge name as an extension into
+ the specified context. This allows tracking down conferences on multi-
+ server installations via alternate means (DUNDI for example). By default
+ this feature is not used.
+
+------------------------------------------------------------------------------
--- Functionality changes from Asterisk 13.8.0 to Asterisk 13.9.0 ------------
------------------------------------------------------------------------------
+res_pjsip
+------------------
+ * Added new global option (disable_multi_domain) to pjsip.
+ Disabling Multi Domain can improve realtime performace by reducing
+ number of database requsts.
+
chan_pjsip
------------------
* Added 'pjsip show channelstats' CLI command.
@@ -124,6 +156,20 @@ res_pjsip
dynamically create and destroy a NoOp priority 1 extension
for a given endpoint who registers or unregisters with us.
+ * Endpoints and aors can now be identified by the username and realm in an
+ incoming Authorization header. To use this feature, add "auth_username"
+ to your endpoint's "identify_by" list. You can combine "auth_username"
+ and the original "username" to test both the From/To and Authorization
+ headers. For endpoints, the order is controlled by the global
+ "endpoint_identifier_order" setting. For matching aors to an endpoint
+ for inbound registration, the order is controlled by this option.
+
+ * In conjunction with the "auth_username" change, 3 new options have been
+ added to the global configuration object that control how many unidentified
+ requests over a certain period from the same IP address can be received
+ before a security altert is generated. A new CLI command
+ "pjsip show unidentified_requests" will list the current candidates.
+
res_pjsip_history
------------------
* A new module, res_pjsip_history, has been added that provides SIP history
diff --git a/apps/app_confbridge.c b/apps/app_confbridge.c
index 55b7b1240..991b3a307 100644
--- a/apps/app_confbridge.c
+++ b/apps/app_confbridge.c
@@ -1253,9 +1253,17 @@ void conf_handle_second_active(struct confbridge_conference *conference)
void conf_ended(struct confbridge_conference *conference)
{
+ struct pbx_find_info q = { .stacklen = 0 };
+
/* Called with a reference to conference */
ao2_unlink(conference_bridges, conference);
send_conf_end_event(conference);
+ if (!ast_strlen_zero(conference->b_profile.regcontext) &&
+ pbx_find_extension(NULL, NULL, &q, conference->b_profile.regcontext,
+ conference->name, 1, NULL, "", E_MATCH)) {
+ ast_context_remove_extension(conference->b_profile.regcontext,
+ conference->name, 1, NULL);
+ }
ao2_lock(conference);
conf_stop_record(conference);
ao2_unlock(conference);
@@ -1360,6 +1368,13 @@ static struct confbridge_conference *join_conference_bridge(const char *conferen
}
send_conf_start_event(conference);
+
+ if (!ast_strlen_zero(conference->b_profile.regcontext)) {
+ if (!ast_exists_extension(NULL, conference->b_profile.regcontext, conference->name, 1, NULL)) {
+ ast_add_extension(conference->b_profile.regcontext, 1, conference->name, 1, NULL, NULL, "Noop", NULL, NULL, "ConfBridge");
+ }
+ }
+
ast_debug(1, "Created conference '%s' and linked to container.\n", conference_name);
}
diff --git a/apps/app_queue.c b/apps/app_queue.c
index 939a0e2ad..dbd83938d 100644
--- a/apps/app_queue.c
+++ b/apps/app_queue.c
@@ -1510,7 +1510,6 @@ struct member {
struct call_queue *lastqueue; /*!< Last queue we received a call */
unsigned int dead:1; /*!< Used to detect members deleted in realtime */
unsigned int delme:1; /*!< Flag to delete entry on reload */
- unsigned int call_pending:1; /*!< TRUE if the Q is attempting to place a call to the member. */
char rt_uniqueid[80]; /*!< Unique id of realtime member entry */
unsigned int ringinuse:1; /*!< Flag to ring queue members even if their status is 'inuse' */
};
@@ -2267,6 +2266,70 @@ static int get_member_status(struct call_queue *q, int max_penalty, int min_pena
return -1;
}
+/*
+ * A "pool" of member objects that calls are currently pending on. If an
+ * agent is a member of multiple queues it's possible for that agent to be
+ * called by each of the queues at the same time. This happens because device
+ * state is slow to notify the queue app of one of it's member's being rung.
+ * This "pool" allows us to track which members are currently being rung while
+ * we wait on the device state change.
+ */
+static struct ao2_container *pending_members;
+#define MAX_CALL_ATTEMPT_BUCKETS 353
+
+static int pending_members_hash(const void *obj, const int flags)
+{
+ const struct member *object;
+ const char *key;
+
+ switch (flags & OBJ_SEARCH_MASK) {
+ case OBJ_SEARCH_KEY:
+ key = obj;
+ break;
+ case OBJ_SEARCH_OBJECT:
+ object = obj;
+ key = object->interface;
+ break;
+ default:
+ ast_assert(0);
+ return 0;
+ }
+ return ast_str_case_hash(key);
+}
+
+static int pending_members_cmp(void *obj, void *arg, int flags)
+{
+ const struct member *object_left = obj;
+ const struct member *object_right = arg;
+ const char *right_key = arg;
+ int cmp;
+
+ switch (flags & OBJ_SEARCH_MASK) {
+ case OBJ_SEARCH_OBJECT:
+ right_key = object_right->interface;
+ /* Fall through */
+ case OBJ_SEARCH_KEY:
+ cmp = strcasecmp(object_left->interface, right_key);
+ break;
+ case OBJ_SEARCH_PARTIAL_KEY:
+ /* Not supported by container. */
+ ast_assert(0);
+ return 0;
+ default:
+ cmp = 0;
+ break;
+ }
+ if (cmp) {
+ return 0;
+ }
+ return CMP_MATCH;
+}
+
+static void pending_members_remove(struct member *mem)
+{
+ ao2_find(pending_members, mem, OBJ_POINTER | OBJ_NODATA | OBJ_UNLINK);
+}
+
/*! \brief set a member's status based on device state of that member's state_interface.
*
* Lock interface list find sc, iterate through each queues queue_member list for member to
@@ -2276,6 +2339,9 @@ static void update_status(struct call_queue *q, struct member *m, const int stat
{
m->status = status;
+ /* Whatever the status is clear the member from the pending members pool */
+ pending_members_remove(m);
+
queue_publish_member_blob(queue_member_status_type(), queue_member_blob_create(q, m));
}
@@ -3132,6 +3198,7 @@ static void member_add_to_queue(struct call_queue *queue, struct member *mem)
*/
static void member_remove_from_queue(struct call_queue *queue, struct member *mem)
{
+ pending_members_remove(mem);
ao2_lock(queue->members);
ast_devstate_changed(QUEUE_UNKNOWN_PAUSED_DEVSTATE, AST_DEVSTATE_CACHABLE, "Queue:%s_pause_%s", queue->name, mem->interface);
queue_member_follower_removal(queue, mem);
@@ -4110,41 +4177,6 @@ static int member_status_available(int status)
/*!
* \internal
- * \brief Clear the member call pending flag.
- *
- * \param mem Queue member.
- *
- * \return Nothing
- */
-static void member_call_pending_clear(struct member *mem)
-{
- ao2_lock(mem);
- mem->call_pending = 0;
- ao2_unlock(mem);
-}
-
-/*!
- * \internal
- * \brief Set the member call pending flag.
- *
- * \param mem Queue member.
- *
- * \retval non-zero if call pending flag was already set.
- */
-static int member_call_pending_set(struct member *mem)
-{
- int old_pending;
-
- ao2_lock(mem);
- old_pending = mem->call_pending;
- mem->call_pending = 1;
- ao2_unlock(mem);
-
- return old_pending;
-}
-
-/*!
- * \internal
* \brief Determine if can ring a queue entry.
*
* \param qe Queue entry to check.
@@ -4164,7 +4196,7 @@ static int can_ring_entry(struct queue_ent *qe, struct callattempt *call)
return 0;
}
- if (call->member->in_call && call->lastqueue->wrapuptime) {
+ if (call->member->in_call && call->lastqueue && call->lastqueue->wrapuptime) {
ast_debug(1, "%s is in call, so not available (wrapuptime %d)\n",
call->interface, call->lastqueue->wrapuptime);
return 0;
@@ -4185,13 +4217,32 @@ static int can_ring_entry(struct queue_ent *qe, struct callattempt *call)
}
if (!call->member->ringinuse) {
- if (member_call_pending_set(call->member)) {
- ast_debug(1, "%s has another call pending, can't receive call\n",
- call->interface);
+ struct member *mem;
+
+ ao2_lock(pending_members);
+
+ mem = ao2_find(pending_members, call->member,
+ OBJ_SEARCH_OBJECT | OBJ_NOLOCK);
+ if (mem) {
+ /*
+ * If found that means this member is currently being attempted
+ * from another calling thread, so stop trying from this thread
+ */
+ ast_debug(1, "%s has another call trying, can't receive call\n",
+ call->interface);
+ ao2_ref(mem, -1);
+ ao2_unlock(pending_members);
return 0;
}
/*
+ * If not found add it to the container so another queue
+ * won't attempt to call this member at the same time.
+ */
+ ao2_link(pending_members, call->member);
+ ao2_unlock(pending_members);
+
+ /*
* The queue member is available. Get current status to be sure
* because the device state and extension state callbacks may
* not have updated the status yet.
@@ -4199,7 +4250,7 @@ static int can_ring_entry(struct queue_ent *qe, struct callattempt *call)
if (!member_status_available(get_queue_member_status(call->member))) {
ast_debug(1, "%s actually not available, can't receive call\n",
call->interface);
- member_call_pending_clear(call->member);
+ pending_members_remove(call->member);
return 0;
}
}
@@ -4236,7 +4287,6 @@ static int ring_entry(struct queue_ent *qe, struct callattempt *tmp, int *busies
++*busies;
return 0;
}
- ast_assert(tmp->member->ringinuse || tmp->member->call_pending);
ast_copy_string(tech, tmp->interface, sizeof(tech));
if ((location = strchr(tech, '/'))) {
@@ -4253,7 +4303,7 @@ static int ring_entry(struct queue_ent *qe, struct callattempt *tmp, int *busies
qe->linpos++;
ao2_unlock(qe->parent);
- member_call_pending_clear(tmp->member);
+ pending_members_remove(tmp->member);
publish_dial_end_event(qe->chan, tmp, NULL, "BUSY");
tmp->stillgoing = 0;
@@ -4324,7 +4374,7 @@ static int ring_entry(struct queue_ent *qe, struct callattempt *tmp, int *busies
/* Again, keep going even if there's an error */
ast_verb(3, "Couldn't call %s\n", tmp->interface);
do_hang(tmp);
- member_call_pending_clear(tmp->member);
+ pending_members_remove(tmp->member);
++*busies;
return 0;
}
@@ -4344,7 +4394,6 @@ static int ring_entry(struct queue_ent *qe, struct callattempt *tmp, int *busies
ast_verb(3, "Called %s\n", tmp->interface);
- member_call_pending_clear(tmp->member);
return 1;
}
@@ -9472,7 +9521,7 @@ static int manager_queues_summary(struct mansession *s, const struct message *m)
ao2_lock(q);
/* List queue properties */
- if (ast_strlen_zero(queuefilter) || !strcmp(q->name, queuefilter)) {
+ if (ast_strlen_zero(queuefilter) || !strcasecmp(q->name, queuefilter)) {
/* Reset the necessary local variables if no queuefilter is set*/
qmemcount = 0;
qmemavail = 0;
@@ -9550,7 +9599,7 @@ static int manager_queues_status(struct mansession *s, const struct message *m)
ao2_lock(q);
/* List queue properties */
- if (ast_strlen_zero(queuefilter) || !strcmp(q->name, queuefilter)) {
+ if (ast_strlen_zero(queuefilter) || !strcasecmp(q->name, queuefilter)) {
sl = ((q->callscompleted > 0) ? 100 * ((float)q->callscompletedinsl / (float)q->callscompleted) : 0);
astman_append(s, "Event: QueueParams\r\n"
"Queue: %s\r\n"
@@ -10806,6 +10855,8 @@ static int unload_module(void)
ast_unload_realtime("queue_members");
ao2_cleanup(queues);
+ ao2_cleanup(pending_members);
+
queues = NULL;
return 0;
}
@@ -10833,6 +10884,13 @@ static int load_module(void)
return AST_MODULE_LOAD_DECLINE;
}
+ pending_members = ao2_container_alloc(
+ MAX_CALL_ATTEMPT_BUCKETS, pending_members_hash, pending_members_cmp);
+ if (!pending_members) {
+ unload_module();
+ return AST_MODULE_LOAD_DECLINE;
+ }
+
use_weight = 0;
if (reload_handler(0, &mask, NULL)) {
diff --git a/apps/app_talkdetect.c b/apps/app_talkdetect.c
index a021252de..f7086fdd9 100644
--- a/apps/app_talkdetect.c
+++ b/apps/app_talkdetect.c
@@ -26,7 +26,7 @@
*/
/*** MODULEINFO
- <support_level>extended</support_level>
+ <support_level>core</support_level>
***/
#include "asterisk.h"
diff --git a/apps/app_voicemail.c b/apps/app_voicemail.c
index 798f844fa..c94513d28 100644
--- a/apps/app_voicemail.c
+++ b/apps/app_voicemail.c
@@ -622,12 +622,12 @@ static AST_LIST_HEAD_STATIC(vmstates, vmstate);
#define OPERATOR_EXIT 300
enum vm_box {
- NEW_FOLDER,
- OLD_FOLDER,
- WORK_FOLDER,
- FAMILY_FOLDER,
- FRIENDS_FOLDER,
- GREETINGS_FOLDER
+ NEW_FOLDER = 0,
+ OLD_FOLDER = 1,
+ WORK_FOLDER = 2,
+ FAMILY_FOLDER = 3,
+ FRIENDS_FOLDER = 4,
+ GREETINGS_FOLDER = -1
};
enum vm_option_flags {
@@ -1725,13 +1725,14 @@ static struct ast_vm_user *find_user(struct ast_vm_user *ivm, const char *contex
}
if (cur) {
/* Make a copy, so that on a reload, we have no race */
- if ((vmu = (ivm ? ivm : ast_malloc(sizeof(*vmu))))) {
+ if ((vmu = (ivm ? ivm : ast_calloc(1, sizeof(*vmu))))) {
+ ast_free(vmu->email);
+ ast_free(vmu->emailbody);
+ ast_free(vmu->emailsubject);
*vmu = *cur;
- if (!ivm) {
- vmu->email = ast_strdup(cur->email);
- vmu->emailbody = ast_strdup(cur->emailbody);
- vmu->emailsubject = ast_strdup(cur->emailsubject);
- }
+ vmu->email = ast_strdup(cur->email);
+ vmu->emailbody = ast_strdup(cur->emailbody);
+ vmu->emailsubject = ast_strdup(cur->emailsubject);
ast_set2_flag(vmu, !ivm, VM_ALLOCED);
AST_LIST_NEXT(vmu, list) = NULL;
}
@@ -2009,17 +2010,18 @@ static int get_folder_by_name(const char *name)
static void free_user(struct ast_vm_user *vmu)
{
- if (ast_test_flag(vmu, VM_ALLOCED)) {
-
- ast_free(vmu->email);
- vmu->email = NULL;
-
- ast_free(vmu->emailbody);
- vmu->emailbody = NULL;
+ if (!vmu) {
+ return;
+ }
- ast_free(vmu->emailsubject);
- vmu->emailsubject = NULL;
+ ast_free(vmu->email);
+ vmu->email = NULL;
+ ast_free(vmu->emailbody);
+ vmu->emailbody = NULL;
+ ast_free(vmu->emailsubject);
+ vmu->emailsubject = NULL;
+ if (ast_test_flag(vmu, VM_ALLOCED)) {
ast_free(vmu);
}
}
@@ -2457,14 +2459,17 @@ static int __messagecount(const char *context, const char *mailbox, const char *
return 0;
/* We have to get the user before we can open the stream! */
+ memset(&vmus, 0, sizeof(vmus));
vmu = find_user(&vmus, context, mailbox);
if (!vmu) {
ast_log(AST_LOG_WARNING, "Couldn't find mailbox %s in context %s\n", mailbox, context);
+ free_user(vmu);
return -1;
} else {
/* No IMAP account available */
if (vmu->imapuser[0] == '\0') {
ast_log(AST_LOG_WARNING, "IMAP user not set for mailbox %s\n", vmu->mailbox);
+ free_user(vmu);
return -1;
}
}
@@ -2484,9 +2489,11 @@ static int __messagecount(const char *context, const char *mailbox, const char *
if (vms_p) {
ast_debug(3, "Returning before search - user is logged in\n");
if (fold == 0) { /* INBOX */
+ free_user(vmu);
return urgent ? vms_p->urgentmessages : vms_p->newmessages;
}
if (fold == 1) { /* Old messages */
+ free_user(vmu);
return vms_p->oldmessages;
}
}
@@ -2503,6 +2510,7 @@ static int __messagecount(const char *context, const char *mailbox, const char *
ret = init_mailstream(vms_p, fold);
if (!vms_p->mailstream) {
ast_log(AST_LOG_ERROR, "Houston we have a problem - IMAP mailstream is NULL\n");
+ free_user(vmu);
return -1;
}
if (ret == 0) {
@@ -2546,6 +2554,7 @@ static int __messagecount(const char *context, const char *mailbox, const char *
/*Freeing the searchpgm also frees the searchhdr*/
mail_free_searchpgm(&pgm);
ast_mutex_unlock(&vms_p->lock);
+ free_user(vmu);
vms_p->updated = 0;
return vms_p->vmArrayIndex;
} else {
@@ -2553,6 +2562,7 @@ static int __messagecount(const char *context, const char *mailbox, const char *
mail_ping(vms_p->mailstream);
ast_mutex_unlock(&vms_p->lock);
}
+ free_user(vmu);
return 0;
}
@@ -6185,6 +6195,7 @@ static int msg_create_from_file(struct ast_vm_recording_data *recdata)
return -1;
}
+ memset(&svm, 0, sizeof(svm));
if (!(recipient = find_user(&svm, recdata->context, recdata->mailbox))) {
ast_log(LOG_ERROR, "No entry in voicemail config file for '%s@%s'\n", recdata->mailbox, recdata->context);
return -1;
@@ -6500,6 +6511,7 @@ static int leave_voicemail(struct ast_channel *chan, char *ext, struct leave_vm_
}
ast_debug(3, "Before find_user\n");
+ memset(&svm, 0, sizeof(svm));
if (!(vmu = find_user(&svm, context, ext))) {
ast_log(AST_LOG_WARNING, "No entry in voicemail config file for '%s'\n", ext);
pbx_builtin_setvar_helper(chan, "VMSTATUS", "FAILED");
@@ -6529,6 +6541,7 @@ static int leave_voicemail(struct ast_channel *chan, char *ext, struct leave_vm_
snprintf(tempfile, sizeof(tempfile), "%s%s/%s/temp", VM_SPOOL_DIR, vmu->context, ext);
if ((res = create_dirpath(tmpdir, sizeof(tmpdir), vmu->context, ext, "tmp"))) {
ast_log(AST_LOG_WARNING, "Failed to make directory (%s)\n", tempfile);
+ free_user(vmu);
ast_free(tmp);
return -1;
}
@@ -6672,9 +6685,9 @@ static int leave_voicemail(struct ast_channel *chan, char *ext, struct leave_vm_
}
ast_play_and_wait(chan, "transfer");
ast_channel_priority_set(chan, 0);
- free_user(vmu);
pbx_builtin_setvar_helper(chan, "VMSTATUS", "USEREXIT");
}
+ free_user(vmu);
ast_free(tmp);
return OPERATOR_EXIT;
}
@@ -6708,6 +6721,7 @@ static int leave_voicemail(struct ast_channel *chan, char *ext, struct leave_vm_
res = inboxcount(ext_context, &newmsgs, &oldmsgs);
if (res < 0) {
ast_log(AST_LOG_NOTICE, "Can not leave voicemail, unable to count messages\n");
+ free_user(vmu);
ast_free(tmp);
return -1;
}
@@ -6718,6 +6732,7 @@ static int leave_voicemail(struct ast_channel *chan, char *ext, struct leave_vm_
*/
if (!(vms = create_vm_state_from_user(vmu))) {
ast_log(AST_LOG_ERROR, "Couldn't allocate necessary space\n");
+ free_user(vmu);
ast_free(tmp);
return -1;
}
@@ -6912,6 +6927,7 @@ static int leave_voicemail(struct ast_channel *chan, char *ext, struct leave_vm_
*cntx = '\0';
cntx++;
}
+ memset(&recipu, 0, sizeof(recipu));
if ((recip = find_user(&recipu, cntx, exten))) {
copy_message(chan, vmu, 0, msgnum, duration, recip, fmt, dir, flag, NULL);
free_user(recip);
@@ -10939,6 +10955,7 @@ static int vm_authenticate(struct ast_channel *chan, char *mailbox, int mailbox_
}
ast_debug(1, "Before find user for mailbox %s\n", mailbox);
+ memset(&vmus, 0, sizeof(vmus));
vmu = find_user(&vmus, context, mailbox);
if (vmu && (vmu->password[0] == '\0' || (vmu->password[0] == '-' && vmu->password[1] == '\0'))) {
/* saved password is blank, so don't bother asking */
@@ -10946,10 +10963,12 @@ static int vm_authenticate(struct ast_channel *chan, char *mailbox, int mailbox_
} else {
if (ast_streamfile(chan, vm_password, ast_channel_language(chan))) {
ast_log(AST_LOG_WARNING, "Unable to stream password file\n");
+ free_user(vmu);
return -1;
}
if (ast_readstring(chan, password, sizeof(password) - 1, 2000, 10000, "#") < 0) {
ast_log(AST_LOG_WARNING, "Unable to read password\n");
+ free_user(vmu);
return -1;
} else if (password[0] == '*') {
/* user entered '*' */
@@ -10957,11 +10976,13 @@ static int vm_authenticate(struct ast_channel *chan, char *mailbox, int mailbox_
if (ast_exists_extension(chan, ast_channel_context(chan), "a", 1,
S_COR(ast_channel_caller(chan)->id.number.valid, ast_channel_caller(chan)->id.number.str, NULL))) {
mailbox[0] = '*';
+ free_user(vmu);
return -1;
}
ast_verb(4, "Jump to extension 'a' failed; setting mailbox and user to NULL\n");
mailbox[0] = '\0';
/* if the password entered was '*', do not let a user mailbox be created if the extension 'a' is not defined */
+ free_user(vmu);
vmu = NULL;
}
}
@@ -10982,6 +11003,7 @@ static int vm_authenticate(struct ast_channel *chan, char *mailbox, int mailbox_
if (skipuser || logretries >= max_logins) {
if (ast_streamfile(chan, "vm-incorrect", ast_channel_language(chan))) {
ast_log(AST_LOG_WARNING, "Unable to stream incorrect message\n");
+ free_user(vmu);
return -1;
}
} else {
@@ -10989,16 +11011,20 @@ static int vm_authenticate(struct ast_channel *chan, char *mailbox, int mailbox_
adsi_login(chan);
if (ast_streamfile(chan, "vm-incorrect-mailbox", ast_channel_language(chan))) {
ast_log(AST_LOG_WARNING, "Unable to stream incorrect mailbox message\n");
+ free_user(vmu);
return -1;
}
}
- if (ast_waitstream(chan, "")) /* Channel is hung up */
+ if (ast_waitstream(chan, "")) { /* Channel is hung up */
+ free_user(vmu);
return -1;
+ }
}
}
if (!valid && (logretries >= max_logins)) {
ast_stopstream(chan);
ast_play_and_wait(chan, "vm-goodbye");
+ free_user(vmu);
return -1;
}
if (vmu && !skipuser) {
@@ -11106,6 +11132,8 @@ play_msg_cleanup:
}
#endif
+ free_user(vmu);
+
return res;
}
@@ -12301,7 +12329,7 @@ AST_TEST_DEFINE(test_voicemail_vmuser)
static int vm_box_exists(struct ast_channel *chan, const char *data)
{
- struct ast_vm_user svm;
+ struct ast_vm_user svm, *vmu;
char *context, *box;
AST_DECLARE_APP_ARGS(args,
AST_APP_ARG(mbox);
@@ -12331,8 +12359,10 @@ static int vm_box_exists(struct ast_channel *chan, const char *data)
context++;
}
- if (find_user(&svm, context, args.mbox)) {
+ vmu = find_user(&svm, context, args.mbox);
+ if (vmu) {
pbx_builtin_setvar_helper(chan, "VMBOXEXISTSSTATUS", "SUCCESS");
+ free_user(vmu);
} else
pbx_builtin_setvar_helper(chan, "VMBOXEXISTSSTATUS", "FAILED");
@@ -12341,7 +12371,7 @@ static int vm_box_exists(struct ast_channel *chan, const char *data)
static int acf_mailbox_exists(struct ast_channel *chan, const char *cmd, char *args, char *buf, size_t len)
{
- struct ast_vm_user svm;
+ struct ast_vm_user svm, *vmu;
AST_DECLARE_APP_ARGS(arg,
AST_APP_ARG(mbox);
AST_APP_ARG(context);
@@ -12360,7 +12390,10 @@ static int acf_mailbox_exists(struct ast_channel *chan, const char *cmd, char *a
ast_log(AST_LOG_WARNING, "MAILBOX_EXISTS is deprecated. Please use ${VM_INFO(%s,exists)} instead.\n", args);
}
- ast_copy_string(buf, find_user(&svm, ast_strlen_zero(arg.context) ? "default" : arg.context, arg.mbox) ? "1" : "0", len);
+ vmu = find_user(&svm, ast_strlen_zero(arg.context) ? "default" : arg.context, arg.mbox);
+ ast_copy_string(buf, vmu ? "1" : "0", len);
+ free_user(vmu);
+
return 0;
}
@@ -12396,10 +12429,12 @@ static int acf_vm_info(struct ast_channel *chan, const char *cmd, char *args, ch
return -1;
}
+ memset(&svm, 0, sizeof(svm));
vmu = find_user(&svm, context, mailbox);
if (!strncasecmp(arg.attribute, "exists", 5)) {
ast_copy_string(buf, vmu ? "1" : "0", len);
+ free_user(vmu);
return 0;
}
@@ -12428,13 +12463,16 @@ static int acf_vm_info(struct ast_channel *chan, const char *cmd, char *args, ch
res = messagecount(mailbox_id, arg.folder);
if (res < 0) {
ast_log(LOG_ERROR, "Unable to retrieve message count for mailbox %s\n", arg.mailbox_context);
+ free_user(vmu);
return -1;
}
snprintf(buf, len, "%d", res);
} else {
ast_log(LOG_ERROR, "Unknown attribute '%s' for VM_INFO\n", arg.attribute);
+ free_user(vmu);
return -1;
}
+ free_user(vmu);
}
return 0;
@@ -14248,6 +14286,7 @@ AST_TEST_DEFINE(test_voicemail_msgcount)
}
#endif
+ memset(&svm, 0, sizeof(svm));
if (!(vmu = find_user(&svm, testcontext, testmailbox)) &&
!(vmu = find_or_create(testcontext, testmailbox))) {
ast_test_status_update(test, "Cannot create vmu structure\n");
@@ -14277,6 +14316,7 @@ AST_TEST_DEFINE(test_voicemail_msgcount)
#ifdef IMAP_STORAGE
chan = ast_channel_unref(chan);
#endif
+ free_user(vmu);
return AST_TEST_FAIL;
}
}
@@ -14360,6 +14400,7 @@ AST_TEST_DEFINE(test_voicemail_msgcount)
syserr > 0 ? strerror(syserr) : "unable to fork()");
}
+ free_user(vmu);
return res;
}
@@ -14469,6 +14510,7 @@ AST_TEST_DEFINE(test_voicemail_notify_endl)
}
}
fclose(file);
+ free_user(vmu);
return res;
}
@@ -14629,6 +14671,7 @@ AST_TEST_DEFINE(test_voicemail_vm_info)
}
chan = ast_channel_unref(chan);
+ free_user(vmu);
return res;
}
#endif /* defined(TEST_FRAMEWORK) */
@@ -15020,8 +15063,10 @@ static int advanced_options(struct ast_channel *chan, struct ast_vm_user *vmu, s
ast_config_destroy(msg_cfg);
return res;
} else {
- struct ast_vm_user vmu2;
- if (find_user(&vmu2, vmu->context, num)) {
+ struct ast_vm_user vmu2, *vmu3;
+ memset(&vmu2, 0, sizeof(vmu2));
+ vmu3 = find_user(&vmu2, vmu->context, num);
+ if (vmu3) {
struct leave_vm_options leave_options;
char mailbox[AST_MAX_EXTENSION * 2 + 2];
snprintf(mailbox, sizeof(mailbox), "%s@%s", num, vmu->context);
@@ -15034,6 +15079,7 @@ static int advanced_options(struct ast_channel *chan, struct ast_vm_user *vmu, s
if (!res)
res = 't';
ast_config_destroy(msg_cfg);
+ free_user(vmu3);
return res;
} else {
/* Sender has no mailbox, can't reply */
@@ -15528,11 +15574,13 @@ static struct ast_vm_mailbox_snapshot *vm_mailbox_snapshot_create(const char *ma
if (!(mailbox_snapshot = ast_calloc(1, sizeof(*mailbox_snapshot)))) {
ast_log(AST_LOG_ERROR, "Failed to allocate memory for mailbox snapshot\n");
+ free_user(vmu);
return NULL;
}
if (!(mailbox_snapshot->snapshots = ast_calloc(ARRAY_LEN(mailbox_folders), sizeof(*mailbox_snapshot->snapshots)))) {
ast_free(mailbox_snapshot);
+ free_user(vmu);
return NULL;
}
@@ -15593,6 +15641,7 @@ snapshot_cleanup:
}
#endif
+ free_user(vmu);
return mailbox_snapshot;
}
@@ -15747,6 +15796,7 @@ static int vm_msg_forward(const char *from_mailbox,
if (!(to_vmu = find_user(&to_vmus, to_context, to_mailbox))) {
ast_log(LOG_WARNING, "Can't find voicemail user to forward to (%s@%s)\n", to_mailbox, to_context);
+ free_user(vmu);
return -1;
}
@@ -15827,6 +15877,8 @@ vm_forward_cleanup:
notify_new_state(to_vmu);
}
+ free_user(vmu);
+ free_user(to_vmu);
return res;
}
@@ -15930,6 +15982,7 @@ vm_move_cleanup:
notify_new_state(vmu);
}
+ free_user(vmu);
return res;
}
@@ -16027,6 +16080,7 @@ vm_remove_cleanup:
notify_new_state(vmu);
}
+ free_user(vmu);
return res;
}
@@ -16140,6 +16194,7 @@ play2_msg_cleanup:
notify_new_state(vmu);
}
+ free_user(vmu);
return res;
}
diff --git a/apps/confbridge/conf_chan_announce.c b/apps/confbridge/conf_chan_announce.c
index 6596a8537..ee4660687 100644
--- a/apps/confbridge/conf_chan_announce.c
+++ b/apps/confbridge/conf_chan_announce.c
@@ -199,7 +199,6 @@ int conf_announce_channel_push(struct ast_channel *ast)
/* Impart the output channel into the bridge */
if (ast_bridge_impart(p->bridge, chan, NULL, features,
AST_BRIDGE_IMPART_CHAN_DEPARTABLE)) {
- ast_bridge_features_destroy(features);
ast_channel_unref(chan);
return -1;
}
diff --git a/apps/confbridge/conf_config_parser.c b/apps/confbridge/conf_config_parser.c
index b8b1e2a9c..f5bb7eb57 100644
--- a/apps/confbridge/conf_config_parser.c
+++ b/apps/confbridge/conf_config_parser.c
@@ -317,6 +317,22 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
on a conference.
</para></description>
</configOption>
+ <configOption name="regcontext">
+ <synopsis>The name of the context into which to register the name of the conference bridge as NoOP() at priority 1</synopsis>
+ <description><para>
+ When set this will cause the name of the created conference to be registered
+ into the named context at priority 1 with an operation of NoOP(). This can
+ then be used in other parts of the dialplan to test for the existence of a
+ specific conference bridge.
+ You should be aware that there are potential races between testing for the
+ existence of a bridge, and taking action upon that information, consider
+ for example two callers executing the check simultaniously, and then taking
+ special action as "first caller" into the bridge. The same for exiting,
+ directly after the check the bridge can be destroyed before the new caller
+ enters (creating a new bridge), for example, and the "first member" actions
+ could thus be missed.
+ </para></description>
+ </configOption>
<configOption name="video_mode">
<synopsis>Sets how confbridge handles video distribution to the conference participants</synopsis>
<description><para>
@@ -1563,6 +1579,8 @@ static char *handle_cli_confbridge_show_bridge_profile(struct ast_cli_entry *e,
ast_cli(a->fd,"Max Members: No Limit\n");
}
+ ast_cli(a->fd,"Registration context: %s\n", b_profile.regcontext);
+
switch (b_profile.flags
& (BRIDGE_OPT_VIDEO_SRC_LAST_MARKED | BRIDGE_OPT_VIDEO_SRC_FIRST_MARKED
| BRIDGE_OPT_VIDEO_SRC_FOLLOW_TALKER)) {
@@ -2128,6 +2146,7 @@ int conf_load_config(void)
aco_option_register(&cfg_info, "record_file_append", ACO_EXACT, bridge_types, "yes", OPT_BOOLFLAG_T, 1, FLDSET(struct bridge_profile, flags), BRIDGE_OPT_RECORD_FILE_APPEND);
aco_option_register(&cfg_info, "max_members", ACO_EXACT, bridge_types, "0", OPT_UINT_T, 0, FLDSET(struct bridge_profile, max_members));
aco_option_register(&cfg_info, "record_file", ACO_EXACT, bridge_types, NULL, OPT_CHAR_ARRAY_T, 0, CHARFLDSET(struct bridge_profile, rec_file));
+ aco_option_register(&cfg_info, "regcontext", ACO_EXACT, bridge_types, NULL, OPT_CHAR_ARRAY_T, 0, CHARFLDSET(struct bridge_profile, regcontext));
aco_option_register(&cfg_info, "language", ACO_EXACT, bridge_types, "en", OPT_CHAR_ARRAY_T, 0, CHARFLDSET(struct bridge_profile, language));
aco_option_register_custom(&cfg_info, "^sound_", ACO_REGEX, bridge_types, NULL, sound_option_handler, 0);
/* This option should only be used with the CONFBRIDGE dialplan function */
diff --git a/apps/confbridge/include/confbridge.h b/apps/confbridge/include/confbridge.h
index 8d2dffb1c..a1fa5a2b7 100644
--- a/apps/confbridge/include/confbridge.h
+++ b/apps/confbridge/include/confbridge.h
@@ -207,6 +207,7 @@ struct bridge_profile {
unsigned int internal_sample_rate; /*!< The internal sample rate of the bridge. 0 when set to auto adjust mode. */
unsigned int mix_interval; /*!< The internal mixing interval used by the bridge. When set to 0 the bridgewill use a default interval. */
struct bridge_profile_sounds *sounds;
+ char regcontext[AST_MAX_CONTEXT];
};
/*! \brief The structure that represents a conference bridge */
diff --git a/bridges/bridge_softmix.c b/bridges/bridge_softmix.c
index e3df18fe5..fe058e4e6 100644
--- a/bridges/bridge_softmix.c
+++ b/bridges/bridge_softmix.c
@@ -359,6 +359,9 @@ static void set_softmix_bridge_data(int rate, int interval, struct ast_bridge_ch
struct ast_format *slin_format;
int setup_fail;
+ /* The callers have already ensured that sc is never NULL. */
+ ast_assert(sc != NULL);
+
slin_format = ast_format_cache_get_slin_by_rate(rate);
ast_mutex_lock(&sc->lock);
@@ -714,7 +717,7 @@ static int softmix_bridge_write(struct ast_bridge *bridge, struct ast_bridge_cha
{
int res = 0;
- if (!bridge->tech_pvt || (bridge_channel && !bridge_channel->tech_pvt)) {
+ if (!bridge->tech_pvt || !bridge_channel || !bridge_channel->tech_pvt) {
/* "Accept" the frame and discard it. */
return 0;
}
@@ -984,6 +987,11 @@ static int softmix_mixing_loop(struct ast_bridge *bridge)
AST_LIST_TRAVERSE(&bridge->channels, bridge_channel, entry) {
struct softmix_channel *sc = bridge_channel->tech_pvt;
+ if (!sc) {
+ /* This channel failed to join successfully. */
+ continue;
+ }
+
/* Update the sample rate to match the bridge's native sample rate if necessary. */
if (update_all_rates) {
set_softmix_bridge_data(softmix_data->internal_rate, softmix_data->internal_mixing_interval, bridge_channel, 1);
@@ -1019,7 +1027,8 @@ static int softmix_mixing_loop(struct ast_bridge *bridge)
AST_LIST_TRAVERSE(&bridge->channels, bridge_channel, entry) {
struct softmix_channel *sc = bridge_channel->tech_pvt;
- if (bridge_channel->suspended) {
+ if (!sc || bridge_channel->suspended) {
+ /* This channel failed to join successfully or is suspended. */
continue;
}
diff --git a/channels/chan_sip.c b/channels/chan_sip.c
index 91fb0b546..7cc91486a 100644
--- a/channels/chan_sip.c
+++ b/channels/chan_sip.c
@@ -17669,6 +17669,10 @@ static enum check_auth_result register_verify(struct sip_pvt *p, struct ast_sock
if (!peer && sip_cfg.autocreatepeer != AUTOPEERS_DISABLED) {
/* Create peer if we have autocreate mode enabled */
peer = temp_peer(name);
+ if (peer && !(peer->endpoint = ast_endpoint_create("SIP", name))) {
+ ao2_t_ref(peer, -1, "failed to allocate Stasis endpoint, drop peer");
+ peer = NULL;
+ }
if (peer) {
ao2_t_link(peers, peer, "link peer into peer table");
if (!ast_sockaddr_isnull(&peer->addr)) {
@@ -35170,17 +35174,19 @@ static int load_module(void)
/* And start the monitor for the first time */
restart_monitor();
- ast_realtime_require_field(ast_check_realtime("sipregs") ? "sipregs" : "sippeers",
- "name", RQ_CHAR, 10,
- "ipaddr", RQ_CHAR, INET6_ADDRSTRLEN - 1,
- "port", RQ_UINTEGER2, 5,
- "regseconds", RQ_INTEGER4, 11,
- "defaultuser", RQ_CHAR, 10,
- "fullcontact", RQ_CHAR, 35,
- "regserver", RQ_CHAR, 20,
- "useragent", RQ_CHAR, 20,
- "lastms", RQ_INTEGER4, 11,
- SENTINEL);
+ if (sip_cfg.peer_rtupdate) {
+ ast_realtime_require_field(ast_check_realtime("sipregs") ? "sipregs" : "sippeers",
+ "name", RQ_CHAR, 10,
+ "ipaddr", RQ_CHAR, INET6_ADDRSTRLEN - 1,
+ "port", RQ_UINTEGER2, 5,
+ "regseconds", RQ_INTEGER4, 11,
+ "defaultuser", RQ_CHAR, 10,
+ "fullcontact", RQ_CHAR, 35,
+ "regserver", RQ_CHAR, 20,
+ "useragent", RQ_CHAR, 20,
+ "lastms", RQ_INTEGER4, 11,
+ SENTINEL);
+ }
sip_register_tests();
@@ -35199,7 +35205,7 @@ static int unload_module(void)
struct sip_pvt *p;
struct sip_threadinfo *th;
struct ao2_iterator i;
- int wait_count;
+ struct timeval start;
ast_sip_api_provider_unregister();
@@ -35349,11 +35355,11 @@ static int unload_module(void)
* joinable. They can die on their own and remove themselves
* from the container thus resulting in a huge memory leak.
*/
- wait_count = 1000;
- while (ao2_container_count(threadt) && --wait_count) {
+ start = ast_tvnow();
+ while (ao2_container_count(threadt) && (ast_tvdiff_sec(ast_tvnow(), start) < 5)) {
sched_yield();
}
- if (!wait_count) {
+ if (ao2_container_count(threadt)) {
ast_debug(2, "TCP/TLS thread container did not become empty :(\n");
}
diff --git a/configs/basic-pbx/asterisk.conf b/configs/basic-pbx/asterisk.conf
index 3ee7b99b8..576cc976b 100644
--- a/configs/basic-pbx/asterisk.conf
+++ b/configs/basic-pbx/asterisk.conf
@@ -1,15 +1,15 @@
[directories]
-astetcdir = /etc/asterisk
-astmoddir = /usr/lib/asterisk/modules
-astvarlibdir = /var/lib/asterisk
-astdbdir = /var/lib/asterisk
-astkeydir = /var/lib/asterisk
-astdatadir = /var/lib/asterisk
-astagidir = /var/lib/asterisk/agi-bin
-astspooldir = /var/spool/asterisk
-astrundir = /var/run/asterisk
-astlogdir = /var/log/asterisk
-astsbindir = /usr/sbin
+astetcdir => /etc/asterisk
+astmoddir => /usr/lib/asterisk/modules
+astvarlibdir => /var/lib/asterisk
+astdbdir => /var/lib/asterisk
+astkeydir => /var/lib/asterisk
+astdatadir => /var/lib/asterisk
+astagidir => /var/lib/asterisk/agi-bin
+astspooldir => /var/spool/asterisk
+astrundir => /var/run/asterisk
+astlogdir => /var/log/asterisk
+astsbindir => /usr/sbin
[options]
; If we want to start Asterisk with a default verbosity for the verbose
diff --git a/configs/samples/confbridge.conf.sample b/configs/samples/confbridge.conf.sample
index d0bdd6fd9..49208c31b 100644
--- a/configs/samples/confbridge.conf.sample
+++ b/configs/samples/confbridge.conf.sample
@@ -211,6 +211,8 @@ type=bridge
;language=en ; Set the language used for announcements to the conference.
; Default is en (English).
+;regcontext=conferences ; The name of the context into which to register conference names as extensions.
+
; All sounds in the conference are customizable using the bridge profile options below.
; Simply state the option followed by the filename or full path of the filename after
; the option. Example: sound_had_joined=conf-hasjoin This will play the conf-hasjoin
diff --git a/configs/samples/pjsip.conf.sample b/configs/samples/pjsip.conf.sample
index 0b321b43c..a17dab7a7 100644
--- a/configs/samples/pjsip.conf.sample
+++ b/configs/samples/pjsip.conf.sample
@@ -620,8 +620,13 @@
; the specified address. (default: "no")
;force_rport=yes ; Force use of return port (default: "yes")
;ice_support=no ; Enable the ICE mechanism to help traverse NAT (default: "no")
-;identify_by=username ; Way s for Endpoint to be identified (default:
- ; "username")
+;identify_by=username ; A comma-separated list of ways the Endpoint or AoR can be
+ ; identified.
+ ; "username": Identify by the From or To username and domain
+ ; "auth_username": Identify by the Authorization username and realm
+ : In all cases, if an exact match on username and domain/realm fails,
+ ; the match will be retried with just the username.
+ ; (default: "username")
;redirect_method=user ; How redirects received from an endpoint are handled
; (default: "user")
;mailboxes= ; NOTIFY the endpoint when state changes for any of the specified mailboxes.
@@ -899,10 +904,19 @@
; (default: "0")
;contact_expiration_check_interval=30
; The interval (in seconds) to check for expired contacts.
+;disable_multi_domain=no
+ ; Disable Multi Domain support.
+ ; If disabled it can improve realtime performace by reducing
+ ; number of database requsts
+ ; (default: "no")
;endpoint_identifier_order=ip,username,anonymous
; The order by which endpoint identifiers are given priority.
- ; Identifier names are derived from res_pjsip_endpoint_identifier_*
- ; modules. (default: ip,username,anonymous)
+ ; Currently, "ip", "username", "auth_username" and "anonymous" are valid
+ ; identifiers as registered by the res_pjsip_endpoint_identifier_* modules.
+ ; Some modules like res_pjsip_endpoint_identifier_user register more than
+ ; one identifier. Use the CLI command "pjsip show identifiers" to see the
+ ; identifiers currently available.
+ ; (default: ip,username,anonymous)
;max_initial_qualify_time=4 ; The maximum amount of time (in seconds) from
; startup that qualifies should be attempted on all
; contacts. If greater than the qualify_frequency
@@ -915,7 +929,28 @@
; The voicemail extension to send in the NOTIFY Message-Account header
; if not set on endpoint or aor.
; (default: "")
-
+;
+; The following unidentified_request options are only used when "auth_username"
+; matching is enabled in "endpoint_identifier_order".
+;
+;unidentified_request_count=5 ; The number of unidentified requests that can be
+ ; received from a single IP address in
+ ; unidentified_request_period seconds before a security
+ ; event is generated. (default: 5)
+;unidentified_request_period=5 ; See above. (default: 5 seconds)
+;unidentified_request_prune_interval=30
+ ; The interval at which unidentified requests
+ ; are check to see if they can be pruned. If they're
+ ; older than twice the unidentified_request_period,
+ ; they're pruned.
+;
+;default_from_user=asterisk ; When Asterisk generates an outgoing SIP request, the
+ ; From header username will be set to this value if
+ ; there is no better option (such as CallerID or
+ ; endpoint/from_user) to be used
+;default_realm=asterisk ; When Asterisk generates a challenge, the realm will be
+ ; set to this value if there is no better option (such as
+ ; auth/realm) to be used
; MODULE PROVIDING BELOW SECTION(S): res_pjsip_acl
;==========================ACL SECTION OPTIONS=========================
diff --git a/configs/samples/sip.conf.sample b/configs/samples/sip.conf.sample
index a24ab30a6..5c3238e2a 100644
--- a/configs/samples/sip.conf.sample
+++ b/configs/samples/sip.conf.sample
@@ -1479,7 +1479,6 @@ srvlookup=yes ; Enable DNS SRV lookups on outbound calls
;allow=ulaw
;allow=alaw
;mailbox=1234@default,1233@default ; Subscribe to status of multiple mailboxes
-;registertrying=yes ; Send a 100 Trying when the device registers.
;[snom]
;type=friend ; Friends place calls and receive calls
diff --git a/configure b/configure
index 14c5a0eba..01604e0bb 100755
--- a/configure
+++ b/configure
@@ -663,6 +663,10 @@ PWLIB_LIBDIR
PWLIB_INCDIR
PWLIBDIR
PTLIB_CONFIG
+PYTHONDEV_LIBS
+PYTHONDEV_CFLAGS
+PYTHONDEV_INCLUDE
+PYTHONDEV_LIB
PJPROJECT_LIBS
PJPROJECT_CFLAGS
PG_CONFIG
@@ -1416,6 +1420,8 @@ LIBEDIT_CFLAGS
LIBEDIT_LIBS
PJPROJECT_CFLAGS
PJPROJECT_LIBS
+PYTHONDEV_CFLAGS
+PYTHONDEV_LIBS
GMIME_CFLAGS
GMIME_LIBS
GTK2_CFLAGS
@@ -2158,6 +2164,10 @@ Some influential environment variables:
C compiler flags for PJPROJECT, overriding pkg-config
PJPROJECT_LIBS
linker flags for PJPROJECT, overriding pkg-config
+ PYTHONDEV_CFLAGS
+ C compiler flags for PYTHONDEV, overriding pkg-config
+ PYTHONDEV_LIBS
+ linker flags for PYTHONDEV, overriding pkg-config
GMIME_CFLAGS
C compiler flags for GMIME, overriding pkg-config
GMIME_LIBS linker flags for GMIME, overriding pkg-config
@@ -24221,16 +24231,16 @@ if test "$USE_PJPROJECT" != "no" ; then
$as_echo_n "checking for embedded pjproject (may have to download)... " >&6; }
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: configuring" >&5
$as_echo "configuring" >&6; }
- make --quiet --no-print-directory -C $PJPROJECT_DIR configure
+ ${GNU_MAKE} --quiet --no-print-directory -C $PJPROJECT_DIR configure
if test $? -ne 0 ; then
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: failed" >&5
$as_echo "failed" >&6; }
{ $as_echo "$as_me:${as_lineno-$LINENO}: Unable to configure $PJPROJECT_DIR" >&5
$as_echo "$as_me: Unable to configure $PJPROJECT_DIR" >&6;}
- as_fn_error $? "Run \"make -C $PJPROJECT_DIR NOISY_BUILD=yes configure\" to see error details." "$LINENO" 5
+ as_fn_error $? "Run \"${GNU_MAKE} -C $PJPROJECT_DIR NOISY_BUILD=yes configure\" to see error details." "$LINENO" 5
fi
- PJPROJECT_INCLUDE=$(make --quiet --no-print-directory -C $PJPROJECT_DIR echo_cflags)
+ PJPROJECT_INCLUDE=$(${GNU_MAKE} --quiet --no-print-directory -C $PJPROJECT_DIR echo_cflags)
PJPROJECT_CFLAGS="$PJPROJECT_INCLUDE"
PBX_PJPROJECT=1
PJPROJECT_BUNDLED=yes
@@ -25172,6 +25182,97 @@ rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
fi
+
+
+ if test "x${PBX_PYTHONDEV}" != "x1" -a "${USE_PYTHONDEV}" != "no"; then
+
+pkg_failed=no
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for PYTHONDEV" >&5
+$as_echo_n "checking for PYTHONDEV... " >&6; }
+
+if test -n "$PYTHONDEV_CFLAGS"; then
+ pkg_cv_PYTHONDEV_CFLAGS="$PYTHONDEV_CFLAGS"
+ elif test -n "$PKG_CONFIG"; then
+ if test -n "$PKG_CONFIG" && \
+ { { $as_echo "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"python\""; } >&5
+ ($PKG_CONFIG --exists --print-errors "python") 2>&5
+ ac_status=$?
+ $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
+ test $ac_status = 0; }; then
+ pkg_cv_PYTHONDEV_CFLAGS=`$PKG_CONFIG --cflags "python" 2>/dev/null`
+ test "x$?" != "x0" && pkg_failed=yes
+else
+ pkg_failed=yes
+fi
+ else
+ pkg_failed=untried
+fi
+if test -n "$PYTHONDEV_LIBS"; then
+ pkg_cv_PYTHONDEV_LIBS="$PYTHONDEV_LIBS"
+ elif test -n "$PKG_CONFIG"; then
+ if test -n "$PKG_CONFIG" && \
+ { { $as_echo "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"python\""; } >&5
+ ($PKG_CONFIG --exists --print-errors "python") 2>&5
+ ac_status=$?
+ $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
+ test $ac_status = 0; }; then
+ pkg_cv_PYTHONDEV_LIBS=`$PKG_CONFIG --libs "python" 2>/dev/null`
+ test "x$?" != "x0" && pkg_failed=yes
+else
+ pkg_failed=yes
+fi
+ else
+ pkg_failed=untried
+fi
+
+
+
+if test $pkg_failed = yes; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+
+if $PKG_CONFIG --atleast-pkgconfig-version 0.20; then
+ _pkg_short_errors_supported=yes
+else
+ _pkg_short_errors_supported=no
+fi
+ if test $_pkg_short_errors_supported = yes; then
+ PYTHONDEV_PKG_ERRORS=`$PKG_CONFIG --short-errors --print-errors --cflags --libs "python" 2>&1`
+ else
+ PYTHONDEV_PKG_ERRORS=`$PKG_CONFIG --print-errors --cflags --libs "python" 2>&1`
+ fi
+ # Put the nasty error message in config.log where it belongs
+ echo "$PYTHONDEV_PKG_ERRORS" >&5
+
+
+ PBX_PYTHONDEV=0
+
+
+elif test $pkg_failed = untried; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+
+ PBX_PYTHONDEV=0
+
+
+else
+ PYTHONDEV_CFLAGS=$pkg_cv_PYTHONDEV_CFLAGS
+ PYTHONDEV_LIBS=$pkg_cv_PYTHONDEV_LIBS
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
+$as_echo "yes" >&6; }
+
+ PBX_PYTHONDEV=1
+ PYTHONDEV_INCLUDE="$PYTHONDEV_CFLAGS"
+ PYTHONDEV_LIB="$PYTHONDEV_LIBS"
+
+$as_echo "#define HAVE_PYTHONDEV 1" >>confdefs.h
+
+
+fi
+ fi
+
+
+
if test "x${PBX_POPT}" != "x1" -a "${USE_POPT}" != "no"; then
pbxlibdir=""
# if --with-POPT=DIR has been specified, use it.
diff --git a/configure.ac b/configure.ac
index 7571f7fd5..822303708 100644
--- a/configure.ac
+++ b/configure.ac
@@ -2197,6 +2197,10 @@ if test "$USE_PJPROJECT" != "no" ; then
fi
fi
+AC_SUBST([PYTHONDEV_LIB])
+AC_SUBST([PYTHONDEV_INCLUDE])
+AST_PKG_CONFIG_CHECK([PYTHONDEV], [python])
+
AST_EXT_LIB_CHECK([POPT], [popt], [poptStrerror], [popt.h])
AST_EXT_LIB_CHECK([PORTAUDIO], [portaudio], [Pa_GetDeviceCount], [portaudio.h])
diff --git a/contrib/ast-db-manage/config/versions/65eb22eb195_add_unidentified_request_options_to_.py b/contrib/ast-db-manage/config/versions/65eb22eb195_add_unidentified_request_options_to_.py
new file mode 100644
index 000000000..e0453a57c
--- /dev/null
+++ b/contrib/ast-db-manage/config/versions/65eb22eb195_add_unidentified_request_options_to_.py
@@ -0,0 +1,27 @@
+"""Add unidentified request options to global
+
+Revision ID: 65eb22eb195
+Revises: 8d478ab86e29
+Create Date: 2016-03-11 11:58:51.567959
+
+"""
+
+# revision identifiers, used by Alembic.
+revision = '65eb22eb195'
+down_revision = '8d478ab86e29'
+
+from alembic import op
+import sqlalchemy as sa
+
+
+def upgrade():
+ op.add_column('ps_globals', sa.Column('unidentified_request_count', sa.Integer))
+ op.add_column('ps_globals', sa.Column('unidentified_request_period', sa.Integer))
+ op.add_column('ps_globals', sa.Column('unidentified_request_prune_interval', sa.Integer))
+ op.add_column('ps_globals', sa.Column('default_realm', sa.String(40)))
+
+def downgrade():
+ op.drop_column('ps_globals', 'unidentified_request_count')
+ op.drop_column('ps_globals', 'unidentified_request_period')
+ op.drop_column('ps_globals', 'unidentified_request_prune_interval')
+ op.drop_column('ps_globals', 'default_realm')
diff --git a/contrib/ast-db-manage/config/versions/81b01a191a46_pjsip_add_contact_reg_server.py b/contrib/ast-db-manage/config/versions/81b01a191a46_pjsip_add_contact_reg_server.py
new file mode 100644
index 000000000..c25fc7233
--- /dev/null
+++ b/contrib/ast-db-manage/config/versions/81b01a191a46_pjsip_add_contact_reg_server.py
@@ -0,0 +1,25 @@
+"""pjsip: add contact reg_server
+
+Revision ID: 81b01a191a46
+Revises: 65eb22eb195
+Create Date: 2016-04-15 15:00:35.024525
+
+"""
+
+# revision identifiers, used by Alembic.
+revision = '81b01a191a46'
+down_revision = '65eb22eb195'
+
+from alembic import op
+import sqlalchemy as sa
+
+
+def upgrade():
+ op.add_column('ps_contacts', sa.Column('reg_server', sa.String(20)))
+ op.drop_constraint(UniqueConstraint('id'), 'ps_contacts', type_='unique')
+ op.create_unique_constraint('ps_contacts_uq', 'ps_contacts', ['id','reg_server'])
+
+def downgrade():
+ op.drop_constraint('ps_contacts_uq', 'ps_contacts', type_='unique')
+ op.drop_column('ps_contacts', 'reg_server')
+ op.create_unique_constraint(None, 'ps_contacts', 'id')
diff --git a/contrib/ast-db-manage/config/versions/8d478ab86e29_pjsip_add_disable_multi_domain.py b/contrib/ast-db-manage/config/versions/8d478ab86e29_pjsip_add_disable_multi_domain.py
new file mode 100644
index 000000000..a78268584
--- /dev/null
+++ b/contrib/ast-db-manage/config/versions/8d478ab86e29_pjsip_add_disable_multi_domain.py
@@ -0,0 +1,31 @@
+"""pjsip_add_disable_multi_domain
+
+Revision ID: 8d478ab86e29
+Revises: 1c688d9a003c
+Create Date: 2016-04-15 11:41:26.988997
+
+"""
+
+# revision identifiers, used by Alembic.
+revision = '8d478ab86e29'
+down_revision = '1c688d9a003c'
+
+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_globals', sa.Column('disable_multi_domain', yesno_values))
+
+
+def downgrade():
+ op.drop_column('ps_globals', 'disable_multi_domain')
diff --git a/contrib/scripts/install_prereq b/contrib/scripts/install_prereq
index 1682558ac..bda28e9f7 100755
--- a/contrib/scripts/install_prereq
+++ b/contrib/scripts/install_prereq
@@ -29,13 +29,15 @@ PACKAGES_DEBIAN="$PACKAGES_DEBIAN libopenh323-dev libvpb-dev libgtk2.0-dev libmy
PACKAGES_DEBIAN="$PACKAGES_DEBIAN libsnmp-dev libiksemel-dev libcorosync-dev libnewt-dev libpopt-dev libical-dev libspandsp-dev libjack-dev"
PACKAGES_DEBIAN="$PACKAGES_DEBIAN libresample-dev libc-client-dev binutils-dev libsrtp-dev libgsm1-dev libedit-dev doxygen libjansson-dev libldap-dev"
PACKAGES_DEBIAN="$PACKAGES_DEBIAN subversion git libxslt1-dev automake libsrtp-dev libncurses5-dev python-dev"
-PACKAGES_RH="automake gcc gcc-c++ ncurses-devel openssl-devel libxml2-devel unixODBC-devel libcurl-devel libogg-devel libvorbis-devel speex-devel"
+PACKAGES_RH="automake bzip2 gcc gcc-c++ patch ncurses-devel openssl-devel libxml2-devel unixODBC-devel libcurl-devel libogg-devel libvorbis-devel speex-devel"
PACKAGES_RH="$PACKAGES_RH spandsp-devel freetds-devel net-snmp-devel iksemel-devel corosynclib-devel newt-devel popt-devel libtool-ltdl-devel lua-devel"
PACKAGES_RH="$PACKAGES_RH sqlite-devel libsqlite3x-devel radiusclient-ng-devel portaudio-devel postgresql-devel libresample-devel neon-devel libical-devel"
PACKAGES_RH="$PACKAGES_RH openldap-devel gmime22-devel sqlite2-devel mysql-devel bluez-libs-devel jack-audio-connection-kit-devel gsm-devel libedit-devel libuuid-devel"
PACKAGES_RH="$PACKAGES_RH jansson-devel libsrtp-devel pjproject-devel subversion git libxslt-devel python-devel"
PACKAGES_OBSD="popt gmake wget libxml libogg libvorbis curl iksemel spandsp speex iodbc freetds-0.63p1-msdblib mysql-client gmime sqlite sqlite3 jack libxslt"
+PACKAGES_FBSD="autoconf gcc binutils popt gmake wget libxml2 libogg libvorbis curl iksemel spandsp speex unixODBC freetds-devel mysql55-client gmime2 sqlite"
+PACKAGES_FBSD="$PACKAGES_FBSD sqlite3 libxslt jansson e2fsprogs-libuuid gsm libsrtp libsamplerate"
KVERS=`uname -r`
@@ -95,23 +97,45 @@ check_installed_pkgs() {
done
}
+check_installed_fpkgs() {
+ for pack in "$@"
+ do
+ if [ `pkg info -a | grep $pack | wc -l` = 0 ]; then
+ echo $pack
+ fi
+ done
+}
+
handle_debian() {
if ! [ -x "$(command -v aptitude)" ]; then
apt-get install aptitude
fi
extra_packs=`check_installed_debs $PACKAGES_DEBIAN`
$testcmd aptitude update
- $testcmd aptitude install -y $extra_packs
+ if [ x"$extra_packs" != "x" ] ; then
+ $testcmd aptitude install -y $extra_packs
+ fi
}
handle_rh() {
extra_packs=`check_installed_rpms $PACKAGES_RH`
- $testcmd yum install -y $extra_packs
+ if [ x"$extra_packs" != "x" ] ; then
+ $testcmd yum install -y $extra_packs
+ fi
}
handle_obsd() {
extra_packs=`check_installed_pkgs $PACKAGES_OBSD`
- $testcmd pkg_add $extra_packs
+ if [ x"$extra_packs" != "x" ] ; then
+ $testcmd pkg_add $extra_packs
+ fi
+}
+
+handle_fbsd() {
+ extra_packs=`check_installed_fpkgs $PACKAGES_FBSD`
+ if [ x"$extra_packs" != "x" ] ; then
+ $testcmd pkg install -y $extra_packs
+ fi
}
install_unpackaged() {
@@ -188,7 +212,7 @@ OS=`uname -s`
unsupported_distro=''
# A number of distributions we don't (yet?) support.
-if [ "$OS" != 'Linux' -a "$OS" != 'OpenBSD' ]; then
+if [ "$OS" != 'Linux' -a "$OS" != 'OpenBSD' -a "$OS" != 'FreeBSD' ]; then
echo >&2 "$0: Your OS ($OS) is currently not supported. Aborting."
exit 1
fi
@@ -221,6 +245,8 @@ elif [ -r /etc/redhat-release ]; then
handle_rh
elif [ "$OS" = 'OpenBSD' ]; then
handle_obsd
+elif [ "$OS" = 'FreeBSD' ]; then
+ handle_fbsd
fi
if ! in_test_mode; then
diff --git a/funcs/func_odbc.c b/funcs/func_odbc.c
index 0af3fd1c8..aa84c00c7 100644
--- a/funcs/func_odbc.c
+++ b/funcs/func_odbc.c
@@ -137,6 +137,218 @@ struct odbc_datastore {
char names[0];
};
+/* \brief Data source name
+ *
+ * This holds data that pertains to a DSN
+ */
+struct dsn {
+ /*! A connection to the database */
+ struct odbc_obj *connection;
+ /*! The name of the DSN as defined in res_odbc.conf */
+ char name[0];
+};
+
+#define DSN_BUCKETS 37
+
+struct ao2_container *dsns;
+
+static int dsn_hash(const void *obj, const int flags)
+{
+ const struct dsn *object;
+ const char *key;
+
+ switch (flags & OBJ_SEARCH_MASK) {
+ case OBJ_SEARCH_KEY:
+ key = obj;
+ break;
+ case OBJ_SEARCH_OBJECT:
+ object = obj;
+ key = object->name;
+ break;
+ default:
+ ast_assert(0);
+ return 0;
+ }
+ return ast_str_hash(key);
+}
+
+static int dsn_cmp(void *obj, void *arg, int flags)
+{
+ const struct dsn *object_left = obj;
+ const struct dsn *object_right = arg;
+ const char *right_key = arg;
+ int cmp;
+
+ switch (flags & OBJ_SEARCH_MASK) {
+ case OBJ_SEARCH_OBJECT:
+ right_key = object_right->name;
+ /* Fall through */
+ case OBJ_SEARCH_KEY:
+ cmp = strcmp(object_left->name, right_key);
+ break;
+ case OBJ_SEARCH_PARTIAL_KEY:
+ cmp = strncmp(object_left->name, right_key, strlen(right_key));
+ break;
+ default:
+ cmp = 0;
+ break;
+ }
+
+ if (cmp) {
+ return 0;
+ }
+
+ return CMP_MATCH;
+}
+
+static void dsn_destructor(void *obj)
+{
+ struct dsn *dsn = obj;
+
+ if (dsn->connection) {
+ ast_odbc_release_obj(dsn->connection);
+ }
+}
+
+/*!
+ * \brief Create a DSN and connect to the database
+ *
+ * \param name The name of the DSN as found in res_odbc.conf
+ * \retval NULL Fail
+ * \retval non-NULL The newly-created structure
+ */
+static struct dsn *create_dsn(const char *name)
+{
+ struct dsn *dsn;
+
+ dsn = ao2_alloc(sizeof(*dsn) + strlen(name) + 1, dsn_destructor);
+ if (!dsn) {
+ return NULL;
+ }
+
+ /* Safe */
+ strcpy(dsn->name, name);
+
+ dsn->connection = ast_odbc_request_obj(name, 0);
+ if (!dsn->connection) {
+ ao2_ref(dsn, -1);
+ return NULL;
+ }
+
+ if (!ao2_link_flags(dsns, dsn, OBJ_NOLOCK)) {
+ ao2_ref(dsn, -1);
+ return NULL;
+ }
+
+ return dsn;
+}
+
+static SQLHSTMT silent_execute(struct odbc_obj *obj, void *data);
+
+/*!
+ * \brief Determine if the connection has died.
+ *
+ * \param connection The connection to check
+ * \retval 1 Yep, it's dead
+ * \retval 0 It's alive and well
+ */
+static int connection_dead(struct odbc_obj *connection)
+{
+ SQLINTEGER dead;
+ SQLRETURN res;
+ SQLHSTMT stmt;
+
+ if (!connection) {
+ return 1;
+ }
+
+ res = SQLGetConnectAttr(connection->con, SQL_ATTR_CONNECTION_DEAD, &dead, 0, 0);
+ if (SQL_SUCCEEDED(res)) {
+ return dead == SQL_CD_TRUE ? 1 : 0;
+ }
+
+ /* If the Driver doesn't support SQL_ATTR_CONNECTION_DEAD do a direct
+ * execute of a probing statement and see if that succeeds instead
+ */
+ stmt = ast_odbc_direct_execute(connection, silent_execute, "SELECT 1");
+ if (!stmt) {
+ return 1;
+ }
+
+ SQLFreeHandle(SQL_HANDLE_STMT, stmt);
+ return 0;
+}
+
+/*!
+ * \brief Retrieve a DSN, or create it if it does not exist.
+ *
+ * The created DSN is returned locked. This should be inconsequential
+ * to callers in most cases.
+ *
+ * When finished with the returned structure, the caller must call
+ * \ref release_dsn
+ *
+ * \param name Name of the DSN as found in res_odbc.conf
+ * \retval NULL Unable to retrieve or create the DSN
+ * \retval non-NULL The retrieved/created locked DSN
+ */
+static struct dsn *get_dsn(const char *name)
+{
+ struct dsn *dsn;
+
+ ao2_lock(dsns);
+ dsn = ao2_find(dsns, name, OBJ_SEARCH_KEY | OBJ_NOLOCK);
+ if (!dsn) {
+ dsn = create_dsn(name);
+ }
+ ao2_unlock(dsns);
+
+ if (!dsn) {
+ return NULL;
+ }
+
+ ao2_lock(dsn);
+ if (!dsn->connection) {
+ dsn->connection = ast_odbc_request_obj(name, 0);
+ if (!dsn->connection) {
+ ao2_unlock(dsn);
+ ao2_ref(dsn, -1);
+ return NULL;
+ }
+ return dsn;
+ }
+
+ if (connection_dead(dsn->connection)) {
+ ast_odbc_release_obj(dsn->connection);
+ dsn->connection = ast_odbc_request_obj(name, 0);
+ if (!dsn->connection) {
+ ao2_unlock(dsn);
+ ao2_ref(dsn, -1);
+ return NULL;
+ }
+ }
+
+ return dsn;
+}
+
+/*!
+ * \brief Unlock and unreference a DSN
+ *
+ * \param dsn The dsn to unlock and unreference
+ * \return NULL
+ */
+static void *release_dsn(struct dsn *dsn)
+{
+ if (!dsn) {
+ return NULL;
+ }
+
+ ao2_unlock(dsn);
+ ao2_ref(dsn, -1);
+
+ return NULL;
+}
+
static AST_RWLIST_HEAD_STATIC(queries, acf_odbc_query);
static int resultcount = 0;
@@ -166,7 +378,16 @@ static void odbc_datastore_free(void *data)
ast_free(result);
}
-static SQLHSTMT generic_execute(struct odbc_obj *obj, void *data)
+/*!
+ * \brief Common execution function for SQL queries.
+ *
+ * \param obj DB connection
+ * \param data The query to execute
+ * \param silent If true, do not print warnings on failure
+ * \retval NULL Failed to execute query
+ * \retval non-NULL The executed statement
+ */
+static SQLHSTMT execute(struct odbc_obj *obj, void *data, int silent)
{
int res;
char *sql = data;
@@ -180,7 +401,7 @@ static SQLHSTMT generic_execute(struct odbc_obj *obj, void *data)
res = SQLExecDirect(stmt, (unsigned char *)sql, SQL_NTS);
if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO) && (res != SQL_NO_DATA)) {
- if (res == SQL_ERROR) {
+ if (res == SQL_ERROR && !silent) {
int i;
SQLINTEGER nativeerror=0, numfields=0;
SQLSMALLINT diagbytes=0;
@@ -197,7 +418,9 @@ static SQLHSTMT generic_execute(struct odbc_obj *obj, void *data)
}
}
- ast_log(LOG_WARNING, "SQL Exec Direct failed (%d)![%s]\n", res, sql);
+ if (!silent) {
+ ast_log(LOG_WARNING, "SQL Exec Direct failed (%d)![%s]\n", res, sql);
+ }
SQLCloseCursor(stmt);
SQLFreeHandle(SQL_HANDLE_STMT, stmt);
return NULL;
@@ -206,6 +429,16 @@ static SQLHSTMT generic_execute(struct odbc_obj *obj, void *data)
return stmt;
}
+static SQLHSTMT generic_execute(struct odbc_obj *obj, void *data)
+{
+ return execute(obj, data, 0);
+}
+
+static SQLHSTMT silent_execute(struct odbc_obj *obj, void *data)
+{
+ return execute(obj, data, 1);
+}
+
/*
* Master control routine
*/
@@ -214,7 +447,7 @@ static int acf_odbc_write(struct ast_channel *chan, const char *cmd, char *s, co
struct odbc_obj *obj = NULL;
struct acf_odbc_query *query;
char *t, varname[15];
- int i, dsn, bogus_chan = 0;
+ int i, dsn_num, bogus_chan = 0;
int transactional = 0;
AST_DECLARE_APP_ARGS(values,
AST_APP_ARG(field)[100];
@@ -227,6 +460,7 @@ static int acf_odbc_write(struct ast_channel *chan, const char *cmd, char *s, co
struct ast_str *buf = ast_str_thread_get(&sql_buf, 16);
struct ast_str *insertbuf = ast_str_thread_get(&sql2_buf, 16);
const char *status = "FAILURE";
+ struct dsn *dsn = NULL;
if (!buf || !insertbuf) {
return -1;
@@ -324,17 +558,21 @@ static int acf_odbc_write(struct ast_channel *chan, const char *cmd, char *s, co
* to multiple DSNs. We MUST have a single handle all the way through the
* transaction, or else we CANNOT enforce atomicity.
*/
- for (dsn = 0; dsn < 5; dsn++) {
- if (!ast_strlen_zero(query->writehandle[dsn])) {
+ for (dsn_num = 0; dsn_num < 5; dsn_num++) {
+ if (!ast_strlen_zero(query->writehandle[dsn_num])) {
if (transactional) {
/* This can only happen second time through or greater. */
ast_log(LOG_WARNING, "Transactions do not work well with multiple DSNs for 'writehandle'\n");
}
- if ((obj = ast_odbc_retrieve_transaction_obj(chan, query->writehandle[dsn]))) {
+ if ((obj = ast_odbc_retrieve_transaction_obj(chan, query->writehandle[dsn_num]))) {
transactional = 1;
} else {
- obj = ast_odbc_request_obj(query->writehandle[dsn], 0);
+ dsn = get_dsn(query->writehandle[dsn_num]);
+ if (!dsn) {
+ continue;
+ }
+ obj = dsn->connection;
transactional = 0;
}
@@ -342,10 +580,7 @@ static int acf_odbc_write(struct ast_channel *chan, const char *cmd, char *s, co
break;
}
- if (obj && !transactional) {
- ast_odbc_release_obj(obj);
- obj = NULL;
- }
+ dsn = release_dsn(dsn);
}
}
@@ -358,25 +593,25 @@ static int acf_odbc_write(struct ast_channel *chan, const char *cmd, char *s, co
status = "SUCCESS";
} else if (query->sql_insert) {
- if (obj && !transactional) {
- ast_odbc_release_obj(obj);
- obj = NULL;
- }
+ dsn = release_dsn(dsn);
- for (transactional = 0, dsn = 0; dsn < 5; dsn++) {
- if (!ast_strlen_zero(query->writehandle[dsn])) {
+ for (transactional = 0, dsn_num = 0; dsn_num < 5; dsn_num++) {
+ if (!ast_strlen_zero(query->writehandle[dsn_num])) {
if (transactional) {
/* This can only happen second time through or greater. */
ast_log(LOG_WARNING, "Transactions do not work well with multiple DSNs for 'writehandle'\n");
} else if (obj) {
- ast_odbc_release_obj(obj);
- obj = NULL;
+ dsn = release_dsn(dsn);
}
- if ((obj = ast_odbc_retrieve_transaction_obj(chan, query->writehandle[dsn]))) {
+ if ((obj = ast_odbc_retrieve_transaction_obj(chan, query->writehandle[dsn_num]))) {
transactional = 1;
} else {
- obj = ast_odbc_request_obj(query->writehandle[dsn], 0);
+ dsn = get_dsn(query->writehandle[dsn_num]);
+ if (!dsn) {
+ continue;
+ }
+ obj = dsn->connection;
transactional = 0;
}
if (obj) {
@@ -406,10 +641,7 @@ static int acf_odbc_write(struct ast_channel *chan, const char *cmd, char *s, co
pbx_builtin_setvar_helper(chan, "ODBCSTATUS", status);
}
- if (obj && !transactional) {
- ast_odbc_release_obj(obj);
- obj = NULL;
- }
+ dsn = release_dsn(dsn);
if (!bogus_chan) {
ast_autoservice_stop(chan);
@@ -420,11 +652,10 @@ static int acf_odbc_write(struct ast_channel *chan, const char *cmd, char *s, co
static int acf_odbc_read(struct ast_channel *chan, const char *cmd, char *s, char *buf, size_t len)
{
- struct odbc_obj *obj = NULL;
struct acf_odbc_query *query;
char varname[15], rowcount[12] = "-1";
struct ast_str *colnames = ast_str_thread_get(&colnames_buf, 16);
- int res, x, y, buflen = 0, escapecommas, rowlimit = 1, multirow = 0, dsn, bogus_chan = 0;
+ int res, x, y, buflen = 0, escapecommas, rowlimit = 1, multirow = 0, dsn_num, bogus_chan = 0;
AST_DECLARE_APP_ARGS(args,
AST_APP_ARG(field)[100];
);
@@ -436,6 +667,7 @@ static int acf_odbc_read(struct ast_channel *chan, const char *cmd, char *s, cha
struct odbc_datastore_row *row = NULL;
struct ast_str *sql = ast_str_thread_get(&sql_buf, 16);
const char *status = "FAILURE";
+ struct dsn *dsn = NULL;
if (!sql || !colnames) {
if (chan) {
@@ -523,28 +755,23 @@ static int acf_odbc_read(struct ast_channel *chan, const char *cmd, char *s, cha
}
AST_RWLIST_UNLOCK(&queries);
- for (dsn = 0; dsn < 5; dsn++) {
- if (!ast_strlen_zero(query->readhandle[dsn])) {
- obj = ast_odbc_request_obj(query->readhandle[dsn], 0);
- if (obj) {
- stmt = ast_odbc_direct_execute(obj, generic_execute, ast_str_buffer(sql));
+ for (dsn_num = 0; dsn_num < 5; dsn_num++) {
+ if (!ast_strlen_zero(query->readhandle[dsn_num])) {
+ dsn = get_dsn(query->readhandle[dsn_num]);
+ if (!dsn) {
+ continue;
}
+ stmt = ast_odbc_direct_execute(dsn->connection, generic_execute, ast_str_buffer(sql));
}
if (stmt) {
break;
}
- if (obj) {
- ast_odbc_release_obj(obj);
- obj = NULL;
- }
+ dsn = release_dsn(dsn);
}
if (!stmt) {
ast_log(LOG_ERROR, "Unable to execute query [%s]\n", ast_str_buffer(sql));
- if (obj) {
- ast_odbc_release_obj(obj);
- obj = NULL;
- }
+ dsn = release_dsn(dsn);
if (!bogus_chan) {
pbx_builtin_setvar_helper(chan, "ODBCROWS", rowcount);
ast_autoservice_stop(chan);
@@ -558,8 +785,7 @@ static int acf_odbc_read(struct ast_channel *chan, const char *cmd, char *s, cha
ast_log(LOG_WARNING, "SQL Column Count error!\n[%s]\n\n", ast_str_buffer(sql));
SQLCloseCursor(stmt);
SQLFreeHandle (SQL_HANDLE_STMT, stmt);
- ast_odbc_release_obj(obj);
- obj = NULL;
+ dsn = release_dsn(dsn);
if (!bogus_chan) {
pbx_builtin_setvar_helper(chan, "ODBCROWS", rowcount);
ast_autoservice_stop(chan);
@@ -583,8 +809,7 @@ static int acf_odbc_read(struct ast_channel *chan, const char *cmd, char *s, cha
}
SQLCloseCursor(stmt);
SQLFreeHandle(SQL_HANDLE_STMT, stmt);
- ast_odbc_release_obj(obj);
- obj = NULL;
+ dsn = release_dsn(dsn);
if (!bogus_chan) {
pbx_builtin_setvar_helper(chan, "ODBCROWS", rowcount);
pbx_builtin_setvar_helper(chan, "ODBCSTATUS", status);
@@ -607,8 +832,7 @@ static int acf_odbc_read(struct ast_channel *chan, const char *cmd, char *s, cha
odbc_datastore_free(resultset);
SQLCloseCursor(stmt);
SQLFreeHandle(SQL_HANDLE_STMT, stmt);
- ast_odbc_release_obj(obj);
- obj = NULL;
+ dsn = release_dsn(dsn);
if (!bogus_chan) {
pbx_builtin_setvar_helper(chan, "ODBCSTATUS", "MEMERROR");
ast_autoservice_stop(chan);
@@ -640,8 +864,7 @@ static int acf_odbc_read(struct ast_channel *chan, const char *cmd, char *s, cha
odbc_datastore_free(resultset);
SQLCloseCursor(stmt);
SQLFreeHandle(SQL_HANDLE_STMT, stmt);
- ast_odbc_release_obj(obj);
- obj = NULL;
+ dsn = release_dsn(dsn);
if (!bogus_chan) {
pbx_builtin_setvar_helper(chan, "ODBCROWS", rowcount);
pbx_builtin_setvar_helper(chan, "ODBCSTATUS", "MEMERROR");
@@ -750,8 +973,7 @@ end_acf_read:
odbc_datastore_free(resultset);
SQLCloseCursor(stmt);
SQLFreeHandle(SQL_HANDLE_STMT, stmt);
- ast_odbc_release_obj(obj);
- obj = NULL;
+ dsn = release_dsn(dsn);
pbx_builtin_setvar_helper(chan, "ODBCSTATUS", "MEMERROR");
ast_autoservice_stop(chan);
return -1;
@@ -764,8 +986,7 @@ end_acf_read:
}
SQLCloseCursor(stmt);
SQLFreeHandle(SQL_HANDLE_STMT, stmt);
- ast_odbc_release_obj(obj);
- obj = NULL;
+ dsn = release_dsn(dsn);
if (resultset && !multirow) {
/* Fetch the first resultset */
if (!acf_fetch(chan, "", buf, buf, len)) {
@@ -1192,8 +1413,8 @@ static char *cli_odbc_read(struct ast_cli_entry *e, int cmd, struct ast_cli_args
if (a->argc == 5 && !strcmp(a->argv[4], "exec")) {
/* Execute the query */
- struct odbc_obj *obj = NULL;
- int dsn, executed = 0;
+ struct dsn *dsn = NULL;
+ int dsn_num, executed = 0;
SQLHSTMT stmt;
int rows = 0, res, x;
SQLSMALLINT colcount = 0, collength;
@@ -1207,19 +1428,18 @@ static char *cli_odbc_read(struct ast_cli_entry *e, int cmd, struct ast_cli_args
return CLI_SUCCESS;
}
- for (dsn = 0; dsn < 5; dsn++) {
- if (ast_strlen_zero(query->readhandle[dsn])) {
+ for (dsn_num = 0; dsn_num < 5; dsn_num++) {
+ if (ast_strlen_zero(query->readhandle[dsn_num])) {
continue;
}
- ast_debug(1, "Found handle %s\n", query->readhandle[dsn]);
- if (!(obj = ast_odbc_request_obj(query->readhandle[dsn], 0))) {
+ dsn = get_dsn(query->readhandle[dsn_num]);
+ if (!dsn) {
continue;
}
+ ast_debug(1, "Found handle %s\n", query->readhandle[dsn_num]);
- ast_debug(1, "Got obj\n");
- if (!(stmt = ast_odbc_direct_execute(obj, generic_execute, ast_str_buffer(sql)))) {
- ast_odbc_release_obj(obj);
- obj = NULL;
+ if (!(stmt = ast_odbc_direct_execute(dsn->connection, generic_execute, ast_str_buffer(sql)))) {
+ dsn = release_dsn(dsn);
continue;
}
@@ -1230,8 +1450,7 @@ static char *cli_odbc_read(struct ast_cli_entry *e, int cmd, struct ast_cli_args
ast_cli(a->fd, "SQL Column Count error!\n[%s]\n\n", ast_str_buffer(sql));
SQLCloseCursor(stmt);
SQLFreeHandle (SQL_HANDLE_STMT, stmt);
- ast_odbc_release_obj(obj);
- obj = NULL;
+ dsn = release_dsn(dsn);
AST_RWLIST_UNLOCK(&queries);
return CLI_SUCCESS;
}
@@ -1240,10 +1459,9 @@ static char *cli_odbc_read(struct ast_cli_entry *e, int cmd, struct ast_cli_args
if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
SQLCloseCursor(stmt);
SQLFreeHandle(SQL_HANDLE_STMT, stmt);
- ast_odbc_release_obj(obj);
- obj = NULL;
+ dsn = release_dsn(dsn);
if (res == SQL_NO_DATA) {
- ast_cli(a->fd, "Returned %d rows. Query executed on handle %d:%s [%s]\n", rows, dsn, query->readhandle[dsn], ast_str_buffer(sql));
+ ast_cli(a->fd, "Returned %d rows. Query executed on handle %d:%s [%s]\n", rows, dsn_num, query->readhandle[dsn_num], ast_str_buffer(sql));
break;
} else {
ast_cli(a->fd, "Error %d in FETCH [%s]\n", res, ast_str_buffer(sql));
@@ -1270,8 +1488,7 @@ static char *cli_odbc_read(struct ast_cli_entry *e, int cmd, struct ast_cli_args
ast_cli(a->fd, "SQL Get Data error %d!\n[%s]\n\n", res, ast_str_buffer(sql));
SQLCloseCursor(stmt);
SQLFreeHandle(SQL_HANDLE_STMT, stmt);
- ast_odbc_release_obj(obj);
- obj = NULL;
+ dsn = release_dsn(dsn);
AST_RWLIST_UNLOCK(&queries);
return CLI_SUCCESS;
}
@@ -1289,15 +1506,11 @@ static char *cli_odbc_read(struct ast_cli_entry *e, int cmd, struct ast_cli_args
}
SQLCloseCursor(stmt);
SQLFreeHandle(SQL_HANDLE_STMT, stmt);
- ast_odbc_release_obj(obj);
- obj = NULL;
- ast_cli(a->fd, "Returned %d row%s. Query executed on handle %d [%s]\n", rows, rows == 1 ? "" : "s", dsn, query->readhandle[dsn]);
+ dsn = release_dsn(dsn);
+ ast_cli(a->fd, "Returned %d row%s. Query executed on handle %d [%s]\n", rows, rows == 1 ? "" : "s", dsn_num, query->readhandle[dsn_num]);
break;
}
- if (obj) {
- ast_odbc_release_obj(obj);
- obj = NULL;
- }
+ dsn = release_dsn(dsn);
if (!executed) {
ast_cli(a->fd, "Failed to execute query. [%s]\n", ast_str_buffer(sql));
@@ -1420,30 +1633,29 @@ static char *cli_odbc_write(struct ast_cli_entry *e, int cmd, struct ast_cli_arg
if (a->argc == 6 && !strcmp(a->argv[5], "exec")) {
/* Execute the query */
- struct odbc_obj *obj = NULL;
- int dsn, executed = 0;
+ struct dsn *dsn;
+ int dsn_num, executed = 0;
SQLHSTMT stmt;
SQLLEN rows = -1;
- for (dsn = 0; dsn < 5; dsn++) {
- if (ast_strlen_zero(query->writehandle[dsn])) {
+ for (dsn_num = 0; dsn_num < 5; dsn_num++) {
+ if (ast_strlen_zero(query->writehandle[dsn_num])) {
continue;
}
- if (!(obj = ast_odbc_request_obj(query->writehandle[dsn], 0))) {
+ dsn = get_dsn(query->writehandle[dsn_num]);
+ if (!dsn) {
continue;
}
- if (!(stmt = ast_odbc_direct_execute(obj, generic_execute, ast_str_buffer(sql)))) {
- ast_odbc_release_obj(obj);
- obj = NULL;
+ if (!(stmt = ast_odbc_direct_execute(dsn->connection, generic_execute, ast_str_buffer(sql)))) {
+ dsn = release_dsn(dsn);
continue;
}
SQLRowCount(stmt, &rows);
SQLCloseCursor(stmt);
SQLFreeHandle(SQL_HANDLE_STMT, stmt);
- ast_odbc_release_obj(obj);
- obj = NULL;
- ast_cli(a->fd, "Affected %d rows. Query executed on handle %d [%s]\n", (int)rows, dsn, query->writehandle[dsn]);
+ dsn = release_dsn(dsn);
+ ast_cli(a->fd, "Affected %d rows. Query executed on handle %d [%s]\n", (int)rows, dsn_num, query->writehandle[dsn_num]);
executed = 1;
break;
}
@@ -1470,6 +1682,11 @@ static int load_module(void)
char *catg;
struct ast_flags config_flags = { 0 };
+ dsns = ao2_container_alloc(DSN_BUCKETS, dsn_hash, dsn_cmp);
+ if (!dsns) {
+ return AST_MODULE_LOAD_DECLINE;
+ }
+
res |= ast_custom_function_register(&fetch_function);
res |= ast_register_application_xml(app_odbcfinish, exec_odbcfinish);
AST_RWLIST_WRLOCK(&queries);
@@ -1478,6 +1695,7 @@ static int load_module(void)
if (!cfg || cfg == CONFIG_STATUS_FILEINVALID) {
ast_log(LOG_NOTICE, "Unable to load config for func_odbc: %s\n", config);
AST_RWLIST_UNLOCK(&queries);
+ ao2_ref(dsns, -1);
return AST_MODULE_LOAD_DECLINE;
}
@@ -1531,6 +1749,8 @@ static int unload_module(void)
AST_RWLIST_WRLOCK(&queries);
AST_RWLIST_UNLOCK(&queries);
+
+ ao2_ref(dsns, -1);
return res;
}
diff --git a/include/asterisk/astobj2.h b/include/asterisk/astobj2.h
index 692cc7cb4..4bd44db76 100644
--- a/include/asterisk/astobj2.h
+++ b/include/asterisk/astobj2.h
@@ -19,6 +19,7 @@
#include "asterisk/compat.h"
#include "asterisk/lock.h"
+#include "asterisk/inline_api.h"
/*! \file
* \ref AstObj2
@@ -638,6 +639,46 @@ int __ao2_trylock(void *a, enum ao2_lock_req lock_how, const char *file, const c
void *ao2_object_get_lockaddr(void *obj);
+/*!
+ * \brief Increment reference count on an object and lock it
+ * \since 13.9.0
+ *
+ * \param[in] obj A pointer to the ao2 object
+ * \retval 0 The object is not an ao2 object or wasn't locked successfully
+ * \retval 1 The object's reference count was incremented and was locked
+ */
+AST_INLINE_API(
+int ao2_ref_and_lock(void *obj),
+{
+ ao2_ref(obj, +1);
+ if (ao2_lock(obj)) {
+ ao2_ref(obj, -1);
+ return 0;
+ }
+ return 1;
+}
+)
+
+/*!
+ * \brief Unlock an object and decrement its reference count
+ * \since 13.9.0
+ *
+ * \param[in] obj A pointer to the ao2 object
+ * \retval 0 The object is not an ao2 object or wasn't unlocked successfully
+ * \retval 1 The object was unlocked and it's reference count was decremented
+ */
+AST_INLINE_API(
+int ao2_unlock_and_unref(void *obj),
+{
+ if (ao2_unlock(obj)) {
+ return 0;
+ }
+ ao2_ref(obj, -1);
+
+ return 1;
+}
+)
+
/*! Global ao2 object holder structure. */
struct ao2_global_obj {
/*! Access lock to the held ao2 object. */
@@ -1985,4 +2026,97 @@ void ao2_iterator_cleanup(struct ao2_iterator *iter);
*/
int ao2_iterator_count(struct ao2_iterator *iter);
+/*!
+ * \brief Creates a hash function for a structure string field.
+ * \param stype The structure type
+ * \param field The string field in the structure to hash
+ *
+ * AO2_STRING_FIELD_HASH_CB(mystruct, myfield) will produce a function
+ * named mystruct_hash_fn which hashes mystruct->myfield.
+ */
+#define AO2_STRING_FIELD_HASH_FN(stype, field) \
+static int stype ## _hash_fn(const void *obj, const int flags) \
+{ \
+ const struct stype *object = obj; \
+ const char *key; \
+ switch (flags & OBJ_SEARCH_MASK) { \
+ case OBJ_SEARCH_KEY: \
+ key = obj; \
+ break; \
+ case OBJ_SEARCH_OBJECT: \
+ key = object->field; \
+ break; \
+ default: \
+ ast_assert(0); \
+ return 0; \
+ } \
+ return ast_str_hash(key); \
+}
+
+/*!
+ * \brief Creates a compare function for a structure string field.
+ * \param stype The structure type
+ * \param field The string field in the structure to compare
+ *
+ * AO2_STRING_FIELD_CMP_FN(mystruct, myfield) will produce a function
+ * named mystruct_cmp_fn which compares mystruct->myfield.
+ */
+#define AO2_STRING_FIELD_CMP_FN(stype, field) \
+static int stype ## _cmp_fn(void *obj, void *arg, int flags) \
+{ \
+ const struct stype *object_left = obj, *object_right = arg; \
+ const char *right_key = arg; \
+ int cmp; \
+ switch (flags & OBJ_SEARCH_MASK) { \
+ case OBJ_SEARCH_OBJECT: \
+ right_key = object_right->field; \
+ case OBJ_SEARCH_KEY: \
+ cmp = strcmp(object_left->field, right_key); \
+ break; \
+ case OBJ_SEARCH_PARTIAL_KEY: \
+ cmp = strncmp(object_left->field, right_key, strlen(right_key)); \
+ break; \
+ default: \
+ cmp = 0; \
+ break; \
+ } \
+ if (cmp) { \
+ return 0; \
+ } \
+ return CMP_MATCH; \
+}
+
+/*!
+ * \brief Creates a sort function for a structure string field.
+ * \param stype The structure type
+ * \param field The string field in the structure to compare
+ *
+ * AO2_STRING_FIELD_SORT_FN(mystruct, myfield) will produce a function
+ * named mystruct_sort_fn which compares mystruct->myfield.
+ */
+#define AO2_STRING_FIELD_SORT_FN(stype, field) \
+static int stype ## _sort_fn(const void *obj, const void *arg, int flags) \
+{ \
+ const struct stype *object_left = obj; \
+ const struct stype *object_right = arg; \
+ const char *right_key = arg; \
+ int cmp; \
+\
+ switch (flags & OBJ_SEARCH_MASK) { \
+ case OBJ_SEARCH_OBJECT: \
+ right_key = object_right->field; \
+ /* Fall through */ \
+ case OBJ_SEARCH_KEY: \
+ cmp = strcmp(object_left->field, right_key); \
+ break; \
+ case OBJ_SEARCH_PARTIAL_KEY: \
+ cmp = strncmp(object_left->field, right_key, strlen(right_key)); \
+ break; \
+ default: \
+ cmp = 0; \
+ break; \
+ } \
+ return cmp; \
+}
+
#endif /* _ASTERISK_ASTOBJ2_H */
diff --git a/include/asterisk/autoconfig.h.in b/include/asterisk/autoconfig.h.in
index 80780986a..a01131cc3 100644
--- a/include/asterisk/autoconfig.h.in
+++ b/include/asterisk/autoconfig.h.in
@@ -731,6 +731,9 @@
/* Define if your system has the PWLib libraries. */
#undef HAVE_PWLIB
+/* Define if your system has the PYTHONDEV libraries. */
+#undef HAVE_PYTHONDEV
+
/* Define to 1 if you have the Radius Client library. */
#undef HAVE_RADIUS
diff --git a/include/asterisk/bridge_channel_internal.h b/include/asterisk/bridge_channel_internal.h
index 7f7d5a88b..fb8e781e8 100644
--- a/include/asterisk/bridge_channel_internal.h
+++ b/include/asterisk/bridge_channel_internal.h
@@ -151,47 +151,20 @@ int bridge_channel_internal_push_full(struct ast_bridge_channel *bridge_channel,
void bridge_channel_internal_pull(struct ast_bridge_channel *bridge_channel);
/*!
- * \brief Internal bridge channel wait condition and associated result.
- */
-struct bridge_channel_internal_cond {
- /*! Lock for the data structure */
- ast_mutex_t lock;
- /*! Wait condition */
- ast_cond_t cond;
- /*! Wait until done */
- int done;
- /*! The bridge channel */
- struct ast_bridge_channel *bridge_channel;
-};
-
-/*!
- * \internal
- * \brief Wait for the expected signal.
- * \since 13.5.0
- *
- * \param cond the wait object
- *
- * \return Nothing
- */
-void bridge_channel_internal_wait(struct bridge_channel_internal_cond *cond);
-
-/*!
- * \internal
- * \brief Signal the condition wait.
- * \since 13.5.0
+ * \brief Signal imparting threads to wake up.
+ * \since 13.9.0
*
- * \param cond the wait object
+ * \param chan Channel imparted that we need to signal.
*
* \return Nothing
*/
-void bridge_channel_internal_signal(struct bridge_channel_internal_cond *cond);
+void bridge_channel_impart_signal(struct ast_channel *chan);
/*!
* \internal
* \brief Join the bridge_channel to the bridge (blocking)
*
* \param bridge_channel The Channel in the bridge
- * \param cond data used for signaling
*
* \note The bridge_channel->swap holds a channel reference for the swap
* channel going into the bridging system. The ref ensures that the swap
@@ -206,8 +179,7 @@ void bridge_channel_internal_signal(struct bridge_channel_internal_cond *cond);
* \retval 0 bridge channel successfully joined the bridge
* \retval -1 bridge channel failed to join the bridge
*/
-int bridge_channel_internal_join(struct ast_bridge_channel *bridge_channel,
- struct bridge_channel_internal_cond *cond);
+int bridge_channel_internal_join(struct ast_bridge_channel *bridge_channel);
/*!
* \internal
diff --git a/include/asterisk/bridge_technology.h b/include/asterisk/bridge_technology.h
index 7de573a23..7f5d746f8 100644
--- a/include/asterisk/bridge_technology.h
+++ b/include/asterisk/bridge_technology.h
@@ -107,6 +107,9 @@ struct ast_bridge_technology {
* \retval -1 on failure
*
* \note On entry, bridge is already locked.
+ *
+ * \note The bridge technology must tollerate a failed to join channel
+ * until it can be kicked from the bridge.
*/
int (*join)(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel);
/*!
diff --git a/include/asterisk/features.h b/include/asterisk/features.h
index b63124c2f..a4aed5d18 100644
--- a/include/asterisk/features.h
+++ b/include/asterisk/features.h
@@ -51,6 +51,7 @@ int ast_bridge_call(struct ast_channel *chan, struct ast_channel *peer, struct a
/*!
* \brief Bridge a call, and add additional flags to the bridge
*
+ * \details
* This does the same thing as \ref ast_bridge_call, except that once the bridge
* is created, the provided flags are set on the bridge. The provided flags are
* added to the bridge's flags; they will not clear any flags already set.
@@ -70,6 +71,7 @@ int ast_bridge_call_with_flags(struct ast_channel *chan, struct ast_channel *pee
* \brief Add an arbitrary channel to a bridge
* \since 12.0.0
*
+ * \details
* The channel that is being added to the bridge can be in any state: unbridged,
* bridged, answered, unanswered, etc. The channel will be added asynchronously,
* meaning that when this function returns once the channel has been added to
@@ -87,11 +89,16 @@ int ast_bridge_call_with_flags(struct ast_channel *chan, struct ast_channel *pee
* \param features Features for this channel in the bridge
* \param play_tone Indicates if a tone should be played to the channel
* \param xfersound Sound that should be used to indicate transfer with play_tone
+ *
+ * \note The features parameter must be NULL or obtained by
+ * ast_bridge_features_new(). You must not dereference features
+ * after calling even if the call fails.
+ *
* \retval 0 Success
* \retval -1 Failure
*/
int ast_bridge_add_channel(struct ast_bridge *bridge, struct ast_channel *chan,
- struct ast_bridge_features *features, int play_tone, const char *xfersound);
+ struct ast_bridge_features *features, int play_tone, const char *xfersound);
diff --git a/include/asterisk/res_pjsip.h b/include/asterisk/res_pjsip.h
index 3a9d61e4c..f985e3254 100644
--- a/include/asterisk/res_pjsip.h
+++ b/include/asterisk/res_pjsip.h
@@ -19,6 +19,13 @@
#ifndef _RES_PJSIP_H
#define _RES_PJSIP_H
+#include <pjsip.h>
+/* Needed for SUBSCRIBE, NOTIFY, and PUBLISH method definitions */
+#include <pjsip_simple.h>
+#include <pjsip/sip_transaction.h>
+#include <pj/timer.h>
+#include <pjlib.h>
+
#include "asterisk/stringfields.h"
/* Needed for struct ast_sockaddr */
#include "asterisk/netsock2.h"
@@ -241,6 +248,8 @@ struct ast_sip_contact {
struct ast_sip_endpoint *endpoint;
/*! The name of the aor this contact belongs to */
char *aor;
+ /*! Asterisk Server name */
+ AST_STRING_FIELD_EXTENDED(reg_server);
};
#define CONTACT_STATUS "contact_status"
@@ -254,6 +263,7 @@ enum ast_sip_contact_status_type {
UNKNOWN,
CREATED,
REMOVED,
+ UPDATED,
};
/*!
@@ -389,7 +399,10 @@ AST_VECTOR(ast_sip_auth_vector, const char *);
enum ast_sip_endpoint_identifier_type {
/*! Identify based on user name in From header */
AST_SIP_ENDPOINT_IDENTIFY_BY_USERNAME = (1 << 0),
+ /*! Identify based on user name in Auth header first, then From header */
+ AST_SIP_ENDPOINT_IDENTIFY_BY_AUTH_USERNAME = (1 << 1),
};
+AST_VECTOR(ast_sip_identify_by_vector, enum ast_sip_endpoint_identifier_type);
enum ast_sip_session_refresh_method {
/*! Use reinvite to negotiate direct media */
@@ -701,6 +714,8 @@ struct ast_sip_endpoint {
enum ast_sip_dtmf_mode dtmf;
/*! Method(s) by which the endpoint should be identified. */
enum ast_sip_endpoint_identifier_type ident_method;
+ /*! Order of the method(s) by which the endpoint should be identified. */
+ struct ast_sip_identify_by_vector ident_method_order;
/*! Boolean indicating if ringing should be sent as inband progress */
unsigned int inband_progress;
/*! Pointer to the persistent Asterisk endpoint */
@@ -1174,8 +1189,9 @@ struct ast_sip_auth *ast_sip_get_artificial_auth(void);
*/
struct ast_sip_endpoint *ast_sip_get_artificial_endpoint(void);
-/*!
- * \page Threading model for SIP
+/*! \defgroup pjsip_threading PJSIP Threading Model
+ * @{
+ * \page PJSIP PJSIP Threading Model
*
* There are three major types of threads that SIP will have to deal with:
* \li Asterisk threads
@@ -1224,6 +1240,19 @@ struct ast_sip_endpoint *ast_sip_get_artificial_endpoint(void);
* previous tasks pushed with the same serializer have completed. For more information
* on serializers and the benefits they provide, see \ref ast_threadpool_serializer
*
+ * \par Scheduler
+ *
+ * Some situations require that a task run periodically or at a future time. Normally
+ * the ast_sched functionality would be used but ast_sched only uses 1 thread for all
+ * tasks and that thread isn't registered with PJLIB and therefore can't do any PJSIP
+ * related work.
+ *
+ * ast_sip_sched uses ast_sched only as a scheduled queue. When a task is ready to run,
+ * it's pushed to a Serializer to be invoked asynchronously by a Servant. This ensures
+ * that the task is executed in a PJLIB registered thread and allows the ast_sched thread
+ * to immediately continue processing the queue. The Serializer used by ast_sip_sched
+ * is one of your choosing or a random one from the res_pjsip pool if you don't choose one.
+ *
* \note
*
* Do not make assumptions about individual threads based on a corresponding serializer.
@@ -1232,6 +1261,8 @@ struct ast_sip_endpoint *ast_sip_get_artificial_endpoint(void);
* tasks, even though they are all guaranteed to be executed in sequence.
*/
+typedef int (*ast_sip_task)(void *user_data);
+
/*!
* \brief Create a new serializer for SIP tasks
*
@@ -1369,6 +1400,214 @@ int ast_sip_push_task_synchronous(struct ast_taskprocessor *serializer, int (*si
int ast_sip_thread_is_servant(void);
/*!
+ * \brief Task flags for the res_pjsip scheduler
+ *
+ * The default is AST_SIP_SCHED_TASK_FIXED
+ * | AST_SIP_SCHED_TASK_DATA_NOT_AO2
+ * | AST_SIP_SCHED_TASK_DATA_NO_CLEANUP
+ * | AST_SIP_SCHED_TASK_PERIODIC
+ */
+enum ast_sip_scheduler_task_flags {
+ /*!
+ * The defaults
+ */
+ AST_SIP_SCHED_TASK_DEFAULTS = (0 << 0),
+
+ /*!
+ * Run at a fixed interval.
+ * Stop scheduling if the callback returns 0.
+ * Any other value is ignored.
+ */
+ AST_SIP_SCHED_TASK_FIXED = (0 << 0),
+ /*!
+ * Run at a variable interval.
+ * Stop scheduling if the callback returns 0.
+ * Any other return value is used as the new interval.
+ */
+ AST_SIP_SCHED_TASK_VARIABLE = (1 << 0),
+
+ /*!
+ * The task data is not an AO2 object.
+ */
+ AST_SIP_SCHED_TASK_DATA_NOT_AO2 = (0 << 1),
+ /*!
+ * The task data is an AO2 object.
+ * A reference count will be held by the scheduler until
+ * after the task has run for the final time (if ever).
+ */
+ AST_SIP_SCHED_TASK_DATA_AO2 = (1 << 1),
+
+ /*!
+ * Don't take any cleanup action on the data
+ */
+ AST_SIP_SCHED_TASK_DATA_NO_CLEANUP = (0 << 3),
+ /*!
+ * If AST_SIP_SCHED_TASK_DATA_AO2 is set, decrement the reference count
+ * otherwise call ast_free on it.
+ */
+ AST_SIP_SCHED_TASK_DATA_FREE = ( 1 << 3 ),
+
+ /*! \brief AST_SIP_SCHED_TASK_PERIODIC
+ * The task is scheduled at multiples of interval
+ * \see Interval
+ */
+ AST_SIP_SCHED_TASK_PERIODIC = (0 << 4),
+ /*! \brief AST_SIP_SCHED_TASK_DELAY
+ * The next invocation of the task is at last finish + interval
+ * \see Interval
+ */
+ AST_SIP_SCHED_TASK_DELAY = (1 << 4),
+};
+
+/*!
+ * \brief Scheduler task data structure
+ */
+struct ast_sip_sched_task;
+
+/*!
+ * \brief Schedule a task to run in the res_pjsip thread pool
+ * \since 13.9.0
+ *
+ * \param serializer The serializer to use. If NULL, don't use a serializer (see note below)
+ * \param interval The invocation interval in milliseconds (see note below)
+ * \param sip_task The task to invoke
+ * \param name An optional name to associate with the task
+ * \param task_data Optional data to pass to the task
+ * \param flags One of enum ast_sip_scheduler_task_type
+ *
+ * \returns Pointer to \ref ast_sip_sched_task ao2 object which must be dereferenced when done.
+ *
+ * \paragraph Serialization
+ *
+ * Specifying a serializer guarantees serialized execution but NOT specifying a serializer
+ * may still result in tasks being effectively serialized if the thread pool is busy.
+ * The point of the serializer BTW is not to prevent parallel executions of the SAME task.
+ * That happens automatically (see below). It's to prevent the task from running at the same
+ * time as other work using the same serializer, whether or not it's being run by the scheduler.
+ *
+ * \paragraph Interval
+ *
+ * The interval is used to calculate the next time the task should run. There are two models.
+ *
+ * \ref AST_SIP_SCHED_TASK_PERIODIC specifies that the invocations of the task occur at the
+ * specific interval. That is, every \ref "interval" milliseconds, regardless of how long the task
+ * takes. If the task takes longer than \ref interval, it will be scheduled at the next available
+ * multiple of \ref interval. For exmaple: If the task has an interval of 60 seconds and the task
+ * takes 70 seconds, the next invocation will happen at 120 seconds.
+ *
+ * \ref AST_SIP_SCHED_TASK_DELAY specifies that the next invocation of the task should start
+ * at \ref interval milliseconds after the current invocation has finished.
+ *
+ */
+struct ast_sip_sched_task *ast_sip_schedule_task(struct ast_taskprocessor *serializer,
+ int interval, ast_sip_task sip_task, char *name, void *task_data,
+ enum ast_sip_scheduler_task_flags flags);
+
+/*!
+ * \brief Cancels the next invocation of a task
+ * \since 13.9.0
+ *
+ * \param schtd The task structure pointer
+ * \retval 0 Success
+ * \retval -1 Failure
+ * \note Only cancels future invocations not the currently running invocation.
+ */
+int ast_sip_sched_task_cancel(struct ast_sip_sched_task *schtd);
+
+/*!
+ * \brief Cancels the next invocation of a task by name
+ * \since 13.9.0
+ *
+ * \param name The task name
+ * \retval 0 Success
+ * \retval -1 Failure
+ * \note Only cancels future invocations not the currently running invocation.
+ */
+int ast_sip_sched_task_cancel_by_name(const char *name);
+
+/*!
+ * \brief Gets the last start and end times of the task
+ * \since 13.9.0
+ *
+ * \param schtd The task structure pointer
+ * \param[out] when_queued Pointer to a timeval structure to contain the time when queued
+ * \param[out] last_start Pointer to a timeval structure to contain the time when last started
+ * \param[out] last_end Pointer to a timeval structure to contain the time when last ended
+ * \retval 0 Success
+ * \retval -1 Failure
+ * \note Any of the pointers can be NULL if you don't need them.
+ */
+int ast_sip_sched_task_get_times(struct ast_sip_sched_task *schtd,
+ struct timeval *when_queued, struct timeval *last_start, struct timeval *last_end);
+
+/*!
+ * \brief Gets the last start and end times of the task by name
+ * \since 13.9.0
+ *
+ * \param name The task name
+ * \param[out] when_queued Pointer to a timeval structure to contain the time when queued
+ * \param[out] last_start Pointer to a timeval structure to contain the time when last started
+ * \param[out] last_end Pointer to a timeval structure to contain the time when last ended
+ * \retval 0 Success
+ * \retval -1 Failure
+ * \note Any of the pointers can be NULL if you don't need them.
+ */
+int ast_sip_sched_task_get_times_by_name(const char *name,
+ struct timeval *when_queued, struct timeval *last_start, struct timeval *last_end);
+
+/*!
+ * \brief Gets the number of milliseconds until the next invocation
+ * \since 13.9.0
+ *
+ * \param schtd The task structure pointer
+ * \return The number of milliseconds until the next invocation or -1 if the task isn't scheduled
+ */
+int ast_sip_sched_task_get_next_run(struct ast_sip_sched_task *schtd);
+
+/*!
+ * \brief Gets the number of milliseconds until the next invocation
+ * \since 13.9.0
+ *
+ * \param name The task name
+ * \return The number of milliseconds until the next invocation or -1 if the task isn't scheduled
+ */
+int ast_sip_sched_task_get_next_run_by_name(const char *name);
+
+/*!
+ * \brief Checks if the task is currently running
+ * \since 13.9.0
+ *
+ * \param schtd The task structure pointer
+ * \retval 0 not running
+ * \retval 1 running
+ */
+int ast_sip_sched_is_task_running(struct ast_sip_sched_task *schtd);
+
+/*!
+ * \brief Checks if the task is currently running
+ * \since 13.9.0
+ *
+ * \param name The task name
+ * \retval 0 not running or not found
+ * \retval 1 running
+ */
+int ast_sip_sched_is_task_running_by_name(const char *name);
+
+/*!
+ * \brief Gets the task name
+ * \since 13.9.0
+ *
+ * \param schtd The task structure pointer
+ * \retval 0 success
+ * \retval 1 failure
+ */
+int ast_sip_sched_task_get_name(struct ast_sip_sched_task *schtd, char *name, size_t maxlen);
+
+/*!
+ * @}
+ */
+
+/*!
* \brief SIP body description
*
* This contains a type and subtype that will be added as
@@ -2223,6 +2462,18 @@ char *ast_sip_get_endpoint_identifier_order(void);
char *ast_sip_get_default_voicemail_extension(void);
/*!
+ * \brief Retrieve the global default realm.
+ *
+ * This is the value placed in outbound challenges' realm if there
+ * is no better option (such as an auth-configured realm).
+ *
+ * \param[out] realm The default realm
+ * \param size The buffer size of realm
+ * \return nothing
+ */
+void ast_sip_get_default_realm(char *realm, size_t size);
+
+/*!
* \brief Retrieve the global default from user.
*
* This is the value placed in outbound requests' From header if there
@@ -2258,6 +2509,13 @@ unsigned int ast_sip_get_keep_alive_interval(void);
*/
unsigned int ast_sip_get_contact_expiration_check_interval(void);
+/*!
+ * \brief Retrieve the system setting 'disable multi domain'.
+ * \since 13.9.0
+ *
+ * \retval non zero if disable multi domain.
+ */
+unsigned int ast_sip_get_disable_multi_domain(void);
/*!
* \brief Retrieve the system max initial qualify time.
@@ -2361,5 +2619,15 @@ int ast_sip_set_tpselector_from_transport_name(const char *transport_name, pjsip
void ast_sip_modify_id_header(pj_pool_t *pool, pjsip_fromto_hdr *id_hdr,
const struct ast_party_id *id);
+/*!
+ * \brief Retrieve the unidentified request security event thresholds
+ * \since 13.8.0
+ *
+ * \param count The maximum number of unidentified requests per source ip to accumulate before emitting a security event
+ * \param period The period in seconds over which to accumulate unidentified requests
+ * \param prune_interval The interval in seconds at which expired entries will be pruned
+ */
+void ast_sip_get_unidentified_request_thresholds(unsigned int *count, unsigned int *period,
+ unsigned int *prune_interval);
#endif /* _RES_PJSIP_H */
diff --git a/include/asterisk/res_pjsip_pubsub.h b/include/asterisk/res_pjsip_pubsub.h
index 84d86fb9e..94576d38b 100644
--- a/include/asterisk/res_pjsip_pubsub.h
+++ b/include/asterisk/res_pjsip_pubsub.h
@@ -214,9 +214,9 @@ enum ast_sip_subscription_notify_reason {
AST_SIP_SUBSCRIPTION_NOTIFY_REASON_OTHER
};
-/*! Type used for conveying mailbox state */
-#define AST_SIP_EXTEN_STATE_DATA "ast_sip_exten_state_data"
/*! Type used for extension state/presence */
+#define AST_SIP_EXTEN_STATE_DATA "ast_sip_exten_state_data"
+/*! Type used for conveying mailbox state */
#define AST_SIP_MESSAGE_ACCUMULATOR "ast_sip_message_accumulator"
/*!
diff --git a/include/asterisk/stasis.h b/include/asterisk/stasis.h
index 16b30ccb3..4fc295bc4 100644
--- a/include/asterisk/stasis.h
+++ b/include/asterisk/stasis.h
@@ -416,14 +416,14 @@ const struct timeval *stasis_message_timestamp(const struct stasis_message *msg)
* May return \c NULL, to indicate no representation. The returned object should
* be ast_json_unref()'ed.
*
- * \param message Message to convert to JSON string.
+ * \param msg Message to convert to JSON string.
* \param sanitize Snapshot sanitization callback.
*
* \return Newly allocated string with JSON message.
* \return \c NULL on error.
* \return \c NULL if JSON format is not supported.
*/
-struct ast_json *stasis_message_to_json(struct stasis_message *message, struct stasis_message_sanitizer *sanitize);
+struct ast_json *stasis_message_to_json(struct stasis_message *msg, struct stasis_message_sanitizer *sanitize);
/*!
* \brief Build the AMI representation of the message.
@@ -431,12 +431,21 @@ struct ast_json *stasis_message_to_json(struct stasis_message *message, struct s
* May return \c NULL, to indicate no representation. The returned object should
* be ao2_cleanup()'ed.
*
- * \param message Message to convert to AMI.
+ * \param msg Message to convert to AMI.
* \return \c NULL on error.
* \return \c NULL if AMI format is not supported.
*/
-struct ast_manager_event_blob *stasis_message_to_ami(
- struct stasis_message *message);
+struct ast_manager_event_blob *stasis_message_to_ami(struct stasis_message *msg);
+
+/*!
+ * \brief Determine if the given message can be converted to AMI.
+ *
+ * \param msg Message to see if can be converted to AMI.
+ *
+ * \retval 0 Cannot be converted
+ * \retval non-zero Can be converted
+ */
+int stasis_message_can_be_ami(struct stasis_message *msg);
/*!
* \brief Build the \ref AstGenericEvents representation of the message.
@@ -444,12 +453,11 @@ struct ast_manager_event_blob *stasis_message_to_ami(
* May return \c NULL, to indicate no representation. The returned object should
* be disposed of via \ref ast_event_destroy.
*
- * \param message Message to convert to AMI.
+ * \param msg Message to convert to AMI.
* \return \c NULL on error.
* \return \c NULL if AMI format is not supported.
*/
-struct ast_event *stasis_message_to_event(
- struct stasis_message *message);
+struct ast_event *stasis_message_to_event(struct stasis_message *msg);
/*! @} */
diff --git a/main/Makefile b/main/Makefile
index a64eabcde..13f1c9cf5 100644
--- a/main/Makefile
+++ b/main/Makefile
@@ -224,11 +224,7 @@ endif
$(ASTSSL_LIB): $(ASTSSL_LIB).$(ASTSSL_SO_VERSION)
$(ECHO_PREFIX) echo " [LN] $< -> $@"
- $(CMD_PREFIX) if [ -x "$(LDCONFIG)" ] ; then \
- $(LDCONFIG) $(LDCONFIG_FLAGS) . 2>/dev/null ;\
- else \
- $(LN) -sf $< $@ ;\
- fi
+ $(LN) -sf $< $@ ;\
else # Darwin
ASTSSL_LIB:=libasteriskssl.dylib
@@ -304,11 +300,7 @@ $(ASTPJ_LIB).$(ASTPJ_SO_VERSION): libasteriskpj.o libasteriskpj.exports
$(ASTPJ_LIB): $(ASTPJ_LIB).$(ASTPJ_SO_VERSION)
$(ECHO_PREFIX) echo " [LN] $< -> $@"
- $(CMD_PREFIX) if [ -x "$(LDCONFIG)" ] ; then \
- $(LDCONFIG) $(LDCONFIG_FLAGS) . 2>/dev/null ;\
- else \
- $(LN) -sf $< $@ ;\
- fi
+ $(LN) -sf $< $@ ;\
else # Darwin
ASTPJ_LIB:=libasteriskpj.dylib
diff --git a/main/asterisk.exports.in b/main/asterisk.exports.in
index 364b3b05e..f997587c9 100644
--- a/main/asterisk.exports.in
+++ b/main/asterisk.exports.in
@@ -49,6 +49,8 @@
LINKER_SYMBOL_PREFIXres_srtp;
LINKER_SYMBOL_PREFIXres_srtp_policy;
LINKER_SYMBOL_PREFIXsecure_call_info;
+ LINKER_SYMBOL_PREFIX__progname;
+ LINKER_SYMBOL_PREFIXenviron;
/*
If _IO_stdin_used is not exported, stdout/stderr may not get
diff --git a/main/bridge.c b/main/bridge.c
index fd83cfb7b..ee5ad735b 100644
--- a/main/bridge.c
+++ b/main/bridge.c
@@ -420,10 +420,12 @@ static void bridge_channel_complete_join(struct ast_bridge *bridge, struct ast_b
bridge->technology->name);
if (bridge->technology->join
&& bridge->technology->join(bridge, bridge_channel)) {
- ast_debug(1, "Bridge %s: %p(%s) failed to join %s technology\n",
+ /* We cannot leave the channel partially in the bridge so we must kick it out */
+ ast_debug(1, "Bridge %s: %p(%s) failed to join %s technology (Kicking it out)\n",
bridge->uniqueid, bridge_channel, ast_channel_name(bridge_channel->chan),
bridge->technology->name);
bridge_channel->just_joined = 1;
+ ast_bridge_channel_leave_bridge(bridge_channel, BRIDGE_CHANNEL_STATE_END, 0);
return;
}
@@ -1483,6 +1485,150 @@ void ast_bridge_notify_masquerade(struct ast_channel *chan)
ao2_ref(bridge_channel, -1);
}
+/*!
+ * \brief Internal bridge impart wait condition and associated conditional.
+ */
+struct bridge_channel_impart_cond {
+ AST_LIST_ENTRY(bridge_channel_impart_cond) node;
+ /*! Lock for the data structure */
+ ast_mutex_t lock;
+ /*! Wait condition */
+ ast_cond_t cond;
+ /*! Wait until done */
+ int done;
+};
+
+AST_LIST_HEAD_NOLOCK(bridge_channel_impart_ds_head, bridge_channel_impart_cond);
+
+/*!
+ * \internal
+ * \brief Signal imparting threads to wake up.
+ * \since 13.9.0
+ *
+ * \param ds_head List of imparting threads to wake up.
+ *
+ * \return Nothing
+ */
+static void bridge_channel_impart_ds_head_signal(struct bridge_channel_impart_ds_head *ds_head)
+{
+ if (ds_head) {
+ struct bridge_channel_impart_cond *cond;
+
+ while ((cond = AST_LIST_REMOVE_HEAD(ds_head, node))) {
+ ast_mutex_lock(&cond->lock);
+ cond->done = 1;
+ ast_cond_signal(&cond->cond);
+ ast_mutex_unlock(&cond->lock);
+ }
+ }
+}
+
+static void bridge_channel_impart_ds_head_dtor(void *doomed)
+{
+ bridge_channel_impart_ds_head_signal(doomed);
+ ast_free(doomed);
+}
+
+/*!
+ * \internal
+ * \brief Fixup the bridge impart datastore.
+ * \since 13.9.0
+ *
+ * \param data Bridge impart datastore data to fixup from old_chan.
+ * \param old_chan The datastore is moving from this channel.
+ * \param new_chan The datastore is moving to this channel.
+ *
+ * \return Nothing
+ */
+static void bridge_channel_impart_ds_head_fixup(void *data, struct ast_channel *old_chan, struct ast_channel *new_chan)
+{
+ /*
+ * Signal any waiting impart threads. The masquerade is going to kill
+ * old_chan and we don't need to be waiting on new_chan.
+ */
+ bridge_channel_impart_ds_head_signal(data);
+}
+
+static const struct ast_datastore_info bridge_channel_impart_ds_info = {
+ .type = "bridge-impart-ds",
+ .destroy = bridge_channel_impart_ds_head_dtor,
+ .chan_fixup = bridge_channel_impart_ds_head_fixup,
+};
+
+/*!
+ * \internal
+ * \brief Add impart wait datastore conditional to channel.
+ * \since 13.9.0
+ *
+ * \param chan Channel to add the impart wait conditional.
+ * \param cond Imparting conditional to add.
+ *
+ * \retval 0 on success.
+ * \retval -1 on error.
+ */
+static int bridge_channel_impart_add(struct ast_channel *chan, struct bridge_channel_impart_cond *cond)
+{
+ struct ast_datastore *datastore;
+ struct bridge_channel_impart_ds_head *ds_head;
+
+ ast_channel_lock(chan);
+
+ datastore = ast_channel_datastore_find(chan, &bridge_channel_impart_ds_info, NULL);
+ if (!datastore) {
+ datastore = ast_datastore_alloc(&bridge_channel_impart_ds_info, NULL);
+ if (!datastore) {
+ ast_channel_unlock(chan);
+ return -1;
+ }
+ ds_head = ast_calloc(1, sizeof(*ds_head));
+ if (!ds_head) {
+ ast_channel_unlock(chan);
+ ast_datastore_free(datastore);
+ return -1;
+ }
+ datastore->data = ds_head;
+ ast_channel_datastore_add(chan, datastore);
+ } else {
+ ds_head = datastore->data;
+ ast_assert(ds_head != NULL);
+ }
+
+ AST_LIST_INSERT_TAIL(ds_head, cond, node);
+
+ ast_channel_unlock(chan);
+ return 0;
+}
+
+void bridge_channel_impart_signal(struct ast_channel *chan)
+{
+ struct ast_datastore *datastore;
+
+ ast_channel_lock(chan);
+ datastore = ast_channel_datastore_find(chan, &bridge_channel_impart_ds_info, NULL);
+ if (datastore) {
+ bridge_channel_impart_ds_head_signal(datastore->data);
+ }
+ ast_channel_unlock(chan);
+}
+
+/*!
+ * \internal
+ * \brief Block imparting channel thread until signaled.
+ * \since 13.9.0
+ *
+ * \param cond Imparting conditional to wait for.
+ *
+ * \return Nothing
+ */
+static void bridge_channel_impart_wait(struct bridge_channel_impart_cond *cond)
+{
+ ast_mutex_lock(&cond->lock);
+ while (!cond->done) {
+ ast_cond_wait(&cond->cond, &cond->lock);
+ }
+ ast_mutex_unlock(&cond->lock);
+}
+
/*
* XXX ASTERISK-21271 make ast_bridge_join() require features to be allocated just like ast_bridge_impart() and not expect the struct back.
*
@@ -1551,7 +1697,7 @@ int ast_bridge_join(struct ast_bridge *bridge,
}
if (!res) {
- res = bridge_channel_internal_join(bridge_channel, NULL);
+ res = bridge_channel_internal_join(bridge_channel);
}
/* Cleanup all the data in the bridge channel after it leaves the bridge. */
@@ -1568,6 +1714,7 @@ int ast_bridge_join(struct ast_bridge *bridge,
join_exit:;
ast_bridge_run_after_callback(chan);
+ bridge_channel_impart_signal(chan);
if (!(ast_channel_softhangup_internal_flag(chan) & AST_SOFTHANGUP_ASYNCGOTO)
&& !ast_bridge_setup_after_goto(chan)) {
/* Claim the after bridge goto is an async goto destination. */
@@ -1581,14 +1728,13 @@ join_exit:;
/*! \brief Thread responsible for imparted bridged channels to be departed */
static void *bridge_channel_depart_thread(void *data)
{
- struct bridge_channel_internal_cond *cond = data;
- struct ast_bridge_channel *bridge_channel = cond->bridge_channel;
+ struct ast_bridge_channel *bridge_channel = data;
if (bridge_channel->callid) {
ast_callid_threadassoc_add(bridge_channel->callid);
}
- bridge_channel_internal_join(bridge_channel, cond);
+ bridge_channel_internal_join(bridge_channel);
/*
* cleanup
@@ -1601,6 +1747,8 @@ static void *bridge_channel_depart_thread(void *data)
bridge_channel->features = NULL;
ast_bridge_discard_after_callback(bridge_channel->chan, 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);
return NULL;
@@ -1609,15 +1757,14 @@ static void *bridge_channel_depart_thread(void *data)
/*! \brief Thread responsible for independent imparted bridged channels */
static void *bridge_channel_ind_thread(void *data)
{
- struct bridge_channel_internal_cond *cond = data;
- struct ast_bridge_channel *bridge_channel = cond->bridge_channel;
+ struct ast_bridge_channel *bridge_channel = data;
struct ast_channel *chan;
if (bridge_channel->callid) {
ast_callid_threadassoc_add(bridge_channel->callid);
}
- bridge_channel_internal_join(bridge_channel, cond);
+ bridge_channel_internal_join(bridge_channel);
chan = bridge_channel->chan;
/* cleanup */
@@ -1634,15 +1781,18 @@ static void *bridge_channel_ind_thread(void *data)
ao2_ref(bridge_channel, -1);
ast_bridge_run_after_callback(chan);
+ /* If join failed there will be impart threads waiting. */
+ bridge_channel_impart_signal(chan);
ast_bridge_run_after_goto(chan);
return NULL;
}
-int ast_bridge_impart(struct ast_bridge *bridge,
+static int bridge_impart_internal(struct ast_bridge *bridge,
struct ast_channel *chan,
struct ast_channel *swap,
struct ast_bridge_features *features,
- enum ast_bridge_impart_flags flags)
+ enum ast_bridge_impart_flags flags,
+ struct bridge_channel_impart_cond *cond)
{
int res = 0;
struct ast_bridge_channel *bridge_channel;
@@ -1701,27 +1851,20 @@ int ast_bridge_impart(struct ast_bridge *bridge,
/* Actually create the thread that will handle the channel */
if (!res) {
- struct bridge_channel_internal_cond cond = {
- .done = 0,
- .bridge_channel = bridge_channel
- };
- ast_mutex_init(&cond.lock);
- ast_cond_init(&cond.cond, NULL);
-
+ res = bridge_channel_impart_add(chan, cond);
+ }
+ if (!res) {
if ((flags & AST_BRIDGE_IMPART_CHAN_MASK) == AST_BRIDGE_IMPART_CHAN_INDEPENDENT) {
res = ast_pthread_create_detached(&bridge_channel->thread, NULL,
- bridge_channel_ind_thread, &cond);
+ bridge_channel_ind_thread, bridge_channel);
} else {
res = ast_pthread_create(&bridge_channel->thread, NULL,
- bridge_channel_depart_thread, &cond);
+ bridge_channel_depart_thread, bridge_channel);
}
if (!res) {
- bridge_channel_internal_wait(&cond);
+ bridge_channel_impart_wait(cond);
}
-
- ast_cond_destroy(&cond.cond);
- ast_mutex_destroy(&cond.lock);
}
if (res) {
@@ -1742,6 +1885,32 @@ int ast_bridge_impart(struct ast_bridge *bridge,
return 0;
}
+int ast_bridge_impart(struct ast_bridge *bridge,
+ struct ast_channel *chan,
+ struct ast_channel *swap,
+ struct ast_bridge_features *features,
+ enum ast_bridge_impart_flags flags)
+{
+ struct bridge_channel_impart_cond cond = {
+ .done = 0,
+ };
+ int res;
+
+ ast_mutex_init(&cond.lock);
+ ast_cond_init(&cond.cond, NULL);
+
+ res = bridge_impart_internal(bridge, chan, swap, features, flags, &cond);
+ if (res) {
+ /* Impart failed. Signal any other waiting impart threads */
+ bridge_channel_impart_signal(chan);
+ }
+
+ ast_cond_destroy(&cond.cond);
+ ast_mutex_destroy(&cond.lock);
+
+ return res;
+}
+
int ast_bridge_depart(struct ast_channel *chan)
{
struct ast_bridge_channel *bridge_channel;
@@ -2318,6 +2487,9 @@ int ast_bridge_add_channel(struct ast_bridge *bridge, struct ast_channel *chan,
if (chan_bridge) {
struct ast_bridge_channel *bridge_channel;
+ /* The channel is in a bridge so it is not getting any new features. */
+ ast_bridge_features_destroy(features);
+
ast_bridge_lock_both(bridge, chan_bridge);
bridge_channel = bridge_find_channel(chan_bridge, chan);
@@ -2340,9 +2512,6 @@ int ast_bridge_add_channel(struct ast_bridge *bridge, struct ast_channel *chan,
bridge_dissolve_check_stolen(chan_bridge, bridge_channel);
ast_bridge_unlock(chan_bridge);
ast_bridge_unlock(bridge);
-
- /* The channel was in a bridge so it is not getting any new features. */
- ast_bridge_features_destroy(features);
} else {
/* Slightly less easy case. We need to yank channel A from
* where he currently is and impart him into our bridge.
@@ -2350,6 +2519,7 @@ int ast_bridge_add_channel(struct ast_bridge *bridge, struct ast_channel *chan,
yanked_chan = ast_channel_yank(chan);
if (!yanked_chan) {
ast_log(LOG_WARNING, "Could not gain control of channel %s\n", ast_channel_name(chan));
+ ast_bridge_features_destroy(features);
return -1;
}
if (ast_channel_state(yanked_chan) != AST_STATE_UP) {
diff --git a/main/bridge_channel.c b/main/bridge_channel.c
index c9262a84a..4baae3cc5 100644
--- a/main/bridge_channel.c
+++ b/main/bridge_channel.c
@@ -2117,13 +2117,14 @@ int bridge_channel_internal_push_full(struct ast_bridge_channel *bridge_channel,
if (bridge->dissolved
|| bridge_channel->state != BRIDGE_CHANNEL_STATE_WAIT
|| (swap && swap->state != BRIDGE_CHANNEL_STATE_WAIT)
- || bridge->v_table->push(bridge, bridge_channel, swap)
- || ast_bridge_channel_establish_roles(bridge_channel)) {
+ || bridge->v_table->push(bridge, bridge_channel, swap)) {
ast_debug(1, "Bridge %s: pushing %p(%s) into bridge failed\n",
bridge->uniqueid, bridge_channel, ast_channel_name(bridge_channel->chan));
return -1;
}
+ ast_bridge_channel_establish_roles(bridge_channel);
+
if (swap) {
int dissolve = ast_test_flag(&bridge->feature_flags, AST_BRIDGE_FLAG_DISSOLVE_EMPTY);
@@ -2636,27 +2637,7 @@ static void bridge_channel_event_join_leave(struct ast_bridge_channel *bridge_ch
ao2_iterator_destroy(&iter);
}
-void bridge_channel_internal_wait(struct bridge_channel_internal_cond *cond)
-{
- ast_mutex_lock(&cond->lock);
- while (!cond->done) {
- ast_cond_wait(&cond->cond, &cond->lock);
- }
- ast_mutex_unlock(&cond->lock);
-}
-
-void bridge_channel_internal_signal(struct bridge_channel_internal_cond *cond)
-{
- if (cond) {
- ast_mutex_lock(&cond->lock);
- cond->done = 1;
- ast_cond_signal(&cond->cond);
- ast_mutex_unlock(&cond->lock);
- }
-}
-
-int bridge_channel_internal_join(struct ast_bridge_channel *bridge_channel,
- struct bridge_channel_internal_cond *cond)
+int bridge_channel_internal_join(struct ast_bridge_channel *bridge_channel)
{
int res = 0;
struct ast_bridge_features *channel_features;
@@ -2686,7 +2667,6 @@ int bridge_channel_internal_join(struct ast_bridge_channel *bridge_channel,
bridge_channel->bridge->uniqueid,
bridge_channel,
ast_channel_name(bridge_channel->chan));
- bridge_channel_internal_signal(cond);
return -1;
}
ast_channel_internal_bridge_set(bridge_channel->chan, bridge_channel->bridge);
@@ -2721,8 +2701,6 @@ int bridge_channel_internal_join(struct ast_bridge_channel *bridge_channel,
}
bridge_reconfigured(bridge_channel->bridge, !bridge_channel->inhibit_colp);
- bridge_channel_internal_signal(cond);
-
if (bridge_channel->state == BRIDGE_CHANNEL_STATE_WAIT) {
/*
* Indicate a source change since this channel is entering the
@@ -2734,6 +2712,7 @@ int bridge_channel_internal_join(struct ast_bridge_channel *bridge_channel,
ast_indicate(bridge_channel->chan, AST_CONTROL_SRCCHANGE);
}
+ bridge_channel_impart_signal(bridge_channel->chan);
ast_bridge_unlock(bridge_channel->bridge);
/* Must release any swap ref after unlocking the bridge. */
diff --git a/main/config.c b/main/config.c
index 2a6baa133..ffada0bf3 100644
--- a/main/config.c
+++ b/main/config.c
@@ -36,6 +36,9 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
#include "asterisk/paths.h" /* use ast_config_AST_CONFIG_DIR */
#include "asterisk/network.h" /* we do some sockaddr manipulation here */
+
+#include <string.h>
+#include <libgen.h>
#include <time.h>
#include <sys/stat.h>
@@ -2512,6 +2515,25 @@ int ast_config_text_file_save(const char *configfile, const struct ast_config *c
return ast_config_text_file_save2(configfile, cfg, generator, CONFIG_SAVE_FLAG_PRESERVE_EFFECTIVE_CONTEXT);
}
+static int is_writable(const char *fn)
+{
+ if (access(fn, F_OK)) {
+ char *dn = dirname(ast_strdupa(fn));
+
+ if (access(dn, R_OK | W_OK)) {
+ ast_log(LOG_ERROR, "Unable to write to directory %s (%s)\n", dn, strerror(errno));
+ return 0;
+ }
+ } else {
+ if (access(fn, R_OK | W_OK)) {
+ ast_log(LOG_ERROR, "Unable to write %s (%s)\n", fn, strerror(errno));
+ return 0;
+ }
+ }
+
+ return 1;
+}
+
int ast_config_text_file_save2(const char *configfile, const struct ast_config *cfg, const char *generator, uint32_t flags)
{
FILE *f;
@@ -2534,20 +2556,20 @@ int ast_config_text_file_save2(const char *configfile, const struct ast_config *
for (incl = cfg->includes; incl; incl = incl->next) {
/* reset all the output flags in case this isn't our first time saving this data */
incl->output = 0;
- /* now make sure we have write access */
+
if (!incl->exec) {
+ /* now make sure we have write access to the include file or its parent directory */
make_fn(fn, sizeof(fn), incl->included_file, configfile);
- if (access(fn, R_OK | W_OK)) {
- ast_log(LOG_ERROR, "Unable to write %s (%s)\n", fn, strerror(errno));
+ /* If the file itself doesn't exist, make sure we have write access to the directory */
+ if (!is_writable(fn)) {
return -1;
}
}
}
- /* now make sure we have write access to the main config file */
+ /* now make sure we have write access to the main config file or its parent directory */
make_fn(fn, sizeof(fn), 0, configfile);
- if (access(fn, R_OK | W_OK)) {
- ast_log(LOG_ERROR, "Unable to write %s (%s)\n", fn, strerror(errno));
+ if (!is_writable(fn)) {
return -1;
}
diff --git a/main/config_options.c b/main/config_options.c
index db99a441d..c8988c984 100644
--- a/main/config_options.c
+++ b/main/config_options.c
@@ -198,8 +198,8 @@ static int link_option_to_types(struct aco_info *info, struct aco_type **types,
#ifdef AST_DEVMODE
opt->doc_unavailable = 1;
#endif
-#endif
}
+#endif
}
/* The container(s) should hold the only ref to opt */
ao2_ref(opt, -1);
diff --git a/main/core_unreal.c b/main/core_unreal.c
index e9b7a8d66..f2404dfca 100644
--- a/main/core_unreal.c
+++ b/main/core_unreal.c
@@ -808,7 +808,6 @@ int ast_unreal_channel_push_to_bridge(struct ast_channel *ast, struct ast_bridge
/* Impart the semi2 channel into the bridge */
if (ast_bridge_impart(bridge, chan, NULL, features,
AST_BRIDGE_IMPART_CHAN_INDEPENDENT)) {
- ast_bridge_features_destroy(features);
ast_channel_unref(chan);
return -1;
}
diff --git a/main/features.c b/main/features.c
index 1810b1556..b96cbd68c 100644
--- a/main/features.c
+++ b/main/features.c
@@ -1104,7 +1104,6 @@ static int bridge_exec(struct ast_channel *chan, const char *data)
xfer_cfg ? xfer_cfg->xfersound : NULL);
ao2_cleanup(xfer_cfg);
if (bridge_add_failed) {
- ast_bridge_features_destroy(peer_features);
ast_bridge_features_cleanup(&chan_features);
ast_bridge_destroy(bridge, 0);
goto done;
diff --git a/main/file.c b/main/file.c
index 7ce021340..1c51177c2 100644
--- a/main/file.c
+++ b/main/file.c
@@ -799,7 +799,7 @@ struct ast_filestream *ast_openvstream(struct ast_channel *chan, const char *fil
/* As above, but for video. But here we don't have translators
* so we must enforce a format.
*/
- struct ast_format_cap *tmp_cap;
+ struct ast_format_cap *nativeformats, *tmp_cap;
char *buf;
int buflen;
int i, fd;
@@ -810,16 +810,23 @@ struct ast_filestream *ast_openvstream(struct ast_channel *chan, const char *fil
buflen = strlen(preflang) + strlen(filename) + 4;
buf = ast_alloca(buflen);
+ ast_channel_lock(chan);
+ nativeformats = ao2_bump(ast_channel_nativeformats(chan));
+ ast_channel_unlock(chan);
+
/* is the channel capable of video without translation ?*/
- if (!ast_format_cap_has_type(ast_channel_nativeformats(chan), AST_MEDIA_TYPE_VIDEO)) {
+ if (!ast_format_cap_has_type(nativeformats, AST_MEDIA_TYPE_VIDEO)) {
+ ao2_cleanup(nativeformats);
return NULL;
}
if (!(tmp_cap = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT))) {
+ ao2_cleanup(nativeformats);
return NULL;
}
/* Video is supported, so see what video formats exist for this file */
if (!fileexists_core(filename, NULL, preflang, buf, buflen, tmp_cap)) {
ao2_ref(tmp_cap, -1);
+ ao2_cleanup(nativeformats);
return NULL;
}
@@ -828,7 +835,7 @@ struct ast_filestream *ast_openvstream(struct ast_channel *chan, const char *fil
struct ast_format *format = ast_format_cap_get_format(tmp_cap, i);
if ((ast_format_get_type(format) != AST_MEDIA_TYPE_VIDEO) ||
- !ast_format_cap_iscompatible(ast_channel_nativeformats(chan), tmp_cap)) {
+ !ast_format_cap_iscompatible(nativeformats, tmp_cap)) {
ao2_ref(format, -1);
continue;
}
@@ -837,12 +844,14 @@ struct ast_filestream *ast_openvstream(struct ast_channel *chan, const char *fil
if (fd >= 0) {
ao2_ref(format, -1);
ao2_ref(tmp_cap, -1);
+ ao2_cleanup(nativeformats);
return ast_channel_vstream(chan);
}
ast_log(LOG_WARNING, "File %s has video but couldn't be opened\n", filename);
ao2_ref(format, -1);
}
ao2_ref(tmp_cap, -1);
+ ao2_cleanup(nativeformats);
return NULL;
}
@@ -1097,8 +1106,10 @@ int ast_streamfile(struct ast_channel *chan, const char *filename, const char *p
fs = ast_openstream(chan, filename, preflang);
if (!fs) {
struct ast_str *codec_buf = ast_str_alloca(AST_FORMAT_CAP_NAMES_LEN);
+ ast_channel_lock(chan);
ast_log(LOG_WARNING, "Unable to open %s (format %s): %s\n",
filename, ast_format_cap_get_names(ast_channel_nativeformats(chan), &codec_buf), strerror(errno));
+ ast_channel_unlock(chan);
return -1;
}
@@ -1133,7 +1144,12 @@ int ast_streamfile(struct ast_channel *chan, const char *filename, const char *p
res = ast_playstream(fs);
if (!res && vfs)
res = ast_playstream(vfs);
- ast_verb(3, "<%s> Playing '%s.%s' (language '%s')\n", ast_channel_name(chan), filename, ast_format_get_name(ast_channel_writeformat(chan)), preflang ? preflang : "default");
+
+ if (VERBOSITY_ATLEAST(3)) {
+ ast_channel_lock(chan);
+ ast_verb(3, "<%s> Playing '%s.%s' (language '%s')\n", ast_channel_name(chan), filename, ast_format_get_name(ast_channel_writeformat(chan)), preflang ? preflang : "default");
+ ast_channel_unlock(chan);
+ }
return res;
}
diff --git a/main/format_cap.c b/main/format_cap.c
index 17ae18cd4..bf3bd1c4b 100644
--- a/main/format_cap.c
+++ b/main/format_cap.c
@@ -376,7 +376,7 @@ int ast_format_cap_update_by_allow_disallow(struct ast_format_cap *cap, const ch
}
- while ((this = strsep(&parse, ",|"))) {
+ while ((this = ast_strip(strsep(&parse, ",|")))) {
int framems = 0;
struct ast_format *format = NULL;
diff --git a/main/lock.c b/main/lock.c
index dd90d7bd9..03f1cd974 100644
--- a/main/lock.c
+++ b/main/lock.c
@@ -286,17 +286,19 @@ int __ast_pthread_mutex_lock(const char *filename, int lineno, const char *func,
if (wait_time > reported_wait && (wait_time % 5) == 0) {
__ast_mutex_logger("%s line %d (%s): Deadlock? waited %d sec for mutex '%s'?\n",
filename, lineno, func, (int) wait_time, mutex_name);
- ast_reentrancy_lock(lt);
+ if (lt) {
+ ast_reentrancy_lock(lt);
#ifdef HAVE_BKTR
- __dump_backtrace(&lt->backtrace[lt->reentrancy], canlog);
+ __dump_backtrace(&lt->backtrace[lt->reentrancy], canlog);
#endif
- __ast_mutex_logger("%s line %d (%s): '%s' was locked here.\n",
- lt->file[ROFFSET], lt->lineno[ROFFSET],
- lt->func[ROFFSET], mutex_name);
+ __ast_mutex_logger("%s line %d (%s): '%s' was locked here.\n",
+ lt->file[ROFFSET], lt->lineno[ROFFSET],
+ lt->func[ROFFSET], mutex_name);
#ifdef HAVE_BKTR
- __dump_backtrace(&lt->backtrace[ROFFSET], canlog);
+ __dump_backtrace(&lt->backtrace[ROFFSET], canlog);
#endif
- ast_reentrancy_unlock(lt);
+ ast_reentrancy_unlock(lt);
+ }
reported_wait = wait_time;
}
usleep(200);
diff --git a/main/manager.c b/main/manager.c
index 7c2155015..ba261e8e9 100644
--- a/main/manager.c
+++ b/main/manager.c
@@ -1541,6 +1541,17 @@ static AST_RWLIST_HEAD_STATIC(manager_hooks, manager_custom_hook);
/*! \brief A container of event documentation nodes */
static AO2_GLOBAL_OBJ_STATIC(event_docs);
+static int __attribute__((format(printf, 9, 0))) __manager_event_sessions(
+ struct ao2_container *sessions,
+ int category,
+ const char *event,
+ int chancount,
+ struct ast_channel **chans,
+ const char *file,
+ int line,
+ const char *func,
+ const char *fmt,
+ ...);
static enum add_filter_result manager_add_filter(const char *filter_pattern, struct ao2_container *whitefilters, struct ao2_container *blackfilters);
static int match_filter(struct mansession *s, char *eventdata);
@@ -1679,37 +1690,75 @@ struct ast_str *ast_manager_str_from_json_object(struct ast_json *blob, key_excl
return res;
}
+#define manager_event_sessions(sessions, category, event, contents , ...) \
+ __manager_event_sessions(sessions, category, event, 0, NULL, __FILE__, __LINE__, __PRETTY_FUNCTION__, contents , ## __VA_ARGS__)
+
+#define any_manager_listeners(sessions) \
+ ((sessions && ao2_container_count(sessions)) || !AST_RWLIST_EMPTY(&manager_hooks))
+
static void manager_default_msg_cb(void *data, struct stasis_subscription *sub,
struct stasis_message *message)
{
- RAII_VAR(struct ast_manager_event_blob *, ev, NULL, ao2_cleanup);
+ struct ao2_container *sessions;
+ struct ast_manager_event_blob *ev;
- ev = stasis_message_to_ami(message);
+ if (!stasis_message_can_be_ami(message)) {
+ /* Not an AMI message; disregard */
+ return;
+ }
+
+ sessions = ao2_global_obj_ref(mgr_sessions);
+ if (!any_manager_listeners(sessions)) {
+ /* Nobody is listening */
+ ao2_cleanup(sessions);
+ return;
+ }
- if (ev == NULL) {
- /* Not and AMI message; disregard */
+ ev = stasis_message_to_ami(message);
+ if (!ev) {
+ /* Conversion failure */
+ ao2_cleanup(sessions);
return;
}
- manager_event(ev->event_flags, ev->manager_event, "%s",
- ev->extra_fields);
+ manager_event_sessions(sessions, ev->event_flags, ev->manager_event,
+ "%s", ev->extra_fields);
+ ao2_ref(ev, -1);
+ ao2_cleanup(sessions);
}
static void manager_generic_msg_cb(void *data, struct stasis_subscription *sub,
struct stasis_message *message)
{
- struct ast_json_payload *payload = stasis_message_data(message);
- int class_type = ast_json_integer_get(ast_json_object_get(payload->json, "class_type"));
- const char *type = ast_json_string_get(ast_json_object_get(payload->json, "type"));
- struct ast_json *event = ast_json_object_get(payload->json, "event");
- RAII_VAR(struct ast_str *, event_buffer, NULL, ast_free);
+ struct ast_json_payload *payload;
+ int class_type;
+ const char *type;
+ struct ast_json *event;
+ struct ast_str *event_buffer;
+ struct ao2_container *sessions;
+
+ sessions = ao2_global_obj_ref(mgr_sessions);
+ if (!any_manager_listeners(sessions)) {
+ /* Nobody is listening */
+ ao2_cleanup(sessions);
+ return;
+ }
+
+ payload = stasis_message_data(message);
+ class_type = ast_json_integer_get(ast_json_object_get(payload->json, "class_type"));
+ type = ast_json_string_get(ast_json_object_get(payload->json, "type"));
+ event = ast_json_object_get(payload->json, "event");
event_buffer = ast_manager_str_from_json_object(event, NULL);
if (!event_buffer) {
ast_log(AST_LOG_WARNING, "Error while creating payload for event %s\n", type);
+ ao2_cleanup(sessions);
return;
}
- manager_event(class_type, type, "%s", ast_str_buffer(event_buffer));
+ manager_event_sessions(sessions, class_type, type,
+ "%s", ast_str_buffer(event_buffer));
+ ast_free(event_buffer);
+ ao2_cleanup(sessions);
}
void ast_manager_publish_event(const char *type, int class_type, struct ast_json *obj)
@@ -4698,7 +4747,7 @@ static int action_blind_transfer(struct mansession *s, const struct message *m)
const char *name = astman_get_header(m, "Channel");
const char *exten = astman_get_header(m, "Exten");
const char *context = astman_get_header(m, "Context");
- RAII_VAR(struct ast_channel *, chan, NULL, ao2_cleanup);
+ struct ast_channel *chan;
if (ast_strlen_zero(name)) {
astman_send_error(s, m, "No channel specified");
@@ -4735,6 +4784,7 @@ static int action_blind_transfer(struct mansession *s, const struct message *m)
break;
}
+ ast_channel_unref(chan);
return 0;
}
@@ -5907,7 +5957,7 @@ static int action_coreshowchannels(struct mansession *s, const struct message *m
const char *actionid = astman_get_header(m, "ActionID");
char idText[256];
int numchans = 0;
- RAII_VAR(struct ao2_container *, channels, NULL, ao2_cleanup);
+ struct ao2_container *channels;
struct ao2_iterator it_chans;
struct stasis_message *msg;
@@ -5917,7 +5967,8 @@ static int action_coreshowchannels(struct mansession *s, const struct message *m
idText[0] = '\0';
}
- if (!(channels = stasis_cache_dump(ast_channel_cache_by_name(), ast_channel_snapshot_type()))) {
+ channels = stasis_cache_dump(ast_channel_cache_by_name(), ast_channel_snapshot_type());
+ if (!channels) {
astman_send_error(s, m, "Could not get cached channels");
return 0;
}
@@ -5969,6 +6020,7 @@ static int action_coreshowchannels(struct mansession *s, const struct message *m
astman_send_list_complete_start(s, m, "CoreShowChannelsComplete", numchans);
astman_send_list_complete_end(s);
+ ao2_ref(channels, -1);
return 0;
}
@@ -6597,11 +6649,10 @@ static int append_event(const char *str, int category)
static void append_channel_vars(struct ast_str **pbuf, struct ast_channel *chan)
{
- RAII_VAR(struct varshead *, vars, NULL, ao2_cleanup);
+ struct varshead *vars;
struct ast_var_t *var;
vars = ast_channel_get_manager_vars(chan);
-
if (!vars) {
return;
}
@@ -6609,62 +6660,67 @@ static void append_channel_vars(struct ast_str **pbuf, struct ast_channel *chan)
AST_LIST_TRAVERSE(vars, var, entries) {
ast_str_append(pbuf, 0, "ChanVariable(%s): %s=%s\r\n", ast_channel_name(chan), var->name, var->value);
}
+ ao2_ref(vars, -1);
}
/* XXX see if can be moved inside the function */
AST_THREADSTORAGE(manager_event_buf);
#define MANAGER_EVENT_BUF_INITSIZE 256
-int __ast_manager_event_multichan(int category, const char *event, int chancount,
- struct ast_channel **chans, const char *file, int line, const char *func,
- const char *fmt, ...)
+static int __attribute__((format(printf, 9, 0))) __manager_event_sessions_va(
+ struct ao2_container *sessions,
+ int category,
+ const char *event,
+ int chancount,
+ struct ast_channel **chans,
+ const char *file,
+ int line,
+ const char *func,
+ const char *fmt,
+ va_list ap)
{
- RAII_VAR(struct ao2_container *, sessions, ao2_global_obj_ref(mgr_sessions), ao2_cleanup);
- struct mansession_session *session;
- struct manager_custom_hook *hook;
struct ast_str *auth = ast_str_alloca(MAX_AUTH_PERM_STRING);
const char *cat_str;
- va_list ap;
struct timeval now;
struct ast_str *buf;
int i;
- if (!(sessions && ao2_container_count(sessions)) && AST_RWLIST_EMPTY(&manager_hooks)) {
- return 0;
- }
-
- if (!(buf = ast_str_thread_get(&manager_event_buf, MANAGER_EVENT_BUF_INITSIZE))) {
+ buf = ast_str_thread_get(&manager_event_buf, MANAGER_EVENT_BUF_INITSIZE);
+ if (!buf) {
return -1;
}
cat_str = authority_to_str(category, &auth);
ast_str_set(&buf, 0,
- "Event: %s\r\nPrivilege: %s\r\n",
- event, cat_str);
+ "Event: %s\r\n"
+ "Privilege: %s\r\n",
+ event, cat_str);
if (timestampevents) {
now = ast_tvnow();
ast_str_append(&buf, 0,
- "Timestamp: %ld.%06lu\r\n",
- (long)now.tv_sec, (unsigned long) now.tv_usec);
+ "Timestamp: %ld.%06lu\r\n",
+ (long)now.tv_sec, (unsigned long) now.tv_usec);
}
if (manager_debug) {
static int seq;
+
ast_str_append(&buf, 0,
- "SequenceNumber: %d\r\n",
- ast_atomic_fetchadd_int(&seq, 1));
+ "SequenceNumber: %d\r\n",
+ ast_atomic_fetchadd_int(&seq, 1));
ast_str_append(&buf, 0,
- "File: %s\r\nLine: %d\r\nFunc: %s\r\n", file, line, func);
+ "File: %s\r\n"
+ "Line: %d\r\n"
+ "Func: %s\r\n",
+ file, line, func);
}
if (!ast_strlen_zero(ast_config_AST_SYSTEM_NAME)) {
ast_str_append(&buf, 0,
- "SystemName: %s\r\n",
- ast_config_AST_SYSTEM_NAME);
+ "SystemName: %s\r\n",
+ ast_config_AST_SYSTEM_NAME);
}
- va_start(ap, fmt);
ast_str_append_va(&buf, 0, fmt, ap);
- va_end(ap);
for (i = 0; i < chancount; i++) {
append_channel_vars(&buf, chans[i]);
}
@@ -6675,9 +6731,11 @@ int __ast_manager_event_multichan(int category, const char *event, int chancount
/* Wake up any sleeping sessions */
if (sessions) {
- struct ao2_iterator i;
- i = ao2_iterator_init(sessions, 0);
- while ((session = ao2_iterator_next(&i))) {
+ struct ao2_iterator iter;
+ struct mansession_session *session;
+
+ iter = ao2_iterator_init(sessions, 0);
+ while ((session = ao2_iterator_next(&iter))) {
ao2_lock(session);
if (session->waiting_thread != AST_PTHREADT_NULL) {
pthread_kill(session->waiting_thread, SIGURG);
@@ -6692,10 +6750,12 @@ int __ast_manager_event_multichan(int category, const char *event, int chancount
ao2_unlock(session);
unref_mansession(session);
}
- ao2_iterator_destroy(&i);
+ ao2_iterator_destroy(&iter);
}
if (category != EVENT_FLAG_SHUTDOWN && !AST_RWLIST_EMPTY(&manager_hooks)) {
+ struct manager_custom_hook *hook;
+
AST_RWLIST_RDLOCK(&manager_hooks);
AST_RWLIST_TRAVERSE(&manager_hooks, hook, list) {
hook->helper(category, event, ast_str_buffer(buf));
@@ -6706,6 +6766,50 @@ int __ast_manager_event_multichan(int category, const char *event, int chancount
return 0;
}
+static int __attribute__((format(printf, 9, 0))) __manager_event_sessions(
+ struct ao2_container *sessions,
+ int category,
+ const char *event,
+ int chancount,
+ struct ast_channel **chans,
+ const char *file,
+ int line,
+ const char *func,
+ const char *fmt,
+ ...)
+{
+ va_list ap;
+ int res;
+
+ va_start(ap, fmt);
+ res = __manager_event_sessions_va(sessions, category, event, chancount, chans,
+ file, line, func, fmt, ap);
+ va_end(ap);
+ return res;
+}
+
+int __ast_manager_event_multichan(int category, const char *event, int chancount,
+ struct ast_channel **chans, const char *file, int line, const char *func,
+ const char *fmt, ...)
+{
+ struct ao2_container *sessions = ao2_global_obj_ref(mgr_sessions);
+ va_list ap;
+ int res;
+
+ if (!any_manager_listeners(sessions)) {
+ /* Nobody is listening */
+ ao2_cleanup(sessions);
+ return 0;
+ }
+
+ va_start(ap, fmt);
+ res = __manager_event_sessions_va(sessions, category, event, chancount, chans,
+ file, line, func, fmt, ap);
+ va_end(ap);
+ ao2_cleanup(sessions);
+ return res;
+}
+
/*! \brief
* support functions to register/unregister AMI action handlers,
*/
@@ -9184,6 +9288,7 @@ int ast_str_append_event_header(struct ast_str **fields_string,
static void manager_event_blob_dtor(void *obj)
{
struct ast_manager_event_blob *ev = obj;
+
ast_string_field_free_memory(ev);
}
@@ -9195,18 +9300,19 @@ ast_manager_event_blob_create(
const char *extra_fields_fmt,
...)
{
- RAII_VAR(struct ast_manager_event_blob *, ev, NULL, ao2_cleanup);
+ struct ast_manager_event_blob *ev;
va_list argp;
ast_assert(extra_fields_fmt != NULL);
ast_assert(manager_event != NULL);
- ev = ao2_alloc(sizeof(*ev), manager_event_blob_dtor);
+ ev = ao2_alloc_options(sizeof(*ev), manager_event_blob_dtor, AO2_ALLOC_OPT_LOCK_NOLOCK);
if (!ev) {
return NULL;
}
if (ast_string_field_init(ev, 20)) {
+ ao2_ref(ev, -1);
return NULL;
}
@@ -9214,10 +9320,8 @@ ast_manager_event_blob_create(
ev->event_flags = event_flags;
va_start(argp, extra_fields_fmt);
- ast_string_field_ptr_build_va(ev, &ev->extra_fields, extra_fields_fmt,
- argp);
+ ast_string_field_ptr_build_va(ev, &ev->extra_fields, extra_fields_fmt, argp);
va_end(argp);
- ao2_ref(ev, +1);
return ev;
}
diff --git a/main/manager_channels.c b/main/manager_channels.c
index adef639e8..ef71c65b1 100644
--- a/main/manager_channels.c
+++ b/main/manager_channels.c
@@ -697,28 +697,33 @@ static void channel_hangup_request_cb(void *data,
struct stasis_message *message)
{
struct ast_channel_blob *obj = stasis_message_data(message);
- RAII_VAR(struct ast_str *, extra, NULL, ast_free);
- RAII_VAR(struct ast_str *, channel_event_string, NULL, ast_free);
+ struct ast_str *extra;
+ struct ast_str *channel_event_string;
struct ast_json *cause;
int is_soft;
char *manager_event = "HangupRequest";
+ if (!obj->snapshot) {
+ /* No snapshot? Likely an earlier allocation failure creating it. */
+ return;
+ }
+
extra = ast_str_create(20);
if (!extra) {
return;
}
channel_event_string = ast_manager_build_channel_state_string(obj->snapshot);
-
if (!channel_event_string) {
+ ast_free(extra);
return;
}
cause = ast_json_object_get(obj->blob, "cause");
if (cause) {
ast_str_append(&extra, 0,
- "Cause: %jd\r\n",
- ast_json_integer_get(cause));
+ "Cause: %jd\r\n",
+ ast_json_integer_get(cause));
}
is_soft = ast_json_is_true(ast_json_object_get(obj->blob, "soft"));
@@ -727,9 +732,12 @@ static void channel_hangup_request_cb(void *data,
}
manager_event(EVENT_FLAG_CALL, manager_event,
- "%s%s",
- ast_str_buffer(channel_event_string),
- ast_str_buffer(extra));
+ "%s%s",
+ ast_str_buffer(channel_event_string),
+ ast_str_buffer(extra));
+
+ ast_free(channel_event_string);
+ ast_free(extra);
}
static void channel_chanspy_stop_cb(void *data, struct stasis_subscription *sub,
diff --git a/main/stasis_endpoints.c b/main/stasis_endpoints.c
index fbca2ad9b..a91cd9f6b 100644
--- a/main/stasis_endpoints.c
+++ b/main/stasis_endpoints.c
@@ -86,6 +86,7 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
<enum name="Reachable"/>
<enum name="Created"/>
<enum name="Removed"/>
+ <enum name="Updated"/>
</enumlist>
</parameter>
<parameter name="AOR">
@@ -97,6 +98,12 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
<parameter name="RoundtripUsec">
<para>The RTT measured during the last qualify.</para>
</parameter>
+ <parameter name="UserAgent">
+ <para>Content of the User-Agent header in REGISTER request</para>
+ </parameter>
+ <parameter name="RegExpire">
+ <para>Absolute time that this contact is no longer valid after</para>
+ </parameter>
</syntax>
</managerEventInstance>
</managerEvent>
diff --git a/main/stasis_message.c b/main/stasis_message.c
index c797cdfa0..99721ef3c 100644
--- a/main/stasis_message.c
+++ b/main/stasis_message.c
@@ -170,17 +170,17 @@ const struct timeval *stasis_message_timestamp(const struct stasis_message *msg)
return &msg->timestamp;
}
-#define INVOKE_VIRTUAL(fn, ...) \
- ({ \
- if (msg == NULL) { \
- return NULL; \
- } \
- ast_assert(msg->type != NULL); \
+#define INVOKE_VIRTUAL(fn, ...) \
+ ({ \
+ if (!msg) { \
+ return NULL; \
+ } \
+ ast_assert(msg->type != NULL); \
ast_assert(msg->type->vtable != NULL); \
- if (msg->type->vtable->fn == NULL) { \
- return NULL; \
- } \
- msg->type->vtable->fn(__VA_ARGS__); \
+ if (!msg->type->vtable->fn) { \
+ return NULL; \
+ } \
+ msg->type->vtable->fn(__VA_ARGS__); \
})
struct ast_manager_event_blob *stasis_message_to_ami(struct stasis_message *msg)
@@ -199,3 +199,18 @@ struct ast_event *stasis_message_to_event(struct stasis_message *msg)
{
return INVOKE_VIRTUAL(to_event, msg);
}
+
+#define HAS_VIRTUAL(fn, msg) \
+ ({ \
+ if (!msg) { \
+ return 0; \
+ } \
+ ast_assert(msg->type != NULL); \
+ ast_assert(msg->type->vtable != NULL); \
+ !!msg->type->vtable->fn; \
+ })
+
+int stasis_message_can_be_ami(struct stasis_message *msg)
+{
+ return HAS_VIRTUAL(to_ami, msg);
+}
diff --git a/makeopts.in b/makeopts.in
index bfc965de4..9fa49bc04 100644
--- a/makeopts.in
+++ b/makeopts.in
@@ -243,6 +243,9 @@ PORTAUDIO_LIB=@PORTAUDIO_LIB@
PRI_INCLUDE=@PRI_INCLUDE@
PRI_LIB=@PRI_LIB@
+PYTHONDEV_INCLUDE=@PYTHONDEV_INCLUDE@
+PYTHONDEV_LIB=@PYTHONDEV_LIB@
+
RESAMPLE_INCLUDE=@RESAMPLE_INCLUDE@
RESAMPLE_LIB=@RESAMPLE_LIB@
diff --git a/res/res_agi.c b/res/res_agi.c
index ff3358062..e3839dd6d 100644
--- a/res/res_agi.c
+++ b/res/res_agi.c
@@ -3736,6 +3736,24 @@ static enum agi_result agi_handle_command(struct ast_channel *chan, AGI *agi, ch
return AGI_RESULT_SUCCESS;
}
+
+AST_LIST_HEAD_NOLOCK(deferred_frames, ast_frame);
+
+static void queue_deferred_frames(struct deferred_frames *deferred_frames,
+ struct ast_channel *chan)
+{
+ struct ast_frame *f;
+
+ if (!AST_LIST_EMPTY(deferred_frames)) {
+ ast_channel_lock(chan);
+ while ((f = AST_LIST_REMOVE_HEAD(deferred_frames, frame_list))) {
+ ast_queue_frame_head(chan, f);
+ ast_frfree(f);
+ }
+ ast_channel_unlock(chan);
+ }
+}
+
static enum agi_result run_agi(struct ast_channel *chan, char *request, AGI *agi, int pid, int *status, int dead, int argc, char *argv[])
{
struct ast_channel *c;
@@ -3754,6 +3772,9 @@ static enum agi_result run_agi(struct ast_channel *chan, char *request, AGI *agi
const char *sighup_str;
const char *exit_on_hangup_str;
int exit_on_hangup;
+ struct deferred_frames deferred_frames;
+
+ AST_LIST_HEAD_INIT_NOLOCK(&deferred_frames);
ast_channel_lock(chan);
sighup_str = pbx_builtin_getvar_helper(chan, "AGISIGHUP");
@@ -3815,8 +3836,20 @@ static enum agi_result run_agi(struct ast_channel *chan, char *request, AGI *agi
/* Write, ignoring errors */
if (write(agi->audio, f->data.ptr, f->datalen) < 0) {
}
+ ast_frfree(f);
+ } else if (ast_is_deferrable_frame(f)) {
+ struct ast_frame *dup_f;
+
+ if ((dup_f = ast_frisolate(f))) {
+ AST_LIST_INSERT_HEAD(&deferred_frames, dup_f, frame_list);
+ }
+
+ if (dup_f != f) {
+ ast_frfree(f);
+ }
+ } else {
+ ast_frfree(f);
}
- ast_frfree(f);
}
} else if (outfd > -1) {
size_t len = sizeof(buf);
@@ -3864,6 +3897,8 @@ static enum agi_result run_agi(struct ast_channel *chan, char *request, AGI *agi
buf[buflen - 1] = '\0';
}
+ queue_deferred_frames(&deferred_frames, chan);
+
if (agidebug)
ast_verbose("<%s>AGI Rx << %s\n", ast_channel_name(chan), buf);
cmd_status = agi_handle_command(chan, agi, buf, dead);
@@ -3885,6 +3920,9 @@ static enum agi_result run_agi(struct ast_channel *chan, char *request, AGI *agi
}
}
}
+
+ queue_deferred_frames(&deferred_frames, chan);
+
if (agi->speech) {
ast_speech_destroy(agi->speech);
}
diff --git a/res/res_fax.c b/res/res_fax.c
index ef0e27696..6282b13d7 100644
--- a/res/res_fax.c
+++ b/res/res_fax.c
@@ -626,6 +626,8 @@ static const struct ast_datastore_info fax_datastore = {
static int fax_gateway_attach(struct ast_channel *chan, struct ast_fax_session_details *details);
static int fax_detect_attach(struct ast_channel *chan, int timeout, int flags);
static struct ast_fax_session_details *find_or_create_details(struct ast_channel *chan);
+static struct ast_fax_session *fax_v21_session_new (struct ast_channel *chan);
+
/*! \brief Copies fax detection and gateway framehooks during masquerades
*
@@ -1447,6 +1449,12 @@ static void set_channel_variables(struct ast_channel *chan, struct ast_fax_sessi
pbx_builtin_setvar_helper(chan, "FAXBITRATE", S_OR(details->transfer_rate, NULL));
pbx_builtin_setvar_helper(chan, "FAXRESOLUTION", S_OR(details->resolution, NULL));
+ if (ast_channel_get_t38_state(chan) == T38_STATE_NEGOTIATED) {
+ pbx_builtin_setvar_helper(chan, "FAXMODE", "T38");
+ } else {
+ pbx_builtin_setvar_helper(chan, "FAXMODE", "audio");
+ }
+
snprintf(buf, sizeof(buf), "%u", details->pages_transferred);
pbx_builtin_setvar_helper(chan, "FAXPAGES", buf);
}
@@ -2071,6 +2079,7 @@ static int receivefax_exec(struct ast_channel *chan, const char *data)
pbx_builtin_setvar_helper(chan, "FAXPAGES", "0");
pbx_builtin_setvar_helper(chan, "FAXBITRATE", NULL);
pbx_builtin_setvar_helper(chan, "FAXRESOLUTION", NULL);
+ pbx_builtin_setvar_helper(chan, "FAXMODE", NULL);
/* Get a FAX session details structure from the channel's FAX datastore and create one if
* it does not already exist. */
@@ -2578,6 +2587,7 @@ static int sendfax_exec(struct ast_channel *chan, const char *data)
pbx_builtin_setvar_helper(chan, "FAXPAGES", "0");
pbx_builtin_setvar_helper(chan, "FAXBITRATE", NULL);
pbx_builtin_setvar_helper(chan, "FAXRESOLUTION", NULL);
+ pbx_builtin_setvar_helper(chan, "FAXMODE", NULL);
/* Get a requirement structure and set it. This structure is used
* to tell the FAX technology module about the higher level FAX session */
@@ -2827,6 +2837,23 @@ static void destroy_gateway(void *data)
ao2_cleanup(gateway->peer_write_format);
}
+static struct ast_fax_session *fax_v21_session_new (struct ast_channel *chan) {
+ struct ast_fax_session_details *v21_details;
+ struct ast_fax_session *v21_session;
+
+ if (!chan || !(v21_details = session_details_new())) {
+ return NULL;
+ }
+
+ v21_details->caps = AST_FAX_TECH_V21_DETECT;
+ if (!(v21_session = fax_session_new(v21_details, chan, NULL, NULL))) {
+ ao2_ref(v21_details, -1);
+ return NULL;
+ }
+
+ return v21_session;
+}
+
/*! \brief Create a new fax gateway object.
* \param chan the channel the gateway object will be attached to
* \param details the fax session details
@@ -2835,29 +2862,15 @@ static void destroy_gateway(void *data)
static struct fax_gateway *fax_gateway_new(struct ast_channel *chan, struct ast_fax_session_details *details)
{
struct fax_gateway *gateway = ao2_alloc(sizeof(*gateway), destroy_gateway);
- struct ast_fax_session_details *v21_details;
if (!gateway) {
return NULL;
}
- if (!(v21_details = session_details_new())) {
- ao2_ref(gateway, -1);
- return NULL;
- }
-
- v21_details->caps = AST_FAX_TECH_V21_DETECT;
- if (!(gateway->chan_v21_session = fax_session_new(v21_details, chan, NULL, NULL))) {
- ao2_ref(v21_details, -1);
- ao2_ref(gateway, -1);
- return NULL;
- }
-
- if (!(gateway->peer_v21_session = fax_session_new(v21_details, chan, NULL, NULL))) {
- ao2_ref(v21_details, -1);
+ if (!(gateway->chan_v21_session = fax_v21_session_new(chan))) {
+ ast_log(LOG_ERROR, "Can't create V21 session on chan %s for T.38 gateway session\n", ast_channel_name(chan));
ao2_ref(gateway, -1);
return NULL;
}
- ao2_ref(v21_details, -1);
gateway->framehook = -1;
@@ -3352,6 +3365,11 @@ static struct ast_frame *fax_gateway_framehook(struct ast_channel *chan, struct
ast_channel_unlock(peer);
gateway->bridged = 1;
+ if (!(gateway->peer_v21_session = fax_v21_session_new(peer))) {
+ ast_log(LOG_ERROR, "Can't create V21 session on chan %s for T.38 gateway session\n", ast_channel_name(peer));
+ ast_framehook_detach(chan, gateway->framehook);
+ return f;
+ }
}
if (gateway->bridged && !ast_tvzero(gateway->timeout_start)) {
@@ -3478,6 +3496,10 @@ static int fax_gateway_attach(struct ast_channel *chan, struct ast_fax_session_d
.disable_inheritance = 1, /* Masquerade inheritance is handled through the datastore fixup */
};
+ if (global_fax_debug) {
+ details->option.debug = AST_FAX_OPTFLAG_TRUE;
+ }
+
ast_string_field_set(details, result, "SUCCESS");
ast_string_field_set(details, resultstr, "gateway operation started successfully");
ast_string_field_set(details, error, "NO_ERROR");
diff --git a/res/res_pjsip.c b/res/res_pjsip.c
index 0437a652e..61d26302d 100644
--- a/res/res_pjsip.c
+++ b/res/res_pjsip.c
@@ -252,18 +252,35 @@
<configOption name="identify_by" default="username,location">
<synopsis>Way(s) for Endpoint to be identified</synopsis>
<description><para>
- An endpoint can be identified in multiple ways. Currently, the only supported
- option is <literal>username</literal>, which matches the endpoint based on the
- username in the From header.
+ Endpoints and aors can be identified in multiple ways. Currently, the supported
+ options are <literal>username</literal>, which matches the endpoint or aor id based on
+ the username and domain in the From header (or To header for aors), and
+ <literal>auth_username</literal>, which matches the endpoint or aor id based on the
+ username and realm in the Authentication header. In all cases, if an exact match
+ on both username and domain/realm fails, the match will be retried with just the username.
</para>
+ <note><para>
+ Identification by auth_username has some security considerations because an
+ Authentication header is not present on the first message of a dialog when
+ digest authentication is used. The client can't generate it until the server
+ sends the challenge in a 401 response. Since Asterisk normally sends a security
+ event when an incoming request can't be matched to an endpoint, using auth_username
+ requires that the security event be deferred until a request is received with
+ the Authentication header and only generated if the username doesn't result in a
+ match. This may result in a delay before an attack is recognized. You can control
+ how many unmatched requests are received from a single ip address before a security
+ event is generated using the unidentified_request parameters in the "global"
+ configuration object.
+ </para></note>
<note><para>Endpoints can also be identified by IP address; however, that method
of identification is not handled by this configuration option. See the documentation
for the <literal>identify</literal> configuration section for more details on that
- method of endpoint identification. If this option is set to <literal>username</literal>
- and an <literal>identify</literal> configuration section exists for the endpoint, then
- the endpoint can be identified in multiple ways.</para></note>
+ method of endpoint identification. If this option is set and an <literal>identify</literal>
+ configuration section exists for the endpoint, then the endpoint can be identified in
+ multiple ways.</para></note>
<enumlist>
<enum name="username" />
+ <enum name="auth_username" />
</enumlist>
</description>
</configOption>
@@ -1099,6 +1116,12 @@
REGISTER requests and is not intended to be configured manually.
</para></description>
</configOption>
+ <configOption name="reg_server">
+ <synopsis>Asterisk Server name</synopsis>
+ <description><para>
+ Asterisk Server name on which SIP endpoint registered.
+ </para></description>
+ </configOption>
</configObject>
<configObject name="aor">
<synopsis>The configuration for a location of an endpoint</synopsis>
@@ -1298,10 +1321,34 @@
<configOption name="contact_expiration_check_interval" default="30">
<synopsis>The interval (in seconds) to check for expired contacts.</synopsis>
</configOption>
+ <configOption name="disable_multi_domain" default="no">
+ <synopsis>Disable Multi Domain support</synopsis>
+ <description><para>
+ If disabled it can improve realtime performace by reducing number of database requsts.
+ </para></description>
+ </configOption>
<configOption name="max_initial_qualify_time" default="0">
<synopsis>The maximum amount of time from startup that qualifies should be attempted on all contacts.
If greater than the qualify_frequency for an aor, qualify_frequency will be used instead.</synopsis>
</configOption>
+ <configOption name="unidentified_request_period" default="5">
+ <synopsis>The number of seconds over which to accumulate unidentified requests.</synopsis>
+ <description><para>
+ If <literal>unidentified_request_count</literal> unidentified requests are received
+ during <literal>unidentified_request_period</literal>, a security event will be generated.
+ </para></description>
+ </configOption>
+ <configOption name="unidentified_request_count" default="5">
+ <synopsis>The number of unidentified requests from a single IP to allow.</synopsis>
+ <description><para>
+ If <literal>unidentified_request_count</literal> unidentified requests are received
+ during <literal>unidentified_request_period</literal>, a security event will be generated.
+ </para></description>
+ </configOption>
+ <configOption name="unidentified_request_prune_interval" default="30">
+ <synopsis>The interval at which unidentified requests are older than
+ twice the unidentified_request_period are pruned.</synopsis>
+ </configOption>
<configOption name="type">
<synopsis>Must be of type 'global'.</synopsis>
</configOption>
@@ -1325,13 +1372,35 @@
<configOption name="endpoint_identifier_order" default="ip,username,anonymous">
<synopsis>The order by which endpoint identifiers are processed and checked.
Identifier names are usually derived from and can be found in the endpoint
- identifier module itself (res_pjsip_endpoint_identifier_*)</synopsis>
+ identifier module itself (res_pjsip_endpoint_identifier_*).
+ You can use the CLI command "pjsip show identifiers" to see the
+ identifiers currently available.</synopsis>
+ <description>
+ <note><para>
+ One of the identifiers is "auth_username" which matches on the username in
+ an Authentication header. This method has some security considerations because an
+ Authentication header is not present on the first message of a dialog when
+ digest authentication is used. The client can't generate it until the server
+ sends the challenge in a 401 response. Since Asterisk normally sends a security
+ event when an incoming request can't be matched to an endpoint, using auth_username
+ requires that the security event be deferred until a request is received with
+ the Authentication header and only generated if the username doesn't result in a
+ match. This may result in a delay before an attack is recognized. You can control
+ how many unmatched requests are received from a single ip address before a security
+ event is generated using the unidentified_request parameters.
+ </para></note>
+ </description>
</configOption>
<configOption name="default_from_user" default="asterisk">
<synopsis>When Asterisk generates an outgoing SIP request, the From header username will be
set to this value if there is no better option (such as CallerID) to be
used.</synopsis>
</configOption>
+ <configOption name="default_realm" default="asterisk">
+ <synopsis>When Asterisk generates an challenge, the digest will be
+ set to this value if there is no better option (such as auth/realm) to be
+ used.</synopsis>
+ </configOption>
</configObject>
</configFile>
</configInfo>
@@ -3609,11 +3678,7 @@ int ast_sip_push_task(struct ast_taskprocessor *serializer, int (*sip_task)(void
serializer = serializer_pool[pos];
}
- if (serializer) {
- return ast_taskprocessor_push(serializer, sip_task, task_data);
- } else {
- return ast_threadpool_push(sip_threadpool, sip_task, task_data);
- }
+ return ast_taskprocessor_push(serializer, sip_task, task_data);
}
struct sync_task_data {
@@ -4132,6 +4197,11 @@ static int load_module(void)
goto error;
}
+ if (ast_sip_initialize_scheduler()) {
+ ast_log(LOG_ERROR, "Failed to start scheduler. Aborting load\n");
+ goto error;
+ }
+
/* Now load all the pjproject infrastructure. */
if (load_pjsip()) {
goto error;
@@ -4172,8 +4242,10 @@ static int load_module(void)
return AST_MODULE_LOAD_SUCCESS;
error:
- /* These functions all check for NULLs and are safe to call at any time */
unload_pjsip(NULL);
+
+ /* These functions all check for NULLs and are safe to call at any time */
+ ast_sip_destroy_scheduler();
serializer_pool_shutdown();
ast_threadpool_shutdown(sip_threadpool);
@@ -4204,7 +4276,7 @@ static int unload_module(void)
* so we have to push the work to the threadpool to handle
*/
ast_sip_push_task_synchronous(NULL, unload_pjsip, NULL);
-
+ ast_sip_destroy_scheduler();
serializer_pool_shutdown();
ast_threadpool_shutdown(sip_threadpool);
diff --git a/res/res_pjsip/config_global.c b/res/res_pjsip/config_global.c
index 8348a1eb5..6bb688804 100644
--- a/res/res_pjsip/config_global.c
+++ b/res/res_pjsip/config_global.c
@@ -35,9 +35,14 @@
#define DEFAULT_ENDPOINT_IDENTIFIER_ORDER "ip,username,anonymous"
#define DEFAULT_MAX_INITIAL_QUALIFY_TIME 0
#define DEFAULT_FROM_USER "asterisk"
+#define DEFAULT_REALM "asterisk"
#define DEFAULT_REGCONTEXT ""
#define DEFAULT_CONTACT_EXPIRATION_CHECK_INTERVAL 30
+#define DEFAULT_DISABLE_MULTI_DOMAIN 0
#define DEFAULT_VOICEMAIL_EXTENSION ""
+#define DEFAULT_UNIDENTIFIED_REQUEST_COUNT 5
+#define DEFAULT_UNIDENTIFIED_REQUEST_PERIOD 5
+#define DEFAULT_UNIDENTIFIED_REQUEST_PRUNE_INTERVAL 30
static char default_useragent[256];
@@ -55,6 +60,8 @@ struct global_config {
AST_STRING_FIELD(default_from_user);
/*! Default voicemail extension */
AST_STRING_FIELD(default_voicemail_extension);
+ /*! Realm to use in challenges before an endpoint is identified */
+ AST_STRING_FIELD(default_realm);
);
/* Value to put in Max-Forwards header */
unsigned int max_forwards;
@@ -64,6 +71,14 @@ struct global_config {
unsigned int max_initial_qualify_time;
/* The interval at which to check for expired contacts */
unsigned int contact_expiration_check_interval;
+ /*! Nonzero to disable multi domain support */
+ unsigned int disable_multi_domain;
+ /* The maximum number of unidentified requests per source IP address before a security event is logged */
+ unsigned int unidentified_request_count;
+ /* The period during which unidentified requests are accumulated */
+ unsigned int unidentified_request_period;
+ /* Interval at which expired unidentifed requests will be pruned */
+ unsigned int unidentified_request_prune_interval;
};
static void global_destructor(void *obj)
@@ -222,6 +237,21 @@ unsigned int ast_sip_get_contact_expiration_check_interval(void)
return interval;
}
+unsigned int ast_sip_get_disable_multi_domain(void)
+{
+ unsigned int disable_multi_domain;
+ struct global_config *cfg;
+
+ cfg = get_global_cfg();
+ if (!cfg) {
+ return DEFAULT_DISABLE_MULTI_DOMAIN;
+ }
+
+ disable_multi_domain = cfg->disable_multi_domain;
+ ao2_ref(cfg, -1);
+ return disable_multi_domain;
+}
+
unsigned int ast_sip_get_max_initial_qualify_time(void)
{
unsigned int time;
@@ -237,6 +267,40 @@ unsigned int ast_sip_get_max_initial_qualify_time(void)
return time;
}
+void ast_sip_get_unidentified_request_thresholds(unsigned int *count, unsigned int *period,
+ unsigned int *prune_interval)
+{
+ struct global_config *cfg;
+
+ cfg = get_global_cfg();
+ if (!cfg) {
+ *count = DEFAULT_UNIDENTIFIED_REQUEST_COUNT;
+ *period = DEFAULT_UNIDENTIFIED_REQUEST_PERIOD;
+ *prune_interval = DEFAULT_UNIDENTIFIED_REQUEST_PRUNE_INTERVAL;
+ return;
+ }
+
+ *count = cfg->unidentified_request_count;
+ *period = cfg->unidentified_request_period;
+ *prune_interval = cfg->unidentified_request_prune_interval;
+
+ ao2_ref(cfg, -1);
+ return;
+}
+
+void ast_sip_get_default_realm(char *realm, size_t size)
+{
+ struct global_config *cfg;
+
+ cfg = get_global_cfg();
+ if (!cfg) {
+ ast_copy_string(realm, DEFAULT_REALM, size);
+ } else {
+ ast_copy_string(realm, cfg->default_realm, size);
+ ao2_ref(cfg, -1);
+ }
+}
+
void ast_sip_get_default_from_user(char *from_user, size_t size)
{
struct global_config *cfg;
@@ -373,6 +437,19 @@ int ast_sip_initialize_sorcery_global(void)
ast_sorcery_object_field_register(sorcery, "global", "contact_expiration_check_interval",
__stringify(DEFAULT_CONTACT_EXPIRATION_CHECK_INTERVAL),
OPT_UINT_T, 0, FLDSET(struct global_config, contact_expiration_check_interval));
+ ast_sorcery_object_field_register(sorcery, "global", "disable_multi_domain", "no",
+ OPT_BOOL_T, 1, FLDSET(struct global_config, disable_multi_domain));
+ ast_sorcery_object_field_register(sorcery, "global", "unidentified_request_count",
+ __stringify(DEFAULT_UNIDENTIFIED_REQUEST_COUNT),
+ OPT_UINT_T, 0, FLDSET(struct global_config, unidentified_request_count));
+ ast_sorcery_object_field_register(sorcery, "global", "unidentified_request_period",
+ __stringify(DEFAULT_UNIDENTIFIED_REQUEST_PERIOD),
+ OPT_UINT_T, 0, FLDSET(struct global_config, unidentified_request_period));
+ ast_sorcery_object_field_register(sorcery, "global", "unidentified_request_prune_interval",
+ __stringify(DEFAULT_UNIDENTIFIED_REQUEST_PRUNE_INTERVAL),
+ OPT_UINT_T, 0, FLDSET(struct global_config, unidentified_request_prune_interval));
+ ast_sorcery_object_field_register(sorcery, "global", "default_realm", DEFAULT_REALM,
+ OPT_STRINGFIELD_T, 0, STRFLDSET(struct global_config, default_realm));
if (ast_sorcery_instance_observer_add(sorcery, &observer_callbacks_global)) {
return -1;
diff --git a/res/res_pjsip/include/res_pjsip_private.h b/res/res_pjsip/include/res_pjsip_private.h
index 72a4387f1..04cd85408 100644
--- a/res/res_pjsip/include/res_pjsip_private.h
+++ b/res/res_pjsip/include/res_pjsip_private.h
@@ -325,4 +325,23 @@ struct ast_sip_contact_status *ast_res_pjsip_find_or_create_contact_status(const
*/
int ast_sip_validate_uri_length(const char *uri);
+/*!
+ * \brief Initialize scheduler
+ * \since 13.9.0
+ *
+ * \retval -1 failure
+ * \retval 0 success
+ */
+int ast_sip_initialize_scheduler(void);
+
+/*!
+ * \internal
+ * \brief Destroy scheduler
+ * \since 13.9.0
+ *
+ * \retval -1 failure
+ * \retval 0 success
+ */
+int ast_sip_destroy_scheduler(void);
+
#endif /* RES_PJSIP_PRIVATE_H_ */
diff --git a/res/res_pjsip/location.c b/res/res_pjsip/location.c
index c30acc7b8..ef06456a7 100644
--- a/res/res_pjsip/location.c
+++ b/res/res_pjsip/location.c
@@ -23,6 +23,7 @@
#include "asterisk/res_pjsip.h"
#include "asterisk/logger.h"
#include "asterisk/astobj2.h"
+#include "asterisk/paths.h"
#include "asterisk/sorcery.h"
#include "include/res_pjsip_private.h"
#include "asterisk/res_pjsip_cli.h"
@@ -120,6 +121,8 @@ static void *contact_alloc(const char *name)
return NULL;
}
+ ast_string_field_init_extended(contact, reg_server);
+
/* Dynamic contacts are delimited with ";@" and static ones with "@@" */
if ((aor_separator = strstr(id, ";@")) || (aor_separator = strstr(id, "@@"))) {
*aor_separator = '\0';
@@ -335,6 +338,10 @@ int ast_sip_location_add_contact_nolock(struct ast_sip_aor *aor, const char *uri
ast_string_field_set(contact, user_agent, user_agent);
}
+ if (!ast_strlen_zero(ast_config_AST_SYSTEM_NAME)) {
+ ast_string_field_set(contact, reg_server, ast_config_AST_SYSTEM_NAME);
+ }
+
contact->endpoint = ao2_bump(endpoint);
return ast_sorcery_create(ast_sip_get_sorcery(), contact);
@@ -417,38 +424,64 @@ static int permanent_uri_sort_fn(const void *obj_left, const void *obj_right, in
int ast_sip_validate_uri_length(const char *contact_uri)
{
- pjsip_uri *uri;
- pjsip_sip_uri *sip_uri;
- pj_pool_t *pool;
int max_length = pj_max_hostname - 1;
+ char *contact = ast_strdupa(contact_uri);
+ char *host;
+ char *at;
+ int theres_a_port = 0;
if (strlen(contact_uri) > pjsip_max_url_size - 1) {
return -1;
}
- if (!(pool = pjsip_endpt_create_pool(ast_sip_get_pjsip_endpoint(), "uri validation", 512, 512))) {
- ast_log(LOG_ERROR, "Unable to allocate pool for uri validation\n");
+ contact = ast_strip_quoted(contact, "<", ">");
+
+ if (!strncasecmp(contact, "sip:", 4)) {
+ host = contact + 4;
+ } else if (!strncasecmp(contact, "sips:", 5)) {
+ host = contact + 5;
+ } else {
+ /* Not a SIP URI */
return -1;
}
- if (!(uri = pjsip_parse_uri(pool, (char *)contact_uri, strlen(contact_uri), 0)) ||
- (!PJSIP_URI_SCHEME_IS_SIP(uri) && !PJSIP_URI_SCHEME_IS_SIPS(uri))) {
- pjsip_endpt_release_pool(ast_sip_get_pjsip_endpoint(), pool);
- return -1;
+ at = strchr(contact, '@');
+ if (at) {
+ /* sip[s]:user@host */
+ host = at + 1;
}
- sip_uri = pjsip_uri_get_uri(uri);
- if (sip_uri->port == 0) {
+ if (host[0] == '[') {
+ /* Host is an IPv6 address. Just get up to the matching bracket */
+ char *close_bracket;
+
+ close_bracket = strchr(host, ']');
+ if (!close_bracket) {
+ return -1;
+ }
+ close_bracket++;
+ if (*close_bracket == ':') {
+ theres_a_port = 1;
+ }
+ *close_bracket = '\0';
+ } else {
+ /* uri parameters could contain ';' so trim them off first */
+ host = strsep(&host, ";?");
+ /* Host is FQDN or IPv4 address. Need to find closing delimiter */
+ if (strchr(host, ':')) {
+ theres_a_port = 1;
+ host = strsep(&host, ":");
+ }
+ }
+
+ if (!theres_a_port) {
max_length -= strlen("_sips.tcp.");
}
- if (sip_uri->host.slen > max_length) {
- pjsip_endpt_release_pool(ast_sip_get_pjsip_endpoint(), pool);
+ if (strlen(host) > max_length) {
return -1;
}
- pjsip_endpt_release_pool(ast_sip_get_pjsip_endpoint(), pool);
-
return 0;
}
@@ -1091,6 +1124,7 @@ int ast_sip_initialize_sorcery_location(void)
ast_sorcery_object_field_register(sorcery, "contact", "authenticate_qualify", "no", OPT_BOOL_T, 1, FLDSET(struct ast_sip_contact, authenticate_qualify));
ast_sorcery_object_field_register(sorcery, "contact", "outbound_proxy", "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct ast_sip_contact, outbound_proxy));
ast_sorcery_object_field_register(sorcery, "contact", "user_agent", "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct ast_sip_contact, user_agent));
+ ast_sorcery_object_field_register(sorcery, "contact", "reg_server", "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct ast_sip_contact, reg_server));
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 06588337c..8e7e95a5d 100644
--- a/res/res_pjsip/pjsip_configuration.c
+++ b/res/res_pjsip/pjsip_configuration.c
@@ -479,6 +479,16 @@ static int ident_handler(const struct aco_option *opt, struct ast_variable *var,
struct ast_sip_endpoint *endpoint = obj;
char *idents = ast_strdupa(var->value);
char *val;
+ enum ast_sip_endpoint_identifier_type method;
+
+ /*
+ * If there's already something in the vector when we get here,
+ * it's the default value so we need to clean it out.
+ */
+ if (AST_VECTOR_SIZE(&endpoint->ident_method_order)) {
+ AST_VECTOR_RESET(&endpoint->ident_method_order, AST_VECTOR_ELEM_CLEANUP_NOOP);
+ endpoint->ident_method = 0;
+ }
while ((val = ast_strip(strsep(&idents, ",")))) {
if (ast_strlen_zero(val)) {
@@ -486,27 +496,55 @@ static int ident_handler(const struct aco_option *opt, struct ast_variable *var,
}
if (!strcasecmp(val, "username")) {
- endpoint->ident_method |= AST_SIP_ENDPOINT_IDENTIFY_BY_USERNAME;
+ method = AST_SIP_ENDPOINT_IDENTIFY_BY_USERNAME;
+ } else if (!strcasecmp(val, "auth_username")) {
+ method = AST_SIP_ENDPOINT_IDENTIFY_BY_AUTH_USERNAME;
} else {
ast_log(LOG_ERROR, "Unrecognized identification method %s specified for endpoint %s\n",
val, ast_sorcery_object_get_id(endpoint));
+ AST_VECTOR_RESET(&endpoint->ident_method_order, AST_VECTOR_ELEM_CLEANUP_NOOP);
+ endpoint->ident_method = 0;
return -1;
}
+
+ endpoint->ident_method |= method;
+ AST_VECTOR_APPEND(&endpoint->ident_method_order, method);
}
+
return 0;
}
static int ident_to_str(const void *obj, const intptr_t *args, char **buf)
{
const struct ast_sip_endpoint *endpoint = obj;
- switch (endpoint->ident_method) {
- case AST_SIP_ENDPOINT_IDENTIFY_BY_USERNAME :
- *buf = "username"; break;
- default:
+ int methods;
+ char *method;
+ int i;
+ int j = 0;
+
+ methods = AST_VECTOR_SIZE(&endpoint->ident_method_order);
+ if (!methods) {
return 0;
}
- *buf = ast_strdup(*buf);
+ if (!(*buf = ast_calloc(MAX_OBJECT_FIELD, sizeof(char)))) {
+ return -1;
+ }
+
+ for (i = 0; i < methods; i++) {
+ switch (AST_VECTOR_GET(&endpoint->ident_method_order, i)) {
+ case AST_SIP_ENDPOINT_IDENTIFY_BY_USERNAME :
+ method = "username";
+ break;
+ case AST_SIP_ENDPOINT_IDENTIFY_BY_AUTH_USERNAME :
+ method = "auth_username";
+ break;
+ default:
+ continue;
+ }
+ j = sprintf(*buf + j, "%s%s", method, i < methods - 1 ? "," : "");
+ }
+
return 0;
}
@@ -1851,6 +1889,7 @@ static void endpoint_destructor(void* obj)
endpoint->pickup.named_pickupgroups = ast_unref_namedgroups(endpoint->pickup.named_pickupgroups);
ao2_cleanup(endpoint->persistent);
ast_variables_destroy(endpoint->channel_vars);
+ AST_VECTOR_FREE(&endpoint->ident_method_order);
}
static int init_subscription_configuration(struct ast_sip_endpoint_subscription_configuration *subscription)
@@ -1895,6 +1934,11 @@ void *ast_sip_endpoint_alloc(const char *name)
return NULL;
}
ast_party_id_init(&endpoint->id.self);
+
+ if (AST_VECTOR_INIT(&endpoint->ident_method_order, 1)) {
+ return NULL;
+ }
+
return endpoint;
}
diff --git a/res/res_pjsip/pjsip_distributor.c b/res/res_pjsip/pjsip_distributor.c
index 834ca10d3..cbe955728 100644
--- a/res/res_pjsip/pjsip_distributor.c
+++ b/res/res_pjsip/pjsip_distributor.c
@@ -24,6 +24,7 @@
#include "include/res_pjsip_private.h"
#include "asterisk/taskprocessor.h"
#include "asterisk/threadpool.h"
+#include "asterisk/res_pjsip_cli.h"
static int distribute(void *data);
static pj_bool_t distributor(pjsip_rx_data *rdata);
@@ -37,6 +38,26 @@ static pjsip_module distributor_mod = {
.on_rx_response = distributor,
};
+struct ast_sched_context *prune_context;
+
+/* From the auth/realm realtime column size */
+#define MAX_REALM_LENGTH 40
+static char default_realm[MAX_REALM_LENGTH + 1];
+
+#define DEFAULT_SUSPECTS_BUCKETS 53
+
+static struct ao2_container *unidentified_requests;
+static unsigned int unidentified_count;
+static unsigned int unidentified_period;
+static unsigned int unidentified_prune_interval;
+static int using_auth_username;
+
+struct unidentified_request{
+ struct timeval first_seen;
+ int count;
+ char src_name[];
+};
+
/*!
* \internal
* \brief Record the task's serializer name on the tdata structure.
@@ -322,7 +343,7 @@ static int create_artificial_auth(void)
return -1;
}
- ast_string_field_set(artificial_auth, realm, "asterisk");
+ ast_string_field_set(artificial_auth, realm, default_realm);
ast_string_field_set(artificial_auth, auth_user, "");
ast_string_field_set(artificial_auth, auth_pass, "");
artificial_auth->type = AST_SIP_AUTH_TYPE_ARTIFICIAL;
@@ -359,27 +380,65 @@ struct ast_sip_endpoint *ast_sip_get_artificial_endpoint(void)
return artificial_endpoint;
}
-static void log_unidentified_request(pjsip_rx_data *rdata)
+static void log_unidentified_request(pjsip_rx_data *rdata, unsigned int count, unsigned int period)
{
char from_buf[PJSIP_MAX_URL_SIZE];
char callid_buf[PJSIP_MAX_URL_SIZE];
pjsip_uri_print(PJSIP_URI_IN_FROMTO_HDR, rdata->msg_info.from->uri, from_buf, PJSIP_MAX_URL_SIZE);
ast_copy_pj_str(callid_buf, &rdata->msg_info.cid->id, PJSIP_MAX_URL_SIZE);
- ast_log(LOG_NOTICE, "Request from '%s' failed for '%s:%d' (callid: %s) - No matching endpoint found\n",
- from_buf, rdata->pkt_info.src_name, rdata->pkt_info.src_port, callid_buf);
+ if (count) {
+ ast_log(LOG_NOTICE, "Request from '%s' failed for '%s:%d' (callid: %s) - No matching endpoint found"
+ " after %u tries in %.3f ms\n",
+ from_buf, rdata->pkt_info.src_name, rdata->pkt_info.src_port, callid_buf, count, period / 1000.0);
+ } else {
+ ast_log(LOG_NOTICE, "Request from '%s' failed for '%s:%d' (callid: %s) - No matching endpoint found",
+ from_buf, rdata->pkt_info.src_name, rdata->pkt_info.src_port, callid_buf);
+ }
+}
+
+static void check_endpoint(pjsip_rx_data *rdata, struct unidentified_request *unid,
+ const char *name)
+{
+ int64_t ms = ast_tvdiff_ms(ast_tvnow(), unid->first_seen);
+
+ ao2_wrlock(unid);
+ unid->count++;
+
+ if (ms < (unidentified_period * 1000) && unid->count >= unidentified_count) {
+ log_unidentified_request(rdata, unid->count, ms);
+ ast_sip_report_invalid_endpoint(name, rdata);
+ }
+ ao2_unlock(unid);
}
static pj_bool_t endpoint_lookup(pjsip_rx_data *rdata)
{
struct ast_sip_endpoint *endpoint;
+ struct unidentified_request *unid;
int is_ack = rdata->msg_info.msg->line.req.method.id == PJSIP_ACK_METHOD;
endpoint = rdata->endpt_info.mod_data[endpoint_mod.id];
if (endpoint) {
+ /*
+ * ao2_find with OBJ_UNLINK always write locks the container before even searching
+ * for the object. Since the majority case is that the object won't be found, do
+ * the find without OBJ_UNLINK to prevent the unnecessary write lock, then unlink
+ * if needed.
+ */
+ if ((unid = ao2_find(unidentified_requests, rdata->pkt_info.src_name, OBJ_SEARCH_KEY))) {
+ ao2_unlink(unidentified_requests, unid);
+ ao2_ref(unid, -1);
+ }
return PJ_FALSE;
}
endpoint = ast_sip_identify_endpoint(rdata);
+ if (endpoint) {
+ if ((unid = ao2_find(unidentified_requests, rdata->pkt_info.src_name, OBJ_SEARCH_KEY))) {
+ ao2_unlink(unidentified_requests, unid);
+ ao2_ref(unid, -1);
+ }
+ }
if (!endpoint && !is_ack) {
char name[AST_UUID_STR_LEN] = "";
@@ -397,8 +456,32 @@ static pj_bool_t endpoint_lookup(pjsip_rx_data *rdata)
ast_copy_pj_str(name, &sip_from->user, sizeof(name));
}
- log_unidentified_request(rdata);
- ast_sip_report_invalid_endpoint(name, rdata);
+ if ((unid = ao2_find(unidentified_requests, rdata->pkt_info.src_name, OBJ_SEARCH_KEY))) {
+ check_endpoint(rdata, unid, name);
+ ao2_ref(unid, -1);
+ } else if (using_auth_username) {
+ ao2_wrlock(unidentified_requests);
+ /* The check again with the write lock held allows us to eliminate the DUPS_REPLACE and sort_fn */
+ if ((unid = ao2_find(unidentified_requests, rdata->pkt_info.src_name, OBJ_SEARCH_KEY | OBJ_NOLOCK))) {
+ check_endpoint(rdata, unid, name);
+ } else {
+ unid = ao2_alloc_options(sizeof(*unid) + strlen(rdata->pkt_info.src_name) + 1, NULL,
+ AO2_ALLOC_OPT_LOCK_RWLOCK);
+ if (!unid) {
+ ao2_unlock(unidentified_requests);
+ return PJ_TRUE;
+ }
+ strcpy(unid->src_name, rdata->pkt_info.src_name); /* Safe */
+ unid->first_seen = ast_tvnow();
+ unid->count = 1;
+ ao2_link_flags(unidentified_requests, unid, OBJ_NOLOCK);
+ }
+ ao2_ref(unid, -1);
+ ao2_unlock(unidentified_requests);
+ } else {
+ log_unidentified_request(rdata, 0, 0);
+ ast_sip_report_invalid_endpoint(name, rdata);
+ }
}
rdata->endpt_info.mod_data[endpoint_mod.id] = endpoint;
return PJ_FALSE;
@@ -413,6 +496,8 @@ static pj_bool_t authenticate(pjsip_rx_data *rdata)
if (!is_ack && ast_sip_requires_authentication(endpoint, rdata)) {
pjsip_tx_data *tdata;
+ struct unidentified_request *unid;
+
pjsip_endpt_create_response(ast_sip_get_pjsip_endpoint(), rdata, 401, NULL, &tdata);
switch (ast_sip_check_authentication(endpoint, rdata, tdata)) {
case AST_SIP_AUTHENTICATION_CHALLENGE:
@@ -421,6 +506,11 @@ static pj_bool_t authenticate(pjsip_rx_data *rdata)
pjsip_endpt_send_response2(ast_sip_get_pjsip_endpoint(), rdata, tdata, NULL, NULL);
return PJ_TRUE;
case AST_SIP_AUTHENTICATION_SUCCESS:
+ /* See note in endpoint_lookup about not holding an unnecessary write lock */
+ if ((unid = ao2_find(unidentified_requests, rdata->pkt_info.src_name, OBJ_SEARCH_KEY))) {
+ ao2_unlink(unidentified_requests, unid);
+ ao2_ref(unid, -1);
+ }
ast_sip_report_auth_success(endpoint, rdata);
pjsip_tx_data_dec_ref(tdata);
return PJ_FALSE;
@@ -480,31 +570,287 @@ struct ast_sip_endpoint *ast_pjsip_rdata_get_endpoint(pjsip_rx_data *rdata)
return endpoint;
}
+static int suspects_sort(const void *obj, const void *arg, int flags)
+{
+ const struct unidentified_request *object_left = obj;
+ const struct unidentified_request *object_right = arg;
+ const char *right_key = arg;
+ int cmp;
+
+ switch (flags & OBJ_SEARCH_MASK) {
+ case OBJ_SEARCH_OBJECT:
+ right_key = object_right->src_name;
+ /* Fall through */
+ case OBJ_SEARCH_KEY:
+ cmp = strcmp(object_left->src_name, right_key);
+ break;
+ case OBJ_SEARCH_PARTIAL_KEY:
+ cmp = strncmp(object_left->src_name, right_key, strlen(right_key));
+ break;
+ default:
+ cmp = 0;
+ break;
+ }
+ return cmp;
+}
+
+static int suspects_compare(void *obj, void *arg, int flags)
+{
+ const struct unidentified_request *object_left = obj;
+ const struct unidentified_request *object_right = arg;
+ const char *right_key = arg;
+ int cmp = 0;
+
+ switch (flags & OBJ_SEARCH_MASK) {
+ case OBJ_SEARCH_OBJECT:
+ right_key = object_right->src_name;
+ /* Fall through */
+ case OBJ_SEARCH_KEY:
+ if (strcmp(object_left->src_name, right_key) == 0) {
+ cmp = CMP_MATCH | CMP_STOP;
+ }
+ break;
+ case OBJ_SEARCH_PARTIAL_KEY:
+ if (strncmp(object_left->src_name, right_key, strlen(right_key)) == 0) {
+ cmp = CMP_MATCH;
+ }
+ break;
+ default:
+ cmp = 0;
+ break;
+ }
+ return cmp;
+}
+
+static int suspects_hash(const void *obj, int flags) {
+ const struct unidentified_request *object_left = obj;
+
+ if (flags & OBJ_SEARCH_OBJECT) {
+ return ast_str_hash(object_left->src_name);
+ } else if (flags & OBJ_SEARCH_KEY) {
+ return ast_str_hash(obj);
+ }
+ return -1;
+}
+
+static struct ao2_container *cli_unid_get_container(const char *regex)
+{
+ struct ao2_container *s_container;
+
+ s_container = ao2_container_alloc_list(AO2_ALLOC_OPT_LOCK_NOLOCK, 0,
+ suspects_sort, suspects_compare);
+ if (!s_container) {
+ return NULL;
+ }
+
+ if (ao2_container_dup(s_container, unidentified_requests, 0)) {
+ ao2_ref(s_container, -1);
+ return NULL;
+ }
+
+ return s_container;
+}
+
+static int cli_unid_iterate(void *container, ao2_callback_fn callback, void *args)
+{
+ ao2_callback(container, 0, callback, args);
+
+ return 0;
+}
+
+static void *cli_unid_retrieve_by_id(const char *id)
+{
+ return ao2_find(unidentified_requests, id, OBJ_SEARCH_KEY);
+}
+
+static const char *cli_unid_get_id(const void *obj)
+{
+ const struct unidentified_request *unid = obj;
+
+ return unid->src_name;
+}
+
+static int cli_unid_print_header(void *obj, void *arg, int flags)
+{
+ struct ast_sip_cli_context *context = arg;
+ RAII_VAR(struct ast_sip_cli_formatter_entry *, formatter_entry, NULL, ao2_cleanup);
+
+ int indent = CLI_INDENT_TO_SPACES(context->indent_level);
+ int filler = CLI_LAST_TABSTOP - indent - 7;
+
+ ast_assert(context->output_buffer != NULL);
+
+ ast_str_append(&context->output_buffer, 0,
+ "%*s: <IP Address%*.*s> <Count> <Age(sec)>\n",
+ indent, "Request", filler, filler, CLI_HEADER_FILLER);
+
+ return 0;
+}
+static int cli_unid_print_body(void *obj, void *arg, int flags)
+{
+ struct unidentified_request *unid = obj;
+ struct ast_sip_cli_context *context = arg;
+ int indent;
+ int flexwidth;
+ int64_t ms = ast_tvdiff_ms(ast_tvnow(), unid->first_seen);
+
+ ast_assert(context->output_buffer != NULL);
+
+ indent = CLI_INDENT_TO_SPACES(context->indent_level);
+ flexwidth = CLI_LAST_TABSTOP - 4;
+
+ ast_str_append(&context->output_buffer, 0, "%*s: %-*.*s %7d %10.3f\n",
+ indent,
+ "Request",
+ flexwidth, flexwidth,
+ unid->src_name, unid->count, ms / 1000.0);
+
+ return 0;
+}
+
+static struct ast_cli_entry cli_commands[] = {
+ AST_CLI_DEFINE(ast_sip_cli_traverse_objects, "Show PJSIP Unidentified Requests",
+ .command = "pjsip show unidentified_requests",
+ .usage = "Usage: pjsip show unidentified_requests\n"
+ " Show the PJSIP Unidentified Requests\n"),
+};
+
+struct ast_sip_cli_formatter_entry *unid_formatter;
+
+static int expire_requests(void *object, void *arg, int flags)
+{
+ struct unidentified_request *unid = object;
+ int *maxage = arg;
+ int64_t ms = ast_tvdiff_ms(ast_tvnow(), unid->first_seen);
+
+ if (ms > (*maxage) * 2 * 1000) {
+ return CMP_MATCH;
+ }
+
+ return 0;
+}
+
+static int prune_task(const void *data)
+{
+ unsigned int maxage;
+
+ ast_sip_get_unidentified_request_thresholds(&unidentified_count, &unidentified_period, &unidentified_prune_interval);
+ maxage = unidentified_period * 2;
+ ao2_callback(unidentified_requests, OBJ_MULTIPLE | OBJ_NODATA | OBJ_UNLINK, expire_requests, &maxage);
+
+ return unidentified_prune_interval * 1000;
+}
+
+static int clean_task(const void *data)
+{
+ return 0;
+}
+
+static void global_loaded(const char *object_type)
+{
+ char *identifier_order = ast_sip_get_endpoint_identifier_order();
+ char *io_copy = ast_strdupa(identifier_order);
+ char *identify_method;
+
+ ast_free(identifier_order);
+ using_auth_username = 0;
+ while ((identify_method = ast_strip(strsep(&io_copy, ",")))) {
+ if (!strcmp(identify_method, "auth_username")) {
+ using_auth_username = 1;
+ break;
+ }
+ }
+
+ ast_sip_get_default_realm(default_realm, sizeof(default_realm));
+ ast_sip_get_unidentified_request_thresholds(&unidentified_count, &unidentified_period, &unidentified_prune_interval);
+
+ /* Clean out the old task, if any */
+ ast_sched_clean_by_callback(prune_context, prune_task, clean_task);
+ if (ast_sched_add_variable(prune_context, unidentified_prune_interval * 1000, prune_task, NULL, 1) < 0) {
+ return;
+ }
+}
+
+/*! \brief Observer which is used to update our interval and default_realm when the global setting changes */
+static struct ast_sorcery_observer global_observer = {
+ .loaded = global_loaded,
+};
+
+
int ast_sip_initialize_distributor(void)
{
+ unidentified_requests = ao2_container_alloc_hash(AO2_ALLOC_OPT_LOCK_RWLOCK, 0,
+ DEFAULT_SUSPECTS_BUCKETS, suspects_hash, NULL, suspects_compare);
+ if (!unidentified_requests) {
+ return -1;
+ }
+
+ prune_context = ast_sched_context_create();
+ if (!prune_context) {
+ ast_sip_destroy_distributor();
+ return -1;
+ }
+
+ if (ast_sched_start_thread(prune_context)) {
+ ast_sip_destroy_distributor();
+ return -1;
+ }
+
+ ast_sorcery_observer_add(ast_sip_get_sorcery(), "global", &global_observer);
+ ast_sorcery_reload_object(ast_sip_get_sorcery(), "global");
+
if (create_artificial_endpoint() || create_artificial_auth()) {
+ ast_sip_destroy_distributor();
return -1;
}
if (internal_sip_register_service(&distributor_mod)) {
+ ast_sip_destroy_distributor();
return -1;
}
if (internal_sip_register_service(&endpoint_mod)) {
+ ast_sip_destroy_distributor();
return -1;
}
if (internal_sip_register_service(&auth_mod)) {
+ ast_sip_destroy_distributor();
return -1;
}
+ unid_formatter = ao2_alloc(sizeof(struct ast_sip_cli_formatter_entry), NULL);
+ if (!unid_formatter) {
+ ast_log(LOG_ERROR, "Unable to allocate memory for unid_formatter\n");
+ return -1;
+ }
+ unid_formatter->name = "unidentified_request";
+ unid_formatter->print_header = cli_unid_print_header;
+ unid_formatter->print_body = cli_unid_print_body;
+ unid_formatter->get_container = cli_unid_get_container;
+ unid_formatter->iterate = cli_unid_iterate;
+ unid_formatter->get_id = cli_unid_get_id;
+ unid_formatter->retrieve_by_id = cli_unid_retrieve_by_id;
+ ast_sip_register_cli_formatter(unid_formatter);
+ ast_cli_register_multiple(cli_commands, ARRAY_LEN(cli_commands));
+
return 0;
}
void ast_sip_destroy_distributor(void)
{
+ ast_cli_unregister_multiple(cli_commands, ARRAY_LEN(cli_commands));
+ ast_sip_unregister_cli_formatter(unid_formatter);
+
internal_sip_unregister_service(&distributor_mod);
internal_sip_unregister_service(&endpoint_mod);
internal_sip_unregister_service(&auth_mod);
ao2_cleanup(artificial_auth);
ao2_cleanup(artificial_endpoint);
+ ao2_cleanup(unidentified_requests);
+
+ ast_sorcery_observer_remove(ast_sip_get_sorcery(), "global", &global_observer);
+
+ if (prune_context) {
+ ast_sched_context_destroy(prune_context);
+ }
}
diff --git a/res/res_pjsip/pjsip_options.c b/res/res_pjsip/pjsip_options.c
index f7233a89c..b9339f60b 100644
--- a/res/res_pjsip/pjsip_options.c
+++ b/res/res_pjsip/pjsip_options.c
@@ -42,6 +42,7 @@ static const char *status_map [] = {
[UNKNOWN] = "Unknown",
[CREATED] = "Created",
[REMOVED] = "Removed",
+ [UPDATED] = "Updated",
};
static const char *short_status_map [] = {
@@ -50,6 +51,7 @@ static const char *short_status_map [] = {
[UNKNOWN] = "Unknown",
[CREATED] = "Created",
[REMOVED] = "Removed",
+ [UPDATED] = "Updated",
};
const char *ast_sip_get_contact_status_label(const enum ast_sip_contact_status_type status)
@@ -543,6 +545,16 @@ static void contact_created(const void *obj)
/*!
* \internal
+ * \brief A contact has been updated.
+ */
+static void contact_updated(const void *obj)
+{
+ update_contact_status((struct ast_sip_contact *) obj, UPDATED);
+ qualify_and_schedule((struct ast_sip_contact *) obj);
+}
+
+/*!
+ * \internal
* \brief A contact has been deleted remove status tracking.
*/
static void contact_deleted(const void *obj)
@@ -567,7 +579,8 @@ static void contact_deleted(const void *obj)
static const struct ast_sorcery_observer contact_observer = {
.created = contact_created,
- .deleted = contact_deleted
+ .deleted = contact_deleted,
+ .updated = contact_updated
};
static pj_bool_t options_start(void)
diff --git a/res/res_pjsip/pjsip_scheduler.c b/res/res_pjsip/pjsip_scheduler.c
new file mode 100644
index 000000000..a5d406cb5
--- /dev/null
+++ b/res/res_pjsip/pjsip_scheduler.c
@@ -0,0 +1,495 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2016, Fairview 5 Engineering, LLC
+ *
+ * George Joseph <george.joseph@fairview5.com>
+ *
+ * See http://www.asterisk.org for more information about
+ * the Asterisk project. Please do not directly contact
+ * any of the maintainers of this project for assistance;
+ * the project provides a web site, mailing lists and IRC
+ * channels for your use.
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE file
+ * at the top of the source tree.
+ */
+
+/*! \file
+ *
+ * \brief res_pjsip Scheduler
+ *
+ * \author George Joseph <george.joseph@fairview5.com>
+ */
+
+#include "asterisk.h"
+
+ASTERISK_REGISTER_FILE()
+
+#include "asterisk/res_pjsip.h"
+#include "include/res_pjsip_private.h"
+#include "asterisk/res_pjsip_cli.h"
+
+#define TASK_BUCKETS 53
+
+static struct ast_sched_context *scheduler_context;
+static struct ao2_container *tasks;
+static int task_count;
+
+struct ast_sip_sched_task {
+ /*! ast_sip_sched task id */
+ uint32_t task_id;
+ /*! ast_sched scheudler id */
+ int current_scheduler_id;
+ /*! task is currently running */
+ int is_running;
+ /*! task */
+ ast_sip_task task;
+ /*! task data */
+ void *task_data;
+ /*! reschedule interval in milliseconds */
+ int interval;
+ /*! the time the task was queued */
+ struct timeval when_queued;
+ /*! the last time the task was started */
+ struct timeval last_start;
+ /*! the last time the task was ended */
+ struct timeval last_end;
+ /*! times run */
+ int run_count;
+ /*! the task reschedule, cleanup and policy flags */
+ enum ast_sip_scheduler_task_flags flags;
+ /*! the serializer to be used (if any) */
+ struct ast_taskprocessor *serializer;
+ /* A name to be associated with the task */
+ char name[0];
+};
+
+AO2_STRING_FIELD_HASH_FN(ast_sip_sched_task, name);
+AO2_STRING_FIELD_CMP_FN(ast_sip_sched_task, name);
+AO2_STRING_FIELD_SORT_FN(ast_sip_sched_task, name);
+
+static int push_to_serializer(const void *data);
+
+/*
+ * This function is run in the context of the serializer.
+ * It runs the task with a simple call and reschedules based on the result.
+ */
+static int run_task(void *data)
+{
+ RAII_VAR(struct ast_sip_sched_task *, schtd, ao2_bump(data), ao2_cleanup);
+ int res;
+ int delay;
+
+ ao2_lock(schtd);
+ schtd->last_start = ast_tvnow();
+ schtd->is_running = 1;
+ schtd->run_count++;
+ ao2_unlock(schtd);
+
+ res = schtd->task(schtd->task_data);
+
+ ao2_lock(schtd);
+ schtd->is_running = 0;
+ schtd->last_end = ast_tvnow();
+
+ /*
+ * Don't restart if the task returned 0 or if the interval
+ * was set to 0 while the task was running
+ */
+ if (!res || !schtd->interval) {
+ schtd->interval = 0;
+ ao2_unlock(schtd);
+ ao2_unlink(tasks, schtd);
+ return -1;
+ }
+
+ if (schtd->flags & AST_SIP_SCHED_TASK_VARIABLE) {
+ schtd->interval = res;
+ }
+
+ if (schtd->flags & AST_SIP_SCHED_TASK_DELAY) {
+ delay = schtd->interval;
+ } else {
+ delay = schtd->interval - (ast_tvdiff_ms(schtd->last_end, schtd->last_start) % schtd->interval);
+ }
+
+ schtd->current_scheduler_id = ast_sched_add(scheduler_context, delay, push_to_serializer, (const void *)schtd);
+ if (schtd->current_scheduler_id < 0) {
+ schtd->interval = 0;
+ ao2_unlock(schtd);
+ ao2_unlink(tasks, schtd);
+ return -1;
+ }
+
+ ao2_unlock(schtd);
+
+ return 0;
+}
+
+/*
+ * This function is run by the scheduler thread. Its only job is to push the task
+ * to the serialize and return. It returns 0 so it's not rescheduled.
+ */
+static int push_to_serializer(const void *data)
+{
+ struct ast_sip_sched_task *schtd = (struct ast_sip_sched_task *)data;
+
+ if (ast_sip_push_task(schtd->serializer, run_task, schtd)) {
+ ao2_ref(schtd, -1);
+ }
+
+ return 0;
+}
+
+int ast_sip_sched_task_cancel(struct ast_sip_sched_task *schtd)
+{
+ int res;
+
+ if (!ao2_ref_and_lock(schtd)) {
+ return -1;
+ }
+
+ if (schtd->current_scheduler_id < 0 || schtd->interval <= 0) {
+ ao2_unlock_and_unref(schtd);
+ return 0;
+ }
+
+ schtd->interval = 0;
+ ao2_unlock_and_unref(schtd);
+ ao2_unlink(tasks, schtd);
+ res = ast_sched_del(scheduler_context, schtd->current_scheduler_id);
+
+ return res;
+}
+
+int ast_sip_sched_task_cancel_by_name(const char *name)
+{
+ RAII_VAR(struct ast_sip_sched_task *, schtd, NULL, ao2_cleanup);
+
+ if (ast_strlen_zero(name)) {
+ return -1;
+ }
+
+ schtd = ao2_find(tasks, name, OBJ_SEARCH_KEY | OBJ_NOLOCK);
+ if (!schtd) {
+ return -1;
+ }
+
+ return ast_sip_sched_task_cancel(schtd);
+}
+
+
+int ast_sip_sched_task_get_times(struct ast_sip_sched_task *schtd,
+ struct timeval *queued, struct timeval *last_start, struct timeval *last_end)
+{
+ if (!ao2_ref_and_lock(schtd)) {
+ return -1;
+ }
+
+ if (queued) {
+ memcpy(queued, &schtd->when_queued, sizeof(struct timeval));
+ }
+ if (last_start) {
+ memcpy(last_start, &schtd->last_start, sizeof(struct timeval));
+ }
+ if (last_end) {
+ memcpy(last_end, &schtd->last_end, sizeof(struct timeval));
+ }
+
+ ao2_unlock_and_unref(schtd);
+
+ return 0;
+}
+
+int ast_sip_sched_task_get_times_by_name(const char *name,
+ struct timeval *queued, struct timeval *last_start, struct timeval *last_end)
+{
+ RAII_VAR(struct ast_sip_sched_task *, schtd, NULL, ao2_cleanup);
+
+ if (ast_strlen_zero(name)) {
+ return -1;
+ }
+
+ schtd = ao2_find(tasks, name, OBJ_SEARCH_KEY | OBJ_NOLOCK);
+ if (!schtd) {
+ return -1;
+ }
+
+ return ast_sip_sched_task_get_times(schtd, queued, last_start, last_end);
+}
+
+int ast_sip_sched_task_get_name(struct ast_sip_sched_task *schtd, char *name, size_t maxlen)
+{
+ if (maxlen <= 0) {
+ return -1;
+ }
+
+ if (!ao2_ref_and_lock(schtd)) {
+ return -1;
+ }
+
+ ast_copy_string(name, schtd->name, maxlen);
+
+ ao2_unlock_and_unref(schtd);
+
+ return 0;
+}
+
+int ast_sip_sched_task_get_next_run(struct ast_sip_sched_task *schtd)
+{
+ int delay;
+ struct timeval since_when;
+ struct timeval now;
+
+ if (!ao2_ref_and_lock(schtd)) {
+ return -1;
+ }
+
+ if (schtd->interval) {
+ delay = schtd->interval;
+ now = ast_tvnow();
+
+ if (schtd->flags & AST_SIP_SCHED_TASK_DELAY) {
+ since_when = schtd->is_running ? now : schtd->last_end;
+ } else {
+ since_when = schtd->last_start.tv_sec ? schtd->last_start : schtd->when_queued;
+ }
+
+ delay -= ast_tvdiff_ms(now, since_when);
+
+ delay = delay < 0 ? 0 : delay;
+ } else {
+ delay = -1;
+ }
+
+ ao2_unlock_and_unref(schtd);
+
+ return delay;
+}
+
+int ast_sip_sched_task_get_next_run_by_name(const char *name)
+{
+ RAII_VAR(struct ast_sip_sched_task *, schtd, NULL, ao2_cleanup);
+
+ if (ast_strlen_zero(name)) {
+ return -1;
+ }
+
+ schtd = ao2_find(tasks, name, OBJ_SEARCH_KEY | OBJ_NOLOCK);
+ if (!schtd) {
+ return -1;
+ }
+
+ return ast_sip_sched_task_get_next_run(schtd);
+}
+
+int ast_sip_sched_is_task_running(struct ast_sip_sched_task *schtd)
+{
+ if (!schtd) {
+ return 0;
+ }
+
+ return schtd->is_running;
+}
+
+int ast_sip_sched_is_task_running_by_name(const char *name)
+{
+ RAII_VAR(struct ast_sip_sched_task *, schtd, NULL, ao2_cleanup);
+
+ if (ast_strlen_zero(name)) {
+ return 0;
+ }
+
+ schtd = ao2_find(tasks, name, OBJ_SEARCH_KEY | OBJ_NOLOCK);
+ if (!schtd) {
+ return 0;
+ }
+
+ return schtd->is_running;
+}
+
+static void schtd_destructor(void *data)
+{
+ struct ast_sip_sched_task *schtd = data;
+
+ if (schtd->flags & AST_SIP_SCHED_TASK_DATA_AO2) {
+ /* release our own ref, then release the callers if asked to do so */
+ ao2_ref(schtd->task_data, (schtd->flags & AST_SIP_SCHED_TASK_DATA_FREE) ? -2 : -1);
+ } else if (schtd->task_data && (schtd->flags & AST_SIP_SCHED_TASK_DATA_FREE)) {
+ ast_free(schtd->task_data);
+ }
+}
+
+struct ast_sip_sched_task *ast_sip_schedule_task(struct ast_taskprocessor *serializer,
+ int interval, ast_sip_task sip_task, char *name, void *task_data, enum ast_sip_scheduler_task_flags flags)
+{
+#define ID_LEN 13 /* task_deadbeef */
+ struct ast_sip_sched_task *schtd;
+ int res;
+
+ if (interval < 0) {
+ return NULL;
+ }
+
+ schtd = ao2_alloc((sizeof(*schtd) + (!ast_strlen_zero(name) ? strlen(name) : ID_LEN) + 1), schtd_destructor);
+ if (!schtd) {
+ return NULL;
+ }
+
+ schtd->task_id = ast_atomic_fetchadd_int(&task_count, 1);
+ schtd->serializer = serializer;
+ schtd->task = sip_task;
+ if (!ast_strlen_zero(name)) {
+ strcpy(schtd->name, name); /* Safe */
+ } else {
+ sprintf(schtd->name, "task_%08x", schtd->task_id);
+ }
+ schtd->task_data = task_data;
+ schtd->flags = flags;
+ schtd->interval = interval;
+ schtd->when_queued = ast_tvnow();
+
+ if (flags & AST_SIP_SCHED_TASK_DATA_AO2) {
+ ao2_ref(task_data, +1);
+ }
+ res = ast_sched_add(scheduler_context, interval, push_to_serializer, (const void *)schtd);
+ if (res < 0) {
+ ao2_ref(schtd, -1);
+ return NULL;
+ } else {
+ schtd->current_scheduler_id = res;
+ ao2_link(tasks, schtd);
+ }
+
+ return schtd;
+#undef ID_LEN
+}
+
+static char *cli_show_tasks(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+ struct ao2_iterator i;
+ struct ast_sip_sched_task *schtd;
+ const char *log_format = ast_logger_get_dateformat();
+ struct ast_tm tm;
+ char queued[32];
+ char last_start[32];
+ char last_end[32];
+ int datelen;
+ struct timeval now = ast_tvnow();
+ const char *separator = "======================================";
+
+ switch (cmd) {
+ case CLI_INIT:
+ e->command = "pjsip show scheduled_tasks";
+ e->usage = "Usage: pjsip show scheduled_tasks\n"
+ " Show all scheduled tasks\n";
+ return NULL;
+ case CLI_GENERATE:
+ return NULL;
+ }
+
+ if (a->argc != 3) {
+ return CLI_SHOWUSAGE;
+ }
+
+ ast_localtime(&now, &tm, NULL);
+ datelen = ast_strftime(queued, sizeof(queued), log_format, &tm);
+
+ ast_cli(a->fd, "PJSIP Scheduled Tasks:\n\n");
+
+ ast_cli(a->fd, " %1$-24s %2$-8s %3$-9s %4$-7s %6$-*5$s %7$-*5$s %8$-*5$s\n",
+ "Task Name", "Interval", "Times Run", "State",
+ datelen, "Queued", "Last Started", "Last Ended");
+
+ ast_cli(a->fd, " %1$-24.24s %2$-8.8s %3$-9.9s %4$-7.7s %6$-*5$.*5$s %7$-*5$.*5$s %8$-*5$.*5$s\n",
+ separator, separator, separator, separator,
+ datelen, separator, separator, separator);
+
+
+ ao2_ref(tasks, +1);
+ ao2_rdlock(tasks);
+ i = ao2_iterator_init(tasks, 0);
+ while ((schtd = ao2_iterator_next(&i))) {
+
+ ast_localtime(&schtd->when_queued, &tm, NULL);
+ ast_strftime(queued, sizeof(queued), log_format, &tm);
+
+ if (ast_tvzero(schtd->last_start)) {
+ strcpy(last_start, "not yet started");
+ } else {
+ ast_localtime(&schtd->last_start, &tm, NULL);
+ ast_strftime(last_start, sizeof(last_start), log_format, &tm);
+ }
+
+ if (ast_tvzero(schtd->last_end)) {
+ if (ast_tvzero(schtd->last_start)) {
+ strcpy(last_end, "not yet started");
+ } else {
+ strcpy(last_end, "running");
+ }
+ } else {
+ ast_localtime(&schtd->last_end, &tm, NULL);
+ ast_strftime(last_end, sizeof(last_end), log_format, &tm);
+ }
+
+ ast_cli(a->fd, " %1$-24.24s %2$-8.3f %3$-9d %4$-7s %6$-*5$s %7$-*5$s %8$-*5$s\n",
+ schtd->name,
+ schtd->interval / 1000.0,
+ schtd->run_count,
+ schtd->is_running ? "running" : "waiting",
+ datelen, queued, last_start, last_end);
+ ao2_cleanup(schtd);
+ }
+ ao2_iterator_destroy(&i);
+ ao2_unlock(tasks);
+ ao2_ref(tasks, -1);
+ ast_cli(a->fd, "\n");
+
+ return CLI_SUCCESS;
+}
+
+static struct ast_cli_entry cli_commands[] = {
+ AST_CLI_DEFINE(cli_show_tasks, "Show all scheduled tasks"),
+};
+
+int ast_sip_initialize_scheduler(void)
+{
+ if (!(scheduler_context = ast_sched_context_create())) {
+ ast_log(LOG_ERROR, "Failed to create scheduler. Aborting load\n");
+ return -1;
+ }
+
+ if (ast_sched_start_thread(scheduler_context)) {
+ ast_log(LOG_ERROR, "Failed to start scheduler. Aborting load\n");
+ ast_sched_context_destroy(scheduler_context);
+ return -1;
+ }
+
+ tasks = ao2_container_alloc_hash(AO2_ALLOC_OPT_LOCK_RWLOCK, AO2_CONTAINER_ALLOC_OPT_DUPS_REJECT,
+ TASK_BUCKETS, ast_sip_sched_task_hash_fn, ast_sip_sched_task_sort_fn, ast_sip_sched_task_cmp_fn);
+ if (!tasks) {
+ ast_log(LOG_ERROR, "Failed to allocate task container. Aborting load\n");
+ ast_sched_context_destroy(scheduler_context);
+ return -1;
+ }
+
+ ast_cli_register_multiple(cli_commands, ARRAY_LEN(cli_commands));
+
+ return 0;
+}
+
+int ast_sip_destroy_scheduler(void)
+{
+ ast_cli_unregister_multiple(cli_commands, ARRAY_LEN(cli_commands));
+
+ if (scheduler_context) {
+ ast_sched_context_destroy(scheduler_context);
+ }
+
+ ao2_cleanup(tasks);
+ tasks = NULL;
+
+ return 0;
+}
diff --git a/res/res_pjsip_authenticator_digest.c b/res/res_pjsip_authenticator_digest.c
index 8a781254c..59e9738fd 100644
--- a/res/res_pjsip_authenticator_digest.c
+++ b/res/res_pjsip_authenticator_digest.c
@@ -31,6 +31,10 @@
<support_level>core</support_level>
***/
+/* From the auth/realm realtime column size */
+#define MAX_REALM_LENGTH 40
+static char default_realm[MAX_REALM_LENGTH + 1];
+
AO2_GLOBAL_OBJ_STATIC(entity_id);
/*!
@@ -202,9 +206,12 @@ static int build_nonce(struct ast_str **nonce, const char *timestamp, const pjsi
RAII_VAR(char *, eid, ao2_global_obj_ref(entity_id), ao2_cleanup);
char hash[33];
+ /*
+ * Note you may be tempted to think why not include the port. The reason
+ * is that when using TCP the port can potentially differ from before.
+ */
ast_str_append(&str, 0, "%s", timestamp);
ast_str_append(&str, 0, ":%s", rdata->pkt_info.src_name);
- ast_str_append(&str, 0, ":%d", rdata->pkt_info.src_port);
ast_str_append(&str, 0, ":%s", eid);
ast_str_append(&str, 0, ":%s", realm);
ast_md5_hash(hash, ast_str_buffer(str));
@@ -409,7 +416,7 @@ static enum ast_sip_check_auth_result digest_check_auth(struct ast_sip_endpoint
for (i = 0; i < auth_size; ++i) {
if (ast_strlen_zero(auths[i]->realm)) {
- ast_string_field_set(auths[i], realm, "asterisk");
+ ast_string_field_set(auths[i], realm, default_realm);
}
verify_res[i] = verify(auths[i], rdata, tdata->pool);
if (verify_res[i] == AUTH_SUCCESS) {
@@ -456,6 +463,16 @@ static int build_entity_id(void)
return 0;
}
+static void global_loaded(const char *object_type)
+{
+ ast_sip_get_default_realm(default_realm, sizeof(default_realm));
+}
+
+/*! \brief Observer which is used to update our default_realm when the global setting changes */
+static struct ast_sorcery_observer global_observer = {
+ .loaded = global_loaded,
+};
+
static int reload_module(void)
{
if (build_entity_id()) {
@@ -471,6 +488,10 @@ static int load_module(void)
if (build_entity_id()) {
return AST_MODULE_LOAD_DECLINE;
}
+
+ ast_sorcery_observer_add(ast_sip_get_sorcery(), "global", &global_observer);
+ ast_sorcery_reload_object(ast_sip_get_sorcery(), "global");
+
if (ast_sip_register_authenticator(&digest_authenticator)) {
ao2_global_obj_release(entity_id);
return AST_MODULE_LOAD_DECLINE;
@@ -480,6 +501,7 @@ static int load_module(void)
static int unload_module(void)
{
+ ast_sorcery_observer_remove(ast_sip_get_sorcery(), "global", &global_observer);
ast_sip_unregister_authenticator(&digest_authenticator);
ao2_global_obj_release(entity_id);
return 0;
@@ -490,5 +512,5 @@ AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_LOAD_ORDER, "PJSIP authentication
.load = load_module,
.unload = unload_module,
.reload = reload_module,
- .load_pri = AST_MODPRI_CHANNEL_DEPEND,
+ .load_pri = AST_MODPRI_CHANNEL_DEPEND - 5,
);
diff --git a/res/res_pjsip_caller_id.c b/res/res_pjsip_caller_id.c
index 9af2a8a64..efa1b89a8 100644
--- a/res/res_pjsip_caller_id.c
+++ b/res/res_pjsip_caller_id.c
@@ -424,6 +424,12 @@ static pjsip_fromto_hdr *create_new_id_hdr(const pj_str_t *hdr_name, pjsip_fromt
ast_escape_quoted(id->name.str, name_buf, name_buf_len);
pj_strdup2(tdata->pool, &id_name_addr->display, name_buf);
+ } else {
+ /*
+ * We need to clear the remnants of the clone or it'll be left set.
+ * pj_strdup2 is safe to call with a NULL src and it resets both slen and ptr.
+ */
+ pj_strdup2(tdata->pool, &id_name_addr->display, NULL);
}
pj_strdup2(tdata->pool, &id_uri->user, id->number.str);
diff --git a/res/res_pjsip_endpoint_identifier_anonymous.c b/res/res_pjsip_endpoint_identifier_anonymous.c
index b39c8632c..b93133824 100644
--- a/res/res_pjsip_endpoint_identifier_anonymous.c
+++ b/res/res_pjsip_endpoint_identifier_anonymous.c
@@ -69,28 +69,30 @@ static struct ast_sip_endpoint *anonymous_identify(pjsip_rx_data *rdata)
return NULL;
}
- /* Attempt to find the endpoint given the name and domain provided */
- snprintf(id, sizeof(id), "anonymous@%s", domain_name);
- if ((endpoint = ast_sorcery_retrieve_by_id(ast_sip_get_sorcery(), "endpoint", id))) {
- goto done;
- }
-
- /* See if an alias exists for the domain provided */
- if ((alias = ast_sorcery_retrieve_by_id(ast_sip_get_sorcery(), "domain_alias", domain_name))) {
- snprintf(id, sizeof(id), "anonymous@%s", alias->domain);
+ if (!ast_sip_get_disable_multi_domain()) {
+ /* Attempt to find the endpoint given the name and domain provided */
+ snprintf(id, sizeof(id), "anonymous@%s", domain_name);
if ((endpoint = ast_sorcery_retrieve_by_id(ast_sip_get_sorcery(), "endpoint", id))) {
goto done;
}
- }
- /* See if the transport this came in on has a provided domain */
- if ((transport_states = ast_sip_get_transport_states())
- && (transport_state = ao2_callback(transport_states, 0, find_transport_state_in_use, rdata))
- && (transport = ast_sorcery_retrieve_by_id(ast_sip_get_sorcery(), "transport", transport_state->id))
- && !ast_strlen_zero(transport->domain)) {
- snprintf(id, sizeof(id), "anonymous@%s", transport->domain);
- if ((endpoint = ast_sorcery_retrieve_by_id(ast_sip_get_sorcery(), "endpoint", id))) {
- goto done;
+ /* See if an alias exists for the domain provided */
+ if ((alias = ast_sorcery_retrieve_by_id(ast_sip_get_sorcery(), "domain_alias", domain_name))) {
+ snprintf(id, sizeof(id), "anonymous@%s", alias->domain);
+ if ((endpoint = ast_sorcery_retrieve_by_id(ast_sip_get_sorcery(), "endpoint", id))) {
+ goto done;
+ }
+ }
+
+ /* See if the transport this came in on has a provided domain */
+ if ((transport_states = ast_sip_get_transport_states())
+ && (transport_state = ao2_callback(transport_states, 0, find_transport_state_in_use, rdata))
+ && (transport = ast_sorcery_retrieve_by_id(ast_sip_get_sorcery(), "transport", transport_state->id))
+ && !ast_strlen_zero(transport->domain)) {
+ snprintf(id, sizeof(id), "anonymous@%s", transport->domain);
+ if ((endpoint = ast_sorcery_retrieve_by_id(ast_sip_get_sorcery(), "endpoint", id))) {
+ goto done;
+ }
}
}
diff --git a/res/res_pjsip_endpoint_identifier_ip.c b/res/res_pjsip_endpoint_identifier_ip.c
index 7f4858af0..b1ffd2cc3 100644
--- a/res/res_pjsip_endpoint_identifier_ip.c
+++ b/res/res_pjsip_endpoint_identifier_ip.c
@@ -515,5 +515,5 @@ AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_LOAD_ORDER, "PJSIP IP endpoint ide
.load = load_module,
.reload = reload_module,
.unload = unload_module,
- .load_pri = AST_MODPRI_APP_DEPEND,
+ .load_pri = AST_MODPRI_CHANNEL_DEPEND - 4,
);
diff --git a/res/res_pjsip_endpoint_identifier_user.c b/res/res_pjsip_endpoint_identifier_user.c
index aa6d398cf..e018abd61 100644
--- a/res/res_pjsip_endpoint_identifier_user.c
+++ b/res/res_pjsip_endpoint_identifier_user.c
@@ -29,7 +29,7 @@
#include "asterisk/res_pjsip.h"
#include "asterisk/module.h"
-static int get_endpoint_details(pjsip_rx_data *rdata, char *endpoint, size_t endpoint_size, char *domain, size_t domain_size)
+static int get_from_header(pjsip_rx_data *rdata, char *username, size_t username_size, char *domain, size_t domain_size)
{
pjsip_uri *from = rdata->msg_info.from->uri;
pjsip_sip_uri *sip_from;
@@ -37,11 +37,28 @@ static int get_endpoint_details(pjsip_rx_data *rdata, char *endpoint, size_t end
return -1;
}
sip_from = (pjsip_sip_uri *) pjsip_uri_get_uri(from);
- ast_copy_pj_str(endpoint, &sip_from->user, endpoint_size);
+ ast_copy_pj_str(username, &sip_from->user, username_size);
ast_copy_pj_str(domain, &sip_from->host, domain_size);
return 0;
}
+static pjsip_authorization_hdr *get_auth_header(pjsip_rx_data *rdata, char *username,
+ size_t username_size, char *realm, size_t realm_size, pjsip_authorization_hdr *start)
+{
+ pjsip_authorization_hdr *header;
+
+ header = pjsip_msg_find_hdr(rdata->msg_info.msg, PJSIP_H_AUTHORIZATION, start);
+
+ if (!header || pj_stricmp2(&header->scheme, "digest")) {
+ return NULL;
+ }
+
+ ast_copy_pj_str(username, &header->credential.digest.username, username_size);
+ ast_copy_pj_str(realm, &header->credential.digest.realm, realm_size);
+
+ return header;
+}
+
static int find_transport_state_in_use(void *obj, void *arg, int flags)
{
struct ast_sip_transport_state *transport_state = obj;
@@ -56,74 +73,126 @@ static int find_transport_state_in_use(void *obj, void *arg, int flags)
return 0;
}
-static struct ast_sip_endpoint *username_identify(pjsip_rx_data *rdata)
+static struct ast_sip_endpoint *find_endpoint(pjsip_rx_data *rdata, char *endpoint_name,
+ char *domain_name)
{
- char endpoint_name[64], domain_name[64], id[AST_UUID_STR_LEN];
+ char id[AST_UUID_STR_LEN];
struct ast_sip_endpoint *endpoint;
RAII_VAR(struct ast_sip_domain_alias *, alias, NULL, ao2_cleanup);
RAII_VAR(struct ao2_container *, transport_states, NULL, ao2_cleanup);
RAII_VAR(struct ast_sip_transport_state *, transport_state, NULL, ao2_cleanup);
RAII_VAR(struct ast_sip_transport *, transport, NULL, ao2_cleanup);
- if (get_endpoint_details(rdata, endpoint_name, sizeof(endpoint_name), domain_name, sizeof(domain_name))) {
- return NULL;
- }
-
- /* Attempt to find the endpoint given the name and domain provided */
- snprintf(id, sizeof(id), "%s@%s", endpoint_name, domain_name);
- if ((endpoint = ast_sorcery_retrieve_by_id(ast_sip_get_sorcery(), "endpoint", id))) {
- goto done;
- }
-
- /* See if an alias exists for the domain provided */
- if ((alias = ast_sorcery_retrieve_by_id(ast_sip_get_sorcery(), "domain_alias", domain_name))) {
- snprintf(id, sizeof(id), "%s@%s", endpoint_name, alias->domain);
+ if (!ast_sip_get_disable_multi_domain()) {
+ /* Attempt to find the endpoint given the name and domain provided */
+ snprintf(id, sizeof(id), "%s@%s", endpoint_name, domain_name);
if ((endpoint = ast_sorcery_retrieve_by_id(ast_sip_get_sorcery(), "endpoint", id))) {
- goto done;
+ return endpoint;
}
- }
- /* See if the transport this came in on has a provided domain */
- if ((transport_states = ast_sip_get_transport_states())
- && (transport_state = ao2_callback(transport_states, 0, find_transport_state_in_use, rdata))
- && (transport = ast_sorcery_retrieve_by_id(ast_sip_get_sorcery(), "transport", transport_state->id))
- && !ast_strlen_zero(transport->domain)) {
- snprintf(id, sizeof(id), "anonymous@%s", transport->domain);
- if ((endpoint = ast_sorcery_retrieve_by_id(ast_sip_get_sorcery(), "endpoint", id))) {
- goto done;
+ /* See if an alias exists for the domain provided */
+ if ((alias = ast_sorcery_retrieve_by_id(ast_sip_get_sorcery(), "domain_alias", domain_name))) {
+ snprintf(id, sizeof(id), "%s@%s", endpoint_name, alias->domain);
+ if ((endpoint = ast_sorcery_retrieve_by_id(ast_sip_get_sorcery(), "endpoint", id))) {
+ return endpoint;
+ }
+ }
+ /* See if the transport this came in on has a provided domain */
+ if ((transport_states = ast_sip_get_transport_states())
+ && (transport_state = ao2_callback(transport_states, 0, find_transport_state_in_use, rdata))
+ && (transport = ast_sorcery_retrieve_by_id(ast_sip_get_sorcery(), "transport", transport_state->id))
+ && !ast_strlen_zero(transport->domain)) {
+ snprintf(id, sizeof(id), "anonymous@%s", transport->domain);
+ if ((endpoint = ast_sorcery_retrieve_by_id(ast_sip_get_sorcery(), "endpoint", id))) {
+ return endpoint;
+ }
}
}
/* Fall back to no domain */
- endpoint = ast_sorcery_retrieve_by_id(ast_sip_get_sorcery(), "endpoint", endpoint_name);
+ return ast_sorcery_retrieve_by_id(ast_sip_get_sorcery(), "endpoint", endpoint_name);
+}
-done:
- if (endpoint) {
- if (!(endpoint->ident_method & AST_SIP_ENDPOINT_IDENTIFY_BY_USERNAME)) {
- ao2_ref(endpoint, -1);
- return NULL;
- }
- ast_debug(3, "Retrieved endpoint %s\n", ast_sorcery_object_get_id(endpoint));
- } else {
- ast_debug(3, "Could not identify endpoint by username '%s'\n", endpoint_name);
+static struct ast_sip_endpoint *username_identify(pjsip_rx_data *rdata)
+{
+ char username[64], domain[64];
+ struct ast_sip_endpoint *endpoint;
+
+ if (get_from_header(rdata, username, sizeof(username), domain, sizeof(domain))) {
+ return NULL;
+ }
+ ast_debug(3, "Attempting identify by From username '%s' domain '%s'\n", username, domain);
+
+ endpoint = find_endpoint(rdata, username, domain);
+ if (!endpoint) {
+ ast_debug(3, "Endpoint not found for From username '%s' domain '%s'\n", username, domain);
+ ao2_cleanup(endpoint);
+ return NULL;
}
+ if (!(endpoint->ident_method & AST_SIP_ENDPOINT_IDENTIFY_BY_USERNAME)) {
+ ast_debug(3, "Endpoint found for '%s' but 'username' method not supported'\n", username);
+ ao2_cleanup(endpoint);
+ return NULL;
+ }
+ ast_debug(3, "Identified by From username '%s' domain '%s'\n", username, domain);
+
return endpoint;
}
+static struct ast_sip_endpoint *auth_username_identify(pjsip_rx_data *rdata)
+{
+ char username[64], realm[64];
+ struct ast_sip_endpoint *endpoint;
+ pjsip_authorization_hdr *auth_header = NULL;
+
+ while ((auth_header = get_auth_header(rdata, username, sizeof(username), realm, sizeof(realm),
+ auth_header ? auth_header->next : NULL))) {
+ ast_debug(3, "Attempting identify by Authorization username '%s' realm '%s'\n", username,
+ realm);
+
+ endpoint = find_endpoint(rdata, username, realm);
+ if (!endpoint) {
+ ast_debug(3, "Endpoint not found for Authentication username '%s' realm '%s'\n",
+ username, realm);
+ ao2_cleanup(endpoint);
+ continue;
+ }
+ if (!(endpoint->ident_method & AST_SIP_ENDPOINT_IDENTIFY_BY_AUTH_USERNAME)) {
+ ast_debug(3, "Endpoint found for '%s' but 'auth_username' method not supported'\n",
+ username);
+ ao2_cleanup(endpoint);
+ continue;
+ }
+ ast_debug(3, "Identified by Authorization username '%s' realm '%s'\n", username, realm);
+
+ return endpoint;
+ }
+
+ return NULL;
+}
+
+
static struct ast_sip_endpoint_identifier username_identifier = {
.identify_endpoint = username_identify,
};
+static struct ast_sip_endpoint_identifier auth_username_identifier = {
+ .identify_endpoint = auth_username_identify,
+};
+
+
static int load_module(void)
{
CHECK_PJSIP_MODULE_LOADED();
ast_sip_register_endpoint_identifier_with_name(&username_identifier, "username");
+ ast_sip_register_endpoint_identifier_with_name(&auth_username_identifier, "auth_username");
return AST_MODULE_LOAD_SUCCESS;
}
static int unload_module(void)
{
+ ast_sip_unregister_endpoint_identifier(&auth_username_identifier);
ast_sip_unregister_endpoint_identifier(&username_identifier);
return 0;
}
@@ -132,5 +201,5 @@ AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_LOAD_ORDER, "PJSIP username endpoi
.support_level = AST_MODULE_SUPPORT_CORE,
.load = load_module,
.unload = unload_module,
- .load_pri = AST_MODPRI_APP_DEPEND,
+ .load_pri = AST_MODPRI_CHANNEL_DEPEND - 4,
);
diff --git a/res/res_pjsip_exten_state.c b/res/res_pjsip_exten_state.c
index 4e225dd1a..69a458993 100644
--- a/res/res_pjsip_exten_state.c
+++ b/res/res_pjsip_exten_state.c
@@ -517,8 +517,8 @@ static int unload_module(void)
}
AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_LOAD_ORDER, "PJSIP Extension State Notifications",
- .support_level = AST_MODULE_SUPPORT_CORE,
- .load = load_module,
- .unload = unload_module,
- .load_pri = AST_MODPRI_CHANNEL_DEPEND,
+ .support_level = AST_MODULE_SUPPORT_CORE,
+ .load = load_module,
+ .unload = unload_module,
+ .load_pri = AST_MODPRI_CHANNEL_DEPEND + 5,
);
diff --git a/res/res_pjsip_mwi.c b/res/res_pjsip_mwi.c
index 64d3c84ea..9eba335b5 100644
--- a/res/res_pjsip_mwi.c
+++ b/res/res_pjsip_mwi.c
@@ -1227,9 +1227,9 @@ static int unload_module(void)
}
AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_LOAD_ORDER, "PJSIP MWI resource",
- .support_level = AST_MODULE_SUPPORT_CORE,
- .load = load_module,
- .unload = unload_module,
- .reload = reload,
- .load_pri = AST_MODPRI_CHANNEL_DEPEND,
+ .support_level = AST_MODULE_SUPPORT_CORE,
+ .load = load_module,
+ .unload = unload_module,
+ .reload = reload,
+ .load_pri = AST_MODPRI_CHANNEL_DEPEND + 5,
);
diff --git a/res/res_pjsip_outbound_publish.c b/res/res_pjsip_outbound_publish.c
index 74b06c2ef..60f9bbb17 100644
--- a/res/res_pjsip_outbound_publish.c
+++ b/res/res_pjsip_outbound_publish.c
@@ -389,7 +389,7 @@ static void sip_outbound_publish_synchronize(struct ast_sip_event_publisher_hand
} else {
state->client->started = 1;
}
- } else if (state->client->started && !handler && removed && !strcmp(publish->event, removed->event_name)) {
+ } else if (!handler && removed && !strcmp(publish->event, removed->event_name)) {
/* If the publisher client has been started but it is going away stop it */
removed->stop_publishing(state->client);
state->client->started = 0;
diff --git a/res/res_pjsip_publish_asterisk.c b/res/res_pjsip_publish_asterisk.c
index c0d3b90cf..3218b0a0c 100644
--- a/res/res_pjsip_publish_asterisk.c
+++ b/res/res_pjsip_publish_asterisk.c
@@ -923,8 +923,8 @@ static int unload_module(void)
}
AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_LOAD_ORDER, "PJSIP Asterisk Event PUBLISH Support",
- .load = load_module,
- .reload = reload_module,
- .unload = unload_module,
- .load_pri = AST_MODPRI_CHANNEL_DEPEND,
+ .load = load_module,
+ .reload = reload_module,
+ .unload = unload_module,
+ .load_pri = AST_MODPRI_CHANNEL_DEPEND + 5,
);
diff --git a/res/res_pjsip_pubsub.c b/res/res_pjsip_pubsub.c
index 670047567..7ed804acf 100644
--- a/res/res_pjsip_pubsub.c
+++ b/res/res_pjsip_pubsub.c
@@ -2541,20 +2541,28 @@ void ast_sip_unregister_subscription_handler(struct ast_sip_subscription_handler
AST_RWLIST_TRAVERSE_SAFE_END;
}
-static struct ast_sip_pubsub_body_generator *find_body_generator_type_subtype(const char *content_type,
- const char *content_subtype)
+static struct ast_sip_pubsub_body_generator *find_body_generator_type_subtype_nolock(const char *type, const char *subtype)
{
- struct ast_sip_pubsub_body_generator *iter;
- SCOPED_LOCK(lock, &body_generators, AST_RWLIST_RDLOCK, AST_RWLIST_UNLOCK);
+ struct ast_sip_pubsub_body_generator *gen;
- AST_LIST_TRAVERSE(&body_generators, iter, list) {
- if (!strcmp(iter->type, content_type) &&
- !strcmp(iter->subtype, content_subtype)) {
+ AST_LIST_TRAVERSE(&body_generators, gen, list) {
+ if (!strcmp(gen->type, type)
+ && !strcmp(gen->subtype, subtype)) {
break;
}
- };
+ }
- return iter;
+ return gen;
+}
+
+static struct ast_sip_pubsub_body_generator *find_body_generator_type_subtype(const char *type, const char *subtype)
+{
+ struct ast_sip_pubsub_body_generator *gen;
+
+ AST_RWLIST_RDLOCK(&body_generators);
+ gen = find_body_generator_type_subtype_nolock(type, subtype);
+ AST_RWLIST_UNLOCK(&body_generators);
+ return gen;
}
static struct ast_sip_pubsub_body_generator *find_body_generator_accept(const char *accept)
@@ -3092,14 +3100,14 @@ int ast_sip_pubsub_register_body_generator(struct ast_sip_pubsub_body_generator
pj_str_t accept;
pj_size_t accept_len;
- existing = find_body_generator_type_subtype(generator->type, generator->subtype);
+ AST_RWLIST_WRLOCK(&body_generators);
+ existing = find_body_generator_type_subtype_nolock(generator->type, generator->subtype);
if (existing) {
- ast_log(LOG_WARNING, "Cannot register body generator of %s/%s."
- "One is already registered.\n", generator->type, generator->subtype);
+ AST_RWLIST_UNLOCK(&body_generators);
+ ast_log(LOG_WARNING, "A body generator for %s/%s is already registered.\n",
+ generator->type, generator->subtype);
return -1;
}
-
- AST_RWLIST_WRLOCK(&body_generators);
AST_LIST_INSERT_HEAD(&body_generators, generator, list);
AST_RWLIST_UNLOCK(&body_generators);
@@ -3179,14 +3187,15 @@ int ast_sip_pubsub_generate_body_content(const char *type, const char *subtype,
}
if (strcmp(data->body_type, generator->body_type)) {
- ast_log(LOG_WARNING, "Body generator does not accept the type of data provided\n");
+ ast_log(LOG_WARNING, "%s/%s body generator does not accept the type of data provided\n",
+ type, subtype);
return -1;
}
body = generator->allocate_body(data->body_data);
if (!body) {
- ast_log(LOG_WARNING, "Unable to allocate a NOTIFY body of type %s/%s\n",
- type, subtype);
+ ast_log(LOG_WARNING, "%s/%s body generator could not to allocate a body\n",
+ type, subtype);
return -1;
}
diff --git a/res/res_pjsip_registrar.c b/res/res_pjsip_registrar.c
index 776700490..a8144fc05 100644
--- a/res/res_pjsip_registrar.c
+++ b/res/res_pjsip_registrar.c
@@ -30,6 +30,7 @@
#include "asterisk/res_pjsip.h"
#include "asterisk/module.h"
+#include "asterisk/paths.h"
#include "asterisk/test.h"
#include "asterisk/taskprocessor.h"
#include "asterisk/manager.h"
@@ -555,6 +556,9 @@ static int rx_task_core(struct rx_task_data *task_data, struct ao2_container *co
if (user_agent) {
ast_string_field_set(contact_update, user_agent, user_agent);
}
+ if (!ast_strlen_zero(ast_config_AST_SYSTEM_NAME)) {
+ ast_string_field_set(contact_update, reg_server, ast_config_AST_SYSTEM_NAME);
+ }
if (ast_sip_location_update_contact(contact_update)) {
ast_log(LOG_ERROR, "Failed to update contact '%s' expiration time to %d seconds.\n",
@@ -576,7 +580,6 @@ static int rx_task_core(struct rx_task_data *task_data, struct ao2_container *co
ao2_cleanup(contact_update);
} else {
/* We want to report the user agent that was actually in the removed contact */
- user_agent = ast_strdupa(contact->user_agent);
ast_sip_location_delete_contact(contact);
ast_verb(3, "Removed contact '%s' from AOR '%s' due to request\n", contact_uri, aor_name);
ast_test_suite_event_notify("AOR_CONTACT_REMOVED",
@@ -585,7 +588,7 @@ static int rx_task_core(struct rx_task_data *task_data, struct ao2_container *co
"UserAgent: %s",
contact_uri,
aor_name,
- user_agent);
+ contact->user_agent);
}
}
@@ -658,6 +661,65 @@ static int rx_task(void *data)
return res;
}
+static int match_aor(const char *aor_name, const char *id)
+{
+ if (ast_strlen_zero(aor_name)) {
+ return 0;
+ }
+
+ if (!strcmp(aor_name, id)) {
+ ast_debug(3, "Matched id '%s' to aor '%s'\n", id, aor_name);
+ return 1;
+ }
+
+ return 0;
+}
+
+static char *find_aor_name(const char *username, const char *domain, const char *aors)
+{
+ char *configured_aors;
+ char *aor_name;
+ char *id_domain;
+ struct ast_sip_domain_alias *alias;
+
+ id_domain = ast_alloca(strlen(username) + strlen(domain) + 2);
+ sprintf(id_domain, "%s@%s", username, domain);
+
+ /* Look for exact match on username@domain */
+ configured_aors = ast_strdupa(aors);
+ while ((aor_name = ast_strip(strsep(&configured_aors, ",")))) {
+ if (match_aor(aor_name, id_domain)) {
+ return ast_strdup(aor_name);
+ }
+ }
+
+ /* If there's a domain alias, look for exact match on username@domain_alias */
+ alias = ast_sorcery_retrieve_by_id(ast_sip_get_sorcery(), "domain_alias", domain);
+ if (alias) {
+ char *id_domain_alias = ast_alloca(strlen(username) + strlen(alias->domain) + 2);
+
+ sprintf(id_domain, "%s@%s", username, alias->domain);
+ ao2_cleanup(alias);
+
+ configured_aors = ast_strdupa(aors);
+ while ((aor_name = ast_strip(strsep(&configured_aors, ",")))) {
+ if (match_aor(aor_name, id_domain_alias)) {
+ return ast_strdup(aor_name);
+ }
+ }
+ }
+
+ /* Look for exact match on username only */
+ configured_aors = ast_strdupa(aors);
+ while ((aor_name = ast_strip(strsep(&configured_aors, ",")))) {
+ if (match_aor(aor_name, username)) {
+ return ast_strdup(aor_name);
+ }
+ }
+
+ return NULL;
+}
+
static pj_bool_t registrar_on_rx_request(struct pjsip_rx_data *rdata)
{
RAII_VAR(struct serializer *, ser, NULL, ao2_cleanup);
@@ -666,10 +728,10 @@ static pj_bool_t registrar_on_rx_request(struct pjsip_rx_data *rdata)
RAII_VAR(struct ast_sip_endpoint *, endpoint,
ast_pjsip_rdata_get_endpoint(rdata), ao2_cleanup);
RAII_VAR(struct ast_sip_aor *, aor, NULL, ao2_cleanup);
- pjsip_sip_uri *uri;
- char *domain_name;
- char *configured_aors, *aor_name;
- RAII_VAR(struct ast_str *, id, NULL, ast_free);
+ char *domain_name = NULL;
+ char *username = NULL;
+ RAII_VAR(char *, aor_name, NULL, ast_free);
+ int i;
if (pjsip_method_cmp(&rdata->msg_info.msg->line.req.method, &pjsip_register_method) || !endpoint) {
return PJ_FALSE;
@@ -690,38 +752,46 @@ static pj_bool_t registrar_on_rx_request(struct pjsip_rx_data *rdata)
return PJ_TRUE;
}
- uri = pjsip_uri_get_uri(rdata->msg_info.to->uri);
- domain_name = ast_alloca(uri->host.slen + 1);
- ast_copy_pj_str(domain_name, &uri->host, uri->host.slen + 1);
+ for (i = 0; i < AST_VECTOR_SIZE(&endpoint->ident_method_order); i++) {
+ pjsip_sip_uri *uri;
+ pjsip_authorization_hdr *header = NULL;
- configured_aors = ast_strdupa(endpoint->aors);
-
- /* Iterate the configured AORs to see if the user or the user+domain match */
- while ((aor_name = ast_strip(strsep(&configured_aors, ",")))) {
- struct ast_sip_domain_alias *alias = NULL;
+ switch (AST_VECTOR_GET(&endpoint->ident_method_order, i)) {
+ case AST_SIP_ENDPOINT_IDENTIFY_BY_USERNAME :
+ uri = pjsip_uri_get_uri(rdata->msg_info.to->uri);
- if (ast_strlen_zero(aor_name)) {
- continue;
- }
+ domain_name = ast_alloca(uri->host.slen + 1);
+ ast_copy_pj_str(domain_name, &uri->host, uri->host.slen + 1);
+ username = ast_alloca(uri->user.slen + 1);
+ ast_copy_pj_str(username, &uri->user, uri->user.slen + 1);
- if (!pj_strcmp2(&uri->user, aor_name)) {
+ aor_name = find_aor_name(username, domain_name, endpoint->aors);
+ if (aor_name) {
+ ast_debug(3, "Matched aor '%s' by To username\n", aor_name);
+ }
break;
+ case AST_SIP_ENDPOINT_IDENTIFY_BY_AUTH_USERNAME :
+ while ((header = pjsip_msg_find_hdr(rdata->msg_info.msg, PJSIP_H_AUTHORIZATION,
+ header ? header->next : NULL))) {
+ if (header && !pj_stricmp2(&header->scheme, "digest")) {
+ username = ast_alloca(header->credential.digest.username.slen + 1);
+ ast_copy_pj_str(username, &header->credential.digest.username, header->credential.digest.username.slen + 1);
+ domain_name = ast_alloca(header->credential.digest.realm.slen + 1);
+ ast_copy_pj_str(domain_name, &header->credential.digest.realm, header->credential.digest.realm.slen + 1);
+
+ aor_name = find_aor_name(username, domain_name, endpoint->aors);
+ if (aor_name) {
+ ast_debug(3, "Matched aor '%s' by Authentication username\n", aor_name);
+ break;
+ }
+ }
+ }
+ break;
+ default:
+ continue;
}
- if (!id && !(id = ast_str_create(uri->user.slen + uri->host.slen + 2))) {
- return PJ_TRUE;
- }
-
- ast_str_set(&id, 0, "%.*s@", (int)uri->user.slen, uri->user.ptr);
- if ((alias = ast_sorcery_retrieve_by_id(ast_sip_get_sorcery(), "domain_alias", domain_name))) {
- ast_str_append(&id, 0, "%s", alias->domain);
- ao2_cleanup(alias);
- } else {
- ast_str_append(&id, 0, "%s", domain_name);
- }
-
- if (!strcmp(aor_name, ast_str_buffer(id))) {
- ast_free(id);
+ if (aor_name) {
break;
}
}
@@ -730,7 +800,7 @@ static pj_bool_t registrar_on_rx_request(struct pjsip_rx_data *rdata)
/* The provided AOR name was not found (be it within the configuration or sorcery itself) */
pjsip_endpt_respond_stateless(ast_sip_get_pjsip_endpoint(), rdata, 404, NULL, NULL, NULL);
ast_sip_report_req_no_support(endpoint, rdata, "registrar_requested_aor_not_found");
- ast_log(LOG_WARNING, "AOR '%.*s' not found for endpoint '%s'\n", (int)uri->user.slen, uri->user.ptr, ast_sorcery_object_get_id(endpoint));
+ ast_log(LOG_WARNING, "AOR '%s' not found for endpoint '%s'\n", username, ast_sorcery_object_get_id(endpoint));
return PJ_TRUE;
}
@@ -887,5 +957,5 @@ AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_LOAD_ORDER, "PJSIP Registrar Suppo
.support_level = AST_MODULE_SUPPORT_CORE,
.load = load_module,
.unload = unload_module,
- .load_pri = AST_MODPRI_APP_DEPEND,
+ .load_pri = AST_MODPRI_CHANNEL_DEPEND - 3,
);
diff --git a/res/res_pjsip_transport_management.c b/res/res_pjsip_transport_management.c
index eb0240438..afd94eb1f 100644
--- a/res/res_pjsip_transport_management.c
+++ b/res/res_pjsip_transport_management.c
@@ -24,6 +24,8 @@
#include "asterisk.h"
+#include <signal.h>
+
#include <pjsip.h>
#include <pjsip_ua.h>
@@ -93,7 +95,7 @@ static void *keepalive_transport_thread(void *data)
/* Once loaded this module just keeps on going as it is unsafe to stop and change the underlying
* callback for the transport manager.
*/
- while (1) {
+ while (keepalive_interval) {
sleep(keepalive_interval);
ao2_callback(transports, OBJ_NODATA, keepalive_transport_cb, NULL);
}
@@ -347,7 +349,19 @@ static int load_module(void)
static int unload_module(void)
{
- /* This will never get called */
+ pjsip_tpmgr *tpmgr = pjsip_endpt_get_tpmgr(ast_sip_get_pjsip_endpoint());
+
+ if (keepalive_interval) {
+ keepalive_interval = 0;
+ pthread_kill(keepalive_thread, SIGURG);
+ pthread_join(keepalive_thread, NULL);
+ }
+
+ ast_sched_context_destroy(sched);
+ ao2_ref(transports, -1);
+
+ ast_sip_unregister_service(&idle_monitor_module);
+ pjsip_tpmgr_set_state_cb(tpmgr, tpmgr_state_callback);
return 0;
}
diff --git a/res/stasis/control.c b/res/stasis/control.c
index 97b0b8809..b2b076b73 100644
--- a/res/stasis/control.c
+++ b/res/stasis/control.c
@@ -323,7 +323,7 @@ static int app_control_dial(struct stasis_app_control *control,
AST_BRIDGE_IMPART_CHAN_INDEPENDENT)) {
ast_hangup(new_chan);
} else {
- control_add_channel_to_bridge(control, chan, bridge);
+ control_swap_channel_in_bridge(control, bridge, chan, NULL);
}
return 0;
@@ -982,11 +982,8 @@ static void bridge_after_cb_failed(enum ast_bridge_after_cb_reason reason,
ast_bridge_after_cb_reason_string(reason));
}
-int control_add_channel_to_bridge(
- struct stasis_app_control *control,
- struct ast_channel *chan, void *data)
+int control_swap_channel_in_bridge(struct stasis_app_control *control, struct ast_bridge *bridge, struct ast_channel *chan, struct ast_channel *swap)
{
- struct ast_bridge *bridge = data;
int res;
if (!control || !bridge) {
@@ -1039,7 +1036,7 @@ int control_add_channel_to_bridge(
res = ast_bridge_impart(bridge,
chan,
- NULL, /* swap channel */
+ swap,
NULL, /* features */
AST_BRIDGE_IMPART_CHAN_DEPARTABLE);
if (res != 0) {
@@ -1055,6 +1052,11 @@ int control_add_channel_to_bridge(
return 0;
}
+int control_add_channel_to_bridge(struct stasis_app_control *control, struct ast_channel *chan, void *data)
+{
+ return control_swap_channel_in_bridge(control, data, chan, NULL);
+}
+
int stasis_app_control_add_channel_to_bridge(
struct stasis_app_control *control, struct ast_bridge *bridge)
{
diff --git a/res/stasis/control.h b/res/stasis/control.h
index 1d37a494a..868a8091b 100644
--- a/res/stasis/control.h
+++ b/res/stasis/control.h
@@ -111,12 +111,20 @@ struct stasis_app *control_app(struct stasis_app_control *control);
* \brief Command callback for adding a channel to a bridge
*
* \param control The control for chan
- * \param channel The channel on which commands should be executed
- * \param bridge Data to be passed to the callback
+ * \param chan The channel on which commands should be executed
+ * \param data Bridge to be passed to the callback
+ */
+int control_add_channel_to_bridge(struct stasis_app_control *control, struct ast_channel *chan, void *data);
+
+/*!
+ * \brief Command for swapping a channel in a bridge
+ *
+ * \param control The control for chan
+ * \param chan The channel on which commands should be executed
+ * \param bridge Bridge to be passed to the callback
+ * \param swap Channel to swap with when joining the bridge
*/
-int control_add_channel_to_bridge(
- struct stasis_app_control *control,
- struct ast_channel *chan, void *obj);
+int control_swap_channel_in_bridge(struct stasis_app_control *control, struct ast_bridge *bridge, struct ast_channel *chan, struct ast_channel *swap);
/*!
* \brief Stop playing silence to a channel right now.
diff --git a/res/stasis/stasis_bridge.c b/res/stasis/stasis_bridge.c
index e41088134..9ffc2d7be 100644
--- a/res/stasis/stasis_bridge.c
+++ b/res/stasis/stasis_bridge.c
@@ -76,24 +76,54 @@ static void bridge_stasis_run_cb(struct ast_channel *chan, void *data)
pbx_exec(chan, app_stasis, app_name);
}
-static int add_channel_to_bridge(
+struct defer_bridge_add_obj {
+ /*! Bridge to join (has ref) */
+ struct ast_bridge *bridge;
+ /*!
+ * \brief Channel to swap with in the bridge. (has ref)
+ *
+ * \note NULL if not swapping with a channel.
+ */
+ struct ast_channel *swap;
+};
+
+static void defer_bridge_add_dtor(void *obj)
+{
+ struct defer_bridge_add_obj *defer = obj;
+
+ ao2_cleanup(defer->bridge);
+ ast_channel_cleanup(defer->swap);
+}
+
+static int defer_bridge_add(
struct stasis_app_control *control,
struct ast_channel *chan, void *obj)
{
- struct ast_bridge *bridge = obj;
- int res;
+ struct defer_bridge_add_obj *defer = obj;
- res = control_add_channel_to_bridge(control,
- chan, bridge);
- return res;
+ return control_swap_channel_in_bridge(control, defer->bridge, chan, defer->swap);
}
static void bridge_stasis_queue_join_action(struct ast_bridge *self,
- struct ast_bridge_channel *bridge_channel)
+ struct ast_bridge_channel *bridge_channel, struct ast_bridge_channel *swap)
{
+ struct defer_bridge_add_obj *defer;
+
+ defer = ao2_alloc_options(sizeof(*defer), defer_bridge_add_dtor,
+ AO2_ALLOC_OPT_LOCK_NOLOCK);
+ if (!defer) {
+ return;
+ }
+ ao2_ref(self, +1);
+ defer->bridge = self;
+ if (swap) {
+ ast_channel_ref(swap->chan);
+ defer->swap = swap->chan;
+ }
+
ast_channel_lock(bridge_channel->chan);
- command_prestart_queue_command(bridge_channel->chan, add_channel_to_bridge,
- ao2_bump(self), __ao2_cleanup);
+ command_prestart_queue_command(bridge_channel->chan, defer_bridge_add,
+ defer, __ao2_cleanup);
ast_channel_unlock(bridge_channel->chan);
}
@@ -167,18 +197,19 @@ static int bridge_stasis_push(struct ast_bridge *self, struct ast_bridge_channel
if (!control && !stasis_app_channel_is_internal(bridge_channel->chan)) {
/* channel not in Stasis(), get it there */
+ ast_debug(1, "Bridge %s: pushing non-stasis %p(%s) setup to come back in under stasis\n",
+ self->uniqueid, bridge_channel, ast_channel_name(bridge_channel->chan));
+
/* Attach after-bridge callback and pass ownership of swap_app to it */
if (ast_bridge_set_after_callback(bridge_channel->chan,
bridge_stasis_run_cb, NULL, NULL)) {
- ast_log(LOG_ERROR, "Failed to set after bridge callback\n");
+ ast_log(LOG_ERROR,
+ "Failed to set after bridge callback for bridge %s non-stasis push of %s\n",
+ self->uniqueid, ast_channel_name(bridge_channel->chan));
return -1;
}
- bridge_stasis_queue_join_action(self, bridge_channel);
- if (swap) {
- /* nudge the swap channel out of the bridge */
- ast_bridge_channel_leave_bridge(swap, BRIDGE_CHANNEL_STATE_END_NO_DISSOLVE, 0);
- }
+ bridge_stasis_queue_join_action(self, bridge_channel, swap);
/* Return -1 so the push fails and the after-bridge callback gets called
* This keeps the bridging framework from putting the channel into the bridge
diff --git a/tests/test_channel_feature_hooks.c b/tests/test_channel_feature_hooks.c
index fbc9786cc..c5d3b9b86 100644
--- a/tests/test_channel_feature_hooks.c
+++ b/tests/test_channel_feature_hooks.c
@@ -40,6 +40,7 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
#include "asterisk/bridge.h"
#include "asterisk/bridge_basic.h"
#include "asterisk/features.h"
+#include "asterisk/format_cache.h"
#define TEST_CATEGORY "/channels/features/"
@@ -47,6 +48,8 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
#define TEST_BACKEND_NAME "Features Test Logging"
+#define TEST_CHANNEL_FORMAT ast_format_slin
+
/*! \brief A channel technology used for the unit tests */
static struct ast_channel_tech test_features_chan_tech = {
.type = CHANNEL_TECH_NAME,
@@ -94,6 +97,11 @@ static void wait_for_unbridged(struct ast_channel *channel)
#define START_CHANNEL(channel, name, number) do { \
channel = ast_channel_alloc(0, AST_STATE_UP, number, name, number, number, \
"default", NULL, NULL, 0, CHANNEL_TECH_NAME "/" name); \
+ ast_channel_nativeformats_set(channel, test_features_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)
@@ -329,12 +337,19 @@ static int unload_module(void)
AST_TEST_UNREGISTER(test_features_channel_interval);
ast_channel_unregister(&test_features_chan_tech);
+ ao2_cleanup(test_features_chan_tech.capabilities);
+ test_features_chan_tech.capabilities = NULL;
return 0;
}
static int load_module(void)
{
+ test_features_chan_tech.capabilities = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT);
+ if (!test_features_chan_tech.capabilities) {
+ return AST_MODULE_LOAD_FAILURE;
+ }
+ ast_format_cap_append(test_features_chan_tech.capabilities, TEST_CHANNEL_FORMAT, 0);
ast_channel_register(&test_features_chan_tech);
AST_TEST_REGISTER(test_features_channel_dtmf);
diff --git a/tests/test_config.c b/tests/test_config.c
index 40de2652b..fd14908b6 100644
--- a/tests/test_config.c
+++ b/tests/test_config.c
@@ -34,6 +34,7 @@
ASTERISK_FILE_VERSION(__FILE__, "$Revision$");
#include <math.h> /* HUGE_VAL */
+#include <sys/stat.h>
#include "asterisk/config.h"
#include "asterisk/module.h"
@@ -49,6 +50,7 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$");
#include "asterisk/format_cap.h"
#define CONFIG_FILE "test_config.conf"
+#define CONFIG_INCLUDE_FILE "test_config_include.conf"
/*
* This builds the folowing config:
@@ -881,6 +883,77 @@ static int hook_cb(struct ast_config *cfg)
return 0;
}
+AST_TEST_DEFINE(config_save)
+{
+ enum ast_test_result_state res = AST_TEST_FAIL;
+ struct ast_flags config_flags = { 0 };
+ struct ast_config *cfg;
+ char config_filename[PATH_MAX];
+ char include_filename[PATH_MAX];
+ struct stat config_stat;
+ off_t before_save;
+
+ switch (cmd) {
+ case TEST_INIT:
+ info->name = "config_save";
+ info->category = "/main/config/";
+ info->summary = "Test config save";
+ info->description =
+ "Test configuration save.";
+ return AST_TEST_NOT_RUN;
+ case TEST_EXECUTE:
+ break;
+ }
+
+ if (write_config_file()) {
+ ast_test_status_update(test, "Could not write initial config files\n");
+ return res;
+ }
+
+ snprintf(config_filename, PATH_MAX, "%s/%s", ast_config_AST_CONFIG_DIR, CONFIG_FILE);
+ snprintf(include_filename, PATH_MAX, "%s/%s", ast_config_AST_CONFIG_DIR, CONFIG_INCLUDE_FILE);
+
+ cfg = ast_config_load(CONFIG_FILE, config_flags);
+ if (!cfg) {
+ ast_test_status_update(test, "Could not load config\n");
+ goto out;
+ }
+
+ /* We need to re-save to get the generator header */
+ if (ast_config_text_file_save(CONFIG_FILE, cfg, "TEST")) {
+ ast_test_status_update(test, "Unable to write files\n");
+ goto out;
+ }
+
+ stat(config_filename, &config_stat);
+ before_save = config_stat.st_size;
+
+ if (!ast_include_new(cfg, CONFIG_FILE, CONFIG_INCLUDE_FILE, 0, NULL, 4, include_filename, PATH_MAX)) {
+ ast_test_status_update(test, "Could not create include\n");
+ goto out;
+ }
+
+ if (ast_config_text_file_save(CONFIG_FILE, cfg, "TEST")) {
+ ast_test_status_update(test, "Unable to write files\n");
+ goto out;
+ }
+
+ stat(config_filename, &config_stat);
+ if (config_stat.st_size <= before_save) {
+ ast_test_status_update(test, "Did not save config file with #include\n");
+ goto out;
+ }
+
+ res = AST_TEST_PASS;
+
+out:
+ ast_config_destroy(cfg);
+ unlink(config_filename);
+ unlink(include_filename);
+
+ return res;
+}
+
AST_TEST_DEFINE(config_hook)
{
enum ast_test_result_state res = AST_TEST_FAIL;
@@ -1734,6 +1807,7 @@ AST_TEST_DEFINE(variable_lists_match)
static int unload_module(void)
{
+ AST_TEST_UNREGISTER(config_save);
AST_TEST_UNREGISTER(config_basic_ops);
AST_TEST_UNREGISTER(config_filtered_ops);
AST_TEST_UNREGISTER(config_template_ops);
@@ -1748,6 +1822,7 @@ static int unload_module(void)
static int load_module(void)
{
+ AST_TEST_REGISTER(config_save);
AST_TEST_REGISTER(config_basic_ops);
AST_TEST_REGISTER(config_filtered_ops);
AST_TEST_REGISTER(config_template_ops);
diff --git a/tests/test_message.c b/tests/test_message.c
index f7ee02730..f73901ea6 100644
--- a/tests/test_message.c
+++ b/tests/test_message.c
@@ -232,8 +232,8 @@ static int user_event_hook_cb(int category, const char *event, char *body)
static int handler_wait_for_message(struct ast_test *test)
{
int error = 0;
- struct timeval wait_now = ast_tvnow();
- struct timespec wait_time = { .tv_sec = wait_now.tv_sec + 1, .tv_nsec = wait_now.tv_usec * 1000 };
+ struct timeval wait = ast_tvadd(ast_tvnow(), ast_tv(5 /* seconds */, 0));
+ struct timespec wait_time = { .tv_sec = wait.tv_sec, .tv_nsec = wait.tv_usec * 1000 };
ast_mutex_lock(&handler_lock);
while (!handler_received_message) {
@@ -253,8 +253,8 @@ static int handler_wait_for_message(struct ast_test *test)
static int user_event_wait_for_events(struct ast_test *test, int expected_events)
{
int error;
- struct timeval wait_now = ast_tvnow();
- struct timespec wait_time = { .tv_sec = wait_now.tv_sec + 1, .tv_nsec = wait_now.tv_usec * 1000 };
+ struct timeval wait = ast_tvadd(ast_tvnow(), ast_tv(5 /* seconds */, 0));
+ struct timespec wait_time = { .tv_sec = wait.tv_sec, .tv_nsec = wait.tv_usec * 1000 };
expected_user_events = expected_events;
diff --git a/tests/test_res_pjsip_scheduler.c b/tests/test_res_pjsip_scheduler.c
new file mode 100644
index 000000000..f9a1633ac
--- /dev/null
+++ b/tests/test_res_pjsip_scheduler.c
@@ -0,0 +1,400 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2016, Fairview 5 Engineering, LLC
+ *
+ * George Joseph <george.joseph@fairview5.com>
+ *
+ * See http://www.asterisk.org for more information about
+ * the Asterisk project. Please do not directly contact
+ * any of the maintainers of this project for assistance;
+ * the project provides a web site, mailing lists and IRC
+ * channels for your use.
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE file
+ * at the top of the source tree.
+ */
+
+/*!
+ * \file
+ * \brief res_pjsip scheduler tests
+ *
+ * \author George Joseph <george.joseph@fairview5.com>
+ *
+ */
+
+/*** MODULEINFO
+ <depend>TEST_FRAMEWORK</depend>
+ <depend>res_pjsip</depend>
+ <support_level>core</support_level>
+ ***/
+
+#include "asterisk.h"
+
+ASTERISK_REGISTER_FILE()
+
+#include <pjsip.h>
+#include "asterisk/test.h"
+#include "asterisk/module.h"
+#include "asterisk/taskprocessor.h"
+#include "asterisk/res_pjsip.h"
+#include "asterisk/utils.h"
+
+#define CATEGORY "/res/res_pjsip/scheduler/"
+
+struct test_data {
+ ast_mutex_t lock;
+ ast_cond_t cond;
+ pthread_t tid;
+ struct timeval test_start;
+ struct timeval task_start;
+ struct timeval task_end;
+ int is_servant;
+ int interval;
+ int sleep;
+ int done;
+ struct ast_test *test;
+};
+
+#define S2U(x) (long int)(x * 1000 * 1000)
+#define M2U(x) (long int)(x * 1000)
+
+static int task_1(void *data)
+{
+ struct test_data *test = data;
+
+ test->done = 0;
+ test->task_start = ast_tvnow();
+ test->tid = pthread_self();
+ test->is_servant = ast_sip_thread_is_servant();
+ usleep(M2U(test->sleep));
+ test->task_end = ast_tvnow();
+
+ ast_mutex_lock(&test->lock);
+ test->done = 1;
+ ast_mutex_unlock(&test->lock);
+ ast_cond_signal(&test->cond);
+
+ return test->interval;
+}
+
+
+static void data_cleanup(void *data)
+{
+ struct test_data *test_data = data;
+ ast_mutex_destroy(&test_data->lock);
+ ast_cond_destroy(&test_data->cond);
+}
+
+#define waitfor(x) \
+{ \
+ ast_mutex_lock(&(x)->lock); \
+ while (!(x)->done) { \
+ ast_cond_wait(&(x)->cond, &(x)->lock); \
+ } \
+ (x)->done = 0; \
+ ast_mutex_unlock(&(x)->lock); \
+}
+
+static int scheduler(struct ast_test *test, int serialized)
+{
+ RAII_VAR(struct ast_taskprocessor *, tp1, NULL, ast_taskprocessor_unreference);
+ RAII_VAR(struct test_data *, test_data1, ao2_alloc(sizeof(*test_data1), data_cleanup), ao2_cleanup);
+ RAII_VAR(struct test_data *, test_data2, ao2_alloc(sizeof(*test_data2), data_cleanup), ao2_cleanup);
+ RAII_VAR(struct ast_sip_sched_task *, task1, NULL, ao2_cleanup);
+ RAII_VAR(struct ast_sip_sched_task *, task2, NULL, ao2_cleanup);
+ int duration;
+ int delay;
+ struct timeval task1_start;
+
+ ast_test_validate(test, test_data1 != NULL);
+ ast_test_validate(test, test_data2 != NULL);
+
+ test_data1->test = test;
+ test_data1->test_start = ast_tvnow();
+ test_data1->interval = 2000;
+ test_data1->sleep = 1000;
+ ast_mutex_init(&test_data1->lock);
+ ast_cond_init(&test_data1->cond, NULL);
+
+ test_data2->test = test;
+ test_data2->test_start = ast_tvnow();
+ test_data2->interval = 2000;
+ test_data2->sleep = 1000;
+ ast_mutex_init(&test_data2->lock);
+ ast_cond_init(&test_data2->cond, NULL);
+
+ if (serialized) {
+ ast_test_status_update(test, "This test will take about %3.1f seconds\n",
+ (test_data1->interval + test_data1->sleep + (MAX(test_data1->interval - test_data2->interval, 0)) + test_data2->sleep) / 1000.0);
+ tp1 = ast_sip_create_serializer();
+ ast_test_validate(test, (tp1 != NULL));
+ } else {
+ ast_test_status_update(test, "This test will take about %3.1f seconds\n",
+ ((MAX(test_data1->interval, test_data2->interval) + MAX(test_data1->sleep, test_data2->sleep)) / 1000.0));
+ }
+
+ task1 = ast_sip_schedule_task(tp1, test_data1->interval, task_1, NULL, test_data1, AST_SIP_SCHED_TASK_FIXED);
+ ast_test_validate(test, task1 != NULL);
+
+ task2 = ast_sip_schedule_task(tp1, test_data2->interval, task_1, NULL, test_data2, AST_SIP_SCHED_TASK_FIXED);
+ ast_test_validate(test, task2 != NULL);
+
+ waitfor(test_data1);
+ ast_sip_sched_task_cancel(task1);
+ ast_test_validate(test, test_data1->is_servant);
+
+ duration = ast_tvdiff_ms(test_data1->task_end, test_data1->test_start);
+ ast_test_validate(test, (duration > ((test_data1->interval + test_data1->sleep) * 0.9))
+ && (duration < ((test_data1->interval + test_data1->sleep) * 1.1)));
+
+ ast_sip_sched_task_get_times(task1, NULL, &task1_start, NULL);
+ delay = ast_tvdiff_ms(task1_start, test_data1->test_start);
+ ast_test_validate(test, (delay > (test_data1->interval * 0.9)
+ && (delay < (test_data1->interval * 1.1))));
+
+ waitfor(test_data2);
+ ast_sip_sched_task_cancel(task2);
+ ast_test_validate(test, test_data2->is_servant);
+
+ if (serialized) {
+ ast_test_validate(test, test_data1->tid == test_data2->tid);
+ ast_test_validate(test, ast_tvdiff_ms(test_data2->task_start, test_data1->task_end) >= 0);
+ } else {
+ ast_test_validate(test, test_data1->tid != test_data2->tid);
+ }
+
+ return AST_TEST_PASS;
+}
+
+AST_TEST_DEFINE(serialized_scheduler)
+{
+
+ switch (cmd) {
+ case TEST_INIT:
+ info->name = __func__;
+ info->category = CATEGORY;
+ info->summary = "Test res_pjsip serialized scheduler";
+ info->description = "Test res_pjsip serialized scheduler";
+ return AST_TEST_NOT_RUN;
+ case TEST_EXECUTE:
+ break;
+ }
+
+ return scheduler(test, 1);
+}
+
+AST_TEST_DEFINE(unserialized_scheduler)
+{
+
+ switch (cmd) {
+ case TEST_INIT:
+ info->name = __func__;
+ info->category = CATEGORY;
+ info->summary = "Test res_pjsip unserialized scheduler";
+ info->description = "Test res_pjsip unserialized scheduler";
+ return AST_TEST_NOT_RUN;
+ case TEST_EXECUTE:
+ break;
+ }
+
+ return scheduler(test, 0);
+}
+
+static int run_count;
+static int destruct_count;
+
+static int dummy_task(void *data)
+{
+ int *sleep = data;
+
+ usleep(M2U(*sleep));
+ run_count++;
+
+ return 0;
+}
+
+static void test_destructor(void *data)
+{
+ destruct_count++;
+}
+
+AST_TEST_DEFINE(scheduler_cleanup)
+{
+ RAII_VAR(int *, sleep, NULL, ao2_cleanup);
+ RAII_VAR(struct ast_sip_sched_task *, task, NULL, ao2_cleanup);
+ int interval;
+ int when;
+
+ switch (cmd) {
+ case TEST_INIT:
+ info->name = __func__;
+ info->category = CATEGORY;
+ info->summary = "Test res_pjsip scheduler cleanup";
+ info->description = "Test res_pjsip scheduler cleanup";
+ return AST_TEST_NOT_RUN;
+ case TEST_EXECUTE:
+ break;
+ }
+
+ destruct_count = 0;
+ interval = 1000;
+
+ sleep = ao2_alloc(sizeof(*sleep), test_destructor);
+ ast_test_validate(test, sleep != NULL);
+ *sleep = 500;
+
+ ast_test_status_update(test, "This test will take about %3.1f seconds\n",
+ ((interval * 1.1) + *sleep) / 1000.0);
+
+ task = ast_sip_schedule_task(NULL, interval, dummy_task, "dummy", sleep,
+ AST_SIP_SCHED_TASK_DATA_AO2 | AST_SIP_SCHED_TASK_DATA_FREE);
+ ast_test_validate(test, task != NULL);
+ usleep(M2U(interval * 0.5));
+ when = ast_sip_sched_task_get_next_run(task);
+ ast_test_validate(test, (when > (interval * 0.4) && when < (interval * 0.6)));
+ usleep(M2U(interval * 0.6));
+ ast_test_validate(test, ast_sip_sched_is_task_running(task));
+
+ usleep(M2U(*sleep));
+
+ ast_test_validate(test, (ast_sip_sched_is_task_running(task) == 0));
+ when = ast_sip_sched_task_get_next_run(task);
+ ast_test_validate(test, (when < 0), res, error);
+ ast_test_validate(test, (ao2_ref(task, 0) == 1));
+ ao2_ref(task, -1);
+ task = NULL;
+ ast_test_validate(test, (destruct_count == 1));
+ sleep = NULL;
+
+ return AST_TEST_PASS;
+}
+
+AST_TEST_DEFINE(scheduler_cancel)
+{
+ RAII_VAR(int *, sleep, NULL, ao2_cleanup);
+ RAII_VAR(struct ast_sip_sched_task *, task, NULL, ao2_cleanup);
+ int interval;
+ int when;
+
+ switch (cmd) {
+ case TEST_INIT:
+ info->name = __func__;
+ info->category = CATEGORY;
+ info->summary = "Test res_pjsip scheduler cancel task";
+ info->description = "Test res_pjsip scheduler cancel task";
+ return AST_TEST_NOT_RUN;
+ case TEST_EXECUTE:
+ break;
+ }
+
+ destruct_count = 0;
+ interval = 1000;
+
+ sleep = ao2_alloc(sizeof(*sleep), test_destructor);
+ ast_test_validate(test, sleep != NULL);
+ *sleep = 500;
+
+ ast_test_status_update(test, "This test will take about %3.1f seconds\n",
+ (interval + *sleep) / 1000.0);
+
+ task = ast_sip_schedule_task(NULL, interval, dummy_task, "dummy", sleep, AST_SIP_SCHED_TASK_DATA_NO_CLEANUP);
+ ast_test_validate(test, task != NULL);
+
+ usleep(M2U(interval * 0.5));
+ when = ast_sip_sched_task_get_next_run_by_name("dummy");
+ ast_test_validate(test, (when > (interval * 0.4) && when < (interval * 0.6)));
+ ast_test_validate(test, !ast_sip_sched_is_task_running_by_name("dummy"));
+ ast_test_validate(test, ao2_ref(task, 0) == 2);
+
+ ast_sip_sched_task_cancel_by_name("dummy");
+
+ when = ast_sip_sched_task_get_next_run(task);
+ ast_test_validate(test, when < 0);
+
+ usleep(M2U(interval));
+ ast_test_validate(test, run_count == 0);
+ ast_test_validate(test, destruct_count == 0);
+ ast_test_validate(test, ao2_ref(task, 0) == 1);
+
+ return AST_TEST_PASS;
+}
+
+AST_TEST_DEFINE(scheduler_policy)
+{
+ RAII_VAR(struct test_data *, test_data1, ao2_alloc(sizeof(*test_data1), data_cleanup), ao2_cleanup);
+ RAII_VAR(struct ast_sip_sched_task *, task, NULL, ao2_cleanup);
+ int when;
+
+ switch (cmd) {
+ case TEST_INIT:
+ info->name = __func__;
+ info->category = CATEGORY;
+ info->summary = "Test res_pjsip scheduler cancel task";
+ info->description = "Test res_pjsip scheduler cancel task";
+ return AST_TEST_NOT_RUN;
+ case TEST_EXECUTE:
+ break;
+ }
+
+ ast_test_validate(test, test_data1 != NULL);
+
+ destruct_count = 0;
+ run_count = 0;
+ test_data1->test = test;
+ test_data1->test_start = ast_tvnow();
+ test_data1->interval = 1000;
+ test_data1->sleep = 500;
+ ast_mutex_init(&test_data1->lock);
+ ast_cond_init(&test_data1->cond, NULL);
+
+ ast_test_status_update(test, "This test will take about %3.1f seconds\n",
+ ((test_data1->interval * 3) + test_data1->sleep) / 1000.0);
+
+ task = ast_sip_schedule_task(NULL, test_data1->interval, task_1, "test_1", test_data1,
+ AST_SIP_SCHED_TASK_DATA_NO_CLEANUP | AST_SIP_SCHED_TASK_PERIODIC);
+ ast_test_validate(test, task != NULL);
+
+ waitfor(test_data1);
+ when = ast_tvdiff_ms(test_data1->task_start, test_data1->test_start);
+ ast_test_validate(test, when > test_data1->interval * 0.9 && when < test_data1->interval * 1.1);
+
+ waitfor(test_data1);
+ when = ast_tvdiff_ms(test_data1->task_start, test_data1->test_start);
+ ast_test_validate(test, when > test_data1->interval * 2 * 0.9 && when < test_data1->interval * 2 * 1.1);
+
+ waitfor(test_data1);
+ when = ast_tvdiff_ms(test_data1->task_start, test_data1->test_start);
+ ast_test_validate(test, when > test_data1->interval * 3 * 0.9 && when < test_data1->interval * 3 * 1.1);
+
+ ast_sip_sched_task_cancel(task);
+ ao2_ref(task, -1);
+ task = NULL;
+
+ return AST_TEST_PASS;
+}
+
+static int load_module(void)
+{
+ CHECK_PJSIP_MODULE_LOADED();
+
+ AST_TEST_REGISTER(serialized_scheduler);
+ AST_TEST_REGISTER(unserialized_scheduler);
+ AST_TEST_REGISTER(scheduler_cleanup);
+ AST_TEST_REGISTER(scheduler_cancel);
+ AST_TEST_REGISTER(scheduler_policy);
+ return AST_MODULE_LOAD_SUCCESS;
+}
+
+static int unload_module(void)
+{
+ AST_TEST_UNREGISTER(scheduler_cancel);
+ AST_TEST_UNREGISTER(scheduler_cleanup);
+ AST_TEST_UNREGISTER(unserialized_scheduler);
+ AST_TEST_UNREGISTER(serialized_scheduler);
+ AST_TEST_UNREGISTER(scheduler_policy);
+ return 0;
+}
+
+AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "res_pjsip scheduler test module");
diff --git a/third-party/pjproject/Makefile b/third-party/pjproject/Makefile
index 5810a65dc..7349db62f 100644
--- a/third-party/pjproject/Makefile
+++ b/third-party/pjproject/Makefile
@@ -45,6 +45,13 @@ ifeq ($(SPECIAL_TARGETS),)
CF := $(filter-out -I%,$(CF))
export CFLAGS += $(CF)
export LDFLAGS += $(CC_LDFLAGS)
+ TARGETS := pjproject.symbols
+ ifeq ($(findstring TEST_FRAMEWORK,$(MENUSELECT_CFLAGS)),TEST_FRAMEWORK)
+ TARGETS += source/pjsip-apps/bin/pjsua-$(TARGET_NAME)
+ ifneq ($(PYTHONDEV_LIB),)
+ TARGETS += source/pjsip-apps/src/python/build/_pjsua.so
+ endif
+ endif
else
all install:
endif
@@ -95,12 +102,10 @@ source/pjlib/build/.pjlib-$(TARGET_NAME).depend: build.mak
$(ECHO_PREFIX) "Making dependencies"
+$(CMD_PREFIX) $(SUBMAKE) -C source dep
-
menuselect: ../../menuselect.makeopts ../../makeopts
-$(CMD_PREFIX) test -d source && ($(SUBMAKE) -C source clean ; find source -name *.a -delete ; rm -rf source/pjsip-apps/src/python/build) || :
-$(CMD_PREFIX) rm -rf pjproject.symbols
-
source/pjlib/lib/libpj-$(TARGET_NAME).a: menuselect source/pjlib/build/.pjlib-$(TARGET_NAME).depend
$(ECHO_PREFIX) Compiling libs
+$(CMD_PREFIX) $(SUBMAKE) -C source lib $(REALLY_QUIET)
@@ -115,20 +120,24 @@ source/pjsip-apps/bin/pjsua-$(TARGET_NAME): source/pjlib/lib/libpj-$(TARGET_NAME
source/pjsip-apps/src/python/build/_pjsua.so: source/pjlib/lib/libpj-$(TARGET_NAME).a
$(ECHO_PREFIX) Compiling python bindings
- $(CMD_PREFIX) (cd source/pjsip-apps/src/python ; python setup.py build --build-platlib=./build $(REALLY_QUIET))
-
+ $(CMD_PREFIX) (cd source/pjsip-apps/src/python ; MAKE=$(MAKE) python setup.py build --build-platlib=./build $(REALLY_QUIET))
-_all: pjproject.symbols source/pjsip-apps/bin/pjsua-$(TARGET_NAME) source/pjsip-apps/src/python/build/_pjsua.so
+_all: $(TARGETS)
_install: _all
- $(ECHO_PREFIX) Installing apps and python bindings
@if [ ! -d "$(DESTDIR)$(ASTDATADIR)/third-party/pjproject" ]; then \
$(INSTALL) -d "$(DESTDIR)$(ASTDATADIR)/third-party/pjproject"; \
fi;
+ifneq ($(findstring source/pjsip-apps/bin/pjsua-$(TARGET_NAME),$(TARGETS)),)
+ $(ECHO_PREFIX) Installing apps
$(CMD_PREFIX) $(INSTALL) -m 755 source/pjsip-apps/bin/pjsua-$(TARGET_NAME) "$(DESTDIR)$(ASTDATADIR)/third-party/pjproject/pjsua"
$(CMD_PREFIX) $(INSTALL) -m 755 source/pjsip-apps/bin/pjsystest-$(TARGET_NAME) "$(DESTDIR)$(ASTDATADIR)/third-party/pjproject/pjsystest"
+endif
+ifneq ($(findstring source/pjsip-apps/src/python/build/_pjsua.so,$(TARGETS)),)
+ $(ECHO_PREFIX) Installing python bindings
$(CMD_PREFIX) $(INSTALL) -m 755 source/pjsip-apps/src/python/build/_pjsua.so "$(DESTDIR)$(ASTDATADIR)/third-party/pjproject/"
$(CMD_PREFIX) $(INSTALL) -m 644 source/pjsip-apps/src/python/build/pjsua.py "$(DESTDIR)$(ASTDATADIR)/third-party/pjproject/"
+endif
uninstall:
$(ECHO_PREFIX) Uninstalling apps and python bindings
@@ -136,7 +145,7 @@ uninstall:
clean:
$(ECHO_PREFIX) Cleaning
- -$(CMD_PREFIX) test -d source && ($(SUBMAKE) -C source clean ; find source -name *.a -delete ; rm -rf source/pjsip-apps/src/python/build) || :
+ -$(CMD_PREFIX) test -d source && ($(SUBMAKE) -C source clean ; find source -name *.a -delete ; rm -rf source/pjsip-apps/src/python/build ; rm -rf source/pjsip-apps/bin/* ) || :
-$(CMD_PREFIX) rm -rf pjproject.symbols
distclean:
diff --git a/third-party/pjproject/Makefile.rules b/third-party/pjproject/Makefile.rules
index 062793f12..d2e7d25a4 100644
--- a/third-party/pjproject/Makefile.rules
+++ b/third-party/pjproject/Makefile.rules
@@ -1,7 +1,10 @@
PJPROJECT_URL = http://www.pjsip.org/release/$(PJPROJECT_VERSION)
# Even though we're not installing pjproject, we're setting prefix to /opt/pjproject to be safe
-PJPROJECT_CONFIG_OPTS = --prefix=/opt/pjproject --with-external-speex --with-external-gsm --with-external-srtp \
- --disable-video --disable-v4l2 --disable-sound --disable-opencore-amr --disable-ilbc-codec \
- --without-libyuv --disable-g7221-codec \
- --enable-epoll
+PJPROJECT_CONFIG_OPTS = --prefix=/opt/pjproject --disable-speex-codec --disable-speex-aec \
+ --disable-gsm-codec --disable-video --disable-v4l2 --disable-sound --disable-opencore-amr \
+ --disable-ilbc-codec --without-libyuv --disable-g7221-codec --disable-resample
+
+ifeq ($(shell uname -s),Linux)
+ PJPROJECT_CONFIG_OPTS += --enable-epoll
+endif
diff --git a/third-party/pjproject/apply_patches b/third-party/pjproject/apply_patches
index 1b72d14b0..5dfdd2a3c 100755
--- a/third-party/pjproject/apply_patches
+++ b/third-party/pjproject/apply_patches
@@ -1,4 +1,4 @@
-#!/bin/bash
+#!/bin/sh
if [ "$1" = "-q" ] ; then
quiet=1
@@ -27,7 +27,7 @@ if [ ! "$(ls -A $patchdir/*.patch 2>/dev/null)" ] ; then
fi
for patchfile in $patchdir/*.patch ; do
- patch -d $sourcedir -p1 -s -r- -f -N --dry-run -i "$patchfile" || (echo "Patchfile $(basename $patchfile) failed to apply >&2" ; exit 1) || exit 1
+ patch -d $sourcedir -p1 -s -r- -f -N --dry-run -i "$patchfile" || (echo "Patchfile $(basename $patchfile) failed to apply" >&2 ; exit 1) || exit 1
done
for patchfile in "$patchdir"/*.patch ; do
diff --git a/third-party/pjproject/configure.m4 b/third-party/pjproject/configure.m4
index 743222774..2cc18bfa8 100644
--- a/third-party/pjproject/configure.m4
+++ b/third-party/pjproject/configure.m4
@@ -21,14 +21,14 @@ AC_DEFUN([PJPROJECT_CONFIGURE],
[
AC_MSG_CHECKING(for embedded pjproject (may have to download))
AC_MSG_RESULT(configuring)
- make --quiet --no-print-directory -C $1 configure
+ ${GNU_MAKE} --quiet --no-print-directory -C $1 configure
if test $? -ne 0 ; then
AC_MSG_RESULT(failed)
AC_MSG_NOTICE(Unable to configure $1)
- AC_MSG_ERROR(Run "make -C $1 NOISY_BUILD=yes configure" to see error details.)
+ AC_MSG_ERROR(Run "${GNU_MAKE} -C $1 NOISY_BUILD=yes configure" to see error details.)
fi
- PJPROJECT_INCLUDE=$(make --quiet --no-print-directory -C $1 echo_cflags)
+ PJPROJECT_INCLUDE=$(${GNU_MAKE} --quiet --no-print-directory -C $1 echo_cflags)
PJPROJECT_CFLAGS="$PJPROJECT_INCLUDE"
PBX_PJPROJECT=1
PJPROJECT_BUNDLED=yes
diff --git a/third-party/pjproject/patches/0001-pjsip-apps-src-python-setup.py-Take-make-from-the-en.patch b/third-party/pjproject/patches/0001-pjsip-apps-src-python-setup.py-Take-make-from-the-en.patch
new file mode 100644
index 000000000..80f8bc0b3
--- /dev/null
+++ b/third-party/pjproject/patches/0001-pjsip-apps-src-python-setup.py-Take-make-from-the-en.patch
@@ -0,0 +1,51 @@
+From 61668b8fcaa0f2a8a05100097284c0c427600033 Mon Sep 17 00:00:00 2001
+From: George Joseph <george.joseph@fairview5.com>
+Date: Mon, 2 May 2016 17:08:15 -0600
+Subject: [PATCH] pjsip-apps/src/python/setup.py: Take "make" from the
+ environment
+
+With "make" hard coded in setup.py, it chokes on FreeBSD because the system
+make command isn't GNU compatibile. This patch allows setup.py to take the
+name of the make command from the MAKE environment variable if it exists.
+If it doesn't, it defaults to "make".
+---
+ pjsip-apps/src/python/setup.py | 8 ++++----
+ 1 file changed, 4 insertions(+), 4 deletions(-)
+
+diff --git a/pjsip-apps/src/python/setup.py b/pjsip-apps/src/python/setup.py
+index 69a9859..ea1427d 100644
+--- a/pjsip-apps/src/python/setup.py
++++ b/pjsip-apps/src/python/setup.py
+@@ -60,25 +60,25 @@ if pj_version_suffix:
+ pj_version += "-" + pj_version_suffix
+
+ #print 'PJ_VERSION = "'+ pj_version + '"'
+-
++MAKE = os.environ.get('MAKE') or "make"
+
+ # Fill in pj_inc_dirs
+ pj_inc_dirs = []
+-f = os.popen("make -f helper.mak inc_dir")
++f = os.popen("%s -f helper.mak inc_dir" % MAKE)
+ for line in f:
+ pj_inc_dirs.append(line.rstrip("\r\n"))
+ f.close()
+
+ # Fill in pj_lib_dirs
+ pj_lib_dirs = []
+-f = os.popen("make -f helper.mak lib_dir")
++f = os.popen("%s -f helper.mak lib_dir" % MAKE)
+ for line in f:
+ pj_lib_dirs.append(line.rstrip("\r\n"))
+ f.close()
+
+ # Fill in pj_libs
+ pj_libs = []
+-f = os.popen("make -f helper.mak libs")
++f = os.popen("%s -f helper.mak libs" % MAKE)
+ for line in f:
+ pj_libs.append(line.rstrip("\r\n"))
+ f.close()
+--
+2.5.5
+
diff --git a/third-party/pjproject/patches/0001-sip_parser.c-Remove-wholesale-strip-from-parse_param.patch b/third-party/pjproject/patches/0001-sip_parser.c-Remove-wholesale-strip-from-parse_param.patch
new file mode 100644
index 000000000..e0bd9129c
--- /dev/null
+++ b/third-party/pjproject/patches/0001-sip_parser.c-Remove-wholesale-strip-from-parse_param.patch
@@ -0,0 +1,55 @@
+From ce426249ec1270f27560919791f3e13eaeea9152 Mon Sep 17 00:00:00 2001
+From: George Joseph <george.joseph@fairview5.com>
+Date: Tue, 12 Apr 2016 14:09:53 -0600
+Subject: [PATCH] sip_parser.c: Remove wholesale '[]' strip from
+ parse_param_impl
+
+The wholesale stripping of '[]' from header parameters causes issues if
+something (like a port) occurrs after the final ']'.
+
+'[2001:a::b]' will correctly parse to '2001:a::b'
+'[2001:a::b]:8080' will correctly parse to '2001:a::b' but the scanner is left
+with ':8080' and parsing stops with a syntax error.
+
+I can't even find a case where stripping the '[]' is a good thing anyway. Even
+if you continued to parse and resulted in a string that looks like this...
+'2001:a::b:8080', it's not valid.
+
+This came up in Asterisk because Kamailio sends us a Contact with an alias
+URI parameter that has an IPv6 address in it like this:
+Contact: <sip:1171@127.0.0.1:5080;alias=[2001:1:2::3]~43691~6>
+which should be legal but causes a syntax error because of the characters
+after the final ']'. Even if it didn't, the '[]' should still not be stripped.
+
+I've run the Asterisk Test Suite for PJSIP (252 tests) many of which are IPv6
+enabled. No issues were caused by removing the code that strips the '[]'.
+
+I tried running 'make pjsip-test' but that fails even without my change. :)
+
+The Asterisk ticket is: https://issues.asterisk.org/jira/browse/ASTERISK-25123
+---
+ pjsip/src/pjsip/sip_parser.c | 8 --------
+ 1 file changed, 8 deletions(-)
+
+diff --git a/pjsip/src/pjsip/sip_parser.c b/pjsip/src/pjsip/sip_parser.c
+index c18faa3..98eb5ea 100644
+--- a/pjsip/src/pjsip/sip_parser.c
++++ b/pjsip/src/pjsip/sip_parser.c
+@@ -1149,14 +1149,6 @@ static void parse_param_imp( pj_scanner *scanner, pj_pool_t *pool,
+ pvalue->ptr++;
+ pvalue->slen -= 2;
+ }
+- } else if (*scanner->curptr == '[') {
+- /* pvalue can be a quoted IPv6; in this case, the
+- * '[' and ']' quote characters are to be removed
+- * from the pvalue.
+- */
+- pj_scan_get_char(scanner);
+- pj_scan_get_until_ch(scanner, ']', pvalue);
+- pj_scan_get_char(scanner);
+ } else if(pj_cis_match(spec, *scanner->curptr)) {
+ parser_get_and_unescape(scanner, pool, spec, esc_spec, pvalue);
+ }
+--
+2.5.5
+
diff --git a/third-party/pjproject/patches/config_site.h b/third-party/pjproject/patches/config_site.h
index 840d8b279..8e854b723 100644
--- a/third-party/pjproject/patches/config_site.h
+++ b/third-party/pjproject/patches/config_site.h
@@ -28,7 +28,11 @@
#define PJSIP_SAFE_MODULE 0
#define PJ_HAS_STRICMP_ALNUM 0
#define PJ_HASH_USE_OWN_TOLOWER 1
-#define PJSIP_UNESCAPE_IN_PLACE 1
+/*
+ It is imperative that PJSIP_UNESCAPE_IN_PLACE remain 0 or undefined.
+ Enabling it will result in SEGFAULTS when URIs containing escape sequences are encountered.
+*/
+#undef PJSIP_UNESCAPE_IN_PLACE
#define PJSIP_MAX_PKT_LEN 6000
#undef PJ_TODO