/* Copyright 2000 by James M. Kretchmar
 *
 * Permission to use, copy, modify, and distribute this software and
 * its documentation for any purpose and without fee is hereby
 * granted, provided that the above copyright notice and this
 * permission notice appear in all copies and in supporting
 * documentation.  No representation is made about the suitability of
 * this software for any purpose.  It is provided "as is" without
 * express or implied warranty.
 */

#include <stdio.h>
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <string.h>
#include <ctype.h>
#include "oak.h"

int main(int argc, char **argv) {
  oak_matchline *m;
  FILE *logfile;
  char buff[LINE];
  int i, j, x, y, ret, first, prescan;
  char *host, *replacement, *logfilename;
  struct stat statbuf;
  ino_t inode;
  off_t fptr;
  oak_matchlist *global_matchlist;
  oak_queuelist *global_queuelist;
  
  if (DEBUG) printf("DEBUG: starting oak\n");

  /* if it's -v print the version */
  if ((argc==2) && (!strcmp(argv[1], "-v"))) {
    printf("oak version %s\n", VERSION);
    exit(0);
  }
  
  /* otherwise if it's not -c <config> then print usage */
  if (argc!=3 || (strcmp(argv[1], "-c"))) {
    if (argc==2 && !strcmp(argv[1], "-h")) {
      print_usage(0);
      exit(0);
    } else {
      print_usage(1);
      exit(1);
    }
  }

  /* read in the config file */
  readconfig(argv[2]);

  /* set some useful local variables */
  global_matchlist=oak_global_get_matchlist(&g);
  global_queuelist=oak_global_get_queuelist(&g);
  logfilename=oak_global_get_logfile(&g);

  /* do we have any prescaning queues? */
  prescan=0;
  j=oak_list_get_size(global_queuelist);
  for (i=0; i<j; i++) {
    if (queue_is_prescan(oak_list_get_element(global_queuelist, i))) prescan=1;
  }

  /* open the logfile */
  if (DEBUG) printf("DEBUG: opening log file\n");
  if ((logfile=fopen(logfilename, "r"))==NULL) {
    perror("could not open logfile");
    exit(1);
  }
  setbuf(logfile, NULL);

  /* save the file pointer at the end of the file */
  fptr=lseek(fileno(logfile), 0, SEEK_END);
  if (DEBUG) printf("DEBUG: current file pointer is %i\n", (int) fptr);
  /* if we're prescanning zoom back to the beginning, otherwise stay at the end */
  if (prescan) lseek(fileno(logfile), 0, SEEK_SET);
  
  /* cache the inode */
  ret=fstat(fileno(logfile), &statbuf);
  if (ret) {
    perror("Could not stat logfile");
    exit(3);
  }
  inode=statbuf.st_ino;
  
  /* deamonize it here if not debug */
  if (!DEBUG) {
    for(i=0; i<getdtablesize(); i++) {
      if (i!=fileno(logfile)) close(i);
    }

    i=fork();
    if (i == -1) {
      fprintf(stderr, "couldn't fork, going to die\n");
      exit(2);
    } else if (i != 0) {
      /* parent exits */
      exit(0);
    }

    setsid();
    chdir("/");
    umask(0);
  }

  /* main loop */
  if (DEBUG) printf("DEBUG: entering main loop\n");
  if (prescan) {
    first=1;
  } else {
    if (DEBUG) printf("DEBUG: skipping prescanning\n");
    first=0;
  }
  replacement=malloc(LINE);
  memset(replacement, 0, LINE);
  while(1) {
    sleep(1);

    /* read a line and check it against all the matchlines */
    clearerr(logfile);
    while (fgets(buff, LINE, logfile)) {
      host=get_host_from_log(buff);
      if (DEBUG) printf("DEBUG: found a new line in the infile from host %s.\n", host);

      if (!oak_global_host_is_ok(&g, host)) {
	if (DEBUG) printf("DEBUG: host %s is being filtered, skipping.\n", host);
	continue;
      }

      j=oak_list_get_size(global_matchlist); /* cache this, don't recompute */
      for (i=0; i<j; i++) {
	m=oak_list_get_element(global_matchlist, i);
	if (matchline_string_matches(m, buff, replacement, oak_global_is_nukepid(&g), oak_global_is_nukeciscopid(&g))) {
	  oak_queuelist *ql;
	  if (DEBUG) printf("DEBUG: got a match\n");
	  if (DEBUG && replacement) printf("DEBUG: got back %s\n", replacement);
	  ql=matchline_get_queuelist(m);
	  x=oak_list_get_size(ql);
	  for (y=0; y<x; y++) {
	    oak_queue *q;
	    q=oak_list_get_element(ql, y);
	    if (first && !queue_is_prescan(q)) continue;
	    if (!queue_is_locking(q)) {
	      queue_add_msg(q, replacement, host);
	    } else {
	      if (!matchline_locked_for_queue(m, q)) {
		queue_add_msg(q, replacement, host);
		matchline_set_lock(m, q);
	      } else {
		if (DEBUG) printf("DEBUG: discarding message due to lock\n");
	      }
	    }
	  }
	  break; /* don't process any more matchlines if found */
	}
      }

      if (first && (lseek(fileno(logfile), 0, SEEK_CUR) >= fptr)) {
	if (DEBUG) printf("DEBUG: past the filepointer now\n");
	first=0;
      }
      free(host);
    }

    /* check if it's time to hit any of the fire actions */
    j=oak_list_get_size(global_queuelist);
    for (i=0; i<j; i++) {
        oak_queue *q;
	q=oak_list_get_element(global_queuelist, i);
	if (fire_is_time(queue_get_fire(q))) {
	  if (DEBUG) printf("DEBUG: time to fire, probing for msgs ...\n");
	  if (queue_get_nummsgs(q) > 0) {
	    int r,s;
	    oak_list *al;
	    if (DEBUG) printf("DEBUG: executing actions\n");
	    al=queue_get_actionlist(q);
	    s=oak_list_get_size(al);
	    for (r=0; r<s; r++) {
	      action_execute(oak_list_get_element(al, r), q);
	    }
	    queue_delete_msgs(q);
	  }
	}
    }

    /* check that the file hasn't moved */
    ret=stat(logfilename, &statbuf);
    if (ret) {
      while (1) {
	sleep(10);
	ret=stat(logfilename, &statbuf);
	if (!ret) break;
      }
    }
    if (inode!=statbuf.st_ino) {
      if (DEBUG) printf("DEBUG: closing old log file\n");
      fclose(logfile);
      if (DEBUG) printf("DEBUG: opening new log file\n");
      if ((logfile=fopen(logfilename, "r"))==NULL) {
	/* some kind of warning */
	exit(1);
      }
      inode=statbuf.st_ino;
      setbuf(logfile, NULL);
    }

  }
  
}

