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