summaryrefslogtreecommitdiffstats
path: root/hcid/dbus-security.c
diff options
context:
space:
mode:
Diffstat (limited to 'hcid/dbus-security.c')
-rw-r--r--hcid/dbus-security.c1113
1 files changed, 1113 insertions, 0 deletions
diff --git a/hcid/dbus-security.c b/hcid/dbus-security.c
new file mode 100644
index 00000000..e84174bb
--- /dev/null
+++ b/hcid/dbus-security.c
@@ -0,0 +1,1113 @@
+/*
+ *
+ * BlueZ - Bluetooth protocol stack for Linux
+ *
+ * Copyright (C) 2006-2007 Nokia Corporation
+ * Copyright (C) 2004-2008 Marcel Holtmann <marcel@holtmann.org>
+ * Copyright (C) 2005-2007 Johan Hedberg <johan.hedberg@nokia.com>
+ *
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <limits.h>
+#include <sys/param.h>
+#include <sys/socket.h>
+#include <sys/ioctl.h>
+
+#include <bluetooth/bluetooth.h>
+#include <bluetooth/hci.h>
+#include <bluetooth/hci_lib.h>
+#include <bluetooth/sdp.h>
+
+#include <glib.h>
+#include <dbus/dbus.h>
+#include <gdbus.h>
+
+#include "adapter.h"
+#include "manager.h"
+#include "hcid.h"
+#include "dbus-common.h"
+#include "dbus-service.h"
+#include "error.h"
+#include "dbus-security.h"
+#include "dbus-hci.h"
+
+#define REQUEST_TIMEOUT (60 * 1000) /* 60 seconds */
+#define AGENT_TIMEOUT (10 * 60 * 1000) /* 10 minutes */
+
+struct passkey_agent {
+ struct adapter *adapter;
+ DBusConnection *conn;
+ char *addr;
+ char *name;
+ char *path;
+ GSList *pending_requests;
+ int exited;
+ guint timeout;
+ guint listener_id;
+};
+
+struct pending_agent_request {
+ struct passkey_agent *agent;
+ int dev;
+ bdaddr_t sba;
+ bdaddr_t bda;
+ char *path;
+ DBusPendingCall *call;
+ int old_if;
+ char *pin;
+};
+
+struct authorization_agent {
+ DBusConnection *conn;
+ char *name;
+ char *path;
+ GSList *pending_requests;
+ guint listener_id;
+};
+
+struct auth_agent_req {
+ struct authorization_agent *agent;
+ char *adapter_path;
+ char *address;
+ char *service_path;
+ char *uuid;
+ service_auth_cb cb;
+ void *user_data;
+ DBusPendingCall *call;
+};
+
+static struct passkey_agent *default_agent = NULL;
+static struct authorization_agent *default_auth_agent = NULL;
+
+static void release_agent(struct passkey_agent *agent);
+static void send_cancel_request(struct pending_agent_request *req);
+
+static void passkey_agent_free(struct passkey_agent *agent)
+{
+ GSList *l;
+
+ if (!agent)
+ return;
+
+ for (l = agent->pending_requests; l != NULL; l = l->next) {
+ struct pending_agent_request *req = l->data;
+ struct adapter *adapter = manager_find_adapter(&req->sba);
+
+ hci_send_cmd(req->dev, OGF_LINK_CTL,
+ OCF_PIN_CODE_NEG_REPLY, 6, &req->bda);
+
+ if (adapter)
+ adapter_auth_request_replied(adapter, &req->bda);
+
+ send_cancel_request(req);
+ }
+
+ if (agent->timeout)
+ g_source_remove(agent->timeout);
+
+ if (!agent->exited)
+ release_agent(agent);
+
+ g_free(agent->name);
+ g_free(agent->path);
+ g_free(agent->addr);
+
+ if (agent->conn)
+ dbus_connection_unref(agent->conn);
+
+ g_slist_free(agent->pending_requests);
+
+ g_free(agent);
+}
+
+static void agent_exited(void *user_data)
+{
+ struct passkey_agent *agent = user_data;
+ struct adapter *adapter = agent->adapter;
+
+ debug("Passkey agent exited without calling Unregister");
+
+ agent->exited = 1;
+
+ adapter->passkey_agents = g_slist_remove(adapter->passkey_agents, agent);
+ passkey_agent_free(agent);
+}
+
+static gboolean agent_timeout(struct passkey_agent *agent)
+{
+ struct adapter *adapter = agent->adapter;
+
+ debug("Passkey Agent at %s, %s timed out", agent->name, agent->path);
+
+ if (adapter)
+ adapter->passkey_agents = g_slist_remove(adapter->passkey_agents, agent);
+
+ agent->timeout = 0;
+
+ passkey_agent_free(agent);
+
+ return FALSE;
+}
+
+static void default_agent_exited(void *data)
+{
+ debug("D-Bus client exited without unregistering the"
+ " default passkey agent");
+
+ default_agent->exited = 1;
+
+ passkey_agent_free(default_agent);
+ default_agent = NULL;
+}
+
+static struct passkey_agent *passkey_agent_new(struct adapter *adapter, DBusConnection *conn,
+ const char *name, const char *path,
+ const char *addr)
+{
+ struct passkey_agent *agent;
+
+ agent = g_new0(struct passkey_agent, 1);
+
+ agent->adapter = adapter;
+
+ agent->name = g_strdup(name);
+ agent->path = g_strdup(path);
+
+ if (addr)
+ agent->addr = g_strdup(addr);
+
+ agent->conn = dbus_connection_ref(conn);
+
+ return agent;
+}
+
+static int agent_cmp(const struct passkey_agent *a, const struct passkey_agent *b)
+{
+ int ret;
+
+ if (b->name) {
+ if (!a->name)
+ return -1;
+ ret = strcmp(a->name, b->name);
+ if (ret)
+ return ret;
+ }
+
+ if (b->path) {
+ if (!a->path)
+ return -1;
+ ret = strcmp(a->path, b->path);
+ if (ret)
+ return ret;
+ }
+
+ if (b->addr) {
+ if (!a->addr)
+ return -1;
+ ret = strcmp(a->addr, b->addr);
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+}
+
+static inline DBusMessage *invalid_args(DBusMessage *msg)
+{
+ return g_dbus_create_error(msg, ERROR_INTERFACE ".InvalidArguments",
+ "Invalid arguments in method call");
+}
+
+static DBusMessage *register_passkey_agent(DBusConnection *conn,
+ DBusMessage *msg, void *data)
+{
+ struct passkey_agent *agent, ref;
+ struct adapter *adapter;
+ const char *path, *addr;
+
+ if (!data) {
+ error("register_passkey_agent called without any adapter info!");
+ return NULL;
+ }
+
+ adapter = data;
+
+ if (!dbus_message_get_args(msg, NULL,
+ DBUS_TYPE_STRING, &path,
+ DBUS_TYPE_STRING, &addr,
+ DBUS_TYPE_INVALID))
+ return invalid_args(msg);
+
+ if ((check_address(addr) < 0) || (path[0] != '/'))
+ return invalid_args(msg);
+
+ memset(&ref, 0, sizeof(ref));
+
+ ref.name = (char *) dbus_message_get_sender(msg);
+ ref.addr = (char *) addr;
+ ref.path = (char *) path;
+
+ if (g_slist_find_custom(adapter->passkey_agents, &ref, (GCompareFunc) agent_cmp))
+ return g_dbus_create_error(msg,
+ ERROR_INTERFACE ".AlreadyExists",
+ "Passkey agent already exists");
+
+ agent = passkey_agent_new(adapter, conn, ref.name, path, addr);
+ if (!agent)
+ return NULL;
+
+ /* Only add a name listener if there isn't one already for this name */
+ ref.addr = NULL;
+ ref.path = NULL;
+ if (!g_slist_find_custom(adapter->passkey_agents, &ref,
+ (GCompareFunc) agent_cmp))
+ agent->listener_id = g_dbus_add_disconnect_watch(conn, ref.name,
+ agent_exited, agent, NULL);
+
+ agent->timeout = g_timeout_add(AGENT_TIMEOUT,
+ (GSourceFunc) agent_timeout, agent);
+
+ adapter->passkey_agents = g_slist_append(adapter->passkey_agents, agent);
+
+ return dbus_message_new_method_return(msg);
+}
+
+static DBusMessage *unregister_passkey_agent(DBusConnection *conn,
+ DBusMessage *msg, void *data)
+{
+ struct adapter *adapter;
+ GSList *match;
+ struct passkey_agent ref, *agent;
+ const char *path, *addr;
+
+ if (!data) {
+ error("unregister_passkey_agent called without any adapter info!");
+ return NULL;
+ }
+
+ adapter = data;
+
+ if (!dbus_message_get_args(msg, NULL,
+ DBUS_TYPE_STRING, &path,
+ DBUS_TYPE_STRING, &addr,
+ DBUS_TYPE_INVALID))
+ return invalid_args(msg);
+
+ memset(&ref, 0, sizeof(ref));
+
+ ref.name = (char *) dbus_message_get_sender(msg);
+ ref.path = (char *) path;
+ ref.addr = (char *) addr;
+
+ match = g_slist_find_custom(adapter->passkey_agents, &ref, (GCompareFunc) agent_cmp);
+ if (!match)
+ return g_dbus_create_error(msg,
+ ERROR_INTERFACE ".DoesNotExist",
+ "Passkey agent does not exist");
+
+ agent = match->data;
+
+ g_dbus_remove_watch(agent->conn, agent->listener_id);
+
+ adapter->passkey_agents = g_slist_remove(adapter->passkey_agents, agent);
+ agent->exited = 1;
+ passkey_agent_free(agent);
+
+ return dbus_message_new_method_return(msg);
+}
+
+static DBusMessage *register_default_passkey_agent(DBusConnection *conn,
+ DBusMessage *msg, void *data)
+{
+ const char *path;
+
+ if (default_agent)
+ return g_dbus_create_error(msg,
+ ERROR_INTERFACE ".AlreadyExists",
+ "Passkey agent already exists");
+
+ if (!dbus_message_get_args(msg, NULL,
+ DBUS_TYPE_STRING, &path,
+ DBUS_TYPE_INVALID))
+ return invalid_args(msg);
+
+ default_agent = passkey_agent_new(NULL, conn, dbus_message_get_sender(msg),
+ path, NULL);
+ if (!default_agent)
+ goto need_memory;
+
+ default_agent->listener_id = g_dbus_add_disconnect_watch(conn,
+ default_agent->name,
+ default_agent_exited,
+ NULL, NULL);
+
+ info("Default passkey agent (%s, %s) registered",
+ default_agent->name, default_agent->path);
+
+ return dbus_message_new_method_return(msg);
+
+need_memory:
+ if (default_agent) {
+ default_agent->exited = 1;
+ passkey_agent_free(default_agent);
+ default_agent = NULL;
+ }
+
+ return NULL;
+}
+
+static DBusMessage *unregister_default_passkey_agent(DBusConnection *conn,
+ DBusMessage *msg, void *data)
+{
+ const char *path, *name;
+
+ if (!default_agent)
+ return g_dbus_create_error(msg,
+ ERROR_INTERFACE ".DoesNotExist",
+ "Passkey agent does not exist");
+
+ if (!dbus_message_get_args(msg, NULL,
+ DBUS_TYPE_STRING, &path,
+ DBUS_TYPE_INVALID))
+ return invalid_args(msg);
+
+ name = dbus_message_get_sender(msg);
+
+ if (strcmp(name, default_agent->name) || strcmp(path, default_agent->path))
+ return g_dbus_create_error(msg,
+ ERROR_INTERFACE ".DoesNotExist",
+ "Passkey agent does not exist");
+
+ g_dbus_remove_watch(default_agent->conn, default_agent->listener_id);
+
+ info("Default passkey agent (%s, %s) unregistered",
+ default_agent->name, default_agent->path);
+
+ default_agent->exited = 1;
+ passkey_agent_free(default_agent);
+ default_agent = NULL;
+
+ return dbus_message_new_method_return(msg);
+}
+
+static struct auth_agent_req *auth_agent_req_new(struct authorization_agent *agent,
+ const char *adapter_path,
+ const char *address,
+ const char *service_path,
+ const char *uuid,
+ service_auth_cb cb,
+ void *user_data)
+{
+ struct auth_agent_req *req;
+
+ req = g_new0(struct auth_agent_req, 1);
+
+ req->agent = agent;
+ req->adapter_path = g_strdup(adapter_path);
+ req->address = g_strdup(address);
+ req->service_path = g_strdup(service_path);
+ req->uuid = g_strdup(uuid);
+ req->cb = cb;
+ req->user_data = user_data;
+
+ return req;
+}
+
+static void auth_agent_req_free(struct auth_agent_req *req)
+{
+ g_free(req->adapter_path);
+ g_free(req->address);
+ g_free(req->service_path);
+ g_free(req->uuid);
+ if (req->call)
+ dbus_pending_call_unref(req->call);
+ g_free(req);
+}
+
+static void auth_agent_req_cancel(struct auth_agent_req *req)
+{
+ dbus_pending_call_cancel(req->call);
+}
+
+static void auth_agent_cancel_requests(struct authorization_agent *agent)
+{
+ GSList *l;
+
+ for (l = agent->pending_requests; l != NULL; l = l->next) {
+ struct auth_agent_req *req = l->data;
+ auth_agent_req_cancel(req);
+ auth_agent_req_free(req);
+ }
+}
+
+static void auth_agent_call_cancel(struct auth_agent_req *req)
+{
+ struct authorization_agent *agent = req->agent;
+ DBusMessage *message;
+
+ message = dbus_message_new_method_call(agent->name, agent->path,
+ "org.bluez.AuthorizationAgent", "Cancel");
+ if (!message) {
+ error("Couldn't allocate D-Bus message");
+ return;
+ }
+
+ dbus_message_append_args(message,
+ DBUS_TYPE_STRING, &req->adapter_path,
+ DBUS_TYPE_STRING, &req->address,
+ DBUS_TYPE_STRING, &req->service_path,
+ DBUS_TYPE_STRING, &req->uuid,
+ DBUS_TYPE_INVALID);
+
+ dbus_message_set_no_reply(message, TRUE);
+
+ dbus_connection_send(agent->conn, message, NULL);
+
+ dbus_message_unref(message);
+}
+
+static void auth_agent_free(struct authorization_agent *agent)
+{
+ g_free(agent->name);
+ g_free(agent->path);
+ dbus_connection_unref(agent->conn);
+ g_slist_free(agent->pending_requests);
+ g_free(agent);
+}
+
+static struct authorization_agent *auth_agent_new(DBusConnection *conn,
+ const char *name,
+ const char *path)
+{
+ struct authorization_agent *agent;
+
+ agent = g_new0(struct authorization_agent, 1);
+
+ agent->name = g_strdup(name);
+ agent->path = g_strdup(path);
+
+ agent->conn = dbus_connection_ref(conn);
+
+ return agent;
+}
+
+static void default_auth_agent_exited(void *data)
+{
+ debug("D-Bus client exited without unregistering the "
+ "default authorization agent");
+
+ auth_agent_cancel_requests(default_auth_agent);
+ auth_agent_free(default_auth_agent);
+ default_auth_agent = NULL;
+}
+
+static void auth_agent_release(struct authorization_agent *agent)
+{
+ DBusMessage *message;
+
+ debug("Releasing authorization agent %s, %s",
+ agent->name, agent->path);
+
+ message = dbus_message_new_method_call(agent->name, agent->path,
+ "org.bluez.AuthorizationAgent", "Release");
+ if (!message) {
+ error("Couldn't allocate D-Bus message");
+ return;
+ }
+
+ dbus_message_set_no_reply(message, TRUE);
+
+ dbus_connection_send(agent->conn, message, NULL);
+
+ dbus_message_unref(message);
+
+ if (agent == default_auth_agent)
+ g_dbus_remove_watch(agent->conn, agent->listener_id);
+}
+
+static DBusMessage *register_default_auth_agent(DBusConnection *conn,
+ DBusMessage *msg,
+ void *data)
+{
+ const char *path;
+
+ if (default_auth_agent)
+ return g_dbus_create_error(msg,
+ ERROR_INTERFACE ".AlreadyExists",
+ "Authorization agent already exists");
+
+ if (!dbus_message_get_args(msg, NULL,
+ DBUS_TYPE_STRING, &path,
+ DBUS_TYPE_INVALID))
+ return invalid_args(msg);
+
+ default_auth_agent = auth_agent_new(conn,
+ dbus_message_get_sender(msg), path);
+ if (!default_auth_agent)
+ goto need_memory;
+
+ default_auth_agent->listener_id = g_dbus_add_disconnect_watch(conn,
+ default_auth_agent->name,
+ default_auth_agent_exited,
+ NULL, NULL);
+
+ info("Default authorization agent (%s, %s) registered",
+ default_auth_agent->name, default_auth_agent->path);
+
+ return dbus_message_new_method_return(msg);
+
+need_memory:
+ if (default_auth_agent) {
+ auth_agent_free(default_auth_agent);
+ default_auth_agent = NULL;
+ }
+
+ return NULL;
+}
+
+static DBusMessage *unregister_default_auth_agent(DBusConnection *conn,
+ DBusMessage *msg,
+ void *data)
+{
+ const char *path, *name;
+
+ if (!default_auth_agent)
+ return g_dbus_create_error(msg,
+ ERROR_INTERFACE ".DoesNotExist",
+ "Authorization agent does not exist");
+
+ if (!dbus_message_get_args(msg, NULL,
+ DBUS_TYPE_STRING, &path,
+ DBUS_TYPE_INVALID))
+ return invalid_args(msg);
+
+ name = dbus_message_get_sender(msg);
+
+ if (strcmp(name, default_auth_agent->name) ||
+ strcmp(path, default_auth_agent->path))
+ return g_dbus_create_error(msg,
+ ERROR_INTERFACE ".DoesNotExist",
+ "Authorization agent does not exist");
+
+ g_dbus_remove_watch(default_auth_agent->conn,
+ default_auth_agent->listener_id);
+
+ info("Default authorization agent (%s, %s) unregistered",
+ default_auth_agent->name, default_auth_agent->path);
+
+ auth_agent_cancel_requests(default_auth_agent);
+ auth_agent_free(default_auth_agent);
+ default_auth_agent = NULL;
+
+ return dbus_message_new_method_return(msg);
+}
+
+static void auth_agent_req_reply(DBusPendingCall *call, void *data)
+{
+ struct auth_agent_req *req = data;
+ DBusMessage *reply = dbus_pending_call_steal_reply(call);
+ DBusError err;
+
+ debug("authorize reply");
+
+ dbus_error_init(&err);
+ dbus_set_error_from_message(&err, reply);
+ req->cb(&err, req->user_data);
+
+ default_auth_agent->pending_requests =
+ g_slist_remove(default_auth_agent->pending_requests, req);
+ auth_agent_req_free(req);
+
+ debug("auth_agent_reply: returning");
+}
+
+static DBusPendingCall *auth_agent_call_authorize(struct authorization_agent *agent,
+ const char *adapter_path,
+ const char *service_path,
+ const char *address,
+ const char *uuid)
+{
+ DBusMessage *message;
+ DBusPendingCall *call;
+
+ message = dbus_message_new_method_call(agent->name, agent->path,
+ "org.bluez.AuthorizationAgent", "Authorize");
+ if (!message) {
+ error("Couldn't allocate D-Bus message");
+ return NULL;
+ }
+
+ dbus_message_append_args(message,
+ DBUS_TYPE_STRING, &adapter_path,
+ DBUS_TYPE_STRING, &address,
+ DBUS_TYPE_STRING, &service_path,
+ DBUS_TYPE_STRING, &uuid,
+ DBUS_TYPE_INVALID);
+
+ if (dbus_connection_send_with_reply(agent->conn, message,
+ &call, REQUEST_TIMEOUT) == FALSE) {
+ error("D-Bus send failed");
+ dbus_message_unref(message);
+ return NULL;
+ }
+
+ dbus_message_unref(message);
+ return call;
+}
+
+int handle_authorize_request_old(struct service *service, const char *path,
+ const char *address, const char *uuid,
+ service_auth_cb cb, void *user_data)
+{
+ struct auth_agent_req *req;
+
+ if (!default_auth_agent) {
+ debug("no default agent");
+ return -EPERM;
+ }
+
+ req = auth_agent_req_new(default_auth_agent, path,
+ address, service->object_path,
+ uuid, cb, user_data);
+
+ req->call = auth_agent_call_authorize(default_auth_agent, path,
+ service->object_path, address, uuid);
+ if (!req->call) {
+ auth_agent_req_free(req);
+ return -ENOMEM;
+ }
+
+ dbus_pending_call_set_notify(req->call, auth_agent_req_reply, req,
+ NULL);
+ default_auth_agent->pending_requests =
+ g_slist_append(default_auth_agent->pending_requests, req);
+
+ debug("authorize request was forwarded");
+
+ return 0;
+}
+
+static int auth_agent_send_cancel(struct authorization_agent *agent,
+ const char *adapter_path,
+ const char *address)
+{
+ struct auth_agent_req *req = NULL;
+ GSList *l;
+
+ for (l = agent->pending_requests; l != NULL; l = l->next) {
+ req = l->data;
+ if (!strcmp(adapter_path, req->adapter_path) &&
+ !strcmp(address, req->address))
+ break;
+ }
+
+ if (!req)
+ return -EIO;
+
+ auth_agent_call_cancel(req);
+ auth_agent_req_cancel(req);
+ agent->pending_requests = g_slist_remove(agent->pending_requests, req);
+ auth_agent_req_free(req);
+
+ return 0;
+}
+
+int cancel_authorize_request_old(const char *path, const char *address)
+{
+ if (!default_auth_agent)
+ return -EIO;
+
+ return auth_agent_send_cancel(default_auth_agent, path, address);
+}
+
+static GDBusMethodTable security_methods[] = {
+ { "RegisterDefaultPasskeyAgent", "s", "",
+ register_default_passkey_agent },
+ { "UnregisterDefaultPasskeyAgent", "s", "",
+ unregister_default_passkey_agent},
+ { "RegisterPasskeyAgent", "ss", "",
+ register_passkey_agent },
+ { "UnregisterPasskeyAgent", "ss", "",
+ unregister_passkey_agent },
+ { "RegisterDefaultAuthorizationAgent", "s", "",
+ register_default_auth_agent },
+ { "UnregisterDefaultAuthorizationAgent","s", "",
+ unregister_default_auth_agent },
+ { }
+};
+
+dbus_bool_t security_init(DBusConnection *conn, const char *path)
+{
+ return g_dbus_register_interface(conn, path, SECURITY_INTERFACE,
+ security_methods, NULL, NULL, NULL, NULL);
+}
+
+dbus_bool_t security_cleanup(DBusConnection *conn, const char *path)
+{
+ return g_dbus_unregister_interface(conn, path, SECURITY_INTERFACE);
+}
+
+static DBusPendingCall *agent_request(const char *path, bdaddr_t *bda,
+ struct passkey_agent *agent,
+ dbus_bool_t numeric, int old_if)
+{
+ DBusMessage *message;
+ DBusPendingCall *call;
+ char bda_str[18], *ptr = bda_str;
+
+ message = dbus_message_new_method_call(agent->name, agent->path,
+ "org.bluez.PasskeyAgent", "Request");
+ if (message == NULL) {
+ error("Couldn't allocate D-Bus message");
+ return NULL;
+ }
+
+ ba2str(bda, bda_str);
+
+ if (old_if)
+ dbus_message_append_args(message,
+ DBUS_TYPE_STRING, &path,
+ DBUS_TYPE_STRING, &ptr,
+ DBUS_TYPE_INVALID);
+ else
+ dbus_message_append_args(message,
+ DBUS_TYPE_STRING, &path,
+ DBUS_TYPE_STRING, &ptr,
+ DBUS_TYPE_BOOLEAN, &numeric,
+ DBUS_TYPE_INVALID);
+
+ if (dbus_connection_send_with_reply(agent->conn, message,
+ &call, REQUEST_TIMEOUT) == FALSE) {
+ error("D-Bus send failed");
+ dbus_message_unref(message);
+ return NULL;
+ }
+
+ dbus_message_unref(message);
+ return call;
+}
+
+static void passkey_agent_reply(DBusPendingCall *call, void *user_data)
+{
+ struct pending_agent_request *req = user_data;
+ struct passkey_agent *agent = req->agent;
+ struct adapter *adapter = manager_find_adapter(&req->sba);
+ pin_code_reply_cp pr;
+ DBusMessage *message;
+ DBusError err;
+ size_t len;
+ char *pin;
+
+ /* steal_reply will always return non-NULL since the callback
+ * is only called after a reply has been received */
+ message = dbus_pending_call_steal_reply(call);
+
+ dbus_error_init(&err);
+ if (dbus_set_error_from_message(&err, message)) {
+ if (!req->old_if && !strcmp(err.name, DBUS_ERROR_UNKNOWN_METHOD)) {
+ debug("New Request API failed, trying old one");
+ req->old_if = 1;
+ dbus_error_free(&err);
+ dbus_pending_call_unref(req->call);
+ req->call = agent_request(req->path, &req->bda, agent,
+ FALSE, 1);
+ if (!req->call)
+ goto fail;
+
+ dbus_message_unref(message);
+
+ dbus_pending_call_set_notify(req->call,
+ passkey_agent_reply,
+ req, NULL);
+ return;
+ }
+
+ error("Passkey agent replied with an error: %s, %s",
+ err.name, err.message);
+
+ dbus_error_free(&err);
+ goto fail;
+ }
+
+ dbus_error_init(&err);
+ if (!dbus_message_get_args(message, &err,
+ DBUS_TYPE_STRING, &pin,
+ DBUS_TYPE_INVALID)) {
+ error("Wrong passkey reply signature: %s", err.message);
+ dbus_error_free(&err);
+ goto fail;
+ }
+
+ len = strlen(pin);
+
+ if (len > 16 || len < 1) {
+ error("Invalid passkey length from handler");
+ goto fail;
+ }
+
+ set_pin_length(&req->sba, len);
+
+ memset(&pr, 0, sizeof(pr));
+ bacpy(&pr.bdaddr, &req->bda);
+ memcpy(pr.pin_code, pin, len);
+ pr.pin_len = len;
+ hci_send_cmd(req->dev, OGF_LINK_CTL,
+ OCF_PIN_CODE_REPLY, PIN_CODE_REPLY_CP_SIZE, &pr);
+
+ goto done;
+
+fail:
+ hci_send_cmd(req->dev, OGF_LINK_CTL,
+ OCF_PIN_CODE_NEG_REPLY, 6, &req->bda);
+
+done:
+ if (adapter)
+ adapter_auth_request_replied(adapter, &req->bda);
+
+ if (message)
+ dbus_message_unref(message);
+
+ agent->pending_requests = g_slist_remove(agent->pending_requests, req);
+ dbus_pending_call_cancel(req->call);
+ if (req->call)
+ dbus_pending_call_unref(req->call);
+ g_free(req->path);
+ g_free(req);
+
+ if (agent != default_agent) {
+ agent->adapter->passkey_agents = g_slist_remove(agent->adapter->passkey_agents,
+ agent);
+ passkey_agent_free(agent);
+ }
+}
+
+static int call_passkey_agent(DBusConnection *conn,
+ struct passkey_agent *agent, int dev,
+ const char *path, bdaddr_t *sba,
+ bdaddr_t *dba)
+{
+ struct pending_agent_request *req;
+ struct adapter *adapter = manager_find_adapter(sba);
+
+ if (!agent) {
+ debug("call_passkey_agent(): no agent available");
+ goto send;
+ }
+
+ debug("Calling PasskeyAgent.Request: name=%s, path=%s",
+ agent->name, agent->path);
+
+ req = g_new0(struct pending_agent_request, 1);
+ req->dev = dev;
+ bacpy(&req->sba, sba);
+ bacpy(&req->bda, dba);
+ req->agent = agent;
+ req->path = g_strdup(path);
+
+ req->call = agent_request(path, dba, agent, FALSE, 0);
+ if (!req->call)
+ goto failed;
+
+ dbus_pending_call_set_notify(req->call, passkey_agent_reply, req, NULL);
+
+ agent->pending_requests = g_slist_append(agent->pending_requests, req);
+
+ return 0;
+
+failed:
+ g_free(req->path);
+ g_free(req);
+
+send:
+ hci_send_cmd(dev, OGF_LINK_CTL, OCF_PIN_CODE_NEG_REPLY, 6, dba);
+
+ if (adapter)
+ adapter_auth_request_replied(adapter, dba);
+
+ return -1;
+}
+
+int handle_passkey_request_old(DBusConnection *conn, int dev,
+ struct adapter *adapter,
+ bdaddr_t *sba, bdaddr_t *dba)
+{
+ struct passkey_agent *agent = default_agent;
+ GSList *l;
+ char addr[18];
+
+ ba2str(dba, addr);
+
+ for (l = adapter->passkey_agents; l != NULL; l = l->next) {
+ struct passkey_agent *a = l->data;
+ if (a != default_agent && g_slist_length(a->pending_requests) >= 1)
+ continue;
+ if (!strcmp(a->addr, addr)) {
+ agent = a;
+ break;
+ }
+ }
+
+ return call_passkey_agent(conn, agent, dev, adapter->path, sba, dba);
+}
+
+static void send_cancel_request(struct pending_agent_request *req)
+{
+ DBusMessage *message;
+ char address[18], *ptr = address;
+
+ message = dbus_message_new_method_call(req->agent->name, req->agent->path,
+ "org.bluez.PasskeyAgent", "Cancel");
+ if (message == NULL) {
+ error("Couldn't allocate D-Bus message");
+ return;
+ }
+
+ ba2str(&req->bda, address);
+
+ dbus_message_append_args(message,
+ DBUS_TYPE_STRING, &req->path,
+ DBUS_TYPE_STRING, &ptr,
+ DBUS_TYPE_INVALID);
+
+ dbus_message_set_no_reply(message, TRUE);
+
+ dbus_connection_send(req->agent->conn, message, NULL);
+
+ dbus_message_unref(message);
+
+ debug("PasskeyAgent.Request(%s, %s) was canceled", req->path, address);
+
+ dbus_pending_call_cancel(req->call);
+ dbus_pending_call_unref(req->call);
+ g_free(req->pin);
+ g_free(req->path);
+ g_free(req);
+}
+
+static void release_agent(struct passkey_agent *agent)
+{
+ DBusMessage *message;
+
+ debug("Releasing agent %s, %s", agent->name, agent->path);
+
+ message = dbus_message_new_method_call(agent->name, agent->path,
+ "org.bluez.PasskeyAgent", "Release");
+ if (message == NULL) {
+ error("Couldn't allocate D-Bus message");
+ return;
+ }
+
+ dbus_message_set_no_reply(message, TRUE);
+
+ dbus_connection_send(agent->conn, message, NULL);
+
+ dbus_message_unref(message);
+
+ if (agent == default_agent)
+ g_dbus_remove_watch(agent->conn, agent->listener_id);
+ else {
+ struct passkey_agent ref;
+
+ /* Only remove the name listener if there are no more agents
+ * for this name */
+ memset(&ref, 0, sizeof(ref));
+ ref.name = agent->name;
+ if (!g_slist_find_custom(agent->adapter->passkey_agents, &ref,
+ (GCompareFunc) agent_cmp))
+ g_dbus_remove_watch(agent->conn, agent->listener_id);
+ }
+}
+
+void release_default_agent_old(void)
+{
+ if (!default_agent)
+ return;
+
+ passkey_agent_free(default_agent);
+ default_agent = NULL;
+}
+
+void release_default_auth_agent(void)
+{
+ if (!default_auth_agent)
+ return;
+
+ auth_agent_cancel_requests(default_auth_agent);
+ auth_agent_release(default_auth_agent);
+
+ auth_agent_free(default_auth_agent);
+ default_auth_agent = NULL;
+}
+
+void release_passkey_agents(struct adapter *adapter, bdaddr_t *bda)
+{
+ GSList *l, *next;
+
+ for (l = adapter->passkey_agents; l != NULL; l = next) {
+ struct passkey_agent *agent = l->data;
+ next = l->next;
+
+ if (bda && agent->addr) {
+ bdaddr_t tmp;
+ str2ba(agent->addr, &tmp);
+ if (bacmp(&tmp, bda))
+ continue;
+ }
+
+ adapter->passkey_agents = g_slist_remove(adapter->passkey_agents, agent);
+ passkey_agent_free(agent);
+ }
+}
+
+void cancel_passkey_agent_requests(GSList *agents, const char *path,
+ bdaddr_t *addr)
+{
+ GSList *l, *next;
+
+ /* First check the default agent */
+ for (l = default_agent ? default_agent->pending_requests : NULL; l != NULL; l = next) {
+ struct pending_agent_request *req = l->data;
+ next = l->next;
+ if (!strcmp(path, req->path) && (!addr || !bacmp(addr, &req->bda))) {
+ send_cancel_request(req);
+ default_agent->pending_requests = g_slist_remove(default_agent->pending_requests,
+ req);
+ }
+ }
+
+ /* and then the adapter specific agents */
+ for (; agents != NULL; agents = agents->next) {
+ struct passkey_agent *agent = agents->data;
+
+ for (l = agent->pending_requests; l != NULL; l = next) {
+ struct pending_agent_request *req = l->data;
+ next = l->next;
+ if (!strcmp(path, req->path) && (!addr || !bacmp(addr, &req->bda))) {
+ send_cancel_request(req);
+ agent->pending_requests = g_slist_remove(agent->pending_requests, req);
+ }
+ }
+ }
+}