/*

 esp_route.c

 Author: Liraz Siri <liraz@bigfoot.com>

 Copyright (c) 1998 Liraz Siri <liraz@bigfoot.com> Ariel, Israel.
                    All rights reserved.

 Created: Sun Sep 27 12:00:49 GMT 1998
 Updated: Mon Sep 28 11:56:01 GMT 1998
 Updated: Mon Oct  5 05:03:04 GMT 1998

 Packet router mechanism. ssh_esp extension.
*/

#include <sys/types.h>
#include <sys/socket.h>

#include "esp_route.h"
#include "buffer.h"
#include "shmalloc.h"

static Buffer transmit_packet;

struct proto {
 int protocol;
 int fd;

 /*-- proto state. neutral: 0, pending_sockopen: 1, pending_sockclose: -1 --*/
 int state;
};

struct rule {
 int		initialized;
 routerule	rule;
};

static int routing_supported = 0;
static int *route_rules, *update_protos;
static struct rule *rulebook;
static struct proto *protos;

/************************************
 * PRIVATE routine implementations: *
 ************************************/


/*-- Find the slot in the protos area matching the protocol proto --*/
int esp_route_find_proto(u_char proto)
{
 int i;
 for(i = 0; i<MAX_PROTOS; i++) 
  if(protos[i].protocol == proto) return i;
 return -1;
}

/*-- Try to find the slot of a rule transmitting with protocol 'proto', 
     return -1 if failed to find such a rule --*/
int esp_route_find_rule_proto(u_char proto)
{
 int i;
 for(i = 0; i<ROUTE_RULES; i++) 
  if(rulebook[i].initialized)
   if(rulebook[i].rule.dest.protocol == proto) return i;
 return -1;
}

/*-- Remove an obsolete protocol socket (used for transmission) --*/
int esp_route_rm_proto(u_char proto)
{
 int proto_slot;

 /*-- don't remove protocol if any existing rules are still using it --*/
 if(esp_route_find_rule_proto(proto) == -1 && 
   (proto_slot = esp_route_find_proto(proto)) != -1)
 {
  protos[proto_slot].protocol = -1;
  protos[proto_slot].state = -1;
  *update_protos = 1;
  return 1;
 }

 return -1;
}

void print_pkt(pktrec *pktrec, int i)
{
 debug("pktrec->protocol = %d.", pktrec->protocol);
 debug("pktrec->sin_addr.s_addr = %u.", pktrec->sin_addr.s_addr);
 debug("pktrec->u.srcport = %u.", ntohs(pktrec->srcport));
 debug("pktrec->u.dstport = %u.", ntohs(pktrec->dstport));
}

/* Returns true if pktrec1 and pktrec2 match, otherwise returns false */
int cmp_pktrec(pktrec *pktrec1, pktrec *pktrec2)
{
 if(pktrec1->sin_addr.s_addr != pktrec2->sin_addr.s_addr ||
    pktrec1->protocol != pktrec2->protocol ||
    pktrec1->srcport != pktrec2->srcport ||
    pktrec1->dstport != pktrec2->dstport) return 0;
  else return 1;
}

/*-- Find the slot of a rule with a matching source field --*/
int esp_route_find_rule(pktrec *source)
{
 int i;
 for(i = 0; i<ROUTE_RULES; i++) 
  if(rulebook[i].initialized)
   if(cmp_pktrec(&(rulebook[i].rule.source), source))
    return i;
 return -1;
}

/*-- do the real socket open close in the father process which does
     the actual routing --*/
void update_protocols(void)
{
 int i;

 for(i = 0; i<MAX_PROTOS; i++) 
  if(protos[i].state != 0)
  {
   switch(protos[i].state) {
    case 1	:
        protos[i].fd = socket(AF_INET, SOCK_RAW, protos[i].protocol);
	break;
    case -1	: 
        close(protos[i].fd);
	break;
   }
   protos[i].state = 0;
  }

}

