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