/*
 * res_netsec.c -- Provides the MIDCOM services to Asterisk
 *
 * Copyright (C) 2005, Ranch Networks, Inc.
 *
 * This program 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.
 *
 * This program 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., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
 *
 * Author : Srivatsa Chivukula <srivatsa@ranchnetworks.com>
 *
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/time.h>
#include <signal.h>
#include <errno.h>
#include <unistd.h>
#include <netinet/in.h>
#include <sys/time.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <fcntl.h>
#include <regex.h>
#include <assert.h>

#include <asterisk/channel.h>
#include <asterisk/rtp.h>
#include <asterisk/config.h>
#include <asterisk/options.h>
#include <asterisk/logger.h>
#include <asterisk/utils.h>
#include <asterisk/cli.h>
#include <asterisk/module.h>

#include "midcom/simco_client.h"
#include "timer/voipTimerP.h"
#include "res_netsec.h"

#define NO_MIDCOM 0
#define NEAREND 1
#define FAREND 2

static const char version[] = "Libmidcom version 0.2.0";
static const char desc[] = "MiddleBox Communication Module (MIDCOM)";
static unsigned int midcomnat = NO_MIDCOM;
static char cert[512];
static char* config = "midcom.conf";
static int midcom_enabled = 1;

static int usecnt = 0;
AST_MUTEX_DEFINE_STATIC(usecnt_lock);

#define SAFE_INTERVAL   60 /* seconds */

#define DEFAULT_FREQ_OK		60   /* How often to check for the midbox to be up */
#define DEFAULT_FREQ_NOTOK	10   /* How often to check, if the midbox is down... */

timer_task_context_t timer_context, *timer;

static timer_task_context_t *timer_context_create(void);
static void timer_context_delete(timer_task_context_t *ttc);

static struct midcom_rule {
  int enable_rtcp;
  struct in_addr midbox_addr;
  char username[256];
  int nat_done;
  int firewall_done;
  int bye_done;
  int novideo;
  int con;
  int ruleid;
  struct sockaddr_in our_internal_addr;
  struct sockaddr_in our_natted_addr;
  struct sockaddr_in redirip_nat;       /* Peer NAT */
  int vruleid;
  struct sockaddr_in our_internal_vaddr;
  struct sockaddr_in our_natted_vaddr;
  struct sockaddr_in vredirip_nat;
  void *channel;                   /* farend */
  struct sockaddr_in remote_addr;  /* farend */
  struct sockaddr_in remote_vaddr; /* farend */
  int lifetime;
  int is_cached;
  int invalid;
  timer_id_t timer_handle;
  void *p;
  struct midcom_rule *next;
} *midcom_rule_head = NULL;

static struct connection {
  struct in_addr ipaddress;
  char name[256];
  char username[256];
  char password[256];
  int tls;
  int mode;
  int sock;
  int pingsock;
  timer_id_t timer_handle;
  struct connection *next;
} *connl = NULL;
	
/* Protect the midcom rule list */
AST_MUTEX_DEFINE_STATIC(mclock);

static int midcom_config_reload(void);

static int get_lb_midbox_ip(struct in_addr *ip);
static int get_midbox_ip(void *bridge, struct in_addr *ip, struct sockaddr_in *addr, struct sockaddr_in *vaddr);
static int is_nearend_nat(void);
static int is_farend_nat(void);

static struct midcom_rule *midcom_rule_alloc(void *p);

static timer_task_context_t *timer_context_create(void);
static void timer_context_delete(timer_task_context_t *ttc);
static int timer_setup_tick(void *data, size_t datalen, unsigned int timeout, timer_id_t *handleP);
static int timer_change_tick(void *data, size_t datalen, unsigned int mew_timeout);
static int timer_cancel_tick(void *data, size_t datalen);
static int timer_tick_handler(void *argptr, size_t arglen);

static void midcom_connect_all(void);
static int midcom_connect(struct in_addr midbox, int *lifetime, int *cached);
static int midcom_change_lifetime(int con, int rule, int *lifetime);
static int midcom_reserve_nat(int con, int *rule, struct sockaddr_in *nat_addr, int enable_rtcp, int *lifetime);
static int midcom_open_firewall(int con, int *rule, struct sockaddr_in *their_external, struct sockaddr_in *our_internal, int *lifetime, int codec);
static int midcom_add_firewall(int con, int rule, struct sockaddr_in *their_external, struct sockaddr_in *our_internal, int *lifetime, int codec);
static int midcom_bridge_firewall(int con, int rule, struct sockaddr_in *our_external, struct sockaddr_in *their_external, int *lifetime, int codec);
static int midcom_delete_rule(int con, int rule);
static void midcom_disconnect(int con, int cached);

static int midcom_enable_firewall(int fd, int argc, char *argv[]);
static int midcom_disable_firewall(int fd, int argc, char *argv[]);
static int midcom_show_firewall(int fd, int argc, char *argv[]);
static int midcom_enable_debug(int fd, int argc, char *argv[]);
static int midcom_disable_debug(int fd, int argc, char *argv[]);
static int midcom_show_version(int fd, int argc, char *argv[]);

static int midcom_handle_request_invite_hook(void *p);
static int midcom_handle_response_invite_hook(void *p);
static int midcom_transmit_response_with_sdp_hook(void *p);
static int midcom_transmit_reinvite_with_sdp_hook(void *p);
static int midcom__sip_destroy_hook(void *p);
static void midcom_rtp_nat_us_audio_hook(struct ast_rtp *rtp, void *r);
static void midcom_rtp_nat_us_video_hook(struct ast_rtp *rtp, void *r);
static void midcom_get_redirip_audio_hook(void *r, struct sockaddr_in *redirip);
static void midcom_get_vredirip_video_hook(void *r, struct sockaddr_in *vredirip);
static void midcom_rtp_get_their_nat_audio_hook(struct ast_rtp *rtp, void *r);
static void midcom_rtp_get_their_nat_video_hook(struct ast_rtp *vrtp, void *r);
static void midcom_sip_helper_register(struct ast_sip_helper_cb *cb);
static void midcom_sip_helper_unregister(void);

static struct midcom_rule *get_peer_rule(struct midcom_rule *r);

static struct ast_sip_helper_cb *sip_cb = NULL;

struct ast_sip_hook_cb *m_cb = NULL; /*move this to the core asterisk files */

static char enable_firewall_usage[] = 
"Usage: midcom enable firewall\n"
"       Enable the midcom communication between Asterisk and middlebox.\n";

static char disable_firewall_usage[] = 
"Usage: midcom disable firewall\n"
"       Disable the midcom communication between Asterisk and middlebox.\n";

static char show_firewall_usage[] = 
"Usage: midcom show firewall\n"
"       Lists all currently active midcom firewalled/bridged rules.\n";

static char show_version_usage[] = 
"Usage: midcom show version\n"
"       Display the current verison of the libmidcom module.\n";

static char enable_debug_usage[] = 
"Usage: midcom debug\n"
"       Enable printing of debug messages on the libmidcom module.\n";

static char disable_debug_usage[] = 
"Usage: midcom no debug\n"
"       Disable printing of debug messages on the libmidcom module.\n";

static struct ast_cli_entry  my_clis[] = {
	{ { "midcom", "enable", "firewall", NULL }, midcom_enable_firewall, "Enable the midcom communication between Asterisk and middlebox", enable_firewall_usage },
	{ { "midcom", "disable", "firewall", NULL }, midcom_disable_firewall, "Disable the midcom communication between Asterisk and middlebox", disable_firewall_usage },
	{ { "midcom", "show", "firewall", NULL }, midcom_show_firewall, "Show all Firewall/Bridged rules created by Asterisk", show_firewall_usage /*,complete_midcom_show_firewall*/ },
	{ { "midcom", "debug", NULL }, midcom_enable_debug, "Enable debug prints in libmidcom module", enable_debug_usage },
	{ { "midcom", "no", "debug", NULL }, midcom_disable_debug, "Disable debug prints in libmidcom module", disable_debug_usage },
	{ { "midcom", "show", "version", NULL }, midcom_show_version, "Display the current version of the midcom communication module", show_version_usage }
};

