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