package de.gultsch.chat.xmpp; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.math.BigInteger; import java.net.Socket; import java.net.UnknownHostException; import java.security.SecureRandom; import javax.net.ssl.SSLSocket; import javax.net.ssl.SSLSocketFactory; import org.xmlpull.v1.XmlPullParserException; import android.os.PowerManager; import android.util.Log; import de.gultsch.chat.entities.Account; import de.gultsch.chat.utils.SASL; import de.gultsch.chat.xml.Element; import de.gultsch.chat.xml.Tag; import de.gultsch.chat.xml.XmlReader; import de.gultsch.chat.xml.TagWriter; public class XmppConnection implements Runnable { protected Account account; private static final String LOGTAG = "xmppService"; private PowerManager.WakeLock wakeLock; private SecureRandom random = new SecureRandom(); private Socket socket; private XmlReader tagReader; private TagWriter tagWriter; private boolean isTlsEncrypted = true; private boolean isAuthenticated = false; private static final int PACKET_IQ = 0; private static final int PACKET_MESSAGE = 1; private static final int PACKET_PRESENCE = 2; public XmppConnection(Account account, PowerManager pm) { this.account = account; wakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "XmppConnection"); tagReader = new XmlReader(wakeLock); tagWriter = new TagWriter(); } protected void connect() { try { socket = new Socket(account.getServer(), 5222); Log.d(LOGTAG, "starting new socket"); OutputStream out = socket.getOutputStream(); tagWriter.setOutputStream(out); InputStream in = socket.getInputStream(); tagReader.setInputStream(in); } catch (UnknownHostException e) { Log.d(LOGTAG, "error during connect. unknown host"); } catch (IOException e) { Log.d(LOGTAG, "error during connect. io exception. falscher port?"); } } @Override public void run() { connect(); try { tagWriter.beginDocument(); sendStartStream(); Tag nextTag; while ((nextTag = tagReader.readTag()) != null) { if (nextTag.isStart("stream")) { processStream(nextTag); } else { Log.d(LOGTAG, "found unexpected tag: " + nextTag.getName()); } } } catch (XmlPullParserException e) { Log.d(LOGTAG, "xml error during normal read. maybe missformed xml? " + e.getMessage()); } catch (IOException e) { Log.d(LOGTAG, "io exception during read. connection lost?"); } } private void processStream(Tag currentTag) throws XmlPullParserException, IOException { Log.d(LOGTAG, "process Stream"); Tag nextTag; while ((nextTag = tagReader.readTag()) != null) { if (nextTag.isStart("error")) { processStreamError(nextTag); } else if (nextTag.isStart("features")) { processStreamFeatures(nextTag); if (!isTlsEncrypted) { sendStartTLS(); } if ((!isAuthenticated) && (isTlsEncrypted)) { sendSaslAuth(); } if ((isAuthenticated)&&(isTlsEncrypted)) { sendBindRequest(); } } else if (nextTag.isStart("proceed")) { switchOverToTls(nextTag); } else if (nextTag.isStart("success")) { isAuthenticated = true; Log.d(LOGTAG,"read success tag in stream. reset again"); tagReader.readTag(); tagReader.reset(); sendStartStream(); processStream(tagReader.readTag()); } else if (nextTag.isStart("iq")) { Log.d(LOGTAG,processIq(nextTag).toString()); } else if (nextTag.isStart("message")) { Log.d(LOGTAG,processMessage(nextTag).toString()); } else if (nextTag.isStart("presence")) { Log.d(LOGTAG,processPresence(nextTag).toString()); } else if (nextTag.isEnd("stream")) { break; } else { Log.d(LOGTAG, "found unexpected tag: " + nextTag.getName() + " as child of " + currentTag.getName()); } } } private Element processPacket(Tag currentTag, int packetType) throws XmlPullParserException, IOException { Element element; switch (packetType) { case PACKET_IQ: element = new IqPacket(); break; case PACKET_MESSAGE: element = new MessagePacket(); break; case PACKET_PRESENCE: element = new PresencePacket(); break; default: return null; } element.setAttributes(currentTag.getAttributes()); Tag nextTag = tagReader.readTag(); while(!nextTag.isEnd(element.getName())) { if (!nextTag.isNo()) { Element child = tagReader.readElement(nextTag); element.addChild(child); } nextTag = tagReader.readTag(); } return element; } private IqPacket processIq(Tag currentTag) throws XmlPullParserException, IOException { return (IqPacket) processPacket(currentTag,PACKET_IQ); } private MessagePacket processMessage(Tag currentTag) throws XmlPullParserException, IOException { return (MessagePacket) processPacket(currentTag, PACKET_MESSAGE); } private PresencePacket processPresence(Tag currentTag) throws XmlPullParserException, IOException { return (PresencePacket) processPacket(currentTag, PACKET_PRESENCE); } private void sendStartTLS() throws XmlPullParserException, IOException { Tag startTLS = Tag.empty("starttls"); startTLS.setAttribute("xmlns", "urn:ietf:params:xml:ns:xmpp-tls"); Log.d(LOGTAG, "sending starttls"); tagWriter.writeTag(startTLS).flush(); } private void switchOverToTls(Tag currentTag) throws XmlPullParserException, IOException { Tag nextTag = tagReader.readTag(); // should be proceed end tag Log.d(LOGTAG, "now switch to ssl"); SSLSocket sslSocket; try { sslSocket = (SSLSocket) ((SSLSocketFactory) SSLSocketFactory .getDefault()).createSocket(socket, socket.getInetAddress() .getHostAddress(), socket.getPort(), true); tagReader.setInputStream(sslSocket.getInputStream()); Log.d(LOGTAG, "reset inputstream"); tagWriter.setOutputStream(sslSocket.getOutputStream()); Log.d(LOGTAG, "switch over seemed to work"); isTlsEncrypted = true; sendStartStream(); processStream(tagReader.readTag()); } catch (IOException e) { Log.d(LOGTAG, "error on ssl" + e.getMessage()); } } private void sendSaslAuth() throws IOException, XmlPullParserException { String saslString = SASL.plain(account.getUsername(), account.getPassword()); Element auth = new Element("auth"); auth.setAttribute("xmlns", "urn:ietf:params:xml:ns:xmpp-sasl"); auth.setAttribute("mechanism", "PLAIN"); auth.setContent(saslString); Log.d(LOGTAG,"sending sasl "+auth.toString()); tagWriter.writeElement(auth); tagWriter.flush(); } private void processStreamFeatures(Tag currentTag) throws XmlPullParserException, IOException { Log.d(LOGTAG, "processStreamFeatures"); Element streamFeatures = new Element("features"); Tag nextTag = tagReader.readTag(); while(!nextTag.isEnd("features")) { Element element = tagReader.readElement(nextTag); streamFeatures.addChild(element); nextTag = tagReader.readTag(); } Log.d(LOGTAG,streamFeatures.toString()); } private void sendBindRequest() throws IOException { IqPacket iq = new IqPacket(nextRandomId(),IqPacket.TYPE_SET); Element bind = new Element("bind"); bind.setAttribute("xmlns","urn:ietf:params:xml:ns:xmpp-bind"); iq.addChild(bind); //Element resource = new Element("resource"); //resource.setContent("mobile"); //bind.addChild(resource); Log.d(LOGTAG,"sending bind request: "+iq.toString()); tagWriter.writeElement(iq); tagWriter.flush(); //technically not bind stuff IqPacket startSession = new IqPacket(this.nextRandomId(), IqPacket.TYPE_SET); Element session = new Element("session"); session.setAttribute("xmlns","urn:ietf:params:xml:ns:xmpp-session"); session.setContent(""); startSession.addChild(session); tagWriter.writeElement(startSession); tagWriter.flush(); Element presence = new Element("presence"); tagWriter.writeElement(presence); tagWriter.flush(); } private void processStreamError(Tag currentTag) { Log.d(LOGTAG, "processStreamError"); } private void sendStartStream() throws IOException { Tag stream = Tag.start("stream"); stream.setAttribute("from", account.getJid()); stream.setAttribute("to", account.getServer()); stream.setAttribute("version", "1.0"); stream.setAttribute("xml:lang", "en"); stream.setAttribute("xmlns", "jabber:client"); stream.setAttribute("xmlns:stream", "http://etherx.jabber.org/streams"); tagWriter.writeTag(stream).flush(); } private String nextRandomId() { return new BigInteger(50, random).toString(32); } }