--- a/libpurple/protocols/bonjour/mdns_dnsapi.c Tue Jan 05 05:51:54 2021 -0500
+++ b/libpurple/protocols/bonjour/mdns_dnsapi.c Tue Jan 05 06:13:59 2021 -0500
@@ -34,12 +34,28 @@
DNS_SERVICE_INSTANCE instance;
DNS_SERVICE_CANCEL register_cancel;
+ DNS_SERVICE_CANCEL browse_cancel; + PDNS_RECORD dns_record; +_mdns_record_data_free(DnsAPIRecordData *data) + if (data->dns_record) { + DnsRecordListFree(data->dns_record, DnsFreeRecordList); _mdns_service_register_main_context(gpointer data)
@@ -66,6 +82,204 @@
GUINT_TO_POINTER(status));
+_mdns_service_browse_error(gpointer data) + DWORD status = GPOINTER_TO_UINT(data); + purple_debug_error("bonjour", "service browser - error (%ld)", status); + return G_SOURCE_REMOVE; +_mdns_str_to_utf8(DNS_CHARSET charset, gpointer data) + if (charset == DnsCharSetUtf8) { + return g_strdup((const gchar *)data); + } else if (charset == DnsCharSetUnicode) { + return g_utf16_to_utf8((const gunichar2 *)data, -1, NULL, NULL, NULL); + } else if (charset == DnsCharSetAnsi) { + return g_locale_to_utf8((const gchar *)data, -1, NULL, NULL, NULL); + return g_strdup("unknown"); +_mdns_namehost_to_service_name(DNS_CHARSET charset, gpointer data) + gchar *name = _mdns_str_to_utf8(charset, data); + size_t len = strlen(name); + if (len > strlen("." LINK_LOCAL_DOMAIN_NAME)) { + gchar *end = name + (len - strlen("." LINK_LOCAL_DOMAIN_NAME)); + if (g_str_equal(end, "." LINK_LOCAL_DOMAIN_NAME)) { +_mdns_service_browse_parse(gpointer _data) + DnsAPIRecordData *data = _data; + PDNS_RECORD pDnsRecord = data->dns_record; + PurpleAccount *account = PURPLE_ACCOUNT(data->context); + if (pDnsRecord->Flags.DW & DNSREC_DELETE) { + gchar *service_name = NULL; + gchar *hostname = NULL; + DNS_CHARSET txt_charset = DnsCharSetUnknown; + PDNS_TXT_DATAW txt = NULL; + PDNS_TXT_DATAA txt = NULL; + BonjourBuddy *bb = NULL; + for (; pDnsRecord; pDnsRecord = pDnsRecord->pNext) { + gchar *record_name = _mdns_str_to_utf8(pDnsRecord->Flags.S.CharSet, + switch (pDnsRecord->wType) { + /* The PTR record tells us a Buddy name exists. */ + if (g_str_equal(record_name, LINK_LOCAL_DOMAIN_NAME)) { + service_name = _mdns_namehost_to_service_name( + pDnsRecord->Flags.S.CharSet, + pDnsRecord->Data.PTR.pNameHost); + /* A presence service instance has been discovered... + * check it isn't us! */ + if (!purple_utf8_strcasecmp(service_name, + bonjour_get_jid(account))) { + return G_SOURCE_REMOVE; + /* The SRV record tells us the hostname and port of the + _mdns_str_to_utf8(pDnsRecord->Flags.S.CharSet, + pDnsRecord->Data.SRV.pNameTarget); + port = pDnsRecord->Data.SRV.wPort; + /* The A record tells us the IPv4 address of the hostname. + if (hostname != NULL && + g_str_equal(hostname, record_name)) { + GInetAddress *addr = g_inet_address_new_from_bytes( + (const guint8 *)&pDnsRecord->Data.A.IpAddress, + if (G_IS_INET_ADDRESS(addr)) { + ip = g_inet_address_to_string(addr); + /* The AAAA record tells us the IPv6 address of the + if (hostname != NULL && + g_str_equal(hostname, record_name)) { + GInetAddress *addr = g_inet_address_new_from_bytes( + (const guint8 *)&pDnsRecord->Data.AAAA + if (G_IS_INET_ADDRESS(addr)) { + ip = g_inet_address_to_string(addr); + /* The TEXT record gives us additional record information. + txt = &pDnsRecord->Data.TXT; + txt_charset = pDnsRecord->Flags.S.CharSet; + purple_debug_warning("bonjour", + "Received unknown DNS record type %d", + if (service_name == NULL || port == 0 || ip == NULL) { + purple_debug_error("bonjour", + "Received incomplete DNS record for '%s'", + return G_SOURCE_REMOVE; + purple_debug_info("bonjour", + "Received new record for '%s' at '%s' via %s:%d", + service_name, hostname, ip, port); + bb = bonjour_buddy_new(service_name, account); + bb->ips = g_slist_prepend(bb->ips, ip); + for (i = 0; i < txt->dwStringCount; i++) { + _mdns_str_to_utf8(txt_charset, txt->pStringArray[i]); + gchar **parts = g_strsplit(tmp, "=", 2); + set_bonjour_buddy_value(bb, parts[0], parts[1], + bonjour_buddy_add_to_purple(bb); + return G_SOURCE_REMOVE; +/* This callback occurs in a different thread; so all it does is pass the + * result to the main context thread, or Purple will break. */ +_mdns_service_browse_callback(DWORD status, PVOID pQueryContext, + PDNS_RECORD pDnsRecord) + if (status == ERROR_SUCCESS) { + DnsAPIRecordData *data = g_new(DnsAPIRecordData, 1); + data->dns_record = pDnsRecord; + data->context = pQueryContext; + g_timeout_add_seconds_full(G_PRIORITY_DEFAULT, 0, + G_SOURCE_FUNC(_mdns_service_browse_parse), + (GDestroyNotify)_mdns_record_data_free); + g_timeout_add_seconds(0, G_SOURCE_FUNC(_mdns_service_browse_error), + GUINT_TO_POINTER(status)); + DnsRecordListFree(pDnsRecord, DnsFreeRecordList); /****************************
* mdns_interface functions *
****************************/
@@ -223,10 +437,26 @@
dnsapi_mdns_browse(BonjourDnsSd *data)
Win32SessionImplData *idata = data->mdns_impl_data;
+ DNS_SERVICE_BROWSE_REQUEST browse; g_return_val_if_fail(idata != NULL, FALSE);
+ browse.Version = DNS_QUERY_REQUEST_VERSION1; + browse.InterfaceIndex = 0; + browse.QueryName = LINK_LOCAL_DOMAIN_NAME_W; + browse.pBrowseCallback = _mdns_service_browse_callback; + browse.pQueryContext = data->account; + status = DnsServiceBrowse(&browse, &idata->browse_cancel); + if (status != DNS_REQUEST_PENDING) { + "Error registering Local Link presence browser. (%ld)", status);