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