/*
 * $Id: lcr_mod.c 2181 2007-05-09 17:33:18Z juhe $
 *
 * Least Cost Routing module (also implements sequential forking)
 *
 * Copyright (C) 2005 Juha Heinanen
 * Copyright (C) 2006 Voice Sistem SRL
 *
 * This file is part of openser, a free SIP server.
 *
 * openser 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
 *
 * openser 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:
 * -------
 *  2005-02-14: Introduced lcr module (jh)
 *  2005-02-20: Added sequential forking functions (jh)
 *  2005-02-25: Added support for int AVP names, combined addr and port
 *              AVPs (jh)
 *  2005-07-28: Added support for gw URI scheme and transport, 
 *              backport from ser (kd)
 *  2005-08-20: Added support for gw prefixes (jh)
 *  2005-09-03: Request-URI user part can be modified between load_gws()
 *              and first next_gw() calls.
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <arpa/inet.h>
#include <regex.h>
#include "../../sr_module.h"
#include "../../dprint.h"
#include "../../ut.h"
#include "../../error.h"
#include "../../mem/mem.h"
#include "../../mem/shm_mem.h"
#include "../../db/db.h"
#include "../../usr_avp.h"
#include "../../parser/parse_uri.h"
#include "../../parser/parse_from.h"
#include "../../parser/msg_parser.h"
#include "../../action.h"
#include "../../qvalue.h"
#include "../../dset.h"
#include "../../ip_addr.h"
#include "../../mi/mi.h"
#include "../mysql/dbase.h"
#include "mi.h"

MODULE_VERSION

/*
 * Version of gw and lcr tables required by the module,
 * increment this value if you change the table in
 * an backwards incompatible way
 */
#define GW_TABLE_VERSION 4
#define LCR_TABLE_VERSION 2

/* usr_avp flag for sequential forking */
#define Q_FLAG      (1<<2)

static void destroy(void);       /* Module destroy function */
static int child_init(int rank); /* Per-child initialization function */
static int mi_child_init(void);
static int mod_init(void);       /* Module initialization function */
static int fixstring2int(void **param, int param_count);

int reload_gws ( void );

#define GW_TABLE "gw"

#define GW_NAME_COL "gw_name"

#define IP_ADDR_COL "ip_addr"

#define PORT_COL "port"

#define URI_SCHEME_COL "uri_scheme"

#define TRANSPORT_COL "transport"

#define GRP_ID_COL "grp_id"

#define LCR_TABLE "lcr"

#define STRIP_COL "strip"

#define PREFIX_COL "prefix"

#define FROM_URI_COL "from_uri"

#define PRIORITY_COL "priority"

#define MAX_QUERY_SIZE 512
#define MAX_NO_OF_GWS 32
#define MAX_NO_OF_LCRS 256
#define MAX_PREFIX_LEN 16
#define MAX_FROM_URI_LEN 256

/* Default avp names */
#define DEF_GW_URI_AVP "1400"
#define DEF_CONTACT_AVP "1401"
#define DEF_RURI_USER_AVP "1402"
#define DEF_FR_INV_TIMER_AVP "fr_inv_timer_avp"
#define DEF_FR_INV_TIMER 90
#define DEF_FR_INV_TIMER_NEXT 30
#define DEF_RPID_AVP "rpid"

/*
 * Type definitions
 */

typedef enum sip_protos uri_transport;

struct gw_info {
    unsigned int ip_addr;
    unsigned int port;
    unsigned int grp_id;
    uri_type scheme;
    uri_transport transport;
    unsigned int strip;
    char prefix[MAX_PREFIX_LEN];
    unsigned short prefix_len;
};

struct lcr_info {
    char prefix[MAX_PREFIX_LEN];
    unsigned short prefix_len;
    char from_uri[MAX_FROM_URI_LEN + 1];
    unsigned short from_uri_len;
    unsigned int grp_id;
    unsigned short priority;
    unsigned short end_record;
};

struct from_uri_regex {
    regex_t re;
    short int valid;
};

struct mi {
    int gw_index;
    int route_index;
    int randomizer;
};


/*
 * Database variables
 */
static db_con_t* db_handle = 0;   /* Database connection handle */
static db_func_t lcr_dbf;

/*
 * Module parameter variables
 */
static str db_url    = str_init(DEFAULT_RODB_URL);
str gw_table         = str_init(GW_TABLE);
str gw_name_col      = str_init(GW_NAME_COL);
str ip_addr_col      = str_init(IP_ADDR_COL);
str port_col         = str_init(PORT_COL);
str uri_scheme_col   = str_init(URI_SCHEME_COL);
str transport_col    = str_init(TRANSPORT_COL);
str grp_id_col       = str_init(GRP_ID_COL);
str lcr_table        = str_init(LCR_TABLE);
str strip_col        = str_init(STRIP_COL);
str prefix_col       = str_init(PREFIX_COL);
str from_uri_col     = str_init(FROM_URI_COL);
str priority_col     = str_init(PRIORITY_COL);

str gw_uri_avp       = str_init(DEF_GW_URI_AVP);
str ruri_user_avp    = str_init(DEF_RURI_USER_AVP);
str contact_avp      = str_init(DEF_CONTACT_AVP);
str inv_timer_avp    = str_init(DEF_FR_INV_TIMER_AVP);
int inv_timer        = DEF_FR_INV_TIMER;
int inv_timer_next   = DEF_FR_INV_TIMER_NEXT;
str rpid_avp         = str_init(DEF_RPID_AVP);

/*
 * Other module types and variables
 */

struct contact {
    str uri;
    qvalue_t q;
    unsigned short q_flag;
    struct contact *next;
};

int_str gw_uri_name, ruri_user_name, contact_name, rpid_name, inv_timer_name;
unsigned short gw_uri_avp_name_str;
unsigned short ruri_user_avp_name_str;
unsigned short contact_avp_name_str;
unsigned short rpid_avp_name_str;

struct gw_info **gws;	/* Pointer to current gw table pointer */
struct gw_info *gws_1;	/* Pointer to gw table 1 */
struct gw_info *gws_2;	/* Pointer to gw table 2 */

struct lcr_info **lcrs;  /* Pointer to current lcr table pointer */
struct lcr_info *lcrs_1; /* Pointer to lcr table 1 */
struct lcr_info *lcrs_2; /* Pointer to lcr table 2 */

unsigned int *lcrs_ws_reload_counter;
unsigned int reload_counter;

struct from_uri_regex from_uri_reg[MAX_NO_OF_LCRS];

/*
 * Module functions that are defined later
 */
int load_gws(struct sip_msg* _m, char* _s1, char* _s2);
int load_gws_grp(struct sip_msg* _m, char* _s1, char* _s2);
int next_gw(struct sip_msg* _m, char* _s1, char* _s2);
int from_gw(struct sip_msg* _m, char* _s1, char* _s2);
int from_gw_grp(struct sip_msg* _m, char* _s1, char* _s2);
int to_gw(struct sip_msg* _m, char* _s1, char* _s2);
int to_gw_grp(struct sip_msg* _m, char* _s1, char* _s2);
int load_contacts (struct sip_msg*, char*, char*);
int next_contacts (struct sip_msg*, char*, char*);

/*
 * Exported functions
 */
static cmd_export_t cmds[] = {
	{"load_gws",      load_gws,      0, 0,
		REQUEST_ROUTE | FAILURE_ROUTE},
	{"load_gws",      load_gws_grp,  1, fixstring2int,
		REQUEST_ROUTE | FAILURE_ROUTE},
	{"next_gw",       next_gw,       0, 0,
		REQUEST_ROUTE | FAILURE_ROUTE},
	{"from_gw",       from_gw,       0, 0,
		REQUEST_ROUTE | FAILURE_ROUTE | ONREPLY_ROUTE},
	{"from_gw",       from_gw_grp,   1, fixstring2int,
		REQUEST_ROUTE | FAILURE_ROUTE | ONREPLY_ROUTE},
	{"to_gw",         to_gw,         0, 0,
		REQUEST_ROUTE | FAILURE_ROUTE},
	{"to_gw",         to_gw_grp,     1, fixstring2int,
		REQUEST_ROUTE | FAILURE_ROUTE},
	{"load_contacts", load_contacts, 0, 0,
		REQUEST_ROUTE},
	{"next_contacts", next_contacts, 0, 0,
		REQUEST_ROUTE | FAILURE_ROUTE},
	{0, 0, 0, 0, 0}
};


