/***************************************
  $Revision: 1.3 $

  UP pre process checks

  Status: NOT REVIEWED, NOT TESTED

  Author(s):       Engin Gunduz, Denis Walker 

  ******************/ /******************
  Modification History:
        engin (15/12/2000) first Created.
        denis (31/08/2001) Modified for new parser API
        denis (11/11/2002) re-write for re-structured dbupdate
  ******************/ /******************
  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 <glib.h>
#include <assert.h>
#include "rip.h"

#include "ca_defs.h"
#include "dbupdate.h"
#include "up_pre_process.h"

/* these enum values and error messages must be kept in sync */
typedef enum
{
  UP_DATE_OK = 0,
  UP_DATE_SYNERR,    
  UP_DATE_FUTURE,    
  UP_DATE_TOOSMALL, 
  UP_DATE_INVMONTH, 
  UP_DATE_INVDAY,    
  UP_DATE_WRONGFORMAT  
} date_return_codes;

char *up_date_errmsgs[]=
{
  "OK",
  "Syntax error in date of changed: attribute",
  "Date in the future in changed: attribute",
  "Date is older than the database itself in changed: attribute",
  "Invalid month in date in changed: attribute",
  "Invalid day in date in changed: attribute",
  "Date must be in YYYYMMDD format in changed: attribute"
};



/* checks that each country in the 'country' attributes is valid
   Receives RT context
            LG context
            parsed object
            array of country code strings
   Returns  UP_FAIL if any country is found to be invalid
            UP_OK if all countries are valid
*/

int UP_check_country_attr(RT_context_t *rt_ctx, LG_context_t *lg_ctx,
                             rpsl_object_t *preproc_obj, char **countries)
{
  int retval = UP_OK; 
  int matched ;
  int ctry_idx;
  char *country_name;
  GList *country_list = NULL;
  GList *country_item = NULL;
 
  LG_log(lg_ctx, LG_FUNC,">UP_check_country_attr: entered\n");

  /* get country attributes from object */
  country_list = rpsl_object_get_attr(preproc_obj, "country");
  rpsl_attr_split_multiple(&country_list);
  
  if ( country_list == NULL )
  {
    /* can only fail if there is a country that is invalid */
    LG_log(lg_ctx, LG_DEBUG,"UP_check_country_attr: object contains no countries");
    LG_log(lg_ctx, LG_FUNC,"<UP_check_country_attr: exiting with value UP_OK");
    return UP_OK;		
  }

  /* check each country from the object and make sure it is in the countries list
     if any one of the countries from the object is not found, return UP_FAIL,
     after checking any remaining countries */
  for ( country_item = country_list; country_item != NULL ; country_item = g_list_next(country_item) )
  {
    country_name = rpsl_attr_get_clean_value( (rpsl_attr_t *)(country_item->data) );
    matched = 0 ;
    ctry_idx = 0 ;
    while(countries[ctry_idx])
    {
      if(strcasecmp(countries[ctry_idx++],country_name) == 0)
      {
        matched = 1;
        break;
      }
    }

    if ( ! matched )
    {
      /* found a country in the object that is not recognised */
      retval = UP_FAIL;
      LG_log(lg_ctx, LG_DEBUG,"UP_check_country_attr: country not recognised [%s]", country_name);
      RT_unknown_country(rt_ctx, country_name);
    }
    free(country_name);
  }
  rpsl_attr_delete_list(country_list);

  LG_log(lg_ctx, LG_FUNC,"<UP_check_country_attr: exiting with value [%s]\n", UP_ret2str(retval));
  return retval;
}



/* checks for a valid suffix at the end of a 'nic-hdl' attributes 
   Receives RT context
            LG context
            options structure
            parsed object
            array of country code strings
   Returns  UP_OK if the nic suffix is valid
            UP_FAIL otherwise
*/

