/*
 * $Id: irrd_ops.c,v 1.27 1999/08/25 18:24:37 polandj Exp $
 */

#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <unistd.h>
#include <stdlib.h>
#include <mrt.h>

#include <irrd_ops.h>
#include <hdr_comm.h>

static char *start_irrd_session (trace_t *tr, int sockfd);
static char *read_socket_cmd (trace_t *tr, int fd, char *response);
static int read_socket_obj (trace_t *tr, int sockfd, FILE *fobj, int obj_size, 
			    int len, int max_line_size);
static char *send_socket_cmd (trace_t *tr, int fd, char *cmd);
static char *send_transaction (trace_t *tr, char *warn_tag, int fd, char *op, 
			       char *source, FILE *fin);

/* open connection  */
int open_connection (trace_t *tr, char *IRRd_HOST, int IRRd_PORT) {
  int sockfd;
  struct sockaddr_in servaddr;
  struct hostent *hoste;
  unsigned long addr;

  trace (TR_TRACE, tr, "open_connection %s:%d\n", IRRd_HOST, IRRd_PORT);

  sockfd = socket(AF_INET, SOCK_STREAM, 0);
  memset(&servaddr, 0, sizeof(servaddr));
  servaddr.sin_family= AF_INET;
  servaddr.sin_port = htons(IRRd_PORT);

  /* don't you love how gethostbyname is too stupid to accept numeric
     addresses? grrrr. */

  addr = inet_addr(IRRd_HOST);
  if ((signed long) addr == -1) {
    if ((hoste = gethostbyname(IRRd_HOST)) == NULL) {
      trace (NORM, tr, 
	     "open_connection () ERROR resolving %s: (%s)\n", 
	     IRRd_HOST, strerror (errno));
      return -1;
    }
    memcpy(&addr, hoste->h_addr_list[0], sizeof(struct in_addr));
  }

  servaddr.sin_addr.s_addr = addr;

  if (-1 == connect(sockfd, (struct sockaddr *) &servaddr, sizeof(servaddr))) {
    trace (NORM, tr, "ERROR opening IRRd TCP connection \n");
    return -1;
  }

  return sockfd;
}

/* close connection */
int close_connection (int fd) {
  close (fd);
  return 1;
}

char *send_socket_cmd (trace_t *tr, int fd, char *cmd) {
  fd_set write_fds;
  struct timeval  tv;

  if (fd < 0)
    return "\"System error: send_socket_cmd () broken IRRd pipe.\"\n";

  /*  fprintf (stderr, "send_socket_cmd () cmd len (%d)\n", strlen(cmd)); */
  FD_ZERO(&write_fds);
  FD_SET(fd, &write_fds);
  tv.tv_sec = 3;
  tv.tv_usec = 0;

  if (select (fd + 1, 0, &write_fds, 0, &tv) < 1) {
    trace (ERROR, tr, "send_socket_cmd () write time out!\n");
    return "\"System error: IRRd write timeout.  IRRd down or connectivity problems.\"\n";
  }

  if (write (fd, cmd, strlen (cmd)) < 0) {
    trace (ERROR, tr, "send_socket_cmd () write error (%s)\n", strerror(errno));
    return "\"System error: IRRd socket write error.\"\n";
  }
 
  return NULL;
}

/*
 * Read something back from IRRd.  If you don't care
 * about the return code from IRRd set response = NULL.
 * Otherwise, set *response to what you are expecting
 * from IRRd (eg, "C\n").  If the response does not match,
 * the function will return the entire IRRd response line.
 *
 * Return codes:
 *  NULL means *response matched IRRd's response
 *  otherwise return IRRd response.
 */
