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