/* Josh Pieper, (c) 2000 */

/* This file is distributed under the GPL, see file COPYING for details */

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#ifndef WIN32
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>
#include <sys/time.h>
#include <netdb.h>
#endif
#include <errno.h>
#include <string.h>
#include <time.h>
#include <pthread.h>
#include <regex.h>

#include "cli_input.h"
#include "gnut_lib.h"
#include "gnut_threads.h"
#include "gnut_connection.h"
#include "Gnut_Queue.h"
#include "gnut_net.h"
#include "gnut_ui.h"
#include "protocol.h"
#include "route.h"
#include "share.h"
#include "conf.h"
#include "host.h"
#include "monitor.h"
#include "query.h"
#include "cache.h"
#include "gnut.h"
#include "blacklist.h"

#ifdef GNUT_HTTP_SEARCH
int gnut_http_result_append(uchar ip[4], uchar port[2], int ref, int speed,
							int size, char *name, uchar guid[16]);
#endif

/* this mutex protects changes to the connection list */
#ifndef PTHREADS_DRAFT4
pthread_mutex_t gc_list_mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_t send_mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_t query_packet_mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_t qreplies_mutex = PTHREAD_MUTEX_INITIALIZER;
#else
pthread_mutex_t gc_list_mutex;
pthread_mutex_t send_mutex;
pthread_mutex_t query_packet_mutex;
pthread_mutex_t qreplies_mutex;
#endif

/* linked list of all connections */
Gnut_List *gc_list=0;

int num_queries=0;
int num_responses=0;

int num_messages=0;
int num_sent=0;

uint64 received_bytes=0;
uint64 sent_bytes=0;
uint64 blacklisted=0;

void free_gc(gcs **x, int bugnum)
{
  yfree((void **) x, bugnum);
}

int gnut_connection_num()
{
  return gnut_list_size(gc_list);
}

int gnut_connection_net_totals(int *messages, int *sent,
  uint64 *rcv_bytes, uint64 *sbytes, uint64 *blacklisted)
{
  *messages=num_messages;
  *sent=num_sent;
  
  gd_s(2, "received_bytes=");
  gd_lu(2, received_bytes);
  gd_s(2, " sent_bytes=");
  gd_lu(2, sent_bytes);
  gd_s(2, "\n");

  *rcv_bytes=received_bytes;
  *sbytes=sent_bytes;
  *blacklisted = gnut_blacklist_totals();
  return 0;
}

int gnut_connection_query_totals(int *queries,int *responses)
{
  *queries=num_queries;
  *responses=num_responses;
  return 0;
}

int gnut_connection_find_ptr(gcs *gc)
{
  Gnut_List *gltmp;
  gcs *mgc;

  for (gltmp=gc_list;gltmp;gltmp=gltmp->next) {
    mgc=gltmp->data;
    if (mgc==gc) {
      return 0;
    }
  }
  return -1;
}

/* int gnut_connection_find(uchar ip[4], uint16 port)
 *
 * Tests to see if the given IP/port is already on the active
 * connections list
 *
 * returns 0 if a connection is found to the listed ip and port
 * returns -1 otherwise
 */
int gnut_connection_find(uchar ip[4], uint16 port)
{
  Gnut_List *gltmp;
  gcs *gc;

  for (gltmp=gc_list; gltmp; gltmp=gltmp->next) {
    gc=gltmp->data;
    if ((memcmp(ip, gc->ip.b, 4)==0) && (gc->port==port)) {
      return 0;
    }
  }

  return -1;
}

int send_to_one(gcs *gc, gnutella_packet *gpa)
{
  gnutella_packet *ngp;
 
  gd_s(3, "send_to_one entering (gc=");
  gd_p(3, gc);
  gd_s(3, ", gpa=");
  gd_p(3, gpa);
  gd_s(3, ")\n");
  
  pthread_mutex_lock(&gc->out_queue_mutex);

  gd_s(3, "send_to_one  done trying mutex\n");
  
  gd_s(3, "send_to_one  adding to packet queue\n");
  ngp=gp_dup(gpa);
  gnut_queue_insert(gc->out_queue, ngp);
  
  gd_s(3, "send_to_one  unlocking mutex\n");
  pthread_mutex_unlock(&gc->out_queue_mutex);

  if (gnut_lib_debug>=2) {
    gd_s(2,"send_to_one I just sent this packet:\n");
    gp_print(gpa);
  }
  
  gd_s(3,"send_to_one  returning success\n");
  return 0;
}

int send_to_all(gnutella_packet *gpa)
{
  Gnut_List *gltemp;
  gnutella_packet *ngp;
  gcs *gc;
  
  gd_s(3, "send_to_all entering\n");

  /*  pthread_mutex_lock(&gc_list_mutex); */
  
  for (gltemp=gc_list;gltemp;gltemp=gltemp->next) {
    gc=gltemp->data;
    
    if (gc->cstate==STATE_CONNECTED) {
      pthread_mutex_lock(&gc->out_queue_mutex);
    
      ngp=gp_dup(gpa);
      gnut_queue_insert(gc->out_queue,ngp);
    
      pthread_mutex_unlock(&gc->out_queue_mutex);
    }
  }
  
  /*  pthread_mutex_unlock(&gc_list_mutex); */
  if (gnut_lib_debug>=2) {
    gd_s(2, "send_to_all I just sent this packet:\n");
    gp_print(gpa);
  }
  
  gd_s(3, "send_to_all returning success\n");
  return 0;
}
    