char *read_socket_cmd (trace_t *tr, int fd, char *response) {
  char buf[MAXLINE];
  int n;
  fd_set read_fds;
  struct timeval  tv;

  if (fd < 0)
    return "\"System error: read_socket_cmd () broken IRRd pipe.\"\n";

  FD_ZERO(&read_fds);
  FD_SET(fd, &read_fds);
  tv.tv_sec = 1;
  tv.tv_usec = 0;

  if (select (fd + 1, 0, &read_fds, 0, &tv) < 1) {
    trace (ERROR, tr, "read_socket_cmd () read time out!\n");
    return "\"System error: Read timeout.  IRRd is down or connectivity problems.\"\n";
  }

  n = read (fd, buf, MAXLINE - 1);
  if (response == NULL)
    return NULL;

  if (n <= 0) {
    trace (ERROR, tr, "read_socket_cmd () read error (%s)!\n", strerror (errno));
    return "\"System error: Read IRRd socket error.\"\n";
  }
  buf[n] = '\0';

  if (strncmp (buf, response, strlen (response)))
    return strdup (buf);

  return NULL;
}


/*
 * Read a single line terminated by a '\n'.  'bufsize' should be >= 2
 * or else function will return an error.  The function pads the line
 * with a '\0' null byte string terminator.
 *
 * Return:
 *   number of characters read.
 */
int read_socket_line (trace_t *tr, int sockfd, char buf[], int bufsize) {
  int n = 0, x = 0, i;
  fd_set read_fds;
  struct timeval  tv;

  if (sockfd < 0) {
    trace (ERROR, tr, "System error: read_socket_line () broken IRRd pipe.\n");
    return -1;
  }

  FD_ZERO(&read_fds);
  FD_SET(sockfd, &read_fds);
  tv.tv_sec = 1;
  tv.tv_usec = 0;

  for (i = bufsize - 1; i > 0; i--) {
    if (select (sockfd + 1, 0, &read_fds, 0, &tv) < 1) {
      trace (ERROR, tr, "read_socket_line () read time out!\n");
      return -1;
    }
    if ((x = read (sockfd, &buf[n], 1)) <= 0 ||
	buf[n++] == '\n')
      break;
  }

  buf[n] = '\0';

  if (x <= 0) {
    trace (ERROR, tr, 
	   "read_socket_line () read (%d) bytes and did not get a '\\n', buffer size (%d)\n", n, bufsize);
    return -1;
  }

  /*
  fprintf (dfile, "read_socket_line () irrd:(%s)\n", buf);
  */

  return n;
}

/* JW: need to check this routine for bug: if an object submission
 * exceeds the 'max_line_size' then SNIP_MSG could be inserted into
 * the object and written to the DB.  Not sure if this is can happen or not.
 */
/* Read a DB object into file 'fobj'.  We have already read the 'A...'
 * answer length specifier, 'len', and are now reading the object data.
 * 'obj_size' can be either 'FULL_OBJECT' or 'SHORT_OBJECT'.  'SHORT_OBJECT'
 * will retrieve select fields only to minimize the amount of processing
 * and disk space needed.  For example, irr_auth only needs the maintainer
 * references, auth fields, etc...  Some aut-num objects can be as large
 * as 25Mb so a definite savings is possible.
 *
 * Return:
 *   -1 means the 'A...'/'len' specifier did not match or a socket
 *      error occured.
 *    1 object was successfully read and saved to file.
 */
int read_socket_obj (trace_t *tr, int sockfd, FILE *fobj, int obj_size, 
		     int len, int max_line_size) {
  char buf[MAXLINE];
  int n, print_snip = 1;
  int dump_line = 0, line_cont = 0, line_begin;

  while (len > 0) {
    if ((n = read_socket_line (tr, sockfd, buf, MAXLINE)) <= 0)
      return -1;
    
    len -= n;

    if ((line_begin = !line_cont))
      dump_line = 0;
    line_cont = (buf[n - 1] != '\n');

    if (dump_line ||
	(line_begin && ((--max_line_size < 0 || obj_size == SHORT_OBJECT) &&
	!(is_upd_to (buf)  || 
	  is_mnt_nfy (buf) ||
	  is_notify (buf)  ||
	  is_auth (buf)    ||
	  is_mnt_by (buf)  ||
	  is_changed (buf) ||
	  is_source (buf))))) {
      if (print_snip && max_line_size < 0) {
	fwrite (SNIP_MSG, 1, strlen (SNIP_MSG), fobj);
	print_snip = 0;
      }
      dump_line = 1;
      continue;
    }

    if (fwrite (buf, 1, n, fobj) < n) {
      trace (ERROR, tr, "read_socket_obj () write file error: (%s)\n", strerror (errno));
      return -1;
    }
  }

  /* read the IRRd 'C\n' return code */
  read_socket_line (tr, sockfd, buf, MAXLINE);

  return 1;
}

