comparison libpurple/protocols/jabber/jabber.c @ 25772:c4eb9f10ecb5

propagate from branch 'im.pidgin.pidgin' (head 11ea1799b1892059650fa7dcf6e848a020045e50) to branch 'org.darkrain42.pidgin.xmpp' (head 5b18884cb593c048171bfce406c09d70ddf03f02)
author Paul Aurich <paul@darkrain42.org>
date Sat, 29 Nov 2008 18:41:39 +0000
parents 5cd0188892f6 e1f363f8fd6b
children 514051f3b6cf
comparison
equal deleted inserted replaced
24508:5cd0188892f6 25772:c4eb9f10ecb5
37 #include "server.h" 37 #include "server.h"
38 #include "util.h" 38 #include "util.h"
39 #include "version.h" 39 #include "version.h"
40 #include "xmlnode.h" 40 #include "xmlnode.h"
41 41
42 #include "caps.h"
42 #include "auth.h" 43 #include "auth.h"
43 #include "buddy.h" 44 #include "buddy.h"
44 #include "chat.h" 45 #include "chat.h"
45 #include "data.h" 46 #include "data.h"
46 #include "disco.h" 47 #include "disco.h"
60 61
61 62
62 #define JABBER_CONNECT_STEPS (js->gsc ? 9 : 5) 63 #define JABBER_CONNECT_STEPS (js->gsc ? 9 : 5)
63 64
64 static PurplePlugin *my_protocol = NULL; 65 static PurplePlugin *my_protocol = NULL;
66
65 GList *jabber_features = NULL; 67 GList *jabber_features = NULL;
68 GList *jabber_identities = NULL;
66 69
67 static void jabber_unregister_account_cb(JabberStream *js); 70 static void jabber_unregister_account_cb(JabberStream *js);
68 static void try_srv_connect(JabberStream *js); 71 static void try_srv_connect(JabberStream *js);
69 72
70 static void jabber_stream_init(JabberStream *js) 73 static void jabber_stream_init(JabberStream *js)
167 hostname[sizeof(hostname) - 1] = '\0'; 170 hostname[sizeof(hostname) - 1] = '\0';
168 171
169 return purple_strreplace(input, "__HOSTNAME__", hostname); 172 return purple_strreplace(input, "__HOSTNAME__", hostname);
170 } 173 }
171 174
172 static void jabber_stream_features_parse(JabberStream *js, xmlnode *packet) 175 void jabber_stream_features_parse(JabberStream *js, xmlnode *packet)
173 { 176 {
174 if(xmlnode_get_child(packet, "starttls")) { 177 if(xmlnode_get_child(packet, "starttls")) {
175 if(jabber_process_starttls(js, packet)) 178 if(jabber_process_starttls(js, packet))
176 179
177 return; 180 return;
383 } 386 }
384 return; 387 return;
385 } 388 }
386 #endif 389 #endif
387 390
388 do_jabber_send_raw(js, data, len); 391 if (len == -1)
392 len = strlen(data);
393
394 if (js->use_bosh) {
395 xmlnode *xnode = xmlnode_from_str(data, len);
396 if (xnode) jabber_bosh_connection_send(&(js->bosh), xnode);
397 else {
398 purple_connection_error_reason(js->gc, PURPLE_CONNECTION_ERROR_OTHER_ERROR,
399 _("Someone tried to send non-XML in a Jabber world."));
400 }
401 } else {
402 do_jabber_send_raw(js, data, len);
403 }
389 } 404 }
390 405
391 int jabber_prpl_send_raw(PurpleConnection *gc, const char *buf, int len) 406 int jabber_prpl_send_raw(PurpleConnection *gc, const char *buf, int len)
392 { 407 {
393 JabberStream *js = (JabberStream*)gc->proto_data; 408 JabberStream *js = (JabberStream*)gc->proto_data;
546 561
547 /* Tell the app that we're doing encryption */ 562 /* Tell the app that we're doing encryption */
548 jabber_stream_set_state(js, JABBER_STREAM_INITIALIZING_ENCRYPTION); 563 jabber_stream_set_state(js, JABBER_STREAM_INITIALIZING_ENCRYPTION);
549 } 564 }
550 565
566 static void
567 jabber_bosh_login_callback(PurpleBOSHConnection *conn)
568 {
569 purple_debug_info("jabber","YAY...BOSH connection established.\n");
570 }
571
572 static void
573 txt_resolved_cb(PurpleTxtResponse *resp, int results, gpointer data)
574 {
575 PurpleConnection *gc = data;
576 JabberStream *js = gc->proto_data;
577 int n;
578
579 if (results == 0) {
580 gchar *tmp;
581 tmp = g_strdup_printf(_("Could not find alternative XMPP connection methods after failing to connect directly.\n"));
582 purple_connection_error_reason (gc,
583 PURPLE_CONNECTION_ERROR_NETWORK_ERROR, tmp);
584 g_free(tmp);
585 return;
586 }
587
588 for (n = 0; n < results; n++) {
589 gchar **token;
590 token = g_strsplit(resp[n].content, "=", 2);
591 if (!strcmp(token[0], "_xmpp-client-xbosh")) {
592 purple_debug_info("jabber","Found alternative connection method using %s at %s.\n", token[0], token[1]);
593 jabber_bosh_connection_init(&(js->bosh), gc->account, js, token[1]);
594 g_strfreev(token);
595 break;
596 }
597 g_strfreev(token);
598 }
599 if (js->bosh.host) {
600 js->bosh.userdata = gc;
601 jabber_bosh_connection_connect(&(js->bosh));
602 } else {
603 purple_debug_info("jabber","Didn't find an alternative connection method.\n");
604 }
605 }
551 606
552 static void 607 static void
553 jabber_login_callback(gpointer data, gint source, const gchar *error) 608 jabber_login_callback(gpointer data, gint source, const gchar *error)
554 { 609 {
555 PurpleConnection *gc = data; 610 PurpleConnection *gc = data;
558 if (source < 0) { 613 if (source < 0) {
559 if (js->srv_rec != NULL) { 614 if (js->srv_rec != NULL) {
560 purple_debug_error("jabber", "Unable to connect to server: %s. Trying next SRV record.\n", error); 615 purple_debug_error("jabber", "Unable to connect to server: %s. Trying next SRV record.\n", error);
561 try_srv_connect(js); 616 try_srv_connect(js);
562 } else { 617 } else {
563 gchar *tmp; 618 purple_debug_info("jabber","Couldn't connect directly to %s. Trying to find alternative connection methods, like BOSH.\n", js->user->domain);
564 tmp = g_strdup_printf(_("Could not establish a connection with the server:\n%s"), 619 purple_txt_resolve("_xmppconnect", js->user->domain, txt_resolved_cb, gc);
565 error);
566 purple_connection_error_reason(gc,
567 PURPLE_CONNECTION_ERROR_NETWORK_ERROR, tmp);
568 g_free(tmp);
569 } 620 }
570 return; 621 return;
571 } 622 }
572 623
573 g_free(js->srv_rec); 624 g_free(js->srv_rec);
729 } 780 }
730 781
731 /* no old-ssl, so if they've specified a connect server, we'll use that, otherwise we'll 782 /* no old-ssl, so if they've specified a connect server, we'll use that, otherwise we'll
732 * invoke the magic of SRV lookups, to figure out host and port */ 783 * invoke the magic of SRV lookups, to figure out host and port */
733 if(!js->gsc) { 784 if(!js->gsc) {
734 if(connect_server[0]) { 785 if(connect_server[0]) {
735 jabber_login_connect(js, js->user->domain, connect_server, purple_account_get_int(account, "port", 5222), TRUE); 786 jabber_login_connect(js, js->user->domain, connect_server, purple_account_get_int(account, "port", 5222), TRUE);
736 } else { 787 } else {
737 js->srv_query_data = purple_srv_resolve("xmpp-client", 788 js->srv_query_data = purple_srv_resolve("xmpp-client",
738 "tcp", js->user->domain, srv_resolved_cb, js); 789 "tcp", js->user->domain, srv_resolved_cb, js);
739 } 790 }
1339 1390
1340 g_free(js->stream_id); 1391 g_free(js->stream_id);
1341 if(js->user) 1392 if(js->user)
1342 jabber_id_free(js->user); 1393 jabber_id_free(js->user);
1343 g_free(js->avatar_hash); 1394 g_free(js->avatar_hash);
1395 g_free(js->caps_hash);
1344 1396
1345 purple_circ_buffer_destroy(js->write_buffer); 1397 purple_circ_buffer_destroy(js->write_buffer);
1346 if(js->writeh) 1398 if(js->writeh)
1347 purple_input_remove(js->writeh); 1399 purple_input_remove(js->writeh);
1348 #ifdef HAVE_CYRUS_SASL 1400 #ifdef HAVE_CYRUS_SASL
1440 JabberStream *js = gc->proto_data; 1492 JabberStream *js = gc->proto_data;
1441 1493
1442 js->idle = idle ? time(NULL) - idle : idle; 1494 js->idle = idle ? time(NULL) - idle : idle;
1443 } 1495 }
1444 1496
1445 void jabber_add_feature(const char *shortname, const char *namespace, JabberFeatureEnabled cb) { 1497 void jabber_add_feature(const char *namespace, JabberFeatureEnabled cb) {
1446 JabberFeature *feat; 1498 JabberFeature *feat;
1447 1499
1448 g_return_if_fail(shortname != NULL);
1449 g_return_if_fail(namespace != NULL); 1500 g_return_if_fail(namespace != NULL);
1450 1501
1451 feat = g_new0(JabberFeature,1); 1502 feat = g_new0(JabberFeature,1);
1452 feat->shortname = g_strdup(shortname);
1453 feat->namespace = g_strdup(namespace); 1503 feat->namespace = g_strdup(namespace);
1454 feat->is_enabled = cb; 1504 feat->is_enabled = cb;
1455 1505
1456 /* try to remove just in case it already exists in the list */ 1506 /* try to remove just in case it already exists in the list */
1457 jabber_remove_feature(shortname); 1507 jabber_remove_feature(namespace);
1458 1508
1459 jabber_features = g_list_append(jabber_features, feat); 1509 jabber_features = g_list_append(jabber_features, feat);
1460 } 1510 }
1461 1511
1462 void jabber_remove_feature(const char *shortname) { 1512 void jabber_remove_feature(const char *namespace) {
1463 GList *feature; 1513 GList *feature;
1464 for(feature = jabber_features; feature; feature = feature->next) { 1514 for(feature = jabber_features; feature; feature = feature->next) {
1465 JabberFeature *feat = (JabberFeature*)feature->data; 1515 JabberFeature *feat = (JabberFeature*)feature->data;
1466 if(!strcmp(feat->shortname, shortname)) { 1516 if(!strcmp(feat->namespace, namespace)) {
1467 g_free(feat->shortname);
1468 g_free(feat->namespace); 1517 g_free(feat->namespace);
1469
1470 g_free(feature->data); 1518 g_free(feature->data);
1471 jabber_features = g_list_delete_link(jabber_features, feature); 1519 jabber_features = g_list_delete_link(jabber_features, feature);
1472 break; 1520 break;
1473 } 1521 }
1522 }
1523 }
1524
1525 static void jabber_features_destroy(void)
1526 {
1527 while (jabber_features) {
1528 JabberFeature *feature = jabber_features->data;
1529 g_free(feature->namespace);
1530 g_free(feature);
1531 jabber_features = g_list_remove_link(jabber_features, jabber_features);
1532 }
1533 }
1534
1535 void jabber_add_identity(const gchar *category, const gchar *type, const gchar *lang, const gchar *name) {
1536 GList *identity;
1537 JabberIdentity *ident;
1538 /* both required according to XEP-0030 */
1539 g_return_if_fail(category != NULL);
1540 g_return_if_fail(type != NULL);
1541
1542 for(identity = jabber_identities; identity; identity = identity->next) {
1543 JabberIdentity *ident = (JabberIdentity*)identity->data;
1544 if (!strcmp(ident->category, category) &&
1545 !strcmp(ident->type, type) &&
1546 ((!ident->lang && !lang) || (ident->lang && lang && !strcmp(ident->lang, lang)))) {
1547 return;
1548 }
1549 }
1550
1551 ident = g_new0(JabberIdentity, 1);
1552 ident->category = g_strdup(category);
1553 ident->type = g_strdup(type);
1554 ident->lang = g_strdup(lang);
1555 ident->name = g_strdup(name);
1556 jabber_identities = g_list_append(jabber_identities, ident);
1557 }
1558
1559 static void jabber_identities_destroy(void)
1560 {
1561 while (jabber_identities) {
1562 JabberIdentity *id = jabber_identities->data;
1563 g_free(id->category);
1564 g_free(id->type);
1565 g_free(id->lang);
1566 g_free(id->name);
1567 g_free(id);
1568 jabber_identities = g_list_remove_link(jabber_identities, jabber_identities);
1474 } 1569 }
1475 } 1570 }
1476 1571
1477 const char *jabber_list_icon(PurpleAccount *a, PurpleBuddy *b) 1572 const char *jabber_list_icon(PurpleAccount *a, PurpleBuddy *b)
1478 { 1573 {
2297 2392
2298 static gboolean _jabber_send_buzz(JabberStream *js, const char *username, char **error) { 2393 static gboolean _jabber_send_buzz(JabberStream *js, const char *username, char **error) {
2299 2394
2300 JabberBuddy *jb; 2395 JabberBuddy *jb;
2301 JabberBuddyResource *jbr; 2396 JabberBuddyResource *jbr;
2302 GList *iter;
2303 2397
2304 if(!username) 2398 if(!username)
2305 return FALSE; 2399 return FALSE;
2306 2400
2307 jb = jabber_buddy_find(js, username, FALSE); 2401 jb = jabber_buddy_find(js, username, FALSE);
2314 if(!jbr) { 2408 if(!jbr) {
2315 *error = g_strdup_printf(_("Unable to buzz, because user %s might be offline."), username); 2409 *error = g_strdup_printf(_("Unable to buzz, because user %s might be offline."), username);
2316 return FALSE; 2410 return FALSE;
2317 } 2411 }
2318 2412
2413 /* Is this message sufficiently useful to not just fold it in with the tail error condition below? */
2319 if(!jbr->caps) { 2414 if(!jbr->caps) {
2320 *error = g_strdup_printf(_("Unable to buzz, because there is nothing known about user %s."), username); 2415 *error = g_strdup_printf(_("Unable to buzz, because there is nothing known about user %s."), username);
2321 return FALSE; 2416 return FALSE;
2322 } 2417 }
2323 2418
2324 for(iter = jbr->caps->features; iter; iter = g_list_next(iter)) { 2419 if (jabber_resource_has_capability(jbr, "http://www.xmpp.org/extensions/xep-0224.html#ns")) {
2325 if(!strcmp(iter->data, "http://www.xmpp.org/extensions/xep-0224.html#ns")) { 2420 xmlnode *buzz, *msg = xmlnode_new("message");
2326 xmlnode *buzz, *msg = xmlnode_new("message"); 2421 gchar *to;
2327 gchar *to; 2422
2328 2423 to = g_strdup_printf("%s/%s", username, jbr->name);
2329 to = g_strdup_printf("%s/%s", username, jbr->name); 2424 xmlnode_set_attrib(msg, "to", to);
2330 xmlnode_set_attrib(msg, "to", to); 2425 g_free(to);
2331 g_free(to); 2426
2332 2427 /* avoid offline storage */
2333 /* avoid offline storage */ 2428 xmlnode_set_attrib(msg, "type", "headline");
2334 xmlnode_set_attrib(msg, "type", "headline"); 2429
2335 2430 buzz = xmlnode_new_child(msg, "attention");
2336 buzz = xmlnode_new_child(msg, "attention"); 2431 xmlnode_set_namespace(buzz, "http://www.xmpp.org/extensions/xep-0224.html#ns");
2337 xmlnode_set_namespace(buzz, "http://www.xmpp.org/extensions/xep-0224.html#ns"); 2432
2338 2433 jabber_send(js, msg);
2339 jabber_send(js, msg); 2434 xmlnode_free(msg);
2340 xmlnode_free(msg); 2435
2341 2436 return TRUE;
2342 return TRUE;
2343 }
2344 } 2437 }
2345 2438
2346 *error = g_strdup_printf(_("Unable to buzz, because the user %s does not support it."), username); 2439 *error = g_strdup_printf(_("Unable to buzz, because the user %s does not support it."), username);
2347 return FALSE; 2440 return FALSE;
2348 } 2441 }
2472 PURPLE_CMD_FLAG_IM | PURPLE_CMD_FLAG_PRPL_ONLY, 2565 PURPLE_CMD_FLAG_IM | PURPLE_CMD_FLAG_PRPL_ONLY,
2473 "prpl-jabber", jabber_cmd_buzz, 2566 "prpl-jabber", jabber_cmd_buzz,
2474 _("buzz: Buzz a user to get their attention"), NULL); 2567 _("buzz: Buzz a user to get their attention"), NULL);
2475 } 2568 }
2476 2569
2570 /* IPC functions */
2571
2572 /**
2573 * IPC function for determining if a contact supports a certain feature.
2574 *
2575 * @param account The PurpleAccount
2576 * @param jid The full JID of the contact.
2577 * @param feature The feature's namespace.
2578 *
2579 * @return TRUE if supports feature; else FALSE.
2580 */
2581 static gboolean
2582 jabber_ipc_contact_has_feature(PurpleAccount *account, const gchar *jid,
2583 const gchar *feature)
2584 {
2585 PurpleConnection *gc = purple_account_get_connection(account);
2586 JabberStream *js;
2587 JabberBuddy *jb;
2588 JabberBuddyResource *jbr;
2589 gchar *resource;
2590
2591 if (!purple_account_is_connected(account))
2592 return FALSE;
2593 js = gc->proto_data;
2594
2595 if (!(resource = jabber_get_resource(jid)) ||
2596 !(jb = jabber_buddy_find(js, jid, FALSE)) ||
2597 !(jbr = jabber_buddy_find_resource(jb, resource))) {
2598 g_free(resource);
2599 return FALSE;
2600 }
2601
2602 g_free(resource);
2603
2604 return jabber_resource_has_capability(jbr, feature);
2605 }
2606
2607 static void
2608 jabber_ipc_add_feature(const gchar *feature)
2609 {
2610 if (!feature)
2611 return;
2612 jabber_add_feature(feature, 0);
2613
2614 /* send presence with new caps info for all connected accounts */
2615 jabber_caps_broadcast_change();
2616 }
2617
2477 void 2618 void
2478 jabber_init_plugin(PurplePlugin *plugin) 2619 jabber_init_plugin(PurplePlugin *plugin)
2479 { 2620 {
2480 my_protocol = plugin; 2621 my_protocol = plugin;
2481 } 2622
2623 jabber_add_identity("client", "pc", NULL, PACKAGE);
2624
2625 /* initialize jabber_features list */
2626 jabber_add_feature("jabber:iq:last", 0);
2627 jabber_add_feature("jabber:iq:oob", 0);
2628 jabber_add_feature("jabber:iq:time", 0);
2629 jabber_add_feature("xmpp:urn:time", 0);
2630 jabber_add_feature("jabber:iq:version", 0);
2631 jabber_add_feature("jabber:x:conference", 0);
2632 jabber_add_feature("http://jabber.org/protocol/bytestreams", 0);
2633 jabber_add_feature("http://jabber.org/protocol/disco#info", 0);
2634 jabber_add_feature("http://jabber.org/protocol/disco#items", 0);
2635 #if 0
2636 jabber_add_feature("http://jabber.org/protocol/ibb", 0);
2637 #endif
2638 jabber_add_feature("http://jabber.org/protocol/muc", 0);
2639 jabber_add_feature("http://jabber.org/protocol/muc#user", 0);
2640 jabber_add_feature("http://jabber.org/protocol/si", 0);
2641 jabber_add_feature("http://jabber.org/protocol/si/profile/file-transfer", 0);
2642 jabber_add_feature("http://jabber.org/protocol/xhtml-im", 0);
2643 jabber_add_feature("urn:xmpp:ping", 0);
2644
2645 /* IPC functions */
2646 purple_plugin_ipc_register(plugin, "contact_has_feature", PURPLE_CALLBACK(jabber_ipc_contact_has_feature),
2647 purple_marshal_BOOLEAN__POINTER_POINTER_POINTER,
2648 purple_value_new(PURPLE_TYPE_BOOLEAN), 3,
2649 purple_value_new(PURPLE_TYPE_SUBTYPE, PURPLE_SUBTYPE_ACCOUNT),
2650 purple_value_new(PURPLE_TYPE_STRING),
2651 purple_value_new(PURPLE_TYPE_STRING));
2652 purple_plugin_ipc_register(plugin, "add_feature", PURPLE_CALLBACK(jabber_ipc_add_feature),
2653 purple_marshal_VOID__POINTER,
2654 NULL, 1,
2655 purple_value_new(PURPLE_TYPE_STRING));
2656 }
2657
2658 void
2659 jabber_uninit_plugin(void)
2660 {
2661 jabber_features_destroy();
2662 jabber_identities_destroy();
2663 }