diff options
Diffstat (limited to 'src/platform-cros.c')
-rw-r--r-- | src/platform-cros.c | 613 |
1 files changed, 613 insertions, 0 deletions
diff --git a/src/platform-cros.c b/src/platform-cros.c new file mode 100644 index 0000000..5a9d7ab --- /dev/null +++ b/src/platform-cros.c @@ -0,0 +1,613 @@ +/* + * platform-cros.c - CrOS platform DBus integration + * Copyright (c) 2013 The Chromium OS Authors. All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ +#include "config.h" + +#include <ctype.h> +#include <dbus/dbus.h> +#include <errno.h> +#include <signal.h> +#include <stdbool.h> +#include <stdint.h> +#include <string.h> +#include <time.h> + +#include <event2/event.h> + +#include "src/dbus.h" +#include "src/platform.h" +#include "src/tlsdate.h" +#include "src/util.h" + +static const char kMatchFormatData[] = "interface='%s',member='%s',arg0='%s'"; +static const char *kMatchFormat = kMatchFormatData; +static const char kMatchNoArgFormatData[] = "interface='%s',member='%s'"; +static const char *kMatchNoArgFormat = kMatchNoArgFormatData; + +static const char kLibCrosDestData[] = "org.chromium.LibCrosService"; +static const char *kLibCrosDest = kLibCrosDestData; +static const char kLibCrosInterfaceData[] = "org.chromium.LibCrosServiceInterface"; +static const char *kLibCrosInterface = kLibCrosInterfaceData; +static const char kLibCrosPathData[] = "/org/chromium/LibCrosService"; +static const char *kLibCrosPath = kLibCrosPathData; +static const char kResolveNetworkProxyData[] = "ResolveNetworkProxy"; +static const char *kResolveNetworkProxy = kResolveNetworkProxyData; + +static const char kDBusInterfaceData[] = "org.freedesktop.DBus"; +static const char *kDBusInterface = kDBusInterfaceData; +static const char kNameOwnerChangedData[] = "NameOwnerChanged"; +static const char *kNameOwnerChanged = kNameOwnerChangedData; +static const char kNameAcquiredData[] = "NameAcquired"; +static const char *kNameAcquired = kNameAcquiredData; + +static const char kManagerInterfaceData[] = "org.chromium.flimflam.Manager"; +static const char *kManagerInterface = kManagerInterfaceData; + +static const char kServiceInterfaceData[] = "org.chromium.flimflam.Service"; +static const char *kServiceInterface = kServiceInterfaceData; +static const char kMemberData[] = "PropertyChanged"; +static const char *kMember = kMemberData; + +static const char kProxyConfigData[] = "ProxyConfig"; +static const char *kProxyConfig = kProxyConfigData; +static const char kDefaultServiceData[] = "DefaultService"; +static const char *kDefaultService = kDefaultServiceData; + +static const char kResolveInterfaceData[] = "org.torproject.tlsdate.Resolver"; +static const char *kResolveInterface = kResolveInterfaceData; +static const char kResolveMemberData[] = "ProxyChange"; +static const char *kResolveMember = kResolveMemberData; + +/* TODO(wad) Integrate with cros_system_api/dbus/service_constants.h */ +static const char kPowerManagerInterfaceData[] = "org.chromium.PowerManager"; +static const char *kPowerManagerInterface = kPowerManagerInterfaceData; +static const char kSuspendDoneData[] = "SuspendDone"; +static const char *kSuspendDone = kSuspendDoneData; + +static const char kErrorServiceUnknownData[] = "org.freedesktop.DBus.Error.ServiceUnknown"; +static const char *kErrorServiceUnknown = kErrorServiceUnknownData; + +struct platform_state +{ + struct event_base *base; + struct state *state; + DBusMessage **resolve_msg; + int resolve_msg_count; + uint32_t resolve_network_proxy_serial; +}; + +static +bool +get_valid_hostport (const char *hostport, char *out, size_t len) +{ + bool host = true; + const char *end = hostport + strlen (hostport); + const char *c; + *out = '\0'; + /* Hosts begin with alphanumeric only. */ + if (!isalnum (*hostport)) + { + info ("Host does not start with alnum"); + return false; + } + *out++ = *hostport; + for (c = hostport + 1; c < end && len > 0; ++c, ++out, --len) + { + *out = *c; + if (host) + { + if (isalnum (*c) || *c == '-' || *c == '.') + { + continue; + } + if (*c == ':') + { + host = false; + continue; + } + } + else + { + if (isdigit (*c)) + continue; + } + *out = '\0'; + return false; + } + *out = '\0'; + return true; +} + +/* Convert PAC return format to tlsdated url format */ +/* TODO(wad) support multiple proxies when Chromium does: + * PROXY x.x.x.x:yyyy; PROXY z.z.z.z:aaaaa + */ +static +void +canonicalize_pac (const char *pac_fmt, char *proxy_url, size_t len) +{ + size_t type_len; + size_t copied = 0; + const char *space; + /* host[255]:port[6]\0 */ + char hostport[6 + 255 + 2]; + proxy_url[0] = '\0'; + if (len < 1) + return; + if (!strcmp (pac_fmt, "DIRECT")) + { + return; + } + /* Find type */ + space = strchr (pac_fmt, ' '); + if (!space) + return; + type_len = space - pac_fmt; + if (!get_valid_hostport (space + 1, hostport, sizeof (hostport))) + { + error ("invalid host:port: %s", space + 1); + return; + } + proxy_url[0] = '\0'; + if (!strncmp (pac_fmt, "PROXY", type_len)) + { + copied = snprintf (proxy_url, len, "http://%s", hostport); + } + else if (!strncmp (pac_fmt, "SOCKS", type_len)) + { + copied = snprintf (proxy_url, len, "socks4://%s", hostport); + } + else if (!strncmp (pac_fmt, "SOCKS5", type_len)) + { + copied = snprintf (proxy_url, len, "socks5://%s", hostport); + } + else if (!strncmp (pac_fmt, "HTTPS", type_len)) + { + copied = snprintf (proxy_url, len, "https://%s", hostport); + } + else + { + error ("pac_fmt unmatched: '%s' %zu", pac_fmt, type_len); + } + if (copied >= len) + { + error ("canonicalize_pac: truncation '%s'", proxy_url); + proxy_url[0] = '\0'; + return; + } +} + +static +DBusHandlerResult +handle_service_change (DBusConnection *connection, + DBusMessage *message, + struct platform_state *ctx) +{ + DBusMessageIter iter, subiter; + DBusError error; + const char *pname; + const char *pval; + const char *service; + dbus_error_init (&error); + verb_debug ("[event:cros:%s]: fired", __func__); + /* TODO(wad) Track the current DefaultService only fire when it changes */ + service = dbus_message_get_path (message); + if (!service) + return DBUS_HANDLER_RESULT_HANDLED; + /* Shill emits string:ProxyConfig variant string:"..." */ + if (!dbus_message_iter_init (message, &iter)) + return DBUS_HANDLER_RESULT_HANDLED; + if (dbus_message_iter_get_arg_type (&iter) != DBUS_TYPE_STRING) + return DBUS_HANDLER_RESULT_HANDLED; + dbus_message_iter_get_basic (&iter, &pname); + /* Make sure we are only firing on a ProxyConfig property change. */ + if (strcmp (pname, kProxyConfig)) + return DBUS_HANDLER_RESULT_HANDLED; + if (!dbus_message_iter_next (&iter)) + return DBUS_HANDLER_RESULT_HANDLED; + if (dbus_message_iter_get_arg_type (&iter) != DBUS_TYPE_VARIANT) + return DBUS_HANDLER_RESULT_HANDLED; + dbus_message_iter_recurse (&iter, &subiter); + if (dbus_message_iter_get_arg_type (&subiter) != DBUS_TYPE_STRING) + return DBUS_HANDLER_RESULT_HANDLED; + dbus_message_iter_get_basic (&subiter, &pval); + /* Right now, nothing is done with the Shill proxy value because + * Chromium handles .pac resolution. This may be more useful for + * ignoring incomplete proxy values sent while a user is typing. + */ + action_kickoff_time_sync (-1, EV_TIMEOUT, ctx->state); + return DBUS_HANDLER_RESULT_HANDLED; +} + +static +DBusHandlerResult +handle_manager_change (DBusConnection *connection, + DBusMessage *message, + struct platform_state *ctx) +{ + DBusMessageIter iter, subiter; + DBusError error; + const char *pname; + const char *pval; + verb_debug ("[event:cros:%s]: fired", __func__); + dbus_error_init (&error); + if (!dbus_message_iter_init (message, &iter)) + return DBUS_HANDLER_RESULT_HANDLED; + if (dbus_message_iter_get_arg_type (&iter) != DBUS_TYPE_STRING) + return DBUS_HANDLER_RESULT_HANDLED; + dbus_message_iter_get_basic (&iter, &pname); + /* Make sure we caught the right property. */ + if (strcmp (pname, kDefaultService)) + return DBUS_HANDLER_RESULT_HANDLED; + if (!dbus_message_iter_next (&iter)) + return DBUS_HANDLER_RESULT_HANDLED; + if (dbus_message_iter_get_arg_type (&iter) != DBUS_TYPE_VARIANT) + return DBUS_HANDLER_RESULT_HANDLED; + dbus_message_iter_recurse (&iter, &subiter); + if (dbus_message_iter_get_arg_type (&subiter) != DBUS_TYPE_OBJECT_PATH) + return DBUS_HANDLER_RESULT_HANDLED; + dbus_message_iter_get_basic (&subiter, &pval); + /* TODO(wad) Filter on the currently active service in pval. */ + verb_debug ("[event:cros:%s] service change on path %s", + __func__, pval); + action_kickoff_time_sync (-1, EV_TIMEOUT, ctx->state); + return DBUS_HANDLER_RESULT_HANDLED; +} + +static +DBusHandlerResult +handle_suspend_done (DBusConnection *connection, + DBusMessage *message, + struct platform_state *ctx) +{ + DBusMessageIter iter; + DBusError error; + const char *pname; + verb_debug ("[event:cros:%s]: fired", __func__); + /* Coming back from resume, trigger a continuity and time + * check just in case none of the other events happen. + */ + action_kickoff_time_sync (-1, EV_TIMEOUT, ctx->state); + return DBUS_HANDLER_RESULT_HANDLED; +} + +static +DBusHandlerResult +handle_proxy_change (DBusConnection *connection, + DBusMessage *message, + struct platform_state *ctx) +{ + DBusMessageIter iter; + DBusError error; + const char *pname; + const char *pval; + char time_host[MAX_PROXY_URL]; + int url_len = 0; + struct source *src = ctx->state->opts.sources; + verb_debug ("[event:cros:%s]: fired", __func__); + if (ctx->state->opts.cur_source && ctx->state->opts.cur_source->next) + src = ctx->state->opts.cur_source->next; + if (!ctx->state->resolving) + { + info ("[event:cros:%s] Unexpected ResolveNetworkProxy signal seen", + __func__); + return DBUS_HANDLER_RESULT_HANDLED; + } + dbus_error_init (&error); + /* Shill emits string:ProxyConfig variant string:"..." */ + if (!dbus_message_iter_init (message, &iter)) + return DBUS_HANDLER_RESULT_HANDLED; + if (dbus_message_iter_get_arg_type (&iter) != DBUS_TYPE_STRING) + return DBUS_HANDLER_RESULT_HANDLED; + dbus_message_iter_get_basic (&iter, &pname); + /* Make sure this was the resolution we asked for */ + url_len = snprintf (time_host, sizeof (time_host), "https://%s:%s", + src->host, src->port); + if (url_len >= sizeof (time_host)) + { + error ("[event:cros:%s]: current source url is too long", + __func__); + } + if (strcmp (pname, time_host)) + { + error ("[event:cros:%s]: resolved host mismatch: %s v %s", + __func__, pname, time_host); + return DBUS_HANDLER_RESULT_HANDLED; + } + if (!dbus_message_iter_next (&iter)) + return DBUS_HANDLER_RESULT_HANDLED; + if (dbus_message_iter_get_arg_type (&iter) != DBUS_TYPE_STRING) + return DBUS_HANDLER_RESULT_HANDLED; + dbus_message_iter_get_basic (&iter, &pval); + ctx->state->resolving = 0; + canonicalize_pac (pval, ctx->state->dynamic_proxy, sizeof (ctx->state->dynamic_proxy)); + trigger_event (ctx->state, E_TLSDATE, 1); + return DBUS_HANDLER_RESULT_HANDLED; +} + +static +DBusHandlerResult +handle_dbus_change (DBusConnection *connection, + DBusMessage *message, + struct platform_state *ctx) +{ + DBusMessageIter iter; + DBusError error; + const char *pname; + verb_debug ("[event:cros:%s]: fired", __func__); + dbus_error_init (&error); + if (!dbus_message_iter_init (message, &iter)) + return DBUS_HANDLER_RESULT_HANDLED; + if (dbus_message_iter_get_arg_type (&iter) != DBUS_TYPE_STRING) + return DBUS_HANDLER_RESULT_HANDLED; + dbus_message_iter_get_basic (&iter, &pname); + /* Make sure we caught the right property. */ + if (strcmp (pname, kLibCrosDest)) + return DBUS_HANDLER_RESULT_HANDLED; + action_kickoff_time_sync (-1, EV_TIMEOUT, ctx->state); + return DBUS_HANDLER_RESULT_HANDLED; +} + +static +void +action_resolve_proxy (evutil_socket_t fd, short what, void *arg) +{ + struct platform_state *ctx = arg; + struct dbus_state *dbus_state = ctx->state->dbus; + DBusConnection *conn = dbus_state->conn; + struct source *src = ctx->state->opts.sources; + verb_debug ("[event:%s] fired", __func__); + /* Emulate tlsdate-monitor.c:build_argv and choose the next source */ + if (ctx->state->opts.cur_source && ctx->state->opts.cur_source->next) + src = ctx->state->opts.cur_source->next; + if (ctx->state->resolving || ctx->resolve_network_proxy_serial) + { + /* Note, this is not the same as the response signal. It just avoids + * multiple requests in a single dispatch window. + */ + info ("[event:%s] no resolve_proxy sent; pending method_reply", + __func__); + return; + } + ctx->state->dynamic_proxy[0] = '\0'; + if (ctx->resolve_msg[src->id] == NULL) + { + info ("[event:%s] no dynamic proxy for %s:%s", __func__, + src->host, src->port); + trigger_event (ctx->state, E_TLSDATE, 1); + return; + } + info ("[event:%s] resolving proxy for %s:%s", __func__, + src->host, src->port); + ctx->state->resolving = 1; + if (!dbus_connection_send (conn, + ctx->resolve_msg[src->id], + &ctx->resolve_network_proxy_serial)) + { + error ("[event:%s] cannot send ResolveNetworkProxy query!", __func__); + return; + } +} + +static +DBusHandlerResult +dbus_filter (DBusConnection *connection, DBusMessage *message, void *data) +{ + struct platform_state *state = data; + /* Terminate gracefully if DBus goes away. */ + if (dbus_message_is_signal (message, DBUS_INTERFACE_LOCAL, "Disconnected")) + { + error ("[cros] DBus system bus has become inaccessible. Terminating."); + /* Trigger a graceful teardown. */ + kill (getpid(), SIGINT); + return DBUS_HANDLER_RESULT_HANDLED; + } + /* Hand it over to the service dispatcher. */ + if (dbus_message_get_type (message) == DBUS_MESSAGE_TYPE_METHOD_CALL) + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; + + /* Handle explicitly defined signals only. */ + if (dbus_message_is_signal (message, kDBusInterface, kNameAcquired)) + { + info ("[cros] DBus name acquired successfully"); + return DBUS_HANDLER_RESULT_HANDLED; + } + if (dbus_message_is_signal (message, kServiceInterface, kMember)) + return handle_service_change (connection, message, state); + if (dbus_message_is_signal (message, kManagerInterface, kMember)) + return handle_manager_change (connection, message, state); + if (dbus_message_is_signal (message, kResolveInterface, kResolveMember)) + return handle_proxy_change (connection, message, state); + if (dbus_message_is_signal (message, kDBusInterface, kNameOwnerChanged)) + return handle_dbus_change (connection, message, state); + if (dbus_message_is_signal (message, kPowerManagerInterface, kSuspendDone)) + return handle_suspend_done (connection, message, state); + if (dbus_message_is_error (message, kErrorServiceUnknown)) + { + info ("[cros] org.chromium.LibCrosService.ResolveNetworkProxy is missing"); + info ("[cros] skipping proxy resolution for now"); + /* Fire off tlsdate rather than letting it fail silently. */ + state->resolve_network_proxy_serial = 0; + state->state->resolving = 0; + trigger_event (state->state, E_TLSDATE, 1); + return DBUS_HANDLER_RESULT_HANDLED; + } + /* Indicates a successful resolve request was issued. */ + if (dbus_message_get_type (message) == DBUS_MESSAGE_TYPE_METHOD_RETURN) + { + uint32_t serial = dbus_message_get_reply_serial (message); + if (serial == state->resolve_network_proxy_serial) + { + state->resolve_network_proxy_serial = 0; + return DBUS_HANDLER_RESULT_HANDLED; + } + info ("[cros] unknown DBus METHOD_RETURN seen: %u", serial); + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; + } + verb_debug ("[cros] unknown message received: " + "type=%s dest=%s interface=%s member=%s path=%s sig=%s error_name=%s", + dbus_message_type_to_string (dbus_message_get_type (message)), + dbus_message_get_destination (message), + dbus_message_get_interface (message), + dbus_message_get_member (message), + dbus_message_get_path (message), + dbus_message_get_signature (message), + dbus_message_get_error_name (message)); + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; +} + +int +add_match (DBusConnection *conn, const char *interface, const char *member, + const char *arg0) +{ + char match[1024]; + DBusError error; + int len; + dbus_error_init (&error); + if (arg0) + { + len = snprintf (match, sizeof (match), kMatchFormat, + interface, member, arg0); + } + else + { + len = snprintf (match, sizeof (match), kMatchNoArgFormat, + interface, member); + } + if (len >= sizeof (match) || len < 0) + { + error ("[dbus] match truncated for '%s,%s'", interface, member); + return 1; + } + dbus_bus_add_match (conn, match, &error); + if (dbus_error_is_set (&error)) + { + error ("[dbus] failed to add_match for '%s,%s'; error: %s, %s", + interface, member, error.name, error.message); + dbus_error_free (&error); + return 1; + } + return 0; +} + +DBusMessage * +new_resolver_message(const struct source *src) +{ + char time_host[MAX_PROXY_URL]; + void *time_host_ptr = &time_host; + int url_len; + DBusMessage *res; + DBusMessageIter args; + if (!src->proxy || strcmp (src->proxy, "dynamic")) + { + return NULL; + } + res = dbus_message_new_method_call (kLibCrosDest, kLibCrosPath, + kLibCrosInterface, kResolveNetworkProxy); + if (!res) + { + error ("[cros] could not setup dynamic proxy for source %d", src->id); + return NULL; + } + /* Build the time_host */ + url_len = snprintf (time_host, sizeof (time_host), "https://%s:%s", + src->host, src->port); + if (url_len >= sizeof (time_host)) + { + fatal ("[cros] source %d url is too long! (%d)", src->id, url_len); + } + /* Finish the message */ + dbus_message_iter_init_append (res, &args); + if (!dbus_message_iter_append_basic (&args, DBUS_TYPE_STRING, &time_host_ptr) || + !dbus_message_iter_append_basic (&args, DBUS_TYPE_STRING, &kResolveInterface) || + !dbus_message_iter_append_basic (&args, DBUS_TYPE_STRING, &kResolveMember)) + { + fatal ("[cros could not append arguments for resolver message"); + } + return res; +} + +int +platform_init_cros (struct state *state) +{ + /* Watch for per-service ProxyConfig property changes */ + struct event_base *base = state->base; + struct dbus_state *dbus_state = state->dbus; + struct source *src = NULL; + int sources = 0; + DBusConnection *conn = dbus_state->conn; + DBusError error; + struct platform_state *platform_state = + calloc (1, sizeof (struct platform_state)); + if (!platform_state) + { + error ("[cros] could not allocate platform_state"); + return -1; + } + /* TODO(wad) Follow up with dbus_error_free() where needed. */ + dbus_error_init (&error); + /* Add watches for: proxy changes, default service changes, proxy resolution, + * LibCrosService ownership, and power state changes. + */ + if (add_match (conn, kServiceInterface, kMember, kProxyConfig) || + add_match (conn, kManagerInterface, kMember, kDefaultService) || + add_match (conn, kResolveInterface, kResolveMember, NULL) || + add_match (conn, kDBusInterface, kNameOwnerChanged, kLibCrosDest) || + add_match (conn, kPowerManagerInterface, kSuspendDone, NULL)) + return 1; + + /* Allocate one per source */ + for (src = state->opts.sources; src; src = src->next, ++sources); + platform_state->resolve_msg_count = sources; + platform_state->resolve_msg = calloc (sources, sizeof (DBusMessage *)); + if (!platform_state->resolve_msg) + { + error ("[cros] cannot allocate resolver messages"); + free (platform_state); + return -1; + } + for (src = state->opts.sources; src; src = src->next) + { + if (src->id >= sources) + fatal ("Source ID is greater than available sources!"); + platform_state->resolve_msg[src->id] = new_resolver_message (src); + if (platform_state->resolve_msg[src->id]) + src->proxy = state->dynamic_proxy; + } + state->dynamic_proxy[0] = '\0'; + if (state->opts.proxy && !strcmp (state->opts.proxy, "dynamic")) + { + info ("[cros] default dynamic proxy support"); + state->opts.proxy = state->dynamic_proxy; + } + platform_state->base = base; + platform_state->state = state; + /* Add the dynamic resolver if tlsdate doesn't already have one. */ + if (!state->events[E_RESOLVER]) + { + state->events[E_RESOLVER] = event_new (base, -1, EV_TIMEOUT, + action_resolve_proxy, + platform_state); + if (!state->events[E_RESOLVER]) + /* Let's not clean up DBus. */ + fatal ("Could not allocated resolver event"); + /* Wake up as a NET event since it'll self-block until DBus has a chance + * to send it. + */ + event_priority_set (state->events[E_RESOLVER], PRI_NET); + } + /* Each platform can attach their own filter, but the filter func needs to be + * willing to DBUS_HANDLER_RESULT_NOT_YET_HANDLED on unexpected events. + */ + /* TODO(wad) add the clean up function as the callback. */ + if (!dbus_connection_add_filter (conn, + dbus_filter, platform_state, NULL)) + { + error ("Failed to register signal handler callback"); + return 1; + } + return 0; +} |