| // Copyright (c) 2012 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 "permission_broker/permission_broker.h" |
| |
| #include <dbus/dbus-glib.h> |
| #include <dbus/dbus-glib-lowlevel.h> |
| #include <gflags/gflags.h> |
| #include <glib.h> |
| #include <grp.h> |
| #include <libudev.h> |
| #include <poll.h> |
| #include <stdint.h> |
| #include <sys/inotify.h> |
| #include <sys/types.h> |
| #include <unistd.h> |
| |
| #include <string> |
| #include <vector> |
| |
| #include "base/logging.h" |
| #include "base/stl_util.h" |
| #include "base/strings/stringprintf.h" |
| #include "chromeos/dbus/dbus.h" |
| #include "chromeos/dbus/service_constants.h" |
| #include "permission_broker/rule.h" |
| |
| DEFINE_string(access_group, "", "The group which has resource access granted " |
| "to it. Must not be empty."); |
| DEFINE_int32(poll_interval, 100, "The interval at which to poll for udev " |
| "events"); |
| |
| using std::string; |
| using std::vector; |
| |
| namespace permission_broker { |
| |
| PermissionBroker::PermissionBroker(const gid_t access_group) |
| : udev_(udev_new()), access_group_(access_group) {} |
| |
| PermissionBroker::PermissionBroker() : udev_(udev_new()) { |
| CHECK(udev_) << "Could not create udev context, is sysfs mounted?"; |
| CHECK(!FLAGS_access_group.empty()) << "You must specify a group name via the " |
| << "--access_group flag."; |
| |
| struct group *access_group = getgrnam(FLAGS_access_group.c_str()); |
| CHECK(access_group) << "Could not resolve \"" << FLAGS_access_group << "\" " |
| << "to a named group."; |
| access_group_ = access_group->gr_gid; |
| } |
| |
| PermissionBroker::~PermissionBroker() { |
| STLDeleteContainerPointers(rules_.begin(), rules_.end()); |
| udev_unref(udev_); |
| } |
| |
| void PermissionBroker::Run() { |
| DBusConnection *const connection = dbus_g_connection_get_connection( |
| chromeos::dbus::GetSystemBusConnection().g_connection()); |
| CHECK(connection) << "Cannot connect to system bus"; |
| |
| DBusError error; |
| dbus_error_init(&error); |
| dbus_bus_request_name(connection, |
| permission_broker::kPermissionBrokerServiceName, 0, &error); |
| if (dbus_error_is_set(&error)) { |
| LOG(FATAL) << "Failed to register " |
| << permission_broker::kPermissionBrokerServiceName |
| << ": " << error.message; |
| dbus_error_free(&error); |
| return; |
| } |
| |
| DBusObjectPathVTable vtable; |
| memset(&vtable, 0, sizeof(vtable)); |
| vtable.message_function = &MainDBusMethodHandler; |
| |
| const dbus_bool_t registration_result = dbus_connection_register_object_path( |
| connection, kPermissionBrokerServicePath, &vtable, this); |
| CHECK(registration_result) << "Could not register object path"; |
| |
| GMainLoop *const loop = g_main_loop_new(NULL, false); |
| g_main_loop_run(loop); |
| } |
| |
| void PermissionBroker::AddUsbException(const uint16_t vendor_id, |
| const uint16_t product_id) { |
| usb_exceptions_.insert(std::make_pair(vendor_id, product_id)); |
| } |
| |
| void PermissionBroker::AddRule(Rule *rule) { |
| CHECK(rule) << "Cannot add NULL as a rule."; |
| rules_.push_back(rule); |
| } |
| |
| bool PermissionBroker::ProcessPath(const string &path, |
| int interface_id) { |
| WaitForEmptyUdevQueue(); |
| |
| LOG(INFO) << "ProcessPath(" << path << ")"; |
| Rule::Result result = Rule::IGNORE; |
| for (unsigned int i = 0; i < rules_.size(); ++i) { |
| const Rule::Result rule_result = rules_[i]->Process(path, |
| interface_id); |
| LOG(INFO) << " " << rules_[i]->name() << ": " |
| << Rule::ResultToString(rule_result); |
| if (rule_result == Rule::DENY) |
| return false; |
| else if (rule_result == Rule::ALLOW) |
| result = Rule::ALLOW; |
| } |
| LOG(INFO) << "Verdict for " << path << ": " << Rule::ResultToString(result); |
| |
| if (result == Rule::ALLOW) |
| return GrantAccess(path); |
| return false; |
| } |
| |
| bool PermissionBroker::GrantAccess(const std::string &path) { |
| if (chown(path.c_str(), -1, access_group_)) { |
| LOG(INFO) << "Could not grant access to " << path; |
| return false; |
| } |
| return true; |
| } |
| |
| bool PermissionBroker::ExpandUsbIdentifiersToPaths( |
| const uint16_t vendor_id, |
| const uint16_t product_id, |
| vector<string> *paths) { |
| CHECK(paths) << "Cannot invoke ExpandUsbIdentifiersToPaths with NULL paths."; |
| paths->clear(); |
| |
| struct udev_enumerate *const enumerate = udev_enumerate_new(udev_); |
| udev_enumerate_add_match_is_initialized(enumerate); |
| udev_enumerate_add_match_subsystem(enumerate, "usb"); |
| udev_enumerate_add_match_sysattr( |
| enumerate, "idVendor", base::StringPrintf("%.4x", vendor_id).c_str()); |
| udev_enumerate_add_match_sysattr( |
| enumerate, "idProduct", base::StringPrintf("%.4x", product_id).c_str()); |
| udev_enumerate_scan_devices(enumerate); |
| |
| struct udev_list_entry *entry = NULL; |
| udev_list_entry_foreach(entry, udev_enumerate_get_list_entry(enumerate)) { |
| const char *const path = udev_list_entry_get_name(entry); |
| struct udev_device *device = udev_device_new_from_syspath(udev_, path); |
| paths->push_back(udev_device_get_devnode(device)); |
| udev_device_unref(device); |
| } |
| |
| udev_enumerate_unref(enumerate); |
| return !paths->empty(); |
| } |
| |
| void PermissionBroker::WaitForEmptyUdevQueue() { |
| struct udev_queue *queue = udev_queue_new(udev_); |
| if (udev_queue_get_queue_is_empty(queue)) { |
| udev_queue_unref(queue); |
| return; |
| } |
| |
| struct pollfd udev_poll; |
| memset(&udev_poll, 0, sizeof(udev_poll)); |
| udev_poll.fd = inotify_init(); |
| udev_poll.events = POLLIN; |
| |
| const char *run_path = udev_get_run_path(udev_); |
| int watch = inotify_add_watch(udev_poll.fd, run_path, IN_MOVED_TO); |
| CHECK(watch != -1) << "Could not add watch for udev run path."; |
| |
| while (!udev_queue_get_queue_is_empty(queue)) { |
| if (poll(&udev_poll, 1, FLAGS_poll_interval) > 0) { |
| char buffer[sizeof(struct inotify_event)]; |
| const ssize_t result = read(udev_poll.fd, buffer, sizeof(buffer)); |
| if (result < 0) |
| LOG(WARNING) << "Did not read complete udev event."; |
| } |
| } |
| udev_queue_unref(queue); |
| close(udev_poll.fd); |
| } |
| |
| DBusHandlerResult PermissionBroker::MainDBusMethodHandler( |
| DBusConnection *connection, DBusMessage *message, void *data) { |
| CHECK(connection) << "Missing connection."; |
| CHECK(message) << "Missing method."; |
| CHECK(data) << "Missing pointer to broker."; |
| |
| if (dbus_message_get_type(message) != DBUS_MESSAGE_TYPE_METHOD_CALL) |
| return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; |
| |
| string interface(dbus_message_get_interface(message)); |
| if (interface != kPermissionBrokerInterface) |
| return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; |
| |
| DBusMessage *reply = NULL; |
| string member(dbus_message_get_member(message)); |
| PermissionBroker *const broker = static_cast<PermissionBroker*>(data); |
| if (member == kRequestPathAccess) |
| reply = broker->HandleRequestPathAccessMethod(message); |
| else if (member == kRequestUsbAccess) |
| reply = broker->HandleRequestUsbAccessMethod(message); |
| else |
| return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; |
| |
| CHECK(dbus_connection_send(connection, reply, NULL)); |
| dbus_message_unref(reply); |
| |
| return DBUS_HANDLER_RESULT_HANDLED; |
| } |
| |
| DBusMessage *PermissionBroker::HandleRequestPathAccessMethod( |
| DBusMessage *message) { |
| DBusMessage *reply = dbus_message_new_method_return(message); |
| CHECK(reply) << "Could not allocate reply message for method call"; |
| |
| dbus_bool_t success = false; |
| char *path = NULL; |
| int interface_id = Rule::ANY_INTERFACE; |
| |
| DBusError error; |
| dbus_error_init(&error); |
| if (!dbus_message_get_args(message, &error, |
| DBUS_TYPE_STRING, &path, |
| DBUS_TYPE_INT32, &interface_id, |
| DBUS_TYPE_INVALID)) { |
| interface_id = Rule::ANY_INTERFACE; |
| if (!dbus_message_get_args(message, &error, |
| DBUS_TYPE_STRING, &path, |
| DBUS_TYPE_INVALID)) { |
| LOG(WARNING) << "Error parsing arguments: " << error.message; |
| dbus_error_free(&error); |
| |
| dbus_message_append_args(reply, |
| DBUS_TYPE_BOOLEAN, &success, |
| DBUS_TYPE_INVALID); |
| return reply; |
| } |
| } |
| |
| success = ProcessPath(path, interface_id); |
| dbus_message_append_args(reply, |
| DBUS_TYPE_BOOLEAN, &success, |
| DBUS_TYPE_INVALID); |
| return reply; |
| } |
| |
| DBusMessage *PermissionBroker::HandleRequestUsbAccessMethod( |
| DBusMessage *message) { |
| DBusMessage *reply = dbus_message_new_method_return(message); |
| CHECK(reply) << "Could not allocate reply message for method call"; |
| |
| dbus_bool_t success = false; |
| uint16_t vendor_id; |
| uint16_t product_id; |
| int interface_id = Rule::ANY_INTERFACE; |
| |
| DBusError error; |
| dbus_error_init(&error); |
| if (!dbus_message_get_args(message, &error, |
| DBUS_TYPE_UINT16, &vendor_id, |
| DBUS_TYPE_UINT16, &product_id, |
| DBUS_TYPE_INT32, &interface_id, |
| DBUS_TYPE_INVALID)) { |
| interface_id = Rule::ANY_INTERFACE; |
| if (!dbus_message_get_args(message, &error, |
| DBUS_TYPE_UINT16, &vendor_id, |
| DBUS_TYPE_UINT16, &product_id, |
| DBUS_TYPE_INVALID)) { |
| LOG(WARNING) << "Error parsing arguments: " << error.message; |
| dbus_error_free(&error); |
| |
| dbus_message_append_args(reply, |
| DBUS_TYPE_BOOLEAN, &success, |
| DBUS_TYPE_INVALID); |
| return reply; |
| } |
| } |
| |
| if (ContainsKey(usb_exceptions_, std::make_pair(vendor_id, product_id))) { |
| success = true; |
| } else { |
| vector<string> paths; |
| if (ExpandUsbIdentifiersToPaths(vendor_id, |
| product_id, |
| &paths)) { |
| success = true; |
| for (unsigned int i = 0; i < paths.size(); ++i) |
| success &= ProcessPath(paths[i], interface_id); |
| } else { |
| LOG(INFO) << "Could not expand (" << vendor_id << ", " << product_id |
| << ") to a list of device nodes."; |
| } |
| } |
| |
| dbus_message_append_args(reply, |
| DBUS_TYPE_BOOLEAN, &success, |
| DBUS_TYPE_INVALID); |
| return reply; |
| } |
| |
| } // namespace permission_broker |