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