/*

esp_proute.c

Author: Liraz Siri <liraz@bigfoot.com>

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

Created: Sat Oct  3 01:27:32 GMT 1998

Code for the implementation client configuration of a remote esp
server routing table. Currently only used by the proute client.
*/

#include "includes.h"
#include "esp_proute.h"
#include "packet.h"
#include "ssh.h"
#include <pcap.h>
#include "logpatch.h"

/*****************************
 * PUBLIC routine prototypes *
 *****************************/

/*-------------------------------------------------------------------------*
   Send a esp route operation to the server side. This should be
   either SSH_CMSG_ROUTE_SET_RULE or SSH_CMSG_ROUTE_REMOVE_RULE, anything
   else will be considered just plain rude. What this actually does
   is set 3 rules on the server's routing table. One rule for our 
   negotiation (aka esp_SYN) packet, which will expire immediately
   after we use it, and another two rules for the actual session
   routing (which live indefinately, until removed). 

   What/How/Why we set the routing table:
    We resolve host, which is the system we actually want to tunnel 
    a connection to, find a suitable local network address, and then
    set the following rule:
    Map symbols ( -> = router-in-the-middle )

    1) OUR_ADDR (proto) [srcport:dstport] -> REMOTE_ADDR (proto) [src:dstport]
    2) REMOTE_ADDR (proto) [srcport:dstport] -> OUR_ADDR (proto) [src:dstport]

 *-------------------------------------------------------------------------*/
void esp_proute_sendop(int type, char *source_host, char *host, int port, 
		           int transmit_proto, int receive_proto, 
			   int negotiation_only)
{
 int i;
 char *device, errbuf[PCAP_ERRBUF_SIZE];
 bpf_u_int32 s_addr[2], mask;
 int idevice;

 pktrec pkt_source[2], pkt_dest[2];

 routerule route_rule;

 if(source_host) 
 {
  if((s_addr[0] = hostlookup(source_host)) != -1) goto found_address;
  fatal("Failed to resolve hostname '%s'.\n", source_host);
 }

 /*-- looking up a suitable network address we can use --*/
 idevice = 0;
 while(device = pcap_lookupdev(errbuf, idevice++)) 
  if(pcap_lookupnet(device, &s_addr[0], &mask, errbuf) != -1) 
   goto found_address;

 /*-- failed to find network interface with a suitable address --*/
 error("Unable to find suitable network interface, forcing loopback.");
 s_addr[0] = inet_addr("127.0.0.1");

found_address:
 if((s_addr[1] = hostlookup(host)) == -1) 
  fatal("Failed to resolve hostname '%s'.", host);

 pkt_source[0].protocol = receive_proto;
 pkt_source[1].protocol = transmit_proto;

 for(i = 0; i<2; i++) {
  pkt_source[i].sin_addr.s_addr = s_addr[i];
  pkt_source[i].srcport = htons(port);
  switch(pkt_source[i].protocol) {
   case IPPROTO_TCP :
       pkt_source[i].dstport = htons(TCP_SERVER_PORT);
       break;
   case IPPROTO_UDP :
       pkt_source[i].dstport = htons(UDP_SERVER_PORT);
       break;
   case IPPROTO_ICMP :
       pkt_source[i].dstport = htons(ICMP_SERVER_PORT);
       break;
  }
 }

 if(negotiation_only) goto negotiation_rule;

 memcpy(&pkt_dest[0], &pkt_source[1], sizeof(pktrec));
 memcpy(&pkt_dest[1], &pkt_source[0], sizeof(pktrec));

 for(i = 0; i<2; i++)
  swap(&pkt_dest[i].srcport, &pkt_dest[i].dstport);

 route_rule.ttl = -1;

 memcpy(&route_rule.source, &pkt_source[0], sizeof(pktrec));
 memcpy(&route_rule.dest, &pkt_source[1], sizeof(pktrec));
 if(transmit_rule(type, &route_rule) < 0) 
  switch(type) {
   case SSH_CMSG_ROUTE_SET_RULE :
    error("Server refuses to set source [input] rule.");
    break;
   case SSH_CMSG_ROUTE_REMOVE_RULE :
    error("Server refuses to remove source [input] rule.");
    break;
  }

 memcpy(&route_rule.source, &pkt_dest[0], sizeof(pktrec));
 memcpy(&route_rule.dest, &pkt_dest[1], sizeof(pktrec));
 if(transmit_rule(type, &route_rule) < 0) 
  switch(type) {
   case SSH_CMSG_ROUTE_SET_RULE :
    error("Server refuses to set destination [output] rule.");
    break;
   case SSH_CMSG_ROUTE_REMOVE_RULE :
    error("Server refuses to remove destination [output] rule.");
    break;
  }

negotiation_rule:

 route_rule.ttl = 1;
 for(i = 0; i < 2; i++) 
  switch(pkt_source[i].protocol) {
   case IPPROTO_TCP : 
       pkt_source[i].srcport = htons(TCP_CLIENT_PORT);
       break;
   case IPPROTO_UDP :
       pkt_source[i].srcport = htons(UDP_CLIENT_PORT);
       break;
   case IPPROTO_ICMP :
       pkt_source[i].srcport = htons(ICMP_CLIENT_PORT);
       break;
  } 

 memcpy(&route_rule.source, &pkt_source[0], sizeof(pktrec));
 memcpy(&route_rule.dest, &pkt_source[1], sizeof(pktrec));
 if(transmit_rule(type, &route_rule) < 0)
  switch(type) {
   case SSH_CMSG_ROUTE_SET_RULE :
    error("Server refuses to set negotiation [SYN] rule.");
    break;
   case SSH_CMSG_ROUTE_REMOVE_RULE :
    error("Server refuses to remove negotiation [SYN] rule.");
    break;
  }
  
}

/*-- Request the remote server to kindly destroy it's routing table --*/
void esp_proute_remove_all(void)
{
 int type;
 packet_start(SSH_CMSG_ROUTE_REMOVE_ALL);
 packet_send();
 packet_write_wait();

 type = packet_read_stimulate(ICMP_READ_ATTEMPTS, ICMP_RESPONSE_TIMEOUT);
 if(type != SSH_SMSG_SUCCESS)
  fatal("Remote server failed destroying rules.");
}


/******************************
 * PRIVATE routine prototypes *
 ******************************/

/*-- Transmit our rule op (setrule/rmrule) via ssh's packet mechanism --*/
int transmit_rule(int type, routerule *route_rule)
{
 int rettype;
 packet_start(type);
 packet_put_string((const char *)route_rule, sizeof(routerule));
 packet_send();
 packet_write_wait();

 rettype = packet_read_stimulate(ICMP_READ_ATTEMPTS, ICMP_RESPONSE_TIMEOUT);
 if(rettype != SSH_SMSG_SUCCESS) return -1;
 return 1;
}

/*-- simple little swap function that could --*/
void swap(u_short *a, u_short *b)
{
 u_short tmp = *a;

 *a = *b;
 *b = tmp;
}

/*-- Look up a hostname, checking first to check wether it's an ip,
     and then actually using gethostbyname to resolve it. Return the
     32bit network number on success, -1 on failure --*/		       
unsigned long hostlookup(char *host)
{
 struct hostent *hp;
 struct in_addr sin_addr;

 /* is host an ip address? */
 if((sin_addr.s_addr = inet_addr(host)) != -1)
  return sin_addr.s_addr;

 if(!(hp = gethostbyname(host)))
  return -1;

 memcpy(&sin_addr, hp->h_addr, sizeof(sin_addr));
 return sin_addr.s_addr;
}