/*--- midcom_sip: Interface structure with callbacks used to connect to sip module --*/
static struct ast_sip_hook_cb sip_hook = {
	handle_request_invite_hook: midcom_handle_request_invite_hook,
	handle_response_invite_hook: midcom_handle_response_invite_hook,
	transmit_response_with_sdp_hook: midcom_transmit_response_with_sdp_hook,
	transmit_reinvite_with_sdp_hook: midcom_transmit_reinvite_with_sdp_hook,
	__sip_destroy_hook: midcom__sip_destroy_hook,
	ast_rtp_nat_us_audio_hook: midcom_rtp_nat_us_audio_hook,
	ast_rtp_nat_us_video_hook: midcom_rtp_nat_us_video_hook,
	ast_get_redirip_audio_hook: midcom_get_redirip_audio_hook,
	ast_get_vredirip_video_hook: midcom_get_vredirip_video_hook,
	ast_rtp_get_their_nat_audio_hook: midcom_rtp_get_their_nat_audio_hook,
	ast_rtp_get_their_nat_video_hook: midcom_rtp_get_their_nat_video_hook,
	ast_sip_helper_register: midcom_sip_helper_register,
	ast_sip_helper_unregister: midcom_sip_helper_unregister,
};

static void midcom_sip_helper_register(struct ast_sip_helper_cb *cb)
{
  sip_cb = cb;
}

static void midcom_sip_helper_unregister(void)
{
  sip_cb = NULL;
}

int load_module(void)
{
  if (midcom_config_reload()) {
    ast_log(LOG_ERROR, "Unable to read conf file\n");
    return -1;
  }

  fflush(stdout);
  if (!mc_init(cert)) {
    ast_log(LOG_ERROR, "Unable to initialize midcom\n");
    return -1;
  }

  timer = timer_context_create();
  if (!timer) {
    ast_log(LOG_ERROR, "Unable to create (midcom) timer context\n");
    mc_close();
    return -1;
  }

  /* Register the sip hook functions */
  m_cb = &sip_hook;

  /* Register all CLI functions for midcom */
  ast_cli_register_multiple(my_clis, sizeof(my_clis)/ sizeof(my_clis[0]));

  midcom_connect_all();
  return 0;
}

int unload_module(void)
{
  struct midcom_rule *r;
  struct connection *c;

  if (!ast_mutex_lock(&mclock)) {
    /* Hangup all interfaces if they have an owner */
    r = midcom_rule_head;
    while (r) {
      if (sip_cb)
	sip_cb->ast_softhangup_helper(r->p);
      r = r->next;
    }
    midcom_rule_head = NULL;
    ast_mutex_unlock(&mclock);
  } else {
    ast_log(LOG_WARNING, "Unable to lock the midcom rule list\n");
  }

  c = connl;
  while (c) {
    timer_cancel_tick(c, sizeof(struct connection));
    mc_comm_disconnect(c->pingsock);
    mc_comm_disconnect(c->sock);
    c = c->next;
  }
  connl = NULL;

  if (timer == NULL) {
    timer_context_delete(timer);
    timer = NULL;
  }

  /* Unregister the sip hook functions */
  m_cb = NULL;

  ast_cli_unregister_multiple(my_clis, sizeof(my_clis)/ sizeof(my_clis[0]));
  mc_close();
  return 0;
}

int usecount()
{
  return usecnt;
}

char *key()
{
  return ASTERISK_GPL_KEY;
}

char *description()
{
  return (char *) desc;
}

/*--- midcom_enable_firewall: Turn on Midcom firewall (CLI command) */
static int midcom_enable_firewall(int fd, int argc, char *argv[])
{
  int oldflag = midcom_enabled;
  if (argc != 3)
    return RESULT_SHOWUSAGE;
  midcom_enabled = 1;
  if (oldflag)
    ast_cli(fd, "SIP Midcom Firewalling re-enabled\n");
  else
    ast_cli(fd, "SIP Midcom Firewalling enabled\n");
  return RESULT_SUCCESS;
}

/*--- midcom_disable_firewall: Turn off Midcom firewall (CLI command) */
static int midcom_disable_firewall(int fd, int argc, char *argv[])
{
  if (argc != 3)
    return RESULT_SHOWUSAGE;
  midcom_enabled = 0;
  ast_cli(fd, "SIP Midcom Firewalling disabled\n");
  return RESULT_SUCCESS;
}

/*--- midcom_show_firewall: Show Midcom firewall rules (CLI command) */
static int midcom_show_firewall(int fd, int argc, char *argv[])
{
  regex_t regexbuf;
  int havepattern = 0;

#define FORMAT2 "%-15.15s  %-21.20s %-8s %-15.15s %-8s %-15.15s %-8s  %-5.5s %-10s\n"
#define FORMAT  "%-15.15s  %-21.20s %-8d %-15.15s %-8d %-15.15s %-8d  %-5.5s %-10s\n"

  struct midcom_rule *r = midcom_rule_head;
  char name[256];
  char iabuf1[INET_ADDRSTRLEN], iabuf2[INET_ADDRSTRLEN], iabuf3[INET_ADDRSTRLEN];
  struct ast_channel *peer_chan;
  struct midcom_rule *peer_rule;
  struct sockaddr_in peer_ip;

  switch (argc) {
  case 5:
    if (!strcasecmp(argv[3], "like")) {
      if (regcomp(&regexbuf, argv[4], REG_EXTENDED | REG_NOSUB))
		  return RESULT_SHOWUSAGE;
      havepattern = 1;
    } else
      return RESULT_SHOWUSAGE;
  case 3:
    break;
  default:
    return RESULT_SHOWUSAGE;
  }

  ast_cli(fd, FORMAT2, "Username", "Originating Address", "Port", "Natted Address", "Port", "Peer Address", "Port", "Media", "Status");
	
  for ( ; r ; r = r->next) {

    char status[20] = "";

    if (havepattern && regexec(&regexbuf, r->username, 0, NULL, 0)) {
      continue;
    }

    strncpy(name, r->username, sizeof(name));
    memset(&peer_ip, 0, sizeof(peer_ip));
    peer_ip = r->redirip_nat;

    if (r->firewall_done) {
      if (is_nearend_nat())
		  strncpy(status, "Firewalled", sizeof(status));
      else if (is_farend_nat()) {

               if (r->nat_done)
		  strncpy(status, "Bridged", sizeof(status));
               else
		  strncpy(status, "Firewalled", sizeof(status));

                  peer_chan = sip_cb->ast_bridged_channel_helper(r->p);
                  if (peer_chan && peer_chan->tech_pvt) {
			peer_rule = sip_cb->ast_get_hook_struct(peer_chan->tech_pvt);
			if (peer_rule)
				peer_ip = peer_rule->our_natted_addr;
	          }
      }
    }
    else if (r->nat_done)
      strncpy(status, "Natted", sizeof(status));
    else if (r->bye_done)
      strncpy(status, "Deleted", sizeof(status));

    ast_cli(fd, FORMAT, name, 
		r->our_internal_addr.sin_addr.s_addr ? ast_inet_ntoa(iabuf1, sizeof(iabuf1), r->our_internal_addr.sin_addr) : "(Unspecified)",
		ntohs(r->our_internal_addr.sin_port),
		r->our_natted_addr.sin_addr.s_addr ? ast_inet_ntoa(iabuf2, sizeof(iabuf2), r->our_natted_addr.sin_addr) : "(Unspecified)",
		ntohs(r->our_natted_addr.sin_port),
		peer_ip.sin_addr.s_addr ? ast_inet_ntoa(iabuf3, sizeof(iabuf3), peer_ip.sin_addr) : "(Unspecified)",
		ntohs(peer_ip.sin_port),
		"Audio",
		status);
     if (!r->novideo) {

       peer_ip = peer_rule->our_natted_vaddr;
       ast_cli(fd, FORMAT, name, 
		r->our_internal_vaddr.sin_addr.s_addr ? ast_inet_ntoa(iabuf1, sizeof(iabuf1), r->our_internal_vaddr.sin_addr) : "(Unspecified)",
		ntohs(r->our_internal_vaddr.sin_port),
		r->our_natted_vaddr.sin_addr.s_addr ? ast_inet_ntoa(iabuf2, sizeof(iabuf2), r->our_natted_vaddr.sin_addr) : "(Unspecified)",
		ntohs(r->our_natted_vaddr.sin_port),
		peer_ip.sin_addr.s_addr ? ast_inet_ntoa(iabuf3, sizeof(iabuf3), peer_ip.sin_addr) : "(Unspecified)",
		ntohs(peer_ip.sin_port),
		"Video",
		status);

     }
  };

  if (havepattern)
    regfree(&regexbuf);

  return RESULT_SUCCESS;
#undef FORMAT
#undef FORMAT2
}