int UP_check_nicsuffixes(RT_context_t *rt_ctx, LG_context_t *lg_ctx,
                             options_struct_t *options, 
                             rpsl_object_t *preproc_obj, char **countries)
{
  int retval = UP_FAIL;
  int str_idx, src_idx, nic_idx, ctry_idx;
  char *name;
  char *nic_str = NULL;
  char *nic_suffixes[NIC_SUFFIX_LIST_SIZE];
  char **temp_vector;
  int num_sources = 0;
  ca_updDbSource_t **upd_source_hdl;
  GList *list;

  LG_log(lg_ctx, LG_FUNC,">UP_check_nicsuffixes: entered\n");

  /* get the nic-hdl from the object, there will either be one or none */
  list = rpsl_object_get_attr(preproc_obj, "nic-hdl");

  if ( list )
  {
    name = rpsl_attr_get_clean_value( (rpsl_attr_t *)(list->data) );
    LG_log(lg_ctx, LG_DEBUG,"UP_check_nicsuffixes: nic-hdl [%s]", name);

    if ( !strchr(name,'-') || strncasecmp(name,"AUTO-",strlen("AUTO-")) == 0 )
    {
      /* the nic-hdl does not have a suffix, or it is an AUTO- nic-hdl */
      retval = UP_OK;
    }
    else
    {
      /* first check against list of standard nic-suffixes */
      /* get nic suffix details from config file */
      LG_log(lg_ctx, LG_INFO,"UP_check_nicsuffixes: get nic suffixes from config file");
      nic_str = ca_get_nicsuffix;

      /* construct nic_suffixes array from nic string variable */
      temp_vector = g_strsplit(nic_str, "\n", 0);
      for (str_idx=0, nic_idx=0; temp_vector[str_idx] != NULL; str_idx++)
      {
        if ( nic_idx == NIC_SUFFIX_LIST_SIZE )
        {
          LG_log(lg_ctx, LG_FATAL, "UP_check_nicsuffixes: NIC_SUFFIX_LIST_SIZE exceeded");
          UP_internal_error(rt_ctx, lg_ctx, options, "UP_check_nicsuffixes: NIC_SUFFIX_LIST_SIZE exceeded\n");
        }

        temp_vector[str_idx] = g_strstrip(temp_vector[str_idx]);
        if (strlen(temp_vector[str_idx]) > 0)
	    {
          nic_suffixes[nic_idx] = strdup(temp_vector[str_idx]);
          g_strup(nic_suffixes[nic_idx++]);
        }
      }
      nic_suffixes[nic_idx] = NULL; /* mark the end of array */
      g_strfreev(temp_vector);
      free(nic_str);
      LG_log(lg_ctx, LG_DEBUG,"UP_check_nicsuffixes: number of nic suffixes [%d]", nic_idx);

      /* compare nic suffix with list entries */
      nic_idx = 0 ;
      while (nic_suffixes[nic_idx])
      {
        if ( strcasecmp(nic_suffixes[nic_idx++],strchr(name,'-')+1) == 0 )
        {
          LG_log(lg_ctx, LG_DEBUG,"UP_check_nicsuffixes: nic-hdl [%s] matched nic-suffix [%s]", name, nic_suffixes[--nic_idx]);
          retval = UP_OK;
          break;
        }
      }

      if ( retval != UP_OK )
      {
        /* next check against source names */
        /* retrieve source variables for the multiple sources */
        /* upd_source_hdl is a pointer to an array of pointers to source data 
           held in the ca module */
        LG_log(lg_ctx, LG_INFO,"UP_check_nicsuffixes: get sources from config file");
        upd_source_hdl = ca_get_UpdSourceHandle(CA_UPDSOURCE);

        if (upd_source_hdl[0] == NULL)
        {
          LG_log(lg_ctx, LG_FATAL,"UP_check_nicsuffixes: There must be at least one updateable source in the config file");
          UP_internal_error(rt_ctx, lg_ctx, options,
                 "There must be at least one updateable source in the config file.");
        }
        num_sources = ca_get_UpdSourceNum();
        LG_log(lg_ctx, LG_DEBUG,"UP_check_nicsuffixes: number of sources [%d]", num_sources);

        for ( src_idx=0; src_idx<num_sources; src_idx++ )
        {
          if ( ! strcasecmp( upd_source_hdl[src_idx]->name, strchr(name,'-')+1) )
          {
            LG_log(lg_ctx, LG_DEBUG,"UP_check_nicsuffixes: nic-hdl [%s] matched source [%s]", name, upd_source_hdl[src_idx]->name);
            retval = UP_OK;
            break;
          }
        }

        if ( retval != UP_OK )
        {
          /* finally check against country codes */
          ctry_idx = 0 ;
          while (countries[ctry_idx])
          {
            if ( strcasecmp(countries[ctry_idx++],strchr(name,'-')+1) == 0 )
            {
              LG_log(lg_ctx, LG_DEBUG,"UP_check_nicsuffixes: nic-hdl [%s] matched country [%s]", name, countries[--ctry_idx]);
              retval = UP_OK;
              break;
            }
          }
        }
      }
    }

    if ( retval != UP_OK )
    {
      /* the nicsuffix is not recognised */
      LG_log(lg_ctx, LG_DEBUG,"UP_check_nicsuffixes: nic-hdl suffix not recognised [%s]", name);
      RT_unknown_nic_suffix(rt_ctx);
    }

    free(name);
    rpsl_attr_delete_list(list);
  }
  else
  {
    LG_log(lg_ctx, LG_DEBUG,"UP_check_nicsuffixes: no nic-hdl found");
    retval = UP_OK;
  }
  
  LG_log(lg_ctx, LG_FUNC,"<UP_check_nicsuffixes: exiting with value [%s]\n", UP_ret2str(retval));
  return retval;
}



