The assertion that peer was not found on final event
[asterisk/asterisk.git] / apps / app_minivm.c
1 /*
2  * Asterisk -- An open source telephony toolkit.
3  *
4  * Copyright (C) 1999 - 2005, Digium, Inc.
5  * and Edvina AB, Sollentuna, Sweden
6  *
7  * Mark Spencer <markster@digium.com> (Comedian Mail)
8  * and Olle E. Johansson, Edvina.net <oej@edvina.net> (Mini-Voicemail changes)
9  *
10  * See http://www.asterisk.org for more information about
11  * the Asterisk project. Please do not directly contact
12  * any of the maintainers of this project for assistance;
13  * the project provides a web site, mailing lists and IRC
14  * channels for your use.
15  *
16  * This program is free software, distributed under the terms of
17  * the GNU General Public License Version 2. See the LICENSE file
18  * at the top of the source tree.
19  */
20
21 /*! \file
22  *
23  * \brief MiniVoiceMail - A Minimal Voicemail System for Asterisk
24  *
25  * A voicemail system in small building blocks, working together
26  * based on the Comedian Mail voicemail system (app_voicemail.c).
27  * 
28  * \par See also
29  * \arg \ref Config_minivm_examples
30  * \arg \ref App_minivm
31  *
32  * \ingroup applications
33  *
34  * \page App_minivm     Asterisk Mini-voicemail - A minimal voicemail system
35  *      
36  *      This is a minimal voicemail system, building blocks for something
37  *      else. It is built for multi-language systems.
38  *      The current version is focused on accounts where voicemail is 
39  *      forwarded to users in e-mail. It's work in progress, with loosed ends hanging
40  *      around from the old voicemail system and it's configuration.
41  *
42  *      Hopefully, we can expand this to be a full replacement of voicemail() and voicemailmain()
43  *      in the future.
44  *
45  *      Dialplan applications
46  *      - minivmRecord - record voicemail and send as e-mail ( \ref minivm_record_exec() )
47  *      - minivmGreet - Play user's greeting or default greeting ( \ref minivm_greet_exec() )
48  *      - minivmNotify - Notify user of message ( \ref minivm_notify_exec() )
49  *      - minivmDelete - Delete voicemail message ( \ref minivm_delete_exec() )
50  *      - minivmAccMess - Record personal messages (busy | unavailable | temporary)
51  *
52  *      Dialplan functions
53  *      - MINIVMACCOUNT() - A dialplan function
54  *      - MINIVMCOUNTER() - Manage voicemail-related counters for accounts or domains
55  *
56  *      CLI Commands
57  *      - minivm list accounts
58  *      - minivm list zones
59  *      - minivm list templates
60  *      - minivm show stats
61  *      - minivm show settings
62  *
63  *      Some notes
64  *      - General configuration in minivm.conf
65  *      - Users in realtime or configuration file
66  *      - Or configured on the command line with just the e-mail address
67  *              
68  *      Voicemail accounts are identified by userid and domain
69  *
70  *      Language codes are like setlocale - langcode_countrycode
71  *      \note Don't use language codes like the rest of Asterisk, two letter countrycode. Use
72  *      language_country like setlocale(). 
73  *      
74  *      Examples:
75  *              - Swedish, Sweden       sv_se
76  *              - Swedish, Finland      sv_fi
77  *              - English, USA          en_us
78  *              - English, GB           en_gb
79  *      
80  * \par See also
81  * \arg \ref Config_minivm
82  * \arg \ref Config_minivm_examples
83  * \arg \ref Minivm_directories
84  * \arg \ref app_minivm.c
85  * \arg Comedian mail: app_voicemail.c
86  * \arg \ref descrip_minivm_accmess
87  * \arg \ref descrip_minivm_greet
88  * \arg \ref descrip_minivm_record
89  * \arg \ref descrip_minivm_delete
90  * \arg \ref descrip_minivm_notify
91  *
92  * \arg \ref App_minivm_todo
93  */
94 /*! \page Minivm_directories Asterisk Mini-Voicemail Directory structure
95  *
96  *      The directory structure for storing voicemail
97  *              - AST_SPOOL_DIR - usually /var/spool/asterisk (configurable in asterisk.conf)
98  *              - MVM_SPOOL_DIR - should be configurable, usually AST_SPOOL_DIR/voicemail
99  *              - Domain        MVM_SPOOL_DIR/domain
100  *              - Username      MVM_SPOOL_DIR/domain/username
101  *                      - /greet        : Recording of account owner's name
102  *                      - /busy         : Busy message
103  *                      - /unavailable  : Unavailable message
104  *                      - /temp         : Temporary message
105  *
106  *      For account anita@localdomain.xx the account directory would as a default be
107  *              \b /var/spool/asterisk/voicemail/localdomain.xx/anita
108  *
109  *      To avoid transcoding, these sound files should be converted into several formats
110  *      They are recorded in the format closest to the incoming streams
111  *
112  *
113  * Back: \ref App_minivm
114  */
115
116 /*! \page Config_minivm_examples Example dialplan for Mini-Voicemail
117  * \section Example dialplan scripts for Mini-Voicemail
118  *  \verbinclude extensions_minivm.conf.sample
119  *
120  * Back: \ref App_minivm
121  */
122
123 /*! \page App_minivm_todo Asterisk Mini-Voicemail - todo
124  *      - configure accounts from AMI?
125  *      - test, test, test, test
126  *      - fix "vm-theextensionis.gsm" voiceprompt from Allison in various formats
127  *              "The extension you are calling"
128  *      - For trunk, consider using channel storage for information passing between small applications
129  *      - Set default directory for voicemail
130  *      - New app for creating directory for account if it does not exist
131  *      - Re-insert code for IMAP storage at some point
132  *      - Jabber integration for notifications
133  *      - Figure out how to handle video in voicemail
134  *      - Integration with the HTTP server
135  *      - New app for moving messages between mailboxes, and optionally mark it as "new"
136  *
137  *      For Asterisk 1.4/trunk
138  *      - Use string fields for minivm_account
139  *
140  * Back: \ref App_minivm
141  */
142
143 /*** MODULEINFO
144         <support_level>extended</support_level>
145  ***/
146
147 #include "asterisk.h"
148
149 ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
150
151 #include <ctype.h>
152 #include <sys/time.h>
153 #include <sys/stat.h>
154 #include <sys/mman.h>
155 #include <time.h>
156 #include <dirent.h>
157 #include <locale.h>
158
159
160 #include "asterisk/paths.h"     /* use various paths */
161 #include "asterisk/lock.h"
162 #include "asterisk/file.h"
163 #include "asterisk/channel.h"
164 #include "asterisk/pbx.h"
165 #include "asterisk/config.h"
166 #include "asterisk/say.h"
167 #include "asterisk/module.h"
168 #include "asterisk/app.h"
169 #include "asterisk/dsp.h"
170 #include "asterisk/localtime.h"
171 #include "asterisk/cli.h"
172 #include "asterisk/utils.h"
173 #include "asterisk/linkedlists.h"
174 #include "asterisk/callerid.h"
175 #include "asterisk/stasis.h"
176 #include "asterisk/stasis_channels.h"
177 #include "asterisk/json.h"
178
179 /*** DOCUMENTATION
180 <application name="MinivmRecord" language="en_US">
181         <synopsis>
182                 Receive Mini-Voicemail and forward via e-mail.
183         </synopsis>
184         <syntax>
185                 <parameter name="mailbox" required="true" argsep="@">
186                         <argument name="username" required="true">
187                                 <para>Voicemail username</para>
188                         </argument>
189                         <argument name="domain" required="true">
190                                 <para>Voicemail domain</para>
191                         </argument>
192                 </parameter>
193                 <parameter name="options" required="false">
194                         <optionlist>
195                                 <option name="0">
196                                         <para>Jump to the <literal>o</literal> extension in the current dialplan context.</para>
197                                 </option>
198                                 <option name="*">
199                                         <para>Jump to the <literal>a</literal> extension in the current dialplan context.</para>
200                                 </option>
201                                 <option name="g">
202                                         <argument name="gain">
203                                                 <para>Amount of gain to use</para>
204                                         </argument>
205                                         <para>Use the specified amount of gain when recording the voicemail message.
206                                         The units are whole-number decibels (dB).</para>
207                                 </option>
208                         </optionlist>
209                 </parameter>
210         </syntax>
211         <description>
212                 <para>This application is part of the Mini-Voicemail system, configured in <filename>minivm.conf</filename></para>
213                 <para>MiniVM records audio file in configured format and forwards message to e-mail and pager.</para>
214                 <para>If there's no user account for that address, a temporary account will be used with default options.</para>
215                 <para>The recorded file name and path will be stored in <variable>MVM_FILENAME</variable> and the duration
216                 of the message will be stored in <variable>MVM_DURATION</variable></para>
217                 <note><para>If the caller hangs up after the recording, the only way to send the message and clean up is to
218                 execute in the <literal>h</literal> extension. The application will exit if any of the following DTMF digits
219                 are received and the requested extension exist in the current context.</para></note>
220                 <variablelist>
221                         <variable name="MVM_RECORD_STATUS">
222                                 <para>This is the status of the record operation</para>
223                                 <value name="SUCCESS" />
224                                 <value name="USEREXIT" />
225                                 <value name="FAILED" />
226                         </variable>
227                 </variablelist>
228         </description>
229 </application>
230 <application name="MinivmGreet" language="en_US">
231         <synopsis>
232                 Play Mini-Voicemail prompts.
233         </synopsis>
234         <syntax>
235                 <parameter name="mailbox" required="true" argsep="@">
236                         <argument name="username" required="true">
237                                 <para>Voicemail username</para>
238                         </argument>
239                         <argument name="domain" required="true">
240                                 <para>Voicemail domain</para>
241                         </argument>
242                 </parameter>
243                 <parameter name="options" required="false">
244                         <optionlist>
245                                 <option name="b">
246                                         <para>Play the <literal>busy</literal> greeting to the calling party.</para>
247                                 </option>
248                                 <option name="s">
249                                         <para>Skip the playback of instructions for leaving a message to the calling party.</para>
250                                 </option>
251                                 <option name="u">
252                                         <para>Play the <literal>unavailable</literal> greeting.</para>
253                                 </option>
254                         </optionlist>
255                 </parameter>
256         </syntax>
257         <description>
258                 <para>This application is part of the Mini-Voicemail system, configured in minivm.conf.</para>
259                 <para>MinivmGreet() plays default prompts or user specific prompts for an account.</para>
260                 <para>Busy and unavailable messages can be choosen, but will be overridden if a temporary
261                 message exists for the account.</para>
262                 <variablelist>
263                         <variable name="MVM_GREET_STATUS">
264                                 <para>This is the status of the greeting playback.</para>
265                                 <value name="SUCCESS" />
266                                 <value name="USEREXIT" />
267                                 <value name="FAILED" />
268                         </variable>
269                 </variablelist>
270         </description>
271 </application>
272 <application name="MinivmNotify" language="en_US">
273         <synopsis>
274                 Notify voicemail owner about new messages.
275         </synopsis>
276         <syntax>
277                 <parameter name="mailbox" required="true" argsep="@">
278                         <argument name="username" required="true">
279                                 <para>Voicemail username</para>
280                         </argument>
281                         <argument name="domain" required="true">
282                                 <para>Voicemail domain</para>
283                         </argument>
284                 </parameter>
285                 <parameter name="options" required="false">
286                         <optionlist>
287                                 <option name="template">
288                                         <para>E-mail template to use for voicemail notification</para>
289                                 </option>
290                         </optionlist>
291                 </parameter>
292         </syntax>
293         <description>
294                 <para>This application is part of the Mini-Voicemail system, configured in minivm.conf.</para>
295                 <para>MiniVMnotify forwards messages about new voicemail to e-mail and pager. If there's no user
296                 account for that address, a temporary account will be used with default options (set in
297                 <filename>minivm.conf</filename>).</para>
298                 <para>If the channel variable <variable>MVM_COUNTER</variable> is set, this will be used in the message
299                 file name and available in the template for the message.</para>
300                 <para>If no template is given, the default email template will be used to send email and default pager
301                 template to send paging message (if the user account is configured with a paging address.</para>
302                 <variablelist>
303                         <variable name="MVM_NOTIFY_STATUS">
304                                 <para>This is the status of the notification attempt</para>
305                                 <value name="SUCCESS" />
306                                 <value name="FAILED" />
307                         </variable>
308                 </variablelist>
309         </description>
310 </application>
311 <application name="MinivmDelete" language="en_US">
312         <synopsis>
313                 Delete Mini-Voicemail voicemail messages.
314         </synopsis>
315         <syntax>
316                 <parameter name="filename" required="true">
317                         <para>File to delete</para>
318                 </parameter>
319         </syntax>
320         <description>
321                 <para>This application is part of the Mini-Voicemail system, configured in <filename>minivm.conf</filename>.</para>
322                 <para>It deletes voicemail file set in MVM_FILENAME or given filename.</para>
323                 <variablelist>
324                         <variable name="MVM_DELETE_STATUS">
325                                 <para>This is the status of the delete operation.</para>
326                                 <value name="SUCCESS" />
327                                 <value name="FAILED" />
328                         </variable>
329                 </variablelist>
330         </description>
331 </application>
332
333 <application name="MinivmAccMess" language="en_US">
334         <synopsis>
335                 Record account specific messages.
336         </synopsis>
337         <syntax>
338                 <parameter name="mailbox" required="true" argsep="@">
339                         <argument name="username" required="true">
340                                 <para>Voicemail username</para>
341                         </argument>
342                         <argument name="domain" required="true">
343                                 <para>Voicemail domain</para>
344                         </argument>
345                 </parameter>
346                 <parameter name="options" required="false">
347                         <optionlist>
348                                 <option name="u">
349                                         <para>Record the <literal>unavailable</literal> greeting.</para>
350                                 </option>
351                                 <option name="b">
352                                         <para>Record the <literal>busy</literal> greeting.</para>
353                                 </option>
354                                 <option name="t">
355                                         <para>Record the temporary greeting.</para>
356                                 </option>
357                                 <option name="n">
358                                         <para>Account name.</para>
359                                 </option>
360                         </optionlist>
361                 </parameter>
362         </syntax>
363         <description>
364                 <para>This application is part of the Mini-Voicemail system, configured in <filename>minivm.conf</filename>.</para>
365                 <para>Use this application to record account specific audio/video messages for busy, unavailable
366                 and temporary messages.</para>
367                 <para>Account specific directories will be created if they do not exist.</para>
368                 <variablelist>
369                         <variable name="MVM_ACCMESS_STATUS">
370                                 <para>This is the result of the attempt to record the specified greeting.</para>
371                                 <para><literal>FAILED</literal> is set if the file can't be created.</para>
372                                 <value name="SUCCESS" />
373                                 <value name="FAILED" />
374                         </variable>
375                 </variablelist>
376         </description>
377 </application>
378 <application name="MinivmMWI" language="en_US">
379         <synopsis>
380                 Send Message Waiting Notification to subscriber(s) of mailbox.
381         </synopsis>
382         <syntax>
383                 <parameter name="mailbox" required="true" argsep="@">
384                         <argument name="username" required="true">
385                                 <para>Voicemail username</para>
386                         </argument>
387                         <argument name="domain" required="true">
388                                 <para>Voicemail domain</para>
389                         </argument>
390                 </parameter>
391                 <parameter name="urgent" required="true">
392                         <para>Number of urgent messages in mailbox.</para>
393                 </parameter>
394                 <parameter name="new" required="true">
395                         <para>Number of new messages in mailbox.</para>
396                 </parameter>
397                 <parameter name="old" required="true">
398                         <para>Number of old messages in mailbox.</para>
399                 </parameter>
400         </syntax>
401         <description>
402                 <para>This application is part of the Mini-Voicemail system, configured in <filename>minivm.conf</filename>.</para>
403                 <para>MinivmMWI is used to send message waiting indication to any devices whose channels have
404                 subscribed to the mailbox passed in the first parameter.</para>
405         </description>
406 </application>
407 <function name="MINIVMCOUNTER" language="en_US">
408         <synopsis>
409                 Reads or sets counters for MiniVoicemail message.
410         </synopsis>
411         <syntax argsep=":">
412                 <parameter name="account" required="true">
413                         <para>If account is given and it exists, the counter is specific for the account.</para>
414                         <para>If account is a domain and the domain directory exists, counters are specific for a domain.</para>
415                 </parameter>
416                 <parameter name="name" required="true">
417                         <para>The name of the counter is a string, up to 10 characters.</para>
418                 </parameter>
419                 <parameter name="operand">
420                         <para>The counters never goes below zero. Valid operands for changing the value of a counter when assigning a value are:</para>
421                         <enumlist>
422                                 <enum name="i"><para>Increment by value.</para></enum>
423                                 <enum name="d"><para>Decrement by value.</para></enum>
424                                 <enum name="s"><para>Set to value.</para></enum>
425                         </enumlist>
426                 </parameter>
427         </syntax>
428         <description>
429                 <para>The operation is atomic and the counter is locked while changing the value. The counters are stored as text files in the minivm account directories. It might be better to use realtime functions if you are using a database to operate your Asterisk.</para>
430         </description>
431         <see-also>
432                 <ref type="application">MinivmRecord</ref>
433                 <ref type="application">MinivmGreet</ref>
434                 <ref type="application">MinivmNotify</ref>
435                 <ref type="application">MinivmDelete</ref>
436                 <ref type="application">MinivmAccMess</ref>
437                 <ref type="application">MinivmMWI</ref>
438                 <ref type="function">MINIVMACCOUNT</ref>
439         </see-also>
440 </function>
441 <function name="MINIVMACCOUNT" language="en_US">
442         <synopsis>
443                 Gets MiniVoicemail account information.
444         </synopsis>
445         <syntax argsep=":">
446                 <parameter name="account" required="true" />
447                 <parameter name="item" required="true">
448                         <para>Valid items are:</para>
449                         <enumlist>
450                                 <enum name="path">
451                                         <para>Path to account mailbox (if account exists, otherwise temporary mailbox).</para>
452                                 </enum>
453                                 <enum name="hasaccount">
454                                         <para>1 is static Minivm account exists, 0 otherwise.</para>
455                                 </enum>
456                                 <enum name="fullname">
457                                         <para>Full name of account owner.</para>
458                                 </enum>
459                                 <enum name="email">
460                                         <para>Email address used for account.</para>
461                                 </enum>
462                                 <enum name="etemplate">
463                                         <para>Email template for account (default template if none is configured).</para>
464                                 </enum>
465                                 <enum name="ptemplate">
466                                         <para>Pager template for account (default template if none is configured).</para>
467                                 </enum>
468                                 <enum name="accountcode">
469                                         <para>Account code for the voicemail account.</para>
470                                 </enum>
471                                 <enum name="pincode">
472                                         <para>Pin code for voicemail account.</para>
473                                 </enum>
474                                 <enum name="timezone">
475                                         <para>Time zone for voicemail account.</para>
476                                 </enum>
477                                 <enum name="language">
478                                         <para>Language for voicemail account.</para>
479                                 </enum>
480                                 <enum name="&lt;channel variable name&gt;">
481                                         <para>Channel variable value (set in configuration for account).</para>
482                                 </enum>
483                         </enumlist>
484                 </parameter>
485         </syntax>
486         <description>
487                 <para />
488         </description>
489         <see-also>
490                 <ref type="application">MinivmRecord</ref>
491                 <ref type="application">MinivmGreet</ref>
492                 <ref type="application">MinivmNotify</ref>
493                 <ref type="application">MinivmDelete</ref>
494                 <ref type="application">MinivmAccMess</ref>
495                 <ref type="application">MinivmMWI</ref>
496                 <ref type="function">MINIVMCOUNTER</ref>
497         </see-also>
498 </function>
499         <managerEvent language="en_US" name="MiniVoiceMail">
500                 <managerEventInstance class="EVENT_FLAG_CALL">
501                         <synopsis>Raised when a notification is sent out by a MiniVoiceMail application</synopsis>
502                         <syntax>
503                                 <channel_snapshot/>
504                                 <parameter name="Action">
505                                         <para>What action was taken. Currently, this will always be <literal>SentNotification</literal></para>
506                                 </parameter>
507                                 <parameter name="Mailbox">
508                                         <para>The mailbox that the notification was about, specified as <literal>mailbox</literal>@<literal>context</literal></para>
509                                 </parameter>
510                                 <parameter name="Counter">
511                                         <para>A message counter derived from the <literal>MVM_COUNTER</literal> channel variable.</para>
512                                 </parameter>
513                         </syntax>
514                 </managerEventInstance>
515         </managerEvent>
516 ***/
517
518 #ifndef TRUE
519 #define TRUE 1
520 #endif
521 #ifndef FALSE
522 #define FALSE 0
523 #endif
524
525
526 #define MVM_REVIEW              (1 << 0)        /*!< Review message */
527 #define MVM_OPERATOR            (1 << 1)        /*!< Operator exit during voicemail recording */
528 #define MVM_REALTIME            (1 << 2)        /*!< This user is a realtime account */
529 #define MVM_SVMAIL              (1 << 3)
530 #define MVM_ENVELOPE            (1 << 4)
531 #define MVM_PBXSKIP             (1 << 9)
532 #define MVM_ALLOCED             (1 << 13)
533
534 /*! \brief Default mail command to mail voicemail. Change it with the
535     mailcmd= command in voicemail.conf */
536 #define SENDMAIL "/usr/sbin/sendmail -t"
537
538 #define SOUND_INTRO             "vm-intro"
539 #define B64_BASEMAXINLINE       256     /*!< Buffer size for Base 64 attachment encoding */
540 #define B64_BASELINELEN         72      /*!< Line length for Base 64 endoded messages */
541 #define EOL                     "\r\n"
542
543 #define MAX_DATETIME_FORMAT     512
544 #define MAX_NUM_CID_CONTEXTS    10
545
546 #define ERROR_LOCK_PATH         -100
547 #define VOICEMAIL_DIR_MODE      0700
548
549 #define VOICEMAIL_CONFIG "minivm.conf"
550 #define ASTERISK_USERNAME "asterisk"    /*!< Default username for sending mail is asterisk\@localhost */
551
552 /*! \brief Message types for notification */
553 enum mvm_messagetype {
554         MVM_MESSAGE_EMAIL,
555         MVM_MESSAGE_PAGE
556         /* For trunk: MVM_MESSAGE_JABBER, */
557 };
558
559 static char MVM_SPOOL_DIR[PATH_MAX];
560
561 /* Module declarations */
562 static char *app_minivm_record = "MinivmRecord";        /* Leave a message */
563 static char *app_minivm_greet = "MinivmGreet";          /* Play voicemail prompts */
564 static char *app_minivm_notify = "MinivmNotify";        /* Notify about voicemail by using one of several methods */
565 static char *app_minivm_delete = "MinivmDelete";        /* Notify about voicemail by using one of several methods */
566 static char *app_minivm_accmess = "MinivmAccMess";      /* Record personal voicemail messages */
567 static char *app_minivm_mwi = "MinivmMWI";
568
569
570
571 enum minivm_option_flags {
572         OPT_SILENT =       (1 << 0),
573         OPT_BUSY_GREETING =    (1 << 1),
574         OPT_UNAVAIL_GREETING = (1 << 2),
575         OPT_TEMP_GREETING = (1 << 3),
576         OPT_NAME_GREETING = (1 << 4),
577         OPT_RECORDGAIN =  (1 << 5),
578 };
579
580 enum minivm_option_args {
581         OPT_ARG_RECORDGAIN = 0,
582         OPT_ARG_ARRAY_SIZE = 1,
583 };
584
585 AST_APP_OPTIONS(minivm_app_options, {
586         AST_APP_OPTION('s', OPT_SILENT),
587         AST_APP_OPTION('b', OPT_BUSY_GREETING),
588         AST_APP_OPTION('u', OPT_UNAVAIL_GREETING),
589         AST_APP_OPTION_ARG('g', OPT_RECORDGAIN, OPT_ARG_RECORDGAIN),
590 });
591
592 AST_APP_OPTIONS(minivm_accmess_options, {
593         AST_APP_OPTION('b', OPT_BUSY_GREETING),
594         AST_APP_OPTION('u', OPT_UNAVAIL_GREETING),
595         AST_APP_OPTION('t', OPT_TEMP_GREETING),
596         AST_APP_OPTION('n', OPT_NAME_GREETING),
597 });
598
599 /*!\internal
600  * \brief Structure for linked list of Mini-Voicemail users: \ref minivm_accounts */
601 struct minivm_account {
602         char username[AST_MAX_CONTEXT]; /*!< Mailbox username */
603         char domain[AST_MAX_CONTEXT];   /*!< Voicemail domain */
604
605         char pincode[10];               /*!< Secret pin code, numbers only */
606         char fullname[120];             /*!< Full name, for directory app */
607         char email[80];                 /*!< E-mail address - override */
608         char pager[80];                 /*!< E-mail address to pager (no attachment) */
609         char accountcode[AST_MAX_ACCOUNT_CODE]; /*!< Voicemail account account code */
610         char serveremail[80];           /*!< From: Mail address */
611         char externnotify[160];         /*!< Configurable notification command */
612         char language[MAX_LANGUAGE];    /*!< Config: Language setting */
613         char zonetag[80];               /*!< Time zone */
614         char uniqueid[20];              /*!< Unique integer identifier */
615         char exit[80];                  /*!< Options for exiting from voicemail() */
616         char attachfmt[80];             /*!< Format for voicemail audio file attachment */
617         char etemplate[80];             /*!< Pager template */
618         char ptemplate[80];             /*!< Voicemail format */
619         unsigned int flags;             /*!< MVM_ flags */
620         struct ast_variable *chanvars;  /*!< Variables for e-mail template */
621         double volgain;                 /*!< Volume gain for voicemails sent via e-mail */
622         AST_LIST_ENTRY(minivm_account) list;
623 };
624
625 /*!\internal
626  * \brief The list of e-mail accounts */
627 static AST_LIST_HEAD_STATIC(minivm_accounts, minivm_account);
628
629 /*!\internal
630  * \brief Linked list of e-mail templates in various languages
631  * These are used as templates for e-mails, pager messages and jabber messages
632  * \ref message_templates
633 */
634 struct minivm_template {
635         char    name[80];               /*!< Template name */
636         char    *body;                  /*!< Body of this template */
637         char    fromaddress[100];       /*!< Who's sending the e-mail? */
638         char    serveremail[80];        /*!< From: Mail address */
639         char    subject[100];           /*!< Subject line */
640         char    charset[32];            /*!< Default character set for this template */
641         char    locale[20];             /*!< Locale for setlocale() */
642         char    dateformat[80];         /*!< Date format to use in this attachment */
643         int     attachment;             /*!< Attachment of media yes/no - no for pager messages */
644         AST_LIST_ENTRY(minivm_template) list;   /*!< List mechanics */
645 };
646
647 /*! \brief The list of e-mail templates */
648 static AST_LIST_HEAD_STATIC(message_templates, minivm_template);
649
650 /*! \brief Options for leaving voicemail with the voicemail() application */
651 struct leave_vm_options {
652         unsigned int flags;
653         signed char record_gain;
654 };
655
656 /*! \brief Structure for base64 encoding */
657 struct b64_baseio {
658         int iocp;
659         int iolen;
660         int linelength;
661         int ateof;
662         unsigned char iobuf[B64_BASEMAXINLINE];
663 };
664
665 /*! \brief Voicemail time zones */
666 struct minivm_zone {
667         char name[80];                          /*!< Name of this time zone */
668         char timezone[80];                      /*!< Timezone definition */
669         char msg_format[BUFSIZ];                /*!< Not used in minivm ...yet */
670         AST_LIST_ENTRY(minivm_zone) list;       /*!< List mechanics */
671 };
672
673 /*! \brief The list of e-mail time zones */
674 static AST_LIST_HEAD_STATIC(minivm_zones, minivm_zone);
675
676 /*! \brief Structure for gathering statistics */
677 struct minivm_stats {
678         int voicemailaccounts;          /*!< Number of static accounts */
679         int timezones;                  /*!< Number of time zones */
680         int templates;                  /*!< Number of templates */
681
682         struct timeval reset;                   /*!< Time for last reset */
683         int receivedmessages;           /*!< Number of received messages since reset */
684         struct timeval lastreceived;            /*!< Time for last voicemail sent */
685 };
686
687 /*! \brief Statistics for voicemail */
688 static struct minivm_stats global_stats;
689
690 AST_MUTEX_DEFINE_STATIC(minivmlock);    /*!< Lock to protect voicemail system */
691 AST_MUTEX_DEFINE_STATIC(minivmloglock); /*!< Lock to protect voicemail system log file */
692
693 static FILE *minivmlogfile;             /*!< The minivm log file */
694
695 static int global_vmminmessage;         /*!< Minimum duration of messages */
696 static int global_vmmaxmessage;         /*!< Maximum duration of message */
697 static int global_maxsilence;           /*!< Maximum silence during recording */
698 static int global_maxgreet;             /*!< Maximum length of prompts  */
699 static int global_silencethreshold = 128;
700 static char global_mailcmd[160];        /*!< Configurable mail cmd */
701 static char global_externnotify[160];   /*!< External notification application */
702 static char global_logfile[PATH_MAX];   /*!< Global log file for messages */
703 static char default_vmformat[80];
704
705 static struct ast_flags globalflags = {0};      /*!< Global voicemail flags */
706 static int global_saydurationminfo;
707
708 static double global_volgain;   /*!< Volume gain for voicmemail via e-mail */
709
710 /*!\internal
711  * \brief Default dateformat, can be overridden in configuration file */
712 #define DEFAULT_DATEFORMAT      "%A, %B %d, %Y at %r"
713 #define DEFAULT_CHARSET         "ISO-8859-1"
714
715 /* Forward declarations */
716 static char *message_template_parse_filebody(const char *filename);
717 static char *message_template_parse_emailbody(const char *body);
718 static int create_vmaccount(char *name, struct ast_variable *var, int realtime);
719 static struct minivm_account *find_user_realtime(const char *domain, const char *username);
720 static char *handle_minivm_reload(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a);
721
722 /*!\internal
723  * \brief Create message template */
724 static struct minivm_template *message_template_create(const char *name)
725 {
726         struct minivm_template *template;
727
728         template = ast_calloc(1, sizeof(*template));
729         if (!template)
730                 return NULL;
731
732         /* Set some defaults for templates */
733         ast_copy_string(template->name, name, sizeof(template->name));
734         ast_copy_string(template->dateformat, DEFAULT_DATEFORMAT, sizeof(template->dateformat));
735         ast_copy_string(template->charset, DEFAULT_CHARSET, sizeof(template->charset));
736         ast_copy_string(template->subject, "New message in mailbox ${MVM_USERNAME}@${MVM_DOMAIN}", sizeof(template->subject));
737         template->attachment = TRUE;
738
739         return template;
740 }
741
742 /*!\internal
743  * \brief Release memory allocated by message template */
744 static void message_template_free(struct minivm_template *template)
745 {
746         if (template->body)
747                 ast_free(template->body);
748
749         ast_free (template);
750 }
751
752 /*!\internal
753  * \brief Build message template from configuration */
754 static int message_template_build(const char *name, struct ast_variable *var)
755 {
756         struct minivm_template *template;
757         int error = 0;
758
759         template = message_template_create(name);
760         if (!template) {
761                 ast_log(LOG_ERROR, "Out of memory, can't allocate message template object %s.\n", name);
762                 return -1;
763         }
764
765         while (var) {
766                 ast_debug(3, "Configuring template option %s = \"%s\" for template %s\n", var->name, var->value, name);
767                 if (!strcasecmp(var->name, "fromaddress")) {
768                         ast_copy_string(template->fromaddress, var->value, sizeof(template->fromaddress));
769                 } else if (!strcasecmp(var->name, "fromemail")) {
770                         ast_copy_string(template->serveremail, var->value, sizeof(template->serveremail));
771                 } else if (!strcasecmp(var->name, "subject")) {
772                         ast_copy_string(template->subject, var->value, sizeof(template->subject));
773                 } else if (!strcasecmp(var->name, "locale")) {
774                         ast_copy_string(template->locale, var->value, sizeof(template->locale));
775                 } else if (!strcasecmp(var->name, "attachmedia")) {
776                         template->attachment = ast_true(var->value);
777                 } else if (!strcasecmp(var->name, "dateformat")) {
778                         ast_copy_string(template->dateformat, var->value, sizeof(template->dateformat));
779                 } else if (!strcasecmp(var->name, "charset")) {
780                         ast_copy_string(template->charset, var->value, sizeof(template->charset));
781                 } else if (!strcasecmp(var->name, "templatefile")) {
782                         if (template->body) 
783                                 ast_free(template->body);
784                         template->body = message_template_parse_filebody(var->value);
785                         if (!template->body) {
786                                 ast_log(LOG_ERROR, "Error reading message body definition file %s\n", var->value);
787                                 error++;
788                         }
789                 } else if (!strcasecmp(var->name, "messagebody")) {
790                         if (template->body) 
791                                 ast_free(template->body);
792                         template->body = message_template_parse_emailbody(var->value);
793                         if (!template->body) {
794                                 ast_log(LOG_ERROR, "Error parsing message body definition:\n          %s\n", var->value);
795                                 error++;
796                         }
797                 } else {
798                         ast_log(LOG_ERROR, "Unknown message template configuration option \"%s=%s\"\n", var->name, var->value);
799                         error++;
800                 }
801                 var = var->next;
802         }
803         if (error)
804                 ast_log(LOG_ERROR, "-- %d errors found parsing message template definition %s\n", error, name);
805
806         AST_LIST_LOCK(&message_templates);
807         AST_LIST_INSERT_TAIL(&message_templates, template, list);
808         AST_LIST_UNLOCK(&message_templates);
809
810         global_stats.templates++;
811
812         return error;
813 }
814
815 /*!\internal
816  * \brief Find named template */
817 static struct minivm_template *message_template_find(const char *name)
818 {
819         struct minivm_template *this, *res = NULL;
820
821         if (ast_strlen_zero(name))
822                 return NULL;
823
824         AST_LIST_LOCK(&message_templates);
825         AST_LIST_TRAVERSE(&message_templates, this, list) {
826                 if (!strcasecmp(this->name, name)) {
827                         res = this;
828                         break;
829                 }
830         }
831         AST_LIST_UNLOCK(&message_templates);
832
833         return res;
834 }
835
836
837 /*!\internal
838  * \brief Clear list of templates */
839 static void message_destroy_list(void)
840 {
841         struct minivm_template *this;
842         AST_LIST_LOCK(&message_templates);
843         while ((this = AST_LIST_REMOVE_HEAD(&message_templates, list))) {
844                 message_template_free(this);
845         }
846
847         AST_LIST_UNLOCK(&message_templates);
848 }
849
850 /*!\internal
851  * \brief read buffer from file (base64 conversion) */
852 static int b64_inbuf(struct b64_baseio *bio, FILE *fi)
853 {
854         int l;
855
856         if (bio->ateof)
857                 return 0;
858
859         if ((l = fread(bio->iobuf, 1, B64_BASEMAXINLINE,fi)) <= 0) {
860                 if (ferror(fi))
861                         return -1;
862
863                 bio->ateof = 1;
864                 return 0;
865         }
866
867         bio->iolen= l;
868         bio->iocp= 0;
869
870         return 1;
871 }
872
873 /*!\internal
874  * \brief read character from file to buffer (base64 conversion) */
875 static int b64_inchar(struct b64_baseio *bio, FILE *fi)
876 {
877         if (bio->iocp >= bio->iolen) {
878                 if (!b64_inbuf(bio, fi))
879                         return EOF;
880         }
881
882         return bio->iobuf[bio->iocp++];
883 }
884
885 /*!\internal
886  * \brief write buffer to file (base64 conversion) */
887 static int b64_ochar(struct b64_baseio *bio, int c, FILE *so)
888 {
889         if (bio->linelength >= B64_BASELINELEN) {
890                 if (fputs(EOL,so) == EOF)
891                         return -1;
892
893                 bio->linelength= 0;
894         }
895
896         if (putc(((unsigned char) c), so) == EOF)
897                 return -1;
898
899         bio->linelength++;
900
901         return 1;
902 }
903
904 /*!\internal
905  * \brief Encode file to base64 encoding for email attachment (base64 conversion) */
906 static int base_encode(char *filename, FILE *so)
907 {
908         unsigned char dtable[B64_BASEMAXINLINE];
909         int i,hiteof= 0;
910         FILE *fi;
911         struct b64_baseio bio;
912
913         memset(&bio, 0, sizeof(bio));
914         bio.iocp = B64_BASEMAXINLINE;
915
916         if (!(fi = fopen(filename, "rb"))) {
917                 ast_log(LOG_WARNING, "Failed to open file: %s: %s\n", filename, strerror(errno));
918                 return -1;
919         }
920
921         for (i= 0; i<9; i++) {
922                 dtable[i]= 'A'+i;
923                 dtable[i+9]= 'J'+i;
924                 dtable[26+i]= 'a'+i;
925                 dtable[26+i+9]= 'j'+i;
926         }
927         for (i= 0; i < 8; i++) {
928                 dtable[i+18]= 'S'+i;
929                 dtable[26+i+18]= 's'+i;
930         }
931         for (i= 0; i < 10; i++) {
932                 dtable[52+i]= '0'+i;
933         }
934         dtable[62]= '+';
935         dtable[63]= '/';
936
937         while (!hiteof){
938                 unsigned char igroup[3], ogroup[4];
939                 int c,n;
940
941                 igroup[0]= igroup[1]= igroup[2]= 0;
942
943                 for (n= 0; n < 3; n++) {
944                         if ((c = b64_inchar(&bio, fi)) == EOF) {
945                                 hiteof= 1;
946                                 break;
947                         }
948                         igroup[n]= (unsigned char)c;
949                 }
950
951                 if (n> 0) {
952                         ogroup[0]= dtable[igroup[0]>>2];
953                         ogroup[1]= dtable[((igroup[0]&3)<<4) | (igroup[1]>>4)];
954                         ogroup[2]= dtable[((igroup[1]&0xF)<<2) | (igroup[2]>>6)];
955                         ogroup[3]= dtable[igroup[2]&0x3F];
956
957                         if (n<3) {
958                                 ogroup[3]= '=';
959
960                                 if (n<2)
961                                         ogroup[2]= '=';
962                         }
963
964                         for (i= 0;i<4;i++)
965                                 b64_ochar(&bio, ogroup[i], so);
966                 }
967         }
968
969         /* Put end of line - line feed */
970         if (fputs(EOL, so) == EOF)
971                 return 0;
972
973         fclose(fi);
974
975         return 1;
976 }
977
978 static int get_date(char *s, int len)
979 {
980         struct ast_tm tm;
981         struct timeval now = ast_tvnow();
982
983         ast_localtime(&now, &tm, NULL);
984         return ast_strftime(s, len, "%a %b %e %r %Z %Y", &tm);
985 }
986
987
988 /*!\internal
989  * \brief Free user structure - if it's allocated */
990 static void free_user(struct minivm_account *vmu)
991 {
992         if (vmu->chanvars)
993                 ast_variables_destroy(vmu->chanvars);
994         ast_free(vmu);
995 }
996
997
998
999 /*!\internal
1000  * \brief Prepare for voicemail template by adding channel variables
1001  * to the channel
1002 */
1003 static void prep_email_sub_vars(struct ast_channel *channel, const struct minivm_account *vmu, const char *cidnum, const char *cidname, const char *dur, const char *date, const char *counter)
1004 {
1005         char callerid[256];
1006         struct ast_variable *var;
1007         
1008         if (!channel) {
1009                 ast_log(LOG_ERROR, "No allocated channel, giving up...\n");
1010                 return;
1011         }
1012
1013         for (var = vmu->chanvars ; var ; var = var->next) {
1014                 pbx_builtin_setvar_helper(channel, var->name, var->value);
1015         }
1016
1017         /* Prepare variables for substition in email body and subject */
1018         pbx_builtin_setvar_helper(channel, "MVM_NAME", vmu->fullname);
1019         pbx_builtin_setvar_helper(channel, "MVM_DUR", dur);
1020         pbx_builtin_setvar_helper(channel, "MVM_DOMAIN", vmu->domain);
1021         pbx_builtin_setvar_helper(channel, "MVM_USERNAME", vmu->username);
1022         pbx_builtin_setvar_helper(channel, "MVM_CALLERID", ast_callerid_merge(callerid, sizeof(callerid), cidname, cidnum, "Unknown Caller"));
1023         pbx_builtin_setvar_helper(channel, "MVM_CIDNAME", (cidname ? cidname : "an unknown caller"));
1024         pbx_builtin_setvar_helper(channel, "MVM_CIDNUM", (cidnum ? cidnum : "an unknown caller"));
1025         pbx_builtin_setvar_helper(channel, "MVM_DATE", date);
1026         if (!ast_strlen_zero(counter))
1027                 pbx_builtin_setvar_helper(channel, "MVM_COUNTER", counter);
1028 }
1029
1030 /*!\internal
1031  * \brief Set default values for Mini-Voicemail users */
1032 static void populate_defaults(struct minivm_account *vmu)
1033 {
1034         ast_copy_flags(vmu, (&globalflags), AST_FLAGS_ALL);     
1035         ast_copy_string(vmu->attachfmt, default_vmformat, sizeof(vmu->attachfmt));
1036         vmu->volgain = global_volgain;
1037 }
1038
1039 /*!\internal
1040  * \brief Allocate new vm user and set default values */
1041 static struct minivm_account *mvm_user_alloc(void)
1042 {
1043         struct minivm_account *new;
1044
1045         new = ast_calloc(1, sizeof(*new));
1046         if (!new)
1047                 return NULL;
1048         populate_defaults(new);
1049
1050         return new;
1051 }
1052
1053
1054 /*!\internal
1055  * \brief Clear list of users */
1056 static void vmaccounts_destroy_list(void)
1057 {
1058         struct minivm_account *this;
1059         AST_LIST_LOCK(&minivm_accounts);
1060         while ((this = AST_LIST_REMOVE_HEAD(&minivm_accounts, list))) 
1061                 ast_free(this);
1062         AST_LIST_UNLOCK(&minivm_accounts);
1063 }
1064
1065
1066 /*!\internal
1067  * \brief Find user from static memory object list */
1068 static struct minivm_account *find_account(const char *domain, const char *username, int createtemp)
1069 {
1070         struct minivm_account *vmu = NULL, *cur;
1071
1072
1073         if (ast_strlen_zero(domain) || ast_strlen_zero(username)) {
1074                 ast_log(LOG_NOTICE, "No username or domain? \n");
1075                 return NULL;
1076         }
1077         ast_debug(3, "Looking for voicemail user %s in domain %s\n", username, domain);
1078
1079         AST_LIST_LOCK(&minivm_accounts);
1080         AST_LIST_TRAVERSE(&minivm_accounts, cur, list) {
1081                 /* Is this the voicemail account we're looking for? */
1082                 if (!strcasecmp(domain, cur->domain) && !strcasecmp(username, cur->username))
1083                         break;
1084         }
1085         AST_LIST_UNLOCK(&minivm_accounts);
1086
1087         if (cur) {
1088                 ast_debug(3, "Found account for %s@%s\n", username, domain);
1089                 vmu = cur;
1090
1091         } else
1092                 vmu = find_user_realtime(domain, username);
1093
1094         if (createtemp && !vmu) {
1095                 /* Create a temporary user, send e-mail and be gone */
1096                 vmu = mvm_user_alloc();
1097                 ast_set2_flag(vmu, TRUE, MVM_ALLOCED);  
1098                 if (vmu) {
1099                         ast_copy_string(vmu->username, username, sizeof(vmu->username));
1100                         ast_copy_string(vmu->domain, domain, sizeof(vmu->domain));
1101                         ast_debug(1, "Created temporary account\n");
1102                 }
1103
1104         }
1105         return vmu;
1106 }
1107
1108 /*!\internal
1109  * \brief Find user in realtime storage
1110  * \return pointer to minivm_account structure
1111 */
1112 static struct minivm_account *find_user_realtime(const char *domain, const char *username)
1113 {
1114         struct ast_variable *var;
1115         struct minivm_account *retval;
1116         char name[MAXHOSTNAMELEN];
1117
1118         retval = mvm_user_alloc();
1119         if (!retval)
1120                 return NULL;
1121
1122         if (username) 
1123                 ast_copy_string(retval->username, username, sizeof(retval->username));
1124
1125         populate_defaults(retval);
1126         var = ast_load_realtime("minivm", "username", username, "domain", domain, SENTINEL);
1127
1128         if (!var) {
1129                 ast_free(retval);
1130                 return NULL;
1131         }
1132
1133         snprintf(name, sizeof(name), "%s@%s", username, domain);
1134         create_vmaccount(name, var, TRUE);
1135
1136         ast_variables_destroy(var);
1137         return retval;
1138 }
1139
1140 /*!\internal
1141  * \brief Check if the string would need encoding within the MIME standard, to
1142  * avoid confusing certain mail software that expects messages to be 7-bit
1143  * clean.
1144  */
1145 static int check_mime(const char *str)
1146 {
1147         for (; *str; str++) {
1148                 if (*str > 126 || *str < 32 || strchr("()<>@,:;/\"[]?.=", *str)) {
1149                         return 1;
1150                 }
1151         }
1152         return 0;
1153 }
1154
1155 /*!\internal
1156  * \brief Encode a string according to the MIME rules for encoding strings
1157  * that are not 7-bit clean or contain control characters.
1158  *
1159  * Additionally, if the encoded string would exceed the MIME limit of 76
1160  * characters per line, then the encoding will be broken up into multiple
1161  * sections, separated by a space character, in order to facilitate
1162  * breaking up the associated header across multiple lines.
1163  *
1164  * \param end An expandable buffer for holding the result
1165  * \param maxlen \see ast_str
1166  * \param charset Character set in which the result should be encoded
1167  * \param start A string to be encoded
1168  * \param preamble The length of the first line already used for this string,
1169  * to ensure that each line maintains a maximum length of 76 chars.
1170  * \param postamble the length of any additional characters appended to the
1171  * line, used to ensure proper field wrapping.
1172  * \return The encoded string.
1173  */
1174 static const char *ast_str_encode_mime(struct ast_str **end, ssize_t maxlen, const char *charset, const char *start, size_t preamble, size_t postamble)
1175 {
1176         struct ast_str *tmp = ast_str_alloca(80);
1177         int first_section = 1;
1178
1179         ast_str_reset(*end);
1180         ast_str_set(&tmp, -1, "=?%s?Q?", charset);
1181         for (; *start; start++) {
1182                 int need_encoding = 0;
1183                 if (*start < 33 || *start > 126 || strchr("()<>@,:;/\"[]?.=_", *start)) {
1184                         need_encoding = 1;
1185                 }
1186                 if ((first_section && need_encoding && preamble + ast_str_strlen(tmp) > 70) ||
1187                         (first_section && !need_encoding && preamble + ast_str_strlen(tmp) > 72) ||
1188                         (!first_section && need_encoding && ast_str_strlen(tmp) > 70) ||
1189                         (!first_section && !need_encoding && ast_str_strlen(tmp) > 72)) {
1190                         /* Start new line */
1191                         ast_str_append(end, maxlen, "%s%s?=", first_section ? "" : " ", ast_str_buffer(tmp));
1192                         ast_str_set(&tmp, -1, "=?%s?Q?", charset);
1193                         first_section = 0;
1194                 }
1195                 if (need_encoding && *start == ' ') {
1196                         ast_str_append(&tmp, -1, "_");
1197                 } else if (need_encoding) {
1198                         ast_str_append(&tmp, -1, "=%hhX", *start);
1199                 } else {
1200                         ast_str_append(&tmp, -1, "%c", *start);
1201                 }
1202         }
1203         ast_str_append(end, maxlen, "%s%s?=%s", first_section ? "" : " ", ast_str_buffer(tmp), ast_str_strlen(tmp) + postamble > 74 ? " " : "");
1204         return ast_str_buffer(*end);
1205 }
1206
1207 /*!\internal
1208  * \brief Wraps a character sequence in double quotes, escaping occurences of quotes within the string.
1209  * \param from The string to work with.
1210  * \param buf The destination buffer to write the modified quoted string.
1211  * \param maxlen Always zero.  \see ast_str
1212  *
1213  * \return The destination string with quotes wrapped on it (the to field).
1214  */
1215 static const char *ast_str_quote(struct ast_str **buf, ssize_t maxlen, const char *from)
1216 {
1217         const char *ptr;
1218
1219         /* We're only ever passing 0 to maxlen, so short output isn't possible */
1220         ast_str_set(buf, maxlen, "\"");
1221         for (ptr = from; *ptr; ptr++) {
1222                 if (*ptr == '"' || *ptr == '\\') {
1223                         ast_str_append(buf, maxlen, "\\%c", *ptr);
1224                 } else {
1225                         ast_str_append(buf, maxlen, "%c", *ptr);
1226                 }
1227         }
1228         ast_str_append(buf, maxlen, "\"");
1229
1230         return ast_str_buffer(*buf);
1231 }
1232
1233 /*!\internal
1234  * \brief Send voicemail with audio file as an attachment */
1235 static int sendmail(struct minivm_template *template, struct minivm_account *vmu, char *cidnum, char *cidname, const char *filename, char *format, int duration, int attach_user_voicemail, enum mvm_messagetype type, const char *counter)
1236 {
1237         FILE *p = NULL;
1238         int pfd;
1239         char email[256] = "";
1240         char who[256] = "";
1241         char date[256];
1242         char bound[256];
1243         char fname[PATH_MAX];
1244         char dur[PATH_MAX];
1245         char tmp[80] = "/tmp/astmail-XXXXXX";
1246         char tmp2[PATH_MAX];
1247         char newtmp[PATH_MAX]; /* Only used with volgain */
1248         struct timeval now;
1249         struct ast_tm tm;
1250         struct minivm_zone *the_zone = NULL;
1251         struct ast_channel *ast;
1252         char *finalfilename = "";
1253         struct ast_str *str1 = ast_str_create(16), *str2 = ast_str_create(16);
1254         char *fromaddress;
1255         char *fromemail;
1256
1257         if (!str1 || !str2) {
1258                 ast_free(str1);
1259                 ast_free(str2);
1260                 return -1;
1261         }
1262
1263         if (type == MVM_MESSAGE_EMAIL) {
1264                 if (vmu && !ast_strlen_zero(vmu->email)) {
1265                         ast_copy_string(email, vmu->email, sizeof(email));      
1266                 } else if (!ast_strlen_zero(vmu->username) && !ast_strlen_zero(vmu->domain))
1267                         snprintf(email, sizeof(email), "%s@%s", vmu->username, vmu->domain);
1268         } else if (type == MVM_MESSAGE_PAGE) {
1269                 ast_copy_string(email, vmu->pager, sizeof(email));
1270         }
1271
1272         if (ast_strlen_zero(email)) {
1273                 ast_log(LOG_WARNING, "No address to send message to.\n");
1274                 ast_free(str1);
1275                 ast_free(str2);
1276                 return -1;      
1277         }
1278
1279         ast_debug(3, "Sending mail to %s@%s - Using template %s\n", vmu->username, vmu->domain, template->name);
1280
1281         if (!strcmp(format, "wav49"))
1282                 format = "WAV";
1283
1284
1285         /* If we have a gain option, process it now with sox */
1286         if (type == MVM_MESSAGE_EMAIL && (vmu->volgain < -.001 || vmu->volgain > .001) ) {
1287                 char tmpcmd[PATH_MAX];
1288                 int tmpfd;
1289
1290                 ast_copy_string(newtmp, "/tmp/XXXXXX", sizeof(newtmp));
1291                 ast_debug(3, "newtmp: %s\n", newtmp);
1292                 tmpfd = mkstemp(newtmp);
1293                 if (tmpfd < 0) {
1294                         ast_log(LOG_WARNING, "Failed to create temporary file for volgain: %d\n", errno);
1295                         ast_free(str1);
1296                         ast_free(str2);
1297                         return -1;
1298                 }
1299                 snprintf(tmpcmd, sizeof(tmpcmd), "sox -v %.4f %s.%s %s.%s", vmu->volgain, filename, format, newtmp, format);
1300                 ast_safe_system(tmpcmd);
1301                 close(tmpfd);
1302                 finalfilename = newtmp;
1303                 ast_debug(3, "VOLGAIN: Stored at: %s.%s - Level: %.4f - Mailbox: %s\n", filename, format, vmu->volgain, vmu->username);
1304         } else {
1305                 finalfilename = ast_strdupa(filename);
1306         }
1307
1308         /* Create file name */
1309         snprintf(fname, sizeof(fname), "%s.%s", finalfilename, format);
1310
1311         if (template->attachment)
1312                 ast_debug(1, "Attaching file '%s', format '%s', uservm is '%d'\n", finalfilename, format, attach_user_voicemail);
1313
1314         /* Make a temporary file instead of piping directly to sendmail, in case the mail
1315            command hangs */
1316         pfd = mkstemp(tmp);
1317         if (pfd > -1) {
1318                 p = fdopen(pfd, "w");
1319                 if (!p) {
1320                         close(pfd);
1321                         pfd = -1;
1322                 }
1323                 ast_debug(1, "Opening temp file for e-mail: %s\n", tmp);
1324         }
1325         if (!p) {
1326                 ast_log(LOG_WARNING, "Unable to open temporary file '%s'\n", tmp);
1327                 ast_free(str1);
1328                 ast_free(str2);
1329                 return -1;
1330         }
1331         /* Allocate channel used for chanvar substitution */
1332         ast = ast_dummy_channel_alloc();
1333         if (!ast) {
1334                 ast_free(str1);
1335                 ast_free(str2);
1336                 return -1;
1337         }
1338
1339         snprintf(dur, sizeof(dur), "%d:%02d", duration / 60, duration % 60);
1340
1341         /* Does this user have a timezone specified? */
1342         if (!ast_strlen_zero(vmu->zonetag)) {
1343                 /* Find the zone in the list */
1344                 struct minivm_zone *z;
1345                 AST_LIST_LOCK(&minivm_zones);
1346                 AST_LIST_TRAVERSE(&minivm_zones, z, list) {
1347                         if (strcmp(z->name, vmu->zonetag)) 
1348                                 continue;
1349                         the_zone = z;
1350                 }
1351                 AST_LIST_UNLOCK(&minivm_zones);
1352         }
1353
1354         now = ast_tvnow();
1355         ast_localtime(&now, &tm, the_zone ? the_zone->timezone : NULL);
1356         ast_strftime(date, sizeof(date), "%a, %d %b %Y %H:%M:%S %z", &tm);
1357
1358         /* Start printing the email to the temporary file */
1359         fprintf(p, "Date: %s\n", date);
1360
1361         /* Set date format for voicemail mail */
1362         ast_strftime(date, sizeof(date), template->dateformat, &tm);
1363
1364
1365         /* Populate channel with channel variables for substitution */
1366         prep_email_sub_vars(ast, vmu, cidnum, cidname, dur, date, counter);
1367
1368         /* Find email address to use */
1369         /* If there's a server e-mail adress in the account, user that, othterwise template */
1370         fromemail = ast_strlen_zero(vmu->serveremail) ?  template->serveremail : vmu->serveremail;
1371
1372         /* Find name to user for server e-mail */
1373         fromaddress = ast_strlen_zero(template->fromaddress) ? "" : template->fromaddress;
1374
1375         /* If needed, add hostname as domain */
1376         if (ast_strlen_zero(fromemail))
1377                 fromemail = "asterisk";
1378
1379         if (strchr(fromemail, '@'))
1380                 ast_copy_string(who, fromemail, sizeof(who));
1381         else  {
1382                 char host[MAXHOSTNAMELEN];
1383                 gethostname(host, sizeof(host)-1);
1384                 snprintf(who, sizeof(who), "%s@%s", fromemail, host);
1385         }
1386
1387         if (ast_strlen_zero(fromaddress)) {
1388                 fprintf(p, "From: Asterisk PBX <%s>\n", who);
1389         } else {
1390                 ast_debug(4, "Fromaddress template: %s\n", fromaddress);
1391                 ast_str_substitute_variables(&str1, 0, ast, fromaddress);
1392                 if (check_mime(ast_str_buffer(str1))) {
1393                         int first_line = 1;
1394                         char *ptr;
1395                         ast_str_encode_mime(&str2, 0, template->charset, ast_str_buffer(str1), strlen("From: "), strlen(who) + 3);
1396                         while ((ptr = strchr(ast_str_buffer(str2), ' '))) {
1397                                 *ptr = '\0';
1398                                 fprintf(p, "%s %s\n", first_line ? "From:" : "", ast_str_buffer(str2));
1399                                 first_line = 0;
1400                                 /* Substring is smaller, so this will never grow */
1401                                 ast_str_set(&str2, 0, "%s", ptr + 1);
1402                         }
1403                         fprintf(p, "%s %s <%s>\n", first_line ? "From:" : "", ast_str_buffer(str2), who);
1404                 } else {
1405                         fprintf(p, "From: %s <%s>\n", ast_str_quote(&str2, 0, ast_str_buffer(str1)), who);
1406                 }
1407         } 
1408
1409         fprintf(p, "Message-ID: <Asterisk-%u-%s-%d-%s>\n", (unsigned int)ast_random(), vmu->username, (int)getpid(), who);
1410
1411         if (ast_strlen_zero(vmu->email)) {
1412                 snprintf(email, sizeof(email), "%s@%s", vmu->username, vmu->domain);
1413         } else {
1414                 ast_copy_string(email, vmu->email, sizeof(email));
1415         }
1416
1417         if (check_mime(vmu->fullname)) {
1418                 int first_line = 1;
1419                 char *ptr;
1420                 ast_str_encode_mime(&str2, 0, template->charset, vmu->fullname, strlen("To: "), strlen(email) + 3);
1421                 while ((ptr = strchr(ast_str_buffer(str2), ' '))) {
1422                         *ptr = '\0';
1423                         fprintf(p, "%s %s\n", first_line ? "To:" : "", ast_str_buffer(str2));
1424                         first_line = 0;
1425                         /* Substring is smaller, so this will never grow */
1426                         ast_str_set(&str2, 0, "%s", ptr + 1);
1427                 }
1428                 fprintf(p, "%s %s <%s>\n", first_line ? "To:" : "", ast_str_buffer(str2), email);
1429         } else {
1430                 fprintf(p, "To: %s <%s>\n", ast_str_quote(&str2, 0, vmu->fullname), email);
1431         }
1432
1433         if (!ast_strlen_zero(template->subject)) {
1434                 ast_str_substitute_variables(&str1, 0, ast, template->subject);
1435                 if (check_mime(ast_str_buffer(str1))) {
1436                         int first_line = 1;
1437                         char *ptr;
1438                         ast_str_encode_mime(&str2, 0, template->charset, ast_str_buffer(str1), strlen("Subject: "), 0);
1439                         while ((ptr = strchr(ast_str_buffer(str2), ' '))) {
1440                                 *ptr = '\0';
1441                                 fprintf(p, "%s %s\n", first_line ? "Subject:" : "", ast_str_buffer(str2));
1442                                 first_line = 0;
1443                                 /* Substring is smaller, so this will never grow */
1444                                 ast_str_set(&str2, 0, "%s", ptr + 1);
1445                         }
1446                         fprintf(p, "%s %s\n", first_line ? "Subject:" : "", ast_str_buffer(str2));
1447                 } else {
1448                         fprintf(p, "Subject: %s\n", ast_str_buffer(str1));
1449                 }
1450         } else {
1451                 fprintf(p, "Subject: New message in mailbox %s@%s\n", vmu->username, vmu->domain);
1452                 ast_debug(1, "Using default subject for this email \n");
1453         }
1454
1455         if (option_debug > 2)
1456                 fprintf(p, "X-Asterisk-debug: template %s user account %s@%s\n", template->name, vmu->username, vmu->domain);
1457         fprintf(p, "MIME-Version: 1.0\n");
1458
1459         /* Something unique. */
1460         snprintf(bound, sizeof(bound), "voicemail_%s%d%u", vmu->username, (int)getpid(), (unsigned int)ast_random());
1461
1462         fprintf(p, "Content-Type: multipart/mixed; boundary=\"%s\"\n\n\n", bound);
1463
1464         fprintf(p, "--%s\n", bound);
1465         fprintf(p, "Content-Type: text/plain; charset=%s\nContent-Transfer-Encoding: 8bit\n\n", template->charset);
1466         if (!ast_strlen_zero(template->body)) {
1467                 ast_str_substitute_variables(&str1, 0, ast, template->body);
1468                 ast_debug(3, "Message now: %s\n-----\n", ast_str_buffer(str1));
1469                 fprintf(p, "%s\n", ast_str_buffer(str1));
1470         } else {
1471                 fprintf(p, "Dear %s:\n\n\tJust wanted to let you know you were just left a %s long message \n"
1472                         "in mailbox %s from %s, on %s so you might\n"
1473                         "want to check it when you get a chance.  Thanks!\n\n\t\t\t\t--Asterisk\n\n", vmu->fullname, 
1474                         dur,  vmu->username, (cidname ? cidname : (cidnum ? cidnum : "an unknown caller")), date);
1475                 ast_debug(3, "Using default message body (no template)\n-----\n");
1476         }
1477         /* Eww. We want formats to tell us their own MIME type */
1478         if (template->attachment) {
1479                 char *ctype = "audio/x-";
1480                 ast_debug(3, "Attaching file to message: %s\n", fname);
1481                 if (!strcasecmp(format, "ogg"))
1482                         ctype = "application/";
1483
1484                 fprintf(p, "--%s\n", bound);
1485                 fprintf(p, "Content-Type: %s%s; name=\"voicemailmsg.%s\"\n", ctype, format, format);
1486                 fprintf(p, "Content-Transfer-Encoding: base64\n");
1487                 fprintf(p, "Content-Description: Voicemail sound attachment.\n");
1488                 fprintf(p, "Content-Disposition: attachment; filename=\"voicemail%s.%s\"\n\n", counter ? counter : "", format);
1489
1490                 base_encode(fname, p);
1491                 fprintf(p, "\n\n--%s--\n.\n", bound);
1492         }
1493         fclose(p);
1494         snprintf(tmp2, sizeof(tmp2), "( %s < %s ; rm -f %s ) &", global_mailcmd, tmp, tmp);
1495         ast_safe_system(tmp2);
1496         ast_debug(1, "Sent message to %s with command '%s' - %s\n", vmu->email, global_mailcmd, template->attachment ? "(media attachment)" : "");
1497         ast_debug(3, "Actual command used: %s\n", tmp2);
1498         ast = ast_channel_unref(ast);
1499         ast_free(str1);
1500         ast_free(str2);
1501         return 0;
1502 }
1503
1504 /*!\internal
1505  * \brief Create directory based on components */
1506 static int make_dir(char *dest, int len, const char *domain, const char *username, const char *folder)
1507 {
1508         return snprintf(dest, len, "%s%s/%s%s%s", MVM_SPOOL_DIR, domain, username, ast_strlen_zero(folder) ? "" : "/", folder ? folder : "");
1509 }
1510
1511 /*!\internal
1512  * \brief Checks if directory exists. Does not create directory, but builds string in dest
1513  * \param dest    String. base directory.
1514  * \param len    Int. Length base directory string.
1515  * \param domain String. Ignored if is null or empty string.
1516  * \param username String. Ignored if is null or empty string. 
1517  * \param folder  String. Ignored if is null or empty string.
1518  * \return 0 on failure, 1 on success.
1519  */
1520 static int check_dirpath(char *dest, int len, char *domain, char *username, char *folder)
1521 {
1522         struct stat filestat;
1523         make_dir(dest, len, domain, username, folder ? folder : "");
1524         if (stat(dest, &filestat)== -1)
1525                 return FALSE;
1526         else
1527                 return TRUE;
1528 }
1529
1530 /*!\internal
1531  * \brief basically mkdir -p $dest/$domain/$username/$folder
1532  * \param dest    String. base directory.
1533  * \param len     Length of directory string
1534  * \param domain  String. Ignored if is null or empty string.
1535  * \param folder  String. Ignored if is null or empty string.
1536  * \param username  String. Ignored if is null or empty string.
1537  * \return -1 on failure, 0 on success.
1538  */
1539 static int create_dirpath(char *dest, int len, char *domain, char *username, char *folder)
1540 {
1541         int res;
1542         make_dir(dest, len, domain, username, folder);
1543         if ((res = ast_mkdir(dest, 0777))) {
1544                 ast_log(LOG_WARNING, "ast_mkdir '%s' failed: %s\n", dest, strerror(res));
1545                 return -1;
1546         }
1547         ast_debug(2, "Creating directory for %s@%s folder %s : %s\n", username, domain, folder, dest);
1548         return 0;
1549 }
1550
1551
1552 /*!\internal
1553  * \brief Play intro message before recording voicemail
1554  */
1555 static int invent_message(struct ast_channel *chan, char *domain, char *username, int busy, char *ecodes)
1556 {
1557         int res;
1558         char fn[PATH_MAX];
1559
1560         ast_debug(2, "Still preparing to play message ...\n");
1561
1562         snprintf(fn, sizeof(fn), "%s%s/%s/greet", MVM_SPOOL_DIR, domain, username);
1563
1564         if (ast_fileexists(fn, NULL, NULL) > 0) {
1565                 res = ast_streamfile(chan, fn, ast_channel_language(chan));
1566                 if (res) 
1567                         return -1;
1568                 res = ast_waitstream(chan, ecodes);
1569                 if (res) 
1570                         return res;
1571         } else {
1572                 int numericusername = 1;
1573                 char *i = username;
1574
1575                 ast_debug(2, "No personal prompts. Using default prompt set for language\n");
1576
1577                 while (*i)  {
1578                         ast_debug(2, "Numeric? Checking %c\n", *i);
1579                         if (!isdigit(*i)) {
1580                                 numericusername = FALSE;
1581                                 break;
1582                         }
1583                         i++;
1584                 }
1585
1586                 if (numericusername) {
1587                         if (ast_streamfile(chan, "vm-theperson", ast_channel_language(chan)))
1588                                 return -1;
1589                         if ((res = ast_waitstream(chan, ecodes)))
1590                                 return res;
1591
1592                         res = ast_say_digit_str(chan, username, ecodes, ast_channel_language(chan));
1593                         if (res)
1594                                 return res;
1595                 } else {
1596                         if (ast_streamfile(chan, "vm-theextensionis", ast_channel_language(chan)))
1597                                 return -1;
1598                         if ((res = ast_waitstream(chan, ecodes)))
1599                                 return res;
1600                 }
1601         }
1602
1603         res = ast_streamfile(chan, busy ? "vm-isonphone" : "vm-isunavail", ast_channel_language(chan));
1604         if (res)
1605                 return -1;
1606         res = ast_waitstream(chan, ecodes);
1607         return res;
1608 }
1609
1610 /*!\internal
1611  * \brief Delete media files and attribute file */
1612 static int vm_delete(char *file)
1613 {
1614         int res;
1615
1616         ast_debug(1, "Deleting voicemail file %s\n", file);
1617
1618         res = unlink(file);     /* Remove the meta data file */
1619         res |=  ast_filedelete(file, NULL);     /* remove the media file */
1620         return res;
1621 }
1622
1623
1624 /*!\internal
1625  * \brief Record voicemail message & let caller review or re-record it, or set options if applicable */
1626 static int play_record_review(struct ast_channel *chan, char *playfile, char *recordfile, int maxtime, char *fmt,
1627                               int outsidecaller, struct minivm_account *vmu, int *duration, int *sound_duration, const char *unlockdir,
1628                               signed char record_gain)
1629 {
1630         int cmd = 0;
1631         int max_attempts = 3;
1632         int attempts = 0;
1633         int recorded = 0;
1634         int message_exists = 0;
1635         signed char zero_gain = 0;
1636         char *acceptdtmf = "#";
1637         char *canceldtmf = "";
1638
1639         /* Note that urgent and private are for flagging messages as such in the future */
1640
1641         /* barf if no pointer passed to store duration in */
1642         if (duration == NULL) {
1643                 ast_log(LOG_WARNING, "Error play_record_review called without duration pointer\n");
1644                 return -1;
1645         }
1646
1647         cmd = '3';       /* Want to start by recording */
1648
1649         while ((cmd >= 0) && (cmd != 't')) {
1650                 switch (cmd) {
1651                 case '1':
1652                         ast_verb(3, "Saving message as is\n");
1653                         ast_stream_and_wait(chan, "vm-msgsaved", "");
1654                         cmd = 't';
1655                         break;
1656                 case '2':
1657                         /* Review */
1658                         ast_verb(3, "Reviewing the message\n");
1659                         ast_streamfile(chan, recordfile, ast_channel_language(chan));
1660                         cmd = ast_waitstream(chan, AST_DIGIT_ANY);
1661                         break;
1662                 case '3':
1663                         message_exists = 0;
1664                         /* Record */
1665                         if (recorded == 1) 
1666                                 ast_verb(3, "Re-recording the message\n");
1667                         else
1668                                 ast_verb(3, "Recording the message\n");
1669                         if (recorded && outsidecaller) 
1670                                 cmd = ast_play_and_wait(chan, "beep");
1671                         recorded = 1;
1672                         /* After an attempt has been made to record message, we have to take care of INTRO and beep for incoming messages, but not for greetings */
1673                         if (record_gain)
1674                                 ast_channel_setoption(chan, AST_OPTION_RXGAIN, &record_gain, sizeof(record_gain), 0);
1675                         if (ast_test_flag(vmu, MVM_OPERATOR))
1676                                 canceldtmf = "0";
1677                         cmd = ast_play_and_record_full(chan, playfile, recordfile, maxtime, fmt, duration, sound_duration, 0, global_silencethreshold, global_maxsilence, unlockdir, acceptdtmf, canceldtmf, 0, AST_RECORD_IF_EXISTS_OVERWRITE);
1678                         if (record_gain)
1679                                 ast_channel_setoption(chan, AST_OPTION_RXGAIN, &zero_gain, sizeof(zero_gain), 0);
1680                         if (cmd == -1) /* User has hung up, no options to give */
1681                                 return cmd;
1682                         if (cmd == '0')
1683                                 break;
1684                         else if (cmd == '*')
1685                                 break;
1686                         else {
1687                                 /* If all is well, a message exists */
1688                                 message_exists = 1;
1689                                 cmd = 0;
1690                         }
1691                         break;
1692                 case '4':
1693                 case '5':
1694                 case '6':
1695                 case '7':
1696                 case '8':
1697                 case '9':
1698                 case '*':
1699                 case '#':
1700                         cmd = ast_play_and_wait(chan, "vm-sorry");
1701                         break;
1702                 case '0':
1703                         if(!ast_test_flag(vmu, MVM_OPERATOR)) {
1704                                 cmd = ast_play_and_wait(chan, "vm-sorry");
1705                                 break;
1706                         }
1707                         if (message_exists || recorded) {
1708                                 cmd = ast_play_and_wait(chan, "vm-saveoper");
1709                                 if (!cmd)
1710                                         cmd = ast_waitfordigit(chan, 3000);
1711                                 if (cmd == '1') {
1712                                         ast_play_and_wait(chan, "vm-msgsaved");
1713                                         cmd = '0';
1714                                 } else {
1715                                         ast_play_and_wait(chan, "vm-deleted");
1716                                         vm_delete(recordfile);
1717                                         cmd = '0';
1718                                 }
1719                         }
1720                         return cmd;
1721                 default:
1722                         /* If the caller is an ouside caller, and the review option is enabled,
1723                            allow them to review the message, but let the owner of the box review
1724                            their OGM's */
1725                         if (outsidecaller && !ast_test_flag(vmu, MVM_REVIEW))
1726                                 return cmd;
1727                         if (message_exists) {
1728                                 cmd = ast_play_and_wait(chan, "vm-review");
1729                         } else {
1730                                 cmd = ast_play_and_wait(chan, "vm-torerecord");
1731                                 if (!cmd)
1732                                         cmd = ast_waitfordigit(chan, 600);
1733                         }
1734
1735                         if (!cmd && outsidecaller && ast_test_flag(vmu, MVM_OPERATOR)) {
1736                                 cmd = ast_play_and_wait(chan, "vm-reachoper");
1737                                 if (!cmd)
1738                                         cmd = ast_waitfordigit(chan, 600);
1739                         }
1740                         if (!cmd)
1741                                 cmd = ast_waitfordigit(chan, 6000);
1742                         if (!cmd) {
1743                                 attempts++;
1744                         }
1745                         if (attempts > max_attempts) {
1746                                 cmd = 't';
1747                         }
1748                 }
1749         }
1750         if (outsidecaller)
1751                 ast_play_and_wait(chan, "vm-goodbye");
1752         if (cmd == 't')
1753                 cmd = 0;
1754         return cmd;
1755 }
1756
1757 /*! \brief Run external notification for voicemail message */
1758 static void run_externnotify(struct ast_channel *chan, struct minivm_account *vmu)
1759 {
1760         char arguments[BUFSIZ];
1761
1762         if (ast_strlen_zero(vmu->externnotify) && ast_strlen_zero(global_externnotify))
1763                 return;
1764
1765         snprintf(arguments, sizeof(arguments), "%s %s@%s %s %s&", 
1766                 ast_strlen_zero(vmu->externnotify) ? global_externnotify : vmu->externnotify, 
1767                 vmu->username, vmu->domain,
1768                 (ast_channel_caller(chan)->id.name.valid && ast_channel_caller(chan)->id.name.str)
1769                         ? ast_channel_caller(chan)->id.name.str : "",
1770                 (ast_channel_caller(chan)->id.number.valid && ast_channel_caller(chan)->id.number.str)
1771                         ? ast_channel_caller(chan)->id.number.str : "");
1772
1773         ast_debug(1, "Executing: %s\n", arguments);
1774         ast_safe_system(arguments);
1775 }
1776
1777 /*!\internal
1778  * \brief Send message to voicemail account owner */
1779 static int notify_new_message(struct ast_channel *chan, const char *templatename, struct minivm_account *vmu, const char *filename, long duration, const char *format, char *cidnum, char *cidname)
1780 {
1781         RAII_VAR(struct ast_json *, json_object, NULL, ast_json_unref);
1782         RAII_VAR(struct stasis_message *, message, NULL, ao2_cleanup);
1783         RAII_VAR(struct ast_mwi_state *, mwi_state, NULL, ao2_cleanup);
1784         char *stringp;
1785         struct minivm_template *etemplate;
1786         char *messageformat;
1787         int res = 0;
1788         char oldlocale[100];
1789         const char *counter;
1790
1791         if (!ast_strlen_zero(vmu->attachfmt)) {
1792                 if (strstr(format, vmu->attachfmt)) {
1793                         format = vmu->attachfmt;
1794                 } else {
1795                         ast_log(LOG_WARNING, "Attachment format '%s' is not one of the recorded formats '%s'.  Falling back to default format for '%s@%s'.\n", vmu->attachfmt, format, vmu->username, vmu->domain);
1796                 }
1797         }
1798
1799         etemplate = message_template_find(vmu->etemplate);
1800         if (!etemplate)
1801                 etemplate = message_template_find(templatename);
1802         if (!etemplate)
1803                 etemplate = message_template_find("email-default");
1804
1805         /* Attach only the first format */
1806         stringp = messageformat = ast_strdupa(format);
1807         strsep(&stringp, "|");
1808
1809         if (!ast_strlen_zero(etemplate->locale)) {
1810                 char *new_locale;
1811                 ast_copy_string(oldlocale, setlocale(LC_TIME, NULL), sizeof(oldlocale));
1812                 ast_debug(2, "Changing locale from %s to %s\n", oldlocale, etemplate->locale);
1813                 new_locale = setlocale(LC_TIME, etemplate->locale);
1814                 if (new_locale == NULL) {
1815                         ast_log(LOG_WARNING, "-_-_- Changing to new locale did not work. Locale: %s\n", etemplate->locale);
1816                 }
1817         }
1818
1819
1820
1821         /* Read counter if available */
1822         ast_channel_lock(chan);
1823         if ((counter = pbx_builtin_getvar_helper(chan, "MVM_COUNTER"))) {
1824                 counter = ast_strdupa(counter);
1825         }
1826         ast_channel_unlock(chan);
1827
1828         if (ast_strlen_zero(counter)) {
1829                 ast_debug(2, "MVM_COUNTER not found\n");
1830         } else {
1831                 ast_debug(2, "MVM_COUNTER found - will use it with value %s\n", counter);
1832         }
1833
1834         res = sendmail(etemplate, vmu, cidnum, cidname, filename, messageformat, duration, etemplate->attachment, MVM_MESSAGE_EMAIL, counter);
1835
1836         if (res == 0 && !ast_strlen_zero(vmu->pager))  {
1837                 /* Find template for paging */
1838                 etemplate = message_template_find(vmu->ptemplate);
1839                 if (!etemplate)
1840                         etemplate = message_template_find("pager-default");
1841                 if (etemplate->locale) {
1842                         ast_copy_string(oldlocale, setlocale(LC_TIME, ""), sizeof(oldlocale));
1843                         setlocale(LC_TIME, etemplate->locale);
1844                 }
1845
1846                 res = sendmail(etemplate, vmu, cidnum, cidname, filename, messageformat, duration, etemplate->attachment, MVM_MESSAGE_PAGE, counter);
1847         }
1848
1849         mwi_state = ast_mwi_create(vmu->username, vmu->domain);
1850         if (!mwi_state) {
1851                 goto notify_cleanup;
1852         }
1853         mwi_state->snapshot = ast_channel_snapshot_get_latest(ast_channel_uniqueid(chan));
1854
1855         json_object = ast_json_pack("{s: s, s: s}",
1856                         "Event", "MiniVoiceMail"
1857                         "Action", "SentNotification",
1858                         "Counter", counter);
1859         if (!json_object) {
1860                 goto notify_cleanup;
1861         }
1862         message = ast_mwi_blob_create(mwi_state, ast_mwi_vm_app_type(), json_object);
1863         if (!message) {
1864                 goto notify_cleanup;
1865         }
1866         stasis_publish(ast_mwi_topic(mwi_state->uniqueid), message);
1867
1868 notify_cleanup:
1869         run_externnotify(chan, vmu);            /* Run external notification */
1870
1871         if (etemplate->locale) {
1872                 setlocale(LC_TIME, oldlocale); /* Rest to old locale */
1873         }
1874         return res;
1875 }
1876
1877  
1878 /*!\internal
1879  * \brief Record voicemail message, store into file prepared for sending e-mail */
1880 static int leave_voicemail(struct ast_channel *chan, char *username, struct leave_vm_options *options)
1881 {
1882         char tmptxtfile[PATH_MAX];
1883         char callerid[256];
1884         FILE *txt;
1885         int res = 0, txtdes;
1886         int duration = 0;
1887         int sound_duration = 0;
1888         char date[256];
1889         char tmpdir[PATH_MAX];
1890         char ext_context[256] = "";
1891         char fmt[80];
1892         char *domain;
1893         char tmp[256] = "";
1894         struct minivm_account *vmu;
1895         int userdir;
1896
1897         ast_copy_string(tmp, username, sizeof(tmp));
1898         username = tmp;
1899         domain = strchr(tmp, '@');
1900         if (domain) {
1901                 *domain = '\0';
1902                 domain++;
1903         }
1904
1905         if (!(vmu = find_account(domain, username, TRUE))) {
1906                 /* We could not find user, let's exit */
1907                 ast_log(LOG_ERROR, "Can't allocate temporary account for '%s@%s'\n", username, domain);
1908                 pbx_builtin_setvar_helper(chan, "MVM_RECORD_STATUS", "FAILED");
1909                 return 0;
1910         }
1911
1912         /* Setup pre-file if appropriate */
1913         if (strcmp(vmu->domain, "localhost"))
1914                 snprintf(ext_context, sizeof(ext_context), "%s@%s", username, vmu->domain);
1915         else
1916                 ast_copy_string(ext_context, vmu->domain, sizeof(ext_context));
1917
1918         /* The meat of recording the message...  All the announcements and beeps have been played*/
1919         if (ast_strlen_zero(vmu->attachfmt))
1920                 ast_copy_string(fmt, default_vmformat, sizeof(fmt));
1921         else
1922                 ast_copy_string(fmt, vmu->attachfmt, sizeof(fmt));
1923
1924         if (ast_strlen_zero(fmt)) {
1925                 ast_log(LOG_WARNING, "No format for saving voicemail? Default %s\n", default_vmformat);
1926                 pbx_builtin_setvar_helper(chan, "MVM_RECORD_STATUS", "FAILED");
1927                 return res;
1928         }
1929
1930         userdir = check_dirpath(tmpdir, sizeof(tmpdir), vmu->domain, username, "tmp");
1931
1932         /* If we have no user directory, use generic temporary directory */
1933         if (!userdir) {
1934                 create_dirpath(tmpdir, sizeof(tmpdir), "0000_minivm_temp", "mediafiles", "");
1935                 ast_debug(3, "Creating temporary directory %s\n", tmpdir);
1936         }
1937
1938
1939         snprintf(tmptxtfile, sizeof(tmptxtfile), "%s/XXXXXX", tmpdir);
1940
1941         /* XXX This file needs to be in temp directory */
1942         txtdes = mkstemp(tmptxtfile);
1943         if (txtdes < 0) {
1944                 ast_log(LOG_ERROR, "Unable to create message file %s: %s\n", tmptxtfile, strerror(errno));
1945                 res = ast_streamfile(chan, "vm-mailboxfull", ast_channel_language(chan));
1946                 if (!res)
1947                         res = ast_waitstream(chan, "");
1948                 pbx_builtin_setvar_helper(chan, "MVM_RECORD_STATUS", "FAILED");
1949                 return res;
1950         }
1951
1952         if (res >= 0) {
1953                 /* Unless we're *really* silent, try to send the beep */
1954                 res = ast_streamfile(chan, "beep", ast_channel_language(chan));
1955                 if (!res)
1956                         res = ast_waitstream(chan, "");
1957         }
1958
1959         /* OEJ XXX Maybe this can be turned into a log file? Hmm. */
1960         /* Store information */
1961         ast_debug(2, "Open file for metadata: %s\n", tmptxtfile);
1962
1963         res = play_record_review(chan, NULL, tmptxtfile, global_vmmaxmessage, fmt, 1, vmu, &duration, &sound_duration, NULL, options->record_gain);
1964
1965         txt = fdopen(txtdes, "w+");
1966         if (!txt) {
1967                 ast_log(LOG_WARNING, "Error opening text file for output\n");
1968         } else {
1969                 struct ast_tm tm;
1970                 struct timeval now = ast_tvnow();
1971                 char timebuf[30];
1972                 char logbuf[BUFSIZ];
1973                 get_date(date, sizeof(date));
1974                 ast_localtime(&now, &tm, NULL);
1975                 ast_strftime(timebuf, sizeof(timebuf), "%H:%M:%S", &tm);
1976
1977                 ast_callerid_merge(callerid, sizeof(callerid),
1978                         S_COR(ast_channel_caller(chan)->id.name.valid, ast_channel_caller(chan)->id.name.str, NULL),
1979                         S_COR(ast_channel_caller(chan)->id.number.valid, ast_channel_caller(chan)->id.number.str, NULL),
1980                         "Unknown");
1981                 snprintf(logbuf, sizeof(logbuf),
1982                         /* "Mailbox:domain:macrocontext:exten:priority:callerchan:callerid:origdate:origtime:duration:durationstatus:accountcode" */
1983                         "%s:%s:%s:%s:%d:%s:%s:%s:%s:%d:%s:%s\n",
1984                         username,
1985                         ast_channel_context(chan),
1986                         ast_channel_macrocontext(chan), 
1987                         ast_channel_exten(chan),
1988                         ast_channel_priority(chan),
1989                         ast_channel_name(chan),
1990                         callerid,
1991                         date, 
1992                         timebuf,
1993                         duration,
1994                         duration < global_vmminmessage ? "IGNORED" : "OK",
1995                         vmu->accountcode
1996                 ); 
1997                 fprintf(txt, "%s", logbuf);
1998                 if (minivmlogfile) {
1999                         ast_mutex_lock(&minivmloglock);
2000                         fprintf(minivmlogfile, "%s", logbuf);
2001                         ast_mutex_unlock(&minivmloglock);
2002                 }
2003
2004                 if (sound_duration < global_vmminmessage) {
2005                         ast_verb(3, "Recording was %d seconds long but needs to be at least %d - abandoning\n", sound_duration, global_vmminmessage);
2006                         fclose(txt);
2007                         ast_filedelete(tmptxtfile, NULL);
2008                         unlink(tmptxtfile);
2009                         pbx_builtin_setvar_helper(chan, "MVM_RECORD_STATUS", "FAILED");
2010                         return 0;
2011                 } 
2012                 fclose(txt); /* Close log file */
2013                 if (ast_fileexists(tmptxtfile, NULL, NULL) <= 0) {
2014                         ast_debug(1, "The recorded media file is gone, so we should remove the .txt file too!\n");
2015                         unlink(tmptxtfile);
2016                         pbx_builtin_setvar_helper(chan, "MVM_RECORD_STATUS", "FAILED");
2017                         if(ast_test_flag(vmu, MVM_ALLOCED))
2018                                 free_user(vmu);
2019                         return 0;
2020                 }
2021
2022                 /* Set channel variables for the notify application */
2023                 pbx_builtin_setvar_helper(chan, "MVM_FILENAME", tmptxtfile);
2024                 snprintf(timebuf, sizeof(timebuf), "%d", duration);
2025                 pbx_builtin_setvar_helper(chan, "MVM_DURATION", timebuf);
2026                 pbx_builtin_setvar_helper(chan, "MVM_FORMAT", fmt);
2027
2028         }
2029         global_stats.lastreceived = ast_tvnow();
2030         global_stats.receivedmessages++;
2031 #if 0
2032         /* Go ahead and delete audio files from system, they're not needed any more */
2033         if (ast_fileexists(tmptxtfile, NULL, NULL) <= 0) {
2034                 ast_filedelete(tmptxtfile, NULL);
2035                  /* Even not being used at the moment, it's better to convert ast_log to ast_debug anyway */
2036                 ast_debug(2, "-_-_- Deleted audio file after notification :: %s \n", tmptxtfile);
2037         }
2038 #endif
2039
2040         if (res > 0)
2041                 res = 0;
2042
2043         if(ast_test_flag(vmu, MVM_ALLOCED))
2044                 free_user(vmu);
2045
2046         pbx_builtin_setvar_helper(chan, "MVM_RECORD_STATUS", "SUCCESS");
2047         return res;
2048 }
2049
2050 /*!\internal
2051  * \brief Queue a message waiting event */
2052 static void queue_mwi_event(const char *channel_id, const char *mbx, const char *ctx, int urgent, int new, int old)
2053 {
2054         char *mailbox, *context;
2055
2056         mailbox = ast_strdupa(mbx);
2057         context = ast_strdupa(ctx);
2058         if (ast_strlen_zero(context)) {
2059                 context = "default";
2060         }
2061
2062         ast_publish_mwi_state_channel(mailbox, context, new + urgent, old, channel_id);
2063 }
2064
2065 /*!\internal
2066  * \brief Send MWI using interal Asterisk event subsystem */
2067 static int minivm_mwi_exec(struct ast_channel *chan, const char *data)
2068 {
2069         int argc;
2070         char *argv[4];
2071         int res = 0;
2072         char *tmpptr;
2073         char tmp[PATH_MAX];
2074         char *mailbox;
2075         char *domain;
2076         if (ast_strlen_zero(data))  {
2077                 ast_log(LOG_ERROR, "Minivm needs at least an account argument \n");
2078                 return -1;
2079         }
2080         tmpptr = ast_strdupa((char *)data);
2081         argc = ast_app_separate_args(tmpptr, ',', argv, ARRAY_LEN(argv));
2082         if (argc < 4) {
2083                 ast_log(LOG_ERROR, "%d arguments passed to MiniVM_MWI, need 4.\n", argc);
2084                 return -1;
2085         }
2086         ast_copy_string(tmp, argv[0], sizeof(tmp));
2087         mailbox = tmp;
2088         domain = strchr(tmp, '@');
2089         if (domain) {
2090                 *domain = '\0';
2091                 domain++;
2092         }
2093         if (ast_strlen_zero(domain) || ast_strlen_zero(mailbox)) {
2094                 ast_log(LOG_ERROR, "Need mailbox@context as argument. Sorry. Argument 0 %s\n", argv[0]);
2095                 return -1;
2096         }
2097         queue_mwi_event(ast_channel_uniqueid(chan), mailbox, domain, atoi(argv[1]), atoi(argv[2]), atoi(argv[3]));
2098
2099         return res;
2100 }
2101
2102
2103 /*!\internal
2104  * \brief Notify voicemail account owners - either generic template or user specific */
2105 static int minivm_notify_exec(struct ast_channel *chan, const char *data)
2106 {
2107         int argc;
2108         char *argv[2];
2109         int res = 0;
2110         char tmp[PATH_MAX];
2111         char *domain;
2112         char *tmpptr;
2113         struct minivm_account *vmu;
2114         char *username;
2115         const char *template = "";
2116         const char *filename;
2117         const char *format;
2118         const char *duration_string;
2119         if (ast_strlen_zero(data))  {
2120                 ast_log(LOG_ERROR, "Minivm needs at least an account argument \n");
2121                 return -1;
2122         }
2123         tmpptr = ast_strdupa((char *)data);
2124         argc = ast_app_separate_args(tmpptr, ',', argv, ARRAY_LEN(argv));
2125
2126         if (argc == 2 && !ast_strlen_zero(argv[1]))
2127                 template = argv[1];
2128
2129         ast_copy_string(tmp, argv[0], sizeof(tmp));
2130         username = tmp;
2131         domain = strchr(tmp, '@');
2132         if (domain) {
2133                 *domain = '\0';
2134                 domain++;
2135         } 
2136         if (ast_strlen_zero(domain) || ast_strlen_zero(username)) {
2137                 ast_log(LOG_ERROR, "Need username@domain as argument. Sorry. Argument 0 %s\n", argv[0]);
2138                 return -1;
2139         }
2140
2141         if(!(vmu = find_account(domain, username, TRUE))) {
2142                 /* We could not find user, let's exit */
2143                 ast_log(LOG_WARNING, "Could not allocate temporary memory for '%s@%s'\n", username, domain);
2144                 pbx_builtin_setvar_helper(chan, "MVM_NOTIFY_STATUS", "FAILED");
2145                 return -1;
2146         }
2147
2148         ast_channel_lock(chan);
2149         if ((filename = pbx_builtin_getvar_helper(chan, "MVM_FILENAME"))) {
2150                 filename = ast_strdupa(filename);
2151         }
2152         ast_channel_unlock(chan);
2153         /* Notify of new message to e-mail and pager */
2154         if (!ast_strlen_zero(filename)) {
2155                 ast_channel_lock(chan); 
2156                 if ((format = pbx_builtin_getvar_helper(chan, "MVM_FORMAT"))) {
2157                         format = ast_strdupa(format);
2158                 }
2159                 if ((duration_string = pbx_builtin_getvar_helper(chan, "MVM_DURATION"))) {
2160                         duration_string = ast_strdupa(duration_string);
2161                 }
2162                 ast_channel_unlock(chan);
2163                 res = notify_new_message(chan, template, vmu, filename, atoi(duration_string),
2164                         format,
2165                         S_COR(ast_channel_caller(chan)->id.number.valid, ast_channel_caller(chan)->id.number.str, NULL),
2166                         S_COR(ast_channel_caller(chan)->id.name.valid, ast_channel_caller(chan)->id.name.str, NULL));
2167         }
2168
2169         pbx_builtin_setvar_helper(chan, "MVM_NOTIFY_STATUS", res == 0 ? "SUCCESS" : "FAILED");
2170
2171
2172         if(ast_test_flag(vmu, MVM_ALLOCED))
2173                 free_user(vmu);
2174
2175         /* Ok, we're ready to rock and roll. Return to dialplan */
2176
2177         return res;
2178
2179 }
2180
2181 /*!\internal
2182  * \brief Dialplan function to record voicemail */
2183 static int minivm_record_exec(struct ast_channel *chan, const char *data)
2184 {
2185         int res = 0;
2186         char *tmp;
2187         struct leave_vm_options leave_options;
2188         int argc;
2189         char *argv[2];
2190         struct ast_flags flags = { 0 };
2191         char *opts[OPT_ARG_ARRAY_SIZE];
2192
2193         memset(&leave_options, 0, sizeof(leave_options));
2194
2195         /* Answer channel if it's not already answered */
2196         if (ast_channel_state(chan) != AST_STATE_UP)
2197                 ast_answer(chan);
2198
2199         if (ast_strlen_zero(data))  {
2200                 ast_log(LOG_ERROR, "Minivm needs at least an account argument \n");
2201                 return -1;
2202         }
2203         tmp = ast_strdupa((char *)data);
2204         argc = ast_app_separate_args(tmp, ',', argv, ARRAY_LEN(argv));
2205         if (argc == 2) {
2206                 if (ast_app_parse_options(minivm_app_options, &flags, opts, argv[1])) {
2207                         return -1;
2208                 }
2209                 ast_copy_flags(&leave_options, &flags, OPT_SILENT | OPT_BUSY_GREETING | OPT_UNAVAIL_GREETING );
2210                 if (ast_test_flag(&flags, OPT_RECORDGAIN)) {
2211                         int gain;
2212
2213                         if (sscanf(opts[OPT_ARG_RECORDGAIN], "%30d", &gain) != 1) {
2214                                 ast_log(LOG_WARNING, "Invalid value '%s' provided for record gain option\n", opts[OPT_ARG_RECORDGAIN]);
2215                                 return -1;
2216                         } else 
2217                                 leave_options.record_gain = (signed char) gain;
2218                 }
2219         } 
2220
2221         /* Now run the appliation and good luck to you! */
2222         res = leave_voicemail(chan, argv[0], &leave_options);
2223
2224         if (res == ERROR_LOCK_PATH) {
2225                 ast_log(LOG_ERROR, "Could not leave voicemail. The path is already locked.\n");
2226                 pbx_builtin_setvar_helper(chan, "MVM_RECORD_STATUS", "FAILED");
2227                 res = 0;
2228         }
2229         pbx_builtin_setvar_helper(chan, "MVM_RECORD_STATUS", "SUCCESS");
2230
2231         return res;
2232 }
2233
2234 /*!\internal
2235  * \brief Play voicemail prompts - either generic or user specific */
2236 static int minivm_greet_exec(struct ast_channel *chan, const char *data)
2237 {
2238         struct leave_vm_options leave_options = { 0, '\0'};
2239         int argc;
2240         char *argv[2];
2241         struct ast_flags flags = { 0 };
2242         char *opts[OPT_ARG_ARRAY_SIZE];
2243         int res = 0;
2244         int ausemacro = 0;
2245         int ousemacro = 0;
2246         int ouseexten = 0;
2247         char tmp[PATH_MAX];
2248         char dest[PATH_MAX];
2249         char prefile[PATH_MAX] = "";
2250         char tempfile[PATH_MAX] = "";
2251         char ext_context[256] = "";
2252         char *domain;
2253         char ecodes[16] = "#";
2254         char *tmpptr;
2255         struct minivm_account *vmu;
2256         char *username = argv[0];
2257
2258         if (ast_strlen_zero(data))  {
2259                 ast_log(LOG_ERROR, "Minivm needs at least an account argument \n");
2260                 return -1;
2261         }
2262         tmpptr = ast_strdupa((char *)data);
2263         argc = ast_app_separate_args(tmpptr, ',', argv, ARRAY_LEN(argv));
2264
2265         if (argc == 2) {
2266                 if (ast_app_parse_options(minivm_app_options, &flags, opts, argv[1]))
2267                         return -1;
2268                 ast_copy_flags(&leave_options, &flags, OPT_SILENT | OPT_BUSY_GREETING | OPT_UNAVAIL_GREETING );
2269         }
2270
2271         ast_copy_string(tmp, argv[0], sizeof(tmp));
2272         username = tmp;
2273         domain = strchr(tmp, '@');
2274         if (domain) {
2275                 *domain = '\0';
2276                 domain++;
2277         } 
2278         if (ast_strlen_zero(domain) || ast_strlen_zero(username)) {
2279                 ast_log(LOG_ERROR, "Need username@domain as argument. Sorry. Argument:  %s\n", argv[0]);
2280                 return -1;
2281         }
2282         ast_debug(1, "Trying to find configuration for user %s in domain %s\n", username, domain);
2283
2284         if (!(vmu = find_account(domain, username, TRUE))) {
2285                 ast_log(LOG_ERROR, "Could not allocate memory. \n");
2286                 return -1;
2287         }
2288
2289         /* Answer channel if it's not already answered */
2290         if (ast_channel_state(chan) != AST_STATE_UP)
2291                 ast_answer(chan);
2292
2293         /* Setup pre-file if appropriate */
2294         if (strcmp(vmu->domain, "localhost"))
2295                 snprintf(ext_context, sizeof(ext_context), "%s@%s", username, vmu->domain);
2296         else
2297                 ast_copy_string(ext_context, vmu->domain, sizeof(ext_context));
2298
2299         if (ast_test_flag(&leave_options, OPT_BUSY_GREETING)) {
2300                 res = check_dirpath(dest, sizeof(dest), vmu->domain, username, "busy");
2301                 if (res)
2302                         snprintf(prefile, sizeof(prefile), "%s%s/%s/busy", MVM_SPOOL_DIR, vmu->domain, username);
2303         } else if (ast_test_flag(&leave_options, OPT_UNAVAIL_GREETING)) {
2304                 res = check_dirpath(dest, sizeof(dest), vmu->domain, username, "unavail");
2305                 if (res)
2306                         snprintf(prefile, sizeof(prefile), "%s%s/%s/unavail", MVM_SPOOL_DIR, vmu->domain, username);
2307         }
2308         /* Check for temporary greeting - it overrides busy and unavail */
2309         snprintf(tempfile, sizeof(tempfile), "%s%s/%s/temp", MVM_SPOOL_DIR, vmu->domain, username);
2310         if (!(res = check_dirpath(dest, sizeof(dest), vmu->domain, username, "temp"))) {
2311                 ast_debug(2, "Temporary message directory does not exist, using default (%s)\n", tempfile);
2312                 ast_copy_string(prefile, tempfile, sizeof(prefile));
2313         }
2314         ast_debug(2, "Preparing to play message ...\n");
2315
2316         /* Check current or macro-calling context for special extensions */
2317         if (ast_test_flag(vmu, MVM_OPERATOR)) {
2318                 if (!ast_strlen_zero(vmu->exit)) {
2319                         if (ast_exists_extension(chan, vmu->exit, "o", 1,
2320                                 S_COR(ast_channel_caller(chan)->id.number.valid, ast_channel_caller(chan)->id.number.str, NULL))) {
2321                                 strncat(ecodes, "0", sizeof(ecodes) - strlen(ecodes) - 1);
2322                                 ouseexten = 1;
2323                         }
2324                 } else if (ast_exists_extension(chan, ast_channel_context(chan), "o", 1,
2325                         S_COR(ast_channel_caller(chan)->id.number.valid, ast_channel_caller(chan)->id.number.str, NULL))) {
2326                         strncat(ecodes, "0", sizeof(ecodes) - strlen(ecodes) - 1);
2327                         ouseexten = 1;
2328                 }
2329                 else if (!ast_strlen_zero(ast_channel_macrocontext(chan))
2330                         && ast_exists_extension(chan, ast_channel_macrocontext(chan), "o", 1,
2331                                 S_COR(ast_channel_caller(chan)->id.number.valid, ast_channel_caller(chan)->id.number.str, NULL))) {
2332                         strncat(ecodes, "0", sizeof(ecodes) - strlen(ecodes) - 1);
2333                         ousemacro = 1;
2334                 }
2335         }
2336
2337         if (!ast_strlen_zero(vmu->exit)) {
2338                 if (ast_exists_extension(chan, vmu->exit, "a", 1,
2339                         S_COR(ast_channel_caller(chan)->id.number.valid, ast_channel_caller(chan)->id.number.str, NULL))) {
2340                         strncat(ecodes, "*", sizeof(ecodes) -  strlen(ecodes) - 1);
2341                 }
2342         } else if (ast_exists_extension(chan, ast_channel_context(chan), "a", 1,
2343                 S_COR(ast_channel_caller(chan)->id.number.valid, ast_channel_caller(chan)->id.number.str, NULL))) {
2344                 strncat(ecodes, "*", sizeof(ecodes) -  strlen(ecodes) - 1);
2345         } else if (!ast_strlen_zero(ast_channel_macrocontext(chan))
2346                 && ast_exists_extension(chan, ast_channel_macrocontext(chan), "a", 1,
2347                         S_COR(ast_channel_caller(chan)->id.number.valid, ast_channel_caller(chan)->id.number.str, NULL))) {
2348                 strncat(ecodes, "*", sizeof(ecodes) -  strlen(ecodes) - 1);
2349                 ausemacro = 1;
2350         }
2351
2352         res = 0;        /* Reset */
2353         /* Play the beginning intro if desired */
2354         if (!ast_strlen_zero(prefile)) {
2355                 if (ast_streamfile(chan, prefile, ast_channel_language(chan)) > -1) 
2356                         res = ast_waitstream(chan, ecodes);
2357         } else {
2358                 ast_debug(2, "%s doesn't exist, doing what we can\n", prefile);
2359                 res = invent_message(chan, vmu->domain, username, ast_test_flag(&leave_options, OPT_BUSY_GREETING), ecodes);
2360         }
2361         if (res < 0) {
2362                 ast_debug(2, "Hang up during prefile playback\n");
2363                 pbx_builtin_setvar_helper(chan, "MVM_GREET_STATUS", "FAILED");
2364                 if(ast_test_flag(vmu, MVM_ALLOCED))
2365                         free_user(vmu);
2366                 return -1;
2367         }
2368         if (res == '#') {
2369                 /* On a '#' we skip the instructions */
2370                 ast_set_flag(&leave_options, OPT_SILENT);
2371                 res = 0;
2372         }
2373         if (!res && !ast_test_flag(&leave_options, OPT_SILENT)) {
2374                 res = ast_streamfile(chan, SOUND_INTRO, ast_channel_language(chan));
2375                 if (!res)
2376                         res = ast_waitstream(chan, ecodes);
2377                 if (res == '#') {
2378                         ast_set_flag(&leave_options, OPT_SILENT);
2379                         res = 0;
2380                 }
2381         }
2382         if (res > 0)
2383                 ast_stopstream(chan);
2384         /* Check for a '*' here in case the caller wants to escape from voicemail to something
2385            other than the operator -- an automated attendant or mailbox login for example */
2386         if (res == '*') {
2387                 ast_channel_exten_set(chan, "a");
2388                 if (!ast_strlen_zero(vmu->exit)) {
2389                         ast_channel_context_set(chan, vmu->exit);
2390                 } else if (ausemacro && !ast_strlen_zero(ast_channel_macrocontext(chan))) {
2391                         ast_channel_context_set(chan, ast_channel_macrocontext(chan));
2392                 }
2393                 ast_channel_priority_set(chan, 0);
2394                 pbx_builtin_setvar_helper(chan, "MVM_GREET_STATUS", "USEREXIT");
2395                 res = 0;
2396         } else if (res == '0') { /* Check for a '0' here */
2397                 if(ouseexten || ousemacro) {
2398                         ast_channel_exten_set(chan, "o");
2399                         if (!ast_strlen_zero(vmu->exit)) {
2400                                 ast_channel_context_set(chan, vmu->exit);
2401                         } else if (ousemacro && !ast_strlen_zero(ast_channel_macrocontext(chan))) {
2402                                 ast_channel_context_set(chan, ast_channel_macrocontext(chan));
2403                         }
2404                         ast_play_and_wait(chan, "transfer");
2405                         ast_channel_priority_set(chan, 0);
2406                         pbx_builtin_setvar_helper(chan, "MVM_GREET_STATUS", "USEREXIT");
2407                 }
2408                 res =  0;
2409         } else if (res < 0) {
2410                 pbx_builtin_setvar_helper(chan, "MVM_GREET_STATUS", "FAILED");
2411                 res = -1;
2412         } else
2413                 pbx_builtin_setvar_helper(chan, "MVM_GREET_STATUS", "SUCCESS");
2414
2415         if(ast_test_flag(vmu, MVM_ALLOCED))
2416                 free_user(vmu);
2417
2418
2419         /* Ok, we're ready to rock and roll. Return to dialplan */
2420         return res;
2421
2422 }
2423
2424 /*!\internal
2425  * \brief Dialplan application to delete voicemail */
2426 static int minivm_delete_exec(struct ast_channel *chan, const char *data)
2427 {
2428         int res = 0;
2429         char filename[BUFSIZ];
2430
2431         if (!ast_strlen_zero(data)) {
2432                 ast_copy_string(filename, (char *) data, sizeof(filename));
2433         } else {
2434                 ast_channel_lock(chan);
2435                 ast_copy_string(filename, pbx_builtin_getvar_helper(chan, "MVM_FILENAME"), sizeof(filename));
2436                 ast_channel_unlock(chan);
2437         }
2438
2439         if (ast_strlen_zero(filename)) {
2440                 ast_log(LOG_ERROR, "No filename given in application arguments or channel variable MVM_FILENAME\n");
2441                 return res;
2442         } 
2443
2444         /* Go ahead and delete audio files from system, they're not needed any more */
2445         /* We should look for both audio and text files here */
2446         if (ast_fileexists(filename, NULL, NULL) > 0) {
2447                 res = vm_delete(filename);
2448                 if (res) {
2449                         ast_debug(2, "Can't delete file: %s\n", filename);
2450                         pbx_builtin_setvar_helper(chan, "MVM_DELETE_STATUS", "FAILED");
2451                 } else {
2452                         ast_debug(2, "Deleted voicemail file :: %s \n", filename);
2453                         pbx_builtin_setvar_helper(chan, "MVM_DELETE_STATUS", "SUCCESS");
2454                 }
2455         } else {
2456                 ast_debug(2, "Filename does not exist: %s\n", filename);
2457                 pbx_builtin_setvar_helper(chan, "MVM_DELETE_STATUS", "FAILED");
2458         }
2459
2460         return res;
2461 }
2462
2463 /*! \brief Record specific messages for voicemail account */
2464 static int minivm_accmess_exec(struct ast_channel *chan, const char *data)
2465 {
2466         int argc = 0;
2467         char *argv[2];
2468         char filename[PATH_MAX];
2469         char tmp[PATH_MAX];
2470         char *domain;
2471         char *tmpptr = NULL;
2472         struct minivm_account *vmu;
2473         char *username;
2474         struct ast_flags flags = { 0 };
2475         char *opts[OPT_ARG_ARRAY_SIZE];
2476         int error = FALSE;
2477         char *message = NULL;
2478         char *prompt = NULL;
2479         int duration;
2480
2481         if (ast_strlen_zero(data))  {
2482                 ast_log(LOG_ERROR, "MinivmAccmess needs at least two arguments: account and option\n");
2483                 error = TRUE;
2484         } else {
2485                 tmpptr = ast_strdupa((char *)data);
2486                 argc = ast_app_separate_args(tmpptr, ',', argv, ARRAY_LEN(argv));
2487         }
2488
2489         if (argc <=1) {
2490                 ast_log(LOG_ERROR, "MinivmAccmess needs at least two arguments: account and option\n");
2491                 error = TRUE;
2492         }
2493         if (!error && strlen(argv[1]) > 1) {
2494                 ast_log(LOG_ERROR, "MinivmAccmess can only handle one option at a time. Bad option string: %s\n", argv[1]);
2495                 error = TRUE;
2496         }
2497
2498         if (!error && ast_app_parse_options(minivm_accmess_options, &flags, opts, argv[1])) {
2499                 ast_log(LOG_ERROR, "Can't parse option %s\n", argv[1]);
2500                 error = TRUE;
2501         }
2502
2503         if (error) {
2504                 pbx_builtin_setvar_helper(chan, "MVM_ACCMESS_STATUS", "FAILED");
2505                 return -1;
2506         }
2507
2508         ast_copy_string(tmp, argv[0], sizeof(tmp));
2509         username = tmp;
2510         domain = strchr(tmp, '@');
2511         if (domain) {
2512                 *domain = '\0';
2513                 domain++;
2514         } 
2515         if (ast_strlen_zero(domain) || ast_strlen_zero(username)) {
2516                 ast_log(LOG_ERROR, "Need username@domain as argument. Sorry. Argument 0 %s\n", argv[0]);
2517                 pbx_builtin_setvar_helper(chan, "MVM_ACCMESS_STATUS", "FAILED");
2518                 return -1;
2519         }
2520
2521         if(!(vmu = find_account(domain, username, TRUE))) {
2522                 /* We could not find user, let's exit */
2523                 ast_log(LOG_WARNING, "Could not allocate temporary memory for '%s@%s'\n", username, domain);
2524                 pbx_builtin_setvar_helper(chan, "MVM_ACCMESS_STATUS", "FAILED");
2525                 return -1;
2526         }
2527
2528         /* Answer channel if it's not already answered */
2529         if (ast_channel_state(chan) != AST_STATE_UP)
2530                 ast_answer(chan);
2531         
2532         /* Here's where the action is */
2533         if (ast_test_flag(&flags, OPT_BUSY_GREETING)) {
2534                 message = "busy";
2535                 prompt = "vm-rec-busy";
2536         } else if (ast_test_flag(&flags, OPT_UNAVAIL_GREETING)) {
2537                 message = "unavailable";
2538                 prompt = "vm-rec-unv";
2539         } else if (ast_test_flag(&flags, OPT_TEMP_GREETING)) {
2540                 message = "temp";
2541                 prompt = "vm-rec-temp";
2542         } else if (ast_test_flag(&flags, OPT_NAME_GREETING)) {
2543                 message = "greet";
2544                 prompt = "vm-rec-name";
2545         }
2546         snprintf(filename,sizeof(filename), "%s%s/%s/%s", MVM_SPOOL_DIR, vmu->domain, vmu->username, message);
2547         /* Maybe we should check the result of play_record_review ? */
2548         play_record_review(chan, prompt, filename, global_maxgreet, default_vmformat, 0, vmu, &duration, NULL, NULL, FALSE);
2549
2550         ast_debug(1, "Recorded new %s message in %s (duration %d)\n", message, filename, duration);
2551
2552         if(ast_test_flag(vmu, MVM_ALLOCED))
2553                 free_user(vmu);
2554
2555         pbx_builtin_setvar_helper(chan, "MVM_ACCMESS_STATUS", "SUCCESS");
2556
2557         /* Ok, we're ready to rock and roll. Return to dialplan */
2558         return 0;
2559 }
2560
2561 /*! \brief Append new mailbox to mailbox list from configuration file */
2562 static int create_vmaccount(char *name, struct ast_variable *var, int realtime)
2563 {
2564         struct minivm_account *vmu;
2565         char *domain;
2566         char *username;
2567         char accbuf[BUFSIZ];
2568
2569         ast_debug(3, "Creating %s account for [%s]\n", realtime ? "realtime" : "static", name);
2570
2571         ast_copy_string(accbuf, name, sizeof(accbuf));
2572         username = accbuf;
2573         domain = strchr(accbuf, '@');
2574         if (domain) {
2575                 *domain = '\0';
2576                 domain++;
2577         }
2578         if (ast_strlen_zero(domain)) {
2579                 ast_log(LOG_ERROR, "No domain given for mini-voicemail account %s. Not configured.\n", name);
2580                 return 0;
2581         }
2582
2583         ast_debug(3, "Creating static account for user %s domain %s\n", username, domain);
2584
2585         /* Allocate user account */
2586         vmu = ast_calloc(1, sizeof(*vmu));
2587         if (!vmu)
2588                 return 0;
2589         
2590         ast_copy_string(vmu->domain, domain, sizeof(vmu->domain));
2591         ast_copy_string(vmu->username, username, sizeof(vmu->username));
2592
2593         populate_defaults(vmu);
2594
2595         ast_debug(3, "...Configuring account %s\n", name);
2596
2597         while (var) {
2598                 ast_debug(3, "Configuring %s = \"%s\" for account %s\n", var->name, var->value, name);
2599                 if (!strcasecmp(var->name, "serveremail")) {
2600                         ast_copy_string(vmu->serveremail, var->value, sizeof(vmu->serveremail));
2601                 } else if (!strcasecmp(var->name, "email")) {
2602                         ast_copy_string(vmu->email, var->value, sizeof(vmu->email));
2603                 } else if (!strcasecmp(var->name, "accountcode")) {
2604                         ast_copy_string(vmu->accountcode, var->value, sizeof(vmu->accountcode));
2605                 } else if (!strcasecmp(var->name, "pincode")) {
2606                         ast_copy_string(vmu->pincode, var->value, sizeof(vmu->pincode));
2607                 } else if (!strcasecmp(var->name, "domain")) {
2608                         ast_copy_string(vmu->domain, var->value, sizeof(vmu->domain));
2609                 } else if (!strcasecmp(var->name, "language")) {
2610                         ast_copy_string(vmu->language, var->value, sizeof(vmu->language));
2611                 } else if (!strcasecmp(var->name, "timezone")) {
2612                         ast_copy_string(vmu->zonetag, var->value, sizeof(vmu->zonetag));
2613                 } else if (!strcasecmp(var->name, "externnotify")) {
2614                         ast_copy_string(vmu->externnotify, var->value, sizeof(vmu->externnotify));
2615                 } else if (!strcasecmp(var->name, "etemplate")) {
2616                         ast_copy_string(vmu->etemplate, var->value, sizeof(vmu->etemplate));
2617                 } else if (!strcasecmp(var->name, "ptemplate")) {
2618                         ast_copy_string(vmu->ptemplate, var->value, sizeof(vmu->ptemplate));
2619                 } else if (!strcasecmp(var->name, "fullname")) {
2620                         ast_copy_string(vmu->fullname, var->value, sizeof(vmu->fullname));
2621                 } else if (!strcasecmp(var->name, "setvar")) {
2622                         char *varval;
2623                         char *varname = ast_strdupa(var->value);
2624                         struct ast_variable *tmpvar;
2625
2626                         if ((varval = strchr(varname, '='))) {
2627                                 *varval = '\0';
2628                                 varval++;
2629                                 if ((tmpvar = ast_variable_new(varname, varval, ""))) {
2630                                         tmpvar->next = vmu->chanvars;
2631                                         vmu->chanvars = tmpvar;
2632                                 }
2633                         }
2634                 } else if (!strcasecmp(var->name, "pager")) {
2635                         ast_copy_string(vmu->pager, var->value, sizeof(vmu->pager));
2636                 } else if (!strcasecmp(var->name, "volgain")) {
2637                         sscanf(var->value, "%30lf", &vmu->volgain);
2638                 } else {
2639                         ast_log(LOG_ERROR, "Unknown configuration option for minivm account %s : %s\n", name, var->name);
2640                 }
2641                 var = var->next;
2642         }
2643         ast_debug(3, "...Linking account %s\n", name);
2644         
2645         AST_LIST_LOCK(&minivm_accounts);
2646         AST_LIST_INSERT_TAIL(&minivm_accounts, vmu, list);
2647         AST_LIST_UNLOCK(&minivm_accounts);
2648
2649         global_stats.voicemailaccounts++;
2650
2651         ast_debug(2, "MVM :: Created account %s@%s - tz %s etemplate %s %s\n", username, domain, ast_strlen_zero(vmu->zonetag) ? "" : vmu->zonetag, ast_strlen_zero(vmu->etemplate) ? "" : vmu->etemplate, realtime ? "(realtime)" : "");
2652         return 0;
2653 }
2654
2655 /*! \brief Free Mini Voicemail timezone */
2656 static void free_zone(struct minivm_zone *z)
2657 {
2658         ast_free(z);
2659 }
2660
2661 /*! \brief Clear list of timezones */
2662 static void timezone_destroy_list(void)
2663 {
2664         struct minivm_zone *this;
2665
2666         AST_LIST_LOCK(&minivm_zones);
2667         while ((this = AST_LIST_REMOVE_HEAD(&minivm_zones, list))) 
2668                 free_zone(this);
2669                 
2670         AST_LIST_UNLOCK(&minivm_zones);
2671 }
2672
2673 /*! \brief Add time zone to memory list */
2674 static int timezone_add(const char *zonename, const char *config)
2675 {
2676         struct minivm_zone *newzone;
2677         char *msg_format, *timezone_str;
2678
2679         newzone = ast_calloc(1, sizeof(*newzone));
2680         if (newzone == NULL)
2681                 return 0;
2682
2683         msg_format = ast_strdupa(config);
2684
2685         timezone_str = strsep(&msg_format, "|");
2686         if (!msg_format) {
2687                 ast_log(LOG_WARNING, "Invalid timezone definition : %s\n", zonename);
2688                 ast_free(newzone);
2689                 return 0;
2690         }
2691                         
2692         ast_copy_string(newzone->name, zonename, sizeof(newzone->name));
2693         ast_copy_string(newzone->timezone, timezone_str, sizeof(newzone->timezone));
2694         ast_copy_string(newzone->msg_format, msg_format, sizeof(newzone->msg_format));
2695
2696         AST_LIST_LOCK(&minivm_zones);
2697         AST_LIST_INSERT_TAIL(&minivm_zones, newzone, list);
2698         AST_LIST_UNLOCK(&minivm_zones);
2699
2700         global_stats.timezones++;
2701
2702         return 0;
2703 }
2704
2705 /*! \brief Read message template from file */
2706 static char *message_template_parse_filebody(const char *filename) {
2707         char buf[BUFSIZ * 6];
2708         char readbuf[BUFSIZ];
2709         char filenamebuf[BUFSIZ];
2710         char *writepos;
2711         char *messagebody;
2712         FILE *fi;
2713         int lines = 0;
2714
2715         if (ast_strlen_zero(filename))
2716                 return NULL;
2717         if (*filename == '/') 
2718                 ast_copy_string(filenamebuf, filename, sizeof(filenamebuf));
2719         else 
2720                 snprintf(filenamebuf, sizeof(filenamebuf), "%s/%s", ast_config_AST_CONFIG_DIR, filename);
2721
2722         if (!(fi = fopen(filenamebuf, "r"))) {
2723                 ast_log(LOG_ERROR, "Can't read message template from file: %s\n", filenamebuf);
2724                 return NULL;
2725         }
2726         writepos = buf;
2727         while (fgets(readbuf, sizeof(readbuf), fi)) {
2728                 lines ++;
2729                 if (writepos != buf) {
2730                         *writepos = '\n';               /* Replace EOL with new line */
2731                         writepos++;
2732                 }
2733                 ast_copy_string(writepos, readbuf, sizeof(buf) - (writepos - buf));
2734                 writepos += strlen(readbuf) - 1;
2735         }
2736         fclose(fi);
2737         messagebody = ast_calloc(1, strlen(buf + 1));
2738         ast_copy_string(messagebody, buf, strlen(buf) + 1);
2739         ast_debug(4, "---> Size of allocation %d\n", (int) strlen(buf + 1) );
2740         ast_debug(4, "---> Done reading message template : \n%s\n---- END message template--- \n", messagebody);
2741
2742         return messagebody;
2743 }
2744
2745 /*! \brief Parse emailbody template from configuration file */
2746 static char *message_template_parse_emailbody(const char *configuration)
2747 {
2748         char *tmpread, *tmpwrite;
2749         char *emailbody = ast_strdup(configuration);
2750
2751         /* substitute strings \t and \n into the apropriate characters */
2752         tmpread = tmpwrite = emailbody;
2753         while ((tmpwrite = strchr(tmpread,'\\'))) {
2754                int len = strlen("\n");
2755                switch (tmpwrite[1]) {
2756                case 'n':
2757                       memmove(tmpwrite + len, tmpwrite + 2, strlen(tmpwrite + 2) + 1);
2758                       strncpy(tmpwrite, "\n", len);
2759                       break;
2760                case 't':
2761                       memmove(tmpwrite + len, tmpwrite + 2, strlen(tmpwrite + 2) + 1);
2762                       strncpy(tmpwrite, "\t", len);
2763                       break;
2764                default:
2765                       ast_log(LOG_NOTICE, "Substitution routine does not support this character: %c\n", tmpwrite[1]);
2766                }
2767                tmpread = tmpwrite + len;
2768         }
2769         return emailbody;       
2770 }
2771
2772 /*! \brief Apply general configuration options */
2773 static int apply_general_options(struct ast_variable *var)
2774 {
2775         int error = 0;
2776
2777         while (var) {
2778                 /* Mail command */
2779                 if (!strcmp(var->name, "mailcmd")) {
2780                         ast_copy_string(global_mailcmd, var->value, sizeof(global_mailcmd)); /* User setting */
2781                 } else if (!strcmp(var->name, "maxgreet")) {
2782                         global_maxgreet = atoi(var->value);
2783                 } else if (!strcmp(var->name, "maxsilence")) {
2784                         global_maxsilence = atoi(var->value);
2785                         if (global_maxsilence > 0)
2786                                 global_maxsilence *= 1000;
2787                 } else if (!strcmp(var->name, "logfile")) {
2788                         if (!ast_strlen_zero(var->value) ) {
2789                                 if(*(var->value) == '/')
2790                                         ast_copy_string(global_logfile, var->value, sizeof(global_logfile));
2791                                 else
2792                                         snprintf(global_logfile, sizeof(global_logfile), "%s/%s", ast_config_AST_LOG_DIR, var->value);
2793                         }
2794                 } else if (!strcmp(var->name, "externnotify")) {
2795                         /* External voicemail notify application */
2796                         ast_copy_string(global_externnotify, var->value, sizeof(global_externnotify));
2797                 } else if (!strcmp(var->name, "silencetreshold")) {
2798                         /* Silence treshold */
2799                         global_silencethreshold = atoi(var->value);
2800                 } else if (!strcmp(var->name, "maxmessage")) {
2801                         int x;
2802                         if (sscanf(var->value, "%30d", &x) == 1) {
2803                                 global_vmmaxmessage = x;
2804                         } else {
2805                                 error ++;
2806                                 ast_log(LOG_WARNING, "Invalid max message time length\n");
2807                         }
2808                 } else if (!strcmp(var->name, "minmessage")) {
2809                         int x;
2810                         if (sscanf(var->value, "%30d", &x) == 1) {
2811                                 global_vmminmessage = x;
2812                                 if (global_maxsilence <= global_vmminmessage)
2813                                         ast_log(LOG_WARNING, "maxsilence should be less than minmessage or you may get empty messages\n");
2814                         } else {
2815                                 error ++;
2816                                 ast_log(LOG_WARNING, "Invalid min message time length\n");
2817                         }
2818                 } else if (!strcmp(var->name, "format")) {
2819                         ast_copy_string(default_vmformat, var->value, sizeof(default_vmformat));
2820                 } else if (!strcmp(var->name, "review")) {
2821                         ast_set2_flag((&globalflags), ast_true(var->value), MVM_REVIEW);        
2822                 } else if (!strcmp(var->name, "operator")) {
2823                         ast_set2_flag((&globalflags), ast_true(var->value), MVM_OPERATOR);      
2824                 }
2825                 var = var->next;
2826         }
2827         return error;
2828 }
2829
2830 /*! \brief Load minivoicemail configuration */
2831 static int load_config(int reload)
2832 {
2833         struct ast_config *cfg;
2834         struct ast_variable *var;
2835         char *cat;
2836         const char *chanvar;
2837         int error = 0;
2838         struct minivm_template *template;
2839         struct ast_flags config_flags = { reload ? CONFIG_FLAG_FILEUNCHANGED : 0 };
2840
2841         cfg = ast_config_load(VOICEMAIL_CONFIG, config_flags);
2842         if (cfg == CONFIG_STATUS_FILEUNCHANGED) {
2843                 return 0;
2844         } else if (cfg == CONFIG_STATUS_FILEINVALID) {
2845                 ast_log(LOG_ERROR, "Config file " VOICEMAIL_CONFIG " is in an invalid format.  Aborting.\n");
2846                 return 0;
2847         }
2848
2849         ast_mutex_lock(&minivmlock);
2850
2851         /* Destroy lists to reconfigure */
2852         message_destroy_list();         /* Destroy list of voicemail message templates */
2853         timezone_destroy_list();        /* Destroy list of timezones */
2854         vmaccounts_destroy_list();      /* Destroy list of voicemail accounts */
2855         ast_debug(2, "Destroyed memory objects...\n");
2856
2857         /* First, set some default settings */
2858         global_externnotify[0] = '\0';
2859         global_logfile[0] = '\0';
2860         global_vmmaxmessage = 2000;
2861         global_maxgreet = 2000;
2862         global_vmminmessage = 0;
2863         strcpy(global_mailcmd, SENDMAIL);
2864         global_maxsilence = 0;
2865         global_saydurationminfo = 2;
2866         ast_copy_string(default_vmformat, "wav", sizeof(default_vmformat));
2867         ast_set2_flag((&globalflags), FALSE, MVM_REVIEW);       
2868         ast_set2_flag((&globalflags), FALSE, MVM_OPERATOR);     
2869         /* Reset statistics */
2870         memset(&global_stats, 0, sizeof(global_stats));
2871         global_stats.reset = ast_tvnow();
2872
2873         global_silencethreshold = ast_dsp_get_threshold_from_settings(THRESHOLD_SILENCE);
2874
2875         /* Make sure we could load configuration file */
2876         if (!cfg) {
2877                 ast_log(LOG_WARNING, "Failed to load configuration file. Module activated with default settings.\n");
2878                 ast_mutex_unlock(&minivmlock);
2879                 return 0;
2880         }
2881
2882         ast_debug(2, "Loaded configuration file, now parsing\n");
2883
2884         /* General settings */
2885
2886         cat = ast_category_browse(cfg, NULL);
2887         while (cat) {
2888                 ast_debug(3, "Found configuration section [%s]\n", cat);
2889                 if (!strcasecmp(cat, "general")) {
2890                         /* Nothing right now */
2891                         error += apply_general_options(ast_variable_browse(cfg, cat));
2892                 } else if (!strncasecmp(cat, "template-", 9))  {
2893                         /* Template */
2894                         char *name = cat + 9;
2895
2896                         /* Now build and link template to list */
2897                         error += message_template_build(name, ast_variable_browse(cfg, cat));
2898                 } else {
2899                         var = ast_variable_browse(cfg, cat);
2900                         if (!strcasecmp(cat, "zonemessages")) {
2901                                 /* Timezones in this context */
2902                                 while (var) {
2903                                         timezone_add(var->name, var->value);
2904                                         var = var->next;
2905                                 }
2906                         } else {
2907                                 /* Create mailbox from this */
2908                                 error += create_vmaccount(cat, var, FALSE);
2909                         }
2910                 }
2911                 /* Find next section in configuration file */
2912                 cat = ast_category_browse(cfg, cat);
2913         }
2914
2915         /* Configure the default email template */
2916         message_template_build("email-default", NULL);
2917         template = message_template_find("email-default");
2918
2919         /* Load date format config for voicemail mail */
2920         if ((chanvar = ast_variable_retrieve(cfg, "general", "emaildateformat"))) 
2921                 ast_copy_string(template->dateformat, chanvar, sizeof(template->dateformat));
2922         if ((chanvar = ast_variable_retrieve(cfg, "general", "emailfromstring")))
2923                 ast_copy_string(template->fromaddress, chanvar, sizeof(template->fromaddress));
2924         if ((chanvar = ast_variable_retrieve(cfg, "general", "emailaaddress")))
2925                 ast_copy_string(template->serveremail, chanvar, sizeof(template->serveremail));
2926         if ((chanvar = ast_variable_retrieve(cfg, "general", "emailcharset")))
2927                 ast_copy_string(template->charset, chanvar, sizeof(template->charset));
2928         if ((chanvar = ast_variable_retrieve(cfg, "general", "emailsubject"))) 
2929                 ast_copy_string(template->subject, chanvar, sizeof(template->subject));
2930         if ((chanvar = ast_variable_retrieve(cfg, "general", "emailbody"))) 
2931                 template->body = message_template_parse_emailbody(chanvar);
2932         template->attachment = TRUE;
2933
2934         message_template_build("pager-default", NULL);
2935         template = message_template_find("pager-default");
2936         if ((chanvar = ast_variable_retrieve(cfg, "general", "pagerfromstring")))
2937                 ast_copy_string(template->fromaddress, chanvar, sizeof(template->fromaddress));
2938         if ((chanvar = ast_variable_retrieve(cfg, "general", "pageraddress")))
2939                 ast_copy_string(template->serveremail, chanvar, sizeof(template->serveremail));
2940         if ((chanvar = ast_variable_retrieve(cfg, "general", "pagercharset")))
2941                 ast_copy_string(template->charset, chanvar, sizeof(template->charset));
2942         if ((chanvar = ast_variable_retrieve(cfg, "general", "pagersubject")))
2943                 ast_copy_string(template->subject, chanvar,sizeof(template->subject));
2944         if ((chanvar = ast_variable_retrieve(cfg, "general", "pagerbody"))) 
2945                 template->body = message_template_parse_emailbody(chanvar);
2946         template->attachment = FALSE;
2947
2948         if (error)
2949                 ast_log(LOG_ERROR, "--- A total of %d errors found in mini-voicemail configuration\n", error);
2950
2951         ast_mutex_unlock(&minivmlock);
2952         ast_config_destroy(cfg);
2953
2954         /* Close log file if it's open and disabled */
2955         if(minivmlogfile)
2956                 fclose(minivmlogfile);
2957
2958         /* Open log file if it's enabled */
2959         if(!ast_strlen_zero(global_logfile)) {
2960                 minivmlogfile = fopen(global_logfile, "a");
2961                 if(!minivmlogfile)
2962                         ast_log(LOG_ERROR, "Failed to open minivm log file %s : %s\n", global_logfile, strerror(errno));
2963                 if (minivmlogfile)
2964                         ast_debug(3, "Opened log file %s \n", global_logfile);
2965         }
2966
2967         return 0;
2968 }
2969
2970 /*! \brief CLI routine for listing templates */
2971 static char *handle_minivm_list_templates(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
2972 {
2973         struct minivm_template *this;
2974 #define HVLT_OUTPUT_FORMAT "%-15s %-10s %-10s %-15.15s %-50s\n"
2975         int count = 0;
2976
2977         switch (cmd) {
2978         case CLI_INIT:
2979                 e->command = "minivm list templates";
2980                 e->usage =
2981                         "Usage: minivm list templates\n"
2982                         "       Lists message templates for e-mail, paging and IM\n";
2983                 return NULL;
2984         case CLI_GENERATE:
2985                 return NULL;
2986         }
2987
2988         if (a->argc > 3)
2989                 return CLI_SHOWUSAGE;
2990
2991         AST_LIST_LOCK(&message_templates);
2992         if (AST_LIST_EMPTY(&message_templates)) {
2993                 ast_cli(a->fd, "There are no message templates defined\n");
2994                 AST_LIST_UNLOCK(&message_templates);
2995                 return CLI_FAILURE;
2996         }
2997         ast_cli(a->fd, HVLT_OUTPUT_FORMAT, "Template name", "Charset", "Locale", "Attach media", "Subject");
2998         ast_cli(a->fd, HVLT_OUTPUT_FORMAT, "-------------", "-------", "------", "------------", "-------");
2999         AST_LIST_TRAVERSE(&message_templates, this, list) {
3000                 ast_cli(a->fd, HVLT_OUTPUT_FORMAT, this->name, 
3001                         this->charset ? this->charset : "-", 
3002                         this->locale ? this->locale : "-",
3003                         this->attachment ? "Yes" : "No",
3004                         this->subject ? this->subject : "-");
3005                 count++;
3006         }
3007         AST_LIST_UNLOCK(&message_templates);
3008         ast_cli(a->fd, "\n * Total: %d minivoicemail message templates\n", count);
3009         return CLI_SUCCESS;
3010 }
3011
3012 static char *complete_minivm_show_users(const char *line, const char *word, int pos, int state)
3013 {
3014         int which = 0;
3015         int wordlen;
3016         struct minivm_account *vmu;
3017         const char *domain = "";
3018
3019         /* 0 - voicemail; 1 - list; 2 - accounts; 3 - for; 4 - <domain> */
3020         if (pos > 4)
3021                 return NULL;
3022         if (pos == 3)
3023                 return (state == 0) ? ast_strdup("for") : NULL;
3024         wordlen = strlen(word);
3025         AST_LIST_TRAVERSE(&minivm_accounts, vmu, list) {
3026                 if (!strncasecmp(word, vmu->domain, wordlen)) {
3027                         if (domain && strcmp(domain, vmu->domain) && ++which > state)
3028                                 return ast_strdup(vmu->domain);
3029                         /* ignore repeated domains ? */
3030                         domain = vmu->domain;
3031                 }
3032         }
3033         return NULL;
3034 }
3035
3036 /*! \brief CLI command to list voicemail accounts */
3037 static char *handle_minivm_show_users(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
3038 {
3039         struct minivm_account *vmu;
3040 #define HMSU_OUTPUT_FORMAT "%-23s %-15s %-15s %-10s %-10s %-50s\n"
3041         int count = 0;
3042
3043         switch (cmd) {
3044         case CLI_INIT:
3045                 e->command = "minivm list accounts";
3046                 e->usage =
3047                         "Usage: minivm list accounts\n"
3048                         "       Lists all mailboxes currently set up\n";
3049                 return NULL;
3050         case CLI_GENERATE:
3051                 return complete_minivm_show_users(a->line, a->word, a->pos, a->n);
3052         }
3053
3054         if ((a->argc < 3) || (a->argc > 5) || (a->argc == 4))
3055                 return CLI_SHOWUSAGE;
3056         if ((a->argc == 5) && strcmp(a->argv[3],"for"))
3057                 return CLI_SHOWUSAGE;
3058
3059         AST_LIST_LOCK(&minivm_accounts);
3060         if (AST_LIST_EMPTY(&minivm_accounts)) {
3061                 ast_cli(a->fd, "There are no voicemail users currently defined\n");
3062                 AST_LIST_UNLOCK(&minivm_accounts);
3063                 return CLI_FAILURE;
3064         }
3065         ast_cli(a->fd, HMSU_OUTPUT_FORMAT, "User", "E-Template", "P-template", "Zone", "Format", "Full name");
3066         ast_cli(a->fd, HMSU_OUTPUT_FORMAT, "----", "----------", "----------", "----", "------", "---------");
3067         AST_LIST_TRAVERSE(&minivm_accounts, vmu, list) {
3068                 char tmp[256] = "";
3069                 if ((a->argc == 3) || ((a->argc == 5) && !strcmp(a->argv[4], vmu->domain))) {
3070                         count++;
3071                         snprintf(tmp, sizeof(tmp), "%s@%s", vmu->username, vmu->domain);
3072                         ast_cli(a->fd, HMSU_OUTPUT_FORMAT, tmp, vmu->etemplate ? vmu->etemplate : "-", 
3073                                 vmu->ptemplate ? vmu->ptemplate : "-",
3074                                 vmu->zonetag ? vmu->zonetag : "-", 
3075                                 vmu->attachfmt ? vmu->attachfmt : "-",
3076