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