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