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