/***************************************
  $Id $

  dbupdate 

  Status: NOT REVIEWED, NOT TESTED

  Author(s):       Denis Walker

  ******************/ /******************
  Modification History:
        denis (20021021) Created.
  ******************/ /******************
  Copyright (c) 2002                         RIPE NCC

  All Rights Reserved

  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 appear in all copies and that
  both that copyright notice and this permission notice appear in
  supporting documentation, and that the name of the author not be
  used in advertising or publicity pertaining to distribution of the
  software without specific, written prior permission.

  THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
  ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS; IN NO EVENT SHALL
  AUTHOR BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY
  DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
  AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
  OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 ***************************************/

#include <stdio.h>
#include <stdlib.h>
#include <glib.h>
#include <assert.h>
#include <sys/stat.h>

#include "rip.h"
#include "dbupdate.h"
#include "up_util.h"


/* list of command line options */
/* short form, long form, argument 
   argument 0-none, 1-required, 2-optional */
opt opt_list[] = { { 'm', "mail", 0},
                   { 'f', "file", 1},
                   { 'c', "config", 1},
                   { 'd', "debug", 2},
                   { 't', "test", 0},
                   { 'r', "redirect", 0},
                   { 'p', "print", 0},
                   { 'o', "origin", 1},
                   { 'k', "keywords", 1}
                 };

/* list of keywords */
key_words keywords[] = { {"help", UP_HELP},
                         {"howto", UP_HOWTO},
                         {"new", UP_NEW}
                       };


/* Sets an option in the options structure for reference later.
   Receives options structure
            option string or character
            argument or null 
            form of option, long or short
   Returns  none
            ( sets values in options structure )
*/

void set_option(options_struct_t *options, char *arg_str, char *optarg, int form)
{
  /* set up the values in the options structure */
  if ( (form==0 && *arg_str=='m') || (form==1 && !strcmp(arg_str, "mail")) )
  {
    options->mail_input = 1;
  }
  else if ( (form==0 && *arg_str=='f') || (form==1 && !strcmp(arg_str, "file")) )
  {
    options->input_file_name = optarg;
  }
  else if ( (form==0 && *arg_str=='c') || (form==1 && !strcmp(arg_str, "config")) )
  {
    options->config_file_name = optarg;
  }
  else if ( (form==0 && *arg_str=='d') || (form==1 && !strcmp(arg_str, "debug")) )
  {
    options->debug = 1;
    if ( optarg )
    {
      options->debug_level = atoi(optarg);
      free(optarg);
    }
    else
    {
      options->debug_level = DEBUG_LEVEL;
    }
  }
  else if ( (form==0 && *arg_str=='t') || (form==1 && !strcmp(arg_str, "test")) )
  {
    options->test_mode = 1;
  }
  else if ( (form==0 && *arg_str=='r') || (form==1 && !strcmp(arg_str, "redirect")) )
  {
    options->redirect = 1;
  }
  else if ( (form==0 && *arg_str=='p') || (form==1 && !strcmp(arg_str, "print")) )
  {
    options->print = 1;
  }
  else if ( (form==0 && *arg_str=='o') || (form==1 && !strcmp(arg_str, "origin")) )
  {
    options->origin = optarg;
  }
  else if ( (form==0 && *arg_str=='k') || (form==1 && !strcmp(arg_str, "keywords")) )
  {
    options->keywords = optarg;
  }
  /* no need for a final 'else invalid' as they have already been checked */
}


/* Determines if an option is valid and if it needs an argument.
   Receives option string or character
            form of option, long or short
            argument required flag pointer
   Returns  UP_OK or UP_FAIL
            sets arg_required flag
*/

int isoption(char *option, int form, int *arg_required)
{
  int opt_idx = 0;
  int found = 0;
  char opt_char[2];
  char *unrec_opt = NULL;
  
  /* find option in list */
  for ( opt_idx=0; opt_idx<sizeof(opt_list)/sizeof(opt); opt_idx++ )
  {
    if ( (form == 0 && *option == opt_list[opt_idx].option) ||
         (form == 1 && ! strcmp(option, opt_list[opt_idx].long_opt)) )
    {
      found = 1;
      *arg_required = opt_list[opt_idx].arg_required;
    }
  }
  
  if ( !found )
  {
    /* unrecognised option */
    if ( form == 0 )
    {
      unrec_opt = malloc(3);
      strcpy(unrec_opt, "-");
      opt_char[0] = *option;
      opt_char[1] = '\0';
      strcat(unrec_opt, opt_char);
    }
    else
    {
      unrec_opt = malloc(3+strlen(option));
      strcpy(unrec_opt, "--");
      strcat(unrec_opt, option);
    }

    fprintf(stderr, "isoption: invalid option %s\n", unrec_opt);
    free(unrec_opt);
  }
  
  return found;
}

