/* 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 <sys/file.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <netdb.h>
#else
#include <io.h>
#endif

#if defined(HAVE_FCNTL)
#include <fcntl.h>
#endif
#include <errno.h>
#include <string.h>
#include <string.h>
#include <pthread.h>
#include <regex.h>
#include <ctype.h>

#include "gnut_lib.h"
#include "gnut_connection.h"
#include "Gnut_Hash.h"
#include "gnut_net.h"
#include "gnut_ui.h"
#include "host.h"
#include "gnut_transfer.h"
#include "query.h"
#include "conf.h"
#include "share.h"
#include "cache.h"
#include "gnut_threads.h"
#include "gnut_http.h"
#include "blacklist.h"

#define SAFE_CLOSE(x) if (x>=0) { shutdown(x,2); close(x); x=-1; }

int num_outgoing=0;
int num_incoming=0;
int num_downloads=0;
int num_uploads=0;
int count_downloads=0;
int count_uploads=0;
int num_searches = 0;

gnutella_packet *current_query_packet;
GnutSearch      *current_searches;

void free_bip(bad_ip **x, int bugnum)
{
  yfree((void **) x, bugnum);
}

void free_gs(GnutSearch **x, int bugnum)
{
  yfree((void **) x, bugnum);
}

void free_ia(incoming_arg **x, int bugnum)
{
  yfree((void **) x, bugnum);
}

void free_pa(push_arg **x, int bugnum)
{
  yfree((void **) x, bugnum);
}

GnutSearch *gnut_search_add(char *guid, char *query)
{
  GnutSearch *t = 0,
    *n = 0;
  int         i = 0;

  if (!guid || !query) {
    return 0;
  }

  if (gnut_search_find_by_query(query)) {
    printf("Ignoring duplicate search string \"%s\"\n", query);
    return 0;
  }
  n = malloc(sizeof(GnutSearch));
  if (!n) {
    return 0;
  } else {
    n->guid = malloc(16);
    /* copy the GUID we were given */
    for (i=0; i<14; i++) {
      n->guid[i] = guid[i];
    }
    n->guid[14] = 0;
    n->guid[15] = 0;
    n->query = strdup(query);
    n->responses = 0;
    n->next = 0;
  }
  
  if (!current_searches) {
    current_searches = n;
  } else {
    for (t=current_searches; t->next; t=t->next) { }
    t->next = n;
  }
  return n;
}

void gnut_search_remove(GnutSearch *s)
{
  GnutSearch *t = 0;
  
  if (!s) {
    return;
  }

  if (current_searches==s) {
    current_searches = s->next;
  } else {
    for (t=current_searches; t; t=t->next) {
      if (t->next==s) {
	t->next = s->next;
	break;
      }
    }
  }

  free_str(&(s->query), 101);
  free_str(&(s->guid), 102);
  free_gs(&s, 103);
  return;
}

void gnut_search_foreach(void (*cb)(GnutSearch *search,
                                    void       *data),
                         void  *data)
{
  GnutSearch *t = 0;
  GnutSearch *next = 0;
  
  for (t=current_searches; t; t=next) {
    next = t->next;
    cb(t,data);
  }
  
  return;
}

GnutSearch *gnut_search_find(char (*cb)(GnutSearch *search,
                                        void       *data),
                             void  *data)
{
  GnutSearch *t = 0,
    *r = 0,
    *next = 0;

  for (t=current_searches; t; t=next) {
    next = t->next;
    if (cb(t,data)) {
      r = t;
      break;
    }
  }
  
  return r;
}

char by_guid_cb(GnutSearch *s, void *data)
{
  char r = 0;

  if (!memcmp(s->guid, data, 14)) {
    r = 1;
  }
  return r;
}

GnutSearch *gnut_search_find_by_guid(char *guid)
{
  return gnut_search_find(by_guid_cb, guid);
}

char by_query_cb(GnutSearch *s, void *data)
{
  char r;

  r = 0;
  if (!strcasecmp(s->query, data)) {
    r = 1;
  }
  return r;
}

GnutSearch *gnut_search_find_by_query(char *query)
{
  return gnut_search_find(by_query_cb, query);
}

int gnut_search_count()
{
  GnutSearch *t = 0;
  int c = 0;

  for (t=current_searches; t; t=t->next) {
    c++;
  }
  return c;
}

int gnut_threads_num_downloads()
{
  return num_downloads;
}

int gnut_threads_num_uploads()
{
  return num_uploads;
}

int gnut_threads_count_downloads()
{
  return count_downloads;
}

int gnut_threads_count_uploads()
{
  return count_uploads;
}

int gnut_threads_num_incoming()
{
  return num_incoming;
}

int gnut_threads_num_outgoing()
{
  return num_outgoing;
}

int transfer_cleanup(gnut_transfer *gt, int state)
{
  gd_s(2, "transfer_cleanup IN\n");

  if ((gt->sock) >= 0) {
    shutdown(gt->sock, 2);
    close(gt->sock);
    gt->sock=-1;
  }

  gt->state=state;
  
  if (!conf_get_int("auto_clear")) {
    gd_s(2, "transfer_cleanup waiting for external clear\n");
    while (gt->state==state) {
      sleep(1);
    }
  }
  
  gd_s(2, "transfer_cleanup done waiting\n");
  
  gnut_transfer_delete(gt);

  gd_s(2, "transfer_cleanup OUT\n");
  return 0;
}

int send_welcome_message(int sock, char *message)
{
  int ret;
  char szBuffer[100];

  gd_s(3, "send_welcome_message entering\n");

  sprintf(szBuffer,"GNUTELLA CONNECT/%s\n\n", message);

  do {
    ret = write(sock,szBuffer,strlen(szBuffer));
  } while (errno==EINTR && ret<0);

  if (ret<0) {
    if (gnut_lib_debug>3) {
      perror("send_welcome_message");
    }
  }

  return ret;
}

int read_welcome_message(int sock, char *arg)
{
  char buf[100];
  int i;
  int flag=0;

  i=read_line(sock, buf, sizeof(buf));
  gd_s(3, "read_welcome_message read_line returned: ");
  gd_i(3, i);
  gd_s(3, "\n");

  if (i<=0) {
    return -1;
  }

  if (gc_debug_opts & 4) {
    /* Print up to 100 bytes of the message */
    printf("%s rwm: ", arg);
    for(i=0; (i < 100) && (buf[i]); i++) {
      printf("%c", buf[i]);
    }
    printf("\n");
  }

  if (strncmp(buf, "GNUTELLA OK", 11) == 0) {
    flag=1;
  }
  if (strncmp(buf, "GNUTELLA PASSWORD REQUIRED", 26) == 0) {
    flag=2;
  }

  if (flag==0) {
    return -1;
  }

  read(sock,buf,sizeof(buf));

  return flag;
}

