/***************************************
  $Revision: 1.24 $

  Functions to process data stream( file, network socket, etc.)

  Status: NOT REVUED, NOT TESTED

 Author(s):       Chris Ottrey, Andrei Robachevsky

  ******************/ /******************
  Modification History:
        andrei (17/01/2000) Created.
  ******************/ /******************
  Copyright (c) 2000                              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 <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include "constants.h"
#include "query_command.h"
#include "ud.h"
#include "ud_int.h"

typedef enum _Line_Type_t {
 LINE_ATTRIBUTE,
 LINE_COMMENT,
 LINE_EMPTY,
 LINE_EOF,
 LINE_ADD,
 LINE_UPD,
 LINE_DEL,
 LINE_OVERRIDE_ADD,
 LINE_OVERRIDE_UPD,
 LINE_OVERRIDE_DEL,
 LINE_PLUS
} Line_Type_t;

/* Maximum number of objects(serials) we can consume at a time */
#define SBUNCH 1000

static int report_transaction(Transaction_t *tr, Log_t *log, char *obj_name, char *reason);
static int process_nrtm(UD_stream_t *ud_stream, Transaction_t *tr, char *object_name, int operation);
static int process_updates(UD_stream_t *ud_stream, Transaction_t *tr, char *object_name, int operation);
static int process_transaction(UD_stream_t *ud_stream,Object_t *obj,char *object_name,nic_handle_t *nh, int operation);
                                                                                                



/* temporary files to download serials */              
char tmpfile1[STR_S], tmpfile2[STR_S];

/************************************************************
* FILE *get_NRTM_stream()                                   *
*                                                           *
* Gets the NRTM stream                                      *
*                                                           *
* First tries to request the serials from the NRTM server   *
* If the name of the server appears to be not a network name*
* it tries to open the file with this name                  *
*                                                           *
* After downloading (opening) the serials into the file     *
* it runs ripe2rpsl script to translate objects into RPSL   *
* They are stored in a temporary file                       *
*                                                           *
* nrtm - pointer to _nrtm structure                         *
* upto_last - if==1 then requests to download serials using *
* LAST keyword                                              *
*                                                           *
* Returns:                                                  *
* A pointer to the temp. file where serials in RPSL format  *
* are stored                                                *
* NULL - error                                              *
*                                                           *
************************************************************/
FILE *get_NRTM_stream(struct _nrtm *nrtm, int upto_last)
{
int sockfd;
struct hostent *hptr;
struct sockaddr_in serv_addr;
struct in_addr *paddr;
char line_buff[STR_XXL];
FILE *fp;
int fd;
int fdtmp;
int nread, nwrite;
struct hostent result;
int error;
int network;


 fprintf(stderr, "Making connection to NRTM server ...\n");
 if ((sockfd=socket(AF_INET, SOCK_STREAM, 0))==-1){
   perror("socket");
   return(NULL);
 }  
/* hptr=gethostbyname(nrtm->server);*/
 hptr=gethostbyname_r(nrtm->server,  &result, line_buff, sizeof(line_buff), &error);
 if (hptr) { /* this is a network stream*/
   paddr=(struct in_addr *)hptr->h_addr;
   bzero(&serv_addr, sizeof(serv_addr));
   serv_addr.sin_family=AF_INET;
   serv_addr.sin_port=nrtm->port;
   memcpy(&serv_addr.sin_addr, paddr, sizeof(struct in_addr));
   fprintf(stderr,"Trying %s port %d\n", inet_ntoa(serv_addr.sin_addr), nrtm->port);
   if(connect(sockfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr))==-1) { 
     perror("connect");
     return(NULL);
   }  
   fprintf(stderr, "Sending Invitation\n");
   
   /* Request all available serials (upto LAST), or SBUNCH of them */
   if(upto_last)
      sprintf(line_buff, "-g RIPE:%d:%ld-LAST\n", nrtm->version, nrtm->current_serial+1);
   else
      sprintf(line_buff, "-g RIPE:%d:%ld-%ld\n", nrtm->version, nrtm->current_serial+1, nrtm->current_serial+SBUNCH);   
   nwrite=SK_write(sockfd, line_buff, strlen(line_buff) );
   if(nwrite != strlen(line_buff)) { perror("write"); return(NULL); }
   fd=sockfd;
   network=1;
   fprintf(stderr, "Returning stream pointer\n");
 }
 else { /* this is a file stream*/
   network=0;
   close(sockfd);
   fprintf(stderr, "Trying file ...\n");
   if((fd=open(nrtm->server, O_RDONLY, 0666))==-1) {
     perror("open");
     return(NULL);
   }  
 }  

