siproxd/src/register.c

673 lines
21 KiB
C

/*
Copyright (C) 2002-2005 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 <stdio.h>
#include <time.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <osipparser2/osip_parser.h>
#include "siproxd.h"
#include "log.h"
static char const ident[]="$Id$";
/* configuration storage */
extern struct siproxd_config configuration;
/* URL mapping table */
struct urlmap_s urlmap[URLMAP_SIZE];
/* time of last save */
static time_t last_save=0;
extern int errno;
/*
* initialize the URL mapping table
*/
void register_init(void) {
FILE *stream;
int sts, size, i;
char buff[128];
memset(urlmap, 0, sizeof(urlmap));
if (configuration.registrationfile) {
stream = fopen(configuration.registrationfile, "r");
if (!stream) {
/*
* the file does not exist, or the size was incorrect,
* delete it and start from scratch
*/
unlink(configuration.registrationfile);
WARN("registration file not found, starting with empty table");
} else {
/* read the url table from file */
for (i=0;i < URLMAP_SIZE; i++) {
fgets(buff, sizeof(buff), stream);
sts=sscanf(buff, "***:%i:%i", &urlmap[i].active, &urlmap[i].expires);
if (urlmap[i].active) {
osip_uri_init(&urlmap[i].true_url);
osip_uri_init(&urlmap[i].masq_url);
osip_uri_init(&urlmap[i].reg_url);
#define R(X) {\
fgets(buff, sizeof(buff), stream);\
buff[sizeof(buff)-1]='\0';\
if (strchr(buff, 10)) *strchr(buff, 10)='\0';\
if (strchr(buff, 13)) *strchr(buff, 13)='\0';\
if (strlen(buff) > 0) {\
size = strlen(buff);\
X =(char*)malloc(size+1);\
sts=sscanf(buff,"%s",X);\
} else {\
X = NULL;\
}\
}
R(urlmap[i].true_url->scheme);
R(urlmap[i].true_url->username);
R(urlmap[i].true_url->host);
R(urlmap[i].true_url->port);
R(urlmap[i].masq_url->scheme);
R(urlmap[i].masq_url->username);
R(urlmap[i].masq_url->host);
R(urlmap[i].masq_url->port);
R(urlmap[i].reg_url->scheme);
R(urlmap[i].reg_url->username);
R(urlmap[i].reg_url->host);
R(urlmap[i].reg_url->port);
}
}
fclose(stream);
}
}
/* initialize save-timer */
time(&last_save);
return;
}
/*
* shut down the URL mapping table
*/
void register_save(void) {
int i;
FILE *stream;
if (configuration.registrationfile) {
/* write urlmap back to file */
stream = fopen(configuration.registrationfile, "w+");
if (!stream) {
/* try to unlink it and open again */
unlink(configuration.registrationfile);
stream = fopen(configuration.registrationfile, "w+");
/* open file for write failed, complain */
if (!stream) {
ERROR("unable to write registration file");
return;
}
}
for (i=0;i < URLMAP_SIZE; i++) {
fprintf(stream, "***:%i:%i\n", urlmap[i].active, urlmap[i].expires);
if (urlmap[i].active) {
#define W(X) fprintf(stream, "%s\n", (X)? X:"");
W(urlmap[i].true_url->scheme);
W(urlmap[i].true_url->username);
W(urlmap[i].true_url->host);
W(urlmap[i].true_url->port);
W(urlmap[i].masq_url->scheme);
W(urlmap[i].masq_url->username);
W(urlmap[i].masq_url->host);
W(urlmap[i].masq_url->port);
W(urlmap[i].reg_url->scheme);
W(urlmap[i].reg_url->username);
W(urlmap[i].reg_url->host);
W(urlmap[i].reg_url->port);
}
}
fclose(stream);
}
return;
}
/*
* handles register requests and updates the URL mapping table
*
* RETURNS:
* STS_SUCCESS : successfully registered
* STS_FAILURE : registration failed
* STS_NEED_AUTH : authentication needed
*/
int register_client(sip_ticket_t *ticket, int force_lcl_masq) {
int i, j, n, sts;
int expires;
time_t time_now;
osip_contact_t *contact;
osip_uri_t *url1_to, *url1_contact=NULL;
osip_uri_t *url2_to;
osip_header_t *expires_hdr;
osip_uri_param_t *expires_param=NULL;
/*
* Authorization - do only if I'm not just acting as outbound proxy
* but am ment to be the registrar
*/
if (force_lcl_masq == 0) {
/*
* RFC 3261, Section 16.3 step 6
* Proxy Behavior - Request Validation - Proxy-Authorization
*/
sts = authenticate_proxy(ticket->sipmsg);
if (sts == STS_FAILURE) {
/* failed */
WARN("proxy authentication failed for %s@%s",
(ticket->sipmsg->to->url->username)?
ticket->sipmsg->to->url->username : "*NULL*",
ticket->sipmsg->to->url->host);
return STS_FAILURE;
} else if (sts == STS_NEED_AUTH) {
/* needed */
DEBUGC(DBCLASS_REG,"proxy authentication needed for %s@%s",
ticket->sipmsg->to->url->username,
ticket->sipmsg->to->url->host);
return STS_NEED_AUTH;
}
}
/*
fetch 1st Via entry and remember this address. Incoming requests
for the registered address have to be passed on to that host.
To: -> address to be registered
Contact: -> host is reachable there
Note: in case of un-REGISTER, the contact header may
contain '*' only - which means "all registrations
made by this UA"
=> Mapping is
To: <1--n> Contact
*/
time(&time_now);
DEBUGC(DBCLASS_BABBLE,"sip_register:");
/*
* First make sure, we have a proper Contact header:
* - url
* - url -> hostname
*
* Libosip parses an:
* "Contact: *"
* the following way (Note: Display name!! and URL is NULL)
* (gdb) p *((osip_contact_t*)(sip->contacts->node->element))
* $5 = {displayname = 0x8af8848 "*", url = 0x0, gen_params = 0x8af8838}
*/
/*
if (ticket->sipmsg->contacts &&
(ticket->sipmsg->contacts->nb_elt != 0) &&
ticket->sipmsg->contacts->node &&
ticket->sipmsg->contacts->node->element) {
url1_contact=((osip_contact_t*)
(ticket->sipmsg->contacts->node->element))->url;
}
*/
osip_message_get_contact(ticket->sipmsg, 0, &contact);
if ((contact == NULL) ||
(contact->url == NULL) ||
(contact->url->host == NULL)) {
/* Don't have required Contact fields.
This may be a Registration query. We should simply forward
this request to its destination. */
DEBUGC(DBCLASS_REG, "empty Contact header - "
"seems to be a registration query");
return STS_SUCCESS;
}
url1_contact=contact->url;
/* evaluate Expires Header field */
osip_message_get_expires(ticket->sipmsg, 0, &expires_hdr);
/*
* look for an Contact expires parameter - in case of REGISTER
* these two are equal. The Contact expires has higher priority!
*/
if (ticket->sipmsg->contacts && ticket->sipmsg->contacts->node &&
ticket->sipmsg->contacts->node->element) {
osip_contact_param_get_byname(
(osip_contact_t*) ticket->sipmsg->contacts->node->element,
EXPIRES, &expires_param);
}
if (expires_param && expires_param->gvalue) {
/* get expires from contact Header */
expires=atoi(expires_param->gvalue);
} else if (expires_hdr && expires_hdr->hvalue) {
/* get expires from expires Header */
expires=atoi(expires_hdr->hvalue);
} else {
char tmp[16];
/* it seems, the expires field is not present everywhere... */
DEBUGC(DBCLASS_REG,"no 'expires' header found - set time to %i sec",
configuration.default_expires);
expires=configuration.default_expires;
sprintf(tmp,"%i",expires);
osip_message_set_expires(ticket->sipmsg, tmp);
}
url1_to=ticket->sipmsg->to->url;
/*
* REGISTER
*/
if (expires > 0) {
DEBUGC(DBCLASS_REG,"register: %s@%s expires=%i seconds",
(url1_contact->username) ? url1_contact->username : "*NULL*",
(url1_contact->host) ? url1_contact->host : "*NULL*",
expires);
/*
* Update registration. There are two possibilities:
* - already registered, then update the existing record
* - not registered, then create a new record
*/
j=-1;
for (i=0; i<URLMAP_SIZE; i++) {
if (urlmap[i].active == 0) {
if (j < 0) j=i; /* remember first hole */
continue;
}
url2_to=urlmap[i].reg_url;
/* check address-of-record ("public address" of user) */
if (compare_url(url1_to, url2_to)==STS_SUCCESS) {
DEBUGC(DBCLASS_REG, "found entry for %s@%s <-> %s@%s at "
"slot=%i, exp=%li",
(url1_contact->username) ? url1_contact->username : "*NULL*",
(url1_contact->host) ? url1_contact->host : "*NULL*",
(url2_to->username) ? url2_to->username : "*NULL*",
(url2_to->host) ? url2_to->host : "*NULL*",
i, urlmap[i].expires-time_now);
break;
}
}
if ( (j < 0) && (i >= URLMAP_SIZE) ) {
/* oops, no free entries left... */
ERROR("URLMAP is full - registration failed");
return STS_FAILURE;
}
if (i >= URLMAP_SIZE) {
/* entry not existing, create new one */
i=j;
/* write entry */
urlmap[i].active=1;
/* Contact: field */
osip_uri_clone( ((osip_contact_t*)
(ticket->sipmsg->contacts->node->element))->url,
&urlmap[i].true_url);
/* To: field */
osip_uri_clone( ticket->sipmsg->to->url,
&urlmap[i].reg_url);
DEBUGC(DBCLASS_REG,"create new entry for %s@%s <-> %s@%s at slot=%i",
(url1_contact->username) ? url1_contact->username : "*NULL*",
(url1_contact->host) ? url1_contact->host : "*NULL*",
(urlmap[i].reg_url->username) ? urlmap[i].reg_url->username : "*NULL*",
(urlmap[i].reg_url->host) ? urlmap[i].reg_url->host : "*NULL*",
i);
/*
* try to figure out if we ought to do some masquerading
*/
osip_uri_clone( ticket->sipmsg->to->url,
&urlmap[i].masq_url);
n=configuration.mask_host.used;
if (n != configuration.masked_host.used) {
ERROR("# of mask_host is not equal to # of masked_host in config!");
n=0;
}
DEBUG("%i entries in MASK config table", n);
for (j=0; j<n; j++) {
DEBUG("compare [%s] <-> [%s]",configuration.mask_host.string[j],
ticket->sipmsg->to->url->host);
if (strcmp(configuration.mask_host.string[j],
ticket->sipmsg->to->url->host)==0)
break;
}
if (j<n) {
/* we are masquerading this UA, replace the host part of the url */
DEBUGC(DBCLASS_REG,"masquerading UA %s@%s as %s@%s",
(url1_contact->username) ? url1_contact->username : "*NULL*",
(url1_contact->host) ? url1_contact->host : "*NULL*",
(url1_contact->username) ? url1_contact->username : "*NULL*",
configuration.masked_host.string[j]);
urlmap[i].masq_url->host=realloc(urlmap[i].masq_url->host,
strlen(configuration.masked_host.string[j])+1);
strcpy(urlmap[i].masq_url->host, configuration.masked_host.string[j]);
}
} else { /* if new entry */
/* This is an existing entry */
/*
* Some phones (like BudgeTones *may* dynamically grab a SIP port
* so we might want to update the true_url and reg_url each time
* we get an REGISTER
*/
/* Contact: field (true_url) */
osip_uri_free(urlmap[i].true_url);
osip_uri_clone( ((osip_contact_t*)
(ticket->sipmsg->contacts->node->element))->url,
&urlmap[i].true_url);
/* To: field (reg_url) */
osip_uri_free(urlmap[i].reg_url);
osip_uri_clone( ticket->sipmsg->to->url,
&urlmap[i].reg_url);
}
/*
* for proxying: force device to be masqueraded
* as with the outbound IP (masq_url)
*/
if (force_lcl_masq) {
struct in_addr addr;
char *addrstr;
if (get_interface_ip(IF_OUTBOUND, &addr) != STS_SUCCESS) {
return STS_FAILURE;
}
/* host part */
addrstr = utils_inet_ntoa(addr);
DEBUGC(DBCLASS_REG,"masquerading UA %s@%s local %s@%s",
(url1_contact->username) ? url1_contact->username : "*NULL*",
(url1_contact->host) ? url1_contact->host : "*NULL*",
(url1_contact->username) ? url1_contact->username : "*NULL*",
addrstr);
urlmap[i].masq_url->host=realloc(urlmap[i].masq_url->host,
strlen(addrstr)+1);
strcpy(urlmap[i].masq_url->host, addrstr);
/* port number if required */
if (configuration.sip_listen_port != SIP_PORT) {
urlmap[i].masq_url->port=realloc(urlmap[i].masq_url->port, 16);
sprintf(urlmap[i].masq_url->port, "%i",
configuration.sip_listen_port);
}
}
/* give some safety margin for the next update */
if (expires > 0) expires+=30;
/* update registration timeout */
urlmap[i].expires=time_now+expires;
/*
* un-REGISTER
*/
} else { /* expires > 0 */
/*
* Remove registration
* Siproxd will ALWAYS remove ALL bindings for a given
* address-of-record
*/
for (i=0; i<URLMAP_SIZE; i++) {
if (urlmap[i].active == 0) continue;
url2_to=urlmap[i].reg_url;
if (compare_url(url1_to, url2_to)==STS_SUCCESS) {
DEBUGC(DBCLASS_REG, "removing registration for %s@%s at slot=%i",
(url2_to->username) ? url2_to->username : "*NULL*",
(url2_to->host) ? url2_to->host : "*NULL*", i);
urlmap[i].expires=0;
break;
}
}
}
return STS_SUCCESS;
}
/*
* cyclically called to do the aging of the URL mapping table entries
* and throw out expired entries.
* Also we do the cyclic saving here - if required.
*/
void register_agemap(void) {
int i;
time_t t;
/* expire old entries */
time(&t);
DEBUGC(DBCLASS_BABBLE,"sip_agemap, t=%i",(int)t);
for (i=0; i<URLMAP_SIZE; i++) {
if ((urlmap[i].active == 1) && (urlmap[i].expires < t)) {
DEBUGC(DBCLASS_REG,"cleaned entry:%i %s@%s", i,
urlmap[i].masq_url->username, urlmap[i].masq_url->host);
urlmap[i].active=0;
osip_uri_free(urlmap[i].true_url);
osip_uri_free(urlmap[i].masq_url);
osip_uri_free(urlmap[i].reg_url);
}
}
/* auto-save of registration table */
if ((configuration.autosave_registrations > 0) &&
((last_save + configuration.autosave_registrations) < t)) {
DEBUGC(DBCLASS_REG,"auto-saving registration table");
register_save();
last_save = t;
}
return;
}
/*
* send answer to a registration request.
* flag = STS_SUCCESS -> positive answer (200)
* flag = STS_FAILURE -> negative answer (503)
* flag = STS_NEED_AUTH -> proxy authentication needed (407)
*
* RETURNS
* STS_SUCCESS on success
* STS_FAILURE on error
*/
int register_response(sip_ticket_t *ticket, int flag) {
osip_message_t *response;
int code;
int sts;
osip_via_t *via;
int port;
char *buffer;
int buflen;
struct in_addr addr;
osip_header_t *expires_hdr;
/* ok -> 200, fail -> 503 */
switch (flag) {
case STS_SUCCESS:
code = 200; /* OK */
break;
case STS_FAILURE:
code = 503; /* failed */
break;
case STS_NEED_AUTH:
code = 407; /* proxy authentication needed */
break;
default:
code = 503; /* failed */
break;
}
/* create the response template */
if ((response=msg_make_template_reply(ticket, code))==NULL) {
ERROR("register_response: error in msg_make_template_reply");
return STS_FAILURE;
}
/* insert the expiration header */
osip_message_get_expires(ticket->sipmsg, 0, &expires_hdr);
if (expires_hdr) {
osip_message_set_expires(response, expires_hdr->hvalue);
}
/* if we send back an proxy authentication needed,
include the Proxy-Authenticate field */
if (code == 407) {
auth_include_authrq(response);
}
/* get the IP address from existing VIA header */
osip_message_get_via (response, 0, &via);
if (via == NULL) {
ERROR("register_response: Cannot send response - no via field");
return STS_FAILURE;
}
/* name resolution needed? */
if (utils_inet_aton(via->host,&addr) == 0) {
/* yes, get IP address */
sts = get_ip_by_host(via->host, &addr);
if (sts == STS_FAILURE) {
DEBUGC(DBCLASS_REG, "register_response: cannot resolve VIA [%s]",
via->host);
return STS_FAILURE;
}
}
sts = sip_message_to_str(response, &buffer, &buflen);
if (sts != 0) {
ERROR("register_response: msg_2char failed");
return STS_FAILURE;
}
/* send answer back */
if (via->port) {
port=atoi(via->port);
} else {
port=configuration.sip_listen_port;
}
sipsock_send(addr, port, ticket->protocol, buffer, buflen);
/* free the resources */
osip_message_free(response);
free(buffer);
return STS_SUCCESS;
}
/*
* set expiration timeout from a SIP response
*
* RETURNS
* STS_SUCCESS on success
* STS_FAILURE on error
*/
int register_set_expire(sip_ticket_t *ticket) {
int i, j;
int expires=-1;
osip_contact_t *contact;
time_t time_now;
osip_header_t *expires_hdr=NULL;
osip_uri_param_t *expires_param=NULL;
if (ticket->direction != RESTYP_INCOMING) {
WARN("register_set_expire called with != incoming response");
return STS_FAILURE;
}
time(&time_now);
DEBUGC(DBCLASS_REG,"REGISTER response, looking for 'Expire' information");
/* evaluate Expires Header field */
osip_message_get_expires(ticket->sipmsg, 0, &expires_hdr);
/* loop for all existing contact headers in message */
for (j=0; contact != NULL; j++) {
osip_message_get_contact(ticket->sipmsg, j, &contact);
/*
* look for an Contact expires parameter - in case of REGISTER
* these two are equal. The Contact expires has higher priority!
*/
if (contact==NULL) continue;
osip_contact_param_get_byname(contact, EXPIRES, &expires_param);
if (expires_param && expires_param->gvalue) {
/* get expires from contact Header */
expires=atoi(expires_param->gvalue);
} else if (expires_hdr && expires_hdr->hvalue) {
/* get expires from expires Header */
expires=atoi(expires_hdr->hvalue);
}
if (expires > 0) {
/* search for an entry */
for (i=0;i<URLMAP_SIZE;i++){
if (urlmap[i].active == 0) continue;
if ((compare_url(contact->url, urlmap[i].masq_url)==STS_SUCCESS)) break;
}
/* found a mapping entry */
if (i<URLMAP_SIZE) {
/* update registration timeout */
DEBUGC(DBCLASS_REG,"changing registration timeout to %i"
" entry [%i]", expires, i);
urlmap[i].expires=time_now+expires;
} else {
DEBUGC(DBCLASS_REG,"no urlmap entry found");
}
}
} /* for j */
return STS_SUCCESS;
}