/*

  gre-tun - create a GRE tunnel between two nodes as defined in RFCs 1701
            and 1702.

  Stuff that's broken:

    [1] I only partly handle the header.  I don't accept packets with
        routing information and the sequence numbering doesn't work
        properly on outbound packets.

    [2] For some reason the ip->dst in the packet dump code on inbound
        packets is generating the same as ip->src which is NOT what I 
        want.

    [3] Need to have the local address = first up and running network
        interface IP address.

    [4] Haven't got the checksumming code working yet.

    [5] And, of course, other protocol support would be nice...


  Legal Info:

  GRE-TUN, a GRE Tunnelling software package for FreeBSD.
  Copyright (C) 1999 Michael C. Newell.

  This program is free software; you can redistribute it and/or
  modify it under the terms of the GNU General Public License
  as published by the Free Software Foundation; either version 2
  of the License, or (at your option) any later version.  If you
  add features or correct bugs please send updates to the author
  at "mnewell@spottydogs.org".  All copies of this program or
  derivative works must include this copyright notice.

  This program is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  GNU General Public License for more details.

  The GNU General Public License text is available from the
  Free Software Foundation, Inc., 59 Temple Place - Suite 330, 
  Boston, MA  02111-1307, USA.  It may also be accessed online
  at "http://www.gnu.org/copyleft/gpl.es.html".  

*/

#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <fcntl.h>
#include <netdb.h>
#include <syslog.h>
#include <unistd.h>
#include <sys/signal.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <netinet/in_systm.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <net/if.h>
#include <arpa/inet.h>

/* Local defines */

#define TUNNEL_PROTOCOL    47       /* protocol for the tunnel */
#define PACKET_BUFFER_LEN  8192     /* buffer space size */

/* ARGV parameters */

char         *ProgramName  = NULL;        /* where the program's name will be stored */
char         *SrcName      = NULL;        /* tunnel source address */
char         *DstName      = NULL;        /* tunnel destination address */
char         *TgtName      = NULL;        /* tunnel target host */
char         *LocName      = NULL;        /* tunnel local host */
char         *TunName      = NULL;        /* name of the tunnel */
unsigned int  NetMask      = 0xfffffff8;  /* network mask; default = point-to-point */
unsigned long KeyValue     = 0;           /* key to check */
int           KeyFlag      = 0;           /* don't do key checking by default */    
int           DebugFlag    = 0;           /* Turn debugging on */
int           SequenceFlag = 0;           /* do sequencing */
int           ChecksumFlag = 0;           /* do checksumming */

/* Misc parameters */

int    DetachedFlag     = 0;        /* did we do a detach? */
char  *ProgramBaseName = NULL;      /* name without file stuff */

/* tunnel work parameters */

int   TunnelFD         = -1;       /* File descriptor for the tunnel device */
int   NetworkFD        = -1;       /* File descriptor for network connection */
char *TunnelDeviceName = NULL;     /* Actual path name of the tunnel device */
char *TunnelIFName     = NULL;     /* tunnel name without rest of the stuff */

struct sockaddr      SA_Src;       /* tunnel source socket address block */
struct sockaddr      SA_Dst;       /* tunnel destination socket address */
struct sockaddr      SA_Local;     /* local end address block */
struct sockaddr      SA_Remote;    /* remote end address block */

unsigned long        ReceiveSequence  = 0;  /* Seq # of last received packet */
unsigned long        SendSequence     = 0;  /* Seq # of last sent packet */

/* Prints log messages in the proper context.  If we are detached they are
   sent to syslog; otherwise they are displayed on controlling terminal.  */

#include <stdarg.h>
#define MAX_LOG_MESSAGE 1024
void LogMessage(char *format, ...) {
  char      buffer[MAX_LOG_MESSAGE];
  va_list   ap;
  va_start(ap,format);
  vsnprintf(buffer,MAX_LOG_MESSAGE,format,ap);
  va_end(ap);
  if (DetachedFlag) {
    /* do something with syslog */
   } else {
    fputs(buffer,stderr);
  }
}

/* Turns us off */