int incoming_gnutella(int sock, struct sockaddr_in *sin)
{
  char *buf;
  int ret;
  gcs *gc;

  gd_s(3, "incoming_gnutella entering\n");

  /* next we need to read the second line of the welcome */
  buf=(char *) xmalloc(100);
  ret=read_line(sock,buf,100);
  gd_s(3, "incoming_gnutella read_line returned ");
  gd_i(3, ret);
  gd_s(3, "\n");

  if (ret<=0) {
    if (gnut_lib_debug>=1) {
      perror("incoming_gnutella, read_line");
    }
    free_str(&buf, 104);
    close(sock);
    return -1;
  }

  /* if authentication is turned on, we must now ask for the 
   * pass phrase */
  if (conf_get_int("password_required")) {
    strcpy(buf,"GNUTELLA PASSWORD REQUIRED\n\n");
    write(sock,buf,strlen(buf));

    /* there should be a one line response, consisting of the
     * pass phrase... if it doesn't match, then we abort, otherwise
     * allow the connection */
    ret=read_line(sock,buf,100);
    gnut_strstrip(buf);
    gd_s(1, "incoming_gnutella \"");
    gd_s(1, buf);
    gd_s(1, "\" attempted as a password\n");
    if (ret<=0 || (strcmp(buf,conf_get_str("password"))!=0)) {
      if (gnut_lib_debug>=1) {
        perror("incoming_gnutella, read_line2");
      }
      free_str(&buf, 105);
      close(sock);
      return -1;
    }
  }

  /* ok we've received it alright, now we can send our reply... */

  if (++num_incoming > conf_get_int("max_incoming")) {
    if (conf_get_int("redirect")) {
      /* drop oldest connection to make room for new */
      gnut_connection_kill_oldest();
    } else {
      num_incoming--;
      /* we have too many incoming! */
      free_str(&buf, 106);
      close(sock);
      return 0;
    }
  }

  /* first we need to enter ourselves into the connection list */
  gc=gnut_connection_new();
  gc->sock=sock;
  gc->ctype=TYPE_INCOMING;
  gc->cstate=STATE_NEGOTIATING;

  memcpy(&(gc->ip.a.s_addr), &sin->sin_addr.s_addr, 4);
  gc->port=ntohs(sin->sin_port);

  strcpy(buf,"GNUTELLA OK\n\n");
  ret=write(sock, buf, strlen(buf));

  if (ret<0) {
    perror("incoming_gnutella, write");
    free_str(&buf, 107);
    gnut_connection_delete(gc);
    num_incoming--;
    close(sock);
    return -1;
  }

  gc->cstate=STATE_CONNECTED;

  ret = connection_loop(gc);
  gd_s(1, "incoming_gnutella connection_loop returned: ");
  gd_i(1, ret);
  gd_s(1, "\n");

  gc->cstate = STATE_CLEANABLE;

  num_incoming--;

  close(gc->sock);
  free_str(&buf, 108);

  gnut_connection_delete(gc);

  gd_s(3,"incoming_gnutella returning success\n");
  return 0;
}

int parse_http_request(char *request, int *ref, char **name)
{
  int iref;
  char *ptr,*ptr2;
  
  gd_s(3, "parse_http_request entering: request=");
  gd_s(3, request);
  gd_s(3, "\n");
  
  ptr=strstr(request,"HTTP/");
  if (ptr == 0) {
    return -1;
  }
  
  gd_s(4, "parse_http_request checked for HTTP\n");
  
  ptr=strstr(request,"/get/");
  if (ptr == 0) {
    return -2;
  }
  
  gd_s(4, "parse_http_request checked for get\n");
  
  ptr+=5;
  ptr2=strchr(ptr,'/');
  if (ptr2 == 0) {
    return -3;
  }

  gd_s(4, "parse_http_request located end slash\n");
  
  *ptr2=0;
  iref=atoi(ptr);
  
  gd_s(4, "parse_http_request located ref num: ");
  gd_i(4, iref);
  gd_s(4, "\n");
  
  ptr2++;
  ptr=strstr(ptr2,"HTTP/");
  ptr--;
  for (;ptr>request && (*ptr)==' ';ptr--) {
  }
  
  if (ptr==request) {
    return -4;
  }
  
  *(++ptr)=0;
  
  *name=strdup(ptr2);
  *ref=iref;
  
  gd_s(4, "parse_http_request returning success\n");
  return 0;
}

int parse_range(char *buf, int size, int *rangemin, int *rangemax)
{
  int i;
  int num,num2;
  char *ptr;
  
  for (i=6;i<strlen(buf) && !isdigit(buf[i]) && buf[i]!='-';i++) {
  }
  
  if (i==strlen(buf)) {
    return 0;
  }
  
  if (buf[i]=='-') {
    num=atoi(&buf[i+1]);
    *rangemin=size-num;
    *rangemax=size;
    return 1;
  }
  
  num=(int) strtol(&buf[i],&ptr,10);
  
  if ((*ptr)!='-') {
    return 0;
  }
  
  ptr++;
  
  if (!isdigit(*ptr)) {
    *rangemin=num;
    *rangemax=size;
    return 1;
  }
  
  num2=(int) strtol(ptr,NULL,10);
  
  *rangemin=num;
  *rangemax=num2;
  return 1;
}

int incoming_transfer_error(int sock, int err, char *message)
{
  /* used to be 404 and "File not found" */
  char a[100];

  sprintf(a, "HTTP %i ", err);
  writestr(sock, a);
  writestr(sock, message);
  writestr(sock, "\r\n\r\n");
  writestr(sock, "There was an error processing your request\r\n");
  writestr(sock, message);
  writestr(sock, "\r\n\r\n");
  
  return 0;
}

int incoming_transfer(int sock, struct sockaddr_in *sin, char *request_line)
{
  gnut_transfer *gt;

  gd_s(1, "incoming_transfer entering num_uploads:");
  gd_i(1, num_uploads);
  gd_s(1, "\n");

  if (++num_uploads > conf_get_int("max_uploads")) {
    num_uploads--;
    incoming_transfer_error(sock, 503, "Upload limit reached");
    write(sock, "The maximum number of uploads at this location has been reached.\r\n", 66);
    shutdown(sock,2);
    close(sock);
    return 0;
  }

  gd_s(3, "incoming_transfer INCOMING TRANSFER!!!\n");
  
  gt=gnut_transfer_new();
  gt->sock=sock;
  gt->type=TYPE_OUTGOING;

  return incoming_transfer_handle(gt,sin,request_line);  
}    

int incoming_transfer_handle(gnut_transfer *gt, struct sockaddr_in *sin,
  char *request_line)
{
  int ret;
  http_request_t *req;

  gd_s(1, "incoming_transfer_handle entering\n");
  
  gt->state=STATE_NEGOTIATING;
  memcpy(gt->ip,&sin->sin_addr.s_addr,4);
  gt->port=ntohs(sin->sin_port);
  gt->rate_limit=0;

  req = http_request_new(gt->sock);
  ret = http_request_parse(req, request_line);
  if (!ret) {
    ret = http_request_read_header(req);
  }
  /* This is not a good request, we want nothing
   * to do with it... shut it down now. */
  if (ret) {
    /* shutdown(gt->sock, 2); */ /* This is done by transfer_cleanup */

    transfer_cleanup(gt, STATE_ERROR);

    return -1;
  }

  gd_s(1, "incoming_transfer_handle Got HTTP request, processing\n");

  /* printf("incoming handle '%s'\n", req->url_file); */

  /* Check for standard Gnutella file GET request */
  if (!strncmp(req->url_file, "/get/", 5)) {
    ret = gnut_http_serve_file(gt, req);
    http_request_delete(req);
    num_uploads--;
    if (!ret) {
      count_uploads++;
    }
    transfer_cleanup(gt, (ret==0) ? STATE_DEAD : STATE_ERROR);
    return ret;
  }
#ifdef GNUT_HTTP_SEARCH
  /* gnut also supports "/" and "/search/...", which provide the
   * HTTP gateway interface. */
  else if ((!strcmp(req->url_file, "/")) && conf_get_int("html_enable")) {
    gd_s(2, "incoming_transfer_handle handling file list...\n");
    ret = gnut_http_file_list(gt, req);
    http_request_delete(req);
    num_uploads--;
    
    transfer_cleanup(gt, (ret==0) ? STATE_DEAD : STATE_ERROR);
    return ret;
  }
#endif

#ifdef GNUT_HTTP_FILE_LIST
  /* Handle a search request. */
  else if ((!strncmp(req->url_file, "/search/", 8)) && conf_get_int("html_enable")) {
    gd_s(2, "incoming_transfer_handle handling search form...\n");
    ret = gnut_http_file_search(gt, req);
    http_request_delete(req);
    num_uploads--;
    transfer_cleanup(gt, (ret==0) ? STATE_DEAD : STATE_ERROR);
    return ret;
  }
#endif

  gd_s(1, "incoming_transfer_handle removing http request\n");
  http_request_delete(req);
  num_uploads--;

  transfer_cleanup(gt,STATE_ERROR);
  
  return ret;
}

