#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <ctype.h>

/* #include <mrt.h>*/
#include "hdr_comm.h"

int verbose;
int mktemp_count = 0;

/* local yokel's */

static u_int dup_token_field (char *line, trans_info_t *ti);
static u_int is_from_field (char *line, trans_info_t *ti);
static u_int is_list_field (trace_t *tr, char *line, trans_info_t *ti);
static u_int int_type_field (char *line, trans_info_t *ti);
static u_int list_members_field (trace_t *tr, char *line, trans_info_t *ti);
static int get_list_members (trace_t *tr, char *p, char *addr_buf, char **next);
static char *find_a_token (char *q);
static int present_in_list (char *list, char *last, char *target_str);
static int is_valid_op (char *);
static char *my_concat (char *, char *);
static char *free_mem (char *);
static int find_token (char **x, char **y);
static char *myconcat (char *x, char *y);
static u_int is_new_list_field (trace_t *tr, char *line, trans_info_t *ti);

/* 
 * Action is to initialize the trans_info struct
 * with the header information located at the begining
 * of the transaction.
 *   Return 0 if required fields are present and 
 * all fields encountered are recognized.
 * Else return 1.
 *   Routine assumes the file pos pointer is on the field
 * immediately after the 'HDR-START' field.
 */
/* JW still need to put in checks for buffer overflow */
int parse_header (trace_t *tr, FILE *fin, long *offset, trans_info_t *ti) {
  char buf[MAXLINE];
  char *cp;
  u_int hdr_f, hdr_flds = 0;

  /* clear the hdr structure */
  memset ((char *) ti, 0, sizeof (*ti));
  ti->nnext = ti->notify_addrs;
  ti->fnext = ti->forward_addrs;
  ti->snext = ti->sender_addrs;

  while ((cp = fgets (buf, MAXLINE - 1, fin)) != NULL) {
    *offset += strlen (buf);
    /*fprintf (stderr, "parse_header () line offset (%ld): %s", *offset, buf);*/

    /* exit loop when HDR-END field is encountered */
    if (!memcmp (HDR_END, buf, strlen (HDR_END))) {
      hdr_flds |= HDR_END_F;
      break;
    }

    if (!(hdr_f = dup_token_field (buf, ti))           &&
	!(hdr_f = list_members_field (tr, buf, ti))    &&
	!(hdr_f = int_type_field (buf, ti))            &&
	!(hdr_f = is_from_field (buf, ti))             &&
	!(hdr_f = is_list_field (tr, buf, ti))         &&
	!(hdr_f = is_new_list_field (tr, buf, ti))) {
      fprintf (stderr, "ERROR: illegal header field encountered \"%s\"\n", buf);
      continue;
    }
    
    hdr_flds |= hdr_f;
  }
  
  ti->hdr_fields = hdr_flds;
  
  /* This means there is no object following the header */
  if (cp == NULL)
    return 1;
  
  return 0;
}

/* 
 * Dup the token pointed to by *p
 */
char *dup_single_token (char *p) {
  char *q;

  if ((q = find_a_token (p)) == NULL)
    return NULL;

  newline_remove (q);

  if (*q == '\0')
    return NULL;

  return strdup (q);
}

/* Given a '"' delimeted string, return everything between
 * the '"'s, minus the "'s.
 *
 * Return:
 *   char * string of the char's between "'s
 *   NULL otherwise
 */
char *dup_string_field (char *p) {
  char *q, *r;

  if ((q = strchr (p, '"')) == NULL)
    return NULL;

  if ((r = strrchr (++q, '"')) == NULL)
    return NULL;

  *r = '\0';

  return strdup (q);
}