/* send '!!' */
char *start_irrd_session (trace_t *tr, int sockfd) {

  return send_socket_cmd (tr, sockfd, "!!\n"); 
}

/* send '!q' */
char *end_irrd_session (trace_t *tr, int sockfd) {

  return send_socket_cmd (tr, sockfd, "!q\n");
}

/* Send a 
 * !us<DB>
 * <OPERATION>
 *
 *...<object>...
 *
 * !ue
 *
 * Return:
 *   Char return line from IRRd.
 *   Char error line from this routine if
 *   a system or network error occurs.
 *   NULL if routine encounters something
 *   other than ADD or DEL (NOOP?).  NULL
 *   means nothing was sent.
 */
char *send_transaction (trace_t *tr, char *warn_tag, int fd, char *op, 
			char *source, FILE *fin) {
  char buf[MAXLINE], *ret_code;
  int line_cont, line_begin, n;

  sprintf (buf, "!us%s\n", source);
  if (!strcmp (op, ADD_OP) || !strcmp (op, REPLACE_OP)) {
    /*fprintf (tr, "!us%s\n", source );*/
    if ((ret_code = send_socket_cmd (tr, fd, buf)) != NULL)
      return ret_code;
    if ((ret_code = read_socket_cmd (tr, fd, "C\n")) != NULL)
      return ret_code;
    if ((ret_code = send_socket_cmd (tr, fd, "ADD\n")) != NULL ||
	(ret_code = send_socket_cmd (tr, fd, "\n")) != NULL)
      return ret_code;
  }    
  else if (!strcmp (op, DEL_OP)) {
    /*fprintf (dfile, "!us%s\nDEL\n\n", source);*/
    if ((ret_code = send_socket_cmd (tr, fd, buf)) != NULL)
      return ret_code;
    if ((ret_code = read_socket_cmd (tr, fd, "C\n")) != NULL)
      return ret_code;
    if ((ret_code = send_socket_cmd (tr, fd, "DEL\n")) != NULL ||
	(ret_code = send_socket_cmd (tr, fd, "\n")) != NULL)
      return ret_code;
  }
  else /* Must be a NOOP, don't do anything.
	* We should never get here, calling routine
	* should recognize NOOP's and skip.
	*/
    return NULL;

  line_cont = 0;
  while (fgets (buf, MAXLINE - 1, fin) != NULL) {
    n = strlen (buf);
    line_begin = !line_cont;
    line_cont = (buf[n - 1] != '\n');
    if (line_begin && !strncmp (buf, warn_tag, strlen (warn_tag)))
      continue;

    if ((ret_code = send_socket_cmd (tr, fd, buf)) != NULL)
      return ret_code;

    if (line_begin && buf[0] == '\n') /* looking for a blank line */
      break;
  }
  
  /*fprintf (dfile, "!ue\n");
  printf ("send !ue...\n");
  */
  if ((ret_code = send_socket_cmd (tr, fd, "!ue\n")) != NULL)
    return ret_code;

  if ((ret_code = read_socket_cmd (tr, fd, "C\n")) != NULL) {
    /*
printf ("send_trans (): exit bad return code (%s)", ret_code);
    */
    return ret_code;
  }

printf ("send_trans: exit good return code (%s)", "C\n");  
  return "C\n";
}

/* Send an IRRd transaction (ie, !us...!ue).
 * Function expects file position pointer to
 * be at the start of the object.
 *
 * Return:
 *
 *   Char *response/return code from IRRd
 *   Char *line from this routine if the IRRd
 *   socket could not be opened succesfully.
 */
