Merge "res_pjsip: New endpoint option "refer_blind_progress""
[asterisk/asterisk.git] / res / res_smdi.c
index 8687dac..e2e5b17 100644 (file)
@@ -1,9 +1,10 @@
 /*
- * Asterisk -- A telephony toolkit for Linux.
+ * Asterisk -- An open source telephony toolkit.
  *
- * Copyright (C) 2005-2006, Digium, Inc.
+ * Copyright (C) 2005-2008, Digium, Inc.
  *
  * Matthew A. Nicholson <mnicholson@digium.com>
+ * Russell Bryant <russell@digium.com>
  *
  * See http://www.asterisk.org for more information about
  * the Asterisk project. Please do not directly contact
  * \file
  * \brief SMDI support for Asterisk.
  * \author Matthew A. Nicholson <mnicholson@digium.com>
+ * \author Russell Bryant <russell@digium.com>
+ *
+ * Here is a useful mailing list post that describes SMDI protocol details:
+ * http://lists.digium.com/pipermail/asterisk-dev/2003-June/000884.html
+ *
+ * \todo This module currently has its own mailbox monitoring thread.  This should
+ * be converted to MWI subscriptions and just let the optional global voicemail
+ * polling thread handle it.
+ */
+
+/*! \li \ref res_smdi.c uses the configuration file \ref smdi.conf
+ * \addtogroup configuration_file Configuration Files
+ */
+
+/*!
+ * \page smdi.conf smdi.conf
+ * \verbinclude smdi.conf.sample
  */
 
-#include <stdio.h>
-#include <stdlib.h>
-#include <errno.h>
+/*** MODULEINFO
+       <support_level>core</support_level>
+ ***/
+
+#include "asterisk.h"
+
 #include <termios.h>
 #include <sys/time.h>
 #include <time.h>
 #include <ctype.h>
 
-#include "asterisk.h"
-
-ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
-
 #include "asterisk/module.h"
 #include "asterisk/lock.h"
 #include "asterisk/utils.h"
+#define AST_API_MODULE
 #include "asterisk/smdi.h"
 #include "asterisk/config.h"
-#include "asterisk/astobj.h"
 #include "asterisk/io.h"
-#include "asterisk/logger.h"
-#include "asterisk/utils.h"
-#include "asterisk/options.h"
+#include "asterisk/stringfields.h"
+#include "asterisk/linkedlists.h"
+#include "asterisk/app.h"
+#include "asterisk/pbx.h"
+#include "asterisk/channel.h"
 
 /* Message expiry time in milliseconds */
 #define SMDI_MSG_EXPIRY_TIME   30000 /* 30 seconds */
 
-static const char tdesc[] = "Asterisk Simplified Message Desk Interface (SMDI) Module";
+/*** DOCUMENTATION
+
+       <function name="SMDI_MSG_RETRIEVE" language="en_US">
+               <synopsis>
+                       Retrieve an SMDI message.
+               </synopsis>
+               <syntax>
+                       <parameter name="smdi port" required="true" />
+                       <parameter name="search key" required="true" />
+                       <parameter name="timeout" />
+                       <parameter name="options">
+                               <enumlist>
+                                       <enum name="t">
+                                               <para>Instead of searching on the forwarding station, search on the message desk terminal.</para>
+                                       </enum>
+                                       <enum name="n">
+                                               <para>Instead of searching on the forwarding station, search on the message desk number.</para>
+                                       </enum>
+                               </enumlist>
+                       </parameter>
+               </syntax>
+               <description>
+                       <para>This function is used to retrieve an incoming SMDI message. It returns
+                       an ID which can be used with the SMDI_MSG() function to access details of
+                       the message.  Note that this is a destructive function in the sense that
+                       once an SMDI message is retrieved using this function, it is no longer in
+                       the global SMDI message queue, and can not be accessed by any other Asterisk
+                       channels.  The timeout for this function is optional, and the default is
+                       3 seconds.  When providing a timeout, it should be in milliseconds.
+                       </para>
+                       <para>The default search is done on the forwarding station ID. However, if
+                       you set one of the search key options in the options field, you can change
+                       this behavior.
+                       </para>
+               </description>
+               <see-also>
+                       <ref type="function">SMDI_MSG</ref>
+               </see-also>
+       </function>
+       <function name="SMDI_MSG" language="en_US">
+               <synopsis>
+                       Retrieve details about an SMDI message.
+               </synopsis>
+               <syntax>
+                       <parameter name="message_id" required="true" />
+                       <parameter name="component" required="true">
+                               <para>Valid message components are:</para>
+                               <enumlist>
+                                       <enum name="number">
+                                               <para>The message desk number</para>
+                                       </enum>
+                                       <enum name="terminal">
+                                               <para>The message desk terminal</para>
+                                       </enum>
+                                       <enum name="station">
+                                               <para>The forwarding station</para>
+                                       </enum>
+                                       <enum name="callerid">
+                                               <para>The callerID of the calling party that was forwarded</para>
+                                       </enum>
+                                       <enum name="type">
+                                               <para>The call type.  The value here is the exact character
+                                               that came in on the SMDI link.  Typically, example values
+                                               are:</para>
+                                               <para>Options:</para>
+                                               <enumlist>
+                                                       <enum name="D">
+                                                               <para>Direct Calls</para>
+                                                       </enum>
+                                                       <enum name="A">
+                                                               <para>Forward All Calls</para>
+                                                       </enum>
+                                                       <enum name="B">
+                                                               <para>Forward Busy Calls</para>
+                                                       </enum>
+                                                       <enum name="N">
+                                                               <para>Forward No Answer Calls</para>
+                                                       </enum>
+                                               </enumlist>
+                                       </enum>
+                               </enumlist>
+                       </parameter>
+               </syntax>
+               <description>
+                       <para>This function is used to access details of an SMDI message that was
+                       pulled from the incoming SMDI message queue using the SMDI_MSG_RETRIEVE()
+                       function.</para>
+               </description>
+               <see-also>
+                       <ref type="function">SMDI_MSG_RETRIEVE</ref>
+               </see-also>
+       </function>
+ ***/
+
 static const char config_file[] = "smdi.conf";
+static int smdi_loaded;
+
+struct ast_smdi_interface {
+       char name[SMDI_MAX_FILENAME_LEN];
+       struct ao2_container *md_q;
+       ast_mutex_t md_q_lock;
+       ast_cond_t md_q_cond;
+       struct ao2_container *mwi_q;
+       ast_mutex_t mwi_q_lock;
+       ast_cond_t mwi_q_cond;
+       FILE *file;
+       int fd;
+       pthread_t thread;
+       struct termios mode;
+       int msdstrip;
+       long msg_expiry;
+};
+
+static AO2_GLOBAL_OBJ_STATIC(smdi_ifaces);
+
+/*! \brief A mapping between an SMDI mailbox ID and an Asterisk mailbox */
+struct mailbox_mapping {
+       /*! This is the current state of the mailbox.  It is simply on or
+        *  off to indicate if there are messages waiting or not. */
+       unsigned int cur_state:1;
+       /*! A Pointer to the appropriate SMDI interface */
+       struct ast_smdi_interface *iface;
+       AST_DECLARE_STRING_FIELDS(
+               /*! The Name of the mailbox for the SMDI link. */
+               AST_STRING_FIELD(smdi);
+               /*! The name of the mailbox on the Asterisk side */
+               AST_STRING_FIELD(mailbox);
+               /*! The name of the voicemail context in use */
+               AST_STRING_FIELD(context);
+       );
+       AST_LIST_ENTRY(mailbox_mapping) entry;
+};
+
+/*! 10 seconds */
+#define DEFAULT_POLLING_INTERVAL 10
+
+/*! \brief Data that gets used by the SMDI MWI monitoring thread */
+static struct {
+       /*! The thread ID */
+       pthread_t thread;
+       ast_mutex_t lock;
+       ast_cond_t cond;
+       /*! A list of mailboxes that need to be monitored */
+       AST_LIST_HEAD_NOLOCK(, mailbox_mapping) mailbox_mappings;
+       /*! Polling Interval for checking mailbox status */
+       unsigned int polling_interval;
+       /*! Set to 1 to tell the polling thread to stop */
+       unsigned int stop:1;
+       /*! The time that the last poll began */
+       struct timeval last_poll;
+} mwi_monitor = {
+       .thread = AST_PTHREADT_NULL,
+};
+
+static void smdi_interface_destroy(void *obj)
+{
+       struct ast_smdi_interface *iface = obj;
 
-static void ast_smdi_md_message_push(struct ast_smdi_interface *iface, struct ast_smdi_md_message *msg);
-static void ast_smdi_mwi_message_push(struct ast_smdi_interface *iface, struct ast_smdi_mwi_message *msg);
+       if (iface->thread != AST_PTHREADT_NULL && iface->thread != AST_PTHREADT_STOP) {
+               pthread_cancel(iface->thread);
+               pthread_join(iface->thread, NULL);
+       }
 
-static void *smdi_read(void *iface_p);
-static int smdi_load(int reload);
+       iface->thread = AST_PTHREADT_STOP;
 
-/* Use count stuff */
+       if (iface->file) {
+               fclose(iface->file);
+       }
 
-LOCAL_USER_DECL;
+       ao2_cleanup(iface->md_q);
+       ast_mutex_destroy(&iface->md_q_lock);
+       ast_cond_destroy(&iface->md_q_cond);
 
-/*! \brief SMDI interface container. */
-struct ast_smdi_interface_container {
-       ASTOBJ_CONTAINER_COMPONENTS(struct ast_smdi_interface);
-} smdi_ifaces;
+       ao2_cleanup(iface->mwi_q);
+       ast_mutex_destroy(&iface->mwi_q_lock);
+       ast_cond_destroy(&iface->mwi_q_cond);
+
+       ast_free(iface);
+
+       ast_module_unref(ast_module_info->self);
+}
 
 /*! 
  * \internal
@@ -74,7 +257,10 @@ struct ast_smdi_interface_container {
  */
 static void ast_smdi_md_message_push(struct ast_smdi_interface *iface, struct ast_smdi_md_message *md_msg)
 {
-       ASTOBJ_CONTAINER_LINK_END(&iface->md_q, md_msg);
+       ast_mutex_lock(&iface->md_q_lock);
+       ao2_link(iface->md_q, md_msg);
+       ast_cond_broadcast(&iface->md_q_cond);
+       ast_mutex_unlock(&iface->md_q_lock);
 }
 
 /*!
@@ -85,251 +271,312 @@ static void ast_smdi_md_message_push(struct ast_smdi_interface *iface, struct as
  */
 static void ast_smdi_mwi_message_push(struct ast_smdi_interface *iface, struct ast_smdi_mwi_message *mwi_msg)
 {
-       ASTOBJ_CONTAINER_LINK_END(&iface->mwi_q, mwi_msg);
+       ast_mutex_lock(&iface->mwi_q_lock);
+       ao2_link(iface->mwi_q, mwi_msg);
+       ast_cond_broadcast(&iface->mwi_q_cond);
+       ast_mutex_unlock(&iface->mwi_q_lock);
 }
 
