package com.juick.components; import com.juick.components.s2s.*; import com.juick.service.*; import com.juick.xmpp.JID; import com.juick.xmpp.Stanza; import com.juick.xmpp.StanzaChild; import com.juick.xmpp.extensions.JuickMessage; import org.apache.commons.lang3.BooleanUtils; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.math.NumberUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.core.env.Environment; import org.xmlpull.v1.XmlPullParserException; import javax.inject.Inject; import java.io.IOException; import java.net.ServerSocket; import java.net.Socket; import java.security.KeyManagementException; import java.security.KeyStoreException; import java.security.NoSuchAlgorithmException; import java.security.UnrecoverableKeyException; import java.security.cert.CertificateException; import java.util.*; import java.util.concurrent.ExecutorService; /** * @author ugnich */ public class XMPPServer implements AutoCloseable { private static final Logger logger = LoggerFactory.getLogger(XMPPServer.class); public ExecutorService service; private ConnectionRouter router; public String HOSTNAME, componentName; public String keystore; public String keystorePassword; public List brokenSSLhosts; public List bannedHosts; private final List inConnections = Collections.synchronizedList(new ArrayList<>()); private final List outConnections = Collections.synchronizedList(new ArrayList<>()); private final List outCache = Collections.synchronizedList(new ArrayList<>()); private final List stanzaListeners = Collections.synchronizedList(new ArrayList<>()); final public HashMap childParsers = new HashMap<>(); @Inject public MessagesService messagesService; @Inject public UserService userService; @Inject public TagService tagService; @Inject public PMQueriesService pmQueriesService; @Inject public SubscriptionService subscriptionService; private JID jid; private ServerSocket listener; public XMPPServer(Environment env, ExecutorService service) { this.service = service; logger.info("component initialized"); try { HOSTNAME = env.getProperty("hostname"); componentName = env.getProperty("componentname"); int componentPort = NumberUtils.toInt(env.getProperty("component_port"), 5347); int s2sPort = NumberUtils.toInt(env.getProperty("s2s_port"), 5269); keystore = env.getProperty("keystore"); keystorePassword = env.getProperty("keystore_password"); brokenSSLhosts = Arrays.asList(env.getProperty("broken_ssl_hosts", StringUtils.EMPTY).split(",")); bannedHosts = Arrays.asList(env.getProperty("banned_hosts", StringUtils.EMPTY).split(",")); jid = new JID(env.getProperty("xmppbot_jid")); boolean disabled = BooleanUtils.toBoolean(env.getProperty("xmpp_disabled", "false")); childParsers.put(JuickMessage.XMLNS, new JuickMessage()); if (!disabled) { router = new ConnectionRouter(this, componentName, componentPort, env.getProperty("xmpp_password")); service.submit(router); } service.submit(() -> { try { listener = new ServerSocket(s2sPort); logger.info("s2s listener ready"); while (true) { if (Thread.currentThread().isInterrupted()) break; Socket socket = listener.accept(); ConnectionIn client = new ConnectionIn(this, socket); addConnectionIn(client); service.submit(client); } } catch (IOException e) { logger.warn("io exception", e); Thread.currentThread().interrupt(); } catch (Exception ex) { logger.warn("s2s error", ex); } logger.info("s2s interrupted"); }); } catch (Exception e) { logger.error("XMPPComponent error", e); } } @Override public void close() throws Exception { synchronized (getOutConnections()) { for (Iterator i = getOutConnections().iterator(); i.hasNext(); ) { ConnectionOut c = i.next(); c.closeConnection(); i.remove(); } } synchronized (getInConnections()) { for (Iterator i = getInConnections().iterator(); i.hasNext(); ) { ConnectionIn c = i.next(); c.closeConnection(); i.remove(); } } if (!listener.isClosed()) { listener.close(); } logger.info("XMPP server destroyed"); } public void addConnectionIn(ConnectionIn c) { synchronized (getInConnections()) { getInConnections().add(c); } } public void addConnectionOut(ConnectionOut c) { synchronized (getOutConnections()) { getOutConnections().add(c); } } public void removeConnectionIn(ConnectionIn c) { synchronized (getInConnections()) { getInConnections().remove(c); } } public void removeConnectionOut(ConnectionOut c) { synchronized (getOutConnections()) { getOutConnections().remove(c); } } public String getFromCache(String hostname) { CacheEntry ret = null; synchronized (getOutCache()) { for (Iterator i = getOutCache().iterator(); i.hasNext(); ) { CacheEntry c = i.next(); if (c.hostname != null && c.hostname.equals(hostname)) { ret = c; i.remove(); break; } } } return (ret != null) ? ret.xml : null; } public ConnectionOut getConnectionOut(String hostname, boolean needReady) { synchronized (getOutConnections()) { for (ConnectionOut c : getOutConnections()) { if (c.to != null && c.to.equals(hostname) && (!needReady || c.streamReady)) { return c; } } } return null; } public ConnectionIn getConnectionIn(String streamID) { synchronized (getInConnections()) { for (ConnectionIn c : getInConnections()) { if (c.streamID != null && c.streamID.equals(streamID)) { return c; } } } return null; } public void sendOut(Stanza s) { sendOut(s.to.Host, s.toString()); } public void sendOut(String hostname, String xml) { boolean haveAnyConn = false; ConnectionOut connOut = null; synchronized (getOutConnections()) { for (ConnectionOut c : getOutConnections()) { if (c.to != null && c.to.equals(hostname)) { if (c.streamReady) { connOut = c; break; } else { haveAnyConn = true; break; } } } } if (connOut != null) { connOut.sendStanza(xml); return; } boolean haveCache = false; synchronized (getOutCache()) { for (CacheEntry c : getOutCache()) { if (c.hostname != null && c.hostname.equals(hostname)) { c.xml += xml; c.tsUpdated = System.currentTimeMillis(); haveCache = true; break; } } if (!haveCache) { getOutCache().add(new CacheEntry(hostname, xml)); } } if (!haveAnyConn) { try { ConnectionOut connectionOut = new ConnectionOut(this, hostname); service.submit(connectionOut); } catch (CertificateException | UnrecoverableKeyException | NoSuchAlgorithmException | XmlPullParserException | KeyStoreException | KeyManagementException | IOException e) { logger.error("s2s out error", e); } } } public ConnectionRouter getRouter() { return router; } public List getInConnections() { return inConnections; } public List getOutConnections() { return outConnections; } public List getOutCache() { return outCache; } public void startDialback(String from, String streamId, String dbKey) throws Exception { ConnectionOut c = getConnectionOut(from, false); if (c != null) { c.sendDialbackVerify(streamId, dbKey); } else { c = new ConnectionOut(this, from, streamId, dbKey); service.submit(c); } } public List getStanzaListeners() { return stanzaListeners; } public void addStanzaListener(StanzaListener listener) { synchronized (stanzaListeners) { stanzaListeners.add(listener); } } public void onStanzaReceived(String type, Stanza xmlValue) { stanzaListeners.forEach(l -> l.stanzaReceived(type, xmlValue)); } public JID getJid() { return jid; } }