/**
    manage lists of proxies and connect to a proxy

    Copyright (c) 2020-2022 The Creators of Simphone

    See the file COPYING.LESSER.txt for copying permission.
**/

#include "config.h"
#include "spth.h"

#include "logger.h"
#include "table.h"
#include "error.h"
#include "utils.h"
#include "system.h"
#include "file.h"
#include "network.h"
#include "mainline.h"
#include "contact.h"
#include "param.h"
#include "proto.h"
#include "proxy.h"
#include "proxies.h"
#include "server.h"
#include "client.h"
#include "api.h"

#include <string.h>
#include <stdlib.h>
#include <time.h>

#ifndef _WIN32
#include <arpa/inet.h>
#endif

#define SIM_MODULE "proxies"

#define FILE_PROXIES "proxies"

#define PROXY_KEY_ARRAY "LRU"     /* PROXY_KEY_ARRAY elements follow */
#define PROXY_KEY_IP4 "ip4"       /* value is struct _network_ip_port */
#define PROXY_KEY_SEEN_FIRST "sf" /* value is a simnumber (PROXY_SEEN_FIRST) */
#define PROXY_KEY_SEEN_LAST "sl"  /* value is a simnumber (PROXY_SEEN_LAST) */

#define PROXY_SEEN_FIRST 0 /* time when first added */
#define PROXY_SEEN_LAST 1  /* time of last stable proxy loop */

#define PROXY_LIST_MINE 0 /* live contacts with incoming connectivity */
#define PROXY_LIST_NEW 1  /* new candidate proxies found */
#define PROXY_LIST_GOOD 2 /* saved (good) proxies */
#define PROXY_LIST_OLD 3  /* saved proxies that failed to connect */
#define PROXY_LIST_BAD 4  /* blacklisted proxies */

#define PROXY_LIST_LOOKUP_NAME(proxies, name) ((proxies) ? proxy_list_names[(proxies) - (proxy_lists)] : (name))
static const char *proxy_list_names[] = { "myproxies", "newproxies", "proxies", "oldproxies", "badproxies" };

static struct proxies {
  struct proxies *next; /* next proxy in linked list - must be first element of structure */
  const char *addr;     /* contact address or else a constant string that indicates origin */
  unsigned senderip;    /* originator ip address of probe (if PROXY_PROBE_DHT) or zero */
  unsigned ip;          /* ip address */
  unsigned short port;  /* port number */
  simnumber seen[2];    /* last seen time (indexed by PROXY_SEEN_xxx) or zero if never seen */
  simtype file;         /* the rest of the data (empty table) */
} * proxy_lists[5];     /* lists of proxies (indexed by PROXY_LIST_xxx) */

static pth_t tid_proxy = NULL; /* proxy thread identifier or NULL if not running */

static simtype proxy_file;              /* contents of proxies file (empty table) */
static simunsigned proxy_score = 0;     /* score of currently connected proxy */
static simunsigned proxy_score_max = 0; /* maximal proxy score (power of two) */

static void proxy_list_new (struct proxies **proxies, simtype file, const char *address, unsigned ip, int port) {
  struct proxies *proxy = sim_new (sizeof (*proxy));
  memset (proxy, 0, sizeof (*proxy));
  proxy->next = *proxies;
  proxy->addr = address;
  proxy->ip = ip, proxy->port = (unsigned short) port;
  proxy->file = file;
  *proxies = proxy;
}