/* Process the command line arguments.
   Receives options structure
            count of number of arguments
            array of argument strings
            pointer to argument string
   Returns  UP_OK or UP_FAIL
*/

int process_arguments(options_struct_t *options,
                        int argc, char **argv, char **args)
{
  int arg_cnt = 0;
  int arg_required = 0;
  int form = 0;
  int ret_val = UP_OK;
  char *arg_str = NULL;
  char *optarg = NULL;
  char *noarg_opt = NULL;
  char *arg_list = NULL;
  char opt_char[2];
  
  /* build a string of command line arguments to log later*/
  for ( arg_cnt = 0; arg_cnt < argc; arg_cnt++ )
  {
    if ( argv[arg_cnt] != NULL )
    {
      if ( arg_list == NULL ){
        arg_list = strdup(argv[arg_cnt]);
      }
      else
      {
        arg_list = realloc(arg_list, strlen(arg_list) + strlen(argv[arg_cnt]) +2);
        strcat(arg_list, " ");
        strcat(arg_list, argv[arg_cnt]);
      }
    }
  }
  *args = arg_list;

  /* process each argument */
  for ( arg_cnt=1; arg_cnt < argc; arg_cnt++ )
  {
    arg_str = argv[arg_cnt];
    if ( *arg_str == '-' )
    {
      /* start of an option */
      if ( *(++arg_str) == '\0' )
      {
        /* invalid option, nothing follows the '-' */
        /* LG_log(lg_ctx, LG_ERROR,"process_arguments: invalid option -"); */
        fprintf(stderr, "process_arguments: invalid option -\n");
        continue;
      }
      if ( *arg_str == '-' && *(arg_str+1) == '\0' )
      {
        /* invalid option, nothing follows the '--' */
        /* LG_log(lg_ctx, LG_ERROR,"process_arguments: invalid option --"); */
        fprintf(stderr, "process_arguments: invalid option --\n");
        continue;
      }
      
      /* determine form of option - short 0, long 1 */
      form = ( *arg_str == '-' ) ? 1 : 0;

      if ( form == 1 )
      {
        /* long form */
        /* move past second '-' */
        ++arg_str;  
        
        /* if this option is recognised, does it need an argument */
        if ( ! isoption(arg_str, form, &arg_required) )
        {
          ret_val = UP_FAIL;
          continue; /* option not recognised */
        }
        
        if ( arg_required )
        {
          /* look for the next argument, which must not start with a '-' */
          if ( arg_cnt+1==argc || *(argv[arg_cnt+1])=='\0' || *(argv[arg_cnt+1])=='-' )
          {
            /* no following argument found for this option */
            if ( arg_required == 1 )
            {
              /* argument is mandatory */
              noarg_opt = malloc(3+strlen(arg_str));
              strcpy(noarg_opt, "--");
              strcat(noarg_opt, arg_str);
              /* LG_log(lg_ctx, LG_ERROR,"process_arguments: missing argument for option %s", noarg_opt); */
              fprintf(stderr, "process_arguments: missing argument for option %s\n", noarg_opt);
              free(noarg_opt);
              noarg_opt = NULL;
              ret_val = UP_FAIL;
              continue;
            }
          }
          else
            optarg = strdup(argv[++arg_cnt]);
        }

        set_option(options, arg_str, optarg, form);
        optarg = NULL;
      }
      else
      {
        /* short form */
        /* this could be a single option or string of options
           the single option or the last of a string of options 
           may require a following argument */
        
        /* loop through the string of options in this arg */
        do
        {
          /* if there is a single '-' in the middle of a string of options,
             then silently ignore it */
          if ( *arg_str=='-' && *(arg_str+1) != '\0' && *(arg_str+1) != '-')
            continue;
          /* If there is a "--" in the middle of a string of options,
             reject this argument as an invalid option. */
          if ( *arg_str=='-' && *(arg_str+1) != '\0' && *(arg_str+1) == '-')
          {
            /* LG_log(lg_ctx, LG_ERROR,"process_arguments: invalid option %s", argv[arg_cnt]); */
            fprintf(stderr, "process_arguments: invalid option %s\n", argv[arg_cnt]);
            ret_val = UP_FAIL;
            break;
          }
          
          /* if this option is recognised, does it need an argument */
          if ( ! isoption(arg_str, form, &arg_required) )
          {
             ret_val = UP_FAIL;
             continue; /* argument not recognised */
          }

          if ( arg_required )
          {
            /* this must be the last option if it is a string of options
               and there must be a following argument, which must not start with a '-' */
            if ( *(arg_str+1) !='\0' || arg_cnt+1==argc || 
                    argv[arg_cnt+1]==NULL || *(argv[arg_cnt+1])=='-' )
            {
              /* no following argument found for this option */
              if ( arg_required == 1 )
              {
                /* argument is mandatory */
                noarg_opt = malloc(3);
                strcpy(noarg_opt, "-");
                opt_char[0] = *arg_str;
                opt_char[1] = '\0';
                strcat(noarg_opt, opt_char);
                /* LG_log(lg_ctx, LG_ERROR,"process_arguments: missing argument for option %s", noarg_opt); */
                fprintf(stderr, "process_arguments: missing argument for option %s\n", noarg_opt);
                free(noarg_opt);
                noarg_opt = NULL;
                ret_val = UP_FAIL;
                continue;
              }
            }
            else
              optarg = strdup(argv[++arg_cnt]);
          }
            
          set_option(options, arg_str, optarg, form);
          optarg = NULL;
        } while ( *(++arg_str) != '\0' );
      }
    }
    else
    {
      /* invalid option */
      /* LG_log(lg_ctx, LG_ERROR,"process_arguments: invalid option %s", arg_str); */
      fprintf(stderr, "process_arguments: invalid option %s\n", arg_str);
      ret_val = UP_FAIL;
      continue;
    }
  }

  /* report any invalid combination of arguments */
  /* -m and -k are mutually exclusive */
  if ( options->mail_input && options->keywords )
  {
    /* LG_log(lg_ctx, LG_ERROR,"process_arguments: invalid combination of options -m and -k"); */
    fprintf(stderr, "process_arguments: invalid combination of options -m and -k\n");
    ret_val = UP_FAIL;
  }
  
  /* -c is mandatory */
  if ( options->config_file_name == NULL )
  {
    /* LG_log(lg_ctx, LG_ERROR,"process_arguments: missing mandatory option -c"); */
    fprintf(stderr, "process_arguments: missing mandatory option -c\n");
    ret_val = UP_FAIL;
  }
  
  /* LG_log(lg_ctx, LG_FUNC,"<process_arguments: exiting with value %s", UP_ret2str(ret_val)); */
  return ret_val;
}