/*--- midcom_show_version: Show the current version of Midcom module */
static int midcom_show_version(int fd, int argc, char *argv[])
{

  if (argc != 3)
      return RESULT_SHOWUSAGE;
  ast_cli(fd, "%s\n", version);
  return RESULT_SUCCESS;
}


/*--- midcom_enable_debug: Enable debug prints on the Midcom module */
static int midcom_enable_debug(int fd, int argc, char *argv[])
{

  if (argc != 2)
      return RESULT_SHOWUSAGE;
  debug_midbox = 1;
  ast_cli(fd, "Debug printing enabled on MIDOCM\n");
  return RESULT_SUCCESS;
}


/*--- midcom_disable_debug: Disable debug prints on the Midcom module */
static int midcom_disable_debug(int fd, int argc, char *argv[])
{

  if (argc != 3)
      return RESULT_SHOWUSAGE;
  debug_midbox = 0;
  ast_cli(fd, "Debug printing disabled on MIDOCM\n");
  return RESULT_SUCCESS;
}



static int midcom_config_reload(void)
{
  struct ast_config *cfg;
  char *s;
  char *cat;
  char *utype;
  struct connection *conn, *curr, *prev = NULL;
  struct ast_variable *v;
  int dupl = 0;
  char iabuf[INET_ADDRSTRLEN];

  cfg = ast_config_load(config);
  if (cfg) {
    if ((s = ast_variable_retrieve(cfg, "general", "midcomnat"))) {
      if (!strcasecmp(s, "nearend"))
	midcomnat = NEAREND;
      else if (!strcasecmp(s, "farend"))
	midcomnat = FAREND;
      else {
	midcomnat = NO_MIDCOM;
	midcom_enabled = 0;
      }
    }
    if ((s = ast_variable_retrieve(cfg, "general", "certificate"))) {
      ast_copy_string(cert, s, sizeof(cert));
    }
    if ((s = ast_variable_retrieve(cfg, "general", "lb"))) {
    }

    cat = ast_category_browse(cfg, NULL);
    while (cat) {
      if (strcasecmp(cat, "general")) {
	utype = ast_variable_retrieve(cfg, cat, "type");
	if (utype) {
	  if (!strcasecmp(utype, "midbox")) {
	    conn = (struct connection*)malloc(sizeof(struct connection));
	    if (!conn)
	      break;
	    memset(conn, 0, sizeof(struct connection));
	    ast_copy_string(conn->name, cat, sizeof(conn->name));
	    v = ast_variable_browse(cfg, cat);
	    while (v) {
	      if (!strcasecmp(v->name, "ip")) {
		inet_aton(v->value, &(conn->ipaddress));
	      } else if (!strcasecmp(v->name, "username")) {
		ast_copy_string(conn->username, v->value, sizeof(conn->username));
	      } else if (!strcasecmp(v->name, "password")) {
		ast_copy_string(conn->password, v->value, sizeof(conn->password));
	      } else if (!strcasecmp(v->name, "transport")) {
		if (!strcasecmp(v->value, "tls"))
		  conn->tls = 1;
	      } else if (!strcasecmp(v->name, "mode")) {
		if (!strcasecmp(v->value, "farend"))
		  conn->mode = FAREND;
		else
		  conn->mode = NEAREND;
	      }
	      v= v->next;
	    }

	    prev = NULL;
	    curr = connl;
	    while (curr) {
	      if (curr->ipaddress.s_addr == conn->ipaddress.s_addr) {
		dupl = 1;
		break;
	      }
	      prev = curr;
	      curr = curr->next;
	    }
	    if (!dupl) {
	      if (!prev)
		connl = conn;
	      else
		prev->next = conn;

	    } else {
	      ast_log(LOG_NOTICE, "Ignoring device '%s' as %s is already defined before.", cat, ast_inet_ntoa(iabuf, sizeof(iabuf), conn->ipaddress));
	    }
	  } else {
	    ast_log(LOG_WARNING, "Unknown type '%s' for '%s' in %s\n", utype, cat, config);
	  }
	} else
	  ast_log(LOG_WARNING, "Section '%s' lacks type\n", cat);
      }
      cat = ast_category_browse(cfg, cat);
    }
    ast_config_destroy(cfg);
  } else {
    ast_log(LOG_NOTICE, "Unable to load config %s, MIDCOM disabled\n", config);
    midcom_enabled = 0;
    return -1;
  }
  
  if (option_verbose > 1)
    ast_verbose(VERBOSE_PREFIX_2 "Midcom Reloaded, %s NAT being used.\n", (midcomnat == 0)? "No": ((midcomnat == 1)? "Near-End": "Far-End"));

  return 0;
}

static struct midcom_rule *midcom_rule_alloc(void *p)
{
  struct midcom_rule *r;
  r = malloc(sizeof(struct midcom_rule));
  if (r == NULL) {
    ast_log(LOG_NOTICE, "Not enough resources\n");
    return NULL;
  }
  memset(r, 0, sizeof(struct midcom_rule));
  r->novideo = sip_cb->ast_get_flag_novideo(p);
  sip_cb->ast_rtp_get_peer_audio_helper(p, &r->our_internal_addr);
  if (!r->novideo)
    sip_cb->ast_rtp_get_peer_video_helper(p, &r->our_internal_vaddr);
  return r;
}

