/*
spa daemon - sniffs packets, decrypts, sends commands via a FIFO pipe to the 
             spa firewall daemon

Compile using:
gcc -O3 -Wall -o spad spad.c -lpcap

27Jul2005
Added several more items to the config file. Got the UDP code working properly.

19Jul2005
Fixed bug in the execl call. Changed how SPA data is shoveled down the FIFO so
that there are now three parameters -- ip address, fifo length, and data.
Makes things easier on the spa_engine.pl end to get the data and gives us
error checking.

15Jul2005
Full amount of data wasn't making it down the FIFO, so am now calculating the
size of the payload based off of packet size minus the headers, Added a
realloc to prevent segfaults.

08Jul2005
General cleanup of code, added some more comments, fixed a few error handling
bugs.

07Jul2005
More bug fixes, added a lot of debugging code, eliminated a fairly bad bug that
caused a heap overflow (fuck!) which was potentially exploitable (double fuck!).

23Jun2005
Passes data to FIFO, both ip address and data, or ip address and port if
doing firewall stuff.

11Apr2005
More cleanup.

24Feb2005
Initial attempt.
 */


/* includes */
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <syslog.h>
#include <fcntl.h>
#include <limits.h>
#include <locale.h>
#include <net/if.h>
#include <linux/sockios.h>
#include <sys/ioctl.h>
#include <time.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <netinet/tcp.h>
#include <netinet/udp.h>
#include <arpa/inet.h>
#include <endian.h>
#include <ctype.h>
#include <pcap.h>
#define __USE_GNU
#include <string.h>

/* some constants */
#define MAX_LINE 256
#define SPA_FIFO "/var/spa/spa"
#define SPA_CONF "/usr/local/etc/spa.conf"

/* globals */
struct bpf_program pcapfilter;
struct in_addr net, mask;
pcap_t *pd;
char pcap_err[PCAP_ERRBUF_SIZE];
u_char *ebuf;
char *dev = NULL;
int spa_fd, fw_fd;
int link_offset, firewall, verbose, debug, console;
struct in_addr my_addr;

union gpg_id {
  char char_server_id[4];
  unsigned long long_server_id;
} gpg_id;

struct payload_packet
{
  char payload[1400];
};

/*
 * This is the heart of the spa daemon. Currently supported is TCP and UDP. This
 * routine looks at every packet we receive, sees if the packet is addressed to
 * us, whether it is TCP or UDP, and checks the packet's contents for our GPG user
 * ID. On TCP packets only packets with the SYN flag are checked. Otherwise, if
 * we are doing firewalling, we pass the source address and destination port on
 * regular traffic (ACK packets) to maintain "state".
 *
 * The trick here is we don't watch for session setup and teardown to maintain
 * timers on "how long" we are seeing traffic for ports that have been opened, we
 * are seeing the regular traffic and simply pass it along. A lack of regular
 * traffic will lead to an eventual timeout.
 */