int send_to_all_except(gcs *gc, gnutella_packet *gpa, int no_drop)
{
  Gnut_List *gltemp;
  gnutella_packet *ngp;
  gcs *mgc;
  
  gd_s(3, "send_to_all_except entering (gc=");
  gd_p(3, gc);
  gd_s(3, ", gpa=");
  gd_p(3, gpa);
  gd_s(3, ")\n");
  
  /*  pthread_mutex_lock(&gc_list_mutex); */
  
  gd_s(3,"done locking\n");
  
  for (gltemp=gc_list;gltemp;gltemp=gltemp->next) {
    gd_s(3, "send_to_all_except casting data\n");
    mgc=gltemp->data;
    gd_s(3, "send_to_all_except checking for exceptions mgc=");
    gd_p(3, mgc);
    gd_s(3, "\n");
    if (mgc!=gc && mgc->cstate==STATE_CONNECTED) {
      gd_s(3, "send_to_all_except adding to packet queue on mgc=");
      gd_p(3, mgc);
      gd_s(3, "\n");
      pthread_mutex_lock(&mgc->out_queue_mutex);
      gd_s(3, "send_to_all_except mutex locked successfully\n");
      
      if (no_drop || (mgc->out_queue->size < BACKLOG_MAX)) {
        gd_s(4, "send_to_all_except duplicating packet\n");
        ngp=gp_dup(gpa);
        gd_s(4, "send_to_all_except prepending to packet stack\n");
        gnut_queue_insert(mgc->out_queue,ngp);
      } else {
	/* gd_s(1,"killing connection %p because of backlog\n",mgc); */
	/* mgc->cstate=STATE_DEAD; */
      }

      pthread_mutex_unlock(&mgc->out_queue_mutex);
    }
  }

  if (gnut_lib_debug>=2) {
    gd_s(2, "send_to_all_except I just sent this packet:\n");
    gp_print(gpa);
  }

  /*  pthread_mutex_unlock(&gc_list_mutex); */
  gd_s(3, "send_to_all_except returning success\n");
  return 0;
}

/* gcs * gnut_connection_new()
 *
 * Allocates a new gcs structure, fills in some default
 * values, and inserts it into the connection list, then returns
 * a pointer to this newly allocated structure.
 */
gcs * gnut_connection_new()
{
  gcs *gc;
  
  gd_s(3,"gnut_connection_new entering\n");
  
  gc=(gcs *) calloc(sizeof(gcs),1);
  
  gc->sock=-1;

#ifndef PTHREADS_DRAFT4
  pthread_mutex_init(&gc->out_queue_mutex, 0);
#else
  pthread_mutex_init(&gc->out_queue_mutex,pthread_mutexattr_default);
#endif

  gc->out_queue=gnut_queue_new();
  
  gc->sent_packets=0;
  gc->received_packets=0;
  gc->dropped_packets=0;
  gc->bad_packets=0;
  
  gc->sent_bytes=0;
  gc->received_bytes=0;
  
  gc->rate_sent_bytes=0;
  gc->rate_received_bytes=0;
  gc->rate_start_time=time(0);

  gc->routing_errors=0;

  gc->seen_pong=0;
  
  gc->ctype=TYPE_UNSPEC;
  gc->cstate=STATE_UNCON;
  
  gc->tid=pthread_self();
  
  pthread_mutex_lock(&gc_list_mutex);
  
  gc_list=gnut_list_append(gc_list, gc);

  pthread_mutex_unlock(&gc_list_mutex);
  
  gd_s(3, "gnut_connection_new returning success\n");
  
  return gc;
}

/* int gnut_connection_delete(gcs *gc)
 *
 * takes the gcs structure, free's up any memory
 * associated with it, and removes it from the connection list
 * returns 0 on success */
int gnut_connection_delete(gcs *gc)
{
  Gnut_List *gltmp;
  gnutella_packet *gpa;
  Gnut_List *unlinked;
  
  gd_s(1, "gnut_connection_delete entering gc=");
  gd_p(1, gc);
  gd_s(1, "\n");
  
  pthread_mutex_lock(&gc_list_mutex);
  
  /* this didn't work, what we need to do is just unlink the list
   * then wait the second to let all references to it past,
   * then it will be safe to delete... */
  
  gc_list=gnut_list_unlink(gc_list,gc,&unlinked);
  
  pthread_mutex_unlock(&gc_list_mutex);
  
  gd_s(1,"gnut_connection_delete unlinked\n");
  
  /* it's possible, although HIGHLY unlikely, that some other
   * function managed to get a hold on our pointer during out remove
   * operation... since I don't want to always lock the connection list
   * we need some other way of making sure that this particular pointer
   * isn't in use.  Perhaps a reference count of some sort....  sleeping
   * for a fraction of a second is probably an easier solution to
   * implement however */
  route_guid_clear(gc);
  
  sleep(2);
  
  free_gl(&unlinked, 241);
  /* now that it's out of the connection list, we can
     go about freeing it up */
  
  for (gltmp=gc->out_queue->head;gltmp;gltmp=gltmp->next) {
    gpa=gltmp->data;
    if (gpa->data) {
      free_v(&(gpa->data), 242);
    }
  }
  
  gnut_queue_free(gc->out_queue);

  pthread_mutex_destroy(&gc->out_queue_mutex); 

  free_gc(&gc, 243);
  
  gd_s(1, "gnut_connection_delete returning success\n");
  
  return 0;
}

/* Sync packet formats
 *
 * Self-sync packet: Sent out a single port to sync the return stream
 * on that port. It is simply a PING with a TTL of 1 (which should
 * generate only one PONG, and get immediately discarded)
 *
 * 00 55 AA FF 53 4E 43 48 22 22 22 22 22 22 xx xx 00 01 00 00 00 00 00
 *              S  N  C  H
 *
 * If you receive more than one PONG from this PING, the servent at
 * the other end has a TTL bug, and you should immediately close the
 * connection.
 *
 * Network sync packet: gnut servents may also participate in the
 * initiation of network-wide sync packets, which are QUERY packets that
 * appear periodically in all connection streams.
 *
 * 00 55 AA FF 53 4E 43 48 33 33 33 33 33 33 xx xx
 *              S  N  C  H
 * 80 tt hh 10 00 00 00
 *
 * 00 00 33 33 33 33 33 33 53 79 4E 4C 48 67 6E 00
 *        3  3  3  3  3  3  S  y  N  C  H  g  n
 *
 * A new packet is generated only if *all* incoming streams have been
 * free of sync packets for at least 2048 bytes.
 */

/* Two lru_hash tables are used to implement the mreply and mpush
 * commands, as well as the instant-search feature.
 *
 * The netfiles hash table has the following key and data:
 *
 *   key = [crc]
 *   data = [ip, port, speed, length, filename]
 *
 * The query-reply hashtable maps equivalent GUID/refnum pairs
 * onto each other. It has this key and data:
 *
 *   key = [guid, refnum]
 *   data = [crc]
 *
 * When query-replies are routed, each data item is added to the netfiles
 * hashtable if it is new, and each GUID/refnum pair is also added to
 * the query-reply hashtable. If the mreply command is active, any new
 * netfiles entries are displayed as they are added.
 *
 * When push requests are routed, the GUID and refnum in the push request
 * is indexed through the query-replies hashtable to get a CRC which is used
 * to get the netfiles entry, for display by the mpush command.
 *
 * To implement instant search, a complete scan of the netfiles
 * hashtable is executed, copying the data into the search results
 * list if matching data is found. */