/* obtains a list of dates from the given list of attributes
   Receives LG context
            list of changed attributes
   Returns  list of date strings
*/

GList *up_get_dates(LG_context_t *lg_ctx, GList *changed_list)
{
  GList *item;
  char *str, *temp; 
  GList *list = NULL;

  LG_log(lg_ctx, LG_FUNC,">up_get_dates: entered\n");

  for ( item = changed_list; item != NULL ; item = g_list_next(item) )
  {
    str = rpsl_attr_get_clean_value((rpsl_attr_t *)(item->data));

    /* 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);  /* shouold never happen */
    /* extract the date from the attribute value */
    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_list_append (list, temp);   
    LG_log(lg_ctx, LG_DEBUG,"up_get_dates: date [%s]", temp);
  }
  
  LG_log(lg_ctx, LG_FUNC,"<up_get_dates: exiting\n");
  return list;
}



/* Does this 'changed' attribute have a date already?
   Receives value part of changed attribute
   Returns 1 if it does
           0 if not.
*/

int up_changed_has_date(char *value)
{
  /* 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") 
     a later check tests the second word for a valid date format */
  if (index(value, ' ') != NULL)
  {
    return 1; 
  }
  else
  {
    return 0;
  }
}



/* supplies the current date in YYYYMMDD format (for example 20011010) 
   Receives none
   Returns  date string
*/

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;
}



/* adds dates to 'changed' attributes which are missing one.
   Receives RT context
            LG context
            parsed object
   Returns  UP_FAIL if more than one changed line with no date found
            UP_OK otherwise
*/

