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
32 ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
39 #include "asterisk/module.h"
40 #include "asterisk/lock.h"
41 #include "asterisk/utils.h"
42 #include "asterisk/smdi.h"
43 #include "asterisk/config.h"
44 #include "asterisk/astobj.h"
45 #include "asterisk/io.h"
46 #include "asterisk/stringfields.h"
47 #include "asterisk/linkedlists.h"
48 #include "asterisk/app.h"
49 #include "asterisk/pbx.h"
50 #include "asterisk/channel.h"
52 /* Message expiry time in milliseconds */
53 #define SMDI_MSG_EXPIRY_TIME 30000 /* 30 seconds */
55 static const char config_file[] = "smdi.conf";
57 /*! \brief SMDI message desk message queue. */
58 struct ast_smdi_md_queue {
59 ASTOBJ_CONTAINER_COMPONENTS(struct ast_smdi_md_message);
62 /*! \brief SMDI message waiting indicator message queue. */
63 struct ast_smdi_mwi_queue {
64 ASTOBJ_CONTAINER_COMPONENTS(struct ast_smdi_mwi_message);
67 struct ast_smdi_interface {
68 ASTOBJ_COMPONENTS_FULL(struct ast_smdi_interface, SMDI_MAX_FILENAME_LEN, 1);
69 struct ast_smdi_md_queue md_q;
70 ast_mutex_t md_q_lock;
72 struct ast_smdi_mwi_queue mwi_q;
73 ast_mutex_t mwi_q_lock;
74 ast_cond_t mwi_q_cond;
83 /*! \brief SMDI interface container. */
84 struct ast_smdi_interface_container {
85 ASTOBJ_CONTAINER_COMPONENTS(struct ast_smdi_interface);
88 /*! \brief A mapping between an SMDI mailbox ID and an Asterisk mailbox */
89 struct mailbox_mapping {
90 /*! This is the current state of the mailbox. It is simply on or
91 * off to indicate if there are messages waiting or not. */
92 unsigned int cur_state:1;
93 /*! A Pointer to the appropriate SMDI interface */
94 struct ast_smdi_interface *iface;
95 AST_DECLARE_STRING_FIELDS(
96 /*! The Name of the mailbox for the SMDI link. */
97 AST_STRING_FIELD(smdi);
98 /*! The name of the mailbox on the Asterisk side */
99 AST_STRING_FIELD(mailbox);
100 /*! The name of the voicemail context in use */
101 AST_STRING_FIELD(context);
103 AST_LIST_ENTRY(mailbox_mapping) entry;
107 #define DEFAULT_POLLING_INTERVAL 10
109 /*! \brief Data that gets used by the SMDI MWI monitoring thread */
115 /*! A list of mailboxes that need to be monitored */
116 AST_LIST_HEAD_NOLOCK(, mailbox_mapping) mailbox_mappings;
117 /*! Polling Interval for checking mailbox status */
118 unsigned int polling_interval;
119 /*! Set to 1 to tell the polling thread to stop */
121 /*! The time that the last poll began */
122 struct timeval last_poll;
124 .thread = AST_PTHREADT_NULL,
127 static void ast_smdi_interface_destroy(struct ast_smdi_interface *iface)
129 if (iface->thread != AST_PTHREADT_NULL && iface->thread != AST_PTHREADT_STOP) {
130 pthread_cancel(iface->thread);
131 pthread_join(iface->thread, NULL);
134 iface->thread = AST_PTHREADT_STOP;
139 ASTOBJ_CONTAINER_DESTROYALL(&iface->md_q, ast_smdi_md_message_destroy);
140 ASTOBJ_CONTAINER_DESTROYALL(&iface->mwi_q, ast_smdi_mwi_message_destroy);
141 ASTOBJ_CONTAINER_DESTROY(&iface->md_q);
142 ASTOBJ_CONTAINER_DESTROY(&iface->mwi_q);
144 ast_mutex_destroy(&iface->md_q_lock);
145 ast_cond_destroy(&iface->md_q_cond);
147 ast_mutex_destroy(&iface->mwi_q_lock);
148 ast_cond_destroy(&iface->mwi_q_cond);
152 ast_module_unref(ast_module_info->self);
155 void ast_smdi_interface_unref(struct ast_smdi_interface *iface)
157 ASTOBJ_UNREF(iface, ast_smdi_interface_destroy);
162 * \brief Push an SMDI message to the back of an interface's message queue.
163 * \param iface a pointer to the interface to use.
164 * \param md_msg a pointer to the message to use.
166 static void ast_smdi_md_message_push(struct ast_smdi_interface *iface, struct ast_smdi_md_message *md_msg)
168 ast_mutex_lock(&iface->md_q_lock);
169 ASTOBJ_CONTAINER_LINK_END(&iface->md_q, md_msg);
170 ast_cond_broadcast(&iface->md_q_cond);
171 ast_mutex_unlock(&iface->md_q_lock);
176 * \brief Push an SMDI message to the back of an interface's message queue.
177 * \param iface a pointer to the interface to use.
178 * \param mwi_msg a pointer to the message to use.
180 static void ast_smdi_mwi_message_push(struct ast_smdi_interface *iface, struct ast_smdi_mwi_message *mwi_msg)
182 ast_mutex_lock(&iface->mwi_q_lock);
183 ASTOBJ_CONTAINER_LINK_END(&iface->mwi_q, mwi_msg);
184 ast_cond_broadcast(&iface->mwi_q_cond);
185 ast_mutex_unlock(&iface->mwi_q_lock);
188 static int smdi_toggle_mwi(struct ast_smdi_interface *iface, const char *mailbox, int on)
193 if (!(file = fopen(iface->name, "w"))) {
194 ast_log(LOG_ERROR, "Error opening SMDI interface %s (%s) for writing\n", iface->name, strerror(errno));
198 ASTOBJ_WRLOCK(iface);
200 fprintf(file, "%s:MWI ", on ? "OP" : "RMV");
202 for (i = 0; i < iface->msdstrip; i++)
205 fprintf(file, "%s!\x04", mailbox);
209 ASTOBJ_UNLOCK(iface);
210 ast_debug(1, "Sent MWI set message for %s on %s\n", mailbox, iface->name);
215 int ast_smdi_mwi_set(struct ast_smdi_interface *iface, const char *mailbox)
217 return smdi_toggle_mwi(iface, mailbox, 1);
220 int ast_smdi_mwi_unset(struct ast_smdi_interface *iface, const char *mailbox)
222 return smdi_toggle_mwi(iface, mailbox, 0);
225 void ast_smdi_md_message_putback(struct ast_smdi_interface *iface, struct ast_smdi_md_message *md_msg)
227 ast_mutex_lock(&iface->md_q_lock);
228 ASTOBJ_CONTAINER_LINK_START(&iface->md_q, md_msg);
229 ast_cond_broadcast(&iface->md_q_cond);
230 ast_mutex_unlock(&iface->md_q_lock);
233 void ast_smdi_mwi_message_putback(struct ast_smdi_interface *iface, struct ast_smdi_mwi_message *mwi_msg)
235 ast_mutex_lock(&iface->mwi_q_lock);
236 ASTOBJ_CONTAINER_LINK_START(&iface->mwi_q, mwi_msg);
237 ast_cond_broadcast(&iface->mwi_q_cond);
238 ast_mutex_unlock(&iface->mwi_q_lock);
241 enum smdi_message_type {
246 static inline int lock_msg_q(struct ast_smdi_interface *iface, enum smdi_message_type type)
250 return ast_mutex_lock(&iface->mwi_q_lock);
252 return ast_mutex_lock(&iface->md_q_lock);
258 static inline int unlock_msg_q(struct ast_smdi_interface *iface, enum smdi_message_type type)
262 return ast_mutex_unlock(&iface->mwi_q_lock);
264 return ast_mutex_unlock(&iface->md_q_lock);
270 static inline void *unlink_from_msg_q(struct ast_smdi_interface *iface, enum smdi_message_type type)
274 return ASTOBJ_CONTAINER_UNLINK_START(&iface->mwi_q);
276 return ASTOBJ_CONTAINER_UNLINK_START(&iface->md_q);
281 static inline struct timeval msg_timestamp(void *msg, enum smdi_message_type type)
283 struct ast_smdi_md_message *md_msg = msg;
284 struct ast_smdi_mwi_message *mwi_msg = msg;
288 return mwi_msg->timestamp;
290 return md_msg->timestamp;
296 static inline void unref_msg(void *msg, enum smdi_message_type type)
298 struct ast_smdi_md_message *md_msg = msg;
299 struct ast_smdi_mwi_message *mwi_msg = msg;
303 ASTOBJ_UNREF(mwi_msg, ast_smdi_mwi_message_destroy);
305 ASTOBJ_UNREF(md_msg, ast_smdi_md_message_destroy);
309 static void purge_old_messages(struct ast_smdi_interface *iface, enum smdi_message_type type)
311 struct timeval now = ast_tvnow();
315 lock_msg_q(iface, type);
316 msg = unlink_from_msg_q(iface, type);
317 unlock_msg_q(iface, type);
319 /* purge old messages */
321 elapsed = ast_tvdiff_ms(now, msg_timestamp(msg, type));
323 if (elapsed > iface->msg_expiry) {
324 /* found an expired message */
325 unref_msg(msg, type);
326 ast_log(LOG_NOTICE, "Purged expired message from %s SMDI %s message queue. "
327 "Message was %ld milliseconds too old.\n",
328 iface->name, (type == SMDI_MD) ? "MD" : "MWI",
329 elapsed - iface->msg_expiry);
331 lock_msg_q(iface, type);
332 msg = unlink_from_msg_q(iface, type);
333 unlock_msg_q(iface, type);
335 /* good message, put it back and return */
338 ast_smdi_md_message_push(iface, msg);
341 ast_smdi_mwi_message_push(iface, msg);
344 unref_msg(msg, type);
350 static void *smdi_msg_pop(struct ast_smdi_interface *iface, enum smdi_message_type type)
354 purge_old_messages(iface, type);
356 lock_msg_q(iface, type);
357 msg = unlink_from_msg_q(iface, type);
358 unlock_msg_q(iface, type);
363 static void *smdi_msg_find(struct ast_smdi_interface *iface,
364 enum smdi_message_type type, const char *station)
368 purge_old_messages(iface, type);
372 msg = ASTOBJ_CONTAINER_FIND(&iface->md_q, station);
375 msg = ASTOBJ_CONTAINER_FIND(&iface->mwi_q, station);
382 static void *smdi_message_wait(struct ast_smdi_interface *iface, int timeout,
383 enum smdi_message_type type, const char *station)
385 struct timeval start;
389 while (diff < timeout) {
390 struct timespec ts = { 0, };
393 lock_msg_q(iface, type);
395 if ((msg = smdi_msg_find(iface, type, station))) {
396 unlock_msg_q(iface, type);
400 tv = ast_tvadd(start, ast_tv(0, timeout));
401 ts.tv_sec = tv.tv_sec;
402 ts.tv_nsec = tv.tv_usec * 1000;
404 /* If there were no messages in the queue, then go to sleep until one
407 ast_cond_timedwait(&iface->md_q_cond, &iface->md_q_lock, &ts);
409 if ((msg = smdi_msg_find(iface, type, station))) {
410 unlock_msg_q(iface, type);
414 unlock_msg_q(iface, type);
417 diff = ast_tvdiff_ms(ast_tvnow(), start);
423 struct ast_smdi_md_message *ast_smdi_md_message_pop(struct ast_smdi_interface *iface)
425 return smdi_msg_pop(iface, SMDI_MD);
428 struct ast_smdi_md_message *ast_smdi_md_message_wait(struct ast_smdi_interface *iface, int timeout)
430 return smdi_message_wait(iface, timeout, SMDI_MD, NULL);
433 struct ast_smdi_mwi_message *ast_smdi_mwi_message_pop(struct ast_smdi_interface *iface)
435 return smdi_msg_pop(iface, SMDI_MWI);
438 struct ast_smdi_mwi_message *ast_smdi_mwi_message_wait(struct ast_smdi_interface *iface, int timeout)
440 return smdi_message_wait(iface, timeout, SMDI_MWI, NULL);
443 struct ast_smdi_mwi_message *ast_smdi_mwi_message_wait_station(struct ast_smdi_interface *iface, int timeout,
446 return smdi_message_wait(iface, timeout, SMDI_MWI, station);
449 struct ast_smdi_interface *ast_smdi_interface_find(const char *iface_name)
451 return (ASTOBJ_CONTAINER_FIND(&smdi_ifaces, iface_name));
456 * \brief Read an SMDI message.
458 * \param iface_p the SMDI interface to read from.
460 * This function loops and reads from and SMDI interface. It must be stopped
461 * using pthread_cancel().
463 static void *smdi_read(void *iface_p)
465 struct ast_smdi_interface *iface = iface_p;
466 struct ast_smdi_md_message *md_msg;
467 struct ast_smdi_mwi_message *mwi_msg;
473 /* read an smdi message */
474 while ((c = fgetc(iface->file))) {
476 /* check if this is the start of a message */
479 ast_log(LOG_DEBUG, "Read an 'M' to start an SMDI message\n");
485 if (c == 'D') { /* MD message */
488 ast_log(LOG_DEBUG, "Read a 'D' ... it's an MD message.\n");
490 if (!(md_msg = ast_calloc(1, sizeof(*md_msg)))) {
491 ASTOBJ_UNREF(iface, ast_smdi_interface_destroy);
497 /* read the message desk number */
498 for (i = 0; i < sizeof(md_msg->mesg_desk_num) - 1; i++) {
499 md_msg->mesg_desk_num[i] = fgetc(iface->file);
500 ast_log(LOG_DEBUG, "Read a '%c'\n", md_msg->mesg_desk_num[i]);
503 md_msg->mesg_desk_num[sizeof(md_msg->mesg_desk_num) - 1] = '\0';
505 ast_log(LOG_DEBUG, "The message desk number is '%s'\n", md_msg->mesg_desk_num);
507 /* read the message desk terminal number */
508 for (i = 0; i < sizeof(md_msg->mesg_desk_term) - 1; i++) {
509 md_msg->mesg_desk_term[i] = fgetc(iface->file);
510 ast_log(LOG_DEBUG, "Read a '%c'\n", md_msg->mesg_desk_term[i]);
513 md_msg->mesg_desk_term[sizeof(md_msg->mesg_desk_term) - 1] = '\0';
515 ast_log(LOG_DEBUG, "The message desk terminal is '%s'\n", md_msg->mesg_desk_term);
517 /* read the message type */
518 md_msg->type = fgetc(iface->file);
520 ast_log(LOG_DEBUG, "Message type is '%c'\n", md_msg->type);
522 /* read the forwarding station number (may be blank) */
523 cp = &md_msg->fwd_st[0];
524 for (i = 0; i < sizeof(md_msg->fwd_st) - 1; i++) {
525 if ((c = fgetc(iface->file)) == ' ') {
527 ast_log(LOG_DEBUG, "Read a space, done looking for the forwarding station\n");
531 /* store c in md_msg->fwd_st */
532 if (i >= iface->msdstrip) {
533 ast_log(LOG_DEBUG, "Read a '%c' and stored it in the forwarding station buffer\n", c);
536 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);
540 /* make sure the value is null terminated, even if this truncates it */
541 md_msg->fwd_st[sizeof(md_msg->fwd_st) - 1] = '\0';
544 ast_log(LOG_DEBUG, "The forwarding station is '%s'\n", md_msg->fwd_st);
546 /* Put the fwd_st in the name field so that we can use ASTOBJ_FIND to look
547 * up a message on this field */
548 ast_copy_string(md_msg->name, md_msg->fwd_st, sizeof(md_msg->name));
550 /* read the calling station number (may be blank) */
551 cp = &md_msg->calling_st[0];
552 for (i = 0; i < sizeof(md_msg->calling_st) - 1; i++) {
553 if (!isdigit((c = fgetc(iface->file)))) {
555 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);
557 /* Don't break on a space. We may read the space before the calling station
558 * here if the forwarding station buffer filled up. */
559 i--; /* We're still on the same character */
565 /* store c in md_msg->calling_st */
566 if (i >= iface->msdstrip) {
567 ast_log(LOG_DEBUG, "Read a '%c' and stored it in the calling station buffer\n", c);
570 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);
574 /* make sure the value is null terminated, even if this truncates it */
575 md_msg->calling_st[sizeof(md_msg->calling_st) - 1] = '\0';
578 ast_log(LOG_DEBUG, "The calling station is '%s'\n", md_msg->calling_st);
580 /* add the message to the message queue */
581 md_msg->timestamp = ast_tvnow();
582 ast_smdi_md_message_push(iface, md_msg);
583 ast_log(LOG_DEBUG, "Recieved SMDI MD message on %s\n", iface->name);
585 ASTOBJ_UNREF(md_msg, ast_smdi_md_message_destroy);
587 } else if (c == 'W') { /* MWI message */
590 ast_log(LOG_DEBUG, "Read a 'W', it's an MWI message. (No more debug coming for MWI messages)\n");
592 if (!(mwi_msg = ast_calloc(1, sizeof(*mwi_msg)))) {
593 ASTOBJ_UNREF(iface,ast_smdi_interface_destroy);
597 ASTOBJ_INIT(mwi_msg);
599 /* discard the 'I' (from 'MWI') */
602 /* read the forwarding station number (may be blank) */
603 cp = &mwi_msg->fwd_st[0];
604 for (i = 0; i < sizeof(mwi_msg->fwd_st) - 1; i++) {
605 if ((c = fgetc(iface->file)) == ' ') {
610 /* store c in md_msg->fwd_st */
611 if (i >= iface->msdstrip)
615 /* make sure the station number is null terminated, even if this will truncate it */
616 mwi_msg->fwd_st[sizeof(mwi_msg->fwd_st) - 1] = '\0';
619 /* Put the fwd_st in the name field so that we can use ASTOBJ_FIND to look
620 * up a message on this field */
621 ast_copy_string(mwi_msg->name, mwi_msg->fwd_st, sizeof(mwi_msg->name));
623 /* read the mwi failure cause */
624 for (i = 0; i < sizeof(mwi_msg->cause) - 1; i++)
625 mwi_msg->cause[i] = fgetc(iface->file);
627 mwi_msg->cause[sizeof(mwi_msg->cause) - 1] = '\0';
629 /* add the message to the message queue */
630 mwi_msg->timestamp = ast_tvnow();
631 ast_smdi_mwi_message_push(iface, mwi_msg);
632 ast_log(LOG_DEBUG, "Recieved SMDI MWI message on %s\n", iface->name);
634 ASTOBJ_UNREF(mwi_msg, ast_smdi_mwi_message_destroy);
636 ast_log(LOG_ERROR, "Unknown SMDI message type recieved on %s (M%c).\n", iface->name, c);
641 ast_log(LOG_ERROR, "Error reading from SMDI interface %s, stopping listener thread\n", iface->name);
642 ASTOBJ_UNREF(iface,ast_smdi_interface_destroy);
646 void ast_smdi_md_message_destroy(struct ast_smdi_md_message *msg)
651 void ast_smdi_mwi_message_destroy(struct ast_smdi_mwi_message *msg)
656 static void destroy_mailbox_mapping(struct mailbox_mapping *mm)
658 ast_string_field_free_memory(mm);
659 ASTOBJ_UNREF(mm->iface, ast_smdi_interface_destroy);
663 static void destroy_all_mailbox_mappings(void)
665 struct mailbox_mapping *mm;
667 ast_mutex_lock(&mwi_monitor.lock);
668 while ((mm = AST_LIST_REMOVE_HEAD(&mwi_monitor.mailbox_mappings, entry)))
669 destroy_mailbox_mapping(mm);
670 ast_mutex_unlock(&mwi_monitor.lock);
673 static void append_mailbox_mapping(struct ast_variable *var, struct ast_smdi_interface *iface)
675 struct mailbox_mapping *mm;
676 char *mailbox, *context;
678 if (!(mm = ast_calloc(1, sizeof(*mm))))
681 if (ast_string_field_init(mm, 32)) {
686 ast_string_field_set(mm, smdi, var->name);
688 context = ast_strdupa(var->value);
689 mailbox = strsep(&context, "@");
690 if (ast_strlen_zero(context))
693 ast_string_field_set(mm, mailbox, mailbox);
694 ast_string_field_set(mm, context, context);
696 mm->iface = ASTOBJ_REF(iface);
698 ast_mutex_lock(&mwi_monitor.lock);
699 AST_LIST_INSERT_TAIL(&mwi_monitor.mailbox_mappings, mm, entry);
700 ast_mutex_unlock(&mwi_monitor.lock);
704 * \note Called with the mwi_monitor.lock locked
706 static void poll_mailbox(struct mailbox_mapping *mm)
711 snprintf(buf, sizeof(buf), "%s@%s", mm->mailbox, mm->context);
713 state = !!ast_app_has_voicemail(mm->mailbox, NULL);
715 if (state != mm->cur_state) {
717 ast_smdi_mwi_set(mm->iface, mm->smdi);
719 ast_smdi_mwi_unset(mm->iface, mm->smdi);
721 mm->cur_state = state;
725 static void *mwi_monitor_handler(void *data)
727 while (!mwi_monitor.stop) {
728 struct timespec ts = { 0, };
730 struct mailbox_mapping *mm;
732 ast_mutex_lock(&mwi_monitor.lock);
734 mwi_monitor.last_poll = ast_tvnow();
736 AST_LIST_TRAVERSE(&mwi_monitor.mailbox_mappings, mm, entry)
739 /* Sleep up to the configured polling interval. Allow unload_module()
740 * to signal us to wake up and exit. */
741 tv = ast_tvadd(mwi_monitor.last_poll, ast_tv(mwi_monitor.polling_interval, 0));
742 ts.tv_sec = tv.tv_sec;
743 ts.tv_nsec = tv.tv_usec * 1000;
744 ast_cond_timedwait(&mwi_monitor.cond, &mwi_monitor.lock, &ts);
746 ast_mutex_unlock(&mwi_monitor.lock);
752 static struct ast_smdi_interface *alloc_smdi_interface(void)
754 struct ast_smdi_interface *iface;
756 if (!(iface = ast_calloc(1, sizeof(*iface))))
760 ASTOBJ_CONTAINER_INIT(&iface->md_q);
761 ASTOBJ_CONTAINER_INIT(&iface->mwi_q);
763 ast_mutex_init(&iface->md_q_lock);
764 ast_cond_init(&iface->md_q_cond, NULL);
766 ast_mutex_init(&iface->mwi_q_lock);
767 ast_cond_init(&iface->mwi_q_cond, NULL);
774 * \brief Load and reload SMDI configuration.
775 * \param reload this should be 1 if we are reloading and 0 if not.
777 * This function loads/reloads the SMDI configuration and starts and stops
778 * interfaces accordingly.
780 * \return zero on success, -1 on failure, and 1 if no smdi interfaces were started.
782 static int smdi_load(int reload)
784 struct ast_config *conf;
785 struct ast_variable *v;
786 struct ast_smdi_interface *iface = NULL;
787 struct ast_flags config_flags = { reload ? CONFIG_FLAG_FILEUNCHANGED : 0 };
791 speed_t baud_rate = B9600; /* 9600 baud rate */
792 tcflag_t paritybit = PARENB; /* even parity checking */
793 tcflag_t charsize = CS7; /* seven bit characters */
794 int stopbits = 0; /* One stop bit */
796 int msdstrip = 0; /* strip zero digits */
797 long msg_expiry = SMDI_MSG_EXPIRY_TIME;
799 if (!(conf = ast_config_load(config_file, config_flags))) {
801 ast_log(LOG_NOTICE, "Unable to reload config %s: SMDI untouched\n", config_file);
803 ast_log(LOG_NOTICE, "Unable to load config %s: SMDI disabled\n", config_file);
805 } else if (conf == CONFIG_STATUS_FILEUNCHANGED)
808 /* Mark all interfaces that we are listening on. We will unmark them
809 * as we find them in the config file, this way we know any interfaces
810 * still marked after we have finished parsing the config file should
814 ASTOBJ_CONTAINER_MARKALL(&smdi_ifaces);
816 for (v = ast_variable_browse(conf, "interfaces"); v; v = v->next) {
817 if (!strcasecmp(v->name, "baudrate")) {
818 if (!strcasecmp(v->value, "9600"))
820 else if (!strcasecmp(v->value, "4800"))
822 else if (!strcasecmp(v->value, "2400"))
824 else if (!strcasecmp(v->value, "1200"))
827 ast_log(LOG_NOTICE, "Invalid baud rate '%s' specified in %s (line %d), using default\n", v->value, config_file, v->lineno);
830 } else if (!strcasecmp(v->name, "msdstrip")) {
831 if (!sscanf(v->value, "%d", &msdstrip)) {
832 ast_log(LOG_NOTICE, "Invalid msdstrip value in %s (line %d), using default\n", config_file, v->lineno);
834 } else if (0 > msdstrip || msdstrip > 9) {
835 ast_log(LOG_NOTICE, "Invalid msdstrip value in %s (line %d), using default\n", config_file, v->lineno);
838 } else if (!strcasecmp(v->name, "msgexpirytime")) {
839 if (!sscanf(v->value, "%ld", &msg_expiry)) {
840 ast_log(LOG_NOTICE, "Invalid msgexpirytime value in %s (line %d), using default\n", config_file, v->lineno);
841 msg_expiry = SMDI_MSG_EXPIRY_TIME;
843 } else if (!strcasecmp(v->name, "paritybit")) {
844 if (!strcasecmp(v->value, "even"))
846 else if (!strcasecmp(v->value, "odd"))
847 paritybit = PARENB | PARODD;
848 else if (!strcasecmp(v->value, "none"))
851 ast_log(LOG_NOTICE, "Invalid parity bit setting in %s (line %d), using default\n", config_file, v->lineno);
854 } else if (!strcasecmp(v->name, "charsize")) {
855 if (!strcasecmp(v->value, "7"))
857 else if (!strcasecmp(v->value, "8"))
860 ast_log(LOG_NOTICE, "Invalid character size setting in %s (line %d), using default\n", config_file, v->lineno);
863 } else if (!strcasecmp(v->name, "twostopbits")) {
864 stopbits = ast_true(v->name);
865 } else if (!strcasecmp(v->name, "smdiport")) {
867 /* we are reloading, check if we are already
868 * monitoring this interface, if we are we do
869 * not want to start it again. This also has
870 * the side effect of not updating different
871 * setting for the serial port, but it should
872 * be trivial to rewrite this section so that
873 * options on the port are changed without
874 * restarting the interface. Or the interface
875 * could be restarted with out emptying the
877 if ((iface = ASTOBJ_CONTAINER_FIND(&smdi_ifaces, v->value))) {
878 ast_log(LOG_NOTICE, "SMDI interface %s already running, not restarting\n", iface->name);
879 ASTOBJ_UNMARK(iface);
880 ASTOBJ_UNREF(iface, ast_smdi_interface_destroy);
885 if (!(iface = alloc_smdi_interface()))
888 ast_copy_string(iface->name, v->value, sizeof(iface->name));
890 iface->thread = AST_PTHREADT_NULL;
892 if (!(iface->file = fopen(iface->name, "r"))) {
893 ast_log(LOG_ERROR, "Error opening SMDI interface %s (%s)\n", iface->name, strerror(errno));
894 ASTOBJ_UNREF(iface, ast_smdi_interface_destroy);
898 iface->fd = fileno(iface->file);
900 /* Set the proper attributes for our serial port. */
902 /* get the current attributes from the port */
903 if (tcgetattr(iface->fd, &iface->mode)) {
904 ast_log(LOG_ERROR, "Error getting atributes of %s (%s)\n", iface->name, strerror(errno));
905 ASTOBJ_UNREF(iface, ast_smdi_interface_destroy);
909 /* set the desired speed */
910 if (cfsetispeed(&iface->mode, baud_rate) || cfsetospeed(&iface->mode, baud_rate)) {
911 ast_log(LOG_ERROR, "Error setting baud rate on %s (%s)\n", iface->name, strerror(errno));
912 ASTOBJ_UNREF(iface, ast_smdi_interface_destroy);
916 /* set the stop bits */
918 iface->mode.c_cflag = iface->mode.c_cflag | CSTOPB; /* set two stop bits */
920 iface->mode.c_cflag = iface->mode.c_cflag & ~CSTOPB; /* set one stop bit */
923 iface->mode.c_cflag = (iface->mode.c_cflag & ~PARENB & ~PARODD) | paritybit;
925 /* set the character size */
926 iface->mode.c_cflag = (iface->mode.c_cflag & ~CSIZE) | charsize;
928 /* commit the desired attributes */
929 if (tcsetattr(iface->fd, TCSAFLUSH, &iface->mode)) {
930 ast_log(LOG_ERROR, "Error setting attributes on %s (%s)\n", iface->name, strerror(errno));
931 ASTOBJ_UNREF(iface, ast_smdi_interface_destroy);
935 /* set the msdstrip */
936 iface->msdstrip = msdstrip;
938 /* set the message expiry time */
939 iface->msg_expiry = msg_expiry;
941 /* start the listener thread */
942 ast_verb(3, "Starting SMDI monitor thread for %s\n", iface->name);
943 if (ast_pthread_create_background(&iface->thread, NULL, smdi_read, iface)) {
944 ast_log(LOG_ERROR, "Error starting SMDI monitor thread for %s\n", iface->name);
945 ASTOBJ_UNREF(iface, ast_smdi_interface_destroy);
949 ASTOBJ_CONTAINER_LINK(&smdi_ifaces, iface);
950 ASTOBJ_UNREF(iface, ast_smdi_interface_destroy);
951 ast_module_ref(ast_module_info->self);
953 ast_log(LOG_NOTICE, "Ignoring unknown option %s in %s\n", v->name, config_file);
957 destroy_all_mailbox_mappings();
958 mwi_monitor.polling_interval = DEFAULT_POLLING_INTERVAL;
962 for (v = ast_variable_browse(conf, "mailboxes"); v; v = v->next) {
963 if (!strcasecmp(v->name, "smdiport")) {
965 ASTOBJ_UNREF(iface, ast_smdi_interface_destroy);
967 if (!(iface = ASTOBJ_CONTAINER_FIND(&smdi_ifaces, v->value))) {
968 ast_log(LOG_NOTICE, "SMDI interface %s not found\n", iface->name);
971 } else if (!strcasecmp(v->name, "pollinginterval")) {
972 if (sscanf(v->value, "%u", &mwi_monitor.polling_interval) != 1) {
973 ast_log(LOG_ERROR, "Invalid value for pollinginterval: %s\n", v->value);
974 mwi_monitor.polling_interval = DEFAULT_POLLING_INTERVAL;
978 ast_log(LOG_ERROR, "Mailbox mapping ignored, no valid SMDI interface specified in mailboxes section\n");
981 append_mailbox_mapping(v, iface);
986 ASTOBJ_UNREF(iface, ast_smdi_interface_destroy);
988 ast_config_destroy(conf);
990 if (!AST_LIST_EMPTY(&mwi_monitor.mailbox_mappings) && mwi_monitor.thread == AST_PTHREADT_NULL
991 && ast_pthread_create_background(&mwi_monitor.thread, NULL, mwi_monitor_handler, NULL)) {
992 ast_log(LOG_ERROR, "Failed to start MWI monitoring thread. This module will not operate.\n");
993 return AST_MODULE_LOAD_FAILURE;
996 /* Prune any interfaces we should no longer monitor. */
998 ASTOBJ_CONTAINER_PRUNE_MARKED(&smdi_ifaces, ast_smdi_interface_destroy);
1000 ASTOBJ_CONTAINER_RDLOCK(&smdi_ifaces);
1001 /* TODO: this is bad, we need an ASTOBJ method for this! */
1002 if (!smdi_ifaces.head)
1004 ASTOBJ_CONTAINER_UNLOCK(&smdi_ifaces);
1009 struct smdi_msg_datastore {
1011 struct ast_smdi_interface *iface;
1012 struct ast_smdi_md_message *md_msg;
1015 static void smdi_msg_datastore_destroy(void *data)
1017 struct smdi_msg_datastore *smd = data;
1020 ASTOBJ_UNREF(smd->iface, ast_smdi_interface_destroy);
1023 ASTOBJ_UNREF(smd->md_msg, ast_smdi_md_message_destroy);
1028 static const struct ast_datastore_info smdi_msg_datastore_info = {
1030 .destroy = smdi_msg_datastore_destroy,
1033 static int smdi_msg_id;
1035 /*! In milliseconds */
1036 #define SMDI_RETRIEVE_TIMEOUT_DEFAULT 3000
1038 static int smdi_msg_retrieve_read(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len)
1040 struct ast_module_user *u;
1041 AST_DECLARE_APP_ARGS(args,
1043 AST_APP_ARG(station);
1044 AST_APP_ARG(timeout);
1046 unsigned int timeout = SMDI_RETRIEVE_TIMEOUT_DEFAULT;
1049 struct smdi_msg_datastore *smd = NULL;
1050 struct ast_datastore *datastore = NULL;
1051 struct ast_smdi_interface *iface = NULL;
1052 struct ast_smdi_md_message *md_msg = NULL;
1054 u = ast_module_user_add(chan);
1056 if (ast_strlen_zero(data)) {
1057 ast_log(LOG_ERROR, "SMDI_MSG_RETRIEVE requires an argument\n");
1062 ast_log(LOG_ERROR, "SMDI_MSG_RETRIEVE must be used with a channel\n");
1066 ast_autoservice_start(chan);
1068 parse = ast_strdupa(data);
1069 AST_STANDARD_APP_ARGS(args, parse);
1071 if (ast_strlen_zero(args.port) || ast_strlen_zero(args.station)) {
1072 ast_log(LOG_ERROR, "Invalid arguments provided to SMDI_MSG_RETRIEVE\n");
1076 if (!(iface = ast_smdi_interface_find(args.port))) {
1077 ast_log(LOG_ERROR, "SMDI port '%s' not found\n", args.port);
1081 if (!ast_strlen_zero(args.timeout)) {
1082 if (sscanf(args.timeout, "%u", &timeout) != 1) {
1083 ast_log(LOG_ERROR, "'%s' is not a valid timeout\n", args.timeout);
1084 timeout = SMDI_RETRIEVE_TIMEOUT_DEFAULT;
1088 if (!(md_msg = smdi_message_wait(iface, timeout, SMDI_MD, args.station))) {
1089 ast_log(LOG_WARNING, "No SMDI message retrieved for station '%s' after "
1090 "waiting %u ms.\n", args.station, timeout);
1094 if (!(smd = ast_calloc(1, sizeof(*smd))))
1097 smd->iface = ASTOBJ_REF(iface);
1098 smd->md_msg = ASTOBJ_REF(md_msg);
1099 smd->id = ast_atomic_fetchadd_int((int *) &smdi_msg_id, 1);
1100 snprintf(buf, len, "%u", smd->id);
1102 if (!(datastore = ast_channel_datastore_alloc(&smdi_msg_datastore_info, buf)))
1105 datastore->data = smd;
1107 ast_channel_lock(chan);
1108 ast_channel_datastore_add(chan, datastore);
1109 ast_channel_unlock(chan);
1115 ASTOBJ_UNREF(iface, ast_smdi_interface_destroy);
1118 ASTOBJ_UNREF(md_msg, ast_smdi_md_message_destroy);
1120 if (smd && !datastore)
1121 smdi_msg_datastore_destroy(smd);
1124 ast_autoservice_stop(chan);
1126 ast_module_user_remove(u);
1131 static int smdi_msg_read(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len)
1133 struct ast_module_user *u;
1135 AST_DECLARE_APP_ARGS(args,
1137 AST_APP_ARG(component);
1140 struct ast_datastore *datastore = NULL;
1141 struct smdi_msg_datastore *smd = NULL;
1143 u = ast_module_user_add(chan);
1146 ast_log(LOG_ERROR, "SMDI_MSG can not be called without a channel\n");
1150 if (ast_strlen_zero(data)) {
1151 ast_log(LOG_WARNING, "SMDI_MSG requires an argument\n");
1155 parse = ast_strdupa(data);
1156 AST_STANDARD_APP_ARGS(args, parse);
1158 if (ast_strlen_zero(args.id)) {
1159 ast_log(LOG_WARNING, "ID must be supplied to SMDI_MSG\n");
1163 if (ast_strlen_zero(args.component)) {
1164 ast_log(LOG_WARNING, "ID must be supplied to SMDI_MSG\n");
1168 ast_channel_lock(chan);
1169 datastore = ast_channel_datastore_find(chan, &smdi_msg_datastore_info, args.id);
1170 ast_channel_unlock(chan);
1173 ast_log(LOG_WARNING, "No SMDI message found for message ID '%s'\n", args.id);
1177 smd = datastore->data;
1179 if (!strcasecmp(args.component, "station")) {
1180 ast_copy_string(buf, smd->md_msg->fwd_st, len);
1181 } else if (!strcasecmp(args.component, "callerid")) {
1182 ast_copy_string(buf, smd->md_msg->calling_st, len);
1183 } else if (!strcasecmp(args.component, "type")) {
1184 snprintf(buf, len, "%c", smd->md_msg->type);
1186 ast_log(LOG_ERROR, "'%s' is not a valid message component for SMDI_MSG\n",
1194 ast_module_user_remove(u);
1199 static struct ast_custom_function smdi_msg_retrieve_function = {
1200 .name = "SMDI_MSG_RETRIEVE",
1201 .synopsis = "Retrieve an SMDI message.",
1202 .syntax = "SMDI_MSG_RETRIEVE(<smdi port>,<station>[,timeout])",
1204 " This function is used to retrieve an incoming SMDI message. It returns\n"
1205 "an ID which can be used with the SMDI_MSG() function to access details of\n"
1206 "the message. Note that this is a destructive function in the sense that\n"
1207 "once an SMDI message is retrieved using this function, it is no longer in\n"
1208 "the global SMDI message queue, and can not be accessed by any other Asterisk\n"
1209 "channels. The timeout for this function is optional, and the default is\n"
1210 "3 seconds. When providing a timeout, it should be in milliseconds.\n"
1212 .read = smdi_msg_retrieve_read,
1215 static struct ast_custom_function smdi_msg_function = {
1217 .synopsis = "Retrieve details about an SMDI message.",
1218 .syntax = "SMDI_MSG(<message_id>,<component>)",
1220 " This function is used to access details of an SMDI message that was\n"
1221 "pulled from the incoming SMDI message queue using the SMDI_MSG_RETRIEVE()\n"
1223 " Valid message components are:\n"
1224 " station - The forwarding station\n"
1225 " callerid - The callerID of the calling party that was forwarded\n"
1226 " type - The call type. The value here is the exact character\n"
1227 " that came in on the SMDI link. Typically, example values\n"
1228 " are: D - Direct Calls, A - Forward All Calls,\n"
1229 " B - Forward Busy Calls, N - Forward No Answer Calls\n"
1231 .read = smdi_msg_read,
1234 static int load_module(void)
1238 /* initialize our containers */
1239 memset(&smdi_ifaces, 0, sizeof(smdi_ifaces));
1240 ASTOBJ_CONTAINER_INIT(&smdi_ifaces);
1242 ast_mutex_init(&mwi_monitor.lock);
1243 ast_cond_init(&mwi_monitor.cond, NULL);
1245 ast_custom_function_register(&smdi_msg_retrieve_function);
1246 ast_custom_function_register(&smdi_msg_function);
1248 /* load the config and start the listener threads*/
1252 } else if (res == 1) {
1253 ast_log(LOG_NOTICE, "No SMDI interfaces are available to listen on, not starting SMDI listener.\n");
1254 return AST_MODULE_LOAD_DECLINE;
1257 return AST_MODULE_LOAD_SUCCESS;
1260 static int unload_module(void)
1262 /* this destructor stops any running smdi_read threads */
1263 ASTOBJ_CONTAINER_DESTROYALL(&smdi_ifaces, ast_smdi_interface_destroy);
1264 ASTOBJ_CONTAINER_DESTROY(&smdi_ifaces);
1266 destroy_all_mailbox_mappings();
1268 ast_mutex_lock(&mwi_monitor.lock);
1269 mwi_monitor.stop = 1;
1270 ast_cond_signal(&mwi_monitor.cond);
1271 ast_mutex_unlock(&mwi_monitor.lock);
1273 pthread_join(mwi_monitor.thread, NULL);
1275 ast_custom_function_unregister(&smdi_msg_retrieve_function);
1276 ast_custom_function_unregister(&smdi_msg_function);
1281 static int reload(void)
1289 } else if (res == 1) {
1290 ast_log(LOG_WARNING, "No SMDI interfaces were specified to listen on, not starting SDMI listener.\n");
1296 AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_GLOBAL_SYMBOLS, "Simplified Message Desk Interface (SMDI) Resource",
1297 .load = load_module,
1298 .unload = unload_module,