| /* |
| * |
| * BlueZ - Bluetooth protocol stack for Linux |
| * |
| * Copyright (C) 2004-2006 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 <sys/types.h> |
| #include <sys/stat.h> |
| #include <fcntl.h> |
| #include <sys/ioctl.h> |
| #include <sys/socket.h> |
| |
| #include <bluetooth/bluetooth.h> |
| #include <bluetooth/rfcomm.h> |
| #include <bluetooth/hci.h> |
| #include <bluetooth/hci_lib.h> |
| |
| #include <dbus/dbus.h> |
| |
| #include "hcid.h" |
| #include "list.h" |
| #include "glib-ectomy.h" |
| #include "dbus.h" |
| |
| /* Waiting for udev to create the device node */ |
| #define MAX_OPEN_TRIES 6 |
| #define OPEN_WAIT (1000 * 300) |
| |
| static int rfcomm_ctl = -1; |
| |
| struct rfcomm_node { |
| int16_t id; /* Device id */ |
| char name[16]; /* Node filename */ |
| |
| /* The following members are only valid for connected nodes */ |
| GIOChannel *io; /* IO Channel for the connection */ |
| guint io_id; /* ID for IO channel */ |
| char *owner; /* D-Bus name that created the node */ |
| }; |
| |
| struct pending_connect { |
| DBusConnection *conn; |
| DBusMessage *msg; |
| GIOChannel *io; |
| char *svc; |
| int canceled; |
| struct sockaddr_rc laddr; |
| struct sockaddr_rc raddr; |
| }; |
| |
| static struct slist *pending_connects = NULL; |
| static struct slist *connected_nodes = NULL; |
| static struct slist *bound_nodes = NULL; |
| |
| static char *rfcomm_node_name_from_id(int16_t id, char *dev, size_t len) |
| { |
| snprintf(dev, len, "/dev/rfcomm%d", id); |
| return dev; |
| } |
| |
| static void rfcomm_node_free(struct rfcomm_node *node) |
| { |
| if (node->io) { |
| g_io_channel_close(node->io); |
| g_io_remove_watch(node->io_id); |
| } |
| if (node->owner) |
| free(node->owner); |
| free(node); |
| } |
| |
| static struct rfcomm_node *find_node_by_name(struct slist *nodes, const char *name) |
| { |
| struct slist *l; |
| |
| for (l = nodes; l != NULL; l = l->next) { |
| struct rfcomm_node *node = l->data; |
| if (!strcmp(node->name, name)) |
| return node; |
| } |
| |
| return NULL; |
| } |
| |
| static struct pending_connect *find_pending_connect(const char *bda, uint8_t ch) |
| { |
| struct slist *l; |
| bdaddr_t dba; |
| |
| str2ba(bda, &dba); |
| |
| for (l = pending_connects; l != NULL; l = l->next) { |
| struct pending_connect *pending = l->data; |
| if (!bacmp(&dba, &pending->raddr.rc_bdaddr) |
| && pending->raddr.rc_channel == ch) |
| return pending; |
| } |
| |
| return NULL; |
| } |
| |
| static void pending_connect_free(struct pending_connect *c) |
| { |
| if (c->svc) |
| free(c->svc); |
| if (c->io) |
| g_io_channel_close(c->io); |
| if (c->msg) |
| dbus_message_unref(c->msg); |
| if (c->conn) |
| dbus_connection_unref(c->conn); |
| free(c); |
| } |
| |
| static int set_nonblocking(int fd, int *err) |
| { |
| long arg; |
| |
| arg = fcntl(fd, F_GETFL); |
| if (arg < 0) { |
| if (err) |
| *err = errno; |
| error("fcntl(F_GETFL): %s (%d)", strerror(errno), errno); |
| return -1; |
| } |
| |
| /* Return if already nonblocking */ |
| if (arg & O_NONBLOCK) |
| return 0; |
| |
| arg |= O_NONBLOCK; |
| if (fcntl(fd, F_SETFL, arg) < 0) { |
| if (err) |
| *err = errno; |
| error("fcntl(F_SETFL, O_NONBLOCK): %s (%d)", |
| strerror(errno), errno); |
| return -1; |
| } |
| |
| return 0; |
| } |
| |
| static int rfcomm_release(struct rfcomm_node *node, int *err) |
| { |
| struct rfcomm_dev_req req; |
| |
| debug("rfcomm_release(%s)", node->name); |
| |
| if (node->io) { |
| g_io_channel_close(node->io); |
| g_io_remove_watch(node->io_id); |
| node->io = NULL; |
| } |
| |
| memset(&req, 0, sizeof(req)); |
| req.dev_id = node->id; |
| |
| if (ioctl(rfcomm_ctl, RFCOMMRELEASEDEV, &req) < 0) { |
| if (err) |
| *err = errno; |
| error("Can't release device %d: %s (%d)", node->id, |
| strerror(errno), errno); |
| return -1; |
| } |
| |
| return 0; |
| } |
| |
| static gboolean rfcomm_disconnect_cb(GIOChannel *io, GIOCondition cond, |
| struct rfcomm_node *node) |
| { |
| debug("RFCOMM node %s was disconnected", node->name); |
| connected_nodes = slist_remove(connected_nodes, node); |
| rfcomm_node_free(node); |
| return FALSE; |
| } |
| |
| static gboolean rfcomm_connect_cb(GIOChannel *chan, GIOCondition cond, |
| struct pending_connect *c) |
| { |
| int sk, ret, err, fd = -1, i; |
| socklen_t len; |
| char *ptr; |
| struct rfcomm_dev_req req; |
| struct rfcomm_node *node = NULL; |
| DBusMessage *reply = NULL; |
| |
| if (c->canceled) { |
| error_connect_canceled(c->conn, c->msg); |
| goto failed; |
| } |
| |
| sk = g_io_channel_unix_get_fd(chan); |
| |
| len = sizeof(ret); |
| if (getsockopt(sk, SOL_SOCKET, SO_ERROR, &ret, &len) < 0) { |
| err = errno; |
| error("getsockopt(SO_ERROR): %s (%d)", strerror(err), err); |
| error_failed(c->conn, c->msg, err); |
| goto failed; |
| } |
| |
| if (ret != 0) { |
| error("connect(): %s (%d)", strerror(ret), ret); |
| error_connection_attempt_failed(c->conn, c->msg, ret); |
| goto failed; |
| } |
| |
| debug("rfcomm_connect_cb: connected"); |
| |
| len = sizeof(c->laddr); |
| if (getsockname(sk, (struct sockaddr *) &c->laddr, &len) < 0) { |
| err = errno; |
| error_failed(c->conn, c->msg, err); |
| error("getsockname: %s (%d)", strerror(err), err); |
| goto failed; |
| } |
| |
| node = malloc(sizeof(struct rfcomm_node)); |
| if (!node) { |
| error_failed(c->conn, c->msg, ENOMEM); |
| goto failed; |
| } |
| |
| /* Create the rfcomm device node */ |
| memset(&req, 0, sizeof(req)); |
| |
| req.dev_id = -1; |
| req.flags = (1 << RFCOMM_REUSE_DLC) | (1 << RFCOMM_RELEASE_ONHUP); |
| |
| bacpy(&req.src, &c->laddr.rc_bdaddr); |
| bacpy(&req.dst, &c->raddr.rc_bdaddr); |
| req.channel = c->raddr.rc_channel; |
| |
| node->id = ioctl(sk, RFCOMMCREATEDEV, &req); |
| if (node->id < 0) { |
| err = errno; |
| error("ioctl(RFCOMMCREATEDEV): %s (%d)", strerror(errno), err); |
| error_failed(c->conn, c->msg, err); |
| goto failed; |
| } |
| |
| rfcomm_node_name_from_id(node->id, node->name, sizeof(node->name)); |
| |
| /* FIXME: instead of looping here we should create a timer and |
| * return to the mainloop */ |
| for (i = 0; i < MAX_OPEN_TRIES; i++) { |
| fd = open(node->name, O_RDONLY | O_NOCTTY); |
| if (fd >= 0) |
| break; |
| usleep(OPEN_WAIT); |
| } |
| |
| if (fd < 0) { |
| err = errno; |
| error("Could not open %s: %s (%d)", node->name, |
| strerror(err), err); |
| rfcomm_release(node, NULL); |
| rfcomm_node_free(node); |
| |
| error_connection_attempt_failed(c->conn, c->msg, err); |
| goto failed; |
| } |
| |
| reply = dbus_message_new_method_return(c->msg); |
| if (!reply) { |
| error_failed(c->conn, c->msg, ENOMEM); |
| goto failed; |
| } |
| |
| ptr = node->name; |
| if (!dbus_message_append_args(reply, |
| DBUS_TYPE_STRING, &ptr, |
| DBUS_TYPE_INVALID)) { |
| error_failed(c->conn, c->msg, ENOMEM); |
| goto failed; |
| } |
| |
| node->owner = strdup(dbus_message_get_sender(c->msg)); |
| if (!node->owner) { |
| error_failed(c->conn, c->msg, ENOMEM); |
| goto failed; |
| } |
| |
| node->io = g_io_channel_unix_new(fd); |
| node->io_id = g_io_add_watch(node->io, G_IO_ERR | G_IO_HUP, |
| (GIOFunc)rfcomm_disconnect_cb, node); |
| |
| send_reply_and_unref(c->conn, reply); |
| |
| connected_nodes = slist_append(connected_nodes, node); |
| |
| goto done; |
| |
| failed: |
| if (fd >= 0) |
| close(fd); |
| if (node) { |
| rfcomm_release(node, NULL); |
| rfcomm_node_free(node); |
| } |
| if (reply) |
| dbus_message_unref(reply); |
| done: |
| pending_connects = slist_remove(pending_connects, c); |
| pending_connect_free(c); |
| |
| return FALSE; |
| } |
| |
| static int rfcomm_connect(DBusConnection *conn, DBusMessage *msg, bdaddr_t *src, |
| const char *bda, const char *svc, uint8_t ch, int *err) |
| { |
| int sk = -1; |
| struct pending_connect *c = NULL; |
| |
| c = malloc(sizeof(struct pending_connect)); |
| if (!c) { |
| if (err) |
| *err = ENOMEM; |
| goto failed; |
| } |
| |
| memset(c, 0, sizeof(struct pending_connect)); |
| |
| if (svc) { |
| c->svc = strdup(svc); |
| if (!c->svc) { |
| if (err) |
| *err = ENOMEM; |
| goto failed; |
| } |
| } |
| |
| c->laddr.rc_family = AF_BLUETOOTH; |
| bacpy(&c->laddr.rc_bdaddr, src); |
| c->laddr.rc_channel = 0; |
| |
| c->raddr.rc_family = AF_BLUETOOTH; |
| str2ba(bda, &c->raddr.rc_bdaddr); |
| c->raddr.rc_channel = ch; |
| |
| sk = socket(AF_BLUETOOTH, SOCK_STREAM, BTPROTO_RFCOMM); |
| if (sk < 0) { |
| if (err) |
| *err = errno; |
| goto failed; |
| } |
| |
| if (bind(sk, (struct sockaddr *)&c->laddr, sizeof(c->laddr)) < 0) { |
| if (err) |
| *err = errno; |
| goto failed; |
| } |
| |
| if (set_nonblocking(sk, err) < 0) |
| goto failed; |
| |
| /* So we can reply to the message later */ |
| c->msg = dbus_message_ref(msg); |
| c->conn = dbus_connection_ref(conn); |
| |
| c->io = g_io_channel_unix_new(sk); |
| |
| if (connect(sk, (struct sockaddr *)&c->raddr, sizeof(c->raddr)) < 0) { |
| /* BlueZ returns EAGAIN eventhough it should return EINPROGRESS */ |
| if (!(errno == EAGAIN || errno == EINPROGRESS)) { |
| if (err) |
| *err = errno; |
| error("connect() failed: %s (%d)", strerror(errno), errno); |
| goto failed; |
| } |
| |
| debug("Connect in progress"); |
| g_io_add_watch(c->io, G_IO_OUT, (GIOFunc)rfcomm_connect_cb, c); |
| pending_connects = slist_append(pending_connects, c); |
| } else { |
| debug("Connect succeeded with first try"); |
| (void) rfcomm_connect_cb(c->io, G_IO_OUT, c); |
| } |
| |
| return 0; |
| |
| failed: |
| if (c) |
| pending_connect_free(c); |
| if (sk >= 0) |
| close(sk); |
| return -1; |
| } |
| |
| static struct rfcomm_node *rfcomm_bind(bdaddr_t *src, const char *bda, uint8_t ch, int *err) |
| { |
| struct rfcomm_dev_req req; |
| struct rfcomm_node *node; |
| |
| debug("rfcomm_bind(%s, %d)", bda, ch); |
| |
| memset(&req, 0, sizeof(req)); |
| req.dev_id = -1; |
| req.flags = 0; |
| bacpy(&req.src, src); |
| |
| str2ba(bda, &req.dst); |
| req.channel = ch; |
| |
| node = malloc(sizeof(struct rfcomm_node)); |
| if (!node) { |
| if (err) |
| *err = ENOMEM; |
| return NULL; |
| } |
| |
| memset(node, 0, sizeof(struct rfcomm_node)); |
| |
| node->id = ioctl(rfcomm_ctl, RFCOMMCREATEDEV, &req); |
| if (node->id < 0) { |
| if (err) |
| *err = errno; |
| error("RFCOMMCREATEDEV failed: %s (%d)", strerror(errno), errno); |
| rfcomm_node_free(node); |
| return NULL; |
| } |
| |
| rfcomm_node_name_from_id(node->id, node->name, sizeof(node->name)); |
| bound_nodes = slist_append(bound_nodes, node); |
| |
| return node; |
| } |
| |
| static DBusHandlerResult rfcomm_connect_req(DBusConnection *conn, |
| DBusMessage *msg, void *data) |
| { |
| error("RFCOMM.Connect not implemented"); |
| return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; |
| } |
| |
| static DBusHandlerResult rfcomm_cancel_connect_req(DBusConnection *conn, |
| DBusMessage *msg, void *data) |
| { |
| error("RFCOMM.CancelConnect not implemented"); |
| return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; |
| } |
| |
| static DBusHandlerResult rfcomm_connect_by_ch_req(DBusConnection *conn, |
| DBusMessage *msg, void *data) |
| { |
| bdaddr_t bdaddr; |
| const char *dst; |
| uint8_t ch; |
| int err; |
| struct hci_dbus_data *dbus_data = data; |
| |
| hci_devba(dbus_data->dev_id, &bdaddr); |
| |
| if (!dbus_message_get_args(msg, NULL, |
| DBUS_TYPE_STRING, &dst, |
| DBUS_TYPE_BYTE, &ch, |
| DBUS_TYPE_INVALID)) |
| return error_invalid_arguments(conn, msg); |
| |
| if (find_pending_connect(dst, ch)) |
| return error_connect_in_progress(conn, msg); |
| |
| if (rfcomm_connect(conn, msg, &bdaddr, dst, NULL, ch, &err) < 0) |
| return error_failed(conn, msg, err); |
| |
| return DBUS_HANDLER_RESULT_HANDLED; |
| } |
| |
| static DBusHandlerResult rfcomm_cancel_connect_by_ch_req(DBusConnection *conn, |
| DBusMessage *msg, void *data) |
| { |
| const char *dst; |
| uint8_t ch; |
| DBusMessage *reply; |
| struct pending_connect *pending; |
| |
| if (!dbus_message_get_args(msg, NULL, |
| DBUS_TYPE_STRING, &dst, |
| DBUS_TYPE_BYTE, &ch, |
| DBUS_TYPE_INVALID)) |
| return error_invalid_arguments(conn, msg); |
| |
| pending = find_pending_connect(dst, ch); |
| if (!pending) |
| return error_connect_not_in_progress(conn, msg); |
| |
| reply = dbus_message_new_method_return(msg); |
| if (!reply) |
| return DBUS_HANDLER_RESULT_NEED_MEMORY; |
| |
| pending->canceled = 1; |
| |
| return send_reply_and_unref(conn, reply); |
| } |
| |
| static DBusHandlerResult rfcomm_disconnect_req(DBusConnection *conn, |
| DBusMessage *msg, void *data) |
| { |
| DBusMessage *reply; |
| const char *name; |
| struct rfcomm_node *node; |
| int err; |
| |
| if (!dbus_message_get_args(msg, NULL, |
| DBUS_TYPE_STRING, &name, |
| DBUS_TYPE_INVALID)) |
| return error_invalid_arguments(conn, msg); |
| |
| node = find_node_by_name(connected_nodes, name); |
| if (!node) |
| return error_not_connected(conn, msg); |
| |
| reply = dbus_message_new_method_return(msg); |
| if (!reply) |
| return DBUS_HANDLER_RESULT_NEED_MEMORY; |
| |
| if (rfcomm_release(node, &err) < 0) { |
| dbus_message_unref(reply); |
| return error_failed(conn, msg, err); |
| } |
| |
| connected_nodes = slist_remove(connected_nodes, node); |
| rfcomm_node_free(node); |
| |
| return send_reply_and_unref(conn, reply); |
| } |
| |
| static DBusHandlerResult rfcomm_bind_req(DBusConnection *conn, |
| DBusMessage *msg, void *data) |
| { |
| error("RFCOMM.Bind not implemented"); |
| return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; |
| } |
| |
| static DBusHandlerResult rfcomm_bind_by_ch_req(DBusConnection *conn, |
| DBusMessage *msg, void *data) |
| { |
| bdaddr_t bdaddr; |
| DBusMessage *reply = NULL; |
| uint8_t ch; |
| int err; |
| const char *dst, *name; |
| struct hci_dbus_data *dbus_data = data; |
| struct rfcomm_node *node = NULL; |
| |
| hci_devba(dbus_data->dev_id, &bdaddr); |
| |
| if (!dbus_message_get_args(msg, NULL, |
| DBUS_TYPE_STRING, &dst, |
| DBUS_TYPE_BYTE, &ch, |
| DBUS_TYPE_INVALID)) |
| return error_invalid_arguments(conn, msg); |
| |
| |
| node = rfcomm_bind(&bdaddr, dst, ch, &err); |
| if (!node) |
| return error_failed(conn, msg, err); |
| |
| reply = dbus_message_new_method_return(msg); |
| if (!reply) |
| goto need_memory; |
| |
| name = node->name; |
| if (!dbus_message_append_args(reply, DBUS_TYPE_STRING, &name, |
| DBUS_TYPE_INVALID)) |
| goto need_memory; |
| |
| return send_reply_and_unref(conn, reply); |
| |
| need_memory: |
| if (reply) |
| dbus_message_unref(reply); |
| if (node) { |
| bound_nodes = slist_remove(bound_nodes, node); |
| rfcomm_release(node, NULL); |
| rfcomm_node_free(node); |
| } |
| return DBUS_HANDLER_RESULT_NEED_MEMORY; |
| } |
| |
| static DBusHandlerResult rfcomm_release_req(DBusConnection *conn, |
| DBusMessage *msg, void *data) |
| { |
| DBusMessage *reply; |
| const char *name; |
| struct rfcomm_node *node; |
| int err; |
| |
| if (!dbus_message_get_args(msg, NULL, |
| DBUS_TYPE_STRING, &name, |
| DBUS_TYPE_INVALID)) |
| return error_invalid_arguments(conn, msg); |
| |
| node = find_node_by_name(bound_nodes, name); |
| if (!node) |
| return error_binding_does_not_exist(conn, msg); |
| |
| reply = dbus_message_new_method_return(msg); |
| if (!reply) |
| return DBUS_HANDLER_RESULT_NEED_MEMORY; |
| |
| if (rfcomm_release(node, &err) < 0) { |
| dbus_message_unref(reply); |
| return error_failed(conn, msg, err); |
| } |
| |
| bound_nodes = slist_remove(bound_nodes, node); |
| rfcomm_node_free(node); |
| |
| return send_reply_and_unref(conn, reply); |
| } |
| |
| static DBusHandlerResult rfcomm_list_bindings_req(DBusConnection *conn, |
| DBusMessage *msg, void *data) |
| { |
| bdaddr_t bdaddr; |
| DBusMessage *reply; |
| DBusMessageIter iter, sub; |
| struct hci_dbus_data *dbus_data = data; |
| struct slist *l; |
| |
| hci_devba(dbus_data->dev_id, &bdaddr); |
| |
| reply = dbus_message_new_method_return(msg); |
| if (!reply) |
| return DBUS_HANDLER_RESULT_NEED_MEMORY; |
| |
| dbus_message_iter_init_append(reply, &iter); |
| |
| if (!dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "s", &sub)) { |
| dbus_message_unref(reply); |
| return DBUS_HANDLER_RESULT_NEED_MEMORY; |
| } |
| |
| for (l = bound_nodes; l != NULL; l = l->next) { |
| struct rfcomm_node *node = l->data; |
| struct rfcomm_dev_info di = { id: node->id }; |
| char *name = node->name; |
| |
| if (ioctl(rfcomm_ctl, RFCOMMGETDEVINFO, &di) < 0) { |
| error("RFCOMMGETDEVINFO(%d): %s (%d)", |
| node->id, strerror(errno), errno); |
| continue; |
| } |
| |
| /* Ignore nodes not specific to this adapter */ |
| if (bacmp(&di.src, &bdaddr)) |
| continue; |
| |
| dbus_message_iter_append_basic(&sub, DBUS_TYPE_STRING, &name); |
| } |
| |
| if (!dbus_message_iter_close_container(&iter, &sub)) { |
| dbus_message_unref(reply); |
| return DBUS_HANDLER_RESULT_NEED_MEMORY; |
| } |
| |
| return send_reply_and_unref(conn, reply); |
| } |
| |
| static struct service_data rfcomm_services[] = { |
| { "Connect", rfcomm_connect_req, }, |
| { "CancelConnect", rfcomm_cancel_connect_req, }, |
| { "ConnectByChannel", rfcomm_connect_by_ch_req, }, |
| { "CancelConnectByChannel", rfcomm_cancel_connect_by_ch_req, }, |
| { "Disconnect", rfcomm_disconnect_req, }, |
| { "Bind", rfcomm_bind_req, }, |
| { "BindByChannel", rfcomm_bind_by_ch_req, }, |
| { "Release", rfcomm_release_req, }, |
| { "ListBindings", rfcomm_list_bindings_req, }, |
| { NULL, NULL, } |
| }; |
| |
| DBusHandlerResult handle_rfcomm_method(DBusConnection *conn, DBusMessage *msg, |
| void *data) |
| { |
| service_handler_func_t handler; |
| |
| if (!hcid_dbus_use_experimental()) |
| return error_unknown_method(conn, msg); |
| |
| if (!data) { |
| error("RFCOMM method called with NULL data pointer!"); |
| return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; |
| } |
| |
| /* Initialize the RFCOMM control socket if has not yet been done */ |
| if (rfcomm_ctl < 0) { |
| rfcomm_ctl = socket(AF_BLUETOOTH, SOCK_RAW, BTPROTO_RFCOMM); |
| if (rfcomm_ctl < 0) |
| return error_failed(conn, msg, errno); |
| } |
| |
| handler = find_service_handler(rfcomm_services, msg); |
| |
| if (handler) |
| return handler(conn, msg, data); |
| |
| return error_unknown_method(conn, msg); |
| } |