u_int dup_token_field (char *line, trans_info_t *ti) {
  char *p;

  if (!strncmp (SOURCE, line, strlen (SOURCE)) &&
      (ti->source = dup_single_token (line + strlen (SOURCE))) != NULL)
    return SOURCE_F;
  
  if (!strncmp (OBJ_TYPE, line, strlen (OBJ_TYPE)) &&
      (ti->obj_type = dup_single_token (line + strlen (OBJ_TYPE))) != NULL)
    return OBJ_TYPE_F;
  
  if (!strncmp (OBJ_KEY, line, strlen (OBJ_KEY)) &&
      (ti->obj_key = dup_single_token (line + strlen (OBJ_KEY))) != NULL)
    return OBJ_KEY_F;
  
  if (!strncmp (OP, line, strlen (OP)) &&
      (ti->op = dup_single_token (line + strlen (OP))) != NULL &&
      is_valid_op (ti->op))
    return OP_F;

  if (!strncmp (SUBJECT, line, strlen (SUBJECT)) &&
      (ti->subject = dup_single_token (line + strlen (SUBJECT))) != NULL)
    return SUBJECT_F;

  if (!strncmp (DATE, line, strlen (DATE)) &&
      (ti->date = dup_single_token (line + strlen (DATE))) != NULL)
    return DATE_F;

  if (!strncmp (MSG_ID, line, strlen (MSG_ID)) &&
      (ti->msg_id = dup_single_token (line + strlen (MSG_ID))) != NULL)
    return MSG_ID_F;

  if (!strncmp (PGP_KEY, line, strlen (PGP_KEY)) &&
      (ti->pgp_key = dup_single_token (line + strlen (PGP_KEY))) != NULL)
    return PGP_KEY_F;

  if (!strncmp (PGP_FROM, line, strlen (PGP_FROM)) &&
      (p = dup_single_token (line + strlen (PGP_FROM))) != NULL) {
    ti->pgp_from = myconcat (ti->pgp_from, p);
    free_mem (p);
    return PGP_FROM_F;
  }

  /*JW take out
  if (!strncmp (PGP_FROM, line, strlen (PGP_FROM)) &&
      (ti->pgp_from = dup_single_token (line + strlen (PGP_FROM))) != NULL)
    return PGP_FROM_F;
    */
  if (!strncmp (OVERRIDE, line, strlen (OVERRIDE)) &&
      (ti->override = dup_single_token (line + strlen (OVERRIDE))) != NULL)
    return OVERRIDE_F;

  if (!strncmp (PASSWORD, line, strlen (PASSWORD)) &&
      (ti->crypt_pw = dup_string_field (line + strlen (PASSWORD))) != NULL)
    return PASSWORD_F;

  if (!strncmp (OTHERFAIL, line, strlen (OTHERFAIL)) &&
      (ti->otherfail = dup_single_token (line + strlen (OTHERFAIL))) != NULL)
    return OTHERFAIL_F;

  if (!strncmp (OLD_OBJ_FILE, line, strlen (OLD_OBJ_FILE)) &&
      (ti->old_obj_fname = 
       dup_single_token (line + strlen (OLD_OBJ_FILE))) != NULL)
    return OLD_OBJ_FILE_F;

  return 0;
}

u_int is_from_field (char *line, trans_info_t *ti) {
  char *p;

  if (!strncmp (FROM, line, strlen (FROM))) {
    p = find_a_token (line + strlen (FROM));
    newline_remove (p);      
    if (*p != '\0')
      strcpy (ti->sender_addrs, p);
    else
      strcpy (ti->sender_addrs, TCP_USER);
    ti->snext += strlen (ti->sender_addrs) + 1;

    return FROM_F;
  }

  return 0;
}

u_int is_new_list_field (trace_t *tr, char *line, trans_info_t *ti) {
  char *p, *q, **r;
  enum HDR_FLDS_T fld;

  if (!strncmp (CHECK_NOTIFY, line, strlen (CHECK_NOTIFY))) {
    p = line + strlen (CHECK_NOTIFY);
    r = &(ti->check_notify);
    fld = CHECK_NOTIFY_F;
  }
  else if (!strncmp (CHECK_MNT_NFY, line, strlen (CHECK_MNT_NFY))) {
    p = line + strlen (CHECK_MNT_NFY);
    r = &(ti->check_mnt_nfy);
    fld = CHECK_MNT_NFY_F;
  }
  else
    return 0;

  *r = NULL;
  q = p;
  while (find_token (&p, &q) > 0) {
    *q = '\0';
    *r = myconcat (*r, p);
    *q = ' ';
  }

  if (*r == NULL)
    return 0;

  return fld;
}