static int midcom_handle_request_invite_hook(void *p)
{
  struct sockaddr_in caller;
  char iabuf[INET_ADDRSTRLEN];
  struct midcom_rule *r = NULL;

  if (!midcom_enabled) {
    ast_log(LOG_ERROR, "Midcom: Module not enabled\n");
    if (option_verbose > 1)
      ast_verbose("Midcom: Module currently disabled\n");
    return 1;
  }

  if (!sip_cb) {
    ast_log(LOG_ERROR, "Midcom: Module not initialized properly\n");
    if (option_verbose > 1)
       ast_verbose("Midcom: Error!! - Module not initialized\n");
    return 0;
  }

  if ((r = sip_cb->ast_get_hook_struct(p)))
    return 1;

  if (is_nearend_nat()) {
    sip_cb->ast_rtp_get_peer_audio_helper(p, &caller);

    if (sip_cb->ast_cmp_sa_addr(p, &caller)) { /* not forwarded from another softswitch */

      r = midcom_rule_alloc(p);
      if (r == NULL)
	return 0;
      /* sip_cb->ast_get_recv_addr(p, &r->midbox_addr); */
      if ( get_lb_midbox_ip(&r->midbox_addr) == -1)
	return 0;
      
      if (!r->nat_done) {
	if ((r->midbox_addr.s_addr) && (r->con = midcom_connect(r->midbox_addr, &r->lifetime, &r->is_cached)) != -1) {
	  if (midcom_reserve_nat(r->con, &r->ruleid, &r->our_natted_addr, /*r->enable_rtcp*/ 1, &r->lifetime) != 0) {
	    free(r);
	    return 0;
	  }

	  if (!r->novideo) {
	    if (midcom_reserve_nat(r->con, &r->vruleid, &r->our_natted_vaddr, /*r->enable_rtcp*/ 1, &r->lifetime) != 0) {
	      free(r);
	      return 0;
	    }
	  }
	  
	} else {
	  ast_log(LOG_NOTICE, "Failed to connect to %s\n", ast_inet_ntoa(iabuf, sizeof(iabuf), r->midbox_addr));
	  free(r);
	  return 0;
	}
      }
      
      r->nat_done = 1;

      ast_mutex_lock(&usecnt_lock);
      usecnt++;
      ast_mutex_unlock(&usecnt_lock);      
      ast_update_use_count();

      sip_cb->ast_map_hook_struct(p, (void *)r);
      r->p = p;
      r->next = midcom_rule_head;
      midcom_rule_head = r;

    } else {
      /* The caller might be forwarded from another softswitch */;
    }
  }
  else
    {
      /* farend */
      r = NULL;
    }
  return 1;
}

static int midcom_handle_response_invite_hook(void *p)
{
  char iabuf[INET_ADDRSTRLEN];
  struct midcom_rule *r = NULL;

  if (!midcom_enabled) {
    ast_log(LOG_ERROR, "Midcom: Module not enabled\n");
    if (option_verbose > 1)
       ast_verbose("Midcom: Module currently disabled\n");
    return 1;
  }

  if (!sip_cb) {
    ast_log(LOG_ERROR, "Midcom: Module not initialized properly\n");
    if (option_verbose > 1)
       ast_verbose("Midcom: Error!! - Module not initialized\n");
    return 0;
  }


  if ((r = sip_cb->ast_get_hook_struct(p)))
    return 1;

  r = midcom_rule_alloc(p);
  if (r == NULL)
    return 0;
  
  if (!r->username[0])
    strncpy(r->username, sip_cb->ast_get_username(p), 256);

  if (option_verbose > 1)
    ast_verbose("Midcom: Allocating new midcom rule for phone: %s\n", r->username);

  
  /* sip_cb->ast_get_recv_addr(p, &r->midbox_addr); */
  if ( get_lb_midbox_ip(&r->midbox_addr) == -1)
	return 0;

  if (is_nearend_nat())
    {
      if ((r->midbox_addr.s_addr) && (r->con = midcom_connect(r->midbox_addr, &r->lifetime, &r->is_cached)) != -1) {
	if (midcom_reserve_nat(r->con, &r->ruleid, &r->our_natted_addr, /*r->enable_rtcp*/ 1, &r->lifetime) != 0) {
	  ast_log(LOG_NOTICE, "Failed to NAT user %s for audio\n", r->username);
	  free(r);
	  return 0;
	}
	
	if (!r->novideo) {
	  if (midcom_reserve_nat(r->con, &r->vruleid, &r->our_natted_vaddr, /*r->enable_rtcp*/ 1, &r->lifetime) != 0) {
	    ast_log(LOG_NOTICE, "Failed to NAT user %s for video\n", r->username);
	    free(r);
	    return 0;
	  }
	}
	
	r->nat_done = 1;
      
      } else {
	ast_log(LOG_NOTICE, "Failed to connect to %s\n", ast_inet_ntoa(iabuf, sizeof(iabuf), r->midbox_addr));
	free(r);
	return 0;
      }
      
    }
  else 
    {
      /* farend */
      struct sockaddr_in sin;
      struct sockaddr_in vsin;
      int codec = 0;

      if ((r->midbox_addr.s_addr) && (r->con = midcom_connect(r->midbox_addr, &r->lifetime, &r->is_cached)) != -1) {
	sip_cb->ast_rtp_get_us_audio_helper(p, &sin);
	if (!r->novideo)
	  sip_cb->ast_rtp_get_us_video_helper(p, &vsin);
	
	codec = sip_cb->ast_get_capability_helper(p);
	if (midcom_open_firewall(r->con, &r->ruleid, &r->our_internal_addr, &sin, &r->lifetime, (codec & 0x0000FFFF)) != 0) {
	  free(r);
	  r = NULL;
	  return 0;
	}
	if (!r->novideo) {
	  if (midcom_open_firewall(r->con, &r->vruleid, &r->our_internal_vaddr, &vsin, &r->lifetime, (codec & 0xFFFF0000)) != 0) {
	    free(r);
	    r = NULL;
	    return 0;
	  }
	}

	r->firewall_done = 1;
	midcom_disconnect(r->con, r->is_cached);
	timer_setup_tick(r, sizeof(struct midcom_rule), r->lifetime, &r->timer_handle);

      } else {
		  ast_log(LOG_NOTICE, "Failed to connect to %s\n", ast_inet_ntoa(iabuf, sizeof(iabuf), r->midbox_addr));
		  free(r);
		  return 0;
      }
    
    }

  ast_mutex_lock(&usecnt_lock);
  usecnt++;
  ast_mutex_unlock(&usecnt_lock);
  ast_update_use_count();
  
  sip_cb->ast_map_hook_struct(p, (void *)r);
  r->p = p;
  r->next = midcom_rule_head;
  midcom_rule_head = r;
  
  return 1;
}

static int midcom_transmit_response_with_sdp_hook(void *p)
{
  char iabuf[INET_ADDRSTRLEN];
  struct midcom_rule *r = NULL;
  struct sockaddr_in sin;
  struct sockaddr_in vsin;
  int codec = 0;

  if (!midcom_enabled) {
    ast_log(LOG_ERROR, "Midcom: Module not enabled\n");
    if (option_verbose > 1)
      ast_verbose("Midcom: Module currently disabled\n");
    return 1;
  }

  if (!sip_cb) {
    ast_log(LOG_ERROR, "Midcom: Module not initialized properly\n");
    if (option_verbose > 1)
       ast_verbose("Midcom: Error!! - Module not initialized\n");
    return 0;
  }

  if ((r = sip_cb->ast_get_hook_struct(p)))
    return 1;

  if (is_farend_nat())
    {
      r = midcom_rule_alloc(p);
      if (r == NULL)
	return 0;
      
      if (!r->username[0])
		  strncpy(r->username, sip_cb->ast_get_username(p), 256);

      if (option_verbose > 1)
        ast_verbose("Midcom: Allocating new rule for phone: %s\n", r->username);
      /* sip_cb->ast_get_recv_addr(p, &r->midbox_addr); */
      if ( get_lb_midbox_ip(&r->midbox_addr) == -1)
        return 0;
		
      if ((r->midbox_addr.s_addr) && (r->con = midcom_connect(r->midbox_addr, &r->lifetime, &r->is_cached)) != -1) {
		  
		  sip_cb->ast_rtp_get_us_audio_helper(p, &sin);
		  if (!r->novideo)
			 sip_cb->ast_rtp_get_us_video_helper(p, &vsin);
		  
		  codec = sip_cb->ast_get_capability_helper(p);
		  if (midcom_open_firewall(r->con, &r->ruleid, &r->our_internal_addr, &sin, &r->lifetime, (codec & 0x0000FFFF)) != 0) {
                         if (option_verbose > 1)
			   ast_verbose("Midcom: Failed to open dynamic pinhole for Audio stream from phone: %s\n", r->username); 
			 free(r);
			 r = NULL;
			 return 0;
		  }
		  if (!r->novideo) {
			 if (midcom_open_firewall(r->con, &r->vruleid, &r->our_internal_vaddr, &vsin, &r->lifetime, (codec & 0xFFFF0000)) != 0) {
                                if (option_verbose > 1)
			           ast_verbose("Midcom: Failed to open dynamic pinhole for Video stream from phone: %s\n", r->username); 
				free(r);
				r = NULL;
				return 0;
			 }
		  }
      } else {
		  ast_log(LOG_NOTICE, "Failed to connect to %s\n", ast_inet_ntoa(iabuf, sizeof(iabuf), r->midbox_addr));
		  free(r);
		  return 0;
      }
      
      r->firewall_done = 1;
      midcom_disconnect(r->con, r->is_cached);
      timer_setup_tick(r, sizeof(struct midcom_rule), r->lifetime, &r->timer_handle);

      ast_mutex_lock(&usecnt_lock);
      usecnt++;
      ast_mutex_unlock(&usecnt_lock);
      ast_update_use_count();
      
      sip_cb->ast_map_hook_struct(p, (void *)r);
      r->p = p;
      r->next = midcom_rule_head;
      midcom_rule_head = r;
    }
  return 1;
}