int up_add_dates(RT_context_t *rt_ctx, LG_context_t *lg_ctx,
                      rpsl_object_t *preproc_obj)
{
  int retval = UP_OK;
  char *current_date = NULL;
  int count_no_date = 0; 
  char *value = NULL;
  char *temp = NULL;
  int pos;
  rpsl_attr_t *changed = NULL;
  GList *item;
  GList *changed_list;

  LG_log(lg_ctx, LG_FUNC,">up_add_dates: entered\n");

  changed_list = rpsl_object_get_attr(preproc_obj, "changed");

  /* get the current date in YYYYMMDD format (for example 20011010) */
  current_date = UP_get_current_date();
  LG_log(lg_ctx, LG_DEBUG,"up_add_dates: current date %s", current_date);
  
  for ( item = changed_list; item != NULL ; item = g_list_next(item) )
  {
    /* if this attribute does not have a date in it, add it. 
       Also add a warning message about this */
    /* although only one changed attr is allowed with no date, add a date
       to multiple attrs with no date so that later checks will not fail */
	value = rpsl_attr_get_clean_value((rpsl_attr_t *)(item->data));

    if ( !up_changed_has_date(value) )
	{
      LG_log(lg_ctx, LG_DEBUG,"up_add_dates: no date found [%s]", value);
      count_no_date++;

	  /* create a copy of this changed attribute and add the date to the value */
	  changed = rpsl_attr_copy((rpsl_attr_t *)(item->data));
      temp = (char *)malloc(strlen(value) + strlen(current_date) + 2 );
      sprintf(temp, "%s %s", value, current_date);
      rpsl_attr_replace_value(changed, temp);
      free(temp);

	  /* delete the original attribute from the object */
	  pos = rpsl_attr_get_ofs(changed);
	  rpsl_object_remove_attr(preproc_obj, pos, NULL);
	  /* add the new changed attribute in the same position */
	  rpsl_object_add_attr(preproc_obj, changed, pos, NULL);

      /* report a warning message */
      LG_log(lg_ctx, LG_DEBUG,"up_add_dates: current date added");
      RT_changed_date_missing(rt_ctx, current_date, value);
    }
	free(value);
  }

  rpsl_attr_delete_list(changed_list);
  
  if (count_no_date > 1)
  { 
    LG_log(lg_ctx, LG_DEBUG,"up_add_dates: More than one 'changed' attributes without date");
    RT_multiple_changed_date_missing(rt_ctx);
    retval = UP_FAIL;
  }

  LG_log(lg_ctx, LG_FUNC,"<up_add_dates: exiting with value [%s]\n", UP_ret2str(retval));
  return retval;
}



/* Checks the order of dates in the given list. 
   The dates in the 'changed' attributes should be in accending order
   Receives RT context
            LG context
            list of date strings
   Returns  UP_OK if they are in order 
            UP_FAIL if not
*/

int up_check_date_order(RT_context_t *rt_ctx, LG_context_t *lg_ctx, GList *list)
{
  int retval = UP_OK;
  char *previous;
  char *value;
  GList *item;

  LG_log(lg_ctx, LG_FUNC,">up_check_date_order: entered\n");

  /* an empty list is UP_OK */
  /* but should never happen */
  if ( list != NULL )
  {
    /* initialize the 'previous' date */
    previous = strdup("00000000");

    for ( item = list; item != NULL ; item = g_list_next(item))
    {
      assert((item->data) != NULL);  /* should never happen */
      /* check if the new date is smaller (earlier) than the previous */
	  value = (char *)(item->data);
      if ( strcmp(value, previous) < 0 )
	  {
        /* date out of order */
        LG_log(lg_ctx, LG_DEBUG,"up_check_date_order: date [%s] is earlier than [%s]",
                            value, previous );
        RT_changed_date_order(rt_ctx, value, previous );
        retval = UP_FAIL;
        break;
      }
      free(previous);
      previous = strdup(value);
    }

    free(previous);
  }
  else
    LG_log(lg_ctx, LG_DEBUG,"up_check_date_order: no dates in list");

  LG_log(lg_ctx, LG_FUNC,"<up_check_date_order: exiting with value [%s}\n", UP_ret2str(retval));
  return retval; 
}



/* checks the syntax of the specified date. 
   The argument is checked if it is in YYYYMMDD format
   Receives LG context
            a date as a string
   Returns  UP_DATE_OK if no error found
            UP_DATE_**** value depending on the type of error.
*/

