Java tutorial
/* * Copyright (C) 2010 Moduad Co., Ltd. * * 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; either version 2 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 General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ package android.apn.androidpn.server.xmpp.net; import java.io.IOException; import java.io.StringReader; import java.util.Random; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.dom4j.Element; import org.dom4j.io.XMPPPacketReader; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; import org.xmpp.packet.IQ; import org.xmpp.packet.Message; import org.xmpp.packet.PacketError; import org.xmpp.packet.Presence; import org.xmpp.packet.Roster; import org.xmpp.packet.StreamError; import android.apn.androidpn.server.util.Config; import android.apn.androidpn.server.xmpp.router.PacketRouter; import android.apn.androidpn.server.xmpp.session.ClientSession; import android.apn.androidpn.server.xmpp.session.Session; import android.apn.jivesoftware.openfire.net.MXParser; /** * This class is to handle incoming XML stanzas. * * @author Sehwan Noh (devnoh@gmail.com) */ public class StanzaHandler { private static final Log log = LogFactory.getLog(StanzaHandler.class); protected Connection connection; protected Session session; protected String serverName; private boolean sessionCreated = false; private boolean startedTLS = false; private PacketRouter router; /** * Constructor. * * @param serverName the server name * @param connection the connection */ public StanzaHandler(String serverName, Connection connection) { this.serverName = serverName; this.connection = connection; this.router = new PacketRouter(); } /** * Process the received stanza using the given XMPP packet reader. * * @param stanza the received statza * @param reader the XMPP packet reader * @throws Exception if the XML stream is not valid. */ public void process(String stanza, XMPPPacketReader reader) throws Exception { boolean initialStream = stanza.startsWith("<stream:stream"); if (!sessionCreated || initialStream) { if (!initialStream) { return; // Ignore <?xml version="1.0"?> } if (!sessionCreated) { sessionCreated = true; MXParser parser = reader.getXPPParser(); parser.setInput(new StringReader(stanza)); createSession(parser); } else if (startedTLS) { startedTLS = false; tlsNegotiated(); } return; } // If end of stream was requested if (stanza.equals("</stream:stream>")) { session.close(); return; } // Ignore <?xml version="1.0"?> if (stanza.startsWith("<?xml")) { return; } // Create DOM object Element doc = reader.read(new StringReader(stanza)).getRootElement(); if (doc == null) { return; } String tag = doc.getName(); if ("starttls".equals(tag)) { if (negotiateTLS()) { // Negotiate TLS startedTLS = true; } else { connection.close(); session = null; } } else if ("message".equals(tag)) { processMessage(doc); } else if ("presence".equals(tag)) { log.debug("presence..."); processPresence(doc); } else if ("iq".equals(tag)) { log.debug("iq..."); processIQ(doc); } else { log.warn("Unexpected packet tag (not message, iq, presence)" + doc.asXML()); session.close(); } } private void processMessage(Element doc) { log.debug("processMessage()..."); Message packet; try { packet = new Message(doc, false); } catch (IllegalArgumentException e) { log.debug("Rejecting packet. JID malformed", e); Message reply = new Message(); reply.setID(doc.attributeValue("id")); reply.setTo(session.getAddress()); reply.getElement().addAttribute("from", doc.attributeValue("to")); reply.setError(PacketError.Condition.jid_malformed); session.process(reply); return; } packet.setFrom(session.getAddress()); router.route(packet); session.incrementClientPacketCount(); } private void processPresence(Element doc) { log.debug("processPresence()..."); Presence packet; try { packet = new Presence(doc, false); } catch (IllegalArgumentException e) { log.debug("Rejecting packet. JID malformed", e); Presence reply = new Presence(); reply.setID(doc.attributeValue("id")); reply.setTo(session.getAddress()); reply.getElement().addAttribute("from", doc.attributeValue("to")); reply.setError(PacketError.Condition.jid_malformed); session.process(reply); return; } if (session.getStatus() == Session.STATUS_CLOSED && packet.isAvailable()) { log.warn("Ignoring available presence packet of closed session: " + packet); return; } packet.setFrom(session.getAddress()); router.route(packet); session.incrementClientPacketCount(); } private void processIQ(Element doc) { log.debug("processIQ()..."); IQ packet; try { packet = getIQ(doc); } catch (IllegalArgumentException e) { log.debug("Rejecting packet. JID malformed", e); IQ reply = new IQ(); if (!doc.elements().isEmpty()) { reply.setChildElement(((Element) doc.elements().get(0)).createCopy()); } reply.setID(doc.attributeValue("id")); reply.setTo(session.getAddress()); String to = doc.attributeValue("to"); if (to != null) { reply.getElement().addAttribute("from", to); } reply.setError(PacketError.Condition.jid_malformed); session.process(reply); return; } // if (packet.getID() == null) { // // IQ packets MUST have an 'id' attribute // StreamError error = new StreamError( // StreamError.Condition.invalid_xml); // session.deliverRawText(error.toXML()); // session.close(); // return; // } packet.setFrom(session.getAddress()); router.route(packet); session.incrementClientPacketCount(); } private IQ getIQ(Element doc) { Element query = doc.element("query"); if (query != null && "jabber:iq:roster".equals(query.getNamespaceURI())) { return new Roster(doc); } else { return new IQ(doc, false); } } private void createSession(XmlPullParser xpp) throws XmlPullParserException, IOException { for (int eventType = xpp.getEventType(); eventType != XmlPullParser.START_TAG;) { eventType = xpp.next(); } // Create the correct session based on the sent namespace String namespace = xpp.getNamespace(null); if ("jabber:client".equals(namespace)) { session = ClientSession.createSession(serverName, connection, xpp); if (session == null) { StringBuilder sb = new StringBuilder(250); sb.append("<?xml version='1.0' encoding='UTF-8'?>"); sb.append("<stream:stream from=\"").append(serverName); sb.append("\" id=\"").append(randomString(5)); sb.append("\" xmlns=\"").append(xpp.getNamespace(null)); sb.append("\" xmlns:stream=\"").append(xpp.getNamespace("stream")); sb.append("\" version=\"1.0\">"); // bad-namespace-prefix in the response StreamError error = new StreamError(StreamError.Condition.bad_namespace_prefix); sb.append(error.toXML()); connection.deliverRawText(sb.toString()); connection.close(); log.warn("Closing session due to bad_namespace_prefix in stream header: " + namespace); } } } private boolean negotiateTLS() { if (connection.getTlsPolicy() == Connection.TLSPolicy.disabled) { // Set the not_authorized error StreamError error = new StreamError(StreamError.Condition.not_authorized); connection.deliverRawText(error.toXML()); connection.close(); log.warn("TLS requested by initiator when TLS was never offered" + " by server. Closing connection : " + connection); return false; } // Client requested to secure the connection using TLS. try { startTLS(); } catch (Exception e) { log.error("Error while negotiating TLS", e); connection.deliverRawText("<failure xmlns=\"urn:ietf:params:xml:ns:xmpp-tls\">"); connection.close(); return false; } return true; } private void startTLS() throws Exception { Connection.ClientAuth policy; try { policy = Connection.ClientAuth.valueOf(Config.getString("xmpp.client.cert.policy", "disabled")); } catch (IllegalArgumentException e) { policy = Connection.ClientAuth.disabled; } connection.startTLS(policy); } private void tlsNegotiated() { // Offer stream features including SASL Mechanisms StringBuilder sb = new StringBuilder(620); sb.append("<?xml version='1.0' encoding='UTF-8'?>"); sb.append("<stream:stream "); sb.append("xmlns:stream=\"http://etherx.jabber.org/streams\" "); sb.append("xmlns=\"jabber:client\" from=\""); sb.append(serverName); sb.append("\" id=\""); sb.append(session.getStreamID()); sb.append("\" xml:lang=\""); sb.append(connection.getLanguage()); sb.append("\" version=\""); sb.append(Session.MAJOR_VERSION).append(".").append(Session.MINOR_VERSION); sb.append("\">"); sb.append("<stream:features>"); // Include specific features such as auth and register for client sessions String specificFeatures = session.getAvailableStreamFeatures(); if (specificFeatures != null) { sb.append(specificFeatures); } sb.append("</stream:features>"); connection.deliverRawText(sb.toString()); } private String randomString(int length) { if (length < 1) { return null; } char[] numbersAndLetters = ("0123456789abcdefghijklmnopqrstuvwxyz" + "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ") .toCharArray(); char[] randBuffer = new char[length]; for (int i = 0; i < randBuffer.length; i++) { randBuffer[i] = numbersAndLetters[new Random().nextInt(71)]; } return new String(randBuffer); } // public String getNamespace() { // return "jabber:client"; //} }