#include <stdio.h>
#include <string.h>
#include "mrt.h"
#include "trace.h"
#include <time.h>
#include "irrd.h"

#define GRM_BUFSIZE (10*1024)

extern trace_t *default_trace;

/*
 * mirrorstatus_buildquery:
 *
 * Builds the !j query that we're going to send to a remote IP.
 * Returns 0 on failure, 1 on success.
 *
 * Arguments:
 * LINKED_LIST *ll_checkdb - Empty linked list.  We add on each
 *   irr_database_t * that matches the mirror_prefix and mirror_port.
 *   This lets us minimize the number of databases we have to iterate through
 *   for checking answers.
 * char *query - Pointer to a string in which to build the query.
 * int query_len - Length of the query array
 * prefix_t *mirror_prefix - The IP address we are querying.
 * int mirror_port - The TCP port that we're querying.
 *
 */
static int mirrorstatus_buildquery (LINKED_LIST *ll_checkdb, char *query, int query_len, prefix_t *mirror_prefix, int mirror_port) {
  char *cp;
  int space_left;
  irr_database_t *database;
  int ret = 0;

  /* Iterate through all the databases looking for this prefix and 
     mirror_port and build up the query. */

  assert(query);
  assert(ll_checkdb);

  sprintf(query, "!j");
  space_left = query_len - 3;
  LL_IntrIterate (IRR.ll_database, database) {
    if ((database->mirror_prefix != NULL) &&
        (prefix_equal(mirror_prefix, database->mirror_prefix)) && 
        (mirror_port == database->mirror_port)) {

      if (strlen(database->name) > (space_left - 1)) {
	/* TODO - abort */
      }
      strncat(query, database->name, space_left);
      strcat(query, ",");
      space_left -= MAX((0), (strlen(database->name) + 1));

      LL_Add (ll_checkdb, database);
    }
  }

  /* Do we have anyone in our list? */
  if (!strcmp(query, "!j")) {
     trace (NORM, default_trace,
            "mirrorstatus: *WARNING* unable to build query - no one mirroring via %s:%d\n",
	    prefix_toa(mirror_prefix), mirror_port);
     *query = '\0';
     goto FAIL;
  }

  cp = query + strlen(query) - 1;
  *(cp++) = '\n'; /* Remove trailing comma */
  *cp = '\0';

  convert_toupper(query+2);

  ret = 1;

FAIL:

  return (ret);
}

/*
 * mirrorstatus_sendquery_getanswer:
 *
 * Sends the !j query to a remote IP.  Pulls back the answer.
 * Returns 0 on failure, 1 on success.
 *
 * Arguments:
 * char *query - Pointer to a string in which to build the query.
 * char *answer - Where we are going to store the answer we get back.
 * int answer_len - Length of the answer array
 * prefix_t *mirror_prefix - The IP address we are querying.
 * int mirror_port - The TCP port that we're querying.
 *
 */
static int mirrorstatus_sendquery_getanswer (char *query, char *answer, int answer_size, prefix_t *mirror_prefix, int mirror_port) {
  int ret = 0, n_ret, select_ret, n_read, space_left;
  int sockfd;
  struct sockaddr_in servaddr;
  char *cp;
  fd_set fd_read;
  struct timeval tv;

#ifdef HAVE_LIBPTHREAD
  tv.tv_usec = 0;
  tv.tv_sec = 30; /* 30 second timeout */
#else
  tv.tv_usec = 0;
  tv.tv_sec = 5; /* 5 second timeout */
#endif

  trace (TRACE, default_trace, "get_remote_mirrorstatus: Connecting to %s:%d\n",
	 prefix_toa(mirror_prefix), mirror_port);

  sockfd = socket(AF_INET, SOCK_STREAM, 0);
  memset(&servaddr, 0, sizeof(servaddr));

  /* Connect */
  n_ret = nonblock_connect (default_trace, mirror_prefix, mirror_port, sockfd);

  if (n_ret != 1) {
    trace (NORM, default_trace, "mirrorstatus: Failed to connect to %s:%d\n",
	   prefix_toa(mirror_prefix), mirror_port);
    if (sockfd > -1) close (sockfd);
    goto FAIL;
  }

  /* This is slightly sloppy.  We count on a single write being able to
     push the answer since its doubtful that we'll have more than the
     lowest MTU (700+bytes) of queries. */
  n_ret = write (sockfd, query, strlen(query));
  if (n_ret != strlen(query)) {
    trace (NORM, default_trace,
	   "mirrorstatus: Failed to write query to %s:%d\nget_remote_mirrorstatus: %s",
	   prefix_toa(mirror_prefix), mirror_port, query);
    if (sockfd > -1) close (sockfd);
    goto FAIL;
  }

  space_left = answer_size - 1;
  answer[answer_size] = '\0';
  n_read = 0;
  cp = answer;

  FD_ZERO (&fd_read);
  FD_SET (sockfd, &fd_read);
  /* We can get away without resetting the fd_read in the select since
     we're only checking one descriptor */

  while ((select_ret = select(sockfd+1, &fd_read, NULL, NULL, &tv)) &&
	 (space_left > 0)) {
    n_read = read(sockfd, cp, space_left);
    if (n_read == 0) {
      close (sockfd);
      sockfd = -1;
      break;
    }
    else if (n_read < 0) {
      trace (NORM, default_trace,
	     "mirrorstatus: Error reading from socket for %s:%d\n",
	     prefix_toa(mirror_prefix), mirror_port);
      goto FAIL;
    }
    cp += n_read;
    space_left -= n_read;
  }

  if (sockfd < 0) {
    /* Alles gute */
  }
  else if (select_ret == 0) {
    /* Select timed out */
    trace (NORM, default_trace,
	   "mirrorstatus: Timed out reading file descriptor for %s:%d\n",
	   prefix_toa(mirror_prefix), mirror_port);
    goto FAIL;
  }
  else if (space_left == 0) {
    /* Ran out of buffer */
    trace (NORM, default_trace,
	   "mirrorstatus: Ran out of buffer space while reading from %s:%d\n",
	   prefix_toa(mirror_prefix), mirror_port);
    goto FAIL;
  }
  else {
    /* Unspecified error */
    trace (NORM, default_trace,
	   "mirrorstatus: Unknown error while reading from %s:%d\n",
	   prefix_toa(mirror_prefix), mirror_port);
    goto FAIL;
  }

  ret = 1;

FAIL:
  if (sockfd >= 0) close (sockfd);

  return (ret);
}