int up_check_date(LG_context_t *lg_ctx, const char *date)
{
  int date_int; /* integer representation of the date */
  char *current_date;
  int year, month, day; /* integers for the components of the date */

  LG_log(lg_ctx, LG_FUNC,">up_check_date: entered with date [%s]\n", date);

  errno = 0;
  date_int = atoi(date);
      
  if (errno != 0)
  { /* there was an error in the conversion, syntax error */
    LG_log(lg_ctx, LG_FUNC,"<up_check_date: exiting with syntax error\n");
    return UP_DATE_SYNERR;
  }
    
  /* wrong format */
  if (date_int <= 10000000 )
  { /* the date is not in YYYYMMDD format */
    LG_log(lg_ctx, LG_FUNC,"<up_check_date: exiting with wrong format\n");
    return UP_DATE_WRONGFORMAT;
  }

  /* check if it is too small */  
  if (date_int <= 19840000 )
  { /* the date is older than the DB itself! */
    LG_log(lg_ctx, LG_FUNC,"<up_check_date: exiting with date too small\n");
    return UP_DATE_TOOSMALL;
  }

  /* check if it is too big */
  if (date_int >= 21000000 )
  {/* too big: syntax error */
    LG_log(lg_ctx, LG_FUNC,"<up_check_date: exiting with date too big\n");
    return UP_DATE_SYNERR;
  }

  /* and now check year, month and day components */
  year = date_int / 10000;
  month = (date_int - (year * 10000) ) / 100;
  day = (date_int % 100);
  
  /* check year */
  if (year < 1984 )
  {
    LG_log(lg_ctx, LG_FUNC,"<up_check_date: exiting with date too small\n");
    return UP_DATE_TOOSMALL;
  }

  /* check month */
  if (month < 1 || month > 12)
  {
    LG_log(lg_ctx, LG_FUNC,"<up_check_date: exiting with invalid month\n");
    return UP_DATE_INVMONTH;
  }

  /* check day */
  if (day < 1 || day > 31)
  {
    LG_log(lg_ctx, LG_FUNC,"<up_check_date: exiting with invalid day\n");
    return UP_DATE_INVDAY;
  }

  switch ( month )
  {
    case 1: case 3: case 5: case 7:
    case 8: case 10: case 12:
         /* already checked max day above */
         break;
    case 2: 
         if ( (year % 4 == 0 && (year % 100 != 0 || year % 400 == 0)) && (day > 29 ))
		 { /* leap year */
           LG_log(lg_ctx, LG_FUNC,"<up_check_date: exiting with invalid day\n");
           return UP_DATE_INVDAY;
         }
		 else if( (!(year % 4 == 0 && (year % 100 != 0 || year % 400 == 0))) && (day > 28) )
		 { /* non-leap year */
           LG_log(lg_ctx, LG_FUNC,"<up_check_date: exiting with invalid day\n");
           return UP_DATE_INVDAY;
         };
         break;
    case 4: case 6: case 9: case 11:
         if (day > 30)
		 {
           LG_log(lg_ctx, LG_FUNC,"<up_check_date: exiting with invalid day\n");
           return UP_DATE_INVDAY;
         };
         break;
    default: 
         LG_log(lg_ctx, LG_FUNC,"<up_check_date: exiting with invalid month\n");
         return UP_DATE_INVMONTH;
  }

  /* check if the date is in the future or not */
  current_date = UP_get_current_date();
  if (strcmp(current_date, date) < 0 )
  { 
    /* date is in the future */
    free(current_date);
    LG_log(lg_ctx, LG_FUNC,"<up_check_date: exiting with future date\n");
    return UP_DATE_FUTURE;
  }
  free(current_date);
    
  LG_log(lg_ctx, LG_FUNC,"<up_check_date: exiting with date ok\n");
  return UP_DATE_OK;
}



/* Checks the syntax of the dates in the list
   Receives RT context
            LG context
            list of date strings
   Returns  UP_OK if all correct
            UP_FAIL if any has incorrect syntax
*/

