| /* | |
| * | |
| * OBEX Server | |
| * | |
| * Copyright (C) 2007-2010 Nokia Corporation | |
| * Copyright (C) 2007-2010 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 <string.h> | |
| #include <unistd.h> | |
| #include <inttypes.h> | |
| #include <fcntl.h> | |
| #include <sys/socket.h> | |
| #include <glib.h> | |
| #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) |