static int proxy_list_load (void) {
  unsigned i, n = 0;
  simtype array = nil (), ip;
  int err = file_load (FILE_PROXIES, FILE_TYPE_ENCRYPTED, &proxy_file);
  for (i = 0; i < SIM_ARRAY_SIZE (proxy_lists); i++)
    proxy_lists[i] = NULL;
  if (err == SIM_OK && (array = table_detach_array_table (proxy_file, PROXY_KEY_ARRAY)).typ != SIMNIL)
    for (i = 1; i <= array.len; i++)
      if ((ip = table_detach_string (array.arr[i], PROXY_KEY_IP4)).typ != SIMNIL) {
        struct _network_ip_port *node = ip.ptr;
        simtype file = array_detach (array, i);
        proxy_list_new (&proxy_lists[PROXY_LIST_GOOD], file, "FILE", ntohl (node->ip), ntohs (node->port));
        proxy_lists[PROXY_LIST_GOOD]->seen[PROXY_SEEN_FIRST] = table_detach_number (file, PROXY_KEY_SEEN_FIRST);
        proxy_lists[PROXY_LIST_GOOD]->seen[PROXY_SEEN_LAST] = table_detach_number (file, PROXY_KEY_SEEN_LAST);
        string_free (ip);
        n++;
      }
  array_free (array);
  LOG_CODE_DEBUG_ (proxy_log_lists (SIM_MODULE, SIM_LOG_DEBUG));
  LOG_DEBUG_ ("loaded %u/%u proxies\n", n, array.len);
  return err;
}

static unsigned proxy_list_count (struct proxies **proxies) {
  unsigned count = 0;
  const struct proxies *proxy;
  for (proxy = *proxies; proxy; proxy = proxy->next)
    count++;
  return count;
}

#define proxy_list_count2(proxies1, proxies2) (proxy_list_count (proxies1) + proxy_list_count (proxies2))
#define proxy_list_count3() (proxy_list_count2 (&proxy_lists[PROXY_LIST_OLD], &proxy_lists[PROXY_LIST_GOOD]) + \
                             proxy_list_count (&proxy_lists[PROXY_LIST_NEW]))

static simbool proxy_list_check_test (const struct proxies *proxy) {
  int port;
  unsigned i;
  simtype testproxies = param_get_strings ("proxy.test");
  for (i = testproxies.len; i; i--)
    if (sim_network_parse_ip_port (testproxies.arr[i].ptr, &port) == proxy->ip && port == proxy->port)
      return true;
  return ! strcmp (proxy->addr, "TEST");
}

int proxy_list_save (void) {
  int err = SIM_OK;
  LOG_CODE_DEBUG_ (proxy_log_lists (SIM_MODULE, SIM_LOG_DEBUG));
  if (param_get_number ("proxy.max") > 0) {
    unsigned i, n = 0;
    const struct proxies *proxy;
    simtype array, ip;
    if (proxy_file.typ == SIMNIL)
      proxy_file = table_new (2);
    array = array_new_tables (i = proxy_list_count2 (&proxy_lists[PROXY_LIST_OLD], &proxy_lists[PROXY_LIST_GOOD]));
    for (proxy = proxy_lists[PROXY_LIST_GOOD]; i; i--) {
      if (! proxy)
        proxy = proxy_lists[PROXY_LIST_OLD];
      if (! proxy_list_check_test (proxy) && ! proxy_check_blacklisted (proxy->ip, proxy->port)) {
        struct _network_ip_port *node = (ip = string_new (CONTACT_SIZE_IP_PORT)).ptr;
        node->ip = htonl (proxy->ip), node->port = htons (proxy->port);
        array.arr[++n] = proxy->file.typ == SIMNIL ? table_new (2) : table_copy (proxy->file, proxy->file.len);
        table_add (array.arr[n], PROXY_KEY_IP4, ip);
        table_add_number (array.arr[n], PROXY_KEY_SEEN_FIRST, proxy->seen[PROXY_SEEN_FIRST]);
        table_add_number (array.arr[n], PROXY_KEY_SEEN_LAST, proxy->seen[PROXY_SEEN_LAST]);
      }
      proxy = proxy->next;
    }
    LOG_DEBUG_ ("saving %u-%u=%u/%u proxies\n",
                array.len, proxy_list_count (&proxy_lists[PROXY_LIST_BAD]), n, param_get_number ("proxy.max"));
    array.len = n;
    table_add (proxy_file, PROXY_KEY_ARRAY, array);
    err = file_save (proxy_file, FILE_PROXIES, FILE_TYPE_ENCRYPTED);
    table_delete (proxy_file, PROXY_KEY_ARRAY);
  }
  return event_test_error (NULL, SIM_EVENT_ERROR_FILE_SAVE, err);
}

