/***************************************
  $Revision: 1.4 $

  UP external syntax checks

  Status: NOT REVIEWED, NOT TESTED

  Author(s):       Engin Gunduz

  ******************/ /******************
  Modification History:
        engin (15/12/2000) Created.
  ******************/ /******************
  Copyright (c) 2001                              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 "rpsl/object.hh"
#include "UP_extrnl_syntax.h"
#include "dbupdate.h"


extern int tracing;




/* obtains a list of dates in the given 
   list of attributes (GSList of attribute_struct) */
GSList * up_get_dates(GSList * attribute_list){

  GSList * next;
  char * temp, * str; 
  int i;
  //char * date;
  GSList * list = NULL;

  for( next = attribute_list; next != NULL ; next = g_slist_next(next) ){
    /* is this a 'changed' attribute? */
    if(strcmp((char *)(((attribute_struct *)(next->data))->type), "changed") == 0){
      temp = strdup(((attribute_struct *)(next->data))->content);

      /* delete the part after '#', inclusive */
      if(index(temp,'#') != NULL){
        temp[index(temp, '#') - temp] = '\0';
      }
      /* replace \n, \r & \t's with " " */
      for(i = 0; i < strlen(temp) ;i++){
        if(temp[i] == '\n' || temp[i] == '\r' || temp[i] == '\t' ){
          temp[i] = ' ';
        }
      }
      g_strstrip(temp);
      /* delete multiple spaces */
      str = (char *)malloc(strlen(temp) + 1);
      up_string_pack(str, temp);
      free(temp); 

      /* now, we have the 'changed' attribute's content in "normalized" form 
         We are sure it contains a date. So, it must be the second (and last)
         word in the attrib. */
      assert(index(str,' ') != NULL);
      temp = (char *)malloc(strlen(str) - (index(str,' ') - str ));
      temp = strncpy(temp, index(str,' ') + 1, strlen(str) - (index(str,' ') - str ) - 1);
      temp[strlen(str) - (index(str,' ') - str ) - 1] = '\0'; /* NULL terminate it */
      list = g_slist_append (list, temp);   
    }
  }
  
  return list;
}




/* Does the 'changed' attribute we got have a date already?
   Returns 1 if it does, 0 if not. */
int up_changed_has_date(char * arg){
  
  int i;
  char * str;
  char * temp;
  
  str = strdup(arg);

  /* cut off the EOL comments */
  if(index(str, '#')){
    str[index(str, '#') - str - 1 ] = '\0';
  }
  
  /* replace \n, \r & \t's with " " */
  for(i = 0; i < strlen(str) ;i++){
    if(str[i] == '\n' || str[i] == '\r' || str[i] == '\t' ){
      str[i] = ' ';
    }
  }
  g_strstrip(str);
  /* delete multiple spaces */
  temp = (char *)malloc(strlen(str) + 1);
  up_string_pack(temp, str);
  
  free(str);
  str = temp;
  
  /* now, if there is still a white space, then we have a date in the string
     (it has to be something like "ripe-dbm@ripe.net 20001210") */
  if(index(str, ' ') != NULL){
    return 1; 
  }else{
    return 0;
  }
}




/* supplies the current date in YYYYMMDD format (for example 20011010) */
char * up_get_current_date(){
  /* We will use Glib's functions here */

  char * date;
  struct tm * time_struct;
  
  time_t * time_loc;

  time_loc = (time_t *)malloc(sizeof(time_t));
  time(time_loc);
  
  time_struct = localtime(time_loc);


  date = (char *)malloc(9);
  sprintf(date, "%04i%02i%02i", 
          time_struct->tm_year + 1900, 
          time_struct->tm_mon + 1,
          time_struct->tm_mday);
  return date;
}








/* void add_dates: adds dates to 'changed' attributes which 
     are missing one.
     Returns 1 if no problems encountered
     Returns 0 if a problem encountered, and the error string is set */
