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