blob: 18c9b68e3fb07388f00dc26cb6e0a5f056aee8d6 [file] [log] [blame]
/*
*
* BlueZ - Bluetooth protocol stack for Linux
*
* Copyright (C) 2006-2007 Nokia Corporation
* Copyright (C) 2004-2008 Marcel Holtmann <marcel@holtmann.org>
*
*
* 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 <unistd.h>
#include <stdlib.h>
#include <dirent.h>
#include <signal.h>
#include <ctype.h>
#include <sys/types.h>
#include <sys/wait.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 "hcid.h"
#include "server.h"
#include "dbus-common.h"
#include "error.h"
#include "manager.h"
#include "adapter.h"
#include "agent.h"
#include "device.h"
#include "dbus-service.h"
#include "dbus-hci.h"
#include "dbus-security.h"
#define SERVICE_INTERFACE "org.bluez.Service"
struct service_uuids {
char *name;
char **uuids;
};
struct service_auth {
service_auth_cb cb;
void *user_data;
};
static GSList *services = NULL;
static GSList *services_uuids = NULL;
static void service_free(struct service *service)
{
if (!service)
return;
g_free(service->object_path);
g_free(service->ident);
g_free(service->name);
g_free(service);
}
static DBusMessage *get_info(DBusConnection *conn,
DBusMessage *msg, void *data)
{
struct service *service = data;
DBusMessage *reply;
DBusMessageIter iter;
DBusMessageIter dict;
reply = dbus_message_new_method_return(msg);
if (!reply)
return NULL;
dbus_message_iter_init_append(reply, &iter);
dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY,
DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING
DBUS_TYPE_STRING_AS_STRING DBUS_TYPE_VARIANT_AS_STRING
DBUS_DICT_ENTRY_END_CHAR_AS_STRING, &dict);
dbus_message_iter_append_dict_entry(&dict, "identifier",
DBUS_TYPE_STRING, &service->ident);
dbus_message_iter_append_dict_entry(&dict, "name",
DBUS_TYPE_STRING, &service->name);
dbus_message_iter_close_container(&iter, &dict);
return reply;
}
static DBusMessage *get_identifier(DBusConnection *conn,
DBusMessage *msg, void *data)
{
struct service *service = data;
DBusMessage *reply;
const char *identifier = "";
reply = dbus_message_new_method_return(msg);
if (!reply)
return NULL;
if (service->ident)
identifier = service->ident;
dbus_message_append_args(reply, DBUS_TYPE_STRING, &identifier,
DBUS_TYPE_INVALID);
return reply;
}
static DBusMessage *get_name(DBusConnection *conn,
DBusMessage *msg, void *data)
{
struct service *service = data;
DBusMessage *reply;
const char *name = "";
reply = dbus_message_new_method_return(msg);
if (!reply)
return NULL;
if (service->name)
name = service->name;
dbus_message_append_args(reply, DBUS_TYPE_STRING, &name,
DBUS_TYPE_INVALID);
return reply;
}
static DBusMessage *get_description(DBusConnection *conn,
DBusMessage *msg, void *data)
{
DBusMessage *reply;
const char *description = "";
reply = dbus_message_new_method_return(msg);
if (!reply)
return NULL;
dbus_message_append_args(reply, DBUS_TYPE_STRING, &description,
DBUS_TYPE_INVALID);
return reply;
}
static DBusMessage *get_bus_name(DBusConnection *conn,
DBusMessage *msg, void *data)
{
DBusMessage *reply;
const char *busname = "org.bluez";
reply = dbus_message_new_method_return(msg);
if (!reply)
return NULL;
dbus_message_append_args(reply, DBUS_TYPE_STRING, &busname,
DBUS_TYPE_INVALID);
return reply;
}
static DBusMessage *start(DBusConnection *conn,
DBusMessage *msg, void *data)
{
return g_dbus_create_error(msg,
ERROR_INTERFACE ".Failed",
strerror(EALREADY));
}
static DBusMessage *stop(DBusConnection *conn,
DBusMessage *msg, void *data)
{
return g_dbus_create_error(msg,
ERROR_INTERFACE ".Failed",
strerror(EPERM));
}
static DBusMessage *is_running(DBusConnection *conn,
DBusMessage *msg, void *data)
{
DBusMessage *reply;
dbus_bool_t running = TRUE;
reply = dbus_message_new_method_return(msg);
if (!reply)
return NULL;
dbus_message_append_args(reply,
DBUS_TYPE_BOOLEAN, &running,
DBUS_TYPE_INVALID);
return reply;
}
static DBusMessage *is_external(DBusConnection *conn,
DBusMessage *msg, void *data)
{
DBusMessage *reply;
dbus_bool_t external = TRUE;
reply = dbus_message_new_method_return(msg);
if (!reply)
return NULL;
dbus_message_append_args(reply,
DBUS_TYPE_BOOLEAN, &external,
DBUS_TYPE_INVALID);
return reply;
}
static inline DBusMessage *invalid_args(DBusMessage *msg)
{
return g_dbus_create_error(msg,
ERROR_INTERFACE ".InvalidArguments",
"Invalid arguments in method call");
}
static DBusMessage *set_trusted(DBusConnection *conn,
DBusMessage *msg, void *data)
{
struct service *service = data;
const char *address;
if (!dbus_message_get_args(msg, NULL,
DBUS_TYPE_STRING, &address,
DBUS_TYPE_INVALID))
return invalid_args(msg);
if (check_address(address) < 0)
return invalid_args(msg);
write_trust(BDADDR_ANY, address, service->ident, TRUE);
g_dbus_emit_signal(conn, service->object_path,
SERVICE_INTERFACE, "TrustAdded",
DBUS_TYPE_STRING, &address,
DBUS_TYPE_INVALID);
return dbus_message_new_method_return(msg);
}
static DBusMessage *list_trusted(DBusConnection *conn,
DBusMessage *msg, void *data)
{
struct service *service = data;
DBusMessage *reply;
GSList *trusts, *l;
char **addrs;
int len;
reply = dbus_message_new_method_return(msg);
if (!reply)
return NULL;
trusts = list_trusts(BDADDR_ANY, service->ident);
addrs = g_new(char *, g_slist_length(trusts));
for (l = trusts, len = 0; l; l = l->next, len++)
addrs[len] = l->data;
dbus_message_append_args(reply,
DBUS_TYPE_ARRAY, DBUS_TYPE_STRING,
&addrs, len, DBUS_TYPE_INVALID);
g_free(addrs);
g_slist_foreach(trusts, (GFunc) g_free, NULL);
g_slist_free(trusts);
return reply;
}
static DBusMessage *is_trusted(DBusConnection *conn,
DBusMessage *msg, void *data)
{
struct service *service = data;
DBusMessage *reply;
const char *address;
dbus_bool_t trusted;
if (!dbus_message_get_args(msg, NULL,
DBUS_TYPE_STRING, &address,
DBUS_TYPE_INVALID))
return invalid_args(msg);
if (check_address(address) < 0)
return invalid_args(msg);
trusted = read_trust(BDADDR_ANY, address, service->ident);
reply = dbus_message_new_method_return(msg);
if (!reply)
return NULL;
dbus_message_append_args(reply,
DBUS_TYPE_BOOLEAN, &trusted,
DBUS_TYPE_INVALID);
return reply;
}
static DBusMessage *remove_trust(DBusConnection *conn,
DBusMessage *msg, void *data)
{
struct service *service = data;
const char *address;
if (!dbus_message_get_args(msg, NULL,
DBUS_TYPE_STRING, &address,
DBUS_TYPE_INVALID))
return invalid_args(msg);
if (check_address(address) < 0)
return invalid_args(msg);
write_trust(BDADDR_ANY, address, service->ident, FALSE);
g_dbus_emit_signal(conn, service->object_path,
SERVICE_INTERFACE, "TrustRemoved",
DBUS_TYPE_STRING, &address,
DBUS_TYPE_INVALID);
return dbus_message_new_method_return(msg);
}
static GDBusMethodTable service_methods[] = {
{ "GetInfo", "", "a{sv}", get_info },
{ "GetIdentifier", "", "s", get_identifier },
{ "GetName", "", "s", get_name },
{ "GetDescription", "", "s", get_description },
{ "GetBusName", "", "s", get_bus_name },
{ "Start", "", "", start },
{ "Stop", "", "", stop },
{ "IsRunning", "", "b", is_running },
{ "IsExternal", "", "b", is_external },
{ "SetTrusted", "s", "", set_trusted },
{ "IsTrusted", "s", "b", is_trusted },
{ "RemoveTrust", "s", "", remove_trust },
{ "ListTrusts", "", "as", list_trusted },
{ NULL, NULL, NULL, NULL }
};
static GDBusSignalTable service_signals[] = {
{ "Started", "" },
{ "Stopped", "" },
{ "TrustAdded", "s" },
{ "TrustRemoved", "s" },
{ NULL, NULL }
};
static int service_cmp_path(struct service *service, const char *path)
{
return strcmp(service->object_path, path);
}
static int service_cmp_ident(struct service *service, const char *ident)
{
return strcmp(service->ident, ident);
}
static int unregister_service_for_connection(DBusConnection *connection,
struct service *service)
{
DBusConnection *conn = get_dbus_connection();
debug("Unregistering service object: %s", service->object_path);
if (!conn)
goto cleanup;
g_dbus_emit_signal(conn, service->object_path,
SERVICE_INTERFACE,
"Stopped", DBUS_TYPE_INVALID);
g_dbus_emit_signal(conn, BASE_PATH, MANAGER_INTERFACE,
"ServiceRemoved",
DBUS_TYPE_STRING, &service->object_path,
DBUS_TYPE_INVALID);
if (!g_dbus_unregister_interface(conn,
service->object_path, SERVICE_INTERFACE)) {
error("D-Bus failed to unregister %s object",
service->object_path);
return -1;
}
cleanup:
services = g_slist_remove(services, service);
service_free(service);
return 0;
}
static int do_unregister(struct service *service)
{
DBusConnection *conn = get_dbus_connection();
return unregister_service_for_connection(conn, service);
}
void release_services(DBusConnection *conn)
{
debug("release_services");
g_slist_foreach(services, (GFunc) do_unregister, NULL);
g_slist_free(services);
services = NULL;
}
struct service *search_service(const char *pattern)
{
GSList *l;
const char *bus_id;
/* Workaround for plugins: share the same bus id */
bus_id = dbus_bus_get_unique_name(get_dbus_connection());
if (!strcmp(bus_id, pattern))
return NULL;
for (l = services; l != NULL; l = l->next) {
struct service *service = l->data;
if (service->ident && !strcmp(service->ident, pattern))
return service;
}
return NULL;
}
void append_available_services(DBusMessageIter *array_iter)
{
GSList *l;
for (l = services; l != NULL; l = l->next) {
struct service *service = l->data;
dbus_message_iter_append_basic(array_iter,
DBUS_TYPE_STRING, &service->object_path);
}
}
int service_unregister(DBusConnection *conn, struct service *service)
{
return unregister_service_for_connection(conn, service);
}
static gint name_cmp(struct service_uuids *su, const char *name)
{
return strcmp(su->name, name);
}
static gint uuid_cmp(struct service_uuids *su, const char *uuid)
{
int i;
for (i = 0; su->uuids[i]; i++) {
if (!strcasecmp(su->uuids[i], uuid))
return 0;
}
return -1;
}
struct service *search_service_by_uuid(const char *uuid)
{
struct service_uuids *su;
struct service *service;
GSList *l;
if (!services_uuids)
return NULL;
l = g_slist_find_custom(services_uuids, uuid, (GCompareFunc) uuid_cmp);
if (!l)
return NULL;
su = l->data;
service = search_service(su->name);
if (!service)
return NULL;
return service;
}
static void register_uuids(const char *ident, const char **uuids)
{
struct service_uuids *su;
int i;
if (!ident)
return;
su = g_new0(struct service_uuids, 1);
su->name = g_strdup(ident);
for (i = 0; uuids[i]; i++);
su->uuids = g_new0(char *, i + 1);
for (i = 0; uuids[i]; i++)
su->uuids[i] = g_strdup(uuids[i]);
services_uuids = g_slist_append(services_uuids, su);
}
static void service_uuids_free(struct service_uuids *su)
{
int i;
if (!su)
return;
g_free(su->name);
for (i = 0; su->uuids[i]; i++)
g_free(su->uuids[i]);
g_free(su);
}
static void unregister_uuids(const char *ident)
{
struct service_uuids *su;
GSList *l;
if (!services_uuids)
return;
l = g_slist_find_custom(services_uuids, ident, (GCompareFunc) name_cmp);
if (!l)
return;
su = l->data;
services_uuids = g_slist_remove(services_uuids, su);
service_uuids_free(su);
}
static struct service *create_external_service(const char *ident)
{
struct service *service;
const char *name;
service = g_try_new0(struct service, 1);
if (!service) {
error("OOM while allocating new external service");
return NULL;
}
if (!strcmp(ident, "input"))
name = "Input service";
else if (!strcmp(ident, "audio"))
name = "Audio service";
else if (!strcmp(ident, "network"))
name = "Network service";
else if (!strcmp(ident, "serial"))
name = "Serial service";
else
name = "";
service->ident = g_strdup(ident);
service->name = g_strdup(name);
return service;
}
int register_service(const char *ident, const char **uuids)
{
DBusConnection *conn = get_dbus_connection();
struct service *service;
char obj_path[PATH_MAX];
int i;
if (g_slist_find_custom(services, ident,
(GCompareFunc) service_cmp_ident))
return -EADDRINUSE;
snprintf(obj_path, sizeof(obj_path) - 1,
"/org/bluez/service_%s", ident);
/* Make the path valid for D-Bus */
for (i = strlen("/org/bluez/"); obj_path[i]; i++) {
if (!isalnum(obj_path[i]))
obj_path[i] = '_';
}
if (g_slist_find_custom(services, obj_path,
(GCompareFunc) service_cmp_path))
return -EADDRINUSE;
service = create_external_service(ident);
debug("Registering service object: %s (%s)",
service->ident, obj_path);
if (!g_dbus_register_interface(conn, obj_path, SERVICE_INTERFACE,
service_methods, service_signals,
NULL, service, NULL)) {
error("D-Bus failed to register %s object", obj_path);
service_free(service);
return -1;
}
service->object_path = g_strdup(obj_path);
services = g_slist_append(services, service);
if (uuids)
register_uuids(ident, uuids);
g_dbus_emit_signal(conn, BASE_PATH, MANAGER_INTERFACE,
"ServiceAdded",
DBUS_TYPE_STRING, &service->object_path,
DBUS_TYPE_INVALID);
g_dbus_emit_signal(conn, service->object_path,
SERVICE_INTERFACE,
"Started", DBUS_TYPE_INVALID);
return 0;
}
void unregister_service(const char *ident)
{
unregister_uuids(ident);
}
static void agent_auth_cb(struct agent *agent, DBusError *derr, void *user_data)
{
struct service_auth *auth = user_data;
auth->cb(derr, auth->user_data);
g_free(auth);
}
int service_req_auth(const bdaddr_t *src, const bdaddr_t *dst,
const char *uuid, service_auth_cb cb, void *user_data)
{
struct service_auth *auth;
struct adapter *adapter;
struct device *device;
struct agent *agent;
struct service *service;
char address[18];
gboolean trusted;
adapter = manager_find_adapter(src);
if (!adapter)
return -EPERM;
/* Device connected? */
if (!g_slist_find_custom(adapter->active_conn,
dst, active_conn_find_by_bdaddr))
return -ENOTCONN;
ba2str(dst, address);
device = adapter_find_device(adapter, address);
if (!device)
return -EPERM;
service = search_service_by_uuid(uuid);
if (!service)
return -EPERM;
trusted = read_trust(src, address, GLOBAL_TRUST);
if (!trusted)
trusted = read_trust(BDADDR_ANY, address, service->ident);
if (trusted) {
cb(NULL, user_data);
return 0;
}
agent = (device->agent ? : adapter->agent);
if (!agent)
return handle_authorize_request_old(service, adapter->path,
address, uuid, cb, user_data);
auth = g_try_new0(struct service_auth, 1);
if (!auth)
return -ENOMEM;
auth->cb = cb;
auth->user_data = user_data;
return agent_authorize(agent, device->path, uuid, agent_auth_cb, auth);
}
int service_cancel_auth(const bdaddr_t *src, const bdaddr_t *dst)
{
struct adapter *adapter = manager_find_adapter(src);
struct device *device;
struct agent *agent;
char address[18];
if (!adapter)
return -EPERM;
ba2str(dst, address);
device = adapter_find_device(adapter, address);
if (!device)
return -EPERM;
/*
* FIXME: Cancel fails if authorization is requested to adapter's
* agent and in the meanwhile CreatePairedDevice is called.
*/
agent = (device->agent ? : adapter->agent);
if (!agent)
return cancel_authorize_request_old(adapter->path, address);
return agent_cancel(agent);
}