static int midcom_transmit_reinvite_with_sdp_hook(void *p)
{
  char iabuf[INET_ADDRSTRLEN], iabuf1[INET_ADDRSTRLEN];
  struct ast_channel *bridgepeer = NULL;
  struct midcom_rule *r = NULL;
  int res = -1;
  int codec = 0;
  struct midcom_rule *peer=NULL;
  int is_farend = 0;
  char *username = NULL;

  if (!midcom_enabled) {
    ast_log(LOG_ERROR, "Midcom: Module not enabled\n");
    if (option_verbose > 1)
      ast_verbose("Midcom: Module currently disabled\n");
    return 1;
  }

  if (!sip_cb) {
    ast_log(LOG_ERROR, "Midcom: Module not initialized properly\n");
    if (option_verbose > 1)
      ast_verbose("Midcom: Error!! - Module not initialized\n");
    return 0;
  }

  if (!(r = sip_cb->ast_get_hook_struct(p))) {

    username = sip_cb->ast_get_username(p);
    ast_log(LOG_ERROR, "Midcom: Unable to retrieve Midcom Rule for phone: %s\n", username);
    if (option_verbose > 1)
      ast_verbose("Midcom: Error!! - Unable to retrieve Midcom Rule for phone: %s\n", username);
    return 0;
  }

  is_farend = is_farend_nat();

  if (is_farend)
    {
      if (!r->nat_done)
	{
	  int first_phone = 0;

	  if ((r->midbox_addr.s_addr) && (r->con = midcom_connect(r->midbox_addr, &r->lifetime, &r->is_cached)) != -1) {
	    
	    timer_cancel_tick(r, sizeof(struct midcom_rule));
	  
	    midcom_delete_rule(r->con, r->ruleid);
	    if (!r->novideo)
	      midcom_delete_rule(r->con, r->vruleid);
	    r->firewall_done = 0;

	    r->channel = sip_cb->ast_channel_helper(p);
	    bridgepeer = sip_cb->ast_bridged_channel_helper(p);
	    first_phone = get_midbox_ip(bridgepeer, &r->midbox_addr, &r->remote_addr, &r->remote_vaddr);
	    if (first_phone == -1) {
	      return 0;
	    }
	    
            peer = get_peer_rule(r);
            if (peer == NULL)
		return 0;

	    if (midcom_reserve_nat(r->con, &r->ruleid, &r->our_natted_addr, /*r->enable_rtcp*/ 1, &r->lifetime) != 0) {
	      ast_log(LOG_NOTICE, "Failed to NAT user %s for audio\n", r->username);
              if (option_verbose > 1)
	         ast_verbose("Midcom: Failed to acquire NAT IP for user %s for audio\n", r->username);
	      return 0;
	    }

	    if ((!r->novideo) && (!peer->novideo)) {
	      if (midcom_reserve_nat(r->con, &r->vruleid, &r->our_natted_vaddr, /*r->enable_rtcp*/ 1, &r->lifetime) != 0) {
		ast_log(LOG_NOTICE, "Failed to NAT user %s for video\n", r->username);
                if (option_verbose > 1)
		  ast_verbose("Midcom: Failed to acquire NAT IP for user %s for video\n", r->username);
		return 0;
	      }
	    }
            else
              /* if the peer does not support video, no need for video on our side too */
              r->novideo = 1; 
	  } else {
	    ast_log(LOG_NOTICE, "Failed to connect to %s\n", ast_inet_ntoa(iabuf, sizeof(iabuf), r->midbox_addr));
	    return 0;
	  }

	  r->nat_done = 1;

          if (option_verbose > 1)
            ast_verbose("Midcom: Acquired NAT IP: %s port: %d for phone %s audio stream with Originating IP: %s port: %d\n", ast_inet_ntoa(iabuf1, sizeof(iabuf1), r->our_natted_addr.sin_addr),  ntohs(r->our_natted_addr.sin_port), r->username, ast_inet_ntoa(iabuf, sizeof(iabuf), r->our_internal_addr.sin_addr), ntohs(r->our_internal_addr.sin_port));

	  if (!r->novideo) {
             if (option_verbose > 1)
               ast_verbose("Midcom: Acquired NAT IP: %s port: %d for phone %s video stream with Originating IP: %s port: %d\n", ast_inet_ntoa(iabuf1, sizeof(iabuf1), r->our_natted_vaddr.sin_addr),  ntohs(r->our_natted_vaddr.sin_port), r->username, ast_inet_ntoa(iabuf, sizeof(iabuf), r->our_internal_vaddr.sin_addr), ntohs(r->our_internal_vaddr.sin_port));
          }
	  if (first_phone)
	    {
	      /*
	      bridgepeer->midbox = r->midbox_addr;
	      bridgepeer->peer_internal_addr = r->our_natted_addr;
	      bridgepeer->peer_internal_vaddr = r->our_natted_vaddr;
	      */
	      r->firewall_done = 1; /* no bridging for the first phone */
	      midcom_disconnect(r->con, r->is_cached);
	      timer_setup_tick(r, sizeof(struct midcom_rule), r->lifetime, &r->timer_handle);
	    }
	} /* (!r->nat_done) */

      r->redirip_nat = r->our_natted_addr;
      r->vredirip_nat = r->our_natted_vaddr;

    }

  if (!r->username[0])
    strncpy(r->username, sip_cb->ast_get_username(p), 256);

  if (!r->firewall_done)
    {
      r->firewall_done = 1;

      if ((r->midbox_addr.s_addr) && (r->con != -1))
	{
	  codec = sip_cb->ast_get_capability_helper(p);
	  if (is_farend)
	    res = midcom_bridge_firewall(r->con, r->ruleid, &r->our_natted_addr, &r->remote_addr, &r->lifetime, (codec & 0x0000FFFF));
	  else
	    res = midcom_add_firewall(r->con, r->ruleid, &r->redirip_nat, &r->our_internal_addr, &r->lifetime, (codec & 0x0000FFFF));

	  if (res != 0)
	    {
	      /* free r */
              if (peer && is_farend) {
                 if (option_verbose > 1)
                     ast_verbose("Midcom: Failed to Bridge Audio streams of phone %s with IP: %s port %d and Phone %s with IP; %s port %d\n", r->username, ast_inet_ntoa(iabuf1, sizeof(iabuf1), r->our_natted_addr.sin_addr),  ntohs(r->our_natted_addr.sin_port), peer->username, ast_inet_ntoa(iabuf, sizeof(iabuf), peer->our_natted_addr.sin_addr), ntohs(peer->our_natted_addr.sin_port));
              }
	      return 0;
	    }

          if (peer && is_farend) {
             if (option_verbose > 1)
                 ast_verbose("Midcom: Bridging Audio streams of phone %s with IP: %s port %d and Phone %s with IP; %s port %d\n", r->username, ast_inet_ntoa(iabuf1, sizeof(iabuf1), r->our_natted_addr.sin_addr),  ntohs(r->our_natted_addr.sin_port), peer->username, ast_inet_ntoa(iabuf, sizeof(iabuf), peer->our_natted_addr.sin_addr), ntohs(peer->our_natted_addr.sin_port));
          }

	  if (!r->novideo) 
	    {
	      if (is_farend)
		res = midcom_bridge_firewall(r->con, r->vruleid, &r->our_natted_vaddr, &r->remote_vaddr, &r->lifetime, (codec & 0xFFFF0000));
	      else
		res = midcom_add_firewall(r->con, r->vruleid, &r->vredirip_nat, &r->our_internal_vaddr, &r->lifetime, (codec & 0xFFFF0000));

	      if (res != 0)
		{
		  /* free r */
	          if (peer && is_farend) {
                     if (option_verbose > 1)
                        ast_verbose("Midcom: Failed to Bridge Video streams of phone %s with IP: %s port %d and Phone %s with IP; %s port %d\n", r->username, ast_inet_ntoa(iabuf1, sizeof(iabuf1), r->our_natted_vaddr.sin_addr),  ntohs(r->our_natted_vaddr.sin_port), peer->username, ast_inet_ntoa(iabuf, sizeof(iabuf), peer->our_natted_vaddr.sin_addr), ntohs(peer->our_natted_vaddr.sin_port));
                  }
		  return 0;
		}
	    }
	  if (!r->novideo && peer && is_farend) {
             if (option_verbose > 1)
                ast_verbose("Midcom: Bridging Video streams of phone %s with IP: %s port %d and Phone %s with IP; %s port %d\n", r->username, ast_inet_ntoa(iabuf1, sizeof(iabuf1), r->our_natted_vaddr.sin_addr),  ntohs(r->our_natted_vaddr.sin_port), peer->username, ast_inet_ntoa(iabuf, sizeof(iabuf), peer->our_natted_vaddr.sin_addr), ntohs(peer->our_natted_vaddr.sin_port));
          }
	  midcom_disconnect(r->con, r->is_cached);
	  timer_setup_tick(r, sizeof(struct midcom_rule), r->lifetime, &r->timer_handle);
	}
      else
	{
	  /* free r */
	  return 0;
	}
    }
  return 1;
}

