#include "ipxtund.h"

FILE *log_fp;
int debuglevel, compresslevel;
boolean verbose;
time_t start_time = (time_t)0;
time_t current_time = (time_t)0;
sigjmp_buf sigenv, sigalrmenv;

static int convert_mac_str(u_char *mac, const char *str);
static void sighandler(int sig);
static int set_rfds_set(fd_set *rfds, const struct Config *config);

int main(int argc, char *argv[])
{
  int max_fd, tcp_fd, k, n, s;
  u_int maxage, err_cnt;
  char conffile[PATH_MAX+1], logfile[PATH_MAX+1], senderfile[PATH_MAX+1], c;
  const char *usage = "usage: ipxtund [-f conffile] [-l logfile] [-d dlevel] [-c clevel] [-m maxage] [-s senderfile] [-v]";
  boolean more_data, sender_ok;
  struct Sdb *peer_db, *sender_db;
  fd_set rfds;
  time_t time_intervall;
  struct timeval select_timeout;
  struct sigaction act;
  struct Config config;
  struct Data_packet packet;
  extern int optind, opterr, optopt;
  extern char *optarg;

  /* setting default values, reading arguments */
  strcpy(conffile, DEFAULT_CONFFILE);
  strcpy(logfile, DEFAULT_LOGFILE);
  *senderfile = '\0';
  debuglevel = 0;
  compresslevel = Z_DEFAULT_COMPRESSION;
  maxage = DEFAULT_MAXAGE;
  verbose = FALSE;
  opterr = FALSE;
  while ((c = getopt(argc, argv, "f:l:d:c:m:s:vh")) != EOF) {
    switch (c) {
    case 'f':
      strncpy(conffile, optarg, PATH_MAX);
      break;
    case 'l':
      strncpy(logfile, optarg, PATH_MAX);
      break;
    case 'd':
      debuglevel = atoi(optarg);
      break;
    case 'c':
      compresslevel = atoi(optarg);
      break;
    case 'm':
      maxage = atoi(optarg);
      break;
    case 's':
      strncpy(senderfile, optarg, PATH_MAX);
      break;
    case 'v':
      verbose = TRUE;
      break;
    case 'h':
      fprintf(stderr,"%s\n", usage);
      return 0;
      break;
    case '?':
      fprintf(stderr,"unrecognized option: -%c\n", optopt);
      opterr = TRUE;
    }
  }
  if (opterr) {
    fprintf(stderr,"%s\n", usage);
    return -1;
  }
  
  /* open log file */
  if ((log_fp = fopen(logfile,"w")) == NULL) {
    fprintf(stderr,"fopen %s: %s\n", logfile, strerror(errno));
    return -1;
  }
  if (chmod(logfile, 0644) < 0) {
    fprintf(stderr,"chmod %s: %s\n", logfile, strerror(errno));
    return -1;
  }
/*
  if (setvbuf(log_fp, NULL, _IOLBF, 0) != 0) {
    fprintf(stderr,"setvbuf %s: %s\n", logfile, strerror(errno));
    return -1;
  }
*/
  
  if (debuglevel > 0) {
    log("ipxtund started\n\n");
  }
  
  /* install signal handler */
  if (sigsetjmp(sigenv, (int)FALSE) == 0) {
    sigemptyset(&act.sa_mask);
    act.sa_handler = sighandler;
    act.sa_flags = 0;
    sigaction(SIGINT, &act, NULL);
    sigaction(SIGHUP, &act, NULL);
    sigaction(SIGQUIT, &act, NULL);
  }
  else {
    goto end_of_prog;
  }

  /* init session */
  if (init(&config, &peer_db, conffile) < 0) {
    errormsg(E_ALL,"main","TERMINATED: initialization failed");
    return -1;
  }
  fflush(log_fp);

  /* build sender data base */
  if (init_sender_db(&sender_db, senderfile) < 0) {
    return -1;
  }
  fflush(log_fp);

  printifverbose("ipxtund is ready\n");
  start_time = 0;
  err_cnt = 0;
  for (;;) {
    fflush(log_fp);
    if (err_cnt > 0) {
      err_cnt--;
    }
#ifdef CHECK_PEER_INTERVALL
    current_time = time((time_t *)0);
    check_peer_timeout(peer_db);
#endif
    /* loop for reading and delivering packets,
       reading and delivering commands */
    select_timeout.tv_sec = 0;
    select_timeout.tv_usec = 1000 * MAX_SEND_DELAY;
    max_fd = set_rfds_set(&rfds, &config);
    n = select(max_fd+1, &rfds, NULL, NULL, &select_timeout);
    current_time = time((time_t *)0);
    if (n > 0) {
      if (start_time == 0) {
        /* time runs if first packet arrives */
        start_time = time((time_t *)0);
      }
      for (k = 0; k < config.ether_anz; k++) {
        /* check ether devices for data */
        for (s = 0; s < config.ether[k].fd_anz; s++) {
  	  if (FD_ISSET(config.ether[k].fd[s], &rfds)) {
	    more_data = FALSE;
	    do {
	      /* read data from ether device */
  	      n = read_ipx_from_ether(&packet, &config.ether[k], s,
	                              &more_data);
	      if (n > 0) {
		sender_ok = filter_sender(sender_db, &packet);
		if (sender_ok) {
		  n = check_ether_age(&packet, &config.ether[k], maxage);
		}
		if (sender_ok && n == 0) {
		  /* store sender into database */
		  n = register_peer(peer_db, &packet, config.ether,
				    config.ether_anz, config.router,
				    config.router_anz);
		  if (n == 0) {
		    /* send data to its destinations */
		    forward_ipx_packet(&packet, config.ether, config.ether_anz,
				       config.router, config.router_anz,
				       peer_db);
		  }
		}
              }
	    } while (more_data);
	  }
	}
      }
      if (FD_ISSET(config.local.udp_fd, &rfds)) {
        /* check UDP socket for data */
        more_data = FALSE;
	do {
	  if (read_ipx_from_udp_tcp(&packet, &config.local, (struct Router *)0,
	      &more_data) > 0) {
	    n = register_peer(peer_db, &packet, config.ether, config.ether_anz,
			      config.router, config.router_anz);
	    if (n == 0) {
	      forward_ipx_packet(&packet, config.ether, config.ether_anz,
				 config.router, config.router_anz, peer_db);
	    }
	  }
	} while (more_data);
      }
#ifndef NO_TCP
      for (k = 0; k < config.router_anz; k++) {
        /* check TCP sockets for data */
        tcp_fd = config.router[k].tcp_fd;
        if ( tcp_fd != config.router[k].udp_fd && FD_ISSET(tcp_fd, &rfds)) {
          more_data = FALSE;
	  do {
	    if (read_ipx_from_udp_tcp(&packet, &config.local,
	                    &config.router[k], &more_data) > 0) {
	      n = register_peer(peer_db, &packet, config.ether,
	                        config.ether_anz, config.router,
			        config.router_anz);
	      if (n == 0) {
		forward_ipx_packet(&packet, config.ether, config.ether_anz,
				   config.router, config.router_anz, peer_db);
	      }
	    }
	  } while (more_data);
	}
      }
      if (FD_ISSET(config.local.ctrl_fd, &rfds)) {
        /* check command socket for command data */
        process_tcp_request(&config.local, config.router, config.router_anz);
      }
      if (config.local.tcp.tcp_listen_port > 0) {
        for (k = 0; k < config.router_anz; k++) {
	  if (config.router[k].com == com_udp) {
            /* check for routers without TCP connection and sent a request to
	       create one if necessary */
	    send_tcp_connect_request(&config.local, &config.router[k]);
	  }
	  else if (config.router[k].com == com_tcp) {
	    /* check existing TCP connection */
  	    check_tcp_connection(&config.router[k]);
	  }
	}
      }
#endif
    }
    else if (n < 0 && errno != EINTR) {
      /* EINTR is caused by SIGPROF */
      /* in case of an error, e.g. a dead TCP socket descriptor */
      errormsg(E_LOG,"main","select: %m");
      for (k = 0; k < config.router_anz; k++) {
        /* close all TCP sockets, somtime this helps */
        if (config.router[k].com == com_tcp) {
	  close_tcp_connection(&config.router[k]);
	}
      }
      err_cnt += 10;
      if (err_cnt > MAX_ERRORS) {
        goto end_of_prog;
      }
    }
    /* flush collected data towards routers */
    for (k = 0; k < config.router_anz; k++) {
      flush_to_udp_tcp(&config.router[k]);
    }
  }

end_of_prog:
  /* log statistics, close ether devices and exit */
  if (start_time > 0) {
    time_intervall = time((time_t *)0) - start_time;
  }
  else {
    time_intervall = (time_t)0;
  }
  log_statistics(time_intervall, config.ether, config.ether_anz,
		  config.router, config.router_anz, &config.local);
  for (k = 0; k < config.ether_anz; k++) {
    close_ether(&config.ether[k]);
  }
  for (k = 0; k < config.router_anz; k++) {
    close_tcp_connection(&config.router[k]);
  }
  close(config.local.udp_fd);
  close(config.local.ctrl_fd);
  close(config.local.tcp.tcp_listen_fd);
  if (debuglevel > 0) {
    fprintf(log_fp,"\n");
    log("ipxtund finished\n");
  }
  fclose(log_fp);
  return 0;
}


