| /* |
| * |
| * 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 <errno.h> |
| #include <stdlib.h> |
| #include <string.h> |
| |
| #include <bluetooth/bluetooth.h> |
| #include <bluetooth/hci.h> |
| #include <bluetooth/hci_lib.h> |
| #include <bluetooth/sdp.h> |
| #include <bluetooth/sdp_lib.h> |
| |
| #include <glib.h> |
| |
| #include <dbus/dbus.h> |
| |
| #include "dbus.h" |
| #include "dbus-helper.h" |
| #include "hcid.h" |
| #include "sdpd.h" |
| #include "sdp-xml.h" |
| #include "manager.h" |
| #include "adapter.h" |
| #include "dbus-hci.h" |
| #include "dbus-common.h" |
| #include "dbus-error.h" |
| #include "error.h" |
| #include "dbus-service.h" |
| #include "dbus-security.h" |
| #include "dbus-database.h" |
| |
| static int sdp_server_enable = 0; |
| |
| static GSList *records = NULL; |
| |
| struct record_data { |
| uint32_t handle; |
| char *sender; |
| }; |
| |
| static struct record_data *find_record(uint32_t handle, const char *sender) |
| { |
| GSList *list; |
| |
| for (list = records; list; list = list->next) { |
| struct record_data *data = list->data; |
| if (handle == data->handle && !strcmp(sender, data->sender)) |
| return data; |
| } |
| |
| return NULL; |
| } |
| |
| static void exit_callback(const char *name, void *user_data) |
| { |
| struct record_data *user_record = user_data; |
| |
| records = g_slist_remove(records, user_record); |
| |
| if (sdp_server_enable) |
| remove_record_from_server(user_record->handle); |
| else |
| unregister_sdp_record(user_record->handle); |
| |
| if (user_record->sender) |
| g_free(user_record->sender); |
| |
| g_free(user_record); |
| } |
| |
| static DBusHandlerResult add_service_record(DBusConnection *conn, |
| DBusMessage *msg, void *data) |
| { |
| DBusMessage *reply; |
| DBusMessageIter iter, array; |
| const char *sender; |
| struct record_data *user_record; |
| sdp_record_t *sdp_record; |
| const uint8_t *record; |
| int scanned, len = -1; |
| |
| dbus_message_iter_init(msg, &iter); |
| dbus_message_iter_recurse(&iter, &array); |
| |
| dbus_message_iter_get_fixed_array(&array, &record, &len); |
| if (len <= 0) |
| return error_invalid_arguments(conn, msg, NULL); |
| |
| user_record = g_new0(struct record_data, 1); |
| |
| if (sdp_server_enable) { |
| sdp_record = sdp_extract_pdu(record, &scanned); |
| if (!sdp_record) { |
| error("Parsing of service record failed"); |
| g_free(user_record); |
| return error_failed_errno(conn, msg, EIO); |
| } |
| |
| if (scanned != len) { |
| error("Size mismatch of service record"); |
| g_free(user_record); |
| sdp_record_free(sdp_record); |
| return error_failed_errno(conn, msg, EIO); |
| } |
| |
| if (add_record_to_server(sdp_record) < 0) { |
| error("Failed to register service record"); |
| g_free(user_record); |
| sdp_record_free(sdp_record); |
| return error_failed_errno(conn, msg, EIO); |
| } |
| |
| user_record->handle = sdp_record->handle; |
| } else { |
| uint32_t size = len; |
| |
| if (register_sdp_binary((uint8_t *) record, size, |
| &user_record->handle) < 0) { |
| error("Failed to register service record"); |
| g_free(user_record); |
| return error_failed_errno(conn, msg, errno); |
| } |
| } |
| |
| sender = dbus_message_get_sender(msg); |
| |
| user_record->sender = g_strdup(sender); |
| |
| records = g_slist_append(records, user_record); |
| |
| name_listener_add(conn, sender, exit_callback, user_record); |
| |
| reply = dbus_message_new_method_return(msg); |
| if (!reply) |
| return DBUS_HANDLER_RESULT_NEED_MEMORY; |
| |
| dbus_message_append_args(reply, DBUS_TYPE_UINT32, &user_record->handle, |
| DBUS_TYPE_INVALID); |
| |
| return send_message_and_unref(conn, reply); |
| } |
| |
| static DBusHandlerResult add_service_record_from_xml(DBusConnection *conn, |
| DBusMessage *msg, void *data) |
| { |
| DBusMessage *reply; |
| const char *sender, *record; |
| struct record_data *user_record; |
| sdp_record_t *sdp_record; |
| |
| if (dbus_message_get_args(msg, NULL, |
| DBUS_TYPE_STRING, &record, DBUS_TYPE_INVALID) == FALSE) |
| return error_invalid_arguments(conn, msg, NULL); |
| |
| user_record = g_new0(struct record_data, 1); |
| |
| sdp_record = sdp_xml_parse_record(record, strlen(record)); |
| if (!sdp_record) { |
| error("Parsing of XML service record failed"); |
| g_free(user_record); |
| return error_failed_errno(conn, msg, EIO); |
| } |
| |
| if (sdp_server_enable) { |
| if (add_record_to_server(sdp_record) < 0) { |
| error("Failed to register service record"); |
| g_free(user_record); |
| sdp_record_free(sdp_record); |
| return error_failed_errno(conn, msg, EIO); |
| } |
| |
| user_record->handle = sdp_record->handle; |
| } else { |
| if (register_sdp_record(sdp_record) < 0) { |
| error("Failed to register service record"); |
| g_free(user_record); |
| sdp_record_free(sdp_record); |
| return error_failed_errno(conn, msg, EIO); |
| } |
| |
| user_record->handle = sdp_record->handle; |
| |
| sdp_record_free(sdp_record); |
| } |
| |
| sender = dbus_message_get_sender(msg); |
| |
| user_record->sender = g_strdup(sender); |
| |
| records = g_slist_append(records, user_record); |
| |
| name_listener_add(conn, sender, exit_callback, user_record); |
| |
| reply = dbus_message_new_method_return(msg); |
| if (!reply) |
| return DBUS_HANDLER_RESULT_NEED_MEMORY; |
| |
| dbus_message_append_args(reply, DBUS_TYPE_UINT32, &user_record->handle, |
| DBUS_TYPE_INVALID); |
| |
| return send_message_and_unref(conn, reply); |
| } |
| |
| |
| static DBusHandlerResult update_record(DBusConnection *conn, DBusMessage *msg, |
| dbus_uint32_t handle, sdp_record_t *sdp_record) |
| { |
| int err; |
| |
| if (sdp_server_enable) { |
| if (remove_record_from_server(handle) < 0) { |
| sdp_record_free(sdp_record); |
| return error_not_available(conn, msg); |
| } |
| |
| sdp_record->handle = handle; |
| err = add_record_to_server(sdp_record); |
| if (err < 0) { |
| sdp_record_free(sdp_record); |
| error("Failed to update the service record"); |
| return error_failed_errno(conn, msg, EIO); |
| } |
| } else { |
| sdp_data_t *d = sdp_data_alloc(SDP_UINT32, &handle); |
| sdp_attr_replace(sdp_record, SDP_ATTR_RECORD_HANDLE, d); |
| |
| err = update_sdp_record(handle, sdp_record); |
| sdp_record_free(sdp_record); |
| if (err < 0) { |
| error("Failed to update the service record"); |
| return error_failed_errno(conn, msg, EIO); |
| } |
| } |
| |
| return send_message_and_unref(conn, |
| dbus_message_new_method_return(msg)); |
| } |
| |
| static DBusHandlerResult update_service_record(DBusConnection *conn, |
| DBusMessage *msg, void *data) |
| { |
| struct record_data *user_record; |
| DBusMessageIter iter, array; |
| sdp_record_t *sdp_record; |
| dbus_uint32_t handle; |
| const uint8_t *bin_record; |
| int scanned, size = -1; |
| |
| dbus_message_iter_init(msg, &iter); |
| dbus_message_iter_get_basic(&iter, &handle); |
| dbus_message_iter_next(&iter); |
| dbus_message_iter_recurse(&iter, &array); |
| |
| dbus_message_iter_get_fixed_array(&array, &bin_record, &size); |
| if (size <= 0) |
| return error_invalid_arguments(conn, msg, NULL); |
| |
| user_record = find_record(handle, dbus_message_get_sender(msg)); |
| if (!user_record) |
| return error_not_available(conn, msg); |
| |
| sdp_record = sdp_extract_pdu(bin_record, &scanned); |
| if (!sdp_record) { |
| error("Parsing of service record failed"); |
| return error_invalid_arguments(conn, msg, NULL); |
| } |
| |
| if (scanned != size) { |
| error("Size mismatch of service record"); |
| sdp_record_free(sdp_record); |
| return error_invalid_arguments(conn, msg, NULL); |
| } |
| |
| return update_record(conn, msg, handle, sdp_record); |
| } |
| |
| static DBusHandlerResult update_service_record_from_xml(DBusConnection *conn, |
| DBusMessage *msg, void *data) |
| { |
| const char *record; |
| struct record_data *user_record; |
| sdp_record_t *sdp_record; |
| dbus_uint32_t handle; |
| int len; |
| |
| if (dbus_message_get_args(msg, NULL, |
| DBUS_TYPE_UINT32, &handle, |
| DBUS_TYPE_STRING, &record, |
| DBUS_TYPE_INVALID) == FALSE) |
| return error_invalid_arguments(conn, msg, NULL); |
| |
| len = (record ? strlen(record) : 0); |
| if (len == 0) |
| return error_invalid_arguments(conn, msg, NULL); |
| |
| user_record = find_record(handle, dbus_message_get_sender(msg)); |
| if (!user_record) |
| return error_not_available(conn, msg); |
| |
| sdp_record = sdp_xml_parse_record(record, len); |
| if (!sdp_record) { |
| error("Parsing of XML service record failed"); |
| sdp_record_free(sdp_record); |
| return error_failed_errno(conn, msg, EIO); |
| } |
| |
| return update_record(conn, msg, handle, sdp_record); |
| } |
| |
| static DBusHandlerResult remove_service_record(DBusConnection *conn, |
| DBusMessage *msg, void *data) |
| { |
| DBusMessage *reply; |
| dbus_uint32_t handle; |
| const char *sender; |
| struct record_data *user_record; |
| |
| if (dbus_message_get_args(msg, NULL, |
| DBUS_TYPE_UINT32, &handle, DBUS_TYPE_INVALID) == FALSE) |
| return error_invalid_arguments(conn, msg, NULL); |
| |
| sender = dbus_message_get_sender(msg); |
| |
| user_record = find_record(handle, sender); |
| if (!user_record) |
| return error_not_available(conn, msg); |
| |
| name_listener_remove(conn, sender, exit_callback, user_record); |
| |
| records = g_slist_remove(records, user_record); |
| |
| if (sdp_server_enable) |
| remove_record_from_server(handle); |
| else |
| unregister_sdp_record(handle); |
| |
| if (user_record->sender) |
| g_free(user_record->sender); |
| |
| g_free(user_record); |
| |
| reply = dbus_message_new_method_return(msg); |
| if (!reply) |
| return DBUS_HANDLER_RESULT_NEED_MEMORY; |
| |
| return send_message_and_unref(conn, reply); |
| } |
| |
| static DBusHandlerResult register_service(DBusConnection *conn, |
| DBusMessage *msg, void *data) |
| { |
| DBusMessage *reply; |
| const char *sender, *ident, *name, *desc; |
| |
| if (dbus_message_get_args(msg, NULL, DBUS_TYPE_STRING, &ident, |
| DBUS_TYPE_STRING, &name, DBUS_TYPE_STRING, &desc, |
| DBUS_TYPE_INVALID) == FALSE) |
| return error_invalid_arguments(conn, msg, NULL); |
| |
| sender = dbus_message_get_sender(msg); |
| |
| if (service_register(conn, sender, ident, name, desc) < 0) |
| return error_failed_errno(conn, msg, EIO); |
| |
| reply = dbus_message_new_method_return(msg); |
| if (!reply) |
| return DBUS_HANDLER_RESULT_NEED_MEMORY; |
| |
| return send_message_and_unref(conn, reply); |
| } |
| |
| static DBusHandlerResult unregister_service(DBusConnection *conn, |
| DBusMessage *msg, void *data) |
| { |
| DBusMessage *reply; |
| const char *sender, *ident; |
| struct service *service; |
| |
| if (dbus_message_get_args(msg, NULL, DBUS_TYPE_STRING, &ident, |
| DBUS_TYPE_INVALID) == FALSE) |
| return error_invalid_arguments(conn, msg, NULL); |
| |
| sender = dbus_message_get_sender(msg); |
| |
| service = search_service(conn, ident); |
| if (!service) |
| return error_service_does_not_exist(conn, msg); |
| |
| if (!service->external || strcmp(sender, service->bus_name)) |
| return error_not_authorized(conn, msg); |
| |
| if (service_unregister(conn, service) < 0) |
| return error_failed_errno(conn, msg, EIO); |
| |
| reply = dbus_message_new_method_return(msg); |
| if (!reply) |
| return DBUS_HANDLER_RESULT_NEED_MEMORY; |
| |
| return send_message_and_unref(conn, reply); |
| } |
| |
| static DBusHandlerResult request_authorization(DBusConnection *conn, |
| DBusMessage *msg, void *data) |
| { |
| const char *sender, *address, *path; |
| struct service *service; |
| bdaddr_t bdaddr; |
| gboolean trusted; |
| int adapter_id; |
| |
| if (dbus_message_get_args(msg, NULL, DBUS_TYPE_STRING, &address, |
| DBUS_TYPE_STRING, &path, DBUS_TYPE_INVALID) == FALSE) |
| return error_invalid_arguments(conn, msg, NULL); |
| |
| sender = dbus_message_get_sender(msg); |
| |
| service = search_service(conn, sender); |
| if (!service) { |
| debug("Got RequestAuthorization from non-service owner %s", |
| sender); |
| return error_not_authorized(conn, msg); |
| } |
| |
| str2ba(address, &bdaddr); |
| adapter_id = hci_for_each_dev(HCI_UP, find_conn, (long) &bdaddr); |
| if (adapter_id < 0) |
| return error_not_connected(conn, msg); |
| |
| hci_devba(adapter_id, &bdaddr); |
| |
| trusted = read_trust(&bdaddr, address, GLOBAL_TRUST); |
| if (!trusted) |
| trusted = read_trust(BDADDR_ANY, address, service->ident); |
| |
| if (trusted) { |
| DBusMessage *reply; |
| |
| reply = dbus_message_new_method_return(msg); |
| if (!reply) |
| return DBUS_HANDLER_RESULT_NEED_MEMORY; |
| |
| return send_message_and_unref(conn, reply); |
| } |
| |
| return handle_authorize_request(conn, msg, service, address, path); |
| } |
| |
| static DBusHandlerResult cancel_authorization_request(DBusConnection *conn, |
| DBusMessage *msg, void *data) |
| { |
| const char *sender, *address, *path; |
| struct service *service; |
| |
| if (dbus_message_get_args(msg, NULL, DBUS_TYPE_STRING, &address, |
| DBUS_TYPE_STRING, &path, DBUS_TYPE_INVALID) == FALSE) |
| return error_invalid_arguments(conn, msg, NULL); |
| |
| sender = dbus_message_get_sender(msg); |
| |
| service = search_service(conn, sender); |
| if (!service) |
| return error_not_authorized(conn, msg); |
| |
| return cancel_authorize_request(conn, msg, service, address, path); |
| } |
| |
| static DBusMethodVTable database_methods[] = { |
| { "AddServiceRecord", add_service_record, "ay", "u" }, |
| { "AddServiceRecordFromXML", add_service_record_from_xml, "s", "u" }, |
| { "UpdateServiceRecord", update_service_record, "uay", "" }, |
| { "UpdateServiceRecordFromXML", update_service_record_from_xml, "us", "" }, |
| { "RemoveServiceRecord", remove_service_record, "u", "" }, |
| { "RegisterService", register_service, "sss", "" }, |
| { "UnregisterService", unregister_service, "s", "" }, |
| { "RequestAuthorization", request_authorization, "ss", "" }, |
| { "CancelAuthorizationRequest", cancel_authorization_request, "ss", "" }, |
| { NULL, NULL, NULL, NULL } |
| }; |
| |
| dbus_bool_t database_init(DBusConnection *conn, const char *path) |
| { |
| return dbus_connection_register_interface(conn, path, |
| DATABASE_INTERFACE, |
| database_methods, |
| NULL, NULL); |
| } |
| |
| DBusHandlerResult database_message(DBusConnection *conn, |
| DBusMessage *msg, void *data) |
| { |
| DBusMethodVTable *current; |
| |
| for (current = database_methods; |
| current->name && current->message_function; current++) { |
| if (!dbus_message_is_method_call(msg, DATABASE_INTERFACE, |
| current->name)) |
| continue; |
| |
| if (dbus_message_has_signature(msg, current->signature)) { |
| debug("%s: %s.%s()", dbus_message_get_path(msg), |
| DATABASE_INTERFACE, current->name); |
| return current->message_function(conn, msg, data); |
| } |
| } |
| |
| return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; |
| } |
| |
| void set_sdp_server_enable(void) |
| { |
| sdp_server_enable = 1; |
| } |