int up_check_dates_syntax(RT_context_t *rt_ctx, LG_context_t *lg_ctx, GList * list)
{
  int retval = UP_OK;
  int res;
  char *err_mess = NULL;
  GList *item;

  LG_log(lg_ctx, LG_FUNC,">up_check_dates_syntax: entered\n");

  /* an empty list is UP_OK */
  /* but should never happen */
  if (list != NULL)
  {
    /* loop through the members of the list, check each of them */
    for ( item = list; item != NULL ; item = g_list_next(item))
    {
      assert((item->data) != NULL);  /* should never happen */

      /* check the date */
      res = up_check_date(lg_ctx, (char *)(item->data));
      switch (res)
	  {
        case UP_DATE_OK: 
               break;

        case UP_DATE_FUTURE:
        case UP_DATE_TOOSMALL:
        case UP_DATE_INVDAY:
        case UP_DATE_INVMONTH:
        case UP_DATE_WRONGFORMAT:

               err_mess = malloc(strlen(up_date_errmsgs[res]) + strlen((char *)(item->data)) + 5);
               err_mess = strcpy(err_mess, up_date_errmsgs[res]);
               err_mess = strcat(err_mess, " '");
               err_mess = strcat(err_mess, (char *)(item->data));
               err_mess = strcat(err_mess, "'");

               LG_log(lg_ctx, LG_ERROR,"up_check_dates_syntax: %s", err_mess);
               RT_changed_date_syntax(rt_ctx, err_mess);
               retval = UP_FAIL;
               free(err_mess);
               break;


        case UP_DATE_SYNERR: /* syntax error in the date */
        default:

               err_mess = malloc(strlen(up_date_errmsgs[UP_DATE_SYNERR]) + strlen((char *)(item->data)) + 5);
               err_mess = strcpy(err_mess, up_date_errmsgs[UP_DATE_SYNERR]);
               err_mess = strcat(err_mess, " '");
               err_mess = strcat(err_mess, (char *)(item->data));
               err_mess = strcat(err_mess, "'");

               LG_log(lg_ctx, LG_ERROR,"up_check_dates_syntax: %s", err_mess);
               RT_changed_date_syntax(rt_ctx, err_mess);
               retval = UP_FAIL;
               free(err_mess);
               break;
      }
    }
  }
  else
    LG_log(lg_ctx, LG_DEBUG,"up_check_dates_syntax: no dates in list");
  
  LG_log(lg_ctx, LG_FUNC,"<up_check_dates_syntax: exiting with value [%s]\n", UP_ret2str(retval));
  return retval;
}



/* adds dates to any changed attributes missing one
   checks the order of dates in the 'changed' attributes 
   checks the syntax of the dates
   Receives RT context
            LG context
            parsed object
   Returns  UP_FAIL if any errors found
            UP_OK if all ok
*/

int UP_check_changed_attr(RT_context_t *rt_ctx, LG_context_t *lg_ctx,
                             rpsl_object_t *preproc_obj)
{
  int retval = UP_OK;
  GList *date_list;
  GList *date_item = NULL;
  GList *changed_list;

  LG_log(lg_ctx, LG_FUNC,">UP_check_changed_attr: entered\n");

  /* perform all checks to report any errors.
     all functions called return 0 (UP_OK) for success, non zero (UP_FAIL) for error
     all function calls must do an |= on return value */

  /* First, add dates to any "changed" attributes without one */
  retval |= up_add_dates(rt_ctx, lg_ctx, preproc_obj);

  /* get the list of dates from the changed attributes and check their order */
  /* we may have added a date to one of the changed attrs so get the list again */
  changed_list = rpsl_object_get_attr(preproc_obj, "changed");
  date_list = up_get_dates(lg_ctx, changed_list);
  rpsl_attr_delete_list(changed_list);

  /* and check the order */ 
  retval |= up_check_date_order(rt_ctx, lg_ctx, date_list);

  /* check the syntax of dates */
  retval |= up_check_dates_syntax(rt_ctx, lg_ctx, date_list);
  
  /* free the date_list */
  for ( date_item = date_list; date_item; date_item = g_list_next(date_item) )
  {
    if ( date_item->data )
      free (date_item->data);
  }
  g_list_free(date_list);

  LG_log(lg_ctx, LG_FUNC,"<UP_check_changed_attr: exiting with value [%s]\n", UP_ret2str(retval));
  return retval;
}


/* gets the certif attributes from a key-cert object and packs the data into
   a string with newlines appended to each line.
   use rpsl_attr_get_clean_lines to make sure blank certif values are transposed into
   blank lines in the output string.
   Receives LG context
            parsed key-cert object
   Returns  key certif data string
*/