int incoming_push(int sock, struct sockaddr_in *sin, char *request)
{
  int ret;
  int i;
  char buf[256];
  uchar guid[16];
  uint32 ref;
  uint16 st;
  char *ptr;
  query_response *qr;  
  gnut_transfer *gt;
  
  gd_s(1, "incoming_push entering sock=");
  gd_i(1, sock);
  gd_s(1, " request=");
  gd_s(1, request);
  gd_s(1, "\n");
  
  ret=1;
  while (ret>0 || errno==EINTR) {
    gd_s(1, "incoming_push calling readline\n");
    ret=read_line(sock,buf,256);
    
    gd_s(1, "incoming_push recvd: ");
    gd_s(1, buf);
    gd_s(1, "\n");
    if (strlen(buf)<3) {
      break;
    }
  }
  
  /* we need to create the appropriate transfer and query struct,
   * then pass them on to the standard downloader.... */
  ref=atoi(&request[4]);
  ptr=strchr(request,':');
  
  if (ptr==0) {
    gd_s(0, "incoming_push AGGG!\n");
    return 0;
  }
  
  ptr++;
  for (i=0;i<16;i++) {
    strncpy(buf,&ptr[i*2],2);
    buf[2]=0;
    guid[i]=strtol(buf,NULL,16);
  }
  gd_s(2, "incoming_push ref=");
  gd_i(2, ref);
  gd_s(2, " GUID=");
  gd_02x(2, guid[0]);
  gd_02x(2, guid[1]);
  gd_02x(2, guid[2]);
  gd_02x(2, guid[3]);
  gd_s(2, "...\n");

  gd_s(1, "incoming_push calling query find...\n");

  qr=query_push_find(guid,ref);

  if (qr==0) {
    /* Usually this happens because a remote host got two copies of
     * a PUSH packet that we sent and has initiated a second connection. */
    gd_s(1, "incoming_push push request not in our list: ");
    gd_s(1, request);
    gd_s(1, "\n");
    close(sock);
    return 0;
  }
  
  gt=gnut_transfer_new();
  gt->sock=sock;
  gt->type=TYPE_RECV_PUSH;
  gt->state=STATE_NEGOTIATING;
  gt->total=qr->size;
  memcpy(gt->ip, qr->qr_ip, 4);
  memcpy(&st,qr->port,2);
  st=GUINT16_FROM_LE(st);
  gt->port=st;
  gt->rate_limit=0;

#if 0
  if (++num_downloads > conf_get_int("max_downloads")) {
    /* enter queued state... */
    gd_s(1, "incoming_push I'm getting queued\n");
    num_downloads--;
    gt->cstate=STATE_QUEUED;
    while ((num_downloads>=conf_get_int("max_downloads")) 
	   && (gt->cstate==STATE_QUEUED)) sleep(1);
    if (gt->cstate!=STATE_QUEUED) {
      transfer_cleanup(gt,STATE_DEAD);
      free_str(&(qr->name), 109);
      free_qr(&qr, 110);
      return 0;
    }
    gd_s(1, "incoming_push I got  unqueued!\n");
    num_downloads++;
    gt->cstate=STATE_NEGOTIATING;
  }
#endif

  num_downloads++;

  ret=gnut_threads_dl_handle(qr, gt, 0, 0);
  
  gd_s(1, "incoming_push gnut_thr_dl_handle returned: ");
  gd_i(1, ret);
  gd_s(1, "\n");
  num_downloads--;
  
  return 0;
}

/* void *gnut_threads_incoming(void *arg)
 *
 * main thread for handling incoming connections, socket is
 * passed via the pointer, it needs to be free'ed.  First it
 * determines whether this is an upload, or gnutella connection,
 * then calls the appropriate handler for each... */
void *gnut_threads_incoming(void *arg)
{
  int sock;
  char buf[256];
  int ret;
  incoming_arg *ia;
    
  gd_s(2,"gnut_threads_incoming entering\n");
  
  ia=arg;
  sock=ia->s;  
  
  fcntl(sock,F_SETFL,0);
  
  ret=read_line(sock,buf,sizeof(buf));
  gd_s(1, "gnut_threads_incoming read_line returned: ");
  gd_i(1, ret);
  gd_s(1, "\n");
  gd_s(1, "the initial line was: ");
  gd_s(1, buf);
  gd_s(1, "\n");
  
  if (ret<=0) {
    close(sock);
    free_ia(&ia, 111);
    return 0;
  }

  if (strncmp(buf,"GNUTELLA CONNECT",16)==0) {
    /* this is an incoming gnutella connection... */
    if (!gnut_blacklist_allow(BLACKLIST_TYPE_INCOMING, 0, &(ia->sin.sin_addr),
			      ia->sin.sin_port)) {
      close(sock);
    } else {
      incoming_gnutella(sock, &ia->sin);
    }
  } else if (strncmp(buf,"GET ",4)==0) {
    if (gc_debug_opts & 4) {
      printf("gth01 %s", buf);
    }

    /* this is an incoming http connection... */
    if (!gnut_blacklist_allow(BLACKLIST_TYPE_HTTP, 0, &(ia->sin.sin_addr),
			      ia->sin.sin_port)) {
      gd_s(1, "gnut_threads_incoming Blacklisted\n");
      close(sock);
    } else {
      incoming_transfer(sock,&ia->sin,buf);
    }
  } else if (strncmp(buf,"GIV ",4)==0) {
    if (gc_debug_opts & 4) {
      printf("gth01 %s", buf);
    }

    /* this is a push connection.... */
    incoming_push(sock,&ia->sin,buf);
  } else {
    if (gc_debug_opts & 4) {
      printf("gth01 %s", buf);
    }

    close(sock);
  }
  free_ia(&ia, 112);
  
  gd_s(3,"gnut_threads_incoming returning\n");
  return 0;
}

Gnut_List *push_crcs = 0;
#ifndef PTHREADS_DRAFT4
pthread_mutex_t push_crc_mutex = PTHREAD_MUTEX_INITIALIZER;
#else
pthread_mutex_t push_crc_mutex;
#endif
Gnut_List *bad_push_ips = 0;

/* Search for a bad IP in the bad push IPs list. Must be called inside
 * mutex lock on the push lists. */
time_t check_push_ip(uint32 tip)
{
  Gnut_List *gl;
  bad_ip *bip;
  time_t rv;

  gl = bad_push_ips;
  rv = 0;
  while(gl) {
    bip = (bad_ip *) (gl->data);
    if (bip) {
      if (bip->ip == tip) {
	rv = bip->when;
	gl = 0;
      }
    }
    if (gl) {
      gl = gl->next;
    }
  }

  return rv;
}

/* Remove a bad IP from the bad push IPs list. Must be called inside mutex
 * lock on the push lists */
void remove_push_ip(uint32 tip)
{
  Gnut_List *gl;
  bad_ip *bip;

  gl = bad_push_ips;
  while(gl) {
    bip = (bad_ip *) (gl->data);
    if (bip) {
      if (bip->ip == tip) {
	bad_push_ips = gnut_list_remove(bad_push_ips, bip);
	free_bip(&bip, 113);
	gl = 0;
      }
    }
    if (gl) {
      gl = gl->next;
    }
  }
}

/* Throw a bad IP address in jail, for a specified number of seconds.
 * Must be called inside push mutex lock. */
