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