/* * Remmina - The GTK+ Remote Desktop Client * Copyright (C) 2009-2010 Vic Lee * Copyright (C) 2014-2015 Antenore Gatta, Fabio Castelli, Giovanni Panozzo * Copyright (C) 2016-2023 Antenore Gatta, Giovanni Panozzo * * 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 02110-1301, USA. * * In addition, as a special exception, the copyright holders give * permission to link the code of portions of this program with the * OpenSSL library under certain conditions as described in each * individual source file, and distribute linked combinations * including the two. * You must obey the GNU General Public License in all respects * for all of the code used other than OpenSSL. * If you modify * file(s) with this exception, you may extend this exception to your * version of the file(s), but you are not obligated to do so. * If you * do not wish to do so, delete this exception statement from your * version. * If you delete this exception statement from all source * files in the program, then also delete it here. * */ #include "config.h" #include "remmina_avahi.h" #include "remmina/remmina_trace_calls.h" #ifdef HAVE_LIBAVAHI_CLIENT #include #include #include #include #include struct _RemminaAvahiPriv { AvahiSimplePoll* simple_poll; AvahiClient* client; AvahiServiceBrowser* sb; guint iterate_handler; gboolean has_event; }; static void remmina_avahi_resolve_callback( AvahiServiceResolver* r, AVAHI_GCC_UNUSED AvahiIfIndex interface, AVAHI_GCC_UNUSED AvahiProtocol protocol, AvahiResolverEvent event, const char* name, const char* type, const char* domain, const char* host_name, const AvahiAddress* address, uint16_t port, AvahiStringList* txt, AvahiLookupResultFlags flags, AVAHI_GCC_UNUSED void* userdata) { TRACE_CALL(__func__); gchar* key; gchar* value; RemminaAvahi* ga = (RemminaAvahi*)userdata; assert(r); ga->priv->has_event = TRUE; switch (event) { case AVAHI_RESOLVER_FAILURE: g_print("(remmina-applet avahi-resolver) Failed to resolve service '%s' of type '%s' in domain '%s': %s\n", name, type, domain, avahi_strerror(avahi_client_errno(avahi_service_resolver_get_client(r)))); break; case AVAHI_RESOLVER_FOUND: key = g_strdup_printf("%s,%s,%s", name, type, domain); if (g_hash_table_lookup(ga->discovered_services, key)) { g_free(key); break; } value = g_strdup_printf("[%s]:%i", host_name, port); g_hash_table_insert(ga->discovered_services, key, value); /* key and value will be freed with g_free when the has table is freed */ g_print("(remmina-applet avahi-resolver) Added service '%s'\n", value); break; } avahi_service_resolver_free(r); } static void remmina_avahi_browse_callback( AvahiServiceBrowser* b, AvahiIfIndex interface, AvahiProtocol protocol, AvahiBrowserEvent event, const char* name, const char* type, const char* domain, AVAHI_GCC_UNUSED AvahiLookupResultFlags flags, void* userdata) { TRACE_CALL(__func__); gchar* key; RemminaAvahi* ga = (RemminaAvahi*)userdata; assert(b); ga->priv->has_event = TRUE; switch (event) { case AVAHI_BROWSER_FAILURE: g_print("(remmina-applet avahi-browser) %s\n", avahi_strerror(avahi_client_errno(avahi_service_browser_get_client(b)))); return; case AVAHI_BROWSER_NEW: key = g_strdup_printf("%s,%s,%s", name, type, domain); if (g_hash_table_lookup(ga->discovered_services, key)) { g_free(key); break; } g_free(key); g_print("(remmina-applet avahi-browser) Found service '%s' of type '%s' in domain '%s'\n", name, type, domain); if (!(avahi_service_resolver_new(ga->priv->client, interface, protocol, name, type, domain, AVAHI_PROTO_UNSPEC, 0, remmina_avahi_resolve_callback, ga))) { g_print("(remmina-applet avahi-browser) Failed to resolve service '%s': %s\n", name, avahi_strerror(avahi_client_errno(ga->priv->client))); } break; case AVAHI_BROWSER_REMOVE: g_print("(remmina-applet avahi-browser) Removed service '%s' of type '%s' in domain '%s'\n", name, type, domain); key = g_strdup_printf("%s,%s,%s", name, type, domain); g_hash_table_remove(ga->discovered_services, key); g_free(key); break; case AVAHI_BROWSER_ALL_FOR_NOW: case AVAHI_BROWSER_CACHE_EXHAUSTED: break; } } static void remmina_avahi_client_callback(AvahiClient* c, AvahiClientState state, AVAHI_GCC_UNUSED void * userdata) { TRACE_CALL(__func__); RemminaAvahi* ga = (RemminaAvahi*)userdata; ga->priv->has_event = TRUE; if (state == AVAHI_CLIENT_FAILURE) { g_print("(remmina-applet avahi) Server connection failure: %s\n", avahi_strerror(avahi_client_errno(c))); } } static gboolean remmina_avahi_iterate(RemminaAvahi* ga) { TRACE_CALL(__func__); while (TRUE) { /* Call the iteration until no further events */ ga->priv->has_event = FALSE; avahi_simple_poll_iterate(ga->priv->simple_poll, 0); if (!ga->priv->has_event) break; } return TRUE; } RemminaAvahi* remmina_avahi_new(void) { TRACE_CALL(__func__); RemminaAvahi* ga; ga = g_new(RemminaAvahi, 1); ga->discovered_services = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free); ga->started = FALSE; ga->priv = g_new(RemminaAvahiPriv, 1); ga->priv->simple_poll = NULL; ga->priv->client = NULL; ga->priv->sb = NULL; ga->priv->iterate_handler = 0; ga->priv->has_event = FALSE; return ga; } void remmina_avahi_start(RemminaAvahi* ga) { TRACE_CALL(__func__); int error; if (ga->started) return; ga->started = TRUE; ga->priv->simple_poll = avahi_simple_poll_new(); if (!ga->priv->simple_poll) { g_print("Failed to create simple poll object.\n"); return; } ga->priv->client = avahi_client_new(avahi_simple_poll_get(ga->priv->simple_poll), 0, remmina_avahi_client_callback, ga, &error); if (!ga->priv->client) { g_print("Failed to create client: %s\n", avahi_strerror(error)); return; } /** @todo Customize the default domain here */ ga->priv->sb = avahi_service_browser_new(ga->priv->client, AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, "_rfb._tcp", NULL, 0, remmina_avahi_browse_callback, ga); if (!ga->priv->sb) { g_print("Failed to create service browser: %s\n", avahi_strerror(avahi_client_errno(ga->priv->client))); return; } ga->priv->iterate_handler = g_timeout_add(5000, (GSourceFunc)remmina_avahi_iterate, ga); } void remmina_avahi_stop(RemminaAvahi* ga) { TRACE_CALL(__func__); g_hash_table_remove_all(ga->discovered_services); if (ga->priv->iterate_handler) { g_source_remove(ga->priv->iterate_handler); ga->priv->iterate_handler = 0; } if (ga->priv->sb) { avahi_service_browser_free(ga->priv->sb); ga->priv->sb = NULL; } if (ga->priv->client) { avahi_client_free(ga->priv->client); ga->priv->client = NULL; } if (ga->priv->simple_poll) { avahi_simple_poll_free(ga->priv->simple_poll); ga->priv->simple_poll = NULL; } ga->started = FALSE; } void remmina_avahi_free(RemminaAvahi* ga) { TRACE_CALL(__func__); if (ga == NULL) return; remmina_avahi_stop(ga); g_free(ga->priv); g_hash_table_destroy(ga->discovered_services); g_free(ga); } #else RemminaAvahi* remmina_avahi_new(void) { TRACE_CALL(__func__); return NULL; } void remmina_avahi_start(RemminaAvahi* ga) { TRACE_CALL(__func__); } void remmina_avahi_stop(RemminaAvahi* ga) { TRACE_CALL(__func__); } void remmina_avahi_free(RemminaAvahi* ga) { TRACE_CALL(__func__); } #endif