/* Logs the options set from the command line.
   Receives LG context
            options structure
            command line string
   Returns  none
*/

void log_options(LG_context_t *lg_ctx, options_struct_t *options, char *args_str)
{
  LG_log(lg_ctx, LG_FUNC,">log_options: entered");

  /* log command line string supplied */
  LG_log(lg_ctx, LG_DEBUG,"log_options: command line = [%s]", args_str);

  /* log the options set in the options structure from parsing the command line */
  if ( options->mail_input )
    LG_log(lg_ctx, LG_DEBUG,"log_options: mail input set");
  if ( options->input_file_name )
    LG_log(lg_ctx, LG_DEBUG,"log_options: input file name = %s", options->input_file_name);
  if ( options->config_file_name )
    LG_log(lg_ctx, LG_DEBUG,"log_options: config file name = %s", options->config_file_name);
  if ( options->debug )
    LG_log(lg_ctx, LG_DEBUG,"log_options: debug set to level %d", options->debug_level);
  if ( options->test_mode )
    LG_log(lg_ctx, LG_DEBUG,"log_options: test mode set");
  if ( options->redirect )
    LG_log(lg_ctx, LG_DEBUG,"log_options: redirect set");
  if ( options->print )
    LG_log(lg_ctx, LG_DEBUG,"log_options: print set");
  if ( options->origin )
      LG_log(lg_ctx, LG_DEBUG,"log_options: origin = %s", options->origin);
  if ( options->keywords )
    LG_log(lg_ctx, LG_DEBUG,"log_options: keywords = %s", options->keywords);

  LG_log(lg_ctx, LG_FUNC,"<log_options: exiting");
}


/* Process the keywords, report any invalid keywords or an invalid combination.
   Receives RT context
            LG context
            options structure
            input data structure
   Returns  sets values in options structure
*/

