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;
422 ast_cond_t *cond = NULL;
423 ast_mutex_t *lock = NULL;
427 cond = &iface->mwi_q_cond;
428 lock = &iface->mwi_q_lock;
431 cond = &iface->md_q_cond;
432 lock = &iface->md_q_lock;
438 while (diff < timeout) {
439 struct timespec ts = { 0, };
442 lock_msg_q(iface, type);
444 if ((msg = smdi_msg_find(iface, type, search_key, options))) {
445 unlock_msg_q(iface, type);
449 tv = ast_tvadd(start, ast_tv(0, timeout));
450 ts.tv_sec = tv.tv_sec;
451 ts.tv_nsec = tv.tv_usec * 1000;
453 /* If there were no messages in the queue, then go to sleep until one
456 ast_cond_timedwait(cond, lock, &ts);
458 if ((msg = smdi_msg_find(iface, type, search_key, options))) {
459 unlock_msg_q(iface, type);
463 unlock_msg_q(iface, type);
466 diff = ast_tvdiff_ms(ast_tvnow(), start);
472 struct ast_smdi_md_message *ast_smdi_md_message_pop(struct ast_smdi_interface *iface)
474 return smdi_msg_pop(iface, SMDI_MD);
477 struct ast_smdi_md_message *ast_smdi_md_message_wait(struct ast_smdi_interface *iface, int timeout)
479 struct ast_flags options = { 0 };
480 return smdi_message_wait(iface, timeout, SMDI_MD, NULL, options);
483 struct ast_smdi_mwi_message *ast_smdi_mwi_message_pop(struct ast_smdi_interface *iface)
485 return smdi_msg_pop(iface, SMDI_MWI);
488 struct ast_smdi_mwi_message *ast_smdi_mwi_message_wait(struct ast_smdi_interface *iface, int timeout)
490 struct ast_flags options = { 0 };
491 return smdi_message_wait(iface, timeout, SMDI_MWI, NULL, options);
494 struct ast_smdi_mwi_message *ast_smdi_mwi_message_wait_station(struct ast_smdi_interface *iface, int timeout,
497 struct ast_flags options = { 0 };
498 return smdi_message_wait(iface, timeout, SMDI_MWI, station, options);
501 struct ast_smdi_interface *ast_smdi_interface_find(const char *iface_name)
503 return (ASTOBJ_CONTAINER_FIND(&smdi_ifaces, iface_name));
508 * \brief Read an SMDI message.
510 * \param iface_p the SMDI interface to read from.
512 * This function loops and reads from and SMDI interface. It must be stopped
513 * using pthread_cancel().
515 static void *smdi_read(void *iface_p)
517 struct ast_smdi_interface *iface = iface_p;
518 struct ast_smdi_md_message *md_msg;
519 struct ast_smdi_mwi_message *mwi_msg;
525 /* read an smdi message */
526 while ((c = fgetc(iface->file))) {
528 /* check if this is the start of a message */
531 ast_log(LOG_DEBUG, "Read an 'M' to start an SMDI message\n");
537 if (c == 'D') { /* MD message */
540 ast_log(LOG_DEBUG, "Read a 'D' ... it's an MD message.\n");
542 if (!(md_msg = ast_calloc(1, sizeof(*md_msg)))) {
543 ASTOBJ_UNREF(iface, ast_smdi_interface_destroy);
549 /* read the message desk number */
550 for (i = 0; i < sizeof(md_msg->mesg_desk_num) - 1; i++) {
551 md_msg->mesg_desk_num[i] = fgetc(iface->file);
552 ast_log(LOG_DEBUG, "Read a '%c'\n", md_msg->mesg_desk_num[i]);
555 md_msg->mesg_desk_num[sizeof(md_msg->mesg_desk_num) - 1] = '\0';
557 ast_log(LOG_DEBUG, "The message desk number is '%s'\n", md_msg->mesg_desk_num);
559 /* read the message desk terminal number */
560 for (i = 0; i < sizeof(md_msg->mesg_desk_term) - 1; i++) {
561 md_msg->mesg_desk_term[i] = fgetc(iface->file);
562 ast_log(LOG_DEBUG, "Read a '%c'\n", md_msg->mesg_desk_term[i]);
565 md_msg->mesg_desk_term[sizeof(md_msg->mesg_desk_term) - 1] = '\0';
567 ast_log(LOG_DEBUG, "The message desk terminal is '%s'\n", md_msg->mesg_desk_term);
569 /* read the message type */
570 md_msg->type = fgetc(iface->file);
572 ast_log(LOG_DEBUG, "Message type is '%c'\n", md_msg->type);
574 /* read the forwarding station number (may be blank) */
575 cp = &md_msg->fwd_st[0];
576 for (i = 0; i < sizeof(md_msg->fwd_st) - 1; i++) {
577 if ((c = fgetc(iface->file)) == ' ') {
579 ast_log(LOG_DEBUG, "Read a space, done looking for the forwarding station\n");
583 /* store c in md_msg->fwd_st */
584 if (i >= iface->msdstrip) {
585 ast_log(LOG_DEBUG, "Read a '%c' and stored it in the forwarding station buffer\n", c);
588 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);
592 /* make sure the value is null terminated, even if this truncates it */
593 md_msg->fwd_st[sizeof(md_msg->fwd_st) - 1] = '\0';
596 ast_log(LOG_DEBUG, "The forwarding station is '%s'\n", md_msg->fwd_st);
598 /* Put the fwd_st in the name field so that we can use ASTOBJ_FIND to look
599 * up a message on this field */
600 ast_copy_string(md_msg->name, md_msg->fwd_st, sizeof(md_msg->name));
602 /* read the calling station number (may be blank) */
603 cp = &md_msg->calling_st[0];
604 for (i = 0; i < sizeof(md_msg->calling_st) - 1; i++) {
605 if (!isdigit((c = fgetc(iface->file)))) {
607 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);
609 /* Don't break on a space. We may read the space before the calling station
610 * here if the forwarding station buffer filled up. */
611 i--; /* We're still on the same character */
617 /* store c in md_msg->calling_st */
618 if (i >= iface->msdstrip) {
619 ast_log(LOG_DEBUG, "Read a '%c' and stored it in the calling station buffer\n", c);
622 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);
626 /* make sure the value is null terminated, even if this truncates it */
627 md_msg->calling_st[sizeof(md_msg->calling_st) - 1] = '\0';
630 ast_log(LOG_DEBUG, "The calling station is '%s'\n", md_msg->calling_st);
632 /* add the message to the message queue */
633 md_msg->timestamp = ast_tvnow();
634 ast_smdi_md_message_push(iface, md_msg);
635 ast_log(LOG_DEBUG, "Recieved SMDI MD message on %s\n", iface->name);
637 ASTOBJ_UNREF(md_msg, ast_smdi_md_message_destroy);
639 } else if (c == 'W') { /* MWI message */
642 ast_log(LOG_DEBUG, "Read a 'W', it's an MWI message. (No more debug coming for MWI messages)\n");
644 if (!(mwi_msg = ast_calloc(1, sizeof(*mwi_msg)))) {
645 ASTOBJ_UNREF(iface,ast_smdi_interface_destroy);
649 ASTOBJ_INIT(mwi_msg);
651 /* discard the 'I' (from 'MWI') */
654 /* read the forwarding station number (may be blank) */
655 cp = &mwi_msg->fwd_st[0];
656 for (i = 0; i < sizeof(mwi_msg->fwd_st) - 1; i++) {
657 if ((c = fgetc(iface->file)) == ' ') {
662 /* store c in md_msg->fwd_st */
663 if (i >= iface->msdstrip)
667 /* make sure the station number is null terminated, even if this will truncate it */
668 mwi_msg->fwd_st[sizeof(mwi_msg->fwd_st) - 1] = '\0';
671 /* Put the fwd_st in the name field so that we can use ASTOBJ_FIND to look
672 * up a message on this field */
673 ast_copy_string(mwi_msg->name, mwi_msg->fwd_st, sizeof(mwi_msg->name));
675 /* read the mwi failure cause */
676 for (i = 0; i < sizeof(mwi_msg->cause) - 1; i++)
677 mwi_msg->cause[i] = fgetc(iface->file);
679 mwi_msg->cause[sizeof(mwi_msg->cause) - 1] = '\0';
681 /* add the message to the message queue */
682 mwi_msg->timestamp = ast_tvnow();
683 ast_smdi_mwi_message_push(iface, mwi_msg);
684 ast_log(LOG_DEBUG, "Recieved SMDI MWI message on %s\n", iface->name);
686 ASTOBJ_UNREF(mwi_msg, ast_smdi_mwi_message_destroy);
688 ast_log(LOG_ERROR, "Unknown SMDI message type recieved on %s (M%c).\n", iface->name, c);
693 ast_log(LOG_ERROR, "Error reading from SMDI interface %s, stopping listener thread\n", iface->name);
694 ASTOBJ_UNREF(iface,ast_smdi_interface_destroy);
698 void ast_smdi_md_message_destroy(struct ast_smdi_md_message *msg)
703 void ast_smdi_mwi_message_destroy(struct ast_smdi_mwi_message *msg)
708 static void destroy_mailbox_mapping(struct mailbox_mapping *mm)
710 ast_string_field_free_memory(mm);
711 ASTOBJ_UNREF(mm->iface, ast_smdi_interface_destroy);
715 static void destroy_all_mailbox_mappings(void)
717 struct mailbox_mapping *mm;
719 ast_mutex_lock(&mwi_monitor.lock);
720 while ((mm = AST_LIST_REMOVE_HEAD(&mwi_monitor.mailbox_mappings, entry)))
721 destroy_mailbox_mapping(mm);
722 ast_mutex_unlock(&mwi_monitor.lock);
725 static void append_mailbox_mapping(struct ast_variable *var, struct ast_smdi_interface *iface)
727 struct mailbox_mapping *mm;
728 char *mailbox, *context;
730 if (!(mm = ast_calloc(1, sizeof(*mm))))
733 if (ast_string_field_init(mm, 32)) {
738 ast_string_field_set(mm, smdi, var->name);
740 context = ast_strdupa(var->value);
741 mailbox = strsep(&context, "@");
742 if (ast_strlen_zero(context))
745 ast_string_field_set(mm, mailbox, mailbox);
746 ast_string_field_set(mm, context, context);
748 mm->iface = ASTOBJ_REF(iface);
750 ast_mutex_lock(&mwi_monitor.lock);
751 AST_LIST_INSERT_TAIL(&mwi_monitor.mailbox_mappings, mm, entry);
752 ast_mutex_unlock(&mwi_monitor.lock);
756 * \note Called with the mwi_monitor.lock locked
758 static void poll_mailbox(struct mailbox_mapping *mm)
763 snprintf(buf, sizeof(buf), "%s@%s", mm->mailbox, mm->context);
765 state = !!ast_app_has_voicemail(mm->mailbox, NULL);
767 if (state != mm->cur_state) {
769 ast_smdi_mwi_set(mm->iface, mm->smdi);
771 ast_smdi_mwi_unset(mm->iface, mm->smdi);
773 mm->cur_state = state;
777 static void *mwi_monitor_handler(void *data)
779 while (!mwi_monitor.stop) {
780 struct timespec ts = { 0, };
782 struct mailbox_mapping *mm;
784 ast_mutex_lock(&mwi_monitor.lock);
786 mwi_monitor.last_poll = ast_tvnow();
788 AST_LIST_TRAVERSE(&mwi_monitor.mailbox_mappings, mm, entry)
791 /* Sleep up to the configured polling interval. Allow unload_module()
792 * to signal us to wake up and exit. */
793 tv = ast_tvadd(mwi_monitor.last_poll, ast_tv(mwi_monitor.polling_interval, 0));
794 ts.tv_sec = tv.tv_sec;
795 ts.tv_nsec = tv.tv_usec * 1000;
796 ast_cond_timedwait(&mwi_monitor.cond, &mwi_monitor.lock, &ts);
798 ast_mutex_unlock(&mwi_monitor.lock);
804 static struct ast_smdi_interface *alloc_smdi_interface(void)
806 struct ast_smdi_interface *iface;
808 if (!(iface = ast_calloc(1, sizeof(*iface))))
812 ASTOBJ_CONTAINER_INIT(&iface->md_q);
813 ASTOBJ_CONTAINER_INIT(&iface->mwi_q);
815 ast_mutex_init(&iface->md_q_lock);
816 ast_cond_init(&iface->md_q_cond, NULL);
818 ast_mutex_init(&iface->mwi_q_lock);
819 ast_cond_init(&iface->mwi_q_cond, NULL);
826 * \brief Load and reload SMDI configuration.
827 * \param reload this should be 1 if we are reloading and 0 if not.
829 * This function loads/reloads the SMDI configuration and starts and stops
830 * interfaces accordingly.
832 * \return zero on success, -1 on failure, and 1 if no smdi interfaces were started.
834 static int smdi_load(int reload)
836 struct ast_config *conf;
837 struct ast_variable *v;
838 struct ast_smdi_interface *iface = NULL;
839 struct ast_flags config_flags = { reload ? CONFIG_FLAG_FILEUNCHANGED : 0 };
843 speed_t baud_rate = B9600; /* 9600 baud rate */
844 tcflag_t paritybit = PARENB; /* even parity checking */
845 tcflag_t charsize = CS7; /* seven bit characters */
846 int stopbits = 0; /* One stop bit */
848 int msdstrip = 0; /* strip zero digits */
849 long msg_expiry = SMDI_MSG_EXPIRY_TIME;
851 if (!(conf = ast_config_load(config_file, config_flags))) {
853 ast_log(LOG_NOTICE, "Unable to reload config %s: SMDI untouched\n", config_file);
855 ast_log(LOG_NOTICE, "Unable to load config %s: SMDI disabled\n", config_file);
857 } else if (conf == CONFIG_STATUS_FILEUNCHANGED)
860 /* Mark all interfaces that we are listening on. We will unmark them
861 * as we find them in the config file, this way we know any interfaces
862 * still marked after we have finished parsing the config file should
866 ASTOBJ_CONTAINER_MARKALL(&smdi_ifaces);
868 for (v = ast_variable_browse(conf, "interfaces"); v; v = v->next) {
869 if (!strcasecmp(v->name, "baudrate")) {
870 if (!strcasecmp(v->value, "9600"))
872 else if (!strcasecmp(v->value, "4800"))
874 else if (!strcasecmp(v->value, "2400"))
876 else if (!strcasecmp(v->value, "1200"))
879 ast_log(LOG_NOTICE, "Invalid baud rate '%s' specified in %s (line %d), using default\n", v->value, config_file, v->lineno);
882 } else if (!strcasecmp(v->name, "msdstrip")) {
883 if (!sscanf(v->value, "%d", &msdstrip)) {
884 ast_log(LOG_NOTICE, "Invalid msdstrip value in %s (line %d), using default\n", config_file, v->lineno);
886 } else if (0 > msdstrip || msdstrip > 9) {
887 ast_log(LOG_NOTICE, "Invalid msdstrip value in %s (line %d), using default\n", config_file, v->lineno);
890 } else if (!strcasecmp(v->name, "msgexpirytime")) {
891 if (!sscanf(v->value, "%ld", &msg_expiry)) {
892 ast_log(LOG_NOTICE, "Invalid msgexpirytime value in %s (line %d), using default\n", config_file, v->lineno);
893 msg_expiry = SMDI_MSG_EXPIRY_TIME;
895 } else if (!strcasecmp(v->name, "paritybit")) {
896 if (!strcasecmp(v->value, "even"))
898 else if (!strcasecmp(v->value, "odd"))
899 paritybit = PARENB | PARODD;
900 else if (!strcasecmp(v->value, "none"))
903 ast_log(LOG_NOTICE, "Invalid parity bit setting in %s (line %d), using default\n", config_file, v->lineno);
906 } else if (!strcasecmp(v->name, "charsize")) {
907 if (!strcasecmp(v->value, "7"))
909 else if (!strcasecmp(v->value, "8"))
912 ast_log(LOG_NOTICE, "Invalid character size setting in %s (line %d), using default\n", config_file, v->lineno);
915 } else if (!strcasecmp(v->name, "twostopbits")) {
916 stopbits = ast_true(v->name);
917 } else if (!strcasecmp(v->name, "smdiport")) {
919 /* we are reloading, check if we are already
920 * monitoring this interface, if we are we do
921 * not want to start it again. This also has
922 * the side effect of not updating different
923 * setting for the serial port, but it should
924 * be trivial to rewrite this section so that
925 * options on the port are changed without
926 * restarting the interface. Or the interface
927 * could be restarted with out emptying the
929 if ((iface = ASTOBJ_CONTAINER_FIND(&smdi_ifaces, v->value))) {
930 ast_log(LOG_NOTICE, "SMDI interface %s already running, not restarting\n", iface->name);
931 ASTOBJ_UNMARK(iface);
932 ASTOBJ_UNREF(iface, ast_smdi_interface_destroy);
937 if (!(iface = alloc_smdi_interface()))
940 ast_copy_string(iface->name, v->value, sizeof(iface->name));
942 iface->thread = AST_PTHREADT_NULL;
944 if (!(iface->file = fopen(iface->name, "r"))) {
945 ast_log(LOG_ERROR, "Error opening SMDI interface %s (%s)\n", iface->name, strerror(errno));
946 ASTOBJ_UNREF(iface, ast_smdi_interface_destroy);
950 iface->fd = fileno(iface->file);
952 /* Set the proper attributes for our serial port. */
954 /* get the current attributes from the port */
955 if (tcgetattr(iface->fd, &iface->mode)) {
956 ast_log(LOG_ERROR, "Error getting atributes of %s (%s)\n", iface->name, strerror(errno));
957 ASTOBJ_UNREF(iface, ast_smdi_interface_destroy);
961 /* set the desired speed */
962 if (cfsetispeed(&iface->mode, baud_rate) || cfsetospeed(&iface->mode, baud_rate)) {
963 ast_log(LOG_ERROR, "Error setting baud rate on %s (%s)\n", iface->name, strerror(errno));
964 ASTOBJ_UNREF(iface, ast_smdi_interface_destroy);
968 /* set the stop bits */
970 iface->mode.c_cflag = iface->mode.c_cflag | CSTOPB; /* set two stop bits */
972 iface->mode.c_cflag = iface->mode.c_cflag & ~CSTOPB; /* set one stop bit */
975 iface->mode.c_cflag = (iface->mode.c_cflag & ~PARENB & ~PARODD) | paritybit;
977 /* set the character size */
978 iface->mode.c_cflag = (iface->mode.c_cflag & ~CSIZE) | charsize;
980 /* commit the desired attributes */
981 if (tcsetattr(iface->fd, TCSAFLUSH, &iface->mode)) {
982 ast_log(LOG_ERROR, "Error setting attributes on %s (%s)\n", iface->name, strerror(errno));
983 ASTOBJ_UNREF(iface, ast_smdi_interface_destroy);
987 /* set the msdstrip */
988 iface->msdstrip = msdstrip;
990 /* set the message expiry time */
991 iface->msg_expiry = msg_expiry;
993 /* start the listener thread */
994 ast_verb(3, "Starting SMDI monitor thread for %s\n", iface->name);
995 if (ast_pthread_create_background(&iface->thread, NULL, smdi_read, iface)) {
996 ast_log(LOG_ERROR, "Error starting SMDI monitor thread for %s\n", iface->name);
997 ASTOBJ_UNREF(iface, ast_smdi_interface_destroy);
1001 ASTOBJ_CONTAINER_LINK(&smdi_ifaces, iface);
1002 ASTOBJ_UNREF(iface, ast_smdi_interface_destroy);
1003 ast_module_ref(ast_module_info->self);
1005 ast_log(LOG_NOTICE, "Ignoring unknown option %s in %s\n", v->name, config_file);
1009 destroy_all_mailbox_mappings();
1010 mwi_monitor.polling_interval = DEFAULT_POLLING_INTERVAL;
1014 for (v = ast_variable_browse(conf, "mailboxes"); v; v = v->next) {
1015 if (!strcasecmp(v->name, "smdiport")) {
1017 ASTOBJ_UNREF(iface, ast_smdi_interface_destroy);
1019 if (!(iface = ASTOBJ_CONTAINER_FIND(&smdi_ifaces, v->value))) {
1020 ast_log(LOG_NOTICE, "SMDI interface %s not found\n", iface->name);
1023 } else if (!strcasecmp(v->name, "pollinginterval")) {
1024 if (sscanf(v->value, "%u", &mwi_monitor.polling_interval) != 1) {
1025 ast_log(LOG_ERROR, "Invalid value for pollinginterval: %s\n", v->value);
1026 mwi_monitor.polling_interval = DEFAULT_POLLING_INTERVAL;
1030 ast_log(LOG_ERROR, "Mailbox mapping ignored, no valid SMDI interface specified in mailboxes section\n");
1033 append_mailbox_mapping(v, iface);
1038 ASTOBJ_UNREF(iface, ast_smdi_interface_destroy);
1040 ast_config_destroy(conf);
1042 if (!AST_LIST_EMPTY(&mwi_monitor.mailbox_mappings) && mwi_monitor.thread == AST_PTHREADT_NULL
1043 && ast_pthread_create_background(&mwi_monitor.thread, NULL, mwi_monitor_handler, NULL)) {
1044 ast_log(LOG_ERROR, "Failed to start MWI monitoring thread. This module will not operate.\n");
1045 return AST_MODULE_LOAD_FAILURE;
1048 /* Prune any interfaces we should no longer monitor. */
1050 ASTOBJ_CONTAINER_PRUNE_MARKED(&smdi_ifaces, ast_smdi_interface_destroy);
1052 ASTOBJ_CONTAINER_RDLOCK(&smdi_ifaces);
1053 /* TODO: this is bad, we need an ASTOBJ method for this! */
1054 if (!smdi_ifaces.head)
1056 ASTOBJ_CONTAINER_UNLOCK(&smdi_ifaces);
1061 struct smdi_msg_datastore {
1063 struct ast_smdi_interface *iface;
1064 struct ast_smdi_md_message *md_msg;
1067 static void smdi_msg_datastore_destroy(void *data)
1069 struct smdi_msg_datastore *smd = data;
1072 ASTOBJ_UNREF(smd->iface, ast_smdi_interface_destroy);
1075 ASTOBJ_UNREF(smd->md_msg, ast_smdi_md_message_destroy);
1080 static const struct ast_datastore_info smdi_msg_datastore_info = {
1082 .destroy = smdi_msg_datastore_destroy,
1085 static int smdi_msg_id;
1087 /*! In milliseconds */
1088 #define SMDI_RETRIEVE_TIMEOUT_DEFAULT 3000
1090 AST_APP_OPTIONS(smdi_msg_ret_options, BEGIN_OPTIONS
1091 AST_APP_OPTION('t', OPT_SEARCH_TERMINAL),
1092 AST_APP_OPTION('n', OPT_SEARCH_NUMBER),
1095 static int smdi_msg_retrieve_read(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len)
1097 struct ast_module_user *u;
1098 AST_DECLARE_APP_ARGS(args,
1100 AST_APP_ARG(search_key);
1101 AST_APP_ARG(timeout);
1102 AST_APP_ARG(options);
1104 struct ast_flags options = { 0 };
1105 unsigned int timeout = SMDI_RETRIEVE_TIMEOUT_DEFAULT;
1108 struct smdi_msg_datastore *smd = NULL;
1109 struct ast_datastore *datastore = NULL;
1110 struct ast_smdi_interface *iface = NULL;
1111 struct ast_smdi_md_message *md_msg = NULL;
1113 u = ast_module_user_add(chan);
1115 if (ast_strlen_zero(data)) {
1116 ast_log(LOG_ERROR, "SMDI_MSG_RETRIEVE requires an argument\n");
1121 ast_log(LOG_ERROR, "SMDI_MSG_RETRIEVE must be used with a channel\n");
1125 ast_autoservice_start(chan);
1127 parse = ast_strdupa(data);
1128 AST_STANDARD_APP_ARGS(args, parse);
1130 if (ast_strlen_zero(args.port) || ast_strlen_zero(args.search_key)) {
1131 ast_log(LOG_ERROR, "Invalid arguments provided to SMDI_MSG_RETRIEVE\n");
1135 if (!(iface = ast_smdi_interface_find(args.port))) {
1136 ast_log(LOG_ERROR, "SMDI port '%s' not found\n", args.port);
1140 if (!ast_strlen_zero(args.options)) {
1141 ast_app_parse_options(smdi_msg_ret_options, &options, NULL, args.options);
1144 if (!ast_strlen_zero(args.timeout)) {
1145 if (sscanf(args.timeout, "%u", &timeout) != 1) {
1146 ast_log(LOG_ERROR, "'%s' is not a valid timeout\n", args.timeout);
1147 timeout = SMDI_RETRIEVE_TIMEOUT_DEFAULT;
1151 if (!(md_msg = smdi_message_wait(iface, timeout, SMDI_MD, args.search_key, options))) {
1152 ast_log(LOG_WARNING, "No SMDI message retrieved for search key '%s' after "
1153 "waiting %u ms.\n", args.search_key, timeout);
1157 if (!(smd = ast_calloc(1, sizeof(*smd))))
1160 smd->iface = ASTOBJ_REF(iface);
1161 smd->md_msg = ASTOBJ_REF(md_msg);
1162 smd->id = ast_atomic_fetchadd_int((int *) &smdi_msg_id, 1);
1163 snprintf(buf, len, "%u", smd->id);
1165 if (!(datastore = ast_channel_datastore_alloc(&smdi_msg_datastore_info, buf)))
1168 datastore->data = smd;
1170 ast_channel_lock(chan);
1171 ast_channel_datastore_add(chan, datastore);
1172 ast_channel_unlock(chan);
1178 ASTOBJ_UNREF(iface, ast_smdi_interface_destroy);
1181 ASTOBJ_UNREF(md_msg, ast_smdi_md_message_destroy);
1183 if (smd && !datastore)
1184 smdi_msg_datastore_destroy(smd);
1187 ast_autoservice_stop(chan);
1189 ast_module_user_remove(u);
1194 static int smdi_msg_read(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len)
1196 struct ast_module_user *u;
1198 AST_DECLARE_APP_ARGS(args,
1200 AST_APP_ARG(component);
1203 struct ast_datastore *datastore = NULL;
1204 struct smdi_msg_datastore *smd = NULL;
1206 u = ast_module_user_add(chan);
1209 ast_log(LOG_ERROR, "SMDI_MSG can not be called without a channel\n");
1213 if (ast_strlen_zero(data)) {
1214 ast_log(LOG_WARNING, "SMDI_MSG requires an argument\n");
1218 parse = ast_strdupa(data);
1219 AST_STANDARD_APP_ARGS(args, parse);
1221 if (ast_strlen_zero(args.id)) {
1222 ast_log(LOG_WARNING, "ID must be supplied to SMDI_MSG\n");
1226 if (ast_strlen_zero(args.component)) {
1227 ast_log(LOG_WARNING, "ID must be supplied to SMDI_MSG\n");
1231 ast_channel_lock(chan);
1232 datastore = ast_channel_datastore_find(chan, &smdi_msg_datastore_info, args.id);
1233 ast_channel_unlock(chan);
1236 ast_log(LOG_WARNING, "No SMDI message found for message ID '%s'\n", args.id);
1240 smd = datastore->data;
1242 if (!strcasecmp(args.component, "number")) {
1243 ast_copy_string(buf, smd->md_msg->mesg_desk_num, len);
1244 } else if (!strcasecmp(args.component, "terminal")) {
1245 ast_copy_string(buf, smd->md_msg->mesg_desk_term, len);
1246 } else if (!strcasecmp(args.component, "station")) {
1247 ast_copy_string(buf, smd->md_msg->fwd_st, len);
1248 } else if (!strcasecmp(args.component, "callerid")) {
1249 ast_copy_string(buf, smd->md_msg->calling_st, len);
1250 } else if (!strcasecmp(args.component, "type")) {
1251 snprintf(buf, len, "%c", smd->md_msg->type);
1253 ast_log(LOG_ERROR, "'%s' is not a valid message component for SMDI_MSG\n",
1261 ast_module_user_remove(u);
1266 static struct ast_custom_function smdi_msg_retrieve_function = {
1267 .name = "SMDI_MSG_RETRIEVE",
1268 .synopsis = "Retrieve an SMDI message.",
1269 .syntax = "SMDI_MSG_RETRIEVE(<smdi port>,<search key>[,timeout[,options]])",
1271 " This function is used to retrieve an incoming SMDI message. It returns\n"
1272 "an ID which can be used with the SMDI_MSG() function to access details of\n"
1273 "the message. Note that this is a destructive function in the sense that\n"
1274 "once an SMDI message is retrieved using this function, it is no longer in\n"
1275 "the global SMDI message queue, and can not be accessed by any other Asterisk\n"
1276 "channels. The timeout for this function is optional, and the default is\n"
1277 "3 seconds. When providing a timeout, it should be in milliseconds.\n"
1278 " The default search is done on the forwarding station ID. However, if\n"
1279 "you set one of the search key options in the options field, you can change\n"
1282 " t - Instead of searching on the forwarding station, search on the message\n"
1284 " n - Instead of searching on the forwarding station, search on the message\n"
1287 .read = smdi_msg_retrieve_read,
1290 static struct ast_custom_function smdi_msg_function = {
1292 .synopsis = "Retrieve details about an SMDI message.",
1293 .syntax = "SMDI_MSG(<message_id>,<component>)",
1295 " This function is used to access details of an SMDI message that was\n"
1296 "pulled from the incoming SMDI message queue using the SMDI_MSG_RETRIEVE()\n"
1298 " Valid message components are:\n"
1299 " number - The message desk number\n"
1300 " terminal - The message desk terminal\n"
1301 " station - The forwarding station\n"
1302 " callerid - The callerID of the calling party that was forwarded\n"
1303 " type - The call type. The value here is the exact character\n"
1304 " that came in on the SMDI link. Typically, example values\n"
1305 " are: D - Direct Calls, A - Forward All Calls,\n"
1306 " B - Forward Busy Calls, N - Forward No Answer Calls\n"
1308 .read = smdi_msg_read,
1311 static int load_module(void)
1315 /* initialize our containers */
1316 memset(&smdi_ifaces, 0, sizeof(smdi_ifaces));
1317 ASTOBJ_CONTAINER_INIT(&smdi_ifaces);
1319 ast_mutex_init(&mwi_monitor.lock);
1320 ast_cond_init(&mwi_monitor.cond, NULL);
1322 ast_custom_function_register(&smdi_msg_retrieve_function);
1323 ast_custom_function_register(&smdi_msg_function);
1325 /* load the config and start the listener threads*/
1329 } else if (res == 1) {
1330 ast_log(LOG_NOTICE, "No SMDI interfaces are available to listen on, not starting SMDI listener.\n");
1331 return AST_MODULE_LOAD_DECLINE;
1334 return AST_MODULE_LOAD_SUCCESS;
1337 static int unload_module(void)
1339 /* this destructor stops any running smdi_read threads */
1340 ASTOBJ_CONTAINER_DESTROYALL(&smdi_ifaces, ast_smdi_interface_destroy);
1341 ASTOBJ_CONTAINER_DESTROY(&smdi_ifaces);
1343 destroy_all_mailbox_mappings();
1345 ast_mutex_lock(&mwi_monitor.lock);
1346 mwi_monitor.stop = 1;
1347 ast_cond_signal(&mwi_monitor.cond);
1348 ast_mutex_unlock(&mwi_monitor.lock);
1350 if (mwi_monitor.thread != AST_PTHREADT_NULL) {
1351 pthread_join(mwi_monitor.thread, NULL);
1354 ast_custom_function_unregister(&smdi_msg_retrieve_function);
1355 ast_custom_function_unregister(&smdi_msg_function);
1360 static int reload(void)
1368 } else if (res == 1) {
1369 ast_log(LOG_WARNING, "No SMDI interfaces were specified to listen on, not starting SDMI listener.\n");
1375 AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_GLOBAL_SYMBOLS, "Simplified Message Desk Interface (SMDI) Resource",
1376 .load = load_module,
1377 .unload = unload_module,