1package de.gultsch.chat.xmpp;
2
3import java.io.IOException;
4import java.io.InputStream;
5import java.io.OutputStream;
6import java.math.BigInteger;
7import java.net.Socket;
8import java.net.UnknownHostException;
9import java.security.SecureRandom;
10
11import javax.net.ssl.SSLSocket;
12import javax.net.ssl.SSLSocketFactory;
13
14import org.xmlpull.v1.XmlPullParserException;
15
16import android.os.PowerManager;
17import android.util.Log;
18import de.gultsch.chat.entities.Account;
19import de.gultsch.chat.utils.SASL;
20import de.gultsch.chat.xml.Element;
21import de.gultsch.chat.xml.Tag;
22import de.gultsch.chat.xml.XmlReader;
23import de.gultsch.chat.xml.TagWriter;
24
25public class XmppConnection implements Runnable {
26
27 protected Account account;
28 private static final String LOGTAG = "xmppService";
29
30 private PowerManager.WakeLock wakeLock;
31
32 private SecureRandom random = new SecureRandom();
33
34 private Socket socket;
35 private XmlReader tagReader;
36 private TagWriter tagWriter;
37
38 private boolean isTlsEncrypted = false;
39 private boolean isAuthenticated = false;
40
41 public XmppConnection(Account account, PowerManager pm) {
42 this.account = account;
43 wakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
44 "XmppConnection");
45 tagReader = new XmlReader(wakeLock);
46 tagWriter = new TagWriter();
47 }
48
49 protected void connect() {
50 try {
51 socket = new Socket(account.getServer(), 5222);
52 Log.d(LOGTAG, "starting new socket");
53 OutputStream out = socket.getOutputStream();
54 tagWriter.setOutputStream(out);
55 InputStream in = socket.getInputStream();
56 tagReader.setInputStream(in);
57 } catch (UnknownHostException e) {
58 Log.d(LOGTAG, "error during connect. unknown host");
59 } catch (IOException e) {
60 Log.d(LOGTAG, "error during connect. io exception. falscher port?");
61 }
62 }
63
64 @Override
65 public void run() {
66 connect();
67 try {
68 tagWriter.beginDocument();
69 sendStartStream();
70 Tag nextTag;
71 while ((nextTag = tagReader.readTag()) != null) {
72 if (nextTag.isStart("stream")) {
73 processStream(nextTag);
74 } else {
75 Log.d(LOGTAG, "found unexpected tag: " + nextTag.getName());
76 }
77 }
78 } catch (XmlPullParserException e) {
79 Log.d(LOGTAG,
80 "xml error during normal read. maybe missformed xml? "
81 + e.getMessage());
82 } catch (IOException e) {
83 Log.d(LOGTAG, "io exception during read. connection lost?");
84 }
85 }
86
87 private void processStream(Tag currentTag) throws XmlPullParserException,
88 IOException {
89 Log.d(LOGTAG, "process Stream");
90 Tag nextTag;
91 while ((nextTag = tagReader.readTag()) != null) {
92 if (nextTag.isStart("error")) {
93 processStreamError(nextTag);
94 } else if (nextTag.isStart("features")) {
95 processStreamFeatures(nextTag);
96 if (!isTlsEncrypted) {
97 sendStartTLS();
98 }
99 if ((!isAuthenticated) && (isTlsEncrypted)) {
100 sendSaslAuth();
101 }
102 if ((isAuthenticated)&&(isTlsEncrypted)) {
103 sendBindRequest();
104 }
105 } else if (nextTag.isStart("proceed")) {
106 switchOverToTls(nextTag);
107 } else if (nextTag.isStart("success")) {
108 isAuthenticated = true;
109 Log.d(LOGTAG,"read success tag in stream. reset again");
110 tagReader.readTag();
111 tagReader.reset();
112 sendStartStream();
113 processStream(tagReader.readTag());
114 } else if (nextTag.isStart("iq")) {
115 processIq(nextTag);
116 } else if (nextTag.isEnd("stream")) {
117 break;
118 } else {
119 Log.d(LOGTAG, "found unexpected tag: " + nextTag.getName()
120 + " as child of " + currentTag.getName());
121 }
122 }
123 }
124
125 private void processIq(Tag currentTag) throws XmlPullParserException, IOException {
126 int typ = -1;
127 if (currentTag.getAttribute("type").equals("result")) {
128 typ = IqPacket.TYPE_RESULT;
129 }
130 IqPacket iq = new IqPacket(currentTag.getAttribute("id"),typ);
131 Tag nextTag = tagReader.readTag();
132 while(!nextTag.isEnd("iq")) {
133 Element element = tagReader.readElement(nextTag);
134 iq.addChild(element);
135 nextTag = tagReader.readTag();
136 }
137 Log.d(LOGTAG,"this is what i understood: "+iq.toString());
138 }
139
140 private void sendStartTLS() throws XmlPullParserException, IOException {
141 Tag startTLS = Tag.empty("starttls");
142 startTLS.setAttribute("xmlns", "urn:ietf:params:xml:ns:xmpp-tls");
143 Log.d(LOGTAG, "sending starttls");
144 tagWriter.writeTag(startTLS).flush();
145 }
146
147 private void switchOverToTls(Tag currentTag) throws XmlPullParserException,
148 IOException {
149 Tag nextTag = tagReader.readTag(); // should be proceed end tag
150 Log.d(LOGTAG, "now switch to ssl");
151 SSLSocket sslSocket;
152 try {
153 sslSocket = (SSLSocket) ((SSLSocketFactory) SSLSocketFactory
154 .getDefault()).createSocket(socket, socket.getInetAddress()
155 .getHostAddress(), socket.getPort(), true);
156 tagReader.setInputStream(sslSocket.getInputStream());
157 Log.d(LOGTAG, "reset inputstream");
158 tagWriter.setOutputStream(sslSocket.getOutputStream());
159 Log.d(LOGTAG, "switch over seemed to work");
160 isTlsEncrypted = true;
161 sendStartStream();
162 processStream(tagReader.readTag());
163 } catch (IOException e) {
164 Log.d(LOGTAG, "error on ssl" + e.getMessage());
165 }
166 }
167
168 private void sendSaslAuth() throws IOException, XmlPullParserException {
169 String saslString = SASL.plain(account.getUsername(),
170 account.getPassword());
171 Element auth = new Element("auth");
172 auth.setAttribute("xmlns", "urn:ietf:params:xml:ns:xmpp-sasl");
173 auth.setAttribute("mechanism", "PLAIN");
174 auth.setContent(saslString);
175 Log.d(LOGTAG,"sending sasl "+auth.toString());
176 tagWriter.writeElement(auth);
177 tagWriter.flush();
178 }
179
180 private void processStreamFeatures(Tag currentTag)
181 throws XmlPullParserException, IOException {
182 Log.d(LOGTAG, "processStreamFeatures");
183
184 Element streamFeatures = new Element("features");
185
186 Tag nextTag = tagReader.readTag();
187 while(!nextTag.isEnd("features")) {
188 Element element = tagReader.readElement(nextTag);
189 streamFeatures.addChild(element);
190 nextTag = tagReader.readTag();
191 }
192 }
193
194 private void sendBindRequest() throws IOException {
195 IqPacket iq = new IqPacket(nextRandomId(),IqPacket.TYPE_SET);
196 Element bind = new Element("bind");
197 bind.setAttribute("xmlns","urn:ietf:params:xml:ns:xmpp-bind");
198 iq.addChild(bind);
199 Log.d(LOGTAG,"sending bind request: "+iq.toString());
200 tagWriter.writeElement(iq);
201 tagWriter.flush();
202 }
203
204 private void processStreamError(Tag currentTag) {
205 Log.d(LOGTAG, "processStreamError");
206 }
207
208 private void sendStartStream() throws IOException {
209 Tag stream = Tag.start("stream");
210 stream.setAttribute("from", account.getJid());
211 stream.setAttribute("to", account.getServer());
212 stream.setAttribute("version", "1.0");
213 stream.setAttribute("xml:lang", "en");
214 stream.setAttribute("xmlns", "jabber:client");
215 stream.setAttribute("xmlns:stream", "http://etherx.jabber.org/streams");
216 tagWriter.writeTag(stream).flush();
217 }
218
219 private String nextRandomId() {
220 return new BigInteger(50, random).toString(32);
221 }
222}