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