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