int init(struct Config *config, struct Sdb **peer_db, const char *conffile)
{
  int k;
  struct sockaddr_in saddr;

  if (geteuid() != 0) {
    errormsg(E_ALL,"init","must be root");
    return -1;
  }
  /* read and interpret configration file */
  if (read_config_file(config, conffile) < 0) {
    return -1;
  }

  /* create all sockets */
  memset((void *) &saddr, '\0', sizeof(saddr));
  saddr.sin_family = AF_INET;
  saddr.sin_addr.s_addr = htons(INADDR_ANY);

  /* socket for UDP data (read / write) */
  config->local.udp_fd = socket(AF_INET, SOCK_DGRAM, 0);
  if (config->local.udp_fd < 0) {
    errormsg(E_ALL,"init","socket(udp): %m");
    return -1;
  }
  saddr.sin_port = htons(config->local.udp_port);
  if (bind(config->local.udp_fd, (struct sockaddr *) &saddr, sizeof(saddr)) <
    0) {
    errormsg(E_ALL,"init","bind(udp): %m");
    return -1;
  }

  /* socket for control messages (read / write) */
  config->local.ctrl_fd = socket(AF_INET, SOCK_DGRAM, 0);
  if (config->local.ctrl_fd < 0) {
    errormsg(E_ALL,"init","socket(ctrl udp): %m");
    return -1;
  }
  saddr.sin_port = htons(config->local.ctrl_port);
  if (bind(config->local.ctrl_fd, (struct sockaddr *) &saddr, sizeof(saddr)) <
    0) {
    errormsg(E_ALL,"init","bind(ctrl udp): %m");
    return -1;
  }
  
  /* socket for accepting TCP connections, i.e. only used by accept() to
     create a new socket which will be used for TCP data transfer */
  config->local.tcp.tcp_listen_fd = socket(AF_INET, SOCK_STREAM, 0);
  if (config->local.tcp.tcp_listen_fd < 0) {
    errormsg(E_ALL,"init","socket(tcp): %m");
    return -1;
  }
  saddr.sin_port = htons(config->local.tcp.tcp_listen_port);
  if (bind(config->local.tcp.tcp_listen_fd, (struct sockaddr *) &saddr,
           sizeof(saddr)) < 0) {
    errormsg(E_ALL,"init","bind(tcp): %m");
    return -1;
  }
  if (listen(config->local.tcp.tcp_listen_fd, 5) < 0) {
    errormsg(E_ALL,"init","listen: %m");
    return -1;
  }

  /* open all ether devices */
  for (k = 0; k < config->ether_anz; k++) {
    if (open_ether(&config->ether[k]) < 0) {
      return -1;
    }
    config->ether[k].packet_db = sdb_open(0, SDB_TOREK_100);
    if (config->ether[k].packet_db == NULL) {
      errormsg(E_ALL,"init","dbopen(packet_db): %m");
      return -1;
    }
  }
  
  /* some timers uses an random timeout to avoid repeated deadlocks */
  srandom((time_t)0);

  /* set timers for TCP connection requests */
  config->local.tcp.req_timer = time((time_t)0) + random() % TCP_TIMEWAIT;
  config->local.tcp.ack_timer = 0;

  clear_stat(&config->local.stat);

  /* the UDP sockets are used for writing to routers, too */
  for (k = 0; k < config->router_anz; k++) {
    config->router[k].udp_fd = config->local.udp_fd;
    config->router[k].ctrl_fd = config->local.ctrl_fd;
    /* tcp_fd == udp_fd means no tcp_fd (no TCP connection) */
    config->router[k].tcp_fd = config->router[k].udp_fd;
  }

#ifndef NO_TCP
  /* negotiate IP addresses with unknown routers */
  if (router_connect(&config->local, config->router,
                     config->router_anz) < 0) {
    errormsg(E_ALL,"init","address negotioation with routers failed");
    return -1;
  }
#endif
  
  if (MAX_CACHE >= 255) {
    errormsg(E_ALL,"init","cache size too big");
    return -1;
  }
  if (MAX_HDR_CACHE > MAX_CACHE) {
    errormsg(E_ALL,"init","header cache size too big");
    return -1;
  }
  
  /* initialize router data */
  for (k = 0; k < config->router_anz; k++) {
    config->router[k].com = com_udp;
    config->router[k].flush = FALSE;
    config->router[k].last_send_time = 0;
    config->router[k].dummycnt = 0;
    config->router[k].ping.pending = FALSE;
    config->router[k].ping.pending_id = 0;
    config->router[k].ping.last_ping_time = 0;
    config->router[k].ping.roundtrip_time = 0;
    config->router[k].c_buf.anz = 0;
    config->router[k].r_hdr_cache.max_anz = MAX_HDR_CACHE;
    config->router[k].s_hdr_cache.max_anz = MAX_HDR_CACHE;
    clear_cache(&config->router[k].r_hdr_cache);
    clear_cache(&config->router[k].s_hdr_cache);

    memset((void *) &(config->router[k].sin), '\0',
           sizeof(config->router[k].sin));
    config->router[k].sin.sin_family = AF_INET;
    if (get_inet_addr(&config->router[k].sin.sin_addr,
        config->router[k].name) < 0) {
      return -1;
    }
    config->router[k].sin.sin_port = htons(config->router[k].udp_port);
    
    clear_stat(&config->router[k].stat);
  }
  
  /* open database for peers */
  if ((*peer_db = sdb_open(0, SDB_PHONG)) == NULL) {
    errormsg(E_ALL,"init","sdb_open(peer_db): %m");
    return -1;
  }
  
  /* write configuration to logfile */
  log_configuration(config);
  return 0;
}