void add_push_ip(uint32 tip, uint32 sentence)
{
  bad_ip *bip;

  bip = (bad_ip *) xmalloc(sizeof(bad_ip));
  bip->ip = tip;
  bip->when = time(0) + sentence;
  bad_push_ips = gnut_list_prepend(bad_push_ips, (void *) bip);
}

/* this is passed a push_arg struc
 *
 * it is called from our program in response to a push packet,
 * so it needs to contact the foreign host, do the negotiating,
 * then send the file
 *
 * There are a bunch of different reasons why push requests fail,
 * and most of these can be used to test subsequent push requests
 * to see if they should be attempted.
 *
 *  - Often, multiple identical push requests will arrive over
 *    different GnutellaNet links. This results from other
 *    clients not routing the push packets properly. To
 *    avoid making multiple simultaneous connections, each push
 *    request must be checked to see if it is identical to a
 *    request already in progress.
 *  - Sometimes a duplicate request arrives after the first request
 *    has completed. In this case, the connect works but the host
 *    at the other end does not issue a GET command. These are put
 *    on the "bad list" for 10 seconds.
 *  - I have seen push requests for IP addresses (such as
 *    100.100.100.100 and 123.45.67.89) that do not work and
 *    are clearly intended to be spam. These do not respond to
 *    connects at all. gnut cannot identify a bad IP without
 *    trying to connect, but if a connect fails the IP is put on
 *    the "bad list" for 10 minutes.
 */
void *gnut_threads_push(void *arg)
{
  push_arg *pa;
  gnut_transfer *gt;
  uint16 st;
  char buf_ip[256];
  char buf2[256];
  int ret;
  char *lname; /* leaf name */
  char *request;
  int i;
  struct sockaddr_in sin;
  int reject;
  unsigned long crc1;
  unsigned long ip1;

  gd_s(1, "gnut_threads_push entering\n");
  pa=arg;

  sprintf(buf_ip, "%i.%i.%i.%i", pa->ip[0], pa->ip[1], pa->ip[2], pa->ip[3]);

  lname=pa->si->path;
#ifndef WIN32
  lname=strrchr(pa->si->path, '/');
  if (lname) {
    lname++;
  } else {
    lname=pa->si->path;
  }
#endif

  /* Calculate CRC for this PUSH request */
  crc1 = crc32_string(lname); ip1 = 0;
  for(i=0; i<4; i++) {
    crc32_add8(((uint32 *) (&crc1)), pa->ip[i]);
    ip1 = (ip1 << 8L) | ((unsigned) pa->ip[i]);
  }

  if (gc_debug_opts & 1) {
    printf("PUSH %lu %s %s\n", crc1, buf_ip, lname);
  }

  /* Check for various reasons why we would reject the PUSH request */
  reject = 0;
  if (!(host_ok_for_get(pa->ip))) {
    /* We cannot reach the IP address in the PUSH request. */
    if (gc_debug_opts & 1) {
      printf("PUSH %lu IP is in inaccessible VPN, ignoring.\n", crc1);
    }
    gd_s(1, "gnut_threads_push reject PUSH from inaccessible VPN address\n");
    reject = 1;
  } else if (num_uploads >= conf_get_int("max_uploads")) {
    /* We have enough uploads already going on. We don't queue PUSH
     * requests. */
    if (gc_debug_opts & 1) {
      printf("PUSH %lu upload limit exceeded, ignoring PUSH.\n", crc1);
    }
    gd_s(1, "gnut_threads_push too many uploads!\n");
    reject = 1;
  } else {
    /* Check for duplicate PUSH request already in progress. */
	
    /* NOTE: The check (searching the list) and insert must be done together
     * as an atomic operation */
    pthread_mutex_lock(&push_crc_mutex);
	
    /* Check PUSH's currently in progress, then check previous known
     * bad IPs */
    if (gnut_list_seek(push_crcs, (void *) crc1)) {
      /* Yup, it's a duplicate */
      if (gc_debug_opts & 1) {
	printf("PUSH %lu already in progress through another thread.\n", crc1);
      }
      gd_s(1, "gnut_threads_push ignoring duplicate PUSH\n");
      reject = 1;
    } else {
      int32 whenbad;
      int32 now;

      now = (int32) time(0);
      whenbad = (int32) check_push_ip(ip1);
      if (whenbad) {
	if (whenbad > now) {
	  /* This IP is blacklisted and its sentence has not yet
           * been served */
	  if (gc_debug_opts & 1) {
	    printf("PUSH %lu bad push IP, ignoring request.\n", crc1);
	  }
	  gd_s(1, "gnut_threads_push ignoring bad push IP\n");
	  reject = 1;
	} else {
	  /* This IP was bad, but it has served its time and may
	   * now go free */
	  if (gc_debug_opts & 1) {
	    printf("PUSH %lu IP was bad, but it's been a while.\n", crc1);
	  }
	  remove_push_ip(ip1);
	}
      } else {
	/* IP isn't in bad list, so we're okay. */
      }
    }

    if (reject == 0) {
      /* join the active push list */
      push_crcs = gnut_list_prepend(push_crcs, (void *) crc1);
    }

    pthread_mutex_unlock(&push_crc_mutex);
  }

  if (reject) {
    /* Forget push request, clean up */
    free_str(&(pa->si->path), 114);
    free_str(&(pa->si->upath), 115);
    free_str(&(pa->si->fpath), 116);
    free_si(&(pa->si), 117);
    free_pa(&pa, 118);
    return 0;
  }

  num_uploads++;
  
  gt = gnut_transfer_new();
  
  gt->type = TYPE_SEND_PUSH;
  gt->state = STATE_CONNECTING;
  gt->substate = 1;
  gt->total = pa->si->size;
  gt->fname = strdup(pa->si->path);
  memcpy(gt->ip, pa->ip, 4);

  memcpy(&st, pa->port, 2);
  st=GUINT16_FROM_LE(st);

  gt->port=st;
  gt->rate_limit=0;
  
  gd_s(1, "gnut_threads_push created new transfer struct\n");

  gt->sock=make_connection(buf_ip, /* In gnut_net.c */
			   gt->port, gt->ip, 1, MT_FOR_PUSH, gt, 0);
  if (gt->sock<0) {
    if (gc_debug_opts & 1) {
      printf("PUSH %lu make_conn failed.\n", crc1);
    }

    pthread_mutex_lock(&push_crc_mutex);
    push_crcs = gnut_list_remove(push_crcs, (void *) crc1);
    add_push_ip(ip1, 600); /* 10 minutes on the no-no list */
    pthread_mutex_unlock(&push_crc_mutex);

    gd_s(1, "gnut_threads_push couldn't connect to ");
    gd_s(1, buf_ip);
    gd_s(1, ":");
    gd_i(1, st);
    gd_s(1, " make_conn returned: ");
    gd_i(1, gt->sock);
    gd_s(1, "\n");
    close(gt->sock);
    free_str(&(pa->si->path), 119);
    free_str(&(pa->si->upath), 120);
    free_str(&(pa->si->fpath), 121);
    free_si(&(pa->si), 122);
    free_pa(&pa, 123);
    num_uploads--;
    
    transfer_cleanup(gt, STATE_ERROR);

    return 0;
  }
  
  memcpy(&sin.sin_addr.s_addr, gt->ip, 4);
  sin.sin_port=gt->port;

  gd_s(1, "gnut_threads_push made connection to ");
  gd_s(1, buf_ip);
  gd_s(1, ":");
  gd_i(1, st);
  gd_s(1, "\n");

  gt->state=STATE_NEGOTIATING;
  
  request=(char *) xmalloc(4096);
  
  sprintf(request, "GIV %i:", pa->si->ref);
  
  for(i=0; i<16; i++) {
    sprintf(buf2, "%02X", (uchar) (conf_get_str("guid"))[i]);
    strcat(request, buf2);
  }
  
  sprintf(buf2, "/%s\n\n", lname);
  
  strcat(request, buf2);

  write(gt->sock, request, strlen(request));
  
  gd_s(1, "gnut_threads_push connect message sent:\n");
  gd_s(1, request);
  gd_s(1, "\n");
  
  /* now we look at the http response and make sure that the
   * correct number of newlines is returned.... */
  while (1) {
    gd_s(1, "gnut_threads_push trying to read line\n");
    ret=read_line(gt->sock, buf2, sizeof(buf2));
    if (ret<=0) {
      if (gc_debug_opts & 1) {
	printf("PUSH %lu remote did not send 'GET', closing.\n", crc1);
      }

      pthread_mutex_lock(&push_crc_mutex);
      push_crcs = gnut_list_remove(push_crcs, (void *) crc1);
      add_push_ip(ip1, 10); /* only 10 seconds, because it's probably just
			     * a duplicate and satisfied past push request */
      pthread_mutex_unlock(&push_crc_mutex);

      gd_s(1, "gnut_threads_push free'ing request\n");
      free_str(&request, 124);
      gd_s(1, "gnut_threads_push closing sock\n");
      close(gt->sock);
      gd_s(1, "gnut_threads_push free'ing pa->si->path\n"); 
      free_str(&(pa->si->path), 125);
      gd_s(1, "gnut_threads_push free'ing pa->si\n");
      free_si(&(pa->si), 126);
      gd_s(1, "gnut_threads_push free'ing pa\n");
      free_pa(&pa, 127);
      num_uploads--;
      
      transfer_cleanup(gt, STATE_ERROR);

      return 0;
    }
    gd_s(1, "gnut_threads_push received: ");
    gd_s(1, buf2);
    if (strncmp(buf2, "GET ", 4)==0) {
      break;
    }
  }

  if (gc_debug_opts & 1) {
    printf("PUSH %lu data transfer begun\n", crc1);
  }

  /* now that we've gotten the GET, we can treat this as any normal
   * file upload, so we'll pass it on to the normal upload handler... */
  ret = incoming_transfer_handle(gt, &sin, buf2);
  
  gd_s(2,"gnut_threads_push ITH returned, now cleaning up\n");

  if (gc_debug_opts & 1) {
    printf("PUSH %lu data transfer done, returned %d\n", crc1, ret);
  }
  
  free_str(&request, 128);
  
  gd_s(2, "gnut_threads_push request free'ed and sock closed\n");

  free_str(&(pa->si->path), 129);
  free_str(&(pa->si->upath), 130);
  free_str(&(pa->si->fpath), 131);
  free_si(&(pa->si), 132);
  free_pa(&pa, 133);
  
  pthread_mutex_lock(&push_crc_mutex);
  push_crcs = gnut_list_remove(push_crcs, (void *) crc1);
  pthread_mutex_unlock(&push_crc_mutex);

  gd_s(2, "gnut_threads_push push_arg struc free'ed\n");
  
  return 0;
}    

