diff options
author | Charles Kerr <charles.kerr@canonical.com> | 2016-03-24 16:16:53 +0000 |
---|---|---|
committer | CI Train Bot <ci-train-bot@canonical.com> | 2016-03-24 16:16:53 +0000 |
commit | e3fa9582a01f14e6d80e00d375361a76df716895 (patch) | |
tree | 7b9521ad7cfa7a2e765be265d5ecd98cb9bcad23 /src | |
parent | d2f14e07000e9a5ee418bcbcd34c5ae57bd45590 (diff) | |
parent | 194d7e85a52cbc0060a2d85b71b9ddd8b606aee4 (diff) | |
download | ayatana-indicator-display-e3fa9582a01f14e6d80e00d375361a76df716895.tar.gz ayatana-indicator-display-e3fa9582a01f14e6d80e00d375361a76df716895.tar.bz2 ayatana-indicator-display-e3fa9582a01f14e6d80e00d375361a76df716895.zip |
When a new device appears to ADB, prompt the user whether or not to allow the connection.
Approved by: PS Jenkins bot, Charles Kerr, Xavi Garcia
Diffstat (limited to 'src')
-rw-r--r-- | src/CMakeLists.txt | 62 | ||||
-rw-r--r-- | src/adbd-client.cpp | 303 | ||||
-rw-r--r-- | src/adbd-client.h | 74 | ||||
-rw-r--r-- | src/dbus-names.h | 60 | ||||
-rw-r--r-- | src/greeter.cpp | 161 | ||||
-rw-r--r-- | src/greeter.h | 47 | ||||
-rw-r--r-- | src/indicator.cpp | 37 | ||||
-rw-r--r-- | src/indicator.h | 13 | ||||
-rw-r--r-- | src/main.cpp | 13 | ||||
-rw-r--r-- | src/rotation-lock.cpp | 1 | ||||
-rw-r--r-- | src/usb-manager.cpp | 180 | ||||
-rw-r--r-- | src/usb-manager.h | 48 | ||||
-rw-r--r-- | src/usb-monitor.cpp | 81 | ||||
-rw-r--r-- | src/usb-monitor.h | 52 | ||||
-rw-r--r-- | src/usb-snap.cpp | 250 | ||||
-rw-r--r-- | src/usb-snap.h | 42 |
16 files changed, 1390 insertions, 34 deletions
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 982aa49..060071d 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,29 +1,37 @@ -set (SERVICE_LIB "indicatordisplayservice") -set (SERVICE_EXEC "indicator-display-service") -add_definitions (-DG_LOG_DOMAIN="${CMAKE_PROJECT_NAME}") - -# handwritten source code... -set (SERVICE_LIB_HANDWRITTEN_SOURCES - exporter.cpp - rotation-lock.cpp) - -add_library (${SERVICE_LIB} STATIC - ${SERVICE_LIB_HANDWRITTEN_SOURCES}) - -# add the bin dir to the include path so that -# the compiler can find the generated header files -include_directories (${CMAKE_CURRENT_BINARY_DIR}) - -link_directories (${SERVICE_DEPS_LIBRARY_DIRS}) - -set (SERVICE_EXEC_HANDWRITTEN_SOURCES main.cpp) -add_executable (${SERVICE_EXEC} ${SERVICE_EXEC_HANDWRITTEN_SOURCES}) -target_link_libraries (${SERVICE_EXEC} ${SERVICE_LIB} ${SERVICE_DEPS_LIBRARIES} ${GCOV_LIBS}) -install (TARGETS ${SERVICE_EXEC} RUNTIME DESTINATION ${CMAKE_INSTALL_FULL_PKGLIBEXECDIR}) - -# add warnings/coverage info on handwritten files -# but not the generated ones... -set_property (SOURCE ${SERVICE_LIB_HANDWRITTEN_SOURCES} ${SERVICE_EXEC_HANDWRITTEN_SOURCES} - APPEND_STRING PROPERTY COMPILE_FLAGS " -std=c++11 -g ${CXX_WARNING_ARGS} ${GCOV_FLAGS}") +add_definitions(-DG_LOG_DOMAIN="${CMAKE_PROJECT_NAME}") + +add_compile_options( + ${CXX_WARNING_ARGS} +) + +add_library( + ${SERVICE_LIB} + STATIC + adbd-client.cpp + exporter.cpp + greeter.cpp + indicator.cpp + rotation-lock.cpp + usb-manager.cpp + usb-monitor.cpp + usb-snap.cpp +) + +add_executable( + ${SERVICE_EXEC} + main.cpp +) + +target_link_libraries(${SERVICE_EXEC} + ${SERVICE_LIB} + ${SERVICE_DEPS_LIBRARIES} + ${THREAD_LINK_LIBRARIES} +) + +install( + TARGETS + ${SERVICE_EXEC} + RUNTIME DESTINATION ${CMAKE_INSTALL_FULL_PKGLIBEXECDIR} +) diff --git a/src/adbd-client.cpp b/src/adbd-client.cpp new file mode 100644 index 0000000..400c7c9 --- /dev/null +++ b/src/adbd-client.cpp @@ -0,0 +1,303 @@ +/* + * Copyright 2016 Canonical Ltd. + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 3, 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 warranties of + * MERCHANTABILITY, SATISFACTORY QUALITY, 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, see <http://www.gnu.org/licenses/>. + * + * Authors: + * Charles Kerr <charles.kerr@canonical.com> + */ + +#include <src/adbd-client.h> + +#include <gio/gio.h> +#include <gio/gunixsocketaddress.h> + +#include <algorithm> +#include <cctype> +#include <cstring> +#include <chrono> +#include <condition_variable> +#include <mutex> +#include <thread> + +class GAdbdClient::Impl +{ +public: + + explicit Impl(const std::string& socket_path): + m_socket_path{socket_path}, + m_cancellable{g_cancellable_new()}, + m_worker_thread{&Impl::worker_func, this} + { + } + + ~Impl() + { + // tell the worker thread to stop whatever it's doing and exit. + g_debug("%s Client::Impl dtor, cancelling m_cancellable", G_STRLOC); + g_cancellable_cancel(m_cancellable); + m_pkresponse_cv.notify_one(); + m_sleep_cv.notify_one(); + m_worker_thread.join(); + g_clear_object(&m_cancellable); + } + + core::Signal<const PKRequest&>& on_pk_request() + { + return m_on_pk_request; + } + +private: + + // struct to carry request info from the worker thread to the GMainContext thread + struct PKIdleData + { + Impl* self = nullptr; + GCancellable* cancellable = nullptr; + const std::string public_key; + + PKIdleData(Impl* self_, GCancellable* cancellable_, std::string public_key_): + self(self_), + cancellable(G_CANCELLABLE(g_object_ref(cancellable_))), + public_key(public_key_) {} + + ~PKIdleData() {g_clear_object(&cancellable);} + + }; + + void pass_public_key_to_main_thread(const std::string& public_key) + { + g_idle_add_full(G_PRIORITY_DEFAULT_IDLE, + on_public_key_request_static, + new PKIdleData{this, m_cancellable, public_key}, + [](gpointer id){delete static_cast<PKIdleData*>(id);}); + } + + static gboolean on_public_key_request_static (gpointer gdata) // runs in main thread + { + /* NB: It's possible (though unlikely) that data.self was destroyed + while this callback was pending, so we must check is-cancelled FIRST */ + auto data = static_cast<PKIdleData*>(gdata); + if (!g_cancellable_is_cancelled(data->cancellable)) + { + // notify our listeners of the request + auto self = data->self; + struct PKRequest req; + req.public_key = data->public_key; + req.fingerprint = get_fingerprint(req.public_key); + req.respond = [self](PKResponse response){self->on_public_key_response(response);}; + self->m_on_pk_request(req); + } + + return G_SOURCE_REMOVE; + } + + void on_public_key_response(PKResponse response) + { + // set m_pkresponse and wake up the waiting worker thread + std::unique_lock<std::mutex> lk(m_pkresponse_mutex); + m_pkresponse = response; + m_pkresponse_ready = true; + m_pkresponse_cv.notify_one(); + } + + /*** + **** + ***/ + + void worker_func() // runs in worker thread + { + const std::string socket_path {m_socket_path}; + + while (!g_cancellable_is_cancelled(m_cancellable)) + { + g_debug("%s creating a client socket to '%s'", G_STRLOC, socket_path.c_str()); + auto socket = create_client_socket(socket_path); + bool got_valid_req = false; + + g_debug("%s calling read_request", G_STRLOC); + std::string reqstr; + if (socket != nullptr) + reqstr = read_request(socket); + if (!reqstr.empty()) + g_debug("%s got request [%s]", G_STRLOC, reqstr.c_str()); + + if (reqstr.substr(0,2) == "PK") { + PKResponse response = PKResponse::DENY; + const auto public_key = reqstr.substr(2); + g_debug("%s got pk [%s]", G_STRLOC, public_key.c_str()); + if (!public_key.empty()) { + got_valid_req = true; + std::unique_lock<std::mutex> lk(m_pkresponse_mutex); + m_pkresponse_ready = false; + pass_public_key_to_main_thread(public_key); + m_pkresponse_cv.wait(lk, [this](){ + return m_pkresponse_ready || g_cancellable_is_cancelled(m_cancellable); + }); + response = m_pkresponse; + g_debug("%s got response '%d', is-cancelled %d", G_STRLOC, + int(response), + int(g_cancellable_is_cancelled(m_cancellable))); + } + if (!g_cancellable_is_cancelled(m_cancellable)) + send_pk_response(socket, response); + } else if (!reqstr.empty()) { + g_warning("Invalid ADB request: [%s]", reqstr.c_str()); + } + + g_clear_object(&socket); + + // If nothing interesting's happening, sleep a bit. + // (Interval copied from UsbDebuggingManager.java) + static constexpr std::chrono::seconds sleep_interval {std::chrono::seconds(1)}; + if (!got_valid_req && !g_cancellable_is_cancelled(m_cancellable)) { + std::unique_lock<std::mutex> lk(m_sleep_mutex); + m_sleep_cv.wait_for(lk, sleep_interval); + } + } + } + + // connect to a local domain socket + GSocket* create_client_socket(const std::string& socket_path) + { + GError* error {}; + auto socket = g_socket_new(G_SOCKET_FAMILY_UNIX, + G_SOCKET_TYPE_STREAM, + G_SOCKET_PROTOCOL_DEFAULT, + &error); + if (error != nullptr) { + g_warning("Error creating adbd client socket: %s", error->message); + g_clear_error(&error); + g_clear_object(&socket); + return nullptr; + } + + auto address = g_unix_socket_address_new(socket_path.c_str()); + const auto connected = g_socket_connect(socket, address, m_cancellable, &error); + g_clear_object(&address); + if (!connected) { + g_debug("unable to connect to '%s': %s", socket_path.c_str(), error->message); + g_clear_error(&error); + g_clear_object(&socket); + return nullptr; + } + + return socket; + } + + std::string read_request(GSocket* socket) + { + char buf[4096] = {}; + g_debug("%s calling g_socket_receive()", G_STRLOC); + const auto n_bytes = g_socket_receive (socket, buf, sizeof(buf), m_cancellable, nullptr); + std::string ret; + if (n_bytes > 0) + ret.append(buf, std::string::size_type(n_bytes)); + g_debug("%s g_socket_receive got %d bytes: [%s]", G_STRLOC, int(n_bytes), ret.c_str()); + return ret; + } + + void send_pk_response(GSocket* socket, PKResponse response) + { + std::string response_str; + switch(response) { + case PKResponse::ALLOW: response_str = "OK"; break; + case PKResponse::DENY: response_str = "NO"; break; + } + g_debug("%s sending reply: [%s]", G_STRLOC, response_str.c_str()); + + GError* error {}; + g_socket_send(socket, + response_str.c_str(), + response_str.size(), + m_cancellable, + &error); + if (error != nullptr) { + if (!g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) + g_warning("GAdbdServer: Error accepting socket connection: %s", error->message); + g_clear_error(&error); + } + } + + static std::string get_fingerprint(const std::string& public_key) + { + // The first token is base64-encoded data, so cut on the first whitespace + const std::string base64 ( + public_key.begin(), + std::find_if( + public_key.begin(), public_key.end(), + [](const std::string::value_type& ch){return std::isspace(ch);} + ) + ); + + gsize digest_len {}; + auto digest = g_base64_decode(base64.c_str(), &digest_len); + + auto checksum = g_compute_checksum_for_data(G_CHECKSUM_MD5, digest, digest_len); + const gsize checksum_len = checksum ? strlen(checksum) : 0; + + // insert ':' between character pairs; eg "ff27b5f3" --> "ff:27:b5:f3" + std::string fingerprint; + for (gsize i=0; i<checksum_len; ) { + fingerprint.append(checksum+i, checksum+i+2); + if (i < checksum_len-2) + fingerprint.append(":"); + i += 2; + } + + g_clear_pointer(&digest, g_free); + g_clear_pointer(&checksum, g_free); + return fingerprint; + } + + const std::string m_socket_path; + GCancellable* m_cancellable = nullptr; + std::thread m_worker_thread; + core::Signal<const PKRequest&> m_on_pk_request; + + std::mutex m_sleep_mutex; + std::condition_variable m_sleep_cv; + + std::mutex m_pkresponse_mutex; + std::condition_variable m_pkresponse_cv; + bool m_pkresponse_ready = false; + PKResponse m_pkresponse = PKResponse::DENY; +}; + +/*** +**** +***/ + +AdbdClient::~AdbdClient() +{ +} + +/*** +**** +***/ + +GAdbdClient::GAdbdClient(const std::string& socket_path): + impl{new Impl{socket_path}} +{ +} + +GAdbdClient::~GAdbdClient() +{ +} + +core::Signal<const AdbdClient::PKRequest&>& +GAdbdClient::on_pk_request() +{ + return impl->on_pk_request(); +} + diff --git a/src/adbd-client.h b/src/adbd-client.h new file mode 100644 index 0000000..dcee2f1 --- /dev/null +++ b/src/adbd-client.h @@ -0,0 +1,74 @@ +/* + * Copyright 2016 Canonical Ltd. + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 3, 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 warranties of + * MERCHANTABILITY, SATISFACTORY QUALITY, 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, see <http://www.gnu.org/licenses/>. + * + * Authors: + * Charles Kerr <charles.kerr@canonical.com> + */ + +#pragma once + +#include <functional> +#include <memory> +#include <string> + +#include <core/signal.h> + +/** + * Receives public key requests from ADBD and sends a response back. + * + * AdbClient only provides a receive/respond mechanism. The decision + * of what response gets sent is delegated out to a listener via + * the on_pk_request signal. + * + * The decider should connect to on_pk_request, listen for PKRequests, + * and call the request's `respond' method with the desired response. + */ +class AdbdClient +{ +public: + virtual ~AdbdClient(); + + enum class PKResponse { DENY, ALLOW }; + + struct PKRequest { + std::string public_key; + std::string fingerprint; + std::function<void(PKResponse)> respond; + }; + + virtual core::Signal<const PKRequest&>& on_pk_request() =0; + +protected: + AdbdClient() =default; +}; + +/** + * An AdbdClient designed to work with GLib's event loop. + * + * The on_pk_request() signal will be called in global GMainContext's thread; + * ie, just like a function passed to g_idle_add() or g_timeout_add(). + */ +class GAdbdClient: public AdbdClient +{ +public: + explicit GAdbdClient(const std::string& socket_path); + ~GAdbdClient(); + core::Signal<const PKRequest&>& on_pk_request() override; + +private: + class Impl; + std::unique_ptr<Impl> impl; +}; + diff --git a/src/dbus-names.h b/src/dbus-names.h new file mode 100644 index 0000000..b31098a --- /dev/null +++ b/src/dbus-names.h @@ -0,0 +1,60 @@ +/* + * Copyright 2016 Canonical Ltd. + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 3, 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 warranties of + * MERCHANTABILITY, SATISFACTORY QUALITY, 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, see <http://www.gnu.org/licenses/>. + * + * Authors: + * Charles Kerr <charles.kerr@canonical.com> + */ + +#pragma once + +namespace DBusNames +{ + namespace Notify + { + static constexpr char const * NAME = "org.freedesktop.Notifications"; + static constexpr char const * PATH = "/org/freedesktop/Notifications"; + static constexpr char const * INTERFACE = "org.freedesktop.Notifications"; + + namespace ActionInvoked + { + static constexpr char const * NAME = "ActionInvoked"; + } + + namespace NotificationClosed + { + static constexpr char const * NAME = "NotificationClosed"; + enum Reason { EXPIRED=1, DISMISSED=2, API=3, UNDEFINED=4 }; + } + } + + namespace UnityGreeter + { + static constexpr char const * NAME = "com.canonical.UnityGreeter"; + static constexpr char const * PATH = "/"; + static constexpr char const * INTERFACE = "com.canonical.UnityGreeter"; + } + + namespace Properties + { + static constexpr char const * INTERFACE = "org.freedesktop.DBus.Properties"; + + namespace PropertiesChanged + { + static constexpr char const* NAME = "PropertiesChanged"; + static constexpr char const* ARGS_VARIANT_TYPE = "(sa{sv}as)"; + } + } +} + diff --git a/src/greeter.cpp b/src/greeter.cpp new file mode 100644 index 0000000..f9cd965 --- /dev/null +++ b/src/greeter.cpp @@ -0,0 +1,161 @@ +/* + * Copyright 2016 Canonical Ltd. + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 3, 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 warranties of + * MERCHANTABILITY, SATISFACTORY QUALITY, 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, see <http://www.gnu.org/licenses/>. + * + * Authors: + * Charles Kerr <charles.kerr@canonical.com> + */ + +#include <src/dbus-names.h> +#include <src/greeter.h> + +#include <gio/gio.h> + +class UnityGreeter::Impl +{ +public: + + Impl(): + m_cancellable{g_cancellable_new()} + { + g_bus_get(G_BUS_TYPE_SESSION, m_cancellable, on_bus_ready_static, this); + } + + ~Impl() + { + g_cancellable_cancel(m_cancellable); + g_clear_object(&m_cancellable); + + if (m_subscription_id != 0) + g_dbus_connection_signal_unsubscribe (m_bus, m_subscription_id); + + g_clear_object(&m_bus); + } + + core::Property<bool>& is_active() + { + return m_is_active; + } + +private: + + static void on_bus_ready_static(GObject* /*source*/, GAsyncResult* res, gpointer gself) + { + GError* error {}; + auto bus = g_bus_get_finish (res, &error); + if (error != nullptr) { + if (!g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) + g_warning("UsbSnap: Error getting session bus: %s", error->message); + g_clear_error(&error); + } else { + static_cast<Impl*>(gself)->on_bus_ready(bus); + } + g_clear_object(&bus); + } + + void on_bus_ready(GDBusConnection* bus) + { + m_bus = G_DBUS_CONNECTION(g_object_ref(G_OBJECT(bus))); + + g_dbus_connection_call(m_bus, + DBusNames::UnityGreeter::NAME, + DBusNames::UnityGreeter::PATH, + DBusNames::Properties::INTERFACE, + "Get", + g_variant_new("(ss)", DBusNames::UnityGreeter::INTERFACE, "IsActive"), + G_VARIANT_TYPE("(v)"), + G_DBUS_CALL_FLAGS_NONE, + -1, + m_cancellable, + on_get_is_active_ready, + this); + + m_subscription_id = g_dbus_connection_signal_subscribe(m_bus, + DBusNames::UnityGreeter::NAME, + DBusNames::Properties::INTERFACE, + DBusNames::Properties::PropertiesChanged::NAME, + DBusNames::UnityGreeter::PATH, + DBusNames::UnityGreeter::INTERFACE, + G_DBUS_SIGNAL_FLAGS_NONE, + on_properties_changed_signal, + this, + nullptr); + } + + static void on_get_is_active_ready(GObject* source, GAsyncResult* res, gpointer gself) + { + GError* error {}; + auto v = g_dbus_connection_call_finish(G_DBUS_CONNECTION(source), res, &error); + if (error != nullptr) { + if (!g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) + g_warning("UsbSnap: Error getting session bus: %s", error->message); + g_clear_error(&error); + } else { + GVariant* is_active {}; + g_variant_get_child(v, 0, "v", &is_active); + static_cast<Impl*>(gself)->m_is_active.set(g_variant_get_boolean(is_active)); + g_clear_pointer(&is_active, g_variant_unref); + } + g_clear_pointer(&v, g_variant_unref); + } + + static void on_properties_changed_signal(GDBusConnection* /*connection*/, + const gchar* /*sender_name*/, + const gchar* object_path, + const gchar* interface_name, + const gchar* signal_name, + GVariant* parameters, + gpointer gself) + { + g_return_if_fail(!g_strcmp0(object_path, DBusNames::UnityGreeter::PATH)); + g_return_if_fail(!g_strcmp0(interface_name, DBusNames::Properties::INTERFACE)); + g_return_if_fail(!g_strcmp0(signal_name, DBusNames::Properties::PropertiesChanged::NAME)); + g_return_if_fail(g_variant_is_of_type(parameters, G_VARIANT_TYPE(DBusNames::Properties::PropertiesChanged::ARGS_VARIANT_TYPE))); + + auto v = g_variant_get_child_value (parameters, 1); + gboolean is_active {}; + if (g_variant_lookup(v, "IsActive", "b", &is_active)) + { + g_debug("%s is_active changed to %d", G_STRLOC, int(is_active)); + static_cast<Impl*>(gself)->m_is_active.set(is_active); + } + g_clear_pointer(&v, g_variant_unref); + } + + core::Property<bool> m_is_active; + GCancellable* m_cancellable {}; + GDBusConnection* m_bus {}; + unsigned int m_subscription_id {}; +}; + +/*** +**** +***/ + +Greeter::Greeter() =default; + +Greeter::~Greeter() =default; + +UnityGreeter::UnityGreeter(): + impl{new Impl{}} +{ +} + +UnityGreeter::~UnityGreeter() =default; + +core::Property<bool>& +UnityGreeter::is_active() +{ + return impl->is_active(); +} diff --git a/src/greeter.h b/src/greeter.h new file mode 100644 index 0000000..e084d25 --- /dev/null +++ b/src/greeter.h @@ -0,0 +1,47 @@ +/* + * Copyright 2016 Canonical Ltd. + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 3, 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 warranties of + * MERCHANTABILITY, SATISFACTORY QUALITY, 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, see <http://www.gnu.org/licenses/>. + * + * Authors: + * Charles Kerr <charles.kerr@canonical.com> + */ + +#pragma once + +#include <core/property.h> + +#include <memory> +#include <string> + +class Greeter +{ +public: + Greeter(); + virtual ~Greeter(); + virtual core::Property<bool>& is_active() =0; +}; + + +class UnityGreeter: public Greeter +{ +public: + UnityGreeter(); + virtual ~UnityGreeter(); + core::Property<bool>& is_active() override; + +protected: + class Impl; + std::unique_ptr<Impl> impl; +}; + diff --git a/src/indicator.cpp b/src/indicator.cpp new file mode 100644 index 0000000..77c4af7 --- /dev/null +++ b/src/indicator.cpp @@ -0,0 +1,37 @@ +/* + * Copyright 2016 Canonical Ltd. + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 3, 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 warranties of + * MERCHANTABILITY, SATISFACTORY QUALITY, 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, see <http://www.gnu.org/licenses/>. + * + * Authors: + * Charles Kerr <charles.kerr@canonical.com> + */ + +#include <src/indicator.h> + +Profile::Profile() +{ +} + +Profile::~Profile() +{ +} + +SimpleProfile::~SimpleProfile() +{ +} + +Indicator::~Indicator() +{ +} + diff --git a/src/indicator.h b/src/indicator.h index d0834fd..c55be79 100644 --- a/src/indicator.h +++ b/src/indicator.h @@ -1,5 +1,5 @@ /* - * Copyright 2014 Canonical Ltd. + * Copyright 2014-2016 Canonical Ltd. * * This program is free software: you can redistribute it and/or modify it * under the terms of the GNU General Public License version 3, as published @@ -17,8 +17,7 @@ * Charles Kerr <charles.kerr@canonical.com> */ -#ifndef INDICATOR_DISPLAY_INDICATOR_H -#define INDICATOR_DISPLAY_INDICATOR_H +#pragma once #include <core/property.h> @@ -52,10 +51,10 @@ public: virtual std::string name() const =0; virtual const core::Property<Header>& header() const =0; virtual std::shared_ptr<GMenuModel> menu_model() const =0; - virtual ~Profile() =default; + virtual ~Profile(); protected: - Profile() =default; + Profile(); }; @@ -63,6 +62,7 @@ class SimpleProfile: public Profile { public: SimpleProfile(const char* name, const std::shared_ptr<GMenuModel>& menu): m_name(name), m_menu(menu) {} + virtual ~SimpleProfile(); std::string name() const {return m_name;} core::Property<Header>& header() {return m_header;} @@ -79,11 +79,10 @@ protected: class Indicator { public: - virtual ~Indicator() =default; + virtual ~Indicator(); virtual const char* name() const =0; virtual GSimpleActionGroup* action_group() const =0; virtual std::vector<std::shared_ptr<Profile>> profiles() const =0; }; -#endif diff --git a/src/main.cpp b/src/main.cpp index 86bdeb3..52cdd58 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -20,6 +20,10 @@ #include <src/exporter.h> #include <src/rotation-lock.h> +#include <src/greeter.h> +#include <src/usb-manager.h> +#include <src/usb-monitor.h> + #include <glib/gi18n.h> // bindtextdomain() #include <gio/gio.h> @@ -54,6 +58,15 @@ main(int /*argc*/, char** /*argv*/) exporters.push_back(exporter); } + // We need the ADBD handler running, + // even though it doesn't have an indicator component yet + static constexpr char const * ADB_SOCKET_PATH {"/dev/socket/adbd"}; + static constexpr char const * PUBLIC_KEYS_FILENAME {"/data/misc/adb/adb_keys"}; + auto usb_monitor = std::make_shared<GUDevUsbMonitor>(); + auto greeter = std::make_shared<UnityGreeter>(); + UsbManager usb_manager {ADB_SOCKET_PATH, PUBLIC_KEYS_FILENAME, usb_monitor, greeter}; + + // let's go! g_main_loop_run(loop); // cleanup diff --git a/src/rotation-lock.cpp b/src/rotation-lock.cpp index f19ac9f..88c7e1b 100644 --- a/src/rotation-lock.cpp +++ b/src/rotation-lock.cpp @@ -43,6 +43,7 @@ public: ~Impl() { + g_signal_handlers_disconnect_by_data(m_settings, this); g_clear_object(&m_action_group); g_clear_object(&m_settings); } diff --git a/src/usb-manager.cpp b/src/usb-manager.cpp new file mode 100644 index 0000000..4d750c0 --- /dev/null +++ b/src/usb-manager.cpp @@ -0,0 +1,180 @@ +/* + * Copyright 2016 Canonical Ltd. + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 3, 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 warranties of + * MERCHANTABILITY, SATISFACTORY QUALITY, 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, see <http://www.gnu.org/licenses/>. + * + * Authors: + * Charles Kerr <charles.kerr@canonical.com> + */ + +#include <src/adbd-client.h> +#include <src/usb-manager.h> +#include <src/usb-snap.h> + +#include <glib.h> + +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <unistd.h> + +#include <set> + +class UsbManager::Impl +{ +public: + + explicit Impl( + const std::string& socket_path, + const std::string& public_keys_filename, + const std::shared_ptr<UsbMonitor>& usb_monitor, + const std::shared_ptr<Greeter>& greeter + ): + m_socket_path{socket_path}, + m_public_keys_filename{public_keys_filename}, + m_usb_monitor{usb_monitor}, + m_greeter{greeter} + { + m_usb_monitor->on_usb_disconnected().connect([this](const std::string& /*usb_name*/) { + restart(); + }); + + m_greeter->is_active().changed().connect([this](bool /*is_active*/) { + maybe_snap(); + }); + + restart(); + } + + ~Impl() + { + if (m_restart_idle_tag) + g_source_remove(m_restart_idle_tag); + + clear(); + } + +private: + + void clear() + { + // clear out old state + m_snap_connections.clear(); + m_snap.reset(); + m_req = decltype(m_req)(); + m_adbd_client.reset(); + } + + void restart() + { + clear(); + + // set a new client + m_adbd_client.reset(new GAdbdClient{m_socket_path}); + m_adbd_client->on_pk_request().connect( + [this](const AdbdClient::PKRequest& req) { + g_debug("%s got pk request: %s", G_STRLOC, req.fingerprint.c_str()); + m_req = req; + maybe_snap(); + } + ); + } + + void maybe_snap() + { + // don't prompt in the greeter! + if (!m_req.public_key.empty() && !m_greeter->is_active().get()) + snap(); + } + + void snap() + { + m_snap = std::make_shared<UsbSnap>(m_req.fingerprint); + m_snap_connections.insert((*m_snap).on_user_response().connect( + [this](AdbdClient::PKResponse response, bool remember_choice){ + g_debug("%s user responded! response %d, remember %d", G_STRLOC, int(response), int(remember_choice)); + m_req.respond(response); + if (remember_choice && (response == AdbdClient::PKResponse::ALLOW)) + write_public_key(m_req.public_key); + m_restart_idle_tag = g_idle_add([](gpointer gself){ + auto self = static_cast<Impl*>(gself); + self->m_restart_idle_tag = 0; + self->restart(); + return G_SOURCE_REMOVE; + }, this); + } + )); + } + + void write_public_key(const std::string& public_key) + { + g_debug("%s writing public key '%s' to '%s'", G_STRLOC, public_key.c_str(), m_public_keys_filename.c_str()); + + // confirm the directory exists + auto dirname = g_path_get_dirname(m_public_keys_filename.c_str()); + const auto dir_exists = g_file_test(dirname, G_FILE_TEST_IS_DIR); + if (!dir_exists) + g_warning("ADB data directory '%s' does not exist", dirname); + g_clear_pointer(&dirname, g_free); + if (!dir_exists) + return; + + // open the file in append mode, with user rw and group r permissions + const auto fd = open( + m_public_keys_filename.c_str(), + O_APPEND|O_CREAT|O_WRONLY, + S_IRUSR|S_IWUSR|S_IRGRP + ); + if (fd == -1) { + g_warning("Error opening ADB datafile: %s", g_strerror(errno)); + return; + } + + // write the new public key on its own line + std::string buf {public_key + '\n'}; + if (write(fd, buf.c_str(), buf.size()) == -1) + g_warning("Error writing ADB datafile: %d %s", errno, g_strerror(errno)); + close(fd); + } + + const std::string m_socket_path; + const std::string m_public_keys_filename; + const std::shared_ptr<UsbMonitor> m_usb_monitor; + const std::shared_ptr<Greeter> m_greeter; + + unsigned int m_restart_idle_tag {}; + + std::shared_ptr<GAdbdClient> m_adbd_client; + AdbdClient::PKRequest m_req; + std::shared_ptr<UsbSnap> m_snap; + std::set<core::ScopedConnection> m_snap_connections; +}; + +/*** +**** +***/ + +UsbManager::UsbManager( + const std::string& socket_path, + const std::string& public_keys_filename, + const std::shared_ptr<UsbMonitor>& usb_monitor, + const std::shared_ptr<Greeter>& greeter +): + impl{new Impl{socket_path, public_keys_filename, usb_monitor, greeter}} +{ +} + +UsbManager::~UsbManager() +{ +} + diff --git a/src/usb-manager.h b/src/usb-manager.h new file mode 100644 index 0000000..b93992f --- /dev/null +++ b/src/usb-manager.h @@ -0,0 +1,48 @@ +/* + * Copyright 2016 Canonical Ltd. + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 3, 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 warranties of + * MERCHANTABILITY, SATISFACTORY QUALITY, 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, see <http://www.gnu.org/licenses/>. + * + * Authors: + * Charles Kerr <charles.kerr@canonical.com> + */ + +#pragma once + +#include <src/greeter.h> +#include <src/usb-monitor.h> + +#include <memory> +#include <string> + +/** + * Manager class that connects the AdbdClient, UsbSnap, and manages the public key file + */ +class UsbManager +{ +public: + + UsbManager( + const std::string& socket_path, + const std::string& public_key_filename, + const std::shared_ptr<UsbMonitor>&, + const std::shared_ptr<Greeter>& + ); + + ~UsbManager(); + +protected: + + class Impl; + std::unique_ptr<Impl> impl; +}; diff --git a/src/usb-monitor.cpp b/src/usb-monitor.cpp new file mode 100644 index 0000000..5fc5a6d --- /dev/null +++ b/src/usb-monitor.cpp @@ -0,0 +1,81 @@ +/* + * Copyright 2016 Canonical Ltd. + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 3, 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 warranties of + * MERCHANTABILITY, SATISFACTORY QUALITY, 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, see <http://www.gnu.org/licenses/>. + * + * Authors: + * Charles Kerr <charles.kerr@canonical.com> + */ + +#include <src/usb-monitor.h> + +#include <glib.h> +#include <gudev/gudev.h> + +class GUDevUsbMonitor::Impl +{ +public: + + Impl() + { + const char* subsystems[] = {"android_usb", nullptr}; + m_udev_client = g_udev_client_new(subsystems); + g_signal_connect(m_udev_client, "uevent", G_CALLBACK(on_android_usb_event), this); + } + + ~Impl() + { + g_signal_handlers_disconnect_by_data(m_udev_client, this); + g_clear_object(&m_udev_client); + } + + core::Signal<const std::string&>& on_usb_disconnected() + { + return m_on_usb_disconnected; + } + +private: + + static void on_android_usb_event(GUdevClient*, gchar* action, GUdevDevice* device, gpointer gself) + { + if (!g_strcmp0(action, "change")) + if (!g_strcmp0(g_udev_device_get_property(device, "USB_STATE"), "DISCONNECTED")) + static_cast<Impl*>(gself)->m_on_usb_disconnected(g_udev_device_get_name(device)); + } + + core::Signal<const std::string&> m_on_usb_disconnected; + + GUdevClient* m_udev_client = nullptr; +}; + +/*** +**** +***/ + +UsbMonitor::UsbMonitor() =default; + +UsbMonitor::~UsbMonitor() =default; + +GUDevUsbMonitor::GUDevUsbMonitor(): + impl{new Impl{}} +{ +} + +GUDevUsbMonitor::~GUDevUsbMonitor() =default; + +core::Signal<const std::string&>& +GUDevUsbMonitor::on_usb_disconnected() +{ + return impl->on_usb_disconnected(); +} + diff --git a/src/usb-monitor.h b/src/usb-monitor.h new file mode 100644 index 0000000..d9be539 --- /dev/null +++ b/src/usb-monitor.h @@ -0,0 +1,52 @@ +/* + * Copyright 2016 Canonical Ltd. + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 3, 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 warranties of + * MERCHANTABILITY, SATISFACTORY QUALITY, 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, see <http://www.gnu.org/licenses/>. + * + * Authors: + * Charles Kerr <charles.kerr@canonical.com> + */ + +#pragma once + +#include <core/signal.h> + +#include <memory> +#include <string> + +/** + * Simple interface that emits signals on USB device state changes + */ +class UsbMonitor +{ +public: + UsbMonitor(); + virtual ~UsbMonitor(); + virtual core::Signal<const std::string&>& on_usb_disconnected() =0; +}; + +/** + * Simple GUDev wrapper that notifies on android_usb device state changes + */ +class GUDevUsbMonitor: public UsbMonitor +{ +public: + GUDevUsbMonitor(); + virtual ~GUDevUsbMonitor(); + core::Signal<const std::string&>& on_usb_disconnected() override; + +protected: + class Impl; + std::unique_ptr<Impl> impl; +}; + diff --git a/src/usb-snap.cpp b/src/usb-snap.cpp new file mode 100644 index 0000000..ba964fb --- /dev/null +++ b/src/usb-snap.cpp @@ -0,0 +1,250 @@ +/* + * Copyright 2016 Canonical Ltd. + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 3, 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 warranties of + * MERCHANTABILITY, SATISFACTORY QUALITY, 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, see <http://www.gnu.org/licenses/>. + * + * Authors: + * Charles Kerr <charles.kerr@canonical.com> + */ + +#include <src/dbus-names.h> +#include <src/usb-snap.h> + +#include <glib/gi18n.h> +#include <gio/gio.h> + +/*** +**** +***/ + +class UsbSnap::Impl +{ +public: + + explicit Impl(const std::string& fingerprint): + m_fingerprint{fingerprint}, + m_cancellable{g_cancellable_new()} + { + g_bus_get (G_BUS_TYPE_SESSION, m_cancellable, on_bus_ready_static, this); + } + + ~Impl() + { + g_cancellable_cancel(m_cancellable); + g_clear_object(&m_cancellable); + + if (m_subscription_id != 0) + g_dbus_connection_signal_unsubscribe (m_bus, m_subscription_id); + + if (m_notification_id != 0) { + GError* error {}; + g_dbus_connection_call_sync(m_bus, + DBusNames::Notify::NAME, + DBusNames::Notify::PATH, + DBusNames::Notify::INTERFACE, + "CloseNotification", + g_variant_new("(u)", m_notification_id), + nullptr, + G_DBUS_CALL_FLAGS_NONE, + -1, + nullptr, + &error); + if (error != nullptr) { + g_warning("Error closing notification: %s", error->message); + g_clear_error(&error); + } + } + + g_clear_object(&m_bus); + } + + core::Signal<AdbdClient::PKResponse,bool>& on_user_response() + { + return m_on_user_response; + } + +private: + + static void on_bus_ready_static(GObject* /*source*/, GAsyncResult* res, gpointer gself) + { + GError* error {}; + auto bus = g_bus_get_finish (res, &error); + if (error != nullptr) { + if (!g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) + g_warning("UsbSnap: Error getting session bus: %s", error->message); + g_clear_error(&error); + } else { + static_cast<Impl*>(gself)->on_bus_ready(bus); + } + g_clear_object(&bus); + } + + void on_bus_ready(GDBusConnection* bus) + { + m_bus = G_DBUS_CONNECTION(g_object_ref(G_OBJECT(bus))); + + m_subscription_id = g_dbus_connection_signal_subscribe(m_bus, + DBusNames::Notify::NAME, + DBusNames::Notify::INTERFACE, + nullptr, + DBusNames::Notify::PATH, + nullptr, + G_DBUS_SIGNAL_FLAGS_NONE, + on_notification_signal_static, + this, + nullptr); + + auto body = g_strdup_printf(_("The computer's RSA key fingerprint is: %s"), m_fingerprint.c_str()); + + GVariantBuilder actions_builder; + g_variant_builder_init(&actions_builder, G_VARIANT_TYPE_STRING_ARRAY); + g_variant_builder_add(&actions_builder, "s", ACTION_ALLOW); + g_variant_builder_add(&actions_builder, "s", _("Allow")); + g_variant_builder_add(&actions_builder, "s", ACTION_DENY); + g_variant_builder_add(&actions_builder, "s", _("Don't Allow")); + + GVariantBuilder hints_builder; + g_variant_builder_init(&hints_builder, G_VARIANT_TYPE_VARDICT); + g_variant_builder_add(&hints_builder, "{sv}", "x-canonical-non-shaped-icon", g_variant_new_string("true")); + g_variant_builder_add(&hints_builder, "{sv}", "x-canonical-snap-decisions", g_variant_new_string("true")); + g_variant_builder_add(&hints_builder, "{sv}", "x-canonical-private-affirmative-tint", g_variant_new_string("true")); + + auto args = g_variant_new("(susssasa{sv}i)", + "", + uint32_t(0), + "computer-symbolic", + _("Allow USB Debugging?"), + body, + &actions_builder, + &hints_builder, + -1); + g_dbus_connection_call(m_bus, + DBusNames::Notify::NAME, + DBusNames::Notify::PATH, + DBusNames::Notify::INTERFACE, + "Notify", + args, + G_VARIANT_TYPE("(u)"), + G_DBUS_CALL_FLAGS_NONE, + -1, // timeout + m_cancellable, + on_notify_reply_static, + this); + + g_clear_pointer(&body, g_free); + } + + static void on_notify_reply_static(GObject* obus, GAsyncResult* res, gpointer gself) + { + GError* error {}; + auto reply = g_dbus_connection_call_finish (G_DBUS_CONNECTION(obus), res, &error); + if (error != nullptr) { + if (!g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) + g_warning("UsbSnap: Error calling Notify: %s", error->message); + g_clear_error(&error); + } else { + uint32_t id {}; + g_variant_get(reply, "(u)", &id); + static_cast<Impl*>(gself)->on_notify_reply(id); + } + g_clear_pointer(&reply, g_variant_unref); + } + + void on_notify_reply(uint32_t id) + { + m_notification_id = id; + } + + static void on_notification_signal_static(GDBusConnection* /*connection*/, + const gchar* /*sender_name*/, + const gchar* object_path, + const gchar* interface_name, + const gchar* signal_name, + GVariant* parameters, + gpointer gself) + { + g_return_if_fail(!g_strcmp0(object_path, DBusNames::Notify::PATH)); + g_return_if_fail(!g_strcmp0(interface_name, DBusNames::Notify::INTERFACE)); + + auto self = static_cast<Impl*>(gself); + + if (!g_strcmp0(signal_name, DBusNames::Notify::ActionInvoked::NAME)) + { + uint32_t id {}; + const char* action_name {}; + g_variant_get(parameters, "(u&s)", &id, &action_name); + if (id == self->m_notification_id) + self->on_action_invoked(action_name); + } + else if (!g_strcmp0(signal_name, DBusNames::Notify::NotificationClosed::NAME)) + { + uint32_t id {}; + uint32_t close_reason {}; + g_variant_get(parameters, "(uu)", &id, &close_reason); + if (id == self->m_notification_id) + self->on_notification_closed(close_reason); + } + } + + void on_action_invoked(const char* action_name) + { + const auto response = !g_strcmp0(action_name, ACTION_ALLOW) + ? AdbdClient::PKResponse::ALLOW + : AdbdClient::PKResponse::DENY; + + // FIXME: the current default is to cover the most common use case. + // We need to get the notification ui's checkbox working ASAP so + // that the user can provide this flag + const bool remember_this_choice = response == AdbdClient::PKResponse::ALLOW; + + m_on_user_response(response, remember_this_choice); + } + + void on_notification_closed(uint32_t close_reason) + { + if (close_reason == DBusNames::Notify::NotificationClosed::Reason::EXPIRED) + m_on_user_response(AdbdClient::PKResponse::DENY, false); + + m_notification_id = 0; + } + + static constexpr char const * ACTION_ALLOW {"allow"}; + static constexpr char const * ACTION_DENY {"deny"}; + + const std::string m_fingerprint; + core::Signal<AdbdClient::PKResponse,bool> m_on_user_response; + GCancellable* m_cancellable {}; + GDBusConnection* m_bus {}; + uint32_t m_notification_id {}; + unsigned int m_subscription_id {}; +}; + +/*** +**** +***/ + +UsbSnap::UsbSnap(const std::string& public_key): + impl{new Impl{public_key}} +{ +} + +UsbSnap::~UsbSnap() +{ +} + +core::Signal<AdbdClient::PKResponse,bool>& +UsbSnap::on_user_response() +{ + return impl->on_user_response(); +} + diff --git a/src/usb-snap.h b/src/usb-snap.h new file mode 100644 index 0000000..94de394 --- /dev/null +++ b/src/usb-snap.h @@ -0,0 +1,42 @@ +/* + * Copyright 2016 Canonical Ltd. + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 3, 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 warranties of + * MERCHANTABILITY, SATISFACTORY QUALITY, 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, see <http://www.gnu.org/licenses/>. + * + * Authors: + * Charles Kerr <charles.kerr@canonical.com> + */ + +#pragma once + +#include <src/adbd-client.h> // AdbdClient::PKResponse + +#include <core/signal.h> + +#include <memory> +#include <string> + +/** + * A snap decision prompt for whether or not to allow an ADB connection + */ +class UsbSnap +{ +public: + explicit UsbSnap(const std::string& public_key); + ~UsbSnap(); + core::Signal<AdbdClient::PKResponse,bool>& on_user_response(); + +protected: + class Impl; + std::unique_ptr<Impl> impl; +}; |