blob: e233e1db25b50c647793b45aac83826042ddaca0 [file] [log] [blame]
/*
*
* PACrunner - Proxy configuration daemon
*
* Copyright (C) 2010-2011 Intel Corporation. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* 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 St, Fifth Floor, Boston, MA 02110-1301 USA
*
*/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <string.h>
#include <stdlib.h>
#include <arpa/inet.h>
#include <errno.h>
#include <pthread.h>
#include "pacrunner.h"
struct pacrunner_proxy {
gint refcount;
char *interface;
enum pacrunner_proxy_method method;
char *url;
char *script;
GList **servers;
GList **excludes;
gboolean browser_only;
GList *domains;
void *jsctx;
};
struct proxy_domain {
char *domain;
int proto;
union {
struct in_addr ip4;
struct in6_addr ip6;
} addr;
int mask;
};
static GList *proxy_list = NULL;
static pthread_mutex_t proxy_mutex;
static pthread_cond_t proxy_cond;
static int timeout_source = 0;
static gint proxy_updating = -1; /* -1 for 'never set', with timeout */
struct pacrunner_proxy *pacrunner_proxy_create(const char *interface)
{
struct pacrunner_proxy *proxy;
DBG("interface %s", interface);
proxy = g_try_new0(struct pacrunner_proxy, 1);
if (!proxy)
return NULL;
proxy->refcount = 1;
proxy->interface = g_strdup(interface);
proxy->method = PACRUNNER_PROXY_METHOD_UNKNOWN;
DBG("proxy %p", proxy);
return proxy;
}
struct pacrunner_proxy *pacrunner_proxy_ref(struct pacrunner_proxy *proxy)
{
DBG("proxy %p", proxy);
if (!proxy)
return NULL;
g_atomic_int_inc(&proxy->refcount);
return proxy;
}
static void proxy_domain_destroy(gpointer data)
{
struct proxy_domain *domain = data;
g_return_if_fail(domain != NULL);
g_free(domain->domain);
g_free(domain);
}
static void reset_proxy(struct pacrunner_proxy *proxy)
{
DBG("proxy %p", proxy);
g_free(proxy->url);
proxy->url = NULL;
g_free(proxy->script);
proxy->script = NULL;
__pacrunner_manual_destroy_servers(proxy->servers);
proxy->servers = NULL;
__pacrunner_manual_destroy_excludes(proxy->excludes);
proxy->excludes = NULL;
}
void pacrunner_proxy_unref(struct pacrunner_proxy *proxy)
{
DBG("proxy %p", proxy);
if (!proxy)
return;
if (!g_atomic_int_dec_and_test(&proxy->refcount))
return;
__pacrunner_js_clear_proxy(proxy);
reset_proxy(proxy);
g_list_free_full(proxy->domains, proxy_domain_destroy);
proxy->domains = NULL;
g_free(proxy->interface);
g_free(proxy);
}
const char *pacrunner_proxy_get_interface(struct pacrunner_proxy *proxy)
{
DBG("proxy %p", proxy);
if (!proxy)
return NULL;
return proxy->interface;
}
const char *pacrunner_proxy_get_script(struct pacrunner_proxy *proxy)
{
DBG("proxy %p", proxy);
if (!proxy)
return NULL;
return proxy->script;
}
static gboolean check_browser_protocol(const char *url)
{
static const char *browser_schemes[] = {
"http://",
"https://",
"ftp://",
"nntp://",
"nntps://",
};
guint i;
for (i = 0; i < G_N_ELEMENTS(browser_schemes); i++) {
if (strncmp(browser_schemes[i], url,
strlen(browser_schemes[i])) == 0)
return TRUE;
}
return FALSE;
}
int pacrunner_proxy_set_domains(struct pacrunner_proxy *proxy, char **domains,
gboolean browser_only)
{
int len;
char *slash, **domain;
char ip[INET6_ADDRSTRLEN + 1];
DBG("proxy %p domains %p browser-only %u", proxy,
domains, browser_only);
proxy->browser_only = browser_only;
if (!proxy)
return -EINVAL;
g_list_free_full(proxy->domains, proxy_domain_destroy);
proxy->domains = NULL;
if (!domains)
return 0;
for (domain = domains; *domain; domain++) {
struct proxy_domain *data;
data = g_malloc0(sizeof(struct proxy_domain));
DBG("proxy %p domain %s", proxy, *domain);
slash = strchr(*domain, '/');
if (!slash) {
data->domain = g_strdup(*domain);
data->proto = 0;
proxy->domains = g_list_append(proxy->domains, data);
continue;
}
len = slash - *domain;
if (len > INET6_ADDRSTRLEN) {
g_free(data);
continue;
}
strncpy(ip, *domain, len);
ip[len] = '\0';
if (inet_pton(AF_INET, ip, &(data->addr.ip4)) == 1) {
data->domain = NULL;
data->proto = 4;
errno = 0;
data->mask = strtol(slash + 1, NULL, 10);
if (errno || data->mask < 0 || data->mask > 32) {
g_free(data);
continue;
}
proxy->domains = g_list_append(proxy->domains, data);
} else if (inet_pton(AF_INET6, ip, &(data->addr.ip6)) == 1) {
data->domain = NULL;
data->proto = 6;
errno = 0;
data->mask = strtol(slash + 1, NULL, 10);
if (errno || data->mask < 0 || data->mask > 128) {
g_free(data);
continue;
}
proxy->domains = g_list_append(proxy->domains, data);
} else {
g_free(data);
continue;
}
}
return 0;
}
static int set_method(struct pacrunner_proxy *proxy,
enum pacrunner_proxy_method method)
{
DBG("proxy %p method %d", proxy, method);
if (!proxy)
return -EINVAL;
if (proxy->method == method)
return 0;
proxy->method = method;
reset_proxy(proxy);
return 0;
}
int pacrunner_proxy_set_direct(struct pacrunner_proxy *proxy)
{
DBG("proxy %p", proxy);
if (!proxy)
return -EINVAL;
pthread_mutex_lock(&proxy_mutex);
if (proxy_updating == -1) {
proxy_updating = 0;
pthread_cond_broadcast(&proxy_cond);
}
pthread_mutex_unlock(&proxy_mutex);
return set_method(proxy, PACRUNNER_PROXY_METHOD_DIRECT);
}
int pacrunner_proxy_set_manual(struct pacrunner_proxy *proxy,
char **servers, char **excludes)
{
int err;
DBG("proxy %p servers %p excludes %p", proxy, servers, excludes);
if (!proxy)
return -EINVAL;
if (!servers)
return -EINVAL;
err = set_method(proxy, PACRUNNER_PROXY_METHOD_MANUAL);
if (err < 0)
return err;
proxy->servers = __pacrunner_manual_parse_servers(servers);
if (!proxy->servers)
return -EINVAL;
proxy->excludes = __pacrunner_manual_parse_excludes(excludes);
pacrunner_proxy_enable(proxy);
return 0;
}
static void download_callback(char *content, void *user_data)
{
struct pacrunner_proxy *proxy = user_data;
DBG("url %s content %p", proxy->url, content);
if (!content) {
pacrunner_error("Failed to retrieve PAC script");
goto done;
}
g_free(proxy->script);
proxy->script = content;
pacrunner_proxy_enable(proxy);
done:
pthread_mutex_lock(&proxy_mutex);
proxy_updating--;
pthread_cond_broadcast(&proxy_cond);
pthread_mutex_unlock(&proxy_mutex);
pacrunner_proxy_unref(proxy);
}
int pacrunner_proxy_set_auto(struct pacrunner_proxy *proxy,
const char *url, const char *script)
{
int err;
DBG("proxy %p url %s script %p", proxy, url, script);
if (!proxy)
return -EINVAL;
err = set_method(proxy, PACRUNNER_PROXY_METHOD_AUTO);
if (err < 0)
return err;
g_free(proxy->url);
proxy->url = g_strdup(url);
if (!proxy->url) {
g_free(proxy->script);
proxy->script = g_strdup(script);
} else {
g_free(proxy->script);
proxy->script = NULL;
}
if (proxy->script) {
pacrunner_proxy_enable(proxy);
return 0;
}
pacrunner_proxy_ref(proxy);
pthread_mutex_lock(&proxy_mutex);
err = __pacrunner_download_update(proxy->interface, proxy->url,
download_callback, proxy);
if (err < 0) {
pacrunner_proxy_unref(proxy);
if (proxy_updating == -1) {
proxy_updating = 0;
pthread_cond_broadcast(&proxy_cond);
}
pthread_mutex_unlock(&proxy_mutex);
return err;
}
if (proxy_updating == -1)
proxy_updating = 1;
else
proxy_updating++;
pthread_mutex_unlock(&proxy_mutex);
return 0;
}
int pacrunner_proxy_enable(struct pacrunner_proxy *proxy)
{
GList *list;
DBG("proxy %p", proxy);
if (!proxy)
return -EINVAL;
list = g_list_find(proxy_list, proxy);
if (list)
return -EEXIST;
proxy = pacrunner_proxy_ref(proxy);
if (!proxy)
return -EIO;
__pacrunner_js_set_proxy(proxy);
pthread_mutex_lock(&proxy_mutex);
if (proxy_updating == -1) {
proxy_updating = 0;
pthread_cond_broadcast(&proxy_cond);
}
proxy_list = g_list_append(proxy_list, proxy);
pthread_mutex_unlock(&proxy_mutex);
return 0;
}
int pacrunner_proxy_disable(struct pacrunner_proxy *proxy)
{
GList *list;
DBG("proxy %p", proxy);
if (!proxy)
return -EINVAL;
list = g_list_find(proxy_list, proxy);
if (!list)
return -ENXIO;
pthread_mutex_lock(&proxy_mutex);
proxy_list = g_list_remove_link(proxy_list, list);
g_list_free(list);
pthread_mutex_unlock(&proxy_mutex);
__pacrunner_js_clear_proxy(proxy);
pacrunner_proxy_unref(proxy);
return 0;
}
static int compare_legacy_ip_in_net(struct in_addr *host,
struct proxy_domain *match)
{
if (ntohl(host->s_addr ^ match->addr.ip4.s_addr) >> (32 - match->mask))
return -1;
return 0;
}
static int compare_ipv6_in_net(struct in6_addr *host,
struct proxy_domain *match)
{
int i, shift;
for (i = 0; i < (match->mask)/8; i++) {
if (host->s6_addr[i] != match->addr.ip6.s6_addr[i])
return -1;
}
if ((match->mask) % 8) {
/**
* If mask bits are not multiple of 8 , 1-7 bits are left
* to be compared.
*/
shift = 8 - (match->mask - (i*8));
if ((host->s6_addr[i] >> shift) !=
(match->addr.ip6.s6_addr[i] >> shift))
return -1;
}
return 0;
}
static int compare_host_in_domain(const char *host, struct proxy_domain *match)
{
size_t hlen = strlen(host);
size_t dlen = strlen(match->domain);
if ((hlen >= dlen) && (strcmp(host + (hlen - dlen),
match->domain) == 0)) {
if (hlen == dlen || host[hlen - dlen - 1] == '.')
return 0;
}
return -1;
}
/**
* A request for a "browser" protocol would match the following configs
* order of preference (if they exist):
* • Matching "Domains", BrowserOnly==TRUE
* • Matching "Domains", BrowserOnly==FALSE
* • Domains==NULL, BrowserOnly==TRUE
* • Domains==NULL, BrowserOnly==FALSE
*
* A request for a non-browser protocol would match the following :
* • Matching "Domains", BrowserOnly==FALSE
* • Domains==NULL, BrowserOnly==FALSE (except if a config exists with
* Matching "Domains", BrowserOnly==TRUE, in which case we need to
* return NULL).
**/
char *pacrunner_proxy_lookup(const char *url, const char *host)
{
GList *l, *list;
int protocol = 0;
struct in_addr ip4_addr;
struct in6_addr ip6_addr;
gboolean request_is_browser;
struct pacrunner_proxy *proxy = NULL;
/* Four classes of 'match' */
struct pacrunner_proxy *alldomains_browseronly = NULL;
struct pacrunner_proxy *alldomains_allprotos = NULL;
struct pacrunner_proxy *domainmatch_browseronly = NULL;
struct pacrunner_proxy *domainmatch_allprotos = NULL;
DBG("url %s host %s", url, host);
pthread_mutex_lock(&proxy_mutex);
while (proxy_updating)
pthread_cond_wait(&proxy_cond, &proxy_mutex);
if (!proxy_list) {
pthread_mutex_unlock(&proxy_mutex);
return NULL;
}
if (inet_pton(AF_INET, host, &ip4_addr) == 1) {
protocol = 4;
} else if (inet_pton(AF_INET6, host, &ip6_addr) == 1) {
protocol = 6;
} else if (host[0] == '[') {
char ip[INET6_ADDRSTRLEN + 1];
int len = strlen(host);
if (len < INET6_ADDRSTRLEN + 2 && host[len - 1] == ']') {
strncpy(ip, host + 1, len - 2);
ip[len - 2] = '\0';
if (inet_pton(AF_INET6, ip, &ip6_addr) == 1)
protocol = 6;
}
}
request_is_browser = check_browser_protocol(url);
for (list = g_list_first(proxy_list); list; list = g_list_next(list)) {
proxy = list->data;
if (!proxy->domains) {
if (proxy->browser_only && !alldomains_browseronly)
alldomains_browseronly = proxy;
else if (!proxy->browser_only && !alldomains_allprotos)
alldomains_allprotos = proxy;
continue;
}
for (l = g_list_first(proxy->domains); l; l = g_list_next(l)) {
struct proxy_domain *data = l->data;
if (data->proto != protocol)
continue;
switch (protocol) {
case 4:
if (compare_legacy_ip_in_net(&ip4_addr,
data) == 0) {
DBG("match proxy %p Legacy IP range %s",
proxy, data->domain);
goto matches;
}
break;
case 6:
if (compare_ipv6_in_net(&ip6_addr,
data) == 0) {
DBG("match proxy %p IPv6 range %s",
proxy, data->domain);
goto matches;
}
break;
default:
if (compare_host_in_domain(host, data) == 0) {
DBG("match proxy %p DNS domain %s",
proxy, data->domain);
goto matches;
}
break;
}
}
/* No match */
continue;
matches:
if (proxy->browser_only == request_is_browser) {
goto found;
} else if (proxy->browser_only) {
/* A non-browser request will return DIRECT instead of
* falling back to alldomains_* if this exists.
*/
if (!domainmatch_browseronly)
domainmatch_browseronly = proxy;
} else {
/* We might fall back to this, for a browser request */
if (!domainmatch_allprotos)
domainmatch_allprotos = proxy;
}
}
if (request_is_browser) {
/* We'll have bailed out immediately if we found a domain match
* with proxy->browser_only==TRUE. Fallbacks in order of prefe-
* rence.
*/
proxy = domainmatch_allprotos;
if (!proxy)
proxy = alldomains_browseronly;
if (!proxy)
proxy = alldomains_allprotos;
} else {
if (!domainmatch_browseronly)
proxy = alldomains_allprotos;
else
proxy = NULL;
}
found:
pthread_mutex_unlock(&proxy_mutex);
if (!proxy)
return NULL;
switch (proxy->method) {
case PACRUNNER_PROXY_METHOD_UNKNOWN:
case PACRUNNER_PROXY_METHOD_DIRECT:
break;
case PACRUNNER_PROXY_METHOD_MANUAL:
return __pacrunner_manual_execute(url, host, proxy->servers,
proxy->excludes);
case PACRUNNER_PROXY_METHOD_AUTO:
return __pacrunner_js_execute(proxy, url, host);
}
return NULL;
}
static gboolean proxy_config_timeout(gpointer user_data)
{
DBG("");
/* If ConnMan/NetworkManager/whatever hasn't given us a config within
a reasonable length of time, start responding 'DIRECT'. */
if (proxy_updating == -1) {
proxy_updating = 0;
pthread_cond_broadcast(&proxy_cond);
}
return FALSE;
}
void __pacrunner_proxy_set_jsctx(struct pacrunner_proxy *proxy, void *jsctx)
{
proxy->jsctx = jsctx;
}
void *__pacrunner_proxy_get_jsctx(struct pacrunner_proxy *proxy)
{
return proxy->jsctx;
}
int __pacrunner_proxy_init(void)
{
DBG("");
pthread_mutex_init(&proxy_mutex, NULL);
pthread_cond_init(&proxy_cond, NULL);
timeout_source = g_timeout_add_seconds(5, proxy_config_timeout, NULL);
return 0;
}
void __pacrunner_proxy_cleanup(void)
{
GList *list;
DBG("");
for (list = g_list_first(proxy_list); list; list = g_list_next(list)) {
struct pacrunner_proxy *proxy = list->data;
DBG("proxy %p", proxy);
if (proxy)
pacrunner_proxy_unref(proxy);
}
pthread_mutex_destroy(&proxy_mutex);
pthread_cond_destroy(&proxy_cond);
g_list_free(proxy_list);
proxy_list = NULL;
if (timeout_source) {
g_source_remove(timeout_source);
timeout_source = 0;
}
}