/* make temporary files */
 sprintf(tmpfile1, "temp.XXXX");

 if((fdtmp=mkstemp(tmpfile1))==-1) {
   perror("mkstemp");
   close(fd);
   return(NULL);
 }  
 
 if(network) {
  while ((nread=SK_read(fd, line_buff, sizeof(line_buff)))) {
    if(nread==-1) return(NULL);
    nwrite=write(fdtmp, line_buff,nread);
    if(nread != nwrite) return(NULL);
  } 
 } else {
  while ((nread=read(fd, line_buff, sizeof(line_buff)))) {
    if(nread==-1) return(NULL);
    nwrite=write(fdtmp, line_buff,nread);
    if(nread != nwrite) return(NULL);
  }
 }
 close(fd); close(fdtmp);
 
 sprintf(tmpfile2, "%s.2", tmpfile1);
 
 sprintf(line_buff, "cat %s | ./ripe2rpsl > %s", tmpfile1, tmpfile2);
 if (system(line_buff)!=0) return(NULL);
 if((fp=fopen(tmpfile2, "r+"))==NULL)return(NULL);
 
 unlink(tmpfile1);
 
 return(fp);
 
}


/* XXX this is from qc module */
char *strsep(char **stringp, const char *delim);
/*
{   
   return strtok_r( *stringp, delim, stringp);
}   

*/

/* Delimiters that separate list members, both RPS(,) and legacy( ) */
#define ATTR_DELIMITERS " ,"

static GSList *split_attribute(GSList *attr_list, A_Type_t attr_type, char *attr_value){
char *token;
char *split;
char *value, *n;
Attribute_t *attr_split;
GSList *the_list = attr_list;

  /* check for end-of-line comments */
  n = index(attr_value, '#');
      /* if there is no comment check for trailing \n */
  if(n == NULL) n = index(attr_value, '\n');
  /* now copy the clean value into the attribute */
  if(n == NULL) value = g_strdup(attr_value); 
  else  value = g_strndup(attr_value, (n - attr_value));
     
  token=value;
  while((split=strsep(&token, ATTR_DELIMITERS))){
     attr_split = attribute_new1(attr_type, split);
     if (attr_split) the_list = g_slist_append(the_list, attr_split);
  }
  free(value);
 return(the_list);
}

/************************************************************
*                                                           *
* The function to reorder attributes in the List            *
* nic-hdl and mnt-by should come first                      *
*                                                           *
* should return 0 if they are equal, a negative value if    * 
* the first element comes before the second, or a positive  *
* value if the first element comes after the second         *
*                                                           *
************************************************************/
static gint reorder_attributes(const void *element1, const void *element2)
{
Attribute_t *attr1 = (Attribute_t *)element1;
Attribute_t *attr2 = (Attribute_t *)element2;
gint order = -1;
  
  if(attr2->type == A_MB) order= 1;
  if(attr1->type == A_MB) order= -1;
  if(attr2->type == A_NH) order= 1;
  if(attr1->type == A_NH) order= -1;

  return(order);

}

/* XXX */
static void each_attribute_print(void *element_data, void *tr_ptr)
{

Attribute_t *attr = (Attribute_t *)element_data;

 fprintf(stderr, "[%d|%s]\n", attr->type, attr->value);

}

/* XXX */
static void print_object(Object_t *obj)
{
  g_slist_foreach(obj->attributes, each_attribute_print, NULL);	
  fprintf(stderr, ">>>>>\n%s\n", obj->object->str);
}


/******************************************************************
* GString *escape_apostrophes()                                   *
* Escapes apostrophes in the text so they do not confuse printf   *
* functions and don't corrupt SQL queries                         *
*                                                                 *
* *****************************************************************/
GString *escape_apostrophes(GString *text) {
  int i;
  for (i=0; i < text->len; i++) {
    if ((text->str[i] == '\'') || (text->str[i] == '\\')) {
      text = g_string_insert_c(text, i, '\\');
      i++;
    }
  }
 return(text); 
} /* escape_apostrophes() */


