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