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 */
60 static const char config_file[] = "smdi.conf";
62 /*! \brief SMDI message desk message queue. */
63 struct ast_smdi_md_queue {
64 ASTOBJ_CONTAINER_COMPONENTS(struct ast_smdi_md_message);
67 /*! \brief SMDI message waiting indicator message queue. */
68 struct ast_smdi_mwi_queue {
69 ASTOBJ_CONTAINER_COMPONENTS(struct ast_smdi_mwi_message);
72 struct ast_smdi_interface {
73 ASTOBJ_COMPONENTS_FULL(struct ast_smdi_interface, SMDI_MAX_FILENAME_LEN, 1);
74 struct ast_smdi_md_queue md_q;
75 ast_mutex_t md_q_lock;
77 struct ast_smdi_mwi_queue mwi_q;
78 ast_mutex_t mwi_q_lock;
79 ast_cond_t mwi_q_cond;
88 /*! \brief SMDI interface container. */
89 struct ast_smdi_interface_container {
90 ASTOBJ_CONTAINER_COMPONENTS(struct ast_smdi_interface);
93 /*! \brief A mapping between an SMDI mailbox ID and an Asterisk mailbox */
94 struct mailbox_mapping {
95 /*! This is the current state of the mailbox. It is simply on or
96 * off to indicate if there are messages waiting or not. */
97 unsigned int cur_state:1;
98 /*! A Pointer to the appropriate SMDI interface */
99 struct ast_smdi_interface *iface;
100 AST_DECLARE_STRING_FIELDS(
101 /*! The Name of the mailbox for the SMDI link. */
102 AST_STRING_FIELD(smdi);
103 /*! The name of the mailbox on the Asterisk side */
104 AST_STRING_FIELD(mailbox);
105 /*! The name of the voicemail context in use */
106 AST_STRING_FIELD(context);
108 AST_LIST_ENTRY(mailbox_mapping) entry;
112 #define DEFAULT_POLLING_INTERVAL 10
114 /*! \brief Data that gets used by the SMDI MWI monitoring thread */
120 /*! A list of mailboxes that need to be monitored */
121 AST_LIST_HEAD_NOLOCK(, mailbox_mapping) mailbox_mappings;
122 /*! Polling Interval for checking mailbox status */
123 unsigned int polling_interval;
124 /*! Set to 1 to tell the polling thread to stop */
126 /*! The time that the last poll began */
127 struct timeval last_poll;
129 .thread = AST_PTHREADT_NULL,
132 static void ast_smdi_interface_destroy(struct ast_smdi_interface *iface)
134 if (iface->thread != AST_PTHREADT_NULL && iface->thread != AST_PTHREADT_STOP) {
135 pthread_cancel(iface->thread);
136 pthread_join(iface->thread, NULL);
139 iface->thread = AST_PTHREADT_STOP;
144 ASTOBJ_CONTAINER_DESTROYALL(&iface->md_q, ast_smdi_md_message_destroy);
145 ASTOBJ_CONTAINER_DESTROYALL(&iface->mwi_q, ast_smdi_mwi_message_destroy);
146 ASTOBJ_CONTAINER_DESTROY(&iface->md_q);
147 ASTOBJ_CONTAINER_DESTROY(&iface->mwi_q);
149 ast_mutex_destroy(&iface->md_q_lock);
150 ast_cond_destroy(&iface->md_q_cond);
152 ast_mutex_destroy(&iface->mwi_q_lock);
153 ast_cond_destroy(&iface->mwi_q_cond);
157 ast_module_unref(ast_module_info->self);
160 void ast_smdi_interface_unref(struct ast_smdi_interface *iface)
162 ASTOBJ_UNREF(iface, ast_smdi_interface_destroy);
167 * \brief Push an SMDI message to the back of an interface's message queue.
168 * \param iface a pointer to the interface to use.
169 * \param md_msg a pointer to the message to use.
171 static void ast_smdi_md_message_push(struct ast_smdi_interface *iface, struct ast_smdi_md_message *md_msg)
173 ast_mutex_lock(&iface->md_q_lock);
174 ASTOBJ_CONTAINER_LINK_END(&iface->md_q, md_msg);
175 ast_cond_broadcast(&iface->md_q_cond);
176 ast_mutex_unlock(&iface->md_q_lock);
181 * \brief Push an SMDI message to the back of an interface's message queue.
182 * \param iface a pointer to the interface to use.
183 * \param mwi_msg a pointer to the message to use.
185 static void ast_smdi_mwi_message_push(struct ast_smdi_interface *iface, struct ast_smdi_mwi_message *mwi_msg)
187 ast_mutex_lock(&iface->mwi_q_lock);
188 ASTOBJ_CONTAINER_LINK_END(&iface->mwi_q, mwi_msg);
189 ast_cond_broadcast(&iface->mwi_q_cond);
190 ast_mutex_unlock(&iface->mwi_q_lock);
193 static int smdi_toggle_mwi(struct ast_smdi_interface *iface, const char *mailbox, int on)
198 if (!(file = fopen(iface->name, "w"))) {
199 ast_log(LOG_ERROR, "Error opening SMDI interface %s (%s) for writing\n", iface->name, strerror(errno));
203 ASTOBJ_WRLOCK(iface);
205 fprintf(file, "%s:MWI ", on ? "OP" : "RMV");
207 for (i = 0; i < iface->msdstrip; i++)
210 fprintf(file, "%s!\x04", mailbox);
214 ASTOBJ_UNLOCK(iface);
215 ast_debug(1, "Sent MWI set message for %s on %s\n", mailbox, iface->name);
220 int ast_smdi_mwi_set(struct ast_smdi_interface *iface, const char *mailbox)
222 return smdi_toggle_mwi(iface, mailbox, 1);
225 int ast_smdi_mwi_unset(struct ast_smdi_interface *iface, const char *mailbox)
227 return smdi_toggle_mwi(iface, mailbox, 0);
230 void ast_smdi_md_message_putback(struct ast_smdi_interface *iface, struct ast_smdi_md_message *md_msg)
232 ast_mutex_lock(&iface->md_q_lock);
233 ASTOBJ_CONTAINER_LINK_START(&iface->md_q, md_msg);
234 ast_cond_broadcast(&iface->md_q_cond);
235 ast_mutex_unlock(&iface->md_q_lock);
238 void ast_smdi_mwi_message_putback(struct ast_smdi_interface *iface, struct ast_smdi_mwi_message *mwi_msg)
240 ast_mutex_lock(&iface->mwi_q_lock);
241 ASTOBJ_CONTAINER_LINK_START(&iface->mwi_q, mwi_msg);
242 ast_cond_broadcast(&iface->mwi_q_cond);
243 ast_mutex_unlock(&iface->mwi_q_lock);
246 enum smdi_message_type {
251 static inline int lock_msg_q(struct ast_smdi_interface *iface, enum smdi_message_type type)
255 return ast_mutex_lock(&iface->mwi_q_lock);
257 return ast_mutex_lock(&iface->md_q_lock);
263 static inline int unlock_msg_q(struct ast_smdi_interface *iface, enum smdi_message_type type)
267 return ast_mutex_unlock(&iface->mwi_q_lock);
269 return ast_mutex_unlock(&iface->md_q_lock);
275 static inline void *unlink_from_msg_q(struct ast_smdi_interface *iface, enum smdi_message_type type)
279 return ASTOBJ_CONTAINER_UNLINK_START(&iface->mwi_q);
281 return ASTOBJ_CONTAINER_UNLINK_START(&iface->md_q);
286 static inline struct timeval msg_timestamp(void *msg, enum smdi_message_type type)
288 struct ast_smdi_md_message *md_msg = msg;
289 struct ast_smdi_mwi_message *mwi_msg = msg;
293 return mwi_msg->timestamp;
295 return md_msg->timestamp;
301 static inline void unref_msg(void *msg, enum smdi_message_type type)
303 struct ast_smdi_md_message *md_msg = msg;
304 struct ast_smdi_mwi_message *mwi_msg = msg;
308 ASTOBJ_UNREF(mwi_msg, ast_smdi_mwi_message_destroy);
311 ASTOBJ_UNREF(md_msg, ast_smdi_md_message_destroy);
316 static void purge_old_messages(struct ast_smdi_interface *iface, enum smdi_message_type type)
318 struct timeval now = ast_tvnow();
322 lock_msg_q(iface, type);
323 msg = unlink_from_msg_q(iface, type);
324 unlock_msg_q(iface, type);
326 /* purge old messages */
328 elapsed = ast_tvdiff_ms(now, msg_timestamp(msg, type));
330 if (elapsed > iface->msg_expiry) {
331 /* found an expired message */
332 unref_msg(msg, type);
333 ast_log(LOG_NOTICE, "Purged expired message from %s SMDI %s message queue. "
334 "Message was %ld milliseconds too old.\n",
335 iface->name, (type == SMDI_MD) ? "MD" : "MWI",
336 elapsed - iface->msg_expiry);
338 lock_msg_q(iface, type);
339 msg = unlink_from_msg_q(iface, type);
340 unlock_msg_q(iface, type);
342 /* good message, put it back and return */
345 ast_smdi_md_message_push(iface, msg);
348 ast_smdi_mwi_message_push(iface, msg);
351 unref_msg(msg, type);
357 static void *smdi_msg_pop(struct ast_smdi_interface *iface, enum smdi_message_type type)
361 purge_old_messages(iface, type);
363 lock_msg_q(iface, type);
364 msg = unlink_from_msg_q(iface, type);
365 unlock_msg_q(iface, type);
371 OPT_SEARCH_TERMINAL = (1 << 0),
372 OPT_SEARCH_NUMBER = (1 << 1),
375 static void *smdi_msg_find(struct ast_smdi_interface *iface,
376 enum smdi_message_type type, const char *search_key, struct ast_flags options)
380 purge_old_messages(iface, type);
384 if (ast_strlen_zero(search_key)) {
385 struct ast_smdi_md_message *md_msg = NULL;
387 /* No search key provided (the code from chan_dahdi does this).
388 * Just pop the top message off of the queue. */
390 ASTOBJ_CONTAINER_TRAVERSE(&iface->md_q, !md_msg, do {
391 md_msg = ASTOBJ_REF(iterator);
395 } else if (ast_test_flag(&options, OPT_SEARCH_TERMINAL)) {
396 struct ast_smdi_md_message *md_msg = NULL;
398 /* Searching by the message desk terminal */
400 ASTOBJ_CONTAINER_TRAVERSE(&iface->md_q, !md_msg, do {
401 if (!strcasecmp(iterator->mesg_desk_term, search_key))
402 md_msg = ASTOBJ_REF(iterator);
406 } else if (ast_test_flag(&options, OPT_SEARCH_NUMBER)) {
407 struct ast_smdi_md_message *md_msg = NULL;
409 /* Searching by the message desk number */
411 ASTOBJ_CONTAINER_TRAVERSE(&iface->md_q, !md_msg, do {
412 if (!strcasecmp(iterator->mesg_desk_num, search_key))
413 md_msg = ASTOBJ_REF(iterator);
418 /* Searching by the forwarding station */
419 msg = ASTOBJ_CONTAINER_FIND(&iface->md_q, search_key);
423 if (ast_strlen_zero(search_key)) {
424 struct ast_smdi_mwi_message *mwi_msg = NULL;
426 /* No search key provided (the code from chan_dahdi does this).
427 * Just pop the top message off of the queue. */
429 ASTOBJ_CONTAINER_TRAVERSE(&iface->mwi_q, !mwi_msg, do {
430 mwi_msg = ASTOBJ_REF(iterator);
435 msg = ASTOBJ_CONTAINER_FIND(&iface->mwi_q, search_key);
443 static void *smdi_message_wait(struct ast_smdi_interface *iface, int timeout,
444 enum smdi_message_type type, const char *search_key, struct ast_flags options)
446 struct timeval start;
449 ast_cond_t *cond = NULL;
450 ast_mutex_t *lock = NULL;
454 cond = &iface->mwi_q_cond;
455 lock = &iface->mwi_q_lock;
458 cond = &iface->md_q_cond;
459 lock = &iface->md_q_lock;
465 while (diff < timeout) {
466 struct timespec ts = { 0, };
469 lock_msg_q(iface, type);
471 if ((msg = smdi_msg_find(iface, type, search_key, options))) {
472 unlock_msg_q(iface, type);
476 wait = ast_tvadd(start, ast_tv(0, timeout));
477 ts.tv_sec = wait.tv_sec;
478 ts.tv_nsec = wait.tv_usec * 1000;
480 /* If there were no messages in the queue, then go to sleep until one
483 ast_cond_timedwait(cond, lock, &ts);
485 if ((msg = smdi_msg_find(iface, type, search_key, options))) {
486 unlock_msg_q(iface, type);
490 unlock_msg_q(iface, type);
493 diff = ast_tvdiff_ms(ast_tvnow(), start);
499 struct ast_smdi_md_message *ast_smdi_md_message_pop(struct ast_smdi_interface *iface)
501 return smdi_msg_pop(iface, SMDI_MD);
504 struct ast_smdi_md_message *ast_smdi_md_message_wait(struct ast_smdi_interface *iface, int timeout)
506 struct ast_flags options = { 0 };
507 return smdi_message_wait(iface, timeout, SMDI_MD, NULL, options);
510 struct ast_smdi_mwi_message *ast_smdi_mwi_message_pop(struct ast_smdi_interface *iface)
512 return smdi_msg_pop(iface, SMDI_MWI);
515 struct ast_smdi_mwi_message *ast_smdi_mwi_message_wait(struct ast_smdi_interface *iface, int timeout)
517 struct ast_flags options = { 0 };
518 return smdi_message_wait(iface, timeout, SMDI_MWI, NULL, options);
521 struct ast_smdi_mwi_message *ast_smdi_mwi_message_wait_station(struct ast_smdi_interface *iface, int timeout,
524 struct ast_flags options = { 0 };
525 return smdi_message_wait(iface, timeout, SMDI_MWI, station, options);
528 struct ast_smdi_interface *ast_smdi_interface_find(const char *iface_name)
530 return (ASTOBJ_CONTAINER_FIND(&smdi_ifaces, iface_name));
535 * \brief Read an SMDI message.
537 * \param iface_p the SMDI interface to read from.
539 * This function loops and reads from and SMDI interface. It must be stopped
540 * using pthread_cancel().
542 static void *smdi_read(void *iface_p)
544 struct ast_smdi_interface *iface = iface_p;
545 struct ast_smdi_md_message *md_msg;
546 struct ast_smdi_mwi_message *mwi_msg;
552 /* read an smdi message */
553 while ((c = fgetc(iface->file))) {
555 /* check if this is the start of a message */
558 ast_log(LOG_DEBUG, "Read an 'M' to start an SMDI message\n");
564 if (c == 'D') { /* MD message */
567 ast_log(LOG_DEBUG, "Read a 'D' ... it's an MD message.\n");
569 if (!(md_msg = ast_calloc(1, sizeof(*md_msg)))) {
570 ASTOBJ_UNREF(iface, ast_smdi_interface_destroy);
576 /* read the message desk number */
577 for (i = 0; i < sizeof(md_msg->mesg_desk_num) - 1; i++) {
578 md_msg->mesg_desk_num[i] = fgetc(iface->file);
579 ast_log(LOG_DEBUG, "Read a '%c'\n", md_msg->mesg_desk_num[i]);
582 md_msg->mesg_desk_num[sizeof(md_msg->mesg_desk_num) - 1] = '\0';
584 ast_log(LOG_DEBUG, "The message desk number is '%s'\n", md_msg->mesg_desk_num);
586 /* read the message desk terminal number */
587 for (i = 0; i < sizeof(md_msg->mesg_desk_term) - 1; i++) {
588 md_msg->mesg_desk_term[i] = fgetc(iface->file);
589 ast_log(LOG_DEBUG, "Read a '%c'\n", md_msg->mesg_desk_term[i]);
592 md_msg->mesg_desk_term[sizeof(md_msg->mesg_desk_term) - 1] = '\0';
594 ast_log(LOG_DEBUG, "The message desk terminal is '%s'\n", md_msg->mesg_desk_term);
596 /* read the message type */
597 md_msg->type = fgetc(iface->file);
599 ast_log(LOG_DEBUG, "Message type is '%c'\n", md_msg->type);
601 /* read the forwarding station number (may be blank) */
602 cp = &md_msg->fwd_st[0];
603 for (i = 0; i < sizeof(md_msg->fwd_st) - 1; i++) {
604 if ((c = fgetc(iface->file)) == ' ') {
606 ast_log(LOG_DEBUG, "Read a space, done looking for the forwarding station\n");
610 /* store c in md_msg->fwd_st */
611 if (i >= iface->msdstrip) {
612 ast_log(LOG_DEBUG, "Read a '%c' and stored it in the forwarding station buffer\n", c);
615 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);
619 /* make sure the value is null terminated, even if this truncates it */
620 md_msg->fwd_st[sizeof(md_msg->fwd_st) - 1] = '\0';
623 ast_log(LOG_DEBUG, "The forwarding station is '%s'\n", md_msg->fwd_st);
625 /* Put the fwd_st in the name field so that we can use ASTOBJ_FIND to look
626 * up a message on this field */
627 ast_copy_string(md_msg->name, md_msg->fwd_st, sizeof(md_msg->name));
629 /* read the calling station number (may be blank) */
630 cp = &md_msg->calling_st[0];
631 for (i = 0; i < sizeof(md_msg->calling_st) - 1; i++) {
632 if (!isdigit((c = fgetc(iface->file)))) {
634 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);
636 /* Don't break on a space. We may read the space before the calling station
637 * here if the forwarding station buffer filled up. */
638 i--; /* We're still on the same character */
644 /* store c in md_msg->calling_st */
645 if (i >= iface->msdstrip) {
646 ast_log(LOG_DEBUG, "Read a '%c' and stored it in the calling station buffer\n", c);
649 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);
653 /* make sure the value is null terminated, even if this truncates it */
654 md_msg->calling_st[sizeof(md_msg->calling_st) - 1] = '\0';
657 ast_log(LOG_DEBUG, "The calling station is '%s'\n", md_msg->calling_st);
659 /* add the message to the message queue */
660 md_msg->timestamp = ast_tvnow();
661 ast_smdi_md_message_push(iface, md_msg);
662 ast_log(LOG_DEBUG, "Recieved SMDI MD message on %s\n", iface->name);
664 ASTOBJ_UNREF(md_msg, ast_smdi_md_message_destroy);
666 } else if (c == 'W') { /* MWI message */
669 ast_log(LOG_DEBUG, "Read a 'W', it's an MWI message. (No more debug coming for MWI messages)\n");
671 if (!(mwi_msg = ast_calloc(1, sizeof(*mwi_msg)))) {
672 ASTOBJ_UNREF(iface,ast_smdi_interface_destroy);
676 ASTOBJ_INIT(mwi_msg);
678 /* discard the 'I' (from 'MWI') */
681 /* read the forwarding station number (may be blank) */
682 cp = &mwi_msg->fwd_st[0];
683 for (i = 0; i < sizeof(mwi_msg->fwd_st) - 1; i++) {
684 if ((c = fgetc(iface->file)) == ' ') {
689 /* store c in md_msg->fwd_st */
690 if (i >= iface->msdstrip)
694 /* make sure the station number is null terminated, even if this will truncate it */
695 mwi_msg->fwd_st[sizeof(mwi_msg->fwd_st) - 1] = '\0';
698 /* Put the fwd_st in the name field so that we can use ASTOBJ_FIND to look
699 * up a message on this field */
700 ast_copy_string(mwi_msg->name, mwi_msg->fwd_st, sizeof(mwi_msg->name));
702 /* read the mwi failure cause */
703 for (i = 0; i < sizeof(mwi_msg->cause) - 1; i++)
704 mwi_msg->cause[i] = fgetc(iface->file);
706 mwi_msg->cause[sizeof(mwi_msg->cause) - 1] = '\0';
708 /* add the message to the message queue */
709 mwi_msg->timestamp = ast_tvnow();
710 ast_smdi_mwi_message_push(iface, mwi_msg);
711 ast_log(LOG_DEBUG, "Recieved SMDI MWI message on %s\n", iface->name);
713 ASTOBJ_UNREF(mwi_msg, ast_smdi_mwi_message_destroy);
715 ast_log(LOG_ERROR, "Unknown SMDI message type recieved on %s (M%c).\n", iface->name, c);
720 ast_log(LOG_ERROR, "Error reading from SMDI interface %s, stopping listener thread\n", iface->name);
721 ASTOBJ_UNREF(iface,ast_smdi_interface_destroy);
725 void ast_smdi_md_message_destroy(struct ast_smdi_md_message *msg)
730 void ast_smdi_mwi_message_destroy(struct ast_smdi_mwi_message *msg)
735 static void destroy_mailbox_mapping(struct mailbox_mapping *mm)
737 ast_string_field_free_memory(mm);
738 ASTOBJ_UNREF(mm->iface, ast_smdi_interface_destroy);
742 static void destroy_all_mailbox_mappings(void)
744 struct mailbox_mapping *mm;
746 ast_mutex_lock(&mwi_monitor.lock);
747 while ((mm = AST_LIST_REMOVE_HEAD(&mwi_monitor.mailbox_mappings, entry)))
748 destroy_mailbox_mapping(mm);
749 ast_mutex_unlock(&mwi_monitor.lock);
752 static void append_mailbox_mapping(struct ast_variable *var, struct ast_smdi_interface *iface)
754 struct mailbox_mapping *mm;
755 char *mailbox, *context;
757 if (!(mm = ast_calloc(1, sizeof(*mm))))
760 if (ast_string_field_init(mm, 32)) {
765 ast_string_field_set(mm, smdi, var->name);
767 context = ast_strdupa(var->value);
768 mailbox = strsep(&context, "@");
769 if (ast_strlen_zero(context))
772 ast_string_field_set(mm, mailbox, mailbox);
773 ast_string_field_set(mm, context, context);
775 mm->iface = ASTOBJ_REF(iface);
777 ast_mutex_lock(&mwi_monitor.lock);
778 AST_LIST_INSERT_TAIL(&mwi_monitor.mailbox_mappings, mm, entry);
779 ast_mutex_unlock(&mwi_monitor.lock);
783 * \note Called with the mwi_monitor.lock locked
785 static void poll_mailbox(struct mailbox_mapping *mm)
790 snprintf(buf, sizeof(buf), "%s@%s", mm->mailbox, mm->context);
792 state = !!ast_app_has_voicemail(mm->mailbox, NULL);
794 if (state != mm->cur_state) {
796 ast_smdi_mwi_set(mm->iface, mm->smdi);
798 ast_smdi_mwi_unset(mm->iface, mm->smdi);
800 mm->cur_state = state;
804 static void *mwi_monitor_handler(void *data)
806 while (!mwi_monitor.stop) {
807 struct timespec ts = { 0, };
808 struct timeval polltime;
809 struct mailbox_mapping *mm;
811 ast_mutex_lock(&mwi_monitor.lock);
813 mwi_monitor.last_poll = ast_tvnow();
815 AST_LIST_TRAVERSE(&mwi_monitor.mailbox_mappings, mm, entry)
818 /* Sleep up to the configured polling interval. Allow unload_module()
819 * to signal us to wake up and exit. */
820 polltime = ast_tvadd(mwi_monitor.last_poll, ast_tv(mwi_monitor.polling_interval, 0));
821 ts.tv_sec = polltime.tv_sec;
822 ts.tv_nsec = polltime.tv_usec * 1000;
823 ast_cond_timedwait(&mwi_monitor.cond, &mwi_monitor.lock, &ts);
825 ast_mutex_unlock(&mwi_monitor.lock);
831 static struct ast_smdi_interface *alloc_smdi_interface(void)
833 struct ast_smdi_interface *iface;
835 if (!(iface = ast_calloc(1, sizeof(*iface))))
839 ASTOBJ_CONTAINER_INIT(&iface->md_q);
840 ASTOBJ_CONTAINER_INIT(&iface->mwi_q);
842 ast_mutex_init(&iface->md_q_lock);
843 ast_cond_init(&iface->md_q_cond, NULL);
845 ast_mutex_init(&iface->mwi_q_lock);
846 ast_cond_init(&iface->mwi_q_cond, NULL);
853 * \brief Load and reload SMDI configuration.
854 * \param reload this should be 1 if we are reloading and 0 if not.
856 * This function loads/reloads the SMDI configuration and starts and stops
857 * interfaces accordingly.
859 * \return zero on success, -1 on failure, and 1 if no smdi interfaces were started.
861 static int smdi_load(int reload)
863 struct ast_config *conf;
864 struct ast_variable *v;
865 struct ast_smdi_interface *iface = NULL;
866 struct ast_flags config_flags = { reload ? CONFIG_FLAG_FILEUNCHANGED : 0 };
870 speed_t baud_rate = B9600; /* 9600 baud rate */
871 tcflag_t paritybit = PARENB; /* even parity checking */
872 tcflag_t charsize = CS7; /* seven bit characters */
873 int stopbits = 0; /* One stop bit */
875 int msdstrip = 0; /* strip zero digits */
876 long msg_expiry = SMDI_MSG_EXPIRY_TIME;
878 if (!(conf = ast_config_load(config_file, config_flags)) || conf == CONFIG_STATUS_FILEINVALID) {
880 ast_log(LOG_NOTICE, "Unable to reload config %s: SMDI untouched\n", config_file);
882 ast_log(LOG_NOTICE, "Unable to load config %s: SMDI disabled\n", config_file);
884 } else if (conf == CONFIG_STATUS_FILEUNCHANGED)
887 /* Mark all interfaces that we are listening on. We will unmark them
888 * as we find them in the config file, this way we know any interfaces
889 * still marked after we have finished parsing the config file should
893 ASTOBJ_CONTAINER_MARKALL(&smdi_ifaces);
895 for (v = ast_variable_browse(conf, "interfaces"); v; v = v->next) {
896 if (!strcasecmp(v->name, "baudrate")) {
897 if (!strcasecmp(v->value, "9600"))
899 else if (!strcasecmp(v->value, "4800"))
901 else if (!strcasecmp(v->value, "2400"))
903 else if (!strcasecmp(v->value, "1200"))
906 ast_log(LOG_NOTICE, "Invalid baud rate '%s' specified in %s (line %d), using default\n", v->value, config_file, v->lineno);
909 } else if (!strcasecmp(v->name, "msdstrip")) {
910 if (!sscanf(v->value, "%d", &msdstrip)) {
911 ast_log(LOG_NOTICE, "Invalid msdstrip value in %s (line %d), using default\n", config_file, v->lineno);
913 } else if (0 > msdstrip || msdstrip > 9) {
914 ast_log(LOG_NOTICE, "Invalid msdstrip value in %s (line %d), using default\n", config_file, v->lineno);
917 } else if (!strcasecmp(v->name, "msgexpirytime")) {
918 if (!sscanf(v->value, "%ld", &msg_expiry)) {
919 ast_log(LOG_NOTICE, "Invalid msgexpirytime value in %s (line %d), using default\n", config_file, v->lineno);
920 msg_expiry = SMDI_MSG_EXPIRY_TIME;
922 } else if (!strcasecmp(v->name, "paritybit")) {
923 if (!strcasecmp(v->value, "even"))
925 else if (!strcasecmp(v->value, "odd"))
926 paritybit = PARENB | PARODD;
927 else if (!strcasecmp(v->value, "none"))
930 ast_log(LOG_NOTICE, "Invalid parity bit setting in %s (line %d), using default\n", config_file, v->lineno);
933 } else if (!strcasecmp(v->name, "charsize")) {
934 if (!strcasecmp(v->value, "7"))
936 else if (!strcasecmp(v->value, "8"))
939 ast_log(LOG_NOTICE, "Invalid character size setting in %s (line %d), using default\n", config_file, v->lineno);
942 } else if (!strcasecmp(v->name, "twostopbits")) {
943 stopbits = ast_true(v->name);
944 } else if (!strcasecmp(v->name, "smdiport")) {
946 /* we are reloading, check if we are already
947 * monitoring this interface, if we are we do
948 * not want to start it again. This also has
949 * the side effect of not updating different
950 * setting for the serial port, but it should
951 * be trivial to rewrite this section so that
952 * options on the port are changed without
953 * restarting the interface. Or the interface
954 * could be restarted with out emptying the
956 if ((iface = ASTOBJ_CONTAINER_FIND(&smdi_ifaces, v->value))) {
957 ast_log(LOG_NOTICE, "SMDI interface %s already running, not restarting\n", iface->name);
958 ASTOBJ_UNMARK(iface);
959 ASTOBJ_UNREF(iface, ast_smdi_interface_destroy);
964 if (!(iface = alloc_smdi_interface()))
967 ast_copy_string(iface->name, v->value, sizeof(iface->name));
969 iface->thread = AST_PTHREADT_NULL;
971 if (!(iface->file = fopen(iface->name, "r"))) {
972 ast_log(LOG_ERROR, "Error opening SMDI interface %s (%s)\n", iface->name, strerror(errno));
973 ASTOBJ_UNREF(iface, ast_smdi_interface_destroy);
977 iface->fd = fileno(iface->file);
979 /* Set the proper attributes for our serial port. */
981 /* get the current attributes from the port */
982 if (tcgetattr(iface->fd, &iface->mode)) {
983 ast_log(LOG_ERROR, "Error getting atributes of %s (%s)\n", iface->name, strerror(errno));
984 ASTOBJ_UNREF(iface, ast_smdi_interface_destroy);
988 /* set the desired speed */
989 if (cfsetispeed(&iface->mode, baud_rate) || cfsetospeed(&iface->mode, baud_rate)) {
990 ast_log(LOG_ERROR, "Error setting baud rate on %s (%s)\n", iface->name, strerror(errno));
991 ASTOBJ_UNREF(iface, ast_smdi_interface_destroy);
995 /* set the stop bits */
997 iface->mode.c_cflag = iface->mode.c_cflag | CSTOPB; /* set two stop bits */
999 iface->mode.c_cflag = iface->mode.c_cflag & ~CSTOPB; /* set one stop bit */
1001 /* set the parity */
1002 iface->mode.c_cflag = (iface->mode.c_cflag & ~PARENB & ~PARODD) | paritybit;
1004 /* set the character size */
1005 iface->mode.c_cflag = (iface->mode.c_cflag & ~CSIZE) | charsize;
1007 /* commit the desired attributes */
1008 if (tcsetattr(iface->fd, TCSAFLUSH, &iface->mode)) {
1009 ast_log(LOG_ERROR, "Error setting attributes on %s (%s)\n", iface->name, strerror(errno));
1010 ASTOBJ_UNREF(iface, ast_smdi_interface_destroy);
1014 /* set the msdstrip */
1015 iface->msdstrip = msdstrip;
1017 /* set the message expiry time */
1018 iface->msg_expiry = msg_expiry;
1020 /* start the listener thread */
1021 ast_verb(3, "Starting SMDI monitor thread for %s\n", iface->name);
1022 if (ast_pthread_create_background(&iface->thread, NULL, smdi_read, iface)) {
1023 ast_log(LOG_ERROR, "Error starting SMDI monitor thread for %s\n", iface->name);
1024 ASTOBJ_UNREF(iface, ast_smdi_interface_destroy);
1028 ASTOBJ_CONTAINER_LINK(&smdi_ifaces, iface);
1029 ASTOBJ_UNREF(iface, ast_smdi_interface_destroy);
1030 ast_module_ref(ast_module_info->self);
1032 ast_log(LOG_NOTICE, "Ignoring unknown option %s in %s\n", v->name, config_file);
1036 destroy_all_mailbox_mappings();
1037 mwi_monitor.polling_interval = DEFAULT_POLLING_INTERVAL;
1041 for (v = ast_variable_browse(conf, "mailboxes"); v; v = v->next) {
1042 if (!strcasecmp(v->name, "smdiport")) {
1044 ASTOBJ_UNREF(iface, ast_smdi_interface_destroy);
1046 if (!(iface = ASTOBJ_CONTAINER_FIND(&smdi_ifaces, v->value))) {
1047 ast_log(LOG_NOTICE, "SMDI interface %s not found\n", iface->name);
1050 } else if (!strcasecmp(v->name, "pollinginterval")) {
1051 if (sscanf(v->value, "%u", &mwi_monitor.polling_interval) != 1) {
1052 ast_log(LOG_ERROR, "Invalid value for pollinginterval: %s\n", v->value);
1053 mwi_monitor.polling_interval = DEFAULT_POLLING_INTERVAL;
1057 ast_log(LOG_ERROR, "Mailbox mapping ignored, no valid SMDI interface specified in mailboxes section\n");
1060 append_mailbox_mapping(v, iface);
1065 ASTOBJ_UNREF(iface, ast_smdi_interface_destroy);
1067 ast_config_destroy(conf);
1069 if (!AST_LIST_EMPTY(&mwi_monitor.mailbox_mappings) && mwi_monitor.thread == AST_PTHREADT_NULL
1070 && ast_pthread_create_background(&mwi_monitor.thread, NULL, mwi_monitor_handler, NULL)) {
1071 ast_log(LOG_ERROR, "Failed to start MWI monitoring thread. This module will not operate.\n");
1072 return AST_MODULE_LOAD_FAILURE;
1075 /* Prune any interfaces we should no longer monitor. */
1077 ASTOBJ_CONTAINER_PRUNE_MARKED(&smdi_ifaces, ast_smdi_interface_destroy);
1079 ASTOBJ_CONTAINER_RDLOCK(&smdi_ifaces);
1080 /* TODO: this is bad, we need an ASTOBJ method for this! */
1081 if (!smdi_ifaces.head)
1083 ASTOBJ_CONTAINER_UNLOCK(&smdi_ifaces);
1088 struct smdi_msg_datastore {
1090 struct ast_smdi_interface *iface;
1091 struct ast_smdi_md_message *md_msg;
1094 static void smdi_msg_datastore_destroy(void *data)
1096 struct smdi_msg_datastore *smd = data;
1099 ASTOBJ_UNREF(smd->iface, ast_smdi_interface_destroy);
1102 ASTOBJ_UNREF(smd->md_msg, ast_smdi_md_message_destroy);
1107 static const struct ast_datastore_info smdi_msg_datastore_info = {
1109 .destroy = smdi_msg_datastore_destroy,
1112 static int smdi_msg_id;
1114 /*! In milliseconds */
1115 #define SMDI_RETRIEVE_TIMEOUT_DEFAULT 3000
1117 AST_APP_OPTIONS(smdi_msg_ret_options, BEGIN_OPTIONS
1118 AST_APP_OPTION('t', OPT_SEARCH_TERMINAL),
1119 AST_APP_OPTION('n', OPT_SEARCH_NUMBER),
1122 static int smdi_msg_retrieve_read(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len)
1124 struct ast_module_user *u;
1125 AST_DECLARE_APP_ARGS(args,
1127 AST_APP_ARG(search_key);
1128 AST_APP_ARG(timeout);
1129 AST_APP_ARG(options);
1131 struct ast_flags options = { 0 };
1132 unsigned int timeout = SMDI_RETRIEVE_TIMEOUT_DEFAULT;
1135 struct smdi_msg_datastore *smd = NULL;
1136 struct ast_datastore *datastore = NULL;
1137 struct ast_smdi_interface *iface = NULL;
1138 struct ast_smdi_md_message *md_msg = NULL;
1140 u = ast_module_user_add(chan);
1142 if (ast_strlen_zero(data)) {
1143 ast_log(LOG_ERROR, "SMDI_MSG_RETRIEVE requires an argument\n");
1148 ast_log(LOG_ERROR, "SMDI_MSG_RETRIEVE must be used with a channel\n");
1152 ast_autoservice_start(chan);
1154 parse = ast_strdupa(data);
1155 AST_STANDARD_APP_ARGS(args, parse);
1157 if (ast_strlen_zero(args.port) || ast_strlen_zero(args.search_key)) {
1158 ast_log(LOG_ERROR, "Invalid arguments provided to SMDI_MSG_RETRIEVE\n");
1162 if (!(iface = ast_smdi_interface_find(args.port))) {
1163 ast_log(LOG_ERROR, "SMDI port '%s' not found\n", args.port);
1167 if (!ast_strlen_zero(args.options)) {
1168 ast_app_parse_options(smdi_msg_ret_options, &options, NULL, args.options);
1171 if (!ast_strlen_zero(args.timeout)) {
1172 if (sscanf(args.timeout, "%u", &timeout) != 1) {
1173 ast_log(LOG_ERROR, "'%s' is not a valid timeout\n", args.timeout);
1174 timeout = SMDI_RETRIEVE_TIMEOUT_DEFAULT;
1178 if (!(md_msg = smdi_message_wait(iface, timeout, SMDI_MD, args.search_key, options))) {
1179 ast_log(LOG_WARNING, "No SMDI message retrieved for search key '%s' after "
1180 "waiting %u ms.\n", args.search_key, timeout);
1184 if (!(smd = ast_calloc(1, sizeof(*smd))))
1187 smd->iface = ASTOBJ_REF(iface);
1188 smd->md_msg = ASTOBJ_REF(md_msg);
1189 smd->id = ast_atomic_fetchadd_int((int *) &smdi_msg_id, 1);
1190 snprintf(buf, len, "%u", smd->id);
1192 if (!(datastore = ast_datastore_alloc(&smdi_msg_datastore_info, buf)))
1195 datastore->data = smd;
1197 ast_channel_lock(chan);
1198 ast_channel_datastore_add(chan, datastore);
1199 ast_channel_unlock(chan);
1205 ASTOBJ_UNREF(iface, ast_smdi_interface_destroy);
1208 ASTOBJ_UNREF(md_msg, ast_smdi_md_message_destroy);
1210 if (smd && !datastore)
1211 smdi_msg_datastore_destroy(smd);
1214 ast_autoservice_stop(chan);
1216 ast_module_user_remove(u);
1221 static int smdi_msg_read(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len)
1223 struct ast_module_user *u;
1225 AST_DECLARE_APP_ARGS(args,
1227 AST_APP_ARG(component);
1230 struct ast_datastore *datastore = NULL;
1231 struct smdi_msg_datastore *smd = NULL;
1233 u = ast_module_user_add(chan);
1236 ast_log(LOG_ERROR, "SMDI_MSG can not be called without a channel\n");
1240 if (ast_strlen_zero(data)) {
1241 ast_log(LOG_WARNING, "SMDI_MSG requires an argument\n");
1245 parse = ast_strdupa(data);
1246 AST_STANDARD_APP_ARGS(args, parse);
1248 if (ast_strlen_zero(args.id)) {
1249 ast_log(LOG_WARNING, "ID must be supplied to SMDI_MSG\n");
1253 if (ast_strlen_zero(args.component)) {
1254 ast_log(LOG_WARNING, "ID must be supplied to SMDI_MSG\n");
1258 ast_channel_lock(chan);
1259 datastore = ast_channel_datastore_find(chan, &smdi_msg_datastore_info, args.id);
1260 ast_channel_unlock(chan);
1263 ast_log(LOG_WARNING, "No SMDI message found for message ID '%s'\n", args.id);
1267 smd = datastore->data;
1269 if (!strcasecmp(args.component, "number")) {
1270 ast_copy_string(buf, smd->md_msg->mesg_desk_num, len);
1271 } else if (!strcasecmp(args.component, "terminal")) {
1272 ast_copy_string(buf, smd->md_msg->mesg_desk_term, len);
1273 } else if (!strcasecmp(args.component, "station")) {
1274 ast_copy_string(buf, smd->md_msg->fwd_st, len);
1275 } else if (!strcasecmp(args.component, "callerid")) {
1276 ast_copy_string(buf, smd->md_msg->calling_st, len);
1277 } else if (!strcasecmp(args.component, "type")) {
1278 snprintf(buf, len, "%c", smd->md_msg->type);
1280 ast_log(LOG_ERROR, "'%s' is not a valid message component for SMDI_MSG\n",
1288 ast_module_user_remove(u);
1293 static struct ast_custom_function smdi_msg_retrieve_function = {
1294 .name = "SMDI_MSG_RETRIEVE",
1295 .synopsis = "Retrieve an SMDI message.",
1296 .syntax = "SMDI_MSG_RETRIEVE(<smdi port>,<search key>[,timeout[,options]])",
1298 " This function is used to retrieve an incoming SMDI message. It returns\n"
1299 "an ID which can be used with the SMDI_MSG() function to access details of\n"
1300 "the message. Note that this is a destructive function in the sense that\n"
1301 "once an SMDI message is retrieved using this function, it is no longer in\n"
1302 "the global SMDI message queue, and can not be accessed by any other Asterisk\n"
1303 "channels. The timeout for this function is optional, and the default is\n"
1304 "3 seconds. When providing a timeout, it should be in milliseconds.\n"
1305 " The default search is done on the forwarding station ID. However, if\n"
1306 "you set one of the search key options in the options field, you can change\n"
1309 " t - Instead of searching on the forwarding station, search on the message\n"
1311 " n - Instead of searching on the forwarding station, search on the message\n"
1314 .read = smdi_msg_retrieve_read,
1317 static struct ast_custom_function smdi_msg_function = {
1319 .synopsis = "Retrieve details about an SMDI message.",
1320 .syntax = "SMDI_MSG(<message_id>,<component>)",
1322 " This function is used to access details of an SMDI message that was\n"
1323 "pulled from the incoming SMDI message queue using the SMDI_MSG_RETRIEVE()\n"
1325 " Valid message components are:\n"
1326 " number - The message desk number\n"
1327 " terminal - The message desk terminal\n"
1328 " station - The forwarding station\n"
1329 " callerid - The callerID of the calling party that was forwarded\n"
1330 " type - The call type. The value here is the exact character\n"
1331 " that came in on the SMDI link. Typically, example values\n"
1332 " are: D - Direct Calls, A - Forward All Calls,\n"
1333 " B - Forward Busy Calls, N - Forward No Answer Calls\n"
1335 .read = smdi_msg_read,
1338 static int unload_module(void);
1340 static int load_module(void)
1344 /* initialize our containers */
1345 memset(&smdi_ifaces, 0, sizeof(smdi_ifaces));
1346 ASTOBJ_CONTAINER_INIT(&smdi_ifaces);
1348 ast_mutex_init(&mwi_monitor.lock);
1349 ast_cond_init(&mwi_monitor.cond, NULL);
1351 ast_custom_function_register(&smdi_msg_retrieve_function);
1352 ast_custom_function_register(&smdi_msg_function);
1354 /* load the config and start the listener threads*/
1359 } else if (res == 1) {
1361 ast_log(LOG_NOTICE, "No SMDI interfaces are available to listen on, not starting SMDI listener.\n");
1362 return AST_MODULE_LOAD_DECLINE;
1365 return AST_MODULE_LOAD_SUCCESS;
1368 static int unload_module(void)
1370 /* this destructor stops any running smdi_read threads */
1371 ASTOBJ_CONTAINER_DESTROYALL(&smdi_ifaces, ast_smdi_interface_destroy);
1372 ASTOBJ_CONTAINER_DESTROY(&smdi_ifaces);
1374 destroy_all_mailbox_mappings();
1376 ast_mutex_lock(&mwi_monitor.lock);
1377 mwi_monitor.stop = 1;
1378 ast_cond_signal(&mwi_monitor.cond);
1379 ast_mutex_unlock(&mwi_monitor.lock);
1381 if (mwi_monitor.thread != AST_PTHREADT_NULL) {
1382 pthread_join(mwi_monitor.thread, NULL);
1385 ast_custom_function_unregister(&smdi_msg_retrieve_function);
1386 ast_custom_function_unregister(&smdi_msg_function);
1391 static int reload(void)
1399 } else if (res == 1) {
1400 ast_log(LOG_WARNING, "No SMDI interfaces were specified to listen on, not starting SDMI listener.\n");
1406 AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_GLOBAL_SYMBOLS, "Simplified Message Desk Interface (SMDI) Resource",
1407 .load = load_module,
1408 .unload = unload_module,