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);
367 static void *smdi_msg_find(struct ast_smdi_interface *iface,
368 enum smdi_message_type type, const char *station)
372 purge_old_messages(iface, type);
376 msg = ASTOBJ_CONTAINER_FIND(&iface->md_q, station);
379 msg = ASTOBJ_CONTAINER_FIND(&iface->mwi_q, station);
386 static void *smdi_message_wait(struct ast_smdi_interface *iface, int timeout,
387 enum smdi_message_type type, const char *station)
389 struct timeval start;
393 while (diff < timeout) {
394 struct timespec ts = { 0, };
397 lock_msg_q(iface, type);
399 if ((msg = smdi_msg_find(iface, type, station))) {
400 unlock_msg_q(iface, type);
404 tv = ast_tvadd(start, ast_tv(0, timeout));
405 ts.tv_sec = tv.tv_sec;
406 ts.tv_nsec = tv.tv_usec * 1000;
408 /* If there were no messages in the queue, then go to sleep until one
411 ast_cond_timedwait(&iface->md_q_cond, &iface->md_q_lock, &ts);
413 if ((msg = smdi_msg_find(iface, type, station))) {
414 unlock_msg_q(iface, type);
418 unlock_msg_q(iface, type);
421 diff = ast_tvdiff_ms(ast_tvnow(), start);
427 struct ast_smdi_md_message *ast_smdi_md_message_pop(struct ast_smdi_interface *iface)
429 return smdi_msg_pop(iface, SMDI_MD);
432 struct ast_smdi_md_message *ast_smdi_md_message_wait(struct ast_smdi_interface *iface, int timeout)
434 return smdi_message_wait(iface, timeout, SMDI_MD, NULL);
437 struct ast_smdi_mwi_message *ast_smdi_mwi_message_pop(struct ast_smdi_interface *iface)
439 return smdi_msg_pop(iface, SMDI_MWI);
442 struct ast_smdi_mwi_message *ast_smdi_mwi_message_wait(struct ast_smdi_interface *iface, int timeout)
444 return smdi_message_wait(iface, timeout, SMDI_MWI, NULL);
447 struct ast_smdi_mwi_message *ast_smdi_mwi_message_wait_station(struct ast_smdi_interface *iface, int timeout,
450 return smdi_message_wait(iface, timeout, SMDI_MWI, station);
453 struct ast_smdi_interface *ast_smdi_interface_find(const char *iface_name)
455 return (ASTOBJ_CONTAINER_FIND(&smdi_ifaces, iface_name));
460 * \brief Read an SMDI message.
462 * \param iface_p the SMDI interface to read from.
464 * This function loops and reads from and SMDI interface. It must be stopped
465 * using pthread_cancel().
467 static void *smdi_read(void *iface_p)
469 struct ast_smdi_interface *iface = iface_p;
470 struct ast_smdi_md_message *md_msg;
471 struct ast_smdi_mwi_message *mwi_msg;
477 /* read an smdi message */
478 while ((c = fgetc(iface->file))) {
480 /* check if this is the start of a message */
483 ast_log(LOG_DEBUG, "Read an 'M' to start an SMDI message\n");
489 if (c == 'D') { /* MD message */
492 ast_log(LOG_DEBUG, "Read a 'D' ... it's an MD message.\n");
494 if (!(md_msg = ast_calloc(1, sizeof(*md_msg)))) {
495 ASTOBJ_UNREF(iface, ast_smdi_interface_destroy);
501 /* read the message desk number */
502 for (i = 0; i < sizeof(md_msg->mesg_desk_num) - 1; i++) {
503 md_msg->mesg_desk_num[i] = fgetc(iface->file);
504 ast_log(LOG_DEBUG, "Read a '%c'\n", md_msg->mesg_desk_num[i]);
507 md_msg->mesg_desk_num[sizeof(md_msg->mesg_desk_num) - 1] = '\0';
509 ast_log(LOG_DEBUG, "The message desk number is '%s'\n", md_msg->mesg_desk_num);
511 /* read the message desk terminal number */
512 for (i = 0; i < sizeof(md_msg->mesg_desk_term) - 1; i++) {
513 md_msg->mesg_desk_term[i] = fgetc(iface->file);
514 ast_log(LOG_DEBUG, "Read a '%c'\n", md_msg->mesg_desk_term[i]);
517 md_msg->mesg_desk_term[sizeof(md_msg->mesg_desk_term) - 1] = '\0';
519 ast_log(LOG_DEBUG, "The message desk terminal is '%s'\n", md_msg->mesg_desk_term);
521 /* read the message type */
522 md_msg->type = fgetc(iface->file);
524 ast_log(LOG_DEBUG, "Message type is '%c'\n", md_msg->type);
526 /* read the forwarding station number (may be blank) */
527 cp = &md_msg->fwd_st[0];
528 for (i = 0; i < sizeof(md_msg->fwd_st) - 1; i++) {
529 if ((c = fgetc(iface->file)) == ' ') {
531 ast_log(LOG_DEBUG, "Read a space, done looking for the forwarding station\n");
535 /* store c in md_msg->fwd_st */
536 if (i >= iface->msdstrip) {
537 ast_log(LOG_DEBUG, "Read a '%c' and stored it in the forwarding station buffer\n", c);
540 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);
544 /* make sure the value is null terminated, even if this truncates it */
545 md_msg->fwd_st[sizeof(md_msg->fwd_st) - 1] = '\0';
548 ast_log(LOG_DEBUG, "The forwarding station is '%s'\n", md_msg->fwd_st);
550 /* Put the fwd_st in the name field so that we can use ASTOBJ_FIND to look
551 * up a message on this field */
552 ast_copy_string(md_msg->name, md_msg->fwd_st, sizeof(md_msg->name));
554 /* read the calling station number (may be blank) */
555 cp = &md_msg->calling_st[0];
556 for (i = 0; i < sizeof(md_msg->calling_st) - 1; i++) {
557 if (!isdigit((c = fgetc(iface->file)))) {
559 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);
561 /* Don't break on a space. We may read the space before the calling station
562 * here if the forwarding station buffer filled up. */
563 i--; /* We're still on the same character */
569 /* store c in md_msg->calling_st */
570 if (i >= iface->msdstrip) {
571 ast_log(LOG_DEBUG, "Read a '%c' and stored it in the calling station buffer\n", c);
574 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);
578 /* make sure the value is null terminated, even if this truncates it */
579 md_msg->calling_st[sizeof(md_msg->calling_st) - 1] = '\0';
582 ast_log(LOG_DEBUG, "The calling station is '%s'\n", md_msg->calling_st);
584 /* add the message to the message queue */
585 md_msg->timestamp = ast_tvnow();
586 ast_smdi_md_message_push(iface, md_msg);
587 ast_log(LOG_DEBUG, "Recieved SMDI MD message on %s\n", iface->name);
589 ASTOBJ_UNREF(md_msg, ast_smdi_md_message_destroy);
591 } else if (c == 'W') { /* MWI message */
594 ast_log(LOG_DEBUG, "Read a 'W', it's an MWI message. (No more debug coming for MWI messages)\n");
596 if (!(mwi_msg = ast_calloc(1, sizeof(*mwi_msg)))) {
597 ASTOBJ_UNREF(iface,ast_smdi_interface_destroy);
601 ASTOBJ_INIT(mwi_msg);
603 /* discard the 'I' (from 'MWI') */
606 /* read the forwarding station number (may be blank) */
607 cp = &mwi_msg->fwd_st[0];
608 for (i = 0; i < sizeof(mwi_msg->fwd_st) - 1; i++) {
609 if ((c = fgetc(iface->file)) == ' ') {
614 /* store c in md_msg->fwd_st */
615 if (i >= iface->msdstrip)
619 /* make sure the station number is null terminated, even if this will truncate it */
620 mwi_msg->fwd_st[sizeof(mwi_msg->fwd_st) - 1] = '\0';
623 /* Put the fwd_st in the name field so that we can use ASTOBJ_FIND to look
624 * up a message on this field */
625 ast_copy_string(mwi_msg->name, mwi_msg->fwd_st, sizeof(mwi_msg->name));
627 /* read the mwi failure cause */
628 for (i = 0; i < sizeof(mwi_msg->cause) - 1; i++)
629 mwi_msg->cause[i] = fgetc(iface->file);
631 mwi_msg->cause[sizeof(mwi_msg->cause) - 1] = '\0';
633 /* add the message to the message queue */
634 mwi_msg->timestamp = ast_tvnow();
635 ast_smdi_mwi_message_push(iface, mwi_msg);
636 ast_log(LOG_DEBUG, "Recieved SMDI MWI message on %s\n", iface->name);
638 ASTOBJ_UNREF(mwi_msg, ast_smdi_mwi_message_destroy);
640 ast_log(LOG_ERROR, "Unknown SMDI message type recieved on %s (M%c).\n", iface->name, c);
645 ast_log(LOG_ERROR, "Error reading from SMDI interface %s, stopping listener thread\n", iface->name);
646 ASTOBJ_UNREF(iface,ast_smdi_interface_destroy);
650 void ast_smdi_md_message_destroy(struct ast_smdi_md_message *msg)
655 void ast_smdi_mwi_message_destroy(struct ast_smdi_mwi_message *msg)
660 static void destroy_mailbox_mapping(struct mailbox_mapping *mm)
662 ast_string_field_free_memory(mm);
663 ASTOBJ_UNREF(mm->iface, ast_smdi_interface_destroy);
667 static void destroy_all_mailbox_mappings(void)
669 struct mailbox_mapping *mm;
671 ast_mutex_lock(&mwi_monitor.lock);
672 while ((mm = AST_LIST_REMOVE_HEAD(&mwi_monitor.mailbox_mappings, entry)))
673 destroy_mailbox_mapping(mm);
674 ast_mutex_unlock(&mwi_monitor.lock);
677 static void append_mailbox_mapping(struct ast_variable *var, struct ast_smdi_interface *iface)
679 struct mailbox_mapping *mm;
680 char *mailbox, *context;
682 if (!(mm = ast_calloc(1, sizeof(*mm))))
685 if (ast_string_field_init(mm, 32)) {
690 ast_string_field_set(mm, smdi, var->name);
692 context = ast_strdupa(var->value);
693 mailbox = strsep(&context, "@");
694 if (ast_strlen_zero(context))
697 ast_string_field_set(mm, mailbox, mailbox);
698 ast_string_field_set(mm, context, context);
700 mm->iface = ASTOBJ_REF(iface);
702 ast_mutex_lock(&mwi_monitor.lock);
703 AST_LIST_INSERT_TAIL(&mwi_monitor.mailbox_mappings, mm, entry);
704 ast_mutex_unlock(&mwi_monitor.lock);
708 * \note Called with the mwi_monitor.lock locked
710 static void poll_mailbox(struct mailbox_mapping *mm)
715 snprintf(buf, sizeof(buf), "%s@%s", mm->mailbox, mm->context);
717 state = !!ast_app_has_voicemail(mm->mailbox, NULL);
719 if (state != mm->cur_state) {
721 ast_smdi_mwi_set(mm->iface, mm->smdi);
723 ast_smdi_mwi_unset(mm->iface, mm->smdi);
725 mm->cur_state = state;
729 static void *mwi_monitor_handler(void *data)
731 while (!mwi_monitor.stop) {
732 struct timespec ts = { 0, };
734 struct mailbox_mapping *mm;
736 ast_mutex_lock(&mwi_monitor.lock);
738 mwi_monitor.last_poll = ast_tvnow();
740 AST_LIST_TRAVERSE(&mwi_monitor.mailbox_mappings, mm, entry)
743 /* Sleep up to the configured polling interval. Allow unload_module()
744 * to signal us to wake up and exit. */
745 tv = ast_tvadd(mwi_monitor.last_poll, ast_tv(mwi_monitor.polling_interval, 0));
746 ts.tv_sec = tv.tv_sec;
747 ts.tv_nsec = tv.tv_usec * 1000;
748 ast_cond_timedwait(&mwi_monitor.cond, &mwi_monitor.lock, &ts);
750 ast_mutex_unlock(&mwi_monitor.lock);
756 static struct ast_smdi_interface *alloc_smdi_interface(void)
758 struct ast_smdi_interface *iface;
760 if (!(iface = ast_calloc(1, sizeof(*iface))))
764 ASTOBJ_CONTAINER_INIT(&iface->md_q);
765 ASTOBJ_CONTAINER_INIT(&iface->mwi_q);
767 ast_mutex_init(&iface->md_q_lock);
768 ast_cond_init(&iface->md_q_cond, NULL);
770 ast_mutex_init(&iface->mwi_q_lock);
771 ast_cond_init(&iface->mwi_q_cond, NULL);
778 * \brief Load and reload SMDI configuration.
779 * \param reload this should be 1 if we are reloading and 0 if not.
781 * This function loads/reloads the SMDI configuration and starts and stops
782 * interfaces accordingly.
784 * \return zero on success, -1 on failure, and 1 if no smdi interfaces were started.
786 static int smdi_load(int reload)
788 struct ast_config *conf;
789 struct ast_variable *v;
790 struct ast_smdi_interface *iface = NULL;
791 struct ast_flags config_flags = { reload ? CONFIG_FLAG_FILEUNCHANGED : 0 };
795 speed_t baud_rate = B9600; /* 9600 baud rate */
796 tcflag_t paritybit = PARENB; /* even parity checking */
797 tcflag_t charsize = CS7; /* seven bit characters */
798 int stopbits = 0; /* One stop bit */
800 int msdstrip = 0; /* strip zero digits */
801 long msg_expiry = SMDI_MSG_EXPIRY_TIME;
803 if (!(conf = ast_config_load(config_file, config_flags))) {
805 ast_log(LOG_NOTICE, "Unable to reload config %s: SMDI untouched\n", config_file);
807 ast_log(LOG_NOTICE, "Unable to load config %s: SMDI disabled\n", config_file);
809 } else if (conf == CONFIG_STATUS_FILEUNCHANGED)
812 /* Mark all interfaces that we are listening on. We will unmark them
813 * as we find them in the config file, this way we know any interfaces
814 * still marked after we have finished parsing the config file should
818 ASTOBJ_CONTAINER_MARKALL(&smdi_ifaces);
820 for (v = ast_variable_browse(conf, "interfaces"); v; v = v->next) {
821 if (!strcasecmp(v->name, "baudrate")) {
822 if (!strcasecmp(v->value, "9600"))
824 else if (!strcasecmp(v->value, "4800"))
826 else if (!strcasecmp(v->value, "2400"))
828 else if (!strcasecmp(v->value, "1200"))
831 ast_log(LOG_NOTICE, "Invalid baud rate '%s' specified in %s (line %d), using default\n", v->value, config_file, v->lineno);
834 } else if (!strcasecmp(v->name, "msdstrip")) {
835 if (!sscanf(v->value, "%d", &msdstrip)) {
836 ast_log(LOG_NOTICE, "Invalid msdstrip value in %s (line %d), using default\n", config_file, v->lineno);
838 } else if (0 > msdstrip || msdstrip > 9) {
839 ast_log(LOG_NOTICE, "Invalid msdstrip value in %s (line %d), using default\n", config_file, v->lineno);
842 } else if (!strcasecmp(v->name, "msgexpirytime")) {
843 if (!sscanf(v->value, "%ld", &msg_expiry)) {
844 ast_log(LOG_NOTICE, "Invalid msgexpirytime value in %s (line %d), using default\n", config_file, v->lineno);
845 msg_expiry = SMDI_MSG_EXPIRY_TIME;
847 } else if (!strcasecmp(v->name, "paritybit")) {
848 if (!strcasecmp(v->value, "even"))
850 else if (!strcasecmp(v->value, "odd"))
851 paritybit = PARENB | PARODD;
852 else if (!strcasecmp(v->value, "none"))
855 ast_log(LOG_NOTICE, "Invalid parity bit setting in %s (line %d), using default\n", config_file, v->lineno);
858 } else if (!strcasecmp(v->name, "charsize")) {
859 if (!strcasecmp(v->value, "7"))
861 else if (!strcasecmp(v->value, "8"))
864 ast_log(LOG_NOTICE, "Invalid character size setting in %s (line %d), using default\n", config_file, v->lineno);
867 } else if (!strcasecmp(v->name, "twostopbits")) {
868 stopbits = ast_true(v->name);
869 } else if (!strcasecmp(v->name, "smdiport")) {
871 /* we are reloading, check if we are already
872 * monitoring this interface, if we are we do
873 * not want to start it again. This also has
874 * the side effect of not updating different
875 * setting for the serial port, but it should
876 * be trivial to rewrite this section so that
877 * options on the port are changed without
878 * restarting the interface. Or the interface
879 * could be restarted with out emptying the
881 if ((iface = ASTOBJ_CONTAINER_FIND(&smdi_ifaces, v->value))) {
882 ast_log(LOG_NOTICE, "SMDI interface %s already running, not restarting\n", iface->name);
883 ASTOBJ_UNMARK(iface);
884 ASTOBJ_UNREF(iface, ast_smdi_interface_destroy);
889 if (!(iface = alloc_smdi_interface()))
892 ast_copy_string(iface->name, v->value, sizeof(iface->name));
894 iface->thread = AST_PTHREADT_NULL;
896 if (!(iface->file = fopen(iface->name, "r"))) {
897 ast_log(LOG_ERROR, "Error opening SMDI interface %s (%s)\n", iface->name, strerror(errno));
898 ASTOBJ_UNREF(iface, ast_smdi_interface_destroy);
902 iface->fd = fileno(iface->file);
904 /* Set the proper attributes for our serial port. */
906 /* get the current attributes from the port */
907 if (tcgetattr(iface->fd, &iface->mode)) {
908 ast_log(LOG_ERROR, "Error getting atributes of %s (%s)\n", iface->name, strerror(errno));
909 ASTOBJ_UNREF(iface, ast_smdi_interface_destroy);
913 /* set the desired speed */
914 if (cfsetispeed(&iface->mode, baud_rate) || cfsetospeed(&iface->mode, baud_rate)) {
915 ast_log(LOG_ERROR, "Error setting baud rate on %s (%s)\n", iface->name, strerror(errno));
916 ASTOBJ_UNREF(iface, ast_smdi_interface_destroy);
920 /* set the stop bits */
922 iface->mode.c_cflag = iface->mode.c_cflag | CSTOPB; /* set two stop bits */
924 iface->mode.c_cflag = iface->mode.c_cflag & ~CSTOPB; /* set one stop bit */
927 iface->mode.c_cflag = (iface->mode.c_cflag & ~PARENB & ~PARODD) | paritybit;
929 /* set the character size */
930 iface->mode.c_cflag = (iface->mode.c_cflag & ~CSIZE) | charsize;
932 /* commit the desired attributes */
933 if (tcsetattr(iface->fd, TCSAFLUSH, &iface->mode)) {
934 ast_log(LOG_ERROR, "Error setting attributes on %s (%s)\n", iface->name, strerror(errno));
935 ASTOBJ_UNREF(iface, ast_smdi_interface_destroy);
939 /* set the msdstrip */
940 iface->msdstrip = msdstrip;
942 /* set the message expiry time */
943 iface->msg_expiry = msg_expiry;
945 /* start the listener thread */
946 ast_verb(3, "Starting SMDI monitor thread for %s\n", iface->name);
947 if (ast_pthread_create_background(&iface->thread, NULL, smdi_read, iface)) {
948 ast_log(LOG_ERROR, "Error starting SMDI monitor thread for %s\n", iface->name);
949 ASTOBJ_UNREF(iface, ast_smdi_interface_destroy);
953 ASTOBJ_CONTAINER_LINK(&smdi_ifaces, iface);
954 ASTOBJ_UNREF(iface, ast_smdi_interface_destroy);
955 ast_module_ref(ast_module_info->self);
957 ast_log(LOG_NOTICE, "Ignoring unknown option %s in %s\n", v->name, config_file);
961 destroy_all_mailbox_mappings();
962 mwi_monitor.polling_interval = DEFAULT_POLLING_INTERVAL;
966 for (v = ast_variable_browse(conf, "mailboxes"); v; v = v->next) {
967 if (!strcasecmp(v->name, "smdiport")) {
969 ASTOBJ_UNREF(iface, ast_smdi_interface_destroy);
971 if (!(iface = ASTOBJ_CONTAINER_FIND(&smdi_ifaces, v->value))) {
972 ast_log(LOG_NOTICE, "SMDI interface %s not found\n", iface->name);
975 } else if (!strcasecmp(v->name, "pollinginterval")) {
976 if (sscanf(v->value, "%u", &mwi_monitor.polling_interval) != 1) {
977 ast_log(LOG_ERROR, "Invalid value for pollinginterval: %s\n", v->value);
978 mwi_monitor.polling_interval = DEFAULT_POLLING_INTERVAL;
982 ast_log(LOG_ERROR, "Mailbox mapping ignored, no valid SMDI interface specified in mailboxes section\n");
985 append_mailbox_mapping(v, iface);
990 ASTOBJ_UNREF(iface, ast_smdi_interface_destroy);
992 ast_config_destroy(conf);
994 if (!AST_LIST_EMPTY(&mwi_monitor.mailbox_mappings) && mwi_monitor.thread == AST_PTHREADT_NULL
995 && ast_pthread_create_background(&mwi_monitor.thread, NULL, mwi_monitor_handler, NULL)) {
996 ast_log(LOG_ERROR, "Failed to start MWI monitoring thread. This module will not operate.\n");
997 return AST_MODULE_LOAD_FAILURE;
1000 /* Prune any interfaces we should no longer monitor. */
1002 ASTOBJ_CONTAINER_PRUNE_MARKED(&smdi_ifaces, ast_smdi_interface_destroy);
1004 ASTOBJ_CONTAINER_RDLOCK(&smdi_ifaces);
1005 /* TODO: this is bad, we need an ASTOBJ method for this! */
1006 if (!smdi_ifaces.head)
1008 ASTOBJ_CONTAINER_UNLOCK(&smdi_ifaces);
1013 struct smdi_msg_datastore {
1015 struct ast_smdi_interface *iface;
1016 struct ast_smdi_md_message *md_msg;
1019 static void smdi_msg_datastore_destroy(void *data)
1021 struct smdi_msg_datastore *smd = data;
1024 ASTOBJ_UNREF(smd->iface, ast_smdi_interface_destroy);
1027 ASTOBJ_UNREF(smd->md_msg, ast_smdi_md_message_destroy);
1032 static const struct ast_datastore_info smdi_msg_datastore_info = {
1034 .destroy = smdi_msg_datastore_destroy,
1037 static int smdi_msg_id;
1039 /*! In milliseconds */
1040 #define SMDI_RETRIEVE_TIMEOUT_DEFAULT 3000
1042 static int smdi_msg_retrieve_read(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len)
1044 struct ast_module_user *u;
1045 AST_DECLARE_APP_ARGS(args,
1047 AST_APP_ARG(station);
1048 AST_APP_ARG(timeout);
1050 unsigned int timeout = SMDI_RETRIEVE_TIMEOUT_DEFAULT;
1053 struct smdi_msg_datastore *smd = NULL;
1054 struct ast_datastore *datastore = NULL;
1055 struct ast_smdi_interface *iface = NULL;
1056 struct ast_smdi_md_message *md_msg = NULL;
1058 u = ast_module_user_add(chan);
1060 if (ast_strlen_zero(data)) {
1061 ast_log(LOG_ERROR, "SMDI_MSG_RETRIEVE requires an argument\n");
1066 ast_log(LOG_ERROR, "SMDI_MSG_RETRIEVE must be used with a channel\n");
1070 ast_autoservice_start(chan);
1072 parse = ast_strdupa(data);
1073 AST_STANDARD_APP_ARGS(args, parse);
1075 if (ast_strlen_zero(args.port) || ast_strlen_zero(args.station)) {
1076 ast_log(LOG_ERROR, "Invalid arguments provided to SMDI_MSG_RETRIEVE\n");
1080 if (!(iface = ast_smdi_interface_find(args.port))) {
1081 ast_log(LOG_ERROR, "SMDI port '%s' not found\n", args.port);
1085 if (!ast_strlen_zero(args.timeout)) {
1086 if (sscanf(args.timeout, "%u", &timeout) != 1) {
1087 ast_log(LOG_ERROR, "'%s' is not a valid timeout\n", args.timeout);
1088 timeout = SMDI_RETRIEVE_TIMEOUT_DEFAULT;
1092 if (!(md_msg = smdi_message_wait(iface, timeout, SMDI_MD, args.station))) {
1093 ast_log(LOG_WARNING, "No SMDI message retrieved for station '%s' after "
1094 "waiting %u ms.\n", args.station, timeout);
1098 if (!(smd = ast_calloc(1, sizeof(*smd))))
1101 smd->iface = ASTOBJ_REF(iface);
1102 smd->md_msg = ASTOBJ_REF(md_msg);
1103 smd->id = ast_atomic_fetchadd_int((int *) &smdi_msg_id, 1);
1104 snprintf(buf, len, "%u", smd->id);
1106 if (!(datastore = ast_channel_datastore_alloc(&smdi_msg_datastore_info, buf)))
1109 datastore->data = smd;
1111 ast_channel_lock(chan);
1112 ast_channel_datastore_add(chan, datastore);
1113 ast_channel_unlock(chan);
1119 ASTOBJ_UNREF(iface, ast_smdi_interface_destroy);
1122 ASTOBJ_UNREF(md_msg, ast_smdi_md_message_destroy);
1124 if (smd && !datastore)
1125 smdi_msg_datastore_destroy(smd);
1128 ast_autoservice_stop(chan);
1130 ast_module_user_remove(u);
1135 static int smdi_msg_read(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len)
1137 struct ast_module_user *u;
1139 AST_DECLARE_APP_ARGS(args,
1141 AST_APP_ARG(component);
1144 struct ast_datastore *datastore = NULL;
1145 struct smdi_msg_datastore *smd = NULL;
1147 u = ast_module_user_add(chan);
1150 ast_log(LOG_ERROR, "SMDI_MSG can not be called without a channel\n");
1154 if (ast_strlen_zero(data)) {
1155 ast_log(LOG_WARNING, "SMDI_MSG requires an argument\n");
1159 parse = ast_strdupa(data);
1160 AST_STANDARD_APP_ARGS(args, parse);
1162 if (ast_strlen_zero(args.id)) {
1163 ast_log(LOG_WARNING, "ID must be supplied to SMDI_MSG\n");
1167 if (ast_strlen_zero(args.component)) {
1168 ast_log(LOG_WARNING, "ID must be supplied to SMDI_MSG\n");
1172 ast_channel_lock(chan);
1173 datastore = ast_channel_datastore_find(chan, &smdi_msg_datastore_info, args.id);
1174 ast_channel_unlock(chan);
1177 ast_log(LOG_WARNING, "No SMDI message found for message ID '%s'\n", args.id);
1181 smd = datastore->data;
1183 if (!strcasecmp(args.component, "station")) {
1184 ast_copy_string(buf, smd->md_msg->fwd_st, len);
1185 } else if (!strcasecmp(args.component, "callerid")) {
1186 ast_copy_string(buf, smd->md_msg->calling_st, len);
1187 } else if (!strcasecmp(args.component, "type")) {
1188 snprintf(buf, len, "%c", smd->md_msg->type);
1190 ast_log(LOG_ERROR, "'%s' is not a valid message component for SMDI_MSG\n",
1198 ast_module_user_remove(u);
1203 static struct ast_custom_function smdi_msg_retrieve_function = {
1204 .name = "SMDI_MSG_RETRIEVE",
1205 .synopsis = "Retrieve an SMDI message.",
1206 .syntax = "SMDI_MSG_RETRIEVE(<smdi port>,<station>[,timeout])",
1208 " This function is used to retrieve an incoming SMDI message. It returns\n"
1209 "an ID which can be used with the SMDI_MSG() function to access details of\n"
1210 "the message. Note that this is a destructive function in the sense that\n"
1211 "once an SMDI message is retrieved using this function, it is no longer in\n"
1212 "the global SMDI message queue, and can not be accessed by any other Asterisk\n"
1213 "channels. The timeout for this function is optional, and the default is\n"
1214 "3 seconds. When providing a timeout, it should be in milliseconds.\n"
1216 .read = smdi_msg_retrieve_read,
1219 static struct ast_custom_function smdi_msg_function = {
1221 .synopsis = "Retrieve details about an SMDI message.",
1222 .syntax = "SMDI_MSG(<message_id>,<component>)",
1224 " This function is used to access details of an SMDI message that was\n"
1225 "pulled from the incoming SMDI message queue using the SMDI_MSG_RETRIEVE()\n"
1227 " Valid message components are:\n"
1228 " station - The forwarding station\n"
1229 " callerid - The callerID of the calling party that was forwarded\n"
1230 " type - The call type. The value here is the exact character\n"
1231 " that came in on the SMDI link. Typically, example values\n"
1232 " are: D - Direct Calls, A - Forward All Calls,\n"
1233 " B - Forward Busy Calls, N - Forward No Answer Calls\n"
1235 .read = smdi_msg_read,
1238 static int load_module(void)
1242 /* initialize our containers */
1243 memset(&smdi_ifaces, 0, sizeof(smdi_ifaces));
1244 ASTOBJ_CONTAINER_INIT(&smdi_ifaces);
1246 ast_mutex_init(&mwi_monitor.lock);
1247 ast_cond_init(&mwi_monitor.cond, NULL);
1249 ast_custom_function_register(&smdi_msg_retrieve_function);
1250 ast_custom_function_register(&smdi_msg_function);
1252 /* load the config and start the listener threads*/
1256 } else if (res == 1) {
1257 ast_log(LOG_NOTICE, "No SMDI interfaces are available to listen on, not starting SMDI listener.\n");
1258 return AST_MODULE_LOAD_DECLINE;
1261 return AST_MODULE_LOAD_SUCCESS;
1264 static int unload_module(void)
1266 /* this destructor stops any running smdi_read threads */
1267 ASTOBJ_CONTAINER_DESTROYALL(&smdi_ifaces, ast_smdi_interface_destroy);
1268 ASTOBJ_CONTAINER_DESTROY(&smdi_ifaces);
1270 destroy_all_mailbox_mappings();
1272 ast_mutex_lock(&mwi_monitor.lock);
1273 mwi_monitor.stop = 1;
1274 ast_cond_signal(&mwi_monitor.cond);
1275 ast_mutex_unlock(&mwi_monitor.lock);
1277 pthread_join(mwi_monitor.thread, NULL);
1279 ast_custom_function_unregister(&smdi_msg_retrieve_function);
1280 ast_custom_function_unregister(&smdi_msg_function);
1285 static int reload(void)
1293 } else if (res == 1) {
1294 ast_log(LOG_WARNING, "No SMDI interfaces were specified to listen on, not starting SDMI listener.\n");
1300 AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_GLOBAL_SYMBOLS, "Simplified Message Desk Interface (SMDI) Resource",
1301 .load = load_module,
1302 .unload = unload_module,