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