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 = true;
39 private boolean isAuthenticated = false;
40
41 private static final int PACKET_IQ = 0;
42 private static final int PACKET_MESSAGE = 1;
43 private static final int PACKET_PRESENCE = 2;
44
45 public XmppConnection(Account account, PowerManager pm) {
46 this.account = account;
47 wakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
48 "XmppConnection");
49 tagReader = new XmlReader(wakeLock);
50 tagWriter = new TagWriter();
51 }
52
53 protected void connect() {
54 try {
55 socket = new Socket(account.getServer(), 5222);
56 Log.d(LOGTAG, "starting new socket");
57 OutputStream out = socket.getOutputStream();
58 tagWriter.setOutputStream(out);
59 InputStream in = socket.getInputStream();
60 tagReader.setInputStream(in);
61 } catch (UnknownHostException e) {
62 Log.d(LOGTAG, "error during connect. unknown host");
63 } catch (IOException e) {
64 Log.d(LOGTAG, "error during connect. io exception. falscher port?");
65 }
66 }
67
68 @Override
69 public void run() {
70 connect();
71 try {
72 tagWriter.beginDocument();
73 sendStartStream();
74 Tag nextTag;
75 while ((nextTag = tagReader.readTag()) != null) {
76 if (nextTag.isStart("stream")) {
77 processStream(nextTag);
78 } else {
79 Log.d(LOGTAG, "found unexpected tag: " + nextTag.getName());
80 }
81 }
82 } catch (XmlPullParserException e) {
83 Log.d(LOGTAG,
84 "xml error during normal read. maybe missformed xml? "
85 + e.getMessage());
86 } catch (IOException e) {
87 Log.d(LOGTAG, "io exception during read. connection lost?");
88 }
89 }
90
91 private void processStream(Tag currentTag) throws XmlPullParserException,
92 IOException {
93 Log.d(LOGTAG, "process Stream");
94 Tag nextTag;
95 while ((nextTag = tagReader.readTag()) != null) {
96 if (nextTag.isStart("error")) {
97 processStreamError(nextTag);
98 } else if (nextTag.isStart("features")) {
99 processStreamFeatures(nextTag);
100 if (!isTlsEncrypted) {
101 sendStartTLS();
102 }
103 if ((!isAuthenticated) && (isTlsEncrypted)) {
104 sendSaslAuth();
105 }
106 if ((isAuthenticated)&&(isTlsEncrypted)) {
107 sendBindRequest();
108 }
109 } else if (nextTag.isStart("proceed")) {
110 switchOverToTls(nextTag);
111 } else if (nextTag.isStart("success")) {
112 isAuthenticated = true;
113 Log.d(LOGTAG,"read success tag in stream. reset again");
114 tagReader.readTag();
115 tagReader.reset();
116 sendStartStream();
117 processStream(tagReader.readTag());
118 } else if (nextTag.isStart("iq")) {
119 Log.d(LOGTAG,processIq(nextTag).toString());
120 } else if (nextTag.isStart("message")) {
121 Log.d(LOGTAG,processMessage(nextTag).toString());
122 } else if (nextTag.isStart("presence")) {
123 Log.d(LOGTAG,processPresence(nextTag).toString());
124 } else if (nextTag.isEnd("stream")) {
125 break;
126 } else {
127 Log.d(LOGTAG, "found unexpected tag: " + nextTag.getName()
128 + " as child of " + currentTag.getName());
129 }
130 }
131 }
132
133 private Element processPacket(Tag currentTag, int packetType) throws XmlPullParserException, IOException {
134 Element element;
135 switch (packetType) {
136 case PACKET_IQ:
137 element = new IqPacket();
138 break;
139 case PACKET_MESSAGE:
140 element = new MessagePacket();
141 break;
142 case PACKET_PRESENCE:
143 element = new PresencePacket();
144 break;
145 default:
146 return null;
147 }
148 element.setAttributes(currentTag.getAttributes());
149 Tag nextTag = tagReader.readTag();
150 while(!nextTag.isEnd(element.getName())) {
151 if (!nextTag.isNo()) {
152 Element child = tagReader.readElement(nextTag);
153 element.addChild(child);
154 }
155 nextTag = tagReader.readTag();
156 }
157 return element;
158 }
159
160
161 private IqPacket processIq(Tag currentTag) throws XmlPullParserException, IOException {
162 return (IqPacket) processPacket(currentTag,PACKET_IQ);
163 }
164
165 private MessagePacket processMessage(Tag currentTag) throws XmlPullParserException, IOException {
166 return (MessagePacket) processPacket(currentTag, PACKET_MESSAGE);
167 }
168
169 private PresencePacket processPresence(Tag currentTag) throws XmlPullParserException, IOException {
170 return (PresencePacket) processPacket(currentTag, PACKET_PRESENCE);
171 }
172
173 private void sendStartTLS() throws XmlPullParserException, IOException {
174 Tag startTLS = Tag.empty("starttls");
175 startTLS.setAttribute("xmlns", "urn:ietf:params:xml:ns:xmpp-tls");
176 Log.d(LOGTAG, "sending starttls");
177 tagWriter.writeTag(startTLS).flush();
178 }
179
180 private void switchOverToTls(Tag currentTag) throws XmlPullParserException,
181 IOException {
182 Tag nextTag = tagReader.readTag(); // should be proceed end tag
183 Log.d(LOGTAG, "now switch to ssl");
184 SSLSocket sslSocket;
185 try {
186 sslSocket = (SSLSocket) ((SSLSocketFactory) SSLSocketFactory
187 .getDefault()).createSocket(socket, socket.getInetAddress()
188 .getHostAddress(), socket.getPort(), true);
189 tagReader.setInputStream(sslSocket.getInputStream());
190 Log.d(LOGTAG, "reset inputstream");
191 tagWriter.setOutputStream(sslSocket.getOutputStream());
192 Log.d(LOGTAG, "switch over seemed to work");
193 isTlsEncrypted = true;
194 sendStartStream();
195 processStream(tagReader.readTag());
196 } catch (IOException e) {
197 Log.d(LOGTAG, "error on ssl" + e.getMessage());
198 }
199 }
200
201 private void sendSaslAuth() throws IOException, XmlPullParserException {
202 String saslString = SASL.plain(account.getUsername(),
203 account.getPassword());
204 Element auth = new Element("auth");
205 auth.setAttribute("xmlns", "urn:ietf:params:xml:ns:xmpp-sasl");
206 auth.setAttribute("mechanism", "PLAIN");
207 auth.setContent(saslString);
208 Log.d(LOGTAG,"sending sasl "+auth.toString());
209 tagWriter.writeElement(auth);
210 tagWriter.flush();
211 }
212
213 private void processStreamFeatures(Tag currentTag)
214 throws XmlPullParserException, IOException {
215 Log.d(LOGTAG, "processStreamFeatures");
216
217 Element streamFeatures = new Element("features");
218
219 Tag nextTag = tagReader.readTag();
220 while(!nextTag.isEnd("features")) {
221 Element element = tagReader.readElement(nextTag);
222 streamFeatures.addChild(element);
223 nextTag = tagReader.readTag();
224 }
225 Log.d(LOGTAG,streamFeatures.toString());
226 }
227
228 private void sendBindRequest() throws IOException {
229 IqPacket iq = new IqPacket(nextRandomId(),IqPacket.TYPE_SET);
230 Element bind = new Element("bind");
231 bind.setAttribute("xmlns","urn:ietf:params:xml:ns:xmpp-bind");
232 iq.addChild(bind);
233 //Element resource = new Element("resource");
234 //resource.setContent("mobile");
235 //bind.addChild(resource);
236 Log.d(LOGTAG,"sending bind request: "+iq.toString());
237 tagWriter.writeElement(iq);
238 tagWriter.flush();
239
240
241 //technically not bind stuff
242 IqPacket startSession = new IqPacket(this.nextRandomId(), IqPacket.TYPE_SET);
243 Element session = new Element("session");
244 session.setAttribute("xmlns","urn:ietf:params:xml:ns:xmpp-session");
245 session.setContent("");
246 startSession.addChild(session);
247
248 tagWriter.writeElement(startSession);
249 tagWriter.flush();
250
251 Element presence = new Element("presence");
252
253 tagWriter.writeElement(presence);
254 tagWriter.flush();
255
256 }
257
258 private void processStreamError(Tag currentTag) {
259 Log.d(LOGTAG, "processStreamError");
260 }
261
262 private void sendStartStream() throws IOException {
263 Tag stream = Tag.start("stream");
264 stream.setAttribute("from", account.getJid());
265 stream.setAttribute("to", account.getServer());
266 stream.setAttribute("version", "1.0");
267 stream.setAttribute("xml:lang", "en");
268 stream.setAttribute("xmlns", "jabber:client");
269 stream.setAttribute("xmlns:stream", "http://etherx.jabber.org/streams");
270 tagWriter.writeTag(stream).flush();
271 }
272
273 private String nextRandomId() {
274 return new BigInteger(50, random).toString(32);
275 }
276}