/*
 * Exported parameters
 */
static param_export_t params[] = {
	{"db_url",                   STR_PARAM, &db_url.s       },
	{"gw_table",                 STR_PARAM, &gw_table.s     },
	{"gw_name_column",           STR_PARAM, &gw_name_col.s  },
	{"ip_addr_column",           STR_PARAM, &ip_addr_col.s  },
	{"port_column",              STR_PARAM, &port_col.s     },
	{"uri_scheme_column",        STR_PARAM, &uri_scheme_col.s },
	{"transport_column",         STR_PARAM, &transport_col.s },
	{"grp_id_column",            STR_PARAM, &grp_id_col.s   },
	{"lcr_table",                STR_PARAM, &lcr_table.s    },
	{"strip_column",             STR_PARAM, &strip_col.s    },
	{"prefix_column",            STR_PARAM, &prefix_col.s   },
	{"from_uri_column",          STR_PARAM, &from_uri_col.s },
	{"priority_column",          STR_PARAM, &priority_col.s },
	{"gw_uri_avp",               STR_PARAM, &gw_uri_avp.s   },
	{"ruri_user_avp",            STR_PARAM, &ruri_user_avp.s },
	{"contact_avp",              STR_PARAM, &contact_avp.s  },
	{"fr_inv_timer_avp",         STR_PARAM, &inv_timer_avp.s },
	{"fr_inv_timer",             INT_PARAM, &inv_timer      },
	{"fr_inv_timer_next",        INT_PARAM, &inv_timer_next },
	{"rpid_avp",                 STR_PARAM, &rpid_avp.s     },
	{0, 0, 0}
};


/*
 * Exported MI functions
 */
static mi_export_t mi_cmds[] = {
	{ MI_LCR_RELOAD,  mi_lcr_reload,   MI_NO_INPUT_FLAG,  0,  mi_child_init },
	{ MI_LCR_DUMP,    mi_lcr_dump,     MI_NO_INPUT_FLAG,  0,  0 },
	{ 0, 0, 0, 0, 0}
};


/*
 * Module interface
 */
struct module_exports exports = {
	"lcr", 
	DEFAULT_DLFLAGS, /* dlopen flags */
	cmds,      /* Exported functions */
	params,    /* Exported parameters */
	0,         /* exported statistics */
	mi_cmds,   /* exported MI functions */
	0,         /* exported pseudo-variables */
	mod_init,  /* module initialization function */
	0,         /* response function */
	destroy,   /* destroy function */
	child_init /* child initialization function */
};


int lcr_db_init(char* db_url)
{
	if (lcr_dbf.init==0){
		LOG(L_CRIT, "BUG: lcr_db_bind: null dbf\n");
		goto error;
	}
	db_handle=lcr_dbf.init(db_url);
	if (db_handle==0){
		LOG(L_ERR, "ERROR: lcr_db_bind: unable to connect to the database\n");
		goto error;
	}
	return 0;
error:
	return -1;
}



int lcr_db_bind(char* db_url)
{
	if (bind_dbmod(db_url, &lcr_dbf)<0){
		LOG(L_ERR, "ERROR: lcr_db_bind: unable to bind to the database"
				" module\n");
		return -1;
	}

	if (!DB_CAPABILITY(lcr_dbf, DB_CAP_QUERY)) {
		LOG(L_ERR, "ERROR: lcr_db_bind: Database module does not "
		    "implement 'query' function\n");
		return -1;
	}

	return 0;
}


void lcr_db_close()
{
	if (db_handle && lcr_dbf.close){
		lcr_dbf.close(db_handle);
		db_handle=0;
	}
}


int lcr_db_ver(char* db_url, str* name)
{
	db_con_t* dbh;
	int ver;

	if (lcr_dbf.init==0){
		LOG(L_CRIT, "BUG: lcr_db_ver: unbound database\n");
		return -1;
	}
	dbh=lcr_dbf.init(db_url);
	if (dbh==0){
		LOG(L_ERR, "ERROR: lcr_db_ver: unable to open database connection\n");
		return -1;
	}
	ver=table_version(&lcr_dbf, dbh, name);
	lcr_dbf.close(dbh);
	return ver;
}


/*
 * Module initialization function callee in each child separately
 */
static int child_init(int rank)
{
	/* don't do anything for non-worker process */
	if (rank<1)
		return 0;

	if (lcr_db_init(db_url.s) < 0) {
		LOG(L_ERR, "ERROR: lcr:child_init():"
		    " Unable to connect to the database\n");
		return -1;
	}

	return 0;
}


static int mi_child_init()
{
	return lcr_db_init(db_url.s);
}


/*
 * Module initialization function that is called before the main process forks
 */
static int mod_init(void)
{
	int ver, i;
	unsigned int par;

	DBG("lcr - initializing\n");

	/* Bind database */
	if (lcr_db_bind(db_url.s)) {
		LOG(L_ERR, "ERROR: lcr:mod_init(): No database module found\n");
		return -1;
	}

	/* Update length of module variables */
	db_url.len = strlen(db_url.s);
	gw_table.len = strlen(gw_table.s);
	gw_name_col.len = strlen(gw_name_col.s);
	ip_addr_col.len = strlen(ip_addr_col.s);
	port_col.len = strlen(port_col.s);
	uri_scheme_col.len = strlen(uri_scheme_col.s);
	transport_col.len = strlen(transport_col.s);
	grp_id_col.len = strlen(grp_id_col.s);
	lcr_table.len = strlen(lcr_table.s);
	strip_col.len = strlen(strip_col.s);
	prefix_col.len = strlen(prefix_col.s);
	from_uri_col.len = strlen(from_uri_col.s);
	priority_col.len = strlen(priority_col.s);
	gw_uri_avp.len = strlen(gw_uri_avp.s);
	ruri_user_avp.len = strlen(ruri_user_avp.s);
	contact_avp.len = strlen(contact_avp.s);
	inv_timer_avp.len = strlen(inv_timer_avp.s);
	rpid_avp.len = strlen(rpid_avp.s);

	/* Check table version */
	ver = lcr_db_ver(db_url.s, &gw_table);
	if (ver < 0) {
		LOG(L_ERR, "ERROR: lcr:mod_init():"
				" Error while querying table version\n");
		goto err;
	} else if (ver < GW_TABLE_VERSION) {
		LOG(L_ERR, "ERROR: lcr:mod_init(): Invalid table version"
				" of gw table\n");
		goto err;
	}		

	/* Check table version */
	ver = lcr_db_ver(db_url.s, &lcr_table);
	if (ver < 0) {
		LOG(L_ERR, "ERROR: lcr:mod_init():"
				" Error while querying table version\n");
		goto err;
	} else if (ver < LCR_TABLE_VERSION) {
		LOG(L_ERR, "ERROR: lcr:mod_init(): Invalid table version of"
				" lcr table (use openser_mysql.sh reinstall)\n");
		goto err;
	}		

	/* Initializing gw tables and gw table pointer variable */
	gws_1 = (struct gw_info *)shm_malloc(sizeof(struct gw_info) * (MAX_NO_OF_GWS + 1));
	if (gws_1 == 0) {
	    LOG(L_ERR, "ERROR: lcr: mod_init(): "
		"No memory for gw table\n");
	    goto err;
	}
	gws_2 = (struct gw_info *)shm_malloc(sizeof(struct gw_info) * (MAX_NO_OF_GWS + 1));
	if (gws_2 == 0) {
	    LOG(L_ERR, "ERROR: lcr: mod_init(): "
		"No memory for gw table\n");
	    goto err;
	}
	for (i = 0; i < MAX_NO_OF_GWS + 1; i++) {
		gws_1[i].ip_addr = gws_2[i].ip_addr = 0;
	}
	gws = (struct gw_info **)shm_malloc(sizeof(struct gw_info *));
	if (gws == 0) {
	    LOG(L_ERR, "ERROR: lcr: mod_init(): "
		"No memory for gw table pointer\n");
	}
	*gws = gws_1;

	/* Initializing lcr tables and lcr table pointer variable */
	lcrs_1 = (struct lcr_info *)shm_malloc(sizeof(struct lcr_info) *
			(MAX_NO_OF_LCRS + 1));
	if (lcrs_1 == 0) {
		LOG(L_ERR, "ERROR: lcr: mod_init(): "
			"No memory for lcr table\n");
		goto err;
	}
	lcrs_2 = (struct lcr_info *)shm_malloc(sizeof(struct lcr_info) *
			(MAX_NO_OF_LCRS + 1));
	if (lcrs_2 == 0) {
		LOG(L_ERR, "ERROR: lcr: mod_init(): "
			"No memory for lcr table\n");
		goto err;
	}
	for (i = 0; i < MAX_NO_OF_LCRS + 1; i++) {
		lcrs_1[i].end_record = lcrs_2[i].end_record = 0;
	}
	lcrs = (struct lcr_info **)shm_malloc(sizeof(struct lcr_info *));
	if (lcrs == 0) {
		LOG(L_ERR, "ERROR: lcr: mod_init(): "
			"No memory for lcr table pointer\n");
		goto err;
	}
	*lcrs = lcrs_1;

	lcrs_ws_reload_counter = (unsigned int *)shm_malloc(sizeof(unsigned int));
	if (lcrs_ws_reload_counter == 0) {
		LOG(L_ERR, "ERROR: lcr: mod_init(): "
			"No memory for counter\n");
		goto err;
	}
	*lcrs_ws_reload_counter = reload_counter = 0;

	memset(from_uri_reg, 0, sizeof(struct from_uri_regex) * MAX_NO_OF_LCRS);

	/* First reload */
	if (reload_gws() == -1) {
		LOG(L_CRIT, "ERROR: lcr:mod_init():"
		    " failed to reload gateways and routes\n");
		goto err;
	}

	/* Assign parameter names */
	if (str2int(&gw_uri_avp, &par) == 0) {
	    gw_uri_name.n = par;
	    gw_uri_avp_name_str = 0;
	} else {
	    gw_uri_name.s = gw_uri_avp;
	    gw_uri_avp_name_str = AVP_NAME_STR;
	}
	if (str2int(&ruri_user_avp, &par) == 0) {
	    ruri_user_name.n = par;
	    ruri_user_avp_name_str = 0;
	} else {
	    ruri_user_name.s = ruri_user_avp;
	    ruri_user_avp_name_str = AVP_NAME_STR;
	}
	if (str2int(&contact_avp, &par) == 0) {
	    contact_name.n = par;
	    contact_avp_name_str = 0;
	} else {
	    contact_name.s = contact_avp;
	    contact_avp_name_str = AVP_NAME_STR;
	}
	if (str2int(&rpid_avp, &par) == 0) {
	    rpid_name.n = par;
	    rpid_avp_name_str = 0;
	} else {
	    rpid_name.s = rpid_avp;
	    rpid_avp_name_str = AVP_NAME_STR;
	}
	inv_timer_name.s = inv_timer_avp;

	return 0;

err:
	return -1;
}


