mirror of
https://git.kernel.org/pub/scm/bluetooth/bluez.git
synced 2024-11-27 06:04:32 +08:00
968be4037c
Socket security shall be based on flags passed from HAL. Android public API uses both encrypt and auth flags for "secure" sockets which should be mapped to high security on our side, but since this would also trigger requirement for 16-digits pin code (which is not used in Android) we'll ignore auth flag and use either low or medium security based on encrypt flag value only.
1161 lines
26 KiB
C
1161 lines
26 KiB
C
/*
|
|
*
|
|
* BlueZ - Bluetooth protocol stack for Linux
|
|
*
|
|
* Copyright (C) 2013-2014 Intel Corporation. All rights reserved.
|
|
*
|
|
*
|
|
* This library is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU Lesser General Public
|
|
* License as published by the Free Software Foundation; either
|
|
* version 2.1 of the License, or (at your option) any later version.
|
|
*
|
|
* This library 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
|
|
* Lesser General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public
|
|
* License along with this library; 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 <glib.h>
|
|
#include <stdbool.h>
|
|
#include <unistd.h>
|
|
#include <errno.h>
|
|
|
|
#include "lib/bluetooth.h"
|
|
#include "btio/btio.h"
|
|
#include "lib/sdp.h"
|
|
#include "lib/sdp_lib.h"
|
|
#include "src/sdp-client.h"
|
|
#include "src/sdpd.h"
|
|
#include "src/log.h"
|
|
|
|
#include "bluetooth.h"
|
|
#include "hal-msg.h"
|
|
#include "hal-ipc.h"
|
|
#include "ipc.h"
|
|
#include "utils.h"
|
|
#include "socket.h"
|
|
|
|
#define RFCOMM_CHANNEL_MAX 30
|
|
|
|
#define OPP_DEFAULT_CHANNEL 12
|
|
#define HFAG_DEFAULT_CHANNEL 13
|
|
#define PBAP_DEFAULT_CHANNEL 19
|
|
|
|
#define SVC_HINT_OBEX 0x10
|
|
|
|
/* Hardcoded MAP stuff needed for MAS SMS Instance.*/
|
|
#define DEFAULT_MAS_INSTANCE 0x00
|
|
|
|
#define MAP_MSG_TYPE_SMS_GSM 0x02
|
|
#define MAP_MSG_TYPE_SMS_CDMA 0x04
|
|
#define DEFAULT_MAS_MSG_TYPE (MAP_MSG_TYPE_SMS_GSM | MAP_MSG_TYPE_SMS_CDMA)
|
|
|
|
|
|
static bdaddr_t adapter_addr;
|
|
|
|
static const uint8_t zero_uuid[16] = { 0 };
|
|
|
|
/* Simple list of RFCOMM connected sockets */
|
|
GList *connections = NULL;
|
|
|
|
struct rfcomm_sock {
|
|
int channel; /* RFCOMM channel */
|
|
BtIOSecLevel sec_level;
|
|
|
|
/* for socket to BT */
|
|
int bt_sock;
|
|
guint bt_watch;
|
|
|
|
/* for socket to HAL */
|
|
int jv_sock;
|
|
guint jv_watch;
|
|
|
|
bdaddr_t dst;
|
|
uint32_t service_handle;
|
|
|
|
uint8_t *buf;
|
|
int buf_size;
|
|
};
|
|
|
|
struct rfcomm_channel {
|
|
bool reserved;
|
|
struct rfcomm_sock *rfsock;
|
|
};
|
|
|
|
static struct rfcomm_channel servers[RFCOMM_CHANNEL_MAX + 1];
|
|
|
|
static int rfsock_set_buffer(struct rfcomm_sock *rfsock)
|
|
{
|
|
socklen_t len = sizeof(int);
|
|
int rcv, snd, size, err;
|
|
|
|
err = getsockopt(rfsock->bt_sock, SOL_SOCKET, SO_RCVBUF, &rcv, &len);
|
|
if (err < 0) {
|
|
error("getsockopt(SO_RCVBUF): %s", strerror(errno));
|
|
return -errno;
|
|
}
|
|
|
|
err = getsockopt(rfsock->bt_sock, SOL_SOCKET, SO_SNDBUF, &snd, &len);
|
|
if (err < 0) {
|
|
error("getsockopt(SO_SNDBUF): %s", strerror(errno));
|
|
return -errno;
|
|
}
|
|
|
|
size = MAX(rcv, snd);
|
|
|
|
DBG("Set buffer size %d", size);
|
|
|
|
rfsock->buf = g_malloc(size);
|
|
rfsock->buf_size = size;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void cleanup_rfsock(gpointer data)
|
|
{
|
|
struct rfcomm_sock *rfsock = data;
|
|
|
|
DBG("rfsock %p bt_sock %d jv_sock %d", rfsock, rfsock->bt_sock,
|
|
rfsock->jv_sock);
|
|
|
|
if (rfsock->jv_sock >= 0)
|
|
if (close(rfsock->jv_sock) < 0)
|
|
error("close() fd %d failed: %s", rfsock->jv_sock,
|
|
strerror(errno));
|
|
|
|
if (rfsock->bt_sock >= 0)
|
|
if (close(rfsock->bt_sock) < 0)
|
|
error("close() fd %d: failed: %s", rfsock->bt_sock,
|
|
strerror(errno));
|
|
|
|
if (rfsock->bt_watch > 0)
|
|
if (!g_source_remove(rfsock->bt_watch))
|
|
error("bt_watch source was not found");
|
|
|
|
if (rfsock->jv_watch > 0)
|
|
if (!g_source_remove(rfsock->jv_watch))
|
|
error("stack_watch source was not found");
|
|
|
|
if (rfsock->service_handle)
|
|
bt_adapter_remove_record(rfsock->service_handle);
|
|
|
|
if (rfsock->buf)
|
|
g_free(rfsock->buf);
|
|
|
|
g_free(rfsock);
|
|
}
|
|
|
|
static struct rfcomm_sock *create_rfsock(int bt_sock, int *hal_sock)
|
|
{
|
|
int fds[2] = {-1, -1};
|
|
struct rfcomm_sock *rfsock;
|
|
|
|
if (socketpair(AF_LOCAL, SOCK_STREAM, 0, fds) < 0) {
|
|
error("socketpair(): %s", strerror(errno));
|
|
*hal_sock = -1;
|
|
return NULL;
|
|
}
|
|
|
|
rfsock = g_new0(struct rfcomm_sock, 1);
|
|
rfsock->jv_sock = fds[0];
|
|
*hal_sock = fds[1];
|
|
rfsock->bt_sock = bt_sock;
|
|
|
|
DBG("rfsock %p", rfsock);
|
|
|
|
if (bt_sock < 0)
|
|
return rfsock;
|
|
|
|
if (rfsock_set_buffer(rfsock) < 0) {
|
|
cleanup_rfsock(rfsock);
|
|
return NULL;
|
|
}
|
|
|
|
return rfsock;
|
|
}
|
|
|
|
static sdp_record_t *create_rfcomm_record(uint8_t chan, uuid_t *uuid,
|
|
const char *svc_name,
|
|
bool has_obex)
|
|
{
|
|
sdp_list_t *svclass_id;
|
|
sdp_list_t *seq, *proto_seq, *pbg_seq;
|
|
sdp_list_t *proto[2];
|
|
uuid_t l2cap_uuid, rfcomm_uuid, obex_uuid, pbg_uuid;
|
|
sdp_data_t *channel;
|
|
sdp_record_t *record;
|
|
|
|
record = sdp_record_alloc();
|
|
if (!record)
|
|
return NULL;
|
|
|
|
record->handle = sdp_next_handle();
|
|
|
|
svclass_id = sdp_list_append(NULL, uuid);
|
|
sdp_set_service_classes(record, svclass_id);
|
|
|
|
sdp_uuid16_create(&l2cap_uuid, L2CAP_UUID);
|
|
proto[0] = sdp_list_append(NULL, &l2cap_uuid);
|
|
seq = sdp_list_append(NULL, proto[0]);
|
|
|
|
sdp_uuid16_create(&rfcomm_uuid, RFCOMM_UUID);
|
|
proto[1] = sdp_list_append(NULL, &rfcomm_uuid);
|
|
channel = sdp_data_alloc(SDP_UINT8, &chan);
|
|
proto[1] = sdp_list_append(proto[1], channel);
|
|
seq = sdp_list_append(seq, proto[1]);
|
|
|
|
if (has_obex) {
|
|
sdp_uuid16_create(&obex_uuid, OBEX_UUID);
|
|
proto[2] = sdp_list_append(NULL, &obex_uuid);
|
|
seq = sdp_list_append(seq, proto[2]);
|
|
}
|
|
|
|
proto_seq = sdp_list_append(NULL, seq);
|
|
sdp_set_access_protos(record, proto_seq);
|
|
|
|
sdp_uuid16_create(&pbg_uuid, PUBLIC_BROWSE_GROUP);
|
|
pbg_seq = sdp_list_append(NULL, &pbg_uuid);
|
|
sdp_set_browse_groups(record, pbg_seq);
|
|
|
|
if (svc_name)
|
|
sdp_set_info_attr(record, svc_name, NULL, NULL);
|
|
|
|
sdp_data_free(channel);
|
|
sdp_list_free(proto[0], NULL);
|
|
sdp_list_free(proto[1], NULL);
|
|
if (has_obex)
|
|
sdp_list_free(proto[2], NULL);
|
|
sdp_list_free(seq, NULL);
|
|
sdp_list_free(proto_seq, NULL);
|
|
sdp_list_free(pbg_seq, NULL);
|
|
sdp_list_free(svclass_id, NULL);
|
|
|
|
return record;
|
|
}
|
|
|
|
static sdp_record_t *create_opp_record(uint8_t chan, const char *svc_name)
|
|
{
|
|
uint8_t formats[] = { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0xff };
|
|
uint8_t dtd = SDP_UINT8;
|
|
uuid_t uuid;
|
|
sdp_list_t *seq;
|
|
sdp_profile_desc_t profile[1];
|
|
void *dtds[sizeof(formats)], *values[sizeof(formats)];
|
|
sdp_data_t *formats_list;
|
|
sdp_record_t *record;
|
|
size_t i;
|
|
|
|
sdp_uuid16_create(&uuid, OBEX_OBJPUSH_SVCLASS_ID);
|
|
|
|
record = create_rfcomm_record(chan, &uuid, svc_name, true);
|
|
if (!record)
|
|
return NULL;
|
|
|
|
sdp_uuid16_create(&profile[0].uuid, OBEX_OBJPUSH_PROFILE_ID);
|
|
profile[0].version = 0x0100;
|
|
seq = sdp_list_append(NULL, profile);
|
|
sdp_set_profile_descs(record, seq);
|
|
|
|
for (i = 0; i < sizeof(formats); i++) {
|
|
dtds[i] = &dtd;
|
|
values[i] = &formats[i];
|
|
}
|
|
formats_list = sdp_seq_alloc(dtds, values, sizeof(formats));
|
|
sdp_attr_add(record, SDP_ATTR_SUPPORTED_FORMATS_LIST, formats_list);
|
|
|
|
sdp_list_free(seq, NULL);
|
|
|
|
return record;
|
|
}
|
|
|
|
static sdp_record_t *create_pbap_record(uint8_t chan, const char *svc_name)
|
|
{
|
|
sdp_list_t *seq;
|
|
sdp_profile_desc_t profile[1];
|
|
uint8_t formats = 0x01;
|
|
sdp_record_t *record;
|
|
uuid_t uuid;
|
|
|
|
sdp_uuid16_create(&uuid, PBAP_PSE_SVCLASS_ID);
|
|
|
|
record = create_rfcomm_record(chan, &uuid, svc_name, true);
|
|
if (!record)
|
|
return NULL;
|
|
|
|
sdp_uuid16_create(&profile[0].uuid, PBAP_PROFILE_ID);
|
|
profile[0].version = 0x0100;
|
|
seq = sdp_list_append(NULL, profile);
|
|
sdp_set_profile_descs(record, seq);
|
|
|
|
sdp_attr_add_new(record, SDP_ATTR_SUPPORTED_REPOSITORIES, SDP_UINT8,
|
|
&formats);
|
|
|
|
sdp_list_free(seq, NULL);
|
|
|
|
return record;
|
|
}
|
|
|
|
static sdp_record_t *create_mas_record(uint8_t chan, const char *svc_name)
|
|
{
|
|
sdp_list_t *seq;
|
|
sdp_profile_desc_t profile[1];
|
|
uint8_t minst = DEFAULT_MAS_INSTANCE;
|
|
uint8_t mtype = DEFAULT_MAS_MSG_TYPE;
|
|
sdp_record_t *record;
|
|
uuid_t uuid;
|
|
|
|
sdp_uuid16_create(&uuid, MAP_MSE_SVCLASS_ID);
|
|
|
|
record = create_rfcomm_record(chan, &uuid, svc_name, true);
|
|
if (!record)
|
|
return NULL;
|
|
|
|
sdp_uuid16_create(&profile[0].uuid, MAP_PROFILE_ID);
|
|
profile[0].version = 0x0101;
|
|
seq = sdp_list_append(NULL, profile);
|
|
sdp_set_profile_descs(record, seq);
|
|
|
|
sdp_attr_add_new(record, SDP_ATTR_MAS_INSTANCE_ID, SDP_UINT8, &minst);
|
|
sdp_attr_add_new(record, SDP_ATTR_SUPPORTED_MESSAGE_TYPES, SDP_UINT8,
|
|
&mtype);
|
|
|
|
sdp_list_free(seq, NULL);
|
|
|
|
return record;
|
|
}
|
|
|
|
static sdp_record_t *create_spp_record(uint8_t chan, const char *svc_name)
|
|
{
|
|
sdp_record_t *record;
|
|
uuid_t uuid;
|
|
|
|
sdp_uuid16_create(&uuid, SERIAL_PORT_SVCLASS_ID);
|
|
|
|
record = create_rfcomm_record(chan, &uuid, svc_name, false);
|
|
if (!record)
|
|
return NULL;
|
|
|
|
return record;
|
|
}
|
|
|
|
static sdp_record_t *create_app_record(uint8_t chan,
|
|
const uint8_t *app_uuid,
|
|
const char *svc_name)
|
|
{
|
|
sdp_record_t *record;
|
|
uuid_t uuid;
|
|
|
|
sdp_uuid128_create(&uuid, app_uuid);
|
|
|
|
record = create_rfcomm_record(chan, &uuid, svc_name, false);
|
|
if (!record)
|
|
return NULL;
|
|
|
|
return record;
|
|
}
|
|
|
|
static const struct profile_info {
|
|
uint8_t uuid[16];
|
|
uint8_t channel;
|
|
uint8_t svc_hint;
|
|
BtIOSecLevel sec_level;
|
|
sdp_record_t * (*create_record)(uint8_t chan, const char *svc_name);
|
|
} profiles[] = {
|
|
{
|
|
.uuid = {
|
|
0x00, 0x00, 0x11, 0x1F, 0x00, 0x00, 0x10, 0x00,
|
|
0x80, 0x00, 0x00, 0x80, 0x5F, 0x9B, 0x34, 0xFB
|
|
},
|
|
.channel = HFAG_DEFAULT_CHANNEL,
|
|
.svc_hint = 0,
|
|
.sec_level = BT_IO_SEC_MEDIUM,
|
|
.create_record = NULL
|
|
}, {
|
|
.uuid = {
|
|
0x00, 0x00, 0x11, 0x2F, 0x00, 0x00, 0x10, 0x00,
|
|
0x80, 0x00, 0x00, 0x80, 0x5F, 0x9B, 0x34, 0xFB
|
|
},
|
|
.channel = PBAP_DEFAULT_CHANNEL,
|
|
.svc_hint = SVC_HINT_OBEX,
|
|
.sec_level = BT_IO_SEC_MEDIUM,
|
|
.create_record = create_pbap_record
|
|
}, {
|
|
.uuid = {
|
|
0x00, 0x00, 0x11, 0x05, 0x00, 0x00, 0x10, 0x00,
|
|
0x80, 0x00, 0x00, 0x80, 0x5F, 0x9B, 0x34, 0xFB
|
|
},
|
|
.channel = OPP_DEFAULT_CHANNEL,
|
|
.svc_hint = SVC_HINT_OBEX,
|
|
.sec_level = BT_IO_SEC_LOW,
|
|
.create_record = create_opp_record
|
|
}, {
|
|
.uuid = {
|
|
0x00, 0x00, 0x11, 0x32, 0x00, 0x00, 0x10, 0x00,
|
|
0x80, 0x00, 0x00, 0x80, 0x5F, 0x9B, 0x34, 0xFB
|
|
},
|
|
.channel = 0,
|
|
.svc_hint = SVC_HINT_OBEX,
|
|
.sec_level = BT_IO_SEC_MEDIUM,
|
|
.create_record = create_mas_record
|
|
}, {
|
|
.uuid = {
|
|
0x00, 0x00, 0x11, 0x01, 0x00, 0x00, 0x10, 0x00,
|
|
0x80, 0x00, 0x00, 0x80, 0x5F, 0x9B, 0x34, 0xFB
|
|
},
|
|
.channel = 0,
|
|
.svc_hint = 0,
|
|
.sec_level = BT_IO_SEC_MEDIUM,
|
|
.create_record = create_spp_record
|
|
},
|
|
};
|
|
|
|
static uint32_t sdp_service_register(uint8_t channel, const uint8_t *uuid,
|
|
const struct profile_info *profile,
|
|
const void *svc_name)
|
|
{
|
|
sdp_record_t *record = NULL;
|
|
uint8_t svc_hint = 0;
|
|
|
|
if (profile && profile->create_record) {
|
|
record = profile->create_record(channel, svc_name);
|
|
svc_hint = profile->svc_hint;
|
|
} else if (uuid) {
|
|
record = create_app_record(channel, uuid, svc_name);
|
|
}
|
|
|
|
if (!record)
|
|
return 0;
|
|
|
|
if (bt_adapter_add_record(record, svc_hint) < 0) {
|
|
error("Failed to register on SDP record");
|
|
sdp_record_free(record);
|
|
return 0;
|
|
}
|
|
|
|
return record->handle;
|
|
}
|
|
|
|
static int bt_sock_send_fd(int sock_fd, const void *buf, int len, int send_fd)
|
|
{
|
|
ssize_t ret;
|
|
struct msghdr msg;
|
|
struct cmsghdr *cmsg;
|
|
struct iovec iv;
|
|
char cmsgbuf[CMSG_SPACE(sizeof(int))];
|
|
|
|
DBG("len %d sock_fd %d send_fd %d", len, sock_fd, send_fd);
|
|
|
|
if (sock_fd == -1 || send_fd == -1)
|
|
return -1;
|
|
|
|
memset(&msg, 0, sizeof(msg));
|
|
memset(cmsgbuf, 0, sizeof(cmsgbuf));
|
|
|
|
msg.msg_control = cmsgbuf;
|
|
msg.msg_controllen = sizeof(cmsgbuf);
|
|
|
|
cmsg = CMSG_FIRSTHDR(&msg);
|
|
cmsg->cmsg_level = SOL_SOCKET;
|
|
cmsg->cmsg_type = SCM_RIGHTS;
|
|
cmsg->cmsg_len = CMSG_LEN(sizeof(send_fd));
|
|
|
|
memcpy(CMSG_DATA(cmsg), &send_fd, sizeof(send_fd));
|
|
|
|
iv.iov_base = (unsigned char *) buf;
|
|
iv.iov_len = len;
|
|
|
|
msg.msg_iov = &iv;
|
|
msg.msg_iovlen = 1;
|
|
|
|
ret = sendmsg(sock_fd, &msg, MSG_NOSIGNAL);
|
|
if (ret < 0) {
|
|
error("sendmsg(): sock_fd %d send_fd %d: %s",
|
|
sock_fd, send_fd, strerror(errno));
|
|
return ret;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static const struct profile_info *get_profile_by_uuid(const uint8_t *uuid)
|
|
{
|
|
unsigned int i;
|
|
|
|
for (i = 0; i < G_N_ELEMENTS(profiles); i++) {
|
|
if (!memcmp(profiles[i].uuid, uuid, 16))
|
|
return &profiles[i];
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static int try_write_all(int fd, unsigned char *buf, int len)
|
|
{
|
|
int sent = 0;
|
|
|
|
while (len > 0) {
|
|
int written;
|
|
|
|
written = write(fd, buf, len);
|
|
if (written < 0) {
|
|
if (errno == EINTR || errno == EAGAIN)
|
|
continue;
|
|
return -1;
|
|
}
|
|
|
|
if (!written)
|
|
return 0;
|
|
|
|
len -= written; buf += written; sent += written;
|
|
}
|
|
|
|
return sent;
|
|
}
|
|
|
|
static gboolean jv_sock_client_event_cb(GIOChannel *io, GIOCondition cond,
|
|
gpointer data)
|
|
{
|
|
struct rfcomm_sock *rfsock = data;
|
|
int len, sent;
|
|
|
|
if (cond & G_IO_HUP) {
|
|
DBG("Socket %d hang up", g_io_channel_unix_get_fd(io));
|
|
goto fail;
|
|
}
|
|
|
|
if (cond & (G_IO_ERR | G_IO_NVAL)) {
|
|
error("Socket %d error", g_io_channel_unix_get_fd(io));
|
|
goto fail;
|
|
}
|
|
|
|
len = read(rfsock->jv_sock, rfsock->buf, rfsock->buf_size);
|
|
if (len <= 0) {
|
|
error("read(): %s", strerror(errno));
|
|
/* Read again */
|
|
return TRUE;
|
|
}
|
|
|
|
sent = try_write_all(rfsock->bt_sock, rfsock->buf, len);
|
|
if (sent < 0) {
|
|
error("write(): %s", strerror(errno));
|
|
goto fail;
|
|
}
|
|
|
|
return TRUE;
|
|
fail:
|
|
DBG("rfsock %p jv_sock %d cond %d", rfsock, rfsock->jv_sock, cond);
|
|
|
|
connections = g_list_remove(connections, rfsock);
|
|
cleanup_rfsock(rfsock);
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
static gboolean bt_sock_event_cb(GIOChannel *io, GIOCondition cond,
|
|
gpointer data)
|
|
{
|
|
struct rfcomm_sock *rfsock = data;
|
|
int len, sent;
|
|
|
|
if (cond & G_IO_HUP) {
|
|
DBG("Socket %d hang up", g_io_channel_unix_get_fd(io));
|
|
goto fail;
|
|
}
|
|
|
|
if (cond & (G_IO_ERR | G_IO_NVAL)) {
|
|
error("Socket %d error", g_io_channel_unix_get_fd(io));
|
|
goto fail;
|
|
}
|
|
|
|
len = read(rfsock->bt_sock, rfsock->buf, rfsock->buf_size);
|
|
if (len <= 0) {
|
|
error("read(): %s", strerror(errno));
|
|
/* Read again */
|
|
return TRUE;
|
|
}
|
|
|
|
sent = try_write_all(rfsock->jv_sock, rfsock->buf, len);
|
|
if (sent < 0) {
|
|
error("write(): %s", strerror(errno));
|
|
goto fail;
|
|
}
|
|
|
|
return TRUE;
|
|
fail:
|
|
DBG("rfsock %p bt_sock %d cond %d", rfsock, rfsock->bt_sock, cond);
|
|
|
|
connections = g_list_remove(connections, rfsock);
|
|
cleanup_rfsock(rfsock);
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
static bool sock_send_accept(struct rfcomm_sock *rfsock, bdaddr_t *bdaddr,
|
|
int fd_accepted)
|
|
{
|
|
struct hal_sock_connect_signal cmd;
|
|
int len;
|
|
|
|
DBG("");
|
|
|
|
cmd.size = sizeof(cmd);
|
|
bdaddr2android(bdaddr, cmd.bdaddr);
|
|
cmd.channel = rfsock->channel;
|
|
cmd.status = 0;
|
|
|
|
len = bt_sock_send_fd(rfsock->jv_sock, &cmd, sizeof(cmd), fd_accepted);
|
|
if (len != sizeof(cmd)) {
|
|
error("Error sending accept signal");
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static gboolean jv_sock_server_event_cb(GIOChannel *io, GIOCondition cond,
|
|
gpointer data)
|
|
{
|
|
struct rfcomm_sock *rfsock = data;
|
|
|
|
DBG("rfsock %p jv_sock %d cond %d", rfsock, rfsock->jv_sock, cond);
|
|
|
|
if (cond & G_IO_NVAL)
|
|
return FALSE;
|
|
|
|
if (cond & (G_IO_ERR | G_IO_HUP )) {
|
|
servers[rfsock->channel].rfsock = NULL;
|
|
cleanup_rfsock(rfsock);
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
static void accept_cb(GIOChannel *io, GError *err, gpointer user_data)
|
|
{
|
|
struct rfcomm_sock *rfsock = user_data;
|
|
struct rfcomm_sock *new_rfsock;
|
|
GIOChannel *jv_io;
|
|
GError *gerr = NULL;
|
|
bdaddr_t dst;
|
|
char address[18];
|
|
int new_sock;
|
|
int hal_sock;
|
|
guint id;
|
|
GIOCondition cond;
|
|
|
|
if (err) {
|
|
error("%s", err->message);
|
|
return;
|
|
}
|
|
|
|
bt_io_get(io, &gerr,
|
|
BT_IO_OPT_DEST_BDADDR, &dst,
|
|
BT_IO_OPT_INVALID);
|
|
if (gerr) {
|
|
error("%s", gerr->message);
|
|
g_error_free(gerr);
|
|
g_io_channel_shutdown(io, TRUE, NULL);
|
|
return;
|
|
}
|
|
|
|
ba2str(&dst, address);
|
|
DBG("Incoming connection from %s on channel %d (rfsock %p)", address,
|
|
rfsock->channel, rfsock);
|
|
|
|
new_sock = g_io_channel_unix_get_fd(io);
|
|
new_rfsock = create_rfsock(new_sock, &hal_sock);
|
|
if (!new_rfsock) {
|
|
g_io_channel_shutdown(io, TRUE, NULL);
|
|
return;
|
|
}
|
|
|
|
DBG("new rfsock %p bt_sock %d jv_sock %d hal_sock %d", new_rfsock,
|
|
new_rfsock->bt_sock, new_rfsock->jv_sock, hal_sock);
|
|
|
|
if (!sock_send_accept(rfsock, &dst, hal_sock)) {
|
|
cleanup_rfsock(new_rfsock);
|
|
return;
|
|
}
|
|
|
|
connections = g_list_append(connections, new_rfsock);
|
|
|
|
/* Handle events from Android */
|
|
cond = G_IO_IN | G_IO_HUP | G_IO_ERR | G_IO_NVAL;
|
|
jv_io = g_io_channel_unix_new(new_rfsock->jv_sock);
|
|
id = g_io_add_watch(jv_io, cond, jv_sock_client_event_cb, new_rfsock);
|
|
g_io_channel_unref(jv_io);
|
|
|
|
new_rfsock->jv_watch = id;
|
|
|
|
/* Handle rfcomm events */
|
|
cond = G_IO_IN | G_IO_HUP | G_IO_ERR | G_IO_NVAL;
|
|
id = g_io_add_watch(io, cond, bt_sock_event_cb, new_rfsock);
|
|
g_io_channel_set_close_on_unref(io, FALSE);
|
|
|
|
new_rfsock->bt_watch = id;
|
|
}
|
|
|
|
static int find_free_channel(void)
|
|
{
|
|
int ch;
|
|
|
|
/* channel 0 is reserver so we don't use it */
|
|
for (ch = 1; ch <= RFCOMM_CHANNEL_MAX; ch++) {
|
|
struct rfcomm_channel *srv = &servers[ch];
|
|
|
|
if (!srv->reserved && srv->rfsock == NULL)
|
|
return ch;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static BtIOSecLevel get_sec_level(uint8_t flags)
|
|
{
|
|
/* HAL_SOCK_FLAG_AUTH should require MITM but in our case setting
|
|
* security to BT_IO_SEC_HIGH would also require 16-digits PIN code
|
|
* for pre-2.1 devices which is not what Android expects. For this
|
|
* reason we ignore this flag to not break apps which use "secure"
|
|
* sockets (have both auth and encrypt flags set, there is no public
|
|
* API in Android which should provide proper high security socket).
|
|
*/
|
|
return flags & HAL_SOCK_FLAG_ENCRYPT ? BT_IO_SEC_MEDIUM :
|
|
BT_IO_SEC_LOW;
|
|
}
|
|
|
|
static uint8_t rfcomm_listen(int chan, const uint8_t *name, const uint8_t *uuid,
|
|
uint8_t flags, int *hal_sock)
|
|
{
|
|
const struct profile_info *profile;
|
|
struct rfcomm_sock *rfsock = NULL;
|
|
BtIOSecLevel sec_level;
|
|
GIOChannel *io, *jv_io;
|
|
GIOCondition cond;
|
|
GError *err = NULL;
|
|
guint id;
|
|
uuid_t uu;
|
|
char uuid_str[32];
|
|
|
|
sdp_uuid128_create(&uu, uuid);
|
|
sdp_uuid2strn(&uu, uuid_str, sizeof(uuid_str));
|
|
|
|
DBG("chan %d flags 0x%02x uuid %s name %s", chan, flags, uuid_str,
|
|
name);
|
|
|
|
if ((!memcmp(uuid, zero_uuid, sizeof(zero_uuid)) && chan <= 0) ||
|
|
(chan > RFCOMM_CHANNEL_MAX)) {
|
|
error("Invalid rfcomm listen params");
|
|
return HAL_STATUS_INVALID;
|
|
}
|
|
|
|
profile = get_profile_by_uuid(uuid);
|
|
if (!profile) {
|
|
sec_level = get_sec_level(flags);
|
|
} else {
|
|
if (!profile->create_record)
|
|
return HAL_STATUS_INVALID;
|
|
|
|
chan = profile->channel;
|
|
sec_level = profile->sec_level;
|
|
}
|
|
|
|
if (chan <= 0)
|
|
chan = find_free_channel();
|
|
|
|
if (!chan) {
|
|
error("No free channels");
|
|
return HAL_STATUS_BUSY;
|
|
}
|
|
|
|
if (servers[chan].rfsock != NULL) {
|
|
error("Channel already registered (%d)", chan);
|
|
return HAL_STATUS_BUSY;
|
|
}
|
|
|
|
DBG("chan %d sec_level %d", chan, sec_level);
|
|
|
|
rfsock = create_rfsock(-1, hal_sock);
|
|
if (!rfsock)
|
|
return HAL_STATUS_FAILED;
|
|
|
|
rfsock->channel = chan;
|
|
|
|
io = bt_io_listen(accept_cb, NULL, rfsock, NULL, &err,
|
|
BT_IO_OPT_SOURCE_BDADDR, &adapter_addr,
|
|
BT_IO_OPT_CHANNEL, chan,
|
|
BT_IO_OPT_SEC_LEVEL, sec_level,
|
|
BT_IO_OPT_INVALID);
|
|
if (!io) {
|
|
error("Failed listen: %s", err->message);
|
|
g_error_free(err);
|
|
goto failed;
|
|
}
|
|
|
|
rfsock->bt_sock = g_io_channel_unix_get_fd(io);
|
|
|
|
g_io_channel_set_close_on_unref(io, FALSE);
|
|
g_io_channel_unref(io);
|
|
|
|
/* Handle events from Android */
|
|
cond = G_IO_HUP | G_IO_ERR | G_IO_NVAL;
|
|
jv_io = g_io_channel_unix_new(rfsock->jv_sock);
|
|
id = g_io_add_watch_full(jv_io, G_PRIORITY_HIGH, cond,
|
|
jv_sock_server_event_cb, rfsock,
|
|
NULL);
|
|
g_io_channel_unref(jv_io);
|
|
|
|
rfsock->jv_watch = id;
|
|
|
|
DBG("rfsock %p bt_sock %d jv_sock %d hal_sock %d", rfsock,
|
|
rfsock->bt_sock,
|
|
rfsock->jv_sock,
|
|
*hal_sock);
|
|
|
|
if (write(rfsock->jv_sock, &chan, sizeof(chan)) != sizeof(chan)) {
|
|
error("Error sending RFCOMM channel");
|
|
goto failed;
|
|
}
|
|
|
|
rfsock->service_handle = sdp_service_register(chan, uuid, profile,
|
|
name);
|
|
|
|
servers[chan].rfsock = rfsock;
|
|
|
|
return HAL_STATUS_SUCCESS;
|
|
|
|
failed:
|
|
|
|
cleanup_rfsock(rfsock);
|
|
close(*hal_sock);
|
|
return HAL_STATUS_FAILED;
|
|
}
|
|
|
|
static void handle_listen(const void *buf, uint16_t len)
|
|
{
|
|
const struct hal_cmd_sock_listen *cmd = buf;
|
|
uint8_t status;
|
|
int hal_sock;
|
|
|
|
switch (cmd->type) {
|
|
case HAL_SOCK_RFCOMM:
|
|
status = rfcomm_listen(cmd->channel, cmd->name, cmd->uuid,
|
|
cmd->flags, &hal_sock);
|
|
break;
|
|
case HAL_SOCK_SCO:
|
|
case HAL_SOCK_L2CAP:
|
|
status = HAL_STATUS_UNSUPPORTED;
|
|
break;
|
|
default:
|
|
status = HAL_STATUS_INVALID;
|
|
break;
|
|
}
|
|
|
|
if (status != HAL_STATUS_SUCCESS)
|
|
goto failed;
|
|
|
|
ipc_send_rsp_full(HAL_SERVICE_ID_SOCK, HAL_OP_SOCK_LISTEN, 0, NULL,
|
|
hal_sock);
|
|
close(hal_sock);
|
|
return ;
|
|
|
|
failed:
|
|
ipc_send_rsp(HAL_SERVICE_ID_SOCK, HAL_OP_SOCK_LISTEN, status);
|
|
}
|
|
|
|
static bool sock_send_connect(struct rfcomm_sock *rfsock, bdaddr_t *bdaddr)
|
|
{
|
|
struct hal_sock_connect_signal cmd;
|
|
int len;
|
|
|
|
DBG("");
|
|
|
|
memset(&cmd, 0, sizeof(cmd));
|
|
cmd.size = sizeof(cmd);
|
|
bdaddr2android(bdaddr, cmd.bdaddr);
|
|
cmd.channel = rfsock->channel;
|
|
cmd.status = 0;
|
|
|
|
len = write(rfsock->jv_sock, &cmd, sizeof(cmd));
|
|
if (len < 0) {
|
|
error("%s", strerror(errno));
|
|
return false;
|
|
}
|
|
|
|
if (len != sizeof(cmd)) {
|
|
error("Error sending connect signal");
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static void connect_cb(GIOChannel *io, GError *err, gpointer user_data)
|
|
{
|
|
struct rfcomm_sock *rfsock = user_data;
|
|
bdaddr_t *dst = &rfsock->dst;
|
|
GIOChannel *jv_io;
|
|
char address[18];
|
|
guint id;
|
|
GIOCondition cond;
|
|
|
|
if (err) {
|
|
error("%s", err->message);
|
|
goto fail;
|
|
}
|
|
|
|
ba2str(dst, address);
|
|
DBG("Connected to %s on channel %d (rfsock %p)", address,
|
|
rfsock->channel, rfsock);
|
|
|
|
if (!sock_send_connect(rfsock, dst))
|
|
goto fail;
|
|
|
|
/* Handle events from Android */
|
|
cond = G_IO_IN | G_IO_HUP | G_IO_ERR | G_IO_NVAL;
|
|
jv_io = g_io_channel_unix_new(rfsock->jv_sock);
|
|
id = g_io_add_watch(jv_io, cond, jv_sock_client_event_cb, rfsock);
|
|
g_io_channel_unref(jv_io);
|
|
|
|
rfsock->jv_watch = id;
|
|
|
|
/* Handle rfcomm events */
|
|
cond = G_IO_IN | G_IO_ERR | G_IO_HUP | G_IO_NVAL;
|
|
id = g_io_add_watch(io, cond, bt_sock_event_cb, rfsock);
|
|
g_io_channel_set_close_on_unref(io, FALSE);
|
|
|
|
rfsock->bt_watch = id;
|
|
|
|
return;
|
|
fail:
|
|
connections = g_list_remove(connections, rfsock);
|
|
cleanup_rfsock(rfsock);
|
|
}
|
|
|
|
static bool do_rfcomm_connect(struct rfcomm_sock *rfsock, int chan)
|
|
{
|
|
GIOChannel *io;
|
|
GError *gerr = NULL;
|
|
|
|
DBG("rfsock %p sec_level %d chan %d", rfsock, rfsock->sec_level, chan);
|
|
|
|
io = bt_io_connect(connect_cb, rfsock, NULL, &gerr,
|
|
BT_IO_OPT_SOURCE_BDADDR, &adapter_addr,
|
|
BT_IO_OPT_DEST_BDADDR, &rfsock->dst,
|
|
BT_IO_OPT_CHANNEL, chan,
|
|
BT_IO_OPT_SEC_LEVEL, rfsock->sec_level,
|
|
BT_IO_OPT_INVALID);
|
|
if (!io) {
|
|
error("Failed connect: %s", gerr->message);
|
|
g_error_free(gerr);
|
|
return false;
|
|
}
|
|
|
|
g_io_channel_set_close_on_unref(io, FALSE);
|
|
g_io_channel_unref(io);
|
|
|
|
if (write(rfsock->jv_sock, &chan, sizeof(chan)) != sizeof(chan)) {
|
|
error("Error sending RFCOMM channel");
|
|
return false;
|
|
}
|
|
|
|
rfsock->bt_sock = g_io_channel_unix_get_fd(io);
|
|
rfsock_set_buffer(rfsock);
|
|
rfsock->channel = chan;
|
|
connections = g_list_append(connections, rfsock);
|
|
|
|
return true;
|
|
}
|
|
|
|
static void sdp_search_cb(sdp_list_t *recs, int err, gpointer data)
|
|
{
|
|
struct rfcomm_sock *rfsock = data;
|
|
sdp_list_t *list;
|
|
int chan;
|
|
|
|
DBG("");
|
|
|
|
if (err < 0) {
|
|
error("Unable to get SDP record: %s", strerror(-err));
|
|
goto fail;
|
|
}
|
|
|
|
if (!recs || !recs->data) {
|
|
error("No SDP records found");
|
|
goto fail;
|
|
}
|
|
|
|
for (list = recs; list != NULL; list = list->next) {
|
|
sdp_record_t *rec = list->data;
|
|
sdp_list_t *protos;
|
|
|
|
if (sdp_get_access_protos(rec, &protos) < 0) {
|
|
error("Unable to get proto list");
|
|
goto fail;
|
|
}
|
|
|
|
chan = 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 (chan)
|
|
break;
|
|
}
|
|
|
|
if (chan <= 0) {
|
|
error("Could not get RFCOMM channel %d", chan);
|
|
goto fail;
|
|
}
|
|
|
|
DBG("Got RFCOMM channel %d", chan);
|
|
|
|
if (do_rfcomm_connect(rfsock, chan))
|
|
return;
|
|
fail:
|
|
cleanup_rfsock(rfsock);
|
|
}
|
|
|
|
static uint8_t connect_rfcomm(const bdaddr_t *addr, int chan,
|
|
const uint8_t *uuid, uint8_t flags,
|
|
int *hal_sock)
|
|
{
|
|
struct rfcomm_sock *rfsock;
|
|
char address[18];
|
|
uuid_t uu;
|
|
char uuid_str[32];
|
|
|
|
sdp_uuid128_create(&uu, uuid);
|
|
sdp_uuid2strn(&uu, uuid_str, sizeof(uuid_str));
|
|
ba2str(addr, address);
|
|
|
|
DBG("addr %s chan %d flags 0x%02x uuid %s", address, chan, flags,
|
|
uuid_str);
|
|
|
|
if ((!memcmp(uuid, zero_uuid, sizeof(zero_uuid)) && chan <= 0) ||
|
|
!bacmp(addr, BDADDR_ANY)) {
|
|
error("Invalid rfcomm connect params");
|
|
return HAL_STATUS_INVALID;
|
|
}
|
|
|
|
rfsock = create_rfsock(-1, hal_sock);
|
|
if (!rfsock)
|
|
return HAL_STATUS_FAILED;
|
|
|
|
DBG("rfsock %p jv_sock %d hal_sock %d", rfsock, rfsock->jv_sock,
|
|
*hal_sock);
|
|
|
|
rfsock->sec_level = get_sec_level(flags);
|
|
|
|
bacpy(&rfsock->dst, addr);
|
|
|
|
if (!memcmp(uuid, zero_uuid, sizeof(zero_uuid))) {
|
|
if (!do_rfcomm_connect(rfsock, chan))
|
|
goto failed;
|
|
} else {
|
|
|
|
if (bt_search_service(&adapter_addr, &rfsock->dst, &uu,
|
|
sdp_search_cb, rfsock, NULL, 0) < 0) {
|
|
error("Failed to search SDP records");
|
|
goto failed;
|
|
}
|
|
}
|
|
|
|
return HAL_STATUS_SUCCESS;
|
|
|
|
failed:
|
|
cleanup_rfsock(rfsock);
|
|
close(*hal_sock);
|
|
return HAL_STATUS_FAILED;
|
|
}
|
|
|
|
static void handle_connect(const void *buf, uint16_t len)
|
|
{
|
|
const struct hal_cmd_sock_connect *cmd = buf;
|
|
bdaddr_t bdaddr;
|
|
uint8_t status;
|
|
int hal_sock;
|
|
|
|
DBG("");
|
|
|
|
android2bdaddr(cmd->bdaddr, &bdaddr);
|
|
|
|
switch (cmd->type) {
|
|
case HAL_SOCK_RFCOMM:
|
|
status = connect_rfcomm(&bdaddr, cmd->channel, cmd->uuid,
|
|
cmd->flags, &hal_sock);
|
|
break;
|
|
case HAL_SOCK_SCO:
|
|
case HAL_SOCK_L2CAP:
|
|
status = HAL_STATUS_UNSUPPORTED;
|
|
break;
|
|
default:
|
|
status = HAL_STATUS_INVALID;
|
|
break;
|
|
}
|
|
|
|
if (status != HAL_STATUS_SUCCESS)
|
|
goto failed;
|
|
|
|
ipc_send_rsp_full(HAL_SERVICE_ID_SOCK, HAL_OP_SOCK_CONNECT, 0, NULL,
|
|
hal_sock);
|
|
close(hal_sock);
|
|
return;
|
|
|
|
failed:
|
|
ipc_send_rsp(HAL_SERVICE_ID_SOCK, HAL_OP_SOCK_CONNECT, status);
|
|
|
|
}
|
|
|
|
static const struct ipc_handler cmd_handlers[] = {
|
|
/* HAL_OP_SOCK_LISTEN */
|
|
{ handle_listen, false, sizeof(struct hal_cmd_sock_listen) },
|
|
/* HAL_OP_SOCK_CONNECT */
|
|
{ handle_connect, false, sizeof(struct hal_cmd_sock_connect) },
|
|
};
|
|
|
|
void bt_socket_register(const bdaddr_t *addr)
|
|
{
|
|
size_t i;
|
|
|
|
DBG("");
|
|
|
|
/* make sure channels assigned for profiles are reserved and not used
|
|
* for app services
|
|
*/
|
|
for (i = 0; i < G_N_ELEMENTS(profiles); i++)
|
|
if (profiles[i].channel)
|
|
servers[profiles[i].channel].reserved = true;
|
|
|
|
bacpy(&adapter_addr, addr);
|
|
ipc_register(HAL_SERVICE_ID_SOCK, cmd_handlers,
|
|
G_N_ELEMENTS(cmd_handlers));
|
|
}
|
|
|
|
void bt_socket_unregister(void)
|
|
{
|
|
int ch;
|
|
|
|
DBG("");
|
|
|
|
g_list_free_full(connections, cleanup_rfsock);
|
|
|
|
for (ch = 0; ch <= RFCOMM_CHANNEL_MAX; ch++)
|
|
if (servers[ch].rfsock)
|
|
cleanup_rfsock(&servers[ch]);
|
|
|
|
memset(servers, 0, sizeof(servers));
|
|
|
|
ipc_unregister(HAL_SERVICE_ID_SOCK);
|
|
}
|