void readconfig(char *file) {
  FILE *configfile;
  int line, ret;
  oak_queue *q=NULL;
  oak_queue *tmpq;
  oak_matchline *ml=NULL;
  oak_action *tmpaction=NULL;
  oak_list *actionlist;
  char *ptr;
  char buff[LINE];
  if (DEBUG) printf("DEBUG: reading config file\n");

  /* set up defaults etc */
  oak_global_init(&g);

  /* create the default trash queue */
  tmpq=queue_create("trash", NULL, NULL);
  oak_list_append_element(oak_global_get_queuelist(&g), tmpq);

  /* read the config file here */
  if (!(configfile=fopen(file, "r"))) {
    perror("Could not open configfile");
    exit(1);
  }

  line=0;
  while(fgets(buff, LINE, configfile)) {
    line++;

    /* remove initial spaces and tabs */
    ptr=buff;
    while ((ptr[0]==' ') || (ptr[0]=='\t')) ptr++;

    /* ignore comments */
    if (ptr[0]=='#') continue;
    
    /* ignore blank lines */
    if (ptr[0]=='\n') continue;

    /* nuke a trailing newline */
    if (ptr[strlen(ptr)-1]=='\n') ptr[strlen(ptr)-1]='\0';

    if (!strncmp(ptr, "set ", 4)) {
      ptr+=4;
      if (!strncmp(ptr, "infile ", 7)) {
	ptr+=7;
	oak_global_set_logfile(&g, ptr);
	if (DEBUG) printf("DEBUG: set logfile to %s.\n", oak_global_get_logfile(&g));
      } else if (!strcmp(ptr, "nukepid")) {
	oak_global_set_nukepid(&g);
	if (DEBUG) printf("DEBUG: set nukepid\n");
      } else if (!strcmp(ptr, "nukeciscopid")) {
	oak_global_set_nukeciscopid(&g);
	if (DEBUG) printf("DEBUG: set nukeciscopid\n");
      } else if (!strncmp(ptr, "ignorehosts ", 12)) {
	ret=oak_global_set_ignorehosts(&g, ptr+12);
	if (ret<0) {
	  fprintf(stderr, "ignorehosts set while onlyhosts is already in use.\n");
	  exit(1);
	}
	if (DEBUG) printf("DEBUG: set ignorehosts\n");
      } else if (!strncmp(ptr, "onlyhosts ", 10)) {
	ret=oak_global_set_onlyhosts(&g, ptr+10);
	if (ret<0) {
	  fprintf(stderr, "onlyhosts set while ignorehosts is already in use.\n");
	  exit(1);
	}
	if (DEBUG) printf("DEBUG: set onlyhosts\n");
      } else {
	fprintf(stderr, "unknown set variable on line %i in configfile\n", line);
	exit(1);
      }
    } else if (!strncmp(ptr, "define ", 7)) {
      ptr+=7;
      if (!strncmp(ptr, "queue ", 6)) {
	ptr+=6;
	actionlist=NULL;
	/* while (ptr[strlen(ptr)-1]==' ' || ptr[strlen(ptr)-1]=='\t') ptr[strlen(ptr)-1]='\0'; */
	q=queue_create(ptr, NULL, NULL);
	oak_list_append_element(oak_global_get_queuelist(&g), q);
      } else {
	fprintf(stderr, "unknown define type on line %i in configfile\n", line);
	exit(1);
      }
      if (DEBUG) printf("DEBUG:\nDEBUG: added queue %s\n", queue_get_name(q));
    } else if (!strncmp(ptr, "prescan", 7)) {
      queue_set_prescan(q);
    } else if (!strncmp(ptr, "action ", 7)) {
      ptr+=7;
      /* memset(strchr(ptr, ' '), 0, 1); */
      if (!q) {
	fprintf(stderr, "action statement with no queue defined on line %i in config\n", line);
	exit(1);
      }

      tmpaction=action_create_from_argstring("silly", ptr);
      if (DEBUG) printf("DEBUG: created action with name %s and args %s\n", "silly", ptr);

      if (!actionlist) {
	actionlist=malloc(sizeof(oak_list));
	oak_list_create(actionlist);
	queue_set_actionlist(q, actionlist);
      }
      oak_list_append_element(actionlist, tmpaction);
      if (DEBUG) printf("DEBUG: added action '%s' to queue %s\n", action_get_name(tmpaction), queue_get_name(q));
    } else if (!strncmp(ptr, "action-limits ", 14)) {
      int a, b, c, d;
      char *tmpptr;
      ptr+=14;
      if (!tmpaction) {
	fprintf(stderr, "action-limits statement with no action specified on line %i in config\n", line);
	exit(1);
      }
      a=atoi(ptr);
      tmpptr=strchr(ptr, ' ')+1;
      b=atoi(tmpptr);
      tmpptr=strchr(tmpptr, ' ')+1;
      c=atoi(tmpptr);
      tmpptr=strchr(tmpptr, ' ')+1;
      d=atoi(tmpptr);
      action_set_limits(tmpaction, a, b, c, d);
    } else if (!strncmp(ptr, "fire ", 5)) {
      oak_fire *tmpfire;
      ptr+=5;
      if (!q) {
	fprintf(stderr, "fire statement with no queue defined on line %i in config\n", line);
	exit(1);
      }
      tmpfire=fire_create(ptr);
      queue_set_fire(q, tmpfire);
      if (DEBUG) printf("DEBUG: added fire %s to queue %s\n", ptr, queue_get_name(q));
    } else if (!strncmp(ptr, "locking ", 8)) {
      ptr+=8;
      if (!q) {
	fprintf(stderr, "locking statement with no queue defined on line %i in config\n", line);
	exit(1);
      }
      queue_set_locktime_fromstring(q, ptr);
      if (DEBUG) printf("DEBUG: added locktime of %i queue %s\n", queue_get_locktime(q), queue_get_name(q));
    } else if (!strncmp(ptr, "header ", 7)) {
      ptr+=7;
      if (!q) {
	fprintf(stderr, "header statement with no queue defined on line %i in config\n", line);
	exit(1);
      }
      queue_set_header(q, ptr);
      if (DEBUG) printf("DEBUG: added header %s to queue %s\n", queue_get_header(q), queue_get_name(q));
    } else if (!strncmp(ptr, "on ", 3)) {
      ptr+=3;
      ml=matchline_create(ptr, NULL);
      oak_list_append_element(oak_global_get_matchlist(&g), ml); /* add  matchline ml to the list */
      if (DEBUG) printf("DEBUG:\nDEBUG: Created matchlline and appended it to global list\n");
    } else if (!strncmp(ptr, "queues ", 7)) {
      ptr+=7;
      if (!ml) {
	fprintf(stderr, "queues statement with no matchline defined on line %i in config\n", line);
	exit(1);
      }
      ret=matchline_set_queuelist_from_string(ml, ptr);
      if (ret) {
	fprintf(stderr, "undefined queue used on line %i in config\n", line);
	exit(1);
      }
	
      if (DEBUG) printf("DEBUG: added queues to matchlist\n");
    } else {
      char tmpbuff[LINE], *ptr2;
      ptr2=ptr;
      ptr2=strchr(ptr, ' ');
      strncpy(tmpbuff, ptr, ptr2-ptr);
      fprintf(stderr, "Uknown directive '%s' on line %i in config\n", tmpbuff, line);
      exit(1);
    }
					   

  }
}


