//
// $Id: irrhelper.cc,v 1.4 2000/07/28 15:49:23 voeckler Exp $
//
// Author:  Jens-S. Vöckler <voeckler@rvs.uni-hannover.de>
//
// File:    irrhelper.cc
//          Sat Aug 28 1999
//
// (c) 1999 Lehrgebiet Rechnernetze und Verteilte Systeme
//          Universität Hannover, Germany
//
// Permission to use, copy, modify, distribute, and sell this software
// and its documentation for any purpose is hereby granted without fee,
// provided that (i) the above copyright notices and this permission
// notice appear in all copies of the software and related documentation,
// and (ii) the names of the Lehrgebiet Rechnernetze und Verteilte
// Systeme and the University of Hannover may not be used in any
// advertising or publicity relating to the software without the
// specific, prior written permission of Lehrgebiet Rechnernetze und
// Verteilte Systeme and the University of Hannover.
//
// THE SOFTWARE IS PROVIDED "AS-IS" AND WITHOUT WARRANTY OF ANY KIND,
// EXPRESS, IMPLIED OR OTHERWISE, INCLUDING WITHOUT LIMITATION, ANY
// WARRANTY OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE.
//
// IN NO EVENT SHALL THE LEHRGEBIET RECHNERNETZE UND VERTEILTE SYSTEME OR
// THE UNIVERSITY OF HANNOVER BE LIABLE FOR ANY SPECIAL, INCIDENTAL,
// INDIRECT OR CONSEQUENTIAL DAMAGES OF ANY KIND, OR ANY DAMAGES
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER OR NOT
// ADVISED OF THE POSSIBILITY OF DAMAGE, AND ON ANY THEORY OF LIABILITY,
// ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
// SOFTWARE.
//
// $Log: irrhelper.cc,v $
// Revision 1.4  2000/07/28 15:49:23  voeckler
// fixed severe debugging code leftover bug, which resulted in
// all MISS scenarios.
//
// Revision 1.3  2000/07/27 07:35:15  voeckler
// made up for many peculiarities when parsing whois output: there were
// bugs when receiving comments and continuation lines.
//
// Revision 1.2  2000/06/09 09:27:17  voeckler
// fixes for FreeBSD 4.0
//
// Revision 1.1  1999/09/02 10:05:57  voeckler
// Initial revision
//
//
#if defined(__GNUC__) || defined(__GNUG__)
#pragma implementation
#endif

#define _XPG4_2 1 // for Solaris, to use void* instead of char*

#include <assert.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
#include <time.h>
#include <sys/uio.h>

#include <signal.h>

#include "typedefs.h"
#include "ctype.hh"
#include "string.hh"
#include "dnsitem.hh"
#include "irritem.hh"

static sig_atomic_t term_flag = 0;
static unsigned int timeout = 90;  // wait 90 seconds max.
static bool map_to_classC = false; // map route lookups failures to class C

#undef DEBUG

ssize_t
writen( int fd, const char* buffer, size_t n )
{
  ssize_t wsize;
  size_t  nleft = n;
  const char* ptr = buffer;
  while ( nleft > 0 ) {
    if ( (wsize=write(fd,ptr,nleft)) < 0 ) {
      if ( errno == EINTR && ::term_flag == 0 ) continue;
      else return wsize;
    }

    nleft -= wsize;
    ptr   += wsize;
  }
  return n; 
}

ssize_t
readn( int fd, char* buffer, size_t n )
{
  ssize_t rsize;
  size_t  nleft = n;
  char* ptr = buffer;
  while ( nleft > 0 ) {
    if ( (rsize=read(fd,ptr,nleft)) < 0 ) {
      if ( errno == EINTR && ::term_flag == 0 ) continue;
      else return rsize;
    } else if ( rsize == 0 ) break;

    nleft -= rsize;
    ptr   += rsize;
  }
  return ( n - nleft );
}

