#if !defined(lint) && !defined(__INSIGHT__)
static char sos__rcsid[] = "bsrelay.c,v 1.1.1.1 1995/06/16 21:10:42 seth Exp";
static char sos__copyright[] = "Copyright (c) 1994, 1995 SOS Corporation";
static char sos__contact[] = "SOS Corporation <sos-info@soscorp.com> +1 800 SOS UNIX";
#endif /* not lint */

/*
 * ++Copyright Released Product++
 *
 * Copyright (c) 1994, 1995 Sources of Supply Corporation ("SOS").
 * All rights reserved.
 *
 * The SOS Released Product License Agreement specifies the terms and
 * conditions for redistribution.  You may find the License Agreement
 * in the file LICENSE.
 *
 * SOS Corporation
 * 461 5th Ave.; 16th floor
 * New York, NY 10017
 *
 * +1 800 SOS UNIX
 * <sos-info@soscorp.com>
 *
 * --Copyright Released Product--
 */

/*
 * bsrelay
 *
 * Brimstone generic TCP/UDP relay.
 */

#include "bsClient.h"

#include <dict.h>
#include <dll.h>



#define ACTIVE 0
#define PASSIVE 1

#define SMALLBUF 512

#define INC_AC { ac++; if (ac == argc)  { if (interactive) fprintf(stderr,"Too few arguments\n"); return -1; } }		   


struct addrspec {
  int has_netmask;		/* Boolean SOS_TRUE if netmask value is set */
  int is_wildcard_addr;		/* Boolean SOS_TRUE if the addrspec is *ANY */
  struct in_addr addr;		/* Address Specification(subject to netmask) */
  struct in_addr netmask;	/* Netmask. */
};

struct halfchannel {
  int is_connected;		/* SOS_TRUE when this *half* channel has peer. */
  struct sos_conninfo conninfo;	/* Connifo struct for bs relay and logging. */
  int to_be_closed;		/* The socket should be closed on TCP errors */
  int proto;			/* TCP or UDP */
  int is_wildcard_port;		/* SOS_TRUE if the port is a wilcard */
  unsigned short port;		/* Port */
  int disposition;		/* ACTIVE or PASSIVE */

  dict_h addrspeclist;		/* List of address specifications */
  
} halfchan[2] = {
  {SOS_FALSE, {NULL, -1, 0L, -1}, -1 , SOS_FALSE, SOS_FALSE, -1, ACTIVE, NULL },
  {SOS_FALSE, {NULL, -1, 0L, -1}, -1 , SOS_FALSE, SOS_FALSE, -1, ACTIVE, NULL },
};


static struct iostat
{
  int starttime;
  int endtime;
  int bytes_from_source;
  int bytes_from_dest;
} ioinfo;



static int bsrelaydebug;	/* Internal debuger. */
static sos_config_t config;	/* Config file pointer */
static char *program;		/* Program name. */
static int interactive=1;	/* Assume interactive mode */
static char *username;		/* User who validated */
static struct sos_bdr_auxinfo auxinfo;
static int peer_count = 2;	/* Number of peers in the relay set */
static struct sos_bdr_fdpair relay[2], *prelay;
static int childdied = 0;	/* Nasty handler/routine comnunication  */
static int passive_count = 0;	/* Number of passive channels */
static int socksize = 0;	/* If set, set the socket size. */
static int nolog = 0;		/* SOS_TRUE if loggin should be OFF */

/* 
 * The names source and dest don't mean too much in this program, but 
 * but the names are maintained for purposes of brimstone source 
 * compatibility. source is just one peer, dest is the other
 */
static struct sos_conninfo sourceinfo = { "not.set.yet [0.0.0.0]", -1 };
static struct sos_conninfo destinfo = { "not.set.yet [0.0.0.0]", -1 };
static struct timeval select_timeout = {0, 0};


extern int errno;
extern int optind;


static void exiterror(char *msg, int wanterrno);
static void exitcleanup(int);
static int parse_addr(int is_passive, char *addrstring, struct addrspec *addr);
static struct addrspec *allocate_addrspec();
static int parse_netmask(char *netmaskstring, struct addrspec *addr);
static int parse_command_line(int ac, int argc, char *argv[]);
static void print_halfchannel(struct halfchannel *chan);
static int do_passive_setup();	/* Both sides passive */
static int do_passive_connect();
static int tcp_accept(int socket, struct sockaddr *addr, int *addrlen);
static int udp_accept(int socket, struct sockaddr *addr, int *addrlen);
static int complete_connections();
static int check_connection(int chanid, struct sockaddr_in *addr);
static void discard_udp_data(int sock);
static void Usage();
void sigtimeout();
void sigchld();
void sigexit();

static struct sigaction old_sigaction; /* Caches old signal vec. */
static struct sigaction childdeath = SIGACTIONDEFINE( sigchld, 0, SA_NOCLDSTOP|_INTERRUPT_SYSCALLS_ );
static struct sigaction sigexits = SIGACTIONDEFINE( sigexit, 0, 0 );

#ifdef NO_TIMEOUT_YET
static struct sigaction iotimeout = SIGACTIONDEFINE( sigtimeout, 0, _INTERRUPT_SYSCALLS_ );
#endif



/*
 * Timeouts
 */
int timedout = 0;
void sigtimeout()
{
  timedout = 1;
}



/*
 * Dead children should be buried
 */
void sigchld()
{
  int reaped;
  do {
    if ( (reaped = waitpid(-1, NULL, WNOHANG)) < 0 ) break;

    if ( bsrelaydebug)
      fprintf(stderr,"reap_child(): child reaped[%d]\n", reaped);

    if ( !nolog ) soslog(SOSLOG_DEBUG,SOSLOG_TYPE_CONDITION,SOSLOG_NO_PERROR,
	  "reap_child(): child reaped[%d]", reaped);
  } while(reaped > 0);
  
  childdied=1;

  return;
}



