| 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 } |
|