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