3cb7fa32adb62b1822baacafcb9d5eaec66bfbdd
[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(1, sizeof(*mm))))
852                 return;
853         
854         if (ast_string_field_init(mm, 32)) {
855                 free(mm);
856                 return;
857         }
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(void);
1398
1399 static int load_module(void)
1400 {
1401         int res;
1402         
1403         /* initialize our containers */
1404         memset(&smdi_ifaces, 0, sizeof(smdi_ifaces));
1405         ASTOBJ_CONTAINER_INIT(&smdi_ifaces);
1406         
1407         ast_mutex_init(&mwi_monitor.lock);
1408         ast_cond_init(&mwi_monitor.cond, NULL);
1409
1410         ast_custom_function_register(&smdi_msg_retrieve_function);
1411         ast_custom_function_register(&smdi_msg_function);
1412
1413         /* load the config and start the listener threads*/
1414         res = smdi_load(0);
1415         if (res < 0) {
1416                 unload_module();
1417                 return res;
1418         } else if (res == 1) {
1419                 unload_module();
1420                 ast_log(LOG_NOTICE, "No SMDI interfaces are available to listen on, not starting SMDI listener.\n");
1421                 return AST_MODULE_LOAD_DECLINE;
1422         }
1423         
1424         return AST_MODULE_LOAD_SUCCESS;
1425 }
1426
1427 static int unload_module(void)
1428 {
1429         /* this destructor stops any running smdi_read threads */
1430         ASTOBJ_CONTAINER_DESTROYALL(&smdi_ifaces, ast_smdi_interface_destroy);
1431         ASTOBJ_CONTAINER_DESTROY(&smdi_ifaces);
1432
1433         destroy_all_mailbox_mappings();
1434
1435         ast_mutex_lock(&mwi_monitor.lock);
1436         mwi_monitor.stop = 1;
1437         ast_cond_signal(&mwi_monitor.cond);
1438         ast_mutex_unlock(&mwi_monitor.lock);
1439
1440         if (mwi_monitor.thread != AST_PTHREADT_NULL) {
1441                 pthread_join(mwi_monitor.thread, NULL);
1442         }
1443
1444         ast_custom_function_unregister(&smdi_msg_retrieve_function);
1445         ast_custom_function_unregister(&smdi_msg_function);
1446
1447         return 0;
1448 }
1449
1450 static int reload(void)
1451 {
1452         int res;
1453
1454         res = smdi_load(1);
1455
1456         if (res < 0) {
1457                 return res;
1458         } else if (res == 1) {
1459                 ast_log(LOG_WARNING, "No SMDI interfaces were specified to listen on, not starting SDMI listener.\n");
1460                 return 0;
1461         } else
1462                 return 0;
1463 }
1464
1465 AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_GLOBAL_SYMBOLS, "Simplified Message Desk Interface (SMDI) Resource",
1466                 .load = load_module,
1467                 .unload = unload_module,
1468                 .reload = reload,
1469                );