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