static void destroy(void)
{
	lcr_db_close();
}

/*
 * Sort lcr records by prefix_len and priority.
 */
static int comp_lcrs(const void *m1, const void *m2)
{
	int result = -1;

	struct mi *mi1 = (struct mi *) m1;
	struct mi *mi2 = (struct mi *) m2;

	struct lcr_info lcr_record1 = (*lcrs)[mi1->route_index];
	struct lcr_info lcr_record2 = (*lcrs)[mi2->route_index];

	/* Sort by prefix. */
	if (lcr_record1.prefix_len > lcr_record2.prefix_len) {
		result = 1;
	}
	else if (lcr_record1.prefix_len == lcr_record2.prefix_len) {
		/* Sort by priority. */
		if (lcr_record1.priority < lcr_record2.priority) {
			result = 1;
		}
		else if (lcr_record1.priority == lcr_record2.priority) {
			/* Nothing to do. */
			result = 0;
		}
	}

	return result;
}

/*
 * Sort lcr records by rand table.
 */
static int rand_lcrs(const void *m1, const void *m2)
{
	int result = -1;

	struct mi mi1 = *((struct mi *) m1);
	struct mi mi2 = *((struct mi *) m2);


	if (mi1.randomizer > mi2.randomizer) {
		result = 1;
	} else if (mi1.randomizer == mi2.randomizer) {
		result = 0;
	}

	return result;
}

/*
 * regcomp each from_uri.
 */
int load_from_uri_regex()
{
	int i, status, result = 0;

	for (i = 0; i < MAX_NO_OF_LCRS; i++) {
		if ((*lcrs)[i].end_record != 0) {
			break;
		}
		if (from_uri_reg[i].valid) {
			regfree(&(from_uri_reg[i].re));
			from_uri_reg[i].valid = 0;
		}
		memset(&(from_uri_reg[i].re), 0, sizeof(regex_t));
		if ((status=regcomp(&(from_uri_reg[i].re),(*lcrs)[i].from_uri,0))!=0){
			LOG(L_ERR, "ERROR:lcr:load_regex: bad from_uri re %s\n", 
					(*lcrs)[i].from_uri);
			result = -1;
			break;
		}
		from_uri_reg[i].valid = 1;
	}

	if (result != -1) {
		reload_counter = *lcrs_ws_reload_counter;
	}
	return result;
}


/*
 * Reload gws to unused gw table and lcrs to unused lcr table, and, when done
 * make unused gw and lcr table the one in use.
 */