int gnut_threads_dl_handle(query_response *qr1, gnut_transfer *gt,
			   int *retry_enable, int *tried_push)
{
  char buf[2048];
  int startp=0;
  char *dpath;
  char *request;
  char *shortname;
  int ret;
  char *dest_dir,*c,*c2;
  int cache_download=0;
  struct stat ss;
#if !defined(HAVE_FLOCK) && defined(HAVE_FCNTL)
  struct flock lock;
#endif
  int overlap;
  char *overlap_buf;
  
  if (qr1->dest_cache) {
    cache_download=1;
  }
  
  /* !!HERE!! is where the entry point needs to be for push connections... */
  gt->state=STATE_NEGOTIATING;
  
  /* first we're going to check to see if the file already exists,
   * if so, then we'll formulate a partial request and use it instead */
  shortname=strrchr(qr1->name,'/');
  if (shortname) {
    shortname++;
  } else {
    shortname=qr1->name;
  }
  dest_dir=(qr1->dest_cache ? "cache_path" : "download_path");
  
  if (!conf_get_str(dest_dir)) {
    if (cache_download) {
      gd_s(1, "gnut_thr_dl_handle no cache dir set.\n");
      cache_unlock();
      return -1;
    }
    gt->fname=(char *) xmalloc(strlen(shortname)+7);
    strcpy(gt->fname,shortname);
    strcpy(buf,gt->fname);
    strcat(gt->fname,".gnut");
  } else {
    gt->fname=(char *) xmalloc(strlen(conf_get_str(dest_dir)) + strlen(qr1->name)+250);
    gt->fname[0]=0;
    dpath=expand_path(conf_get_str(dest_dir));
    if (strlen(dpath)>1) {
      strcpy(gt->fname,dpath);
      if (dpath[strlen(dpath)-1]!=path_slash[0]) {
	strcat(gt->fname,path_slash);
      }
    }
    strcat(gt->fname,shortname);
    strcpy(buf,gt->fname);
    strcat(gt->fname,".gnut");
    free_str(&dpath, 134);
  }
  
  /* first we're going to see if the un-munged name exists, if so
   * then always make the download fail, because purportedly a good file
   * already exists */
  gd_s(1, "gnut_thr_dl_handle checking if file \"");
  gd_s(1, buf);
  gd_s(1, "\" exists\n");

  ret=stat(buf, &ss);

  if (ret != -1) {
    /* woah! this file already exists, we shouldn't download it */
    if (conf_get_int("verbose") & 1) {
      printf(UI_TH_FILE_EXISTS, buf);
    }
    if (cache_download) {
      cache_unlock();
    }

    if (retry_enable) {
      if (*retry_enable) {
	if (gc_debug_opts & 8) {
	  printf("retry disable 01 (finished transfer already exists)\n");
	}
        *retry_enable = 0;
      }
    }

    transfer_cleanup(gt, STATE_ERROR);
	
    return -1;
  }  

  gd_s(1, "gnut_thr_dl_handle testing file \"");
  gd_s(1, gt->fname);
  gd_s(1, "\" for size\n");

  overlap = 0;
  overlap_buf = 0;
  
  gt->fsock = open(gt->fname, O_RDONLY);
  if (gt->fsock >= 0) {
    gd_s(1, "gnut_thr_dl_handle file exists!\n");
    /* the file exists, so we'll find out its size... */
    startp = lseek(gt->fsock, 0, SEEK_END);
    if (gc_debug_opts & 16) {
      printf("End of existing file is %d\n", startp);
    }
    gd_s(1, "gnut_thr_dl_handle start=");
    gd_i(1, startp);
    gd_s(1, "\n");
    if (startp < 0) {
      startp = 0;
    } else if (startp > 512) {
      overlap_buf = xmalloc(512);
      if (overlap_buf) {
	overlap = 512;
	startp -= overlap; /* %%% operlap checking isn't implemented
			      yet, we just blindly overlap always. This
			      eliminates error-text turds but not
			      variant content errors. Still need to
			      merge file I/O */
      }
    } else {
      startp = 0;
    }

#ifdef WIN32
#undef close
    close(gt->fsock);
#define close(x) closesocket(x)
#else
    close(gt->fsock);
#endif
  }

  gd_s(3, "gnut_thr_dl_handle checked for existing, start=");
  gd_i(3, startp);
  gd_s(3, "\n");

  /* now that we're connected, we can send our request message.... */
  request=(char *) xmalloc(strlen(qr1->name)+2049);

  /* Write out the initial GET */
  sprintf(request, "GET /get/%i/%s HTTP/1.0\r\n", qr1->ref, qr1->name);
  write(gt->sock, request, strlen(request));

  /* Write out the user-specified headers for HTTP client emulation */
  {
    char *c;
    sprintf(request, "%s", conf_get_str("http_client_headers"));
    /* Do backslash escapes. Only one is provided -- '\n' becomes an HTTP
     * standard line-break CRLF. All other '\' sequences are left untouched.
     * conveniently, we can turn '\n' into CRLF without having to shift
     * the rest of the string */
    for(c=request; *c;) {
      if (*c == '\\') {
        if (*(c+1) == 'n') {
	  *c++ = '\r';
	  *c++ = '\n';
	}
      }
      if (*c) {
	c++;
      }
    }
    if (gc_debug_opts & 4) {
      printf("gdh02 Sending HTTP client header:\n%s", request);
    }
    write(gt->sock, request, strlen(request));
  }

  /* Write out the range header, if any */
  if (startp == 0) {
    /* We want the whole file -- no range necessary */
  } else {
    sprintf(request, "Range: bytes=%i-\r\n", startp);
  }
  write(gt->sock,request,strlen(request));

  /* Write out the final crlf */
  sprintf(request, "\r\n");
  write(gt->sock,request,strlen(request));
  
  gd_s(3,"gnut_thr_dl_handle connect message sent\n");
  
  /* now we look at the http response and make sure that the
   * correct number of newlines is returned.... */
  
  while (1) {
    ret=read_line(gt->sock, buf, sizeof(buf));
    if (gc_debug_opts & 4) {
      printf("gdh01 %s", buf);
    }

    if ((ret <= 0) || strstr(buf, "HTTP 4")) {
      if (cache_download) {
	cache_unlock();
      }
      free_str(&request, 135);

      if (strstr(buf, "HTTP 4")) {
        if (retry_enable) {
	  if (*retry_enable) {
	    if (gc_debug_opts & 8) {
	      printf("retry disable 11 (HTTP 4xx response)\n");
	    }
	    *retry_enable = 0;
	  }
	}
      }
      if (overlap_buf) {
	free_str(&overlap_buf, 136);
      }
	  
      transfer_cleanup(gt,STATE_ERROR);
	  
      return -1;
    }
    if (strlen(buf)<=2) {
      break;
    }
  }
  
  gd_s(3, "gnut_thr_dl_handle read http headers...\n");

  /* We used to open with O_WRONLY | O_CREAT | O_APPEND, but once overlap
   * is implemented we'll need to be able to read (in order to verify that
   * it's the same file) and in order to reset the pointer we cannot
   * have O_APPEND. */
  gt->fsock = open(gt->fname, O_RDWR | O_CREAT, OCTAL(6,6,6));

  if (gt->fsock < 0) {
    fprintf(stderr, UI_TH_CANT_WRITE, gt->fname);
  }

  ret = 0;
#ifdef HAVE_FLOCK
  ret=flock(gt->fsock,LOCK_EX | LOCK_NB);
#elif defined(HAVE_FCNTL)
  lock.l_type=F_WRLCK;
  lock.l_whence=SEEK_SET;
  lock.l_start=0;
  lock.l_len=0;
  ret=fcntl( gt->fsock, F_SETLK, &lock );
#endif

  if ((gt->fsock>0) && (ret>=0)) {
    int wh;

    gt->state = STATE_CONNECTED;
    gt->bytes = startp;

    if (gc_debug_opts & 16) {
      printf("lseek %d", startp);
    }
    wh = lseek(gt->fsock, startp, SEEK_SET);
    if (gc_debug_opts & 16) {
      printf(" returned %d\n", wh);
    }

    count_downloads++;
	
    if (gt->type==TYPE_RECV_PUSH) {
      /* we need to remove this query struc */
      gd_s(1, "gnut_thr_dl_handle removing query from list\n");
      query_push_remove(qr1);
    }

    ret=gnut_transfer_loop(gt, overlap, overlap_buf);

    if (overlap_buf) {
      free_str(&overlap_buf, 137);
    }

    /* do not create incomplete cache files, and don't leave *any*
     * incomplete files that are less than the cutoff set by the user */
    if (ret != 0) {
      if (cache_download) {
	unlink(gt->fname);
      } else if (gt->bytes < conf_get_int("leave_turds")) {
	unlink(gt->fname);
      }
    }

    if (ret==0) {
      /* Successful download */
      if (retry_enable) {
	if (*retry_enable) {
	  if (gc_debug_opts & 8) {
	    printf("retry disable 03 (success, general)\n");
	  }
	  *retry_enable = 0;
	}
      }

      if (conf_get_int("verbose") & 1) {
	if (gt->type==TYPE_RECV_PUSH) {
	  printf(UI_TH_PDL_SUCCEED, gt->fname);
	} else {
	  printf(UI_TH_GDL_SUCCEED, gt->fname);
	}
      }

      /* since we  succeeded, let's change the name to what 
       * it originally was intended to be */
      if (strlen(gt->fname)>5) {
	c=&gt->fname[strlen(gt->fname)-5];
	gd_s(1, "gnut_thr_dl_handle c=");
	gd_s(1, c);
	gd_s(1, "\n");
	if (strncmp(c,".gnut",5)==0) {
	  gd_s(1, "gnut_thr_dl_handle attempt rename file...\n");
	  c2=strdup(gt->fname);
	  *c=0;
	  rename(c2,gt->fname);
	  /* %%% Here's where we would test for a renaming error.
           * any attempted fix should include proper handling of the
	   * extension, if any. */
	}
      }
      
      if (gt->type==TYPE_RECV_PUSH) {
	gd_s(1, "gnut_thr_dl_handle finishing off query\n");
	/* we succeeded, so we can finish off the query struc */
	if (retry_enable) {
	  if (*retry_enable) {
	    if (gc_debug_opts & 8) {
	      printf("retry disable 04 (success, push)\n");
	    }
	    *retry_enable = 0;
	  }
	}
      }
    } else {
      int allow_push;

      if (conf_get_int("verbose") & 1) {
	if (gt->state != STATE_CONNECTED) {
	  if (*retry_enable) {
	    if (gc_debug_opts & 8) {
	      printf("retry disable 13 (by 'stop' command)\n");
	    }
	    *retry_enable = 0;
	  }
	  allow_push = 0;
	} else {
	  if (gt->type==TYPE_RECV_PUSH) {
	    printf(UI_TH_PDL_FAILED, gt->fname, gt->bytes);
	  } else {
	    printf(UI_TH_GDL_FAILED, gt->fname, gt->bytes);
	  }
	}
      }

      allow_push = 1;

      /* If there is no retry, then we don't push */
      if (conf_get_int("auto_download_retry") == 0) {
	if (retry_enable) {
	  if (*retry_enable) {
	    if (gc_debug_opts & 8) {
	      printf("retry disable 05 (by user setting, lost)\n");
	    }
	    *retry_enable = 0;
	  }
	  allow_push = 0;
	}
      }

      /* If we've already tried push once, we don't try again */
      if (tried_push) {
	if(*tried_push) {
	  allow_push = 0;
	}
      }

      /* Check to see if user completely disallowed push retries */
      if (conf_get_int("retry_push") <= 0) {
	allow_push = 0;
      }

      /* Check to see if user selected rfc-1597 compliant push retries */
      if ((conf_get_int("retry_push") == 1)
	  && (!(host_ok_for_push(qr1->qr_ip)))) {
	allow_push = 0;
      }

      /* If this transfer was a direct (non-push) and we got a
       * significant number of bytes, then we assume a direct download
       * is a workable way to get the file, and we don't have to do
       * the push thang */
      if ((gt->type != TYPE_RECV_PUSH) && (gt->bytes >= 512)) {
	allow_push = 0;
      }

      if (allow_push) {
	/* If this was a push connection, we put the push request back
	 * on the list to allow another future one to come in. If this
	 * wasn't a push connection, we need to create a new push
	 * record and send the push request packet */
	if (gt->type != TYPE_RECV_PUSH) {
	  gnutella_packet *gpa;

	  if (conf_get_int("verbose") & 1) {
	    printf(UI_TH_DL_STPUSH, qr1->name);
	  }

	  gpa=gp_push_make(conf_get_str("mac"), conf_get_int("ttl"),
			   qr1->guid, qr1->ref, net_local_ip(),
			   conf_get_int("local_port"));

	  send_to_all(gpa);

	  free_v(&(gpa->data), 138);
	  free_gpa(&gpa, 139);

	  if (tried_push) {
	    *tried_push = 1;
	  }
	}

	gd_s(1, "gnut_thr_dl_handle adding query back to list\n");
	query_push_add(qr1);
      }
    }
    if (gc_transferlog) {
      fprintf(gc_transferlog, "%d\tr\t%i.%i.%i.%i:%i\t%d\t%d\t%d\t%s\n",
	      (int) time(0), gt->ip[0], gt->ip[1], gt->ip[2], gt->ip[3],
	      gt->port, gt->bytes, gt->total, gt->rate_bytes >> 4L,
	      gt->fname);
    }
    gd_s(4, "gnut_thr_dl_handle decreasing downloads\n");  
  }
  
  gd_s(3, "gnut_thr_dl_handle closing up transfer\n");
  free_str(&request, 140);
  
  if (cache_download) {
    cache_timestamp();
    rescan_cache();
    cache_unlock();
  }
  
  transfer_cleanup(gt,STATE_DEAD);
  
  gd_s(1, "gnut_thr_dl_handle returning ");
  gd_i(1, ret);
  gd_s(1, "\n");
  return ret;
}

