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