/**************************************************************************
**
** sngrep - SIP Messages flow viewer
**
** Copyright (C) 2013-2018 Ivan Alonso (Kaian)
** Copyright (C) 2013-2018 Irontec SL. All rights reserved.
**
** This program is free software: you can redistribute it and/or modify
** it under the terms of the GNU General Public License as published by
** the Free Software Foundation, either version 3 of the License, or
** (at your option) any later version.
**
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
** GNU General Public License for more details.
**
** You should have received a copy of the GNU General Public License
** along with this program. If not, see .
**
****************************************************************************/
/**
* @file sip.c
* @author Ivan Alonso [aka Kaian]
*
* @brief Source of functions defined in sip.h
*/
#include "config.h"
#include
#include
#include
#include
#include
#include
#include "sip.h"
#include "option.h"
#include "setting.h"
#include "filter.h"
/**
* @brief Linked list of parsed calls
*
* All parsed calls will be added to this list, only accesible from
* this awesome structure, so, keep it thread-safe.
*/
sip_call_list_t calls =
{ 0 };
/* @brief list of methods and responses */
sip_code_t sip_codes[] = {
{ SIP_METHOD_REGISTER, "REGISTER" },
{ SIP_METHOD_INVITE, "INVITE" },
{ SIP_METHOD_SUBSCRIBE, "SUBSCRIBE" },
{ SIP_METHOD_NOTIFY, "NOTIFY" },
{ SIP_METHOD_OPTIONS, "OPTIONS" },
{ SIP_METHOD_PUBLISH, "PUBLISH" },
{ SIP_METHOD_KDMQ, "KDMQ" },
{ SIP_METHOD_MESSAGE, "MESSAGE" },
{ SIP_METHOD_CANCEL, "CANCEL" },
{ SIP_METHOD_BYE, "BYE" },
{ SIP_METHOD_ACK, "ACK" },
{ SIP_METHOD_PRACK, "PRACK" },
{ SIP_METHOD_INFO, "INFO" },
{ SIP_METHOD_REFER, "REFER" },
{ SIP_METHOD_UPDATE, "UPDATE" },
{ 100, "100 Trying" },
{ 180, "180 Ringing" },
{ 181, "181 Call is Being Forwarded" },
{ 182, "182 Queued" },
{ 183, "183 Session Progress" },
{ 199, "199 Early Dialog Terminated" },
{ 200, "200 OK" },
{ 202, "202 Accepted" },
{ 204, "204 No Notification" },
{ 300, "300 Multiple Choices" },
{ 301, "301 Moved Permanently" },
{ 302, "302 Moved Temporarily" },
{ 305, "305 Use Proxy" },
{ 380, "380 Alternative Service" },
{ 400, "400 Bad Request" },
{ 401, "401 Unauthorized" },
{ 402, "402 Payment Required" },
{ 403, "403 Forbidden" },
{ 404, "404 Not Found" },
{ 405, "405 Method Not Allowed" },
{ 406, "406 Not Acceptable" },
{ 407, "407 Proxy Authentication Required" },
{ 408, "408 Request Timeout" },
{ 409, "409 Conflict" },
{ 410, "410 Gone" },
{ 411, "411 Length Required" },
{ 412, "412 Conditional Request Failed" },
{ 413, "413 Request Entity Too Large" },
{ 414, "414 Request-URI Too Long" },
{ 415, "415 Unsupported Media Type" },
{ 416, "416 Unsupported URI Scheme" },
{ 417, "417 Unknown Resource-Priority" },
{ 420, "420 Bad Extension" },
{ 421, "421 Extension Required" },
{ 422, "422 Session Interval Too Small" },
{ 423, "423 Interval Too Brief" },
{ 424, "424 Bad Location Information" },
{ 428, "428 Use Identity Header" },
{ 429, "429 Provide Referrer Identity" },
{ 430, "430 Flow Failed" },
{ 433, "433 Anonymity Disallowed" },
{ 436, "436 Bad Identity-Info" },
{ 437, "437 Unsupported Certificate" },
{ 438, "438 Invalid Identity Header" },
{ 439, "439 First Hop Lacks Outbound Support" },
{ 470, "470 Consent Needed" },
{ 480, "480 Temporarily Unavailable" },
{ 481, "481 Call/Transaction Does Not Exist" },
{ 482, "482 Loop Detected." },
{ 483, "483 Too Many Hops" },
{ 484, "484 Address Incomplete" },
{ 485, "485 Ambiguous" },
{ 486, "486 Busy Here" },
{ 487, "487 Request Terminated" },
{ 488, "488 Not Acceptable Here" },
{ 489, "489 Bad Event" },
{ 491, "491 Request Pending" },
{ 493, "493 Undecipherable" },
{ 494, "494 Security Agreement Required" },
{ 500, "500 Server Internal Error" },
{ 501, "501 Not Implemented" },
{ 502, "502 Bad Gateway" },
{ 503, "503 Service Unavailable" },
{ 504, "504 Server Time-out" },
{ 505, "505 Version Not Supported" },
{ 513, "513 Message Too Large" },
{ 580, "580 Precondition Failure" },
{ 600, "600 Busy Everywhere" },
{ 603, "603 Decline" },
{ 604, "604 Does Not Exist Anywhere" },
{ 606, "606 Not Acceptable" },
{ -1 , NULL },
};
void
sip_init(int limit, int only_calls, int no_incomplete)
{
int match_flags, reg_rule_len, reg_rule_err;
char reg_rule[SIP_ATTR_MAXLEN];
const char *setting = NULL;
// Store capture limit
calls.limit = limit;
calls.only_calls = only_calls;
calls.ignore_incomplete = no_incomplete;
calls.last_index = 0;
// Create a vector to store calls
calls.list = vector_create(200, 50);
vector_set_destroyer(calls.list, call_destroyer);
vector_set_sorter(calls.list, sip_list_sorter);
calls.active = vector_create(10, 10);
// Create hash table for callid search
calls.callids = htable_create(calls.limit);
// Set default sorting field
if (sip_attr_from_name(setting_get_value(SETTING_CL_SORTFIELD)) >= 0) {
calls.sort.by = sip_attr_from_name(setting_get_value(SETTING_CL_SORTFIELD));
calls.sort.asc = (!strcmp(setting_get_value(SETTING_CL_SORTORDER), "asc"));
} else {
// Fallback to default sorting field
calls.sort.by = SIP_ATTR_CALLINDEX;
calls.sort.asc = true;
}
// Initialize payload parsing regexp
match_flags = REG_EXTENDED | REG_ICASE | REG_NEWLINE;
regcomp(&calls.reg_method, "^([a-zA-Z]+) [a-zA-Z]+:.* SIP/2.0[ ]*\r", match_flags & ~REG_NEWLINE);
regcomp(&calls.reg_callid, "^(Call-ID|i):[ ]*([^ ]+)[ ]*\r$", match_flags);
setting = setting_get_value(SETTING_SIP_HEADER_X_CID);
reg_rule_len = strlen(setting) + 22;
if (reg_rule_len >= SIP_ATTR_MAXLEN) {
setting = "X-Call-ID|X-CID";
reg_rule_len = strlen(setting) + 22;
fprintf(stderr, "%s setting too long, using default.\n",
setting_name(SETTING_SIP_HEADER_X_CID));
}
snprintf(reg_rule, reg_rule_len, "^(%s):[ ]*([^ ]+)[ ]*\r$", setting);
reg_rule_err = regcomp(&calls.reg_xcallid, reg_rule, match_flags);
if(reg_rule_err != 0) {
regerror(reg_rule_err, &calls.reg_xcallid, reg_rule, SIP_ATTR_MAXLEN);
regfree(&calls.reg_xcallid);
fprintf(stderr, "%s setting produces regex compilation error: %s"
"using default value instead\n",
setting_name(SETTING_SIP_HEADER_X_CID), reg_rule);
regcomp(&calls.reg_xcallid,
"^(X-Call-ID|X-CID):[ ]*([^ ]+)[ ]*\r$", match_flags);
}
regcomp(&calls.reg_response, "^SIP/2.0[ ]*(([0-9]{3}) [^\r]*)[ ]*\r", match_flags & ~REG_NEWLINE);
regcomp(&calls.reg_cseq, "^CSeq:[ ]*([0-9]{1,10}) .+\r$", match_flags);
regcomp(&calls.reg_from, "^(From|f):[ ]*[^:]*:(([^@>]+)@?[^\r>;]+)", match_flags);
regcomp(&calls.reg_to, "^(To|t):[ ]*[^:]*:(([^@>]+)@?[^\r>;]+)", match_flags);
regcomp(&calls.reg_valid, "^([A-Z]+ [a-zA-Z]+:|SIP/2.0 [0-9]{3})", match_flags & ~REG_NEWLINE);
regcomp(&calls.reg_cl, "^(Content-Length|l):[ ]*([0-9]+)[ ]*\r$", match_flags);
regcomp(&calls.reg_body, "\r\n\r\n(.*)", match_flags & ~REG_NEWLINE);
regcomp(&calls.reg_reason, "Reason:[ ]*[^\r]*;text=\"([^\r]+)\"", match_flags);
regcomp(&calls.reg_warning, "Warning:[ ]*([0-9]*)", match_flags);
}
void
sip_deinit()
{
// Remove all calls
sip_calls_clear();
// Remove Call-id hash table
htable_destroy(calls.callids);
// Remove calls vector
vector_destroy(calls.list);
vector_destroy(calls.active);
// Deallocate regular expressions
regfree(&calls.reg_method);
regfree(&calls.reg_callid);
regfree(&calls.reg_xcallid);
regfree(&calls.reg_response);
regfree(&calls.reg_cseq);
regfree(&calls.reg_from);
regfree(&calls.reg_to);
regfree(&calls.reg_valid);
regfree(&calls.reg_cl);
regfree(&calls.reg_body);
regfree(&calls.reg_reason);
regfree(&calls.reg_warning);
}
char *
sip_get_callid(const char* payload, char *callid)
{
regmatch_t pmatch[3];
// Try to get Call-ID from payload
if (regexec(&calls.reg_callid, payload, 3, pmatch, 0) == 0) {
// Copy the matching part of payload
strncpy(callid, payload + pmatch[2].rm_so, (int) pmatch[2].rm_eo - pmatch[2].rm_so);
}
return callid;
}
char *
sip_get_xcallid(const char *payload, char *xcallid)
{
regmatch_t pmatch[3];
// Try to get X-Call-ID from payload
if (regexec(&calls.reg_xcallid, (const char *)payload, 3, pmatch, 0) == 0) {
strncpy(xcallid, (const char *)payload + pmatch[2].rm_so, (int)pmatch[2].rm_eo - pmatch[2].rm_so);
}
return xcallid;
}
int
sip_validate_packet(packet_t *packet)
{
uint32_t plen = packet_payloadlen(packet);
u_char payload[MAX_SIP_PAYLOAD];
regmatch_t pmatch[4];
char cl_header[10];
int content_len;
int bodylen;
// Max SIP payload allowed
if (plen == 0 || plen > MAX_SIP_PAYLOAD)
return VALIDATE_NOT_SIP;
// Get payload from packet(s)
memset(payload, 0, MAX_SIP_PAYLOAD);
memcpy(payload, packet_payload(packet), plen);
// Initialize variables
memset(cl_header, 0, sizeof(cl_header));
// Check if the first line follows SIP request or response format
if (regexec(&calls.reg_valid, (const char *) payload, 2, pmatch, 0) != 0) {
// Not a SIP message AT ALL
return VALIDATE_NOT_SIP;
}
// Check if we have Content Length header
if (regexec(&calls.reg_cl, (const char *) payload, 4, pmatch, 0) != 0) {
// Not a SIP message or not complete
return VALIDATE_PARTIAL_SIP;
}
strncpy(cl_header, (const char *)payload + pmatch[2].rm_so, (int)pmatch[2].rm_eo - pmatch[2].rm_so);
content_len = atoi(cl_header);
// Check if we have Body separator field
if (regexec(&calls.reg_body, (const char *) payload, 2, pmatch, 0) != 0) {
// Not a SIP message or not complete
return VALIDATE_PARTIAL_SIP;
}
// Get the SIP message body length
bodylen = (int) pmatch[1].rm_eo - pmatch[1].rm_so;
// The SDP body of the SIP message ends in another packet
if (content_len > bodylen) {
return VALIDATE_PARTIAL_SIP;
}
if (content_len < bodylen) {
// Check body ends with '\r\n'
if (payload[pmatch[1].rm_so + content_len - 1] != '\n')
return VALIDATE_NOT_SIP;
if (payload[pmatch[1].rm_so + content_len - 2] != '\r')
return VALIDATE_NOT_SIP;
// We got more than one SIP message in the same packet
packet_set_payload(packet, payload, pmatch[1].rm_so + content_len);
return VALIDATE_MULTIPLE_SIP;
}
// We got all the SDP body of the SIP message
return VALIDATE_COMPLETE_SIP;
}
sip_msg_t *
sip_check_packet(packet_t *packet)
{
sip_msg_t *msg;
sip_call_t *call;
char callid[1024], xcallid[1024];
u_char payload[MAX_SIP_PAYLOAD];
bool newcall = false;
// Max SIP payload allowed
if (packet->payload_len > MAX_SIP_PAYLOAD)
return NULL;
// Initialize local variables
memset(callid, 0, sizeof(callid));
memset(xcallid, 0, sizeof(xcallid));
// Get payload from packet(s)
memset(payload, 0, MAX_SIP_PAYLOAD);
memcpy(payload, packet_payload(packet), packet_payloadlen(packet));
// Get the Call-ID of this message
if (!sip_get_callid((const char*) payload, callid))
return NULL;
// Create a new message from this data
if (!(msg = msg_create((const char*) payload)))
return NULL;
// Get Method and request for the following checks
// There is no need to parse all payload at this point
// If no response or request code is found, this is not a SIP message
if (!sip_get_msg_reqresp(msg, payload)) {
// Deallocate message memory
msg_destroy(msg);
return NULL;
}
// Find the call for this msg
if (!(call = sip_find_by_callid(callid))) {
// Check if payload matches expression
if (!sip_check_match_expression((const char*) payload))
goto skip_message;
// User requested only INVITE starting dialogs
if (calls.only_calls && msg->reqresp != SIP_METHOD_INVITE)
goto skip_message;
// Only create a new call if the first msg
// is a request message in the following gorup
if (calls.ignore_incomplete && msg->reqresp > SIP_METHOD_MESSAGE)
goto skip_message;
// Get the Call-ID of this message
sip_get_xcallid((const char*) payload, xcallid);
// Rotate call list if limit has been reached
if (calls.limit == sip_calls_count())
sip_calls_rotate();
// Create the call if not found
if (!(call = call_create(callid, xcallid)))
goto skip_message;
// Add this Call-Id to hash table
htable_insert(calls.callids, call->callid, call);
// Set call index
call->index = ++calls.last_index;
// Mark this as a new call
newcall = true;
}
// At this point we know we're handling an interesting SIP Packet
msg->packet = packet;
// Always parse first call message
if (call_msg_count(call) == 0) {
// Parse SIP payload
sip_parse_msg_payload(msg, payload);
// If this call has X-Call-Id, append it to the parent call
if (strlen(call->xcallid)) {
call_add_xcall(sip_find_by_callid(call->xcallid), call);
}
}
// Add the message to the call
call_add_message(call, msg);
// check if message is a retransmission
call_msg_retrans_check(msg);
if (call_is_invite(call)) {
// Parse media data
sip_parse_msg_media(msg, payload);
// Update Call State
call_update_state(call, msg);
// Parse extra fields
sip_parse_extra_headers(msg, payload);
// Check if this call should be in active call list
if (call_is_active(call)) {
if (sip_call_is_active(call)) {
vector_append(calls.active, call);
}
} else {
if (sip_call_is_active(call)) {
vector_remove(calls.active, call);
}
}
}
if (newcall) {
// Append this call to the call list
vector_append(calls.list, call);
}
// Mark the list as changed
calls.changed = true;
// Return the loaded message
return msg;
skip_message:
// Deallocate message memory
msg_destroy(msg);
return NULL;
}
bool
sip_calls_has_changed()
{
bool changed = calls.changed;
calls.changed = false;
return changed;
}
int
sip_calls_count()
{
return vector_count(calls.list);
}
vector_iter_t
sip_calls_iterator()
{
return vector_iterator(calls.list);
}
vector_iter_t
sip_active_calls_iterator()
{
return vector_iterator(calls.active);
}
bool
sip_call_is_active(sip_call_t *call)
{
return vector_index(calls.active, call) != -1;
}
vector_t *
sip_calls_vector()
{
return calls.list;
}
vector_t *
sip_active_calls_vector()
{
return calls.active;
}
sip_stats_t
sip_calls_stats()
{
sip_stats_t stats;
vector_iter_t it = vector_iterator(calls.list);
// Total number of calls without filtering
stats.total = vector_iterator_count(&it);
// Total number of calls after filtering
vector_iterator_set_filter(&it, filter_check_call);
stats.displayed = vector_iterator_count(&it);
return stats;
}
sip_call_t *
sip_find_by_index(int index)
{
return vector_item(calls.list, index);
}
sip_call_t *
sip_find_by_callid(const char *callid)
{
return htable_find(calls.callids, callid);
}
int
sip_get_msg_reqresp(sip_msg_t *msg, const u_char *payload)
{
regmatch_t pmatch[3];
char resp_str[SIP_ATTR_MAXLEN];
char reqresp[SIP_ATTR_MAXLEN];
char cseq[11];
const char *resp_def;
// Initialize variables
memset(pmatch, 0, sizeof(pmatch));
memset(resp_str, 0, sizeof(resp_str));
memset(reqresp, 0, sizeof(reqresp));
// If not already parsed
if (!msg->reqresp) {
// Method & CSeq
if (regexec(&calls.reg_method, (const char *)payload, 2, pmatch, 0) == 0) {
if ((int)(pmatch[1].rm_eo - pmatch[1].rm_so) >= SIP_ATTR_MAXLEN) {
strncpy(reqresp, "", 11);
} else {
sprintf(reqresp, "%.*s", (int) (pmatch[1].rm_eo - pmatch[1].rm_so), payload + pmatch[1].rm_so);
}
}
// CSeq
if (regexec(&calls.reg_cseq, (char*)payload, 2, pmatch, 0) == 0) {
sprintf(cseq, "%.*s", (int)(pmatch[1].rm_eo - pmatch[1].rm_so), payload + pmatch[1].rm_so);
msg->cseq = atoi(cseq);
}
// Response code
if (regexec(&calls.reg_response, (const char *)payload, 3, pmatch, 0) == 0) {
if ((int)(pmatch[1].rm_eo - pmatch[1].rm_so) >= SIP_ATTR_MAXLEN) {
strncpy(resp_str, "", 11);
} else {
sprintf(resp_str, "%.*s", (int) (pmatch[1].rm_eo - pmatch[1].rm_so), payload + pmatch[1].rm_so);
}
if ((int)(pmatch[2].rm_eo - pmatch[2].rm_so) >= SIP_ATTR_MAXLEN) {
strncpy(resp_str, "", 11);
} else {
sprintf(reqresp, "%.*s", (int) (pmatch[2].rm_eo - pmatch[2].rm_so), payload + pmatch[2].rm_so);
}
}
// Get Request/Response Code
msg->reqresp = sip_method_from_str(reqresp);
// For response codes, check if the text matches the default
if (!msg_is_request(msg)) {
resp_def = sip_method_str(msg->reqresp);
if (!resp_def || strcmp(resp_def, resp_str)) {
msg->resp_str = strdup(resp_str);
}
}
}
return msg->reqresp;
}
const char *
sip_get_msg_reqresp_str(sip_msg_t *msg)
{
// Check if code has non-standard text
if (msg->resp_str) {
return msg->resp_str;
} else {
return sip_method_str(msg->reqresp);
}
}
sip_msg_t *
sip_parse_msg(sip_msg_t *msg)
{
if (msg && !msg->cseq) {
sip_parse_msg_payload(msg, (u_char*) msg_get_payload(msg));
}
return msg;
}
int
sip_parse_msg_payload(sip_msg_t *msg, const u_char *payload)
{
regmatch_t pmatch[4];
// From
if (regexec(&calls.reg_from, (const char *)payload, 4, pmatch, 0) == 0) {
msg->sip_from = sng_malloc((int)pmatch[2].rm_eo - pmatch[2].rm_so + 1);
strncpy(msg->sip_from, (const char *)payload + pmatch[2].rm_so, (int)pmatch[2].rm_eo - pmatch[2].rm_so);
} else {
// Malformed From Header
msg->sip_from = sng_malloc(12);
strncpy(msg->sip_from, "", 11);
}
// To
if (regexec(&calls.reg_to, (const char *)payload, 4, pmatch, 0) == 0) {
msg->sip_to = sng_malloc((int)pmatch[2].rm_eo - pmatch[2].rm_so + 1);
strncpy(msg->sip_to, (const char *)payload + pmatch[2].rm_so, (int)pmatch[2].rm_eo - pmatch[2].rm_so);
} else {
// Malformed To Header
msg->sip_to = sng_malloc(12);
strncpy(msg->sip_to, "", 11);
}
return 0;
}
void
sip_parse_msg_media(sip_msg_t *msg, const u_char *payload)
{
#define ADD_STREAM(stream) \
if (stream) { \
if (!rtp_find_call_stream(call, src, stream->dst)) { \
call_add_stream(call, stream); \
} else { \
sng_free(stream); \
stream = NULL; \
} \
}
address_t dst, src = { };
rtp_stream_t *rtp_stream = NULL, *rtcp_stream = NULL, *msg_rtp_stream = NULL;
char media_type[MEDIATYPELEN + 1] = { };
char media_format[30] = { };
char address[ADDRESSLEN + 1] = { };
uint32_t media_fmt_pref;
uint32_t media_fmt_code;
sdp_media_t *media = NULL;
char *payload2, *tofree, *line;
sip_call_t *call = msg_get_call(msg);
// If message is retrans, there's no need to parse the payload again
if (msg->retrans) {
// Use the media vector from the original message
msg->medias = msg->retrans->medias;
return;
}
// Parse each line of payload looking for sdp information
tofree = payload2 = strdup((char*)payload);
while ((line = strsep(&payload2, "\r\n")) != NULL) {
// Check if we have a media string
if (!strncmp(line, "m=", 2)) {
if (sscanf(line, "m=%" STRINGIFY(MEDIATYPELEN) "s %hu RTP/%*s %u", media_type, &dst.port, &media_fmt_pref) == 3
|| sscanf(line, "m=%" STRINGIFY(MEDIATYPELEN) "s %hu UDP/%*s %u", media_type, &dst.port, &media_fmt_pref) == 3) {
// Add streams from previous 'm=' line to the call
ADD_STREAM(msg_rtp_stream);
ADD_STREAM(rtp_stream);
ADD_STREAM(rtcp_stream);
// Create a new media structure for this message
if ((media = media_create(msg))) {
media_set_type(media, media_type);
media_set_address(media, dst);
media_set_prefered_format(media, media_fmt_pref);
msg_add_media(msg, media);
/**
* From SDP we can only guess destination address port. RTP Capture proccess
* will determine when the stream has been completed, getting source address
* and port of the stream.
*/
// Create a new stream with this destination address:port
// Create RTP stream with source of message as destination address
msg_rtp_stream = stream_create(media, dst, PACKET_RTP);
msg_rtp_stream->dst = msg->packet->src;
msg_rtp_stream->dst.port = dst.port;
// Create RTP stream
rtp_stream = stream_create(media, dst, PACKET_RTP);
// Create RTCP stream
rtcp_stream = stream_create(media, dst, PACKET_RTCP);
rtcp_stream->dst.port++;
}
}
}
// Check if we have a connection string
if (!strncmp(line, "c=", 2)) {
if (sscanf(line, "c=IN IP%*c %" STRINGIFY(ADDRESSLEN) "s", address)) {
strncpy(dst.ip, address, ADDRESSLEN - 1);
if (media) {
media_set_address(media, dst);
strcpy(rtp_stream->dst.ip, dst.ip);
strcpy(rtcp_stream->dst.ip, dst.ip);
}
}
}
// Check if we have attribute format string
if (!strncmp(line, "a=rtpmap:", 9)) {
if (media && sscanf(line, "a=rtpmap:%u %29[^ ]", &media_fmt_code, media_format)) {
media_add_format(media, media_fmt_code, media_format);
}
}
// Check if we have attribute format RTCP port
if (!strncmp(line, "a=rtcp:", 7) && rtcp_stream) {
sscanf(line, "a=rtcp:%hu", &rtcp_stream->dst.port);
}
}
// Add streams from last 'm=' line to the call
ADD_STREAM(msg_rtp_stream);
ADD_STREAM(rtp_stream);
ADD_STREAM(rtcp_stream);
sng_free(tofree);
#undef ADD_STREAM
}
void
sip_parse_extra_headers(sip_msg_t *msg, const u_char *payload)
{
regmatch_t pmatch[4];
char warning[10];
// Reason text
if (regexec(&calls.reg_reason, (const char *)payload, 2, pmatch, 0) == 0) {
msg->call->reasontxt = sng_malloc((int)pmatch[1].rm_eo - pmatch[1].rm_so + 1);
strncpy(msg->call->reasontxt, (const char *)payload + pmatch[1].rm_so, (int)pmatch[1].rm_eo - pmatch[1].rm_so);
}
// Warning code
if (regexec(&calls.reg_warning, (const char *)payload, 2, pmatch, 0) == 0) {
strncpy(warning, (const char *)payload + pmatch[1].rm_so, (int)pmatch[1].rm_eo - pmatch[1].rm_so);
msg->call->warning = atoi(warning);
}
}
void
sip_calls_clear()
{
// Create again the callid hash table
htable_destroy(calls.callids);
calls.callids = htable_create(calls.limit);
// Remove all items from vector
vector_clear(calls.list);
vector_clear(calls.active);
}
void
sip_calls_clear_soft()
{
// Create again the callid hash table
htable_destroy(calls.callids);
calls.callids = htable_create(calls.limit);
// Repopulate list applying current filter
calls.list = vector_copy_if(sip_calls_vector(), filter_check_call);
calls.active = vector_copy_if(sip_active_calls_vector(), filter_check_call);
// Repopulate callids based on filtered list
sip_call_t *call;
vector_iter_t it = vector_iterator(calls.list);
while ((call = vector_iterator_next(&it)))
{
htable_insert(calls.callids, call->callid, call);
}
}
void
sip_calls_rotate()
{
sip_call_t *call;
vector_iter_t it = vector_iterator(calls.list);
while ((call = vector_iterator_next(&it))) {
if (!call->locked) {
// Remove from callids hash
htable_remove(calls.callids, call->callid);
// Remove first call from active and call lists
vector_remove(calls.active, call);
vector_remove(calls.list, call);
return;
}
}
}
int
sip_set_match_expression(const char *expr, int insensitive, int invert)
{
// Store expression text
calls.match_expr = expr;
// Set invert flag
calls.match_invert = invert;
#ifdef WITH_PCRE
const char *re_err = NULL;
int32_t err_offset;
int32_t pflags = PCRE_UNGREEDY | PCRE_DOTALL;
if (insensitive)
pflags |= PCRE_CASELESS;
// Check if we have a valid expression
calls.match_regex = pcre_compile(expr, pflags, &re_err, &err_offset, 0);
return calls.match_regex == NULL;
#else
int cflags = REG_EXTENDED;
// Case insensitive requested
if (insensitive)
cflags |= REG_ICASE;
// Check the expresion is a compilable regexp
return regcomp(&calls.match_regex, expr, cflags) != 0;
#endif
}
const char *
sip_get_match_expression()
{
return calls.match_expr;
}
int
sip_check_match_expression(const char *payload)
{
// Everything matches when there is no match
if (!calls.match_expr)
return 1;
#ifdef WITH_PCRE
switch (pcre_exec(calls.match_regex, 0, payload, strlen(payload), 0, 0, 0, 0)) {
case PCRE_ERROR_NOMATCH:
return 1 == calls.match_invert;
}
return 0 == calls.match_invert;
#else
// Check if payload matches the given expresion
return (regexec(&calls.match_regex, payload, 0, NULL, 0) == calls.match_invert);
#endif
}
const char *
sip_method_str(int method)
{
int i;
// Standard method
for (i = 0; sip_codes[i].id > 0; i++) {
if (method == sip_codes[i].id)
return sip_codes[i].text;
}
return NULL;
}
int
sip_method_from_str(const char *method)
{
int i;
// Standard method
for (i = 0; sip_codes[i].id > 0; i++) {
if (!strcmp(method, sip_codes[i].text))
return sip_codes[i].id;
}
return atoi(method);
}
const char *
sip_transport_str(int transport)
{
switch(transport)
{
case PACKET_SIP_UDP:
return "UDP";
case PACKET_SIP_TCP:
return "TCP";
case PACKET_SIP_TLS:
return "TLS";
case PACKET_SIP_WS:
return "WS";
case PACKET_SIP_WSS:
return "WSS";
}
return "";
}
char *
sip_get_msg_header(sip_msg_t *msg, char *out)
{
char from_addr[80], to_addr[80], time[80], date[80];
// Source and Destination address
msg_get_attribute(msg, SIP_ATTR_DATE, date);
msg_get_attribute(msg, SIP_ATTR_TIME, time);
msg_get_attribute(msg, SIP_ATTR_SRC, from_addr);
msg_get_attribute(msg, SIP_ATTR_DST, to_addr);
// Get msg header
if (setting_enabled(SETTING_DISPLAY_ALIAS)) {
sprintf(out, "%s %s %s -> %s", date, time, get_alias_value(from_addr), get_alias_value(to_addr));
} else {
sprintf(out, "%s %s %s -> %s", date, time, from_addr, to_addr);
}
return out;
}
void
sip_set_sort_options(sip_sort_t sort)
{
calls.sort = sort;
sip_sort_list();
}
sip_sort_t
sip_sort_options()
{
return calls.sort;
}
void
sip_sort_list()
{
// Cloning the vector automatically sorts it
vector_t *clone = vector_clone(calls.list);
// FIXME FIXME FIXME
// There should be a way to destroy the vector without calling the
// vector destroyer for each item...
vector_set_destroyer(calls.list, NULL);
vector_destroy(calls.list);
// The new sorted list
calls.list = clone;
}
void
sip_list_sorter(vector_t *vector, void *item)
{
sip_call_t *prev, *cur = (sip_call_t *)item;
int count = vector_count(vector);
int i;
// First item is alway sorted
if (vector_count(vector) == 1)
return;
for (i = count - 2 ; i >= 0; i--) {
// Get previous item
prev = vector_item(vector, i);
// Check if the item is already in a sorted position
int cmp = call_attr_compare(cur, prev, calls.sort.by);
if ((calls.sort.asc && cmp > 0) || (!calls.sort.asc && cmp < 0)) {
vector_insert(vector, item, i + 1);
return;
}
}
// Put this item at the begining of the vector
vector_insert(vector, item, 0);
}