static int midcom__sip_destroy_hook(void *p)
{
  struct midcom_rule *s, *r, *prev = NULL;
  char *username=NULL;

  if (!midcom_enabled) {
    ast_log(LOG_ERROR, "Midcom: Module not enabled\n");
    if (option_verbose > 1)
      ast_verbose("Midcom: Module currently disabled\n");
    return 1;
  }

  if (!sip_cb) {
    ast_log(LOG_ERROR, "Midcom: Module not initialized properly\n");
    if (option_verbose > 1)
      ast_verbose("Midcom: Error!! - Module not initialized\n");
    return 0;
  }

  if (!(s = sip_cb->ast_get_hook_struct(p))) {

    username = sip_cb->ast_get_username(p);
    if (username && username[0]) {
      ast_log(LOG_ERROR, "Midcom: Unable to retrieve Midcom Rule for phone: %s\n", username);
      if (option_verbose > 1)
        ast_verbose("Midcom: Error!! - Unable to retrieve Midcom Rule for phone: %s\n", username);
    }
    return 1;
  }

  r = midcom_rule_head;
  while (r && (r != s)) {
    prev = r;
    r = r->next;
  }

  if (!r) /* node not found */
    return 0;

  if (!prev)
    midcom_rule_head = r->next;
  else
    prev->next = r->next;

  if ((!r->invalid) && (r->nat_done) && (!r->bye_done)) {
    if (r->firewall_done) {
      timer_cancel_tick(r, sizeof(struct midcom_rule));
      r->con = midcom_connect(r->midbox_addr, &r->lifetime, &r->is_cached);
    }
    if (r->con != -1) {
      if (option_verbose > 1)
         ast_verbose("Midcom: Unbridging Audio media and destroying midcom rule for phone: %s\n", r->username);
      midcom_delete_rule(r->con, r->ruleid);
      if (!r->novideo) {
         if (option_verbose > 1)
            ast_verbose("Midcom: Unbridging Video media and destroying midcom rule for phone: %s\n", r->username);
	 midcom_delete_rule(r->con, r->vruleid);
      }
      midcom_disconnect(r->con, r->is_cached);
    }
    r->bye_done = 1;
  }
  free(r);

  ast_mutex_lock(&usecnt_lock);
  usecnt--;
  ast_mutex_unlock(&usecnt_lock);
  ast_update_use_count();

  return 1;
}

static void midcom_rtp_nat_us_audio_hook(struct ast_rtp *rtp, void *r)
{
  if (!midcom_enabled)
    return;
  if (is_nearend_nat())
    ast_rtp_nat_us(rtp, &((struct midcom_rule*)r)->our_natted_addr);
}

static void midcom_rtp_nat_us_video_hook(struct ast_rtp *rtp, void *r)
{
  if (!midcom_enabled)
    return;
  if (is_nearend_nat())
    ast_rtp_nat_us(rtp, &((struct midcom_rule*)r)->our_natted_vaddr);
}

static void  midcom_get_redirip_audio_hook(void *r, struct sockaddr_in *redirip)
{
  if (!midcom_enabled)
    return;
  memcpy(redirip, &((struct midcom_rule*)r)->redirip_nat, sizeof(struct sockaddr_in));
}

static void  midcom_get_vredirip_video_hook(void *r, struct sockaddr_in *vredirip)
{
  if (!midcom_enabled)
    return;
  memcpy(vredirip, &((struct midcom_rule*)r)->vredirip_nat, sizeof(struct sockaddr_in));
}

static void midcom_rtp_get_their_nat_audio_hook(struct ast_rtp *rtp, void *r)
{
  if (!midcom_enabled)
    return;
  if (is_nearend_nat())
    ast_rtp_get_their_nat(rtp, &((struct midcom_rule*)r)->redirip_nat);
}

static void midcom_rtp_get_their_nat_video_hook(struct ast_rtp *vrtp, void *r)
{
  if (!midcom_enabled)
    return;
  if (is_nearend_nat())
    ast_rtp_get_their_nat(vrtp, &((struct midcom_rule*)r)->vredirip_nat);
}

static void midcom_connect_all()
{
  struct connection *conn;
  char iabuf[INET_ADDRSTRLEN];

  conn = connl;
  while (conn) {
    ast_inet_ntoa(iabuf, sizeof(iabuf), conn->ipaddress);
    if ((conn->sock = mc_comm_connect(iabuf)) == -1)
      ast_log(LOG_WARNING, "Could not connect to middlebox %s\n", iabuf);
    else
      ast_log(LOG_NOTICE, "Connected to middlebox %s\n", iabuf);
	 /*
    if ((conn->pingsock = mc_comm_connect(iabuf)) == -1)
      timer_setup_tick(conn, sizeof(struct connection), DEFAULT_FREQ_NOTOK, &conn->timer_handle);
    else      
      timer_setup_tick(conn, sizeof(struct connection), DEFAULT_FREQ_OK, &conn->timer_handle);
	 */
    conn = conn->next;
  }

  return;
}