extern "C" {
static
void
sigAlarm( int signo )
{
  ::term_flag = signo;
}
typedef void (SigFunc)( int );
}


SigFunc*
stevensSignal( int signo, SigFunc* func, bool doInterrupt = false )
{
  struct sigaction action, oldaction;
  action.sa_handler = func;
  sigemptyset( &action.sa_mask );
  action.sa_flags = 0;
  if ( signo == SIGALRM || doInterrupt ) {
#ifdef SA_INTERRUPT
    action.sa_flags |= SA_INTERRUPT;
#endif
  } else {
#ifdef SA_RESTART
    action.sa_flags |= SA_RESTART;
#endif
  }
  return ( sigaction(signo,&action,&oldaction) < 0 ?
	   SIG_ERR :
	   oldaction.sa_handler );
}


static
int
connectTo( MyUInt32 host )
{
  int sockfd = socket( PF_INET, SOCK_STREAM, IPPROTO_TCP );
  if ( sockfd == -1 ) {
    perror("socket");
    return -1;
  }

  struct sockaddr_in server;
  memset( &server, 0, sizeof(server) );
  server.sin_family = AF_INET;
  server.sin_addr.s_addr = host;
  server.sin_port = htons(43);
  if ( connect( sockfd, (struct sockaddr*) &server, sizeof(server) ) == -1 ) {
    fprintf( stderr, "connect(%s): %s\n", 
	     inet_ntoa(server.sin_addr), strerror(errno));
    close(sockfd);
    return -1;
  }

  return sockfd;
}

#include <map>
#include <vector>
typedef std::vector< String > Line;
typedef std::map< String, Line, std::less<String> > Paragraph;
typedef std::vector< Paragraph > Answer;

bool
no_junk( const Paragraph& chunk )
{
  Paragraph::const_iterator i = chunk.find( String("route") );
  if ( i == chunk.end() ) return true; // no junk detected
  Line::const_iterator j = (*i).second.begin();
  if ( j == (*i).second.end() ) return true;

  String temp( *j );
  static const String first( "0.0.0.0/" );
  static const String second("128.0.0.0/1" );
  static const String third( "192.0.0.0/2" );

  return ( temp.substring(0,first.length()) != first &&
	   temp.substring(0,second.length()) != second &&
	   temp.substring(0,third.length()) != third );
}

inline
size_t
Minimum( size_t a, size_t b )
{
#ifdef __GNUG__
  return a <? b;
#else
  return a < b ? a : b;
#endif
}

