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