/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ /* GIO - GLib Input, Output and Streaming Library * * Copyright (C) 2008 Red Hat, Inc. * * SPDX-License-Identifier: LGPL-2.1-or-later * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General * Public License along with this library; if not, see . */ #include "config.h" #include #include "glibintl.h" #include "gnetworkservice.h" #include "gcancellable.h" #include "ginetaddress.h" #include "ginetsocketaddress.h" #include "gioerror.h" #include "gnetworkaddress.h" #include "gnetworkingprivate.h" #include "gresolver.h" #include "gtask.h" #include "gsocketaddressenumerator.h" #include "gsocketconnectable.h" #include "gsrvtarget.h" #include #include /** * SECTION:gnetworkservice * @short_description: A GSocketConnectable for resolving SRV records * @include: gio/gio.h * * Like #GNetworkAddress does with hostnames, #GNetworkService * provides an easy way to resolve a SRV record, and then attempt to * connect to one of the hosts that implements that service, handling * service priority/weighting, multiple IP addresses, and multiple * address families. * * See #GSrvTarget for more information about SRV records, and see * #GSocketConnectable for an example of using the connectable * interface. */ /** * GNetworkService: * * A #GSocketConnectable for resolving a SRV record and connecting to * that service. */ struct _GNetworkServicePrivate { gchar *service, *protocol, *domain, *scheme; GList *targets; }; enum { PROP_0, PROP_SERVICE, PROP_PROTOCOL, PROP_DOMAIN, PROP_SCHEME }; static void g_network_service_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec); static void g_network_service_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec); static void g_network_service_connectable_iface_init (GSocketConnectableIface *iface); static GSocketAddressEnumerator *g_network_service_connectable_enumerate (GSocketConnectable *connectable); static GSocketAddressEnumerator *g_network_service_connectable_proxy_enumerate (GSocketConnectable *connectable); static gchar *g_network_service_connectable_to_string (GSocketConnectable *connectable); G_DEFINE_TYPE_WITH_CODE (GNetworkService, g_network_service, G_TYPE_OBJECT, G_ADD_PRIVATE (GNetworkService) G_IMPLEMENT_INTERFACE (G_TYPE_SOCKET_CONNECTABLE, g_network_service_connectable_iface_init)) static void g_network_service_finalize (GObject *object) { GNetworkService *srv = G_NETWORK_SERVICE (object); g_free (srv->priv->service); g_free (srv->priv->protocol); g_free (srv->priv->domain); g_free (srv->priv->scheme); if (srv->priv->targets) g_resolver_free_targets (srv->priv->targets); G_OBJECT_CLASS (g_network_service_parent_class)->finalize (object); } static void g_network_service_class_init (GNetworkServiceClass *klass) { GObjectClass *gobject_class = G_OBJECT_CLASS (klass); gobject_class->set_property = g_network_service_set_property; gobject_class->get_property = g_network_service_get_property; gobject_class->finalize = g_network_service_finalize; g_object_class_install_property (gobject_class, PROP_SERVICE, g_param_spec_string ("service", P_("Service"), P_("Service name, eg \"ldap\""), NULL, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS)); g_object_class_install_property (gobject_class, PROP_PROTOCOL, g_param_spec_string ("protocol", P_("Protocol"), P_("Network protocol, eg \"tcp\""), NULL, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS)); g_object_class_install_property (gobject_class, PROP_DOMAIN, g_param_spec_string ("domain", P_("Domain"), P_("Network domain, eg, \"example.com\""), NULL, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS)); g_object_class_install_property (gobject_class, PROP_DOMAIN, g_param_spec_string ("scheme", P_("Scheme"), P_("Network scheme (default is to use service)"), NULL, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); } static void g_network_service_connectable_iface_init (GSocketConnectableIface *connectable_iface) { connectable_iface->enumerate = g_network_service_connectable_enumerate; connectable_iface->proxy_enumerate = g_network_service_connectable_proxy_enumerate; connectable_iface->to_string = g_network_service_connectable_to_string; } static void g_network_service_init (GNetworkService *srv) { srv->priv = g_network_service_get_instance_private (srv); } static void g_network_service_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { GNetworkService *srv = G_NETWORK_SERVICE (object); switch (prop_id) { case PROP_SERVICE: srv->priv->service = g_value_dup_string (value); break; case PROP_PROTOCOL: srv->priv->protocol = g_value_dup_string (value); break; case PROP_DOMAIN: srv->priv->domain = g_value_dup_string (value); break; case PROP_SCHEME: g_network_service_set_scheme (srv, g_value_get_string (value)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void g_network_service_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { GNetworkService *srv = G_NETWORK_SERVICE (object); switch (prop_id) { case PROP_SERVICE: g_value_set_string (value, g_network_service_get_service (srv)); break; case PROP_PROTOCOL: g_value_set_string (value, g_network_service_get_protocol (srv)); break; case PROP_DOMAIN: g_value_set_string (value, g_network_service_get_domain (srv)); break; case PROP_SCHEME: g_value_set_string (value, g_network_service_get_scheme (srv)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } /** * g_network_service_new: * @service: the service type to look up (eg, "ldap") * @protocol: the networking protocol to use for @service (eg, "tcp") * @domain: the DNS domain to look up the service in * * Creates a new #GNetworkService representing the given @service, * @protocol, and @domain. This will initially be unresolved; use the * #GSocketConnectable interface to resolve it. * * Returns: (transfer full) (type GNetworkService): a new #GNetworkService * * Since: 2.22 */ GSocketConnectable * g_network_service_new (const gchar *service, const gchar *protocol, const gchar *domain) { return g_object_new (G_TYPE_NETWORK_SERVICE, "service", service, "protocol", protocol, "domain", domain, NULL); } /** * g_network_service_get_service: * @srv: a #GNetworkService * * Gets @srv's service name (eg, "ldap"). * * Returns: @srv's service name * * Since: 2.22 */ const gchar * g_network_service_get_service (GNetworkService *srv) { g_return_val_if_fail (G_IS_NETWORK_SERVICE (srv), NULL); return srv->priv->service; } /** * g_network_service_get_protocol: * @srv: a #GNetworkService * * Gets @srv's protocol name (eg, "tcp"). * * Returns: @srv's protocol name * * Since: 2.22 */ const gchar * g_network_service_get_protocol (GNetworkService *srv) { g_return_val_if_fail (G_IS_NETWORK_SERVICE (srv), NULL); return srv->priv->protocol; } /** * g_network_service_get_domain: * @srv: a #GNetworkService * * Gets the domain that @srv serves. This might be either UTF-8 or * ASCII-encoded, depending on what @srv was created with. * * Returns: @srv's domain name * * Since: 2.22 */ const gchar * g_network_service_get_domain (GNetworkService *srv) { g_return_val_if_fail (G_IS_NETWORK_SERVICE (srv), NULL); return srv->priv->domain; } /** * g_network_service_get_scheme: * @srv: a #GNetworkService * * Gets the URI scheme used to resolve proxies. By default, the service name * is used as scheme. * * Returns: @srv's scheme name * * Since: 2.26 */ const gchar * g_network_service_get_scheme (GNetworkService *srv) { g_return_val_if_fail (G_IS_NETWORK_SERVICE (srv), NULL); if (srv->priv->scheme) return srv->priv->scheme; else return srv->priv->service; } /** * g_network_service_set_scheme: * @srv: a #GNetworkService * @scheme: a URI scheme * * Set's the URI scheme used to resolve proxies. By default, the service name * is used as scheme. * * Since: 2.26 */ void g_network_service_set_scheme (GNetworkService *srv, const gchar *scheme) { g_return_if_fail (G_IS_NETWORK_SERVICE (srv)); g_free (srv->priv->scheme); srv->priv->scheme = g_strdup (scheme); g_object_notify (G_OBJECT (srv), "scheme"); } static GList * g_network_service_fallback_targets (GNetworkService *srv) { GSrvTarget *target; gboolean has_port; guint16 port; has_port = g_getservbyname_ntohs (srv->priv->service, "tcp", &port); #ifdef HAVE_ENDSERVENT endservent (); #endif if (!has_port) return NULL; target = g_srv_target_new (srv->priv->domain, port, 0, 0); return g_list_append (NULL, target); } #define G_TYPE_NETWORK_SERVICE_ADDRESS_ENUMERATOR (_g_network_service_address_enumerator_get_type ()) #define G_NETWORK_SERVICE_ADDRESS_ENUMERATOR(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), G_TYPE_NETWORK_SERVICE_ADDRESS_ENUMERATOR, GNetworkServiceAddressEnumerator)) typedef struct { GSocketAddressEnumerator parent_instance; GResolver *resolver; GNetworkService *srv; GSocketAddressEnumerator *addr_enum; GList *t; gboolean use_proxy; GError *error; } GNetworkServiceAddressEnumerator; typedef struct { GSocketAddressEnumeratorClass parent_class; } GNetworkServiceAddressEnumeratorClass; static GType _g_network_service_address_enumerator_get_type (void); G_DEFINE_TYPE (GNetworkServiceAddressEnumerator, _g_network_service_address_enumerator, G_TYPE_SOCKET_ADDRESS_ENUMERATOR) static GSocketAddress * g_network_service_address_enumerator_next (GSocketAddressEnumerator *enumerator, GCancellable *cancellable, GError **error) { GNetworkServiceAddressEnumerator *srv_enum = G_NETWORK_SERVICE_ADDRESS_ENUMERATOR (enumerator); GSocketAddress *ret = NULL; /* If we haven't yet resolved srv, do that */ if (!srv_enum->srv->priv->targets) { GList *targets; GError *my_error = NULL; targets = g_resolver_lookup_service (srv_enum->resolver, srv_enum->srv->priv->service, srv_enum->srv->priv->protocol, srv_enum->srv->priv->domain, cancellable, &my_error); if (!targets && g_error_matches (my_error, G_RESOLVER_ERROR, G_RESOLVER_ERROR_NOT_FOUND)) { targets = g_network_service_fallback_targets (srv_enum->srv); if (targets) g_clear_error (&my_error); } if (my_error) { g_propagate_error (error, my_error); return NULL; } srv_enum->srv->priv->targets = targets; srv_enum->t = srv_enum->srv->priv->targets; } /* Delegate to GNetworkAddress */ do { if (srv_enum->addr_enum == NULL && srv_enum->t) { GError *my_error = NULL; gchar *uri; gchar *hostname; GSocketConnectable *addr; GSrvTarget *target = srv_enum->t->data; srv_enum->t = g_list_next (srv_enum->t); hostname = g_hostname_to_ascii (g_srv_target_get_hostname (target)); if (hostname == NULL) { if (srv_enum->error == NULL) srv_enum->error = g_error_new (G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT, "Received invalid hostname '%s' from GSrvTarget", g_srv_target_get_hostname (target)); continue; } uri = g_uri_join (G_URI_FLAGS_NONE, g_network_service_get_scheme (srv_enum->srv), NULL, hostname, g_srv_target_get_port (target), "", NULL, NULL); g_free (hostname); addr = g_network_address_parse_uri (uri, g_srv_target_get_port (target), &my_error); g_free (uri); if (addr == NULL) { if (srv_enum->error == NULL) srv_enum->error = my_error; else g_error_free (my_error); continue; } if (srv_enum->use_proxy) srv_enum->addr_enum = g_socket_connectable_proxy_enumerate (addr); else srv_enum->addr_enum = g_socket_connectable_enumerate (addr); g_object_unref (addr); } if (srv_enum->addr_enum) { GError *my_error = NULL; ret = g_socket_address_enumerator_next (srv_enum->addr_enum, cancellable, &my_error); if (my_error) { if (srv_enum->error == NULL) srv_enum->error = my_error; else g_error_free (my_error); } if (!ret) { g_object_unref (srv_enum->addr_enum); srv_enum->addr_enum = NULL; } } } while (srv_enum->addr_enum == NULL && srv_enum->t); if (ret == NULL && srv_enum->error) { g_propagate_error (error, srv_enum->error); srv_enum->error = NULL; } return ret; } static void next_async_resolved_targets (GObject *source_object, GAsyncResult *result, gpointer user_data); static void next_async_have_targets (GTask *srv_enum); static void next_async_have_address (GObject *source_object, GAsyncResult *result, gpointer user_data); static void g_network_service_address_enumerator_next_async (GSocketAddressEnumerator *enumerator, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data) { GNetworkServiceAddressEnumerator *srv_enum = G_NETWORK_SERVICE_ADDRESS_ENUMERATOR (enumerator); GTask *task; task = g_task_new (enumerator, cancellable, callback, user_data); g_task_set_source_tag (task, g_network_service_address_enumerator_next_async); /* If we haven't yet resolved srv, do that */ if (!srv_enum->srv->priv->targets) { g_resolver_lookup_service_async (srv_enum->resolver, srv_enum->srv->priv->service, srv_enum->srv->priv->protocol, srv_enum->srv->priv->domain, cancellable, next_async_resolved_targets, task); } else next_async_have_targets (task); } static void next_async_resolved_targets (GObject *source_object, GAsyncResult *result, gpointer user_data) { GTask *task = user_data; GNetworkServiceAddressEnumerator *srv_enum = g_task_get_source_object (task); GError *error = NULL; GList *targets; targets = g_resolver_lookup_service_finish (srv_enum->resolver, result, &error); if (!targets && g_error_matches (error, G_RESOLVER_ERROR, G_RESOLVER_ERROR_NOT_FOUND)) { targets = g_network_service_fallback_targets (srv_enum->srv); if (targets) g_clear_error (&error); } if (error) { g_task_return_error (task, error); g_object_unref (task); } else { srv_enum->t = srv_enum->srv->priv->targets = targets; next_async_have_targets (task); } } static void next_async_have_targets (GTask *task) { GNetworkServiceAddressEnumerator *srv_enum = g_task_get_source_object (task); /* Delegate to GNetworkAddress */ if (srv_enum->addr_enum == NULL && srv_enum->t) { GSocketConnectable *addr; GSrvTarget *target = srv_enum->t->data; srv_enum->t = g_list_next (srv_enum->t); addr = g_network_address_new (g_srv_target_get_hostname (target), (guint16) g_srv_target_get_port (target)); if (srv_enum->use_proxy) srv_enum->addr_enum = g_socket_connectable_proxy_enumerate (addr); else srv_enum->addr_enum = g_socket_connectable_enumerate (addr); g_object_unref (addr); } if (srv_enum->addr_enum) { g_socket_address_enumerator_next_async (srv_enum->addr_enum, g_task_get_cancellable (task), next_async_have_address, task); } else { if (srv_enum->error) { g_task_return_error (task, srv_enum->error); srv_enum->error = NULL; } else g_task_return_pointer (task, NULL, NULL); g_object_unref (task); } } static void next_async_have_address (GObject *source_object, GAsyncResult *result, gpointer user_data) { GTask *task = user_data; GNetworkServiceAddressEnumerator *srv_enum = g_task_get_source_object (task); GSocketAddress *address; GError *error = NULL; address = g_socket_address_enumerator_next_finish (srv_enum->addr_enum, result, &error); if (error) { if (srv_enum->error == NULL) srv_enum->error = error; else g_error_free (error); } if (!address) { g_object_unref (srv_enum->addr_enum); srv_enum->addr_enum = NULL; next_async_have_targets (task); } else { g_task_return_pointer (task, address, g_object_unref); g_object_unref (task); } } static GSocketAddress * g_network_service_address_enumerator_next_finish (GSocketAddressEnumerator *enumerator, GAsyncResult *result, GError **error) { return g_task_propagate_pointer (G_TASK (result), error); } static void _g_network_service_address_enumerator_init (GNetworkServiceAddressEnumerator *enumerator) { } static void g_network_service_address_enumerator_finalize (GObject *object) { GNetworkServiceAddressEnumerator *srv_enum = G_NETWORK_SERVICE_ADDRESS_ENUMERATOR (object); if (srv_enum->srv) g_object_unref (srv_enum->srv); if (srv_enum->addr_enum) g_object_unref (srv_enum->addr_enum); if (srv_enum->resolver) g_object_unref (srv_enum->resolver); if (srv_enum->error) g_error_free (srv_enum->error); G_OBJECT_CLASS (_g_network_service_address_enumerator_parent_class)->finalize (object); } static void _g_network_service_address_enumerator_class_init (GNetworkServiceAddressEnumeratorClass *srvenum_class) { GObjectClass *object_class = G_OBJECT_CLASS (srvenum_class); GSocketAddressEnumeratorClass *enumerator_class = G_SOCKET_ADDRESS_ENUMERATOR_CLASS (srvenum_class); enumerator_class->next = g_network_service_address_enumerator_next; enumerator_class->next_async = g_network_service_address_enumerator_next_async; enumerator_class->next_finish = g_network_service_address_enumerator_next_finish; object_class->finalize = g_network_service_address_enumerator_finalize; } static GSocketAddressEnumerator * g_network_service_connectable_enumerate (GSocketConnectable *connectable) { GNetworkServiceAddressEnumerator *srv_enum; srv_enum = g_object_new (G_TYPE_NETWORK_SERVICE_ADDRESS_ENUMERATOR, NULL); srv_enum->srv = g_object_ref (G_NETWORK_SERVICE (connectable)); srv_enum->resolver = g_resolver_get_default (); srv_enum->use_proxy = FALSE; return G_SOCKET_ADDRESS_ENUMERATOR (srv_enum); } static GSocketAddressEnumerator * g_network_service_connectable_proxy_enumerate (GSocketConnectable *connectable) { GSocketAddressEnumerator *addr_enum; GNetworkServiceAddressEnumerator *srv_enum; addr_enum = g_network_service_connectable_enumerate (connectable); srv_enum = G_NETWORK_SERVICE_ADDRESS_ENUMERATOR (addr_enum); srv_enum->use_proxy = TRUE; return addr_enum; } static gchar * g_network_service_connectable_to_string (GSocketConnectable *connectable) { GNetworkService *service; service = G_NETWORK_SERVICE (connectable); return g_strdup_printf ("(%s, %s, %s, %s)", service->priv->service, service->priv->protocol, service->priv->domain, service->priv->scheme); }