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