char *up_get_certif_data(LG_context_t *lg_ctx, rpsl_object_t *preproc_obj)
{
  char *value;
  char *key;
  GList *certiflist, *item;
  GString *gkey;

  LG_log(lg_ctx, LG_FUNC,">up_get_certif_data: entered\n");

  /* get the certif data first */
  certiflist = rpsl_object_get_attr(preproc_obj, "certif");

  gkey = g_string_sized_new(1024);
  for( item = certiflist; item != NULL ; item = g_list_next(item) )
  {
    value = rpsl_attr_get_clean_lines( (rpsl_attr_t *)(item->data) );
	if (value == NULL)
	{ /* if this was an empty attribute, just print an empty line */
      g_string_sprintfa(gkey, "\n"); 
    }
	else
	{
	  g_string_sprintfa(gkey, "%s\n", value);
	  free(value);
	}
  }

  rpsl_attr_delete_list(certiflist);
  key = gkey->str;
  g_string_free(gkey, FALSE);
  LG_log(lg_ctx, LG_FUNC,"<up_get_certif_data: exiting with key [\n%s]\n", key);
  return key;
}


/* gets data from a key-cert object
   Receives RT context
            LG context
            key_data pointer
            pointer to internal key data structure
            parsed object
   Returns  KM_OK if successful
            anything else is a failure.
*/

int UP_get_key_data(RT_context_t *rt_ctx, LG_context_t *lg_ctx,
                         key_info_t *key_info, KM_key_return_t **key_data,
                         rpsl_object_t *preproc_obj)
{
  int key_status;
  char *key = NULL;

  LG_log(lg_ctx, LG_FUNC,">UP_get_key_data: entered\n");

  /* get the key data and extract some info from it */
  key = up_get_certif_data(lg_ctx, preproc_obj);
  key_info->key = key;
  key_status = KM_key_get_info(KM_PGP, key, key_data);
  /* key_data is a KM internal structure and should only be accessed 
     with KM supplied accessing functions */
  /* report any errors */
  RT_report_key_info(rt_ctx, *key_data);

  if ( key_status == KM_OK )
  {
    key_info->key_id = strdup(KM_key_return_get_key_id(*key_data));
    LG_log(lg_ctx, LG_DEBUG,"UP_get_key_data: key_id [%s]", key_info->key_id);
  }
  else
    LG_log(lg_ctx, LG_DEBUG,"UP_get_key_data: no key_id set");

  LG_log(lg_ctx, LG_FUNC,"<UP_get_key_data: exiting with KM return status %s", KM_return_string(key_status));
  return key_status;
}


/* Processes a generated attribute for a key-cert object.
   The object may already contain this attribute. 
   If present and it does not match the generated value
   a warning message is included in the ack and the
   supplied value is replaced by the new generated value.
   Receives LG context
            parsed object
            attribute type (method, owner, fingerpr)
            generated attribute
            generated attribute value
            pointer to warnings list
            preset position of generated attribute in object
   Returns  none
*/

void up_process_attr(LG_context_t *lg_ctx, rpsl_object_t *preproc_obj,
                       char *attr_type, rpsl_attr_t *gen_attr, const char *gen_value,
                       GList **warn_list, int preset_pos)
{
  char *sup_value;
  int pos;
  char *gen;
  GList *attr_list = NULL;

  LG_log(lg_ctx, LG_FUNC,">up_process_attr: entered with generated %s attribute [%s]\n",
                                attr_type, gen_value);
  gen = strdup(gen_value);
  g_strstrip(gen);

  /* check if supplied object already has this attribute */
  if ( (attr_list = rpsl_object_get_attr(preproc_obj, attr_type)) )
  {
    /* list with one item only, get the supplied value */
    /* don't use rpsl_attr_get_clean_value here as it reducez the two
       spaces in the middle of the fingerpr: atr to one space */
    sup_value = (char *)rpsl_attr_get_value((rpsl_attr_t *)(attr_list->data));
    g_strstrip(sup_value);
    LG_log(lg_ctx, LG_DEBUG, "up_process_attr: supplied %s attribute [%s]", attr_type, sup_value );

    /* compare supplied value with generated one */
    if ( strcasecmp(sup_value, gen) )
    {
      /* values are different, add warning */
      *warn_list = g_list_append (*warn_list, attr_type); 
      LG_log(lg_ctx, LG_DEBUG, "up_process_attr: WARNING %s attribute different", attr_type);

      /* replace supplied attribute with generated one */
      pos = rpsl_attr_get_ofs((rpsl_attr_t *)(attr_list->data));

      /* remove the supplied attribute */
      rpsl_object_remove_attr(preproc_obj, pos, NULL);

      /* insert new generated attribute in same position */
      rpsl_object_add_attr(preproc_obj, gen_attr, pos, NULL);
      LG_log(lg_ctx, LG_DEBUG, 
         "up_process_attr: supplied %s attribute replaced with generated %s attribute",
                            attr_type, attr_type );
    }
    else 
    {
      /* values are the same, no need to change */
      LG_log(lg_ctx, LG_DEBUG, "up_process_attr: supplied %s attribute used", attr_type);
    }

    free(sup_value);
    rpsl_attr_delete_list(attr_list);
  }
  else
  {
    /* attribute not supplied, so add generated attribute */
    rpsl_object_add_attr(preproc_obj, gen_attr, preset_pos, NULL);
    LG_log(lg_ctx, LG_DEBUG, "up_process_attr: generated %s attribute used", attr_type);
  }

  free(gen);
  LG_log(lg_ctx, LG_FUNC,"<up_process_attr: exiting \n");
}


