2 * Asterisk -- An open source telephony toolkit.
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 * 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.
34 /*! \li \ref res_smdi.c uses the configuration file \ref smdi.conf
35 * \addtogroup configuration_file Configuration Files
39 * \page smdi.conf smdi.conf
40 * \verbinclude smdi.conf.sample
44 <support_level>core</support_level>
49 ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
56 #include "asterisk/module.h"
57 #include "asterisk/lock.h"
58 #include "asterisk/utils.h"
59 #define AST_API_MODULE
60 #include "asterisk/smdi.h"
61 #include "asterisk/config.h"
62 #include "asterisk/io.h"
63 #include "asterisk/stringfields.h"
64 #include "asterisk/linkedlists.h"
65 #include "asterisk/app.h"
66 #include "asterisk/pbx.h"
67 #include "asterisk/channel.h"
69 /* Message expiry time in milliseconds */
70 #define SMDI_MSG_EXPIRY_TIME 30000 /* 30 seconds */
74 <function name="SMDI_MSG_RETRIEVE" language="en_US">
76 Retrieve an SMDI message.
79 <parameter name="smdi port" required="true" />
80 <parameter name="search key" required="true" />
81 <parameter name="timeout" />
82 <parameter name="options">
85 <para>Instead of searching on the forwarding station, search on the message desk terminal.</para>
88 <para>Instead of searching on the forwarding station, search on the message desk number.</para>
94 <para>This function is used to retrieve an incoming SMDI message. It returns
95 an ID which can be used with the SMDI_MSG() function to access details of
96 the message. Note that this is a destructive function in the sense that
97 once an SMDI message is retrieved using this function, it is no longer in
98 the global SMDI message queue, and can not be accessed by any other Asterisk
99 channels. The timeout for this function is optional, and the default is
100 3 seconds. When providing a timeout, it should be in milliseconds.
102 <para>The default search is done on the forwarding station ID. However, if
103 you set one of the search key options in the options field, you can change
108 <ref type="function">SMDI_MSG</ref>
111 <function name="SMDI_MSG" language="en_US">
113 Retrieve details about an SMDI message.
116 <parameter name="message_id" required="true" />
117 <parameter name="component" required="true">
118 <para>Valid message components are:</para>
121 <para>The message desk number</para>
123 <enum name="terminal">
124 <para>The message desk terminal</para>
126 <enum name="station">
127 <para>The forwarding station</para>
129 <enum name="callerid">
130 <para>The callerID of the calling party that was forwarded</para>
133 <para>The call type. The value here is the exact character
134 that came in on the SMDI link. Typically, example values
136 <para>Options:</para>
139 <para>Direct Calls</para>
142 <para>Forward All Calls</para>
145 <para>Forward Busy Calls</para>
148 <para>Forward No Answer Calls</para>
156 <para>This function is used to access details of an SMDI message that was
157 pulled from the incoming SMDI message queue using the SMDI_MSG_RETRIEVE()
161 <ref type="function">SMDI_MSG_RETRIEVE</ref>
166 static const char config_file[] = "smdi.conf";
167 static int smdi_loaded;
169 struct ast_smdi_interface {
170 char name[SMDI_MAX_FILENAME_LEN];
171 struct ao2_container *md_q;
172 ast_mutex_t md_q_lock;
173 ast_cond_t md_q_cond;
174 struct ao2_container *mwi_q;
175 ast_mutex_t mwi_q_lock;
176 ast_cond_t mwi_q_cond;
185 static AO2_GLOBAL_OBJ_STATIC(smdi_ifaces);
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 smdi_interface_destroy(void *obj)
228 struct ast_smdi_interface *iface = obj;
230 if (iface->thread != AST_PTHREADT_NULL && iface->thread != AST_PTHREADT_STOP) {
231 pthread_cancel(iface->thread);
232 pthread_join(iface->thread, NULL);
235 iface->thread = AST_PTHREADT_STOP;
241 ao2_cleanup(iface->md_q);
242 ast_mutex_destroy(&iface->md_q_lock);
243 ast_cond_destroy(&iface->md_q_cond);
245 ao2_cleanup(iface->mwi_q);
246 ast_mutex_destroy(&iface->mwi_q_lock);
247 ast_cond_destroy(&iface->mwi_q_cond);
251 ast_module_unref(ast_module_info->self);
256 * \brief Push an SMDI message to the back of an interface's message queue.
257 * \param iface a pointer to the interface to use.
258 * \param md_msg a pointer to the message to use.
260 static void ast_smdi_md_message_push(struct ast_smdi_interface *iface, struct ast_smdi_md_message *md_msg)
262 ast_mutex_lock(&iface->md_q_lock);
263 ao2_link(iface->md_q, md_msg);
264 ast_cond_broadcast(&iface->md_q_cond);
265 ast_mutex_unlock(&iface->md_q_lock);
270 * \brief Push an SMDI message to the back of an interface's message queue.
271 * \param iface a pointer to the interface to use.
272 * \param mwi_msg a pointer to the message to use.
274 static void ast_smdi_mwi_message_push(struct ast_smdi_interface *iface, struct ast_smdi_mwi_message *mwi_msg)
276 ast_mutex_lock(&iface->mwi_q_lock);
277 ao2_link(iface->mwi_q, mwi_msg);
278 ast_cond_broadcast(&iface->mwi_q_cond);
279 ast_mutex_unlock(&iface->mwi_q_lock);
282 static int smdi_toggle_mwi(struct ast_smdi_interface *iface, const char *mailbox, int on)
287 if (!(file = fopen(iface->name, "w"))) {
288 ast_log(LOG_ERROR, "Error opening SMDI interface %s (%s) for writing\n", iface->name, strerror(errno));
294 fprintf(file, "%s:MWI ", on ? "OP" : "RMV");
296 for (i = 0; i < iface->msdstrip; i++)
299 fprintf(file, "%s!\x04", mailbox);
304 ast_debug(1, "Sent MWI set message for %s on %s\n", mailbox, iface->name);
309 int AST_OPTIONAL_API_NAME(ast_smdi_mwi_set)(struct ast_smdi_interface *iface, const char *mailbox)
311 return smdi_toggle_mwi(iface, mailbox, 1);
314 int AST_OPTIONAL_API_NAME(ast_smdi_mwi_unset)(struct ast_smdi_interface *iface, const char *mailbox)
316 return smdi_toggle_mwi(iface, mailbox, 0);
319 enum smdi_message_type {
324 static inline int lock_msg_q(struct ast_smdi_interface *iface, enum smdi_message_type type)
328 return ast_mutex_lock(&iface->mwi_q_lock);
330 return ast_mutex_lock(&iface->md_q_lock);
336 static inline int unlock_msg_q(struct ast_smdi_interface *iface, enum smdi_message_type type)
340 return ast_mutex_unlock(&iface->mwi_q_lock);
342 return ast_mutex_unlock(&iface->md_q_lock);
348 static inline void *unlink_from_msg_q(struct ast_smdi_interface *iface, enum smdi_message_type type)
352 return ao2_callback(iface->mwi_q, OBJ_UNLINK, NULL, NULL);
354 return ao2_callback(iface->md_q, OBJ_UNLINK, NULL, NULL);
360 static inline struct timeval msg_timestamp(void *msg, enum smdi_message_type type)
362 struct ast_smdi_md_message *md_msg = msg;
363 struct ast_smdi_mwi_message *mwi_msg = msg;
367 return mwi_msg->timestamp;
369 return md_msg->timestamp;
375 static void purge_old_messages(struct ast_smdi_interface *iface, enum smdi_message_type type)
377 struct timeval now = ast_tvnow();
381 lock_msg_q(iface, type);
382 msg = unlink_from_msg_q(iface, type);
383 unlock_msg_q(iface, type);
385 /* purge old messages */
387 elapsed = ast_tvdiff_ms(now, msg_timestamp(msg, type));
389 if (elapsed > iface->msg_expiry) {
390 /* found an expired message */
392 ast_log(LOG_NOTICE, "Purged expired message from %s SMDI %s message queue. "
393 "Message was %ld milliseconds too old.\n",
394 iface->name, (type == SMDI_MD) ? "MD" : "MWI",
395 elapsed - iface->msg_expiry);
397 lock_msg_q(iface, type);
398 msg = unlink_from_msg_q(iface, type);
399 unlock_msg_q(iface, type);
401 /* good message, put it back and return */
404 ast_smdi_md_message_push(iface, msg);
407 ast_smdi_mwi_message_push(iface, msg);
416 static void *smdi_msg_pop(struct ast_smdi_interface *iface, enum smdi_message_type type)
420 purge_old_messages(iface, type);
422 lock_msg_q(iface, type);
423 msg = unlink_from_msg_q(iface, type);
424 unlock_msg_q(iface, type);
430 OPT_SEARCH_TERMINAL = (1 << 0),
431 OPT_SEARCH_NUMBER = (1 << 1),
434 static void *smdi_msg_find(struct ast_smdi_interface *iface,
435 enum smdi_message_type type, const char *search_key, struct ast_flags options)
439 purge_old_messages(iface, type);
443 if (ast_strlen_zero(search_key)) {
444 /* No search key provided (the code from chan_dahdi does this).
445 * Just pop the top message off of the queue. */
447 msg = ao2_callback(iface->md_q, 0, NULL, NULL);
448 } else if (ast_test_flag(&options, OPT_SEARCH_TERMINAL)) {
449 /* Searching by the message desk terminal */
450 struct ast_smdi_md_message md_msg = { .name = "" };
451 strncpy(md_msg.mesg_desk_term, search_key, SMDI_MESG_DESK_TERM_LEN);
452 msg = ao2_find(iface->md_q, &md_msg, OBJ_SEARCH_OBJECT);
453 } else if (ast_test_flag(&options, OPT_SEARCH_NUMBER)) {
454 /* Searching by the message desk number */
455 struct ast_smdi_md_message md_msg = { .name = "" };
456 strncpy(md_msg.mesg_desk_num, search_key, SMDI_MESG_DESK_NUM_LEN);
457 msg = ao2_find(iface->md_q, &md_msg, OBJ_SEARCH_OBJECT);
459 /* Searching by the forwarding station */
460 msg = ao2_find(iface->md_q, search_key, OBJ_SEARCH_KEY);
464 if (ast_strlen_zero(search_key)) {
465 /* No search key provided (the code from chan_dahdi does this).
466 * Just pop the top message off of the queue. */
468 msg = ao2_callback(iface->mwi_q, 0, NULL, NULL);
470 msg = ao2_find(iface->mwi_q, search_key, OBJ_SEARCH_KEY);
478 static void *smdi_message_wait(struct ast_smdi_interface *iface, int timeout,
479 enum smdi_message_type type, const char *search_key, struct ast_flags options)
481 struct timeval start;
484 ast_cond_t *cond = NULL;
485 ast_mutex_t *lock = NULL;
489 cond = &iface->mwi_q_cond;
490 lock = &iface->mwi_q_lock;
493 cond = &iface->md_q_cond;
494 lock = &iface->md_q_lock;
500 while (diff < timeout) {
501 struct timespec ts = { 0, };
504 lock_msg_q(iface, type);
506 if ((msg = smdi_msg_find(iface, type, search_key, options))) {
507 unlock_msg_q(iface, type);
511 wait = ast_tvadd(start, ast_tv(0, timeout));
512 ts.tv_sec = wait.tv_sec;
513 ts.tv_nsec = wait.tv_usec * 1000;
515 /* If there were no messages in the queue, then go to sleep until one
518 ast_cond_timedwait(cond, lock, &ts);
520 if ((msg = smdi_msg_find(iface, type, search_key, options))) {
521 unlock_msg_q(iface, type);
525 unlock_msg_q(iface, type);
528 diff = ast_tvdiff_ms(ast_tvnow(), start);
534 struct ast_smdi_md_message * AST_OPTIONAL_API_NAME(ast_smdi_md_message_pop)(struct ast_smdi_interface *iface)
536 return smdi_msg_pop(iface, SMDI_MD);
539 struct ast_smdi_md_message * AST_OPTIONAL_API_NAME(ast_smdi_md_message_wait)(struct ast_smdi_interface *iface, int timeout)
541 struct ast_flags options = { 0 };
542 return smdi_message_wait(iface, timeout, SMDI_MD, NULL, options);
545 struct ast_smdi_mwi_message * AST_OPTIONAL_API_NAME(ast_smdi_mwi_message_pop)(struct ast_smdi_interface *iface)
547 return smdi_msg_pop(iface, SMDI_MWI);
550 struct ast_smdi_mwi_message * AST_OPTIONAL_API_NAME(ast_smdi_mwi_message_wait)(struct ast_smdi_interface *iface, int timeout)
552 struct ast_flags options = { 0 };
553 return smdi_message_wait(iface, timeout, SMDI_MWI, NULL, options);
556 struct ast_smdi_mwi_message * AST_OPTIONAL_API_NAME(ast_smdi_mwi_message_wait_station)(struct ast_smdi_interface *iface, int timeout,
559 struct ast_flags options = { 0 };
560 return smdi_message_wait(iface, timeout, SMDI_MWI, station, options);
563 struct ast_smdi_interface * AST_OPTIONAL_API_NAME(ast_smdi_interface_find)(const char *iface_name)
565 struct ao2_container *c;
566 struct ast_smdi_interface *iface = NULL;
568 c = ao2_global_obj_ref(smdi_ifaces);
570 iface = ao2_find(c, iface_name, OBJ_SEARCH_KEY);
579 * \brief Read an SMDI message.
581 * \param iface_p the SMDI interface to read from.
583 * This function loops and reads from and SMDI interface. It must be stopped
584 * using pthread_cancel().
586 static void *smdi_read(void *iface_p)
588 struct ast_smdi_interface *iface = iface_p;
589 struct ast_smdi_md_message *md_msg;
590 struct ast_smdi_mwi_message *mwi_msg;
596 /* read an smdi message */
597 while ((c = fgetc(iface->file))) {
599 /* check if this is the start of a message */
602 ast_debug(1, "Read an 'M' to start an SMDI message\n");
608 if (c == 'D') { /* MD message */
611 ast_debug(1, "Read a 'D' ... it's an MD message.\n");
613 if (!(md_msg = ast_calloc(1, sizeof(*md_msg)))) {
618 md_msg = ao2_alloc(sizeof(*md_msg), NULL);
620 /* read the message desk number */
621 for (i = 0; i < sizeof(md_msg->mesg_desk_num) - 1; i++) {
622 md_msg->mesg_desk_num[i] = fgetc(iface->file);
623 ast_debug(1, "Read a '%c'\n", md_msg->mesg_desk_num[i]);
626 md_msg->mesg_desk_num[sizeof(md_msg->mesg_desk_num) - 1] = '\0';
628 ast_debug(1, "The message desk number is '%s'\n", md_msg->mesg_desk_num);
630 /* read the message desk terminal number */
631 for (i = 0; i < sizeof(md_msg->mesg_desk_term) - 1; i++) {
632 md_msg->mesg_desk_term[i] = fgetc(iface->file);
633 ast_debug(1, "Read a '%c'\n", md_msg->mesg_desk_term[i]);
636 md_msg->mesg_desk_term[sizeof(md_msg->mesg_desk_term) - 1] = '\0';
638 ast_debug(1, "The message desk terminal is '%s'\n", md_msg->mesg_desk_term);
640 /* read the message type */
641 md_msg->type = fgetc(iface->file);
643 ast_debug(1, "Message type is '%c'\n", md_msg->type);
645 /* read the forwarding station number (may be blank) */
646 cp = &md_msg->fwd_st[0];
647 for (i = 0; i < sizeof(md_msg->fwd_st) - 1; i++) {
648 if ((c = fgetc(iface->file)) == ' ') {
650 ast_debug(1, "Read a space, done looking for the forwarding station\n");
654 /* store c in md_msg->fwd_st */
655 if (i >= iface->msdstrip) {
656 ast_debug(1, "Read a '%c' and stored it in the forwarding station buffer\n", c);
659 ast_debug(1, "Read a '%c', but didn't store it in the fwd station buffer, because of the msdstrip setting (%d < %d)\n", c, i, iface->msdstrip);
663 /* make sure the value is null terminated, even if this truncates it */
664 md_msg->fwd_st[sizeof(md_msg->fwd_st) - 1] = '\0';
667 ast_debug(1, "The forwarding station is '%s'\n", md_msg->fwd_st);
669 /* Put the fwd_st in the name field so that we can use ao2_find to look
670 * up a message on this field */
671 ast_copy_string(md_msg->name, md_msg->fwd_st, sizeof(md_msg->name));
673 /* read the calling station number (may be blank) */
674 cp = &md_msg->calling_st[0];
675 for (i = 0; i < sizeof(md_msg->calling_st) - 1; i++) {
676 if (!isdigit((c = fgetc(iface->file)))) {
678 ast_debug(1, "Read a '%c', but didn't store it in the calling station buffer because it's not a digit\n", c);
680 /* Don't break on a space. We may read the space before the calling station
681 * here if the forwarding station buffer filled up. */
682 i--; /* We're still on the same character */
688 /* store c in md_msg->calling_st */
689 if (i >= iface->msdstrip) {
690 ast_debug(1, "Read a '%c' and stored it in the calling station buffer\n", c);
693 ast_debug(1, "Read a '%c', but didn't store it in the calling station buffer, because of the msdstrip setting (%d < %d)\n", c, i, iface->msdstrip);
697 /* make sure the value is null terminated, even if this truncates it */
698 md_msg->calling_st[sizeof(md_msg->calling_st) - 1] = '\0';
701 ast_debug(1, "The calling station is '%s'\n", md_msg->calling_st);
703 /* add the message to the message queue */
704 md_msg->timestamp = ast_tvnow();
705 ast_smdi_md_message_push(iface, md_msg);
706 ast_debug(1, "Received SMDI MD message on %s\n", iface->name);
710 } else if (c == 'W') { /* MWI message */
713 ast_debug(1, "Read a 'W', it's an MWI message. (No more debug coming for MWI messages)\n");
715 if (!(mwi_msg = ast_calloc(1, sizeof(*mwi_msg)))) {
720 mwi_msg = ao2_alloc(sizeof(*mwi_msg), NULL);
722 /* discard the 'I' (from 'MWI') */
725 /* read the forwarding station number (may be blank) */
726 cp = &mwi_msg->fwd_st[0];
727 for (i = 0; i < sizeof(mwi_msg->fwd_st) - 1; i++) {
728 if ((c = fgetc(iface->file)) == ' ') {
733 /* store c in md_msg->fwd_st */
734 if (i >= iface->msdstrip)
738 /* make sure the station number is null terminated, even if this will truncate it */
739 mwi_msg->fwd_st[sizeof(mwi_msg->fwd_st) - 1] = '\0';
742 /* Put the fwd_st in the name field so that we can use ao2_find to look
743 * up a message on this field */
744 ast_copy_string(mwi_msg->name, mwi_msg->fwd_st, sizeof(mwi_msg->name));
746 /* read the mwi failure cause */
747 for (i = 0; i < sizeof(mwi_msg->cause) - 1; i++)
748 mwi_msg->cause[i] = fgetc(iface->file);
750 mwi_msg->cause[sizeof(mwi_msg->cause) - 1] = '\0';
752 /* add the message to the message queue */
753 mwi_msg->timestamp = ast_tvnow();
754 ast_smdi_mwi_message_push(iface, mwi_msg);
755 ast_debug(1, "Received SMDI MWI message on %s\n", iface->name);
757 ao2_ref(mwi_msg, -1);
759 ast_log(LOG_ERROR, "Unknown SMDI message type received on %s (M%c).\n", iface->name, c);
764 ast_log(LOG_ERROR, "Error reading from SMDI interface %s, stopping listener thread\n", iface->name);
769 static void destroy_mailbox_mapping(struct mailbox_mapping *mm)
771 ast_string_field_free_memory(mm);
772 ao2_ref(mm->iface, -1);
776 static void destroy_all_mailbox_mappings(void)
778 struct mailbox_mapping *mm;
780 ast_mutex_lock(&mwi_monitor.lock);
781 while ((mm = AST_LIST_REMOVE_HEAD(&mwi_monitor.mailbox_mappings, entry)))
782 destroy_mailbox_mapping(mm);
783 ast_mutex_unlock(&mwi_monitor.lock);
786 static void append_mailbox_mapping(struct ast_variable *var, struct ast_smdi_interface *iface)
788 struct mailbox_mapping *mm;
789 char *mailbox, *context;
791 if (!(mm = ast_calloc_with_stringfields(1, struct mailbox_mapping, 32)))
794 ast_string_field_set(mm, smdi, var->name);
796 context = ast_strdupa(var->value);
797 mailbox = strsep(&context, "@");
798 if (ast_strlen_zero(context))
801 ast_string_field_set(mm, mailbox, mailbox);
802 ast_string_field_set(mm, context, context);
804 mm->iface = ao2_bump(iface);
806 ast_mutex_lock(&mwi_monitor.lock);
807 AST_LIST_INSERT_TAIL(&mwi_monitor.mailbox_mappings, mm, entry);
808 ast_mutex_unlock(&mwi_monitor.lock);
812 * \note Called with the mwi_monitor.lock locked
814 static void poll_mailbox(struct mailbox_mapping *mm)
819 snprintf(buf, sizeof(buf), "%s@%s", mm->mailbox, mm->context);
821 state = !!ast_app_has_voicemail(mm->mailbox, NULL);
823 if (state != mm->cur_state) {
825 ast_smdi_mwi_set(mm->iface, mm->smdi);
827 ast_smdi_mwi_unset(mm->iface, mm->smdi);
829 mm->cur_state = state;
833 static void *mwi_monitor_handler(void *data)
835 while (!mwi_monitor.stop) {
836 struct timespec ts = { 0, };
837 struct timeval polltime;
838 struct mailbox_mapping *mm;
840 ast_mutex_lock(&mwi_monitor.lock);
842 mwi_monitor.last_poll = ast_tvnow();
844 AST_LIST_TRAVERSE(&mwi_monitor.mailbox_mappings, mm, entry)
847 /* Sleep up to the configured polling interval. Allow unload_module()
848 * to signal us to wake up and exit. */
849 polltime = ast_tvadd(mwi_monitor.last_poll, ast_tv(mwi_monitor.polling_interval, 0));
850 ts.tv_sec = polltime.tv_sec;
851 ts.tv_nsec = polltime.tv_usec * 1000;
852 ast_cond_timedwait(&mwi_monitor.cond, &mwi_monitor.lock, &ts);
854 ast_mutex_unlock(&mwi_monitor.lock);
860 static int smdi_mwi_q_cmp_fn(void *obj, void *data, int flags)
862 struct ast_smdi_mwi_message *msg = obj;
864 return !strcmp(msg->name, str) ? CMP_MATCH | CMP_STOP : 0;
867 static int smdi_md_q_cmp_fn(void *obj, void *arg, int flags)
869 const struct ast_smdi_md_message *msg = obj;
870 const struct ast_smdi_md_message *search_msg = arg;
871 const char *search_key = arg;
874 switch (flags & OBJ_SEARCH_MASK) {
875 case OBJ_SEARCH_OBJECT:
876 if (search_msg->mesg_desk_num) {
877 cmp = strcmp(msg->mesg_desk_num, search_msg->mesg_desk_num);
879 if (search_msg->mesg_desk_term) {
880 cmp |= strcmp(msg->mesg_desk_term, search_msg->mesg_desk_term);
884 cmp = strcmp(msg->name, search_key);
895 static struct ast_smdi_interface *alloc_smdi_interface(void)
897 struct ast_smdi_interface *iface;
899 if (!(iface = ao2_alloc(sizeof(*iface), smdi_interface_destroy))) {
903 iface->md_q = ao2_container_alloc_list(AO2_ALLOC_OPT_LOCK_NOLOCK, 0, NULL, smdi_md_q_cmp_fn);
904 iface->mwi_q = ao2_container_alloc_list(AO2_ALLOC_OPT_LOCK_NOLOCK, 0, NULL, smdi_mwi_q_cmp_fn);
906 ast_mutex_init(&iface->md_q_lock);
907 ast_cond_init(&iface->md_q_cond, NULL);
909 ast_mutex_init(&iface->mwi_q_lock);
910 ast_cond_init(&iface->mwi_q_cond, NULL);
915 static int smdi_ifaces_cmp_fn(void *obj, void *data, int flags)
917 struct ast_smdi_interface *iface = obj;
920 return !strcmp(iface->name, str) ? CMP_MATCH | CMP_STOP : 0;
925 * \brief Load and reload SMDI configuration.
926 * \param reload this should be 1 if we are reloading and 0 if not.
928 * This function loads/reloads the SMDI configuration and starts and stops
929 * interfaces accordingly.
931 * \return zero on success, -1 on failure, and 1 if no smdi interfaces were started.
933 static int smdi_load(int reload)
935 struct ast_config *conf;
936 struct ast_variable *v;
937 struct ast_flags config_flags = { reload ? CONFIG_FLAG_FILEUNCHANGED : 0 };
939 RAII_VAR(struct ao2_container *, new_ifaces, NULL, ao2_cleanup);
940 RAII_VAR(struct ao2_container *, old_ifaces, ao2_global_obj_ref(smdi_ifaces), ao2_cleanup);
941 struct ast_smdi_interface *mailbox_iface = NULL;
944 speed_t baud_rate = B9600; /* 9600 baud rate */
945 tcflag_t paritybit = PARENB; /* even parity checking */
946 tcflag_t charsize = CS7; /* seven bit characters */
947 int stopbits = 0; /* One stop bit */
949 int msdstrip = 0; /* strip zero digits */
950 long msg_expiry = SMDI_MSG_EXPIRY_TIME;
952 if (!(conf = ast_config_load(config_file, config_flags)) || conf == CONFIG_STATUS_FILEINVALID) {
954 ast_log(LOG_NOTICE, "Unable to reload config %s: SMDI untouched\n", config_file);
956 ast_log(LOG_NOTICE, "Unable to load config %s: SMDI disabled\n", config_file);
958 } else if (conf == CONFIG_STATUS_FILEUNCHANGED)
961 new_ifaces = ao2_container_alloc_list(AO2_ALLOC_OPT_LOCK_MUTEX, 0, NULL, smdi_ifaces_cmp_fn);
962 for (v = ast_variable_browse(conf, "interfaces"); v; v = v->next) {
963 RAII_VAR(struct ast_smdi_interface *, iface, NULL, ao2_cleanup);
965 if (!strcasecmp(v->name, "baudrate")) {
966 if (!strcasecmp(v->value, "9600"))
968 else if (!strcasecmp(v->value, "4800"))
970 else if (!strcasecmp(v->value, "2400"))
972 else if (!strcasecmp(v->value, "1200"))
975 ast_log(LOG_NOTICE, "Invalid baud rate '%s' specified in %s (line %d), using default\n", v->value, config_file, v->lineno);
978 } else if (!strcasecmp(v->name, "msdstrip")) {
979 if (!sscanf(v->value, "%30d", &msdstrip)) {
980 ast_log(LOG_NOTICE, "Invalid msdstrip value in %s (line %d), using default\n", config_file, v->lineno);
982 } else if (0 > msdstrip || msdstrip > 9) {
983 ast_log(LOG_NOTICE, "Invalid msdstrip value in %s (line %d), using default\n", config_file, v->lineno);
986 } else if (!strcasecmp(v->name, "msgexpirytime")) {
987 if (!sscanf(v->value, "%30ld", &msg_expiry)) {
988 ast_log(LOG_NOTICE, "Invalid msgexpirytime value in %s (line %d), using default\n", config_file, v->lineno);
989 msg_expiry = SMDI_MSG_EXPIRY_TIME;
991 } else if (!strcasecmp(v->name, "paritybit")) {
992 if (!strcasecmp(v->value, "even"))
994 else if (!strcasecmp(v->value, "odd"))
995 paritybit = PARENB | PARODD;
996 else if (!strcasecmp(v->value, "none"))
999 ast_log(LOG_NOTICE, "Invalid parity bit setting in %s (line %d), using default\n", config_file, v->lineno);
1002 } else if (!strcasecmp(v->name, "charsize")) {
1003 if (!strcasecmp(v->value, "7"))
1005 else if (!strcasecmp(v->value, "8"))
1008 ast_log(LOG_NOTICE, "Invalid character size setting in %s (line %d), using default\n", config_file, v->lineno);
1011 } else if (!strcasecmp(v->name, "twostopbits")) {
1012 stopbits = ast_true(v->name);
1013 } else if (!strcasecmp(v->name, "smdiport")) {
1015 /* we are reloading, check if we are already
1016 * monitoring this interface, if we are we do
1017 * not want to start it again. This also has
1018 * the side effect of not updating different
1019 * setting for the serial port, but it should
1020 * be trivial to rewrite this section so that
1021 * options on the port are changed without
1022 * restarting the interface. Or the interface
1023 * could be restarted with out emptying the
1025 if ((iface = ao2_find(old_ifaces, v->value, OBJ_SEARCH_KEY))) {
1026 ast_log(LOG_NOTICE, "SMDI interface %s already running, not restarting\n", iface->name);
1027 ao2_link(new_ifaces, iface);
1032 if (!(iface = alloc_smdi_interface()))
1035 ast_copy_string(iface->name, v->value, sizeof(iface->name));
1037 iface->thread = AST_PTHREADT_NULL;
1039 if (!(iface->file = fopen(iface->name, "r"))) {
1040 ast_log(LOG_ERROR, "Error opening SMDI interface %s (%s)\n", iface->name, strerror(errno));
1044 iface->fd = fileno(iface->file);
1046 /* Set the proper attributes for our serial port. */
1048 /* get the current attributes from the port */
1049 if (tcgetattr(iface->fd, &iface->mode)) {
1050 ast_log(LOG_ERROR, "Error getting atributes of %s (%s)\n", iface->name, strerror(errno));
1054 /* set the desired speed */
1055 if (cfsetispeed(&iface->mode, baud_rate) || cfsetospeed(&iface->mode, baud_rate)) {
1056 ast_log(LOG_ERROR, "Error setting baud rate on %s (%s)\n", iface->name, strerror(errno));
1060 /* set the stop bits */
1062 iface->mode.c_cflag = iface->mode.c_cflag | CSTOPB; /* set two stop bits */
1064 iface->mode.c_cflag = iface->mode.c_cflag & ~CSTOPB; /* set one stop bit */
1066 /* set the parity */
1067 iface->mode.c_cflag = (iface->mode.c_cflag & ~PARENB & ~PARODD) | paritybit;
1069 /* set the character size */
1070 iface->mode.c_cflag = (iface->mode.c_cflag & ~CSIZE) | charsize;
1072 /* commit the desired attributes */
1073 if (tcsetattr(iface->fd, TCSAFLUSH, &iface->mode)) {
1074 ast_log(LOG_ERROR, "Error setting attributes on %s (%s)\n", iface->name, strerror(errno));
1078 /* set the msdstrip */
1079 iface->msdstrip = msdstrip;
1081 /* set the message expiry time */
1082 iface->msg_expiry = msg_expiry;
1084 /* start the listener thread */
1085 ast_verb(3, "Starting SMDI monitor thread for %s\n", iface->name);
1086 if (ast_pthread_create_background(&iface->thread, NULL, smdi_read, iface)) {
1087 ast_log(LOG_ERROR, "Error starting SMDI monitor thread for %s\n", iface->name);
1091 ao2_link(new_ifaces, iface);
1092 ast_module_ref(ast_module_info->self);
1094 ast_log(LOG_NOTICE, "Ignoring unknown option %s in %s\n", v->name, config_file);
1098 destroy_all_mailbox_mappings();
1099 mwi_monitor.polling_interval = DEFAULT_POLLING_INTERVAL;
1101 for (v = ast_variable_browse(conf, "mailboxes"); v; v = v->next) {
1102 if (!strcasecmp(v->name, "smdiport")) {
1103 ao2_cleanup(mailbox_iface);
1105 if (!(mailbox_iface = ao2_find(new_ifaces, v->value, OBJ_SEARCH_KEY))) {
1106 ast_log(LOG_NOTICE, "SMDI interface %s not found\n", v->value);
1109 } else if (!strcasecmp(v->name, "pollinginterval")) {
1110 if (sscanf(v->value, "%30u", &mwi_monitor.polling_interval) != 1) {
1111 ast_log(LOG_ERROR, "Invalid value for pollinginterval: %s\n", v->value);
1112 mwi_monitor.polling_interval = DEFAULT_POLLING_INTERVAL;
1115 if (!mailbox_iface) {
1116 ast_log(LOG_ERROR, "Mailbox mapping ignored, no valid SMDI interface specified in mailboxes section\n");
1119 append_mailbox_mapping(v, mailbox_iface);
1122 ao2_cleanup(mailbox_iface);
1124 ast_config_destroy(conf);
1126 ao2_global_obj_replace_unref(smdi_ifaces, new_ifaces);
1128 if (!AST_LIST_EMPTY(&mwi_monitor.mailbox_mappings) && mwi_monitor.thread == AST_PTHREADT_NULL
1129 && ast_pthread_create_background(&mwi_monitor.thread, NULL, mwi_monitor_handler, NULL)) {
1130 ast_log(LOG_ERROR, "Failed to start MWI monitoring thread. This module will not operate.\n");
1131 return AST_MODULE_LOAD_FAILURE;
1134 if (ao2_container_count(new_ifaces)) {
1141 struct smdi_msg_datastore {
1143 struct ast_smdi_interface *iface;
1144 struct ast_smdi_md_message *md_msg;
1147 static void smdi_msg_datastore_destroy(void *data)
1149 struct smdi_msg_datastore *smd = data;
1151 ao2_cleanup(smd->iface);
1152 ao2_cleanup(smd->md_msg);
1157 static const struct ast_datastore_info smdi_msg_datastore_info = {
1159 .destroy = smdi_msg_datastore_destroy,
1162 static int smdi_msg_id;
1164 /*! In milliseconds */
1165 #define SMDI_RETRIEVE_TIMEOUT_DEFAULT 3000
1167 AST_APP_OPTIONS(smdi_msg_ret_options, BEGIN_OPTIONS
1168 AST_APP_OPTION('t', OPT_SEARCH_TERMINAL),
1169 AST_APP_OPTION('n', OPT_SEARCH_NUMBER),
1172 static int smdi_msg_retrieve_read(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len)
1174 struct ast_module_user *u;
1175 AST_DECLARE_APP_ARGS(args,
1177 AST_APP_ARG(search_key);
1178 AST_APP_ARG(timeout);
1179 AST_APP_ARG(options);
1181 struct ast_flags options = { 0 };
1182 unsigned int timeout = SMDI_RETRIEVE_TIMEOUT_DEFAULT;
1185 struct smdi_msg_datastore *smd = NULL;
1186 struct ast_datastore *datastore = NULL;
1187 struct ast_smdi_interface *iface = NULL;
1188 struct ast_smdi_md_message *md_msg = NULL;
1190 u = ast_module_user_add(chan);
1192 if (ast_strlen_zero(data)) {
1193 ast_log(LOG_ERROR, "SMDI_MSG_RETRIEVE requires an argument\n");
1198 ast_log(LOG_ERROR, "SMDI_MSG_RETRIEVE must be used with a channel\n");
1202 ast_autoservice_start(chan);
1204 parse = ast_strdupa(data);
1205 AST_STANDARD_APP_ARGS(args, parse);
1207 if (ast_strlen_zero(args.port) || ast_strlen_zero(args.search_key)) {
1208 ast_log(LOG_ERROR, "Invalid arguments provided to SMDI_MSG_RETRIEVE\n");
1212 if (!(iface = ast_smdi_interface_find(args.port))) {
1213 ast_log(LOG_ERROR, "SMDI port '%s' not found\n", args.port);
1217 if (!ast_strlen_zero(args.options)) {
1218 ast_app_parse_options(smdi_msg_ret_options, &options, NULL, args.options);
1221 if (!ast_strlen_zero(args.timeout)) {
1222 if (sscanf(args.timeout, "%30u", &timeout) != 1) {
1223 ast_log(LOG_ERROR, "'%s' is not a valid timeout\n", args.timeout);
1224 timeout = SMDI_RETRIEVE_TIMEOUT_DEFAULT;
1228 if (!(md_msg = smdi_message_wait(iface, timeout, SMDI_MD, args.search_key, options))) {
1229 ast_log(LOG_WARNING, "No SMDI message retrieved for search key '%s' after "
1230 "waiting %u ms.\n", args.search_key, timeout);
1234 if (!(smd = ast_calloc(1, sizeof(*smd))))
1237 smd->iface = ao2_bump(iface);
1238 smd->md_msg = ao2_bump(md_msg);
1239 smd->id = ast_atomic_fetchadd_int((int *) &smdi_msg_id, 1);
1240 snprintf(buf, len, "%u", smd->id);
1242 if (!(datastore = ast_datastore_alloc(&smdi_msg_datastore_info, buf)))
1245 datastore->data = smd;
1247 ast_channel_lock(chan);
1248 ast_channel_datastore_add(chan, datastore);
1249 ast_channel_unlock(chan);
1255 ao2_cleanup(md_msg);
1257 if (smd && !datastore)
1258 smdi_msg_datastore_destroy(smd);
1261 ast_autoservice_stop(chan);
1263 ast_module_user_remove(u);
1268 static int smdi_msg_read(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len)
1270 struct ast_module_user *u;
1272 AST_DECLARE_APP_ARGS(args,
1274 AST_APP_ARG(component);
1277 struct ast_datastore *datastore = NULL;
1278 struct smdi_msg_datastore *smd = NULL;
1280 u = ast_module_user_add(chan);
1283 ast_log(LOG_ERROR, "SMDI_MSG can not be called without a channel\n");
1287 if (ast_strlen_zero(data)) {
1288 ast_log(LOG_WARNING, "SMDI_MSG requires an argument\n");
1292 parse = ast_strdupa(data);
1293 AST_STANDARD_APP_ARGS(args, parse);
1295 if (ast_strlen_zero(args.id)) {
1296 ast_log(LOG_WARNING, "ID must be supplied to SMDI_MSG\n");
1300 if (ast_strlen_zero(args.component)) {
1301 ast_log(LOG_WARNING, "ID must be supplied to SMDI_MSG\n");
1305 ast_channel_lock(chan);
1306 datastore = ast_channel_datastore_find(chan, &smdi_msg_datastore_info, args.id);
1307 ast_channel_unlock(chan);
1310 ast_log(LOG_WARNING, "No SMDI message found for message ID '%s'\n", args.id);
1314 smd = datastore->data;
1316 if (!strcasecmp(args.component, "number")) {
1317 ast_copy_string(buf, smd->md_msg->mesg_desk_num, len);
1318 } else if (!strcasecmp(args.component, "terminal")) {
1319 ast_copy_string(buf, smd->md_msg->mesg_desk_term, len);
1320 } else if (!strcasecmp(args.component, "station")) {
1321 ast_copy_string(buf, smd->md_msg->fwd_st, len);
1322 } else if (!strcasecmp(args.component, "callerid")) {
1323 ast_copy_string(buf, smd->md_msg->calling_st, len);
1324 } else if (!strcasecmp(args.component, "type")) {
1325 snprintf(buf, len, "%c", smd->md_msg->type);
1327 ast_log(LOG_ERROR, "'%s' is not a valid message component for SMDI_MSG\n",
1335 ast_module_user_remove(u);
1340 static struct ast_custom_function smdi_msg_retrieve_function = {
1341 .name = "SMDI_MSG_RETRIEVE",
1342 .read = smdi_msg_retrieve_read,
1345 static struct ast_custom_function smdi_msg_function = {
1347 .read = smdi_msg_read,
1350 static int _unload_module(int fromload);
1353 * \brief Load the module
1355 * Module loading including tests for configuration or dependencies.
1356 * This function can return AST_MODULE_LOAD_FAILURE, AST_MODULE_LOAD_DECLINE,
1357 * or AST_MODULE_LOAD_SUCCESS. If a dependency or environment variable fails
1358 * tests return AST_MODULE_LOAD_FAILURE. If the module can not load the
1359 * configuration file or other non-critical problem return
1360 * AST_MODULE_LOAD_DECLINE. On success return AST_MODULE_LOAD_SUCCESS.
1362 static int load_module(void)
1367 ast_mutex_init(&mwi_monitor.lock);
1368 ast_cond_init(&mwi_monitor.cond, NULL);
1370 /* load the config and start the listener threads*/
1375 } else if (res == 1) {
1377 ast_log(LOG_NOTICE, "No SMDI interfaces are available to listen on, not starting SMDI listener.\n");
1378 return AST_MODULE_LOAD_DECLINE;
1381 ast_custom_function_register(&smdi_msg_retrieve_function);
1382 ast_custom_function_register(&smdi_msg_function);
1384 return AST_MODULE_LOAD_SUCCESS;
1387 static int _unload_module(int fromload)
1393 ao2_global_obj_release(smdi_ifaces);
1395 destroy_all_mailbox_mappings();
1397 ast_mutex_lock(&mwi_monitor.lock);
1398 mwi_monitor.stop = 1;
1399 ast_cond_signal(&mwi_monitor.cond);
1400 ast_mutex_unlock(&mwi_monitor.lock);
1402 if (mwi_monitor.thread != AST_PTHREADT_NULL) {
1403 pthread_join(mwi_monitor.thread, NULL);
1407 ast_custom_function_unregister(&smdi_msg_retrieve_function);
1408 ast_custom_function_unregister(&smdi_msg_function);
1415 static int unload_module(void)
1417 return _unload_module(0);
1420 static int reload(void)
1428 } else if (res == 1) {
1429 ast_log(LOG_WARNING, "No SMDI interfaces were specified to listen on, not starting SDMI listener.\n");
1435 AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_GLOBAL_SYMBOLS | AST_MODFLAG_LOAD_ORDER, "Simplified Message Desk Interface (SMDI) Resource",
1436 .load = load_module,
1437 .unload = unload_module,
1439 .load_pri = AST_MODPRI_CHANNEL_DEPEND,