static
bool
query_server( int sockfd, const char* query, Answer& answer )
  // returns: false for failure, true otherwise
{
  String result;
  char   raw_answer[8192];

  // set time out, so we can abort the procedure
  alarm(::timeout);

  // query server
  int rsize;
  writen( sockfd, query, strlen(query) );
#ifdef DEBUG  
  fprintf( stderr, "# writing %d byte to the server:\n%s", 
	   strlen(query), query );
#endif

  // half close pipe (this is easier on the server's resources)
  shutdown( sockfd, 1 ); // no more sends

  // read all of the answer
  memset( raw_answer, 0, sizeof(raw_answer) );
  do {
    rsize = read( sockfd, raw_answer, sizeof(raw_answer)-1 );
    if ( rsize > 0 ) {
      raw_answer[rsize] = '\0'; // terminate string
      result += String(raw_answer);
    } else {
      if ( rsize == -1 && errno == EINTR && ::term_flag == 0 ) continue;
    }
  } while ( rsize > 0 && ::term_flag == 0 );
  if ( rsize < 0 || ::term_flag ) return false;
  else rsize = result.length();
  alarm(0);

#ifdef DEBUG
  fprintf( stderr, "# got %d byte answer from server:\n%s",
	   rsize, result.c_str() );
#endif

  // split result into paragraph, and paragraphs into lines
  char* keep = strdup(result.c_str()); 
  char* start = keep;

  for ( char *cursor = strstr( start, "\n\n" ); 
	start - keep < rsize; 
	cursor = strstr( start, "\n\n" ) ) {
    if ( cursor == 0 ) {
      cursor = keep+rsize-1;
      *cursor++ = '\0';
    }
    *cursor = '\0'; // terminate string here

#ifdef DEBUG
    fprintf( stderr, "# %d byte in paragraph:\n%s\n",
	     cursor-start, start );
#endif

    {
      Paragraph local;
      char* s = start;
      for ( char* t = strchr(s,'\n'); s < cursor; t=strchr(s,'\n') ) {
	if ( t == 0 ) t = cursor;
	else if ( ISBLANK( t[1] ) ) {
	  // continuation line, remove line break
	  *t = ' '; // make continuation line
	  continue;
	}

	// regular line, terminate line and process it.
	*t = '\0';
#ifdef DEBUG
	fprintf( stderr, "# line> %s\n", s );
#endif

	// skip comments (line starting with percent)
	if ( s[0] != '%' ) {
	  // split line into "ISHNAME(key)[ \t]+:[ \t]+ANY(value)"
	  char key[32];
	  char* a = s;
	  char* b = key;
	  while ( ISHNAME(*a) && b-key < sizeof(key)-1 ) *b++ = *a++;
	  *b = '\0'; // terminate key
#ifdef DEBUG
	  fprintf( stderr, "# token> \"%s\"\n", key );
#endif
	  // skip over whitespaces
	  while ( ISBLANK(*a) ) a++;

	  // assert the colon, skip, if not there
	  if ( *a++ == ':' ) {
	    // skip over further whitespaces
	    while ( ISBLANK(*a) ) a++;
	    
	    // copy value into key while trimming whitespaces
	    char* q = raw_answer; 
	    bool seenSpace = false;
	    while ( q - raw_answer < sizeof(raw_answer)-1 && *a != 0 ) {
	      if ( ISSPACE(*a) ) {
		if ( ! seenSpace ) *q++ = ' ';
		seenSpace = true;
		++a;
	      } else {
		*q++ = *a++;
		seenSpace = false;
	      }
	    }
	    *q = '\0';
	      
#ifdef DEBUG
	    fprintf( stderr, "# token> \"%s\"\n", raw_answer );
#endif
	    local[ String(key) ].push_back( String(raw_answer) );
	  }
	}
	s = t+1;
      }

      if ( no_junk(local) ) answer.push_back(local);
    }
    start = cursor+2;
  }

  free((void*) keep);
  return true;
}

String
findFirst( const Answer& answer, const String& key )
{
  String result;
  for ( Answer::const_iterator a = answer.begin();
	a != answer.end();
	++a ) {
    Paragraph::const_iterator i = (*a).find(key);
    if ( i == (*a).end() ) continue;

    Line::const_iterator j = (*i).second.begin();
    if ( j != (*i).second.end() ) result = *j;
  }
  return result;
}

String
findRoute( const Answer& answer, String& request )
{
  String result;
  for ( Answer::const_iterator a = answer.begin();
	a != answer.end();
	++a ) {
    Paragraph::const_iterator i = (*a).find("origin");
    if ( i == (*a).end() ) continue;

    Line::const_iterator j = (*i).second.begin();
    if ( j != (*i).second.end() ) {
      Paragraph::const_iterator rq = (*a).find("route");
      if ( rq != (*a).end() ) {
	Line::const_iterator rj = (*rq).second.begin();
	if ( rj != (*rq).second.end() ) request = *rj;
      }
      result = *j;
    }
  }
  return result;
}