static void proxy_list_free (struct proxies **proxies) {
  struct proxies *proxy = *proxies;
  *proxies = NULL;
  while (proxy) {
    struct proxies *p = proxy;
    table_free (proxy->file);
    proxy = p->next;
    sim_free (p, sizeof (*p));
  }
}

static void *proxy_list_find (struct proxies **proxies, unsigned ip, int port, simbool exact) {
  struct proxies *proxy;
  for (proxy = *proxies; proxy; proxy = proxy->next)
    if (proxy->ip == ip) {
      if (! exact && proxy->port == SIM_PROTO_PORT && port != SIM_PROTO_PORT) {
        LOG_DEBUG_ ("%s:%s:%d = %d '%s'\n", PROXY_LIST_LOOKUP_NAME (proxies, NULL),
                    network_convert_ip (proxy->ip), proxy->port, port, CONTACT_GET_NICK (proxy->addr));
        proxy->port = (unsigned short) port;
      }
      if (proxy->port == port)
        break;
      if (! exact && port == SIM_PROTO_PORT) {
        LOG_DEBUG_ ("%s:%s:%d == %d '%s'\n", PROXY_LIST_LOOKUP_NAME (proxies, NULL),
                    network_convert_ip (proxy->ip), proxy->port, port, CONTACT_GET_NICK (proxy->addr));
        break;
      }
    }
  return proxy;
}

#define PROXY_LIST_DELETE(proxies, proxy) proxy_list_prepend (NULL, proxies, proxy, (proxy)->addr)

/* move proxy from src to start of dst. if src is NULL, prepend new proxy. if dst is NULL, delete proxy. */
static void proxy_list_prepend (struct proxies **dst, struct proxies **src, struct proxies *proxy,
                                const char *address) {
  if (LOG_PROTECT_ (SIM_MODULE, SIM_LOG_DEBUG)) {
    LOG_DEBUG_ ("%s", PROXY_LIST_LOOKUP_NAME (dst, "NULL"));
    if (proxy->addr != address)
      LOG_DEBUG_ (" '%s'", CONTACT_GET_NICK (address));
    LOG_DEBUG_ (" <- %s:%s:%d '%s'\n", PROXY_LIST_LOOKUP_NAME (src, "NEW"),
                network_convert_ip (proxy->ip), proxy->port, CONTACT_GET_NICK (proxy->addr));
    LOG_UNPROTECT_ ();
  }
  proxy->addr = address;
  if (src && ! sim_list_delete (src, proxy)) {
    if (dst)
      LOG_ERROR_ ("zombie %s <- %s:%s:%d '%s'\n",
                  PROXY_LIST_LOOKUP_NAME (dst, "NULL"), PROXY_LIST_LOOKUP_NAME (src, "NEW"),
                  network_convert_ip (proxy->ip), proxy->port, CONTACT_GET_NICK (proxy->addr));
  } else if (dst) {
    proxy->next = *dst;
    *dst = proxy;
  } else {
    table_free (proxy->file);
    sim_free (proxy, sizeof (*proxy));
  }
}