/* name_tok_cb is used to implement strict_search when multi_enable is set:
 * it is a callback to match a query reply against all one of the searches
 * in the current active search list */
char name_tok_cb(GnutSearch *s, void *data)
{
  return is_match_tok(s->query, data, 1, 1);
}

/* do whatever processing is necessary */
int connection_handle_packet(gcs *gc, gnutella_packet *gpa)
{
  unsigned char *dataraw;
  gcs *mgc;
  gnutella_packet *mgpa;
  gnutella_servent *gm;
  gnutella_results *grh;
  gnutella_results_name *grn;
  gnutella_results_suffix *grs;
  gnutella_push *gp;
  uint32 num, size;
  host_entry *he;
  int isnew;
  Gnut_List *results;
  char *buf;
  uint32 speed;
  uint32 dlen;
  int i;
  int accept;
  uint32 pref;
  int32 psize;
  route_entry *the_re;
  int no_drop;
  int multi_enable;
  GnutSearch *msearch = 0;
  struct in_addr ipt;

  no_drop = 0;
  isnew=0;
  
  gd_s(3, "conn_hdl_packet entering gc=");
  gd_p(3, gc);
  gd_s(3, ", gpa=");
  gd_p(3, gpa);
  gd_s(3, "\n");

  memcpy(&dlen, gpa->gh.dlen, 4);
  dlen=GUINT32_FROM_LE(dlen);

#ifdef MOREPACKETSTATS
  ++gc->packet_count[gpa->gh.func];
  if (dlen > gc->max_packet_size[gpa->gh.func]) {
    gc->max_packet_size[gpa->gh.func] = dlen;
  }
  gc->packet_byte_count[gpa->gh.func] += dlen;
  ++gc->ttl_count[gpa->gh.ttl];
#endif

  if (dlen>67075) {
    gd_s(0, "conn_hdl_packet WOAH! packet of size: ");
    gd_i(0, dlen);
    gd_s(0, " made it through!\n");
    return -1;
  }

  if (gnut_lib_debug>=2) {
    gd_s(2, "conn_hdl_packet received following packet\n");
    gp_print(gpa);
  }

  /* If the ttl is greater than 200, it's a packet that was forwarded by
   * a client with the "TTL wraparound bug", probably Windows Gnutella
   * prior to 0.56. These should be dropped. */
  if (gpa->gh.ttl>200 || gpa->gh.hops>200) {
    gd_s(2, "conn_hdl_packet ttl too high: ttl=");
    gd_i(2, gpa->gh.ttl);
    gd_s(2, "\n");
    return 0;    
  }

  /* Any other packets with a TTL bigger than our TTL should have their
   * TTL diminished, and if their hopcount is greater than our TTL we
   * should drop 'em */
  if (gpa->gh.ttl > gc_ttl) {
    gpa->gh.ttl = gc_ttl;
  }
  if (gpa->gh.hops > gc_ttl) {
    gd_s(2, "conn_hdl_packet hops too high: ");
    gd_i(2, gpa->gh.hops);
    gd_s(2, "\n");
    if (0) {
      /* %%%RPM diagnostics: bad TTLs implies strange data in packets,
       * perhaps ASCII data like query replies or chatroom messages */
      int j; char c; char *t;
      printf("%i'", gpa->gh.hops);
      t = (char *) (&(gpa->gh.guid));
      for(j=0; j<22; j++) {
	c = *t++;
	if (c>31 && c<127)
	  printf("%c", c);
      }
      printf("'");
    }
    return 0;
  }

  /* Check for GUID's we've seen before, and locate the connection
   * that GUID originated from. If it's a known GUID we might drop the
   * packet, or we might route it to that particular connection, depending
   * on the type of packet. */
  mgc = route_guid_find(gpa->gh.guid, &the_re);
  if (mgc) {
    isnew=0;
  } else {
    isnew=1;
  }
  
  gd_s(3, "conn_hdl_packet entering packet switch\n");
  
  switch(gpa->gh.func) {
  case 0x00:
    /* a PING */

    if (isnew == 0) {
      /* gc->dropped_packets++; */
      return 0;

      /* Where can I apply the blacklist rules
       * for pings? */
      /* } else if (!gnut_blacklist_allow(BLACKLIST_TYPE_PING, 0, */
      /*  IP-goes-here , 0)) { */
      /* return 0; */
    } else if ((gpa->gh.hops > 2) && (gpa->gh.hops > (gc_ttl / 2))) {
      /* We discourage PINGs that have travelled a long way before
       * reaching us, by silently dropping them. */
      if (gc_debug_opts & 32) {
        printf("drop1\n");
      }
      /* gc->dropped_packets++; */
      return 0;
    } else if (!conf_get_int("hidden")) {
      gd_s(3,"conn_hdl_packet PING packet\n");
      if (dlen>0) {
	gd_s(1, "conn_hdl_packet dlen=");
	gd_i(1, dlen);
	gd_s(1, "\n");
	return -2;
      }
      gd_s(2, "conn_hdl_packet handling PING\n");

      /* this is a ping packet, we should reply
       * to it with a servent packet... */
      share_totals(&num, &size);

      mgpa=gp_pong_make(gpa->gh.guid, num, size, 
			net_local_ip() , conf_get_int("local_port") , 
			gc_ttl);

      send_to_one(gc, mgpa);
	  
      /* We're done with the PONG packet; free it. */
      if (mgpa->data) {
	free_v(&(mgpa->data), 244);
      }
      free_gpa(&mgpa, 245);
    }
    break;

  case 0x01:
    /* A PONG (ping reply) */

    if (dlen!=sizeof(gnutella_servent)) {
      gd_s(1, "conn_hdl_packet bad PONG packet dlen=");
      gd_i(1, dlen);
      gd_s(1, " should be=");
      gd_i(1, sizeof(gnutella_servent));
      gd_s(1, "\n");
      return -3;
    }
    gm = gpa->data;

    dataraw = gpa->data;
    if (0) {
      printf("PONG IP %hhu.%hhu.%hhu.%hhu:%hhu.%hhu\n", dataraw[2], dataraw[3],
	   dataraw[4], dataraw[5], dataraw[0], dataraw[1]);
    }

    if (gc->seen_pong == 0) {
      if (gc_debug_opts & 32) {
        printf("First PONG on %hhu.%hhu.%hhu.%hhu is %hhu.%hhu.%hhu.%hhu\n",
	       gc->ip.b[0], gc->ip.b[1], gc->ip.b[2], gc->ip.b[3],
	       dataraw[2], dataraw[3], dataraw[4], dataraw[5]);
      }
      gc->seen_pong = 1;
    }

    /* Apply the blacklist rules. */
    memcpy(&ipt, &(gm->ip), sizeof(ipt));
    if (!gnut_blacklist_allow(BLACKLIST_TYPE_PING, 0, &ipt, 0)) {
      return 0;
    }
	
    gd_s(2, "conn_hdl_packet handling PONG\n");

#if 0
    memcpy(&i_port, gm->port, 2); i_port = GUINT16_FROM_LE(i_port);
    if (i_port == 5634) {
      printf("\nhttp://%i.%i.%i.%i:5634\n", gm->ip[0], gm->ip[1], gm->ip[2],
	     gm->ip[3]);
    }
#endif
    if (host_find(gm->ip, gm->port) == -1) {
      if (host_ok_for_get(gm->ip)) {
	gd_s(2, "conn_hdl_packet adding new servent\n");
	/* the host wasn't found in the database, so we'll add it */
	memcpy(&num,gm->files,4);
	num=GUINT32_FROM_LE(num);
        
	memcpy(&size,gm->bytes,4);
	size=GUINT32_FROM_LE(size);
		
	he=(host_entry *) xmalloc(sizeof(host_entry));
	memcpy(he->ip,gm->ip,4);
	memcpy(he->port, gm->port, 2);
        
	he->files=num;
	he->bytes=size;
	host_add(he);
      }
    }
    if (memcmp(conf_get_str("update_guid"),gpa->gh.guid,16)==0) {
      return 0;
    }
    break;
	
  case 0x40:
    /* A PUSH request */

    if (dlen!=sizeof(gnutella_push)) {
      gd_s(1, "conn_hdl_packet bad push request! dlen=");
      gd_i(1, dlen);
      gd_s(1, " should be=");
      gd_i(1, sizeof(gnutella_push));
      gd_s(1, "\n");
      return -4;
    }

    gp=gpa->data;
    gd_s(3, "conn_hdl_packet push request\n");
    if (memcmp(gp->guid, conf_get_str("guid"), 16)==0) {
      gd_s(2, "conn_hdl_packet my own push request\n");
      gnut_push_new(gp);
      /* return because we don't want to route this sucker */
      return 0;
    }

    /* now we're going to see if this is in our routing table, if
     * it is, then we send to just that one and quit, otherwise we will
     * let it drop through into the regular routing code which will end
     * up broadcasting it */
	
    /* screw that, only do pushes that we saw the original query for
     * that should help congestion a bit */
    mgc = route_guid_find(gp->guid, &the_re);
    if (mgc) {
      memcpy(&pref, gp->ref, 4); pref=GUINT32_FROM_LE(pref);
      if (monitor_push) {
	guid_ref key;
	uint32 len;
	void * data;

	/* construct the key */
	memcpy(key.guid, gp->guid, 16);
	memcpy(key.ref, gp->ref, 4);

	/* look it up */
	pthread_mutex_lock(&qreplies_mutex);
	data = lru_copy(&qreplies, &key, sizeof(key), &len);
	pthread_mutex_unlock(&qreplies_mutex);
	if (data) {
	  /* printf("Found PUSH "); print_ascii((char *) data); printf("\n"); */
	  monitor_search_add(data, 0);
	  free_v(&data, 246);
	} else {
	  /* printf("Didn't find PUSH\n"); */
	}
      }

      gd_s(2, "conn_hdl_packet routing PUSH\n");
      /* here we do the routing */
      gpa->gh.hops++;
      if ((gpa->gh.ttl != 0) && (--gpa->gh.ttl>0)) {
	send_to_one(mgc,gpa);
      } else {
	/* gc->dropped_packets++; */
      }
    }

    /* Always return -- if we didn't just send this packet, we drop it */
    return 0;

    break;
	
  case 0x80:
    /* QUERY */
    buf=gpa->data;

    /* First make sure strlen will work */
    if (dlen > 0) {
      buf[dlen-1]=0;
    }

    /* Filter as per client recommendation */
    if (dlen > 257) {
      gd_s(1, "conn_hdl_packet Query packet too long dlen=");
      gd_i(1, dlen);
      gd_s(1, "\n");
      return -4;
    }

    /* Test for query strings of 3 characters or less, e.g. "jpg".
     * Such queries produce more query replies than the requestor
     * can even store, much less display. So, we drop the packet in
     * order to discourage such queries, and more importantly, avoid
     * the flood of return query replies that would be routed through
     * us.
     * We also filter really long queries, which have appeared as spam
     * lately. */
    if (dlen > 80) {
      /* spam packet. Suppress it by truncating its data */
      if (0) {
	printf("truncate long query '");
	print_ascii(&buf[2]);
	printf("'\n");
      }
      buf[2] = 0;
      dlen = 0;
      memcpy(gpa->gh.dlen, &dlen, 4);
      no_drop = 1;
    } else if (dlen <= (2 + 3 + 1)) {
      return -5;
    } else if (strlen(&buf[2]) > dlen - 3) {
      /* not a C string */
      /* printf("drop non-C-string query\n"); */
      return -5;
    } else if (strlen_an(&buf[2]) <= 3) {
      /* Less than 3 "real" characters in query */
      /* printf ("drop dlen %i query '%s'\n", dlen-3, &buf[2]); */
      return -5;
    }
    gd_s(3, "conn_hdl_packet query request\n");

    if (isnew) {
      /* Run the query against our shared files */
      num_queries++;
      gd_s(3, "conn_hdl_packet new query request\n");
      buf[dlen-1]=0;

      /* Drop any packets which are on our blacklist. */
      if (!gnut_blacklist_allow(BLACKLIST_TYPE_SEARCH, &buf[2], 0, 0)) {
	return 0;
      }

      results=share_search(&buf[2], conf_get_int("max_results"));

      /* printf("kept dlen %i query '%s'\n", dlen-3, &buf[2]); */
      
      if (monitor_query) {
	monitor_search_add(&buf[2], !!results);
      }

      if (gc_searchlog) {
	fprintf(gc_searchlog, "%d\t%d\t%s\n", (int) (time(0)), !!results,
		&buf[2]);
      }
      
      if (results) {
	num_responses++;
	gd_s(3, "conn_hdl_packet responding to query: ");
	gd_s(3, &buf[2]);
	gd_s(3, "\n");
        
	mgpa=gp_reply_make(gpa->gh.guid, results,
			   net_local_ip(), conf_get_int("local_port"),
			   conf_get_int("speed"),
			   conf_get_str("guid"), conf_get_int("ttl"));

	send_to_one(gc, mgpa);
        
	share_clear_list(results);
        
	free_v(&(mgpa->data), 247);
	free_gpa(&mgpa, 248);
      }
    } else if (isnew == 0) {
      /* This is the normal route for detecting small loops -- duplicate
       * packets get dropped. */
      gc->dropped_packets++;
    }
    break;

  case 0x81:
    /* HITS a.k.a. Query Reply */
    if (dlen<sizeof(gnutella_results)) {
      gd_s(1, "conn_hdl_packet QREPLY dlen=");
      gd_i(1, dlen);
      gd_s(1, " should be > ");
      gd_i(1, sizeof(gnutella_results));
      gd_s(1, "\n");
      return -7;
    }
    gd_s(3, "conn_hdl_packet handling QREPLY\n");
	
    grh=gpa->data;
    grs=(gnutella_results_suffix *) ((char *) gpa->data + dlen - 16);
    grn=(gnutella_results_name *)
      ((char *) gpa->data+sizeof(gnutella_results));

    /* Get the first item's refnum */
    memcpy(&pref, grn->ref, 4); pref=GUINT32_FROM_LE(pref);

    /* we stick this into the routing table, so that
     * it is possible to route push request packets back to the
     * place that originally sent us the query... */
    route_guid_add(grs->guid, gc);

    /* Add all query replies to the qreply lru_hash. This is to support the
     * mpush command and the (future) instant-search feature */
    for (i=0; i<grh->num; i++)  {
      guid_ref key;
      int l;
	  
      if (((char *)&grn->name - (char *)gpa->data) > dlen) {
	gd_s(1, "conn_hdl_packet bad response packet\n");
	return -8;
      }

      /* construct the key */
      memcpy(key.guid, grs->guid, 16);
      memcpy(key.ref, grn->ref, 4);
	  
      /* Right now we don't construct the data, but in the future, we'll
       * be storing filename, IP, port, refnum and size in a structure. */
	  
      l = strlen_an(grn->name);
      if (l > 5 && l < 80) {
	int new;

	/* Store it. */
	pthread_mutex_lock(&qreplies_mutex);
	new = lru_storecopy(&qreplies, &key, sizeof(key), grn->name,
			    strlen(grn->name)+1);
	pthread_mutex_unlock(&qreplies_mutex);
	if (new) {
	  if (monitor_reply) {
	    int accept;

	    accept = 0;
	    if (monitor_filter) {
	      if (is_match_tok(monitor_filter, grn->name, 1, 0)) {
		accept = 1;
	      }
	    } else {
	      accept = 1;
	    }

	    if (accept) {
	      monitor_search_add(grn->name, 0);
	    }
	  }
	}
      }
	  
      /* Skip to the next one */
      grn=(gnutella_results_name *)
	((char *) grn+sizeof(gnutella_results_name) + strlen(grn->name)+1);
    }
    /* reset name pointer */
    grn = (gnutella_results_name *)
      ((char *) gpa->data+sizeof(gnutella_results));

    memcpy(&psize, grn->size, 4);  psize=GUINT32_FROM_LE(psize);
    memcpy(&speed, grh->speed, 4); speed=GUINT32_FROM_LE(speed);

#ifdef GNUT_HTTP_SEARCH
    {
      int ret;

      memcpy(&ipt, &(grh->ip), sizeof(ipt));
      
      /* Drop any packets which are on our blacklist. */
      if (!gnut_blacklist_allow(BLACKLIST_TYPE_SEARCH | BLACKLIST_TYPE_HTTP,
				0, &ipt, 0)) {
	return 0;
      }

      /* If this was requested through an HTTP request, append it to the
       * appropriate cache, and not to our interactive query list. */
      ret = gnut_http_result_append(grh->ip, grh->port, pref, speed, psize,
				    grn->name, gpa->gh.guid);
      if (ret) {
	return 0;
      }
    }
#endif
	
    /* Drop any packets which are on our blacklist. */
    memcpy(&ipt, &(grh->ip), sizeof(ipt));
    if (!gnut_blacklist_allow(BLACKLIST_TYPE_SEARCH, 0, &ipt, 0)) {
      return 0;
    }

    /* If we have a query, look for matches. If strich_search is on, we
     * can match any query reply packet, getting more and quicker results */
    multi_enable = conf_get_int("multi_enable");
    if ((multi_enable && current_searches)
	|| (!multi_enable && current_query_packet)) {
      int match_guid = 0;
	  
      /* We now only compare the first 14 bytes of the query GUID, because
       * the last two are used for subsequent invocations of the same query
       * over new GnutellaNet connections. */
      match_guid = 0;
      if (multi_enable) {
	if ((msearch = gnut_search_find_by_guid(gpa->gh.guid))) {
	  match_guid = 1;
	}
      } else {
	match_guid = (memcmp(conf_get_str("query_guid"), gpa->gh.guid, 14)==0);
      }

      if (match_guid || gc_strict_search) {
	char *exts;
	char *ourquery;
		
	gd_s(3, "conn_hdl_packet its our query reply\n");
		
	ourquery = (current_query_packet->data)+2;
	exts = conf_get_str("search_extensions");
	for (i=0; i<grh->num; i++)  {
	  /* Make sure we haven't gone off the end of the packet */
	  if (((char *)&grn->name - (char *)gpa->data) > dlen) {
	    gd_s(1, "conn_hdl_packet bad response packet\n");
	    return -8;
	  }
		  
	  /* Match query reply against current query to see if it's really a
	   * match. %%% We want to make this code usable by the HTTP query too,
	   * but that will require using the GUID to find out what the query
	   * string was; also gnut_http_result_append() needs to be called
	   * on each query result (not just the first) */
	  accept = 0;
	  if (gc_strict_search) {
	    /* Make sure this filename matches what we were searching for */
	    if (multi_enable) {
	      if ((msearch = gnut_search_find(name_tok_cb, grn->name)))
		accept=1;
	    } else {
	      accept = is_match_tok(ourquery, grn->name, 1, 1); 
	    }
	  } else if (match_guid) {
	    /* Strict search is off, but the packet matches our GUID so we
	     * accept all results */
	    accept = 1;
	  }

	  /* Ignore anything that cannot be transferred by either 'get'
	   * or 'put' */
	  if (host_ok_for_neither(grh->ip)) {
	    accept = 0;
	  }

	  /* Match query filename extension */
	  if (accept && exts) {
	    /* search_extensions must be a non-null string, and not starting
	     * with "*" which is the special value that causes all files to
	     * match */
	    if ((*exts) && (*exts != '*')) {
	      char *ext;

	      /* Now see if the file has an extension -- if not we'll skip
	       * the test */
	      ext = strrchr(grn->name, '.');
	      if (ext) {
		ext++;
				
		if (ext) {
		  /* Whew. We are actually ready to check for a match */
		  if (strstr(exts, ext)) {
		    /* Found, leave it alone */
		  } else {
		    /* Not found, ignore this query response entry */
		    accept = 0;
		  }
		}
	      }
	    }
	  }

	  /* Filter against minimum size */
	  if (accept && gc_search_min_size) {
            if (psize < gc_search_min_size) {
	      accept = 0;
	    }
          }
		  
	  if (accept) {
	    memcpy(&pref, grn->ref, 4);
	    pref=GUINT32_FROM_LE(pref);
			
	    query_add(grh->ip, grh->port, pref, speed, psize, grn->name,
		      grs->guid);

	    if (multi_enable) {
	      if (msearch) {
		msearch->responses++;
	      }
	    }
	  }

	  grn=(gnutella_results_name *)
	    ((char *) grn+sizeof(gnutella_results_name) + strlen(grn->name)+1);
	}

	if (match_guid) {
	  return 0;		
	}
      }
    }

    /* If we did not search for this query response, possibly cache it. This
     * allows us to route PUSH requests. */
    if (cache_time_for_update()) {
      int pref, psize;

      gd_s(1, "conn_hdl_packet caching from query reply\n");
      grh=gpa->data;
	  
      /* no loop... just cache the first in the replies (FIXME - Ray) */
      grn=(gnutella_results_name *) ((char *) gpa->data+sizeof(gnutella_results));
      if (((char *)&grn->name - (char *)gpa->data) > dlen) {
	gd_s(1, "conn_hdl_packet bad response packet\n");
	return -8;
      }
	  
      memcpy(&pref,grn->ref,4);
      pref=GUINT32_FROM_LE(pref);
	  
      memcpy(&psize,grn->size,4);
      psize=GUINT32_FROM_LE(psize);

      query_add_cache(grh->ip, grh->port, pref, speed, psize, grn->name,
		      grs->guid);

      return 0;
    }
	
    break;

  default:
    gd_s(1, "conn_hdl_packet packet type: ");
    gd_i(1, gpa->gh.func);
    gd_s(1, "\n");

    /* "spam" packet. (Actually, I've found that most of these are the
     * result of misaligned data being forwarded as a packet by a dumb
     * routine algorithm in another client). We suppress the packet
     * by forwarding it with no data -- thus its GUID will be cached,
     * but it won't take up a lot of bandwidth. */
    if(0) {
      printf("truncate spam packet type %i len %i data '", gpa->gh.func, dlen);
      if (dlen > 0) {
	print_asc_n(gpa->data, dlen);
      }
      printf("'\n");
    }
    gpa->gh.func = 0x80;
    dlen = 0;
    memcpy(gpa->gh.dlen, &dlen, 4);
    no_drop = 1;

    /* return here so we don't pass along bad packets... */
    /* return -9; */
  }
  
  gd_s(2, "conn_hdl_packet running routing logic\n");
  
  if (gpa->gh.func & 1) {
    if (mgc == 0) {
      gd_s(2, "conn_hdl_packet Routing error!\n");
      gc->routing_errors++;
      return 0;
    } else {
      gd_s(2, "conn_hdl_packet Routing packet...\n");
      gpa->gh.hops++;
      if (gpa->gh.ttl && --gpa->gh.ttl>0) {
	send_to_one(mgc, gpa);
      } else {
	/* gc->dropped_packets++; */
      }
    }
  } else {
    /* broadcast packets: ping or query (push request doesn't reach
     * here because case 0x40 returns rather than dropping through) */
    if (!mgc) {
      /* not in routing table, means not seen yet */

      gd_s(2, "conn_hdl_packet Spreading packet!\n");

      /* Add a broadcast packet (ping or query) to the routing table */
      route_guid_add(gpa->gh.guid, gc);
      
      gpa->gh.hops++;
      if (gpa->gh.ttl && (--gpa->gh.ttl)>0) {
        send_to_all_except(gc, gpa, no_drop);
      } else {
        /* gc->dropped_packets++; */
      }
    }
  }
  
  gd_s(3, "conn_hdl_packet returning success\n");
  return 0;
}

