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