Move Asterisk-addons modules into the main Asterisk source tree.
authorRussell Bryant <russell@russellbryant.com>
Tue, 30 Jun 2009 16:40:38 +0000 (16:40 +0000)
committerRussell Bryant <russell@russellbryant.com>
Tue, 30 Jun 2009 16:40:38 +0000 (16:40 +0000)
Someone asked yesterday, "is there a good reason why we can't just put these
modules in Asterisk?".  After a brief discussion, as long as the modules are
clearly set aside in their own directory and not enabled by default, it is
perfectly fine.

For more information about why a module goes in addons, see README-addons.txt.

chan_ooh323 does not currently compile as it is behind some trunk API updates.
However, it will not build by default, so it should be okay for now.

git-svn-id: https://origsvn.digium.com/svn/asterisk/trunk@204413 65c4cc65-6c06-0410-ace0-fbb531ad65f3

113 files changed:
Makefile
README-addons.txt [new file with mode: 0644]
UPGRADE.txt
addons/Makefile [new file with mode: 0644]
addons/app_addon_sql_mysql.c [new file with mode: 0644]
addons/app_saycountpl.c [new file with mode: 0644]
addons/cdr_addon_mysql.c [new file with mode: 0644]
addons/chan_mobile.c [new file with mode: 0644]
addons/chan_ooh323.c [new file with mode: 0644]
addons/chan_ooh323.h [new file with mode: 0644]
addons/format_mp3.c [new file with mode: 0644]
addons/mp3/MPGLIB_README [new file with mode: 0644]
addons/mp3/MPGLIB_TODO [new file with mode: 0644]
addons/mp3/Makefile [new file with mode: 0644]
addons/mp3/README [new file with mode: 0644]
addons/mp3/common.c [new file with mode: 0644]
addons/mp3/dct64_i386.c [new file with mode: 0644]
addons/mp3/decode_i386.c [new file with mode: 0644]
addons/mp3/decode_ntom.c [new file with mode: 0644]
addons/mp3/huffman.h [new file with mode: 0644]
addons/mp3/interface.c [new file with mode: 0644]
addons/mp3/layer3.c [new file with mode: 0644]
addons/mp3/mpg123.h [new file with mode: 0644]
addons/mp3/mpglib.h [new file with mode: 0644]
addons/mp3/tabinit.c [new file with mode: 0644]
addons/ooh323c/COPYING [new file with mode: 0644]
addons/ooh323c/README [new file with mode: 0644]
addons/ooh323c/src/Makefile.in [new file with mode: 0644]
addons/ooh323c/src/context.c [new file with mode: 0644]
addons/ooh323c/src/decode.c [new file with mode: 0644]
addons/ooh323c/src/dlist.c [new file with mode: 0644]
addons/ooh323c/src/dlist.h [new file with mode: 0644]
addons/ooh323c/src/encode.c [new file with mode: 0644]
addons/ooh323c/src/errmgmt.c [new file with mode: 0644]
addons/ooh323c/src/eventHandler.c [new file with mode: 0644]
addons/ooh323c/src/eventHandler.h [new file with mode: 0644]
addons/ooh323c/src/h323/H235-SECURITY-MESSAGES.h [new file with mode: 0644]
addons/ooh323c/src/h323/H235-SECURITY-MESSAGESDec.c [new file with mode: 0644]
addons/ooh323c/src/h323/H235-SECURITY-MESSAGESEnc.c [new file with mode: 0644]
addons/ooh323c/src/h323/H323-MESSAGES.c [new file with mode: 0644]
addons/ooh323c/src/h323/H323-MESSAGES.h [new file with mode: 0644]
addons/ooh323c/src/h323/H323-MESSAGESDec.c [new file with mode: 0644]
addons/ooh323c/src/h323/H323-MESSAGESEnc.c [new file with mode: 0644]
addons/ooh323c/src/h323/MULTIMEDIA-SYSTEM-CONTROL.c [new file with mode: 0644]
addons/ooh323c/src/h323/MULTIMEDIA-SYSTEM-CONTROL.h [new file with mode: 0644]
addons/ooh323c/src/h323/MULTIMEDIA-SYSTEM-CONTROLDec.c [new file with mode: 0644]
addons/ooh323c/src/h323/MULTIMEDIA-SYSTEM-CONTROLEnc.c [new file with mode: 0644]
addons/ooh323c/src/memheap.c [new file with mode: 0644]
addons/ooh323c/src/memheap.h [new file with mode: 0644]
addons/ooh323c/src/ooCalls.c [new file with mode: 0644]
addons/ooh323c/src/ooCalls.h [new file with mode: 0644]
addons/ooh323c/src/ooCapability.c [new file with mode: 0644]
addons/ooh323c/src/ooCapability.h [new file with mode: 0644]
addons/ooh323c/src/ooCmdChannel.c [new file with mode: 0644]
addons/ooh323c/src/ooCmdChannel.h [new file with mode: 0644]
addons/ooh323c/src/ooCommon.h [new file with mode: 0644]
addons/ooh323c/src/ooDateTime.c [new file with mode: 0644]
addons/ooh323c/src/ooDateTime.h [new file with mode: 0644]
addons/ooh323c/src/ooGkClient.c [new file with mode: 0644]
addons/ooh323c/src/ooGkClient.h [new file with mode: 0644]
addons/ooh323c/src/ooLogChan.c [new file with mode: 0644]
addons/ooh323c/src/ooLogChan.h [new file with mode: 0644]
addons/ooh323c/src/ooSocket.c [new file with mode: 0644]
addons/ooh323c/src/ooSocket.h [new file with mode: 0644]
addons/ooh323c/src/ooStackCmds.c [new file with mode: 0644]
addons/ooh323c/src/ooStackCmds.h [new file with mode: 0644]
addons/ooh323c/src/ooTimer.c [new file with mode: 0644]
addons/ooh323c/src/ooTimer.h [new file with mode: 0644]
addons/ooh323c/src/ooUtils.c [new file with mode: 0644]
addons/ooh323c/src/ooUtils.h [new file with mode: 0644]
addons/ooh323c/src/ooasn1.h [new file with mode: 0644]
addons/ooh323c/src/oochannels.c [new file with mode: 0644]
addons/ooh323c/src/oochannels.h [new file with mode: 0644]
addons/ooh323c/src/ooh245.c [new file with mode: 0644]
addons/ooh323c/src/ooh245.h [new file with mode: 0644]
addons/ooh323c/src/ooh323.c [new file with mode: 0644]
addons/ooh323c/src/ooh323.h [new file with mode: 0644]
addons/ooh323c/src/ooh323ep.c [new file with mode: 0644]
addons/ooh323c/src/ooh323ep.h [new file with mode: 0644]
addons/ooh323c/src/oohdr.h [new file with mode: 0644]
addons/ooh323c/src/ooper.h [new file with mode: 0644]
addons/ooh323c/src/ooports.c [new file with mode: 0644]
addons/ooh323c/src/ooports.h [new file with mode: 0644]
addons/ooh323c/src/ooq931.c [new file with mode: 0644]
addons/ooh323c/src/ooq931.h [new file with mode: 0644]
addons/ooh323c/src/ootrace.c [new file with mode: 0644]
addons/ooh323c/src/ootrace.h [new file with mode: 0644]
addons/ooh323c/src/ootypes.h [new file with mode: 0644]
addons/ooh323c/src/perutil.c [new file with mode: 0644]
addons/ooh323c/src/printHandler.c [new file with mode: 0644]
addons/ooh323c/src/printHandler.h [new file with mode: 0644]
addons/ooh323c/src/rtctype.c [new file with mode: 0644]
addons/ooh323c/src/rtctype.h [new file with mode: 0644]
addons/ooh323cDriver.c [new file with mode: 0644]
addons/ooh323cDriver.h [new file with mode: 0644]
addons/res_config_mysql.c [new file with mode: 0644]
autoconf/ast_ext_tool_check.m4
build_tools/menuselect-deps.in
configs/cdr_mysql.conf.sample [new file with mode: 0644]
configs/mobile.conf.sample [new file with mode: 0644]
configs/mysql.conf.sample [new file with mode: 0644]
configs/ooh323.conf.sample [new file with mode: 0644]
configs/res_mysql.conf.sample [new file with mode: 0644]
configure
configure.ac
doc/tex/Makefile
doc/tex/asterisk.tex
doc/tex/cdrdriver.tex
doc/tex/chan_mobile.tex [new file with mode: 0644]
include/asterisk/autoconfig.h.in
include/asterisk/mod_format.h
main/file.c
makeopts.in

index a74b95b..0d57d65 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -293,7 +293,7 @@ endif
 #      value directly to ASTCFLAGS
 ASTCFLAGS+=$(MALLOC_DEBUG)$(OPTIONS)
 
-MOD_SUBDIRS:=channels pbx apps codecs formats cdr cel bridges funcs tests main res $(LOCAL_MOD_SUBDIRS)
+MOD_SUBDIRS:=addons channels pbx apps codecs formats cdr cel bridges funcs tests main res $(LOCAL_MOD_SUBDIRS)
 OTHER_SUBDIRS:=utils agi
 SUBDIRS:=$(OTHER_SUBDIRS) $(MOD_SUBDIRS)
 SUBDIRS_INSTALL:=$(SUBDIRS:%=%-install)
diff --git a/README-addons.txt b/README-addons.txt
new file mode 100644 (file)
index 0000000..4d5e198
--- /dev/null
@@ -0,0 +1,27 @@
+===============================================================================
+===                       Asterisk Add-on Modules                           ===
+===============================================================================
+
+    This document pertains to the modules that reside in the addons/
+subdirectory of the source tree.  By default, these modules are not compiled
+and installed.  If you choose to enable them, you must be aware of what
+potential licensing and/or patent implications that has on your usage and
+distribution of Asterisk.
+
+    Even though Asterisk is released as open source under the terms of the
+GPLv2 (see LICENSE for details), no core functionality in Asterisk has any 
+dependencies on libraries that are licensed under the GPL.  One reason a module
+may be in the add-ons category is that it may have a GPL dependency.  Since
+these dependencies are not compatible with dual licensing of Asterisk, the
+dependant modules are set aside to make it clear that they may not be used 
+with commercial versions of Asterisk, unless other licensing arrangements are 
+made with the copyright holders of those dependencies.
+
+    Another reason that modules may be set aside is that there may be
+additional restrictions on the usage of the code imposed by the license or
+related patents.  The MySQL and MP3 modules are examples of this.
+       
+    If you have any questions, contact your lawyer.
+
+===============================================================================
+===============================================================================
index d670e41..651ea18 100644 (file)
 
 From 1.6.2 to 1.6.3:
 
+* Asterisk-addons no longer exists as an independent package.  Those modules
+  now live in the addons directory of the main Asterisk source tree.  They
+  are not enabled by default.  For more information about why modules live in
+  addons, see README-addons.txt.
+
 * The rarely used 'event_log' and LOG_EVENT channel have been removed; the few
   users of this channel in the tree have been converted to LOG_NOTICE or removed
   (in cases where the same message was already generated to another channel).