/******************************************************************
* Line_Type_t line_type(e)                                        *
* Determines the line type analysing the first letters            *
*                                                                 *
* ****************************************************************/
static Line_Type_t line_type(const char *line) {
  Line_Type_t result = -1;

  if (strncmp(line, "# EOF", 4) == 0) {
    result = LINE_EOF;
  }
  else if (strncmp(line, "#", 1) == 0) {
    result = LINE_COMMENT;
  }
  else if (strcmp(line, "\n") == 0) {
    result = LINE_EMPTY;
  }
  else if (strcmp(line, "ADD\n") == 0) {
    result = LINE_ADD;
  }
  else if (strcmp(line, "UPD\n") == 0) {
    result = LINE_UPD;
  }
  else if (strcmp(line, "DEL\n") == 0) {
    result = LINE_DEL;
  }
  else if (strcmp(line, "ADD_OVERRIDE\n") == 0) {
    result = LINE_OVERRIDE_ADD;
  }
  else if (strcmp(line, "UPD_OVERRIDE\n") == 0) {
    result = LINE_OVERRIDE_UPD;
  }
  else if (strcmp(line, "DEL_OVERRIDE\n") == 0) {
    result = LINE_OVERRIDE_DEL;
  }
  else if (strncmp(line, "+", 1) == 0) {
    result = LINE_PLUS;
  }  
  else {
    result = LINE_ATTRIBUTE;
  }

  return result;
} /* line_type() */


/******************************************************************
* report_transaction()                                            *
*                                                                 * 
* Prints error report to the log                                  *
*                                                                 *
* reason - additional message that will be included               *
*                                                                 *
* *****************************************************************/
static int report_transaction(Transaction_t *tr, Log_t *log, char *obj_name, char *reason)
{
int result=0;

 if(tr->succeeded==0) {
  result=tr->error;
  log->num_failed++;
  fprintf(stderr, "FAILED[%s][%s(%d)](%d/%d)\n ", obj_name, reason, result, log->num_failed, (log->num_failed)+(log->num_ok)); 
  fprintf(log->logfile, "*FAILED[%s][%s](%d/%d)\n ", obj_name, reason, log->num_failed, (log->num_failed)+(log->num_ok));
  if(result & ERROR_U_MEM) fprintf(log->logfile, "\t*Memory allocation error\n");
  if(result & ERROR_U_DBS) fprintf(log->logfile, "\t*Database (SQL) error\n");
  if(result & ERROR_U_OBJ) fprintf(log->logfile, "\t*Object (RF) error\n");
  if(result & ERROR_U_AUT) fprintf(log->logfile, "\t*Object authentication error\n");
  if(result & ERROR_U_BADOP) fprintf(log->logfile, "\t*Bad operation\n");
  if(result & ERROR_U_COP) fprintf(log->logfile, "\t*Conflicting operation\n");
  if(result & ERROR_U_NSUP) fprintf(log->logfile, "\t*Object of this type is not supported\n");
  if(result & ERROR_U_BUG) fprintf(log->logfile, "\t*Software bug - report to <ripe-dbm@ripe.net>\n");
  fprintf(log->logfile, "%s", (tr->error_script)->str);
  result=(-1)*result;                                                
  fflush(log->logfile);
 }
 else {
  result=1;
  log->num_ok++;
  fprintf(stderr, "OK(%d/%d)\n", log->num_ok, (log->num_failed)+(log->num_ok));
 }
                                                                                                                                                
 return(result);
}/* report_transaction() */



/************************************************************
* process_nrtm()                                            *
*                                                           *
* Process object in NRTM client mode                        *
*                                                           *
* nrtm - pointer to _nrtm structure                         *
* log - pointer to Log_t structure                          *
* object_name - name of the object                          * 
* operation - operation code (OP_ADD/OP_DEL)                *
*                                                           *
* Returns:                                                  *
* 1  - okay                                                 *
* <0 - error                                                *
*                                                           *
************************************************************/

