/****************************************************************************
 *                   ^     +----- |  / ^     ^ |     | +-\                  *
 *                  / \    |      | /  |\   /| |     | |  \                 *
 *                 /   \   +---   |<   | \ / | |     | |  |                 *
 *                /-----\  |      | \  |  v  | |     | |  /                 *
 *               /       \ |      |  \ |     | +-----+ +-/                  *
 ****************************************************************************
 * AFKMud (c)1997-2002 Alsherok. Contributors: Samson, Dwip, Whir,          *
 * Cyberfox, Karangi, Rathian, Cam, Raine, and Tarl.                        *
 *                                                                          *
 * Original SMAUG 1.4a written by Thoric (Derek Snider) with Altrag,        *
 * Blodkai, Haus, Narn, Scryn, Swordbearer, Tricops, Gorog, Rennard,        *
 * Grishnakh, Fireblade, and Nivek.                                         *
 *                                                                          *
 * Original MERC 2.1 code by Hatchet, Furey, and Kahn.                      *
 *                                                                          *
 * Original DikuMUD code by: Hans Staerfeldt, Katja Nyboe, Tom Madsen,      *
 * Michael Seifert, and Sebastian Hammer.                                   *
 ****************************************************************************
 *                          DNS Resolver Module                             *
 ****************************************************************************/

#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <errno.h>
#include <ctype.h>
#include "mud.h"

DNS_DATA *first_cache;
DNS_DATA *last_cache;

void save_dns( void );

void prune_dns( void )
{
   DNS_DATA *cache, *cache_next;

   for( cache = first_cache; cache; cache = cache_next )
   {
	cache_next = cache->next;

	/* Stay in cache for 14 days */
	if( current_time - cache->time >= 1209600 || !str_cmp( cache->ip, "Unknown??" ) || !str_cmp( cache->name, "Unknown??" ) )
	{
	   STRFREE( cache->ip );
	   STRFREE( cache->name );
	   UNLINK( cache, first_cache, last_cache, next, prev );
	   DISPOSE( cache );
	}
   }
   save_dns();
   return;
}

void check_dns( void )
{
   if( current_time >= new_boot_time_t )
	prune_dns();
   return;
}

void add_dns( char *dhost, char *address )
{
   DNS_DATA *cache;

   CREATE( cache, DNS_DATA, 1 );
   cache->ip = STRALLOC( dhost );
   cache->name = STRALLOC( address );
   cache->time = current_time;
   LINK( cache, first_cache, last_cache, next, prev );

   save_dns();
   return;
}

char *in_dns_cache( char *ip )
{
   DNS_DATA *cache;
   static char dnsbuf[MSL];

   dnsbuf[0] = '\0';

   for( cache = first_cache; cache; cache = cache->next )
   {
	if( !str_cmp( ip, cache->ip ) )
	{
	   strcpy( dnsbuf, cache->name );
	   break;
	}
   }
   return dnsbuf;
}

#if defined(KEY)
#undef KEY
#endif

#define KEY( literal, field, value )					\
				if ( !str_cmp( word, literal ) )	\
				{					\
				      field = value;			\
				      fMatch = TRUE;			\
				      break;				\
				}

void fread_dns( DNS_DATA *cache, FILE *fp )
{
   char *word;
   bool fMatch;

   for ( ; ; )
   {
	word   = feof( fp ) ? "End" : fread_word( fp );
	fMatch = FALSE;

	switch ( UPPER(word[0]) )
	{
	   case '*':
	      fMatch = TRUE;
	      fread_to_eol( fp );
	   break;

	   case 'E':
	      if ( !str_cmp( word, "End" ) )
	      {
		   if( !cache->ip )
		      cache->ip = STRALLOC( "Unknown??" );
		   if( !cache->name )
			cache->name = STRALLOC( "Unknown??" );
		   return;
	      }
	   break;

	   case 'I':
	      KEY( "IP", cache->ip, fread_string( fp ) );
	   break;

	   case 'N':
	      KEY( "Name", cache->name, fread_string( fp ) );
	   break;

	   case 'T':
		KEY( "Time", cache->time, fread_number( fp ) );
	   break;
	}

	if( !fMatch )
	   bug( "fread_dns: no match: %s", word );
   }
}

