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