| /* |
| * |
| * BlueZ - Bluetooth protocol stack for Linux |
| * |
| * Copyright (C) 2004-2007 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 <fcntl.h> |
| #include <unistd.h> |
| #include <stdlib.h> |
| #include <sys/stat.h> |
| #include <sys/ioctl.h> |
| #include <sys/socket.h> |
| #include <arpa/inet.h> |
| |
| #include <bluetooth/bluetooth.h> |
| #include <bluetooth/rfcomm.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 "dbus-common.h" |
| #include "dbus-hci.h" |
| #include "dbus-adapter.h" |
| #include "dbus-error.h" |
| #include "dbus-sdp.h" |
| #include "dbus-rfcomm.h" |
| |
| /* Waiting for udev to create the device node */ |
| #define MAX_OPEN_TRIES 5 |
| #define OPEN_WAIT 300 /* ms */ |
| |
| static int rfcomm_ctl = -1; |
| |
| struct rfcomm_node { |
| int16_t id; /* Device id */ |
| char name[16]; /* Node filename */ |
| |
| DBusConnection *conn; /* for name listener handling */ |
| |
| /* 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; |
| |
| /* Used only when we wait for udev to create the device node */ |
| struct rfcomm_node *node; |
| int ntries; |
| }; |
| |
| static GSList *pending_connects = NULL; |
| static GSList *connected_nodes = NULL; |
| static GSList *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) |
| { |
| g_free(node->owner); |
| if (node->io) { |
| g_source_remove(node->io_id); |
| g_io_channel_unref(node->io); |
| } |
| if (node->conn) |
| dbus_connection_unref(node->conn); |
| g_free(node); |
| } |
| |
| static struct rfcomm_node *find_node_by_name(GSList *nodes, const char *name) |
| { |
| GSList *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_by_channel(const char *bda, |
| uint8_t ch) |
| { |
| GSList *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 struct pending_connect *find_pending_connect_by_service(const char *bda, |
| const char *svc) |
| { |
| GSList *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) && |
| !strcmp(pending->svc, svc)) |
| return pending; |
| } |
| |
| return NULL; |
| } |
| |
| static void pending_connect_free(struct pending_connect *c) |
| { |
| g_free(c->svc); |
| if (c->io) |
| g_io_channel_unref(c->io); |
| if (c->msg) |
| dbus_message_unref(c->msg); |
| if (c->conn) |
| dbus_connection_unref(c->conn); |
| g_free(c); |
| } |
| |
| static int rfcomm_release(struct rfcomm_node *node, int *err) |
| { |
| struct rfcomm_dev_req req; |
| |
| debug("rfcomm_release(%s)", node->name); |
| |
| memset(&req, 0, sizeof(req)); |
| req.dev_id = node->id; |
| |
| #if 0 |
| /* |
| * We are hitting a kernel bug inside RFCOMM code when |
| * RFCOMM_HANGUP_NOW bit is set on request's flags passed to |
| * ioctl(RFCOMMRELEASEDEV)! |
| */ |
| req.flags = (1 << RFCOMM_HANGUP_NOW); |
| #endif |
| |
| 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 void rfcomm_connect_req_exit(const char *name, void *data) |
| { |
| struct rfcomm_node *node = data; |
| debug("Connect requestor %s exited. Releasing %s node", |
| name, node->name); |
| rfcomm_release(node, NULL); |
| connected_nodes = g_slist_remove(connected_nodes, node); |
| rfcomm_node_free(node); |
| } |
| |
| static gboolean rfcomm_disconnect_cb(GIOChannel *io, GIOCondition cond, |
| struct rfcomm_node *node) |
| { |
| debug("RFCOMM node %s was disconnected", node->name); |
| name_listener_remove(node->conn, node->owner, |
| rfcomm_connect_req_exit, node); |
| connected_nodes = g_slist_remove(connected_nodes, node); |
| rfcomm_node_free(node); |
| return FALSE; |
| } |
| |
| static void rfcomm_connect_cb_devnode_opened(int fd, struct pending_connect *c, |
| struct rfcomm_node *node) |
| { |
| DBusMessage *reply = NULL; |
| char *ptr; |
| |
| 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 = g_strdup(dbus_message_get_sender(c->msg)); |
| |
| /* Check if the caller is still present */ |
| if (!dbus_bus_name_has_owner(c->conn, node->owner, NULL)) { |
| error("RFCOMM.Connect requestor %s exited", node->owner); |
| goto failed; |
| } |
| |
| node->io = g_io_channel_unix_new(fd); |
| g_io_channel_set_close_on_unref(node->io, TRUE); |
| node->io_id = g_io_add_watch(node->io, G_IO_ERR | G_IO_HUP, |
| (GIOFunc) rfcomm_disconnect_cb, node); |
| |
| send_message_and_unref(c->conn, reply); |
| |
| connected_nodes = g_slist_append(connected_nodes, node); |
| |
| node->conn = dbus_connection_ref(c->conn); |
| name_listener_add(node->conn, node->owner, |
| rfcomm_connect_req_exit, node); |
| |
| goto done; |
| |
| failed: |
| close(fd); |
| rfcomm_release(node, NULL); |
| rfcomm_node_free(node); |
| if (reply) |
| dbus_message_unref(reply); |
| done: |
| pending_connects = g_slist_remove(pending_connects, c); |
| pending_connect_free(c); |
| } |
| |
| static gboolean rfcomm_connect_cb_continue(void *data) |
| { |
| struct pending_connect *c = data; |
| struct rfcomm_node *node = c->node; |
| int fd; |
| |
| if (c->canceled) { |
| error_connect_canceled(c->conn, c->msg); |
| goto failed; |
| } |
| |
| fd = open(node->name, O_RDONLY | O_NOCTTY); |
| if (fd < 0) { |
| if (++c->ntries >= MAX_OPEN_TRIES) { |
| int err = errno; |
| error("Could not open %s: %s (%d)", |
| node->name, strerror(err), err); |
| error_connection_attempt_failed(c->conn, c->msg, err); |
| goto failed; |
| } |
| return TRUE; |
| } |
| |
| rfcomm_connect_cb_devnode_opened(fd, c, node); |
| |
| return FALSE; |
| |
| failed: |
| rfcomm_release(node, NULL); |
| rfcomm_node_free(node); |
| |
| pending_connects = g_slist_remove(pending_connects, c); |
| pending_connect_free(c); |
| |
| return FALSE; |
| } |
| |
| static gboolean rfcomm_connect_cb(GIOChannel *chan, GIOCondition cond, |
| struct pending_connect *c) |
| { |
| struct rfcomm_node *node = NULL; |
| struct rfcomm_dev_req req; |
| int sk, ret, err, fd = -1; |
| socklen_t len; |
| |
| 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 = g_new0(struct rfcomm_node, 1); |
| |
| /* 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)); |
| |
| fd = open(node->name, O_RDONLY | O_NOCTTY); |
| if (fd < 0) { |
| c->node = node; |
| c->ntries = 0; |
| g_timeout_add(OPEN_WAIT, rfcomm_connect_cb_continue, c); |
| return FALSE; |
| } |
| |
| rfcomm_connect_cb_devnode_opened(fd, c, node); |
| |
| return FALSE; |
| |
| failed: |
| if (node) |
| rfcomm_node_free(node); |
| |
| pending_connects = g_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 = g_new0(struct pending_connect, 1); |
| |
| if (svc) |
| c->svc = g_strdup(svc); |
| |
| 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) < 0) { |
| *err = errno; |
| 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); |
| g_io_channel_set_close_on_unref(c->io, TRUE); |
| |
| 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 = g_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 void rfcomm_bind_req_exit(const char *name, void *data) |
| { |
| struct rfcomm_node *node = data; |
| debug("Bind requestor %s exited. Releasing %s node", name, node->name); |
| rfcomm_release(node, NULL); |
| bound_nodes = g_slist_remove(bound_nodes, node); |
| rfcomm_node_free(node); |
| } |
| |
| static struct rfcomm_node *rfcomm_bind(bdaddr_t *src, const char *bda, |
| uint8_t ch, DBusConnection *conn, const char *owner, 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 = g_new0(struct rfcomm_node, 1); |
| |
| node->owner = g_strdup(owner); |
| |
| 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 = g_slist_append(bound_nodes, node); |
| |
| node->conn = dbus_connection_ref(conn); |
| name_listener_add(node->conn, node->owner, rfcomm_bind_req_exit, node); |
| |
| return node; |
| } |
| |
| typedef struct { |
| DBusConnection *conn; |
| DBusMessage *msg; |
| char *dst; |
| char *svc; |
| struct adapter *adapter; |
| } rfcomm_continue_data_t; |
| |
| static rfcomm_continue_data_t *rfcomm_continue_data_new(DBusConnection *conn, |
| DBusMessage *msg, |
| const char *dst, |
| const char *svc, |
| struct adapter *adapter) |
| { |
| rfcomm_continue_data_t *new; |
| |
| new = g_new(rfcomm_continue_data_t, 1); |
| |
| new->dst = g_strdup(dst); |
| if (!new->dst) { |
| g_free(new); |
| return NULL; |
| } |
| |
| new->svc = g_strdup(svc); |
| new->conn = dbus_connection_ref(conn); |
| new->msg = dbus_message_ref(msg); |
| new->adapter = adapter; |
| |
| return new; |
| } |
| |
| static void rfcomm_continue_data_free(rfcomm_continue_data_t *d) |
| { |
| dbus_connection_unref(d->conn); |
| dbus_message_unref(d->msg); |
| g_free(d->svc); |
| g_free(d->dst); |
| g_free(d); |
| } |
| |
| static void rfcomm_conn_req_continue(sdp_record_t *rec, void *data, int err) |
| { |
| rfcomm_continue_data_t *cdata = data; |
| int ch = -1, conn_err; |
| sdp_list_t *protos; |
| bdaddr_t bdaddr; |
| |
| if (err || !rec) { |
| error_record_does_not_exist(cdata->conn, cdata->msg); |
| goto failed; |
| } |
| |
| if (!sdp_get_access_protos(rec, &protos)) { |
| ch = sdp_get_proto_port(protos, RFCOMM_UUID); |
| sdp_list_foreach(protos, (sdp_list_func_t) sdp_list_free, NULL); |
| sdp_list_free(protos, NULL); |
| } |
| if (ch == -1) { |
| error_record_does_not_exist(cdata->conn, cdata->msg); |
| goto failed; |
| } |
| |
| if (find_pending_connect_by_channel(cdata->dst, ch)) { |
| error_connect_in_progress(cdata->conn, cdata->msg); |
| goto failed; |
| } |
| |
| hci_devba(cdata->adapter->dev_id, &bdaddr); |
| if (rfcomm_connect(cdata->conn, cdata->msg, &bdaddr, |
| cdata->dst, cdata->svc, ch, &conn_err) < 0) |
| error_failed(cdata->conn, cdata->msg, conn_err); |
| |
| failed: |
| rfcomm_continue_data_free(cdata); |
| } |
| |
| static DBusHandlerResult rfcomm_connect_req(DBusConnection *conn, |
| DBusMessage *msg, void *data) |
| { |
| struct adapter *adapter = data; |
| rfcomm_continue_data_t *cdata; |
| uint32_t handle; |
| uuid_t uuid; |
| const char *string; |
| const char *dst; |
| int err; |
| |
| if (!adapter->up) |
| return error_not_ready(conn, msg); |
| |
| if (!dbus_message_get_args(msg, NULL, |
| DBUS_TYPE_STRING, &dst, |
| DBUS_TYPE_STRING, &string, |
| DBUS_TYPE_INVALID)) |
| return error_invalid_arguments(conn, msg); |
| |
| cdata = rfcomm_continue_data_new(conn, msg, dst, string, adapter); |
| if (!cdata) |
| return DBUS_HANDLER_RESULT_NEED_MEMORY; |
| |
| if (str2uuid(&uuid, string) == 0) |
| err = get_record_with_uuid(conn, msg, adapter->dev_id, dst, |
| &uuid, rfcomm_conn_req_continue, cdata); |
| else if ((handle = strtol(string, NULL, 0))) |
| err = get_record_with_handle(conn, msg, adapter->dev_id, dst, |
| handle, rfcomm_conn_req_continue, cdata); |
| else { |
| rfcomm_continue_data_free(cdata); |
| return error_invalid_arguments(conn, msg); |
| } |
| |
| if (!err) |
| return DBUS_HANDLER_RESULT_HANDLED; |
| |
| rfcomm_continue_data_free(cdata); |
| |
| if (err == -ENOMEM) |
| return DBUS_HANDLER_RESULT_NEED_MEMORY; |
| |
| return error_failed(conn, msg, err); |
| } |
| |
| static DBusHandlerResult rfcomm_cancel_connect_req(DBusConnection *conn, |
| DBusMessage *msg, |
| void *data) |
| { |
| struct pending_connect *pending; |
| DBusMessage *reply; |
| const char *string; |
| const char *dst; |
| struct adapter *adapter = data; |
| |
| if (!adapter->up) |
| return error_not_ready(conn, msg); |
| |
| if (!dbus_message_get_args(msg, NULL, |
| DBUS_TYPE_STRING, &dst, |
| DBUS_TYPE_STRING, &string, |
| DBUS_TYPE_INVALID)) |
| return error_invalid_arguments(conn, msg); |
| |
| pending = find_pending_connect_by_service(dst, string); |
| 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_message_and_unref(conn, reply); |
| } |
| |
| 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 adapter *adapter = data; |
| |
| if (!adapter->up) |
| return error_not_ready(conn, msg); |
| |
| hci_devba(adapter->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_by_channel(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; |
| struct adapter *adapter = data; |
| |
| if (!adapter->up) |
| return error_not_ready(conn, msg); |
| |
| 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_by_channel(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_message_and_unref(conn, reply); |
| } |
| |
| static DBusHandlerResult rfcomm_disconnect_req(DBusConnection *conn, |
| DBusMessage *msg, void *data) |
| { |
| struct rfcomm_node *node; |
| DBusMessage *reply; |
| const char *name; |
| int err; |
| struct adapter *adapter = data; |
| |
| if (!adapter->up) |
| return error_not_ready(conn, msg); |
| |
| 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); |
| |
| if (strcmp(node->owner, dbus_message_get_sender(msg))) |
| return error_not_authorized(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); |
| } |
| |
| name_listener_remove(node->conn, node->owner, |
| rfcomm_connect_req_exit, node); |
| connected_nodes = g_slist_remove(connected_nodes, node); |
| rfcomm_node_free(node); |
| |
| return send_message_and_unref(conn, reply); |
| } |
| |
| static void rfcomm_bind_req_continue(sdp_record_t *rec, void *data, int err) |
| { |
| rfcomm_continue_data_t *cdata = data; |
| struct rfcomm_node *node = NULL; |
| DBusMessage *reply = NULL; |
| int ch = -1, bind_err; |
| sdp_list_t *protos; |
| const char *name; |
| bdaddr_t bdaddr; |
| |
| if (err || !rec) { |
| error_record_does_not_exist(cdata->conn, cdata->msg); |
| goto failed; |
| } |
| |
| if (!sdp_get_access_protos(rec, &protos)) { |
| ch = sdp_get_proto_port(protos, RFCOMM_UUID); |
| sdp_list_foreach(protos, (sdp_list_func_t) sdp_list_free, NULL); |
| sdp_list_free(protos, NULL); |
| } |
| if (ch == -1) { |
| error_record_does_not_exist(cdata->conn, cdata->msg); |
| goto failed; |
| } |
| |
| hci_devba(cdata->adapter->dev_id, &bdaddr); |
| |
| node = rfcomm_bind(&bdaddr, cdata->dst, ch, cdata->conn, |
| dbus_message_get_sender(cdata->msg), &bind_err); |
| if (!node) { |
| error_failed(cdata->conn, cdata->msg, bind_err); |
| goto failed; |
| } |
| |
| reply = dbus_message_new_method_return(cdata->msg); |
| if (!reply) { |
| error_failed(cdata->conn, cdata->msg, ENOMEM); |
| goto failed; |
| } |
| |
| name = node->name; |
| if (!dbus_message_append_args(reply, DBUS_TYPE_STRING, &name, |
| DBUS_TYPE_INVALID)) { |
| error_failed(cdata->conn, cdata->msg, ENOMEM); |
| goto failed; |
| } |
| |
| send_message_and_unref(cdata->conn, reply); |
| |
| rfcomm_continue_data_free(cdata); |
| |
| return; |
| |
| failed: |
| if (reply) |
| dbus_message_unref(reply); |
| if (node) { |
| bound_nodes = g_slist_remove(bound_nodes, node); |
| rfcomm_release(node, NULL); |
| rfcomm_node_free(node); |
| } |
| |
| rfcomm_continue_data_free(cdata); |
| } |
| |
| static DBusHandlerResult rfcomm_bind_req(DBusConnection *conn, |
| DBusMessage *msg, void *data) |
| { |
| struct adapter *adapter = data; |
| rfcomm_continue_data_t *cdata; |
| uint32_t handle; |
| uuid_t uuid; |
| const char *string; |
| const char *dst; |
| int err; |
| |
| if (!adapter->up) |
| return error_not_ready(conn, msg); |
| |
| if (!dbus_message_get_args(msg, NULL, |
| DBUS_TYPE_STRING, &dst, |
| DBUS_TYPE_STRING, &string, |
| DBUS_TYPE_INVALID)) |
| return error_invalid_arguments(conn, msg); |
| |
| cdata = rfcomm_continue_data_new(conn, msg, dst, string, adapter); |
| if (!cdata) |
| return DBUS_HANDLER_RESULT_NEED_MEMORY; |
| |
| if (str2uuid(&uuid, string) == 0) |
| err = get_record_with_uuid(conn, msg, adapter->dev_id, dst, |
| &uuid, rfcomm_bind_req_continue, cdata); |
| else if ((handle = strtol(string, NULL, 0))) |
| err = get_record_with_handle(conn, msg, adapter->dev_id, dst, |
| handle, rfcomm_bind_req_continue, cdata); |
| else { |
| rfcomm_continue_data_free(cdata); |
| return error_invalid_arguments(conn, msg); |
| } |
| |
| if (!err) |
| return DBUS_HANDLER_RESULT_HANDLED; |
| |
| rfcomm_continue_data_free(cdata); |
| |
| if (err == -ENOMEM) |
| return DBUS_HANDLER_RESULT_NEED_MEMORY; |
| |
| return error_failed(conn, msg, err); |
| } |
| |
| 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 adapter *adapter = data; |
| struct rfcomm_node *node = NULL; |
| |
| if (!adapter->up) |
| return error_not_ready(conn, msg); |
| |
| hci_devba(adapter->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, conn, |
| dbus_message_get_sender(msg), &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_message_and_unref(conn, reply); |
| |
| need_memory: |
| if (reply) |
| dbus_message_unref(reply); |
| if (node) { |
| bound_nodes = g_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; |
| struct adapter *adapter = data; |
| |
| if (!adapter->up) |
| return error_not_ready(conn, msg); |
| |
| 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); |
| |
| if (strcmp(node->owner, dbus_message_get_sender(msg))) |
| return error_not_authorized(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); |
| } |
| |
| name_listener_remove(node->conn, node->owner, |
| rfcomm_bind_req_exit, node); |
| bound_nodes = g_slist_remove(bound_nodes, node); |
| rfcomm_node_free(node); |
| |
| return send_message_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 adapter *adapter = data; |
| GSList *l; |
| |
| if (!adapter->up) |
| return error_not_ready(conn, msg); |
| |
| hci_devba(adapter->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_message_and_unref(conn, reply); |
| } |
| |
| static DBusMethodVTable rfcomm_methods[] = { |
| { "Connect", rfcomm_connect_req, |
| "ss", "s" }, |
| { "CancelConnect", rfcomm_cancel_connect_req, |
| "ss", "" }, |
| { "ConnectByChannel", rfcomm_connect_by_ch_req, |
| "sy", "s" }, |
| { "CancelConnectByChannel", rfcomm_cancel_connect_by_ch_req, |
| "sy", "" }, |
| { "Disconnect", rfcomm_disconnect_req, |
| "s", "" }, |
| { "Bind", rfcomm_bind_req, |
| "ss", "s" }, |
| { "BindByChannel", rfcomm_bind_by_ch_req, |
| "sy", "s" }, |
| { "Release", rfcomm_release_req, |
| "s", "" }, |
| { "ListBindings", rfcomm_list_bindings_req, |
| "", "as" }, |
| { NULL, NULL, NULL, NULL } |
| }; |
| |
| dbus_bool_t rfcomm_init(DBusConnection *conn, const char *path) |
| { |
| if (rfcomm_ctl < 0) { |
| rfcomm_ctl = socket(AF_BLUETOOTH, SOCK_RAW, BTPROTO_RFCOMM); |
| if (rfcomm_ctl < 0) |
| return FALSE; |
| } |
| |
| return dbus_connection_register_interface(conn, path, RFCOMM_INTERFACE, |
| rfcomm_methods, |
| NULL, NULL); |
| } |