Completely remove all of the code related to jumping to priority n + 101. yay!
[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(*template));
474         if (!template)
475                 return NULL;
476
477         /* Set some defaults for templates */
478         ast_copy_string(template->name, name, sizeof(template->name));
479         ast_copy_string(template->dateformat, DEFAULT_DATEFORMAT, sizeof(template->dateformat));
480         ast_copy_string(template->charset, DEFAULT_CHARSET, sizeof(template->charset));
481         ast_copy_string(template->subject, "New message in mailbox ${MVM_USERNAME}@${MVM_DOMAIN}", sizeof(template->subject));
482         template->attachment = TRUE;
483
484         return template;
485 }
486
487 /*! \brief Release memory allocated by message template */
488 static void message_template_free(struct minivm_template *template)
489 {
490         if (template->body)
491                 ast_free(template->body);
492
493         ast_free (template);
494 }
495
496 /*! \brief Build message template from configuration */
497 static int message_template_build(const char *name, struct ast_variable *var)
498 {
499         struct minivm_template *template;
500         int error = 0;
501
502         template = message_template_create(name);
503         if (!template) {
504                 ast_log(LOG_ERROR, "Out of memory, can't allocate message template object %s.\n", name);
505                 return -1;
506         }
507
508         while (var) {
509                 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                                 ast_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                                 ast_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         ast_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 = ast_calloc(1, sizeof(*new));
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                 ast_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                 ast_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 len    Int. Length base directory string.
1150  * \param domain String. Ignored if is null or empty string.
1151  * \param username String. Ignored if is null or empty string. 
1152  * \param folder  String. Ignored if is null or empty string.
1153  * \return 0 on failure, 1 on success.
1154  */
1155 static int check_dirpath(char *dest, int len, char *domain, char *username, char *folder)
1156 {
1157         struct stat filestat;
1158         make_dir(dest, len, domain, username, folder ? folder : "");
1159         if (stat(dest, &filestat)== -1)
1160                 return FALSE;
1161         else
1162                 return TRUE;
1163 }
1164
1165 /*! \brief basically mkdir -p $dest/$domain/$username/$folder
1166  * \param dest    String. base directory.
1167  * \param len     Length of directory string
1168  * \param domain  String. Ignored if is null or empty string.
1169  * \param folder  String. Ignored if is null or empty string. 
1170  * \param username  String. Ignored if is null or empty string.
1171  * \return -1 on failure, 0 on success.
1172  */
1173 static int create_dirpath(char *dest, int len, char *domain, char *username, char *folder)
1174 {
1175         mode_t  mode = VOICEMAIL_DIR_MODE;
1176
1177         if(!ast_strlen_zero(domain)) {
1178                 make_dir(dest, len, domain, "", "");
1179                 if(mkdir(dest, mode) && errno != EEXIST) {
1180                         ast_log(LOG_WARNING, "mkdir '%s' failed: %s\n", dest, strerror(errno));
1181                         return -1;
1182                 }
1183         }
1184         if(!ast_strlen_zero(username)) {
1185                 make_dir(dest, len, domain, username, "");
1186                 if(mkdir(dest, mode) && errno != EEXIST) {
1187                         ast_log(LOG_WARNING, "mkdir '%s' failed: %s\n", dest, strerror(errno));
1188                         return -1;
1189                 }
1190         }
1191         if(!ast_strlen_zero(folder)) {
1192                 make_dir(dest, len, domain, username, folder);
1193                 if(mkdir(dest, mode) && errno != EEXIST) {
1194                         ast_log(LOG_WARNING, "mkdir '%s' failed: %s\n", dest, strerror(errno));
1195                         return -1;
1196                 }
1197         }
1198         if (option_debug > 1)
1199                 ast_log(LOG_DEBUG, "Creating directory for %s@%s folder %s : %s\n", username, domain, folder, dest);
1200         return 0;
1201 }
1202
1203
1204 /*! \brief Play intro message before recording voicemail 
1205 */
1206 static int invent_message(struct ast_channel *chan, char *domain, char *username, int busy, char *ecodes)
1207 {
1208         int res;
1209         char fn[PATH_MAX];
1210
1211         if (option_debug > 1)
1212                 ast_log(LOG_DEBUG, "-_-_- Still preparing to play message ...\n");
1213
1214         snprintf(fn, sizeof(fn), "%s%s/%s/greet", MVM_SPOOL_DIR, domain, username);
1215
1216         if (ast_fileexists(fn, NULL, NULL) > 0) {
1217                 res = ast_streamfile(chan, fn, chan->language);
1218                 if (res) 
1219                         return -1;
1220                 res = ast_waitstream(chan, ecodes);
1221                 if (res) 
1222                         return res;
1223         } else {
1224                 int numericusername = 1;
1225                 char *i = username;
1226
1227                 if (option_debug > 1)
1228                         ast_log(LOG_DEBUG, "-_-_- No personal prompts. Using default prompt set for language\n");
1229                 
1230                 while (*i)  {
1231                         if (option_debug > 1)
1232                                 ast_log(LOG_DEBUG, "-_-_- Numeric? Checking %c\n", *i);
1233                         if (!isdigit(*i)) {
1234                                 numericusername = FALSE;
1235                                 break;
1236                         }
1237                         i++;
1238                 }
1239
1240                 if (numericusername) {
1241                         if(ast_streamfile(chan, "vm-theperson", chan->language))
1242                                 return -1;
1243                         if ((res = ast_waitstream(chan, ecodes)))
1244                                 return res;
1245         
1246                         res = ast_say_digit_str(chan, username, ecodes, chan->language);
1247                         if (res)
1248                                 return res;
1249                 } else {
1250                         if(ast_streamfile(chan, "vm-theextensionis", chan->language))
1251                                 return -1;
1252                         if ((res = ast_waitstream(chan, ecodes)))
1253                                 return res;
1254                 }
1255         }
1256
1257         res = ast_streamfile(chan, busy ? "vm-isonphone" : "vm-isunavail", chan->language);
1258         if (res)
1259                 return -1;
1260         res = ast_waitstream(chan, ecodes);
1261         return res;
1262 }
1263
1264 /*! \brief Delete media files and attribute file */
1265 static int vm_delete(char *file)
1266 {
1267         int res;
1268
1269         if (option_debug)
1270                 ast_log(LOG_DEBUG, "-_-_- Deleting voicemail file %s\n", file);
1271
1272         res = unlink(file);     /* Remove the meta data file */
1273         res |=  ast_filedelete(file, NULL);     /* remove the media file */
1274         return res;
1275 }
1276
1277
1278 /*! \brief Record voicemail message & let caller review or re-record it, or set options if applicable */
1279 static int play_record_review(struct ast_channel *chan, char *playfile, char *recordfile, int maxtime, char *fmt,
1280                               int outsidecaller, struct minivm_account *vmu, int *duration, const char *unlockdir,
1281                               signed char record_gain)
1282 {
1283         int cmd = 0;
1284         int max_attempts = 3;
1285         int attempts = 0;
1286         int recorded = 0;
1287         int message_exists = 0;
1288         signed char zero_gain = 0;
1289         char *acceptdtmf = "#";
1290         char *canceldtmf = "";
1291
1292         /* Note that urgent and private are for flagging messages as such in the future */
1293  
1294         /* barf if no pointer passed to store duration in */
1295         if (duration == NULL) {
1296                 ast_log(LOG_WARNING, "Error play_record_review called without duration pointer\n");
1297                 return -1;
1298         }
1299
1300         cmd = '3';       /* Want to start by recording */
1301  
1302         while ((cmd >= 0) && (cmd != 't')) {
1303                 switch (cmd) {
1304                 case '2':
1305                         /* Review */
1306                         if (option_verbose > 2)
1307                                 ast_verbose(VERBOSE_PREFIX_3 "Reviewing the message\n");
1308                         ast_streamfile(chan, recordfile, chan->language);
1309                         cmd = ast_waitstream(chan, AST_DIGIT_ANY);
1310                         break;
1311                 case '3':
1312                         message_exists = 0;
1313                         /* Record */
1314                         if (option_verbose > 2) {
1315                                 if (recorded == 1) 
1316                                         ast_verbose(VERBOSE_PREFIX_3 "Re-recording the message\n");
1317                                 else
1318                                         ast_verbose(VERBOSE_PREFIX_3 "Recording the message\n");
1319                         }
1320                         if (recorded && outsidecaller) 
1321                                 cmd = ast_play_and_wait(chan, "beep");
1322                         recorded = 1;
1323                         /* 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 */
1324                         if (record_gain)
1325                                 ast_channel_setoption(chan, AST_OPTION_RXGAIN, &record_gain, sizeof(record_gain), 0);
1326                         if (ast_test_flag(vmu, MVM_OPERATOR))
1327                                 canceldtmf = "0";
1328                         cmd = ast_play_and_record_full(chan, playfile, recordfile, maxtime, fmt, duration, global_silencethreshold, global_maxsilence, unlockdir, acceptdtmf, canceldtmf);
1329                         if (record_gain)
1330                                 ast_channel_setoption(chan, AST_OPTION_RXGAIN, &zero_gain, sizeof(zero_gain), 0);
1331                         if (cmd == -1) /* User has hung up, no options to give */
1332                                 return cmd;
1333                         if (cmd == '0')
1334                                 break;
1335                         else if (cmd == '*')
1336                                 break;
1337                         else {
1338                                 /* If all is well, a message exists */
1339                                 message_exists = 1;
1340                                 cmd = 0;
1341                         }
1342                         break;
1343                 case '4':
1344                 case '5':
1345                 case '6':
1346                 case '7':
1347                 case '8':
1348                 case '9':
1349                 case '*':
1350                 case '#':
1351                         cmd = ast_play_and_wait(chan, "vm-sorry");
1352                         break;
1353                 case '0':
1354                         if(!ast_test_flag(vmu, MVM_OPERATOR)) {
1355                                 cmd = ast_play_and_wait(chan, "vm-sorry");
1356                                 break;
1357                         }
1358                         if (message_exists || recorded) {
1359                                 cmd = ast_play_and_wait(chan, "vm-saveoper");
1360                                 if (!cmd)
1361                                         cmd = ast_waitfordigit(chan, 3000);
1362                                 if (cmd == '1') {
1363                                         ast_play_and_wait(chan, "vm-msgsaved");
1364                                         cmd = '0';
1365                                 } else {
1366                                         ast_play_and_wait(chan, "vm-deleted");
1367                                         vm_delete(recordfile);
1368                                         cmd = '0';
1369                                 }
1370                         }
1371                         return cmd;
1372                 default:
1373                         /* If the caller is an ouside caller, and the review option is enabled,
1374                            allow them to review the message, but let the owner of the box review
1375                            their OGM's */
1376                         if (outsidecaller && !ast_test_flag(vmu, MVM_REVIEW))
1377                                 return cmd;
1378                         if (message_exists) {
1379                                 cmd = ast_play_and_wait(chan, "vm-review");
1380                         } else {
1381                                 cmd = ast_play_and_wait(chan, "vm-torerecord");
1382                                 if (!cmd)
1383                                         cmd = ast_waitfordigit(chan, 600);
1384                         }
1385                         
1386                         if (!cmd && outsidecaller && ast_test_flag(vmu, MVM_OPERATOR)) {
1387                                 cmd = ast_play_and_wait(chan, "vm-reachoper");
1388                                 if (!cmd)
1389                                         cmd = ast_waitfordigit(chan, 600);
1390                         }
1391                         if (!cmd)
1392                                 cmd = ast_waitfordigit(chan, 6000);
1393                         if (!cmd) {
1394                                 attempts++;
1395                         }
1396                         if (attempts > max_attempts) {
1397                                 cmd = 't';
1398                         }
1399                 }
1400         }
1401         if (outsidecaller)  
1402                 ast_play_and_wait(chan, "vm-goodbye");
1403         if (cmd == 't')
1404                 cmd = 0;
1405         return cmd;
1406 }
1407
1408 /*! \brief Run external notification for voicemail message */
1409 static void run_externnotify(struct ast_channel *chan, struct minivm_account *vmu)
1410 {
1411         char arguments[BUFSIZ];
1412
1413         if (ast_strlen_zero(vmu->externnotify) && ast_strlen_zero(global_externnotify))
1414                 return;
1415
1416         snprintf(arguments, sizeof(arguments), "%s %s@%s %s %s&", 
1417                 ast_strlen_zero(vmu->externnotify) ? global_externnotify : vmu->externnotify, 
1418                 vmu->username, vmu->domain,
1419                 chan->cid.cid_name, chan->cid.cid_num);
1420
1421         if (option_debug)
1422                 ast_log(LOG_DEBUG, "Executing: %s\n", arguments);
1423
1424         ast_safe_system(arguments);
1425 }
1426
1427 /*! \brief Send message to voicemail account owner */
1428 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)
1429 {
1430         char *stringp;
1431         struct minivm_template *etemplate;
1432         char *messageformat;
1433         int res = 0;
1434         char oldlocale[100];
1435         const char *counter;
1436
1437         if (!ast_strlen_zero(vmu->attachfmt)) {
1438                 if (strstr(format, vmu->attachfmt)) {
1439                         format = vmu->attachfmt;
1440                 } else 
1441                         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);
1442         }
1443
1444         etemplate = message_template_find(vmu->etemplate);
1445         if (!etemplate)
1446                 etemplate = message_template_find(templatename);
1447         if (!etemplate)
1448                 etemplate = message_template_find("email-default");
1449
1450         /* Attach only the first format */
1451         stringp = messageformat = ast_strdupa(format);
1452         strsep(&stringp, "|");
1453
1454         if (!ast_strlen_zero(etemplate->locale)) {
1455                 char *newlocale;
1456                 ast_copy_string(oldlocale, setlocale(LC_TIME, NULL), sizeof(oldlocale));
1457                 if (option_debug > 1)
1458                         ast_log(LOG_DEBUG, "-_-_- Changing locale from %s to %s\n", oldlocale, etemplate->locale);
1459                 newlocale = setlocale(LC_TIME, etemplate->locale);
1460                 if (newlocale == NULL) {
1461                         ast_log(LOG_WARNING, "-_-_- Changing to new locale did not work. Locale: %s\n", etemplate->locale);
1462                 }
1463         }
1464
1465
1466
1467         /* Read counter if available */
1468         counter = pbx_builtin_getvar_helper(chan, "MVM_COUNTER");
1469         if (option_debug > 1) {
1470                 if (ast_strlen_zero(counter))
1471                         ast_log(LOG_DEBUG, "-_-_- MVM_COUNTER not found\n");
1472                 else
1473                         ast_log(LOG_DEBUG, "-_-_- MVM_COUNTER found - will use it with value %s\n", counter);
1474         }
1475
1476         res = sendmail(etemplate, vmu, cidnum, cidname, filename, messageformat, duration, etemplate->attachment, MVM_MESSAGE_EMAIL, counter);
1477
1478         if (res == 0 && !ast_strlen_zero(vmu->pager))  {
1479                 /* Find template for paging */
1480                 etemplate = message_template_find(vmu->ptemplate);
1481                 if (!etemplate)
1482                         etemplate = message_template_find("pager-default");
1483                 if (etemplate->locale) {
1484                         ast_copy_string(oldlocale, setlocale(LC_TIME, ""), sizeof(oldlocale));
1485                         setlocale(LC_TIME, etemplate->locale);
1486                 }
1487
1488                 res = sendmail(etemplate, vmu, cidnum, cidname, filename, messageformat, duration, etemplate->attachment, MVM_MESSAGE_PAGE, counter);
1489         }
1490
1491         manager_event(EVENT_FLAG_CALL, "MiniVoiceMail", "Action: SentNotification\rn\nMailbox: %s@%s\r\nCounter: %s\r\n", vmu->username, vmu->domain, counter);
1492
1493         run_externnotify(chan, vmu);            /* Run external notification */
1494
1495         if (etemplate->locale) 
1496                 setlocale(LC_TIME, oldlocale); /* Rest to old locale */
1497         return res;
1498 }
1499
1500  
1501 /*! \brief Record voicemail message, store into file prepared for sending e-mail */
1502 static int leave_voicemail(struct ast_channel *chan, char *username, struct leave_vm_options *options)
1503 {
1504         char tmptxtfile[PATH_MAX];
1505         char callerid[256];
1506         FILE *txt;
1507         int res = 0, txtdes;
1508         int msgnum;
1509         int duration = 0;
1510         char date[256];
1511         char tmpdir[PATH_MAX];
1512         char ext_context[256] = "";
1513         char fmt[80];
1514         char *domain;
1515         char tmp[256] = "";
1516         struct minivm_account *vmu;
1517         int userdir;
1518
1519         ast_copy_string(tmp, username, sizeof(tmp));
1520         username = tmp;
1521         domain = strchr(tmp, '@');
1522         if (domain) {
1523                 *domain = '\0';
1524                 domain++;
1525         }
1526
1527         if (!(vmu = find_account(domain, username, TRUE))) {
1528                 /* We could not find user, let's exit */
1529                 ast_log(LOG_ERROR, "Can't allocate temporary account for '%s@%s'\n", username, domain);
1530                 pbx_builtin_setvar_helper(chan, "MINIVM_RECORD_STATUS", "FAILED");
1531                 return 0;
1532         }
1533
1534         /* Setup pre-file if appropriate */
1535         if (strcmp(vmu->domain, "localhost"))
1536                 snprintf(ext_context, sizeof(ext_context), "%s@%s", username, vmu->domain);
1537         else
1538                 ast_copy_string(ext_context, vmu->domain, sizeof(ext_context));
1539
1540         /* The meat of recording the message...  All the announcements and beeps have been played*/
1541         if (ast_strlen_zero(vmu->attachfmt))
1542                 ast_copy_string(fmt, default_vmformat, sizeof(fmt));
1543         else
1544                 ast_copy_string(fmt, vmu->attachfmt, sizeof(fmt));
1545
1546         if (ast_strlen_zero(fmt)) {
1547                 ast_log(LOG_WARNING, "No format for saving voicemail? Default %s\n", default_vmformat);
1548                 pbx_builtin_setvar_helper(chan, "MINIVM_RECORD_STATUS", "FAILED");
1549                 return res;
1550         }
1551         msgnum = 0;
1552
1553         userdir = check_dirpath(tmpdir, sizeof(tmpdir), vmu->domain, username, "tmp");
1554
1555         /* If we have no user directory, use generic temporary directory */
1556         if (!userdir) {
1557                 create_dirpath(tmpdir, sizeof(tmpdir), "0000_minivm_temp", "mediafiles", "");
1558                 if (option_debug > 2)
1559                         ast_log(LOG_DEBUG, "Creating temporary directory %s\n", tmpdir);
1560         }
1561
1562
1563         snprintf(tmptxtfile, sizeof(tmptxtfile), "%s/XXXXXX", tmpdir);
1564         
1565
1566         /* XXX This file needs to be in temp directory */
1567         txtdes = mkstemp(tmptxtfile);
1568         if (txtdes < 0) {
1569                 ast_log(LOG_ERROR, "Unable to create message file %s: %s\n", tmptxtfile, strerror(errno));
1570                 res = ast_streamfile(chan, "vm-mailboxfull", chan->language);
1571                 if (!res)
1572                         res = ast_waitstream(chan, "");
1573                 pbx_builtin_setvar_helper(chan, "MINIVM_RECORD_STATUS", "FAILED");
1574                 return res;
1575         }
1576
1577         if (res >= 0) {
1578                 /* Unless we're *really* silent, try to send the beep */
1579                 res = ast_streamfile(chan, "beep", chan->language);
1580                 if (!res)
1581                         res = ast_waitstream(chan, "");
1582         }
1583
1584         /* OEJ XXX Maybe this can be turned into a log file? Hmm. */
1585         /* Store information */
1586         if (option_debug > 1)
1587                 ast_log(LOG_DEBUG, "Open file for metadata: %s\n", tmptxtfile);
1588
1589         res = play_record_review(chan, NULL, tmptxtfile, global_vmmaxmessage, fmt, 1, vmu, &duration, NULL, options->record_gain);
1590
1591         txt = fdopen(txtdes, "w+");
1592         if (!txt) {
1593                 ast_log(LOG_WARNING, "Error opening text file for output\n");
1594         } else {
1595                 struct tm tm;
1596                 time_t now;
1597                 char timebuf[30];
1598                 char logbuf[BUFSIZ];
1599                 get_date(date, sizeof(date));
1600                 now = time(NULL);
1601                 ast_localtime(&now, &tm, NULL);
1602                 strftime(timebuf, sizeof(timebuf), "%H:%M:%S", &tm);
1603                 
1604                 snprintf(logbuf, sizeof(logbuf),
1605                         /* "Mailbox:domain:macrocontext:exten:priority:callerchan:callerid:origdate:origtime:duration:durationstatus:accountcode" */
1606                         "%s:%s:%s:%s:%d:%s:%s:%s:%s:%d:%s:%s\n",
1607                         username,
1608                         chan->context,
1609                         chan->macrocontext, 
1610                         chan->exten,
1611                         chan->priority,
1612                         chan->name,
1613                         ast_callerid_merge(callerid, sizeof(callerid), chan->cid.cid_name, chan->cid.cid_num, "Unknown"),
1614                         date, 
1615                         timebuf,
1616                         duration,
1617                         duration < global_vmminmessage ? "IGNORED" : "OK",
1618                         vmu->accountcode
1619                 ); 
1620                 fprintf(txt, logbuf);
1621                 if (minivmlogfile) {
1622                         ast_mutex_lock(&minivmloglock);
1623                         fprintf(minivmlogfile, logbuf);
1624                         ast_mutex_unlock(&minivmloglock);
1625                 }
1626
1627                 if (duration < global_vmminmessage) {
1628                         if (option_verbose > 2) 
1629                                 ast_verbose( VERBOSE_PREFIX_3 "Recording was %d seconds long but needs to be at least %d - abandoning\n", duration, global_vmminmessage);
1630                         fclose(txt);
1631                         ast_filedelete(tmptxtfile, NULL);
1632                         unlink(tmptxtfile);
1633                         pbx_builtin_setvar_helper(chan, "MINIVM_RECORD_STATUS", "FAILED");
1634                         return 0;
1635                 } 
1636                 fclose(txt); /* Close log file */
1637                 if (ast_fileexists(tmptxtfile, NULL, NULL) <= 0) {
1638                         if (option_debug) 
1639                                 ast_log(LOG_DEBUG, "The recorded media file is gone, so we should remove the .txt file too!\n");
1640                         unlink(tmptxtfile);
1641                         pbx_builtin_setvar_helper(chan, "MINIVM_RECORD_STATUS", "FAILED");
1642                         if(ast_test_flag(vmu, MVM_ALLOCED))
1643                                 free_user(vmu);
1644                         return 0;
1645                 }
1646
1647                 /* Set channel variables for the notify application */
1648                 pbx_builtin_setvar_helper(chan, "MVM_FILENAME", tmptxtfile);
1649                 snprintf(timebuf, sizeof(timebuf), "%d", duration);
1650                 pbx_builtin_setvar_helper(chan, "MVM_DURATION", timebuf);
1651                 pbx_builtin_setvar_helper(chan, "MVM_FORMAT", fmt);
1652
1653         }
1654         global_stats.lastreceived = time(NULL);
1655         global_stats.receivedmessages++;
1656 //      /* Go ahead and delete audio files from system, they're not needed any more */
1657 //      if (ast_fileexists(tmptxtfile, NULL, NULL) <= 0) {
1658 //              ast_filedelete(tmptxtfile, NULL);
1659 //              if (option_debug > 1)
1660 //                      ast_log(LOG_DEBUG, "-_-_- Deleted audio file after notification :: %s \n", tmptxtfile);
1661 //      }
1662
1663         if (res > 0)
1664                 res = 0;
1665
1666         if(ast_test_flag(vmu, MVM_ALLOCED))
1667                 free_user(vmu);
1668
1669         pbx_builtin_setvar_helper(chan, "MINIVM_RECORD_STATUS", "SUCCESS");
1670         return res;
1671 }
1672
1673 /*! \brief Notify voicemail account owners - either generic template or user specific */
1674 static int minivm_notify_exec(struct ast_channel *chan, void *data)
1675 {
1676         struct ast_module_user *u;
1677         int argc;
1678         char *argv[2];
1679         int res = 0;
1680         char tmp[PATH_MAX];
1681         char *domain;
1682         char *tmpptr;
1683         struct minivm_account *vmu;
1684         char *username = argv[0];
1685         const char *template = "";
1686         const char *filename;
1687         const char *format;
1688         const char *duration_string;
1689         
1690         u = ast_module_user_add(chan);
1691
1692
1693         if (ast_strlen_zero(data))  {
1694                 ast_log(LOG_ERROR, "Minivm needs at least an account argument \n");
1695                 ast_module_user_remove(u);
1696                 return -1;
1697         }
1698         tmpptr = ast_strdupa((char *)data);
1699         if (!tmpptr) {
1700                 ast_log(LOG_ERROR, "Out of memory\n");
1701                 ast_module_user_remove(u);
1702                 return -1;
1703         }
1704         argc = ast_app_separate_args(tmpptr, '|', argv, sizeof(argv) / sizeof(argv[0]));
1705
1706         if (argc == 2 && !ast_strlen_zero(argv[1]))
1707                 template = argv[1];
1708
1709         ast_copy_string(tmp, argv[0], sizeof(tmp));
1710         username = tmp;
1711         domain = strchr(tmp, '@');
1712         if (domain) {
1713                 *domain = '\0';
1714                 domain++;
1715         } 
1716         if (ast_strlen_zero(domain) || ast_strlen_zero(username)) {
1717                 ast_log(LOG_ERROR, "Need username@domain as argument. Sorry. Argument 0 %s\n", argv[0]);
1718                 ast_module_user_remove(u);
1719                 return -1;
1720         }
1721
1722         if(!(vmu = find_account(domain, username, TRUE))) {
1723                 /* We could not find user, let's exit */
1724                 ast_log(LOG_WARNING, "Could not allocate temporary memory for '%s@%s'\n", username, domain);
1725                 pbx_builtin_setvar_helper(chan, "MINIVM_NOTIFY_STATUS", "FAILED");
1726                 ast_module_user_remove(u);
1727                 return -1;
1728         }
1729         
1730         filename = pbx_builtin_getvar_helper(chan, "MVM_FILENAME");
1731         format = pbx_builtin_getvar_helper(chan, "MVM_FORMAT");
1732         duration_string = pbx_builtin_getvar_helper(chan, "MVM_DURATION");
1733         /* Notify of new message to e-mail and pager */
1734         if (!ast_strlen_zero(filename)) {
1735                 res = notify_new_message(chan, template, vmu, filename, atoi(duration_string), format, chan->cid.cid_num, chan->cid.cid_name);
1736         };
1737
1738         pbx_builtin_setvar_helper(chan, "MINIVM_NOTIFY_STATUS", res == 0 ? "SUCCESS" : "FAILED");
1739
1740
1741         if(ast_test_flag(vmu, MVM_ALLOCED))
1742                 free_user(vmu);
1743
1744         /* Ok, we're ready to rock and roll. Return to dialplan */
1745         ast_module_user_remove(u);
1746
1747         return res;
1748
1749 }
1750
1751 /*! \brief Dialplan function to record voicemail */
1752 static int minivm_record_exec(struct ast_channel *chan, void *data)
1753 {
1754         int res = 0;
1755         struct ast_module_user *u;
1756         char *tmp;
1757         struct leave_vm_options leave_options;
1758         int argc;
1759         char *argv[2];
1760         struct ast_flags flags = { 0 };
1761         char *opts[OPT_ARG_ARRAY_SIZE];
1762         
1763         u = ast_module_user_add(chan);
1764         
1765         memset(&leave_options, 0, sizeof(leave_options));
1766
1767         /* Answer channel if it's not already answered */
1768         if (chan->_state != AST_STATE_UP)
1769                 ast_answer(chan);
1770
1771         if (ast_strlen_zero(data))  {
1772                 ast_log(LOG_ERROR, "Minivm needs at least an account argument \n");
1773                 ast_module_user_remove(u);
1774                 return -1;
1775         }
1776         tmp = ast_strdupa((char *)data);
1777         if (!tmp) {
1778                 ast_log(LOG_ERROR, "Out of memory\n");
1779                 ast_module_user_remove(u);
1780                 return -1;
1781         }
1782         argc = ast_app_separate_args(tmp, '|', argv, sizeof(argv) / sizeof(argv[0]));
1783         if (argc == 2) {
1784                 if (ast_app_parse_options(minivm_app_options, &flags, opts, argv[1])) {
1785                         ast_module_user_remove(u);
1786                         return -1;
1787                 }
1788                 ast_copy_flags(&leave_options, &flags, OPT_SILENT | OPT_BUSY_GREETING | OPT_UNAVAIL_GREETING );
1789                 if (ast_test_flag(&flags, OPT_RECORDGAIN)) {
1790                         int gain;
1791
1792                         if (sscanf(opts[OPT_ARG_RECORDGAIN], "%d", &gain) != 1) {
1793                                 ast_log(LOG_WARNING, "Invalid value '%s' provided for record gain option\n", opts[OPT_ARG_RECORDGAIN]);
1794                                 ast_module_user_remove(u);
1795                                 return -1;
1796                         } else 
1797                                 leave_options.record_gain = (signed char) gain;
1798                 }
1799         } 
1800
1801         /* Now run the appliation and good luck to you! */
1802         res = leave_voicemail(chan, argv[0], &leave_options);
1803
1804         if (res == ERROR_LOCK_PATH) {
1805                 ast_log(LOG_ERROR, "Could not leave voicemail. The path is already locked.\n");
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(*vmu));
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         ast_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         AST_LIST_LOCK(&minivm_zones);
2288         while ((this = AST_LIST_REMOVE_HEAD(&minivm_zones, list))) 
2289                 free_zone(this);
2290                 
2291         AST_LIST_UNLOCK(&minivm_zones);
2292 }
2293
2294 /*! \brief Add time zone to memory list */
2295 static int timezone_add(char *zonename, char *config)
2296 {
2297
2298         struct minivm_zone *newzone;
2299         char *msg_format, *timezone;
2300
2301         newzone = ast_calloc(1, sizeof(*newzone));
2302         if (newzone == NULL)
2303                 return 0;
2304
2305         msg_format = ast_strdupa(config);
2306         if (msg_format == NULL) {
2307                 ast_log(LOG_WARNING, "Out of memory.\n");
2308                 ast_free(newzone);
2309                 return 0;
2310         }
2311
2312         timezone = strsep(&msg_format, "|");
2313         if (!msg_format) {
2314                 ast_log(LOG_WARNING, "Invalid timezone definition : %s\n", zonename);
2315                 ast_free(newzone);
2316                 return 0;
2317         }
2318                         
2319         ast_copy_string(newzone->name, zonename, sizeof(newzone->name));
2320         ast_copy_string(newzone->timezone, timezone, sizeof(newzone->timezone));
2321         ast_copy_string(newzone->msg_format, msg_format, sizeof(newzone->msg_format));
2322
2323         AST_LIST_LOCK(&minivm_zones);
2324         AST_LIST_INSERT_TAIL(&minivm_zones, newzone, list);
2325         AST_LIST_UNLOCK(&minivm_zones);
2326
2327         global_stats.timezones++;
2328
2329         return 0;
2330 }
2331
2332 /*! \brief Read message template from file */
2333 static char *message_template_parse_filebody(char *filename) {
2334         char buf[BUFSIZ * 6];
2335         char readbuf[BUFSIZ];
2336         char filenamebuf[BUFSIZ];
2337         char *writepos;
2338         char *messagebody;
2339         FILE *fi;
2340         int lines = 0;
2341
2342         if (ast_strlen_zero(filename))
2343                 return NULL;
2344         if (*filename == '/') 
2345                 ast_copy_string(filenamebuf, filename, sizeof(filenamebuf));
2346         else 
2347                 snprintf(filenamebuf, sizeof(filenamebuf), "%s/%s", ast_config_AST_CONFIG_DIR, filename);
2348
2349         if (!(fi = fopen(filenamebuf, "r"))) {
2350                 ast_log(LOG_ERROR, "Can't read message template from file: %s\n", filenamebuf);
2351                 return NULL;
2352         }
2353         writepos = buf;
2354         while (fgets(readbuf, sizeof(readbuf), fi)) {
2355                 lines ++;
2356                 if (writepos != buf) {
2357                         *writepos = '\n';               /* Replace EOL with new line */
2358                         writepos++;
2359                 }
2360                 ast_copy_string(writepos, readbuf, sizeof(buf) - (writepos - buf));
2361                 writepos += strlen(readbuf) - 1;
2362         }
2363         fclose(fi);
2364         messagebody = ast_calloc(1, strlen(buf + 1));
2365         ast_copy_string(messagebody, buf, strlen(buf) + 1);
2366         if (option_debug > 3) {
2367                 ast_log(LOG_DEBUG, "---> Size of allocation %d\n", (int) strlen(buf + 1) );
2368                 ast_log(LOG_DEBUG, "---> Done reading message template : \n%s\n---- END message template--- \n", messagebody);
2369         }
2370
2371         return messagebody;
2372 }
2373
2374 /*! \brief Parse emailbody template from configuration file */
2375 static char *message_template_parse_emailbody(const char *configuration)
2376 {
2377         char *tmpread, *tmpwrite;
2378         char *emailbody = strdup(configuration);
2379
2380         /* substitute strings \t and \n into the apropriate characters */
2381         tmpread = tmpwrite = emailbody;
2382         while ((tmpwrite = strchr(tmpread,'\\'))) {
2383                int len = strlen("\n");
2384                switch (tmpwrite[1]) {
2385                case 'n':
2386                       strncpy(tmpwrite+len, tmpwrite+2, strlen(tmpwrite+2)+1);
2387                       strncpy(tmpwrite, "\n", len);
2388                       break;
2389                case 't':
2390                       strncpy(tmpwrite+len, tmpwrite+2, strlen(tmpwrite+2)+1);
2391                       strncpy(tmpwrite, "\t", len);
2392                       break;
2393                default:
2394                       ast_log(LOG_NOTICE, "Substitution routine does not support this character: %c\n", tmpwrite[1]);
2395                }
2396                tmpread = tmpwrite + len;
2397         }
2398         return emailbody;       
2399 }
2400
2401 /*! \brief Apply general configuration options */
2402 static int apply_general_options(struct ast_variable *var)
2403 {
2404         int error = 0;
2405
2406         while (var) {
2407                 /* Mail command */
2408                 if (!strcmp(var->name, "mailcmd")) {
2409                         ast_copy_string(global_mailcmd, var->value, sizeof(global_mailcmd)); /* User setting */
2410                 } else if (!strcmp(var->name, "maxgreet")) {
2411                         global_maxgreet = atoi(var->value);
2412                 } else if (!strcmp(var->name, "maxsilence")) {
2413                         global_maxsilence = atoi(var->value);
2414                         if (global_maxsilence > 0)
2415                                 global_maxsilence *= 1000;
2416                 } else if (!strcmp(var->name, "logfile")) {
2417                         if (!ast_strlen_zero(var->value) ) {
2418                                 if(*(var->value) == '/')
2419                                         ast_copy_string(global_logfile, var->value, sizeof(global_logfile));
2420                                 else
2421                                         snprintf(global_logfile, sizeof(global_logfile), "%s/%s", ast_config_AST_LOG_DIR, var->value);
2422                         }
2423                 } else if (!strcmp(var->name, "externnotify")) {
2424                         /* External voicemail notify application */
2425                         ast_copy_string(global_externnotify, var->value, sizeof(global_externnotify));
2426                 } else if (!strcmp(var->name, "silencetreshold")) {
2427                         /* Silence treshold */
2428                         global_silencethreshold = atoi(var->value);
2429                 } else if (!strcmp(var->name, "maxmessage")) {
2430                         int x;
2431                         if (sscanf(var->value, "%d", &x) == 1) {
2432                                 global_vmmaxmessage = x;
2433                         } else {
2434                                 error ++;
2435                                 ast_log(LOG_WARNING, "Invalid max message time length\n");
2436                         }
2437                 } else if (!strcmp(var->name, "minmessage")) {
2438                         int x;
2439                         if (sscanf(var->value, "%d", &x) == 1) {
2440                                 global_vmminmessage = x;
2441                                 if (global_maxsilence <= global_vmminmessage)
2442                                         ast_log(LOG_WARNING, "maxsilence should be less than minmessage or you may get empty messages\n");
2443                         } else {
2444                                 error ++;
2445                                 ast_log(LOG_WARNING, "Invalid min message time length\n");
2446                         }
2447                 } else if (!strcmp(var->name, "format")) {
2448                         ast_copy_string(default_vmformat, var->value, sizeof(default_vmformat));
2449                 } else if (!strcmp(var->name, "review")) {
2450                         ast_set2_flag((&globalflags), ast_true(var->value), MVM_REVIEW);        
2451                 } else if (!strcmp(var->name, "operator")) {
2452                         ast_set2_flag((&globalflags), ast_true(var->value), MVM_OPERATOR);      
2453                 }
2454                 var = var->next;
2455         }
2456         return error;
2457 }
2458
2459 /*! \brief Load minivoicemail configuration */
2460 static int load_config(void)
2461 {
2462         struct ast_config *cfg;
2463         struct ast_variable *var;
2464         char *cat;
2465         const char *chanvar;
2466         int error = 0;
2467
2468         cfg = ast_config_load(VOICEMAIL_CONFIG);
2469         ast_mutex_lock(&minivmlock);
2470
2471         /* Destroy lists to reconfigure */
2472         message_destroy_list();         /* Destroy list of voicemail message templates */
2473         timezone_destroy_list();        /* Destroy list of timezones */
2474         vmaccounts_destroy_list();      /* Destroy list of voicemail accounts */
2475         if (option_debug > 1)
2476                 ast_log(LOG_DEBUG, "Destroyed memory objects...\n");
2477
2478         /* First, set some default settings */
2479         global_externnotify[0] = '\0';
2480         global_logfile[0] = '\0';
2481         global_silencethreshold = 256;
2482         global_vmmaxmessage = 2000;
2483         global_maxgreet = 2000;
2484         global_vmminmessage = 0;
2485         strcpy(global_mailcmd, SENDMAIL);
2486         global_maxsilence = 0;
2487         global_saydurationminfo = 2;
2488         ast_copy_string(default_vmformat, "wav", sizeof(default_vmformat));
2489         ast_set2_flag((&globalflags), FALSE, MVM_REVIEW);       
2490         ast_set2_flag((&globalflags), FALSE, MVM_OPERATOR);     
2491         strcpy(global_charset, "ISO-8859-1");
2492         struct minivm_template *template;
2493         /* Reset statistics */
2494         memset(&global_stats, 0, sizeof(struct minivm_stats));
2495         global_stats.reset = time(NULL);
2496
2497         /* Make sure we could load configuration file */
2498         if (!cfg) {
2499                 ast_log(LOG_WARNING, "Failed to load configuration file. Module activated with default settings.\n");
2500                 ast_mutex_unlock(&minivmlock);
2501                 return 0;
2502         }
2503
2504         if (option_debug > 1)
2505                 ast_log(LOG_DEBUG, "-_-_- Loaded configuration file, now parsing\n");
2506
2507         /* General settings */
2508
2509         cat = ast_category_browse(cfg, NULL);
2510         while (cat) {
2511                 if (option_debug > 2)
2512                         ast_log(LOG_DEBUG, "-_-_- Found configuration section [%s]\n", cat);
2513                 if (!strcasecmp(cat, "general")) {
2514                         /* Nothing right now */
2515                         error += apply_general_options(ast_variable_browse(cfg, cat));
2516                 } else if (!strncasecmp(cat, "template-", 9))  {
2517                         /* Template */
2518                         char *name = cat + 9;
2519
2520                         /* Now build and link template to list */
2521                         error += message_template_build(name, ast_variable_browse(cfg, cat));
2522                 } else {
2523                         var = ast_variable_browse(cfg, cat);
2524                         if (!strcasecmp(cat, "zonemessages")) {
2525                                 /* Timezones in this context */
2526                                 while (var) {
2527                                         timezone_add(var->name, var->value);
2528                                         var = var->next;
2529                                 }
2530                         } else {
2531                                 /* Create mailbox from this */
2532                                 error += create_vmaccount(cat, var, FALSE);
2533                         }
2534                 }
2535                 /* Find next section in configuration file */
2536                 cat = ast_category_browse(cfg, cat);
2537         }
2538
2539         /* Configure the default email template */
2540         message_template_build("email-default", NULL);
2541         template = message_template_find("email-default");
2542
2543         /* Load date format config for voicemail mail */
2544         if ((chanvar = ast_variable_retrieve(cfg, "general", "emaildateformat"))) 
2545                 ast_copy_string(template->dateformat, chanvar, sizeof(template->dateformat));
2546         if ((chanvar = ast_variable_retrieve(cfg, "general", "emailfromstring")))
2547                 ast_copy_string(template->fromaddress, chanvar, sizeof(template->fromaddress));
2548         if ((chanvar = ast_variable_retrieve(cfg, "general", "emailaaddress")))
2549                 ast_copy_string(template->serveremail, chanvar, sizeof(template->serveremail));
2550         if ((chanvar = ast_variable_retrieve(cfg, "general", "emailcharset")))
2551                 ast_copy_string(template->charset, chanvar, sizeof(template->charset));
2552         if ((chanvar = ast_variable_retrieve(cfg, "general", "emailsubject"))) 
2553                 ast_copy_string(template->subject, chanvar, sizeof(template->subject));
2554         if ((chanvar = ast_variable_retrieve(cfg, "general", "emailbody"))) 
2555                 template->body = message_template_parse_emailbody(chanvar);
2556         template->attachment = TRUE;
2557
2558         message_template_build("pager-default", NULL);
2559         template = message_template_find("pager-default");
2560         if ((chanvar = ast_variable_retrieve(cfg, "general", "pagerfromstring")))
2561                 ast_copy_string(template->fromaddress, chanvar, sizeof(template->fromaddress));
2562         if ((chanvar = ast_variable_retrieve(cfg, "general", "pageraddress")))
2563                 ast_copy_string(template->serveremail, chanvar, sizeof(template->serveremail));
2564         if ((chanvar = ast_variable_retrieve(cfg, "general", "pagercharset")))
2565                 ast_copy_string(template->charset, chanvar, sizeof(template->charset));
2566         if ((chanvar = ast_variable_retrieve(cfg, "general", "pagersubject")))
2567                 ast_copy_string(template->subject, chanvar,sizeof(template->subject));
2568         if ((chanvar = ast_variable_retrieve(cfg, "general", "pagerbody"))) 
2569                 template->body = message_template_parse_emailbody(chanvar);
2570         template->attachment = FALSE;
2571
2572         if (error)
2573                 ast_log(LOG_ERROR, "--- A total of %d errors found in mini-voicemail configuration\n", error);
2574
2575         ast_mutex_unlock(&minivmlock);
2576         ast_config_destroy(cfg);
2577
2578         /* Close log file if it's open and disabled */
2579         if(minivmlogfile)
2580                 fclose(minivmlogfile);
2581
2582         /* Open log file if it's enabled */
2583         if(!ast_strlen_zero(global_logfile)) {
2584                 minivmlogfile = fopen(global_logfile, "a");
2585                 if(!minivmlogfile)
2586                         ast_log(LOG_ERROR, "Failed to open minivm log file %s : %s\n", global_logfile, strerror(errno));
2587                 if (option_debug > 2 && minivmlogfile)
2588                         ast_log(LOG_DEBUG, "-_-_- Opened log file %s \n", global_logfile);
2589         }
2590
2591         return 0;
2592 }
2593
2594 static const char minivm_show_users_help[] =
2595 "Usage: minivm list accounts\n"
2596 "       Lists all mailboxes currently set up\n";
2597
2598 static const char minivm_show_zones_help[] =
2599 "Usage: minivm list zones\n"
2600 "       Lists zone message formats\n";
2601
2602 static const char minivm_list_templates_help[] =
2603 "Usage: minivm list templates\n"
2604 "       Lists message templates for e-mail, paging and IM\n";
2605
2606 static const char minivm_show_stats_help[] =
2607 "Usage: minivm show stats\n"
2608 "       Display Mini-Voicemail counters\n";
2609
2610 static const char minivm_show_settings_help[] =
2611 "Usage: minivm show settings\n"
2612 "       Display Mini-Voicemail general settings\n";
2613
2614 static const char minivm_reload_help[] =
2615 "Usage: minivm reload\n"
2616 "       Reload mini-voicemail configuration and reset statistics\n";
2617
2618 /*! \brief CLI routine for listing templates */
2619 static int handle_minivm_list_templates(int fd, int argc, char *argv[])
2620 {
2621         struct minivm_template *this;
2622         char *output_format = "%-15s %-10s %-10s %-15.15s %-50s\n";
2623         int count = 0;
2624
2625         if (argc > 3)
2626                 return RESULT_SHOWUSAGE;
2627
2628         AST_LIST_LOCK(&message_templates);
2629         if (AST_LIST_EMPTY(&message_templates)) {
2630                 ast_cli(fd, "There are no message templates defined\n");
2631                 AST_LIST_UNLOCK(&message_templates);
2632                 return RESULT_FAILURE;
2633         }
2634         ast_cli(fd, output_format, "Template name", "Charset", "Locale", "Attach media", "Subject");
2635         ast_cli(fd, output_format, "-------------", "-------", "------", "------------", "-------");
2636         AST_LIST_TRAVERSE(&message_templates, this, list) {
2637                 ast_cli(fd, output_format, this->name, 
2638                         this->charset ? this->charset : "-", 
2639                         this->locale ? this->locale : "-",
2640                         this->attachment ? "Yes" : "No",
2641                         this->subject ? this->subject : "-");
2642                 count++;
2643         }
2644         AST_LIST_UNLOCK(&message_templates);
2645         ast_cli(fd, "\n * Total: %d minivoicemail message templates\n", count);
2646         return RESULT_SUCCESS;
2647 }
2648
2649 /*! \brief CLI command to list voicemail accounts */
2650 static int handle_minivm_show_users(int fd, int argc, char *argv[])
2651 {
2652         struct minivm_account *vmu;
2653         char *output_format = "%-23s %-15s %-15s %-10s %-10s %-50s\n";
2654         int count = 0;
2655
2656         if ((argc < 3) || (argc > 5) || (argc == 4))
2657                 return RESULT_SHOWUSAGE;
2658         if ((argc == 5) && strcmp(argv[3],"for"))
2659                 return RESULT_SHOWUSAGE;
2660
2661         AST_LIST_LOCK(&minivm_accounts);
2662         if (AST_LIST_EMPTY(&minivm_accounts)) {
2663                 ast_cli(fd, "There are no voicemail users currently defined\n");
2664                 AST_LIST_UNLOCK(&minivm_accounts);
2665                 return RESULT_FAILURE;
2666         }
2667         ast_cli(fd, output_format, "User", "E-Template", "P-template", "Zone", "Format", "Full name");
2668         ast_cli(fd, output_format, "----", "----------", "----------", "----", "------", "---------");
2669         AST_LIST_TRAVERSE(&minivm_accounts, vmu, list) {
2670                 char tmp[256] = "";
2671
2672
2673                 if ((argc == 3) || ((argc == 5) && !strcmp(argv[4], vmu->domain))) {
2674                         count++;
2675                         snprintf(tmp, sizeof(tmp), "%s@%s", vmu->username, vmu->domain);
2676                         ast_cli(fd, output_format, tmp, vmu->etemplate ? vmu->etemplate : "-", 
2677                                 vmu->ptemplate ? vmu->ptemplate : "-",
2678                                 vmu->zonetag ? vmu->zonetag : "-", 
2679                                 vmu->attachfmt ? vmu->attachfmt : "-",
2680                                 vmu->fullname);
2681                 }
2682         }
2683         AST_LIST_UNLOCK(&minivm_accounts);
2684         ast_cli(fd, "\n * Total: %d minivoicemail accounts\n", count);
2685         return RESULT_SUCCESS;
2686 }
2687
2688 /*! \brief Show a list of voicemail zones in the CLI */
2689 static int handle_minivm_show_zones(int fd, int argc, char *argv[])
2690 {
2691         struct minivm_zone *zone;
2692         char *output_format = "%-15s %-20s %-45s\n";
2693         int res = RESULT_SUCCESS;
2694
2695         if (argc != 3)
2696                 return RESULT_SHOWUSAGE;
2697
2698         AST_LIST_LOCK(&minivm_zones);
2699         if (!AST_LIST_EMPTY(&minivm_zones)) {
2700                 ast_cli(fd, output_format, "Zone", "Timezone", "Message Format");
2701                 ast_cli(fd, output_format, "----", "--------", "--------------");
2702                 AST_LIST_TRAVERSE(&minivm_zones, zone, list) {
2703                         ast_cli(fd, output_format, zone->name, zone->timezone, zone->msg_format);
2704                 }
2705         } else {
2706                 ast_cli(fd, "There are no voicemail zones currently defined\n");
2707                 res = RESULT_FAILURE;
2708         }
2709         AST_LIST_UNLOCK(&minivm_zones);
2710
2711         return res;
2712 }
2713
2714
2715 static char *complete_minivm_show_users(const char *line, const char *word, int pos, int state)
2716 {
2717         int which = 0;
2718         int wordlen;
2719         struct minivm_account *vmu;
2720         const char *domain = "";
2721
2722         /* 0 - show; 1 - voicemail; 2 - users; 3 - for; 4 - <domain> */
2723         if (pos > 4)
2724                 return NULL;
2725         if (pos == 3)
2726                 return (state == 0) ? strdup("for") : NULL;
2727         wordlen = strlen(word);
2728         AST_LIST_TRAVERSE(&minivm_accounts, vmu, list) {
2729                 if (!strncasecmp(word, vmu->domain, wordlen)) {
2730                         if (domain && strcmp(domain, vmu->domain) && ++which > state)
2731                                 return strdup(vmu->domain);
2732                         /* ignore repeated domains ? */
2733                         domain = vmu->domain;
2734                 }
2735         }
2736         return NULL;
2737 }
2738
2739 /*! \brief CLI Show settings */
2740 static int handle_minivm_show_settings(int fd, int argc, char *argv[])
2741 {
2742         ast_cli(fd, "* Mini-Voicemail general settings\n");
2743         ast_cli(fd, "  -------------------------------\n");
2744         ast_cli(fd, "\n");
2745         ast_cli(fd, "  Mail command (shell):               %s\n", global_mailcmd);
2746         ast_cli(fd, "  Max silence:                        %d\n", global_maxsilence);
2747         ast_cli(fd, "  Silence treshold:                   %d\n", global_silencethreshold);
2748         ast_cli(fd, "  Max message length (secs):          %d\n", global_vmmaxmessage);
2749         ast_cli(fd, "  Min message length (secs):          %d\n", global_vmminmessage);
2750         ast_cli(fd, "  Default format:                     %s\n", default_vmformat);
2751         ast_cli(fd, "  Extern notify (shell):              %s\n", global_externnotify);
2752         ast_cli(fd, "  Logfile:                            %s\n", global_logfile[0] ? global_logfile : "<disabled>");
2753         ast_cli(fd, "  Operator exit:                      %s\n", ast_test_flag(&globalflags, MVM_OPERATOR) ? "Yes" : "No");
2754         ast_cli(fd, "  Message review:                     %s\n", ast_test_flag(&globalflags, MVM_REVIEW) ? "Yes" : "No");
2755
2756         ast_cli(fd, "\n");
2757         return RESULT_SUCCESS;
2758 }
2759
2760 /*! \brief Show stats */
2761 static int handle_minivm_show_stats(int fd, int argc, char *argv[])
2762 {
2763         struct tm time;
2764         char buf[BUFSIZ];
2765
2766         ast_cli(fd, "* Mini-Voicemail statistics\n");
2767         ast_cli(fd, "  -------------------------\n");
2768         ast_cli(fd, "\n");
2769         ast_cli(fd, "  Voicemail accounts:                  %-5.5d\n", global_stats.voicemailaccounts);
2770         ast_cli(fd, "  Templates:                           %-5.5d\n", global_stats.templates);
2771         ast_cli(fd, "  Timezones:                           %-5.5d\n", global_stats.timezones);
2772         if (global_stats.receivedmessages == 0) {
2773                 ast_cli(fd, "  Received messages since last reset:  <none>\n");
2774         } else {
2775                 ast_cli(fd, "  Received messages since last reset:  %d\n", global_stats.receivedmessages);
2776                 ast_localtime(&global_stats.lastreceived, &time, NULL);
2777                 strftime(buf, sizeof(buf), "%a %b %e %r %Z %Y", &time);
2778                 ast_cli(fd, "  Last received voicemail:             %s\n", buf);
2779         }
2780         ast_localtime(&global_stats.reset, &time, NULL);
2781         strftime(buf, sizeof(buf), "%a %b %e %r %Z %Y", &time);
2782         ast_cli(fd, "  Last reset:                          %s\n", buf);
2783
2784         ast_cli(fd, "\n");
2785         return RESULT_SUCCESS;
2786 }
2787
2788 /*! \brief  ${MINIVMACCOUNT()} Dialplan function - reads account data */
2789 static int minivm_account_func_read(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len)
2790 {
2791         struct minivm_account *vmu;
2792         char *username, *domain, *colname;
2793
2794         if (!(username = ast_strdupa(data))) {
2795                 ast_log(LOG_ERROR, "Memory Error!\n");
2796                 return -1;
2797         }
2798
2799         if ((colname = strchr(username, ':'))) {
2800                 *colname = '\0';
2801                 colname++;
2802         } else {
2803                 colname = "path";
2804         }
2805         if ((domain = strchr(username, '@'))) {
2806                 *domain = '\0';
2807                 domain++;
2808         }
2809         if (ast_strlen_zero(username) || ast_strlen_zero(domain)) {
2810                 ast_log(LOG_ERROR, "This function needs a username and a domain: username@domain\n");
2811                 return 0;
2812         }
2813
2814         if (!(vmu = find_account(domain, username, TRUE)))
2815                 return 0;
2816
2817         if (!strcasecmp(colname, "hasaccount")) {
2818                 ast_copy_string(buf, (ast_test_flag(vmu, MVM_ALLOCED) ? "0" : "1"), len);
2819         } else  if (!strcasecmp(colname, "fullname")) { 
2820                 ast_copy_string(buf, vmu->fullname, len);
2821         } else  if (!strcasecmp(colname, "email")) { 
2822                 if (!ast_strlen_zero(vmu->email))
2823                         ast_copy_string(buf, vmu->email, len);
2824                 else
2825                         snprintf(buf, len, "%s@%s", vmu->username, vmu->domain);
2826         } else  if (!strcasecmp(colname, "pager")) { 
2827                 ast_copy_string(buf, vmu->pager, len);
2828         } else  if (!strcasecmp(colname, "etemplate")) { 
2829                 if (!ast_strlen_zero(vmu->etemplate))
2830                         ast_copy_string(buf, vmu->etemplate, len);
2831                 else
2832                         ast_copy_string(buf, "email-default", len);
2833         } else  if (!strcasecmp(colname, "language")) { 
2834                 ast_copy_string(buf, vmu->language, len);
2835         } else  if (!strcasecmp(colname, "timezone")) { 
2836                 ast_copy_string(buf, vmu->zonetag, len);
2837         } else  if (!strcasecmp(colname, "ptemplate")) { 
2838                 if (!ast_strlen_zero(vmu->ptemplate))
2839                         ast_copy_string(buf, vmu->ptemplate, len);
2840                 else
2841                         ast_copy_string(buf, "email-default", len);
2842         } else  if (!strcasecmp(colname, "accountcode")) {
2843                 ast_copy_string(buf, vmu->accountcode, len);
2844         } else  if (!strcasecmp(colname, "pincode")) {
2845                 ast_copy_string(buf, vmu->pincode, len);
2846         } else  if (!strcasecmp(colname, "path")) {
2847                 check_dirpath(buf, len, vmu->domain, vmu->username, NULL);
2848         } else {        /* Look in channel variables */
2849                 struct ast_variable *var;
2850                 int found = 0;
2851
2852                 for (var = vmu->chanvars ; var ; var = var->next)
2853                         if (!strcmp(var->name, colname)) {
2854                                 ast_copy_string(buf, var->value, len);
2855                                 found = 1;
2856                                 break;
2857                         }
2858         }
2859
2860         if(ast_test_flag(vmu, MVM_ALLOCED))
2861                 free_user(vmu);
2862
2863         return 0;
2864 }
2865
2866 /*! \brief lock directory
2867
2868    only return failure if ast_lock_path returns 'timeout',
2869    not if the path does not exist or any other reason
2870 */
2871 static int vm_lock_path(const char *path)
2872 {
2873         switch (ast_lock_path(path)) {
2874         case AST_LOCK_TIMEOUT:
2875                 return -1;
2876         default:
2877                 return 0;
2878         }
2879 }
2880
2881 /*! \brief Access counter file, lock directory, read and possibly write it again changed 
2882         \param directory        Directory to crate file in
2883         \param countername      filename 
2884         \param value            If set to zero, we only read the variable
2885         \param operand          0 to read, 1 to set new value, 2 to change 
2886         \return -1 on error, otherwise counter value
2887 */
2888 static int access_counter_file(char *directory, char *countername, int value, int operand)
2889 {
2890         char filename[BUFSIZ];
2891         char readbuf[BUFSIZ];
2892         FILE *counterfile;
2893         int old = 0, counter = 0;
2894
2895         /* Lock directory */
2896         if (vm_lock_path(directory)) {
2897                 return -1;      /* Could not lock directory */
2898         }
2899         snprintf(filename, sizeof(filename), "%s/%s.counter", directory, countername);
2900         if (operand != 1) {
2901                 counterfile = fopen(filename, "r");
2902                 if (counterfile) {
2903                         if(fgets(readbuf, sizeof(readbuf), counterfile)) {
2904                                 if (option_debug > 2)
2905                                         ast_log(LOG_DEBUG, "Read this string from counter file: %s\n", readbuf);
2906                                 old = counter = atoi(readbuf);
2907                         }
2908                         fclose(counterfile);
2909                 }
2910         }
2911         switch (operand) {
2912         case 0: /* Read only */
2913                 ast_unlock_path(directory);
2914                 if (option_debug > 1)
2915                         ast_log(LOG_DEBUG, "MINIVM Counter %s/%s: Value %d\n", directory, countername, counter);
2916                 return counter;
2917                 break;
2918         case 1: /* Set new value */
2919                 counter = value;
2920                 break;
2921         case 2: /* Change value */
2922                 counter += value;
2923                 if (counter < 0)        /* Don't allow counters to fall below zero */
2924                         counter = 0;
2925                 break;
2926         }
2927         
2928         /* Now, write the new value to the file */
2929         counterfile = fopen(filename, "w");
2930         if (!counterfile) {
2931                 ast_log(LOG_ERROR, "Could not open counter file for writing : %s - %s\n", filename, strerror(errno));
2932                 ast_unlock_path(directory);
2933                 return -1;      /* Could not open file for writing */
2934         }
2935         fprintf(counterfile, "%d\n\n", counter);
2936         fclose(counterfile);
2937         ast_unlock_path(directory);
2938         if (option_debug > 1)
2939                 ast_log(LOG_DEBUG, "MINIVM Counter %s/%s: Old value %d New value %d\n", directory, countername, old, counter);
2940         return counter;
2941 }
2942
2943 /*! \brief  ${MINIVMCOUNTER()} Dialplan function - read counters */
2944 static int minivm_counter_func_read(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len)
2945 {
2946         char *username, *domain, *countername;
2947         struct minivm_account *vmu = NULL;
2948         char userpath[BUFSIZ];
2949         int res;
2950
2951         *buf = '\0';
2952
2953         if (!(username = ast_strdupa(data))) {  /* Copy indata to local buffer */
2954                 ast_log(LOG_WARNING, "Memory error!\n");
2955                 return -1;
2956         }
2957         if ((countername = strchr(username, ':'))) {
2958                 *countername = '\0';
2959                 countername++;
2960         } 
2961
2962         if ((domain = strchr(username, '@'))) {
2963                 *domain = '\0';
2964                 domain++;
2965         }
2966
2967         /* If we have neither username nor domain now, let's give up */
2968         if (ast_strlen_zero(username) && ast_strlen_zero(domain)) {
2969                 ast_log(LOG_ERROR, "No account given\n");
2970                 return -1;
2971         }
2972
2973         if (ast_strlen_zero(countername)) {
2974                 ast_log(LOG_ERROR, "This function needs two arguments: Account:countername\n");
2975                 return -1;
2976         }
2977
2978         /* We only have a domain, no username */
2979         if (!ast_strlen_zero(username) && ast_strlen_zero(domain)) {
2980                 domain = username;
2981                 username = NULL;
2982         }
2983
2984         /* If we can't find account or if the account is temporary, return. */
2985         if (!ast_strlen_zero(username) && !(vmu = find_account(domain, username, FALSE))) {
2986                 ast_log(LOG_ERROR, "Minivm account does not exist: %s@%s\n", username, domain);
2987                 return 0;
2988         }
2989
2990         create_dirpath(userpath, sizeof(userpath), domain, username, NULL);
2991
2992         /* We have the path, now read the counter file */
2993         res = access_counter_file(userpath, countername, 0, 0);
2994         if (res >= 0)
2995                 snprintf(buf, len, "%d", res);
2996         return 0;
2997 }
2998
2999 /*! \brief  ${MINIVMCOUNTER()} Dialplan function - changes counter data */
3000 static int minivm_counter_func_write(struct ast_channel *chan, const char *cmd, char *data, const char *value)
3001 {
3002         char *username, *domain, *countername, *operand;
3003         char userpath[BUFSIZ];
3004         struct minivm_account *vmu;
3005         int change = 0;
3006         int operation = 0;
3007
3008         if(!value)
3009                 return -1;
3010         change = atoi(value);
3011
3012         if (!(username = ast_strdupa(data))) {  /* Copy indata to local buffer */
3013                 ast_log(LOG_WARNING, "Memory error!\n");
3014                 return -1;
3015         }
3016
3017         if ((countername = strchr(username, ':'))) {
3018                 *countername = '\0';
3019                 countername++;
3020         } 
3021         if ((operand = strchr(countername, ':'))) {
3022                 *operand = '\0';
3023                 operand++;
3024         } 
3025
3026         if ((domain = strchr(username, '@'))) {
3027                 *domain = '\0';
3028                 domain++;
3029         }
3030
3031         /* If we have neither username nor domain now, let's give up */
3032         if (ast_strlen_zero(username) && ast_strlen_zero(domain)) {
3033                 ast_log(LOG_ERROR, "No account given\n");
3034                 return -1;
3035         }
3036
3037         /* We only have a domain, no username */
3038         if (!ast_strlen_zero(username) && ast_strlen_zero(domain)) {
3039                 domain = username;
3040                 username = NULL;
3041         }
3042
3043         if (ast_strlen_zero(operand) || ast_strlen_zero(countername)) {
3044                 ast_log(LOG_ERROR, "Writing to this function requires three arguments: Account:countername:operand\n");
3045                 return -1;
3046         }
3047
3048         /* If we can't find account or if the account is temporary, return. */
3049         if (!ast_strlen_zero(username) && !(vmu = find_account(domain, username, FALSE))) {
3050                 ast_log(LOG_ERROR, "Minivm account does not exist: %s@%s\n", username, domain);
3051                 return 0;
3052         }
3053
3054         create_dirpath(userpath, sizeof(userpath), domain, username, NULL);
3055         /* Now, find out our operator */
3056         if (*operand == 'i') /* Increment */
3057                 operation = 2;
3058         else if (*operand == 'd') {
3059                 change = change * -1;
3060                 operation = 2;
3061         } else if (*operand == 's')
3062                 operation = 1;
3063         else {
3064                 ast_log(LOG_ERROR, "Unknown operator: %s\n", operand);
3065                 return -1;
3066         }
3067
3068         /* We have the path, now read the counter file */
3069         access_counter_file(userpath, countername, change, operation);
3070         return 0;
3071 }
3072
3073
3074 /*! \brief CLI commands for Mini-voicemail */
3075 static struct ast_cli_entry cli_minivm[] = {
3076         { { "minivm", "list", "accounts", NULL },
3077         handle_minivm_show_users, "List defined mini-voicemail boxes",
3078         minivm_show_users_help, complete_minivm_show_users, NULL },
3079
3080         { { "minivm", "list", "zones", NULL },
3081         handle_minivm_show_zones, "List zone message formats",
3082         minivm_show_zones_help, NULL, NULL },
3083
3084         { { "minivm", "list", "templates", NULL },
3085         handle_minivm_list_templates, "List message templates",
3086         minivm_list_templates_help, NULL, NULL },
3087
3088         { { "minivm", "reload", NULL, NULL },
3089         handle_minivm_reload, "Reload Mini-voicemail configuration",
3090         minivm_reload_help, NULL, NULL },
3091
3092         { { "minivm", "show", "stats", NULL },
3093         handle_minivm_show_stats, "Show some mini-voicemail statistics",
3094         minivm_show_stats_help, NULL, NULL },
3095
3096         { { "minivm", "show", "settings", NULL },
3097         handle_minivm_show_settings, "Show mini-voicemail general settings",
3098         minivm_show_settings_help, NULL, NULL },
3099 };
3100
3101 static struct ast_custom_function minivm_counter_function = {
3102         .name = "MINIVMCOUNTER",
3103         .synopsis = "Reads or sets counters for MiniVoicemail message",
3104         .syntax = "MINIVMCOUNTER(<account>:name[:operand])",
3105         .read = minivm_counter_func_read,
3106         .write = minivm_counter_func_write,
3107         .desc = "Valid operands for changing the value of a counter when assigning a value are:\n"
3108         "- i   Increment by value\n"
3109         "- d   Decrement by value\n"
3110         "- s   Set to value\n"
3111         "\nThe counters never goes below zero.\n"
3112         "- The name of the counter is a string, up to 10 characters\n"
3113         "- If account is given and it exists, the counter is specific for the account\n"
3114         "- If account is a domain and the domain directory exists, counters are specific for a domain\n"
3115         "The operation is atomic and the counter is locked while changing the value\n"
3116         "\nThe counters are stored as text files in the minivm account directories. It might be better to use\n"
3117         "realtime functions if you are using a database to operate your Asterisk\n",
3118 };
3119
3120 static struct ast_custom_function minivm_account_function = {
3121         .name = "MINIVMACCOUNT",
3122         .synopsis = "Gets MiniVoicemail account information",
3123         .syntax = "MINIVMACCOUNT(<account>:item)",
3124         .read = minivm_account_func_read,
3125         .desc = "Valid items are:\n"
3126         "- path           Path to account mailbox (if account exists, otherwise temporary mailbox)\n"
3127         "- hasaccount     1 if static Minivm account exists, 0 otherwise\n"
3128         "- fullname       Full name of account owner\n"
3129         "- email          Email address used for account\n"
3130         "- etemplate      E-mail template for account (default template if none is configured)\n"   
3131         "- ptemplate      Pager template for account (default template if none is configured)\n"   
3132         "- accountcode    Account code for voicemail account\n"
3133         "- pincode        Pin code for voicemail account\n"
3134         "- timezone       Time zone for voicemail account\n"
3135         "- language       Language for voicemail account\n"
3136         "- <channel variable name> Channel variable value (set in configuration for account)\n"
3137         "\n",
3138 };
3139
3140 /*! \brief Load mini voicemail module */
3141 static int load_module(void)
3142 {
3143         int res;
3144
3145         res = ast_register_application(app_minivm_record, minivm_record_exec, synopsis_minivm_record, descrip_minivm_record);
3146         res = ast_register_application(app_minivm_greet, minivm_greet_exec, synopsis_minivm_greet, descrip_minivm_greet);
3147         res = ast_register_application(app_minivm_notify, minivm_notify_exec, synopsis_minivm_notify, descrip_minivm_notify);
3148         res = ast_register_application(app_minivm_delete, minivm_delete_exec, synopsis_minivm_delete, descrip_minivm_delete);
3149         res = ast_register_application(app_minivm_accmess, minivm_accmess_exec, synopsis_minivm_accmess, descrip_minivm_accmess);
3150
3151         ast_custom_function_register(&minivm_account_function);
3152         ast_custom_function_register(&minivm_counter_function);
3153         if (res)
3154                 return(res);
3155
3156         if ((res = load_config()))
3157                 return(res);
3158
3159         ast_cli_register_multiple(cli_minivm, sizeof(cli_minivm)/sizeof(cli_minivm[0]));
3160
3161         /* compute the location of the voicemail spool directory */
3162         snprintf(MVM_SPOOL_DIR, sizeof(MVM_SPOOL_DIR), "%s/voicemail/", ast_config_AST_SPOOL_DIR);
3163
3164         return res;
3165 }
3166
3167 /*! \brief Reload mini voicemail module */
3168 static int reload(void)
3169 {
3170         return(load_config());
3171 }
3172
3173 /*! \brief Reload cofiguration */
3174 static int handle_minivm_reload(int fd, int argc, char *argv[])
3175 {
3176         reload();
3177         ast_cli(fd, "\n-- Mini voicemail re-configured \n");
3178         return RESULT_SUCCESS;
3179 }
3180
3181 /*! \brief Unload mini voicemail module */
3182 static int unload_module(void)
3183 {
3184         int res;
3185         
3186         res = ast_unregister_application(app_minivm_record);
3187         res |= ast_unregister_application(app_minivm_greet);
3188         res |= ast_unregister_application(app_minivm_notify);
3189         res |= ast_unregister_application(app_minivm_delete);
3190         res |= ast_unregister_application(app_minivm_accmess);
3191         ast_cli_unregister_multiple(cli_minivm, sizeof(cli_minivm)/sizeof(cli_minivm[0]));
3192         ast_custom_function_unregister(&minivm_account_function);
3193         ast_custom_function_unregister(&minivm_counter_function);
3194
3195         message_destroy_list();         /* Destroy list of voicemail message templates */
3196         timezone_destroy_list();        /* Destroy list of timezones */
3197         vmaccounts_destroy_list();      /* Destroy list of voicemail accounts */
3198
3199         
3200         ast_module_user_hangup_all();
3201
3202         return res;
3203 }
3204
3205
3206 AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_DEFAULT, "Mini VoiceMail (A minimal Voicemail e-mail System)",
3207                 .load = load_module,
3208                 .unload = unload_module,
3209                 .reload = reload,
3210                 );