IRRItem
lookup( int sockfd, String& request, size_t positiveTTL, size_t negativeTTL )
{
  time_t now = time(0);
  Answer answer;
  IRRItem result( now + negativeTTL, String() );
  
  if ( query_server( sockfd, (request+"\r\n").c_str(), answer ) &&
       answer.begin() != answer.end() ) {
    // ok, possibly successful query. So, what was the question
    if ( TOUPPER(request[0]) == 'A' ) {
      // origin description
      result = IRRItem( now+positiveTTL, findFirst(answer,"descr") );
    } else if ( ISDIGIT(request[0]) ) {
      // routing information
      // rewrite request, if the lookup was successful
      result = IRRItem( now+positiveTTL, findRoute( answer, request ) );
    }
  } else {
    // ok, request failed (no or empty answer)
    // rewrite request, if the lookup failed and the request was w/o netmask
    if ( ISDIGIT(request[0]) && request.index('/') == -1 ) {
      if ( ::map_to_classC ) {
	// map to class C network
	MyUInt32 temp = htonl(IN_CLASSC_NET & ntohl(DNSItem::aton(request)));
	request = DNSItem::ntoa(temp) + "/24";
      } else {
	// don't touch, create host route
	request += "/32";
      }
    }
  }

  return result;
}

inline
void
fill( struct iovec& v, const void* base, size_t size )
{
#ifdef FREEBSD
  v.iov_base = (char*) base;
#else
  v.iov_base = (void*) base;
#endif
  v.iov_len = size;
}

int
main( int argc, char* argv[] )
{
  struct iovec output[5];
  fill( output[2], " ", 1 );
  fill( output[4], "\n", 1 );

  char request[256];
  memset( request, 0, sizeof(request) );

  size_t positiveTTL = 2592000;
  size_t negativeTTL = 604800;
  char*  irrServer = 0;
  switch ( argc ) {
  case 4:
    negativeTTL = strtoul( argv[3], 0, 0 );
  case 3:
    positiveTTL = strtoul( argv[2], 0, 0 );
  case 2:
    irrServer = strdup(argv[1]);
    if ( positiveTTL && negativeTTL ) break;
  default:
    fprintf( stderr, "Usage: %s irr.ra.server [posTTL [negTTL]]\n", argv[0] );
    return 1;
  }

  // set signal handler
  if ( stevensSignal( SIGTERM, sigAlarm, true ) == SIG_ERR ||
       stevensSignal( SIGPIPE, sigAlarm, true ) == SIG_ERR ||
       stevensSignal( SIGALRM, sigAlarm, true ) == SIG_ERR ) {
    perror( "cannot set signal handler" );
    return 1;
  }

  // get server address
  MyUInt32 host = DNSItem::aton(irrServer);
  if ( host == C_U32(-1) ) {
    struct hostent* h = ::gethostbyname(irrServer);
    if ( h == 0 ) {
      fprintf( stderr, "unable to look up \"%s\"\n", irrServer );
      return 1;
    }
    memcpy( &host, h->h_addr_list[0], sizeof(MyUInt32) );
  }

  // answer queries
  while ( fgets( request, sizeof(request), stdin ) != 0 ) {
    String s( String(request).trim() );
    if ( s.length() == 0 || s[0] == '#' ) {
      if ( write( STDOUT_FILENO, "\n", 1 ) < 1 ) return 1;
    } else {
      // establish TCP connection to server
      int sockfd = connectTo( host );
      if ( sockfd == -1 ) write( STDOUT_FILENO, "\n", 1 );
      else {
	// regular whois connection is open for 1 query
	String result( lookup(sockfd,s,positiveTTL,negativeTTL).toString() );
	size_t size = s.length() + 1 + result.length() + 1;

	char sizebuf[16];
	sprintf( sizebuf, "0x%04x ", size );
	fill( output[0], sizebuf, strlen(sizebuf) );
	fill( output[1], s.c_str(), s.length() );
	fill( output[3], result.c_str(), result.length() );

	// try atomic write
	if ( writev( STDOUT_FILENO, output, 5 ) <= 0 ) break;
	
	// close connection to whois server
	close(sockfd);
      }
    }

    // reset terminate flag (may have been set asynchroneously)
    if ( ::term_flag == SIGTERM || ::term_flag == SIGPIPE ) break;
    else ::term_flag = 0;
  }

  return 0;
}

