Set MINIVM_ACCMESS_STATUS in all cases. Also, remove a variable that was not needed.
[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, "%s", "");
1110
1111
1112         snprintf(dur, sizeof(dur), "%d:%02d", duration / 60, duration % 60);
1113
1114         /* Does this user have a timezone specified? */
1115         if (!ast_strlen_zero(vmu->zonetag)) {
1116                 /* Find the zone in the list */
1117                 struct minivm_zone *z;
1118                 AST_LIST_LOCK(&minivm_zones);
1119                 AST_LIST_TRAVERSE(&minivm_zones, z, list) {
1120                         if (strcmp(z->name, vmu->zonetag)) 
1121                                 continue;
1122                         the_zone = z;
1123                 }
1124                 AST_LIST_UNLOCK(&minivm_zones);
1125         }
1126
1127         now = ast_tvnow();
1128         ast_localtime(&now, &tm, the_zone ? the_zone->timezone : NULL);
1129         ast_strftime(date, sizeof(date), "%a, %d %b %Y %H:%M:%S %z", &tm);
1130
1131         /* Start printing the email to the temporary file */
1132         fprintf(p, "Date: %s\n", date);
1133
1134         /* Set date format for voicemail mail */
1135         ast_strftime(date, sizeof(date), template->dateformat, &tm);
1136
1137
1138         /* Populate channel with channel variables for substitution */
1139         prep_email_sub_vars(ast, vmu, cidnum, cidname, dur, date, counter);
1140
1141         /* Find email address to use */
1142         /* If there's a server e-mail adress in the account, user that, othterwise template */
1143         fromemail = ast_strlen_zero(vmu->serveremail) ?  template->serveremail : vmu->serveremail;
1144
1145         /* Find name to user for server e-mail */
1146         fromaddress = ast_strlen_zero(template->fromaddress) ? "" : template->fromaddress;
1147
1148         /* If needed, add hostname as domain */
1149         if (ast_strlen_zero(fromemail))
1150                 fromemail = "asterisk";
1151
1152         if (strchr(fromemail, '@'))
1153                 ast_copy_string(who, fromemail, sizeof(who));
1154         else  {
1155                 char host[MAXHOSTNAMELEN];
1156                 gethostname(host, sizeof(host)-1);
1157                 snprintf(who, sizeof(who), "%s@%s", fromemail, host);
1158         }
1159
1160         if (ast_strlen_zero(fromaddress)) {
1161                 fprintf(p, "From: Asterisk PBX <%s>\n", who);
1162         } else {
1163                 /* Allocate a buffer big enough for variable substitution */
1164                 int vmlen = strlen(fromaddress) * 3 + 200;
1165
1166                 ast_debug(4, "-_-_- Fromaddress template: %s\n", fromaddress);
1167                 if ((passdata = alloca(vmlen))) {
1168                         pbx_substitute_variables_helper(ast, fromaddress, passdata, vmlen);
1169                         len_passdata = strlen(passdata) * 2 + 3;
1170                         passdata2 = alloca(len_passdata);
1171                         fprintf(p, "From: %s <%s>\n", mailheader_quote(passdata, passdata2, len_passdata), who);
1172                 } else  {
1173                         ast_log(LOG_WARNING, "Cannot allocate workspace for variable substitution\n");
1174                         fclose(p);
1175                         return -1;      
1176                 }
1177         } 
1178         ast_debug(4, "-_-_- Fromstring now: %s\n", ast_strlen_zero(passdata) ? "-default-" : passdata);
1179
1180         fprintf(p, "Message-ID: <Asterisk-%d-%s-%d-%s>\n", (unsigned int)rand(), vmu->username, (int)getpid(), who);
1181         len_passdata = strlen(vmu->fullname) * 2 + 3;
1182         passdata2 = alloca(len_passdata);
1183         if (!ast_strlen_zero(vmu->email))
1184                 fprintf(p, "To: %s <%s>\n", mailheader_quote(vmu->fullname, passdata2, len_passdata), vmu->email);
1185         else
1186                 fprintf(p, "To: %s <%s@%s>\n", mailheader_quote(vmu->fullname, passdata2, len_passdata), vmu->username, vmu->domain);
1187
1188         if (!ast_strlen_zero(template->subject)) {
1189                 char *pass_data;
1190                 int vmlen = strlen(template->subject) * 3 + 200;
1191                 if ((pass_data = alloca(vmlen))) {
1192                         pbx_substitute_variables_helper(ast, template->subject, pass_data, vmlen);
1193                         fprintf(p, "Subject: %s\n", pass_data);
1194                 } else {
1195                         ast_log(LOG_WARNING, "Cannot allocate workspace for variable substitution\n");
1196                         fclose(p);
1197                         return -1;      
1198                 }
1199
1200                 ast_debug(4, "-_-_- Subject now: %s\n", pass_data);
1201
1202         } else  {
1203                 fprintf(p, "Subject: New message in mailbox %s@%s\n", vmu->username, vmu->domain);
1204                 ast_debug(1, "-_-_- Using default subject for this email \n");
1205         }
1206
1207
1208         if (option_debug > 2)
1209                 fprintf(p, "X-Asterisk-debug: template %s user account %s@%s\n", template->name, vmu->username, vmu->domain);
1210         fprintf(p, "MIME-Version: 1.0\n");
1211
1212         /* Something unique. */
1213         snprintf(bound, sizeof(bound), "voicemail_%s%d%d", vmu->username, (int)getpid(), (unsigned int)rand());
1214
1215         fprintf(p, "Content-Type: multipart/mixed; boundary=\"%s\"\n\n\n", bound);
1216
1217         fprintf(p, "--%s\n", bound);
1218         fprintf(p, "Content-Type: text/plain; charset=%s\nContent-Transfer-Encoding: 8bit\n\n", global_charset);
1219         if (!ast_strlen_zero(template->body)) {
1220                 char *pass_data;
1221                 int vmlen = strlen(template->body)*3 + 200;
1222                 if ((pass_data = alloca(vmlen))) {
1223                         pbx_substitute_variables_helper(ast, template->body, pass_data, vmlen);
1224                         ast_debug(3, "Message now: %s\n-----\n", pass_data);
1225                         fprintf(p, "%s\n", pass_data);
1226                 } else
1227                         ast_log(LOG_WARNING, "Cannot allocate workspace for variable substitution\n");
1228         } else {
1229                 fprintf(p, "Dear %s:\n\n\tJust wanted to let you know you were just left a %s long message \n"
1230
1231                         "in mailbox %s from %s, on %s so you might\n"
1232                         "want to check it when you get a chance.  Thanks!\n\n\t\t\t\t--Asterisk\n\n", vmu->fullname, 
1233                         dur,  vmu->username, (cidname ? cidname : (cidnum ? cidnum : "an unknown caller")), date);
1234                 ast_debug(3, "Using default message body (no template)\n-----\n");
1235         }
1236         /* Eww. We want formats to tell us their own MIME type */
1237         if (template->attachment) {
1238                 char *ctype = "audio/x-";
1239                 ast_debug(3, "-_-_- Attaching file to message: %s\n", fname);
1240                 if (!strcasecmp(format, "ogg"))
1241                         ctype = "application/";
1242         
1243                 fprintf(p, "--%s\n", bound);
1244                 fprintf(p, "Content-Type: %s%s; name=\"voicemailmsg.%s\"\n", ctype, format, format);
1245                 fprintf(p, "Content-Transfer-Encoding: base64\n");
1246                 fprintf(p, "Content-Description: Voicemail sound attachment.\n");
1247                 fprintf(p, "Content-Disposition: attachment; filename=\"voicemail%s.%s\"\n\n", counter ? counter : "", format);
1248
1249                 base_encode(fname, p);
1250                 fprintf(p, "\n\n--%s--\n.\n", bound);
1251         }
1252         fclose(p);
1253         snprintf(tmp2, sizeof(tmp2), "( %s < %s ; rm -f %s ) &", global_mailcmd, tmp, tmp);
1254         ast_safe_system(tmp2);
1255         ast_debug(1, "Sent message to %s with command '%s' - %s\n", vmu->email, global_mailcmd, template->attachment ? "(media attachment)" : "");
1256         ast_debug(3, "-_-_- Actual command used: %s\n", tmp2);
1257         if (ast)
1258                 ast_channel_free(ast);
1259         return 0;
1260 }
1261
1262 /*! \brief Create directory based on components */
1263 static int make_dir(char *dest, int len, const char *domain, const char *username, const char *folder)
1264 {
1265         return snprintf(dest, len, "%s%s/%s%s%s", MVM_SPOOL_DIR, domain, username, ast_strlen_zero(folder) ? "" : "/", folder ? folder : "");
1266 }
1267
1268 /*! \brief Checks if directory exists. Does not create directory, but builds string in dest
1269  * \param dest    String. base directory.
1270  * \param len    Int. Length base directory string.
1271  * \param domain String. Ignored if is null or empty string.
1272  * \param username String. Ignored if is null or empty string. 
1273  * \param folder  String. Ignored if is null or empty string.
1274  * \return 0 on failure, 1 on success.
1275  */
1276 static int check_dirpath(char *dest, int len, char *domain, char *username, char *folder)
1277 {
1278         struct stat filestat;
1279         make_dir(dest, len, domain, username, folder ? folder : "");
1280         if (stat(dest, &filestat)== -1)
1281                 return FALSE;
1282         else
1283                 return TRUE;
1284 }
1285
1286 /*! \brief basically mkdir -p $dest/$domain/$username/$folder
1287  * \param dest    String. base directory.
1288  * \param len     Length of directory string
1289  * \param domain  String. Ignored if is null or empty string.
1290  * \param folder  String. Ignored if is null or empty string. 
1291  * \param username  String. Ignored if is null or empty string.
1292  * \return -1 on failure, 0 on success.
1293  */
1294 static int create_dirpath(char *dest, int len, char *domain, char *username, char *folder)
1295 {
1296         int res;
1297         make_dir(dest, len, domain, username, folder);
1298         if ((res = ast_mkdir(dest, 0777))) {
1299                 ast_log(LOG_WARNING, "ast_mkdir '%s' failed: %s\n", dest, strerror(res));
1300                 return -1;
1301         }
1302         ast_debug(2, "Creating directory for %s@%s folder %s : %s\n", username, domain, folder, dest);
1303         return 0;
1304 }
1305
1306
1307 /*! \brief Play intro message before recording voicemail 
1308 */
1309 static int invent_message(struct ast_channel *chan, char *domain, char *username, int busy, char *ecodes)
1310 {
1311         int res;
1312         char fn[PATH_MAX];
1313
1314         ast_debug(2, "-_-_- Still preparing to play message ...\n");
1315
1316         snprintf(fn, sizeof(fn), "%s%s/%s/greet", MVM_SPOOL_DIR, domain, username);
1317
1318         if (ast_fileexists(fn, NULL, NULL) > 0) {
1319                 res = ast_streamfile(chan, fn, chan->language);
1320                 if (res) 
1321                         return -1;
1322                 res = ast_waitstream(chan, ecodes);
1323                 if (res) 
1324                         return res;
1325         } else {
1326                 int numericusername = 1;
1327                 char *i = username;
1328
1329                 ast_debug(2, "-_-_- No personal prompts. Using default prompt set for language\n");
1330                 
1331                 while (*i)  {
1332                         ast_debug(2, "-_-_- Numeric? Checking %c\n", *i);
1333                         if (!isdigit(*i)) {
1334                                 numericusername = FALSE;
1335                                 break;
1336                         }
1337                         i++;
1338                 }
1339
1340                 if (numericusername) {
1341                         if(ast_streamfile(chan, "vm-theperson", chan->language))
1342                                 return -1;
1343                         if ((res = ast_waitstream(chan, ecodes)))
1344                                 return res;
1345         
1346                         res = ast_say_digit_str(chan, username, ecodes, chan->language);
1347                         if (res)
1348                                 return res;
1349                 } else {
1350                         if(ast_streamfile(chan, "vm-theextensionis", chan->language))
1351                                 return -1;
1352                         if ((res = ast_waitstream(chan, ecodes)))
1353                                 return res;
1354                 }
1355         }
1356
1357         res = ast_streamfile(chan, busy ? "vm-isonphone" : "vm-isunavail", chan->language);
1358         if (res)
1359                 return -1;
1360         res = ast_waitstream(chan, ecodes);
1361         return res;
1362 }
1363
1364 /*! \brief Delete media files and attribute file */
1365 static int vm_delete(char *file)
1366 {
1367         int res;
1368
1369         ast_debug(1, "-_-_- Deleting voicemail file %s\n", file);
1370
1371         res = unlink(file);     /* Remove the meta data file */
1372         res |=  ast_filedelete(file, NULL);     /* remove the media file */
1373         return res;
1374 }
1375
1376
1377 /*! \brief Record voicemail message & let caller review or re-record it, or set options if applicable */
1378 static int play_record_review(struct ast_channel *chan, char *playfile, char *recordfile, int maxtime, char *fmt,
1379                               int outsidecaller, struct minivm_account *vmu, int *duration, const char *unlockdir,
1380                               signed char record_gain)
1381 {
1382         int cmd = 0;
1383         int max_attempts = 3;
1384         int attempts = 0;
1385         int recorded = 0;
1386         int message_exists = 0;
1387         signed char zero_gain = 0;
1388         char *acceptdtmf = "#";
1389         char *canceldtmf = "";
1390
1391         /* Note that urgent and private are for flagging messages as such in the future */
1392  
1393         /* barf if no pointer passed to store duration in */
1394         if (duration == NULL) {
1395                 ast_log(LOG_WARNING, "Error play_record_review called without duration pointer\n");
1396                 return -1;
1397         }
1398
1399         cmd = '3';       /* Want to start by recording */
1400  
1401         while ((cmd >= 0) && (cmd != 't')) {
1402                 switch (cmd) {
1403                 case '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         char filename[PATH_MAX];
2198         char tmp[PATH_MAX];
2199         char *domain;
2200         char *tmpptr = NULL;
2201         struct minivm_account *vmu;
2202         char *username = argv[0];
2203         struct ast_flags flags = { 0 };
2204         char *opts[OPT_ARG_ARRAY_SIZE];
2205         int error = FALSE;
2206         char *message = NULL;
2207         char *prompt = NULL;
2208         int duration;
2209         int cmd;
2210
2211         if (ast_strlen_zero(data))  {
2212                 ast_log(LOG_ERROR, "MinivmAccmess needs at least two arguments: account and option\n");
2213                 error = TRUE;
2214         } else 
2215                 tmpptr = ast_strdupa((char *)data);
2216         if (!error) {
2217                 if (!tmpptr) {
2218                         ast_log(LOG_ERROR, "Out of memory\n");
2219                         error = TRUE;
2220                 } else
2221                         argc = ast_app_separate_args(tmpptr, ',', argv, ARRAY_LEN(argv));
2222         }
2223
2224         if (argc <=1) {
2225                 ast_log(LOG_ERROR, "MinivmAccmess needs at least two arguments: account and option\n");
2226                 error = TRUE;
2227         }
2228         if (!error && strlen(argv[1]) > 1) {
2229                 ast_log(LOG_ERROR, "MinivmAccmess can only handle one option at a time. Bad option string: %s\n", argv[1]);
2230                 error = TRUE;
2231         }
2232
2233         if (!error && ast_app_parse_options(minivm_accmess_options, &flags, opts, argv[1])) {
2234                 ast_log(LOG_ERROR, "Can't parse option %s\n", argv[1]);
2235                 error = TRUE;
2236         }
2237
2238         if (error) {
2239                 pbx_builtin_setvar_helper(chan, "MINIVM_ACCMESS_STATUS", "FAILED");
2240                 return -1;
2241         }
2242
2243         ast_copy_string(tmp, argv[0], sizeof(tmp));
2244         username = tmp;
2245         domain = strchr(tmp, '@');
2246         if (domain) {
2247                 *domain = '\0';
2248                 domain++;
2249         } 
2250         if (ast_strlen_zero(domain) || ast_strlen_zero(username)) {
2251                 ast_log(LOG_ERROR, "Need username@domain as argument. Sorry. Argument 0 %s\n", argv[0]);
2252                 pbx_builtin_setvar_helper(chan, "MINIVM_ACCMESS_STATUS", "FAILED");
2253                 return -1;
2254         }
2255
2256         if(!(vmu = find_account(domain, username, TRUE))) {
2257                 /* We could not find user, let's exit */
2258                 ast_log(LOG_WARNING, "Could not allocate temporary memory for '%s@%s'\n", username, domain);
2259                 pbx_builtin_setvar_helper(chan, "MINIVM_NOTIFY_STATUS", "FAILED");
2260                 return -1;
2261         }
2262
2263         /* Answer channel if it's not already answered */
2264         if (chan->_state != AST_STATE_UP)
2265                 ast_answer(chan);
2266         
2267         /* Here's where the action is */
2268         if (ast_test_flag(&flags, OPT_BUSY_GREETING)) {
2269                 message = "busy";
2270                 prompt = "vm-rec-busy";
2271         } else if (ast_test_flag(&flags, OPT_UNAVAIL_GREETING)) {
2272                 message = "unavailable";
2273                 prompt = "vm-rec-unavail";
2274         } else if (ast_test_flag(&flags, OPT_TEMP_GREETING)) {
2275                 message = "temp";
2276                 prompt = "vm-temp-greeting";
2277         } else if (ast_test_flag(&flags, OPT_NAME_GREETING)) {
2278                 message = "greet";
2279                 prompt = "vm-rec-name";
2280         }
2281         snprintf(filename,sizeof(filename), "%s%s/%s/%s", MVM_SPOOL_DIR, vmu->domain, vmu->username, message);
2282         /* Maybe we should check the result of play_record_review ? */
2283         cmd = play_record_review(chan, prompt, filename, global_maxgreet, default_vmformat, 0, vmu, &duration, NULL, FALSE);
2284
2285         ast_debug(1, "Recorded new %s message in %s (duration %d)\n", message, filename, duration);
2286
2287         if(ast_test_flag(vmu, MVM_ALLOCED))
2288                 free_user(vmu);
2289
2290         pbx_builtin_setvar_helper(chan, "MINIVM_NOTIFY_STATUS", "SUCCESS");
2291
2292         /* Ok, we're ready to rock and roll. Return to dialplan */
2293         return 0;
2294 }
2295
2296 /*! \brief Append new mailbox to mailbox list from configuration file */
2297 static int create_vmaccount(char *name, struct ast_variable *var, int realtime)
2298 {
2299         struct minivm_account *vmu;
2300         char *domain;
2301         char *username;
2302         char accbuf[BUFSIZ];
2303
2304         ast_debug(3, "Creating %s account for [%s]\n", realtime ? "realtime" : "static", name);
2305
2306         ast_copy_string(accbuf, name, sizeof(accbuf));
2307         username = accbuf;
2308         domain = strchr(accbuf, '@');
2309         if (domain) {
2310                 *domain = '\0';
2311                 domain++;
2312         }
2313         if (ast_strlen_zero(domain)) {
2314                 ast_log(LOG_ERROR, "No domain given for mini-voicemail account %s. Not configured.\n", name);
2315                 return 0;
2316         }
2317
2318         ast_debug(3, "Creating static account for user %s domain %s\n", username, domain);
2319
2320         /* Allocate user account */
2321         vmu = ast_calloc(1, sizeof(*vmu));
2322         if (!vmu)
2323                 return 0;
2324         
2325         ast_copy_string(vmu->domain, domain, sizeof(vmu->domain));
2326         ast_copy_string(vmu->username, username, sizeof(vmu->username));
2327
2328         populate_defaults(vmu);
2329
2330         ast_debug(3, "...Configuring account %s\n", name);
2331
2332         while (var) {
2333                 ast_debug(3, "---- Configuring %s = \"%s\" for account %s\n", var->name, var->value, name);
2334                 if (!strcasecmp(var->name, "serveremail")) {
2335                         ast_copy_string(vmu->serveremail, var->value, sizeof(vmu->serveremail));
2336                 } else if (!strcasecmp(var->name, "email")) {
2337                         ast_copy_string(vmu->email, var->value, sizeof(vmu->email));
2338                 } else if (!strcasecmp(var->name, "accountcode")) {
2339                         ast_copy_string(vmu->accountcode, var->value, sizeof(vmu->accountcode));
2340                 } else if (!strcasecmp(var->name, "pincode")) {
2341                         ast_copy_string(vmu->pincode, var->value, sizeof(vmu->pincode));
2342                 } else if (!strcasecmp(var->name, "domain")) {
2343                         ast_copy_string(vmu->domain, var->value, sizeof(vmu->domain));
2344                 } else if (!strcasecmp(var->name, "language")) {
2345                         ast_copy_string(vmu->language, var->value, sizeof(vmu->language));
2346                 } else if (!strcasecmp(var->name, "timezone")) {
2347                         ast_copy_string(vmu->zonetag, var->value, sizeof(vmu->zonetag));
2348                 } else if (!strcasecmp(var->name, "externnotify")) {
2349                         ast_copy_string(vmu->externnotify, var->value, sizeof(vmu->externnotify));
2350                 } else if (!strcasecmp(var->name, "etemplate")) {
2351                         ast_copy_string(vmu->etemplate, var->value, sizeof(vmu->etemplate));
2352                 } else if (!strcasecmp(var->name, "ptemplate")) {
2353                         ast_copy_string(vmu->ptemplate, var->value, sizeof(vmu->ptemplate));
2354                 } else if (!strcasecmp(var->name, "fullname")) {
2355                         ast_copy_string(vmu->fullname, var->value, sizeof(vmu->fullname));
2356                 } else if (!strcasecmp(var->name, "setvar")) {
2357                         char *varval;
2358                         char *varname = ast_strdupa(var->value);
2359                         struct ast_variable *tmpvar;
2360
2361                         if (varname && (varval = strchr(varname, '='))) {
2362                                 *varval = '\0';
2363                                 varval++;
2364                                 if ((tmpvar = ast_variable_new(varname, varval, ""))) {
2365                                         tmpvar->next = vmu->chanvars;
2366                                         vmu->chanvars = tmpvar;
2367                                 }
2368                         }
2369                 } else if (!strcasecmp(var->name, "pager")) {
2370                         ast_copy_string(vmu->pager, var->value, sizeof(vmu->pager));
2371                 } else if (!strcasecmp(var->name, "volgain")) {
2372                         sscanf(var->value, "%lf", &vmu->volgain);
2373                 } else {
2374                         ast_log(LOG_ERROR, "Unknown configuration option for minivm account %s : %s\n", name, var->name);
2375                 }
2376                 var = var->next;
2377         }
2378         ast_debug(3, "...Linking account %s\n", name);
2379         
2380         AST_LIST_LOCK(&minivm_accounts);
2381         AST_LIST_INSERT_TAIL(&minivm_accounts, vmu, list);
2382         AST_LIST_UNLOCK(&minivm_accounts);
2383
2384         global_stats.voicemailaccounts++;
2385
2386         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)" : "");
2387         return 0;
2388 }
2389
2390 /*! \brief Free Mini Voicemail timezone */
2391 static void free_zone(struct minivm_zone *z)
2392 {
2393         ast_free(z);
2394 }
2395
2396 /*! \brief Clear list of timezones */
2397 static void timezone_destroy_list(void)
2398 {
2399         struct minivm_zone *this;
2400
2401         AST_LIST_LOCK(&minivm_zones);
2402         while ((this = AST_LIST_REMOVE_HEAD(&minivm_zones, list))) 
2403                 free_zone(this);
2404                 
2405         AST_LIST_UNLOCK(&minivm_zones);
2406 }
2407
2408 /*! \brief Add time zone to memory list */
2409 static int timezone_add(const char *zonename, const char *config)
2410 {
2411         struct minivm_zone *newzone;
2412         char *msg_format, *timezone_str;
2413
2414         newzone = ast_calloc(1, sizeof(*newzone));
2415         if (newzone == NULL)
2416                 return 0;
2417
2418         msg_format = ast_strdupa(config);
2419         if (msg_format == NULL) {
2420                 ast_log(LOG_WARNING, "Out of memory.\n");
2421                 ast_free(newzone);
2422                 return 0;
2423         }
2424
2425         timezone_str = strsep(&msg_format, "|");
2426         if (!msg_format) {
2427                 ast_log(LOG_WARNING, "Invalid timezone definition : %s\n", zonename);
2428                 ast_free(newzone);
2429                 return 0;
2430         }
2431                         
2432         ast_copy_string(newzone->name, zonename, sizeof(newzone->name));
2433         ast_copy_string(newzone->timezone, timezone_str, sizeof(newzone->timezone));
2434         ast_copy_string(newzone->msg_format, msg_format, sizeof(newzone->msg_format));
2435
2436         AST_LIST_LOCK(&minivm_zones);
2437         AST_LIST_INSERT_TAIL(&minivm_zones, newzone, list);
2438         AST_LIST_UNLOCK(&minivm_zones);
2439
2440         global_stats.timezones++;
2441
2442         return 0;
2443 }
2444
2445 /*! \brief Read message template from file */
2446 static char *message_template_parse_filebody(const char *filename) {
2447         char buf[BUFSIZ * 6];
2448         char readbuf[BUFSIZ];
2449         char filenamebuf[BUFSIZ];
2450         char *writepos;
2451         char *messagebody;
2452         FILE *fi;
2453         int lines = 0;
2454
2455         if (ast_strlen_zero(filename))
2456                 return NULL;
2457         if (*filename == '/') 
2458                 ast_copy_string(filenamebuf, filename, sizeof(filenamebuf));
2459         else 
2460                 snprintf(filenamebuf, sizeof(filenamebuf), "%s/%s", ast_config_AST_CONFIG_DIR, filename);
2461
2462         if (!(fi = fopen(filenamebuf, "r"))) {
2463                 ast_log(LOG_ERROR, "Can't read message template from file: %s\n", filenamebuf);
2464                 return NULL;
2465         }
2466         writepos = buf;
2467         while (fgets(readbuf, sizeof(readbuf), fi)) {
2468                 lines ++;
2469                 if (writepos != buf) {
2470                         *writepos = '\n';               /* Replace EOL with new line */
2471                         writepos++;
2472                 }
2473                 ast_copy_string(writepos, readbuf, sizeof(buf) - (writepos - buf));
2474                 writepos += strlen(readbuf) - 1;
2475         }
2476         fclose(fi);
2477         messagebody = ast_calloc(1, strlen(buf + 1));
2478         ast_copy_string(messagebody, buf, strlen(buf) + 1);
2479         ast_debug(4, "---> Size of allocation %d\n", (int) strlen(buf + 1) );
2480         ast_debug(4, "---> Done reading message template : \n%s\n---- END message template--- \n", messagebody);
2481
2482         return messagebody;
2483 }
2484
2485 /*! \brief Parse emailbody template from configuration file */
2486 static char *message_template_parse_emailbody(const char *configuration)
2487 {
2488         char *tmpread, *tmpwrite;
2489         char *emailbody = ast_strdup(configuration);
2490
2491         /* substitute strings \t and \n into the apropriate characters */
2492         tmpread = tmpwrite = emailbody;
2493         while ((tmpwrite = strchr(tmpread,'\\'))) {
2494                int len = strlen("\n");
2495                switch (tmpwrite[1]) {
2496                case 'n':
2497                       memmove(tmpwrite + len, tmpwrite + 2, strlen(tmpwrite + 2) + 1);
2498                       strncpy(tmpwrite, "\n", len);
2499                       break;
2500                case 't':
2501                       memmove(tmpwrite + len, tmpwrite + 2, strlen(tmpwrite + 2) + 1);
2502                       strncpy(tmpwrite, "\t", len);
2503                       break;
2504                default:
2505                       ast_log(LOG_NOTICE, "Substitution routine does not support this character: %c\n", tmpwrite[1]);
2506                }
2507                tmpread = tmpwrite + len;
2508         }
2509         return emailbody;       
2510 }
2511
2512 /*! \brief Apply general configuration options */
2513 static int apply_general_options(struct ast_variable *var)
2514 {
2515         int error = 0;
2516
2517         while (var) {
2518                 /* Mail command */
2519                 if (!strcmp(var->name, "mailcmd")) {
2520                         ast_copy_string(global_mailcmd, var->value, sizeof(global_mailcmd)); /* User setting */
2521                 } else if (!strcmp(var->name, "maxgreet")) {
2522                         global_maxgreet = atoi(var->value);
2523                 } else if (!strcmp(var->name, "maxsilence")) {
2524                         global_maxsilence = atoi(var->value);
2525                         if (global_maxsilence > 0)
2526                                 global_maxsilence *= 1000;
2527                 } else if (!strcmp(var->name, "logfile")) {
2528                         if (!ast_strlen_zero(var->value) ) {
2529                                 if(*(var->value) == '/')
2530                                         ast_copy_string(global_logfile, var->value, sizeof(global_logfile));
2531                                 else
2532                                         snprintf(global_logfile, sizeof(global_logfile), "%s/%s", ast_config_AST_LOG_DIR, var->value);
2533                         }
2534                 } else if (!strcmp(var->name, "externnotify")) {
2535                         /* External voicemail notify application */
2536                         ast_copy_string(global_externnotify, var->value, sizeof(global_externnotify));
2537                 } else if (!strcmp(var->name, "silencetreshold")) {
2538                         /* Silence treshold */
2539                         global_silencethreshold = atoi(var->value);
2540                 } else if (!strcmp(var->name, "maxmessage")) {
2541                         int x;
2542                         if (sscanf(var->value, "%d", &x) == 1) {
2543                                 global_vmmaxmessage = x;
2544                         } else {
2545                                 error ++;
2546                                 ast_log(LOG_WARNING, "Invalid max message time length\n");
2547                         }
2548                 } else if (!strcmp(var->name, "minmessage")) {
2549                         int x;
2550                         if (sscanf(var->value, "%d", &x) == 1) {
2551                                 global_vmminmessage = x;
2552                                 if (global_maxsilence <= global_vmminmessage)
2553                                         ast_log(LOG_WARNING, "maxsilence should be less than minmessage or you may get empty messages\n");
2554                         } else {
2555                                 error ++;
2556                                 ast_log(LOG_WARNING, "Invalid min message time length\n");
2557                         }
2558                 } else if (!strcmp(var->name, "format")) {
2559                         ast_copy_string(default_vmformat, var->value, sizeof(default_vmformat));
2560                 } else if (!strcmp(var->name, "review")) {
2561                         ast_set2_flag((&globalflags), ast_true(var->value), MVM_REVIEW);        
2562                 } else if (!strcmp(var->name, "operator")) {
2563                         ast_set2_flag((&globalflags), ast_true(var->value), MVM_OPERATOR);      
2564                 }
2565                 var = var->next;
2566         }
2567         return error;
2568 }
2569
2570 /*! \brief Load minivoicemail configuration */
2571 static int load_config(int reload)
2572 {
2573         struct ast_config *cfg;
2574         struct ast_variable *var;
2575         char *cat;
2576         const char *chanvar;
2577         int error = 0;
2578         struct minivm_template *template;
2579         struct ast_flags config_flags = { reload ? CONFIG_FLAG_FILEUNCHANGED : 0 };
2580
2581         cfg = ast_config_load(VOICEMAIL_CONFIG, config_flags);
2582         if (cfg == CONFIG_STATUS_FILEUNCHANGED) {
2583                 return 0;
2584         } else if (cfg == CONFIG_STATUS_FILEINVALID) {
2585                 ast_log(LOG_ERROR, "Config file " VOICEMAIL_CONFIG " is in an invalid format.  Aborting.\n");
2586                 return 0;
2587         }
2588
2589         ast_mutex_lock(&minivmlock);
2590
2591         /* Destroy lists to reconfigure */
2592         message_destroy_list();         /* Destroy list of voicemail message templates */
2593         timezone_destroy_list();        /* Destroy list of timezones */
2594         vmaccounts_destroy_list();      /* Destroy list of voicemail accounts */
2595         ast_debug(2, "Destroyed memory objects...\n");
2596
2597         /* First, set some default settings */
2598         global_externnotify[0] = '\0';
2599         global_logfile[0] = '\0';
2600         global_vmmaxmessage = 2000;
2601         global_maxgreet = 2000;
2602         global_vmminmessage = 0;
2603         strcpy(global_mailcmd, SENDMAIL);
2604         global_maxsilence = 0;
2605         global_saydurationminfo = 2;
2606         ast_copy_string(default_vmformat, "wav", sizeof(default_vmformat));
2607         ast_set2_flag((&globalflags), FALSE, MVM_REVIEW);       
2608         ast_set2_flag((&globalflags), FALSE, MVM_OPERATOR);     
2609         strcpy(global_charset, "ISO-8859-1");
2610         /* Reset statistics */
2611         memset(&global_stats, 0, sizeof(global_stats));
2612         global_stats.reset = ast_tvnow();
2613
2614         global_silencethreshold = ast_dsp_get_threshold_from_settings(THRESHOLD_SILENCE);
2615
2616         /* Make sure we could load configuration file */
2617         if (!cfg) {
2618                 ast_log(LOG_WARNING, "Failed to load configuration file. Module activated with default settings.\n");
2619                 ast_mutex_unlock(&minivmlock);
2620                 return 0;
2621         }
2622
2623         ast_debug(2, "-_-_- Loaded configuration file, now parsing\n");
2624
2625         /* General settings */
2626
2627         cat = ast_category_browse(cfg, NULL);
2628         while (cat) {
2629                 ast_debug(3, "-_-_- Found configuration section [%s]\n", cat);
2630                 if (!strcasecmp(cat, "general")) {
2631                         /* Nothing right now */
2632                         error += apply_general_options(ast_variable_browse(cfg, cat));
2633                 } else if (!strncasecmp(cat, "template-", 9))  {
2634                         /* Template */
2635                         char *name = cat + 9;
2636
2637                         /* Now build and link template to list */
2638                         error += message_template_build(name, ast_variable_browse(cfg, cat));
2639                 } else {
2640                         var = ast_variable_browse(cfg, cat);
2641                         if (!strcasecmp(cat, "zonemessages")) {
2642                                 /* Timezones in this context */
2643                                 while (var) {
2644                                         timezone_add(var->name, var->value);
2645                                         var = var->next;
2646                                 }
2647                         } else {
2648                                 /* Create mailbox from this */
2649                                 error += create_vmaccount(cat, var, FALSE);
2650                         }
2651                 }
2652                 /* Find next section in configuration file */
2653                 cat = ast_category_browse(cfg, cat);
2654         }
2655
2656         /* Configure the default email template */
2657         message_template_build("email-default", NULL);
2658         template = message_template_find("email-default");
2659
2660         /* Load date format config for voicemail mail */
2661         if ((chanvar = ast_variable_retrieve(cfg, "general", "emaildateformat"))) 
2662                 ast_copy_string(template->dateformat, chanvar, sizeof(template->dateformat));
2663         if ((chanvar = ast_variable_retrieve(cfg, "general", "emailfromstring")))
2664                 ast_copy_string(template->fromaddress, chanvar, sizeof(template->fromaddress));
2665         if ((chanvar = ast_variable_retrieve(cfg, "general", "emailaaddress")))
2666                 ast_copy_string(template->serveremail, chanvar, sizeof(template->serveremail));
2667         if ((chanvar = ast_variable_retrieve(cfg, "general", "emailcharset")))
2668                 ast_copy_string(template->charset, chanvar, sizeof(template->charset));
2669         if ((chanvar = ast_variable_retrieve(cfg, "general", "emailsubject"))) 
2670                 ast_copy_string(template->subject, chanvar, sizeof(template->subject));
2671         if ((chanvar = ast_variable_retrieve(cfg, "general", "emailbody"))) 
2672                 template->body = message_template_parse_emailbody(chanvar);
2673         template->attachment = TRUE;
2674
2675         message_template_build("pager-default", NULL);
2676         template = message_template_find("pager-default");
2677         if ((chanvar = ast_variable_retrieve(cfg, "general", "pagerfromstring")))
2678                 ast_copy_string(template->fromaddress, chanvar, sizeof(template->fromaddress));
2679         if ((chanvar = ast_variable_retrieve(cfg, "general", "pageraddress")))
2680                 ast_copy_string(template->serveremail, chanvar, sizeof(template->serveremail));
2681         if ((chanvar = ast_variable_retrieve(cfg, "general", "pagercharset")))
2682                 ast_copy_string(template->charset, chanvar, sizeof(template->charset));
2683         if ((chanvar = ast_variable_retrieve(cfg, "general", "pagersubject")))
2684                 ast_copy_string(template->subject, chanvar,sizeof(template->subject));
2685         if ((chanvar = ast_variable_retrieve(cfg, "general", "pagerbody"))) 
2686                 template->body = message_template_parse_emailbody(chanvar);
2687         template->attachment = FALSE;
2688
2689         if (error)
2690                 ast_log(LOG_ERROR, "--- A total of %d errors found in mini-voicemail configuration\n", error);
2691
2692         ast_mutex_unlock(&minivmlock);
2693         ast_config_destroy(cfg);
2694
2695         /* Close log file if it's open and disabled */
2696         if(minivmlogfile)
2697                 fclose(minivmlogfile);
2698
2699         /* Open log file if it's enabled */
2700         if(!ast_strlen_zero(global_logfile)) {
2701                 minivmlogfile = fopen(global_logfile, "a");
2702                 if(!minivmlogfile)
2703                         ast_log(LOG_ERROR, "Failed to open minivm log file %s : %s\n", global_logfile, strerror(errno));
2704                 if (minivmlogfile)
2705                         ast_debug(3, "-_-_- Opened log file %s \n", global_logfile);
2706         }
2707
2708         return 0;
2709 }
2710
2711 /*! \brief CLI routine for listing templates */
2712 static char *handle_minivm_list_templates(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
2713 {
2714         struct minivm_template *this;
2715 #define HVLT_OUTPUT_FORMAT "%-15s %-10s %-10s %-15.15s %-50s\n"
2716         int count = 0;
2717
2718         switch (cmd) {
2719         case CLI_INIT:
2720                 e->command = "minivm list templates";
2721                 e->usage =
2722                         "Usage: minivm list templates\n"
2723                         "       Lists message templates for e-mail, paging and IM\n";
2724                 return NULL;
2725         case CLI_GENERATE:
2726                 return NULL;
2727         }
2728
2729         if (a->argc > 3)
2730                 return CLI_SHOWUSAGE;
2731
2732         AST_LIST_LOCK(&message_templates);
2733         if (AST_LIST_EMPTY(&message_templates)) {
2734                 ast_cli(a->fd, "There are no message templates defined\n");
2735                 AST_LIST_UNLOCK(&message_templates);
2736                 return CLI_FAILURE;
2737         }
2738         ast_cli(a->fd, HVLT_OUTPUT_FORMAT, "Template name", "Charset", "Locale", "Attach media", "Subject");
2739         ast_cli(a->fd, HVLT_OUTPUT_FORMAT, "-------------", "-------", "------", "------------", "-------");
2740         AST_LIST_TRAVERSE(&message_templates, this, list) {
2741                 ast_cli(a->fd, HVLT_OUTPUT_FORMAT, this->name, 
2742                         this->charset ? this->charset : "-", 
2743                         this->locale ? this->locale : "-",
2744                         this->attachment ? "Yes" : "No",
2745                         this->subject ? this->subject : "-");
2746                 count++;
2747         }
2748         AST_LIST_UNLOCK(&message_templates);
2749         ast_cli(a->fd, "\n * Total: %d minivoicemail message templates\n", count);
2750         return CLI_SUCCESS;
2751 }
2752
2753 static char *complete_minivm_show_users(const char *line, const char *word, int pos, int state)
2754 {
2755         int which = 0;
2756         int wordlen;
2757         struct minivm_account *vmu;
2758         const char *domain = "";
2759
2760         /* 0 - voicemail; 1 - list; 2 - accounts; 3 - for; 4 - <domain> */
2761         if (pos > 4)
2762                 return NULL;
2763         if (pos == 3)
2764                 return (state == 0) ? ast_strdup("for") : NULL;
2765         wordlen = strlen(word);
2766         AST_LIST_TRAVERSE(&minivm_accounts, vmu, list) {
2767                 if (!strncasecmp(word, vmu->domain, wordlen)) {
2768                         if (domain && strcmp(domain, vmu->domain) && ++which > state)
2769                                 return ast_strdup(vmu->domain);
2770                         /* ignore repeated domains ? */
2771                         domain = vmu->domain;
2772                 }
2773         }
2774         return NULL;
2775 }
2776
2777 /*! \brief CLI command to list voicemail accounts */
2778 static char *handle_minivm_show_users(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
2779 {
2780         struct minivm_account *vmu;
2781 #define HMSU_OUTPUT_FORMAT "%-23s %-15s %-15s %-10s %-10s %-50s\n"
2782         int count = 0;
2783
2784         switch (cmd) {
2785         case CLI_INIT:
2786                 e->command = "minivm list accounts";
2787                 e->usage =
2788                         "Usage: minivm list accounts\n"
2789                         "       Lists all mailboxes currently set up\n";
2790                 return NULL;
2791         case CLI_GENERATE:
2792                 return complete_minivm_show_users(a->line, a->word, a->pos, a->n);
2793         }
2794
2795         if ((a->argc < 3) || (a->argc > 5) || (a->argc == 4))
2796                 return CLI_SHOWUSAGE;
2797         if ((a->argc == 5) && strcmp(a->argv[3],"for"))
2798                 return CLI_SHOWUSAGE;
2799
2800         AST_LIST_LOCK(&minivm_accounts);
2801         if (AST_LIST_EMPTY(&minivm_accounts)) {
2802                 ast_cli(a->fd, "There are no voicemail users currently defined\n");
2803                 AST_LIST_UNLOCK(&minivm_accounts);
2804                 return CLI_FAILURE;
2805         }
2806         ast_cli(a->fd, HMSU_OUTPUT_FORMAT, "User", "E-Template", "P-template", "Zone", "Format", "Full name");
2807         ast_cli(a->fd, HMSU_OUTPUT_FORMAT, "----", "----------", "----------", "----", "------", "---------");
2808         AST_LIST_TRAVERSE(&minivm_accounts, vmu, list) {
2809                 char tmp[256] = "";
2810                 if ((a->argc == 3) || ((a->argc == 5) && !strcmp(a->argv[4], vmu->domain))) {
2811                         count++;
2812                         snprintf(tmp, sizeof(tmp), "%s@%s", vmu->username, vmu->domain);
2813                         ast_cli(a->fd, HMSU_OUTPUT_FORMAT, tmp, vmu->etemplate ? vmu->etemplate : "-", 
2814                                 vmu->ptemplate ? vmu->ptemplate : "-",
2815                                 vmu->zonetag ? vmu->zonetag : "-", 
2816                                 vmu->attachfmt ? vmu->attachfmt : "-",
2817                                 vmu->fullname);
2818                 }
2819         }
2820         AST_LIST_UNLOCK(&minivm_accounts);
2821         ast_cli(a->fd, "\n * Total: %d minivoicemail accounts\n", count);
2822         return CLI_SUCCESS;
2823 }
2824
2825 /*! \brief Show a list of voicemail zones in the CLI */
2826 static char *handle_minivm_show_zones(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
2827 {
2828         struct minivm_zone *zone;
2829 #define HMSZ_OUTPUT_FORMAT "%-15s %-20s %-45s\n"
2830         char *res = CLI_SUCCESS;
2831
2832         switch (cmd) {
2833         case CLI_INIT:
2834                 e->command = "minivm list zones";
2835                 e->usage =
2836                         "Usage: minivm list zones\n"
2837                         "       Lists zone message formats\n";
2838                 return NULL;
2839         case CLI_GENERATE:
2840                 return NULL;
2841         }
2842
2843         if (a->argc != e->args)
2844                 return CLI_SHOWUSAGE;
2845
2846         AST_LIST_LOCK(&minivm_zones);
2847         if (!AST_LIST_EMPTY(&minivm_zones)) {
2848                 ast_cli(a->fd, HMSZ_OUTPUT_FORMAT, "Zone", "Timezone", "Message Format");
2849                 ast_cli(a->fd, HMSZ_OUTPUT_FORMAT, "----", "--------", "--------------");
2850                 AST_LIST_TRAVERSE(&minivm_zones, zone, list) {
2851                         ast_cli(a->fd, HMSZ_OUTPUT_FORMAT, zone->name, zone->timezone, zone->msg_format);
2852                 }
2853         } else {
2854                 ast_cli(a->fd, "There are no voicemail zones currently defined\n");
2855                 res = CLI_FAILURE;
2856         }
2857         AST_LIST_UNLOCK(&minivm_zones);
2858
2859         return res;
2860 }
2861
2862 /*! \brief CLI Show settings */
2863 static char *handle_minivm_show_settings(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
2864 {
2865         switch (cmd) {
2866         case CLI_INIT:
2867                 e->command = "minivm show settings";
2868                 e->usage =
2869                         "Usage: minivm show settings\n"
2870                         "       Display Mini-Voicemail general settings\n";
2871                 return NULL;
2872         case CLI_GENERATE:
2873                 return NULL;
2874         }
2875
2876         ast_cli(a->fd, "* Mini-Voicemail general settings\n");
2877         ast_cli(a->fd, "  -------------------------------\n");
2878         ast_cli(a->fd, "\n");
2879         ast_cli(a->fd, "  Mail command (shell):               %s\n", global_mailcmd);
2880         ast_cli(a->fd, "  Max silence:                        %d\n", global_maxsilence);
2881         ast_cli(a->fd, "  Silence threshold:                  %d\n", global_silencethreshold);
2882         ast_cli(a->fd, "  Max message length (secs):          %d\n", global_vmmaxmessage);
2883         ast_cli(a->fd, "  Min message length (secs):          %d\n", global_vmminmessage);
2884         ast_cli(a->fd, "  Default format:                     %s\n", default_vmformat);
2885         ast_cli(a->fd, "  Extern notify (shell):              %s\n", global_externnotify);
2886         ast_cli(a->fd, "  Logfile:                            %s\n", global_logfile[0] ? global_logfile : "<disabled>");
2887         ast_cli(a->fd, "  Operator exit:                      %s\n", ast_test_flag(&globalflags, MVM_OPERATOR) ? "Yes" : "No");
2888         ast_cli(a->fd, "  Message review:                     %s\n", ast_test_flag(&globalflags, MVM_REVIEW) ? "Yes" : "No");
2889
2890         ast_cli(a->fd, "\n");
2891         return CLI_SUCCESS;
2892 }
2893
2894 /*! \brief Show stats */
2895 static char *handle_minivm_show_stats(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
2896 {
2897         struct ast_tm timebuf;
2898         char buf[BUFSIZ];
2899
2900         switch (cmd) {
2901         
2902         case CLI_INIT:
2903                 e->command = "minivm show stats";
2904                 e->usage =
2905                         "Usage: minivm show stats\n"
2906                         "       Display Mini-Voicemail counters\n";
2907                 return NULL;
2908         case CLI_GENERATE:
2909                 return NULL;
2910         }
2911
2912         ast_cli(a->fd, "* Mini-Voicemail statistics\n");
2913         ast_cli(a->fd, "  -------------------------\n");
2914         ast_cli(a->fd, "\n");
2915         ast_cli(a->fd, "  Voicemail accounts:                  %5d\n", global_stats.voicemailaccounts);
2916         ast_cli(a->fd, "  Templates:                           %5d\n", global_stats.templates);
2917         ast_cli(a->fd, "  Timezones:                           %5d\n", global_stats.timezones);
2918         if (global_stats.receivedmessages == 0) {
2919                 ast_cli(a->fd, "  Received messages since last reset:  <none>\n");
2920         } else {
2921                 ast_cli(a->fd, "  Received messages since last reset:  %d\n", global_stats.receivedmessages);
2922                 ast_localtime(&global_stats.lastreceived, &timebuf, NULL);
2923                 ast_strftime(buf, sizeof(buf), "%a %b %e %r %Z %Y", &timebuf);
2924                 ast_cli(a->fd, "  Last received voicemail:             %s\n", buf);
2925         }
2926         ast_localtime(&global_stats.reset, &timebuf, NULL);
2927         ast_strftime(buf, sizeof(buf), "%a %b %e %r %Z %Y", &timebuf);
2928         ast_cli(a->fd, "  Last reset:                          %s\n", buf);
2929
2930         ast_cli(a->fd, "\n");
2931         return CLI_SUCCESS;
2932 }
2933
2934 /*! \brief  ${MINIVMACCOUNT()} Dialplan function - reads account data */
2935 static int minivm_account_func_read(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len)
2936 {
2937         struct minivm_account *vmu;
2938         char *username, *domain, *colname;
2939
2940         if (!(username = ast_strdupa(data))) {
2941                 ast_log(LOG_ERROR, "Memory Error!\n");
2942                 return -1;
2943         }
2944
2945         if ((colname = strchr(username, ':'))) {
2946                 *colname = '\0';
2947                 colname++;
2948         } else {
2949                 colname = "path";
2950         }
2951         if ((domain = strchr(username, '@'))) {
2952                 *domain = '\0';
2953                 domain++;
2954         }
2955         if (ast_strlen_zero(username) || ast_strlen_zero(domain)) {
2956                 ast_log(LOG_ERROR, "This function needs a username and a domain: username@domain\n");
2957                 return 0;
2958         }
2959
2960         if (!(vmu = find_account(domain, username, TRUE)))
2961                 return 0;
2962
2963         if (!strcasecmp(colname, "hasaccount")) {
2964                 ast_copy_string(buf, (ast_test_flag(vmu, MVM_ALLOCED) ? "0" : "1"), len);
2965         } else  if (!strcasecmp(colname, "fullname")) { 
2966                 ast_copy_string(buf, vmu->fullname, len);
2967         } else  if (!strcasecmp(colname, "email")) { 
2968                 if (!ast_strlen_zero(vmu->email))
2969                         ast_copy_string(buf, vmu->email, len);
2970                 else
2971                         snprintf(buf, len, "%s@%s", vmu->username, vmu->domain);
2972         } else  if (!strcasecmp(colname, "pager")) { 
2973                 ast_copy_string(buf, vmu->pager, len);
2974         } else  if (!strcasecmp(colname, "etemplate")) { 
2975                 if (!ast_strlen_zero(vmu->etemplate))
2976                         ast_copy_string(buf, vmu->etemplate, len);
2977                 else
2978                         ast_copy_string(buf, "email-default", len);
2979         } else  if (!strcasecmp(colname, "language")) { 
2980                 ast_copy_string(buf, vmu->language, len);
2981         } else  if (!strcasecmp(colname, "timezone")) { 
2982                 ast_copy_string(buf, vmu->zonetag, len);
2983         } else  if (!strcasecmp(colname, "ptemplate")) { 
2984                 if (!ast_strlen_zero(vmu->ptemplate))
2985                         ast_copy_string(buf, vmu->ptemplate, len);
2986                 else
2987                         ast_copy_string(buf, "email-default", len);
2988         } else  if (!strcasecmp(colname, "accountcode")) {
2989                 ast_copy_string(buf, vmu->accountcode, len);
2990         } else  if (!strcasecmp(colname, "pincode")) {
2991                 ast_copy_string(buf, vmu->pincode, len);
2992         } else  if (!strcasecmp(colname, "path")) {
2993                 check_dirpath(buf, len, vmu->domain, vmu->username, NULL);
2994         } else {        /* Look in channel variables */
2995                 struct ast_variable *var;
2996                 int found = 0;
2997
2998                 for (var = vmu->chanvars ; var ; var = var->next)
2999                         if (!strcmp(var->name, colname)) {
3000                                 ast_copy_string(buf, var->value, len);
3001                                 found = 1;
3002                                 break;
3003                         }
3004         }
3005
3006         if(ast_test_flag(vmu, MVM_ALLOCED))
3007                 free_user(vmu);
3008
3009         return 0;
3010 }
3011
3012 /*! \brief lock directory
3013
3014    only return failure if ast_lock_path returns 'timeout',
3015    not if the path does not exist or any other reason
3016 */
3017 static int vm_lock_path(const char *path)
3018 {
3019         switch (ast_lock_path(path)) {
3020         case AST_LOCK_TIMEOUT:
3021                 return -1;
3022         default:
3023                 return 0;
3024         }
3025 }
3026
3027 /*! \brief Access counter file, lock directory, read and possibly write it again changed 
3028         \param directory        Directory to crate file in
3029         \param countername      filename 
3030         \param value            If set to zero, we only read the variable
3031         \param operand          0 to read, 1 to set new value, 2 to change 
3032         \return -1 on error, otherwise counter value
3033 */
3034 static int access_counter_file(char *directory, char *countername, int value, int operand)
3035 {
3036         char filename[BUFSIZ];
3037         char readbuf[BUFSIZ];
3038         FILE *counterfile;
3039         int old = 0, counter = 0;
3040
3041         /* Lock directory */
3042         if (vm_lock_path(directory)) {
3043                 return -1;      /* Could not lock directory */
3044         }
3045         snprintf(filename, sizeof(filename), "%s/%s.counter", directory, countername);
3046         if (operand != 1) {
3047                 counterfile = fopen(filename, "r");
3048                 if (counterfile) {
3049                         if(fgets(readbuf, sizeof(readbuf), counterfile)) {
3050                                 ast_debug(3, "Read this string from counter file: %s\n", readbuf);
3051                                 old = counter = atoi(readbuf);
3052                         }
3053                         fclose(counterfile);
3054                 }
3055         }
3056         switch (operand) {
3057         case 0: /* Read only */
3058                 ast_unlock_path(directory);
3059                 ast_debug(2, "MINIVM Counter %s/%s: Value %d\n", directory, countername, counter);
3060                 return counter;
3061                 break;
3062         case 1: /* Set new value */
3063                 counter = value;
3064                 break;
3065         case 2: /* Change value */
3066                 counter += value;
3067                 if (counter < 0)        /* Don't allow counters to fall below zero */
3068                         counter = 0;
3069                 break;
3070         }
3071         
3072         /* Now, write the new value to the file */
3073         counterfile = fopen(filename, "w");
3074         if (!counterfile) {
3075                 ast_log(LOG_ERROR, "Could not open counter file for writing : %s - %s\n", filename, strerror(errno));
3076                 ast_unlock_path(directory);
3077                 return -1;      /* Could not open file for writing */
3078         }
3079         fprintf(counterfile, "%d\n\n", counter);
3080         fclose(counterfile);
3081         ast_unlock_path(directory);
3082         ast_debug(2, "MINIVM Counter %s/%s: Old value %d New value %d\n", directory, countername, old, counter);
3083         return counter;
3084 }
3085
3086 /*! \brief  ${MINIVMCOUNTER()} Dialplan function - read counters */
3087 static int minivm_counter_func_read(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len)
3088 {
3089         char *username, *domain, *countername;
3090         struct minivm_account *vmu = NULL;
3091         char userpath[BUFSIZ];
3092         int res;
3093
3094         *buf = '\0';
3095
3096         if (!(username = ast_strdupa(data))) {  /* Copy indata to local buffer */
3097                 ast_log(LOG_WARNING, "Memory error!\n");
3098                 return -1;
3099         }
3100         if ((countername = strchr(username, ':'))) {
3101                 *countername = '\0';
3102                 countername++;
3103         } 
3104
3105         if ((domain = strchr(username, '@'))) {
3106                 *domain = '\0';
3107                 domain++;
3108         }
3109
3110         /* If we have neither username nor domain now, let's give up */
3111         if (ast_strlen_zero(username) && ast_strlen_zero(domain)) {
3112                 ast_log(LOG_ERROR, "No account given\n");
3113                 return -1;
3114         }
3115
3116         if (ast_strlen_zero(countername)) {
3117                 ast_log(LOG_ERROR, "This function needs two arguments: Account:countername\n");
3118                 return -1;
3119         }
3120
3121         /* We only have a domain, no username */
3122         if (!ast_strlen_zero(username) && ast_strlen_zero(domain)) {
3123                 domain = username;
3124                 username = NULL;
3125         }
3126
3127         /* If we can't find account or if the account is temporary, return. */
3128         if (!ast_strlen_zero(username) && !(vmu = find_account(domain, username, FALSE))) {
3129                 ast_log(LOG_ERROR, "Minivm account does not exist: %s@%s\n", username, domain);
3130                 return 0;
3131         }
3132
3133         create_dirpath(userpath, sizeof(userpath), domain, username, NULL);
3134
3135         /* We have the path, now read the counter file */
3136         res = access_counter_file(userpath, countername, 0, 0);
3137         if (res >= 0)
3138                 snprintf(buf, len, "%d", res);
3139         return 0;
3140 }
3141
3142 /*! \brief  ${MINIVMCOUNTER()} Dialplan function - changes counter data */
3143 static int minivm_counter_func_write(struct ast_channel *chan, const char *cmd, char *data, const char *value)
3144 {
3145         char *username, *domain, *countername, *operand;
3146         char userpath[BUFSIZ];
3147         struct minivm_account *vmu;
3148         int change = 0;
3149         int operation = 0;
3150
3151         if(!value)
3152                 return -1;
3153         change = atoi(value);
3154
3155         if (!(username = ast_strdupa(data))) {  /* Copy indata to local buffer */
3156                 ast_log(LOG_WARNING, "Memory error!\n");
3157                 return -1;
3158         }
3159
3160         if ((countername = strchr(username, ':'))) {
3161                 *countername = '\0';
3162                 countername++;
3163         } 
3164         if ((operand = strchr(countername, ':'))) {
3165                 *operand = '\0';
3166                 operand++;
3167         } 
3168
3169         if ((domain = strchr(username, '@'))) {
3170                 *domain = '\0';
3171                 domain++;
3172         }
3173
3174         /* If we have neither username nor domain now, let's give up */
3175         if (ast_strlen_zero(username) && ast_strlen_zero(domain)) {
3176                 ast_log(LOG_ERROR, "No account given\n");
3177                 return -1;
3178         }
3179
3180         /* We only have a domain, no username */
3181         if (!ast_strlen_zero(username) && ast_strlen_zero(domain)) {
3182                 domain = username;
3183                 username = NULL;
3184         }
3185
3186         if (ast_strlen_zero(operand) || ast_strlen_zero(countername)) {
3187                 ast_log(LOG_ERROR, "Writing to this function requires three arguments: Account:countername:operand\n");
3188                 return -1;
3189         }
3190
3191         /* If we can't find account or if the account is temporary, return. */
3192         if (!ast_strlen_zero(username) && !(vmu = find_account(domain, username, FALSE))) {
3193                 ast_log(LOG_ERROR, "Minivm account does not exist: %s@%s\n", username, domain);
3194                 return 0;
3195         }
3196
3197         create_dirpath(userpath, sizeof(userpath), domain, username, NULL);
3198         /* Now, find out our operator */
3199         if (*operand == 'i') /* Increment */
3200                 operation = 2;
3201         else if (*operand == 'd') {
3202                 change = change * -1;
3203                 operation = 2;
3204         } else if (*operand == 's')
3205                 operation = 1;
3206         else {
3207                 ast_log(LOG_ERROR, "Unknown operator: %s\n", operand);
3208                 return -1;
3209         }
3210
3211         /* We have the path, now read the counter file */
3212         access_counter_file(userpath, countername, change, operation);
3213         return 0;
3214 }
3215
3216
3217 /*! \brief CLI commands for Mini-voicemail */
3218 static struct ast_cli_entry cli_minivm[] = {
3219         AST_CLI_DEFINE(handle_minivm_show_users, "List defined mini-voicemail boxes"),
3220         AST_CLI_DEFINE(handle_minivm_show_zones, "List zone message formats"),
3221         AST_CLI_DEFINE(handle_minivm_list_templates, "List message templates"), 
3222         AST_CLI_DEFINE(handle_minivm_reload, "Reload Mini-voicemail configuration"),
3223         AST_CLI_DEFINE(handle_minivm_show_stats, "Show some mini-voicemail statistics"),
3224         AST_CLI_DEFINE(handle_minivm_show_settings, "Show mini-voicemail general settings"),
3225 };
3226
3227 static struct ast_custom_function minivm_counter_function = {
3228         .name = "MINIVMCOUNTER",
3229         .synopsis = "Reads or sets counters for MiniVoicemail message",
3230         .syntax = "MINIVMCOUNTER(<account>:name[:operand])",
3231         .read = minivm_counter_func_read,
3232         .write = minivm_counter_func_write,
3233         .desc = "Valid operands for changing the value of a counter when assigning a value are:\n"
3234         "- i   Increment by value\n"
3235         "- d   Decrement by value\n"
3236         "- s   Set to value\n"
3237         "\nThe counters never goes below zero.\n"
3238         "- The name of the counter is a string, up to 10 characters\n"
3239         "- If account is given and it exists, the counter is specific for the account\n"
3240         "- If account is a domain and the domain directory exists, counters are specific for a domain\n"
3241         "The operation is atomic and the counter is locked while changing the value\n"
3242         "\nThe counters are stored as text files in the minivm account directories. It might be better to use\n"
3243         "realtime functions if you are using a database to operate your Asterisk\n",
3244 };
3245
3246 static struct ast_custom_function minivm_account_function = {
3247         .name = "MINIVMACCOUNT",
3248         .synopsis = "Gets MiniVoicemail account information",
3249         .syntax = "MINIVMACCOUNT(<account>:item)",
3250         .read = minivm_account_func_read,
3251         .desc = "Valid items are:\n"
3252         "- path           Path to account mailbox (if account exists, otherwise temporary mailbox)\n"
3253         "- hasaccount     1 if static Minivm account exists, 0 otherwise\n"
3254         "- fullname       Full name of account owner\n"
3255         "- email          Email address used for account\n"
3256         "- etemplate      E-mail template for account (default template if none is configured)\n"   
3257         "- ptemplate      Pager template for account (default template if none is configured)\n"   
3258         "- accountcode    Account code for voicemail account\n"
3259         "- pincode        Pin code for voicemail account\n"
3260         "- timezone       Time zone for voicemail account\n"
3261         "- language       Language for voicemail account\n"
3262         "- <channel variable name> Channel variable value (set in configuration for account)\n"
3263         "\n",
3264 };
3265
3266 /*! \brief Load mini voicemail module */
3267 static int load_module(void)
3268 {
3269         int res;
3270
3271         res = ast_register_application_xml(app_minivm_record, minivm_record_exec);
3272         res = ast_register_application_xml(app_minivm_greet, minivm_greet_exec);
3273         res = ast_register_application_xml(app_minivm_notify, minivm_notify_exec);
3274         res = ast_register_application_xml(app_minivm_delete, minivm_delete_exec);
3275         res = ast_register_application_xml(app_minivm_accmess, minivm_accmess_exec);
3276         res = ast_register_application_xml(app_minivm_mwi, minivm_mwi_exec);
3277
3278         ast_custom_function_register(&minivm_account_function);
3279         ast_custom_function_register(&minivm_counter_function);
3280         if (res)
3281                 return(res);
3282
3283         if ((res = load_config(0)))
3284                 return(res);
3285
3286         ast_cli_register_multiple(cli_minivm, ARRAY_LEN(cli_minivm));
3287
3288         /* compute the location of the voicemail spool directory */
3289         snprintf(MVM_SPOOL_DIR, sizeof(MVM_SPOOL_DIR), "%s/voicemail/", ast_config_AST_SPOOL_DIR);
3290
3291         return res;
3292 }
3293
3294 /*! \brief Reload mini voicemail module */
3295 static int reload(void)
3296 {
3297         return(load_config(1));
3298 }
3299
3300 /*! \brief Reload cofiguration */
3301 static char *handle_minivm_reload(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
3302 {
3303         
3304         switch (cmd) {
3305         case CLI_INIT:
3306                 e->command = "minivm reload";
3307                 e->usage =
3308                         "Usage: minivm reload\n"
3309                         "       Reload mini-voicemail configuration and reset statistics\n";
3310                 return NULL;
3311         case CLI_GENERATE:
3312                 return NULL;
3313         }
3314         
3315         reload();
3316         ast_cli(a->fd, "\n-- Mini voicemail re-configured \n");
3317         return CLI_SUCCESS;
3318 }
3319
3320 /*! \brief Unload mini voicemail module */
3321 static int unload_module(void)
3322 {
3323         int res;
3324         
3325         res = ast_unregister_application(app_minivm_record);
3326         res |= ast_unregister_application(app_minivm_greet);
3327         res |= ast_unregister_application(app_minivm_notify);
3328         res |= ast_unregister_application(app_minivm_delete);
3329         res |= ast_unregister_application(app_minivm_accmess);
3330         ast_cli_unregister_multiple(cli_minivm, ARRAY_LEN(cli_minivm));
3331         ast_custom_function_unregister(&minivm_account_function);
3332         ast_custom_function_unregister(&minivm_counter_function);
3333
3334         message_destroy_list();         /* Destroy list of voicemail message templates */
3335         timezone_destroy_list();        /* Destroy list of timezones */
3336         vmaccounts_destroy_list();      /* Destroy list of voicemail accounts */
3337
3338         return res;
3339 }
3340
3341
3342 AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_DEFAULT, "Mini VoiceMail (A minimal Voicemail e-mail System)",
3343                 .load = load_module,
3344                 .unload = unload_module,
3345                 .reload = reload,
3346                 );