u_int is_list_field (trace_t *tr, char *line, trans_info_t *ti) {
  char *p;

  if (!strncmp (MAINT_NO_EXIST, line, strlen (MAINT_NO_EXIST))) {
    ti->maint_no_exist = NULL;
    p = line + strlen (MAINT_NO_EXIST);
    if ((p = strtok (p, " ")) == NULL) {
      fprintf (stderr, "ERROR: empty MAINT_NO_EXIST hdr field\n");
      return 0;
    }
    
    while (p != NULL) {
      if (*p != '\n') {
	ti->maint_no_exist = my_concat (ti->maint_no_exist, " ");
	ti->maint_no_exist = my_concat (ti->maint_no_exist, p);
      }
      p = strtok (NULL, " ");
    }
    
    if (ti->maint_no_exist != NULL)
      return MAINT_NO_EXIST_F;	
  }

  if (!strncmp (MNT_BY, line, strlen (MNT_BY))) {
    ti->mnt_by[0] = '\0';
    p = line + strlen (MNT_BY);
    if ((p = strtok (p, " ")) == NULL) {
      fprintf (stderr, "ERROR: empty MNT_BY hdr field\n");
      return 0;
    }
    
    while (p != NULL) {
      strcat (ti->mnt_by, " ");
      strcat (ti->mnt_by, p);
      p = strtok (NULL, " ");
    }
    
    /* get rid of \n if there is one 
    p = ti->mnt_by + strlen (ti->mnt_by) - 1;
    if (*p == '\n')
      *p = '\0';
      */
    newline_remove(ti->mnt_by);
    if (ti->mnt_by[0] != '\0')
      return MNT_BY_F;	
  }

  return 0;
}

u_int int_type_field (char *line, trans_info_t *ti) {

  if (!memcmp (SYNTAX_ERRORS, line, strlen (SYNTAX_ERRORS)))
    return ((u_int)(ti->syntax_errors = SYNTAX_ERRORS_F));

  if (!memcmp (SYNTAX_WARNS, line, strlen (SYNTAX_WARNS)))
    return ((u_int)(ti->syntax_warns = SYNTAX_WARNS_F));
  
  if (!memcmp (DEL_NO_EXIST, line, strlen (DEL_NO_EXIST)))
    return ((u_int)(ti->del_no_exist = DEL_NO_EXIST_F));
  
  if (!memcmp (AUTHFAIL, line, strlen (AUTHFAIL)))
    return ((u_int)(ti->authfail = AUTHFAIL_F));
  
  if (!memcmp (NEW_MNT_ERROR, line, strlen (NEW_MNT_ERROR)))
    return ((u_int)(ti->new_mnt_error = NEW_MNT_ERROR_F));

  if (!memcmp (BAD_OVERRIDE, line, strlen (BAD_OVERRIDE)))
    return ((u_int)(ti->bad_override = BAD_OVERRIDE_F));

  if (!memcmp (UNKNOWN_USER, line, strlen (UNKNOWN_USER)))
    return ((u_int)(ti->unknown_user = UNKNOWN_USER_F));
  
  
  return 0;
}

u_int list_members_field (trace_t *tr, char *line, trans_info_t *ti) {

  if (!memcmp (NOTIFY, line, strlen (NOTIFY)) &&
      get_list_members (tr, line + strlen (NOTIFY), 
			ti->notify_addrs, &(ti->nnext)))
    return NOTIFY_F;
  
  if (!memcmp (MNT_NFY, line, strlen (MNT_NFY)) &&
      get_list_members (tr, line + strlen (MNT_NFY), 
			ti->notify_addrs, &(ti->nnext)))
    return MNT_NFY_F;
  
  if (!memcmp (UPD_TO, line, strlen (UPD_TO)) &&
      get_list_members (tr, line + strlen (UPD_TO), 
			ti->forward_addrs, &(ti->fnext)))
    return UPD_TO_F;

  return 0;
}

/* return index of element in list or return -1 */
int present_in_list (char *list, char *last, char *target_str) {
  char *p;
  int i = 0;

  for (p = list; p < last; p += strlen (p) + 1, i++)
    if (!strcasecmp (target_str, p))
      return i;

  return -1;
}

/*
 * Get the list of notifiers (ie, list of email addr's) and
 * place into the buffer pointed to by *next
 * (ie, forward or notify buffer).
 *   Return 1 if field is non-empty, else return 0.
 */
int get_list_members (trace_t *tr, char *p, char *addr_buf, char **next) {
  char *q;

  if ((q = strtok (p, " ")) == NULL) {
    fprintf (stderr, "ERROR: empty list hdr field\n");
    return 0;
  }
  
  while (q != NULL) {
    newline_remove(q);
    if (*q != '\0' &&
	present_in_list (addr_buf, *next, q) < 0) {
      strcpy (*next, q);
      *next += strlen (q) + 1;
    }
      q = strtok (NULL, " ");
  }

  return 1;
}

/* 
 * Return a pointer to the first non-space character.
 * Else return NULL.
 */
char *find_a_token (char *q) {
  
  for (;*q != '\0' && (*q == ' ' || *q == '\t'); q++);

  if (*q == '\0')
    return NULL;

  return q;
}

