#include "ipxtund.h"

/*
  Functions to resolve unknown routers. Unknown routers are identified by
  nicknames like "onno". Each configuration file besides "onno" itself have
  an entry "router ?onno". One of the routers acts as server by receiving
  a message from "onno", looking at the sender IP address (any UDP message
  contains it) and delivers back a "onno"-IP address pair to all routers.
  Now all routers knows "onno"s IP address.
  The server is determined at "onno" by a line "server servername onno".
  Due to this mechanism, the server itself have to have a known IP address.
*/

static void sigalrmhandler(int sig);

int router_connect(struct Local *local, struct Router *router_array,
                   int router_anz)
{
  int k;
  boolean servermode = FALSE;	/* true if address negotiaton is needed */
  boolean i_am_client = FALSE;	/* true if we are client, false if we server */
  
  /* check if we have a "server" configured */
  if (*local->server_name != '\0') {
    servermode = TRUE;
    i_am_client = TRUE;
  }
  /* check if a router is unknown */
  for (k = 0; k < router_anz; k++) {
    if (*router_array[k].name == '?') {
      servermode = TRUE;
      break;
    }
  }
  
  if (servermode) {
    if (i_am_client) {
      /* server mode, and I am a client */
      if (debuglevel > 0) {
	fprintf(log_fp,"client mode\n");
      }
      router_connect_client(local, router_array, router_anz);
    }
    else {
      /* server mode, and I am the server */
      if (debuglevel > 0) {
	fprintf(log_fp,"server mode\n");
      }
      router_connect_server(local, router_array, router_anz);
    }
  }
  else {
    if (debuglevel > 0) {
      fprintf(log_fp,"direct mode\n");
    }
  }

  return 0;
}


int router_connect_server(struct Local *local, struct Router *router_array,
                          int router_anz)
/* this is the server-side routine */
{
  int s_fd, k, n;
  u_short id_anz, found, *usp;
  u_long *ulp;
  size_t from_sin_len, len;
  char in_buf[128], out_buf[BUFSIZE], id[60], *r_name;
  struct sockaddr_in c_sin, from_sin;

  /* communication to clients take place by using the controll channel */
  s_fd = local->ctrl_fd;
  memset((void *)out_buf, '\0', BUFSIZE);
  id_anz = 0;
  found = 0;
  /* count unknown routers */
  for (k = 0; k < router_anz; k++) {
    if (*router_array[k].name == '?') {
      id_anz++;
    }
  }
  if (id_anz == 0) {
    if (debuglevel > 0) {
      fprintf(log_fp,"no unknown clients\n");
    }
    return 0;
  }
  if (debuglevel > 0) {
    fprintf(log_fp,"%u unknown clients\n", id_anz);
  }
  
  /* catch resolving messages from all unknown clients */
  printifverbose("waiting for clients to resolve addresses\n");
  do {
    memset((void *)in_buf, '\0', 128);
    from_sin_len = sizeof(from_sin);
    do {
      n = recvfrom(s_fd, in_buf, 128, 0, (struct sockaddr *) &from_sin,
		    &from_sin_len);
      if (n < 0) {
	errormsg(E_ALL,"router_connect_server","recvfrom: %m");
	sleep(10);
      }
    } while (n < 0);
    /* any resolving client message is a string build by concatenating
       "CLIENT" with the router id, e.g. for router "onno": "CLIENTonno"
       The IP address can be extracted from "from_sin" */
    if (strncmp(in_buf,"CLIENT", strlen("CLIENT")) != 0) {
      errormsg(E_ALL,"router_connect_server","got wrong answer (not CLIENT)");
      return -1;
    }
    strcpy(id, &in_buf[strlen("CLIENT")]);
    for (n = 0; n < router_anz; n++) {
      /* search unknown router entry and resolve it */
      r_name = router_array[n].name;
      if (*r_name == '?' && strcmp(&r_name[1], id) == 0) {
        printifverbose("router %s has IP address %s\n", id,
	               inet_ntoa(from_sin.sin_addr));
	if (debuglevel > 0) {
	  fprintf(log_fp,"router %s resolves to %s\n", id,
		  inet_ntoa(from_sin.sin_addr));
	}
	/* collect id / IP-address pairs into a buffer, buffer format is:
	   16 bytes for buffer header
	   60 bytes for id 0
	   4  bytes for IP address 0
	   60 bytes for id 1
	   4  bytes for IP address 1
	   ...
	*/
	strcpy(r_name, inet_ntoa(from_sin.sin_addr));
	strcpy(&out_buf[16+found*64], id);
	ulp = (u_long *) &out_buf[16+found*64+60];
	*ulp = from_sin.sin_addr.s_addr;
	found++;
	break;
      }
    }
  } while (found < id_anz);
  
  /* the 16 bytes sized buffer header has the format:
     "SERVER" in bytes 0-5
     no. of id / IP-address pairs as u_short in byte 8-9
   */
  len = 16 + id_anz * 64;
  strcpy(out_buf,"SERVER");
  usp = (u_short *) &out_buf[8];
  *usp = htons(id_anz);
  memset((void *) &c_sin, '\0', sizeof(c_sin));
  c_sin.sin_family = AF_INET;
  /* send buffer to all routers */
  for (k = 0; k < router_anz; k++) {
    if (get_inet_addr(&c_sin.sin_addr, router_array[k].name) < 0) {
      return -1;
    }
    c_sin.sin_port = htons(router_array[k].ctrl_port);
    if (sendto(s_fd, (void *)out_buf, len, 0, (struct sockaddr *) &c_sin,
	    sizeof(c_sin)) < 0) {
      errormsg(E_ALL,"router_connect_server","sendto: %m");
    }
    if (debuglevel > 0) {
      fprintf(log_fp,"resolved addresses delivered to client %s\n",
              inet_ntoa(c_sin.sin_addr));
    }
  }
  printifverbose("addresses sent to all routers\n");
  return 0;
}


