| |
1 /* |
| |
2 * This program is free software; you can redistribute it and/or modify |
| |
3 * it under the terms of the GNU General Public License as published by |
| |
4 * the Free Software Foundation; either version 2 of the License, or |
| |
5 * (at your option) any later version. |
| |
6 * |
| |
7 * This program is distributed in the hope that it will be useful, |
| |
8 * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| |
9 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| |
10 * GNU Library General Public License for more details. |
| |
11 * |
| |
12 * You should have received a copy of the GNU General Public License |
| |
13 * along with this program; if not, write to the Free Software |
| |
14 * Foundation, Inc., 51 Franklin Street, Fifth Floor Boston, MA 02110-1301, USA |
| |
15 */ |
| |
16 |
| |
17 #include "ibb.h" |
| |
18 #include "debug.h" |
| |
19 #include "xmlnode.h" |
| |
20 |
| |
21 #include <glib.h> |
| |
22 #include <string.h> |
| |
23 |
| |
24 #define JABBER_IBB_SESSION_DEFAULT_BLOCK_SIZE 4096 |
| |
25 |
| |
26 static GHashTable *jabber_ibb_sessions = NULL; |
| |
27 static GList *open_handlers = NULL; |
| |
28 |
| |
29 JabberIBBSession * |
| |
30 jabber_ibb_session_create(JabberStream *js, const gchar *sid, const gchar *who, |
| |
31 gpointer user_data) |
| |
32 { |
| |
33 JabberIBBSession *sess = g_new0(JabberIBBSession, 1); |
| |
34 |
| |
35 if (!sess) { |
| |
36 purple_debug_error("jabber", "Could not allocate IBB session object\n"); |
| |
37 return NULL; |
| |
38 } |
| |
39 |
| |
40 sess->js = js; |
| |
41 if (sid) { |
| |
42 sess->sid = g_strdup(sid); |
| |
43 } else { |
| |
44 sess->sid = g_strdup(jabber_get_next_id(js)); |
| |
45 } |
| |
46 sess->who = g_strdup(who); |
| |
47 sess->block_size = JABBER_IBB_SESSION_DEFAULT_BLOCK_SIZE; |
| |
48 sess->state = JABBER_IBB_SESSION_NOT_OPENED; |
| |
49 sess->user_data = user_data; |
| |
50 |
| |
51 g_hash_table_insert(jabber_ibb_sessions, sess->sid, sess); |
| |
52 |
| |
53 return sess; |
| |
54 } |
| |
55 |
| |
56 JabberIBBSession * |
| |
57 jabber_ibb_session_create_from_xmlnode(JabberStream *js, xmlnode *packet, |
| |
58 gpointer user_data) |
| |
59 { |
| |
60 JabberIBBSession *sess = NULL; |
| |
61 xmlnode *open = xmlnode_get_child_with_namespace(packet, "open", |
| |
62 XEP_0047_NAMESPACE); |
| |
63 const gchar *sid = xmlnode_get_attrib(open, "sid"); |
| |
64 const gchar *block_size = xmlnode_get_attrib(open, "block-size"); |
| |
65 gsize block_size_int; |
| |
66 |
| |
67 if (!open) { |
| |
68 return NULL; |
| |
69 } |
| |
70 |
| |
71 sess = g_new0(JabberIBBSession, 1); |
| |
72 |
| |
73 if (!sess) { |
| |
74 purple_debug_error("jabber", "Could not allocate IBB session object\n"); |
| |
75 return NULL; |
| |
76 } |
| |
77 |
| |
78 if (!sid || !block_size) { |
| |
79 purple_debug_error("jabber", |
| |
80 "IBB session open tag requires sid and block-size attributes\n"); |
| |
81 g_free(sess); |
| |
82 return NULL; |
| |
83 } |
| |
84 |
| |
85 block_size_int = atoi(block_size); |
| |
86 sess->js = js; |
| |
87 sess->sid = g_strdup(sid); |
| |
88 sess->id = g_strdup(xmlnode_get_attrib(packet, "id")); |
| |
89 sess->who = g_strdup(xmlnode_get_attrib(packet, "from")); |
| |
90 sess->block_size = block_size_int; |
| |
91 /* if we create a session from an incoming <open/> request, it means the |
| |
92 session is immediatly open... */ |
| |
93 sess->state = JABBER_IBB_SESSION_OPENED; |
| |
94 sess->user_data = user_data; |
| |
95 |
| |
96 g_hash_table_insert(jabber_ibb_sessions, sess->sid, sess); |
| |
97 |
| |
98 return sess; |
| |
99 } |
| |
100 |
| |
101 void |
| |
102 jabber_ibb_session_destroy(JabberIBBSession *sess) |
| |
103 { |
| |
104 purple_debug_info("jabber", "IBB: destroying session %lx %s\n", sess, |
| |
105 sess->sid); |
| |
106 |
| |
107 if (jabber_ibb_session_get_state(sess) == JABBER_IBB_SESSION_OPENED) { |
| |
108 jabber_ibb_session_close(sess); |
| |
109 } |
| |
110 |
| |
111 purple_debug_info("jabber", "IBB: last_iq_id: %lx\n", sess->last_iq_id); |
| |
112 if (sess->last_iq_id) { |
| |
113 purple_debug_info("jabber", "IBB: removing callback for <iq/> %s\n", |
| |
114 sess->last_iq_id); |
| |
115 jabber_iq_remove_callback_by_id(jabber_ibb_session_get_js(sess), |
| |
116 sess->last_iq_id); |
| |
117 g_free(sess->last_iq_id); |
| |
118 sess->last_iq_id = NULL; |
| |
119 } |
| |
120 |
| |
121 g_hash_table_remove(jabber_ibb_sessions, sess->sid); |
| |
122 g_free(sess->sid); |
| |
123 g_free(sess->who); |
| |
124 g_free(sess); |
| |
125 } |
| |
126 |
| |
127 const gchar * |
| |
128 jabber_ibb_session_get_sid(const JabberIBBSession *sess) |
| |
129 { |
| |
130 return sess->sid; |
| |
131 } |
| |
132 |
| |
133 JabberStream * |
| |
134 jabber_ibb_session_get_js(JabberIBBSession *sess) |
| |
135 { |
| |
136 return sess->js; |
| |
137 } |
| |
138 |
| |
139 const gchar * |
| |
140 jabber_ibb_session_get_who(const JabberIBBSession *sess) |
| |
141 { |
| |
142 return sess->who; |
| |
143 } |
| |
144 |
| |
145 guint16 |
| |
146 jabber_ibb_session_get_send_seq(const JabberIBBSession *sess) |
| |
147 { |
| |
148 return sess->send_seq; |
| |
149 } |
| |
150 |
| |
151 guint16 |
| |
152 jabber_ibb_session_get_recv_seq(const JabberIBBSession *sess) |
| |
153 { |
| |
154 return sess->recv_seq; |
| |
155 } |
| |
156 |
| |
157 JabberIBBSessionState |
| |
158 jabber_ibb_session_get_state(const JabberIBBSession *sess) |
| |
159 { |
| |
160 return sess->state; |
| |
161 } |
| |
162 |
| |
163 gsize |
| |
164 jabber_ibb_session_get_block_size(const JabberIBBSession *sess) |
| |
165 { |
| |
166 return sess->block_size; |
| |
167 } |
| |
168 |
| |
169 void |
| |
170 jabber_ibb_session_set_block_size(JabberIBBSession *sess, gsize size) |
| |
171 { |
| |
172 if (jabber_ibb_session_get_state(sess) == JABBER_IBB_SESSION_NOT_OPENED) { |
| |
173 sess->block_size = size; |
| |
174 } else { |
| |
175 purple_debug_error("jabber", |
| |
176 "Can't set block size on an open IBB session\n"); |
| |
177 } |
| |
178 } |
| |
179 |
| |
180 gpointer |
| |
181 jabber_ibb_session_get_user_data(JabberIBBSession *sess) |
| |
182 { |
| |
183 return sess->user_data; |
| |
184 } |
| |
185 |
| |
186 void |
| |
187 jabber_ibb_session_set_opened_callback(JabberIBBSession *sess, |
| |
188 JabberIBBOpenedCallback *cb) |
| |
189 { |
| |
190 sess->opened_cb = cb; |
| |
191 } |
| |
192 |
| |
193 void |
| |
194 jabber_ibb_session_set_data_sent_callback(JabberIBBSession *sess, |
| |
195 JabberIBBSentCallback *cb) |
| |
196 { |
| |
197 sess->data_sent_cb = cb; |
| |
198 } |
| |
199 |
| |
200 void |
| |
201 jabber_ibb_session_set_closed_callback(JabberIBBSession *sess, |
| |
202 JabberIBBClosedCallback *cb) |
| |
203 { |
| |
204 sess->closed_cb = cb; |
| |
205 } |
| |
206 |
| |
207 void |
| |
208 jabber_ibb_session_set_data_received_callback(JabberIBBSession *sess, |
| |
209 JabberIBBDataCallback *cb) |
| |
210 { |
| |
211 sess->data_received_cb = cb; |
| |
212 } |
| |
213 |
| |
214 void |
| |
215 jabber_ibb_session_set_error_callback(JabberIBBSession *sess, |
| |
216 JabberIBBErrorCallback *cb) |
| |
217 { |
| |
218 sess->error_cb = cb; |
| |
219 } |
| |
220 |
| |
221 static void |
| |
222 jabber_ibb_session_opened_cb(JabberStream *js, xmlnode *packet, gpointer data) |
| |
223 { |
| |
224 JabberIBBSession *sess = (JabberIBBSession *) data; |
| |
225 |
| |
226 sess->state = JABBER_IBB_SESSION_OPENED; |
| |
227 |
| |
228 if (sess->opened_cb) { |
| |
229 sess->opened_cb(sess); |
| |
230 } |
| |
231 } |
| |
232 |
| |
233 void |
| |
234 jabber_ibb_session_open(JabberIBBSession *sess) |
| |
235 { |
| |
236 if (jabber_ibb_session_get_state(sess) != JABBER_IBB_SESSION_NOT_OPENED) { |
| |
237 purple_debug_error("jabber", |
| |
238 "jabber_ibb_session called on an already open stream\n"); |
| |
239 } else { |
| |
240 JabberIq *set = jabber_iq_new(sess->js, JABBER_IQ_SET); |
| |
241 xmlnode *open = xmlnode_new("open"); |
| |
242 gchar block_size[10]; |
| |
243 |
| |
244 xmlnode_set_attrib(set->node, "to", jabber_ibb_session_get_who(sess)); |
| |
245 xmlnode_set_namespace(open, XEP_0047_NAMESPACE); |
| |
246 xmlnode_set_attrib(open, "sid", jabber_ibb_session_get_sid(sess)); |
| |
247 g_snprintf(block_size, sizeof(block_size), "%ld", |
| |
248 jabber_ibb_session_get_block_size(sess)); |
| |
249 xmlnode_set_attrib(open, "block-size", block_size); |
| |
250 xmlnode_insert_child(set->node, open); |
| |
251 |
| |
252 jabber_iq_set_callback(set, jabber_ibb_session_opened_cb, sess); |
| |
253 |
| |
254 jabber_iq_send(set); |
| |
255 } |
| |
256 } |
| |
257 |
| |
258 void |
| |
259 jabber_ibb_session_close(JabberIBBSession *sess) |
| |
260 { |
| |
261 JabberIBBSessionState state = jabber_ibb_session_get_state(sess); |
| |
262 |
| |
263 if (state != JABBER_IBB_SESSION_OPENED && state != JABBER_IBB_SESSION_ERROR) { |
| |
264 purple_debug_error("jabber", |
| |
265 "jabber_ibb_session_close called on a session that has not been" |
| |
266 "opened\n"); |
| |
267 } else { |
| |
268 JabberIq *set = jabber_iq_new(jabber_ibb_session_get_js(sess), |
| |
269 JABBER_IQ_SET); |
| |
270 xmlnode *close = xmlnode_new("close"); |
| |
271 |
| |
272 xmlnode_set_attrib(set->node, "to", jabber_ibb_session_get_who(sess)); |
| |
273 xmlnode_set_namespace(close, XEP_0047_NAMESPACE); |
| |
274 xmlnode_set_attrib(close, "sid", jabber_ibb_session_get_sid(sess)); |
| |
275 xmlnode_insert_child(set->node, close); |
| |
276 jabber_iq_send(set); |
| |
277 sess->state = JABBER_IBB_SESSION_CLOSED; |
| |
278 } |
| |
279 } |
| |
280 |
| |
281 void |
| |
282 jabber_ibb_session_accept(JabberIBBSession *sess) |
| |
283 { |
| |
284 JabberIq *result = jabber_iq_new(jabber_ibb_session_get_js(sess), |
| |
285 JABBER_IQ_RESULT); |
| |
286 |
| |
287 xmlnode_set_attrib(result->node, "to", jabber_ibb_session_get_who(sess)); |
| |
288 jabber_iq_set_id(result, sess->id); |
| |
289 jabber_iq_send(result); |
| |
290 sess->state = JABBER_IBB_SESSION_OPENED; |
| |
291 } |
| |
292 |
| |
293 static void |
| |
294 jabber_ibb_session_send_acknowledge_cb(JabberStream *js, xmlnode *packet, gpointer data) |
| |
295 { |
| |
296 JabberIBBSession *sess = (JabberIBBSession *) data; |
| |
297 xmlnode *error = xmlnode_get_child(packet, "error"); |
| |
298 |
| |
299 if (sess) { |
| |
300 /* reset callback */ |
| |
301 if (sess->last_iq_id) { |
| |
302 g_free(sess->last_iq_id); |
| |
303 sess->last_iq_id = NULL; |
| |
304 } |
| |
305 |
| |
306 if (error) { |
| |
307 jabber_ibb_session_close(sess); |
| |
308 sess->state = JABBER_IBB_SESSION_ERROR; |
| |
309 |
| |
310 if (sess->error_cb) { |
| |
311 sess->error_cb(sess); |
| |
312 } |
| |
313 } else { |
| |
314 if (sess->data_sent_cb) { |
| |
315 sess->data_sent_cb(sess); |
| |
316 } |
| |
317 } |
| |
318 } else { |
| |
319 /* the session has gone away, it was probably cancelled */ |
| |
320 purple_debug_info("jabber", |
| |
321 "got response from send data, but IBB session is no longer active\n"); |
| |
322 } |
| |
323 } |
| |
324 |
| |
325 void |
| |
326 jabber_ibb_session_send_data(JabberIBBSession *sess, gpointer data, gsize size) |
| |
327 { |
| |
328 JabberIBBSessionState state = jabber_ibb_session_get_state(sess); |
| |
329 |
| |
330 if (state != JABBER_IBB_SESSION_OPENED) { |
| |
331 purple_debug_error("jabber", |
| |
332 "trying to send data on a non-open IBB session\n"); |
| |
333 } else if (size > jabber_ibb_session_get_block_size(sess)) { |
| |
334 purple_debug_error("jabber", |
| |
335 "trying to send a too large packet in the IBB session\n"); |
| |
336 } else { |
| |
337 JabberIq *set = jabber_iq_new(jabber_ibb_session_get_js(sess), |
| |
338 JABBER_IQ_SET); |
| |
339 xmlnode *data_element = xmlnode_new("data"); |
| |
340 char *base64 = purple_base64_encode(data, size); |
| |
341 char seq[10]; |
| |
342 g_snprintf(seq, sizeof(seq), "%d", jabber_ibb_session_get_send_seq(sess)); |
| |
343 |
| |
344 xmlnode_set_attrib(set->node, "to", jabber_ibb_session_get_who(sess)); |
| |
345 xmlnode_set_namespace(data_element, XEP_0047_NAMESPACE); |
| |
346 xmlnode_set_attrib(data_element, "sid", jabber_ibb_session_get_sid(sess)); |
| |
347 xmlnode_set_attrib(data_element, "seq", seq); |
| |
348 xmlnode_insert_data(data_element, base64, strlen(base64)); |
| |
349 |
| |
350 xmlnode_insert_child(set->node, data_element); |
| |
351 |
| |
352 purple_debug_info("jabber", |
| |
353 "IBB: setting send <iq/> callback for session %lx %s\n", sess, |
| |
354 sess->sid); |
| |
355 jabber_iq_set_callback(set, jabber_ibb_session_send_acknowledge_cb, sess); |
| |
356 sess->last_iq_id = g_strdup(xmlnode_get_attrib(set->node, "id")); |
| |
357 purple_debug_info("jabber", "IBB: set sess->last_iq_id: %lx %lx\n", |
| |
358 sess->last_iq_id, xmlnode_get_attrib(set->node, "id")); |
| |
359 jabber_iq_send(set); |
| |
360 |
| |
361 g_free(base64); |
| |
362 (sess->send_seq)++; |
| |
363 } |
| |
364 } |
| |
365 |
| |
366 void |
| |
367 jabber_ibb_parse(JabberStream *js, xmlnode *packet) |
| |
368 { |
| |
369 xmlnode *data = xmlnode_get_child_with_namespace(packet, "data", |
| |
370 XEP_0047_NAMESPACE); |
| |
371 xmlnode *close = xmlnode_get_child_with_namespace(packet, "close", |
| |
372 XEP_0047_NAMESPACE); |
| |
373 xmlnode *open = xmlnode_get_child_with_namespace(packet, "open", |
| |
374 XEP_0047_NAMESPACE); |
| |
375 const gchar *sid = |
| |
376 data ? xmlnode_get_attrib(data, "sid") : |
| |
377 close ? xmlnode_get_attrib(close, "sid") : NULL; |
| |
378 JabberIBBSession *sess = |
| |
379 sid ? g_hash_table_lookup(jabber_ibb_sessions, sid) : NULL; |
| |
380 const gchar *who = xmlnode_get_attrib(packet, "from"); |
| |
381 |
| |
382 if (sess) { |
| |
383 |
| |
384 if (strcmp(who, jabber_ibb_session_get_who(sess)) != 0) { |
| |
385 /* the iq comes from a different JID than the remote JID of the |
| |
386 session, ignore it */ |
| |
387 purple_debug_error("jabber", |
| |
388 "Got IBB iq from wrong JID, ignoring\n"); |
| |
389 } else if (data) { |
| |
390 guint16 seq = atoi(xmlnode_get_attrib(data, "seq")); |
| |
391 |
| |
392 /* reject the data, and set the session in error if we get an |
| |
393 out-of-order packet */ |
| |
394 if (seq == jabber_ibb_session_get_recv_seq(sess)) { |
| |
395 /* sequence # is the expected... */ |
| |
396 JabberIq *result = jabber_iq_new(js, JABBER_IQ_RESULT); |
| |
397 |
| |
398 jabber_iq_set_id(result, xmlnode_get_attrib(packet, "id")); |
| |
399 xmlnode_set_attrib(result->node, "to", |
| |
400 xmlnode_get_attrib(packet, "from")); |
| |
401 |
| |
402 if (sess->data_received_cb) { |
| |
403 gchar *base64 = xmlnode_get_data(data); |
| |
404 gsize size; |
| |
405 gpointer rawdata = purple_base64_decode(base64, &size); |
| |
406 |
| |
407 g_free(base64); |
| |
408 |
| |
409 if (rawdata) { |
| |
410 if (size > jabber_ibb_session_get_block_size(sess)) { |
| |
411 purple_debug_error("jabber", |
| |
412 "IBB: received a too large packet\n"); |
| |
413 } else { |
| |
414 sess->data_received_cb(sess, rawdata, size); |
| |
415 } |
| |
416 g_free(rawdata); |
| |
417 } else { |
| |
418 purple_debug_error("jabber", |
| |
419 "IBB: invalid BASE64 data received\n"); |
| |
420 } |
| |
421 } |
| |
422 |
| |
423 (sess->recv_seq)++; |
| |
424 jabber_iq_send(result); |
| |
425 |
| |
426 } else { |
| |
427 purple_debug_error("jabber", |
| |
428 "Received an out-of-order IBB packet\n"); |
| |
429 sess->state = JABBER_IBB_SESSION_ERROR; |
| |
430 |
| |
431 if (sess->error_cb) { |
| |
432 sess->error_cb(sess); |
| |
433 } |
| |
434 } |
| |
435 } else if (close) { |
| |
436 sess->state = JABBER_IBB_SESSION_CLOSED; |
| |
437 purple_debug_info("jabber", "IBB: received close\n"); |
| |
438 |
| |
439 if (sess->closed_cb) { |
| |
440 purple_debug_info("jabber", "IBB: calling closed handler\n"); |
| |
441 sess->closed_cb(sess); |
| |
442 } |
| |
443 |
| |
444 } else { |
| |
445 /* this should never happen */ |
| |
446 purple_debug_error("jabber", "Received bogus iq for IBB session\n"); |
| |
447 } |
| |
448 } else if (open) { |
| |
449 JabberIq *result; |
| |
450 const GList *iterator; |
| |
451 |
| |
452 /* run all open handlers registered until one returns true */ |
| |
453 for (iterator = open_handlers ; iterator ; |
| |
454 iterator = g_list_next(iterator)) { |
| |
455 JabberIBBOpenHandler *handler = (JabberIBBOpenHandler *) iterator->data; |
| |
456 |
| |
457 if (handler(js, packet)) { |
| |
458 result = jabber_iq_new(js, JABBER_IQ_RESULT); |
| |
459 xmlnode_set_attrib(result->node, "to", |
| |
460 xmlnode_get_attrib(packet, "from")); |
| |
461 jabber_iq_set_id(result, xmlnode_get_attrib(packet, "id")); |
| |
462 jabber_iq_send(result); |
| |
463 return; |
| |
464 } |
| |
465 } |
| |
466 } else { |
| |
467 /* send error reply */ |
| |
468 JabberIq *result = jabber_iq_new(js, JABBER_IQ_ERROR); |
| |
469 xmlnode *error = xmlnode_new("error"); |
| |
470 xmlnode *item_not_found = xmlnode_new("item-not-found"); |
| |
471 |
| |
472 xmlnode_set_namespace(item_not_found, |
| |
473 "urn:ietf:params:xml:ns:xmpp-stanzas"); |
| |
474 xmlnode_set_attrib(error, "code", "440"); |
| |
475 xmlnode_set_attrib(error, "type", "cancel"); |
| |
476 jabber_iq_set_id(result, xmlnode_get_attrib(packet, "id")); |
| |
477 xmlnode_set_attrib(result->node, "to", |
| |
478 xmlnode_get_attrib(packet, "from")); |
| |
479 xmlnode_insert_child(error, item_not_found); |
| |
480 xmlnode_insert_child(result->node, error); |
| |
481 |
| |
482 jabber_iq_send(result); |
| |
483 } |
| |
484 } |
| |
485 |
| |
486 void |
| |
487 jabber_ibb_register_open_handler(JabberIBBOpenHandler *cb) |
| |
488 { |
| |
489 open_handlers = g_list_append(open_handlers, cb); |
| |
490 } |
| |
491 |
| |
492 void |
| |
493 jabber_ibb_unregister_open_handler(JabberIBBOpenHandler *cb) |
| |
494 { |
| |
495 open_handlers = g_list_remove(open_handlers, cb); |
| |
496 } |
| |
497 |
| |
498 void |
| |
499 jabber_ibb_init(void) |
| |
500 { |
| |
501 jabber_ibb_sessions = g_hash_table_new(g_str_hash, g_str_equal); |
| |
502 } |
| |
503 |
| |
504 void |
| |
505 jabber_ibb_uninit(void) |
| |
506 { |
| |
507 g_hash_table_destroy(jabber_ibb_sessions); |
| |
508 g_list_free(open_handlers); |
| |
509 jabber_ibb_sessions = NULL; |
| |
510 open_handlers = NULL; |
| |
511 } |
| |
512 |
| |
513 |
| |
514 |