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