int reload_gws ( void )
{
    unsigned int i, port, strip, prefix_len, from_uri_len, grp_id, priority;
    struct in_addr ip_addr;
    uri_type scheme;
    uri_transport transport;
    db_con_t* dbh;
    char *prefix, *from_uri;
    db_res_t* res = NULL;
    db_row_t* row;
    db_key_t gw_cols[7];
    db_key_t lcr_cols[4];

    gw_cols[0] = ip_addr_col.s;
    gw_cols[1] = port_col.s;
    gw_cols[2] = uri_scheme_col.s;
    gw_cols[3] = transport_col.s;
    gw_cols[4] = strip_col.s;
    gw_cols[5] = prefix_col.s;
    /* FIXME: is this ok if we have different names for grp_id
       in the two tables? (ge vw lcr) */
    gw_cols[6] = grp_id_col.s;

    lcr_cols[0] = prefix_col.s;
    lcr_cols[1] = from_uri_col.s;
    /* FIXME: is this ok if we have different names for grp_id
       in the two tables? (ge vw lcr) */
    lcr_cols[2] = grp_id_col.s;
    lcr_cols[3] = priority_col.s;

    if (lcr_dbf.init==0){
	    LOG(L_CRIT, "ERROR: lcr_db_ver: unbound database\n");
	    return -1;
    }
    dbh=lcr_dbf.init(db_url.s);
    if (dbh==0){
	    LOG(L_ERR, "ERROR: reload_gws: unable to open database connection\n");
	    return -1;
    }

    if (lcr_dbf.use_table(dbh, gw_table.s) < 0) {
	    LOG(L_ERR, "lcr_reload_gws(): Error while trying to use gw table\n");
	    return -1;
    }

    if (lcr_dbf.query(dbh, NULL, 0, NULL, gw_cols, 0, 7, 0, &res) < 0) {
	    LOG(L_ERR, "lcr_reload_gws(): Failed to query gw data\n");
	    lcr_dbf.close(dbh);
	    return -1;
    }

    if (RES_ROW_N(res) + 1 > MAX_NO_OF_GWS) {
	    LOG(L_ERR, "reload_gws(): Too many gateways\n");
	    lcr_dbf.free_result(dbh, res);
	    lcr_dbf.close(dbh);
	    return -1;
    }
    
    for (i = 0; i < RES_ROW_N(res); i++) {
	row = RES_ROWS(res) + i;
	if (!((VAL_TYPE(ROW_VALUES(row)) == DB_STRING) &&
	      !VAL_NULL(ROW_VALUES(row)) &&
	      inet_aton((char *)VAL_STRING(ROW_VALUES(row)), &ip_addr) != 0)) {
	    LOG(L_ERR, "reload_gws(): Invalid IP address of GW\n");
	    lcr_dbf.free_result(dbh, res);
	    lcr_dbf.close(dbh);
	    return -1;
	}
	if (VAL_NULL(ROW_VALUES(row) + 1) == 1) {
		port = 0;
	} else {
		port = (unsigned int)VAL_INT(ROW_VALUES(row) + 1);
	}
	if (port > 65536) {
	    LOG(L_ERR, "reload_gws(): Port of GW is too large: %u\n", port);
	    lcr_dbf.free_result(dbh, res);
	    lcr_dbf.close(dbh);
	    return -1;
	}
	if (VAL_NULL(ROW_VALUES(row) + 2) == 1) {
	    scheme = SIP_URI_T;
	} else {
	    scheme = (uri_type)VAL_INT(ROW_VALUES(row) + 2);
	    if ((scheme != SIP_URI_T) && (scheme != SIPS_URI_T)) {
		LOG(L_ERR, "reload_gws(): Unknown or unsupported URI scheme: %u\n", (unsigned int)scheme);
		lcr_dbf.free_result(dbh, res);
		lcr_dbf.close(dbh);
		return -1;
	    }
	}
	if (VAL_NULL(ROW_VALUES(row) + 3) == 1) {
	    transport = PROTO_NONE;
	} else {
	    transport = (uri_transport)VAL_INT(ROW_VALUES(row) + 3);
	    if ((transport != PROTO_UDP) && (transport != PROTO_TCP) &&
		(transport != PROTO_TLS)) {
		LOG(L_ERR, "reload_gws(): Unknown or unsupported transport: %u\n", (unsigned int)transport);
		lcr_dbf.free_result(dbh, res);
		lcr_dbf.close(dbh);
		return -1;
	    }
	}
	if (VAL_NULL(ROW_VALUES(row) + 4) == 1) {
	    strip = 0;
	} else {
	    strip = (unsigned int)VAL_INT(ROW_VALUES(row) + 4);
	}
	if (VAL_NULL(ROW_VALUES(row) + 5) == 1) {
	    prefix_len = 0;
	    prefix = (char *)0;
	} else {
	    prefix = (char *)VAL_STRING(ROW_VALUES(row) + 5);
	    prefix_len = strlen(prefix);
	    if (prefix_len > MAX_PREFIX_LEN) {
		LOG(L_ERR, "reload_gws(): too long gw prefix\n");
		lcr_dbf.free_result(dbh, res);
		lcr_dbf.close(dbh);
		return -1;
	    }
	}
	if (VAL_NULL(ROW_VALUES(row) + 6) == 1) {
	    grp_id = 0;
	} else {
	    grp_id = VAL_INT(ROW_VALUES(row) + 6);
	}
	if (*gws == gws_1) {
		gws_2[i].ip_addr = (unsigned int)ip_addr.s_addr;
		gws_2[i].port = port;
		gws_2[i].grp_id = grp_id;
		gws_2[i].scheme = scheme;
		gws_2[i].transport = transport;
		gws_2[i].strip = strip;
		gws_2[i].prefix_len = prefix_len;
		if (prefix_len)
		    memcpy(&(gws_2[i].prefix[0]), prefix, prefix_len);
	} else {
		gws_1[i].ip_addr = (unsigned int)ip_addr.s_addr;
		gws_1[i].port = port;
		gws_1[i].grp_id = grp_id;
		gws_1[i].scheme = scheme;
		gws_1[i].transport = transport;
		gws_1[i].strip = strip;
		gws_1[i].prefix_len = prefix_len;
		if (prefix_len)
		    memcpy(&(gws_1[i].prefix[0]), prefix, prefix_len);
	}
    }

    lcr_dbf.free_result(dbh, res);

    if (*gws == gws_1) {
	    gws_2[i].ip_addr = 0;
	    *gws = gws_2;
    } else {
	    gws_1[i].ip_addr = 0;
	    *gws = gws_1;
    }


    if (lcr_dbf.use_table(dbh, lcr_table.s) < 0) {
	    LOG(L_ERR, "lcr_reload_gws(): Error while trying to use lcr table\n");
	    return -1;
    }

    if (lcr_dbf.query(dbh, NULL, 0, NULL, lcr_cols, 0, 4, 0, &res) < 0) {
	    LOG(L_ERR, "lcr_reload_gws(): Failed to query lcr data\n");
	    lcr_dbf.close(dbh);
	    return -1;
    }

    if (RES_ROW_N(res) + 1 > MAX_NO_OF_LCRS) {
	    LOG(L_ERR, "reload_gws(): Too many lcr entries\n");
	    lcr_dbf.free_result(dbh, res);
	    lcr_dbf.close(dbh);
	    return -1;
    }
    for (i = 0; i < RES_ROW_N(res); i++) {
	row = RES_ROWS(res) + i;
	if (VAL_NULL(ROW_VALUES(row)) == 1) {
	    prefix_len = 0;
	    prefix = 0;
	}
	else {
	    prefix = (char *)VAL_STRING(ROW_VALUES(row));
	    prefix_len = strlen(prefix);
	    if (prefix_len > MAX_PREFIX_LEN) {
	      LOG(L_ERR, "reload_gws(): too long lcr prefix\n");
	      lcr_dbf.free_result(dbh, res);
	      lcr_dbf.close(dbh);
	      return -1;
	    }
	}
	if (VAL_NULL(ROW_VALUES(row) + 1) == 1) {
	    from_uri_len = 0;
		from_uri = 0;
	}
	else {
	    from_uri = (char *)VAL_STRING(ROW_VALUES(row) + 1);
	    from_uri_len = strlen(from_uri);
	    if (from_uri_len > MAX_FROM_URI_LEN) {
		LOG(L_ERR, "reload_gws(): too long from_uri\n");
		lcr_dbf.free_result(dbh, res);
		lcr_dbf.close(dbh);
		return -1;
	    }
	}
	if (VAL_NULL(ROW_VALUES(row) + 2) == 1) {
	    LOG(L_ERR, "reload_gws(): route grp_id is NULL\n");
	    lcr_dbf.free_result(dbh, res);
	    lcr_dbf.close(dbh);
	    return -1;
	}
	grp_id = (unsigned int)VAL_INT(ROW_VALUES(row) + 2);
	if (VAL_NULL(ROW_VALUES(row) + 3) == 1) {
	    LOG(L_ERR, "reload_gws(): route priority is NULL\n");
	    lcr_dbf.free_result(dbh, res);
	    lcr_dbf.close(dbh);
	    return -1;
	}
	priority = (unsigned int)VAL_INT(ROW_VALUES(row) + 3);

	if (*lcrs == lcrs_1) {
		lcrs_2[i].prefix_len = prefix_len;
		if (prefix_len)
		    memcpy(&(lcrs_2[i].prefix[0]), prefix, prefix_len);
		lcrs_2[i].from_uri_len = from_uri_len;
		if (from_uri_len) {
		    memcpy(&(lcrs_2[i].from_uri[0]), from_uri, from_uri_len);
		    lcrs_2[i].from_uri[from_uri_len] = '\0';
		}
		lcrs_2[i].grp_id = grp_id;
		lcrs_2[i].priority = priority;
		lcrs_2[i].end_record = 0;
	} else {
		lcrs_1[i].prefix_len = prefix_len;
		if (prefix_len)
		    memcpy(&(lcrs_1[i].prefix[0]), prefix, prefix_len);
		lcrs_1[i].from_uri_len = from_uri_len;
                if (from_uri_len) {
                    memcpy(&(lcrs_1[i].from_uri[0]), from_uri, from_uri_len);
		    lcrs_1[i].from_uri[from_uri_len] = '\0';
		}
                lcrs_1[i].grp_id = grp_id;
		lcrs_1[i].priority = priority;
		lcrs_1[i].end_record = 0;
	}
    }

    lcr_dbf.free_result(dbh, res);
    lcr_dbf.close(dbh);

    if (*lcrs == lcrs_1) {
	lcrs_2[i].end_record = 1;
	*lcrs = lcrs_2;
    } else {
	lcrs_1[i].end_record = 1;
	*lcrs = lcrs_1;
    }

    (*lcrs_ws_reload_counter)++;
    if (0 != load_from_uri_regex()) {
	return -1;
    }

    return 1;
}


