/* $Id: nathelper.c,v 1.67.2.5 2005/07/20 17:11:51 andrei Exp $
 *
 * Copyright (C) 2003 Porta Software Ltd
 *
 * This file is part of ser, a free SIP server.
 *
 * ser 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
 *
 * For a license to use the ser software under conditions
 * other than those described here, or to purchase support for this
 * software, please contact iptel.org by e-mail at the following addresses:
 *    info@iptel.org
 *
 * ser 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 this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 * History:
 * ---------
 * 2003-10-09	nat_uac_test introduced (jiri)
 *
 * 2003-11-06   nat_uac_test permitted from onreply_route (jiri)
 *
 * 2003-12-01   unforce_rtp_proxy introduced (sobomax)
 *
 * 2004-01-07	RTP proxy support updated to support new version of the
 *		RTP proxy (20040107).
 *
 *		force_rtp_proxy() now inserts a special flag
 *		into the SDP body to indicate that this session already
 *		proxied and ignores sessions with such flag.
 *
 *		Added run-time check for version of command protocol
 *		supported by the RTP proxy.
 *
 * 2004-01-16   Integrated slightly modified patch from Tristan Colgate,
 *		force_rtp_proxy function with IP as a parameter (janakj)
 *
 * 2004-01-28	nat_uac_test extended to allow testing SDP body (sobomax)
 *
 *		nat_uac_test extended to allow testing top Via (sobomax)
 *
 * 2004-02-21	force_rtp_proxy now accepts option argument, which
 *		consists of string of chars, each of them turns "on"
 *		some feature, currently supported ones are:
 *
 *		 `a' - flags that UA from which message is received
 *		       doesn't support symmetric RTP;
 *		 `l' - force "lookup", that is, only rewrite SDP when
 *		       corresponding session is already exists in the
 *		       RTP proxy. Only makes sense for SIP requests,
 *		       replies are always processed in "lookup" mode;
 *		 `i' - flags that message is received from UA in the
 *		       LAN. Only makes sense when RTP proxy is running
 *		       in the bridge mode.
 *
 *		force_rtp_proxy can now be invoked without any arguments,
 *		as previously, with one argument - in this case argument
 *		is treated as option string and with two arguments, in
 *		which case 1st argument is option string and the 2nd
 *		one is IP address which have to be inserted into
 *		SDP (IP address on which RTP proxy listens).
 *
 * 2004-03-12	Added support for IPv6 addresses in SDPs. Particularly,
 *		force_rtp_proxy now can work with IPv6-aware RTP proxy,
 *		replacing IPv4 address in SDP with IPv6 one and vice versa.
 *		This allows creating full-fledged IPv4<->IPv6 gateway.
 *		See 4to6.cfg file for example.
 *
 *		Two new options added into force_rtp_proxy:
 *
 *		 `f' - instructs nathelper to ignore marks inserted
 *		       by another nathelper in transit to indicate
 *		       that the session is already goes through another
 *		       proxy. Allows creating chain of proxies.
 *		 `r' - flags that IP address in SDP should be trusted.
 *		       Without this flag, nathelper ignores address in the
 *		       SDP and uses source address of the SIP message
 *		       as media address which is passed to the RTP proxy.
 *
 *		Protocol between nathelper and RTP proxy in bridge
 *		mode has been slightly changed. Now RTP proxy expects SER
 *		to provide 2 flags when creating or updating session
 *		to indicate direction of this session. Each of those
 *		flags can be either `e' or `i'. For example `ei' means
 *		that we received INVITE from UA on the "external" network
 *		network and will send it to the UA on "internal" one.
 *		Also possible `ie' (internal->external), `ii'
 *		(internal->internal) and `ee' (external->external). See
 *		example file alg.cfg for details.
 *
 * 2004-03-15	If the rtp proxy test failed (wrong version or not started)
 *		retry test from time to time, when some *rtpproxy* function
 *		is invoked. Minimum interval between retries can be
 *		configured via rtpproxy_disable_tout module parameter (default
 *		is 60 seconds). Setting it to -1 will disable periodic
 *		rechecks completely, setting it to 0 will force checks
 *		for each *rtpproxy* function call. (andrei)
 *
 * 2004-03-22	Fix assignment of rtpproxy_retr and rtpproxy_tout module
 *		parameters.
 *
 * 2004-03-22	Fix get_body position (should be called before get_callid)
 * 				(andrei)
 *
 * 2004-03-24	Fix newport for null ip address case (e.g onhold re-INVITE)
 * 				(andrei)
 *
 * 2004-09-30	added received port != via port test (andrei)
 *
 * 2004-10-10   force_socket option introduced (jiri)
 * 2004-12-21	support for multiple medias added (netch)
 * 2005-01-18	proxying on protocol type (RTP/AVP, udp, udptl), not
 *		media type. (sobomax,netch)
 *
 * 2005-02-24	Added support for using more than one rtp proxy, in which
 *		case traffic will be distributed evenly among them. In addition,
 *		each such proxy can be assigned a weight, which will specify
 *		which share of the traffic should be placed to this particular
 *		proxy.
 *
 *		Introduce failover mechanism, so that if SER detects that one
 *		of many proxies is no longer available it temporarily decreases
 *		its weight to 0, so that no traffic will be assigned to it.
 *		Such "disabled" proxies are periodically checked to see if they
 *		are back to normal in which case respective weight is restored
 *		resulting in traffic being sent to that proxy again.
 *
 *		Those features can be enabled by specifying more than one "URI"
 *		in the rtpproxy_sock parameter, optionally followed by the weight,
 *		which if absent is assumed to be 1, for example:
 *
 *		rtpproxy_sock="unix:/foo/bar=4 udp:1.2.3.4:3456=3 udp:5.6.7.8:5432=1"
 *
 * 2005-03-24	music-on-hold implemented (netch)
 *
 * 2007-04-23	Do NAT pinging in the separate dedicated process. It provides much
 *		better scalability than doing it in the main one.
 */

#include "nhelpr_funcs.h"
#include "nathelper.h"
#include "../../action.h"
#include "../../flags.h"
#include "../../sr_module.h"
#include "../../dprint.h"
#include "../../data_lump.h"
#include "../../data_lump_rpl.h"
#include "../../error.h"
#include "../../forward.h"
#include "../../mem/mem.h"
#include "../../parser/parse_from.h"
#include "../../parser/parse_hostport.h"
#include "../../parser/parse_to.h"
#include "../../parser/parse_uri.h"
#include "../../parser/parser_f.h"
#include "../../resolve.h"
#include "../../timer.h"
#include "../../trim.h"
#include "../../ut.h"
#include "../registrar/sip_msg.h"
#include "../../msg_translator.h"
#include "../usrloc/usrloc.h"
#include "../../usr_avp.h"
#include "../../socket_info.h"
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/uio.h>
#include <sys/un.h>
#include <ctype.h>
#include <errno.h>
#include <netdb.h>
#include <poll.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

MODULE_VERSION

#if !defined(AF_LOCAL)
#define	AF_LOCAL AF_UNIX
#endif
#if !defined(PF_LOCAL)
#define	PF_LOCAL PF_UNIX
#endif

/* NAT UAC test constants */
#define	NAT_UAC_TEST_C_1918	0x01
#define	NAT_UAC_TEST_RCVD	0x02
#define	NAT_UAC_TEST_V_1918	0x04
#define	NAT_UAC_TEST_S_1918	0x08
#define	NAT_UAC_TEST_RPORT	0x10

/* Supported version of the RTP proxy command protocol */
#define	SUP_CPROTOVER	20040107
/* Required additional version of the RTP proxy command protocol */
#define	REQ_CPROTOVER	"20050322"
#define	CPORT		"22222"

static int nat_uac_test_f(struct sip_msg* msg, char* str1, char* str2);
static int fix_nated_contact_f(struct sip_msg *, char *, char *);
static int fix_nated_sdp_f(struct sip_msg *, char *, char *);
static int fixate_sdp_f(struct sip_msg *, char *, char *);
static char *gencookie();
static int rtpp_test(struct rtpp_node*, int, int);
static int unforce_rtp_proxy_f(struct sip_msg *, char *, char *);
static int start_recording_f(struct sip_msg *, char *, char *);
static int force_rtp_proxy0_f(struct sip_msg *, char *, char *);
static int force_rtp_proxy1_f(struct sip_msg *, char *, char *);
static int force_rtp_proxy2_f(struct sip_msg *, char *, char *);
static int fix_nated_register_f(struct sip_msg *, char *, char *);
static int add_rcv_param_f(struct sip_msg *, char *, char *);
static int rewrite_from_from_f(struct sip_msg *, char *, char *);

inline static int fixup_str2int(void**, int);
static int mod_init(void);
static void mod_cleanup(void);
static int child_init(int);

struct socket_info* force_socket = 0;


static struct {
	const char *cnetaddr;
	uint32_t netaddr;
	uint32_t mask;
} nets_1918[] = {
	{"10.0.0.0",    0, 0xffffffffu << 24},
	{"172.16.0.0",  0, 0xffffffffu << 20},
	{"192.168.0.0", 0, 0xffffffffu << 16},
	{NULL, 0, 0}
};

static str sup_ptypes[] = {
	{.s = "udp", .len = 3},
	{.s = "udptl", .len = 5},
	{.s = "rtp/avp", .len = 7},
	{.s = NULL, .len = 0}
};

static char *rtpproxy_sock = "unix:/var/run/rtpproxy.sock"; /* list */
static char *force_socket_str = 0;
static int rtpproxy_disable = 0;
static int rtpproxy_disable_tout = 60;
static int rtpproxy_retr = 5;
static int rtpproxy_tout = 1;
static pid_t mypid;
static unsigned int myseqn = 0;
static int rcv_avp_no = 42;
char *pname_audio = "-";
char *pname_video = "-";
char *codecs_audio = "-";
char *codecs_video = "-";

struct rtpp_head {
	struct rtpp_node	*rn_first;
	struct rtpp_node	*rn_last;
};

struct rtpp_node {
	char			*rn_url;	/* unparsed, deletable */
	int			rn_umode;
	char			*rn_address;	/* substring of rn_url */
	int			rn_fd;		/* control fd */
	int			rn_disabled;	/* found unaccessible? */
	unsigned		rn_weight;	/* for load balancing */
	int			rn_recheck_ticks;
	struct rtpp_node	*rn_next;
};

/* RTP proxy balancing list */
static struct rtpp_head rtpp_list;
static int rtpp_node_count = 0;

