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