/* GIO - GLib Input, Output and Streaming Library * * Copyright 2010, 2013 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 #include "gsimpleproxyresolver.h" #include "ginetaddress.h" #include "ginetaddressmask.h" #include "gnetworkingprivate.h" #include "gtask.h" #include "glibintl.h" /** * SECTION:gsimpleproxyresolver * @short_description: Simple proxy resolver implementation * @include: gio/gio.h * @see_also: g_socket_client_set_proxy_resolver() * * #GSimpleProxyResolver is a simple #GProxyResolver implementation * that handles a single default proxy, multiple URI-scheme-specific * proxies, and a list of hosts that proxies should not be used for. * * #GSimpleProxyResolver is never the default proxy resolver, but it * can be used as the base class for another proxy resolver * implementation, or it can be created and used manually, such as * with g_socket_client_set_proxy_resolver(). * * Since: 2.36 */ typedef struct { gchar *name; gsize length; gushort port; } GSimpleProxyResolverDomain; struct _GSimpleProxyResolverPrivate { gchar *default_proxy, **ignore_hosts; GHashTable *uri_proxies; GPtrArray *ignore_ips; GSimpleProxyResolverDomain *ignore_domains; }; static void g_simple_proxy_resolver_iface_init (GProxyResolverInterface *iface); G_DEFINE_TYPE_WITH_CODE (GSimpleProxyResolver, g_simple_proxy_resolver, G_TYPE_OBJECT, G_ADD_PRIVATE (GSimpleProxyResolver) G_IMPLEMENT_INTERFACE (G_TYPE_PROXY_RESOLVER, g_simple_proxy_resolver_iface_init)) enum { PROP_0, PROP_DEFAULT_PROXY, PROP_IGNORE_HOSTS }; static void reparse_ignore_hosts (GSimpleProxyResolver *resolver); static void g_simple_proxy_resolver_finalize (GObject *object) { GSimpleProxyResolver *resolver = G_SIMPLE_PROXY_RESOLVER (object); GSimpleProxyResolverPrivate *priv = resolver->priv; g_free (priv->default_proxy); g_hash_table_destroy (priv->uri_proxies); g_clear_pointer (&priv->ignore_hosts, g_strfreev); /* This will free ignore_ips and ignore_domains */ reparse_ignore_hosts (resolver); G_OBJECT_CLASS (g_simple_proxy_resolver_parent_class)->finalize (object); } static void g_simple_proxy_resolver_init (GSimpleProxyResolver *resolver) { resolver->priv = g_simple_proxy_resolver_get_instance_private (resolver); resolver->priv->uri_proxies = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free); } static void g_simple_proxy_resolver_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { GSimpleProxyResolver *resolver = G_SIMPLE_PROXY_RESOLVER (object); switch (prop_id) { case PROP_DEFAULT_PROXY: g_simple_proxy_resolver_set_default_proxy (resolver, g_value_get_string (value)); break; case PROP_IGNORE_HOSTS: g_simple_proxy_resolver_set_ignore_hosts (resolver, g_value_get_boxed (value)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); } } static void g_simple_proxy_resolver_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { GSimpleProxyResolver *resolver = G_SIMPLE_PROXY_RESOLVER (object); switch (prop_id) { case PROP_DEFAULT_PROXY: g_value_set_string (value, resolver->priv->default_proxy); break; case PROP_IGNORE_HOSTS: g_value_set_boxed (value, resolver->priv->ignore_hosts); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); } } static void reparse_ignore_hosts (GSimpleProxyResolver *resolver) { GSimpleProxyResolverPrivate *priv = resolver->priv; GPtrArray *ignore_ips; GArray *ignore_domains; gchar *host, *tmp, *colon, *bracket; GInetAddress *iaddr; GInetAddressMask *mask; GSimpleProxyResolverDomain domain; gushort port; int i; if (priv->ignore_ips) g_ptr_array_free (priv->ignore_ips, TRUE); if (priv->ignore_domains) { for (i = 0; priv->ignore_domains[i].name; i++) g_free (priv->ignore_domains[i].name); g_free (priv->ignore_domains); } priv->ignore_ips = NULL; priv->ignore_domains = NULL; if (!priv->ignore_hosts || !priv->ignore_hosts[0]) return; ignore_ips = g_ptr_array_new_with_free_func (g_object_unref); ignore_domains = g_array_new (TRUE, FALSE, sizeof (GSimpleProxyResolverDomain)); for (i = 0; priv->ignore_hosts[i]; i++) { host = g_strchomp (priv->ignore_hosts[i]); /* See if it's an IP address or IP/length mask */ mask = g_inet_address_mask_new_from_string (host, NULL); if (mask) { g_ptr_array_add (ignore_ips, mask); continue; } port = 0; if (*host == '[') { /* [IPv6]:port */ host++; bracket = strchr (host, ']'); if (!bracket || !bracket[1] || bracket[1] != ':') goto bad; port = strtoul (bracket + 2, &tmp, 10); if (*tmp) goto bad; *bracket = '\0'; } else { colon = strchr (host, ':'); if (colon && !strchr (colon + 1, ':')) { /* hostname:port or IPv4:port */ port = strtoul (colon + 1, &tmp, 10); if (*tmp) goto bad; *colon = '\0'; } } iaddr = g_inet_address_new_from_string (host); if (iaddr) g_object_unref (iaddr); else { if (g_str_has_prefix (host, "*.")) host += 2; else if (*host == '.') host++; } memset (&domain, 0, sizeof (domain)); domain.name = g_strdup (host); domain.length = strlen (domain.name); domain.port = port; g_array_append_val (ignore_domains, domain); continue; bad: g_warning ("Ignoring invalid ignore_hosts value '%s'", host); } if (ignore_ips->len) priv->ignore_ips = ignore_ips; else g_ptr_array_free (ignore_ips, TRUE); if (ignore_domains->len) priv->ignore_domains = (GSimpleProxyResolverDomain *)ignore_domains->data; g_array_free (ignore_domains, ignore_domains->len == 0); } static gboolean ignore_host (GSimpleProxyResolver *resolver, const gchar *host, gushort port) { GSimpleProxyResolverPrivate *priv = resolver->priv; gchar *ascii_host = NULL; gboolean ignore = FALSE; gsize offset, length; guint i; if (priv->ignore_ips) { GInetAddress *iaddr; iaddr = g_inet_address_new_from_string (host); if (iaddr) { for (i = 0; i < priv->ignore_ips->len; i++) { GInetAddressMask *mask = priv->ignore_ips->pdata[i]; if (g_inet_address_mask_matches (mask, iaddr)) { ignore = TRUE; break; } } g_object_unref (iaddr); if (ignore) return TRUE; } } if (priv->ignore_domains) { length = 0; if (g_hostname_is_non_ascii (host)) host = ascii_host = g_hostname_to_ascii (host); if (host) length = strlen (host); for (i = 0; length > 0 && priv->ignore_domains[i].length; i++) { GSimpleProxyResolverDomain *domain = &priv->ignore_domains[i]; if (domain->length > length) continue; offset = length - domain->length; if ((domain->port == 0 || domain->port == port) && (offset == 0 || (offset > 0 && host[offset - 1] == '.')) && (g_ascii_strcasecmp (domain->name, host + offset) == 0)) { ignore = TRUE; break; } } g_free (ascii_host); } return ignore; } static gchar ** g_simple_proxy_resolver_lookup (GProxyResolver *proxy_resolver, const gchar *uri, GCancellable *cancellable, GError **error) { GSimpleProxyResolver *resolver = G_SIMPLE_PROXY_RESOLVER (proxy_resolver); GSimpleProxyResolverPrivate *priv = resolver->priv; const gchar *proxy = NULL; gchar **proxies; if (priv->ignore_ips || priv->ignore_domains) { gchar *host = NULL; gint port; if (g_uri_split_network (uri, G_URI_FLAGS_NONE, NULL, &host, &port, NULL) && ignore_host (resolver, host, port > 0 ? port : 0)) proxy = "direct://"; g_free (host); } if (!proxy && g_hash_table_size (priv->uri_proxies)) { gchar *scheme = g_ascii_strdown (uri, strcspn (uri, ":")); proxy = g_hash_table_lookup (priv->uri_proxies, scheme); g_free (scheme); } if (!proxy) proxy = priv->default_proxy; if (!proxy) proxy = "direct://"; if (!strncmp (proxy, "socks://", 8)) { proxies = g_new0 (gchar *, 4); proxies[0] = g_strdup_printf ("socks5://%s", proxy + 8); proxies[1] = g_strdup_printf ("socks4a://%s", proxy + 8); proxies[2] = g_strdup_printf ("socks4://%s", proxy + 8); proxies[3] = NULL; } else { proxies = g_new0 (gchar *, 2); proxies[0] = g_strdup (proxy); } return proxies; } static void g_simple_proxy_resolver_lookup_async (GProxyResolver *proxy_resolver, const gchar *uri, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data) { GSimpleProxyResolver *resolver = G_SIMPLE_PROXY_RESOLVER (proxy_resolver); GTask *task; GError *error = NULL; char **proxies; task = g_task_new (resolver, cancellable, callback, user_data); g_task_set_source_tag (task, g_simple_proxy_resolver_lookup_async); proxies = g_simple_proxy_resolver_lookup (proxy_resolver, uri, cancellable, &error); if (proxies) g_task_return_pointer (task, proxies, (GDestroyNotify)g_strfreev); else g_task_return_error (task, error); g_object_unref (task); } static gchar ** g_simple_proxy_resolver_lookup_finish (GProxyResolver *resolver, GAsyncResult *result, GError **error) { g_return_val_if_fail (g_task_is_valid (result, resolver), NULL); return g_task_propagate_pointer (G_TASK (result), error); } static void g_simple_proxy_resolver_class_init (GSimpleProxyResolverClass *resolver_class) { GObjectClass *object_class = G_OBJECT_CLASS (resolver_class); object_class->get_property = g_simple_proxy_resolver_get_property; object_class->set_property = g_simple_proxy_resolver_set_property; object_class->finalize = g_simple_proxy_resolver_finalize; /** * GSimpleProxyResolver:default-proxy: * * The default proxy URI that will be used for any URI that doesn't * match #GSimpleProxyResolver:ignore-hosts, and doesn't match any * of the schemes set with g_simple_proxy_resolver_set_uri_proxy(). * * Note that as a special case, if this URI starts with * "socks://", #GSimpleProxyResolver will treat it as referring * to all three of the socks5, socks4a, and socks4 proxy types. */ g_object_class_install_property (object_class, PROP_DEFAULT_PROXY, g_param_spec_string ("default-proxy", P_("Default proxy"), P_("The default proxy URI"), NULL, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); /** * GSimpleProxyResolver:ignore-hosts: * * A list of hostnames and IP addresses that the resolver should * allow direct connections to. * * Entries can be in one of 4 formats: * * - A hostname, such as "example.com", ".example.com", or * "*.example.com", any of which match "example.com" or * any subdomain of it. * * - An IPv4 or IPv6 address, such as "192.168.1.1", * which matches only that address. * * - A hostname or IP address followed by a port, such as * "example.com:80", which matches whatever the hostname or IP * address would match, but only for URLs with the (explicitly) * indicated port. In the case of an IPv6 address, the address * part must appear in brackets: "[::1]:443" * * - An IP address range, given by a base address and prefix length, * such as "fe80::/10", which matches any address in that range. * * Note that when dealing with Unicode hostnames, the matching is * done against the ASCII form of the name. * * Also note that hostname exclusions apply only to connections made * to hosts identified by name, and IP address exclusions apply only * to connections made to hosts identified by address. That is, if * example.com has an address of 192.168.1.1, and the :ignore-hosts list * contains only "192.168.1.1", then a connection to "example.com" * (eg, via a #GNetworkAddress) will use the proxy, and a connection to * "192.168.1.1" (eg, via a #GInetSocketAddress) will not. * * These rules match the "ignore-hosts"/"noproxy" rules most * commonly used by other applications. */ g_object_class_install_property (object_class, PROP_IGNORE_HOSTS, g_param_spec_boxed ("ignore-hosts", P_("Ignore hosts"), P_("Hosts that will not use the proxy"), G_TYPE_STRV, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); } static void g_simple_proxy_resolver_iface_init (GProxyResolverInterface *iface) { iface->lookup = g_simple_proxy_resolver_lookup; iface->lookup_async = g_simple_proxy_resolver_lookup_async; iface->lookup_finish = g_simple_proxy_resolver_lookup_finish; } /** * g_simple_proxy_resolver_new: * @default_proxy: (nullable): the default proxy to use, eg * "socks://192.168.1.1" * @ignore_hosts: (array zero-terminated=1) (nullable): an optional list of hosts/IP addresses * to not use a proxy for. * * Creates a new #GSimpleProxyResolver. See * #GSimpleProxyResolver:default-proxy and * #GSimpleProxyResolver:ignore-hosts for more details on how the * arguments are interpreted. * * Returns: (transfer full): a new #GSimpleProxyResolver * * Since: 2.36 */ GProxyResolver * g_simple_proxy_resolver_new (const gchar *default_proxy, gchar **ignore_hosts) { return g_object_new (G_TYPE_SIMPLE_PROXY_RESOLVER, "default-proxy", default_proxy, "ignore-hosts", ignore_hosts, NULL); } /** * g_simple_proxy_resolver_set_default_proxy: * @resolver: a #GSimpleProxyResolver * @default_proxy: (nullable): the default proxy to use * * Sets the default proxy on @resolver, to be used for any URIs that * don't match #GSimpleProxyResolver:ignore-hosts or a proxy set * via g_simple_proxy_resolver_set_uri_proxy(). * * If @default_proxy starts with "socks://", * #GSimpleProxyResolver will treat it as referring to all three of * the socks5, socks4a, and socks4 proxy types. * * Since: 2.36 */ void g_simple_proxy_resolver_set_default_proxy (GSimpleProxyResolver *resolver, const gchar *default_proxy) { g_return_if_fail (G_IS_SIMPLE_PROXY_RESOLVER (resolver)); g_return_if_fail (default_proxy == NULL || g_uri_is_valid (default_proxy, G_URI_FLAGS_NONE, NULL)); g_free (resolver->priv->default_proxy); resolver->priv->default_proxy = g_strdup (default_proxy); g_object_notify (G_OBJECT (resolver), "default-proxy"); } /** * g_simple_proxy_resolver_set_ignore_hosts: * @resolver: a #GSimpleProxyResolver * @ignore_hosts: (array zero-terminated=1): %NULL-terminated list of hosts/IP addresses * to not use a proxy for * * Sets the list of ignored hosts. * * See #GSimpleProxyResolver:ignore-hosts for more details on how the * @ignore_hosts argument is interpreted. * * Since: 2.36 */ void g_simple_proxy_resolver_set_ignore_hosts (GSimpleProxyResolver *resolver, gchar **ignore_hosts) { g_return_if_fail (G_IS_SIMPLE_PROXY_RESOLVER (resolver)); g_strfreev (resolver->priv->ignore_hosts); resolver->priv->ignore_hosts = g_strdupv (ignore_hosts); reparse_ignore_hosts (resolver); g_object_notify (G_OBJECT (resolver), "ignore-hosts"); } /** * g_simple_proxy_resolver_set_uri_proxy: * @resolver: a #GSimpleProxyResolver * @uri_scheme: the URI scheme to add a proxy for * @proxy: the proxy to use for @uri_scheme * * Adds a URI-scheme-specific proxy to @resolver; URIs whose scheme * matches @uri_scheme (and which don't match * #GSimpleProxyResolver:ignore-hosts) will be proxied via @proxy. * * As with #GSimpleProxyResolver:default-proxy, if @proxy starts with * "socks://", #GSimpleProxyResolver will treat it * as referring to all three of the socks5, socks4a, and socks4 proxy * types. * * Since: 2.36 */ void g_simple_proxy_resolver_set_uri_proxy (GSimpleProxyResolver *resolver, const gchar *uri_scheme, const gchar *proxy) { g_return_if_fail (G_IS_SIMPLE_PROXY_RESOLVER (resolver)); g_hash_table_replace (resolver->priv->uri_proxies, g_ascii_strdown (uri_scheme, -1), g_strdup (proxy)); }