| /* |
| * 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 <errno.h> |
| #include <stdio.h> |
| #include <stdint.h> |
| #include <time.h> |
| #include <unistd.h> |
| |
| #include <dbus/dbus.h> |
| #include <event2/event.h> |
| #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; |
| |
| #ifdef TLSDATED_MAIN |
| static const char kTimeUpdatedData[] = "TimeUpdated"; |
| static const char *kTimeUpdated = kTimeUpdatedData; |
| #endif |
| |
| 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); |
| } |
| } |
| |
| #ifdef TLSDATED_MAIN |
| void |
| dbus_announce (struct state *global_state) |
| { |
| struct dbus_state *state = global_state->dbus; |
| DBusConnection *conn = state->conn; |
| DBusMessage *msg; |
| uint32_t ignored; |
| const char *sync_type = sync_type_str (global_state->last_sync_type); |
| 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; |
| } |
| } |
| #endif |
| |
| 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; |
| debug ("[event:%s]: fired", __func__); |
| dbus_error_init (&error); |
| |
| /* Expects DBUS_TYPE_INT64:<time_t> */ |
| 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) |
| { |
| 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; |
| |
| 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; |
| |
| 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) |
| { |
| debug ("[dbus] service request fired with bogus data"); |
| /* Consume it */ |
| return DBUS_HANDLER_RESULT_HANDLED; |
| } |
| if (strcmp (interface, kServiceInterface)) |
| { |
| 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); |
| 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; |
| } |
| |
| debug ("[dbus] initialized"); |
| return 0; |
| err: |
| tlsdate_state->dbus = NULL; |
| free (state); |
| return 1; |
| } |