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