static cmd_export_t cmds[] = {
	{"fix_nated_contact",  fix_nated_contact_f,    0, 0,             REQUEST_ROUTE | ONREPLY_ROUTE },
	{"fix_nated_sdp",      fix_nated_sdp_f,        1, fixup_str2int, REQUEST_ROUTE | ONREPLY_ROUTE | FAILURE_ROUTE },
	{"fixate_sdp",         fixate_sdp_f,           0, 0,             REQUEST_ROUTE | ONREPLY_ROUTE },
	{"unforce_rtp_proxy",  unforce_rtp_proxy_f,    0, 0,             REQUEST_ROUTE | ONREPLY_ROUTE | FAILURE_ROUTE },
	{"force_rtp_proxy",    force_rtp_proxy0_f,     0, 0,             REQUEST_ROUTE | ONREPLY_ROUTE },
	{"force_rtp_proxy",    force_rtp_proxy1_f,     1, 0,             REQUEST_ROUTE | ONREPLY_ROUTE },
	{"force_rtp_proxy",    force_rtp_proxy2_f,     2, 0,             REQUEST_ROUTE | ONREPLY_ROUTE },
	{"nat_uac_test",       nat_uac_test_f,         1, fixup_str2int, REQUEST_ROUTE | ONREPLY_ROUTE | FAILURE_ROUTE },
	{"fix_nated_register", fix_nated_register_f,   0, 0,             REQUEST_ROUTE },
	{"add_rcv_param",      add_rcv_param_f,        0, 0,             REQUEST_ROUTE },
	{"rewrite_from_from",  rewrite_from_from_f,    0, 0,             REQUEST_ROUTE | ONREPLY_ROUTE },
	{"is_hold",		is_hold_f,	       0, 0,		 REQUEST_ROUTE | ONREPLY_ROUTE },
	{"start_moh",		start_moh_f,	       0, 0,		 REQUEST_ROUTE | ONREPLY_ROUTE },
	{"stop_moh",		stop_moh_f,	       0, 0,		 REQUEST_ROUTE | ONREPLY_ROUTE },
	{"start_recording",	start_recording_f,     0, 0,		 REQUEST_ROUTE | ONREPLY_ROUTE },
	{0, 0, 0, 0, 0}
};

static param_export_t params[] = {
	{"natping_interval",      INT_PARAM, &natping_interval      },
	{"natping_method",	  STR_PARAM, &natping_method	    },
	{"ping_nated_only",       INT_PARAM, &ping_nated_only       },
	{"rtpproxy_sock",         STR_PARAM, &rtpproxy_sock         },
	{"rtpproxy_disable",      INT_PARAM, &rtpproxy_disable      },
	{"rtpproxy_disable_tout", INT_PARAM, &rtpproxy_disable_tout },
	{"rtpproxy_retr",         INT_PARAM, &rtpproxy_retr         },
	{"rtpproxy_tout",         INT_PARAM, &rtpproxy_tout         },
	{"received_avp",          INT_PARAM, &rcv_avp_no            },
	{"force_socket",          STR_PARAM, &force_socket_str		},
	{"pname_audio",		  STR_PARAM, &pname_audio		},
	{"pname_video",		  STR_PARAM, &pname_video		},
	{"codecs_audio",	  STR_PARAM, &codecs_audio		},
	{"codecs_video",	  STR_PARAM, &codecs_video		},
	{0, 0, 0}
};

struct module_exports exports = {
	"nathelper",
	cmds,
	params,
	mod_init,
	0, /* reply processing */
	mod_cleanup, /* destroy function */
	0, /* on_break */
	child_init
};

static int
mod_init(void)
{
	int i;
	struct in_addr addr;
	str socket_str;

	if (force_socket_str) {
		socket_str.s=force_socket_str;
		socket_str.len=strlen(socket_str.s);
		force_socket=grep_sock_info(&socket_str,0,0);
	}

	if (natpinger_init() < 0) {
		LOG(L_ERR, "nathelper: natpinger_init() failed\n");
		return -1;
	}

	/* Prepare 1918 networks list */
	for (i = 0; nets_1918[i].cnetaddr != NULL; i++) {
		if (inet_aton(nets_1918[i].cnetaddr, &addr) != 1)
			abort();
		nets_1918[i].netaddr = ntohl(addr.s_addr) & nets_1918[i].mask;
	}

	memset(&rtpp_list, 0, sizeof(rtpp_list));
	rtpp_node_count = 0;
	if (rtpproxy_disable == 0) {
		/* Make rtp proxies list. */
		char *p, *p1, *p2, *plim;

		p = rtpproxy_sock;
		plim = p + strlen(p);
		for(;;) {
			struct rtpp_node *pnode;
			int weight;

			weight = 1;
			while (*p && isspace(*p))
				++p;
			if (p >= plim)
				break;
			p1 = p;
			while (*p && !isspace(*p))
				++p;
			if (p <= p1)
				break; /* may happen??? */
			/* Have weight specified? If yes, scan it */
			p2 = memchr(p1, '=', p - p1);
			if (p2 != NULL) {
				weight = strtoul(p2 + 1, NULL, 10);
			} else {
				p2 = p;
			}
			pnode = pkg_malloc(sizeof(struct rtpp_node));
			if (pnode == NULL) {
				LOG(L_ERR, "nathelper: Can't allocate memory\n");
				return -1;
			}
			memset(pnode, 0, sizeof(*pnode));
			pnode->rn_recheck_ticks = 0;
			pnode->rn_weight = weight;
			pnode->rn_umode = 0;
			pnode->rn_fd = -1;
			pnode->rn_disabled = 0;
			pnode->rn_url = pkg_malloc(p2 - p1 + 1);
			if (pnode->rn_url == NULL) {
				LOG(L_ERR, "nathelper: Can't allocate memory\n");
				return -1;
			}
			memmove(pnode->rn_url, p1, p2 - p1);
			pnode->rn_url[p2 - p1] = 0;
			if (rtpp_list.rn_first == NULL) {
				rtpp_list.rn_first = pnode;
			} else {
				rtpp_list.rn_last->rn_next = pnode;
			}
			rtpp_list.rn_last = pnode;
			++rtpp_node_count;
			/* Leave only address in rn_address */
			pnode->rn_address = pnode->rn_url;
			if (strncmp(pnode->rn_address, "udp:", 4) == 0) {
				pnode->rn_umode = 1;
				pnode->rn_address += 4;
			} else if (strncmp(pnode->rn_address, "udp6:", 5) == 0) {
				pnode->rn_umode = 6;
				pnode->rn_address += 5;
			} else if (strncmp(pnode->rn_address, "unix:", 5) == 0) {
				pnode->rn_umode = 0;
				pnode->rn_address += 5;
			}
		}
	}

	return 0;
}

static void
mod_cleanup(void)
{

	natpinger_cleanup();
}

static int
child_init(int rank)
{
	int n;
	char *cp;
	struct addrinfo hints, *res;
	struct rtpp_node *pnode;

	if (natpinger_child_init(rank) < 0)
		return -1;

	/* Iterate known RTP proxies - create sockets */
	mypid = getpid();
	for (pnode = rtpp_list.rn_first; pnode != NULL; pnode = pnode->rn_next) {
		char *old_colon;

		if (pnode->rn_umode == 0)
			goto rptest;
		/*
		 * This is UDP or UDP6. Detect host and port; lookup host;
		 * do connect() in order to specify peer address
		 */
		old_colon = cp = strrchr(pnode->rn_address, ':');
		if (cp != NULL) {
			old_colon = cp;
			*cp = '\0';
			cp++;
		}
		if (cp == NULL || *cp == '\0')
			cp = CPORT;

		memset(&hints, 0, sizeof(hints));
		hints.ai_flags = 0;
		hints.ai_family = (pnode->rn_umode == 6) ? AF_INET6 : AF_INET;
		hints.ai_socktype = SOCK_DGRAM;
		if ((n = getaddrinfo(pnode->rn_address, cp, &hints, &res)) != 0) {
			LOG(L_ERR, "nathelper: getaddrinfo: %s\n", gai_strerror(n));
			return -1;
		}
		if (old_colon)
			*old_colon = ':'; /* restore rn_address */

		pnode->rn_fd = socket((pnode->rn_umode == 6)
		    ? AF_INET6 : AF_INET, SOCK_DGRAM, 0);
		if (pnode->rn_fd == -1) {
			LOG(L_ERR, "nathelper: can't create socket\n");
			freeaddrinfo(res);
			return -1;
		}

		if (connect(pnode->rn_fd, res->ai_addr, res->ai_addrlen) == -1) {
			LOG(L_ERR, "nathelper: can't connect to a RTP proxy\n");
			close(pnode->rn_fd);
			pnode->rn_fd = -1;
			freeaddrinfo(res);
			return -1;
		}
		freeaddrinfo(res);
rptest:
		pnode->rn_disabled = rtpp_test(pnode, 0, 1);
	}

	if (rtpproxy_disable)
		rtpproxy_disable_tout = -1;

	return 0;
}

int
isnulladdr(str *sx, int pf)
{
	char *cp;

	if (pf == AF_INET6) {
		for(cp = sx->s; cp < sx->s + sx->len; cp++)
			if (*cp != '0' && *cp != ':')
				return 0;
		return 1;
	}
	return (sx->len == 7 && memcmp("0.0.0.0", sx->s, 7) == 0);
}

/*
 * ser_memmem() returns the location of the first occurrence of data
 * pattern b2 of size len2 in memory block b1 of size len1 or
 * NULL if none is found. Obtained from NetBSD.
 */
static void *
ser_memmem(const void *b1, const void *b2, size_t len1, size_t len2)
{
	/* Initialize search pointer */
	char *sp = (char *) b1;

	/* Initialize pattern pointer */
	char *pp = (char *) b2;

	/* Initialize end of search address space pointer */
	char *eos = sp + len1 - len2;

	/* Sanity check */
	if(!(b1 && b2 && len1 && len2))
		return NULL;

	while (sp <= eos) {
		if (*sp == *pp)
			if (memcmp(sp, pp, len2) == 0)
				return sp;

			sp++;
	}

	return NULL;
}

/*
 * Some helper functions taken verbatim from tm module.
 */

/*
 * Extract tag from To header field of a response
 * assumes the to header is already parsed, so
 * make sure it really is before calling this function
 */
