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