int register_peer(struct Sdb *peer_db, const struct Data_packet *packet,
                  struct Ether ether_array[], const int ether_anz,
                  struct Router router_array[], const int router_anz)
{
  int n, k;
  u_int frametype;
  u_long inaddr;
  u_char ipx_source_node[6];
  struct Sdb_datum dbkey, dbdata;
  struct Peer peer;
  struct Ether *etherptr;
  
  /* look up database for senders address */
  frametype = 0;
  get_ipx_source_node(ipx_source_node, packet->data);
  dbkey.data = ipx_source_node;
  dbkey.size = 6;
  n = peer_db->get(peer_db, &dbkey, &dbdata, SDB_DATA_REFERENCE);
  if (n < 0) {
    errormsg(E_LOG,"register_peer","DB get: %m");
    return -1;
  }
  if (n == 0) {
    /* sender is already known */
    if (debuglevel > 2) {
      fprintf(log_fp,"peer already registered\n");
    }
    /* update time of last received packet */
    memcpy(&peer, dbdata.data, dbdata.size);
#ifdef CHECK_PEER_INTERVALL
    peer.last_time = current_time;
    dbdata.data = (char *)&peer;
    if (peer_db->put(peer_db, &dbkey, &dbdata, 0) < 0) {
      errormsg(E_LOG,"register_peer","DB put: %m");
      return -1;
    }
#endif
    /* update frametype */
    if (packet->from_com == com_ether) {
      etherptr = &ether_array[peer.fw_idx]; 
      if (etherptr->s_autoframetype &&
          etherptr->s_frametype != packet->from_frametype) {
        etherptr->s_frametype = packet->from_frametype;
        if (debuglevel > 0) {
          fprintf(log_fp,"autoconfigured port %s changed frametype to ",
                  etherptr->name);
          log_frametypes(packet->from_frametype);
          fprintf(log_fp,"\n");
        }
      }
    }
    return 0;
  }

