Move function MINIVMACCOUNT and MINIVMCOUNTER static documentation to XML.
[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 {
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 } minivm_option_flags;
559
560 enum {
561         OPT_ARG_RECORDGAIN = 0,
562         OPT_ARG_ARRAY_SIZE = 1,
563 } minivm_option_args;
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 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_channel_alloc(0, AST_STATE_DOWN, 0, 0, "", "", "", 0, "%s", "");
1303
1304
1305         snprintf(dur, sizeof(dur), "%d:%02d", duration / 60, duration % 60);
1306
1307         /* Does this user have a timezone specified? */
1308         if (!ast_strlen_zero(vmu->zonetag)) {
1309                 /* Find the zone in the list */
1310                 struct minivm_zone *z;
1311                 AST_LIST_LOCK(&minivm_zones);
1312                 AST_LIST_TRAVERSE(&minivm_zones, z, list) {
1313                         if (strcmp(z->name, vmu->zonetag)) 
1314                                 continue;
1315                         the_zone = z;
1316                 }
1317                 AST_LIST_UNLOCK(&minivm_zones);
1318         }
1319
1320         now = ast_tvnow();
1321         ast_localtime(&now, &tm, the_zone ? the_zone->timezone : NULL);
1322         ast_strftime(date, sizeof(date), "%a, %d %b %Y %H:%M:%S %z", &tm);
1323
1324         /* Start printing the email to the temporary file */
1325         fprintf(p, "Date: %s\n", date);
1326
1327         /* Set date format for voicemail mail */
1328         ast_strftime(date, sizeof(date), template->dateformat, &tm);
1329
1330
1331         /* Populate channel with channel variables for substitution */
1332         prep_email_sub_vars(ast, vmu, cidnum, cidname, dur, date, counter);
1333
1334         /* Find email address to use */
1335         /* If there's a server e-mail adress in the account, user that, othterwise template */
1336         fromemail = ast_strlen_zero(vmu->serveremail) ?  template->serveremail : vmu->serveremail;
1337
1338         /* Find name to user for server e-mail */
1339         fromaddress = ast_strlen_zero(template->fromaddress) ? "" : template->fromaddress;
1340
1341         /* If needed, add hostname as domain */
1342         if (ast_strlen_zero(fromemail))
1343                 fromemail = "asterisk";
1344
1345         if (strchr(fromemail, '@'))
1346                 ast_copy_string(who, fromemail, sizeof(who));
1347         else  {
1348                 char host[MAXHOSTNAMELEN];
1349                 gethostname(host, sizeof(host)-1);
1350                 snprintf(who, sizeof(who), "%s@%s", fromemail, host);
1351         }
1352
1353         if (ast_strlen_zero(fromaddress)) {
1354                 fprintf(p, "From: Asterisk PBX <%s>\n", who);
1355         } else {
1356                 ast_debug(4, "Fromaddress template: %s\n", fromaddress);
1357                 ast_str_substitute_variables(&str1, 0, ast, fromaddress);
1358                 if (check_mime(ast_str_buffer(str1))) {
1359                         int first_line = 1;
1360                         char *ptr;
1361                         ast_str_encode_mime(&str2, 0, template->charset, ast_str_buffer(str1), strlen("From: "), strlen(who) + 3);
1362                         while ((ptr = strchr(ast_str_buffer(str2), ' '))) {
1363                                 *ptr = '\0';
1364                                 fprintf(p, "%s %s\n", first_line ? "From:" : "", ast_str_buffer(str2));
1365                                 first_line = 0;
1366                                 /* Substring is smaller, so this will never grow */
1367                                 ast_str_set(&str2, 0, "%s", ptr + 1);
1368                         }
1369                         fprintf(p, "%s %s <%s>\n", first_line ? "From:" : "", ast_str_buffer(str2), who);
1370                 } else {
1371                         fprintf(p, "From: %s <%s>\n", ast_str_quote(&str2, 0, ast_str_buffer(str1)), who);
1372                 }
1373         } 
1374
1375         fprintf(p, "Message-ID: <Asterisk-%d-%s-%d-%s>\n", (unsigned int)ast_random(), vmu->username, (int)getpid(), who);
1376
1377         if (ast_strlen_zero(vmu->email)) {
1378                 snprintf(email, sizeof(email), "%s@%s", vmu->username, vmu->domain);
1379         } else {
1380                 ast_copy_string(email, vmu->email, sizeof(email));
1381         }
1382
1383         if (check_mime(vmu->fullname)) {
1384                 int first_line = 1;
1385                 char *ptr;
1386                 ast_str_encode_mime(&str2, 0, template->charset, vmu->fullname, strlen("To: "), strlen(email) + 3);
1387                 while ((ptr = strchr(ast_str_buffer(str2), ' '))) {
1388                         *ptr = '\0';
1389                         fprintf(p, "%s %s\n", first_line ? "To:" : "", ast_str_buffer(str2));
1390                         first_line = 0;
1391                         /* Substring is smaller, so this will never grow */
1392                         ast_str_set(&str2, 0, "%s", ptr + 1);
1393                 }
1394                 fprintf(p, "%s %s <%s>\n", first_line ? "To:" : "", ast_str_buffer(str2), email);
1395         } else {
1396                 fprintf(p, "To: %s <%s>\n", ast_str_quote(&str2, 0, vmu->fullname), email);
1397         }
1398
1399         if (!ast_strlen_zero(template->subject)) {
1400                 ast_str_substitute_variables(&str1, 0, ast, template->subject);
1401                 if (check_mime(ast_str_buffer(str1))) {
1402                         int first_line = 1;
1403                         char *ptr;
1404                         ast_str_encode_mime(&str2, 0, template->charset, ast_str_buffer(str1), strlen("Subject: "), 0);
1405                         while ((ptr = strchr(ast_str_buffer(str2), ' '))) {
1406                                 *ptr = '\0';
1407                                 fprintf(p, "%s %s\n", first_line ? "Subject:" : "", ast_str_buffer(str2));
1408                                 first_line = 0;
1409                                 /* Substring is smaller, so this will never grow */
1410                                 ast_str_set(&str2, 0, "%s", ptr + 1);
1411                         }
1412                         fprintf(p, "%s %s\n", first_line ? "Subject:" : "", ast_str_buffer(str2));
1413                 } else {
1414                         fprintf(p, "Subject: %s\n", ast_str_buffer(str1));
1415                 }
1416         } else {
1417                 fprintf(p, "Subject: New message in mailbox %s@%s\n", vmu->username, vmu->domain);
1418                 ast_debug(1, "Using default subject for this email \n");
1419         }
1420
1421         if (option_debug > 2)
1422                 fprintf(p, "X-Asterisk-debug: template %s user account %s@%s\n", template->name, vmu->username, vmu->domain);
1423         fprintf(p, "MIME-Version: 1.0\n");
1424
1425         /* Something unique. */
1426         snprintf(bound, sizeof(bound), "voicemail_%s%d%d", vmu->username, (int)getpid(), (unsigned int)ast_random());
1427
1428         fprintf(p, "Content-Type: multipart/mixed; boundary=\"%s\"\n\n\n", bound);
1429
1430         fprintf(p, "--%s\n", bound);
1431         fprintf(p, "Content-Type: text/plain; charset=%s\nContent-Transfer-Encoding: 8bit\n\n", template->charset);
1432         if (!ast_strlen_zero(template->body)) {
1433                 ast_str_substitute_variables(&str1, 0, ast, template->body);
1434                 ast_debug(3, "Message now: %s\n-----\n", ast_str_buffer(str1));
1435                 fprintf(p, "%s\n", ast_str_buffer(str1));
1436         } else {
1437                 fprintf(p, "Dear %s:\n\n\tJust wanted to let you know you were just left a %s long message \n"
1438                         "in mailbox %s from %s, on %s so you might\n"
1439                         "want to check it when you get a chance.  Thanks!\n\n\t\t\t\t--Asterisk\n\n", vmu->fullname, 
1440                         dur,  vmu->username, (cidname ? cidname : (cidnum ? cidnum : "an unknown caller")), date);
1441                 ast_debug(3, "Using default message body (no template)\n-----\n");
1442         }
1443         /* Eww. We want formats to tell us their own MIME type */
1444         if (template->attachment) {
1445                 char *ctype = "audio/x-";
1446                 ast_debug(3, "Attaching file to message: %s\n", fname);
1447                 if (!strcasecmp(format, "ogg"))
1448                         ctype = "application/";
1449
1450                 fprintf(p, "--%s\n", bound);
1451                 fprintf(p, "Content-Type: %s%s; name=\"voicemailmsg.%s\"\n", ctype, format, format);
1452                 fprintf(p, "Content-Transfer-Encoding: base64\n");
1453                 fprintf(p, "Content-Description: Voicemail sound attachment.\n");
1454                 fprintf(p, "Content-Disposition: attachment; filename=\"voicemail%s.%s\"\n\n", counter ? counter : "", format);
1455
1456                 base_encode(fname, p);
1457                 fprintf(p, "\n\n--%s--\n.\n", bound);
1458         }
1459         fclose(p);
1460         snprintf(tmp2, sizeof(tmp2), "( %s < %s ; rm -f %s ) &", global_mailcmd, tmp, tmp);
1461         ast_safe_system(tmp2);
1462         ast_debug(1, "Sent message to %s with command '%s' - %s\n", vmu->email, global_mailcmd, template->attachment ? "(media attachment)" : "");
1463         ast_debug(3, "Actual command used: %s\n", tmp2);
1464         if (ast) {
1465                 ast = ast_channel_release(ast);
1466         }
1467         ast_free(str1);
1468         ast_free(str2);
1469         return 0;
1470 }
1471
1472 /*!\internal
1473  * \brief Create directory based on components */
1474 static int make_dir(char *dest, int len, const char *domain, const char *username, const char *folder)
1475 {
1476         return snprintf(dest, len, "%s%s/%s%s%s", MVM_SPOOL_DIR, domain, username, ast_strlen_zero(folder) ? "" : "/", folder ? folder : "");
1477 }
1478
1479 /*!\internal
1480  * \brief Checks if directory exists. Does not create directory, but builds string in dest
1481  * \param dest    String. base directory.
1482  * \param len    Int. Length base directory string.
1483  * \param domain String. Ignored if is null or empty string.
1484  * \param username String. Ignored if is null or empty string. 
1485  * \param folder  String. Ignored if is null or empty string.
1486  * \return 0 on failure, 1 on success.
1487  */
1488 static int check_dirpath(char *dest, int len, char *domain, char *username, char *folder)
1489 {
1490         struct stat filestat;
1491         make_dir(dest, len, domain, username, folder ? folder : "");
1492         if (stat(dest, &filestat)== -1)
1493                 return FALSE;
1494         else
1495                 return TRUE;
1496 }
1497
1498 /*!\internal
1499  * \brief basically mkdir -p $dest/$domain/$username/$folder
1500  * \param dest    String. base directory.
1501  * \param len     Length of directory string
1502  * \param domain  String. Ignored if is null or empty string.
1503  * \param folder  String. Ignored if is null or empty string.
1504  * \param username  String. Ignored if is null or empty string.
1505  * \return -1 on failure, 0 on success.
1506  */
1507 static int create_dirpath(char *dest, int len, char *domain, char *username, char *folder)
1508 {
1509         int res;
1510         make_dir(dest, len, domain, username, folder);
1511         if ((res = ast_mkdir(dest, 0777))) {
1512                 ast_log(LOG_WARNING, "ast_mkdir '%s' failed: %s\n", dest, strerror(res));
1513                 return -1;
1514         }
1515         ast_debug(2, "Creating directory for %s@%s folder %s : %s\n", username, domain, folder, dest);
1516         return 0;
1517 }
1518
1519
1520 /*!\internal
1521  * \brief Play intro message before recording voicemail
1522  */
1523 static int invent_message(struct ast_channel *chan, char *domain, char *username, int busy, char *ecodes)
1524 {
1525         int res;
1526         char fn[PATH_MAX];
1527
1528         ast_debug(2, "Still preparing to play message ...\n");
1529
1530         snprintf(fn, sizeof(fn), "%s%s/%s/greet", MVM_SPOOL_DIR, domain, username);
1531
1532         if (ast_fileexists(fn, NULL, NULL) > 0) {
1533                 res = ast_streamfile(chan, fn, chan->language);
1534                 if (res) 
1535                         return -1;
1536                 res = ast_waitstream(chan, ecodes);
1537                 if (res) 
1538                         return res;
1539         } else {
1540                 int numericusername = 1;
1541                 char *i = username;
1542
1543                 ast_debug(2, "No personal prompts. Using default prompt set for language\n");
1544
1545                 while (*i)  {
1546                         ast_debug(2, "Numeric? Checking %c\n", *i);
1547                         if (!isdigit(*i)) {
1548                                 numericusername = FALSE;
1549                                 break;
1550                         }
1551                         i++;
1552                 }
1553
1554                 if (numericusername) {
1555                         if (ast_streamfile(chan, "vm-theperson", chan->language))
1556                                 return -1;
1557                         if ((res = ast_waitstream(chan, ecodes)))
1558                                 return res;
1559
1560                         res = ast_say_digit_str(chan, username, ecodes, chan->language);
1561                         if (res)
1562                                 return res;
1563                 } else {
1564                         if (ast_streamfile(chan, "vm-theextensionis", chan->language))
1565                                 return -1;
1566                         if ((res = ast_waitstream(chan, ecodes)))
1567                                 return res;
1568                 }
1569         }
1570
1571         res = ast_streamfile(chan, busy ? "vm-isonphone" : "vm-isunavail", chan->language);
1572         if (res)
1573                 return -1;
1574         res = ast_waitstream(chan, ecodes);
1575         return res;
1576 }
1577
1578 /*!\internal
1579  * \brief Delete media files and attribute file */
1580 static int vm_delete(char *file)
1581 {
1582         int res;
1583
1584         ast_debug(1, "Deleting voicemail file %s\n", file);
1585
1586         res = unlink(file);     /* Remove the meta data file */
1587         res |=  ast_filedelete(file, NULL);     /* remove the media file */
1588         return res;
1589 }
1590
1591
1592 /*!\internal
1593  * \brief Record voicemail message & let caller review or re-record it, or set options if applicable */
1594 static int play_record_review(struct ast_channel *chan, char *playfile, char *recordfile, int maxtime, char *fmt,
1595                               int outsidecaller, struct minivm_account *vmu, int *duration, const char *unlockdir,
1596                               signed char record_gain)
1597 {
1598         int cmd = 0;
1599         int max_attempts = 3;
1600         int attempts = 0;
1601         int recorded = 0;
1602         int message_exists = 0;
1603         signed char zero_gain = 0;
1604         char *acceptdtmf = "#";
1605         char *canceldtmf = "";
1606
1607         /* Note that urgent and private are for flagging messages as such in the future */
1608
1609         /* barf if no pointer passed to store duration in */
1610         if (duration == NULL) {
1611                 ast_log(LOG_WARNING, "Error play_record_review called without duration pointer\n");
1612                 return -1;
1613         }
1614
1615         cmd = '3';       /* Want to start by recording */
1616
1617         while ((cmd >= 0) && (cmd != 't')) {
1618                 switch (cmd) {
1619                 case '1':
1620                         ast_verb(3, "Saving message as is\n");
1621                         ast_stream_and_wait(chan, "vm-msgsaved", "");
1622                         cmd = 't';
1623                         break;
1624                 case '2':
1625                         /* Review */
1626                         ast_verb(3, "Reviewing the message\n");
1627                         ast_streamfile(chan, recordfile, chan->language);
1628                         cmd = ast_waitstream(chan, AST_DIGIT_ANY);
1629                         break;
1630                 case '3':
1631                         message_exists = 0;
1632                         /* Record */
1633                         if (recorded == 1) 
1634                                 ast_verb(3, "Re-recording the message\n");
1635                         else
1636                                 ast_verb(3, "Recording the message\n");
1637                         if (recorded && outsidecaller) 
1638                                 cmd = ast_play_and_wait(chan, "beep");
1639                         recorded = 1;
1640                         /* 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 */
1641                         if (record_gain)
1642                                 ast_channel_setoption(chan, AST_OPTION_RXGAIN, &record_gain, sizeof(record_gain), 0);
1643                         if (ast_test_flag(vmu, MVM_OPERATOR))
1644                                 canceldtmf = "0";
1645                         cmd = ast_play_and_record_full(chan, playfile, recordfile, maxtime, fmt, duration, global_silencethreshold, global_maxsilence, unlockdir, acceptdtmf, canceldtmf);
1646                         if (record_gain)
1647                                 ast_channel_setoption(chan, AST_OPTION_RXGAIN, &zero_gain, sizeof(zero_gain), 0);
1648                         if (cmd == -1) /* User has hung up, no options to give */
1649                                 return cmd;
1650                         if (cmd == '0')
1651                                 break;
1652                         else if (cmd == '*')
1653                                 break;
1654                         else {
1655                                 /* If all is well, a message exists */
1656                                 message_exists = 1;
1657                                 cmd = 0;
1658                         }
1659                         break;
1660                 case '4':
1661                 case '5':
1662                 case '6':
1663                 case '7':
1664                 case '8':
1665                 case '9':
1666                 case '*':
1667                 case '#':
1668                         cmd = ast_play_and_wait(chan, "vm-sorry");
1669                         break;
1670                 case '0':
1671                         if(!ast_test_flag(vmu, MVM_OPERATOR)) {
1672                                 cmd = ast_play_and_wait(chan, "vm-sorry");
1673                                 break;
1674                         }
1675                         if (message_exists || recorded) {
1676                                 cmd = ast_play_and_wait(chan, "vm-saveoper");
1677                                 if (!cmd)
1678                                         cmd = ast_waitfordigit(chan, 3000);
1679                                 if (cmd == '1') {
1680                                         ast_play_and_wait(chan, "vm-msgsaved");
1681                                         cmd = '0';
1682                                 } else {
1683                                         ast_play_and_wait(chan, "vm-deleted");
1684                                         vm_delete(recordfile);
1685                                         cmd = '0';
1686                                 }
1687                         }
1688                         return cmd;
1689                 default:
1690                         /* If the caller is an ouside caller, and the review option is enabled,
1691                            allow them to review the message, but let the owner of the box review
1692                            their OGM's */
1693                         if (outsidecaller && !ast_test_flag(vmu, MVM_REVIEW))
1694                                 return cmd;
1695                         if (message_exists) {
1696                                 cmd = ast_play_and_wait(chan, "vm-review");
1697                         } else {
1698                                 cmd = ast_play_and_wait(chan, "vm-torerecord");
1699                                 if (!cmd)
1700                                         cmd = ast_waitfordigit(chan, 600);
1701                         }
1702
1703                         if (!cmd && outsidecaller && ast_test_flag(vmu, MVM_OPERATOR)) {
1704                                 cmd = ast_play_and_wait(chan, "vm-reachoper");
1705                                 if (!cmd)
1706                                         cmd = ast_waitfordigit(chan, 600);
1707                         }
1708                         if (!cmd)
1709                                 cmd = ast_waitfordigit(chan, 6000);
1710                         if (!cmd) {
1711                                 attempts++;
1712                         }
1713                         if (attempts > max_attempts) {
1714                                 cmd = 't';
1715                         }
1716                 }
1717         }
1718         if (outsidecaller)
1719                 ast_play_and_wait(chan, "vm-goodbye");
1720         if (cmd == 't')
1721                 cmd = 0;
1722         return cmd;
1723 }
1724
1725 /*! \brief Run external notification for voicemail message */
1726 static void run_externnotify(struct ast_channel *chan, struct minivm_account *vmu)
1727 {
1728         char arguments[BUFSIZ];
1729
1730         if (ast_strlen_zero(vmu->externnotify) && ast_strlen_zero(global_externnotify))
1731                 return;
1732
1733         snprintf(arguments, sizeof(arguments), "%s %s@%s %s %s&", 
1734                 ast_strlen_zero(vmu->externnotify) ? global_externnotify : vmu->externnotify, 
1735                 vmu->username, vmu->domain,
1736                 chan->cid.cid_name, chan->cid.cid_num);
1737
1738         ast_debug(1, "Executing: %s\n", arguments);
1739         ast_safe_system(arguments);
1740 }
1741
1742 /*!\internal
1743  * \brief Send message to voicemail account owner */
1744 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)
1745 {
1746         char *stringp;
1747         struct minivm_template *etemplate;
1748         char *messageformat;
1749         int res = 0;
1750         char oldlocale[100];
1751         const char *counter;
1752
1753         if (!ast_strlen_zero(vmu->attachfmt)) {
1754                 if (strstr(format, vmu->attachfmt)) {
1755                         format = vmu->attachfmt;
1756                 } else {
1757                         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);
1758                 }
1759         }
1760
1761         etemplate = message_template_find(vmu->etemplate);
1762         if (!etemplate)
1763                 etemplate = message_template_find(templatename);
1764         if (!etemplate)
1765                 etemplate = message_template_find("email-default");
1766
1767         /* Attach only the first format */
1768         stringp = messageformat = ast_strdupa(format);
1769         strsep(&stringp, "|");
1770
1771         if (!ast_strlen_zero(etemplate->locale)) {
1772                 char *new_locale;
1773                 ast_copy_string(oldlocale, setlocale(LC_TIME, NULL), sizeof(oldlocale));
1774                 ast_debug(2, "Changing locale from %s to %s\n", oldlocale, etemplate->locale);
1775                 new_locale = setlocale(LC_TIME, etemplate->locale);
1776                 if (new_locale == NULL) {
1777                         ast_log(LOG_WARNING, "-_-_- Changing to new locale did not work. Locale: %s\n", etemplate->locale);
1778                 }
1779         }
1780
1781
1782
1783         /* Read counter if available */
1784         ast_channel_lock(chan);
1785         if ((counter = pbx_builtin_getvar_helper(chan, "MVM_COUNTER"))) {
1786                 counter = ast_strdupa(counter);
1787         }
1788         ast_channel_unlock(chan);
1789
1790         if (ast_strlen_zero(counter)) {
1791                 ast_debug(2, "MVM_COUNTER not found\n");
1792         } else {
1793                 ast_debug(2, "MVM_COUNTER found - will use it with value %s\n", counter);
1794         }
1795
1796         res = sendmail(etemplate, vmu, cidnum, cidname, filename, messageformat, duration, etemplate->attachment, MVM_MESSAGE_EMAIL, counter);
1797
1798         if (res == 0 && !ast_strlen_zero(vmu->pager))  {
1799                 /* Find template for paging */
1800                 etemplate = message_template_find(vmu->ptemplate);
1801                 if (!etemplate)
1802                         etemplate = message_template_find("pager-default");
1803                 if (etemplate->locale) {
1804                         ast_copy_string(oldlocale, setlocale(LC_TIME, ""), sizeof(oldlocale));
1805                         setlocale(LC_TIME, etemplate->locale);
1806                 }
1807
1808                 res = sendmail(etemplate, vmu, cidnum, cidname, filename, messageformat, duration, etemplate->attachment, MVM_MESSAGE_PAGE, counter);
1809         }
1810
1811         manager_event(EVENT_FLAG_CALL, "MiniVoiceMail", "Action: SentNotification\rn\nMailbox: %s@%s\r\nCounter: %s\r\n", vmu->username, vmu->domain, counter);
1812
1813         run_externnotify(chan, vmu);            /* Run external notification */
1814
1815         if (etemplate->locale) {
1816                 setlocale(LC_TIME, oldlocale); /* Rest to old locale */
1817         }
1818         return res;
1819 }
1820
1821  
1822 /*!\internal
1823  * \brief Record voicemail message, store into file prepared for sending e-mail */
1824 static int leave_voicemail(struct ast_channel *chan, char *username, struct leave_vm_options *options)
1825 {
1826         char tmptxtfile[PATH_MAX];
1827         char callerid[256];
1828         FILE *txt;
1829         int res = 0, txtdes;
1830         int msgnum;
1831         int duration = 0;
1832         char date[256];
1833         char tmpdir[PATH_MAX];
1834         char ext_context[256] = "";
1835         char fmt[80];
1836         char *domain;
1837         char tmp[256] = "";
1838         struct minivm_account *vmu;
1839         int userdir;
1840
1841         ast_copy_string(tmp, username, sizeof(tmp));
1842         username = tmp;
1843         domain = strchr(tmp, '@');
1844         if (domain) {
1845                 *domain = '\0';
1846                 domain++;
1847         }
1848
1849         if (!(vmu = find_account(domain, username, TRUE))) {
1850                 /* We could not find user, let's exit */
1851                 ast_log(LOG_ERROR, "Can't allocate temporary account for '%s@%s'\n", username, domain);
1852                 pbx_builtin_setvar_helper(chan, "MVM_RECORD_STATUS", "FAILED");
1853                 return 0;
1854         }
1855
1856         /* Setup pre-file if appropriate */
1857         if (strcmp(vmu->domain, "localhost"))
1858                 snprintf(ext_context, sizeof(ext_context), "%s@%s", username, vmu->domain);
1859         else
1860                 ast_copy_string(ext_context, vmu->domain, sizeof(ext_context));
1861
1862         /* The meat of recording the message...  All the announcements and beeps have been played*/
1863         if (ast_strlen_zero(vmu->attachfmt))
1864                 ast_copy_string(fmt, default_vmformat, sizeof(fmt));
1865         else
1866                 ast_copy_string(fmt, vmu->attachfmt, sizeof(fmt));
1867
1868         if (ast_strlen_zero(fmt)) {
1869                 ast_log(LOG_WARNING, "No format for saving voicemail? Default %s\n", default_vmformat);
1870                 pbx_builtin_setvar_helper(chan, "MVM_RECORD_STATUS", "FAILED");
1871                 return res;
1872         }
1873         msgnum = 0;
1874
1875         userdir = check_dirpath(tmpdir, sizeof(tmpdir), vmu->domain, username, "tmp");
1876
1877         /* If we have no user directory, use generic temporary directory */
1878         if (!userdir) {
1879                 create_dirpath(tmpdir, sizeof(tmpdir), "0000_minivm_temp", "mediafiles", "");
1880                 ast_debug(3, "Creating temporary directory %s\n", tmpdir);
1881         }
1882
1883
1884         snprintf(tmptxtfile, sizeof(tmptxtfile), "%s/XXXXXX", tmpdir);
1885
1886         /* XXX This file needs to be in temp directory */
1887         txtdes = mkstemp(tmptxtfile);
1888         if (txtdes < 0) {
1889                 ast_log(LOG_ERROR, "Unable to create message file %s: %s\n", tmptxtfile, strerror(errno));
1890                 res = ast_streamfile(chan, "vm-mailboxfull", chan->language);
1891                 if (!res)
1892                         res = ast_waitstream(chan, "");
1893                 pbx_builtin_setvar_helper(chan, "MVM_RECORD_STATUS", "FAILED");
1894                 return res;
1895         }
1896
1897         if (res >= 0) {
1898                 /* Unless we're *really* silent, try to send the beep */
1899                 res = ast_streamfile(chan, "beep", chan->language);
1900                 if (!res)
1901                         res = ast_waitstream(chan, "");
1902         }
1903
1904         /* OEJ XXX Maybe this can be turned into a log file? Hmm. */
1905         /* Store information */
1906         ast_debug(2, "Open file for metadata: %s\n", tmptxtfile);
1907
1908         res = play_record_review(chan, NULL, tmptxtfile, global_vmmaxmessage, fmt, 1, vmu, &duration, NULL, options->record_gain);
1909
1910         txt = fdopen(txtdes, "w+");
1911         if (!txt) {
1912                 ast_log(LOG_WARNING, "Error opening text file for output\n");
1913         } else {
1914                 struct ast_tm tm;
1915                 struct timeval now = ast_tvnow();
1916                 char timebuf[30];
1917                 char logbuf[BUFSIZ];
1918                 get_date(date, sizeof(date));
1919                 ast_localtime(&now, &tm, NULL);
1920                 ast_strftime(timebuf, sizeof(timebuf), "%H:%M:%S", &tm);
1921
1922                 snprintf(logbuf, sizeof(logbuf),
1923                         /* "Mailbox:domain:macrocontext:exten:priority:callerchan:callerid:origdate:origtime:duration:durationstatus:accountcode" */
1924                         "%s:%s:%s:%s:%d:%s:%s:%s:%s:%d:%s:%s\n",
1925                         username,
1926                         chan->context,
1927                         chan->macrocontext, 
1928                         chan->exten,
1929                         chan->priority,
1930                         chan->name,
1931                         ast_callerid_merge(callerid, sizeof(callerid), chan->cid.cid_name, chan->cid.cid_num, "Unknown"),
1932                         date, 
1933                         timebuf,
1934                         duration,
1935                         duration < global_vmminmessage ? "IGNORED" : "OK",
1936                         vmu->accountcode
1937                 ); 
1938                 fprintf(txt, "%s", logbuf);
1939                 if (minivmlogfile) {
1940                         ast_mutex_lock(&minivmloglock);
1941                         fprintf(minivmlogfile, "%s", logbuf);
1942                         ast_mutex_unlock(&minivmloglock);
1943                 }
1944
1945                 if (duration < global_vmminmessage) {
1946                         ast_verb(3, "Recording was %d seconds long but needs to be at least %d - abandoning\n", duration, global_vmminmessage);
1947                         fclose(txt);
1948                         ast_filedelete(tmptxtfile, NULL);
1949                         unlink(tmptxtfile);
1950                         pbx_builtin_setvar_helper(chan, "MVM_RECORD_STATUS", "FAILED");
1951                         return 0;
1952                 } 
1953                 fclose(txt); /* Close log file */
1954                 if (ast_fileexists(tmptxtfile, NULL, NULL) <= 0) {
1955                         ast_debug(1, "The recorded media file is gone, so we should remove the .txt file too!\n");
1956                         unlink(tmptxtfile);
1957                         pbx_builtin_setvar_helper(chan, "MVM_RECORD_STATUS", "FAILED");
1958                         if(ast_test_flag(vmu, MVM_ALLOCED))
1959                                 free_user(vmu);
1960                         return 0;
1961                 }
1962
1963                 /* Set channel variables for the notify application */
1964                 pbx_builtin_setvar_helper(chan, "MVM_FILENAME", tmptxtfile);
1965                 snprintf(timebuf, sizeof(timebuf), "%d", duration);
1966                 pbx_builtin_setvar_helper(chan, "MVM_DURATION", timebuf);
1967                 pbx_builtin_setvar_helper(chan, "MVM_FORMAT", fmt);
1968
1969         }
1970         global_stats.lastreceived = ast_tvnow();
1971         global_stats.receivedmessages++;
1972 #if 0
1973         /* Go ahead and delete audio files from system, they're not needed any more */
1974         if (ast_fileexists(tmptxtfile, NULL, NULL) <= 0) {
1975                 ast_filedelete(tmptxtfile, NULL);
1976                  /* Even not being used at the moment, it's better to convert ast_log to ast_debug anyway */
1977                 ast_debug(2, "-_-_- Deleted audio file after notification :: %s \n", tmptxtfile);
1978         }
1979 #endif
1980
1981         if (res > 0)
1982                 res = 0;
1983
1984         if(ast_test_flag(vmu, MVM_ALLOCED))
1985                 free_user(vmu);
1986
1987         pbx_builtin_setvar_helper(chan, "MVM_RECORD_STATUS", "SUCCESS");
1988         return res;
1989 }
1990
1991 /*!\internal
1992  * \brief Queue a message waiting event */
1993 static void queue_mwi_event(const char *mbx, const char *ctx, int urgent, int new, int old)
1994 {
1995         struct ast_event *event;
1996         char *mailbox, *context;
1997
1998         mailbox = ast_strdupa(mbx);
1999         context = ast_strdupa(ctx);
2000         if (ast_strlen_zero(context)) {
2001                 context = "default";
2002         }
2003
2004         if (!(event = ast_event_new(AST_EVENT_MWI,
2005                         AST_EVENT_IE_MAILBOX, AST_EVENT_IE_PLTYPE_STR, mailbox,
2006                         AST_EVENT_IE_CONTEXT, AST_EVENT_IE_PLTYPE_STR, context,
2007                         AST_EVENT_IE_NEWMSGS, AST_EVENT_IE_PLTYPE_UINT, (new+urgent),
2008                         AST_EVENT_IE_OLDMSGS, AST_EVENT_IE_PLTYPE_UINT, old,
2009                         AST_EVENT_IE_END))) {
2010                 return;
2011         }
2012
2013         ast_event_queue_and_cache(event);
2014 }
2015
2016 /*!\internal
2017  * \brief Send MWI using interal Asterisk event subsystem */
2018 static int minivm_mwi_exec(struct ast_channel *chan, const char *data)
2019 {
2020         int argc;
2021         char *argv[4];
2022         int res = 0;
2023         char *tmpptr;
2024         char tmp[PATH_MAX];
2025         char *mailbox;
2026         char *domain;
2027         if (ast_strlen_zero(data))  {
2028                 ast_log(LOG_ERROR, "Minivm needs at least an account argument \n");
2029                 return -1;
2030         }
2031         tmpptr = ast_strdupa((char *)data);
2032         if (!tmpptr) {
2033                 ast_log(LOG_ERROR, "Out of memory\n");
2034                 return -1;
2035         }
2036         argc = ast_app_separate_args(tmpptr, ',', argv, ARRAY_LEN(argv));
2037         if (argc < 4) {
2038                 ast_log(LOG_ERROR, "%d arguments passed to MiniVM_MWI, need 4.\n", argc);
2039                 return -1;
2040         }
2041         ast_copy_string(tmp, argv[0], sizeof(tmp));
2042         mailbox = tmp;
2043         domain = strchr(tmp, '@');
2044         if (domain) {
2045                 *domain = '\0';
2046                 domain++;
2047         }
2048         if (ast_strlen_zero(domain) || ast_strlen_zero(mailbox)) {
2049                 ast_log(LOG_ERROR, "Need mailbox@context as argument. Sorry. Argument 0 %s\n", argv[0]);
2050                 return -1;
2051         }
2052         queue_mwi_event(mailbox, domain, atoi(argv[1]), atoi(argv[2]), atoi(argv[3]));
2053
2054         return res;
2055 }
2056
2057
2058 /*!\internal
2059  * \brief Notify voicemail account owners - either generic template or user specific */
2060 static int minivm_notify_exec(struct ast_channel *chan, const char *data)
2061 {
2062         int argc;
2063         char *argv[2];
2064         int res = 0;
2065         char tmp[PATH_MAX];
2066         char *domain;
2067         char *tmpptr;
2068         struct minivm_account *vmu;
2069         char *username = argv[0];
2070         const char *template = "";
2071         const char *filename;
2072         const char *format;
2073         const char *duration_string;
2074
2075         if (ast_strlen_zero(data))  {
2076                 ast_log(LOG_ERROR, "Minivm needs at least an account argument \n");
2077                 return -1;
2078         }
2079         tmpptr = ast_strdupa((char *)data);
2080         if (!tmpptr) {
2081                 ast_log(LOG_ERROR, "Out of memory\n");
2082                 return -1;
2083         }
2084         argc = ast_app_separate_args(tmpptr, ',', argv, ARRAY_LEN(argv));
2085
2086         if (argc == 2 && !ast_strlen_zero(argv[1]))
2087                 template = argv[1];
2088
2089         ast_copy_string(tmp, argv[0], sizeof(tmp));
2090         username = tmp;
2091         domain = strchr(tmp, '@');
2092         if (domain) {
2093                 *domain = '\0';
2094                 domain++;
2095         } 
2096         if (ast_strlen_zero(domain) || ast_strlen_zero(username)) {
2097                 ast_log(LOG_ERROR, "Need username@domain as argument. Sorry. Argument 0 %s\n", argv[0]);
2098                 return -1;
2099         }
2100
2101         if(!(vmu = find_account(domain, username, TRUE))) {
2102                 /* We could not find user, let's exit */
2103                 ast_log(LOG_WARNING, "Could not allocate temporary memory for '%s@%s'\n", username, domain);
2104                 pbx_builtin_setvar_helper(chan, "MVM_NOTIFY_STATUS", "FAILED");
2105                 return -1;
2106         }
2107
2108         ast_channel_lock(chan);
2109         if ((filename = pbx_builtin_getvar_helper(chan, "MVM_FILENAME"))) {
2110                 filename = ast_strdupa(filename);
2111         }
2112         ast_channel_unlock(chan);
2113         /* Notify of new message to e-mail and pager */
2114         if (!ast_strlen_zero(filename)) {
2115                 ast_channel_lock(chan); 
2116                 if ((format = pbx_builtin_getvar_helper(chan, "MVM_FORMAT"))) {
2117                         format = ast_strdupa(format);
2118                 }
2119                 if ((duration_string = pbx_builtin_getvar_helper(chan, "MVM_DURATION"))) {
2120                         duration_string = ast_strdupa(duration_string);
2121                 }
2122                 ast_channel_unlock(chan);
2123                 res = notify_new_message(chan, template, vmu, filename, atoi(duration_string), format, chan->cid.cid_num, chan->cid.cid_name);
2124         }
2125
2126         pbx_builtin_setvar_helper(chan, "MVM_NOTIFY_STATUS", res == 0 ? "SUCCESS" : "FAILED");
2127
2128
2129         if(ast_test_flag(vmu, MVM_ALLOCED))
2130                 free_user(vmu);
2131
2132         /* Ok, we're ready to rock and roll. Return to dialplan */
2133
2134         return res;
2135
2136 }
2137
2138 /*!\internal
2139  * \brief Dialplan function to record voicemail */
2140 static int minivm_record_exec(struct ast_channel *chan, const char *data)
2141 {
2142         int res = 0;
2143         char *tmp;
2144         struct leave_vm_options leave_options;
2145         int argc;
2146         char *argv[2];
2147         struct ast_flags flags = { 0 };
2148         char *opts[OPT_ARG_ARRAY_SIZE];
2149
2150         memset(&leave_options, 0, sizeof(leave_options));
2151
2152         /* Answer channel if it's not already answered */
2153         if (chan->_state != AST_STATE_UP)
2154                 ast_answer(chan);
2155
2156         if (ast_strlen_zero(data))  {
2157                 ast_log(LOG_ERROR, "Minivm needs at least an account argument \n");
2158                 return -1;
2159         }
2160         tmp = ast_strdupa((char *)data);
2161         if (!tmp) {
2162                 ast_log(LOG_ERROR, "Out of memory\n");
2163                 return -1;
2164         }
2165         argc = ast_app_separate_args(tmp, ',', argv, ARRAY_LEN(argv));
2166         if (argc == 2) {
2167                 if (ast_app_parse_options(minivm_app_options, &flags, opts, argv[1])) {
2168                         return -1;
2169                 }
2170                 ast_copy_flags(&leave_options, &flags, OPT_SILENT | OPT_BUSY_GREETING | OPT_UNAVAIL_GREETING );
2171                 if (ast_test_flag(&flags, OPT_RECORDGAIN)) {
2172                         int gain;
2173
2174                         if (sscanf(opts[OPT_ARG_RECORDGAIN], "%d", &gain) != 1) {
2175                                 ast_log(LOG_WARNING, "Invalid value '%s' provided for record gain option\n", opts[OPT_ARG_RECORDGAIN]);
2176                                 return -1;
2177                         } else 
2178                                 leave_options.record_gain = (signed char) gain;
2179                 }
2180         } 
2181
2182         /* Now run the appliation and good luck to you! */
2183         res = leave_voicemail(chan, argv[0], &leave_options);
2184
2185         if (res == ERROR_LOCK_PATH) {
2186                 ast_log(LOG_ERROR, "Could not leave voicemail. The path is already locked.\n");
2187                 pbx_builtin_setvar_helper(chan, "MVM_RECORD_STATUS", "FAILED");
2188                 res = 0;
2189         }
2190         pbx_builtin_setvar_helper(chan, "MVM_RECORD_STATUS", "SUCCESS");
2191
2192         return res;
2193 }
2194
2195 /*!\internal
2196  * \brief Play voicemail prompts - either generic or user specific */
2197 static int minivm_greet_exec(struct ast_channel *chan, const char *data)
2198 {
2199         struct leave_vm_options leave_options = { 0, '\0'};
2200         int argc;
2201         char *argv[2];
2202         struct ast_flags flags = { 0 };
2203         char *opts[OPT_ARG_ARRAY_SIZE];
2204         int res = 0;
2205         int ausemacro = 0;
2206         int ousemacro = 0;
2207         int ouseexten = 0;
2208         char tmp[PATH_MAX];
2209         char dest[PATH_MAX];
2210         char prefile[PATH_MAX] = "";
2211         char tempfile[PATH_MAX] = "";
2212         char ext_context[256] = "";
2213         char *domain;
2214         char ecodes[16] = "#";
2215         char *tmpptr;
2216         struct minivm_account *vmu;
2217         char *username = argv[0];
2218
2219         if (ast_strlen_zero(data))  {
2220                 ast_log(LOG_ERROR, "Minivm needs at least an account argument \n");
2221                 return -1;
2222         }
2223         tmpptr = ast_strdupa((char *)data);
2224         if (!tmpptr) {
2225                 ast_log(LOG_ERROR, "Out of memory\n");
2226                 return -1;
2227         }
2228         argc = ast_app_separate_args(tmpptr, ',', argv, ARRAY_LEN(argv));
2229
2230         if (argc == 2) {
2231                 if (ast_app_parse_options(minivm_app_options, &flags, opts, argv[1]))
2232                         return -1;
2233                 ast_copy_flags(&leave_options, &flags, OPT_SILENT | OPT_BUSY_GREETING | OPT_UNAVAIL_GREETING );
2234         }
2235
2236         ast_copy_string(tmp, argv[0], sizeof(tmp));
2237         username = tmp;
2238         domain = strchr(tmp, '@');
2239         if (domain) {
2240                 *domain = '\0';
2241                 domain++;
2242         } 
2243         if (ast_strlen_zero(domain) || ast_strlen_zero(username)) {
2244                 ast_log(LOG_ERROR, "Need username@domain as argument. Sorry. Argument:  %s\n", argv[0]);
2245                 return -1;
2246         }
2247         ast_debug(1, "Trying to find configuration for user %s in domain %s\n", username, domain);
2248
2249         if (!(vmu = find_account(domain, username, TRUE))) {
2250                 ast_log(LOG_ERROR, "Could not allocate memory. \n");
2251                 return -1;
2252         }
2253
2254         /* Answer channel if it's not already answered */
2255         if (chan->_state != AST_STATE_UP)
2256                 ast_answer(chan);
2257
2258         /* Setup pre-file if appropriate */
2259         if (strcmp(vmu->domain, "localhost"))
2260                 snprintf(ext_context, sizeof(ext_context), "%s@%s", username, vmu->domain);
2261         else
2262                 ast_copy_string(ext_context, vmu->domain, sizeof(ext_context));
2263
2264         if (ast_test_flag(&leave_options, OPT_BUSY_GREETING)) {
2265                 res = check_dirpath(dest, sizeof(dest), vmu->domain, username, "busy");
2266                 if (res)
2267                         snprintf(prefile, sizeof(prefile), "%s%s/%s/busy", MVM_SPOOL_DIR, vmu->domain, username);
2268         } else if (ast_test_flag(&leave_options, OPT_UNAVAIL_GREETING)) {
2269                 res = check_dirpath(dest, sizeof(dest), vmu->domain, username, "unavail");
2270                 if (res)
2271                         snprintf(prefile, sizeof(prefile), "%s%s/%s/unavail", MVM_SPOOL_DIR, vmu->domain, username);
2272         }
2273         /* Check for temporary greeting - it overrides busy and unavail */
2274         snprintf(tempfile, sizeof(tempfile), "%s%s/%s/temp", MVM_SPOOL_DIR, vmu->domain, username);
2275         if (!(res = check_dirpath(dest, sizeof(dest), vmu->domain, username, "temp"))) {
2276                 ast_debug(2, "Temporary message directory does not exist, using default (%s)\n", tempfile);
2277                 ast_copy_string(prefile, tempfile, sizeof(prefile));
2278         }
2279         ast_debug(2, "Preparing to play message ...\n");
2280
2281         /* Check current or macro-calling context for special extensions */
2282         if (ast_test_flag(vmu, MVM_OPERATOR)) {
2283                 if (!ast_strlen_zero(vmu->exit)) {
2284                         if (ast_exists_extension(chan, vmu->exit, "o", 1, chan->cid.cid_num)) {
2285                                 strncat(ecodes, "0", sizeof(ecodes) - strlen(ecodes) - 1);
2286                                 ouseexten = 1;
2287                         }
2288                 } else if (ast_exists_extension(chan, chan->context, "o", 1, chan->cid.cid_num)) {
2289                         strncat(ecodes, "0", sizeof(ecodes) - strlen(ecodes) - 1);
2290                         ouseexten = 1;
2291                 }
2292                 else if (!ast_strlen_zero(chan->macrocontext) && ast_exists_extension(chan, chan->macrocontext, "o", 1, chan->cid.cid_num)) {
2293                         strncat(ecodes, "0", sizeof(ecodes) - strlen(ecodes) - 1);
2294                         ousemacro = 1;
2295                 }
2296         }
2297
2298         if (!ast_strlen_zero(vmu->exit)) {
2299                 if (ast_exists_extension(chan, vmu->exit, "a", 1, chan->cid.cid_num))
2300                         strncat(ecodes, "*", sizeof(ecodes) -  strlen(ecodes) - 1);
2301         } else if (ast_exists_extension(chan, chan->context, "a", 1, chan->cid.cid_num))
2302                 strncat(ecodes, "*", sizeof(ecodes) -  strlen(ecodes) - 1);
2303         else if (!ast_strlen_zero(chan->macrocontext) && ast_exists_extension(chan, chan->macrocontext, "a", 1, chan->cid.cid_num)) {
2304                 strncat(ecodes, "*", sizeof(ecodes) -  strlen(ecodes) - 1);
2305                 ausemacro = 1;
2306         }
2307
2308         res = 0;        /* Reset */
2309         /* Play the beginning intro if desired */
2310         if (!ast_strlen_zero(prefile)) {
2311                 if (ast_streamfile(chan, prefile, chan->language) > -1) 
2312                         res = ast_waitstream(chan, ecodes);
2313         } else {
2314                 ast_debug(2, "%s doesn't exist, doing what we can\n", prefile);
2315                 res = invent_message(chan, vmu->domain, username, ast_test_flag(&leave_options, OPT_BUSY_GREETING), ecodes);
2316         }
2317         if (res < 0) {
2318                 ast_debug(2, "Hang up during prefile playback\n");
2319                 pbx_builtin_setvar_helper(chan, "MVM_GREET_STATUS", "FAILED");
2320                 if(ast_test_flag(vmu, MVM_ALLOCED))
2321                         free_user(vmu);
2322                 return -1;
2323         }
2324         if (res == '#') {
2325                 /* On a '#' we skip the instructions */
2326                 ast_set_flag(&leave_options, OPT_SILENT);
2327                 res = 0;
2328         }
2329         if (!res && !ast_test_flag(&leave_options, OPT_SILENT)) {
2330                 res = ast_streamfile(chan, SOUND_INTRO, chan->language);
2331                 if (!res)
2332                         res = ast_waitstream(chan, ecodes);
2333                 if (res == '#') {
2334                         ast_set_flag(&leave_options, OPT_SILENT);
2335                         res = 0;
2336                 }
2337         }
2338         if (res > 0)
2339                 ast_stopstream(chan);
2340         /* Check for a '*' here in case the caller wants to escape from voicemail to something
2341            other than the operator -- an automated attendant or mailbox login for example */
2342         if (res == '*') {
2343                 chan->exten[0] = 'a';
2344                 chan->exten[1] = '\0';
2345                 if (!ast_strlen_zero(vmu->exit)) {
2346                         ast_copy_string(chan->context, vmu->exit, sizeof(chan->context));
2347                 } else if (ausemacro && !ast_strlen_zero(chan->macrocontext)) {
2348                         ast_copy_string(chan->context, chan->macrocontext, sizeof(chan->context));
2349                 }
2350                 chan->priority = 0;
2351                 pbx_builtin_setvar_helper(chan, "MVM_GREET_STATUS", "USEREXIT");
2352                 res = 0;
2353         } else if (res == '0') { /* Check for a '0' here */
2354                 if(ouseexten || ousemacro) {
2355                         chan->exten[0] = 'o';
2356                         chan->exten[1] = '\0';
2357                         if (!ast_strlen_zero(vmu->exit)) {
2358                                 ast_copy_string(chan->context, vmu->exit, sizeof(chan->context));
2359                         } else if (ousemacro && !ast_strlen_zero(chan->macrocontext)) {
2360                                 ast_copy_string(chan->context, chan->macrocontext, sizeof(chan->context));
2361                         }
2362                         ast_play_and_wait(chan, "transfer");
2363                         chan->priority = 0;
2364                         pbx_builtin_setvar_helper(chan, "MVM_GREET_STATUS", "USEREXIT");
2365                 }
2366                 res =  0;
2367         } else if (res < 0) {
2368                 pbx_builtin_setvar_helper(chan, "MVM_GREET_STATUS", "FAILED");
2369                 res = -1;
2370         } else
2371                 pbx_builtin_setvar_helper(chan, "MVM_GREET_STATUS", "SUCCESS");
2372
2373         if(ast_test_flag(vmu, MVM_ALLOCED))
2374                 free_user(vmu);
2375
2376
2377         /* Ok, we're ready to rock and roll. Return to dialplan */
2378         return res;
2379
2380 }
2381
2382 /*!\internal
2383  * \brief Dialplan application to delete voicemail */
2384 static int minivm_delete_exec(struct ast_channel *chan, const char *data)
2385 {
2386         int res = 0;
2387         char filename[BUFSIZ];
2388
2389         if (!ast_strlen_zero(data)) {
2390                 ast_copy_string(filename, (char *) data, sizeof(filename));
2391         } else {
2392                 ast_channel_lock(chan);
2393                 ast_copy_string(filename, pbx_builtin_getvar_helper(chan, "MVM_FILENAME"), sizeof(filename));
2394                 ast_channel_unlock(chan);
2395         }
2396
2397         if (ast_strlen_zero(filename)) {
2398                 ast_log(LOG_ERROR, "No filename given in application arguments or channel variable MVM_FILENAME\n");
2399                 return res;
2400         } 
2401
2402         /* Go ahead and delete audio files from system, they're not needed any more */
2403         /* We should look for both audio and text files here */
2404         if (ast_fileexists(filename, NULL, NULL) > 0) {
2405                 res = vm_delete(filename);
2406                 if (res) {
2407                         ast_debug(2, "Can't delete file: %s\n", filename);
2408                         pbx_builtin_setvar_helper(chan, "MVM_DELETE_STATUS", "FAILED");
2409                 } else {
2410                         ast_debug(2, "Deleted voicemail file :: %s \n", filename);
2411                         pbx_builtin_setvar_helper(chan, "MVM_DELETE_STATUS", "SUCCESS");
2412                 }
2413         } else {
2414                 ast_debug(2, "Filename does not exist: %s\n", filename);
2415                 pbx_builtin_setvar_helper(chan, "MVM_DELETE_STATUS", "FAILED");
2416         }
2417
2418         return res;
2419 }
2420
2421 /*! \brief Record specific messages for voicemail account */
2422 static int minivm_accmess_exec(struct ast_channel *chan, const char *data)
2423 {
2424         int argc = 0;
2425         char *argv[2];
2426         char filename[PATH_MAX];
2427         char tmp[PATH_MAX];
2428         char *domain;
2429         char *tmpptr = NULL;
2430         struct minivm_account *vmu;
2431         char *username = argv[0];
2432         struct ast_flags flags = { 0 };
2433         char *opts[OPT_ARG_ARRAY_SIZE];
2434         int error = FALSE;
2435         char *message = NULL;
2436         char *prompt = NULL;
2437         int duration;
2438         int cmd;
2439
2440         if (ast_strlen_zero(data))  {
2441                 ast_log(LOG_ERROR, "MinivmAccmess needs at least two arguments: account and option\n");
2442                 error = TRUE;
2443         } else 
2444                 tmpptr = ast_strdupa((char *)data);
2445         if (!error) {
2446                 if (!tmpptr) {
2447                         ast_log(LOG_ERROR, "Out of memory\n");
2448                         error = TRUE;
2449                 } else
2450                         argc = ast_app_separate_args(tmpptr, ',', argv, ARRAY_LEN(argv));
2451         }
2452
2453         if (argc <=1) {
2454                 ast_log(LOG_ERROR, "MinivmAccmess needs at least two arguments: account and option\n");
2455                 error = TRUE;
2456         }
2457         if (!error && strlen(argv[1]) > 1) {
2458                 ast_log(LOG_ERROR, "MinivmAccmess can only handle one option at a time. Bad option string: %s\n", argv[1]);
2459                 error = TRUE;
2460         }
2461
2462         if (!error && ast_app_parse_options(minivm_accmess_options, &flags, opts, argv[1])) {
2463                 ast_log(LOG_ERROR, "Can't parse option %s\n", argv[1]);
2464                 error = TRUE;
2465         }
2466
2467         if (error) {
2468                 pbx_builtin_setvar_helper(chan, "MVM_ACCMESS_STATUS", "FAILED");
2469                 return -1;
2470         }
2471
2472         ast_copy_string(tmp, argv[0], sizeof(tmp));
2473         username = tmp;
2474         domain = strchr(tmp, '@');
2475         if (domain) {
2476                 *domain = '\0';
2477                 domain++;
2478         } 
2479         if (ast_strlen_zero(domain) || ast_strlen_zero(username)) {
2480                 ast_log(LOG_ERROR, "Need username@domain as argument. Sorry. Argument 0 %s\n", argv[0]);
2481                 pbx_builtin_setvar_helper(chan, "MVM_ACCMESS_STATUS", "FAILED");
2482                 return -1;
2483         }
2484
2485         if(!(vmu = find_account(domain, username, TRUE))) {
2486                 /* We could not find user, let's exit */
2487                 ast_log(LOG_WARNING, "Could not allocate temporary memory for '%s@%s'\n", username, domain);
2488                 pbx_builtin_setvar_helper(chan, "MVM_ACCMESS_STATUS", "FAILED");
2489                 return -1;
2490         }
2491
2492         /* Answer channel if it's not already answered */
2493         if (chan->_state != AST_STATE_UP)
2494                 ast_answer(chan);
2495         
2496         /* Here's where the action is */
2497         if (ast_test_flag(&flags, OPT_BUSY_GREETING)) {
2498                 message = "busy";
2499                 prompt = "vm-rec-busy";
2500         } else if (ast_test_flag(&flags, OPT_UNAVAIL_GREETING)) {
2501                 message = "unavailable";
2502                 prompt = "vm-rec-unv";
2503         } else if (ast_test_flag(&flags, OPT_TEMP_GREETING)) {
2504                 message = "temp";
2505                 prompt = "vm-rec-temp";
2506         } else if (ast_test_flag(&flags, OPT_NAME_GREETING)) {
2507                 message = "greet";
2508                 prompt = "vm-rec-name";
2509         }
2510         snprintf(filename,sizeof(filename), "%s%s/%s/%s", MVM_SPOOL_DIR, vmu->domain, vmu->username, message);
2511         /* Maybe we should check the result of play_record_review ? */
2512         cmd = play_record_review(chan, prompt, filename, global_maxgreet, default_vmformat, 0, vmu, &duration, NULL, FALSE);
2513
2514         ast_debug(1, "Recorded new %s message in %s (duration %d)\n", message, filename, duration);
2515
2516         if(ast_test_flag(vmu, MVM_ALLOCED))
2517                 free_user(vmu);
2518
2519         pbx_builtin_setvar_helper(chan, "MVM_ACCMESS_STATUS", "SUCCESS");
2520
2521         /* Ok, we're ready to rock and roll. Return to dialplan */
2522         return 0;
2523 }
2524
2525 /*! \brief Append new mailbox to mailbox list from configuration file */
2526 static int create_vmaccount(char *name, struct ast_variable *var, int realtime)
2527 {
2528         struct minivm_account *vmu;
2529         char *domain;
2530         char *username;
2531         char accbuf[BUFSIZ];
2532
2533         ast_debug(3, "Creating %s account for [%s]\n", realtime ? "realtime" : "static", name);
2534
2535         ast_copy_string(accbuf, name, sizeof(accbuf));
2536         username = accbuf;
2537         domain = strchr(accbuf, '@');
2538         if (domain) {
2539                 *domain = '\0';
2540                 domain++;
2541         }
2542         if (ast_strlen_zero(domain)) {
2543                 ast_log(LOG_ERROR, "No domain given for mini-voicemail account %s. Not configured.\n", name);
2544                 return 0;
2545         }
2546
2547         ast_debug(3, "Creating static account for user %s domain %s\n", username, domain);
2548
2549         /* Allocate user account */
2550         vmu = ast_calloc(1, sizeof(*vmu));
2551         if (!vmu)
2552                 return 0;
2553         
2554         ast_copy_string(vmu->domain, domain, sizeof(vmu->domain));
2555         ast_copy_string(vmu->username, username, sizeof(vmu->username));
2556
2557         populate_defaults(vmu);
2558
2559         ast_debug(3, "...Configuring account %s\n", name);
2560
2561         while (var) {
2562                 ast_debug(3, "Configuring %s = \"%s\" for account %s\n", var->name, var->value, name);
2563                 if (!strcasecmp(var->name, "serveremail")) {
2564                         ast_copy_string(vmu->serveremail, var->value, sizeof(vmu->serveremail));
2565                 } else if (!strcasecmp(var->name, "email")) {
2566                         ast_copy_string(vmu->email, var->value, sizeof(vmu->email));
2567                 } else if (!strcasecmp(var->name, "accountcode")) {
2568                         ast_copy_string(vmu->accountcode, var->value, sizeof(vmu->accountcode));
2569                 } else if (!strcasecmp(var->name, "pincode")) {
2570                         ast_copy_string(vmu->pincode, var->value, sizeof(vmu->pincode));
2571                 } else if (!strcasecmp(var->name, "domain")) {
2572                         ast_copy_string(vmu->domain, var->value, sizeof(vmu->domain));
2573                 } else if (!strcasecmp(var->name, "language")) {
2574                         ast_copy_string(vmu->language, var->value, sizeof(vmu->language));
2575                 } else if (!strcasecmp(var->name, "timezone")) {
2576                         ast_copy_string(vmu->zonetag, var->value, sizeof(vmu->zonetag));
2577                 } else if (!strcasecmp(var->name, "externnotify")) {
2578                         ast_copy_string(vmu->externnotify, var->value, sizeof(vmu->externnotify));
2579                 } else if (!strcasecmp(var->name, "etemplate")) {
2580                         ast_copy_string(vmu->etemplate, var->value, sizeof(vmu->etemplate));
2581                 } else if (!strcasecmp(var->name, "ptemplate")) {
2582                         ast_copy_string(vmu->ptemplate, var->value, sizeof(vmu->ptemplate));
2583                 } else if (!strcasecmp(var->name, "fullname")) {
2584                         ast_copy_string(vmu->fullname, var->value, sizeof(vmu->fullname));
2585                 } else if (!strcasecmp(var->name, "setvar")) {
2586                         char *varval;
2587                         char *varname = ast_strdupa(var->value);
2588                         struct ast_variable *tmpvar;
2589
2590                         if (varname && (varval = strchr(varname, '='))) {
2591                                 *varval = '\0';
2592                                 varval++;
2593                                 if ((tmpvar = ast_variable_new(varname, varval, ""))) {
2594                                         tmpvar->next = vmu->chanvars;
2595                                         vmu->chanvars = tmpvar;
2596                                 }
2597                         }
2598                 } else if (!strcasecmp(var->name, "pager")) {
2599                         ast_copy_string(vmu->pager, var->value, sizeof(vmu->pager));
2600                 } else if (!strcasecmp(var->name, "volgain")) {
2601                         sscanf(var->value, "%lf", &vmu->volgain);
2602                 } else {
2603                         ast_log(LOG_ERROR, "Unknown configuration option for minivm account %s : %s\n", name, var->name);
2604                 }
2605                 var = var->next;
2606         }
2607         ast_debug(3, "...Linking account %s\n", name);
2608         
2609         AST_LIST_LOCK(&minivm_accounts);
2610         AST_LIST_INSERT_TAIL(&minivm_accounts, vmu, list);
2611         AST_LIST_UNLOCK(&minivm_accounts);
2612
2613         global_stats.voicemailaccounts++;
2614
2615         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)" : "");
2616         return 0;
2617 }
2618
2619 /*! \brief Free Mini Voicemail timezone */
2620 static void free_zone(struct minivm_zone *z)
2621 {
2622         ast_free(z);
2623 }
2624
2625 /*! \brief Clear list of timezones */
2626 static void timezone_destroy_list(void)
2627 {
2628         struct minivm_zone *this;
2629
2630         AST_LIST_LOCK(&minivm_zones);
2631         while ((this = AST_LIST_REMOVE_HEAD(&minivm_zones, list))) 
2632                 free_zone(this);
2633                 
2634         AST_LIST_UNLOCK(&minivm_zones);
2635 }
2636
2637 /*! \brief Add time zone to memory list */
2638 static int timezone_add(const char *zonename, const char *config)
2639 {
2640         struct minivm_zone *newzone;
2641         char *msg_format, *timezone_str;
2642
2643         newzone = ast_calloc(1, sizeof(*newzone));
2644         if (newzone == NULL)
2645                 return 0;
2646
2647         msg_format = ast_strdupa(config);
2648         if (msg_format == NULL) {
2649                 ast_log(LOG_WARNING, "Out of memory.\n");
2650                 ast_free(newzone);
2651                 return 0;
2652         }
2653
2654         timezone_str = strsep(&msg_format, "|");
2655         if (!msg_format) {
2656                 ast_log(LOG_WARNING, "Invalid timezone definition : %s\n", zonename);
2657                 ast_free(newzone);
2658                 return 0;
2659         }
2660                         
2661         ast_copy_string(newzone->name, zonename, sizeof(newzone->name));
2662         ast_copy_string(newzone->timezone, timezone_str, sizeof(newzone->timezone));
2663         ast_copy_string(newzone->msg_format, msg_format, sizeof(newzone->msg_format));
2664
2665         AST_LIST_LOCK(&minivm_zones);
2666         AST_LIST_INSERT_TAIL(&minivm_zones, newzone, list);
2667         AST_LIST_UNLOCK(&minivm_zones);
2668
2669         global_stats.timezones++;
2670
2671         return 0;
2672 }
2673
2674 /*! \brief Read message template from file */
2675 static char *message_template_parse_filebody(const char *filename) {
2676         char buf[BUFSIZ * 6];
2677         char readbuf[BUFSIZ];
2678         char filenamebuf[BUFSIZ];
2679         char *writepos;
2680         char *messagebody;
2681         FILE *fi;
2682         int lines = 0;
2683
2684         if (ast_strlen_zero(filename))
2685                 return NULL;
2686         if (*filename == '/') 
2687                 ast_copy_string(filenamebuf, filename, sizeof(filenamebuf));
2688         else 
2689                 snprintf(filenamebuf, sizeof(filenamebuf), "%s/%s", ast_config_AST_CONFIG_DIR, filename);
2690
2691         if (!(fi = fopen(filenamebuf, "r"))) {
2692                 ast_log(LOG_ERROR, "Can't read message template from file: %s\n", filenamebuf);
2693                 return NULL;
2694         }
2695         writepos = buf;
2696         while (fgets(readbuf, sizeof(readbuf), fi)) {
2697                 lines ++;
2698                 if (writepos != buf) {
2699                         *writepos = '\n';               /* Replace EOL with new line */
2700                         writepos++;
2701                 }
2702                 ast_copy_string(writepos, readbuf, sizeof(buf) - (writepos - buf));
2703                 writepos += strlen(readbuf) - 1;
2704         }
2705         fclose(fi);
2706         messagebody = ast_calloc(1, strlen(buf + 1));
2707         ast_copy_string(messagebody, buf, strlen(buf) + 1);
2708         ast_debug(4, "---> Size of allocation %d\n", (int) strlen(buf + 1) );
2709         ast_debug(4, "---> Done reading message template : \n%s\n---- END message template--- \n", messagebody);
2710
2711         return messagebody;
2712 }
2713
2714 /*! \brief Parse emailbody template from configuration file */
2715 static char *message_template_parse_emailbody(const char *configuration)
2716 {
2717         char *tmpread, *tmpwrite;
2718         char *emailbody = ast_strdup(configuration);
2719
2720         /* substitute strings \t and \n into the apropriate characters */
2721         tmpread = tmpwrite = emailbody;
2722         while ((tmpwrite = strchr(tmpread,'\\'))) {
2723                int len = strlen("\n");
2724                switch (tmpwrite[1]) {
2725                case 'n':
2726                       memmove(tmpwrite + len, tmpwrite + 2, strlen(tmpwrite + 2) + 1);
2727                       strncpy(tmpwrite, "\n", len);
2728                       break;
2729                case 't':
2730                       memmove(tmpwrite + len, tmpwrite + 2, strlen(tmpwrite + 2) + 1);
2731                       strncpy(tmpwrite, "\t", len);
2732                       break;
2733                default:
2734                       ast_log(LOG_NOTICE, "Substitution routine does not support this character: %c\n", tmpwrite[1]);
2735                }
2736                tmpread = tmpwrite + len;
2737         }
2738         return emailbody;       
2739 }
2740
2741 /*! \brief Apply general configuration options */
2742 static int apply_general_options(struct ast_variable *var)
2743 {
2744         int error = 0;
2745
2746         while (var) {
2747                 /* Mail command */
2748                 if (!strcmp(var->name, "mailcmd")) {
2749                         ast_copy_string(global_mailcmd, var->value, sizeof(global_mailcmd)); /* User setting */
2750                 } else if (!strcmp(var->name, "maxgreet")) {
2751                         global_maxgreet = atoi(var->value);
2752                 } else if (!strcmp(var->name, "maxsilence")) {
2753                         global_maxsilence = atoi(var->value);
2754                         if (global_maxsilence > 0)
2755                                 global_maxsilence *= 1000;
2756                 } else if (!strcmp(var->name, "logfile")) {
2757                         if (!ast_strlen_zero(var->value) ) {
2758                                 if(*(var->value) == '/')
2759                                         ast_copy_string(global_logfile, var->value, sizeof(global_logfile));
2760                                 else
2761                                         snprintf(global_logfile, sizeof(global_logfile), "%s/%s", ast_config_AST_LOG_DIR, var->value);
2762                         }
2763                 } else if (!strcmp(var->name, "externnotify")) {
2764                         /* External voicemail notify application */
2765                         ast_copy_string(global_externnotify, var->value, sizeof(global_externnotify));
2766                 } else if (!strcmp(var->name, "silencetreshold")) {
2767                         /* Silence treshold */
2768                         global_silencethreshold = atoi(var->value);
2769                 } else if (!strcmp(var->name, "maxmessage")) {
2770                         int x;
2771                         if (sscanf(var->value, "%d", &x) == 1) {
2772                                 global_vmmaxmessage = x;
2773                         } else {
2774                                 error ++;
2775                                 ast_log(LOG_WARNING, "Invalid max message time length\n");
2776                         }
2777                 } else if (!strcmp(var->name, "minmessage")) {
2778                         int x;
2779                         if (sscanf(var->value, "%d", &x) == 1) {
2780                                 global_vmminmessage = x;
2781                                 if (global_maxsilence <= global_vmminmessage)
2782                                         ast_log(LOG_WARNING, "maxsilence should be less than minmessage or you may get empty messages\n");
2783                         } else {
2784                                 error ++;
2785                                 ast_log(LOG_WARNING, "Invalid min message time length\n");
2786                         }
2787                 } else if (!strcmp(var->name, "format")) {
2788                         ast_copy_string(default_vmformat, var->value, sizeof(default_vmformat));
2789                 } else if (!strcmp(var->name, "review")) {
2790                         ast_set2_flag((&globalflags), ast_true(var->value), MVM_REVIEW);        
2791                 } else if (!strcmp(var->name, "operator")) {
2792                         ast_set2_flag((&globalflags), ast_true(var->value), MVM_OPERATOR);      
2793                 }
2794                 var = var->next;
2795         }
2796         return error;
2797 }
2798
2799 /*! \brief Load minivoicemail configuration */
2800 static int load_config(int reload)
2801 {
2802         struct ast_config *cfg;
2803         struct ast_variable *var;
2804         char *cat;
2805         const char *chanvar;
2806         int error = 0;
2807         struct minivm_template *template;
2808         struct ast_flags config_flags = { reload ? CONFIG_FLAG_FILEUNCHANGED : 0 };
2809
2810         cfg = ast_config_load(VOICEMAIL_CONFIG, config_flags);
2811         if (cfg == CONFIG_STATUS_FILEUNCHANGED) {
2812                 return 0;
2813         } else if (cfg == CONFIG_STATUS_FILEINVALID) {
2814                 ast_log(LOG_ERROR, "Config file " VOICEMAIL_CONFIG " is in an invalid format.  Aborting.\n");
2815                 return 0;
2816         }
2817
2818         ast_mutex_lock(&minivmlock);
2819
2820         /* Destroy lists to reconfigure */
2821         message_destroy_list();         /* Destroy list of voicemail message templates */
2822         timezone_destroy_list();        /* Destroy list of timezones */
2823         vmaccounts_destroy_list();      /* Destroy list of voicemail accounts */
2824         ast_debug(2, "Destroyed memory objects...\n");
2825
2826         /* First, set some default settings */
2827         global_externnotify[0] = '\0';
2828         global_logfile[0] = '\0';
2829         global_vmmaxmessage = 2000;
2830         global_maxgreet = 2000;
2831         global_vmminmessage = 0;
2832         strcpy(global_mailcmd, SENDMAIL);
2833         global_maxsilence = 0;
2834         global_saydurationminfo = 2;
2835         ast_copy_string(default_vmformat, "wav", sizeof(default_vmformat));
2836         ast_set2_flag((&globalflags), FALSE, MVM_REVIEW);       
2837         ast_set2_flag((&globalflags), FALSE, MVM_OPERATOR);     
2838         /* Reset statistics */
2839         memset(&global_stats, 0, sizeof(global_stats));
2840         global_stats.reset = ast_tvnow();
2841
2842         global_silencethreshold = ast_dsp_get_threshold_from_settings(THRESHOLD_SILENCE);
2843
2844         /* Make sure we could load configuration file */
2845         if (!cfg) {
2846                 ast_log(LOG_WARNING, "Failed to load configuration file. Module activated with default settings.\n");
2847                 ast_mutex_unlock(&minivmlock);
2848                 return 0;
2849         }
2850
2851         ast_debug(2, "Loaded configuration file, now parsing\n");
2852
2853         /* General settings */
2854
2855         cat = ast_category_browse(cfg, NULL);
2856         while (cat) {
2857                 ast_debug(3, "Found configuration section [%s]\n", cat);
2858                 if (!strcasecmp(cat, "general")) {
2859                         /* Nothing right now */
2860                         error += apply_general_options(ast_variable_browse(cfg, cat));
2861                 } else if (!strncasecmp(cat, "template-", 9))  {
2862                         /* Template */
2863                         char *name = cat + 9;
2864
2865                         /* Now build and link template to list */
2866                         error += message_template_build(name, ast_variable_browse(cfg, cat));
2867                 } else {
2868                         var = ast_variable_browse(cfg, cat);
2869                         if (!strcasecmp(cat, "zonemessages")) {
2870                                 /* Timezones in this context */
2871                                 while (var) {
2872                                         timezone_add(var->name, var->value);
2873                                         var = var->next;
2874                                 }
2875                         } else {
2876                                 /* Create mailbox from this */
2877                                 error += create_vmaccount(cat, var, FALSE);
2878                         }
2879                 }
2880                 /* Find next section in configuration file */
2881                 cat = ast_category_browse(cfg, cat);
2882         }
2883
2884         /* Configure the default email template */
2885         message_template_build("email-default", NULL);
2886         template = message_template_find("email-default");
2887
2888         /* Load date format config for voicemail mail */
2889         if ((chanvar = ast_variable_retrieve(cfg, "general", "emaildateformat"))) 
2890                 ast_copy_string(template->dateformat, chanvar, sizeof(template->dateformat));
2891         if ((chanvar = ast_variable_retrieve(cfg, "general", "emailfromstring")))
2892                 ast_copy_string(template->fromaddress, chanvar, sizeof(template->fromaddress));
2893         if ((chanvar = ast_variable_retrieve(cfg, "general", "emailaaddress")))
2894                 ast_copy_string(template->serveremail, chanvar, sizeof(template->serveremail));
2895         if ((chanvar = ast_variable_retrieve(cfg, "general", "emailcharset")))
2896                 ast_copy_string(template->charset, chanvar, sizeof(template->charset));
2897         if ((chanvar = ast_variable_retrieve(cfg, "general", "emailsubject"))) 
2898                 ast_copy_string(template->subject, chanvar, sizeof(template->subject));
2899         if ((chanvar = ast_variable_retrieve(cfg, "general", "emailbody"))) 
2900                 template->body = message_template_parse_emailbody(chanvar);
2901         template->attachment = TRUE;
2902
2903         message_template_build("pager-default", NULL);
2904         template = message_template_find("pager-default");
2905         if ((chanvar = ast_variable_retrieve(cfg, "general", "pagerfromstring")))
2906                 ast_copy_string(template->fromaddress, chanvar, sizeof(template->fromaddress));
2907         if ((chanvar = ast_variable_retrieve(cfg, "general", "pageraddress")))
2908                 ast_copy_string(template->serveremail, chanvar, sizeof(template->serveremail));
2909         if ((chanvar = ast_variable_retrieve(cfg, "general", "pagercharset")))
2910                 ast_copy_string(template->charset, chanvar, sizeof(template->charset));
2911         if ((chanvar = ast_variable_retrieve(cfg, "general", "pagersubject")))
2912                 ast_copy_string(template->subject, chanvar,sizeof(template->subject));
2913         if ((chanvar = ast_variable_retrieve(cfg, "general", "pagerbody"))) 
2914                 template->body = message_template_parse_emailbody(chanvar);
2915         template->attachment = FALSE;
2916
2917         if (error)
2918                 ast_log(LOG_ERROR, "--- A total of %d errors found in mini-voicemail configuration\n", error);
2919
2920         ast_mutex_unlock(&minivmlock);
2921         ast_config_destroy(cfg);
2922
2923         /* Close log file if it's open and disabled */
2924         if(minivmlogfile)
2925                 fclose(minivmlogfile);
2926
2927         /* Open log file if it's enabled */
2928         if(!ast_strlen_zero(global_logfile)) {
2929                 minivmlogfile = fopen(global_logfile, "a");
2930                 if(!minivmlogfile)
2931                         ast_log(LOG_ERROR, "Failed to open minivm log file %s : %s\n", global_logfile, strerror(errno));
2932                 if (minivmlogfile)
2933                         ast_debug(3, "Opened log file %s \n", global_logfile);
2934         }
2935
2936         return 0;
2937 }
2938
2939 /*! \brief CLI routine for listing templates */
2940 static char *handle_minivm_list_templates(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
2941 {
2942         struct minivm_template *this;
2943 #define HVLT_OUTPUT_FORMAT "%-15s %-10s %-10s %-15.15s %-50s\n"
2944         int count = 0;
2945
2946         switch (cmd) {
2947         case CLI_INIT:
2948                 e->command = "minivm list templates";
2949                 e->usage =
2950                         "Usage: minivm list templates\n"
2951                         "       Lists message templates for e-mail, paging and IM\n";
2952                 return NULL;
2953         case CLI_GENERATE:
2954                 return NULL;
2955         }
2956
2957         if (a->argc > 3)
2958                 return CLI_SHOWUSAGE;
2959
2960         AST_LIST_LOCK(&message_templates);
2961         if (AST_LIST_EMPTY(&message_templates)) {
2962                 ast_cli(a->fd, "There are no message templates defined\n");
2963                 AST_LIST_UNLOCK(&message_templates);
2964                 return CLI_FAILURE;
2965         }
2966         ast_cli(a->fd, HVLT_OUTPUT_FORMAT, "Template name", "Charset", "Locale", "Attach media", "Subject");
2967         ast_cli(a->fd, HVLT_OUTPUT_FORMAT, "-------------", "-------", "------", "------------", "-------");
2968         AST_LIST_TRAVERSE(&message_templates, this, list) {
2969                 ast_cli(a->fd, HVLT_OUTPUT_FORMAT, this->name, 
2970                         this->charset ? this->charset : "-", 
2971                         this->locale ? this->locale : "-",
2972                         this->attachment ? "Yes" : "No",
2973                         this->subject ? this->subject : "-");
2974                 count++;
2975         }
2976         AST_LIST_UNLOCK(&message_templates);
2977         ast_cli(a->fd, "\n * Total: %d minivoicemail message templates\n", count);
2978         return CLI_SUCCESS;
2979 }
2980
2981 static char *complete_minivm_show_users(const char *line, const char *word, int pos, int state)
2982 {
2983         int which = 0;
2984         int wordlen;
2985         struct minivm_account *vmu;
2986         const char *domain = "";
2987
2988         /* 0 - voicemail; 1 - list; 2 - accounts; 3 - for; 4 - <domain> */
2989         if (pos > 4)
2990                 return NULL;
2991         if (pos == 3)
2992                 return (state == 0) ? ast_strdup("for") : NULL;
2993         wordlen = strlen(word);
2994         AST_LIST_TRAVERSE(&minivm_accounts, vmu, list) {
2995                 if (!strncasecmp(word, vmu->domain, wordlen)) {
2996                         if (domain && strcmp(domain, vmu->domain) && ++which > state)
2997                                 return ast_strdup(vmu->domain);
2998                         /* ignore repeated domains ? */
2999                         domain = vmu->domain;
3000                 }
3001         }
3002         return NULL;
3003 }
3004
3005 /*! \brief CLI command to list voicemail accounts */
3006 static char *handle_minivm_show_users(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
3007 {
3008         struct minivm_account *vmu;
3009 #define HMSU_OUTPUT_FORMAT "%-23s %-15s %-15s %-10s %-10s %-50s\n"
3010         int count = 0;
3011
3012         switch (cmd) {
3013         case CLI_INIT:
3014                 e->command = "minivm list accounts";
3015                 e->usage =
3016                         "Usage: minivm list accounts\n"
3017                         "       Lists all mailboxes currently set up\n";
3018                 return NULL;
3019         case CLI_GENERATE:
3020                 return complete_minivm_show_users(a->line, a->word, a->pos, a->n);
3021         }
3022
3023         if ((a->argc < 3) || (a->argc > 5) || (a->argc == 4))
3024                 return CLI_SHOWUSAGE;
3025         if ((a->argc == 5) && strcmp(a->argv[3],"for"))
3026                 return CLI_SHOWUSAGE;
3027
3028         AST_LIST_LOCK(&minivm_accounts);
3029         if (AST_LIST_EMPTY(&minivm_accounts)) {
3030                 ast_cli(a->fd, "There are no voicemail users currently defined\n");
3031                 AST_LIST_UNLOCK(&minivm_accounts);
3032                 return CLI_FAILURE;
3033         }
3034         ast_cli(a->fd, HMSU_OUTPUT_FORMAT, "User", "E-Template", "P-template", "Zone", "Format", "Full name");
3035         ast_cli(a->fd, HMSU_OUTPUT_FORMAT, "----", "----------", "----------", "----", "------", "---------");
3036         AST_LIST_TRAVERSE(&minivm_accounts, vmu, list) {
3037                 char tmp[256] = "";
3038                 if ((a->argc == 3) || ((a->argc == 5) && !strcmp(a->argv[4], vmu->domain))) {
3039                         count++;
3040                         snprintf(tmp, sizeof(tmp), "%s@%s", vmu->username, vmu->domain);
3041                         ast_cli(a->fd, HMSU_OUTPUT_FORMAT, tmp, vmu->etemplate ? vmu->etemplate : "-", 
3042                                 vmu->ptemplate ? vmu->ptemplate : "-",
3043                                 vmu->zonetag ? vmu->zonetag : "-", 
3044                                 vmu->attachfmt ? vmu->attachfmt : "-",
3045                                 vmu->fullname);
3046                 }
3047         }
3048         AST_LIST_UNLOCK(&minivm_accounts);
3049         ast_cli(a->fd, "\n * Total: %d minivoicemail accounts\n", count);
3050         return CLI_SUCCESS;
3051 }
3052
3053 /*! \brief Show a list of voicemail zones in the CLI */
3054 static char *handle_minivm_show_zones(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
3055 {
3056         struct minivm_zone *zone;
3057 #define HMSZ_OUTPUT_FORMAT "%-15s %-20s %-45s\n"
3058         char *res = CLI_SUCCESS;
3059
3060         switch (cmd) {
3061         case CLI_INIT:
3062                 e->command = "minivm list zones";
3063                 e->usage =
3064                         "Usage: minivm list zones\n"
3065                         "       Lists zone message formats\n";
3066                 return NULL;
3067         case CLI_GENERATE:
3068                 return NULL;
3069         }
3070
3071         if (a->argc != e->args)
3072                 return CLI_SHOWUSAGE;
3073
3074         AST_LIST_LOCK(&minivm_zones);
3075         if (!AST_LIST_EMPTY(&minivm_zones)) {
3076                 ast_cli(a->fd, HMSZ_OUTPUT_FORMAT, "Zone", "Timezone", "Message Format");
3077                 ast_cli(a->fd, HMSZ_OUTPUT_FORMAT, "----", "--------", "--------------");
3078                 AST_LIST_TRAVERSE(&minivm_zones, zone, list) {
3079                         ast_cli(a->fd, HMSZ_OUTPUT_FORMAT, zone->name, zone->timezone, zone->msg_format);
3080                 }
3081         } else {
3082                 ast_cli(a->fd, "There are no voicemail zones currently defined\n");
3083                 res = CLI_FAILURE;
3084         }
3085         AST_LIST_UNLOCK(&minivm_zones);
3086
3087         return res;
3088 }
3089