/*
 * Terminal signal
 */
void sigexit(int signal)
{
  if ( !nolog ) soslog(SOSLOG_INFO, SOSLOG_TYPE_EVENT, SOSLOG_NO_PERROR,
	 "Exiting on signal %d. Some children may remain", signal);

  if ( interactive )
    fprintf(stderr,"Exiting on signal %d. Some children may remain\n", signal);
    
  (void)exitcleanup(0);
  return;
}



/*
 * Print usage message
 */
void 
Usage()
{
  if ( interactive )
    {
      fprintf(stderr,"bsrelay [-b <socket size> ] [-l buflen] [-B] -- <half channel specification> < half channel specification >\n");
      fprintf(stderr,"\ttcp <etc>\n");
    }

  exit (1);

  /* NOTREACHED */
  return;
}



int
main(int argc, char *argv[])
{
  char *config_file;		/* Config file name */
  char *called_from_telnet;	/* "true" if called from telnet  */
  char *tmp;			/* Various uses */
  int i;			/* Loop counter */
  int pid;			/* Return from fork() */
  int select_timeout_val;	/* Num seconds select() will hang around */
  int ret_val;			/* Generic return value  */
  int persistant=0;		/* SOS_TRUE of relay should accept >1 conn.  */
  struct stat s;		/* Used to check the disposition of stdin */
  int shouldcheckacl = 0;	/* Set if ACL lists should be checked. */
  int c;			/* Used for getopt -- must be int! */
  int priv_deny = 0;		/* Denies access to priv. options  */

  setvbuf(stdout, NULL, _IOLBF, BUFSIZ);

  /* Get program name without location information */
  tmp = strrchr(argv[0],'/');
  if (!tmp)
    tmp = argv[0];
  else
    tmp++;
  program = strdup(tmp);

  ioinfo.starttime = time(NULL);
  ioinfo.endtime = -1;
  ioinfo.bytes_from_source = 0;
  ioinfo.bytes_from_dest = 0;


  auxinfo.veclen = 10;
  auxinfo.totbuf = 16;
  auxinfo.buflen = 16384;
  auxinfo.align = 16384;
  auxinfo.offset = 0;
  auxinfo.common_exit = 0;
  auxinfo.fullread = 0;


  prelay = &relay[0];
  relay[0].rinfo.fd = -1;
  relay[0].rinfo.iofun = (int (*)(int, void *, __SIZE_TYPE__))read;
  relay[0].winfo.fd = -1;
  relay[0].winfo.iofun = (int (*)(int, void *, __SIZE_TYPE__))write;
  relay[0].curRbuf.iov_base = NULL;
  relay[0].curRbuf.iov_len = 0;
  relay[0].shutdown = 0;
  relay[0].nbytes = 0;
  relay[0].filter = NULL;
  relay[0].bd.cur_write_hand = 0;
  relay[0].bd.cur_read_hand = -1;
  if (!(relay[0].bd.curWbuf = (struct iovec *)malloc(sizeof(struct iovec)*auxinfo.veclen)))
    exiterror("malloc",1);
  if (!(relay[0].bd.fullbuf = (struct iovec *)malloc(sizeof(struct iovec)*auxinfo.veclen)))
    exiterror("malloc",1);
  
  relay[1].rinfo.fd = -1;
  relay[1].rinfo.iofun = (int (*)(int, void *, __SIZE_TYPE__))read;
  relay[1].winfo.fd = -1;
  relay[1].winfo.iofun = (int (*)(int, void *, __SIZE_TYPE__))write;
  relay[1].curRbuf.iov_base = NULL;
  relay[1].curRbuf.iov_len = 0;
  relay[1].bd.cur_write_hand = 0;
  relay[1].bd.cur_read_hand = -1;
  relay[1].shutdown = 0;
  relay[1].nbytes = 0;
  relay[1].filter = NULL;
  if (!(relay[1].bd.curWbuf = (struct iovec *)malloc(sizeof(struct iovec)*auxinfo.veclen)))
    exiterror("malloc",1);
  if (!(relay[1].bd.fullbuf = (struct iovec *)malloc(sizeof(struct iovec)*auxinfo.veclen)))
    exiterror("malloc",1);
  
  
  config_file=SOS_ENV_GWD(BS_CLIENT_CONF_ENV,BS_CLIENT_CONF); /* Make this an option */
  username="Command Line";	/* Default user name for command line */

  if ( argc < 6 ) 
    Usage();
    
  called_from_telnet = SOS_ENV_GWD("BS_CALLED_FROM_TELNET", "false");
  
  if ( SOS_STREQ(called_from_telnet, "true" ) )
      {
	shouldcheckacl++;
	username = strdup(SOS_ENV_GWD("USER", "UNKNOWN"));
      }
      

  while ( (c = getopt(argc, argv, "dif:b:l:pnB")) != -1 )
    switch (c)
      {
      case 'd':
	if ( !shouldcheckacl )
	  bsrelaydebug++;
	else
	  {
	    printf("`%c' is a privileged option\n", c);
	    priv_deny++;
	  }
	break;
      case  'i': 
	interactive++;
	break;
      case 'f':
	config_file = strdup (argv[optind]);
	break;
      case 'b':
	socksize = atoi(argv[optind]);
	break;
      case 'l':
	auxinfo.buflen = atoi(argv[optind]);
	break;
      case 'p':
	persistant++;
	break;
      case 'n':
	if ( !shouldcheckacl )
	  /*
	   * Supress non fatal, normal messages, in order to improve throughput
	   * of short-lived connections
	   */
	  nolog++;
	else
	  {
	    printf("`%c' is a privileged option\n", c);
	    priv_deny++;
	  }
	break;
      case 'B':
	/* NOOP -- supported to make ttcp'er happy :-) */
	break;
      default:
	Usage();
	
      }

  if ( priv_deny )
    exiterror("Priviledged option are only supported from the command line",0);

  if ( parse_command_line(optind, argc, argv) < 0 ) 
    exiterror("Command line parse failed", 0);
  

  open_soslog(program, bsrelaydebug);

  sigaction (SIGCHLD,(struct sigaction *)&childdeath, NULL);
  sigaction (SIGHUP,(struct sigaction *)&sigexits, NULL);
  sigaction (SIGQUIT,(struct sigaction *)&sigexits, NULL);
  sigaction (SIGTERM,(struct sigaction *)&sigexits, NULL);
  sigaction (SIGINT,(struct sigaction *)&sigexits, NULL);



  /* This is non-fatal */
  
  if ((config = sos_config_read(config_file)) == NULL)
    {
      if (!nolog) soslog(SOSLOG_NOTICE, SOSLOG_TYPE_EVENT, 1, 
	     "Could not read config file %s",config_file);
    }


  /*
   * The authors have opted (for various reasons) to require that the a 
   * relay forked from telnet (as implied when shouldcheckacl > 0 ) should
   * be restricted to a PASSIVE <-> ACTIVE connection.
   */
  if ( shouldcheckacl )
    {
      if ( !(halfchan[0].disposition == ACTIVE && halfchan[1].disposition == PASSIVE) &&
	  !(halfchan[0].disposition == PASSIVE && halfchan[1].disposition == ACTIVE) )
	{
	  soslog(SOSLOG_WARNING, SOSLOG_TYPE_EVENT,
		 SOSLOG_NO_PERROR,
		 "user %s attempted to start an illegal relay", 
		 username);
	  fprintf(stderr,"%s\n", SOS_GWD("BSRELAY_ILLEGAL_RELAY","Illegal relay specification"));
	  exit (1);
	}
	
    }

  select_timeout_val = atoi(SOS_GWD("BSRELAY_PASSIVETIMEOUT","0"));
  select_timeout.tv_sec = select_timeout_val;

  if (bsrelaydebug)
    fprintf(stderr,"Setting select timeout to %d\n", select_timeout_val);

  if ( do_passive_setup() < 0 )
    {
      if (interactive)
	fprintf(stderr,"main(): Passive set up failed -- exiting\n");
      /*
       * Assume that we have logged problem in command -- No logging.
       */
      exiterror("Passive set up failed", 0);
    }

  
  /*
   * At some point we need to free the parent btelnet process from waiting
   * for possible bsrelay output. As of now, all bsrelay ever reports back
   * are the ports that were acutally assigned to an 'any' specification.
   * Since that is certainly done by this point, we will close stdout and 
   * stderr which will allow the parent btelnet to continue.
   */
  if ( shouldcheckacl /* This also means forked from btelnet */ )
    {
      fclose(stdout);
      fclose(stderr);
    }
  

 persistant_loop: /* Goto actually makes this code cleaner */
  if ( passive_count )
    {
      if  ( (ret_val = do_passive_connect()) < 0 )
	{
	  if (interactive)
	    fprintf(stderr,"main(): Passive connect failed -- exiting\n");
	  /*
	   * Assume that we have logged problem in command -- No logging.
	   */
	  exiterror("Passive connect failed", 0);
	}

      if ( ret_val == 0 )
	{
	  /* select timedout. Don't consider this an error,  but exit */
	  if (!nolog) soslog(SOSLOG_INFO, SOSLOG_TYPE_CONDITION, 0,
		 "Exit: timing out after %d seconds of inactivity", 
		 select_timeout.tv_sec);
	  exit(0);
	}

      /* 
       * If the user has requested persistance, *both* sides of the link
       * are TCP and at least one side is passive, make the relay into
       * a server.
       */
	 
      if ( persistant && 
	  halfchan[0].proto == IPPROTO_TCP &&
	  halfchan[1].proto == IPPROTO_TCP && 
	  (halfchan[0].disposition == PASSIVE ||
	   halfchan[1].disposition == PASSIVE ) )

	{

	  if ( bsrelaydebug)
	    fprintf(stderr,"Should be persistant\n");

	  switch ( pid=fork() ) 
	    {
	    case 0: 
	      /*
	       *  Child. This code will follow the switch
	       */
	      break;

	    case -1:
	      if (bsrelaydebug)
		fprintf(stderr,"main(): Fork failed -- exiting\n");
	      /*
	       * Assume that we have logged problem in command -- No logging.
	       */
	      exiterror("Fork failed", 1);

	    default:
	      /*
	       * Parent 
	       * Close some decriptors
	       * Erase is_connecting
	       */
	      for (i=0; i< 2; i++) /* Loop over half connections. */
		if ( halfchan[i].proto == IPPROTO_TCP &&
		    halfchan[i].is_connected)
		  {
		    /* Close up new descriptors received from accept() */
		    close (halfchan[i].conninfo.fd);
		    halfchan[i].conninfo.fd = halfchan[i].to_be_closed;
		    if (bsrelaydebug)
		      fprintf(stderr,"main(): parent: Closing fd: %d\n",
			      halfchan[i].conninfo.fd);
		  }

	      halfchan[0].is_connected = 0;
	      halfchan[1].is_connected = 0;

	      goto persistant_loop;
	      break;
	  	    
	    }

	}  

      /*
       * From here to the end of main is run only in the child (assuming
       * bsrealy is running in server mode).
       */
      for (i=0; i< 2; i++) /* Loop over half connections. */
	if ( halfchan[i].proto == IPPROTO_TCP &&
	    halfchan[i].is_connected)
	  /* Close up listenening socket. */
	  close (halfchan[i].to_be_closed);

    } /* End setting up and connecting passive */
  

  if ( complete_connections() < 0 )
    {
      if (interactive)
	(stderr,"main(): Active connection(s) failed\n");
      /*
       * Assume that we have logged problem in command -- No logging.
       */
      exiterror("Active connection(s) failed", 1);
    }


  memset(&sourceinfo, (char)0, sizeof (sourceinfo));
  memcpy (&sourceinfo, &halfchan[0].conninfo, sizeof(sourceinfo));
  memset(&destinfo, (char)0, sizeof (destinfo));
  memcpy (&destinfo, &halfchan[1].conninfo, sizeof(destinfo));


  if ( shouldcheckacl )
    {
      if ( bsrelaydebug )
	fprintf(stderr,"main(): Checking ACL\n");
      ret_val = BS_CheckACL_Num(username, 
				sourceinfo.addr, sourceinfo.port, 
				destinfo.addr, destinfo.port); 

      if (ret_val != AUTH_SUCCEED)
	{
	  if (ret_val == AUTH_FAIL)
	    {
	      soslog(SOSLOG_NOTICE, SOSLOG_TYPE_EVENT, 0,
		     "checkacl: DENY: user %s denied permission srcaddr: %s, srcport: %d, dstaddr: %s, dstport: %d: %s\n",
		     username,
		     sourceinfo.printhost, sourceinfo.port,
		     destinfo.printhost, destinfo.port,
		     bs_auth_extended);
	      if (bsrelaydebug)
		fprintf(stderr, "user %s was denied access\n", username);
	      exitcleanup(0);
	    }
	  else
	    if (!nolog) soslog(SOSLOG_ERR, SOSLOG_TYPE_CONDITION, 1,
		   "checkacl: Error checking ACL: %s", bs_auth_extended);
	}
    }
  else
    {
      if ( bsrelaydebug )
	fprintf(stderr,"I'm NOT checking the ACL\n");
    }
  
  relay[0].rinfo.fd = sourceinfo.fd;
  relay[0].winfo.fd = destinfo.fd;
  relay[1].rinfo.fd = destinfo.fd;
  relay[1].winfo.fd = sourceinfo.fd;

  if ( bsrelaydebug)
    {
      int ssize=-1;
      int len = sizeof(ssize);
      getsockopt(sourceinfo.fd, SOL_SOCKET, SO_SNDBUF, (char *)&ssize, (int *)&len);
      fprintf(stderr, "main(): Socket buffer sizes set to %d\n", ssize);
    }
  
  /*
   * Reset the start time from the value inherited by parent. We don't want
   * the child to appear to have been running for hours before it even starts
   */
  ioinfo.starttime = 0;

  if ( sos_bdrelay((struct sos_bdr_fdpair **)&prelay, (int*)&peer_count,
		   (struct sos_bdr_auxinfo *)&auxinfo)< 0 )
    exiterror("bsrelay did not complete", 1);

  ioinfo.bytes_from_source += relay[0].nbytes;
  ioinfo.bytes_from_dest += relay[1].nbytes;


  exitcleanup(0);
}



  /*
   * This routine is hideous but necessary. It parses the painful command
   * line for tuforw which is quite heavliy structured.
   * 
   * [-t|-u] [-b|-B|-l] [ (<host>|<*ANY>|<host/netmak>).<port> | 
   *			  <host>.<port> [, <host>.<port>]  ]
   */
