siproxd/src/proxy.c
Thomas Ries 22c97a4398
2015-07-17 09:19:16 +00:00

1237 lines
41 KiB
C

/*
Copyright (C) 2002-2009 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 <ctype.h>
#include <time.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <osipparser2/osip_parser.h>
#include <osipparser2/sdp_message.h>
#include "siproxd.h"
#include "plugins.h"
#include "log.h"
static char const ident[]="$Id$";
/* configuration storage */
extern struct siproxd_config configuration; /* defined in siproxd.c */
extern struct urlmap_s urlmap[]; /* URL mapping table */
extern struct lcl_if_s local_addresses;
/*
* PROXY_REQUEST
*
* RETURNS
* STS_SUCCESS on success
* STS_FAILURE on error
*
* RFC3261
* Section 16.3: Proxy Behavior - Request Validation
* 1. Reasonable Syntax
* 2. URI scheme
* 3. Max-Forwards
* 4. (Optional) Loop Detection
* 5. Proxy-Require
* 6. Proxy-Authorization
*
* Section 16.6: Proxy Behavior - Request Forwarding
* 1. Make a copy of the received request
* 2. Update the Request-URI
* 3. Update the Max-Forwards header field
* 4. Optionally add a Record-route header field value
* 5. Optionally add additional header fields
* 6. Postprocess routing information
* 7. Determine the next-hop address, port, and transport
* 8. Add a Via header field value
* 9. Add a Content-Length header field if necessary
* 10. Forward the new request
* 11. Set timer C
*/
int proxy_request (sip_ticket_t *ticket) {
int i;
int sts;
int type;
struct in_addr sendto_addr;
int port;
char *buffer;
size_t buflen;
osip_message_t *request;
DEBUGC(DBCLASS_PROXY,"proxy_request");
if (ticket==NULL) {
ERROR("proxy_request: called with NULL ticket");
return STS_FAILURE;
}
request=ticket->sipmsg;
/*
* RFC&&&&
* add a received= parameter to the topmost Via header. Used for TCP
* connections - send answer within the existing TCP connection back
* to client.
*/
if (ticket->protocol == PROTO_TCP) {
sip_add_received_param(ticket);
}
/*
* RFC 3261, Section 16.4
* Proxy Behavior - Route Information Preprocessing
* (process Route header)
*/
route_preprocess(ticket);
/*
* figure out whether this is an incoming or outgoing request
* by doing a lookup in the registration table.
*/
sip_find_direction(ticket, &i);
type = ticket->direction;
/* Call Plugins for stage: PLUGIN_PRE_PROXY */
sts = call_plugins(PLUGIN_PRE_PROXY, ticket);
/*
* RFC 3261, Section 16.6 step 1
* Proxy Behavior - Request Forwarding - Make a copy
*/
/* nothing to do here, copy is ready in 'request'*/
switch (type) {
/*
* from an external host to the internal masqueraded host
*/
case REQTYP_INCOMING:
DEBUGC(DBCLASS_PROXY,"incoming request from %s@%s from outbound",
request->from->url->username? request->from->url->username:"*NULL*",
request->from->url->host? request->from->url->host: "*NULL*");
/*
* RFC 3261, Section 16.6 step 2
* Proxy Behavior - Request Forwarding - Request-URI
* (rewrite request URI to point to the real host)
*/
/* 'i' still holds the valid index into the URLMAP table */
proxy_rewrite_request_uri(request, i);
/* if this is CANCEL/BYE request, stop RTP proxying */
if (MSG_IS_BYE(request) || MSG_IS_CANCEL(request)) {
/* stop the RTP proxying stream(s) */
rtp_stop_fwd(osip_message_get_call_id(request), DIR_INCOMING);
rtp_stop_fwd(osip_message_get_call_id(request), DIR_OUTGOING);
/* check for incoming request */
} else if (MSG_IS_INVITE(request)) {
/* Rewrite the body */
sts = proxy_rewrite_invitation_body(ticket, DIR_INCOMING);
} else if (MSG_IS_ACK(request) || MSG_IS_PRACK(request)) {
/* Rewrite the body */
sts = proxy_rewrite_invitation_body(ticket, DIR_INCOMING);
} else if (MSG_IS_UPDATE(request)) {
/* Rewrite the body */
sts = proxy_rewrite_invitation_body(ticket, DIR_INCOMING);
}
sts=sip_obscure_callid(ticket);
break;
/*
* from the internal masqueraded host to an external host
*/
case REQTYP_OUTGOING:
DEBUGC(DBCLASS_PROXY,"outgoing request from %s@%s from inbound",
request->from->url->username? request->from->url->username:"*NULL*",
request->from->url->host? request->from->url->host: "*NULL*");
/*
* RFC 3261, Section 16.6 step 2
* Proxy Behavior - Request Forwarding - Request-URI
*/
/* nothing to do for an outgoing request */
/* if it is addressed to myself, then it must be some request
* method that I as a proxy do not support. Reject */
#if 0
/* careful - an internal UA might send an request to another internal UA.
This would be caught here, so don't do this. This situation should be
caught in the default part of the CASE statement below */
if (is_sipuri_local(ticket) == STS_TRUE) {
WARN("unsupported request [%s] directed to proxy from %s@%s -> %s@%s",
request->sip_method? request->sip_method:"*NULL*",
request->from->url->username? request->from->url->username:"*NULL*",
request->from->url->host? request->from->url->host : "*NULL*",
request->req_uri->username? request->req_uri->username : "*NULL*",
request->req_uri->host? request->req_uri->host : "*NULL*");
sip_gen_response(ticket, 403 /*forbidden*/);
return STS_FAILURE;
}
#endif
/* Rewrite Contact header to represent the masqued address */
sip_rewrite_contact(ticket, DIR_OUTGOING);
/* Masquerade the User-Agent if configured to do so */
proxy_rewrite_useragent(ticket);
/* if an INVITE, rewrite body */
if (MSG_IS_INVITE(request)) {
sts = proxy_rewrite_invitation_body(ticket, DIR_OUTGOING);
} else if (MSG_IS_ACK(request) || MSG_IS_PRACK(request)) {
sts = proxy_rewrite_invitation_body(ticket, DIR_OUTGOING);
} else if (MSG_IS_UPDATE(request)) {
sts = proxy_rewrite_invitation_body(ticket, DIR_OUTGOING);
}
/* if this is CANCEL/BYE request, stop RTP proxying */
if (MSG_IS_BYE(request) || MSG_IS_CANCEL(request)) {
/* stop the RTP proxying stream(s) */
rtp_stop_fwd(osip_message_get_call_id(request), DIR_INCOMING);
rtp_stop_fwd(osip_message_get_call_id(request), DIR_OUTGOING);
}
sts=sip_obscure_callid(ticket);
break;
default:
DEBUGC(DBCLASS_PROXY, "request [%s] from/to unregistered UA "
"(RQ: %s@%s -> %s@%s)",
request->sip_method? request->sip_method:"*NULL*",
request->from->url->username? request->from->url->username:"*NULL*",
request->from->url->host? request->from->url->host : "*NULL*",
request->req_uri->username? request->req_uri->username : "*NULL*",
request->req_uri->host? request->req_uri->host : "*NULL*");
/*
* we may end up here for two reasons:
* 1) An incomming request (from outbound) that is directed to
* an unknown (not registered) local UA
* 2) an outgoing request from a local UA that is not registered.
*
* Case 1) we should probably answer with "404 Not Found",
* case 2) more likely a "403 Forbidden"
*
* How about "408 Request Timeout" ?
*
*/
/* Call Plugins for stage: PLUGIN_PROXY_UNK */
sts = call_plugins(PLUGIN_PROXY_UNK, ticket);
/*&&& we might want have the possibility for a plugin to generate
and send a response, similar an in stage PLUGIN_DETERMINE_TARGET */
sip_gen_response(ticket, 408 /* Request Timeout */);
return STS_FAILURE;
}
/*
* RFC 3261, Section 16.6 step 3
* Proxy Behavior - Request Forwarding - Max-Forwards
* (if Max-Forwards header exists, decrement by one, if it does not
* exist, add a new one with value SHOULD be 70)
*/
{
osip_header_t *max_forwards;
int forwards_count = DEFAULT_MAXFWD;
char mfwd[12]; /* 10 digits, +/- sign, termination */
osip_message_get_max_forwards(request, 0, &max_forwards);
if (max_forwards == NULL) {
sprintf(mfwd, "%i", forwards_count);
osip_message_set_max_forwards(request, mfwd);
} else {
if (max_forwards->hvalue) {
forwards_count = atoi(max_forwards->hvalue);
if ((forwards_count<0)||
(forwards_count>255)) forwards_count=DEFAULT_MAXFWD;
forwards_count -=1;
osip_free (max_forwards->hvalue);
}
sprintf(mfwd, "%i", forwards_count);
max_forwards->hvalue = osip_strdup(mfwd);
}
DEBUGC(DBCLASS_PROXY,"setting Max-Forwards=%s",mfwd);
}
/*
* RFC 3261, Section 16.6 step 4
* Proxy Behavior - Request Forwarding - Add a Record-route header
*/
/*
* for ALL incoming requests, include my Record-Route header.
* The local UA will probably send its answer to the topmost
* Route Header (8.1.2 of RFC3261)
*/
if (type == REQTYP_INCOMING) {
DEBUGC(DBCLASS_PROXY,"Adding my Record-Route");
route_add_recordroute(ticket);
} else {
/*
* outgoing packets must not have my record route header, as
* this likely will contain a private IP address (my inbound).
*/
DEBUGC(DBCLASS_PROXY,"Purging Record-Routes (outgoing packet)");
route_purge_recordroute(ticket);
}
/*
* RFC 3261, Section 16.6 step 5
* Proxy Behavior - Request Forwarding - Add Additional Header Fields
*/
/* NOT IMPLEMENTED (optional) */
/*
* RFC 3261, Section 16.6 step 6
* Proxy Behavior - Request Forwarding - Postprocess routing information
*
* If the copy contains a Route header field, the proxy MUST
* inspect the URI in its first value. If that URI does not
* contain an lr parameter, the proxy MUST modify the copy as
* follows:
*
* - The proxy MUST place the Request-URI into the Route header
* field as the last value.
*
* - The proxy MUST then place the first Route header field value
* into the Request-URI and remove that value from the Route
* header field.
*/
#if 0
route_postprocess(ticket);
#endif
/*
* RFC 3261, Section 16.6 step 7
* Proxy Behavior - Determine Next-Hop Address
*/
/*&&&& priority probably should be:
* 1) Route header
* 2) fixed outbound proxy
* 3) SIP URI
*/
/*
* Route present?
* If so, fetch address from topmost Route: header and remove it.
*/
if ((type == REQTYP_OUTGOING) &&
(!osip_list_eol(&(request->routes), 0))) {
sts=route_determine_nexthop(ticket, &sendto_addr, &port);
if (sts == STS_FAILURE) {
DEBUGC(DBCLASS_PROXY, "proxy_request: route_determine_nexthop failed");
return STS_FAILURE;
}
DEBUGC(DBCLASS_PROXY, "proxy_request: have Route header to %s:%i",
utils_inet_ntoa(sendto_addr), port);
/*
* fixed or domain outbound proxy defined ?
*/
} else if ((type == REQTYP_OUTGOING) &&
(sip_find_outbound_proxy(ticket, &sendto_addr, &port) == STS_SUCCESS)) {
DEBUGC(DBCLASS_PROXY, "proxy_request: have outbound proxy %s:%i",
utils_inet_ntoa(sendto_addr), port);
/*
* destination from SIP URI
*/
} else {
/* get the destination from the SIP URI */
/*&&&& Here, the SRV record lookup magic must go.
In a first implementation we may just try to get the lowest priority,
max weighted '_sip._udp.domain' entry and port number.
No load balancing and no failover are supported with this.
&&&*/
sts = get_ip_by_host(request->req_uri->host, &sendto_addr);
if (sts == STS_FAILURE) {
DEBUGC(DBCLASS_PROXY, "proxy_request: cannot resolve URI [%s]",
request->req_uri->host);
return STS_FAILURE;
}
if (request->req_uri->port) {
port=atoi(request->req_uri->port);
if ((port<=0) || (port>65535)) port=SIP_PORT;
} else {
port=SIP_PORT;
}
DEBUGC(DBCLASS_PROXY, "proxy_request: have SIP URI to %s:%i",
request->req_uri->host, port);
}
/*
* RFC 3261, Section 16.6 step 8
* Proxy Behavior - Add a Via header field value
*/
/* add my Via header line (outbound interface)*/
if (type == REQTYP_INCOMING) {
sts = sip_add_myvia(ticket, IF_INBOUND);
if (sts == STS_FAILURE) {
ERROR("adding my inbound via failed!");
}
} else {
sts = sip_add_myvia(ticket, IF_OUTBOUND);
if (sts == STS_FAILURE) {
ERROR("adding my outbound via failed!");
return STS_FAILURE;
}
}
/*
* RFC 3261, Section 16.6 step 9
* Proxy Behavior - Add a Content-Length header field if necessary
*/
/* not necessary, already in message and we do not support TCP */
/* Call Plugins for stage: PLUGIN_POST_PROXY */
sts = call_plugins(PLUGIN_POST_PROXY, ticket);
/*
* RFC 3261, Section 16.6 step 10
* Proxy Behavior - Forward the new request
*/
sts = sip_message_to_str(request, &buffer, &buflen);
if (sts != 0) {
ERROR("proxy_request: sip_message_to_str failed");
return STS_FAILURE;
}
sipsock_send(sendto_addr, port, ticket->protocol, buffer, buflen);
osip_free (buffer);
/*
* RFC 3261, Section 16.6 step 11
* Proxy Behavior - Set timer C
*/
/* NOT IMPLEMENTED - does this really apply for stateless proxies? */
return STS_SUCCESS;
}
/*
* PROXY_RESPONSE
*
* RETURNS
* STS_SUCCESS on success
* STS_FAILURE on error
* RFC3261
* Section 16.7: Proxy Behavior - Response Processing
* 1. Find the appropriate response context
* 2. Update timer C for provisional responses
* 3. Remove the topmost Via
* 4. Add the response to the response context
* 5. Check to see if this response should be forwarded immediately
* 6. When necessary, choose the best final response from the
* response context
* 7. Aggregate authorization header field values if necessary
* 8. Optionally rewrite Record-Route header field values
* 9. Forward the response
* 10. Generate any necessary CANCEL requests
*
*/
int proxy_response (sip_ticket_t *ticket) {
int sts;
int type;
struct in_addr sendto_addr;
osip_via_t *via;
int port;
char *buffer;
size_t buflen;
osip_message_t *response;
DEBUGC(DBCLASS_PROXY,"proxy_response");
if (ticket==NULL) {
ERROR("proxy_response: called with NULL ticket");
return STS_FAILURE;
}
response=ticket->sipmsg;
/*
* RFC 3261, Section 16.7 step 3
* Proxy Behavior - Response Processing - Remove my Via header field value
*/
/* remove my Via header line */
sts = sip_del_myvia(ticket);
if (sts == STS_FAILURE) {
DEBUGC(DBCLASS_PROXY,"not addressed to my VIA, ignoring response");
return STS_FAILURE;
}
/*
* figure out if this is an request coming from the outside
* world to one of our registered clients
*/
sip_find_direction(ticket, NULL);
type = ticket->direction;
/* Call Plugins for stage: PLUGIN_PRE_PROXY */
sts = call_plugins(PLUGIN_PRE_PROXY, ticket);
/*
* ok, we got a response that we are allowed to process.
*/
switch (type) {
/*
* from an external host to the internal masqueraded host
*/
case RESTYP_INCOMING:
DEBUGC(DBCLASS_PROXY,"incoming response for %s@%s from outbound",
response->from->url->username? response->from->url->username:"*NULL*",
response->from->url->host? response->from->url->host : "*NULL*");
sts=sip_obscure_callid(ticket);
/*
* Response for INVITE - deal with RTP data in body and
* start RTP proxy stream(s). In case
* of a negative answer, stop RTP stream
*/
if ((MSG_IS_RESPONSE_FOR(response,"INVITE")) ||
(MSG_IS_RESPONSE_FOR(response,"UPDATE"))) {
/* positive response, start RTP stream */
if ((MSG_IS_STATUS_1XX(response)) ||
(MSG_IS_STATUS_2XX(response))) {
if (configuration.rtp_proxy_enable == 1) {
sts = proxy_rewrite_invitation_body(ticket, DIR_INCOMING);
}
/* negative - stop a possibly started RTP stream */
} else if ((MSG_IS_STATUS_4XX(response)) ||
(MSG_IS_STATUS_5XX(response)) ||
(MSG_IS_STATUS_6XX(response))) {
rtp_stop_fwd(osip_message_get_call_id(response), DIR_INCOMING);
rtp_stop_fwd(osip_message_get_call_id(response), DIR_OUTGOING);
}
} /* if INVITE */
/*
* Response for REGISTER - special handling of Contact header
*/
if (MSG_IS_RESPONSE_FOR(response,"REGISTER")) {
/*
* REGISTER returns *my* Contact header information.
* Rewrite Contact header back to represent the true address.
* Other responses do return the Contact header of the sender.
* also change the expiration timeout to the value returned by the
* server.
*/
sts = register_set_expire(ticket);
sip_rewrite_contact(ticket, DIR_INCOMING);
}
/*
* Response for SUBSCRIBE
*
* HACK for Grandstream SIP phones (with newer firmware like 1.0.4.40):
* They send a SUBSCRIBE request to the registration server. In
* case of beeing registering directly to siproxd, this request of
* course will eventually be forwarded back to the same UA.
* Grandstream then does reply with an '202' response (A 202
* response merely indicates that the subscription has been
* understood, and that authorization may or may not have been
* granted), which then of course is forwarded back to the phone.
* And it seems that the Grandstream can *not* *handle* this
* response, as it immediately sends another SUBSCRIBE request.
* And this games goes on and on and on...
*
* As a workaround we will transform any 202 response to a
* '404 unknown destination'
*
*/
{
osip_header_t *ua_hdr=NULL;
osip_message_get_user_agent(response, 0, &ua_hdr);
if (ua_hdr && ua_hdr->hvalue &&
(osip_strncasecmp(ua_hdr->hvalue,"grandstream", 11)==0) &&
(MSG_IS_RESPONSE_FOR(response,"SUBSCRIBE")) &&
(MSG_TEST_CODE(response, 202))) {
DEBUGC(DBCLASS_PROXY, "proxy_response: Grandstream hack 202->404");
response->status_code=404;
}
}
break;
/*
* from the internal masqueraded host to an external host
*/
case RESTYP_OUTGOING:
DEBUGC(DBCLASS_PROXY,"outgoing response for %s@%s from inbound",
response->from->url->username ?
response->from->url->username : "*NULL*",
response->from->url->host ?
response->from->url->host : "*NULL*");
sts=sip_obscure_callid(ticket);
/* Rewrite Contact header to represent the masqued address */
sip_rewrite_contact(ticket, DIR_OUTGOING);
/* Masquerade the User-Agent if configured to do so */
proxy_rewrite_useragent(ticket);
/*
* If an 2xx OK or 1xx response, answer to an INVITE request,
* rewrite body
*
* In case of a negative answer, stop RTP stream
*/
if ((MSG_IS_RESPONSE_FOR(response,"INVITE")) ||
(MSG_IS_RESPONSE_FOR(response,"UPDATE"))) {
/* positive response, start RTP stream */
if ((MSG_IS_STATUS_1XX(response)) ||
(MSG_IS_STATUS_2XX(response))) {
/* This is an outgoing response, therefore an outgoing stream */
sts = proxy_rewrite_invitation_body(ticket, DIR_OUTGOING);
/* megative - stop a possibly started RTP stream */
} else if ((MSG_IS_STATUS_4XX(response)) ||
(MSG_IS_STATUS_5XX(response)) ||
(MSG_IS_STATUS_6XX(response))) {
rtp_stop_fwd(osip_message_get_call_id(response), DIR_INCOMING);
rtp_stop_fwd(osip_message_get_call_id(response), DIR_OUTGOING);
}
} /* if INVITE */
break;
default:
DEBUGC(DBCLASS_PROXY, "response from/to unregistered UA (%s@%s)",
response->from->url->username? response->from->url->username:"*NULL*",
response->from->url->host? response->from->url->host : "*NULL*");
/* Call Plugins for stage: PLUGIN_PROXY_UNK */
sts = call_plugins(PLUGIN_PROXY_UNK, ticket);
return STS_FAILURE;
}
/*
* for ALL incoming response include my Record-Route header.
* The local UA will probably send its answer to the topmost
* Route Header (8.1.2 of RFC3261)
*/
if (type == RESTYP_INCOMING) {
DEBUGC(DBCLASS_PROXY,"Adding my Record-Route");
route_add_recordroute(ticket);
} else {
/*
* outgoing packets must not have my record route header, as
* this likely will contain a private IP address (my inbound).
*/
DEBUGC(DBCLASS_PROXY,"Purging Record-Routes (outgoing packet)");
route_purge_recordroute(ticket);
}
/*
* Determine Next-Hop Address
*/
/*&&&& priority probably should be:
* 0) rport=;received= header (TCP only for now)
* 1) Route header
* 2) fixed outbound proxy
* 3) Via header
*/
/*
* IF TCP, check for rport=x;received=y parameters in VIA
*/
if ((ticket->protocol == PROTO_TCP) &&
(sip_get_received_param(ticket, &sendto_addr, &port) == STS_SUCCESS)) {
DEBUGC(DBCLASS_PROXY, "proxy_response: have received/rport to %s:%i",
utils_inet_ntoa(sendto_addr), port);
/*
* Route present?
* If so, fetch address from topmost Route: header and remove it.
*/
} else if ((type == RESTYP_OUTGOING) &&
(!osip_list_eol(&(response->routes), 0))) {
sts=route_determine_nexthop(ticket, &sendto_addr, &port);
if (sts == STS_FAILURE) {
DEBUGC(DBCLASS_PROXY, "proxy_response: route_determine_nexthop failed");
return STS_FAILURE;
}
DEBUGC(DBCLASS_PROXY, "proxy_response: have Route header to %s:%i",
utils_inet_ntoa(sendto_addr), port);
/*
* check if we need to send to an outbound proxy
*/
} else if ((type == RESTYP_OUTGOING) &&
(sip_find_outbound_proxy(ticket, &sendto_addr, &port) == STS_SUCCESS)) {
DEBUGC(DBCLASS_PROXY, "proxy_response: have outbound proxy %s:%i",
utils_inet_ntoa(sendto_addr), port);
} else {
/* get target address and port from VIA header */
via = (osip_via_t *) osip_list_get (&(response->vias), 0);
if (via == NULL) {
ERROR("proxy_response: list_get via failed");
return STS_FAILURE;
}
sts = get_ip_by_host(via->host, &sendto_addr);
if (sts == STS_FAILURE) {
DEBUGC(DBCLASS_PROXY, "proxy_response: cannot resolve VIA [%s]",
via->host);
return STS_FAILURE;
}
if (via->port) {
port=atoi(via->port);
if ((port<=0) || (port>65535)) port=SIP_PORT;
} else {
port=SIP_PORT;
}
}
/* Call Plugins for stage: PLUGIN_POST_PROXY */
sts = call_plugins(PLUGIN_POST_PROXY, ticket);
/*
* Proxy Behavior - Forward the response
*/
sts = sip_message_to_str(response, &buffer, &buflen);
if (sts != 0) {
ERROR("proxy_response: sip_message_to_str failed");
return STS_FAILURE;
}
sipsock_send(sendto_addr, port, ticket->protocol, buffer, buflen);
osip_free (buffer);
return STS_SUCCESS;
}
/*
* PROXY_REWRITE_INVITATION_BODY
*
* rewrites the outgoing INVITATION request or response packet
*
* RETURNS
* STS_SUCCESS on success
* STS_FAILURE on error
*/
int proxy_rewrite_invitation_body(sip_ticket_t *ticket, int direction){
osip_message_t *mymsg=ticket->sipmsg;
osip_body_t *body;
sdp_message_t *sdp;
struct in_addr map_addr, addr_sess, addr_media, outside_addr, inside_addr;
int sts;
char *buff;
size_t buflen;
char clen[8]; /* content length: probably never more than 7 digits !*/
int map_port, msg_port;
int media_stream_no;
sdp_connection_t *sdp_conn;
sdp_media_t *sdp_med;
int rtp_direction=0;
int call_direction=0;
int have_c_media=0;
int isrtp = 0 ;
if (configuration.rtp_proxy_enable == 0) return STS_SUCCESS;
/*
* get SDP structure
*/
sts = osip_message_get_body(mymsg, 0, &body);
if (sts != 0) {
DEBUGC(DBCLASS_PROXY, "rewrite_invitation_body: "
"no body found in message");
return STS_SUCCESS;
}
sts = sip_body_to_str(body, &buff, &buflen);
if (sts != 0) {
ERROR("rewrite_invitation_body: unable to sip_body_to_str");
return STS_FAILURE;
}
DEBUGC(DBCLASS_PROXY, "rewrite_invitation_body: payload %ld bytes",
(long)buflen);
DUMP_BUFFER(DBCLASS_PROXY, buff, buflen);
sts = sdp_message_init(&sdp);
sts = sdp_message_parse (sdp, buff);
if (sts != 0) {
ERROR("rewrite_invitation_body: unable to sdp_message_parse body");
DUMP_BUFFER(-1, buff, buflen);
osip_free(buff);
sdp_message_free(sdp);
return STS_FAILURE;
}
osip_free(buff);
if (configuration.debuglevel)
{ /* just dump the buffer */
char *tmp, *tmp2;
size_t tmplen;
sts = osip_message_get_body(mymsg, 0, &body);
sts = sip_body_to_str(body, &tmp, &tmplen);
if (sts == 0) {
osip_content_length_to_str(mymsg->content_length, &tmp2);
DEBUG("Body before rewrite (may be truncated) - (clen=%s, strlen=%ld):\n%s\n----",
tmp2, (long)tmplen, tmp);
osip_free(tmp);
osip_free(tmp2);
} else {
DEBUG("Body before rewrite: failed to decode!");
}
}
/*
* RTP proxy: get ready and start forwarding
* start forwarding for each media stream ('m=' item in SIP message)
*/
/* get outbound address */
if (get_interface_ip(IF_OUTBOUND, &outside_addr) != STS_SUCCESS) {
sdp_message_free(sdp);
return STS_FAILURE;
}
/* get inbound address */
if (get_interface_ip(IF_INBOUND, &inside_addr) != STS_SUCCESS) {
sdp_message_free(sdp);
return STS_FAILURE;
}
/* figure out what address to use for RTP masquerading */
if (MSG_IS_REQUEST(mymsg)) {
if (direction == DIR_INCOMING) {
memcpy(&map_addr, &inside_addr, sizeof (map_addr));
rtp_direction = DIR_OUTGOING;
call_direction = DIR_INCOMING;
} else {
memcpy(&map_addr, &outside_addr, sizeof (map_addr));
rtp_direction = DIR_INCOMING;
call_direction = DIR_OUTGOING;
}
} else /* MSG_IS_REPONSE(mymsg) */ {
if (direction == DIR_INCOMING) {
memcpy(&map_addr, &inside_addr, sizeof (map_addr));
rtp_direction = DIR_OUTGOING;
call_direction = DIR_OUTGOING;
} else {
memcpy(&map_addr, &outside_addr, sizeof (map_addr));
rtp_direction = DIR_INCOMING;
call_direction = DIR_INCOMING;
}
}
DEBUGC(DBCLASS_PROXY, "proxy_rewrite_invitation_body: SIP[%s %s] RTP[%s %s]",
MSG_IS_REQUEST(mymsg)? "RQ" : "RS",
(direction==DIR_INCOMING)? "IN" : "OUT",
(rtp_direction==DIR_INCOMING)? "IN" : "OUT",
utils_inet_ntoa(map_addr));
/*
* first, check presence of a 'c=' item on session level
*/
if (sdp->c_connection==NULL || sdp->c_connection->c_addr==NULL) {
/*
* No 'c=' on session level, search on media level now
*
* According to RFC2327, ALL media description must
* include a 'c=' item now:
*/
media_stream_no=0;
while (!sdp_message_endof_media(sdp, media_stream_no)) {
/* check if n'th media stream is present */
if (sdp_message_c_addr_get(sdp, media_stream_no, 0) == NULL) {
ERROR("SDP: have no 'c=' on session level and neither "
"on media level (media=%i)",media_stream_no);
sdp_message_free(sdp);
return STS_FAILURE;
}
media_stream_no++;
} /* while */
}
/* Required 'c=' items ARE present */
/*
* rewrite 'c=' item on session level if present and not yet done.
* remember the original address in addr_sess
*/
memset(&addr_sess, 0, sizeof(addr_sess));
if (sdp->c_connection && sdp->c_connection->c_addr) {
sts = get_ip_by_host(sdp->c_connection->c_addr, &addr_sess);
if (sts == STS_FAILURE) {
ERROR("SDP: cannot resolve session 'c=' host [%s]",
sdp->c_connection->c_addr);
sdp_message_free(sdp);
return STS_FAILURE;
}
/*
* Rewrite
* an IP address of 0.0.0.0 means *MUTE*, don't rewrite such
*/
if (strcmp(sdp->c_connection->c_addr, "0.0.0.0") != 0) {
osip_free(sdp->c_connection->c_addr);
sdp->c_connection->c_addr=osip_malloc(HOSTNAME_SIZE);
sprintf(sdp->c_connection->c_addr, "%s", utils_inet_ntoa(map_addr));
} else {
/* 0.0.0.0 - don't rewrite */
DEBUGC(DBCLASS_PROXY, "proxy_rewrite_invitation_body: "
"got a MUTE c= record (on session level - legal?)");
}
}
/*
* rewrite 'o=' item (originator) on session level if present.
*/
if (sdp->o_addrtype && sdp->o_addr) {
if (strcmp(sdp->o_addrtype, "IP4") != 0) {
ERROR("got IP6 in SDP originator - not yet suported by siproxd");
sdp_message_free(sdp);
return STS_FAILURE;
}
osip_free(sdp->o_addr);
sdp->o_addr=osip_malloc(HOSTNAME_SIZE);
sprintf(sdp->o_addr, "%s", utils_inet_ntoa(map_addr));
}
/*
* loop through all media descritions,
* start RTP proxy and rewrite them
*/
for (media_stream_no=0;;media_stream_no++) {
/* check if n'th media stream is present */
if (sdp_message_m_port_get(sdp, media_stream_no) == NULL) break;
/*
* check if a 'c=' item is present in this media description,
* if so -> rewrite it
*/
memset(&addr_media, 0, sizeof(addr_media));
have_c_media=0;
sdp_conn=sdp_message_connection_get(sdp, media_stream_no, 0);
if (sdp_conn && sdp_conn->c_addr) {
if (strcmp(sdp_conn->c_addr, "0.0.0.0") != 0) {
sts = get_ip_by_host(sdp_conn->c_addr, &addr_media);
have_c_media=1;
/* have a valid address */
osip_free(sdp_conn->c_addr);
sdp_conn->c_addr=osip_malloc(HOSTNAME_SIZE);
sprintf(sdp_conn->c_addr, "%s", utils_inet_ntoa(map_addr));
} else {
/* 0.0.0.0 - don't rewrite */
DEBUGC(DBCLASS_PROXY, "proxy_rewrite_invitation_body: got a "
"MUTE c= record (media level)");
}
}
/* start an RTP proxying stream */
if (sdp_message_m_port_get(sdp, media_stream_no)) {
msg_port=atoi(sdp_message_m_port_get(sdp, media_stream_no));
if ((msg_port > 0) && (msg_port <= 65535)) {
client_id_t client_id;
osip_contact_t *contact = NULL;
char *protocol=NULL;
/* try to get some additional UA specific unique ID.
* This Client-ID should be guaranteed persistent
* and not depend on if a UA/Server does include a
* particular Header (Contact) or not.
*/
/* we will use - if present the from/to fields.
* Outgoing call (RQ out, RS in => use the "from" field to identify local client
* Incoming call (RQ in, RS out => use the "to" field to identify local client
*
* According to RFC3261, the From and To headers MUST NOT change
* withing an ongoing dialog:
*
* 8.2.6.2 Headers and Tags
*
* The From field of the response MUST equal the From header field of
* the request. The Call-ID header field of the response MUST equal the
* Call-ID header field of the request. The CSeq header field of the
* response MUST equal the CSeq field of the request. The Via header
* field values in the response MUST equal the Via header field values
* in the request and MUST maintain the same ordering.
*
* If a request contained a To tag in the request, the To header field
* in the response MUST equal that of the request. However, if the To
* header field in the request did not contain a tag, the URI in the To
* header field in the response MUST equal the URI in the To header
* field; additionally, the UAS MUST add a tag to the To header field in
* the response (with the exception of the 100 (Trying) response, in
* [...]
*/
/* If no proper TO/FROM headers are present, fall back to use Contact header... */
memset(&client_id, 0, sizeof(client_id));
/* Outgoing call (RQ out, RS in => use the "from" field to identify local client */
if ((MSG_IS_REQUEST(mymsg) && direction == DIR_OUTGOING) ||
(!MSG_IS_REQUEST(mymsg) && direction == DIR_INCOMING)) {
/* I have a full FROM SIP URI 'user@host' */
if (mymsg->from && mymsg->from->url &&
mymsg->from->url->username && mymsg->from->url->host) {
snprintf(client_id.idstring, CLIENT_ID_SIZE-1, "%s@%s",
mymsg->from->url->username, mymsg->from->url->host);
} else {
char *tmp=NULL;
/* get the Contact Header if present */
osip_message_get_contact(mymsg, 0, &contact);
if (contact) osip_contact_to_str(contact, &tmp);
if (tmp) strncpy(client_id.idstring, tmp, CLIENT_ID_SIZE-1);
} /* if from header */
/* Incoming call (RQ in, RS out => use the "to" field to identify local client */
} else { /*(MSG_IS_REQUEST(mymsg) && direction == DIR_INCOMING) ||
(!MSG_IS_REQUEST(mymsg) && direction == DIR_OUTGOING)) */
/* I have a full TO SIP URI 'user@host' */
if (mymsg->to && mymsg->to->url &&
mymsg->to->url->username && mymsg->to->url->host) {
snprintf(client_id.idstring, CLIENT_ID_SIZE-1, "%s@%s",
mymsg->to->url->username, mymsg->to->url->host);
} else {
char *tmp=NULL;
/* get the Contact Header if present */
osip_message_get_contact(mymsg, 0, &contact);
if (contact) osip_contact_to_str(contact, &tmp);
if (tmp) strncpy(client_id.idstring, tmp, CLIENT_ID_SIZE-1);
} /* if to header */
}
/* store the IP address of the sender */
memcpy(&client_id.from_ip, &ticket->from.sin_addr,
sizeof(client_id.from_ip));
/*
* is this an RTP stream ? If yes, set 'isrtp=1'
*/
protocol = sdp_message_m_proto_get (sdp, media_stream_no);
if (protocol == NULL) {
DEBUGC(DBCLASS_PROXY, "no protocol definition found!");
} else {
char *check;
char *cmp;
isrtp = 1;
check = protocol ;
cmp = "RTP/" ;
while (*cmp && (isrtp = isrtp && *check) &&
(isrtp = isrtp && (*cmp++ == toupper(*check++))) ) {} ;
if (isrtp) {
DEBUGC(DBCLASS_PROXY, "found RTP protocol [%s]!", protocol);
} else {
DEBUGC(DBCLASS_PROXY, "found non RTP protocol [%s]!", protocol);
}
}
/*
* do we have a 'c=' item on media level?
* if not, use the same as on session level
*/
if (have_c_media == 0) {
memcpy(&addr_media, &addr_sess, sizeof(addr_sess));
}
/*
* Am I running in front of the routing device? Then I cannot
* use the external IP to bind a listen socket to, but should
* use my real IP on the outbound interface (which may legally
* be the same as the inbound interface if only one interface
* is used).
*/
if ((rtp_direction == DIR_INCOMING) &&
(configuration.outbound_host) &&
(strcmp(configuration.outbound_host, "")!=0)) {
DEBUGC(DBCLASS_PROXY, "proxy_rewrite_invitation_body: "
"in-front-of-NAT-Router, use real outboud IP");
if (get_interface_real_ip(IF_OUTBOUND, &map_addr)
!= STS_SUCCESS) {
ERROR("cannot get my real outbound interface address");
/* as we do not know better, take the internal address */
memcpy(&map_addr, &inside_addr, sizeof (map_addr));
}
}
/*
* Start the RTP stream
*/
sts = rtp_start_fwd(osip_message_get_call_id(mymsg),
client_id,
rtp_direction, call_direction,
media_stream_no,
map_addr, &map_port,
addr_media, msg_port,
isrtp);
if (sts == STS_SUCCESS) {
/* and rewrite the port */
sdp_med=osip_list_get(&(sdp->m_medias), media_stream_no);
if (sdp_med && sdp_med->m_port) {
osip_free(sdp_med->m_port);
sdp_med->m_port=osip_malloc(8); /* 5 digits, \0 + align */
sprintf(sdp_med->m_port, "%i", map_port);
DEBUGC(DBCLASS_PROXY, "proxy_rewrite_invitation_body: "
"m= rewrote port to [%i]",map_port);
} else {
ERROR("rewriting port in m= failed sdp_med=%p, "
"m_number_of_port=%p", sdp_med, sdp_med->m_port);
}
} /* sts == success */
} /* if msg_port > 0 */
} else {
/* no port defined - skip entry */
WARN("no port defined in m=(media) stream_no=%i", media_stream_no);
continue;
}
} /* for media_stream_no */
/* remove old body */
sts = osip_list_remove(&(mymsg->bodies), 0);
osip_body_free(body);
/* dump new body */
sdp_message_to_str(sdp, &buff);
buflen=strlen(buff);
/* free sdp structure */
sdp_message_free(sdp);
/* include new body */
sip_message_set_body(mymsg, buff, buflen);
if (sts != 0) {
ERROR("rewrite_invitation_body: unable to sip_message_set_body body");
}
/* free content length resource and include new one*/
osip_content_length_free(mymsg->content_length);
mymsg->content_length=NULL;
sprintf(clen,"%ld",(long)buflen);
sts = osip_message_set_content_length(mymsg, clen);
/* free new body string*/
osip_free(buff);
if (configuration.debuglevel)
{ /* just dump the buffer */
char *tmp, *tmp2;
size_t tmplen;
sts = osip_message_get_body(mymsg, 0, &body);
sts = sip_body_to_str(body, &tmp, &tmplen);
if (sts == 0) {
osip_content_length_to_str(mymsg->content_length, &tmp2);
DEBUG("Body after rewrite (may be truncated) - (clen=%s, strlen=%ld):\n%s\n----",
tmp2, (long)tmplen, tmp);
osip_free(tmp);
osip_free(tmp2);
} else {
DEBUG("Body after rewrite: failed to decode!");
}
}
return STS_SUCCESS;
}
/*
* PROXY_REWRITE_REQUEST_URI
*
* rewrites the incoming Request URI
*
* RETURNS
* STS_SUCCESS on success
*/
int proxy_rewrite_request_uri(osip_message_t *mymsg, int idx){
osip_uri_t *url;
int sts;
char *tmp1=NULL;
char *tmp2=NULL;
if ((idx >= URLMAP_SIZE) || (idx < 0)) {
WARN("proxy_rewrite_request_uri: called with invalid index");
return STS_FAILURE;
}
DEBUGC(DBCLASS_PROXY,"rewriting incoming Request URI");
url=osip_message_get_uri(mymsg);
osip_uri_to_str(url, &tmp1);
osip_uri_to_str(urlmap[idx].true_url, &tmp2);
DEBUGC(DBCLASS_BABBLE,"proxy_rewrite_request_uri: %s -> %s", tmp1, tmp2);
osip_uri_free(url);
url=NULL;
osip_message_set_uri(mymsg, url);
sts = osip_uri_clone(urlmap[idx].true_url, &url);
if (sts != 0) {
ERROR("osip_uri_clone failed");
}
osip_message_set_uri(mymsg, url);
return STS_SUCCESS;
}
/*
* PROXY_REWRITE_USERAGENT
*
* rewrites the User Agent String
*
* RETURNS
* STS_SUCCESS on success
*/
int proxy_rewrite_useragent(sip_ticket_t *ticket){
osip_header_t *ua_hdr=NULL;
osip_message_get_user_agent(ticket->sipmsg, 0, &ua_hdr);
/* Configured? & Does User-Agent header exist? */
if ((configuration.ua_string) && (ua_hdr && ua_hdr->hvalue)) {
DEBUGC(DBCLASS_PROXY,"proxy_rewrite_useragent: [%s] -> [%s]",
ua_hdr->hvalue, configuration.ua_string);
osip_free(ua_hdr->hvalue);
ua_hdr->hvalue=osip_malloc(strlen(configuration.ua_string)+1);
strcpy(ua_hdr->hvalue, configuration.ua_string);
}
return STS_SUCCESS;
}