-/*!
- * \brief Set the MWI indicator for a mailbox.
- * \param iface the interface to use.
- * \param mailbox the mailbox to use.
- */
-int ast_smdi_mwi_set(struct ast_smdi_interface *iface, const char *mailbox)
+static int smdi_toggle_mwi(struct ast_smdi_interface *iface, const char *mailbox, int on)
 {
        FILE *file;
        int i;
-       
-       file = fopen(iface->name, "w");
-       if(!file) {
+
+       if (!(file = fopen(iface->name, "w"))) {
                ast_log(LOG_ERROR, "Error opening SMDI interface %s (%s) for writing\n", iface->name, strerror(errno));
                return 1;
-       }       
+       }
 
-       ASTOBJ_WRLOCK(iface);
+       ao2_wrlock(iface);
 
-       fprintf(file, "OP:MWI ");
+       fprintf(file, "%s:MWI ", on ? "OP" : "RMV");
 
-       for(i = 0; i < iface->msdstrip; i++)
-          fprintf(file, "0");
+       for (i = 0; i < iface->msdstrip; i++)
+               fprintf(file, "0");
 
        fprintf(file, "%s!\x04", mailbox);
+
        fclose(file);
 
-       ASTOBJ_UNLOCK(iface);
-       ast_log(LOG_DEBUG, "Sent MWI set message for %s on %s\n", mailbox, iface->name);
+       ao2_unlock(iface);
+       ast_debug(1, "Sent MWI set message for %s on %s\n", mailbox, iface->name);
+
        return 0;
 }
 
-/*! 
- * \brief Unset the MWI indicator for a mailbox.
- * \param iface the interface to use.
- * \param mailbox the mailbox to use.
- */
-int ast_smdi_mwi_unset(struct ast_smdi_interface *iface, const char *mailbox)
+int AST_OPTIONAL_API_NAME(ast_smdi_mwi_set)(struct ast_smdi_interface *iface, const char *mailbox)
 {
-       FILE *file;
-       int i;
-       
-       file = fopen(iface->name, "w");
-       if(!file) {
-               ast_log(LOG_ERROR, "Error opening SMDI interface %s (%s) for writing\n", iface->name, strerror(errno));
-               return 1;
-       }       
+       return smdi_toggle_mwi(iface, mailbox, 1);
+}
 
-       ASTOBJ_WRLOCK(iface);
+int AST_OPTIONAL_API_NAME(ast_smdi_mwi_unset)(struct ast_smdi_interface *iface, const char *mailbox)
+{
+       return smdi_toggle_mwi(iface, mailbox, 0);
+}
 
-       fprintf(file, "RMV:MWI ");
+enum smdi_message_type {
+       SMDI_MWI,
+       SMDI_MD,
+};
 
-       for(i = 0; i < iface->msdstrip; i++)
-          fprintf(file, "0");
+static inline int lock_msg_q(struct ast_smdi_interface *iface, enum smdi_message_type type)
+{
+       switch (type) {
+       case SMDI_MWI:
+               return ast_mutex_lock(&iface->mwi_q_lock);
+       case SMDI_MD:   
+               return ast_mutex_lock(&iface->md_q_lock);
+       }
+       
+       return -1;
+}
 
-       fprintf(file, "%s!\x04", mailbox);
-       fclose(file);
+static inline int unlock_msg_q(struct ast_smdi_interface *iface, enum smdi_message_type type)
+{
+       switch (type) {
+       case SMDI_MWI:
+               return ast_mutex_unlock(&iface->mwi_q_lock);
+       case SMDI_MD:
+               return ast_mutex_unlock(&iface->md_q_lock);
+       }
 
-       ASTOBJ_UNLOCK(iface);
-       ast_log(LOG_DEBUG, "Sent MWI unset message for %s on %s", mailbox, iface->name);
-       return 0;
+       return -1;
 }
 
-/*!
- * \brief Put an SMDI message back in the front of the queue.
- * \param iface a pointer to the interface to use.
- * \param msg a pointer to the message to use.
- *
- * This function puts a message back in the front of the specified queue.  It
- * should be used if a message was popped but is not going to be processed for
- * some reason, and the message needs to be returned to the queue.
- */
-void ast_smdi_md_message_putback(struct ast_smdi_interface *iface, struct ast_smdi_md_message *md_msg)
+static inline void *unlink_from_msg_q(struct ast_smdi_interface *iface, enum smdi_message_type type)
 {
-       ASTOBJ_CONTAINER_LINK_START(&iface->md_q, md_msg);
+       switch (type) {
+       case SMDI_MWI:
+               return ao2_callback(iface->mwi_q, OBJ_UNLINK, NULL, NULL);
+       case SMDI_MD:
+               return ao2_callback(iface->md_q, OBJ_UNLINK, NULL, NULL);
+       }
+
+       return NULL;
 }
 
-/*!
- * \brief Put an SMDI message back in the front of the queue.
- * \param iface a pointer to the interface to use.
- * \param msg a pointer to the message to use.
- *
- * This function puts a message back in the front of the specified queue.  It
- * should be used if a message was popped but is not going to be processed for
- * some reason, and the message needs to be returned to the queue.
- */
-void ast_smdi_mwi_message_putback(struct ast_smdi_interface *iface, struct ast_smdi_mwi_message *mwi_msg)
+static inline struct timeval msg_timestamp(void *msg, enum smdi_message_type type)
 {
-       ASTOBJ_CONTAINER_LINK_START(&iface->mwi_q, mwi_msg);
+       struct ast_smdi_md_message *md_msg = msg;
+       struct ast_smdi_mwi_message *mwi_msg = msg;
+
+       switch (type) {
+       case SMDI_MWI:
+               return mwi_msg->timestamp;
+       case SMDI_MD:
+               return md_msg->timestamp;
+       }
+
+       return ast_tv(0, 0);
 }
 
