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