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