Sat, 29 May 2021 03:19:13 -0500
Convert the Pidgin plugins to use GPLUGIN_NATIVE_PLUGIN_DECLARE
Testing Done:
Compiled and verified that the plugins that build by default showed up in the plugins window.
Reviewed at https://reviews.imfreedom.org/r/680/
/* * Purple - XMPP Service Disco Browser * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA * */ /* TODO list (a little bit of a brain dump): * Support more actions than "register" and "add" based on context. - Subscribe to pubsub nodes (just...because?) - Execute ad-hoc commands - Change 'Register' to 'Unregister' if we're registered? - Administer MUCs * Enumerate pubsub node contents. - PEP too? (useful development tool at times) * See if we can better handle the ad-hoc commands that ejabberd returns when disco'ing a server as an administrator: from disco#items: <item jid='darkrain42.org' node='announce' name='Announcements'/> disco#info: <iq from='darkrain42.org' type='result'> <query xmlns='http://jabber.org/protocol/disco#info' node='announce'/> </iq> * For services that are a JID w/o a node, handle fetching ad-hoc commands? */ #include <glib/gi18n-lib.h> #include <purple.h> #include <pidgin.h> #include "xmppdisco.h" #include "gtkdisco.h" /* Variables */ PurplePlugin *my_plugin = NULL; static GHashTable *iq_callbacks = NULL; static gboolean iq_listening = FALSE; typedef void (*XmppIqCallback)(PurpleConnection *pc, const char *type, const char *id, const char *from, PurpleXmlNode *iq, gpointer data); struct item_data { PidginDiscoList *list; XmppDiscoService *parent; char *name; char *node; /* disco#info replies don't always include the node */ }; struct xmpp_iq_cb_data { /* * Every IQ callback in this plugin uses the same structure for the * callback data. It's a hack (it wouldn't scale), but it's used so that * it's easy to clean up all the callbacks when the account disconnects * (see remove_iq_callbacks_by_pc below). */ struct item_data *context; PurpleConnection *pc; XmppIqCallback cb; }; static char* generate_next_id() { static guint32 index = 0; if (index == 0) { do { index = g_random_int(); } while (index == 0); } return g_strdup_printf("purpledisco%x", index++); } static gboolean remove_iq_callbacks_by_pc(gpointer key, gpointer value, gpointer user_data) { struct xmpp_iq_cb_data *cb_data = value; if (cb_data && cb_data->pc == user_data) { struct item_data *item_data = cb_data->context; if (item_data) { pidgin_disco_list_unref(item_data->list); g_free(item_data->name); g_free(item_data->node); g_free(item_data); } return TRUE; } else return FALSE; } static gboolean xmpp_iq_received(PurpleConnection *pc, const char *type, const char *id, const char *from, PurpleXmlNode *iq) { struct xmpp_iq_cb_data *cb_data; cb_data = g_hash_table_lookup(iq_callbacks, id); if (!cb_data) return FALSE; cb_data->cb(cb_data->pc, type, id, from, iq, cb_data->context); g_hash_table_remove(iq_callbacks, id); if (g_hash_table_size(iq_callbacks) == 0) { PurpleProtocol *protocol = purple_connection_get_protocol(pc); iq_listening = FALSE; purple_signal_disconnect(protocol, "jabber-receiving-iq", my_plugin, PURPLE_CALLBACK(xmpp_iq_received)); } /* Om nom nom nom */ return TRUE; } static void xmpp_iq_register_callback(PurpleConnection *pc, gchar *id, gpointer data, XmppIqCallback cb) { struct xmpp_iq_cb_data *cbdata = g_new0(struct xmpp_iq_cb_data, 1); cbdata->context = data; cbdata->cb = cb; cbdata->pc = pc; g_hash_table_insert(iq_callbacks, id, cbdata); if (!iq_listening) { PurpleProtocol *protocol = NULL; PurpleProtocolManager *manager = purple_protocol_manager_get_default(); protocol = purple_protocol_manager_find(manager, XMPP_PROTOCOL_ID); iq_listening = TRUE; purple_signal_connect(protocol, "jabber-receiving-iq", my_plugin, PURPLE_CALLBACK(xmpp_iq_received), NULL); } } static void xmpp_disco_info_do(PurpleConnection *pc, gpointer cbdata, const char *jid, const char *node, XmppIqCallback cb) { PurpleXmlNode *iq, *query; char *id = generate_next_id(); iq = purple_xmlnode_new("iq"); purple_xmlnode_set_attrib(iq, "type", "get"); purple_xmlnode_set_attrib(iq, "to", jid); purple_xmlnode_set_attrib(iq, "id", id); query = purple_xmlnode_new_child(iq, "query"); purple_xmlnode_set_namespace(query, NS_DISCO_INFO); if (node) purple_xmlnode_set_attrib(query, "node", node); /* Steals id */ xmpp_iq_register_callback(pc, id, cbdata, cb); purple_signal_emit(purple_connection_get_protocol(pc), "jabber-sending-xmlnode", pc, &iq); if (iq != NULL) purple_xmlnode_free(iq); } static void xmpp_disco_items_do(PurpleConnection *pc, gpointer cbdata, const char *jid, const char *node, XmppIqCallback cb) { PurpleXmlNode *iq, *query; char *id = generate_next_id(); iq = purple_xmlnode_new("iq"); purple_xmlnode_set_attrib(iq, "type", "get"); purple_xmlnode_set_attrib(iq, "to", jid); purple_xmlnode_set_attrib(iq, "id", id); query = purple_xmlnode_new_child(iq, "query"); purple_xmlnode_set_namespace(query, NS_DISCO_ITEMS); if (node) purple_xmlnode_set_attrib(query, "node", node); /* Steals id */ xmpp_iq_register_callback(pc, id, cbdata, cb); purple_signal_emit(purple_connection_get_protocol(pc), "jabber-sending-xmlnode", pc, &iq); if (iq != NULL) purple_xmlnode_free(iq); } static XmppDiscoServiceType disco_service_type_from_identity(PurpleXmlNode *identity) { const char *category, *type; if (!identity) return XMPP_DISCO_SERVICE_TYPE_OTHER; category = purple_xmlnode_get_attrib(identity, "category"); type = purple_xmlnode_get_attrib(identity, "type"); if (!category) return XMPP_DISCO_SERVICE_TYPE_OTHER; if (purple_strequal(category, "conference")) return XMPP_DISCO_SERVICE_TYPE_CHAT; else if (purple_strequal(category, "directory")) return XMPP_DISCO_SERVICE_TYPE_DIRECTORY; else if (purple_strequal(category, "gateway")) return XMPP_DISCO_SERVICE_TYPE_GATEWAY; else if (purple_strequal(category, "pubsub")) { if (!type || purple_strequal(type, "collection")) return XMPP_DISCO_SERVICE_TYPE_PUBSUB_COLLECTION; else if (purple_strequal(type, "leaf")) return XMPP_DISCO_SERVICE_TYPE_PUBSUB_LEAF; else if (purple_strequal(type, "service")) return XMPP_DISCO_SERVICE_TYPE_OTHER; else { purple_debug_warning("xmppdisco", "Unknown pubsub type '%s'\n", type); return XMPP_DISCO_SERVICE_TYPE_OTHER; } } return XMPP_DISCO_SERVICE_TYPE_OTHER; } static const struct { const char *from; const char *to; } disco_type_mappings[] = { { "gadu-gadu", "gadu-gadu" }, /* the protocol is gg, but list_icon returns "gadu-gadu" */ { "sametime", "meanwhile" }, { "xmpp", "jabber" }, /* jabber (mentioned in case the protocol is renamed so this line will match) */ { NULL, NULL } }; static const gchar * disco_type_from_string(const gchar *str) { int i = 0; g_return_val_if_fail(str != NULL, ""); for ( ; disco_type_mappings[i].from; ++i) { if (!g_ascii_strcasecmp(str, disco_type_mappings[i].from)) return disco_type_mappings[i].to; } /* fallback to the string itself */ return str; } static void got_info_cb(PurpleConnection *pc, const char *type, const char *id, const char *from, PurpleXmlNode *iq, gpointer data) { struct item_data *item_data = data; PidginDiscoList *list = item_data->list; PurpleXmlNode *query; --list->fetch_count; if (!list->in_progress) goto out; if (purple_strequal(type, "result") && (query = purple_xmlnode_get_child(iq, "query"))) { PurpleXmlNode *identity = purple_xmlnode_get_child(query, "identity"); XmppDiscoService *service; PurpleXmlNode *feature; service = g_new0(XmppDiscoService, 1); service->list = item_data->list; purple_debug_info("xmppdisco", "parent for %s is %p\n", from, item_data->parent); service->parent = item_data->parent; service->flags = 0; service->type = disco_service_type_from_identity(identity); if (item_data->node) { if (item_data->name) { service->name = item_data->name; item_data->name = NULL; } else service->name = g_strdup(item_data->node); service->node = item_data->node; item_data->node = NULL; if (service->type == XMPP_DISCO_SERVICE_TYPE_PUBSUB_COLLECTION) service->flags |= XMPP_DISCO_BROWSE; } else service->name = g_strdup(from); if (!service->node) /* Only support adding JIDs, not JID+node combos */ service->flags |= XMPP_DISCO_ADD; if (item_data->name) { service->description = item_data->name; item_data->name = NULL; } else if (identity) service->description = g_strdup(purple_xmlnode_get_attrib(identity, "name")); /* TODO: Overlap with service->name a bit */ service->jid = g_strdup(from); for (feature = purple_xmlnode_get_child(query, "feature"); feature; feature = purple_xmlnode_get_next_twin(feature)) { const char *var; if (!(var = purple_xmlnode_get_attrib(feature, "var"))) continue; if (purple_strequal(var, NS_REGISTER)) service->flags |= XMPP_DISCO_REGISTER; else if (purple_strequal(var, NS_DISCO_ITEMS)) service->flags |= XMPP_DISCO_BROWSE; else if (purple_strequal(var, NS_MUC)) { service->flags |= XMPP_DISCO_BROWSE; service->type = XMPP_DISCO_SERVICE_TYPE_CHAT; } } if (service->type == XMPP_DISCO_SERVICE_TYPE_GATEWAY) service->gateway_type = g_strdup(disco_type_from_string( purple_xmlnode_get_attrib(identity, "type"))); pidgin_disco_add_service(list, service, service->parent); } out: if (list->fetch_count == 0) pidgin_disco_list_set_in_progress(list, FALSE); g_free(item_data->name); g_free(item_data->node); g_free(item_data); pidgin_disco_list_unref(list); } static void got_items_cb(PurpleConnection *pc, const char *type, const char *id, const char *from, PurpleXmlNode *iq, gpointer data) { struct item_data *item_data = data; PidginDiscoList *list = item_data->list; PurpleXmlNode *query; gboolean has_items = FALSE; --list->fetch_count; if (!list->in_progress) goto out; if (purple_strequal(type, "result") && (query = purple_xmlnode_get_child(iq, "query"))) { PurpleXmlNode *item; for (item = purple_xmlnode_get_child(query, "item"); item; item = purple_xmlnode_get_next_twin(item)) { const char *jid = purple_xmlnode_get_attrib(item, "jid"); const char *name = purple_xmlnode_get_attrib(item, "name"); const char *node = purple_xmlnode_get_attrib(item, "node"); has_items = TRUE; if (item_data->parent->type == XMPP_DISCO_SERVICE_TYPE_CHAT) { /* This is a hacky first-order approximation. Any MUC * component that has a >1 level hierarchy (a Yahoo MUC * transport component probably does) will violate this. * * On the other hand, this is better than querying all the * chats at conference.jabber.org to enumerate them. */ XmppDiscoService *service = g_new0(XmppDiscoService, 1); service->list = item_data->list; service->parent = item_data->parent; service->flags = XMPP_DISCO_ADD; service->type = XMPP_DISCO_SERVICE_TYPE_CHAT; service->name = g_strdup(name); service->jid = g_strdup(jid); service->node = g_strdup(node); pidgin_disco_add_service(list, service, item_data->parent); } else { struct item_data *item_data2 = g_new0(struct item_data, 1); item_data2->list = item_data->list; item_data2->parent = item_data->parent; item_data2->name = g_strdup(name); item_data2->node = g_strdup(node); ++list->fetch_count; pidgin_disco_list_ref(list); xmpp_disco_info_do(pc, item_data2, jid, node, got_info_cb); } } } if (!has_items) pidgin_disco_add_service(list, NULL, item_data->parent); out: if (list->fetch_count == 0) pidgin_disco_list_set_in_progress(list, FALSE); g_free(item_data); pidgin_disco_list_unref(list); } static void server_items_cb(PurpleConnection *pc, const char *type, const char *id, const char *from, PurpleXmlNode *iq, gpointer data) { struct item_data *cb_data = data; PidginDiscoList *list = cb_data->list; PurpleXmlNode *query; g_free(cb_data); --list->fetch_count; if (purple_strequal(type, "result") && (query = purple_xmlnode_get_child(iq, "query"))) { PurpleXmlNode *item; for (item = purple_xmlnode_get_child(query, "item"); item; item = purple_xmlnode_get_next_twin(item)) { const char *jid = purple_xmlnode_get_attrib(item, "jid"); const char *name = purple_xmlnode_get_attrib(item, "name"); const char *node = purple_xmlnode_get_attrib(item, "node"); struct item_data *item_data; if (!jid) continue; item_data = g_new0(struct item_data, 1); item_data->list = list; item_data->name = g_strdup(name); item_data->node = g_strdup(node); ++list->fetch_count; pidgin_disco_list_ref(list); xmpp_disco_info_do(pc, item_data, jid, node, got_info_cb); } } if (list->fetch_count == 0) pidgin_disco_list_set_in_progress(list, FALSE); pidgin_disco_list_unref(list); } static void server_info_cb(PurpleConnection *pc, const char *type, const char *id, const char *from, PurpleXmlNode *iq, gpointer data) { struct item_data *cb_data = data; PidginDiscoList *list = cb_data->list; PurpleXmlNode *query; PurpleXmlNode *error; gboolean items = FALSE; --list->fetch_count; if (purple_strequal(type, "result") && (query = purple_xmlnode_get_child(iq, "query"))) { PurpleXmlNode *feature; for (feature = purple_xmlnode_get_child(query, "feature"); feature; feature = purple_xmlnode_get_next_twin(feature)) { const char *var = purple_xmlnode_get_attrib(feature, "var"); if (purple_strequal(var, NS_DISCO_ITEMS)) { items = TRUE; break; } } if (items) { xmpp_disco_items_do(pc, cb_data, from, NULL /* node */, server_items_cb); ++list->fetch_count; pidgin_disco_list_ref(list); } else { pidgin_disco_list_set_in_progress(list, FALSE); g_free(cb_data); } } else { error = purple_xmlnode_get_child(iq, "error"); if (purple_xmlnode_get_child(error, "remote-server-not-found") || purple_xmlnode_get_child(error, "jid-malformed")) { purple_notify_error(my_plugin, _("Error"), _("Server does not exist"), NULL, NULL); } else { purple_notify_error(my_plugin, _("Error"), _("Server does not support service discovery"), NULL, NULL); } pidgin_disco_list_set_in_progress(list, FALSE); g_free(cb_data); } pidgin_disco_list_unref(list); } void xmpp_disco_start(PidginDiscoList *list) { struct item_data *cb_data; g_return_if_fail(list != NULL); ++list->fetch_count; pidgin_disco_list_ref(list); cb_data = g_new0(struct item_data, 1); cb_data->list = list; xmpp_disco_info_do(list->pc, cb_data, list->server, NULL, server_info_cb); } void xmpp_disco_service_expand(XmppDiscoService *service) { struct item_data *item_data; g_return_if_fail(service != NULL); if (service->expanded) return; item_data = g_new0(struct item_data, 1); item_data->list = service->list; item_data->parent = service; ++service->list->fetch_count; pidgin_disco_list_ref(service->list); pidgin_disco_list_set_in_progress(service->list, TRUE); xmpp_disco_items_do(service->list->pc, item_data, service->jid, service->node, got_items_cb); service->expanded = TRUE; } void xmpp_disco_service_register(XmppDiscoService *service) { PurpleXmlNode *iq, *query; char *id = generate_next_id(); iq = purple_xmlnode_new("iq"); purple_xmlnode_set_attrib(iq, "type", "get"); purple_xmlnode_set_attrib(iq, "to", service->jid); purple_xmlnode_set_attrib(iq, "id", id); query = purple_xmlnode_new_child(iq, "query"); purple_xmlnode_set_namespace(query, NS_REGISTER); purple_signal_emit(purple_connection_get_protocol(service->list->pc), "jabber-sending-xmlnode", service->list->pc, &iq); if (iq != NULL) purple_xmlnode_free(iq); g_free(id); } static void create_dialog(PurplePluginAction *action) { pidgin_disco_dialog_new(); } static GList * actions(PurplePlugin *plugin) { GList *l = NULL; PurplePluginAction *action = NULL; action = purple_plugin_action_new(_("XMPP Service Discovery"), create_dialog); l = g_list_prepend(l, action); return l; } static void signed_off_cb(PurpleConnection *pc, gpointer unused) { /* Deal with any dialogs */ pidgin_disco_signed_off_cb(pc); /* Remove all the IQ callbacks for this connection */ g_hash_table_foreach_remove(iq_callbacks, remove_iq_callbacks_by_pc, pc); } static GPluginPluginInfo * xmpp_disco_query(GError **error) { const gchar * const authors[] = { "Paul Aurich <paul@darkrain42.org>", NULL }; return pidgin_plugin_info_new( "id", PLUGIN_ID, "name", N_("XMPP Service Discovery"), "version", DISPLAY_VERSION, "category", N_("Protocol utility"), "summary", N_("Allows browsing and registering services."), "description", N_("This plugin is useful for registering with legacy " "transports or other XMPP services."), "authors", authors, "website", PURPLE_WEBSITE, "abi-version", PURPLE_ABI_VERSION, "actions-cb", actions, NULL ); } static gboolean xmpp_disco_load(GPluginPlugin *plugin, GError **error) { PurpleProtocol *xmpp_protocol; PurpleProtocolManager *manager; my_plugin = plugin; manager = purple_protocol_manager_get_default(); xmpp_protocol = purple_protocol_manager_find(manager, XMPP_PROTOCOL_ID); if (NULL == xmpp_protocol) { g_set_error_literal(error, PLUGIN_DOMAIN, 0, _("XMPP protocol is not loaded.")); return FALSE; } pidgin_disco_dialog_register(plugin); purple_signal_connect(purple_connections_get_handle(), "signing-off", plugin, PURPLE_CALLBACK(signed_off_cb), NULL); iq_callbacks = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free); return TRUE; } static gboolean xmpp_disco_unload(GPluginPlugin *plugin, GError **error) { g_hash_table_destroy(iq_callbacks); iq_callbacks = NULL; purple_signals_disconnect_by_handle(plugin); pidgin_disco_dialogs_destroy_all(); return TRUE; } GPLUGIN_NATIVE_PLUGIN_DECLARE(xmpp_disco)