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