void check_keywords(RT_context_t *rt_ctx, LG_context_t *lg_ctx,
                       options_struct_t *options, EP_input_structure_t *input)
{
  int kw_idx, kw_item;
  int invalid = 0;
  int found;
  char *keyword_str = NULL;
  char **keywords_list = NULL;
  int keywords_found[NUMBER_OF_KEYWORDS];
  GString *invalid_keywords;

  LG_log(lg_ctx, LG_FUNC,">check_keywords: entered");

  invalid_keywords = g_string_new(NULL);

  /* if it is a mail message, get a list of the keywords from the subject line */
  if ( options->mail_input )
  {
    keyword_str = (char *)EP_get_candidate_keywords(input);
  }
  else
    keyword_str = options->keywords;

  /* initialise the keywords_found array */
  for (kw_idx = 0; kw_idx < NUMBER_OF_KEYWORDS; kw_idx++)
    keywords_found[kw_idx] = 0;

  if ( keyword_str)
  {
    LG_log(lg_ctx, LG_DEBUG,"check_keywords: keywords [%s]", keyword_str);
    /* split the keyword string on comma */
    keywords_list = g_strsplit(keyword_str, ",", 0);
    
    /* check for valid and invalid keywords */
    for (kw_idx = 0; keywords_list[kw_idx] != NULL; kw_idx++)
    {
      found = 0;
      for ( kw_item=0; kw_item<sizeof(keywords)/sizeof(key_words); kw_item++ )
      {
        if ( ! strcasecmp(keywords_list[kw_idx], keywords[kw_item].word) )
        {
          /* found a valid keyword */
          LG_log(lg_ctx, LG_DEBUG,"check_keywords: valid keyword [%s]", keywords_list[kw_idx]);
          keywords_found[keywords[kw_item].index] = 1;
          found = 1;
          break;
        }
      }
      if ( ! found )
      {
        LG_log(lg_ctx, LG_DEBUG,"check_keywords: invalid keyword [%s]", keywords_list[kw_idx]);
        if ( invalid )
          invalid_keywords = g_string_append(invalid_keywords, ", ");
        invalid_keywords = g_string_append(invalid_keywords, keywords_list[kw_idx]);
        invalid = 1;
      }
    }
    g_strfreev(keywords_list);
    
    if ( invalid )
    {
      /* at least one invalid keyword found, so ignoring all keywords */
      RT_invalid_keyword(rt_ctx, invalid_keywords->str);
      LG_log(lg_ctx, LG_WARN,
          "check_keywords: invalid keyword(s) found [%s]\nAll keywords will be ignored", 
          keyword_str);
    }
    else
    {
      /* check that the keywords combination is valid */
      if ( (keywords_found[UP_HELP] || keywords_found[UP_HOWTO]) && 
               keywords_found[UP_NEW] )
      {
        /* illegal combination */
        RT_invalid_keyword_combination(rt_ctx, "NEW and HELP/HOWTO");
        LG_log(lg_ctx, LG_WARN,
            "check_keywords: invalid combination of keywords NEW and HELP/HOWTO\nAll keywords will be ignored");
      }
      else
      {
        /* set options values for keywords */
        if ( keywords_found[UP_HELP] || keywords_found[UP_HOWTO] )
        {
          options->help = 1;
          LG_log(lg_ctx, LG_DEBUG,"check_keywords: options->help= %d", options->help);
        }
        if ( keywords_found[UP_NEW] )
        {
          options->enforced_new = 1;
          LG_log(lg_ctx, LG_DEBUG,"check_keywords: options->enforced_new= %d", options->enforced_new);
        }
      }
    }
  }
  else
    LG_log(lg_ctx, LG_DEBUG,"check_keywords: no keywords found");
  
  g_string_free(invalid_keywords, 1);
  LG_log(lg_ctx, LG_FUNC,"<check_keywords: exiting");
}



/************************ NOT USED ***********************************/
/* Get the help message text and send it to RT.
   Receives RT context
            LG context
            options structure
   Returns  none
*/

void help_request(RT_context_t *rt_ctx, LG_context_t *lg_ctx)
{
  char *autodbmhelp = NULL;
  char *autodbmhelp_str = NULL;
  char  buf[1024];
  FILE *help_file;

  LG_log(lg_ctx, LG_FUNC,">help_request: entered");


  if ( ! autodbmhelp || (help_file = fopen(autodbmhelp, "r")) == NULL )
  {
    LG_log(lg_ctx, LG_DEBUG, "can't open help file [%s]", autodbmhelp);
    LG_log(lg_ctx, LG_PERROR, "can't open help file [%s]", autodbmhelp);
    autodbmhelp_str = "\nHelp file could not be found.\nPlease notify the database administrator at ripe-dbm@ripe.net\n";
    RT_help_request(rt_ctx);
  }
  else
  {
    while ( fgets(buf, 1023, help_file) != NULL )
    {
      if ( ! autodbmhelp_str )
      {
        autodbmhelp_str = malloc(1024);
        strcpy(autodbmhelp_str, buf);
      }
      else
      {
        autodbmhelp_str = realloc(autodbmhelp_str, strlen(autodbmhelp_str) + 1025);
        strcat(autodbmhelp_str, buf);
      }
    }

    RT_help_request(rt_ctx);
    free(autodbmhelp_str);
  }

  LG_log(lg_ctx, LG_FUNC,"<help_request: exiting");
}
/*********************************************************************/


