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