/*
 * mirrorstatus_process_answer
 *
 * When we've gotten an "A" answer back from the remote server,
 * this function parses the output.
 * Returns 0 on failure, 1 on success.
 *
 * Arguments:
 * LINKED_LIST *ll_checkdb - Empty linked list.  We add on each
 *   irr_database_t * that matches the mirror_prefix and mirror_port.
 *   This lets us minimize the number of databases we have to iterate through
 *   for checking answers.
 * char *answer - Where we are going to store the answer we get back.
 *
 */
static int mirrorstatus_process_answer (LINKED_LIST *ll_checkdb, char *answer) {
  char *answer_line, *db_name, *lasts, *lasts2;
  char *p_flag, *p_answer, *p_last_export;
  irr_database_t *db;
  u_long range_start, range_end, last_export;
  int ret = 0;

  /* Realize of course the "answer" is getting horribly mangled by
     all of these strtok's that are being called */

  /* Tokenize on newlines */
  answer_line = strtok_r(answer, "\n", &lasts);
  do {
    db_name = strtok_r (answer_line, ":", &lasts2);
    /* Don't trust the other side to send us non-gibberish */
    if (db_name == NULL) {
      trace (NORM, default_trace,
             "mirrorstatus: *ERROR* Got invalid !j db_name.\n");
      goto FAIL;
    }

    LL_Iterate(ll_checkdb, db) {
      if (!strcasecmp(db->name, db_name)) {
	p_flag = strtok_r (NULL, ":", &lasts2);
	p_answer = strtok_r (NULL, ":", &lasts2);
	p_last_export = strtok_r (NULL, ":", &lasts2);

	if ((p_flag == NULL) || (strlen(p_flag) != 1)) {
	  trace (NORM, default_trace,
		 "mirrorstatus: *ERROR* Got invalid !j answer [2].\n");
	  goto FAIL;
	}

	switch (*p_flag) {
	  case 'Y' :
	  case 'N' :
	    if ((p_answer != NULL) && (sscanf(p_answer, "%lu-%lu", &range_start, &range_end) != 2)) {
	      trace (NORM, default_trace,
		     "mirrorstatus: *ERROR* Invalid range tokens\n");
	      goto FAIL;
	    }
	    last_export = 0;
	    if ((p_last_export != NULL) && (sscanf(p_last_export, "%lu", &last_export) != 1)) {
	      trace (NORM, default_trace,
		     "mirrorstatus: *ERROR* Invalid last export tokens\n");
	      goto FAIL;
	    }
	    db->remote_mirrorstatus = (*p_flag == 'Y') ? MIRRORSTATUS_YES : MIRRORSTATUS_NO;
	    db->remote_oldestjournal = (*p_flag == 'Y') ? range_start : 0;
	    db->remote_currentserial = range_end;
	    db->remote_lastexport    = last_export;
	    break;
	  case 'X' :
	    db->remote_mirrorstatus = MIRRORSTATUS_UNAVAILABLE;
	    db->remote_oldestjournal = 0;
	    db->remote_currentserial = 0;
	    db->remote_lastexport = 0;
	    break;
	  default  :
	    trace (NORM, default_trace, 
	           "mirrorstatus: *ERROR* Invalid answer flag\n");
	    goto FAIL;
	}
	break; /* We only want to go through the loop once per db match */
      }
    }
    answer_line = strtok_r(NULL, "\n", &lasts);
  } while (answer_line != NULL);

  ret = 1;

FAIL:
  return (ret);
}

