Newer
Older
}
static int find_sip_monitor_instance_by_subscription_pvt(void *obj, void *arg, int flags)
{
struct sip_monitor_instance *monitor_instance = obj;
return monitor_instance->subscription_pvt == arg ? CMP_MATCH | CMP_STOP : 0;
}
static int find_sip_monitor_instance_by_suspension_entry(void *obj, void *arg, int flags)
{
struct sip_monitor_instance *monitor_instance = obj;
return monitor_instance->suspension_entry == arg ? CMP_MATCH | CMP_STOP : 0;
}
static int sip_cc_monitor_request_cc(struct ast_cc_monitor *monitor, int *available_timer_id);
static int sip_cc_monitor_suspend(struct ast_cc_monitor *monitor);
static int sip_cc_monitor_unsuspend(struct ast_cc_monitor *monitor);
static int sip_cc_monitor_cancel_available_timer(struct ast_cc_monitor *monitor, int *sched_id);
static void sip_cc_monitor_destructor(void *private_data);
static struct ast_cc_monitor_callbacks sip_cc_monitor_callbacks = {
.type = "SIP",
.request_cc = sip_cc_monitor_request_cc,
.suspend = sip_cc_monitor_suspend,
.unsuspend = sip_cc_monitor_unsuspend,
.cancel_available_timer = sip_cc_monitor_cancel_available_timer,
.destructor = sip_cc_monitor_destructor,
};
static int sip_cc_monitor_request_cc(struct ast_cc_monitor *monitor, int *available_timer_id)
{
struct sip_monitor_instance *monitor_instance = monitor->private_data;
enum ast_cc_service_type service = monitor->service_offered;
int when;
if (!monitor_instance) {
return -1;
}
Jonathan Rose
committed
if (!(monitor_instance->subscription_pvt = sip_alloc(NULL, NULL, 0, SIP_SUBSCRIBE, NULL, NULL))) {
}
when = service == AST_CC_CCBS ? ast_get_ccbs_available_timer(monitor->interface->config_params) :
ast_get_ccnr_available_timer(monitor->interface->config_params);
sip_pvt_lock(monitor_instance->subscription_pvt);
ast_set_flag(&monitor_instance->subscription_pvt->flags[0], SIP_OUTGOING);
create_addr(monitor_instance->subscription_pvt, monitor_instance->peername, 0, 1);
ast_sip_ouraddrfor(&monitor_instance->subscription_pvt->sa, &monitor_instance->subscription_pvt->ourip, monitor_instance->subscription_pvt);
monitor_instance->subscription_pvt->subscribed = CALL_COMPLETION;
monitor_instance->subscription_pvt->expiry = when;
transmit_invite(monitor_instance->subscription_pvt, SIP_SUBSCRIBE, FALSE, 2, monitor_instance->subscribe_uri);
sip_pvt_unlock(monitor_instance->subscription_pvt);
ao2_t_ref(monitor, +1, "Adding a ref to the monitor for the scheduler");
*available_timer_id = ast_sched_add(sched, when * 1000, ast_cc_available_timer_expire, monitor);
return 0;
}
static int construct_pidf_body(enum sip_cc_publish_state state, char *pidf_body, size_t size, const char *presentity)
struct ast_str *body = ast_str_alloca(size);
char tuple_id[32];
generate_random_string(tuple_id, sizeof(tuple_id));
/* We'll make this a bare-bones pidf body. In state_notify_build_xml, the PIDF
* body gets a lot more extra junk that isn't necessary, so we'll leave it out here.
*/
ast_str_append(&body, 0, "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
/* XXX The entity attribute is currently set to the peer name associated with the
* dialog. This is because we currently only call this function for call-completion
* PUBLISH bodies. In such cases, the entity is completely disregarded. For other
* event packages, it may be crucial to have a proper URI as the presentity so this
* should be revisited as support is expanded.
*/
ast_str_append(&body, 0, "<presence xmlns=\"urn:ietf:params:xml:ns:pidf\" entity=\"%s\">\n", presentity);
ast_str_append(&body, 0, "<tuple id=\"%s\">\n", tuple_id);
ast_str_append(&body, 0, "<status><basic>%s</basic></status>\n", state == CC_OPEN ? "open" : "closed");
ast_str_append(&body, 0, "</tuple>\n");
ast_str_append(&body, 0, "</presence>\n");
ast_copy_string(pidf_body, ast_str_buffer(body), size);
return 0;
static int sip_cc_monitor_suspend(struct ast_cc_monitor *monitor)
struct sip_monitor_instance *monitor_instance = monitor->private_data;
enum sip_publish_type publish_type;
struct cc_epa_entry *cc_entry;
if (!monitor_instance) {
return -1;
}
if (!monitor_instance->suspension_entry) {
/* We haven't yet allocated the suspension entry, so let's give it a shot */
if (!(monitor_instance->suspension_entry = create_epa_entry("call-completion", monitor_instance->peername))) {
ast_log(LOG_WARNING, "Unable to allocate sip EPA entry for call-completion\n");
ao2_ref(monitor_instance, -1);
return -1;
}
if (!(cc_entry = ast_calloc(1, sizeof(*cc_entry)))) {
ast_log(LOG_WARNING, "Unable to allocate space for instance data of EPA entry for call-completion\n");
ao2_ref(monitor_instance, -1);
return -1;
}
cc_entry->core_id = monitor->core_id;
monitor_instance->suspension_entry->instance_data = cc_entry;
publish_type = SIP_PUBLISH_INITIAL;
} else {
publish_type = SIP_PUBLISH_MODIFY;
cc_entry = monitor_instance->suspension_entry->instance_data;
}
cc_entry->current_state = CC_CLOSED;
if (ast_strlen_zero(monitor_instance->notify_uri)) {
/* If we have no set notify_uri, then what this means is that we have
* not received a NOTIFY from this destination stating that he is
* currently available.
*
* This situation can arise when the core calls the suspend callbacks
* of multiple destinations. If one of the other destinations aside
* from this one notified Asterisk that he is available, then there
* is no reason to take any suspension action on this device. Rather,
* we should return now and if we receive a NOTIFY while monitoring
* is still "suspended" then we can immediately respond with the
* proper PUBLISH to let this endpoint know what is going on.
*/
return 0;
}
construct_pidf_body(CC_CLOSED, monitor_instance->suspension_entry->body, sizeof(monitor_instance->suspension_entry->body), monitor_instance->peername);
return transmit_publish(monitor_instance->suspension_entry, publish_type, monitor_instance->notify_uri);
}
static int sip_cc_monitor_unsuspend(struct ast_cc_monitor *monitor)
{
struct sip_monitor_instance *monitor_instance = monitor->private_data;
struct cc_epa_entry *cc_entry;
if (!monitor_instance) {
return -1;
}
ast_assert(monitor_instance->suspension_entry != NULL);
Kevin P. Fleming
committed
cc_entry = monitor_instance->suspension_entry->instance_data;
cc_entry->current_state = CC_OPEN;
if (ast_strlen_zero(monitor_instance->notify_uri)) {
/* This means we are being asked to unsuspend a call leg we never
* sent a PUBLISH on. As such, there is no reason to send another
* PUBLISH at this point either. We can just return instead.
*/
return 0;
}
construct_pidf_body(CC_OPEN, monitor_instance->suspension_entry->body, sizeof(monitor_instance->suspension_entry->body), monitor_instance->peername);
return transmit_publish(monitor_instance->suspension_entry, SIP_PUBLISH_MODIFY, monitor_instance->notify_uri);
}
static int sip_cc_monitor_cancel_available_timer(struct ast_cc_monitor *monitor, int *sched_id)
{
if (*sched_id != -1) {
AST_SCHED_DEL(sched, *sched_id);
ao2_t_ref(monitor, -1, "Removing scheduler's reference to the monitor");
}
return 0;
}
static void sip_cc_monitor_destructor(void *private_data)
{
struct sip_monitor_instance *monitor_instance = private_data;
ao2_unlink(sip_monitor_instances, monitor_instance);
ast_module_unref(ast_module_info->self);
}
static int sip_get_cc_information(struct sip_request *req, char *subscribe_uri, size_t size, enum ast_cc_service_type *service)
{
char *call_info = ast_strdupa(sip_get_header(req, "Call-Info"));
char *uri;
char *purpose;
char *service_str;
static const char cc_purpose[] = "purpose=call-completion";
static const int cc_purpose_len = sizeof(cc_purpose) - 1;
if (ast_strlen_zero(call_info)) {
/* No Call-Info present. Definitely no CC offer */
return -1;
}
uri = strsep(&call_info, ";");
while ((purpose = strsep(&call_info, ";"))) {
if (!strncmp(purpose, cc_purpose, cc_purpose_len)) {
break;
}
}
if (!purpose) {
/* We didn't find the appropriate purpose= parameter. Oh well */
return -1;
}
/* Okay, call-completion has been offered. Let's figure out what type of service this is */
while ((service_str = strsep(&call_info, ";"))) {
if (!strncmp(service_str, "m=", 2)) {
break;
}
if (!service_str) {
/* So they didn't offer a particular service, We'll just go with CCBS since it really
* doesn't matter anyway
*/
service_str = "BS";
} else {
/* We already determined that there is an "m=" so no need to check
* the result of this strsep
*/
strsep(&service_str, "=");
}
if ((*service = service_string_to_service_type(service_str)) == AST_CC_NONE) {
/* Invalid service offered */
return -1;
}
ast_copy_string(subscribe_uri, get_in_brackets(uri), size);
2234
2235
2236
2237
2238
2239
2240
2241
2242
2243
2244
2245
2246
2247
2248
2249
2250
2251
2252
2253
2254
2255
/*
* \brief Determine what, if any, CC has been offered and queue a CC frame if possible
*
* After taking care of some formalities to be sure that this call is eligible for CC,
* we first try to see if we can make use of native CC. We grab the information from
* the passed-in sip_request (which is always a response to an INVITE). If we can
* use native CC monitoring for the call, then so be it.
*
* If native cc monitoring is not possible or not supported, then we will instead attempt
* to use generic monitoring. Falling back to generic from a failed attempt at using native
* monitoring will only work if the monitor policy of the endpoint is "always"
*
* \param pvt The current dialog. Contains CC parameters for the endpoint
* \param req The response to the INVITE we want to inspect
* \param service The service to use if generic monitoring is to be used. For native
* monitoring, we get the service from the SIP response itself
*/
static void sip_handle_cc(struct sip_pvt *pvt, struct sip_request *req, enum ast_cc_service_type service)
{
enum ast_cc_monitor_policies monitor_policy = ast_get_cc_monitor_policy(pvt->cc_params);
int core_id;
char interface_name[AST_CHANNEL_NAME];
if (monitor_policy == AST_CC_MONITOR_NEVER) {
/* Don't bother, just return */
return;
if ((core_id = ast_cc_get_current_core_id(pvt->owner)) == -1) {
/* For some reason, CC is invalid, so don't try it! */
return;
}
ast_channel_get_device_name(pvt->owner, interface_name, sizeof(interface_name));
if (monitor_policy == AST_CC_MONITOR_ALWAYS || monitor_policy == AST_CC_MONITOR_NATIVE) {
char subscribe_uri[SIPBUFSIZE];
char device_name[AST_CHANNEL_NAME];
enum ast_cc_service_type offered_service;
struct sip_monitor_instance *monitor_instance;
if (sip_get_cc_information(req, subscribe_uri, sizeof(subscribe_uri), &offered_service)) {
/* If CC isn't being offered to us, or for some reason the CC offer is
* not formatted correctly, then it may still be possible to use generic
* call completion since the monitor policy may be "always"
*/
goto generic;
}
ast_channel_get_device_name(pvt->owner, device_name, sizeof(device_name));
if (!(monitor_instance = sip_monitor_instance_init(core_id, subscribe_uri, pvt->peername, device_name))) {
/* Same deal. We can try using generic still */
goto generic;
}
/* We bump the refcount of chan_sip because once we queue this frame, the CC core
* will have a reference to callbacks in this module. We decrement the module
* refcount once the monitor destructor is called
*/
ast_module_ref(ast_module_info->self);
ast_queue_cc_frame(pvt->owner, "SIP", pvt->dialstring, offered_service, monitor_instance);
ao2_ref(monitor_instance, -1);
return;
}
generic:
if (monitor_policy == AST_CC_MONITOR_GENERIC || monitor_policy == AST_CC_MONITOR_ALWAYS) {
ast_queue_cc_frame(pvt->owner, AST_CC_GENERIC_MONITOR_TYPE, interface_name, service, NULL);
}
/*! \brief Working TLS connection configuration */
static struct ast_tls_config sip_tls_cfg;
2305
2306
2307
2308
2309
2310
2311
2312
2313
2314
2315
2316
2317
2318
2319
2320
2321
2322
2323
2324
2325
2326
2327
2328
2329
2330
2331
2332
2333
/*! \brief Default TLS connection configuration */
static struct ast_tls_config default_tls_cfg;
/*! \brief The TCP server definition */
static struct ast_tcptls_session_args sip_tcp_desc = {
.accept_fd = -1,
.master = AST_PTHREADT_NULL,
.tls_cfg = NULL,
.poll_timeout = -1,
.name = "SIP TCP server",
.accept_fn = ast_tcptls_server_root,
.worker_fn = sip_tcp_worker_fn,
};
/*! \brief The TCP/TLS server definition */
static struct ast_tcptls_session_args sip_tls_desc = {
.accept_fd = -1,
.master = AST_PTHREADT_NULL,
.tls_cfg = &sip_tls_cfg,
.poll_timeout = -1,
.name = "SIP TLS server",
.accept_fn = ast_tcptls_server_root,
.worker_fn = sip_tcp_worker_fn,
};
/*! \brief Append to SIP dialog history
\return Always returns 0 */
#define append_history(p, event, fmt , args... ) append_history_full(p, "%-15s " fmt, event, ## args)
struct sip_pvt *dialog_ref_debug(struct sip_pvt *p, const char *tag, char *file, int line, const char *func)
if (p)
#ifdef REF_DEBUG
__ao2_ref_debug(p, 1, tag, file, line, func);
#else
ao2_ref(p, 1);
#endif
else
ast_log(LOG_ERROR, "Attempt to Ref a null pointer\n");
return p;
struct sip_pvt *dialog_unref_debug(struct sip_pvt *p, const char *tag, char *file, int line, const char *func)
if (p)
#ifdef REF_DEBUG
__ao2_ref_debug(p, -1, tag, file, line, func);
#else
ao2_ref(p, -1);
#endif
return NULL;
}
/*! \brief map from an integer value to a string.
* If no match is found, return errorstring
static const char *map_x_s(const struct _map_x_s *table, int x, const char *errorstring)
const struct _map_x_s *cur;
for (cur = table; cur->s; cur++) {
if (cur->x == x) {
/*! \brief map from a string to an integer value, case insensitive.
* If no match is found, return errorvalue.
static int map_s_x(const struct _map_x_s *table, const char *s, int errorvalue)
for (cur = table; cur->s; cur++) {
if (!strcasecmp(cur->s, s)) {
static enum AST_REDIRECTING_REASON sip_reason_str_to_code(const char *text)
{
enum AST_REDIRECTING_REASON ast = AST_REDIRECTING_REASON_UNKNOWN;
int i;
for (i = 0; i < ARRAY_LEN(sip_reason_table); ++i) {
if (!strcasecmp(text, sip_reason_table[i].text)) {
ast = sip_reason_table[i].code;
break;
Joshua Colp
committed
}
}
static const char *sip_reason_code_to_str(struct ast_party_redirecting_reason *reason, int *table_lookup)
2405
2406
2407
2408
2409
2410
2411
2412
2413
2414
2415
2416
2417
2418
2419
2420
2421
2422
2423
2424
2425
2426
2427
2428
2429
2430
2431
int code = reason->code;
/* If there's a specific string set, then we just
* use it.
*/
if (!ast_strlen_zero(reason->str)) {
/* If we care about whether this can be found in
* the table, then we need to check about that.
*/
if (table_lookup) {
/* If the string is literally "unknown" then don't bother with the lookup
* because it can lead to a false negative.
*/
if (!strcasecmp(reason->str, "unknown") ||
sip_reason_str_to_code(reason->str) != AST_REDIRECTING_REASON_UNKNOWN) {
*table_lookup = TRUE;
} else {
*table_lookup = FALSE;
}
}
return reason->str;
}
if (table_lookup) {
*table_lookup = TRUE;
}
if (code >= 0 && code < ARRAY_LEN(sip_reason_table)) {
return sip_reason_table[code].text;
Joshua Colp
committed
}
}
/*!
* \brief generic function for determining if a correct transport is being
* used to contact a peer
*
* this is done as a macro so that the "tmpl" var can be passed either a
* sip_request or a sip_peer
*/
#define check_request_transport(peer, tmpl) ({ \
int ret = 0; \
if (peer->socket.type == tmpl->socket.type) \
; \
else if (!(peer->transports & tmpl->socket.type)) {\
ast_log(LOG_ERROR, \
"'%s' is not a valid transport for '%s'. we only use '%s'! ending call.\n", \
sip_get_transport(tmpl->socket.type), peer->name, get_transport_list(peer->transports) \
} else if (peer->socket.type & AST_TRANSPORT_TLS) { \
ast_log(LOG_WARNING, \
"peer '%s' HAS NOT USED (OR SWITCHED TO) TLS in favor of '%s' (but this was allowed in sip.conf)!\n", \
peer->name, sip_get_transport(tmpl->socket.type) \
); \
} else { \
ast_debug(1, \
"peer '%s' has contacted us over %s even though we prefer %s.\n", \
peer->name, sip_get_transport(tmpl->socket.type), sip_get_transport(peer->socket.type) \
); \
}\
(ret); \
})
/*! \brief
* duplicate a list of channel variables, \return the copy.
*/
static struct ast_variable *copy_vars(struct ast_variable *src)
Olle Johansson
committed
{
struct ast_variable *res = NULL, *tmp, *v = NULL;
for (v = src ; v ; v = v->next) {
if ((tmp = ast_variable_new(v->name, v->value, v->file))) {
tmp->next = res;
res = tmp;
}
}
return res;
}
static void tcptls_packet_destructor(void *obj)
struct tcptls_packet *packet = obj;
Olle Johansson
committed
}
static void sip_tcptls_client_args_destructor(void *obj)
struct ast_tcptls_session_args *args = obj;
if (args->tls_cfg) {
ast_free(args->tls_cfg->certfile);
ast_free(args->tls_cfg->pvtfile);
ast_free(args->tls_cfg->cipher);
ast_free(args->tls_cfg->cafile);
ast_free(args->tls_cfg->capath);
ast_ssl_teardown(args->tls_cfg);
}
ast_free(args->tls_cfg);
ast_free((char *) args->name);
static void sip_threadinfo_destructor(void *obj)
struct sip_threadinfo *th = obj;
struct tcptls_packet *packet;
if (th->alert_pipe[1] > -1) {
close(th->alert_pipe[0]);
if (th->alert_pipe[1] > -1) {
close(th->alert_pipe[1]);
}
th->alert_pipe[0] = th->alert_pipe[1] = -1;
while ((packet = AST_LIST_REMOVE_HEAD(&th->packet_q, entry))) {
ao2_t_ref(packet, -1, "thread destruction, removing packet from frame queue");
if (th->tcptls_session) {
ao2_t_ref(th->tcptls_session, -1, "remove tcptls_session for sip_threadinfo object");
}
/*! \brief creates a sip_threadinfo object and links it into the threadt table. */
static struct sip_threadinfo *sip_threadinfo_create(struct ast_tcptls_session_instance *tcptls_session, int transport)
Joshua Colp
committed
if (!tcptls_session || !(th = ao2_alloc(sizeof(*th), sip_threadinfo_destructor))) {
Joshua Colp
committed
return NULL;
}
th->alert_pipe[0] = th->alert_pipe[1] = -1;
if (pipe(th->alert_pipe) == -1) {
ao2_t_ref(th, -1, "Failed to open alert pipe on sip_threadinfo");
ast_log(LOG_ERROR, "Could not create sip alert pipe in tcptls thread, error %s\n", strerror(errno));
return NULL;
ao2_t_ref(tcptls_session, +1, "tcptls_session ref for sip_threadinfo object");
th->tcptls_session = tcptls_session;
th->type = transport ? transport : (tcptls_session->ssl ? AST_TRANSPORT_TLS: AST_TRANSPORT_TCP);
ao2_t_link(threadt, th, "Adding new tcptls helper thread");
ao2_t_ref(th, -1, "Decrementing threadinfo ref from alloc, only table ref remains");
return th;
/*! \brief used to indicate to a tcptls thread that data is ready to be written */
static int sip_tcptls_write(struct ast_tcptls_session_instance *tcptls_session, const void *buf, size_t len)
int res = len;
struct sip_threadinfo *th = NULL;
struct tcptls_packet *packet = NULL;
struct sip_threadinfo tmp = {
.tcptls_session = tcptls_session,
};
enum sip_tcptls_alert alert = TCPTLS_ALERT_DATA;
if (!tcptls_session) {
return XMIT_ERROR;
Richard Mudgett
committed
ao2_lock(tcptls_session);
if ((tcptls_session->fd == -1) ||
!(th = ao2_t_find(threadt, &tmp, OBJ_POINTER, "ao2_find, getting sip_threadinfo in tcp helper thread")) ||
!(packet = ao2_alloc(sizeof(*packet), tcptls_packet_destructor)) ||
!(packet->data = ast_str_create(len))) {
goto tcptls_write_setup_error;
}
Russell Bryant
committed
/* goto tcptls_write_error should _NOT_ be used beyond this point */
ast_str_set(&packet->data, 0, "%s", (char *) buf);
packet->len = len;
Russell Bryant
committed
/* alert tcptls thread handler that there is a packet to be sent.
* must lock the thread info object to guarantee control of the
* packet queue */
ao2_lock(th);
if (write(th->alert_pipe[1], &alert, sizeof(alert)) == -1) {
ast_log(LOG_ERROR, "write() to alert pipe failed: %s\n", strerror(errno));
ao2_t_ref(packet, -1, "could not write to alert pipe, remove packet");
packet = NULL;
res = XMIT_ERROR;
} else { /* it is safe to queue the frame after issuing the alert when we hold the threadinfo lock */
AST_LIST_INSERT_TAIL(&th->packet_q, packet, entry);
}
Richard Mudgett
committed
ao2_unlock(tcptls_session);
ao2_t_ref(th, -1, "In sip_tcptls_write, unref threadinfo object after finding it");
return res;
tcptls_write_setup_error:
if (th) {
ao2_t_ref(th, -1, "In sip_tcptls_write, unref threadinfo obj, could not create packet");
if (packet) {
ao2_t_ref(packet, -1, "could not allocate packet's data");
}
Richard Mudgett
committed
ao2_unlock(tcptls_session);
/*! \brief SIP TCP connection handler */
static void *sip_tcp_worker_fn(void *data)
struct ast_tcptls_session_instance *tcptls_session = data;
return _sip_tcp_helper_thread(tcptls_session);
/*! \brief SIP WebSocket connection handler */
static void sip_websocket_callback(struct ast_websocket *session, struct ast_variable *parameters, struct ast_variable *headers)
{
int res;
if (ast_websocket_set_nonblock(session)) {
goto end;
}
if (ast_websocket_set_timeout(session, sip_cfg.websocket_write_timeout)) {
goto end;
}
while ((res = ast_wait_for_input(ast_websocket_fd(session), -1)) > 0) {
char *payload;
uint64_t payload_len;
enum ast_websocket_opcode opcode;
int fragmented;
if (ast_websocket_read(session, &payload, &payload_len, &opcode, &fragmented)) {
/* We err on the side of caution and terminate the session if any error occurs */
break;
}
if (opcode == AST_WEBSOCKET_OPCODE_TEXT || opcode == AST_WEBSOCKET_OPCODE_BINARY) {
struct sip_request req = { 0, };
if (!(req.data = ast_str_create(payload_len + 1))) {
goto end;
}
if (ast_str_set(&req.data, -1, "%s", payload) == AST_DYNSTR_BUILD_FAILED) {
deinit_req(&req);
goto end;
}
req.socket.fd = ast_websocket_fd(session);
set_socket_transport(&req.socket, ast_websocket_is_secure(session) ? AST_TRANSPORT_WSS : AST_TRANSPORT_WS);
req.socket.ws_session = session;
handle_request_do(&req, ast_websocket_remote_address(session));
deinit_req(&req);
} else if (opcode == AST_WEBSOCKET_OPCODE_CLOSE) {
break;
}
}
end:
ast_websocket_unref(session);
}
2673
2674
2675
2676
2677
2678
2679
2680
2681
2682
2683
2684
2685
2686
2687
2688
2689
2690
2691
2692
2693
2694
2695
2696
2697
/*! \brief Check if the authtimeout has expired.
* \param start the time when the session started
*
* \retval 0 the timeout has expired
* \retval -1 error
* \return the number of milliseconds until the timeout will expire
*/
static int sip_check_authtimeout(time_t start)
{
int timeout;
time_t now;
if(time(&now) == -1) {
ast_log(LOG_ERROR, "error executing time(): %s\n", strerror(errno));
return -1;
}
timeout = (authtimeout - (now - start)) * 1000;
if (timeout < 0) {
/* we have timed out */
return 0;
}
return timeout;
}
2698
2699
2700
2701
2702
2703
2704
2705
2706
2707
2708
2709
2710
2711
2712
2713
2714
2715
2716
2717
2718
2719
2720
2721
2722
2723
2724
2725
2726
2727
2728
2729
2730
2731
/*!
* \brief Indication of a TCP message's integrity
*/
enum message_integrity {
/*!
* The message has an error in it with
* regards to its Content-Length header
*/
MESSAGE_INVALID,
/*!
* The message is incomplete
*/
MESSAGE_FRAGMENT,
/*!
* The data contains a complete message
* plus a fragment of another.
*/
MESSAGE_FRAGMENT_COMPLETE,
/*!
* The message is complete
*/
MESSAGE_COMPLETE,
};
/*!
* \brief
* Get the content length from an unparsed SIP message
*
* \param message The unparsed SIP message headers
* \return The value of the Content-Length header or -1 if message is invalid
*/
static int read_raw_content_length(const char *message)
{
char *content_length_str;
int content_length = -1;
struct ast_str *msg_copy;
/* Using a ast_str because lws2sws takes one of those */
if (!(msg_copy = ast_str_create(strlen(message) + 1))) {
return -1;
}
ast_str_set(&msg_copy, 0, "%s", message);
if (sip_cfg.pedanticsipchecking) {
lws2sws(msg_copy);
}
msg = ast_str_buffer(msg_copy);
/* Let's find a Content-Length header */
if ((content_length_str = strcasestr(msg, "\nContent-Length:"))) {
content_length_str += sizeof("\nContent-Length:") - 1;
} else if ((content_length_str = strcasestr(msg, "\nl:"))) {
content_length_str += sizeof("\nl:") - 1;
} else {
/* RFC 3261 18.3
* "In the case of stream-oriented transports such as TCP, the Content-
* Length header field indicates the size of the body. The Content-
* Length header field MUST be used with stream oriented transports."
*/
/* Double-check that this is a complete header */
if (!strchr(content_length_str, '\n')) {
goto done;
if (sscanf(content_length_str, "%30d", &content_length) != 1) {
content_length = -1;
done:
ast_free(msg_copy);
return content_length;
}
/*!
* \brief Check that a message received over TCP is a full message
*
* This will take the information read in and then determine if
* 1) The message is a full SIP request
* 2) The message is a partial SIP request
* 3) The message contains a full SIP request along with another partial request
* \param data The unparsed incoming SIP message.
* \param request The resulting request with extra fragments removed.
* \param overflow If the message contains more than a full request, this is the remainder of the message
* \return The resulting integrity of the message
*/
static enum message_integrity check_message_integrity(struct ast_str **request, struct ast_str **overflow)
{
char *message = ast_str_buffer(*request);
char *body;
int content_length;
int message_len = ast_str_strlen(*request);
int body_len;
/* Important pieces to search for in a SIP request are \r\n\r\n. This
* marks either
* 1) The division between the headers and body
* 2) The end of the SIP request
*/
body = strstr(message, "\r\n\r\n");
if (!body) {
/* This is clearly a partial message since we haven't reached an end
* yet.
*/
return MESSAGE_FRAGMENT;
}
body += sizeof("\r\n\r\n") - 1;
body_len = message_len - (body - message);
body[-1] = '\0';
content_length = read_raw_content_length(message);
body[-1] = '\n';
if (content_length < 0) {
return MESSAGE_INVALID;
} else if (content_length == 0) {
/* We've definitely received an entire message. We need
* to check if there's also a fragment of another message
* in addition.
*/
if (body_len == 0) {
return MESSAGE_COMPLETE;
} else {
ast_str_append(overflow, 0, "%s", body);
ast_str_truncate(*request, message_len - body_len);
return MESSAGE_FRAGMENT_COMPLETE;
}
}
/* Positive content length. Let's see what sort of
* message body we're dealing with.
*/
if (body_len < content_length) {
/* We don't have the full message body yet */
return MESSAGE_FRAGMENT;
} else if (body_len > content_length) {
/* We have the full message plus a fragment of a further
* message
*/
ast_str_append(overflow, 0, "%s", body + content_length);
ast_str_truncate(*request, message_len - (body_len - content_length));
return MESSAGE_FRAGMENT_COMPLETE;
} else {
/* Yay! Full message with no extra content */
return MESSAGE_COMPLETE;
}
}
/*!
* \brief Read SIP request or response from a TCP/TLS connection
*
* \param req The request structure to be filled in
* \param tcptls_session The TCP/TLS connection from which to read
* \retval -1 Failed to read data
* \retval 0 Successfully read data
*/
static int sip_tcptls_read(struct sip_request *req, struct ast_tcptls_session_instance *tcptls_session,
int authenticated, time_t start)
{
enum message_integrity message_integrity = MESSAGE_FRAGMENT;
while (message_integrity == MESSAGE_FRAGMENT) {
size_t datalen;
if (ast_str_strlen(tcptls_session->overflow_buf) == 0) {
char readbuf[4097];
int timeout;
int res;
if (!tcptls_session->client && !authenticated) {
if ((timeout = sip_check_authtimeout(start)) < 0) {
return -1;
}
if (timeout == 0) {
ast_debug(2, "SIP TCP/TLS server timed out\n");
return -1;
}
} else {
timeout = -1;
}
res = ast_wait_for_input(tcptls_session->fd, timeout);
if (res < 0) {
ast_debug(2, "SIP TCP/TLS server :: ast_wait_for_input returned %d\n", res);
return -1;
} else if (res == 0) {
ast_debug(2, "SIP TCP/TLS server timed out\n");
return -1;
}
res = ast_tcptls_server_read(tcptls_session, readbuf, sizeof(readbuf) - 1);
if (errno == EAGAIN || errno == EINTR) {
continue;
}
ast_debug(2, "SIP TCP/TLS server error when receiving data\n");
return -1;
} else if (res == 0) {
ast_debug(2, "SIP TCP/TLS server has shut down\n");
return -1;
}
readbuf[res] = '\0';
ast_str_append(&req->data, 0, "%s", readbuf);
} else {
ast_str_append(&req->data, 0, "%s", ast_str_buffer(tcptls_session->overflow_buf));
ast_str_reset(tcptls_session->overflow_buf);
}
Matthew Jordan
committed
datalen = ast_str_strlen(req->data);
if (datalen > SIP_MAX_PACKET_SIZE) {
ast_log(LOG_WARNING, "Rejecting TCP/TLS packet from '%s' because way too large: %zu\n",
ast_sockaddr_stringify(&tcptls_session->remote_address), datalen);
return -1;
}
message_integrity = check_message_integrity(&req->data, &tcptls_session->overflow_buf);
}
return 0;
}
/*! \brief SIP TCP thread management function
This function reads from the socket, parses the packet into a request
*/
static void *_sip_tcp_helper_thread(struct ast_tcptls_session_instance *tcptls_session)
int res, timeout = -1, authenticated = 0, flags;
struct sip_request req = { 0, } , reqcpy = { 0, };
struct sip_threadinfo *me = NULL;
char buf[1024] = "";
struct pollfd fds[2] = { { 0 }, { 0 }, };
struct ast_tcptls_session_args *ca = NULL;
/* If this is a server session, then the connection has already been
* setup. Check if the authlimit has been reached and if not create the
* threadinfo object so we can access this thread for writing.
*
* if this is a client connection more work must be done.
* 1. We own the parent session args for a client connection. This pointer needs
* to be held on to so we can decrement it's ref count on thread destruction.
* 2. The threadinfo object was created before this thread was launched, however
* it must be found within the threadt table.
* 3. Last, the tcptls_session must be started.
*/
if (!tcptls_session->client) {
if (ast_atomic_fetchadd_int(&unauth_sessions, +1) >= authlimit) {
/* unauth_sessions is decremented in the cleanup code */
goto cleanup;
}
if ((flags = fcntl(tcptls_session->fd, F_GETFL)) == -1) {
ast_log(LOG_ERROR, "error setting socket to non blocking mode, fcntl() failed: %s\n", strerror(errno));
goto cleanup;
}
flags |= O_NONBLOCK;
if (fcntl(tcptls_session->fd, F_SETFL, flags) == -1) {
ast_log(LOG_ERROR, "error setting socket to non blocking mode, fcntl() failed: %s\n", strerror(errno));
goto cleanup;
}
if (!(me = sip_threadinfo_create(tcptls_session, tcptls_session->ssl ? AST_TRANSPORT_TLS : AST_TRANSPORT_TCP))) {
goto cleanup;
}
ao2_t_ref(me, +1, "Adding threadinfo ref for tcp_helper_thread");
} else {
struct sip_threadinfo tmp = {
.tcptls_session = tcptls_session,
};
if ((!(ca = tcptls_session->parent)) ||
(!(me = ao2_t_find(threadt, &tmp, OBJ_POINTER, "ao2_find, getting sip_threadinfo in tcp helper thread"))) ||
(!(tcptls_session = ast_tcptls_client_start(tcptls_session)))) {
goto cleanup;
}
flags = 1;
if (setsockopt(tcptls_session->fd, SOL_SOCKET, SO_KEEPALIVE, &flags, sizeof(flags))) {
ast_log(LOG_ERROR, "error enabling TCP keep-alives on sip socket: %s\n", strerror(errno));
goto cleanup;
}
me->threadid = pthread_self();
ast_debug(2, "Starting thread for %s server\n", tcptls_session->ssl ? "TLS" : "TCP");
/* set up pollfd to watch for reads on both the socket and the alert_pipe */
fds[0].fd = tcptls_session->fd;
fds[1].fd = me->alert_pipe[0];
fds[0].events = fds[1].events = POLLIN | POLLPRI;
Brett Bryant
committed
if (!(req.data = ast_str_create(SIP_MIN_PACKET))) {
}
if (!(reqcpy.data = ast_str_create(SIP_MIN_PACKET))) {