/* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2006-2010 Nokia Corporation * Copyright (C) 2004-2010 Marcel Holtmann * * * 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 #endif #include #include #include #include #include #include #include #include #include #include #include #include "log.h" #include "error.h" #include "dbus-common.h" #include "adapter.h" #include "device.h" #include "manager.h" #include "agent.h" #define IO_CAPABILITY_DISPLAYONLY 0x00 #define IO_CAPABILITY_DISPLAYYESNO 0x01 #define IO_CAPABILITY_KEYBOARDONLY 0x02 #define IO_CAPABILITY_NOINPUTNOOUTPUT 0x03 #define IO_CAPABILITY_KEYBOARDDISPLAY 0x04 #define IO_CAPABILITY_INVALID 0xFF #define REQUEST_TIMEOUT (60 * 1000) /* 60 seconds */ #define AGENT_INTERFACE "org.bluez.Agent1" static GHashTable *agent_list; static struct agent *default_agent = NULL; typedef enum { AGENT_REQUEST_PASSKEY, AGENT_REQUEST_CONFIRMATION, AGENT_REQUEST_AUTHORIZATION, AGENT_REQUEST_PINCODE, AGENT_REQUEST_AUTHORIZE_SERVICE, AGENT_REQUEST_CONFIRM_MODE, AGENT_REQUEST_DISPLAY_PINCODE, } agent_request_type_t; struct agent { int ref; char *owner; char *path; uint8_t capability; struct agent_request *request; guint watch; }; struct agent_request { agent_request_type_t type; struct agent *agent; DBusMessage *msg; DBusPendingCall *call; void *cb; void *user_data; GDestroyNotify destroy; }; static void agent_release(struct agent *agent) { DBusMessage *message; DBG("Releasing agent %s, %s", agent->owner, agent->path); if (agent->request) agent_cancel(agent); message = dbus_message_new_method_call(agent->owner, agent->path, AGENT_INTERFACE, "Release"); if (message == NULL) { error("Couldn't allocate D-Bus message"); return; } g_dbus_send_message(btd_get_dbus_connection(), message); } static int send_cancel_request(struct agent_request *req) { DBusMessage *message; DBG("Sending Cancel request to %s, %s", req->agent->owner, req->agent->path); message = dbus_message_new_method_call(req->agent->owner, req->agent->path, AGENT_INTERFACE, "Cancel"); if (message == NULL) { error("Couldn't allocate D-Bus message"); return -ENOMEM; } g_dbus_send_message(btd_get_dbus_connection(), message); return 0; } static void agent_request_free(struct agent_request *req, gboolean destroy) { if (req->msg) dbus_message_unref(req->msg); if (req->call) dbus_pending_call_unref(req->call); if (req->agent && req->agent->request) req->agent->request = NULL; if (destroy && req->destroy) req->destroy(req->user_data); g_free(req); } static void set_io_cap(struct btd_adapter *adapter, gpointer user_data) { struct agent *agent = user_data; uint8_t io_cap; if (agent) io_cap = agent->capability; else io_cap = IO_CAPABILITY_NOINPUTNOOUTPUT; adapter_set_io_capability(adapter, io_cap); } static void set_default_agent(struct agent *agent) { if (default_agent == agent) return; default_agent = agent; manager_foreach_adapter(set_io_cap, agent); } static void agent_disconnect(DBusConnection *conn, void *user_data) { struct agent *agent = user_data; DBG("Agent %s disconnected", agent->owner); if (agent->watch > 0) { g_dbus_remove_watch(conn, agent->watch); agent->watch = 0; } if (agent == default_agent) set_default_agent(NULL); g_hash_table_remove(agent_list, agent->owner); } struct agent *agent_ref(struct agent *agent) { agent->ref++; DBG("%p: ref=%d", agent, agent->ref); return agent; } void agent_unref(struct agent *agent) { agent->ref--; DBG("%p: ref=%d", agent, agent->ref); if (agent->ref > 0) return; if (agent->request) { DBusError err; agent_pincode_cb pincode_cb; agent_passkey_cb passkey_cb; agent_cb cb; dbus_error_init(&err); dbus_set_error_const(&err, "org.bluez.Error.Failed", "Canceled"); switch (agent->request->type) { case AGENT_REQUEST_PINCODE: pincode_cb = agent->request->cb; pincode_cb(agent, &err, NULL, agent->request->user_data); break; case AGENT_REQUEST_PASSKEY: passkey_cb = agent->request->cb; passkey_cb(agent, &err, 0, agent->request->user_data); break; default: cb = agent->request->cb; cb(agent, &err, agent->request->user_data); } dbus_error_free(&err); agent_cancel(agent); } g_free(agent->owner); g_free(agent->path); g_free(agent); } struct agent *agent_get(const char *owner) { struct agent *agent; if (owner) { agent = g_hash_table_lookup(agent_list, owner); if (agent) return agent_ref(agent); } if (default_agent) return agent_ref(default_agent); return NULL; } static struct agent *agent_create( const char *name, const char *path, uint8_t capability) { struct agent *agent; agent = g_new0(struct agent, 1); agent->owner = g_strdup(name); agent->path = g_strdup(path); agent->capability = capability; agent->watch = g_dbus_add_disconnect_watch(btd_get_dbus_connection(), name, agent_disconnect, agent, NULL); return agent_ref(agent); } static struct agent_request *agent_request_new(struct agent *agent, agent_request_type_t type, void *cb, void *user_data, GDestroyNotify destroy) { struct agent_request *req; req = g_new0(struct agent_request, 1); req->agent = agent; req->type = type; req->cb = cb; req->user_data = user_data; req->destroy = destroy; return req; } int agent_cancel(struct agent *agent) { if (!agent->request) return -EINVAL; if (agent->request->call) { dbus_pending_call_cancel(agent->request->call); send_cancel_request(agent->request); } agent_request_free(agent->request, TRUE); agent->request = NULL; return 0; } static void simple_agent_reply(DBusPendingCall *call, void *user_data) { struct agent_request *req = user_data; struct agent *agent = req->agent; DBusMessage *message; DBusError err; agent_cb cb = req->cb; /* 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)) { error("Agent replied with an error: %s, %s", err.name, err.message); cb(agent, &err, req->user_data); if (dbus_error_has_name(&err, DBUS_ERROR_NO_REPLY)) { agent_cancel(agent); dbus_message_unref(message); dbus_error_free(&err); return; } dbus_error_free(&err); goto done; } if (!dbus_message_get_args(message, &err, DBUS_TYPE_INVALID)) { error("Wrong reply signature: %s", err.message); cb(agent, &err, req->user_data); dbus_error_free(&err); goto done; } cb(agent, NULL, req->user_data); done: dbus_message_unref(message); agent->request = NULL; agent_request_free(req, TRUE); } static int agent_call_authorize_service(struct agent_request *req, const char *device_path, const char *uuid) { struct agent *agent = req->agent; req->msg = dbus_message_new_method_call(agent->owner, agent->path, AGENT_INTERFACE, "AuthorizeService"); if (!req->msg) { error("Couldn't allocate D-Bus message"); return -ENOMEM; } dbus_message_append_args(req->msg, DBUS_TYPE_OBJECT_PATH, &device_path, DBUS_TYPE_STRING, &uuid, DBUS_TYPE_INVALID); if (dbus_connection_send_with_reply(btd_get_dbus_connection(), req->msg, &req->call, REQUEST_TIMEOUT) == FALSE) { error("D-Bus send failed"); return -EIO; } dbus_pending_call_set_notify(req->call, simple_agent_reply, req, NULL); return 0; } int agent_authorize_service(struct agent *agent, const char *path, const char *uuid, agent_cb cb, void *user_data, GDestroyNotify destroy) { struct agent_request *req; int err; if (agent->request) return -EBUSY; req = agent_request_new(agent, AGENT_REQUEST_AUTHORIZE_SERVICE, cb, user_data, destroy); err = agent_call_authorize_service(req, path, uuid); if (err < 0) { agent_request_free(req, FALSE); return -ENOMEM; } agent->request = req; DBG("authorize service request was sent for %s", path); return 0; } static void pincode_reply(DBusPendingCall *call, void *user_data) { struct agent_request *req = user_data; struct agent *agent = req->agent; agent_pincode_cb cb = req->cb; 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)) { error("Agent %s replied with an error: %s, %s", agent->path, err.name, err.message); cb(agent, &err, NULL, req->user_data); dbus_error_free(&err); goto done; } if (!dbus_message_get_args(message, &err, DBUS_TYPE_STRING, &pin, DBUS_TYPE_INVALID)) { error("Wrong passkey reply signature: %s", err.message); cb(agent, &err, NULL, req->user_data); dbus_error_free(&err); goto done; } len = strlen(pin); if (len > 16 || len < 1) { error("Invalid PIN length (%zu) from agent", len); dbus_set_error_const(&err, "org.bluez.Error.InvalidArgs", "Invalid passkey length"); cb(agent, &err, NULL, req->user_data); dbus_error_free(&err); goto done; } cb(agent, NULL, pin, req->user_data); done: if (message) dbus_message_unref(message); dbus_pending_call_cancel(req->call); agent->request = NULL; agent_request_free(req, TRUE); } static int pincode_request_new(struct agent_request *req, const char *device_path, dbus_bool_t secure) { struct agent *agent = req->agent; /* TODO: Add a new method or a new param to Agent interface to request secure pin. */ req->msg = dbus_message_new_method_call(agent->owner, agent->path, AGENT_INTERFACE, "RequestPinCode"); if (req->msg == NULL) { error("Couldn't allocate D-Bus message"); return -ENOMEM; } dbus_message_append_args(req->msg, DBUS_TYPE_OBJECT_PATH, &device_path, DBUS_TYPE_INVALID); if (dbus_connection_send_with_reply(btd_get_dbus_connection(), req->msg, &req->call, REQUEST_TIMEOUT) == FALSE) { error("D-Bus send failed"); return -EIO; } dbus_pending_call_set_notify(req->call, pincode_reply, req, NULL); return 0; } int agent_request_pincode(struct agent *agent, struct btd_device *device, agent_pincode_cb cb, gboolean secure, void *user_data, GDestroyNotify destroy) { struct agent_request *req; const gchar *dev_path = device_get_path(device); int err; if (agent->request) return -EBUSY; req = agent_request_new(agent, AGENT_REQUEST_PINCODE, cb, user_data, destroy); err = pincode_request_new(req, dev_path, secure); if (err < 0) goto failed; agent->request = req; return 0; failed: agent_request_free(req, FALSE); return err; } static void passkey_reply(DBusPendingCall *call, void *user_data) { struct agent_request *req = user_data; struct agent *agent = req->agent; agent_passkey_cb cb = req->cb; DBusMessage *message; DBusError err; uint32_t passkey; /* 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)) { error("Agent replied with an error: %s, %s", err.name, err.message); cb(agent, &err, 0, req->user_data); dbus_error_free(&err); goto done; } if (!dbus_message_get_args(message, &err, DBUS_TYPE_UINT32, &passkey, DBUS_TYPE_INVALID)) { error("Wrong passkey reply signature: %s", err.message); cb(agent, &err, 0, req->user_data); dbus_error_free(&err); goto done; } cb(agent, NULL, passkey, req->user_data); done: if (message) dbus_message_unref(message); dbus_pending_call_cancel(req->call); agent->request = NULL; agent_request_free(req, TRUE); } static int passkey_request_new(struct agent_request *req, const char *device_path) { struct agent *agent = req->agent; req->msg = dbus_message_new_method_call(agent->owner, agent->path, AGENT_INTERFACE, "RequestPasskey"); if (req->msg == NULL) { error("Couldn't allocate D-Bus message"); return -ENOMEM; } dbus_message_append_args(req->msg, DBUS_TYPE_OBJECT_PATH, &device_path, DBUS_TYPE_INVALID); if (dbus_connection_send_with_reply(btd_get_dbus_connection(), req->msg, &req->call, REQUEST_TIMEOUT) == FALSE) { error("D-Bus send failed"); return -EIO; } dbus_pending_call_set_notify(req->call, passkey_reply, req, NULL); return 0; } int agent_request_passkey(struct agent *agent, struct btd_device *device, agent_passkey_cb cb, void *user_data, GDestroyNotify destroy) { struct agent_request *req; const gchar *dev_path = device_get_path(device); int err; if (agent->request) return -EBUSY; DBG("Calling Agent.RequestPasskey: name=%s, path=%s", agent->owner, agent->path); req = agent_request_new(agent, AGENT_REQUEST_PASSKEY, cb, user_data, destroy); err = passkey_request_new(req, dev_path); if (err < 0) goto failed; agent->request = req; return 0; failed: agent_request_free(req, FALSE); return err; } static int confirmation_request_new(struct agent_request *req, const char *device_path, uint32_t passkey) { struct agent *agent = req->agent; req->msg = dbus_message_new_method_call(agent->owner, agent->path, AGENT_INTERFACE, "RequestConfirmation"); if (req->msg == NULL) { error("Couldn't allocate D-Bus message"); return -ENOMEM; } dbus_message_append_args(req->msg, DBUS_TYPE_OBJECT_PATH, &device_path, DBUS_TYPE_UINT32, &passkey, DBUS_TYPE_INVALID); if (dbus_connection_send_with_reply(btd_get_dbus_connection(), req->msg, &req->call, REQUEST_TIMEOUT) == FALSE) { error("D-Bus send failed"); return -EIO; } dbus_pending_call_set_notify(req->call, simple_agent_reply, req, NULL); return 0; } int agent_request_confirmation(struct agent *agent, struct btd_device *device, uint32_t passkey, agent_cb cb, void *user_data, GDestroyNotify destroy) { struct agent_request *req; const gchar *dev_path = device_get_path(device); int err; if (agent->request) return -EBUSY; DBG("Calling Agent.RequestConfirmation: name=%s, path=%s, passkey=%06u", agent->owner, agent->path, passkey); req = agent_request_new(agent, AGENT_REQUEST_CONFIRMATION, cb, user_data, destroy); err = confirmation_request_new(req, dev_path, passkey); if (err < 0) goto failed; agent->request = req; return 0; failed: agent_request_free(req, FALSE); return err; } static int authorization_request_new(struct agent_request *req, const char *device_path) { struct agent *agent = req->agent; req->msg = dbus_message_new_method_call(agent->owner, agent->path, AGENT_INTERFACE, "RequestAuthorization"); if (req->msg == NULL) { error("Couldn't allocate D-Bus message"); return -ENOMEM; } dbus_message_append_args(req->msg, DBUS_TYPE_OBJECT_PATH, &device_path, DBUS_TYPE_INVALID); if (dbus_connection_send_with_reply(btd_get_dbus_connection(), req->msg, &req->call, REQUEST_TIMEOUT) == FALSE) { error("D-Bus send failed"); return -EIO; } dbus_pending_call_set_notify(req->call, simple_agent_reply, req, NULL); return 0; } int agent_request_authorization(struct agent *agent, struct btd_device *device, agent_cb cb, void *user_data, GDestroyNotify destroy) { struct agent_request *req; const gchar *dev_path = device_get_path(device); int err; if (agent->request) return -EBUSY; DBG("Calling Agent.RequestAuthorization: name=%s, path=%s", agent->owner, agent->path); req = agent_request_new(agent, AGENT_REQUEST_AUTHORIZATION, cb, user_data, destroy); err = authorization_request_new(req, dev_path); if (err < 0) goto failed; agent->request = req; return 0; failed: agent_request_free(req, FALSE); return err; } int agent_display_passkey(struct agent *agent, struct btd_device *device, uint32_t passkey, uint16_t entered) { DBusMessage *message; const gchar *dev_path = device_get_path(device); message = dbus_message_new_method_call(agent->owner, agent->path, AGENT_INTERFACE, "DisplayPasskey"); if (!message) { error("Couldn't allocate D-Bus message"); return -1; } dbus_message_append_args(message, DBUS_TYPE_OBJECT_PATH, &dev_path, DBUS_TYPE_UINT32, &passkey, DBUS_TYPE_UINT16, &entered, DBUS_TYPE_INVALID); if (!g_dbus_send_message(btd_get_dbus_connection(), message)) { error("D-Bus send failed"); return -1; } return 0; } static void display_pincode_reply(DBusPendingCall *call, void *user_data) { struct agent_request *req = user_data; struct agent *agent = req->agent; DBusMessage *message; DBusError err; agent_cb cb = req->cb; /* clear agent->request early; our callback will likely try * another request */ agent->request = NULL; /* 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)) { error("Agent replied with an error: %s, %s", err.name, err.message); cb(agent, &err, req->user_data); if (dbus_error_has_name(&err, DBUS_ERROR_NO_REPLY)) { agent_cancel(agent); dbus_message_unref(message); dbus_error_free(&err); return; } dbus_error_free(&err); goto done; } if (!dbus_message_get_args(message, &err, DBUS_TYPE_INVALID)) { error("Wrong reply signature: %s", err.message); cb(agent, &err, req->user_data); dbus_error_free(&err); goto done; } cb(agent, NULL, req->user_data); done: dbus_message_unref(message); agent_request_free(req, TRUE); } static int display_pincode_request_new(struct agent_request *req, const char *device_path, const char *pincode) { struct agent *agent = req->agent; req->msg = dbus_message_new_method_call(agent->owner, agent->path, AGENT_INTERFACE, "DisplayPinCode"); if (req->msg == NULL) { error("Couldn't allocate D-Bus message"); return -ENOMEM; } dbus_message_append_args(req->msg, DBUS_TYPE_OBJECT_PATH, &device_path, DBUS_TYPE_STRING, &pincode, DBUS_TYPE_INVALID); if (dbus_connection_send_with_reply(btd_get_dbus_connection(), req->msg, &req->call, REQUEST_TIMEOUT) == FALSE) { error("D-Bus send failed"); return -EIO; } dbus_pending_call_set_notify(req->call, display_pincode_reply, req, NULL); return 0; } int agent_display_pincode(struct agent *agent, struct btd_device *device, const char *pincode, agent_cb cb, void *user_data, GDestroyNotify destroy) { struct agent_request *req; const gchar *dev_path = device_get_path(device); int err; if (agent->request) return -EBUSY; DBG("Calling Agent.DisplayPinCode: name=%s, path=%s, pincode=%s", agent->owner, agent->path, pincode); req = agent_request_new(agent, AGENT_REQUEST_DISPLAY_PINCODE, cb, user_data, destroy); err = display_pincode_request_new(req, dev_path, pincode); if (err < 0) goto failed; agent->request = req; return 0; failed: agent_request_free(req, FALSE); return err; } uint8_t agent_get_io_capability(struct agent *agent) { return agent->capability; } static void agent_destroy(gpointer data) { struct agent *agent = data; DBG("agent %s", agent->owner); if (agent->watch > 0) { g_dbus_remove_watch(btd_get_dbus_connection(), agent->watch); agent->watch = 0; agent_release(agent); } agent_unref(agent); } static uint8_t parse_io_capability(const char *capability) { if (g_str_equal(capability, "")) return IO_CAPABILITY_DISPLAYYESNO; if (g_str_equal(capability, "DisplayOnly")) return IO_CAPABILITY_DISPLAYONLY; if (g_str_equal(capability, "DisplayYesNo")) return IO_CAPABILITY_DISPLAYYESNO; if (g_str_equal(capability, "KeyboardOnly")) return IO_CAPABILITY_KEYBOARDONLY; if (g_str_equal(capability, "NoInputNoOutput")) return IO_CAPABILITY_NOINPUTNOOUTPUT; if (g_str_equal(capability, "KeyboardDisplay")) return IO_CAPABILITY_KEYBOARDDISPLAY; return IO_CAPABILITY_INVALID; } static DBusMessage *register_agent(DBusConnection *conn, DBusMessage *msg, void *user_data) { struct agent *agent; const char *sender, *path, *capability; uint8_t cap; sender = dbus_message_get_sender(msg); agent = g_hash_table_lookup(agent_list, sender); if (agent) return btd_error_already_exists(msg); if (dbus_message_get_args(msg, NULL, DBUS_TYPE_OBJECT_PATH, &path, DBUS_TYPE_STRING, &capability, DBUS_TYPE_INVALID) == FALSE) return btd_error_invalid_args(msg); cap = parse_io_capability(capability); agent = agent_create(sender, path, cap); if (!agent) return btd_error_invalid_args(msg); DBG("agent %s", agent->owner); g_hash_table_replace(agent_list, agent->owner, agent); return dbus_message_new_method_return(msg); } static DBusMessage *unregister_agent(DBusConnection *conn, DBusMessage *msg, void *user_data) { struct agent *agent; const char *sender, *path; sender = dbus_message_get_sender(msg); agent = g_hash_table_lookup(agent_list, sender); if (!agent) return btd_error_does_not_exist(msg); DBG("agent %s", agent->owner); if (dbus_message_get_args(msg, NULL, DBUS_TYPE_OBJECT_PATH, &path, DBUS_TYPE_INVALID) == FALSE) return btd_error_invalid_args(msg); if (g_str_equal(path, agent->path) == FALSE) return btd_error_does_not_exist(msg); agent_disconnect(conn, agent); return dbus_message_new_method_return(msg); } static DBusMessage *request_default(DBusConnection *conn, DBusMessage *msg, void *user_data) { struct agent *agent; const char *sender; sender = dbus_message_get_sender(msg); agent = g_hash_table_lookup(agent_list, sender); if (!agent) return btd_error_does_not_exist(msg); set_default_agent(agent); return dbus_message_new_method_return(msg); } static const GDBusMethodTable methods[] = { { GDBUS_METHOD("RegisterAgent", GDBUS_ARGS({ "agent", "o"}, { "capability", "s" }), NULL, register_agent) }, { GDBUS_METHOD("UnregisterAgent", GDBUS_ARGS({ "agent", "o" }), NULL, unregister_agent) }, { GDBUS_METHOD("RequestDefault", NULL, NULL, request_default ) }, { } }; void btd_agent_init(void) { agent_list = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, agent_destroy); g_dbus_register_interface(btd_get_dbus_connection(), "/org/bluez", "org.bluez.AgentManager1", methods, NULL, NULL, NULL, NULL); } void btd_agent_cleanup(void) { g_dbus_unregister_interface(btd_get_dbus_connection(), "/org/bluez", "org.bluez.AgentManager1"); set_default_agent(NULL); g_hash_table_destroy(agent_list); }