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