2012-01-21 01:59:55

by Scott James Remnant

[permalink] [raw]
Subject: [RFC PATCH 0/2] auto-pairing plugin

A few core changes here to support retrying of connections without
losing the original connection request, temporary D-Bus device object
or Agent information - and plugin callbacks for when bonding is
complete and cancelled.

This lets us implement an auto-pair plugin that tries the PIN 0000 for
commonly dumb devices, and retries using the Agent when that PIN fails.
For added cutesyness we save the blacklist we use to avoid repeatedly
sending 0000, so if there really are any devices out there with
different default PINs we can learn about them over time.

Net effect - another thing done in the UI Agent moved into bluetoothd
where it belongs.

*** BLURB HERE ***

Scott James Remnant (2):
plugin: add bonding complete and cancel callbacks with optional retry
autopair: add autopair plugin

Makefile.am | 5 +
acinclude.m4 | 6 ++
plugins/autopair.c | 209 ++++++++++++++++++++++++++++++++++++++++++++++++++++
src/adapter.c | 2 +-
src/device.c | 86 +++++++++++++++++++++
src/device.h | 12 +++
6 files changed, 319 insertions(+), 1 deletions(-)
create mode 100644 plugins/autopair.c

--
1.7.7.3



2012-01-23 14:28:05

by Bastien Nocera

[permalink] [raw]
Subject: Re: [RFC PATCH 0/2] auto-pairing plugin

On Fri, 2012-01-20 at 17:59 -0800, Scott James Remnant wrote:
> A few core changes here to support retrying of connections without
> losing the original connection request, temporary D-Bus device object
> or Agent information - and plugin callbacks for when bonding is
> complete and cancelled.
>
> This lets us implement an auto-pair plugin that tries the PIN 0000 for
> commonly dumb devices, and retries using the Agent when that PIN fails.
> For added cutesyness we save the blacklist we use to avoid repeatedly
> sending 0000, so if there really are any devices out there with
> different default PINs we can learn about them over time.

I'd agree to this if you could make it use the XML database that
gnome-bluetooth uses:
http://git.gnome.org/browse/gnome-bluetooth/tree/wizard/pin-code-database.xml

That would mean that we can try the automatic password, and then
fallback to asking for a PIN if it fails (like it would have for those
devices without any input that use "1234" as the pairing).

> Net effect - another thing done in the UI Agent moved into bluetoothd
> where it belongs.
>
> *** BLURB HERE ***
>
> Scott James Remnant (2):
> plugin: add bonding complete and cancel callbacks with optional retry
> autopair: add autopair plugin
>
> Makefile.am | 5 +
> acinclude.m4 | 6 ++
> plugins/autopair.c | 209 ++++++++++++++++++++++++++++++++++++++++++++++++++++
> src/adapter.c | 2 +-
> src/device.c | 86 +++++++++++++++++++++
> src/device.h | 12 +++
> 6 files changed, 319 insertions(+), 1 deletions(-)
> create mode 100644 plugins/autopair.c
>



2012-01-21 01:59:57

by Scott James Remnant

[permalink] [raw]
Subject: [RFC PATCH 2/2] autopair: add autopair plugin

The autopair plugin sends an initial PIN of 0000 to "dumb" devices such
as mice and headsets (list of classes unashamedly stolen from Android)
and registers a bonding complete callback for that device.

If bonding should fail with the fixed PIN, the device is blacklisted
from auto-pairing and the pairing tried again after a short back-off
period, this time using the Agent's RequestPinCode method.
---
Makefile.am | 5 +
acinclude.m4 | 6 ++
plugins/autopair.c | 209 ++++++++++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 220 insertions(+), 0 deletions(-)
create mode 100644 plugins/autopair.c

diff --git a/Makefile.am b/Makefile.am
index 8dbe603..cdfdb93 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -278,6 +278,11 @@ builtin_modules += dbusoob
builtin_sources += plugins/dbusoob.c
endif

