/***************************************
  $Revision: 1.19 $

  Wrapper for NRTM client

  Status: NOT REVUED, NOT TESTED

 Author(s):       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 <netinet/in.h>
#include <arpa/inet.h>
#include <fcntl.h>
#include <signal.h>
#include "ud.h"
#include "ud_int.h"
#include "constants.h"

#include "server.h"
#include "protocol_mirror.h"
#include "ta.h"

/* here we store sockets for update threads */
/* they are from SV module */
extern int SV_update_sock[];

/* Response time to swtching updates on and off */
#define TIMEOUT 60 
/* Maximum number of objects(serials) we can consume at a time */
#define SBUNCH 1000

/************************************************************
* int get_NRTM_fd()                                         *
*                                                           *
* 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                  *
*                                                           *
* nrtm - pointer to _nrtm structure                         *
* upto_last - if==1 then requests to download serials using *
* LAST keyword                                              *
*                                                           *
* Returns:                                                  *
* A file descriptor for a data stream                       *
* -1 - error                                                *
*                                                           *
************************************************************/
int get_NRTM_fd(struct _nrtm *nrtm, int upto_last, char *source)
{
int sockfd;
struct hostent *hptr;
struct sockaddr_in serv_addr;
struct in_addr *paddr;
char line_buff[STR_XXL];
int fd;
int 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(-1);
 }  
/* hptr=gethostbyname(nrtm->server);*/
 hptr=gethostbyname_r(nrtm->server,  &result, line_buff, sizeof(line_buff), &error);

 /* Check if it is a network stream or a file */
 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(-1);
   }  
   fprintf(stderr, "Sending Invitation\n");
   
   /* Request all available serials (upto LAST), or SBUNCH of them */
   if(upto_last)
      sprintf(line_buff, "-g %s:%d:%ld-LAST\n", source, nrtm->version, nrtm->current_serial+1);
   else
      sprintf(line_buff, "-g %s:%d:%ld-%ld\n", source, 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(-1); }
   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(-1);
   }  
 }  
 return(fd);
} 

/************************************************************
*  void UD_do_nrtm()                                        *
*                                                           *
* Processes NRTM stream                                     *
*                                                           *
* It cycles requesting objects from the NRTM server,        * 
* processing them and then sleeping a specified amount of   *
* time.                                                     *
*                                                           *
* It starts by requesting SBUNCH number of serials and does *
* so untill no serials are received (actually a warning     *
* is received saying that the requested range is invalid)   *
* This approach avoids excessive load on the NRTM server    *
*                                                           *
* After that it requests serials using LAST keyward keeping *
* almost in sync with the server                            *
*                                                           *
************************************************************/
 