int mi_print_gws (struct mi_node* rpl)
{
    unsigned int i;
    struct mi_attr* attr;
    uri_transport transport;
    char *transp;
    struct mi_node* node;
    struct ip_addr address;
    char* p;
    int len;

    for (i = 0; i < MAX_NO_OF_GWS; i++) {

	if ((*gws)[i].ip_addr == 0) 
	    break;

	node= add_mi_node_child(rpl,0 ,"GW", 2, 0, 0);
	if(node == NULL)
	    return -1;

	p = int2str((unsigned long)(*gws)[i].grp_id, &len );
	attr = add_mi_attr(node, MI_DUP_VALUE, "GRP_ID", 6, p, len );
	if(attr == NULL)
	    return -1;

	transport = (*gws)[i].transport;
	if (transport == PROTO_UDP)
	    transp= ";transport=udp";
	else  if (transport == PROTO_TCP)
	    transp= ";transport=tcp";
	else  if (transport == PROTO_TLS)
	    transp= ";transport=tls";
	else
	    transp= "";

	address.af = AF_INET;
	address.len = 4;
	address.u.addr32[0] = (*gws)[i].ip_addr;
	attr= addf_mi_attr(node,0 ,"URI", 3,"%s:%s:%d%s",
			   ((*gws)[i].scheme == SIP_URI_T)?"sip":"sips",
			   ip_addr2a(&address),
			   ((*gws)[i].port == 0)?5060:(*gws)[i].port,transp);
	if(attr == NULL)
	    return -1;

	p = int2str((unsigned long)(*gws)[i].strip, &len );
	attr = add_mi_attr(node, MI_DUP_VALUE, "STRIP", 5, p, len);
	if(attr == NULL)
	    return -1;

	attr = add_mi_attr(node, MI_DUP_VALUE, "PREFIX", 6,
			   (*gws)[i].prefix, (*gws)[i].prefix_len );
	if(attr == NULL)
	    return -1;
    }

    for (i = 0; i < MAX_NO_OF_LCRS; i++) {
	if ((*lcrs)[i].end_record != 0)
	    break;

	node= add_mi_node_child(rpl, 0, "RULE", 4, 0, 0);
	attr = add_mi_attr(node, 0, "PREFIX", 6, (*lcrs)[i].prefix,
			   (*lcrs)[i].prefix_len );
	if(attr== 0)
	    return -1;

	attr = add_mi_attr(node, 0, "FROM_URI", 8, (*lcrs)[i].from_uri,
			   (*lcrs)[i].from_uri_len );
	if(attr== 0)
	    return -1;
	
	p = int2str((unsigned long)(*lcrs)[i].grp_id, &len );
	attr = add_mi_attr(node, MI_DUP_VALUE, "GRP_ID", 6, p, len );
	if(attr == NULL)
	    return -1;

	p = int2str((unsigned long)(*lcrs)[i].priority, &len );
	attr = add_mi_attr(node, MI_DUP_VALUE, "PRIORITY", 8, p, len );
	if(attr == NULL)
	    return -1;

    }

    return 0;
}


/*
 * Load info of matching GWs from database to gw_uri AVPs
 */