/*-- Get rid of the unused routing sockets in a child process --*/
void esp_route_close_sockets(void)
{
 int i;
 for(i = 0; i < MAX_PROTOS; i++)
  if(protos[i].protocol != -1 && protos[i].state != 1)
   close(protos[i].fd);
}

/* Return 0 if already initialized, 1 if initialized successfully, and -1
   on socket failure/no more space in protos array */
int esp_route_initproto(u_char proto)
{
 int sock, i;

 if(esp_route_find_proto(proto) != -1) 
  return 0;

 for(i = 0; i<MAX_PROTOS; i++)
  if(protos[i].protocol == -1)

/* we only open the socket to see if we support it's type. */
   if((sock = socket(AF_INET, SOCK_RAW, (int)proto)) != -1)
   {
    close(sock);
    protos[i].protocol = proto;
    protos[i].state = 1;
    *update_protos = 1;
    return 1;
   }

 return -1;  
}

/*-- decrement ttl unless of negative value. expire the rule when ttl
     hits null. --*/
void age_rule(routerule *rule)
{
 if(rule->ttl != -1 && !(--rule->ttl)) 
  esp_route_rm_rule(rule, 0);
}

/*-- 								      --*
     parse packet into a pktrec, to be crossreferenced with our set of
     rules, and protodata, which holds all our valuable protocol data,
     including sequence, code, payload and payload length. 
 *--                                                                  --*/
int parse_pkt(pktrec *pkt_rec, struct esp_protodata *protodata,
	      const u_char *packet, int len)
{
 struct iprec *ip_rec;
 struct tcprec *tcp_rec;
 struct udprec *udp_rec;
 struct icmp_echo *icmp_rec;

 ip_rec = (struct iprec *)packet;

 protodata->payload_len = len;
 memcpy(&pkt_rec->sin_addr, &ip_rec->ip_src, sizeof(struct in_addr));
 pkt_rec->protocol = ip_rec->ip_p;
 switch(ip_rec->ip_p) {
  case IPPROTO_TCP	:
   tcp_rec = (struct tcprec *)(packet + sizeof(struct iprec));
   pkt_rec->srcport = tcp_rec->tcp_srcport;
   pkt_rec->dstport = tcp_rec->tcp_dstport;
   protodata->sequence = tcp_rec->tcp_seq;
   protodata->code = tcp_rec->tcp_code;
   protodata->payload = (u_char *)(packet + TCP_HDR_SIZE);
   protodata->payload_len -= TCP_HDR_SIZE;
   break;
  case IPPROTO_UDP	:
   udp_rec = (struct udprec *)(packet + sizeof(struct iprec));
   pkt_rec->srcport = udp_rec->udp_srcport;
   pkt_rec->dstport = udp_rec->udp_dstport;
   protodata->sequence = udp_rec->udp_seq;
   protodata->code = udp_rec->udp_code;
   protodata->payload = (u_char *)(packet + UDP_HDR_SIZE);
   protodata->payload_len -= UDP_HDR_SIZE;
   break;
  case IPPROTO_ICMP	:
   icmp_rec = (struct icmp_echo *)(packet + sizeof(struct iprec));
   pkt_rec->srcport = icmp_rec->icmp_srcport;
   pkt_rec->dstport = icmp_rec->icmp_dstport;
   protodata->sequence = icmp_rec->icmp_seq;
   protodata->code = icmp_rec->icmp_code;
   protodata->payload = (u_char *)(packet + ICMP_HDR_SIZE);
   protodata->payload_len -= ICMP_HDR_SIZE;
   break;
  default		: return -1;
 }

 return 1;
}

/*-- Assemble a raw protocol/ip packet --*/
int assemble_pkt(u_char **packet, pktrec *pkt_rec, 
		 struct esp_protodata *protodata)
{
 char *cp;
 struct tcprec *tcp_rec;
 struct udprec *udp_rec;
 struct icmp_echo *icmp_rec;