void process_packets(unsigned char *data1, struct pcap_pkthdr* h, unsigned char *p)
{
  struct ip* ip = (struct ip *)(p + link_offset);
  struct tcphdr* tcp = (struct tcphdr *)(ip + 1);
  struct udphdr* udp = (struct udphdr *)(ip + 1);
// not yet on icmp, errors compiling due to gnu vs non-gnu shit, will
// figure it out later
//  struct icmphdr *icmp = (struct icmphdr *)(ip + 1);
  struct payload_packet *tcp_data = (struct payload_packet *)(tcp + 1);
  struct payload_packet *udp_data = (struct payload_packet *)(udp + 1);
//  struct payload_packet icmp_data = (struct payload_packet )(icmp + 1);
  int found=0,i=0,j=0,datasize=0,datalen=0;

// ok if I try to free one of these later, I get an occassional double free
// error -- is glibc as fucked as I think it is, or do I have some other
// bug? maybe a REAL coder should look at this shite
  char *buf = (char *)malloc(sizeof(buf));
  char *buf2 = (char *)malloc(sizeof(buf2));

  // length of packet
  datalen = ntohs(ip->ip_len);

  if(my_addr.s_addr == ip->ip_dst.s_addr)
  {
    switch(ip->ip_p)
    {
      case IPPROTO_TCP:
        // we don't want to see a rst or fin packet
        if(tcp->rst || tcp->fin) break;
        // recalc the length of the payload by subtracting the header
        datalen = datalen - 40;
        // on tcp, we check the syn packets as well as out gpg id within
        // that syn packet at a specific offset. we check it two ways as
        // we've seen some odd byte ordering (endian issues)
	// also note, syn packets with payloads may get dropped by
	// certain firewalls, maybe I'll make that a configurable option
	// to not use/look for the syn and grab say an ack or something
        if((tcp->syn) &&
          (((tcp_data->payload[8] == gpg_id.char_server_id[3]) &&
            (tcp_data->payload[9] == gpg_id.char_server_id[2]) &&
            (tcp_data->payload[10] == gpg_id.char_server_id[1]) &&
            (tcp_data->payload[11] == gpg_id.char_server_id[0])) ||
           ((tcp_data->payload[9] == gpg_id.char_server_id[3]) &&
            (tcp_data->payload[8] == gpg_id.char_server_id[2]) &&
            (tcp_data->payload[11] == gpg_id.char_server_id[1]) &&
            (tcp_data->payload[10] == gpg_id.char_server_id[0]))))
        {
           // got one
           if(console) 
             printf("TCP user packet received from %s (%d bytes)\n",(char *)inet_ntoa(ip->ip_src),datasize);
           else
             syslog(LOG_INFO,"TCP user packet received from %s (%d bytes)",(char *)inet_ntoa(ip->ip_src),datasize);
           found = 1;

           // we need to send ip address, total length, and data portion thru
           // the fifo, so we have to calc the total length of the fifo's
           // payload. the four covers space and final \n\n on the payload
           datasize = strlen((char *)inet_ntoa(ip->ip_src)) + datalen + 4;

           // gawd this is butt ugly but we have to know how much space our
           // datasize takes up in the payload as a part of a string
           if(datalen>0) datasize++;
           if(datalen>9) datasize++;
           if(datalen>99) datasize++;
           if(datalen>999) datasize++;

           // realloc our space
           buf2 = realloc((char *)buf2,datasize);
           for(i = 0; i < datasize; i++) buf2[i] = 0;

           // now we build our fifo buffer, first the ip address and total
           // fifo length go in
           sprintf(buf2,"%s %d ",(char *)inet_ntoa(ip->ip_src),datasize);
           j = datasize - datalen - 2;
           // next we add the data from the packet
           for(i=j;i<(datalen+j);i++)
             buf2[i] = tcp_data->payload[i-j];
           // cap it off with a couple of \n's
           buf2[datasize-2] = 0x0a;
           buf2[datasize-1] = 0x0a;

           // then we send it
           if(debug)
           {
             if(console)
               printf("Sending SPA payload... \n");
             else
               syslog(LOG_INFO,"DEBUG: Sending SPA payload... ");
           }
           j = write(spa_fd,buf2,datasize);
           if(debug)
           {
             if(console)
               printf("Sent %d bytes via FIFO\n",j);
             else
               syslog(LOG_INFO,"DEBUG: Sent %d bytes via FIFO",j);
           }
        }

        // if we didn't find our GPG payload and we are doing firewall stuff
        // we want to show regular traffic moving for tracking in the firewall
        if((!found) && (firewall) && (!tcp->syn))
        {
          if(console)
            printf("Sending TCP packet from %s (dst port %d) for firewall timer processing\n",(char *)inet_ntoa(ip->ip_src),ntohs(tcp->dest));
          else
            syslog(LOG_INFO,"Sending TCP packet from %s (dst port %d) for firewall timer processing",(char *)inet_ntoa(ip->ip_src),ntohs(tcp->dest));
          sprintf(buf, "%s %d\n",(char *)inet_ntoa(ip->ip_src),ntohs(tcp->dest));
          if(debug)
          {
            if(console)
              printf("Sending '%s' (%d bytes) via FIFO\n",buf,strlen(buf));
            else
              syslog(LOG_INFO,"DEBUG: Sending '%s' (%d bytes) via FIFO",buf,strlen(buf));
          }
          j = write(spa_fd,buf,strlen(buf));
          if(debug)
          {
            if(console)
              printf("Sent %d bytes via FIFO\n",j);
            else
              syslog(LOG_INFO,"DEBUG: Sent %d bytes via FIFO",j);
          }
        }
        break;
      case IPPROTO_UDP:
        // same as above, basically, except udp
        datalen = datalen - 28;
        if(((udp_data->payload[8] == gpg_id.char_server_id[3]) &&
            (udp_data->payload[9] == gpg_id.char_server_id[2]) &&
            (udp_data->payload[10] == gpg_id.char_server_id[1]) &&
            (udp_data->payload[11] == gpg_id.char_server_id[0])) ||
           ((udp_data->payload[9] == gpg_id.char_server_id[3]) &&
            (udp_data->payload[8] == gpg_id.char_server_id[2]) &&
            (udp_data->payload[11] == gpg_id.char_server_id[1]) &&
            (udp_data->payload[10] == gpg_id.char_server_id[0])))
        {
           // got one
           if(console)
             printf("UDP user packet received from %s (%d bytes)\n",(char *)inet_ntoa(ip->ip_src),datasize);
           else
             syslog(LOG_INFO,"UDP user packet received from %s (%d bytes)",(char *)inet_ntoa(ip->ip_src),datasize);
           found = 1;

           // recalc our buffer size
           datasize = strlen((char *)inet_ntoa(ip->ip_src)) + datalen + 4;

           //ugly but we have to know how much space our datasize takes up
           if(datalen>0) datasize++;
           if(datalen>9) datasize++;
           if(datalen>99) datasize++;
           if(datalen>999) datasize++;

           // realloc our space
           buf2 = realloc((char *)buf2,datasize);
           for(i = 0; i < datasize; i++) buf2[i] = 0;

           // now we build our fifo buffer, first the ip address and total
           // fifo length go in
           sprintf(buf2,"%s %d ",(char *)inet_ntoa(ip->ip_src),datasize);
           j = datasize - datalen - 2;

           // add in the payload from the packet
           for(i=j;i<(datalen+j);i++)
             buf2[i] = tcp_data->payload[i-j];
           // cap it off with a couple of \n's
           buf2[datasize-2] = 0x0a;
           buf2[datasize-1] = 0x0a;

           if(debug)
           {
             if(console)
               printf("Sending SPA payload...\n");
             else
               syslog(LOG_INFO,"DEBUG: Sending SPA payload... ");
           }
           j = write(spa_fd,buf2,datasize);
           if(debug)
           {
             if(console)
               printf("Sent %d bytes via FIFO\n",j);
             else
               syslog(LOG_INFO,"DEBUG: Sent %d bytes via FIFO",j);
           }
        }

        // if we didn't find our gpg id, and we are firewalling stuff,
        // send it along
        if(!found && firewall)
        {
          if(console)
            printf("Sending UDP packet from %s (dst port %d) for firewall timer processing\n",(char *)inet_ntoa(ip->ip_src),ntohs(udp->dest));
          else
            syslog(LOG_INFO,"Sending UDP packet from %s (dst port %d) for firewall timer processing",(char *)inet_ntoa(ip->ip_src),ntohs(udp->dest));
          sprintf(buf, "%s %d\n",(char *)inet_ntoa(ip->ip_src),ntohs(udp->dest));
          if(debug)
          {
            if(console)
              printf("Sending '%s' (%d bytes) via FIFO\n",buf,strlen(buf));
            else
              syslog(LOG_INFO,"DEBUG: Sending '%s' (%d bytes) via FIFO",buf,strlen(buf));
          }
          j = write(spa_fd,buf,strlen(buf));
          if(debug)
          {
            if(console)
              printf("Sent %d bytes via FIFO\n",j);
            else
              syslog(LOG_INFO,"DEBUG: Sent %d bytes via FIFO",j);
          }
        }
        break;
      case IPPROTO_ICMP:
/*        if((icmp_data.payload[8] == GPG_SERVER_ID[0]) &&
           (icmp_data.payload[9] == GPG_SERVER_ID[1]) &&
           (icmp_data.payload[10] == GPG_SERVER_ID[2]) &&
           (icmp_data.payload[11] == GPG_SERVER_ID[3]))
        {
           provide_data(inet_ntoa(ip->ip_src), icmp_data);
        } */
        break;
      default:
        break;
    }
  } 
}