/* loops through any packets available on the out stack,
 * and sends them in turn... */
int connection_send_packets(gcs *gc)
{
  gnutella_packet *gpa;
  int ret;
  fd_set fsw;
  int i=0;
  struct timeval tv;
  int sendit;

  gd_s(4, "connection_send_packets entering\n");

  while (gc->out_queue->size>0) {
    gd_s(3, "connection_send_packets there are ");
    gd_i(3, gc->out_queue->size);
    gd_s(3, " packets left to send\n");

    FD_ZERO(&fsw);
    FD_SET(gc->sock,&fsw);

    sendit = 1;

    /* Wait 0.5 seconds for socket to be writable. What this really means
       is we're waiting for the current I/O to complete. It takes longer
       if the connection at the other end is slow */
    tv.tv_sec=1;
    tv.tv_usec=500000;
    ret=select(gc->sock+1, 0, &fsw, 0, &tv);

    if (ret==0) {
      sendit = 0;
    }

    /* we don't want anyone trying to change the out stack
     * while we're playing around with it... */
    pthread_mutex_lock(&gc->out_queue_mutex);
    gpa=gnut_queue_remove(gc->out_queue);
    pthread_mutex_unlock(&gc->out_queue_mutex);

    if (sendit) {
      ret=send_packet(gc->sock, gpa);

      if (ret>=0) {
	gd_s(4, "connection_send_packets wrote packet, size: ");
	gd_i(4, ret);
	gd_s(4, "\n");
	gc->sent_bytes+=ret;
	gc->rate_sent_bytes+=ret;
	gc->sent_packets++;
	sent_bytes+=ret;
	num_sent++;
      }
    
#ifdef MOREPACKETSTATS
      ++gc->out_packet_count[gpa->gh.func];
      gc->out_packet_byte_count[gpa->gh.func] += (ret - 23);
      ++gc->out_ttl_count[gpa->gh.ttl];
      if ((ret - 23) > gc->out_max_packet_size[gpa->gh.func])
	gc->out_max_packet_size[gpa->gh.func] = (ret - 23);
#endif
    } else {
      /* We're gonna just drop the packet. Better than dropping the whole connection! */
      if (gc_debug_opts & 32) {
        printf("drop2\n");
      }
      gc->dropped_packets++;
      ret = 0;
    }

    if (gpa->data) {
      free_v(&(gpa->data), 249);
    }
    free_gpa(&gpa, 250);

    if  (ret<0) {
      gd_s(1, "connection_send_packets send_packet error ");
      gd_s(1, strerror(errno));
      return ret;
    }
    
    /* Limit to sending 5 packets at a time. This is so we can still receive stuff over the link... */
    if ((++i)>5) {
      return 0;
    }
  }

  gd_s(4, "connection_send_packets returning success\n");
  
  return 0;
}

