From 81d7b6c8eecfdbafab4381e1f1e5d294d33b0e06 Mon Sep 17 00:00:00 2001 From: Alexander Orlenko Date: Sun, 9 Jan 2011 00:32:59 +1000 Subject: Updated .gitignore Added capability/pin/daemon options to bt-agent Added support of PIN Hash Table to lib/bluez/agent.c Added support of Interactive Mode to lib/bluez/agent.c --- .gitignore | 1 + src/bt-agent.c | 166 ++++++++++++++++++++++++++-- src/lib/bluez/agent.c | 296 ++++++++++++++++++++++++++++++++++++++------------ 3 files changed, 385 insertions(+), 78 deletions(-) diff --git a/.gitignore b/.gitignore index 5795628..80994da 100644 --- a/.gitignore +++ b/.gitignore @@ -25,3 +25,4 @@ src/bt-network src/bt-obex src/bt-serial stamp-h1 +test_* diff --git a/src/bt-agent.c b/src/bt-agent.c index 6093b8e..ce791ef 100644 --- a/src/bt-agent.c +++ b/src/bt-agent.c @@ -2,7 +2,7 @@ * * bluez-tools - a set of tools to manage bluetooth devices for linux * - * Copyright (C) 2010 Alexander Orlenko + * Copyright (C) 2010-2011 Alexander Orlenko * * * This program is free software; you can redistribute it and/or modify @@ -29,8 +29,11 @@ #include #include #include +#include +#include #include +#include #include "lib/dbus-common.h" #include "lib/helpers.h" @@ -39,12 +42,100 @@ static gboolean need_unregister = TRUE; static GMainLoop *mainloop = NULL; -static void sigterm_handler(int sig) +static GHashTable *pin_hash_table = NULL; +static gchar *pin_arg = NULL; + +static void read_pin_file(const gchar *filename, GHashTable *pin_hash_table, gboolean first_run) { + g_assert(filename != NULL && strlen(filename) > 0); + g_assert(pin_hash_table != NULL); + + GStatBuf sbuf; + memset(&sbuf, 0, sizeof(sbuf)); + if (g_stat(filename, &sbuf) != 0) { + if (first_run) { + g_printerr("%s: %s\n", filename, strerror(errno)); + exit(EXIT_FAILURE); + } else { + return; + } + } + if (!S_ISREG(sbuf.st_mode)) { + if (first_run) { + g_printerr("%s: It's not a regular file\n", filename); + exit(EXIT_FAILURE); + } else { + return; + } + } + if (sbuf.st_mode & S_IROTH) { + if (first_run) + g_print("Warning! %s is world readable!\n", filename); + } + + FILE *fp = g_fopen(filename, "r"); + if (!fp) { + if (first_run) { + g_printerr("%s: %s\n", filename, strerror(errno)); + exit(EXIT_FAILURE); + } else { + return; + } + } + + g_hash_table_remove_all(pin_hash_table); + + gchar *line = NULL; + size_t len = 0; + ssize_t read; + guint n = 0; + GRegex *regex = g_regex_new("^(\\S+)\\s+(\\S+)$", 0, 0, NULL); + + while ((read = getline(&line, &len, fp)) != -1) { + n++; + + if (g_regex_match_simple("^\\s*(#|$)", line, 0, 0)) + continue; + + GMatchInfo *match_info; + if (g_regex_match(regex, line, 0, &match_info)) { + gchar **t = g_match_info_fetch_all(match_info); + /* Convert MAC to upper case */ + if (g_regex_match_simple("^([0-9a-fA-F]{2}(:|$)){6}$", t[1], 0, 0)) + g_hash_table_insert(pin_hash_table, g_ascii_strup(t[1], -1), g_strdup(t[2])); + else + g_hash_table_insert(pin_hash_table, g_strdup(t[1]), g_strdup(t[2])); + g_strfreev(t); + } else { + if (first_run) + g_print("%d: Invalid line (ignored)\n", n); + } + g_match_info_free(match_info); + } + + g_regex_unref(regex); + if (line) + g_free(line); + fclose(fp); + + first_run = FALSE; + + return; +} + +static void signal_handler(int sig) { - g_message("%s received", sig == SIGTERM ? "SIGTERM" : "SIGINT"); + g_message("%s received", sig == SIGTERM ? "SIGTERM" : (sig == SIGUSR1 ? "SIGUSR1" : "SIGINT")); - if (g_main_loop_is_running(mainloop)) - g_main_loop_quit(mainloop); + if (sig == SIGUSR1 && pin_arg) { + /* Re-read PIN's file */ + g_print("Re-reading PIN's file\n"); + read_pin_file(pin_arg, pin_hash_table, FALSE); + } else if (sig == SIGTERM || sig == SIGINT) { + + + if (g_main_loop_is_running(mainloop)) + g_main_loop_quit(mainloop); + } } static void agent_released(Agent *agent, gpointer data) @@ -56,12 +147,19 @@ static void agent_released(Agent *agent, gpointer data) if (g_main_loop_is_running(mainloop)) g_main_loop_quit(mainloop); + + g_print("Agent released\n"); } static gchar *adapter_arg = NULL; +static gchar *capability_arg = NULL; +static gchar *daemon_arg = FALSE; static GOptionEntry entries[] = { {"adapter", 'a', 0, G_OPTION_ARG_STRING, &adapter_arg, "Adapter Name or MAC", ""}, + {"capability", 'c', 0, G_OPTION_ARG_STRING, &capability_arg, "Agent capability", ""}, + {"pin", 'p', 0, G_OPTION_ARG_STRING, &pin_arg, "Path to the PIN's file"}, + {"daemon", 'd', 0, G_OPTION_ARG_NONE, &daemon_arg, "Run in background (as daemon)"}, {NULL} }; @@ -80,6 +178,11 @@ int main(int argc, char *argv[]) g_option_context_add_main_entries(context, entries, NULL); g_option_context_set_summary(context, "Version "PACKAGE_VERSION); g_option_context_set_description(context, + "`capability` can be one of:\n" + " DisplayOnly\n" + " DisplayYesNo (default)\n" + " KeyboardOnly\n" + " NoInputNoOutput\n\n" //"Report bugs to <"PACKAGE_BUGREPORT">." "Project home page <"PACKAGE_URL">." ); @@ -90,6 +193,21 @@ int main(int argc, char *argv[]) exit(EXIT_FAILURE); } + if (capability_arg) { + if ( + g_strcmp0(capability_arg, "DisplayOnly") != 0 && + g_strcmp0(capability_arg, "DisplayYesNo") != 0 && + g_strcmp0(capability_arg, "KeyboardOnly") != 0 && + g_strcmp0(capability_arg, "NoInputNoOutput") != 0 + ) { + g_print("%s: Invalid capability: %s\n", g_get_prgname(), capability_arg); + g_print("Try `%s --help` for more information.\n", g_get_prgname()); + exit(EXIT_FAILURE); + } + } else { + capability_arg = "DisplayYesNo"; // default value + } + g_option_context_free(context); if (!dbus_system_connect(&error)) { @@ -104,6 +222,12 @@ int main(int argc, char *argv[]) exit(EXIT_FAILURE); } + /* Read PIN's file */ + if (pin_arg) { + pin_hash_table = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free); + read_pin_file(pin_arg, pin_hash_table, TRUE); + } + mainloop = g_main_loop_new(NULL, FALSE); Manager *manager = g_object_new(MANAGER_TYPE, NULL); @@ -111,19 +235,43 @@ int main(int argc, char *argv[]) Adapter *adapter = find_adapter(adapter_arg, &error); exit_if_error(error); - Agent *agent = g_object_new(AGENT_TYPE, NULL); + Agent *agent = g_object_new(AGENT_TYPE, "PinHashTable", pin_hash_table, "Interactive", !daemon_arg, NULL); - adapter_register_agent(adapter, AGENT_DBUS_PATH, "DisplayYesNo", &error); + adapter_register_agent(adapter, AGENT_DBUS_PATH, capability_arg, &error); exit_if_error(error); + g_print("Agent registered\n"); g_signal_connect(agent, "AgentReleased", G_CALLBACK(agent_released), mainloop); - /* Add SIGTERM && SIGINT handlers */ + if (daemon_arg) { + pid_t pid, sid; + + /* Fork off the parent process */ + pid = fork(); + if (pid < 0) + exit(EXIT_FAILURE); + /* Ok, terminate parent proccess */ + if (pid > 0) + exit(EXIT_SUCCESS); + + /* Create a new SID for the child process */ + sid = setsid(); + if (sid < 0) + exit(EXIT_FAILURE); + + /* Close out the standard file descriptors */ + close(STDIN_FILENO); + close(STDOUT_FILENO); + close(STDERR_FILENO); + } + + /* Add SIGTERM/SIGINT/SIGUSR1 handlers */ struct sigaction sa; memset(&sa, 0, sizeof(sa)); - sa.sa_handler = sigterm_handler; + sa.sa_handler = signal_handler; sigaction(SIGTERM, &sa, NULL); sigaction(SIGINT, &sa, NULL); + sigaction(SIGUSR1, &sa, NULL); g_main_loop_run(mainloop); diff --git a/src/lib/bluez/agent.c b/src/lib/bluez/agent.c index bff75c0..dc29706 100644 --- a/src/lib/bluez/agent.c +++ b/src/lib/bluez/agent.c @@ -2,7 +2,7 @@ * * bluez-tools - a set of tools to manage bluetooth devices for linux * - * Copyright (C) 2010 Alexander Orlenko + * Copyright (C) 2010-2011 Alexander Orlenko * * * This program is free software; you can redistribute it and/or modify @@ -41,12 +41,23 @@ #define AGENT_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE((obj), AGENT_TYPE, AgentPrivate)) struct _AgentPrivate { - /* Unused */ - DBusGProxy *proxy; + /* Properties */ + GHashTable *pin_hash_table; /* (extern) name|mac -> pin hash table */ + gboolean interactive; /* Interactive mode */ }; G_DEFINE_TYPE(Agent, agent, G_TYPE_OBJECT); +enum { + PROP_0, + + PROP_PIN_HASH_TABLE, /* write, construct only */ + PROP_INTERACTIVE /* write, construct only */ +}; + +static void _agent_get_property(GObject *object, guint property_id, GValue *value, GParamSpec *pspec); +static void _agent_set_property(GObject *object, guint property_id, const GValue *value, GParamSpec *pspec); + enum { AGENT_RELEASED, @@ -61,9 +72,6 @@ static void agent_dispose(GObject *gobject) dbus_g_connection_unregister_g_object(system_conn, gobject); - /* Proxy free */ - //g_object_unref(self->priv->proxy); - /* Chain up to the parent class */ G_OBJECT_CLASS(agent_parent_class)->dispose(gobject); } @@ -76,6 +84,20 @@ static void agent_class_init(AgentClass *klass) g_type_class_add_private(klass, sizeof(AgentPrivate)); + /* Properties registration */ + GParamSpec *pspec; + + gobject_class->get_property = _agent_get_property; + gobject_class->set_property = _agent_set_property; + + /* object PinHashTable [write, construct only] */ + pspec = g_param_spec_boxed("PinHashTable", "pin_hash_table", "Name|MAC -> PIN hash table", G_TYPE_HASH_TABLE, G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY); + g_object_class_install_property(gobject_class, PROP_PIN_HASH_TABLE, pspec); + + /* boolean Interactive [write, construct only] */ + pspec = g_param_spec_boolean("Interactive", "interactive", "Interactive mode", TRUE, G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY); + g_object_class_install_property(gobject_class, PROP_INTERACTIVE, pspec); + /* Signals registation */ signals[AGENT_RELEASED] = g_signal_new("AgentReleased", G_TYPE_FROM_CLASS(gobject_class), @@ -91,16 +113,65 @@ static void agent_init(Agent *self) g_assert(system_conn != NULL); + /* Properties init */ + self->priv->pin_hash_table = NULL; + self->priv->interactive = TRUE; + dbus_g_connection_register_g_object(system_conn, AGENT_DBUS_PATH, G_OBJECT(self)); - g_print("Agent registered\n"); +} + +static void _agent_get_property(GObject *object, guint property_id, GValue *value, GParamSpec *pspec) +{ + Agent *self = AGENT(object); + + switch (property_id) { + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec); + break; + } +} + +static void _agent_set_property(GObject *object, guint property_id, const GValue *value, GParamSpec *pspec) +{ + Agent *self = AGENT(object); + + switch (property_id) { + case PROP_PIN_HASH_TABLE: + self->priv->pin_hash_table = g_value_get_boxed(value); + break; + + case PROP_INTERACTIVE: + self->priv->interactive = g_value_get_boolean(value); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec); + break; + } } /* Methods */ -gboolean agent_release(Agent *self, GError **error) +static const gchar *find_device_pin(Agent *self, Device *device_obj) { - g_print("Agent released\n"); + if (self->priv->pin_hash_table) { + const gchar *pin_by_addr = g_hash_table_lookup(self->priv->pin_hash_table, device_get_address(device_obj)); + const gchar *pin_by_alias = g_hash_table_lookup(self->priv->pin_hash_table, device_get_alias(device_obj)); + const gchar *pin_all = g_hash_table_lookup(self->priv->pin_hash_table, "*"); + + if (pin_by_addr) + return pin_by_addr; + else if (pin_by_alias) + return pin_by_alias; + else if (pin_all) + return pin_all; + } + + return NULL; +} +gboolean agent_release(Agent *self, GError **error) +{ g_signal_emit(self, signals[AGENT_RELEASED], 0); return TRUE; @@ -109,113 +180,200 @@ gboolean agent_release(Agent *self, GError **error) gboolean agent_request_pin_code(Agent *self, const gchar *device, gchar **ret, GError **error) { Device *device_obj = g_object_new(DEVICE_TYPE, "DBusObjectPath", device, NULL); - g_print("Device: %s (%s)\n", device_get_alias(device_obj), device_get_address(device_obj)); + const gchar *pin = find_device_pin(self, device_obj); + + if (self->priv->interactive) + g_print("Device: %s (%s)\n", device_get_alias(device_obj), device_get_address(device_obj)); + g_object_unref(device_obj); - *ret = g_new0(gchar, 17); - g_print("Enter PIN code: "); - errno = 0; - if (scanf("%16s", *ret) == EOF && errno) { - g_warning("%s\n", strerror(errno)); + /* Try to use found PIN */ + if (pin != NULL) { + if (self->priv->interactive) + g_print("PIN found\n"); + + *ret = g_new0(gchar, 17); + sscanf(pin, "%16s", *ret); + + return TRUE; + } else if (self->priv->interactive) { + g_print("Enter PIN code: "); + *ret = g_new0(gchar, 17); + errno = 0; + if (scanf("%16s", *ret) == EOF && errno) { + g_warning("%s\n", strerror(errno)); + } + + return TRUE; } - return TRUE; + + return FALSE; // Request rejected } gboolean agent_request_passkey(Agent *self, const gchar *device, guint *ret, GError **error) { Device *device_obj = g_object_new(DEVICE_TYPE, "DBusObjectPath", device, NULL); - g_print("Device: %s (%s)\n", device_get_alias(device_obj), device_get_address(device_obj)); + const gchar *pin = find_device_pin(self, device_obj); + + if (self->priv->interactive) + g_print("Device: %s (%s)\n", device_get_alias(device_obj), device_get_address(device_obj)); + g_object_unref(device_obj); - g_print("Enter passkey: "); - errno = 0; - if (scanf("%u", ret) == EOF && errno) { - g_warning("%s\n", strerror(errno)); + /* Try to use found PIN */ + if (pin != NULL) { + if (self->priv->interactive) + g_print("Passkey found\n"); + + sscanf(pin, "%u", ret); + + return TRUE; + } else if (self->priv->interactive) { + g_print("Enter passkey: "); + errno = 0; + if (scanf("%u", ret) == EOF && errno) { + g_warning("%s\n", strerror(errno)); + } + + return TRUE; } - return TRUE; + + return FALSE; // Request rejected } gboolean agent_display_passkey(Agent *self, const gchar *device, guint passkey, guint8 entered, GError **error) { Device *device_obj = g_object_new(DEVICE_TYPE, "DBusObjectPath", device, NULL); - g_print("Device: %s (%s)\n", device_get_alias(device_obj), device_get_address(device_obj)); + const gchar *pin = find_device_pin(self, device_obj); + + if (self->priv->interactive) + g_print("Device: %s (%s)\n", device_get_alias(device_obj), device_get_address(device_obj)); + g_object_unref(device_obj); - g_print("Passkey: %u, entered: %u\n", passkey, entered); - return TRUE; + if (self->priv->interactive) { + g_print("Passkey: %u, entered: %u\n", passkey, entered); + + return TRUE; + } else if (pin != NULL) { + /* OK, device found */ + return TRUE; + } + + return FALSE; // Request rejected } gboolean agent_request_confirmation(Agent *self, const gchar *device, guint passkey, GError **error) { Device *device_obj = g_object_new(DEVICE_TYPE, "DBusObjectPath", device, NULL); - g_print("Device: %s (%s)\n", device_get_alias(device_obj), device_get_address(device_obj)); + const gchar *pin = find_device_pin(self, device_obj); + + if (self->priv->interactive) + g_print("Device: %s (%s)\n", device_get_alias(device_obj), device_get_address(device_obj)); + g_object_unref(device_obj); - gchar yn[4] = {0,}; - g_print("Confirm passkey: %u (yes/no)? ", passkey); - errno = 0; - if (scanf("%3s", yn) == EOF && errno) { - g_warning("%s\n", strerror(errno)); - } - if (g_strcmp0(yn, "y") == 0 || g_strcmp0(yn, "yes") == 0) { - return TRUE; - } else { - // TODO: Fix error code - if (error) - *error = g_error_new(g_quark_from_static_string("org.bluez.Error.Rejected"), 0, "Passkey doesn't match"); - return FALSE; + /* Try to use found PIN */ + if (pin != NULL) { + guint passkey_t; + sscanf(pin, "%u", &passkey_t); + + if (g_strcmp0(pin, "*") == 0 || passkey_t == passkey) { + if (self->priv->interactive) + g_print("Passkey confirmed\n"); + + return TRUE; + } else { + if (self->priv->interactive) + g_print("Passkey rejected\n"); + + // TODO: Fix error code + if (error) + *error = g_error_new(g_quark_from_static_string("org.bluez.Error.Rejected"), 0, "Passkey doesn't match"); + return FALSE; + } + } else if (self->priv->interactive) { + g_print("Confirm passkey: %u (yes/no)? ", passkey); + gchar yn[4] = {0,}; + errno = 0; + if (scanf("%3s", yn) == EOF && errno) { + g_warning("%s\n", strerror(errno)); + } + if (g_strcmp0(yn, "y") == 0 || g_strcmp0(yn, "yes") == 0) { + return TRUE; + } else { + // TODO: Fix error code + if (error) + *error = g_error_new(g_quark_from_static_string("org.bluez.Error.Rejected"), 0, "Passkey doesn't match"); + return FALSE; + } } - return TRUE; + return FALSE; // Request rejected } gboolean agent_authorize(Agent *self, const gchar *device, const gchar *uuid, GError **error) { Device *device_obj = g_object_new(DEVICE_TYPE, "DBusObjectPath", device, NULL); - g_print("Device: %s (%s)\n", device_get_alias(device_obj), device_get_address(device_obj)); + const gchar *pin = find_device_pin(self, device_obj); + + if (self->priv->interactive) + g_print("Device: %s (%s)\n", device_get_alias(device_obj), device_get_address(device_obj)); + g_object_unref(device_obj); - gchar yn[4] = {0,}; - g_print("Authorize a connection to: %s (yes/no)? ", uuid2name(uuid)); - errno = 0; - if (scanf("%3s", yn) == EOF && errno) { - g_warning("%s\n", strerror(errno)); - } - if (g_strcmp0(yn, "y") == 0 || g_strcmp0(yn, "yes") == 0) { + if (pin != NULL) { + if (self->priv->interactive) + g_print("Connection to %s authorized\n", uuid2name(uuid)); + return TRUE; - } else { - // TODO: Fix error code - if (error) - *error = g_error_new(g_quark_from_static_string("org.bluez.Error.Rejected"), 0, "Connection rejected by user"); - return FALSE; + } else if (self->priv->interactive) { + g_print("Authorize a connection to: %s (yes/no)? ", uuid2name(uuid)); + gchar yn[4] = {0,}; + errno = 0; + if (scanf("%3s", yn) == EOF && errno) { + g_warning("%s\n", strerror(errno)); + } + if (g_strcmp0(yn, "y") == 0 || g_strcmp0(yn, "yes") == 0) { + return TRUE; + } else { + // TODO: Fix error code + if (error) + *error = g_error_new(g_quark_from_static_string("org.bluez.Error.Rejected"), 0, "Connection rejected by user"); + return FALSE; + } } - return TRUE; + return FALSE; // Request rejected } gboolean agent_confirm_mode_change(Agent *self, const gchar *mode, GError **error) { - gchar yn[4] = {0,}; - g_print("Confirm mode change: %s (yes/no)? ", mode); - errno = 0; - if (scanf("%3s", yn) == EOF && errno) { - g_warning("%s\n", strerror(errno)); - } - if (g_strcmp0(yn, "y") == 0 || g_strcmp0(yn, "yes") == 0) { - return TRUE; - } else { - // TODO: Fix error code - if (error) - *error = g_error_new(g_quark_from_static_string("org.bluez.Error.Rejected"), 0, "Confirmation rejected by user"); - return FALSE; + if (self->priv->interactive) { + g_print("Confirm mode change: %s (yes/no)? ", mode); + gchar yn[4] = {0,}; + errno = 0; + if (scanf("%3s", yn) == EOF && errno) { + g_warning("%s\n", strerror(errno)); + } + if (g_strcmp0(yn, "y") == 0 || g_strcmp0(yn, "yes") == 0) { + return TRUE; + } else { + // TODO: Fix error code + if (error) + *error = g_error_new(g_quark_from_static_string("org.bluez.Error.Rejected"), 0, "Confirmation rejected by user"); + return FALSE; + } } - return TRUE; + return TRUE; // Request accepted } gboolean agent_cancel(Agent *self, GError **error) { - g_print("Request cancelled\n"); + if (self->priv->interactive) + g_print("Request cancelled\n"); + return TRUE; } -- cgit v1.2.1