Package tigase.server.xmppserver

Source Code of tigase.server.xmppserver.ServerConnectionManager$ConnectionWatchdogTask

/*
* Tigase Jabber/XMPP Server
* Copyright (C) 2004-2007 "Artur Hefczyc" <artur.hefczyc@tigase.org>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. Look for COPYING file in the top folder.
* If not, see http://www.gnu.org/licenses/.
*
* $Rev: 1315 $
* Last modified by $Author: kobit $
* $Date: 2008-12-15 18:12:22 +0000 (Mon, 15 Dec 2008) $
*/

package tigase.server.xmppserver;

import java.net.UnknownHostException;
import java.security.NoSuchAlgorithmException;
import java.util.List;
import java.util.Map;
import java.util.Queue;
import java.util.Timer;
import java.util.TimerTask;
import java.util.TreeMap;
import java.util.UUID;
import java.util.concurrent.ConcurrentSkipListMap;
import java.util.logging.Level;
import java.util.logging.Logger;
import tigase.net.ConnectionType;
//import tigase.net.IOService;
import tigase.net.SocketType;
import tigase.server.ConnectionManager;
import tigase.server.Packet;
import tigase.util.Algorithms;
import tigase.util.DNSResolver;
import tigase.util.JIDUtils;
import tigase.xml.Element;
import tigase.xmpp.StanzaType;
import tigase.xmpp.XMPPIOService;
import tigase.xmpp.Authorization;
import tigase.xmpp.PacketErrorTypeException;
import tigase.stats.StatRecord;