  /* sender is unknown */
  if (packet->from_com == com_ether) {
    /* packet comes from an ethernet device */
    /* check if packet arrives from an configured ethernet device */
    for (k = 0; k < ether_anz &&
                    packet->from_fd != ether_array[k].fd[0]; k++) {
    }
    if (k >= ether_anz) {
      errormsg(E_LOG,"register_peer",
               "packet skipped: got packet from unknown ether");
      return 1;
    }
    /* source is ok, define a Peer struct accordingly */
    peer.com = com_ether;
    peer.fw_idx = k;
    /* if ether device is configured "automatic" (a), set frametype for
       sending to the received frametype */
    if (ether_array[peer.fw_idx].s_frametype == ETHER_AUTO) {
      frametype = packet->from_frametype;
      if ( ! is_realframetype(frametype)) {
        errormsg(E_LOG,"register_peer",
                 "packet skipped: unknown frametype");
        return 1;
      }
      ether_array[peer.fw_idx].s_frametype = frametype;
    }
  }
  else if (packet->from_com == com_udp) {
    /* packet comes from UDP data interface, i.e. from a router */
    /* check if packet arrives from an configured router */
    inaddr = packet->from_sin.sin_addr.s_addr;
    for (k=0; k < router_anz &&
         inaddr != router_array[k].sin.sin_addr.s_addr; k++) {
    }
    if (k >= router_anz) {
      errormsg(E_LOG,"register_peer",
               "packet skipped: got UDP packet from unconfigured router");
      return 1;
    }
    /* router is ok, define a Peer struct accordingly */
    peer.com = com_udp_tcp;
    peer.fw_idx = k;
    /* if ether device is configured "automatic" (a), set frametype for
       sending to the received frametype */
    if (router_array[peer.fw_idx].s_frametype == ETHER_AUTO) {
      frametype = packet->from_frametype;
      if ( ! is_realframetype(frametype)) {
        errormsg(E_LOG,"register_peer",
                 "packet skipped: unknown frametype");
        return 1;
      }
      router_array[peer.fw_idx].s_frametype = frametype;
    }
  }
  else if (packet->from_com == com_tcp) {
    /* packet arrives from a TCP connection */
    /* this check is a bit unnecessary because a TCP connection to an
       unconfigured router should not exist, but why not test twice */
    for (k = 0; k < router_anz &&
                    packet->from_fd != router_array[k].tcp_fd; k++) {
    }
    if (k >= router_anz) {
      errormsg(E_LOG,"register_peer",
               "packet skipped: got TCP packet from unconfigured router");
      return 1;
    }
    /* router is ok, define a Peer struct accordingly */
    peer.com = com_udp_tcp;
    peer.fw_idx = k;
    /* if ether device is configured "automatic" (a), set frametype for
       sending to the received frametype (should not happen) */
    if (router_array[peer.fw_idx].s_frametype == ETHER_AUTO) {
      frametype = packet->from_frametype;
      if ( ! is_realframetype(frametype)) {
        errormsg(E_LOG,"register_peer",
                 "packet skipped: unknown frametype");
        return 1;
      }
      router_array[peer.fw_idx].s_frametype = frametype;
    }
  }
  else {
    errormsg(E_LOG,"register_peer",
             "packet skipped: got packet of unknown com type");
    return 1;
  }
  /* store peer into database */
  peer.last_time = current_time;
  dbdata.data = (char *)&peer;
  dbdata.size = sizeof(peer);
  n = peer_db->put(peer_db, &dbkey, &dbdata, 0);
  if (n < 0) {
    errormsg(E_LOG,"register_peer","DB put: %m");
    return -1;
  }

