/** * Copyright 2012 Voxbone SA/NV * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.voxbone.kelpie; import java.io.IOException; import java.net.InetSocketAddress; import java.nio.channels.SelectionKey; import java.nio.channels.Selector; import java.nio.channels.SocketChannel; import java.nio.channels.spi.SelectorProvider; import java.util.ArrayList; import java.util.Iterator; import org.apache.log4j.Logger; import org.jabberstudio.jso.JID; import org.jabberstudio.jso.JSOImplementation; import org.jabberstudio.jso.NSI; import org.jabberstudio.jso.Packet; import org.jabberstudio.jso.Stream; import org.jabberstudio.jso.StreamException; import org.jabberstudio.jso.event.PacketEvent; import org.jabberstudio.jso.event.PacketListener; import org.jabberstudio.jso.io.src.ChannelStreamSource; import org.jabberstudio.jso.util.Utilities; /** * * XMPP's Server to Server mechanism uses DNS based dialback auth, this connection deals * with the auth handshake * */ public class DialbackSession extends Thread { private JID local; private JID remote; private String sessionId; private String sessionKey; private String internalCallId; private Stream conn = null; private SocketChannel socketChannel = null; boolean valid = false; boolean done = false; Logger logger = Logger.getLogger(this.getClass()); private class CallBackSession implements PacketListener { public void packetTransferred(PacketEvent evt) { logger.debug("[[" + internalCallId + "]] Got evt: " + evt.getData()); if (evt.getData().getQualifiedName().equals("db:verify")) { logger.debug("[[" + internalCallId + "]] Got a Verify"); if (evt.getData().getAttributeValue("type").equals("valid")) { logger.debug("[[" + internalCallId + "]] Session is valid :-)"); valid = true; } else { logger.debug("[[" + internalCallId + "]] Session is not valid :-("); valid = false; } try { conn.disconnect(); } catch (StreamException e) { logger.error("Exception closing callback stream", e); } } evt.setHandled(true); } } public DialbackSession(String internalCallId, JID local, JID remote, String sessionId, String sessionKey) { this.local = local; this.remote = remote; this.sessionId = sessionId; this.sessionKey = sessionKey; this.internalCallId = internalCallId; } public boolean doDialback() { JSOImplementation jso = JSOImplementation.getInstance(); conn = jso.createStream(Utilities.SERVER_NAMESPACE); conn.getOutboundContext().addNamespace("db", "jabber:server:dialback"); conn.addStreamStatusListener(new StatusMonitor(internalCallId)); conn.addPacketListener(PacketEvent.RECEIVED, new CallBackSession()); try { logger.info("[[" + internalCallId + "]] Trying to connect to " + remote.getDomain()); ArrayList<String> dests = DNSHelper.getSRVServerList(remote.getDomain(), "xmpp-server", "tcp"); if (dests == null) { logger.warn("[[" + internalCallId + "]] Record has no destinations."); return false; } logger.debug("[[" + internalCallId + "]] Record has " + dests.toString()); String parts[]; while (dests.size() > 0) { String dest = dests.remove(0); logger.debug("[[" + internalCallId + "]] Record resolves to " + dest); if (dest != null) { parts = dest.split(":"); } else { // fallback to normal dns if no SRV record parts = new String[2]; parts[0] = remote.getDomain(); parts[1] = "5269"; } try { InetSocketAddress addr = new InetSocketAddress(parts[0], Integer.parseInt(parts[1])); socketChannel = SocketChannel.open(); socketChannel.socket().connect(addr, 5000); socketChannel.configureBlocking(false); } catch (IOException e) { logger.error("[[" + internalCallId + "]] Error connecting to server", e); continue; } break; } conn.connect(new ChannelStreamSource(socketChannel)); conn.open(); logger.info("[[" + internalCallId + "]] Streams established, sending verify"); Packet p = conn.getDataFactory().createPacketNode(new NSI("verify", "jabber:server:dialback"), Packet.class); p.setID(sessionId); p.setFrom(local); p.setTo(remote); p.addText(sessionKey); conn.send(p); synchronized (this) { start(); while (!done) { this.wait(); } } } catch (IllegalArgumentException e) { logger.error("Exception in doDialback", e); } catch (IOException e) { logger.error("Exception in doDialback", e); } catch (StreamException e) { logger.error("Exception in doDialback", e); } catch (Exception e) { logger.error("Exception in doDialback", e); } return valid; } @Override public void run() { Selector sel = null; try { sel = SelectorProvider.provider().openSelector(); socketChannel.register(sel, SelectionKey.OP_READ, this); while (true) { if (sel.select() >= 0) { Iterator<SelectionKey> itr = sel.selectedKeys().iterator(); while (itr.hasNext()) { SelectionKey key = itr.next(); if (key.isReadable()) { if (conn.getCurrentStatus().isConnected()) { conn.process(); } else { conn.disconnect(); } } } } if (conn.getCurrentStatus().isDisconnected()) { break; } } logger.debug("[[" + internalCallId + "]] Dialback connection finished, returning result"); sel.close(); } catch (IOException e) { logger.error("Exception in DialbackSession.run", e); } catch (StreamException e) { logger.error("Exception in DialbackSession.run", e); } catch (Exception e) { logger.error("Exception in DialbackSession.run", e); } finally { try { sel.close(); } catch (IOException e) { logger.error("Exception in DialbackSession.run", e); } } // make sure the connection is closed try { conn.disconnect(); } catch (StreamException e) { logger.error("Exception in DialbackSession.run", e); } synchronized (this) { done = true; this.notify(); } } }