void UD_do_nrtm(void *arg)
{
int source = (int)arg;
UD_stream_t ud_stream;
struct _nrtm *nrtm;
int delay;
int do_update=1;
int do_server;
char *logfilename;
FILE *file;
int nrtm_fd;
int num_ok;
int upto_last;
char ta_activity[STR_M];
ca_dbSource_t *source_hdl = ca_get_SourceHandleByPosition(source);
char *db_host, *db_name, *db_user, *db_passwd;
int db_port;
char *source_name;



  nrtm=calloc(1, sizeof(struct _nrtm));
  if(nrtm==NULL) {
	  printf("Cannot allocate memory\n");
	  die;
  }	  
/* get mode of operation: protected/unprotected (dummy) */
  ud_stream.source_hdl=source_hdl;
  ud_stream.ud_mode=ca_get_srcmode(source_hdl);

  fprintf(stderr, "Mode of operation:\n");
  if(IS_DUMMY_ALLOWED(ud_stream.ud_mode))fprintf(stderr, "* dummy allowed\n"); 
   else fprintf(stderr, "* dummy not allowed\n");
  if(IS_UPDATE(ud_stream.ud_mode))fprintf(stderr, "* DBupdate\n");
   else fprintf(stderr, "* NRTM\n");
  if(IS_STANDALONE(ud_stream.ud_mode))fprintf(stderr, "* running standalone\n");
   else fprintf(stderr, "* running as a server\n");
  
/* get mirror server */
  nrtm->server=ca_get_srcnrtmhost(source_hdl);

  
/* get mirror port */
  nrtm->port = htons(ca_get_srcnrtmport(source_hdl));
  printf("XXX nrtm_port=%d\n", ntohs(nrtm->port));

/*
  if(nrtm->port == -1) {
    printf("Invalid service/port: %d\n", nrtm->port);
    return;
  }
*/  
              
/* get mirror version */
  nrtm->version=ca_get_srcnrtmprotocolvers(source_hdl);
 
/* get source we are going to mirror */
  source_name = ca_get_srcname(source_hdl);  

  
/* get error log facility */
   logfilename=ca_get_srcnrtmlog(source_hdl);

   db_host = ca_get_srcdbmachine(source_hdl);
   db_port = ca_get_srcdbport(source_hdl);
   db_name = ca_get_srcdbname(source_hdl);
   db_user = ca_get_srcdbuser(source_hdl);
   db_passwd = ca_get_srcdbpassword(source_hdl);
  
/* Connect to the database */
  fprintf(stderr, "D: Making SQL connection to %s@%s ...", db_name, db_host);
  ud_stream.db_connection=SQ_get_connection(db_host, db_port, db_name, db_user, db_passwd);
     
 
  if(! ud_stream.db_connection) {
   fprintf(stderr, "D: ERROR: no SQL connection\n");
    die;
  }
  	
  fprintf(stderr, "OK\n");

  ud_stream.num_skip=0;
  ud_stream.load_pass=0;
  ud_stream.nrtm=nrtm;
  ud_stream.log.logfile = fopen(logfilename, "a+");
  if(!ud_stream.log.logfile){
	  fprintf(stderr, "D: ERROR: cannot open log file %s\n", logfilename);
	  die;
  }
  
  free(logfilename);

  
  upto_last=0; /* let's start gradually if the backlog is > SBUNCH (1000) serials*/

/*+++ main cycle +++*/

 do {
  do_update=CO_get_do_update();
  if(do_update) {
 
   /* Check connection to the database and try to reconnect */
   if(mysql_ping(ud_stream.db_connection)) {
    fprintf(stderr, "D: ERROR: SQL connection time out - reestablishing\n");
   }

  /* get current serial */
   nrtm->current_serial=PM_get_current_serial(ud_stream.db_connection);
   
   if(nrtm->current_serial == -1) {
     fprintf(stderr, "D: ERROR: Error obtaining current serial: %ld\n", nrtm->current_serial);
     die;
   }

   fprintf(stderr, "current_serial:\t%ld\n", nrtm->current_serial);
   fprintf(stderr, "conecting to server...\n");
      
  /* Get file descriptor of the data stream (RPSL format, use mirror reflector to convert if needed)*/
    nrtm_fd=get_NRTM_fd(nrtm, upto_last, source_name);
   
    /* make a record for thread accounting */
    TA_add(nrtm_fd, "nrtm_clnt");
    sprintf(ta_activity,"[%s]%ld->", source_name, nrtm->current_serial);
    TA_setactivity(ta_activity);
    file=fdopen(nrtm_fd, "r+");

    
    fprintf(stderr, "OK\n");
    printf("OK\n");


    if (file==NULL) { 
     fprintf(stderr, "Cannot open data stream. Trying...\n");
     sleep(100);
     continue;
    }  


   ud_stream.stream=file;
   ud_stream.log.num_ok=0; 
   ud_stream.log.num_failed=0;
  

   fprintf(stderr, "starting processing stream\n");

   num_ok=UD_process_stream(&ud_stream);
  
  
     /*Check for errors */
   if(num_ok<0) {
	 fprintf(stderr, "processing stream failed\n");
	 do_server=0;
	 break;    
   }	   
   else fprintf(stderr, "processing stream finished\n"); 
   
  /* Now we can process serials in normal way (upto LAST)*/ 
   if(num_ok==0) upto_last=1;

   fprintf(ud_stream.log.logfile, "forwarded to serial:\t%ld\n", (nrtm->current_serial+num_ok));
   fflush(ud_stream.log.logfile);
   fprintf(stderr, "forwarded to serial:\t%ld\n", (nrtm->current_serial+num_ok));
   printf("Objects received: %d\n-----------\n", num_ok);

   /* set activity for thread record */
   sprintf(ta_activity,"[%s]->%ld", source_name, (nrtm->current_serial+num_ok));
   TA_setactivity(ta_activity);


  /* get delay */
   delay=ca_get_srcnrtmdelay(source_hdl);
   SV_sleep(LOCK_SHTDOWN, delay);
  } /* if do_updates */
  else SV_sleep(LOCK_SHTDOWN, TIMEOUT); 

  do_server=CO_get_do_server();
  TA_delete();
  
 } while(do_server);  /* main cycle */

   fclose(ud_stream.log.logfile);
   free(source_name);
/* free data associated with nrtm structure */         
 if(nrtm) {
   free(nrtm->server);
   free(nrtm);
 }
 
 /* That's all. Close connection to the DB */ 
 SQ_close_connection(ud_stream.db_connection);
 free(db_host);
 free(db_name);
 free(db_user);
 free(db_passwd);

 fprintf(stderr, "NRTM stopped\n");  

} /* UD_do_nrtm() */