  printifverbose("new peer registered: %02x:%02x:%02x:%02x:%02x:%02x -> ",
                 ipx_source_node[0], ipx_source_node[1], ipx_source_node[2],
		 ipx_source_node[3], ipx_source_node[4], ipx_source_node[5]);
  if (peer.com == com_ether) {
    printifverbose("%s [Ethernet]\n", ether_array[peer.fw_idx].name);
  }
  else if (peer.com == com_udp_tcp) {
    printifverbose("%s [UDP/TCP]\n", router_array[peer.fw_idx].name);
  }                 
  if (debuglevel > 0) {
    fprintf(log_fp,"new peer registered: ");
    fprint_as_hex(log_fp, ipx_source_node, 6);
    if (peer.com == com_ether) {
      fprintf(log_fp,"  -> %s [Ethernet]\n", ether_array[peer.fw_idx].name);
    }
    else if (peer.com == com_udp_tcp) {
      fprintf(log_fp," -> %s [UDP/TCP]\n", router_array[peer.fw_idx].name);
    }
    if (frametype != 0) {
      fprintf(log_fp,"registration sets autoconfigured port to frametype ");
      log_frametypes(frametype);
      fprintf(log_fp,"\n");
    }
  }

  return 0;
}


int forward_ipx_packet(struct Data_packet *packet,
		       struct Ether *ether_array, const int ether_anz,
                       struct Router *router_array, const int router_anz,
		       struct Sdb *peer_db)
{
  int k, n;
  char ipx_dest_node[6];
  struct Sdb_datum dbkey, dbdata;
  struct Peer peer;
  boolean broadcast = TRUE;
  