/* gnut_th_dl sets up a connection for downloading a file.
 * this is passed a query_response struc... */
void *gnut_th_dl(void *arg, int *retry_enable, int *tried_push, int sl)
{
  gnut_transfer *gt;
  query_response *qr2;
  gnutella_packet *gpa;
  char buf[100];
  uint16 st;
  int ret;
  
  gd_s(2, "gnut_th_dl entering\n");

  qr2=arg;
  
  if (qr2->dest_cache) {
    /* only one cache download at once */
    if (! cache_lock()) {
      if (retry_enable) {
	if (*retry_enable) {
	  if (gc_debug_opts & 8) {
	    printf("retry disable 06 (mutex cache)\n");
	  }
	  *retry_enable = 0;
	}
      }
      return 0;
    }
	
    /* empty enough space in the cache */
    if (cache_shrink(qr2->size) < 0) {
      gd_s(2, "gnut_th_dl aborting download - couldn't free enough cache space.\n");
      if (retry_enable) {
	if (*retry_enable) {
	  if (gc_debug_opts & 8) {
	    printf("retry disable 07 (cache full)\n");
	  }
	  *retry_enable = 0;
	}
      }
      return 0;
    }
  }

  /* Put ourselves in the transfers list */
  gt=gnut_transfer_new();
  gt->type=TYPE_INCOMING;
  gt->state=STATE_RETRY_WAIT;
  gt->total=qr2->size;
  memcpy(gt->ip, qr2->qr_ip, 4);
  memcpy(&st,qr2->port,2);
  st=GUINT16_FROM_LE(st);
  gt->port=st;
  gt->rate_limit=0;
  gt->fname=strdup(qr2->name);

  gd_s(3, "gnut_th_dl created new transfer struc, and set up\n");

  /* Sleep, if requested by our caller */
  if (sl) {
    if (gc_debug_opts & 8) {
      printf(UI_TH_DL_RETRY, sl);
    }
    gd_s(1, "gnut_th_dl retry-sleep ");
    gd_i(1, sl);
    gd_s(1, "\n");
    while ((sl > 0) && (gt->state == STATE_RETRY_WAIT)) {
      sleep(1);
      sl--;
    }
  }

  if (gt->state != STATE_RETRY_WAIT) {
    gd_s(3, "gnut_th_dl state was changed while we waited for retry\n");
    transfer_cleanup(gt, STATE_DEAD);
    if (retry_enable) {
      if (*retry_enable) {
	if (gc_debug_opts & 8) {
	  printf("retry disable 08 (stopped in retry-sleep)\n");
	}
	*retry_enable = 0;
      }
    }
    return 0;
  }

  gt->state = STATE_CONNECTING;
  gt->substate = 2;

  if (++num_downloads > conf_get_int("max_downloads")) {
    /* enter queued state... */
    gd_s(1, "gnut_th_dl I'm queued\n");
    num_downloads--;
    
    if (conf_get_int("verbose") & 1) {
      printf(UI_TH_DL_QUEUED, gt->fname);
    }
    gt->state=STATE_QUEUED;
    while ((num_downloads >= conf_get_int("max_downloads")) 
	   && (gt->state == STATE_QUEUED)) {
      sleep(1);
    }
    /* Now check to see if the transfer was killed while we were
       sleeping */
    if (gt->state != STATE_QUEUED) {
      transfer_cleanup(gt, STATE_DEAD);
      if (retry_enable) {
	if (*retry_enable) {
	  if (gc_debug_opts & 8) {
	    printf("retry disable 12 (stopped while queued)\n");
	  }
	  *retry_enable = 0;
	}
      }
      return 0;
    }
    gd_s(1, "gnut_th_dl and now I'm not!\n");
    num_downloads++;
    gt->state = STATE_CONNECTING;
    gt->substate = 3;
  }
  
  sprintf(buf, "%i.%i.%i.%i", qr2->qr_ip[0], qr2->qr_ip[1],
	  qr2->qr_ip[2], qr2->qr_ip[3]);
  
  gt->sock = make_connection(buf,  /* In gnut_net.c */
			     st, gt->ip, 1, MT_FOR_DOWNLOAD, gt,
			     retry_enable);
  
  if (gt->sock < 0) {
    int allow_push;

    allow_push = 1;

    /* If there is no retry, then we don't push */
    if (conf_get_int("auto_download_retry") == 0) {
      if (retry_enable) {
	if (*retry_enable) {
	  if (gc_debug_opts & 8) {
	    printf("retry disable 09 (by user setting, blocked)\n");
	  }
	  *retry_enable = 0;
	}
	allow_push = 0;
      }
    }

    /* If we've already tried push once, we don't try again */
    if (tried_push) {
      if (*tried_push) {
	allow_push = 0;
      }
    }

    /* Check to see if user completely disallowed push retries */
    if (conf_get_int("retry_push") == 0) {
      allow_push = 0;
    }
    
    /* Check to see if user selected rfc-1597 compliant push retries */
    if ((conf_get_int("retry_push") == 1)
	&& (!(host_ok_for_push(qr2->qr_ip)))) {
      allow_push = 0;
    }

    num_downloads--;

    /* ignore push cache requests (FIXME - Ray) */
    if (qr2->dest_cache) {
      cache_unlock();
      transfer_cleanup(gt, STATE_ERROR);

      if (retry_enable) {
	if (*retry_enable) {
	  if (gc_debug_opts & 8) {
	    printf("retry disable 10 (dest cache)\n");
	  }
	  *retry_enable = 0;
	}
      }
      return 0;
    }

    gd_s(1, "gnut_th_dl couldn't connect to ");
    gd_s(1, buf);
    gd_s(1, ":");
    gd_i(1, st);
    gd_s(1, " make_conn returned: ");
    gd_i(1, gt->sock);
    gd_s(1, "\n");

    transfer_cleanup(gt,STATE_ERROR);

    if (allow_push) {
      /* what I'm going to do now is send out a push request for the file
       * we just couldn't get.... maybe it will work on occasion */

      if (conf_get_int("verbose") & 1) {
	printf(UI_TH_DL_STPUSH, qr2->name);
      }

      if (tried_push) {
	*tried_push = 1;
      }

      gpa=gp_push_make(conf_get_str("mac"), conf_get_int("ttl"),
		       qr2->guid, qr2->ref, net_local_ip(),
		       conf_get_int("local_port"));

      query_push_add(qr2);  
	  
      send_to_all(gpa);
      free_v(&(gpa->data), 141);
      free_gpa(&gpa, 142);
    }
    
    return 0;
  }
  gt->port=st;

  gd_s(3, "gnut_th_dl made connection to ");
  gd_s(3, buf);
  gd_s(3, ":");
  gd_i(3, st);
  gd_s(3, "\n");

  free_str(&(gt->fname), 143);
  ret = gnut_threads_dl_handle(qr2, gt, retry_enable, tried_push);

  num_downloads--;
  return 0;
}  