static int do_load_gws(struct sip_msg* _m, int grp_id)
{
    str ruri_user, from_uri, value;
    char from_uri_str[MAX_FROM_URI_LEN + 1];
    char ruri[MAX_URI_SIZE];
    unsigned int i, j, k, index;
    unsigned int addr, port;
    unsigned int strip, gw_index, duplicated_gw;
    uri_type scheme;
    uri_transport transport;
    struct ip_addr address;
    str addr_str, port_str;
    char *at, *prefix, *strip_string;
    int_str val;
    struct mi matched_gws[MAX_NO_OF_GWS + 1];
    unsigned short prefix_len, priority;
    int randomizer_start, randomizer_end, randomizer_flag, strip_len;
    struct lcr_info lcr_rec;

    /* Find Request-URI user */
    if (parse_sip_msg_uri(_m) < 0) {
	    LOG(L_ERR, "load_gws(): Error while parsing R-URI\n");
	    return -1;
    }
    ruri_user = _m->parsed_uri.user;

   /* Look for Caller RPID or From URI */
    if (search_first_avp(rpid_avp_name_str, rpid_name, &val, 0) &&
	val.s.s && val.s.len) {
	/* Get URI user from RPID */
	from_uri.len = val.s.len;
	from_uri.s = val.s.s;
    } else {
	/* Get URI from From URI */
	if ((!_m->from) && (parse_headers(_m, HDR_FROM_F, 0) == -1)) {
	    LOG(L_ERR, "load_gws(): Error while parsing message\n");
	    return -1;
	}
	if (!_m->from) {
	    LOG(L_ERR, "load_gws(): FROM header field not found\n");
	    return -1;
	}
	if ((!(_m->from)->parsed) && (parse_from_header(_m) < 0)) {
	    LOG(L_ERR, "load_gws(): Error while parsing From body\n");
	    return -1;
	}
	from_uri = get_from(_m)->uri;
    }
    if (from_uri.len < MAX_FROM_URI_LEN) {
	strncpy(from_uri_str, from_uri.s, from_uri.len);
	from_uri_str[from_uri.len] = '\0';
    } else {
	LOG(L_ERR, "load_gws(): from_uri to large\n");
	return -1;
    }

    /*
     * Check if the gws and lcrs were reloaded
     */
    if (reload_counter != *lcrs_ws_reload_counter) {
	if (load_from_uri_regex() != 0) {
	    return -1;
	}
    }

    /*
     * Let's match the gws:
     *  1. prefix matching
     *  2. from_uri matching
     *  3. grp_id matching
     *
     * Note: A gateway must be in the list _only_ once.
     */
    gw_index = 0;
    duplicated_gw = 0;
    for (i = 0; i < MAX_NO_OF_LCRS; i++) {
	lcr_rec = (*lcrs)[i];
	if (lcr_rec.end_record != 0) {
	    break;
	}
	if ((lcr_rec.prefix_len <= ruri_user.len) &&
	    (strncmp(lcr_rec.prefix, ruri_user.s, lcr_rec.prefix_len)==0)) {
	    /* 1. Prefix matching is done */
	    if ((lcr_rec.from_uri_len == 0) ||
		(from_uri_reg[i].valid && (regexec(&(from_uri_reg[i].re), from_uri_str, 0, (regmatch_t *)NULL, 0) == 0))) {
		/* 2. from_uri matching is done */
		for (j = 0; j < MAX_NO_OF_GWS; j++) {
		    if ((*gws)[j].ip_addr == 0) {
			break;
		    }
		    if (lcr_rec.grp_id == (*gws)[j].grp_id && (grp_id < 0 || (*gws)[j].grp_id == grp_id)) {
			/* 3. grp_id matching is done */
			for (k = 0; k < gw_index; k++) {
			    if ((*gws)[j].ip_addr ==
				(*gws)[matched_gws[k].gw_index].ip_addr) {
				/* Found the same gw in the list  */
				/* Let's keep the one with higher */
				/* match on prefix len            */
				DBG("DEBUG:lcr:load_gws: duplicate gw for index"
				    " %d [%d,%d] and current [%d,%d] \n",
				    k, matched_gws[k].route_index,
				    matched_gws[k].route_index, i, j);
				duplicated_gw = 1;
				if (lcr_rec.prefix_len >
				    (*lcrs)[matched_gws[k].route_index].prefix_len) {
				    /* Replace the old entry with the new one */
				    DBG("DEBUG:lcr:load_gws: replace[%d,%d]"
					" with [%d,%d] on index %d:"
					" prefix reason %d>%d\n",
					matched_gws[k].route_index,
					matched_gws[k].gw_index, i, j, k,
					lcr_rec.prefix_len,
					(*lcrs)[matched_gws[k].route_index].prefix_len);
				    matched_gws[k].route_index = i;
				    matched_gws[k].gw_index = j;
				    /* Stop searching in the matched_gws list */
				    break;
				} else if (lcr_rec.prefix_len ==
					   (*lcrs)[matched_gws[k].route_index].prefix_len) {
				    if (lcr_rec.priority >
					(*lcrs)[matched_gws[k].route_index].priority) {
					/* Replace the old entry with the new one */
					DBG("DEBUG:lcr:load_gws: replace[%d,%d] with"
					    " [%d,%d] on index %d:"
					    " priority reason %d>%d\n",
					    matched_gws[k].route_index,
					    matched_gws[k].gw_index, i, j, k,
					    lcr_rec.priority,
					    (*lcrs)[matched_gws[k].route_index].priority);
					matched_gws[k].route_index = i;
					matched_gws[k].gw_index = j;
					/* Stop searching in the matched_gws list */
					break;
				    }
				}
			    }
			}
			if (duplicated_gw == 0) {
			    /* This is a new gw */
			    matched_gws[gw_index].route_index = i;
			    matched_gws[gw_index].gw_index = j;
			    DBG("DEBUG:lcr:load_gws: add matched_gws[%d]=[%d,%d]\n",
				gw_index, i, j);
			    gw_index++;
			} else {
			    duplicated_gw = 0;
			}
		    }
		}
	    }
	}
    }
    matched_gws[gw_index].route_index = -1;
    matched_gws[gw_index].gw_index = -1;

    /*
     * Sort the gateways based on:
     *  1. prefix len
     *  2. priority
     */
    qsort(matched_gws, gw_index, sizeof(struct mi), comp_lcrs);
	randomizer_start = 0;

    /* Randomizing the gateways with same prefix_len and same priority */
    randomizer_flag = 0;
    prefix_len = (*lcrs)[matched_gws[0].route_index].prefix_len;
    priority = (*lcrs)[matched_gws[0].route_index].priority;
    for (i = 1; i < gw_index; i++) {
 	if ( prefix_len == (*lcrs)[matched_gws[i].route_index].prefix_len &&
 	     priority == (*lcrs)[matched_gws[i].route_index].priority) {
	    /* we have a match */
	    if (randomizer_flag == 0) {
		randomizer_flag = 1;
		randomizer_start = i - 1;
	    }
	    matched_gws[i - 1].randomizer = rand();
 	}
	else {
	    if (randomizer_flag == 1) {
		randomizer_end = i - 1;
		randomizer_flag = 0;
		qsort(&matched_gws[randomizer_start],
		      randomizer_end - randomizer_start + 1,
		      sizeof(struct mi), rand_lcrs);
	    }
	    prefix_len = (*lcrs)[matched_gws[i].route_index].prefix_len;
	    priority = (*lcrs)[matched_gws[i].route_index].priority;
	}
    }
    if (randomizer_flag == 1) {
	randomizer_end = gw_index - 1;
	matched_gws[i - 1].randomizer = rand();
	qsort(&matched_gws[randomizer_start],
	      randomizer_end - randomizer_start + 1,
	      sizeof(struct mi), rand_lcrs);
    }
    
    for (i = 0; i < MAX_NO_OF_GWS; i++) {
	index = matched_gws[i].gw_index;
	if (index == -1) {
	    break;
	}
      	addr = (*gws)[index].ip_addr;
	port = (*gws)[index].port;
	scheme = (*gws)[index].scheme;
	transport = (*gws)[index].transport;
	strip = (*gws)[index].strip;
	if (strip > ruri_user.len) {
	    LOG(L_ERR, "load_gws(): Strip count of gw is too large\n");
	    goto skip;
	}
	strip_string = int2str(strip, &strip_len);
	prefix_len = (*gws)[index].prefix_len;
	prefix = (*gws)[index].prefix;

	if (5 + prefix_len + 1 + strip_len + 1 + 15 + 1 + 5 + 1 + 14 >
	    MAX_URI_SIZE) {
	    LOG(L_ERR, "load_gws(): Request URI would be too long\n");
	    goto skip;
	}
	at = (char *)&(ruri[0]);
	if (scheme == SIP_URI_T) {
	    memcpy(at, "sip:", 4); at = at + 4;
	} else if (scheme == SIPS_URI_T) {
	    memcpy(at, "sips:", 5); at = at + 5;
	} else {
	    LOG(L_ERR, "load_gws(): Unknown or unsupported URI scheme: %u\n",
		(unsigned int)scheme);
	    goto skip;
	}
	if (prefix_len) {
	    memcpy(at, prefix, prefix_len); at = at + prefix_len;
	}
	//Add strip in this form |number. For example: |3 means strip first 3 characters
	*at = '|'; at = at + 1;
	memcpy(at, strip_string, strip_len);
	at = at + strip_len;

	*at = '@'; at = at + 1;
	address.af = AF_INET;
	address.len = 4;
	address.u.addr32[0] = addr;
	addr_str.s = ip_addr2a(&address);
	addr_str.len = strlen(addr_str.s);
	memcpy(at, addr_str.s, addr_str.len); at = at + addr_str.len;
	if (port != 0) {
	    if (port > 65536) {
		LOG(L_ERR, "load_gws(): Port of GW is too large: %u\n", port);
		goto skip;
	    }
	    *at = ':'; at = at + 1;
	    port_str.s = int2str(port, &port_str.len);
	    memcpy(at, port_str.s, port_str.len); at = at + port_str.len;
	}
	if (transport != PROTO_NONE) {
	    memcpy(at, ";transport=", 11); at = at + 11;
	    if (transport == PROTO_UDP) {
		memcpy(at, "udp", 3); at = at + 3;
	    } else if (transport == PROTO_TCP) {
		memcpy(at, "tcp", 3); at = at + 3;
	    } else if (transport == PROTO_TLS) {
		memcpy(at, "tls", 3); at = at + 3;
	    } else {
		LOG(L_ERR, "load_gws(): Unknown or unsupported transport: %u\n",
		    (unsigned int)transport);
		goto skip;
	    }
	}
	value.s = (char *)&(ruri[0]);
	value.len = at - value.s;
	val.s = value;
	add_avp(gw_uri_avp_name_str|AVP_VAL_STR, gw_uri_name, val);
	DBG("load_gws(): DEBUG: Added gw_uri_avp <%.*s>\n",
	    value.len, value.s);
    skip:
	continue;
    }

    return 1;
}

/*
 * Load info of matching GWs from database to gw_uri AVPs
 * taking into account the given group id.
 */
int load_gws_grp(struct sip_msg* _m, char* _s1, char* _s2)
{
    int grp_id;

    grp_id = (int)(long)_s1;
    return do_load_gws(_m, grp_id);
}

/*
 * Load info of matching GWs from database to gw_uri AVPs
 * ignoring the group id.
 */
int load_gws(struct sip_msg* _m, char* _s1, char* _s2)
{
    return do_load_gws(_m, -1);
}



/*
 * If called from request route block, rewrites scheme, host, port, and
 * transport parts of R-URI based on first gw_uri AVP value, which is then
 * destroyed.  Also saves R-URI user to ruri_user AVP for later use in
 * failure route block.
 * If called from failure route block, appends a new branch to request
 * where scheme, host, port, and transport of URI are taken from the first
 * gw_uri AVP value, which is then destroyed.  URI user is taken from
 * ruri_user AVP value saved earlier.
 * Returns 1 upon success and -1 upon failure.
 */