/* move proxy from src to end of dst. if src is NULL, append new proxy. */
static void proxy_list_append (struct proxies **dst, struct proxies **src, struct proxies *proxy) {
  struct proxies *p;
  LOG_DEBUG_ ("%s:%s:%d '%s' -> %s\n", PROXY_LIST_LOOKUP_NAME (src, "NEW"), network_convert_ip (proxy->ip),
              proxy->port, CONTACT_GET_NICK (proxy->addr), PROXY_LIST_LOOKUP_NAME (dst, "NULL"));
  for (p = *dst; p && p->next; p = p->next) {}
  if (p != proxy) {
    if (! src || sim_list_delete (src, proxy)) {
      sim_list_append (dst, p, proxy);
    } else
      LOG_ERROR_ ("zombie %s:%s:%d '%s' -> %s\n", PROXY_LIST_LOOKUP_NAME (src, "NEW"), network_convert_ip (proxy->ip),
                  proxy->port, CONTACT_GET_NICK (proxy->addr), PROXY_LIST_LOOKUP_NAME (dst, "NULL"));
  }
}

int proxy_blacklist (const char *address, unsigned ip, int port) {
  struct proxies *proxy = NULL;
  if (proxy_list_find (&proxy_lists[PROXY_LIST_BAD], ip, port, false))
    return SIM_PROXY_BLACKLISTED;
  proxy_list_new (&proxy, nil (), address, ip, port);
  proxy->seen[PROXY_SEEN_FIRST] = time (NULL);
  proxy_list_prepend (&proxy_lists[PROXY_LIST_BAD], NULL, proxy, address);
  if (! strcmp (address, "BLOCKED"))
    if (! proxy_list_find (&proxy_lists[PROXY_LIST_MINE], ip, port, true))
      if (! proxy_list_find (&proxy_lists[PROXY_LIST_NEW], ip, port, true))
        if (! proxy_list_find (&proxy_lists[PROXY_LIST_GOOD], ip, port, true))
          if (! proxy_list_find (&proxy_lists[PROXY_LIST_OLD], ip, port, true))
            return SIM_NAT_REVERSE_NO_PROXY;
  return SIM_OK;
}

simbool proxy_check_blacklisted (unsigned ip, int port) {
  return proxy_list_find (&proxy_lists[PROXY_LIST_BAD], ip, port, false) != NULL;
}

void proxy_probe (const char *address, unsigned ip, int port, unsigned probe) {
  static const char *proxy_probe_names[] = { "DHT", "IP", "CONTACT", "NOCONTACT" };
  simbool exact = probe == PROXY_PROBE_NOCONTACT;
  struct proxies *proxy, **oldproxies = &proxy_lists[PROXY_LIST_MINE], **proxies = NULL;
  unsigned senderip = 0;
  if (probe != PROXY_PROBE_DHT && ! strcmp (address, "DHT")) {
    senderip = probe;
    probe = PROXY_PROBE_DHT;
  }
  if (simself.state != CLIENT_STATE_RUNNING || simself.status == SIM_STATUS_OFF || contact_list.test & 0x100 << probe)
    return;
  LOG_DEBUG_ ("probe %s %s:%d '%s'\n", proxy_probe_names[probe],
              network_convert_ip (ip), port, CONTACT_GET_NICK (address));
  if ((proxy = proxy_list_find (&proxy_lists[PROXY_LIST_MINE], ip, port, exact)) == NULL) {
    if (probe == PROXY_PROBE_NOCONTACT)
      return;
    proxy = proxy_list_find (proxies = oldproxies = &proxy_lists[PROXY_LIST_GOOD], ip, port, exact);
    if (! proxy && (proxy = proxy_list_find (&proxy_lists[PROXY_LIST_OLD], ip, port, exact)) != NULL)
      proxies = oldproxies = &proxy_lists[PROXY_LIST_OLD];
    if (! proxy) {
      if ((proxy = proxy_list_find (&proxy_lists[PROXY_LIST_NEW], ip, port, exact)) == NULL) {
        proxy_list_new (&proxy, nil (), address, ip, port);
        proxy->seen[PROXY_SEEN_FIRST] = time (NULL);
        oldproxies = NULL;
      } else
        proxies = oldproxies = &proxy_lists[PROXY_LIST_NEW];
      if (probe == PROXY_PROBE_DHT) {
        if (! oldproxies) {
          unsigned max = abs (param_get_number ("proxy.max"));
          if (! max || proxy_list_count (&proxy_lists[PROXY_LIST_NEW]) < max) {
            proxy->senderip = senderip;
            proxy_list_append (&proxy_lists[PROXY_LIST_NEW], NULL, proxy);
          } else
            LOG_WARN_ ("probe DROP from %s\n", network_convert_ip (senderip));
        }
        return;
      }
    }
  }
  if (probe != PROXY_PROBE_CONTACT) {
    if (probe == PROXY_PROBE_NOCONTACT && proxy->addr == address) {
      proxy_list_prepend (&proxy_lists[PROXY_LIST_MINE], &proxy_lists[PROXY_LIST_MINE], proxy, NULL);
    } else if (proxies) {
      struct proxies **newproxies = probe == PROXY_PROBE_IP ? &proxy_lists[PROXY_LIST_GOOD] : proxies;
      proxy_list_prepend (newproxies, oldproxies, proxy, proxy->addr);
    }
  } else if (! proxy_check_blacklisted (ip, port)) {
    proxy_list_prepend (&proxy_lists[PROXY_LIST_MINE], oldproxies, proxy, address);
    if (param_get_number ("proxy.strangers") >= 0)
      client_cancel_proxy (SIM_PROXY_NOT_PREFERRED);
  }
}

