/*

                                   _JNJ`                        
                               .JNMH`                           
                              JMMF`       `;.                   
                            .NMM)          `MN.                 
                            MMM)            (MML                
                           (MMM`             MMML               
                     M  I  D  N  I  G  H  T    C  o  D  E       
                           (NMMF             MHNH               
                            NMML            .MMM                
                             NMML          .NMH                 
                              4MMNL       .#F                   
                               `4HNNL_   `                      
                                   `"""`                        

                          Copyright (C) 2004-2007
       "Ian (Larry) Latter" <ian dot latter at midnightcode dot org>

    Midnight Code is a registered trademark of Ian Latter.

    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, and mirrored at the Midnight Code web
    site; as at version 2 of the License only.
  
    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.
  
    You should have received a copy of the GNU General Public License
    (version 2) along with this program; if not, write to the Free
    Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
    02111-1307 USA, or see http://midnightcode.org/gplv2.txt

*/

/* midnightcode-filter.c :: Midnight Code packet filter library		*/


#include <stdio.h>
#include <string.h>
#include <stdarg.h>
#include <stdlib.h>
#include <unistd.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#include "preferences.h"
#include "midnightcode-list.h"
#include "midnightcode-process.h"
#include "midnightcode-filter.h"

/*

int
midnightcode_config_parseline(char *config_buf, int config_buf_max,
                              struct midnightcode_config_head_t * elements,
                              int type, int options) {
  char *args[64];
  int  arg_max, arg_index;
  char *config_buf_p;
  int  config_buf_offset;
  char parse_buf[4096];
  int  config_command, config_structure, config_section;
  int  config_variable, config_value;
  int  exists;
  char *tmp_value_p;
  struct midnightcode_config_element_t * now_element;


  config_buf_p = config_buf;
  config_buf_offset = strlen(config_buf) - 1;
  config_buf_p += config_buf_offset;
  if(*config_buf_p == '\n')
    *config_buf_p = '\0';
  arg_max = midnightcode_parse_strtoarg(config_buf, config_buf_max,
                                        args, 63);

  config_command = 0;
  config_structure = 0;
  config_section = 0;
  config_variable = 0;
  config_value = 0;
  switch(type) {
    case MIDNIGHTCODE_CONFIG_TYPE_NEWSET:
      config_command = 1;
      if(strncasecmp(args[config_command], "new", 3) == 0) {
        config_structure = 2;
        config_section = 3;
        config_variable = 0;
        config_value = 0;
      } else {
        config_structure = 2;
        config_section = 3;
        config_variable = 4;
        config_value = 5;
      }
      break;

    case MIDNIGHTCODE_CONFIG_TYPE_INI:
      break;

    default:
      return MIDNIGHTCODE_CONFIG_ERROR_WRONGTYPE;
      break;
  }

  if(options & MIDNIGHTCODE_CONFIG_OPTION_MERGEARGS &&
     config_value > 0 && arg_max > config_value) {
    memset(parse_buf, 0, 4096);
    strncat(parse_buf, args[config_value],
            sizeof(parse_buf) - strlen(parse_buf) - 1);
    for(arg_index = config_value + 1;
        arg_index <= arg_max && args[arg_index]; arg_index++) {
      strncat(parse_buf, " ", sizeof(parse_buf) - strlen(parse_buf) - 1);
      strncat(parse_buf, args[arg_index],
              sizeof(parse_buf) - strlen(parse_buf) - 1);
    }
    args[config_value] = parse_buf;
    args[config_value + 1] = NULL;
  }


  switch(type) {
    case MIDNIGHTCODE_CONFIG_TYPE_NEWSET:
      if(strncasecmp(args[config_command], "new", 3) == 0) {

        if(!(options & MIDNIGHTCODE_CONFIG_OPTION_AUTONEW)) {
          exists = 0;
          midnightcode_list_for_each(now_element, elements) {
            if(strncasecmp(args[config_command], 
               now_element->command, strlen(now_element->command)) == 0 &&
               strncasecmp(args[config_structure], 
               now_element->structure, strlen(now_element->structure)) == 0 &&
               strncasecmp(args[config_section], 
               now_element->section, strlen(now_element->section)) == 0) {
              exists |= 1;
              break;
            }
          }

          if(exists)
            return MIDNIGHTCODE_CONFIG_ERROR_EXISTS;

          now_element = malloc(sizeof(struct midnightcode_config_element_t));
          memset(now_element, 0,
                 sizeof(struct midnightcode_config_element_t));
          strncpy(now_element->command, args[config_command], 255);
          strncpy(now_element->structure, args[config_structure], 255);
          strncpy(now_element->section, args[config_section], 255);
          midnightcode_list_append(elements, now_element);

        }

      } else if(strncasecmp(args[config_command], "set", 3) == 0) {
        exists = 0;
        midnightcode_list_for_each(now_element, elements) {
          if(strncasecmp(args[config_structure], 
             now_element->structure, strlen(now_element->structure)) == 0 &&
             strncasecmp(args[config_section], 
             now_element->section, strlen(now_element->section)) == 0) {
            if(strncasecmp("new", 
               now_element->command, strlen(now_element->command)) == 0) {
              exists |= 1;
            }
            if(strncasecmp(args[config_command], 
               now_element->command, strlen(now_element->command)) == 0 &&
               strncasecmp(args[config_variable], 
               now_element->variable, strlen(now_element->variable)) == 0) {
              exists |= 2;
              break;
            }
          }
        }

        if(!(options & MIDNIGHTCODE_CONFIG_OPTION_AUTONEW)) {
          if(!(exists & 1))
            return MIDNIGHTCODE_CONFIG_ERROR_NONEW;
        }
        if(exists & 2)
          return MIDNIGHTCODE_CONFIG_ERROR_EXISTS;

        now_element = malloc(sizeof(struct midnightcode_config_element_t));
        memset(now_element, 0,
               sizeof(struct midnightcode_config_element_t));
        strncpy(now_element->command, args[config_command], 255);
        strncpy(now_element->structure, args[config_structure], 255);
        strncpy(now_element->section, args[config_section], 255);
        strncpy(now_element->variable, args[config_variable], 255);
        if(config_value != 0 && strlen(args[config_value]) > 0) {
          tmp_value_p = malloc(strlen(args[config_value]) + 1);
          memset(tmp_value_p, 0, strlen(args[config_value]) + 1);
          strncpy(tmp_value_p, args[config_value],
                  strlen(args[config_value]) + 1);
          now_element->value = tmp_value_p;
          now_element->value_size = strlen(args[config_value]) + 1;
        }
        midnightcode_list_append(elements, now_element);

      } else {
        return MIDNIGHTCODE_CONFIG_ERROR_SYNTAX;

      }
      break;

    case MIDNIGHTCODE_CONFIG_TYPE_INI:
      break;

  }

  return MIDNIGHTCODE_CONFIG_ERROR_NONE;
}


int
midnightcode_config_read(char *config_file,
                         struct midnightcode_config_head_t * elements,
                         int type, int options) {
  FILE *fp;
  char parse_buf[16384];
  char *parse_ptr, *parse_ref;
  int ret_i;
  int line;


  if((fp = fopen(config_file, "r")) == NULL)
    return -1;

  ret_i = 0;
  line = 0;
  parse_ptr = parse_buf;
  while(fgets(parse_ptr, sizeof(parse_buf) - (parse_ptr - parse_buf), fp)) {
    line++;
    if (parse_ptr[0] == '#' || parse_ptr[0] == '\n')
      continue;
    parse_ref = parse_ptr + strlen(parse_ptr);
    if (parse_ref > parse_ptr &&
        *--parse_ref == '\n' && *--parse_ref == '\\') {
      parse_ptr = parse_ref;
      continue;
    }
    if (parse_buf[0] == '\n' || parse_buf[0] == '\0') {
      parse_ptr = parse_buf;
      continue;
    }

    if((ret_i = midnightcode_config_parseline(parse_buf, 16383,
                                              elements, type, options)) < 0) {
      switch(ret_i) {
        case MIDNIGHTCODE_CONFIG_ERROR_NONE:
          break;

        case MIDNIGHTCODE_CONFIG_ERROR_SYNTAX:
          if(!(options & MIDNIGHTCODE_CONFIG_OPTION_SILENT))
            printf("Config file - Syntax error on line %d of %s\n",
                   line, config_file);
          break;

        case MIDNIGHTCODE_CONFIG_ERROR_NONEW:
          if(!(options & MIDNIGHTCODE_CONFIG_OPTION_SILENT))
            printf("Config file - No \"new\" for \"set\" on line %d of %s\n",
                   line, config_file);
          break;

        case MIDNIGHTCODE_CONFIG_ERROR_EXISTS:
          if(!(options & MIDNIGHTCODE_CONFIG_OPTION_SILENT))
            printf("Config file - Duplicate entry on line %d of %s\n",
                   line, config_file);
          break;

      }
      if(ret_i < 0)
        break;
    }
    parse_ptr = parse_buf;
  }
  fclose(fp);

  return ret_i;
}

*/