/* this needs to read a packet if available, and it NEEDS to timeout
 * after 1/4 second or less
 * returns 0 on success or timeout
 * returns <0 on error */
int connection_receive_packet(gcs *gc)
{
  fd_set fsr;
  struct timeval tv;
  int ret;
  gnutella_packet * gpa;
  
  gd_s(4, "conn_rcv_packet entering\n");
  
  FD_ZERO(&fsr);
  FD_SET(gc->sock,&fsr);
  
  tv.tv_sec=0;
  tv.tv_usec=250000;
  ret=select(gc->sock+1, &fsr, 0, 0, &tv);
  
  if (ret<=0) {
	/*    perror("conn_rcv_packet, select"); */
    return ret;
  }
  
  gd_s(3,"conn_rcv_packet trying to read packet\n");
  gpa=(gnutella_packet *) calloc(sizeof(gnutella_packet),1);
  
  gnut_mprobe(gpa);
  ret=read_packet(gc->sock,gpa);
  
  gnut_mprobe(gpa);
  
  if (ret<0 && errno!=EINTR) {
    gd_s(1, "conn_rcv_packet read_packet returned:");
    gd_i(1, ret);
    gd_s(1, "\n");
    if (gpa->data) {
      free_v(&(gpa->data), 251);
    }
    free_gpa(&gpa, 252);
    return ret;
  }
  
  if (ret>0) {
    received_bytes+=(uint64) ret;
    gc->received_bytes+=(uint64) ret;
    gc->rate_received_bytes+=(uint64) ret;
    gc->received_packets++;
    num_messages++;
  
    /* now we can do any actual processing on the packet! */
  
    ret=connection_handle_packet(gc,gpa);
  
    gnut_mprobe(gc);
    gnut_mprobe(gpa);
    if (gpa->data) gnut_mprobe(gpa->data);
  
    if (ret<0) {
      gd_s(1, "conn_rcv_packet bad packet on tid: ");
      gd_li(1, gc->tid);
      gd_s(1, " with ret=");
      gd_i(1, ret);
      gd_s(1, "\n");
      gc->bad_packets++;
      if (gc->bad_packets>BAD_PACKET_MAX) {
        gd_s(1,"conn_rcv_packet max bad packets reached on this connection\n");
        return -1;
      }
      gd_s(2, "conn_rcv_packet ret=");
      gd_i(2, ret);
      gd_s(2, "\n");
    }
  
    gd_s(3, "conn_rcv_packet trying to free gpa->data\n");
  
    if (gpa->data) {
      gnut_mprobe(gpa->data);
      free_v(&(gpa->data), 253);
    }
  }
  gd_s(3,"conn_rcv_packet trying to free gpa\n");
  
  gnut_mprobe(gpa);
  free_gpa(&gpa, 254);
  
  gd_s(3, "conn_rcv_packet returning success\n");
  
  return 0;
}


