/* * 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; int 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 < 0 || ((size_t) 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) { 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 < 0 || ((size_t) 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 < 0 || ((size_t) len) >= sizeof (match)) { 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 < 0 || ((size_t) 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; if (!dbus_state) { info ("[cros] DBus not connected, skipping platform initialization."); return 0; } 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; }