/* $Id: mediaproxy.c,v 1.23.2.4 2005/07/20 05:45:10 danp Exp $
*
* Copyright (C) 2004 Dan Pascu
*
* This file is part of ser, a free SIP server.
*
* ser 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 2 of the License, or
* (at your option) any later version
*
* For a license to use the ser software under conditions
* other than those described here, or to purchase support for this
* software, please contact iptel.org by e-mail at the following addresses:
* info@iptel.org
*
* ser 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, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*/
// TODO
//
// - make the asymmetric files use CFG_DIR
// - find a way to install the config files with make install
#include "../../sr_module.h"
#include "../../dprint.h"
#include "../../str.h"
#include "../../error.h"
#include "../../data_lump.h"
#include "../../forward.h"
#include "../../mem/mem.h"
#include "../../resolve.h"
#include "../../timer.h"
#include "../../ut.h"
#include "../../parser/parse_from.h"
#include "../../parser/parse_to.h"
#include "../../parser/parse_uri.h"
#include "../../msg_translator.h"
#include "../registrar/sip_msg.h"
#include "../usrloc/usrloc.h"
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <ctype.h>
#include <errno.h>
#include <regex.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/un.h>
MODULE_VERSION
// Although `AF_LOCAL' is mandated by POSIX.1g, `AF_UNIX' is portable to
// more systems. `AF_UNIX' was the traditional name stemming from BSD, so
// even most POSIX systems support it. It is also the name of choice in
// the Unix98 specification. So if there's no AF_LOCAL fallback to AF_UNIX
#ifndef AF_LOCAL
# define AF_LOCAL AF_UNIX
#endif
#define min(x, y) (((x) < (y)) ? (x) : (y))
#define isAnyAddress(adr) ((adr).len==7 && memcmp("0.0.0.0", (adr).s, 7)==0)
typedef int Bool;
#define True 1
#define False 0
typedef int (*CheckLocalPartyProc)(struct sip_msg* msg, char* s1, char* s2);
typedef Bool (*NatTestProc)(struct sip_msg* msg);
typedef enum {
NTNone=0,
NTPrivateContact=1,
NTSourceAddress=2,
NTPrivateVia=4
} NatTestType;
typedef enum {
STUnknown=0,
STAudio,
STVideo,
STAudioVideo
} StreamType;
typedef struct {
const char *name;
uint32_t address;
uint32_t mask;
} NetInfo;
typedef struct {
str ip;
str port;
str type; // stream type (`audio', `video', ...)
int localIP; // true if the IP is locally defined inside this media stream
} StreamInfo;
typedef struct {
NatTestType test;
NatTestProc proc;
} NatTest;
typedef struct {
char *file; // the file which lists the asymmetric clients
long timestamp; // for checking if it was modified
regex_t **clients; // the asymmetric clients regular expressions
int size; // size of array above
int count; // how many clients are in array above
} AsymmetricClients;
/* Function prototypes */
static int ClientNatTest(struct sip_msg *msg, char *str1, char *str2);
static int FixContact(struct sip_msg *msg, char *str1, char *str2);
static int UseMediaProxy(struct sip_msg *msg, char *str1, char *str2);
static int EndMediaSession(struct sip_msg *msg, char *str1, char *str2);
static int mod_init(void);
static int fixstring2int(void **param, int param_count);
static Bool testPrivateContact(struct sip_msg* msg);
static Bool testSourceAddress(struct sip_msg* msg);
static Bool testPrivateVia(struct sip_msg* msg);
/* Local global variables */
static char *mediaproxySocket = "/var/run/proxydispatcher.sock";
static int natpingInterval = 60; // 60 seconds
static usrloc_api_t userLocation;
static AsymmetricClients sipAsymmetrics = {
"/etc/ser/sip-asymmetric-clients",
//CFG_DIR"/sip-asymmetric-clients",
0,
NULL,
0,
0
};
static AsymmetricClients rtpAsymmetrics = {
"/etc/ser/rtp-asymmetric-clients",
0,
NULL,
0,
0
};
static CheckLocalPartyProc isFromLocal;
//static CheckLocalPartyProc isToLocal;
static CheckLocalPartyProc isDestinationLocal;
NetInfo rfc1918nets[] = {
{"10.0.0.0", 0x0a000000UL, 0xff000000UL},
{"172.16.0.0", 0xac100000UL, 0xfff00000UL},
{"192.168.0.0", 0xc0a80000UL, 0xffff0000UL},
{NULL, 0UL, 0UL}
};
NatTest natTests[] = {
{NTPrivateContact, testPrivateContact},
{NTSourceAddress, testSourceAddress},
{NTPrivateVia, testPrivateVia},
{NTNone, NULL}
};
static cmd_export_t commands[] = {
{"fix_contact", FixContact, 0, 0, REQUEST_ROUTE | ONREPLY_ROUTE },
{"use_media_proxy", UseMediaProxy, 0, 0, REQUEST_ROUTE | ONREPLY_ROUTE },
{"end_media_session", EndMediaSession, 0, 0, REQUEST_ROUTE | ONREPLY_ROUTE | FAILURE_ROUTE },
{"client_nat_test", ClientNatTest, 1, fixstring2int, REQUEST_ROUTE | ONREPLY_ROUTE | FAILURE_ROUTE },
{0, 0, 0, 0, 0}
};
static param_export_t parameters[] = {
{"mediaproxy_socket", STR_PARAM, &mediaproxySocket},
{"sip_asymmetrics", STR_PARAM, &(sipAsymmetrics.file)},
{"rtp_asymmetrics", STR_PARAM, &(rtpAsymmetrics.file)},
{"natping_interval", INT_PARAM, &natpingInterval},
{0, 0, 0}
};
struct module_exports exports = {
"mediaproxy", // module name
commands, // module exported functions
parameters, // module exported parameters
mod_init, // module init (before any kid is created. kids will inherit)
NULL, // reply processing
NULL, // destroy function
NULL, // on_break
NULL // child_init
};
/* Helper functions */
// Functions dealing with strings
/*
* strfind() finds the start of the first occurrence of the substring needle
* of length nlen in the memory area haystack of length len.
*/
static void*
strfind(const void *haystack, size_t len, const void *needle, size_t nlen)
{
char *sp;
/* Sanity check */
if(!(haystack && needle && nlen && len>=nlen))
return NULL;
for (sp = (char*)haystack; sp <= (char*)haystack + len - nlen; sp++) {
if (*sp == *(char*)needle && memcmp(sp, needle, nlen)==0) {
return sp;
}
}
return NULL;
}
/*
* strcasefind() finds the start of the first occurrence of the substring
* needle of length nlen in the memory area haystack of length len by doing
* a case insensitive search
*/
static void*
strcasefind(const char *haystack, size_t len, const char *needle, size_t nlen)
{
char *sp;
/* Sanity check */
if(!(haystack && needle && nlen && len>=nlen))
return NULL;
for (sp = (char*)haystack; sp <= (char*)haystack + len - nlen; sp++) {
if (tolower(*sp) == tolower(*(char*)needle) &&
strncasecmp(sp, needle, nlen)==0) {
return sp;
}
}
return NULL;
}
/* returns string with whitespace trimmed from left end */
static inline void
ltrim(str *string)
{
while (string->len>0 && isspace((int)*(string->s))) {
string->len--;
string->s++;
}
}
/* returns string with whitespace trimmed from right end */
static inline void
rtrim(str *string)
{
char *ptr;
ptr = string->s + string->len - 1;
while (string->len>0 && (*ptr==0 || isspace((int)*ptr))) {
string->len--;
ptr--;
}
}
/* returns string with whitespace trimmed from both ends */
static inline void
trim(str *string)
{
ltrim(string);
rtrim(string);
}
/* returns a pointer to first CR or LF char found or the end of string */
static char*
findendline(char *string, int len)
{
char *ptr = string;
while(ptr - string < len && *ptr != '\n' && *ptr != '\r')
ptr++;
return ptr;
}
static int
strtoint(str *data)
{
long int result;
char c;
// hack to avoid copying the string
c = data->s[data->len];
data->s[data->len] = 0;
result = strtol(data->s, NULL, 10);
data->s[data->len] = c;
return (int)result;
}
/* Free the returned string with pkg_free() after you've done with it */
static char*
encodeQuopri(str buf)
{
char *result;
int i, j;
char c;
result = pkg_malloc(buf.len*3+1);
if (!result) {
LOG(L_ERR, "error: mediaproxy/encodeQuopri(): out of memory\n");
return NULL;
}
for (i=0, j=0; i<buf.len; i++) {
c = buf.s[i];
if ((c>0x20 && c<0x7f && c!='=') || c=='\n' || c=='\r') {
result[j++] = c;
} else {
result[j++] = '=';
sprintf(&result[j], "%02X", (c & 0xff));
j += 2;
}
}
result[j] = 0;
return result;
}
/* Find a line in str `block' that starts with `start'. */
static char*
findLineStartingWith(str *block, char *start, int ignoreCase)
{
char *ptr, *bend;
str zone;
int tlen;
bend = block->s + block->len;
tlen = strlen(start);
ptr = NULL;
for (zone = *block; zone.len > 0; zone.len = bend - zone.s) {
if (ignoreCase)
ptr = strcasefind(zone.s, zone.len, start, tlen);
else
ptr = strfind(zone.s, zone.len, start, tlen);
if (!ptr || ptr==zone.s || ptr[-1]=='\n' || ptr[-1]=='\r')
break;
zone.s = ptr + tlen;
}
return ptr;
}
/* get up to `limit' whitespace separated tokens from `char *string' */
static int
getTokens(char *string, str *tokens, int limit)
{
int i, len, size;
char *ptr;
if (!string) {
return 0;
}
len = strlen(string);
for (ptr=string, i=0; i<limit && len>0; i++) {
size = strspn(ptr, " \t\n\r");
ptr += size;
len -= size;
if (len <= 0)
break;
size = strcspn(ptr, " \t\n\r");
if (size==0)
break;
tokens[i].s = ptr;
tokens[i].len = size;
ptr += size;
len -= size;
}
return i;
}
/* get up to `limit' whitespace separated tokens from `str *string' */
static int
getStrTokens(str *string, str *tokens, int limit)
{
int count;
char c;
if (!string || !string->s) {
return 0;
}
c = string->s[string->len];
string->s[string->len] = 0;
count = getTokens(string->s, tokens, limit);
string->s[string->len] = c;
return count;
}
/* Functions to extract the info we need from the SIP/SDP message */
/* Extract Call-ID value. */
static Bool
getCallId(struct sip_msg* msg, str *cid)
{
if (msg->callid == NULL) {
if (parse_headers(msg, HDR_CALLID, 0) == -1) {
return False;
}
if (msg->callid == NULL) {
return False;
}
}
*cid = msg->callid->body;
trim(cid);
return True;
}
/* Get caller domain */
static str
getFromDomain(struct sip_msg* msg)
{
static char buf[16] = "unknown"; // buf is here for a reason. don't
static str notfound = {buf, 7}; // use the constant string directly!
static struct sip_uri puri;
str uri;
if (parse_from_header(msg) < 0) {
LOG(L_ERR, "error: mediaproxy/getFromDomain(): error parsing `From' header\n");
return notfound;
}
uri = get_from(msg)->uri;
if (parse_uri(uri.s, uri.len, &puri) < 0) {
LOG(L_ERR, "error: mediaproxy/getFromDomain(): error parsing `From' URI\n");
return notfound;
} else if (puri.host.len == 0) {
return notfound;
}
return puri.host;
}
/* Get called domain */
static str
getToDomain(struct sip_msg* msg)
{
static char buf[16] = "unknown"; // buf is here for a reason. don't
static str notfound = {buf, 7}; // use the constant string directly!
static struct sip_uri puri;
str uri;
uri = get_to(msg)->uri;
if (parse_uri(uri.s, uri.len, &puri) < 0) {
LOG(L_ERR, "error: mediaproxy/getToDomain(): error parsing `To' URI\n");
return notfound;
} else if (puri.host.len == 0) {
return notfound;
}
return puri.host;
}
/* Get destination domain */
// This function only works when called for a request although it's more
// reliable than the getToDomain function.
static str
getDestinationDomain(struct sip_msg* msg)
{
static char buf[16] = "unknown"; // buf is here for a reason. don't
static str notfound = {buf, 7}; // use the constant string directly!
if (parse_sip_msg_uri(msg) < 0) {
LOG(L_ERR, "error: mediaproxy/getDestinationDomain(): error parsing destination URI\n");
return notfound;
} else if (msg->parsed_uri.host.len==0) {
return notfound;
}
return msg->parsed_uri.host;
}
/* Get From tag */
static str
getFromAddress(struct sip_msg *msg)
{
static char buf[16] = "unknown"; // buf is here for a reason. don't
static str notfound = {buf, 7}; // use the constant string directly!
str uri;
char *ptr;
if (parse_from_header(msg) == -1) {
LOG(L_ERR, "error: mediaproxy/getFromAddress(): error parsing From: field\n");
return notfound;
}
uri = get_from(msg)->uri;
if (uri.len == 0)
return notfound;
if (strncmp(uri.s, "sip:", 4)==0) {
uri.s += 4;
uri.len -= 4;
}
if ((ptr = strfind(uri.s, uri.len, ";", 1))!=NULL) {
uri.len = ptr - uri.s;
}
return uri;
}
/* Get To tag */
static str
getToAddress(struct sip_msg *msg)
{
static char buf[16] = "unknown"; // buf is here for a reason. don't
static str notfound = {buf, 7}; // use the constant string directly!
str uri;
char *ptr;
if (!msg->to) {
LOG(L_ERR, "error: mediaproxy/getToAddress(): missing To: field\n");
return notfound;
}
uri = get_to(msg)->uri;
if (uri.len == 0)
return notfound;
if (strncmp(uri.s, "sip:", 4)==0) {
uri.s += 4;
uri.len -= 4;
}
if ((ptr = strfind(uri.s, uri.len, ";", 1))!=NULL) {
uri.len = ptr - uri.s;
}
return uri;
}
/* Get From tag */
static str
getFromTag(struct sip_msg *msg)
{
static char buf[4] = ""; // buf is here for a reason. don't
static str notfound = {buf, 0}; // use the constant string directly!
str tag;
if (parse_from_header(msg) == -1) {
LOG(L_ERR, "error: mediaproxy/getFromTag(): error parsing From: field\n");
return notfound;
}
tag = get_from(msg)->tag_value;
if (tag.len == 0)
return notfound;
return tag;
}
/* Get To tag */
static str
getToTag(struct sip_msg *msg)
{
static char buf[4] = ""; // buf is here for a reason. don't
static str notfound = {buf, 0}; // use the constant string directly!
str tag;
if (!msg->to) {
LOG(L_ERR, "error: mediaproxy/getToTag(): missing To: field\n");
return notfound;
}
tag = get_to(msg)->tag_value;
if (tag.len == 0)
return notfound;
return tag;
}
/* Extract User-Agent */
static str
getUserAgent(struct sip_msg* msg)
{
static char buf[16] = "unknown-agent"; // buf is here for a reason. don't
static str notfound = {buf, 13}; // use the constant string directly!
str block, server;
char *ptr;
if ((parse_headers(msg, HDR_USERAGENT, 0)!=-1) && msg->user_agent &&
msg->user_agent->body.len>0) {
return msg->user_agent->body;
}
// If we can't find user-agent, look after the Server: field
// This is a temporary hack. Normally it should be extracted by ser
// (either as the Server field, or if User-Agent is missing in place
// of the User-Agent field)
block.s = msg->buf;
block.len = msg->len;
ptr = findLineStartingWith(&block, "Server:", True);
if (!ptr)
return notfound;
server.s = ptr + 7;
server.len = findendline(server.s, block.s+block.len-server.s) - server.s;
trim(&server);
if (server.len == 0)
return notfound;
return server;
}
// Get URI from the Contact: field.
static Bool
getContactURI(struct sip_msg* msg, struct sip_uri *uri, contact_t** _c)
{
if ((parse_headers(msg, HDR_CONTACT, 0) == -1) || !msg->contact)
return False;
if (!msg->contact->parsed && parse_contact(msg->contact) < 0) {
LOG(L_ERR, "error: mediaproxy/getContactURI(): cannot parse Contact header\n");
return False;
}
*_c = ((contact_body_t*)msg->contact->parsed)->contacts;
if (*_c == NULL) {
return False;
}
if (parse_uri((*_c)->uri.s, (*_c)->uri.len, uri) < 0 || uri->host.len <= 0) {
LOG(L_ERR, "error: mediaproxy/getContactURI(): cannot parse Contact URI\n");
return False;
}
return True;
}
// Functions to manipulate the SDP message body
static Bool
checkContentType(struct sip_msg *msg)
{
str type;
if (!msg->content_type) {
LOG(L_WARN, "warning: mediaproxy/checkContentType(): Content-Type "
"header missing! Let's assume the content is text/plain ;-)\n");
return True;
}
type = msg->content_type->body;
trim(&type);
if (strncasecmp(type.s, "application/sdp", 15) != 0) {
LOG(L_ERR, "error: mediaproxy/checkContentType(): invalid Content-Type "
"for SDP message\n");
return False;
}
if (!(isspace((int)type.s[15]) || type.s[15] == ';' || type.s[15] == 0)) {
LOG(L_ERR,"error: mediaproxy/checkContentType(): invalid character "
"after Content-Type!\n");
return False;
}
return True;
}
// Get the SDP message from SIP message and check it's Content-Type
// return -1 on error, 0 if empty message, 1 if message present and not empty
static int
getSDPMessage(struct sip_msg *msg, str *sdp)
{
sdp->s = get_body(msg);
if (sdp->s==NULL) {
LOG(L_ERR, "error: mediaproxy/getSDPMessage(): cannot get the SDP body from SIP message\n");
return -1;
}
sdp->len = msg->buf + msg->len - sdp->s;
if (sdp->len==0) {
// 0 length body is ok for ACK messages
if (!(msg->first_line.type == SIP_REQUEST &&
msg->first_line.u.request.method_value == METHOD_ACK)) {
LOG(L_ERR, "error: mediaproxy/getSDPMessage(): SDP message has zero length\n");
}
return 0;
}
if (!checkContentType(msg)) {
LOG(L_ERR, "error: mediaproxy/getSDPMessage(): content type is not `application/sdp'\n");
return -1;
}
return 1;
}
// will return the ip address present in a `c=' line in the given block
// returns: -1 on error, 0 if not found, 1 if found
static int
getMediaIPFromBlock(str *block, str *mediaip)
{
str tokens[3], zone;
char *ptr;
int count;
ptr = findLineStartingWith(block, "c=", False);
if (!ptr) {
mediaip->s = NULL;
mediaip->len = 0;
return 0;
}
zone.s = ptr + 2;
zone.len = findendline(zone.s, block->s + block->len - zone.s) - zone.s;
count = getStrTokens(&zone, tokens, 3);
if (count != 3) {
LOG(L_ERR, "error: mediaproxy/getMediaIPFromBlock(): invalid `c=' "
"line in SDP body\n");
return -1;
}
// can also check if tokens[1] == 'IP4'
*mediaip = tokens[2];
return 1;
}
// Get the session-level media IP defined by the SDP message
static Bool
getSessionLevelMediaIP(str *sdp, str *mediaip)
{
str block;
char *ptr;
// session IP can be found from the beginning up to the first media block
ptr = findLineStartingWith(sdp, "m=", False);
if (ptr) {
block.s = sdp->s;
block.len = ptr - block.s;
} else {
block = *sdp;
}
if (getMediaIPFromBlock(&block, mediaip) == -1) {
LOG(L_ERR, "error: mediaproxy/getSessionLevelMediaIP(): parse error "
"while getting session-level media IP from SDP body\n");
return False;
}
// it's not an error to be missing. it can be locally defined
// by each media stream. thus we return true even if not found
return True;
}
// will get all media streams
static int
getMediaStreams(str *sdp, str *sessionIP, StreamInfo *streams, int limit)
{
str tokens[2], block, zone;
char *ptr, *sdpEnd;
int i, count, streamCount, result;
sdpEnd = sdp->s + sdp->len;
for (i=0, block=*sdp; i<limit; i++) {
ptr = findLineStartingWith(&block, "m=", False);
if (!ptr)
break;
zone.s = ptr + 2;
zone.len = findendline(zone.s, sdpEnd - zone.s) - zone.s;
count = getStrTokens(&zone, tokens, 2);
if (count != 2) {
LOG(L_ERR, "error: mediaproxy/getMediaStreams(): invalid `m=' "
"line in SDP body\n");
return -1;
}
streams[i].type = tokens[0];
streams[i].port = tokens[1];
block.s = zone.s + zone.len;
block.len = sdpEnd - block.s;
}
streamCount = i;
for (i=0; i<streamCount; i++) {
block.s = streams[i].port.s;
if (i < streamCount-1)
block.len = streams[i+1].port.s - block.s;
else
block.len = sdpEnd - block.s;
result = getMediaIPFromBlock(&block, &(streams[i].ip));
if (result == -1) {
LOG(L_ERR, "error: mediaproxy/getMediaStreams(): parse error in "
"getting the contact IP for the media stream nr. %d\n", i+1);
return -1;
} else if (result == 0) {
if (sessionIP->s == NULL) {
LOG(L_ERR, "error: mediaproxy/getMediaStreams(): media stream "
"doesn't define a contact IP and the session-level IP "
"is missing\n");
return -1;
}
streams[i].ip = *sessionIP;
streams[i].localIP = 0;
} else {
streams[i].localIP = 1;
}
}
return streamCount;
}
static Bool
replaceElement(struct sip_msg *msg, str *oldElem, str *newElem)
{
struct lump* anchor;
char *buf;
if (newElem->len==oldElem->len &&
memcmp(newElem->s, oldElem->s, newElem->len)==0) {
return True;
}
buf = pkg_malloc(newElem->len);
if (!buf) {
LOG(L_ERR, "error: mediaproxy/replaceElement(): out of memory\n");
return False;
}
anchor = del_lump(msg, oldElem->s - msg->buf, oldElem->len, 0);
if (!anchor) {
LOG(L_ERR, "error: mediaproxy/replaceElement(): failed to delete old element\n");
pkg_free(buf);
return False;
}
memcpy(buf, newElem->s, newElem->len);
if (insert_new_lump_after(anchor, buf, newElem->len, 0)==0) {
LOG(L_ERR, "error: mediaproxy/replaceElement(): failed to insert new element\n");
pkg_free(buf);
return False;
}
return True;
}
// Functions dealing with the external mediaproxy helper
static inline size_t
uwrite(int fd, const void *buf, size_t count)
{
int len;
do
len = write(fd, buf, count);
while (len == -1 && errno == EINTR);
return len;
}
static inline size_t
uread(int fd, void *buf, size_t count)
{
int len;
do
len = read(fd, buf, count);
while (len == -1 && errno == EINTR);
return len;
}
static inline size_t
readall(int fd, void *buf, size_t count)
{
int len, total;
for (len=0, total=0; count-total>0; total+=len) {
len = uread(fd, (char*)buf+total, count-total);
if (len == -1) {
return -1;
}
if (len == 0) {
break;
}
}
return total;
}
static char*
sendMediaproxyCommand(char *command)
{
struct sockaddr_un addr;
int smpSocket, len;
static char buf[1024];
memset(&addr, 0, sizeof(addr));
addr.sun_family = AF_LOCAL;
strncpy(addr.sun_path, mediaproxySocket, sizeof(addr.sun_path) - 1);
#ifdef HAVE_SOCKADDR_SA_LEN
addr.sun_len = strlen(addr.sun_path);
#endif
smpSocket = socket(AF_LOCAL, SOCK_STREAM, 0);
if (smpSocket < 0) {
LOG(L_ERR, "error: mediaproxy/sendMediaproxyCommand(): can't create socket\n");
return NULL;
}
if (connect(smpSocket, (struct sockaddr*)&addr, sizeof(addr)) < 0) {
close(smpSocket);
LOG(L_ERR, "error: mediaproxy/sendMediaproxyCommand(): can't connect to MediaProxy\n");
return NULL;
}
len = uwrite(smpSocket, command, strlen(command));
if (len <= 0) {
close(smpSocket);
LOG(L_ERR, "error: mediaproxy/sendMediaproxyCommand(): can't send command to MediaProxy\n");
return NULL;
}
len = readall(smpSocket, buf, sizeof(buf)-1);
close(smpSocket);
if (len < 0) {
LOG(L_ERR, "error: mediaproxy/sendMediaproxyCommand(): can't read reply from MediaProxy\n");
return NULL;
}
buf[len] = 0;
return buf;
}
// Miscellaneous helper functions
/* Test if IP in `address' belongs to a RFC1918 network */
static inline int
rfc1918address(str *address)
{
struct in_addr inaddr;
uint32_t netaddr;
int i, result;
char c;
c = address->s[address->len];
address->s[address->len] = 0;
result = inet_aton(address->s, &inaddr);
address->s[address->len] = c;
if (result==0)
return -1; /* invalid address to test */
netaddr = ntohl(inaddr.s_addr);
for (i=0; rfc1918nets[i].name!=NULL; i++) {
if ((netaddr & rfc1918nets[i].mask)==rfc1918nets[i].address) {
return 1;
}
}
return 0;
}
#define isPrivateAddress(x) (rfc1918address(x)==1 ? 1 : 0)
// test for a public address is more complex (also need to test for
// address not in 0.0.0.0/8, 127.0.0.0/8, 224.0.0.0/4).
// #define isPublicAddress(x) (rfc1918address(x)==0 ? 1 : 0)
// Check if the requested asymmetrics file has changed and reload it if needed
static void
checkAsymmetricFile(AsymmetricClients *aptr)
{
char buf[512], errbuf[256], *which;
regex_t *re, **regs;
int i, size, code;
Bool firstTime = False;
struct stat statbuf;
FILE *file;
str line;
if (stat(aptr->file, &statbuf) < 0)
return; // ignore missing file
if (statbuf.st_mtime <= aptr->timestamp)
return; // not changed
// now we have work to do
which = (aptr == &sipAsymmetrics ? "SIP" : "RTP");
if (!aptr->clients) {
// if we are here the first time allocate memory to hold the regexps
// initial size of array
// (hopefully not reached so we won't need to realloc)
size = 32;
aptr->clients = (regex_t**)pkg_malloc(size*sizeof(regex_t**));
if (!aptr->clients) {
LOG(L_WARN, "warning: mediaproxy/checkAsymmetricFile() cannot "
"allocate memory for the %s asymmetric client list. "
"%s asymmetric clients will not be handled properly.\n",
which, which);
return; // ignore as it is not fatal
}
aptr->size = size;
aptr->count = 0;
firstTime = True;
} else {
// else clean old stuff
for (i=0; i<aptr->count; i++) {
regfree(aptr->clients[i]);
pkg_free(aptr->clients[i]);
aptr->clients[i] = NULL;
}
aptr->count = 0;
}
// read new
file = fopen(aptr->file, "r");
i = 0; // this will count on which line are we in the file
while (!feof(file)) {
if (!fgets(buf, 512, file))
break;
i++;
line.s = buf;
line.len = strlen(buf);
trim(&line);
if (line.len == 0 || line.s[0] == '#')
continue; // comment or empty line. ignore
line.s[line.len] = 0;
re = (regex_t*)pkg_malloc(sizeof(regex_t));
if (!re) {
LOG(L_WARN, "warning: mediaproxy/checkAsymmetricFile(): "
"cannot allocate memory for all the %s asymmetric "
"clients listed in file. Some of them will not be "
"handled properly.\n", which);
break;
}
code = regcomp(re, line.s, REG_EXTENDED|REG_ICASE|REG_NOSUB);
if (code == 0) {
if (aptr->count+1 > aptr->size) {
size = aptr->size * 2;
regs = aptr->clients;
regs = (regex_t**)pkg_realloc(regs, size*sizeof(regex_t**));
if (!regs) {
LOG(L_WARN, "warning: mediaproxy/checkAsymmetricFile(): "
"cannot allocate memory for all the %s asymmetric "
"clients listed in file. Some of them will not be "
"handled properly.\n", which);
break;
}
aptr->clients = regs;
aptr->size = size;
}
aptr->clients[aptr->count] = re;
aptr->count++;
} else {
regerror(code, re, errbuf, 256);
LOG(L_WARN, "warning: mediaproxy/checkAsymmetricFile(): cannot "
"compile line %d of the %s asymmetric clients file into a "
"regular expression (will be ignored): %s", i, which, errbuf);
pkg_free(re);
}
}
aptr->timestamp = statbuf.st_mtime;
LOG(L_INFO, "info: mediaproxy: %sloaded %s asymmetric clients file "
"containing %d entr%s.\n", firstTime ? "" : "re",
which, aptr->count, aptr->count==1 ? "y" : "ies");
}
//
// This is a hack. Until a I find a better way to allow all children to update
// the asymmetric client list when the files change on disk, it stays as it is
// A timer registered from mod_init() only runs in one of the ser processes
// and the others will always see the file that was read at startup.
//
#include <time.h>
#define CHECK_INTERVAL 5
static void
periodicAsymmetricsCheck(void)
{
static time_t last = 0;
time_t now;
// this is not guaranteed to run at every CHECK_INTERVAL.
// it is only guaranteed that the files won't be checked more often.
now = time(NULL);
if (now > last + CHECK_INTERVAL) {
checkAsymmetricFile(&sipAsymmetrics);
checkAsymmetricFile(&rtpAsymmetrics);
last = now;
}
}
static Bool
isSIPAsymmetric(str userAgent)
{
int i, code;
char c;
periodicAsymmetricsCheck();
if (!sipAsymmetrics.clients || sipAsymmetrics.count==0)
return False;
c = userAgent.s[userAgent.len];
userAgent.s[userAgent.len] = 0;
for (i=0; i<sipAsymmetrics.count; i++) {
code = regexec(sipAsymmetrics.clients[i], userAgent.s, 0, NULL, 0);
if (code == 0) {
userAgent.s[userAgent.len] = c;
return True;
} else if (code != REG_NOMATCH) {
char errbuf[256];
regerror(code, sipAsymmetrics.clients[i], errbuf, 256);
LOG(L_WARN, "warning: mediaproxy/isSIPAsymmetric() failed to "
"match regexp: %s\n", errbuf);
}
}
userAgent.s[userAgent.len] = c;
return False;
}
static Bool
isRTPAsymmetric(str userAgent)
{
int i, code;
char c;
periodicAsymmetricsCheck();
if (!rtpAsymmetrics.clients || rtpAsymmetrics.count==0)
return False;
c = userAgent.s[userAgent.len];
userAgent.s[userAgent.len] = 0;
for (i=0; i<rtpAsymmetrics.count; i++) {
code = regexec(rtpAsymmetrics.clients[i], userAgent.s, 0, NULL, 0);
if (code == 0) {
userAgent.s[userAgent.len] = c;
return True;
} else if (code != REG_NOMATCH) {
char errbuf[256];
regerror(code, rtpAsymmetrics.clients[i], errbuf, 256);
LOG(L_WARN, "warning: mediaproxy/isRTPAsymmetric() failed to "
"match regexp: %s\n", errbuf);
}
}
userAgent.s[userAgent.len] = c;
return False;
}
// NAT tests
/* tests if address of signaling is different from address in 1st Via field */
static Bool
testSourceAddress(struct sip_msg* msg)
{
Bool diffIP, diffPort;
int via1Port;
diffIP = received_test(msg);
if (isSIPAsymmetric(getUserAgent(msg))) {
// ignore port test for asymmetric clients (it's always different)
diffPort = False;
} else {
via1Port = (msg->via1->port ? msg->via1->port : SIP_PORT);
diffPort = (msg->rcv.src_port != via1Port);
}
return (diffIP || diffPort);
}
/* tests if Contact field contains a private IP address as defined in RFC1918 */
static Bool
testPrivateContact(struct sip_msg* msg)
{
struct sip_uri uri;
contact_t* contact;
if (!getContactURI(msg, &uri, &contact))
return False;
return isPrivateAddress(&(uri.host));
}
/* tests if top Via field contains a private IP address as defined in RFC1918 */
static Bool
testPrivateVia(struct sip_msg* msg)
{
return isPrivateAddress(&(msg->via1->host));
}
#include "functions.h"
/* The public functions that are exported by this module */
static int
ClientNatTest(struct sip_msg* msg, char* str1, char* str2)
{
int tests, i;
tests = (int)(long)str1;
for (i=0; natTests[i].test!=NTNone; i++) {
if ((tests & natTests[i].test)!=0 && natTests[i].proc(msg)) {
return 1;
}
}
return -1; // all failed
}
static int
EndMediaSession(struct sip_msg* msg, char* str1, char* str2)
{
char *command, *result;
str callId;
if (!getCallId(msg, &callId)) {
LOG(L_ERR, "error: end_media_session(): can't get Call-Id\n");
return -1;
}
command = pkg_malloc(callId.len + 20);
if (command == NULL) {
LOG(L_ERR, "error: end_media_session(): out of memory\n");
return -1;
}
sprintf(command, "delete %.*s info=\n", callId.len, callId.s);
result = sendMediaproxyCommand(command);
pkg_free(command);
return result==NULL ? -1 : 1;
}
#define MSG_UNKNOWN 0
#define MSG_INVITE 1
#define MSG_ACK 2
#define MSG_REPLY 3
static int
UseMediaProxy(struct sip_msg* msg, char* str1, char* str2)
{
str sdp, sessionIP, callId, fromDomain, toDomain, userAgent, tokens[64];
str fromAddr, toAddr, fromTag, toTag;
char *clientIP, *ptr, *command, *result, *agent, *fromType, *toType, *info;
int streamCount, i, port, count, portCount, cmdlen, infolen, success, type;
StreamInfo streams[64], stream;
Bool request;
if (msg->first_line.type == SIP_REQUEST) {
if (msg->first_line.u.request.method_value == METHOD_INVITE)
type = MSG_INVITE;
else if (msg->first_line.u.request.method_value == METHOD_ACK)
type = MSG_ACK;
else
type = MSG_UNKNOWN;
} else if (msg->first_line.type == SIP_REPLY) {
type = MSG_REPLY;
} else {
type = MSG_UNKNOWN;
}
if (type==MSG_INVITE || type==MSG_ACK) {
request = True;
} else if (type==MSG_REPLY) {
request = False;
} else {
return -1;
}
if (!getCallId(msg, &callId)) {
LOG(L_ERR, "error: use_media_proxy(): can't get Call-Id\n");
return -1;
}
success = getSDPMessage(msg, &sdp);
if (success==0 && type==MSG_ACK) {
return 1; // nothing to do. it's ok for ACK to not have a SDP body
} else if (success <= 0) {
LOG(L_ERR, "error: use_media_proxy(): failed to get the SDP message\n");
return -1;
}
if (!getSessionLevelMediaIP(&sdp, &sessionIP)) {
LOG(L_ERR, "error: use_media_proxy(): error parsing the SDP message\n");
return -1;
}
streamCount = getMediaStreams(&sdp, &sessionIP, streams, 64);
if (streamCount == -1) {
LOG(L_ERR, "error: use_media_proxy(): can't extract media streams "
"from the SDP message\n");
return -1;
}
if (streamCount == 0) {
// there are no media streams. we have nothing to do.
return 1;
}
fromDomain = getFromDomain(msg);
fromType = (isFromLocal(msg, NULL, NULL)>0) ? "local" : "remote";
fromAddr = getFromAddress(msg);
toAddr = getToAddress(msg);
fromTag = getFromTag(msg);
toTag = getToTag(msg);
userAgent = getUserAgent(msg);
if (request) {
toDomain = getDestinationDomain(msg); // call only for requests
toType = (isDestinationLocal(msg, NULL, NULL)>0) ? "local" : "remote";
} else {
toDomain = getToDomain(msg);
toType = "unknown"; //there's no function to check if To domain is local
}
clientIP = ip_addr2a(&msg->rcv.src_ip);
infolen = fromAddr.len + toAddr.len + fromTag.len + toTag.len + 64;
cmdlen = callId.len + strlen(clientIP) + fromDomain.len + toDomain.len +
userAgent.len*3 + infolen + 128;
for (i=0; i<streamCount; i++) {
stream = streams[i];
cmdlen += stream.ip.len + stream.port.len + stream.type.len + 4;
}
command = pkg_malloc(cmdlen);
if (!command) {
LOG(L_ERR, "error: use_media_proxy(): out of memory\n");
return -1;
}
if (request)
count = sprintf(command, "request %.*s", callId.len, callId.s);
else
count = sprintf(command, "lookup %.*s", callId.len, callId.s);
for (i=0, ptr=command+count; i<streamCount; i++) {
char c = (i==0 ? ' ' : ',');
count = sprintf(ptr, "%c%.*s:%.*s:%.*s", c,
streams[i].ip.len, streams[i].ip.s,
streams[i].port.len, streams[i].port.s,
streams[i].type.len, streams[i].type.s);
ptr += count;
}
agent = encodeQuopri(userAgent);
if (!agent) {
LOG(L_ERR, "error: use_media_proxy(): out of memory\n");
pkg_free(command);
return -1;
}
info = pkg_malloc(infolen);
if (!info) {
LOG(L_ERR, "error: use_media_proxy(): out of memory\n");
pkg_free(command);
pkg_free(agent);
return -1;
}
sprintf(info, "from:%.*s,to:%.*s,fromtag:%.*s,totag:%.*s",
fromAddr.len, fromAddr.s, toAddr.len, toAddr.s,
fromTag.len, fromTag.s, toTag.len, toTag.s);
if (isRTPAsymmetric(userAgent)) {
strcat(info, ",asymmetric");
}
snprintf(ptr, command + cmdlen - ptr, " %s %.*s %s %.*s %s %s info=%s\n",
clientIP, fromDomain.len, fromDomain.s, fromType,
toDomain.len, toDomain.s, toType, agent, info);
pkg_free(info);
pkg_free(agent);
result = sendMediaproxyCommand(command);
pkg_free(command);
if (result == NULL)
return -1;
count = getTokens(result, tokens, sizeof(tokens)/sizeof(str));
if (count == 0) {
LOG(L_ERR, "error: use_media_proxy(): empty response from mediaproxy\n");
return -1;
} else if (count<streamCount+1) {
if (request) {
LOG(L_ERR, "error: use_media_proxy(): insufficient ports returned "
"from mediaproxy: got %d, expected %d\n", count-1, streamCount);
return -1;
} else {
LOG(L_WARN, "warning: use_media_proxy(): broken client. Called UA "
"added extra media stream(s) in the OK reply\n");
}
}
if (sessionIP.s && !isAnyAddress(sessionIP)) {
success = replaceElement(msg, &sessionIP, &tokens[0]);
if (!success) {
LOG(L_ERR, "error: use_media_proxy(): failed to replace "
"session-level media IP in SDP body\n");
return -1;
}
}
portCount = min(count-1, streamCount);
for (i=0; i<portCount; i++) {
// check. is this really necessary?
port = strtoint(&tokens[i+1]);
if (port <= 0 || port > 65535) {
LOG(L_ERR, "error: use_media_proxy(): invalid port returned "
"by mediaproxy: %.*s\n", tokens[i+1].len, tokens[i+1].s);
//return -1;
continue;
}
if (streams[i].port.len!=1 || streams[i].port.s[0]!='0') {
success = replaceElement(msg, &(streams[i].port), &tokens[i+1]);
if (!success) {
LOG(L_ERR, "error: use_media_proxy(): failed to replace "
"port in media stream nr. %d\n", i+1);
return -1;
}
}
if (streams[i].localIP && !isAnyAddress(streams[i].ip)) {
success = replaceElement(msg, &(streams[i].ip), &tokens[0]);
if (!success) {
LOG(L_ERR, "error: use_media_proxy(): failed to replace "
"IP address in media stream nr. %d\n", i+1);
return -1;
}
}
}
return 1;
}
/* Module management: initialization/destroy/function-parameter-fixing/... */
static int
mod_init(void)
{
bind_usrloc_t ul_bind_usrloc;
isFromLocal = (CheckLocalPartyProc)find_export("is_from_local", 0, 0);
isDestinationLocal = (CheckLocalPartyProc)find_export("is_uri_host_local", 0, 0);
if (!isFromLocal || !isDestinationLocal) {
LOG(L_ERR, "error: mediaproxy/mod_init(): can't find is_from_local "
"and/or is_uri_host_local functions. Check if domain.so is loaded\n");
return -1;
}
if (natpingInterval > 0) {
ul_bind_usrloc = (bind_usrloc_t)find_export("ul_bind_usrloc", 1, 0);
if (!ul_bind_usrloc) {
LOG(L_ERR, "error: mediaproxy/mod_init(): can't find the usrloc "
"module. Check if usrloc.so is loaded.\n");
return -1;
}
if (ul_bind_usrloc(&userLocation) < 0) {
LOG(L_ERR, "error: mediaproxy/mod_init(): can't access the usrloc module.\n");
return -1;
}
register_timer(pingClients, NULL, natpingInterval);
}
checkAsymmetricFile(&sipAsymmetrics);
checkAsymmetricFile(&rtpAsymmetrics);
// children won't benefit from this. figure another way
//register_timer(checkAsymmetricFiles, NULL, 5);
return 0;
}
// convert string parameter to integer for functions that expect an integer
static int
fixstring2int(void **param, int param_count)
{
unsigned long number;
int err;
if (param_count == 1) {
number = str2s(*param, strlen(*param), &err);
if (err == 0) {
pkg_free(*param);
*param = (void*)number;
return 0;
} else {
LOG(L_ERR, "error: mediaproxy/fixstring2int(): bad number `%s'\n",
(char*)(*param));
return E_CFG;
}
}
return 0;
}
syntax highlighted by Code2HTML, v. 0.9.1