int router_connect_client(struct Local *local, struct Router *router_array,
                          int router_anz)
/* this is the client-side routine */
{
  int k, n;
  u_short id_anz, *usp;
  size_t from_sin_len;
  char buf[BUFSIZE], id[60], *r_name;
  struct in_addr in_addr;
  struct sigaction act;
  struct sockaddr_in s_sin, from_sin;
  struct Router *server;

  /* check, if server is one of the routers */
  k = 0;
  while (k < router_anz &&
         strcmp(local->server_name, router_array[k].name) != 0) {
    k++;
  }
  if (k >= router_anz) {
    errormsg(E_ALL,"router_connect_client",
             "server %s not found (have to be one of the routers)",
	     local->server_name);
    return -1;
  }
  server = &router_array[k];
  
  /* build address to server (port is the control port) and build message
     to sent (format see above) */
  memset((void *) &s_sin, '\0', sizeof(s_sin));
  s_sin.sin_family = AF_INET;
  if (get_inet_addr(&s_sin.sin_addr, server->name) < 0) {
    return -1;
  }
  s_sin.sin_port = htons(server->ctrl_port);
  
  memset((void *)buf, '\0', BUFSIZE);
  strcpy(buf,"CLIENT");
  strcat(buf, local->hostid);
  /* timeout loop, message will be repeated if no answer reach us in time */
  if (sigsetjmp(sigalrmenv, (int)TRUE) == 0) {
    sigemptyset(&act.sa_mask);
    act.sa_handler = sigalrmhandler;
    act.sa_flags = 0;
    sigaction(SIGALRM, &act, NULL);
  }
  else {
    /* Timeout */
    alarm(0);
  }

  printifverbose("sending my IP address to server %s...\n", server->name);
  if (debuglevel > 0) {
    log("id %s sent to server %s\n", local->hostid, server->name);
  }
  do {
    /* send message to server */
    n = sendto(server->ctrl_fd, (void *)buf, strlen(buf)+1, 0,
	       (struct sockaddr *) &s_sin, sizeof(s_sin));
    if (n < 0) {
      errormsg(E_ALL,"router_connect_client","sendto: %m");
      sleep(10);
    }
  } while (n < 0);
  do {
    /* wait for answer until timeout or success */
    alarm(SERVERWAIT_INTERVALL);
    from_sin_len = sizeof(from_sin);
    n = recvfrom(local->ctrl_fd, buf, BUFSIZE, 0,
                 (struct sockaddr *) &from_sin, &from_sin_len);
    alarm(0);
    if (n < 0) {
      errormsg(E_ALL,"router_connect_client","recvfrom: %m");
      sleep(10);
    }
  } while (n < 0);
  
  /* check answer */
  if (strncmp(buf,"SERVER", 6) != 0) {
    errormsg(E_ALL,"router_connect_client","got wrong answer (not SERVER)");
    return -1;
  }
  if (debuglevel > 0) {
    log("received resolvering answer from server %s\n", server->name);
  }
  
  usp = (u_short *) &buf[8];
  id_anz = ntohs(*usp);
  /* resolve unknown routers */
  for (k = 0; k < id_anz; k++) {
    strcpy(id, &buf[16+k*64]);
    if (strcmp(id, local->hostid) == 0) {
      continue;
    }
    for (n = 0; n < router_anz; n++) {
      r_name = router_array[n].name;
      if (*r_name == '?' && strcmp(id, &r_name[1]) == 0) {
        in_addr.s_addr = (u_long)buf[16+k*64+60];
	printifverbose("router %s resolves to %s\n", id,
		       inet_ntoa(in_addr));
	if (debuglevel > 0) {
	  fprintf(log_fp,"router %s resolves to %s\n", id,
		  inet_ntoa(in_addr));
	}
	strcpy(r_name, inet_ntoa(in_addr));
	break;
      }
    }
    if (n >= router_anz) {
      errormsg(E_ALL,"router_connect_client","router id %s unknown\n", id);
      return -1;
    }
  }
  printifverbose("address resolution ok\n");
  return 0;
}


static void sigalrmhandler(int sig)
{
  siglongjmp(sigalrmenv, 1);
}