void *gnut_threads_dl(void *arg)
{
  query_response *qr3;
  int retry_enable;
  int tried_push;
  int sl;

  qr3 = arg;
  retry_enable = qr3->qr_retr_enable;
  tried_push = 0;

  sl = 0;
  gnut_th_dl(arg, &retry_enable, &tried_push, sl);

  sl = 8;
  while (conf_get_int("auto_download_retry") && retry_enable) {
    /* Exponential backoff algorithm */
    if (sl < conf_get_int("auto_download_retry")) {
      /* They set a positive integer */
      sl = ((sl * 5) / 4) + 1;
    } else {
      /* They set a negative integer, or we've reached the limit */
      sl = conf_get_int("auto_download_retry");
      /* Check for negative */
      if (sl < 0) {
	sl = 0 - sl;
      }
    }

    /* Okay, try that download again! */
    gnut_th_dl(arg, &retry_enable, &tried_push, sl);
  }

  free_str(&(qr3->name), 144);
  free_qr(&qr3, 145);

  return 0;
}

/* void *gnut_threads_outgoing(void *arg)
 *
 * This is the main thread for handling an outgoing GnutellaNet connection.
 * It does the connecting, negotiates with the remote servant, transfers
 * the packets and cleans up after itself... arg is a pointer to a string
 * that indicates the host to connect to */