 buffer_clear(&transmit_packet);
 switch(pkt_rec->protocol) {
  case IPPROTO_TCP	:
   buffer_append_space(&transmit_packet, &cp, sizeof(struct tcprec));
   tcp_rec = (struct tcprec *)cp;
   tcp_rec->tcp_type = TCP_TRANSMIT_TYPE;
   tcp_rec->tcp_srcport = pkt_rec->srcport;
   tcp_rec->tcp_dstport = pkt_rec->dstport;
   tcp_rec->tcp_seq = protodata->sequence;
   tcp_rec->tcp_code = protodata->code;
  
   buffer_append(&transmit_packet, protodata->payload, protodata->payload_len);
   tcp_rec->tcp_cksum = 0;
   tcp_rec->tcp_cksum = esp_in_cksum((u_short *)
					 buffer_ptr(&transmit_packet),
					 buffer_len(&transmit_packet));
   break;
  case IPPROTO_UDP	:
   buffer_append_space(&transmit_packet, &cp, sizeof(struct udprec));
   udp_rec = (struct udprec *)cp;
   udp_rec->udp_srcport = pkt_rec->srcport;
   udp_rec->udp_dstport = pkt_rec->dstport;
   udp_rec->udp_seq = protodata->sequence;
   udp_rec->udp_code = protodata->code;
  
   buffer_append(&transmit_packet, protodata->payload, protodata->payload_len);
   udp_rec->udp_len = htons(buffer_len(&transmit_packet));
   udp_rec->udp_cksum = 0;
   udp_rec->udp_cksum = esp_in_cksum((u_short *)
					 buffer_ptr(&transmit_packet),
					 buffer_len(&transmit_packet));
   break;
  case IPPROTO_ICMP	:
   buffer_append_space(&transmit_packet, &cp, sizeof(struct icmp_echo));
   icmp_rec = (struct icmp_echo *)cp;
   icmp_rec->icmp_type = ICMP_TRANSMIT_TYPE;
   icmp_rec->icmp_srcport = pkt_rec->srcport;
   icmp_rec->icmp_dstport = pkt_rec->dstport;
   icmp_rec->icmp_code = protodata->code;
   icmp_rec->icmp_seq = protodata->sequence;

   buffer_append(&transmit_packet, protodata->payload, protodata->payload_len);
   icmp_rec->icmp_cksum = 0;
   icmp_rec->icmp_cksum = esp_in_cksum((u_short *)
					   buffer_ptr(&transmit_packet),
					   buffer_len(&transmit_packet));
   break;  
  default	: return -1;
 }

 *packet = (u_char *)buffer_ptr(&transmit_packet);
 return buffer_len(&transmit_packet);
}


/*-- reassemble/route packet --*/
int esp_route_transmit(pktrec *pkt_rec, struct esp_protodata *protodata)
{
 int packet_len, proto_slot;
 u_char *packet;
 struct sockaddr_in destination;

 memcpy(&destination.sin_addr, &pkt_rec->sin_addr, sizeof(struct in_addr));
 destination.sin_family = AF_INET;
 destination.sin_port = 0;

 packet_len = assemble_pkt(&packet, pkt_rec, protodata);
 if((proto_slot = esp_route_find_proto(pkt_rec->protocol)) != -1 &&
    sendto(protos[proto_slot].fd, packet, packet_len, 0, 
	  (struct sockaddr *)&destination, 
          sizeof(struct sockaddr)) == packet_len)
 {
  debug("Routed packet, protocol (%u).", protos[proto_slot].protocol);
  return 1;
 }

 debug("Failed to route packet!.");
 return -1;  
}

/**********************************
 * PUBLIC routine implementations *
 **********************************/

/*-- Initialize internal route structures, buffers. etc. --*/
int esp_route_init(void)
{
 int i;

 shmalloc_init();

 if(!(rulebook = (struct rule *) shmalloc(sizeof(struct rule) * ROUTE_RULES)) ||
    !(protos = (struct proto *) shmalloc(sizeof(struct proto) * MAX_PROTOS)) ||
    !(route_rules = (int *) shmalloc(sizeof(int))) ||
    !(update_protos = (int *)shmalloc(sizeof(int)))) 
  return -1;

 buffer_init(&transmit_packet);
 for(i = 0; i<ROUTE_RULES; i++) 
  rulebook[i].initialized = 0;

 for(i = 0; i<MAX_PROTOS; i++) {
  protos[i].state = 0;
  protos[i].protocol = -1;
 }

 routing_supported = 1;
 *route_rules = *update_protos = 0;
 
 return 1;
}