int up_add_dates(GSList * attribute_list, char ** warning_str, char ** error_str){
  
  GSList * next;
  char * attribute, * current_date;
  int count_no_date = 0; 
  char * temp;
  
  *warning_str = NULL;
  *error_str   = NULL;

  /* get the current date in YYYYMMDD format (for example 20011010) */
  current_date = up_get_current_date();
  
  for( next = attribute_list; next != NULL ; next = g_slist_next(next) ){
    /* is this a 'changed' attribute? */
    if(strcmp((char *)(((attribute_struct *)(next->data))->type), "changed") == 0){
      /* if this attribute does not have a date in it, add it. Also add 
          a warning message about this */
      if( !up_changed_has_date((char *)(((attribute_struct *)(next->data))->content))){
        count_no_date++;
        attribute = (char *)(((attribute_struct *)(next->data))->content);
        temp = (char *)malloc(strlen(attribute) + strlen(current_date) + 2 );
        if(index(attribute, '#')){/* cut off the EOL comments */
          attribute[index(attribute, '#') - attribute - 1] = '\0';
        }
        sprintf(temp, "%s %s", attribute, current_date);
        ((attribute_struct *)(next->data))->content = temp;
        free(attribute);
        /* add a warning message */
        if( *warning_str == NULL){
          *warning_str = (char *)malloc(strlen("WARNING  date '' added to 'changed' attribute") + 9 );
          sprintf(*warning_str, "WARNING  date '%s' added to 'changed' attribute", current_date);
        }else{
          temp = (char *)malloc(strlen(*warning_str) + 1 
                                       + strlen("WARNING  date '' added to 'changed' attribute") + 9 );
          sprintf(temp, "%s\nWARNING  date '%s' added to 'changed' attribute", 
                              *warning_str, current_date);
          free(*warning_str);
          *warning_str = temp; 
        }
      }
    }
  }
  
  if(count_no_date > 1){ 
    *error_str = strdup("***Error: More than one 'changed' attributes without dates");
    return 0;
  }else{
    return 1;
  }
  

}






/* Checks the order of dates in the given list. 
   If they are in order, returns 1, 
   if not, returns 0 */
int up_check_date_order(GSList * list){
   
  GSList * next;
  char * previous;

  /* if list is empty, return 1 immediately */
  if(list == NULL){
    return 1;
  }

  /* initialize the 'previous' date */
  previous = strdup("00000000");
   
  for( next = list; next != NULL ; next = g_slist_next(next)){
    assert((next->data) != NULL);
    /* if the new date is smaller than the previous */
    if(strcmp((char *)(next->data), previous) < 0 ){
      free(previous);
      return 0;
    }
    free(previous);
    previous = strdup((char *)(next->data));
  }
   
  free(previous);
  /* Reached the end, without finding out-of-order date. Return 1, then */
  return 1; 
   
}







/* void up_check_changed_attr 
   checks the order of dates in the 'changed' attributes */
void up_check_changed_attr(Object * obj, char * obj_text, GSList * attribute_list, external_syntax_struct * result){

  GSList * date_list;
  int res;
  char ** warning, **error;
  char * temp;

  warning = (char **)malloc(sizeof(char **));
  error   = (char **)malloc(sizeof(char **));
   

  /* Now, add dates to the "changed" attributes */
  res = up_add_dates(/*changed_list*/attribute_list, warning, error);
  if(!res){
    /* so, add the error string to result's error string */
    if(result->error_str == NULL){
      result->error_str = strdup(*error);
    }else{
      temp = (char *)malloc(strlen(result->error_str) + strlen(*error) + 2);
      sprintf(temp, "%s\n%s", result->error_str, *error);
      free(result->error_str);
      result->error_str = temp;
    }
  }

  /* and get the list of dates, we must check their order */
  date_list = up_get_dates(attribute_list);
  /* and check the order */ 
  res = up_check_date_order(date_list);
  if(!res){
    /* so, add the error string to result's error string */
    if(result->error_str == NULL){
      result->error_str = strdup("***Error: The dates in the 'changed' attributes should be in order");
    }else{
      temp = (char *)malloc(strlen(result->error_str) 
              + strlen("***Error: The dates in the 'changed' attributes should be in order") + 2);
      sprintf(temp, "%s\n%s", result->error_str, 
              "***Error: The dates in the 'changed' attributes should be in order");
      free(result->error_str);
      result->error_str = temp;
    }
    /* and here we have to change the result code of "result" here ... */
    switch(result->result){
      case UP_EXTSYN_OK:       result->result = UP_EXTSYN_ERR; break;
      case UP_EXTSYN_ERR:      result->result = UP_EXTSYN_ERR; break;
      case UP_EXTSYN_WARN:     result->result = UP_EXTSYN_ERR_WARN; break;
      case UP_EXTSYN_ERR_WARN: result->result = UP_EXTSYN_ERR_WARN; break;
      default: ;
    }
  }
}





