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