Clean up and ensure proper usage of alloca()
[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         argc = ast_app_separate_args(tmpptr, ',', argv, ARRAY_LEN(argv));
2058         if (argc < 4) {
2059                 ast_log(LOG_ERROR, "%d arguments passed to MiniVM_MWI, need 4.\n", argc);
2060                 return -1;
2061         }
2062         ast_copy_string(tmp, argv[0], sizeof(tmp));
2063         mailbox = tmp;
2064         domain = strchr(tmp, '@');
2065         if (domain) {
2066                 *domain = '\0';
2067                 domain++;
2068         }
2069         if (ast_strlen_zero(domain) || ast_strlen_zero(mailbox)) {
2070                 ast_log(LOG_ERROR, "Need mailbox@context as argument. Sorry. Argument 0 %s\n", argv[0]);
2071                 return -1;
2072         }
2073         queue_mwi_event(mailbox, domain, atoi(argv[1]), atoi(argv[2]), atoi(argv[3]));
2074
2075         return res;
2076 }
2077
2078
2079 /*!\internal
2080  * \brief Notify voicemail account owners - either generic template or user specific */
2081 static int minivm_notify_exec(struct ast_channel *chan, const char *data)
2082 {
2083         int argc;
2084         char *argv[2];
2085         int res = 0;
2086         char tmp[PATH_MAX];
2087         char *domain;
2088         char *tmpptr;
2089         struct minivm_account *vmu;
2090         char *username;
2091         const char *template = "";
2092         const char *filename;
2093         const char *format;
2094         const char *duration_string;
2095
2096         if (ast_strlen_zero(data))  {
2097                 ast_log(LOG_ERROR, "Minivm needs at least an account argument \n");
2098                 return -1;
2099         }
2100         tmpptr = ast_strdupa((char *)data);
2101         argc = ast_app_separate_args(tmpptr, ',', argv, ARRAY_LEN(argv));
2102
2103         if (argc == 2 && !ast_strlen_zero(argv[1]))
2104                 template = argv[1];
2105
2106         ast_copy_string(tmp, argv[0], sizeof(tmp));
2107         username = tmp;
2108         domain = strchr(tmp, '@');
2109         if (domain) {
2110                 *domain = '\0';
2111                 domain++;
2112         } 
2113         if (ast_strlen_zero(domain) || ast_strlen_zero(username)) {
2114                 ast_log(LOG_ERROR, "Need username@domain as argument. Sorry. Argument 0 %s\n", argv[0]);
2115                 return -1;
2116         }
2117
2118         if(!(vmu = find_account(domain, username, TRUE))) {
2119                 /* We could not find user, let's exit */
2120                 ast_log(LOG_WARNING, "Could not allocate temporary memory for '%s@%s'\n", username, domain);
2121                 pbx_builtin_setvar_helper(chan, "MVM_NOTIFY_STATUS", "FAILED");
2122                 return -1;
2123         }
2124
2125         ast_channel_lock(chan);
2126         if ((filename = pbx_builtin_getvar_helper(chan, "MVM_FILENAME"))) {
2127                 filename = ast_strdupa(filename);
2128         }
2129         ast_channel_unlock(chan);
2130         /* Notify of new message to e-mail and pager */
2131         if (!ast_strlen_zero(filename)) {
2132                 ast_channel_lock(chan); 
2133                 if ((format = pbx_builtin_getvar_helper(chan, "MVM_FORMAT"))) {
2134                         format = ast_strdupa(format);
2135                 }
2136                 if ((duration_string = pbx_builtin_getvar_helper(chan, "MVM_DURATION"))) {
2137                         duration_string = ast_strdupa(duration_string);
2138                 }
2139                 ast_channel_unlock(chan);
2140                 res = notify_new_message(chan, template, vmu, filename, atoi(duration_string),
2141                         format,
2142                         S_COR(ast_channel_caller(chan)->id.number.valid, ast_channel_caller(chan)->id.number.str, NULL),
2143                         S_COR(ast_channel_caller(chan)->id.name.valid, ast_channel_caller(chan)->id.name.str, NULL));
2144         }
2145
2146         pbx_builtin_setvar_helper(chan, "MVM_NOTIFY_STATUS", res == 0 ? "SUCCESS" : "FAILED");
2147
2148
2149         if(ast_test_flag(vmu, MVM_ALLOCED))
2150                 free_user(vmu);
2151
2152         /* Ok, we're ready to rock and roll. Return to dialplan */
2153
2154         return res;
2155
2156 }
2157
2158 /*!\internal
2159  * \brief Dialplan function to record voicemail */
2160 static int minivm_record_exec(struct ast_channel *chan, const char *data)
2161 {
2162         int res = 0;
2163         char *tmp;
2164         struct leave_vm_options leave_options;
2165         int argc;
2166         char *argv[2];
2167         struct ast_flags flags = { 0 };
2168         char *opts[OPT_ARG_ARRAY_SIZE];
2169
2170         memset(&leave_options, 0, sizeof(leave_options));
2171
2172         /* Answer channel if it's not already answered */
2173         if (ast_channel_state(chan) != AST_STATE_UP)
2174                 ast_answer(chan);
2175
2176         if (ast_strlen_zero(data))  {
2177                 ast_log(LOG_ERROR, "Minivm needs at least an account argument \n");
2178                 return -1;
2179         }
2180         tmp = ast_strdupa((char *)data);
2181         argc = ast_app_separate_args(tmp, ',', argv, ARRAY_LEN(argv));
2182         if (argc == 2) {
2183                 if (ast_app_parse_options(minivm_app_options, &flags, opts, argv[1])) {
2184                         return -1;
2185                 }
2186                 ast_copy_flags(&leave_options, &flags, OPT_SILENT | OPT_BUSY_GREETING | OPT_UNAVAIL_GREETING );
2187                 if (ast_test_flag(&flags, OPT_RECORDGAIN)) {
2188                         int gain;
2189
2190                         if (sscanf(opts[OPT_ARG_RECORDGAIN], "%30d", &gain) != 1) {
2191                                 ast_log(LOG_WARNING, "Invalid value '%s' provided for record gain option\n", opts[OPT_ARG_RECORDGAIN]);
2192                                 return -1;
2193                         } else 
2194                                 leave_options.record_gain = (signed char) gain;
2195                 }
2196         } 
2197
2198         /* Now run the appliation and good luck to you! */
2199         res = leave_voicemail(chan, argv[0], &leave_options);
2200
2201         if (res == ERROR_LOCK_PATH) {
2202                 ast_log(LOG_ERROR, "Could not leave voicemail. The path is already locked.\n");
2203                 pbx_builtin_setvar_helper(chan, "MVM_RECORD_STATUS", "FAILED");
2204                 res = 0;
2205         }
2206         pbx_builtin_setvar_helper(chan, "MVM_RECORD_STATUS", "SUCCESS");
2207
2208         return res;
2209 }
2210
2211 /*!\internal
2212  * \brief Play voicemail prompts - either generic or user specific */
2213 static int minivm_greet_exec(struct ast_channel *chan, const char *data)
2214 {
2215         struct leave_vm_options leave_options = { 0, '\0'};
2216         int argc;
2217         char *argv[2];
2218         struct ast_flags flags = { 0 };
2219         char *opts[OPT_ARG_ARRAY_SIZE];
2220         int res = 0;
2221         int ausemacro = 0;
2222         int ousemacro = 0;
2223         int ouseexten = 0;
2224         char tmp[PATH_MAX];
2225         char dest[PATH_MAX];
2226         char prefile[PATH_MAX] = "";
2227         char tempfile[PATH_MAX] = "";
2228         char ext_context[256] = "";
2229         char *domain;
2230         char ecodes[16] = "#";
2231         char *tmpptr;
2232         struct minivm_account *vmu;
2233         char *username = argv[0];
2234
2235         if (ast_strlen_zero(data))  {
2236                 ast_log(LOG_ERROR, "Minivm needs at least an account argument \n");
2237                 return -1;
2238         }
2239         tmpptr = ast_strdupa((char *)data);
2240         argc = ast_app_separate_args(tmpptr, ',', argv, ARRAY_LEN(argv));
2241
2242         if (argc == 2) {
2243                 if (ast_app_parse_options(minivm_app_options, &flags, opts, argv[1]))
2244                         return -1;
2245                 ast_copy_flags(&leave_options, &flags, OPT_SILENT | OPT_BUSY_GREETING | OPT_UNAVAIL_GREETING );
2246         }
2247
2248         ast_copy_string(tmp, argv[0], sizeof(tmp));
2249         username = tmp;
2250         domain = strchr(tmp, '@');
2251         if (domain) {
2252                 *domain = '\0';
2253                 domain++;
2254         } 
2255         if (ast_strlen_zero(domain) || ast_strlen_zero(username)) {
2256                 ast_log(LOG_ERROR, "Need username@domain as argument. Sorry. Argument:  %s\n", argv[0]);
2257                 return -1;
2258         }
2259         ast_debug(1, "Trying to find configuration for user %s in domain %s\n", username, domain);
2260
2261         if (!(vmu = find_account(domain, username, TRUE))) {
2262                 ast_log(LOG_ERROR, "Could not allocate memory. \n");
2263                 return -1;
2264         }
2265
2266         /* Answer channel if it's not already answered */
2267         if (ast_channel_state(chan) != AST_STATE_UP)
2268                 ast_answer(chan);
2269
2270         /* Setup pre-file if appropriate */
2271         if (strcmp(vmu->domain, "localhost"))
2272                 snprintf(ext_context, sizeof(ext_context), "%s@%s", username, vmu->domain);
2273         else
2274                 ast_copy_string(ext_context, vmu->domain, sizeof(ext_context));
2275
2276         if (ast_test_flag(&leave_options, OPT_BUSY_GREETING)) {
2277                 res = check_dirpath(dest, sizeof(dest), vmu->domain, username, "busy");
2278                 if (res)
2279                         snprintf(prefile, sizeof(prefile), "%s%s/%s/busy", MVM_SPOOL_DIR, vmu->domain, username);
2280         } else if (ast_test_flag(&leave_options, OPT_UNAVAIL_GREETING)) {
2281                 res = check_dirpath(dest, sizeof(dest), vmu->domain, username, "unavail");
2282                 if (res)
2283                         snprintf(prefile, sizeof(prefile), "%s%s/%s/unavail", MVM_SPOOL_DIR, vmu->domain, username);
2284         }
2285         /* Check for temporary greeting - it overrides busy and unavail */
2286         snprintf(tempfile, sizeof(tempfile), "%s%s/%s/temp", MVM_SPOOL_DIR, vmu->domain, username);
2287         if (!(res = check_dirpath(dest, sizeof(dest), vmu->domain, username, "temp"))) {
2288                 ast_debug(2, "Temporary message directory does not exist, using default (%s)\n", tempfile);
2289                 ast_copy_string(prefile, tempfile, sizeof(prefile));
2290         }
2291         ast_debug(2, "Preparing to play message ...\n");
2292
2293         /* Check current or macro-calling context for special extensions */
2294         if (ast_test_flag(vmu, MVM_OPERATOR)) {
2295                 if (!ast_strlen_zero(vmu->exit)) {
2296                         if (ast_exists_extension(chan, vmu->exit, "o", 1,
2297                                 S_COR(ast_channel_caller(chan)->id.number.valid, ast_channel_caller(chan)->id.number.str, NULL))) {
2298                                 strncat(ecodes, "0", sizeof(ecodes) - strlen(ecodes) - 1);
2299                                 ouseexten = 1;
2300                         }
2301                 } else if (ast_exists_extension(chan, ast_channel_context(chan), "o", 1,
2302                         S_COR(ast_channel_caller(chan)->id.number.valid, ast_channel_caller(chan)->id.number.str, NULL))) {
2303                         strncat(ecodes, "0", sizeof(ecodes) - strlen(ecodes) - 1);
2304                         ouseexten = 1;
2305                 }
2306                 else if (!ast_strlen_zero(ast_channel_macrocontext(chan))
2307                         && ast_exists_extension(chan, ast_channel_macrocontext(chan), "o", 1,
2308                                 S_COR(ast_channel_caller(chan)->id.number.valid, ast_channel_caller(chan)->id.number.str, NULL))) {
2309                         strncat(ecodes, "0", sizeof(ecodes) - strlen(ecodes) - 1);
2310                         ousemacro = 1;
2311                 }
2312         }
2313
2314         if (!ast_strlen_zero(vmu->exit)) {
2315                 if (ast_exists_extension(chan, vmu->exit, "a", 1,
2316                         S_COR(ast_channel_caller(chan)->id.number.valid, ast_channel_caller(chan)->id.number.str, NULL))) {
2317                         strncat(ecodes, "*", sizeof(ecodes) -  strlen(ecodes) - 1);
2318                 }
2319         } else if (ast_exists_extension(chan, ast_channel_context(chan), "a", 1,
2320                 S_COR(ast_channel_caller(chan)->id.number.valid, ast_channel_caller(chan)->id.number.str, NULL))) {
2321                 strncat(ecodes, "*", sizeof(ecodes) -  strlen(ecodes) - 1);
2322         } else if (!ast_strlen_zero(ast_channel_macrocontext(chan))
2323                 && ast_exists_extension(chan, ast_channel_macrocontext(chan), "a", 1,
2324                         S_COR(ast_channel_caller(chan)->id.number.valid, ast_channel_caller(chan)->id.number.str, NULL))) {
2325                 strncat(ecodes, "*", sizeof(ecodes) -  strlen(ecodes) - 1);
2326                 ausemacro = 1;
2327         }
2328
2329         res = 0;        /* Reset */
2330         /* Play the beginning intro if desired */
2331         if (!ast_strlen_zero(prefile)) {
2332                 if (ast_streamfile(chan, prefile, ast_channel_language(chan)) > -1) 
2333                         res = ast_waitstream(chan, ecodes);
2334         } else {
2335                 ast_debug(2, "%s doesn't exist, doing what we can\n", prefile);
2336                 res = invent_message(chan, vmu->domain, username, ast_test_flag(&leave_options, OPT_BUSY_GREETING), ecodes);
2337         }
2338         if (res < 0) {
2339                 ast_debug(2, "Hang up during prefile playback\n");
2340                 pbx_builtin_setvar_helper(chan, "MVM_GREET_STATUS", "FAILED");
2341                 if(ast_test_flag(vmu, MVM_ALLOCED))
2342                         free_user(vmu);
2343                 return -1;
2344         }
2345         if (res == '#') {
2346                 /* On a '#' we skip the instructions */
2347                 ast_set_flag(&leave_options, OPT_SILENT);
2348                 res = 0;
2349         }
2350         if (!res && !ast_test_flag(&leave_options, OPT_SILENT)) {
2351                 res = ast_streamfile(chan, SOUND_INTRO, ast_channel_language(chan));
2352                 if (!res)
2353                         res = ast_waitstream(chan, ecodes);
2354                 if (res == '#') {
2355                         ast_set_flag(&leave_options, OPT_SILENT);
2356                         res = 0;
2357                 }
2358         }
2359         if (res > 0)
2360                 ast_stopstream(chan);
2361         /* Check for a '*' here in case the caller wants to escape from voicemail to something
2362            other than the operator -- an automated attendant or mailbox login for example */
2363         if (res == '*') {
2364                 ast_channel_exten_set(chan, "a");
2365                 if (!ast_strlen_zero(vmu->exit)) {
2366                         ast_channel_context_set(chan, vmu->exit);
2367                 } else if (ausemacro && !ast_strlen_zero(ast_channel_macrocontext(chan))) {
2368                         ast_channel_context_set(chan, ast_channel_macrocontext(chan));
2369                 }
2370                 ast_channel_priority_set(chan, 0);
2371                 pbx_builtin_setvar_helper(chan, "MVM_GREET_STATUS", "USEREXIT");
2372                 res = 0;
2373         } else if (res == '0') { /* Check for a '0' here */
2374                 if(ouseexten || ousemacro) {
2375                         ast_channel_exten_set(chan, "o");
2376                         if (!ast_strlen_zero(vmu->exit)) {
2377                                 ast_channel_context_set(chan, vmu->exit);
2378                         } else if (ousemacro && !ast_strlen_zero(ast_channel_macrocontext(chan))) {
2379                                 ast_channel_context_set(chan, ast_channel_macrocontext(chan));
2380                         }
2381                         ast_play_and_wait(chan, "transfer");
2382                         ast_channel_priority_set(chan, 0);
2383                         pbx_builtin_setvar_helper(chan, "MVM_GREET_STATUS", "USEREXIT");
2384                 }
2385                 res =  0;
2386         } else if (res < 0) {
2387                 pbx_builtin_setvar_helper(chan, "MVM_GREET_STATUS", "FAILED");
2388                 res = -1;
2389         } else
2390                 pbx_builtin_setvar_helper(chan, "MVM_GREET_STATUS", "SUCCESS");
2391
2392         if(ast_test_flag(vmu, MVM_ALLOCED))
2393                 free_user(vmu);
2394
2395
2396         /* Ok, we're ready to rock and roll. Return to dialplan */
2397         return res;
2398
2399 }
2400
2401 /*!\internal
2402  * \brief Dialplan application to delete voicemail */
2403 static int minivm_delete_exec(struct ast_channel *chan, const char *data)
2404 {
2405         int res = 0;
2406         char filename[BUFSIZ];
2407
2408         if (!ast_strlen_zero(data)) {
2409                 ast_copy_string(filename, (char *) data, sizeof(filename));
2410         } else {
2411                 ast_channel_lock(chan);
2412                 ast_copy_string(filename, pbx_builtin_getvar_helper(chan, "MVM_FILENAME"), sizeof(filename));
2413                 ast_channel_unlock(chan);
2414         }
2415
2416         if (ast_strlen_zero(filename)) {
2417                 ast_log(LOG_ERROR, "No filename given in application arguments or channel variable MVM_FILENAME\n");
2418                 return res;
2419         } 
2420
2421         /* Go ahead and delete audio files from system, they're not needed any more */
2422         /* We should look for both audio and text files here */
2423         if (ast_fileexists(filename, NULL, NULL) > 0) {
2424                 res = vm_delete(filename);
2425                 if (res) {
2426                         ast_debug(2, "Can't delete file: %s\n", filename);
2427                         pbx_builtin_setvar_helper(chan, "MVM_DELETE_STATUS", "FAILED");
2428                 } else {
2429                         ast_debug(2, "Deleted voicemail file :: %s \n", filename);
2430                         pbx_builtin_setvar_helper(chan, "MVM_DELETE_STATUS", "SUCCESS");
2431                 }
2432         } else {
2433                 ast_debug(2, "Filename does not exist: %s\n", filename);
2434                 pbx_builtin_setvar_helper(chan, "MVM_DELETE_STATUS", "FAILED");
2435         }
2436
2437         return res;
2438 }
2439
2440 /*! \brief Record specific messages for voicemail account */
2441 static int minivm_accmess_exec(struct ast_channel *chan, const char *data)
2442 {
2443         int argc = 0;
2444         char *argv[2];
2445         char filename[PATH_MAX];
2446         char tmp[PATH_MAX];
2447         char *domain;
2448         char *tmpptr = NULL;
2449         struct minivm_account *vmu;
2450         char *username;
2451         struct ast_flags flags = { 0 };
2452         char *opts[OPT_ARG_ARRAY_SIZE];
2453         int error = FALSE;
2454         char *message = NULL;
2455         char *prompt = NULL;
2456         int duration;
2457
2458         if (ast_strlen_zero(data))  {
2459                 ast_log(LOG_ERROR, "MinivmAccmess needs at least two arguments: account and option\n");
2460                 error = TRUE;
2461         } else {
2462                 tmpptr = ast_strdupa((char *)data);
2463                 argc = ast_app_separate_args(tmpptr, ',', argv, ARRAY_LEN(argv));
2464         }
2465
2466         if (argc <=1) {
2467                 ast_log(LOG_ERROR, "MinivmAccmess needs at least two arguments: account and option\n");
2468                 error = TRUE;
2469         }
2470         if (!error && strlen(argv[1]) > 1) {
2471                 ast_log(LOG_ERROR, "MinivmAccmess can only handle one option at a time. Bad option string: %s\n", argv[1]);
2472                 error = TRUE;
2473         }
2474
2475         if (!error && ast_app_parse_options(minivm_accmess_options, &flags, opts, argv[1])) {
2476                 ast_log(LOG_ERROR, "Can't parse option %s\n", argv[1]);
2477                 error = TRUE;
2478         }
2479
2480         if (error) {
2481                 pbx_builtin_setvar_helper(chan, "MVM_ACCMESS_STATUS", "FAILED");
2482                 return -1;
2483         }
2484
2485         ast_copy_string(tmp, argv[0], sizeof(tmp));
2486         username = tmp;
2487         domain = strchr(tmp, '@');
2488         if (domain) {
2489                 *domain = '\0';
2490                 domain++;
2491         } 
2492         if (ast_strlen_zero(domain) || ast_strlen_zero(username)) {
2493                 ast_log(LOG_ERROR, "Need username@domain as argument. Sorry. Argument 0 %s\n", argv[0]);
2494                 pbx_builtin_setvar_helper(chan, "MVM_ACCMESS_STATUS", "FAILED");
2495                 return -1;
2496         }
2497
2498         if(!(vmu = find_account(domain, username, TRUE))) {
2499                 /* We could not find user, let's exit */
2500                 ast_log(LOG_WARNING, "Could not allocate temporary memory for '%s@%s'\n", username, domain);
2501                 pbx_builtin_setvar_helper(chan, "MVM_ACCMESS_STATUS", "FAILED");
2502                 return -1;
2503         }
2504
2505         /* Answer channel if it's not already answered */
2506         if (ast_channel_state(chan) != AST_STATE_UP)
2507                 ast_answer(chan);
2508         
2509         /* Here's where the action is */
2510         if (ast_test_flag(&flags, OPT_BUSY_GREETING)) {
2511                 message = "busy";
2512                 prompt = "vm-rec-busy";
2513         } else if (ast_test_flag(&flags, OPT_UNAVAIL_GREETING)) {
2514                 message = "unavailable";
2515                 prompt = "vm-rec-unv";
2516         } else if (ast_test_flag(&flags, OPT_TEMP_GREETING)) {
2517                 message = "temp";
2518                 prompt = "vm-rec-temp";
2519         } else if (ast_test_flag(&flags, OPT_NAME_GREETING)) {
2520                 message = "greet";
2521                 prompt = "vm-rec-name";
2522         }
2523         snprintf(filename,sizeof(filename), "%s%s/%s/%s", MVM_SPOOL_DIR, vmu->domain, vmu->username, message);
2524         /* Maybe we should check the result of play_record_review ? */
2525         play_record_review(chan, prompt, filename, global_maxgreet, default_vmformat, 0, vmu, &duration, NULL, NULL, FALSE);
2526
2527         ast_debug(1, "Recorded new %s message in %s (duration %d)\n", message, filename, duration);
2528
2529         if(ast_test_flag(vmu, MVM_ALLOCED))
2530                 free_user(vmu);
2531
2532         pbx_builtin_setvar_helper(chan, "MVM_ACCMESS_STATUS", "SUCCESS");
2533
2534         /* Ok, we're ready to rock and roll. Return to dialplan */
2535         return 0;
2536 }
2537
2538 /*! \brief Append new mailbox to mailbox list from configuration file */
2539 static int create_vmaccount(char *name, struct ast_variable *var, int realtime)
2540 {
2541         struct minivm_account *vmu;
2542         char *domain;
2543         char *username;
2544         char accbuf[BUFSIZ];
2545
2546         ast_debug(3, "Creating %s account for [%s]\n", realtime ? "realtime" : "static", name);
2547
2548         ast_copy_string(accbuf, name, sizeof(accbuf));
2549         username = accbuf;
2550         domain = strchr(accbuf, '@');
2551         if (domain) {
2552                 *domain = '\0';
2553                 domain++;
2554         }
2555         if (ast_strlen_zero(domain)) {
2556                 ast_log(LOG_ERROR, "No domain given for mini-voicemail account %s. Not configured.\n", name);
2557                 return 0;
2558         }
2559
2560         ast_debug(3, "Creating static account for user %s domain %s\n", username, domain);
2561
2562         /* Allocate user account */
2563         vmu = ast_calloc(1, sizeof(*vmu));
2564         if (!vmu)
2565                 return 0;
2566         
2567         ast_copy_string(vmu->domain, domain, sizeof(vmu->domain));
2568         ast_copy_string(vmu->username, username, sizeof(vmu->username));
2569
2570         populate_defaults(vmu);
2571
2572         ast_debug(3, "...Configuring account %s\n", name);
2573
2574         while (var) {
2575                 ast_debug(3, "Configuring %s = \"%s\" for account %s\n", var->name, var->value, name);
2576                 if (!strcasecmp(var->name, "serveremail")) {
2577                         ast_copy_string(vmu->serveremail, var->value, sizeof(vmu->serveremail));
2578                 } else if (!strcasecmp(var->name, "email")) {
2579                         ast_copy_string(vmu->email, var->value, sizeof(vmu->email));
2580                 } else if (!strcasecmp(var->name, "accountcode")) {
2581                         ast_copy_string(vmu->accountcode, var->value, sizeof(vmu->accountcode));
2582                 } else if (!strcasecmp(var->name, "pincode")) {
2583                         ast_copy_string(vmu->pincode, var->value, sizeof(vmu->pincode));
2584                 } else if (!strcasecmp(var->name, "domain")) {
2585                         ast_copy_string(vmu->domain, var->value, sizeof(vmu->domain));
2586                 } else if (!strcasecmp(var->name, "language")) {
2587                         ast_copy_string(vmu->language, var->value, sizeof(vmu->language));
2588                 } else if (!strcasecmp(var->name, "timezone")) {
2589                         ast_copy_string(vmu->zonetag, var->value, sizeof(vmu->zonetag));
2590                 } else if (!strcasecmp(var->name, "externnotify")) {
2591                         ast_copy_string(vmu->externnotify, var->value, sizeof(vmu->externnotify));
2592                 } else if (!strcasecmp(var->name, "etemplate")) {
2593                         ast_copy_string(vmu->etemplate, var->value, sizeof(vmu->etemplate));
2594                 } else if (!strcasecmp(var->name, "ptemplate")) {
2595                         ast_copy_string(vmu->ptemplate, var->value, sizeof(vmu->ptemplate));
2596                 } else if (!strcasecmp(var->name, "fullname")) {
2597                         ast_copy_string(vmu->fullname, var->value, sizeof(vmu->fullname));
2598                 } else if (!strcasecmp(var->name, "setvar")) {
2599                         char *varval;
2600                         char *varname = ast_strdupa(var->value);
2601                         struct ast_variable *tmpvar;
2602
2603                         if ((varval = strchr(varname, '='))) {
2604                                 *varval = '\0';
2605                                 varval++;
2606                                 if ((tmpvar = ast_variable_new(varname, varval, ""))) {
2607                                         tmpvar->next = vmu->chanvars;
2608                                         vmu->chanvars = tmpvar;
2609                                 }
2610                         }
2611                 } else if (!strcasecmp(var->name, "pager")) {
2612                         ast_copy_string(vmu->pager, var->value, sizeof(vmu->pager));
2613                 } else if (!strcasecmp(var->name, "volgain")) {
2614                         sscanf(var->value, "%30lf", &vmu->volgain);
2615                 } else {
2616                         ast_log(LOG_ERROR, "Unknown configuration option for minivm account %s : %s\n", name, var->name);
2617                 }
2618                 var = var->next;
2619         }
2620         ast_debug(3, "...Linking account %s\n", name);
2621         
2622         AST_LIST_LOCK(&minivm_accounts);
2623         AST_LIST_INSERT_TAIL(&minivm_accounts, vmu, list);
2624         AST_LIST_UNLOCK(&minivm_accounts);
2625
2626         global_stats.voicemailaccounts++;
2627
2628         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)" : "");
2629         return 0;
2630 }
2631
2632 /*! \brief Free Mini Voicemail timezone */
2633 static void free_zone(struct minivm_zone *z)
2634 {
2635         ast_free(z);
2636 }
2637
2638 /*! \brief Clear list of timezones */
2639 static void timezone_destroy_list(void)
2640 {
2641         struct minivm_zone *this;
2642
2643         AST_LIST_LOCK(&minivm_zones);
2644         while ((this = AST_LIST_REMOVE_HEAD(&minivm_zones, list))) 
2645                 free_zone(this);
2646                 
2647         AST_LIST_UNLOCK(&minivm_zones);
2648 }
2649
2650 /*! \brief Add time zone to memory list */
2651 static int timezone_add(const char *zonename, const char *config)
2652 {
2653         struct minivm_zone *newzone;
2654         char *msg_format, *timezone_str;
2655
2656         newzone = ast_calloc(1, sizeof(*newzone));
2657         if (newzone == NULL)
2658                 return 0;
2659
2660         msg_format = ast_strdupa(config);
2661
2662         timezone_str = strsep(&msg_format, "|");
2663         if (!msg_format) {
2664                 ast_log(LOG_WARNING, "Invalid timezone definition : %s\n", zonename);
2665                 ast_free(newzone);
2666                 return 0;
2667         }
2668                         
2669         ast_copy_string(newzone->name, zonename, sizeof(newzone->name));
2670         ast_copy_string(newzone->timezone, timezone_str, sizeof(newzone->timezone));
2671         ast_copy_string(newzone->msg_format, msg_format, sizeof(newzone->msg_format));
2672
2673         AST_LIST_LOCK(&minivm_zones);
2674         AST_LIST_INSERT_TAIL(&minivm_zones, newzone, list);
2675         AST_LIST_UNLOCK(&minivm_zones);
2676
2677         global_stats.timezones++;
2678
2679         return 0;
2680 }
2681
2682 /*! \brief Read message template from file */
2683 static char *message_template_parse_filebody(const char *filename) {
2684         char buf[BUFSIZ * 6];
2685         char readbuf[BUFSIZ];
2686         char filenamebuf[BUFSIZ];
2687         char *writepos;
2688         char *messagebody;
2689         FILE *fi;
2690         int lines = 0;
2691
2692         if (ast_strlen_zero(filename))
2693                 return NULL;
2694         if (*filename == '/') 
2695                 ast_copy_string(filenamebuf, filename, sizeof(filenamebuf));
2696         else 
2697                 snprintf(filenamebuf, sizeof(filenamebuf), "%s/%s", ast_config_AST_CONFIG_DIR, filename);
2698
2699         if (!(fi = fopen(filenamebuf, "r"))) {
2700                 ast_log(LOG_ERROR, "Can't read message template from file: %s\n", filenamebuf);
2701                 return NULL;
2702         }
2703         writepos = buf;
2704         while (fgets(readbuf, sizeof(readbuf), fi)) {
2705                 lines ++;
2706                 if (writepos != buf) {
2707                         *writepos = '\n';               /* Replace EOL with new line */
2708                         writepos++;
2709                 }
2710                 ast_copy_string(writepos, readbuf, sizeof(buf) - (writepos - buf));
2711                 writepos += strlen(readbuf) - 1;
2712         }
2713         fclose(fi);
2714         messagebody = ast_calloc(1, strlen(buf + 1));
2715         ast_copy_string(messagebody, buf, strlen(buf) + 1);
2716         ast_debug(4, "---> Size of allocation %d\n", (int) strlen(buf + 1) );
2717         ast_debug(4, "---> Done reading message template : \n%s\n---- END message template--- \n", messagebody);
2718
2719         return messagebody;
2720 }
2721
2722 /*! \brief Parse emailbody template from configuration file */
2723 static char *message_template_parse_emailbody(const char *configuration)
2724 {
2725         char *tmpread, *tmpwrite;
2726         char *emailbody = ast_strdup(configuration);
2727
2728         /* substitute strings \t and \n into the apropriate characters */
2729         tmpread = tmpwrite = emailbody;
2730         while ((tmpwrite = strchr(tmpread,'\\'))) {
2731                int len = strlen("\n");
2732                switch (tmpwrite[1]) {
2733                case 'n':
2734                       memmove(tmpwrite + len, tmpwrite + 2, strlen(tmpwrite + 2) + 1);
2735                       strncpy(tmpwrite, "\n", len);
2736                       break;
2737                case 't':
2738                       memmove(tmpwrite + len, tmpwrite + 2, strlen(tmpwrite + 2) + 1);
2739                       strncpy(tmpwrite, "\t", len);
2740                       break;
2741                default:
2742                       ast_log(LOG_NOTICE, "Substitution routine does not support this character: %c\n", tmpwrite[1]);
2743                }
2744                tmpread = tmpwrite + len;
2745         }
2746         return emailbody;       
2747 }
2748
2749 /*! \brief Apply general configuration options */
2750 static int apply_general_options(struct ast_variable *var)
2751 {
2752         int error = 0;
2753
2754         while (var) {
2755                 /* Mail command */
2756                 if (!strcmp(var->name, "mailcmd")) {
2757                         ast_copy_string(global_mailcmd, var->value, sizeof(global_mailcmd)); /* User setting */
2758                 } else if (!strcmp(var->name, "maxgreet")) {
2759                         global_maxgreet = atoi(var->value);
2760                 } else if (!strcmp(var->name, "maxsilence")) {
2761                         global_maxsilence = atoi(var->value);
2762                         if (global_maxsilence > 0)
2763                                 global_maxsilence *= 1000;
2764                 } else if (!strcmp(var->name, "logfile")) {
2765                         if (!ast_strlen_zero(var->value) ) {
2766                                 if(*(var->value) == '/')
2767                                         ast_copy_string(global_logfile, var->value, sizeof(global_logfile));
2768                                 else
2769                                         snprintf(global_logfile, sizeof(global_logfile), "%s/%s", ast_config_AST_LOG_DIR, var->value);
2770                         }
2771                 } else if (!strcmp(var->name, "externnotify")) {
2772                         /* External voicemail notify application */
2773                         ast_copy_string(global_externnotify, var->value, sizeof(global_externnotify));
2774                 } else if (!strcmp(var->name, "silencetreshold")) {
2775                         /* Silence treshold */
2776                         global_silencethreshold = atoi(var->value);
2777                 } else if (!strcmp(var->name, "maxmessage")) {
2778                         int x;
2779                         if (sscanf(var->value, "%30d", &x) == 1) {
2780                                 global_vmmaxmessage = x;
2781                         } else {
2782                                 error ++;
2783                                 ast_log(LOG_WARNING, "Invalid max message time length\n");
2784                         }
2785                 } else if (!strcmp(var->name, "minmessage")) {
2786                         int x;
2787                         if (sscanf(var->value, "%30d", &x) == 1) {
2788                                 global_vmminmessage = x;
2789                                 if (global_maxsilence <= global_vmminmessage)
2790                                         ast_log(LOG_WARNING, "maxsilence should be less than minmessage or you may get empty messages\n");
2791                         } else {
2792                                 error ++;
2793                                 ast_log(LOG_WARNING, "Invalid min message time length\n");
2794                         }
2795                 } else if (!strcmp(var->name, "format")) {
2796                         ast_copy_string(default_vmformat, var->value, sizeof(default_vmformat));
2797                 } else if (!strcmp(var->name, "review")) {
2798                         ast_set2_flag((&globalflags), ast_true(var->value), MVM_REVIEW);        
2799                 } else if (!strcmp(var->name, "operator")) {
2800                         ast_set2_flag((&globalflags), ast_true(var->value), MVM_OPERATOR);      
2801                 }
2802                 var = var->next;
2803         }
2804         return error;
2805 }
2806
2807 /*! \brief Load minivoicemail configuration */
2808 static int load_config(int reload)
2809 {
2810         struct ast_config *cfg;
2811         struct ast_variable *var;
2812         char *cat;
2813         const char *chanvar;
2814         int error = 0;
2815         struct minivm_template *template;
2816         struct ast_flags config_flags = { reload ? CONFIG_FLAG_FILEUNCHANGED : 0 };
2817
2818         cfg = ast_config_load(VOICEMAIL_CONFIG, config_flags);
2819         if (cfg == CONFIG_STATUS_FILEUNCHANGED) {
2820                 return 0;
2821         } else if (cfg == CONFIG_STATUS_FILEINVALID) {
2822                 ast_log(LOG_ERROR, "Config file " VOICEMAIL_CONFIG " is in an invalid format.  Aborting.\n");
2823                 return 0;
2824         }
2825
2826         ast_mutex_lock(&minivmlock);
2827
2828         /* Destroy lists to reconfigure */
2829         message_destroy_list();         /* Destroy list of voicemail message templates */
2830         timezone_destroy_list();        /* Destroy list of timezones */
2831         vmaccounts_destroy_list();      /* Destroy list of voicemail accounts */
2832         ast_debug(2, "Destroyed memory objects...\n");
2833
2834         /* First, set some default settings */
2835         global_externnotify[0] = '\0';
2836         global_logfile[0] = '\0';
2837         global_vmmaxmessage = 2000;
2838         global_maxgreet = 2000;
2839         global_vmminmessage = 0;
2840         strcpy(global_mailcmd, SENDMAIL);
2841         global_maxsilence = 0;
2842         global_saydurationminfo = 2;
2843         ast_copy_string(default_vmformat, "wav", sizeof(default_vmformat));
2844         ast_set2_flag((&globalflags), FALSE, MVM_REVIEW);       
2845         ast_set2_flag((&globalflags), FALSE, MVM_OPERATOR);     
2846         /* Reset statistics */
2847         memset(&global_stats, 0, sizeof(global_stats));
2848         global_stats.reset = ast_tvnow();
2849
2850         global_silencethreshold = ast_dsp_get_threshold_from_settings(THRESHOLD_SILENCE);
2851
2852         /* Make sure we could load configuration file */
2853         if (!cfg) {
2854                 ast_log(LOG_WARNING, "Failed to load configuration file. Module activated with default settings.\n");
2855                 ast_mutex_unlock(&minivmlock);
2856                 return 0;
2857         }
2858
2859         ast_debug(2, "Loaded configuration file, now parsing\n");
2860
2861         /* General settings */
2862
2863         cat = ast_category_browse(cfg, NULL);
2864         while (cat) {
2865                 ast_debug(3, "Found configuration section [%s]\n", cat);
2866                 if (!strcasecmp(cat, "general")) {
2867                         /* Nothing right now */
2868                         error += apply_general_options(ast_variable_browse(cfg, cat));
2869                 } else if (!strncasecmp(cat, "template-", 9))  {
2870                         /* Template */
2871                         char *name = cat + 9;
2872
2873                         /* Now build and link template to list */
2874                         error += message_template_build(name, ast_variable_browse(cfg, cat));
2875                 } else {
2876                         var = ast_variable_browse(cfg, cat);
2877                         if (!strcasecmp(cat, "zonemessages")) {
2878                                 /* Timezones in this context */
2879                                 while (var) {
2880                                         timezone_add(var->name, var->value);
2881                                         var = var->next;
2882                                 }
2883                         } else {
2884                                 /* Create mailbox from this */
2885                                 error += create_vmaccount(cat, var, FALSE);
2886                         }
2887                 }
2888                 /* Find next section in configuration file */
2889                 cat = ast_category_browse(cfg, cat);
2890         }
2891
2892         /* Configure the default email template */
2893         message_template_build("email-default", NULL);
2894         template = message_template_find("email-default");
2895
2896         /* Load date format config for voicemail mail */
2897         if ((chanvar = ast_variable_retrieve(cfg, "general", "emaildateformat"))) 
2898                 ast_copy_string(template->dateformat, chanvar, sizeof(template->dateformat));
2899         if ((chanvar = ast_variable_retrieve(cfg, "general", "emailfromstring")))
2900                 ast_copy_string(template->fromaddress, chanvar, sizeof(template->fromaddress));
2901         if ((chanvar = ast_variable_retrieve(cfg, "general", "emailaaddress")))
2902                 ast_copy_string(template->serveremail, chanvar, sizeof(template->serveremail));
2903         if ((chanvar = ast_variable_retrieve(cfg, "general", "emailcharset")))
2904                 ast_copy_string(template->charset, chanvar, sizeof(template->charset));
2905         if ((chanvar = ast_variable_retrieve(cfg, "general", "emailsubject"))) 
2906                 ast_copy_string(template->subject, chanvar, sizeof(template->subject));
2907         if ((chanvar = ast_variable_retrieve(cfg, "general", "emailbody"))) 
2908                 template->body = message_template_parse_emailbody(chanvar);
2909         template->attachment = TRUE;
2910
2911         message_template_build("pager-default", NULL);
2912         template = message_template_find("pager-default");
2913         if ((chanvar = ast_variable_retrieve(cfg, "general", "pagerfromstring")))
2914                 ast_copy_string(template->fromaddress, chanvar, sizeof(template->fromaddress));
2915         if ((chanvar = ast_variable_retrieve(cfg, "general", "pageraddress")))
2916                 ast_copy_string(template->serveremail, chanvar, sizeof(template->serveremail));
2917         if ((chanvar = ast_variable_retrieve(cfg, "general", "pagercharset")))
2918                 ast_copy_string(template->charset, chanvar, sizeof(template->charset));
2919         if ((chanvar = ast_variable_retrieve(cfg, "general", "pagersubject")))
2920                 ast_copy_string(template->subject, chanvar,sizeof(template->subject));
2921         if ((chanvar = ast_variable_retrieve(cfg, "general", "pagerbody"))) 
2922                 template->body = message_template_parse_emailbody(chanvar);
2923         template->attachment = FALSE;
2924
2925         if (error)
2926                 ast_log(LOG_ERROR, "--- A total of %d errors found in mini-voicemail configuration\n", error);
2927
2928         ast_mutex_unlock(&minivmlock);
2929         ast_config_destroy(cfg);
2930
2931         /* Close log file if it's open and disabled */
2932         if(minivmlogfile)
2933                 fclose(minivmlogfile);
2934
2935         /* Open log file if it's enabled */
2936         if(!ast_strlen_zero(global_logfile)) {
2937                 minivmlogfile = fopen(global_logfile, "a");
2938                 if(!minivmlogfile)
2939                         ast_log(LOG_ERROR, "Failed to open minivm log file %s : %s\n", global_logfile, strerror(errno));
2940                 if (minivmlogfile)
2941                         ast_debug(3, "Opened log file %s \n", global_logfile);
2942         }
2943
2944         return 0;
2945 }
2946
2947 /*! \brief CLI routine for listing templates */
2948 static char *handle_minivm_list_templates(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
2949 {
2950         struct minivm_template *this;
2951 #define HVLT_OUTPUT_FORMAT "%-15s %-10s %-10s %-15.15s %-50s\n"
2952         int count = 0;
2953
2954         switch (cmd) {
2955         case CLI_INIT:
2956                 e->command = "minivm list templates";
2957                 e->usage =
2958                         "Usage: minivm list templates\n"
2959                         "       Lists message templates for e-mail, paging and IM\n";
2960                 return NULL;
2961         case CLI_GENERATE:
2962                 return NULL;
2963         }
2964
2965         if (a->argc > 3)
2966                 return CLI_SHOWUSAGE;
2967
2968         AST_LIST_LOCK(&message_templates);
2969         if (AST_LIST_EMPTY(&message_templates)) {
2970                 ast_cli(a->fd, "There are no message templates defined\n");
2971                 AST_LIST_UNLOCK(&message_templates);
2972                 return CLI_FAILURE;
2973         }
2974         ast_cli(a->fd, HVLT_OUTPUT_FORMAT, "Template name", "Charset", "Locale", "Attach media", "Subject");
2975         ast_cli(a->fd, HVLT_OUTPUT_FORMAT, "-------------", "-------", "------", "------------", "-------");
2976         AST_LIST_TRAVERSE(&message_templates, this, list) {
2977                 ast_cli(a->fd, HVLT_OUTPUT_FORMAT, this->name, 
2978                         this->charset ? this->charset : "-", 
2979                         this->locale ? this->locale : "-",
2980                         this->attachment ? "Yes" : "No",
2981                         this->subject ? this->subject : "-");
2982                 count++;
2983         }
2984         AST_LIST_UNLOCK(&message_templates);
2985         ast_cli(a->fd, "\n * Total: %d minivoicemail message templates\n", count);
2986         return CLI_SUCCESS;
2987 }
2988
2989 static char *complete_minivm_show_users(const char *line, const char *word, int pos, int state)
2990 {
2991         int which = 0;
2992         int wordlen;
2993         struct minivm_account *vmu;
2994         const char *domain = "";
2995
2996         /* 0 - voicemail; 1 - list; 2 - accounts; 3 - for; 4 - <domain> */
2997         if (pos > 4)
2998                 return NULL;
2999         if (pos == 3)
3000                 return (state == 0) ? ast_strdup("for") : NULL;
3001         wordlen = strlen(word);
3002         AST_LIST_TRAVERSE(&minivm_accounts, vmu, list) {
3003                 if (!strncasecmp(word, vmu->domain, wordlen)) {
3004                         if (domain && strcmp(domain, vmu->domain) && ++which > state)
3005                                 return ast_strdup(vmu->domain);
3006                         /* ignore repeated domains ? */
3007                         domain = vmu->domain;
3008                 }
3009         }
3010         return NULL;
3011 }
3012
3013 /*! \brief CLI command to list voicemail accounts */
3014 static char *handle_minivm_show_users(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
3015 {
3016         struct minivm_account *vmu;
3017 #define HMSU_OUTPUT_FORMAT "%-23s %-15s %-15s %-10s %-10s %-50s\n"
3018         int count = 0;
3019
3020         switch (cmd) {
3021         case CLI_INIT:
3022                 e->command = "minivm list accounts";
3023                 e->usage =
3024                         "Usage: minivm list accounts\n"
3025                         "       Lists all mailboxes currently set up\n";
3026                 return NULL;
3027         case CLI_GENERATE:
3028                 return complete_minivm_show_users(a->line, a->word, a->pos, a->n);
3029         }
3030
3031         if ((a->argc < 3) || (a->argc > 5) || (a->argc == 4))
3032                 return CLI_SHOWUSAGE;
3033         if ((a->argc == 5) && strcmp(a->argv[3],"for"))
3034                 return CLI_SHOWUSAGE;
3035
3036         AST_LIST_LOCK(&minivm_accounts);
3037         if (AST_LIST_EMPTY(&minivm_accounts)) {
3038                 ast_cli(a->fd, "There are no voicemail users currently defined\n");
3039                 AST_LIST_UNLOCK(&minivm_accounts);
3040                 return CLI_FAILURE;
3041         }
3042         ast_cli(a->fd, HMSU_OUTPUT_FORMAT, "User", "E-Template", "P-template", "Zone", "Format", "Full name");
3043         ast_cli(a->fd, HMSU_OUTPUT_FORMAT, "----", "----------", "----------", "----", "------", "---------");
3044         AST_LIST_TRAVERSE(&minivm_accounts, vmu, list) {
3045                 char tmp[256] = "";
3046                 if ((a->argc == 3) || ((a->argc == 5) && !strcmp(a->argv[4], vmu->domain))) {
3047                         count++;
3048                         snprintf(tmp, sizeof(tmp), "%s@%s", vmu->username, vmu->domain);
3049                         ast_cli(a->fd, HMSU_OUTPUT_FORMAT, tmp, vmu->etemplate ? vmu->etemplate : "-", 
3050                                 vmu->ptemplate ? vmu->ptemplate : "-",
3051                                 vmu->zonetag ? vmu->zonetag : "-", 
3052                                 vmu->attachfmt ? vmu->attachfmt : "-",
3053                                 vmu->fullname);
3054                 }
3055         }
3056         AST_LIST_UNLOCK(&minivm_accounts);
3057         ast_cli(a->fd, "\n * Total: %d minivoicemail accounts\n", count);
3058         return CLI_SUCCESS;
3059 }
3060
3061 /*! \brief Show a list of voicemail zones in the CLI */
3062 static char *handle_minivm_show_zones(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
3063 {
3064         struct minivm_zone *zone;
3065 #define HMSZ_OUTPUT_FORMAT "%-15s %-20s %-45s\n"
3066         char *res = CLI_SUCCESS;
3067
3068         switch (cmd) {
3069         case CLI_INIT:
3070                 e->command = "minivm list zones";
3071                 e->usage =
3072                         "Usage: minivm list zones\n"
3073                         "       Lists zone message formats\n";
3074                 return NULL;
3075         case CLI_GENERATE:
3076                 return NULL;
3077         }
3078
3079         if (a->argc != e->args)
3080                 return CLI_SHOWUSAGE;
3081
3082         AST_LIST_LOCK(&minivm_zones);
3083         if (!AST_LIST_EMPTY(&minivm_zones)) {
3084                 ast_cli(a->fd, HMSZ_OUTPUT_FORMAT, "Zone", "Timezone", "Message Format");
3085                 ast_cli(a->fd, HMSZ_OUTPUT_FORMAT, "----", "--------", "--------------");
3086                 AST_LIST_TRAVERSE(&minivm_zones, zone, list) {
3087                         ast_cli(a->fd, HMSZ_OUTPUT_FORMAT, zone->name, zone->timezone, zone->msg_format);
3088                 }
3089         } else {
3090                 ast_cli(a->fd, "There are no voicemail zones currently defined\n");
3091                 res = CLI_FAILURE;
3092         }
3093         AST_LIST_UNLOCK(&minivm_zones);
3094
3095         return res;
3096 }
3097
3098 /*! \brief CLI Show settings */
3099 static char *handle_minivm_show_settings(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
3100 {
3101         switch (cmd) {
3102         case CLI_INIT:
3103                 e->command = "minivm show settings";
3104                 e->usage =
3105                         "Usage: minivm show settings\n"
3106                         "       Display Mini-Voicemail general settings\n";
3107                 return NULL;
3108         case CLI_GENERATE:
3109                 return NULL;
3110         }
3111
3112         ast_cli(a->fd, "* Mini-Voicemail general settings\n");
3113         ast_cli(a->fd, "  -------------------------------\n");
3114         ast_cli(a->fd, "\n");
3115         ast_cli(a->fd, "  Mail command (shell):               %s\n", global_mailcmd);
3116         ast_cli(a->fd, "  Max silence:                        %d\n", global_maxsilence);
3117         ast_cli(a->fd, "  Silence threshold:                  %d\n", global_silencethreshold);
3118         ast_cli(a->fd, "  Max message length (secs):          %d\n", global_vmmaxmessage);
3119         ast_cli(a->fd, "  Min message length (secs):          %d\n", global_vmminmessage);
3120         ast_cli(a->fd, "  Default format:                     %s\n", default_vmformat);
3121         ast_cli(a->fd, "  Extern notify (shell):              %s\n", global_externnotify);
3122         ast_cli(a->fd, "  Logfile:                            %s\n", global_logfile[0] ? global_logfile : "<disabled>");
3123         ast_cli(a->fd, "  Operator exit:                      %s\n", ast_test_flag(&globalflags, MVM_OPERATOR) ? "Yes" : "No");
3124         ast_cli(a->fd, "  Message review:                     %s\n", ast_test_flag(&globalflags, MVM_REVIEW) ? "Yes" : "No");
3125
3126         ast_cli(a->fd, "\n");
3127         return CLI_SUCCESS;
3128 }
3129
3130 /*! \brief Show stats */
3131 static char *handle_minivm_show_stats(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
3132 {
3133         struct ast_tm timebuf;
3134         char buf[BUFSIZ];
3135
3136         switch (cmd) {
3137         
3138         case CLI_INIT:
3139                 e->command = "minivm show stats";
3140                 e->usage =
3141                         "Usage: minivm show stats\n"
3142                         "       Display Mini-Voicemail counters\n";
3143                 return NULL;
3144         case CLI_GENERATE:
3145                 return NULL;
3146         }
3147
3148         ast_cli(a->fd, "* Mini-Voicemail statistics\n");
3149         ast_cli(a->fd, "  -------------------------\n");
3150         ast_cli(a->fd, "\n");
3151         ast_cli(a->fd, "  Voicemail accounts:                  %5d\n", global_stats.voicemailaccounts);
3152         ast_cli(a->fd, "  Templates:                           %5d\n", global_stats.templates);
3153         ast_cli(a->fd, "  Timezones:                           %5d\n", global_stats.timezones);
3154         if (global_stats.receivedmessages == 0) {
3155                 ast_cli(a->fd, "  Received messages since last reset:  <none>\n");
3156         } else {
3157                 ast_cli(a->fd, "  Received messages since last reset:  %d\n", global_stats.receivedmessages);
3158                 ast_localtime(&global_stats.lastreceived, &timebuf, NULL);
3159                 ast_strftime(buf, sizeof(buf), "%a %b %e %r %Z %Y", &timebuf);
3160                 ast_cli(a->fd, "  Last received voicemail:             %s\n", buf);
3161         }
3162         ast_localtime(&global_stats.reset, &timebuf, NULL);
3163         ast_strftime(buf, sizeof(buf), "%a %b %e %r %Z %Y", &timebuf);
3164         ast_cli(a->fd, "  Last reset:                          %s\n", buf);
3165
3166         ast_cli(a->fd, "\n");
3167         return CLI_SUCCESS;
3168 }
3169
3170 /*! \brief  ${MINIVMACCOUNT()} Dialplan function - reads account data */
3171 static int minivm_account_func_read(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len)
3172 {
3173         struct minivm_account *vmu;
3174         char *username, *domain, *colname;
3175
3176         username = ast_strdupa(data);
3177
3178         if ((colname = strchr(username, ':'))) {
3179                 *colname = '\0';
3180                 colname++;
3181         } else {
3182                 colname = "path";
3183         }
3184         if ((domain = strchr(username, '@'))) {
3185                 *domain = '\0';
3186                 domain++;
3187         }
3188         if (ast_strlen_zero(username) || ast_strlen_zero(domain)) {
3189                 ast_log(LOG_ERROR, "This function needs a username and a domain: username@domain\n");
3190                 return 0;
3191         }
3192
3193         if (!(vmu = find_account(domain, username, TRUE)))
3194                 return 0;
3195
3196         if (!strcasecmp(colname, "hasaccount")) {
3197                 ast_copy_string(buf, (ast_test_flag(vmu, MVM_ALLOCED) ? "0" : "1"), len);
3198         } else  if (!strcasecmp(colname, "fullname")) { 
3199                 ast_copy_string(buf, vmu->fullname, len);
3200         } else  if (!strcasecmp(colname, "email")) { 
3201                 if (!ast_strlen_zero(vmu->email))
3202                         ast_copy_string(buf, vmu->email, len);
3203                 else
3204                         snprintf(buf, len, "%s@%s", vmu->username, vmu->domain);
3205         } else  if (!strcasecmp(colname, "pager")) { 
3206                 ast_copy_string(buf, vmu->pager, len);
3207         } else  if (!strcasecmp(colname, "etemplate")) { 
3208                 if (!ast_strlen_zero(vmu->etemplate))
3209                         ast_copy_string(buf, vmu->etemplate, len);
3210                 else
3211                         ast_copy_string(buf, "email-default", len);
3212         } else  if (!strcasecmp(colname, "language")) { 
3213                 ast_copy_string(buf, vmu->language, len);
3214         } else  if (!strcasecmp(colname, "timezone")) { 
3215                 ast_copy_string(buf, vmu->zonetag, len);
3216         } else  if (!strcasecmp(colname, "ptemplate")) { 
3217                 if (!ast_strlen_zero(vmu->ptemplate))
3218                         ast_copy_string(buf, vmu->ptemplate, len);
3219                 else
3220                         ast_copy_string(buf, "email-default", len);
3221         } else  if (!strcasecmp(colname, "accountcode")) {
3222                 ast_copy_string(buf, vmu->accountcode, len);
3223         } else  if (!strcasecmp(colname, "pincode")) {
3224                 ast_copy_string(buf, vmu->pincode, len);
3225         } else  if (!strcasecmp(colname, "path")) {
3226                 check_dirpath(buf, len, vmu->domain, vmu->username, NULL);
3227         } else {        /* Look in channel variables */
3228                 struct ast_variable *var;
3229
3230                 for (var = vmu->chanvars ; var ; var = var->next)
3231                         if (!strcmp(var->name, colname)) {
3232                                 ast_copy_string(buf, var->value, len);
3233                                 break;
3234                         }
3235         }
3236
3237         if(ast_test_flag(vmu, MVM_ALLOCED))
3238                 free_user(vmu);
3239
3240         return 0;
3241 }
3242
3243 /*! \brief lock directory
3244
3245    only return failure if ast_lock_path returns 'timeout',
3246    not if the path does not exist or any other reason
3247 */
3248 static int vm_lock_path(const char *path)
3249 {
3250         switch (ast_lock_path(path)) {
3251         case AST_LOCK_TIMEOUT:
3252                 return -1;
3253         default:
3254                 return 0;
3255         }
3256 }
3257
3258 /*! \brief Access counter file, lock directory, read and possibly write it again changed 
3259         \param directory        Directory to crate file in
3260         \param countername      filename 
3261         \param value            If set to zero, we only read the variable
3262         \param operand          0 to read, 1 to set new value, 2 to change 
3263         \return -1 on error, otherwise counter value
3264 */
3265 static int access_counter_file(char *directory, char *countername, int value, int operand)
3266 {
3267         char filename[BUFSIZ];
3268         char readbuf[BUFSIZ];
3269         FILE *counterfile;
3270         int old = 0, counter = 0;
3271
3272         /* Lock directory */
3273         if (vm_lock_path(directory)) {
3274                 return -1;      /* Could not lock directory */
3275         }
3276         snprintf(filename, sizeof(filename), "%s/%s.counter", directory, countername);
3277         if (operand != 1) {
3278                 counterfile = fopen(filename, "r");
3279                 if (counterfile) {
3280                         if(fgets(readbuf, sizeof(readbuf), counterfile)) {
3281                                 ast_debug(3, "Read this string from counter file: %s\n", readbuf);
3282                                 old = counter = atoi(readbuf);
3283                         }
3284                         fclose(counterfile);
3285                 }
3286         }
3287         switch (operand) {
3288         case 0: /* Read only */
3289                 ast_unlock_path(directory);
3290                 ast_debug(2, "MINIVM Counter %s/%s: Value %d\n", directory, countername, counter);
3291                 return counter;
3292                 break;
3293         case 1: /* Set new value */
3294                 counter = value;
3295                 break;
3296         case 2: /* Change value */
3297                 counter += value;
3298                 if (counter < 0)        /* Don't allow counters to fall below zero */
3299                         counter = 0;
3300                 break;
3301         }
3302         
3303         /* Now, write the new value to the file */
3304         counterfile = fopen(filename, "w");
3305         if (!counterfile) {
3306                 ast_log(LOG_ERROR, "Could not open counter file for writing : %s - %s\n", filename, strerror(errno));
3307                 ast_unlock_path(directory);
3308                 return -1;      /* Could not open file for writing */
3309         }
3310         fprintf(counterfile, "%d\n\n", counter);
3311         fclose(counterfile);
3312         ast_unlock_path(directory);
3313         ast_debug(2, "MINIVM Counter %s/%s: Old value %d New value %d\n", directory, countername, old, counter);
3314         return counter;
3315 }
3316
3317 /*! \brief  ${MINIVMCOUNTER()} Dialplan function - read counters */
3318 static int minivm_counter_func_read(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len)
3319 {
3320         char *username, *domain, *countername;
3321         char userpath[BUFSIZ];
3322         int res;
3323
3324         *buf = '\0';
3325
3326         username = ast_strdupa(data);
3327
3328         if ((countername = strchr(username, ':'))) {
3329                 *countername = '\0';
3330                 countername++;
3331         } 
3332
3333         if ((domain = strchr(username, '@'))) {
3334                 *domain = '\0';
3335                 domain++;
3336         }
3337
3338         /* If we have neither username nor domain now, let's give up */
3339         if (ast_strlen_zero(username) && ast_strlen_zero(domain)) {
3340                 ast_log(LOG_ERROR, "No account given\n");
3341                 return -1;
3342         }
3343
3344         if (ast_strlen_zero(countername)) {
3345                 ast_log(LOG_ERROR, "This function needs two arguments: Account:countername\n");
3346                 return -1;
3347         }
3348
3349         /* We only have a domain, no username */
3350         if (!ast_strlen_zero(username) && ast_strlen_zero(domain)) {
3351                 domain = username;
3352                 username = NULL;
3353         }
3354
3355         /* If we can't find account or if the account is temporary, return. */
3356         if (!ast_strlen_zero(username) && !find_account(domain, username, FALSE)) {
3357                 ast_log(LOG_ERROR, "Minivm account does not exist: %s@%s\n", username, domain);
3358                 return 0;
3359         }
3360
3361         create_dirpath(userpath, sizeof(userpath), domain, username, NULL);
3362
3363         /* We have the path, now read the counter file */
3364         res = access_counter_file(userpath, countername, 0, 0);
3365         if (res >= 0)
3366                 snprintf(buf, len, "%d", res);
3367         return 0;
3368 }
3369
3370 /*! \brief  ${MINIVMCOUNTER()} Dialplan function - changes counter data */
3371 static int minivm_counter_func_write(struct ast_channel *chan, const char *cmd, char *data, const char *value)
3372 {
3373         char *username, *domain, *countername, *operand;
3374         char userpath[BUFSIZ];
3375         int change = 0;
3376         int operation = 0;
3377
3378         if(!value)
3379                 return -1;
3380         change = atoi(value);
3381
3382         username = ast_strdupa(data);
3383
3384         if ((countername = strchr(username, ':'))) {
3385                 *countername = '\0';
3386                 countername++;
3387         } 
3388         if ((operand = strchr(countername, ':'))) {
3389                 *operand = '\0';
3390                 operand++;
3391         } 
3392
3393         if ((domain = strchr(username, '@'))) {
3394                 *domain = '\0';
3395                 domain++;
3396         }
3397
3398         /* If we have neither username nor domain now, let's give up */
3399         if (ast_strlen_zero(username) && ast_strlen_zero(domain)) {
3400                 ast_log(LOG_ERROR, "No account given\n");
3401                 return -1;
3402         }
3403
3404         /* We only have a domain, no username */
3405         if (!ast_strlen_zero(username) && ast_strlen_zero(domain)) {
3406                 domain = username;
3407                 username = NULL;
3408         }
3409
3410         if (ast_strlen_zero(operand) || ast_strlen_zero(countername)) {
3411                 ast_log(LOG_ERROR, "Writing to this function requires three arguments: Account:countername:operand\n");
3412                 return -1;
3413         }
3414
3415         /* If we can't find account or if the account is temporary, return. */
3416         if (!ast_strlen_zero(username) && !find_account(domain, username, FALSE)) {
3417                 ast_log(LOG_ERROR, "Minivm account does not exist: %s@%s\n", username, domain);
3418                 return 0;
3419         }
3420
3421         create_dirpath(userpath, sizeof(userpath), domain, username, NULL);
3422         /* Now, find out our operator */
3423         if (*operand == 'i') /* Increment */
3424                 operation = 2;
3425         else if (*operand == 'd') {
3426                 change = change * -1;
3427                 operation = 2;
3428         } else if (*operand == 's')
3429                 operation = 1;
3430         else {
3431                 ast_log(LOG_ERROR, "Unknown operator: %s\n", operand);
3432                 return -1;
3433         }
3434
3435         /* We have the path, now read the counter file */
3436         access_counter_file(userpath, countername, change, operation);
3437         return 0;
3438 }
3439
3440
3441 /*! \brief CLI commands for Mini-voicemail */
3442 static struct ast_cli_entry cli_minivm[] = {
3443         AST_CLI_DEFINE(handle_minivm_show_users, "List defined mini-voicemail boxes"),
3444         AST_CLI_DEFINE(handle_minivm_show_zones, "List zone message formats"),
3445         AST_CLI_DEFINE(handle_minivm_list_templates, "List message templates"), 
3446         AST_CLI_DEFINE(handle_minivm_reload, "Reload Mini-voicemail configuration"),
3447         AST_CLI_DEFINE(handle_minivm_show_stats, "Show some mini-voicemail statistics"),
3448         AST_CLI_DEFINE(handle_minivm_show_settings, "Show mini-voicemail general settings"),
3449 };
3450
3451 static struct ast_custom_function minivm_counter_function = {
3452         .name = "MINIVMCOUNTER",
3453         .read = minivm_counter_func_read,
3454         .write = minivm_counter_func_write,
3455 };
3456
3457 static struct ast_custom_function minivm_account_function = {
3458         .name = "MINIVMACCOUNT",
3459         .read = minivm_account_func_read,
3460 };
3461
3462 /*! \brief Load mini voicemail module */
3463 static int load_module(void)
3464 {
3465         int res;
3466
3467         res = ast_register_application_xml(app_minivm_record, minivm_record_exec);
3468         res = ast_register_application_xml(app_minivm_greet, minivm_greet_exec);
3469         res = ast_register_application_xml(app_minivm_notify, minivm_notify_exec);
3470         res = ast_register_application_xml(app_minivm_delete, minivm_delete_exec);
3471         res = ast_register_application_xml(app_minivm_accmess, minivm_accmess_exec);
3472         res = ast_register_application_xml(app_minivm_mwi, minivm_mwi_exec);
3473
3474         ast_custom_function_register(&minivm_account_function);
3475         ast_custom_function_register(&minivm_counter_function);
3476         if (res)
3477                 return(res);
3478
3479         if ((res = load_config(0)))
3480                 return(res);
3481
3482         ast_cli_register_multiple(cli_minivm, ARRAY_LEN(cli_minivm));
3483
3484         /* compute the location of the voicemail spool directory */
3485         snprintf(MVM_SPOOL_DIR, sizeof(MVM_SPOOL_DIR), "%s/voicemail/", ast_config_AST_SPOOL_DIR);
3486
3487         return res;
3488 }
3489
3490 /*! \brief Reload mini voicemail module */
3491 static int reload(void)
3492 {
3493         return(load_config(1));
3494 }
3495
3496 /*! \brief Reload cofiguration */
3497 static char *handle_minivm_reload(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
3498 {
3499         
3500         switch (cmd) {
3501         case CLI_INIT:
3502                 e->command = "minivm reload";
3503                 e->usage =
3504                         "Usage: minivm reload\n"
3505                         "       Reload mini-voicemail configuration and reset statistics\n";
3506                 return NULL;
3507         case CLI_GENERATE:
3508                 return NULL;
3509         }
3510         
3511         reload();
3512         ast_cli(a->fd, "\n-- Mini voicemail re-configured \n");
3513         return CLI_SUCCESS;
3514 }
3515
3516 /*! \brief Unload mini voicemail module */
3517 static int unload_module(void)
3518 {
3519         int res;
3520         
3521         res = ast_unregister_application(app_minivm_record);
3522         res |= ast_unregister_application(app_minivm_greet);
3523         res |= ast_unregister_application(app_minivm_notify);
3524         res |= ast_unregister_application(app_minivm_delete);
3525         res |= ast_unregister_application(app_minivm_accmess);
3526         res |= ast_unregister_application(app_minivm_mwi);
3527
3528         ast_cli_unregister_multiple(cli_minivm, ARRAY_LEN(cli_minivm));
3529         ast_custom_function_unregister(&minivm_account_function);
3530         ast_custom_function_unregister(&minivm_counter_function);
3531
3532         message_destroy_list();         /* Destroy list of voicemail message templates */
3533         timezone_destroy_list();        /* Destroy list of timezones */
3534         vmaccounts_destroy_list();      /* Destroy list of voicemail accounts */
3535
3536         return res;
3537 }
3538
3539
3540 AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_DEFAULT, "Mini VoiceMail (A minimal Voicemail e-mail System)",
3541                 .load = load_module,
3542                 .unload = unload_module,
3543                 .reload = reload,
3544                 );