Index: channels/chan_zap.c
===================================================================
--- channels/chan_zap.c	(revision 8223)
+++ channels/chan_zap.c	(working copy)
@@ -96,6 +96,11 @@
 #include "asterisk/term.h"
 #include "asterisk/utils.h"
 #include "asterisk/transcap.h"
+#ifdef WITH_SMDI
+#include "asterisk/smdi.h"
+#include "asterisk/astobj.h"
+#define SMDI_MD_WAIT_TIMEOUT 1500 /* 1.5 seconds */
+#endif
 
 #ifndef ZT_SIG_EM_E1
 #error "Your zaptel is too old.  please cvs update"
@@ -270,7 +275,10 @@
 static int amaflags = 0;
 
 static int adsi = 0;
-
+#ifdef WITH_SMDI
+static int use_smdi = 0;
+static char smdi_port[SMDI_MAX_FILENAME_LEN] = "/dev/ttyS0";
+#endif
 static int numbufs = 4;
 
 static int cur_prewink = -1;
@@ -596,6 +604,10 @@
 	unsigned int r2blocked:1;
 	unsigned int sigchecked:1;
 #endif
+#ifdef WITH_SMDI
+	unsigned int use_smdi:1;		/* Whether to use SMDI on this channel */
+	struct ast_smdi_interface *smdi_iface;	/* The serial port to listen for SMDI data on */
+#endif
 
 	struct zt_distRings drings;
 
@@ -2149,6 +2161,10 @@
 		p->prev->next = p->next;
 	if(p->next)
 		p->next->prev = p->prev;
+#ifdef WITH_SMDI
+	if(p->use_smdi)
+		ASTOBJ_UNREF(p->smdi_iface, ast_smdi_interface_destroy);
+#endif
 	ast_mutex_destroy(&p->lock);
 	free(p);
 	*pvt = NULL;
@@ -5177,7 +5193,7 @@
 	unsigned char buf[256];
 	char dtmfcid[300];
 	char dtmfbuf[300];
-	struct callerid_state *cs;
+	struct callerid_state *cs = NULL;
 	char *name=NULL, *number=NULL;
 	int distMatches;
 	int curRingData[3];
@@ -5185,7 +5201,9 @@
 	int counter1;
 	int counter;
 	int samples = 0;
-
+#ifdef WITH_SMDI
+	struct ast_smdi_md_message *smdi_msg = NULL;
+#endif
 	int flags;
 	int i;
 	int timeout;
@@ -5787,10 +5805,35 @@
 			}
 		}
 #endif
