// SPDX-License-Identifier: GPL-2.0-or-later /* * * OBEX Server * * Copyright (C) 2007-2010 Nokia Corporation * Copyright (C) 2007-2010 Marcel Holtmann * * */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include #include #include #include #include #include "lib/bluetooth.h" #include "lib/uuid.h" #include "gdbus/gdbus.h" #include "btio/btio.h" #include "obexd/src/obexd.h" #include "obexd/src/plugin.h" #include "obexd/src/server.h" #include "obexd/src/obex.h" #include "obexd/src/transport.h" #include "obexd/src/service.h" #include "obexd/src/log.h" #define BT_RX_MTU 32767 #define BT_TX_MTU 32767 struct bluetooth_profile { struct obex_server *server; struct obex_service_driver *driver; char *uuid; char *path; }; static GSList *profiles = NULL; static DBusConnection *connection = NULL; static DBusMessage *profile_release(DBusConnection *conn, DBusMessage *msg, void *data) { return g_dbus_create_reply(msg, DBUS_TYPE_INVALID); } static void connect_event(GIOChannel *io, GError *err, void *user_data) { int sk = g_io_channel_unix_get_fd(io); struct bluetooth_profile *profile = user_data; struct obex_server *server = profile->server; int type; uint16_t omtu = BT_TX_MTU; uint16_t imtu = BT_RX_MTU; gboolean stream = TRUE; socklen_t len = sizeof(int); if (err) goto drop; if (getsockopt(sk, SOL_SOCKET, SO_TYPE, &type, &len) < 0) goto done; if (type != SOCK_SEQPACKET) goto done; stream = FALSE; /* Read MTU if io is an L2CAP socket */ bt_io_get(io, NULL, BT_IO_OPT_OMTU, &omtu, BT_IO_OPT_IMTU, &imtu, BT_IO_OPT_INVALID); done: if (obex_server_new_connection(server, io, omtu, imtu, stream) < 0) g_io_channel_shutdown(io, TRUE, NULL); return; drop: error("%s", err->message); g_io_channel_shutdown(io, TRUE, NULL); return; } static DBusMessage *invalid_args(DBusMessage *msg) { return g_dbus_create_error(msg, "org.bluez.Error.InvalidArguments", "Invalid arguments in method call"); } static DBusMessage *profile_new_connection(DBusConnection *conn, DBusMessage *msg, void *data) { DBusMessageIter args; const char *device; int fd; GIOChannel *io; dbus_message_iter_init(msg, &args); if (dbus_message_iter_get_arg_type(&args) != DBUS_TYPE_OBJECT_PATH) return invalid_args(msg); dbus_message_iter_get_basic(&args, &device); dbus_message_iter_next(&args); if (dbus_message_iter_get_arg_type(&args) != DBUS_TYPE_UNIX_FD) return invalid_args(msg); dbus_message_iter_get_basic(&args, &fd); if (fd < 0) { error("bluetooth: NewConnection invalid fd"); return invalid_args(msg); } /* Read fd flags to make sure it can be used */ if (fcntl(fd, F_GETFD) < 0) { error("bluetooth: fcntl(%d, F_GETFD): %s (%d)", fd, strerror(errno), errno); close(fd); return invalid_args(msg); } io = g_io_channel_unix_new(fd); if (io == NULL) { close(fd); return invalid_args(msg); } DBG("device %s", device); connect_event(io, NULL, data); g_io_channel_unref(io); return g_dbus_create_reply(msg, DBUS_TYPE_INVALID); } static DBusMessage *profile_request_disconnection(DBusConnection *conn, DBusMessage *msg, void *data) { return g_dbus_create_reply(msg, DBUS_TYPE_INVALID); } static DBusMessage *profile_cancel(DBusConnection *conn, DBusMessage *msg, void *data) { return g_dbus_create_reply(msg, DBUS_TYPE_INVALID); } static const GDBusMethodTable profile_methods[] = { { GDBUS_METHOD("Release", NULL, NULL, profile_release) }, { GDBUS_METHOD("NewConnection", GDBUS_ARGS({ "device", "o" }, { "fd", "h" }, { "options", "a{sv}" }), NULL, profile_new_connection) }, { GDBUS_METHOD("RequestDisconnection", GDBUS_ARGS({ "device", "o" }), NULL, profile_request_disconnection) }, { GDBUS_METHOD("Cancel", NULL, NULL, profile_cancel) }, { } }; static void unregister_profile(struct bluetooth_profile *profile) { g_dbus_unregister_interface(connection, profile->path, "org.bluez.Profile1"); g_free(profile->path); profile->path = NULL; } static void register_profile_reply(DBusPendingCall *call, void *user_data) { struct bluetooth_profile *profile = user_data; DBusMessage *reply = dbus_pending_call_steal_reply(call); DBusError derr; dbus_error_init(&derr); if (!dbus_set_error_from_message(&derr, reply)) { DBG("Profile %s registered", profile->path); goto done; } unregister_profile(profile); error("bluetooth: RequestProfile error: %s, %s", derr.name, derr.message); dbus_error_free(&derr); done: dbus_message_unref(reply); } static void profile_free(void *data) { struct bluetooth_profile *profile = data; if (profile->path != NULL) unregister_profile(profile); g_free(profile->uuid); g_free(profile); } static int register_profile(struct bluetooth_profile *profile) { DBusMessage *msg; DBusMessageIter iter, opt; DBusPendingCall *call; dbus_bool_t auto_connect = FALSE; char *xml; int ret = 0; profile->path = g_strconcat("/org/bluez/obex/", profile->uuid, NULL); g_strdelimit(profile->path, "-", '_'); if (!g_dbus_register_interface(connection, profile->path, "org.bluez.Profile1", profile_methods, NULL, NULL, profile, NULL)) { error("D-Bus failed to register %s", profile->path); g_free(profile->path); profile->path = NULL; return -1; } msg = dbus_message_new_method_call("org.bluez", "/org/bluez", "org.bluez.ProfileManager1", "RegisterProfile"); dbus_message_iter_init_append(msg, &iter); dbus_message_iter_append_basic(&iter, DBUS_TYPE_OBJECT_PATH, &profile->path); dbus_message_iter_append_basic(&iter, DBUS_TYPE_STRING, &profile->uuid); 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, &opt); g_dbus_dict_append_entry(&opt, "AutoConnect", DBUS_TYPE_BOOLEAN, &auto_connect); if (profile->driver->record) { if (profile->driver->port != 0) xml = g_markup_printf_escaped(profile->driver->record, profile->driver->channel, profile->driver->name, profile->driver->port); else xml = g_markup_printf_escaped(profile->driver->record, profile->driver->channel, profile->driver->name); g_dbus_dict_append_entry(&opt, "ServiceRecord", DBUS_TYPE_STRING, &xml); g_free(xml); } dbus_message_iter_close_container(&iter, &opt); if (!g_dbus_send_message_with_reply(connection, msg, &call, -1)) { ret = -1; unregister_profile(profile); goto failed; } dbus_pending_call_set_notify(call, register_profile_reply, profile, NULL); dbus_pending_call_unref(call); failed: dbus_message_unref(msg); return ret; } static const char *service2uuid(uint16_t service) { switch (service) { case OBEX_OPP: return OBEX_OPP_UUID; case OBEX_FTP: return OBEX_FTP_UUID; case OBEX_PBAP: return OBEX_PSE_UUID; case OBEX_IRMC: return OBEX_SYNC_UUID; case OBEX_PCSUITE: return "00005005-0000-1000-8000-0002ee000001"; case OBEX_SYNCEVOLUTION: return "00000002-0000-1000-8000-0002ee000002"; case OBEX_MAS: return OBEX_MAS_UUID; case OBEX_MNS: return OBEX_MNS_UUID; } return NULL; } static void name_acquired(DBusConnection *conn, void *user_data) { GSList *l; DBG("org.bluez appeared"); for (l = profiles; l; l = l->next) { struct bluetooth_profile *profile = l->data; if (profile->path != NULL) continue; if (register_profile(profile) < 0) { error("bluetooth: Failed to register profile %s", profile->path); g_free(profile->path); profile->path = NULL; } } } static void name_released(DBusConnection *conn, void *user_data) { GSList *l; DBG("org.bluez disappered"); for (l = profiles; l; l = l->next) { struct bluetooth_profile *profile = l->data; if (profile->path == NULL) continue; unregister_profile(profile); } } static void *bluetooth_start(struct obex_server *server, int *err) { const GSList *l; for (l = server->drivers; l; l = l->next) { struct obex_service_driver *driver = l->data; struct bluetooth_profile *profile; const char *uuid; uuid = service2uuid(driver->service); if (uuid == NULL) continue; profile = g_new0(struct bluetooth_profile, 1); profile->driver = driver; profile->server = server; profile->uuid = g_strdup(uuid); profiles = g_slist_prepend(profiles, profile); } return profiles; } static void bluetooth_stop(void *data) { g_slist_free_full(profiles, profile_free); profiles = NULL; } static int bluetooth_getpeername(GIOChannel *io, char **name) { GError *gerr = NULL; char address[18]; bt_io_get(io, &gerr, BT_IO_OPT_DEST, address, BT_IO_OPT_INVALID); if (gerr) { error("%s", gerr->message); g_error_free(gerr); return -EINVAL; } *name = g_strdup(address); return 0; } static int bluetooth_getsockname(GIOChannel *io, char **name) { GError *gerr = NULL; char address[18]; bt_io_get(io, &gerr, BT_IO_OPT_SOURCE, address, BT_IO_OPT_INVALID); if (gerr) { error("%s", gerr->message); g_error_free(gerr); return -EINVAL; } *name = g_strdup(address); return 0; } static struct obex_transport_driver driver = { .name = "bluetooth", .start = bluetooth_start, .getpeername = bluetooth_getpeername, .getsockname = bluetooth_getsockname, .stop = bluetooth_stop }; static unsigned int listener_id = 0; static int bluetooth_init(void) { connection = g_dbus_setup_private(DBUS_BUS_SYSTEM, NULL, NULL); if (connection == NULL) return -EPERM; listener_id = g_dbus_add_service_watch(connection, "org.bluez", name_acquired, name_released, NULL, NULL); return obex_transport_driver_register(&driver); } static void bluetooth_exit(void) { g_dbus_remove_watch(connection, listener_id); g_slist_free_full(profiles, profile_free); if (connection) dbus_connection_unref(connection); obex_transport_driver_unregister(&driver); } OBEX_PLUGIN_DEFINE(bluetooth, bluetooth_init, bluetooth_exit)