/* Adds the generated attrs to a key-cert object.
   The object may already contain these attributes. 
   If present and they do not match the generated values
   a warning message is included in the ack and the
   supplied values are replaced by the new generated values.
   Receives RT context
            LG context
            key_data pointer
            parsed object
   Returns  UP_OK if successful
            UP_FAIL otherwise
*/

int UP_generate_keycert_attrs(RT_context_t *rt_ctx, LG_context_t *lg_ctx, 
                           key_info_t *key_info, rpsl_object_t *preproc_obj)
{
  int retval = UP_OK;
  const char *type;
  char *attr_str;
  const char *method = "PGP";
  const char *keyowner = NULL;
  const char *fingerprint = NULL;
  int key_status;
  rpsl_attr_t *method_attr;
  rpsl_attr_t *owner_attr;
  rpsl_attr_t *fingerpr_attr;
  KM_key_return_t *key_data = NULL;
  GList *warn_list = NULL;

  LG_log(lg_ctx, LG_FUNC,">UP_add_keycert_attrs: entered\n");
  
  type = rpsl_object_get_class(preproc_obj);

  /* get the key data and extract some info from it */
  key_status = UP_get_key_data(rt_ctx, lg_ctx, key_info, &key_data, preproc_obj);

  if ( key_status == KM_OK )
  {
    /* method: attribute */
    /* process the attribute */
    if ( (method_attr = rpsl_attr_init("method: PGP", type)) )
      up_process_attr(lg_ctx, preproc_obj, "method", method_attr, method, &warn_list, 1);

    /* owner: attribute, generate value */
    keyowner = KM_key_return_get_parameter(key_data, "key_owner");
    assert(keyowner != NULL);  /* should never happen */
    attr_str = (char *)malloc( strlen("owner: ") + strlen(keyowner) +1 );
    strcpy(attr_str, "owner: ");
    strcat(attr_str, keyowner);

    /* process the attribute */
    if ( (owner_attr = rpsl_attr_init(attr_str, type)) )
      up_process_attr(lg_ctx, preproc_obj, "owner", owner_attr, keyowner, &warn_list, 2);
    free(attr_str);

    /* fingerprint: attribute, generate value */
    fingerprint = KM_key_return_get_parameter(key_data, "finger_print");
    assert(fingerprint != NULL);  /* should never happen */
    attr_str = (char *)malloc( strlen("fingerpr: ") + strlen(fingerprint) +1 );
    strcpy(attr_str, "fingerpr: ");
    strcat(attr_str, fingerprint);

    /* process the attribute */
    if ( (fingerpr_attr = rpsl_attr_init(attr_str, type)) )
      up_process_attr(lg_ctx, preproc_obj, "fingerpr", fingerpr_attr, fingerprint, &warn_list, 3);
    free(attr_str);
    
    /* report warnings, if any */
    if ( warn_list )
    {
      RT_kc_gen_diff(rt_ctx, warn_list);
      g_list_free(warn_list);
    }
  }
  else
    retval = UP_FAIL;
  
  KM_key_return_free(key_data);
  LG_log(lg_ctx, LG_FUNC,"<UP_add_keycert_attrs: exiting with value [%s]\n", UP_ret2str(retval));
  return retval;
}