/* int connection_loop(gcs *gc)
 *
 * this is the main loop which handles sending and receiving
 * all packets over a connection.  It should be called after
 * all initial negotiation is complete.  When it returns, it should
 * be time to remove the connection from the connection
 * list.
 */
int connection_loop(gcs *gc)
{
  int ret=1;
  
  gd_s(3, "connection_loop entering\n");
  
  gc->cstate=STATE_CONNECTED;
  
  /* If there is a current query, we send it out the new connection.
   * The effect of this is that, if you issue a 'find' command right
   * after starting gnut, you'll get just as many results as you would
   * have gotten if you waited for all 4 (or however many) connections
   * to be up before doing the 'find'. This is good, because it means
   * you don't have to wait every time you launch gnut, or (even
   * worse :-) repeat the search command 'just to make sure'. */
  if (query_num() < conf_get_int("max_responses")) {
    pthread_mutex_lock(&query_packet_mutex);
    if (current_query_packet) {
      /* There is a query (find) in progress, and we have just established
       * a new connection into the network.
       *
       * Because this new connection gives us access to hosts that were
       * hitherto unreachable within the TTL, we want to send out the
       * query packet so those new hosts can get the query and respond to it.
       *
       * (For maximum search results, we would also have to change the GUID
       * in the query. If we don't, and if the host we've just connected to
       * has already seen the query (which is very likely, since we got the
       * host's IP address from a packet and therefore the host was within
       * TTL of a previous connection) it would just throw the packet away
       * and the packet would not reach our new hosts. Also, if a match
       * occurred, the results would get routed through the old path, but
       * we want them to come back on the new path. */
      if (gc_debug_opts & 2) {
	/* Changing the GUID isn't that great, because some people think it's
	 * spam */
	current_query_packet->gh.guid[14]++;
	if (current_query_packet->gh.guid[14] == 0) {
	  current_query_packet->gh.guid[15]++;
	}
      }
      send_to_one(gc, current_query_packet);
    }
    pthread_mutex_unlock(&query_packet_mutex);
  }

  /* If for some reason our host list is empty or nearly empty, we send a
   * PING packet. This will happen most often with new users trying to
   * start up without installing a .gnut_hosts file. */
  {
    uint h_num;
    uint64 size,files;
    int do_ping;

    do_ping = 0;

    host_totals(&h_num, &files, &size);
    if (h_num < 10) {
      do_ping = conf_get_int("ttl");
      /* Limit TTL to 4 so we only bother about 150 hosts */
      if (do_ping > 4) {
        do_ping = 4;
      }
    }

    /* Always send a PING with at least a TTL 1. This is required for
     * some of the newer, noncompliant servents like Limewire and
     * BearShare. */
    if (do_ping < 1) {
      do_ping = 1;
    }

    if (do_ping) {
      gnutella_packet *gpa;
  
      if (0) {
        gpa = gp_ping_make(conf_get_str("mac"), conf_get_int("ttl"));
      } else {
        gpa = gp_ping_make(conf_get_str("mac"), do_ping);
      }

      conf_set_str_len("update_guid", gpa->gh.guid, 16);
	
      send_to_one(gc, gpa);
	
      if (gpa->data) {
	free_v(&(gpa->data), 255);
      }
      free_gpa(&gpa, 256);
    }
  }
  
  while((gc->cstate==STATE_CONNECTED) && (ret>=0)) {
    ret = connection_receive_packet(gc);
    gd_s(3, "conn_rcv_packet returned: ");
    gd_i(3, ret);
    gd_s(3, "\n");
    if (ret<0) {
      /* the line is closed, we must be done! */
      gd_s(1, "conn_rcv_packet returned: ");
      gd_i(1, ret);
      gd_s(1, "\n");
    }
	
    if (ret>=0) {
      /* send all queued up packets */
      gd_s(3, "conn_rcv_packet about to call csp with gc=");
      gd_p(3, gc);
      gd_s(3, "\n");
      ret=connection_send_packets(gc);
      gd_s(3, "conn_rcv_packet csp returned: ");
      gd_i(3, ret);
      gd_s(3, "\n");
      if (gc->cstate==STATE_CONNECTED) {
        gd_s(20,"this shouldn't get written\n");
      }
      gd_s(3, "conn_rcv_packet gc->cstate is readable\n");
    }
  }
  
  if (ret >= 0) {
    /* We exited on cstate */
  }
  
  gd_s(3, "conn_rcv_packet returning success\n");
  return 0;
}