void proxy_cancel_probe_ (unsigned senderip, int seconds) {
  struct proxies *proxy;
  unsigned count = 0;
  for (proxy = proxy_lists[PROXY_LIST_NEW]; proxy; proxy = proxy->next)
    if (proxy->senderip == senderip) {
      LOG_DEBUG_ ("cancel %s:%s:%d '%s'\n", PROXY_LIST_LOOKUP_NAME (&proxy_lists[PROXY_LIST_NEW], NULL),
                  network_convert_ip (proxy->ip), proxy->port, CONTACT_GET_NICK (proxy->addr));
      proxy->ip = 0;
      count++;
    }
  LOG_WARN_ ("count = %u, waiting %d seconds...\n", proxy_list_count3 (), seconds);
  if (proxy_list_count (&proxy_lists[PROXY_LIST_NEW]) > count)
    while (seconds-- > 0 && simself.state != CLIENT_STATE_STOPPED && simself.status != SIM_STATUS_OFF)
      pth_sleep_ (1);
}

void proxy_set_score (simcontact contact, int error, int score) {
  simunsigned n;
  simnumber s = score == PROXY_SCORE_OK ? proxy_score_max / 2 : 0;
  if (score == PROXY_SCORE_INIT) {
    proxy_score = (proxy_score_max = (simunsigned) 1 << param_get_number_min ("client.verify", 9)) - 1;
    for (n = 1; n <= contact_list.array.len; n++)
      ((simcontact) contact_list.array.arr[n].ptr)->verify = 0;
    return;
  }
  if (proxy_score_max == 1)
    return;
  if (! s && ! contact) {
    simunsigned tick = system_get_tick ();
    for (n = 1; n <= contact_list.array.len; n++)
      if ((contact = contact_list.array.arr[n].ptr)->verify)
        if (contact->verify <= tick) {
          proxy_score >>= 1;
          LOG_DEBUG_ ("verify error (%d seconds) SCORE = 0x%llX '%s'\n",
                      (int) (tick - contact->verify) / 1000, proxy_score, contact->nick.str);
          contact->verify = 0;
        }
  } else if (s) {
    proxy_score = (proxy_score >> 1) + s;
    if (contact->verify) {
      LOG_DEBUG_ ("verify ok (%d seconds) SCORE = 0x%llX '%s'\n",
                  (int) (system_get_tick () - contact->verify) / 1000, proxy_score, contact->nick.str);
    } else
      LOG_DEBUG_ ("verify ok SCORE = 0x%llX '%s'\n", proxy_score, contact->nick.str);
    contact->verify = 0;
  } else {
    proxy_score >>= 1;
    LOG_DEBUG_ ("verify error %d SCORE = 0x%llX '%s'\n", error, proxy_score, contact->nick.str);
  }
  if (! s)
    for (n = proxy_score_max >> 1; n; n >>= 1)
      s += proxy_score & n ? 2 : -1;
  if (s < 0)
    client_cancel_proxy (SIM_PROXY_BLACKLISTED);
}