/*
 * Your basic sniffing loop....
 */
void receive_packets(void)
{
  int snaplen = 65535, promisc = 1, to_ms = 1000;

  if (pcap_lookupnet(dev,&net.s_addr,&mask.s_addr, pcap_err) == -1)
  {
    perror(pcap_err);
    exit(-1);
  }

  if ((pd = pcap_open_live(dev, snaplen, promisc, to_ms, pcap_err)) == NULL)
  {
    perror(pcap_err);
    exit(-1);
  }

  switch(pcap_datalink(pd))
  {
    case DLT_EN10MB:
    case DLT_IEEE802:
      link_offset = 14;
      break;
    case DLT_SLIP:
      link_offset = 16;
      break;
    case DLT_PPP:
    case DLT_NULL:
      link_offset = 4;
      break;
    case DLT_RAW:
      link_offset = 0;
      break;
    default:
      fprintf(stderr,"unsupported interface type\n");
      exit(-1);
  }

  while (pcap_loop(pd,0,(pcap_handler)process_packets,0));
}

/*
 * For the time being, I am assuming you need an IP address on the device you
 * are sniffing on to get this daemon running, so this routine pulls the IP
 * address from the specified interface.
 */
unsigned long get_myaddr(const char *device)
{
  struct ifreq ifr;
  register struct sockaddr_in *sin;
  int fd;

  fd = socket(PF_INET, SOCK_DGRAM, 0);
  if (fd < 0) return(0);

  memset(&ifr, 0, sizeof(ifr));
  sin = (struct sockaddr_in *)&ifr.ifr_addr;
  strncpy(ifr.ifr_name, device, sizeof(ifr.ifr_name));

  ifr.ifr_addr.sa_family = AF_INET;

  if (ioctl(fd, SIOCGIFADDR, (char*) &ifr) < 0)
  {
    close(fd);
    return(0);
  }
  close(fd);
  return (sin->sin_addr.s_addr);
}