-/*! 
- * \brief Get the next SMDI message from the queue.
- * \param iface a pointer to the interface to use.
- *
- * This function pulls the first unexpired message from the SMDI message queue
- * on the specified interface.  It will purge all expired SMDI messages before
- * returning.
- *
- * \return the next SMDI message, or NULL if there were no pending messages.
- */
-struct ast_smdi_md_message *ast_smdi_md_message_pop(struct ast_smdi_interface *iface)
+static void purge_old_messages(struct ast_smdi_interface *iface, enum smdi_message_type type)
 {
-       struct ast_smdi_md_message *md_msg = ASTOBJ_CONTAINER_UNLINK_START(&iface->md_q);
-       struct timeval now;
+       struct timeval now = ast_tvnow();
        long elapsed = 0;
+       void *msg;
+       
+       lock_msg_q(iface, type);
+       msg = unlink_from_msg_q(iface, type);
+       unlock_msg_q(iface, type);
 
        /* purge old messages */
-       now = ast_tvnow();
-       while (md_msg) {
-               elapsed = ast_tvdiff_ms(now, md_msg->timestamp);
+       while (msg) {
+               elapsed = ast_tvdiff_ms(now, msg_timestamp(msg, type));
 
                if (elapsed > iface->msg_expiry) {
                        /* found an expired message */
-                       ASTOBJ_UNREF(md_msg, ast_smdi_md_message_destroy);
-                       ast_log(LOG_NOTICE, "Purged expired message from %s SMDI MD message queue.  Message was %ld milliseconds too old.",
-                               iface->name, elapsed - iface->msg_expiry);
-                       md_msg = ASTOBJ_CONTAINER_UNLINK_START(&iface->md_q);
-               }
-               else {
-                       /* good message, return it */
+                       ao2_ref(msg, -1);
+                       ast_log(LOG_NOTICE, "Purged expired message from %s SMDI %s message queue.  "
+                               "Message was %ld milliseconds too old.\n",
+                               iface->name, (type == SMDI_MD) ? "MD" : "MWI", 
+                               elapsed - iface->msg_expiry);
+
+                       lock_msg_q(iface, type);
+                       msg = unlink_from_msg_q(iface, type);
+                       unlock_msg_q(iface, type);
+               } else {
+                       /* good message, put it back and return */
+                       switch (type) {
+                       case SMDI_MD:
+                               ast_smdi_md_message_push(iface, msg);
+                               break;
+                       case SMDI_MWI:
+                               ast_smdi_mwi_message_push(iface, msg);
+                               break;
+                       }
+                       ao2_ref(msg, -1);
                        break;
                }
        }
-
-       return md_msg;
 }
 
-/*!
- * \brief Get the next SMDI message from the queue.
- * \param iface a pointer to the interface to use.
- * \param timeout the time to wait before returning in milliseconds.
- *
- * This function pulls a message from the SMDI message queue on the specified
- * interface.  If no message is available this function will wait the specified
- * amount of time before returning.
- *
- * \return the next SMDI message, or NULL if there were no pending messages and
- * the timeout has expired.
- */
-extern struct ast_smdi_md_message *ast_smdi_md_message_wait(struct ast_smdi_interface *iface, int timeout)
+static void *smdi_msg_pop(struct ast_smdi_interface *iface, enum smdi_message_type type)
 {
-       struct timeval start;
-       long diff = 0;
-       struct ast_smdi_md_message *msg;
+       void *msg;
 
-       start = ast_tvnow();
-       while (diff < timeout) {
+       purge_old_messages(iface, type);
 
-               if ((msg = ast_smdi_md_message_pop(iface)))
-                       return msg;
+       lock_msg_q(iface, type);
+       msg = unlink_from_msg_q(iface, type);
+       unlock_msg_q(iface, type);
 
-               /* check timeout */
-               diff = ast_tvdiff_ms(ast_tvnow(), start);
-       }
-
-       return (ast_smdi_md_message_pop(iface));
+       return msg;
 }
 
-/*!
- * \brief Get the next SMDI message from the queue.
- * \param iface a pointer to the interface to use.
- *
- * This function pulls the first unexpired message from the SMDI message queue
- * on the specified interface.  It will purge all expired SMDI messages before
- * returning.
- *
- * \return the next SMDI message, or NULL if there were no pending messages.
- */
-extern struct ast_smdi_mwi_message *ast_smdi_mwi_message_pop(struct ast_smdi_interface *iface)
-{
-       struct ast_smdi_mwi_message *mwi_msg = ASTOBJ_CONTAINER_UNLINK_START(&iface->mwi_q);
-       struct timeval now;
-       long elapsed = 0;
-
-       /* purge old messages */
-       now = ast_tvnow();
-       while (mwi_msg) {
-               elapsed = ast_tvdiff_ms(now, mwi_msg->timestamp);
+enum {
+       OPT_SEARCH_TERMINAL = (1 << 0),
+       OPT_SEARCH_NUMBER   = (1 << 1),
+};
 
-               if (elapsed > iface->msg_expiry) {
-                       /* found an expired message */
-                       ASTOBJ_UNREF(mwi_msg, ast_smdi_mwi_message_destroy);
-                       ast_log(LOG_NOTICE, "Purged expired message from %s SMDI MWI message queue.  Message was %ld milliseconds too old.",
-                               iface->name, elapsed - iface->msg_expiry);
-                       mwi_msg = ASTOBJ_CONTAINER_UNLINK_START(&iface->mwi_q);
+static void *smdi_msg_find(struct ast_smdi_interface *iface,
+       enum smdi_message_type type, const char *search_key, struct ast_flags options)
+{
+       void *msg = NULL;
+
+       purge_old_messages(iface, type);
+
+       switch (type) {
+       case SMDI_MD:
+               if (ast_strlen_zero(search_key)) {
+                       /* No search key provided (the code from chan_dahdi does this).
+                        * Just pop the top message off of the queue. */
+
+                       msg = ao2_callback(iface->md_q, 0, NULL, NULL);
+               } else if (ast_test_flag(&options, OPT_SEARCH_TERMINAL)) {
+                       /* Searching by the message desk terminal */
+                       struct ast_smdi_md_message md_msg = { .name = "" };
+                       strncpy(md_msg.mesg_desk_term, search_key, SMDI_MESG_DESK_TERM_LEN);
+                       msg = ao2_find(iface->md_q, &md_msg, OBJ_SEARCH_OBJECT);
+               } else if (ast_test_flag(&options, OPT_SEARCH_NUMBER)) {
+                       /* Searching by the message desk number */
+                       struct ast_smdi_md_message md_msg = { .name = "" };
+                       strncpy(md_msg.mesg_desk_num, search_key, SMDI_MESG_DESK_NUM_LEN);
+                       msg = ao2_find(iface->md_q, &md_msg, OBJ_SEARCH_OBJECT);
+               } else {
+                       /* Searching by the forwarding station */
+                       msg = ao2_find(iface->md_q, search_key, OBJ_SEARCH_KEY);
                }
-               else {
-                       /* good message, return it */
-                       break;
+               break;
+       case SMDI_MWI:
+               if (ast_strlen_zero(search_key)) {
+                       /* No search key provided (the code from chan_dahdi does this).
+                        * Just pop the top message off of the queue. */
+
+                       msg = ao2_callback(iface->mwi_q, 0, NULL, NULL);
+               } else {
+                       msg = ao2_find(iface->mwi_q, search_key, OBJ_SEARCH_KEY);
                }
+               break;
        }
 
-       return mwi_msg;
+       return msg;
 }
 
-/*!
- * \brief Get the next SMDI message from the queue.
- * \param iface a pointer to the interface to use.
- * \param timeout the time to wait before returning in milliseconds.
- *
- * This function pulls a message from the SMDI message queue on the specified
- * interface.  If no message is available this function will wait the specified
- * amount of time before returning.
- *
- * \return the next SMDI message, or NULL if there were no pending messages and
- * the timeout has expired.
- */
-extern struct ast_smdi_mwi_message *ast_smdi_mwi_message_wait(struct ast_smdi_interface *iface, int timeout)
+static void *smdi_message_wait(struct ast_smdi_interface *iface, int timeout, 
+       enum smdi_message_type type, const char *search_key, struct ast_flags options)
 {
        struct timeval start;
        long diff = 0;
-       struct ast_smdi_mwi_message *msg;
+       void *msg;
+       ast_cond_t *cond = NULL;
+       ast_mutex_t *lock = NULL;
+
+       switch (type) {
+       case SMDI_MWI:
+               cond = &iface->mwi_q_cond;
+               lock = &iface->mwi_q_lock;
+               break;
+       case SMDI_MD:
+               cond = &iface->md_q_cond;
+               lock = &iface->md_q_lock;
+               break;
+       }
 
        start = ast_tvnow();
+
        while (diff < timeout) {
+               struct timespec ts = { 0, };
+               struct timeval wait;
+
+               lock_msg_q(iface, type);
 
-               if ((msg = ast_smdi_mwi_message_pop(iface)))
+               if ((msg = smdi_msg_find(iface, type, search_key, options))) {
+                       unlock_msg_q(iface, type);
                        return msg;
+               }
+
+               wait = ast_tvadd(start, ast_tv(0, timeout));
+               ts.tv_sec = wait.tv_sec;
+               ts.tv_nsec = wait.tv_usec * 1000;
+
+               /* If there were no messages in the queue, then go to sleep until one
+                * arrives. */
+
+               ast_cond_timedwait(cond, lock, &ts);
+
+               if ((msg = smdi_msg_find(iface, type, search_key, options))) {
+                       unlock_msg_q(iface, type);
+                       return msg;
+               }
+
+               unlock_msg_q(iface, type);
 
                /* check timeout */
                diff = ast_tvdiff_ms(ast_tvnow(), start);
        }
 
-       return (ast_smdi_mwi_message_pop(iface));
+       return NULL;
+}
+
+struct ast_smdi_md_message * AST_OPTIONAL_API_NAME(ast_smdi_md_message_pop)(struct ast_smdi_interface *iface)
+{
+       return smdi_msg_pop(iface, SMDI_MD);
 }
 
-/*!
- * \brief Find an SMDI interface with the specified name.
- * \param iface_name the name/port of the interface to search for.
- *
- * \return a pointer to the interface located or NULL if none was found.  This
- * actually returns an ASTOBJ reference and should be released using
- * #ASTOBJ_UNREF(iface, ast_smdi_interface_destroy).
- */
-extern struct ast_smdi_interface *ast_smdi_interface_find(const char *iface_name)
+struct ast_smdi_md_message * AST_OPTIONAL_API_NAME(ast_smdi_md_message_wait)(struct ast_smdi_interface *iface, int timeout)
 {
-       return (ASTOBJ_CONTAINER_FIND(&smdi_ifaces, iface_name));
+       struct ast_flags options = { 0 };
+       return smdi_message_wait(iface, timeout, SMDI_MD, NULL, options);
 }
 
-/*! \brief Read an SMDI message.
+struct ast_smdi_mwi_message * AST_OPTIONAL_API_NAME(ast_smdi_mwi_message_pop)(struct ast_smdi_interface *iface)
+{
+       return smdi_msg_pop(iface, SMDI_MWI);
+}
+
+struct ast_smdi_mwi_message * AST_OPTIONAL_API_NAME(ast_smdi_mwi_message_wait)(struct ast_smdi_interface *iface, int timeout)
+{
+       struct ast_flags options = { 0 };
+       return smdi_message_wait(iface, timeout, SMDI_MWI, NULL, options);
+}
+
+struct ast_smdi_mwi_message * AST_OPTIONAL_API_NAME(ast_smdi_mwi_message_wait_station)(struct ast_smdi_interface *iface, int timeout,
+       const char *station)
+{
+       struct ast_flags options = { 0 };
+       return smdi_message_wait(iface, timeout, SMDI_MWI, station, options);
+}
+
+struct ast_smdi_interface * AST_OPTIONAL_API_NAME(ast_smdi_interface_find)(const char *iface_name)
+{
+       struct ao2_container *c;
+       struct ast_smdi_interface *iface = NULL;
+
+       c = ao2_global_obj_ref(smdi_ifaces);
+       if (c) {
+               iface = ao2_find(c, iface_name, OBJ_SEARCH_KEY);
+               ao2_ref(c, -1);
+       }
+
+       return iface;
+}
+
+/*! 
+ * \internal
+ * \brief Read an SMDI message.
  *
- * \param iface the SMDI interface to read from.
+ * \param iface_p the SMDI interface to read from.
  *
  * This function loops and reads from and SMDI interface.  It must be stopped
  * using pthread_cancel().
@@ -343,168 +590,332 @@ static void *smdi_read(void *iface_p)
        char *cp = NULL;
        int i;
        int start = 0;
-               
+
        /* read an smdi message */
        while ((c = fgetc(iface->file))) {
 
                /* check if this is the start of a message */
                if (!start) {
-                       if (c == 'M')
+                       if (c == 'M') {
+                               ast_debug(1, "Read an 'M' to start an SMDI message\n");
                                start = 1;
+                       }
+                       continue;
                }
-               else { /* Determine if this is a MD or MWI message */
-                       if(c == 'D') { /* MD message */
-                               start = 0;
 
-                               if (!(md_msg = ast_calloc(1, sizeof(*md_msg)))) {
-                                       ASTOBJ_UNREF(iface,ast_smdi_interface_destroy);
-                                       return NULL;
+               if (c == 'D') { /* MD message */
+                       start = 0;
+
+                       ast_debug(1, "Read a 'D' ... it's an MD message.\n");
+
+                       if (!(md_msg = ast_calloc(1, sizeof(*md_msg)))) {
+                               ao2_ref(iface, -1);
+                               return NULL;
+                       }
+
+                       md_msg = ao2_alloc(sizeof(*md_msg), NULL);
+
+                       /* read the message desk number */
+                       for (i = 0; i < sizeof(md_msg->mesg_desk_num) - 1; i++) {
+                               md_msg->mesg_desk_num[i] = fgetc(iface->file);
+                               ast_debug(1, "Read a '%c'\n", md_msg->mesg_desk_num[i]);
+                       }
+
+                       md_msg->mesg_desk_num[sizeof(md_msg->mesg_desk_num) - 1] = '\0';
+
+                       ast_debug(1, "The message desk number is '%s'\n", md_msg->mesg_desk_num);
+
+                       /* read the message desk terminal number */
+                       for (i = 0; i < sizeof(md_msg->mesg_desk_term) - 1; i++) {
+                               md_msg->mesg_desk_term[i] = fgetc(iface->file);
+                               ast_debug(1, "Read a '%c'\n", md_msg->mesg_desk_term[i]);
+                       }
+
+                       md_msg->mesg_desk_term[sizeof(md_msg->mesg_desk_term) - 1] = '\0';
+
+                       ast_debug(1, "The message desk terminal is '%s'\n", md_msg->mesg_desk_term);
+
+                       /* read the message type */
+                       md_msg->type = fgetc(iface->file);
+
+                       ast_debug(1, "Message type is '%c'\n", md_msg->type);
+
+                       /* read the forwarding station number (may be blank) */
+                       cp = &md_msg->fwd_st[0];
+                       for (i = 0; i < sizeof(md_msg->fwd_st) - 1; i++) {
+                               if ((c = fgetc(iface->file)) == ' ') {
+                                       *cp = '\0';
+                                       ast_debug(1, "Read a space, done looking for the forwarding station\n");
+                                       break;
                                }
-                               
-                               ASTOBJ_INIT(md_msg);
-
-                               /* read the message desk number */
-                               for(i = 0; i < SMDI_MESG_DESK_NUM_LEN; i++)
-                                       md_msg->mesg_desk_num[i] = fgetc(iface->file);
-
-                               md_msg->mesg_desk_num[SMDI_MESG_DESK_NUM_LEN] = '\0';
-
-                               /* read the message desk terminal number */
-                               for(i = 0; i < SMDI_MESG_DESK_TERM_LEN; i++)
-                                       md_msg->mesg_desk_term[i] = fgetc(iface->file);
-
-                               md_msg->mesg_desk_term[SMDI_MESG_DESK_TERM_LEN] = '\0';
-
-                               /* read the message type */
-                               md_msg->type = fgetc(iface->file);
-                          
-                               /* read the forwarding station number (may be blank) */
-                               cp = &md_msg->fwd_st[0];
-                               for (i = 0; i < SMDI_MAX_STATION_NUM_LEN + 1; i++) {
-                                       if((c = fgetc(iface->file)) == ' ') {
-                                               *cp = '\0';
-                                               break;
-                                       }
 
-                                       /* store c in md_msg->fwd_st */
-                                       if( i >= iface->msdstrip)
-                                               *cp++ = c;
+                               /* store c in md_msg->fwd_st */
+                               if (i >= iface->msdstrip) {
+                                       ast_debug(1, "Read a '%c' and stored it in the forwarding station buffer\n", c);
+                                       *cp++ = c;
+                               } else {
+                                       ast_debug(1, "Read a '%c', but didn't store it in the fwd station buffer, because of the msdstrip setting (%d < %d)\n", c, i, iface->msdstrip);
                                }
+                       }
 
-                               /* make sure the value is null terminated, even if this truncates it */
-                               md_msg->fwd_st[SMDI_MAX_STATION_NUM_LEN] = '\0';
-                               cp = NULL;
-                               
-                               /* read the calling station number (may be blank) */
-                               cp = &md_msg->calling_st[0];
-                               for (i = 0; i < SMDI_MAX_STATION_NUM_LEN + 1; i++) {
-                                       if (!isdigit((c = fgetc(iface->file)))) {
-                                               *cp = '\0';
-                                               break;
+                       /* make sure the value is null terminated, even if this truncates it */
+                       md_msg->fwd_st[sizeof(md_msg->fwd_st) - 1] = '\0';
+                       cp = NULL;
+
+                       ast_debug(1, "The forwarding station is '%s'\n", md_msg->fwd_st);
+
+                       /* Put the fwd_st in the name field so that we can use ao2_find to look
+                        * up a message on this field */
+                       ast_copy_string(md_msg->name, md_msg->fwd_st, sizeof(md_msg->name));
+
+                       /* read the calling station number (may be blank) */
+                       cp = &md_msg->calling_st[0];
+                       for (i = 0; i < sizeof(md_msg->calling_st) - 1; i++) {
+                               if (!isdigit((c = fgetc(iface->file)))) {
+                                       *cp = '\0';
+                                       ast_debug(1, "Read a '%c', but didn't store it in the calling station buffer because it's not a digit\n", c);
+                                       if (c == ' ') {
+                                               /* Don't break on a space.  We may read the space before the calling station
+                                                * here if the forwarding station buffer filled up. */
+                                               i--; /* We're still on the same character */
+                                               continue;
                                        }
+                                       break;
+                               }
 
-                                       /* store c in md_msg->calling_st */
-                                       if (i >= iface->msdstrip)
-                                               *cp++ = c;
+                               /* store c in md_msg->calling_st */
+                               if (i >= iface->msdstrip) {
+                                       ast_debug(1, "Read a '%c' and stored it in the calling station buffer\n", c);
+                                       *cp++ = c;
+                               } else {
+                                       ast_debug(1, "Read a '%c', but didn't store it in the calling station buffer, because of the msdstrip setting (%d < %d)\n", c, i, iface->msdstrip);
                                }
+                       }
 
-                               /* make sure the value is null terminated, even if this truncates it */
-                               md_msg->calling_st[SMDI_MAX_STATION_NUM_LEN] = '\0';
-                               cp = NULL;
+                       /* make sure the value is null terminated, even if this truncates it */
+                       md_msg->calling_st[sizeof(md_msg->calling_st) - 1] = '\0';
+                       cp = NULL;
 
-                               /* add the message to the message queue */
-                               md_msg->timestamp = ast_tvnow();
-                               ast_smdi_md_message_push(iface, md_msg);
-                               ast_log(LOG_DEBUG, "Recieved SMDI MD message on %s\n", iface->name);
-                               
-                               ASTOBJ_UNREF(md_msg, ast_smdi_md_message_destroy);
+                       ast_debug(1, "The calling station is '%s'\n", md_msg->calling_st);
 
-                       } else if(c == 'W') { /* MWI message */
-                               start = 0;
+                       /* add the message to the message queue */
+                       md_msg->timestamp = ast_tvnow();
+                       ast_smdi_md_message_push(iface, md_msg);
+                       ast_debug(1, "Received SMDI MD message on %s\n", iface->name);
 
-                               if (!(mwi_msg = ast_calloc(1, sizeof(*mwi_msg)))) {
-                                       ASTOBJ_UNREF(iface,ast_smdi_interface_destroy);
-                                       return NULL;
-                               }
+                       ao2_ref(md_msg, -1);
 
-                               ASTOBJ_INIT(mwi_msg);
-
-                               /* discard the 'I' (from 'MWI') */
-                               fgetc(iface->file);
-                               
-                               /* read the forwarding station number (may be blank) */
-                               cp = &mwi_msg->fwd_st[0];
-                               for (i = 0; i < SMDI_MAX_STATION_NUM_LEN + 1; i++) {
-                                       if ((c = fgetc(iface->file)) == ' ') {
-                                               *cp = '\0';
-                                               break;
-                                       }
+               } else if (c == 'W') { /* MWI message */
+                       start = 0;
+
+                       ast_debug(1, "Read a 'W', it's an MWI message. (No more debug coming for MWI messages)\n");
+
+                       if (!(mwi_msg = ast_calloc(1, sizeof(*mwi_msg)))) {
+                               ao2_ref(iface, -1);
+                               return NULL;
+                       }
+
+                       mwi_msg = ao2_alloc(sizeof(*mwi_msg), NULL);
 
-                                       /* store c in md_msg->fwd_st */
-                                       if (i >= iface->msdstrip)
-                                               *cp++ = c;
+                       /* discard the 'I' (from 'MWI') */
+                       fgetc(iface->file);
+                       
+                       /* read the forwarding station number (may be blank) */
+                       cp = &mwi_msg->fwd_st[0];
+                       for (i = 0; i < sizeof(mwi_msg->fwd_st) - 1; i++) {
+                               if ((c = fgetc(iface->file)) == ' ') {
+                                       *cp = '\0';
+                                       break;
                                }
 
-                               /* make sure the station number is null terminated, even if this will truncate it */
-                               mwi_msg->fwd_st[SMDI_MAX_STATION_NUM_LEN] = '\0';
-                               cp = NULL;
-                               
-                               /* read the mwi failure cause */
-                               for (i = 0; i < SMDI_MWI_FAIL_CAUSE_LEN; i++)
-                                       mwi_msg->cause[i] = fgetc(iface->file);
-
-                               mwi_msg->cause[SMDI_MWI_FAIL_CAUSE_LEN] = '\0';
-
-                               /* add the message to the message queue */
-                               mwi_msg->timestamp = ast_tvnow();
-                               ast_smdi_mwi_message_push(iface, mwi_msg);
-                               ast_log(LOG_DEBUG, "Recieved SMDI MWI message on %s\n", iface->name);
-                               
-                               ASTOBJ_UNREF(mwi_msg, ast_smdi_mwi_message_destroy);
-                       } else {
-                               ast_log(LOG_ERROR, "Unknown SMDI message type recieved on %s (M%c).\n", iface->name, c);
-                               start = 0;
+                               /* store c in md_msg->fwd_st */
+                               if (i >= iface->msdstrip)
+                                       *cp++ = c;
                        }
+
+                       /* make sure the station number is null terminated, even if this will truncate it */
+                       mwi_msg->fwd_st[sizeof(mwi_msg->fwd_st) - 1] = '\0';
+                       cp = NULL;
+
+                       /* Put the fwd_st in the name field so that we can use ao2_find to look
+                        * up a message on this field */
+                       ast_copy_string(mwi_msg->name, mwi_msg->fwd_st, sizeof(mwi_msg->name));
+
+                       /* read the mwi failure cause */
+                       for (i = 0; i < sizeof(mwi_msg->cause) - 1; i++)
+                               mwi_msg->cause[i] = fgetc(iface->file);
+
+                       mwi_msg->cause[sizeof(mwi_msg->cause) - 1] = '\0';
+
+                       /* add the message to the message queue */
+                       mwi_msg->timestamp = ast_tvnow();
+                       ast_smdi_mwi_message_push(iface, mwi_msg);
+                       ast_debug(1, "Received SMDI MWI message on %s\n", iface->name);
+
+                       ao2_ref(mwi_msg, -1);
+               } else {
+                       ast_log(LOG_ERROR, "Unknown SMDI message type received on %s (M%c).\n", iface->name, c);
+                       start = 0;
                }
        }
 
        ast_log(LOG_ERROR, "Error reading from SMDI interface %s, stopping listener thread\n", iface->name);
-       ASTOBJ_UNREF(iface,ast_smdi_interface_destroy);
+       ao2_ref(iface, -1);
        return NULL;
 }
 
-/*! \brief ast_smdi_md_message destructor. */
-void ast_smdi_md_message_destroy(struct ast_smdi_md_message *msg)
+static void destroy_mailbox_mapping(struct mailbox_mapping *mm)
 {
-       free(msg);
+       ast_string_field_free_memory(mm);
+       ao2_ref(mm->iface, -1);
+       ast_free(mm);
 }
 
-/*! \brief ast_smdi_mwi_message destructor. */
-void ast_smdi_mwi_message_destroy(struct ast_smdi_mwi_message *msg)
+static void destroy_all_mailbox_mappings(void)
 {
-       free(msg);
+       struct mailbox_mapping *mm;
+
+       ast_mutex_lock(&mwi_monitor.lock);
+       while ((mm = AST_LIST_REMOVE_HEAD(&mwi_monitor.mailbox_mappings, entry)))
+               destroy_mailbox_mapping(mm);
+       ast_mutex_unlock(&mwi_monitor.lock);
 }
 
-/*! \brief ast_smdi_interface destructor. */
-void ast_smdi_interface_destroy(struct ast_smdi_interface *iface)
+static void append_mailbox_mapping(struct ast_variable *var, struct ast_smdi_interface *iface)
 {
-       if (iface->thread != AST_PTHREADT_NULL && iface->thread != AST_PTHREADT_STOP) {
-               pthread_cancel(iface->thread);
-               pthread_join(iface->thread, NULL);
+       struct mailbox_mapping *mm;
+       char *mailbox, *context;
+
+       if (!(mm = ast_calloc_with_stringfields(1, struct mailbox_mapping, 32)))
+               return;
+
+       ast_string_field_set(mm, smdi, var->name);
+
+       context = ast_strdupa(var->value);
+       mailbox = strsep(&context, "@");
+       if (ast_strlen_zero(context))
+               context = "default";
+
+       ast_string_field_set(mm, mailbox, mailbox);
+       ast_string_field_set(mm, context, context);
+
+       mm->iface = ao2_bump(iface);
+
+       ast_mutex_lock(&mwi_monitor.lock);
+       AST_LIST_INSERT_TAIL(&mwi_monitor.mailbox_mappings, mm, entry);
+       ast_mutex_unlock(&mwi_monitor.lock);
+}
+
+/*!
+ * \note Called with the mwi_monitor.lock locked
+ */
+static void poll_mailbox(struct mailbox_mapping *mm)
+{
+       char buf[1024];
+       unsigned int state;
+
+       snprintf(buf, sizeof(buf), "%s@%s", mm->mailbox, mm->context);
+
+       state = !!ast_app_has_voicemail(mm->mailbox, NULL);
+
+       if (state != mm->cur_state) {
+               if (state)
+                       ast_smdi_mwi_set(mm->iface, mm->smdi);
+               else
+                       ast_smdi_mwi_unset(mm->iface, mm->smdi);
+
+               mm->cur_state = state;
        }
-       
-       iface->thread = AST_PTHREADT_STOP;
-       
-       if(iface->file) 
-               fclose(iface->file);
-       
-       ASTOBJ_CONTAINER_DESTROYALL(&iface->md_q, ast_smdi_md_message_destroy);
-       ASTOBJ_CONTAINER_DESTROYALL(&iface->mwi_q, ast_smdi_mwi_message_destroy);
-       ASTOBJ_CONTAINER_DESTROY(&iface->md_q);
-       ASTOBJ_CONTAINER_DESTROY(&iface->mwi_q);
-       free(iface);
+}
+
+static void *mwi_monitor_handler(void *data)
+{
+       while (!mwi_monitor.stop) {
+               struct timespec ts = { 0, };
+               struct timeval polltime;
+               struct mailbox_mapping *mm;
 
-       STANDARD_DECREMENT_USECOUNT;
+               ast_mutex_lock(&mwi_monitor.lock);
+
+               mwi_monitor.last_poll = ast_tvnow();
+
+               AST_LIST_TRAVERSE(&mwi_monitor.mailbox_mappings, mm, entry)
+                       poll_mailbox(mm);
+
+               /* Sleep up to the configured polling interval.  Allow unload_module()
+                * to signal us to wake up and exit. */
+               polltime = ast_tvadd(mwi_monitor.last_poll, ast_tv(mwi_monitor.polling_interval, 0));
+               ts.tv_sec = polltime.tv_sec;
+               ts.tv_nsec = polltime.tv_usec * 1000;
+               ast_cond_timedwait(&mwi_monitor.cond, &mwi_monitor.lock, &ts);
+
+               ast_mutex_unlock(&mwi_monitor.lock);
+       }
+
+       return NULL;
+}
+
+static int smdi_mwi_q_cmp_fn(void *obj, void *data, int flags)
+{
+       struct ast_smdi_mwi_message *msg = obj;
+       char *str = data;
+       return !strcmp(msg->name, str) ? CMP_MATCH | CMP_STOP : 0;
+}
+
+static int smdi_md_q_cmp_fn(void *obj, void *arg, int flags)
+{
+       const struct ast_smdi_md_message *msg = obj;
+       const struct ast_smdi_md_message *search_msg = arg;
+       const char *search_key = arg;
+       int cmp = 0;
+
+       switch (flags & OBJ_SEARCH_MASK) {
+       case OBJ_SEARCH_OBJECT:
+               if (!ast_strlen_zero(search_msg->mesg_desk_num)) {
+                       cmp = strcmp(msg->mesg_desk_num, search_msg->mesg_desk_num);
+               }
+               if (!ast_strlen_zero(search_msg->mesg_desk_term)) {
+                       cmp |= strcmp(msg->mesg_desk_term, search_msg->mesg_desk_term);
+               }
+               break;
+       case OBJ_SEARCH_KEY:
+               cmp = strcmp(msg->name, search_key);
+               break;
+       }
+
+       if (cmp) {
+               return 0;
+       }
+
+       return CMP_MATCH;
+}
+
+static struct ast_smdi_interface *alloc_smdi_interface(void)
+{
+       struct ast_smdi_interface *iface;
+
+       if (!(iface = ao2_alloc(sizeof(*iface), smdi_interface_destroy))) {
+               return NULL;
+       }
+
+       iface->md_q = ao2_container_alloc_list(AO2_ALLOC_OPT_LOCK_NOLOCK, 0, NULL, smdi_md_q_cmp_fn);
+       iface->mwi_q = ao2_container_alloc_list(AO2_ALLOC_OPT_LOCK_NOLOCK, 0, NULL, smdi_mwi_q_cmp_fn);
+
+       ast_mutex_init(&iface->md_q_lock);
+       ast_cond_init(&iface->md_q_cond, NULL);
+
+       ast_mutex_init(&iface->mwi_q_lock);
+       ast_cond_init(&iface->mwi_q_cond, NULL);
+
+       return iface;
+}
+
+static int smdi_ifaces_cmp_fn(void *obj, void *data, int flags)
+{
+       struct ast_smdi_interface *iface = obj;
+
+       char *str = data;
+       return !strcmp(iface->name, str) ? CMP_MATCH | CMP_STOP : 0;
 }
 
 /*!
@@ -521,8 +932,11 @@ static int smdi_load(int reload)
 {
        struct ast_config *conf;
        struct ast_variable *v;
-       struct ast_smdi_interface *iface = NULL;
+       struct ast_flags config_flags = { reload ? CONFIG_FLAG_FILEUNCHANGED : 0 };
        int res = 0;
+       RAII_VAR(struct ao2_container *, new_ifaces, NULL, ao2_cleanup);
+       RAII_VAR(struct ao2_container *, old_ifaces, ao2_global_obj_ref(smdi_ifaces), ao2_cleanup);
+       struct ast_smdi_interface *mailbox_iface = NULL;
 
        /* Config options */
        speed_t baud_rate = B9600;     /* 9600 baud rate */
@@ -532,41 +946,35 @@ static int smdi_load(int reload)
        
        int msdstrip = 0;              /* strip zero digits */
        long msg_expiry = SMDI_MSG_EXPIRY_TIME;
-       
-       conf = ast_config_load(config_file);
 
-       if (!conf) {
+       if (!(conf = ast_config_load(config_file, config_flags)) || conf == CONFIG_STATUS_FILEINVALID) {
                if (reload)
                        ast_log(LOG_NOTICE, "Unable to reload config %s: SMDI untouched\n", config_file);
                else
                        ast_log(LOG_NOTICE, "Unable to load config %s: SMDI disabled\n", config_file);
                return 1;
-       }
-
-       /* Mark all interfaces that we are listening on.  We will unmark them
-        * as we find them in the config file, this way we know any interfaces
-        * still marked after we have finished parsing the config file should
-        * be stopped.
-        */
-       if (reload)
-               ASTOBJ_CONTAINER_MARKALL(&smdi_ifaces);
+       } else if (conf == CONFIG_STATUS_FILEUNCHANGED)
+               return 0;
 
+       new_ifaces = ao2_container_alloc_list(AO2_ALLOC_OPT_LOCK_MUTEX, 0, NULL, smdi_ifaces_cmp_fn);
        for (v = ast_variable_browse(conf, "interfaces"); v; v = v->next) {
+               RAII_VAR(struct ast_smdi_interface *, iface, NULL, ao2_cleanup);
+
                if (!strcasecmp(v->name, "baudrate")) {
                        if (!strcasecmp(v->value, "9600"))
                                baud_rate = B9600;
-                       else if(!strcasecmp(v->value, "4800"))
+                       else if (!strcasecmp(v->value, "4800"))
                                baud_rate = B4800;
-                       else if(!strcasecmp(v->value, "2400"))
+                       else if (!strcasecmp(v->value, "2400"))
                                baud_rate = B2400;
-                       else if(!strcasecmp(v->value, "1200"))
+                       else if (!strcasecmp(v->value, "1200"))
                                baud_rate = B1200;
                        else {
                                ast_log(LOG_NOTICE, "Invalid baud rate '%s' specified in %s (line %d), using default\n", v->value, config_file, v->lineno);
                                baud_rate = B9600;
                        }
                } else if (!strcasecmp(v->name, "msdstrip")) {
-                       if (!sscanf(v->value, "%d", &msdstrip)) {
+                       if (!sscanf(v->value, "%30d", &msdstrip)) {
                                ast_log(LOG_NOTICE, "Invalid msdstrip value in %s (line %d), using default\n", config_file, v->lineno);
                                msdstrip = 0;
                        } else if (0 > msdstrip || msdstrip > 9) {
@@ -574,7 +982,7 @@ static int smdi_load(int reload)
                                msdstrip = 0;
                        }
                } else if (!strcasecmp(v->name, "msgexpirytime")) {
-                       if (!sscanf(v->value, "%ld", &msg_expiry)) {
+                       if (!sscanf(v->value, "%30ld", &msg_expiry)) {
                                ast_log(LOG_NOTICE, "Invalid msgexpirytime value in %s (line %d), using default\n", config_file, v->lineno);
                                msg_expiry = SMDI_MSG_EXPIRY_TIME;
                        }
@@ -612,25 +1020,22 @@ static int smdi_load(int reload)
                                 * restarting the interface.  Or the interface
                                 * could be restarted with out emptying the
                                 * queue. */
-                               if ((iface = ASTOBJ_CONTAINER_FIND(&smdi_ifaces, v->value))) {
+                               if ((iface = ao2_find(old_ifaces, v->value, OBJ_SEARCH_KEY))) {
                                        ast_log(LOG_NOTICE, "SMDI interface %s already running, not restarting\n", iface->name);
-                                       ASTOBJ_UNMARK(iface);
-                                       ASTOBJ_UNREF(iface, ast_smdi_interface_destroy);
+                                       ao2_link(new_ifaces, iface);
                                        continue;
                                }
                        }
-                                                       
-                       iface = ast_calloc(1, sizeof(*iface));
-
-                       ASTOBJ_INIT(iface);
-                       ASTOBJ_CONTAINER_INIT(&iface->md_q);
-                       ASTOBJ_CONTAINER_INIT(&iface->mwi_q);
+                       
+                       if (!(iface = alloc_smdi_interface()))
+                               continue;
 
                        ast_copy_string(iface->name, v->value, sizeof(iface->name));
 
+                       iface->thread = AST_PTHREADT_NULL;
+
                        if (!(iface->file = fopen(iface->name, "r"))) {
                                ast_log(LOG_ERROR, "Error opening SMDI interface %s (%s)\n", iface->name, strerror(errno));
-                               ASTOBJ_UNREF(iface, ast_smdi_interface_destroy);
                                continue;
                        }
 
@@ -641,33 +1046,30 @@ static int smdi_load(int reload)
                        /* get the current attributes from the port */
                        if (tcgetattr(iface->fd, &iface->mode)) {
                                ast_log(LOG_ERROR, "Error getting atributes of %s (%s)\n", iface->name, strerror(errno));
-                               ASTOBJ_UNREF(iface, ast_smdi_interface_destroy);
                                continue;
                        }
 
                        /* set the desired speed */
                        if (cfsetispeed(&iface->mode, baud_rate) || cfsetospeed(&iface->mode, baud_rate)) {
                                ast_log(LOG_ERROR, "Error setting baud rate on %s (%s)\n", iface->name, strerror(errno));
-                               ASTOBJ_UNREF(iface, ast_smdi_interface_destroy);
                                continue;
                        }
                        
                        /* set the stop bits */
                        if (stopbits)
-                          iface->mode.c_cflag = iface->mode.c_cflag | CSTOPB;   /* set two stop bits */
+                               iface->mode.c_cflag = iface->mode.c_cflag | CSTOPB;   /* set two stop bits */
                        else
-                          iface->mode.c_cflag = iface->mode.c_cflag & ~CSTOPB;  /* set one stop bit */
-
+                               iface->mode.c_cflag = iface->mode.c_cflag & ~CSTOPB;  /* set one stop bit */
+                       
                        /* set the parity */
                        iface->mode.c_cflag = (iface->mode.c_cflag & ~PARENB & ~PARODD) | paritybit;
-
+                       
                        /* set the character size */
                        iface->mode.c_cflag = (iface->mode.c_cflag & ~CSIZE) | charsize;
                        
                        /* commit the desired attributes */
                        if (tcsetattr(iface->fd, TCSAFLUSH, &iface->mode)) {
                                ast_log(LOG_ERROR, "Error setting attributes on %s (%s)\n", iface->name, strerror(errno));
-                               ASTOBJ_UNREF(iface, ast_smdi_interface_destroy);
                                continue;
                        }
 
@@ -677,73 +1079,343 @@ static int smdi_load(int reload)
                        /* set the message expiry time */
                        iface->msg_expiry = msg_expiry;
 
-                        /* start the listner thread */
-                       if (option_verbose > 2)
-                               ast_verbose(VERBOSE_PREFIX_3 "Starting SMDI monitor thread for %s\n", iface->name);
-                       if (ast_pthread_create(&iface->thread, NULL, smdi_read, iface)) {
+                       /* start the listener thread */
+                       ast_verb(3, "Starting SMDI monitor thread for %s\n", iface->name);
+                       if (ast_pthread_create_background(&iface->thread, NULL, smdi_read, iface)) {
                                ast_log(LOG_ERROR, "Error starting SMDI monitor thread for %s\n", iface->name);
-                               ASTOBJ_UNREF(iface, ast_smdi_interface_destroy);
                                continue;
                        }
 
-                       ASTOBJ_CONTAINER_LINK(&smdi_ifaces, iface);
-                       ASTOBJ_UNREF(iface, ast_smdi_interface_destroy);
-                       STANDARD_INCREMENT_USECOUNT;
+                       ao2_link(new_ifaces, iface);
+                       ast_module_ref(ast_module_info->self);
                } else {
                        ast_log(LOG_NOTICE, "Ignoring unknown option %s in %s\n", v->name, config_file);
                }
-               v = v->next;
        }
+
+       destroy_all_mailbox_mappings();
+       mwi_monitor.polling_interval = DEFAULT_POLLING_INTERVAL;
+
+       for (v = ast_variable_browse(conf, "mailboxes"); v; v = v->next) {
+               if (!strcasecmp(v->name, "smdiport")) {
+                       ao2_cleanup(mailbox_iface);
+
+                       if (!(mailbox_iface = ao2_find(new_ifaces, v->value, OBJ_SEARCH_KEY))) {
+                               ast_log(LOG_NOTICE, "SMDI interface %s not found\n", v->value);
+                               continue;
+                       }
+               } else if (!strcasecmp(v->name, "pollinginterval")) {
+                       if (sscanf(v->value, "%30u", &mwi_monitor.polling_interval) != 1) {
+                               ast_log(LOG_ERROR, "Invalid value for pollinginterval: %s\n", v->value);
+                               mwi_monitor.polling_interval = DEFAULT_POLLING_INTERVAL;
+                       }
+               } else {
+                       if (!mailbox_iface) {
+                               ast_log(LOG_ERROR, "Mailbox mapping ignored, no valid SMDI interface specified in mailboxes section\n");
+                               continue;
+                       }
+                       append_mailbox_mapping(v, mailbox_iface);
+               }
+       }
+       ao2_cleanup(mailbox_iface);
+
        ast_config_destroy(conf);
 
-       /* Prune any interfaces we should no longer monitor. */
-       if (reload)
-               ASTOBJ_CONTAINER_PRUNE_MARKED(&smdi_ifaces, ast_smdi_interface_destroy);
-       
-       ASTOBJ_CONTAINER_RDLOCK(&smdi_ifaces);
-       /* TODO: this is bad, we need an ASTOBJ method for this! */
-       if (!smdi_ifaces.head)
+       ao2_global_obj_replace_unref(smdi_ifaces, new_ifaces);
+
+       if (!AST_LIST_EMPTY(&mwi_monitor.mailbox_mappings) && mwi_monitor.thread == AST_PTHREADT_NULL
+               && ast_pthread_create_background(&mwi_monitor.thread, NULL, mwi_monitor_handler, NULL)) {
+               ast_log(LOG_ERROR, "Failed to start MWI monitoring thread.  This module will not operate.\n");
+               return -1;
+       }
+
+       if (ao2_container_count(new_ifaces)) {
                res = 1;
-       ASTOBJ_CONTAINER_UNLOCK(&smdi_ifaces);
-                       
+       }
+       
        return res;
 }
 
+struct smdi_msg_datastore {
+       unsigned int id;
+       struct ast_smdi_interface *iface;
+       struct ast_smdi_md_message *md_msg;
+};
 
-char *description(void)
+static void smdi_msg_datastore_destroy(void *data)
 {
-       return (char *) tdesc;
+       struct smdi_msg_datastore *smd = data;
+
+       ao2_cleanup(smd->iface);
+       ao2_cleanup(smd->md_msg);
+
+       ast_free(smd);
 }
 
-int load_module(void)
+static const struct ast_datastore_info smdi_msg_datastore_info = {
+       .type = "SMDIMSG",
+       .destroy = smdi_msg_datastore_destroy,
+};
+
+static int smdi_msg_id;
+
+/*! In milliseconds */
+#define SMDI_RETRIEVE_TIMEOUT_DEFAULT 3000
+
+AST_APP_OPTIONS(smdi_msg_ret_options, BEGIN_OPTIONS
+       AST_APP_OPTION('t', OPT_SEARCH_TERMINAL),
+       AST_APP_OPTION('n', OPT_SEARCH_NUMBER),
+END_OPTIONS );
+
+static int smdi_msg_retrieve_read(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len)
+{
+       struct ast_module_user *u;
+       AST_DECLARE_APP_ARGS(args,
+               AST_APP_ARG(port);
+               AST_APP_ARG(search_key);
+               AST_APP_ARG(timeout);
+               AST_APP_ARG(options);
+       );
+       struct ast_flags options = { 0 };
+       unsigned int timeout = SMDI_RETRIEVE_TIMEOUT_DEFAULT;
+       int res = -1;
+       char *parse = NULL;
+       struct smdi_msg_datastore *smd = NULL;
+       struct ast_datastore *datastore = NULL;
+       struct ast_smdi_interface *iface = NULL;
+       struct ast_smdi_md_message *md_msg = NULL;
+
+       u = ast_module_user_add(chan);
+
+       if (ast_strlen_zero(data)) {
+               ast_log(LOG_ERROR, "SMDI_MSG_RETRIEVE requires an argument\n");
+               goto return_error;
+       }
+
+       if (!chan) {
+               ast_log(LOG_ERROR, "SMDI_MSG_RETRIEVE must be used with a channel\n");
+               goto return_error;
+       }
+
+       ast_autoservice_start(chan);
+
+       parse = ast_strdupa(data);
+       AST_STANDARD_APP_ARGS(args, parse);
+
+       if (ast_strlen_zero(args.port) || ast_strlen_zero(args.search_key)) {
+               ast_log(LOG_ERROR, "Invalid arguments provided to SMDI_MSG_RETRIEVE\n");
+               goto return_error;
+       }
+
+       if (!(iface = ast_smdi_interface_find(args.port))) {
+               ast_log(LOG_ERROR, "SMDI port '%s' not found\n", args.port);
+               goto return_error;
+       }
+
+       if (!ast_strlen_zero(args.options)) {
+               ast_app_parse_options(smdi_msg_ret_options, &options, NULL, args.options);
+       }
+
+       if (!ast_strlen_zero(args.timeout)) {
+               if (sscanf(args.timeout, "%30u", &timeout) != 1) {
+                       ast_log(LOG_ERROR, "'%s' is not a valid timeout\n", args.timeout);
+                       timeout = SMDI_RETRIEVE_TIMEOUT_DEFAULT;
+               }
+       }
+
+       if (!(md_msg = smdi_message_wait(iface, timeout, SMDI_MD, args.search_key, options))) {
+               ast_log(LOG_WARNING, "No SMDI message retrieved for search key '%s' after "
+                       "waiting %u ms.\n", args.search_key, timeout);
+               goto return_error;
+       }
+
+       if (!(smd = ast_calloc(1, sizeof(*smd))))
+               goto return_error;
+
+       smd->iface = ao2_bump(iface);
+       smd->md_msg = ao2_bump(md_msg);
+       smd->id = ast_atomic_fetchadd_int((int *) &smdi_msg_id, 1);
+       snprintf(buf, len, "%u", smd->id);
+
+       if (!(datastore = ast_datastore_alloc(&smdi_msg_datastore_info, buf)))
+               goto return_error;
+
+       datastore->data = smd;
+
+       ast_channel_lock(chan);
+       ast_channel_datastore_add(chan, datastore);
+       ast_channel_unlock(chan);
+
+       res = 0;
+
+return_error:
+       ao2_cleanup(iface);
+       ao2_cleanup(md_msg);
+
+       if (smd && !datastore)
+               smdi_msg_datastore_destroy(smd);
+
+       if (parse)
+               ast_autoservice_stop(chan);
+
+       ast_module_user_remove(u);
+
+       return res;
+}
+
+static int smdi_msg_read(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len)
+{
+       struct ast_module_user *u;
+       int res = -1;
+       AST_DECLARE_APP_ARGS(args,
+               AST_APP_ARG(id);
+               AST_APP_ARG(component);
+       );
+       char *parse;
+       struct ast_datastore *datastore = NULL;
+       struct smdi_msg_datastore *smd = NULL;
+
+       u = ast_module_user_add(chan);
+
+       if (!chan) {
+               ast_log(LOG_ERROR, "SMDI_MSG can not be called without a channel\n");
+               goto return_error;
+       }
+
+       if (ast_strlen_zero(data)) {
+               ast_log(LOG_WARNING, "SMDI_MSG requires an argument\n");
+               goto return_error;
+       }
+
+       parse = ast_strdupa(data);
+       AST_STANDARD_APP_ARGS(args, parse);
+
+       if (ast_strlen_zero(args.id)) {
+               ast_log(LOG_WARNING, "ID must be supplied to SMDI_MSG\n");
+               goto return_error;
+       }
+
+       if (ast_strlen_zero(args.component)) {
+               ast_log(LOG_WARNING, "ID must be supplied to SMDI_MSG\n");
+               goto return_error;
+       }
+
+       ast_channel_lock(chan);
+       datastore = ast_channel_datastore_find(chan, &smdi_msg_datastore_info, args.id);
+       ast_channel_unlock(chan);
+       
+       if (!datastore) {
+               ast_log(LOG_WARNING, "No SMDI message found for message ID '%s'\n", args.id);
+               goto return_error;
+       }
+
+       smd = datastore->data;
+
+       if (!strcasecmp(args.component, "number")) {
+               ast_copy_string(buf, smd->md_msg->mesg_desk_num, len);
+       } else if (!strcasecmp(args.component, "terminal")) {
+               ast_copy_string(buf, smd->md_msg->mesg_desk_term, len);
+       } else if (!strcasecmp(args.component, "station")) {
+               ast_copy_string(buf, smd->md_msg->fwd_st, len);
+       } else if (!strcasecmp(args.component, "callerid")) {
+               ast_copy_string(buf, smd->md_msg->calling_st, len);
+       } else if (!strcasecmp(args.component, "type")) {
+               snprintf(buf, len, "%c", smd->md_msg->type);
+       } else {
+               ast_log(LOG_ERROR, "'%s' is not a valid message component for SMDI_MSG\n",
+                       args.component);
+               goto return_error;
+       }
+
+       res = 0;
+
+return_error:
+       ast_module_user_remove(u);
+
+       return res;
+}
+
+static struct ast_custom_function smdi_msg_retrieve_function = {
+       .name = "SMDI_MSG_RETRIEVE",
+       .read = smdi_msg_retrieve_read,
+};
+
+static struct ast_custom_function smdi_msg_function = {
+       .name = "SMDI_MSG",
+       .read = smdi_msg_read,
+};
+
+static int _unload_module(int fromload);
+
+/*!
+ * \brief Load the module
+ *
+ * Module loading including tests for configuration or dependencies.
+ * This function can return AST_MODULE_LOAD_FAILURE, AST_MODULE_LOAD_DECLINE,
+ * or AST_MODULE_LOAD_SUCCESS. If a dependency or environment variable fails
+ * tests return AST_MODULE_LOAD_FAILURE. If the module can not load the 
+ * configuration file or other non-critical problem return 
+ * AST_MODULE_LOAD_DECLINE. On success return AST_MODULE_LOAD_SUCCESS.
+ */
+static int load_module(void)
 {
        int res;
+       smdi_loaded = 1;
 
-       /* initialize our containers */
-       memset(&smdi_ifaces, 0, sizeof(smdi_ifaces));
-       ASTOBJ_CONTAINER_INIT(&smdi_ifaces);
+       ast_mutex_init(&mwi_monitor.lock);
+       ast_cond_init(&mwi_monitor.cond, NULL);
 
        /* load the config and start the listener threads*/
        res = smdi_load(0);
        if (res < 0) {
-               return res;
+               _unload_module(1);
+               return AST_MODULE_LOAD_DECLINE;
        } else if (res == 1) {
-               ast_log(LOG_WARNING, "No SMDI interfaces are available to listen on, not starting SDMI listener.\n");
-               return 0;
-       } else
-               return 0;
+               _unload_module(1);
+               ast_log(LOG_NOTICE, "No SMDI interfaces are available to listen on, not starting SMDI listener.\n");
+               return AST_MODULE_LOAD_DECLINE;
+       }
+
+       ast_custom_function_register(&smdi_msg_retrieve_function);
+       ast_custom_function_register(&smdi_msg_function);
+
+       return AST_MODULE_LOAD_SUCCESS;
 }
 
-int unload_module(void)
+static int _unload_module(int fromload)
 {
-       /* this destructor stops any running smdi_read threads */
-       ASTOBJ_CONTAINER_DESTROYALL(&smdi_ifaces, ast_smdi_interface_destroy);
-       ASTOBJ_CONTAINER_DESTROY(&smdi_ifaces);
+       if (!smdi_loaded) {
+               return 0;
+       }
+
+       ao2_global_obj_release(smdi_ifaces);
+
+       destroy_all_mailbox_mappings();
 
+       ast_mutex_lock(&mwi_monitor.lock);
+       mwi_monitor.stop = 1;
+       ast_cond_signal(&mwi_monitor.cond);
+       ast_mutex_unlock(&mwi_monitor.lock);
+
+       if (mwi_monitor.thread != AST_PTHREADT_NULL) {
+               pthread_join(mwi_monitor.thread, NULL);
+       }
+
+       if (!fromload) {
+               ast_custom_function_unregister(&smdi_msg_retrieve_function);
+               ast_custom_function_unregister(&smdi_msg_function);
+       }
+
+       smdi_loaded = 0;
        return 0;
 }
 
-int reload(void)
+static int unload_module(void)
+{
+       return _unload_module(0);
+}
+
+static int reload(void)
 {
        int res;
 
@@ -758,16 +1430,10 @@ int reload(void)
                return 0;
 }
 
-int usecount(void)
-{
-       int res;
-
-       STANDARD_USECOUNT(res);
-
-       return res;
-}
-
-char *key()
-{
-       return ASTERISK_GPL_KEY;
-}
+AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_GLOBAL_SYMBOLS | AST_MODFLAG_LOAD_ORDER, "Simplified Message Desk Interface (SMDI) Resource",
+       .support_level = AST_MODULE_SUPPORT_CORE,
+       .load = load_module,
+       .unload = unload_module,
+       .reload = reload,
+       .load_pri = AST_MODPRI_CHANNEL_DEPEND,
+);