int
parse_command_line(int ac, int argc, char *argv[])
{
  int chanind=0;		/* Index of current half channel */
  char *argend;			/* Pointer to argend (parsing aid) */
  char *netmask;		/* Pointer to slash in host/net pair */
  struct addrspec *addr;	/* Structure pointer */
  
  while (chanind < 2 )
    {
      if ( !memcmp(argv[ac],"tcp",3))
	halfchan[chanind].proto = IPPROTO_TCP;
      else if ( !memcmp(argv[ac], "udp", 3) )
	halfchan[chanind].proto = IPPROTO_UDP;
      else
	{
	  if ( interactive )
	    fprintf(stderr,"Unknown relay protocol\n");
	  return -1;
	}
      
      INC_AC;
      
      /* Create the new half channed structure and the associated addr list */
      halfchan[chanind].addrspeclist = dll_create ((int *)NULL, (int *)NULL, 
					      DICT_UNORDERED, (int *)NULL);

      /*
       * Host parse. Really evil
       */

      if ( *argv[ac] == '(' ) 
	{
	  int final_addr = 0;
	  char save_char; 
	  char *addrspec = argv[ac];

	  /* 
	   * Forgive this please!!!! We have to code for the Byzantine 
	   * possiblility of "()" as well as the quite normal cases of 
	   * "( address" ( open paren, followed by space, followed by
	   * address specification) and "(address" (open paren followed 
	   * *immediately* by address specification.  Please don't ask why
	   * I'm  not using lex and yacc, you would snort at the answer.
	   * jtt -- who is wholely responsible for this mess.
	   */
	  
	  if ( SOS_STREQ(addrspec, "()" )) 
	    {
	      return -1;	/* Bad input */
	    }
	  if ( SOS_STREQ(addrspec, "("))
	    {
	      INC_AC;		/* Sigleton parens, just inc arg count */
	      addrspec=argv[ac];
	    }
	  else
	    addrspec++;		/* Advance beyond open parens  */
		  
	  /* PASSIVE HOST SPEC */
	  halfchan[chanind].disposition = PASSIVE;
	  while ( !final_addr) {
	  if (argend = strchr(addrspec, ')'))
	    {
	      /* _Final_ address specification in list */
	      if ( *addrspec == ')' )
		{
		  /* 
		   * If close parens starts a token drop to port parse
		   */
		  argend++;
		  break;
		}
	      final_addr++;
	      save_char = *argend;
	      *argend = '\0';
	      
	    }

	  if ( ( addr = allocate_addrspec())  == NULL) 
	    return -1;

	  if ( ( netmask = strchr(addrspec,'/')) != NULL )
	    {
	      /*
	       * Netmask
	       */
	      *netmask='\0'; 
	      
	      if ( parse_netmask((char *)(netmask+1), addr) < 0  )
		/* We have already noted error */
		return -1;
	      
	    }
	  
	  if ( parse_addr(SOS_TRUE, (char *)addrspec, addr) < 0 )
	    return -1;

	  if (bsrelaydebug)
	    fprintf(stderr,"parse_command_line(): After parse_addr(): Addr: %s\n", inet_ntoa(addr->addr));


	  (void)dll_insert(halfchan[chanind].addrspeclist, (dict_obj)addr);


	  /* Restore the 'damage' we did. */
	  if (netmask) 
	    *netmask='/'; 

	  if (argend ) {
	    *argend = save_char; 
	    argend++;		/* Increment beyond close parens */
	  }
	  else 
	    {
	      INC_AC;
	      addrspec = argv[ac];
	    }

	}
	  if ( *argend != '/' )
	    {
	      if ( interactive )
		fprintf(stderr,"Syntax error (no port): **%s**\n", 
			addrspec);
	      return -1;
	    }
	  argend++;
	  
	  if ( !memcmp(argend, "any", 3) )
	    {
	      halfchan[chanind].is_wildcard_port = SOS_TRUE;
	    }

	  halfchan[chanind].port = atoi(argend);

	}
      else 
	{
	  /* ACTIVE HOST SPEC */
	  halfchan[chanind].disposition = ACTIVE;
	  
	  if ( ( addr = allocate_addrspec())  == NULL) 
	    return -1;


	  if ( (argend = strchr(argv[ac], '/') ) == NULL )
	    {
	      if (interactive)
		fprintf(stderr,"Syntax error (no port): **%s**\n",
			argv[ac]);
	      return -1;
	    }
	  
	  *argend = '\0';
	  if ( parse_addr(SOS_FALSE, (char *)(argv[ac]), addr) < 0 )
	    return -1;
	  halfchan[chanind].port = atoi(argend+1);
	  *argend = '/';

	  (void)dll_insert(halfchan[chanind].addrspeclist, (dict_obj)addr);
	}

      if ( bsrelaydebug )
	(void)print_halfchannel(&halfchan[chanind]);

      chanind++;
	
      if (chanind < 2 )
	INC_AC;
    }
  return 0;
}