int
get_to_tag(struct sip_msg* _m, str* _tag)
{

	if (!_m->to) {
		LOG(L_ERR, "get_to_tag(): To header field missing\n");
		return -1;
	}

	if (get_to(_m)->tag_value.len) {
		_tag->s = get_to(_m)->tag_value.s;
		_tag->len = get_to(_m)->tag_value.len;
	} else {
		_tag->s = 0; /* fixes gcc 4.0 warnings */
		_tag->len = 0;
	}

	return 0;
}


/*
 * Extract tag from From header field of a request
 */
int
get_from_tag(struct sip_msg* _m, str* _tag)
{

	if (parse_from_header(_m) == -1) {
		LOG(L_ERR, "get_from_tag(): Error while parsing From header\n");
		return -1;
	}

	if (get_from(_m)->tag_value.len) {
		_tag->s = get_from(_m)->tag_value.s;
		_tag->len = get_from(_m)->tag_value.len;
	} else {
		_tag->len = 0;
	}

	return 0;
}

/*
 * Extract Call-ID value
 * assumes the callid header is already parsed
 * (so make sure it is, before calling this function or
 *  it might fail even if the message _has_ a callid)
 */
int
get_callid(struct sip_msg* _m, str* _cid)
{

	if ((parse_headers(_m, HDR_CALLID, 0) == -1)) {
		LOG(L_ERR, "get_callid(): parse_headers() failed\n");
		return -1;
	}

	if (_m->callid == NULL) {
		LOG(L_ERR, "get_callid(): Call-ID not found\n");
		return -1;
	}

	_cid->s = _m->callid->body.s;
	_cid->len = _m->callid->body.len;
	trim(_cid);
	return 0;
}

/*
 * Extract URI from the Contact header field
 */
static inline int
get_contact_uri(struct sip_msg* _m, struct sip_uri *uri, contact_t** _c)
{

	if ((parse_headers(_m, HDR_CONTACT, 0) == -1) || !_m->contact)
		return -1;
	if (!_m->contact->parsed && parse_contact(_m->contact) < 0) {
		LOG(L_ERR, "get_contact_uri: Error while parsing Contact body\n");
		return -1;
	}
	*_c = ((contact_body_t*)_m->contact->parsed)->contacts;
	if (*_c == NULL) {
		LOG(L_ERR, "get_contact_uri: Error while parsing Contact body\n");
		return -1;
	}
	if (parse_uri((*_c)->uri.s, (*_c)->uri.len, uri) < 0 || uri->host.len <= 0) {
		LOG(L_ERR, "get_contact_uri: Error while parsing Contact URI\n");
		return -1;
	}
	return 0;
}

/*
 * Replaces ip:port pair in the Contact: field with the source address
 * of the packet.
 */
static int
fix_nated_contact_f(struct sip_msg* msg, char* str1, char* str2)
{
	int offset, len, len1;
	char *cp, *buf, temp[2];
	contact_t* c;
	struct lump* anchor;
	struct sip_uri uri;
	str hostport;

	if (get_contact_uri(msg, &uri, &c) == -1)
		return -1;
	if (uri.proto != PROTO_UDP && uri.proto != PROTO_NONE)
		return -1;
	if ((c->uri.s < msg->buf) || (c->uri.s > (msg->buf + msg->len))) {
		LOG(L_ERR, "ERROR: you can't call fix_nated_contact twice, "
		    "check your config!\n");
		return -1;
	}

	offset = c->uri.s - msg->buf;
	anchor = del_lump(msg, offset, c->uri.len, HDR_CONTACT);
	if (anchor == 0)
		return -1;

	if (uri.maddr_val.len == 0) {
		hostport = uri.host;
		if (uri.port.len > 0)
			hostport.len = uri.port.s + uri.port.len - uri.host.s;
	} else {
		hostport = uri.maddr_val;
	}

	cp = ip_addr2a(&msg->rcv.src_ip);
	len = c->uri.len + strlen(cp) + 6 /* :port */ - hostport.len + 1;
	buf = pkg_malloc(len);
	if (buf == NULL) {
		LOG(L_ERR, "ERROR: fix_nated_contact: out of memory\n");
		return -1;
	}
	temp[0] = hostport.s[0];
	temp[1] = c->uri.s[c->uri.len];
	c->uri.s[c->uri.len] = hostport.s[0] = '\0';
	len1 = snprintf(buf, len, "%s%s:%d%s", c->uri.s, cp, msg->rcv.src_port,
	    hostport.s + hostport.len);
	if (len1 < len)
		len = len1;
	hostport.s[0] = temp[0];
	c->uri.s[c->uri.len] = temp[1];
	if (insert_new_lump_after(anchor, buf, len, HDR_CONTACT) == 0) {
		pkg_free(buf);
		return -1;
	}
	c->uri.s = buf;
	c->uri.len = len;

	return 1;
}

inline static int
fixup_str2int( void** param, int param_no)
{
	unsigned long go_to;
	int err;

	if (param_no == 1 || param_no == 2) {
		go_to = str2s(*param, strlen(*param), &err);
		if (err == 0) {
			pkg_free(*param);
			*param = (void *)go_to;
			return 0;
		} else {
			LOG(L_ERR, "ERROR: fixup_str2int: bad number <%s>\n",
				(char *)(*param));
			return E_CFG;
		}
	}
	return 0;
}

/*
 * Test if IP address pointed to by saddr belongs to RFC1918 networks
 */
static inline int
is1918addr(str *saddr)
{
	struct in_addr addr;
	uint32_t netaddr;
	int i, rval;
	char backup;

	rval = -1;
	backup = saddr->s[saddr->len];
	saddr->s[saddr->len] = '\0';
	if (inet_aton(saddr->s, &addr) != 1)
		goto theend;
	netaddr = ntohl(addr.s_addr);
	for (i = 0; nets_1918[i].cnetaddr != NULL; i++) {
		if ((netaddr & nets_1918[i].mask) == nets_1918[i].netaddr) {
			rval = 1;
			goto theend;
		}
	}
	rval = 0;

theend:
	saddr->s[saddr->len] = backup;
	return rval;
}

/*
 * test for occurrence of RFC1918 IP address in Contact HF
 */
static int
contact_1918(struct sip_msg* msg)
{
	struct sip_uri uri;
	contact_t* c;
	char t;
	str host;
	short int port;

	if (get_contact_uri(msg, &uri, &c) == -1)
		return -1;

	if (uri.maddr_val.len == 0)
		return (is1918addr(&(uri.host)) == 1) ? 1 : 0;
	t = uri.maddr_val.s[uri.maddr_val.len];
	uri.maddr_val.s[uri.maddr_val.len] = '\0';
	parse_hostport(uri.maddr_val.s, &host, &port);
	uri.maddr_val.s[uri.maddr_val.len] = t;
	if (host.len <= 0)
		return 0;
	return (is1918addr(&host) == 1) ? 1 : 0;
}

/*
 * test for occurrence of RFC1918 IP address in SDP
 */
static int
sdp_1918(struct sip_msg* msg)
{
	str body, ip;
	int pf;

	if (extract_body(msg, &body) == -1) {
		LOG(L_ERR,"ERROR: sdp_1918: cannot extract body from msg!\n");
		return 0;
	}
	if (extract_mediaip(&body, &ip, &pf) == -1) {
		LOG(L_ERR, "ERROR: sdp_1918: can't extract media IP from the SDP\n");
		return 0;
	}
	if (pf != AF_INET || isnulladdr(&ip, pf))
		return 0;

	return (is1918addr(&ip) == 1) ? 1 : 0;
}

/*
 * test for occurrence of RFC1918 IP address in top Via
 */
static int
via_1918(struct sip_msg* msg)
{

	return (is1918addr(&(msg->via1->host)) == 1) ? 1 : 0;
}

static int
nat_uac_test_f(struct sip_msg* msg, char* str1, char* str2)
{
	int tests;

	tests = (int)(long)str1;

	/* return true if any of the NAT-UAC tests holds */

	/* test if the source port is different from the port in Via */
	if ((tests & NAT_UAC_TEST_RPORT) &&
		 (msg->rcv.src_port!=(msg->via1->port?msg->via1->port:SIP_PORT)) ){
		return 1;
	}
	/*
	 * test if source address of signaling is different from
	 * address advertised in Via
	 */
	if ((tests & NAT_UAC_TEST_RCVD) && received_test(msg))
		return 1;
	/*
	 * test for occurrences of RFC1918 addresses in Contact
	 * header field
	 */
	if ((tests & NAT_UAC_TEST_C_1918) && (contact_1918(msg)>0))
		return 1;
	/*
	 * test for occurrences of RFC1918 addresses in SDP body
	 */
	if ((tests & NAT_UAC_TEST_S_1918) && sdp_1918(msg))
		return 1;
	/*
	 * test for occurrences of RFC1918 addresses top Via
	 */
	if ((tests & NAT_UAC_TEST_V_1918) && via_1918(msg))
		return 1;

	/* no test succeeded */
	return -1;

}

#define	ADD_ADIRECTION	0x01
#define	FIX_MEDIP	0x02
#define	ADD_ANORTPPROXY	0x04

#define	ADIRECTION	"a=direction:active\r\n"
#define	ADIRECTION_LEN	(sizeof(ADIRECTION) - 1)

#define	AOLDMEDIP	"a=oldmediaip:"
#define	AOLDMEDIP_LEN	(sizeof(AOLDMEDIP) - 1)

#define	AOLDMEDIP6	"a=oldmediaip6:"
#define	AOLDMEDIP6_LEN	(sizeof(AOLDMEDIP6) - 1)

#define	AOLDMEDPRT	"a=oldmediaport:"
#define	AOLDMEDPRT_LEN	(sizeof(AOLDMEDPRT) - 1)

#define	ANORTPPROXY	"a=nortpproxy:yes\r\n"
#define	ANORTPPROXY_LEN	(sizeof(ANORTPPROXY) - 1)

