diff options
Diffstat (limited to 'res/res_smdi.c')
-rw-r--r-- | res/res_smdi.c | 1384 |
1 files changed, 0 insertions, 1384 deletions
diff --git a/res/res_smdi.c b/res/res_smdi.c deleted file mode 100644 index 406a740c9..000000000 --- a/res/res_smdi.c +++ /dev/null @@ -1,1384 +0,0 @@ -/* - * Asterisk -- A telephony toolkit for Linux. - * - * 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 - * any of the maintainers of this project for assistance; - * the project provides a web site, mailing lists and IRC - * channels for your use. - * - * This program is free software, distributed under the terms of - * the GNU General Public License Version 2. See the LICENSE file - * at the top of the source tree. - */ - -/*! - * \file - * \brief 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: - * \ref http://lists.digium.com/pipermail/asterisk-dev/2003-June/000884.html - */ - -#include "asterisk.h" - -ASTERISK_FILE_VERSION(__FILE__, "$Revision$") - -#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" -#include "asterisk/stringfields.h" -#include "asterisk/linkedlists.h" -#include "asterisk/app.h" -#include "asterisk/pbx.h" - -/* Message expiry time in milliseconds */ -#define SMDI_MSG_EXPIRY_TIME 30000 /* 30 seconds */ - -static const char config_file[] = "smdi.conf"; - -/*! \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); -}; - -struct ast_smdi_interface { - ASTOBJ_COMPONENTS_FULL(struct ast_smdi_interface, SMDI_MAX_FILENAME_LEN, 1); - struct ast_smdi_md_queue md_q; - ast_mutex_t md_q_lock; - ast_cond_t md_q_cond; - struct ast_smdi_mwi_queue 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; -}; - -/*! \brief SMDI interface container. */ -struct ast_smdi_interface_container { - ASTOBJ_CONTAINER_COMPONENTS(struct ast_smdi_interface); -} 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 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); - - ast_mutex_destroy(&iface->md_q_lock); - ast_cond_destroy(&iface->md_q_cond); - - ast_mutex_destroy(&iface->mwi_q_lock); - ast_cond_destroy(&iface->mwi_q_cond); - - free(iface); - - ast_module_unref(ast_module_info->self); -} - -void ast_smdi_interface_unref(struct ast_smdi_interface *iface) -{ - ASTOBJ_UNREF(iface, ast_smdi_interface_destroy); -} - -/*! - * \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) -{ - ast_mutex_lock(&iface->md_q_lock); - ASTOBJ_CONTAINER_LINK_END(&iface->md_q, md_msg); - ast_cond_broadcast(&iface->md_q_cond); - ast_mutex_unlock(&iface->md_q_lock); -} - -/*! - * \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) -{ - ast_mutex_lock(&iface->mwi_q_lock); - ASTOBJ_CONTAINER_LINK_END(&iface->mwi_q, mwi_msg); - ast_cond_broadcast(&iface->mwi_q_cond); - ast_mutex_unlock(&iface->mwi_q_lock); -} - -static int smdi_toggle_mwi(struct ast_smdi_interface *iface, const char *mailbox, int on) -{ - FILE *file; - int i; - - 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); - - fprintf(file, "%s:MWI ", on ? "OP" : "RMV"); - - 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 %s message for %s on %s\n", on ? "set" : "unset", - mailbox, iface->name); - - return 0; -} - -int ast_smdi_mwi_set(struct ast_smdi_interface *iface, const char *mailbox) -{ - return smdi_toggle_mwi(iface, mailbox, 1); -} - -int ast_smdi_mwi_unset(struct ast_smdi_interface *iface, const char *mailbox) -{ - return smdi_toggle_mwi(iface, mailbox, 0); -} - -void ast_smdi_md_message_putback(struct ast_smdi_interface *iface, struct ast_smdi_md_message *md_msg) -{ - ast_mutex_lock(&iface->md_q_lock); - ASTOBJ_CONTAINER_LINK_START(&iface->md_q, md_msg); - ast_cond_broadcast(&iface->md_q_cond); - ast_mutex_unlock(&iface->md_q_lock); -} - -void ast_smdi_mwi_message_putback(struct ast_smdi_interface *iface, struct ast_smdi_mwi_message *mwi_msg) -{ - ast_mutex_lock(&iface->mwi_q_lock); - ASTOBJ_CONTAINER_LINK_START(&iface->mwi_q, mwi_msg); - ast_cond_broadcast(&iface->mwi_q_cond); - ast_mutex_unlock(&iface->mwi_q_lock); -} - -enum smdi_message_type { - SMDI_MWI, - SMDI_MD, -}; - -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; -} - -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); - } - - return -1; -} - -static inline void *unlink_from_msg_q(struct ast_smdi_interface *iface, enum smdi_message_type type) -{ - switch (type) { - case SMDI_MWI: - return ASTOBJ_CONTAINER_UNLINK_START(&iface->mwi_q); - case SMDI_MD: - return ASTOBJ_CONTAINER_UNLINK_START(&iface->md_q); - } - - return NULL; -} - -static inline struct timeval msg_timestamp(void *msg, enum smdi_message_type type) -{ - 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); -} - -static inline void unref_msg(void *msg, enum smdi_message_type type) -{ - struct ast_smdi_md_message *md_msg = msg; - struct ast_smdi_mwi_message *mwi_msg = msg; - - switch (type) { - case SMDI_MWI: - ASTOBJ_UNREF(mwi_msg, ast_smdi_mwi_message_destroy); - case SMDI_MD: - ASTOBJ_UNREF(md_msg, ast_smdi_md_message_destroy); - } -} - -static void purge_old_messages(struct ast_smdi_interface *iface, enum smdi_message_type type) -{ - struct timeval now; - 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 (msg) { - elapsed = ast_tvdiff_ms(now, msg_timestamp(msg, type)); - - if (elapsed > iface->msg_expiry) { - /* found an expired message */ - unref_msg(msg, type); - 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; - } - unref_msg(msg, type); - break; - } - } -} - -static void *smdi_msg_pop(struct ast_smdi_interface *iface, enum smdi_message_type type) -{ - void *msg; - - purge_old_messages(iface, type); - - lock_msg_q(iface, type); - msg = unlink_from_msg_q(iface, type); - unlock_msg_q(iface, type); - - return msg; -} - -enum { - OPT_SEARCH_TERMINAL = (1 << 0), - OPT_SEARCH_NUMBER = (1 << 1), -}; - -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_test_flag(&options, OPT_SEARCH_TERMINAL)) { - struct ast_smdi_md_message *md_msg = NULL; - - /* Searching by the message desk terminal */ - - ASTOBJ_CONTAINER_TRAVERSE(&iface->md_q, !md_msg, do { - if (!strcasecmp(iterator->mesg_desk_term, search_key)) - md_msg = ASTOBJ_REF(iterator); - } while (0); ); - - msg = md_msg; - } else if (ast_test_flag(&options, OPT_SEARCH_NUMBER)) { - struct ast_smdi_md_message *md_msg = NULL; - - /* Searching by the message desk number */ - - ASTOBJ_CONTAINER_TRAVERSE(&iface->md_q, !md_msg, do { - if (!strcasecmp(iterator->mesg_desk_num, search_key)) - md_msg = ASTOBJ_REF(iterator); - } while (0); ); - - msg = md_msg; - } else { - /* Searching by the forwarding station */ - msg = ASTOBJ_CONTAINER_FIND(&iface->md_q, search_key); - } - break; - case SMDI_MWI: - msg = ASTOBJ_CONTAINER_FIND(&iface->mwi_q, search_key); - break; - } - - return msg; -} - -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; - 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 tv; - - lock_msg_q(iface, type); - - if ((msg = smdi_msg_find(iface, type, search_key, options))) { - unlock_msg_q(iface, type); - return msg; - } - - tv = ast_tvadd(start, ast_tv(0, timeout)); - ts.tv_sec = tv.tv_sec; - ts.tv_nsec = tv.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 NULL; -} - -struct ast_smdi_md_message *ast_smdi_md_message_pop(struct ast_smdi_interface *iface) -{ - return smdi_msg_pop(iface, SMDI_MD); -} - -struct ast_smdi_md_message *ast_smdi_md_message_wait(struct ast_smdi_interface *iface, int timeout) -{ - struct ast_flags options = { 0 }; - return smdi_message_wait(iface, timeout, SMDI_MD, NULL, options); -} - -struct ast_smdi_mwi_message *ast_smdi_mwi_message_pop(struct ast_smdi_interface *iface) -{ - return smdi_msg_pop(iface, SMDI_MWI); -} - -struct ast_smdi_mwi_message *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_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_smdi_interface_find(const char *iface_name) -{ - return (ASTOBJ_CONTAINER_FIND(&smdi_ifaces, iface_name)); -} - -/*! - * \internal - * \brief Read an SMDI message. - * - * \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(). - */ -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') { - ast_log(LOG_DEBUG, "Read an 'M' to start an SMDI message\n"); - start = 1; - } - continue; - } - - if (c == 'D') { /* MD message */ - start = 0; - - ast_log(LOG_DEBUG, "Read a 'D' ... it's an MD message.\n"); - - if (!(md_msg = ast_calloc(1, sizeof(*md_msg)))) { - ASTOBJ_UNREF(iface, ast_smdi_interface_destroy); - return NULL; - } - - ASTOBJ_INIT(md_msg); - - /* 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_log(LOG_DEBUG, "Read a '%c'\n", md_msg->mesg_desk_num[i]); - } - - md_msg->mesg_desk_num[sizeof(md_msg->mesg_desk_num) - 1] = '\0'; - - ast_log(LOG_DEBUG, "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_log(LOG_DEBUG, "Read a '%c'\n", md_msg->mesg_desk_term[i]); - } - - md_msg->mesg_desk_term[sizeof(md_msg->mesg_desk_term) - 1] = '\0'; - - ast_log(LOG_DEBUG, "The message desk terminal is '%s'\n", md_msg->mesg_desk_term); - - /* read the message type */ - md_msg->type = fgetc(iface->file); - - ast_log(LOG_DEBUG, "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_log(LOG_DEBUG, "Read a space, done looking for the forwarding station\n"); - break; - } - - /* store c in md_msg->fwd_st */ - if (i >= iface->msdstrip) { - ast_log(LOG_DEBUG, "Read a '%c' and stored it in the forwarding station buffer\n", c); - *cp++ = c; - } else { - ast_log(LOG_DEBUG, "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[sizeof(md_msg->fwd_st) - 1] = '\0'; - cp = NULL; - - ast_log(LOG_DEBUG, "The forwarding station is '%s'\n", md_msg->fwd_st); - - /* Put the fwd_st in the name field so that we can use ASTOBJ_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_log(LOG_DEBUG, "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) { - ast_log(LOG_DEBUG, "Read a '%c' and stored it in the calling station buffer\n", c); - *cp++ = c; - } else { - ast_log(LOG_DEBUG, "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[sizeof(md_msg->calling_st) - 1] = '\0'; - cp = NULL; - - ast_log(LOG_DEBUG, "The calling station is '%s'\n", md_msg->calling_st); - - /* 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); - - } else if (c == 'W') { /* MWI message */ - start = 0; - - ast_log(LOG_DEBUG, "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)))) { - ASTOBJ_UNREF(iface,ast_smdi_interface_destroy); - return NULL; - } - - 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 < sizeof(mwi_msg->fwd_st) - 1; i++) { - if ((c = fgetc(iface->file)) == ' ') { - *cp = '\0'; - break; - } - - /* 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 ASTOBJ_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_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; -} - -void ast_smdi_md_message_destroy(struct ast_smdi_md_message *msg) -{ - free(msg); -} - -void ast_smdi_mwi_message_destroy(struct ast_smdi_mwi_message *msg) -{ - free(msg); -} - -static void destroy_mailbox_mapping(struct mailbox_mapping *mm) -{ - ast_string_field_free_memory(mm); - ASTOBJ_UNREF(mm->iface, ast_smdi_interface_destroy); - free(mm); -} - -static void destroy_all_mailbox_mappings(void) -{ - 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); -} - -static void append_mailbox_mapping(struct ast_variable *var, struct ast_smdi_interface *iface) -{ - struct mailbox_mapping *mm; - char *mailbox, *context; - - if (!(mm = ast_calloc(1, sizeof(*mm)))) - return; - - if (ast_string_field_init(mm, 32)) { - free(mm); - 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 = ASTOBJ_REF(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; - } -} - -static void *mwi_monitor_handler(void *data) -{ - while (!mwi_monitor.stop) { - struct timespec ts = { 0, }; - struct timeval tv; - struct mailbox_mapping *mm; - - 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. */ - tv = ast_tvadd(mwi_monitor.last_poll, ast_tv(mwi_monitor.polling_interval, 0)); - ts.tv_sec = tv.tv_sec; - ts.tv_nsec = tv.tv_usec * 1000; - ast_cond_timedwait(&mwi_monitor.cond, &mwi_monitor.lock, &ts); - - ast_mutex_unlock(&mwi_monitor.lock); - } - - return NULL; -} - -static struct ast_smdi_interface *alloc_smdi_interface(void) -{ - struct ast_smdi_interface *iface; - - if (!(iface = ast_calloc(1, sizeof(*iface)))) - return NULL; - - ASTOBJ_INIT(iface); - ASTOBJ_CONTAINER_INIT(&iface->md_q); - ASTOBJ_CONTAINER_INIT(&iface->mwi_q); - - 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; -} - -/*! - * \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) { - 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); - - for (v = ast_variable_browse(conf, "interfaces"); v; v = v->next) { - 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. */ - if ((iface = ASTOBJ_CONTAINER_FIND(&smdi_ifaces, v->value))) { - ast_log(LOG_NOTICE, "SMDI interface %s already running, not restarting\n", iface->name); - ASTOBJ_UNMARK(iface); - ASTOBJ_UNREF(iface, ast_smdi_interface_destroy); - continue; - } - } - - 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; - } - - 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); - 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 */ - 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); - continue; - } - - /* set the msdstrip */ - iface->msdstrip = msdstrip; - - /* set the message expiry time */ - iface->msg_expiry = msg_expiry; - - /* start the listener thread */ - if (option_verbose > 2) - ast_verbose(VERBOSE_PREFIX_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); - ast_module_ref(ast_module_info->self); - } else { - ast_log(LOG_NOTICE, "Ignoring unknown option %s in %s\n", v->name, config_file); - } - } - - destroy_all_mailbox_mappings(); - mwi_monitor.polling_interval = DEFAULT_POLLING_INTERVAL; - - iface = NULL; - - for (v = ast_variable_browse(conf, "mailboxes"); v; v = v->next) { - if (!strcasecmp(v->name, "smdiport")) { - if (iface) - ASTOBJ_UNREF(iface, ast_smdi_interface_destroy); - - if (!(iface = ASTOBJ_CONTAINER_FIND(&smdi_ifaces, v->value))) { - ast_log(LOG_NOTICE, "SMDI interface %s not found\n", iface->name); - continue; - } - } else if (!strcasecmp(v->name, "pollinginterval")) { - if (sscanf(v->value, "%u", &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 (!iface) { - ast_log(LOG_ERROR, "Mailbox mapping ignored, no valid SMDI interface specified in mailboxes section\n"); - continue; - } - append_mailbox_mapping(v, iface); - } - } - - if (iface) - ASTOBJ_UNREF(iface, ast_smdi_interface_destroy); - - ast_config_destroy(conf); - - 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 AST_MODULE_LOAD_FAILURE; - } - - /* 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) - 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; -}; - -static void smdi_msg_datastore_destroy(void *data) -{ - struct smdi_msg_datastore *smd = data; - - if (smd->iface) - ASTOBJ_UNREF(smd->iface, ast_smdi_interface_destroy); - - if (smd->md_msg) - ASTOBJ_UNREF(smd->md_msg, ast_smdi_md_message_destroy); - - free(smd); -} - -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, 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, "%u", &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 = ASTOBJ_REF(iface); - smd->md_msg = ASTOBJ_REF(md_msg); - smd->id = ast_atomic_fetchadd_int((int *) &smdi_msg_id, 1); - snprintf(buf, len, "%u", smd->id); - - if (!(datastore = ast_channel_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: - if (iface) - ASTOBJ_UNREF(iface, ast_smdi_interface_destroy); - - if (md_msg) - ASTOBJ_UNREF(md_msg, ast_smdi_md_message_destroy); - - 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, 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", - .synopsis = "Retrieve an SMDI message.", - .syntax = "SMDI_MSG_RETRIEVE(<smdi port>,<search key>[,timeout[,options]])", - .desc = - " This function is used to retrieve an incoming SMDI message. It returns\n" - "an ID which can be used with the SMDI_MSG() function to access details of\n" - "the message. Note that this is a destructive function in the sense that\n" - "once an SMDI message is retrieved using this function, it is no longer in\n" - "the global SMDI message queue, and can not be accessed by any other Asterisk\n" - "channels. The timeout for this function is optional, and the default is\n" - "3 seconds. When providing a timeout, it should be in milliseconds.\n" - " The default search is done on the forwarding station ID. However, if\n" - "you set one of the search key options in the options field, you can change\n" - "this behavior.\n" - " Options:\n" - " t - Instead of searching on the forwarding station, search on the message\n" - " desk terminal.\n" - " n - Instead of searching on the forwarding station, search on the message\n" - " desk number.\n" - "", - .read = smdi_msg_retrieve_read, -}; - -static struct ast_custom_function smdi_msg_function = { - .name = "SMDI_MSG", - .synopsis = "Retrieve details about an SMDI message.", - .syntax = "SMDI_MSG(<message_id>,<component>)", - .desc = - " This function is used to access details of an SMDI message that was\n" - "pulled from the incoming SMDI message queue using the SMDI_MSG_RETRIEVE()\n" - "function.\n" - " Valid message components are:\n" - " number - The message desk number\n" - " terminal - The message desk terminal\n" - " station - The forwarding station\n" - " callerid - The callerID of the calling party that was forwarded\n" - " type - The call type. The value here is the exact character\n" - " that came in on the SMDI link. Typically, example values\n" - " are: D - Direct Calls, A - Forward All Calls,\n" - " B - Forward Busy Calls, N - Forward No Answer Calls\n" - "", - .read = smdi_msg_read, -}; - -static int load_module(void) -{ - int res; - - /* 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); - - ast_custom_function_register(&smdi_msg_retrieve_function); - ast_custom_function_register(&smdi_msg_function); - - /* 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 SMDI listener.\n"); - return AST_MODULE_LOAD_DECLINE; - } - - return 0; -} - -static 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); - - 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); - } - - ast_custom_function_unregister(&smdi_msg_retrieve_function); - ast_custom_function_unregister(&smdi_msg_function); - - return 0; -} - -static 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; - } else - return 0; -} - -AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_GLOBAL_SYMBOLS, "Simplified Message Desk Interface (SMDI) Resource", - .load = load_module, - .unload = unload_module, - .reload = reload, - ); |