/* * Copyright (C) 2008-2017, Juick * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. * * 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 Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ package com.juick.server.xmpp.s2s; import com.juick.server.XMPPServer; import com.juick.xmpp.extensions.StreamError; import com.juick.xmpp.utils.XmlUtils; import org.apache.commons.lang3.StringUtils; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; import rocks.xmpp.addr.Jid; import java.io.EOFException; import java.io.IOException; import java.net.Socket; import java.net.SocketException; import java.time.Instant; import java.util.Arrays; import java.util.List; import java.util.UUID; import java.util.concurrent.CopyOnWriteArrayList; /** * @author ugnich */ public class ConnectionIn extends Connection implements Runnable { final public List from = new CopyOnWriteArrayList<>(); public Instant received; public long packetsRemote = 0; ConnectionListener listener; public ConnectionIn(XMPPServer xmpp, Socket socket) throws XmlPullParserException, IOException { super(xmpp); this.setSocket(socket); restartParser(); } @Override public void run() { try { parser.next(); // stream:stream updateTsRemoteData(); if (!parser.getName().equals("stream") || !parser.getNamespace("stream").equals(NS_STREAM)) { // || !parser.getAttributeValue(null, "version").equals("1.0") // || !parser.getAttributeValue(null, "to").equals(Main.HOSTNAME)) { throw new Exception(String.format("stream from %s invalid", getSocket().getRemoteSocketAddress())); } streamID = parser.getAttributeValue(null, "id"); if (streamID == null) { streamID = UUID.randomUUID().toString(); } boolean xmppversionnew = parser.getAttributeValue(null, "version") != null; String from = parser.getAttributeValue(null, "from"); if (Arrays.asList(xmpp.bannedHosts).contains(from)) { closeConnection(); return; } sendOpenStream(from, xmppversionnew); while (parser.next() != XmlPullParser.END_DOCUMENT) { updateTsRemoteData(); if (parser.getEventType() != XmlPullParser.START_TAG) { continue; } logParser(); packetsRemote++; String tag = parser.getName(); if (tag.equals("result") && parser.getNamespace().equals(NS_DB)) { String dfrom = parser.getAttributeValue(null, "from"); String to = parser.getAttributeValue(null, "to"); logger.debug("stream from {} to {} {} asking for dialback", dfrom, to, streamID); if (dfrom.endsWith(xmpp.getJid().toEscapedString()) && (dfrom.equals(xmpp.getJid().toEscapedString()) || dfrom.endsWith("." + xmpp.getJid()))) { logger.warn("stream from {} is invalid", dfrom); break; } if (to != null && to.equals(xmpp.getJid().toEscapedString())) { String dbKey = XmlUtils.getTagText(parser); updateTsRemoteData(); xmpp.startDialback(Jid.of(dfrom), streamID, dbKey); } else { logger.warn("stream from " + dfrom + " " + streamID + " invalid to " + to); break; } } else if (tag.equals("verify") && parser.getNamespace().equals(NS_DB)) { String vfrom = parser.getAttributeValue(null, "from"); String vto = parser.getAttributeValue(null, "to"); String vid = parser.getAttributeValue(null, "id"); String vkey = XmlUtils.getTagText(parser); updateTsRemoteData(); final boolean[] valid = {false}; if (vfrom != null && vto != null && vid != null && vkey != null) { xmpp.getConnectionOut(Jid.of(vfrom), false).ifPresent(c -> { String dialbackKey = c.dbKey; valid[0] = vkey.equals(dialbackKey); }); } if (valid[0]) { sendStanza(""); logger.debug("stream from {} {} dialback verify valid", vfrom, streamID); } else { sendStanza(""); logger.warn("stream from {} {} dialback verify invalid", vfrom, streamID); } } else if (tag.equals("presence") && checkFromTo(parser)) { String xml = XmlUtils.parseToString(parser, false); logger.debug("stream {} presence: {}", streamID, xml); xmpp.onStanzaReceived(xml); } else if (tag.equals("message") && checkFromTo(parser)) { updateTsRemoteData(); String xml = XmlUtils.parseToString(parser, false); logger.debug("stream {} message: {}", streamID, xml); xmpp.onStanzaReceived(xml); } else if (tag.equals("iq") && checkFromTo(parser)) { updateTsRemoteData(); String type = parser.getAttributeValue(null, "type"); String xml = XmlUtils.parseToString(parser, false); if (type == null || !type.equals("error")) { logger.debug("stream {} iq: {}", streamID, xml); xmpp.onStanzaReceived(xml); } } else if (!isSecured() && tag.equals("starttls")) { listener.starttls(this); } else if (isSecured() && tag.equals("stream") && parser.getNamespace().equals(NS_STREAM)) { sendOpenStream(null, true); } else if (tag.equals("error")) { StreamError streamError = StreamError.parse(parser); logger.debug("Stream error from {}: {}", streamID, streamError.getCondition()); xmpp.removeConnectionIn(this); closeConnection(); } else { String unhandledStanza = XmlUtils.parseToString(parser, true); logger.warn("Unhandled stanza from {}: {}", streamID, unhandledStanza); } } logger.warn("stream {} finished", streamID); xmpp.removeConnectionIn(this); closeConnection(); } catch (EOFException | SocketException ex) { logger.debug("stream {} closed (dirty)", streamID); xmpp.removeConnectionIn(this); closeConnection(); } catch (Exception e) { logger.debug("stream {} error {}", streamID, e); xmpp.removeConnectionIn(this); closeConnection(); } } void updateTsRemoteData() { received = Instant.now(); } void sendOpenStream(String from, boolean xmppversionnew) throws IOException { String openStream = ""; if (xmppversionnew) { openStream += ""; if (listener != null && listener.isTlsAvailable() && !isSecured() && !Arrays.asList(xmpp.brokenSSLhosts).contains(from)) { openStream += ""; } openStream += ""; } sendStanza(openStream); } public void sendDialbackResult(Jid sfrom, String type) { sendStanza(""); if (type.equals("valid")) { from.add(sfrom); logger.debug("stream from {} {} ready", sfrom, streamID); } } boolean checkFromTo(XmlPullParser parser) throws Exception { String cfrom = parser.getAttributeValue(null, "from"); String cto = parser.getAttributeValue(null, "to"); if (StringUtils.isNotEmpty(cfrom) && StringUtils.isNotEmpty(cto)) { Jid jidfrom = Jid.of(cfrom); for (Jid aFrom : from) { if (aFrom.equals(Jid.of(jidfrom.getDomain()))) { return true; } } } return false; } public void setListener(ConnectionListener listener) { this.listener = listener; } }