aa86b188d7c9ca3938227618029cc5982e24bc93
[asterisk/asterisk.git] / res / res_smdi.c
1 /*
2  * Asterisk -- A telephony toolkit for Linux.
3  *
4  * Copyright (C) 2005-2008, Digium, Inc.
5  *
6  * Matthew A. Nicholson <mnicholson@digium.com>
7  * Russell Bryant <russell@digium.com>
8  *
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.
14  *
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.
18  */
19
20 /*!
21  * \file
22  * \brief SMDI support for Asterisk.
23  * \author Matthew A. Nicholson <mnicholson@digium.com>
24  * \author Russell Bryant <russell@digium.com>
25  *
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
28  *
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.
32  */
33
34 #include "asterisk.h"
35
36 ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
37
38 #include <termios.h>
39 #include <sys/time.h>
40 #include <time.h>
41 #include <ctype.h>
42
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"
55
56 /* Message expiry time in milliseconds */
57 #define SMDI_MSG_EXPIRY_TIME    30000 /* 30 seconds */
58
59 static const char config_file[] = "smdi.conf";
60
61 /*! \brief SMDI message desk message queue. */
62 struct ast_smdi_md_queue {
63         ASTOBJ_CONTAINER_COMPONENTS(struct ast_smdi_md_message);
64 };
65
66 /*! \brief SMDI message waiting indicator message queue. */
67 struct ast_smdi_mwi_queue {
68         ASTOBJ_CONTAINER_COMPONENTS(struct ast_smdi_mwi_message);
69 };
70
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;
75         ast_cond_t md_q_cond;
76         struct ast_smdi_mwi_queue mwi_q;
77         ast_mutex_t mwi_q_lock;
78         ast_cond_t mwi_q_cond;
79         FILE *file;
80         int fd;
81         pthread_t thread;
82         struct termios mode;
83         int msdstrip;
84         long msg_expiry;
85 };
86
87 /*! \brief SMDI interface container. */
88 struct ast_smdi_interface_container {
89         ASTOBJ_CONTAINER_COMPONENTS(struct ast_smdi_interface);
90 } smdi_ifaces;
91
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);
106         );
107         AST_LIST_ENTRY(mailbox_mapping) entry;
108 };
109
110 /*! 10 seconds */
111 #define DEFAULT_POLLING_INTERVAL 10
112
113 /*! \brief Data that gets used by the SMDI MWI monitoring thread */
114 static struct {
115         /*! The thread ID */
116         pthread_t thread;
117         ast_mutex_t lock;
118         ast_cond_t cond;
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 */
124         unsigned int stop:1;
125         /*! The time that the last poll began */
126         struct timeval last_poll;
127 } mwi_monitor = {
128         .thread = AST_PTHREADT_NULL,
129 };
130
131 static void ast_smdi_interface_destroy(struct ast_smdi_interface *iface)
132 {
133         if (iface->thread != AST_PTHREADT_NULL && iface->thread != AST_PTHREADT_STOP) {
134                 pthread_cancel(iface->thread);
135                 pthread_join(iface->thread, NULL);
136         }
137         
138         iface->thread = AST_PTHREADT_STOP;
139         
140         if (iface->file) 
141                 fclose(iface->file);
142         
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);
147
148         ast_mutex_destroy(&iface->md_q_lock);
149         ast_cond_destroy(&iface->md_q_cond);
150
151         ast_mutex_destroy(&iface->mwi_q_lock);
152         ast_cond_destroy(&iface->mwi_q_cond);
153
154         free(iface);
155
156         ast_module_unref(ast_module_info->self);
157 }
158
159 void ast_smdi_interface_unref(struct ast_smdi_interface *iface)
160 {
161         ASTOBJ_UNREF(iface, ast_smdi_interface_destroy);
162 }
163
164 /*! 
165  * \internal
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.
169  */
170 static void ast_smdi_md_message_push(struct ast_smdi_interface *iface, struct ast_smdi_md_message *md_msg)
171 {
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);
176 }
177
178 /*!
179  * \internal
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.
183  */
184 static void ast_smdi_mwi_message_push(struct ast_smdi_interface *iface, struct ast_smdi_mwi_message *mwi_msg)
185 {
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);
190 }
191
192 static int smdi_toggle_mwi(struct ast_smdi_interface *iface, const char *mailbox, int on)
193 {
194         FILE *file;
195         int i;
196         
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));
199                 return 1;
200         }       
201         
202         ASTOBJ_WRLOCK(iface);
203         
204         fprintf(file, "%s:MWI ", on ? "OP" : "RMV");
205         
206         for (i = 0; i < iface->msdstrip; i++)
207                 fprintf(file, "0");
208
209         fprintf(file, "%s!\x04", mailbox);
210
211         fclose(file);
212
213         ASTOBJ_UNLOCK(iface);
214         ast_debug(1, "Sent MWI set message for %s on %s\n", mailbox, iface->name);
215
216         return 0;
217 }
218
219 int ast_smdi_mwi_set(struct ast_smdi_interface *iface, const char *mailbox)
220 {
221         return smdi_toggle_mwi(iface, mailbox, 1);
222 }
223
224 int ast_smdi_mwi_unset(struct ast_smdi_interface *iface, const char *mailbox)
225 {
226         return smdi_toggle_mwi(iface, mailbox, 0);
227 }
228
229 void ast_smdi_md_message_putback(struct ast_smdi_interface *iface, struct ast_smdi_md_message *md_msg)
230 {
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);
235 }
236
237 void ast_smdi_mwi_message_putback(struct ast_smdi_interface *iface, struct ast_smdi_mwi_message *mwi_msg)
238 {
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);
243 }
244
245 enum smdi_message_type {
246         SMDI_MWI,
247         SMDI_MD,
248 };
249
250 static inline int lock_msg_q(struct ast_smdi_interface *iface, enum smdi_message_type type)
251 {
252         switch (type) {
253         case SMDI_MWI:
254                 return ast_mutex_lock(&iface->mwi_q_lock);
255         case SMDI_MD:   
256                 return ast_mutex_lock(&iface->md_q_lock);
257         }
258         
259         return -1;
260 }
261
262 static inline int unlock_msg_q(struct ast_smdi_interface *iface, enum smdi_message_type type)
263 {
264         switch (type) {
265         case SMDI_MWI:
266                 return ast_mutex_unlock(&iface->mwi_q_lock);
267         case SMDI_MD:
268                 return ast_mutex_unlock(&iface->md_q_lock);
269         }
270
271         return -1;
272 }
273
274 static inline void *unlink_from_msg_q(struct ast_smdi_interface *iface, enum smdi_message_type type)
275 {
276         switch (type) {
277         case SMDI_MWI:
278                 return ASTOBJ_CONTAINER_UNLINK_START(&iface->mwi_q);
279         case SMDI_MD:
280                 return ASTOBJ_CONTAINER_UNLINK_START(&iface->md_q);
281         }
282         return NULL;
283 }
284
285 static inline struct timeval msg_timestamp(void *msg, enum smdi_message_type type)
286 {
287         struct ast_smdi_md_message *md_msg = msg;
288         struct ast_smdi_mwi_message *mwi_msg = msg;
289
290         switch (type) {
291         case SMDI_MWI:
292                 return mwi_msg->timestamp;
293         case SMDI_MD:
294                 return md_msg->timestamp;
295         }
296
297         return ast_tv(0, 0);
298 }
299
300 static inline void unref_msg(void *msg, enum smdi_message_type type)
301 {
302         struct ast_smdi_md_message *md_msg = msg;
303         struct ast_smdi_mwi_message *mwi_msg = msg;
304
305         switch (type) {
306         case SMDI_MWI:
307                 ASTOBJ_UNREF(mwi_msg, ast_smdi_mwi_message_destroy);
308         case SMDI_MD:
309                 ASTOBJ_UNREF(md_msg, ast_smdi_md_message_destroy);
310         }
311 }
312
313 static void purge_old_messages(struct ast_smdi_interface *iface, enum smdi_message_type type)
314 {
315         struct timeval now = ast_tvnow();
316         long elapsed = 0;
317         void *msg;
318         
319         lock_msg_q(iface, type);
320         msg = unlink_from_msg_q(iface, type);
321         unlock_msg_q(iface, type);
322
323         /* purge old messages */
324         while (msg) {
325                 elapsed = ast_tvdiff_ms(now, msg_timestamp(msg, type));
326
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);
334
335                         lock_msg_q(iface, type);
336                         msg = unlink_from_msg_q(iface, type);
337                         unlock_msg_q(iface, type);
338                 } else {
339                         /* good message, put it back and return */
340                         switch (type) {
341                         case SMDI_MD:
342                                 ast_smdi_md_message_push(iface, msg);
343                                 break;
344                         case SMDI_MWI:
345                                 ast_smdi_mwi_message_push(iface, msg);
346                                 break;
347                         }
348                         unref_msg(msg, type);
349                         break;
350                 }
351         }
352 }
353
354 static void *smdi_msg_pop(struct ast_smdi_interface *iface, enum smdi_message_type type)
355 {
356         void *msg;
357
358         purge_old_messages(iface, type);
359
360         lock_msg_q(iface, type);
361         msg = unlink_from_msg_q(iface, type);
362         unlock_msg_q(iface, type);
363
364         return msg;
365 }
366
367 enum {
368         OPT_SEARCH_TERMINAL = (1 << 0),
369         OPT_SEARCH_NUMBER   = (1 << 1),
370 };
371
372 static void *smdi_msg_find(struct ast_smdi_interface *iface,
373         enum smdi_message_type type, const char *search_key, struct ast_flags options)
374 {
375         void *msg = NULL;
376
377         purge_old_messages(iface, type);
378
379         switch (type) {
380         case SMDI_MD:
381                 if (ast_test_flag(&options, OPT_SEARCH_TERMINAL)) {
382                         struct ast_smdi_md_message *md_msg = NULL;
383
384                         /* Searching by the message desk terminal */
385
386                         ASTOBJ_CONTAINER_TRAVERSE(&iface->md_q, !md_msg, do {
387                                 if (!strcasecmp(iterator->mesg_desk_term, search_key))
388                                         md_msg = ASTOBJ_REF(iterator);
389                         } while (0); );
390
391                         msg = md_msg;
392                 } else if (ast_test_flag(&options, OPT_SEARCH_NUMBER)) {
393                         struct ast_smdi_md_message *md_msg = NULL;
394
395                         /* Searching by the message desk number */
396
397                         ASTOBJ_CONTAINER_TRAVERSE(&iface->md_q, !md_msg, do {
398                                 if (!strcasecmp(iterator->mesg_desk_num, search_key))
399                                         md_msg = ASTOBJ_REF(iterator);
400                         } while (0); );
401
402                         msg = md_msg;
403                 } else {
404                         /* Searching by the forwarding station */
405                         msg = ASTOBJ_CONTAINER_FIND(&iface->md_q, search_key);
406                 }
407                 break;
408         case SMDI_MWI:
409                 msg = ASTOBJ_CONTAINER_FIND(&iface->mwi_q, search_key);
410                 break;
411         }
412
413         return msg;
414 }
415
416 static void *smdi_message_wait(struct ast_smdi_interface *iface, int timeout, 
417         enum smdi_message_type type, const char *search_key, struct ast_flags options)
418 {
419         struct timeval start;
420         long diff = 0;
421         void *msg;
422
423         while (diff < timeout) {
424                 struct timespec ts = { 0, };
425                 struct timeval tv;
426
427                 lock_msg_q(iface, type);
428
429                 if ((msg = smdi_msg_find(iface, type, search_key, options))) {
430                         unlock_msg_q(iface, type);
431                         return msg;
432                 }
433
434                 tv = ast_tvadd(start, ast_tv(0, timeout));
435                 ts.tv_sec = tv.tv_sec;
436                 ts.tv_nsec = tv.tv_usec * 1000;
437
438                 /* If there were no messages in the queue, then go to sleep until one
439                  * arrives. */
440
441                 ast_cond_timedwait(&iface->md_q_cond, &iface->md_q_lock, &ts);
442
443                 if ((msg = smdi_msg_find(iface, type, search_key, options))) {
444                         unlock_msg_q(iface, type);
445                         return msg;
446                 }
447
448                 unlock_msg_q(iface, type);
449
450                 /* check timeout */
451                 diff = ast_tvdiff_ms(ast_tvnow(), start);
452         }
453
454         return NULL;
455 }
456
457 struct ast_smdi_md_message *ast_smdi_md_message_pop(struct ast_smdi_interface *iface)
458 {
459         return smdi_msg_pop(iface, SMDI_MD);
460 }
461
462 struct ast_smdi_md_message *ast_smdi_md_message_wait(struct ast_smdi_interface *iface, int timeout)
463 {
464         struct ast_flags options = { 0 };
465         return smdi_message_wait(iface, timeout, SMDI_MD, NULL, options);
466 }
467
468 struct ast_smdi_mwi_message *ast_smdi_mwi_message_pop(struct ast_smdi_interface *iface)
469 {
470         return smdi_msg_pop(iface, SMDI_MWI);
471 }
472
473 struct ast_smdi_mwi_message *ast_smdi_mwi_message_wait(struct ast_smdi_interface *iface, int timeout)
474 {
475         struct ast_flags options = { 0 };
476         return smdi_message_wait(iface, timeout, SMDI_MWI, NULL, options);
477 }
478
479 struct ast_smdi_mwi_message *ast_smdi_mwi_message_wait_station(struct ast_smdi_interface *iface, int timeout,
480         const char *station)
481 {
482         struct ast_flags options = { 0 };
483         return smdi_message_wait(iface, timeout, SMDI_MWI, station, options);
484 }
485
486 struct ast_smdi_interface *ast_smdi_interface_find(const char *iface_name)
487 {
488         return (ASTOBJ_CONTAINER_FIND(&smdi_ifaces, iface_name));
489 }
490
491 /*! 
492  * \internal
493  * \brief Read an SMDI message.
494  *
495  * \param iface_p the SMDI interface to read from.
496  *
497  * This function loops and reads from and SMDI interface.  It must be stopped
498  * using pthread_cancel().
499  */
500 static void *smdi_read(void *iface_p)
501 {
502         struct ast_smdi_interface *iface = iface_p;
503         struct ast_smdi_md_message *md_msg;
504         struct ast_smdi_mwi_message *mwi_msg;
505         char c = '\0';
506         char *cp = NULL;
507         int i;
508         int start = 0;
509                 
510         /* read an smdi message */
511         while ((c = fgetc(iface->file))) {
512
513                 /* check if this is the start of a message */
514                 if (!start) {
515                         if (c == 'M') {
516                                 ast_log(LOG_DEBUG, "Read an 'M' to start an SMDI message\n");
517                                 start = 1;
518                         }
519                         continue;
520                 }
521                 
522                 if (c == 'D') { /* MD message */
523                         start = 0;
524
525                         ast_log(LOG_DEBUG, "Read a 'D' ... it's an MD message.\n");
526
527                         if (!(md_msg = ast_calloc(1, sizeof(*md_msg)))) {
528                                 ASTOBJ_UNREF(iface, ast_smdi_interface_destroy);
529                                 return NULL;
530                         }
531                         
532                         ASTOBJ_INIT(md_msg);
533
534                         /* read the message desk number */
535                         for (i = 0; i < sizeof(md_msg->mesg_desk_num) - 1; i++) {
536                                 md_msg->mesg_desk_num[i] = fgetc(iface->file);
537                                 ast_log(LOG_DEBUG, "Read a '%c'\n", md_msg->mesg_desk_num[i]);
538                         }
539
540                         md_msg->mesg_desk_num[sizeof(md_msg->mesg_desk_num) - 1] = '\0';
541                         
542                         ast_log(LOG_DEBUG, "The message desk number is '%s'\n", md_msg->mesg_desk_num);
543
544                         /* read the message desk terminal number */
545                         for (i = 0; i < sizeof(md_msg->mesg_desk_term) - 1; i++) {
546                                 md_msg->mesg_desk_term[i] = fgetc(iface->file);
547                                 ast_log(LOG_DEBUG, "Read a '%c'\n", md_msg->mesg_desk_term[i]);
548                         }
549
550                         md_msg->mesg_desk_term[sizeof(md_msg->mesg_desk_term) - 1] = '\0';
551
552                         ast_log(LOG_DEBUG, "The message desk terminal is '%s'\n", md_msg->mesg_desk_term);
553
554                         /* read the message type */
555                         md_msg->type = fgetc(iface->file);
556                  
557                         ast_log(LOG_DEBUG, "Message type is '%c'\n", md_msg->type);
558
559                         /* read the forwarding station number (may be blank) */
560                         cp = &md_msg->fwd_st[0];
561                         for (i = 0; i < sizeof(md_msg->fwd_st) - 1; i++) {
562                                 if ((c = fgetc(iface->file)) == ' ') {
563                                         *cp = '\0';
564                                         ast_log(LOG_DEBUG, "Read a space, done looking for the forwarding station\n");
565                                         break;
566                                 }
567
568                                 /* store c in md_msg->fwd_st */
569                                 if (i >= iface->msdstrip) {
570                                         ast_log(LOG_DEBUG, "Read a '%c' and stored it in the forwarding station buffer\n", c);
571                                         *cp++ = c;
572                                 } else {
573                                         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);
574                                 }
575                         }
576
577                         /* make sure the value is null terminated, even if this truncates it */
578                         md_msg->fwd_st[sizeof(md_msg->fwd_st) - 1] = '\0';
579                         cp = NULL;
580
581                         ast_log(LOG_DEBUG, "The forwarding station is '%s'\n", md_msg->fwd_st);
582
583                         /* Put the fwd_st in the name field so that we can use ASTOBJ_FIND to look
584                          * up a message on this field */
585                         ast_copy_string(md_msg->name, md_msg->fwd_st, sizeof(md_msg->name));
586
587                         /* read the calling station number (may be blank) */
588                         cp = &md_msg->calling_st[0];
589                         for (i = 0; i < sizeof(md_msg->calling_st) - 1; i++) {
590                                 if (!isdigit((c = fgetc(iface->file)))) {
591                                         *cp = '\0';
592                                         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);
593                                         if (c == ' ') {
594                                                 /* Don't break on a space.  We may read the space before the calling station
595                                                  * here if the forwarding station buffer filled up. */
596                                                 i--; /* We're still on the same character */
597                                                 continue;
598                                         }
599                                         break;
600                                 }
601
602                                 /* store c in md_msg->calling_st */
603                                 if (i >= iface->msdstrip) {
604                                         ast_log(LOG_DEBUG, "Read a '%c' and stored it in the calling station buffer\n", c);
605                                         *cp++ = c;
606                                 } else {
607                                         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);
608                                 }
609                         }
610
611                         /* make sure the value is null terminated, even if this truncates it */
612                         md_msg->calling_st[sizeof(md_msg->calling_st) - 1] = '\0';
613                         cp = NULL;
614
615                         ast_log(LOG_DEBUG, "The calling station is '%s'\n", md_msg->calling_st);
616
617                         /* add the message to the message queue */
618                         md_msg->timestamp = ast_tvnow();
619                         ast_smdi_md_message_push(iface, md_msg);
620                         ast_log(LOG_DEBUG, "Recieved SMDI MD message on %s\n", iface->name);
621                         
622                         ASTOBJ_UNREF(md_msg, ast_smdi_md_message_destroy);
623
624                 } else if (c == 'W') { /* MWI message */
625                         start = 0;
626
627                         ast_log(LOG_DEBUG, "Read a 'W', it's an MWI message. (No more debug coming for MWI messages)\n");
628
629                         if (!(mwi_msg = ast_calloc(1, sizeof(*mwi_msg)))) {
630                                 ASTOBJ_UNREF(iface,ast_smdi_interface_destroy);
631                                 return NULL;
632                         }
633
634                         ASTOBJ_INIT(mwi_msg);
635
636                         /* discard the 'I' (from 'MWI') */
637                         fgetc(iface->file);
638                         
639                         /* read the forwarding station number (may be blank) */
640                         cp = &mwi_msg->fwd_st[0];
641                         for (i = 0; i < sizeof(mwi_msg->fwd_st) - 1; i++) {
642                                 if ((c = fgetc(iface->file)) == ' ') {
643                                         *cp = '\0';
644                                         break;
645                                 }
646
647                                 /* store c in md_msg->fwd_st */
648                                 if (i >= iface->msdstrip)
649                                         *cp++ = c;
650                         }
651
652                         /* make sure the station number is null terminated, even if this will truncate it */
653                         mwi_msg->fwd_st[sizeof(mwi_msg->fwd_st) - 1] = '\0';
654                         cp = NULL;
655                         
656                         /* Put the fwd_st in the name field so that we can use ASTOBJ_FIND to look
657                          * up a message on this field */
658                         ast_copy_string(mwi_msg->name, mwi_msg->fwd_st, sizeof(mwi_msg->name));
659
660                         /* read the mwi failure cause */
661                         for (i = 0; i < sizeof(mwi_msg->cause) - 1; i++)
662                                 mwi_msg->cause[i] = fgetc(iface->file);
663
664                         mwi_msg->cause[sizeof(mwi_msg->cause) - 1] = '\0';
665
666                         /* add the message to the message queue */
667                         mwi_msg->timestamp = ast_tvnow();
668                         ast_smdi_mwi_message_push(iface, mwi_msg);
669                         ast_log(LOG_DEBUG, "Recieved SMDI MWI message on %s\n", iface->name);
670                         
671                         ASTOBJ_UNREF(mwi_msg, ast_smdi_mwi_message_destroy);
672                 } else {
673                         ast_log(LOG_ERROR, "Unknown SMDI message type recieved on %s (M%c).\n", iface->name, c);
674                         start = 0;
675                 }
676         }
677
678         ast_log(LOG_ERROR, "Error reading from SMDI interface %s, stopping listener thread\n", iface->name);
679         ASTOBJ_UNREF(iface,ast_smdi_interface_destroy);
680         return NULL;
681 }
682
683 void ast_smdi_md_message_destroy(struct ast_smdi_md_message *msg)
684 {
685         ast_free(msg);
686 }
687
688 void ast_smdi_mwi_message_destroy(struct ast_smdi_mwi_message *msg)
689 {
690         ast_free(msg);
691 }
692
693 static void destroy_mailbox_mapping(struct mailbox_mapping *mm)
694 {
695         ast_string_field_free_memory(mm);
696         ASTOBJ_UNREF(mm->iface, ast_smdi_interface_destroy);
697         free(mm);
698 }
699
700 static void destroy_all_mailbox_mappings(void)
701 {
702         struct mailbox_mapping *mm;
703
704         ast_mutex_lock(&mwi_monitor.lock);
705         while ((mm = AST_LIST_REMOVE_HEAD(&mwi_monitor.mailbox_mappings, entry)))
706                 destroy_mailbox_mapping(mm);
707         ast_mutex_unlock(&mwi_monitor.lock);
708 }
709
710 static void append_mailbox_mapping(struct ast_variable *var, struct ast_smdi_interface *iface)
711 {
712         struct mailbox_mapping *mm;
713         char *mailbox, *context;
714
715         if (!(mm = ast_calloc(1, sizeof(*mm))))
716                 return;
717         
718         if (ast_string_field_init(mm, 32)) {
719                 free(mm);
720                 return;
721         }
722
723         ast_string_field_set(mm, smdi, var->name);
724
725         context = ast_strdupa(var->value);
726         mailbox = strsep(&context, "@");
727         if (ast_strlen_zero(context))
728                 context = "default";
729
730         ast_string_field_set(mm, mailbox, mailbox);
731         ast_string_field_set(mm, context, context);
732
733         mm->iface = ASTOBJ_REF(iface);
734
735         ast_mutex_lock(&mwi_monitor.lock);
736         AST_LIST_INSERT_TAIL(&mwi_monitor.mailbox_mappings, mm, entry);
737         ast_mutex_unlock(&mwi_monitor.lock);
738 }
739
740 /*!
741  * \note Called with the mwi_monitor.lock locked
742  */
743 static void poll_mailbox(struct mailbox_mapping *mm)
744 {
745         char buf[1024];
746         unsigned int state;
747
748         snprintf(buf, sizeof(buf), "%s@%s", mm->mailbox, mm->context);
749
750         state = !!ast_app_has_voicemail(mm->mailbox, NULL);
751
752         if (state != mm->cur_state) {
753                 if (state)
754                         ast_smdi_mwi_set(mm->iface, mm->smdi);
755                 else
756                         ast_smdi_mwi_unset(mm->iface, mm->smdi);
757
758                 mm->cur_state = state;
759         }
760 }
761
762 static void *mwi_monitor_handler(void *data)
763 {
764         while (!mwi_monitor.stop) {
765                 struct timespec ts = { 0, };
766                 struct timeval tv;
767                 struct mailbox_mapping *mm;
768
769                 ast_mutex_lock(&mwi_monitor.lock);
770
771                 mwi_monitor.last_poll = ast_tvnow();
772
773                 AST_LIST_TRAVERSE(&mwi_monitor.mailbox_mappings, mm, entry)
774                         poll_mailbox(mm);
775
776                 /* Sleep up to the configured polling interval.  Allow unload_module()
777                  * to signal us to wake up and exit. */
778                 tv = ast_tvadd(mwi_monitor.last_poll, ast_tv(mwi_monitor.polling_interval, 0));
779                 ts.tv_sec = tv.tv_sec;
780                 ts.tv_nsec = tv.tv_usec * 1000;
781                 ast_cond_timedwait(&mwi_monitor.cond, &mwi_monitor.lock, &ts);
782
783                 ast_mutex_unlock(&mwi_monitor.lock);
784         }
785
786         return NULL;
787 }
788
789 static struct ast_smdi_interface *alloc_smdi_interface(void)
790 {
791         struct ast_smdi_interface *iface;
792
793         if (!(iface = ast_calloc(1, sizeof(*iface))))
794                 return NULL;
795
796         ASTOBJ_INIT(iface);
797         ASTOBJ_CONTAINER_INIT(&iface->md_q);
798         ASTOBJ_CONTAINER_INIT(&iface->mwi_q);
799
800         ast_mutex_init(&iface->md_q_lock);
801         ast_cond_init(&iface->md_q_cond, NULL);
802
803         ast_mutex_init(&iface->mwi_q_lock);
804         ast_cond_init(&iface->mwi_q_cond, NULL);
805
806         return iface;
807 }
808
809 /*!
810  * \internal
811  * \brief Load and reload SMDI configuration.
812  * \param reload this should be 1 if we are reloading and 0 if not.
813  *
814  * This function loads/reloads the SMDI configuration and starts and stops
815  * interfaces accordingly.
816  *
817  * \return zero on success, -1 on failure, and 1 if no smdi interfaces were started.
818  */
819 static int smdi_load(int reload)
820 {
821         struct ast_config *conf;
822         struct ast_variable *v;
823         struct ast_smdi_interface *iface = NULL;
824         struct ast_flags config_flags = { reload ? CONFIG_FLAG_FILEUNCHANGED : 0 };
825         int res = 0;
826
827         /* Config options */
828         speed_t baud_rate = B9600;     /* 9600 baud rate */
829         tcflag_t paritybit = PARENB;   /* even parity checking */
830         tcflag_t charsize = CS7;       /* seven bit characters */
831         int stopbits = 0;              /* One stop bit */
832         
833         int msdstrip = 0;              /* strip zero digits */
834         long msg_expiry = SMDI_MSG_EXPIRY_TIME;
835
836         if (!(conf = ast_config_load(config_file, config_flags))) {
837                 if (reload)
838                         ast_log(LOG_NOTICE, "Unable to reload config %s: SMDI untouched\n", config_file);
839                 else
840                         ast_log(LOG_NOTICE, "Unable to load config %s: SMDI disabled\n", config_file);
841                 return 1;
842         } else if (conf == CONFIG_STATUS_FILEUNCHANGED)
843                 return 0;
844
845         /* Mark all interfaces that we are listening on.  We will unmark them
846          * as we find them in the config file, this way we know any interfaces
847          * still marked after we have finished parsing the config file should
848          * be stopped.
849          */
850         if (reload)
851                 ASTOBJ_CONTAINER_MARKALL(&smdi_ifaces);
852
853         for (v = ast_variable_browse(conf, "interfaces"); v; v = v->next) {
854                 if (!strcasecmp(v->name, "baudrate")) {
855                         if (!strcasecmp(v->value, "9600"))
856                                 baud_rate = B9600;
857                         else if (!strcasecmp(v->value, "4800"))
858                                 baud_rate = B4800;
859                         else if (!strcasecmp(v->value, "2400"))
860                                 baud_rate = B2400;
861                         else if (!strcasecmp(v->value, "1200"))
862                                 baud_rate = B1200;
863                         else {
864                                 ast_log(LOG_NOTICE, "Invalid baud rate '%s' specified in %s (line %d), using default\n", v->value, config_file, v->lineno);
865                                 baud_rate = B9600;
866                         }
867                 } else if (!strcasecmp(v->name, "msdstrip")) {
868                         if (!sscanf(v->value, "%d", &msdstrip)) {
869                                 ast_log(LOG_NOTICE, "Invalid msdstrip value in %s (line %d), using default\n", config_file, v->lineno);
870                                 msdstrip = 0;
871                         } else if (0 > msdstrip || msdstrip > 9) {
872                                 ast_log(LOG_NOTICE, "Invalid msdstrip value in %s (line %d), using default\n", config_file, v->lineno);
873                                 msdstrip = 0;
874                         }
875                 } else if (!strcasecmp(v->name, "msgexpirytime")) {
876                         if (!sscanf(v->value, "%ld", &msg_expiry)) {
877                                 ast_log(LOG_NOTICE, "Invalid msgexpirytime value in %s (line %d), using default\n", config_file, v->lineno);
878                                 msg_expiry = SMDI_MSG_EXPIRY_TIME;
879                         }
880                 } else if (!strcasecmp(v->name, "paritybit")) {
881                         if (!strcasecmp(v->value, "even"))
882                                 paritybit = PARENB;
883                         else if (!strcasecmp(v->value, "odd"))
884                                 paritybit = PARENB | PARODD;
885                         else if (!strcasecmp(v->value, "none"))
886                                 paritybit = ~PARENB;
887                         else {
888                                 ast_log(LOG_NOTICE, "Invalid parity bit setting in %s (line %d), using default\n", config_file, v->lineno);
889                                 paritybit = PARENB;
890                         }
891                 } else if (!strcasecmp(v->name, "charsize")) {
892                         if (!strcasecmp(v->value, "7"))
893                                 charsize = CS7;
894                         else if (!strcasecmp(v->value, "8"))
895                                 charsize = CS8;
896                         else {
897                                 ast_log(LOG_NOTICE, "Invalid character size setting in %s (line %d), using default\n", config_file, v->lineno);
898                                 charsize = CS7;
899                         }
900                 } else if (!strcasecmp(v->name, "twostopbits")) {
901                         stopbits = ast_true(v->name);
902                 } else if (!strcasecmp(v->name, "smdiport")) {
903                         if (reload) {
904                                 /* we are reloading, check if we are already
905                                  * monitoring this interface, if we are we do
906                                  * not want to start it again.  This also has
907                                  * the side effect of not updating different
908                                  * setting for the serial port, but it should
909                                  * be trivial to rewrite this section so that
910                                  * options on the port are changed without
911                                  * restarting the interface.  Or the interface
912                                  * could be restarted with out emptying the
913                                  * queue. */
914                                 if ((iface = ASTOBJ_CONTAINER_FIND(&smdi_ifaces, v->value))) {
915                                         ast_log(LOG_NOTICE, "SMDI interface %s already running, not restarting\n", iface->name);
916                                         ASTOBJ_UNMARK(iface);
917                                         ASTOBJ_UNREF(iface, ast_smdi_interface_destroy);
918                                         continue;
919                                 }
920                         }
921                         
922                         if (!(iface = alloc_smdi_interface()))
923                                 continue;
924
925                         ast_copy_string(iface->name, v->value, sizeof(iface->name));
926
927                         iface->thread = AST_PTHREADT_NULL;
928
929                         if (!(iface->file = fopen(iface->name, "r"))) {
930                                 ast_log(LOG_ERROR, "Error opening SMDI interface %s (%s)\n", iface->name, strerror(errno));
931                                 ASTOBJ_UNREF(iface, ast_smdi_interface_destroy);
932                                 continue;
933                         }
934
935                         iface->fd = fileno(iface->file);
936
937                         /* Set the proper attributes for our serial port. */
938
939                         /* get the current attributes from the port */
940                         if (tcgetattr(iface->fd, &iface->mode)) {
941                                 ast_log(LOG_ERROR, "Error getting atributes of %s (%s)\n", iface->name, strerror(errno));
942                                 ASTOBJ_UNREF(iface, ast_smdi_interface_destroy);
943                                 continue;
944                         }
945
946                         /* set the desired speed */
947                         if (cfsetispeed(&iface->mode, baud_rate) || cfsetospeed(&iface->mode, baud_rate)) {
948                                 ast_log(LOG_ERROR, "Error setting baud rate on %s (%s)\n", iface->name, strerror(errno));
949                                 ASTOBJ_UNREF(iface, ast_smdi_interface_destroy);
950                                 continue;
951                         }
952                         
953                         /* set the stop bits */
954                         if (stopbits)
955                                 iface->mode.c_cflag = iface->mode.c_cflag | CSTOPB;   /* set two stop bits */
956                         else
957                                 iface->mode.c_cflag = iface->mode.c_cflag & ~CSTOPB;  /* set one stop bit */
958                         
959                         /* set the parity */
960                         iface->mode.c_cflag = (iface->mode.c_cflag & ~PARENB & ~PARODD) | paritybit;
961                         
962                         /* set the character size */
963                         iface->mode.c_cflag = (iface->mode.c_cflag & ~CSIZE) | charsize;
964                         
965                         /* commit the desired attributes */
966                         if (tcsetattr(iface->fd, TCSAFLUSH, &iface->mode)) {
967                                 ast_log(LOG_ERROR, "Error setting attributes on %s (%s)\n", iface->name, strerror(errno));
968                                 ASTOBJ_UNREF(iface, ast_smdi_interface_destroy);
969                                 continue;
970                         }
971
972                         /* set the msdstrip */
973                         iface->msdstrip = msdstrip;
974
975                         /* set the message expiry time */
976                         iface->msg_expiry = msg_expiry;
977
978                         /* start the listener thread */
979                         ast_verb(3, "Starting SMDI monitor thread for %s\n", iface->name);
980                         if (ast_pthread_create_background(&iface->thread, NULL, smdi_read, iface)) {
981                                 ast_log(LOG_ERROR, "Error starting SMDI monitor thread for %s\n", iface->name);
982                                 ASTOBJ_UNREF(iface, ast_smdi_interface_destroy);
983                                 continue;
984                         }
985
986                         ASTOBJ_CONTAINER_LINK(&smdi_ifaces, iface);
987                         ASTOBJ_UNREF(iface, ast_smdi_interface_destroy);
988                         ast_module_ref(ast_module_info->self);
989                 } else {
990                         ast_log(LOG_NOTICE, "Ignoring unknown option %s in %s\n", v->name, config_file);
991                 }
992         }
993
994         destroy_all_mailbox_mappings();
995         mwi_monitor.polling_interval = DEFAULT_POLLING_INTERVAL;
996         
997         iface = NULL;
998
999         for (v = ast_variable_browse(conf, "mailboxes"); v; v = v->next) {
1000                 if (!strcasecmp(v->name, "smdiport")) {
1001                         if (iface)
1002                                 ASTOBJ_UNREF(iface, ast_smdi_interface_destroy);
1003
1004                         if (!(iface = ASTOBJ_CONTAINER_FIND(&smdi_ifaces, v->value))) {
1005                                 ast_log(LOG_NOTICE, "SMDI interface %s not found\n", iface->name);
1006                                 continue;
1007                         }
1008                 } else if (!strcasecmp(v->name, "pollinginterval")) {
1009                         if (sscanf(v->value, "%u", &mwi_monitor.polling_interval) != 1) {
1010                                 ast_log(LOG_ERROR, "Invalid value for pollinginterval: %s\n", v->value);
1011                                 mwi_monitor.polling_interval = DEFAULT_POLLING_INTERVAL;
1012                         }
1013                 } else {
1014                         if (!iface) {
1015                                 ast_log(LOG_ERROR, "Mailbox mapping ignored, no valid SMDI interface specified in mailboxes section\n");
1016                                 continue;
1017                         }
1018                         append_mailbox_mapping(v, iface);
1019                 }
1020         }
1021
1022         if (iface)
1023                 ASTOBJ_UNREF(iface, ast_smdi_interface_destroy);
1024
1025         ast_config_destroy(conf);
1026         
1027         if (!AST_LIST_EMPTY(&mwi_monitor.mailbox_mappings) && mwi_monitor.thread == AST_PTHREADT_NULL
1028                 && ast_pthread_create_background(&mwi_monitor.thread, NULL, mwi_monitor_handler, NULL)) {
1029                 ast_log(LOG_ERROR, "Failed to start MWI monitoring thread.  This module will not operate.\n");
1030                 return AST_MODULE_LOAD_FAILURE;
1031         }
1032
1033         /* Prune any interfaces we should no longer monitor. */
1034         if (reload)
1035                 ASTOBJ_CONTAINER_PRUNE_MARKED(&smdi_ifaces, ast_smdi_interface_destroy);
1036         
1037         ASTOBJ_CONTAINER_RDLOCK(&smdi_ifaces);
1038         /* TODO: this is bad, we need an ASTOBJ method for this! */
1039         if (!smdi_ifaces.head)
1040                 res = 1;
1041         ASTOBJ_CONTAINER_UNLOCK(&smdi_ifaces);
1042         
1043         return res;
1044 }
1045
1046 struct smdi_msg_datastore {
1047         unsigned int id;
1048         struct ast_smdi_interface *iface;
1049         struct ast_smdi_md_message *md_msg;
1050 };
1051
1052 static void smdi_msg_datastore_destroy(void *data)
1053 {
1054         struct smdi_msg_datastore *smd = data;
1055
1056         if (smd->iface)
1057                 ASTOBJ_UNREF(smd->iface, ast_smdi_interface_destroy);
1058
1059         if (smd->md_msg)
1060                 ASTOBJ_UNREF(smd->md_msg, ast_smdi_md_message_destroy);
1061
1062         free(smd);
1063 }
1064
1065 static const struct ast_datastore_info smdi_msg_datastore_info = {
1066         .type = "SMDIMSG",
1067         .destroy = smdi_msg_datastore_destroy,
1068 };
1069
1070 static int smdi_msg_id;
1071
1072 /*! In milliseconds */
1073 #define SMDI_RETRIEVE_TIMEOUT_DEFAULT 3000
1074
1075 AST_APP_OPTIONS(smdi_msg_ret_options, BEGIN_OPTIONS
1076         AST_APP_OPTION('t', OPT_SEARCH_TERMINAL),
1077         AST_APP_OPTION('n', OPT_SEARCH_NUMBER),
1078 END_OPTIONS );
1079
1080 static int smdi_msg_retrieve_read(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len)
1081 {
1082         struct ast_module_user *u;
1083         AST_DECLARE_APP_ARGS(args,
1084                 AST_APP_ARG(port);
1085                 AST_APP_ARG(search_key);
1086                 AST_APP_ARG(timeout);
1087                 AST_APP_ARG(options);
1088         );
1089         struct ast_flags options = { 0 };
1090         unsigned int timeout = SMDI_RETRIEVE_TIMEOUT_DEFAULT;
1091         int res = -1;
1092         char *parse = NULL;
1093         struct smdi_msg_datastore *smd = NULL;
1094         struct ast_datastore *datastore = NULL;
1095         struct ast_smdi_interface *iface = NULL;
1096         struct ast_smdi_md_message *md_msg = NULL;
1097
1098         u = ast_module_user_add(chan);
1099
1100         if (ast_strlen_zero(data)) {
1101                 ast_log(LOG_ERROR, "SMDI_MSG_RETRIEVE requires an argument\n");
1102                 goto return_error;
1103         }
1104
1105         if (!chan) {
1106                 ast_log(LOG_ERROR, "SMDI_MSG_RETRIEVE must be used with a channel\n");
1107                 goto return_error;
1108         }
1109
1110         ast_autoservice_start(chan);
1111
1112         parse = ast_strdupa(data);
1113         AST_STANDARD_APP_ARGS(args, parse);
1114
1115         if (ast_strlen_zero(args.port) || ast_strlen_zero(args.search_key)) {
1116                 ast_log(LOG_ERROR, "Invalid arguments provided to SMDI_MSG_RETRIEVE\n");
1117                 goto return_error;
1118         }
1119
1120         if (!(iface = ast_smdi_interface_find(args.port))) {
1121                 ast_log(LOG_ERROR, "SMDI port '%s' not found\n", args.port);
1122                 goto return_error;
1123         }
1124
1125         if (!ast_strlen_zero(args.options)) {
1126                 ast_app_parse_options(smdi_msg_ret_options, &options, NULL, args.options);
1127         }
1128
1129         if (!ast_strlen_zero(args.timeout)) {
1130                 if (sscanf(args.timeout, "%u", &timeout) != 1) {
1131                         ast_log(LOG_ERROR, "'%s' is not a valid timeout\n", args.timeout);
1132                         timeout = SMDI_RETRIEVE_TIMEOUT_DEFAULT;
1133                 }
1134         }
1135
1136         if (!(md_msg = smdi_message_wait(iface, timeout, SMDI_MD, args.search_key, options))) {
1137                 ast_log(LOG_WARNING, "No SMDI message retrieved for search key '%s' after "
1138                         "waiting %u ms.\n", args.search_key, timeout);
1139                 goto return_error;
1140         }
1141
1142         if (!(smd = ast_calloc(1, sizeof(*smd))))
1143                 goto return_error;
1144
1145         smd->iface = ASTOBJ_REF(iface);
1146         smd->md_msg = ASTOBJ_REF(md_msg);
1147         smd->id = ast_atomic_fetchadd_int((int *) &smdi_msg_id, 1);
1148         snprintf(buf, len, "%u", smd->id);
1149
1150         if (!(datastore = ast_channel_datastore_alloc(&smdi_msg_datastore_info, buf)))
1151                 goto return_error;
1152
1153         datastore->data = smd;
1154
1155         ast_channel_lock(chan);
1156         ast_channel_datastore_add(chan, datastore);
1157         ast_channel_unlock(chan);
1158
1159         res = 0;
1160
1161 return_error:
1162         if (iface)
1163                 ASTOBJ_UNREF(iface, ast_smdi_interface_destroy);
1164
1165         if (md_msg)
1166                 ASTOBJ_UNREF(md_msg, ast_smdi_md_message_destroy);
1167
1168         if (smd && !datastore)
1169                 smdi_msg_datastore_destroy(smd);
1170
1171         if (parse)
1172                 ast_autoservice_stop(chan);
1173
1174         ast_module_user_remove(u);
1175
1176         return res;
1177 }
1178
1179 static int smdi_msg_read(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len)
1180 {
1181         struct ast_module_user *u;
1182         int res = -1;
1183         AST_DECLARE_APP_ARGS(args,
1184                 AST_APP_ARG(id);
1185                 AST_APP_ARG(component);
1186         );
1187         char *parse;
1188         struct ast_datastore *datastore = NULL;
1189         struct smdi_msg_datastore *smd = NULL;
1190
1191         u = ast_module_user_add(chan);
1192
1193         if (!chan) {
1194                 ast_log(LOG_ERROR, "SMDI_MSG can not be called without a channel\n");
1195                 goto return_error;
1196         }
1197
1198         if (ast_strlen_zero(data)) {
1199                 ast_log(LOG_WARNING, "SMDI_MSG requires an argument\n");
1200                 goto return_error;
1201         }
1202
1203         parse = ast_strdupa(data);
1204         AST_STANDARD_APP_ARGS(args, parse);
1205
1206         if (ast_strlen_zero(args.id)) {
1207                 ast_log(LOG_WARNING, "ID must be supplied to SMDI_MSG\n");
1208                 goto return_error;
1209         }
1210
1211         if (ast_strlen_zero(args.component)) {
1212                 ast_log(LOG_WARNING, "ID must be supplied to SMDI_MSG\n");
1213                 goto return_error;
1214         }
1215
1216         ast_channel_lock(chan);
1217         datastore = ast_channel_datastore_find(chan, &smdi_msg_datastore_info, args.id);
1218         ast_channel_unlock(chan);
1219         
1220         if (!datastore) {
1221                 ast_log(LOG_WARNING, "No SMDI message found for message ID '%s'\n", args.id);
1222                 goto return_error;
1223         }
1224
1225         smd = datastore->data;
1226
1227         if (!strcasecmp(args.component, "number")) {
1228                 ast_copy_string(buf, smd->md_msg->mesg_desk_num, len);
1229         } else if (!strcasecmp(args.component, "terminal")) {
1230                 ast_copy_string(buf, smd->md_msg->mesg_desk_term, len);
1231         } else if (!strcasecmp(args.component, "station")) {
1232                 ast_copy_string(buf, smd->md_msg->fwd_st, len);
1233         } else if (!strcasecmp(args.component, "callerid")) {
1234                 ast_copy_string(buf, smd->md_msg->calling_st, len);
1235         } else if (!strcasecmp(args.component, "type")) {
1236                 snprintf(buf, len, "%c", smd->md_msg->type);
1237         } else {
1238                 ast_log(LOG_ERROR, "'%s' is not a valid message component for SMDI_MSG\n",
1239                         args.component);
1240                 goto return_error;
1241         }
1242
1243         res = 0;
1244
1245 return_error:
1246         ast_module_user_remove(u);
1247
1248         return 0;
1249 }
1250
1251 static struct ast_custom_function smdi_msg_retrieve_function = {
1252         .name = "SMDI_MSG_RETRIEVE",
1253         .synopsis = "Retrieve an SMDI message.",
1254         .syntax = "SMDI_MSG_RETRIEVE(<smdi port>,<search key>[,timeout[,options]])",
1255         .desc = 
1256         "   This function is used to retrieve an incoming SMDI message.  It returns\n"
1257         "an ID which can be used with the SMDI_MSG() function to access details of\n"
1258         "the message.  Note that this is a destructive function in the sense that\n"
1259         "once an SMDI message is retrieved using this function, it is no longer in\n"
1260         "the global SMDI message queue, and can not be accessed by any other Asterisk\n"
1261         "channels.  The timeout for this function is optional, and the default is\n"
1262         "3 seconds.  When providing a timeout, it should be in milliseconds.\n"
1263         "   The default search is done on the forwarding station ID.  However, if\n"
1264         "you set one of the search key options in the options field, you can change\n"
1265         "this behavior.\n"
1266         "   Options:\n"
1267         "     t - Instead of searching on the forwarding station, search on the message\n"
1268         "         desk terminal.\n"
1269         "     n - Instead of searching on the forwarding station, search on the message\n"
1270         "         desk number.\n"
1271         "",
1272         .read = smdi_msg_retrieve_read,
1273 };
1274
1275 static struct ast_custom_function smdi_msg_function = {
1276         .name = "SMDI_MSG",
1277         .synopsis = "Retrieve details about an SMDI message.",
1278         .syntax = "SMDI_MSG(<message_id>,<component>)",
1279         .desc = 
1280         "   This function is used to access details of an SMDI message that was\n"
1281         "pulled from the incoming SMDI message queue using the SMDI_MSG_RETRIEVE()\n"
1282         "function.\n"
1283         "   Valid message components are:\n"
1284         "      number   - The message desk number\n"
1285         "      terminal - The message desk terminal\n"
1286         "      station  - The forwarding station\n"
1287         "      callerid - The callerID of the calling party that was forwarded\n"
1288         "      type     - The call type.  The value here is the exact character\n"
1289         "                 that came in on the SMDI link.  Typically, example values\n"
1290         "                 are: D - Direct Calls, A - Forward All Calls,\n"
1291         "                      B - Forward Busy Calls, N - Forward No Answer Calls\n"
1292         "",
1293         .read = smdi_msg_read,
1294 };
1295
1296 static int load_module(void)
1297 {
1298         int res;
1299         
1300         /* initialize our containers */
1301         memset(&smdi_ifaces, 0, sizeof(smdi_ifaces));
1302         ASTOBJ_CONTAINER_INIT(&smdi_ifaces);
1303         
1304         ast_mutex_init(&mwi_monitor.lock);
1305         ast_cond_init(&mwi_monitor.cond, NULL);
1306
1307         ast_custom_function_register(&smdi_msg_retrieve_function);
1308         ast_custom_function_register(&smdi_msg_function);
1309
1310         /* load the config and start the listener threads*/
1311         res = smdi_load(0);
1312         if (res < 0) {
1313                 return res;
1314         } else if (res == 1) {
1315                 ast_log(LOG_NOTICE, "No SMDI interfaces are available to listen on, not starting SMDI listener.\n");
1316                 return AST_MODULE_LOAD_DECLINE;
1317         }
1318         
1319         return AST_MODULE_LOAD_SUCCESS;
1320 }
1321
1322 static int unload_module(void)
1323 {
1324         /* this destructor stops any running smdi_read threads */
1325         ASTOBJ_CONTAINER_DESTROYALL(&smdi_ifaces, ast_smdi_interface_destroy);
1326         ASTOBJ_CONTAINER_DESTROY(&smdi_ifaces);
1327
1328         destroy_all_mailbox_mappings();
1329
1330         ast_mutex_lock(&mwi_monitor.lock);
1331         mwi_monitor.stop = 1;
1332         ast_cond_signal(&mwi_monitor.cond);
1333         ast_mutex_unlock(&mwi_monitor.lock);
1334
1335         if (mwi_monitor.thread != AST_PTHREADT_NULL) {
1336                 pthread_join(mwi_monitor.thread, NULL);
1337         }
1338
1339         ast_custom_function_unregister(&smdi_msg_retrieve_function);
1340         ast_custom_function_unregister(&smdi_msg_function);
1341
1342         return 0;
1343 }
1344
1345 static int reload(void)
1346 {
1347         int res;
1348
1349         res = smdi_load(1);
1350
1351         if (res < 0) {
1352                 return res;
1353         } else if (res == 1) {
1354                 ast_log(LOG_WARNING, "No SMDI interfaces were specified to listen on, not starting SDMI listener.\n");
1355                 return 0;
1356         } else
1357                 return 0;
1358 }
1359
1360 AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_GLOBAL_SYMBOLS, "Simplified Message Desk Interface (SMDI) Resource",
1361                 .load = load_module,
1362                 .unload = unload_module,
1363                 .reload = reload,
1364                );