/**
* Class ServerConnectionManager
*
*
* Created: Tue Nov 22 07:07:11 2005
*
* @author <a href="mailto:artur.hefczyc@tigase.org">Artur Hefczyc</a>
* @version $Rev: 1315 $
*/
public class ServerConnectionManager extends ConnectionManager<XMPPIOService>
  implements ConnectionHandlerIfc {

  /**
   * Variable <code>log</code> is a class logger.
   */
  private static final Logger log =
    Logger.getLogger("tigase.server.xmppserver.ServerConnectionManager");

  private static final String XMLNS_DB_ATT = "xmlns:db";
  private static final String XMLNS_DB_VAL = "jabber:server:dialback";
  private static final String RESULT_EL_NAME = "result";
  private static final String DB_RESULT_EL_NAME = "db:result";
  private static final String VERIFY_EL_NAME = "verify";
  private static final String DB_VERIFY_EL_NAME = "db:verify";
//  public static final String HOSTNAMES_PROP_KEY = "hostnames";
//  public String[] HOSTNAMES_PROP_VAL =  {"localhost", "hostname"};
  public static final String MAX_PACKET_WAITING_TIME_PROP_KEY =
    "max-packet-waiting-time";
  public static final long MAX_PACKET_WAITING_TIME_PROP_VAL = 7*MINUTE;

//  private String[] hostnames = HOSTNAMES_PROP_VAL;

  /**
   * <code>maxPacketWaitingTime</code> keeps the maximum time packets
   * can wait for sending in ServerPacketQueue. Packets are put in the
   * queue only when connection to remote server is not established so
   * effectively this timeout specifies the maximum time for connecting
   * to remote server. If this time is exceeded then no more reconnecting
   * attempts are performed and packets are sent back with error information.
   *
   * Default TCP/IP timeout is 300 seconds to we can follow this convention
   * but administrator can set different timeout in server configuration.
   */
  private long maxPacketWaitingTime = MAX_PACKET_WAITING_TIME_PROP_VAL;

  /**
   * Services connected and autorized/autenticated
   */
  private Map<String, ServerConnections> connectionsByLocalRemote =
    new ConcurrentSkipListMap<String, ServerConnections>();

  /**
   * Incoming (accept) services by sessionId. Some servers (EJabberd) opens
   * many connections for each domain, especially when in cluster mode.
   */
  private ConcurrentSkipListMap<String, XMPPIOService> incoming =
    new ConcurrentSkipListMap<String, XMPPIOService>();

  private Timer connectionWatchdog = new Timer();

  protected ServerConnections getServerConnections(String cid) {
    return connectionsByLocalRemote.get(cid);
  }

  protected ServerConnections removeServerConnections(String cid) {
    return connectionsByLocalRemote.remove(cid);
  }

  public void processPacket(Packet packet) {
    if (log.isLoggable(Level.FINEST)) {
      log.finest("Processing packet: " + packet.toString());
    }
    if (!packet.isCommand() || !processCommand(packet)) {

      if (packet.getElemTo() == null) {
        log.warning("Missing 'to' attribute, ignoring packet..."
          + packet.toString()
          + "\n This most likely happens due to missconfiguration of components domain names.");
        return;
      }

      if (packet.getElemFrom() == null) {
        log.warning("Missing 'from' attribute, ignoring packet..."
          + packet.toString());
        return;
      }

      // Check whether addressing is correct:
      String to_hostname = JIDUtils.getNodeHost(packet.getElemTo());
      // We don't send packets to local domains trough s2s, there
      // must be something wrong with configuration
      if (isLocalDomainOrComponent(to_hostname)) {
        // Ups, remote hostname is the same as one of local hostname??
        // Internal loop possible, we don't want that....
        // Let's send the packet back....
        log.finest("Packet addresses to localhost, I am not processing it: "
          + packet.getStringData());
        try {
          addOutPacket(
            Authorization.SERVICE_UNAVAILABLE.getResponseMessage(packet,
              "S2S - not delivered. Server missconfiguration.", true));
        } catch (PacketErrorTypeException e) {
          log.warning("Packet processing exception: " + e);
        }
        return;
      }

      // I think from_hostname needs to be different from to_hostname at
      // this point... or s2s doesn't make sense
      String from_hostname = JIDUtils.getNodeHost(packet.getElemFrom());
      if (to_hostname.equals(from_hostname)) {
        log.warning("Dropping incorrect packet - from_hostname == to_hostname: "
          + packet.toString());
        return;
      }

      String cid = getConnectionId(packet);
      log.finest("Connection ID is: " + cid);
      ServerConnections serv_conn = getServerConnections(cid);
      if (serv_conn == null
        || (!serv_conn.sendPacket(packet) && serv_conn.needsConnection())) {
        log.finest("Couldn't send packet, creating a new connection.");
        createServerConnection(cid, packet, serv_conn);
      } else {
        log.finest("Packet seems to be sent correctly: " + packet.toString());
      }
    } // end of else
  }

  private ServerConnections createNewServerConnections(String cid, Packet packet) {
    ServerConnections conns = new ServerConnections(this, cid);
    if (packet != null) {
      if (packet.getElement().getXMLNS() == XMLNS_DB_VAL) {
        conns.addControlPacket(packet);
      } else {
        conns.addDataPacket(packet);
      }
    }
    connectionsByLocalRemote.put(cid, conns);
    return conns;
  }

  /**
   * Mehtod <code>createServerConnection</code> is called only when a new
   * connection is needed for any reason for given hostnames combination.
   *
   * @param cid a <code>String</code> s2s connection ID (localhost@remotehost)
   * @param packet a <code>Packet</code> packet to send, should be passed to the
   * ServerConnections only when it was null.
   * @param serv_conn a <code>ServerConnections</code> which was called for
   * the packet.
   */
  private void createServerConnection(String cid, Packet packet,
    ServerConnections serv_conn) {

    ServerConnections conns = serv_conn;
    if (conns == null) {
      conns = createNewServerConnections(cid, packet);
    }

    String localhost = JIDUtils.getNodeNick(cid);
    String remotehost = JIDUtils.getNodeHost(cid);
    if (openNewServerConnection(localhost, remotehost)) {
      conns.setConnecting();
      connectionWatchdog.schedule(
              new ConnectionWatchdogTask(conns, localhost, remotehost),
              2*MINUTE);
      log.finest("Connecting a new s2s service: " + cid);
    } else {
      log.finest("Couldn't open a new s2s service: (UknownHost??) " + cid);
      // Can't establish connection...., unknown host??
      Queue<Packet> waitingPackets = conns.getWaitingPackets();
      // Well, is somebody injects a packet with the same sender and
      // receiver domain and this domain is not valid then we have
      // infinite loop here....
      // Let's try to handle this by dropping such packet.
      // It may happen as well that the source domain is different from
      // target domain and both are invalid, what then?
      // The best option would be to drop the packet if it is already an
      // error - remote-server-not-found....
      // For dialback packet just ignore the error completely as it means
      // remote server tries to connect from domain which doesn't exist
      // in DNS so no further action should be performed.
      Packet p = null;
      while ((p = waitingPackets.poll()) != null) {
        if (p.getElement().getXMLNS() != XMLNS_DB_VAL) {
          try {
            addOutPacket(
              Authorization.REMOTE_SERVER_NOT_FOUND.getResponseMessage(p,
              "S2S - destination host not found", true));
          } catch (PacketErrorTypeException e) {
            log.warning("Packet: " + p.toString() + " processing exception: " + e);
          }
        }
      }
      conns.stopAll();
      //connectionsByLocalRemote.remove(cid);
    }
  }

//   private void dumpCurrentStack(StackTraceElement[] stack) {
//     StringBuilder sb = new StringBuilder();
//     for (StackTraceElement st_el: stack) {
//       sb.append("\n" + st_el.toString());
//     }
//     log.finest(sb.toString());
//   }

  private boolean openNewServerConnection(String localhost, String remotehost) {

    //    dumpCurrentStack(Thread.currentThread().getStackTrace());

    try {
      String ipAddress = DNSResolver.getHostSRV_IP(remotehost);
      Map<String, Object> port_props = new TreeMap<String, Object>();
      port_props.put("remote-ip", ipAddress);
      port_props.put("local-hostname", localhost);
      port_props.put("remote-hostname", remotehost);
      port_props.put("ifc", new String[] {ipAddress});
      port_props.put("socket", SocketType.plain);
      port_props.put("type", ConnectionType.connect);
      port_props.put("port-no", 5269);
      String cid =
        getConnectionId(localhost, remotehost);
      port_props.put("cid", cid);
      log.finest("STARTING new connection: " + cid);
      addWaitingTask(port_props);
      //reconnectService(port_props, 5*SECOND);
      return true;
    } catch (UnknownHostException e) {
      log.info("UnknownHostException for host: " + remotehost);
      return false;
    } // end of try-catch

  }

  private String getConnectionId(String localhost, String remotehost) {
    return JIDUtils.getJID(localhost, remotehost, null);
  }

  private String getConnectionId(Packet packet) {
    return JIDUtils.getJID(JIDUtils.getNodeHost(packet.getElemFrom()),
      JIDUtils.getNodeHost(packet.getElemTo()), null);
  }

  private String getConnectionId(XMPPIOService service) {
    String local_hostname =
      (String)service.getSessionData().get("local-hostname");
    String remote_hostname =
      (String)service.getSessionData().get("remote-hostname");
    String cid = getConnectionId(local_hostname,
      (remote_hostname != null ? remote_hostname : "NULL"));
    return cid;
  }

  public Queue<Packet> processSocketData(XMPPIOService serv) {
    Queue<Packet> packets = serv.getReceivedPackets();
    Packet p = null;
    while ((p = packets.poll()) != null) {
//       log.finer("Processing packet: " + p.getElemName()
//         + ", type: " + p.getType());
      if (p.getElement().getXMLNS() == null) {
        p.getElement().setXMLNS("jabber:client");
      }
      log.finest("Processing socket data: " + p.toString());

      if (p.getElement().getXMLNS() == XMLNS_DB_VAL) {
        processDialback(p, serv);
      } else {
        if (p.getElemName() == "error") {
          processStreamError(p, serv);
          return null;
        } else {
          if (checkPacket(p, serv)) {
            log.finest("Adding packet out: " + p.getStringData());
            addOutPacket(p);
          } else {
            return null;
          }
        }
      } // end of else
    } // end of while ()
     return null;
  }

  private void bouncePacketsBack(Authorization author, String cid) {
    ServerConnections serv_conns = getServerConnections(cid);
    if (serv_conns != null) {
      Queue<Packet> waiting =  serv_conns.getWaitingPackets();
      Packet p = null;
      while ((p = waiting.poll()) != null) {
        log.finest("Sending packet back: " + p.getStringData());
        try {
          addOutPacket(author.getResponseMessage(p, "S2S - not delivered", true));
        } catch (PacketErrorTypeException e) {
          log.warning("Packet processing exception: " + e);
        }
      } // end of while (p = waitingPackets.remove(ipAddress) != null)
    } else {
      log.warning("No ServerConnections for cid: " + cid);
    }
  }


  private void processStreamError(Packet packet, XMPPIOService serv) {
    Authorization author = Authorization.RECIPIENT_UNAVAILABLE;
    if (packet.getElement().getChild("host-unknown") != null) {
      author = Authorization.REMOTE_SERVER_NOT_FOUND;
    }
    String cid = getConnectionId(serv);
    bouncePacketsBack(author, cid);
    serv.stop();
  }

  private boolean checkPacket(Packet packet, XMPPIOService serv) {
    String packet_from = packet.getElemFrom();
    String packet_to = packet.getElemTo();
    if (packet_from == null || packet_to == null) {
      generateStreamError("improper-addressing", serv);
      return false;
    }
    String remote_hostname =
      (String)serv.getSessionData().get("remote-hostname");
    if (!JIDUtils.getNodeHost(packet_from).equals(remote_hostname)) {
      log.finer("Invalid hostname from the remote server, expected: "
        + remote_hostname);
      generateStreamError("invalid-from", serv);
      return false;
    }
    String local_hostname =  (String)serv.getSessionData().get("local-hostname");
    if (!JIDUtils.getNodeHost(packet_to).equals(local_hostname)) {
      log.finer("Invalid hostname of the local server, expected: "
        + local_hostname);
      generateStreamError("host-unknown", serv);
      return false;
    }
    String cid = getConnectionId(serv);
    String session_id = (String)serv.getSessionData().get(serv.SESSION_ID_KEY);
    if (!isIncomingValid(session_id)) {
      log.info("Incoming connection hasn't been validated");
      return false;
    }
    return true;
  }

  public boolean isIncomingValid(String session_id) {
    if (session_id == null) {
      return false;
    }
    XMPPIOService serv = incoming.get(session_id);
    if (serv == null || serv.getSessionData().get("valid") == null) {
      return false;
    } else {
      return (Boolean)serv.getSessionData().get("valid");
    }
  }

  private boolean processCommand(final Packet packet) {
    //    XMPPIOService serv = getXMPPIOService(packet);
    switch (packet.getCommand()) {
    case STARTTLS:
      break;
    case STREAM_CLOSED:
      break;
    case GETDISCO:
      break;
    case CLOSE:
      break;
    default:
      break;
    } // end of switch (pc.getCommand())
    return false;
  }

  public String xmppStreamOpened(XMPPIOService serv,
    Map<String, String> attribs) {

    log.finer("Stream opened: " + attribs.toString());

    switch (serv.connectionType()) {
    case connect: {
      // It must be always set for connect connection type
      String remote_hostname =
        (String)serv.getSessionData().get("remote-hostname");
      String local_hostname =
        (String)serv.getSessionData().get("local-hostname");
      String cid = getConnectionId(local_hostname, remote_hostname);
      log.finest("Stream opened for: " + cid);
      ServerConnections serv_conns = getServerConnections(cid);
      if (serv_conns == null) {
        serv_conns = createNewServerConnections(cid, null);
      }
      serv_conns.addOutgoing(serv);
      log.finest("Counters: ioservices: " + countIOServices()
        + ", s2s connections: " + countOpenConnections());
      String remote_id = attribs.get("id");
      serv.getSessionData().put(serv.SESSION_ID_KEY, remote_id);
      String uuid = UUID.randomUUID().toString();
      String key = null;
      try {
        key = Algorithms.hexDigest(remote_id, uuid, "SHA");
      } catch (NoSuchAlgorithmException e) {
        key = uuid;
      } // end of try-catch
      serv_conns.putDBKey(remote_id, key);
      Element elem = new Element(DB_RESULT_EL_NAME, key,
        new String[] {"from", "to", XMLNS_DB_ATT},
        new String[] {local_hostname, remote_hostname, XMLNS_DB_VAL});
      serv_conns.addControlPacket(new Packet(elem));
      serv_conns.sendAllControlPackets();
      return null;
    }
    case accept: {
      String remote_hostname =
        (String)serv.getSessionData().get("remote-hostname");
      String local_hostname =
        (String)serv.getSessionData().get("local-hostname");
      String cid = getConnectionId(
        (local_hostname != null ? local_hostname : "NULL"),
        (remote_hostname != null ? remote_hostname : "NULL"));
      log.finest("Stream opened for: " + cid);
      if (remote_hostname != null) {
        log.fine("Opening stream for already established connection...., trying to turn on TLS????");
      }

      String id = UUID.randomUUID().toString();
      // We don't know hostname yet so we have to save session-id in
      // connection temp data
      serv.getSessionData().put(serv.SESSION_ID_KEY, id);
      incoming.put(id, serv);
      return "<stream:stream"
        + " xmlns:stream='http://etherx.jabber.org/streams'"
        + " xmlns='jabber:server'"
        + " xmlns:db='jabber:server:dialback'"
        + " id='" + id + "'"
        + ">"
        ;
    }
    default:
      log.severe("Warning, program shouldn't reach that point.");
      break;
    } // end of switch (serv.connectionType())
    return null;
  }

  public void xmppStreamClosed(XMPPIOService serv) {
    log.finer("Stream closed: " + getConnectionId(serv));
  }

  public Map<String, Object> getDefaults(Map<String, Object> params) {
    Map<String, Object> props = super.getDefaults(params);
// Usually we want the server to do s2s for the external component too:
//    if (params.get(GEN_VIRT_HOSTS) != null) {
//      HOSTNAMES_PROP_VAL = ((String)params.get(GEN_VIRT_HOSTS)).split(",");
//    } else {
//      HOSTNAMES_PROP_VAL = DNSResolver.getDefHostNames();
//    }
//    ArrayList<String> vhosts =
//      new ArrayList<String>(Arrays.asList(HOSTNAMES_PROP_VAL));
//    for (Map.Entry<String, Object> entry: params.entrySet()) {
//      if (entry.getKey().startsWith(GEN_EXT_COMP)) {
//        String ext_comp = (String)entry.getValue();
//        if (ext_comp != null) {
//          String[] comp_params = ext_comp.split(",");
//          vhosts.add(comp_params[1]);
//        }
//      }
//      if (entry.getKey().startsWith(GEN_COMP_NAME)) {
//        String comp_name_suffix = entry.getKey().substring(GEN_COMP_NAME.length());
//        String c_name = (String)params.get(GEN_COMP_NAME + comp_name_suffix);
//        for (String vhost: HOSTNAMES_PROP_VAL) {
//          vhosts.add(c_name + "." + vhost);
//        }
//      }
//    }
//    HOSTNAMES_PROP_VAL = vhosts.toArray(new String[0]);
//    hostnames = HOSTNAMES_PROP_VAL;
//    props.put(HOSTNAMES_PROP_KEY, HOSTNAMES_PROP_VAL);
    props.put(MAX_PACKET_WAITING_TIME_PROP_KEY,
      MAX_PACKET_WAITING_TIME_PROP_VAL);
    return props;
  }

  @Override
  protected int[] getDefPlainPorts() {
    return new int[] {5269};
  }

  @Override
  public boolean handlesNonLocalDomains() {
    return true;
  }

  @Override
  public void setProperties(Map<String, Object> props) {
    super.setProperties(props);
//    hostnames = (String[])props.get(HOSTNAMES_PROP_KEY);
//    if (hostnames == null || hostnames.length == 0) {
//      log.warning("Hostnames definition is empty, setting 'localhost'");
//      hostnames = new String[] {"localhost"};
//    } // end of if (hostnames == null || hostnames.length == 0)
//    Arrays.sort(hostnames);

//    addRouting("*");
    maxPacketWaitingTime = (Long)props.get(MAX_PACKET_WAITING_TIME_PROP_KEY);
  }

  public void serviceStarted(XMPPIOService serv) {
    super.serviceStarted(serv);
    log.finest("s2s connection opened: " + serv.getRemoteAddress()
      + ", type: " + serv.connectionType().toString()
      + ", id=" + serv.getUniqueId());
    switch (serv.connectionType()) {
    case connect:
      // Send init xmpp stream here
      //      XMPPIOService serv = (XMPPIOService)service;
      String data = "<stream:stream"
        + " xmlns:stream='http://etherx.jabber.org/streams'"
        + " xmlns='jabber:server'"
        + " xmlns:db='jabber:server:dialback'"
        + ">";
      log.finest("cid: " + (String)serv.getSessionData().get("cid")
        + ", sending: " + data);
      serv.xmppStreamOpen(data);
      break;
    default:
      // Do nothing, more data should come soon...
      break;
    } // end of switch (service.connectionType())
  }

  private int countOpenConnections() {
    int open_s2s_connections = incoming.size();
    for (Map.Entry<String, ServerConnections> entry:
      connectionsByLocalRemote.entrySet()) {
      ServerConnections conn = entry.getValue();
      if (conn.isOutgoingConnected()) {
        ++open_s2s_connections;
      }
    }
    return open_s2s_connections;
  }

  public List<StatRecord> getStatistics() {
    List<StatRecord> stats = super.getStatistics();
    int waiting_packets = 0;
    int open_s2s_connections = incoming.size();
    int connected_servers = 0;
    int server_connections_instances = connectionsByLocalRemote.size();
    for (Map.Entry<String, ServerConnections> entry:
      connectionsByLocalRemote.entrySet()) {
      ServerConnections conn = entry.getValue();
      waiting_packets += conn.getWaitingPackets().size();
      if (conn.isOutgoingConnected()) {
        ++open_s2s_connections;
        ++connected_servers;
      }
      log.finest("s2s instance: " + entry.getKey() +
              ", waitingQueue: " + conn.getWaitingPackets().size() +
              ", outgoingIsNull(): " + conn.outgoingIsNull() +
              ", outgoingActive: " + conn.isOutgoingConnected() +
              ", OutgoingState: " + conn.getOutgoingState().toString() +
              ", db_keys.size(): " + conn.getDBKeysSize());
    }
     stats.add(new StatRecord(getName(), "Open s2s connections", "int",
         open_s2s_connections, Level.INFO));
    stats.add(new StatRecord(getName(), "Packets queued", "int",
        waiting_packets, Level.INFO));
    stats.add(new StatRecord(getName(), "Connected servers", "int",
        connected_servers, Level.FINE));
    stats.add(new StatRecord(getName(), "Connection instances", "int",
        server_connections_instances, Level.FINER));
    return stats;
  }

  public void serviceStopped(final XMPPIOService serv) {
    super.serviceStopped(serv);
    switch (serv.connectionType()) {
    case connect:
      String local_hostname =
        (String)serv.getSessionData().get("local-hostname");
      String remote_hostname =
        (String)serv.getSessionData().get("remote-hostname");
      if (remote_hostname == null) {
        // There is something wrong...
        // It may happen only when remote host connecting to Tigase
        // closed connection before it send any db:... packet
        // so remote domain is not known.
        // Let's do nothing for now.
        log.info("remote-hostname is NULL, local-hostname: " + local_hostname
          + ", local address: " + serv.getLocalAddress()
          + ", remote address: " + serv.getRemoteAddress());
      } else {
        String cid = getConnectionId(local_hostname, remote_hostname);
        ServerConnections serv_conns = getServerConnections(cid);
        if (serv_conns == null) {
          log.warning("There is no ServerConnections for stopped service: "
            + serv.getUniqueId() + ", cid: " + cid);
          log.finest("Counters: ioservices: " + countIOServices()
            + ", s2s connections: " + countOpenConnections());
          return;
        }
        serv_conns.serviceStopped(serv);
        Queue<Packet> waiting = serv_conns.getWaitingPackets();
        if (waiting.size() > 0) {
          if (serv_conns.waitingTime() > maxPacketWaitingTime) {
            bouncePacketsBack(Authorization.REMOTE_SERVER_TIMEOUT, cid);
          } else {
            createServerConnection(cid, null, serv_conns);
          }
        }
      }
      break;
    case accept:
      String session_id = (String)serv.getSessionData().get(serv.SESSION_ID_KEY);
      if (session_id != null) {
        XMPPIOService rem = incoming.remove(session_id);
        if (rem == null) {
          log.fine("No service with given SESSION_ID: " + session_id);
        } else {
          log.finer("Connection removed: " + session_id);
        }
      } else {
        log.fine("session_id is null, didn't remove the connection");
      }
      break;
    default:
      log.severe("Warning, program shouldn't reach that point.");
      break;
    } // end of switch (serv.connectionType())

    log.finest("Counters: ioservices: " + countIOServices()
      + ", s2s connections: " + countOpenConnections());
  }

  private void generateStreamError(String error_el, XMPPIOService serv) {
    Element error = new Element("stream:error",
      new Element[] {
        new Element(error_el,
          new String[] {"xmlns"},
          new String[] {"urn:ietf:params:xml:ns:xmpp-streams"})
      }, null, null);
    try {
      writeRawData(serv, error.toString());
//       serv.writeRawData(error.toString());
//       serv.writeRawData("</stream:stream>");
      serv.stop();
    } catch (Exception e) {
      serv.forceStop();
    }
  }

  public synchronized void processDialback(Packet packet, XMPPIOService serv) {

    log.finest("DIALBACK - " + packet.getStringData());

    String local_hostname = JIDUtils.getNodeHost(packet.getElemTo());
    // Check whether this is correct local host name...
    if (!isLocalDomainOrComponent(local_hostname)) {
      // Ups, this hostname is not served by this server, return stream
      // error and close the connection....
      generateStreamError("host-unknown", serv);
      return;
    }
    String remote_hostname = JIDUtils.getNodeHost(packet.getElemFrom());
    // And we don't want to accept any connection which is from remote
    // host name the same as one my localnames.......
    if (isLocalDomainOrComponent(remote_hostname)) {
      // Ups, remote hostname is the same as one of local hostname??
      // fake server or what? internal loop, we don't want that....
      // error and close the connection....
      generateStreamError("host-unknown", serv);
      return;
    }
    String cid = getConnectionId(local_hostname, remote_hostname);
    ServerConnections serv_conns = getServerConnections(cid);
    String session_id = (String)serv.getSessionData().get(serv.SESSION_ID_KEY);
    String serv_local_hostname =
      (String)serv.getSessionData().get("local-hostname");
    String serv_remote_hostname =
      (String)serv.getSessionData().get("remote-hostname");
    String serv_cid = serv_remote_hostname == null ? null
      : getConnectionId(serv_local_hostname, serv_remote_hostname);
    if (serv_cid != null && !cid.equals(serv_cid)) {
      log.info("Somebody tries to reuse connection?"
        + " old_cid: " + serv_cid + ", new_cid: " + cid);
  }

    // <db:result>
    if ((packet.getElemName() == RESULT_EL_NAME) ||
      (packet.getElemName() == DB_RESULT_EL_NAME)) {
      if (packet.getType() == null) {
        // This is incoming connection with dialback key for verification
        if (packet.getElemCData() != null) {
          // db:result with key to validate from accept connection
          String db_key = packet.getElemCData();
          //initServiceMapping(local_hostname, remote_hostname, accept_jid, serv);

          // <db:result> with CDATA containing KEY
          Element elem = new Element(DB_VERIFY_EL_NAME, db_key,
            new String[] {"id", "to", "from", XMLNS_DB_ATT},
            new String[] {session_id, remote_hostname, local_hostname,
                          XMLNS_DB_VAL});
          Packet result = new Packet(elem);
          if (serv_conns == null) {
            serv_conns = createNewServerConnections(cid, null);
          }
          //serv_conns.putDBKey(session_id, db_key);
          serv.getSessionData().put("remote-hostname", remote_hostname);
          serv.getSessionData().put("local-hostname", local_hostname);
          log.finest("cid: " + cid + ", sessionId: " + session_id
            + ", Counters: ioservices: " + countIOServices()
            + ", s2s connections: " + countOpenConnections());
          if (!serv_conns.sendControlPacket(result)
            && serv_conns.needsConnection()) {
            createServerConnection(cid, result, serv_conns);
          }
        } else {
          // Incorrect dialback packet, it happens for some servers....
          // I don't know yet what software they use.
          // Let's just disconnect and signal unrecoverable conection error
          log.finer("Incorrect diablack packet: " + packet.getStringData());
          bouncePacketsBack(Authorization.SERVICE_UNAVAILABLE, cid);
          generateStreamError("bad-format", serv);
        }
      } else {
        // <db:result> with type 'valid' or 'invalid'
        // It means that session has been validated now....
        //XMPPIOService connect_serv = handshakingByHost_Type.get(connect_jid);
        switch (packet.getType()) {
        case valid:
          log.finer("Connection: " + cid
            + " is valid, adding to available services.");
          serv_conns.handleDialbackSuccess();
          break;
        default:
          log.finer("Connection: " + cid + " is invalid!! Stopping...");
          serv_conns.handleDialbackFailure();
          break;
        } // end of switch (packet.getType())
      } // end of if (packet.getType() != null) else
    } // end of if (packet != null && packet.getElemName().equals("db:result"))

    // <db:verify> with type 'valid' or 'invalid'
    if ((packet.getElemName() == VERIFY_EL_NAME)
      || (packet.getElemName() == DB_VERIFY_EL_NAME)) {
      if (packet.getElemId() != null) {

        String forkey_session_id = packet.getElemId();
        if (packet.getType() == null) {
          // When type is NULL then it means this packet contains
          // data for verification
          if (packet.getElemId() != null && packet.getElemCData() != null) {
            String db_key = packet.getElemCData();
            // This might be the first dialback packet from remote server
//             serv.getSessionData().put("remote-hostname", remote_hostname);
//             serv.getSessionData().put("local-hostname", local_hostname);
//             serv_conns.addIncoming(session_id, serv);
//             log.finest("cid: " + cid + ", sessionId: " + session_id
//               + ", Counters: ioservices: " + countIOServices()
//               + ", s2s connections: " + countOpenConnections());
            //initServiceMapping(local_hostname, remote_hostname, accept_jid, serv);

            String local_key = getLocalDBKey(cid, db_key, forkey_session_id,
              session_id);

            if (local_key == null) {
              log.fine("db key is not availablefor session ID: " + forkey_session_id
                + ", key for validation: " + db_key);
            } else {
              log.fine("Local key for cid=" + cid + " is " + local_key);
              sendVerifyResult(local_hostname, remote_hostname, forkey_session_id,
                db_key.equals(local_key), serv_conns, session_id);
            }
          } // end of if (packet.getElemName().equals("db:verify"))
        else {
          // Type is not null so this is packet with verification result.
          // If the type is valid it means accept connection has been
          // validated and we can now receive data from this channel.

          Element elem = new Element(DB_RESULT_EL_NAME,
            new String[] {"type", "to", "from", XMLNS_DB_ATT},
            new String[] {packet.getType().toString(),
                          remote_hostname, local_hostname,
                          XMLNS_DB_VAL});

          sendToIncoming(forkey_session_id, new Packet(elem));
          validateIncoming(forkey_session_id,
            (packet.getType() == StanzaType.valid));

        } // end of if (packet.getType() == null) else
      } else {
        // Incorrect dialback packet, it happens for some servers....
        // I don't know yet what software they use.
        // Let's just disconnect and signal unrecoverable conection error
        log.finer("Incorrect diablack packet: " + packet.getStringData());
        bouncePacketsBack(Authorization.SERVICE_UNAVAILABLE, cid);
        generateStreamError("bad-format", serv);
      }
    } // end of if (packet != null && packet.getType() != null)

  }

  public boolean sendToIncoming(String session_id, Packet packet) {
    XMPPIOService serv = incoming.get(session_id);
    if (serv != null) {
      log.finest("Sending to incoming connectin: " + session_id
        + " packet: " + packet.toString());
      return writePacketToSocket(serv, packet);
    } else {
      log.finer("Trying to send packet: " + packet.toString()
        + " to nonexisten connection with sessionId: " + session_id);
      return false;
    }
  }

  public void validateIncoming(String session_id, boolean valid) {
    XMPPIOService serv = incoming.get(session_id);
    if (serv != null) {
      serv.getSessionData().put("valid", valid);
      if (!valid) {
        serv.stop();
      }
    }
  }

  protected String getLocalDBKey(String cid, String key, String forkey_sessionId,
    String asking_sessionId) {
    ServerConnections serv_conns = getServerConnections(cid);
    return serv_conns == null ? null : serv_conns.getDBKey(forkey_sessionId);
  }

  protected void sendVerifyResult(String from, String to, String forkey_sessionId,
    boolean valid, ServerConnections serv_conns, String asking_sessionId) {
    String type = (valid ? "valid" : "invalid");
    Element result_el = new Element(DB_VERIFY_EL_NAME,
      new String[] {"from", "to", "id", "type", XMLNS_DB_ATT},
      new String[] {from, to, forkey_sessionId, type, XMLNS_DB_VAL});
    Packet result = new Packet(result_el);
    if (!sendToIncoming(asking_sessionId, result)) {
      log.warning("Can not send verification packet back: " + result.toString());
    }
  }

  /**
   * Method <code>getMaxInactiveTime</code> returns max keep-alive time
   * for inactive connection. Let's assume s2s should send something
   * at least once every 15 minutes....
   *
   * @return a <code>long</code> value
   */
  protected long getMaxInactiveTime() {
    return 15*MINUTE;
  }

  protected XMPPIOService getXMPPIOServiceInstance() {
    return new XMPPIOService();
  }

  private class ConnectionWatchdogTask extends TimerTask {

    private ServerConnections conns = null;
    private String localhost = null;
    private String remotehost = null;

    private ConnectionWatchdogTask(ServerConnections conns, String localhost,
            String remotehost) {
      this.conns = conns;
      this.localhost = localhost;
      this.remotehost = remotehost;
    }

    public void run() {
      if (conns.getOutgoingState() ==
              ServerConnections.OutgoingState.CONNECTING) {
        log.finest("Connecting timeout expired, still connecting: " +
                conns.getCID());
        Queue<Packet> waiting = conns.getWaitingPackets();
        if (waiting.size() > 0) {
          if (conns.waitingTime() > maxPacketWaitingTime) {
            log.finest("Max packets waiting time expired, sending all back: " +
                    conns.getCID());
            conns.stopAll();
            bouncePacketsBack(Authorization.REMOTE_SERVER_TIMEOUT,
                    conns.getCID());
          } else {
            log.finest("Reconnecting: " +  conns.getCID());
            createServerConnection(conns.getCID(), null, conns);
          }
        } else {
          conns.stopAll();
          log.finest("No packets waiting in queue, giving up: " +
                  conns.getCID());
        }
      } else {
        log.finest("Connecting timeout expired: " + conns.getCID() +
                ", connection state is: " +  conns.getOutgoingState());
      }
    }

  }

}
TOP

Related Classes of tigase.server.xmppserver.ServerConnectionManager$ConnectionWatchdogTask

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.