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