void free_ti_mem (trans_info_t *ti) {

  if (ti->subject != NULL)
    free (ti->subject);

  if (ti->date != NULL)
    free (ti->date);

  if (ti->msg_id != NULL)
    free (ti->msg_id);

  if (ti->source != NULL)
    free (ti->source);

  if (ti->obj_type != NULL)
    free (ti->obj_type);

  /* causing memory problems... chl */
  if (ti->obj_key != NULL) 
    free (ti->obj_key);

  if (ti->op != NULL)
    free (ti->op);

  if (ti->check_notify != NULL)
    free (ti->check_notify);

  if (ti->check_mnt_nfy != NULL)
    free (ti->check_mnt_nfy);
}
    
void print_hdr_struct (FILE *fout, trans_info_t *ti) {
  char *p;
  
  fprintf (fout, "%s\n", HDR_START);
  
  /*
    fprintf (stderr, "trans_code-(%d)\n", ti->trans_success);
    */
  
  /* pgp and email processing */

  if (ti->sender_addrs[0] != '\0')
    fprintf (fout, "%s%s\n", FROM, ti->sender_addrs);
  
  if (ti->date != NULL)
    fprintf (fout, "%s%s\n", DATE, ti->date);
  
  if (ti->subject != NULL)
    fprintf (fout, "%s%s\n", SUBJECT, ti->subject);
  
  if (ti->msg_id != NULL)
    fprintf (fout, "%s%s\n", MSG_ID, ti->msg_id);
  
  if (ti->pgp_from != NULL)
    fprintf (fout, "%s%s\n", PGP_FROM, ti->pgp_from);
  
  if (ti->pgp_key != NULL)
    fprintf (fout, "%s%s\n", PGP_KEY, ti->pgp_key);
  
  /* irr_check */

  if (ti->op != NULL)
    fprintf (fout, "%s%s\n", OP, ti->op);
  
  if (ti->obj_type != NULL)
    fprintf (fout, "%s%s\n", OBJ_TYPE, ti->obj_type);
  
  if (ti->obj_key != NULL)
    fprintf (fout, "%s%s\n", OBJ_KEY, ti->obj_key);
  
  if (ti->source != NULL)
    fprintf (fout, "%s%s\n", SOURCE, ti->source);
  
  if (ti->syntax_errors)
    fprintf (fout, "%s\n", SYNTAX_ERRORS);
  
  if (ti->syntax_warns)
    fprintf (fout, "%s\n", SYNTAX_WARNS);
  
  if (ti->mnt_by[0] != '\0')
    fprintf (fout, "%s%s\n", MNT_BY, ti->mnt_by);
  
  if (ti->override != NULL)
    fprintf (fout, "%s%s\n", OVERRIDE, ti->override);

  if (ti->crypt_pw != NULL)
    fprintf (fout, "%s\"%s\"\n", PASSWORD, ti->crypt_pw);

  /* irr_auth */

  if (ti->otherfail != NULL)
    fprintf (fout, "%s%s\n", OTHERFAIL, ti->otherfail);

  if (ti->authfail)
    fprintf (fout, "%s\n", AUTHFAIL);
    
  if (ti->del_no_exist)
    fprintf (fout, "%s\n", DEL_NO_EXIST);
  
  if (ti->maint_no_exist != NULL)
    fprintf (fout, "%s%s\n", MAINT_NO_EXIST, ti->maint_no_exist);
  
  if (ti->old_obj_fname != NULL)
    fprintf (fout, "%s%s\n", OLD_OBJ_FILE, ti->old_obj_fname);

  if (ti->notify != NULL)
    fprintf (fout, "%s%s\n", NOTIFY, ti->notify);

  if (ti->forward != NULL)
    fprintf (fout, "%s%s\n", UPD_TO, ti->forward);

  if (ti->new_mnt_error)
    fprintf (fout, "%s\n", NEW_MNT_ERROR);

  if (ti->bad_override)
    fprintf (fout, "%s\n", BAD_OVERRIDE);

  if (ti->unknown_user)
    fprintf (fout, "%s\n", UNKNOWN_USER);

  fprintf (fout, "%s\n", HDR_END);

  
  return;

  fprintf (fout, "NOTIFY---\n");
  if (ti->nnext != ti->notify_addrs) {
    p = ti->notify_addrs;
    for (;p < ti->nnext; p += strlen (p) + 1)
      fprintf (fout, "  %s", p);
    fprintf (fout, ":\n");
  }
  
  fprintf (fout, "FORWARD---\n");
  if (ti->fnext != ti->forward_addrs) {
    p = ti->forward_addrs;
    for (; p < ti->fnext; p += strlen (p) + 1)
      fprintf (fout, "  %s", p);
    fprintf (fout, ":\n");
  }

  fprintf (fout, "----------\n");
}