void *gnut_threads_outgoing(void *arg)
{
  gcs *mgc;
  char *ptr;
  int ret;
  uchar cport[2];
  uint16 mport;
  struct hostent *he;
  
  gd_s(1, "gnut_thr_outgoing entering arg=");
  gd_s(1, arg);
  gd_s(1, "\n");
  
  num_outgoing++;
  
  mgc=gnut_connection_new();
  
  mgc->ctype = TYPE_OUTGOING;
  mgc->cstate = STATE_CONNECTING;

  gnut_strdelimit(arg, ":", ' ');
  
  ptr=strchr(arg, ' ');
  if (ptr) {
    *(ptr++)=0;
    mgc->port=atoi(ptr);
  } else {
    mgc->port=6346;
  }
  
  mport=GUINT16_TO_LE(mgc->port);
  memcpy(cport,&mport,2);

  /* inet_aton(arg, (struct in_addr *) mgc->ip); */
  he = gethostbyname(arg);
  if (he == 0) {
    mgc->cstate=STATE_CLEANABLE;
    gnut_connection_delete(mgc);
    free_v(&arg, 146);
    num_outgoing--;
    return 0;
  }
  memcpy(mgc->ip.b, he->h_addr_list[0], 4);

  mgc->sock = make_connection(arg,  /* In gnut_net.c */
			      mgc->port, mgc->ip.b, 0, MT_FOR_GNET, 0, 0);

  if  (mgc->sock<0) {
    gd_s(1, "gnut_thr_outgoing couldn't connect to: ");
    gd_s(1, arg);
    gd_s(1, ":");
    gd_i(1, mgc->port);
    gd_s(1, "\n");
    mgc->cstate=STATE_CLEANABLE;
	
    host_remove(mgc->ip.b, cport);
	
    gnut_connection_delete(mgc);
    free_v(&arg, 147);
    num_outgoing--;
    return 0;
  }
  
  mgc->cstate=STATE_NEGOTIATING;
  
  ret=send_welcome_message(mgc->sock, "0.4");
  
  if (ret<0) {
    gd_s(1, "gnut_thr_outgoing couldn't send message\n");
    close(mgc->sock);
	
    host_remove(mgc->ip.b, cport);
	
    gnut_connection_delete(mgc);
    gd_s(1, "gnut_thr_outgoing free'ing arg\n");
    free_v(&arg, 148);
    gd_s(1, "gnut_thr_outgoing done free'ing arg\n");
    num_outgoing--;
    pthread_exit(0);
    return 0;
  }
  
  ret=read_welcome_message(mgc->sock, arg);

  if (ret==2) {
    char buf[256];
    /* a password was requested, we'll try sending the
     * one in our config */
    sprintf(buf,"%s\n", conf_get_str("password"));
	
    write(mgc->sock,buf,strlen(buf));
	
    /* now we need to repeat the read for a welcome message */
    ret=read_welcome_message(mgc->sock, arg);
    if (ret<0) {
      if (strlen(conf_get_str("password"))) {
	printf(UI_TH_INCOR_PASS);
      }
    }
  }

  if (ret < 0) {
    gd_s(1, "gnut_thr_outgoing couldn't read welcome message\n");
    close(mgc->sock);

    host_remove(mgc->ip.b, cport);
    gnut_connection_delete(mgc);
    gd_s(1, "gnut_thr_outgoing free'ing arg\n");
    free_v(&arg, 0); /* %%% double-free error 149, disabled now because I
		      * don't know why it happens (suspect ymalloc 269). */
    gd_s(1, "gnut_thr_outgoing done free'ing arg\n");
    num_outgoing--;
    pthread_exit(0);
    return 0;
  }
  
  mgc->cstate = STATE_CONNECTED;

  /* Read and write packets */
  ret = connection_loop(mgc);
  
  mgc->cstate = STATE_CLEANABLE;
  num_outgoing--;
  close(mgc->sock);
  host_remove(mgc->ip.b, cport);

  gnut_connection_delete(mgc);
  free_v(&arg, 150);

  pthread_exit(0);
  return 0;
}

#ifdef PTHREADS_DRAFT4
#include <pthread.h>
/* Pthreads draft 4 implements a number of things differently, one of which
 * is that you can't statically initalise mutexes. kxp@atl.hp.com  */
 
void gnut_mutexes_init() {
  pthread_mutex_init(&gc_list_mutex,pthread_mutexattr_default);
  pthread_mutex_init(&query_packet_mutex,pthread_mutexattr_default);
  pthread_mutex_init(&send_mutex, pthread_mutexattr_default);
  pthread_mutex_init(&_g_debug_mutex,pthread_mutexattr_default);
  pthread_mutex_init(&make_conn_mutex, pthread_mutexattr_default);
  pthread_mutex_init(&transfer_mutex,pthread_mutexattr_default);
  pthread_mutex_init(&host_mutex,pthread_mutexattr_default);
  pthread_mutex_init(&monitor_mutex,pthread_mutexattr_default);
  pthread_mutex_init(&query_mutex, pthread_mutexattr_default);
  pthread_mutex_init(&push_mutex, pthread_mutexattr_default);
  pthread_mutex_init(&route_mutex, pthread_mutexattr_default);
  pthread_mutex_init(&share_mutex, pthread_mutexattr_default);
  pthread_mutex_init(&cache_mutex, pthread_mutexattr_default);
  pthread_mutex_init(&push_crc_mutex, pthread_mutexattr_default);
  pthread_mutex_init(&qreplies_mutex,pthread_mutexattr_default);
}
#endif