void load_dns( void )
{
   char filename[256];
   DNS_DATA *cache;
   FILE *fp;
     
   first_cache = NULL;
   last_cache = NULL;

   sprintf( filename, "%s", DNS_FILE );

   if( ( fp = fopen( filename, "r" ) ) != NULL )
   {
	for ( ; ; )
	{
	   char letter;
	   char *word;

	   letter = fread_letter( fp );
	   if ( letter == '*' )
	   {
		fread_to_eol( fp );
		continue;
	   }

	   if ( letter != '#' )
	   {
		bug( "load_dns: # not found.", 0 );
		break;
	   }

	   word = fread_word( fp );
	   if ( !str_cmp( word, "CACHE" ) )
	   {
		CREATE( cache, DNS_DATA, 1 );
		fread_dns( cache, fp );
		LINK( cache, first_cache, last_cache, next, prev );
		continue;
	   }
	   else
            if ( !str_cmp( word, "END"	) )
	         break;
	   else
	   {
		bug( "load_dns: bad section: %s.", word );
		continue;
	   }
	}
	FCLOSE( fp );
   }
   prune_dns(); /* Clean out entries beyond 14 days */
   return;
}

void save_dns( void )
{
   DNS_DATA *cache;
   FILE *fp;
   char filename[256];

   sprintf( filename, "%s", DNS_FILE );

   if ( ( fp = fopen( filename, "w" ) ) == NULL )
   {
	bug( "save_dns: fopen", 0 );
	perror( filename );
   }
   else
   {
	for( cache = first_cache; cache; cache = cache->next )
      {
	   fprintf( fp, "#CACHE\n" );
	   fprintf( fp, "IP		%s~\n",	cache->ip );
	   fprintf( fp, "Name		%s~\n",	cache->name );
	   fprintf( fp, "Time		%ld\n",	cache->time );
	   fprintf( fp, "End\n\n" );
	}
	fprintf( fp, "#END\n" );
	FCLOSE(fp);
   }
   return;
}

/* DNS Resolver code by Trax of Forever's End */
/*
 * Almost the same as read_from_buffer...
 */
bool read_from_dns( int fd, char *buffer )
{
   static char inbuf[MAX_STRING_LENGTH*2];
   int iStart, i, j, k;

   /* Check for overflow. */
   iStart = strlen( inbuf );
   if ( iStart >= sizeof( inbuf ) - 10 )
   {
	bug( "DNS input overflow!!!", 0 );
	return FALSE;
   }

   /* Snarf input. */
   for ( ; ; )
   {
	int nRead;

	nRead = read( fd, inbuf + iStart, sizeof( inbuf ) - 10 - iStart );
	if ( nRead > 0 )
	{
	   iStart += nRead;
	   if ( inbuf[iStart-2] == '\n' || inbuf[iStart-2] == '\r' )
		break;
	}
	else if ( nRead == 0 )
	{
	   return FALSE;
	}
	else if ( errno == EWOULDBLOCK )
	   break;
	else
	{
	   perror( "Read_from_dns" );
	   return FALSE;
	}
   }

   inbuf[iStart] = '\0';

   /*
    * Look for at least one new line.
    */
   for( i = 0; inbuf[i] != '\n' && inbuf[i] != '\r'; i++ )
   {
	if( inbuf[i] == '\0' )
	   return FALSE;
   }

   /*
    * Canonical input processing.
    */
   for( i = 0, k = 0; inbuf[i] != '\n' && inbuf[i] != '\r'; i++ )
   {
	if ( inbuf[i] == '\b' && k > 0 )
	   --k;
	else if( isascii( inbuf[i] ) && isprint( inbuf[i] ) )
	   buffer[k++] = inbuf[i];
   }

   /*
    * Finish off the line.
    */
   if( k == 0 )
	buffer[k++] = ' ';
   buffer[k] = '\0';

   /*
    * Shift the input buffer.
    */
   while( inbuf[i] == '\n' || inbuf[i] == '\r' )
	i++;
   for ( j = 0; ( inbuf[j] = inbuf[i+j] ) != '\0'; j++ )
	;
    
   return TRUE;
}