+#ifdef WITH_SMDI
+		/* check for SMDI messages */
+		if(p->use_smdi && p->smdi_iface) {
+			smdi_msg = ast_smdi_md_message_wait(p->smdi_iface, SMDI_MD_WAIT_TIMEOUT);
+
+			if(smdi_msg != NULL) {
+				ast_copy_string(chan->exten, smdi_msg->fwd_st, sizeof(chan->exten));
+
+				if (smdi_msg->type == 'B')
+					pbx_builtin_setvar_helper(chan, "_SMDI_VM_TYPE", "b");
+				else if (smdi_msg->type == 'N')
+					pbx_builtin_setvar_helper(chan, "_SMDI_VM_TYPE", "u");
+
+				ast_log(LOG_DEBUG, "Recieved SMDI message on %s\n", chan->name);
+			} else {
+				ast_log(LOG_WARNING, "SMDI enabled but no SMDI message present\n");
+			}
+		}
+
+		if (p->use_callerid && (p->cid_signalling == CID_SIG_SMDI && smdi_msg)) {
+	     		number = smdi_msg->calling_st;
+
 		/* If we want caller id, we're in a prering state due to a polarity reversal
 		 * and we're set to use a polarity reversal to trigger the start of caller id,
 		 * grab the caller id and wait for ringing to start... */
+		} else if (p->use_callerid && (chan->_state == AST_STATE_PRERING && p->cid_start == CID_START_POLARITY)) {
+#else
 		if (p->use_callerid && (chan->_state == AST_STATE_PRERING && p->cid_start == CID_START_POLARITY)) {
+#endif
 			/* If set to use DTMF CID signalling, listen for DTMF */
 			if (p->cid_signalling == CID_SIG_DTMF) {
 				int i = 0;
@@ -6132,7 +6175,10 @@
 			ast_shrink_phone_number(number);
 
 		ast_set_callerid(chan, number, name, number);
-
+#ifdef WITH_SMDI
+		if (smdi_msg)
+			ASTOBJ_UNREF(smdi_msg, ast_smdi_md_message_destroy);
+#endif
 		if (cs)
 			callerid_free(cs);
 		ast_setstate(chan, AST_STATE_RING);
@@ -7109,6 +7155,9 @@
 		tmp->callwaitingcallerid = callwaitingcallerid;
 		tmp->threewaycalling = threewaycalling;
 		tmp->adsi = adsi;
+#ifdef WITH_SMDI
+		tmp->use_smdi = use_smdi;
+#endif
 		tmp->permhidecallerid = hidecallerid;
 		tmp->callreturn = callreturn;
 		tmp->echocancel = echocancel;
@@ -7141,6 +7190,22 @@
 			}
 		}
 
+#ifdef WITH_SMDI
+		if (tmp->cid_signalling == CID_SIG_SMDI) {
+			if (!tmp->use_smdi) {
+				ast_log(LOG_WARNING, "SMDI callerid requires SMDI to be enabled, enabling...\n");
+				tmp->use_smdi = 1;
+			}
+		}
+		if (tmp->use_smdi) {
+			tmp->smdi_iface = ast_smdi_interface_find(smdi_port);
+			if (!(tmp->smdi_iface)) {
+				ast_log(LOG_ERROR, "Invalid SMDI port specfied, disabling SMDI support\n");
+				tmp->use_smdi = 0;
+			}
+		}
+#endif
+
 		ast_copy_string(tmp->accountcode, accountcode, sizeof(tmp->accountcode));
 		tmp->amaflags = amaflags;
 		if (!here) {
@@ -10292,6 +10357,10 @@
 				cid_signalling = CID_SIG_V23;
 			else if (!strcasecmp(v->value, "dtmf"))
 				cid_signalling = CID_SIG_DTMF;
+#ifdef WITH_SMDI
+			else if (!strcasecmp(v->value, "smdi"))
+				cid_signalling = CID_SIG_SMDI;
+#endif
 			else if (ast_true(v->value))
 				cid_signalling = CID_SIG_BELL;
 		} else if (!strcasecmp(v->name, "cidstart")) {
@@ -10314,6 +10383,12 @@
 			ast_copy_string(mailbox, v->value, sizeof(mailbox));
 		} else if (!strcasecmp(v->name, "adsi")) {
 			adsi = ast_true(v->value);
+#ifdef WITH_SMDI
+		} else if (!strcasecmp(v->name, "usesmdi")) {
+			use_smdi = ast_true(v->value);
+		} else if (!strcasecmp(v->name, "smdiport")) {
+			ast_copy_string(smdi_port, v->value, sizeof(smdi_port));
+#endif
 		} else if (!strcasecmp(v->name, "transfer")) {
 			transfer = ast_true(v->value);
 		} else if (!strcasecmp(v->name, "canpark")) {
Index: channels/Makefile
===================================================================
--- channels/Makefile	(revision 8223)
+++ channels/Makefile	(working copy)
@@ -77,6 +77,13 @@
   CHANNEL_LIBS+=chan_phone.so
 endif
 
+#
+# Asterisk SMDI integration
+#
+ifeq (${WITH_SMDI},1)
+CFLAGS+=-DWITH_SMDI
+endif
+
 ifneq ($(wildcard h323/libchanh323.a),)
   CHANNEL_LIBS+=chan_h323.so
 endif
Index: apps/Makefile
===================================================================
--- apps/Makefile	(revision 8223)
+++ apps/Makefile	(working copy)
@@ -32,6 +32,13 @@
      app_mixmonitor.so app_stack.so
 
 #
+# Asterisk SMDI integration
+#
+ifeq (${WITH_SMDI},1)
+CFLAGS+=-DWITH_SMDI
+endif
+
+#
 # Obsolete things...
 #
 #APPS+=app_sql_postgres.so
Index: apps/app_voicemail.c
===================================================================
--- apps/app_voicemail.c	(revision 8223)
+++ apps/app_voicemail.c	(working copy)
@@ -70,6 +70,10 @@
 #include "asterisk/localtime.h"
 #include "asterisk/cli.h"
 #include "asterisk/utils.h"
+#ifdef WITH_SMDI
+#include "asterisk/smdi.h"
+#define SMDI_MWI_WAIT_TIMEOUT 1000 /* 1 second */
+#endif
 #ifdef USE_ODBC_STORAGE
 #include "asterisk/res_odbc.h"
 #endif
@@ -382,7 +386,9 @@
 static char serveremail[80];
 static char mailcmd[160];	/* Configurable mail cmd */
 static char externnotify[160]; 
-
+#ifdef WITH_SMDI
+static struct ast_smdi_interface *smdi_iface = NULL;
+#endif
 static char vmfmts[80];
 static int vmminmessage;
 static int vmmaxmessage;
@@ -2316,13 +2322,34 @@
 	char arguments[255];
 	char ext_context[256] = "";
 	int newvoicemails = 0, oldvoicemails = 0;
+#ifdef WITH_SMDI
+	struct ast_smdi_mwi_message *mwi_msg;
+#endif
 
 	if (!ast_strlen_zero(context))
 		snprintf(ext_context, sizeof(ext_context), "%s@%s", extension, context);
 	else
 		ast_copy_string(ext_context, extension, sizeof(ext_context));
 
+#ifdef WITH_SMDI
+	if (!strcasecmp(externnotify, "smdi")) {
+
+		if(ast_app_has_voicemail(ext_context, NULL)) 
+			ast_smdi_mwi_set(smdi_iface, extension);
+		else
+			ast_smdi_mwi_unset(smdi_iface, extension);
+
+		mwi_msg = ast_smdi_mwi_message_wait(smdi_iface, SMDI_MWI_WAIT_TIMEOUT);
+		if(mwi_msg) {
+			ast_log(LOG_ERROR, "Error executing SMDI MWI change for %s on %s\n", extension, smdi_iface->name);
+			ASTOBJ_UNREF(mwi_msg, ast_smdi_mwi_message_destroy);
+		} else {
+			ast_log(LOG_DEBUG, "Successfully executed SMDI MWI change for %s on %s\n", extension, smdi_iface->name);
+		}
+	} else if (!ast_strlen_zero(externnotify)) {
+#else
 	if (!ast_strlen_zero(externnotify)) {
+#endif
 		if (messagecount(ext_context, &newvoicemails, &oldvoicemails)) {
 			ast_log(LOG_ERROR, "Problem in calculating number of voicemail messages available for extension %s\n", extension);
 		} else {
@@ -5817,6 +5844,9 @@
 	char *cat;
 	struct ast_variable *var;
 	char *notifystr = NULL;
+#ifdef WITH_SMDI
+	char *smdistr = NULL;
+#endif
 	char *astattach;
 	char *astsearch;
 	char *astsaycid;
@@ -5926,6 +5956,25 @@
 		if ((notifystr = ast_variable_retrieve(cfg, "general", "externnotify"))) {
 			ast_copy_string(externnotify, notifystr, sizeof(externnotify));
 			ast_log(LOG_DEBUG, "found externnotify: %s\n", externnotify);
+#ifdef WITH_SMDI
+			if(!strcasecmp(externnotify, "smdi")) {
+				ast_log(LOG_DEBUG, "Using SMDI for external voicemail notification\n");
+
+				if ((smdistr = ast_variable_retrieve(cfg, "general", "smdiport"))) {
+					smdi_iface = ast_smdi_interface_find(smdistr);
+				} else {
+					ast_log(LOG_DEBUG, "No SMDI interface set, trying default (/dev/ttyS0)\n");
+					smdi_iface = ast_smdi_interface_find("/dev/ttyS0");
+				}
+
+				if(!smdi_iface) {
+					ast_log(LOG_ERROR, "No valid SMDI interface specfied, disabling external voicemail notification\n");
+					externnotify[0] = '\0';
+				} else {
+					ast_log(LOG_DEBUG, "Using SMDI port %s\n", smdi_iface->name);
+				}
+			}
+#endif
 		} else {
 			externnotify[0] = '\0';
 		}
Index: doc/README.smdi
===================================================================
--- doc/README.smdi	(revision 0)
+++ doc/README.smdi	(revision 0)
@@ -0,0 +1,34 @@
+Asterisk SMDI (Simple Message Desk Interface) integration
+---------------------------------------------------------
+
+Support for using Asterisk as an SMDI message desk was developed by Matthew A.
+Nicholson <mnicholson@digium.com>.  To enable SMDI support in asterisk edit the
+Makefile file and uncomment the line regarding SMDI so it reads like
+this:
+
+#
+# Asterisk SMDI integration
+#
+WITH_SMDI = 1
+
+SMDI integration is configured in smdi.conf, zaptel.conf, and voicemail.conf.
+Various characteristics of the SMDI interfaces to be used (serial ports) are
+defined in smdi.conf.  SMDI integration for callerid and MWI are defined in
+zaptel.conf and voicemail.conf respectively.
+
+When SMDI is enabled and a call comes into Asterisk, the forwarding station
+number is used as the destination for the call and any callerid information
+present is used.  This way you can configure your extensions.conf as follows to
+behave as a message desk.
+
+[default]
+
+exten => _XXXXXXX,1,VoiceMail(${EXTEN}|${SMDI_VM_TYPE})
+exten => _XXXXXXX,n,Hangup
+
+exten => s,1,VoiceMailMain(${CALLERIDNUM})
+exten => s,n,Hangup
+
+The ${SMDI_VM_TYPE} variable will be set to u, b, or nothing depending on the
+contents of the type of SMDI message received.
+
Index: Makefile
===================================================================
--- Makefile	(revision 8223)
+++ Makefile	(working copy)
@@ -68,6 +68,11 @@
 #OPTIONS += -DLOW_MEMORY
 #endif
 
+#
+# Asterisk SMDI integration
+#
+#WITH_SMDI = 1
+
 # Optional debugging parameters
 DEBUG_THREADS = #-DDUMP_SCHEDULER #-DDEBUG_SCHEDULER #-DDEBUG_THREADS #-DDO_CRASH #-DDETECT_DEADLOCKS
 
Index: include/asterisk/smdi.h
===================================================================
--- include/asterisk/smdi.h	(revision 0)
+++ include/asterisk/smdi.h	(revision 0)
@@ -0,0 +1,120 @@
+/*
+ * Asterisk -- A telephony toolkit for Linux.
+ *
+ * SMDI support for Asterisk.
+ * 
+ * Copyright (C) 2005, Digium, Inc.
+ *
+ * Matthew A. Nicholson <mnicholson@digium.com>
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License.
+ */
+
+/*! 
+ * \file
+ * \brief SMDI support for Asterisk.
+ * \author Matthew A. Nicholson <mnicholson@digium.com>
+ */
+
+
+/* C is simply a ego booster for those who want to do objects the hard way. */
+
+
+#ifndef AST_SMDI_H
+#define AST_SMDI_H
+
+#include "asterisk/config.h"
+#include "asterisk/module.h"
+#include "asterisk/astobj.h"
+#include <termios.h>
+#include <time.h>
+
+#define SMDI_MESG_DESK_NUM_LEN 3
+#define SMDI_MESG_DESK_TERM_LEN 4
+#define SMDI_MWI_FAIL_CAUSE_LEN 3
+#define SMDI_MAX_STATION_NUM_LEN 10
+#define SMDI_MAX_FILENAME_LEN 256
+
+/*!
+ * \brief An SMDI message waiting indicator message.
+ *
+ * The ast_smdi_mwi_message structure contains the parsed out parts of an smdi
+ * message.  Each ast_smdi_interface structure has a message queue consisting
+ * ast_smdi_mwi_message structures. 
+ */
+struct ast_smdi_mwi_message {
+	ASTOBJ_COMPONENTS(struct ast_smdi_mwi_message);
+	char fwd_st[SMDI_MAX_STATION_NUM_LEN + 1];		/* forwarding station number */
+	char cause[SMDI_MWI_FAIL_CAUSE_LEN + 1];		/* the type of failure */
+	struct timeval timestamp;				/* a timestamp for the message */
+};
+
+/*!
+ * \brief An SMDI message desk message.
+ *
+ * The ast_smdi_md_message structure contains the parsed out parts of an smdi
+ * message.  Each ast_smdi_interface structure has a message queue consisting
+ * ast_smdi_md_message structures. 
+ */
+struct ast_smdi_md_message {
+	ASTOBJ_COMPONENTS(struct ast_smdi_md_message);
+	char mesg_desk_num[SMDI_MESG_DESK_NUM_LEN + 1];		/* message desk number */
+	char mesg_desk_term[SMDI_MESG_DESK_TERM_LEN + 1];	/* message desk terminal */
+	char fwd_st[SMDI_MAX_STATION_NUM_LEN + 1];		/* forwarding station number */
+	char calling_st[SMDI_MAX_STATION_NUM_LEN + 1];		/* calling station number */
+	char type;						/* the type of the call */
+	struct timeval timestamp;				/* a timestamp for the message */
+};
+
+/*! \brief SMDI message desk message queue. */
+struct ast_smdi_md_queue {
+	ASTOBJ_CONTAINER_COMPONENTS(struct ast_smdi_md_message);
+};
+
+/*! \brief SMDI message waiting indicator message queue. */
+struct ast_smdi_mwi_queue {
+	ASTOBJ_CONTAINER_COMPONENTS(struct ast_smdi_mwi_message);
+};
+
+/*! 
+ * \brief SMDI interface structure.
+ *
+ * The ast_smdi_interface structure holds information on a serial port that
+ * should be monitored for SMDI activity.  The structure contains a message
+ * queue of messages that have been recieved on the interface.
+ */
+struct ast_smdi_interface {
+	ASTOBJ_COMPONENTS_FULL(struct ast_smdi_interface, SMDI_MAX_FILENAME_LEN, 1);
+	struct ast_smdi_md_queue md_q;
+	struct ast_smdi_mwi_queue mwi_q;
+	FILE *file;
+	int fd;
+	pthread_t thread;
+	struct termios mode;
+	int msdstrip;
+	long msg_expiry;
+};
+
+
+/* MD message queue functions */
+extern struct ast_smdi_md_message *ast_smdi_md_message_pop(struct ast_smdi_interface *iface);
+extern struct ast_smdi_md_message *ast_smdi_md_message_wait(struct ast_smdi_interface *iface, int timeout);
+extern void ast_smdi_md_message_putback(struct ast_smdi_interface *iface, struct ast_smdi_md_message *msg);
+
+/* MWI message queue functions */
+extern struct ast_smdi_mwi_message *ast_smdi_mwi_message_pop(struct ast_smdi_interface *iface);
+extern struct ast_smdi_mwi_message *ast_smdi_mwi_message_wait(struct ast_smdi_interface *iface, int timeout);
+extern void ast_smdi_mwi_message_putback(struct ast_smdi_interface *iface, struct ast_smdi_mwi_message *msg);
+
+extern struct ast_smdi_interface *ast_smdi_interface_find(const char *iface_name);
+
+/* MWI functions */
+extern int ast_smdi_mwi_set(struct ast_smdi_interface *iface, const char *mailbox);
+extern int ast_smdi_mwi_unset(struct ast_smdi_interface *iface, const char *mailbox);
+
+extern void ast_smdi_md_message_destroy(struct ast_smdi_md_message *msg);
+extern void ast_smdi_mwi_message_destroy(struct ast_smdi_mwi_message *msg);
+
+extern void ast_smdi_interface_destroy(struct ast_smdi_interface *iface);
+#endif
Index: include/asterisk/astobj.h
===================================================================
--- include/asterisk/astobj.h	(revision 8223)
+++ include/asterisk/astobj.h	(working copy)
@@ -571,6 +571,76 @@
 		found; \
 	})
 
+/*! \brief Add an object to the end of a container.
+ *
+ * \param container A pointer to the container to operate on.
+ * \param newobj A pointer to the object to be added.
+ *
+ * This macro adds an object to the end of a container.
+ */
+#define ASTOBJ_CONTAINER_LINK_END(container,newobj) \
+	do { \
+		typeof((container)->head) iterator; \
+		typeof((container)->head) next; \
+		typeof((container)->head) prev; \
+		ASTOBJ_CONTAINER_RDLOCK(container); \
+		prev = NULL; \
+		next = (container)->head; \
+		while((iterator = next)) { \
+			next = iterator->next[0]; \
+			prev = iterator; \
+		} \
+		if(prev) { \
+			ASTOBJ_CONTAINER_WRLOCK((container)); \
+			prev->next[0] = ASTOBJ_REF(newobj); \
+			(newobj)->next[0] = NULL; \
+			ASTOBJ_CONTAINER_UNLOCK((container)); \
+		} else { \
+			ASTOBJ_CONTAINER_LINK_START((container),(newobj)); \
+		} \
+		ASTOBJ_CONTAINER_UNLOCK((container)); \
+	} while(0)
+
+/*! \brief Add an object to the front of a container.
+ *
+ * \param container A pointer to the container to operate on.
+ * \param newobj A pointer to the object to be added.
+ *
+ * This macro adds an object to the start of a container.
+ */
+#define ASTOBJ_CONTAINER_LINK_START(container,newobj) \
+	do { \
+		ASTOBJ_CONTAINER_WRLOCK(container); \
+		(newobj)->next[0] = (container)->head; \
+		(container)->head = ASTOBJ_REF(newobj); \
+		ASTOBJ_CONTAINER_UNLOCK(container); \
+	} while(0)
+
+/*! \brief Remove an object from the front of a container.
+ *
+ * \param container A pointer to the container to operate on.
+ *
+ * This macro removes the first object in a container.
+ *
+ * \note This macro does not destroy any objects, it simply unlinks
+ * them from the list.  No destructors are called.
+ *
+ * \return The container's reference to the removed object or NULL if no
+ * matching object was found.
+ */
+#define ASTOBJ_CONTAINER_UNLINK_START(container) \
+	({ \
+		typeof((container)->head) found = NULL; \
+		ASTOBJ_CONTAINER_WRLOCK(container); \
+	 	if((container)->head) { \
+	 		found = (container)->head; \
+	 		(container)->head = (container)->head->next[0]; \
+	 		found->next[0] = NULL; \
+	 	} \
+		ASTOBJ_CONTAINER_UNLOCK(container); \
+	 	found; \
+	})
+
 /*! \brief Prune marked objects from a container.
  *
  * \param container A pointer to the container to prune.
Index: include/asterisk/callerid.h
===================================================================
--- include/asterisk/callerid.h	(revision 8223)
+++ include/asterisk/callerid.h	(working copy)
@@ -41,6 +41,9 @@
 #define CID_SIG_BELL	1
 #define CID_SIG_V23	2
 #define CID_SIG_DTMF	3
+#ifdef WITH_SMDI
+#define CID_SIG_SMDI	4
+#endif
 
 #define CID_START_RING	1
 #define CID_START_POLARITY 2
Index: configs/voicemail.conf.sample
===================================================================
--- configs/voicemail.conf.sample	(revision 8223)
+++ configs/voicemail.conf.sample	(working copy)
@@ -49,12 +49,18 @@
 silencethreshold=128
 ; Max number of failed login attempts
 maxlogins=3
-; If you need to have an external program, i.e. /usr/bin/myapp called when a
-; voicemail is left, delivered, or your voicemailbox is checked, uncomment
-; this:
+
+; If you need to have an external program, i.e. /usr/bin/myapp
+; called when a voicemail is left, delivered, or your voicemailbox 
+; is checked, uncomment this.  It can also be set to 'smdi' to use
+; smdi for external notification.  If it is 'smdi', smdiport should
+; be set to a valid port as specfied in smdi.conf.
+
 ;externnotify=/usr/bin/myapp
-; If you need to have an external program, i.e. /usr/bin/myapp called when a
-; voicemail password is changed, uncomment this:
+;smdiport=/dev/ttyS0
+
+; If you need to have an external program, i.e. /usr/bin/myapp
+; called when a voicemail password is changed, uncomment this:
 ;externpass=/usr/bin/myapp
 ; For the directory, you can override the intro file if you want
 ;directoryintro=dir-intro
Index: configs/zapata.conf.sample
===================================================================
--- configs/zapata.conf.sample	(revision 8223)
+++ configs/zapata.conf.sample	(working copy)
@@ -222,6 +222,7 @@
 ;     bell     = bell202 as used in US
 ;     v23      = v23 as used in the UK
 ;     dtmf     = DTMF as used in Denmark, Sweden and Netherlands
+;     smdi     = Use SMDI for callerid.  Requires SMDI to be enabled (usesmdi).
 ;
 ;cidsignalling=bell
 ;
@@ -376,6 +377,14 @@
 ;
 ;adsi=yes
 ;
+; SMDI (Simplified Message Desk Interface) can be enabled on a per-channel
+; basis if you would like that channel to behave like an SMDI message desk.
+; The SMDI port specfied should have already been defined in smdi.conf.  The
+; default port is /dev/ttyS0.
+;
+;usesmdi=yes
+;smdiport=/dev/ttyS0
+;
 ; On trunk interfaces (FXS) and E&M interfaces (E&M, Wink, Feature Group D
 ; etc, it can be useful to perform busy detection either in an effort to 
 ; detect hangup or for detecting busies.  This enables listening for
Index: configs/smdi.conf.sample
===================================================================
--- configs/smdi.conf.sample	(revision 0)
+++ configs/smdi.conf.sample	(revision 0)
@@ -0,0 +1,44 @@
+; Asterisk SMDI configuration
+
+[interfaces]
+; Specify serial ports to listen for SMDI messages on below.  These will be
+; referenced later in zapata.conf.  If you do not specify any interfaces then
+; SMDI will be disabled.  Interfaces can have several different attributes
+; associated with them.
+
+; Set the number of stop bits to use per character here.  The default is no,
+; in which case one stop bit will be used.
+
+;twostopbits = no
+
+; Character size or bit length is the size of each character sent accross the
+; link.  Character size can be 7 or 8.  The default is 7.
+
+;charsize = 7
+
+; If you need parity checking enabled you can turn it on here.  Acceptable
+; values are even, odd, and none.  The default is even.
+
+;paritybit = even
+
+; The baudrate to use for this port.  Acceptable values are 1200, 2400, 4800,
+; and 9600.  The default is 9600.
+
+;baudrate = 1200
+
+; Often the numbering scheme for a set of mailboxes or extensions will not be 7
+; or 10 digits (as SMDI requires).  Use the msdstrip option to strip unused
+; digits from the start of numbers.
+
+;msdstrip = 0
+
+; Occasionally Asterisk and the SMDI switch may become out of sync.  If this
+; happens, Asterisk will appear one or several calls behind as it processes
+; voicemail requests.  To prevent this from hapening adjust the msgexpirytime.
+; This will make Asterisk discard old SMDI messages that have not yet been
+; processed.  The default expiry time is 30000 milliseconds.
+
+;msgexpirytime = 30000
+
+;smdiport => /dev/ttyS0
+
Index: res/res_smdi.c
===================================================================
--- res/res_smdi.c	(revision 0)
+++ res/res_smdi.c	(revision 0)
@@ -0,0 +1,795 @@
+/*
+ * Asterisk -- A telephony toolkit for Linux.
+ *
+ * SMDI support for Asterisk.
+ * 
+ * Copyright (C) 2005, Digium, Inc.
+ *
+ * Matthew A. Nicholson <mnicholson@digium.com>
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <termios.h>
+#include <sys/time.h>
+#include <time.h>
+#include <ctype.h>
+#include "asterisk/module.h"
+#include "asterisk/lock.h"
+#include "asterisk/utils.h"
+#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"
+
+/*!
+ * \file
+ * \brief SMDI support for Asterisk.
+ * \author Matthew A. Nicholson <mnicholson@digium.com>
+ */
+
+/* Message expiry time in milliseconds */
+#define SMDI_MSG_EXPIRY_TIME	30000 /* 30 seconds */
+
+static const char tdesc[] = "Asterisk Simplified Message Desk Interface (SMDI) Module";
+static const char config_file[] = "smdi.conf";
+
+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);
+
+static void *smdi_read(void *iface_p);
+static int smdi_load(int reload);
+
+/* Use count stuff */
+
+AST_MUTEX_DEFINE_STATIC(localuser_lock);
+static int localusecnt = 0;
+
+/*! \brief SMDI interface container. */
+struct ast_smdi_interface_container {
+	ASTOBJ_CONTAINER_COMPONENTS(struct ast_smdi_interface);
+} smdi_ifaces;
+
+/*! 
+ * \internal
+ * \brief Push an SMDI message to the back of an interface's message queue.
+ * \param iface a pointer to the interface to use.
+ * \param md_msg a pointer to the message to use.
+ */
+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);
+}
+
+/*!
+ * \internal
+ * \brief Push an SMDI message to the back of an interface's message queue.
+ * \param iface a pointer to the interface to use.
+ * \param mwi_msg a pointer to the message to use.
+ */
+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);
+}
+
+/*!
+ * \brief Set the MWI indicator for a mailbox.
+ * \param iface the interface to use.
+ * \param mailbox the mailbox to use.
+ */
+extern int 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;
+	}	
+
+	ASTOBJ_WRLOCK(iface);
+
+	fprintf(file, "OP:MWI ");
+
+	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);
+	return 0;
+}
+
+/*! 
+ * \brief Unset the MWI indicator for a mailbox.
+ * \param iface the interface to use.
+ * \param mailbox the mailbox to use.
+ */
+extern int ast_smdi_mwi_unset(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;
+	}	
+
+	ASTOBJ_WRLOCK(iface);
+
+	fprintf(file, "RMV:MWI ");
+
+	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 unset message for %s on %s", mailbox, iface->name);
+	return 0;
+}
+
+/*!
+ * \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.
+ */
+extern void ast_smdi_md_message_putback(struct ast_smdi_interface *iface, struct ast_smdi_md_message *md_msg)
+{
+	ASTOBJ_CONTAINER_LINK_START(&iface->md_q, md_msg);
+}
+
+/*!
+ * \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.
+ */
+extern void ast_smdi_mwi_message_putback(struct ast_smdi_interface *iface, struct ast_smdi_mwi_message *mwi_msg)
+{
+	ASTOBJ_CONTAINER_LINK_START(&iface->mwi_q, mwi_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_md_message *ast_smdi_md_message_pop(struct ast_smdi_interface *iface)
+{
+	struct ast_smdi_md_message *md_msg = ASTOBJ_CONTAINER_UNLINK_START(&iface->md_q);
+	struct timeval now;
+	long elapsed = 0;
+
+	/* purge old messages */
+	gettimeofday(&now, NULL);
+	while(md_msg)
+	{
+		/* calculate the elapsed time since this message was recieved ( in milliseconds) */
+		elapsed = (now.tv_sec - md_msg->timestamp.tv_sec) * 1000;
+		elapsed += (now.tv_usec - md_msg->timestamp.tv_usec) / 1000;
+
+		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 */
+		{
+			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)
+{
+	struct timeval start, end;
+	long diff = 0;
+	struct ast_smdi_md_message *msg;
+
+	gettimeofday(&start, NULL);
+	while(diff < timeout) {
+
+		if((msg = ast_smdi_md_message_pop(iface)))
+			return msg;
+
+		/* check timeout */
+		gettimeofday(&end, NULL);
+		diff = (end.tv_sec - start.tv_sec) * 1000;
+		diff += (end.tv_usec - start.tv_usec) / 1000;
+	}
+	return (ast_smdi_md_message_pop(iface));
+}
+
+/*!
+ * \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 */
+	gettimeofday(&now, NULL);
+	while(mwi_msg)
+	{
+		/* calculate the elapsed time since this message was recieved ( in milliseconds) */
+		elapsed = (now.tv_sec - mwi_msg->timestamp.tv_sec) * 1000;
+		elapsed += (now.tv_usec - mwi_msg->timestamp.tv_usec) / 1000;
+
+		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);
+		}
+		else /* good message, return it */
+		{
+			break;
+		}
+	}
+
+	return mwi_msg;
+	return (ASTOBJ_CONTAINER_UNLINK_START(&iface->mwi_q));
+}
+
+/*!
+ * \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)
+{
+	struct timeval start, end;
+	long diff = 0;
+	struct ast_smdi_mwi_message *msg;
+
+	gettimeofday(&start, NULL);
+	while(diff < timeout) {
+
+		if((msg = ast_smdi_mwi_message_pop(iface)))
+			return msg;
+
+		/* check timeout */
+		gettimeofday(&end, NULL);
+		diff = (end.tv_sec - start.tv_sec) * 1000;
+		diff += (end.tv_usec - start.tv_usec) / 1000;
+	}
+	return (ast_smdi_mwi_message_pop(iface));
+}
+
+/*!
+ * \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)
+{
+	return (ASTOBJ_CONTAINER_FIND(&smdi_ifaces,iface_name));
+}
+
+/*! \brief Read an SMDI message.
+ *
+ * \param iface the SMDI interface to read from.
+ *
+ * This function loops and reads from and SMDI interface.  It must be stopped
+ * using pthread_cancel().
+ */
+static void *smdi_read(void *iface_p)
+{
+	struct ast_smdi_interface *iface = iface_p;
+	struct ast_smdi_md_message *md_msg;
+	struct ast_smdi_mwi_message *mwi_msg;
+	char c = '\0';
+	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')
+				start = 1;
+		}
+		else /* Determine if this is a MD or MWI message */
+		{
+			if(c == 'D') { /* MD message */
+				start = 0;
+
+				md_msg = malloc(sizeof(struct ast_smdi_md_message));
+				if(!md_msg) {
+					ast_log(LOG_ERROR, "Error allocating memory for ast_smdi_md_message.  Stopping listner thread.\n");
+					ASTOBJ_UNREF(iface,ast_smdi_interface_destroy);
+					return NULL;
+				}
+				memset(md_msg, 0, sizeof(struct ast_smdi_md_message));
+				
+				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;
+						cp++;
+					}
+				}
+				/* 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;
+					}
+
+					/* store c in md_msg->calling_st */
+					if( i >= iface->msdstrip) {
+						*cp = c;
+						cp++;
+					}
+				}
+				/* make sure the value is null terminated, even if this truncates it */
+				md_msg->calling_st[SMDI_MAX_STATION_NUM_LEN] = '\0';
+				cp = NULL;
+
+				/* add the message to the message queue */
+				gettimeofday(&md_msg->timestamp, NULL);
+				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);
+
+			} else if(c == 'W') { /* MWI message */
+				start = 0;
+
+				mwi_msg = malloc(sizeof(struct ast_smdi_mwi_message));
+				if(!mwi_msg) {
+					ast_log(LOG_ERROR, "Error allocating memory for ast_smdi_mwi_message.  Stopping listner thread.\n");
+					ASTOBJ_UNREF(iface,ast_smdi_interface_destroy);
+					return NULL;
+				}
+				memset(mwi_msg, 0, sizeof(struct ast_smdi_mwi_message));
+				
+				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;
+					}
+
+					/* store c in md_msg->fwd_st */
+					if( i >= iface->msdstrip) {
+						*cp = c;
+						cp++;
+					}
+				}
+				/* 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 */
+				gettimeofday(&mwi_msg->timestamp, NULL);
+				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;
+			}
+		}
+	}
+
+	ast_log(LOG_ERROR, "Error reading from SMDI interface %s, stopping listener thread\n", iface->name);
+	ASTOBJ_UNREF(iface,ast_smdi_interface_destroy);
+	return NULL;
+}
+
+/*! \brief ast_smdi_md_message destructor. */
+void ast_smdi_md_message_destroy(struct ast_smdi_md_message *msg)
+{
+	free(msg);
+}
+
+/*! \brief ast_smdi_mwi_message destructor. */
+void ast_smdi_mwi_message_destroy(struct ast_smdi_mwi_message *msg)
+{
+	free(msg);
+}
+
+/*! \brief ast_smdi_interface destructor. */
+void ast_smdi_interface_destroy(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);
+	}
+	
+	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);
+
+	ast_mutex_lock(&localuser_lock);
+	localusecnt--;
+	ast_mutex_unlock(&localuser_lock);
+	ast_update_use_count();
+}
+
+/*!
+ * \internal
+ * \brief Load and reload SMDI configuration.
+ * \param reload this should be 1 if we are reloading and 0 if not.
+ *
+ * This function loads/reloads the SMDI configuration and starts and stops
+ * interfaces accordingly.
+ *
+ * \return zero on success, -1 on failure, and 1 if no smdi interfaces were started.
+ */
+static int smdi_load(int reload)
+{
+	struct ast_config *conf;
+	struct ast_variable *v;
+	struct ast_smdi_interface *iface = NULL;
+	int res = 0;
+
+	/* Config options */
+	speed_t baud_rate = B9600;     /* 9600 baud rate */
+	tcflag_t paritybit = PARENB;   /* even parity checking */
+	tcflag_t charsize = CS7;       /* seven bit characters */
+	int stopbits = 0;              /* One stop bit */
+	
+	int msdstrip = 0;              /* strip zero digits */
+	long msg_expiry = SMDI_MSG_EXPIRY_TIME;
+	
+	conf = ast_config_load(config_file);
+
+	if(!conf) {
+		ast_log(LOG_ERROR, "Unable to load config %s\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);
+
+	v = ast_variable_browse(conf, "interfaces");
+	while(v) {
+		if(!strcasecmp(v->name, "baudrate")) {
+			if(!strcasecmp(v->value, "9600")) {
+				baud_rate = B9600;
+			} else if(!strcasecmp(v->value, "4800")) {
+				baud_rate = B4800;
+			} else if(!strcasecmp(v->value, "2400")) {
+				baud_rate = B2400;
+			} 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)) {
+				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) {
+				ast_log(LOG_NOTICE, "Invalid msdstrip value in %s (line %d), using default\n", config_file, v->lineno);
+				msdstrip = 0;
+			}
+		} else if(!strcasecmp(v->name, "msgexpirytime")) {
+			if(!sscanf(v->value, "%ld", &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;
+			}
+		} else if(!strcasecmp(v->name, "paritybit")) {
+			if(!strcasecmp(v->value, "even")) {
+				paritybit = PARENB;
+			} else if(!strcasecmp(v->value, "odd")) {
+				paritybit = PARENB | PARODD;
+			} else if(!strcasecmp(v->value, "none")) {
+				paritybit = ~PARENB;
+			} else {
+				ast_log(LOG_NOTICE, "Invalid parity bit setting in %s (line %d), using default\n", config_file, v->lineno);
+				paritybit = PARENB;
+			}
+		} else if(!strcasecmp(v->name, "charsize")) {
+			if(!strcasecmp(v->value, "7")) {
+				charsize = CS7;
+			} else if(!strcasecmp(v->value, "8")) {
+				charsize = CS8;
+			} else {
+				ast_log(LOG_NOTICE, "Invalid character size setting in %s (line %d), using default\n", config_file, v->lineno);
+				charsize = CS7;
+			}
+		} else if(!strcasecmp(v->name, "twostopbits")) {
+			stopbits = ast_true(v->name);
+		} else if(!strcasecmp(v->name, "smdiport")) {
+			if(reload) {
+				/* we are reloading, check if we are already
+				 * monitoring this interface, if we are we do
+				 * not want to start it again.  This also has
+				 * the side effect of not updating different
+				 * setting for the serial port, but it should
+				 * be trivial to rewrite this section so that
+				 * options on the port are changed without
+				 * restarting the interface.  Or the interface
+				 * could be restarted with out emptying the
+				 * queue. */
+				iface = ASTOBJ_CONTAINER_FIND(&smdi_ifaces, v->value);
+				if(iface) {
+					ast_log(LOG_NOTICE, "SMDI interface %s already running, not restarting\n", iface->name);
+					ASTOBJ_UNMARK(iface);
+					ASTOBJ_UNREF(iface, ast_smdi_interface_destroy);
+					v = v->next;
+					continue;
+				}
+			}
+							
+			iface = malloc(sizeof(struct ast_smdi_interface));
+
+			ASTOBJ_INIT(iface);
+			ASTOBJ_CONTAINER_INIT(&iface->md_q);
+			ASTOBJ_CONTAINER_INIT(&iface->mwi_q);
+			iface->md_q.head = NULL;
+			iface->mwi_q.head = NULL;
+			iface->thread = AST_PTHREADT_NULL;
+			memset(&iface->mode, 0, sizeof(iface->mode));
+
+			ast_copy_string(iface->name, v->value, sizeof(iface->name));
+
+			iface->file = fopen(iface->name, "r");
+			if(!(iface->file)) {
+				ast_log(LOG_ERROR, "Error opening SMDI interface %s (%s)\n", iface->name, strerror(errno));
+				ASTOBJ_UNREF(iface, ast_smdi_interface_destroy);
+		                v = v->next;
+				continue;
+			}
+			iface->fd = fileno(iface->file);
+
+			/* Set the proper attributes for our serial port. */
+
+			/* 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);
+				v = v->next;
+				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);
+				v = v->next;
+				continue;
+			}
+			
+			/* set the stop bits */
+			if(stopbits)
+			   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 */
+
+			/* 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);
+				v = v->next;
+				continue;
+			}
+
+			/* set the msdstrip */
+			iface->msdstrip = msdstrip;
+
+			/* 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)) {
+				ast_log(LOG_ERROR, "Error starting SMDI monitor thread for %s\n", iface->name);
+				ASTOBJ_UNREF(iface, ast_smdi_interface_destroy);
+		                v = v->next;
+				continue;
+			}
+			ASTOBJ_CONTAINER_LINK(&smdi_ifaces, iface);
+			ast_mutex_lock(&localuser_lock);
+			localusecnt++;
+			ast_mutex_unlock(&localuser_lock);
+			ast_update_use_count();
+		} else {
+			ast_log(LOG_NOTICE, "Ignoring unknown option %s in %s\n", v->name, config_file);
+		}
+		v = v->next;
+	}
+	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);
+	if(!smdi_ifaces.head)
+		res = 1;
+	ASTOBJ_CONTAINER_UNLOCK(&smdi_ifaces);
+			
+	return res;
+}
+
+
+char *description(void)
+{
+	return (char *)tdesc;
+}
+
+int load_module(void)
+{
+	int res;
+
+	/* initilize our containers */
+	ASTOBJ_CONTAINER_INIT(&smdi_ifaces);
+	smdi_ifaces.head = NULL;
+
+	/* load the config and start the listener threads*/
+	res = smdi_load(0);
+	if(res < 0) {
+		return res;
+	} else if(res == 1) {
+		ast_log(LOG_WARNING, "No SMDI interfaces are available to listen on, not starting SDMI listener.\n");
+		return 0;
+	}
+
+	return 0;
+}
+
+int unload_module(void)
+{
+	/* this destructor stops any running smdi_read threads */
+	ASTOBJ_CONTAINER_DESTROYALL(&smdi_ifaces,ast_smdi_interface_destroy);
+	ASTOBJ_CONTAINER_DESTROY(&smdi_ifaces);
+
+	return 0;
+}
+
+int reload(void)
+{
+	int res;
+	res = smdi_load(1);
+
+	if(res < 0) {
+		return res;
+	} else if(res == 1) {
+		ast_log(LOG_WARNING, "No SMDI interfaces were specified to listen on, not starting SDMI listener.\n");
+		return 0;
+	}
+
+	/* notify the listner thread? */
+	return 0;
+}
+
+int usecount(void)
+{
+	int res;
+	STANDARD_USECOUNT(res);
+	return res;
+}
+
+char *key()
+{
+	return ASTERISK_GPL_KEY;
+}
+
Index: res/Makefile
===================================================================
--- res/Makefile	(revision 8223)
+++ res/Makefile	(working copy)
@@ -69,6 +69,13 @@
 CFLAGS+=-DOPENSSL_NO_KRB5 -fPIC
 endif
 
+#
+# Asterisk SMDI integration
+#
+ifeq (${WITH_SMDI},1)
+MODS+=res_smdi.so
+endif
+
 all: depend $(MODS)
 
 install: all
