/* *************************************************************
   RWhois Software

   Copyright (c) 1994 Scott Williamson and Mark Kosters
   Copyright (c) 1996-2000 Network Solutions, Inc.

   See the file LICENSE for conditions of use and distribution.
**************************************************************** */


/* Implementation of xfer.
   -----------------------
   -xfer <auth-area> *[[class=] *[attr=]] [Serial-Num] */

#include <ctype.h>
#include "xfer.h"

#include "attributes.h"
#include "auth_area.h"
#include "client_msgs.h"
#include "defines.h"
#include "dl_list.h"
#include "fileinfo.h"
#include "log.h"
#include "misc.h"
#include "mkdb_types.h"
#include "records.h"
#include "types.h"
#include "strutil.h"
#include "schema.h"
#include "fileutils.h"

/* Data structure for a class; each class consists of a list of
   attributes; This class is an element in a linked-list of classes */
 
typedef struct xfer_class_struct 
{
  class_struct *class;
  dl_list_type attr_list;
} xfer_class_struct;
 

/* Data structure for the entire argument list.  The required auth-area is
   stored in auth_area, and the optional serial_num is stored in serial_num.
   The pointer to the head of the class list is first_class; */

typedef struct xfer_arg_struct 
{
  auth_area_struct *auth_area;
  char             *serial_no;
  dl_list_type     xfer_class_list;
} xfer_arg_struct;



/* ------------------- Local Functions -------------------- */


static int
destroy_xfer_class_data(xclass)
  xfer_class_struct *xclass;
{
  if (!xclass) return TRUE;

  dl_list_destroy(&(xclass->attr_list));
  free(xclass);

  return TRUE;
}

static int
destroy_xfer_arg_data(xarg)
  xfer_arg_struct *xarg;
{
  if (!xarg) return TRUE;

  if (xarg->serial_no)
  {
    free(xarg->serial_no);
  }

  dl_list_destroy(&(xarg->xfer_class_list));

  free(xarg);

  return TRUE;
}

/* xfer_split_av: splits a string into its attribute and 
value components. */

static void 
xfer_split_av(in_str, attr, value)
  char *in_str;
  char *attr;
  char *value;
{
  char  *eq = NULL;

  /* First see if there is an '=' in in_str.  If not, then copy the 
     whole string to value.  */
  eq = strchr(in_str, '=');
  
  if (!eq)
  {
    strcpy(value, in_str);
    attr[0] = '\0';
    return;
  }

  *eq++ = '\0';
  strcpy(attr, in_str);
  trim(attr);

  strcpy(value, eq);
  trim(value);

} /* end of xfer_split_av */


/* xfer_parse_args: function parses the argument list to the -xfer 
     directive.  It checks for all required arguments and stores the
     optional arguments in the appropriate data structure as defined
     in xfer_arg_struct.*/