/* 
 * Parses an *address* from the command line (though it certainly could be
 * something less than a real address (eg a network).
 */
int
parse_addr(int is_passive, char *addrstring, struct addrspec *addr)
{
  if ( bsrelaydebug )
    fprintf(stderr,"parse_addr(): Enter: addrstring: **%s** -- passive: %s\n",
	    addrstring, (is_passive?"TRUE":"FALSE") );

  if (!memcmp (addrstring, "*ANY", 4))
    {
      if ( !is_passive )
	{
	  if (interactive)
	    fprintf(stderr,"Wildcards not allowed in an active connection");
	  return -1;
	}
      addr->is_wildcard_addr = SOS_TRUE;
      return 0;
    }

  if (sos_getabyfoo(addrstring, &(addr->addr)) < 0)
    {
      if (interactive )
	fprintf(stderr,"Bad address: **%s**\n", addrstring);
      return -1;
    }

  if ( bsrelaydebug )
    fprintf(stderr,"Found address: %s\n", inet_ntoa(addr->addr));
  
  return 0;
}



/*
 * Parse out netmask and place it in the addr structure. Netmask can be 
 * must be fully specfied.
 */
int
parse_netmask (char *netmaskstring, struct addrspec *addr)
{
  int a,b,c,d;			/* Cheap parsing aides */
  long tmpaddr=0;			/* Temporary holding for addr */

  if ( bsrelaydebug )
    fprintf(stderr,"parse_netmask(): Enter: netmaskstring: **%s**\n",
	    netmaskstring);

  if ( sos_getabyfoo(netmaskstring, &addr->netmask) < 0 )
    {
      return -1;
    }

  if (bsrelaydebug)
    fprintf(stderr,"Found passive netmask: **%s**\n", 
	    inet_ntoa(addr->netmask));

  addr->has_netmask = SOS_TRUE;

  
  return 0;
}