  /* increment hop count and test if it is too high */
  if (! test_and_increment_ipx_ctl(packet->data)) {
    errormsg(E_LOG,"forward_ipx_packet","packet dropped: too many hops");
    return -1;
  }

  /* determine type of delivery */
  /* address == broadcast -> deliver by broadcast */
  get_ipx_dest_node(ipx_dest_node, packet->data);
  if (memcmp(ipx_dest_node, IPX_BROADCAST_NODE, 6) != 0) {
    /* address != broadcast */
    dbkey.data = ipx_dest_node;
    dbkey.size = 6;
    n = peer_db->get(peer_db, &dbkey, &dbdata, SDB_DATA_REFERENCE);
    if (n < 0) {
      errormsg(E_LOG,"forward_ipx_packet","DB get: %m");
      return -1;
    }
    if (n == 0) {
      /* address known (registerd by register_peer) -> no broadcast */
      broadcast = FALSE;
      memcpy(&peer, dbdata.data, dbdata.size);
    }
    /* n > 1 means not found / registered -> broadcast */
  }
  
#ifndef LOCAL_UDP_TESTLOOP
  if (! broadcast) {
    /* no broadcast because adress peer is known */
    if (peer.com == com_ether) {
      /* ether to peer from DB */
      if (ether_array[peer.fw_idx].fd[0] == packet->from_fd) {
	if (debuglevel > 1) {
	  fprintf(log_fp,"no transfer because peer is at same ethernet\n");
	}
      }
      else {
	if (debuglevel > 1) {
	  fprintf(log_fp,"single transfer by ether (%s)\n",
		  ether_array[peer.fw_idx].name);
	}
        write_ipx_to_ether(packet, &ether_array[peer.fw_idx]);
      }
    }
    else {
      /* udp/tcp to peer from DB */
      if (debuglevel > 1) {
        fprintf(log_fp,"single transfer by udp/tcp to router %s\n",
	                router_array[peer.fw_idx].name);
      }
      write_ipx_to_udp_tcp(packet, &router_array[peer.fw_idx]);
    }
  }
  else {
    /* broadcast because address peer is unknown or address is broadcast itself
       */
    if (packet->from_com == com_ether) {
      /* udp/tcp to all routers */
      if (debuglevel > 1) {
        fprintf(log_fp,"broadcast to all router by udp/tcp:\n");
      }
      for (k = 0; k < router_anz; k++) {
        if (debuglevel > 2) {
          fprintf(log_fp,"to %s\n", router_array[k].name);
	}
        write_ipx_to_udp_tcp(packet, &router_array[k]);
      }
      /* ether to all ether_fd != from_fd */
      if (debuglevel > 2) {
        fprintf(log_fp,
	        "broadcast to all ether interfaces besides the receiver:\n");
      }
      for (k = 0; k < ether_anz; k++) {
        if (ether_array[k].fd[0] != packet->from_fd) {
          if (debuglevel > 2) {
	    fprintf(log_fp,"to %s\n", ether_array[k].name);
	  }
	  write_ipx_to_ether(packet, &ether_array[k]);
	}
      }
    }
    else { /* packet->from_com != com_ether */
      /* ether to all ether_fd */
      if (debuglevel > 1) {
        fprintf(log_fp,"broadcast to all ether interfaces:\n");
      }
      for (k = 0; k < ether_anz; k++) {
        if (debuglevel > 2) {
          fprintf(log_fp,"to %s\n", ether_array[k].name);
	}
        write_ipx_to_ether(packet, &ether_array[k]);
      }
    }
  }
#else
  if (debuglevel > 1) {
    fprintf(log_fp,"LOCAL_TESTLOOP\n");
  }
  if (packet->from_com == com_ether) {
    /* ATTENTION: router_array[0] have to point to local host */
    /* ATTENTION: TCP have to be disabled */
    if (debuglevel > 1) {
      fprintf(log_fp,"forwarding to myself by udp/tcp\n");
    }
    write_ipx_to_udp_tcp(packet, &router_array[0]);
  }
  else {
    if (! broadcast) {
      if (debuglevel > 1) {
	fprintf(log_fp,"single transfer by ether (%s)\n",
		ether_array[peer.fw_idx].name);
      }
      write_ipx_to_ether(packet, &ether_array[peer.fw_idx]);
    }
    else {
      get_ipx_source_node(ipx_dest_node, packet->data);
      dbkey.data = ipx_dest_node;
      dbkey.size = 6;
      n = peer_db->get(peer_db, &dbkey, &dbdata, SDB_DATA_REFERENCE);
      if (n < 0) {
        errormsg(E_LOG,"forward_ipx_packet","DB get2: %m");
        return -1;
      }
      if (n > 0) {
        errormsg(E_LOG,"forward_ipx_packet","cannot found source peer");
        return -1;
      }
      memcpy(&peer, dbdata.data, dbdata.size);
      if (peer.com != com_ether) {
        errormsg(E_LOG,"forward_ipx_packet","source peer not at ether");
        return -1;
      }
      n = ether_array[peer.fw_idx].fd[0];
      if (debuglevel > 1) {
        fprintf(log_fp,
	        "broadcast to all ether interfaces besides the receiver:\n");
      }
      for (k = 0; k < ether_anz; k++) {
        if (ether_array[k].fd[0] != n) {
          if (debuglevel > 2) {
	    fprintf(log_fp,"to %s\n", ether_array[k].name);
	  }
	  write_ipx_to_ether(packet, &ether_array[k]);
	}
      }      
    }
  }
#endif
  return 0;
}


