names. This setting is configurable for cdr_adaptive_odbc via the
quoted_identifiers in configuration file cdr_adaptive_odbc.conf.
+
+------------------------------------------------------------------------------
+--- Functionality changes from Asterisk 13.5.0 to Asterisk 13.6.0 ------------
+------------------------------------------------------------------------------
+
+Dialplan Functions
+------------------
+ * The CHANNEL function, when used on a PJSIP channel, now exposes a 'call-id'
+ extraction option when using with the 'pjsip' signalling option. It will
+ return the SIP Call-ID associated with the INVITE request that established
+ the PJSIP channel.
+
------------------------------------------------------------------------------
--- Functionality changes from Asterisk 13.4.0 to Asterisk 13.5.0 ------------
------------------------------------------------------------------------------
"make menuselect" to view the dependencies for specific modules.
On many distributions, these dependencies are installed by packages with names
-like 'glibc-devel', 'ncurses-devel', 'openssl-devel' and 'zlib-devel'
+like 'glibc-devel', 'ncurses-devel', 'openssl-devel' and 'zlib-devel'
or similar.
So, let's proceed:
many places). A configuration file is divided into sections whose names
appear in []'s. Each section typically contains two types of statements,
those of the form 'variable = value', and those of the form 'object =>
-parameters'. Internally the use of '=' and '=>' is exactly the same, so
+parameters'. Internally the use of '=' and '=>' is exactly the same, so
they're used only to help make the configuration file easier to
understand, and do not affect how it is actually parsed.
The "national" switchtype would be applied to channels one through
four and channels 10 through 12, whereas the "dms100" switchtype would
apply to channels 25 through 47.
-
+
The "object => parameters" instantiates an object with the given
parameters. For example, the line "channel => 25-47" creates objects for
the channels 25 through 47 of the card, obtaining the settings
-------------------------------------------------------------------------------
--- SPECIAL NOTE ON TIME ------------------------------------------------------
-
+
Those using SIP phones should be aware that Asterisk is sensitive to
large jumps in time. Manually changing the system time using date(1)
(or other similar commands) may cause SIP registrations and other
The purpose of this document is to define best practices when working with
Asterisk in order to minimize possible security breaches and to provide tried
-examples in field deployments. This is a living document and is subject to
+examples in field deployments. This is a living document and is subject to
change over time as best practices are defined.
--------
Sections
--------
-* Filtering Data:
+* Filtering Data:
How to protect yourself from redial attacks
-* Proper Device Naming:
+* Proper Device Naming:
Why to not use numbered extensions for devices
-* Secure Passwords:
+* Secure Passwords:
Secure passwords limit your risk to brute force attacks
-* Reducing Pattern Match Typos:
+* Reducing Pattern Match Typos:
Using the 'same' prefix, or using Goto()
* Manager Class Authorizations:
Filtering Data
==============
-In the Asterisk dialplan, several channel variables contain data potentially
-supplied by outside sources. This could lead to a potential security concern
-where those outside sources may send cleverly crafted strings of data which
+In the Asterisk dialplan, several channel variables contain data potentially
+supplied by outside sources. This could lead to a potential security concern
+where those outside sources may send cleverly crafted strings of data which
could be utilized, e.g. to place calls to unexpected locations.
An example of this can be found in the use of pattern matching and the ${EXTEN}
variable, so it is important to be aware of where the data you're using is
coming from.
-For example, this common dialplan takes 2 or more characters of data, starting
+For example, this common dialplan takes 2 or more characters of data, starting
with a number 0-9, and then accepts any additional information supplied by the
request.
[NOTE: We use SIP in this example, but is not limited to SIP only; protocols
such as Jabber/XMPP or IAX2 are also susceptible to the same sort of
injection problem.]
-
+
[incoming]
exten => _X.,1,Verbose(2,Incoming call to extension ${EXTEN})
SIP/500 and is then used by the Dial() application to place a call), someone
could potentially send a string like "500&SIP/itsp/14165551212".
-The string "500&SIP/itsp/14165551212" would then be contained within the
+The string "500&SIP/itsp/14165551212" would then be contained within the
${EXTEN} channel variable, which is then utilized by the Dial() application in
our example, thereby giving you the dialplan line of:
-----------------------
The simple way to mitigate this problem is with a strict pattern match that does
-not utilize the period (.) or bang (!) characters to match on one-or-more
+not utilize the period (.) or bang (!) characters to match on one-or-more
characters or zero-or-more characters (respectively). To fine tune our example
to only accept three digit extensions, we could change our pattern match to
be:
external source. Lets take a look at how we can use FILTER() to control what
data we allow.
-Using our previous example to accept any string length of 2 or more characters,
-starting with a number of zero through nine, we can use FILTER() to limit what
+Using our previous example to accept any string length of 2 or more characters,
+starting with a number of zero through nine, we can use FILTER() to limit what
we will accept to just numbers. Our example would then change to something like:
[incoming]
Secure Passwords
================
-Secure passwords are necessary in many (if not all) environments, and Asterisk
+Secure passwords are necessary in many (if not all) environments, and Asterisk
is certainly no exception, especially when it comes to expensive long distance
calls that could potentially cost your company hundreds or thousands of dollars
on an expensive monthly phone bill, with little to no recourse to fight the
charges.
Whenever you are positioned to add a password to your system, whether that is
-for a device configuration, a database connection, or any other secure
+for a device configuration, a database connection, or any other secure
connection, be sure to use a secure password. A good example of a secure
password would be something like:
aE3%B8*$jk^G
Our password also contains 12 characters with a mixture of upper and
-lower case characters, numbers, and symbols. Because these passwords are likely
+lower case characters, numbers, and symbols. Because these passwords are likely
to only be entered once, or loaded via a configuration file, there is
no need to create simple passwords, even in testing. Some of the holes found in
production systems used for exploitations involve finding the one test extension
distribution of Asterisk.
Even though Asterisk is released as open source under the terms of the
-GPLv2 (see LICENSE for details), no core functionality in Asterisk has any
+GPLv2 (see LICENSE for details), no core functionality in Asterisk has any
dependencies on libraries that are licensed under the GPL. One reason a module
may be in the add-ons category is that it may have a GPL dependency. Since
these dependencies are not compatible with dual licensing of Asterisk, the
-dependant modules are set aside to make it clear that they may not be used
-with commercial versions of Asterisk, unless other licensing arrangements are
+dependant modules are set aside to make it clear that they may not be used
+with commercial versions of Asterisk, unless other licensing arrangements are
made with the copyright holders of those dependencies.
Another reason that modules may be set aside is that there may be
additional restrictions on the usage of the code imposed by the license or
related patents. The MySQL and MP3 modules are examples of this.
-
+
If you have any questions, contact your lawyer.
===============================================================================
}
if (cur->rtp) {
+ ast_rtp_instance_stop(cur->rtp);
ast_rtp_instance_destroy(cur->rtp);
cur->rtp = NULL;
}
i += pq931Msg->causeIE->length;
}
- /*Add progress indicator IE
- if(pq931Msg->messageType == Q931AlertingMsg || pq931Msg->messageType == Q931CallProceedingMsg)
+ /* Add progress indicator IE */
+ if(pq931Msg->messageType == Q931AlertingMsg || pq931Msg->messageType == Q931ProgressMsg)
{
msgbuf[i++] = Q931ProgressIndicatorIE;
msgbuf[i++] = 2; //Length is 2 octet
msgbuf[i++] = 0x80; //PI=8
msgbuf[i++] = 0x88;
- }*/
+ }
/*Add display ie. for all but Status message as per ASTERISK-18748 */
if(!ooUtilsIsStrEmpty(call->ourCallerId) && (pq931Msg->messageType != Q931StatusMsg))
static int chan_pjsip_hangup(struct ast_channel *ast)
{
struct ast_sip_channel_pvt *channel = ast_channel_tech_pvt(ast);
- struct chan_pjsip_pvt *pvt = channel->pvt;
- int cause = hangup_cause2sip(ast_channel_hangupcause(channel->session->channel));
- struct hangup_data *h_data = hangup_data_alloc(cause, ast);
+ struct chan_pjsip_pvt *pvt;
+ int cause;
+ struct hangup_data *h_data;
+
+ if (!channel || !channel->session) {
+ return -1;
+ }
+
+ pvt = channel->pvt;
+ cause = hangup_cause2sip(ast_channel_hangupcause(channel->session->channel));
+ h_data = hangup_data_alloc(cause, ast);
if (!h_data) {
goto failure;
struct ast_channel *chan;
chan = stuff;
+ ast_channel_hangupcause_set(chan, AST_CAUSE_NORMAL_CLEARING);
if (ast_pickup_call(chan)) {
ast_channel_hangupcause_set(chan, AST_CAUSE_CALL_REJECTED);
- } else {
- ast_channel_hangupcause_set(chan, AST_CAUSE_NORMAL_CLEARING);
}
ast_hangup(chan);
ast_channel_unref(chan);
<literal>type</literal> parameter must be provided. It specifies
which signalling parameter to read.</para>
<enumlist>
+ <enum name="call-id">
+ <para>The SIP call-id.</para>
+ </enum>
<enum name="secure">
<para>Whether or not the signalling uses a secure transport.</para>
<enumlist>
if (ast_strlen_zero(type)) {
ast_log(LOG_WARNING, "You must supply a type field for 'pjsip' information\n");
return -1;
+ } else if (!strcmp(type, "call-id")) {
+ snprintf(buf, buflen, "%.*s", (int) pj_strlen(&dlg->call_id->id), pj_strbuf(&dlg->call_id->id));
} else if (!strcmp(type, "secure")) {
#ifdef HAVE_PJSIP_GET_DEST_INFO
pjsip_host_info dest;
--- /dev/null
+"""add default_from_user
+
+Revision ID: 154177371065
+Revises: 26f10cadc157
+Create Date: 2015-09-04 14:13:59.195013
+
+"""
+
+# revision identifiers, used by Alembic.
+revision = '154177371065'
+down_revision = '26f10cadc157'
+
+from alembic import op
+import sqlalchemy as sa
+
+
+def upgrade():
+ op.add_column('ps_globals', sa.Column('default_from_user', sa.String(80)))
+
+
+def downgrade():
+ op.drop_column('ps_globals', 'default_from_user')
*/
char *ast_sip_get_endpoint_identifier_order(void);
+/*!
+ * \brief Retrieve the global default from user.
+ *
+ * This is the value placed in outbound requests' From header if there
+ * is no better option (such as an endpoint-configured from_user or
+ * caller ID number).
+ *
+ * \param[out] from_user The default from user
+ * \param size The buffer size of from_user
+ * \return nothing
+ */
+void ast_sip_get_default_from_user(char *from_user, size_t size);
+
/*! \brief Determines whether the res_pjsip module is loaded */
#define CHECK_PJSIP_MODULE_LOADED() \
do { \
*/
int ast_sip_failover_request(pjsip_tx_data *tdata);
+/*
+ * \brief Retrieve the local host address in IP form
+ *
+ * \param af The address family to retrieve
+ * \param addr A place to store the local host address
+ *
+ * \retval 0 success
+ * \retval -1 failure
+ *
+ * \since 13.6.0
+ */
+int ast_sip_get_host_ip(int af, pj_sockaddr *addr);
+
+/*!
+ * \brief Retrieve the local host address in string form
+ *
+ * \param af The address family to retrieve
+ *
+ * \retval non-NULL success
+ * \retval NULL failure
+ *
+ * \since 13.6.0
+ *
+ * \note An empty string may be returned if the address family is valid but no local address exists
+ */
+const char *ast_sip_get_host_ip_string(int af);
+
#endif /* _RES_PJSIP_H */
int (*apply_negotiated_sdp_stream)(struct ast_sip_session *session, struct ast_sip_session_media *session_media, const struct pjmedia_sdp_session *local, const struct pjmedia_sdp_media *local_stream,
const struct pjmedia_sdp_session *remote, const struct pjmedia_sdp_media *remote_stream);
/*!
+ * \brief Stop a session_media created by this handler but do not destroy resources
+ * \param session The session for which media is being stopped
+ * \param session_media The media to destroy
+ */
+ void (*stream_stop)(struct ast_sip_session_media *session_media);
+ /*!
* \brief Destroy a session_media created by this handler
* \param session The session for which media is being destroyed
* \param session_media The media to destroy
default:
break;
}
+
+ /* While invoking an action it is possible for the channel to be hung up. So
+ * that the bridge respects this we check here and if hung up kick it out.
+ */
+ if (bridge_channel->chan && ast_check_hangup_locked(bridge_channel->chan)) {
+ ast_bridge_channel_kick(bridge_channel, 0);
+ }
}
/*!
struct ast_endpoint_snapshot *ast_endpoint_snapshot_create(
struct ast_endpoint *endpoint)
{
- RAII_VAR(struct ast_endpoint_snapshot *, snapshot, NULL, ao2_cleanup);
+ struct ast_endpoint_snapshot *snapshot;
int channel_count;
struct ao2_iterator i;
void *obj;
}
ao2_iterator_destroy(&i);
- ao2_ref(snapshot, +1);
return snapshot;
}
continue;
}
watchers = ao2_container_count(hint->callbacks);
- sprintf(buf, "%s@%s",
+ snprintf(buf, sizeof(buf), "%s@%s",
ast_get_extension_name(hint->exten),
ast_get_context_name(ast_get_extension_context(hint->exten)));
/* Wait for executing task to complete so that caller of ast_sched_del() does not
* free memory out from under the task.
*/
- ast_cond_wait(&s->cond, &con->lock);
+ while (con->currently_executing && (id == con->currently_executing->id)) {
+ ast_cond_wait(&s->cond, &con->lock);
+ }
/* Do not sched_release() here because ast_sched_runq() will do it */
}
return NULL;
}
+ /* To prevent another thread from finding and getting a reference to this
+ * taskprocessor we hold the singletons lock. If we didn't do this then
+ * they may acquire it and find that the listener has been shut down.
+ */
+ ao2_lock(tps_singletons);
+
if (ao2_ref(tps, -1) > 3) {
+ ao2_unlock(tps_singletons);
return NULL;
}
+
/* If we're down to 3 references, then those must be:
* 1. The reference we just got rid of
* 2. The container
* 3. The listener
*/
- ao2_unlink(tps_singletons, tps);
+ ao2_unlink_flags(tps_singletons, tps, OBJ_NOLOCK);
+ ao2_unlock(tps_singletons);
+
listener_shutdown(tps->listener);
return NULL;
}
pthread_attr_init(attr);
}
-#ifdef __linux__
- /* On Linux, pthread_attr_init() defaults to PTHREAD_EXPLICIT_SCHED,
+#if defined(__linux__) || defined(__FreeBSD__)
+ /* On Linux and FreeBSD , pthread_attr_init() defaults to PTHREAD_EXPLICIT_SCHED,
which is kind of useless. Change this here to
PTHREAD_INHERIT_SCHED; that way the -p option to set realtime
priority will propagate down to new threads by default.
return pa_data;
}
+/*! \internal
+ * \brief Gathers inheritable channel variables from a channel by name.
+ *
+ * \param oh outgoing helper struct we are bestowing inheritable variables to
+ * \param channel_id name or uniqueID of the channel to inherit variables from
+ *
+ * \return Nothing
+ */
+static void inherit_channel_vars_from_id(struct outgoing_helper *oh, const char *channel_id)
+{
+ struct ast_channel *chan = ast_channel_get_by_name(channel_id);
+ struct ast_var_t *current;
+ struct ast_variable *newvar;
+ const char *varname;
+ int vartype;
+
+
+ if (!chan) {
+ /* Already gone */
+ return;
+ }
+
+ ast_channel_lock(chan);
+
+ AST_LIST_TRAVERSE(ast_channel_varshead((struct ast_channel *) chan), current, entries) {
+ varname = ast_var_full_name(current);
+ if (!varname) {
+ continue;
+ }
+
+ vartype = 0;
+ if (varname[0] == '_') {
+ vartype = 1;
+ if (varname[1] == '_') {
+ vartype = 2;
+ }
+ }
+
+ switch (vartype) {
+ case 1:
+ newvar = ast_variable_new(&varname[1], ast_var_value(current), "");
+ break;
+ case 2:
+ newvar = ast_variable_new(varname, ast_var_value(current), "");
+ break;
+ default:
+ continue;
+ }
+ if (newvar) {
+ ast_debug(1, "Inheriting variable %s from %s.\n",
+ newvar->name, ast_channel_name(chan));
+ if (oh->vars) {
+ newvar->next = oh->vars;
+ oh->vars = newvar;
+ }
+ }
+ }
+
+ ast_channel_unlock(chan);
+ ast_channel_cleanup(chan);
+}
+
static void announce_to_dial(char *dial_string, char *announce_string, int parkingspace, struct ast_channel_snapshot *parkee_snapshot)
{
struct ast_channel *dchan;
snprintf(buf, sizeof(buf), "%d", parkingspace);
oh.vars = ast_variable_new("_PARKEDAT", buf, "");
+
+ inherit_channel_vars_from_id(&oh, parkee_snapshot->uniqueid);
+
dchan = __ast_request_and_dial(dial_tech, cap_slin, NULL, NULL, dial_string, 30000,
&outstate,
parkee_snapshot->caller_number,
Identifier names are usually derived from and can be found in the endpoint
identifier module itself (res_pjsip_endpoint_identifier_*)</synopsis>
</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>
</configObject>
</configFile>
</configInfo>
static struct ast_threadpool *sip_threadpool;
+/*! Local host address for IPv4 */
+static pj_sockaddr host_ip_ipv4;
+
+/*! Local host address for IPv4 (string form) */
+static char host_ip_ipv4_string[PJ_INET6_ADDRSTRLEN + 2];
+
+/*! Local host address for IPv6 */
+static pj_sockaddr host_ip_ipv6;
+
+/*! Local host address for IPv6 (string form) */
+static char host_ip_ipv6_string[PJ_INET6_ADDRSTRLEN + 2];
+
static int register_service_noref(void *data)
{
pjsip_module **module = data;
pjsip_sip_uri *sip_uri;
pjsip_transport_type_e type = PJSIP_TRANSPORT_UNSPECIFIED;
int local_port;
- char uuid_str[AST_UUID_STR_LEN];
+ char default_user[PJSIP_MAX_URL_SIZE];
if (ast_strlen_zero(user)) {
- user = ast_uuid_generate_str(uuid_str, sizeof(uuid_str));
+ ast_sip_get_default_from_user(default_user, sizeof(default_user));
+ user = default_user;
}
/* Parse the provided target URI so we can determine what transport it will end up using */
pjsip_transaction *tsx;
if (pjsip_tsx_create_uas(NULL, rdata, &tsx) != PJ_SUCCESS) {
+ struct ast_sip_contact *contact;
+
+ /* ast_sip_create_response bumps the refcount of the contact and adds it to the tdata.
+ * We'll leak that reference if we don't get rid of it here.
+ */
+ contact = ast_sip_mod_data_get(tdata->mod_data, supplement_module.id, MOD_DATA_CONTACT);
+ ao2_cleanup(contact);
+ ast_sip_mod_data_set(tdata->pool, tdata->mod_data, supplement_module.id, MOD_DATA_CONTACT, NULL);
pjsip_tx_data_dec_ref(tdata);
return -1;
}
return res;
}
+int ast_sip_get_host_ip(int af, pj_sockaddr *addr)
+{
+ if (af == pj_AF_INET() && !ast_strlen_zero(host_ip_ipv4_string)) {
+ pj_sockaddr_copy_addr(addr, &host_ip_ipv4);
+ return 0;
+ } else if (af == pj_AF_INET6() && !ast_strlen_zero(host_ip_ipv6_string)) {
+ pj_sockaddr_copy_addr(addr, &host_ip_ipv6);
+ return 0;
+ }
+
+ return -1;
+}
+
+const char *ast_sip_get_host_ip_string(int af)
+{
+ if (af == pj_AF_INET()) {
+ return host_ip_ipv4_string;
+ } else if (af == pj_AF_INET6()) {
+ return host_ip_ipv6_string;
+ }
+
+ return NULL;
+}
+
static void remove_request_headers(pjsip_endpoint *endpt)
{
const pjsip_hdr *request_headers = pjsip_endpt_get_request_headers(endpt);
return AST_MODULE_LOAD_DECLINE;
}
+ if (!pj_gethostip(pj_AF_INET(), &host_ip_ipv4)) {
+ pj_sockaddr_print(&host_ip_ipv4, host_ip_ipv4_string, sizeof(host_ip_ipv4_string), 2);
+ ast_verb(3, "Local IPv4 address determined to be: %s\n", host_ip_ipv4_string);
+ }
+
+ if (!pj_gethostip(pj_AF_INET6(), &host_ip_ipv6)) {
+ pj_sockaddr_print(&host_ip_ipv6, host_ip_ipv6_string, sizeof(host_ip_ipv6_string), 2);
+ ast_verb(3, "Local IPv6 address determined to be: %s\n", host_ip_ipv6_string);
+ }
+
if (ast_sip_initialize_system()) {
ast_log(LOG_ERROR, "Failed to initialize SIP 'system' configuration section. Aborting load\n");
pj_pool_release(memory_pool);
#define DEFAULT_DEBUG "no"
#define DEFAULT_ENDPOINT_IDENTIFIER_ORDER "ip,username,anonymous"
#define DEFAULT_MAX_INITIAL_QUALIFY_TIME 0
+#define DEFAULT_FROM_USER "asterisk"
static char default_useragent[256];
AST_STRING_FIELD(debug);
/*! Order by which endpoint identifiers are checked (comma separated list) */
AST_STRING_FIELD(endpoint_identifier_order);
+ /*! User name to place in From header if there is no better option */
+ AST_STRING_FIELD(default_from_user);
);
/* Value to put in Max-Forwards header */
unsigned int max_forwards;
return time;
}
+void ast_sip_get_default_from_user(char *from_user, size_t size)
+{
+ struct global_config *cfg;
+
+ cfg = get_global_cfg();
+ if (!cfg) {
+ ast_copy_string(from_user, DEFAULT_FROM_USER, size);
+ } else {
+ ast_copy_string(from_user, cfg->default_from_user, size);
+ ao2_ref(cfg, -1);
+ }
+}
+
/*!
* \internal
* \brief Observer to set default global object if none exist.
ast_sorcery_object_field_register(sorcery, "global", "max_initial_qualify_time",
__stringify(DEFAULT_MAX_INITIAL_QUALIFY_TIME),
OPT_UINT_T, 0, FLDSET(struct global_config, max_initial_qualify_time));
+ ast_sorcery_object_field_register(sorcery, "global", "default_from_user", DEFAULT_FROM_USER,
+ OPT_STRINGFIELD_T, 0, STRFLDSET(struct global_config, default_from_user));
if (ast_sorcery_instance_observer_add(sorcery, &observer_callbacks_global)) {
return -1;
return aor;
}
+/*! \brief Internal callback function which destroys the specified contact */
+static int destroy_contact(void *obj, void *arg, int flags)
+{
+ struct ast_sip_contact *contact = obj;
+
+ ast_sip_location_delete_contact(contact);
+
+ return CMP_MATCH;
+}
+
+static void aor_deleted_observer(const void *object)
+{
+ const char *aor_id = ast_sorcery_object_get_id(object);
+ /* Give enough space for ^ at the beginning and ;@ at the end, since that is our object naming scheme */
+ char regex[strlen(aor_id) + 4];
+ struct ao2_container *contacts;
+
+ snprintf(regex, sizeof(regex), "^%s;@", aor_id);
+
+ if (!(contacts = ast_sorcery_retrieve_by_regex(ast_sip_get_sorcery(), "contact", regex))) {
+ return;
+ }
+
+ /* Destroy any contacts that may still exist that were made for this AoR */
+ ao2_callback(contacts, OBJ_NODATA | OBJ_MULTIPLE | OBJ_UNLINK, destroy_contact, NULL);
+
+ ao2_ref(contacts, -1);
+}
+
+/*! \brief Observer for contacts so state can be updated on respective endpoints */
+static const struct ast_sorcery_observer aor_observer = {
+ .deleted = aor_deleted_observer,
+};
+
+
/*! \brief Destructor for contact */
static void contact_destroy(void *obj)
{
return 0;
}
-/*! \brief Simple callback function which returns immediately, used to grab the first contact of an AOR */
-static int contact_find_first(void *obj, void *arg, int flags)
-{
- return CMP_MATCH | CMP_STOP;
-}
-
struct ast_sip_contact *ast_sip_location_retrieve_first_aor_contact(const struct ast_sip_aor *aor)
{
RAII_VAR(struct ao2_container *, contacts, NULL, ao2_cleanup);
return NULL;
}
- contact = ao2_callback(contacts, 0, contact_find_first, NULL);
+ /* Get the first AOR contact in the container. */
+ contact = ao2_callback(contacts, 0, NULL, NULL);
return contact;
}
{
char name[MAX_OBJECT_FIELD * 2 + 3];
RAII_VAR(struct ast_sip_contact *, contact, NULL, ao2_cleanup);
+ char hash[33];
- snprintf(name, sizeof(name), "%s;@%s", ast_sorcery_object_get_id(aor), uri);
+ ast_md5_hash(hash, uri);
+ snprintf(name, sizeof(name), "%s;@%s", ast_sorcery_object_get_id(aor), hash);
if (!(contact = ast_sorcery_alloc(ast_sip_get_sorcery(), "contact", name))) {
return -1;
return -1;
}
+ ast_sorcery_observer_add(sorcery, "aor", &aor_observer);
+
ast_sorcery_object_field_register(sorcery, "contact", "type", "", OPT_NOOP_T, 0, 0);
ast_sorcery_object_field_register(sorcery, "contact", "uri", "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct ast_sip_contact, uri));
ast_sorcery_object_field_register(sorcery, "contact", "path", "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct ast_sip_contact, path));
int ast_sip_destroy_sorcery_location(void)
{
+ ast_sorcery_observer_remove(ast_sip_get_sorcery(), "aor", &aor_observer);
ast_cli_unregister_multiple(cli_commands, ARRAY_LEN(cli_commands));
ast_sip_unregister_cli_formatter(contact_formatter);
ast_sip_unregister_cli_formatter(aor_formatter);
}
while ((val = strsep(&auth_names, ","))) {
+ if (ast_strlen_zero(val)) {
+ continue;
+ }
+
val = ast_strdup(val);
if (!val) {
goto failure;
#include "asterisk/res_pjsip.h"
#include "asterisk/module.h"
-/*! \brief Local host address for IPv4 */
-static char host_ipv4[PJ_INET_ADDRSTRLEN + 2];
-
-/*! \brief Local host address for IPv6 */
-static char host_ipv6[PJ_INET6_ADDRSTRLEN + 2];
-
/*! \brief Helper function which returns a UDP transport bound to the given address and port */
static pjsip_transport *multihomed_get_udp_transport(pj_str_t *address, int port)
{
}
/* If the host address is used in the SDP replace it with the address of what this is going out on */
- if ((!pj_strcmp2(&sdp->conn->addr_type, "IP4") && !pj_strcmp2(&sdp->conn->addr, host_ipv4)) ||
- (!pj_strcmp2(&sdp->conn->addr_type, "IP6") && !pj_strcmp2(&sdp->conn->addr, host_ipv6))) {
+ if ((!pj_strcmp2(&sdp->conn->addr_type, "IP4") && !pj_strcmp2(&sdp->conn->addr,
+ ast_sip_get_host_ip_string(pj_AF_INET()))) ||
+ (!pj_strcmp2(&sdp->conn->addr_type, "IP6") && !pj_strcmp2(&sdp->conn->addr,
+ ast_sip_get_host_ip_string(pj_AF_INET6())))) {
return 1;
}
static int load_module(void)
{
char hostname[MAXHOSTNAMELEN] = "";
- pj_sockaddr addr;
CHECK_PJSIP_MODULE_LOADED();
hostname);
}
- if (!pj_gethostip(pj_AF_INET(), &addr)) {
- pj_sockaddr_print(&addr, host_ipv4, sizeof(host_ipv4), 2);
- ast_verb(3, "Local IPv4 address determined to be: %s\n", host_ipv4);
- }
-
- if (!pj_gethostip(pj_AF_INET6(), &addr)) {
- pj_sockaddr_print(&addr, host_ipv6, sizeof(host_ipv6), 2);
- ast_verb(3, "Local IPv6 address determined to be: %s\n", host_ipv6);
- }
-
if (ast_sip_register_service(&multihomed_module)) {
ast_log(LOG_ERROR, "Could not register multihomed module for incoming and outgoing requests\n");
return AST_MODULE_LOAD_FAILURE;
expires = expires_hdr ? expires_hdr->ivalue : DEFAULT_PUBLISH_EXPIRES;
sub_tree->persistence->expires = ast_tvadd(ast_tvnow(), ast_samp2tv(expires, 1));
- pjsip_msg_print(rdata->msg_info.msg, sub_tree->persistence->packet,
- sizeof(sub_tree->persistence->packet));
+ /* When receiving a packet on an streaming transport, it's possible to receive more than one SIP
+ * message at a time into the rdata->pkt_info.packet buffer. However, the rdata->msg_info.msg_buf
+ * will always point to the proper SIP message that is to be processed. When updating subscription
+ * persistence that is pulled from persistent storage, though, the rdata->pkt_info.packet will
+ * only ever have a single SIP message on it, and so we base persistence on that.
+ */
+ if (rdata->msg_info.msg_buf) {
+ ast_copy_string(sub_tree->persistence->packet, rdata->msg_info.msg_buf,
+ MIN(sizeof(sub_tree->persistence->packet), rdata->msg_info.len));
+ } else {
+ ast_copy_string(sub_tree->persistence->packet, rdata->pkt_info.packet,
+ sizeof(sub_tree->persistence->packet));
+ }
ast_copy_string(sub_tree->persistence->src_name, rdata->pkt_info.src_name,
sizeof(sub_tree->persistence->src_name));
sub_tree->persistence->src_port = rdata->pkt_info.src_port;
tree->root = tree_node_alloc(resource, &visited, list->full_state);
if (!tree->root) {
+ AST_VECTOR_FREE(&visited);
return 500;
}
return;
}
+ /* We notify subscription shutdown only on the tree leaves. */
if (sub->handler->subscription_shutdown) {
sub->handler->subscription_shutdown(sub);
}
sub_tree = allocate_subscription_tree(endpoint);
if (!sub_tree) {
- pjsip_endpt_respond_stateless(ast_sip_get_pjsip_endpoint(), rdata, 500, NULL, NULL, NULL);
+ *dlg_status = PJ_ENOMEM;
return NULL;
}
sub_tree->role = AST_SIP_NOTIFIER;
request_uri = pjsip_uri_get_uri(rdata.msg_info.msg->line.req.uri);
resource_size = pj_strlen(&request_uri->user) + 1;
- resource = alloca(resource_size);
+ resource = ast_alloca(resource_size);
ast_copy_pj_str(resource, &request_uri->user, resource_size);
/* Update the expiration header with the new expiration */
subscription_persistence_update(sub_tree, &rdata);
if (generate_initial_notify(sub_tree->root)) {
pjsip_evsub_terminate(sub_tree->evsub, PJ_TRUE);
+ } else {
+ send_notify(sub_tree, 1);
}
- send_notify(sub_tree, 1);
} else {
ast_sorcery_delete(ast_sip_get_sorcery(), persistence);
}
return pjsip_msg_find_hdr_by_name(msg, &name, NULL);
}
+/* XXX This function is not used. */
struct ast_sip_subscription *ast_sip_create_subscription(const struct ast_sip_subscription_handler *handler,
struct ast_sip_endpoint *endpoint, const char *resource)
{
return 0;
}
+ /* We notify subscription establishment only on the tree leaves. */
if (sub->handler->notifier->subscription_established(sub)) {
return -1;
}
request_uri_sip = pjsip_uri_get_uri(request_uri);
resource_size = pj_strlen(&request_uri_sip->user) + 1;
- resource = alloca(resource_size);
+ resource = ast_alloca(resource_size);
ast_copy_pj_str(resource, &request_uri_sip->user, resource_size);
expires_header = pjsip_msg_find_hdr(rdata->msg_info.msg, PJSIP_H_EXPIRES, rdata->msg_info.msg->hdr.next);
request_uri_sip = pjsip_uri_get_uri(request_uri);
resource_size = pj_strlen(&request_uri_sip->user) + 1;
- resource_name = alloca(resource_size);
+ resource_name = ast_alloca(resource_size);
ast_copy_pj_str(resource_name, &request_uri_sip->user, resource_size);
resource = ast_sorcery_retrieve_by_id(ast_sip_get_sorcery(), "inbound-publication", resource_name);
AST_LIST_INSERT_HEAD(&body_generators, generator, list);
AST_RWLIST_UNLOCK(&body_generators);
- /* Lengths of type and subtype plus space for a slash. pj_str_t is not
- * null-terminated, so there is no need to allocate for the extra null
- * byte
- */
+ /* Lengths of type and subtype plus a slash. */
accept_len = strlen(generator->type) + strlen(generator->subtype) + 1;
- accept.ptr = alloca(accept_len);
- accept.slen = accept_len;
- /* Safe use of sprintf */
- sprintf(accept.ptr, "%s/%s", generator->type, generator->subtype);
+ /* Add room for null terminator that sprintf() will set. */
+ pj_strset(&accept, ast_alloca(accept_len + 1), accept_len);
+ sprintf((char *) pj_strbuf(&accept), "%s/%s", generator->type, generator->subtype);/* Safe */
+
pjsip_endpt_add_capability(ast_sip_get_pjsip_endpoint(), &pubsub_module,
PJSIP_H_ACCEPT, NULL, 1, &accept);
if (apply_list_configuration(sorcery)) {
ast_sip_unregister_service(&pubsub_module);
ast_sched_context_destroy(sched);
+ return AST_MODULE_LOAD_FAILURE;
}
ast_sorcery_apply_default(sorcery, "inbound-publication", "config", "pjsip.conf,criteria=type=inbound-publication");
*optimistic = 0;
+ if (!transport_str) {
+ return AST_SIP_MEDIA_TRANSPORT_INVALID;
+ }
if (strstr(transport_str, "UDP/TLS")) {
return AST_SIP_MEDIA_ENCRYPT_DTLS;
} else if (strstr(transport_str, "SAVP")) {
static const pj_str_t STR_SENDRECV = { "sendrecv", 8 };
static const pj_str_t STR_SENDONLY = { "sendonly", 8 };
pjmedia_sdp_media *media;
- char hostip[PJ_INET6_ADDRSTRLEN+2];
+ const char *hostip = NULL;
struct ast_sockaddr addr;
char tmp[512];
pj_str_t stmp;
/* Add connection level details */
if (direct_media_enabled) {
- ast_copy_string(hostip, ast_sockaddr_stringify_fmt(&session_media->direct_media_addr, AST_SOCKADDR_STR_ADDR), sizeof(hostip));
+ hostip = ast_sockaddr_stringify_fmt(&session_media->direct_media_addr, AST_SOCKADDR_STR_ADDR);
} else if (ast_strlen_zero(session->endpoint->media.address)) {
- pj_sockaddr localaddr;
-
- if (pj_gethostip(session->endpoint->media.rtp.ipv6 ? pj_AF_INET6() : pj_AF_INET(), &localaddr)) {
- return -1;
- }
- pj_sockaddr_print(&localaddr, hostip, sizeof(hostip), 2);
+ hostip = ast_sip_get_host_ip_string(session->endpoint->media.rtp.ipv6 ? pj_AF_INET6() : pj_AF_INET());
} else {
- ast_copy_string(hostip, session->endpoint->media.address, sizeof(hostip));
+ hostip = session->endpoint->media.address;
+ }
+
+ if (ast_strlen_zero(hostip)) {
+ ast_log(LOG_ERROR, "No local host IP available for stream %s\n", session_media->stream_type);
+ return -1;
}
media->conn->net_type = STR_IN;
* a NAT. This way there won't be an awkward delay before media starts flowing in some
* scenarios.
*/
+ AST_SCHED_DEL(sched, session_media->keepalive_sched_id);
session_media->keepalive_sched_id = ast_sched_add_variable(sched, 500, send_keepalive,
session_media, 1);
}
pj_strdup2(tdata->pool, &stream->conn->addr, transport->external_media_address);
}
+/*! \brief Function which stops the RTP instance */
+static void stream_stop(struct ast_sip_session_media *session_media)
+{
+ if (!session_media->rtp) {
+ return;
+ }
+
+ AST_SCHED_DEL(sched, session_media->keepalive_sched_id);
+ AST_SCHED_DEL(sched, session_media->timeout_sched_id);
+ ast_rtp_instance_stop(session_media->rtp);
+}
+
/*! \brief Function which destroys the RTP instance when session ends */
static void stream_destroy(struct ast_sip_session_media *session_media)
{
if (session_media->rtp) {
- AST_SCHED_DEL(sched, session_media->keepalive_sched_id);
- AST_SCHED_DEL(sched, session_media->timeout_sched_id);
- ast_rtp_instance_stop(session_media->rtp);
+ stream_stop(session_media);
ast_rtp_instance_destroy(session_media->rtp);
}
session_media->rtp = NULL;
.create_outgoing_sdp_stream = create_outgoing_sdp_stream,
.apply_negotiated_sdp_stream = apply_negotiated_sdp_stream,
.change_outgoing_sdp_stream_media_address = change_outgoing_sdp_stream_media_address,
+ .stream_stop = stream_stop,
.stream_destroy = stream_destroy,
};
.create_outgoing_sdp_stream = create_outgoing_sdp_stream,
.apply_negotiated_sdp_stream = apply_negotiated_sdp_stream,
.change_outgoing_sdp_stream_media_address = change_outgoing_sdp_stream_media_address,
+ .stream_stop = stream_stop,
.stream_destroy = stream_destroy,
};
}
}
}
+
+ if (session_media->handler && session_media->handler->stream_stop) {
+ ast_debug(1, "Stopping SDP media stream '%s' as it is not currently negotiated\n",
+ session_media->stream_type);
+ session_media->handler->stream_stop(session_media);
+ }
+
return CMP_MATCH;
}
session->contact = ao2_bump(contact);
session->inv_session = inv_session;
session->req_caps = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT);
+ if (!session->req_caps) {
+ /* Release the ref held by session->inv_session */
+ ao2_ref(session, -1);
+ return NULL;
+ }
if ((endpoint->dtmf == AST_SIP_DTMF_INBAND) || (endpoint->dtmf == AST_SIP_DTMF_AUTO)) {
dsp_features |= DSP_FEATURE_DIGIT_DETECT;
break;
case PJSIP_EVENT_RX_MSG:
cb = ast_sip_mod_data_get(tsx->mod_data, session_module.id, MOD_DATA_ON_RESPONSE);
- handle_incoming(session, e->body.tsx_state.src.rdata, e->type,
- AST_SIP_SESSION_AFTER_MEDIA);
+ /* As the PJSIP invite session implementation responds with a 200 OK before we have a
+ * chance to be invoked session supplements for BYE requests actually end up executing
+ * in the invite session state callback as well. To prevent session supplements from
+ * running on the BYE request again we explicitly squash invocation of them here.
+ */
+ if ((e->body.tsx_state.src.rdata->msg_info.msg->type != PJSIP_REQUEST_MSG) ||
+ (tsx->method.id != PJSIP_BYE_METHOD)) {
+ handle_incoming(session, e->body.tsx_state.src.rdata, e->type,
+ AST_SIP_SESSION_AFTER_MEDIA);
+ }
if (tsx->method.id == PJSIP_INVITE_METHOD) {
if (tsx->role == PJSIP_ROLE_UAC) {
if (tsx->state == PJSIP_TSX_STATE_COMPLETED) {
if (!ast_strlen_zero(session->endpoint->media.address)) {
pj_strdup2(inv->pool_prov, &local->origin.addr, session->endpoint->media.address);
} else {
- pj_sockaddr localaddr;
- char our_ip[PJ_INET6_ADDRSTRLEN];
-
- pj_gethostip(session->endpoint->media.rtp.ipv6 ? pj_AF_INET6() : pj_AF_INET(), &localaddr);
- pj_sockaddr_print(&localaddr, our_ip, sizeof(our_ip), 0);
- pj_strdup2(inv->pool_prov, &local->origin.addr, our_ip);
+ pj_strdup2(inv->pool_prov, &local->origin.addr, ast_sip_get_host_ip_string(session->endpoint->media.rtp.ipv6 ? pj_AF_INET6() : pj_AF_INET()));
}
}
static const pj_str_t STR_T38UDPREDUNDANCY = { "t38UDPRedundancy", 16 };
struct t38_state *state;
pjmedia_sdp_media *media;
- char hostip[PJ_INET6_ADDRSTRLEN+2];
+ const char *hostip = NULL;
struct ast_sockaddr addr;
char tmp[512];
pj_str_t stmp;
media->desc.transport = STR_UDPTL;
if (ast_strlen_zero(session->endpoint->media.address)) {
- pj_sockaddr localaddr;
-
- if (pj_gethostip(session->endpoint->media.t38.ipv6 ? pj_AF_INET6() : pj_AF_INET(), &localaddr)) {
- return -1;
- }
- pj_sockaddr_print(&localaddr, hostip, sizeof(hostip), 2);
+ hostip = ast_sip_get_host_ip_string(session->endpoint->media.t38.ipv6 ? pj_AF_INET6() : pj_AF_INET());
} else {
- ast_copy_string(hostip, session->endpoint->media.address, sizeof(hostip));
+ hostip = session->endpoint->media.address;
+ }
+
+ if (ast_strlen_zero(hostip)) {
+ return -1;
}
media->conn->net_type = STR_IN;
CHECK_PJSIP_MODULE_LOADED();
pjsip_transport_register_type(PJSIP_TRANSPORT_RELIABLE, "WS", 5060, &transport_type_ws);
- pjsip_transport_register_type(PJSIP_TRANSPORT_RELIABLE, "WSS", 5060, &transport_type_wss);
+ pjsip_transport_register_type(PJSIP_TRANSPORT_RELIABLE | PJSIP_TRANSPORT_SECURE, "WSS", 5060, &transport_type_wss);
if (ast_sip_register_service(&websocket_module) != PJ_SUCCESS) {
return AST_MODULE_LOAD_DECLINE;
pj_ice_sess_role role = rtp->ice->role;
int res;
+ ast_debug(3, "Resetting ICE for RTP instance '%p'\n", instance);
if (!rtp->ice->is_nominating && !rtp->ice->is_complete) {
+ ast_debug(3, "Nevermind. ICE isn't ready for a reset\n");
return 0;
}
+ ast_debug(3, "Stopping ICE for RTP instance '%p'\n", instance);
ast_rtp_ice_stop(instance);
+ ast_debug(3, "Recreating ICE session %s (%d) for RTP instance '%p'\n", ast_sockaddr_stringify(&rtp->ice_original_rtp_addr), rtp->ice_port, instance);
res = ice_create(instance, &rtp->ice_original_rtp_addr, rtp->ice_port, 1);
if (!res) {
/* Preserve the role that the old ICE session used */
/* Check for equivalence in the lists */
if (rtp->ice_active_remote_candidates &&
!ice_candidates_compare(rtp->ice_proposed_remote_candidates, rtp->ice_active_remote_candidates)) {
+ ast_debug(3, "Proposed == active candidates for RTP instance '%p'\n", instance);
ao2_cleanup(rtp->ice_proposed_remote_candidates);
rtp->ice_proposed_remote_candidates = NULL;
return;
}
if (candidate->id == AST_RTP_ICE_COMPONENT_RTP && rtp->turn_rtp) {
+ ast_debug(3, "RTP candidate %s (%p)\n", ast_sockaddr_stringify(&candidate->address), instance);
pj_turn_sock_set_perm(rtp->turn_rtp, 1, &candidates[cand_cnt].addr, 1);
} else if (candidate->id == AST_RTP_ICE_COMPONENT_RTCP && rtp->turn_rtcp) {
+ ast_debug(3, "RTCP candidate %s (%p)\n", ast_sockaddr_stringify(&candidate->address), instance);
pj_turn_sock_set_perm(rtp->turn_rtcp, 1, &candidates[cand_cnt].addr, 1);
}
ao2_iterator_destroy(&i);
- if (has_rtp && has_rtcp &&
- pj_ice_sess_create_check_list(rtp->ice, &ufrag, &passwd, ao2_container_count(
- rtp->ice_active_remote_candidates), &candidates[0]) == PJ_SUCCESS) {
- ast_test_suite_event_notify("ICECHECKLISTCREATE", "Result: SUCCESS");
- pj_ice_sess_start_check(rtp->ice);
- pj_timer_heap_poll(timer_heap, NULL);
- rtp->strict_rtp_state = STRICT_RTP_OPEN;
- return;
+ if (cand_cnt < ao2_container_count(rtp->ice_active_remote_candidates)) {
+ ast_log(LOG_WARNING, "Lost %d ICE candidates. Consider increasing PJ_ICE_MAX_CAND in PJSIP (%p)\n",
+ ao2_container_count(rtp->ice_active_remote_candidates) - cand_cnt, instance);
+ }
+
+ if (!has_rtp) {
+ ast_log(LOG_WARNING, "No RTP candidates; skipping ICE checklist (%p)\n", instance);
+ }
+
+ if (!has_rtcp) {
+ ast_log(LOG_WARNING, "No RTCP candidates; skipping ICE checklist (%p)\n", instance);
+ }
+
+ if (has_rtp && has_rtcp) {
+ pj_status_t res = pj_ice_sess_create_check_list(rtp->ice, &ufrag, &passwd, cand_cnt, &candidates[0]);
+ char reason[80];
+
+ if (res == PJ_SUCCESS) {
+ ast_debug(3, "Successfully created ICE checklist (%p)\n", instance);
+ ast_test_suite_event_notify("ICECHECKLISTCREATE", "Result: SUCCESS");
+ pj_ice_sess_start_check(rtp->ice);
+ pj_timer_heap_poll(timer_heap, NULL);
+ rtp->strict_rtp_state = STRICT_RTP_OPEN;
+ return;
+ }
+
+ pj_strerror(res, reason, sizeof(reason));
+ ast_log(LOG_WARNING, "Failed to create ICE session check list: %s (%p)\n", reason, instance);
}
ast_test_suite_event_notify("ICECHECKLISTCREATE", "Result: FAILURE");
/* even though create check list failed don't stop ice as
it might still work */
- ast_debug(1, "Failed to create ICE session check list\n");
/* however we do need to reset remote candidates since
this function may be re-entered */
ao2_ref(rtp->ice_active_remote_candidates, -1);
{
struct ast_rtp *rtp = ast_rtp_instance_get_data(instance);
+ ast_debug(3, "Set role to %s (%p)\n",
+ role == AST_RTP_ICE_ROLE_CONTROLLED ? "CONTROLLED" : "CONTROLLING", instance);
+
if (!rtp->ice) {
+ ast_log(LOG_WARNING, "Set role failed; no ice instance (%p)\n", instance);
return;
}
create_new_socket("RTP",
ast_sockaddr_is_ipv4(addr) ? AF_INET :
ast_sockaddr_is_ipv6(addr) ? AF_INET6 : -1)) < 0) {
- ast_debug(1, "Failed to create a new socket for RTP instance '%p'\n", instance);
+ ast_log(LOG_WARNING, "Failed to create a new socket for RTP instance '%p'\n", instance);
ast_free(rtp);
return -1;
}
#ifdef HAVE_PJPROJECT
/* Create an ICE session for ICE negotiation */
if (icesupport) {
+ ast_debug(3, "Creating ICE session %s (%d) for RTP instance '%p'\n", ast_sockaddr_stringify(addr), x, instance);
if (ice_create(instance, addr, x, 0)) {
ast_log(LOG_NOTICE, "Failed to start ICE session\n");
} else {