/*
 * Exit and syslog a message
 */
void exiterror(char *msg, int wanterrno)
{
  char *errmsg = (char *) strerror(errno);

  alarm (0);
  soslog(SOSLOG_ERR, SOSLOG_TYPE_EVENT, 1, msg);

  exitcleanup(1);
}



/*
 * Log exit messages
 */
void exitcleanup(int status)
{
  alarm (0);

  ioinfo.endtime = time(NULL);

  /* **** Must be able to use this **** */
  if (!nolog) soslog(SOSLOG_INFO, SOSLOG_TYPE_CONDITION, 0,
	 "Exit: User :%s:, %d bytes from :%s:, %d bytes from :%s:, %d seconds",
	 username,ioinfo.bytes_from_source,sourceinfo.printhost,
	 ioinfo.bytes_from_dest,destinfo.printhost,ioinfo.endtime-ioinfo.starttime);
  exit(status);
}  



/*
 * Create and initialize an addrspec
 */
struct addrspec *allocate_addrspec()
{
  struct addrspec *new;

  if ( (new = (struct addrspec *)malloc(sizeof(struct addrspec))) == NULL )
    return NULL;

  new->has_netmask = SOS_FALSE;
  new->is_wildcard_addr = SOS_FALSE;
  (void)memset((void *)&(new->addr), (char)0, sizeof(new->addr));
  (void)memset((void *)&(new->netmask), (char)0, sizeof(new->netmask));