static void *thread_proxy_ (void *arg) {
  simunsigned tick = system_get_tick (), t;
  unsigned retry = param_get_number_min ("proxy.retry", param_get_default_number ("proxy.retry", 0)) * 1000;
  unsigned count = proxy_list_count3 ();
  LOG_API_DEBUG_ ("count = %u\n", count);
  while (simself.state != CLIENT_STATE_STOPPED) {
    int err = SIM_NO_ERROR, max = abs (param_get_number ("proxy.max"));
    int strangers = param_get_number ("proxy.strangers");
    struct proxies *proxy, *oldproxy = NULL, **proxies;
    if (simself.status == SIM_STATUS_OFF || simself.state != CLIENT_STATE_RUNNING) {
    next:
      pth_sleep_ (1);
      continue;
    }
    if (! proxy_check_required (true)) {
      simbool upnp = (simself.flags & SIM_STATUS_FLAG_UPNP) != 0;
      pth_sleep_ (1);
      if (simself.status == SIM_STATUS_OFF || param_get_number ("proxy.require") < 0)
        continue;
      if (! upnp || simself.flags & SIM_STATUS_FLAG_UPNP) {
        if (! retry || (simnumber) (t = system_get_tick ()) - simself.tickin < retry)
          continue;
        simself.flags &= ~SIM_STATUS_FLAG_UPNP;
        simself.tickin = t;
        network_periodic (NETWORK_CMD_START);
      }
      simself.oldflags = simself.flags & SIM_STATUS_FLAG_SSL_IN;
      simself.flags &= ~(SIM_STATUS_FLAG_TCP_IN | SIM_STATUS_FLAG_SSL_IN);
      LOG_CODE_DEBUG_ (network_log_flags (SIM_MODULE, SIM_LOG_DEBUG));
      retry = param_get_number_min ("proxy.retry", param_get_default_number ("proxy.retry", 0)) * 1000;
    }
    if (strangers > 0 && system_get_tick () - tick <= (unsigned) strangers * 1000)
      strangers = 0;
    if (strangers && ! main_search_test (PROXY_ADDRESS_PROXY, 0))
      main_search_start (PROXY_ADDRESS_PROXY, 0, MAIN_MODE_PASSIVE);
    if (strangers < 0 || ((proxy = proxy_lists[PROXY_LIST_MINE]) == NULL && strangers)) {
      if ((proxy = *(proxies = &proxy_lists[PROXY_LIST_GOOD])) == NULL) {
        if ((proxy_lists[PROXY_LIST_GOOD] = proxy = proxy_lists[PROXY_LIST_OLD]) != NULL)
          LOG_INFO_ ("moving %u oldproxies to proxies\n", proxy_list_count (&proxy_lists[PROXY_LIST_GOOD]));
        proxy_lists[PROXY_LIST_OLD] = NULL;
      }
      if (! proxy || (err = proxy_loop_proxy_ (NULL, proxy->ip, proxy->port, proxy->senderip)) == SIM_NO_ERROR) { /* could not connect at all */
        if (proxy && proxy_list_find (&proxy_lists[PROXY_LIST_MINE], proxy->ip, proxy->port, true) != proxy) {
          if (max && proxy_list_count2 (&proxy_lists[PROXY_LIST_OLD], &proxy_lists[PROXY_LIST_GOOD]) > (unsigned) max) {
            PROXY_LIST_DELETE (&proxy_lists[PROXY_LIST_GOOD], proxy);
          } else
            proxy_list_append (&proxy_lists[PROXY_LIST_OLD], &proxy_lists[PROXY_LIST_GOOD], oldproxy = proxy);
        }
        if ((proxy = *(proxies = &proxy_lists[PROXY_LIST_NEW])) != NULL)
          if (proxy_list_find (&proxy_lists[PROXY_LIST_MINE], proxy->ip, proxy->port, true) != proxy) {
            count -= count != 0;
            err = proxy->ip ? proxy_loop_proxy_ (NULL, proxy->ip, proxy->port, proxy->senderip) : SIM_NO_ERROR;
            if (proxy_list_find (&proxy_lists[PROXY_LIST_GOOD], proxy->ip, proxy->port, true) == proxy) {
              proxies = &proxy_lists[PROXY_LIST_GOOD];
            } else if (err == SIM_NO_ERROR)
              PROXY_LIST_DELETE (&proxy_lists[PROXY_LIST_NEW], proxy);
            if (err != SIM_NO_ERROR && oldproxy && max)
              if (proxy_list_count2 (&proxy_lists[PROXY_LIST_OLD], &proxy_lists[PROXY_LIST_GOOD]) >= (unsigned) max)
                PROXY_LIST_DELETE (&proxy_lists[PROXY_LIST_OLD], oldproxy); /* delete old (unreachable) proxy to make space for new one */
          }
      }
      if (err != SIM_NO_ERROR) {
        proxy->seen[PROXY_SEEN_LAST] = time (NULL);
        if (proxy_list_find (&proxy_lists[PROXY_LIST_MINE], proxy->ip, proxy->port, true) != proxy) {
          if (err == SIM_OK) { /* disconnected by proxy */
            proxy_list_append (&proxy_lists[PROXY_LIST_GOOD], proxies, proxy);
          } else /* socket was cancelled */
            proxy_list_prepend (&proxy_lists[PROXY_LIST_GOOD], proxies, proxy, proxy->addr);
        }
      }
    } else if (proxy) {
      const char *addr = proxy->addr;
      if (addr && (err = proxy_loop_proxy_ (addr, proxy->ip, proxy->port, proxy->senderip)) != SIM_NO_ERROR) {
#ifdef DONOT_DEFINE
        simcontact contact = contact_list_find_address (addr); /* last chance to resurrect dead proxy connection to live contact */
        if (contact && contact->client && nat_get_connected (contact->client))
          client_send_cmd (contact->client, SIM_CMD_REQUEST_PROXY, NULL, nil (), NULL, nil ());
#endif
        proxy->seen[PROXY_SEEN_LAST] = time (NULL);
      }
      PROXY_LIST_DELETE (&proxy_lists[PROXY_LIST_MINE], proxy);
      count += (! count) + 1;
    } else
      goto next;
    if (err != SIM_NO_ERROR) {
      LOG_DEBUG_ ("deleting %u newproxies\n", proxy_list_count (&proxy_lists[PROXY_LIST_NEW]));
      proxy_list_free (&proxy_lists[PROXY_LIST_NEW]);
      count = proxy_list_count2 (&proxy_lists[PROXY_LIST_OLD], &proxy_lists[PROXY_LIST_GOOD]);
    } else if (! count || ! --count) { /* wait a while to avoid doing (and logging) a whole lot */
      int sec = param_get_number ("proxy.delay");
      const struct proxies *myproxy = proxy_lists[PROXY_LIST_MINE];
      const struct proxies *newproxy = proxy_lists[PROXY_LIST_NEW];
      proxy = proxy_lists[PROXY_LIST_GOOD];
      oldproxy = proxy_lists[PROXY_LIST_OLD];
      LOG_WARN_ ("count = 0, waiting...\n");
      main_search_start (PROXY_ADDRESS_PROXY, 0, MAIN_MODE_PASSIVE);
      while (sec-- && simself.state != CLIENT_STATE_STOPPED && simself.status != SIM_STATUS_OFF) {
        if (myproxy != proxy_lists[PROXY_LIST_MINE] || newproxy != proxy_lists[PROXY_LIST_NEW])
          break;
        if (proxy != proxy_lists[PROXY_LIST_GOOD] || oldproxy != proxy_lists[PROXY_LIST_OLD])
          break;
        pth_sleep_ (1);
        if (! main_search_test (PROXY_ADDRESS_PROXY, 0) && main_get_status () > MAIN_DHT_DISCONNECT)
          main_search_start (PROXY_ADDRESS_PROXY, 0, MAIN_MODE_PASSIVE);
      }
      count = proxy_list_count3 ();
    }
    LOG_DEBUG_ ("count = %u\n", count);
  }
  LOG_API_DEBUG_ ("\n");
  return pth_thread_exit_ (true);
}