diff --git a/addons/Makefile b/addons/Makefile
new file mode 100644 (file)
index 0000000..c937597
--- /dev/null
@@ -0,0 +1,43 @@
+#
+# Asterisk -- A telephony toolkit for Linux.
+#
+# Makefile for Add-on Modules
+#
+# Copyright (C) 2009, Digium, Inc.
+#
+# This program is free software, distributed under the terms of
+# the GNU General Public License
+#
+
+-include $(ASTTOPDIR)/menuselect.makeopts $(ASTTOPDIR)/menuselect.makedeps
+
+MODULE_PREFIX=app bridge cdr cel chan codec format func pbx res test
+MENUSELECT_CATEGORY=Addons
+MENUSELECT_DESCRIPTION=Add-ons (See README-addons.txt)
+
+H323OBJS:=ooCmdChannel.o ooLogChan.o ooUtils.o ooGkClient.o context.o \
+       ooDateTime.o decode.o dlist.o encode.o errmgmt.o \
+       memheap.o ootrace.o oochannels.o ooh245.o ooports.o \
+       ooq931.o ooCapability.o ooSocket.o perutil.o eventHandler.o \
+       ooCalls.o ooStackCmds.o ooh323.o ooh323ep.o printHandler.o \
+       rtctype.o ooTimer.o h323/H235-SECURITY-MESSAGESDec.o \
+       h323/H235-SECURITY-MESSAGESEnc.o h323/H323-MESSAGES.o h323/H323-MESSAGESDec.o \
+       h323/H323-MESSAGESEnc.o h323/MULTIMEDIA-SYSTEM-CONTROL.o \
+       h323/MULTIMEDIA-SYSTEM-CONTROLDec.o h323/MULTIMEDIA-SYSTEM-CONTROLEnc.o
+
+H323CFLAGS:=-Iooh323c/src -Iooh323c/src/h323
+
+all: _all
+
+include $(ASTTOPDIR)/Makefile.moddir_rules
+
+clean::
+       $(MAKE) -C mp3 clean
+       rm -f $(addprefix ooh323c/src/,$(H323OBJS))
+
+$(if $(filter format_mp3,$(EMBEDDED_MODS)),modules.link,format_mp3.so): mp3/common.o mp3/dct64_i386.o mp3/decode_ntom.o mp3/layer3.o mp3/tabinit.o mp3/interface.o
+
+chan_ooh323.o: ASTCFLAGS+=$(H323CFLAGS)
+
+$(if $(filter chan_ooh323,$(EMBEDDED_MODS)),modules.link,chan_ooh323.so): ASTCFLAGS+=$(H323CFLAGS)
+$(if $(filter chan_ooh323,$(EMBEDDED_MODS)),modules.link,chan_ooh323.so): $(addprefix ooh323c/src/,$(H323OBJS)) chan_ooh323.o ooh323cDriver.o
diff --git a/addons/app_addon_sql_mysql.c b/addons/app_addon_sql_mysql.c
new file mode 100644 (file)
index 0000000..db33c11
--- /dev/null
@@ -0,0 +1,611 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2004, Constantine Filin and Christos Ricudis
+ *
+ * Christos Ricudis <ricudis@itc.auth.gr>
+ * Constantine Filin <cf@intermedia.net>
+ *
+ * See http://www.asterisk.org for more information about
+ * the Asterisk project. Please do not directly contact
+ * any of the maintainers of this project for assistance;
+ * the project provides a web site, mailing lists and IRC
+ * channels for your use.
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE file
+ * at the top of the source tree.
+ */
+
+/*!
+ * \file
+ * \brief MYSQL dialplan application
+ * \ingroup applications
+ */
+
+/*** MODULEINFO
+       <depend>mysqlclient</depend>
+       <defaultenabled>no</defaultenabled>
+ ***/
+
+#include "asterisk.h"
+
+#include <mysql/mysql.h>
+
+#include "asterisk/file.h"
+#include "asterisk/logger.h"
+#include "asterisk/channel.h"
+#include "asterisk/pbx.h"
+#include "asterisk/module.h"
+#include "asterisk/linkedlists.h"
+#include "asterisk/chanvars.h"
+#include "asterisk/lock.h"
+#include "asterisk/options.h"
+#include "asterisk/app.h"
+#include "asterisk/config.h"
+
+#define EXTRA_LOG 0
+
+enum { NULLSTRING, NULLVALUE, EMPTYSTRING } nullvalue = NULLSTRING;
+
+static const char app[] = "MYSQL";
+
+static const char synopsis[] = "Do several mySQLy things";
+
+static const char descrip[] =
+"MYSQL():  Do several mySQLy things\n"
+"Syntax:\n"
+"  MYSQL(Set timeout <num>)\n"
+"    Set the connection timeout, in seconds.\n"
+"  MYSQL(Connect connid dhhost dbuser dbpass dbname)\n"
+"    Connects to a database.  Arguments contain standard MySQL parameters\n"
+"    passed to function mysql_real_connect.  Connection identifer returned\n"
+"    in ${connid}\n"
+"  MYSQL(Query resultid ${connid} query-string)\n"
+"    Executes standard MySQL query contained in query-string using established\n"
+"    connection identified by ${connid}. Result of query is stored in ${resultid}.\n"
+"  MYSQL(Nextresult resultid ${connid}\n"
+"    If last query returned more than one result set, it stores the next\n"
+"    result set in ${resultid}. It's useful with stored procedures\n"
+"  MYSQL(Fetch fetchid ${resultid} var1 var2 ... varN)\n"
+"    Fetches a single row from a result set contained in ${result_identifier}.\n"
+"    Assigns returned fields to ${var1} ... ${varn}.  ${fetchid} is set TRUE\n"
+"    if additional rows exist in result set.\n"
+"  MYSQL(Clear ${resultid})\n"
+"    Frees memory and datastructures associated with result set.\n"
+"  MYSQL(Disconnect ${connid})\n"
+"    Disconnects from named connection to MySQL.\n"
+"  On exit, always returns 0. Sets MYSQL_STATUS to 0 on success and -1 on error.\n";
+
+/*
+EXAMPLES OF USE :
+
+exten => s,2,MYSQL(Connect connid localhost asterisk mypass credit)
+exten => s,3,MYSQL(Query resultid ${connid} SELECT username,credit FROM credit WHERE callerid=${CALLERIDNUM})
+exten => s,4,MYSQL(Fetch fetchid ${resultid} datavar1 datavar2)
+exten => s,5,GotoIf(${fetchid}?6:8)
+exten => s,6,Festival("User ${datavar1} currently has credit balance of ${datavar2} dollars.")
+exten => s,7,Goto(s,4)
+exten => s,8,MYSQL(Clear ${resultid})
+exten => s,9,MYSQL(Disconnect ${connid})
+*/
+
+AST_MUTEX_DEFINE_STATIC(_mysql_mutex);
+
+#define MYSQL_CONFIG "mysql.conf"
+#define AST_MYSQL_ID_DUMMY   0
+#define AST_MYSQL_ID_CONNID  1
+#define AST_MYSQL_ID_RESID   2
+#define AST_MYSQL_ID_FETCHID 3
+
+static int autoclear = 0;
+
+static void mysql_ds_destroy(void *data);
+static void mysql_ds_fixup(void *data, struct ast_channel *oldchan, struct ast_channel *newchan);
+
+static struct ast_datastore_info mysql_ds_info = {
+       .type = "APP_ADDON_SQL_MYSQL",
+       .destroy = mysql_ds_destroy,
+       .chan_fixup = mysql_ds_fixup,
+};
+
+struct ast_MYSQL_id {
+       struct ast_channel *owner;
+       int identifier_type; /* 0=dummy, 1=connid, 2=resultid */
+       int identifier;
+       void *data;
+       AST_LIST_ENTRY(ast_MYSQL_id) entries;
+} *ast_MYSQL_id;
+
+AST_LIST_HEAD(MYSQLidshead,ast_MYSQL_id) _mysql_ids_head;
+
+static void mysql_ds_destroy(void *data)
+{
+       /* Destroy any IDs owned by the channel */
+       struct ast_MYSQL_id *i;
+       if (AST_LIST_LOCK(&_mysql_ids_head)) {
+               ast_log(LOG_WARNING, "Unable to lock identifiers list\n");
+       } else {
+               AST_LIST_TRAVERSE_SAFE_BEGIN(&_mysql_ids_head, i, entries) {
+                       if (i->owner == data) {
+                               AST_LIST_REMOVE_CURRENT(entries);
+                               if (i->identifier_type == AST_MYSQL_ID_CONNID) {
+                                       /* Drop connection */
+                                       mysql_close(i->data);
+                               } else if (i->identifier_type == AST_MYSQL_ID_RESID) {
+                                       /* Drop result */
+                                       mysql_free_result(i->data);
+                               }
+                               ast_free(i);
+                       }
+               }
+               AST_LIST_TRAVERSE_SAFE_END
+               AST_LIST_UNLOCK(&_mysql_ids_head);
+       }
+}
+
+static void mysql_ds_fixup(void *data, struct ast_channel *oldchan, struct ast_channel *newchan)
+{
+       /* Destroy any IDs owned by the channel */
+       struct ast_MYSQL_id *i;
+       if (AST_LIST_LOCK(&_mysql_ids_head)) {
+               ast_log(LOG_WARNING, "Unable to lock identifiers list\n");
+       } else {
+               AST_LIST_TRAVERSE_SAFE_BEGIN(&_mysql_ids_head, i, entries) {
+                       if (i->owner == data) {
+                               AST_LIST_REMOVE_CURRENT(entries);
+                               if (i->identifier_type == AST_MYSQL_ID_CONNID) {
+                                       /* Drop connection */
+                                       mysql_close(i->data);
+                               } else if (i->identifier_type == AST_MYSQL_ID_RESID) {
+                                       /* Drop result */
+                                       mysql_free_result(i->data);
+                               }
+                               ast_free(i);
+                       }
+               }
+               AST_LIST_TRAVERSE_SAFE_END
+               AST_LIST_UNLOCK(&_mysql_ids_head);
+       }
+}
+
+/* helpful procs */
+static void *find_identifier(int identifier, int identifier_type)
+{
+       struct MYSQLidshead *headp = &_mysql_ids_head;
+       struct ast_MYSQL_id *i;
+       void *res=NULL;
+       int found=0;
+
+       if (AST_LIST_LOCK(headp)) {
+               ast_log(LOG_WARNING, "Unable to lock identifiers list\n");
+       } else {
+               AST_LIST_TRAVERSE(headp, i, entries) {
+                       if ((i->identifier == identifier) && (i->identifier_type == identifier_type)) {
+                               found = 1;
+                               res = i->data;
+                               break;
+                       }
+               }
+               if (!found) {
+                       ast_log(LOG_WARNING, "Identifier %d, identifier_type %d not found in identifier list\n", identifier, identifier_type);
+               }
+               AST_LIST_UNLOCK(headp);
+       }
+
+       return res;
+}
+
+static int add_identifier(struct ast_channel *chan, int identifier_type, void *data)
+{
+       struct ast_MYSQL_id *i = NULL, *j = NULL;
+       struct MYSQLidshead *headp = &_mysql_ids_head;
+       int maxidentifier = 0;
+
+       if (AST_LIST_LOCK(headp)) {
+               ast_log(LOG_WARNING, "Unable to lock identifiers list\n");
+               return -1;
+       } else {
+               i = malloc(sizeof(*i));
+               AST_LIST_TRAVERSE(headp, j, entries) {
+                       if (j->identifier > maxidentifier) {
+                               maxidentifier = j->identifier;
+                       }
+               }
+               i->identifier = maxidentifier + 1;
+               i->identifier_type = identifier_type;
+               i->data = data;
+               i->owner = chan;
+               AST_LIST_INSERT_HEAD(headp, i, entries);
+               AST_LIST_UNLOCK(headp);
+       }
+       return i->identifier;
+}
+
+static int del_identifier(int identifier, int identifier_type)
+{
+       struct ast_MYSQL_id *i;
+       struct MYSQLidshead *headp = &_mysql_ids_head;
+       int found = 0;
+
+       if (AST_LIST_LOCK(headp)) {
+               ast_log(LOG_WARNING, "Unable to lock identifiers list\n");
+       } else {
+               AST_LIST_TRAVERSE(headp, i, entries) {
+                       if ((i->identifier == identifier) &&
+                           (i->identifier_type == identifier_type)) {
+                               AST_LIST_REMOVE(headp, i, entries);
+                               free(i);
+                               found = 1;
+                               break;
+                       }
+               }
+               AST_LIST_UNLOCK(headp);
+       }
+
+       if (found == 0) {
+               ast_log(LOG_WARNING, "Could not find identifier %d, identifier_type %d in list to delete\n", identifier, identifier_type);
+               return -1;
+       } else {
+               return 0;
+       }
+}
+
+static int set_asterisk_int(struct ast_channel *chan, char *varname, int id)
+{
+       if (id >= 0) {
+               char s[12] = "";
+               snprintf(s, sizeof(s), "%d", id);
+               ast_debug(5, "MYSQL: setting var '%s' to value '%s'\n", varname, s);
+               pbx_builtin_setvar_helper(chan, varname, s);
+       }
+       return id;
+}
+
+static int add_identifier_and_set_asterisk_int(struct ast_channel *chan, char *varname, int identifier_type, void *data)
+{
+       return set_asterisk_int(chan, varname, add_identifier(chan, identifier_type, data));
+}
+
+static int safe_scan_int(char **data, char *delim, int def)
+{
+       char *end;
+       int res = def;
+       char *s = strsep(data, delim);
+       if (s) {
+               res = strtol(s, &end, 10);
+               if (*end)
+                       res = def;  /* not an integer */
+       }
+       return res;
+}
+
+static int aMYSQL_set(struct ast_channel *chan, char *data)
+{
+       char *var, *tmp;
+       AST_DECLARE_APP_ARGS(args,
+               AST_APP_ARG(set);
+               AST_APP_ARG(variable);
+               AST_APP_ARG(value);
+       );
+
+       AST_NONSTANDARD_APP_ARGS(args, data, ' ');
+
+       if (args.argc == 3) {
+               var = alloca(6 + strlen(args.variable) + 1);
+               sprintf(var, "MYSQL_%s", args.variable);
+
+               /* Make the parameter case-insensitive */
+               for (tmp = var + 6; *tmp; tmp++)
+                       *tmp = toupper(*tmp);
+
+               pbx_builtin_setvar_helper(chan, var, args.value);
+       }
+       return 0;
+}
+
+/* MYSQL operations */
+static int aMYSQL_connect(struct ast_channel *chan, char *data)
+{
+       AST_DECLARE_APP_ARGS(args,
+               AST_APP_ARG(connect);
+               AST_APP_ARG(connid);
+               AST_APP_ARG(dbhost);
+               AST_APP_ARG(dbuser);
+               AST_APP_ARG(dbpass);
+               AST_APP_ARG(dbname);
+       );
+       MYSQL *mysql;
+       int timeout;
+       const char *ctimeout;
+
+       AST_NONSTANDARD_APP_ARGS(args, data, ' ');
+
+       if (args.argc != 6) {
+               ast_log(LOG_WARNING, "MYSQL_connect is missing some arguments\n");
+               return -1;
+       }
+
+       if (!(mysql = mysql_init(NULL))) {
+               ast_log(LOG_WARNING, "mysql_init returned NULL\n");
+               return -1;
+       }
+
+       ctimeout = pbx_builtin_getvar_helper(chan, "MYSQL_TIMEOUT");
+       if (ctimeout && sscanf(ctimeout, "%d", &timeout) == 1) {
+               mysql_options(mysql, MYSQL_OPT_CONNECT_TIMEOUT, (void *)&timeout);
+       }
+
+       if (! mysql_real_connect(mysql, args.dbhost, args.dbuser, args.dbpass, args.dbname, 0, NULL,
+#ifdef CLIENT_MULTI_STATEMENTS
+                       CLIENT_MULTI_STATEMENTS | CLIENT_MULTI_RESULTS
+#elif defined(CLIENT_MULTI_QUERIES)
+                       CLIENT_MULTI_QUERIES
+#else
+                       0
+#endif
+               )) {
+               ast_log(LOG_WARNING, "mysql_real_connect(mysql,%s,%s,dbpass,%s,...) failed(%d): %s\n",
+                               args.dbhost, args.dbuser, args.dbname, mysql_errno(mysql), mysql_error(mysql));
+               return -1;
+       }
+
+       add_identifier_and_set_asterisk_int(chan, args.connid, AST_MYSQL_ID_CONNID, mysql);
+       return 0;
+}
+
+static int aMYSQL_query(struct ast_channel *chan, char *data)
+{
+       AST_DECLARE_APP_ARGS(args,
+               AST_APP_ARG(query);
+               AST_APP_ARG(resultid);
+               AST_APP_ARG(connid);
+               AST_APP_ARG(sql);
+       );
+       MYSQL       *mysql;
+       MYSQL_RES   *mysqlres;
+       int connid;
+       int mysql_query_res;
+
+       AST_NONSTANDARD_APP_ARGS(args, data, ' ');
+
+       if (args.argc != 4 || (connid = atoi(args.connid)) == 0) {
+               ast_log(LOG_WARNING, "missing some arguments\n");
+               return -1;
+       }
+
+       if (!(mysql = find_identifier(connid, AST_MYSQL_ID_CONNID))) {
+               ast_log(LOG_WARNING, "Invalid connection identifier %s passed in aMYSQL_query\n", args.connid);
+               return -1;
+       }
+
+       if ((mysql_query_res = mysql_query(mysql, args.sql)) != 0) {
+               ast_log(LOG_WARNING, "aMYSQL_query: mysql_query failed. Error: %s\n", mysql_error(mysql));
+               return -1;
+       }
+
+       if ((mysqlres = mysql_store_result(mysql))) {
+               add_identifier_and_set_asterisk_int(chan, args.resultid, AST_MYSQL_ID_RESID, mysqlres);
+               return 0;
+       } else if (!mysql_field_count(mysql)) {
+               return 0;
+       } else
+               ast_log(LOG_WARNING, "mysql_store_result() failed on query %s\n", args.sql);
+
+       return -1;
+}
+
+static int aMYSQL_nextresult(struct ast_channel *chan, char *data)
+{
+       MYSQL       *mysql;
+       MYSQL_RES   *mysqlres;
+       AST_DECLARE_APP_ARGS(args,
+               AST_APP_ARG(nextresult);
+               AST_APP_ARG(resultid);
+               AST_APP_ARG(connid);
+       );
+       int connid = -1;
+
+       AST_NONSTANDARD_APP_ARGS(args, data, ' ');
+       sscanf(args.connid, "%d", &connid);
+
+       if (args.argc != 3 || connid <= 0) {
+               ast_log(LOG_WARNING, "missing some arguments\n");
+               return -1;
+       }
+
+       if (!(mysql = find_identifier(connid, AST_MYSQL_ID_CONNID))) {
+               ast_log(LOG_WARNING, "Invalid connection identifier %d passed in aMYSQL_query\n", connid);
+               return -1;
+       }
+
+       if (mysql_more_results(mysql)) {
+               mysql_next_result(mysql);
+               if ((mysqlres = mysql_store_result(mysql))) {
+                       add_identifier_and_set_asterisk_int(chan, args.resultid, AST_MYSQL_ID_RESID, mysqlres);
+                       return 0;
+               } else if (!mysql_field_count(mysql)) {
+                       return 0;
+               } else
+                       ast_log(LOG_WARNING, "mysql_store_result() failed on storing next_result\n");
+       } else
+               ast_log(LOG_WARNING, "mysql_more_results() result set has no more results\n");
+
+       return 0;
+}
+
+
+static int aMYSQL_fetch(struct ast_channel *chan, char *data)
+{
+       MYSQL_RES *mysqlres;
+       MYSQL_ROW mysqlrow;
+       AST_DECLARE_APP_ARGS(args,
+               AST_APP_ARG(fetch);
+               AST_APP_ARG(resultvar);
+               AST_APP_ARG(fetchid);
+               AST_APP_ARG(vars);
+       );
+       char *s5, *parse;
+       int resultid = -1, numFields, j;
+
+       parse = ast_strdupa(data);
+       AST_NONSTANDARD_APP_ARGS(args, parse, ' ');
+       sscanf(args.fetchid, "%d", &resultid);
+
+       if (args.resultvar && (resultid >= 0) ) {
+               if ((mysqlres = find_identifier(resultid, AST_MYSQL_ID_RESID)) != NULL) {
+                       /* Grab the next row */
+                       if ((mysqlrow = mysql_fetch_row(mysqlres)) != NULL) {
+                               numFields = mysql_num_fields(mysqlres);
+                               for (j = 0; j < numFields; j++) {
+                                       s5 = strsep(&args.vars, " ");
+                                       if (s5 == NULL) {
+                                               ast_log(LOG_WARNING, "ast_MYSQL_fetch: More fields (%d) than variables (%d)\n", numFields, j);
+                                               break;
+                                       }
+
+                                       pbx_builtin_setvar_helper(chan, s5, mysqlrow[j] ? mysqlrow[j] :
+                                               nullvalue == NULLSTRING ? "NULL" :
+                                               nullvalue == EMPTYSTRING ? "" :
+                                               NULL);
+                               }
+                               ast_debug(5, "ast_MYSQL_fetch: numFields=%d\n", numFields);
+                               set_asterisk_int(chan, args.resultvar, 1); /* try more rows */
+                       } else {
+                               ast_debug(5, "ast_MYSQL_fetch : EOF\n");
+                               set_asterisk_int(chan, args.resultvar, 0); /* no more rows */
+                       }
+                       return 0;
+               } else {
+                       set_asterisk_int(chan, args.resultvar, 0);
+                       ast_log(LOG_WARNING, "aMYSQL_fetch: Invalid result identifier %d passed\n", resultid);
+               }
+       } else {
+               ast_log(LOG_WARNING, "aMYSQL_fetch: missing some arguments\n");
+       }
+
+       return -1;
+}
+
+static int aMYSQL_clear(struct ast_channel *chan, char *data)
+{
+       MYSQL_RES *mysqlres;
+
+       int id;
+       strsep(&data, " "); /* eat the first token, we already know it :P */
+       id = safe_scan_int(&data, " \n", -1);
+       if ((mysqlres = find_identifier(id, AST_MYSQL_ID_RESID)) == NULL) {
+               ast_log(LOG_WARNING, "Invalid result identifier %d passed in aMYSQL_clear\n", id);
+       } else {
+               mysql_free_result(mysqlres);
+               del_identifier(id, AST_MYSQL_ID_RESID);
+       }
+
+       return 0;
+}
+
+static int aMYSQL_disconnect(struct ast_channel *chan, char *data)
+{
+       MYSQL *mysql;
+       int id;
+       strsep(&data, " "); /* eat the first token, we already know it :P */
+
+       id = safe_scan_int(&data, " \n", -1);
+       if ((mysql = find_identifier(id, AST_MYSQL_ID_CONNID)) == NULL) {
+               ast_log(LOG_WARNING, "Invalid connection identifier %d passed in aMYSQL_disconnect\n", id);
+       } else {
+               mysql_close(mysql);
+               del_identifier(id, AST_MYSQL_ID_CONNID);
+       }
+
+       return 0;
+}
+
+static int MYSQL_exec(struct ast_channel *chan, const char *data)
+{
+       int result;
+       char sresult[10];
+
+       ast_debug(5, "MYSQL: data=%s\n", data);
+
+       if (!data) {
+               ast_log(LOG_WARNING, "MYSQL requires an argument (see manual)\n");
+               return -1;
+       }
+
+       result = 0;
+
+       if (autoclear) {
+               struct ast_datastore *mysql_store = ast_channel_datastore_find(chan, &mysql_ds_info, NULL);
+               if (!mysql_store) {
+                       if (!(mysql_store = ast_datastore_alloc(&mysql_ds_info, NULL))) {
+                               ast_log(LOG_WARNING, "Unable to allocate new datastore.\n");
+                       } else {
+                               mysql_store->data = chan;
+                               ast_channel_datastore_add(chan, mysql_store);
+                       }
+               }
+       }
+       ast_mutex_lock(&_mysql_mutex);
+
+       if (strncasecmp("connect", data, strlen("connect")) == 0) {
+               result = aMYSQL_connect(chan, ast_strdupa(data));
+       } else if (strncasecmp("query", data, strlen("query")) == 0) {
+               result = aMYSQL_query(chan, ast_strdupa(data));
+       } else if (strncasecmp("nextresult", data, strlen("nextresult")) == 0) {
+               result = aMYSQL_nextresult(chan, ast_strdupa(data));
+       } else if (strncasecmp("fetch", data, strlen("fetch")) == 0) {
+               result = aMYSQL_fetch(chan, ast_strdupa(data));
+       } else if (strncasecmp("clear", data, strlen("clear")) == 0) {
+               result = aMYSQL_clear(chan, ast_strdupa(data));
+       } else if (strncasecmp("disconnect", data, strlen("disconnect")) == 0) {
+               result = aMYSQL_disconnect(chan, ast_strdupa(data));
+       } else if (strncasecmp("set", data, 3) == 0) {
+               result = aMYSQL_set(chan, ast_strdupa(data));
+       } else {
+               ast_log(LOG_WARNING, "Unknown argument to MYSQL application : %s\n", data);
+               result = -1;
+       }
+
+       ast_mutex_unlock(&_mysql_mutex);
+
+       snprintf(sresult, sizeof(sresult), "%d", result);
+       pbx_builtin_setvar_helper(chan, "MYSQL_STATUS", sresult);
+       return 0;
+}
+
+static int unload_module(void)
+{
+       return ast_unregister_application(app);
+}
+
+static int load_module(void)
+{
+       struct MYSQLidshead *headp = &_mysql_ids_head;
+       struct ast_flags config_flags = { 0 };
+       struct ast_config *cfg = ast_config_load(MYSQL_CONFIG, config_flags);
+       const char *temp;
+
+       if (cfg) {
+               if ((temp = ast_variable_retrieve(cfg, "general", "nullvalue"))) {
+                       if (!strcasecmp(temp, "nullstring")) {
+                               nullvalue = NULLSTRING;
+                       } else if (!strcasecmp(temp, "emptystring")) {
+                               nullvalue = EMPTYSTRING;
+                       } else if (!strcasecmp(temp, "null")) {
+                               nullvalue = NULLVALUE;
+                       } else {
+                               ast_log(LOG_WARNING, "Illegal value for 'nullvalue': '%s' (must be 'nullstring', 'null', or 'emptystring')\n", temp);
+                       }
+               }
+               if ((temp = ast_variable_retrieve(cfg, "general", "autoclear")) && ast_true(temp)) {
+                       autoclear = 1;
+               }
+               ast_config_destroy(cfg);
+       }
+
+       AST_LIST_HEAD_INIT(headp);
+       return ast_register_application(app, MYSQL_exec, synopsis, descrip);
+}
+
+AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Simple Mysql Interface");
diff --git a/addons/app_saycountpl.c b/addons/app_saycountpl.c
new file mode 100644 (file)
index 0000000..fd921de
--- /dev/null
@@ -0,0 +1,136 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2004, Andy Powell & TAAN Softworks Corp. 
+ *
+ * See http://www.asterisk.org for more information about
+ * the Asterisk project. Please do not directly contact
+ * any of the maintainers of this project for assistance;
+ * the project provides a web site, mailing lists and IRC
+ * channels for your use.
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE file
+ * at the top of the source tree.
+ */
+
+/*!
+ * \file
+ * \brief Say Polish counting words
+ * \author Andy Powell
+ */
+
+/*** MODULEINFO
+       <defaultenabled>no</defaultenabled>
+ ***/
+
+#include "asterisk.h"
+
+ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
+
+#include "asterisk/file.h"
+#include "asterisk/logger.h"
+#include "asterisk/channel.h"
+#include "asterisk/pbx.h"
+#include "asterisk/module.h"
+#include "asterisk/lock.h"
+#include "asterisk/app.h"
+
+/*** DOCUMENTATION
+       <application name="SayCountPL" language="en_US">
+               <synopsis>
+                       Say Polish counting words.
+               </synopsis>
+               <syntax>
+                       <parameter name="word1" required="true" />
+                       <parameter name="word2" required="true" />
+                       <parameter name="word5" required="true" />
+                       <parameter name="number" required="true" />
+               </syntax>
+               <description>
+                       <para>Polish grammar has some funny rules for counting words. for example 1 zloty,
+                       2 zlote, 5 zlotych. This application will take the words for 1, 2-4 and 5 and
+                       decide based on grammar rules which one to use with the number you pass to it.</para>
+                       <para>Example: SayCountPL(zloty,zlote,zlotych,122) will give: zlote</para>
+               </description>
+       </application>
+
+ ***/
+static const char app[] = "SayCountPL";
+
+static int saywords(struct ast_channel *chan, char *word1, char *word2, char *word5, int num)
+{
+       /* Put this in a separate proc because it's bound to change */
+       int d = 0;
+
+       if (num > 0) {
+               if (num % 1000 == 1) {
+                       ast_streamfile(chan, word1, chan->language);
+                       d = ast_waitstream(chan,"");
+               } else if (((num % 10) >= 2) && ((num % 10) <= 4 ) && ((num % 100) < 10 || (num % 100) > 20)) {
+                       ast_streamfile(chan, word2, chan->language);
+                       d = ast_waitstream(chan, "");
+               } else {
+                       ast_streamfile(chan, word5, chan->language);
+                       d = ast_waitstream(chan, "");
+               }
+       }
+
+       return d;
+}
+
+
+static int sayword_exec(struct ast_channel *chan, const char *data)
+{
+       int res = 0;
+       char *s;
+       int inum;
+       AST_DECLARE_APP_ARGS(args,
+               AST_APP_ARG(word1);
+               AST_APP_ARG(word2);
+               AST_APP_ARG(word5);
+               AST_APP_ARG(num);
+       );
+
+       if (!data) {
+               ast_log(LOG_WARNING, "SayCountPL requires 4 arguments: word-1,word-2,word-5,number\n");
+               return -1;
+       }
+
+       s = ast_strdupa(data);
+
+       AST_STANDARD_APP_ARGS(args, s);
+
+       /* Check to see if params passed */
+       if (!args.word1 || !args.word2 || !args.word5 || !args.num) {
+               ast_log(LOG_WARNING, "SayCountPL requires 4 arguments: word-1,word-2,word-3,number\n");
+               return -1;
+       }
+
+       if (sscanf(args.num, "%d", &inum) != 1) {
+               ast_log(LOG_WARNING, "'%s' is not a valid number\n", args.num);
+               return -1;
+       }
+
+       /* do the saying part (after a bit of maths) */
+
+       res = saywords(chan, args.word1, args.word2, args.word5, inum);
+
+       return res;
+}
+
+static int unload_module(void)
+{
+       return ast_unregister_application(app);
+}
+
+static int load_module(void)
+{
+       int res;
+
+       res = ast_register_application_xml(app, sayword_exec);
+
+       return res ? AST_MODULE_LOAD_DECLINE : AST_MODULE_LOAD_SUCCESS;
+}
+
+AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Say polish counting words");
diff --git a/addons/cdr_addon_mysql.c b/addons/cdr_addon_mysql.c
new file mode 100644 (file)
index 0000000..2a3ef43
--- /dev/null
@@ -0,0 +1,641 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * James Sharp <jsharp@psychoses.org>
+ *
+ * Modified August 2003
+ * Tilghman Lesher <asterisk__cdr__cdr_mysql__200308@the-tilghman.com>
+ *
+ * Modified August 6, 2005
+ * Joseph Benden <joe@thrallingpenguin.com>
+ * Added mysql connection timeout parameter
+ * Added an automatic reconnect as to not lose a cdr record
+ * Cleaned up the original code to match the coding guidelines
+ *
+ * Modified Juli 2006
+ * Martin Portmann <map@infinitum.ch>
+ * Added mysql ssl support
+ *
+ * See http://www.asterisk.org for more information about
+ * the Asterisk project. Please do not directly contact
+ * any of the maintainers of this project for assistance;
+ * the project provides a web site, mailing lists and IRC
+ * channels for your use.
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE file
+ * at the top of the source tree.
+ */
+
+/*!
+ * \file
+ * \brief MySQL CDR backend
+ * \ingroup cdr_drivers
+ */
+
+/*** MODULEINFO
+       <depend>mysqlclient</depend>
+       <defaultenabled>no</defaultenabled>
+ ***/
+
+#include "asterisk.h"
+
+ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
+
+#include <mysql/mysql.h>
+#include <mysql/errmsg.h>
+
+#include "asterisk/config.h"
+#include "asterisk/options.h"
+#include "asterisk/channel.h"
+#include "asterisk/cdr.h"
+#include "asterisk/module.h"
+#include "asterisk/logger.h"
+#include "asterisk/cli.h"
+#include "asterisk/strings.h"
+#include "asterisk/linkedlists.h"
+#include "asterisk/threadstorage.h"
+
+#define DATE_FORMAT "%Y-%m-%d %T"
+
+AST_THREADSTORAGE(sql1_buf);
+AST_THREADSTORAGE(sql2_buf);
+AST_THREADSTORAGE(escape_buf);
+
+static const char desc[] = "MySQL CDR Backend";
+static const char name[] = "mysql";
+static const char config[] = "cdr_mysql.conf";
+
+static struct ast_str *hostname = NULL, *dbname = NULL, *dbuser = NULL, *password = NULL, *dbsock = NULL, *dbtable = NULL, *dbcharset = NULL;
+
+static struct ast_str *ssl_ca = NULL, *ssl_cert = NULL, *ssl_key = NULL;
+
+static int dbport = 0;
+static int connected = 0;
+static time_t connect_time = 0;
+static int records = 0;
+static int totalrecords = 0;
+static int timeout = 0;
+static int calldate_compat = 0;
+
+AST_MUTEX_DEFINE_STATIC(mysql_lock);
+
+struct unload_string {
+       AST_LIST_ENTRY(unload_string) entry;
+       struct ast_str *str;
+};
+
+static AST_LIST_HEAD_STATIC(unload_strings, unload_string);
+
+struct column {
+       char *name;
+       char *cdrname;
+       char *staticvalue;
+       char *type;
+       AST_LIST_ENTRY(column) list;
+};
+
+/* Protected with mysql_lock */
+static AST_RWLIST_HEAD_STATIC(columns, column);
+
+static MYSQL mysql = { { NULL }, };
+
+static char *handle_cli_cdr_mysql_status(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+       switch (cmd) {
+       case CLI_INIT:
+               e->command = "cdr mysql status";
+               e->usage =
+                       "Usage: cdr mysql status\n"
+                       "       Shows current connection status for cdr_mysql\n";
+               return NULL;
+       case CLI_GENERATE:
+               return NULL;
+       }
+
+       if (a->argc != 3)
+               return CLI_SHOWUSAGE;
+
+       if (connected) {
+               char status[256], status2[100] = "";
+               int ctime = time(NULL) - connect_time;
+               if (dbport)
+                       snprintf(status, 255, "Connected to %s@%s, port %d", ast_str_buffer(dbname), ast_str_buffer(hostname), dbport);
+               else if (dbsock)
+                       snprintf(status, 255, "Connected to %s on socket file %s", ast_str_buffer(dbname), S_OR(ast_str_buffer(dbsock), "default"));
+               else
+                       snprintf(status, 255, "Connected to %s@%s", ast_str_buffer(dbname), ast_str_buffer(hostname));
+
+               if (!ast_strlen_zero(ast_str_buffer(dbuser)))
+                       snprintf(status2, 99, " with username %s", ast_str_buffer(dbuser));
+               if (ast_str_strlen(dbtable))
+                       snprintf(status2, 99, " using table %s", ast_str_buffer(dbtable));
+               if (ctime > 31536000) {
+                       ast_cli(a->fd, "%s%s for %d years, %d days, %d hours, %d minutes, %d seconds.\n", status, status2, ctime / 31536000, (ctime % 31536000) / 86400, (ctime % 86400) / 3600, (ctime % 3600) / 60, ctime % 60);
+               } else if (ctime > 86400) {
+                       ast_cli(a->fd, "%s%s for %d days, %d hours, %d minutes, %d seconds.\n", status, status2, ctime / 86400, (ctime % 86400) / 3600, (ctime % 3600) / 60, ctime % 60);
+               } else if (ctime > 3600) {
+                       ast_cli(a->fd, "%s%s for %d hours, %d minutes, %d seconds.\n", status, status2, ctime / 3600, (ctime % 3600) / 60, ctime % 60);
+               } else if (ctime > 60) {
+                       ast_cli(a->fd, "%s%s for %d minutes, %d seconds.\n", status, status2, ctime / 60, ctime % 60);
+               } else {
+                       ast_cli(a->fd, "%s%s for %d seconds.\n", status, status2, ctime);
+               }
+               if (records == totalrecords)
+                       ast_cli(a->fd, "  Wrote %d records since last restart.\n", totalrecords);
+               else
+                       ast_cli(a->fd, "  Wrote %d records since last restart and %d records since last reconnect.\n", totalrecords, records);
+       } else {
+               ast_cli(a->fd, "Not currently connected to a MySQL server.\n");
+       }
+
+       return CLI_SUCCESS;
+}
+
+static struct ast_cli_entry cdr_mysql_status_cli[] = {
+       AST_CLI_DEFINE(handle_cli_cdr_mysql_status, "Show connection status of cdr_mysql"),
+};
+
+static int mysql_log(struct ast_cdr *cdr)
+{
+       struct ast_str *sql1 = ast_str_thread_get(&sql1_buf, 1024), *sql2 = ast_str_thread_get(&sql2_buf, 1024);
+       int retries = 5;
+#if MYSQL_VERSION_ID >= 50013
+       my_bool my_bool_true = 1;
+#endif
+
+       if (!sql1 || !sql2) {
+               ast_log(LOG_ERROR, "Memory error\n");
+               return -1;
+       }
+
+       ast_mutex_lock(&mysql_lock);
+
+db_reconnect:
+       if ((!connected) && (hostname || dbsock) && dbuser && password && dbname && dbtable ) {
+               /* Attempt to connect */
+               mysql_init(&mysql);
+               /* Add option to quickly timeout the connection */
+               if (timeout && mysql_options(&mysql, MYSQL_OPT_CONNECT_TIMEOUT, (char *)&timeout) != 0) {
+                       ast_log(LOG_ERROR, "mysql_options returned (%d) %s\n", mysql_errno(&mysql), mysql_error(&mysql));
+               }
+#if MYSQL_VERSION_ID >= 50013
+               /* Add option for automatic reconnection */
+               if (mysql_options(&mysql, MYSQL_OPT_RECONNECT, &my_bool_true) != 0) {
+                       ast_log(LOG_ERROR, "mysql_options returned (%d) %s\n", mysql_errno(&mysql), mysql_error(&mysql));
+               }
+#endif
+               if (ssl_ca || ssl_cert || ssl_key) {
+                       mysql_ssl_set(&mysql, ssl_key ? ast_str_buffer(ssl_key) : NULL, ssl_cert ? ast_str_buffer(ssl_cert) : NULL, ssl_ca ? ast_str_buffer(ssl_ca) : NULL, NULL, NULL);
+               }
+               if (mysql_real_connect(&mysql, ast_str_buffer(hostname), ast_str_buffer(dbuser), ast_str_buffer(password), ast_str_buffer(dbname), dbport, dbsock && ast_str_strlen(dbsock) ? ast_str_buffer(dbsock) : NULL, ssl_ca ? CLIENT_SSL : 0)) {
+                       connected = 1;
+                       connect_time = time(NULL);
+                       records = 0;
+                       if (dbcharset) {
+                               ast_str_set(&sql1, 0, "SET NAMES '%s'", ast_str_buffer(dbcharset));
+                               mysql_real_query(&mysql, ast_str_buffer(sql1), ast_str_strlen(sql1));
+                               ast_debug(1, "SQL command as follows: %s\n", ast_str_buffer(sql1));
+                       }
+               } else {
+                       ast_log(LOG_ERROR, "Cannot connect to database server %s: (%d) %s\n", ast_str_buffer(hostname), mysql_errno(&mysql), mysql_error(&mysql));
+                       connected = 0;
+               }
+       } else {
+               /* Long connection - ping the server */
+               int error;
+               if ((error = mysql_ping(&mysql))) {
+                       connected = 0;
+                       records = 0;
+                       switch (mysql_errno(&mysql)) {
+                               case CR_SERVER_GONE_ERROR:
+                               case CR_SERVER_LOST:
+                                       ast_log(LOG_ERROR, "Server has gone away. Attempting to reconnect.\n");
+                                       break;
+                               default:
+                                       ast_log(LOG_ERROR, "Unknown connection error: (%d) %s\n", mysql_errno(&mysql), mysql_error(&mysql));
+                       }
+                       retries--;
+                       if (retries) {
+                               goto db_reconnect;
+                       } else {
+                               ast_log(LOG_ERROR, "Retried to connect five times, giving up.\n");
+                       }
+               }
+       }
+
+       if (connected) {
+               int column_count = 0;
+               char *cdrname;
+               char workspace[2048], *value = NULL;
+               struct column *entry;
+               struct ast_str *escape = ast_str_thread_get(&escape_buf, 16);
+
+               ast_str_set(&sql1, 0, "INSERT INTO %s (", AS_OR(dbtable, "cdr"));
+               ast_str_set(&sql2, 0, ") VALUES (");
+
+               AST_RWLIST_RDLOCK(&columns);
+               AST_RWLIST_TRAVERSE(&columns, entry, list) {
+                       if (!strcmp(entry->name, "calldate")) {
+                               /*!\note
+                                * For some dumb reason, "calldate" used to be formulated using
+                                * the datetime the record was posted, rather than the start
+                                * time of the call.  If someone really wants the old compatible
+                                * behavior, it's provided here.
+                                */
+                               if (calldate_compat) {
+                                       struct timeval tv = ast_tvnow();
+                                       struct ast_tm tm;
+                                       char timestr[128];
+                                       ast_localtime(&tv, &tm, NULL);
+                                       ast_strftime(timestr, sizeof(timestr), "%Y-%m-%d %T", &tm);
+                                       ast_cdr_setvar(cdr, "calldate", timestr, 0);
+                                       cdrname = "calldate";
+                               } else {
+                                       cdrname = "start";
+                               }
+                       } else {
+                               cdrname = entry->cdrname;
+                       }
+
+                       /* Construct SQL */
+
+                       /* Need the type and value to determine if we want the raw value or not */
+                       if (entry->staticvalue) {
+                               value = ast_strdupa(entry->staticvalue);
+                       } else if ((!strcmp(cdrname, "start") ||
+                                !strcmp(cdrname, "answer") ||
+                                !strcmp(cdrname, "end") ||
+                                !strcmp(cdrname, "disposition") ||
+                                !strcmp(cdrname, "amaflags")) &&
+                               (strstr(entry->type, "int") ||
+                                strstr(entry->type, "dec") ||
+                                strstr(entry->type, "float") ||
+                                strstr(entry->type, "double") ||
+                                strstr(entry->type, "real") ||
+                                strstr(entry->type, "numeric") ||
+                                strstr(entry->type, "fixed"))) {
+                               ast_cdr_getvar(cdr, cdrname, &value, workspace, sizeof(workspace), 0, 1);
+                       } else {
+                               ast_cdr_getvar(cdr, cdrname, &value, workspace, sizeof(workspace), 0, 0);
+                       }
+
+                       if (value) {
+                               size_t valsz;
+
+                               if (column_count++) {
+                                       ast_str_append(&sql1, 0, ",");
+                                       ast_str_append(&sql2, 0, ",");
+                               }
+
+                               ast_str_make_space(&escape, (valsz = strlen(value)) * 2 + 1);
+                               mysql_real_escape_string(&mysql, ast_str_buffer(escape), value, valsz);
+
+                               ast_str_append(&sql1, 0, "%s", entry->name);
+                               ast_str_append(&sql2, 0, "'%s'", ast_str_buffer(escape));
+                       }
+               }
+               AST_RWLIST_UNLOCK(&columns);
+
+               ast_debug(1, "Inserting a CDR record.\n");
+               ast_str_append(&sql1, 0, "%s)", ast_str_buffer(sql2));
+
+               ast_debug(1, "SQL command as follows: %s\n", ast_str_buffer(sql1));
+
+               if (mysql_real_query(&mysql, ast_str_buffer(sql1), ast_str_strlen(sql1))) {
+                       ast_log(LOG_ERROR, "Failed to insert into database: (%d) %s\n", mysql_errno(&mysql), mysql_error(&mysql));
+                       mysql_close(&mysql);
+                       connected = 0;
+               } else {
+                       records++;
+                       totalrecords++;
+               }
+       }
+       ast_mutex_unlock(&mysql_lock);
+       return 0;
+}
+
+static int my_unload_module(int reload)
+{ 
+       struct unload_string *us;
+       struct column *entry;
+
+       ast_cli_unregister_multiple(cdr_mysql_status_cli, sizeof(cdr_mysql_status_cli) / sizeof(struct ast_cli_entry));
+
+       if (connected) {
+               mysql_close(&mysql);
+               connected = 0;
+               records = 0;
+       }
+
+       AST_LIST_LOCK(&unload_strings);
+       while ((us = AST_LIST_REMOVE_HEAD(&unload_strings, entry))) {
+               ast_free(us->str);
+               ast_free(us);
+       }
+       AST_LIST_UNLOCK(&unload_strings);
+
+       if (!reload) {
+               AST_RWLIST_WRLOCK(&columns);
+       }
+       while ((entry = AST_RWLIST_REMOVE_HEAD(&columns, list))) {
+               ast_free(entry);
+       }
+       if (!reload) {
+               AST_RWLIST_UNLOCK(&columns);
+       }
+
+       dbport = 0;
+       ast_cdr_unregister(name);
+       
+       return 0;
+}
+
+static int my_load_config_string(struct ast_config *cfg, const char *category, const char *variable, struct ast_str **field, const char *def)
+{
+       struct unload_string *us;
+       const char *tmp;
+
+       if (!(us = ast_calloc(1, sizeof(*us))))
+               return -1;
+
+       if (!(*field = ast_str_create(16))) {
+               ast_free(us);
+               return -1;
+       }
+
+       us->str = *field;
+
+       AST_LIST_LOCK(&unload_strings);
+       AST_LIST_INSERT_HEAD(&unload_strings, us, entry);
+       AST_LIST_UNLOCK(&unload_strings);
+
+       tmp = ast_variable_retrieve(cfg, category, variable);
+
+       ast_str_set(field, 0, "%s", tmp ? tmp : def);
+
+       return 0;
+}
+
+static int my_load_config_number(struct ast_config *cfg, const char *category, const char *variable, int *field, int def)
+{
+       const char *tmp;
+
+       tmp = ast_variable_retrieve(cfg, category, variable);
+
+       if (!tmp || sscanf(tmp, "%d", field) < 1)
+               *field = def;
+
+       return 0;
+}
+
+static int my_load_module(int reload)
+{
+       int res;
+       struct ast_config *cfg;
+       struct ast_variable *var;
+       struct ast_flags config_flags = { reload ? CONFIG_FLAG_FILEUNCHANGED : 0 };
+       struct column *entry;
+       char *temp;
+       struct ast_str *compat;
+       MYSQL_ROW row;
+       MYSQL_RES *result;
+       char sqldesc[128];
+#if MYSQL_VERSION_ID >= 50013
+       my_bool my_bool_true = 1;
+#endif
+
+       cfg = ast_config_load(config, config_flags);
+       if (!cfg) {
+               ast_log(LOG_WARNING, "Unable to load config for mysql CDR's: %s\n", config);
+               return AST_MODULE_LOAD_SUCCESS;
+       } else if (cfg == CONFIG_STATUS_FILEUNCHANGED)
+               return AST_MODULE_LOAD_SUCCESS;
+
+       if (reload) {
+               AST_RWLIST_WRLOCK(&columns);
+               my_unload_module(1);
+       }
+
+       var = ast_variable_browse(cfg, "global");
+       if (!var) {
+               /* nothing configured */
+               if (reload) {
+                       AST_RWLIST_UNLOCK(&columns);
+               }
+               return AST_MODULE_LOAD_SUCCESS;
+       }
+
+       res = 0;
+
+       res |= my_load_config_string(cfg, "global", "hostname", &hostname, "localhost");
+       res |= my_load_config_string(cfg, "global", "dbname", &dbname, "astriskcdrdb");
+       res |= my_load_config_string(cfg, "global", "user", &dbuser, "root");
+       res |= my_load_config_string(cfg, "global", "sock", &dbsock, "");
+       res |= my_load_config_string(cfg, "global", "table", &dbtable, "cdr");
+       res |= my_load_config_string(cfg, "global", "password", &password, "");
+
+       res |= my_load_config_string(cfg, "global", "charset", &dbcharset, "");
+
+       res |= my_load_config_string(cfg, "global", "ssl_ca", &ssl_ca, "");
+       res |= my_load_config_string(cfg, "global", "ssl_cert", &ssl_cert, "");
+       res |= my_load_config_string(cfg, "global", "ssl_key", &ssl_key, "");
+
+       res |= my_load_config_number(cfg, "global", "port", &dbport, 0);
+       res |= my_load_config_number(cfg, "global", "timeout", &timeout, 0);
+       res |= my_load_config_string(cfg, "global", "compat", &compat, "no");
+       if (ast_true(ast_str_buffer(compat))) {
+               calldate_compat = 1;
+       } else {
+               calldate_compat = 0;
+       }
+
+       if (res < 0) {
+               if (reload) {
+                       AST_RWLIST_UNLOCK(&columns);
+               }
+               return AST_MODULE_LOAD_FAILURE;
+       }
+
+       /* Check for any aliases */
+       if (!reload) {
+               /* Lock, if not already */
+               AST_RWLIST_WRLOCK(&columns);
+       }
+       while ((entry = AST_LIST_REMOVE_HEAD(&columns, list))) {
+               ast_free(entry);
+       }
+
+       ast_debug(1, "Got hostname of %s\n", ast_str_buffer(hostname));
+       ast_debug(1, "Got port of %d\n", dbport);
+       ast_debug(1, "Got a timeout of %d\n", timeout);
+       if (dbsock)
+               ast_debug(1, "Got sock file of %s\n", ast_str_buffer(dbsock));
+       ast_debug(1, "Got user of %s\n", ast_str_buffer(dbuser));
+       ast_debug(1, "Got dbname of %s\n", ast_str_buffer(dbname));
+       ast_debug(1, "Got password of %s\n", ast_str_buffer(password));
+       ast_debug(1, "%sunning in calldate compatibility mode\n", calldate_compat ? "R" : "Not r");
+
+       if (dbcharset) {
+               ast_debug(1, "Got DB charset of %s\n", ast_str_buffer(dbcharset));
+       }
+
+       mysql_init(&mysql);
+
+       if (timeout && mysql_options(&mysql, MYSQL_OPT_CONNECT_TIMEOUT, (char *)&timeout) != 0) {
+               ast_log(LOG_ERROR, "cdr_mysql: mysql_options returned (%d) %s\n", mysql_errno(&mysql), mysql_error(&mysql));
+       }
+
+#if MYSQL_VERSION_ID >= 50013
+       /* Add option for automatic reconnection */
+       if (mysql_options(&mysql, MYSQL_OPT_RECONNECT, &my_bool_true) != 0) {
+               ast_log(LOG_ERROR, "cdr_mysql: mysql_options returned (%d) %s\n", mysql_errno(&mysql), mysql_error(&mysql));
+       }
+#endif
+
+       if ((ssl_ca && ast_str_strlen(ssl_ca)) || (ssl_cert && ast_str_strlen(ssl_cert)) || (ssl_key && ast_str_strlen(ssl_key))) {
+               mysql_ssl_set(&mysql,
+                       ssl_key ? ast_str_buffer(ssl_key) : NULL,
+                       ssl_cert ? ast_str_buffer(ssl_cert) : NULL,
+                       ssl_ca ? ast_str_buffer(ssl_ca) : NULL,
+                       NULL, NULL);
+       }
+       temp = dbsock && ast_str_strlen(dbsock) ? ast_str_buffer(dbsock) : NULL;
+       if (!mysql_real_connect(&mysql, ast_str_buffer(hostname), ast_str_buffer(dbuser), ast_str_buffer(password), ast_str_buffer(dbname), dbport, temp, ssl_ca && ast_str_strlen(ssl_ca) ? CLIENT_SSL : 0)) {
+               ast_log(LOG_ERROR, "Failed to connect to mysql database %s on %s.\n", ast_str_buffer(dbname), ast_str_buffer(hostname));
+               connected = 0;
+               records = 0;
+       } else {
+               ast_debug(1, "Successfully connected to MySQL database.\n");
+               connected = 1;
+               records = 0;
+               connect_time = time(NULL);
+               if (dbcharset) {
+                       snprintf(sqldesc, sizeof(sqldesc), "SET NAMES '%s'", ast_str_buffer(dbcharset));
+                       mysql_real_query(&mysql, sqldesc, strlen(sqldesc));
+                       ast_debug(1, "SQL command as follows: %s\n", sqldesc);
+               }
+
+               /* Get table description */
+               snprintf(sqldesc, sizeof(sqldesc), "DESC %s", dbtable ? ast_str_buffer(dbtable) : "cdr");
+               if (mysql_query(&mysql, sqldesc)) {
+                       ast_log(LOG_ERROR, "Unable to query table description!!  Logging disabled.\n");
+                       mysql_close(&mysql);
+                       connected = 0;
+                       AST_RWLIST_UNLOCK(&columns);
+                       ast_config_destroy(cfg);
+                       return AST_MODULE_LOAD_SUCCESS;
+               }
+
+               if (!(result = mysql_store_result(&mysql))) {
+                       ast_log(LOG_ERROR, "Unable to query table description!!  Logging disabled.\n");
+                       mysql_close(&mysql);
+                       connected = 0;
+                       AST_RWLIST_UNLOCK(&columns);
+                       ast_config_destroy(cfg);
+                       return AST_MODULE_LOAD_SUCCESS;
+               }
+
+               while ((row = mysql_fetch_row(result))) {
+                       struct column *entry;
+                       char *cdrvar = "", *staticvalue = "";
+
+                       ast_debug(1, "Got a field '%s' of type '%s'\n", row[0], row[1]);
+                       /* Check for an alias or a static value */
+                       for (var = ast_variable_browse(cfg, "columns"); var; var = var->next) {
+                               if (strncmp(var->name, "alias", 5) == 0 && strcasecmp(var->value, row[0]) == 0 ) {
+                                       char *alias = ast_strdupa(var->name + 5);
+                                       cdrvar = ast_strip(alias);
+                                       ast_verb(3, "Found alias %s for column %s\n", cdrvar, row[0]);
+                                       break;
+                               } else if (strncmp(var->name, "static", 6) == 0 && strcasecmp(var->value, row[0]) == 0) {
+                                       char *item = ast_strdupa(var->name + 6);
+                                       item = ast_strip(item);
+                                       if (item[0] == '"' && item[strlen(item) - 1] == '"') {
+                                               /* Remove surrounding quotes */
+                                               item[strlen(item) - 1] = '\0';
+                                               item++;
+                                       }
+                                       staticvalue = item;
+                               }
+                       }
+
+                       entry = ast_calloc(sizeof(char), sizeof(*entry) + strlen(row[0]) + 1 + strlen(cdrvar) + 1 + strlen(staticvalue) + 1 + strlen(row[1]) + 1);
+                       if (!entry) {
+                               ast_log(LOG_ERROR, "Out of memory creating entry for column '%s'\n", row[0]);
+                               res = -1;
+                               break;
+                       }
+
+                       entry->name = (char *)entry + sizeof(*entry);
+                       strcpy(entry->name, row[0]);
+
+                       if (!ast_strlen_zero(cdrvar)) {
+                               entry->cdrname = entry->name + strlen(row[0]) + 1;
+                               strcpy(entry->cdrname, cdrvar);
+                       } else { /* Point to same place as the column name */
+                               entry->cdrname = (char *)entry + sizeof(*entry);
+                       }
+
+                       if (!ast_strlen_zero(staticvalue)) {
+                               entry->staticvalue = entry->cdrname + strlen(entry->cdrname) + 1;
+                               strcpy(entry->staticvalue, staticvalue);
+                               ast_debug(1, "staticvalue length: %d\n", (int) strlen(staticvalue) );
+                               entry->type = entry->staticvalue + strlen(entry->staticvalue) + 1;
+                       } else {
+                               entry->type = entry->cdrname + strlen(entry->cdrname) + 1;
+                       }
+                       strcpy(entry->type, row[1]);
+
+                       ast_debug(1, "Entry name '%s'\n", entry->name);
+                       ast_debug(1, "   cdrname '%s'\n", entry->cdrname);
+                       ast_debug(1, "    static '%s'\n", entry->staticvalue);
+                       ast_debug(1, "      type '%s'\n", entry->type);
+
+                       AST_LIST_INSERT_TAIL(&columns, entry, list);
+               }
+               mysql_free_result(result);
+       }
+       AST_RWLIST_UNLOCK(&columns);
+       ast_config_destroy(cfg);
+       if (res < 0) {
+               return AST_MODULE_LOAD_FAILURE;
+       }
+
+       res = ast_cdr_register(name, desc, mysql_log);
+       if (res) {
+               ast_log(LOG_ERROR, "Unable to register MySQL CDR handling\n");
+       } else {
+               res = ast_cli_register_multiple(cdr_mysql_status_cli, sizeof(cdr_mysql_status_cli) / sizeof(struct ast_cli_entry));
+       }
+
+       return res;
+}
+
+static int load_module(void)
+{
+       return my_load_module(0);
+}
+
+static int unload_module(void)
+{
+       return my_unload_module(0);
+}
+
+static int reload(void)
+{
+       int ret;
+
+       ast_mutex_lock(&mysql_lock);
+       ret = my_load_module(1);
+       ast_mutex_unlock(&mysql_lock);
+
+       return ret;
+}
+
+AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_DEFAULT, "MySQL CDR Backend",
+       .load = load_module,
+       .unload = unload_module,
+       .reload = reload,
+);
+
diff --git a/addons/chan_mobile.c b/addons/chan_mobile.c
new file mode 100644 (file)
index 0000000..415ca61
--- /dev/null
@@ -0,0 +1,4266 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 1999 - 2006, Digium, Inc.
+ *
+ * Mark Spencer <markster@digium.com>
+ *
+ * See http://www.asterisk.org for more information about
+ * the Asterisk project. Please do not directly contact
+ * any of the maintainers of this project for assistance;
+ * the project provides a web site, mailing lists and IRC
+ * channels for your use.
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE file
+ * at the top of the source tree.
+ */
+
+/*! 
+ * \file
+ * \brief Bluetooth Mobile Device channel driver
+ *
+ * \author Dave Bowerman <david.bowerman@gmail.com>
+ *
+ * \ingroup channel_drivers
+ */
+
+/*** MODULEINFO
+       <depend>bluetooth</depend>
+       <defaultenabled>no</defaultenabled>
+ ***/
+
+#include "asterisk.h"
+
+ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
+
+#include <pthread.h>
+#include <signal.h>
+
+#include <bluetooth/bluetooth.h>
+#include <bluetooth/hci.h>
+#include <bluetooth/hci_lib.h>
+#include <bluetooth/sdp.h>
+#include <bluetooth/sdp_lib.h>
+#include <bluetooth/rfcomm.h>
+#include <bluetooth/sco.h>
+#include <bluetooth/l2cap.h>
+
+#include "asterisk/compat.h"
+#include "asterisk/lock.h"
+#include "asterisk/channel.h"
+#include "asterisk/config.h"
+#include "asterisk/logger.h"
+#include "asterisk/module.h"
+#include "asterisk/pbx.h"
+#include "asterisk/options.h"
+#include "asterisk/utils.h"
+#include "asterisk/linkedlists.h"
+#include "asterisk/cli.h"
+#include "asterisk/devicestate.h"
+#include "asterisk/causes.h"
+#include "asterisk/dsp.h"
+#include "asterisk/app.h"
+#include "asterisk/manager.h"
+#include "asterisk/io.h"
+
+#define MBL_CONFIG "mobile.conf"
+
+#define DEVICE_FRAME_SIZE 48
+#define DEVICE_FRAME_FORMAT AST_FORMAT_SLINEAR
+#define CHANNEL_FRAME_SIZE 320
+
+static int prefformat = DEVICE_FRAME_FORMAT;
+
+static int discovery_interval = 60;                    /* The device discovery interval, default 60 seconds. */
+static pthread_t discovery_thread = AST_PTHREADT_NULL; /* The discovery thread */
+static sdp_session_t *sdp_session;
+
+AST_MUTEX_DEFINE_STATIC(unload_mutex);
+static int unloading_flag = 0;
+static inline int check_unloading(void);
+static inline void set_unloading(void);
+
+enum mbl_type {
+       MBL_TYPE_PHONE,
+       MBL_TYPE_HEADSET
+};
+
+struct adapter_pvt {
+       int dev_id;                                     /* device id */
+       int hci_socket;                                 /* device descriptor */
+       char id[31];                                    /* the 'name' from mobile.conf */
+       bdaddr_t addr;                                  /* adddress of adapter */
+       unsigned int inuse:1;                           /* are we in use ? */
+       unsigned int alignment_detection:1;             /* do alignment detection on this adpater? */
+       struct io_context *io;                          /*!< io context for audio connections */
+       struct io_context *accept_io;                   /*!< io context for sco listener */
+       int *sco_id;                                    /*!< the io context id of the sco listener socket */
+       int sco_socket;                                 /*!< sco listener socket */
+       pthread_t sco_listener_thread;                  /*!< sco listener thread */
+       AST_LIST_ENTRY(adapter_pvt) entry;
+};
+
+static AST_RWLIST_HEAD_STATIC(adapters, adapter_pvt);
+
+struct msg_queue_entry;
+struct hfp_pvt;
+struct mbl_pvt {
+       struct ast_channel *owner;                      /* Channel we belong to, possibly NULL */
+       struct ast_frame fr;                            /* "null" frame */
+       ast_mutex_t lock;                               /*!< pvt lock */
+       /*! queue for messages we are expecting */
+       AST_LIST_HEAD_NOLOCK(msg_queue, msg_queue_entry) msg_queue;
+       enum mbl_type type;                             /* Phone or Headset */
+       char id[31];                                    /* The id from mobile.conf */
+       int group;                                      /* group number for group dialling */
+       bdaddr_t addr;                                  /* address of device */
+       struct adapter_pvt *adapter;                    /* the adapter we use */
+       char context[AST_MAX_CONTEXT];                  /* the context for incoming calls */
+       struct hfp_pvt *hfp;                            /*!< hfp pvt */
+       int rfcomm_port;                                /* rfcomm port number */
+       int rfcomm_socket;                              /* rfcomm socket descriptor */
+       char rfcomm_buf[256];
+       char io_buf[CHANNEL_FRAME_SIZE + AST_FRIENDLY_OFFSET];
+       struct ast_smoother *smoother;                  /* our smoother, for making 48 byte frames */
+       int sco_socket;                                 /* sco socket descriptor */
+       pthread_t monitor_thread;                       /* monitor thread handle */
+       int timeout;                                    /*!< used to set the timeout for rfcomm data (may be used in the future) */
+       unsigned int no_callsetup:1;
+       unsigned int has_sms:1;
+       unsigned int do_alignment_detection:1;
+       unsigned int alignment_detection_triggered:1;
+       unsigned int blackberry:1;
+       short alignment_samples[4];
+       int alignment_count;
+       int ring_sched_id;
+       struct ast_dsp *dsp;
+       struct sched_context *sched;
+
+       /* flags */
+       unsigned int outgoing:1;        /*!< outgoing call */
+       unsigned int incoming:1;        /*!< incoming call */
+       unsigned int outgoing_sms:1;    /*!< outgoing sms */
+       unsigned int incoming_sms:1;    /*!< outgoing sms */
+       unsigned int needcallerid:1;    /*!< we need callerid */
+       unsigned int needchup:1;        /*!< we need to send a chup */
+       unsigned int needring:1;        /*!< we need to send a RING */
+       unsigned int answered:1;        /*!< we sent/recieved an answer */
+       unsigned int connected:1;       /*!< do we have an rfcomm connection to a device */
+
+       AST_LIST_ENTRY(mbl_pvt) entry;
+};
+
+static AST_RWLIST_HEAD_STATIC(devices, mbl_pvt);
+
+static int handle_response_ok(struct mbl_pvt *pvt, char *buf);
+static int handle_response_error(struct mbl_pvt *pvt, char *buf);
+static int handle_response_ciev(struct mbl_pvt *pvt, char *buf);
+static int handle_response_clip(struct mbl_pvt *pvt, char *buf);
+static int handle_response_ring(struct mbl_pvt *pvt, char *buf);
+static int handle_response_cmti(struct mbl_pvt *pvt, char *buf);
+static int handle_response_cmgr(struct mbl_pvt *pvt, char *buf);
+static int handle_sms_prompt(struct mbl_pvt *pvt, char *buf);
+
+/* CLI stuff */
+static char *handle_cli_mobile_show_devices(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a);
+static char *handle_cli_mobile_search(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a);
+static char *handle_cli_mobile_rfcomm(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a);
+
+static struct ast_cli_entry mbl_cli[] = {
+       AST_CLI_DEFINE(handle_cli_mobile_show_devices, "Show Bluetooth Cell / Mobile devices"),
+       AST_CLI_DEFINE(handle_cli_mobile_search,       "Search for Bluetooth Cell / Mobile devices"),
+       AST_CLI_DEFINE(handle_cli_mobile_rfcomm,       "Send commands to the rfcomm port for debugging"),
+};
+
+/* App stuff */
+static char *app_mblstatus = "MobileStatus";
+static char *mblstatus_synopsis = "MobileStatus(Device,Variable)";
+static char *mblstatus_desc =
+"MobileStatus(Device,Variable)\n"
+"  Device - Id of mobile device from mobile.conf\n"
+"  Variable - Variable to store status in will be 1-3.\n"
+"             In order, Disconnected, Connected & Free, Connected & Busy.\n";
+
+static char *app_mblsendsms = "MobileSendSMS";
+static char *mblsendsms_synopsis = "MobileSendSMS(Device,Dest,Message)";
+static char *mblsendsms_desc =
+"MobileSendSms(Device,Dest,Message)\n"
+"  Device - Id of device from mobile.conf\n"
+"  Dest - destination\n"
+"  Message - text of the message\n";
+
+static struct ast_channel *mbl_new(int state, struct mbl_pvt *pvt, char *cid_num,
+               const struct ast_channel *requestor);
+static struct ast_channel *mbl_request(const char *type, int format,
+               const struct ast_channel *requestor, void *data, int *cause);
+static int mbl_call(struct ast_channel *ast, char *dest, int timeout);
+static int mbl_hangup(struct ast_channel *ast);
+static int mbl_answer(struct ast_channel *ast);
+static int mbl_digit_end(struct ast_channel *ast, char digit, unsigned int duration);
+static struct ast_frame *mbl_read(struct ast_channel *ast);
+static int mbl_write(struct ast_channel *ast, struct ast_frame *frame);
+static int mbl_fixup(struct ast_channel *oldchan, struct ast_channel *newchan);
+static int mbl_devicestate(void *data);
+
+static void do_alignment_detection(struct mbl_pvt *pvt, char *buf, int buflen);
+
+static int mbl_queue_control(struct mbl_pvt *pvt, enum ast_control_frame_type control);
+static int mbl_queue_hangup(struct mbl_pvt *pvt);
+static int mbl_ast_hangup(struct mbl_pvt *pvt);
+
+static int rfcomm_connect(bdaddr_t src, bdaddr_t dst, int remote_channel);
+static int rfcomm_write(int rsock, char *buf);
+static int rfcomm_write_full(int rsock, char *buf, size_t count);
+static int rfcomm_wait(int rsock, int *ms);
+static ssize_t rfcomm_read(int rsock, char *buf, size_t count);
+
+static int sco_connect(bdaddr_t src, bdaddr_t dst);
+static int sco_write(int s, char *buf, int len);
+static int sco_accept(int *id, int fd, short events, void *data);
+static int sco_bind(struct adapter_pvt *adapter);
+
+static void *do_sco_listen(void *data);
+static int sdp_search(char *addr, int profile);
+
+static int headset_send_ring(const void *data);
+
+/*
+ * bluetooth handsfree profile helpers
+ */
+
+#define HFP_HF_ECNR    (1 << 0)
+#define HFP_HF_CW      (1 << 1)
+#define HFP_HF_CID     (1 << 2)
+#define HFP_HF_VOICE   (1 << 3)
+#define HFP_HF_VOLUME  (1 << 4)
+#define HFP_HF_STATUS  (1 << 5)
+#define HFP_HF_CONTROL (1 << 6)
+
+#define HFP_AG_CW      (1 << 0)
+#define HFP_AG_ECNR    (1 << 1)
+#define HFP_AG_VOICE   (1 << 2)
+#define HFP_AG_RING    (1 << 3)
+#define HFP_AG_TAG     (1 << 4)
+#define HFP_AG_REJECT  (1 << 5)
+#define HFP_AG_STATUS  (1 << 6)
+#define HFP_AG_CONTROL (1 << 7)
+#define HFP_AG_ERRORS  (1 << 8)
+
+#define HFP_CIND_UNKNOWN       -1
+#define HFP_CIND_NONE          0
+#define HFP_CIND_SERVICE       1
+#define HFP_CIND_CALL          2
+#define HFP_CIND_CALLSETUP     3
+#define HFP_CIND_CALLHELD      4
+#define HFP_CIND_SIGNAL                5
+#define HFP_CIND_ROAM          6
+#define HFP_CIND_BATTCHG       7
+
+/* call indicator values */
+#define HFP_CIND_CALL_NONE     0
+#define HFP_CIND_CALL_ACTIVE   1
+
+/* callsetup indicator values */
+#define HFP_CIND_CALLSETUP_NONE                0
+#define HFP_CIND_CALLSETUP_INCOMING    1
+#define HFP_CIND_CALLSETUP_OUTGOING    2
+#define HFP_CIND_CALLSETUP_ALERTING    3
+
+/*!
+ * \brief This struct holds HFP features that we support.
+ */
+struct hfp_hf {
+       int ecnr:1;     /*!< echo-cancel/noise reduction */
+       int cw:1;       /*!< call waiting and three way calling */
+       int cid:1;      /*!< cli presentation (callier id) */
+       int voice:1;    /*!< voice recognition activation */
+       int volume:1;   /*!< remote volume control */
+       int status:1;   /*!< enhanced call status */
+       int control:1;  /*!< enhanced call control*/
+};
+
+/*!
+ * \brief This struct holds HFP features the AG supports.
+ */
+struct hfp_ag {
+       int cw:1;       /*!< three way calling */
+       int ecnr:1;     /*!< echo-cancel/noise reduction */
+       int voice:1;    /*!< voice recognition */
+       int ring:1;     /*!< in band ring tone capability */
+       int tag:1;      /*!< attach a number to a voice tag */
+       int reject:1;   /*!< ability to reject a call */
+       int status:1;   /*!< enhanced call status */
+       int control:1;  /*!< enhanced call control*/
+       int errors:1;   /*!< extended error result codes*/
+};
+
+/*!
+ * \brief This struct holds mappings for indications.
+ */
+struct hfp_cind {
+       int service;    /*!< whether we have service or not */
+       int call;       /*!< call state */
+       int callsetup;  /*!< bluetooth call setup indications */
+       int callheld;   /*!< bluetooth call hold indications */
+       int signal;     /*!< signal strength */
+       int roam;       /*!< roaming indicator */
+       int battchg;    /*!< battery charge indicator */
+};
+
+
+/*!
+ * \brief This struct holds state information about the current hfp connection.
+ */
+struct hfp_pvt {
+       struct mbl_pvt *owner;          /*!< the mbl_pvt struct that owns this struct */
+       int initialized:1;              /*!< whether a service level connection exists or not */
+       int nocallsetup:1;              /*!< whether we detected a callsetup indicator */
+       struct hfp_ag brsf;             /*!< the supported feature set of the AG */
+       int cind_index[16];             /*!< the cind/ciev index to name mapping for this AG */
+       int cind_state[16];             /*!< the cind/ciev state for this AG */
+       struct hfp_cind cind_map;       /*!< the cind name to index mapping for this AG */
+       int rsock;                      /*!< our rfcomm socket */
+       int rport;                      /*!< our rfcomm port */
+};
+
+
+/* Our supported features.
+ * we only support caller id
+ */
+static struct hfp_hf hfp_our_brsf = {
+       .ecnr = 0,
+       .cw = 0,
+       .cid = 1,
+       .voice = 0,
+       .volume = 0,
+       .status = 0,
+       .control = 0,
+};
+
+
+static int hfp_parse_ciev(struct hfp_pvt *hfp, char *buf, int *value);
+static char *hfp_parse_clip(struct hfp_pvt *hfp, char *buf);
+static int hfp_parse_cmti(struct hfp_pvt *hfp, char *buf);
+static int hfp_parse_cmgr(struct hfp_pvt *hfp, char *buf, char **from_number, char **text);
+static int hfp_parse_brsf(struct hfp_pvt *hfp, const char *buf);
+static int hfp_parse_cind(struct hfp_pvt *hfp, char *buf);
+static int hfp_parse_cind_test(struct hfp_pvt *hfp, char *buf);
+
+static int hfp_brsf2int(struct hfp_hf *hf);
+static struct hfp_ag *hfp_int2brsf(int brsf, struct hfp_ag *ag);
+
+static int hfp_send_brsf(struct hfp_pvt *hfp, struct hfp_hf *brsf);
+static int hfp_send_cind(struct hfp_pvt *hfp);
+static int hfp_send_cind_test(struct hfp_pvt *hfp);
+static int hfp_send_cmer(struct hfp_pvt *hfp, int status);
+static int hfp_send_clip(struct hfp_pvt *hfp, int status);
+static int hfp_send_vgs(struct hfp_pvt *hfp, int value);
+
+#if 0
+static int hfp_send_vgm(struct hfp_pvt *hfp, int value);
+#endif
+static int hfp_send_dtmf(struct hfp_pvt *hfp, char digit);
+static int hfp_send_cmgf(struct hfp_pvt *hfp, int mode);
+static int hfp_send_cnmi(struct hfp_pvt *hfp);
+static int hfp_send_cmgr(struct hfp_pvt *hfp, int index);
+static int hfp_send_cmgs(struct hfp_pvt *hfp, const char *number);
+static int hfp_send_sms_text(struct hfp_pvt *hfp, const char *message);
+static int hfp_send_chup(struct hfp_pvt *hfp);
+static int hfp_send_atd(struct hfp_pvt *hfp, const char *number);
+static int hfp_send_ata(struct hfp_pvt *hfp);
+
+/*
+ * bluetooth headset profile helpers
+ */
+static int hsp_send_ok(int rsock);
+static int hsp_send_error(int rsock);
+static int hsp_send_vgs(int rsock, int gain);
+static int hsp_send_vgm(int rsock, int gain);
+static int hsp_send_ring(int rsock);
+
+
+/*
+ * Hayes AT command helpers
+ */
+typedef enum {
+       /* errors */
+       AT_PARSE_ERROR = -2,
+       AT_READ_ERROR = -1,
+       AT_UNKNOWN = 0,
+       /* at responses */
+       AT_OK,
+       AT_ERROR,
+       AT_RING,
+       AT_BRSF,
+       AT_CIND,
+       AT_CIEV,
+       AT_CLIP,
+       AT_CMTI,
+       AT_CMGR,
+       AT_SMS_PROMPT,
+       AT_CMS_ERROR,
+       /* at commands */
+       AT_A,
+       AT_D,
+       AT_CHUP,
+       AT_CKPD,
+       AT_CMGS,
+       AT_VGM,
+       AT_VGS,
+       AT_VTS,
+       AT_CMGF,
+       AT_CNMI,
+       AT_CMER,
+       AT_CIND_TEST,
+} at_message_t;
+
+static int at_match_prefix(char *buf, char *prefix);
+static at_message_t at_read_full(int rsock, char *buf, size_t count);
+static inline const char *at_msg2str(at_message_t msg);
+
+struct msg_queue_entry {
+       at_message_t expected;
+       at_message_t response_to;
+       void *data;
+
+       AST_LIST_ENTRY(msg_queue_entry) entry;
+};
+
+static int msg_queue_push(struct mbl_pvt *pvt, at_message_t expect, at_message_t response_to);
+static int msg_queue_push_data(struct mbl_pvt *pvt, at_message_t expect, at_message_t response_to, void *data);
+static struct msg_queue_entry *msg_queue_pop(struct mbl_pvt *pvt);
+static void msg_queue_free_and_pop(struct mbl_pvt *pvt);
+static void msg_queue_flush(struct mbl_pvt *pvt);
+static struct msg_queue_entry *msg_queue_head(struct mbl_pvt *pvt);
+
+/*
+ * channel stuff
+ */
+
+static const struct ast_channel_tech mbl_tech = {
+       .type = "Mobile",
+       .description = "Bluetooth Mobile Device Channel Driver",
+       .capabilities = AST_FORMAT_SLINEAR,
+       .requester = mbl_request,
+       .call = mbl_call,
+       .hangup = mbl_hangup,
+       .answer = mbl_answer,
+       .send_digit_end = mbl_digit_end,
+       .read = mbl_read,
+       .write = mbl_write,
+       .fixup = mbl_fixup,
+       .devicestate = mbl_devicestate
+};
+
+/* CLI Commands implementation */
+
+static char *handle_cli_mobile_show_devices(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+       struct mbl_pvt *pvt;
+       char bdaddr[18];
+       char group[6];
+
+#define FORMAT1 "%-15.15s %-17.17s %-5.5s %-15.15s %-9.9s %-5.5s %-3.3s\n"
+
+       switch (cmd) {
+       case CLI_INIT:
+               e->command = "mobile show devices";
+               e->usage =
+                       "Usage: mobile show devices\n"
+                       "       Shows the state of Bluetooth Cell / Mobile devices.\n";
+               return NULL;
+       case CLI_GENERATE:
+               return NULL;
+       }
+
+       if (a->argc != 3)
+               return CLI_SHOWUSAGE;
+
+       ast_cli(a->fd, FORMAT1, "ID", "Address", "Group", "Adapter", "Connected", "State", "SMS");
+       AST_RWLIST_RDLOCK(&devices);
+       AST_RWLIST_TRAVERSE(&devices, pvt, entry) {
+               ast_mutex_lock(&pvt->lock);
+               ba2str(&pvt->addr, bdaddr);
+               snprintf(group, sizeof(group), "%d", pvt->group);
+               ast_cli(a->fd, FORMAT1,
+                               pvt->id,
+                               bdaddr,
+                               group,
+                               pvt->adapter->id,
+                               pvt->connected ? "Yes" : "No",
+                               (!pvt->connected) ? "None" : (pvt->owner) ? "Busy" : (pvt->outgoing_sms || pvt->incoming_sms) ? "SMS" : "Free",
+                               (pvt->has_sms) ? "Yes" : "No"
+                      );
+               ast_mutex_unlock(&pvt->lock);
+       }
+       AST_RWLIST_UNLOCK(&devices);
+
+#undef FORMAT1
+
+       return CLI_SUCCESS;
+}
+
+static char *handle_cli_mobile_search(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+       struct adapter_pvt *adapter;
+       inquiry_info *ii = NULL;
+       int max_rsp, num_rsp;
+       int len, flags;
+       int i, phport, hsport;
+       char addr[19] = {0};
+       char name[31] = {0};
+
+#define FORMAT1 "%-17.17s %-30.30s %-6.6s %-7.7s %-4.4s\n"
+#define FORMAT2 "%-17.17s %-30.30s %-6.6s %-7.7s %d\n"
+
+       switch (cmd) {
+       case CLI_INIT:
+               e->command = "mobile search";
+               e->usage =
+                       "Usage: mobile search\n"
+                       "       Searches for Bluetooth Cell / Mobile devices in range.\n";
+               return NULL;
+       case CLI_GENERATE:
+               return NULL;
+       }
+
+       if (a->argc != 2)
+               return CLI_SHOWUSAGE;
+
+       /* find a free adapter */
+       AST_RWLIST_RDLOCK(&adapters);
+       AST_RWLIST_TRAVERSE(&adapters, adapter, entry) {
+               if (!adapter->inuse)
+                       break;
+       }
+       AST_RWLIST_UNLOCK(&adapters);
+
+       if (!adapter) {
+               ast_cli(a->fd, "All Bluetooth adapters are in use at this time.\n");
+               return CLI_SUCCESS;
+       }
+
+       len  = 8;
+       max_rsp = 255;
+       flags = IREQ_CACHE_FLUSH;
+
+       ii = alloca(max_rsp * sizeof(inquiry_info));
+       num_rsp = hci_inquiry(adapter->dev_id, len, max_rsp, NULL, &ii, flags);
+       if (num_rsp > 0) {
+               ast_cli(a->fd, FORMAT1, "Address", "Name", "Usable", "Type", "Port");
+               for (i = 0; i < num_rsp; i++) {
+                       ba2str(&(ii + i)->bdaddr, addr);
+                       name[0] = 0x00;
+                       if (hci_read_remote_name(adapter->hci_socket, &(ii + i)->bdaddr, sizeof(name) - 1, name, 0) < 0)
+                               strcpy(name, "[unknown]");
+                       phport = sdp_search(addr, HANDSFREE_AGW_PROFILE_ID);
+                       if (!phport)
+                               hsport = sdp_search(addr, HEADSET_PROFILE_ID);
+                       else
+                               hsport = 0;
+                       ast_cli(a->fd, FORMAT2, addr, name, (phport > 0 || hsport > 0) ? "Yes" : "No",
+                               (phport > 0) ? "Phone" : "Headset", (phport > 0) ? phport : hsport);
+               }
+       } else
+               ast_cli(a->fd, "No Bluetooth Cell / Mobile devices found.\n");
+
+#undef FORMAT1
+#undef FORMAT2
+
+       return CLI_SUCCESS;
+}
+
+static char *handle_cli_mobile_rfcomm(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+       char buf[128];
+       struct mbl_pvt *pvt = NULL;
+
+       switch (cmd) {
+       case CLI_INIT:
+               e->command = "mobile rfcomm";
+               e->usage =
+                       "Usage: mobile rfcomm <device ID> <command>\n"
+                       "       Send <command> to the rfcomm port on the device\n"
+                       "       with the specified <device ID>.\n";
+               return NULL;
+       case CLI_GENERATE:
+               return NULL;
+       }
+
+       if (a->argc != 4)
+               return CLI_SHOWUSAGE;
+
+       AST_RWLIST_RDLOCK(&devices);
+       AST_RWLIST_TRAVERSE(&devices, pvt, entry) {
+               if (!strcmp(pvt->id, a->argv[2]))
+                       break;
+       }
+       AST_RWLIST_UNLOCK(&devices);
+
+       if (!pvt) {
+               ast_cli(a->fd, "Device %s not found.\n", a->argv[2]);
+               goto e_return;
+       }
+
+       ast_mutex_lock(&pvt->lock);
+       if (!pvt->connected) {
+               ast_cli(a->fd, "Device %s not connected.\n", a->argv[2]);
+               goto e_unlock_pvt;
+       }
+
+       snprintf(buf, sizeof(buf), "%s\r", a->argv[3]);
+       rfcomm_write(pvt->rfcomm_socket, buf);
+       msg_queue_push(pvt, AT_OK, AT_UNKNOWN);
+
+e_unlock_pvt:
+       ast_mutex_unlock(&pvt->lock);
+e_return:
+       return CLI_SUCCESS;
+}
+
+/*
+
+       Dialplan applications implementation
+
+*/
+
+static int mbl_status_exec(struct ast_channel *ast, const char *data)
+{
+
+       struct mbl_pvt *pvt;
+       char *parse;
+       int stat;
+       char status[2];
+
+       AST_DECLARE_APP_ARGS(args,
+               AST_APP_ARG(device);
+               AST_APP_ARG(variable);
+       );
+
+       if (ast_strlen_zero(data))
+               return -1;
+
+       parse = ast_strdupa(data);
+
+       AST_STANDARD_APP_ARGS(args, parse);
+
+       if (ast_strlen_zero(args.device) || ast_strlen_zero(args.variable))
+               return -1;
+
+       stat = 1;
+
+       AST_RWLIST_RDLOCK(&devices);
+       AST_RWLIST_TRAVERSE(&devices, pvt, entry) {
+               if (!strcmp(pvt->id, args.device))
+                       break;
+       }
+       AST_RWLIST_UNLOCK(&devices);
+
+       if (pvt) {
+               ast_mutex_lock(&pvt->lock);
+               if (pvt->connected)
+                       stat = 2;
+               if (pvt->owner)
+                       stat = 3;
+               ast_mutex_unlock(&pvt->lock);
+       }
+
+       snprintf(status, sizeof(status), "%d", stat);
+       pbx_builtin_setvar_helper(ast, args.variable, status);
+
+       return 0;
+
+}
+
+static int mbl_sendsms_exec(struct ast_channel *ast, const char *data)
+{
+
+       struct mbl_pvt *pvt;
+       char *parse, *message;
+
+       AST_DECLARE_APP_ARGS(args,
+               AST_APP_ARG(device);
+               AST_APP_ARG(dest);
+               AST_APP_ARG(message);
+       );
+
+       if (ast_strlen_zero(data))
+               return -1;
+
+       parse = ast_strdupa(data);
+
+       AST_STANDARD_APP_ARGS(args, parse);
+
+       if (ast_strlen_zero(args.device)) {
+               ast_log(LOG_ERROR,"NULL device for message -- SMS will not be sent.\n");
+               return -1;
+       }
+
+       if (ast_strlen_zero(args.dest)) {
+               ast_log(LOG_ERROR,"NULL destination for message -- SMS will not be sent.\n");
+               return -1;
+       }
+
+       if (ast_strlen_zero(args.message)) {
+               ast_log(LOG_ERROR,"NULL Message to be sent -- SMS will not be sent.\n");
+               return -1;
+       }
+
+       AST_RWLIST_RDLOCK(&devices);
+       AST_RWLIST_TRAVERSE(&devices, pvt, entry) {
+               if (!strcmp(pvt->id, args.device))
+                       break;
+       }
+       AST_RWLIST_UNLOCK(&devices);
+
+       if (!pvt) {
+               ast_log(LOG_ERROR,"Bluetooth device %s wasn't found in the list -- SMS will not be sent.\n", args.device);
+               goto e_return;
+       }
+
+       ast_mutex_lock(&pvt->lock);
+       if (!pvt->connected) {
+               ast_log(LOG_ERROR,"Bluetooth device %s wasn't connected -- SMS will not be sent.\n", args.device);
+               goto e_unlock_pvt;
+       }
+
+       if (!pvt->has_sms) {
+               ast_log(LOG_ERROR,"Bluetooth device %s doesn't handle SMS -- SMS will not be sent.\n", args.device);
+               goto e_unlock_pvt;
+       }
+
+       message = ast_strdup(args.message);
+
+       if (hfp_send_cmgs(pvt->hfp, args.dest)
+               || msg_queue_push_data(pvt, AT_SMS_PROMPT, AT_CMGS, message)) {
+
+               ast_log(LOG_ERROR, "[%s] problem sending SMS message\n", pvt->id);
+               goto e_free_message;
+       }
+
+       ast_mutex_unlock(&pvt->lock);
+
+       return 0;
+
+e_free_message:
+       ast_free(message);
+e_unlock_pvt:
+       ast_mutex_unlock(&pvt->lock);
+e_return:
+       return -1;
+}
+
+/*
+
+       Channel Driver callbacks
+
+*/
+
+static struct ast_channel *mbl_new(int state, struct mbl_pvt *pvt, char *cid_num,
+               const struct ast_channel *requestor)
+{
+
+       struct ast_channel *chn;
+
+       pvt->answered = 0;
+       pvt->alignment_count = 0;
+       pvt->alignment_detection_triggered = 0;
+       if (pvt->adapter->alignment_detection)
+               pvt->do_alignment_detection = 1;
+       else
+               pvt->do_alignment_detection = 0;
+
+       ast_smoother_reset(pvt->smoother, DEVICE_FRAME_SIZE);
+       ast_dsp_digitreset(pvt->dsp);
+
+       chn = ast_channel_alloc(1, state, cid_num, pvt->id, 0, 0, pvt->context,
+                       requestor ? requestor->linkedid : "", 0,
+                       "Mobile/%s-%04lx", pvt->id, ast_random() & 0xffff);
+       if (!chn) {
+               goto e_return;
+       }
+
+       chn->tech = &mbl_tech;
+       chn->nativeformats = prefformat;
+       chn->rawreadformat = prefformat;
+       chn->rawwriteformat = prefformat;
+       chn->writeformat = prefformat;
+       chn->readformat = prefformat;
+       chn->tech_pvt = pvt;
+
+       if (state == AST_STATE_RING)
+               chn->rings = 1;
+
+       ast_string_field_set(chn, language, "en");
+       pvt->owner = chn;
+
+       if (pvt->sco_socket != -1) {
+               ast_channel_set_fd(chn, 0, pvt->sco_socket);
+       }
+
+       return chn;
+
+e_return:
+       return NULL;
+}
+
+static struct ast_channel *mbl_request(const char *type, int format,
+               const struct ast_channel *requestor, void *data, int *cause)
+{
+
+       struct ast_channel *chn = NULL;
+       struct mbl_pvt *pvt;
+       char *dest_dev = NULL;
+       char *dest_num = NULL;
+       int oldformat, group = -1;
+
+       if (!data) {
+               ast_log(LOG_WARNING, "Channel requested with no data\n");
+               *cause = AST_CAUSE_INCOMPATIBLE_DESTINATION;
+               return NULL;
+       }
+
+       oldformat = format;
+       format &= (AST_FORMAT_SLINEAR);
+       if (!format) {
+               ast_log(LOG_WARNING, "Asked to get a channel of unsupported format '%d'\n", oldformat);
+               *cause = AST_CAUSE_FACILITY_NOT_IMPLEMENTED;
+               return NULL;
+       }
+
+       dest_dev = ast_strdupa((char *)data);
+
+       dest_num = strchr(dest_dev, '/');
+       if (dest_num)
+               *dest_num++ = 0x00;
+
+       if (((dest_dev[0] == 'g') || (dest_dev[0] == 'G')) && ((dest_dev[1] >= '0') && (dest_dev[1] <= '9'))) {
+               group = atoi(&dest_dev[1]);
+       }
+
+       /* Find requested device and make sure it's connected. */
+       AST_RWLIST_RDLOCK(&devices);
+       AST_RWLIST_TRAVERSE(&devices, pvt, entry) {
+               if (group > -1 && pvt->group == group && pvt->connected && !pvt->owner) {
+                       break;
+               } else if (!strcmp(pvt->id, dest_dev)) {
+                       break;
+               }
+       }
+       AST_RWLIST_UNLOCK(&devices);
+       if (!pvt || !pvt->connected || pvt->owner) {
+               ast_log(LOG_WARNING, "Request to call on device %s which is not connected / already in use.\n", dest_dev);
+               *cause = AST_CAUSE_REQUESTED_CHAN_UNAVAIL;
+               return NULL;
+       }
+
+       if ((pvt->type == MBL_TYPE_PHONE) && !dest_num) {
+               ast_log(LOG_WARNING, "Can't determine destination number.\n");
+               *cause = AST_CAUSE_INCOMPATIBLE_DESTINATION;
+               return NULL;
+       }
+
+       ast_mutex_lock(&pvt->lock);
+       chn = mbl_new(AST_STATE_DOWN, pvt, NULL, requestor);
+       ast_mutex_unlock(&pvt->lock);
+       if (!chn) {
+               ast_log(LOG_WARNING, "Unable to allocate channel structure.\n");
+               *cause = AST_CAUSE_REQUESTED_CHAN_UNAVAIL;
+               return NULL;
+       }
+
+       return chn;
+
+}
+
+static int mbl_call(struct ast_channel *ast, char *dest, int timeout)
+{
+
+       struct mbl_pvt *pvt;
+       char *dest_dev = NULL;
+       char *dest_num = NULL;
+
+       dest_dev = ast_strdupa((char *)dest);
+
+       pvt = ast->tech_pvt;
+
+       if (pvt->type == MBL_TYPE_PHONE) {
+               dest_num = strchr(dest_dev, '/');
+               if (!dest_num) {
+                       ast_log(LOG_WARNING, "Cant determine destination number.\n");
+                       return -1;
+               }
+               *dest_num++ = 0x00;
+       }
+
+       if ((ast->_state != AST_STATE_DOWN) && (ast->_state != AST_STATE_RESERVED)) {
+               ast_log(LOG_WARNING, "mbl_call called on %s, neither down nor reserved\n", ast->name);
+               return -1;
+       }
+
+       ast_debug(1, "Calling %s on %s\n", dest, ast->name);
+
+       ast_mutex_lock(&pvt->lock);
+       if (pvt->type == MBL_TYPE_PHONE) {
+               if (hfp_send_atd(pvt->hfp, dest_num)) {
+                       ast_mutex_unlock(&pvt->lock);
+                       ast_log(LOG_ERROR, "error sending ATD command on %s\n", pvt->id);
+                       return -1;
+               }
+               pvt->needchup = 1;
+               msg_queue_push(pvt, AT_OK, AT_D);
+       } else {
+               if (hsp_send_ring(pvt->rfcomm_socket)) {
+                       ast_log(LOG_ERROR, "[%s] error ringing device\n", pvt->id);
+                       ast_mutex_unlock(&pvt->lock);
+                       return -1;
+               }
+
+               if ((pvt->ring_sched_id = ast_sched_add(pvt->sched, 6000, headset_send_ring, pvt)) == -1) {
+                       ast_log(LOG_ERROR, "[%s] error ringing device\n", pvt->id);
+                       ast_mutex_unlock(&pvt->lock);
+                       return -1;
+               }
+
+               pvt->outgoing = 1;
+               pvt->needring = 1;
+       }
+       ast_mutex_unlock(&pvt->lock);
+
+       return 0;
+
+}
+
+static int mbl_hangup(struct ast_channel *ast)
+{
+
+       struct mbl_pvt *pvt;
+
+       if (!ast->tech_pvt) {
+               ast_log(LOG_WARNING, "Asked to hangup channel not connected\n");
+               return 0;
+       }
+       pvt = ast->tech_pvt;
+
+       ast_debug(1, "[%s] hanging up device\n", pvt->id);
+
+       ast_mutex_lock(&pvt->lock);
+       ast_channel_set_fd(ast, 0, -1);
+       close(pvt->sco_socket);
+       pvt->sco_socket = -1;
+
+       if (pvt->needchup) {
+               hfp_send_chup(pvt->hfp);
+               msg_queue_push(pvt, AT_OK, AT_CHUP);
+               pvt->needchup = 0;
+       }
+
+       pvt->outgoing = 0;
+       pvt->incoming = 0;
+       pvt->needring = 0;
+       pvt->owner = NULL;
+       ast->tech_pvt = NULL;
+
+       ast_mutex_unlock(&pvt->lock);
+
+       ast_setstate(ast, AST_STATE_DOWN);
+
+       return 0;
+
+}
+
+static int mbl_answer(struct ast_channel *ast)
+{
+
+       struct mbl_pvt *pvt;
+
+       pvt = ast->tech_pvt;
+
+       if (pvt->type == MBL_TYPE_HEADSET)
+               return 0;
+
+       ast_mutex_lock(&pvt->lock);
+       if (pvt->incoming) {
+               hfp_send_ata(pvt->hfp);
+               msg_queue_push(pvt, AT_OK, AT_A);
+               pvt->answered = 1;
+       }
+       ast_mutex_unlock(&pvt->lock);
+
+       return 0;
+
+}
+
+static int mbl_digit_end(struct ast_channel *ast, char digit, unsigned int duration)
+{
+       struct mbl_pvt *pvt = ast->tech_pvt;
+
+       if (pvt->type == MBL_TYPE_HEADSET)
+               return 0;
+
+       ast_mutex_lock(&pvt->lock);
+       if (hfp_send_dtmf(pvt->hfp, digit)) {
+               ast_mutex_unlock(&pvt->lock);
+               ast_debug(1, "[%s] error sending digit %c\n", pvt->id, digit);
+               return -1;
+       }
+       msg_queue_push(pvt, AT_OK, AT_VTS);
+       ast_mutex_unlock(&pvt->lock);
+
+       ast_debug(1, "[%s] dialed %c\n", pvt->id, digit);
+
+       return 0;
+}
+
+static struct ast_frame *mbl_read(struct ast_channel *ast)
+{
+
+       struct mbl_pvt *pvt = ast->tech_pvt;
+       struct ast_frame *fr = &ast_null_frame;
+       int r;
+
+       ast_debug(3, "*** mbl_read()\n");
+
+       while (ast_mutex_trylock(&pvt->lock)) {
+               CHANNEL_DEADLOCK_AVOIDANCE(ast);
+       }
+
+       if (!pvt->owner || pvt->sco_socket == -1) {
+               goto e_return;
+       }
+
+       memset(&pvt->fr, 0x00, sizeof(struct ast_frame));
+       pvt->fr.frametype = AST_FRAME_VOICE;
+       pvt->fr.subclass = DEVICE_FRAME_FORMAT;
+       pvt->fr.src = "Mobile";
+       pvt->fr.offset = AST_FRIENDLY_OFFSET;
+       pvt->fr.mallocd = 0;
+       pvt->fr.delivery.tv_sec = 0;
+       pvt->fr.delivery.tv_usec = 0;
+       pvt->fr.data.ptr = pvt->io_buf + AST_FRIENDLY_OFFSET;
+
+       if ((r = read(pvt->sco_socket, pvt->fr.data.ptr, DEVICE_FRAME_SIZE)) == -1) {
+               if (errno != EAGAIN && errno != EINTR) {
+                       ast_debug(1, "[%s] read error %d, going to wait for new connection\n", pvt->id, errno);
+                       close(pvt->sco_socket);
+                       pvt->sco_socket = -1;
+                       ast_channel_set_fd(ast, 0, -1);
+               }
+               goto e_return;
+       }
+
+       pvt->fr.datalen = r;
+       pvt->fr.samples = r / 2;
+
+       if (pvt->do_alignment_detection)
+               do_alignment_detection(pvt, pvt->fr.data.ptr, r);
+
+       fr = ast_dsp_process(ast, pvt->dsp, &pvt->fr);
+
+       ast_mutex_unlock(&pvt->lock);
+
+       return fr;
+
+e_return:
+       ast_mutex_unlock(&pvt->lock);
+       return fr;
+}
+
+static int mbl_write(struct ast_channel *ast, struct ast_frame *frame)
+{
+
+       struct mbl_pvt *pvt = ast->tech_pvt;
+       struct ast_frame *f;
+
+       ast_debug(3, "*** mbl_write\n");
+
+       if (frame->frametype != AST_FRAME_VOICE) {
+               return 0;
+       }
+
+       while (ast_mutex_trylock(&pvt->lock)) {
+               CHANNEL_DEADLOCK_AVOIDANCE(ast);
+       }
+
+       ast_smoother_feed(pvt->smoother, frame);
+
+       while ((f = ast_smoother_read(pvt->smoother))) {
+               sco_write(pvt->sco_socket, f->data.ptr, f->datalen);
+               ast_frfree(f);
+       }
+
+       ast_mutex_unlock(&pvt->lock);
+
+       return 0;
+
+}
+
+static int mbl_fixup(struct ast_channel *oldchan, struct ast_channel *newchan)
+{
+
+       struct mbl_pvt *pvt = oldchan->tech_pvt;
+
+       if (!pvt) {
+               ast_debug(1, "fixup failed, no pvt on oldchan\n");
+               return -1;
+       }
+
+       ast_mutex_lock(&pvt->lock);
+       if (pvt->owner == oldchan)
+               pvt->owner = newchan;
+       ast_mutex_unlock(&pvt->lock);
+
+       return 0;
+
+}
+
+static int mbl_devicestate(void *data)
+{
+
+       char *device;
+       int res = AST_DEVICE_INVALID;
+       struct mbl_pvt *pvt;
+
+       device = ast_strdupa(S_OR(data, ""));
+
+       ast_debug(1, "Checking device state for device %s\n", device);
+
+       AST_RWLIST_RDLOCK(&devices);
+       AST_RWLIST_TRAVERSE(&devices, pvt, entry) {
+               if (!strcmp(pvt->id, device))
+                       break;
+       }
+       AST_RWLIST_UNLOCK(&devices);
+
+       if (!pvt)
+               return res;
+
+       ast_mutex_lock(&pvt->lock);
+       if (pvt->connected) {
+               if (pvt->owner)
+                       res = AST_DEVICE_INUSE;
+               else
+                       res = AST_DEVICE_NOT_INUSE;
+       }
+       ast_mutex_unlock(&pvt->lock);
+
+       return res;
+
+}
+
+/*
+
+       Callback helpers
+
+*/
+
+/*
+
+       do_alignment_detection()
+
+       This routine attempts to detect where we get misaligned sco audio data from the bluetooth adaptor.
+
+       Its enabled by alignmentdetect=yes under the adapter entry in mobile.conf
+
+       Some adapters suffer a problem where occasionally they will byte shift the audio stream one byte to the right.
+       The result is static or white noise on the inbound (from the adapter) leg of the call.
+       This is characterised by a sudden jump in magnitude of the value of the 16 bit samples.
+
+       Here we look at the first 4 48 byte frames. We average the absolute values of each sample in the frame,
+       then average the sum of the averages of frames 1, 2, and 3.
+       Frame zero is usually zero.
+       If the end result > 100, and it usually is if we have the problem, set a flag and compensate by shifting the bytes
+       for each subsequent frame during the call.
+
+       If the result is <= 100 then clear the flag so we dont come back in here...
+
+       This seems to work OK....
+
+*/
+
+static void do_alignment_detection(struct mbl_pvt *pvt, char *buf, int buflen)
+{
+
+       int i;
+       short a, *s;
+       char *p;
+
+       if (pvt->alignment_detection_triggered) {
+               for (i=buflen, p=buf+buflen-1; i>0; i--, p--)
+                       *p = *(p-1);
+               *(p+1) = 0;
+               return;
+       }
+
+       if (pvt->alignment_count < 4) {
+               s = (short *)buf;
+               for (i=0, a=0; i<buflen/2; i++) {
+                       a += *s++;
+                       a /= i+1;
+               }
+               pvt->alignment_samples[pvt->alignment_count++] = a;
+               return;
+       }
+
+       ast_debug(1, "Alignment Detection result is [%-d %-d %-d %-d]\n", pvt->alignment_samples[0], pvt->alignment_samples[1], pvt->alignment_samples[2], pvt->alignment_samples[3]);
+
+       a = abs(pvt->alignment_samples[1]) + abs(pvt->alignment_samples[2]) + abs(pvt->alignment_samples[3]);
+       a /= 3;
+       if (a > 100) {
+               pvt->alignment_detection_triggered = 1;
+               ast_debug(1, "Alignment Detection Triggered.\n");
+       } else
+               pvt->do_alignment_detection = 0;
+
+}
+
+static int mbl_queue_control(struct mbl_pvt *pvt, enum ast_control_frame_type control)
+{
+       for (;;) {
+               if (pvt->owner) {
+                       if (ast_channel_trylock(pvt->owner)) {
+                               DEADLOCK_AVOIDANCE(&pvt->lock);
+                       } else {
+                               ast_queue_control(pvt->owner, control);
+                               ast_channel_unlock(pvt->owner);
+                               break;
+                       }
+               } else
+                       break;
+       }
+       return 0;
+}
+
+static int mbl_queue_hangup(struct mbl_pvt *pvt)
+{
+       for (;;) {
+               if (pvt->owner) {
+                       if (ast_channel_trylock(pvt->owner)) {
+                               DEADLOCK_AVOIDANCE(&pvt->lock);
+                       } else {
+                               ast_queue_hangup(pvt->owner);
+                               ast_channel_unlock(pvt->owner);
+                               break;
+                       }
+               } else
+                       break;
+       }
+       return 0;
+}
+
+static int mbl_ast_hangup(struct mbl_pvt *pvt)
+{
+       int res = 0;
+       for (;;) {
+               if (pvt->owner) {
+                       if (ast_channel_trylock(pvt->owner)) {
+                               DEADLOCK_AVOIDANCE(&pvt->lock);
+                       } else {
+                               res = ast_hangup(pvt->owner);
+                               /* no need to unlock, ast_hangup() frees the
+                                * channel */
+                               break;
+                       }
+               } else
+                       break;
+       }
+       return res;
+}
+
+/*
+
+       rfcomm helpers
+
+*/
+
+static int rfcomm_connect(bdaddr_t src, bdaddr_t dst, int remote_channel)
+{
+
+       struct sockaddr_rc addr;
+       int s;
+
+       if ((s = socket(PF_BLUETOOTH, SOCK_STREAM, BTPROTO_RFCOMM)) < 0) {
+               ast_debug(1, "socket() failed (%d).\n", errno);
+               return -1;
+       }
+
+       memset(&addr, 0, sizeof(addr));
+       addr.rc_family = AF_BLUETOOTH;
+       bacpy(&addr.rc_bdaddr, &src);
+       addr.rc_channel = (uint8_t) 1;
+       if (bind(s, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
+               ast_debug(1, "bind() failed (%d).\n", errno);
+               close(s);
+               return -1;
+       }
+
+       memset(&addr, 0, sizeof(addr));
+       addr.rc_family = AF_BLUETOOTH;
+       bacpy(&addr.rc_bdaddr, &dst);
+       addr.rc_channel = remote_channel;
+       if (connect(s, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
+               ast_debug(1, "connect() failed (%d).\n", errno);
+               close(s);
+               return -1;
+       }
+
+       return s;
+
+}
+
+/*!
+ * \brief Write to an rfcomm socket.
+ * \param rsock the socket to write to
+ * \param buf the null terminated buffer to write
+ *
+ * This function will write characters from buf.  The buffer must be null
+ * terminated.
+ *
+ * \retval -1 error
+ * \retval 0 success
+ */
+static int rfcomm_write(int rsock, char *buf)
+{
+       return rfcomm_write_full(rsock, buf, strlen(buf));
+}
+
+
+/*!
+ * \brief Write to an rfcomm socket.
+ * \param rsock the socket to write to
+ * \param buf the buffer to write
+ * \param count the number of characters from the buffer to write
+ *
+ * This function will write count characters from buf.  It will always write
+ * count chars unless it encounters an error.
+ *
+ * \retval -1 error
+ * \retval 0 success
+ */
+static int rfcomm_write_full(int rsock, char *buf, size_t count)
+{
+       char *p = buf;
+       ssize_t out_count;
+
+       ast_debug(1, "rfcomm_write() (%d) [%.*s]\n", rsock, (int) count, buf);
+       while (count > 0) {
+               if ((out_count = write(rsock, p, count)) == -1) {
+                       ast_debug(1, "rfcomm_write() error [%d]\n", errno);
+                       return -1;
+               }
+               count -= out_count;
+               p += out_count;
+       }
+
+       return 0;
+}
+
+/*!
+ * \brief Wait for activity on an rfcomm socket.
+ * \param rsock the socket to watch
+ * \param ms a pointer to an int containing a timeout in ms
+ * \return zero on timeout and the socket fd (non-zero) otherwise
+ * \retval 0 timeout
+ */
+static int rfcomm_wait(int rsock, int *ms)
+{
+       int exception, outfd;
+       outfd = ast_waitfor_n_fd(&rsock, 1, ms, &exception);
+       if (outfd < 0)
+               outfd = 0;
+
+       return outfd;
+}
+
+#ifdef RFCOMM_READ_DEBUG
+#define rfcomm_read_debug(c) __rfcomm_read_debug(c)
+static void __rfcomm_read_debug(char c)
+{
+       if (c == '\r')
+               ast_debug(2, "rfcomm_read: \\r\n");
+       else if (c == '\n')
+               ast_debug(2, "rfcomm_read: \\n\n");
+       else
+               ast_debug(2, "rfcomm_read: %c\n", c);
+}
+#else
+#define rfcomm_read_debug(c)
+#endif
+
+/*!
+ * \brief Append the given character to the given buffer and increase the
+ * in_count.
+ */
+static void inline rfcomm_append_buf(char **buf, size_t count, size_t *in_count, char c)
+{
+       if (*in_count < count) {
+               (*in_count)++;
+               *(*buf)++ = c;
+       }
+}
+
+/*!
+ * \brief Read a character from the given stream and check if it matches what
+ * we expected.
+ */
+static int rfcomm_read_and_expect_char(int rsock, char *result, char expected)
+{
+       int res;
+       char c;
+
+       if (!result)
+               result = &c;
+
+       if ((res = read(rsock, result, 1)) < 1) {
+               return res;
+       }
+       rfcomm_read_debug(*result);
+
+       if (*result != expected) {
+               return -2;
+       }
+
+       return 1;
+}
+
+/*!
+ * \brief Read a character from the given stream and append it to the given
+ * buffer if it matches the expected character.
+ */
+static int rfcomm_read_and_append_char(int rsock, char **buf, size_t count, size_t *in_count, char *result, char expected)
+{
+       int res;
+       char c;
+
+       if (!result)
+               result = &c;
+
+       if ((res = rfcomm_read_and_expect_char(rsock, result, expected)) < 1) {
+               return res;
+       }
+
+       rfcomm_append_buf(buf, count, in_count, *result);
+       return 1;
+}
+
+/*!
+ * \brief Read until '\r\n'.
+ * This function consumes the '\r\n' but does not add it to buf.
+ */
+static int rfcomm_read_until_crlf(int rsock, char **buf, size_t count, size_t *in_count)
+{
+       int res;
+       char c;
+
+       while ((res = read(rsock, &c, 1)) == 1) {
+               rfcomm_read_debug(c);
+               if (c == '\r') {
+                       if ((res = rfcomm_read_and_expect_char(rsock, &c, '\n')) == 1) {
+                               break;
+                       } else if (res == -2) {
+                               rfcomm_append_buf(buf, count, in_count, '\r');
+                       } else {
+                               rfcomm_append_buf(buf, count, in_count, '\r');
+                               break;
+                       }
+               }
+
+               rfcomm_append_buf(buf, count, in_count, c);
+       }
+       return res;
+}
+
+/*!
+ * \brief Read the remainder of an AT SMS prompt.
+ * \note the entire parsed string is '\r\n> '
+ *
+ * By the time this function is executed, only a ' ' is left to read.
+ */
+static int rfcomm_read_sms_prompt(int rsock, char **buf, size_t count, size_t *in_count)
+{
+       int res;
+       if ((res = rfcomm_read_and_append_char(rsock, buf, count, in_count, NULL, ' ')) < 1)
+              goto e_return;
+
+       return 1;
+
+e_return:
+       ast_log(LOG_ERROR, "error parsing SMS prompt on rfcomm socket\n");
+       return res;
+}
+
+/*!
+ * \brief Read and AT result code.
+ * \note the entire parsed string is '\r\n<result code>\r\n'
+ */
+static int rfcomm_read_result(int rsock, char **buf, size_t count, size_t *in_count)
+{
+       int res;
+       char c;
+
+       if ((res = rfcomm_read_and_expect_char(rsock, &c, '\n')) < 1) {
+               goto e_return;
+       }
+
+       if ((res = rfcomm_read_and_append_char(rsock, buf, count, in_count, &c, '>')) == 1) {
+               return rfcomm_read_sms_prompt(rsock, buf, count, in_count);
+       } else if (res != -2) {
+               goto e_return;
+       }
+
+       rfcomm_append_buf(buf, count, in_count, c);
+       res = rfcomm_read_until_crlf(rsock, buf, count, in_count);
+
+       if (res != 1)
+               return res;
+
+       /* check for CMGR, which contains an embedded \r\n */
+       if (*in_count >= 5 && !strncmp(*buf - *in_count, "+CMGR", 5)) {
+               rfcomm_append_buf(buf, count, in_count, '\r');
+               rfcomm_append_buf(buf, count, in_count, '\n');
+               return rfcomm_read_until_crlf(rsock, buf, count, in_count);
+       }
+
+       return 1;
+
+e_return:
+       ast_log(LOG_ERROR, "error parsing AT result on rfcomm socket");
+       return res;
+}
+
+/*!
+ * \brief Read the remainder of an AT command.
+ * \note the entire parsed string is '<at command>\r'
+ */
+static int rfcomm_read_command(int rsock, char **buf, size_t count, size_t *in_count)
+{
+       int res;
+       char c;
+
+       while ((res = read(rsock, &c, 1)) == 1) {
+               rfcomm_read_debug(c);
+               /* stop when we get to '\r' */
+               if (c == '\r')
+                       break;
+
+               rfcomm_append_buf(buf, count, in_count, c);
+       }
+       return res;
+}
+
+/*!
+ * \brief Read one Hayes AT message from an rfcomm socket.
+ * \param rsock the rfcomm socket to read from
+ * \param buf the buffer to store the result in
+ * \param count the size of the buffer or the maximum number of characters to read
+ *
+ * Here we need to read complete Hayes AT messages.  The AT message formats we
+ * support are listed below.
+ *
+ * \verbatim
+ * \r\n<result code>\r\n
+ * <at command>\r
+ * \r\n> 
+ * \endverbatim
+ *
+ * These formats correspond to AT result codes, AT commands, and the AT SMS
+ * prompt respectively.  When messages are read the leading and trailing '\r'
+ * and '\n' characters are discarded.  If the given buffer is not large enough
+ * to hold the response, what does not fit in the buffer will be dropped.
+ *
+ * \note The rfcomm connection to the device is asynchronous, so there is no
+ * guarantee that responses will be returned in a single read() call. We handle
+ * this by blocking until we can read an entire response.
+ *
+ * \retval 0 end of file
+ * \retval -1 read error
+ * \retval -2 parse error
+ * \retval other the number of characters added to buf
+ */
+static ssize_t rfcomm_read(int rsock, char *buf, size_t count)
+{
+       ssize_t res;
+       size_t in_count = 0;
+       char c;
+
+       if ((res = rfcomm_read_and_expect_char(rsock, &c, '\r')) == 1) {
+               res = rfcomm_read_result(rsock, &buf, count, &in_count);
+       } else if (res == -2) {
+               rfcomm_append_buf(&buf, count, &in_count, c);
+               res = rfcomm_read_command(rsock, &buf, count, &in_count);
+       }
+
+       if (res < 1)
+               return res;
+       else
+               return in_count;
+}
+
+/*
+
+       sco helpers and callbacks
+
+*/
+
+static int sco_connect(bdaddr_t src, bdaddr_t dst)
+{
+
+       struct sockaddr_sco addr;
+       int s;
+
+       if ((s = socket(PF_BLUETOOTH, SOCK_SEQPACKET, BTPROTO_SCO)) < 0) {
+               ast_debug(1, "socket() failed (%d).\n", errno);
+               return -1;
+       }
+
+/* XXX this does not work with the do_sco_listen() thread (which also bind()s
+ * to this address).  Also I am not sure if it is necessary. */
+#if 0
+       memset(&addr, 0, sizeof(addr));
+       addr.sco_family = AF_BLUETOOTH;
+       bacpy(&addr.sco_bdaddr, &src);
+       if (bind(s, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
+               ast_debug(1, "bind() failed (%d).\n", errno);
+               close(s);
+               return -1;
+       }
+#endif
+
+       memset(&addr, 0, sizeof(addr));
+       addr.sco_family = AF_BLUETOOTH;
+       bacpy(&addr.sco_bdaddr, &dst);
+
+       if (connect(s, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
+               ast_debug(1, "sco connect() failed (%d).\n", errno);
+               close(s);
+               return -1;
+       }
+
+       return s;
+
+}
+
+static int sco_write(int s, char *buf, int len)
+{
+
+       int r;
+
+       if (s == -1) {
+               ast_debug(3, "sco_write() not ready\n");
+               return 0;
+       }
+
+       ast_debug(3, "sco_write()\n");
+
+       r = write(s, buf, len);
+       if (r == -1) {
+               ast_debug(3, "sco write error %d\n", errno);
+               return 0;
+       }
+
+       return 1;
+
+}
+
+/*!
+ * \brief Accept SCO connections.
+ * This function is an ast_io callback function used to accept incoming sco
+ * audio connections.
+ */
+static int sco_accept(int *id, int fd, short events, void *data)
+{
+       struct adapter_pvt *adapter = (struct adapter_pvt *) data;
+       struct sockaddr_sco addr;
+       socklen_t addrlen;
+       struct mbl_pvt *pvt;
+       socklen_t len;
+       char saddr[18];
+       struct sco_options so;
+       int sock;
+
+       addrlen = sizeof(struct sockaddr_sco);
+       if ((sock = accept(fd, (struct sockaddr *)&addr, &addrlen)) == -1) {
+               ast_log(LOG_ERROR, "error accepting audio connection on adapter %s\n", adapter->id);
+               return 0;
+       }
+
+       len = sizeof(so);
+       getsockopt(sock, SOL_SCO, SCO_OPTIONS, &so, &len);
+
+       ba2str(&addr.sco_bdaddr, saddr);
+       ast_debug(1, "Incoming Audio Connection from device %s MTU is %d\n", saddr, so.mtu);
+
+       /* figure out which device this sco connection belongs to */
+       pvt = NULL;
+       AST_RWLIST_RDLOCK(&devices);
+       AST_RWLIST_TRAVERSE(&devices, pvt, entry) {
+               if (!bacmp(&pvt->addr, &addr.sco_bdaddr))
+                       break;
+       }
+       AST_RWLIST_UNLOCK(&devices);
+       if (!pvt) {
+               ast_log(LOG_WARNING, "could not find device for incoming audio connection\n");
+               close(sock);
+               return 1;
+       }
+
+       ast_mutex_lock(&pvt->lock);
+       if (pvt->sco_socket != -1) {
+               close(pvt->sco_socket);
+               pvt->sco_socket = -1;
+       }
+
+       pvt->sco_socket = sock;
+       if (pvt->owner) {
+               ast_channel_set_fd(pvt->owner, 0, sock);
+       } else {
+               ast_debug(1, "incoming audio connection for pvt without owner\n");
+       }
+
+       ast_mutex_unlock(&pvt->lock);
+
+       return 1;
+}
+
+/*!
+ * \brief Bind an SCO listener socket for the given adapter.
+ * \param adapter an adapter_pvt
+ * \return -1 on error, non zero on success
+ */
+static int sco_bind(struct adapter_pvt *adapter)
+{
+       struct sockaddr_sco addr;
+       int opt = 1;
+
+       if ((adapter->sco_socket = socket(PF_BLUETOOTH, SOCK_SEQPACKET, BTPROTO_SCO)) < 0) {
+               ast_log(LOG_ERROR, "Unable to create sco listener socket for adapter %s.\n", adapter->id);
+               goto e_return;
+       }
+
+       memset(&addr, 0, sizeof(addr));
+       addr.sco_family = AF_BLUETOOTH;
+       bacpy(&addr.sco_bdaddr, &adapter->addr);
+       if (bind(adapter->sco_socket, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
+               ast_log(LOG_ERROR, "Unable to bind sco listener socket. (%d)\n", errno);
+               goto e_close_socket;
+       }
+       if (setsockopt(adapter->sco_socket, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)) == -1) {
+               ast_log(LOG_ERROR, "Unable to setsockopt sco listener socket.\n");
+               goto e_close_socket;
+       }
+       if (listen(adapter->sco_socket, 5) < 0) {
+               ast_log(LOG_ERROR, "Unable to listen sco listener socket.\n");
+               goto e_close_socket;
+       }
+
+       return adapter->sco_socket;
+
+e_close_socket:
+       close(adapter->sco_socket);
+       adapter->sco_socket = -1;
+e_return:
+       return -1;
+}
+
+
+/*
+ * Hayes AT command helpers.
+ */
+
+/*!
+ * \brief Match the given buffer with the given prefix.
+ * \param buf the buffer to match
+ * \param prefix the prefix to match
+ */
+static int at_match_prefix(char *buf, char *prefix)
+{
+       return !strncmp(buf, prefix, strlen(prefix));
+}
+
+/*!
+ * \brief Read an AT message and clasify it.
+ * \param rsock an rfcomm socket
+ * \param buf the buffer to store the result in
+ * \param count the size of the buffer or the maximum number of characters to read
+ * \return the type of message received, in addition buf will contain the
+ * message received and will be null terminated
+ * \see at_read()
+ */
+static at_message_t at_read_full(int rsock, char *buf, size_t count)
+{
+       ssize_t s;
+       if ((s = rfcomm_read(rsock, buf, count - 1)) < 1)
+               return s;
+       buf[s] = '\0';
+
+       if (!strcmp("OK", buf)) {
+               return AT_OK;
+       } else if (!strcmp("ERROR", buf)) {
+               return AT_ERROR;
+       } else if (!strcmp("RING", buf)) {
+               return AT_RING;
+       } else if (!strcmp("AT+CKPD=200", buf)) {
+               return AT_CKPD;
+       } else if (!strcmp("> ", buf)) {
+               return AT_SMS_PROMPT;
+       } else if (at_match_prefix(buf, "+CMTI:")) {
+               return AT_CMTI;
+       } else if (at_match_prefix(buf, "+CIEV:")) {
+               return AT_CIEV;
+       } else if (at_match_prefix(buf, "+BRSF:")) {
+               return AT_BRSF;
+       } else if (at_match_prefix(buf, "+CIND:")) {
+               return AT_CIND;
+       } else if (at_match_prefix(buf, "+CLIP:")) {
+               return AT_CLIP;
+       } else if (at_match_prefix(buf, "+CMGR:")) {
+               return AT_CMGR;
+       } else if (at_match_prefix(buf, "+VGM:")) {
+               return AT_VGM;
+       } else if (at_match_prefix(buf, "+VGS:")) {
+               return AT_VGS;
+       } else if (at_match_prefix(buf, "+CMS ERROR:")) {
+               return AT_CMS_ERROR;
+       } else if (at_match_prefix(buf, "AT+VGM=")) {
+               return AT_VGM;
+       } else if (at_match_prefix(buf, "AT+VGS=")) {
+               return AT_VGS;
+       } else {
+               return AT_UNKNOWN;
+       }
+}
+
+/*!
+ * \brief Get the string representation of the given AT message.
+ * \param msg the message to process
+ * \return a string describing the given message
+ */
+static inline const char *at_msg2str(at_message_t msg)
+{
+       switch (msg) {
+       /* errors */
+       case AT_PARSE_ERROR:
+               return "PARSE ERROR";
+       case AT_READ_ERROR:
+               return "READ ERROR";
+       default:
+       case AT_UNKNOWN:
+               return "UNKNOWN";
+       /* at responses */
+       case AT_OK:
+               return "OK";
+       case AT_ERROR:
+               return "ERROR";
+       case AT_RING:
+               return "RING";
+       case AT_BRSF:
+               return "AT+BRSF";
+       case AT_CIND:
+               return "AT+CIND";
+       case AT_CIEV:
+               return "AT+CIEV";
+       case AT_CLIP:
+               return "AT+CLIP";
+       case AT_CMTI:
+               return "AT+CMTI";
+       case AT_CMGR:
+               return "AT+CMGR";
+       case AT_SMS_PROMPT:
+               return "SMS PROMPT";
+       case AT_CMS_ERROR:
+               return "+CMS ERROR";
+       /* at commands */
+       case AT_A:
+               return "ATA";
+       case AT_D:
+               return "ATD";
+       case AT_CHUP:
+               return "AT+CHUP";
+       case AT_CKPD:
+               return "AT+CKPD";
+       case AT_CMGS:
+               return "AT+CMGS";
+       case AT_VGM:
+               return "AT+VGM";
+       case AT_VGS:
+               return "AT+VGS";
+       case AT_VTS:
+               return "AT+VTS";
+       case AT_CMGF:
+               return "AT+CMGF";
+       case AT_CNMI:
+               return "AT+CNMI";
+       case AT_CMER:
+               return "AT+CMER";
+       case AT_CIND_TEST:
+               return "AT+CIND=?";
+       }
+}
+
+
+/*
+ * bluetooth handsfree profile helpers
+ */
+
+/*!
+ * \brief Parse a CIEV event.
+ * \param hfp an hfp_pvt struct
+ * \param buf the buffer to parse (null terminated)
+ * \param value a pointer to an int to store the event value in (can be NULL)
+ * \return 0 on error (parse error, or unknown event) or a HFP_CIND_* value on
+ * success
+ */
+static int hfp_parse_ciev(struct hfp_pvt *hfp, char *buf, int *value)
+{
+       int i, v;
+       if (!value)
+               value = &v;
+
+       if (!sscanf(buf, "+CIEV: %d,%d", &i, value)) {
+               ast_debug(2, "[%s] error parsing CIEV event '%s'\n", hfp->owner->id, buf);
+               return HFP_CIND_NONE;
+       }
+
+       if (i >= sizeof(hfp->cind_state)) {
+               ast_debug(2, "[%s] CIEV event index too high (%s)\n", hfp->owner->id, buf);
+               return HFP_CIND_NONE;
+       }
+
+       hfp->cind_state[i] = *value;
+       return hfp->cind_index[i];
+}
+
+/*!
+ * \brief Parse a CLIP event.
+ * \param hfp an hfp_pvt struct
+ * \param buf the buffer to parse (null terminated)
+ * @note buf will be modified when the CID string is parsed
+ * \return NULL on error (parse error) or a pointer to the caller id
+ * inforamtion in buf
+ * success
+ */
+static char *hfp_parse_clip(struct hfp_pvt *hfp, char *buf)
+{
+       int i, state;
+       char *clip = NULL;
+       size_t s;
+
+       /* parse clip info in the following format:
+        * +CLIP: "123456789",128,...
+        */
+       state = 0;
+       s = strlen(buf);
+       for (i = 0; i < s && state != 3; i++) {
+               switch (state) {
+               case 0: /* search for start of the number (") */
+                       if (buf[i] == '"') {
+                               state++;
+                       }
+                       break;
+               case 1: /* mark the number */
+                       clip = &buf[i];
+                       state++;
+                       /* fall through */
+               case 2: /* search for the end of the number (") */
+                       if (buf[i] == '"') {
+                               buf[i] = '\0';
+                               state++;
+                       }
+                       break;
+               }
+       }
+
+       if (state != 3) {
+               return NULL;
+       }
+
+       return clip;
+}
+
+/*!
+ * \brief Parse a CMTI notification.
+ * \param hfp an hfp_pvt struct
+ * \param buf the buffer to parse (null terminated)
+ * @note buf will be modified when the CMTI message is parsed
+ * \return -1 on error (parse error) or the index of the new sms message
+ */
+static int hfp_parse_cmti(struct hfp_pvt *hfp, char *buf)
+{
+       int index = -1;
+
+       /* parse cmti info in the following format:
+        * +CMTI: <mem>,<index> 
+        */
+       if (!sscanf(buf, "+CMTI: %*[^,],%d", &index)) {
+               ast_debug(2, "[%s] error parsing CMTI event '%s'\n", hfp->owner->id, buf);
+               return -1;
+       }
+
+       return index;
+}
+
+/*!
+ * \brief Parse a CMGR message.
+ * \param hfp an hfp_pvt struct
+ * \param buf the buffer to parse (null terminated)
+ * \param from_number a pointer to a char pointer which will store the from
+ * number
+ * \param text a pointer to a char pointer which will store the message text
+ * @note buf will be modified when the CMGR message is parsed
+ * \retval -1 parse error
+ * \retval 0 success
+ */
+static int hfp_parse_cmgr(struct hfp_pvt *hfp, char *buf, char **from_number, char **text)
+{
+       int i, state;
+       size_t s;
+
+       /* parse cmgr info in the following format:
+        * +CMGR: <msg status>,"+123456789",...\r\n
+        * <message text>
+        */
+       state = 0;
+       s = strlen(buf);
+       for (i = 0; i < s && s != 6; i++) {
+               switch (state) {
+               case 0: /* search for start of the number section (,) */
+                       if (buf[i] == ',') {
+                               state++;
+                       }
+                       break;
+               case 1: /* find the opening quote (") */
+                       if (buf[i] == '"') {
+                               state++;
+                       }
+               case 2: /* mark the start of the number */
+                       if (from_number) {
+                               *from_number = &buf[i];
+                               state++;
+                       }
+                       /* fall through */
+               case 3: /* search for the end of the number (") */
+                       if (buf[i] == '"') {
+                               buf[i] = '\0';
+                               state++;
+                       }
+                       break;
+               case 4: /* search for the start of the message text (\n) */
+                       if (buf[i] == '\n') {
+                               state++;
+                       }
+                       break;
+               case 5: /* mark the start of the message text */
+                       if (text) {
+                               *text = &buf[i];
+                               state++;
+                       }
+                       break;
+               }
+       }
+
+       if (state != 6) {
+               return -1;
+       }
+
+       return 0;
+}
+
+/*!
+ * \brief Convert a hfp_hf struct to a BRSF int.
+ * \param hf an hfp_hf brsf object
+ * \return an integer representing the given brsf struct
+ */
+static int hfp_brsf2int(struct hfp_hf *hf)
+{
+       int brsf = 0;
+
+       brsf |= hf->ecnr ? HFP_HF_ECNR : 0;
+       brsf |= hf->cw ? HFP_HF_CW : 0;
+       brsf |= hf->cid ? HFP_HF_CID : 0;
+       brsf |= hf->voice ? HFP_HF_VOICE : 0;
+       brsf |= hf->volume ? HFP_HF_VOLUME : 0;
+       brsf |= hf->status ? HFP_HF_STATUS : 0;
+       brsf |= hf->control ? HFP_HF_CONTROL : 0;
+
+       return brsf;
+}
+
+/*!
+ * \brief Convert a BRSF int to an hfp_ag struct.
+ * \param brsf a brsf integer
+ * \param ag a AG (hfp_ag) brsf object
+ * \return a pointer to the given hfp_ag object populated with the values from
+ * the given brsf integer
+ */
+static struct hfp_ag *hfp_int2brsf(int brsf, struct hfp_ag *ag)
+{
+       ag->cw = brsf & HFP_AG_CW ? 1 : 0;
+       ag->ecnr = brsf & HFP_AG_ECNR ? 1 : 0;
+       ag->voice = brsf & HFP_AG_VOICE ? 1 : 0;
+       ag->ring = brsf & HFP_AG_RING ? 1 : 0;
+       ag->tag = brsf & HFP_AG_TAG ? 1 : 0;
+       ag->reject = brsf & HFP_AG_REJECT ? 1 : 0;
+       ag->status = brsf & HFP_AG_STATUS ? 1 : 0;
+       ag->control = brsf & HFP_AG_CONTROL ? 1 : 0;
+       ag->errors = brsf & HFP_AG_ERRORS ? 1 : 0;
+
+       return ag;
+}
+
+
+/*!
+ * \brief Send a BRSF request.
+ * \param hfp an hfp_pvt struct
+ * \param brsf an hfp_hf brsf struct
+ *
+ * \retval 0 on success
+ * \retval -1 on error
+ */
+static int hfp_send_brsf(struct hfp_pvt *hfp, struct hfp_hf *brsf)
+{
+       char cmd[32];
+       snprintf(cmd, sizeof(cmd), "AT+BRSF=%d\r", hfp_brsf2int(brsf));
+       return rfcomm_write(hfp->rsock, cmd);
+}
+
+/*!
+ * \brief Send the CIND read command.
+ * \param hfp an hfp_pvt struct
+ */
+static int hfp_send_cind(struct hfp_pvt *hfp)
+{
+       return rfcomm_write(hfp->rsock, "AT+CIND?\r");
+}
+
+/*!
+ * \brief Send the CIND test command.
+ * \param hfp an hfp_pvt struct
+ */
+static int hfp_send_cind_test(struct hfp_pvt *hfp)
+{
+       return rfcomm_write(hfp->rsock, "AT+CIND=?\r");
+}
+
+/*!
+ * \brief Enable or disable indicator events reporting.
+ * \param hfp an hfp_pvt struct
+ * \param status enable or disable events reporting (should be 1 or 0)
+ */
+static int hfp_send_cmer(struct hfp_pvt *hfp, int status)
+{
+       char cmd[32];
+       snprintf(cmd, sizeof(cmd), "AT+CMER=3,0,0,%d\r", status ? 1 : 0);
+       return rfcomm_write(hfp->rsock, cmd);
+}
+
+/*!
+ * \brief Send the current speaker gain level.
+ * \param hfp an hfp_pvt struct
+ * \param value the value to send (must be between 0 and 15)
+ */
+static int hfp_send_vgs(struct hfp_pvt *hfp, int value)
+{
+       char cmd[32];
+       snprintf(cmd, sizeof(cmd), "AT+VGS=%d\r", value);
+       return rfcomm_write(hfp->rsock, cmd);
+}
+
+#if 0
+/*!
+ * \brief Send the current microphone gain level.
+ * \param hfp an hfp_pvt struct
+ * \param value the value to send (must be between 0 and 15)
+ */
+static int hfp_send_vgm(struct hfp_pvt *hfp, int value)
+{
+       char cmd[32];
+       snprintf(cmd, sizeof(cmd), "AT+VGM=%d\r", value);
+       return rfcomm_write(hfp->rsock, cmd);
+}
+#endif
+
+/*!
+ * \brief Enable or disable calling line identification.
+ * \param hfp an hfp_pvt struct
+ * \param status enable or disable calling line identification (should be 1 or
+ * 0)
+ */
+static int hfp_send_clip(struct hfp_pvt *hfp, int status)
+{
+       char cmd[32];
+       snprintf(cmd, sizeof(cmd), "AT+CLIP=%d\r", status ? 1 : 0);
+       return rfcomm_write(hfp->rsock, cmd);
+}
+
+/*!
+ * \brief Send a DTMF command.
+ * \param hfp an hfp_pvt struct
+ * \param digit the dtmf digit to send
+ * \return the result of rfcomm_write() or -1 on an invalid digit being sent
+ */
+static int hfp_send_dtmf(struct hfp_pvt *hfp, char digit)
+{
+       char cmd[10];
+
+       switch(digit) {
+       case '0':
+       case '1':
+       case '2':
+       case '3':
+       case '4':
+       case '5':
+       case '6':
+       case '7':
+       case '8':
+       case '9':
+       case '*':
+       case '#':
+               snprintf(cmd, sizeof(cmd), "AT+VTS=%c\r", digit);
+               return rfcomm_write(hfp->rsock, cmd);
+       default:
+               return -1;
+       }
+}
+
+/*!
+ * \brief Set the SMS mode.
+ * \param hfp an hfp_pvt struct
+ * \param mode the sms mode (0 = PDU, 1 = Text)
+ */
+static int hfp_send_cmgf(struct hfp_pvt *hfp, int mode)
+{
+       char cmd[32];
+       snprintf(cmd, sizeof(cmd), "AT+CMGF=%d\r", mode);
+       return rfcomm_write(hfp->rsock, cmd);
+}
+
+/*!
+ * \brief Setup SMS new message indication.
+ * \param hfp an hfp_pvt struct
+ */
+static int hfp_send_cnmi(struct hfp_pvt *hfp)
+{
+       return rfcomm_write(hfp->rsock, "AT+CNMI=2,1,0,0,0\r");
+}
+
+/*!
+ * \brief Read an SMS message.
+ * \param hfp an hfp_pvt struct
+ * \param index the location of the requested message
+ */
+static int hfp_send_cmgr(struct hfp_pvt *hfp, int index)
+{
+       char cmd[32];
+       snprintf(cmd, sizeof(cmd), "AT+CMGR=%d\r", index);
+       return rfcomm_write(hfp->rsock, cmd);
+}
+
+/*!
+ * \brief Start sending an SMS message.
+ * \param hfp an hfp_pvt struct
+ * \param number the destination of the message
+ */
+static int hfp_send_cmgs(struct hfp_pvt *hfp, const char *number)
+{
+       char cmd[64];
+       snprintf(cmd, sizeof(cmd), "AT+CMGS=\"%s\"\r", number);
+       return rfcomm_write(hfp->rsock, cmd);
+}
+
+/*!
+ * \brief Send the text of an SMS message.
+ * \param hfp an hfp_pvt struct
+ * \param message the text of the message
+ */
+static int hfp_send_sms_text(struct hfp_pvt *hfp, const char *message)
+{
+       char cmd[162];
+       snprintf(cmd, sizeof(cmd), "%.160s\x1a", message);
+       return rfcomm_write(hfp->rsock, cmd);
+}
+
+/*!
+ * \brief Send AT+CHUP.
+ * \param hfp an hfp_pvt struct
+ */
+static int hfp_send_chup(struct hfp_pvt *hfp)
+{
+       return rfcomm_write(hfp->rsock, "AT+CHUP\r");
+}
+
+/*!
+ * \brief Send ATD.
+ * \param hfp an hfp_pvt struct
+ * \param number the number to send
+ */
+static int hfp_send_atd(struct hfp_pvt *hfp, const char *number)
+{
+       char cmd[64];
+       snprintf(cmd, sizeof(cmd), "ATD%s;\r", number);
+       return rfcomm_write(hfp->rsock, cmd);
+}
+
+/*!
+ * \brief Send ATA.
+ * \param hfp an hfp_pvt struct
+ */
+static int hfp_send_ata(struct hfp_pvt *hfp)
+{
+       return rfcomm_write(hfp->rsock, "ATA\r");
+}
+
+/*!
+ * \brief Parse BRSF data.
+ * \param hfp an hfp_pvt struct
+ * \param buf the buffer to parse (null terminated)
+ */
+static int hfp_parse_brsf(struct hfp_pvt *hfp, const char *buf)
+{
+       int brsf;
+
+       if (!sscanf(buf, "+BRSF:%d", &brsf))
+               return -1;
+
+       hfp_int2brsf(brsf, &hfp->brsf);
+
+       return 0;
+}
+
+/*!
+ * \brief Parse and store the given indicator.
+ * \param hfp an hfp_pvt struct
+ * \param group the indicator group
+ * \param indicator the indicator to parse
+ */
+static int hfp_parse_cind_indicator(struct hfp_pvt *hfp, int group, char *indicator)
+{
+       int value;
+
+       /* store the current indicator */
+       if (group >= sizeof(hfp->cind_state)) {
+               ast_debug(1, "ignoring CIND state '%s' for group %d, we only support up to %d indicators\n", indicator, group, (int) sizeof(hfp->cind_state));
+               return -1;
+       }
+
+       if (!sscanf(indicator, "%d", &value)) {
+               ast_debug(1, "error parsing CIND state '%s' for group %d\n", indicator, group);
+               return -1;
+       }
+
+       hfp->cind_state[group] = value;
+       return 0;
+}
+
+/*!
+ * \brief Read the result of the AT+CIND? command.
+ * \param hfp an hfp_pvt struct
+ * \param buf the buffer to parse (null terminated)
+ * \note hfp_send_cind_test() and hfp_parse_cind_test() should be called at
+ * least once before this function is called.
+ */
+static int hfp_parse_cind(struct hfp_pvt *hfp, char *buf)
+{
+       int i, state, group;
+       size_t s;
+       char *indicator = NULL;
+
+       /* parse current state of all of our indicators.  The list is in the
+        * following format:
+        * +CIND: 1,0,2,0,0,0,0
+        */
+       group = 0;
+       state = 0;
+       s = strlen(buf);
+       for (i = 0; i < s; i++) {
+               switch (state) {
+               case 0: /* search for start of the status indicators (a space) */
+                       if (buf[i] == ' ') {
+                               group++;
+                               state++;
+                       }
+                       break;
+               case 1: /* mark this indicator */
+                       indicator = &buf[i];
+                       state++;
+                       break;
+               case 2: /* search for the start of the next indicator (a comma) */
+                       if (buf[i] == ',') {
+                               buf[i] = '\0';
+
+                               hfp_parse_cind_indicator(hfp, group, indicator);
+
+                               group++;
+                               state = 1;
+                       }
+                       break;
+               }
+       }
+
+       /* store the last indicator */
+       if (state == 2)
+               hfp_parse_cind_indicator(hfp, group, indicator);
+
+       return 0;
+}
+
+/*!
+ * \brief Parse the result of the AT+CIND=? command.
+ * \param hfp an hfp_pvt struct
+ * \param buf the buffer to parse (null terminated)
+ */
+static int hfp_parse_cind_test(struct hfp_pvt *hfp, char *buf)
+{
+       int i, state, group;
+       size_t s;
+       char *indicator = NULL, *values;
+
+       hfp->nocallsetup = 1;
+
+       /* parse the indications list.  It is in the follwing format:
+        * +CIND: ("ind1",(0-1)),("ind2",(0-5))
+        */
+       group = 0;
+       state = 0;
+       s = strlen(buf);
+       for (i = 0; i < s; i++) {
+               switch (state) {
+               case 0: /* search for start of indicator block */
+                       if (buf[i] == '(') {
+                               group++;
+                               state++;
+                       }
+                       break;
+               case 1: /* search for '"' in indicator block */
+                       if (buf[i] == '"') {
+                               state++;
+                       }
+                       break;
+               case 2: /* mark the start of the indicator name */
+                       indicator = &buf[i];
+                       state++;
+                       break;
+               case 3: /* look for the end of the indicator name */
+                       if (buf[i] == '"') {
+                               buf[i] = '\0';
+                               state++;
+                       }
+                       break;
+               case 4: /* find the start of the value range */
+                       if (buf[i] == '(') {
+                               state++;
+                       }
+                       break;
+               case 5: /* mark the start of the value range */
+                       values = &buf[i];
+                       state++;
+                       break;
+               case 6: /* find the end of the value range */
+                       if (buf[i] == ')') {
+                               buf[i] = '\0';
+                               state++;
+                       }
+                       break;
+               case 7: /* process the values we found */
+                       if (group < sizeof(hfp->cind_index)) {
+                               if (!strcmp(indicator, "service")) {
+                                       hfp->cind_map.service = group;
+                                       hfp->cind_index[group] = HFP_CIND_SERVICE;
+                               } else if (!strcmp(indicator, "call")) {
+                                       hfp->cind_map.call = group;
+                                       hfp->cind_index[group] = HFP_CIND_CALL;
+                               } else if (!strcmp(indicator, "callsetup")) {
+                                       hfp->nocallsetup = 0;
+                                       hfp->cind_map.callsetup = group;
+                                       hfp->cind_index[group] = HFP_CIND_CALLSETUP;
+                               } else if (!strcmp(indicator, "call_setup")) { /* non standard call setup identifier */
+                                       hfp->nocallsetup = 0;
+                                       hfp->cind_map.callsetup = group;
+                                       hfp->cind_index[group] = HFP_CIND_CALLSETUP;
+                               } else if (!strcmp(indicator, "callheld")) {
+                                       hfp->cind_map.callheld = group;
+                                       hfp->cind_index[group] = HFP_CIND_CALLHELD;
+                               } else if (!strcmp(indicator, "signal")) {
+                                       hfp->cind_map.signal = group;
+                                       hfp->cind_index[group] = HFP_CIND_SIGNAL;
+                               } else if (!strcmp(indicator, "roam")) {
+                                       hfp->cind_map.roam = group;
+                                       hfp->cind_index[group] = HFP_CIND_ROAM;
+                               } else if (!strcmp(indicator, "battchg")) {
+                                       hfp->cind_map.battchg = group;
+                                       hfp->cind_index[group] = HFP_CIND_BATTCHG;
+                               } else {
+                                       hfp->cind_index[group] = HFP_CIND_UNKNOWN;
+                                       ast_debug(2, "ignoring unknown CIND indicator '%s'\n", indicator);
+                               }
+                       } else {
+                                       ast_debug(1, "can't store indicator %d (%s), we only support up to %d indicators", group, indicator, (int) sizeof(hfp->cind_index));
+                       }
+
+                       state = 0;
+                       break;
+               }
+       }
+
+       hfp->owner->no_callsetup = hfp->nocallsetup;
+
+       return 0;
+}
+
+
+/*
+ * Bluetooth Headset Profile helpers
+ */
+
+/*!
+ * \brief Send an OK AT response.
+ * \param rsock the rfcomm socket to use
+ */
+static int hsp_send_ok(int rsock)
+{
+       return rfcomm_write(rsock, "\r\nOK\r\n");
+}
+
+/*!
+ * \brief Send an ERROR AT response.
+ * \param rsock the rfcomm socket to use
+ */
+static int hsp_send_error(int rsock)
+{
+       return rfcomm_write(rsock, "\r\nERROR\r\n");
+}
+
+/*!
+ * \brief Send a speaker gain unsolicited AT response
+ * \param rsock the rfcomm socket to use
+ * \param gain the speaker gain value
+ */
+static int hsp_send_vgs(int rsock, int gain)
+{
+       char cmd[32];
+       snprintf(cmd, sizeof(cmd), "\r\n+VGS=%d\r\n", gain);
+       return rfcomm_write(rsock, cmd);
+}
+
+/*!
+ * \brief Send a microphone gain unsolicited AT response
+ * \param rsock the rfcomm socket to use
+ * \param gain the microphone gain value
+ */
+static int hsp_send_vgm(int rsock, int gain)
+{
+       char cmd[32];
+       snprintf(cmd, sizeof(cmd), "\r\n+VGM=%d\r\n", gain);
+       return rfcomm_write(rsock, cmd);
+}
+
+/*!
+ * \brief Send a RING unsolicited AT response.
+ * \param rsock the rfcomm socket to use
+ */
+static int hsp_send_ring(int rsock)
+{
+       return rfcomm_write(rsock, "\r\nRING\r\n");
+}
+
+/*
+ * message queue functions
+ */
+
+/*!
+ * \brief Add an item to the back of the queue.
+ * \param pvt a mbl_pvt structure
+ * \param expect the msg we expect to recieve
+ * \param response_to the message that was sent to generate the expected
+ * response
+ */
+static int msg_queue_push(struct mbl_pvt *pvt, at_message_t expect, at_message_t response_to)
+{
+       struct msg_queue_entry *msg;
+       if (!(msg = ast_calloc(1, sizeof(*msg)))) {
+               return -1;
+       }
+       msg->expected = expect;
+       msg->response_to = response_to;
+
+       AST_LIST_INSERT_TAIL(&pvt->msg_queue, msg, entry);
+       return 0;
+}
+
+/*!
+ * \brief Add an item to the back of the queue with data.
+ * \param pvt a mbl_pvt structure
+ * \param expect the msg we expect to recieve
+ * \param response_to the message that was sent to generate the expected
+ * response
+ * \param data data associated with this message, it will be freed when the
+ * message is freed
+ */
+static int msg_queue_push_data(struct mbl_pvt *pvt, at_message_t expect, at_message_t response_to, void *data)
+{
+       struct msg_queue_entry *msg;
+       if (!(msg = ast_calloc(1, sizeof(*msg)))) {
+               return -1;
+       }
+       msg->expected = expect;
+       msg->response_to = response_to;
+       msg->data = data;
+
+       AST_LIST_INSERT_TAIL(&pvt->msg_queue, msg, entry);
+       return 0;
+}
+
+/*!
+ * \brief Remove an item from the front of the queue.
+ * \param pvt a mbl_pvt structure
+ * \return a pointer to the removed item
+ */
+static struct msg_queue_entry *msg_queue_pop(struct mbl_pvt *pvt)
+{
+       return AST_LIST_REMOVE_HEAD(&pvt->msg_queue, entry);
+}
+
+/*!
+ * \brief Remove an item from the front of the queue, and free it.
+ * \param pvt a mbl_pvt structure
+ */
+static void msg_queue_free_and_pop(struct mbl_pvt *pvt)
+{
+       struct msg_queue_entry *msg;
+       if ((msg = msg_queue_pop(pvt))) {
+               if (msg->data)
+                       ast_free(msg->data);
+               ast_free(msg);
+       }
+}
+
+/*!
+ * \brief Remove all itmes from the queue and free them.
+ * \param pvt a mbl_pvt structure
+ */
+static void msg_queue_flush(struct mbl_pvt *pvt)
+{
+       struct msg_queue_entry *msg;
+       while ((msg = msg_queue_head(pvt)))
+               msg_queue_free_and_pop(pvt);
+}
+
+/*!
+ * \brief Get the head of a queue.
+ * \param pvt a mbl_pvt structure
+ * \return a pointer to the head of the given msg queue
+ */
+static struct msg_queue_entry *msg_queue_head(struct mbl_pvt *pvt)
+{
+       return AST_LIST_FIRST(&pvt->msg_queue);
+}
+
+
+
+/*
+
+       sdp helpers
+
+*/
+
+static int sdp_search(char *addr, int profile)
+{
+
+       sdp_session_t *session = 0;
+       bdaddr_t bdaddr;
+       uuid_t svc_uuid;
+       uint32_t range = 0x0000ffff;
+       sdp_list_t *response_list, *search_list, *attrid_list;
+       int status, port;
+       sdp_list_t *proto_list;
+       sdp_record_t *sdprec;
+
+       str2ba(addr, &bdaddr);
+       port = 0;
+       session = sdp_connect(BDADDR_ANY, &bdaddr, SDP_RETRY_IF_BUSY);
+       if (!session) {
+               ast_debug(1, "sdp_connect() failed on device %s.\n", addr);
+               return 0;
+       }
+
+       sdp_uuid32_create(&svc_uuid, profile);
+       search_list = sdp_list_append(0, &svc_uuid);
+       attrid_list = sdp_list_append(0, &range);
+       response_list = 0x00;
+       status = sdp_service_search_attr_req(session, search_list, SDP_ATTR_REQ_RANGE, attrid_list, &response_list);
+       if (status == 0) {
+               if (response_list) {
+                       sdprec = (sdp_record_t *) response_list->data;
+                       proto_list = 0x00;
+                       if (sdp_get_access_protos(sdprec, &proto_list) == 0) {
+                               port = sdp_get_proto_port(proto_list, RFCOMM_UUID);
+                               sdp_list_free(proto_list, 0);
+                       }
+                       sdp_record_free(sdprec);
+                       sdp_list_free(response_list, 0);
+               } else
+                       ast_debug(1, "No responses returned for device %s.\n", addr);
+       } else
+               ast_debug(1, "sdp_service_search_attr_req() failed on device %s.\n", addr);
+
+       sdp_list_free(search_list, 0);
+       sdp_list_free(attrid_list, 0);
+       sdp_close(session);
+
+       return port;
+
+}
+
+static sdp_session_t *sdp_register(void)
+{
+
+       uint32_t service_uuid_int[] = {0, 0, 0, GENERIC_AUDIO_SVCLASS_ID};
+       uint8_t rfcomm_channel = 1;
+       const char *service_name = "Asterisk PABX";
+       const char *service_dsc = "Asterisk PABX";
+       const char *service_prov = "Asterisk";
+
+       uuid_t root_uuid, l2cap_uuid, rfcomm_uuid, svc_uuid, svc_class1_uuid, svc_class2_uuid;
+       sdp_list_t  *l2cap_list = 0, *rfcomm_list = 0, *root_list = 0, *proto_list = 0, *access_proto_list = 0, *svc_uuid_list = 0;
+       sdp_data_t *channel = 0;
+
+       int err = 0;
+       sdp_session_t *session = 0;
+
+       sdp_record_t *record = sdp_record_alloc();
+
+       sdp_uuid128_create(&svc_uuid, &service_uuid_int);
+       sdp_set_service_id(record, svc_uuid);
+
+       sdp_uuid32_create(&svc_class1_uuid, GENERIC_AUDIO_SVCLASS_ID);
+       sdp_uuid32_create(&svc_class2_uuid, HEADSET_PROFILE_ID);
+
+       svc_uuid_list = sdp_list_append(0, &svc_class1_uuid);
+       svc_uuid_list = sdp_list_append(svc_uuid_list, &svc_class2_uuid);
+       sdp_set_service_classes(record, svc_uuid_list);
+
+       sdp_uuid16_create(&root_uuid, PUBLIC_BROWSE_GROUP);
+       root_list = sdp_list_append(0, &root_uuid);
+       sdp_set_browse_groups( record, root_list );
+
+       sdp_uuid16_create(&l2cap_uuid, L2CAP_UUID);
+       l2cap_list = sdp_list_append(0, &l2cap_uuid);
+       proto_list = sdp_list_append(0, l2cap_list);
+
+       sdp_uuid16_create(&rfcomm_uuid, RFCOMM_UUID);
+       channel = sdp_data_alloc(SDP_UINT8, &rfcomm_channel);
+       rfcomm_list = sdp_list_append(0, &rfcomm_uuid);
+       sdp_list_append(rfcomm_list, channel);
+       sdp_list_append(proto_list, rfcomm_list);
+
+       access_proto_list = sdp_list_append(0, proto_list);
+       sdp_set_access_protos(record, access_proto_list);
+
+       sdp_set_info_attr(record, service_name, service_prov, service_dsc);
+
+       if (!(session = sdp_connect(BDADDR_ANY, BDADDR_LOCAL, SDP_RETRY_IF_BUSY)))
+               ast_log(LOG_WARNING, "Failed to connect sdp and create session.\n");
+       else
+               err = sdp_record_register(session, record, 0);
+
+       sdp_data_free(channel);
+       sdp_list_free(rfcomm_list, 0);
+       sdp_list_free(root_list, 0);
+       sdp_list_free(access_proto_list, 0);
+       sdp_list_free(svc_uuid_list, 0);
+
+       return session;
+
+}
+
+/*
+
+       Thread routines
+
+*/
+
+/*!
+ * \brief Handle the BRSF response.
+ * \param pvt a mbl_pvt structure
+ * \param buf a null terminated buffer containing an AT message
+ * \retval 0 success
+ * \retval -1 error
+ */
+static int handle_response_brsf(struct mbl_pvt *pvt, char *buf)
+{
+       struct msg_queue_entry *entry;
+       if ((entry = msg_queue_head(pvt)) && entry->expected == AT_BRSF) {
+               if (hfp_parse_brsf(pvt->hfp, buf)) {
+                       ast_debug(1, "[%s] error parsing BRSF\n", pvt->id);
+                       goto e_return;
+               }
+
+               if (msg_queue_push(pvt, AT_OK, AT_BRSF)) {
+                       ast_debug(1, "[%s] error handling BRSF\n", pvt->id);
+                       goto e_return;
+               }
+
+               msg_queue_free_and_pop(pvt);
+       } else if (entry) {
+               ast_debug(1, "[%s] recieved unexpected AT message 'BRSF' when expecting %s, ignoring\n", pvt->id, at_msg2str(entry->expected));
+       } else {
+               ast_debug(1, "[%s] recieved unexpected AT message 'BRSF'\n", pvt->id);
+       }
+
+       return 0;
+
+e_return:
+       msg_queue_free_and_pop(pvt);
+       return -1;
+}
+
+/*!
+ * \brief Handle the CIND response.
+ * \param pvt a mbl_pvt structure
+ * \param buf a null terminated buffer containing an AT message
+ * \retval 0 success
+ * \retval -1 error
+ */
+static int handle_response_cind(struct mbl_pvt *pvt, char *buf)
+{
+       struct msg_queue_entry *entry;
+       if ((entry = msg_queue_head(pvt)) && entry->expected == AT_CIND) {
+               switch (entry->response_to) {
+               case AT_CIND_TEST:
+                       if (hfp_parse_cind_test(pvt->hfp, buf) || msg_queue_push(pvt, AT_OK, AT_CIND_TEST)) {
+                               ast_debug(1, "[%s] error performing CIND test\n", pvt->id);
+                               goto e_return;
+                       }
+                       break;
+               case AT_CIND:
+                       if (hfp_parse_cind(pvt->hfp, buf) || msg_queue_push(pvt, AT_OK, AT_CIND)) {
+                               ast_debug(1, "[%s] error getting CIND state\n", pvt->id);
+                               goto e_return;
+                       }
+                       break;
+               default:
+                       ast_debug(1, "[%s] error getting CIND state\n", pvt->id);
+                       goto e_return;
+               }
+               msg_queue_free_and_pop(pvt);
+       } else if (entry) {
+               ast_debug(1, "[%s] recieved unexpected AT message 'CIND' when expecting %s, ignoring\n", pvt->id, at_msg2str(entry->expected));
+       } else {
+               ast_debug(1, "[%s] recieved unexpected AT message 'CIND'\n", pvt->id);
+       }
+
+       return 0;
+
+e_return:
+       msg_queue_free_and_pop(pvt);
+       return -1;
+}
+
+/*!
+ * \brief Handle OK AT messages.
+ * \param pvt a mbl_pvt structure
+ * \param buf a null terminated buffer containing an AT message
+ * \retval 0 success
+ * \retval -1 error
+ */
+static int handle_response_ok(struct mbl_pvt *pvt, char *buf)
+{
+       struct msg_queue_entry *entry;
+       if ((entry = msg_queue_head(pvt)) && entry->expected == AT_OK) {
+               switch (entry->response_to) {
+
+               /* initilization stuff */
+               case AT_BRSF:
+                       ast_debug(1, "[%s] BSRF sent successfully\n", pvt->id);
+
+                       /* If this is a blackberry do CMER now, otherwise
+                        * continue with CIND as normal. */
+                       if (pvt->blackberry) {
+                               if (hfp_send_cmer(pvt->hfp, 1) || msg_queue_push(pvt, AT_OK, AT_CMER)) {
+                                       ast_debug(1, "[%s] error sending CMER\n", pvt->id);
+                                       goto e_return;
+                               }
+                       } else {
+                               if (hfp_send_cind_test(pvt->hfp) || msg_queue_push(pvt, AT_CIND, AT_CIND_TEST)) {
+                                       ast_debug(1, "[%s] error sending CIND test\n", pvt->id);
+                                       goto e_return;
+                               }
+                       }
+                       break;
+               case AT_CIND_TEST:
+                       ast_debug(1, "[%s] CIND test sent successfully\n", pvt->id);
+
+                       ast_debug(2, "[%s] call: %d\n", pvt->id, pvt->hfp->cind_map.call);
+                       ast_debug(2, "[%s] callsetup: %d\n", pvt->id, pvt->hfp->cind_map.callsetup);
+
+                       if (hfp_send_cind(pvt->hfp) || msg_queue_push(pvt, AT_CIND, AT_CIND)) {
+                               ast_debug(1, "[%s] error requesting CIND state\n", pvt->id);
+                               goto e_return;
+                       }
+                       break;
+               case AT_CIND:
+                       ast_debug(1, "[%s] CIND sent successfully\n", pvt->id);
+
+                       /* check if a call is active */
+                       if (pvt->hfp->cind_state[pvt->hfp->cind_map.call]) {
+                               ast_verb(3, "Bluetooth Device %s has a call in progress - delaying connection.\n", pvt->id);
+                               goto e_return;
+                       }
+
+                       /* If this is NOT a blackberry proceed with CMER,
+                        * otherwise send CLIP. */
+                       if (!pvt->blackberry) {
+                               if (hfp_send_cmer(pvt->hfp, 1) || msg_queue_push(pvt, AT_OK, AT_CMER)) {
+                                       ast_debug(1, "[%s] error sending CMER\n", pvt->id);
+                                       goto e_return;
+                               }
+                       } else {
+                               if (hfp_send_clip(pvt->hfp, 1) || msg_queue_push(pvt, AT_OK, AT_CLIP)) {
+                                       ast_debug(1, "[%s] error enabling calling line notification\n", pvt->id);
+                                       goto e_return;
+                               }
+                       }
+                       break;
+               case AT_CMER:
+                       ast_debug(1, "[%s] CMER sent successfully\n", pvt->id);
+
+                       /* If this is a blackberry proceed with the CIND test,
+                        * otherwise send CLIP. */
+                       if (pvt->blackberry) {
+                               if (hfp_send_cind_test(pvt->hfp) || msg_queue_push(pvt, AT_CIND, AT_CIND_TEST)) {
+                                       ast_debug(1, "[%s] error sending CIND test\n", pvt->id);
+                                       goto e_return;
+                               }
+                       } else {
+                               if (hfp_send_clip(pvt->hfp, 1) || msg_queue_push(pvt, AT_OK, AT_CLIP)) {
+                                       ast_debug(1, "[%s] error enabling calling line notification\n", pvt->id);
+                                       goto e_return;
+                               }
+                       }
+                       break;
+               case AT_CLIP:
+                       ast_debug(1, "[%s] caling line indication enabled\n", pvt->id);
+                       if (hfp_send_vgs(pvt->hfp, 15) || msg_queue_push(pvt, AT_OK, AT_VGS)) {
+                               ast_debug(1, "[%s] error synchronizing gain settings\n", pvt->id);
+                               goto e_return;
+                       }
+
+                       pvt->timeout = -1;
+                       pvt->hfp->initialized = 1;
+                       ast_verb(3, "Bluetooth Device %s initialized and ready.\n", pvt->id);
+
+                       break;
+               case AT_VGS:
+                       ast_debug(1, "[%s] volume level synchronization successful\n", pvt->id);
+
+                       /* set the SMS operating mode to text mode */
+                       if (hfp_send_cmgf(pvt->hfp, 1) || msg_queue_push(pvt, AT_OK, AT_CMGF)) {
+                               ast_debug(1, "[%s] error setting CMGF\n", pvt->id);
+                               goto e_return;
+                       }
+                       break;
+               case AT_CMGF:
+                       ast_debug(1, "[%s] sms text mode enabled\n", pvt->id);
+                       /* turn on SMS new message indication */
+                       if (hfp_send_cnmi(pvt->hfp) || msg_queue_push(pvt, AT_OK, AT_CNMI)) {
+                               ast_debug(1, "[%s] error setting CNMI\n", pvt->id);
+                               goto e_return;
+                       }
+                       break;
+               case AT_CNMI:
+                       ast_debug(1, "[%s] sms new message indication enabled\n", pvt->id);
+                       pvt->has_sms = 1;
+                       break;
+               /* end initilization stuff */
+
+               case AT_A:
+                       ast_debug(1, "[%s] answer sent successfully\n", pvt->id);
+                       pvt->needchup = 1;
+                       break;
+               case AT_D:
+                       ast_debug(1, "[%s] dial sent successfully\n", pvt->id);
+                       pvt->needchup = 1;
+                       pvt->outgoing = 1;
+                       mbl_queue_control(pvt, AST_CONTROL_PROGRESS);
+                       break;
+               case AT_CHUP:
+                       ast_debug(1, "[%s] successful hangup\n", pvt->id);
+                       break;
+               case AT_CMGR:
+                       ast_debug(1, "[%s] successfully read sms message\n", pvt->id);
+                       pvt->incoming_sms = 0;
+                       break;
+               case AT_CMGS:
+                       ast_debug(1, "[%s] successfully sent sms message\n", pvt->id);
+                       pvt->outgoing_sms = 0;
+                       break;
+               case AT_VTS:
+                       ast_debug(1, "[%s] digit sent successfully\n", pvt->id);
+                       break;
+               case AT_UNKNOWN:
+               default:
+                       ast_debug(1, "[%s] recieved OK for unhandled request: %s\n", pvt->id, at_msg2str(entry->response_to));
+                       break;
+               }
+               msg_queue_free_and_pop(pvt);
+       } else if (entry) {
+               ast_debug(1, "[%s] recieved AT message 'OK' when expecting %s, ignoring\n", pvt->id, at_msg2str(entry->expected));
+       } else {
+               ast_debug(1, "[%s] recieved unexpected AT message 'OK'\n", pvt->id);
+       }
+       return 0;
+
+e_return:
+       msg_queue_free_and_pop(pvt);
+       return -1;
+}
+
+/*!
+ * \brief Handle ERROR AT messages.
+ * \param pvt a mbl_pvt structure
+ * \param buf a null terminated buffer containing an AT message
+ * \retval 0 success
+ * \retval -1 error
+ */
+static int handle_response_error(struct mbl_pvt *pvt, char *buf)
+{
+       struct msg_queue_entry *entry;
+       if ((entry = msg_queue_head(pvt))
+                       && (entry->expected == AT_OK
+                       || entry->expected == AT_ERROR
+                       || entry->expected == AT_CMS_ERROR
+                       || entry->expected == AT_CMGR
+                       || entry->expected == AT_SMS_PROMPT)) {
+               switch (entry->response_to) {
+
+               /* initilization stuff */
+               case AT_BRSF:
+                       ast_debug(1, "[%s] error reading BSRF\n", pvt->id);
+                       goto e_return;
+               case AT_CIND_TEST:
+                       ast_debug(1, "[%s] error during CIND test\n", pvt->id);
+                       goto e_return;
+               case AT_CIND:
+                       ast_debug(1, "[%s] error requesting CIND state\n", pvt->id);
+                       goto e_return;
+               case AT_CMER:
+                       ast_debug(1, "[%s] error during CMER request\n", pvt->id);
+                       goto e_return;
+               case AT_CLIP:
+                       ast_debug(1, "[%s] error enabling calling line indication\n", pvt->id);
+                       goto e_return;
+               case AT_VGS:
+                       ast_debug(1, "[%s] volume level synchronization failed\n", pvt->id);
+
+                       /* this is not a fatal error, let's continue with initilization */
+
+                       /* set the SMS operating mode to text mode */
+                       if (hfp_send_cmgf(pvt->hfp, 1) || msg_queue_push(pvt, AT_OK, AT_CMGF)) {
+                               ast_debug(1, "[%s] error setting CMGF\n", pvt->id);
+                               goto e_return;
+                       }
+                       break;
+               case AT_CMGF:
+                       ast_debug(1, "[%s] error setting CMGF\n", pvt->id);
+                       ast_debug(1, "[%s] no SMS support\n", pvt->id);
+                       break;
+               case AT_CNMI:
+                       ast_debug(1, "[%s] error setting CNMI\n", pvt->id);
+                       ast_debug(1, "[%s] no SMS support\n", pvt->id);
+                       break;
+               /* end initilization stuff */
+
+               case AT_A:
+                       ast_debug(1, "[%s] answer failed\n", pvt->id);
+                       mbl_queue_hangup(pvt);
+                       break;
+               case AT_D:
+                       ast_debug(1, "[%s] dial failed\n", pvt->id);
+                       pvt->needchup = 0;
+                       mbl_queue_control(pvt, AST_CONTROL_CONGESTION);
+                       break;
+               case AT_CHUP:
+                       ast_debug(1, "[%s] error sending hangup, disconnecting\n", pvt->id);
+                       goto e_return;
+               case AT_CMGR:
+                       ast_debug(1, "[%s] error reading sms message\n", pvt->id);
+                       pvt->incoming_sms = 0;
+                       break;
+               case AT_CMGS:
+                       ast_debug(1, "[%s] error sending sms message\n", pvt->id);
+                       pvt->outgoing_sms = 0;
+                       break;
+               case AT_VTS:
+                       ast_debug(1, "[%s] error sending digit\n", pvt->id);
+                       break;
+               case AT_UNKNOWN:
+               default:
+                       ast_debug(1, "[%s] recieved ERROR for unhandled request: %s\n", pvt->id, at_msg2str(entry->response_to));
+                       break;
+               }
+               msg_queue_free_and_pop(pvt);
+       } else if (entry) {
+               ast_debug(1, "[%s] recieved AT message 'ERROR' when expecting %s, ignoring\n", pvt->id, at_msg2str(entry->expected));
+       } else {
+               ast_debug(1, "[%s] recieved unexpected AT message 'ERROR'\n", pvt->id);
+       }
+
+       return 0;
+
+e_return:
+       msg_queue_free_and_pop(pvt);
+       return -1;
+}
+
+/*!
+ * \brief Handle AT+CIEV messages.
+ * \param pvt a mbl_pvt structure
+ * \param buf a null terminated buffer containing an AT message
+ * \retval 0 success
+ * \retval -1 error
+ */
+static int handle_response_ciev(struct mbl_pvt *pvt, char *buf)
+{
+       int i;
+       switch (hfp_parse_ciev(pvt->hfp, buf, &i)) {
+       case HFP_CIND_CALL:
+               switch (i) {
+               case HFP_CIND_CALL_NONE:
+                       ast_debug(1, "[%s] line disconnected\n", pvt->id);
+                       if (pvt->owner) {
+                               ast_debug(1, "[%s] hanging up owner\n", pvt->id);
+                               if (mbl_queue_hangup(pvt)) {
+                                       ast_log(LOG_ERROR, "[%s] error queueing hangup, disconnectiong...\n", pvt->id);
+                                       return -1;
+                               }
+                       }
+                       pvt->needchup = 0;
+                       pvt->needcallerid = 0;
+                       pvt->incoming = 0;
+                       pvt->outgoing = 0;
+                       break;
+               case HFP_CIND_CALL_ACTIVE:
+                       if (pvt->outgoing) {
+                               ast_debug(1, "[%s] remote end answered\n", pvt->id);
+                               mbl_queue_control(pvt, AST_CONTROL_ANSWER);
+                       } else if (pvt->incoming && pvt->answered) {
+                               ast_setstate(pvt->owner, AST_STATE_UP);
+                       } else if (pvt->incoming) {
+                               /* user answered from handset, disconnecting */
+                               ast_verb(3, "[%s] user answered bluetooth device from handset, disconnecting\n", pvt->id);
+                               mbl_queue_hangup(pvt);
+                               return -1;
+                       }
+                       break;
+               }
+               break;
+
+       case HFP_CIND_CALLSETUP:
+               switch (i) {
+               case HFP_CIND_CALLSETUP_NONE:
+                       if (pvt->hfp->cind_state[pvt->hfp->cind_map.call] != HFP_CIND_CALL_ACTIVE) {
+                               if (pvt->owner) {
+                                       if (mbl_queue_hangup(pvt)) {
+                                               ast_log(LOG_ERROR, "[%s] error queueing hangup, disconnectiong...\n", pvt->id);
+                                               return -1;
+                                       }
+                               }
+                               pvt->needchup = 0;
+                               pvt->needcallerid = 0;
+                               pvt->incoming = 0;
+                               pvt->outgoing = 0;
+                       }
+                       break;
+               case HFP_CIND_CALLSETUP_INCOMING:
+                       ast_debug(1, "[%s] incoming call, waiting for caller id\n", pvt->id);
+                       pvt->needcallerid = 1;
+                       pvt->incoming = 1;
+                       break;
+               case HFP_CIND_CALLSETUP_OUTGOING:
+                       if (pvt->outgoing) {
+                               ast_debug(1, "[%s] outgoing call\n", pvt->id);
+                       } else {
+                               ast_verb(3, "[%s] user dialed from handset, disconnecting\n", pvt->id);
+                               return -1;
+                       }
+                       break;
+               case HFP_CIND_CALLSETUP_ALERTING:
+                       if (pvt->outgoing) {
+                               ast_debug(1, "[%s] remote alerting\n", pvt->id);
+                               mbl_queue_control(pvt, AST_CONTROL_RINGING);
+                       }
+                       break;
+               }
+               break;
+       case HFP_CIND_NONE:
+               ast_debug(1, "[%s] error parsing CIND: %s\n", pvt->id, buf);
+               break;
+       }
+       return 0;
+}
+
+/*!
+ * \brief Handle AT+CLIP messages.
+ * \param pvt a mbl_pvt structure
+ * \param buf a null terminated buffer containing an AT message
+ * \retval 0 success
+ * \retval -1 error
+ */
+static int handle_response_clip(struct mbl_pvt *pvt, char *buf)
+{
+       char *clip;
+       struct msg_queue_entry *msg;
+       struct ast_channel *chan;
+
+       if ((msg = msg_queue_head(pvt)) && msg->expected == AT_CLIP) {
+               msg_queue_free_and_pop(pvt);
+
+               pvt->needcallerid = 0;
+               if (!(clip = hfp_parse_clip(pvt->hfp, buf))) {
+                       ast_debug(1, "[%s] error parsing CLIP: %s\n", pvt->id, buf);
+               }
+
+               if (!(chan = mbl_new(AST_STATE_RING, pvt, clip, NULL))) {
+                       ast_log(LOG_ERROR, "[%s] unable to allocate channel for incoming call\n", pvt->id);
+                       hfp_send_chup(pvt->hfp);
+                       msg_queue_push(pvt, AT_OK, AT_CHUP);
+                       return -1;
+               }
+
+               /* from this point on, we need to send a chup in the event of a
+                * hangup */
+               pvt->needchup = 1;
+
+               if (ast_pbx_start(chan)) {
+                       ast_log(LOG_ERROR, "[%s] unable to start pbx on incoming call\n", pvt->id);
+                       mbl_ast_hangup(pvt);
+                       return -1;
+               }
+       }
+
+       return 0;
+}
+
+/*!
+ * \brief Handle RING messages.
+ * \param pvt a mbl_pvt structure
+ * \param buf a null terminated buffer containing an AT message
+ * \retval 0 success
+ * \retval -1 error
+ */
+static int handle_response_ring(struct mbl_pvt *pvt, char *buf)
+{
+       if (pvt->needcallerid) {
+               ast_debug(1, "[%s] got ring while waiting for caller id\n", pvt->id);
+               return msg_queue_push(pvt, AT_CLIP, AT_UNKNOWN);
+       } else {
+               return 0;
+       }
+}
+
+/*!
+ * \brief Handle AT+CMTI messages.
+ * \param pvt a mbl_pvt structure
+ * \param buf a null terminated buffer containing an AT message
+ * \retval 0 success
+ * \retval -1 error
+ */
+static int handle_response_cmti(struct mbl_pvt *pvt, char *buf)
+{
+       int index = hfp_parse_cmti(pvt->hfp, buf);
+       if (index > 0) {
+               ast_debug(1, "[%s] incoming sms message\n", pvt->id);
+
+               if (hfp_send_cmgr(pvt->hfp, index)
+                               || msg_queue_push(pvt, AT_CMGR, AT_CMGR)) {
+                       ast_debug(1, "[%s] error sending CMGR to retrieve SMS message\n", pvt->id);
+                       return -1;
+               }
+
+               pvt->incoming_sms = 1;
+               return 0;
+       } else {
+               ast_debug(1, "[%s] error parsing incoming sms message alert, disconnecting\n", pvt->id);
+               return -1;
+       }
+}
+
+/*!
+ * \brief Handle AT+CMGR messages.
+ * \param pvt a mbl_pvt structure
+ * \param buf a null terminated buffer containing an AT message
+ * \retval 0 success
+ * \retval -1 error
+ */
+static int handle_response_cmgr(struct mbl_pvt *pvt, char *buf)
+{
+       char *from_number = NULL, *text = NULL;
+       struct ast_channel *chan;
+       struct msg_queue_entry *msg;
+
+       if ((msg = msg_queue_head(pvt)) && msg->expected == AT_CMGR) {
+               msg_queue_free_and_pop(pvt);
+
+               if (hfp_parse_cmgr(pvt->hfp, buf, &from_number, &text)
+                               || msg_queue_push(pvt, AT_OK, AT_CMGR)) {
+
+                       ast_debug(1, "[%s] error parsing sms message, disconnecting\n", pvt->id);
+                       return -1;
+               }
+
+               /* XXX this channel probably does not need to be associated with this pvt */
+               if (!(chan = mbl_new(AST_STATE_DOWN, pvt, NULL, NULL))) {
+                       ast_debug(1, "[%s] error creating sms message channel, disconnecting\n", pvt->id);
+                       return -1;
+               }
+
+               strcpy(chan->exten, "sms");
+               pbx_builtin_setvar_helper(chan, "SMSSRC", from_number);
+               pbx_builtin_setvar_helper(chan, "SMSTXT", text);
+
+               if (ast_pbx_start(chan)) {
+                       ast_log(LOG_ERROR, "[%s] unable to start pbx on incoming sms\n", pvt->id);
+                       mbl_ast_hangup(pvt);
+               }
+       } else {
+               ast_debug(1, "[%s] got unexpected +CMGR message, ignoring\n", pvt->id);
+       }
+
+       return 0;
+}
+
+/*!
+ * \brief Send an SMS message from the queue.
+ * \param pvt a mbl_pvt structure
+ * \param buf a null terminated buffer containing an AT message
+ * \retval 0 success
+ * \retval -1 error
+ */
+static int handle_sms_prompt(struct mbl_pvt *pvt, char *buf)
+{
+       struct msg_queue_entry *msg;
+       if (!(msg = msg_queue_head(pvt))) {
+               ast_debug(1, "[%s] error, got sms prompt with no pending sms messages\n", pvt->id);
+               return 0;
+       }
+
+       if (msg->expected != AT_SMS_PROMPT) {
+               ast_debug(1, "[%s] error, got sms prompt but no pending sms messages\n", pvt->id);
+               return 0;
+       }
+
+       if (hfp_send_sms_text(pvt->hfp, msg->data)
+                       || msg_queue_push(pvt, AT_OK, AT_CMGS)) {
+               msg_queue_free_and_pop(pvt);
+               ast_debug(1, "[%s] error sending sms message\n", pvt->id);
+               return 0;
+       }
+
+       msg_queue_free_and_pop(pvt);
+       return 0;
+}
+
+
+static void *do_monitor_phone(void *data)
+{
+       struct mbl_pvt *pvt = (struct mbl_pvt *)data;
+       struct hfp_pvt *hfp = pvt->hfp;
+       char buf[256];
+       int t;
+       at_message_t at_msg;
+       struct msg_queue_entry *entry;
+
+       /* Note: At one point the initilization procedure was neatly contained
+        * in the hfp_init() function, but that initilization method did not
+        * work with non standard devices.  As a result, the initilization
+        * procedure is not spread throughout the event handling loop.
+        */
+
+       /* start initilization with the BRSF request */
+       ast_mutex_lock(&pvt->lock);
+       pvt->timeout = 10000;
+       if (hfp_send_brsf(hfp, &hfp_our_brsf)  || msg_queue_push(pvt, AT_BRSF, AT_BRSF)) {
+               ast_debug(1, "[%s] error sending BRSF\n", hfp->owner->id);
+               goto e_cleanup;
+       }
+       ast_mutex_unlock(&pvt->lock);
+
+       while (!check_unloading()) {
+               ast_mutex_lock(&pvt->lock);
+               t = pvt->timeout;
+               ast_mutex_unlock(&pvt->lock);
+
+               if (!rfcomm_wait(pvt->rfcomm_socket, &t)) {
+                       ast_debug(1, "[%s] timeout waiting for rfcomm data, disconnecting\n", pvt->id);
+                       ast_mutex_lock(&pvt->lock);
+                       if (!hfp->initialized) {
+                               if ((entry = msg_queue_head(pvt))) {
+                                       switch (entry->response_to) {
+                                       case AT_CIND_TEST:
+                                               if (pvt->blackberry)
+                                                       ast_debug(1, "[%s] timeout during CIND test\n", hfp->owner->id);
+                                               else
+                                                       ast_debug(1, "[%s] timeout during CIND test, try setting 'blackberry=yes'\n", hfp->owner->id);
+                                               break;
+                                       case AT_CMER:
+                                               if (pvt->blackberry)
+                                                       ast_debug(1, "[%s] timeout after sending CMER, try setting 'blackberry=no'\n", hfp->owner->id);
+                                               else
+                                                       ast_debug(1, "[%s] timeout after sending CMER\n", hfp->owner->id);
+                                               break;
+                                       default:
+                                               ast_debug(1, "[%s] timeout while waiting for %s in response to %s\n", pvt->id, at_msg2str(entry->expected), at_msg2str(entry->response_to));
+                                               break;
+                                       }
+                               }
+                       }
+                       ast_mutex_unlock(&pvt->lock);
+                       goto e_cleanup;
+               }
+
+               if ((at_msg = at_read_full(hfp->rsock, buf, sizeof(buf))) < 0) {
+                       /* XXX gnu specific strerror_r is assummed here, this
+                        * is not really safe.  See the strerror(3) man page
+                        * for more info. */
+                       ast_debug(1, "[%s] error reading from device: %s (%d)\n", pvt->id, strerror_r(errno, buf, sizeof(buf)), errno);
+                       break;
+               }
+
+               ast_debug(1, "[%s] %s\n", pvt->id, buf);
+
+               switch (at_msg) {
+               case AT_BRSF:
+                       ast_mutex_lock(&pvt->lock);
+                       if (handle_response_brsf(pvt, buf)) {
+                               ast_mutex_unlock(&pvt->lock);
+                               goto e_cleanup;
+                       }
+                       ast_mutex_unlock(&pvt->lock);
+                       break;
+               case AT_CIND:
+                       ast_mutex_lock(&pvt->lock);
+                       if (handle_response_cind(pvt, buf)) {
+                               ast_mutex_unlock(&pvt->lock);
+                               goto e_cleanup;
+                       }
+                       ast_mutex_unlock(&pvt->lock);
+                       break;
+               case AT_OK:
+                       ast_mutex_lock(&pvt->lock);
+                       if (handle_response_ok(pvt, buf)) {
+                               ast_mutex_unlock(&pvt->lock);
+                               goto e_cleanup;
+                       }
+                       ast_mutex_unlock(&pvt->lock);
+                       break;
+               case AT_CMS_ERROR:
+               case AT_ERROR:
+                       ast_mutex_lock(&pvt->lock);
+                       if (handle_response_error(pvt, buf)) {
+                               ast_mutex_unlock(&pvt->lock);
+                               goto e_cleanup;
+                       }
+                       ast_mutex_unlock(&pvt->lock);
+                       break;
+               case AT_RING:
+                       ast_mutex_lock(&pvt->lock);
+                       if (handle_response_ring(pvt, buf)) {
+                               ast_mutex_unlock(&pvt->lock);
+                               goto e_cleanup;
+                       }
+                       ast_mutex_unlock(&pvt->lock);
+                       break;
+               case AT_CIEV:
+                       ast_mutex_lock(&pvt->lock);
+                       if (handle_response_ciev(pvt, buf)) {
+                               ast_mutex_unlock(&pvt->lock);
+                               goto e_cleanup;
+                       }
+                       ast_mutex_unlock(&pvt->lock);
+                       break;
+               case AT_CLIP:
+                       ast_mutex_lock(&pvt->lock);
+                       if (handle_response_clip(pvt, buf)) {
+                               ast_mutex_unlock(&pvt->lock);
+                               goto e_cleanup;
+                       }
+                       ast_mutex_unlock(&pvt->lock);
+                       break;
+               case AT_CMTI:
+                       ast_mutex_lock(&pvt->lock);
+                       if (handle_response_cmti(pvt, buf)) {
+                               ast_mutex_unlock(&pvt->lock);
+                               goto e_cleanup;
+                       }
+                       ast_mutex_unlock(&pvt->lock);
+                       break;
+               case AT_CMGR:
+                       ast_mutex_lock(&pvt->lock);
+                       if (handle_response_cmgr(pvt, buf)) {
+                               ast_mutex_unlock(&pvt->lock);
+                               goto e_cleanup;
+                       }
+                       ast_mutex_unlock(&pvt->lock);
+                       break;
+               case AT_SMS_PROMPT:
+                       ast_mutex_lock(&pvt->lock);
+                       if (handle_sms_prompt(pvt, buf)) {
+                               ast_mutex_unlock(&pvt->lock);
+                               goto e_cleanup;
+                       }
+                       ast_mutex_unlock(&pvt->lock);
+                       break;
+               case AT_UNKNOWN:
+                       ast_debug(1, "[%s] ignoring unknown message: %s\n", pvt->id, buf);
+                       break;
+               case AT_PARSE_ERROR:
+                       ast_debug(1, "[%s] error parsing message\n", pvt->id);
+                       goto e_cleanup;
+               case AT_READ_ERROR:
+                       ast_debug(1, "[%s] error reading from device: %s (%d)\n", pvt->id, strerror_r(errno, buf, sizeof(buf)), errno);
+                       goto e_cleanup;
+               default:
+                       break;
+               }
+       }
+
+e_cleanup:
+
+       if (!hfp->initialized)
+               ast_verb(3, "Error initializing Bluetooth device %s.\n", pvt->id);
+
+       ast_mutex_lock(&pvt->lock);
+       if (pvt->owner) {
+               ast_debug(1, "[%s] device disconnected, hanging up owner\n", pvt->id);
+               pvt->needchup = 0;
+               mbl_queue_hangup(pvt);
+       }
+
+       close(pvt->rfcomm_socket);
+       close(pvt->sco_socket);
+       pvt->sco_socket = -1;
+
+       msg_queue_flush(pvt);
+
+       pvt->connected = 0;
+       hfp->initialized = 0;
+
+       pvt->adapter->inuse = 0;
+       ast_mutex_unlock(&pvt->lock);
+
+       ast_verb(3, "Bluetooth Device %s has disconnected.\n", pvt->id);
+       manager_event(EVENT_FLAG_SYSTEM, "MobileStatus", "Status: Disconnect\r\nDevice: %s\r\n", pvt->id);
+
+       return NULL;
+}
+
+static int headset_send_ring(const void *data)
+{
+       struct mbl_pvt *pvt = (struct mbl_pvt *) data;
+       ast_mutex_lock(&pvt->lock);
+       if (!pvt->needring) {
+               ast_mutex_unlock(&pvt->lock);
+               return 0;
+       }
+       ast_mutex_unlock(&pvt->lock);
+
+       if (hsp_send_ring(pvt->rfcomm_socket)) {
+               ast_debug(1, "[%s] error sending RING\n", pvt->id);
+               return 0;
+       }
+       return 1;
+}
+
+static void *do_monitor_headset(void *data)
+{
+
+       struct mbl_pvt *pvt = (struct mbl_pvt *)data;
+       char buf[256];
+       int t;
+       at_message_t at_msg;
+       struct ast_channel *chan = NULL;
+
+       ast_verb(3, "Bluetooth Device %s initialised and ready.\n", pvt->id);
+
+       while (!check_unloading()) {
+
+               t = ast_sched_wait(pvt->sched);
+               if (t == -1) {
+                       t = 6000;
+               }
+
+               ast_sched_runq(pvt->sched);
+
+               if (rfcomm_wait(pvt->rfcomm_socket, &t) == 0)
+                       continue;
+
+               if ((at_msg = at_read_full(pvt->rfcomm_socket, buf, sizeof(buf))) < 0) {
+                       if (strerror_r(errno, buf, sizeof(buf)))
+                               ast_debug(1, "[%s] error reading from device\n", pvt->id);
+                       else
+                               ast_debug(1, "[%s] error reading from device: %s (%d)\n", pvt->id, buf, errno);
+
+                       goto e_cleanup;
+               }
+               ast_debug(1, "[%s] %s\n", pvt->id, buf);
+
+               switch (at_msg) {
+               case AT_VGS:
+               case AT_VGM:
+                       /* XXX volume change requested, we will just
+                        * pretend to do something with it */
+                       if (hsp_send_ok(pvt->rfcomm_socket)) {
+                               ast_debug(1, "[%s] error sending AT message 'OK'\n", pvt->id);
+                               goto e_cleanup;
+                       }
+                       break;
+               case AT_CKPD:
+                       ast_mutex_lock(&pvt->lock);
+                       if (pvt->outgoing) {
+                               pvt->needring = 0;
+                               hsp_send_ok(pvt->rfcomm_socket);
+                               if (pvt->answered) {
+                                       /* we have an answered call up to the
+                                        * HS, he wants to hangup */
+                                       mbl_queue_hangup(pvt);
+                               } else {
+                                       /* we have an outgoing call to the HS,
+                                        * he wants to answer */
+                                       if ((pvt->sco_socket = sco_connect(pvt->adapter->addr, pvt->addr)) == -1) {
+                                               ast_log(LOG_ERROR, "[%s] unable to create audio connection\n", pvt->id);
+                                               mbl_queue_hangup(pvt);
+                                               ast_mutex_unlock(&pvt->lock);
+                                               goto e_cleanup;
+                                       }
+
+                                       ast_channel_set_fd(pvt->owner, 0, pvt->sco_socket);
+
+                                       mbl_queue_control(pvt, AST_CONTROL_ANSWER);
+                                       pvt->answered = 1;
+
+                                       if (hsp_send_vgs(pvt->rfcomm_socket, 13) || hsp_send_vgm(pvt->rfcomm_socket, 13)) {
+                                               ast_debug(1, "[%s] error sending VGS/VGM\n", pvt->id);
+                                               mbl_queue_hangup(pvt);
+                                               ast_mutex_unlock(&pvt->lock);
+                                               goto e_cleanup;
+                                       }
+                               }
+                       } else if (pvt->incoming) {
+                               /* we have an incoming call from the
+                                * HS, he wants to hang up */
+                               mbl_queue_hangup(pvt);
+                       } else {
+                               /* no call is up, HS wants to dial */
+                               hsp_send_ok(pvt->rfcomm_socket);
+
+                               if ((pvt->sco_socket = sco_connect(pvt->adapter->addr, pvt->addr)) == -1) {
+                                       ast_log(LOG_ERROR, "[%s] unable to create audio connection\n", pvt->id);
+                                       ast_mutex_unlock(&pvt->lock);
+                                       goto e_cleanup;
+                               }
+
+                               pvt->incoming = 1;
+
+                               if (!(chan = mbl_new(AST_STATE_UP, pvt, NULL, NULL))) {
+                                       ast_log(LOG_ERROR, "[%s] unable to allocate channel for incoming call\n", pvt->id);
+                                       ast_mutex_unlock(&pvt->lock);
+                                       goto e_cleanup;
+                               }
+
+                               ast_channel_set_fd(chan, 0, pvt->sco_socket);
+
+                               ast_copy_string(chan->exten, "s", AST_MAX_EXTENSION);
+                               if (ast_pbx_start(chan)) {
+                                       ast_log(LOG_ERROR, "[%s] unable to start pbx on incoming call\n", pvt->id);
+                                       ast_hangup(chan);
+                                       ast_mutex_unlock(&pvt->lock);
+                                       goto e_cleanup;
+                               }
+                       }
+                       ast_mutex_unlock(&pvt->lock);
+                       break;
+               default:
+                       ast_debug(1, "[%s] received unknown AT command: %s (%s)\n", pvt->id, buf, at_msg2str(at_msg));
+                       if (hsp_send_error(pvt->rfcomm_socket)) {
+                               ast_debug(1, "[%s] error sending AT message 'ERROR'\n", pvt->id);
+                               goto e_cleanup;
+                       }
+                       break;
+               }
+       }
+
+e_cleanup:
+       ast_mutex_lock(&pvt->lock);
+       if (pvt->owner) {
+               ast_debug(1, "[%s] device disconnected, hanging up owner\n", pvt->id);
+               mbl_queue_hangup(pvt);
+       }
+
+
+       close(pvt->rfcomm_socket);
+       close(pvt->sco_socket);
+       pvt->sco_socket = -1;
+
+       pvt->connected = 0;
+
+       pvt->needring = 0;
+       pvt->outgoing = 0;
+       pvt->incoming = 0;
+
+       pvt->adapter->inuse = 0;
+       ast_mutex_unlock(&pvt->lock);
+
+       manager_event(EVENT_FLAG_SYSTEM, "MobileStatus", "Status: Disconnect\r\nDevice: %s\r\n", pvt->id);
+       ast_verb(3, "Bluetooth Device %s has disconnected\n", pvt->id);
+
+       return NULL;
+
+}
+
+static int start_monitor(struct mbl_pvt *pvt)
+{
+
+       if (pvt->type == MBL_TYPE_PHONE) {
+               pvt->hfp->rsock = pvt->rfcomm_socket;
+
+               if (ast_pthread_create_background(&pvt->monitor_thread, NULL, do_monitor_phone, pvt) < 0) {
+                       pvt->monitor_thread = AST_PTHREADT_NULL;
+                       return 0;
+               }
+       } else {
+               if (ast_pthread_create_background(&pvt->monitor_thread, NULL, do_monitor_headset, pvt) < 0) {
+                       pvt->monitor_thread = AST_PTHREADT_NULL;
+                       return 0;
+               }
+       }
+
+       return 1;
+
+}
+
+static void *do_discovery(void *data)
+{
+
+       struct adapter_pvt *adapter;
+       struct mbl_pvt *pvt;
+
+       while (!check_unloading()) {
+               AST_RWLIST_RDLOCK(&adapters);
+               AST_RWLIST_TRAVERSE(&adapters, adapter, entry) {
+                       if (!adapter->inuse) {
+                               AST_RWLIST_RDLOCK(&devices);
+                               AST_RWLIST_TRAVERSE(&devices, pvt, entry) {
+                                       ast_mutex_lock(&pvt->lock);
+                                       if (!adapter->inuse && !pvt->connected && !strcmp(adapter->id, pvt->adapter->id)) {
+                                               if ((pvt->rfcomm_socket = rfcomm_connect(adapter->addr, pvt->addr, pvt->rfcomm_port)) > -1) {
+                                                       if (start_monitor(pvt)) {
+                                                               pvt->connected = 1;
+                                                               adapter->inuse = 1;
+                                                               manager_event(EVENT_FLAG_SYSTEM, "MobileStatus", "Status: Connect\r\nDevice: %s\r\n", pvt->id);
+                                                               ast_verb(3, "Bluetooth Device %s has connected, initilizing...\n", pvt->id);
+                                                       }
+                                               }
+                                       }
+                                       ast_mutex_unlock(&pvt->lock);
+                               }
+                               AST_RWLIST_UNLOCK(&devices);
+                       }
+               }
+               AST_RWLIST_UNLOCK(&adapters);
+
+
+               /* Go to sleep (only if we are not unloading) */
+               if (!check_unloading())
+                       sleep(discovery_interval);
+       }
+
+       return NULL;
+}
+
+/*!
+ * \brief Service new and existing SCO connections.
+ * This thread accepts new sco connections and handles audio data.  There is
+ * one do_sco_listen thread for each adapter.
+ */
+static void *do_sco_listen(void *data)
+{
+       struct adapter_pvt *adapter = (struct adapter_pvt *) data;
+       int res;
+
+       while (!check_unloading()) {
+               /* check for new sco connections */
+               if ((res = ast_io_wait(adapter->accept_io, 0)) == -1) {
+                       /* handle errors */
+                       ast_log(LOG_ERROR, "ast_io_wait() failed for adapter %s\n", adapter->id);
+                       break;
+               }
+
+               /* handle audio data */
+               if ((res = ast_io_wait(adapter->io, 1)) == -1) {
+                       ast_log(LOG_ERROR, "ast_io_wait() failed for audio on adapter %s\n", adapter->id);
+                       break;
+               }
+       }
+
+       return NULL;
+}
+
+/*
+
+       Module
+
+*/
+
+/*!
+ * \brief Load an adapter from the configuration file.
+ * \param cfg the config to load the adapter from
+ * \param cat the adapter to load
+ *
+ * This function loads the given adapter and starts the sco listener thread for
+ * that adapter.
+ *
+ * \return NULL on error, a pointer to the adapter that was loaded on success
+ */
+static struct adapter_pvt *mbl_load_adapter(struct ast_config *cfg, const char *cat)
+{
+       const char *id, *address;
+       struct adapter_pvt *adapter;
+       struct ast_variable *v;
+       struct hci_dev_req dr;
+       uint16_t vs;
+
+       id = ast_variable_retrieve(cfg, cat, "id");
+       address = ast_variable_retrieve(cfg, cat, "address");
+
+       if (ast_strlen_zero(id) || ast_strlen_zero(address)) {
+               ast_log(LOG_ERROR, "Skipping adapter. Missing id or address settings.\n");
+               goto e_return;
+       }
+
+       ast_debug(1, "Reading configuration for adapter %s %s.\n", id, address);
+
+       if (!(adapter = ast_calloc(1, sizeof(*adapter)))) {
+               ast_log(LOG_ERROR, "Skipping adapter %s. Error allocating memory.\n", id);
+               goto e_return;
+       }
+
+       ast_copy_string(adapter->id, id, sizeof(adapter->id));
+       str2ba(address, &adapter->addr);
+
+       /* attempt to connect to the adapter */
+       adapter->dev_id = hci_devid(address);
+       adapter->hci_socket = hci_open_dev(adapter->dev_id);
+       if (adapter->dev_id < 0 || adapter->hci_socket < 0) {
+               ast_log(LOG_ERROR, "Skipping adapter %s. Unable to communicate with adapter.\n", adapter->id);
+               goto e_free_adapter;
+       }
+
+       /* check voice setting */
+       hci_read_voice_setting(adapter->hci_socket, &vs, 1000);
+       vs = htobs(vs);
+       if (vs != 0x0060) {
+               ast_log(LOG_ERROR, "Skipping adapter %s. Voice setting must be 0x0060 - see 'man hciconfig' for details.\n", adapter->id);
+               goto e_hci_close_dev;
+       }
+
+       for (v = ast_variable_browse(cfg, cat); v; v = v->next) {
+               if (!strcasecmp(v->name, "forcemaster")) {
+                       if (ast_true(v->value)) {
+                               dr.dev_id = adapter->dev_id;
+                               if (hci_strtolm("master", &dr.dev_opt)) {
+                                       if (ioctl(adapter->hci_socket, HCISETLINKMODE, (unsigned long) &dr) < 0) {
+                                               ast_log(LOG_WARNING, "Unable to set adapter %s link mode to MASTER. Ignoring 'forcemaster' option.\n", adapter->id);
+                                       }
+                               }
+                       }
+               } else if (!strcasecmp(v->name, "alignmentdetection")) {
+                       adapter->alignment_detection = ast_true(v->value);
+               }
+       }
+
+       /* create io contexts */
+       if (!(adapter->accept_io = io_context_create())) {
+               ast_log(LOG_ERROR, "Unable to create I/O context for audio connection listener\n");
+               goto e_hci_close_dev;
+       }
+
+       if (!(adapter->io = io_context_create())) {
+               ast_log(LOG_ERROR, "Unable to create I/O context for audio connections\n");
+               goto e_destroy_accept_io;
+       }
+
+       /* bind the sco listener socket */
+       if (sco_bind(adapter) < 0) {
+               ast_log(LOG_ERROR, "Skipping adapter %s. Error binding audio connection listerner socket.\n", adapter->id);
+               goto e_destroy_io;
+       }
+
+       /* add the socket to the io context */
+       if (!(adapter->sco_id = ast_io_add(adapter->accept_io, adapter->sco_socket, sco_accept, AST_IO_IN, adapter))) {
+               ast_log(LOG_ERROR, "Skipping adapter %s. Error adding listener socket to I/O context.\n", adapter->id);
+               goto e_close_sco;
+       }
+
+       /* start the sco listener for this adapter */
+       if (ast_pthread_create_background(&adapter->sco_listener_thread, NULL, do_sco_listen, adapter)) {
+               ast_log(LOG_ERROR, "Skipping adapter %s. Error creating audio connection listerner thread.\n", adapter->id);
+               goto e_remove_sco;
+       }
+
+       /* add the adapter to our global list */
+       AST_RWLIST_WRLOCK(&adapters);
+       AST_RWLIST_INSERT_HEAD(&adapters, adapter, entry);
+       AST_RWLIST_UNLOCK(&adapters);
+       ast_debug(1, "Loaded adapter %s %s.\n", adapter->id, address);
+
+       return adapter;
+
+e_remove_sco:
+       ast_io_remove(adapter->accept_io, adapter->sco_id);
+e_close_sco:
+       close(adapter->sco_socket);
+e_destroy_io:
+       io_context_destroy(adapter->io);
+e_destroy_accept_io:
+       io_context_destroy(adapter->accept_io);
+e_hci_close_dev:
+       hci_close_dev(adapter->hci_socket);
+e_free_adapter:
+       ast_free(adapter);
+e_return:
+       return NULL;
+}
+
+/*!
+ * \brief Load a device from the configuration file.
+ * \param cfg the config to load the device from
+ * \param cat the device to load
+ * \return NULL on error, a pointer to the device that was loaded on success
+ */
+static struct mbl_pvt *mbl_load_device(struct ast_config *cfg, const char *cat)
+{
+       struct mbl_pvt *pvt;
+       struct adapter_pvt *adapter;
+       struct ast_variable *v;
+       const char *address, *adapter_str, *port;
+       ast_debug(1, "Reading configuration for device %s.\n", cat);
+
+       adapter_str = ast_variable_retrieve(cfg, cat, "adapter");
+       if(ast_strlen_zero(adapter_str)) {
+               ast_log(LOG_ERROR, "Skipping device %s. No adapter specified.\n", cat);
+               goto e_return;
+       }
+
+       /* find the adapter */
+       AST_RWLIST_RDLOCK(&adapters);
+       AST_RWLIST_TRAVERSE(&adapters, adapter, entry) {
+               if (!strcmp(adapter->id, adapter_str))
+                       break;
+       }
+       AST_RWLIST_UNLOCK(&adapters);
+       if (!adapter) {
+               ast_log(LOG_ERROR, "Skiping device %s. Unknown adapter '%s' specified.\n", cat, adapter_str);
+               goto e_return;
+       }
+
+       address = ast_variable_retrieve(cfg, cat, "address");
+       port = ast_variable_retrieve(cfg, cat, "port");
+       if (ast_strlen_zero(port) || ast_strlen_zero(address)) {
+               ast_log(LOG_ERROR, "Skipping device %s. Missing required port or address setting.\n", cat);
+               goto e_return;
+       }
+
+       /* create and initialize our pvt structure */
+       if (!(pvt = ast_calloc(1, sizeof(*pvt)))) {
+               ast_log(LOG_ERROR, "Skipping device %s. Error allocating memory.\n", cat);
+               goto e_return;
+       }
+
+       ast_mutex_init(&pvt->lock);
+       AST_LIST_HEAD_INIT_NOLOCK(&pvt->msg_queue);
+
+       /* set some defaults */
+
+       pvt->type = MBL_TYPE_PHONE;
+       ast_copy_string(pvt->context, "default", sizeof(pvt->context));
+
+       /* populate the pvt structure */
+       pvt->adapter = adapter;
+       ast_copy_string(pvt->id, cat, sizeof(pvt->id));
+       str2ba(address, &pvt->addr);
+       pvt->timeout = -1;
+       pvt->rfcomm_socket = -1;
+       pvt->rfcomm_port = atoi(port);
+       pvt->sco_socket = -1;
+       pvt->monitor_thread = AST_PTHREADT_NULL;
+       pvt->ring_sched_id = -1;
+
+       /* setup the smoother */
+       if (!(pvt->smoother = ast_smoother_new(DEVICE_FRAME_SIZE))) {
+               ast_log(LOG_ERROR, "Skipping device %s. Error setting up frame smoother.\n", cat);
+               goto e_free_pvt;
+       }
+
+       /* setup the dsp */
+       if (!(pvt->dsp = ast_dsp_new())) {
+               ast_log(LOG_ERROR, "Skipping device %s. Error setting up dsp for dtmf detection.\n", cat);
+               goto e_free_smoother;
+       }
+
+       /* setup the scheduler */
+       if (!(pvt->sched = sched_context_create())) {
+               ast_log(LOG_ERROR, "Unable to create scheduler context for headset device\n");
+               goto e_free_dsp;
+       }
+
+       ast_dsp_set_features(pvt->dsp, DSP_FEATURE_DIGIT_DETECT);
+       ast_dsp_set_digitmode(pvt->dsp, DSP_DIGITMODE_DTMF | DSP_DIGITMODE_RELAXDTMF);
+
+       for (v = ast_variable_browse(cfg, cat); v; v = v->next) {
+               if (!strcasecmp(v->name, "type")) {
+                       if (!strcasecmp(v->value, "headset"))
+                               pvt->type = MBL_TYPE_HEADSET;
+                       else
+                               pvt->type = MBL_TYPE_PHONE;
+               } else if (!strcasecmp(v->name, "context")) {
+                       ast_copy_string(pvt->context, v->value, sizeof(pvt->context));
+               } else if (!strcasecmp(v->name, "group")) {
+                       /* group is set to 0 if invalid */
+                       pvt->group = atoi(v->value);
+               } else if (!strcasecmp(v->name, "nocallsetup")) {
+                       pvt->no_callsetup = ast_true(v->value);
+
+                       if (pvt->no_callsetup)
+                               ast_debug(1, "Setting nocallsetup mode for device %s.\n", pvt->id);
+               } else if (!strcasecmp(v->name, "blackberry")) {
+                       pvt->blackberry = ast_true(v->value);
+               }
+       }
+
+       if (pvt->type == MBL_TYPE_PHONE) {
+               if (!(pvt->hfp = ast_calloc(1, sizeof(*pvt->hfp)))) {
+                       ast_log(LOG_ERROR, "Skipping device %s. Error allocating memory.\n", pvt->id);
+                       goto e_free_sched;
+               }
+
+               pvt->hfp->owner = pvt;
+               pvt->hfp->rport = pvt->rfcomm_port;
+               pvt->hfp->nocallsetup = pvt->no_callsetup;
+       }
+
+       AST_RWLIST_WRLOCK(&devices);
+       AST_RWLIST_INSERT_HEAD(&devices, pvt, entry);
+       AST_RWLIST_UNLOCK(&devices);
+       ast_debug(1, "Loaded device %s.\n", pvt->id);
+
+       return pvt;
+
+e_free_sched:
+       sched_context_destroy(pvt->sched);
+e_free_dsp:
+       ast_dsp_free(pvt->dsp);
+e_free_smoother:
+       ast_smoother_free(pvt->smoother);
+e_free_pvt:
+       ast_free(pvt);
+e_return:
+       return NULL;
+}
+
+static int mbl_load_config(void)
+{
+       struct ast_config *cfg;
+       const char *cat;
+       struct ast_variable *v;
+       struct ast_flags config_flags = { 0 };
+
+       cfg = ast_config_load(MBL_CONFIG, config_flags);
+       if (!cfg)
+               return -1;
+
+       /* parse [general] section */
+       for (v = ast_variable_browse(cfg, "general"); v; v = v->next) {
+               if (!strcasecmp(v->name, "interval")) {
+                       if (!sscanf(v->value, "%d", &discovery_interval)) {
+                               ast_log(LOG_NOTICE, "error parsing 'interval' in general section, using default value\n");
+                       }
+               }
+       }
+
+       /* load adapters */
+       for (cat = ast_category_browse(cfg, NULL); cat; cat = ast_category_browse(cfg, cat)) {
+               if (!strcasecmp(cat, "adapter")) {
+                       mbl_load_adapter(cfg, cat);
+               }
+       }
+
+       if (AST_RWLIST_EMPTY(&adapters)) {
+               ast_log(LOG_ERROR,
+                       "***********************************************************************\n"
+                       "No adapters could be loaded from the configuration file.\n"
+                       "Please review mobile.conf. See sample for details.\n"
+                       "***********************************************************************\n"
+                      );
+               ast_config_destroy(cfg);
+               return -1;
+       }
+
+       /* now load devices */
+       for (cat = ast_category_browse(cfg, NULL); cat; cat = ast_category_browse(cfg, cat)) {
+               if (strcasecmp(cat, "general") && strcasecmp(cat, "adapter")) {
+                       mbl_load_device(cfg, cat);
+               }
+       }
+
+       ast_config_destroy(cfg);
+
+       return 0;
+}
+
+/*!
+ * \brief Check if the module is unloading.
+ * \retval 0 not unloading
+ * \retval 1 unloading
+ */
+static inline int check_unloading()
+{
+       int res;
+       ast_mutex_lock(&unload_mutex);
+       res = unloading_flag;
+       ast_mutex_unlock(&unload_mutex);
+
+       return res;
+}
+
+/*!
+ * \brief Set the unloading flag.
+ */
+static inline void set_unloading()
+{
+       ast_mutex_lock(&unload_mutex);
+       unloading_flag = 1;
+       ast_mutex_unlock(&unload_mutex);
+}
+
+static int unload_module(void)
+{
+       struct mbl_pvt *pvt;
+       struct adapter_pvt *adapter;
+
+       /* First, take us out of the channel loop */
+       ast_channel_unregister(&mbl_tech);
+
+       /* Unregister the CLI & APP */
+       ast_cli_unregister_multiple(mbl_cli, sizeof(mbl_cli) / sizeof(mbl_cli[0]));
+       ast_unregister_application(app_mblstatus);
+       ast_unregister_application(app_mblsendsms);
+
+       /* signal everyone we are unloading */
+       set_unloading();
+
+       /* Kill the discovery thread */
+       if (discovery_thread != AST_PTHREADT_NULL) {
+               pthread_kill(discovery_thread, SIGURG);
+               pthread_join(discovery_thread, NULL);
+       }
+
+       /* stop the sco listener threads */
+       AST_RWLIST_WRLOCK(&adapters);
+       AST_RWLIST_TRAVERSE(&adapters, adapter, entry) {
+               pthread_kill(adapter->sco_listener_thread, SIGURG);
+               pthread_join(adapter->sco_listener_thread, NULL);
+       }
+       AST_RWLIST_UNLOCK(&adapters);
+
+       /* Destroy the device list */
+       AST_RWLIST_WRLOCK(&devices);
+       while ((pvt = AST_RWLIST_REMOVE_HEAD(&devices, entry))) {
+               if (pvt->monitor_thread != AST_PTHREADT_NULL) {
+                       pthread_kill(pvt->monitor_thread, SIGURG);
+                       pthread_join(pvt->monitor_thread, NULL);
+               }
+
+               close(pvt->sco_socket);
+               close(pvt->rfcomm_socket);
+
+               msg_queue_flush(pvt);
+
+               if (pvt->hfp) {
+                       ast_free(pvt->hfp);
+               }
+
+               ast_smoother_free(pvt->smoother);
+               ast_dsp_free(pvt->dsp);
+               sched_context_destroy(pvt->sched);
+               ast_free(pvt);
+       }
+       AST_RWLIST_UNLOCK(&devices);
+
+       /* Destroy the adapter list */
+       AST_RWLIST_WRLOCK(&adapters);
+       while ((adapter = AST_RWLIST_REMOVE_HEAD(&adapters, entry))) {
+               close(adapter->sco_socket);
+               io_context_destroy(adapter->io);
+               io_context_destroy(adapter->accept_io);
+               hci_close_dev(adapter->hci_socket);
+               ast_free(adapter);
+       }
+       AST_RWLIST_UNLOCK(&adapters);
+
+       if (sdp_session)
+               sdp_close(sdp_session);
+
+       return 0;
+}
+
+static int load_module(void)
+{
+
+       int dev_id, s;
+
+       /* Check if we have Bluetooth, no point loading otherwise... */
+       dev_id = hci_get_route(NULL);
+       s = hci_open_dev(dev_id);
+       if (dev_id < 0 || s < 0) {
+               ast_log(LOG_ERROR, "No Bluetooth devices found. Not loading module.\n");
+               return AST_MODULE_LOAD_DECLINE;
+       }
+
+       hci_close_dev(s);
+
+       if (mbl_load_config()) {
+               ast_log(LOG_ERROR, "Errors reading config file %s. Not loading module.\n", MBL_CONFIG);
+               return AST_MODULE_LOAD_DECLINE;
+       }
+
+       sdp_session = sdp_register();
+
+       /* Spin the discovery thread */
+       if (ast_pthread_create_background(&discovery_thread, NULL, do_discovery, NULL) < 0) {
+               ast_log(LOG_ERROR, "Unable to create discovery thread.\n");
+               goto e_cleanup;
+       }
+
+       /* register our channel type */
+       if (ast_channel_register(&mbl_tech)) {
+               ast_log(LOG_ERROR, "Unable to register channel class %s\n", "Mobile");
+               goto e_cleanup;
+       }
+
+       ast_cli_register_multiple(mbl_cli, sizeof(mbl_cli) / sizeof(mbl_cli[0]));
+       ast_register_application(app_mblstatus, mbl_status_exec, mblstatus_synopsis, mblstatus_desc);
+       ast_register_application(app_mblsendsms, mbl_sendsms_exec, mblsendsms_synopsis, mblsendsms_desc);
+
+       return AST_MODULE_LOAD_SUCCESS;
+
+e_cleanup:
+       if (sdp_session)
+               sdp_close(sdp_session);
+
+       return AST_MODULE_LOAD_FAILURE;
+}
+
+AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_DEFAULT, "Bluetooth Mobile Device Channel Driver",
+               .load = load_module,
+               .unload = unload_module,
+);
diff --git a/addons/chan_ooh323.c b/addons/chan_ooh323.c
new file mode 100644 (file)
index 0000000..70c0f02
--- /dev/null
@@ -0,0 +1,3162 @@
+/*
+ * Copyright (C) 2004-2005 by Objective Systems, Inc.
+ *
+ * This software is furnished under an open source license and may be 
+ * used and copied only in accordance with the terms of this license. 
+ * The text of the license may generally be found in the root 
+ * directory of this installation in the COPYING file.  It 
+ * can also be viewed online at the following URL:
+ *
+ *   http://www.obj-sys.com/open/license.html
+ *
+ * Any redistributions of this file including modified versions must 
+ * maintain this copyright notice.
+ *
+ *****************************************************************************/
+
+
+#include "chan_ooh323.h"
+
+/*** MODULEINFO
+       <defaultenabled>no</defaultenabled>
+ ***/
+
+/* Defaults */
+#define DEFAULT_CONTEXT "default"
+#define DEFAULT_H323ID "Asterisk PBX"
+#define DEFAULT_LOGFILE "/var/log/asterisk/h323_log"
+#define DEFAULT_H323ACCNT "ast_h323"
+
+/* Flags */
+#define H323_SILENCESUPPRESSION (1<<0)
+#define H323_GKROUTED           (1<<1)
+#define H323_TUNNELING          (1<<2)
+#define H323_FASTSTART          (1<<3)
+#define H323_OUTGOING           (1<<4)
+#define H323_ALREADYGONE        (1<<5)
+#define H323_NEEDDESTROY        (1<<6)
+#define H323_DISABLEGK          (1<<7)
+
+/* Channel description */
+static const char type[] = "OOH323";
+static const char tdesc[] = "Objective Systems H323 Channel Driver";
+static const char config[] = "ooh323.conf";
+
+
+/* Channel Definition */
+static struct ast_channel *ooh323_request(const char *type, int format, 
+                                        void *data, int *cause);
+static int ooh323_digit_begin(struct ast_channel *ast, char digit);
+static int ooh323_digit_end(struct ast_channel *ast, char digit, unsigned int duration);
+static int ooh323_call(struct ast_channel *ast, char *dest, int timeout);
+static int ooh323_hangup(struct ast_channel *ast);
+static int ooh323_answer(struct ast_channel *ast);
+static struct ast_frame *ooh323_read(struct ast_channel *ast);
+static int ooh323_write(struct ast_channel *ast, struct ast_frame *f);
+static int ooh323_indicate(struct ast_channel *ast, int condition, const void *data, size_t datalen);
+static int ooh323_fixup(struct ast_channel *oldchan, struct ast_channel *newchan);
+
+static enum ast_rtp_get_result ooh323_get_rtp_peer(struct ast_channel *chan, struct ast_rtp **rtp);
+static enum ast_rtp_get_result ooh323_get_vrtp_peer(struct ast_channel *chan, struct ast_rtp **rtp);
+static int ooh323_set_rtp_peer(struct ast_channel *chan, struct ast_rtp *rtp, 
+                             struct ast_rtp *vrtp, struct ast_rtp *trtp, int codecs, int nat_active);
+
+static void print_codec_to_cli(int fd, struct ast_codec_pref *pref);
+
+#if 0
+static void ast_ooh323c_exit();
+#endif
+
+static const struct ast_channel_tech ooh323_tech = {
+       .type = type,
+       .description = tdesc,
+       .capabilities = -1,
+       .requester = ooh323_request,
+       .send_digit_begin = ooh323_digit_begin,
+       .send_digit_end = ooh323_digit_end,
+       .call = ooh323_call,
+       .hangup = ooh323_hangup,
+       .answer = ooh323_answer,
+       .read = ooh323_read,
+       .write = ooh323_write,
+       .exception = ooh323_read,
+       .indicate = ooh323_indicate,
+       .fixup = ooh323_fixup,
+       .send_html = 0,
+       .bridge = ast_rtp_bridge,
+};
+
+static struct ast_rtp_protocol ooh323_rtp = {
+       .type = type,
+       .get_rtp_info = ooh323_get_rtp_peer,
+       .get_vrtp_info = ooh323_get_vrtp_peer,
+       .set_rtp_peer = ooh323_set_rtp_peer
+};
+
+/* H.323 channel private structure */
+static struct ooh323_pvt {
+       ast_mutex_t lock;               /* Channel private lock */
+       struct ast_rtp *rtp;
+       struct ast_rtp *vrtp; /* Placeholder for now */
+       struct ast_channel *owner;      /* Master Channel */
+       time_t lastrtptx;
+       time_t lastrtprx;
+       unsigned int flags;
+       unsigned int call_reference;
+       char *callToken;
+       char *username;
+       char *host;
+       char *callerid_name;
+       char *callerid_num;
+       char caller_h323id[AST_MAX_EXTENSION];
+       char caller_dialedDigits[AST_MAX_EXTENSION];
+       char caller_email[AST_MAX_EXTENSION];
+       char caller_url[256];
+       char callee_h323id[AST_MAX_EXTENSION];
+       char callee_dialedDigits[AST_MAX_EXTENSION];
+       char callee_email[AST_MAX_EXTENSION];
+       char callee_url[AST_MAX_EXTENSION];
+       int port;
+       int readformat;   /* negotiated read format */
+       int writeformat;  /* negotiated write format */
+       int capability;
+       struct ast_codec_pref prefs;
+       int dtmfmode;
+       char exten[AST_MAX_EXTENSION];  /* Requested extension */
+       char context[AST_MAX_EXTENSION];        /* Context where to start */
+       char accountcode[256];  /* Account code */
+       int nat;
+       int amaflags;
+       struct ast_dsp *vad;
+       struct ooh323_pvt *next;        /* Next entity */
+} *iflist = NULL;
+
+/* Protect the channel/interface list (ooh323_pvt) */
+AST_MUTEX_DEFINE_STATIC(iflock);
+
+/* Profile of H.323 user registered with PBX*/
+struct ooh323_user{
+       ast_mutex_t lock;
+       char        name[256];
+       char        context[AST_MAX_EXTENSION];
+       int         incominglimit;
+       unsigned    inUse;
+       char        accountcode[20];
+       int         amaflags;
+       int         capability;
+       struct ast_codec_pref prefs;
+       int         dtmfmode;
+       int         rtptimeout;
+       int         mUseIP;        /* Use IP address or H323-ID to search user */
+       char        mIP[20];
+       struct ooh323_user *next;
+};
+
+/* Profile of valid asterisk peers */
+struct ooh323_peer{
+       ast_mutex_t lock;
+       char        name[256];
+       unsigned    outgoinglimit;
+       unsigned    outUse;
+       int         capability;
+       struct ast_codec_pref prefs;
+       char        accountcode[20];
+       int         amaflags;
+       int         dtmfmode;
+       int         mFriend;    /* indicates defined as friend */
+       char        ip[20];
+       int         port;
+       char        *h323id;    /* H323-ID alias, which asterisk will register with gk to reach this peer*/
+       char        *email;     /* Email alias, which asterisk will register with gk to reach this peer*/
+       char        *url;       /* url alias, which asterisk will register with gk to reach this peer*/
+       char        *e164;      /* e164 alias, which asterisk will register with gk to reach this peer*/
+       int         rtptimeout;
+       struct ooh323_peer *next;
+};
+
+
+/* List of H.323 users known to PBX */
+static struct ast_user_list {
+       struct ooh323_user *users;
+       ast_mutex_t lock;
+} userl;
+
+static struct ast_peer_list {
+       struct ooh323_peer *peers;
+       ast_mutex_t lock;
+} peerl;
+
+/* Mutex to protect H.323 reload process */
+static int h323_reloading = 0;
+AST_MUTEX_DEFINE_STATIC(h323_reload_lock);
+
+/* Mutex to protect usage counter */
+static int usecnt = 0;
+AST_MUTEX_DEFINE_STATIC(usecnt_lock);
+
+AST_MUTEX_DEFINE_STATIC(ooh323c_cmd_lock);
+
+/* stack callbacks */
+int onAlerting(ooCallData *call);
+int onNewCallCreated(ooCallData *call);
+int onCallEstablished(ooCallData *call);
+int onCallCleared(ooCallData *call);
+
+static char gLogFile[256] = DEFAULT_LOGFILE;
+static int  gPort = 1720;
+static char gIP[20];
+static char gCallerID[AST_MAX_EXTENSION] = DEFAULT_H323ID;
+static struct ooAliases *gAliasList;
+static int  gCapability = AST_FORMAT_ULAW;
+static struct ast_codec_pref gPrefs;
+static int  gDTMFMode = H323_DTMF_RFC2833;
+static char gGatekeeper[100];
+static enum RasGatekeeperMode gRasGkMode = RasNoGatekeeper;
+
+static int  gIsGateway = 0;
+static int  gFastStart = 1;
+static int  gTunneling = 1;
+static int  gMediaWaitForConnect = 0;
+static int  gTOS = 0;
+static int  gRTPTimeout = 60;
+static char gAccountcode[80] = DEFAULT_H323ACCNT;
+static int  gAMAFLAGS;
+static char gContext[AST_MAX_EXTENSION] = DEFAULT_CONTEXT;
+static int  gIncomingLimit = 4;
+static int  gOutgoingLimit = 4;
+OOBOOL gH323Debug = FALSE;
+
+static struct ooh323_config
+{
+   int  mTCPPortStart;
+   int  mTCPPortEnd;
+} ooconfig;
+
+/** Asterisk RTP stuff*/
+static struct sched_context *sched;
+static struct io_context *io;
+
+/* Protect the monitoring thread, so only one process can kill or start it, 
+   and not when it's doing something critical. */
+AST_MUTEX_DEFINE_STATIC(monlock);
+
+
+/* This is the thread for the monitor which checks for input on the channels
+   which are not currently in use.  */
+static pthread_t monitor_thread = AST_PTHREADT_NULL;
+
+
+static struct ast_channel *ooh323_new(struct ooh323_pvt *i, int state,
+                                             const char *host) 
+{
+       struct ast_channel *ch = NULL;
+       int fmt;
+       if (gH323Debug)
+               ast_verbose("---   ooh323_new - %s\n", host);
+
+
+       /* Don't hold a h323 pvt lock while we allocate a channel */
+       ast_mutex_unlock(&i->lock);
+       ch = ast_channel_alloc(1, state, i->callerid_num, i->callerid_name, i->accountcode, i->exten, i->context, i->amaflags, "OOH323/%s-%08x", host, (unsigned int)(unsigned long) i);
+       ast_mutex_lock(&i->lock);
+
+       if (ch) {
+               ast_channel_lock(ch);
+               ch->tech = &ooh323_tech;
+
+               ch->nativeformats = i->capability;
+
+               fmt = ast_best_codec(ch->nativeformats);
+
+               ch->fds[0] = ast_rtp_fd(i->rtp);
+               ch->fds[1] = ast_rtcp_fd(i->rtp);
+
+               if (state == AST_STATE_RING)
+                       ch->rings = 1;
+
+               ch->adsicpe = AST_ADSI_UNAVAILABLE;
+               ch->writeformat = fmt;
+               ch->rawwriteformat = fmt;
+               ch->readformat = fmt;
+               ch->rawreadformat = fmt;
+               ch->tech_pvt = i;
+               i->owner = ch;
+
+               /* Allocate dsp for in-band DTMF support */
+               if (i->dtmfmode & H323_DTMF_INBAND) {
+                       i->vad = ast_dsp_new();
+                       ast_dsp_set_features(i->vad, DSP_FEATURE_DIGIT_DETECT);
+               }
+
+               ast_mutex_lock(&usecnt_lock);
+               usecnt++;
+               ast_mutex_unlock(&usecnt_lock);
+
+               /* Notify the module monitors that use count for resource has changed*/
+               ast_update_use_count();
+
+               ast_copy_string(ch->context, i->context, sizeof(ch->context));
+               ast_copy_string(ch->exten, i->exten, sizeof(ch->exten));
+
+               ch->priority = 1;
+               if (i->callerid_name) {
+                       ch->cid.cid_name = strdup(i->callerid_name);
+               }
+               if (i->callerid_num) {
+
+                       ch->cid.cid_num = strdup(i->callerid_num);
+               }
+
+               if (!ast_test_flag(i, H323_OUTGOING)) {
+               
+                       if (!ast_strlen_zero(i->caller_h323id)) {
+                               pbx_builtin_setvar_helper(ch, "_CALLER_H323ID", i->caller_h323id);
+
+                       }
+                       if (!ast_strlen_zero(i->caller_dialedDigits)) {
+                               pbx_builtin_setvar_helper(ch, "_CALLER_H323DIALEDDIGITS", 
+                               i->caller_dialedDigits);
+                       }
+                       if (!ast_strlen_zero(i->caller_email)) {
+                               pbx_builtin_setvar_helper(ch, "_CALLER_H323EMAIL", 
+                               i->caller_email);
+                       }
+                       if (!ast_strlen_zero(i->caller_url)) {
+                               pbx_builtin_setvar_helper(ch, "_CALLER_H323URL", i->caller_url);
+                       }
+               }
+
+               if (!ast_strlen_zero(i->accountcode))
+                       ast_string_field_set(ch, accountcode, i->accountcode);
+               
+               if (i->amaflags)
+                       ch->amaflags = i->amaflags;
+
+               ast_setstate(ch, state);
+               if (state != AST_STATE_DOWN) {
+                       if (ast_pbx_start(ch)) {
+                               ast_log(LOG_WARNING, "Unable to start PBX on %s\n", ch->name);
+                               ast_channel_unlock(ch);
+                               ast_hangup(ch);
+                               ch = NULL;
+                       }
+               }
+       } else
+               ast_log(LOG_WARNING, "Unable to allocate channel structure\n");
+
+
+       if (ch)
+               ast_channel_unlock(ch);
+
+       if (gH323Debug)
+               ast_verbose("+++   h323_new\n");
+
+       return ch;
+}
+
+
+
+static struct ooh323_pvt *ooh323_alloc(int callref, char *callToken) 
+{
+       struct ooh323_pvt *pvt = NULL;
+       struct in_addr ipAddr;
+       if (gH323Debug)
+               ast_verbose("---   ooh323_alloc\n");
+
+       if (!(pvt = ast_calloc(1, sizeof(*pvt)))) {
+               ast_log(LOG_ERROR, "Couldn't allocate private ooh323 structure\n");
+               return NULL;
+       }
+
+       ast_mutex_init(&pvt->lock);
+       ast_mutex_lock(&pvt->lock);
+
+       if (!inet_aton(gIP, &ipAddr)) {
+               ast_log(LOG_ERROR, "Invalid OOH323 driver ip address\n");
+               ast_mutex_unlock(&pvt->lock);
+               ast_mutex_destroy(&pvt->lock);
+               free(pvt);
+               return NULL;
+       }
+
+       if (!(pvt->rtp = ast_rtp_new_with_bindaddr(sched, io, 1, 0, ipAddr))) {
+               ast_log(LOG_WARNING, "Unable to create RTP session: %s\n", 
+                                 strerror(errno));
+               ast_mutex_unlock(&pvt->lock);
+               ast_mutex_destroy(&pvt->lock);
+               free(pvt);
+               return NULL;
+       }
+       ast_rtp_setqos(pvt->rtp, gTOS, 0, "ooh323");
+
+       pvt->call_reference = callref;
+       if (callToken)
+               pvt->callToken = strdup(callToken);
+
+       /* whether to use gk for this call */
+       if (gRasGkMode == RasNoGatekeeper)
+               OO_SETFLAG(pvt->flags, H323_DISABLEGK);
+
+       pvt->dtmfmode = gDTMFMode;
+       ast_copy_string(pvt->context, gContext, sizeof(pvt->context));
+       ast_copy_string(pvt->accountcode, gAccountcode, sizeof(pvt->accountcode));
+       pvt->amaflags = gAMAFLAGS;
+       pvt->capability = gCapability;
+       memcpy(&pvt->prefs, &gPrefs, sizeof(pvt->prefs));
+
+       ast_mutex_unlock(&pvt->lock); 
+       /* Add to interface list */
+       ast_mutex_lock(&iflock);
+       pvt->next = iflist;
+       iflist = pvt;
+       ast_mutex_unlock(&iflock);
+
+       if (gH323Debug)
+               ast_verbose("+++   ooh323_alloc\n");
+
+       return pvt;
+}
+
+
+/*
+       Possible data values - peername, exten/peername, exten@ip
+ */
+static struct ast_channel *ooh323_request(const char *type, int format, 
+                                                                                                        void *data, int *cause)
+{
+       struct ast_channel *chan = NULL;
+       struct ooh323_pvt *p = NULL;
+       struct ooh323_peer *peer = NULL;
+       char *dest = NULL; 
+       char *ext = NULL;
+       char tmp[256];
+       char formats[512];
+       int oldformat;
+       int port = 0;
+
+       if (gH323Debug)
+               ast_verbose("---   ooh323_request - data %s format %s\n", (char*)data,  
+                                                                               ast_getformatname_multiple(formats,512,format));
+
+       oldformat = format;
+       format &= AST_FORMAT_AUDIO_MASK;
+       if (!format) {
+               ast_log(LOG_NOTICE, "Asked to get a channel of unsupported format "
+                                                                 "'%d'\n", format);
+               return NULL;
+       }
+
+       p = ooh323_alloc(0,0); /* Initial callRef is zero */
+
+       if (!p) {
+               ast_log(LOG_WARNING, "Unable to build pvt data for '%s'\n", (char*)data);
+               return NULL;
+       }
+       ast_mutex_lock(&p->lock);
+
+       /* This is an outgoing call, since ooh323_request is called */
+       ast_set_flag(p, H323_OUTGOING);
+
+       ast_copy_string(tmp, data, sizeof(data));
+
+       dest = strchr(tmp, '/');
+
+       if (dest) {  
+               *dest = '\0';
+               dest++;
+               ext = tmp;
+       } else if ((dest = strchr(tmp, '@'))) {
+               *dest = '\0';
+               dest++;
+               ext = tmp;
+       } else {
+               dest = tmp;
+               ext = NULL;
+       }
+
+#if 0
+       if ((sport = strchr(dest, ':'))) {
+               *sport = '\0';
+               sport++;
+               port = atoi(sport);
+       }
+#endif
+
+       if (dest) {
+               peer = find_peer(dest, port);
+       } else{
+               ast_log(LOG_ERROR, "Destination format is not supported\n");
+               return NULL;
+       }
+
+       if (peer) {
+               p->username = strdup(peer->name);
+               p->host = strdup(peer->ip);
+               p->port = peer->port;
+               /* Disable gk as we are going to call a known peer*/
+               OO_SETFLAG(p->flags, H323_DISABLEGK);
+
+               if (ext)
+                       ast_copy_string(p->exten, ext, sizeof(p->exten));
+
+               if (peer->capability & format) {
+                       p->capability = peer->capability & format;
+               } else {
+                 p->capability = peer->capability;
+               }
+               memcpy(&p->prefs, &peer->prefs, sizeof(struct ast_codec_pref));
+               p->dtmfmode = peer->dtmfmode;
+               ast_copy_string(p->accountcode, peer->accountcode, sizeof(p->accountcode));
+               p->amaflags = peer->amaflags;
+       } else {
+               p->dtmfmode = gDTMFMode;
+               p->capability = gCapability;
+
+               memcpy(&p->prefs, &gPrefs, sizeof(struct ast_codec_pref));
+               p->username = strdup(dest);
+
+               p->host = strdup(dest);
+               if (port > 0) {
+                       p->port = port;
+               }
+               if (ext) {
+                       ast_copy_string(p->exten, ext, sizeof(p->exten));
+               }
+       }
+
+
+       chan = ooh323_new(p, AST_STATE_DOWN, p->username);
+       
+       ast_mutex_unlock(&p->lock);
+
+       if (!chan) {
+               ast_mutex_lock(&iflock);
+               ooh323_destroy(p);
+               ast_mutex_unlock(&iflock);
+       }
+
+       restart_monitor();
+       if (gH323Debug)
+               ast_verbose("+++   ooh323_request\n");
+
+       return chan;
+
+}
+
+
+static struct ooh323_pvt* find_call(ooCallData *call)
+{
+       struct ooh323_pvt *p;
+
+       if (gH323Debug)
+               ast_verbose("---   find_call\n");
+
+       ast_mutex_lock(&iflock);
+
+       for (p = iflist; p; p = p->next) {
+               if (p->callToken && !strcmp(p->callToken, call->callToken)) {
+                       break;
+               }
+       }
+       ast_mutex_unlock(&iflock);
+
+       if (gH323Debug)
+               ast_verbose("+++   find_call\n");
+
+       return p;
+}
+
+struct ooh323_user *find_user(const char * name, const char* ip)
+{
+       struct ooh323_user *user;
+
+       if (gH323Debug)
+               ast_verbose("---   find_user\n");
+
+       ast_mutex_lock(&userl.lock);
+       for (user = userl.users; user; user = user->next) {
+               if (ip && user->mUseIP && !strcmp(user->mIP, ip)) {
+                       break;
+               }
+               if (name && !strcmp(user->name, name)) {
+                       break;
+               }
+       }
+       ast_mutex_unlock(&userl.lock);
+
+       if (gH323Debug)
+               ast_verbose("+++   find_user\n");
+
+       return user;
+}
+
+struct ooh323_peer *find_friend(const char *name, int port)
+{
+       struct ooh323_peer *peer;  
+
+       if (gH323Debug)
+               ast_verbose("---   find_friend \"%s\"\n", name);
+
+
+       ast_mutex_lock(&peerl.lock);
+       for (peer = peerl.peers; peer; peer = peer->next) {
+               if (gH323Debug) {
+                       ast_verbose("           comparing with \"%s\"\n", peer->ip);
+               }
+               if (!strcmp(peer->ip, name)) {
+                       if (port <= 0 || (port > 0 && peer->port == port)) {
+                               break;
+                       }
+               }
+       }
+       ast_mutex_unlock(&peerl.lock);
+
+       if (gH323Debug) {
+               if (peer) {
+                       ast_verbose("           found matching friend\n");
+               }
+               ast_verbose("+++   find_friend \"%s\"\n", name);
+       }
+
+       return peer;            
+}
+
+
+struct ooh323_peer *find_peer(const char * name, int port)
+{
+       struct ooh323_peer *peer;
+
+       if (gH323Debug)
+               ast_verbose("---   find_peer \"%s\"\n", name);
+
+       ast_mutex_lock(&peerl.lock);
+       for (peer = peerl.peers; peer; peer = peer->next) {
+               if (gH323Debug) {
+                       ast_verbose("           comparing with \"%s\"\n", peer->ip);
+               }
+               if (!strcasecmp(peer->name, name))
+                       break;
+               if (peer->h323id && !strcasecmp(peer->h323id, name))
+                       break;
+               if (peer->e164 && !strcasecmp(peer->e164, name))
+                       break;
+               /*
+               if (!strcmp(peer->ip, name)) {
+                       if (port > 0 && peer->port == port) { break; }
+                       else if (port <= 0) { break; }
+               }
+               */
+       }
+       ast_mutex_unlock(&peerl.lock);
+
+       if (gH323Debug) {
+               if (peer) {
+                       ast_verbose("           found matching peer\n");
+               }
+               ast_verbose("+++   find_peer \"%s\"\n", name);
+       }
+
+       return peer;            
+}
+
+static int ooh323_digit_begin(struct ast_channel *chan, char digit)
+{
+       char dtmf[2];
+       struct ooh323_pvt *p = (struct ooh323_pvt *) chan->tech_pvt;
+       
+       if (gH323Debug)
+               ast_verbose("---   ooh323_digit_begin\n");
+
+       if (!p) {
+               ast_log(LOG_ERROR, "No private structure for call\n");
+               return -1;
+       }
+       ast_mutex_lock(&p->lock);
+       if (p->rtp && (p->dtmfmode & H323_DTMF_RFC2833)) {
+               ast_rtp_senddigit_begin(p->rtp, digit);
+       } else if (((p->dtmfmode & H323_DTMF_Q931) ||
+                                                (p->dtmfmode & H323_DTMF_H245ALPHANUMERIC) ||
+                                                (p->dtmfmode & H323_DTMF_H245SIGNAL))) {
+               dtmf[0] = digit;
+               dtmf[1] = '\0';
+               ast_mutex_lock(&ooh323c_cmd_lock);
+               ooSendDTMFDigit(p->callToken, dtmf);
+               ast_mutex_unlock(&ooh323c_cmd_lock);
+       }
+       ast_mutex_unlock(&p->lock);
+       if (gH323Debug)
+               ast_verbose("+++   ooh323_digit_begin\n");
+
+       return 0;
+}
+
+static int ooh323_digit_end(struct ast_channel *chan, char digit, unsigned int duration)
+{
+       struct ooh323_pvt *p = (struct ooh323_pvt *) chan->tech_pvt;
+
+       if (gH323Debug)
+               ast_verbose("---   ooh323_digit_end\n");
+
+       if (!p) {
+               ast_log(LOG_ERROR, "No private structure for call\n");
+               return -1;
+       }
+       ast_mutex_lock(&p->lock);
+       if (p->rtp && (p->dtmfmode & H323_DTMF_RFC2833)) 
+               ast_rtp_senddigit_end(p->rtp, digit);
+
+       ast_mutex_unlock(&p->lock);
+       if (gH323Debug)
+               ast_verbose("+++   ooh323_digit_end\n");
+
+       return 0;
+}
+
+
+static int ooh323_call(struct ast_channel *ast, char *dest, int timeout)
+{
+       struct ooh323_pvt *p = ast->tech_pvt;
+       char destination[256];
+       int res = 0;
+       const char *val = NULL;
+       ooCallOptions opts = {
+               .fastStart = TRUE,
+               .tunneling = TRUE,
+               .disableGk = TRUE,
+               .callMode = OO_CALLMODE_AUDIOCALL
+       };
+       if (gH323Debug)
+               ast_verbose("---   ooh323_call- %s\n", dest);
+
+       if ((ast->_state != AST_STATE_DOWN) && (ast->_state != AST_STATE_RESERVED)) {
+               ast_log(LOG_WARNING, "ooh323_call called on %s, neither down nor "
+                                                                       "reserved\n", ast->name);
+               return -1;
+       }
+       ast_mutex_lock(&p->lock);
+       ast_set_flag(p, H323_OUTGOING);
+       if (ast->cid.cid_num) {
+               if (p->callerid_num) {
+                       free(p->callerid_num);
+               }
+               p->callerid_num = strdup(ast->cid.cid_num);
+       }
+
+       if (ast->cid.cid_name) {
+               if (p->callerid_name) {
+                       free(p->callerid_name);
+               }
+               p->callerid_name = strdup(ast->cid.cid_name);
+       }
+       else{
+               ast->cid.cid_name = strdup(gCallerID);
+               if (p->callerid_name) {
+                       free(p->callerid_name);
+               }
+               p->callerid_name = strdup(ast->cid.cid_name);
+       }
+
+       /* Retrieve vars */
+
+
+       if ((val = pbx_builtin_getvar_helper(ast, "CALLER_H323ID"))) {
+               ast_copy_string(p->caller_h323id, val, sizeof(p->caller_h323id));
+       }
+       
+       if ((val = pbx_builtin_getvar_helper(ast, "CALLER_H323DIALEDDIGITS"))) {
+               ast_copy_string(p->caller_dialedDigits, val, sizeof(p->caller_dialedDigits));
+               if (!p->callerid_num) {
+                       p->callerid_num = strdup(val);
+               }
+       }
+
+       if ((val = pbx_builtin_getvar_helper(ast, "CALLER_H323EMAIL"))) {
+               ast_copy_string(p->caller_email, val, sizeof(p->caller_email));
+       }
+
+       if ((val = pbx_builtin_getvar_helper(ast, "CALLER_H323URL"))) {
+               ast_copy_string(p->caller_url, val, sizeof(p->caller_url));
+       }
+
+
+       if (!(p->callToken = (char*)malloc(AST_MAX_EXTENSION))) {
+               ast_mutex_unlock(&p->lock);
+               ast_log(LOG_ERROR, "Failed to allocate memory for callToken\n");
+               return -1; /* TODO: need to clean/hangup?? */
+       }               
+
+       if (p->host && p->port != 0)
+               snprintf(destination, sizeof(destination), "%s:%d", p->host, p->port);
+       else if (p->host)
+               snprintf(destination, sizeof(destination), "%s", p->host);
+       else
+               ast_copy_string(destination, dest, sizeof(destination));
+
+       ast_mutex_lock(&ooh323c_cmd_lock);
+       if (OO_TESTFLAG(p->flags, H323_DISABLEGK))
+               res = ooMakeCall(destination, p->callToken, AST_MAX_EXTENSION, &opts);
+       else
+               res = ooMakeCall(destination, p->callToken, AST_MAX_EXTENSION, NULL);
+       ast_mutex_unlock(&ooh323c_cmd_lock);
+
+       ast_mutex_unlock(&p->lock);
+       if (res != OO_OK) {
+               ast_log(LOG_ERROR, "Failed to make call\n");
+               return -1; /* TODO: cleanup */
+       }
+       if (gH323Debug)
+               ast_verbose("+++   ooh323_call\n");
+
+  return 0;
+}
+
+static int ooh323_hangup(struct ast_channel *ast)
+{
+       struct ooh323_pvt *p = ast->tech_pvt;
+
+       if (gH323Debug)
+               ast_verbose("---   ooh323_hangup\n");
+
+       if (p) {
+               ast_mutex_lock(&p->lock);
+
+               if (gH323Debug)
+                       ast_verbose("    hanging %s\n", p->username);
+               ast->tech_pvt = NULL; 
+               if (!ast_test_flag(p, H323_ALREADYGONE)) {
+                       ast_mutex_lock(&ooh323c_cmd_lock);
+                       ooHangCall(p->callToken, 
+                                ooh323_convert_hangupcause_asteriskToH323(p->owner->hangupcause));
+                       ast_mutex_unlock(&ooh323c_cmd_lock);
+                       ast_set_flag(p, H323_ALREADYGONE);
+                       /* ast_mutex_unlock(&p->lock); */
+               } else {
+                       ast_set_flag(p, H323_NEEDDESTROY);
+               }
+               /* detach channel here */
+               if (p->owner) {
+                       p->owner->tech_pvt = NULL;
+                       p->owner = NULL;
+               }
+
+               ast_mutex_unlock(&p->lock);
+               ast_mutex_lock(&usecnt_lock);
+               usecnt--;
+               ast_mutex_unlock(&usecnt_lock);
+
+               /* Notify the module monitors that use count for resource has changed */
+               ast_update_use_count();
+         
+       } else {
+               ast_log(LOG_ERROR, "No call to hangup\n" );
+               return -1;
+       }
+       
+       if (gH323Debug)
+               ast_verbose("+++   ooh323_hangup\n");
+
+  return 0;
+}
+
+static int ooh323_answer(struct ast_channel *ast)
+{
+       struct ooh323_pvt *p = ast->tech_pvt;
+
+       if (gH323Debug)
+               ast_verbose("--- ooh323_answer\n");
+
+       ast_mutex_lock(&p->lock);
+       if (ast->_state != AST_STATE_UP) {
+               ast_channel_lock(ast);
+               ast_setstate(ast, AST_STATE_UP);
+               ast_debug(1, "ooh323_answer(%s)\n", ast->name);
+               ast_channel_unlock(ast);
+               ast_mutex_lock(&ooh323c_cmd_lock);
+               ooAnswerCall(p->callToken);
+               ast_mutex_unlock(&ooh323c_cmd_lock);
+       }
+       ast_mutex_unlock(&p->lock);
+
+       if (gH323Debug)
+               ast_verbose("+++ ooh323_answer\n");
+
+  return 0;
+}
+
+static struct ast_frame *ooh323_read(struct ast_channel *ast)
+{
+       struct ast_frame *fr;
+       static struct ast_frame null_frame = { AST_FRAME_NULL, };
+       struct ooh323_pvt *p = ast->tech_pvt;
+
+       ast_mutex_lock(&p->lock);
+       if (p->rtp)
+               fr = ooh323_rtp_read(ast, p);
+       else
+               fr = &null_frame;
+       /* time(&p->lastrtprx); */
+       ast_mutex_unlock(&p->lock);
+       return fr;
+}
+
+static int ooh323_write(struct ast_channel *ast, struct ast_frame *f)
+{
+       struct ooh323_pvt *p = ast->tech_pvt;
+       int res = 0;
+
+       if (f->frametype == AST_FRAME_VOICE) {
+               if (!(f->subclass & ast->nativeformats)) {
+                       ast_log(LOG_WARNING, "Asked to transmit frame type %d, while native "
+                                                                         "formats is %d (read/write = %d/%d)\n",
+                                                                       f->subclass, ast->nativeformats, ast->readformat,
+                                                                               ast->writeformat);
+                       return 0;
+               }
+               if (p) {
+                       ast_mutex_lock(&p->lock);
+                       if (p->rtp)
+                               res = ast_rtp_write(p->rtp, f);
+                       ast_mutex_unlock(&p->lock);
+               }
+       } else if (f->frametype == AST_FRAME_IMAGE) {
+               return 0;
+       } else {
+               ast_log(LOG_WARNING, "Can't send %d type frames with SIP write\n", 
+                                                                        f->frametype);
+               return 0;
+       }
+
+       return res;
+}
+
+static int ooh323_indicate(struct ast_channel *ast, int condition, const void *data, size_t datalen)
+{
+
+       struct ooh323_pvt *p = (struct ooh323_pvt *) ast->tech_pvt;
+       char *callToken = (char *)NULL;
+
+       ast_mutex_lock(&p->lock);
+       callToken = (p->callToken ? strdup(p->callToken) : NULL);
+       ast_mutex_unlock(&p->lock);
+
+       if (!callToken) {
+               if (gH323Debug)
+                       ast_verbose("   ooh323_indicate - No callToken\n");
+               return -1;
+       }
+
+       if (gH323Debug)
+               ast_verbose("----- ooh323_indicate %d on call %s\n", condition, callToken);
+        
+
+       switch (condition) {
+       case AST_CONTROL_CONGESTION:
+               if (!ast_test_flag(p, H323_ALREADYGONE)) {
+                       ast_mutex_lock(&ooh323c_cmd_lock);
+                       ooHangCall(callToken, OO_REASON_LOCAL_CONGESTED);
+                       ast_mutex_unlock(&ooh323c_cmd_lock);
+                       ast_set_flag(p, H323_ALREADYGONE);
+               }
+               break;
+       case AST_CONTROL_BUSY:
+               if (!ast_test_flag(p, H323_ALREADYGONE)) {
+                       ast_mutex_lock(&ooh323c_cmd_lock);
+                       ooHangCall(callToken, OO_REASON_LOCAL_BUSY);
+                       ast_mutex_unlock(&ooh323c_cmd_lock);
+                       ast_set_flag(p, H323_ALREADYGONE);
+               }
+               break;
+       case AST_CONTROL_HOLD:
+               ast_moh_start(ast, data, NULL);         
+               break;
+       case AST_CONTROL_UNHOLD:
+               ast_moh_stop(ast);
+               break;
+       case AST_CONTROL_PROCEEDING:
+       case AST_CONTROL_RINGING:
+       case AST_CONTROL_PROGRESS:
+       case -1:
+               break;
+       default:
+               ast_log(LOG_WARNING, "Don't know how to indicate condition %d on %s\n",
+                                                                       condition, callToken);
+       }
+
+       if (gH323Debug)
+               ast_verbose("++++  ooh323_indicate %d on %s\n", condition, callToken);
+
+
+       return -1;
+}
+
+static int ooh323_fixup(struct ast_channel *oldchan, struct ast_channel *newchan)
+{
+       struct ooh323_pvt *p = newchan->tech_pvt;
+
+       if (gH323Debug)
+               ast_verbose("--- ooh323c ooh323_fixup\n");
+
+       ast_mutex_lock(&p->lock);
+       if (p->owner != oldchan) {
+               ast_log(LOG_WARNING, "Old channel wasn't %p but was %p\n", oldchan, p->owner);
+               ast_mutex_unlock(&p->lock);
+               return -1;
+       }
+
+       if (p->owner == oldchan) {
+               p->owner = newchan;
+       } else {
+               p->owner = oldchan;
+       }
+
+       ast_mutex_unlock(&p->lock);
+
+       if (gH323Debug)
+               ast_verbose("+++ ooh323c ooh323_fixup \n");
+
+       return 0;
+}
+
+
+void ooh323_set_write_format(ooCallData *call, int fmt)
+{
+#if 0
+       struct ooh323_pvt *p = NULL;
+       char formats[512];
+#ifdef print_debug
+       printf("---   ooh323_update_writeformat %s\n", 
+                                                                               ast_getformatname_multiple(formats,512, fmt));
+#endif
+       
+       p = find_call(call);
+       if (!p) {
+               ast_log(LOG_ERROR, "No matching call found for %s\n", call->callToken);
+               return;
+       }
+
+       ast_mutex_lock(&p->lock);
+
+       p->writeformat = fmt;
+       ast_mutex_unlock(&p->lock);
+
+       if (p->owner) {
+         printf("Writeformat before update %s\n", 
+                                 ast_getformatname_multiple(formats,512, p->owner->writeformat));
+         ast_set_write_format(p->owner, fmt);
+       }
+       else
+         ast_log(LOG_ERROR, "No owner found\n");
+
+
+#ifdef print_debug
+  printf("+++   ooh323_update_writeformat\n");
+#endif
+#endif
+}
+
+
+void ooh323_set_read_format(ooCallData *call, int fmt)
+{
+#if 0
+       struct ooh323_pvt *p = NULL;
+       char formats[512];
+#ifdef print_debug
+       printf("---   ooh323_update_readformat %s\n", 
+                                                                                       ast_getformatname_multiple(formats,512, fmt));
+#endif
+
+       p = find_call(call);
+       if (!p) {
+               ast_log(LOG_ERROR, "No matching call found for %s\n", call->callToken);
+               return;
+       }
+
+       ast_mutex_lock(&p->lock);
+       p->readformat = fmt;
+       ast_mutex_unlock(&p->lock);
+       ast_set_read_format(p->owner, fmt);     
+       
+#ifdef print_debug
+  printf("+++   ooh323_update_readformat\n");
+#endif
+#endif
+}
+
+int onAlerting(ooCallData *call)
+{
+       struct ooh323_pvt *p = NULL;
+       struct ast_channel *c = NULL;
+
+       if (gH323Debug)
+               ast_verbose("--- onAlerting %s\n", call->callToken);
+
+       if (!(p = find_call(call))) {
+               ast_log(LOG_ERROR, "No matching call found\n");
+               return -1;
+       }  
+       ast_mutex_lock(&p->lock);
+       if (!ast_test_flag(p, H323_OUTGOING)) {
+               if (!(c = ooh323_new(p, AST_STATE_RING, p->username))) {
+                       ast_mutex_unlock(&p->lock);
+                       ast_log(LOG_ERROR, "Could not create ast_channel\n");
+                       return -1;
+               }
+               ast_mutex_unlock(&p->lock);
+       } else {
+               if (!p->owner) {
+                       ast_mutex_unlock(&p->lock);
+                       ast_log(LOG_ERROR, "Channel has no owner\n");
+                       return 0;
+               }
+               c = p->owner;
+               ast_mutex_unlock(&p->lock);
+               ast_channel_lock(c);
+               ast_setstate(c, AST_STATE_RINGING);
+               ast_channel_unlock(c);
+               ast_queue_control(c, AST_CONTROL_RINGING);
+       }
+
+       if (gH323Debug)
+               ast_verbose("+++ onAlerting %s\n", call->callToken);
+
+       return OO_OK;
+}
+
+/**
+  * Callback for sending digits from H.323 up to asterisk
+  *
+  */
+int ooh323_onReceivedDigit(OOH323CallData *call, const char *digit)
+{
+       struct ooh323_pvt *p = NULL;
+       struct ast_frame f;
+       int res;
+
+       ast_debug(1, "Received Digit: %c\n", digit[0]);
+       p = find_call(call);
+       if (!p) {
+               ast_log(LOG_ERROR, "Failed to find a matching call.\n");
+               return -1;
+       }
+       if (!p->owner) {
+               ast_log(LOG_ERROR, "Channel has no owner\n");
+               return -1;
+       }
+       ast_mutex_lock(&p->lock);
+       memset(&f, 0, sizeof(f));
+       f.frametype = AST_FRAME_DTMF;
+       f.subclass = digit[0];
+       f.datalen = 0;
+       f.samples = 800;
+       f.offset = 0;
+       f.data.ptr = NULL;
+       f.mallocd = 0;
+       f.src = "SEND_DIGIT";
+       ast_mutex_unlock(&p->lock);
+       res = ast_queue_frame(p->owner, &f);
+       return res;
+}
+
+int ooh323_onReceivedSetup(ooCallData *call, Q931Message *pmsg)
+{
+       struct ooh323_pvt *p = NULL;
+       struct ooh323_user *user = NULL;
+       ooAliases *alias = NULL;
+       char *at = NULL;
+       char number [OO_MAX_NUMBER_LENGTH];
+
+       if (gH323Debug)
+               ast_verbose("---   ooh323_onReceivedSetup %s\n", call->callToken);
+
+
+       if (!(p = ooh323_alloc(call->callReference, call->callToken))) {
+               ast_log(LOG_ERROR, "Failed to create a new call.\n");
+               return -1;
+       }
+       ast_mutex_lock(&p->lock);
+       ast_clear_flag(p, H323_OUTGOING);
+  
+
+       if (call->remoteDisplayName) {
+               p->callerid_name = strdup(call->remoteDisplayName);
+       }
+
+       if (ooCallGetCallingPartyNumber(call, number, OO_MAX_NUMBER_LENGTH) == OO_OK) {
+               p->callerid_num = strdup(number);
+       }
+
+       if (call->remoteAliases) {
+               for (alias = call->remoteAliases; alias; alias = alias->next) {
+                       if (alias->type == T_H225AliasAddress_h323_ID) {
+                               if (!p->callerid_name) {
+                                       p->callerid_name = strdup(alias->value);
+                               }
+                               ast_copy_string(p->caller_h323id, alias->value, sizeof(p->caller_h323id));
+                       } else if (alias->type == T_H225AliasAddress_dialedDigits) {
+                               if (!p->callerid_num) {
+                                       p->callerid_num = strdup(alias->value);
+                               }
+                               ast_copy_string(p->caller_dialedDigits, alias->value, 
+                                                                                                                       sizeof(p->caller_dialedDigits));
+                       } else if (alias->type == T_H225AliasAddress_email_ID) {
+                               ast_copy_string(p->caller_email, alias->value, sizeof(p->caller_email));
+                       } else if (alias->type == T_H225AliasAddress_url_ID) {
+                               ast_copy_string(p->caller_url, alias->value, sizeof(p->caller_url));
+                       }
+               }
+       }
+
+       number[0] = '\0';
+       if (ooCallGetCalledPartyNumber(call, number, OO_MAX_NUMBER_LENGTH) == OO_OK) {
+               ast_copy_string(p->exten, number, sizeof(p->exten));
+       } else {
+               update_our_aliases(call, p);
+               if (!ast_strlen_zero(p->callee_dialedDigits)) {
+                       ast_copy_string(p->exten, p->callee_dialedDigits, sizeof(p->exten));
+               } else if (!ast_strlen_zero(p->callee_h323id)) {
+                       ast_copy_string(p->exten, p->callee_h323id, sizeof(p->exten));
+               } else if (!ast_strlen_zero(p->callee_email)) {
+                       ast_copy_string(p->exten, p->callee_email, sizeof(p->exten));
+                       if ((at = strchr(p->exten, '@'))) {
+                               *at = '\0';
+                       }
+               }
+       }
+
+       /* if no extension found, set to default 's' */
+       if (ast_strlen_zero(p->exten)) {
+               ast_copy_string(p->exten, "s", sizeof(p->exten));
+       }
+
+       if (!p->callerid_name) {
+               p->callerid_name = strdup(call->remoteIP);
+       }
+       
+       if (p->callerid_name) {
+               if ((user = find_user(p->callerid_name, call->remoteIP))) {
+                       ast_mutex_lock(&user->lock);
+                       p->username = strdup(user->name);
+                       ast_copy_string(p->context, user->context, sizeof(p->context));
+                       ast_copy_string(p->accountcode, user->accountcode, sizeof(p->accountcode));
+                       p->amaflags = user->amaflags;
+                       p->capability = user->capability;
+                       memcpy(&p->prefs, &user->prefs, sizeof(struct ast_codec_pref));
+                       p->dtmfmode = user->dtmfmode;
+                       /* Since, call is coming from a pbx user, no need to use gk */
+                       OO_SETFLAG(p->flags, H323_DISABLEGK);
+                       OO_SETFLAG(call->flags, OO_M_DISABLEGK);
+                       ast_mutex_unlock(&user->lock);
+               }
+       }
+
+
+       ooh323c_set_capability_for_call(call, &p->prefs, p->capability, p->dtmfmode);
+       configure_local_rtp(p, call);
+
+       ast_mutex_unlock(&p->lock);
+
+       if (gH323Debug)
+               ast_verbose("+++   ooh323_onReceivedSetup - Determined context %s, "
+                                               "extension %s\n", p->context, p->exten);
+
+       return OO_OK;
+}
+
+
+
+int onNewCallCreated(ooCallData *call)
+{
+       struct ooh323_pvt *p = NULL;
+       int i = 0;
+
+       if (gH323Debug)
+               ast_verbose("---   onNewCallCreated %s\n", call->callToken);
+
+       if (!strcmp(call->callType, "outgoing")) {
+               p = find_call(call);
+               if (!p) {
+                       ast_log(LOG_ERROR, "No matching call found for outgoing call\n");
+                       return -1;
+               }
+               ast_mutex_lock(&p->lock);
+               if (p->callerid_name) {
+                       ooCallSetCallerId(call, p->callerid_name);
+               }
+               if (p->callerid_num) {
+                       i = 0;
+                       while (*(p->callerid_num + i) != '\0') {
+                               if (!isdigit(*(p->callerid_num + i))) {
+                                       break;
+                               }
+                               i++;
+                       }
+                       if (*(p->callerid_num + i) == '\0') {
+                               ooCallSetCallingPartyNumber(call, p->callerid_num);
+                       } else {
+                               if (!p->callerid_name) {
+                                       ooCallSetCallerId(call, p->callerid_num);
+                               }
+                       }
+               }
+               
+               if (!ast_strlen_zero(p->caller_h323id))
+                       ooCallAddAliasH323ID(call, p->caller_h323id);
+
+               if (!ast_strlen_zero(p->caller_dialedDigits)) {
+                       if (gH323Debug) {
+                               ast_verbose("Setting dialed digits %s\n", p->caller_dialedDigits);
+                       }
+                       ooCallAddAliasDialedDigits(call, p->caller_dialedDigits);
+               } else if (p->callerid_num) {
+                       if (ooIsDailedDigit(p->callerid_num)) {
+                               if (gH323Debug) {
+                                       ast_verbose("setting callid number %s\n", p->callerid_num);
+                               }
+                               ooCallAddAliasDialedDigits(call, p->callerid_num);
+                       } else if (ast_strlen_zero(p->caller_h323id)) {
+                               ooCallAddAliasH323ID(call, p->callerid_num);
+                       }
+               }
+  
+
+               if (!ast_strlen_zero(p->exten))  {
+                       if (ooIsDailedDigit(p->exten)) {
+                               ooCallSetCalledPartyNumber(call, p->exten);
+                               ooCallAddRemoteAliasDialedDigits(call, p->exten);
+                       } else {
+                         ooCallAddRemoteAliasH323ID(call, p->exten);
+                       }
+               }
+
+               if (gH323Debug) {
+                       char prefsBuf[256];
+                       ast_codec_pref_string(&p->prefs, prefsBuf, sizeof(prefsBuf));
+                       ast_verbose(" Outgoing call %s(%s) - Codec prefs - %s\n", 
+                                                 p->username?p->username:"NULL", call->callToken, prefsBuf);
+               }
+
+               ooh323c_set_capability_for_call(call, &p->prefs, p->capability, p->dtmfmode);
+
+               configure_local_rtp(p, call);
+               ast_mutex_unlock(&p->lock);
+       }
+       if (gH323Debug)
+               ast_verbose("+++   onNewCallCreated %s\n", call->callToken);
+
+       return OO_OK;
+}
+
+int onCallEstablished(ooCallData *call)
+{
+       struct ooh323_pvt *p = NULL;
+
+       if (gH323Debug)
+               ast_verbose("---   onCallEstablished %s\n", call->callToken);
+
+       if (!(p = find_call(call))) {
+               ast_log(LOG_ERROR, "Failed to find a matching call.\n");
+               return -1;
+       }
+       ast_mutex_lock(&p->lock);
+       if (!p->owner) {
+               ast_mutex_unlock(&p->lock);
+               ast_log(LOG_ERROR, "Channel has no owner\n");
+               return -1;
+       }
+       
+       while (ast_channel_trylock(p->owner)) {
+               ast_debug(1,"Failed to grab lock, trying again\n");
+               ast_mutex_unlock(&p->lock);
+               usleep(1);
+               ast_mutex_lock(&p->lock);
+       }        
+       if (p->owner->_state != AST_STATE_UP) {
+               ast_setstate(p->owner, AST_STATE_UP);
+       }
+       ast_channel_unlock(p->owner);
+       if (ast_test_flag(p, H323_OUTGOING)) {
+               struct ast_channel* c = p->owner;
+               ast_mutex_unlock(&p->lock);
+               ast_queue_control(c, AST_CONTROL_ANSWER);
+       } else {
+               ast_mutex_unlock(&p->lock);
+       }
+
+       if (gH323Debug)
+               ast_verbose("+++   onCallEstablished %s\n", call->callToken);
+
+       return OO_OK;
+}
+
+int onCallCleared(ooCallData *call)
+{
+       struct ooh323_pvt *p = NULL;
+       int ownerLock = 0;
+
+       if (gH323Debug)
+               ast_verbose("---   onCallCleared %s \n", call->callToken);
+
+       p = find_call(call); 
+       if (!p) {
+               return 0;
+       }
+       ast_mutex_lock(&p->lock);
+  
+       while (p->owner) {
+               if (ast_channel_trylock(p->owner)) {
+                       ooTrace(OOTRCLVLINFO, "Failed to grab lock, trying again\n");
+                       ast_debug(1,"Failed to grab lock, trying again\n");
+                       ast_mutex_unlock(&p->lock);
+                       usleep(1);
+                       ast_mutex_lock(&p->lock);
+               } else {
+                       ownerLock = 1;
+                       break;
+               }
+       }
+
+       if (ownerLock) {
+               if (!ast_test_flag(p, H323_ALREADYGONE)) { 
+
+                       /* NOTE: Channel is not detached yet */
+                       ast_set_flag(p, H323_ALREADYGONE);
+                       p->owner->hangupcause = 
+                               ooh323_convert_hangupcause_h323ToAsterisk(call->callEndReason);
+                       p->owner->_softhangup |= AST_SOFTHANGUP_DEV;
+                       ast_channel_unlock(p->owner);
+                       ast_queue_hangup(p->owner);
+                       ast_mutex_unlock(&p->lock);
+                       return OO_OK;
+               }
+               ast_channel_unlock(p->owner);
+       }
+       ast_set_flag(p, H323_NEEDDESTROY);
+       ast_mutex_unlock(&p->lock);
+
+       if (gH323Debug)
+               ast_verbose("+++   onCallCleared\n");
+
+       return OO_OK;
+}
+
+#if 0
+static void ooh323_delete_user(struct ooh323_user *user)
+{
+       struct ooh323_user *prev = NULL, *cur = NULL;
+
+       if (gH323Debug)
+               ast_verbose("---   ooh323_delete_user\n");
+
+       if (user) {     
+               cur = userl.users;
+               ast_mutex_lock(&userl.lock);
+               while (cur) {
+                       if (cur == user) break;
+                       prev = cur;
+                       cur = cur->next;
+               }
+
+               if (cur) {
+                       if (prev)
+                               prev->next = cur->next;
+                       else
+                               userl.users = cur->next;
+               }
+               ast_mutex_unlock(&userl.lock);
+
+               free(user);
+       }  
+
+       if (gH323Debug)
+               ast_verbose("+++   ooh323_delete_user\n");
+
+}
+#endif
+
+void ooh323_delete_peer(struct ooh323_peer *peer)
+{
+       struct ooh323_peer *prev = NULL, *cur = NULL;
+
+       if (gH323Debug)
+               ast_verbose("---   ooh323_delete_peer\n");
+
+       if (peer) {     
+               ast_mutex_lock(&peerl.lock);
+               for (cur = peerl.peers; cur; prev = cur, cur = cur->next) {
+                       if (cur == peer) {
+                               break;
+                       }
+               }
+
+               if (cur) {
+                       if (prev) {
+                               prev->next = cur->next;
+                       } else {
+                               peerl.peers = cur->next;
+                       }
+               }
+               ast_mutex_unlock(&peerl.lock);
+
+               if (peer->h323id)
+                       free(peer->h323id);
+               if (peer->email)
+                       free(peer->email);
+               if (peer->url)
+                       free(peer->url);
+               if (peer->e164)
+                       free(peer->e164);
+
+               free(peer);
+       }  
+
+       if (gH323Debug)
+               ast_verbose("+++   ooh323_delete_peer\n");
+}
+
+
+
+static struct ooh323_user *build_user(const char *name, struct ast_variable *v)
+{
+       struct ooh323_user *user = NULL;
+
+       if (gH323Debug)
+               ast_verbose("---   build_user\n");
+
+       user = ast_calloc(1, sizeof(*user));
+       if (user) {
+               ast_mutex_init(&user->lock);
+               ast_copy_string(user->name, name, sizeof(user->name));
+               user->capability = gCapability;
+               memcpy(&user->prefs, &gPrefs, sizeof(user->prefs));
+               user->rtptimeout = gRTPTimeout;
+               user->dtmfmode = gDTMFMode;
+               /* set default context */
+               ast_copy_string(user->context, gContext, sizeof(user->context));
+               ast_copy_string(user->accountcode, gAccountcode, sizeof(user->accountcode));
+               user->amaflags = gAMAFLAGS;
+
+               while (v) {
+                       if (!strcasecmp(v->name, "context")) {
+                               ast_copy_string(user->context, v->value, sizeof(user->context));
+                       } else if (!strcasecmp(v->name, "incominglimit")) {
+                               user->incominglimit = atoi(v->value);
+                               if (user->incominglimit < 0)
+                                       user->incominglimit = 0;
+                       } else if (!strcasecmp(v->name, "accountcode")) {
+                               ast_copy_string(user->accountcode, v->value, sizeof(user->accountcode));
+                       } else if (!strcasecmp(v->name, "rtptimeout")) {
+                               user->rtptimeout = atoi(v->value);
+                               if (user->rtptimeout < 0)
+                                       user->rtptimeout = gRTPTimeout;
+                       } else if (!strcasecmp(v->name, "disallow")) {
+                               ast_parse_allow_disallow(&user->prefs, &user->capability, 
+                                                                                                v->value, 0);
+                       } else if (!strcasecmp(v->name, "allow")) {
+                               const char* tcodecs = v->value;
+                               if (!strcasecmp(v->value, "all")) {
+                                       tcodecs = "ulaw,alaw,g729,g723,gsm";
+                               }
+                               ast_parse_allow_disallow(&user->prefs, &user->capability, 
+                                                                                                tcodecs, 1);
+                       } else if (!strcasecmp(v->name, "amaflags")) {
+                               user->amaflags = ast_cdr_amaflags2int(v->value);
+                       } else if (!strcasecmp(v->name, "ip")) {
+                               ast_copy_string(user->mIP, v->value, sizeof(user->mIP));
+                               user->mUseIP = 1;
+                       } else if (!strcasecmp(v->name, "dtmfmode")) {
+                               if (!strcasecmp(v->value, "rfc2833"))
+                                       user->dtmfmode = H323_DTMF_RFC2833;
+                               else if (!strcasecmp(v->value, "q931keypad"))
+                                       user->dtmfmode = H323_DTMF_Q931;
+                               else if (!strcasecmp(v->value, "h245alphanumeric"))
+                                       user->dtmfmode = H323_DTMF_H245ALPHANUMERIC;
+                               else if (!strcasecmp(v->value, "h245signal"))
+                                       user->dtmfmode = H323_DTMF_H245SIGNAL;
+                       }
+                       v = v->next;
+               }
+       }
+
+       if (gH323Debug)
+               ast_verbose("+++   build_user\n");
+
+       return user;
+}
+
+static struct ooh323_peer *build_peer(const char *name, struct ast_variable *v, int friend_type)
+{
+       struct ooh323_peer *peer = NULL;
+
+       if (gH323Debug)
+               ast_verbose("---   build_peer\n");
+
+       peer = ast_calloc(1, sizeof(*peer));
+       if (peer) {
+               memset(peer, 0, sizeof(struct ooh323_peer));
+               ast_mutex_init(&peer->lock);
+               ast_copy_string(peer->name, name, sizeof(peer->name));
+               peer->capability = gCapability;
+               memcpy(&peer->prefs, &gPrefs, sizeof(struct ast_codec_pref));
+               peer->rtptimeout = gRTPTimeout;
+               ast_copy_string(peer->accountcode, gAccountcode, sizeof(peer->accountcode));
+               peer->amaflags = gAMAFLAGS;
+               peer->dtmfmode = gDTMFMode;
+               if (0 == friend_type) {
+                       peer->mFriend = 1;
+               }
+
+               while (v) {
+                       if (!strcasecmp(v->name, "h323id")) {
+                               if (!(peer->h323id = ast_strdup(v->value))) {
+                                       ast_log(LOG_ERROR, "Could not allocate memory for h323id of "
+                                                                                        "peer %s\n", name);
+                                       ooh323_delete_peer(peer);
+                                       return NULL;
+                               }
+                       } else if (!strcasecmp(v->name, "e164")) {
+                               if (!(peer->e164 = ast_strdup(v->value))) {
+                                       ast_log(LOG_ERROR, "Could not allocate memory for e164 of "
+                                                                                        "peer %s\n", name);
+                                       ooh323_delete_peer(peer);
+                                       return NULL;
+                               }
+                       } else  if (!strcasecmp(v->name, "email")) {
+                               if (!(peer->email = ast_strdup(v->value))) {
+                                       ast_log(LOG_ERROR, "Could not allocate memory for email of "
+                                                                                        "peer %s\n", name);
+                                       ooh323_delete_peer(peer);
+                                       return NULL;
+                               }
+                       } else if (!strcasecmp(v->name, "url")) {
+                               if (!(peer->url = ast_strdup(v->value))) {
+                                       ast_log(LOG_ERROR, "Could not allocate memory for h323id of "
+                                                                                        "peer %s\n", name);
+                                       ooh323_delete_peer(peer);
+                                       return NULL;
+                               }
+                       } else if (!strcasecmp(v->name, "port")) {
+                               peer->port = atoi(v->value);
+                       } else if (!strcasecmp(v->name, "ip")) {
+                               ast_copy_string(peer->ip, v->value, sizeof(peer->ip));
+                       } else if (!strcasecmp(v->name, "outgoinglimit")) {
+                               if ((peer->outgoinglimit = atoi(v->value)) < 0) {
+                                       peer->outgoinglimit = 0;
+                               }
+                       } else if (!strcasecmp(v->name, "accountcode")) {
+                               ast_copy_string(peer->accountcode, v->value, sizeof(peer->accountcode));
+                       } else if (!strcasecmp(v->name, "rtptimeout")) {
+                               if ((peer->rtptimeout = atoi(v->value)) < 0) {
+                                       peer->rtptimeout = gRTPTimeout;
+                               }
+                       } else if (!strcasecmp(v->name, "disallow")) {
+                               ast_parse_allow_disallow(&peer->prefs, &peer->capability, 
+                                                                                                v->value, 0); 
+                       } else if (!strcasecmp(v->name, "allow")) {
+                               const char* tcodecs = v->value;
+                               if (!strcasecmp(v->value, "all")) {
+                                       tcodecs = "ulaw,alaw,g729,g723,gsm";
+                               }
+                               ast_parse_allow_disallow(&peer->prefs, &peer->capability, 
+                                                                                                tcodecs, 1);                            
+                       } else if (!strcasecmp(v->name,  "amaflags")) {
+                               peer->amaflags = ast_cdr_amaflags2int(v->value);
+                       } else if (!strcasecmp(v->name, "dtmfmode")) {
+                               if (!strcasecmp(v->value, "rfc2833"))
+                                       peer->dtmfmode = H323_DTMF_RFC2833;
+                               else if (!strcasecmp(v->value, "q931keypad"))
+                                       peer->dtmfmode = H323_DTMF_Q931;
+                               else if (!strcasecmp(v->value, "h245alphanumeric"))
+                                       peer->dtmfmode = H323_DTMF_H245ALPHANUMERIC;
+                               else if (!strcasecmp(v->value, "h245signal"))
+                                       peer->dtmfmode = H323_DTMF_H245SIGNAL;
+                       }
+                       v = v->next;
+               }
+       }
+
+       if (gH323Debug)
+               ast_verbose("+++   build_peer\n");
+
+       return peer;
+}
+
+static int ooh323_do_reload(void)
+{
+       if (gH323Debug) {
+               ast_verbose("---   ooh323_do_reload\n");
+       }
+
+       reload_config(1);
+
+       if (gH323Debug) {
+               ast_verbose("+++   ooh323_do_reload\n");
+       }
+
+       return 0;
+}
+
+#if 0
+/*--- h323_reload: Force reload of module from cli ---*/
+static int ooh323_reload(int fd, int argc, char *argv[])
+{
+
+       if (gH323Debug)
+               ast_verbose("---   ooh323_reload\n");
+
+       ast_mutex_lock(&h323_reload_lock);
+       if (h323_reloading) {
+               ast_verbose("Previous OOH323 reload not yet done\n");
+       } 
+       else {
+               h323_reloading = 1;
+       }
+       ast_mutex_unlock(&h323_reload_lock);
+       restart_monitor();
+
+       if (gH323Debug)
+               ast_verbose("+++   ooh323_reload\n");
+
+       return 0;
+}
+#endif
+
+#if 0
+static int reload(void *mod)
+{
+       return ooh323_reload(0, 0, NULL);
+}
+#endif
+
+int reload_config(int reload)
+{
+       int format;
+       struct ooAliases  *pNewAlias = NULL;
+       struct ast_config *cfg;
+       struct ast_variable *v;
+       struct ast_flags config_flags = { reload ? CONFIG_FLAG_FILEUNCHANGED : 0 };
+       struct ooh323_user *user = NULL;
+       struct ooh323_peer *peer = NULL;
+       char *cat;
+       const char *utype;
+
+       if (gH323Debug)
+               ast_verbose("---   reload_config\n");
+
+       cfg = ast_config_load((char*)config, config_flags);
+
+       /* We *must* have a config file otherwise stop immediately */
+       if (!cfg) {
+               ast_log(LOG_NOTICE, "Unable to load config %s, OOH323 disabled\n", config);
+               return 1;
+       } else if (cfg == CONFIG_STATUS_FILEUNCHANGED)
+               return RESULT_SUCCESS;
+
+       if (reload) {
+               delete_users();
+               delete_peers();
+       }
+
+       /* Inintialize everything to default */
+       strcpy(gLogFile, DEFAULT_LOGFILE);
+       gPort = 1720;
+       gIP[0] = '\0';
+       strcpy(gCallerID, DEFAULT_H323ID);
+       gCapability = AST_FORMAT_ULAW;
+       memset(&gPrefs, 0, sizeof(struct ast_codec_pref));
+       gDTMFMode = H323_DTMF_RFC2833;
+       gRasGkMode = RasNoGatekeeper;
+       gGatekeeper[0] = '\0';
+       gRTPTimeout = 60;
+       strcpy(gAccountcode, DEFAULT_H323ACCNT);
+       gFastStart = 1;
+       gTunneling = 1;
+       gTOS = 0;
+       strcpy(gContext, DEFAULT_CONTEXT);
+       gAliasList = NULL;
+       gMediaWaitForConnect = 0;
+       ooconfig.mTCPPortStart = 12030;
+       ooconfig.mTCPPortEnd = 12230;
+
+       v = ast_variable_browse(cfg, "general");
+       while (v) {
+       
+               if (!strcasecmp(v->name, "port")) {
+                       gPort = (int)strtol(v->value, NULL, 10);
+               } else if (!strcasecmp(v->name, "bindaddr")) {
+                       ast_copy_string(gIP, v->value, sizeof(gIP));
+               } else if (!strcasecmp(v->name, "h225portrange")) {
+                       char* endlimit = 0;
+                       char temp[256];
+                       ast_copy_string(temp, v->value, sizeof(temp));
+                       endlimit = strchr(temp, ',');
+                       if (endlimit) {
+                               *endlimit = '\0';
+                               endlimit++;
+                               ooconfig.mTCPPortStart = atoi(temp);
+                               ooconfig.mTCPPortEnd = atoi(endlimit);
+
+                &nbs