/*
 * This routine is an exercise in reading text from a text "config" file. It is
 * a sad and arduous process, yet it works. Granted, one fucking space extra and 
 * the whole thing sucks ass. This is definitely NOT one of C's strong suits...
 */
int process_config_file(char *cf)
{
  FILE *spa_conf = NULL;
  char buf_conf[MAX_LINE+1];
  int line = 0;
  int confdev = 0,j = 0;
  int ret = 0;

  spa_conf = fopen(cf,"r");
  if(spa_conf == NULL)
  {
    if(console)
      printf("Cannot find configuration file\n");
    else
      syslog(LOG_INFO,"Cannot find configuration file");
    exit(-1);
  }
  buf_conf[MAX_LINE] = 0;

  while(!feof(spa_conf))
  {
    fgets(buf_conf,MAX_LINE,spa_conf);
    line++;
    if((buf_conf[0] == '#') || (buf_conf[0] == '\n')) continue;
    if(buf_conf[strlen(buf_conf)-1] == '\n') buf_conf[strlen(buf_conf)-1] = 0;

    if(!strncasecmp(buf_conf,"FIREWALL",8))
    {
      if(debug)
      {
        if(console) printf("Firewall option processed in config\n");
        else syslog(LOG_INFO,"DEBUG: Firewall option processed in config");
      }
      if((!strncasecmp(strdup(strchr(buf_conf,' ')+1),"YES",3)) ||
         (!strncasecmp(strdup(strchr(buf_conf,' ')+1),"1",1)))
      {
        if(debug)
        {
          if(console) printf("Firewall processing is on\n");
          else syslog(LOG_INFO,"DEBUG: Firewall processing is on");
        }
        firewall = 1;
      }
      else
      {
        if(debug)
        {
          if(console) printf("Firewall processing is off\n");
          else syslog(LOG_INFO,"DEBUG: Firewall processing is off");
        }
      }
      continue;
    }
    if((!strncasecmp(buf_conf,"INTERFACE",9)) && (dev == NULL))
    {
      j = strlen(buf_conf);
      /* parse for lo (for testing), eth1, iwlan0 etc*/
      if((j < 12) || (j > 16))
      {
        if(debug)
        {
          if(console) printf("Invalid interface option in config\n");
          else syslog(LOG_INFO,"DEBUG: Invalid interface option in config");
        }
        ret = -1;
      }
      else 
      {
        dev = (char *)strndup(strchr(buf_conf,' ')+1,j - 10);
        confdev = 1;
        if(debug)
        {
          if(console) printf("Using interface %s\n",dev);
          else syslog(LOG_INFO,"DEBUG: Using interface %s",dev);
        }
        continue;
      }
    }
    if(!strncasecmp(buf_conf,"ID",2))
    {
      j = strlen(buf_conf);
      /* parse for ID */
      if(j > 13)
      {
        if(debug)
        {
          if(console) printf("Invalid server ID in config\n");
          else syslog(LOG_INFO,"DEBUG: Invalid server ID in config");
        }
        ret = -1;
      }
      else
      {
        gpg_id.long_server_id = strtoul(strchr(buf_conf,' ')+1,NULL,0);
        if(debug)
        {
          if(console) printf("Looking for server GPG ID %s\n",strchr(buf_conf,' ')+1);
          else syslog(LOG_INFO,"DEBUG: Looking for server GPG ID %s",strchr(buf_conf,' ')+1);
        }
        continue;
      }
    }
    // stuff in conf we don't care about
    if(!strncasecmp(buf_conf,"SERVER",6))
      continue;
    if(!strncasecmp(buf_conf,"PACKETTIMEOUT",13))
      continue;
    if(!strncasecmp(buf_conf,"FIREWALLTIMEOUT",15))
      continue;
    if(!strncasecmp(buf_conf,"DEBUG",5))
      continue;
    if(!strncasecmp(buf_conf,"FIREWALLSTATE",13))
      continue;
    if(!strncasecmp(buf_conf,"USERCONF",8))
      continue;
    if(!strncasecmp(buf_conf,"IPFW",4))
      continue;
    if(!strncasecmp(buf_conf,"GPGDIR",6))
      continue;
    // non-fatal errors parsing the conf file (should these be fatal?)
    if(console)
      printf("Unable to parse line %d of %s\n",line,cf);
    else
      syslog(LOG_INFO,"Unable to parse line %d of %s",line,cf);
  }
  fclose(spa_conf);
  /* some kind of default */
  if((dev != NULL) && (confdev))
  {
    if(console)
      printf("Listening on interface defined in config file: %s\n",dev);
    else
      syslog(LOG_INFO,"Listening on interface defined in config file: %s",dev);
  }
    
  return(ret);
}