static int set_rfds_set(fd_set *rfds, const struct Config *config)
{
  int max_fd, k, s;
  
  FD_ZERO(rfds);
  FD_SET(config->local.udp_fd, rfds);
  max_fd = config->local.udp_fd;
#ifndef NO_TCP
  FD_SET(config->local.ctrl_fd, rfds);
  if (config->local.ctrl_fd > max_fd) {
    max_fd = config->local.ctrl_fd;
  }
#endif
  for (k = 0; k < config->ether_anz; k++) {
    for (s = 0; s < config->ether[k].fd_anz; s++) {
      FD_SET(config->ether[k].fd[s], rfds);
      if (config->ether[k].fd[s] > max_fd) {
	max_fd = config->ether[k].fd[s];
      }
    }
  }
#ifndef NO_TCP
  for (k = 0; k < config->router_anz; k++) {
    FD_SET(config->router[k].tcp_fd, rfds);
    if (config->router[k].tcp_fd > max_fd) {
      max_fd = config->router[k].tcp_fd;
    }
  }
#endif
  return max_fd;
}


int check_peer_timeout(struct Sdb *peer_db)
{
  /*
    remove outdated peers from internal database
  */
#ifdef CHECK_PEER_INTERVALL
  int n;
  u_int flags;
  u_char ipx_source_node[6];
  static time_t check_time = (time_t)0;
  struct Sdb_datum dbkey, dbdata;
  struct Peer peer;

  if (CHECK_PEER_INTERVALL == 0) {
    return 0;
  }

  if (current_time > check_time) {
    check_time = current_time + CHECK_PEER_INTERVALL;
    flags = SDB_KEY_REFERENCE | SDB_DATA_REFERENCE | SDB_FIRST;
    do {
      n = peer_db->seq(peer_db, &dbkey, &dbdata, flags);
      if (n < 0) {
	errormsg(E_LOG,"check_peer_timeout","seq: %m");
	return -1;
      }
      if (n == 0) {
        memcpy(&peer, dbdata.data, dbdata.size);
        if (peer.last_time + CHECK_PEER_INTERVALL < current_time) {
	  memcpy(ipx_source_node, dbkey.data, 6);
	  printifverbose("peer removed: %02x:%02x:%02x:%02x:%02x:%02x\n",
		 ipx_source_node[0], ipx_source_node[1], ipx_source_node[2],
                 ipx_source_node[3], ipx_source_node[4], ipx_source_node[5]);
	  if (peer_db->del(peer_db, &dbkey, 0) < 0) {
	    errormsg(E_LOG,"check_peer_timeout","del: %m");
	    return -1;
	  }
	  if (debuglevel > 0) {
	    fprintf(log_fp,"peer removed: ");
	    fprint_as_hex_nl(log_fp, ipx_source_node, 6);
	  }
	  /* we need to restart, because db has a bug */
	  flags = SDB_KEY_REFERENCE | SDB_DATA_REFERENCE | SDB_FIRST;
        }
        else {
	  flags = SDB_KEY_REFERENCE | SDB_DATA_REFERENCE;
        }
      }
    } while (n == 0);
  }
#endif
  return 0;
}