int proxy_list_init_ (void) {
  int err = proxy_list_load (), port;
  if (err == SIM_OK) {
    unsigned i;
    simtype testproxies = param_get_strings ("proxy.test");
    for (i = testproxies.len; i; i--) {
      unsigned ip = sim_network_parse_ip_port (testproxies.arr[i].ptr, &port);
      if (ip && port > 0 && port < 0x10000) {
        int test = contact_list.test;
        contact_list.test &= ~0xF00;
        proxy_probe ("TEST", ip, port, param_get_number ("proxy.strangers") < 0 ? PROXY_PROBE_IP : PROXY_PROBE_CONTACT);
        contact_list.test = test;
      }
    }
    if ((err = proxy_init (true)) != SIM_OK) {
      server_uninit_ (true);
      event_send_name (NULL, SIM_EVENT_ERROR, SIM_EVENT_ERROR_INIT_LOCAL, number_new (err));
    }
    if ((err = pth_thread_spawn (thread_proxy_, NULL, &tid_proxy, -1)) != SIM_OK) {
      proxy_list_uninit_ ();
    } else
      LOG_DEBUG_SIMTYPE_ (testproxies, 0, "init ");
  }
  return err;
}

int proxy_list_uninit_ (void) {
  unsigned i;
  int err = pth_thread_join_ (&tid_proxy, NULL, thread_proxy_, -1);
  proxy_list_save ();
  for (i = 0; i < SIM_ARRAY_SIZE (proxy_lists); i++)
    proxy_list_free (&proxy_lists[i]);
  table_free (proxy_file), proxy_file = nil ();
  proxy_uninit ();
  return err;
}