/* DNS Resolver code by Trax of Forever's End */
/*
 * Process input that we got from resolve_dns.
 */
void process_dns( DESCRIPTOR_DATA *d )
{
   char address[MIL];
   int status;
    
   address[0] = '\0';
    
   if( !read_from_dns( d->ifd, address ) || address[0] == '\0' )
    	return;
    
   if ( address[0] != '\0' )
   {
	add_dns( d->host, address ); /* Add entry to DNS cache */
	STRFREE( d->host );
      d->host = STRALLOC( address );
	if( d->character )
	{
	   STRFREE( d->character->pcdata->lasthost );
	   d->character->pcdata->lasthost = STRALLOC( address );
	}
   }
    
   /* close descriptor and kill dns process */
   if( d->ifd != -1 )
   {
      close( d->ifd );
      d->ifd = -1;
   }

   /* 
    * we don't have to check here, 
    * cos the child is probably dead already. (but out of safety we do)
    * 
    * (later) I found this not to be true. The call to waitpid( ) is
    * necessary, because otherwise the child processes become zombie
    * and keep lingering around... The waitpid( ) removes them.
    */
   if( d->ipid != -1 )
   {
      waitpid( d->ipid, &status, 0 );
      d->ipid = -1;
   }
   return;
}

/* DNS Resolver hook. Code written by Trax of Forever's End */
void resolve_dns( DESCRIPTOR_DATA *d, long ip )
{
   int fds[2];
   pid_t pid;
    
   /* create pipe first */
   if( pipe( fds )!=0 )
   {
      perror( "resolve_dns: pipe: " );
      return;
   }
    
   if( dup2( fds[1], STDOUT_FILENO ) != STDOUT_FILENO )
   {
      perror( "resolve_dns: dup2(stdout): " );
      return;
   }
    
   if( ( pid = fork() ) > 0 )
   {
    	/* parent process */
    	d->ifd  = fds[0];
    	d->ipid = pid;
      close( fds[1] );
   }
   else if( pid == 0 )
   {
    	/* child process */
	char str_ip[64];
	int i;
        
    	d->ifd  = fds[0]; 
    	d->ipid = pid;

      for( i = 2; i < 255; ++i ) close(i);

	sprintf( str_ip, "%ld", ip );
    	execl( "../src/resolver", "AFKMud Resolver", str_ip, NULL );
    	/* Still here --> hmm. An error. */
    	bug( "resolve_dns: Exec failed; Closing child.", 0 );
    	d->ifd  = -1;
    	d->ipid = -1;    	
    	exit( 0 );
   }
   else 
   {
    	/* error */
    	perror( "resolve_dns: failed fork" );
    	close( fds[0] );
    	close( fds[1] );
   }
}

void do_cache( CHAR_DATA *ch, char *argument )
{
   DNS_DATA *cache;
   int ip = 0;

   send_to_pager( "&YCached DNS Information\n\r", ch );
   send_to_pager( "IP               | Address\n\r", ch );
   send_to_pager( "------------------------------------------------------------------------------\n\r", ch );
   for( cache = first_cache; cache; cache = cache->next )
   {
	pager_printf( ch, "&W%16.16s  &Y%s\n\r", cache->ip, cache->name );
	ip++;
   }
   pager_printf( ch, "\n\r&W%d IPs in the cache.\n\r", ip );
   return;
}