/* Deletes entries in the AUTO_nic_hdl_hash
   Receives key
            value
            NULL
   Returns  none
 */

void  free_auto_nic(gpointer key, gpointer value, gpointer user_data)
{
  free((char *)key);
  free((char *)value);
}


/* Builds up a list of blobs (submissions with associated credentals).
   Receives an input blob
            list pointer
   Returns  adds blob to list
*/

void get_input_submission_list(EP_blob_credential_t *blob, gpointer user_data)
{
   /* add blobs to list */
   GList **plist;

   plist = (GList**)user_data;

   *plist = g_list_append(*plist, blob);
}


/* Gets the input and breaks it down to a list of submissions with credentials.
   Receives RT context
            LG context
            options structure
   Returns  none
*/

void get_and_process_input(RT_context_t *rt_ctx, LG_context_t *lg_ctx, 
                                 options_struct_t *options)
{
  int num_submissions = 0;
  int retval = UP_OK;
  char *temp_input_file_name = NULL;  /* used to hold input from stdin */
  char input_char;
  char *log_msg = NULL;
  int input_file_invalid = 0;
  struct stat stat_buf;
  char *field_ptr;
  EP_input_structure_t *input = NULL; /* 'black box' structure containing un-folded data */
  FILE *input_file;
  GList *submission_list = NULL;
  GList *submission_item = NULL;

  LG_log(lg_ctx, LG_FUNC,">get_and_process_input: entered");

  /* Get the input data.
     If it is stdin, then write it to a temp file. 
     Log the input to updlog */
  if ( ! options->input_file_name )
  {
    /* no file supplied so it must be from stdin */
    temp_input_file_name = UP_get_temp_filename(lg_ctx, "input");
    /* store temp filename for use in notifications forw_create message */
    options->input_file_name = temp_input_file_name;

    if (( input_file = fopen(temp_input_file_name, "a")) == NULL)
    {
      LG_log(lg_ctx, LG_FATAL, "get_and_process_input: Can't open temp input file [%s]", temp_input_file_name);
      UP_internal_error(rt_ctx, lg_ctx, options, "Can't open temp input file\n");
    }

    /* read stdin */
    LG_log(lg_ctx, LG_INFO, "get_and_process_input: reading stdin");
    while ((input_char = getchar()) != EOF)
    {
      fprintf(input_file, "%c",input_char);
    }
    fclose(input_file);
  }
   else
  {
    /* check access to the supplied input file */
    if ( stat(options->input_file_name, &stat_buf) )
    {
      input_file_invalid = 1;
    }
    else
    {
      if ( ! S_ISREG(stat_buf.st_mode) )
      {
        input_file_invalid  = 1;
      }
    }
    if ( input_file_invalid  )
    {
      LG_log(lg_ctx, LG_FATAL, "get_and_process_input: Can't access input file [%s]", options->input_file_name);
      UP_internal_error(rt_ctx, lg_ctx, options, "Can't access input file\n");
    }
  }
 
  /* open input file for input processing */
  if ((input_file = fopen(options->input_file_name, "r")) == NULL)
  {
    LG_log(lg_ctx, LG_FATAL, "get_and_process_input: Can't open input file [%s]", options->input_file_name);
    UP_internal_error(rt_ctx, lg_ctx, options, "Can't open input file\n");
  }

  /* log input to updlog and state file */
  UP_add_to_upd_log(rt_ctx, lg_ctx, options, input_file);
  /* rewind the file pointer */
  retval = fseek(input_file, 0, SEEK_SET);
  if ( retval )
  {
    LG_log(lg_ctx, LG_FATAL, "get_and_process_input: Can't rewind input file [%s]", options->input_file_name);
    UP_internal_error(rt_ctx, lg_ctx, options, "Can't rewind input file\n");
  }
//fclose(input_file);
//input_file = fopen(options->input_file_name, "r");

  /* initialise the input processing */
  LG_log(lg_ctx, LG_INFO, "get_and_process_input: initialise the input processing");
  EP_init(lg_ctx);
  
  /* expand the input */
  LG_log(lg_ctx, LG_INFO, "get_and_process_input: expand the input");
  input = EP_unfold(input_file, options->mail_input);
  /* close the input stream */
  fclose(input_file);

  /* report any errors in the expanded input */
  log_msg = RT_EP_analyse(rt_ctx, input);
  assert(log_msg != NULL);  /* should never happen */
  LG_log(lg_ctx, LG_DEBUG, "get_and_process_input: %s", log_msg);

  /* check the keywords */
  check_keywords(rt_ctx, lg_ctx, options, input);

    /* if it is a mail mesage, get the mail hdr data */
    if ( options->mail_input )
    {
      options->mail_hdr_data.from    =
        (field_ptr = (char *)EP_get_mail_hdr_field(input, "From")) == NULL  ? NULL : strdup(field_ptr);

      options->mail_hdr_data.cc      =
        (field_ptr = (char *)EP_get_mail_hdr_field(input, "Cc")) == NULL  ? NULL : strdup(field_ptr);

      options->mail_hdr_data.subject =
        (field_ptr = (char *)EP_get_mail_hdr_field(input, "Subject")) == NULL  ? NULL : strdup(field_ptr);

      options->mail_hdr_data.date    =
        (field_ptr = (char *)EP_get_mail_hdr_field(input, "Date")) == NULL  ? NULL : strdup(field_ptr);

      options->mail_hdr_data.replyto =
        (field_ptr = (char *)EP_get_mail_hdr_field(input, "ReplyTo")) == NULL  ? NULL : strdup(field_ptr);
// printf("replyto [%s]\n", options->mail_hdr_data.replyto ? options->mail_hdr_data.replyto : "NULL");

      options->mail_hdr_data.msgid   =
        (field_ptr = (char *)EP_get_mail_hdr_field(input, "Message-ID")) == NULL  ? NULL : strdup(field_ptr);


/*      options->mail_hdr_data.from    = strdup((char *)EP_get_mail_hdr_field(input, "From"));
      options->mail_hdr_data.cc      = strdup((char *)EP_get_mail_hdr_field(input, "Cc"));
      options->mail_hdr_data.subject = strdup((char *)EP_get_mail_hdr_field(input, "Subject"));
      options->mail_hdr_data.date    = strdup((char *)EP_get_mail_hdr_field(input, "Date"));
      options->mail_hdr_data.replyto = strdup((char *)EP_get_mail_hdr_field(input, "ReplyTo"));
      options->mail_hdr_data.msgid   = strdup((char *)EP_get_mail_hdr_field(input, "MsgId"));
*/
      /* outgoing email header */
      RT_header(rt_ctx,
                 options->mail_hdr_data.replyto ? options->mail_hdr_data.replyto : options->mail_hdr_data.from,
                 g_strstrip(ca_get_humailbox),
                 options->mail_hdr_data.subject);

      /* incomming email header */
      RT_from_mail(rt_ctx, &options->mail_hdr_data);
    }

  if (options->origin)
  {
    /* write the details of the origin causing these changes to this ctx */
    RT_notif_origin(rt_ctx, options->origin);
  }
    
  if ( ! options->help )
  {
    /* get a list of submissions */
    EP_blobs_foreach(input, get_input_submission_list, &submission_list);
    num_submissions = g_list_length(submission_list);
    LG_log(lg_ctx, LG_DEBUG, "get_and_process_input: number of submissions= %d", num_submissions);

    /* process each submission in turn, 
       but stop processing if a fatal error is found in a submission */
    for ( submission_item = submission_list; submission_item != NULL && !(retval & UP_FATAL) ; 
                       submission_item = g_list_next(submission_item) )
    {
      retval = UP_process_submission(rt_ctx, lg_ctx, options, (EP_blob_credential_t*)(submission_item->data) );
    }
  }
  else
    RT_help_request(rt_ctx);

  /* tidy up */
  EP_input_structure_free(input);
  input = NULL;
  EP_end();
  g_list_free(submission_list);
  submission_list= NULL;
  if ( options->mail_hdr_data.from_email )
    free(options->mail_hdr_data.from_email);
  
  if ( temp_input_file_name )
  {
    unlink(temp_input_file_name);
    free(temp_input_file_name);
    temp_input_file_name = NULL;
    options->input_file_name = NULL;
  }
  LG_log(lg_ctx, LG_FUNC,"<get_and_process_input: exiting");
}