char *irrd_transaction (trace_t *tr, char *warn_tag, int *socketfd, FILE *fin, 
			char *op, char *source, int num_trans, int *open_conn,
			char *IRRd_HOST, int IRRd_PORT) {
  char *ret_code;
  
  if (num_trans == 1) {
    
    /* Open connection */
    if ((*socketfd = open_connection (tr, IRRd_HOST, IRRd_PORT)) < 0)
      return "\"System error: Open socket () error!\"\n";
    
    *open_conn = 1;
    
    if ((ret_code = start_irrd_session (tr, *socketfd)) != NULL)
      return ret_code;
  }
  
  return send_transaction (tr, warn_tag, *socketfd, op, source, fin);
}

/* Fetch a DB object from IRRd and put it into file '*fname'.  'obj_size'
 * can be either 'SHORT_OBJECT' in which case only notify, mnt_nfy, upd_to,
 * and auth fields are fetched, or 'FULL_OBJECT'.
 *
 * Return:
 *  Stream file pointer to the object if it was received successfully from IRRd
 *  and the name of the file in 'fname'.
 *  NULL otherwise.
 */
FILE *IRRd_fetch_obj (trace_t *tr, char *fname, int *conn_open, int *sockfd,
		      int obj_size, char *obj_type, char *obj_key, char *source,
		      int max_obj_line_size, char *IRRd_HOST, int IRRd_PORT) {
  char buf[MAXLINE];
  FILE *fobj;
  
  /* open the output file */
  my_mktemp (tr, fname);
  if ((fobj = fopen (fname, "w+")) == NULL) {
    trace (ERROR, tr, "IRRd_fetch_obj () can't open \"%s\": (%s)\n", 
	   fname, strerror (errno));
    return NULL;
  }
  
  /* Open an IRRd connection */
  if (++*conn_open == 1) {
    if ((*sockfd = open_connection (tr, IRRd_HOST, IRRd_PORT)) < 0) {
      fclose (fobj);
      remove (fname);
      *conn_open = 0;
      return NULL;
    }

    /* Send a '!!' */
    if (start_irrd_session (tr, *sockfd) != NULL) {
      trace (ERROR, tr, "IRRd_fetch_obj () '!!' failed\n");
      fclose (fobj);
      remove (fname);
      return NULL;
    }
    
    /* make sure we can get withdrawn routes too */
    sprintf (buf, "!uwd=1\n");
    if (send_socket_cmd (tr, *sockfd, buf)   != NULL ||
	read_socket_cmd (tr, *sockfd, "C\n") != NULL) {
      trace (ERROR, tr, "IRRd_fetch_obj () '!uwd=1' failed\n");
      fclose (fobj);
      remove (fname);
      return NULL;
    }
  }
  
  /* send the IRRd commands to retrieve the object */
  sprintf (buf, "!s%s\n", source);
  if (send_socket_cmd (tr, *sockfd, buf)   != NULL ||
      read_socket_cmd (tr, *sockfd, "C\n") != NULL) {
    fclose (fobj);
    remove (fname);
    return NULL;
  }
    
  sprintf (buf, "!m%s,%s\n", obj_type, obj_key);
  if (send_socket_cmd (tr, *sockfd, buf)           != NULL ||
      read_socket_line (tr, *sockfd, buf, MAXLINE) < 0) {
    fclose (fobj);
    remove (fname);
    return NULL;
  }
  
  /* An 'A' return code means we have the object, otherwise 
   * the object was not found.
   */
  /*fprintf (dfile, "IRRd_fetch_object () irrd ret_code (%s)\n", buf);*/
  if (buf[0] != 'A') {
    fclose (fobj);
    remove (fname);
    return NULL;
  }
  
  /* put the object into file */
  newline_remove (buf);
  if (read_socket_obj (tr, *sockfd, fobj, obj_size, atoi (&buf[1]), max_obj_line_size) < 0) {
    fclose (fobj);
    remove (fname);
    return NULL;
  }
  
  /* Simplify NOOP checking, put a blank line after the object */
  if (EOF == fputs ("\n", fobj))
    trace (ERROR, tr, "IRRd_fetch_obj () can't write \\n at object end: (%s)\n", strerror (errno)); 

  return fobj;
}