static int process_nrtm(UD_stream_t *ud_stream, Transaction_t *tr, char *object_name, int operation)
{
int result=0;
int dummy=0;
struct _nrtm *nrtm = ud_stream->nrtm;
Log_t *log_ptr= &(ud_stream->log);
  
  switch (operation) {
  
  case OP_ADD:
  /* XXX This hack is made to allow NRTM updates for some inconsistent objects */
  /* One of the examples is reference by name which looks like nic-handle */
  /* For this purpose we allow dummy creation when updating an object */
  tr->dummy=1;
    if(nrtm->tr){ /*saved?*/
      if(tr->object_id==0) { 
	/* object does not exist in the DB */      
        object_process(nrtm->tr); /* delete the previous(saved) object*/
        result=report_transaction(nrtm->tr, log_ptr, nrtm->object_name, "NRTM:DEL:While deleting previous(saved)");
        if(result==1)create_serial(nrtm->tr);
        object_free(nrtm->tr->object);
        transaction_free(nrtm->tr); nrtm->tr=NULL;
        /* Create an object and update NHR */
        tr->action=(TA_CREATE | TA_UPD_NHR);
/*      fprintf(stderr,"CREATE next\n"); */
        object_process(tr); /* create a new one*/
        result=report_transaction(tr, log_ptr, object_name, "NRTM:ADD:While creating new");
        if(result==1)create_serial(tr);
      }
      else { 
      /* object already exists in the DB - update or dummy replacement*/
        if(tr->object_id==nrtm->tr->object_id) {/*compare the two, may be we may collapse operations*/
          object_free(nrtm->tr->object);
          transaction_free(nrtm->tr); nrtm->tr=NULL;
/*        fprintf(stderr,"DEL-ADD ->> UPDATE\n");*/
          tr->action=TA_UPDATE;
          object_process(tr);
          report_transaction(tr, log_ptr, object_name,"NRTM:upd");
          result=report_transaction(tr, log_ptr, object_name,"NRTM:upd");
          if(result==1)create_serial(tr);
        }
        else { /* this should be a dummy object in the database(that we are going to replace with the real one */
        /* or an interleaved operation*/
/*        fprintf(stderr,"DEL previous\n");*/
          object_process(nrtm->tr); /* delete the previous(saved) object*/
          result=report_transaction(nrtm->tr, log_ptr, nrtm->object_name, "NRTM:DEL:While deleting previous(saved)");
          if(result==1)create_serial(nrtm->tr);
          object_free(nrtm->tr->object);
          transaction_free(nrtm->tr); nrtm->tr=NULL;
          tr->action=TA_UPDATE;
          dummy=isdummy(tr);
          /* If we are replacing dummy with a real object update NHR */
          if(dummy==1) tr->action |= TA_UPD_NHR;
/*        fprintf(stderr,"UPDATE next(dummy)\n"); */
          object_process(tr); /* create a new one*/
          result=report_transaction(tr, log_ptr, object_name, "NRTM:ADD:While creating new");
          /* check if it was dummy */
          if (dummy==1)
            tr->action=TA_CREATE; /* we don't want to generate DEL serial for dummy replacement*/
          if(result==1)create_serial(tr);
        }
      }
    }
    else { /* brand new object*/
      if(tr->object_id==0) {
/*      fprintf(stderr,"CREATE new\n");*/
        /* Create an object and update NHR */
        tr->action=(TA_CREATE | TA_UPD_NHR);
        object_process(tr);
        result=report_transaction(tr, log_ptr, object_name,"NRTM:ADD:While creating new");
        if(result==1)create_serial(tr);
      }
      else { /* this may happen because of dummies*/
/*      fprintf(stderr,"CREATE new\n");*/
        tr->action=TA_UPDATE;
        dummy=isdummy(tr);
 /* If we are replacing dummy with a real object update NHR */
 if(dummy==1) tr->action |= TA_UPD_NHR;
        object_process(tr);
        result=report_transaction(tr, log_ptr, object_name,"NRTM:ADD:While creating new");
   /* check if it was dummy */
        if (dummy==1)
           tr->action=TA_CREATE; /* we don't want to generate DEL serial for dummy replacement*/
        if(result==1)create_serial(tr);
      } 
    }
    break;
    
  case OP_DEL:
    if(nrtm->tr){ /*saved?*/
/*    fprintf(stderr,"DEL previous\n");*/
      object_process(nrtm->tr); /* delete the previous(saved) object*/
      result=report_transaction(nrtm->tr, log_ptr, nrtm->object_name, "NRTM:DEL:While deleting previous(saved) object");
      if(result==1)create_serial(nrtm->tr);
      object_free(nrtm->tr->object);
      transaction_free(nrtm->tr); nrtm->tr=NULL;
    }
    if(tr->object_id>0){ /* save the object*/
      fprintf(stderr,"SAVED\n");
      tr->action=TA_DELETE;
      nrtm->tr=tr;
      strcpy(nrtm->object_name, object_name);
      return(1);
    }
    else { /* this is an error*/
      tr->succeeded=0; tr->error|=ERROR_U_COP;
      result=report_transaction(tr, log_ptr, object_name, "NRTM:OOS:Trying to DEL non-existing object");
/*    fprintf(stderr,"Lost sync. Skipping\n");*/
    }
    break;
  
  default:
    tr->succeeded=0; tr->error |=ERROR_U_BADOP;
    break;  
  }

 /* Free resources */  
  object_free(tr->object);
  transaction_free(tr);
  
  return(result);
} /* process_nrtm() */