+if AUTOPAIRPLUGIN
+builtin_modules += autopair
+builtin_sources += plugins/autopair.c
+endif
+
if MAINTAINER_MODE
plugin_LTLIBRARIES += plugins/external-dummy.la
plugins_external_dummy_la_SOURCES = plugins/external-dummy.c
diff --git a/acinclude.m4 b/acinclude.m4
index 57fc5e0..187a049 100644
--- a/acinclude.m4
+++ b/acinclude.m4
@@ -218,6 +218,7 @@ AC_DEFUN([AC_ARG_BLUEZ], [
dbusoob_enable=no
wiimote_enable=no
thermometer_enable=no
+ autopair_enable=no

AC_ARG_ENABLE(optimization, AC_HELP_STRING([--disable-optimization], [disable code optimization]), [
optimization_enable=${enableval}
@@ -374,6 +375,10 @@ AC_DEFUN([AC_ARG_BLUEZ], [
thermometer_enable=${enableval}
])

+ AC_ARG_ENABLE(autopair, AC_HELP_STRING([--enable-autopair], [enable auto-pairplugin]), [
+ autopair_enable=${enableval}
+ ])
+
if (test "${fortify_enable}" = "yes"); then
CFLAGS="$CFLAGS -D_FORTIFY_SOURCE=2"
fi
@@ -432,4 +437,5 @@ AC_DEFUN([AC_ARG_BLUEZ], [
AM_CONDITIONAL(DBUSOOBPLUGIN, test "${dbusoob_enable}" = "yes")
AM_CONDITIONAL(WIIMOTEPLUGIN, test "${wiimote_enable}" = "yes")
AM_CONDITIONAL(THERMOMETERPLUGIN, test "${thermometer_enable}" = "yes")
+ AM_CONDITIONAL(AUTOPAIRPLUGIN, test "${autopair_enable}" = "yes")
])
diff --git a/plugins/autopair.c b/plugins/autopair.c
new file mode 100644
index 0000000..6099b0f
--- /dev/null
+++ b/plugins/autopair.c
@@ -0,0 +1,209 @@
+/*
+ *
+ * BlueZ - Bluetooth protocol stack for Linux
+ *
+ * Copyright (C) 2012 Google Inc.
+ *
+ *
+ * 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 <sys/stat.h>
+
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+
+#include <glib.h>
+
+#include <bluetooth/bluetooth.h>
+
+#include "glib-compat.h"
+#include "plugin.h"
+#include "adapter.h"
+#include "device.h"
+#include "storage.h"
+#include "textfile.h"
+#include "bt_ids.h"
+#include "log.h"
+
+static GSList *attempting = NULL;
+static GSList *dynamic_blacklist = NULL;
+
+static gboolean autopair_bondingcb(struct btd_device *device, uint8_t status)
+{
+ GSList *match;
+ bdaddr_t *ba;
+
+ match = g_slist_find(attempting, device);
+ if (!match)
+ return FALSE;
+
+ attempting = g_slist_remove_link(attempting, match);
+ btd_device_unref(device);
+
+ /* successful pair */
+ if (!status)
+ return FALSE;
+
+ /* failed: blacklist and retry with the user's agent */
+ ba = g_new0(bdaddr_t, 1);
+ device_get_address(device, ba, NULL);
+ dynamic_blacklist = g_slist_prepend(dynamic_blacklist, ba);
+
+ return TRUE;
+}
+
+static void autopair_bonding_cancelcb(struct btd_device *device)
+{
+ GSList *match;
+
+ if ((match = g_slist_find(attempting, device))) {
+ attempting = g_slist_remove_link(attempting, match);
+ btd_device_unref (device);
+ }
+}
+
+static gboolean autopair_attempt(struct btd_device *device)
+{
+ if (g_slist_find(attempting, device))
+ return FALSE;
+
+ attempting = g_slist_prepend(attempting, btd_device_ref(device));
+
+ btd_device_register_bonding_cb(device, autopair_bondingcb,
+ autopair_bonding_cancelcb);
+ return TRUE;
+}
+
+static ssize_t autopair_pincb(struct btd_adapter *adapter,
+ struct btd_device *device, char *pinbuf)
+{
+ bdaddr_t local, peer;
+ uint32_t class;
+
+ /* Only autopair on host-initiated connections */
+ if (!device_is_bonding(device, NULL))
+ return 0;
+
+ device_get_address(device, &peer, NULL);
+ adapter_get_address(adapter, &local);
+
+ if (g_slist_find_custom(dynamic_blacklist, &peer, (GCompareFunc) bacmp))
+ return 0;
+
+ if (read_remote_class(&local, &peer, &class) != 0)
+ return 0;
+
+ switch (BLUETOOTH_DEVICE_CLASS(class)) {
+ case BLUETOOTH_DEVICE_CLASS_AUDIO_VIDEO_WEARABLE_HEADSET:
+ case BLUETOOTH_DEVICE_CLASS_AUDIO_VIDEO_HANDSFREE:
+ case BLUETOOTH_DEVICE_CLASS_AUDIO_VIDEO_HEADPHONES:
+ case BLUETOOTH_DEVICE_CLASS_AUDIO_VIDEO_PORTABLE_AUDIO:
+ case BLUETOOTH_DEVICE_CLASS_AUDIO_VIDEO_HIFI_AUDIO:
+ /* These are the classes Android attempts auto-pairing with */
+ case BLUETOOTH_DEVICE_CLASS_PERIPHERAL_POINTING:
+ /* Attempt auto-pairing with mice too */
+ if (autopair_attempt(device)) {
+ DBG("attempting auto-pairing with device (%d)", class);
+ memcpy(pinbuf, "0000", 4);
+ return 4;
+ }
+ break;
+ }
+
+ return 0;
+}
+
+static int autopair_probe(struct btd_adapter *adapter)
+{
+ btd_adapter_register_pin_cb(adapter, autopair_pincb);
+
+ return 0;
+}
+
+static void autopair_remove(struct btd_adapter *adapter)
+{
+ btd_adapter_unregister_pin_cb(adapter, autopair_pincb);
+}
+
+static struct btd_adapter_driver autopair_driver = {
+ .name = "autopair",
+ .probe = autopair_probe,
+ .remove = autopair_remove,
+};
+
+static void autopair_add_blacklist(char *key, char *value, void *data)
+{
+ bdaddr_t *ba;
+
+ if (strcmp(value, "blacklist"))
+ return;
+
+ ba = g_new0(bdaddr_t, 1);
+ str2ba(key, ba);
+ dynamic_blacklist = g_slist_prepend(dynamic_blacklist, ba);
+}
+
+static int autopair_init(void)
+{
+ char filename[PATH_MAX + 1];
+
+ /* Load dynamic blacklist */
+ create_name(filename, PATH_MAX, STORAGEDIR, "autopair", "blacklist");
+ textfile_foreach(filename, autopair_add_blacklist, NULL);
+
+ return btd_register_adapter_driver(&autopair_driver);
+}
+
+static void autopair_exit(void)
+{
+ GSList *l;
+ struct btd_device *device;
+ char filename[PATH_MAX + 1];
+ bdaddr_t *ba;
+ char addr[18];
+
+ btd_unregister_adapter_driver(&autopair_driver);
+
+ /* Unregister device-level callbacks and unreference */
+ for (l = attempting; l != NULL; l = g_slist_next(l)) {
+ device = l->data;
+ btd_device_unregister_bonding_cb(device, autopair_bondingcb,
+ autopair_bonding_cancelcb);
+ btd_device_unref(device);
+ }
+ g_slist_free(attempting);
+
+ /* Save dynamic blacklist */
+ create_name(filename, PATH_MAX, STORAGEDIR, "autopair", "blacklist");
+ create_file(filename, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
+
+ for (l = dynamic_blacklist; l != NULL; l = g_slist_next(l)) {
+ ba = l->data;
+ ba2str(ba, addr);
+ textfile_put(filename, addr, "blacklist");
+ }
+
+ g_slist_free_full(dynamic_blacklist, g_free);
+}
+
+BLUETOOTH_PLUGIN_DEFINE(autopair, VERSION,
+ BLUETOOTH_PLUGIN_PRIORITY_DEFAULT, autopair_init, autopair_exit)
--
1.7.7.3


2012-01-21 01:59:56

by Scott James Remnant

[permalink] [raw]
Subject: [RFC PATCH 1/2] plugin: add bonding complete and cancel callbacks with optional retry

Allow plugins to register a device-level bonding complete callback
which, if it returns TRUE, specifies that a failed bonding attempt
should be retried after a short backoff period.

Since these will likely store state, require them to register a
bonding cancelled callback at the same time so they can clean up.
---
src/adapter.c | 2 +-
src/device.c | 86 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++
src/device.h | 12 ++++++++
3 files changed, 99 insertions(+), 1 deletions(-)

diff --git a/src/adapter.c b/src/adapter.c
index a75a0c4..d489e50 100644
--- a/src/adapter.c
+++ b/src/adapter.c
@@ -2975,7 +2975,7 @@ void adapter_remove_connection(struct btd_adapter *adapter,
if (device_is_authenticating(device))
device_cancel_authentication(device, TRUE);

- if (device_is_temporary(device)) {
+ if (device_is_temporary(device) && !device_is_retrying(device)) {
const char *path = device_get_path(device);

DBG("Removing temporary device %s", path);
diff --git a/src/device.c b/src/device.c
index 16855b1..50dfe8c 100644
--- a/src/device.c
+++ b/src/device.c
@@ -86,6 +86,7 @@ struct bonding_req {
GIOChannel *io;
guint listener_id;
struct btd_device *device;
+ uint8_t capability;
};

struct authentication_req {
@@ -149,6 +150,10 @@ struct btd_device {
guint auto_id; /* Auto connect source id */

gboolean connected;
+ GSList *bonding_callbacks;
+ GSList *bonding_cancel_callbacks;
+ uint8_t bonding_retry_status;
+ guint bonding_retry_timer;

sdp_list_t *tmp_records;

@@ -232,6 +237,9 @@ static void device_free(gpointer user_data)
g_slist_free_full(device->attios, g_free);
g_slist_free_full(device->attios_offline, g_free);

+ g_slist_free(device->bonding_callbacks);
+ g_slist_free(device->bonding_cancel_callbacks);
+
g_attrib_unref(device->attrib);

if (device->tmp_records)
@@ -247,6 +255,9 @@ static void device_free(gpointer user_data)
if (device->auto_id)
g_source_remove(device->auto_id);

+ if (device->bonding_retry_timer)
+ g_source_remove(device->bonding_retry_timer);
+
DBG("%p", device);

g_free(device->authr);
@@ -2340,6 +2351,65 @@ static void device_auth_req_free(struct btd_device *device)
device->authr = NULL;
}

+void btd_device_register_bonding_cb(struct btd_device *device,
+ btd_device_bonding_cb_t cb,
+ btd_device_bonding_cancel_cb_t ccb) {
+ device->bonding_callbacks = g_slist_prepend(
+ device->bonding_callbacks, cb);
+ device->bonding_cancel_callbacks = g_slist_prepend(
+ device->bonding_cancel_callbacks, ccb);
+}
+
+void btd_device_unregister_bonding_cb(struct btd_device *device,
+ btd_device_bonding_cb_t cb,
+ btd_device_bonding_cancel_cb_t ccb) {
+ device->bonding_callbacks = g_slist_remove(
+ device->bonding_callbacks, cb);
+ device->bonding_cancel_callbacks = g_slist_remove(
+ device->bonding_cancel_callbacks, ccb);
+}
+
+static gboolean device_bonding_retry(gpointer data)
+{
+ struct btd_device *device = data;
+ struct btd_adapter *adapter = device_get_adapter(device);
+ struct bonding_req *bonding = device->bonding;
+ int err;
+
+ DBG("retrying");
+ err = adapter_create_bonding(adapter, &device->bdaddr,
+ bonding->capability);
+ if (err < 0) {
+ DBG("retry failed");
+ device_bonding_complete(device, device->bonding_retry_status);
+ }
+
+ return FALSE;
+}
+
+static gboolean device_bonding_get_retry(struct btd_device *device,
+ uint8_t status)
+{
+ GSList *l;
+ btd_device_bonding_cb_t cb;
+ gboolean retry = FALSE;
+
+ for (l = device->bonding_callbacks; l != NULL; l = g_slist_next(l)) {
+ cb = l->data;
+ retry |= cb(device, status);
+ }
+
+ g_slist_free(device->bonding_callbacks);
+ device->bonding_callbacks = NULL;
+
+ return retry;
+}
+
+gboolean device_is_retrying(struct btd_device *device)
+{
+ return device->bonding_retry_timer != 0;
+}
+
void device_bonding_complete(struct btd_device *device, uint8_t status)
{
struct bonding_req *bonding = device->bonding;
@@ -2347,6 +2417,14 @@ void device_bonding_complete(struct btd_device *device, uint8_t status)

DBG("bonding %p status 0x%02x", bonding, status);

+ if (device_bonding_get_retry(device, status) && status) {
+ DBG("retrying in 3s");
+ device->bonding_retry_status = status;
+ device->bonding_retry_timer = g_timeout_add(3000,
+ device_bonding_retry, device);
+ return;
+ }
+
if (auth && auth->type == AUTH_TYPE_NOTIFY && auth->agent)
agent_cancel(auth->agent);

@@ -2437,6 +2515,8 @@ void device_cancel_bonding(struct btd_device *device, uint8_t status)
struct bonding_req *bonding = device->bonding;
DBusMessage *reply;
char addr[18];
+ GSList *l;
+ btd_device_bonding_cancel_cb_t ccb;

if (!bonding)
return;
@@ -2444,6 +2524,12 @@ void device_cancel_bonding(struct btd_device *device, uint8_t status)
ba2str(&device->bdaddr, addr);
DBG("Canceling bonding request for %s", addr);

+ for (l = device->bonding_cancel_callbacks; l != NULL;
+ l = g_slist_next(l)) {
+ ccb = l->data;
+ ccb(device);
+ }
+
if (device->authr)
device_cancel_authentication(device, FALSE);

diff --git a/src/device.h b/src/device.h
index 13005ae..4f7fd53 100644
--- a/src/device.h
+++ b/src/device.h
@@ -73,6 +73,7 @@ void device_set_temporary(struct btd_device *device, gboolean temporary);
void device_set_bonded(struct btd_device *device, gboolean bonded);
void device_set_auto_connect(struct btd_device *device, gboolean enable);
gboolean device_is_connected(struct btd_device *device);
+gboolean device_is_retrying(struct btd_device *device);
DBusMessage *device_create_bonding(struct btd_device *device,
DBusConnection *conn, DBusMessage *msg,
const char *agent_path, uint8_t capability);
@@ -100,6 +101,17 @@ guint device_add_disconnect_watch(struct btd_device *device,
void device_remove_disconnect_watch(struct btd_device *device, guint id);
void device_set_class(struct btd_device *device, uint32_t value);

+typedef gboolean (*btd_device_bonding_cb_t) (struct btd_device *device,
+ uint8_t status);
+typedef void (*btd_device_bonding_cancel_cb_t) (struct btd_device *device);
+
+void btd_device_register_bonding_cb(struct btd_device *dev,
+ btd_device_bonding_cb_t cb,
+ btd_device_bonding_cancel_cb_t ccb);
+void btd_device_unregister_bonding_cb(struct btd_device *dev,
+ btd_device_bonding_cb_t cb,
+ btd_device_bonding_cancel_cb_t ccb);
+
#define BTD_UUIDS(args...) ((const char *[]) { args, NULL } )

struct btd_device_driver {
--
1.7.7.3