  return new;
  
}



/*
 * Debugging for a halfchannel
 */
void 
print_halfchannel(struct halfchannel *chan)
{
  struct addrspec *addr;

  printf ("/**********  HALF CHANNEL SPECIFICATION **********/\n");

  printf("Protocol: %s\n", (chan->proto == IPPROTO_TCP?"tcp":"udp")); 
  printf("Disposition: %s\n", (chan->disposition == ACTIVE?"ACTIVE":"PASSIVE"));
  printf("Port: ");
  if ( chan->is_wildcard_port)
    printf("ANY PORT\n");

  else
    printf("%d\n", chan->port);

  printf ("\t/**** Adress list ****/\n\n");
  for ( addr=(struct addrspec *)dll_maximum(chan->addrspeclist);
       addr != (struct addrspec *)NULL;
       addr=(struct addrspec *)dll_predecessor(chan->addrspeclist, addr) )
    {
      printf("\tAddr: ");
      if ( addr->is_wildcard_addr )
	printf("WILDCARD ADDR\n");
      else
	printf("%s\n", inet_ntoa(addr->addr));

      printf("\tNetmask: ");
      if ( addr->has_netmask )
	printf("%s\n", inet_ntoa(addr->netmask));
      else
	printf("NONE\n");

      printf("\t  ****\n");
    }

  return;
}



/*
 * Setup a passive connection
 */
int
do_passive_setup()
{
  int i;			/* Loop counters */
  int s;			/* New socket */
  struct sockaddr_in passaddr;	/* Passive address to bind. */


  for (i=0; i < 2; i++)
  {
    if ( halfchan[i].disposition == ACTIVE )
      /* We don't care about active connections yet */
      continue;
    
    if ( ( s = socket(AF_INET, (halfchan[i].proto==IPPROTO_TCP?SOCK_STREAM:SOCK_DGRAM), 0)) < 0 )
	{
	  if (interactive)
	    perror("do_passive_setup(): Passive socket failed");
	  return -1;
	}
	
    /*
     * According to our bsdi source. This socket opt should be valid provided
     * socreate() has been called
     */

    if ( socksize  ) 
      if ( setsockopt(s, SOL_SOCKET, SO_SNDBUF,
		      (char *)&socksize, sizeof(socksize) ) < 0 ||
	  setsockopt(s, SOL_SOCKET, SO_RCVBUF,
		      (char *)&socksize, sizeof(socksize) ) < 0 )
	{
	  if (interactive)
	    perror("do_passive_setup(): Setting socket size failed");
	  return -1;
	}
    

    (void)memset(&passaddr, (char)0, sizeof(passaddr));
    passaddr.sin_family = AF_INET;
    passaddr.sin_port = htons (halfchan[i].is_wildcard_port?0:halfchan[i].port);
    passaddr.sin_addr.s_addr = htonl(INADDR_ANY);

    if ( bind (s, (struct sockaddr *)&passaddr, sizeof(passaddr)) < 0 )
      {
	if (interactive)
	  perror("do_passive_setup(): Binding to passive address");
	return -1;
	
      }

    /*
     * If the user requested any port, report back assigned port 
     * (if possible -- don't worry too much about errors).
     */
    if ( passaddr.sin_port == 0 )
      {
	struct sockaddr_in tmp;
	int len=sizeof(tmp);
	
	(void)memset(&tmp, (char)0, len);
	if ( getsockname(s, (struct sockaddr *)&tmp, (int *)&len) < 0 )
	  {
	    return -1;
	  }
	else
	  {
	    printf("Will listen for address specification %d on port %d\n",
		   i+1,   tmp.sin_port); fflush (stdout);
	  }
	  
      }

    if ( halfchan[i].proto == IPPROTO_TCP )
      if ( listen (s, 10) < 0 )
	{
	  if (interactive)
	    perror("do_passive_setup(): Listening on passive connection");
	  return -1;
	}
    
    /* 
     * HACK??! Maybe.  We're going to use the convenient conninfo structure
     * embedded in the halfchan structure to manage some state.  This use is
     * arguably unjustifiable but it helps keep the stuctures more lean and 
     * mean
     */
    halfchan[i].conninfo.fd = s;
    halfchan[i].conninfo.port = passaddr.sin_port;

    passive_count++; 

  }  /* End of for loop (all work should be done within loop) */

  return 0;
}



/*
 * Wait on passive connections, accept, check and cache them. 
 */
