Files
siproxd/src/sip_utils.c
Thomas Ries 5735a8161e - rtpproxy_masq: fixed an issue in port allocation
which lead to syslog entries from IPCHAINS complaining
  about 'already used connection'
2003-10-08 20:15:00 +00:00

619 lines
17 KiB
C

/*
Copyright (C) 2002 Thomas Ries <tries@gmx.net>
This file is part of Siproxd.
Siproxd 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.
Siproxd 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 Siproxd; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include "config.h"
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <time.h>
#include <signal.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <net/if.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <pwd.h>
#include <osipparser2/osip_parser.h>
#include <osipparser2/osip_port.h>
#include "siproxd.h"
#include "rewrite_rules.h"
#include "log.h"
static char const ident[]="$Id: " __FILE__ ": " PACKAGE "-" VERSION "-"\
BUILDSTR " $";
/* configuration storage */
extern struct siproxd_config configuration;
extern int h_errno;
extern int sip_socket; /* sending SIP datagrams */
/*
* create a reply template from an given SIP request
*
* RETURNS a pointer to osip_message_t
*/
osip_message_t *msg_make_template_reply (osip_message_t * request, int code) {
osip_message_t *response;
int pos;
osip_message_init (&response);
response->message=NULL;
osip_message_set_version (response, osip_strdup ("SIP/2.0"));
osip_message_set_status_code (response, code);
osip_message_set_reason_phrase (response,
osip_strdup(osip_message_get_reason (code)));
if (request->to==NULL) {
ERROR("msg_make_template_reply: empty To in request header");
}
if (request->from==NULL) {
ERROR("msg_make_template_reply: empty From in request header");
}
osip_to_clone (request->to, &response->to);
osip_from_clone (request->from, &response->from);
/* via headers */
pos = 0;
while (!osip_list_eol (request->vias, pos)) {
char *tmp;
osip_via_t *via;
via = (osip_via_t *) osip_list_get (request->vias, pos);
osip_via_to_str (via, &tmp);
osip_message_set_via (response, tmp);
osip_free (tmp);
pos++;
}
osip_call_id_clone(request->call_id,&response->call_id);
osip_cseq_clone(request->cseq,&response->cseq);
return response;
}
/*
* check for a via loop.
* It checks for the presense of a via entry that holds one of
* my IP addresses and is *not* the topmost via.
*
* RETURNS
* STS_TRUE if loop detected
* STS_FALSE if no loop
*/
int check_vialoop (osip_message_t *my_msg) {
/*
!!! actually this is a problematic one.
1) for requests, I must search the whole VIA list
(topmost via is the previos station in the path)
2) for responses I must skip the topmost via, as this is mine
(and will be removed later on)
3) What happens if we have 'clashes' with private addresses??
From that point of view, siproxd *should* not try to
check against it's local IF addresses if thei are private.
this then of course again can lead to a endless loop...
can we use something like a Tag in via headers?? (a veriy likely
to-be-unique ID)
*/
int sts;
int pos;
int found_own_via;
found_own_via=0;
pos = 1; /* for detecting a loop, don't check the first entry
as this is my own VIA! */
while (!osip_list_eol (my_msg->vias, pos)) {
osip_via_t *via;
via = (osip_via_t *) osip_list_get (my_msg->vias, pos);
sts = is_via_local (via);
if (sts == STS_TRUE) found_own_via=1;
pos++;
}
return (found_own_via)? STS_TRUE : STS_FALSE;
}
/*
* check if a given osip_via_t is local. I.e. its address is owned
* by my inbound or outbound interface
*
* RETURNS
* STS_TRUE if the given VIA is one of my interfaces
* STS_FALSE otherwise
*/
int is_via_local (osip_via_t *via) {
int sts, found;
struct in_addr addr_via, addr_myself;
char *my_interfaces[]=
{ configuration.inbound_if, configuration.outbound_if, (char*)-1 };
int port;
int i;
char *ptr;
if (via==NULL) {
ERROR("called is_via_local with NULL via");
return STS_FALSE;
}
DEBUGC(DBCLASS_BABBLE,"via name %s",via->host);
if (inet_aton(via->host,&addr_via) == 0) {
/* need name resolution */
sts=get_ip_by_host(via->host, &addr_via);
if (sts == STS_FAILURE) {
DEBUGC(DBCLASS_DNS, "is_via_local: cannot resolve VIA [%s]",
via->host);
return STS_FAILURE;
}
}
found=0;
for (i=0; ; i++) {
/*
* try to search by interface name first
*/
ptr=my_interfaces[i];
if (ptr==(char*)-1) break; /* end of list mark */
if (ptr) {
DEBUGC(DBCLASS_BABBLE,"resolving IP of interface %s",ptr);
sts = get_ip_by_ifname(ptr, &addr_myself);
}
/* check the extracted VIA against my own host addresses */
if (via->port) port=atoi(via->port);
else port=SIP_PORT;
if ( (memcmp(&addr_myself, &addr_via, sizeof(addr_myself))==0) &&
(port == configuration.sip_listen_port) ) {
DEBUG("address match [%s] <-> [%s]", inet_ntoa(addr_myself),
inet_ntoa(addr_via));
found=1;
break;
}
}
return (found)? STS_TRUE : STS_FALSE;
}
/*
* compares two URLs
* (by now, only hostname and username are compared)
*
* RETURNS
* STS_SUCCESS if equal
* STS_FAILURE if non equal or error
*/
int compare_url(osip_uri_t *url1, osip_uri_t *url2) {
int sts;
struct in_addr addr1, addr2;
/* sanity checks */
if ((url1 == NULL) || (url2 == NULL)) {
ERROR("compare_url: NULL ptr: url1=0x%p, url2=0x%p",url1, url2);
return STS_FAILURE;
}
/* sanity checks: host part is a MUST */
if ((url1->host == NULL) || (url2->host == NULL)) {
ERROR("compare_url: NULL ptr: url1->host=0x%p, url2->host=0x%p",
url1->host, url2->host);
return STS_FAILURE;
}
/* get the IP addresses from the (possible) hostnames */
sts=get_ip_by_host(url1->host, &addr1);
if (sts == STS_FAILURE) {
DEBUGC(DBCLASS_PROXY, "compare_url: cannot resolve host [%s]",
url1->host);
return STS_FAILURE;
}
sts=get_ip_by_host(url2->host, &addr2);
if (sts == STS_FAILURE) {
DEBUGC(DBCLASS_PROXY, "compare_url: cannot resolve host [%s]",
url2->host);
return STS_FAILURE;
}
/* Broken(?) MSN messenger - does not supply a user name part.
So we simply compare the host part then */
if ((url1->username == NULL) || (url2->username == NULL)) {
/* let's be nice to Billy boy and don't complain evey time ;-)
// WARN("compare_url: NULL username pointer: MSN messenger is known to "
// "trigger this one!"); */
DEBUGC(DBCLASS_PROXY, "comparing broken urls (no user): "
"%s[%s] -> %s[%s]",
url1->host, inet_ntoa(addr1), url2->host, inet_ntoa(addr2));
if (memcmp(&addr1, &addr2, sizeof(addr1))==0) {
sts = STS_SUCCESS;
} else {
sts = STS_FAILURE;
}
return sts;
}
/* we have a proper URL */
/* comparison of hosts should be based on IP addresses, no? */
DEBUGC(DBCLASS_PROXY, "comparing urls: %s@%s[%s] -> %s@%s[%s]",
url1->username, url1->host, inet_ntoa(addr1),
url2->username, url2->host, inet_ntoa(addr2));
if ((strcmp(url1->username, url2->username)==0) &&
(memcmp(&addr1, &addr2, sizeof(addr1))==0)) {
sts = STS_SUCCESS;
} else {
sts = STS_FAILURE;
}
return sts;
}
/*
* compares two Call IDs
* (by now, only hostname and username are compared)
*
* RETURNS
* STS_SUCCESS if equal
* STS_FAILURE if non equal or error
*/
int compare_callid(osip_call_id_t *cid1, osip_call_id_t *cid2) {
if ((cid1==0) || (cid2==0)) {
ERROR("compare_callid: NULL ptr: cid1=0x%p, cid2=0x%p",cid1, cid2);
return STS_FAILURE;
}
/*
* Check number part: if present must be equal,
* if not present, must be not present in both cids
*/
if (cid1->number && cid2->number) {
/* have both numbers */
if (strcmp(cid1->number, cid2->number) != 0) goto mismatch;
} else {
/* at least one number missing, make sure that both are empty */
if ( (cid1->number && (cid1->number[0]!='\0')) ||
(cid2->number && (cid2->number[0]!='\0'))) {
goto mismatch;
}
}
/*
* Check host part: if present must be equal,
* if not present, must be not present in both cids
*/
if (cid1->host && cid2->host) {
/* have both hosts */
if (strcmp(cid1->host, cid2->host) != 0) goto mismatch;
} else {
/* at least one host missing, make sure that both are empty */
if ( (cid1->host && (cid1->host[0]!='\0')) ||
(cid2->host && (cid2->host[0]!='\0'))) {
goto mismatch;
}
}
DEBUGC(DBCLASS_BABBLE, "comparing callid - matched: "
"%s@%s <-> %s@%s",
cid1->number, cid1->host, cid2->number, cid2->host);
return STS_SUCCESS;
mismatch:
DEBUGC(DBCLASS_BABBLE, "comparing callid - mismatch: "
"%s@%s <-> %s@%s",
cid1->number, cid1->host, cid2->number, cid2->host);
return STS_FAILURE;
}
/*
* check if a given request is addressed to local. I.e. it is addressed
* to the porxy itself (IP of my inbound or outbound interface, same port)
*
* RETURNS
* STS_TRUE if the request is addressed local
* STS_FALSE otherwise
*/
int is_sipuri_local (osip_message_t *sip) {
int sts, found;
struct in_addr addr_uri, addr_myself;
char *my_interfaces[]=
{ configuration.inbound_if, configuration.outbound_if, (char*)-1 };
int port;
int i;
char *ptr;
if (sip==NULL) {
ERROR("called is_sipuri_local with NULL sip");
return STS_FALSE;
}
if (!sip || !sip->req_uri) {
ERROR("is_sipuri_local: no request URI present");
return STS_FALSE;
}
DEBUGC(DBCLASS_DNS,"check for local SIP URI %s:%s",
sip->req_uri->host? sip->req_uri->host : "*NULL*",
sip->req_uri->port? sip->req_uri->port : "*NULL*");
if (inet_aton(sip->req_uri->host, &addr_uri) == 0) {
/* need name resolution */
get_ip_by_host(sip->req_uri->host, &addr_uri);
}
found=0;
for (i=0; ; i++) {
/*
* try to search by interface name first
*/
ptr=my_interfaces[i];
if (ptr==(char*)-1) break; /* end of list mark */
if (ptr) {
DEBUGC(DBCLASS_BABBLE,"resolving IP of interface %s",ptr);
sts = get_ip_by_ifname(ptr, &addr_myself);
}
/* check the extracted HOST against my own host addresses */
if (sip->req_uri->port) {
port=atoi(sip->req_uri->port);
} else {
port=SIP_PORT;
}
if ( (memcmp(&addr_myself, &addr_uri, sizeof(addr_myself))==0) &&
(port == configuration.sip_listen_port) ) {
DEBUG("address match [%s] <-> [%s]", inet_ntoa(addr_myself),
inet_ntoa(addr_uri));
found=1;
break;
}
}
DEBUGC(DBCLASS_DNS, "SIP URI is %slocal", found? "":"not ");
return (found)? STS_TRUE : STS_FALSE;
}
/*
* check if a given request (outbound -> inbound) shall its
* request URI get rewritten based upon our UA knowledge
*
* RETURNS
* STS_TRUE if to be rewritten
* STS_FALSE otherwise
*/
int check_rewrite_rq_uri (osip_message_t *sip) {
int i, j, sts;
int dflidx;
osip_header_t *ua_hdr;
/* get index of default entry */
dflidx=(sizeof(RQ_rewrite)/sizeof(RQ_rewrite[0])) - 1;
/* check fort existence of method */
if ((sip==NULL) ||
(sip->sip_method==NULL)) {
ERROR("check_rewrite_rq_uri: got NULL method");
return STS_FALSE;
}
/* extract UA string */
osip_message_get_user_agent (sip, 0, &ua_hdr);
if ((ua_hdr==NULL) || (ua_hdr->hvalue==NULL)) {
WARN("check_rewrite_rq_uri: NULL UA in Header, using default");
i=dflidx;
} else {
/* loop through the knowledge base */
for (i=0; RQ_rewrite[i].UAstring; i++) {
if (strncmp(RQ_rewrite[i].UAstring, ua_hdr->hvalue,
sizeof(RQ_rewrite[i].UAstring))==0) {
DEBUGC(DBCLASS_SIP, "got knowledge entry for [%s]",
ua_hdr->hvalue);
break;
}
} /* for i */
} /* if ua_hdr */
for (j=0; RQ_method[j].name; j++) {
if (strncmp(RQ_method[j].name,
sip->sip_method, RQ_method[j].size)==0) {
if (RQ_rewrite[i].action[j] >= 0) {
sts = (RQ_rewrite[i].action[j])? STS_TRUE: STS_FALSE;
} else {
sts = (RQ_rewrite[dflidx].action[j])? STS_TRUE: STS_FALSE;
}
DEBUGC(DBCLASS_SIP, "check_rewrite_rq_uri: [%s:%s, i=%i, j=%i] "
"got action %s",
(sip && sip->sip_method) ?
sip->sip_method : "*NULL*",
(ua_hdr && ua_hdr->hvalue)? ua_hdr->hvalue:"*NULL*",
i, j, (sts==STS_TRUE)? "rewrite":"norewrite");
return sts;
}
} /* for j */
WARN("check_rewrite_rq_uri: didn't get a hit of the method [%s]",
sip->sip_method);
return STS_FALSE;
}
/*
* PROXY_GEN_RESPONSE
*
* send an proxy generated response back to the client.
* Only errors are reported from the proxy itself.
* code = SIP result code to deliver
*
* RETURNS
* STS_SUCCESS on success
* STS_FAILURE on error
*/
int sip_gen_response(osip_message_t *request, int code) {
osip_message_t *response;
int sts;
osip_via_t *via;
int port;
char *buffer;
struct in_addr addr;
/* create the response template */
if ((response=msg_make_template_reply(request, code))==NULL) {
ERROR("proxy_response: error in msg_make_template_reply");
return STS_FAILURE;
}
/* we must check if first via has x.x.x.x address. If not, we must resolve it */
osip_message_get_via (response, 0, &via);
if (via == NULL)
{
ERROR("proxy_response: Cannot send response - no via field");
return STS_FAILURE;
}
/* name resolution */
if (inet_aton(via->host, &addr) == 0)
{
/* need name resolution */
DEBUGC(DBCLASS_DNS,"resolving name:%s",via->host);
sts = get_ip_by_host(via->host, &addr);
if (sts == STS_FAILURE) {
DEBUGC(DBCLASS_PROXY, "sip_gen_response: cannot resolve via [%s]",
via->host);
return STS_FAILURE;
}
}
sts = osip_message_to_str(response, &buffer);
if (sts != 0) {
ERROR("proxy_response: msg_2char failed");
return STS_FAILURE;
}
if (via->port) {
port=atoi(via->port);
} else {
port=SIP_PORT;
}
/* send to destination */
sipsock_send_udp(&sip_socket, addr, port,
buffer, strlen(buffer), 1);
/* free the resources */
osip_message_free(response);
osip_free(buffer);
return STS_SUCCESS;
}
/*
* PROXY_ADD_MYVIA
*
* interface == IF_OUTBOUND, IF_INBOUND
*
* RETURNS
* STS_SUCCESS on success
* STS_FAILURE on error
*/
int sip_add_myvia (osip_message_t *request, int interface) {
struct in_addr addr;
char tmp[URL_STRING_SIZE];
osip_via_t *via;
int sts;
if (interface == IF_OUTBOUND) {
sts = get_ip_by_ifname(configuration.outbound_if, &addr);
if (sts == STS_FAILURE) {
ERROR("can't find outbound interface %s - configuration error?",
configuration.outbound_if);
return STS_FAILURE;
}
} else {
sts = get_ip_by_ifname(configuration.inbound_if, &addr);
if (sts == STS_FAILURE) {
ERROR("can't find inbound interface %s - configuration error?",
configuration.inbound_if);
return STS_FAILURE;
}
}
sprintf(tmp, "SIP/2.0/UDP %s:%i", inet_ntoa(addr),
configuration.sip_listen_port);
DEBUGC(DBCLASS_BABBLE,"adding VIA:%s",tmp);
sts = osip_via_init(&via);
if (sts!=0) return STS_FAILURE; /* allocation failed */
sts = osip_via_parse(via, tmp);
if (sts!=0) return STS_FAILURE;
osip_list_add(request->vias,via,0);
return STS_SUCCESS;
}
/*
* PROXY_DEL_MYVIA
*
* RETURNS
* STS_SUCCESS on success
* STS_FAILURE on error
*/
int sip_del_myvia (osip_message_t *response) {
osip_via_t *via;
int sts;
DEBUGC(DBCLASS_PROXY,"deleting topmost VIA");
via = osip_list_get (response->vias, 0);
if ( is_via_local(via) == STS_FALSE ) {
ERROR("I'm trying to delete a VIA but it's not mine! host=%s",via->host);
return STS_FAILURE;
}
sts = osip_list_remove(response->vias, 0);
osip_via_free (via);
return STS_SUCCESS;
}