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