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