void main(int argc, char **argv)
{
  char *args_str = NULL;
  char *pgp_public_key_ring = NULL;
  char *gpgcmd = NULL;
  char *tmp_dir = NULL;
  char *filename;
  char *state_file_name;
  int config_file_invalid = 0;
  struct stat stat_buf;
  time_t cur_time;
  FILE *state;                        /* file pointer for state log file */
  RT_context_t *rt_ctx;               /* context pointer used in all calls to RT module */
  LG_context_t *lg_ctx = NULL;        /* context pointer used by LG module */
  LG_appender_t *app;
  KM_context_data_t *km_ctx;          

  mail_hdr_t mail_hdr = { NULL, NULL, NULL, NULL, NULL, NULL, NULL };
//  options_struct_t options = { NULL, NULL, 0, 0, 0, 0, 0, 0, NULL, NULL, 0, 0, 0, 0, NULL, { NULL, NULL, NULL, NULL, NULL, NULL, NULL }, NULL, NULL, NULL};
  options_struct_t options = { NULL, NULL, NULL, 0, 0, 0, 0, 0, 0, NULL, NULL, 0, 0, 0, 0, NULL, { NULL }, NULL, NULL, NULL};
  options.mail_hdr_data = mail_hdr;
  

  /********************* INITIALISE AND CONFIGURE  ***************************/

  /* get date and time for the start of dbupdate */
  cur_time = time(NULL);
  options.time_str = strdup(ctime(&cur_time));

  /* create hashes */
  options.ntfy_hash = g_hash_table_new(g_str_hash, g_str_equal);
  options.forw_hash = g_hash_table_new(g_str_hash, g_str_equal);
  options.AUTO_nic_hdl_hash = g_hash_table_new(g_str_hash, g_str_equal);       

  /* process command line arguments */
  if ( process_arguments(&options, argc, argv, &args_str) != UP_OK )
  {
    /* LG_log(lg_ctx, LG_FATAL,"main: error processing command line arguments"); */
    fprintf(stderr, "main: error processing command line arguments\n");
    fprintf(stderr, "main: arguments: %s\n", args_str);
    free(args_str);
    free(options.time_str);
    exit(11);
  }

  /* configure */
  /* if -c flag is given, use the named file as config file */ 
  if ( options.config_file_name != NULL)
  {
    /* check for the existence of the file */
    if ( stat(options.config_file_name, &stat_buf) )
      config_file_invalid = 1;
    else
    {
      if ( ! S_ISREG(stat_buf.st_mode) )
        config_file_invalid = 1;
    }
    
    if ( config_file_invalid )
    {
      /* config file does not exist or can't be accessed */
      fprintf(stderr, "main: config file %s does not exist\n", options.config_file_name);
      free(args_str);
      free(options.time_str);
      exit(12);
    }
    ca_init(options.config_file_name);
	free(options.config_file_name);
    options.config_file_name = NULL;
  }
  else
  {
    fprintf(stderr, "main: no config file specified\n");
    free(args_str);
    free(options.time_str);
    exit(13);
  }

  /* set up a reporting context */
  rt_ctx = RT_start();

  /* create state log file */
  state_file_name = UP_get_temp_filename(NULL, "state");
  state = fopen(state_file_name, "a");
  dieif( state == NULL ); /* shouold never happen */
  options.state = state;

  /* initialise logging to state file */
  lg_ctx = LG_ctx_new();
  app = LG_app_get_file_info_dump(state);
  LG_app_set_level(app, LG_ALL);
  LG_app_set_formatter(app, LG_frm_dbupdate_prepared());
  LG_ctx_add_appender(lg_ctx, app);

  /* initialise logging to dbupdate error log file */
  filename = ca_get_dberrlog;
  app = LG_app_get_daily_info_dump(filename);
  free(filename);
  LG_app_set_level(app, LG_PERROR);
  LG_app_set_formatter(app, LG_frm_general_prepared("$TIMESTAMP dbupdate-$PID/ $SEVERITY $MESSAGE\n"));
  LG_ctx_add_appender(lg_ctx, app);

  /* if debug was selected, add it to the LG context */
  if ( options.debug )
  {
    app = LG_app_get_file_info_dump(stdout);
    LG_app_set_level(app, options.debug_level);
    LG_app_set_formatter(app, LG_frm_dbupdate_prepared());
    LG_ctx_add_appender(lg_ctx, app);
  }

  LG_log(lg_ctx, LG_INFO,"main:           dbupdate started at %s", options.time_str);
  RT_process_time(rt_ctx, options.time_str);

  /* log the command line options */
  log_options(lg_ctx, &options, args_str);
  free(args_str);

  /* are we in test mode */
  if ( ! options.test_mode)
  {
    /* if it is not already set to 1 (from command line), read from config */
    options.test_mode = ca_get_testmode;
    /* log test mode, if just set */
    if (options.test_mode)
      LG_log(lg_ctx, LG_DEBUG,"main: test mode option set from config file");
  }

  /* initialise the rpsl dictionary */
  LG_log(lg_ctx, LG_INFO,"main: initialise rpsl dictionary");
  rpsl_load_dictionary(RPSL_DICT_FRONT_END);

  /* initialise key management library */
  pgp_public_key_ring = ca_get_pgppath;
  gpgcmd = ca_get_gpgcmd;
  tmp_dir = ca_get_tmpdir;
  LG_log(lg_ctx, LG_INFO,"main: initialise key management with keyring %s", pgp_public_key_ring);
  LG_log(lg_ctx, LG_INFO,"main: initialise key management with gpgcmd %s", gpgcmd );
  LG_log(lg_ctx, LG_INFO,"main: initialise key management with tmp_dir %s", tmp_dir);
  km_ctx = KM_context_data_new();
  KM_context_data_add(km_ctx, "key_ring", pgp_public_key_ring);
  KM_context_data_add(km_ctx, "gpg_path", gpgcmd);
  KM_context_data_add(km_ctx, "temporary_directory", tmp_dir);
  KM_init(KM_PGP, lg_ctx, km_ctx);
  KM_context_data_free(km_ctx);
  free(pgp_public_key_ring);

  /* initialise auth module */
  if ( ! AU_init(lg_ctx, options.test_mode) )
  {
    /* fatal error */
    LG_log(lg_ctx, LG_FATAL,"main:: AU module init failed");
    UP_internal_error(rt_ctx, lg_ctx, &options, "main: AU module init failed\n");
  }

  /* initialise other modules */
  PG_init();
  SQ_init(lg_ctx);
  SK_init(lg_ctx);
  UT_init(lg_ctx);

  /***************************** UPDATE  **************************************/

  /* get the input and process it */
  get_and_process_input(rt_ctx, lg_ctx, &options);
  
  if ( ! options.help )
  {
    /* check if we have processed any objects */
    if ( options.count_successful == 0 && options.count_unsuccessful == 0 )
    {
      RT_no_objects_processed(rt_ctx);
    }

    /* close input to the reporting context */
    RT_end(rt_ctx);
    /* log and send replies */
    NT_process_acknowledgement(rt_ctx, lg_ctx, &options);
    NT_process_notifications(rt_ctx, lg_ctx, &options, UP_NOTIF);
    NT_process_notifications(rt_ctx, lg_ctx, &options, UP_FRWD);
  }
  else
  {
    /* close input to the reporting context */
    RT_end(rt_ctx);
    /* for a help request just send the help info as an ack */
    NT_process_acknowledgement(rt_ctx, lg_ctx, &options);
  }

  /************************* TIDY UP AND SHUTDOWN  ***************************/

  /* close down modules as required */
  PG_end();
  AU_end();
  KM_end(KM_PGP);
  
  /* close down the report */
  RT_destroy(rt_ctx);
  
  /* free any remaining memory */
  /* this is also done in the UP_internal_error function */
  if (options.input_file_name)
    free(options.input_file_name);
  if (options.keywords)
    free(options.keywords);
  if (options.time_str)
    free(options.time_str);
  if (options.origin)
    free(options.origin);
  if (options.mail_hdr_data.from)
    free(options.mail_hdr_data.from);
  if (options.mail_hdr_data.cc)
    free(options.mail_hdr_data.cc);
  if (options.mail_hdr_data.subject)
    free(options.mail_hdr_data.subject);
  if (options.mail_hdr_data.date)
    free(options.mail_hdr_data.date);
  if (options.mail_hdr_data.replyto)
    free(options.mail_hdr_data.replyto);
  if (options.mail_hdr_data.msgid)
    free(options.mail_hdr_data.msgid);
  /* free the AUTO_nic_hdl_hash */
  g_hash_table_foreach( options.AUTO_nic_hdl_hash, (GHFunc)free_auto_nic, NULL);
  g_hash_table_destroy(options.AUTO_nic_hdl_hash);
  
  
  /* close down the logging and delete the state log file */
  LG_ctx_free(lg_ctx);
  fclose(state);
  if ( state_file_name )
  {
    unlink(state_file_name);
    free(state_file_name);
    state_file_name = NULL;
  }
  
  /* all tasks done successfully */
  /* report successful termination to the wrapper */
  exit(0); 
}