/* calls function a for every connection in the list */
int gnut_connection_enumerate( int (*a)(gcs *))
{
  Gnut_List *gl;
  
  pthread_mutex_lock(&gc_list_mutex);
  
  for (gl=gc_list;gl;gl=gl->next) {
    if ((*a)(gl->data)==-1) {
      break;
    }
  }
  
  pthread_mutex_unlock(&gc_list_mutex);
  return 0;
}

/* kill the oldest incoming connection */
int gnut_connection_kill_oldest()
{
  Gnut_List *gl;
  gcs *gc=0;
  gcs *gtc;
  int going;
  
  pthread_mutex_lock(&gc_list_mutex);
  
  going = 1;
  for (gl=gc_list; gl && going; gl=gl->next) {
    gtc=gl->data;
    if ((gtc->ctype==TYPE_INCOMING) && (gtc->cstate==STATE_CONNECTED)) {
      gc=gtc;
      going = 0;
    }
  }

  if (gc) {
    /* The instance of connection_loop() handling this connection
     * will notice this and actually close the connection */
    gc->cstate=STATE_DEAD;
  }

  pthread_mutex_unlock(&gc_list_mutex);

  return 0;
}

/* droprate returns ratio of two longs, used to compute
   ratio of dropped to total received packets. Result is an
   8-bit fractional number: 256 means 1.0 or above; 0 means 0.0;
   in-between is a fraction, for example 192 means 0.75 */