/*
 * See if *op is a valid operation 
 */
int is_valid_op (char *op) {

  if (strcmp (op, ADD_OP)     &&
      strcmp (op, DEL_OP)     &&
      strcmp (op, REPLACE_OP) &&
      strcmp (op, NOOP_OP))
    return 0;
  
  return 1;
}

/*
 * This routine concat's x and y.  It assumes that the
 * caller is placing the result in x, so the routine
 * free's x if it points to something.
 * So the calling convention should be like this:
 * x = my_concat (x, y);
 *   x and/or y may be NULL and routine will work.
 */
char *my_concat (char *x, char *y) {
  char buf[MAXLINE];

  if (x == NULL)
    buf[0] = '\0';
  else {
    strcpy (buf, x);
    free_mem (x);
  }

  if (y != NULL)
    strcat (buf, y);

  if (buf[0] == '\0')
    return NULL;
  else
    return strdup (buf);
}

char *free_mem (char *p) {

  if (p != NULL)
    free (p);
  
  return NULL;
}

/* Given the transaction struct for this update, determine if there
 * are any errors.  Updates that do not have errors will be sent to
 * IRRd in a !us...!ue DB update.
 *
 * Return:
 *   1 if any error is found
 *   0 if no errors were found
 */
int update_has_errors (trans_info_t *ti) {

  return (ti->hdr_fields & ERROR_FLDS);
}


/* This routine finds a token in the string.  *x will
 * point to the first character in the string and *y will
 * point to the first character after the token.  A token
 * is a printable character string.  A '\n' is not considered
 * part of a legal token.  
 *   This function is rpsl-capable.  It will look for '#'s in
 * the string and assume everything after is a comment.
 * Invoke this routine by setting 'x' and 'y' to the beginning
 * of the target string like this: 'x = y = target_string;
 * if (find_token (&x, &y) < 0) ...
 * each successive call to find_token () will move the pointer
 * along.
 *
 * Return:
 *   1 if a token is found (*x points to token, *y first token after)
 *  -1 if no token is found (*x and *y are to be ignored)
 */
int find_token (char **x, char **y) {

  /* It's possible the target string is NULL 
   * or we are in a rpsl comment 
   */
  if (*y == NULL || **y == '#')
    return -1;

  *x = *y;
  /* fprintf (stderr, "p-(%c)\n",**x); */
  /* find the start of a token, ie, first printable character */
  while (**x != '\0' && (**x == ' ' || **x == '\t' || **x == '\n')) (*x)++;

  if (**x == '\0' || **x == '#')
    return -1;

  /* find the first space at the end of the token */
  *y = *x + 1;
  while (**y != '\0' && (isgraph ((int) **y) && **y != '#')) (*y)++;

  /* fprintf (stderr, "JW: find_token () returns 1..\n"); */
  return 1;  
}

/* This routine concat's x and y, putting a space
 * beteen x and y.  It assumes that the
 * caller is placing the result in x, so the routine
 * free's x if it points to something.
 * 
 * The calling convention should be like this:
 * x = myconcat (x, y);
 *   x and/or y may be NULL and routine will work.
 */
char *myconcat (char *x, char *y) {
  char buf[MAXLINE];

  if (x == NULL)
    buf[0] = '\0';
  else {
    strcpy (buf, x);
    free_mem (x);
  }

  if (y != NULL) {
    if (buf[0] != '\0')
      strcat (buf, " ");

    strcat (buf, y);
  }

  if (buf[0] == '\0')
    return NULL;
  else
    return strdup (buf);
}

/* Make a temp file name.  There can only be 26 mktemp files
 * per thread.  If mktemp () fails, try it with another name.
 * If mktemp () fails a second then about execution.
 *
 * Return:
 *   void
 */
void my_mktemp (trace_t *tr, char *fname) {

  mktemp (fname);
  if (!strcmp (fname, "")) {
     sprintf (fname, "%s%d.XXXXXX", "/var/tmp/irr.", mktemp_count++);
     trace (NORM, tr, "mktemp failure (count %d), changing to (%s)\n", mktemp_count, fname);
     mktemp (fname);
     if (!strcmp (fname, "")) {
       trace (NORM, tr, "mktemp failure a second time, abort!\n");
       exit (0);
     }
  }
}