static int
fix_nated_sdp_f(struct sip_msg* msg, char* str1, char* str2)
{
	str body, body1, oldip, newip;
	int level, pf;
	char *buf;
	struct lump* anchor;

	level = (int)(long)str1;

	if (extract_body(msg, &body) == -1) {
		LOG(L_ERR,"ERROR: fix_nated_sdp: cannot extract body from msg!\n");
		return -1;
	}

	if (level & (ADD_ADIRECTION | ADD_ANORTPPROXY)) {
		msg->msg_flags |= FL_FORCE_ACTIVE;
		anchor = anchor_lump(msg, body.s + body.len - msg->buf, 0, 0);
		if (anchor == NULL) {
			LOG(L_ERR, "ERROR: fix_nated_sdp: anchor_lump failed\n");
			return -1;
		}
		if (level & ADD_ADIRECTION) {
			buf = pkg_malloc(ADIRECTION_LEN * sizeof(char));
			if (buf == NULL) {
				LOG(L_ERR, "ERROR: fix_nated_sdp: out of memory\n");
				return -1;
			}
			memcpy(buf, ADIRECTION, ADIRECTION_LEN);
			if (insert_new_lump_after(anchor, buf, ADIRECTION_LEN, 0) == NULL) {
				LOG(L_ERR, "ERROR: fix_nated_sdp: insert_new_lump_after failed\n");
				pkg_free(buf);
				return -1;
			}
		}
		if (level & ADD_ANORTPPROXY) {
			buf = pkg_malloc(ANORTPPROXY_LEN * sizeof(char));
			if (buf == NULL) {
				LOG(L_ERR, "ERROR: fix_nated_sdp: out of memory\n");
				return -1;
			}
			memcpy(buf, ANORTPPROXY, ANORTPPROXY_LEN);
			if (insert_new_lump_after(anchor, buf, ANORTPPROXY_LEN, 0) == NULL) {
				LOG(L_ERR, "ERROR: fix_nated_sdp: insert_new_lump_after failed\n");
				pkg_free(buf);
				return -1;
			}
		}
	}

	if (level & FIX_MEDIP) {
		/* Iterate all c= and replace ips in them. */
		unsigned hasreplaced = 0;
		int pf1 = 0;
		str body2;
		char* bodylimit = body.s + body.len;
		newip.s = ip_addr2a(&msg->rcv.src_ip);
		newip.len = strlen(newip.s);
		body1 = body;
		for(;;) {
			if (extract_mediaip(&body1, &oldip, &pf) == -1)
				break;
			if (pf != AF_INET) {
				LOG(L_ERR, "ERROR: fix_nated_sdp: "
				    "not an IPv4 address in SDP\n");
				goto finalize;
			}
			if (!pf1)
				pf1 = pf;
			else if (pf != pf1) {
				LOG(L_ERR, "ERROR: fix_nated_sdp: mismatching "
				    "address families in SDP\n");
				return -1;
			}
			body2.s = oldip.s + oldip.len;
			body2.len = bodylimit - body2.s;
			if (alter_mediaip(msg, &body1, &oldip, pf, &newip, pf,
			    1) == -1)
			{
				LOG(L_ERR, "ERROR: fix_nated_sdp: can't alter media IP");
				return -1;
			}
			hasreplaced = 1;
			body1 = body2;
		}
		if (!hasreplaced) {
			LOG(L_ERR, "ERROR: fix_nated_sdp: can't extract media IP from the SDP\n");
			goto finalize;
		}
	}

finalize:
	return 1;
}

int
extract_mediaip(str *body, str *mediaip, int *pf)
{
	char *cp, *cp1;
	int len, nextisip;

	cp1 = NULL;
	for (cp = body->s; (len = body->s + body->len - cp) > 0;) {
		cp1 = ser_memmem(cp, "c=", len, 2);
		if (cp1 == NULL || cp1[-1] == '\n' || cp1[-1] == '\r')
			break;
		cp = cp1 + 2;
	}
	if (cp1 == NULL) {
		LOG(L_ERR, "ERROR: extract_mediaip: no `c=' in SDP\n");
		return -1;
	}
	mediaip->s = cp1 + 2;
	mediaip->len = eat_line(mediaip->s, body->s + body->len - mediaip->s) - mediaip->s;
	trim_len(mediaip->len, mediaip->s, *mediaip);

	nextisip = 0;
	for (cp = mediaip->s; cp < mediaip->s + mediaip->len;) {
		len = eat_token_end(cp, mediaip->s + mediaip->len) - cp;
		if (nextisip == 1) {
			mediaip->s = cp;
			mediaip->len = len;
			nextisip++;
			break;
		}
		if (len == 3 && memcmp(cp, "IP", 2) == 0) {
			switch (cp[2]) {
			case '4':
				nextisip = 1;
				*pf = AF_INET;
				break;

			case '6':
				nextisip = 1;
				*pf = AF_INET6;
				break;

			default:
				break;
			}
		}
		cp = eat_space_end(cp + len, mediaip->s + mediaip->len);
	}
	if (nextisip != 2 || mediaip->len == 0) {
		LOG(L_ERR, "ERROR: extract_mediaip: "
		    "no `IP[4|6]' in `c=' field\n");
		return -1;
	}
	return 1;
}

int
extract_mediaport(str *body, str *mediaport)
{
	char *cp, *cp1;
	int len, i;
	str ptype;

	cp1 = NULL;
	for (cp = body->s; (len = body->s + body->len - cp) > 0;) {
		cp1 = ser_memmem(cp, "m=", len, 2);
		if (cp1 == NULL || cp1[-1] == '\n' || cp1[-1] == '\r')
			break;
		cp = cp1 + 2;
	}
	if (cp1 == NULL) {
		LOG(L_ERR, "ERROR: extract_mediaport: no `m=' in SDP\n");
		return -1;
	}
	mediaport->s = cp1 + 2; /* skip `m=' */
	mediaport->len = eat_line(mediaport->s, body->s + body->len -
	  mediaport->s) - mediaport->s;
	trim_len(mediaport->len, mediaport->s, *mediaport);

	/* Skip media supertype and spaces after it */
	cp = eat_token_end(mediaport->s, mediaport->s + mediaport->len);
	mediaport->len -= cp - mediaport->s;
	if (mediaport->len <= 0 || cp == mediaport->s) {
		LOG(L_ERR, "ERROR: extract_mediaport: no port in `m='\n");
		return -1;
	}
	mediaport->s = cp;
	cp = eat_space_end(mediaport->s, mediaport->s + mediaport->len);
	mediaport->len -= cp - mediaport->s;
	if (mediaport->len <= 0 || cp == mediaport->s) {
		LOG(L_ERR, "ERROR: extract_mediaport: no port in `m='\n");
		return -1;
	}
	/* Extract port */
	mediaport->s = cp;
	cp = eat_token_end(mediaport->s, mediaport->s + mediaport->len);
	ptype.len = mediaport->len - (cp - mediaport->s);
	if (ptype.len <= 0 || cp == mediaport->s) {
		LOG(L_ERR, "ERROR: extract_mediaport: no port in `m='\n");
		return -1;
	}
	ptype.s = cp;
	mediaport->len = cp - mediaport->s;
	/* Skip spaces after port */
	cp = eat_space_end(ptype.s, ptype.s + ptype.len);
	ptype.len -= cp - ptype.s;
	if (ptype.len <= 0 || cp == ptype.s) {
		LOG(L_ERR, "ERROR: extract_mediaport: no protocol type in `m='\n");
		return -1;
	}
	/* Extract protocol type */
	ptype.s = cp;
	cp = eat_token_end(ptype.s, ptype.s + ptype.len);
	if (cp == ptype.s) {
		LOG(L_ERR, "ERROR: extract_mediaport: no protocol type in `m='\n");
		return -1;
	}
	ptype.len = cp - ptype.s;

	for (i = 0; sup_ptypes[i].s != NULL; i++)
		if (ptype.len == sup_ptypes[i].len &&
		    strncasecmp(ptype.s, sup_ptypes[i].s, ptype.len) == 0)
			return 0;
	/* Unproxyable protocol type. Generally it isn't error. */
	return -1;
}

int
alter_mediaip(struct sip_msg *msg, str *body, str *oldip, int oldpf,
  str *newip, int newpf, int flags)
{
	char *buf;
	int offset;
	struct lump* anchor;
	str omip, nip, oip;

	/* check that updating mediaip is really necessary */
	if (oldpf == newpf && isnulladdr(oldip, oldpf) && !(flags & 2))
		return 0;
	if (newip->len == oldip->len &&
	    memcmp(newip->s, oldip->s, newip->len) == 0)
		return 0;

	/*
	 * Since rewriting the same info twice will mess SDP up,
	 * apply simple anti foot shooting measure - put flag on
	 * messages that have been altered and check it when
	 * another request comes.
	 */
#if 0
	/* disabled:
	 *  - alter_mediaip is called twice if 2 c= lines are present
	 *    in the sdp (and we want to allow it)
	 *  - the message flags are propagated in the on_reply_route
	 *  => if we set the flags for the request they will be seen for the
	 *    reply too, but we don't want that
	 *  --andrei
	 */
	if (msg->msg_flags & FL_SDP_IP_AFS) {
		LOG(L_ERR, "ERROR: alter_mediaip: you can't rewrite the same "
		  "SDP twice, check your config!\n");
		return -1;
	}
#endif

	if ((flags & 1) != 0) {
		anchor = anchor_lump(msg, body->s + body->len - msg->buf, 0, 0);
		if (anchor == NULL) {
			LOG(L_ERR, "ERROR: alter_mediaip: anchor_lump failed\n");
			return -1;
		}
		if (oldpf == AF_INET6) {
			omip.s = AOLDMEDIP6;
			omip.len = AOLDMEDIP6_LEN;
		} else {
			omip.s = AOLDMEDIP;
			omip.len = AOLDMEDIP_LEN;
		}
		buf = pkg_malloc(omip.len + oldip->len + CRLF_LEN);
		if (buf == NULL) {
			LOG(L_ERR, "ERROR: alter_mediaip: out of memory\n");
			return -1;
		}
		memcpy(buf, omip.s, omip.len);
		memcpy(buf + omip.len, oldip->s, oldip->len);
		memcpy(buf + omip.len + oldip->len, CRLF, CRLF_LEN);
		if (insert_new_lump_after(anchor, buf,
		    omip.len + oldip->len + CRLF_LEN, 0) == NULL) {
			LOG(L_ERR, "ERROR: alter_mediaip: insert_new_lump_after failed\n");
			pkg_free(buf);
			return -1;
		}
	}

	if (oldpf == newpf) {
		nip.len = newip->len;
		nip.s = pkg_malloc(nip.len);
		if (nip.s == NULL) {
			LOG(L_ERR, "ERROR: alter_mediaip: out of memory\n");
			return -1;
		}
		memcpy(nip.s, newip->s, newip->len);
	} else {
		nip.len = newip->len + 2;
		nip.s = pkg_malloc(nip.len);
		if (nip.s == NULL) {
			LOG(L_ERR, "ERROR: alter_mediaip: out of memory\n");
			return -1;
		}
		memcpy(nip.s + 2, newip->s, newip->len);
		nip.s[0] = (newpf == AF_INET6) ? '6' : '4';
		nip.s[1] = ' ';
	}

	oip = *oldip;
	if (oldpf != newpf) {
		do {
			oip.s--;
			oip.len++;
		} while (*oip.s != '6' && *oip.s != '4');
	}
	offset = oip.s - msg->buf;
	anchor = del_lump(msg, offset, oip.len, 0);
	if (anchor == NULL) {
		LOG(L_ERR, "ERROR: alter_mediaip: del_lump failed\n");
		pkg_free(nip.s);
		return -1;
	}

#if 0
	msg->msg_flags |= FL_SDP_IP_AFS;
#endif

	if (insert_new_lump_after(anchor, nip.s, nip.len, 0) == 0) {
		LOG(L_ERR, "ERROR: alter_mediaip: insert_new_lump_after failed\n");
		pkg_free(nip.s);
		return -1;
	}
	return 0;
}

