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