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