int
alter_mediaport(struct sip_msg *msg, str *body, str *oldport, str *newport,
  int preserve)
{
	char *buf;
	int offset;
	struct lump* anchor;

	/* check that updating mediaport is really necessary */
	if (newport->len == oldport->len &&
	    memcmp(newport->s, oldport->s, newport->len) == 0)
		return 0;

	/*
	 * Since rewriting the same info twice will mess SDP up,
	 * apply simple anti foot shooting measure - put flag on
	 * messages that have been altered and check it when
	 * another request comes.
	 */
#if 0
	/* disabled: - it propagates to the reply and we don't want this
	 *  -- andrei */
	if (msg->msg_flags & FL_SDP_PORT_AFS) {
		LOG(L_ERR, "ERROR: alter_mediaip: you can't rewrite the same "
		  "SDP twice, check your config!\n");
		return -1;
	}
#endif

	if (preserve != 0) {
		anchor = anchor_lump(msg, body->s + body->len - msg->buf, 0, 0);
		if (anchor == NULL) {
			LOG(L_ERR, "ERROR: alter_mediaport: anchor_lump failed\n");
			return -1;
		}
		buf = pkg_malloc(AOLDMEDPRT_LEN + oldport->len + CRLF_LEN);
		if (buf == NULL) {
			LOG(L_ERR, "ERROR: alter_mediaport: out of memory\n");
			return -1;
		}
		memcpy(buf, AOLDMEDPRT, AOLDMEDPRT_LEN);
		memcpy(buf + AOLDMEDPRT_LEN, oldport->s, oldport->len);
		memcpy(buf + AOLDMEDPRT_LEN + oldport->len, CRLF, CRLF_LEN);
		if (insert_new_lump_after(anchor, buf,
		    AOLDMEDPRT_LEN + oldport->len + CRLF_LEN, 0) == NULL) {
			LOG(L_ERR, "ERROR: alter_mediaport: insert_new_lump_after failed\n");
			pkg_free(buf);
			return -1;
		}
	}

	buf = pkg_malloc(newport->len);
	if (buf == NULL) {
		LOG(L_ERR, "ERROR: alter_mediaport: out of memory\n");
		return -1;
	}
	offset = oldport->s - msg->buf;
	anchor = del_lump(msg, offset, oldport->len, 0);
	if (anchor == NULL) {
		LOG(L_ERR, "ERROR: alter_mediaport: del_lump failed\n");
		pkg_free(buf);
		return -1;
	}
	memcpy(buf, newport->s, newport->len);
	if (insert_new_lump_after(anchor, buf, newport->len, 0) == 0) {
		LOG(L_ERR, "ERROR: alter_mediaport: insert_new_lump_after failed\n");
		pkg_free(buf);
		return -1;
	}

#if 0
	msg->msg_flags |= FL_SDP_PORT_AFS;
#endif
	return 0;
}

/*
 * Finds specified text in area [*pp...bodylimit) at line beginning.
 * Returns pointer to text, updates *pp to position after it.
 */

static char*
find_sdp_text_bol(char **pp, char *plimit, char *text, size_t textlen)
{
	/* Find text at beginning of line */
	if (*pp == NULL)
		return NULL;
	for(;;) {
		char* p;
		if (*pp >= plimit)
			return NULL;
		if (!(p = ser_memmem(*pp, text, plimit - *pp, textlen))) {
			*pp = plimit;
			return NULL;
		}
		*pp = p + 1;
		if (p[-1] != '\n' && p[-1] != '\r')
			continue;
		return p;
	}
	/*UNREACHED*/
	return NULL;
}

static int
fixate_sdp_f(struct sip_msg* msg, char* str1, char* str2)
{
	char *cp;
	int newpf;
	str body, newip, newport, dest, oldip, oldport;
	struct sip_uri ruri;
	struct hdr_field *hdr;
	struct via_body *rvia;
	char *bodylimit, *v1p, *v2p, *m1p, *m2p, *c1p, *c2p;
	char *om_ip_pos, *om_port_pos;
	int c1_altered;

	if (msg->first_line.type == SIP_REQUEST &&
	    msg->first_line.u.request.method_value == METHOD_INVITE) {
		LOG(L_ERR, "DEBUG: fixate_sdp: request\n");
		if (msg->parsed_uri_ok) {
			dest = msg->parsed_uri.host;
		} else {
			if (parse_uri(msg->new_uri.s, msg->new_uri.len, &ruri) < 0) {
				LOG(L_ERR, "ERROR: fixate_sdp: can't parse request uri\n");
				return -1;
			}
			dest = ruri.host;
		}
	} else if (msg->first_line.type == SIP_REPLY) {
		LOG(L_ERR, "DEBUG: fixate_sdp: reply\n");
		rvia = NULL;
		for (hdr=msg->headers; hdr; hdr=hdr->next) {
			if (hdr->type == HDR_VIA)
				rvia = hdr->parsed;
		}
		if (rvia == NULL) {
			LOG(L_ERR, "ERROR: fixate_sdp: no or incorrect Via in reply\n");
			return -1;
		}
		if (rvia->received != NULL) {
			dest = rvia->received->value;
		} else {
			dest = rvia->host;
		}
	} else {
		return -1;
	}

	if (extract_body(msg, &body) == -1 || body.len == 0) {
		LOG(L_ERR, "ERROR: fixate_sdp: can't extract body "
		    "from the message\n");
		return -1;
	}
	bodylimit = body.s + body.len;
	v2p = v1p = find_sdp_line(body.s, bodylimit, 'v');
	if (!v1p) {
		LOG(L_ERR, "fixate_sdp: no sessions found\n");
		return -1;
	}
	om_ip_pos = body.s;
	om_port_pos = body.s;
	for(;;) {
		v1p = v2p;
		if (v1p == NULL || v1p >= bodylimit)
			break;
		v2p = find_next_sdp_line(v1p, bodylimit, 'v', bodylimit);
		m2p = m1p = find_sdp_line(v1p, v2p, 'm');
		c1p = find_sdp_line(v1p, v2p, 'c');
		c1_altered = 0;
		if (!m1p) {
			LOG(L_ERR, "fixate_sdp: session without media\n");
			return -1;
		}
		for(;;) {
			str tmpstr1;
			m1p = m2p;
			if (m1p == NULL || m1p >= v2p)
				break;
			m2p = find_next_sdp_line(m1p, v2p, 'm', v2p);
			c2p = find_sdp_line(m1p, m2p, 'c');
			/* Set old port and IP. Order doesn't matter. */
			tmpstr1.s = m1p;
			tmpstr1.len = bodylimit - m1p;
			if (extract_mediaport(&tmpstr1, &newport) == -1) {
				LOG(L_ERR, "ERROR: fixate_sdp: can't extract media port from the SDP\n");
				return -1;
			}
			cp = find_sdp_text_bol(&om_port_pos, bodylimit, AOLDMEDPRT, AOLDMEDPRT_LEN);
			if (cp) {
				oldport.s = cp + AOLDMEDPRT_LEN;
				oldport.len = eat_line(oldport.s, bodylimit - oldport.s) - oldport.s;
				trim_len(oldport.len, oldport.s, oldport);
				if (oldport.len != 0 &&
				    alter_mediaport(msg, &body, &newport, &oldport, 0) == -1)
					return -1;
			}
			if (c2p || !c1_altered) {
				tmpstr1.s = c2p ? c2p : c1p;
				tmpstr1.len = bodylimit - tmpstr1.s;
				if (extract_mediaip(&tmpstr1, &newip, &newpf) == -1) {
					LOG(L_ERR, "ERROR: fixate_sdp: can't extract media IP from the SDP\n");
					return -1;
				}
				if (newip.len != dest.len || memcmp(newip.s, dest.s, dest.len) != 0 ||
				    isnulladdr(&newip, newpf))
					return -1;
				cp = find_sdp_text_bol(&om_ip_pos, bodylimit, AOLDMEDIP, AOLDMEDIP_LEN);
				if (cp) {
					oldip.s = cp + AOLDMEDIP_LEN;
					oldip.len = eat_line(oldip.s, bodylimit - oldip.s) - oldip.s;
					trim_len(oldip.len, oldip.s, oldip);
				}
				if (newip.len == oldip.len &&
				    memcmp(newip.s, oldip.s, newip.len) == 0)
					oldip.len = 0;
						
				if (oldip.len != 0) {
					if (alter_mediaip(msg, &body, &newip, newpf, &oldip, AF_INET,
					    0) == -1)
						return -1;
				}
				if (!c2p)
					c1_altered = 1;
			} /* if rewrite IP */
		} /* medias */
	} /* sessions */

	return 1;
}

static char *
gencookie()
{
	static char cook[34];

	sprintf(cook, "%d_%u ", (int)mypid, myseqn);
	myseqn++;
	return cook;
}

