Add smdi support for asterisk (see doc/smdi.txt for config info) (#5945)

git-svn-id: https://origsvn.digium.com/svn/asterisk/trunk@9423 65c4cc65-6c06-0410-ace0-fbb531ad65f3
This commit is contained in:
Matthew Fredrickson
2006-02-10 21:50:56 +00:00
parent cadfcdfe8e
commit af07dc8883
13 changed files with 1173 additions and 15 deletions

View File

@@ -73,6 +73,11 @@ DEBUG=-g3 #-pg
#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

View File

@@ -59,7 +59,12 @@ CFLAGS+=-fPIC
APPS+=app_sms.so
endif
# Asterisk SMDI integration
#
ifeq (${WITH_SMDI},1)
CFLAGS+=-DWITH_SMDI
endif
# If you have UnixODBC you can use ODBC voicemail
# storage
#

View File

@@ -73,6 +73,10 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
#include "asterisk/cli.h"
#include "asterisk/utils.h"
#include "asterisk/stringfields.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
@@ -393,7 +397,9 @@ static int silencethreshold = 128;
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;
@@ -2318,13 +2324,39 @@ static void run_externnotify(char *context, char *extension)
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);
if (!strncmp(mwi_msg->cause, "INV", 3))
ast_log(LOG_ERROR, "Invalid MWI extension: %s\n", mwi_msg->fwd_st);
else if (!strncmp(mwi_msg->cause, "BLK", 3))
ast_log(LOG_WARNING, "MWI light was already on or off for %s\n", mwi_msg->fwd_st);
ast_log(LOG_WARNING, "The switch reported '%s'\n", mwi_msg->cause);
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 {
@@ -5842,6 +5874,9 @@ static int load_config(void)
char *cat;
struct ast_variable *var;
char *notifystr = NULL;
#ifdef WITH_SMDI
char *smdistr = NULL;
#endif
char *astattach;
char *astsearch;
char *astsaycid;
@@ -5951,6 +5986,25 @@ static int load_config(void)
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';
}

View File

@@ -65,6 +65,13 @@ ifneq ($(wildcard $(CROSS_COMPILE_TARGET)/usr/include/linux/ixjuser.h)$(wildcard
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

View File

@@ -101,6 +101,11 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
#include "asterisk/utils.h"
#include "asterisk/transcap.h"
#include "asterisk/stringfields.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_HARDHDLC
#error "Your zaptel is too old. please update"
@@ -279,7 +284,10 @@ static char mailbox[AST_MAX_EXTENSION];
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;
@@ -605,6 +613,10 @@ static struct zt_pvt {
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;
@@ -2165,6 +2177,10 @@ static void destroy_zt_pvt(struct zt_pvt **pvt)
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;
@@ -5274,7 +5290,7 @@ static void *ss_thread(void *data)
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];
@@ -5282,7 +5298,9 @@ static void *ss_thread(void *data)
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;
@@ -5928,10 +5946,35 @@ lax);
}
}
#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;
@@ -6297,7 +6340,10 @@ lax);
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);
@@ -7274,6 +7320,9 @@ static struct zt_pvt *mkintf(int channel, int signalling, int radio, struct zt_p
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;
@@ -7306,6 +7355,22 @@ static struct zt_pvt *mkintf(int channel, int signalling, int radio, struct zt_p
}
}
#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) {
@@ -10479,12 +10544,12 @@ static int setup_zap(int reload)
cid_signalling = CID_SIG_V23;
else if (!strcasecmp(v->value, "dtmf"))
cid_signalling = CID_SIG_DTMF;
else if (!strcasecmp(v->value, "v23_jp"))
#ifdef ZT_EVENT_RINGBEGIN
cid_signalling = CID_SIG_V23_JP;
#else
ast_log(LOG_WARNING, "Asterisk compiled aginst Zaptel version too old to support japanese CallerID!\n");
#ifdef WITH_SMDI
else if (!strcasecmp(v->value, "smdi"))
cid_signalling = CID_SIG_SMDI;
#endif
else if (!strcasecmp(v->value, "v23_jp"))
cid_signalling = CID_SIG_V23_JP;
else if (ast_true(v->value))
cid_signalling = CID_SIG_BELL;
} else if (!strcasecmp(v->name, "cidstart")) {
@@ -10507,6 +10572,12 @@ static int setup_zap(int reload)
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")) {

43
configs/smdi.conf.sample Normal file
View File

@@ -0,0 +1,43 @@
; 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

View File

@@ -49,12 +49,18 @@ maxsilence=10
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

View File

@@ -225,6 +225,7 @@ usecallerid=yes
; v23 = v23 as used in the UK
; v23_jp = v23 as used in Japan
; dtmf = DTMF as used in Denmark, Sweden and Netherlands
; smdi = Use SMDI for callerid. Requires SMDI to be enabled (usesmdi).
;
;cidsignalling=bell
;
@@ -380,6 +381,14 @@ immediate=no
;
;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

34
doc/smdi.txt Normal file
View File

@@ -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.

View File

@@ -42,6 +42,9 @@
#define CID_SIG_V23 2
#define CID_SIG_DTMF 3
#define CID_SIG_V23_JP 4
#ifdef WITH_SMDI
#define CID_SIG_SMDI 5
#endif
#define CID_START_RING 1
#define CID_START_POLARITY 2

120
include/asterisk/smdi.h Normal file
View File

@@ -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

View File

@@ -64,6 +64,13 @@ else
CFLAGS+=-DOPENSSL_NO_KRB5 -fPIC
endif
#
# Asterisk SMDI integration
#
ifeq (${WITH_SMDI},1)
MODS+=res_smdi.so
endif
all: depend $(MODS)
install: all

794
res/res_smdi.c Normal file
View File

@@ -0,0 +1,794 @@
/*
* 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;
}