/************************************************************
*  void UD_do_updates()                                     *
*                                                           *
* Processes updates                                         *
*                                                           *
* It cycles accepting connections and processing them       * 
* (interactive server). This assures that there is only     *
* one write thread per database/source.                     *
*                                                           *
************************************************************/
   
void UD_do_updates(void *arg)
{
int source = (int)arg;
int listening_socket = SV_update_sock[source];
int connected_socket;
UD_stream_t ud_stream;
int do_update=1;
int do_server;
char *logfilename;
FILE *file, *file_ack;
int num_ok;
ca_dbSource_t *source_hdl = ca_get_SourceHandleByPosition(source);
char *db_host, *db_name, *db_user, *db_passwd;
int db_port;



/* get mode of operation: protected/unprotected (dummy) */
/*  ud_stream.ud_mode=CO_get_update_mode(); */
  ud_stream.source_hdl=source_hdl;
  ud_stream.ud_mode=ca_get_srcmode(source_hdl);

  fprintf(stderr, "Mode of operation:\n");
  if(IS_DUMMY_ALLOWED(ud_stream.ud_mode))fprintf(stderr, "* dummy allowed\n"); 
   else fprintf(stderr, "* dummy not allowed\n");
  if(IS_UPDATE(ud_stream.ud_mode))fprintf(stderr, "* DBupdate\n");
   else fprintf(stderr, "* NRTM\n");
  if(IS_STANDALONE(ud_stream.ud_mode))fprintf(stderr, "* running standalone\n");
   else fprintf(stderr, "* running as a server\n");


/* get error log facility */
  logfilename=ca_get_srcnrtmlog(source_hdl);
  db_host = ca_get_srcdbmachine(source_hdl);
  db_port = ca_get_srcdbport(source_hdl);
  db_name = ca_get_srcdbname(source_hdl);
  db_user = ca_get_srcdbuser(source_hdl);
  db_passwd = ca_get_srcdbpassword(source_hdl);
  
/* Connect to the database */
  fprintf(stderr, "D: Making SQL connection to %s@%s ...", db_name, db_host);

/*  ud_stream.db_connection=SQ_get_connection2(); */
  ud_stream.db_connection=SQ_get_connection(db_host, db_port, db_name, db_user, db_passwd);
   
  if(! ud_stream.db_connection) {
   fprintf(stderr, "D: ERROR: no SQL connection\n");
    die;
  }
  	
  fprintf(stderr, "OK\n");

  ud_stream.num_skip=0;
  ud_stream.load_pass=0;
  ud_stream.nrtm=NULL;
  ud_stream.log.logfile = fopen(logfilename, "a+");
  if(!ud_stream.log.logfile){
	  fprintf(stderr, "D: ERROR: cannot open log file %s\n", logfilename);
	  die;
  }

  free(logfilename);

 
/*+++ main cycle +++*/

do { /* be alive while do_server is 1. do_server is turned off by SIGINT */
 
  /* make a record for thread accounting */
  TA_add(listening_socket, "update");
  TA_setactivity("waiting");
  
 
/* accept connection */
   connected_socket = SK_accept_connection(listening_socket);
   if(connected_socket==-1) break;
   

   /* make a record for thread accounting */
   TA_delete(); /* Delete 'waiting' record */
   TA_add(connected_socket, "update");

   file=fdopen(connected_socket, "r");
   file_ack=fdopen(connected_socket, "w");

 do_update=CO_get_do_update();
 if(do_update) {
 
   TA_setactivity("suspended");
   
   fprintf(stderr, "Connection accepted...\n");
   
  if ((file==NULL) || (file_ack==NULL)) {
    fprintf(stderr, "Cannot open data stream. Closing connction\n");
    return; 
  }
  fprintf(stderr, "Connection accepted...\n");

  ud_stream.stream=file;
  ud_stream.log.num_ok=0; 
  ud_stream.log.num_failed=0;
 
  /* Check connection to the database and try to reconnect*/
  if(mysql_ping(ud_stream.db_connection)) {
   fprintf(stderr, "D: ERROR: SQL connection time out - reestablishing\n");
  }

  fprintf(stderr, "starting processing object\n");

  num_ok=UD_process_stream(&ud_stream);
  
  fprintf(stderr, "processing object finished\n");  

  if(num_ok==1) {
   fprintf(file_ack, "%%ERROR 0\n");
   fprintf(stderr, "%%ERROR 0\n");
  } 
   else {
      num_ok=(-1)*num_ok;
      fprintf(file_ack, "%%ERROR %d\n",num_ok);
      fprintf(stderr, "%%ERROR %d\n",num_ok);
      fprintf(file_ack, "Transaction had the following problems:\n");
      if(num_ok & ERROR_U_MEM) fprintf(file_ack, "Memory allocation error\n");
/*      if(num_ok & ERROR_U_DBS) fprintf(file_ack, "Database (SQL) error\n");*/
/*      if(num_ok & ERROR_U_OBJ) fprintf(file_ack, "Object (RF) error\n");*/
/*      if(num_ok & ERROR_U_AUT) fprintf(file_ack, "Object authentication error\n");*/
      if(num_ok & ERROR_U_BADOP) fprintf(file_ack, "Bad operation\n");
      if(num_ok & ERROR_U_COP) fprintf(file_ack, "Conflicting operation\n");
      if(num_ok & ERROR_U_NSUP) fprintf(file_ack, "Object of this type is not supported\n");
      if(num_ok & ERROR_U_BUG) fprintf(file_ack, "Software bug - report to <ripe-dbm@ripe.net>\n");
   }
   if(ud_stream.error_script)fprintf(file_ack, "%s\n", ud_stream.error_script);
   
   if(ud_stream.error_script) free(ud_stream.error_script);
   
   fflush(file_ack); fclose(file_ack);
   fclose(file);
 }  /* if do_update*/
else { /* Otherwise print a message*/
 /* To display with 'show threads' */
  TA_setactivity("suspended");
 
  fprintf(file_ack, "%%ERROR 1000\n%%Updates are suspended\n");
  fflush(file_ack); fclose(file_ack);
  fclose(file);
 }
  /* make a record for thread accounting */
   TA_delete();

   do_server=CO_get_do_server();  

} while (do_server);  /* main cycle */
  
   fclose(ud_stream.log.logfile);
 /* That's all. Close connection to the DB */ 
 SQ_close_connection(ud_stream.db_connection);
 free(db_host);
 free(db_name);
 free(db_user);
 free(db_passwd);


 fprintf(stderr, "server stopped\n");  

} /* UD_do_update() */