/*-- Remove ALL the rules, free all protocols. --*/
void esp_route_rm_rules(void)
{
 int i;

 for(i = 0; i<ROUTE_RULES; i++) 
  if(rulebook[i].initialized) 
   {
    memset(&rulebook[i].rule, 0, sizeof(routerule));
    rulebook[i].initialized = 0;
   }

 for(i = 0; i<MAX_PROTOS; i++)
  if(protos[i].protocol != -1) 
  {
   protos[i].protocol = -1;
   protos[i].state = -1;
   *update_protos = 1;
  }

 *route_rules = 0;
}

/*
 * Remove a rule:
 * - Destroy the rule structure.
 * - Remove any unused protocol sockets.
 * - free memory used by the 'rule' pointer if the free_memory flag is non
 *   zero.
 *
 * Note: we search for the rule by trying to match it's source
 *       field. (We ignore rule's it's dest side).
 */
int esp_route_rm_rule(routerule *rule, int free_memory)
{
 int ret, rule_slot;
 u_char proto;

 if((rule_slot = esp_route_find_rule(&rule->source)) != -1) 
 {
  proto = rulebook[rule_slot].rule.dest.protocol;

  memset(&rulebook[rule_slot].rule, 0, sizeof(routerule));
  rulebook[rule_slot].initialized = 0;

  esp_route_rm_proto(proto);
  (*route_rules)--;

  ret = 1;
 } else ret = -1;

 if(free_memory)
 {
  memset(rule, 0, sizeof(routerule));
  free(rule);
 }

 return ret;
}


/*-------------------------------------------------------------------*
   Set route rule. packets processed by the routing mechanism
   will be crossreferenced with the source field of the routerule.
   If a match is found, they will be reassembled using the dest
   field of the routerule and send off. note that setrule also
   initializes a matching protocol socket based on the dest.protocol
   routerule field.
 *------------------------------------------------------------------*/
int esp_route_setrule(routerule *rule)
{
 int i;

 /*-- Check if a rule for the routerule source pktrec already exists */
 if(esp_route_find_rule(&rule->source) != -1) 
  return -1;

 /*-- Find an empty slot and put our rule in it --*/
 for(i = 0; i<ROUTE_RULES; i++) 
  if(!rulebook[i].initialized) 
   if(esp_route_initproto(rule->dest.protocol) != -1)
   {
    memcpy(&rulebook[i].rule, rule, sizeof(routerule));
    rulebook[i].initialized = 1;

    memset(rule, 0, sizeof(routerule));
    free(rule);

    (*route_rules)++;
    return 1;
   }

 memset(rule, 0, sizeof(routerule));
 free(rule);

 return -1;
}

/*----------------------------------------------------------------* 
   handle raw ip packet (assumption: handling valid ip packets).
   We parse the packet, try to find a matching rule for the packet,
   reassemble the packet with the 'dest' side of the rule, and 
   transmit the packet. esp_route_handle_packet is the only part
   of the public esp_route interface to be executed from the pearent,
   the rest are routing table manipulators called by the children.					
 *----------------------------------------------------------------*/
int esp_route_handle_packet(const u_char *packet, int len)
{
 int rule_match; 

 pktrec pkt_rec;
 struct esp_protodata protodata;

 if(*update_protos) {
  update_protocols();
  *update_protos = 0;
 }

 if(!(*route_rules) ||
    parse_pkt(&pkt_rec, &protodata, packet, len) < 0 ||
    (rule_match = esp_route_find_rule(&pkt_rec)) < 0) 
  return -1;

 if(esp_route_transmit(&(rulebook[rule_match].rule.dest), 
			   &protodata) < 0) return 0;
 age_rule(&rulebook[rule_match].rule); 
 return 1;
}

int esp_route_supported(void)
{
 return routing_supported;
}

