/**
   * Perform the actual version check by connecting to the version server.
   *
   * @param data_to_send version message
   * @return version reply
   * @throws Exception if the server check connection fails
   */
  private Map performVersionCheck(
      Map data_to_send, boolean use_az_message, boolean use_http, boolean v6) throws Exception {
    Exception error = null;
    Map reply = null;

    if (use_http) {

      try {
        reply = executeHTTP(data_to_send, v6);

        reply.put("protocol_used", "HTTP");

        error = null;
      } catch (IOException e) {
        error = e;
      } catch (Exception e) {
        Debug.printStackTrace(e);
        error = e;
      }
    }

    if (reply == null && use_az_message) {

      try {
        reply = executeAZMessage(data_to_send, v6);

        reply.put("protocol_used", "AZMSG");
      } catch (IOException e) {
        error = e;
      } catch (Exception e) {
        Debug.printStackTrace(e);
        error = e;
      }
    }
    if (error != null) {

      throw (error);
    }

    if (Logger.isEnabled())
      Logger.log(
          new LogEvent(
              LOGID,
              "VersionCheckClient server "
                  + "version check successful. Received "
                  + reply.size()
                  + " reply keys."));

    if (v6) {

      last_check_time_v6 = SystemTime.getCurrentTime();

    } else {

      last_check_time_v4 = SystemTime.getCurrentTime();
    }

    return reply;
  }
  public AuthenticatorWindow() {
    SESecurityManager.addPasswordListener(this);

    // System.out.println( "AuthenticatorWindow");

    Map cache = COConfigurationManager.getMapParameter(CONFIG_PARAM, new HashMap());

    try {
      Iterator it = cache.entrySet().iterator();

      while (it.hasNext()) {

        Map.Entry entry = (Map.Entry) it.next();

        String key = (String) entry.getKey();
        Map value = (Map) entry.getValue();

        String user = new String((byte[]) value.get("user"), "UTF-8");
        char[] pw = new String((byte[]) value.get("pw"), "UTF-8").toCharArray();

        auth_cache.put(key, new authCache(key, new PasswordAuthentication(user, pw), true));
      }

    } catch (Throwable e) {

      COConfigurationManager.setParameter(CONFIG_PARAM, new HashMap());

      Debug.printStackTrace(e);
    }
  }
  protected String[] getAuthenticationDialog(final String realm, final String tracker) {
    final Display display = SWTThread.getInstance().getDisplay();

    if (display.isDisposed()) {

      return (null);
    }

    final AESemaphore sem = new AESemaphore("SWTAuth");

    final authDialog[] dialog = new authDialog[1];

    TOTorrent torrent = TorrentUtils.getTLSTorrent();

    final String torrent_name;

    if (torrent == null) {

      torrent_name = null;

    } else {

      torrent_name = TorrentUtils.getLocalisedName(torrent);
    }

    try {
      display.asyncExec(
          new AERunnable() {
            public void runSupport() {
              dialog[0] = new authDialog(sem, display, realm, tracker, torrent_name);
            }
          });
    } catch (Throwable e) {

      Debug.printStackTrace(e);

      return (null);
    }
    sem.reserve();

    String user = dialog[0].getUsername();
    String pw = dialog[0].getPassword();
    String persist = dialog[0].savePassword() ? "true" : "false";

    if (user == null) {

      return (null);
    }

    return (new String[] {user, pw == null ? "" : pw, persist});
  }
  protected void saveAuthCache() {
    try {
      this_mon.enter();

      HashMap map = new HashMap();

      Iterator it = auth_cache.values().iterator();

      while (it.hasNext()) {

        authCache value = (authCache) it.next();

        if (value.isPersistent()) {

          try {
            HashMap entry_map = new HashMap();

            entry_map.put("user", value.getAuth().getUserName().getBytes("UTF-8"));
            entry_map.put("pw", new String(value.getAuth().getPassword()).getBytes("UTF-8"));

            map.put(value.getKey(), entry_map);

          } catch (Throwable e) {

            Debug.printStackTrace(e);
          }
        }
      }

      COConfigurationManager.setParameter(CONFIG_PARAM, map);

    } finally {

      this_mon.exit();
    }
  }
  public void runSupport() {
    byte[] input_buffer = new byte[request_dg.getLength()];

    System.arraycopy(request_dg.getData(), 0, input_buffer, 0, input_buffer.length);

    int packet_data_length = input_buffer.length;

    String auth_user = null;
    byte[] auth_user_bytes = null;
    byte[] auth_hash = null;

    if (server.isTrackerPasswordEnabled()) {

      // auth detail should be attached to the packet. Auth details are 16
      // bytes

      if (input_buffer.length < 17) {

        Logger.log(
            new LogEvent(
                LOGID,
                LogEvent.LT_WARNING,
                "TRTrackerServerProcessorUDP: " + "packet received but authorisation missing"));

        return;
      }

      packet_data_length -= 16;

      auth_user_bytes = new byte[8];

      auth_hash = new byte[8];

      System.arraycopy(input_buffer, packet_data_length, auth_user_bytes, 0, 8);

      int user_len = 0;

      while (user_len < 8 && auth_user_bytes[user_len] != 0) {

        user_len++;
      }

      auth_user = new String(auth_user_bytes, 0, user_len);

      System.arraycopy(input_buffer, packet_data_length + 8, auth_hash, 0, 8);
    }

    DataInputStream is =
        new DataInputStream(new ByteArrayInputStream(input_buffer, 0, packet_data_length));

    try {
      String client_ip_address = request_dg.getAddress().getHostAddress();

      PRUDPPacketRequest request = PRUDPPacketRequest.deserialiseRequest(null, is);

      Logger.log(
          new LogEvent(
              LOGID, "TRTrackerServerProcessorUDP: packet received: " + request.getString()));

      PRUDPPacket reply = null;
      TRTrackerServerTorrentImpl torrent = null;

      if (auth_user_bytes != null) {

        // user name is irrelevant as we only have one at the moment

        // <parg_home> so <new_packet> = <old_packet> + <user_padded_to_8_bytes> + <hash>
        // <parg_home> where <hash> = first 8 bytes of sha1(<old_packet> + <user_padded_to_8> +
        // sha1(pass))
        // <XTF> Yes

        byte[] sha1_pw = null;

        if (server.hasExternalAuthorisation()) {

          try {
            URL resource = new URL("udp://" + server.getHost() + ":" + server.getPort() + "/");

            sha1_pw = server.performExternalAuthorisation(resource, auth_user);

          } catch (MalformedURLException e) {

            Debug.printStackTrace(e);
          }

          if (sha1_pw == null) {

            Logger.log(
                new LogEvent(
                    LOGID,
                    LogEvent.LT_ERROR,
                    "TRTrackerServerProcessorUDP: auth fails for user '" + auth_user + "'"));

            reply = new PRUDPPacketReplyError(request.getTransactionId(), "Access Denied");
          }
        } else {

          sha1_pw = server.getPassword();
        }

        // if we haven't already failed then check the PW

        if (reply == null) {

          SHA1Hasher hasher = new SHA1Hasher();

          hasher.update(input_buffer, 0, packet_data_length);
          hasher.update(auth_user_bytes);
          hasher.update(sha1_pw);

          byte[] digest = hasher.getDigest();

          for (int i = 0; i < auth_hash.length; i++) {

            if (auth_hash[i] != digest[i]) {

              Logger.log(
                  new LogEvent(
                      LOGID,
                      LogEvent.LT_ERROR,
                      "TRTrackerServerProcessorUDP: auth fails for user '" + auth_user + "'"));

              reply = new PRUDPPacketReplyError(request.getTransactionId(), "Access Denied");

              break;
            }
          }
        }
      }

      int request_type = TRTrackerServerRequest.RT_UNKNOWN;

      if (reply == null) {

        if (server.isEnabled()) {

          try {
            int type = request.getAction();

            if (type == PRUDPPacketTracker.ACT_REQUEST_CONNECT) {

              reply = handleConnect(client_ip_address, request);

            } else if (type == PRUDPPacketTracker.ACT_REQUEST_ANNOUNCE) {

              Object[] x =
                  handleAnnounceAndScrape(
                      client_ip_address, request, TRTrackerServerRequest.RT_ANNOUNCE);

              if (x == null) {

                throw (new Exception("Connection ID mismatch"));
              }

              reply = (PRUDPPacket) x[0];
              torrent = (TRTrackerServerTorrentImpl) x[1];

              request_type = TRTrackerServerRequest.RT_ANNOUNCE;

            } else if (type == PRUDPPacketTracker.ACT_REQUEST_SCRAPE) {

              Object[] x =
                  handleAnnounceAndScrape(
                      client_ip_address, request, TRTrackerServerRequest.RT_SCRAPE);

              if (x == null) {

                throw (new Exception("Connection ID mismatch"));
              }

              reply = (PRUDPPacket) x[0];
              torrent = (TRTrackerServerTorrentImpl) x[1];

              request_type = TRTrackerServerRequest.RT_SCRAPE;

            } else {

              reply = new PRUDPPacketReplyError(request.getTransactionId(), "unsupported action");
            }
          } catch (Throwable e) {

            // e.printStackTrace();

            String error = e.getMessage();

            if (error == null) {

              error = e.toString();
            }

            reply = new PRUDPPacketReplyError(request.getTransactionId(), error);
          }
        } else {

          System.out.println("UDP Tracker: replying 'disabled' to " + client_ip_address);

          reply = new PRUDPPacketReplyError(request.getTransactionId(), "UDP Tracker disabled");
        }
      }

      if (reply != null) {

        InetAddress address = request_dg.getAddress();

        ByteArrayOutputStream baos = new ByteArrayOutputStream();

        DataOutputStream os = new DataOutputStream(baos);

        reply.serialise(os);

        byte[] output_buffer = baos.toByteArray();

        DatagramPacket reply_packet =
            new DatagramPacket(output_buffer, output_buffer.length, address, request_dg.getPort());

        socket.send(reply_packet);

        server.updateStats(request_type, torrent, input_buffer.length, output_buffer.length);
      }

    } catch (Throwable e) {

      Logger.log(new LogEvent(LOGID, "TRTrackerServerProcessorUDP: processing fails", e));
    } finally {

      try {
        is.close();

      } catch (Throwable e) {

      }
    }
  }
  protected String[] getAuthenticationDialog(final String realm, final String location) {
    final Display display = SWTThread.getInstance().getDisplay();

    if (display.isDisposed()) {

      return (null);
    }

    final AESemaphore sem = new AESemaphore("SWTAuth");

    final authDialog[] dialog = new authDialog[1];

    TOTorrent torrent = TorrentUtils.getTLSTorrent();

    final boolean is_tracker;
    final String details;

    if (torrent == null) {

      is_tracker = false;

      details = TorrentUtils.getTLSDescription();

    } else {

      details = TorrentUtils.getLocalisedName(torrent);
      is_tracker = true;
    }

    try {
      if (display.getThread() == Thread.currentThread()) {

        dialog[0] = new authDialog(sem, display, realm, is_tracker, location, details);

        while (!(display.isDisposed() || sem.isReleasedForever())) {

          if (!display.readAndDispatch()) {

            display.sleep();
          }
        }

        if (display.isDisposed()) {

          return (null);
        }
      } else {

        display.asyncExec(
            new AERunnable() {
              public void runSupport() {
                dialog[0] = new authDialog(sem, display, realm, is_tracker, location, details);
              }
            });
      }
    } catch (Throwable e) {

      Debug.printStackTrace(e);

      return (null);
    }

    sem.reserve();

    String user = dialog[0].getUsername();
    String pw = dialog[0].getPassword();
    String persist = dialog[0].savePassword() ? "true" : "false";

    if (user == null) {

      return (null);
    }

    return (new String[] {user, pw == null ? "" : pw, persist});
  }
  public PasswordAuthentication getAuthentication(
      String realm, String protocol, String host, int port) {
    try {
      this_mon.enter();

      String tracker = protocol + "://" + host + ":" + port + "/";

      InetAddress bind_ip = NetworkAdmin.getSingleton().getSingleHomedServiceBindAddress();

      String self_addr;

      // System.out.println( "auth req for " + realm + " - " + tracker );

      if (bind_ip == null || bind_ip.isAnyLocalAddress()) {

        self_addr = "127.0.0.1";

      } else {

        self_addr = bind_ip.getHostAddress();
      }

      // when the tracker is connected to internally we don't want to prompt
      // for the password. Here we return a special user and the password hash
      // which is picked up in the tracker auth code - search for "<internal>"!

      // also include the tracker IP as well as for scrapes these can occur on
      // a raw torrent which hasn't been modified to point to localhost

      if (host.equals(self_addr)
          || host.equals(COConfigurationManager.getStringParameter("Tracker IP", ""))) {

        try {
          byte[] pw = COConfigurationManager.getByteParameter("Tracker Password", new byte[0]);

          String str_pw = new String(Base64.encode(pw));

          return (new PasswordAuthentication("<internal>", str_pw.toCharArray()));

        } catch (Throwable e) {

          Debug.printStackTrace(e);
        }
      }

      String auth_key = realm + ":" + tracker;

      authCache cache = (authCache) auth_cache.get(auth_key);

      if (cache != null) {

        PasswordAuthentication auth = cache.getAuth();

        if (auth != null) {

          return (auth);
        }
      }

      String[] res = getAuthenticationDialog(realm, tracker);

      if (res == null) {

        return (null);

      } else {

        PasswordAuthentication auth = new PasswordAuthentication(res[0], res[1].toCharArray());

        boolean save_pw = res[2].equals("true");

        boolean old_entry_existed =
            auth_cache.put(auth_key, new authCache(auth_key, auth, save_pw)) != null;

        if (save_pw || old_entry_existed) {

          saveAuthCache();
        }

        return (auth);
      }
    } finally {

      this_mon.exit();
    }
  }
  protected void preProcessReply(Map reply, final boolean v6) {
    NetworkAdmin admin = NetworkAdmin.getSingleton();

    try {
      byte[] address = (byte[]) reply.get("source_ip_address");

      InetAddress my_ip = InetAddress.getByName(new String(address));

      NetworkAdminASN old_asn = admin.getCurrentASN();

      NetworkAdminASN new_asn = admin.lookupCurrentASN(my_ip);

      if (!new_asn.sameAs(old_asn)) {

        // kick off a secondary version check to communicate the new information

        if (!secondary_check_done) {

          secondary_check_done = true;

          new AEThread("Secondary version check", true) {
            public void runSupport() {
              getVersionCheckInfoSupport(REASON_SECONDARY_CHECK, false, true, v6);
            }
          }.start();
        }
      }
    } catch (Throwable e) {

      Debug.printStackTrace(e);
    }

    Long as_advice = (Long) reply.get("as_advice");

    if (as_advice != null) {

      NetworkAdminASN current_asn = admin.getCurrentASN();

      String asn = current_asn.getASName();

      if (asn != null) {

        long advice = as_advice.longValue();

        if (advice != 0) {

          // require crypto

          String done_asn = COConfigurationManager.getStringParameter("ASN Advice Followed", "");

          if (!done_asn.equals(asn)) {

            COConfigurationManager.setParameter("ASN Advice Followed", asn);

            boolean change = advice == 1 || advice == 2;
            boolean alert = advice == 1 || advice == 3;

            if (!COConfigurationManager.getBooleanParameter(
                "network.transport.encrypted.require")) {

              if (change) {

                COConfigurationManager.setParameter("network.transport.encrypted.require", true);
              }

              if (alert) {

                String msg = MessageText.getString("crypto.alert.as.warning", new String[] {asn});

                Logger.log(new LogAlert(false, LogAlert.AT_WARNING, msg));
              }
            }
          }
        }
      }
    }

    // set ui.toolbar.uiswitcher based on instructions from tracker
    // Really shouldn't be in VersionCheck client, but instead have some
    // listener and have the code elsewhere.  Simply calling
    // getVersionCheckInfo from "code elsewhere" (to get the cached result)
    // caused a deadlock at startup.
    Long lEnabledUISwitcher = (Long) reply.get("ui.toolbar.uiswitcher");
    if (lEnabledUISwitcher != null) {
      COConfigurationManager.setBooleanDefault(
          "ui.toolbar.uiswitcher", lEnabledUISwitcher.longValue() == 1);
    }
  }