2 * Asterisk -- A telephony toolkit for Linux.
4 * Copyright (C) 2005-2008, Digium, Inc.
6 * Matthew A. Nicholson <mnicholson@digium.com>
7 * Russell Bryant <russell@digium.com>
9 * See http://www.asterisk.org for more information about
10 * the Asterisk project. Please do not directly contact
11 * any of the maintainers of this project for assistance;
12 * the project provides a web site, mailing lists and IRC
13 * channels for your use.
15 * This program is free software, distributed under the terms of
16 * the GNU General Public License Version 2. See the LICENSE file
17 * at the top of the source tree.
22 * \brief SMDI support for Asterisk.
23 * \author Matthew A. Nicholson <mnicholson@digium.com>
24 * \author Russell Bryant <russell@digium.com>
26 * Here is a useful mailing list post that describes SMDI protocol details:
27 * \ref http://lists.digium.com/pipermail/asterisk-dev/2003-June/000884.html
29 * \todo This module currently has its own mailbox monitoring thread. This should
30 * be converted to MWI subscriptions and just let the optional global voicemail
31 * polling thread handle it.
36 ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
43 #include "asterisk/module.h"
44 #include "asterisk/lock.h"
45 #include "asterisk/utils.h"
46 #define AST_API_MODULE
47 #include "asterisk/smdi.h"
48 #include "asterisk/config.h"
49 #include "asterisk/astobj.h"
50 #include "asterisk/io.h"
51 #include "asterisk/stringfields.h"
52 #include "asterisk/linkedlists.h"
53 #include "asterisk/app.h"
54 #include "asterisk/pbx.h"
55 #include "asterisk/channel.h"
57 /* Message expiry time in milliseconds */
58 #define SMDI_MSG_EXPIRY_TIME 30000 /* 30 seconds */
62 <function name="SMDI_MSG_RETRIEVE" language="en_US">
64 Retrieve an SMDI message.
67 <parameter name="smdi port" required="true" />
68 <parameter name="search key" required="true" />
69 <parameter name="timeout" />
70 <parameter name="options">
73 <para>Instead of searching on the forwarding station, search on the message desk terminal.</para>
76 <para>Instead of searching on the forwarding station, search on the message desk number.</para>
82 <para>This function is used to retrieve an incoming SMDI message. It returns
83 an ID which can be used with the SMDI_MSG() function to access details of
84 the message. Note that this is a destructive function in the sense that
85 once an SMDI message is retrieved using this function, it is no longer in
86 the global SMDI message queue, and can not be accessed by any other Asterisk
87 channels. The timeout for this function is optional, and the default is
88 3 seconds. When providing a timeout, it should be in milliseconds.
90 <para>The default search is done on the forwarding station ID. However, if
91 you set one of the search key options in the options field, you can change
96 <ref type="function">SMDI_MSG</ref>
99 <function name="SMDI_MSG" language="en_US">
101 Retrieve details about an SMDI message.
104 <parameter name="message_id" required="true" />
105 <parameter name="component" required="true">
106 <para>Valid message components are:</para>
109 <para>The message desk number</para>
111 <enum name="terminal">
112 <para>The message desk terminal</para>
114 <enum name="station">
115 <para>The forwarding station</para>
117 <enum name="callerid">
118 <para>The callerID of the calling party that was forwarded</para>
121 <para>The call type. The value here is the exact character
122 that came in on the SMDI link. Typically, example values
124 <para>Options:</para>
127 <para>Direct Calls</para>
130 <para>Forward All Calls</para>
133 <para>Forward Busy Calls</para>
136 <para>Forward No Answer Calls</para>
144 <para>This function is used to access details of an SMDI message that was
145 pulled from the incoming SMDI message queue using the SMDI_MSG_RETRIEVE()
149 <ref type="function">SMDI_MSG_RETRIEVE</ref>
154 static const char config_file[] = "smdi.conf";
156 /*! \brief SMDI message desk message queue. */
157 struct ast_smdi_md_queue {
158 ASTOBJ_CONTAINER_COMPONENTS(struct ast_smdi_md_message);
161 /*! \brief SMDI message waiting indicator message queue. */
162 struct ast_smdi_mwi_queue {
163 ASTOBJ_CONTAINER_COMPONENTS(struct ast_smdi_mwi_message);
166 struct ast_smdi_interface {
167 ASTOBJ_COMPONENTS_FULL(struct ast_smdi_interface, SMDI_MAX_FILENAME_LEN, 1);
168 struct ast_smdi_md_queue md_q;
169 ast_mutex_t md_q_lock;
170 ast_cond_t md_q_cond;
171 struct ast_smdi_mwi_queue mwi_q;
172 ast_mutex_t mwi_q_lock;
173 ast_cond_t mwi_q_cond;
182 /*! \brief SMDI interface container. */
183 static struct ast_smdi_interface_container {
184 ASTOBJ_CONTAINER_COMPONENTS(struct ast_smdi_interface);
187 /*! \brief A mapping between an SMDI mailbox ID and an Asterisk mailbox */
188 struct mailbox_mapping {
189 /*! This is the current state of the mailbox. It is simply on or
190 * off to indicate if there are messages waiting or not. */
191 unsigned int cur_state:1;
192 /*! A Pointer to the appropriate SMDI interface */
193 struct ast_smdi_interface *iface;
194 AST_DECLARE_STRING_FIELDS(
195 /*! The Name of the mailbox for the SMDI link. */
196 AST_STRING_FIELD(smdi);
197 /*! The name of the mailbox on the Asterisk side */
198 AST_STRING_FIELD(mailbox);
199 /*! The name of the voicemail context in use */
200 AST_STRING_FIELD(context);
202 AST_LIST_ENTRY(mailbox_mapping) entry;
206 #define DEFAULT_POLLING_INTERVAL 10
208 /*! \brief Data that gets used by the SMDI MWI monitoring thread */
214 /*! A list of mailboxes that need to be monitored */
215 AST_LIST_HEAD_NOLOCK(, mailbox_mapping) mailbox_mappings;
216 /*! Polling Interval for checking mailbox status */
217 unsigned int polling_interval;
218 /*! Set to 1 to tell the polling thread to stop */
220 /*! The time that the last poll began */
221 struct timeval last_poll;
223 .thread = AST_PTHREADT_NULL,
226 static void ast_smdi_interface_destroy(struct ast_smdi_interface *iface)
228 if (iface->thread != AST_PTHREADT_NULL && iface->thread != AST_PTHREADT_STOP) {
229 pthread_cancel(iface->thread);
230 pthread_join(iface->thread, NULL);
233 iface->thread = AST_PTHREADT_STOP;
238 ASTOBJ_CONTAINER_DESTROYALL(&iface->md_q, ast_smdi_md_message_destroy);
239 ASTOBJ_CONTAINER_DESTROYALL(&iface->mwi_q, ast_smdi_mwi_message_destroy);
240 ASTOBJ_CONTAINER_DESTROY(&iface->md_q);
241 ASTOBJ_CONTAINER_DESTROY(&iface->mwi_q);
243 ast_mutex_destroy(&iface->md_q_lock);
244 ast_cond_destroy(&iface->md_q_cond);
246 ast_mutex_destroy(&iface->mwi_q_lock);
247 ast_cond_destroy(&iface->mwi_q_cond);
251 ast_module_unref(ast_module_info->self);
254 void AST_OPTIONAL_API_NAME(ast_smdi_interface_unref)(struct ast_smdi_interface *iface)
256 ASTOBJ_UNREF(iface, ast_smdi_interface_destroy);
261 * \brief Push an SMDI message to the back of an interface's message queue.
262 * \param iface a pointer to the interface to use.
263 * \param md_msg a pointer to the message to use.
265 static void ast_smdi_md_message_push(struct ast_smdi_interface *iface, struct ast_smdi_md_message *md_msg)
267 ast_mutex_lock(&iface->md_q_lock);
268 ASTOBJ_CONTAINER_LINK_END(&iface->md_q, md_msg);
269 ast_cond_broadcast(&iface->md_q_cond);
270 ast_mutex_unlock(&iface->md_q_lock);
275 * \brief Push an SMDI message to the back of an interface's message queue.
276 * \param iface a pointer to the interface to use.
277 * \param mwi_msg a pointer to the message to use.
279 static void ast_smdi_mwi_message_push(struct ast_smdi_interface *iface, struct ast_smdi_mwi_message *mwi_msg)
281 ast_mutex_lock(&iface->mwi_q_lock);
282 ASTOBJ_CONTAINER_LINK_END(&iface->mwi_q, mwi_msg);
283 ast_cond_broadcast(&iface->mwi_q_cond);
284 ast_mutex_unlock(&iface->mwi_q_lock);
287 static int smdi_toggle_mwi(struct ast_smdi_interface *iface, const char *mailbox, int on)
292 if (!(file = fopen(iface->name, "w"))) {
293 ast_log(LOG_ERROR, "Error opening SMDI interface %s (%s) for writing\n", iface->name, strerror(errno));
297 ASTOBJ_WRLOCK(iface);
299 fprintf(file, "%s:MWI ", on ? "OP" : "RMV");
301 for (i = 0; i < iface->msdstrip; i++)
304 fprintf(file, "%s!\x04", mailbox);
308 ASTOBJ_UNLOCK(iface);
309 ast_debug(1, "Sent MWI set message for %s on %s\n", mailbox, iface->name);
314 int AST_OPTIONAL_API_NAME(ast_smdi_mwi_set)(struct ast_smdi_interface *iface, const char *mailbox)
316 return smdi_toggle_mwi(iface, mailbox, 1);
319 int AST_OPTIONAL_API_NAME(ast_smdi_mwi_unset)(struct ast_smdi_interface *iface, const char *mailbox)
321 return smdi_toggle_mwi(iface, mailbox, 0);
324 void AST_OPTIONAL_API_NAME(ast_smdi_md_message_putback)(struct ast_smdi_interface *iface, struct ast_smdi_md_message *md_msg)
326 ast_mutex_lock(&iface->md_q_lock);
327 ASTOBJ_CONTAINER_LINK_START(&iface->md_q, md_msg);
328 ast_cond_broadcast(&iface->md_q_cond);
329 ast_mutex_unlock(&iface->md_q_lock);
332 void AST_OPTIONAL_API_NAME(ast_smdi_mwi_message_putback)(struct ast_smdi_interface *iface, struct ast_smdi_mwi_message *mwi_msg)
334 ast_mutex_lock(&iface->mwi_q_lock);
335 ASTOBJ_CONTAINER_LINK_START(&iface->mwi_q, mwi_msg);
336 ast_cond_broadcast(&iface->mwi_q_cond);
337 ast_mutex_unlock(&iface->mwi_q_lock);
340 enum smdi_message_type {
345 static inline int lock_msg_q(struct ast_smdi_interface *iface, enum smdi_message_type type)
349 return ast_mutex_lock(&iface->mwi_q_lock);
351 return ast_mutex_lock(&iface->md_q_lock);
357 static inline int unlock_msg_q(struct ast_smdi_interface *iface, enum smdi_message_type type)
361 return ast_mutex_unlock(&iface->mwi_q_lock);
363 return ast_mutex_unlock(&iface->md_q_lock);
369 static inline void *unlink_from_msg_q(struct ast_smdi_interface *iface, enum smdi_message_type type)
373 return ASTOBJ_CONTAINER_UNLINK_START(&iface->mwi_q);
375 return ASTOBJ_CONTAINER_UNLINK_START(&iface->md_q);
380 static inline struct timeval msg_timestamp(void *msg, enum smdi_message_type type)
382 struct ast_smdi_md_message *md_msg = msg;
383 struct ast_smdi_mwi_message *mwi_msg = msg;
387 return mwi_msg->timestamp;
389 return md_msg->timestamp;
395 static inline void unref_msg(void *msg, enum smdi_message_type type)
397 struct ast_smdi_md_message *md_msg = msg;
398 struct ast_smdi_mwi_message *mwi_msg = msg;
402 ASTOBJ_UNREF(mwi_msg, ast_smdi_mwi_message_destroy);
405 ASTOBJ_UNREF(md_msg, ast_smdi_md_message_destroy);
410 static void purge_old_messages(struct ast_smdi_interface *iface, enum smdi_message_type type)
412 struct timeval now = ast_tvnow();
416 lock_msg_q(iface, type);
417 msg = unlink_from_msg_q(iface, type);
418 unlock_msg_q(iface, type);
420 /* purge old messages */
422 elapsed = ast_tvdiff_ms(now, msg_timestamp(msg, type));
424 if (elapsed > iface->msg_expiry) {
425 /* found an expired message */
426 unref_msg(msg, type);
427 ast_log(LOG_NOTICE, "Purged expired message from %s SMDI %s message queue. "
428 "Message was %ld milliseconds too old.\n",
429 iface->name, (type == SMDI_MD) ? "MD" : "MWI",
430 elapsed - iface->msg_expiry);
432 lock_msg_q(iface, type);
433 msg = unlink_from_msg_q(iface, type);
434 unlock_msg_q(iface, type);
436 /* good message, put it back and return */
439 ast_smdi_md_message_push(iface, msg);
442 ast_smdi_mwi_message_push(iface, msg);
445 unref_msg(msg, type);
451 static void *smdi_msg_pop(struct ast_smdi_interface *iface, enum smdi_message_type type)
455 purge_old_messages(iface, type);
457 lock_msg_q(iface, type);
458 msg = unlink_from_msg_q(iface, type);
459 unlock_msg_q(iface, type);
465 OPT_SEARCH_TERMINAL = (1 << 0),
466 OPT_SEARCH_NUMBER = (1 << 1),
469 static void *smdi_msg_find(struct ast_smdi_interface *iface,
470 enum smdi_message_type type, const char *search_key, struct ast_flags options)
474 purge_old_messages(iface, type);
478 if (ast_strlen_zero(search_key)) {
479 struct ast_smdi_md_message *md_msg = NULL;
481 /* No search key provided (the code from chan_dahdi does this).
482 * Just pop the top message off of the queue. */
484 ASTOBJ_CONTAINER_TRAVERSE(&iface->md_q, !md_msg, do {
485 md_msg = ASTOBJ_REF(iterator);
489 } else if (ast_test_flag(&options, OPT_SEARCH_TERMINAL)) {
490 struct ast_smdi_md_message *md_msg = NULL;
492 /* Searching by the message desk terminal */
494 ASTOBJ_CONTAINER_TRAVERSE(&iface->md_q, !md_msg, do {
495 if (!strcasecmp(iterator->mesg_desk_term, search_key))
496 md_msg = ASTOBJ_REF(iterator);
500 } else if (ast_test_flag(&options, OPT_SEARCH_NUMBER)) {
501 struct ast_smdi_md_message *md_msg = NULL;
503 /* Searching by the message desk number */
505 ASTOBJ_CONTAINER_TRAVERSE(&iface->md_q, !md_msg, do {
506 if (!strcasecmp(iterator->mesg_desk_num, search_key))
507 md_msg = ASTOBJ_REF(iterator);
512 /* Searching by the forwarding station */
513 msg = ASTOBJ_CONTAINER_FIND(&iface->md_q, search_key);
517 if (ast_strlen_zero(search_key)) {
518 struct ast_smdi_mwi_message *mwi_msg = NULL;
520 /* No search key provided (the code from chan_dahdi does this).
521 * Just pop the top message off of the queue. */
523 ASTOBJ_CONTAINER_TRAVERSE(&iface->mwi_q, !mwi_msg, do {
524 mwi_msg = ASTOBJ_REF(iterator);
529 msg = ASTOBJ_CONTAINER_FIND(&iface->mwi_q, search_key);
537 static void *smdi_message_wait(struct ast_smdi_interface *iface, int timeout,
538 enum smdi_message_type type, const char *search_key, struct ast_flags options)
540 struct timeval start;
543 ast_cond_t *cond = NULL;
544 ast_mutex_t *lock = NULL;
548 cond = &iface->mwi_q_cond;
549 lock = &iface->mwi_q_lock;
552 cond = &iface->md_q_cond;
553 lock = &iface->md_q_lock;
559 while (diff < timeout) {
560 struct timespec ts = { 0, };
563 lock_msg_q(iface, type);
565 if ((msg = smdi_msg_find(iface, type, search_key, options))) {
566 unlock_msg_q(iface, type);
570 wait = ast_tvadd(start, ast_tv(0, timeout));
571 ts.tv_sec = wait.tv_sec;
572 ts.tv_nsec = wait.tv_usec * 1000;
574 /* If there were no messages in the queue, then go to sleep until one
577 ast_cond_timedwait(cond, lock, &ts);
579 if ((msg = smdi_msg_find(iface, type, search_key, options))) {
580 unlock_msg_q(iface, type);
584 unlock_msg_q(iface, type);
587 diff = ast_tvdiff_ms(ast_tvnow(), start);
593 struct ast_smdi_md_message * AST_OPTIONAL_API_NAME(ast_smdi_md_message_pop)(struct ast_smdi_interface *iface)
595 return smdi_msg_pop(iface, SMDI_MD);
598 struct ast_smdi_md_message * AST_OPTIONAL_API_NAME(ast_smdi_md_message_wait)(struct ast_smdi_interface *iface, int timeout)
600 struct ast_flags options = { 0 };
601 return smdi_message_wait(iface, timeout, SMDI_MD, NULL, options);
604 struct ast_smdi_mwi_message * AST_OPTIONAL_API_NAME(ast_smdi_mwi_message_pop)(struct ast_smdi_interface *iface)
606 return smdi_msg_pop(iface, SMDI_MWI);
609 struct ast_smdi_mwi_message * AST_OPTIONAL_API_NAME(ast_smdi_mwi_message_wait)(struct ast_smdi_interface *iface, int timeout)
611 struct ast_flags options = { 0 };
612 return smdi_message_wait(iface, timeout, SMDI_MWI, NULL, options);
615 struct ast_smdi_mwi_message * AST_OPTIONAL_API_NAME(ast_smdi_mwi_message_wait_station)(struct ast_smdi_interface *iface, int timeout,
618 struct ast_flags options = { 0 };
619 return smdi_message_wait(iface, timeout, SMDI_MWI, station, options);
622 struct ast_smdi_interface * AST_OPTIONAL_API_NAME(ast_smdi_interface_find)(const char *iface_name)
624 return (ASTOBJ_CONTAINER_FIND(&smdi_ifaces, iface_name));
629 * \brief Read an SMDI message.
631 * \param iface_p the SMDI interface to read from.
633 * This function loops and reads from and SMDI interface. It must be stopped
634 * using pthread_cancel().
636 static void *smdi_read(void *iface_p)
638 struct ast_smdi_interface *iface = iface_p;
639 struct ast_smdi_md_message *md_msg;
640 struct ast_smdi_mwi_message *mwi_msg;
646 /* read an smdi message */
647 while ((c = fgetc(iface->file))) {
649 /* check if this is the start of a message */
652 ast_log(LOG_DEBUG, "Read an 'M' to start an SMDI message\n");
658 if (c == 'D') { /* MD message */
661 ast_log(LOG_DEBUG, "Read a 'D' ... it's an MD message.\n");
663 if (!(md_msg = ast_calloc(1, sizeof(*md_msg)))) {
664 ASTOBJ_UNREF(iface, ast_smdi_interface_destroy);
670 /* read the message desk number */
671 for (i = 0; i < sizeof(md_msg->mesg_desk_num) - 1; i++) {
672 md_msg->mesg_desk_num[i] = fgetc(iface->file);
673 ast_log(LOG_DEBUG, "Read a '%c'\n", md_msg->mesg_desk_num[i]);
676 md_msg->mesg_desk_num[sizeof(md_msg->mesg_desk_num) - 1] = '\0';
678 ast_log(LOG_DEBUG, "The message desk number is '%s'\n", md_msg->mesg_desk_num);
680 /* read the message desk terminal number */
681 for (i = 0; i < sizeof(md_msg->mesg_desk_term) - 1; i++) {
682 md_msg->mesg_desk_term[i] = fgetc(iface->file);
683 ast_log(LOG_DEBUG, "Read a '%c'\n", md_msg->mesg_desk_term[i]);
686 md_msg->mesg_desk_term[sizeof(md_msg->mesg_desk_term) - 1] = '\0';
688 ast_log(LOG_DEBUG, "The message desk terminal is '%s'\n", md_msg->mesg_desk_term);
690 /* read the message type */
691 md_msg->type = fgetc(iface->file);
693 ast_log(LOG_DEBUG, "Message type is '%c'\n", md_msg->type);
695 /* read the forwarding station number (may be blank) */
696 cp = &md_msg->fwd_st[0];
697 for (i = 0; i < sizeof(md_msg->fwd_st) - 1; i++) {
698 if ((c = fgetc(iface->file)) == ' ') {
700 ast_log(LOG_DEBUG, "Read a space, done looking for the forwarding station\n");
704 /* store c in md_msg->fwd_st */
705 if (i >= iface->msdstrip) {
706 ast_log(LOG_DEBUG, "Read a '%c' and stored it in the forwarding station buffer\n", c);
709 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);
713 /* make sure the value is null terminated, even if this truncates it */
714 md_msg->fwd_st[sizeof(md_msg->fwd_st) - 1] = '\0';
717 ast_log(LOG_DEBUG, "The forwarding station is '%s'\n", md_msg->fwd_st);
719 /* Put the fwd_st in the name field so that we can use ASTOBJ_FIND to look
720 * up a message on this field */
721 ast_copy_string(md_msg->name, md_msg->fwd_st, sizeof(md_msg->name));
723 /* read the calling station number (may be blank) */
724 cp = &md_msg->calling_st[0];
725 for (i = 0; i < sizeof(md_msg->calling_st) - 1; i++) {
726 if (!isdigit((c = fgetc(iface->file)))) {
728 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);
730 /* Don't break on a space. We may read the space before the calling station
731 * here if the forwarding station buffer filled up. */
732 i--; /* We're still on the same character */
738 /* store c in md_msg->calling_st */
739 if (i >= iface->msdstrip) {
740 ast_log(LOG_DEBUG, "Read a '%c' and stored it in the calling station buffer\n", c);
743 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);
747 /* make sure the value is null terminated, even if this truncates it */
748 md_msg->calling_st[sizeof(md_msg->calling_st) - 1] = '\0';
751 ast_log(LOG_DEBUG, "The calling station is '%s'\n", md_msg->calling_st);
753 /* add the message to the message queue */
754 md_msg->timestamp = ast_tvnow();
755 ast_smdi_md_message_push(iface, md_msg);
756 ast_log(LOG_DEBUG, "Recieved SMDI MD message on %s\n", iface->name);
758 ASTOBJ_UNREF(md_msg, ast_smdi_md_message_destroy);
760 } else if (c == 'W') { /* MWI message */
763 ast_log(LOG_DEBUG, "Read a 'W', it's an MWI message. (No more debug coming for MWI messages)\n");
765 if (!(mwi_msg = ast_calloc(1, sizeof(*mwi_msg)))) {
766 ASTOBJ_UNREF(iface,ast_smdi_interface_destroy);
770 ASTOBJ_INIT(mwi_msg);
772 /* discard the 'I' (from 'MWI') */
775 /* read the forwarding station number (may be blank) */
776 cp = &mwi_msg->fwd_st[0];
777 for (i = 0; i < sizeof(mwi_msg->fwd_st) - 1; i++) {
778 if ((c = fgetc(iface->file)) == ' ') {
783 /* store c in md_msg->fwd_st */
784 if (i >= iface->msdstrip)
788 /* make sure the station number is null terminated, even if this will truncate it */
789 mwi_msg->fwd_st[sizeof(mwi_msg->fwd_st) - 1] = '\0';
792 /* Put the fwd_st in the name field so that we can use ASTOBJ_FIND to look
793 * up a message on this field */
794 ast_copy_string(mwi_msg->name, mwi_msg->fwd_st, sizeof(mwi_msg->name));
796 /* read the mwi failure cause */
797 for (i = 0; i < sizeof(mwi_msg->cause) - 1; i++)
798 mwi_msg->cause[i] = fgetc(iface->file);
800 mwi_msg->cause[sizeof(mwi_msg->cause) - 1] = '\0';
802 /* add the message to the message queue */
803 mwi_msg->timestamp = ast_tvnow();
804 ast_smdi_mwi_message_push(iface, mwi_msg);
805 ast_log(LOG_DEBUG, "Recieved SMDI MWI message on %s\n", iface->name);
807 ASTOBJ_UNREF(mwi_msg, ast_smdi_mwi_message_destroy);
809 ast_log(LOG_ERROR, "Unknown SMDI message type recieved on %s (M%c).\n", iface->name, c);
814 ast_log(LOG_ERROR, "Error reading from SMDI interface %s, stopping listener thread\n", iface->name);
815 ASTOBJ_UNREF(iface,ast_smdi_interface_destroy);
819 void AST_OPTIONAL_API_NAME(ast_smdi_md_message_destroy)(struct ast_smdi_md_message *msg)
824 void AST_OPTIONAL_API_NAME(ast_smdi_mwi_message_destroy)(struct ast_smdi_mwi_message *msg)
829 static void destroy_mailbox_mapping(struct mailbox_mapping *mm)
831 ast_string_field_free_memory(mm);
832 ASTOBJ_UNREF(mm->iface, ast_smdi_interface_destroy);
836 static void destroy_all_mailbox_mappings(void)
838 struct mailbox_mapping *mm;
840 ast_mutex_lock(&mwi_monitor.lock);
841 while ((mm = AST_LIST_REMOVE_HEAD(&mwi_monitor.mailbox_mappings, entry)))
842 destroy_mailbox_mapping(mm);
843 ast_mutex_unlock(&mwi_monitor.lock);
846 static void append_mailbox_mapping(struct ast_variable *var, struct ast_smdi_interface *iface)
848 struct mailbox_mapping *mm;
849 char *mailbox, *context;
851 if (!(mm = ast_calloc(1, sizeof(*mm))))
854 if (ast_string_field_init(mm, 32)) {
859 ast_string_field_set(mm, smdi, var->name);
861 context = ast_strdupa(var->value);
862 mailbox = strsep(&context, "@");
863 if (ast_strlen_zero(context))
866 ast_string_field_set(mm, mailbox, mailbox);
867 ast_string_field_set(mm, context, context);
869 mm->iface = ASTOBJ_REF(iface);
871 ast_mutex_lock(&mwi_monitor.lock);
872 AST_LIST_INSERT_TAIL(&mwi_monitor.mailbox_mappings, mm, entry);
873 ast_mutex_unlock(&mwi_monitor.lock);
877 * \note Called with the mwi_monitor.lock locked
879 static void poll_mailbox(struct mailbox_mapping *mm)
884 snprintf(buf, sizeof(buf), "%s@%s", mm->mailbox, mm->context);
886 state = !!ast_app_has_voicemail(mm->mailbox, NULL);
888 if (state != mm->cur_state) {
890 ast_smdi_mwi_set(mm->iface, mm->smdi);
892 ast_smdi_mwi_unset(mm->iface, mm->smdi);
894 mm->cur_state = state;
898 static void *mwi_monitor_handler(void *data)
900 while (!mwi_monitor.stop) {
901 struct timespec ts = { 0, };
902 struct timeval polltime;
903 struct mailbox_mapping *mm;
905 ast_mutex_lock(&mwi_monitor.lock);
907 mwi_monitor.last_poll = ast_tvnow();
909 AST_LIST_TRAVERSE(&mwi_monitor.mailbox_mappings, mm, entry)
912 /* Sleep up to the configured polling interval. Allow unload_module()
913 * to signal us to wake up and exit. */
914 polltime = ast_tvadd(mwi_monitor.last_poll, ast_tv(mwi_monitor.polling_interval, 0));
915 ts.tv_sec = polltime.tv_sec;
916 ts.tv_nsec = polltime.tv_usec * 1000;
917 ast_cond_timedwait(&mwi_monitor.cond, &mwi_monitor.lock, &ts);
919 ast_mutex_unlock(&mwi_monitor.lock);
925 static struct ast_smdi_interface *alloc_smdi_interface(void)
927 struct ast_smdi_interface *iface;
929 if (!(iface = ast_calloc(1, sizeof(*iface))))
933 ASTOBJ_CONTAINER_INIT(&iface->md_q);
934 ASTOBJ_CONTAINER_INIT(&iface->mwi_q);
936 ast_mutex_init(&iface->md_q_lock);
937 ast_cond_init(&iface->md_q_cond, NULL);
939 ast_mutex_init(&iface->mwi_q_lock);
940 ast_cond_init(&iface->mwi_q_cond, NULL);
947 * \brief Load and reload SMDI configuration.
948 * \param reload this should be 1 if we are reloading and 0 if not.
950 * This function loads/reloads the SMDI configuration and starts and stops
951 * interfaces accordingly.
953 * \return zero on success, -1 on failure, and 1 if no smdi interfaces were started.
955 static int smdi_load(int reload)
957 struct ast_config *conf;
958 struct ast_variable *v;
959 struct ast_smdi_interface *iface = NULL;
960 struct ast_flags config_flags = { reload ? CONFIG_FLAG_FILEUNCHANGED : 0 };
964 speed_t baud_rate = B9600; /* 9600 baud rate */
965 tcflag_t paritybit = PARENB; /* even parity checking */
966 tcflag_t charsize = CS7; /* seven bit characters */
967 int stopbits = 0; /* One stop bit */
969 int msdstrip = 0; /* strip zero digits */
970 long msg_expiry = SMDI_MSG_EXPIRY_TIME;
972 if (!(conf = ast_config_load(config_file, config_flags)) || conf == CONFIG_STATUS_FILEINVALID) {
974 ast_log(LOG_NOTICE, "Unable to reload config %s: SMDI untouched\n", config_file);
976 ast_log(LOG_NOTICE, "Unable to load config %s: SMDI disabled\n", config_file);
978 } else if (conf == CONFIG_STATUS_FILEUNCHANGED)
981 /* Mark all interfaces that we are listening on. We will unmark them
982 * as we find them in the config file, this way we know any interfaces
983 * still marked after we have finished parsing the config file should
987 ASTOBJ_CONTAINER_MARKALL(&smdi_ifaces);
989 for (v = ast_variable_browse(conf, "interfaces"); v; v = v->next) {
990 if (!strcasecmp(v->name, "baudrate")) {
991 if (!strcasecmp(v->value, "9600"))
993 else if (!strcasecmp(v->value, "4800"))
995 else if (!strcasecmp(v->value, "2400"))
997 else if (!strcasecmp(v->value, "1200"))
1000 ast_log(LOG_NOTICE, "Invalid baud rate '%s' specified in %s (line %d), using default\n", v->value, config_file, v->lineno);
1003 } else if (!strcasecmp(v->name, "msdstrip")) {
1004 if (!sscanf(v->value, "%d", &msdstrip)) {
1005 ast_log(LOG_NOTICE, "Invalid msdstrip value in %s (line %d), using default\n", config_file, v->lineno);
1007 } else if (0 > msdstrip || msdstrip > 9) {
1008 ast_log(LOG_NOTICE, "Invalid msdstrip value in %s (line %d), using default\n", config_file, v->lineno);
1011 } else if (!strcasecmp(v->name, "msgexpirytime")) {
1012 if (!sscanf(v->value, "%ld", &msg_expiry)) {
1013 ast_log(LOG_NOTICE, "Invalid msgexpirytime value in %s (line %d), using default\n", config_file, v->lineno);
1014 msg_expiry = SMDI_MSG_EXPIRY_TIME;
1016 } else if (!strcasecmp(v->name, "paritybit")) {
1017 if (!strcasecmp(v->value, "even"))
1019 else if (!strcasecmp(v->value, "odd"))
1020 paritybit = PARENB | PARODD;
1021 else if (!strcasecmp(v->value, "none"))
1022 paritybit = ~PARENB;
1024 ast_log(LOG_NOTICE, "Invalid parity bit setting in %s (line %d), using default\n", config_file, v->lineno);
1027 } else if (!strcasecmp(v->name, "charsize")) {
1028 if (!strcasecmp(v->value, "7"))
1030 else if (!strcasecmp(v->value, "8"))
1033 ast_log(LOG_NOTICE, "Invalid character size setting in %s (line %d), using default\n", config_file, v->lineno);
1036 } else if (!strcasecmp(v->name, "twostopbits")) {
1037 stopbits = ast_true(v->name);
1038 } else if (!strcasecmp(v->name, "smdiport")) {
1040 /* we are reloading, check if we are already
1041 * monitoring this interface, if we are we do
1042 * not want to start it again. This also has
1043 * the side effect of not updating different
1044 * setting for the serial port, but it should
1045 * be trivial to rewrite this section so that
1046 * options on the port are changed without
1047 * restarting the interface. Or the interface
1048 * could be restarted with out emptying the
1050 if ((iface = ASTOBJ_CONTAINER_FIND(&smdi_ifaces, v->value))) {
1051 ast_log(LOG_NOTICE, "SMDI interface %s already running, not restarting\n", iface->name);
1052 ASTOBJ_UNMARK(iface);
1053 ASTOBJ_UNREF(iface, ast_smdi_interface_destroy);
1058 if (!(iface = alloc_smdi_interface()))
1061 ast_copy_string(iface->name, v->value, sizeof(iface->name));
1063 iface->thread = AST_PTHREADT_NULL;
1065 if (!(iface->file = fopen(iface->name, "r"))) {
1066 ast_log(LOG_ERROR, "Error opening SMDI interface %s (%s)\n", iface->name, strerror(errno));
1067 ASTOBJ_UNREF(iface, ast_smdi_interface_destroy);
1071 iface->fd = fileno(iface->file);
1073 /* Set the proper attributes for our serial port. */
1075 /* get the current attributes from the port */
1076 if (tcgetattr(iface->fd, &iface->mode)) {
1077 ast_log(LOG_ERROR, "Error getting atributes of %s (%s)\n", iface->name, strerror(errno));
1078 ASTOBJ_UNREF(iface, ast_smdi_interface_destroy);
1082 /* set the desired speed */
1083 if (cfsetispeed(&iface->mode, baud_rate) || cfsetospeed(&iface->mode, baud_rate)) {
1084 ast_log(LOG_ERROR, "Error setting baud rate on %s (%s)\n", iface->name, strerror(errno));
1085 ASTOBJ_UNREF(iface, ast_smdi_interface_destroy);
1089 /* set the stop bits */
1091 iface->mode.c_cflag = iface->mode.c_cflag | CSTOPB; /* set two stop bits */
1093 iface->mode.c_cflag = iface->mode.c_cflag & ~CSTOPB; /* set one stop bit */
1095 /* set the parity */
1096 iface->mode.c_cflag = (iface->mode.c_cflag & ~PARENB & ~PARODD) | paritybit;
1098 /* set the character size */
1099 iface->mode.c_cflag = (iface->mode.c_cflag & ~CSIZE) | charsize;
1101 /* commit the desired attributes */
1102 if (tcsetattr(iface->fd, TCSAFLUSH, &iface->mode)) {
1103 ast_log(LOG_ERROR, "Error setting attributes on %s (%s)\n", iface->name, strerror(errno));
1104 ASTOBJ_UNREF(iface, ast_smdi_interface_destroy);
1108 /* set the msdstrip */
1109 iface->msdstrip = msdstrip;
1111 /* set the message expiry time */
1112 iface->msg_expiry = msg_expiry;
1114 /* start the listener thread */
1115 ast_verb(3, "Starting SMDI monitor thread for %s\n", iface->name);
1116 if (ast_pthread_create_background(&iface->thread, NULL, smdi_read, iface)) {
1117 ast_log(LOG_ERROR, "Error starting SMDI monitor thread for %s\n", iface->name);
1118 ASTOBJ_UNREF(iface, ast_smdi_interface_destroy);
1122 ASTOBJ_CONTAINER_LINK(&smdi_ifaces, iface);
1123 ASTOBJ_UNREF(iface, ast_smdi_interface_destroy);
1124 ast_module_ref(ast_module_info->self);
1126 ast_log(LOG_NOTICE, "Ignoring unknown option %s in %s\n", v->name, config_file);
1130 destroy_all_mailbox_mappings();
1131 mwi_monitor.polling_interval = DEFAULT_POLLING_INTERVAL;
1135 for (v = ast_variable_browse(conf, "mailboxes"); v; v = v->next) {
1136 if (!strcasecmp(v->name, "smdiport")) {
1138 ASTOBJ_UNREF(iface, ast_smdi_interface_destroy);
1140 if (!(iface = ASTOBJ_CONTAINER_FIND(&smdi_ifaces, v->value))) {
1141 ast_log(LOG_NOTICE, "SMDI interface %s not found\n", iface->name);
1144 } else if (!strcasecmp(v->name, "pollinginterval")) {
1145 if (sscanf(v->value, "%u", &mwi_monitor.polling_interval) != 1) {
1146 ast_log(LOG_ERROR, "Invalid value for pollinginterval: %s\n", v->value);
1147 mwi_monitor.polling_interval = DEFAULT_POLLING_INTERVAL;
1151 ast_log(LOG_ERROR, "Mailbox mapping ignored, no valid SMDI interface specified in mailboxes section\n");
1154 append_mailbox_mapping(v, iface);
1159 ASTOBJ_UNREF(iface, ast_smdi_interface_destroy);
1161 ast_config_destroy(conf);
1163 if (!AST_LIST_EMPTY(&mwi_monitor.mailbox_mappings) && mwi_monitor.thread == AST_PTHREADT_NULL
1164 && ast_pthread_create_background(&mwi_monitor.thread, NULL, mwi_monitor_handler, NULL)) {
1165 ast_log(LOG_ERROR, "Failed to start MWI monitoring thread. This module will not operate.\n");
1166 return AST_MODULE_LOAD_FAILURE;
1169 /* Prune any interfaces we should no longer monitor. */
1171 ASTOBJ_CONTAINER_PRUNE_MARKED(&smdi_ifaces, ast_smdi_interface_destroy);
1173 ASTOBJ_CONTAINER_RDLOCK(&smdi_ifaces);
1174 /* TODO: this is bad, we need an ASTOBJ method for this! */
1175 if (!smdi_ifaces.head)
1177 ASTOBJ_CONTAINER_UNLOCK(&smdi_ifaces);
1182 struct smdi_msg_datastore {
1184 struct ast_smdi_interface *iface;
1185 struct ast_smdi_md_message *md_msg;
1188 static void smdi_msg_datastore_destroy(void *data)
1190 struct smdi_msg_datastore *smd = data;
1193 ASTOBJ_UNREF(smd->iface, ast_smdi_interface_destroy);
1196 ASTOBJ_UNREF(smd->md_msg, ast_smdi_md_message_destroy);
1201 static const struct ast_datastore_info smdi_msg_datastore_info = {
1203 .destroy = smdi_msg_datastore_destroy,
1206 static int smdi_msg_id;
1208 /*! In milliseconds */
1209 #define SMDI_RETRIEVE_TIMEOUT_DEFAULT 3000
1211 AST_APP_OPTIONS(smdi_msg_ret_options, BEGIN_OPTIONS
1212 AST_APP_OPTION('t', OPT_SEARCH_TERMINAL),
1213 AST_APP_OPTION('n', OPT_SEARCH_NUMBER),
1216 static int smdi_msg_retrieve_read(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len)
1218 struct ast_module_user *u;
1219 AST_DECLARE_APP_ARGS(args,
1221 AST_APP_ARG(search_key);
1222 AST_APP_ARG(timeout);
1223 AST_APP_ARG(options);
1225 struct ast_flags options = { 0 };
1226 unsigned int timeout = SMDI_RETRIEVE_TIMEOUT_DEFAULT;
1229 struct smdi_msg_datastore *smd = NULL;
1230 struct ast_datastore *datastore = NULL;
1231 struct ast_smdi_interface *iface = NULL;
1232 struct ast_smdi_md_message *md_msg = NULL;
1234 u = ast_module_user_add(chan);
1236 if (ast_strlen_zero(data)) {
1237 ast_log(LOG_ERROR, "SMDI_MSG_RETRIEVE requires an argument\n");
1242 ast_log(LOG_ERROR, "SMDI_MSG_RETRIEVE must be used with a channel\n");
1246 ast_autoservice_start(chan);
1248 parse = ast_strdupa(data);
1249 AST_STANDARD_APP_ARGS(args, parse);
1251 if (ast_strlen_zero(args.port) || ast_strlen_zero(args.search_key)) {
1252 ast_log(LOG_ERROR, "Invalid arguments provided to SMDI_MSG_RETRIEVE\n");
1256 if (!(iface = ast_smdi_interface_find(args.port))) {
1257 ast_log(LOG_ERROR, "SMDI port '%s' not found\n", args.port);
1261 if (!ast_strlen_zero(args.options)) {
1262 ast_app_parse_options(smdi_msg_ret_options, &options, NULL, args.options);
1265 if (!ast_strlen_zero(args.timeout)) {
1266 if (sscanf(args.timeout, "%u", &timeout) != 1) {
1267 ast_log(LOG_ERROR, "'%s' is not a valid timeout\n", args.timeout);
1268 timeout = SMDI_RETRIEVE_TIMEOUT_DEFAULT;
1272 if (!(md_msg = smdi_message_wait(iface, timeout, SMDI_MD, args.search_key, options))) {
1273 ast_log(LOG_WARNING, "No SMDI message retrieved for search key '%s' after "
1274 "waiting %u ms.\n", args.search_key, timeout);
1278 if (!(smd = ast_calloc(1, sizeof(*smd))))
1281 smd->iface = ASTOBJ_REF(iface);
1282 smd->md_msg = ASTOBJ_REF(md_msg);
1283 smd->id = ast_atomic_fetchadd_int((int *) &smdi_msg_id, 1);
1284 snprintf(buf, len, "%u", smd->id);
1286 if (!(datastore = ast_datastore_alloc(&smdi_msg_datastore_info, buf)))
1289 datastore->data = smd;
1291 ast_channel_lock(chan);
1292 ast_channel_datastore_add(chan, datastore);
1293 ast_channel_unlock(chan);
1299 ASTOBJ_UNREF(iface, ast_smdi_interface_destroy);
1302 ASTOBJ_UNREF(md_msg, ast_smdi_md_message_destroy);
1304 if (smd && !datastore)
1305 smdi_msg_datastore_destroy(smd);
1308 ast_autoservice_stop(chan);
1310 ast_module_user_remove(u);
1315 static int smdi_msg_read(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len)
1317 struct ast_module_user *u;
1319 AST_DECLARE_APP_ARGS(args,
1321 AST_APP_ARG(component);
1324 struct ast_datastore *datastore = NULL;
1325 struct smdi_msg_datastore *smd = NULL;
1327 u = ast_module_user_add(chan);
1330 ast_log(LOG_ERROR, "SMDI_MSG can not be called without a channel\n");
1334 if (ast_strlen_zero(data)) {
1335 ast_log(LOG_WARNING, "SMDI_MSG requires an argument\n");
1339 parse = ast_strdupa(data);
1340 AST_STANDARD_APP_ARGS(args, parse);
1342 if (ast_strlen_zero(args.id)) {
1343 ast_log(LOG_WARNING, "ID must be supplied to SMDI_MSG\n");
1347 if (ast_strlen_zero(args.component)) {
1348 ast_log(LOG_WARNING, "ID must be supplied to SMDI_MSG\n");
1352 ast_channel_lock(chan);
1353 datastore = ast_channel_datastore_find(chan, &smdi_msg_datastore_info, args.id);
1354 ast_channel_unlock(chan);
1357 ast_log(LOG_WARNING, "No SMDI message found for message ID '%s'\n", args.id);
1361 smd = datastore->data;
1363 if (!strcasecmp(args.component, "number")) {
1364 ast_copy_string(buf, smd->md_msg->mesg_desk_num, len);
1365 } else if (!strcasecmp(args.component, "terminal")) {
1366 ast_copy_string(buf, smd->md_msg->mesg_desk_term, len);
1367 } else if (!strcasecmp(args.component, "station")) {
1368 ast_copy_string(buf, smd->md_msg->fwd_st, len);
1369 } else if (!strcasecmp(args.component, "callerid")) {
1370 ast_copy_string(buf, smd->md_msg->calling_st, len);
1371 } else if (!strcasecmp(args.component, "type")) {
1372 snprintf(buf, len, "%c", smd->md_msg->type);
1374 ast_log(LOG_ERROR, "'%s' is not a valid message component for SMDI_MSG\n",
1382 ast_module_user_remove(u);
1387 static struct ast_custom_function smdi_msg_retrieve_function = {
1388 .name = "SMDI_MSG_RETRIEVE",
1389 .read = smdi_msg_retrieve_read,
1392 static struct ast_custom_function smdi_msg_function = {
1394 .read = smdi_msg_read,
1397 static int unload_module(void);
1399 static int load_module(void)
1403 /* initialize our containers */
1404 memset(&smdi_ifaces, 0, sizeof(smdi_ifaces));
1405 ASTOBJ_CONTAINER_INIT(&smdi_ifaces);
1407 ast_mutex_init(&mwi_monitor.lock);
1408 ast_cond_init(&mwi_monitor.cond, NULL);
1410 ast_custom_function_register(&smdi_msg_retrieve_function);
1411 ast_custom_function_register(&smdi_msg_function);
1413 /* load the config and start the listener threads*/
1418 } else if (res == 1) {
1420 ast_log(LOG_NOTICE, "No SMDI interfaces are available to listen on, not starting SMDI listener.\n");
1421 return AST_MODULE_LOAD_DECLINE;
1424 return AST_MODULE_LOAD_SUCCESS;
1427 static int unload_module(void)
1429 /* this destructor stops any running smdi_read threads */
1430 ASTOBJ_CONTAINER_DESTROYALL(&smdi_ifaces, ast_smdi_interface_destroy);
1431 ASTOBJ_CONTAINER_DESTROY(&smdi_ifaces);
1433 destroy_all_mailbox_mappings();
1435 ast_mutex_lock(&mwi_monitor.lock);
1436 mwi_monitor.stop = 1;
1437 ast_cond_signal(&mwi_monitor.cond);
1438 ast_mutex_unlock(&mwi_monitor.lock);
1440 if (mwi_monitor.thread != AST_PTHREADT_NULL) {
1441 pthread_join(mwi_monitor.thread, NULL);
1444 ast_custom_function_unregister(&smdi_msg_retrieve_function);
1445 ast_custom_function_unregister(&smdi_msg_function);
1450 static int reload(void)
1458 } else if (res == 1) {
1459 ast_log(LOG_WARNING, "No SMDI interfaces were specified to listen on, not starting SDMI listener.\n");
1465 AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_GLOBAL_SYMBOLS, "Simplified Message Desk Interface (SMDI) Resource",
1466 .load = load_module,
1467 .unload = unload_module,