1 /* iksemel (XML parser for Jabber)
2 ** Copyright (C) 2011 Gurer Ozen
3 ** This code is free software; you can redistribute it and/or
4 ** modify it under the terms of GNU Lesser General Public License.
5 */
6 
7 #include "exceptions.h"
8 #include "document.h"
9 #include "stream.h"
10 
11 typedef struct {
12 	PyObject_HEAD
13 	PyObject *jid;
14 	iksparser *parser;
15 	int features;
16 	int authorized;
17 	int use_sasl;
18 	int use_tls;
19 } Stream;
20 
21 static int Stream_init(Stream *self, PyObject *args, PyObject *kwargs);
22 static PyObject *Stream_connect(Stream *self, PyObject *args, PyObject *kwargs);
23 static PyObject *Stream_fileno(Stream *self);
24 static PyObject *Stream_send(Stream *self, PyObject *args);
25 static PyObject *Stream_recv(Stream *self);
26 static void Stream_dealloc(Stream *self);
27 
28 static PyMethodDef Stream_methods[] = {
29 	{ "connect", (PyCFunction) Stream_connect, METH_KEYWORDS, "Connect stream to an XMPP server" },
30 	{ "fileno", (PyCFunction) Stream_fileno, METH_NOARGS, "Return file descriptor of the socket" },
31 	{ "send", (PyCFunction) Stream_send, METH_VARARGS, "Send stanzas to server" },
32 	{ "recv", (PyCFunction) Stream_recv, METH_NOARGS, "Read data from server" },
33 	{ NULL }
34 };
35 
36 static PyTypeObject Stream_type = {
37 	PyObject_HEAD_INIT(NULL)
38 	0,			/* ob_size */
39 	"iksemel.Stream",	/* tp_name */
40 	sizeof(Stream),		/* tp_basicsize */
41 	0,			/* tp_itemsize */
42 	(destructor)Stream_dealloc, /* tp_dealloc */
43 	0,			/* tp_print */
44 	0,			/* tp_getattr */
45 	0,			/* tp_setattr  */
46 	0,			/* tp_compare */
47 	0,			/* tp_repr */
48 	0,			/* tp_as_number */
49 	0,			/* tp_as_sequence */
50 	0,			/* tp_as_mapping */
51 	0,			/* tp_hash */
52 	0,			/* tp_call */
53 	0,			/* tp_str */
54 	0,			/* tp_getattro */
55 	0,			/* tp_setattro */
56 	0,			/* tp_as_buffer */
57 	Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /* tp_flags */
58 	"iksemel stream object", /* tp_doc */
59 	0,			/* tp_traverse */
60 	0,			/* tp_clear */
61 	0,			/* tp_richcompare */
62 	0,			/* tp_weaklistoffset */
63 	0,			/* tp_iter */
64 	0,			/* tp_iternext */
65 	Stream_methods,		/* tp_methods */
66 	0,			/* tp_members */
67 	0,			/* tp_getset */
68 	0,			/* tp_base */
69 	0,			/* tp_dict */
70 	0,			/* tp_descr_get */
71 	0,			/* tp_descr_set */
72 	0,			/* tp_dictoffset */
73 	(initproc)Stream_init,	/* tp_init */
74 	0,			/* tp_alloc */
75 	0			/* tp_new */
76 };
77 
78 static void
start_sasl(Stream * self,enum ikssasltype type)79 start_sasl(Stream *self, enum ikssasltype type)
80 {
81 	PyObject *hook;
82 	PyObject *ret;
83 	PyObject *o;
84 	char *local;
85 	char *pw;
86 
87 	o = PyObject_GetAttrString(self->jid, "local");
88 	if (!o) return;
89 	local = PyString_AsString(o);
90 	if (!local) {
91 		Py_DECREF(o);
92 		return;
93 	}
94 
95 	hook = PyObject_GetAttrString((PyObject *) self, "ask_password");
96 	if (!hook) {
97 		Py_DECREF(o);
98 		return;
99 	}
100 	ret = PyObject_CallObject(hook, NULL);
101 	Py_DECREF(hook);
102 	if (!ret) {
103 		Py_DECREF(o);
104 		return;
105 	}
106 	pw = PyString_AsString(ret);
107 	if (!pw) {
108 		Py_DECREF(ret);
109 		Py_DECREF(o);
110 		return;
111 	}
112 
113 	iks_start_sasl(self->parser, type, local, pw);
114 
115 	Py_DECREF(ret);
116 	Py_DECREF(o);
117 }
118 
119 static void
make_bind(Stream * self)120 make_bind(Stream *self)
121 {
122 	iks *x, *y, *z;
123 	PyObject *o;
124 	char *resource;
125 
126 	o = PyObject_GetAttrString(self->jid, "resource");
127 	if (!o) return;
128 	resource = PyString_AsString(o);
129 	if (!resource) {
130 		PyErr_Clear();
131 		resource = "iksemel";
132 	}
133 
134 	x = iks_new("iq");
135 	iks_insert_attrib(x, "type", "set");
136 	y = iks_insert(x, "bind");
137 	iks_insert_attrib(y, "xmlns", IKS_NS_XMPP_BIND);
138 	z = iks_insert(y, "resource");
139 	iks_insert_cdata(z, resource, 0);
140 
141 	iks_send(self->parser, x);
142 
143 	iks_delete(x);
144 	Py_DECREF(o);
145 }
146 
147 static void
on_features(Stream * self,iks * node)148 on_features(Stream *self, iks *node)
149 {
150 	iks *x;
151 
152 	self->features = iks_stream_features(node);
153 	if (self->use_sasl) {
154 		if (self->use_tls && !iks_is_secure(self->parser)) return;
155 		if (self->authorized) {
156 			if (self->features & IKS_STREAM_BIND) {
157 				make_bind(self);
158 			}
159 			if (self->features & IKS_STREAM_SESSION) {
160 				x = iks_make_session();
161 				iks_insert_attrib(x, "id", "auth");
162 				iks_send(self->parser, x);
163 				iks_delete(x);
164 			}
165 		} else {
166 			if (self->features & IKS_STREAM_SASL_MD5)
167 				start_sasl(self, IKS_SASL_DIGEST_MD5);
168 			else if (self->features & IKS_STREAM_SASL_PLAIN)
169 				start_sasl(self, IKS_SASL_PLAIN);
170 		}
171 	}
172 }
173 
174 static void
on_success(Stream * self,iks * node)175 on_success(Stream *self, iks *node)
176 {
177 	PyObject *o;
178 	char *domain;
179 
180 	o = PyObject_GetAttrString(self->jid, "domain");
181 	if (!o) return;
182 	domain = PyString_AsString(o);
183 	if (!domain) {
184 		Py_DECREF(o);
185 		return;
186 	}
187 
188 	self->authorized = 1;
189 	iks_send_header(self->parser, domain);
190 
191 	Py_DECREF(o);
192 }
193 
194 static int
on_stream(Stream * self,int type,iks * node)195 on_stream(Stream *self, int type, iks *node)
196 {
197 	PyObject *hook;
198 	PyObject *doc;
199 	PyObject *ret;
200 //	iks *x;
201 
202 	switch (type) {
203 		case IKS_NODE_NORMAL:
204 			if (strcmp("stream:features", iks_name(node)) == 0) {
205 				on_features(self, node);
206 			} else if (strcmp("success", iks_name(node)) == 0) {
207 				on_success(self, node);
208 			}
209 #if 0
210 		case IKS_NODE_START:
211 			if (!iks_is_secure(self->parser)) {
212 				iks_start_tls(self->parser);
213 				break;
214 			}
215 			if (!self->use_sasl) {
216 				x = iks_make_auth (, , NULL);
217 				iks_insert_attrib (x, "id", "auth");
218 				iks_send (self->parser, x);
219 				iks_delete (x);
220 			}
221 			break;
222 #endif
223 	hook = PyObject_GetAttrString((PyObject *) self, "on_stanza");
224 	if (hook) {
225 		doc = Document_from_iks(NULL, node);
226 		if (!doc) {
227 			Py_DECREF(hook);
228 			return IKS_NOMEM;
229 		}
230 		ret = PyObject_CallFunctionObjArgs(hook, doc, NULL);
231 		Py_DECREF(hook);
232 		// FIXME: handle error
233 		if (ret == NULL) {
234 			Py_DECREF(ret);
235 			return IKS_HOOK;
236 		}
237 	}
238 	}
239 	if (node) iks_delete (node);
240 	return IKS_OK;
241 }
242 
243 void
on_log(Stream * self,const char * data,size_t size,int is_incoming)244 on_log(Stream *self, const char *data, size_t size, int is_incoming)
245 {
246 	PyObject *hook;
247 	PyObject *args;
248 	PyObject *ret;
249 	PyObject *b;
250 
251 	hook = PyObject_GetAttrString((PyObject *) self, "on_xml");
252 	if (hook) {
253 		b = Py_False;
254 		if (is_incoming) b = Py_True;
255 		args = Py_BuildValue("s#O", data, size, b);
256 		if (args) {
257 			ret = PyObject_CallObject(hook, args);
258 			if (ret) { Py_DECREF(ret); }
259 			Py_DECREF(args);
260 		}
261 		Py_DECREF(hook);
262 	}
263 }
264 
265 static int
Stream_init(Stream * self,PyObject * args,PyObject * kwargs)266 Stream_init(Stream *self, PyObject *args, PyObject *kwargs)
267 {
268 	PyObject *hook;
269 
270 	self->jid = NULL;
271 	self->parser = iks_stream_new (
272 		IKS_NS_CLIENT,
273 		self,
274 		(iksStreamHook *) on_stream
275 	);
276 	self->features = 0;
277 	self->authorized = 0;
278 	self->use_sasl = 1;
279 	self->use_tls = 1;
280 
281 	hook = PyObject_GetAttrString((PyObject *) self, "on_xml");
282 	if (hook) {
283 		iks_set_log_hook(self->parser, (iksLogHook *) on_log);
284 		Py_DECREF(hook);
285 	}
286 
287 	return 0;
288 }
289 
290 static PyObject *
Stream_connect(Stream * self,PyObject * args,PyObject * kwargs)291 Stream_connect(Stream *self, PyObject *args, PyObject *kwargs)
292 {
293 	PyObject *o;
294 	char *host;
295 	int e;
296 
297 	o = PyMapping_GetItemString(kwargs, "tls");
298 	if (o) {
299 		if (PyObject_IsTrue(o)) {
300 			self->use_tls = 1;
301 		} else {
302 			self->use_tls = 0;
303 		}
304 	}
305 	PyErr_Clear();
306 
307 	o = PyMapping_GetItemString(kwargs, "sasl");
308 	if (o) {
309 		if (PyObject_IsTrue(o)) {
310 			self->use_sasl = 1;
311 		} else {
312 			self->use_sasl = 0;
313 		}
314 	}
315 	PyErr_Clear();
316 
317 	o = PyMapping_GetItemString(kwargs, "jid");
318 	if (!o) {
319 		PyErr_SetString(PyExc_TypeError, "jid keyword argument is required");
320 		return NULL;
321 	}
322 	Py_INCREF(o);
323 	self->jid = o;
324 
325 	o = PyObject_GetAttrString(self->jid, "domain");
326 	if (!o) return NULL;
327 	host = PyString_AsString(o);
328 	if (!host) {
329 		Py_DECREF(o);
330 		return NULL;
331 	}
332 
333 	e = iks_connect_tcp(self->parser, host, IKS_JABBER_PORT);
334 	if (e) {
335 		return exceptions_stream_error(e);
336 	}
337 
338 	Py_DECREF(o);
339 
340 	Py_INCREF(Py_None);
341 	return Py_None;
342 }
343 
344 static PyObject *
Stream_fileno(Stream * self)345 Stream_fileno(Stream *self)
346 {
347 	return Py_BuildValue("i", iks_fd(self->parser));
348 }
349 
350 static PyObject *
Stream_send(Stream * self,PyObject * args)351 Stream_send(Stream *self, PyObject *args)
352 {
353 	int e;
354 	PyObject *s;
355 	char *str;
356 
357 	if (!PyArg_ParseTuple(args, "O", &s))
358 		return NULL;
359 
360 	s = PyObject_Str(s);
361 	if (s) {
362 		str = PyString_AsString(s);
363 		if (str) {
364 			e = iks_send_raw(self->parser, str);
365 			if (e) {
366 				Py_DECREF(s);
367 				return exceptions_stream_error(e);
368 			}
369 		}
370 		Py_DECREF(s);
371 	}
372 
373 	Py_INCREF(Py_None);
374 	return Py_None;
375 }
376 
377 static PyObject *
Stream_recv(Stream * self)378 Stream_recv(Stream *self)
379 {
380 	int e;
381 
382 	e = iks_recv(self->parser, 0);
383 	if (e) {
384 		return exceptions_stream_error(e);
385 	}
386 
387 	Py_INCREF(Py_None);
388 	return Py_None;
389 }
390 
391 static void
Stream_dealloc(Stream * self)392 Stream_dealloc(Stream *self)
393 {
394 	if (self->jid) { Py_DECREF(self->jid); }
395 	iks_parser_delete(self->parser);
396 	self->ob_type->tp_free((PyObject *) self);
397 }
398 
399 void
Stream_setup(PyObject * module)400 Stream_setup(PyObject *module)
401 {
402 	Stream_type.tp_new = PyType_GenericNew;
403 	if (PyType_Ready(&Stream_type) < 0) return;
404 	Py_INCREF(&Stream_type);
405 
406 	PyModule_AddObject(module, "Stream", (PyObject *) &Stream_type);
407 }
408