static int midcom_connect(struct in_addr midbox, int *lifetime, int *cached)
{
  int con = -1;
  char iabuf[INET_ADDRSTRLEN];
  char *login = "root";
  char *password = "ranchroot";
  int res = -1;
  struct connection *curr;
  int ret;

  curr = connl;
  while (curr) {
    if (midbox.s_addr == curr->ipaddress.s_addr)
      break;
	 curr = curr->next;
  }

  if (curr && (curr->sock >= 0)) {
    if ((ret = simco_se_b(curr->sock)) == 0) {
      if ((ret = simco_sa_b(curr->sock, curr->username, curr->password)) == 0) {
		  *cached = 1;
		  *lifetime = 600; /* to be removed */
		  return curr->sock;
      }
		else
		  ast_log(LOG_WARNING, "Connection closed while authenticating session error=0x%x\n", ret);
    }
    else
      ast_log(LOG_WARNING, "Connection closed while establishing session error=0x%x\n", ret);
  }
  else
	 ast_log(LOG_WARNING, "Connection stale \n");

  ast_inet_ntoa(iabuf, sizeof(iabuf), midbox);
  ast_log(LOG_NOTICE, "Re-opening new connection to %s!!!\n", iabuf);

  if (curr)
    curr->sock = -1;

  if ((con = mc_comm_connect(iabuf)) == -1) {
    ast_log(LOG_WARNING, "Midcom: Could not connect to middlebox %s\n", iabuf);
    *cached = 0;
    return -1;
  }

  if (simco_se_b(con) != 0)
    {
      ast_log(LOG_WARNING, "Midcom: Could not establish new session %s\n", iabuf);
      mc_comm_disconnect(con);
      *cached = 0;
      return -1;
    }

  if (curr) {
    res = simco_sa_b(con, curr->username, curr->password);
  } else {
    res = simco_sa_b(con, login, password);
  }
    
  if (res != 0)
    {
      ast_log(LOG_WARNING, "Midcom: Could not authenticate on %s \n", iabuf);
      simco_st_b(con);
      mc_comm_disconnect(con);
      *cached = 0;
      return -1;
    }

  if (curr) {
    curr->sock = con;
    *cached = 1;
  } else {
    *cached = 0;
  }

  *lifetime = 600; /* to be removed */
  return con;
}

static int midcom_change_lifetime(int con, int rule, int *lifetime)
{
  int ret = 0;
  simcoPacket_t spacket;

  memset((void*)&spacket, 0, sizeof(simcoPacket_t));
  spacket.ruleId = rule;
  spacket.lifetime = *lifetime;
  if ((ret = simco_plc_b(con, &spacket)) != 0)
    {
      ast_log(LOG_WARNING, "Midcom: Error - simco_plc_b: 0x%x\n", ret);
      return -1;
    }
  *lifetime = spacket.lifetime;
  return 0;
}

static int midcom_reserve_nat(int con, int *rule, struct sockaddr_in *nat_addr, int enable_rtcp, int *lifetime)
{
  int ret = 0;
  simcoPacket_t spacket;

  memset((void*)&spacket, 0, sizeof(simcoPacket_t));
  spacket.lifetime = *lifetime;
  /*if (enable_rtcp) */
    spacket.nat_port_range = 2;
  if ((ret = simco_prr_b(con, &spacket)) != 0)
    {
      ast_log(LOG_WARNING, "Midcom: Error - simco_prr_b: 0x%x\n", ret);
      return -1;
    }
  ast_log(LOG_DEBUG, "Midcom: PRR: spacket.ruleId=%d, spacket.outside_ip=%s, spacket.outside_port=%d\n",
	  spacket.ruleId, spacket.outside_ip, spacket.outside_port);
  *lifetime = spacket.lifetime;
  inet_aton(spacket.outside_ip, &nat_addr->sin_addr);
  nat_addr->sin_port = htons(spacket.outside_port);
  *rule = spacket.ruleId;
  return 0;
}

static int midcom_open_firewall(int con, int *rule, struct sockaddr_in *their_external, struct sockaddr_in *our_internal, int *lifetime, int codec)
{
  int ret = 0;
  simcoPacket_t spacket;
  char iabuf[INET_ADDRSTRLEN];

  memset((void*)&spacket, 0, sizeof(simcoPacket_t));
  ast_inet_ntoa(iabuf, sizeof(iabuf), our_internal->sin_addr);
  strncpy(spacket.internal_ip, iabuf, 16);
  spacket.internal_port = ntohs(our_internal->sin_port);
  ast_inet_ntoa(iabuf, sizeof(iabuf), their_external->sin_addr);
  strncpy(spacket.external_ip, iabuf, 16);
  spacket.external_port = ntohs(their_external->sin_port);
  spacket.lifetime = *lifetime;
  spacket.codec = codec;
  if ((ret = simco_per_b(con, &spacket)) != 0)
    {
      ast_log(LOG_WARNING, "Error - simco_per_b: 0x%x\n", ret);
    }
  ast_log(LOG_DEBUG, "PER: spacket.ruleId=%d, spacket.internal_ip=%s, spacket.internal_port=%d, spacket.external_ip=%s, spacket.external_port=%d\n",
	  spacket.ruleId, spacket.internal_ip, spacket.internal_port, spacket.external_ip, spacket.external_port);
  *lifetime = spacket.lifetime;
  *rule = spacket.ruleId;
  return 0;
}

static int midcom_add_firewall(int con, int rule, struct sockaddr_in *their_external, struct sockaddr_in *our_internal, int *lifetime, int codec)
{
  int ret = 0;
  simcoPacket_t spacket;
  char iabuf[INET_ADDRSTRLEN];

  memset((void*)&spacket, 0, sizeof(simcoPacket_t));
  spacket.ruleId = rule;
  ast_inet_ntoa(iabuf, sizeof(iabuf), our_internal->sin_addr);
  strncpy(spacket.internal_ip, iabuf, 16);
  spacket.internal_port = ntohs(our_internal->sin_port);
  ast_inet_ntoa(iabuf, sizeof(iabuf), their_external->sin_addr);
  strncpy(spacket.external_ip, iabuf, 16);
  spacket.external_port = ntohs(their_external->sin_port);
  spacket.lifetime = *lifetime;
  spacket.codec = codec;
  if ((ret = simco_pea_b(con, &spacket)) != 0)
    {
      ast_log(LOG_WARNING, "Error - simco_pea_b: 0x%x\n", ret);
    }
  ast_log(LOG_DEBUG, "PEA: spacket.ruleId=%d, spacket.internal_ip=%s, spacket.internal_port=%d, spacket.external_ip=%s, spacket.external_port=%d\n",
	  spacket.ruleId, spacket.internal_ip, spacket.internal_port, spacket.external_ip, spacket.external_port);
  *lifetime = spacket.lifetime;
  return 0;
}


static int midcom_bridge_firewall(int con, int rule, struct sockaddr_in *our_external, struct sockaddr_in *their_external, int *lifetime, int codec)
{
  int ret = 0;
  simcoPacket_t spacket;
  char iabuf[INET_ADDRSTRLEN];

  memset((void*)&spacket, 0, sizeof(simcoPacket_t));
  spacket.ruleId = rule;
  ast_inet_ntoa(iabuf, sizeof(iabuf), their_external->sin_addr);
  strncpy(spacket.internal_ip, iabuf, 16);
  spacket.internal_port = ntohs(their_external->sin_port);
  ast_inet_ntoa(iabuf, sizeof(iabuf), our_external->sin_addr);
  strncpy(spacket.external_ip, iabuf, 16);
  spacket.external_port = ntohs(our_external->sin_port);
  spacket.lifetime = *lifetime;
  spacket.codec = codec;
  if ((ret = simco_pba_b(con, &spacket)) != 0)
    {
      ast_log(LOG_WARNING, "Error - simco_pba_b: 0x%x\n", ret);
      return -1;
    }
  ast_log(LOG_DEBUG, "PBA: spacket.ruleId=%d, spacket.internal_ip=%s, spacket.internal_port=%d, spacket.external_ip=%s, spacket.external_port=%d\n", 
	  spacket.ruleId, spacket.internal_ip, spacket.internal_port, spacket.external_ip, spacket.external_port);
  *lifetime = spacket.lifetime;
  return 0;
}