int up_check_an_auth_attr(const char * arg, char ** error){

  char * attr;
  int ret = 1;
  char * cryptpw, * key;


  attr = strdup(arg);
  /*  chop the whitespace in hte beginning and end */
  g_strstrip(attr);

  /* Convert to uppercase  */
  g_strup(attr);

  /* chop the EOL comment off */
  if(index(attr, '#') != NULL){
    attr[index(attr, '#') - attr ] = '\0';
  }

  if(strstr(attr, "MAIL-FROM ") == attr){
    /* It must have somethings after "MAIL-FROM ", which are
      supposed to be a regexp */
    if(strlen(attr) <= strlen("MAIL-FROM ")){
      ret = 0;
    }
  }else if(strstr(attr, "NONE") == attr){
    /* We mustn't have anything after "NONE"  */
    if(strlen(attr) != strlen("NONE")){
      *error = strdup("***Error: There mustn't be anything after NONE in 'auth' attribute");
      ret = 0;
    }
  }else if(strstr(attr, "CRYPT-PW ") == attr){
    /* The string after CRYPT-PW must be of length 13 and must consist of certain
       characters */
    cryptpw = strdup(attr + strlen("CRYPT-PW "));
    g_strstrip(cryptpw);
    if(strlen(cryptpw) != 13){
      *error = strdup("***Error: The crypted password must be 13-character long in 'auth' attribute");
      free(cryptpw);
      ret = 0;
    }
  }else if(strstr(attr, "PGPKEY-") == attr){
    /* The string after CRYPT-PW must be of length 13 and must consist of certain
       characters */
    key = strdup(attr + strlen("PGPKEY-"));
    g_strchomp(key);
    if(strlen(key) != 8){
      *error = strdup("***Error: The PGP key must be 8-character long in 'auth' attribute");
      
      free(key);
      ret = 0;
    }

  }else{
    *error = strdup("***Error: 'auth' attribute must start with MAIL-FROM, NONE, PGPKEY- or CRYPT-PW");
    ret = 0;
  }

  free(attr);
  return ret;
}







/* void up_check_auth_attr 
   checks the syntax of 'auth' attributes */
void up_check_auth_attr(Object * obj, char * obj_text, GSList * attribute_list, external_syntax_struct * result){

  //GSList * date_list;
  int res;
  char /* ** warning,*/ **error;
  char * temp;
  GSList * next;

  //warning = (char **)malloc(sizeof(char **));
  error   = (char **)malloc(sizeof(char **));

  /* loop in the attribute_list, find the 'auth' attribs, and check their syntax */
  for( next = attribute_list; next != NULL ; next = g_slist_next(next) ){
    /* is this an 'auth' attribute? */
    if(strcmp((char *)(((attribute_struct *)(next->data))->type), "auth") == 0){
      /* chech its syntax */
      res = up_check_an_auth_attr((char *)(((attribute_struct *)(next->data))->content), error);
      if(!res){
        /* so, add the error string to result's error string */
        if(result->error_str == NULL){
          result->error_str = strdup(*error);
        }else{
          temp = (char *)malloc(strlen(result->error_str) 
                  + strlen(*error) + 2);
          sprintf(temp, "%s\n%s", result->error_str, 
                  *error);
          free(result->error_str);
          result->error_str = temp;
        }
        /* and here we have to change the result code of "result" here ... */
        switch(result->result){
          case UP_EXTSYN_OK:       result->result = UP_EXTSYN_ERR; break;
          case UP_EXTSYN_ERR:      result->result = UP_EXTSYN_ERR; break;
          case UP_EXTSYN_WARN:     result->result = UP_EXTSYN_ERR_WARN; break;
          case UP_EXTSYN_ERR_WARN: result->result = UP_EXTSYN_ERR_WARN; break;
          default: ;
        }
      }
    }
  }
}