static int
rtpp_test(struct rtpp_node *node, int isdisabled, int force)
{
	int rtpp_ver;
	char *cp;
	struct iovec v[2] = {{NULL, 0}, {"V", 1}};
	struct iovec vf[4] = {{NULL, 0}, {"VF", 2}, {" ", 1},
	    {REQ_CPROTOVER, 8}};

	if (force == 0) {
		if (isdisabled == 0)
			return 0;
		if (node->rn_recheck_ticks > get_ticks())
			return 1;
	}
	do {
		cp = send_rtpp_command(node, v, 2);
		if (cp == NULL) {
			LOG(L_WARN,"WARNING: rtpp_test: can't get version of "
			    "the RTP proxy\n");
			break;
		}
		rtpp_ver = atoi(cp);
		if (rtpp_ver != SUP_CPROTOVER) {
			LOG(L_WARN, "WARNING: rtpp_test: unsupported "
			    "version of RTP proxy <%s> found: %d supported, "
			    "%d present\n", node->rn_url,
			    SUP_CPROTOVER, rtpp_ver);
			break;
		}
		cp = send_rtpp_command(node, vf, 4);
		if (cp[0] == 'E' || atoi(cp) != 1) {
			LOG(L_WARN, "WARNING: rtpp_test: of RTP proxy <%s>"
			    "doesn't support required protocol version %s\n",
			    node->rn_url, REQ_CPROTOVER);
			break;
		}
		LOG(L_INFO, "rtpp_test: RTP proxy <%s> found, support for "
		    "it %senabled\n",
		    node->rn_url, force == 0 ? "re-" : "");
		return 0;
	} while(0);
	LOG(L_WARN, "WARNING: rtpp_test: support for RTP proxy <%s>"
	    "has been disabled%s\n", node->rn_url,
	    rtpproxy_disable_tout < 0 ? "" : " temporarily");
	if (rtpproxy_disable_tout >= 0)
		node->rn_recheck_ticks = get_ticks() + rtpproxy_disable_tout;

	return 1;
}

char *
send_rtpp_command(struct rtpp_node *node, struct iovec *v, int vcnt)
{
	struct sockaddr_un addr;
	int fd, len, i;
	char *cp;
	static char buf[256];
	struct pollfd fds[1];

	len = 0;
	cp = buf;
	if (node->rn_umode == 0) {
		memset(&addr, 0, sizeof(addr));
		addr.sun_family = AF_LOCAL;
		strncpy(addr.sun_path, node->rn_address,
		    sizeof(addr.sun_path) - 1);
#ifdef HAVE_SOCKADDR_SA_LEN
		addr.sun_len = strlen(addr.sun_path);
#endif

		fd = socket(AF_LOCAL, SOCK_STREAM, 0);
		if (fd < 0) {
			LOG(L_ERR, "ERROR: send_rtpp_command: can't create socket\n");
			goto badproxy;
		}
		if (connect(fd, (struct sockaddr *) &addr, sizeof(addr)) < 0) {
			close(fd);
			LOG(L_ERR, "ERROR: send_rtpp_command: can't connect to RTP proxy\n");
			goto badproxy;
		}

		do {
			len = writev(fd, v + 1, vcnt - 1);
		} while (len == -1 && errno == EINTR);
		if (len <= 0) {
			close(fd);
			LOG(L_ERR, "ERROR: send_rtpp_command: can't send command to a RTP proxy\n");
			goto badproxy;
		}
		do {
			len = read(fd, buf, sizeof(buf) - 1);
		} while (len == -1 && errno == EINTR);
		close(fd);
		if (len <= 0) {
			LOG(L_ERR, "ERROR: send_rtpp_command: can't read reply from a RTP proxy\n");
			goto badproxy;
		}
	} else {
		fds[0].fd = node->rn_fd;
		fds[0].events = POLLIN;
		fds[0].revents = 0;
		/* Drain input buffer */
		while ((poll(fds, 1, 0) == 1) &&
		    ((fds[0].revents & POLLIN) != 0)) {
			recv(node->rn_fd, buf, sizeof(buf) - 1, 0);
			fds[0].revents = 0;
		}
		v[0].iov_base = gencookie();
		v[0].iov_len = strlen(v[0].iov_base);
		for (i = 0; i < rtpproxy_retr; i++) {
			do {
				len = writev(node->rn_fd, v, vcnt);
			} while (len == -1 && (errno == EINTR || errno == ENOBUFS));
			if (len <= 0) {
				LOG(L_ERR, "ERROR: send_rtpp_command: "
				    "can't send command to a RTP proxy\n");
				goto badproxy;
			}
			while ((poll(fds, 1, rtpproxy_tout * 1000) == 1) &&
			    (fds[0].revents & POLLIN) != 0) {
				do {
					len = recv(node->rn_fd, buf, sizeof(buf) - 1, 0);
				} while (len == -1 && errno == EINTR);
				if (len <= 0) {
					LOG(L_ERR, "ERROR: send_rtpp_command: "
					    "can't read reply from a RTP proxy\n");
					goto badproxy;
				}
				if (len >= (v[0].iov_len - 1) &&
				    memcmp(buf, v[0].iov_base, (v[0].iov_len - 1)) == 0) {
					len -= (v[0].iov_len - 1);
					cp += (v[0].iov_len - 1);
					if (len != 0) {
						len--;
						cp++;
					}
					goto out;
				}
				fds[0].revents = 0;
			}
		}
		if (i == rtpproxy_retr) {
			LOG(L_ERR, "ERROR: send_rtpp_command: "
			    "timeout waiting reply from a RTP proxy\n");
			goto badproxy;
		}
	}

out:
	cp[len] = '\0';
	return cp;
badproxy:
	LOG(L_ERR, "send_rtpp_command(): proxy <%s> does not responding, disable it\n", node->rn_url);
	node->rn_disabled = 1;
	node->rn_recheck_ticks = get_ticks() + rtpproxy_disable_tout;
	return NULL;
}

/*
 * Main balancing routine. This does not try to keep the same proxy for
 * the call if some proxies were disabled or enabled; proxy death considered
 * too rare. Otherwise we should implement "mature" HA clustering, which is
 * too expensive here.
 */
struct rtpp_node *
select_rtpp_node(str callid, int do_test)
{
	unsigned sum, sumcut, weight_sum;
	struct rtpp_node* node;
	int was_forced;

	/* Most popular case: 1 proxy, nothing to calculate */
	if (rtpp_node_count == 1) {
		node = rtpp_list.rn_first;
		return node->rn_disabled ? NULL : node;
	}

	/* XXX Use quick-and-dirty hashing algo */
	for(sum = 0; callid.len > 0; callid.len--)
		sum += callid.s[callid.len - 1];
	sum &= 0xff;

	was_forced = 0;
retry:
	weight_sum = 0;
	for (node = rtpp_list.rn_first; node != NULL; node = node->rn_next) {
		if (node->rn_disabled) {
			/* Try to enable if it's time to try. */
			if (node->rn_recheck_ticks <= get_ticks())
				node->rn_disabled = rtpp_test(node, 1, 0);
		}
		if (!node->rn_disabled)
			weight_sum += node->rn_weight;
	}
	if (weight_sum == 0) {
		/* No proxies? Force all to be redetected, if not yet */
		if (was_forced)
			return NULL;
		was_forced = 1;
		for (node = rtpp_list.rn_first; node != NULL; node = node->rn_next) {
			node->rn_disabled = rtpp_test(node, 1, 1);
		}
		goto retry;
	}
	sumcut = sum % weight_sum;
	/*
	 * sumcut here lays from 0 to weight_sum-1.
	 * Scan proxy list and decrease until appropriate proxy is found.
	 */
	for (node = rtpp_list.rn_first; node != NULL; node = node->rn_next) {
		if (node->rn_disabled)
			continue;
		if (sumcut < node->rn_weight)
			goto found;
		sumcut -= node->rn_weight;
	}
	/* No node list */
	return NULL;
found:
	if (do_test) {
		node->rn_disabled = rtpp_test(node, node->rn_disabled, 0);
		if (node->rn_disabled)
			goto retry;
	}
	return node;
}

static int
unforce_rtp_proxy_f(struct sip_msg* msg, char* str1, char* str2)
{
	str callid, from_tag, to_tag;
	struct rtpp_node *node;
	struct iovec v[1 + 4 + 3] = {{NULL, 0}, {"D", 1}, {" ", 1}, {NULL, 0}, {" ", 1}, {NULL, 0}, {" ", 1}, {NULL, 0}};
						/* 1 */   /* 2 */   /* 3 */    /* 4 */   /* 5 */    /* 6 */   /* 1 */

	if (get_callid(msg, &callid) == -1 || callid.len == 0) {
		LOG(L_ERR, "ERROR: unforce_rtp_proxy: can't get Call-Id field\n");
		return -1;
	}
	if (get_to_tag(msg, &to_tag) == -1) {
		LOG(L_ERR, "ERROR: unforce_rtp_proxy: can't get To tag\n");
		return -1;
	}
	if (get_from_tag(msg, &from_tag) == -1 || from_tag.len == 0) {
		LOG(L_ERR, "ERROR: unforce_rtp_proxy: can't get From tag\n");
		return -1;
	}
	STR2IOVEC(callid, v[3]);
	STR2IOVEC(from_tag, v[5]);
	STR2IOVEC(to_tag, v[7]);
	node = select_rtpp_node(callid, 1);
	if (!node) {
		LOG(L_ERR, "ERROR: unforce_rtp_proxy: no available proxies\n");
		return -1;
	}
	send_rtpp_command(node, v, (to_tag.len > 0) ? 8 : 6);

	return 1;
}

static int
start_recording_f(struct sip_msg* msg, char* str1, char* str2)
{
	int nitems;
	str callid, from_tag, to_tag;
	struct rtpp_node *node;
	struct iovec v[1 + 4 + 3] = {{NULL, 0}, {"R", 1}, {" ", 1}, {NULL, 0}, {" ", 1}, {NULL, 0}, {" ", 1}, {NULL, 0}};
						/* 1 */   /* 2 */   /* 3 */    /* 4 */   /* 5 */    /* 6 */   /* 1 */

	if (get_callid(msg, &callid) == -1 || callid.len == 0) {
		LOG(L_ERR, "ERROR: start_recording: can't get Call-Id field\n");
		return -1;
	}
	if (get_to_tag(msg, &to_tag) == -1) {
		LOG(L_ERR, "ERROR: start_recording: can't get To tag\n");
		return -1;
	}
	if (get_from_tag(msg, &from_tag) == -1 || from_tag.len == 0) {
		LOG(L_ERR, "ERROR: start_recording: can't get From tag\n");
		return -1;
	}
	STR2IOVEC(callid, v[3]);
	STR2IOVEC(from_tag, v[5]);
	STR2IOVEC(to_tag, v[7]);
	node = select_rtpp_node(callid, 1);
	if (!node) {
		LOG(L_ERR, "ERROR: start_recording: no available proxies\n");
		return -1;
	}
	nitems = 8;
	if (msg->first_line.type == SIP_REPLY) {
		if (to_tag.len == 0)
			return -1;
		STR2IOVEC(to_tag, v[5]);
		STR2IOVEC(from_tag, v[7]);
	} else {
		STR2IOVEC(from_tag, v[5]);
		STR2IOVEC(to_tag, v[7]);
		if (to_tag.len <= 0)
			nitems = 6;
	}
	send_rtpp_command(node, v, nitems);

	return 1;
}

