Merge "res_musiconhold: Start playlist after initial announcement"
[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         RAII_VAR(struct ast_str *, str1, ast_str_create(16), ast_free);
1236         RAII_VAR(struct ast_str *, str2, ast_str_create(16), ast_free);
1237         FILE *p = NULL;
1238         int pfd;
1239         char email[256] = "";
1240         char who[256] = "";
1241         char date[256];
1242         char bound[256];
1243         char fname[PATH_MAX];
1244         char dur[PATH_MAX];
1245         char tmp[80] = "/tmp/astmail-XXXXXX";
1246         char mail_cmd_buffer[PATH_MAX];
1247         char sox_gain_tmpdir[PATH_MAX] = ""; /* Only used with volgain */
1248         char *file_to_delete = NULL, *dir_to_delete = NULL;
1249         struct timeval now;
1250         struct ast_tm tm;
1251         struct minivm_zone *the_zone = NULL;
1252         struct ast_channel *chan = NULL;
1253         char *fromaddress;
1254         char *fromemail;
1255         int res = -1;
1256
1257         if (!str1 || !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                 return -1;
1273         }
1274
1275         ast_debug(3, "Sending mail to %s@%s - Using template %s\n", vmu->username, vmu->domain, template->name);
1276
1277         if (!strcmp(format, "wav49"))
1278                 format = "WAV";
1279
1280         /* If we have a gain option, process it now with sox */
1281         if (type == MVM_MESSAGE_EMAIL && (vmu->volgain < -.001 || vmu->volgain > .001) ) {
1282                 char sox_gain_cmd[PATH_MAX];
1283
1284                 ast_copy_string(sox_gain_tmpdir, "/tmp/minivm-gain-XXXXXX", sizeof(sox_gain_tmpdir));
1285                 ast_debug(3, "sox_gain_tmpdir: %s\n", sox_gain_tmpdir);
1286                 if (!mkdtemp(sox_gain_tmpdir)) {
1287                         ast_log(LOG_WARNING, "Failed to create temporary directory for volgain: %d\n", errno);
1288                         return -1;
1289                 }
1290                 snprintf(fname, sizeof(fname), "%s/output.%s", sox_gain_tmpdir, format);
1291                 snprintf(sox_gain_cmd, sizeof(sox_gain_cmd), "sox -v %.4f %s.%s %s", vmu->volgain, filename, format, fname);
1292                 ast_safe_system(sox_gain_cmd);
1293                 ast_debug(3, "VOLGAIN: Stored at: %s.%s - Level: %.4f - Mailbox: %s\n", filename, format, vmu->volgain, vmu->username);
1294
1295                 /* Mark some things for deletion */
1296                 file_to_delete = fname;
1297                 dir_to_delete = sox_gain_tmpdir;
1298         } else {
1299                 snprintf(fname, sizeof(fname), "%s.%s", filename, format);
1300         }
1301
1302         if (template->attachment)
1303                 ast_debug(1, "Attaching file '%s', format '%s', uservm is '%d'\n", fname, format, attach_user_voicemail);
1304
1305         /* Make a temporary file instead of piping directly to sendmail, in case the mail
1306            command hangs */
1307         pfd = mkstemp(tmp);
1308         if (pfd > -1) {
1309                 p = fdopen(pfd, "w");
1310                 if (!p) {
1311                         close(pfd);
1312                         pfd = -1;
1313                 }
1314                 ast_debug(1, "Opening temp file for e-mail: %s\n", tmp);
1315         }
1316         if (!p) {
1317                 ast_log(LOG_WARNING, "Unable to open temporary file '%s'\n", tmp);
1318                 goto out;
1319         }
1320         /* Allocate channel used for chanvar substitution */
1321         chan = ast_dummy_channel_alloc();
1322         if (!chan) {
1323                 goto out;
1324         }
1325
1326         snprintf(dur, sizeof(dur), "%d:%02d", duration / 60, duration % 60);
1327
1328         /* Does this user have a timezone specified? */
1329         if (!ast_strlen_zero(vmu->zonetag)) {
1330                 /* Find the zone in the list */
1331                 struct minivm_zone *z;
1332                 AST_LIST_LOCK(&minivm_zones);
1333                 AST_LIST_TRAVERSE(&minivm_zones, z, list) {
1334                         if (strcmp(z->name, vmu->zonetag)) 
1335                                 continue;
1336                         the_zone = z;
1337                 }
1338                 AST_LIST_UNLOCK(&minivm_zones);
1339         }
1340
1341         now = ast_tvnow();
1342         ast_localtime(&now, &tm, the_zone ? the_zone->timezone : NULL);
1343         ast_strftime(date, sizeof(date), "%a, %d %b %Y %H:%M:%S %z", &tm);
1344
1345         /* Start printing the email to the temporary file */
1346         fprintf(p, "Date: %s\n", date);
1347
1348         /* Set date format for voicemail mail */
1349         ast_strftime(date, sizeof(date), template->dateformat, &tm);
1350
1351         /* Populate channel with channel variables for substitution */
1352         prep_email_sub_vars(chan, vmu, cidnum, cidname, dur, date, counter);
1353
1354         /* Find email address to use */
1355         /* If there's a server e-mail address in the account, use that, othterwise template */
1356         fromemail = ast_strlen_zero(vmu->serveremail) ?  template->serveremail : vmu->serveremail;
1357
1358         /* Find name to user for server e-mail */
1359         fromaddress = ast_strlen_zero(template->fromaddress) ? "" : template->fromaddress;
1360
1361         /* If needed, add hostname as domain */
1362         if (ast_strlen_zero(fromemail))
1363                 fromemail = "asterisk";
1364
1365         if (strchr(fromemail, '@'))
1366                 ast_copy_string(who, fromemail, sizeof(who));
1367         else  {
1368                 char host[MAXHOSTNAMELEN];
1369                 gethostname(host, sizeof(host)-1);
1370                 snprintf(who, sizeof(who), "%s@%s", fromemail, host);
1371         }
1372
1373         if (ast_strlen_zero(fromaddress)) {
1374                 fprintf(p, "From: Asterisk PBX <%s>\n", who);
1375         } else {
1376                 ast_debug(4, "Fromaddress template: %s\n", fromaddress);
1377                 ast_str_substitute_variables(&str1, 0, chan, fromaddress);
1378                 if (check_mime(ast_str_buffer(str1))) {
1379                         int first_line = 1;
1380                         char *ptr;
1381                         ast_str_encode_mime(&str2, 0, template->charset, ast_str_buffer(str1), strlen("From: "), strlen(who) + 3);
1382                         while ((ptr = strchr(ast_str_buffer(str2), ' '))) {
1383                                 *ptr = '\0';
1384                                 fprintf(p, "%s %s\n", first_line ? "From:" : "", ast_str_buffer(str2));
1385                                 first_line = 0;
1386                                 /* Substring is smaller, so this will never grow */
1387                                 ast_str_set(&str2, 0, "%s", ptr + 1);
1388                         }
1389                         fprintf(p, "%s %s <%s>\n", first_line ? "From:" : "", ast_str_buffer(str2), who);
1390                 } else {
1391                         fprintf(p, "From: %s <%s>\n", ast_str_quote(&str2, 0, ast_str_buffer(str1)), who);
1392                 }
1393         } 
1394
1395         fprintf(p, "Message-ID: <Asterisk-%u-%s-%d-%s>\n", (unsigned int)ast_random(), vmu->username, (int)getpid(), who);
1396
1397         if (ast_strlen_zero(vmu->email)) {
1398                 snprintf(email, sizeof(email), "%s@%s", vmu->username, vmu->domain);
1399         } else {
1400                 ast_copy_string(email, vmu->email, sizeof(email));
1401         }
1402
1403         if (check_mime(vmu->fullname)) {
1404                 int first_line = 1;
1405                 char *ptr;
1406                 ast_str_encode_mime(&str2, 0, template->charset, vmu->fullname, strlen("To: "), strlen(email) + 3);
1407                 while ((ptr = strchr(ast_str_buffer(str2), ' '))) {
1408                         *ptr = '\0';
1409                         fprintf(p, "%s %s\n", first_line ? "To:" : "", ast_str_buffer(str2));
1410                         first_line = 0;
1411                         /* Substring is smaller, so this will never grow */
1412                         ast_str_set(&str2, 0, "%s", ptr + 1);
1413                 }
1414                 fprintf(p, "%s %s <%s>\n", first_line ? "To:" : "", ast_str_buffer(str2), email);
1415         } else {
1416                 fprintf(p, "To: %s <%s>\n", ast_str_quote(&str2, 0, vmu->fullname), email);
1417         }
1418
1419         if (!ast_strlen_zero(template->subject)) {
1420                 ast_str_substitute_variables(&str1, 0, chan, template->subject);
1421                 if (check_mime(ast_str_buffer(str1))) {
1422                         int first_line = 1;
1423                         char *ptr;
1424                         ast_str_encode_mime(&str2, 0, template->charset, ast_str_buffer(str1), strlen("Subject: "), 0);
1425                         while ((ptr = strchr(ast_str_buffer(str2), ' '))) {
1426                                 *ptr = '\0';
1427                                 fprintf(p, "%s %s\n", first_line ? "Subject:" : "", ast_str_buffer(str2));
1428                                 first_line = 0;
1429                                 /* Substring is smaller, so this will never grow */
1430                                 ast_str_set(&str2, 0, "%s", ptr + 1);
1431                         }
1432                         fprintf(p, "%s %s\n", first_line ? "Subject:" : "", ast_str_buffer(str2));
1433                 } else {
1434                         fprintf(p, "Subject: %s\n", ast_str_buffer(str1));
1435                 }
1436         } else {
1437                 fprintf(p, "Subject: New message in mailbox %s@%s\n", vmu->username, vmu->domain);
1438                 ast_debug(1, "Using default subject for this email \n");
1439         }
1440
1441         if (option_debug > 2)
1442                 fprintf(p, "X-Asterisk-debug: template %s user account %s@%s\n", template->name, vmu->username, vmu->domain);
1443         fprintf(p, "MIME-Version: 1.0\n");
1444
1445         /* Something unique. */
1446         snprintf(bound, sizeof(bound), "voicemail_%s%d%u", vmu->username, (int)getpid(), (unsigned int)ast_random());
1447
1448         fprintf(p, "Content-Type: multipart/mixed; boundary=\"%s\"\n\n\n", bound);
1449
1450         fprintf(p, "--%s\n", bound);
1451         fprintf(p, "Content-Type: text/plain; charset=%s\nContent-Transfer-Encoding: 8bit\n\n", template->charset);
1452         if (!ast_strlen_zero(template->body)) {
1453                 ast_str_substitute_variables(&str1, 0, chan, template->body);
1454                 ast_debug(3, "Message now: %s\n-----\n", ast_str_buffer(str1));
1455                 fprintf(p, "%s\n", ast_str_buffer(str1));
1456         } else {
1457                 fprintf(p, "Dear %s:\n\n\tJust wanted to let you know you were just left a %s long message \n"
1458                         "in mailbox %s from %s, on %s so you might\n"
1459                         "want to check it when you get a chance.  Thanks!\n\n\t\t\t\t--Asterisk\n\n", vmu->fullname, 
1460                         dur,  vmu->username, (cidname ? cidname : (cidnum ? cidnum : "an unknown caller")), date);
1461                 ast_debug(3, "Using default message body (no template)\n-----\n");
1462         }
1463         /* Eww. We want formats to tell us their own MIME type */
1464         if (template->attachment) {
1465                 char *ctype = "audio/x-";
1466                 ast_debug(3, "Attaching file to message: %s\n", fname);
1467                 if (!strcasecmp(format, "ogg"))
1468                         ctype = "application/";
1469
1470                 fprintf(p, "--%s\n", bound);
1471                 fprintf(p, "Content-Type: %s%s; name=\"voicemailmsg.%s\"\n", ctype, format, format);
1472                 fprintf(p, "Content-Transfer-Encoding: base64\n");
1473                 fprintf(p, "Content-Description: Voicemail sound attachment.\n");
1474                 fprintf(p, "Content-Disposition: attachment; filename=\"voicemail%s.%s\"\n\n", counter ? counter : "", format);
1475
1476                 base_encode(fname, p);
1477                 fprintf(p, "\n\n--%s--\n.\n", bound);
1478         }
1479         fclose(p);
1480
1481         chan = ast_channel_unref(chan);
1482
1483         if (file_to_delete && dir_to_delete) {
1484                 /* We can't delete these files ourselves because the mail command will execute in
1485                    the background and we'll end up deleting them out from under it. */
1486                 res = snprintf(mail_cmd_buffer, sizeof(mail_cmd_buffer),
1487                                            "( %s < %s ; rm -f %s %s ; rmdir %s ) &",
1488                                            global_mailcmd, tmp, tmp, file_to_delete, dir_to_delete);
1489         } else {
1490                 res = snprintf(mail_cmd_buffer, sizeof(mail_cmd_buffer),
1491                                            "( %s < %s ; rm -f %s ) &",
1492                                            global_mailcmd, tmp, tmp);
1493         }
1494
1495         if (res < sizeof(mail_cmd_buffer)) {
1496                 file_to_delete = dir_to_delete = NULL;
1497         } else {
1498                 ast_log(LOG_ERROR, "Could not send message, command line too long\n");
1499                 res = -1;
1500                 goto out;
1501         }
1502
1503         ast_safe_system(mail_cmd_buffer);
1504         ast_debug(1, "Sent message to %s with command '%s'%s\n", vmu->email, global_mailcmd, template->attachment ? " - (media attachment)" : "");
1505         ast_debug(3, "Actual command used: %s\n", mail_cmd_buffer);
1506
1507         res = 0;
1508
1509 out:
1510         if (file_to_delete) {
1511                 unlink(file_to_delete);
1512         }
1513
1514         if (dir_to_delete) {
1515                 rmdir(dir_to_delete);
1516         }
1517
1518         return res;
1519 }
1520
1521 /*!\internal
1522  * \brief Create directory based on components */
1523 static int make_dir(char *dest, int len, const char *domain, const char *username, const char *folder)
1524 {
1525         return snprintf(dest, len, "%s%s/%s%s%s", MVM_SPOOL_DIR, domain, username, ast_strlen_zero(folder) ? "" : "/", folder ? folder : "");
1526 }
1527
1528 /*!\internal
1529  * \brief Checks if directory exists. Does not create directory, but builds string in dest
1530  * \param dest    String. base directory.
1531  * \param len    Int. Length base directory string.
1532  * \param domain String. Ignored if is null or empty string.
1533  * \param username String. Ignored if is null or empty string. 
1534  * \param folder  String. Ignored if is null or empty string.
1535  * \return 0 on failure, 1 on success.
1536  */
1537 static int check_dirpath(char *dest, int len, char *domain, char *username, char *folder)
1538 {
1539         struct stat filestat;
1540         make_dir(dest, len, domain, username, folder ? folder : "");
1541         if (stat(dest, &filestat)== -1)
1542                 return FALSE;
1543         else
1544                 return TRUE;
1545 }
1546
1547 /*!\internal
1548  * \brief basically mkdir -p $dest/$domain/$username/$folder
1549  * \param dest    String. base directory.
1550  * \param len     Length of directory string
1551  * \param domain  String. Ignored if is null or empty string.
1552  * \param folder  String. Ignored if is null or empty string.
1553  * \param username  String. Ignored if is null or empty string.
1554  * \return -1 on failure, 0 on success.
1555  */
1556 static int create_dirpath(char *dest, int len, char *domain, char *username, char *folder)
1557 {
1558         int res;
1559         make_dir(dest, len, domain, username, folder);
1560         if ((res = ast_mkdir(dest, 0777))) {
1561                 ast_log(LOG_WARNING, "ast_mkdir '%s' failed: %s\n", dest, strerror(res));
1562                 return -1;
1563         }
1564         ast_debug(2, "Creating directory for %s@%s folder %s : %s\n", username, domain, folder, dest);
1565         return 0;
1566 }
1567
1568
1569 /*!\internal
1570  * \brief Play intro message before recording voicemail
1571  */
1572 static int invent_message(struct ast_channel *chan, char *domain, char *username, int busy, char *ecodes)
1573 {
1574         int res;
1575         char fn[PATH_MAX];
1576
1577         ast_debug(2, "Still preparing to play message ...\n");
1578
1579         snprintf(fn, sizeof(fn), "%s%s/%s/greet", MVM_SPOOL_DIR, domain, username);
1580
1581         if (ast_fileexists(fn, NULL, NULL) > 0) {
1582                 res = ast_streamfile(chan, fn, ast_channel_language(chan));
1583                 if (res) 
1584                         return -1;
1585                 res = ast_waitstream(chan, ecodes);
1586                 if (res) 
1587                         return res;
1588         } else {
1589                 int numericusername = 1;
1590                 char *i = username;
1591
1592                 ast_debug(2, "No personal prompts. Using default prompt set for language\n");
1593
1594                 while (*i)  {
1595                         ast_debug(2, "Numeric? Checking %c\n", *i);
1596                         if (!isdigit(*i)) {
1597                                 numericusername = FALSE;
1598                                 break;
1599                         }
1600                         i++;
1601                 }
1602
1603                 if (numericusername) {
1604                         if (ast_streamfile(chan, "vm-theperson", ast_channel_language(chan)))
1605                                 return -1;
1606                         if ((res = ast_waitstream(chan, ecodes)))
1607                                 return res;
1608
1609                         res = ast_say_digit_str(chan, username, ecodes, ast_channel_language(chan));
1610                         if (res)
1611                                 return res;
1612                 } else {
1613                         if (ast_streamfile(chan, "vm-theextensionis", ast_channel_language(chan)))
1614                                 return -1;
1615                         if ((res = ast_waitstream(chan, ecodes)))
1616                                 return res;
1617                 }
1618         }
1619
1620         res = ast_streamfile(chan, busy ? "vm-isonphone" : "vm-isunavail", ast_channel_language(chan));
1621         if (res)
1622                 return -1;
1623         res = ast_waitstream(chan, ecodes);
1624         return res;
1625 }
1626
1627 /*!\internal
1628  * \brief Delete media files and attribute file */
1629 static int vm_delete(char *file)
1630 {
1631         int res;
1632
1633         ast_debug(1, "Deleting voicemail file %s\n", file);
1634
1635         res = unlink(file);     /* Remove the meta data file */
1636         res |=  ast_filedelete(file, NULL);     /* remove the media file */
1637         return res;
1638 }
1639
1640
1641 /*!\internal
1642  * \brief Record voicemail message & let caller review or re-record it, or set options if applicable */
1643 static int play_record_review(struct ast_channel *chan, char *playfile, char *recordfile, int maxtime, char *fmt,
1644                               int outsidecaller, struct minivm_account *vmu, int *duration, int *sound_duration, const char *unlockdir,
1645                               signed char record_gain)
1646 {
1647         int cmd = 0;
1648         int max_attempts = 3;
1649         int attempts = 0;
1650         int recorded = 0;
1651         int message_exists = 0;
1652         signed char zero_gain = 0;
1653         char *acceptdtmf = "#";
1654         char *canceldtmf = "";
1655
1656         /* Note that urgent and private are for flagging messages as such in the future */
1657
1658         /* barf if no pointer passed to store duration in */
1659         if (duration == NULL) {
1660                 ast_log(LOG_WARNING, "Error play_record_review called without duration pointer\n");
1661                 return -1;
1662         }
1663
1664         cmd = '3';       /* Want to start by recording */
1665
1666         while ((cmd >= 0) && (cmd != 't')) {
1667                 switch (cmd) {
1668                 case '1':
1669                         ast_verb(3, "Saving message as is\n");
1670                         ast_stream_and_wait(chan, "vm-msgsaved", "");
1671                         cmd = 't';
1672                         break;
1673                 case '2':
1674                         /* Review */
1675                         ast_verb(3, "Reviewing the message\n");
1676                         ast_streamfile(chan, recordfile, ast_channel_language(chan));
1677                         cmd = ast_waitstream(chan, AST_DIGIT_ANY);
1678                         break;
1679                 case '3':
1680                         message_exists = 0;
1681                         /* Record */
1682                         if (recorded == 1) 
1683                                 ast_verb(3, "Re-recording the message\n");
1684                         else
1685                                 ast_verb(3, "Recording the message\n");
1686                         if (recorded && outsidecaller) 
1687                                 cmd = ast_play_and_wait(chan, "beep");
1688                         recorded = 1;
1689                         /* 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 */
1690                         if (record_gain)
1691                                 ast_channel_setoption(chan, AST_OPTION_RXGAIN, &record_gain, sizeof(record_gain), 0);
1692                         if (ast_test_flag(vmu, MVM_OPERATOR))
1693                                 canceldtmf = "0";
1694                         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);
1695                         if (record_gain)
1696                                 ast_channel_setoption(chan, AST_OPTION_RXGAIN, &zero_gain, sizeof(zero_gain), 0);
1697                         if (cmd == -1) /* User has hung up, no options to give */
1698                                 return cmd;
1699                         if (cmd == '0')
1700                                 break;
1701                         else if (cmd == '*')
1702                                 break;
1703                         else {
1704                                 /* If all is well, a message exists */
1705                                 message_exists = 1;
1706                                 cmd = 0;
1707                         }
1708                         break;
1709                 case '4':
1710                 case '5':
1711                 case '6':
1712                 case '7':
1713                 case '8':
1714                 case '9':
1715                 case '*':
1716                 case '#':
1717                         cmd = ast_play_and_wait(chan, "vm-sorry");
1718                         break;
1719                 case '0':
1720                         if(!ast_test_flag(vmu, MVM_OPERATOR)) {
1721                                 cmd = ast_play_and_wait(chan, "vm-sorry");
1722                                 break;
1723                         }
1724                         if (message_exists || recorded) {
1725                                 cmd = ast_play_and_wait(chan, "vm-saveoper");
1726                                 if (!cmd)
1727                                         cmd = ast_waitfordigit(chan, 3000);
1728                                 if (cmd == '1') {
1729                                         ast_play_and_wait(chan, "vm-msgsaved");
1730                                         cmd = '0';
1731                                 } else {
1732                                         ast_play_and_wait(chan, "vm-deleted");
1733                                         vm_delete(recordfile);
1734                                         cmd = '0';
1735                                 }
1736                         }
1737                         return cmd;
1738                 default:
1739                         /* If the caller is an ouside caller, and the review option is enabled,
1740                            allow them to review the message, but let the owner of the box review
1741                            their OGM's */
1742                         if (outsidecaller && !ast_test_flag(vmu, MVM_REVIEW))
1743                                 return cmd;
1744                         if (message_exists) {
1745                                 cmd = ast_play_and_wait(chan, "vm-review");
1746                         } else {
1747                                 cmd = ast_play_and_wait(chan, "vm-torerecord");
1748                                 if (!cmd)
1749                                         cmd = ast_waitfordigit(chan, 600);
1750                         }
1751
1752                         if (!cmd && outsidecaller && ast_test_flag(vmu, MVM_OPERATOR)) {
1753                                 cmd = ast_play_and_wait(chan, "vm-reachoper");
1754                                 if (!cmd)
1755                                         cmd = ast_waitfordigit(chan, 600);
1756                         }
1757                         if (!cmd)
1758                                 cmd = ast_waitfordigit(chan, 6000);
1759                         if (!cmd) {
1760                                 attempts++;
1761                         }
1762                         if (attempts > max_attempts) {
1763                                 cmd = 't';
1764                         }
1765                 }
1766         }
1767         if (outsidecaller)
1768                 ast_play_and_wait(chan, "vm-goodbye");
1769         if (cmd == 't')
1770                 cmd = 0;
1771         return cmd;
1772 }
1773
1774 /*! \brief Run external notification for voicemail message */
1775 static void run_externnotify(struct ast_channel *chan, struct minivm_account *vmu)
1776 {
1777         char fquser[AST_MAX_CONTEXT * 2];
1778         char *argv[5] = { NULL };
1779         struct ast_party_caller *caller;
1780         char *cid;
1781         int idx;
1782
1783         if (ast_strlen_zero(vmu->externnotify) && ast_strlen_zero(global_externnotify)) {
1784                 return;
1785         }
1786
1787         snprintf(fquser, sizeof(fquser), "%s@%s", vmu->username, vmu->domain);
1788
1789         caller = ast_channel_caller(chan);
1790         idx = 0;
1791         argv[idx++] = ast_strlen_zero(vmu->externnotify) ? global_externnotify : vmu->externnotify;
1792         argv[idx++] = fquser;
1793         cid = S_COR(caller->id.name.valid, caller->id.name.str, NULL);
1794         if (cid) {
1795                 argv[idx++] = cid;
1796         }
1797         cid = S_COR(caller->id.number.valid, caller->id.number.str, NULL);
1798         if (cid) {
1799                 argv[idx++] = cid;
1800         }
1801         argv[idx] = NULL;
1802
1803         ast_debug(1, "Executing: %s %s %s %s\n",
1804                 argv[0], argv[1], argv[2] ?: "", argv[3] ?: "");
1805         ast_safe_execvp(1, argv[0], argv);
1806 }
1807
1808 /*!\internal
1809  * \brief Send message to voicemail account owner */
1810 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)
1811 {
1812         RAII_VAR(struct ast_json *, json_object, NULL, ast_json_unref);
1813         RAII_VAR(struct stasis_message *, message, NULL, ao2_cleanup);
1814         RAII_VAR(struct ast_mwi_state *, mwi_state, NULL, ao2_cleanup);
1815         char *stringp;
1816         struct minivm_template *etemplate;
1817         char *messageformat;
1818         int res = 0;
1819         char oldlocale[100];
1820         const char *counter;
1821
1822         if (!ast_strlen_zero(vmu->attachfmt)) {
1823                 if (strstr(format, vmu->attachfmt)) {
1824                         format = vmu->attachfmt;
1825                 } else {
1826                         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);
1827                 }
1828         }
1829
1830         etemplate = message_template_find(vmu->etemplate);
1831         if (!etemplate)
1832                 etemplate = message_template_find(templatename);
1833         if (!etemplate)
1834                 etemplate = message_template_find("email-default");
1835
1836         /* Attach only the first format */
1837         stringp = messageformat = ast_strdupa(format);
1838         strsep(&stringp, "|");
1839
1840         if (!ast_strlen_zero(etemplate->locale)) {
1841                 char *new_locale;
1842                 ast_copy_string(oldlocale, setlocale(LC_TIME, NULL), sizeof(oldlocale));
1843                 ast_debug(2, "Changing locale from %s to %s\n", oldlocale, etemplate->locale);
1844                 new_locale = setlocale(LC_TIME, etemplate->locale);
1845                 if (new_locale == NULL) {
1846                         ast_log(LOG_WARNING, "-_-_- Changing to new locale did not work. Locale: %s\n", etemplate->locale);
1847                 }
1848         }
1849
1850
1851
1852         /* Read counter if available */
1853         ast_channel_lock(chan);
1854         if ((counter = pbx_builtin_getvar_helper(chan, "MVM_COUNTER"))) {
1855                 counter = ast_strdupa(counter);
1856         }
1857         ast_channel_unlock(chan);
1858
1859         if (ast_strlen_zero(counter)) {
1860                 ast_debug(2, "MVM_COUNTER not found\n");
1861         } else {
1862                 ast_debug(2, "MVM_COUNTER found - will use it with value %s\n", counter);
1863         }
1864
1865         res = sendmail(etemplate, vmu, cidnum, cidname, filename, messageformat, duration, etemplate->attachment, MVM_MESSAGE_EMAIL, counter);
1866
1867         if (res == 0 && !ast_strlen_zero(vmu->pager))  {
1868                 /* Find template for paging */
1869                 etemplate = message_template_find(vmu->ptemplate);
1870                 if (!etemplate)
1871                         etemplate = message_template_find("pager-default");
1872
1873                 if (!ast_strlen_zero(etemplate->locale)) {
1874                         ast_copy_string(oldlocale, setlocale(LC_TIME, ""), sizeof(oldlocale));
1875                         setlocale(LC_TIME, etemplate->locale);
1876                 }
1877
1878                 res = sendmail(etemplate, vmu, cidnum, cidname, filename, messageformat, duration, etemplate->attachment, MVM_MESSAGE_PAGE, counter);
1879         }
1880
1881         mwi_state = ast_mwi_create(vmu->username, vmu->domain);
1882         if (!mwi_state) {
1883                 goto notify_cleanup;
1884         }
1885         mwi_state->snapshot = ast_channel_snapshot_get_latest(ast_channel_uniqueid(chan));
1886
1887         json_object = ast_json_pack("{s: s, s: s, s: s}",
1888                 "Event", "MiniVoiceMail",
1889                 "Action", "SentNotification",
1890                 "Counter", counter ?: "");
1891         if (!json_object) {
1892                 goto notify_cleanup;
1893         }
1894         message = ast_mwi_blob_create(mwi_state, ast_mwi_vm_app_type(), json_object);
1895         if (!message) {
1896                 goto notify_cleanup;
1897         }
1898         stasis_publish(ast_mwi_topic(mwi_state->uniqueid), message);
1899
1900 notify_cleanup:
1901         run_externnotify(chan, vmu);            /* Run external notification */
1902         if (!ast_strlen_zero(etemplate->locale)) {
1903                 setlocale(LC_TIME, oldlocale);  /* Reset to old locale */
1904         }
1905         return res;
1906 }
1907
1908  
1909 /*!\internal
1910  * \brief Record voicemail message, store into file prepared for sending e-mail */
1911 static int leave_voicemail(struct ast_channel *chan, char *username, struct leave_vm_options *options)
1912 {
1913         char tmptxtfile[PATH_MAX];
1914         char callerid[256];
1915         FILE *txt;
1916         int res = 0, txtdes;
1917         int duration = 0;
1918         int sound_duration = 0;
1919         char date[256];
1920         char tmpdir[PATH_MAX];
1921         char ext_context[256] = "";
1922         char fmt[80];
1923         char *domain;
1924         char tmp[256] = "";
1925         struct minivm_account *vmu;
1926         int userdir;
1927
1928         ast_copy_string(tmp, username, sizeof(tmp));
1929         username = tmp;
1930         domain = strchr(tmp, '@');
1931         if (domain) {
1932                 *domain = '\0';
1933                 domain++;
1934         }
1935
1936         if (!(vmu = find_account(domain, username, TRUE))) {
1937                 /* We could not find user, let's exit */
1938                 ast_log(LOG_ERROR, "Can't allocate temporary account for '%s@%s'\n", username, domain);
1939                 pbx_builtin_setvar_helper(chan, "MVM_RECORD_STATUS", "FAILED");
1940                 return 0;
1941         }
1942
1943         /* Setup pre-file if appropriate */
1944         if (strcmp(vmu->domain, "localhost"))
1945                 snprintf(ext_context, sizeof(ext_context), "%s@%s", username, vmu->domain);
1946         else
1947                 ast_copy_string(ext_context, vmu->domain, sizeof(ext_context));
1948
1949         /* The meat of recording the message...  All the announcements and beeps have been played*/
1950         if (ast_strlen_zero(vmu->attachfmt))
1951                 ast_copy_string(fmt, default_vmformat, sizeof(fmt));
1952         else
1953                 ast_copy_string(fmt, vmu->attachfmt, sizeof(fmt));
1954
1955         if (ast_strlen_zero(fmt)) {
1956                 ast_log(LOG_WARNING, "No format for saving voicemail? Default %s\n", default_vmformat);
1957                 pbx_builtin_setvar_helper(chan, "MVM_RECORD_STATUS", "FAILED");
1958                 return res;
1959         }
1960
1961         userdir = check_dirpath(tmpdir, sizeof(tmpdir), vmu->domain, username, "tmp");
1962
1963         /* If we have no user directory, use generic temporary directory */
1964         if (!userdir) {
1965                 create_dirpath(tmpdir, sizeof(tmpdir), "0000_minivm_temp", "mediafiles", "");
1966                 ast_debug(3, "Creating temporary directory %s\n", tmpdir);
1967         }
1968
1969
1970         snprintf(tmptxtfile, sizeof(tmptxtfile), "%s/XXXXXX", tmpdir);
1971
1972         /* XXX This file needs to be in temp directory */
1973         txtdes = mkstemp(tmptxtfile);
1974         if (txtdes < 0) {
1975                 ast_log(LOG_ERROR, "Unable to create message file %s: %s\n", tmptxtfile, strerror(errno));
1976                 res = ast_streamfile(chan, "vm-mailboxfull", ast_channel_language(chan));
1977                 if (!res)
1978                         res = ast_waitstream(chan, "");
1979                 pbx_builtin_setvar_helper(chan, "MVM_RECORD_STATUS", "FAILED");
1980                 return res;
1981         }
1982
1983         if (res >= 0) {
1984                 /* Unless we're *really* silent, try to send the beep */
1985                 res = ast_streamfile(chan, "beep", ast_channel_language(chan));
1986                 if (!res)
1987                         res = ast_waitstream(chan, "");
1988         }
1989
1990         /* OEJ XXX Maybe this can be turned into a log file? Hmm. */
1991         /* Store information */
1992         ast_debug(2, "Open file for metadata: %s\n", tmptxtfile);
1993
1994         res = play_record_review(chan, NULL, tmptxtfile, global_vmmaxmessage, fmt, 1, vmu, &duration, &sound_duration, NULL, options->record_gain);
1995
1996         txt = fdopen(txtdes, "w+");
1997         if (!txt) {
1998                 ast_log(LOG_WARNING, "Error opening text file for output\n");
1999         } else {
2000                 struct ast_tm tm;
2001                 struct timeval now = ast_tvnow();
2002                 char timebuf[30];
2003                 char logbuf[BUFSIZ];
2004                 get_date(date, sizeof(date));
2005                 ast_localtime(&now, &tm, NULL);
2006                 ast_strftime(timebuf, sizeof(timebuf), "%H:%M:%S", &tm);
2007
2008                 ast_callerid_merge(callerid, sizeof(callerid),
2009                         S_COR(ast_channel_caller(chan)->id.name.valid, ast_channel_caller(chan)->id.name.str, NULL),
2010                         S_COR(ast_channel_caller(chan)->id.number.valid, ast_channel_caller(chan)->id.number.str, NULL),
2011                         "Unknown");
2012                 snprintf(logbuf, sizeof(logbuf),
2013                         /* "Mailbox:domain:macrocontext:exten:priority:callerchan:callerid:origdate:origtime:duration:durationstatus:accountcode" */
2014                         "%s:%s:%s:%s:%d:%s:%s:%s:%s:%d:%s:%s\n",
2015                         username,
2016                         ast_channel_context(chan),
2017                         ast_channel_macrocontext(chan), 
2018                         ast_channel_exten(chan),
2019                         ast_channel_priority(chan),
2020                         ast_channel_name(chan),
2021                         callerid,
2022                         date, 
2023                         timebuf,
2024                         duration,
2025                         duration < global_vmminmessage ? "IGNORED" : "OK",
2026                         vmu->accountcode
2027                 ); 
2028                 fprintf(txt, "%s", logbuf);
2029                 if (minivmlogfile) {
2030                         ast_mutex_lock(&minivmloglock);
2031                         fprintf(minivmlogfile, "%s", logbuf);
2032                         ast_mutex_unlock(&minivmloglock);
2033                 }
2034
2035                 if (sound_duration < global_vmminmessage) {
2036                         ast_verb(3, "Recording was %d seconds long but needs to be at least %d - abandoning\n", sound_duration, global_vmminmessage);
2037                         fclose(txt);
2038                         ast_filedelete(tmptxtfile, NULL);
2039                         unlink(tmptxtfile);
2040                         pbx_builtin_setvar_helper(chan, "MVM_RECORD_STATUS", "FAILED");
2041                         return 0;
2042                 } 
2043                 fclose(txt); /* Close log file */
2044                 if (ast_fileexists(tmptxtfile, NULL, NULL) <= 0) {
2045                         ast_debug(1, "The recorded media file is gone, so we should remove the .txt file too!\n");
2046                         unlink(tmptxtfile);
2047                         pbx_builtin_setvar_helper(chan, "MVM_RECORD_STATUS", "FAILED");
2048                         if(ast_test_flag(vmu, MVM_ALLOCED))
2049                                 free_user(vmu);
2050                         return 0;
2051                 }
2052
2053                 /* Set channel variables for the notify application */
2054                 pbx_builtin_setvar_helper(chan, "MVM_FILENAME", tmptxtfile);
2055                 snprintf(timebuf, sizeof(timebuf), "%d", duration);
2056                 pbx_builtin_setvar_helper(chan, "MVM_DURATION", timebuf);
2057                 pbx_builtin_setvar_helper(chan, "MVM_FORMAT", fmt);
2058
2059         }
2060         global_stats.lastreceived = ast_tvnow();
2061         global_stats.receivedmessages++;
2062 #if 0
2063         /* Go ahead and delete audio files from system, they're not needed any more */
2064         if (ast_fileexists(tmptxtfile, NULL, NULL) <= 0) {
2065                 ast_filedelete(tmptxtfile, NULL);
2066                  /* Even not being used at the moment, it's better to convert ast_log to ast_debug anyway */
2067                 ast_debug(2, "-_-_- Deleted audio file after notification :: %s \n", tmptxtfile);
2068         }
2069 #endif
2070
2071         if (res > 0)
2072                 res = 0;
2073
2074         if(ast_test_flag(vmu, MVM_ALLOCED))
2075                 free_user(vmu);
2076
2077         pbx_builtin_setvar_helper(chan, "MVM_RECORD_STATUS", "SUCCESS");
2078         return res;
2079 }
2080
2081 /*!\internal
2082  * \brief Queue a message waiting event */
2083 static void queue_mwi_event(const char *channel_id, const char *mbx, const char *ctx, int urgent, int new, int old)
2084 {
2085         char *mailbox, *context;
2086
2087         mailbox = ast_strdupa(mbx);
2088         context = ast_strdupa(ctx);
2089         if (ast_strlen_zero(context)) {
2090                 context = "default";
2091         }
2092
2093         ast_publish_mwi_state_channel(mailbox, context, new + urgent, old, channel_id);
2094 }
2095
2096 /*!\internal
2097  * \brief Send MWI using interal Asterisk event subsystem */
2098 static int minivm_mwi_exec(struct ast_channel *chan, const char *data)
2099 {
2100         int argc;
2101         char *argv[4];
2102         int res = 0;
2103         char *tmpptr;
2104         char tmp[PATH_MAX];
2105         char *mailbox;
2106         char *domain;
2107         if (ast_strlen_zero(data))  {
2108                 ast_log(LOG_ERROR, "Minivm needs at least an account argument \n");
2109                 return -1;
2110         }
2111         tmpptr = ast_strdupa((char *)data);
2112         argc = ast_app_separate_args(tmpptr, ',', argv, ARRAY_LEN(argv));
2113         if (argc < 4) {
2114                 ast_log(LOG_ERROR, "%d arguments passed to MiniVM_MWI, need 4.\n", argc);
2115                 return -1;
2116         }
2117         ast_copy_string(tmp, argv[0], sizeof(tmp));
2118         mailbox = tmp;
2119         domain = strchr(tmp, '@');
2120         if (domain) {
2121                 *domain = '\0';
2122                 domain++;
2123         }
2124         if (ast_strlen_zero(domain) || ast_strlen_zero(mailbox)) {
2125                 ast_log(LOG_ERROR, "Need mailbox@context as argument. Sorry. Argument 0 %s\n", argv[0]);
2126                 return -1;
2127         }
2128         queue_mwi_event(ast_channel_uniqueid(chan), mailbox, domain, atoi(argv[1]), atoi(argv[2]), atoi(argv[3]));
2129
2130         return res;
2131 }
2132
2133
2134 /*!\internal
2135  * \brief Notify voicemail account owners - either generic template or user specific */
2136 static int minivm_notify_exec(struct ast_channel *chan, const char *data)
2137 {
2138         int argc;
2139         char *argv[2];
2140         int res = 0;
2141         char tmp[PATH_MAX];
2142         char *domain;
2143         char *tmpptr;
2144         struct minivm_account *vmu;
2145         char *username;
2146         const char *template = "";
2147         const char *filename;
2148         const char *format;
2149         const char *duration_string;
2150         if (ast_strlen_zero(data))  {
2151                 ast_log(LOG_ERROR, "Minivm needs at least an account argument \n");
2152                 return -1;
2153         }
2154         tmpptr = ast_strdupa((char *)data);
2155         argc = ast_app_separate_args(tmpptr, ',', argv, ARRAY_LEN(argv));
2156
2157         if (argc == 2 && !ast_strlen_zero(argv[1]))
2158                 template = argv[1];
2159
2160         ast_copy_string(tmp, argv[0], sizeof(tmp));
2161         username = tmp;
2162         domain = strchr(tmp, '@');
2163         if (domain) {
2164                 *domain = '\0';
2165                 domain++;
2166         } 
2167         if (ast_strlen_zero(domain) || ast_strlen_zero(username)) {
2168                 ast_log(LOG_ERROR, "Need username@domain as argument. Sorry. Argument 0 %s\n", argv[0]);
2169                 return -1;
2170         }
2171
2172         if(!(vmu = find_account(domain, username, TRUE))) {
2173                 /* We could not find user, let's exit */
2174                 ast_log(LOG_WARNING, "Could not allocate temporary memory for '%s@%s'\n", username, domain);
2175                 pbx_builtin_setvar_helper(chan, "MVM_NOTIFY_STATUS", "FAILED");
2176                 return -1;
2177         }
2178
2179         ast_channel_lock(chan);
2180         if ((filename = pbx_builtin_getvar_helper(chan, "MVM_FILENAME"))) {
2181                 filename = ast_strdupa(filename);
2182         }
2183         ast_channel_unlock(chan);
2184         /* Notify of new message to e-mail and pager */
2185         if (!ast_strlen_zero(filename)) {
2186                 ast_channel_lock(chan); 
2187                 if ((format = pbx_builtin_getvar_helper(chan, "MVM_FORMAT"))) {
2188                         format = ast_strdupa(format);
2189                 }
2190                 if ((duration_string = pbx_builtin_getvar_helper(chan, "MVM_DURATION"))) {
2191                         duration_string = ast_strdupa(duration_string);
2192                 }
2193                 ast_channel_unlock(chan);
2194                 res = notify_new_message(chan, template, vmu, filename, atoi(duration_string),
2195                         format,
2196                         S_COR(ast_channel_caller(chan)->id.number.valid, ast_channel_caller(chan)->id.number.str, NULL),
2197                         S_COR(ast_channel_caller(chan)->id.name.valid, ast_channel_caller(chan)->id.name.str, NULL));
2198         }
2199
2200         pbx_builtin_setvar_helper(chan, "MVM_NOTIFY_STATUS", res == 0 ? "SUCCESS" : "FAILED");
2201
2202
2203         if(ast_test_flag(vmu, MVM_ALLOCED))
2204                 free_user(vmu);
2205
2206         /* Ok, we're ready to rock and roll. Return to dialplan */
2207
2208         return res;
2209
2210 }
2211
2212 /*!\internal
2213  * \brief Dialplan function to record voicemail */
2214 static int minivm_record_exec(struct ast_channel *chan, const char *data)
2215 {
2216         int res = 0;
2217         char *tmp;
2218         struct leave_vm_options leave_options;
2219         int argc;
2220         char *argv[2];
2221         struct ast_flags flags = { 0 };
2222         char *opts[OPT_ARG_ARRAY_SIZE];
2223
2224         memset(&leave_options, 0, sizeof(leave_options));
2225
2226         /* Answer channel if it's not already answered */
2227         if (ast_channel_state(chan) != AST_STATE_UP)
2228                 ast_answer(chan);
2229
2230         if (ast_strlen_zero(data))  {
2231                 ast_log(LOG_ERROR, "Minivm needs at least an account argument \n");
2232                 return -1;
2233         }
2234         tmp = ast_strdupa((char *)data);
2235         argc = ast_app_separate_args(tmp, ',', argv, ARRAY_LEN(argv));
2236         if (argc == 2) {
2237                 if (ast_app_parse_options(minivm_app_options, &flags, opts, argv[1])) {
2238                         return -1;
2239                 }
2240                 ast_copy_flags(&leave_options, &flags, OPT_SILENT | OPT_BUSY_GREETING | OPT_UNAVAIL_GREETING );
2241                 if (ast_test_flag(&flags, OPT_RECORDGAIN)) {
2242                         int gain;
2243
2244                         if (sscanf(opts[OPT_ARG_RECORDGAIN], "%30d", &gain) != 1) {
2245                                 ast_log(LOG_WARNING, "Invalid value '%s' provided for record gain option\n", opts[OPT_ARG_RECORDGAIN]);
2246                                 return -1;
2247                         } else 
2248                                 leave_options.record_gain = (signed char) gain;
2249                 }
2250         } 
2251
2252         /* Now run the appliation and good luck to you! */
2253         res = leave_voicemail(chan, argv[0], &leave_options);
2254
2255         if (res == ERROR_LOCK_PATH) {
2256                 ast_log(LOG_ERROR, "Could not leave voicemail. The path is already locked.\n");
2257                 pbx_builtin_setvar_helper(chan, "MVM_RECORD_STATUS", "FAILED");
2258                 res = 0;
2259         }
2260         pbx_builtin_setvar_helper(chan, "MVM_RECORD_STATUS", "SUCCESS");
2261
2262         return res;
2263 }
2264
2265 /*!\internal
2266  * \brief Play voicemail prompts - either generic or user specific */
2267 static int minivm_greet_exec(struct ast_channel *chan, const char *data)
2268 {
2269         struct leave_vm_options leave_options = { 0, '\0'};
2270         int argc;
2271         char *argv[2];
2272         struct ast_flags flags = { 0 };
2273         char *opts[OPT_ARG_ARRAY_SIZE];
2274         int res = 0;
2275         int ausemacro = 0;
2276         int ousemacro = 0;
2277         int ouseexten = 0;
2278         char tmp[PATH_MAX];
2279         char dest[PATH_MAX];
2280         char prefile[PATH_MAX] = "";
2281         char tempfile[PATH_MAX] = "";
2282         char ext_context[256] = "";
2283         char *domain;
2284         char ecodes[16] = "#";
2285         char *tmpptr;
2286         struct minivm_account *vmu;
2287         char *username;
2288
2289         if (ast_strlen_zero(data))  {
2290                 ast_log(LOG_ERROR, "Minivm needs at least an account argument \n");
2291                 return -1;
2292         }
2293         tmpptr = ast_strdupa((char *)data);
2294         argc = ast_app_separate_args(tmpptr, ',', argv, ARRAY_LEN(argv));
2295
2296         if (argc == 2) {
2297                 if (ast_app_parse_options(minivm_app_options, &flags, opts, argv[1]))
2298                         return -1;
2299                 ast_copy_flags(&leave_options, &flags, OPT_SILENT | OPT_BUSY_GREETING | OPT_UNAVAIL_GREETING );
2300         }
2301
2302         ast_copy_string(tmp, argv[0], sizeof(tmp));
2303         username = tmp;
2304         domain = strchr(tmp, '@');
2305         if (domain) {
2306                 *domain = '\0';
2307                 domain++;
2308         } 
2309         if (ast_strlen_zero(domain) || ast_strlen_zero(username)) {
2310                 ast_log(LOG_ERROR, "Need username@domain as argument. Sorry. Argument:  %s\n", argv[0]);
2311                 return -1;
2312         }
2313         ast_debug(1, "Trying to find configuration for user %s in domain %s\n", username, domain);
2314
2315         if (!(vmu = find_account(domain, username, TRUE))) {
2316                 ast_log(LOG_ERROR, "Could not allocate memory. \n");
2317                 return -1;
2318         }
2319
2320         /* Answer channel if it's not already answered */
2321         if (ast_channel_state(chan) != AST_STATE_UP)
2322                 ast_answer(chan);
2323
2324         /* Setup pre-file if appropriate */
2325         if (strcmp(vmu->domain, "localhost"))
2326                 snprintf(ext_context, sizeof(ext_context), "%s@%s", username, vmu->domain);
2327         else
2328                 ast_copy_string(ext_context, vmu->domain, sizeof(ext_context));
2329
2330         if (ast_test_flag(&leave_options, OPT_BUSY_GREETING)) {
2331                 res = check_dirpath(dest, sizeof(dest), vmu->domain, username, "busy");
2332                 if (res)
2333                         snprintf(prefile, sizeof(prefile), "%s%s/%s/busy", MVM_SPOOL_DIR, vmu->domain, username);
2334         } else if (ast_test_flag(&leave_options, OPT_UNAVAIL_GREETING)) {
2335                 res = check_dirpath(dest, sizeof(dest), vmu->domain, username, "unavail");
2336                 if (res)
2337                         snprintf(prefile, sizeof(prefile), "%s%s/%s/unavail", MVM_SPOOL_DIR, vmu->domain, username);
2338         }
2339         /* Check for temporary greeting - it overrides busy and unavail */
2340         snprintf(tempfile, sizeof(tempfile), "%s%s/%s/temp", MVM_SPOOL_DIR, vmu->domain, username);
2341         if (!(res = check_dirpath(dest, sizeof(dest), vmu->domain, username, "temp"))) {
2342                 ast_debug(2, "Temporary message directory does not exist, using default (%s)\n", tempfile);
2343                 ast_copy_string(prefile, tempfile, sizeof(prefile));
2344         }
2345         ast_debug(2, "Preparing to play message ...\n");
2346
2347         /* Check current or macro-calling context for special extensions */
2348         if (ast_test_flag(vmu, MVM_OPERATOR)) {
2349                 if (!ast_strlen_zero(vmu->exit)) {
2350                         if (ast_exists_extension(chan, vmu->exit, "o", 1,
2351                                 S_COR(ast_channel_caller(chan)->id.number.valid, ast_channel_caller(chan)->id.number.str, NULL))) {
2352                                 strncat(ecodes, "0", sizeof(ecodes) - strlen(ecodes) - 1);
2353                                 ouseexten = 1;
2354                         }
2355                 } else if (ast_exists_extension(chan, ast_channel_context(chan), "o", 1,
2356                         S_COR(ast_channel_caller(chan)->id.number.valid, ast_channel_caller(chan)->id.number.str, NULL))) {
2357                         strncat(ecodes, "0", sizeof(ecodes) - strlen(ecodes) - 1);
2358                         ouseexten = 1;
2359                 }
2360                 else if (!ast_strlen_zero(ast_channel_macrocontext(chan))
2361                         && ast_exists_extension(chan, ast_channel_macrocontext(chan), "o", 1,
2362                                 S_COR(ast_channel_caller(chan)->id.number.valid, ast_channel_caller(chan)->id.number.str, NULL))) {
2363                         strncat(ecodes, "0", sizeof(ecodes) - strlen(ecodes) - 1);
2364                         ousemacro = 1;
2365                 }
2366         }
2367
2368         if (!ast_strlen_zero(vmu->exit)) {
2369                 if (ast_exists_extension(chan, vmu->exit, "a", 1,
2370                         S_COR(ast_channel_caller(chan)->id.number.valid, ast_channel_caller(chan)->id.number.str, NULL))) {
2371                         strncat(ecodes, "*", sizeof(ecodes) -  strlen(ecodes) - 1);
2372                 }
2373         } else if (ast_exists_extension(chan, ast_channel_context(chan), "a", 1,
2374                 S_COR(ast_channel_caller(chan)->id.number.valid, ast_channel_caller(chan)->id.number.str, NULL))) {
2375                 strncat(ecodes, "*", sizeof(ecodes) -  strlen(ecodes) - 1);
2376         } else if (!ast_strlen_zero(ast_channel_macrocontext(chan))
2377                 && ast_exists_extension(chan, ast_channel_macrocontext(chan), "a", 1,
2378                         S_COR(ast_channel_caller(chan)->id.number.valid, ast_channel_caller(chan)->id.number.str, NULL))) {
2379                 strncat(ecodes, "*", sizeof(ecodes) -  strlen(ecodes) - 1);
2380                 ausemacro = 1;
2381         }
2382
2383         res = 0;        /* Reset */
2384         /* Play the beginning intro if desired */
2385         if (!ast_strlen_zero(prefile)) {
2386                 if (ast_streamfile(chan, prefile, ast_channel_language(chan)) > -1) 
2387                         res = ast_waitstream(chan, ecodes);
2388         } else {
2389                 ast_debug(2, "%s doesn't exist, doing what we can\n", prefile);
2390                 res = invent_message(chan, vmu->domain, username, ast_test_flag(&leave_options, OPT_BUSY_GREETING), ecodes);
2391         }
2392         if (res < 0) {
2393                 ast_debug(2, "Hang up during prefile playback\n");
2394                 pbx_builtin_setvar_helper(chan, "MVM_GREET_STATUS", "FAILED");
2395                 if(ast_test_flag(vmu, MVM_ALLOCED))
2396                         free_user(vmu);
2397                 return -1;
2398         }
2399         if (res == '#') {
2400                 /* On a '#' we skip the instructions */
2401                 ast_set_flag(&leave_options, OPT_SILENT);
2402                 res = 0;
2403         }
2404         if (!res && !ast_test_flag(&leave_options, OPT_SILENT)) {
2405                 res = ast_streamfile(chan, SOUND_INTRO, ast_channel_language(chan));
2406                 if (!res)
2407                         res = ast_waitstream(chan, ecodes);
2408                 if (res == '#') {
2409                         ast_set_flag(&leave_options, OPT_SILENT);
2410                         res = 0;
2411                 }
2412         }
2413         if (res > 0)
2414                 ast_stopstream(chan);
2415         /* Check for a '*' here in case the caller wants to escape from voicemail to something
2416            other than the operator -- an automated attendant or mailbox login for example */
2417         if (res == '*') {
2418                 ast_channel_exten_set(chan, "a");
2419                 if (!ast_strlen_zero(vmu->exit)) {
2420                         ast_channel_context_set(chan, vmu->exit);
2421                 } else if (ausemacro && !ast_strlen_zero(ast_channel_macrocontext(chan))) {
2422                         ast_channel_context_set(chan, ast_channel_macrocontext(chan));
2423                 }
2424                 ast_channel_priority_set(chan, 0);
2425                 pbx_builtin_setvar_helper(chan, "MVM_GREET_STATUS", "USEREXIT");
2426                 res = 0;
2427         } else if (res == '0') { /* Check for a '0' here */
2428                 if(ouseexten || ousemacro) {
2429                         ast_channel_exten_set(chan, "o");
2430                         if (!ast_strlen_zero(vmu->exit)) {
2431                                 ast_channel_context_set(chan, vmu->exit);
2432                         } else if (ousemacro && !ast_strlen_zero(ast_channel_macrocontext(chan))) {
2433                                 ast_channel_context_set(chan, ast_channel_macrocontext(chan));
2434                         }
2435                         ast_play_and_wait(chan, "transfer");
2436                         ast_channel_priority_set(chan, 0);
2437                         pbx_builtin_setvar_helper(chan, "MVM_GREET_STATUS", "USEREXIT");
2438                 }
2439                 res =  0;
2440         } else if (res < 0) {
2441                 pbx_builtin_setvar_helper(chan, "MVM_GREET_STATUS", "FAILED");
2442                 res = -1;
2443         } else
2444                 pbx_builtin_setvar_helper(chan, "MVM_GREET_STATUS", "SUCCESS");
2445
2446         if(ast_test_flag(vmu, MVM_ALLOCED))
2447                 free_user(vmu);
2448
2449
2450         /* Ok, we're ready to rock and roll. Return to dialplan */
2451         return res;
2452
2453 }
2454
2455 /*!\internal
2456  * \brief Dialplan application to delete voicemail */
2457 static int minivm_delete_exec(struct ast_channel *chan, const char *data)
2458 {
2459         int res = 0;
2460         char filename[BUFSIZ];
2461
2462         if (!ast_strlen_zero(data)) {
2463                 ast_copy_string(filename, (char *) data, sizeof(filename));
2464         } else {
2465                 ast_channel_lock(chan);
2466                 ast_copy_string(filename, pbx_builtin_getvar_helper(chan, "MVM_FILENAME"), sizeof(filename));
2467                 ast_channel_unlock(chan);
2468         }
2469
2470         if (ast_strlen_zero(filename)) {
2471                 ast_log(LOG_ERROR, "No filename given in application arguments or channel variable MVM_FILENAME\n");
2472                 return res;
2473         } 
2474
2475         /* Go ahead and delete audio files from system, they're not needed any more */
2476         /* We should look for both audio and text files here */
2477         if (ast_fileexists(filename, NULL, NULL) > 0) {
2478                 res = vm_delete(filename);
2479                 if (res) {
2480                         ast_debug(2, "Can't delete file: %s\n", filename);
2481                         pbx_builtin_setvar_helper(chan, "MVM_DELETE_STATUS", "FAILED");
2482                 } else {
2483                         ast_debug(2, "Deleted voicemail file :: %s \n", filename);
2484                         pbx_builtin_setvar_helper(chan, "MVM_DELETE_STATUS", "SUCCESS");
2485                 }
2486         } else {
2487                 ast_debug(2, "Filename does not exist: %s\n", filename);
2488                 pbx_builtin_setvar_helper(chan, "MVM_DELETE_STATUS", "FAILED");
2489         }
2490
2491         return res;
2492 }
2493
2494 /*! \brief Record specific messages for voicemail account */
2495 static int minivm_accmess_exec(struct ast_channel *chan, const char *data)
2496 {
2497         int argc = 0;
2498         char *argv[2];
2499         char filename[PATH_MAX];
2500         char tmp[PATH_MAX];
2501         char *domain;
2502         char *tmpptr = NULL;
2503         struct minivm_account *vmu;
2504         char *username;
2505         struct ast_flags flags = { 0 };
2506         char *opts[OPT_ARG_ARRAY_SIZE];
2507         int error = FALSE;
2508         char *message = NULL;
2509         char *prompt = NULL;
2510         int duration;
2511
2512         if (ast_strlen_zero(data))  {
2513                 ast_log(LOG_ERROR, "MinivmAccmess needs at least two arguments: account and option\n");
2514                 error = TRUE;
2515         } else {
2516                 tmpptr = ast_strdupa((char *)data);
2517                 argc = ast_app_separate_args(tmpptr, ',', argv, ARRAY_LEN(argv));
2518         }
2519
2520         if (argc <=1) {
2521                 ast_log(LOG_ERROR, "MinivmAccmess needs at least two arguments: account and option\n");
2522                 error = TRUE;
2523         }
2524         if (!error && strlen(argv[1]) > 1) {
2525                 ast_log(LOG_ERROR, "MinivmAccmess can only handle one option at a time. Bad option string: %s\n", argv[1]);
2526                 error = TRUE;
2527         }
2528
2529         if (!error && ast_app_parse_options(minivm_accmess_options, &flags, opts, argv[1])) {
2530                 ast_log(LOG_ERROR, "Can't parse option %s\n", argv[1]);
2531                 error = TRUE;
2532         }
2533
2534         if (error) {
2535                 pbx_builtin_setvar_helper(chan, "MVM_ACCMESS_STATUS", "FAILED");
2536                 return -1;
2537         }
2538
2539         ast_copy_string(tmp, argv[0], sizeof(tmp));
2540         username = tmp;
2541         domain = strchr(tmp, '@');
2542         if (domain) {
2543                 *domain = '\0';
2544                 domain++;
2545         } 
2546         if (ast_strlen_zero(domain) || ast_strlen_zero(username)) {
2547                 ast_log(LOG_ERROR, "Need username@domain as argument. Sorry. Argument 0 %s\n", argv[0]);
2548                 pbx_builtin_setvar_helper(chan, "MVM_ACCMESS_STATUS", "FAILED");
2549                 return -1;
2550         }
2551
2552         if(!(vmu = find_account(domain, username, TRUE))) {
2553                 /* We could not find user, let's exit */
2554                 ast_log(LOG_WARNING, "Could not allocate temporary memory for '%s@%s'\n", username, domain);
2555                 pbx_builtin_setvar_helper(chan, "MVM_ACCMESS_STATUS", "FAILED");
2556                 return -1;
2557         }
2558
2559         /* Answer channel if it's not already answered */
2560         if (ast_channel_state(chan) != AST_STATE_UP)
2561                 ast_answer(chan);
2562         
2563         /* Here's where the action is */
2564         if (ast_test_flag(&flags, OPT_BUSY_GREETING)) {
2565                 message = "busy";
2566                 prompt = "vm-rec-busy";
2567         } else if (ast_test_flag(&flags, OPT_UNAVAIL_GREETING)) {
2568                 message = "unavailable";
2569                 prompt = "vm-rec-unv";
2570         } else if (ast_test_flag(&flags, OPT_TEMP_GREETING)) {
2571                 message = "temp";
2572                 prompt = "vm-rec-temp";
2573         } else if (ast_test_flag(&flags, OPT_NAME_GREETING)) {
2574                 message = "greet";
2575                 prompt = "vm-rec-name";
2576         }
2577         snprintf(filename,sizeof(filename), "%s%s/%s/%s", MVM_SPOOL_DIR, vmu->domain, vmu->username, message);
2578         /* Maybe we should check the result of play_record_review ? */
2579         play_record_review(chan, prompt, filename, global_maxgreet, default_vmformat, 0, vmu, &duration, NULL, NULL, FALSE);
2580
2581         ast_debug(1, "Recorded new %s message in %s (duration %d)\n", message, filename, duration);
2582
2583         if(ast_test_flag(vmu, MVM_ALLOCED))
2584                 free_user(vmu);
2585
2586         pbx_builtin_setvar_helper(chan, "MVM_ACCMESS_STATUS", "SUCCESS");
2587
2588         /* Ok, we're ready to rock and roll. Return to dialplan */
2589         return 0;
2590 }
2591
2592 /*! \brief Append new mailbox to mailbox list from configuration file */
2593 static int create_vmaccount(char *name, struct ast_variable *var, int realtime)
2594 {
2595         struct minivm_account *vmu;
2596         char *domain;
2597         char *username;
2598         char accbuf[BUFSIZ];
2599
2600         ast_debug(3, "Creating %s account for [%s]\n", realtime ? "realtime" : "static", name);
2601
2602         ast_copy_string(accbuf, name, sizeof(accbuf));
2603         username = accbuf;
2604         domain = strchr(accbuf, '@');
2605         if (domain) {
2606                 *domain = '\0';
2607                 domain++;
2608         }
2609         if (ast_strlen_zero(domain)) {
2610                 ast_log(LOG_ERROR, "No domain given for mini-voicemail account %s. Not configured.\n", name);
2611                 return 0;
2612         }
2613
2614         ast_debug(3, "Creating static account for user %s domain %s\n", username, domain);
2615
2616         /* Allocate user account */
2617         vmu = ast_calloc(1, sizeof(*vmu));
2618         if (!vmu)
2619                 return 0;
2620         
2621         ast_copy_string(vmu->domain, domain, sizeof(vmu->domain));
2622         ast_copy_string(vmu->username, username, sizeof(vmu->username));
2623
2624         populate_defaults(vmu);
2625
2626         ast_debug(3, "...Configuring account %s\n", name);
2627
2628         while (var) {
2629                 ast_debug(3, "Configuring %s = \"%s\" for account %s\n", var->name, var->value, name);
2630                 if (!strcasecmp(var->name, "serveremail")) {
2631                         ast_copy_string(vmu->serveremail, var->value, sizeof(vmu->serveremail));
2632                 } else if (!strcasecmp(var->name, "email")) {
2633                         ast_copy_string(vmu->email, var->value, sizeof(vmu->email));
2634                 } else if (!strcasecmp(var->name, "accountcode")) {
2635                         ast_copy_string(vmu->accountcode, var->value, sizeof(vmu->accountcode));
2636                 } else if (!strcasecmp(var->name, "pincode")) {
2637                         ast_copy_string(vmu->pincode, var->value, sizeof(vmu->pincode));
2638                 } else if (!strcasecmp(var->name, "domain")) {
2639                         ast_copy_string(vmu->domain, var->value, sizeof(vmu->domain));
2640                 } else if (!strcasecmp(var->name, "language")) {
2641                         ast_copy_string(vmu->language, var->value, sizeof(vmu->language));
2642                 } else if (!strcasecmp(var->name, "timezone")) {
2643                         ast_copy_string(vmu->zonetag, var->value, sizeof(vmu->zonetag));
2644                 } else if (!strcasecmp(var->name, "externnotify")) {
2645                         ast_copy_string(vmu->externnotify, var->value, sizeof(vmu->externnotify));
2646                 } else if (!strcasecmp(var->name, "etemplate")) {
2647                         ast_copy_string(vmu->etemplate, var->value, sizeof(vmu->etemplate));
2648                 } else if (!strcasecmp(var->name, "ptemplate")) {
2649                         ast_copy_string(vmu->ptemplate, var->value, sizeof(vmu->ptemplate));
2650                 } else if (!strcasecmp(var->name, "fullname")) {
2651                         ast_copy_string(vmu->fullname, var->value, sizeof(vmu->fullname));
2652                 } else if (!strcasecmp(var->name, "setvar")) {
2653                         char *varval;
2654                         char *varname = ast_strdupa(var->value);
2655                         struct ast_variable *tmpvar;
2656
2657                         if ((varval = strchr(varname, '='))) {
2658                                 *varval = '\0';
2659                                 varval++;
2660                                 if ((tmpvar = ast_variable_new(varname, varval, ""))) {
2661                                         tmpvar->next = vmu->chanvars;
2662                                         vmu->chanvars = tmpvar;
2663                                 }
2664                         }
2665                 } else if (!strcasecmp(var->name, "pager")) {
2666                         ast_copy_string(vmu->pager, var->value, sizeof(vmu->pager));
2667                 } else if (!strcasecmp(var->name, "volgain")) {
2668                         sscanf(var->value, "%30lf", &vmu->volgain);
2669                 } else {
2670                         ast_log(LOG_ERROR, "Unknown configuration option for minivm account %s : %s\n", name, var->name);
2671                 }
2672                 var = var->next;
2673         }
2674         ast_debug(3, "...Linking account %s\n", name);
2675         
2676         AST_LIST_LOCK(&minivm_accounts);
2677         AST_LIST_INSERT_TAIL(&minivm_accounts, vmu, list);
2678         AST_LIST_UNLOCK(&minivm_accounts);
2679
2680         global_stats.voicemailaccounts++;
2681
2682         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)" : "");
2683         return 0;
2684 }
2685
2686 /*! \brief Free Mini Voicemail timezone */
2687 static void free_zone(struct minivm_zone *z)
2688 {
2689         ast_free(z);
2690 }
2691
2692 /*! \brief Clear list of timezones */
2693 static void timezone_destroy_list(void)
2694 {
2695         struct minivm_zone *this;
2696
2697         AST_LIST_LOCK(&minivm_zones);
2698         while ((this = AST_LIST_REMOVE_HEAD(&minivm_zones, list))) 
2699                 free_zone(this);
2700                 
2701         AST_LIST_UNLOCK(&minivm_zones);
2702 }
2703
2704 /*! \brief Add time zone to memory list */
2705 static int timezone_add(const char *zonename, const char *config)
2706 {
2707         struct minivm_zone *newzone;
2708         char *msg_format, *timezone_str;
2709
2710         newzone = ast_calloc(1, sizeof(*newzone));
2711         if (newzone == NULL)
2712                 return 0;
2713
2714         msg_format = ast_strdupa(config);
2715
2716         timezone_str = strsep(&msg_format, "|");
2717         if (!msg_format) {
2718                 ast_log(LOG_WARNING, "Invalid timezone definition : %s\n", zonename);
2719                 ast_free(newzone);
2720                 return 0;
2721         }
2722                         
2723         ast_copy_string(newzone->name, zonename, sizeof(newzone->name));
2724         ast_copy_string(newzone->timezone, timezone_str, sizeof(newzone->timezone));
2725         ast_copy_string(newzone->msg_format, msg_format, sizeof(newzone->msg_format));
2726
2727         AST_LIST_LOCK(&minivm_zones);
2728         AST_LIST_INSERT_TAIL(&minivm_zones, newzone, list);
2729         AST_LIST_UNLOCK(&minivm_zones);
2730
2731         global_stats.timezones++;
2732
2733         return 0;
2734 }
2735
2736 /*! \brief Read message template from file */
2737 static char *message_template_parse_filebody(const char *filename) {
2738         char buf[BUFSIZ * 6];
2739         char readbuf[BUFSIZ];
2740         char filenamebuf[BUFSIZ];
2741         char *writepos;
2742         char *messagebody;
2743         FILE *fi;
2744         int lines = 0;
2745
2746         if (ast_strlen_zero(filename))
2747                 return NULL;
2748         if (*filename == '/') 
2749                 ast_copy_string(filenamebuf, filename, sizeof(filenamebuf));
2750         else 
2751                 snprintf(filenamebuf, sizeof(filenamebuf), "%s/%s", ast_config_AST_CONFIG_DIR, filename);
2752
2753         if (!(fi = fopen(filenamebuf, "r"))) {
2754                 ast_log(LOG_ERROR, "Can't read message template from file: %s\n", filenamebuf);
2755                 return NULL;
2756         }
2757         writepos = buf;
2758         while (fgets(readbuf, sizeof(readbuf), fi)) {
2759                 lines ++;
2760                 if (writepos != buf) {
2761                         *writepos = '\n';               /* Replace EOL with new line */
2762                         writepos++;
2763                 }
2764                 ast_copy_string(writepos, readbuf, sizeof(buf) - (writepos - buf));
2765                 writepos += strlen(readbuf) - 1;
2766         }
2767         fclose(fi);
2768         messagebody = ast_calloc(1, strlen(buf + 1));
2769         ast_copy_string(messagebody, buf, strlen(buf) + 1);
2770         ast_debug(4, "---> Size of allocation %d\n", (int) strlen(buf + 1) );
2771         ast_debug(4, "---> Done reading message template : \n%s\n---- END message template--- \n", messagebody);
2772
2773         return messagebody;
2774 }
2775
2776 /*! \brief Parse emailbody template from configuration file */
2777 static char *message_template_parse_emailbody(const char *configuration)
2778 {
2779         char *tmpread, *tmpwrite;
2780         char *emailbody = ast_strdup(configuration);
2781
2782         /* substitute strings \t and \n into the apropriate characters */
2783         tmpread = tmpwrite = emailbody;
2784         while ((tmpwrite = strchr(tmpread,'\\'))) {
2785                int len = strlen("\n");
2786                switch (tmpwrite[1]) {
2787                case 'n':
2788                       memmove(tmpwrite + len, tmpwrite + 2, strlen(tmpwrite + 2) + 1);
2789                       strncpy(tmpwrite, "\n", len);
2790                       break;
2791                case 't':
2792                       memmove(tmpwrite + len, tmpwrite + 2, strlen(tmpwrite + 2) + 1);
2793                       strncpy(tmpwrite, "\t", len);
2794                       break;
2795                default:
2796                       ast_log(LOG_NOTICE, "Substitution routine does not support this character: %c\n", tmpwrite[1]);
2797                }
2798                tmpread = tmpwrite + len;
2799         }
2800         return emailbody;       
2801 }
2802
2803 /*! \brief Apply general configuration options */
2804 static int apply_general_options(struct ast_variable *var)
2805 {
2806         int error = 0;
2807
2808         while (var) {
2809                 /* Mail command */
2810                 if (!strcmp(var->name, "mailcmd")) {
2811                         ast_copy_string(global_mailcmd, var->value, sizeof(global_mailcmd)); /* User setting */
2812                 } else if (!strcmp(var->name, "maxgreet")) {
2813                         global_maxgreet = atoi(var->value);
2814                 } else if (!strcmp(var->name, "maxsilence")) {
2815                         global_maxsilence = atoi(var->value);
2816                         if (global_maxsilence > 0)
2817                                 global_maxsilence *= 1000;
2818                 } else if (!strcmp(var->name, "logfile")) {
2819                         if (!ast_strlen_zero(var->value) ) {
2820                                 if(*(var->value) == '/')
2821                                         ast_copy_string(global_logfile, var->value, sizeof(global_logfile));
2822                                 else
2823                                         snprintf(global_logfile, sizeof(global_logfile), "%s/%s", ast_config_AST_LOG_DIR, var->value);
2824                         }
2825                 } else if (!strcmp(var->name, "externnotify")) {
2826                         /* External voicemail notify application */
2827                         ast_copy_string(global_externnotify, var->value, sizeof(global_externnotify));
2828                 } else if (!strcmp(var->name, "silencetreshold")) {
2829                         /* Silence treshold */
2830                         global_silencethreshold = atoi(var->value);
2831                 } else if (!strcmp(var->name, "maxmessage")) {
2832                         int x;
2833                         if (sscanf(var->value, "%30d", &x) == 1) {
2834                                 global_vmmaxmessage = x;
2835                         } else {
2836                                 error ++;
2837                                 ast_log(LOG_WARNING, "Invalid max message time length\n");
2838                         }
2839                 } else if (!strcmp(var->name, "minmessage")) {
2840                         int x;
2841                         if (sscanf(var->value, "%30d", &x) == 1) {
2842                                 global_vmminmessage = x;
2843                                 if (global_maxsilence <= global_vmminmessage)
2844                                         ast_log(LOG_WARNING, "maxsilence should be less than minmessage or you may get empty messages\n");
2845                         } else {
2846                                 error ++;
2847                                 ast_log(LOG_WARNING, "Invalid min message time length\n");
2848                         }
2849                 } else if (!strcmp(var->name, "format")) {
2850                         ast_copy_string(default_vmformat, var->value, sizeof(default_vmformat));
2851                 } else if (!strcmp(var->name, "review")) {
2852                         ast_set2_flag((&globalflags), ast_true(var->value), MVM_REVIEW);        
2853                 } else if (!strcmp(var->name, "operator")) {
2854                         ast_set2_flag((&globalflags), ast_true(var->value), MVM_OPERATOR);      
2855                 }
2856                 var = var->next;
2857         }
2858         return error;
2859 }
2860
2861 /*! \brief Load minivoicemail configuration */
2862 static int load_config(int reload)
2863 {
2864         struct ast_config *cfg;
2865         struct ast_variable *var;
2866         char *cat;
2867         const char *chanvar;
2868         int error = 0;
2869         struct minivm_template *template;
2870         struct ast_flags config_flags = { reload ? CONFIG_FLAG_FILEUNCHANGED : 0 };
2871
2872         cfg = ast_config_load(VOICEMAIL_CONFIG, config_flags);
2873         if (cfg == CONFIG_STATUS_FILEUNCHANGED) {
2874                 return 0;
2875         } else if (cfg == CONFIG_STATUS_FILEINVALID) {
2876                 ast_log(LOG_ERROR, "Config file " VOICEMAIL_CONFIG " is in an invalid format.  Aborting.\n");
2877                 return 0;
2878         }
2879
2880         ast_mutex_lock(&minivmlock);
2881
2882         /* Destroy lists to reconfigure */
2883         message_destroy_list();         /* Destroy list of voicemail message templates */
2884         timezone_destroy_list();        /* Destroy list of timezones */
2885         vmaccounts_destroy_list();      /* Destroy list of voicemail accounts */
2886         ast_debug(2, "Destroyed memory objects...\n");
2887
2888         /* First, set some default settings */
2889         global_externnotify[0] = '\0';
2890         global_logfile[0] = '\0';
2891         global_vmmaxmessage = 2000;
2892         global_maxgreet = 2000;
2893         global_vmminmessage = 0;
2894         strcpy(global_mailcmd, SENDMAIL);
2895         global_maxsilence = 0;
2896         global_saydurationminfo = 2;
2897         ast_copy_string(default_vmformat, "wav", sizeof(default_vmformat));
2898         ast_set2_flag((&globalflags), FALSE, MVM_REVIEW);       
2899         ast_set2_flag((&globalflags), FALSE, MVM_OPERATOR);     
2900         /* Reset statistics */
2901         memset(&global_stats, 0, sizeof(global_stats));
2902         global_stats.reset = ast_tvnow();
2903
2904         global_silencethreshold = ast_dsp_get_threshold_from_settings(THRESHOLD_SILENCE);
2905
2906         /* Make sure we could load configuration file */
2907         if (!cfg) {
2908                 ast_log(LOG_WARNING, "Failed to load configuration file. Module activated with default settings.\n");
2909                 ast_mutex_unlock(&minivmlock);
2910                 return 0;
2911         }
2912
2913         ast_debug(2, "Loaded configuration file, now parsing\n");
2914
2915         /* General settings */
2916
2917         cat = ast_category_browse(cfg, NULL);
2918         while (cat) {
2919                 ast_debug(3, "Found configuration section [%s]\n", cat);
2920                 if (!strcasecmp(cat, "general")) {
2921                         /* Nothing right now */
2922                         error += apply_general_options(ast_variable_browse(cfg, cat));
2923                 } else if (!strncasecmp(cat, "template-", 9))  {
2924                         /* Template */
2925                         char *name = cat + 9;
2926
2927                         /* Now build and link template to list */
2928                         error += message_template_build(name, ast_variable_browse(cfg, cat));
2929                 } else {
2930                         var = ast_variable_browse(cfg, cat);
2931                         if (!strcasecmp(cat, "zonemessages")) {
2932                                 /* Timezones in this context */
2933                                 while (var) {
2934                                         timezone_add(var->name, var->value);
2935                                         var = var->next;
2936                                 }
2937                         } else {
2938                                 /* Create mailbox from this */
2939                                 error += create_vmaccount(cat, var, FALSE);
2940                         }
2941                 }
2942                 /* Find next section in configuration file */
2943                 cat = ast_category_browse(cfg, cat);
2944         }
2945
2946         /* Configure the default email template */
2947         message_template_build("email-default", NULL);
2948         template = message_template_find("email-default");
2949
2950         /* Load date format config for voicemail mail */
2951         if ((chanvar = ast_variable_retrieve(cfg, "general", "emaildateformat"))) 
2952                 ast_copy_string(template->dateformat, chanvar, sizeof(template->dateformat));
2953         if ((chanvar = ast_variable_retrieve(cfg, "general", "emailfromstring")))
2954                 ast_copy_string(template->fromaddress, chanvar, sizeof(template->fromaddress));
2955         if ((chanvar = ast_variable_retrieve(cfg, "general", "emailaaddress")))
2956                 ast_copy_string(template->serveremail, chanvar, sizeof(template->serveremail));
2957         if ((chanvar = ast_variable_retrieve(cfg, "general", "emailcharset")))
2958                 ast_copy_string(template->charset, chanvar, sizeof(template->charset));
2959         if ((chanvar = ast_variable_retrieve(cfg, "general", "emailsubject"))) 
2960                 ast_copy_string(template->subject, chanvar, sizeof(template->subject));
2961         if ((chanvar = ast_variable_retrieve(cfg, "general", "emailbody"))) 
2962                 template->body = message_template_parse_emailbody(chanvar);
2963         template->attachment = TRUE;
2964
2965         message_template_build("pager-default", NULL);
2966         template = message_template_find("pager-default");
2967         if ((chanvar = ast_variable_retrieve(cfg, "general", "pagerfromstring")))
2968                 ast_copy_string(template->fromaddress, chanvar, sizeof(template->fromaddress));
2969         if ((chanvar = ast_variable_retrieve(cfg, "general", "pageraddress")))
2970                 ast_copy_string(template->serveremail, chanvar, sizeof(template->serveremail));
2971         if ((chanvar = ast_variable_retrieve(cfg, "general", "pagercharset")))
2972                 ast_copy_string(template->charset, chanvar, sizeof(template->charset));
2973         if ((chanvar = ast_variable_retrieve(cfg, "general", "pagersubject")))
2974                 ast_copy_string(template->subject, chanvar,sizeof(template->subject));
2975         if ((chanvar = ast_variable_retrieve(cfg, "general", "pagerbody"))) 
2976                 template->body = message_template_parse_emailbody(chanvar);
2977         template->attachment = FALSE;
2978
2979         if (error)
2980                 ast_log(LOG_ERROR, "--- A total of %d errors found in mini-voicemail configuration\n", error);
2981
2982         ast_mutex_unlock(&minivmlock);
2983         ast_config_destroy(cfg);
2984
2985         /* Close log file if it's open and disabled */
2986         if(minivmlogfile)
2987                 fclose(minivmlogfile);
2988
2989         /* Open log file if it's enabled */
2990         if(!ast_strlen_zero(global_logfile)) {
2991                 minivmlogfile = fopen(global_logfile, "a");
2992                 if(!minivmlogfile)
2993                         ast_log(LOG_ERROR, "Failed to open minivm log file %s : %s\n", global_logfile, strerror(errno));
2994                 if (minivmlogfile)
2995                         ast_debug(3, "Opened log file %s \n", global_logfile);
2996         }
2997
2998         return 0;
2999 }
3000
3001 /*! \brief CLI routine for listing templates */
3002 static char *handle_minivm_list_templates(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
3003 {
3004         struct minivm_template *this;
3005 #define HVLT_OUTPUT_FORMAT "%-15s %-10s %-10s %-15.15s %-50s\n"
3006         int count = 0;
3007
3008         switch (cmd) {
3009         case CLI_INIT:
3010                 e->command = "minivm list templates";
3011                 e->usage =
3012                         "Usage: minivm list templates\n"
3013                         "       Lists message templates for e-mail, paging and IM\n";
3014                 return NULL;
3015         case CLI_GENERATE:
3016                 return NULL;
3017         }
3018
3019         if (a->argc > 3)
3020                 return CLI_SHOWUSAGE;
3021
3022         AST_LIST_LOCK(&message_templates);
3023         if (AST_LIST_EMPTY(&message_templates)) {
3024                 ast_cli(a->fd, "There are no message templates defined\n");
3025                 AST_LIST_UNLOCK(&message_templates);
3026                 return CLI_FAILURE;
3027         }
3028         ast_cli(a->fd, HVLT_OUTPUT_FORMAT, "Template name", "Charset", "Locale", "Attach media", "Subject");
3029         ast_cli(a->fd, HVLT_OUTPUT_FORMAT, "-------------", "-------", "------", "------------", "-------");
3030         AST_LIST_TRAVERSE(&message_templates, this, list) {
3031                 ast_cli(a->fd, HVLT_OUTPUT_FORMAT, this->name, 
3032                         S_OR(this->charset, "-"),
3033                         S_OR(this->locale, "-"),
3034                         this->attachment ? "Yes" : "No",
3035                         S_OR(this->subject, "-"));
3036                 count++;
3037         }
3038         AST_LIST_UNLOCK(&message_templates);
3039         ast_cli(a->fd, "\n * Total: %d minivoicemail message templates\n", count);
3040         return CLI_SUCCESS;
3041 }
3042
3043 static char *complete_minivm_show_users(const char *line, const char *word, int pos, int state)
3044 {
3045         int which = 0;
3046         int wordlen;
3047         struct minivm_account *vmu;
3048         const char *domain = "";
3049
3050         /* 0 - minivm; 1 - list; 2 - accounts; 3 - for; 4 - <domain> */
3051         if (pos > 4)
3052                 return NULL;
3053         wordlen = strlen(word);
3054         AST_LIST_TRAVERSE(&minivm_accounts, vmu, list) {
3055                 if (!strncasecmp(word, vmu->domain, wordlen)) {
3056                         if (domain && strcmp(domain, vmu->domain) && ++which > state)
3057                                 return ast_strdup(vmu->domain);
3058                         /* ignore repeated domains ? */
3059                         domain = vmu->domain;
3060                 }
3061         }
3062         return NULL;
3063 }
3064
3065 /*! \brief CLI command to list voicemail accounts */
3066 static char *handle_minivm_show_users(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
3067 {
3068         struct minivm_account *vmu;
3069 #define HMSU_OUTPUT_FORMAT "%-23s %-15s %-15s %-10s %-10s %-50s\n"
3070         int count = 0;
3071
3072         switch (cmd) {
3073         case CLI_INIT:
3074                 e->command = "minivm list accounts [for]";
3075                 e->usage =
3076                         "Usage: minivm list accounts [for <domain>]\n"
3077                         "       Lists all mailboxes currently set up\n";
3078                 return NULL;
3079         case CLI_GENERATE:
3080                 return complete_minivm_show_users(a->line, a-&g