void CloseTunnel();
void ShutDown(const int code) {
  if (DebugFlag) LogMessage("Info[Die]: Shutting down/n");
  CloseTunnel(); exit(code);
}

/* Sets the address on a sockaddr_in structure. */

void SetAddress(char *name, struct sockaddr_in *sainp) {

  bzero((char *)sainp, sizeof(struct sockaddr_in));
  sainp->sin_family = AF_INET;
  if ((sainp->sin_addr.s_addr = inet_addr(name)) == (u_long) -1) {
    struct hostent *hep = gethostbyname(name);
    if (!hep) {
      fprintf(stderr,"Error[SetAddress]: Host name lookup failure for '%s'\n",
              name);
      ShutDown(1);
    }
    sainp->sin_family = hep->h_addrtype;
    bcopy(hep->h_addr, (caddr_t)&sainp->sin_addr, hep->h_length);    
  }
}

/* Routine to open the tunnel */

int OpenTunnel(char *tunname) {
  int                   tunfd;
  int                   sock;
  char                 *cp;
  char                  buffer[1024];
  struct ifreq          ifrb;
  unsigned int          nm;

  /* Get the tunnel device; if none is supplied try to find one. */

  if (tunname == NULL) {
    int ii;
    if ((TunnelDeviceName = (char *) malloc(16)) == NULL) {
      fprintf(stderr,
              "Error[OpenTunnel]: Cannot allocate device name buffer; %s\n",
              strerror(errno));
      exit(1);
    }
    for (ii = 0; ii < 100; ++ii) {
      sprintf(TunnelDeviceName,"/dev/tun%d",ii);
      if (DebugFlag) 
        fprintf(stderr,"Debug[OpenTunnel]: Trying tunnel '%s'\n",
                TunnelDeviceName);
      if ((tunfd = open(TunnelDeviceName,O_RDWR))  > 0) {
        if (DebugFlag) fprintf(stderr,"Info[OpenTunnel]: Using tunnel device '%s'\n",
                               TunnelDeviceName);
        break;
       } else if (errno != EBUSY) {
        fprintf(stderr,
          "Error[OpenTunnel]: Cannot find an available tunnel device; %s\n",
          strerror(errno)); 
        exit(1);
      }
    }
   } else {
    TunnelDeviceName = strdup(tunname);
    if (DebugFlag) 
      fprintf(stderr,"Debug[OpenTunnel]: Opening the specified tunnel '%s'\n",
              TunnelDeviceName);
    if ((tunfd = open(TunnelDeviceName,O_RDWR))  < 0) {
      fprintf(stderr,"Error: cannot open device '%s'; %s\n",
              TunnelDeviceName,strerror(errno));
      exit(1);
    }
  }
  if ((cp = strrchr(TunnelDeviceName,'/')) != NULL) 
     TunnelIFName = strdup(++cp);
    else TunnelIFName = strdup(TunnelDeviceName);
  if (DebugFlag) fprintf(stderr,"Debug[OpenTunnel]: Open device '%s', 
                         interface '%s'\n",
                         TunnelDeviceName,TunnelIFName);

  /* Grab a socket on which to communicate. */

  if ((sock = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
    fprintf(stderr,"Error[OpenTunnel]: Cannot allocate a socket; %s\n",
            strerror(errno)); 
    ShutDown(1);
  }

  /* Clear the current interface IP address. */

  bzero(&ifrb, sizeof(ifrb));
  strncpy(ifrb.ifr_name,TunnelIFName,IFNAMSIZ);

  if (ioctl(sock,SIOCDIFADDR,&ifrb) < 0) {
    if (errno != EADDRNOTAVAIL) {
      fprintf(stderr,"Error[OpenTunnel]: Can't clear previous addresses; %s\n",
              strerror(errno));
      ShutDown(1);
    }
  }

  /* Set the proper IP address */

  bcopy(&SA_Src,&ifrb.ifr_addr,sizeof(SA_Src)); 
  ((struct sockaddr_in *)&ifrb.ifr_addr)->sin_len = sizeof(struct sockaddr_in);
  if (ioctl(sock,SIOCSIFADDR,&ifrb) < 0) {
    fprintf(stderr,"Error[OpenTunnel]: Cannot set tunnel source address on '%s'; %s\n",
            ifrb.ifr_name,strerror(errno));
    ShutDown(1);
  }

  /* Now the proper destination.  Note that I can do the address setting with a
     single socket alias call, but I'm trying to be really generic here... */

  bcopy(&SA_Dst,&ifrb.ifr_addr,sizeof(SA_Dst)); 
  ((struct sockaddr_in *)&ifrb.ifr_addr)->sin_len = sizeof(struct sockaddr_in);
  if (ioctl(sock,SIOCSIFDSTADDR,&ifrb) < 0) {
    fprintf(stderr,"Error[OpenTunnel]: Cannot set tunnel destination address on '%s'; %s\n",
            ifrb.ifr_name,strerror(errno));
    ShutDown(1);
  }

  /* Ok, now set the netmask. */

  ifrb.ifr_addr.sa_family = AF_INET;
  nm = htonl(NetMask); bcopy(&nm,&((struct sockaddr_in *)&ifrb.ifr_addr)->sin_addr,sizeof(nm));
  ((struct sockaddr_in *)&ifrb.ifr_addr)->sin_len = sizeof(nm);
  if (ioctl(sock,SIOCSIFNETMASK,&ifrb) < 0) {
    fprintf(stderr,"Error[OpenTunnel]: Cannot set netmask on tunnel '%s'; %s\nn",
            ifrb.ifr_name,strerror(errno));
    ShutDown(1);
  }

  /* Now turn it on! */

  if (ioctl(sock,SIOCGIFFLAGS, &ifrb) < 0) {
    fprintf(stderr,"Error[OpenTunnel]: cannot get flags for '%s'; %s\n",
            ifrb.ifr_name, strerror(errno));
    ShutDown(1);
  }
  ifrb.ifr_flags |= IFF_UP;
  if (ioctl(sock,SIOCSIFFLAGS,&ifrb) < 0) {
    fprintf(stderr,"Error[OpenTunnel]: Cannot set '%s' to UP; %s\n",
            ifrb.ifr_name,strerror(errno));
    ShutDown(1);
  }
  close(sock);
  
  return tunfd;
}

/* Closes the tunnel.  Almost no error checking; I'm a lazy bastard
   and I want to get to the cool stuff... */

void CloseTunnel() {
  int                   sock;
  struct ifreq          ifrb;

  if (DebugFlag) LogMessage("Info[CloseTunnel]: Closing tunnel device\n");

  /* Grab a socket on which to communicate. */

  if ((sock = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
    LogMessage("Error[CloseTunnel]: Cannot allocate a socket; %s\n",
               strerror(errno)); exit(1);
  }

  /* try to clear the UP flag */

  bzero((char *)&ifrb,sizeof(ifrb));
  strncpy(ifrb.ifr_name,      TunnelIFName,IFNAMSIZ);
  if (ioctl(sock,SIOCGIFFLAGS, &ifrb) < 0) {
    LogMessage("Warning[CloseTunnel]: Cannot read tunnel state flags; %s\n",
               strerror(errno));
   } else {
    ifrb.ifr_flags &= ~(IFF_UP | IFF_RUNNING);
    if (ioctl(sock,SIOCSIFFLAGS,&ifrb) < 0) 
      LogMessage("Warning[CloseTunnel]: cannot unset 'up' flag; %s\n",
                 strerror(errno));
  }

  /* Clear the IP addresses */

  bzero(&ifrb.ifr_addr,sizeof(struct sockaddr)); 
  if (ioctl(sock,SIOCSIFADDR,&ifrb) < 0) {
    fprintf(stderr,"Error[OpenTunnel]: Cannot clear tunnel source address on '%s'; %s\n",
            ifrb.ifr_name,strerror(errno));
    ShutDown(1);
  }
  if (ioctl(sock,SIOCSIFDSTADDR,&ifrb) < 0) {
    fprintf(stderr,"Error[OpenTunnel]: Cannot clear tunnel destination address on '%s'; %s\n",
            ifrb.ifr_name,strerror(errno));
    ShutDown(1);
  }

#ifdef NOWAY
  /* Try to clear the IP addresses */

  bzero((char *)&ifarb,sizeof(ifarb));
  strncpy(ifarb.ifra_name,TunnelIFName,IFNAMSIZ);
  if (ioctl(sock,SIOCDIFADDR,&ifarb) < 0) {
    if (errno != EADDRNOTAVAIL)
      fprintf(stderr,
              "Warning[CloseTunnel]: Cannot clear previous addresses; %s\n",
              strerror(errno));
  }
#endif

  closelog(); close(TunnelFD); close(sock);

}

/* Parses out a subnet mask; looks for hex (0x...), octal (0...), and 
   dotted quads. */

unsigned int ParseNetMask(char *string) {
  char         *cp;
  unsigned int  mask=0;

  while (*string == ' ') string++;

    /* Dotted quad */

  if ((cp = strchr(string,'.')) != NULL) {
    unsigned int  jj = 0;
    while (-1) {
      unsigned int ii = 0;
      if (isdigit(*string)) {
        while (isdigit(*string)) ii = 10*ii + (*string++ - '0');
        if (ii > 255) {
          fprintf(stderr,"Error: invalid octet in netmask\n"); exit(1);
        }
        mask = (mask << 8) + ii;
       } else if (*string == '.') {
        if (++jj > 3) {
          fprintf(stderr,"Error: too many octets in netmask\n");
          exit(1);
        }
        ++string; 
        if (!isdigit(*string)) {
          fprintf(stderr,"Error: syntax error in netmask near '%s'\n",string);
          exit(1);
        }
       } else if (*string == '\0') {
	 if (jj < 3) {
           fprintf(stderr,"Error: too few octets in netmask\n"); 
           exit(1);
         }
         return mask;
       } else {
        fprintf(stderr,"Error: invalid netmask\n"); exit(1);
      }
    }
  }

    /* Hexadecmial */

  if (!strncmp(string,"0x",2)) {
    char *hexdigs = "0123456789abcdef";
    string += 2;
    while ((cp = strchr(hexdigs,*string)) != NULL) {
      mask = (mask << 4) + (unsigned int) (cp - hexdigs); 
      if (*++string == '\0') break;;
    }
    if (*string != '\0') {
      fprintf(stderr,"Error: invalid hex digit at '%s' in netmask\n",string);
      exit(1);
    }
    return mask;
  }

    /* Octal */

  if (*string == '0') {
    while (isdigit(*string)) {
      unsigned int ii = *string++ - '0';
      if (ii > 7) {
        fprintf(stderr,"Error: invalid octal digit at '%s' in netmask\n",string);
        exit(1);
      }
      mask = (8*mask) + ii;
    }
    if (*string != '\0') {
      fprintf(stderr,"Error: invalid digit at '%s' in netmask\n",string);
      exit(1);
    }
    return mask;
  }
  fprintf(stderr,"Error: invalid subnet mask '%s'\n",string); exit(1);
}

/* Parses off an unsigned long  value */

unsigned long ParseUL(char *string) {
  unsigned long  ul = 0;
  char          *cp;
    /* Hexadecmial */

  if (!strncmp(string,"0x",2)) {
    char *hexdigs = "0123456789abcdef";
    string += 2;
    while ((cp = strchr(hexdigs,*string)) != NULL) {
      ul = (ul << 4) + (unsigned int) (cp - hexdigs); 
      if (*++string == '\0') break;;
    }
    if (*string != '\0') {
      fprintf(stderr,"Error: invalid hex digit at '%s'\n",string);
      exit(1);
    }
    return ul;
  }

    /* Octal */

  if (*string == '0') {
    while (isdigit(*string)) {
      unsigned int ii = *string++ - '0';
      if (ii > 7) {
        fprintf(stderr,"Error: invalid octal digit at '%s' in netmask\n",string);
        exit(1);
      }
      ul = (8*ul) + ii;
    }
    if (*string != '\0') {
      fprintf(stderr,"Error: invalid digit at '%s'\n",string);
      exit(1);
    }
    return ul;
  }

    /* Decimal */

  while (isdigit(*string)) ul = 10*ul + (*string++ - '0');
  if (*string != '\0') {
    fprintf(stderr,"Error: invalid digit at '%s'\n",string); exit(1);
  }
  return ul;
  
}

/* Parses off the argument list */

void ParseArgs(char **argv) {
  char *cp;

  ProgramName = *argv;
  ProgramBaseName = 
    ((cp = strrchr(ProgramName,'/')) == NULL) ? ProgramName : ++cp;

  while (*++argv) {
    int argl = strlen(*argv);
    int arglmin = (argl > 3) ? argl : 3;
    if (!strcmp(*argv,"-debug")) {
      ++DebugFlag;
     } else if (!strncmp(*argv,"-source",arglmin)) {
      if ((SrcName = *++argv) == NULL) {
        fprintf(stderr,"Error: missing source address\n"); exit(1);
      }
     } else if (!strncmp(*argv,"-destination",arglmin)) {
      if ((DstName = *++argv) == NULL) {
        fprintf(stderr,"Error: missing destination address\n"); exit(1);
      }
     } else if (!strncmp(*argv,"-remote",arglmin)) {
      if ((TgtName = *++argv) == NULL) {
        fprintf(stderr,"Error: missing target address\n"); exit(1);
      }
     } else if (!strncmp(*argv,"-local",arglmin)) {
      if ((LocName = *++argv) == NULL) {
        fprintf(stderr,"Error: missing local host address\n"); exit(1);
      }
     } else if (!strncmp(*argv,"-tunnel",arglmin)) {
      if ((TunName = *++argv) == NULL) {
        fprintf(stderr,"Error: missing tunnel device name\n"); exit(1);
      }
     } else if (!strncmp(*argv,"-netmask",arglmin)) {
      char *cp = *++argv;
      if (cp == NULL) {
        fprintf(stderr,"Error: missing netmask\n"); exit(1);
      }
     } else if (!strncmp(*argv,"-key",arglmin)) {
      char *cp = *++argv;
      if (cp == NULL) {
        fprintf(stderr,"Error: missing key value\n"); exit(1);
      }
      KeyValue = ParseUL(cp); KeyFlag = -1;
     } else if (!strncmp(*argv,"-sequence",arglmin)) {
       SequenceFlag = -1;
     } else {
      if (strcmp(*argv,"-help")) 
        fprintf(stderr,"Error: unknown flag '%s'\n",*argv);
      fprintf(stderr,"Usage: %s {options}\n",ProgramName);
      fputs("  options:\n",stderr);
      fputs("\t-source\t\t<srcaddress>\tset tunnel source address\n",stderr);
      fputs("\t-destination\t<dstaddress>\tset tunnel destination address\n",
            stderr);
      fputs("\t-remote\t\t<tgtaddress>\tset address of remote tunnel host\n",
            stderr);
      fputs("\t-local\t\t<locaddress>\tset address of local tunnel host\n",
            stderr);
      fputs("\t-tunnel\t\t<tundevice>\tset tunnel device name\n",stderr);
      fputs("\t-netmask\t<mask>\t\tset tunnel network mask\n",stderr);
      fputs("\t-key\t\t<key>\t\tset the tunnel key\n",stderr);
      fputs("\t-sequence\t\t\tturn on packet sequencing\n",stderr);
      fputs("\t-checksum\t\t\tturn on packet checksumming\n",stderr);
      fputs("\t-debug\t\t\t\tturn on debugging\n",stderr);
      exit(1);
     }
  }

  /* Make sure adequate parameters are set */

  if (SrcName == NULL) SrcName = LocName;
  if (DstName == NULL) DstName = TgtName;
  if (LocName == NULL) {
    fprintf(stderr,"Error: local address not specified\n"); exit(1);
  }
  if (TgtName == NULL) {
    fprintf(stderr,"Error: target address not specified\n"); exit(1);
  }

  if (DebugFlag) {
    fprintf(stderr,"Debugging           = ON\n");
    fprintf(stderr,"Source address      = %s\n",SrcName);
    fprintf(stderr,"Destination address = %s\n",DstName);
    fprintf(stderr,"Local address       = %s\n",LocName);
    fprintf(stderr,"Remote address      = %s\n",TgtName);
    fprintf(stderr,"Tunnel device       = %s\n",TunName);
    fprintf(stderr,"Subnet mask         = 0x%08x\n",NetMask);
    fprintf(stderr,"Key                 = 0x%08x (%s)\n",KeyValue,(KeyFlag)?"set":"not set");
    fprintf(stderr,"Sequence numbering  = %s\n",(SequenceFlag)?"Yes":"No");
  }
}

/* Dumps a packet to the log area.  I should take the time to do this right
   and decode the packet, but I'm really lazy... */

void DumpPacket(char *buffer, int len) {
  char   hexline[1024];
  char   ascline[1024];
  char  *bp=buffer, *hp=hexline, *ap=ascline;
  char  *hexdigits="0123456789abcdef";
  char  *fmt = "    %s     %s\n";
  int    ii=0;

  while (len--) {
    int  ch = (0x000000ff) & (int) *buffer++;
    ++ii;
    *hp++ = hexdigits[0x0f & (ch >> 4)]; *hp++ = hexdigits[0x0f & ch]; 
    *ap++ = ((ch >= ' ') && (ch <= '~')) ? ch : '.';
    if (ii == 16) {
      *ap = '\0'; *hp= '\0'; LogMessage(fmt,hexline,ascline); 
      hp = hexline; ap = ascline; ii = 0;
    }
  }

  if (ii) {
    while (ii < 16) { *ap++ = ' '; *hp ++ = ' '; *hp ++ = ' '; ++ii; }
    *ap = '\0'; *hp= '\0'; LogMessage(fmt,hexline,ascline);
  }
}

#define GRE_FLAG_CHECKSUM 0x8000
#define GRE_FLAG_ROUTING  0x4000
#define GRE_FLAG_KEY      0x2000
#define GRE_FLAG_SEQUENCE 0x1000
#define GRE_FLAG_SRCRTE   0x0800
#define GRE_MASK_RECUR    0x0700
#define GRE_MASK_FLAGS    0x00F8
#define GRE_MASK_VERSION  0x0007

/*  Processes a packet received over the GRE tunnel */

ProcessGREPacket(struct ip *ipp, int len) {
  int            iphl  = ipp->ip_hl << 2;
  char          *pp    = (char *)ipp + iphl;
  unsigned short flagsver;
  unsigned short protocol;
  unsigned short checksum;
  unsigned short offset = 0;
  unsigned long  key;
  unsigned long  sequence;
  unsigned long  routing;

  len -= iphl;

  /* First off get the flags and protocol which we know are there */

  flagsver = ntohs(*((unsigned short *)pp)++);  len -= sizeof(unsigned short);
  protocol = ntohs(*((unsigned short *)pp)++);  len -= sizeof(unsigned short);

  if (DebugFlag)
    LogMessage("Debug[ProcessGREPacket]: flags = 0x%04x, protocol = 0x%04x\n",
               flagsver, protocol);

  if (flagsver & (GRE_FLAG_CHECKSUM | GRE_FLAG_ROUTING)) {
    checksum = ntohs(*((unsigned short *)pp)++); len -= sizeof(unsigned short);
    offset   = ntohs(*((unsigned short *)pp)++); len -= sizeof(unsigned short);
    if (DebugFlag)
      LogMessage("Debug[ProcessGREPacket]: Checksum = 0x%04x, offset = 0x%04x\n",
                 checksum, offset);
  }

  if (flagsver & GRE_FLAG_KEY) {
    key = ntohl(*((unsigned long *)pp)++); len -= sizeof(unsigned long);
    if (DebugFlag)
      LogMessage("Debug[ProcessGREPacket]: Key = 0x%08x\n",key);
    if (KeyFlag) {
      if (key != KeyValue) {
        LogMessage("Warning[ProcessGREPacket]: Received key = 0x%08x, expecting 0x%08x\n",
                   key,KeyValue);
        return;
      } 
     } else {
	 LogMessage("Warning[ProcessGREPacket]: Received key = 0x%08x but keys not expected\n",
                    key,KeyValue);
    }
   } else if (KeyFlag) {
    LogMessage("Warning[ProcessGREPacket]: Expecting keys but none supplied\n");
    return;
  }

  if (flagsver & GRE_FLAG_SEQUENCE) {
    sequence = ntohl(*((unsigned long *)pp)++); len -= sizeof(unsigned long);
    if (DebugFlag)
      LogMessage("Debug[ProcessGREPacket]: Sequence # = 0x%08x\n",sequence);
    if (++ReceiveSequence != sequence) {
      LogMessage("Warning[ProcessGREPacket]: Received sequence = %d, expecting %d\n",
                 sequence,ReceiveSequence);
      ReceiveSequence = sequence;
    }
  }

  if (flagsver & GRE_FLAG_ROUTING) {
    LogMessage("Warning[ProcessGREPacket]: Routing flag set; not supported\n");
    return;
  }

  if (flagsver & GRE_FLAG_SRCRTE) {
    LogMessage("Warning[ProcessGREPacket]: Source routing flag set; not supported\n");
    return;
  }

  if (flagsver & GRE_MASK_RECUR) {
    LogMessage("Warning[ProcessGREPacket]: Recursion control set; not supported\n");
    return;
  }

  if (flagsver & GRE_MASK_VERSION) {
     LogMessage("Warning[ProcessGREPacket]: Version set to %d, should be zero\n",
                flagsver & GRE_MASK_VERSION);
  }

  ipp = (struct ip *)pp;
  if (DebugFlag) {
    LogMessage("Debug[main]: Raw IP packet from %s to %s len %d: \n",
      inet_ntoa(ipp->ip_src),
      inet_ntoa(ipp->ip_dst),len);
    DumpPacket((char *)ipp,len);
  }
  if (write(TunnelFD,(char *)ipp,len) <= 0)
    LogMessage("Warning[main]: Failed to write packet to tunnel; %s\n",
               strerror(errno));
  return;
}

/* Processes a packet heading outbound.  This is probably less effient than it
   needs to be, but... We will NOT generate packets with routing information.  */

ProcessOutboundPacket(struct ip *ipp, int len) {
  char            buffer[PACKET_BUFFER_LEN];
  int             bufflen  = 4;
  unsigned short *flags    = (unsigned short *)&buffer;
  unsigned short *protocol = (unsigned short *)&buffer[2];
  char           *buffp    = &buffer[4];
  unsigned short *checksum = NULL;
  unsigned short *offset   = NULL;
  unsigned long  *seqnum   = NULL;
  unsigned long  *key      = NULL;


  if (DebugFlag) {
    LogMessage("Received packet from tunnel to '%s' length = %d:\n",inet_ntoa(ipp->ip_dst),len);
    DumpPacket((char *)ipp,len);
  }
  
  /* Set the appropriate flags and protocol in the header */

  *flags = 0; 
  if (ChecksumFlag) { 
    *flags |= GRE_FLAG_CHECKSUM; 
    checksum = ((unsigned short *)buffp)++; bufflen += sizeof(unsigned short);
    offset   = ((unsigned short *)buffp)++; bufflen += sizeof(unsigned short);
    *checksum = 0; *offset = 0;
  }
  if (KeyFlag) { 
    *flags |= GRE_FLAG_KEY; 
    key = ((unsigned long *)buffp)++; bufflen += sizeof(unsigned long);
    *key = htonl(KeyValue);
  }
  if (SequenceFlag) { 
    *flags |= GRE_FLAG_SEQUENCE;
    seqnum = ((unsigned long *)buffp)++; bufflen += sizeof(unsigned long);
    *seqnum = ++SendSequence;
  }
  *flags = htons(*flags); *protocol = htons(0x0800);

  /* Copy the packet into the buffer and send it. */

  if ((bufflen + len) > PACKET_BUFFER_LEN) {
    LogMessage("Error[ProcessOutboundPacket]: buffer overflow; length = %d\n",len);
    return;
  }
  bcopy(ipp,buffp,len); bufflen += len;
  if (DebugFlag) {
    LogMessage("Debug[ProcessOutboundPacket]: Packet length = %d\n",bufflen);
    DumpPacket(buffer,bufflen);
  }
  if (send(NetworkFD,buffer,bufflen,0) <= 0)
    LogMessage("Warning[main]: Can't send packet to remote; %s\n", strerror(errno));

}


/* MAIN program! */

main(int argc, char **argv) {
  fd_set      readfds, writefds, exceptfds;
  int         nfd;
  int         maxfd;
  char        pbuffer[PACKET_BUFFER_LEN];

  /* Parse the argument list */

  ParseArgs(argv);

  /* Set up the socket address blocks so we know that they are valid,
     then open the tunnel. */

  SetAddress(SrcName,(struct sockaddr_in *)&SA_Src);   
  SetAddress(DstName,(struct sockaddr_in *)&SA_Dst);
  SetAddress(LocName,(struct sockaddr_in *)&SA_Local); 
  SetAddress(TgtName,(struct sockaddr_in *)&SA_Remote);
  TunnelFD = OpenTunnel(TunName);

  /* Open the syslog */

  openlog(ProgramBaseName,LOG_PID, LOG_DAEMON);

  /* Open a connection to the remote host */

  if ((NetworkFD = socket(AF_INET, SOCK_RAW, TUNNEL_PROTOCOL)) < 0) {
    LogMessage("Error[main]: Cannot establish socket to remote host; %s\n",
               strerror(errno));
    ShutDown(1);
  }
  if (connect(NetworkFD,&SA_Remote,sizeof(struct sockaddr_in)) < 0) {
    LogMessage("Error[main]: Cannot connect remote stream to socket; %s\n",
               strerror(errno));
    ShutDown(1);
  }

  /* If we are not debugging now would be a good time to detach. */

  if (!DebugFlag) {
    if (daemon(0,0) < 0) {
      fprintf(stderr,"Error[main]: Cannot daemonize; %s\n",strerror(errno)); 
      ShutDown(1);
    }
  }

  /* Install signal handlers */

  signal(SIGHUP,ShutDown); signal(SIGTERM,ShutDown);

  /* Now do the process loop */

  maxfd = (TunnelFD > NetworkFD) ? TunnelFD : NetworkFD;
  while (-1) {

      /* Set up for the select() */

    FD_ZERO(&readfds); FD_ZERO(&writefds); FD_ZERO(&exceptfds); 
    FD_SET(TunnelFD,&readfds); FD_SET(NetworkFD,&readfds);
    if ((nfd = select(maxfd+10,&readfds,&writefds,&exceptfds,NULL)) < 0) {
      LogMessage("Error[main]: Main select was interrupted\n"); ShutDown(1);
     } else if (nfd == 0) {
      LogMessage("Error[main]: Main select returned zero!\n"); ShutDown(1);
    }
    if (DebugFlag) LogMessage("Debug[main]: Select returned %d\n",nfd);

      /* Check for socket packet; note that it's gotta come from our 
         partner or we will reject it. */

    if (FD_ISSET(NetworkFD,&readfds)) {
      struct ip          *ipp   = (struct ip *)pbuffer;
      struct sockaddr_in *sainp = (struct sockaddr_in *) &SA_Remote;
      int len = read(NetworkFD,pbuffer,sizeof(pbuffer));
      if (DebugFlag) { 
        LogMessage("Received packet from socket from %s length = %d:\n",
                   inet_ntoa(ipp->ip_src),len); 
        DumpPacket(pbuffer,len);
      }

      /* If the packet came from our partner deal with it; else don't */

      if (ipp->ip_src.s_addr == sainp->sin_addr.s_addr) {
        ProcessGREPacket(ipp,len);
       } else {
	 /* Packet is not from our partner; ignore it */
        LogMessage("Warning[main]: Received packet from '%s'\n",
                    inet_ntoa(ipp->ip_src));
      }
    }
      /* Check for tunnel packet */

    if (FD_ISSET(TunnelFD,&readfds)) {
      int         len = read(TunnelFD,&pbuffer,sizeof(pbuffer));
      struct ip  *ipp = (struct ip *)&pbuffer;
      ProcessOutboundPacket(ipp,len);
    }
  }
}

