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