int init_sender_db(struct Sdb **sender_db, const char *senderfile)
{
  u_short line_no;
  char line[MAXLINE];
  u_char mac[6];
  struct Sdb_datum dbkey, dbdata;
  FILE *fp;

  *sender_db = NULL;
  if (*senderfile == '\0')  return 0;
  if ((fp = fopen(senderfile, "r")) == NULL) {
    errormsg(E_ALL,"init_sender_db","fopen %s: %m", senderfile);
    return -1;
  }
  *sender_db = sdb_open(0, SDB_PHONG);
  if (*sender_db == NULL) {
    errormsg(E_ALL,"init_sender_db","sdb_open(sender_db): %m");
    return -1;
  }
  line_no = 0;
  while (fgets(line, MAXLINE, fp)) {
    line_no++;
    if (*line == '#' || isspace(*line) || *line == '\n') {
      continue;
    }
    if (convert_mac_str(mac, line) < 0) {
      errormsg(E_ALL,"init_sender_db","line %d: wrong MAC syntax", line_no);
      return -1;
    }
    dbkey.data = mac;
    dbkey.size = 6;
    dbdata.data = "y";
    dbdata.size = 1;
    if ((*sender_db)->put(*sender_db, &dbkey, &dbdata, 0) < 0) {
      errormsg(E_ALL,"init_sender_db","line %d: DB put: %m");
      return -1;
    }
  }
  fclose(fp);
  fprintf(log_fp,"sender MAC list %s recognized\n", senderfile);
  return 0;
}


static int convert_mac_str(u_char *mac, const char *str)
{
  int k;
  u_int uc;
  const u_char *cp;
  
  cp = str;
  if (*cp != '[') {
    return -1;
  }
  for (k=0; k < 6; k++) {
    cp++;
    sscanf(cp,"%2x", (u_int *) &uc);
    while (*cp != ':' && *cp != ']' && *cp) {
      cp++;
    }
    mac[k] = uc;
  }
  if (*cp != ']') {
    return -1;
  }
  return 0;
}


static void sighandler(int sig)
{
  siglongjmp(sigenv,1);
}