//  usage routine
void usage(char *prog)
{
  fprintf(stdout,"USAGE:\n%s <options>\n",prog);
  fprintf(stderr,"Options include:\n");
  fprintf(stderr," -c <filename>  read in config file from alternate location\n");
  fprintf(stderr," -v             verbose output\n");
  fprintf(stderr," -C             console mode, don't fork, good for testing\n");
  fprintf(stderr," -D             debugging output, good for finding problems\n");
  fprintf(stderr," -h             this help screen\n\n");
}

int main(int argc, char **argv)
{
  pid_t pid;
  char *prog;
  char * spa_conf_name = NULL;
  extern char *optarg;
  extern int optind;
  extern int optopt;
  extern int opterr;
  char ch;
  int ret;

  /* defaults */
  prog = argv[0];
  firewall = 0;
  verbose = 0;
  debug = 0;
  console = 0;

  /* process cmd line args */
  while ((ch = getopt(argc, argv, "CDhvc:i:")) != EOF)
    switch(ch)
    {
      case 'C':
        console = 1;
        break;
      case 'D':
        debug = 1;
        break;
      case 'h':
        usage(prog);
        exit(0);
      case 'v':
        verbose = 1;
        break;
      case 'c':
        if(spa_conf_name)
        {
          fprintf(stderr,"You cannot use -c more than once\n");
          exit(-1);
        }
        spa_conf_name = strdup(optarg);
        break;
      case 'i':
        if(dev)
        {
          fprintf(stderr,"You cannot use -i more than once\n");
          exit(-1);
        }
        dev = strdup(optarg);
        break;
      default:
        usage(prog);
        exit(-1);
    }

  /* say hello... */
  if(console)
  {
    printf("SPA - Single Packet Authentication\nCopyright (c) 2005 NMRC  spa@nmrc.org\n");
    printf("SPA daemon started, uid %d\n",getuid());
  }
  else
    syslog(LOG_INFO,"SPA daemon started, uid %d",getuid());

  if(dev != NULL)
  {
    if(console)
      printf("Listening on interface defined on command line: %s\n",dev);
    else
      syslog(LOG_INFO,"Listening on interface defined on command line: %s",dev);
  }

  /* assign a conf name if not given on the cmd line */
  if(!spa_conf_name) spa_conf_name = SPA_CONF;

  /* read in and process the conf file */
  ret = process_config_file(spa_conf_name);
  if(ret == -1)
  {
    if(console) printf("Error opening config file %s\n",spa_conf_name);
    else syslog(LOG_INFO,"Error opening config file %s",spa_conf_name);
    exit(-1);
  }

  /* can't listen to nothing, assign a default... */
  if(dev == NULL)
  {
    dev = "eth0";
    if(console)
      printf("Listening on default interface: %s\n",dev);
    else
      syslog(LOG_INFO,"Listening on default interface: %s",dev);
  }

  /* probe the network device and get its assigned address */
  my_addr.s_addr = get_myaddr(dev);

  if(my_addr.s_addr == 0)
  {
    if(console) printf("Unable to get IP address from %s\n",dev);  
    else syslog(LOG_INFO,"Unable to get IP address from %s",dev);
  }

  if(console) printf("Listening for SPA packets on %s\n",(char *)inet_ntoa(my_addr));
  else syslog(LOG_INFO,"Listening for SPA packets on %s ",(char *)inet_ntoa(my_addr));

  /* open the FIFO to talk to the spa engine */
  mkfifo(SPA_FIFO, 0770);

  spa_fd = open(SPA_FIFO, O_RDWR);
  if(spa_fd == -1)
  {
    if(console) printf("Error opening SPA FIFO %s\n",SPA_FIFO);
    else syslog(LOG_INFO,"Error opening SPA FIFO %s",SPA_FIFO);
    exit(-1);
  }

  /* if in console mode, don't fork */
  if(!console)
  {
    pid = fork();
    switch(pid)
    {
      case -1:
        fprintf(stderr,"Unable to fork off, exiting\n");
        exit(-1);
      case 0: // XXX
        // start spa engine
        execl("/usr/local/sbin/spa_engine.pl","&", 0);
        break;
      default:
        receive_packets();
    }
  }
  else
  {
    execl("/usr/local/sbin/spa_engine.pl", "&", 0);
    receive_packets();
  }
  exit(-1);
}