int
midnightcode_filter_bycommand(struct midnightcode_filter_command_t * commands,
                              char * executable) {
  int options;
  int terminal;
  int ret_i;
  int cmdn;
  char execute[4096];
  struct midnightcode_filter_command_t * cmd;


  options = 0;
  options |= MIDNIGHTCODE_PROCESS_OPTION_WAITTERM;
  options |= MIDNIGHTCODE_PROCESS_OPTION_NULLIO;
  options |= MIDNIGHTCODE_PROCESS_OPTION_EXITCODE;
  terminal = MIDNIGHTCODE_TERMINAL_VIRTUAL_NULL;

  if(strlen(executable) <= 0)
    return -1;

  
  ret_i = 0;
  for(cmd = commands, cmdn = 0; cmd[cmdn].exec != NULL; cmdn++) {
    memset(execute, 0, sizeof(execute));
    ret_i = snprintf(execute,  sizeof(execute) - 1, "%s %s",
                     executable, cmd[cmdn].exec);
    if(ret_i >= (int)sizeof(execute)) {
      ret_i = 0 - cmdn;
      break;
    }
    ret_i = midnightcode_process(execute, options, terminal);
    if(ret_i != 0) {
      ret_i = 0 - cmdn;
      break;
    }
  }
  if(ret_i == 0)
    ret_i = cmdn;


  return ret_i;
}


