siproxd/src/register.c
Thomas Ries 59c58117c8
2017-10-24 18:56:46 +00:00

724 lines
23 KiB
C

/*
Copyright (C) 2002-2008 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, i;
char buff[128];
// char *c;
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 */
DEBUGC(DBCLASS_REG,"loading registration table, size=%i",URLMAP_SIZE);
for (i=0;i < URLMAP_SIZE; i++) {
fgets(buff, sizeof(buff), stream);
sts=sscanf(buff, "****:%i:%i", &urlmap[i].active, &urlmap[i].expires);
if (sts == 0) break; /* format error */
if (urlmap[i].active) {
#define R(X) {\
sts=osip_uri_init(&X); \
if (sts == 0) { \
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) {\
sts = osip_uri_parse(X, buff); \
if (sts != 0) { \
ERROR("Unable to parse URI: %s", buff); \
osip_uri_free(X); \
X = NULL; \
} \
} else { \
DEBUGC(DBCLASS_BABBLE, "empty URI"); \
osip_uri_free(X); \
X = NULL; \
} \
} else { \
ERROR("Unable to initialize URI structure"); \
} \
}
R(urlmap[i].true_url);
R(urlmap[i].masq_url);
R(urlmap[i].reg_url);
}
}
fclose(stream);
/* check for premature abort of reading the registration file,
may happen if URLMAP_SIZE has been resized (bigger) */
if (i < URLMAP_SIZE) {
WARN("registration file may be corrupt or URLMAP_SIZE has been resized");
}
}
}
/* initialize save-timer */
time(&last_save);
return;
}
/*
* shut down the URL mapping table
*/
void register_save(void) {
int i;
FILE *stream;
if (configuration.registrationfile) {
DEBUGC(DBCLASS_REG,"saving registration table");
/* 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) { \
char *tmp=NULL; \
osip_uri_to_str(X, &tmp); \
fprintf(stream, "%s\n", (tmp)? tmp:""); \
if (tmp) osip_free(tmp); \
}
// true_url
W(urlmap[i].true_url);
// masq_url
W(urlmap[i].masq_url);
// reg_url
W(urlmap[i].reg_url);
}
}
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}
*/
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 or unregistering all registered
records for this UA. We should simply forward this request to its
destination. However, if this is an unregistration from a client
that is not registered (Grandstream "unregister at startup" option)
-> How do I handle this one?
Right now we do a direction lookup and if this fails we generate
an OK message by ourself (fake) */
DEBUGC(DBCLASS_REG, "empty Contact header - "
"seems to be a registration query");
sts = sip_find_direction(ticket, NULL);
if (sts != STS_SUCCESS) {
/* answer the request myself. Most likely this is an UNREGISTER
* request when the client just booted */
sts = register_response(ticket, STS_SUCCESS);
return STS_SIP_SENT;
}
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.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);
if ((expires < 0) || (expires >= UINT_MAX ))
expires=configuration.default_expires;
} else if (expires_hdr && expires_hdr->hvalue) {
/* get expires from expires Header */
expires=atoi(expires_hdr->hvalue);
if ((expires < 0) || (expires >= UINT_MAX ))
expires=configuration.default_expires;
} 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, (long)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
* 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;
}
/* do ensure that the host part (IP) is kept up to date
* as it might change */
/* host part */
addrstr = utils_inet_ntoa(addr);
DEBUGC(DBCLASS_REG,"masquerading Contact %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);
}
/* A proxied REGISTER request does not yet mean that this is a
* legit registration... So just give a few seconds here,
* enough to receive the REGISTER response, then upon success (2XX)
* the full expiration time will be given to this record. */
/* &&& this may be reduced to 0 and left solely to the grace period */
#define EXPIRE_NULL 5
expires = EXPIRE_NULL;
}
/* update registration timeout if we will give additional time */
if (urlmap[i].expires < time_now+expires) {
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=time_now+EXPIRE_NULL;
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;
#define REGISTER_GRACE 5
/* 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+REGISTER_GRACE < 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)) {
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;
size_t 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);
if ((port<=0) || (port>65535)) port=SIP_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 as received with 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=NULL;
time_t time_now;
osip_header_t *expires_hdr=NULL;
osip_uri_param_t *expires_param=NULL;
osip_message_t *response;
if (ticket == NULL) {
WARN("register_set_expire called with ticket == NULL");
return STS_FAILURE;
}
if (ticket->direction != RESTYP_INCOMING) {
WARN("register_set_expire called with != incoming response");
return STS_FAILURE;
}
if (ticket->sipmsg == NULL) {
WARN("register_set_expire called with ticket->sipmsg = NULL");
return STS_FAILURE;
}
response=ticket->sipmsg;
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==0); 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);
if ((expires < 0) || (expires >= UINT_MAX ))
expires=configuration.default_expires;
} else if (expires_hdr && expires_hdr->hvalue) {
/* get expires from expires Header */
expires=atoi(expires_hdr->hvalue);
if ((expires < 0) || (expires >= UINT_MAX ))
expires=configuration.default_expires;
}
DEBUGC(DBCLASS_REG,"Expires=%i, expires_param=%p, expires_hdr=%p",
expires, expires_param, expires_hdr);
/* if 2xx response (success), then set full registration time,
* else if not 2XX, then don't touch existing expires value */
if (!MSG_IS_STATUS_2XX(response)) {
expires = 0;
}
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;
} /* for i */
/* found a mapping entry */
if (i<URLMAP_SIZE) {
/* update registration timeout */
DEBUGC(DBCLASS_REG,"changing registration timeout to %i"
" in entry [%i]", expires, i);
urlmap[i].expires=time_now+expires;
} else {
DEBUGC(DBCLASS_REG,"no urlmap entry found");
}
}
} /* for j */
return STS_SUCCESS;
}