int next_gw(struct sip_msg* _m, char* _s1, char* _s2)
{
    int_str gw_uri_val, ruri_user_val, val;
    struct action act;
    int rval;
    struct usr_avp *gw_uri_avp, *ruri_user_avp;
    str new_ruri;
    char *at, *at_char, *strip_char, *endptr;
    unsigned int strip;

    gw_uri_avp = search_first_avp(gw_uri_avp_name_str,
				  gw_uri_name, &gw_uri_val, 0);
    if (!gw_uri_avp) return -1;

    if (route_type == REQUEST_ROUTE) {
	/* Create new Request-URI taking URI user from current Request-URI
	   and other parts of from gw_uri AVP. */
	if (parse_sip_msg_uri(_m) < 0) {
	    LOG(L_ERR, "next_gw(): Parsing of R-URI failed.\n");
	    return -1;
	}
	new_ruri.s = pkg_malloc(gw_uri_val.s.len +_m->parsed_uri.user.len);
	if (!new_ruri.s) {
	    LOG(L_ERR, "next_gw(): No memory for new R-URI.\n");
	    return -1;
	}
	at_char = memchr(gw_uri_val.s.s, '@', gw_uri_val.s.len);
	if (!at_char) {
	    pkg_free(new_ruri.s);
	    LOG(L_ERR, "next_gw(): No @ in gateway URI.\n");
	    return -1;
	}
	strip_char = memchr(gw_uri_val.s.s, '|', gw_uri_val.s.len);
	if (!strip_char || strip_char + 1 >= at_char) {
	    pkg_free(new_ruri.s);
	    LOG(L_ERR, "next_gw(): No strip character | and at least one "
		"character before @ in gateway URI.\n");
	    return -1;
	}
	at = new_ruri.s;
	memcpy(at, gw_uri_val.s.s, strip_char - gw_uri_val.s.s);
	at = at + (strip_char - gw_uri_val.s.s);
	strip = strtol(strip_char + 1, &endptr, 10);
	if (endptr != at_char) {
	    LOG(L_ERR, "next_gw(): Non-digit character between | "
		"and @ chars\n");
	    return -1;
	}
	if (_m->parsed_uri.user.len - strip > 0) {
	    memcpy(at, _m->parsed_uri.user.s + strip,
		   _m->parsed_uri.user.len - strip);
	    at = at + _m->parsed_uri.user.len - strip;
	}
	if (*(at - 1) != ':') {
	    memcpy(at, at_char, gw_uri_val.s.len - (at_char - gw_uri_val.s.s));
	    at = at + gw_uri_val.s.len - (at_char - gw_uri_val.s.s);
	} else {
	    memcpy(at, at_char + 1, gw_uri_val.s.len -
		   (at_char + 1 - gw_uri_val.s.s));
	    at = at + gw_uri_val.s.len - (at_char + 1 - gw_uri_val.s.s);
	}
	*at = '\0';
	/* Save Request-URI user for use in FAILURE_ROUTE */
	val.s = _m->parsed_uri.user;
	add_avp(ruri_user_avp_name_str|AVP_VAL_STR, ruri_user_name, val);
	DBG("load_gws(): DEBUG: Added ruri_user_avp <%.*s>\n",
	    val.s.len, val.s.s);
	/* Rewrite Request URI */
	act.type = SET_URI_T;
	act.elem[0].type = STRING_ST;
	act.elem[0].u.string = new_ruri.s;
	rval = do_action(&act, _m);
	pkg_free(new_ruri.s);
	destroy_avp(gw_uri_avp);
	if (rval != 1) {
	    LOG(L_ERR, "next_gw(): ERROR: do_action failed with return "
		"value <%d>\n", rval);
	    return -1;
	}
	return 1;
    } else if (route_type == FAILURE_ROUTE) {
	/* Create new Request-URI taking URI user from ruri_user AVP
	   and other parts of from gateway URI AVP. */
	ruri_user_avp = search_first_avp(ruri_user_avp_name_str,
					 ruri_user_name, &ruri_user_val, 0);
	if (!ruri_user_avp) {
	    LOG(L_ERR, "next_gw(): No ruri_user AVP\n");
	    return -1;
	}
	new_ruri.s = pkg_malloc(gw_uri_val.s.len + ruri_user_val.s.len);
	if (!new_ruri.s) {
	    LOG(L_ERR, "next_gw(): No memory for new R-URI.\n");
	    return -1;
	}
	at_char = memchr(gw_uri_val.s.s, '@', gw_uri_val.s.len);
	if (!at_char) {
	    pkg_free(new_ruri.s);
	    LOG(L_ERR, "next_gw(): No @ in gateway URI.\n");
	    return -1;
	}
	strip_char = memchr(gw_uri_val.s.s, '|', gw_uri_val.s.len);
	if (!strip_char || strip_char + 1 >= at_char) {
	    pkg_free(new_ruri.s);
	    LOG(L_ERR, "next_gw(): No strip character | and at least one "
		"character before @ in gateway URI.\n");
	    return -1;
	}
	at = new_ruri.s;
	memcpy(at, gw_uri_val.s.s, strip_char - gw_uri_val.s.s);
	at = at + (strip_char - gw_uri_val.s.s);
	strip = strtol(strip_char + 1, &endptr, 10);
	if (endptr != at_char) {
	    LOG(L_ERR, "next_gw(): Non-digit character between | "
		"and @ chars\n");
	    return -1;
	}
	if (ruri_user_val.s.len - strip > 0) {
	    memcpy(at, ruri_user_val.s.s + strip,
		   ruri_user_val.s.len - strip);
	    at = at + ruri_user_val.s.len - strip;
	}
	if (*(at - 1) != ':') {
	    memcpy(at, at_char, gw_uri_val.s.len - (at_char - gw_uri_val.s.s));
	    at = at + gw_uri_val.s.len - (at_char - gw_uri_val.s.s);
	} else {
	    memcpy(at, at_char + 1, gw_uri_val.s.len -
		   (at_char + 1 - gw_uri_val.s.s));
	    at = at + gw_uri_val.s.len - (at_char + 1 - gw_uri_val.s.s);
	}
	new_ruri.len = at - new_ruri.s;
	act.type = APPEND_BRANCH_T;
	act.elem[0].type = STRING_ST;
	act.elem[0].u.s = new_ruri;
	act.elem[1].type = NUMBER_ST;
	act.elem[1].u.number = 0;
	rval = do_action(&act, _m);
	pkg_free(new_ruri.s);
	destroy_avp(gw_uri_avp);
	if (rval != 1) {
	    LOG(L_ERR, "next_gw(): ERROR: do_action failed with return "
		"value <%d>\n", rval);
	    return -1;
	}
	return 1;
    }
    /* unsupported route type */
    return -1;
}


/*
 * Checks if request comes from a gateway
 */
static int do_from_gw(struct sip_msg* _m, int grp_id)
{
    int i;
    unsigned int src_addr;

    src_addr = _m->rcv.src_ip.u.addr32[0];

    for (i = 0; i < MAX_NO_OF_GWS; i++) {
	    if ((*gws)[i].ip_addr == 0) {
		    return -1;
	    }
	    if ((*gws)[i].ip_addr == src_addr && 
		    (grp_id < 0 || (*gws)[i].grp_id == grp_id)) {
		    return 1;
	    }
    }

    return -1;
}


/*
 * Checks if request comes from a gateway, taking
 * into account the group id.
 */
int from_gw_grp(struct sip_msg* _m, char* _s1, char* _s2)
{
	int grp_id;

	grp_id = (int)(long)_s1;
	return do_from_gw(_m, grp_id);
}

/*
 * Checks if request comes from a gateway, ignoring
 * the group id.
 */
int from_gw(struct sip_msg* _m, char* _s1, char* _s2)
{
	return do_from_gw(_m, -1);
}


/*
 * Checks if in-dialog request goes to gateway
 */
static int do_to_gw(struct sip_msg* _m, int grp_id)
{
    char host[16];
    struct in_addr addr;
    unsigned int i;

    if((_m->parsed_uri_ok == 0) && (parse_sip_msg_uri(_m) < 0)) {
	LOG(L_ERR, "LCR: to_gw: ERROR while parsing the R-URI\n");
	return -1;
    }

    if (_m->parsed_uri.host.len > 15) {
	return -1;
    }
    memcpy(host, _m->parsed_uri.host.s, _m->parsed_uri.host.len);
    host[_m->parsed_uri.host.len] = 0;
    
    if (!inet_aton(host, &addr)) {
	return -1;
    }

    for (i = 0; i < MAX_NO_OF_GWS; i++) {
	if ((*gws)[i].ip_addr == 0) {
	    return -1;
	}
	if ((*gws)[i].ip_addr == addr.s_addr && 
		(grp_id < 0 || (*gws)[i].grp_id == grp_id)) {
	    return 1;
	}
    }

    return -1;
}


/*
 * Checks if in-dialog request goes to gateway, taking
 * into account the group id.
 */
int to_gw_grp(struct sip_msg* _m, char* _s1, char* _s2)
{
	int grp_id;

	grp_id = (int)(long)_s1;
	return do_to_gw(_m, grp_id);
}


/*
 * Checks if in-dialog request goes to gateway, ignoring
 * the group id.
 */