long droprate(long drop, long recv)
{
  long rv;

  if ((recv == 0) || (drop == 0)) {
    rv = 0;
  } else if (drop > recv) {
    rv = 256;
  } else if (drop > 65536L) {
    rv = drop / (recv >> 8L);
  } else {
    rv = (drop << 8L) / recv;
  }

  /* convert to a percentage */
  rv = ((rv * 100) + 128) / 256;

  return rv;
}

/* kill the worst (by dropped packet percentage) connection */
int gnut_connection_kill_worst(int interact)
{
  Gnut_List *gl;
  gcs *worst_gc=0;
  gcs *gtc;
  long this_dr;
  long worst_dr;
  int killed_ip[4];

  pthread_mutex_lock(&gc_list_mutex);

  worst_dr = conf_get_int("autokill_thres");
  for (gl=gc_list; gl; gl=gl->next) {
    gtc=gl->data;
    if (((gtc->ctype==TYPE_OUTGOING) || (gtc->ctype==TYPE_INCOMING))
         && (gtc->cstate==STATE_CONNECTED)
         && ((gtc->sent_packets + gtc->received_packets + gtc->dropped_packets) > 1000))
    /* %%%RPM also want to test for really low transfer/sec stats. It
     * needs to measured relative to other connections, to auto-compensate
     * for our ISP speed, which may vary with time (cable modems) */
    {
      this_dr = droprate(gtc->dropped_packets, gtc->received_packets);

    /* 20000611 change > to >=; break ties by last found rather than first; (this is newest connection after reversing order of connection list insertion) */
      if (this_dr >= worst_dr) {
        worst_dr = this_dr;
        worst_gc=gtc;
      }
    }
  }
  if (worst_gc) {
    /* record the IP of the connection we killed (no printf inside mutex) */
    killed_ip[0] = worst_gc->ip.b[0];
    killed_ip[1] = worst_gc->ip.b[1];
    killed_ip[2] = worst_gc->ip.b[2];
    killed_ip[3] = worst_gc->ip.b[3];

    /* set flag to kill connection:
     * the instance of connection_loop() handling this connection
     * will notice this and actually close the connection */
    worst_gc->cstate=STATE_DEAD;
  }

  pthread_mutex_unlock(&gc_list_mutex);

  if (interact) {
    /* indicate which , if any, we killed */
    if (worst_gc) {
      printf(UI_CN_KILLING, killed_ip[0],
			 killed_ip[1], killed_ip[2], killed_ip[3]);
    } else {
      printf(UI_CN_NO_WORST);
    }
  }

  return 0;
}