/************************************************************
* process_updates()                                         *
*                                                           *
* Process object in update mode                             *
*                                                           *
* ud_stream - pointer to UD_stream structure                *
* object_name - name of the object                          *
* operation - operation code (OP_ADD/OP_DEL)                *
*                                                           *
* Note:                                                     *
* Frees tr and tr->obj on exit                              *
*                                                           *
* Returns:                                                  *
* 1  - okay                                                 *
* <0 - error                                                *
*                                                           * 
************************************************************/

static int process_updates(UD_stream_t *ud_stream, Transaction_t *tr, char *object_name, int operation)
{
int result=0;
Log_t *log_ptr= &(ud_stream->log);
int dummy=0;

    switch(operation) {
    /* Compare operations and report an error if they do not match */    
    case OP_ADD:
             if(tr->object_id!=0) { /* trying to create, but object exists */
        tr->succeeded=0; tr->error|=ERROR_U_COP;
      } else {
       /* Action: create the object and update NHR */
        tr->action=(TA_CREATE | TA_UPD_NHR);
        object_process(tr);
      }
      break;
    case OP_UPD:
      if(tr->object_id==0) { /* trying to update non-existing object*/
        tr->succeeded=0; tr->error|=ERROR_U_COP;
      } else {
        tr->action=TA_UPDATE;
        dummy=isdummy(tr);
        /* If we are replacing dummy with a real object update NHR */
        if(dummy==1) tr->action |= TA_UPD_NHR;
        object_process(tr);
      }
      break;

    case OP_DEL:        
      if(tr->object_id==0) { /* trying t delete non-existing object*/
        tr->succeeded=0; tr->error|=ERROR_U_COP;
      } else {
        tr->action=TA_DELETE;
        object_process(tr);
      }
      break;
                
    default:                
      /* bad operation for this mode if not standalone */
      if(tr->standalone) {
        if(tr->object_id==0)tr->action=TA_CREATE; else tr->action=TA_UPDATE;
        object_process(tr);
      }
      else {
        tr->succeeded=0; 
        tr->error|=ERROR_U_BADOP; 
      }
      break;
    }
   /* Make a report */
    result=report_transaction(tr, log_ptr, object_name, "RIPupd:");

   /* If not in standalone mode create serial and copy error transcript */ 
    if(!tr->standalone) {
      if(result==1)create_serial(tr);
      ud_stream->error_script=g_strdup((tr->error_script)->str);
    }  

   /* Free resources */   
    object_free(tr->object);
    transaction_free(tr);
    
    return(result);
        
} /* process_updates() */


/************************************************************
*                                                           *
* int process_transaction()                                 *
*                                                           *
* Processes the transaction                                 *
*                                                           *
* ud_stream - pointer to UD_stream_t structure              *
*                                                           *
* Returns:                                                  *
* 1 - no error                                              *
* <0- errors                                                *
*                                                           *
************************************************************/

/* It frees the obj */