int
midnightcode_filter(int action, char * chain, int position,
                    struct midnightcode_filter_rule_head_t * rule_elements,
                    struct midnightcode_filter_chain_head_t * chain_elements) {
  int ret_i;
  struct midnightcode_filter_rule_element_t * rule_element;
  struct midnightcode_filter_chain_element_t * chain_element;
  /* char executable[256] = "/sbin/iptables"; */
  char executable[256] = "/bin/echo";

  struct midnightcode_filter_command_t set_shutdown[] = {
    { "-P INPUT ALLOW" },
    { "-P OUTPUT ALLOW" },
    { "-P FORWARD ALLOW" },
    { "-F -t filter" },
    { "-F -t nat" },
    { "-F -t mangle" },
    { 0 }
  };

  struct midnightcode_filter_command_t set_initialise[] = {
    { "-P INPUT DROP" },
    { "-P OUTPUT DROP" },
    { "-P FORWARD DROP" },
    { "-A INPUT -i lo -j ACCEPT" },
    { "-A OUTPUT -o lo -j ACCEPT" },
    { "-N MIDNIGHTCODE_CLEAN" },
    { "-A MIDNIGHTCODE_CLEAN -m state --state INVALID -j DROP" },
    { "-A MIDNIGHTCODE_CLEAN -m state --state NEW -p tcp ! "
      "--tcp-flags SYN,RST,ACK SYN -j DROP" },
    { "-A MIDNIGHTCODE_CLEAN -p tcp --tcp-flags ALL ALL -j DROP" },
    { "-A MIDNIGHTCODE_CLEAN -p tcp --tcp-flags ALL SYN,RST,ACK,FIN,URG "
      "-j DROP" },
    { "-A MIDNIGHTCODE_CLEAN -p tcp --tcp-flags ALL FIN,URG,PSH -j DROP" },
    { "-A MIDNIGHTCODE_CLEAN -p tcp --tcp-flags ALL NONE -j DROP" },
    { "-A MIDNIGHTCODE_CLEAN -p tcp --tcp-flags SYN,RST SYN,RST -j DROP" },
    { "-A MIDNIGHTCODE_CLEAN -p tcp --tcp-flags SYN,FIN SYN,FIN -j DROP" },
    { "-A MIDNIGHTCODE_CLEAN -j RETURN" },
    { "-A INPUT -j MIDNIGHTCODE_CLEAN" },
    { "-A OUTPUT -j MIDNIGHTCODE_CLEAN" },
    { "-A FORWARD -j MIDNIGHTCODE_CLEAN" },
    { "-A FORWARD -p tcp --tcp-flags SYN,RST SYN -j TCPMSS "
      "--clamp-mss-to-pmtu" },
    { "-A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT" },
    { "-A OUTPUT -m state --state ESTABLISHED,RELATED -j ACCEPT" },
    { "-A FORWARD -m state --state ESTABLISHED,RELATED -j ACCEPT" },
    { "-A INPUT -p icmp --icmp-type fragmentation-needed -j ACCEPT" },
    { "-A OUTPUT -p icmp --icmp-type fragmentation-needed -j ACCEPT" },
    { "-A FORWARD -p icmp --icmp-type fragmentation-needed -j ACCEPT" },
    { 0 }
  };

  struct midnightcode_filter_command_t set_log[] = {
    { "-A INPUT -j LOG --log-level info --log-prefix \"Firewal   (input):\"" },
    { "-A OUTPUT -j LOG --log-level info --log-prefix \"Firewal  (output):\"" },
    { "-A FORWARD -j LOG --log-level info --log-prefix "
      "\"Firewal (forward):\"" },
    { 0 }
  };

  ret_i = MIDNIGHTCODE_FILTER_ERROR_NONE;

  switch(action) {
    case MIDNIGHTCODE_FILTER_ACTION_INITIALISE:
      if(midnightcode_filter_bycommand(set_shutdown, executable) < 0)
        ret_i = MIDNIGHTCODE_FILTER_ERROR_INITIALISE;
      if(midnightcode_filter_bycommand(set_initialise, executable) < 0)
        ret_i = MIDNIGHTCODE_FILTER_ERROR_INITIALISE;
      if(midnightcode_filter_bycommand(set_log, executable) < 0)
        ret_i = MIDNIGHTCODE_FILTER_ERROR_INITIALISE;
      break;

    case MIDNIGHTCODE_FILTER_ACTION_FIND:
      /* TODO: implement filters */
      // for each chain
      //  for each rule
      //    add rule 
      break;

    case MIDNIGHTCODE_FILTER_ACTION_INSERT:
      break;

    case MIDNIGHTCODE_FILTER_ACTION_ADD:
      break;

    case MIDNIGHTCODE_FILTER_ACTION_DELETE:
      break;

    case MIDNIGHTCODE_FILTER_ACTION_APPLY:
      break;

    case MIDNIGHTCODE_FILTER_ACTION_SHUTDOWN:
      if(midnightcode_filter_bycommand(set_shutdown, executable) < 0)
        ret_i = MIDNIGHTCODE_FILTER_ERROR_SHUTDOWN;
      break;

    default:
      ret_i = MIDNIGHTCODE_FILTER_ERROR_ACTION;
      break;

  }

  return ret_i;
}