/*
 * get_remote_mirrorstatus
 *
 * Returns 0 on failure, 1 on success.
 *
 * Gets the remote mirror status.  Since a given irrd client may mirror
 * multiple databases from a given server, its nice to not have to 
 * send individual !j's to get answers.  (!! aside)
 * Thus this function expects a prefix and a port and will examine the
 * database linked list and send queries for all databases that we
 * are mirroring off a given IP address.
 *
 * prefix_t *mirror_prefix - The IP address that we want status for
 * int mirror_port - The TCP port that we are getting the status from.
 *
 */
int get_remote_mirrorstatus (prefix_t *mirror_prefix, int mirror_port) {
  char query[BUFSIZE];
  char answer[GRM_BUFSIZE], *p_dup_answer = NULL, *cp;
  int return_val = 0;
  int answer_length;
  irr_database_t *database;
  LINKED_LIST *ll_checkdb;

  assert(mirror_prefix);
  assert(mirror_port > 0);

  ll_checkdb = LL_Create(0);
  assert(ll_checkdb);

  if (!mirrorstatus_buildquery (ll_checkdb, query, BUFSIZE, 
                                mirror_prefix, mirror_port)) {
    LL_Iterate(ll_checkdb, database) {
      database->remote_mirrorstatus = MIRRORSTATUS_FAILED;
      database->remote_oldestjournal = 0;
      database->remote_currentserial = 0;
    }
    goto FAIL;
  }

  if (!mirrorstatus_sendquery_getanswer(query, answer, GRM_BUFSIZE, 
                                        mirror_prefix, mirror_port)) {
    LL_Iterate(ll_checkdb, database) {
      database->remote_mirrorstatus = MIRRORSTATUS_FAILED;
      database->remote_oldestjournal = 0;
      database->remote_currentserial = 0;
      }
    goto FAIL;
  }

  switch (answer[0]) {
    case '\n' : /* RIPE-181 */
    case 'F'  : /* Not Supported */
      LL_Iterate(ll_checkdb, database) {
	database->remote_mirrorstatus = MIRRORSTATUS_UNSUPPORTED;
	database->remote_oldestjournal = 0;
	database->remote_currentserial = 0;
      }
      break;
    case 'D'  : /* Empty query */
      LL_Iterate(ll_checkdb, database) {
	database->remote_mirrorstatus = MIRRORSTATUS_UNAVAILABLE;
	database->remote_oldestjournal = 0;
	database->remote_currentserial = 0;
      }
      break;
    case 'A'  : /* Valid query - here's the answer */
      p_dup_answer = strdup(answer);

      cp = p_dup_answer + 1;
      if (!sscanf (cp, "%d\n", &answer_length)) goto FAIL;
      while (*cp != '\n') cp++;
      cp++;
      cp[answer_length - 1] = '\0'; /* Trims off last NL */

      if (!mirrorstatus_process_answer (ll_checkdb, cp)) {
	trace (NORM, default_trace, "mirrorstatus: Invalid answer follows\n%s\n", answer);
        goto FAIL;
      }
      break;
    default   :
      trace (NORM, default_trace, "mirrorstatus: *ERROR* got gibberish for an answer to !j\n%s\n", answer);
      goto FAIL;
      break; /* NOT REACHED */
  }

  return_val = 1;

FAIL:
  LL_Destroy (ll_checkdb);
  if (p_dup_answer) { free(p_dup_answer); }

  return (return_val);
}

/*
 * uii_mirrorstatus_db
 * 
 * Returns 0 on failure, 1 on success.
 *
 * Arguments:
 * uii_connection_t *uii - Pointer to the outside world.
 * char *db_name - the DB we've been asked about
 *
 * This is a sloppy function, meant to be run by humans.  It will
 * call get_remote_mirrorstatus for a specific database and display
 * the output to uii.  Biggest problem is that it'll !j each time you
 * want to see the data.
 *
 */
