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