/*
* 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.components.s2s;
import com.juick.components.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.info("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.info("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.info("stream {} presence: {}", streamID, xml);
xmpp.onStanzaReceived(xml);
} else if (tag.equals("message") && checkFromTo(parser)) {
updateTsRemoteData();
String xml = XmlUtils.parseToString(parser, false);
logger.info("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.info("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.warn("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.info("stream {} closed (dirty)", streamID);
xmpp.removeConnectionIn(this);
closeConnection();
} catch (Exception e) {
logger.warn("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 && !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.info("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 jidto = Jid.of(cto);
if (jidto.getDomain().equals(xmpp.getJid().toEscapedString())) {
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;
}
}