static int midcom_delete_rule(int con, int rule)
{
  int ret = 0;
  simcoPacket_t spacket;

  memset((void*)&spacket, 0, sizeof(simcoPacket_t));
  spacket.ruleId = rule;
  spacket.lifetime = 0;
  if ((ret = simco_plc_b(con, &spacket)) != 0)
    {
      ast_log(LOG_WARNING, "Midcom: Error - simco_plc_b: 0x%x\n", ret);
      return -1;
    }
  return 0;
}

static void midcom_disconnect(int con, int cached)
{
  if (con != -1)
    {
      simco_st_b(con);
      if (!cached) {
		  ast_log(LOG_NOTICE, "Closing connection to midbox\n");
		  mc_comm_disconnect(con);
      }
    }
}

static timer_task_context_t* timer_context_create(void)
{
  timer_context.task_name = "midcom";
  if (sysInstallTimerManager(1000, &timer_context) == 0)
    return &timer_context;
  return NULL;
}

static void timer_context_delete(timer_task_context_t *ttc)
{
  sysUninstallTimerManager(ttc);
}

static int timer_setup_tick(void *data, size_t datalen, unsigned int timeout, timer_id_t *handleP)
{
  sysTimer_t tickTimer;
  memset(&tickTimer, 0, sizeof(sysTimer_t));
  if (datalen == sizeof(struct midcom_rule))
    timeout -= SAFE_INTERVAL;
  tickTimer.initialInterval = timeout*1000;
  tickTimer.context = data;
  tickTimer.contextSize = datalen;
  tickTimer.handler = timer_tick_handler;
  tickTimer.cleanup = 0;
  if ((*handleP = sysSetTimer(timer, &tickTimer)))
    return 0;
  if (datalen == sizeof(struct midcom_rule))
    ast_log(LOG_WARNING, "sysSetTimer failed for %s\n", ((struct midcom_rule*)data)->username);
  else
    ast_log(LOG_WARNING, "sysSetTimer failed\n");
  return -1;
}

static int timer_change_tick(void *data, size_t datalen, unsigned int new_timeout)
{
  if (datalen == sizeof(struct midcom_rule)) {

    struct midcom_rule *r;

    r = (struct midcom_rule*)data;
    ast_log(LOG_DEBUG, "Change Timer for %s\n", r->username);
    if (r->timer_handle)
      sysCancelTimer(timer, r->timer_handle);
    timer_setup_tick(data, datalen, new_timeout, &r->timer_handle);

  } else if (datalen == sizeof(struct connection)) {

    struct connection *c;

    c = (struct connection*)data;
    if (c->timer_handle)
      sysCancelTimer(timer, c->timer_handle);
    timer_setup_tick(data, datalen, new_timeout, &c->timer_handle);

  } else
    return -1;

  return 0;
}

static int timer_cancel_tick(void *data, size_t datalen)
{
  if (datalen == sizeof(struct midcom_rule)) {

    struct midcom_rule *r;

    r = (struct midcom_rule*)data;
    if (r->timer_handle) {
      ast_log(LOG_DEBUG, "Cancel Timer for %s\n", r->username);
      sysCancelTimer(timer, r->timer_handle);
    }

  } else if (datalen == sizeof(struct connection)) {

    struct connection *c;

    c = (struct connection*)data;
    if (c->timer_handle)
      sysCancelTimer(timer, c->timer_handle);

  } else
    return -1;

  return 0;
}

static int timer_tick_handler(void *argptr, size_t arglen)
{
  if (arglen == sizeof(struct midcom_rule))
    {
      struct midcom_rule *r;
      int lifetime, ret, vret;

      r = (struct midcom_rule*)argptr;
      if (!r->bye_done)
	{
	  if ((r->midbox_addr.s_addr) && (r->con = midcom_connect(r->midbox_addr, &r->lifetime, &r->is_cached)) != -1)
	    {
	      lifetime = 600;
	      ret = midcom_change_lifetime(r->con, r->ruleid, &lifetime);
	      if (!r->novideo)
		{
		  lifetime = 600;
		  vret = midcom_change_lifetime(r->con, r->vruleid, &lifetime);
		}
	      else 
		vret = 0;
	      if (!ret && !vret)
		timer_change_tick(argptr, arglen, lifetime);
	      midcom_disconnect(r->con, r->is_cached);
	    }
	  else
	    {
	      timer_cancel_tick(argptr, arglen);
	      r->invalid = 1; /* this rule would automatically expire on server */
	    }
	}
      return 0;
    }
  
  if (arglen == sizeof(struct connection))
    {
      struct connection *c;
      char iabuf[INET_ADDRSTRLEN];

      c = (struct connection*)argptr;
      if (c && (c->pingsock >= 0)) {
		  if (simco_ping_b(c->pingsock) == 0) {
			 timer_change_tick(argptr, arglen, DEFAULT_FREQ_OK);
			 return 0;
		  }
      }

      ast_inet_ntoa(iabuf, sizeof(iabuf), c->ipaddress);
      if ((c->pingsock = mc_comm_connect(iabuf)) != -1) {
		  ast_log(LOG_DEBUG, "Re-opened connection %s.\n", iabuf);
		  timer_change_tick(argptr, arglen, DEFAULT_FREQ_OK);
		  return 0;
		}
      ast_log(LOG_NOTICE, "Connection to %s lost. Retrying...\n", iabuf);
      timer_change_tick(argptr, arglen, DEFAULT_FREQ_NOTOK);
      return 0;
    }

  return -1;
}

static int get_lb_midbox_ip(struct in_addr *ip)
{
  if (connl) {
    memcpy(ip, &connl->ipaddress, sizeof(struct in_addr));
    return 0;
  }
  return -1;
}

static int get_midbox_ip(void *bridge, struct in_addr *ip, struct sockaddr_in *addr, struct sockaddr_in *vaddr)
{
  struct midcom_rule *r;

  r = midcom_rule_head;
  while (r) {
    if (r->channel == bridge) {
      memcpy(ip, &r->midbox_addr, sizeof(struct in_addr));
      memcpy(addr, &r->our_natted_addr, sizeof(struct sockaddr_in));
      memcpy(vaddr, &r->our_natted_vaddr, sizeof(struct sockaddr_in));
      return 0;
    }
    r = r->next;
  }
  if (get_lb_midbox_ip(ip)) {
    ast_log(LOG_WARNING, "No farend midbox available\n");
    return -1;
  }
  return 1;
}

static int is_nearend_nat(void)
{
  return (midcomnat == NEAREND);
}

static int is_farend_nat(void)
{
  return (midcomnat == FAREND);
}


static struct midcom_rule *get_peer_rule(struct midcom_rule *r)
{
  struct ast_channel *peer_chan;
  struct midcom_rule *peer_rule=NULL;

  if (r && r->p) { /* pure paranoia */

     peer_chan = sip_cb->ast_bridged_channel_helper(r->p);
     if (peer_chan && peer_chan->tech_pvt)
          peer_rule = sip_cb->ast_get_hook_struct(peer_chan->tech_pvt);
  }
  return peer_rule;
}