void proxy_log_lists (const char *module, int level) {
  unsigned i;
  for (i = 0; i < SIM_ARRAY_SIZE (proxy_lists); i++) {
    simbool first = true;
    const struct proxies *proxy;
    for (proxy = proxy_lists[i]; proxy; proxy = proxy->next) {
      simnumber seen = proxy->seen[PROXY_SEEN_LAST];
      if (first)
        log_any_ (module, level, "[%s]\n", PROXY_LIST_LOOKUP_NAME (&proxy_lists[i], NULL));
      first = false;
      log_any_ (module, level, "%s:%d:%s", network_convert_ip (proxy->ip), proxy->port, CONTACT_GET_NICK (proxy->addr));
      if (proxy->senderip)
        log_any_ (module, level, ":%s", network_convert_ip (proxy->senderip));
      if (seen && (seen = time (NULL) - seen) >= 0)
        log_any_ (module, level, " <%lld:%02d:%02d>", seen / 3600, (int) (seen / 60 % 60), (int) (seen % 60));
      if ((seen = proxy->seen[PROXY_SEEN_FIRST]) != 0) {
        char buf[SIM_SIZE_TIME];
        sim_convert_time_to_string (seen, buf);
        log_any_ (module, level, " %s", buf);
      }
      log_any_ (module, level, "\n");
    }
  }
  log_any_ (module, level, "VERIFY = 0x%llX\n", proxy_score);
}
