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