libpurple/protocols/bonjour/xmpp.c

changeset 42670
a3b862b8dcde
parent 42669
6d41b29637ef
child 42671
68cc8544b438
equal deleted inserted replaced
42669:6d41b29637ef 42670:a3b862b8dcde
1 /*
2 * purple - Bonjour Protocol Plugin
3 *
4 * Purple is the legal property of its developers, whose names are too numerous
5 * to list here. Please refer to the COPYRIGHT file distributed with this
6 * source distribution.
7 *
8 * This program is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation; either version 2 of the License, or
11 * (at your option) any later version.
12 *
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
17 *
18 * You should have received a copy of the GNU General Public License
19 * along with this program; if not, write to the Free Software
20 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA
21 */
22
23 #include <glib/gi18n-lib.h>
24
25 #include <purple.h>
26
27 #include <sys/types.h>
28
29 #include <glib.h>
30 #ifdef HAVE_UNISTD_H
31 #include <unistd.h>
32 #endif
33
34 #include "xmpp.h"
35 #include "parser.h"
36 #include "bonjour.h"
37 #include "buddy.h"
38 #include "bonjour_ft.h"
39
40 #define STREAM_END "</stream:stream>"
41 /* TODO: specify version='1.0' and send stream features */
42 #define DOCTYPE "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n" \
43 "<stream:stream xmlns=\"jabber:client\" xmlns:stream=\"http://etherx.jabber.org/streams\" from=\"%s\" to=\"%s\">"
44
45 enum sent_stream_start_types {
46 NOT_SENT = 0,
47 PARTIALLY_SENT = 1,
48 FULLY_SENT = 2
49 };
50
51 static void
52 xep_iq_parse(PurpleXmlNode *packet, PurpleContact *contact);
53
54 static BonjourXMPPConversation *
55 bonjour_xmpp_conv_new(PurpleContact *contact, PurpleAccount *account,
56 const char *ip)
57 {
58
59 BonjourXMPPConversation *bconv = g_new0(BonjourXMPPConversation, 1);
60 bconv->cancellable = g_cancellable_new();
61 bconv->tx_buf = purple_circular_buffer_new(512);
62 bconv->tx_handler = 0;
63 bconv->rx_handler = 0;
64 bconv->contact = contact;
65 bconv->account = account;
66 bconv->ip = g_strdup(ip);
67
68 bonjour_parser_setup(bconv);
69
70 return bconv;
71 }
72
73 static const char *
74 _font_size_ichat_to_purple(int size)
75 {
76 if (size > 24) {
77 return "7";
78 } else if (size >= 21) {
79 return "6";
80 } else if (size >= 17) {
81 return "5";
82 } else if (size >= 14) {
83 return "4";
84 } else if (size >= 12) {
85 return "3";
86 } else if (size >= 10) {
87 return "2";
88 }
89
90 return "1";
91 }
92
93 static gchar *
94 get_xmlnode_contents(PurpleXmlNode *node)
95 {
96 gchar *contents;
97
98 contents = purple_xmlnode_to_str(node, NULL);
99
100 /* we just want the stuff inside <font></font>
101 * There isn't stuff exposed in PurpleXmlNode.c to do this more cleanly. */
102
103 if (contents) {
104 char *bodystart = strchr(contents, '>');
105 char *bodyend = bodystart ? strrchr(bodystart, '<') : NULL;
106 if (bodystart && bodyend && (bodystart + 1) != bodyend) {
107 *bodyend = '\0';
108 memmove(contents, bodystart + 1, (bodyend - bodystart));
109 }
110 }
111
112 return contents;
113 }
114
115 static void
116 _xmpp_parse_and_write_message_to_ui(PurpleXmlNode *message_node,
117 PurpleContact *contact)
118 {
119 PurpleXmlNode *body_node, *html_node, *events_node;
120 PurpleAccount *account = NULL;
121 PurpleConnection *gc = NULL;
122 gchar *body = NULL;
123 const char *username = NULL;
124
125 account = purple_contact_get_account(contact);
126 gc = purple_account_get_connection(account);
127
128 body_node = purple_xmlnode_get_child(message_node, "body");
129 html_node = purple_xmlnode_get_child(message_node, "html");
130
131 if (body_node == NULL && html_node == NULL) {
132 purple_debug_error("bonjour", "No body or html node found, discarding message.\n");
133 return;
134 }
135
136 events_node = purple_xmlnode_get_child_with_namespace(message_node, "x", "jabber:x:event");
137 if (events_node != NULL) {
138 if (purple_xmlnode_get_child(events_node, "id") != NULL) {
139 /* The user is just typing */
140 /* TODO: Deal with typing notification */
141 return;
142 }
143 }
144
145 if (html_node != NULL) {
146 PurpleXmlNode *html_body_node;
147
148 html_body_node = purple_xmlnode_get_child(html_node, "body");
149 if (html_body_node != NULL) {
150 PurpleXmlNode *html_body_font_node;
151
152 html_body_font_node = purple_xmlnode_get_child(html_body_node, "font");
153 /* Types of messages sent by iChat */
154 if (html_body_font_node != NULL) {
155 gchar *html_body;
156 const char *font_face, *font_size, *font_color,
157 *ichat_balloon_color, *ichat_text_color;
158
159 font_face = purple_xmlnode_get_attrib(html_body_font_node, "face");
160 /* The absolute iChat font sizes should be converted to 1..7 range */
161 font_size = purple_xmlnode_get_attrib(html_body_font_node, "ABSZ");
162 if (font_size != NULL)
163 font_size = _font_size_ichat_to_purple(atoi(font_size));
164 font_color = purple_xmlnode_get_attrib(html_body_font_node, "color");
165 ichat_balloon_color = purple_xmlnode_get_attrib(html_body_node, "ichatballooncolor");
166 ichat_text_color = purple_xmlnode_get_attrib(html_body_node, "ichattextcolor");
167
168 html_body = get_xmlnode_contents(html_body_font_node);
169
170 if (html_body == NULL)
171 /* This is the kind of formatted messages that Purple creates */
172 html_body = purple_xmlnode_to_str(html_body_font_node, NULL);
173
174 if (html_body != NULL) {
175 GString *str = g_string_new("<font");
176
177 if (font_face)
178 g_string_append_printf(str, " face='%s'", font_face);
179 if (font_size)
180 g_string_append_printf(str, " size='%s'", font_size);
181 if (font_color)
182 g_string_append_printf(str, " color='%s'", font_color);
183 else if (ichat_text_color)
184 g_string_append_printf(str, " color='%s'", ichat_text_color);
185 if (ichat_balloon_color)
186 g_string_append_printf(str, " back='%s'", ichat_balloon_color);
187 g_string_append_printf(str, ">%s</font>", html_body);
188
189 body = g_string_free(str, FALSE);
190
191 g_free(html_body);
192 }
193 }
194 }
195 }
196
197 /* Compose the message */
198 if (body == NULL && body_node != NULL)
199 body = purple_xmlnode_get_data(body_node);
200
201 if (body == NULL) {
202 purple_debug_error("bonjour", "No html body or regular body found.\n");
203 return;
204 }
205
206 /* Send the message to the UI */
207 username = purple_contact_info_get_username(PURPLE_CONTACT_INFO(contact));
208 purple_serv_got_im(gc, username, body, 0, time(NULL));
209
210 g_free(body);
211 }
212
213 static GSList *
214 _find_match_buddies_by_address(const BonjourXMPP *jdata, const char *address)
215 {
216 PurpleContactManager *manager = NULL;
217 GListModel *contacts = NULL;
218 GSList *ret = NULL;
219
220 manager = purple_contact_manager_get_default();
221 contacts = purple_contact_manager_get_all(manager, jdata->account);
222 for(guint i = 0; i < g_list_model_get_n_items(contacts); i++) {
223 PurpleContact *contact = NULL;
224 BonjourBuddy *bb = NULL;
225
226 contact = g_list_model_get_item(contacts, i);
227 bb = g_object_get_data(G_OBJECT(contact), "bonjour-buddy");
228 if(bb != NULL) {
229 if(g_slist_find_custom(bb->ips, address,
230 (GCompareFunc)g_ascii_strcasecmp))
231 {
232 ret = g_slist_prepend(ret, contact);
233 }
234 }
235
236 g_clear_object(&contact);
237 }
238
239 return ret;
240 }
241
242 static void
243 _send_data_write_cb(GObject *stream, gpointer data)
244 {
245 PurpleContact *contact = data;
246 BonjourBuddy *bb = NULL;
247 BonjourXMPPConversation *bconv = NULL;
248 gsize writelen;
249 gssize ret;
250 GError *error = NULL;
251
252 bb = g_object_get_data(G_OBJECT(contact), "bonjour-buddy");
253 bconv = bb->conversation;
254 writelen = purple_circular_buffer_get_max_read(bconv->tx_buf);
255
256 if (writelen == 0) {
257 g_clear_handle_id(&bconv->tx_handler, g_source_remove);
258 return;
259 }
260
261 ret = g_pollable_output_stream_write_nonblocking(
262 G_POLLABLE_OUTPUT_STREAM(stream),
263 purple_circular_buffer_get_output(bconv->tx_buf), writelen,
264 bconv->cancellable, &error);
265
266 if (ret < 0 && error->code == G_IO_ERROR_WOULD_BLOCK) {
267 g_clear_error(&error);
268 return;
269 } else if (ret <= 0) {
270 PurpleAccount *account = NULL;
271 PurpleConversation *conv = NULL;
272 PurpleConversationManager *manager = NULL;
273
274 manager = purple_conversation_manager_get_default();
275
276 purple_debug_error(
277 "bonjour",
278 "Error sending message to buddy %s error: %s",
279 purple_contact_info_get_username(PURPLE_CONTACT_INFO(contact)),
280 error ? error->message : "(null)");
281
282 account = purple_contact_get_account(contact);
283
284 conv = purple_conversation_manager_find_im(manager, account, bb->name);
285 if (conv != NULL) {
286 purple_conversation_write_system_message(conv,
287 _("Unable to send message."),
288 PURPLE_MESSAGE_ERROR);
289 }
290
291 bonjour_xmpp_close_conversation(bb->conversation);
292 bb->conversation = NULL;
293 g_clear_error(&error);
294
295 return;
296 }
297
298 purple_circular_buffer_mark_read(bconv->tx_buf, ret);
299 }
300
301 static gint
302 _send_data(PurpleContact *contact, char *message)
303 {
304 BonjourBuddy *bb = g_object_get_data(G_OBJECT(contact), "bonjour-buddy");
305 BonjourXMPPConversation *bconv = bb->conversation;
306 gsize len = strlen(message);
307 gssize ret;
308 GError *error = NULL;
309
310 /* If we're not ready to actually send, append it to the buffer */
311 if (bconv->tx_handler != 0
312 || bconv->sent_stream_start != FULLY_SENT
313 || !bconv->recv_stream_start
314 || purple_circular_buffer_get_max_read(bconv->tx_buf) > 0) {
315 ret = -1;
316 g_set_error_literal(&error, G_IO_ERROR, G_IO_ERROR_WOULD_BLOCK,
317 "Not yet ready to send.");
318 } else {
319 ret = g_pollable_output_stream_write_nonblocking(
320 G_POLLABLE_OUTPUT_STREAM(bconv->output), message, len,
321 bconv->cancellable, &error);
322 }
323
324 if (ret == -1 && error->code == G_IO_ERROR_WOULD_BLOCK) {
325 ret = 0;
326 g_clear_error(&error);
327 } else if (ret <= 0) {
328 PurpleAccount *account;
329 PurpleConversation *conv;
330 PurpleConversationManager *manager;
331
332 manager = purple_conversation_manager_get_default();
333
334 purple_debug_error(
335 "bonjour",
336 "Error sending message to buddy %s error: %s",
337 purple_contact_info_get_username(PURPLE_CONTACT_INFO(contact)),
338 error ? error->message : "(null)");
339
340 account = purple_contact_get_account(contact);
341
342 conv = purple_conversation_manager_find_im(manager, account, bb->name);
343 if (conv != NULL) {
344 purple_conversation_write_system_message(conv,
345 _("Unable to send message."),
346 PURPLE_MESSAGE_ERROR);
347 }
348
349 bonjour_xmpp_close_conversation(bb->conversation);
350 bb->conversation = NULL;
351 g_clear_error(&error);
352 return -1;
353 }
354
355 if ((gsize)ret < len) {
356 /* Don't interfere with the stream starting */
357 if (bconv->sent_stream_start == FULLY_SENT &&
358 bconv->recv_stream_start && bconv->tx_handler == 0) {
359 GSource *source =
360 g_pollable_output_stream_create_source(
361 G_POLLABLE_OUTPUT_STREAM(bconv->output),
362 bconv->cancellable);
363 g_source_set_callback(source,
364 G_SOURCE_FUNC(_send_data_write_cb),
365 g_object_ref(contact), g_object_unref);
366 bconv->tx_handler = g_source_attach(source, NULL);
367 g_source_unref(source);
368 }
369 purple_circular_buffer_append(bconv->tx_buf, message + ret, len - ret);
370 }
371
372 return ret;
373 }
374
375 void
376 bonjour_xmpp_process_packet(PurpleContact *contact, PurpleXmlNode *packet) {
377 g_return_if_fail(PURPLE_IS_CONTACT(contact));
378 g_return_if_fail(packet != NULL);
379
380 if (purple_strequal(packet->name, "message")) {
381 _xmpp_parse_and_write_message_to_ui(packet, contact);
382 } else if (purple_strequal(packet->name, "iq")) {
383 xep_iq_parse(packet, contact);
384 } else {
385 purple_debug_warning("bonjour", "Unknown packet: %s\n",
386 packet->name ? packet->name : "(null)");
387 }
388 }
389
390 static void
391 bonjour_xmpp_stream_ended(BonjourXMPPConversation *bconv) {
392 /* Inform the user that the conversation has been closed */
393 BonjourBuddy *bb = NULL;
394 const gchar *name = "(unknown)";
395
396 if(PURPLE_IS_CONTACT(bconv->contact)) {
397 PurpleContactInfo *info = PURPLE_CONTACT_INFO(bconv->contact);
398
399 name = purple_contact_info_get_username(info);
400 bb = g_object_get_data(G_OBJECT(bconv->contact), "bonjour-buddy");
401 }
402
403 purple_debug_info("bonjour", "Received conversation close notification from %s.\n", name);
404
405 /* Close the socket, clear the watcher and free memory */
406 bonjour_xmpp_close_conversation(bconv);
407 if(bb != NULL) {
408 bb->conversation = NULL;
409 }
410 }
411
412 static gboolean
413 _client_socket_handler(GObject *stream, gpointer data)
414 {
415 BonjourXMPPConversation *bconv = data;
416 GError *error = NULL;
417 gssize len;
418 static char message[4096];
419
420 /* Read the data from the socket */
421 len = g_pollable_input_stream_read_nonblocking(
422 G_POLLABLE_INPUT_STREAM(stream), message, sizeof(message) - 1,
423 bconv->cancellable, &error);
424 if (len == -1) {
425 /* There has been an error reading from the socket */
426 if (error == NULL || (error->code != G_IO_ERROR_WOULD_BLOCK &&
427 error->code != G_IO_ERROR_CANCELLED)) {
428 purple_debug_warning(
429 "bonjour",
430 "receive of %" G_GSSIZE_FORMAT " error: %s",
431 len, error ? error->message : "(null)");
432
433 bonjour_xmpp_close_conversation(bconv);
434 if(PURPLE_IS_CONTACT(bconv->contact)) {
435 BonjourBuddy *bb = NULL;
436
437 bb = g_object_get_data(G_OBJECT(bconv->contact),
438 "bonjour-buddy");
439 if(bb != NULL) {
440 bb->conversation = NULL;
441 }
442 }
443
444 /* I guess we really don't need to notify the user.
445 * If they try to send another message it'll reconnect */
446 }
447 g_clear_error(&error);
448 return FALSE;
449 } else if (len == 0) { /* The other end has closed the socket */
450 const gchar *name = NULL;
451 name = purple_contact_info_get_username(PURPLE_CONTACT_INFO(bconv->contact));
452 purple_debug_warning("bonjour", "Connection closed (without stream end) by %s.\n", (name) ? name : "(unknown)");
453 bonjour_xmpp_stream_ended(bconv);
454 return FALSE;
455 }
456
457 message[len] = '\0';
458
459 purple_debug_info("bonjour", "Receive: -%s- %" G_GSSIZE_FORMAT " bytes\n", message, len);
460 bonjour_parser_process(bconv, message, len);
461
462 return TRUE;
463 }
464
465 struct _stream_start_data {
466 char *msg;
467 };
468
469 static void
470 _start_stream(GObject *stream, gpointer data)
471 {
472 BonjourXMPPConversation *bconv = data;
473 struct _stream_start_data *ss = bconv->stream_data;
474 GError *error = NULL;
475 gsize len;
476 gssize ret;
477
478 len = strlen(ss->msg);
479
480 /* Start Stream */
481 ret = g_pollable_output_stream_write_nonblocking(
482 G_POLLABLE_OUTPUT_STREAM(stream), ss->msg, len,
483 bconv->cancellable, &error);
484
485 if (ret == -1 && error->code == G_IO_ERROR_WOULD_BLOCK) {
486 g_clear_error(&error);
487 return;
488 } else if (ret <= 0) {
489 PurpleConversation *conv;
490 PurpleConversationManager *manager;
491 BonjourBuddy *bb = NULL;
492 const char *bname = bconv->buddy_name;
493
494 manager = purple_conversation_manager_get_default();
495
496 if(PURPLE_IS_CONTACT(bconv->contact)) {
497 bb = g_object_get_data(G_OBJECT(bconv->contact), "bonjour-buddy");
498 bname = purple_contact_info_get_username(PURPLE_CONTACT_INFO(bconv->contact));
499 }
500
501 purple_debug_error(
502 "bonjour",
503 "Error starting stream with buddy %s at %s error: %s",
504 bname ? bname : "(unknown)", bconv->ip,
505 error ? error->message : "(null)");
506
507 conv = purple_conversation_manager_find_im(manager, bconv->account,
508 bname);
509 if (conv != NULL) {
510 purple_conversation_write_system_message(conv,
511 _("Unable to send the message, the conversation couldn't be started."),
512 PURPLE_MESSAGE_ERROR);
513 }
514
515 bonjour_xmpp_close_conversation(bconv);
516 if(bb != NULL) {
517 bb->conversation = NULL;
518 }
519
520 g_clear_error(&error);
521 return;
522 }
523
524 /* This is EXTREMELY unlikely to happen */
525 if (G_UNLIKELY((gsize)ret < len)) {
526 char *tmp = g_strdup(ss->msg + ret);
527 g_free(ss->msg);
528 ss->msg = tmp;
529 return;
530 }
531
532 g_free(ss->msg);
533 g_free(ss);
534 bconv->stream_data = NULL;
535
536 /* Stream started; process the send buffer if there is one */
537 g_clear_handle_id(&bconv->tx_handler, g_source_remove);
538 bconv->sent_stream_start = FULLY_SENT;
539
540 bonjour_xmpp_stream_started(bconv);
541 }
542
543 static gboolean
544 bonjour_xmpp_send_stream_init(BonjourXMPPConversation *bconv,
545 GError **error)
546 {
547 gchar *stream_start;
548 gsize len;
549 gssize ret;
550 const char *bname = bconv->buddy_name;
551
552 g_return_val_if_fail(error != NULL, FALSE);
553
554 if(PURPLE_IS_CONTACT(bconv->contact)) {
555 bname = purple_contact_info_get_username(PURPLE_CONTACT_INFO(bconv->contact));
556 }
557
558 /* If we have no idea who "to" is, use an empty string.
559 * If we don't know now, it is because the other side isn't playing nice, so they can't complain. */
560 if (bname == NULL)
561 bname = "";
562
563 stream_start = g_strdup_printf(DOCTYPE, bonjour_get_jid(bconv->account), bname);
564 len = strlen(stream_start);
565
566 bconv->sent_stream_start = PARTIALLY_SENT;
567
568 /* Start the stream */
569 ret = g_pollable_output_stream_write_nonblocking(
570 G_POLLABLE_OUTPUT_STREAM(bconv->output), stream_start, len,
571 bconv->cancellable, error);
572 if (ret == -1 && (*error)->code == G_IO_ERROR_WOULD_BLOCK) {
573 ret = 0;
574 g_clear_error(error);
575 } else if (ret <= 0) {
576 purple_debug_error(
577 "bonjour",
578 "Error starting stream with buddy %s at %s error: %s",
579 (*bname) ? bname : "(unknown)", bconv->ip,
580 *error ? (*error)->message : "(null)");
581
582 if(PURPLE_IS_CONTACT(bconv->contact)) {
583 PurpleConversation *conv;
584 PurpleConversationManager *manager;
585
586 manager = purple_conversation_manager_get_default();
587
588 conv = purple_conversation_manager_find_im(manager, bconv->account,
589 bname);
590 if (conv != NULL) {
591 purple_conversation_write_system_message(conv,
592 _("Unable to send the message, the conversation couldn't be started."),
593 PURPLE_MESSAGE_ERROR);
594 }
595 }
596
597 purple_gio_graceful_close(G_IO_STREAM(bconv->socket),
598 G_INPUT_STREAM(bconv->input),
599 G_OUTPUT_STREAM(bconv->output));
600 g_clear_object(&bconv->socket);
601 g_clear_object(&bconv->input);
602 g_clear_object(&bconv->output);
603 g_free(stream_start);
604
605 return FALSE;
606 }
607
608 /* This is unlikely to happen */
609 if ((gsize)ret < len) {
610 GSource *source;
611 struct _stream_start_data *ss = g_new(struct _stream_start_data, 1);
612 ss->msg = g_strdup(stream_start + ret);
613 bconv->stream_data = ss;
614 /* Finish sending the stream start */
615 source = g_pollable_output_stream_create_source(
616 G_POLLABLE_OUTPUT_STREAM(bconv->output),
617 bconv->cancellable);
618 g_source_set_callback(source, G_SOURCE_FUNC(_start_stream), bconv,
619 NULL);
620 bconv->tx_handler = g_source_attach(source, NULL);
621 g_source_unref(source);
622 } else {
623 bconv->sent_stream_start = FULLY_SENT;
624 }
625
626 g_free(stream_start);
627
628 return TRUE;
629 }
630
631 /* This gets called when we've successfully sent our <stream:stream />
632 * AND when we've received a <stream:stream /> */
633 void
634 bonjour_xmpp_stream_started(BonjourXMPPConversation *bconv)
635 {
636 GError *error = NULL;
637
638 if (bconv->sent_stream_start == NOT_SENT &&
639 !bonjour_xmpp_send_stream_init(bconv, &error)) {
640 const char *bname = bconv->buddy_name;
641
642 if(PURPLE_IS_CONTACT(bconv->contact)) {
643 bname = purple_contact_info_get_username(PURPLE_CONTACT_INFO(bconv->contact));
644 }
645
646 purple_debug_error(
647 "bonjour",
648 "Error starting stream with buddy %s at %s error: %s",
649 bname ? bname : "(unknown)", bconv->ip,
650 error ? error->message : "(null)");
651
652 if(PURPLE_IS_CONTACT(bconv->contact)) {
653 PurpleConversation *conv;
654 PurpleConversationManager *manager;
655
656 manager = purple_conversation_manager_get_default();
657
658 conv = purple_conversation_manager_find_im(manager, bconv->account,
659 bname);
660 if (conv != NULL) {
661 purple_conversation_write_system_message(conv,
662 _("Unable to send the message, the conversation couldn't be started."),
663 PURPLE_MESSAGE_ERROR);
664 }
665 }
666
667 /* We don't want to receive anything else */
668 purple_gio_graceful_close(G_IO_STREAM(bconv->socket),
669 G_INPUT_STREAM(bconv->input),
670 G_OUTPUT_STREAM(bconv->output));
671 g_clear_object(&bconv->socket);
672 g_clear_object(&bconv->input);
673 g_clear_object(&bconv->output);
674
675 /* This must be asynchronous because it destroys the parser and we
676 * may be in the middle of parsing.
677 */
678 async_bonjour_xmpp_close_conversation(bconv);
679 g_clear_error(&error);
680 return;
681 }
682
683 /* If the stream has been completely started and we know who we're talking to, we can start doing stuff. */
684 /* I don't think the circ_buffer can actually contain anything without a buddy being associated, but lets be explicit. */
685 if(bconv->sent_stream_start == FULLY_SENT && bconv->recv_stream_start &&
686 PURPLE_IS_CONTACT(bconv->contact) &&
687 purple_circular_buffer_get_max_read(bconv->tx_buf) > 0)
688 {
689 /* Watch for when we can write the buffered messages */
690 GSource *source = g_pollable_output_stream_create_source(
691 G_POLLABLE_OUTPUT_STREAM(bconv->output),
692 bconv->cancellable);
693 g_source_set_callback(source, G_SOURCE_FUNC(_send_data_write_cb),
694 g_object_ref(bconv->contact), g_object_unref);
695 bconv->tx_handler = g_source_attach(source, NULL);
696 g_source_unref(source);
697 /* We can probably write the data right now. */
698 g_object_ref(bconv->contact);
699 _send_data_write_cb(G_OBJECT(bconv->output), bconv->contact);
700 g_object_unref(bconv->contact);
701 }
702 }
703
704 #ifndef INET6_ADDRSTRLEN
705 #define INET6_ADDRSTRLEN 46
706 #endif
707
708 static void
709 _server_socket_handler(G_GNUC_UNUSED GSocketService *service,
710 GSocketConnection *connection,
711 G_GNUC_UNUSED GObject *source_object, gpointer data)
712 {
713 BonjourXMPP *jdata = data;
714 GSocketAddress *their_addr; /* connector's address information */
715 GInetAddress *their_inet_addr;
716 gchar *address_text;
717 BonjourXMPPConversation *bconv;
718 GSList *contacts;
719 GSource *source;
720
721 their_addr = g_socket_connection_get_remote_address(connection, NULL);
722 if (their_addr == NULL) {
723 return;
724 }
725 their_inet_addr = g_inet_socket_address_get_address(
726 G_INET_SOCKET_ADDRESS(their_addr));
727
728 /* Look for the buddy that has opened the conversation and fill information */
729 address_text = g_inet_address_to_string(their_inet_addr);
730 if (g_inet_address_get_family(their_inet_addr) ==
731 G_SOCKET_FAMILY_IPV6 &&
732 g_inet_address_get_is_link_local(their_inet_addr)) {
733 gchar *tmp = g_strdup_printf(
734 "%s%%%d", address_text,
735 g_inet_socket_address_get_scope_id(
736 G_INET_SOCKET_ADDRESS(their_addr)));
737 g_free(address_text);
738 address_text = tmp;
739 }
740 g_object_unref(their_addr);
741
742 purple_debug_info("bonjour", "Received incoming connection from %s.\n", address_text);
743
744 contacts = _find_match_buddies_by_address(jdata, address_text);
745 if (contacts == NULL) {
746 purple_debug_info("bonjour", "We don't like invisible buddies, this is not a superheroes comic\n");
747 g_free(address_text);
748 return;
749 }
750
751 g_slist_free(contacts);
752
753 /* We've established that this *could* be from one of our buddies.
754 * Wait for the stream open to see if that matches too before assigning it.
755 */
756 bconv = bonjour_xmpp_conv_new(NULL, jdata->account, address_text);
757
758 /* We wait for the stream start before doing anything else */
759 bconv->socket = g_object_ref(connection);
760 bconv->input = g_object_ref(
761 g_io_stream_get_input_stream(G_IO_STREAM(bconv->socket)));
762 bconv->output = g_object_ref(
763 g_io_stream_get_output_stream(G_IO_STREAM(bconv->socket)));
764 source = g_pollable_input_stream_create_source(
765 G_POLLABLE_INPUT_STREAM(bconv->input), bconv->cancellable);
766 g_source_set_callback(source, G_SOURCE_FUNC(_client_socket_handler),
767 bconv, NULL);
768 bconv->rx_handler = g_source_attach(source, NULL);
769 g_source_unref(source);
770 g_free(address_text);
771 }
772
773 gint
774 bonjour_xmpp_start(BonjourXMPP *jdata)
775 {
776 GError *error = NULL;
777 guint16 port;
778
779 purple_debug_info("bonjour", "Attempting to bind IP socket to port %d.",
780 jdata->port);
781
782 /* Open a listening server for incoming conversations */
783 jdata->service = g_socket_service_new();
784 g_socket_listener_set_backlog(G_SOCKET_LISTENER(jdata->service), 10);
785 port = jdata->port;
786 if (!g_socket_listener_add_inet_port(G_SOCKET_LISTENER(jdata->service),
787 port, NULL, &error)) {
788 purple_debug_info("bonjour",
789 "Unable to bind to specified port %i: %s",
790 port, error ? error->message : "(unknown)");
791 g_clear_error(&error);
792 port = g_socket_listener_add_any_inet_port(
793 G_SOCKET_LISTENER(jdata->service), NULL, &error);
794 if (port == 0) {
795 purple_debug_error(
796 "bonjour", "Unable to create socket: %s",
797 error ? error->message : "(unknown)");
798 g_clear_error(&error);
799 return -1;
800 }
801 }
802 purple_debug_info("bonjour", "Bound IP socket to port %u.", port);
803 jdata->port = port;
804
805 g_signal_connect(G_OBJECT(jdata->service), "incoming",
806 G_CALLBACK(_server_socket_handler), jdata);
807
808 return jdata->port;
809 }
810
811 static void
812 _connected_to_buddy(GObject *source, GAsyncResult *res, gpointer user_data)
813 {
814 BonjourBuddy *bb = NULL;
815 PurpleAccount *account = NULL;
816 PurpleContact *contact = user_data;
817 GSocketConnection *conn;
818 GSource *rx_source;
819 GError *error = NULL;
820 const char *username = NULL;
821
822 bb = g_object_get_data(G_OBJECT(contact), "bonjour-buddy");
823 conn = g_socket_client_connect_to_host_finish(G_SOCKET_CLIENT(source),
824 res, &error);
825 username = purple_contact_info_get_username(PURPLE_CONTACT_INFO(contact));
826 account = purple_contact_get_account(contact);
827
828 if (conn == NULL) {
829 PurpleConversation *conv = NULL;
830 PurpleConversationManager *manager = NULL;
831 GSList *tmp;
832
833 if (error && error->code == G_IO_ERROR_CANCELLED) {
834 /* This conversation was closed before it started. */
835 g_error_free(error);
836 g_clear_object(&contact);
837
838 return;
839 }
840
841 purple_debug_error("bonjour",
842 "Error connecting to buddy %s at %s:%d "
843 "(%s); Trying next IP address",
844 username,
845 bb->conversation->ip, bb->port_p2pj,
846 error ? error->message : "(unknown)");
847 g_clear_error(&error);
848
849 /* There may be multiple entries for the same IP - one per
850 * presence received (e.g. multiple interfaces).
851 * We need to make sure that we find the previously used entry.
852 */
853 tmp = g_slist_find(bb->ips, bb->conversation->ip_link);
854 if (tmp)
855 tmp = g_slist_next(tmp);
856
857 if (tmp != NULL) {
858 const gchar *ip;
859 GSocketClient *client;
860
861 bb->conversation->ip_link = ip = tmp->data;
862
863 purple_debug_info("bonjour", "Starting conversation with %s at %s:%d\n",
864 username, ip, bb->port_p2pj);
865
866 /* Make sure to connect without a proxy. */
867 client = g_socket_client_new();
868 if (client != NULL) {
869 g_free(bb->conversation->ip);
870 bb->conversation->ip = g_strdup(ip);
871 /* We pass our reference on contact to the callback. */
872 g_socket_client_connect_to_host_async(
873 client, ip, bb->port_p2pj,
874 bb->conversation->cancellable,
875 _connected_to_buddy, contact);
876 g_object_unref(client);
877 return;
878 }
879 }
880
881 purple_debug_error("bonjour",
882 "No more addresses for buddy %s. Aborting",
883 username);
884
885 manager = purple_conversation_manager_get_default();
886
887 conv = purple_conversation_manager_find_im(manager, account, bb->name);
888 if (conv != NULL) {
889 purple_conversation_write_system_message(conv,
890 _("Unable to send the message, the conversation couldn't be started."),
891 PURPLE_MESSAGE_ERROR);
892 }
893
894 bonjour_xmpp_close_conversation(bb->conversation);
895 bb->conversation = NULL;
896
897 g_clear_object(&contact);
898
899 return;
900 }
901
902 bb->conversation->socket = conn;
903 bb->conversation->input =
904 g_object_ref(g_io_stream_get_input_stream(G_IO_STREAM(conn)));
905 bb->conversation->output =
906 g_object_ref(g_io_stream_get_output_stream(G_IO_STREAM(conn)));
907
908 if (!bonjour_xmpp_send_stream_init(bb->conversation, &error)) {
909 PurpleConversation *conv = NULL;
910 PurpleConversationManager *manager = NULL;
911
912 purple_debug_error("bonjour",
913 "Error starting stream with buddy %s at "
914 "%s:%d error: %s",
915 username,
916 bb->conversation->ip, bb->port_p2pj,
917 error ? error->message : "(null)");
918
919 manager = purple_conversation_manager_get_default();
920
921 conv = purple_conversation_manager_find_im(manager, account, bb->name);
922 if (conv != NULL) {
923 purple_conversation_write_system_message(conv,
924 _("Unable to send the message, the conversation couldn't be started."),
925 PURPLE_MESSAGE_ERROR);
926 }
927
928 bonjour_xmpp_close_conversation(bb->conversation);
929 bb->conversation = NULL;
930 g_clear_error(&error);
931 g_clear_object(&contact);
932
933 return;
934 }
935
936 /* Start listening for the stream acknowledgement */
937 rx_source = g_pollable_input_stream_create_source(
938 G_POLLABLE_INPUT_STREAM(bb->conversation->input),
939 bb->conversation->cancellable);
940 g_source_set_callback(rx_source, G_SOURCE_FUNC(_client_socket_handler),
941 bb->conversation, NULL);
942 bb->conversation->rx_handler = g_source_attach(rx_source, NULL);
943 g_source_unref(rx_source);
944 g_clear_object(&contact);
945 }
946
947 void
948 bonjour_xmpp_conv_match_by_name(BonjourXMPPConversation *bconv) {
949 BonjourBuddy *bb = NULL;
950 PurpleContact *contact = NULL;
951 PurpleContactManager *manager = NULL;
952
953 g_return_if_fail(bconv->ip != NULL);
954 g_return_if_fail(!PURPLE_IS_CONTACT(bconv->contact));
955
956 manager = purple_contact_manager_get_default();
957 contact = purple_contact_manager_find_with_username(manager,
958 bconv->account,
959 bconv->buddy_name);
960
961 if(contact && (bb = g_object_get_data(G_OBJECT(contact), "bonjour-buddy"))) {
962 const char *username = NULL;
963
964 username = purple_contact_info_get_username(PURPLE_CONTACT_INFO(contact));
965
966 purple_debug_info("bonjour",
967 "Found buddy %s for incoming conversation \"from\" attrib.\n",
968 username);
969
970 /* Check that one of the buddy's IPs matches */
971 if (g_slist_find_custom(bb->ips, bconv->ip, (GCompareFunc)g_ascii_strcasecmp)) {
972 PurpleConnection *pc = purple_account_get_connection(bconv->account);
973 BonjourData *bd = purple_connection_get_protocol_data(pc);
974 BonjourXMPP *jdata = bd->xmpp_data;
975
976 purple_debug_info("bonjour", "Matched buddy %s to incoming conversation \"from\" attrib and IP (%s)",
977 username, bconv->ip);
978
979 /* Attach conv. to buddy and remove from pending list */
980 jdata->pending_conversations = g_slist_remove(jdata->pending_conversations, bconv);
981
982 /* Check if the buddy already has a conversation and, if so, replace it */
983 if (bb->conversation != NULL && bb->conversation != bconv) {
984 bonjour_xmpp_close_conversation(bb->conversation);
985 }
986
987 bconv->contact = contact;
988 bb->conversation = bconv;
989 }
990 }
991
992 /* We've failed to match a buddy - give up */
993 if(!PURPLE_IS_CONTACT(bconv->contact)) {
994 /* This must be asynchronous because it destroys the parser and we
995 * may be in the middle of parsing.
996 */
997 async_bonjour_xmpp_close_conversation(bconv);
998 }
999
1000 g_clear_object(&contact);
1001 }
1002
1003
1004 void
1005 bonjour_xmpp_conv_match_by_ip(BonjourXMPPConversation *bconv) {
1006 PurpleConnection *pc = purple_account_get_connection(bconv->account);
1007 BonjourData *bd = purple_connection_get_protocol_data(pc);
1008 BonjourXMPP *jdata = bd->xmpp_data;
1009 GSList *contacts;
1010
1011 contacts = _find_match_buddies_by_address(jdata, bconv->ip);
1012
1013 /* If there is exactly one match, use it */
1014 if (!contacts) {
1015 purple_debug_error("bonjour", "No buddies matched for ip %s.", bconv->ip);
1016 } else if (contacts->next != NULL) {
1017 purple_debug_error("bonjour", "More than one buddy matched for ip %s.", bconv->ip);
1018 } else {
1019 PurpleContact *contact = contacts->data;
1020 BonjourBuddy *bb = g_object_get_data(G_OBJECT(contact), "bonjour-buddy");
1021
1022 purple_debug_info("bonjour", "Matched buddy %s to incoming conversation using IP (%s)",
1023 purple_contact_info_get_username(PURPLE_CONTACT_INFO(contact)),
1024 bconv->ip);
1025
1026 /* Attach conv. to buddy and remove from pending list */
1027 jdata->pending_conversations = g_slist_remove(jdata->pending_conversations, bconv);
1028
1029 /* Check if the buddy already has a conversation and, if so, replace it */
1030 if (bb->conversation != NULL && bb->conversation != bconv) {
1031 bonjour_xmpp_close_conversation(bb->conversation);
1032 }
1033
1034 bconv->contact = contact;
1035 bb->conversation = bconv;
1036 }
1037
1038 /* We've failed to match a buddy - give up */
1039 if(!PURPLE_IS_CONTACT(bconv->contact)) {
1040 /* This must be asynchronous because it destroys the parser and we
1041 * may be in the middle of parsing.
1042 */
1043 async_bonjour_xmpp_close_conversation(bconv);
1044 }
1045
1046 g_slist_free(contacts);
1047 }
1048
1049 static PurpleContact *
1050 _find_or_start_conversation(BonjourXMPP *jdata, const gchar *to)
1051 {
1052 PurpleContact *contact = NULL;
1053 PurpleContactManager *manager = NULL;
1054 BonjourBuddy *bb = NULL;
1055
1056 g_return_val_if_fail(jdata != NULL, NULL);
1057 g_return_val_if_fail(to != NULL, NULL);
1058
1059 manager = purple_contact_manager_get_default();
1060 contact = purple_contact_manager_find_with_username(manager,
1061 jdata->account, to);
1062
1063 if(!PURPLE_IS_CONTACT(contact)) {
1064 return NULL;
1065 }
1066
1067 bb = g_object_get_data(G_OBJECT(contact), "bonjour-buddy");
1068 if(bb == NULL) {
1069 g_clear_object(&contact);
1070 return NULL;
1071 }
1072
1073 /* Check if there is a previously open conversation */
1074 if (bb->conversation == NULL) {
1075 GSocketClient *client;
1076 /* Start with the first IP address. */
1077 const gchar *ip = bb->ips->data;
1078
1079 purple_debug_info("bonjour",
1080 "Starting conversation with %s at %s:%d", to,
1081 ip, bb->port_p2pj);
1082
1083 /* Make sure to connect without a proxy. */
1084 client = g_socket_client_new();
1085 if (client == NULL) {
1086 purple_debug_error("bonjour",
1087 "Unable to connect to buddy (%s).",
1088 to);
1089 g_clear_object(&contact);
1090 return NULL;
1091 }
1092
1093 bb->conversation = bonjour_xmpp_conv_new(contact, jdata->account, ip);
1094 bb->conversation->ip_link = ip;
1095
1096 g_socket_client_connect_to_host_async(
1097 client, ip, bb->port_p2pj,
1098 bb->conversation->cancellable, _connected_to_buddy,
1099 g_object_ref(contact));
1100 g_object_unref(client);
1101 }
1102 return contact;
1103 }
1104
1105 int
1106 bonjour_xmpp_send_message(BonjourXMPP *jdata, const gchar *to, const gchar *body)
1107 {
1108 PurpleXmlNode *message_node, *node, *node2;
1109 gchar *message, *xhtml;
1110 PurpleContact *contact = NULL;
1111 BonjourBuddy *bb;
1112 int ret;
1113
1114 contact = _find_or_start_conversation(jdata, to);
1115 if(!PURPLE_IS_CONTACT(contact) ||
1116 (bb = g_object_get_data(G_OBJECT(contact), "bonjour-buddy")) == NULL)
1117 {
1118 purple_debug_info("bonjour", "Can't send a message to an offline buddy (%s).\n", to);
1119 g_clear_object(&contact);
1120 /* You can not send a message to an offline buddy */
1121 return -10000;
1122 }
1123
1124 purple_markup_html_to_xhtml(body, &xhtml, &message);
1125
1126 message_node = purple_xmlnode_new("message");
1127 purple_xmlnode_set_attrib(message_node, "to", bb->name);
1128 purple_xmlnode_set_attrib(message_node, "from", bonjour_get_jid(jdata->account));
1129 purple_xmlnode_set_attrib(message_node, "type", "chat");
1130
1131 /* Enclose the message from the UI within a "font" node */
1132 node = purple_xmlnode_new_child(message_node, "body");
1133 purple_xmlnode_insert_data(node, message, strlen(message));
1134 g_free(message);
1135
1136 node = purple_xmlnode_new_child(message_node, "html");
1137 purple_xmlnode_set_namespace(node, "http://www.w3.org/1999/xhtml");
1138
1139 node = purple_xmlnode_new_child(node, "body");
1140 message = g_strdup_printf("<font>%s</font>", xhtml);
1141 node2 = purple_xmlnode_from_str(message, strlen(message));
1142 g_free(xhtml);
1143 g_free(message);
1144 purple_xmlnode_insert_child(node, node2);
1145
1146 node = purple_xmlnode_new_child(message_node, "x");
1147 purple_xmlnode_set_namespace(node, "jabber:x:event");
1148 purple_xmlnode_insert_child(node, purple_xmlnode_new("composing"));
1149
1150 message = purple_xmlnode_to_str(message_node, NULL);
1151 purple_xmlnode_free(message_node);
1152
1153 ret = _send_data(contact, message) >= 0;
1154
1155 g_free(message);
1156
1157 g_clear_object(&contact);
1158
1159 return ret;
1160 }
1161
1162 static gboolean
1163 _async_bonjour_xmpp_close_conversation_cb(gpointer data) {
1164 BonjourXMPPConversation *bconv = data;
1165 bonjour_xmpp_close_conversation(bconv);
1166 return FALSE;
1167 }
1168
1169 void
1170 async_bonjour_xmpp_close_conversation(BonjourXMPPConversation *bconv) {
1171 PurpleConnection *pc = purple_account_get_connection(bconv->account);
1172 BonjourData *bd = purple_connection_get_protocol_data(pc);
1173 BonjourXMPP *jdata = bd->xmpp_data;
1174
1175 jdata->pending_conversations = g_slist_remove(jdata->pending_conversations, bconv);
1176
1177 /* Disconnect this conv. from the buddy here so it can't be disposed of twice.*/
1178 if(PURPLE_IS_CONTACT(bconv->contact)) {
1179 BonjourBuddy *bb = g_object_get_data(G_OBJECT(bconv->contact), "bonjour-buddy");
1180 if (bb->conversation == bconv)
1181 bb->conversation = NULL;
1182 }
1183
1184 bconv->close_timeout = g_timeout_add(0, _async_bonjour_xmpp_close_conversation_cb, bconv);
1185 }
1186
1187 void
1188 bonjour_xmpp_close_conversation(BonjourXMPPConversation *bconv)
1189 {
1190 BonjourData *bd = NULL;
1191 PurpleConnection *pc = NULL;
1192
1193 if (bconv == NULL) {
1194 return;
1195 }
1196
1197 pc = purple_account_get_connection(bconv->account);
1198 PURPLE_ASSERT_CONNECTION_IS_VALID(pc);
1199
1200 bd = purple_connection_get_protocol_data(pc);
1201 if (bd) {
1202 bd->xmpp_data->pending_conversations = g_slist_remove(
1203 bd->xmpp_data->pending_conversations, bconv);
1204 }
1205
1206 /* Cancel any file transfers that are waiting to begin */
1207 /* There won't be any transfers if it hasn't been attached to a buddy */
1208 if (PURPLE_IS_CONTACT(bconv->contact) && bd != NULL) {
1209 GSList *xfers, *tmp_next;
1210 const char *username = NULL;
1211
1212 xfers = bd->xfer_lists;
1213 username = purple_contact_info_get_username(PURPLE_CONTACT_INFO(bconv->contact));
1214
1215 while (xfers != NULL) {
1216 PurpleXfer *xfer = xfers->data;
1217
1218 tmp_next = xfers->next;
1219
1220 /* We only need to cancel this if it hasn't actually started transferring. */
1221 /* This will change if we ever support IBB transfers. */
1222 if (purple_strequal(purple_xfer_get_remote_user(xfer), username)
1223 && (purple_xfer_get_status(xfer) == PURPLE_XFER_STATUS_NOT_STARTED
1224 || purple_xfer_get_status(xfer) == PURPLE_XFER_STATUS_UNKNOWN))
1225 {
1226 purple_xfer_cancel_remote(xfer);
1227 }
1228 xfers = tmp_next;
1229 }
1230 }
1231
1232 /* Close the socket and remove the watcher */
1233 if (bconv->socket != NULL) {
1234 /* Send the end of the stream to the other end of the conversation */
1235 if (bconv->sent_stream_start == FULLY_SENT) {
1236 size_t len = strlen(STREAM_END);
1237 if (g_pollable_output_stream_write_nonblocking(
1238 G_POLLABLE_OUTPUT_STREAM(bconv->output),
1239 STREAM_END, len, bconv->cancellable,
1240 NULL) != (gssize)len) {
1241 purple_debug_error("bonjour",
1242 "bonjour_xmpp_close_conversation: "
1243 "couldn't send data\n");
1244 }
1245 }
1246 /* TODO: We're really supposed to wait for "</stream:stream>" before closing the socket */
1247 purple_gio_graceful_close(G_IO_STREAM(bconv->socket),
1248 G_INPUT_STREAM(bconv->input),
1249 G_OUTPUT_STREAM(bconv->output));
1250 }
1251 g_clear_handle_id(&bconv->rx_handler, g_source_remove);
1252 g_clear_handle_id(&bconv->tx_handler, g_source_remove);
1253
1254 /* Cancel any pending operations. */
1255 if (bconv->cancellable != NULL) {
1256 g_cancellable_cancel(bconv->cancellable);
1257 g_clear_object(&bconv->cancellable);
1258 }
1259
1260 /* Free all the data related to the conversation */
1261 g_clear_object(&bconv->socket);
1262 g_clear_object(&bconv->input);
1263 g_clear_object(&bconv->output);
1264
1265 g_object_unref(bconv->tx_buf);
1266 if (bconv->stream_data != NULL) {
1267 struct _stream_start_data *ss = bconv->stream_data;
1268 g_free(ss->msg);
1269 g_free(ss);
1270 }
1271
1272 if (bconv->context != NULL) {
1273 bonjour_parser_setup(bconv);
1274 }
1275
1276 g_clear_handle_id(&bconv->close_timeout, g_source_remove);
1277
1278 g_free(bconv->buddy_name);
1279 g_free(bconv->ip);
1280 g_free(bconv);
1281 }
1282
1283 void
1284 bonjour_xmpp_stop(BonjourXMPP *jdata)
1285 {
1286 /* Close the server socket and remove the watcher */
1287 if (jdata->service) {
1288 g_socket_service_stop(jdata->service);
1289 g_socket_listener_close(G_SOCKET_LISTENER(jdata->service));
1290 g_clear_object(&jdata->service);
1291 }
1292
1293 /* Close all the conversation sockets and remove all the watchers after sending end streams */
1294 if (!purple_account_is_disconnected(jdata->account)) {
1295 PurpleContactManager *manager = NULL;
1296 GListModel *model = NULL;
1297
1298 manager = purple_contact_manager_get_default();
1299 model = purple_contact_manager_get_all(manager, jdata->account);
1300
1301 for(guint i = 0; i < g_list_model_get_n_items(model); i++) {
1302 PurpleContact *contact = g_list_model_get_item(model, i);
1303 BonjourBuddy *bb = g_object_get_data(G_OBJECT(contact),
1304 "bonjour-buddy");
1305
1306 if(bb != NULL && bb->conversation != NULL) {
1307 /* Any ongoing connection attempt is cancelled
1308 * when a connection is destroyed */
1309 bonjour_xmpp_close_conversation(bb->conversation);
1310 bb->conversation = NULL;
1311 }
1312
1313 g_clear_object(&contact);
1314 }
1315 }
1316
1317 g_slist_free_full(jdata->pending_conversations, (GDestroyNotify)bonjour_xmpp_close_conversation);
1318 }
1319
1320 XepIq *
1321 xep_iq_new(void *data, XepIqType type, const char *to, const char *from, const char *id)
1322 {
1323 PurpleXmlNode *iq_node = NULL;
1324 XepIq *iq = NULL;
1325
1326 g_return_val_if_fail(data != NULL, NULL);
1327 g_return_val_if_fail(to != NULL, NULL);
1328 g_return_val_if_fail(id != NULL, NULL);
1329
1330 iq_node = purple_xmlnode_new("iq");
1331
1332 purple_xmlnode_set_attrib(iq_node, "to", to);
1333 purple_xmlnode_set_attrib(iq_node, "from", from);
1334 purple_xmlnode_set_attrib(iq_node, "id", id);
1335 switch (type) {
1336 case XEP_IQ_SET:
1337 purple_xmlnode_set_attrib(iq_node, "type", "set");
1338 break;
1339 case XEP_IQ_GET:
1340 purple_xmlnode_set_attrib(iq_node, "type", "get");
1341 break;
1342 case XEP_IQ_RESULT:
1343 purple_xmlnode_set_attrib(iq_node, "type", "result");
1344 break;
1345 case XEP_IQ_ERROR:
1346 purple_xmlnode_set_attrib(iq_node, "type", "error");
1347 break;
1348 case XEP_IQ_NONE:
1349 default:
1350 purple_xmlnode_set_attrib(iq_node, "type", "none");
1351 break;
1352 }
1353
1354 iq = g_new0(XepIq, 1);
1355 iq->node = iq_node;
1356 iq->type = type;
1357 iq->data = ((BonjourData*)data)->xmpp_data;
1358 iq->to = (char*)to;
1359
1360 return iq;
1361 }
1362
1363 static void
1364 xep_iq_parse(PurpleXmlNode *packet, PurpleContact *contact) {
1365 PurpleAccount *account;
1366 PurpleConnection *gc;
1367
1368 account = purple_contact_get_account(contact);
1369 gc = purple_account_get_connection(account);
1370
1371 if(purple_xmlnode_get_child(packet, "si") != NULL ||
1372 purple_xmlnode_get_child(packet, "error") != NULL)
1373 {
1374 xep_si_parse(gc, packet, contact);
1375 } else {
1376 xep_bytestreams_parse(gc, packet, contact);
1377 }
1378 }
1379
1380 int
1381 xep_iq_send_and_free(XepIq *iq)
1382 {
1383 int ret = -1;
1384 PurpleContact *contact = NULL;
1385
1386 /* start the talk, reuse the message socket */
1387 contact = _find_or_start_conversation((BonjourXMPP*) iq->data, iq->to);
1388 /* Send the message */
1389 if(PURPLE_IS_CONTACT(contact)) {
1390 /* Convert xml node into stream */
1391 gchar *msg = purple_xmlnode_to_str(iq->node, NULL);
1392 ret = _send_data(contact, msg);
1393 g_free(msg);
1394 g_clear_object(&contact);
1395 }
1396
1397 purple_xmlnode_free(iq->node);
1398 iq->node = NULL;
1399 g_free(iq);
1400
1401 return (ret >= 0) ? 0 : -1;
1402 }
1403
1404 void
1405 append_iface_if_linklocal(char *ip, guint32 interface_param) {
1406 GInetAddress *addr;
1407 int len_remain = INET6_ADDRSTRLEN - strlen(ip);
1408
1409 if (len_remain <= 1)
1410 return;
1411
1412 addr = g_inet_address_new_from_string(ip);
1413 if (addr == NULL ||
1414 g_inet_address_get_family(addr) != G_SOCKET_FAMILY_IPV6 ||
1415 !g_inet_address_get_is_link_local(addr))
1416 {
1417 g_clear_object(&addr);
1418 return;
1419 }
1420 g_clear_object(&addr);
1421
1422 g_snprintf(ip + strlen(ip), len_remain, "%%%d", interface_param);
1423 }

mercurial