static int
do_passive_connect()
{
  int connection_count;		/* Loop counter */
  int i,i2;			/* Loop counter */
  fd_set select_set;		/* FD's to select() on */
  int maxfd;			/* Maximum file descriptor */
  int ret_val;
  struct sockaddr_in peeraddr;	/* Address of connecting peer */
  int peerlen = sizeof(peeraddr); /* Len of connecting peer addr */
  int tmp;			/* Used for swapping to integers. */

  if ( bsrelaydebug )
    fprintf(stderr,"do_passive_connect(): Entering\n");

  connection_count = 0;
  while ( connection_count < passive_count )
    {
      /*
       * Get our select set in order
       */
      FD_ZERO(&select_set);
      for (i=0; i < 2; i++ ) /* Look at all your halfchannels */
	  if ( halfchan[i].disposition == PASSIVE && !halfchan[i].is_connected)
	    {
	      FD_SET(halfchan[i].conninfo.fd, &select_set);
	      if ( bsrelaydebug)
		fprintf(stderr,"do_passive_connect(): Adding %d to select set\n",
			halfchan[i].conninfo.fd);
	    }


      /*
       * XXXX Get tablesize here
       */
      maxfd = 64;

    select_restart: /* BSD (sunos) doesn't automatically restart */

      if ( select_timeout.tv_sec )
	/*
	 * Select with timeout
	 */
	ret_val = select(maxfd, &select_set, 
			 (fd_set *)NULL, (fd_set *)NULL, 
			 (struct timeval *)&select_timeout);
      else
	/*
	 * Select without timeout
	 */
	ret_val = select(maxfd, &select_set, 
			 (fd_set *)NULL, (fd_set *)NULL, 
			 (struct timeval *)NULL);

      if ( ret_val < 0 )
	  {
	    if ( errno == EINTR && childdied ) 
	      {
		/* Don't care about dead kids (evil laugh here) */
		childdied = 0; 
		if ( bsrelaydebug) 
		  fprintf(stderr,"do_passive_connect(): Restarting select()\n");
		goto select_restart;
	      }

	    if (interactive)
	      perror("do_passive_connect(): Selecting");
	    return ret_val;
	    
	  }
      
      if ( ret_val == 0 )
	{
	  if (bsrelaydebug)
	    fprintf(stderr,"do_passive_connect(): Select timed out\n");
	    /* Timeout */ 
	  return ret_val;
	}
      for ( i2=0; i2 < 2; i2++ )
	{
	  if ( halfchan[i2].conninfo.fd < 0 || 
	      !FD_ISSET(halfchan[i2].conninfo.fd, &select_set)  )
	    continue; 

	  /*
	   * "Accept" connection according to protocol 
	   */
	  if ( halfchan[i2].proto == IPPROTO_TCP )
	    ret_val = tcp_accept(halfchan[i2].conninfo.fd,
				 (struct sockaddr *)&peeraddr, 
				 (int *)&peerlen);
	  else
	    ret_val = udp_accept(halfchan[i2].conninfo.fd,
				 (struct sockaddr *)&peeraddr,
				 (int *)&peerlen);
		
	  if ( ret_val == -1 )
	    return -1;

	  if ( halfchan[i2].proto == IPPROTO_TCP )
	    halfchan[i2].to_be_closed = ret_val; /* Save new fd */
	      
	      
	  if ( !check_connection(i2, &peeraddr) )
	    {
	      if ( halfchan[i2].proto == IPPROTO_TCP )
		close (halfchan[i2].to_be_closed); /* Clean up the new FD. */

	      if ( halfchan[i2].proto == IPPROTO_UDP )
		discard_udp_data(halfchan[i2].conninfo.fd);
		  
	      continue;
	    }

	  if (bsrelaydebug)
	    fprintf(stderr,"Adding passive connection: %s\n", 
		    inet_ntoa(*(struct in_addr *)&(peeraddr.sin_addr)));
	  
	  /* 
	   * Add real conninfo
	   * reset to_be_closed so that child process (if forking and tcp)
	   * can close the parent listener
	   */
	  if ( halfchan[i2].proto == IPPROTO_TCP )
	    {
	      tmp = halfchan[i2].conninfo.fd;
	      sos_makeprinthostfromaddr(halfchan[i2].to_be_closed,
					peeraddr.sin_port, 
					&peeraddr,
					&halfchan[i2].conninfo);
	      halfchan[i2].to_be_closed = tmp;
	    }
	  else
	    {
	      sos_makeprinthostfromaddr(halfchan[i2].conninfo.fd,
					peeraddr.sin_port, 
					&peeraddr,
					&halfchan[i2].conninfo);
	    }
	  

	  /*
	   * Call connect if UDP.
	   */
	  if ( halfchan[i2].disposition == IPPROTO_UDP )
	    {
	      if ( bsrelaydebug)
		fprintf(stderr,"do_passive_connect(): Connecting UDP.\n\tchanid: %d\n\tsocket: %d\n\taddress: %s\n\tport: %d\n",
			i2, 
			halfchan[i2].conninfo.fd,
			inet_ntoa(halfchan[i2].conninfo.addr),
			halfchan[i2].conninfo.port);
	      
	      if ( connect(halfchan[i2].conninfo.fd, 
			   (struct sockaddr *)&halfchan[i2].conninfo.addr,
			   sizeof(struct sockaddr)) < 0 )
		{
		  if ( interactive)
		    perror("do_passive_connect(): UDP connecting");
		  return -1;
		}
	    }

	  halfchan[i2].is_connected = 1;
	  connection_count++;

	}
    } /* End looping over passive channels */

  return 1; /* DO NOT RETURN 0 */
}



/*
 * Check to see if a connection should be allowed
 */