char *get_line_from_log(char *log) {
  char *ptr, *ptr2, *out;

  ptr=strchr(log, ':');
  if (ptr) {
    ptr2=strchr(ptr+1, ':');
  }
  ptr2+=4;

  if (isdigit((int) ptr2[0]) && isdigit((int) ptr2[1]) && isdigit((int) ptr2[2]) && isdigit((int) ptr2[3]) && ptr2[4] == ' ') {
    ptr2+=5;
  }

  ptr=strchr(ptr2, ' ')+1;

  out=malloc(strlen(ptr)+10);
  strcpy(out, ptr);

  /* strip the trailing new line */
  if (out[strlen(out)-1]=='\n') {
    out[strlen(out)-1]='\0';
  }

  return(out);
}


char *get_host_from_log(char *log) {
  char *ptr, *ptr2, *out;

  ptr=strchr(log, ':');
  if (!ptr) {
    /* do something drastic otherwise the next line will fail */
  }
  ptr2=strchr(ptr+1, ':');
    
  ptr2+=4;

  if (isdigit((int) ptr2[0]) && isdigit((int) ptr2[1]) && isdigit((int) ptr2[2]) && isdigit((int) ptr2[3]) && ptr2[4] == ' ') {
    ptr2+=5;
  }

  ptr=strchr(ptr2, ' ')+1;

  out=malloc(strlen(ptr2)+10); /* more than we need but OK */
  strncpy(out, ptr2, ptr-ptr2);
  out[(ptr-ptr2)-1]='\0';

  return(out);

}


void print_usage(int err) {
  FILE *file;

  if (err) {
    file=stderr;
  } else {
    file=stdout;
  }

  fprintf(file, "usage:\n");
  fprintf(file, "       oak -c <configfile>       run oak\n");
  fprintf(file, "       oak -v                    print oak version\n");
}