/*
 * Auxiliary for some functions.
 * Returns pointer to first character of found line, or NULL if no such line.
 */

char*
find_sdp_line(char *p, char *plimit, char linechar)
{
	static char linehead[3] = "x=";
	char *cp, *cp1;
	linehead[0] = linechar;
	/* Iterate thru body */
	cp = p;
	for (;;) {
		if (cp >= plimit)
			return NULL;
		cp1 = ser_memmem(cp, linehead, plimit-cp, 2);
		if (cp1 == NULL)
			return NULL;
		/*
		 * As it is body, we assume it has previous line and we can
		 * lookup previous character.
		 */
		if (cp1[-1] == '\n' || cp1[-1] == '\r')
			return cp1;
		/*
		 * Having such data, but not at line beginning.
		 * Skip them and reiterate. ser_memmem() will find next
		 * occurence.
		 */
		if (plimit - cp1 < 2)
			return NULL;
		cp = cp1 + 2;
	}
	/*UNREACHED*/
	return NULL;
}

/* This function assumes p points to a line of requested type. */

char*
find_next_sdp_line(char *p, char *plimit, char linechar, char *defptr)
{
	char* t;
	if (p >= plimit || plimit - p < 3)
		return defptr;
	t = find_sdp_line(p + 2, plimit, linechar);
	return t ? t : defptr;
}

static int
alter_line(struct sip_msg *msg, str *where, str *what)
{
	struct lump *anchor;
	anchor = del_lump(msg, where->s - msg->buf, where->len, 0);
	if (!anchor) {
		LOG(L_ERR, "del_lump() failed\n");
		return 0;
	}
	if (insert_new_lump_after(anchor, what->s, what->len, 0) == 0) {
		LOG(L_ERR, "insert_new_lump_after() failed\n");
		return 0;
	}
	return 1;
}

/*
 * The following macro is used in force_rtp_proxy2_f() and twice
 * in start_moh() 
 */

#define PARSE_PROXY_REPLY \
		 do { \
			argc = 0;	\
			memset(argv, 0, sizeof(argv));	\
			cpend=cp+strlen(cp);	\
			next=eat_token_end(cp, cpend);	\
			for (ap = argv; cp<cpend; cp=next+1, next=eat_token_end(cp, cpend)){	\
				*next=0;	\
				if (*cp != '\0') {	\
					*ap=cp;	\
					argc++;	\
					if ((char*)++ap >= ((char*)argv+sizeof(argv)))	\
						break;	\
				}	\
			} \
		} while(0)

static int
force_rtp_proxy2_f(struct sip_msg* msg, char* str1, char* str2)
{
	str body, body1, oldport, oldip, newport, newip;
	str callid, from_tag, to_tag;
	int create, port, len, asymmetric, flookup, argc, proxied, real;
	int create1;
	int oidx, pf, pf1, force;
	char opts[16];
	char *cp, *cp1;
	char  *cpend, *next;
	char **ap, *argv[10];
	struct lump* anchor;
	struct rtpp_node *node;
	struct iovec v[14] = {
		{NULL, 0},	/* command */
		{NULL, 0},	/* options */
		{" ", 1},	/* separator */
		{NULL, 0},	/* callid */
		{" ", 1},	/* separator */
		{NULL, 7},	/* newip */
		{" ", 1},	/* separator */
		{NULL, 1},	/* oldport */
		{" ", 1},	/* separator */
		{NULL, 0},	/* from_tag */
		{";", 1},	/* separator */
		{NULL, 0},	/* medianum */
		{" ", 1},	/* separator */
		{NULL, 0}	/* to_tag */
	};
	char *v1p, *v2p, *c1p, *c2p, *m1p, *m2p, *bodylimit;
	char medianum_buf[20];
	int medianum, media_multi;
	str medianum_str, tmpstr1;
	int c1_altered;

	v[1].iov_base=opts;
	asymmetric = flookup = force = real = 0;
	oidx = 1;
	for (cp = str1; *cp != '\0'; cp++) {
		switch (*cp) {
		case 'a':
		case 'A':
			opts[oidx++] = 'A';
			asymmetric = 1;
			real = 1;
			break;

		case 'i':
		case 'I':
			opts[oidx++] = 'I';
			break;

		case 'e':
		case 'E':
			opts[oidx++] = 'E';
			break;

		case 'l':
		case 'L':
			flookup = 1;
			break;

		case 'f':
		case 'F':
			force = 1;
			break;

		case 'r':
		case 'R':
			real = 1;
			break;

		default:
			LOG(L_ERR, "ERROR: force_rtp_proxy2: unknown option `%c'\n", *cp);
			return -1;
		}
	}

	if (msg->first_line.type == SIP_REQUEST &&
	    msg->first_line.u.request.method_value == METHOD_INVITE) {
		create = 1;
	} else if (msg->first_line.type == SIP_REPLY) {
		create = 0;
	} else {
		return -1;
	}
	/* extract_body will also parse all the headers in the message as
	 * a side effect => don't move get_callid/get_to_tag in front of it
	 * -- andrei */
	if (extract_body(msg, &body) == -1) {
		LOG(L_ERR, "ERROR: force_rtp_proxy2: can't extract body "
		    "from the message\n");
		return -1;
	}
	if (get_callid(msg, &callid) == -1 || callid.len == 0) {
		LOG(L_ERR, "ERROR: force_rtp_proxy2: can't get Call-Id field\n");
		return -1;
	}
	if (get_to_tag(msg, &to_tag) == -1) {
		LOG(L_ERR, "ERROR: force_rtp_proxy2: can't get To tag\n");
		return -1;
	}
	if (get_from_tag(msg, &from_tag) == -1 || from_tag.len == 0) {
		LOG(L_ERR, "ERROR: force_rtp_proxy2: can't get From tag\n");
		return -1;
	}

	proxied = 0;
	for (cp = body.s; (len = body.s + body.len - cp) >= ANORTPPROXY_LEN;) {
		cp1 = ser_memmem(cp, ANORTPPROXY, len, ANORTPPROXY_LEN);
		if (cp1 == NULL)
			break;
		if (cp1[-1] == '\n' || cp1[-1] == '\r') {
			proxied = 1;
			break;
		}
		cp = cp1 + ANORTPPROXY_LEN;
	}
	if (proxied != 0 && force == 0)
		return -1;
	/*
	 * Parsing of SDP body.
	 * It can contain a few session descriptions (each start with
	 * "v=" line), and each session may contain a few media descriptions
	 * (each start with "m=" line).
	 * We have to change ports in "m=", and also change IP addresses in
	 * "c=" which can be placed either in session header (fallback for
	 * all medias) or media description.
	 * Ports should be allocated for any media. IPs all should be changed
	 * to the same value (RTP proxy IP), so we can change all "c="
	 * unconditionally.
	 */
	bodylimit = body.s + body.len;
	v1p = find_sdp_line(body.s, bodylimit, 'v');
	if (v1p == NULL) {
		LOG(L_ERR, "ERROR: force_rtp_proxy2: no sessions in SDP\n");
		return -1;
	}
	v2p = find_next_sdp_line(v1p, bodylimit, 'v', bodylimit);
	media_multi = (v2p != bodylimit);
	v2p = v1p;
	medianum = 0;
	for (;;) {
		unsigned nmseen, nmchanged;
		/* Per-session iteration. */
		v1p = v2p;
		if (v1p == NULL || v1p >= bodylimit)
			break; /* No sessions left */
		v2p = find_next_sdp_line(v1p, bodylimit, 'v', bodylimit);
		/* v2p is text limit for session parsing. */
		m1p = find_sdp_line(v1p, v2p, 'm');
		/* Have this session media description? */
		if (m1p == NULL) {
			LOG(L_ERR, "ERROR: force_rtp_proxy2: no m= in session\n");
			return -1;
		}
		/*
		 * Find c1p only between session begin and first media.
		 * c1p will give common c= for all medias.
		 */
		c1p = find_sdp_line(v1p, m1p, 'c');
		c1_altered = 0;
		/* Have session. Iterate media descriptions in session */
		m2p = m1p;
		nmseen = nmchanged = 0;
		for (;;) {
			create1 = create;
			if (flookup != 0) {
				if (!create || to_tag.len <= 0) {
#if 0
					LOG(L_ERR, "force_rtp_proxy(): inappropriate 'l'\n");
#endif
					return -1;
				}
				create1 = 0;
			}
			m1p = m2p;
			if (m1p == NULL || m1p >= v2p)
				break;
			m2p = find_next_sdp_line(m1p, v2p, 'm', v2p);
			/* c2p will point to per-media "c=" */
			c2p = find_sdp_line(m1p, m2p, 'c');
			/* Extract address and port */
			tmpstr1.s = c2p ? c2p : c1p;
			++nmseen;
			if (tmpstr1.s == NULL) {
				/* No "c=" */
				LOG(L_ERR, "ERROR: force_rtp_proxy2: can't find media IP "
				    "in the message\n");
				return -1;
			}
			tmpstr1.len = v2p - tmpstr1.s; /* limit is session limit text */
			if (extract_mediaip(&tmpstr1, &oldip, &pf) == -1) {
				LOG(L_ERR, "ERROR: force_rtp_proxy2: can't extract media IP "
				    "from the message\n");
				return -1;
			}
			tmpstr1.s = m1p;
			tmpstr1.len = m2p - m1p;
			if (extract_mediaport(&tmpstr1, &oldport) == -1) {
				LOG(L_ERR, "ERROR: force_rtp_proxy2: can't extract media port "
				    "from the message\n");
				return -1;
			}
			++medianum;
			if (asymmetric != 0 || real != 0) {
				newip = oldip;
			} else {
				newip.s = ip_addr2a(&msg->rcv.src_ip);
				newip.len = strlen(newip.s);
			}
			/* XXX must compare address families in all addresses */
			if (pf == AF_INET6) {
				opts[oidx] = '6';
				oidx++;
			}
			snprintf(medianum_buf, sizeof medianum_buf, "%d", medianum);
			medianum_str.s = medianum_buf;
			medianum_str.len = strlen(medianum_buf);
			opts[0] = (create1 == 0) ? 'L' : 'U';
			v[1].iov_len = oidx;
			STR2IOVEC(callid, v[3]);
			STR2IOVEC(newip, v[5]);
			STR2IOVEC(oldport, v[7]);
			/*assert(!flookup || to_tag.len > 0);*/
			STR2IOVEC(flookup ? to_tag : from_tag, v[9]);
			if (1 || media_multi) /* XXX */ {
				STR2IOVEC(medianum_str, v[11]);
			} else {
				v[10].iov_len = v[11].iov_len = 0;
			}
			STR2IOVEC(flookup ? from_tag : to_tag, v[13]);
			do {
				node = select_rtpp_node(callid, 1);
				if (!node) {
					LOG(L_ERR, "ERROR: force_rtp_proxy2: no available proxies\n");
					return -1;
				}
				cp = send_rtpp_command(node, v,
				    (to_tag.len > 0) ? 14 : 12);
			} while (cp == NULL);
			LOG(L_DBG, "force_rtp_proxy2: proxy reply: %s\n", cp);
			PARSE_PROXY_REPLY;
			if (argc < 1) {
				LOG(L_ERR, "force_rtp_proxy2: no reply from rtp proxy\n");
				return -1;
			}
			port = atoi(argv[0]);
			if (port <= 0 || port > 65535) {
				/*
				 * In forced lookup mode absence of session indicated
				 * by port number being zero is not an error condition
				 * but merely of indication that there is no such
				 * session in the proxy exists.
				 */
				if (port != 0 || flookup == 0)
					LOG(L_ERR, "force_rtp_proxy2: incorrect port in reply from rtp proxy\n");
				return -1;
			}

			pf1 = (argc >= 3 && argv[2][0] == '6') ? AF_INET6 : AF_INET;

			if (isnulladdr(&oldip, pf)) {
				if (pf1 == AF_INET6) {
					newip.s = "::";
					newip.len = 2;
				} else {
					newip.s = "0.0.0.0";
					newip.len = 7;
				}
			} else {
				newip.s = (argc < 2) ? str2 : argv[1];
				newip.len = strlen(newip.s);
			}
			newport.s = int2str(port, &newport.len); /* beware static buffer */
			/* Alter port. */
			body1.s = m1p;
			body1.len = bodylimit - body1.s;
			if (alter_mediaport(msg, &body1, &oldport, &newport, 0) == -1)
				return -1;
			/*
			 * Alter IP. Don't alter IP common for the session
			 * more than once.
			 */
			if (c2p != NULL || !c1_altered) {
				body1.s = c2p ? c2p : c1p;
				body1.len = bodylimit - body1.s;
				if (alter_mediaip(msg, &body1, &oldip, pf, &newip, pf1, 0) == -1)
					return -1;
				if (!c2p)
					c1_altered = 1;
			}
			++nmchanged;
			/*
			 * Gross hack: turn off flookup here, after first
			 * media stream. This is used to allow changing number
			 * of media streams during re-INVITE.
			 */
			flookup = 0;
		} /* Iterate medias in session */
		if (c1p && nmseen == nmchanged && !c1_altered) {
			/* Alter default c-line of this session. */
			str c1s, janus;
			size_t newlen;
			c1s.s = c1p;
			c1s.len = eat_line(c1p, bodylimit - c1p) - c1p;
			newlen = c1s.len + 6 + 1;
			janus.s = pkg_malloc(c1s.len);
			if (janus.s == NULL) {
				LOG(L_ERR, "pkg_malloc failed\n");
				return 0;
			}
			sprintf(janus.s, "a=oldc:%*.*s",
			    (int) c1s.len, (int) c1s.len, c1s.s);
			janus.len = strlen(janus.s);
			if (alter_line(msg, &c1s, &janus) <= 0) {
				LOG(L_ERR, "alter_line() failed\n");
				return 0;
			}
		}
	} /* Iterate sessions */

	if (proxied == 0) {
		cp = pkg_malloc(ANORTPPROXY_LEN * sizeof(char));
		if (cp == NULL) {
			LOG(L_ERR, "ERROR: force_rtp_proxy2: out of memory\n");
			return -1;
		}
		anchor = anchor_lump(msg, body.s + body.len - msg->buf, 0, 0);
		if (anchor == NULL) {
			LOG(L_ERR, "ERROR: force_rtp_proxy2: anchor_lump failed\n");
			pkg_free(cp);
			return -1;
		}
		memcpy(cp, ANORTPPROXY, ANORTPPROXY_LEN);
		if (insert_new_lump_after(anchor, cp, ANORTPPROXY_LEN, 0) == NULL) {
			LOG(L_ERR, "ERROR: force_rtp_proxy2: insert_new_lump_after failed\n");
			pkg_free(cp);
			return -1;
		}
	}

	return 1;
}

