Wed, 10 Mar 2021 03:13:38 -0600
bonjour: Rewrite name lookup as a GTask.
And fix compile with `PurpleProtocol` refactor.
Testing Done:
Compile only.
Reviewed at https://reviews.imfreedom.org/r/567/
/* * purple - Bonjour Protocol Plugin * * Purple is the legal property of its developers, whose names are too numerous * to list here. Please refer to the COPYRIGHT file distributed with this * source distribution. * * 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 */ #include <glib/gi18n-lib.h> #include <glib.h> #ifndef _WIN32 #include <pwd.h> #else #define UNICODE #include <winsock2.h> #include <windows.h> #include <lm.h> #endif #include <purple.h> #include "bonjour.h" #include "mdns_common.h" #include "buddy.h" #include "bonjour_ft.h" #include "xmpp.h" static PurpleProtocol *my_protocol = NULL; static char *default_firstname; static char *default_lastname; const char * bonjour_get_jid(PurpleAccount *account) { PurpleConnection *conn = purple_account_get_connection(account); BonjourData *bd = purple_connection_get_protocol_data(conn); return bd->jid; } const gchar * bonjour_get_group_name(void) { return _("Bonjour"); } static void bonjour_removeallfromlocal(PurpleConnection *conn, PurpleGroup *bonjour_group) { PurpleAccount *account = purple_connection_get_account(conn); PurpleBlistNode *cnode, *cnodenext, *bnode, *bnodenext; PurpleBuddy *buddy; if (bonjour_group == NULL) return; /* Go through and remove all buddies that belong to this account */ for (cnode = purple_blist_node_get_first_child((PurpleBlistNode *) bonjour_group); cnode; cnode = cnodenext) { cnodenext = purple_blist_node_get_sibling_next(cnode); if (!PURPLE_IS_CONTACT(cnode)) continue; for (bnode = purple_blist_node_get_first_child(cnode); bnode; bnode = bnodenext) { bnodenext = purple_blist_node_get_sibling_next(bnode); if (!PURPLE_IS_BUDDY(bnode)) continue; buddy = (PurpleBuddy *) bnode; if (purple_buddy_get_account(buddy) != account) continue; purple_account_remove_buddy(account, buddy, NULL); purple_blist_remove_buddy(buddy); } } } static void bonjour_login(PurpleAccount *account) { PurpleConnection *gc = purple_account_get_connection(account); BonjourData *bd; PurpleStatus *status; PurplePresence *presence; if (!mdns_available()) { purple_connection_error(gc, PURPLE_CONNECTION_ERROR_OTHER_ERROR, _("Unable to find Apple's \"Bonjour for Windows\" toolkit, see " "https://developer.pidgin.im/BonjourWindows for more information.")); return; } purple_connection_set_flags(gc, PURPLE_CONNECTION_FLAG_HTML | PURPLE_CONNECTION_FLAG_NO_IMAGES); bd = g_new0(BonjourData, 1); purple_connection_set_protocol_data(gc, bd); /* Start waiting for xmpp connections (iChat style) */ bd->xmpp_data = g_new0(BonjourXMPP, 1); bd->xmpp_data->port = purple_account_get_int(account, "port", BONJOUR_DEFAULT_PORT); bd->xmpp_data->account = account; if (bonjour_xmpp_start(bd->xmpp_data) == -1) { /* Send a message about the connection error */ purple_connection_error (gc, PURPLE_CONNECTION_ERROR_NETWORK_ERROR, _("Unable to listen for incoming IM connections")); return; } /* Connect to the mDNS daemon looking for buddies in the LAN */ bd->dns_sd_data = bonjour_dns_sd_new(); bd->dns_sd_data->first = g_strdup(purple_account_get_string(account, "first", default_firstname)); bd->dns_sd_data->last = g_strdup(purple_account_get_string(account, "last", default_lastname)); bd->dns_sd_data->port_p2pj = bd->xmpp_data->port; /* Not engaged in AV conference */ bd->dns_sd_data->vc = g_strdup("!"); status = purple_account_get_active_status(account); presence = purple_account_get_presence(account); if (purple_presence_is_available(presence)) bd->dns_sd_data->status = g_strdup("avail"); else if (purple_presence_is_idle(presence)) bd->dns_sd_data->status = g_strdup("away"); else bd->dns_sd_data->status = g_strdup("dnd"); bd->dns_sd_data->msg = g_strdup(purple_status_get_attr_string(status, "message")); bd->dns_sd_data->account = account; if (!bonjour_dns_sd_start(bd->dns_sd_data)) { purple_connection_error (gc, PURPLE_CONNECTION_ERROR_NETWORK_ERROR, _("Unable to establish connection with the local mDNS server. Is it running?")); return; } bonjour_dns_sd_update_buddy_icon(bd->dns_sd_data); /* Show the buddy list by telling Purple we have already connected */ purple_connection_set_state(gc, PURPLE_CONNECTION_CONNECTED); } static void bonjour_close(PurpleConnection *connection) { PurpleGroup *bonjour_group; BonjourData *bd = purple_connection_get_protocol_data(connection); bonjour_group = purple_blist_find_group(BONJOUR_GROUP_NAME); /* Remove all the bonjour buddies */ bonjour_removeallfromlocal(connection, bonjour_group); /* Stop looking for buddies in the LAN */ if (bd != NULL && bd->dns_sd_data != NULL) { bonjour_dns_sd_stop(bd->dns_sd_data); bonjour_dns_sd_free(bd->dns_sd_data); } if (bd != NULL && bd->xmpp_data != NULL) { /* Stop waiting for conversations */ bonjour_xmpp_stop(bd->xmpp_data); g_free(bd->xmpp_data); } /* Delete the bonjour group * (purple_blist_remove_group will bail out if the group isn't empty) */ if (bonjour_group != NULL) purple_blist_remove_group(bonjour_group); /* Cancel any file transfers */ while (bd != NULL && bd->xfer_lists) { purple_xfer_cancel_local(bd->xfer_lists->data); } if (bd != NULL) g_free(bd->jid); g_free(bd); purple_connection_set_protocol_data(connection, NULL); } static const char * bonjour_list_icon(PurpleAccount *account, PurpleBuddy *buddy) { return BONJOUR_ICON_NAME; } static GList * bonjour_protocol_get_account_options(PurpleProtocol *protocol) { PurpleAccountOption *option; GList *opts = NULL; /* Creating the options for the protocol */ option = purple_account_option_int_new(_("Local Port"), "port", BONJOUR_DEFAULT_PORT); opts = g_list_append(opts, option); option = purple_account_option_string_new(_("First name"), "first", default_firstname); opts = g_list_append(opts, option); option = purple_account_option_string_new(_("Last name"), "last", default_lastname); opts = g_list_append(opts, option); option = purple_account_option_string_new(_("Email"), "email", ""); opts = g_list_append(opts, option); option = purple_account_option_string_new(_("AIM Account"), "AIM", ""); opts = g_list_append(opts, option); option = purple_account_option_string_new(_("XMPP Account"), "jid", ""); opts = g_list_append(opts, option); return opts; } static PurpleBuddyIconSpec * bonjour_protocol_get_buddy_icon_spec(PurpleProtocol *protocol) { return purple_buddy_icon_spec_new("png,gif,jpeg", 0, 0, 96, 96, 65535, PURPLE_ICON_SCALE_DISPLAY); } static int bonjour_send_im(PurpleProtocolIM *im, PurpleConnection *connection, PurpleMessage *msg) { BonjourData *bd = purple_connection_get_protocol_data(connection); if (purple_message_is_empty(msg) || !purple_message_get_recipient(msg)) return 0; return bonjour_xmpp_send_message(bd->xmpp_data, purple_message_get_recipient(msg), purple_message_get_contents(msg)); } static void bonjour_set_status(PurpleProtocolServer *protocol_server, PurpleAccount *account, PurpleStatus *status) { PurpleConnection *gc; BonjourData *bd; PurplePresence *presence; const char *message, *bonjour_status; gchar *stripped; gc = purple_account_get_connection(account); bd = purple_connection_get_protocol_data(gc); presence = purple_account_get_presence(account); message = purple_status_get_attr_string(status, "message"); if (message == NULL) message = ""; stripped = purple_markup_strip_html(message); /* * The three possible status for Bonjour are * -available ("avail") * -idle ("away") * -away ("dnd") * Each of them can have an optional message. */ if (purple_presence_is_available(presence)) bonjour_status = "avail"; else if (purple_presence_is_idle(presence)) bonjour_status = "away"; else bonjour_status = "dnd"; bonjour_dns_sd_send_status(bd->dns_sd_data, bonjour_status, stripped); g_free(stripped); } /* * The add_buddy callback removes the buddy from the local list. * Bonjour manages buddies for you, and adding someone locally by * hand is stupid. Perhaps we should change libpurple not to allow adding * if there is no add_buddy callback. */ static void bonjour_fake_add_buddy(PurpleProtocolServer *protocol_server, PurpleConnection *pc, PurpleBuddy *buddy, PurpleGroup *group, const gchar *message) { purple_debug_error("bonjour", "Buddy '%s' manually added; removing. " "Bonjour buddies must be discovered and not manually added.\n", purple_buddy_get_name(buddy)); /* I suppose we could alert the user here, but it seems unnecessary. */ /* If this causes problems, it can be moved to an idle callback */ purple_blist_remove_buddy(buddy); } static void bonjour_remove_buddy(PurpleProtocolServer *protocol_server, PurpleConnection *pc, PurpleBuddy *buddy, PurpleGroup *group) { BonjourBuddy *bb = purple_buddy_get_protocol_data(buddy); if (bb) { bonjour_buddy_delete(bb); purple_buddy_set_protocol_data(buddy, NULL); } } static GList * bonjour_status_types(PurpleAccount *account) { GList *status_types = NULL; PurpleStatusType *type; g_return_val_if_fail(account != NULL, NULL); type = purple_status_type_new_with_attrs(PURPLE_STATUS_AVAILABLE, BONJOUR_STATUS_ID_AVAILABLE, NULL, TRUE, TRUE, FALSE, "message", _("Message"), purple_value_new(G_TYPE_STRING), NULL); status_types = g_list_append(status_types, type); type = purple_status_type_new_with_attrs(PURPLE_STATUS_AWAY, BONJOUR_STATUS_ID_AWAY, NULL, TRUE, TRUE, FALSE, "message", _("Message"), purple_value_new(G_TYPE_STRING), NULL); status_types = g_list_append(status_types, type); type = purple_status_type_new_full(PURPLE_STATUS_OFFLINE, BONJOUR_STATUS_ID_OFFLINE, NULL, TRUE, TRUE, FALSE); status_types = g_list_append(status_types, type); return status_types; } static void bonjour_convo_closed(PurpleProtocolClient *client, PurpleConnection *connection, const char *who) { PurpleBuddy *buddy = purple_blist_find_buddy(purple_connection_get_account(connection), who); BonjourBuddy *bb; if (buddy == NULL || (bb = purple_buddy_get_protocol_data(buddy)) == NULL) { /* * This buddy is not in our buddy list, and therefore does not really * exist, so we won't have any data about them. */ return; } bonjour_xmpp_close_conversation(bb->conversation); bb->conversation = NULL; } static void bonjour_set_buddy_icon(PurpleProtocolServer *protocol_server, PurpleConnection *conn, PurpleImage *img) { BonjourData *bd = purple_connection_get_protocol_data(conn); bonjour_dns_sd_update_buddy_icon(bd->dns_sd_data); } static char * bonjour_status_text(PurpleProtocolClient *client, PurpleBuddy *buddy) { PurplePresence *presence; PurpleStatus *status; const char *message; gchar *ret = NULL; presence = purple_buddy_get_presence(buddy); status = purple_presence_get_active_status(presence); message = purple_status_get_attr_string(status, "message"); if (message != NULL) { ret = g_markup_escape_text(message, -1); purple_util_chrreplace(ret, '\n', ' '); } return ret; } static void bonjour_tooltip_text(PurpleProtocolClient *client, PurpleBuddy *buddy, PurpleNotifyUserInfo *user_info, gboolean full) { PurplePresence *presence; PurpleStatus *status; BonjourBuddy *bb = purple_buddy_get_protocol_data(buddy); const char *status_description; const char *message; presence = purple_buddy_get_presence(buddy); status = purple_presence_get_active_status(presence); message = purple_status_get_attr_string(status, "message"); if (purple_presence_is_available(presence)) status_description = purple_status_get_name(status); else if (purple_presence_is_idle(presence)) status_description = _("Idle"); else status_description = purple_status_get_name(status); purple_notify_user_info_add_pair_plaintext(user_info, _("Status"), status_description); if (message != NULL) { /* TODO: Check whether it's correct to call add_pair_html, or if we should be using add_pair_plaintext */ purple_notify_user_info_add_pair_html(user_info, _("Message"), message); } if (bb == NULL) { purple_debug_error("bonjour", "Got tooltip request for a buddy without protocol data.\n"); return; } /* Only show first/last name if there is a nickname set (to avoid duplication) */ if (bb->nick != NULL && *bb->nick != '\0') { if (bb->first != NULL && *bb->first != '\0') { /* TODO: Check whether it's correct to call add_pair_html, or if we should be using add_pair_plaintext */ purple_notify_user_info_add_pair_html(user_info, _("First name"), bb->first); } if (bb->last != NULL && *bb->last != '\0') { /* TODO: Check whether it's correct to call add_pair_html, or if we should be using add_pair_plaintext */ purple_notify_user_info_add_pair_html(user_info, _("Last name"), bb->last); } } if (bb->email != NULL && *bb->email != '\0') { /* TODO: Check whether it's correct to call add_pair_html, or if we should be using add_pair_plaintext */ purple_notify_user_info_add_pair_html(user_info, _("Email"), bb->email); } if (bb->AIM != NULL && *bb->AIM != '\0') { /* TODO: Check whether it's correct to call add_pair_html, or if we should be using add_pair_plaintext */ purple_notify_user_info_add_pair_html(user_info, _("AIM Account"), bb->AIM); } if (bb->jid != NULL && *bb->jid != '\0') { /* TODO: Check whether it's correct to call add_pair_html, or if we should be using add_pair_plaintext */ purple_notify_user_info_add_pair_html(user_info, _("XMPP Account"), bb->jid); } } static void bonjour_do_group_change(PurpleBuddy *buddy, const char *new_group) { if (buddy == NULL) return; /* If we're moving them out of the bonjour group, make them persistent */ if (purple_strequal(new_group, BONJOUR_GROUP_NAME)) purple_blist_node_set_transient(PURPLE_BLIST_NODE(buddy), TRUE); else purple_blist_node_set_transient(PURPLE_BLIST_NODE(buddy), !purple_blist_node_is_transient(PURPLE_BLIST_NODE(buddy))); } static void bonjour_group_buddy(PurpleProtocolServer *protocol_server, PurpleConnection *connection, const gchar *who, const gchar *old_group, const gchar *new_group) { PurpleBuddy *buddy = purple_blist_find_buddy(purple_connection_get_account(connection), who); bonjour_do_group_change(buddy, new_group); } static void bonjour_rename_group(PurpleProtocolServer *protocol_server, PurpleConnection *connection, const gchar *old_name, PurpleGroup *group, GList *moved_buddies) { const gchar *new_group; new_group = purple_group_get_name(group); g_list_foreach(moved_buddies, (GFunc)bonjour_do_group_change, (gpointer)new_group); } static gboolean bonjour_can_receive_file(PurpleProtocolXfer *prplxfer, PurpleConnection *connection, const char *who) { PurpleBuddy *buddy = purple_blist_find_buddy(purple_connection_get_account(connection), who); return (buddy != NULL && purple_buddy_get_protocol_data(buddy) != NULL); } static gssize bonjour_get_max_message_size(PurpleProtocolClient *client, PurpleConversation *conv) { return -1; /* 5MB successfully tested. */ } #ifdef WIN32 static void _set_default_name_cb(G_GNUC_UNUSED GObject *source, GAsyncResult *result, G_GNUC_UNUSED gpointer data) { gchar *fullname = NULL; const char *splitpoint; GError *error = NULL; fullname = g_task_propagate_pointer(G_TASK(result), &error); if (fullname == NULL) { purple_debug_info("bonjour", "Unable to look up First and Last name or Username " "from system (%s); using defaults.", error ? error->message : "unknown reason"); g_clear_error(&error); return; } g_free(default_firstname); g_free(default_lastname); /* Split the real name into a first and last name */ splitpoint = strchr(fullname, ' '); if (splitpoint != NULL) { default_firstname = g_strndup(fullname, splitpoint - fullname); default_lastname = g_strdup(&splitpoint[1]); } else { default_firstname = g_strdup(fullname); default_lastname = g_strdup(""); } g_free(fullname); } static void _win32_name_lookup_thread(GTask *task, G_GNUC_UNUSED gpointer source, G_GNUC_UNUSED gpointer data, G_GNUC_UNUSED GCancellable *cancellable) { gchar *fullname = NULL; wchar_t username[UNLEN + 1]; DWORD dwLenUsername = UNLEN + 1; GetUserNameW((LPWSTR) &username, &dwLenUsername); if (*username != '\0') { LPBYTE servername = NULL; LPBYTE info = NULL; NetGetDCName(NULL, NULL, &servername); /* purple_debug_info("bonjour", "Looking up the full name from the %s.\n", (servername ? "domain controller" : "local machine")); */ if (NetUserGetInfo((LPCWSTR) servername, username, 10, &info) == NERR_Success && info != NULL && ((LPUSER_INFO_10) info)->usri10_full_name != NULL && *(((LPUSER_INFO_10) info)->usri10_full_name) != '\0') { fullname = g_utf16_to_utf8( ((LPUSER_INFO_10) info)->usri10_full_name, -1, NULL, NULL, NULL); } /* Fall back to the local machine if we didn't get the full name from the domain controller */ else if (servername != NULL) { /* purple_debug_info("bonjour", "Looking up the full name from the local machine"); */ if (info != NULL) NetApiBufferFree(info); info = NULL; if (NetUserGetInfo(NULL, username, 10, &info) == NERR_Success && info != NULL && ((LPUSER_INFO_10) info)->usri10_full_name != NULL && *(((LPUSER_INFO_10) info)->usri10_full_name) != '\0') { fullname = g_utf16_to_utf8( ((LPUSER_INFO_10) info)->usri10_full_name, -1, NULL, NULL, NULL); } } if (info != NULL) NetApiBufferFree(info); if (servername != NULL) NetApiBufferFree(servername); if (!fullname) fullname = g_utf16_to_utf8(username, -1, NULL, NULL, NULL); } g_task_return_pointer(task, fullname, g_free); } #endif static void initialize_default_account_values(void) { #ifndef _WIN32 struct passwd *info; #else GTask *lookup; #endif const char *fullname = NULL, *splitpoint, *tmp; gchar *conv = NULL; #ifndef _WIN32 /* Try to figure out the user's real name */ info = getpwuid(getuid()); if ((info != NULL) && (info->pw_gecos != NULL) && (info->pw_gecos[0] != '\0')) fullname = info->pw_gecos; else if ((info != NULL) && (info->pw_name != NULL) && (info->pw_name[0] != '\0')) fullname = info->pw_name; else if (((fullname = getlogin()) != NULL) && (fullname[0] == '\0')) fullname = NULL; #else /* The Win32 username lookup functions are synchronous so we do it in a thread */ lookup = g_task_new(my_protocol, NULL, _set_default_name_cb, NULL); g_task_run_in_thread(lookup, _win32_name_lookup_thread); g_object_unref(lookup); #endif /* Make sure fullname is valid UTF-8. If not, try to convert it. */ if (fullname != NULL && !g_utf8_validate(fullname, -1, NULL)) { fullname = conv = g_locale_to_utf8(fullname, -1, NULL, NULL, NULL); if (conv == NULL || *conv == '\0') fullname = NULL; } if (fullname == NULL) fullname = _("Purple Person"); /* Split the real name into a first and last name */ splitpoint = strchr(fullname, ' '); if (splitpoint != NULL) { default_firstname = g_strndup(fullname, splitpoint - fullname); tmp = &splitpoint[1]; /* The last name may be followed by a comma and additional data. * Only use the last name itself. */ splitpoint = strchr(tmp, ','); if (splitpoint != NULL) default_lastname = g_strndup(tmp, splitpoint - tmp); else default_lastname = g_strdup(tmp); } else { default_firstname = g_strdup(fullname); default_lastname = g_strdup(""); } g_free(conv); } static void bonjour_protocol_init(BonjourProtocol *self) { } static void bonjour_protocol_class_init(BonjourProtocolClass *klass) { PurpleProtocolClass *protocol_class = PURPLE_PROTOCOL_CLASS(klass); protocol_class->login = bonjour_login; protocol_class->close = bonjour_close; protocol_class->status_types = bonjour_status_types; protocol_class->list_icon = bonjour_list_icon; protocol_class->get_account_options = bonjour_protocol_get_account_options; protocol_class->get_buddy_icon_spec = bonjour_protocol_get_buddy_icon_spec; } static void bonjour_protocol_class_finalize(G_GNUC_UNUSED BonjourProtocolClass *klass) { } static void bonjour_protocol_client_iface_init(PurpleProtocolClientInterface *client_iface) { client_iface->status_text = bonjour_status_text; client_iface->tooltip_text = bonjour_tooltip_text; client_iface->convo_closed = bonjour_convo_closed; client_iface->get_max_message_size = bonjour_get_max_message_size; } static void bonjour_protocol_server_iface_init(PurpleProtocolServerInterface *server_iface) { server_iface->add_buddy = bonjour_fake_add_buddy; server_iface->remove_buddy = bonjour_remove_buddy; server_iface->group_buddy = bonjour_group_buddy; server_iface->rename_group = bonjour_rename_group; server_iface->set_buddy_icon = bonjour_set_buddy_icon; server_iface->set_status = bonjour_set_status; } static void bonjour_protocol_im_iface_init(PurpleProtocolIMInterface *im_iface) { im_iface->send = bonjour_send_im; } static void bonjour_protocol_xfer_iface_init(PurpleProtocolXferInterface *xfer_iface) { xfer_iface->can_receive = bonjour_can_receive_file; xfer_iface->send_file = bonjour_send_file; xfer_iface->new_xfer = bonjour_new_xfer; } G_DEFINE_DYNAMIC_TYPE_EXTENDED( BonjourProtocol, bonjour_protocol, PURPLE_TYPE_PROTOCOL, 0, G_IMPLEMENT_INTERFACE_DYNAMIC(PURPLE_TYPE_PROTOCOL_CLIENT, bonjour_protocol_client_iface_init) G_IMPLEMENT_INTERFACE_DYNAMIC(PURPLE_TYPE_PROTOCOL_SERVER, bonjour_protocol_server_iface_init) G_IMPLEMENT_INTERFACE_DYNAMIC(PURPLE_TYPE_PROTOCOL_IM, bonjour_protocol_im_iface_init) G_IMPLEMENT_INTERFACE_DYNAMIC(PURPLE_TYPE_PROTOCOL_XFER, bonjour_protocol_xfer_iface_init)); static PurpleProtocol * bonjour_protocol_new(void) { return PURPLE_PROTOCOL(g_object_new( BONJOUR_TYPE_PROTOCOL, "id", "prpl-bonjour", "name", "Bonjour", "options", OPT_PROTO_NO_PASSWORD, NULL)); } static PurplePluginInfo * plugin_query(GError **error) { return purple_plugin_info_new( "id", "prpl-bonjour", "name", "Bonjour Protocol", "version", DISPLAY_VERSION, "category", N_("Protocol"), "summary", N_("Bonjour Protocol Plugin"), "description", N_("Bonjour Protocol Plugin"), "website", PURPLE_WEBSITE, "abi-version", PURPLE_ABI_VERSION, "flags", PURPLE_PLUGIN_INFO_FLAGS_INTERNAL | PURPLE_PLUGIN_INFO_FLAGS_AUTO_LOAD, NULL ); } static gboolean plugin_load(PurplePlugin *plugin, GError **error) { PurpleProtocolManager *manager = purple_protocol_manager_get_default(); bonjour_protocol_register_type(G_TYPE_MODULE(plugin)); xep_xfer_register(G_TYPE_MODULE(plugin)); my_protocol = bonjour_protocol_new(); if(!purple_protocol_manager_register(manager, my_protocol, error)) { g_clear_object(&my_protocol); return FALSE; } initialize_default_account_values(); return TRUE; } static gboolean plugin_unload(PurplePlugin *plugin, GError **error) { PurpleProtocolManager *manager = purple_protocol_manager_get_default(); if(!purple_protocol_manager_unregister(manager, my_protocol, error)) { return FALSE; } g_clear_object(&my_protocol); g_free(default_firstname); g_free(default_lastname); return TRUE; } PURPLE_PLUGIN_INIT(bonjour, plugin_query, plugin_load, plugin_unload);