static int process_transaction(UD_stream_t *ud_stream, 
                        Object_t *obj, 
                        char *object_name, 
                        nic_handle_t *nh,
                        int operation)
{
Transaction_t *tr = NULL;
Log_t *log_ptr = &(ud_stream->log);
Attribute_t *attr=NULL;
int result;

/* start new transaction now */ 
 tr = transaction_new(ud_stream->db_connection, obj->type);

/* Return with error if transaction cannot be created */ 
 if (tr == NULL) return(-1);
 
 tr->standalone=IS_STANDALONE(ud_stream->ud_mode);
 tr->dummy=IS_DUMMY_ALLOWED(ud_stream->ud_mode);
 tr->load_pass=ud_stream->load_pass;
 tr->object=obj;
 tr->nh=nh;
 
/* We perform no commit/rollback in the loader mode, so thread_id should be set to 0 */
 if(ud_stream->load_pass!=0) { tr->thread_ins=0; tr->thread_upd=0; }
    
/* For the first load pass we only create objects */ 
 if(ud_stream->load_pass==1) tr->object_id=0;
  else tr->object_id=get_object_id(tr);
 
/* Object cannot be retrieved */
 if(tr->object_id==-1) { /* DB error*/
    tr->succeeded=0;
    tr->error |= ERROR_U_DBS;
    report_transaction(tr, log_ptr, object_name, "Object cannot be retrieved");
    transaction_free(tr);
    object_free(obj);
    return(-1);
 }
/* save the name of person/role as we need it for referential */
/* integrity check when deleting the object against names. */
/* This is needed to support legacy references by name rather */
/* then by nic_hdl */
  if((tr->class_type==C_PN) || (tr->class_type==C_RO)){
     attr = attribute_new(object_name);
     if (attr==NULL) {
       tr->succeeded=0;
       tr->error |= ERROR_U_MEM;
       report_transaction(tr, log_ptr, object_name, "Cannot allocate memery");
       transaction_free(tr);
       object_free(obj);
       return(-1);
    }
    /* Save the value */
    tr->save=g_strdup(attr->value);
  }
                                               
/* Process transaction. tr and obj are freed inside the process_* functions */

 if(IS_UPDATE(ud_stream->ud_mode))
 /* We are in update mode */
    result=process_updates(ud_stream, tr, object_name, operation);
 else
 /* We are in NRTM mode */   
    result=process_nrtm(ud_stream, tr, object_name, operation);
 
 /* free attr if has been allocated */   
 if(attr) attribute_free(attr, NULL);
 
 return(result);

}          
          

/************************************************************
*                                                           *
* int UD_process_stream(UD_stream_t *ud_stream)             *
*                                                           *
* Processes the stream                                      *
*                                                           *
* ud_stream - pointer to UD_stream_t structure              *
*                                                           *
* Returns:                                                  *
* in update mode (!standalone)(1 object processed):         *
* 1 - no error                                              *
* <0- errors                                                *
*                                                           *
* in NRTM & standalone modes                                *
* total number of object processed                          *
*                                                           *
************************************************************/