static xfer_arg_struct * 
xfer_parse_args(str)
  char *str;
{
  xfer_arg_struct   *xs;
  xfer_class_struct *cur_xclass = NULL;
  auth_area_struct  *aa;
  class_struct      *class;
  attribute_struct  *a;
  int               argc;
  char              **argv;
  char              attr[MAX_LINE];
  char              value[MAX_LINE];
  int               i;
  int               j;
  char              *world = "0.0.0.0/0";  

  split_arg_list(str, &argc, &argv);

  if (argc < 1)
  {
    print_error(INVALID_DIRECTIVE_PARAM, "");
    free_arg_list(argv);
    return NULL;
  }

  xs = xcalloc(1, sizeof(*xs));
  dl_list_default(&(xs->xfer_class_list), FALSE, destroy_xfer_class_data);

  if (STR_EQ(world, argv[0]))
  {
    print_error(INVALID_AUTH_AREA, "No world dumps allowed");
    return NULL;
  }

  /* locate the authority area */
  aa = find_auth_area_by_name(argv[0]);
  if (!aa)
  {
    print_error(INVALID_AUTH_AREA, "");
    free_arg_list(argv);
    destroy_xfer_arg_data(xs);
    return NULL;
  }
    if (aa->type == AUTH_AREA_SECONDARY)
    {
        print_error(NOT_MASTER_AUTH_AREA, "");
    free_arg_list(argv);
    destroy_xfer_arg_data(xs);
    return NULL;
  }

  xs->auth_area = aa;

  /* short-circuit check */
  if (argc == 1)
  {
    free_arg_list(argv);
    return(xs);
  }

  /* Split each argv into an AV pair.  Compare the A with "class" 
     or "attr" and store it in the appropriate variables.  If it
     is a Serial-Num, then store it in Serial-Num.  */

  for (i = 1; i < argc; i++)
  {
    xfer_split_av(argv[i], attr, value);

    if (!*attr)
    {
            if (strlen(value) != 17)
            {
                print_error(INVALID_DIRECTIVE_PARAM, "Wrong Version Number Format");
                free_arg_list(argv);
                destroy_xfer_class_data(cur_xclass);
                destroy_xfer_arg_data(xs);
                return NULL;
            }
            for (j = 0; j < strlen(value); j++)
            {
                if (!isdigit(value[j]))
                {
                    print_error(INVALID_DIRECTIVE_PARAM, "Wrong Version Number Format");
          free_arg_list(argv);
          destroy_xfer_class_data(cur_xclass);
          destroy_xfer_arg_data(xs);
          return NULL;
              }
      }
      xs->serial_no = xstrdup(value);
    }
    else
    {
      if (STR_EQ(attr, "class"))
      {
        cur_xclass = xcalloc(1, sizeof(*cur_xclass));
        dl_list_default(&(cur_xclass->attr_list), FALSE, null_destroy_data);
        
        class = find_class_by_name(aa->schema, value);
        if (!class)
        {
          print_error(INVALID_CLASS, "");
          free_arg_list(argv);
          destroy_xfer_class_data(cur_xclass);
          destroy_xfer_arg_data(xs);
          return NULL;
        }

        cur_xclass->class = class;
        dl_list_append(&(xs->xfer_class_list), cur_xclass);
      } /* end of if (STR_EQ(attr, "class")) */
      else if (STR_EQ(attr, "attr"))
      {
        if (!cur_xclass)
        {
          print_error(INVALID_DIRECTIVE_PARAM, "");
          free_arg_list(argv);
          destroy_xfer_arg_data(xs);
          return NULL;
        }
        
        a = find_attribute_by_name(class, value);
        if (!a)
        {
          print_error(INVALID_ATTRIBUTE, "");
          free_arg_list(argv);
          destroy_xfer_arg_data(xs);
          return NULL;
        }
        dl_list_append(&(cur_xclass->attr_list), a);
      } /* end of else if (STR_EQ(attr, "attr")) */
    }  /* end of else. */
  } /* end of for loop */

  free_arg_list(argv);
  
  return(xs);
}  /* end of xfer_parse_args */


/* attr_in_xfer_class: check to see if attr is in the list of named
     attributes; if the xfer_class's attribute list is empty, assume
     that we want all attributes */
static int
attr_in_xfer_class(attr, xfer_class)
  attribute_struct  *attr;
  xfer_class_struct *xfer_class;
{
  dl_list_type     *attr_list;
  attribute_struct *a;
  int              not_done;
  
  attr_list = &(xfer_class->attr_list);

  if (dl_list_empty(attr_list)) return TRUE;
  
  not_done = dl_list_first(attr_list);
  while (not_done)
  {
    a = dl_list_value(attr_list);
    if (attr->local_id == a->local_id)
    {
      return TRUE;
    }

    not_done = dl_list_next(attr_list);
  }

  return FALSE;
}

static int
is_record_new(rec, serial_no)
  record_struct *rec;
  char          *serial_no;
{
  av_pair_struct *av;

  if (!rec || !serial_no || !*serial_no)
  {
    return FALSE;
  }

  av = find_attr_in_record_by_name(rec, "updated");
  if (av && av->value && (strcmp(av->value, serial_no) >= 0))
  {
    return TRUE;
  }

  return FALSE;
}

/* xfer_display_record:  xfer_display_record prints the record to the
     standard output.  This record belongs to the class class and
     displays only those fields which match the attributes given in
     curr_class.  It returns TRUE if any data is displayed. */
static int 
xfer_display_record(rec, class, curr_class)
  record_struct     *rec;
  class_struct      *class;
  xfer_class_struct *curr_class;
{
  av_pair_struct *av_pair;
  int            not_done;
  int            found_attr = FALSE;
  
  not_done = dl_list_first(&(rec->av_pair_list));

  while (not_done)
  {
    av_pair = dl_list_value(&(rec->av_pair_list));

    /* if curr_class is null, we want to transfer all attributes */
    if (!curr_class || attr_in_xfer_class(av_pair->attr, curr_class))
    {
      found_attr++;
      print_response(RESP_XFER, "%s:%s:%s", rec->class->name,
                     av_pair->attr->name, SAFE_STR(av_pair->value, ""));
    }

    not_done = dl_list_next(&(rec->av_pair_list));

  } /* end of while (not_done_rec) */

  if (found_attr)
  {
    print_response(RESP_XFER, "");
  }

  return(found_attr);
}  /* end of xfer_display_record */


/* xfer_file_xfer:  This function xfers data from curr_file containing data
     of curr_class in authority area aa, depending on the value of serial_no.
     It returns TRUE if any data is xferred. */ 