void uii_mirrorstatus_db (uii_connection_t *uii, char *db_name) {
  irr_database_t *database;
  u_long oldest_journal = 0;
  int ret;
  enum { UNDETERMINED, READONLY, AUTHORITATIVE, MIRROR } status = UNDETERMINED;

  if ((database = find_database (db_name)) != NULL) {
    uii_add_bulk_output (uii, "%s (", db_name);
    if (database->mirror_prefix == NULL) {
      if ((database->flags & IRR_AUTHORITATIVE) == IRR_AUTHORITATIVE) {
	status = AUTHORITATIVE;
	uii_add_bulk_output (uii, "Authoritative)\r\n", db_name);
      }
      else {
	status = READONLY;
	uii_add_bulk_output (uii, "Read only)\r\n", db_name);
      }
    }
    else {
      status = MIRROR;
      uii_add_bulk_output (uii, "Mirror)\r\n", db_name);
    }

    uii_add_bulk_output (uii, "\r\nLocal Information:\r\n");

    if (find_oldest_serial(database->name, JOURNAL_OLD, &oldest_journal) != 1)
      if (find_oldest_serial(database->name, JOURNAL_NEW, &oldest_journal) != 1)
	 oldest_journal = 0;

    if (oldest_journal) {
      uii_add_bulk_output (uii, "  Oldest journal serial number: %lu\r\n",
			   oldest_journal);
    }
    uii_add_bulk_output (uii, "  Current serial number: %lu\r\n",
                         database->serial_number);

    if (status == MIRROR) {
      uii_add_bulk_output (uii, "\r\nRemote Information:\r\n");
      uii_add_bulk_output (uii, "  Mirror host: %s:%d\r\n",
                           prefix_toa(database->mirror_prefix),
			   database->mirror_port);

      ret = get_remote_mirrorstatus(database->mirror_prefix, database->mirror_port);
      if (ret != 1) {
	uii_add_bulk_output (uii, "  *WARNING* Error getting status for remote database\r\n");
	uii_add_bulk_output (uii, "  *WARNING* See logfile for further info.\r\n", db_name);
      }

      else {
	switch (database->remote_mirrorstatus) {
	  case MIRRORSTATUS_UNDETERMINED:
	    uii_add_bulk_output (uii, "  Status undetermined.  See logfiles for further info.\r\n");
	    break;
	  case MIRRORSTATUS_FAILED:
	    uii_add_bulk_output (uii, "  Failed to connect to remote database.  See logfiles for further info.\r\n");
	    break;
	  case MIRRORSTATUS_UNSUPPORTED:
	    uii_add_bulk_output (uii, "  Remote status query unsupported.\r\n");
	    break;
	  case MIRRORSTATUS_UNAVAILABLE:
	    uii_add_bulk_output (uii, "  Remote status information unavailable.\r\n");
	    break;
	  case MIRRORSTATUS_YES:
	    uii_add_bulk_output (uii, "  Mirrorable.\r\n");
	    uii_add_bulk_output (uii, "  Oldest journal serial number: %d.\r\n",
	                         database->remote_oldestjournal);
	    uii_add_bulk_output (uii, "  Current serial number: %d.\r\n",
	                         database->remote_currentserial);
	    if (database->remote_lastexport > 0)
	      uii_add_bulk_output (uii, "  Last exported at serial number: %d.\r\n",
				   database->remote_lastexport);

	    if (database->serial_number < database->remote_oldestjournal) {
	      uii_add_bulk_output (uii, "  *WARNING* Remote oldest journal > our CURRENTSERIAL.\r\n");
	      uii_add_bulk_output (uii, "  *WARNING* Mirroring will fail.  Please reseed your database.\r\n");
	    }
	    if (database->serial_number > database->remote_currentserial) {
	      uii_add_bulk_output (uii, "  *WARNING* Remote CURRENTSERIAL < our CURRENTSERIAL.\r\n");
	      uii_add_bulk_output (uii, "  *WARNING* Mirroring will fail.  Please reseed your database.\r\n");
	      uii_add_bulk_output (uii, "  *WARNING* (Remote database corruption suspected.  Contact remote db-admin.)\r\n");
	    }
	    break;
	  case MIRRORSTATUS_NO:
	    uii_add_bulk_output (uii, "  Not Mirrorable.\r\n");
	    uii_add_bulk_output (uii, "  Current serial number: %d.\r\n",
	                         database->remote_currentserial);
	    if (database->remote_lastexport > 0)
	      uii_add_bulk_output (uii, "  Last exported at serial number: %d.\r\n",
				   database->remote_lastexport);
	    if (database->serial_number > database->remote_currentserial) {
	      uii_add_bulk_output (uii, "  *WARNING* Remote CURRENTSERIAL < our CURRENTSERIAL.\r\n");
	      uii_add_bulk_output (uii, "  *WARNING* (Remote database corruption suspected.  Contact remote db-admin.)\r\n");
	    }
	    break;
	  default:
	    uii_add_bulk_output (uii, "  *ERROR* Invalid value in databases remote_mirrorstatus field\r\n");
	    break;
	}
      }
    }
  }
  else {
    uii_add_bulk_output (uii, "Database %s does not exist.\r\n", db_name);
  }
}


syntax highlighted by Code2HTML, v. 0.9.1