int 
check_connection(int chanid, struct sockaddr_in *peeraddr)
{
  struct addrspec *addr;
  int ret_val = SOS_FALSE;
  struct sos_conninfo logname;

  if ( bsrelaydebug)
    fprintf(stderr,"check_connection(): Entering. passid: %d  -- sockaddr: %s\n",
	    chanid, inet_ntoa(peeraddr->sin_addr));

  for ( addr=(struct addrspec *)dll_maximum(halfchan[chanid].addrspeclist);
       addr != (struct addrspec *)NULL;
       addr=(struct addrspec *)dll_predecessor(halfchan[chanid].addrspeclist, addr) )
    {
      if (bsrelaydebug)
	fprintf(stderr,"check_connection(): Checking against addr: %s\n", 
		inet_ntoa(addr->addr));
      if ( addr->has_netmask )
	{
	  
	  if (bsrelaydebug)
	    {
	      fprintf(stderr,"check_connection(): Netmask is: %s\n",
		      inet_ntoa(addr->netmask));
	      
	      fprintf(stderr,"check_connection():\n\targument address:0x%x\n\tmatch address: 0x%x\n\tnetmask: 0x%x\n", 
		      peeraddr->sin_addr.s_addr, 
		      (addr->addr).s_addr, (addr->netmask).s_addr);

	      fprintf(stderr,"check_connection(): Masks\n\targument address:0x%x\n\tmatch address: 0x%x\n", 
		      ((addr->addr).s_addr & (addr->netmask).s_addr),
		      (peeraddr->sin_addr.s_addr & (addr->netmask).s_addr) );
	    }
	  
	  if ( ((addr->addr).s_addr & (addr->netmask).s_addr) == 
	      (peeraddr->sin_addr.s_addr & (addr->netmask).s_addr) )
	    {
	      ret_val = SOS_TRUE;
	      break;
	    }
	}
      else
	{
	  if (bsrelaydebug)
	    fprintf(stderr,"check_connection(): NO NETMASK\n");
	  
	  if ( (addr->addr).s_addr == peeraddr->sin_addr.s_addr )
	    {
	      ret_val = SOS_TRUE;
	      break;
	    }
	}
    }
  

  if ( bsrelaydebug )
    fprintf(stderr,"check_connection(): check %s\n", (ret_val?"passed":"failed"));
    
  /*
   * Log the result of the check. If we can't get 'printhost' name, then
   * just use the ip number
   */
  if ( sos_makeprinthost(0, 0, inet_ntoa(peeraddr->sin_addr), &logname) < 0 )
      if (!nolog) soslog(SOSLOG_INFO, SOSLOG_TYPE_EVENT, SOSLOG_NO_PERROR,
	     "%s connection from %s", (ret_val?"Accepting":"Rejecting"),
	     inet_ntoa(peeraddr->sin_addr));
  else
      if (!nolog) soslog(SOSLOG_INFO, SOSLOG_TYPE_EVENT, SOSLOG_NO_PERROR,
	     "%s connection from %s", (ret_val?"Accepting":"Rejecting"),
	     logname.printhost);


  return ret_val;
}



/*
 * Accept a connection
 */
int 
tcp_accept(int s, struct sockaddr *peeraddr, int *peerlen )
{
  int new_fd;			/* Return value from accept */
  if ( bsrelaydebug )
    fprintf(stderr,"tcp_accept(): Entering: Socket: %d\n", s);

  if ( (new_fd = accept ( s, peeraddr, peerlen )) < 0 )
    {
      if (interactive)
	perror("tcp_accept(): Passive socket failed");
      return -1;
    }
  
  return new_fd;
}



/*
 * Use the MSG_PEEK option to collect the incoming connection address without
 * disturbing the data. Simulates accept() quite nicely.
 */
int 
udp_accept(int s, struct sockaddr *peeraddr, int *peerlen)
{
  char buf[1];			/* Recvfrom *requires a nonnull buffer */
  if ( bsrelaydebug )
    fprintf(stderr,"udp_accept(): Entering: Socket: %d\n", s);

  if ( recvfrom (s, buf, 1, MSG_PEEK, peeraddr, peerlen) < 0 )
    {
      if (interactive)
	perror("udp_accept(): Passive recvfrom failed");
      return -1;
    }
  return 0;
}



/*
 * Make an active connection to remote host
 */
static int
complete_connections()
{
  int i;			/* Loop counter */
  int s;			/* Socket for connection */
  struct addrspec *connectaddrspec; /* Don't ask. */
  struct sockaddr_in actaddr;	/* Active address */

  if ( bsrelaydebug )
    fprintf(stderr,"complete_connections(): Entering\n");

  for (i=0; i < 2; i++ )
    {
      if ( halfchan[i].is_connected )
	continue;

      if ( (s = socket(AF_INET, halfchan[i].proto == IPPROTO_TCP?SOCK_STREAM:SOCK_DGRAM, 0)) < 0 )
	{
	  if (interactive)
	    perror("complete_connections(): Active socket failed");
	  return -1;
	}


      if ( socksize  ) 
	if ( setsockopt(s, SOL_SOCKET, SO_SNDBUF,
			(char *)&socksize, sizeof(socksize) ) < 0 ||
	    setsockopt(s, SOL_SOCKET, SO_RCVBUF,
		       (char *)&socksize, sizeof(socksize) ) < 0 )
	  {
	    if (interactive)
	      perror("complete_connections(): Setting socket size failed");
	    return -1;
	  }

      connectaddrspec=(struct addrspec *)dll_maximum(halfchan[i].addrspeclist);

      (void)memset(&actaddr, (char)0, sizeof(actaddr));
      actaddr.sin_family = AF_INET;
      actaddr.sin_port = htons(halfchan[i].port);
      (void)memcpy(&(actaddr.sin_addr.s_addr), &(connectaddrspec->addr), 
		   sizeof(actaddr.sin_addr.s_addr));

      if (connect(s, (struct sockaddr *)&actaddr, sizeof(actaddr)) < 0 )
	{
	  if (interactive)
	    perror("complete_connections(): Active connect failed");
	  return -1;
	}
      
      sos_makeprinthostfromaddr(s, actaddr.sin_port, &actaddr, 
				&halfchan[i].conninfo);
	
    } /* End Looping over non completed connections */

  return 0;
}



/*
 * Discard all the data from a rejected udp connection.
 */
void 
discard_udp_data(int sock)
{
  char *data;

  if ( (data=malloc(auxinfo.buflen)) == NULL )
    {
      return;
    }
  
  /* Get data and drop it. */
  recvfrom (sock, data, auxinfo.buflen, 0, NULL, 0);

  if ( bsrelaydebug )
    fprintf(stderr,"Discarding udp data: **%s**\n", data);

  (void)memset(data, (char)0, auxinfo.buflen);
  free(data);

  return;
}