int to_gw(struct sip_msg* _m, char* _s1, char* _s2)
{
	return do_to_gw(_m, -1);
}


/* 
 * Frees contact list used by load_contacts function
 */
static inline void free_contact_list(struct contact *curr) {
    struct contact *prev;
    while (curr) {
	prev = curr;
	curr = curr->next;
	pkg_free(prev);
    }
}


/* 
 * Loads contacts in destination set into "lcr_contact" AVP in reverse
 * priority order and associated each contact with Q_FLAG telling if
 * contact is the last one in its priority class.  Finally, removes
 * all branches from destination set.
 */
int load_contacts(struct sip_msg* msg, char* key, char* value)
{
	str branch, *ruri;
	qvalue_t q, ruri_q;
	struct contact *contacts, *next, *prev, *curr;
	int_str val;
	int idx;

	/* Check if anything needs to be done */
	if (nr_branches == 0) {
	    DBG("load_contacts(): DEBUG: Nothing to do - no branches!\n");
	    return 1;
	}

	ruri = GET_RURI(msg);
	if (!ruri) {
	    LOG(L_ERR, "ERROR: load_contacts(): No Request-URI found\n");
	    return -1;
	}
	ruri_q = get_ruri_q();

	for( idx=0 ; (branch.s=get_branch(idx,&branch.len,&q,0,0,0,0))!=0 ; idx++ ) {
	    if (q != ruri_q) {
		goto rest;
	    }
	}
	DBG("load_contacts(): DEBUG: Nothing to do - all same q!\n");
	return 1;

rest:
	/* Insert Request-URI to contact list */
	contacts = (struct contact *)pkg_malloc(sizeof(struct contact));
	if (!contacts) {
	    LOG(L_ERR, "ERROR: load_contacts(): No memory for Request-URI\n");
	    return -1;
	}
	contacts->uri.s = ruri->s;
	contacts->uri.len = ruri->len;
	contacts->q = ruri_q;
	contacts->next = (struct contact *)0;

	/* Insert branch URIs to contact list in increasing q order */
	for( idx=0 ; (branch.s=get_branch(idx,&branch.len,&q,0,0,0,0))!=0 ; idx++ ) {
	    next = (struct contact *)pkg_malloc(sizeof(struct contact));
	    if (!next) {
		LOG(L_ERR, "ERROR: load_contacts(): No memory for branch URI\n");
		free_contact_list(contacts);
		return -1;
	    }
	    next->uri = branch;
	    next->q = q;
	    prev = (struct contact *)0;
	    curr = contacts;
	    while (curr && (curr->q < q)) {
		prev = curr;
		curr = curr->next;
	    }
	    if (!curr) {
		next->next = (struct contact *)0;
		prev->next = next;
	    } else {
		next->next = curr;
		if (prev) {
		    prev->next = next;
		} else {
		    contacts = next;
		}
	    }		    
	}

	/* Assign values for q_flags */
	curr = contacts;
	curr->q_flag = 0;
	while (curr->next) {
	    if (curr->q < curr->next->q) {
		curr->next->q_flag = Q_FLAG;
	    } else {
		curr->next->q_flag = 0;
	    }
	    curr = curr->next;
	}

	/* Add contacts to "contacts" AVP */
	curr = contacts;
	while (curr) {
	    val.s = curr->uri;
	    add_avp(contact_avp_name_str|AVP_VAL_STR|(curr->q_flag),
		    contact_name, val);
	    DBG("load_contacts(): DEBUG: Loaded <%s>, q_flag <%d>\n",
		val.s.s, curr->q_flag);	    
	    curr = curr->next;
	}

	/* Clear all branches */
	clear_branches();

	/* Free contacts list */
	free_contact_list(contacts);

	return 1;
}


/*
 * Adds to request a destination set that includes all highest priority
 * class contacts in "lcr_contact" AVP.   If called from a route block,
 * rewrites the request uri with first contact and adds the remaining
 * contacts as branches.  If called from failure route block, adds all
 * contacts as brances.  Removes added contacts from "lcr_contact" AVP.
 */
int next_contacts(struct sip_msg* msg, char* key, char* value)
{
    struct usr_avp *avp, *prev;
    int_str val;
    struct action act;
    int rval;

	if ( route_type == REQUEST_ROUTE) {
		/* Find first lcr_contact_avp value */
		avp = search_first_avp(contact_avp_name_str, contact_name, &val, 0);
		if (!avp) {
			DBG("next_contacts(): DEBUG: No AVPs -- we are done!\n");
			return 1;
		}

		/* Set Request-URI */
		act.type = SET_URI_T;
		act.elem[0].type = STRING_ST;
		act.elem[0].u.string = val.s.s;
		rval = do_action(&act, msg);
		if (rval != 1) {
			destroy_avp(avp);
			return rval;
		}
		DBG("next_contacts(): DEBUG: R-URI is <%s>\n", val.s.s);
		if (avp->flags & Q_FLAG) {
			destroy_avp(avp);
			/* Set fr_inv_timer */
			val.n = inv_timer_next;
			if (add_avp(AVP_NAME_STR, inv_timer_name, val) != 0) {
				LOG(L_ERR, "next_contacts(): ERROR: setting of "
					"fr_inv_timer_avp failed\n");
				return -1;
			}
			return 1;
		}
		/* Append branches until out of branches or Q_FLAG is set */
		prev = avp;
		while ((avp = search_next_avp(avp, &val))) {
			destroy_avp(prev);
			act.type = APPEND_BRANCH_T;
			act.elem[0].type = STRING_ST;
			act.elem[0].u.s = val.s;
			act.elem[1].type = NUMBER_ST;
			act.elem[1].u.number = 0;
			rval = do_action(&act, msg);
			if (rval != 1) {
				destroy_avp(avp);
				LOG(L_ERR, "next_contacts(): ERROR: do_action failed "
					"with return value <%d>\n", rval);
				return -1;
			}
			DBG("next_contacts(): DEBUG: Branch is <%s>\n", val.s.s);
			if (avp->flags & Q_FLAG) {
				destroy_avp(avp);
				val.n = inv_timer_next;
				if (add_avp(AVP_NAME_STR, inv_timer_name, val) != 0) {
					LOG(L_ERR, "next_contacts(): ERROR: setting of "
						"fr_inv_timer_avp failed\n");
					return -1;
				}
				return 1;
			}
			prev = avp;
		}

	} else if ( route_type == FAILURE_ROUTE) {
		avp = search_first_avp(contact_avp_name_str, contact_name, &val, 0);
		if (!avp) return -1;

		prev = avp;
		do {
			act.type = APPEND_BRANCH_T;
			act.elem[0].type = STRING_ST;
			act.elem[0].u.s = val.s;
			act.elem[1].type = NUMBER_ST;
			act.elem[1].u.number = 0;
			rval = do_action(&act, msg);
			if (rval != 1) {
				destroy_avp(avp);
				return rval;
			}
			DBG("next_contacts(): DEBUG: New branch is <%s>\n", val.s.s);
			if (avp->flags & Q_FLAG) {
				destroy_avp(avp);
				return 1;
			}
			prev = avp;
			avp = search_next_avp(avp, &val);
			destroy_avp(prev);
		} while (avp);

		/* Restore fr_inv_timer */
		val.n = inv_timer;
		if (add_avp(AVP_NAME_STR, inv_timer_name, val) != 0) {
			LOG(L_ERR, "next_contacts(): ERROR: setting of "
				"fr_inv_timer_avp failed\n");
			return -1;
		}

	} else {
		/* unsupported rout type */
		return -1;
	}

	return 1;
}

/* 
 * Convert string parameter to integer for functions that expect an integer.
 * Taken from mediaproxy module.
 */
static int fixstring2int(void **param, int param_count)
{
	unsigned long number;
	int err;

	if (param_count == 1) {
		number = str2s(*param, strlen(*param), &err);
		if (err == 0) {
			pkg_free(*param);
			*param = (void*)number;
			return 0;
		} else {
			LOG(L_ERR, "lcr/fixstring2int(): ERROR: bad number `%s'\n",
				(char*)(*param));
			return E_CFG;
		}
	}
	return 0;
}



syntax highlighted by Code2HTML, v. 0.9.1