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