/* Constructs a list of all attributes of an object, and returns it
   as a list of attribute_struct */
GSList * up_get_attribute_list(Object * o, char * text){

  char * value = NULL;
  Attr *attr;
  GSList *list_of_attributes = NULL;
  attribute_struct * attribute;
  
  for(attr = o->attrs.head(); attr; attr = o->attrs.next(attr)){
    value = (char*)malloc((*attr).len - strlen(attr->type->name()) - 1);
    strncpy(value, (char *)(text+attr->offset) + strlen(attr->type->name())+1,
        attr->len - strlen(attr->type->name()) -2 );
    value[attr->len - strlen(attr->type->name()) - 2 ] = '\0';
      if(tracing) {
        cout << "TRACING: up_get_attributes: adding " << g_strstrip(value) << endl;
      }
      attribute = (attribute_struct *)malloc(sizeof(attribute_struct)); 
      attribute->content = value;
      attribute->type = strdup(attr->type->name());
      list_of_attributes = g_slist_append(list_of_attributes, attribute);
  }

  
  return list_of_attributes; 

}





void up_reconstruct_object(GSList * attr_list, external_syntax_struct *result){

  char * recons_obj = NULL;
  char * temp;
  char * content, * type;
  GSList * next;




  for( next = attr_list; next != NULL ; next = g_slist_next(next) ){

    content = strdup((char *)(((attribute_struct *)(next->data))->content));
    type = strdup((char *)(((attribute_struct *)(next->data))->type));

    if(tracing){
      printf("TRACING: up_reconstruct_object:[%s:%s]\n", type, content);
    }
    
    if(recons_obj == NULL){
      recons_obj = (char *)malloc(13 + strlen(content) + 1 );
      /* trim the white spaces in the beginning */
      g_strchug(content);

      /* add ':' at the end of 'type' */
      temp = (char *)malloc(strlen(type) + 2);
      sprintf(temp, "%s:", type);
      free(type);
      type = temp;

      sprintf(recons_obj, "%-13s%s", type, content);
    }else{
      /* trim the white spaces in the beginning */
      g_strchug(content);

      /* add ':' at the end of 'type' */
      temp = (char *)malloc(strlen(type) + 2);
      sprintf(temp, "%s:", type);
      free(type);
      type = temp;
      
      temp = (char *)malloc(strlen(recons_obj) + 13 + strlen(content) + 3 );
      sprintf(temp, "%s\n%-13s%s", recons_obj, type, content);
      free(recons_obj);
      recons_obj = temp;
    }
    
  }
  
  result->new_obj = recons_obj;
  
}





external_syntax_struct * UP_check_external_syntax(Object * arg, char * obj_text){
  
  external_syntax_struct *result;
  GSList * attribute_list;
  
 


  result = (external_syntax_struct *)malloc(sizeof(external_syntax_struct));

  /* initialize the struct */
  result->result = 0;
  result->error_str = strdup(""); 
  result->warning_str = strdup("");

  /* get a list of all attributes */
  attribute_list = up_get_attribute_list(arg, obj_text);

  up_check_changed_attr(arg, obj_text, attribute_list, result);
  up_check_auth_attr   (arg, obj_text, attribute_list, result);  

  
  up_reconstruct_object(attribute_list, result);

  if(tracing){
    printf("TRACING: UP_check_external_syntax: the reconstructed object is=[%s]\n", result->new_obj);
    printf("TRACING: UP_check_external_syntax: ... and the result code is=[%i]\n", result->result);
  }

  return result;  
}
