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