/* * dbus.c - event loop 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 #include #include #include #include #include #include #include "src/dbus.h" #include "src/tlsdate.h" #include "src/util.h" /* Pointers are needed so that we don't have to deal with array-to-pointer * weirdness with DBus argument passing. */ static const char kServiceInterfaceData[] = "org.torproject.tlsdate"; static const char *kServiceInterface = kServiceInterfaceData; static const char kServicePathData[] = "/org/torproject/tlsdate"; static const char *kServicePath = kServicePathData; static const char kServiceSetTimeData[] = "SetTime"; static const char *kServiceSetTime = kServiceSetTimeData; static const char kServiceCanSetTimeData[] = "CanSetTime"; static const char *kServiceCanSetTime = kServiceCanSetTimeData; static const char kServiceLastSyncInfoData[] = "LastSyncInfo"; static const char *kServiceLastSyncInfo = kServiceLastSyncInfoData; static const char kTimeUpdatedData[] = "TimeUpdated"; static const char *kTimeUpdated = kTimeUpdatedData; static short dbus_to_event (unsigned int flags) { short events = 0; if (flags & DBUS_WATCH_READABLE) events |= EV_READ; if (flags & DBUS_WATCH_WRITABLE) events |= EV_WRITE; return events; } static unsigned int event_to_dbus (short events) { unsigned int flags = 0; if (events & EV_READ) flags |= DBUS_WATCH_READABLE; if (events & EV_WRITE) flags |= DBUS_WATCH_WRITABLE; return flags; } static void watch_handler (evutil_socket_t fd, short what, void *arg) { DBusWatch *watch = arg; struct dbus_event_data *data = dbus_watch_get_data (watch); unsigned int flags = event_to_dbus (what); dbus_connection_ref (data->state->conn); while (!dbus_watch_handle (watch, flags)) { info ("dbus_watch_handle waiting for memory . . ."); /* TODO(wad) this seems like a bad idea. */ sleep (1); } while (dbus_connection_dispatch (data->state->conn) == DBUS_DISPATCH_DATA_REMAINS); dbus_connection_unref (data->state->conn); } static dbus_bool_t add_watch (DBusWatch *watch, void *user_data) { struct state *tlsdate_state = user_data; struct dbus_state *state = tlsdate_state->dbus; struct dbus_event_data *data; /* Don't add anything if it isn't active. */ data = dbus_malloc0 (sizeof (struct dbus_event_data)); if (!data) return FALSE; data->state = state; data->event = event_new (tlsdate_state->base, dbus_watch_get_unix_fd (watch), EV_PERSIST|dbus_to_event (dbus_watch_get_flags (watch)), watch_handler, watch); if (!data->event) { dbus_free (data); return FALSE; } event_priority_set (data->event, PRI_WAKE); dbus_watch_set_data (watch, data, dbus_free); if (!dbus_watch_get_enabled (watch)) return TRUE; /* Only add the event if it is enabled. */ if (event_add (data->event, NULL)) { error ("Could not add a new watch!"); event_free (data->event); dbus_free (data); return FALSE; } return TRUE; } static void remove_watch (DBusWatch *watch, void *user_data) { struct dbus_event_data *data = dbus_watch_get_data (watch); /* TODO(wad) should this just be in a free_function? */ if (data && data->event) { event_del (data->event); event_free (data->event); } } static void toggle_watch (DBusWatch *watch, void *user_data) { struct dbus_event_data *data = dbus_watch_get_data (watch); if (!data || !data->event) /* should not be possible */ return; /* If the event is pending, then we have to remove it to * disable it or remove it before re-enabling it. */ if (event_pending (data->event, dbus_to_event (dbus_watch_get_flags (watch)), NULL)) event_del (data->event); if (dbus_watch_get_enabled (watch)) { event_add (data->event, NULL); } } static void timeout_handler (evutil_socket_t fd, short what, void *arg) { DBusTimeout *t = arg; struct dbus_event_data *data = dbus_timeout_get_data (t); dbus_connection_ref (data->state->conn); dbus_timeout_handle (t); dbus_connection_unref (data->state->conn); } static dbus_bool_t add_timeout (DBusTimeout *t, void *user_data) { struct state *tlsdate_state = user_data; struct dbus_state *state = tlsdate_state->dbus; struct dbus_event_data *data; int ms = dbus_timeout_get_interval (t); struct timeval interval; data = dbus_malloc0 (sizeof (struct dbus_event_data)); if (!data) return FALSE; interval.tv_sec = ms / 1000; interval.tv_usec = (ms % 1000) * 1000; data->state = state; data->event = event_new (tlsdate_state->base, -1, EV_TIMEOUT|EV_PERSIST, timeout_handler, t); if (!data->event) { dbus_free (data); return FALSE; } event_priority_set (data->event, PRI_WAKE); dbus_timeout_set_data (t, data, dbus_free); /* Only add it to the queue if it is enabled. */ if (!dbus_timeout_get_enabled (t)) return TRUE; if (event_add (data->event, &interval)) { error ("Could not add a new timeout!"); event_free (data->event); dbus_free (data); return FALSE; } return TRUE; } static void remove_timeout (DBusTimeout *t, void *user_data) { struct dbus_event_data *data = dbus_timeout_get_data (t); if (data && data->event) { event_del (data->event); event_free (data->event); } } static void toggle_timeout (DBusTimeout *t, void *user_data) { struct dbus_event_data *data = dbus_timeout_get_data (t); int ms = dbus_timeout_get_interval (t); struct timeval interval; /* If the event is pending, then we have to remove it to * disable it or remove it before re-enabling it. */ if (evtimer_pending (data->event, NULL)) event_del (data->event); if (dbus_timeout_get_enabled (t)) { interval.tv_sec = ms / 1000; interval.tv_usec = (ms % 1000) * 1000; event_add (data->event, &interval); } } void dbus_announce (struct state *global_state) { struct dbus_state *state = global_state->dbus; DBusConnection *conn; DBusMessage *msg; uint32_t ignored; const char *sync_type = sync_type_str (global_state->last_sync_type); #ifndef TLSDATED_MAIN /* Return early if we're not linked to tlsdated. */ return; #endif conn = state->conn; msg = dbus_message_new_signal (kServicePath, kServiceInterface, kTimeUpdated); if (!msg) { error ("[dbus] could not allocate new announce signal"); return; } if (!dbus_message_append_args (msg, DBUS_TYPE_STRING, &sync_type, DBUS_TYPE_INVALID)) { error ("[dbus] could not allocate new announce args"); return; } if (!dbus_connection_send (conn, msg, &ignored)) { error ("[dbus] could not send announce signal"); return; } } static DBusHandlerResult send_time_reply (DBusConnection *connection, DBusMessage *message, dbus_uint32_t code) { DBusMessage *reply; DBusMessageIter args; dbus_uint32_t serial = dbus_message_get_serial (message); reply = dbus_message_new_method_return (message); if (!reply) { error ("[dbus] no memory to reply to SetTime"); return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; } if (!dbus_message_set_reply_serial (reply, serial)) { error ("[dbus] no memory to set serial for reply to SetTime"); dbus_message_unref (reply); return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; } dbus_message_iter_init_append (reply, &args); if (!dbus_message_iter_append_basic (&args, DBUS_TYPE_UINT32, &code)) { error ("[dbus] no memory to add reply args to SetTime"); dbus_message_unref (reply); return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; } if (!dbus_connection_send (connection, reply, &serial)) { error ("[dbus] unable to send SetTime reply"); dbus_message_unref (reply); return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; } dbus_connection_flush (connection); dbus_message_unref (reply); return DBUS_HANDLER_RESULT_HANDLED; } static DBusHandlerResult send_can_reply (DBusConnection *connection, DBusMessage *message, dbus_bool_t allowed) { DBusMessage *reply; DBusMessageIter args; dbus_uint32_t serial = dbus_message_get_serial (message); reply = dbus_message_new_method_return (message); if (!reply) { error ("[dbus] no memory to reply to CanSetTime"); return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; } if (!dbus_message_set_reply_serial (reply, serial)) { error ("[dbus] no memory to set serial for reply to CanSetTime"); dbus_message_unref (reply); return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; } dbus_message_iter_init_append (reply, &args); if (!dbus_message_iter_append_basic (&args, DBUS_TYPE_BOOLEAN, &allowed)) { error ("[dbus] no memory to add reply args to CanSetTime"); dbus_message_unref (reply); return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; } if (!dbus_connection_send (connection, reply, &serial)) { error ("[dbus] unable to send CanSetTime reply"); dbus_message_unref (reply); return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; } dbus_connection_flush (connection); dbus_message_unref (reply); return DBUS_HANDLER_RESULT_HANDLED; } /* Returns 0 if time cannot be set, and 1 otherwise. */ static int can_set_time (struct state *state) { time_t delta = state->clock_delta; /* Force a synchronization check. */ if (check_continuity (&delta) > 0) { info ("[event:%s] clock delta desync detected (%ld != %ld)", __func__, state->clock_delta, delta); delta = state->clock_delta = 0; invalidate_time (state); } /* Only use the time if we're not synchronized. */ return !state->clock_delta; } static DBusHandlerResult handle_set_time (DBusConnection *connection, DBusMessage *message, struct state *state) { DBusMessageIter iter; DBusError error; dbus_int64_t requested_time = 0; verb_debug ("[event:%s]: fired", __func__); dbus_error_init (&error); /* Expects DBUS_TYPE_INT64: */ if (!dbus_message_iter_init (message, &iter)) return send_time_reply (connection, message, SET_TIME_BAD_CALL); if (dbus_message_iter_get_arg_type (&iter) != DBUS_TYPE_INT64) return send_time_reply (connection, message, SET_TIME_BAD_CALL); dbus_message_iter_get_basic (&iter, &requested_time); if (!is_sane_time ((time_t) requested_time)) { error ("event:%s] invalid time from user: %ld", __func__, (time_t) requested_time); return send_time_reply (connection, message, SET_TIME_INVALID); } if (!can_set_time (state)) { info ("[event:%s]: time is already synchronized.", __func__); return send_time_reply (connection, message, SET_TIME_NOT_ALLOWED); } state->last_time = requested_time; state->last_sync_type = SYNC_TYPE_PLATFORM; trigger_event (state, E_SAVE, -1); /* Kick off a network sync for good measure. */ action_kickoff_time_sync (-1, EV_TIMEOUT, state); return send_time_reply (connection, message, SET_TIME_OK); } static DBusHandlerResult handle_can_set_time (DBusConnection *connection, DBusMessage *message, struct state *state) { verb_debug ("[event:%s]: fired", __func__); return send_can_reply (connection, message, can_set_time (state)); } static DBusHandlerResult handle_last_sync_info (DBusConnection *connection, DBusMessage *message, struct state *state) { DBusMessage *reply; DBusMessageIter args; dbus_uint32_t serial = dbus_message_get_serial (message); dbus_bool_t net_synced = !!state->clock_delta; const char *sync = sync_type_str (state->last_sync_type); int64_t t = state->last_time; verb_debug ("[dbus]: handler fired"); reply = dbus_message_new_method_return (message); if (!reply) { error ("[dbus] no memory to reply to LastSyncInfo"); return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; } if (!dbus_message_set_reply_serial (reply, serial)) { error ("[dbus] no memory to set serial for reply to LastSyncInfo"); dbus_message_unref (reply); return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; } dbus_message_iter_init_append (reply, &args); if (!dbus_message_iter_append_basic (&args, DBUS_TYPE_BOOLEAN, &net_synced)) { error ("[dbus] no memory to add reply args to LastSyncInfo"); dbus_message_unref (reply); return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; } if (!dbus_message_iter_append_basic (&args, DBUS_TYPE_STRING, &sync)) { error ("[dbus] no memory to add reply args to LastSyncInfo"); dbus_message_unref (reply); return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; } if (!dbus_message_iter_append_basic (&args, DBUS_TYPE_INT64, &t)) { error ("[dbus] no memory to add reply args to LastSyncInfo"); dbus_message_unref (reply); return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; } if (!dbus_connection_send (connection, reply, &serial)) { error ("[dbus] unable to send LastSyncInfo reply"); dbus_message_unref (reply); return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; } dbus_connection_flush (connection); dbus_message_unref (reply); return DBUS_HANDLER_RESULT_HANDLED; } static void unregister_service (DBusConnection *conn, void *data) { info ("dbus service has been unregistered"); } static DBusHandlerResult service_dispatch (DBusConnection *conn, DBusMessage *msg, void *data) { struct state *state = data; const char *interface; const char *method; verb_debug ("[dbus] service dispatcher called"); if (dbus_message_get_type (msg) != DBUS_MESSAGE_TYPE_METHOD_CALL) return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; interface = dbus_message_get_interface (msg); method = dbus_message_get_member (msg); if (!interface || !method) { verb_debug ("[dbus] service request fired with bogus data"); /* Consume it */ return DBUS_HANDLER_RESULT_HANDLED; } if (strcmp (interface, kServiceInterface)) { verb_debug ("[dbus] invalid interface supplied"); return DBUS_HANDLER_RESULT_HANDLED; } if (!strcmp (method, kServiceSetTime)) return handle_set_time (conn, msg, state); else if (!strcmp (method, kServiceCanSetTime)) return handle_can_set_time (conn, msg, state); else if (!strcmp (method, kServiceLastSyncInfo)) return handle_last_sync_info (conn, msg, state); verb_debug ("[dbus] invalid method supplied"); return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; } static DBusObjectPathVTable service_vtable = { .unregister_function = unregister_service, .message_function = service_dispatch, }; int init_dbus (struct state *tlsdate_state) { DBusError error; dbus_error_init (&error); struct dbus_state *state = calloc (1, sizeof (struct dbus_state)); if (!state) return 1; tlsdate_state->dbus = state; state->conn = dbus_bus_get (DBUS_BUS_SYSTEM, &error); if (state->conn == NULL || dbus_error_is_set (&error)) { error ("[dbus] error when connecting to the bus: %s", error.message); goto err; } if (!dbus_connection_set_timeout_functions (state->conn, add_timeout, remove_timeout, toggle_timeout, tlsdate_state, dbus_free)) { error ("[dbus] dbus_connection_set_timeout_functions failed"); /* TODO(wad) disconnect from DBus */ goto err; } if (!dbus_connection_set_watch_functions (state->conn, add_watch, remove_watch, toggle_watch, tlsdate_state, dbus_free)) { error ("[dbus] dbus_connection_set_watch_functions failed"); goto err; } if (!dbus_bus_request_name (state->conn, kServiceInterface, 0, &error) || dbus_error_is_set (&error)) { error ("[dbus] failed to get name: %s", error.message); goto err; } /* Setup the vtable for dispatching incoming messages. */ if (dbus_connection_register_object_path ( state->conn, kServicePath, &service_vtable, tlsdate_state) == FALSE) { error ("[dbus] failed to register object path: %s", kServicePath); goto err; } verb_debug ("[dbus] initialized"); return 0; err: tlsdate_state->dbus = NULL; free (state); return 1; }