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 #include "asterisk/smdi.h"
47 #include "asterisk/config.h"
48 #include "asterisk/astobj.h"
49 #include "asterisk/io.h"
50 #include "asterisk/stringfields.h"
51 #include "asterisk/linkedlists.h"
52 #include "asterisk/app.h"
53 #include "asterisk/pbx.h"
54 #include "asterisk/channel.h"
56 /* Message expiry time in milliseconds */
57 #define SMDI_MSG_EXPIRY_TIME 30000 /* 30 seconds */
59 static const char config_file[] = "smdi.conf";
61 /*! \brief SMDI message desk message queue. */
62 struct ast_smdi_md_queue {
63 ASTOBJ_CONTAINER_COMPONENTS(struct ast_smdi_md_message);
66 /*! \brief SMDI message waiting indicator message queue. */
67 struct ast_smdi_mwi_queue {
68 ASTOBJ_CONTAINER_COMPONENTS(struct ast_smdi_mwi_message);
71 struct ast_smdi_interface {
72 ASTOBJ_COMPONENTS_FULL(struct ast_smdi_interface, SMDI_MAX_FILENAME_LEN, 1);
73 struct ast_smdi_md_queue md_q;
74 ast_mutex_t md_q_lock;
76 struct ast_smdi_mwi_queue mwi_q;
77 ast_mutex_t mwi_q_lock;
78 ast_cond_t mwi_q_cond;
87 /*! \brief SMDI interface container. */
88 struct ast_smdi_interface_container {
89 ASTOBJ_CONTAINER_COMPONENTS(struct ast_smdi_interface);
92 /*! \brief A mapping between an SMDI mailbox ID and an Asterisk mailbox */
93 struct mailbox_mapping {
94 /*! This is the current state of the mailbox. It is simply on or
95 * off to indicate if there are messages waiting or not. */
96 unsigned int cur_state:1;
97 /*! A Pointer to the appropriate SMDI interface */
98 struct ast_smdi_interface *iface;
99 AST_DECLARE_STRING_FIELDS(
100 /*! The Name of the mailbox for the SMDI link. */
101 AST_STRING_FIELD(smdi);
102 /*! The name of the mailbox on the Asterisk side */
103 AST_STRING_FIELD(mailbox);
104 /*! The name of the voicemail context in use */
105 AST_STRING_FIELD(context);
107 AST_LIST_ENTRY(mailbox_mapping) entry;
111 #define DEFAULT_POLLING_INTERVAL 10
113 /*! \brief Data that gets used by the SMDI MWI monitoring thread */
119 /*! A list of mailboxes that need to be monitored */
120 AST_LIST_HEAD_NOLOCK(, mailbox_mapping) mailbox_mappings;
121 /*! Polling Interval for checking mailbox status */
122 unsigned int polling_interval;
123 /*! Set to 1 to tell the polling thread to stop */
125 /*! The time that the last poll began */
126 struct timeval last_poll;
128 .thread = AST_PTHREADT_NULL,
131 static void ast_smdi_interface_destroy(struct ast_smdi_interface *iface)
133 if (iface->thread != AST_PTHREADT_NULL && iface->thread != AST_PTHREADT_STOP) {
134 pthread_cancel(iface->thread);
135 pthread_join(iface->thread, NULL);
138 iface->thread = AST_PTHREADT_STOP;
143 ASTOBJ_CONTAINER_DESTROYALL(&iface->md_q, ast_smdi_md_message_destroy);
144 ASTOBJ_CONTAINER_DESTROYALL(&iface->mwi_q, ast_smdi_mwi_message_destroy);
145 ASTOBJ_CONTAINER_DESTROY(&iface->md_q);
146 ASTOBJ_CONTAINER_DESTROY(&iface->mwi_q);
148 ast_mutex_destroy(&iface->md_q_lock);
149 ast_cond_destroy(&iface->md_q_cond);
151 ast_mutex_destroy(&iface->mwi_q_lock);
152 ast_cond_destroy(&iface->mwi_q_cond);
156 ast_module_unref(ast_module_info->self);
159 void ast_smdi_interface_unref(struct ast_smdi_interface *iface)
161 ASTOBJ_UNREF(iface, ast_smdi_interface_destroy);
166 * \brief Push an SMDI message to the back of an interface's message queue.
167 * \param iface a pointer to the interface to use.
168 * \param md_msg a pointer to the message to use.
170 static void ast_smdi_md_message_push(struct ast_smdi_interface *iface, struct ast_smdi_md_message *md_msg)
172 ast_mutex_lock(&iface->md_q_lock);
173 ASTOBJ_CONTAINER_LINK_END(&iface->md_q, md_msg);
174 ast_cond_broadcast(&iface->md_q_cond);
175 ast_mutex_unlock(&iface->md_q_lock);
180 * \brief Push an SMDI message to the back of an interface's message queue.
181 * \param iface a pointer to the interface to use.
182 * \param mwi_msg a pointer to the message to use.
184 static void ast_smdi_mwi_message_push(struct ast_smdi_interface *iface, struct ast_smdi_mwi_message *mwi_msg)
186 ast_mutex_lock(&iface->mwi_q_lock);
187 ASTOBJ_CONTAINER_LINK_END(&iface->mwi_q, mwi_msg);
188 ast_cond_broadcast(&iface->mwi_q_cond);
189 ast_mutex_unlock(&iface->mwi_q_lock);
192 static int smdi_toggle_mwi(struct ast_smdi_interface *iface, const char *mailbox, int on)
197 if (!(file = fopen(iface->name, "w"))) {
198 ast_log(LOG_ERROR, "Error opening SMDI interface %s (%s) for writing\n", iface->name, strerror(errno));
202 ASTOBJ_WRLOCK(iface);
204 fprintf(file, "%s:MWI ", on ? "OP" : "RMV");
206 for (i = 0; i < iface->msdstrip; i++)
209 fprintf(file, "%s!\x04", mailbox);
213 ASTOBJ_UNLOCK(iface);
214 ast_debug(1, "Sent MWI set message for %s on %s\n", mailbox, iface->name);
219 int ast_smdi_mwi_set(struct ast_smdi_interface *iface, const char *mailbox)
221 return smdi_toggle_mwi(iface, mailbox, 1);
224 int ast_smdi_mwi_unset(struct ast_smdi_interface *iface, const char *mailbox)
226 return smdi_toggle_mwi(iface, mailbox, 0);
229 void ast_smdi_md_message_putback(struct ast_smdi_interface *iface, struct ast_smdi_md_message *md_msg)
231 ast_mutex_lock(&iface->md_q_lock);
232 ASTOBJ_CONTAINER_LINK_START(&iface->md_q, md_msg);
233 ast_cond_broadcast(&iface->md_q_cond);
234 ast_mutex_unlock(&iface->md_q_lock);
237 void ast_smdi_mwi_message_putback(struct ast_smdi_interface *iface, struct ast_smdi_mwi_message *mwi_msg)
239 ast_mutex_lock(&iface->mwi_q_lock);
240 ASTOBJ_CONTAINER_LINK_START(&iface->mwi_q, mwi_msg);
241 ast_cond_broadcast(&iface->mwi_q_cond);
242 ast_mutex_unlock(&iface->mwi_q_lock);
245 enum smdi_message_type {
250 static inline int lock_msg_q(struct ast_smdi_interface *iface, enum smdi_message_type type)
254 return ast_mutex_lock(&iface->mwi_q_lock);
256 return ast_mutex_lock(&iface->md_q_lock);
262 static inline int unlock_msg_q(struct ast_smdi_interface *iface, enum smdi_message_type type)
266 return ast_mutex_unlock(&iface->mwi_q_lock);
268 return ast_mutex_unlock(&iface->md_q_lock);
274 static inline void *unlink_from_msg_q(struct ast_smdi_interface *iface, enum smdi_message_type type)
278 return ASTOBJ_CONTAINER_UNLINK_START(&iface->mwi_q);
280 return ASTOBJ_CONTAINER_UNLINK_START(&iface->md_q);
285 static inline struct timeval msg_timestamp(void *msg, enum smdi_message_type type)
287 struct ast_smdi_md_message *md_msg = msg;
288 struct ast_smdi_mwi_message *mwi_msg = msg;
292 return mwi_msg->timestamp;
294 return md_msg->timestamp;
300 static inline void unref_msg(void *msg, enum smdi_message_type type)
302 struct ast_smdi_md_message *md_msg = msg;
303 struct ast_smdi_mwi_message *mwi_msg = msg;
307 ASTOBJ_UNREF(mwi_msg, ast_smdi_mwi_message_destroy);
309 ASTOBJ_UNREF(md_msg, ast_smdi_md_message_destroy);
313 static void purge_old_messages(struct ast_smdi_interface *iface, enum smdi_message_type type)
315 struct timeval now = ast_tvnow();
319 lock_msg_q(iface, type);
320 msg = unlink_from_msg_q(iface, type);
321 unlock_msg_q(iface, type);
323 /* purge old messages */
325 elapsed = ast_tvdiff_ms(now, msg_timestamp(msg, type));
327 if (elapsed > iface->msg_expiry) {
328 /* found an expired message */
329 unref_msg(msg, type);
330 ast_log(LOG_NOTICE, "Purged expired message from %s SMDI %s message queue. "
331 "Message was %ld milliseconds too old.\n",
332 iface->name, (type == SMDI_MD) ? "MD" : "MWI",
333 elapsed - iface->msg_expiry);
335 lock_msg_q(iface, type);
336 msg = unlink_from_msg_q(iface, type);
337 unlock_msg_q(iface, type);
339 /* good message, put it back and return */
342 ast_smdi_md_message_push(iface, msg);
345 ast_smdi_mwi_message_push(iface, msg);
348 unref_msg(msg, type);
354 static void *smdi_msg_pop(struct ast_smdi_interface *iface, enum smdi_message_type type)
358 purge_old_messages(iface, type);
360 lock_msg_q(iface, type);
361 msg = unlink_from_msg_q(iface, type);
362 unlock_msg_q(iface, type);
368 OPT_SEARCH_TERMINAL = (1 << 0),
369 OPT_SEARCH_NUMBER = (1 << 1),
372 static void *smdi_msg_find(struct ast_smdi_interface *iface,
373 enum smdi_message_type type, const char *search_key, struct ast_flags options)
377 purge_old_messages(iface, type);
381 if (ast_test_flag(&options, OPT_SEARCH_TERMINAL)) {
382 struct ast_smdi_md_message *md_msg = NULL;
384 /* Searching by the message desk terminal */
386 ASTOBJ_CONTAINER_TRAVERSE(&iface->md_q, !md_msg, do {
387 if (!strcasecmp(iterator->mesg_desk_term, search_key))
388 md_msg = ASTOBJ_REF(iterator);
392 } else if (ast_test_flag(&options, OPT_SEARCH_NUMBER)) {
393 struct ast_smdi_md_message *md_msg = NULL;
395 /* Searching by the message desk number */
397 ASTOBJ_CONTAINER_TRAVERSE(&iface->md_q, !md_msg, do {
398 if (!strcasecmp(iterator->mesg_desk_num, search_key))
399 md_msg = ASTOBJ_REF(iterator);
404 /* Searching by the forwarding station */
405 msg = ASTOBJ_CONTAINER_FIND(&iface->md_q, search_key);
409 msg = ASTOBJ_CONTAINER_FIND(&iface->mwi_q, search_key);
416 static void *smdi_message_wait(struct ast_smdi_interface *iface, int timeout,
417 enum smdi_message_type type, const char *search_key, struct ast_flags options)
419 struct timeval start;
425 while (diff < timeout) {
426 struct timespec ts = { 0, };
429 lock_msg_q(iface, type);
431 if ((msg = smdi_msg_find(iface, type, search_key, options))) {
432 unlock_msg_q(iface, type);
436 tv = ast_tvadd(start, ast_tv(0, timeout));
437 ts.tv_sec = tv.tv_sec;
438 ts.tv_nsec = tv.tv_usec * 1000;
440 /* If there were no messages in the queue, then go to sleep until one
443 ast_cond_timedwait(&iface->md_q_cond, &iface->md_q_lock, &ts);
445 if ((msg = smdi_msg_find(iface, type, search_key, options))) {
446 unlock_msg_q(iface, type);
450 unlock_msg_q(iface, type);
453 diff = ast_tvdiff_ms(ast_tvnow(), start);
459 struct ast_smdi_md_message *ast_smdi_md_message_pop(struct ast_smdi_interface *iface)
461 return smdi_msg_pop(iface, SMDI_MD);
464 struct ast_smdi_md_message *ast_smdi_md_message_wait(struct ast_smdi_interface *iface, int timeout)
466 struct ast_flags options = { 0 };
467 return smdi_message_wait(iface, timeout, SMDI_MD, NULL, options);
470 struct ast_smdi_mwi_message *ast_smdi_mwi_message_pop(struct ast_smdi_interface *iface)
472 return smdi_msg_pop(iface, SMDI_MWI);
475 struct ast_smdi_mwi_message *ast_smdi_mwi_message_wait(struct ast_smdi_interface *iface, int timeout)
477 struct ast_flags options = { 0 };
478 return smdi_message_wait(iface, timeout, SMDI_MWI, NULL, options);
481 struct ast_smdi_mwi_message *ast_smdi_mwi_message_wait_station(struct ast_smdi_interface *iface, int timeout,
484 struct ast_flags options = { 0 };
485 return smdi_message_wait(iface, timeout, SMDI_MWI, station, options);
488 struct ast_smdi_interface *ast_smdi_interface_find(const char *iface_name)
490 return (ASTOBJ_CONTAINER_FIND(&smdi_ifaces, iface_name));
495 * \brief Read an SMDI message.
497 * \param iface_p the SMDI interface to read from.
499 * This function loops and reads from and SMDI interface. It must be stopped
500 * using pthread_cancel().
502 static void *smdi_read(void *iface_p)
504 struct ast_smdi_interface *iface = iface_p;
505 struct ast_smdi_md_message *md_msg;
506 struct ast_smdi_mwi_message *mwi_msg;
512 /* read an smdi message */
513 while ((c = fgetc(iface->file))) {
515 /* check if this is the start of a message */
518 ast_log(LOG_DEBUG, "Read an 'M' to start an SMDI message\n");
524 if (c == 'D') { /* MD message */
527 ast_log(LOG_DEBUG, "Read a 'D' ... it's an MD message.\n");
529 if (!(md_msg = ast_calloc(1, sizeof(*md_msg)))) {
530 ASTOBJ_UNREF(iface, ast_smdi_interface_destroy);
536 /* read the message desk number */
537 for (i = 0; i < sizeof(md_msg->mesg_desk_num) - 1; i++) {
538 md_msg->mesg_desk_num[i] = fgetc(iface->file);
539 ast_log(LOG_DEBUG, "Read a '%c'\n", md_msg->mesg_desk_num[i]);
542 md_msg->mesg_desk_num[sizeof(md_msg->mesg_desk_num) - 1] = '\0';
544 ast_log(LOG_DEBUG, "The message desk number is '%s'\n", md_msg->mesg_desk_num);
546 /* read the message desk terminal number */
547 for (i = 0; i < sizeof(md_msg->mesg_desk_term) - 1; i++) {
548 md_msg->mesg_desk_term[i] = fgetc(iface->file);
549 ast_log(LOG_DEBUG, "Read a '%c'\n", md_msg->mesg_desk_term[i]);
552 md_msg->mesg_desk_term[sizeof(md_msg->mesg_desk_term) - 1] = '\0';
554 ast_log(LOG_DEBUG, "The message desk terminal is '%s'\n", md_msg->mesg_desk_term);
556 /* read the message type */
557 md_msg->type = fgetc(iface->file);
559 ast_log(LOG_DEBUG, "Message type is '%c'\n", md_msg->type);
561 /* read the forwarding station number (may be blank) */
562 cp = &md_msg->fwd_st[0];
563 for (i = 0; i < sizeof(md_msg->fwd_st) - 1; i++) {
564 if ((c = fgetc(iface->file)) == ' ') {
566 ast_log(LOG_DEBUG, "Read a space, done looking for the forwarding station\n");
570 /* store c in md_msg->fwd_st */
571 if (i >= iface->msdstrip) {
572 ast_log(LOG_DEBUG, "Read a '%c' and stored it in the forwarding station buffer\n", c);
575 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);
579 /* make sure the value is null terminated, even if this truncates it */
580 md_msg->fwd_st[sizeof(md_msg->fwd_st) - 1] = '\0';
583 ast_log(LOG_DEBUG, "The forwarding station is '%s'\n", md_msg->fwd_st);
585 /* Put the fwd_st in the name field so that we can use ASTOBJ_FIND to look
586 * up a message on this field */
587 ast_copy_string(md_msg->name, md_msg->fwd_st, sizeof(md_msg->name));
589 /* read the calling station number (may be blank) */
590 cp = &md_msg->calling_st[0];
591 for (i = 0; i < sizeof(md_msg->calling_st) - 1; i++) {
592 if (!isdigit((c = fgetc(iface->file)))) {
594 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);
596 /* Don't break on a space. We may read the space before the calling station
597 * here if the forwarding station buffer filled up. */
598 i--; /* We're still on the same character */
604 /* store c in md_msg->calling_st */
605 if (i >= iface->msdstrip) {
606 ast_log(LOG_DEBUG, "Read a '%c' and stored it in the calling station buffer\n", c);
609 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);
613 /* make sure the value is null terminated, even if this truncates it */
614 md_msg->calling_st[sizeof(md_msg->calling_st) - 1] = '\0';
617 ast_log(LOG_DEBUG, "The calling station is '%s'\n", md_msg->calling_st);
619 /* add the message to the message queue */
620 md_msg->timestamp = ast_tvnow();
621 ast_smdi_md_message_push(iface, md_msg);
622 ast_log(LOG_DEBUG, "Recieved SMDI MD message on %s\n", iface->name);
624 ASTOBJ_UNREF(md_msg, ast_smdi_md_message_destroy);
626 } else if (c == 'W') { /* MWI message */
629 ast_log(LOG_DEBUG, "Read a 'W', it's an MWI message. (No more debug coming for MWI messages)\n");
631 if (!(mwi_msg = ast_calloc(1, sizeof(*mwi_msg)))) {
632 ASTOBJ_UNREF(iface,ast_smdi_interface_destroy);
636 ASTOBJ_INIT(mwi_msg);
638 /* discard the 'I' (from 'MWI') */
641 /* read the forwarding station number (may be blank) */
642 cp = &mwi_msg->fwd_st[0];
643 for (i = 0; i < sizeof(mwi_msg->fwd_st) - 1; i++) {
644 if ((c = fgetc(iface->file)) == ' ') {
649 /* store c in md_msg->fwd_st */
650 if (i >= iface->msdstrip)
654 /* make sure the station number is null terminated, even if this will truncate it */
655 mwi_msg->fwd_st[sizeof(mwi_msg->fwd_st) - 1] = '\0';
658 /* Put the fwd_st in the name field so that we can use ASTOBJ_FIND to look
659 * up a message on this field */
660 ast_copy_string(mwi_msg->name, mwi_msg->fwd_st, sizeof(mwi_msg->name));
662 /* read the mwi failure cause */
663 for (i = 0; i < sizeof(mwi_msg->cause) - 1; i++)
664 mwi_msg->cause[i] = fgetc(iface->file);
666 mwi_msg->cause[sizeof(mwi_msg->cause) - 1] = '\0';
668 /* add the message to the message queue */
669 mwi_msg->timestamp = ast_tvnow();
670 ast_smdi_mwi_message_push(iface, mwi_msg);
671 ast_log(LOG_DEBUG, "Recieved SMDI MWI message on %s\n", iface->name);
673 ASTOBJ_UNREF(mwi_msg, ast_smdi_mwi_message_destroy);
675 ast_log(LOG_ERROR, "Unknown SMDI message type recieved on %s (M%c).\n", iface->name, c);
680 ast_log(LOG_ERROR, "Error reading from SMDI interface %s, stopping listener thread\n", iface->name);
681 ASTOBJ_UNREF(iface,ast_smdi_interface_destroy);
685 void ast_smdi_md_message_destroy(struct ast_smdi_md_message *msg)
690 void ast_smdi_mwi_message_destroy(struct ast_smdi_mwi_message *msg)
695 static void destroy_mailbox_mapping(struct mailbox_mapping *mm)
697 ast_string_field_free_memory(mm);
698 ASTOBJ_UNREF(mm->iface, ast_smdi_interface_destroy);
702 static void destroy_all_mailbox_mappings(void)
704 struct mailbox_mapping *mm;
706 ast_mutex_lock(&mwi_monitor.lock);
707 while ((mm = AST_LIST_REMOVE_HEAD(&mwi_monitor.mailbox_mappings, entry)))
708 destroy_mailbox_mapping(mm);
709 ast_mutex_unlock(&mwi_monitor.lock);
712 static void append_mailbox_mapping(struct ast_variable *var, struct ast_smdi_interface *iface)
714 struct mailbox_mapping *mm;
715 char *mailbox, *context;
717 if (!(mm = ast_calloc(1, sizeof(*mm))))
720 if (ast_string_field_init(mm, 32)) {
725 ast_string_field_set(mm, smdi, var->name);
727 context = ast_strdupa(var->value);
728 mailbox = strsep(&context, "@");
729 if (ast_strlen_zero(context))
732 ast_string_field_set(mm, mailbox, mailbox);
733 ast_string_field_set(mm, context, context);
735 mm->iface = ASTOBJ_REF(iface);
737 ast_mutex_lock(&mwi_monitor.lock);
738 AST_LIST_INSERT_TAIL(&mwi_monitor.mailbox_mappings, mm, entry);
739 ast_mutex_unlock(&mwi_monitor.lock);
743 * \note Called with the mwi_monitor.lock locked
745 static void poll_mailbox(struct mailbox_mapping *mm)
750 snprintf(buf, sizeof(buf), "%s@%s", mm->mailbox, mm->context);
752 state = !!ast_app_has_voicemail(mm->mailbox, NULL);
754 if (state != mm->cur_state) {
756 ast_smdi_mwi_set(mm->iface, mm->smdi);
758 ast_smdi_mwi_unset(mm->iface, mm->smdi);
760 mm->cur_state = state;
764 static void *mwi_monitor_handler(void *data)
766 while (!mwi_monitor.stop) {
767 struct timespec ts = { 0, };
769 struct mailbox_mapping *mm;
771 ast_mutex_lock(&mwi_monitor.lock);
773 mwi_monitor.last_poll = ast_tvnow();
775 AST_LIST_TRAVERSE(&mwi_monitor.mailbox_mappings, mm, entry)
778 /* Sleep up to the configured polling interval. Allow unload_module()
779 * to signal us to wake up and exit. */
780 tv = ast_tvadd(mwi_monitor.last_poll, ast_tv(mwi_monitor.polling_interval, 0));
781 ts.tv_sec = tv.tv_sec;
782 ts.tv_nsec = tv.tv_usec * 1000;
783 ast_cond_timedwait(&mwi_monitor.cond, &mwi_monitor.lock, &ts);
785 ast_mutex_unlock(&mwi_monitor.lock);
791 static struct ast_smdi_interface *alloc_smdi_interface(void)
793 struct ast_smdi_interface *iface;
795 if (!(iface = ast_calloc(1, sizeof(*iface))))
799 ASTOBJ_CONTAINER_INIT(&iface->md_q);
800 ASTOBJ_CONTAINER_INIT(&iface->mwi_q);
802 ast_mutex_init(&iface->md_q_lock);
803 ast_cond_init(&iface->md_q_cond, NULL);
805 ast_mutex_init(&iface->mwi_q_lock);
806 ast_cond_init(&iface->mwi_q_cond, NULL);
813 * \brief Load and reload SMDI configuration.
814 * \param reload this should be 1 if we are reloading and 0 if not.
816 * This function loads/reloads the SMDI configuration and starts and stops
817 * interfaces accordingly.
819 * \return zero on success, -1 on failure, and 1 if no smdi interfaces were started.
821 static int smdi_load(int reload)
823 struct ast_config *conf;
824 struct ast_variable *v;
825 struct ast_smdi_interface *iface = NULL;
826 struct ast_flags config_flags = { reload ? CONFIG_FLAG_FILEUNCHANGED : 0 };
830 speed_t baud_rate = B9600; /* 9600 baud rate */
831 tcflag_t paritybit = PARENB; /* even parity checking */
832 tcflag_t charsize = CS7; /* seven bit characters */
833 int stopbits = 0; /* One stop bit */
835 int msdstrip = 0; /* strip zero digits */
836 long msg_expiry = SMDI_MSG_EXPIRY_TIME;
838 if (!(conf = ast_config_load(config_file, config_flags))) {
840 ast_log(LOG_NOTICE, "Unable to reload config %s: SMDI untouched\n", config_file);
842 ast_log(LOG_NOTICE, "Unable to load config %s: SMDI disabled\n", config_file);
844 } else if (conf == CONFIG_STATUS_FILEUNCHANGED)
847 /* Mark all interfaces that we are listening on. We will unmark them
848 * as we find them in the config file, this way we know any interfaces
849 * still marked after we have finished parsing the config file should
853 ASTOBJ_CONTAINER_MARKALL(&smdi_ifaces);
855 for (v = ast_variable_browse(conf, "interfaces"); v; v = v->next) {
856 if (!strcasecmp(v->name, "baudrate")) {
857 if (!strcasecmp(v->value, "9600"))
859 else if (!strcasecmp(v->value, "4800"))
861 else if (!strcasecmp(v->value, "2400"))
863 else if (!strcasecmp(v->value, "1200"))
866 ast_log(LOG_NOTICE, "Invalid baud rate '%s' specified in %s (line %d), using default\n", v->value, config_file, v->lineno);
869 } else if (!strcasecmp(v->name, "msdstrip")) {
870 if (!sscanf(v->value, "%d", &msdstrip)) {
871 ast_log(LOG_NOTICE, "Invalid msdstrip value in %s (line %d), using default\n", config_file, v->lineno);
873 } else if (0 > msdstrip || msdstrip > 9) {
874 ast_log(LOG_NOTICE, "Invalid msdstrip value in %s (line %d), using default\n", config_file, v->lineno);
877 } else if (!strcasecmp(v->name, "msgexpirytime")) {
878 if (!sscanf(v->value, "%ld", &msg_expiry)) {
879 ast_log(LOG_NOTICE, "Invalid msgexpirytime value in %s (line %d), using default\n", config_file, v->lineno);
880 msg_expiry = SMDI_MSG_EXPIRY_TIME;
882 } else if (!strcasecmp(v->name, "paritybit")) {
883 if (!strcasecmp(v->value, "even"))
885 else if (!strcasecmp(v->value, "odd"))
886 paritybit = PARENB | PARODD;
887 else if (!strcasecmp(v->value, "none"))
890 ast_log(LOG_NOTICE, "Invalid parity bit setting in %s (line %d), using default\n", config_file, v->lineno);
893 } else if (!strcasecmp(v->name, "charsize")) {
894 if (!strcasecmp(v->value, "7"))
896 else if (!strcasecmp(v->value, "8"))
899 ast_log(LOG_NOTICE, "Invalid character size setting in %s (line %d), using default\n", config_file, v->lineno);
902 } else if (!strcasecmp(v->name, "twostopbits")) {
903 stopbits = ast_true(v->name);
904 } else if (!strcasecmp(v->name, "smdiport")) {
906 /* we are reloading, check if we are already
907 * monitoring this interface, if we are we do
908 * not want to start it again. This also has
909 * the side effect of not updating different
910 * setting for the serial port, but it should
911 * be trivial to rewrite this section so that
912 * options on the port are changed without
913 * restarting the interface. Or the interface
914 * could be restarted with out emptying the
916 if ((iface = ASTOBJ_CONTAINER_FIND(&smdi_ifaces, v->value))) {
917 ast_log(LOG_NOTICE, "SMDI interface %s already running, not restarting\n", iface->name);
918 ASTOBJ_UNMARK(iface);
919 ASTOBJ_UNREF(iface, ast_smdi_interface_destroy);
924 if (!(iface = alloc_smdi_interface()))
927 ast_copy_string(iface->name, v->value, sizeof(iface->name));
929 iface->thread = AST_PTHREADT_NULL;
931 if (!(iface->file = fopen(iface->name, "r"))) {
932 ast_log(LOG_ERROR, "Error opening SMDI interface %s (%s)\n", iface->name, strerror(errno));
933 ASTOBJ_UNREF(iface, ast_smdi_interface_destroy);
937 iface->fd = fileno(iface->file);
939 /* Set the proper attributes for our serial port. */
941 /* get the current attributes from the port */
942 if (tcgetattr(iface->fd, &iface->mode)) {
943 ast_log(LOG_ERROR, "Error getting atributes of %s (%s)\n", iface->name, strerror(errno));
944 ASTOBJ_UNREF(iface, ast_smdi_interface_destroy);
948 /* set the desired speed */
949 if (cfsetispeed(&iface->mode, baud_rate) || cfsetospeed(&iface->mode, baud_rate)) {
950 ast_log(LOG_ERROR, "Error setting baud rate on %s (%s)\n", iface->name, strerror(errno));
951 ASTOBJ_UNREF(iface, ast_smdi_interface_destroy);
955 /* set the stop bits */
957 iface->mode.c_cflag = iface->mode.c_cflag | CSTOPB; /* set two stop bits */
959 iface->mode.c_cflag = iface->mode.c_cflag & ~CSTOPB; /* set one stop bit */
962 iface->mode.c_cflag = (iface->mode.c_cflag & ~PARENB & ~PARODD) | paritybit;
964 /* set the character size */
965 iface->mode.c_cflag = (iface->mode.c_cflag & ~CSIZE) | charsize;
967 /* commit the desired attributes */
968 if (tcsetattr(iface->fd, TCSAFLUSH, &iface->mode)) {
969 ast_log(LOG_ERROR, "Error setting attributes on %s (%s)\n", iface->name, strerror(errno));
970 ASTOBJ_UNREF(iface, ast_smdi_interface_destroy);
974 /* set the msdstrip */
975 iface->msdstrip = msdstrip;
977 /* set the message expiry time */
978 iface->msg_expiry = msg_expiry;
980 /* start the listener thread */
981 ast_verb(3, "Starting SMDI monitor thread for %s\n", iface->name);
982 if (ast_pthread_create_background(&iface->thread, NULL, smdi_read, iface)) {
983 ast_log(LOG_ERROR, "Error starting SMDI monitor thread for %s\n", iface->name);
984 ASTOBJ_UNREF(iface, ast_smdi_interface_destroy);
988 ASTOBJ_CONTAINER_LINK(&smdi_ifaces, iface);
989 ASTOBJ_UNREF(iface, ast_smdi_interface_destroy);
990 ast_module_ref(ast_module_info->self);
992 ast_log(LOG_NOTICE, "Ignoring unknown option %s in %s\n", v->name, config_file);
996 destroy_all_mailbox_mappings();
997 mwi_monitor.polling_interval = DEFAULT_POLLING_INTERVAL;
1001 for (v = ast_variable_browse(conf, "mailboxes"); v; v = v->next) {
1002 if (!strcasecmp(v->name, "smdiport")) {
1004 ASTOBJ_UNREF(iface, ast_smdi_interface_destroy);
1006 if (!(iface = ASTOBJ_CONTAINER_FIND(&smdi_ifaces, v->value))) {
1007 ast_log(LOG_NOTICE, "SMDI interface %s not found\n", iface->name);
1010 } else if (!strcasecmp(v->name, "pollinginterval")) {
1011 if (sscanf(v->value, "%u", &mwi_monitor.polling_interval) != 1) {
1012 ast_log(LOG_ERROR, "Invalid value for pollinginterval: %s\n", v->value);
1013 mwi_monitor.polling_interval = DEFAULT_POLLING_INTERVAL;
1017 ast_log(LOG_ERROR, "Mailbox mapping ignored, no valid SMDI interface specified in mailboxes section\n");
1020 append_mailbox_mapping(v, iface);
1025 ASTOBJ_UNREF(iface, ast_smdi_interface_destroy);
1027 ast_config_destroy(conf);
1029 if (!AST_LIST_EMPTY(&mwi_monitor.mailbox_mappings) && mwi_monitor.thread == AST_PTHREADT_NULL
1030 && ast_pthread_create_background(&mwi_monitor.thread, NULL, mwi_monitor_handler, NULL)) {
1031 ast_log(LOG_ERROR, "Failed to start MWI monitoring thread. This module will not operate.\n");
1032 return AST_MODULE_LOAD_FAILURE;
1035 /* Prune any interfaces we should no longer monitor. */
1037 ASTOBJ_CONTAINER_PRUNE_MARKED(&smdi_ifaces, ast_smdi_interface_destroy);
1039 ASTOBJ_CONTAINER_RDLOCK(&smdi_ifaces);
1040 /* TODO: this is bad, we need an ASTOBJ method for this! */
1041 if (!smdi_ifaces.head)
1043 ASTOBJ_CONTAINER_UNLOCK(&smdi_ifaces);
1048 struct smdi_msg_datastore {
1050 struct ast_smdi_interface *iface;
1051 struct ast_smdi_md_message *md_msg;
1054 static void smdi_msg_datastore_destroy(void *data)
1056 struct smdi_msg_datastore *smd = data;
1059 ASTOBJ_UNREF(smd->iface, ast_smdi_interface_destroy);
1062 ASTOBJ_UNREF(smd->md_msg, ast_smdi_md_message_destroy);
1067 static const struct ast_datastore_info smdi_msg_datastore_info = {
1069 .destroy = smdi_msg_datastore_destroy,
1072 static int smdi_msg_id;
1074 /*! In milliseconds */
1075 #define SMDI_RETRIEVE_TIMEOUT_DEFAULT 3000
1077 AST_APP_OPTIONS(smdi_msg_ret_options, BEGIN_OPTIONS
1078 AST_APP_OPTION('t', OPT_SEARCH_TERMINAL),
1079 AST_APP_OPTION('n', OPT_SEARCH_NUMBER),
1082 static int smdi_msg_retrieve_read(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len)
1084 struct ast_module_user *u;
1085 AST_DECLARE_APP_ARGS(args,
1087 AST_APP_ARG(search_key);
1088 AST_APP_ARG(timeout);
1089 AST_APP_ARG(options);
1091 struct ast_flags options = { 0 };
1092 unsigned int timeout = SMDI_RETRIEVE_TIMEOUT_DEFAULT;
1095 struct smdi_msg_datastore *smd = NULL;
1096 struct ast_datastore *datastore = NULL;
1097 struct ast_smdi_interface *iface = NULL;
1098 struct ast_smdi_md_message *md_msg = NULL;
1100 u = ast_module_user_add(chan);
1102 if (ast_strlen_zero(data)) {
1103 ast_log(LOG_ERROR, "SMDI_MSG_RETRIEVE requires an argument\n");
1108 ast_log(LOG_ERROR, "SMDI_MSG_RETRIEVE must be used with a channel\n");
1112 ast_autoservice_start(chan);
1114 parse = ast_strdupa(data);
1115 AST_STANDARD_APP_ARGS(args, parse);
1117 if (ast_strlen_zero(args.port) || ast_strlen_zero(args.search_key)) {
1118 ast_log(LOG_ERROR, "Invalid arguments provided to SMDI_MSG_RETRIEVE\n");
1122 if (!(iface = ast_smdi_interface_find(args.port))) {
1123 ast_log(LOG_ERROR, "SMDI port '%s' not found\n", args.port);
1127 if (!ast_strlen_zero(args.options)) {
1128 ast_app_parse_options(smdi_msg_ret_options, &options, NULL, args.options);
1131 if (!ast_strlen_zero(args.timeout)) {
1132 if (sscanf(args.timeout, "%u", &timeout) != 1) {
1133 ast_log(LOG_ERROR, "'%s' is not a valid timeout\n", args.timeout);
1134 timeout = SMDI_RETRIEVE_TIMEOUT_DEFAULT;
1138 if (!(md_msg = smdi_message_wait(iface, timeout, SMDI_MD, args.search_key, options))) {
1139 ast_log(LOG_WARNING, "No SMDI message retrieved for search key '%s' after "
1140 "waiting %u ms.\n", args.search_key, timeout);
1144 if (!(smd = ast_calloc(1, sizeof(*smd))))
1147 smd->iface = ASTOBJ_REF(iface);
1148 smd->md_msg = ASTOBJ_REF(md_msg);
1149 smd->id = ast_atomic_fetchadd_int((int *) &smdi_msg_id, 1);
1150 snprintf(buf, len, "%u", smd->id);
1152 if (!(datastore = ast_channel_datastore_alloc(&smdi_msg_datastore_info, buf)))
1155 datastore->data = smd;
1157 ast_channel_lock(chan);
1158 ast_channel_datastore_add(chan, datastore);
1159 ast_channel_unlock(chan);
1165 ASTOBJ_UNREF(iface, ast_smdi_interface_destroy);
1168 ASTOBJ_UNREF(md_msg, ast_smdi_md_message_destroy);
1170 if (smd && !datastore)
1171 smdi_msg_datastore_destroy(smd);
1174 ast_autoservice_stop(chan);
1176 ast_module_user_remove(u);
1181 static int smdi_msg_read(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len)
1183 struct ast_module_user *u;
1185 AST_DECLARE_APP_ARGS(args,
1187 AST_APP_ARG(component);
1190 struct ast_datastore *datastore = NULL;
1191 struct smdi_msg_datastore *smd = NULL;
1193 u = ast_module_user_add(chan);
1196 ast_log(LOG_ERROR, "SMDI_MSG can not be called without a channel\n");
1200 if (ast_strlen_zero(data)) {
1201 ast_log(LOG_WARNING, "SMDI_MSG requires an argument\n");
1205 parse = ast_strdupa(data);
1206 AST_STANDARD_APP_ARGS(args, parse);
1208 if (ast_strlen_zero(args.id)) {
1209 ast_log(LOG_WARNING, "ID must be supplied to SMDI_MSG\n");
1213 if (ast_strlen_zero(args.component)) {
1214 ast_log(LOG_WARNING, "ID must be supplied to SMDI_MSG\n");
1218 ast_channel_lock(chan);
1219 datastore = ast_channel_datastore_find(chan, &smdi_msg_datastore_info, args.id);
1220 ast_channel_unlock(chan);
1223 ast_log(LOG_WARNING, "No SMDI message found for message ID '%s'\n", args.id);
1227 smd = datastore->data;
1229 if (!strcasecmp(args.component, "number")) {
1230 ast_copy_string(buf, smd->md_msg->mesg_desk_num, len);
1231 } else if (!strcasecmp(args.component, "terminal")) {
1232 ast_copy_string(buf, smd->md_msg->mesg_desk_term, len);
1233 } else if (!strcasecmp(args.component, "station")) {
1234 ast_copy_string(buf, smd->md_msg->fwd_st, len);
1235 } else if (!strcasecmp(args.component, "callerid")) {
1236 ast_copy_string(buf, smd->md_msg->calling_st, len);
1237 } else if (!strcasecmp(args.component, "type")) {
1238 snprintf(buf, len, "%c", smd->md_msg->type);
1240 ast_log(LOG_ERROR, "'%s' is not a valid message component for SMDI_MSG\n",
1248 ast_module_user_remove(u);
1253 static struct ast_custom_function smdi_msg_retrieve_function = {
1254 .name = "SMDI_MSG_RETRIEVE",
1255 .synopsis = "Retrieve an SMDI message.",
1256 .syntax = "SMDI_MSG_RETRIEVE(<smdi port>,<search key>[,timeout[,options]])",
1258 " This function is used to retrieve an incoming SMDI message. It returns\n"
1259 "an ID which can be used with the SMDI_MSG() function to access details of\n"
1260 "the message. Note that this is a destructive function in the sense that\n"
1261 "once an SMDI message is retrieved using this function, it is no longer in\n"
1262 "the global SMDI message queue, and can not be accessed by any other Asterisk\n"
1263 "channels. The timeout for this function is optional, and the default is\n"
1264 "3 seconds. When providing a timeout, it should be in milliseconds.\n"
1265 " The default search is done on the forwarding station ID. However, if\n"
1266 "you set one of the search key options in the options field, you can change\n"
1269 " t - Instead of searching on the forwarding station, search on the message\n"
1271 " n - Instead of searching on the forwarding station, search on the message\n"
1274 .read = smdi_msg_retrieve_read,
1277 static struct ast_custom_function smdi_msg_function = {
1279 .synopsis = "Retrieve details about an SMDI message.",
1280 .syntax = "SMDI_MSG(<message_id>,<component>)",
1282 " This function is used to access details of an SMDI message that was\n"
1283 "pulled from the incoming SMDI message queue using the SMDI_MSG_RETRIEVE()\n"
1285 " Valid message components are:\n"
1286 " number - The message desk number\n"
1287 " terminal - The message desk terminal\n"
1288 " station - The forwarding station\n"
1289 " callerid - The callerID of the calling party that was forwarded\n"
1290 " type - The call type. The value here is the exact character\n"
1291 " that came in on the SMDI link. Typically, example values\n"
1292 " are: D - Direct Calls, A - Forward All Calls,\n"
1293 " B - Forward Busy Calls, N - Forward No Answer Calls\n"
1295 .read = smdi_msg_read,
1298 static int load_module(void)
1302 /* initialize our containers */
1303 memset(&smdi_ifaces, 0, sizeof(smdi_ifaces));
1304 ASTOBJ_CONTAINER_INIT(&smdi_ifaces);
1306 ast_mutex_init(&mwi_monitor.lock);
1307 ast_cond_init(&mwi_monitor.cond, NULL);
1309 ast_custom_function_register(&smdi_msg_retrieve_function);
1310 ast_custom_function_register(&smdi_msg_function);
1312 /* load the config and start the listener threads*/
1316 } else if (res == 1) {
1317 ast_log(LOG_NOTICE, "No SMDI interfaces are available to listen on, not starting SMDI listener.\n");
1318 return AST_MODULE_LOAD_DECLINE;
1321 return AST_MODULE_LOAD_SUCCESS;
1324 static int unload_module(void)
1326 /* this destructor stops any running smdi_read threads */
1327 ASTOBJ_CONTAINER_DESTROYALL(&smdi_ifaces, ast_smdi_interface_destroy);
1328 ASTOBJ_CONTAINER_DESTROY(&smdi_ifaces);
1330 destroy_all_mailbox_mappings();
1332 ast_mutex_lock(&mwi_monitor.lock);
1333 mwi_monitor.stop = 1;
1334 ast_cond_signal(&mwi_monitor.cond);
1335 ast_mutex_unlock(&mwi_monitor.lock);
1337 if (mwi_monitor.thread != AST_PTHREADT_NULL) {
1338 pthread_join(mwi_monitor.thread, NULL);
1341 ast_custom_function_unregister(&smdi_msg_retrieve_function);
1342 ast_custom_function_unregister(&smdi_msg_function);
1347 static int reload(void)
1355 } else if (res == 1) {
1356 ast_log(LOG_WARNING, "No SMDI interfaces were specified to listen on, not starting SDMI listener.\n");
1362 AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_GLOBAL_SYMBOLS, "Simplified Message Desk Interface (SMDI) Resource",
1363 .load = load_module,
1364 .unload = unload_module,