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