static int 
xfer_file_xfer(aa, curr_file, class, curr_class, serial_no)
  auth_area_struct  *aa;
  file_struct       *curr_file; 
  class_struct      *class;
  xfer_class_struct *curr_class;
  char              *serial_no;
{
  record_struct    *rec;
  FILE             *fp;
  rec_parse_result status;
  int              found = 0;
  
  fp = fopen(curr_file->filename, "r");
  if (!fp)
  {
    /* FIXME: log and print_error */
    return FALSE;
  }

  while ( (rec = mkdb_read_next_record(class, aa, curr_file->file_no, 0,
                                       &status, fp)) 
          != NULL )
  {
    if (!serial_no || is_record_new(rec, serial_no))
    {
      found += xfer_display_record(rec, class, curr_class);
    }

    destroy_record_data(rec);
  }  /* end of while ((rec = mkdb_read_record(class, ... ) */

  fclose(fp);   

  return(found);
}  /* end of xfer_file_xfer */


/* xfer_class:  This function xfers data from a class curr_class in an
   authority area aa, depending upon the value of the serial number.  It
   returns TRUE on successful xfer of data. */
static int 
xfer_class(aa, class, curr_class, serial_no)
  auth_area_struct  *aa;
  class_struct      *class;
  xfer_class_struct *curr_class; 
  char              *serial_no;
{
  int          not_done;
  int          found_data       = 0;
  dl_list_type master_file_list;
  dl_list_type file_list;
  file_struct  *curr_file;
  
  if (!aa || !class)
  {
    return FALSE;
  }

  dl_list_default(&master_file_list, FALSE, destroy_file_struct_data);
  dl_list_default(&file_list, FALSE, destroy_file_struct_data);
  
  /* pull all of the data files for the current class */
  if (!get_file_list(class, aa, &master_file_list))
  {
    return FALSE;
  }
  filter_file_list(&file_list, MKDB_DATA_FILE, &master_file_list);
  dl_list_destroy(&master_file_list);
  
  not_done = dl_list_first(&file_list);
    
  while (not_done)
  {
    curr_file = dl_list_value(&file_list);
    if (!file_exists(curr_file->filename))
    {
      return FALSE;
    }
    found_data += xfer_file_xfer(aa, curr_file, class, 
                                curr_class, serial_no);

    not_done = dl_list_next(&file_list);
  } /* end of while (not_done_file) */

  dl_list_destroy(&file_list);
  
  return(found_data);
}  /* end of xfer_class */


/* xfer_all_classes:  This function xfers data from all classes in an
     authority area aa, depending upon the value of serial number.  It
     returns TRUE if data is transferred.  */
static int 
xfer_all_classes(aa, serial_no)
  auth_area_struct *aa;
  char             *serial_no;
{
  int          not_done;
  int          found_data = 0;
  dl_list_type *class_list;
  class_struct *class;

  if (!aa)
  {
    return FALSE;
  }

  class_list = &(aa->schema->class_list);
  
  not_done = dl_list_first(class_list);

  while (not_done)
  {
    class = dl_list_value(class_list);

    found_data += xfer_class(aa, class, NULL, serial_no);

    not_done = dl_list_next(class_list);
  } /* end of while (not_done_class) */

  return(found_data);
}  /* end of xfer_all_classes */

static int
xfer_some_classes(xs)
  xfer_arg_struct *xs;
{
  xfer_class_struct *curr_class;
  int               not_done;
  int               found_data = 0;

  not_done = dl_list_first(&(xs->xfer_class_list));

  while (not_done)
  {
    curr_class  = dl_list_value(&(xs->xfer_class_list));
    found_data += xfer_class(xs->auth_area, curr_class->class, curr_class,
                             xs->serial_no);

    not_done = dl_list_next(&(xs->xfer_class_list));
  }
  
  return(found_data);
}

/* ------------------- PUBLIC FUNCTIONS ------------------- */


/* xfer_directive:  This function xfers data depending upon the 
   parameters passed in str.  Returns TRUE on success. */

int 
xfer_directive( str)
  char *str;
{
  xfer_arg_struct   *xs;
  int               found_data;
  
  xs = xfer_parse_args(str);

  if (!(xs))
  {
    return FALSE;
  }

  log(L_LOG_DEBUG, CLIENT, "xfer directive: %s", str);
  
  if (dl_list_empty(&(xs->xfer_class_list)))
  {
    found_data = xfer_all_classes(xs->auth_area, xs->serial_no);
  }
  else
  {
    found_data = xfer_some_classes(xs);
  }
  
  destroy_xfer_arg_data(xs);
    
  if (found_data)
  {
/*     print_ok(); */
    return TRUE;
  }

  print_error(NO_TRANSFER, "");
  return FALSE;
  
}