int UD_process_stream(UD_stream_t *ud_stream)
{
  char line_buff[STR_XXL], object_name[STR_XXL];
  GString *g_line_buff; // needed to escape apostrophes
  GSList *class_attr_list = NULL;
  Attribute_t *class_attr;
  Attribute_t *attr;
  nic_handle_t *nh_ptr=NULL; /* To save  NIC handle structure */
  Object_t *obj = NULL;
  Transaction_t *tr = NULL;
  SQ_connection_t *sql_connection;
  int start_object;
  int a_type;
  char *a_value;
  char *ptr;
  /* here we will store the parsed nic-hdl in required format */
  char nic[MAX_NH_LENGTH];
  long num_skip;
  struct _nrtm *nrtm;
  Log_t *log_ptr= &(ud_stream->log);
  time_t stime, ftime;
  double obj_second1, obj_second10;
  int result;
  int operation=0;
  int interrupt=0;
  int do_update;
  int default_ud_mode = ud_stream->ud_mode;
  Line_Type_t linetype;
  
  nrtm=ud_stream->nrtm;
  start_object = 1;
  a_type=-1; 


  /* Allocate line bufer */
  if ((g_line_buff = g_string_sized_new(STR_XXL)) == NULL){ 
    fprintf(stderr, "E: cannot allocate gstring\n"); 
    return(-1); 
  }

  /* Check connection to the database */
  if(mysql_ping(ud_stream->db_connection)) {
   fprintf(stderr, "D: ERROR: no SQL connection\n");
   g_string_free(g_line_buff, TRUE);
   return(-1);
  }
   
  fprintf(stderr, "OK\n");
  sql_connection=ud_stream->db_connection;

/* This is useful for loading DB from huge disk file. */
/* We may start from <num_skip>th object */
  num_skip=ud_stream->num_skip;
  if(num_skip>0) fprintf(stderr, "skipping %lu records\n", num_skip);

  /* Start timer for statistics */
  stime=time(NULL);

 /* Main loop. Reading input stream line by line */
 /* Empty line signals to start processing an object, if we have it */ 
  while (fgets(line_buff, STR_XXL, ud_stream->stream) != NULL) {

    switch (linetype=line_type(line_buff)) {
      case LINE_PLUS:
      case LINE_ATTRIBUTE:
       if (start_object == 1) {
          /* This is for loading stuff */
/*    if(num_skip>0){ fprintf(stderr, "\r%10lu", num_skip); num_skip--; log_ptr->num_ok++; break; } */
          obj = object_new(line_buff);
       }
      if (obj) {
          g_string_sprintf(g_line_buff, "%s", line_buff);
   /* escape apostrophes in the input line */
          g_line_buff=escape_apostrophes(g_line_buff);
   
       if(start_object){
   /* If this is the first attribute(==object name/type) */   
          start_object=0;
          strncpy(object_name, g_line_buff->str, g_line_buff->len-1);
          *(object_name+g_line_buff->len-1)='\0';
          fprintf(stderr, "D: object: [%s] ", object_name);
	  /* Create an attribute - the first one determines a class */
	  class_attr_list=NULL; /* Initialize the list that will hold splitted class attribute */
	  class_attr = attribute_new(g_line_buff->str);
	  if (class_attr == NULL) die; /* Should not happen */
	  if((class_attr->type==A_PN)||(class_attr->type==A_RO)){
		/* split names */  
		class_attr_list = split_attribute(class_attr_list, class_attr->type, class_attr->value);  
		attribute_free(class_attr, NULL);
          } else {
		class_attr_list = g_slist_append(class_attr_list, class_attr); 
          }
	  /* do nothing more with this attribute - we will prepend it at the end */
       }
       else {
	
	attr = attribute_new(g_line_buff->str);
	
        if (attr) {
          a_type=attr->type;   
	  a_value=attr->value;
          if(a_type==A_NH) {
          /*  Parse the string into nh structure */
          /*  In case of an AUTO NIC handle check the ID in the database */
          /* Possible errors leave to core processing */
           if(NH_parse(attr->value, &nh_ptr) == 0) { 
/*         fprintf(stderr, "D:parsing NIC: [%s]\n", attr->value); */
           /* Check if we can allocate it */  
            if((result = NH_check(nh_ptr, sql_connection))>0){
            /* Convert nh to the database format */  
              NH_convert(nic, nh_ptr);
/*      fprintf(stderr, "D:NIC:[%s]\n", nic); */
              /* Replace NIC handle in the string which is copied to the text object */
              sprintf(line_buff, g_line_buff->str);
              ptr = strstr(line_buff, attr->value);
              /* compose new attribute string */
              strcpy(ptr, nic);
              g_string_sprintf(g_line_buff, line_buff);
              g_string_sprintfa(g_line_buff, "\n");
              /* Update the attribute */
              attribute_upd(attr, attr->type, nic); 
/*      fprintf(stderr, "D:attribute updated\n"); */
            }
           }
          } /* NHR stuff */
        }
	else {
	   if(linetype==LINE_PLUS)a_value=g_line_buff->str+1; /* skip '+' sign */ 
	   else a_value=g_line_buff->str;
	}
        if (a_type>=0) { /* This indicates that the input line contains the value of the attribute */
	 switch	(a_type) {
            /*these attributes may appear several on the line - split them*/   
            case A_PN: /* person */
            case A_RO: /* role */
            case A_MR: /* mbrs-by-ref */
            case A_MB: /* mnt-by */
            case A_MO: /* member-of */
            case A_SD: /* sub-dom */
            case A_RZ: /* rev-srv */
            case A_NS: /* nserver */
                obj->attributes = split_attribute(obj->attributes, a_type, a_value);
                if (attr) attribute_free(attr, NULL);
             attr=NULL;
             break; 
            default: break;  
         }
/*       g_string_sprintfa(obj->object, "%s", g_line_buff->str); */
         if(attr)obj->attributes = g_slist_append(obj->attributes, attr);  
        }
       } /* if not start_object (not the first/class attribute) */
        /* copy the line into object no matter whether it is an attribute or not (continualtion, etc.) */
        g_string_sprintfa(obj->object, "%s", g_line_buff->str);
      }/* if (obj) */
      break;

      case LINE_COMMENT:
      break;

      case LINE_EOF:
      break;
      
      case LINE_ADD:
      /* restore the default operation mode */
       operation=OP_ADD;
       ud_stream->ud_mode=default_ud_mode;
      break;
      
      case LINE_OVERRIDE_ADD:
      /* for override - switch the dummy bit on */
       operation=OP_ADD;
       ud_stream->ud_mode=default_ud_mode|B_DUMMY;
      break;
      
      case LINE_UPD:
      /* restore the default operation mode */
       operation=OP_UPD;
       ud_stream->ud_mode=default_ud_mode;
      break;  

      case LINE_OVERRIDE_UPD:
      /* for override - switch the dummy bit on */
       operation=OP_UPD;
       ud_stream->ud_mode=default_ud_mode|B_DUMMY;
      break;
      
      case LINE_DEL:
      /* restore the default operation mode */
       operation=OP_DEL;
       ud_stream->ud_mode=default_ud_mode;
      break; 

      case LINE_OVERRIDE_DEL:
      /* for override - switch the dummy bit on */
       operation=OP_DEL;
       ud_stream->ud_mode=default_ud_mode|B_DUMMY;
      break;
 
      case LINE_EMPTY:
       /* Indicate that we have complete object, so a new one will be collected later */ 
        start_object=1;
	a_type=-1;
       /* start processing the object */ 
        if (obj == NULL) break;  /* may be the previous lines were just garbage*/
	/* reorder some attributes */
        obj->attributes = g_slist_sort(obj->attributes, reorder_attributes);
	/* prepend the class attribute */
	obj->attributes = g_slist_concat(class_attr_list, obj->attributes);
	/* forget the location */
	class_attr=NULL;

	/* XXX */
	/*  print_object(obj); */

  /* start new transaction now */ 
        result=process_transaction(ud_stream, obj, object_name, nh_ptr, operation);
          
        /* process_transaction() frees tr and obj structures, */
        /* so make sure we'll not reference these objects in the future */
        obj=NULL; operation=OP_NOOP;
        nh_ptr=NULL;
        ud_stream->ud_mode=default_ud_mode;
          
        /* this is a good place for quick interrupt */
        do_update=CO_get_do_update();
        if (do_update) interrupt=0; else interrupt=1;
        /* we still need to exit in update server mode (only 1 object at a time */ 
        if (IS_UPDATE(ud_stream->ud_mode) && (!IS_STANDALONE(ud_stream->ud_mode))) interrupt=1;

      break;

      default:
        fprintf(stderr, "ERROR: Bad line type\n");
    } /* switch */
    
    /* Finish processing if interrupt has been set */
    if (interrupt) break;
  } /* while */
 
 /* Some postprocessing */
  if(!IS_UPDATE(ud_stream->ud_mode)){
  /* We are in NRTM mode */
  /* Clean up */
   fclose(ud_stream->stream);
   unlink(tmpfile2);
  /* In NRTM mode there may be a saved object that is unprocessed */   
   if(nrtm->tr){ /*saved backlog?*/
    object_process(nrtm->tr); /* delete the previous(saved) object*/
    result=report_transaction(nrtm->tr, &(ud_stream->log), nrtm->object_name, 
                              "NRTM:DEL:While deleting previous(saved) object");
    if(result==1) create_serial(nrtm->tr);
    object_free(nrtm->tr->object);
    transaction_free(nrtm->tr); nrtm->tr=NULL;
   } 
  }

 /* That's all. Free GString */
  g_string_free(g_line_buff, TRUE);
                                                                                                       
 /* Calculate some statistics */
  ftime=time(NULL);
  obj_second1 = (float)(log_ptr->num_ok)/(ftime-stime);
  obj_second10 = (float)(log_ptr->num_ok+log_ptr->num_failed)/(ftime-stime);
  
  /* Print the report */
  if(IS_STANDALONE(ud_stream->ud_mode) || (!IS_UPDATE(ud_stream->ud_mode))) {
/*   printf("\n\n******** report **********\n%d objects OK\n%d objects failed\n", 
            log_ptr->num_ok, log_ptr->num_failed); */
   fprintf(log_ptr->logfile,"\n******** report **********\n");
   fprintf(log_ptr->logfile," %d objects OK (%5.2f obj/s)\n", log_ptr->num_ok, obj_second1);
   fprintf(log_ptr->logfile," %d objects failed\n", log_ptr->num_failed);
   fprintf(log_ptr->logfile," average processing time %5.2f obj/s (%5.2f obj/min)\n", 
                          obj_second10, obj_second10*60);
   result=log_ptr->num_ok+log_ptr->num_failed;
  }
  return(result);

} /* UD_process_stream */

