add note that the user will need to enable codec_ilbc to get it to build
[asterisk-bristuff.git] / res / res_smdi.c
blob9315cf973241968ba2686e087200a9741c6e0409
1 /*
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.
20 /*!
21 * \file
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
30 #include "asterisk.h"
32 ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
34 #include <stdio.h>
35 #include <stdlib.h>
36 #include <errno.h>
37 #include <termios.h>
38 #include <sys/time.h>
39 #include <time.h>
40 #include <ctype.h>
42 #include "asterisk/module.h"
43 #include "asterisk/lock.h"
44 #include "asterisk/utils.h"
45 #include "asterisk/smdi.h"
46 #include "asterisk/config.h"
47 #include "asterisk/astobj.h"
48 #include "asterisk/io.h"
49 #include "asterisk/logger.h"
50 #include "asterisk/utils.h"
51 #include "asterisk/options.h"
52 #include "asterisk/stringfields.h"
53 #include "asterisk/linkedlists.h"
54 #include "asterisk/app.h"
55 #include "asterisk/pbx.h"
57 /* Message expiry time in milliseconds */
58 #define SMDI_MSG_EXPIRY_TIME 30000 /* 30 seconds */
60 static const char config_file[] = "smdi.conf";
62 /*! \brief SMDI message desk message queue. */
63 struct ast_smdi_md_queue {
64 ASTOBJ_CONTAINER_COMPONENTS(struct ast_smdi_md_message);
67 /*! \brief SMDI message waiting indicator message queue. */
68 struct ast_smdi_mwi_queue {
69 ASTOBJ_CONTAINER_COMPONENTS(struct ast_smdi_mwi_message);
72 struct ast_smdi_interface {
73 ASTOBJ_COMPONENTS_FULL(struct ast_smdi_interface, SMDI_MAX_FILENAME_LEN, 1);
74 struct ast_smdi_md_queue md_q;
75 ast_mutex_t md_q_lock;
76 ast_cond_t md_q_cond;
77 struct ast_smdi_mwi_queue mwi_q;
78 ast_mutex_t mwi_q_lock;
79 ast_cond_t mwi_q_cond;
80 FILE *file;
81 int fd;
82 pthread_t thread;
83 struct termios mode;
84 int msdstrip;
85 long msg_expiry;
88 /*! \brief SMDI interface container. */
89 struct ast_smdi_interface_container {
90 ASTOBJ_CONTAINER_COMPONENTS(struct ast_smdi_interface);
91 } smdi_ifaces;
93 /*! \brief A mapping between an SMDI mailbox ID and an Asterisk mailbox */
94 struct mailbox_mapping {
95 /*! This is the current state of the mailbox. It is simply on or
96 * off to indicate if there are messages waiting or not. */
97 unsigned int cur_state:1;
98 /*! A Pointer to the appropriate SMDI interface */
99 struct ast_smdi_interface *iface;
100 AST_DECLARE_STRING_FIELDS(
101 /*! The Name of the mailbox for the SMDI link. */
102 AST_STRING_FIELD(smdi);
103 /*! The name of the mailbox on the Asterisk side */
104 AST_STRING_FIELD(mailbox);
105 /*! The name of the voicemail context in use */
106 AST_STRING_FIELD(context);
108 AST_LIST_ENTRY(mailbox_mapping) entry;
111 /*! 10 seconds */
112 #define DEFAULT_POLLING_INTERVAL 10
114 /*! \brief Data that gets used by the SMDI MWI monitoring thread */
115 static struct {
116 /*! The thread ID */
117 pthread_t thread;
118 ast_mutex_t lock;
119 ast_cond_t cond;
120 /*! A list of mailboxes that need to be monitored */
121 AST_LIST_HEAD_NOLOCK(, mailbox_mapping) mailbox_mappings;
122 /*! Polling Interval for checking mailbox status */
123 unsigned int polling_interval;
124 /*! Set to 1 to tell the polling thread to stop */
125 unsigned int stop:1;
126 /*! The time that the last poll began */
127 struct timeval last_poll;
128 } mwi_monitor = {
129 .thread = AST_PTHREADT_NULL,
132 static void ast_smdi_interface_destroy(struct ast_smdi_interface *iface)
134 if (iface->thread != AST_PTHREADT_NULL && iface->thread != AST_PTHREADT_STOP) {
135 pthread_cancel(iface->thread);
136 pthread_join(iface->thread, NULL);
139 iface->thread = AST_PTHREADT_STOP;
141 if (iface->file)
142 fclose(iface->file);
144 ASTOBJ_CONTAINER_DESTROYALL(&iface->md_q, ast_smdi_md_message_destroy);
145 ASTOBJ_CONTAINER_DESTROYALL(&iface->mwi_q, ast_smdi_mwi_message_destroy);
146 ASTOBJ_CONTAINER_DESTROY(&iface->md_q);
147 ASTOBJ_CONTAINER_DESTROY(&iface->mwi_q);
149 ast_mutex_destroy(&iface->md_q_lock);
150 ast_cond_destroy(&iface->md_q_cond);
152 ast_mutex_destroy(&iface->mwi_q_lock);
153 ast_cond_destroy(&iface->mwi_q_cond);
155 free(iface);
157 ast_module_unref(ast_module_info->self);
160 void ast_smdi_interface_unref(struct ast_smdi_interface *iface)
162 ASTOBJ_UNREF(iface, ast_smdi_interface_destroy);
165 /*!
166 * \internal
167 * \brief Push an SMDI message to the back of an interface's message queue.
168 * \param iface a pointer to the interface to use.
169 * \param md_msg a pointer to the message to use.
171 static void ast_smdi_md_message_push(struct ast_smdi_interface *iface, struct ast_smdi_md_message *md_msg)
173 ast_mutex_lock(&iface->md_q_lock);
174 ASTOBJ_CONTAINER_LINK_END(&iface->md_q, md_msg);
175 ast_cond_broadcast(&iface->md_q_cond);
176 ast_mutex_unlock(&iface->md_q_lock);
180 * \internal
181 * \brief Push an SMDI message to the back of an interface's message queue.
182 * \param iface a pointer to the interface to use.
183 * \param mwi_msg a pointer to the message to use.
185 static void ast_smdi_mwi_message_push(struct ast_smdi_interface *iface, struct ast_smdi_mwi_message *mwi_msg)
187 ast_mutex_lock(&iface->mwi_q_lock);
188 ASTOBJ_CONTAINER_LINK_END(&iface->mwi_q, mwi_msg);
189 ast_cond_broadcast(&iface->mwi_q_cond);
190 ast_mutex_unlock(&iface->mwi_q_lock);
193 static int smdi_toggle_mwi(struct ast_smdi_interface *iface, const char *mailbox, int on)
195 FILE *file;
196 int i;
198 if (!(file = fopen(iface->name, "w"))) {
199 ast_log(LOG_ERROR, "Error opening SMDI interface %s (%s) for writing\n", iface->name, strerror(errno));
200 return 1;
203 ASTOBJ_WRLOCK(iface);
205 fprintf(file, "%s:MWI ", on ? "OP" : "RMV");
207 for (i = 0; i < iface->msdstrip; i++)
208 fprintf(file, "0");
210 fprintf(file, "%s!\x04", mailbox);
212 fclose(file);
214 ASTOBJ_UNLOCK(iface);
216 ast_log(LOG_DEBUG, "Sent MWI %s message for %s on %s\n", on ? "set" : "unset",
217 mailbox, iface->name);
219 return 0;
222 int ast_smdi_mwi_set(struct ast_smdi_interface *iface, const char *mailbox)
224 return smdi_toggle_mwi(iface, mailbox, 1);
227 int ast_smdi_mwi_unset(struct ast_smdi_interface *iface, const char *mailbox)
229 return smdi_toggle_mwi(iface, mailbox, 0);
232 void ast_smdi_md_message_putback(struct ast_smdi_interface *iface, struct ast_smdi_md_message *md_msg)
234 ast_mutex_lock(&iface->md_q_lock);
235 ASTOBJ_CONTAINER_LINK_START(&iface->md_q, md_msg);
236 ast_cond_broadcast(&iface->md_q_cond);
237 ast_mutex_unlock(&iface->md_q_lock);
240 void ast_smdi_mwi_message_putback(struct ast_smdi_interface *iface, struct ast_smdi_mwi_message *mwi_msg)
242 ast_mutex_lock(&iface->mwi_q_lock);
243 ASTOBJ_CONTAINER_LINK_START(&iface->mwi_q, mwi_msg);
244 ast_cond_broadcast(&iface->mwi_q_cond);
245 ast_mutex_unlock(&iface->mwi_q_lock);
248 enum smdi_message_type {
249 SMDI_MWI,
250 SMDI_MD,
253 static inline int lock_msg_q(struct ast_smdi_interface *iface, enum smdi_message_type type)
255 switch (type) {
256 case SMDI_MWI:
257 return ast_mutex_lock(&iface->mwi_q_lock);
258 case SMDI_MD:
259 return ast_mutex_lock(&iface->md_q_lock);
262 return -1;
265 static inline int unlock_msg_q(struct ast_smdi_interface *iface, enum smdi_message_type type)
267 switch (type) {
268 case SMDI_MWI:
269 return ast_mutex_unlock(&iface->mwi_q_lock);
270 case SMDI_MD:
271 return ast_mutex_unlock(&iface->md_q_lock);
274 return -1;
277 static inline void *unlink_from_msg_q(struct ast_smdi_interface *iface, enum smdi_message_type type)
279 switch (type) {
280 case SMDI_MWI:
281 return ASTOBJ_CONTAINER_UNLINK_START(&iface->mwi_q);
282 case SMDI_MD:
283 return ASTOBJ_CONTAINER_UNLINK_START(&iface->md_q);
286 return NULL;
289 static inline struct timeval msg_timestamp(void *msg, enum smdi_message_type type)
291 struct ast_smdi_md_message *md_msg = msg;
292 struct ast_smdi_mwi_message *mwi_msg = msg;
294 switch (type) {
295 case SMDI_MWI:
296 return mwi_msg->timestamp;
297 case SMDI_MD:
298 return md_msg->timestamp;
301 return ast_tv(0, 0);
304 static inline void unref_msg(void *msg, enum smdi_message_type type)
306 struct ast_smdi_md_message *md_msg = msg;
307 struct ast_smdi_mwi_message *mwi_msg = msg;
309 switch (type) {
310 case SMDI_MWI:
311 ASTOBJ_UNREF(mwi_msg, ast_smdi_mwi_message_destroy);
312 case SMDI_MD:
313 ASTOBJ_UNREF(md_msg, ast_smdi_md_message_destroy);
317 static void purge_old_messages(struct ast_smdi_interface *iface, enum smdi_message_type type)
319 struct timeval now;
320 long elapsed = 0;
321 void *msg;
323 lock_msg_q(iface, type);
324 msg = unlink_from_msg_q(iface, type);
325 unlock_msg_q(iface, type);
327 /* purge old messages */
328 now = ast_tvnow();
329 while (msg) {
330 elapsed = ast_tvdiff_ms(now, msg_timestamp(msg, type));
332 if (elapsed > iface->msg_expiry) {
333 /* found an expired message */
334 unref_msg(msg, type);
335 ast_log(LOG_NOTICE, "Purged expired message from %s SMDI %s message queue. "
336 "Message was %ld milliseconds too old.\n",
337 iface->name, (type == SMDI_MD) ? "MD" : "MWI",
338 elapsed - iface->msg_expiry);
340 lock_msg_q(iface, type);
341 msg = unlink_from_msg_q(iface, type);
342 unlock_msg_q(iface, type);
343 } else {
344 /* good message, put it back and return */
345 switch (type) {
346 case SMDI_MD:
347 ast_smdi_md_message_push(iface, msg);
348 break;
349 case SMDI_MWI:
350 ast_smdi_mwi_message_push(iface, msg);
351 break;
353 unref_msg(msg, type);
354 break;
359 static void *smdi_msg_pop(struct ast_smdi_interface *iface, enum smdi_message_type type)
361 void *msg;
363 purge_old_messages(iface, type);
365 lock_msg_q(iface, type);
366 msg = unlink_from_msg_q(iface, type);
367 unlock_msg_q(iface, type);
369 return msg;
372 static void *smdi_msg_find(struct ast_smdi_interface *iface,
373 enum smdi_message_type type, const char *station)
375 void *msg = NULL;
377 purge_old_messages(iface, type);
379 switch (type) {
380 case SMDI_MD:
381 msg = ASTOBJ_CONTAINER_FIND(&iface->md_q, station);
382 break;
383 case SMDI_MWI:
384 msg = ASTOBJ_CONTAINER_FIND(&iface->mwi_q, station);
385 break;
388 return msg;
391 static void *smdi_message_wait(struct ast_smdi_interface *iface, int timeout,
392 enum smdi_message_type type, const char *station)
394 struct timeval start;
395 long diff = 0;
396 void *msg;
398 start = ast_tvnow();
399 while (diff < timeout) {
400 struct timespec ts = { 0, };
401 struct timeval tv;
403 lock_msg_q(iface, type);
405 if ((msg = smdi_msg_find(iface, type, station))) {
406 unlock_msg_q(iface, type);
407 return msg;
410 tv = ast_tvadd(start, ast_tv(0, timeout));
411 ts.tv_sec = tv.tv_sec;
412 ts.tv_nsec = tv.tv_usec * 1000;
414 /* If there were no messages in the queue, then go to sleep until one
415 * arrives. */
417 ast_cond_timedwait(&iface->md_q_cond, &iface->md_q_lock, &ts);
419 if ((msg = smdi_msg_find(iface, type, station))) {
420 unlock_msg_q(iface, type);
421 return msg;
424 unlock_msg_q(iface, type);
426 /* check timeout */
427 diff = ast_tvdiff_ms(ast_tvnow(), start);
430 return NULL;
433 struct ast_smdi_md_message *ast_smdi_md_message_pop(struct ast_smdi_interface *iface)
435 return smdi_msg_pop(iface, SMDI_MD);
438 struct ast_smdi_md_message *ast_smdi_md_message_wait(struct ast_smdi_interface *iface, int timeout)
440 return smdi_message_wait(iface, timeout, SMDI_MD, NULL);
443 struct ast_smdi_mwi_message *ast_smdi_mwi_message_pop(struct ast_smdi_interface *iface)
445 return smdi_msg_pop(iface, SMDI_MWI);
448 struct ast_smdi_mwi_message *ast_smdi_mwi_message_wait(struct ast_smdi_interface *iface, int timeout)
450 return smdi_message_wait(iface, timeout, SMDI_MWI, NULL);
453 struct ast_smdi_mwi_message *ast_smdi_mwi_message_wait_station(struct ast_smdi_interface *iface, int timeout,
454 const char *station)
456 return smdi_message_wait(iface, timeout, SMDI_MWI, station);
459 struct ast_smdi_interface *ast_smdi_interface_find(const char *iface_name)
461 return (ASTOBJ_CONTAINER_FIND(&smdi_ifaces, iface_name));
464 /*!
465 * \internal
466 * \brief Read an SMDI message.
468 * \param iface_p the SMDI interface to read from.
470 * This function loops and reads from and SMDI interface. It must be stopped
471 * using pthread_cancel().
473 static void *smdi_read(void *iface_p)
475 struct ast_smdi_interface *iface = iface_p;
476 struct ast_smdi_md_message *md_msg;
477 struct ast_smdi_mwi_message *mwi_msg;
478 char c = '\0';
479 char *cp = NULL;
480 int i;
481 int start = 0;
483 /* read an smdi message */
484 while ((c = fgetc(iface->file))) {
486 /* check if this is the start of a message */
487 if (!start) {
488 if (c == 'M') {
489 ast_log(LOG_DEBUG, "Read an 'M' to start an SMDI message\n");
490 start = 1;
492 continue;
495 if (c == 'D') { /* MD message */
496 start = 0;
498 ast_log(LOG_DEBUG, "Read a 'D' ... it's an MD message.\n");
500 if (!(md_msg = ast_calloc(1, sizeof(*md_msg)))) {
501 ASTOBJ_UNREF(iface, ast_smdi_interface_destroy);
502 return NULL;
505 ASTOBJ_INIT(md_msg);
507 /* read the message desk number */
508 for (i = 0; i < sizeof(md_msg->mesg_desk_num) - 1; i++) {
509 md_msg->mesg_desk_num[i] = fgetc(iface->file);
510 ast_log(LOG_DEBUG, "Read a '%c'\n", md_msg->mesg_desk_num[i]);
513 md_msg->mesg_desk_num[sizeof(md_msg->mesg_desk_num) - 1] = '\0';
515 ast_log(LOG_DEBUG, "The message desk number is '%s'\n", md_msg->mesg_desk_num);
517 /* read the message desk terminal number */
518 for (i = 0; i < sizeof(md_msg->mesg_desk_term) - 1; i++) {
519 md_msg->mesg_desk_term[i] = fgetc(iface->file);
520 ast_log(LOG_DEBUG, "Read a '%c'\n", md_msg->mesg_desk_term[i]);
523 md_msg->mesg_desk_term[sizeof(md_msg->mesg_desk_term) - 1] = '\0';
525 ast_log(LOG_DEBUG, "The message desk terminal is '%s'\n", md_msg->mesg_desk_term);
527 /* read the message type */
528 md_msg->type = fgetc(iface->file);
530 ast_log(LOG_DEBUG, "Message type is '%c'\n", md_msg->type);
532 /* read the forwarding station number (may be blank) */
533 cp = &md_msg->fwd_st[0];
534 for (i = 0; i < sizeof(md_msg->fwd_st) - 1; i++) {
535 if ((c = fgetc(iface->file)) == ' ') {
536 *cp = '\0';
537 ast_log(LOG_DEBUG, "Read a space, done looking for the forwarding station\n");
538 break;
541 /* store c in md_msg->fwd_st */
542 if (i >= iface->msdstrip) {
543 ast_log(LOG_DEBUG, "Read a '%c' and stored it in the forwarding station buffer\n", c);
544 *cp++ = c;
545 } else {
546 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);
550 /* make sure the value is null terminated, even if this truncates it */
551 md_msg->fwd_st[sizeof(md_msg->fwd_st) - 1] = '\0';
552 cp = NULL;
554 ast_log(LOG_DEBUG, "The forwarding station is '%s'\n", md_msg->fwd_st);
556 /* Put the fwd_st in the name field so that we can use ASTOBJ_FIND to look
557 * up a message on this field */
558 ast_copy_string(md_msg->name, md_msg->fwd_st, sizeof(md_msg->name));
560 /* read the calling station number (may be blank) */
561 cp = &md_msg->calling_st[0];
562 for (i = 0; i < sizeof(md_msg->calling_st) - 1; i++) {
563 if (!isdigit((c = fgetc(iface->file)))) {
564 *cp = '\0';
565 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);
566 if (c == ' ') {
567 /* Don't break on a space. We may read the space before the calling station
568 * here if the forwarding station buffer filled up. */
569 i--; /* We're still on the same character */
570 continue;
572 break;
575 /* store c in md_msg->calling_st */
576 if (i >= iface->msdstrip) {
577 ast_log(LOG_DEBUG, "Read a '%c' and stored it in the calling station buffer\n", c);
578 *cp++ = c;
579 } else {
580 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);
584 /* make sure the value is null terminated, even if this truncates it */
585 md_msg->calling_st[sizeof(md_msg->calling_st) - 1] = '\0';
586 cp = NULL;
588 ast_log(LOG_DEBUG, "The calling station is '%s'\n", md_msg->calling_st);
590 /* add the message to the message queue */
591 md_msg->timestamp = ast_tvnow();
592 ast_smdi_md_message_push(iface, md_msg);
593 ast_log(LOG_DEBUG, "Recieved SMDI MD message on %s\n", iface->name);
595 ASTOBJ_UNREF(md_msg, ast_smdi_md_message_destroy);
597 } else if (c == 'W') { /* MWI message */
598 start = 0;
600 ast_log(LOG_DEBUG, "Read a 'W', it's an MWI message. (No more debug coming for MWI messages)\n");
602 if (!(mwi_msg = ast_calloc(1, sizeof(*mwi_msg)))) {
603 ASTOBJ_UNREF(iface,ast_smdi_interface_destroy);
604 return NULL;
607 ASTOBJ_INIT(mwi_msg);
609 /* discard the 'I' (from 'MWI') */
610 fgetc(iface->file);
612 /* read the forwarding station number (may be blank) */
613 cp = &mwi_msg->fwd_st[0];
614 for (i = 0; i < sizeof(mwi_msg->fwd_st) - 1; i++) {
615 if ((c = fgetc(iface->file)) == ' ') {
616 *cp = '\0';
617 break;
620 /* store c in md_msg->fwd_st */
621 if (i >= iface->msdstrip)
622 *cp++ = c;
625 /* make sure the station number is null terminated, even if this will truncate it */
626 mwi_msg->fwd_st[sizeof(mwi_msg->fwd_st) - 1] = '\0';
627 cp = NULL;
629 /* Put the fwd_st in the name field so that we can use ASTOBJ_FIND to look
630 * up a message on this field */
631 ast_copy_string(mwi_msg->name, mwi_msg->fwd_st, sizeof(mwi_msg->name));
633 /* read the mwi failure cause */
634 for (i = 0; i < sizeof(mwi_msg->cause) - 1; i++)
635 mwi_msg->cause[i] = fgetc(iface->file);
637 mwi_msg->cause[sizeof(mwi_msg->cause) - 1] = '\0';
639 /* add the message to the message queue */
640 mwi_msg->timestamp = ast_tvnow();
641 ast_smdi_mwi_message_push(iface, mwi_msg);
642 ast_log(LOG_DEBUG, "Recieved SMDI MWI message on %s\n", iface->name);
644 ASTOBJ_UNREF(mwi_msg, ast_smdi_mwi_message_destroy);
645 } else {
646 ast_log(LOG_ERROR, "Unknown SMDI message type recieved on %s (M%c).\n", iface->name, c);
647 start = 0;
651 ast_log(LOG_ERROR, "Error reading from SMDI interface %s, stopping listener thread\n", iface->name);
652 ASTOBJ_UNREF(iface,ast_smdi_interface_destroy);
653 return NULL;
656 void ast_smdi_md_message_destroy(struct ast_smdi_md_message *msg)
658 free(msg);
661 void ast_smdi_mwi_message_destroy(struct ast_smdi_mwi_message *msg)
663 free(msg);
666 static void destroy_mailbox_mapping(struct mailbox_mapping *mm)
668 ast_string_field_free_memory(mm);
669 ASTOBJ_UNREF(mm->iface, ast_smdi_interface_destroy);
670 free(mm);
673 static void destroy_all_mailbox_mappings(void)
675 struct mailbox_mapping *mm;
677 ast_mutex_lock(&mwi_monitor.lock);
678 while ((mm = AST_LIST_REMOVE_HEAD(&mwi_monitor.mailbox_mappings, entry)))
679 destroy_mailbox_mapping(mm);
680 ast_mutex_unlock(&mwi_monitor.lock);
683 static void append_mailbox_mapping(struct ast_variable *var, struct ast_smdi_interface *iface)
685 struct mailbox_mapping *mm;
686 char *mailbox, *context;
688 if (!(mm = ast_calloc(1, sizeof(*mm))))
689 return;
691 if (ast_string_field_init(mm, 32)) {
692 free(mm);
693 return;
696 ast_string_field_set(mm, smdi, var->name);
698 context = ast_strdupa(var->value);
699 mailbox = strsep(&context, "@");
700 if (ast_strlen_zero(context))
701 context = "default";
703 ast_string_field_set(mm, mailbox, mailbox);
704 ast_string_field_set(mm, context, context);
706 mm->iface = ASTOBJ_REF(iface);
708 ast_mutex_lock(&mwi_monitor.lock);
709 AST_LIST_INSERT_TAIL(&mwi_monitor.mailbox_mappings, mm, entry);
710 ast_mutex_unlock(&mwi_monitor.lock);
714 * \note Called with the mwi_monitor.lock locked
716 static void poll_mailbox(struct mailbox_mapping *mm)
718 char buf[1024];
719 unsigned int state;
721 snprintf(buf, sizeof(buf), "%s@%s", mm->mailbox, mm->context);
723 state = !!ast_app_has_voicemail(mm->mailbox, NULL);
725 if (state != mm->cur_state) {
726 if (state)
727 ast_smdi_mwi_set(mm->iface, mm->smdi);
728 else
729 ast_smdi_mwi_unset(mm->iface, mm->smdi);
731 mm->cur_state = state;
735 static void *mwi_monitor_handler(void *data)
737 while (!mwi_monitor.stop) {
738 struct timespec ts = { 0, };
739 struct timeval tv;
740 struct mailbox_mapping *mm;
742 ast_mutex_lock(&mwi_monitor.lock);
744 mwi_monitor.last_poll = ast_tvnow();
746 AST_LIST_TRAVERSE(&mwi_monitor.mailbox_mappings, mm, entry)
747 poll_mailbox(mm);
749 /* Sleep up to the configured polling interval. Allow unload_module()
750 * to signal us to wake up and exit. */
751 tv = ast_tvadd(mwi_monitor.last_poll, ast_tv(mwi_monitor.polling_interval, 0));
752 ts.tv_sec = tv.tv_sec;
753 ts.tv_nsec = tv.tv_usec * 1000;
754 ast_cond_timedwait(&mwi_monitor.cond, &mwi_monitor.lock, &ts);
756 ast_mutex_unlock(&mwi_monitor.lock);
759 return NULL;
762 static struct ast_smdi_interface *alloc_smdi_interface(void)
764 struct ast_smdi_interface *iface;
766 if (!(iface = ast_calloc(1, sizeof(*iface))))
767 return NULL;
769 ASTOBJ_INIT(iface);
770 ASTOBJ_CONTAINER_INIT(&iface->md_q);
771 ASTOBJ_CONTAINER_INIT(&iface->mwi_q);
773 ast_mutex_init(&iface->md_q_lock);
774 ast_cond_init(&iface->md_q_cond, NULL);
776 ast_mutex_init(&iface->mwi_q_lock);
777 ast_cond_init(&iface->mwi_q_cond, NULL);
779 return iface;
783 * \internal
784 * \brief Load and reload SMDI configuration.
785 * \param reload this should be 1 if we are reloading and 0 if not.
787 * This function loads/reloads the SMDI configuration and starts and stops
788 * interfaces accordingly.
790 * \return zero on success, -1 on failure, and 1 if no smdi interfaces were started.
792 static int smdi_load(int reload)
794 struct ast_config *conf;
795 struct ast_variable *v;
796 struct ast_smdi_interface *iface = NULL;
797 int res = 0;
799 /* Config options */
800 speed_t baud_rate = B9600; /* 9600 baud rate */
801 tcflag_t paritybit = PARENB; /* even parity checking */
802 tcflag_t charsize = CS7; /* seven bit characters */
803 int stopbits = 0; /* One stop bit */
805 int msdstrip = 0; /* strip zero digits */
806 long msg_expiry = SMDI_MSG_EXPIRY_TIME;
808 conf = ast_config_load(config_file);
810 if (!conf) {
811 if (reload)
812 ast_log(LOG_NOTICE, "Unable to reload config %s: SMDI untouched\n", config_file);
813 else
814 ast_log(LOG_NOTICE, "Unable to load config %s: SMDI disabled\n", config_file);
815 return 1;
818 /* Mark all interfaces that we are listening on. We will unmark them
819 * as we find them in the config file, this way we know any interfaces
820 * still marked after we have finished parsing the config file should
821 * be stopped.
823 if (reload)
824 ASTOBJ_CONTAINER_MARKALL(&smdi_ifaces);
826 for (v = ast_variable_browse(conf, "interfaces"); v; v = v->next) {
827 if (!strcasecmp(v->name, "baudrate")) {
828 if (!strcasecmp(v->value, "9600"))
829 baud_rate = B9600;
830 else if (!strcasecmp(v->value, "4800"))
831 baud_rate = B4800;
832 else if (!strcasecmp(v->value, "2400"))
833 baud_rate = B2400;
834 else if (!strcasecmp(v->value, "1200"))
835 baud_rate = B1200;
836 else {
837 ast_log(LOG_NOTICE, "Invalid baud rate '%s' specified in %s (line %d), using default\n", v->value, config_file, v->lineno);
838 baud_rate = B9600;
840 } else if (!strcasecmp(v->name, "msdstrip")) {
841 if (!sscanf(v->value, "%d", &msdstrip)) {
842 ast_log(LOG_NOTICE, "Invalid msdstrip value in %s (line %d), using default\n", config_file, v->lineno);
843 msdstrip = 0;
844 } else if (0 > msdstrip || msdstrip > 9) {
845 ast_log(LOG_NOTICE, "Invalid msdstrip value in %s (line %d), using default\n", config_file, v->lineno);
846 msdstrip = 0;
848 } else if (!strcasecmp(v->name, "msgexpirytime")) {
849 if (!sscanf(v->value, "%ld", &msg_expiry)) {
850 ast_log(LOG_NOTICE, "Invalid msgexpirytime value in %s (line %d), using default\n", config_file, v->lineno);
851 msg_expiry = SMDI_MSG_EXPIRY_TIME;
853 } else if (!strcasecmp(v->name, "paritybit")) {
854 if (!strcasecmp(v->value, "even"))
855 paritybit = PARENB;
856 else if (!strcasecmp(v->value, "odd"))
857 paritybit = PARENB | PARODD;
858 else if (!strcasecmp(v->value, "none"))
859 paritybit = ~PARENB;
860 else {
861 ast_log(LOG_NOTICE, "Invalid parity bit setting in %s (line %d), using default\n", config_file, v->lineno);
862 paritybit = PARENB;
864 } else if (!strcasecmp(v->name, "charsize")) {
865 if (!strcasecmp(v->value, "7"))
866 charsize = CS7;
867 else if (!strcasecmp(v->value, "8"))
868 charsize = CS8;
869 else {
870 ast_log(LOG_NOTICE, "Invalid character size setting in %s (line %d), using default\n", config_file, v->lineno);
871 charsize = CS7;
873 } else if (!strcasecmp(v->name, "twostopbits")) {
874 stopbits = ast_true(v->name);
875 } else if (!strcasecmp(v->name, "smdiport")) {
876 if (reload) {
877 /* we are reloading, check if we are already
878 * monitoring this interface, if we are we do
879 * not want to start it again. This also has
880 * the side effect of not updating different
881 * setting for the serial port, but it should
882 * be trivial to rewrite this section so that
883 * options on the port are changed without
884 * restarting the interface. Or the interface
885 * could be restarted with out emptying the
886 * queue. */
887 if ((iface = ASTOBJ_CONTAINER_FIND(&smdi_ifaces, v->value))) {
888 ast_log(LOG_NOTICE, "SMDI interface %s already running, not restarting\n", iface->name);
889 ASTOBJ_UNMARK(iface);
890 ASTOBJ_UNREF(iface, ast_smdi_interface_destroy);
891 continue;
895 if (!(iface = alloc_smdi_interface()))
896 continue;
898 ast_copy_string(iface->name, v->value, sizeof(iface->name));
900 iface->thread = AST_PTHREADT_NULL;
902 if (!(iface->file = fopen(iface->name, "r"))) {
903 ast_log(LOG_ERROR, "Error opening SMDI interface %s (%s)\n", iface->name, strerror(errno));
904 ASTOBJ_UNREF(iface, ast_smdi_interface_destroy);
905 continue;
908 iface->fd = fileno(iface->file);
910 /* Set the proper attributes for our serial port. */
912 /* get the current attributes from the port */
913 if (tcgetattr(iface->fd, &iface->mode)) {
914 ast_log(LOG_ERROR, "Error getting atributes of %s (%s)\n", iface->name, strerror(errno));
915 ASTOBJ_UNREF(iface, ast_smdi_interface_destroy);
916 continue;
919 /* set the desired speed */
920 if (cfsetispeed(&iface->mode, baud_rate) || cfsetospeed(&iface->mode, baud_rate)) {
921 ast_log(LOG_ERROR, "Error setting baud rate on %s (%s)\n", iface->name, strerror(errno));
922 ASTOBJ_UNREF(iface, ast_smdi_interface_destroy);
923 continue;
926 /* set the stop bits */
927 if (stopbits)
928 iface->mode.c_cflag = iface->mode.c_cflag | CSTOPB; /* set two stop bits */
929 else
930 iface->mode.c_cflag = iface->mode.c_cflag & ~CSTOPB; /* set one stop bit */
932 /* set the parity */
933 iface->mode.c_cflag = (iface->mode.c_cflag & ~PARENB & ~PARODD) | paritybit;
935 /* set the character size */
936 iface->mode.c_cflag = (iface->mode.c_cflag & ~CSIZE) | charsize;
938 /* commit the desired attributes */
939 if (tcsetattr(iface->fd, TCSAFLUSH, &iface->mode)) {
940 ast_log(LOG_ERROR, "Error setting attributes on %s (%s)\n", iface->name, strerror(errno));
941 ASTOBJ_UNREF(iface, ast_smdi_interface_destroy);
942 continue;
945 /* set the msdstrip */
946 iface->msdstrip = msdstrip;
948 /* set the message expiry time */
949 iface->msg_expiry = msg_expiry;
951 /* start the listener thread */
952 if (option_verbose > 2)
953 ast_verbose(VERBOSE_PREFIX_3 "Starting SMDI monitor thread for %s\n", iface->name);
954 if (ast_pthread_create_background(&iface->thread, NULL, smdi_read, iface)) {
955 ast_log(LOG_ERROR, "Error starting SMDI monitor thread for %s\n", iface->name);
956 ASTOBJ_UNREF(iface, ast_smdi_interface_destroy);
957 continue;
960 ASTOBJ_CONTAINER_LINK(&smdi_ifaces, iface);
961 ASTOBJ_UNREF(iface, ast_smdi_interface_destroy);
962 ast_module_ref(ast_module_info->self);
963 } else {
964 ast_log(LOG_NOTICE, "Ignoring unknown option %s in %s\n", v->name, config_file);
968 destroy_all_mailbox_mappings();
969 mwi_monitor.polling_interval = DEFAULT_POLLING_INTERVAL;
971 iface = NULL;
973 for (v = ast_variable_browse(conf, "mailboxes"); v; v = v->next) {
974 if (!strcasecmp(v->name, "smdiport")) {
975 if (iface)
976 ASTOBJ_UNREF(iface, ast_smdi_interface_destroy);
978 if (!(iface = ASTOBJ_CONTAINER_FIND(&smdi_ifaces, v->value))) {
979 ast_log(LOG_NOTICE, "SMDI interface %s not found\n", iface->name);
980 continue;
982 } else if (!strcasecmp(v->name, "pollinginterval")) {
983 if (sscanf(v->value, "%u", &mwi_monitor.polling_interval) != 1) {
984 ast_log(LOG_ERROR, "Invalid value for pollinginterval: %s\n", v->value);
985 mwi_monitor.polling_interval = DEFAULT_POLLING_INTERVAL;
987 } else {
988 if (!iface) {
989 ast_log(LOG_ERROR, "Mailbox mapping ignored, no valid SMDI interface specified in mailboxes section\n");
990 continue;
992 append_mailbox_mapping(v, iface);
996 if (iface)
997 ASTOBJ_UNREF(iface, ast_smdi_interface_destroy);
999 ast_config_destroy(conf);
1001 if (!AST_LIST_EMPTY(&mwi_monitor.mailbox_mappings) && mwi_monitor.thread == AST_PTHREADT_NULL
1002 && ast_pthread_create_background(&mwi_monitor.thread, NULL, mwi_monitor_handler, NULL)) {
1003 ast_log(LOG_ERROR, "Failed to start MWI monitoring thread. This module will not operate.\n");
1004 return AST_MODULE_LOAD_FAILURE;
1007 /* Prune any interfaces we should no longer monitor. */
1008 if (reload)
1009 ASTOBJ_CONTAINER_PRUNE_MARKED(&smdi_ifaces, ast_smdi_interface_destroy);
1011 ASTOBJ_CONTAINER_RDLOCK(&smdi_ifaces);
1012 /* TODO: this is bad, we need an ASTOBJ method for this! */
1013 if (!smdi_ifaces.head)
1014 res = 1;
1015 ASTOBJ_CONTAINER_UNLOCK(&smdi_ifaces);
1017 return res;
1020 struct smdi_msg_datastore {
1021 unsigned int id;
1022 struct ast_smdi_interface *iface;
1023 struct ast_smdi_md_message *md_msg;
1026 static void smdi_msg_datastore_destroy(void *data)
1028 struct smdi_msg_datastore *smd = data;
1030 if (smd->iface)
1031 ASTOBJ_UNREF(smd->iface, ast_smdi_interface_destroy);
1033 if (smd->md_msg)
1034 ASTOBJ_UNREF(smd->md_msg, ast_smdi_md_message_destroy);
1036 free(smd);
1039 static const struct ast_datastore_info smdi_msg_datastore_info = {
1040 .type = "SMDIMSG",
1041 .destroy = smdi_msg_datastore_destroy,
1044 static int smdi_msg_id;
1046 /*! In milliseconds */
1047 #define SMDI_RETRIEVE_TIMEOUT_DEFAULT 3000
1049 static int smdi_msg_retrieve_read(struct ast_channel *chan, char *cmd, char *data, char *buf, size_t len)
1051 struct ast_module_user *u;
1052 AST_DECLARE_APP_ARGS(args,
1053 AST_APP_ARG(port);
1054 AST_APP_ARG(station);
1055 AST_APP_ARG(timeout);
1057 unsigned int timeout = SMDI_RETRIEVE_TIMEOUT_DEFAULT;
1058 int res = -1;
1059 char *parse = NULL;
1060 struct smdi_msg_datastore *smd = NULL;
1061 struct ast_datastore *datastore = NULL;
1062 struct ast_smdi_interface *iface = NULL;
1063 struct ast_smdi_md_message *md_msg = NULL;
1065 u = ast_module_user_add(chan);
1067 if (ast_strlen_zero(data)) {
1068 ast_log(LOG_ERROR, "SMDI_MSG_RETRIEVE requires an argument\n");
1069 goto return_error;
1072 if (!chan) {
1073 ast_log(LOG_ERROR, "SMDI_MSG_RETRIEVE must be used with a channel\n");
1074 goto return_error;
1077 ast_autoservice_start(chan);
1079 parse = ast_strdupa(data);
1080 AST_STANDARD_APP_ARGS(args, parse);
1082 if (ast_strlen_zero(args.port) || ast_strlen_zero(args.station)) {
1083 ast_log(LOG_ERROR, "Invalid arguments provided to SMDI_MSG_RETRIEVE\n");
1084 goto return_error;
1087 if (!(iface = ast_smdi_interface_find(args.port))) {
1088 ast_log(LOG_ERROR, "SMDI port '%s' not found\n", args.port);
1089 goto return_error;
1092 if (!ast_strlen_zero(args.timeout)) {
1093 if (sscanf(args.timeout, "%u", &timeout) != 1) {
1094 ast_log(LOG_ERROR, "'%s' is not a valid timeout\n", args.timeout);
1095 timeout = SMDI_RETRIEVE_TIMEOUT_DEFAULT;
1099 if (!(md_msg = smdi_message_wait(iface, timeout, SMDI_MD, args.station))) {
1100 ast_log(LOG_WARNING, "No SMDI message retrieved for station '%s' after "
1101 "waiting %u ms.\n", args.station, timeout);
1102 goto return_error;
1105 if (!(smd = ast_calloc(1, sizeof(*smd))))
1106 goto return_error;
1108 smd->iface = ASTOBJ_REF(iface);
1109 smd->md_msg = ASTOBJ_REF(md_msg);
1110 smd->id = ast_atomic_fetchadd_int((int *) &smdi_msg_id, 1);
1111 snprintf(buf, len, "%u", smd->id);
1113 if (!(datastore = ast_channel_datastore_alloc(&smdi_msg_datastore_info, buf)))
1114 goto return_error;
1116 datastore->data = smd;
1118 ast_channel_lock(chan);
1119 ast_channel_datastore_add(chan, datastore);
1120 ast_channel_unlock(chan);
1122 res = 0;
1124 return_error:
1125 if (iface)
1126 ASTOBJ_UNREF(iface, ast_smdi_interface_destroy);
1128 if (md_msg)
1129 ASTOBJ_UNREF(md_msg, ast_smdi_md_message_destroy);
1131 if (smd && !datastore)
1132 smdi_msg_datastore_destroy(smd);
1134 if (parse)
1135 ast_autoservice_stop(chan);
1137 ast_module_user_remove(u);
1139 return res;
1142 static int smdi_msg_read(struct ast_channel *chan, char *cmd, char *data, char *buf, size_t len)
1144 struct ast_module_user *u;
1145 int res = -1;
1146 AST_DECLARE_APP_ARGS(args,
1147 AST_APP_ARG(id);
1148 AST_APP_ARG(component);
1150 char *parse;
1151 struct ast_datastore *datastore = NULL;
1152 struct smdi_msg_datastore *smd = NULL;
1154 u = ast_module_user_add(chan);
1156 if (!chan) {
1157 ast_log(LOG_ERROR, "SMDI_MSG can not be called without a channel\n");
1158 goto return_error;
1161 if (ast_strlen_zero(data)) {
1162 ast_log(LOG_WARNING, "SMDI_MSG requires an argument\n");
1163 goto return_error;
1166 parse = ast_strdupa(data);
1167 AST_STANDARD_APP_ARGS(args, parse);
1169 if (ast_strlen_zero(args.id)) {
1170 ast_log(LOG_WARNING, "ID must be supplied to SMDI_MSG\n");
1171 goto return_error;
1174 if (ast_strlen_zero(args.component)) {
1175 ast_log(LOG_WARNING, "ID must be supplied to SMDI_MSG\n");
1176 goto return_error;
1179 ast_channel_lock(chan);
1180 datastore = ast_channel_datastore_find(chan, &smdi_msg_datastore_info, args.id);
1181 ast_channel_unlock(chan);
1183 if (!datastore) {
1184 ast_log(LOG_WARNING, "No SMDI message found for message ID '%s'\n", args.id);
1185 goto return_error;
1188 smd = datastore->data;
1190 if (!strcasecmp(args.component, "station")) {
1191 ast_copy_string(buf, smd->md_msg->fwd_st, len);
1192 } else if (!strcasecmp(args.component, "callerid")) {
1193 ast_copy_string(buf, smd->md_msg->calling_st, len);
1194 } else if (!strcasecmp(args.component, "type")) {
1195 snprintf(buf, len, "%c", smd->md_msg->type);
1196 } else {
1197 ast_log(LOG_ERROR, "'%s' is not a valid message component for SMDI_MSG\n",
1198 args.component);
1199 goto return_error;
1202 res = 0;
1204 return_error:
1205 ast_module_user_remove(u);
1207 return 0;
1210 static struct ast_custom_function smdi_msg_retrieve_function = {
1211 .name = "SMDI_MSG_RETRIEVE",
1212 .synopsis = "Retrieve an SMDI message.",
1213 .syntax = "SMDI_MSG_RETRIEVE(<smdi port>,<station>[,timeout])",
1214 .desc =
1215 " This function is used to retrieve an incoming SMDI message. It returns\n"
1216 "an ID which can be used with the SMDI_MSG() function to access details of\n"
1217 "the message. Note that this is a destructive function in the sense that\n"
1218 "once an SMDI message is retrieved using this function, it is no longer in\n"
1219 "the global SMDI message queue, and can not be accessed by any other Asterisk\n"
1220 "channels. The timeout for this function is optional, and the default is\n"
1221 "3 seconds. When providing a timeout, it should be in milliseconds.\n"
1223 .read = smdi_msg_retrieve_read,
1226 static struct ast_custom_function smdi_msg_function = {
1227 .name = "SMDI_MSG",
1228 .synopsis = "Retrieve details about an SMDI message.",
1229 .syntax = "SMDI_MSG(<message_id>,<component>)",
1230 .desc =
1231 " This function is used to access details of an SMDI message that was\n"
1232 "pulled from the incoming SMDI message queue using the SMDI_MSG_RETRIEVE()\n"
1233 "function.\n"
1234 " Valid message components are:\n"
1235 " station - The forwarding station\n"
1236 " callerid - The callerID of the calling party that was forwarded\n"
1237 " type - The call type. The value here is the exact character\n"
1238 " that came in on the SMDI link. Typically, example values\n"
1239 " are: D - Direct Calls, A - Forward All Calls,\n"
1240 " B - Forward Busy Calls, N - Forward No Answer Calls\n"
1242 .read = smdi_msg_read,
1245 static int load_module(void)
1247 int res;
1249 /* initialize our containers */
1250 memset(&smdi_ifaces, 0, sizeof(smdi_ifaces));
1251 ASTOBJ_CONTAINER_INIT(&smdi_ifaces);
1253 ast_mutex_init(&mwi_monitor.lock);
1254 ast_cond_init(&mwi_monitor.cond, NULL);
1256 ast_custom_function_register(&smdi_msg_retrieve_function);
1257 ast_custom_function_register(&smdi_msg_function);
1259 /* load the config and start the listener threads*/
1260 res = smdi_load(0);
1261 if (res < 0) {
1262 return res;
1263 } else if (res == 1) {
1264 ast_log(LOG_WARNING, "No SMDI interfaces are available to listen on, not starting SMDI listener.\n");
1265 return AST_MODULE_LOAD_DECLINE;
1268 return 0;
1271 static int unload_module(void)
1273 /* this destructor stops any running smdi_read threads */
1274 ASTOBJ_CONTAINER_DESTROYALL(&smdi_ifaces, ast_smdi_interface_destroy);
1275 ASTOBJ_CONTAINER_DESTROY(&smdi_ifaces);
1277 destroy_all_mailbox_mappings();
1279 ast_mutex_lock(&mwi_monitor.lock);
1280 mwi_monitor.stop = 1;
1281 ast_cond_signal(&mwi_monitor.cond);
1282 ast_mutex_unlock(&mwi_monitor.lock);
1284 if (mwi_monitor.thread != AST_PTHREADT_NULL) {
1285 pthread_join(mwi_monitor.thread, NULL);
1288 ast_custom_function_unregister(&smdi_msg_retrieve_function);
1289 ast_custom_function_unregister(&smdi_msg_function);
1291 return 0;
1294 static int reload(void)
1296 int res;
1298 res = smdi_load(1);
1300 if (res < 0) {
1301 return res;
1302 } else if (res == 1) {
1303 ast_log(LOG_WARNING, "No SMDI interfaces were specified to listen on, not starting SDMI listener.\n");
1304 return 0;
1305 } else
1306 return 0;
1309 AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_GLOBAL_SYMBOLS, "Simplified Message Desk Interface (SMDI) Resource",
1310 .load = load_module,
1311 .unload = unload_module,
1312 .reload = reload,