static int
force_rtp_proxy1_f(struct sip_msg* msg, char* str1, char* str2)
{
	char *cp;
	char newip[IP_ADDR_MAX_STR_SIZE];

	cp = ip_addr2a(&msg->rcv.dst_ip);
	strcpy(newip, cp);
	return force_rtp_proxy2_f(msg, str1, newip);
}

static int
force_rtp_proxy0_f(struct sip_msg* msg, char* str1, char* str2)
{
	char arg[1] = {'\0'};

	return force_rtp_proxy1_f(msg, arg, NULL);
}

static int
rewrite_from_from_f(struct sip_msg* msg, char* str1, char* str2)
{
	struct action act;
	struct sip_uri uri;
	str ruri;

	/* parsing from header */
	if (parse_from_header(msg) == -1) {
		LOG(L_ERR, "rewrite_from_from(): cannot get FROM header\n");
		return -1;
	}
	ruri.len = get_from(msg)->uri.len;
	ruri.s = pkg_malloc(ruri.len + 1);
	memcpy(ruri.s, get_from(msg)->uri.s, ruri.len + 1);
	if (parse_uri(ruri.s, ruri.len, &uri) < 0) {
		LOG(L_ERR, "rewrite_from_from(): can't parse FROM URI\n");
		pkg_free(ruri.s);
		return -1;
	}
	if (uri.user.len <= 0) {
		uri.user.s = "Unknown";
		uri.user.len = 7;
	} else {
		uri.user.s[uri.user.len] = '\0';
	}

	bzero(&act, sizeof(act));
	act.type = SET_USER_T;
	act.p1_type = STRING_ST;
	act.p1.string = uri.user.s;
	do_action(&act, msg);

	pkg_free(ruri.s);
	return 1;
}


/*
 * Create received SIP uri that will be either
 * passed to registrar in an AVP or apended
 * to Contact header field as a parameter
 */
static int
create_rcv_uri(str* uri, struct sip_msg* m)
{
	static char buf[MAX_URI_SIZE];
	char* p;
	str ip, port;
	int len;
	str proto;

	if (!uri || !m) {
		LOG(L_ERR, "create_rcv_uri: Invalid parameter value\n");
		return -1;
	}

	ip.s = ip_addr2a(&m->rcv.src_ip);
	ip.len = strlen(ip.s);

	port.s = int2str(m->rcv.src_port, &port.len);

	switch(m->rcv.proto) {
	case PROTO_NONE:
	case PROTO_UDP:
		proto.s = 0; /* Do not add transport parameter, UDP is default */
		proto.len = 0;
		break;

	case PROTO_TCP:
		proto.s = "TCP";
		proto.len = 3;
		break;

	case PROTO_TLS:
		proto.s = "TLS";
		proto.len = 3;
		break;

	case PROTO_SCTP:
		proto.s = "SCTP";
		proto.len = 4;
		break;

	default:
		LOG(L_ERR, "BUG: create_rcv_uri: Unknown transport protocol\n");
		return -1;
	}

	len = 4 + ip.len + 1 + port.len;
	if (proto.s) {
		len += TRANSPORT_PARAM_LEN;
		len += proto.len;
	}

	if (len > MAX_URI_SIZE) {
		LOG(L_ERR, "create_rcv_uri: Buffer too small\n");
		return -1;
	}

	p = buf;
	memcpy(p, "sip:", 4);
	p += 4;
	
	memcpy(p, ip.s, ip.len);
	p += ip.len;

	*p++ = ':';
	
	memcpy(p, port.s, port.len);
	p += port.len;

	if (proto.s) {
		memcpy(p, TRANSPORT_PARAM, TRANSPORT_PARAM_LEN);
		p += TRANSPORT_PARAM_LEN;

		memcpy(p, proto.s, proto.len);
		p += proto.len;
	}

	uri->s = buf;
	uri->len = len;

	return 0;
}


/*
 * Add received parameter to Contacts for further
 * forwarding of the REGISTER requuest
 */
static int
add_rcv_param_f(struct sip_msg* msg, char* str1, char* str2)
{
	contact_t* c;
	struct lump* anchor;
	char* param;
	str uri;

	if (create_rcv_uri(&uri, msg) < 0) {
		return -1;
	}

	if (contact_iterator(&c, msg, 0) < 0) {
		return -1;
	}

	while(c) {
		param = (char*)pkg_malloc(RECEIVED_LEN + 2 + uri.len);
		if (!param) {
			LOG(L_ERR, "add_rcv_param: No memory left\n");
			return -1;
		}
		memcpy(param, RECEIVED, RECEIVED_LEN);
		param[RECEIVED_LEN] = '\"';
		memcpy(param + RECEIVED_LEN + 1, uri.s, uri.len);
		param[RECEIVED_LEN + 1 + uri.len] = '\"';

		anchor = anchor_lump(msg, c->name.s + c->len - msg->buf, 0, 0);
		if (anchor == NULL) {
			LOG(L_ERR, "add_rcv_param: anchor_lump failed\n");
			return -1;
		}		

		if (insert_new_lump_after(anchor, param, RECEIVED_LEN + 1 + uri.len + 1, 0) == 0) {
			LOG(L_ERR, "add_rcv_param: insert_new_lump_after failed\n");
			pkg_free(param);
			return -1;
		}

		if (contact_iterator(&c, msg, c) < 0) {
			return -1;
		}
	}

	return 1;
}


/*
 * Create an AVP to be used by registrar with the source IP and port
 * of the REGISTER
 */
static int
fix_nated_register_f(struct sip_msg* msg, char* str1, char* str2)
{
	str uri;
	int_str val;
	int_str rcv_avp;

	if (create_rcv_uri(&uri, msg) < 0) {
		return -1;
	}

	val.s = &uri;

	rcv_avp.n=rcv_avp_no;
	if (add_avp(AVP_VAL_STR, rcv_avp, val) < 0) {
		LOG(L_ERR, "fix_nated_register: Error while creating AVP\n");
		return -1;
	}

	return 1;
}


syntax highlighted by Code2HTML, v. 0.9.1