/* Copyright (C) 2002-2005 Thomas Ries 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 #include #include #include #include #include #include #include #include #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 %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 [%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 (jusername) ? 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; iusername) ? 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; iusername, 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;iurl, urlmap[i].masq_url)==STS_SUCCESS)) break; } /* found a mapping entry */ if (i