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.
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).
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()
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)
54 * - MINIVMACCOUNT() - A dialplan function
55 * - MINIVMCOUNTER() - Manage voicemail-related counters for accounts or domains
58 * - minivm list accounts
60 * - minivm list templates
62 * - minivm show settings
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().
76 * - Swedish, Sweden sv_se
77 * - Swedish, Finland sv_fi
78 * - English, USA en_us
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
151 #include <sys/time.h>
152 #include <sys/stat.h>
153 #include <sys/types.h>
154 #include <sys/mman.h>
159 #include "asterisk.h"
161 ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
163 #include "asterisk/astobj.h"
164 #include "asterisk/lock.h"
165 #include "asterisk/file.h"
166 #include "asterisk/logger.h"
167 #include "asterisk/channel.h"
168 #include "asterisk/pbx.h"
169 #include "asterisk/options.h"
170 #include "asterisk/config.h"
171 #include "asterisk/say.h"
172 #include "asterisk/module.h"
173 #include "asterisk/app.h"
174 #include "asterisk/manager.h"
175 #include "asterisk/dsp.h"
176 #include "asterisk/localtime.h"
177 #include "asterisk/cli.h"
178 #include "asterisk/utils.h"
179 #include "asterisk/linkedlists.h"
180 #include "asterisk/callerid.h"
190 #define MVM_REVIEW (1 << 0) /*!< Review message */
191 #define MVM_OPERATOR (1 << 1) /*!< Operator exit during voicemail recording */
192 #define MVM_REALTIME (1 << 2) /*!< This user is a realtime account */
193 #define MVM_SVMAIL (1 << 3)
194 #define MVM_ENVELOPE (1 << 4)
195 #define MVM_PBXSKIP (1 << 9)
196 #define MVM_ALLOCED (1 << 13)
198 /*! \brief Default mail command to mail voicemail. Change it with the
199 mailcmd= command in voicemail.conf */
200 #define SENDMAIL "/usr/sbin/sendmail -t"
202 #define SOUND_INTRO "vm-intro"
203 #define B64_BASEMAXINLINE 256 /*!< Buffer size for Base 64 attachment encoding */
204 #define B64_BASELINELEN 72 /*!< Line length for Base 64 endoded messages */
207 #define MAX_DATETIME_FORMAT 512
208 #define MAX_NUM_CID_CONTEXTS 10
210 #define ERROR_LOCK_PATH -100
211 #define VOICEMAIL_DIR_MODE 0700
213 #define VOICEMAIL_CONFIG "minivm.conf"
214 #define ASTERISK_USERNAME "asterisk" /*!< Default username for sending mail is asterisk\@localhost */
216 /*! \brief Message types for notification */
217 enum mvm_messagetype {
220 /* For trunk: MVM_MESSAGE_JABBER, */
223 static char MVM_SPOOL_DIR[PATH_MAX];
225 /* Module declarations */
226 static char *app_minivm_record = "MinivmRecord"; /* Leave a message */
227 static char *app_minivm_greet = "MinivmGreet"; /* Play voicemail prompts */
228 static char *app_minivm_notify = "MinivmNotify"; /* Notify about voicemail by using one of several methods */
229 static char *app_minivm_delete = "MinivmDelete"; /* Notify about voicemail by using one of several methods */
230 static char *app_minivm_accmess = "MinivmAccMess"; /* Record personal voicemail messages */
232 static char *synopsis_minivm_record = "Receive Mini-Voicemail and forward via e-mail";
233 static char *descrip_minivm_record =
234 "Syntax: MinivmRecord(username@domain[,options])\n"
235 "This application is part of the Mini-Voicemail system, configured in minivm.conf.\n"
236 "MiniVM records audio file in configured format and forwards message to e-mail and pager.\n"
237 "If there's no user account for that address, a temporary account will\n"
238 "be used with default options.\n"
239 "The recorded file name and path will be stored in MINIVM_FILENAME and the \n"
240 "duration of the message will be stored in MINIVM_DURATION\n"
241 "\nNote: If the caller hangs up after the recording, the only way to send\n"
242 "the message and clean up is to execute in the \"h\" extension.\n"
243 "\nThe application will exit if any of the following DTMF digits are \n"
244 "received and the requested extension exist in the current context.\n"
245 " 0 - Jump to the 'o' extension in the current dialplan context.\n"
246 " * - Jump to the 'a' extension in the current dialplan context.\n"
248 "Result is given in channel variable MINIVM_RECORD_STATUS\n"
249 " The possible values are: SUCCESS | USEREXIT | FAILED\n\n"
251 " g(#) - Use the specified amount of gain when recording the voicemail\n"
252 " message. The units are whole-number decibels (dB).\n"
255 static char *synopsis_minivm_greet = "Play Mini-Voicemail prompts";
256 static char *descrip_minivm_greet =
257 "Syntax: MinivmGreet(username@domain[,options])\n"
258 "This application is part of the Mini-Voicemail system, configured in minivm.conf.\n"
259 "MinivmGreet() plays default prompts or user specific prompts for an account.\n"
260 "Busy and unavailable messages can be choosen, but will be overridden if a temporary\n"
261 "message exists for the account.\n"
263 "Result is given in channel variable MINIVM_GREET_STATUS\n"
264 " The possible values are: SUCCESS | USEREXIT | FAILED\n\n"
266 " b - Play the 'busy' greeting to the calling party.\n"
267 " s - Skip the playback of instructions for leaving a message to the\n"
269 " u - Play the 'unavailable greeting.\n"
272 static char *synopsis_minivm_notify = "Notify voicemail owner about new messages.";
273 static char *descrip_minivm_notify =
274 "Syntax: MinivmNotify(username@domain[,template])\n"
275 "This application is part of the Mini-Voicemail system, configured in minivm.conf.\n"
276 "MiniVMnotify forwards messages about new voicemail to e-mail and pager.\n"
277 "If there's no user account for that address, a temporary account will\n"
278 "be used with default options (set in minivm.conf).\n"
279 "The recorded file name and path will be read from MVM_FILENAME and the \n"
280 "duration of the message will be accessed from MVM_DURATION (set by MinivmRecord() )\n"
281 "If the channel variable MVM_COUNTER is set, this will be used in the\n"
282 "message file name and available in the template for the message.\n"
283 "If not template is given, the default email template will be used to send email and\n"
284 "default pager template to send paging message (if the user account is configured with\n"
285 "a paging address.\n"
287 "Result is given in channel variable MINIVM_NOTIFY_STATUS\n"
288 " The possible values are: SUCCESS | FAILED\n"
291 static char *synopsis_minivm_delete = "Delete Mini-Voicemail voicemail messages";
292 static char *descrip_minivm_delete =
293 "Syntax: MinivmDelete(filename)\n"
294 "This application is part of the Mini-Voicemail system, configured in minivm.conf.\n"
295 "It deletes voicemail file set in MVM_FILENAME or given filename.\n"
297 "Result is given in channel variable MINIVM_DELETE_STATUS\n"
298 " The possible values are: SUCCESS | FAILED\n"
299 " FAILED is set if the file does not exist or can't be deleted.\n"
302 static char *synopsis_minivm_accmess = "Record account specific messages";
303 static char *descrip_minivm_accmess =
304 "Syntax: MinivmAccmess(username@domain,option)\n"
305 "This application is part of the Mini-Voicemail system, configured in minivm.conf.\n"
306 "Use this application to record account specific audio/video messages for\n"
307 "busy, unavailable and temporary messages.\n"
308 "Account specific directories will be created if they do not exist.\n"
309 "\nThe option selects message to be recorded:\n"
312 " t Temporary (overrides busy and unavailable)\n"
315 "Result is given in channel variable MINIVM_ACCMESS_STATUS\n"
316 " The possible values are: SUCCESS | FAILED\n"
317 " FAILED is set if the file can't be created.\n"
321 OPT_SILENT = (1 << 0),
322 OPT_BUSY_GREETING = (1 << 1),
323 OPT_UNAVAIL_GREETING = (1 << 2),
324 OPT_TEMP_GREETING = (1 << 3),
325 OPT_NAME_GREETING = (1 << 4),
326 OPT_RECORDGAIN = (1 << 5),
327 } minivm_option_flags;
330 OPT_ARG_RECORDGAIN = 0,
331 OPT_ARG_ARRAY_SIZE = 1,
332 } minivm_option_args;
334 AST_APP_OPTIONS(minivm_app_options, {
335 AST_APP_OPTION('s', OPT_SILENT),
336 AST_APP_OPTION('b', OPT_BUSY_GREETING),
337 AST_APP_OPTION('u', OPT_UNAVAIL_GREETING),
338 AST_APP_OPTION_ARG('g', OPT_RECORDGAIN, OPT_ARG_RECORDGAIN),
341 AST_APP_OPTIONS(minivm_accmess_options, {
342 AST_APP_OPTION('b', OPT_BUSY_GREETING),
343 AST_APP_OPTION('u', OPT_UNAVAIL_GREETING),
344 AST_APP_OPTION('t', OPT_TEMP_GREETING),
345 AST_APP_OPTION('n', OPT_NAME_GREETING),
348 /*! \brief Structure for linked list of Mini-Voicemail users: \ref minivm_accounts */
349 struct minivm_account {
350 char username[AST_MAX_CONTEXT]; /*!< Mailbox username */
351 char domain[AST_MAX_CONTEXT]; /*!< Voicemail domain */
353 char pincode[10]; /*!< Secret pin code, numbers only */
354 char fullname[120]; /*!< Full name, for directory app */
355 char email[80]; /*!< E-mail address - override */
356 char pager[80]; /*!< E-mail address to pager (no attachment) */
357 char accountcode[AST_MAX_ACCOUNT_CODE]; /*!< Voicemail account account code */
358 char serveremail[80]; /*!< From: Mail address */
359 char externnotify[160]; /*!< Configurable notification command */
360 char language[MAX_LANGUAGE]; /*!< Config: Language setting */
361 char zonetag[80]; /*!< Time zone */
362 char uniqueid[20]; /*!< Unique integer identifier */
363 char exit[80]; /*!< Options for exiting from voicemail() */
364 char attachfmt[80]; /*!< Format for voicemail audio file attachment */
365 char etemplate[80]; /*!< Pager template */
366 char ptemplate[80]; /*!< Voicemail format */
367 unsigned int flags; /*!< MVM_ flags */
368 struct ast_variable *chanvars; /*!< Variables for e-mail template */
369 double volgain; /*!< Volume gain for voicemails sent via e-mail */
370 AST_LIST_ENTRY(minivm_account) list;
373 /*! \brief The list of e-mail accounts */
374 static AST_LIST_HEAD_STATIC(minivm_accounts, minivm_account);
376 /*! \brief Linked list of e-mail templates in various languages
377 These are used as templates for e-mails, pager messages and jabber messages
378 \ref message_templates
380 struct minivm_template {
381 char name[80]; /*!< Template name */
382 char *body; /*!< Body of this template */
383 char fromaddress[100]; /*!< Who's sending the e-mail? */
384 char serveremail[80]; /*!< From: Mail address */
385 char subject[100]; /*!< Subject line */
386 char charset[32]; /*!< Default character set for this template */
387 char locale[20]; /*!< Locale for setlocale() */
388 char dateformat[80]; /*!< Date format to use in this attachment */
389 int attachment; /*!< Attachment of media yes/no - no for pager messages */
390 AST_LIST_ENTRY(minivm_template) list; /*!< List mechanics */
393 /*! \brief The list of e-mail templates */
394 static AST_LIST_HEAD_STATIC(message_templates, minivm_template);
396 /*! \brief Options for leaving voicemail with the voicemail() application */
397 struct leave_vm_options {
399 signed char record_gain;
402 /*! \brief Structure for base64 encoding */
408 unsigned char iobuf[B64_BASEMAXINLINE];
411 /*! \brief Voicemail time zones */
413 char name[80]; /*!< Name of this time zone */
414 char timezone[80]; /*!< Timezone definition */
415 char msg_format[BUFSIZ]; /*!< Not used in minivm ...yet */
416 AST_LIST_ENTRY(minivm_zone) list; /*!< List mechanics */
419 /*! \brief The list of e-mail time zones */
420 static AST_LIST_HEAD_STATIC(minivm_zones, minivm_zone);
422 /*! \brief Structure for gathering statistics */
423 struct minivm_stats {
424 int voicemailaccounts; /*!< Number of static accounts */
425 int timezones; /*!< Number of time zones */
426 int templates; /*!< Number of templates */
428 time_t reset; /*!< Time for last reset */
429 int receivedmessages; /*!< Number of received messages since reset */
430 time_t lastreceived; /*!< Time for last voicemail sent */
433 /*! \brief Statistics for voicemail */
434 static struct minivm_stats global_stats;
436 AST_MUTEX_DEFINE_STATIC(minivmlock); /*!< Lock to protect voicemail system */
437 AST_MUTEX_DEFINE_STATIC(minivmloglock); /*!< Lock to protect voicemail system log file */
439 FILE *minivmlogfile; /*!< The minivm log file */
441 static int global_vmminmessage; /*!< Minimum duration of messages */
442 static int global_vmmaxmessage; /*!< Maximum duration of message */
443 static int global_maxsilence; /*!< Maximum silence during recording */
444 static int global_maxgreet; /*!< Maximum length of prompts */
445 static int global_silencethreshold = 128;
446 static char global_mailcmd[160]; /*!< Configurable mail cmd */
447 static char global_externnotify[160]; /*!< External notification application */
448 static char global_logfile[PATH_MAX]; /*!< Global log file for messages */
449 static char default_vmformat[80];
451 static struct ast_flags globalflags = {0}; /*!< Global voicemail flags */
452 static int global_saydurationminfo;
453 static char global_charset[32]; /*!< Global charset in messages */
455 static double global_volgain; /*!< Volume gain for voicmemail via e-mail */
457 /*! \brief Default dateformat, can be overridden in configuration file */
458 #define DEFAULT_DATEFORMAT "%A, %B %d, %Y at %r"
459 #define DEFAULT_CHARSET "ISO-8859-1"
461 /* Forward declarations */
462 static char *message_template_parse_filebody(char *filename);
463 static char *message_template_parse_emailbody(const char *body);
464 static int create_vmaccount(char *name, struct ast_variable *var, int realtime);
465 static struct minivm_account *find_user_realtime(const char *domain, const char *username);
466 static int handle_minivm_reload(int fd, int argc, char *argv[]);
468 /*! \brief Create message template */
469 static struct minivm_template *message_template_create(const char *name)
471 struct minivm_template *template;
473 template = ast_calloc(1, sizeof(*template));
477 /* Set some defaults for templates */
478 ast_copy_string(template->name, name, sizeof(template->name));
479 ast_copy_string(template->dateformat, DEFAULT_DATEFORMAT, sizeof(template->dateformat));
480 ast_copy_string(template->charset, DEFAULT_CHARSET, sizeof(template->charset));
481 ast_copy_string(template->subject, "New message in mailbox ${MVM_USERNAME}@${MVM_DOMAIN}", sizeof(template->subject));
482 template->attachment = TRUE;
487 /*! \brief Release memory allocated by message template */
488 static void message_template_free(struct minivm_template *template)
491 ast_free(template->body);
496 /*! \brief Build message template from configuration */
497 static int message_template_build(const char *name, struct ast_variable *var)
499 struct minivm_template *template;
502 template = message_template_create(name);
504 ast_log(LOG_ERROR, "Out of memory, can't allocate message template object %s.\n", name);
509 ast_debug(3, "-_-_- Configuring template option %s = \"%s\" for template %s\n", var->name, var->value, name);
510 if (!strcasecmp(var->name, "fromaddress")) {
511 ast_copy_string(template->fromaddress, var->value, sizeof(template->fromaddress));
512 } else if (!strcasecmp(var->name, "fromemail")) {
513 ast_copy_string(template->serveremail, var->value, sizeof(template->serveremail));
514 } else if (!strcasecmp(var->name, "subject")) {
515 ast_copy_string(template->subject, var->value, sizeof(template->subject));
516 } else if (!strcasecmp(var->name, "locale")) {
517 ast_copy_string(template->locale, var->value, sizeof(template->locale));
518 } else if (!strcasecmp(var->name, "attachmedia")) {
519 template->attachment = ast_true(var->value);
520 } else if (!strcasecmp(var->name, "dateformat")) {
521 ast_copy_string(template->dateformat, var->value, sizeof(template->dateformat));
522 } else if (!strcasecmp(var->name, "charset")) {
523 ast_copy_string(template->charset, var->value, sizeof(template->charset));
524 } else if (!strcasecmp(var->name, "templatefile")) {
526 ast_free(template->body);
527 template->body = message_template_parse_filebody(var->value);
528 if (!template->body) {
529 ast_log(LOG_ERROR, "Error reading message body definition file %s\n", var->value);
532 } else if (!strcasecmp(var->name, "messagebody")) {
534 ast_free(template->body);
535 template->body = message_template_parse_emailbody(var->value);
536 if (!template->body) {
537 ast_log(LOG_ERROR, "Error parsing message body definition:\n %s\n", var->value);
541 ast_log(LOG_ERROR, "Unknown message template configuration option \"%s=%s\"\n", var->name, var->value);
547 ast_log(LOG_ERROR, "-- %d errors found parsing message template definition %s\n", error, name);
549 AST_LIST_LOCK(&message_templates);
550 AST_LIST_INSERT_TAIL(&message_templates, template, list);
551 AST_LIST_UNLOCK(&message_templates);
553 global_stats.templates++;
558 /*! \brief Find named template */
559 static struct minivm_template *message_template_find(const char *name)
561 struct minivm_template *this, *res = NULL;
563 if (ast_strlen_zero(name))
566 AST_LIST_LOCK(&message_templates);
567 AST_LIST_TRAVERSE(&message_templates, this, list) {
568 if (!strcasecmp(this->name, name)) {
573 AST_LIST_UNLOCK(&message_templates);
579 /*! \brief Clear list of templates */
580 static void message_destroy_list(void)
582 struct minivm_template *this;
583 AST_LIST_LOCK(&message_templates);
584 while ((this = AST_LIST_REMOVE_HEAD(&message_templates, list)))
585 message_template_free(this);
587 AST_LIST_UNLOCK(&message_templates);
590 /*! \brief read buffer from file (base64 conversion) */
591 static int b64_inbuf(struct b64_baseio *bio, FILE *fi)
598 if ((l = fread(bio->iobuf, 1, B64_BASEMAXINLINE,fi)) <= 0) {
612 /*! \brief read character from file to buffer (base64 conversion) */
613 static int b64_inchar(struct b64_baseio *bio, FILE *fi)
615 if (bio->iocp >= bio->iolen) {
616 if (!b64_inbuf(bio, fi))
620 return bio->iobuf[bio->iocp++];
623 /*! \brief write buffer to file (base64 conversion) */
624 static int b64_ochar(struct b64_baseio *bio, int c, FILE *so)
626 if (bio->linelength >= B64_BASELINELEN) {
627 if (fputs(EOL,so) == EOF)
633 if (putc(((unsigned char) c), so) == EOF)
641 /*! \brief Encode file to base64 encoding for email attachment (base64 conversion) */
642 static int base_encode(char *filename, FILE *so)
644 unsigned char dtable[B64_BASEMAXINLINE];
647 struct b64_baseio bio;
649 memset(&bio, 0, sizeof(bio));
650 bio.iocp = B64_BASEMAXINLINE;
652 if (!(fi = fopen(filename, "rb"))) {
653 ast_log(LOG_WARNING, "Failed to open file: %s: %s\n", filename, strerror(errno));
657 for (i= 0; i<9; i++) {
661 dtable[26+i+9]= 'j'+i;
663 for (i= 0; i < 8; i++) {
665 dtable[26+i+18]= 's'+i;
667 for (i= 0; i < 10; i++) {
674 unsigned char igroup[3], ogroup[4];
677 igroup[0]= igroup[1]= igroup[2]= 0;
679 for (n= 0; n < 3; n++) {
680 if ((c = b64_inchar(&bio, fi)) == EOF) {
684 igroup[n]= (unsigned char)c;
688 ogroup[0]= dtable[igroup[0]>>2];
689 ogroup[1]= dtable[((igroup[0]&3)<<4)|(igroup[1]>>4)];
690 ogroup[2]= dtable[((igroup[1]&0xF)<<2)|(igroup[2]>>6)];
691 ogroup[3]= dtable[igroup[2]&0x3F];
701 b64_ochar(&bio, ogroup[i], so);
705 /* Put end of line - line feed */
706 if (fputs(EOL, so) == EOF)
714 static int get_date(char *s, int len)
720 ast_localtime(&t, &tm, NULL);
721 return strftime(s, len, "%a %b %e %r %Z %Y", &tm);
725 /*! \brief Free user structure - if it's allocated */
726 static void free_user(struct minivm_account *vmu)
729 ast_variables_destroy(vmu->chanvars);
735 /*! \brief Prepare for voicemail template by adding channel variables
738 static void prep_email_sub_vars(struct ast_channel *channel, const struct minivm_account *vmu, const char *cidnum, const char *cidname, const char *dur, const char *date, const char *counter)
741 struct ast_variable *var;
744 ast_log(LOG_ERROR, "No allocated channel, giving up...\n");
748 for (var = vmu->chanvars ; var ; var = var->next)
749 pbx_builtin_setvar_helper(channel, var->name, var->value);
751 /* Prepare variables for substition in email body and subject */
752 pbx_builtin_setvar_helper(channel, "MVM_NAME", vmu->fullname);
753 pbx_builtin_setvar_helper(channel, "MVM_DUR", dur);
754 pbx_builtin_setvar_helper(channel, "MVM_DOMAIN", vmu->domain);
755 pbx_builtin_setvar_helper(channel, "MVM_USERNAME", vmu->username);
756 pbx_builtin_setvar_helper(channel, "MVM_CALLERID", ast_callerid_merge(callerid, sizeof(callerid), cidname, cidnum, "Unknown Caller"));
757 pbx_builtin_setvar_helper(channel, "MVM_CIDNAME", (cidname ? cidname : "an unknown caller"));
758 pbx_builtin_setvar_helper(channel, "MVM_CIDNUM", (cidnum ? cidnum : "an unknown caller"));
759 pbx_builtin_setvar_helper(channel, "MVM_DATE", date);
760 if (!ast_strlen_zero(counter))
761 pbx_builtin_setvar_helper(channel, "MVM_COUNTER", counter);
764 /*! \brief Set default values for Mini-Voicemail users */
765 static void populate_defaults(struct minivm_account *vmu)
767 ast_copy_flags(vmu, (&globalflags), AST_FLAGS_ALL);
768 ast_copy_string(vmu->attachfmt, default_vmformat, sizeof(vmu->attachfmt));
769 vmu->volgain = global_volgain;
772 /*! \brief Fix quote of mail headers for non-ascii characters */
773 static char *mailheader_quote(const char *from, char *to, size_t len)
777 for (; ptr < to + len - 1; from++) {
780 else if (*from == '\0')
784 if (ptr < to + len - 1)
791 /*! \brief Allocate new vm user and set default values */
792 static struct minivm_account *mvm_user_alloc(void)
794 struct minivm_account *new;
796 new = ast_calloc(1, sizeof(*new));
799 populate_defaults(new);
805 /*! \brief Clear list of users */
806 static void vmaccounts_destroy_list(void)
808 struct minivm_account *this;
809 AST_LIST_LOCK(&minivm_accounts);
810 while ((this = AST_LIST_REMOVE_HEAD(&minivm_accounts, list)))
812 AST_LIST_UNLOCK(&minivm_accounts);
816 /*! \brief Find user from static memory object list */
817 static struct minivm_account *find_account(const char *domain, const char *username, int createtemp)
819 struct minivm_account *vmu = NULL, *cur;
822 if (ast_strlen_zero(domain) || ast_strlen_zero(username)) {
823 ast_log(LOG_NOTICE, "No username or domain? \n");
826 ast_debug(3, "-_-_-_- Looking for voicemail user %s in domain %s\n", username, domain);
828 AST_LIST_LOCK(&minivm_accounts);
829 AST_LIST_TRAVERSE(&minivm_accounts, cur, list) {
830 /* Is this the voicemail account we're looking for? */
831 if (!strcasecmp(domain, cur->domain) && !strcasecmp(username, cur->username))
834 AST_LIST_UNLOCK(&minivm_accounts);
837 ast_debug(3, "-_-_- Found account for %s@%s\n", username, domain);
841 vmu = find_user_realtime(domain, username);
843 if (createtemp && !vmu) {
844 /* Create a temporary user, send e-mail and be gone */
845 vmu = mvm_user_alloc();
846 ast_set2_flag(vmu, TRUE, MVM_ALLOCED);
848 ast_copy_string(vmu->username, username, sizeof(vmu->username));
849 ast_copy_string(vmu->domain, domain, sizeof(vmu->domain));
850 ast_debug(1, "--- Created temporary account\n");
857 /*! \brief Find user in realtime storage
858 Returns pointer to minivm_account structure
860 static struct minivm_account *find_user_realtime(const char *domain, const char *username)
862 struct ast_variable *var;
863 struct minivm_account *retval;
864 char name[MAXHOSTNAMELEN];
866 retval = mvm_user_alloc();
871 ast_copy_string(retval->username, username, sizeof(retval->username));
873 populate_defaults(retval);
874 var = ast_load_realtime("minivm", "username", username, "domain", domain, NULL);
881 snprintf(name, sizeof(name), "%s@%s", username, domain);
882 create_vmaccount(name, var, TRUE);
884 ast_variables_destroy(var);
888 /*! \brief Send voicemail with audio file as an attachment */
889 static int sendmail(struct minivm_template *template, struct minivm_account *vmu, char *cidnum, char *cidname, const char *filename, char *format, int duration, int attach_user_voicemail, enum mvm_messagetype type, const char *counter)
893 char email[256] = "";
897 char fname[PATH_MAX];
899 char tmp[80] = "/tmp/astmail-XXXXXX";
903 struct minivm_zone *the_zone = NULL;
905 struct ast_channel *ast;
907 char *passdata = NULL;
908 char *passdata2 = NULL;
912 if (type == MVM_MESSAGE_EMAIL) {
913 if (vmu && !ast_strlen_zero(vmu->email)) {
914 ast_copy_string(email, vmu->email, sizeof(email));
915 } else if (!ast_strlen_zero(vmu->username) && !ast_strlen_zero(vmu->domain))
916 snprintf(email, sizeof(email), "%s@%s", vmu->username, vmu->domain);
917 } else if (type == MVM_MESSAGE_PAGE) {
918 ast_copy_string(email, vmu->pager, sizeof(email));
921 if (ast_strlen_zero(email)) {
922 ast_log(LOG_WARNING, "No address to send message to.\n");
926 ast_debug(3, "-_-_- Sending mail to %s@%s - Using template %s\n", vmu->username, vmu->domain, template->name);
928 if (!strcmp(format, "wav49"))
932 /* If we have a gain option, process it now with sox */
933 if (type == MVM_MESSAGE_EMAIL && (vmu->volgain < -.001 || vmu->volgain > .001) ) {
934 char newtmp[PATH_MAX];
935 char tmpcmd[PATH_MAX];
938 snprintf(newtmp, sizeof(newtmp), "/tmp/XXXXXX");
939 ast_debug(3, "newtmp: %s\n", newtmp);
940 tmpfd = mkstemp(newtmp);
941 snprintf(tmpcmd, sizeof(tmpcmd), "sox -v %.4f %s.%s %s.%s", vmu->volgain, filename, format, newtmp, format);
942 ast_safe_system(tmpcmd);
943 finalfilename = newtmp;
944 ast_debug(3, "-- VOLGAIN: Stored at: %s.%s - Level: %.4f - Mailbox: %s\n", filename, format, vmu->volgain, vmu->username);
946 finalfilename = ast_strdupa(filename);
949 /* Create file name */
950 snprintf(fname, sizeof(fname), "%s.%s", finalfilename, format);
952 if (template->attachment)
953 ast_debug(1, "-- Attaching file '%s', format '%s', uservm is '%d'\n", finalfilename, format, attach_user_voicemail);
955 /* Make a temporary file instead of piping directly to sendmail, in case the mail
959 p = fdopen(pfd, "w");
964 ast_debug(1, "-_-_- Opening temp file for e-mail: %s\n", tmp);
967 ast_log(LOG_WARNING, "Unable to open temporary file '%s'\n", tmp);
970 /* Allocate channel used for chanvar substitution */
971 ast = ast_channel_alloc(0, AST_STATE_DOWN, 0, 0, "", "", "", 0, 0);
974 snprintf(dur, sizeof(dur), "%d:%02d", duration / 60, duration % 60);
976 /* Does this user have a timezone specified? */
977 if (!ast_strlen_zero(vmu->zonetag)) {
978 /* Find the zone in the list */
979 struct minivm_zone *z;
980 AST_LIST_LOCK(&minivm_zones);
981 AST_LIST_TRAVERSE(&minivm_zones, z, list) {
982 if (strcmp(z->name, vmu->zonetag))
986 AST_LIST_UNLOCK(&minivm_zones);
990 ast_localtime(&now, &tm, the_zone ? the_zone->timezone : NULL);
991 strftime(date, sizeof(date), "%a, %d %b %Y %H:%M:%S %z", &tm);
993 /* Start printing the email to the temporary file */
994 fprintf(p, "Date: %s\n", date);
996 /* Set date format for voicemail mail */
997 strftime(date, sizeof(date), template->dateformat, &tm);
1000 /* Populate channel with channel variables for substitution */
1001 prep_email_sub_vars(ast, vmu, cidnum, cidname, dur, date, counter);
1003 /* Find email address to use */
1004 /* If there's a server e-mail adress in the account, user that, othterwise template */
1005 fromemail = ast_strlen_zero(vmu->serveremail) ? template->serveremail : vmu->serveremail;
1007 /* Find name to user for server e-mail */
1008 fromaddress = ast_strlen_zero(template->fromaddress) ? "" : template->fromaddress;
1010 /* If needed, add hostname as domain */
1011 if (ast_strlen_zero(fromemail))
1012 fromemail = "asterisk";
1014 if (strchr(fromemail, '@'))
1015 ast_copy_string(who, fromemail, sizeof(who));
1017 char host[MAXHOSTNAMELEN];
1018 gethostname(host, sizeof(host)-1);
1019 snprintf(who, sizeof(who), "%s@%s", fromemail, host);
1022 if (ast_strlen_zero(fromaddress)) {
1023 fprintf(p, "From: Asterisk PBX <%s>\n", who);
1025 ast_debug(4, "-_-_- Fromaddress template: %s\n", fromaddress);
1026 /* Allocate a buffer big enough for variable substitution */
1027 int vmlen = strlen(fromaddress) * 3 + 200;
1029 if ((passdata = alloca(vmlen))) {
1030 memset(passdata, 0, vmlen);
1031 pbx_substitute_variables_helper(ast, fromaddress, passdata, vmlen);
1032 len_passdata = strlen(passdata) * 2 + 3;
1033 passdata2 = alloca(len_passdata);
1034 fprintf(p, "From: %s <%s>\n", mailheader_quote(passdata, passdata2, len_passdata), who);
1036 ast_log(LOG_WARNING, "Cannot allocate workspace for variable substitution\n");
1041 ast_debug(4, "-_-_- Fromstring now: %s\n", ast_strlen_zero(passdata) ? "-default-" : passdata);
1043 fprintf(p, "Message-ID: <Asterisk-%d-%s-%d-%s>\n", (unsigned int)rand(), vmu->username, getpid(), who);
1044 len_passdata = strlen(vmu->fullname) * 2 + 3;
1045 passdata2 = alloca(len_passdata);
1046 if (!ast_strlen_zero(vmu->email))
1047 fprintf(p, "To: %s <%s>\n", mailheader_quote(vmu->fullname, passdata2, len_passdata), vmu->email);
1049 fprintf(p, "To: %s <%s@%s>\n", mailheader_quote(vmu->fullname, passdata2, len_passdata), vmu->username, vmu->domain);
1051 if (!ast_strlen_zero(template->subject)) {
1053 int vmlen = strlen(template->subject) * 3 + 200;
1054 if ((passdata = alloca(vmlen))) {
1055 memset(passdata, 0, vmlen);
1056 pbx_substitute_variables_helper(ast, template->subject, passdata, vmlen);
1057 fprintf(p, "Subject: %s\n", passdata);
1059 ast_log(LOG_WARNING, "Cannot allocate workspace for variable substitution\n");
1064 ast_debug(4, "-_-_- Subject now: %s\n", passdata);
1067 fprintf(p, "Subject: New message in mailbox %s@%s\n", vmu->username, vmu->domain);
1068 ast_debug(1, "-_-_- Using default subject for this email \n");
1072 if (option_debug > 2)
1073 fprintf(p, "X-Asterisk-debug: template %s user account %s@%s\n", template->name, vmu->username, vmu->domain);
1074 fprintf(p, "MIME-Version: 1.0\n");
1076 /* Something unique. */
1077 snprintf(bound, sizeof(bound), "voicemail_%s%d%d", vmu->username, getpid(), (unsigned int)rand());
1079 fprintf(p, "Content-Type: multipart/mixed; boundary=\"%s\"\n\n\n", bound);
1081 fprintf(p, "--%s\n", bound);
1082 fprintf(p, "Content-Type: text/plain; charset=%s\nContent-Transfer-Encoding: 8bit\n\n", global_charset);
1083 if (!ast_strlen_zero(template->body)) {
1085 int vmlen = strlen(template->body)*3 + 200;
1086 if ((passdata = alloca(vmlen))) {
1087 memset(passdata, 0, vmlen);
1088 pbx_substitute_variables_helper(ast, template->body, passdata, vmlen);
1089 ast_debug(3, "Message now: %s\n-----\n", passdata);
1090 fprintf(p, "%s\n", passdata);
1092 ast_log(LOG_WARNING, "Cannot allocate workspace for variable substitution\n");
1094 fprintf(p, "Dear %s:\n\n\tJust wanted to let you know you were just left a %s long message \n"
1096 "in mailbox %s from %s, on %s so you might\n"
1097 "want to check it when you get a chance. Thanks!\n\n\t\t\t\t--Asterisk\n\n", vmu->fullname,
1098 dur, vmu->username, (cidname ? cidname : (cidnum ? cidnum : "an unknown caller")), date);
1099 ast_debug(3, "Using default message body (no template)\n-----\n");
1101 /* Eww. We want formats to tell us their own MIME type */
1102 if (template->attachment) {
1103 ast_debug(3, "-_-_- Attaching file to message: %s\n", fname);
1104 char *ctype = "audio/x-";
1105 if (!strcasecmp(format, "ogg"))
1106 ctype = "application/";
1108 fprintf(p, "--%s\n", bound);
1109 fprintf(p, "Content-Type: %s%s; name=\"voicemailmsg.%s\"\n", ctype, format, format);
1110 fprintf(p, "Content-Transfer-Encoding: base64\n");
1111 fprintf(p, "Content-Description: Voicemail sound attachment.\n");
1112 fprintf(p, "Content-Disposition: attachment; filename=\"voicemail%s.%s\"\n\n", counter ? counter : "", format);
1114 base_encode(fname, p);
1115 fprintf(p, "\n\n--%s--\n.\n", bound);
1118 snprintf(tmp2, sizeof(tmp2), "( %s < %s ; rm -f %s ) &", global_mailcmd, tmp, tmp);
1119 ast_safe_system(tmp2);
1120 ast_debug(1, "Sent message to %s with command '%s' - %s\n", vmu->email, global_mailcmd, template->attachment ? "(media attachment)" : "");
1121 ast_debug(3, "-_-_- Actual command used: %s\n", tmp2);
1123 ast_channel_free(ast);
1127 /*! \brief Create directory based on components */
1128 static int make_dir(char *dest, int len, const char *domain, const char *username, const char *folder)
1130 return snprintf(dest, len, "%s%s/%s%s%s", MVM_SPOOL_DIR, domain, username, ast_strlen_zero(folder) ? "" : "/", folder ? folder : "");
1133 /*! \brief Checks if directory exists. Does not create directory, but builds string in dest
1134 * \param dest String. base directory.
1135 * \param len Int. Length base directory string.
1136 * \param domain String. Ignored if is null or empty string.
1137 * \param username String. Ignored if is null or empty string.
1138 * \param folder String. Ignored if is null or empty string.
1139 * \return 0 on failure, 1 on success.
1141 static int check_dirpath(char *dest, int len, char *domain, char *username, char *folder)
1143 struct stat filestat;
1144 make_dir(dest, len, domain, username, folder ? folder : "");
1145 if (stat(dest, &filestat)== -1)
1151 /*! \brief basically mkdir -p $dest/$domain/$username/$folder
1152 * \param dest String. base directory.
1153 * \param len Length of directory string
1154 * \param domain String. Ignored if is null or empty string.
1155 * \param folder String. Ignored if is null or empty string.
1156 * \param username String. Ignored if is null or empty string.
1157 * \return -1 on failure, 0 on success.
1159 static int create_dirpath(char *dest, int len, char *domain, char *username, char *folder)
1162 make_dir(dest, len, domain, username, folder);
1163 if ((res = ast_mkdir(dest, 0777))) {
1164 ast_log(LOG_WARNING, "ast_mkdir '%s' failed: %s\n", dest, strerror(res));
1167 ast_debug(2, "Creating directory for %s@%s folder %s : %s\n", username, domain, folder, dest);
1172 /*! \brief Play intro message before recording voicemail
1174 static int invent_message(struct ast_channel *chan, char *domain, char *username, int busy, char *ecodes)
1179 ast_debug(2, "-_-_- Still preparing to play message ...\n");
1181 snprintf(fn, sizeof(fn), "%s%s/%s/greet", MVM_SPOOL_DIR, domain, username);
1183 if (ast_fileexists(fn, NULL, NULL) > 0) {
1184 res = ast_streamfile(chan, fn, chan->language);
1187 res = ast_waitstream(chan, ecodes);
1191 int numericusername = 1;
1194 ast_debug(2, "-_-_- No personal prompts. Using default prompt set for language\n");
1197 ast_debug(2, "-_-_- Numeric? Checking %c\n", *i);
1199 numericusername = FALSE;
1205 if (numericusername) {
1206 if(ast_streamfile(chan, "vm-theperson", chan->language))
1208 if ((res = ast_waitstream(chan, ecodes)))
1211 res = ast_say_digit_str(chan, username, ecodes, chan->language);
1215 if(ast_streamfile(chan, "vm-theextensionis", chan->language))
1217 if ((res = ast_waitstream(chan, ecodes)))
1222 res = ast_streamfile(chan, busy ? "vm-isonphone" : "vm-isunavail", chan->language);
1225 res = ast_waitstream(chan, ecodes);
1229 /*! \brief Delete media files and attribute file */
1230 static int vm_delete(char *file)
1234 ast_debug(1, "-_-_- Deleting voicemail file %s\n", file);
1236 res = unlink(file); /* Remove the meta data file */
1237 res |= ast_filedelete(file, NULL); /* remove the media file */
1242 /*! \brief Record voicemail message & let caller review or re-record it, or set options if applicable */
1243 static int play_record_review(struct ast_channel *chan, char *playfile, char *recordfile, int maxtime, char *fmt,
1244 int outsidecaller, struct minivm_account *vmu, int *duration, const char *unlockdir,
1245 signed char record_gain)
1248 int max_attempts = 3;
1251 int message_exists = 0;
1252 signed char zero_gain = 0;
1253 char *acceptdtmf = "#";
1254 char *canceldtmf = "";
1256 /* Note that urgent and private are for flagging messages as such in the future */
1258 /* barf if no pointer passed to store duration in */
1259 if (duration == NULL) {
1260 ast_log(LOG_WARNING, "Error play_record_review called without duration pointer\n");
1264 cmd = '3'; /* Want to start by recording */
1266 while ((cmd >= 0) && (cmd != 't')) {
1270 if (option_verbose > 2)
1271 ast_verbose(VERBOSE_PREFIX_3 "Reviewing the message\n");
1272 ast_streamfile(chan, recordfile, chan->language);
1273 cmd = ast_waitstream(chan, AST_DIGIT_ANY);
1278 if (option_verbose > 2) {
1280 ast_verbose(VERBOSE_PREFIX_3 "Re-recording the message\n");
1282 ast_verbose(VERBOSE_PREFIX_3 "Recording the message\n");
1284 if (recorded && outsidecaller)
1285 cmd = ast_play_and_wait(chan, "beep");
1287 /* 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 */
1289 ast_channel_setoption(chan, AST_OPTION_RXGAIN, &record_gain, sizeof(record_gain), 0);
1290 if (ast_test_flag(vmu, MVM_OPERATOR))
1292 cmd = ast_play_and_record_full(chan, playfile, recordfile, maxtime, fmt, duration, global_silencethreshold, global_maxsilence, unlockdir, acceptdtmf, canceldtmf);
1294 ast_channel_setoption(chan, AST_OPTION_RXGAIN, &zero_gain, sizeof(zero_gain), 0);
1295 if (cmd == -1) /* User has hung up, no options to give */
1299 else if (cmd == '*')
1302 /* If all is well, a message exists */
1315 cmd = ast_play_and_wait(chan, "vm-sorry");
1318 if(!ast_test_flag(vmu, MVM_OPERATOR)) {
1319 cmd = ast_play_and_wait(chan, "vm-sorry");
1322 if (message_exists || recorded) {
1323 cmd = ast_play_and_wait(chan, "vm-saveoper");
1325 cmd = ast_waitfordigit(chan, 3000);
1327 ast_play_and_wait(chan, "vm-msgsaved");
1330 ast_play_and_wait(chan, "vm-deleted");
1331 vm_delete(recordfile);
1337 /* If the caller is an ouside caller, and the review option is enabled,
1338 allow them to review the message, but let the owner of the box review
1340 if (outsidecaller && !ast_test_flag(vmu, MVM_REVIEW))
1342 if (message_exists) {
1343 cmd = ast_play_and_wait(chan, "vm-review");
1345 cmd = ast_play_and_wait(chan, "vm-torerecord");
1347 cmd = ast_waitfordigit(chan, 600);
1350 if (!cmd && outsidecaller && ast_test_flag(vmu, MVM_OPERATOR)) {
1351 cmd = ast_play_and_wait(chan, "vm-reachoper");
1353 cmd = ast_waitfordigit(chan, 600);
1356 cmd = ast_waitfordigit(chan, 6000);
1360 if (attempts > max_attempts) {
1366 ast_play_and_wait(chan, "vm-goodbye");
1372 /*! \brief Run external notification for voicemail message */
1373 static void run_externnotify(struct ast_channel *chan, struct minivm_account *vmu)
1375 char arguments[BUFSIZ];
1377 if (ast_strlen_zero(vmu->externnotify) && ast_strlen_zero(global_externnotify))
1380 snprintf(arguments, sizeof(arguments), "%s %s@%s %s %s&",
1381 ast_strlen_zero(vmu->externnotify) ? global_externnotify : vmu->externnotify,
1382 vmu->username, vmu->domain,
1383 chan->cid.cid_name, chan->cid.cid_num);
1385 ast_debug(1, "Executing: %s\n", arguments);
1386 ast_safe_system(arguments);
1389 /*! \brief Send message to voicemail account owner */
1390 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)
1393 struct minivm_template *etemplate;
1394 char *messageformat;
1396 char oldlocale[100];
1397 const char *counter;
1399 if (!ast_strlen_zero(vmu->attachfmt)) {
1400 if (strstr(format, vmu->attachfmt)) {
1401 format = vmu->attachfmt;
1403 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);
1406 etemplate = message_template_find(vmu->etemplate);
1408 etemplate = message_template_find(templatename);
1410 etemplate = message_template_find("email-default");
1412 /* Attach only the first format */
1413 stringp = messageformat = ast_strdupa(format);
1414 strsep(&stringp, "|");
1416 if (!ast_strlen_zero(etemplate->locale)) {
1418 ast_copy_string(oldlocale, setlocale(LC_TIME, NULL), sizeof(oldlocale));
1419 ast_debug(2, "-_-_- Changing locale from %s to %s\n", oldlocale, etemplate->locale);
1420 newlocale = setlocale(LC_TIME, etemplate->locale);
1421 if (newlocale == NULL) {
1422 ast_log(LOG_WARNING, "-_-_- Changing to new locale did not work. Locale: %s\n", etemplate->locale);
1428 /* Read counter if available */
1429 counter = pbx_builtin_getvar_helper(chan, "MVM_COUNTER");
1430 if (ast_strlen_zero(counter)) {
1431 ast_debug(2, "-_-_- MVM_COUNTER not found\n");
1433 ast_debug(2, "-_-_- MVM_COUNTER found - will use it with value %s\n", counter);
1436 res = sendmail(etemplate, vmu, cidnum, cidname, filename, messageformat, duration, etemplate->attachment, MVM_MESSAGE_EMAIL, counter);
1438 if (res == 0 && !ast_strlen_zero(vmu->pager)) {
1439 /* Find template for paging */
1440 etemplate = message_template_find(vmu->ptemplate);
1442 etemplate = message_template_find("pager-default");
1443 if (etemplate->locale) {
1444 ast_copy_string(oldlocale, setlocale(LC_TIME, ""), sizeof(oldlocale));
1445 setlocale(LC_TIME, etemplate->locale);
1448 res = sendmail(etemplate, vmu, cidnum, cidname, filename, messageformat, duration, etemplate->attachment, MVM_MESSAGE_PAGE, counter);
1451 manager_event(EVENT_FLAG_CALL, "MiniVoiceMail", "Action: SentNotification\rn\nMailbox: %s@%s\r\nCounter: %s\r\n", vmu->username, vmu->domain, counter);
1453 run_externnotify(chan, vmu); /* Run external notification */
1455 if (etemplate->locale)
1456 setlocale(LC_TIME, oldlocale); /* Rest to old locale */
1461 /*! \brief Record voicemail message, store into file prepared for sending e-mail */
1462 static int leave_voicemail(struct ast_channel *chan, char *username, struct leave_vm_options *options)
1464 char tmptxtfile[PATH_MAX];
1467 int res = 0, txtdes;
1471 char tmpdir[PATH_MAX];
1472 char ext_context[256] = "";
1476 struct minivm_account *vmu;
1479 ast_copy_string(tmp, username, sizeof(tmp));
1481 domain = strchr(tmp, '@');
1487 if (!(vmu = find_account(domain, username, TRUE))) {
1488 /* We could not find user, let's exit */
1489 ast_log(LOG_ERROR, "Can't allocate temporary account for '%s@%s'\n", username, domain);
1490 pbx_builtin_setvar_helper(chan, "MINIVM_RECORD_STATUS", "FAILED");
1494 /* Setup pre-file if appropriate */
1495 if (strcmp(vmu->domain, "localhost"))
1496 snprintf(ext_context, sizeof(ext_context), "%s@%s", username, vmu->domain);
1498 ast_copy_string(ext_context, vmu->domain, sizeof(ext_context));
1500 /* The meat of recording the message... All the announcements and beeps have been played*/
1501 if (ast_strlen_zero(vmu->attachfmt))
1502 ast_copy_string(fmt, default_vmformat, sizeof(fmt));
1504 ast_copy_string(fmt, vmu->attachfmt, sizeof(fmt));
1506 if (ast_strlen_zero(fmt)) {
1507 ast_log(LOG_WARNING, "No format for saving voicemail? Default %s\n", default_vmformat);
1508 pbx_builtin_setvar_helper(chan, "MINIVM_RECORD_STATUS", "FAILED");
1513 userdir = check_dirpath(tmpdir, sizeof(tmpdir), vmu->domain, username, "tmp");
1515 /* If we have no user directory, use generic temporary directory */
1517 create_dirpath(tmpdir, sizeof(tmpdir), "0000_minivm_temp", "mediafiles", "");
1518 ast_debug(3, "Creating temporary directory %s\n", tmpdir);
1522 snprintf(tmptxtfile, sizeof(tmptxtfile), "%s/XXXXXX", tmpdir);
1525 /* XXX This file needs to be in temp directory */
1526 txtdes = mkstemp(tmptxtfile);
1528 ast_log(LOG_ERROR, "Unable to create message file %s: %s\n", tmptxtfile, strerror(errno));
1529 res = ast_streamfile(chan, "vm-mailboxfull", chan->language);
1531 res = ast_waitstream(chan, "");
1532 pbx_builtin_setvar_helper(chan, "MINIVM_RECORD_STATUS", "FAILED");
1537 /* Unless we're *really* silent, try to send the beep */
1538 res = ast_streamfile(chan, "beep", chan->language);
1540 res = ast_waitstream(chan, "");
1543 /* OEJ XXX Maybe this can be turned into a log file? Hmm. */
1544 /* Store information */
1545 ast_debug(2, "Open file for metadata: %s\n", tmptxtfile);
1547 res = play_record_review(chan, NULL, tmptxtfile, global_vmmaxmessage, fmt, 1, vmu, &duration, NULL, options->record_gain);
1549 txt = fdopen(txtdes, "w+");
1551 ast_log(LOG_WARNING, "Error opening text file for output\n");
1556 char logbuf[BUFSIZ];
1557 get_date(date, sizeof(date));
1559 ast_localtime(&now, &tm, NULL);
1560 strftime(timebuf, sizeof(timebuf), "%H:%M:%S", &tm);
1562 snprintf(logbuf, sizeof(logbuf),
1563 /* "Mailbox:domain:macrocontext:exten:priority:callerchan:callerid:origdate:origtime:duration:durationstatus:accountcode" */
1564 "%s:%s:%s:%s:%d:%s:%s:%s:%s:%d:%s:%s\n",
1571 ast_callerid_merge(callerid, sizeof(callerid), chan->cid.cid_name, chan->cid.cid_num, "Unknown"),
1575 duration < global_vmminmessage ? "IGNORED" : "OK",
1578 fprintf(txt, logbuf);
1579 if (minivmlogfile) {
1580 ast_mutex_lock(&minivmloglock);
1581 fprintf(minivmlogfile, logbuf);
1582 ast_mutex_unlock(&minivmloglock);
1585 if (duration < global_vmminmessage) {
1586 if (option_verbose > 2)
1587 ast_verbose( VERBOSE_PREFIX_3 "Recording was %d seconds long but needs to be at least %d - abandoning\n", duration, global_vmminmessage);
1589 ast_filedelete(tmptxtfile, NULL);
1591 pbx_builtin_setvar_helper(chan, "MINIVM_RECORD_STATUS", "FAILED");
1594 fclose(txt); /* Close log file */
1595 if (ast_fileexists(tmptxtfile, NULL, NULL) <= 0) {
1596 ast_debug(1, "The recorded media file is gone, so we should remove the .txt file too!\n");
1598 pbx_builtin_setvar_helper(chan, "MINIVM_RECORD_STATUS", "FAILED");
1599 if(ast_test_flag(vmu, MVM_ALLOCED))
1604 /* Set channel variables for the notify application */
1605 pbx_builtin_setvar_helper(chan, "MVM_FILENAME", tmptxtfile);
1606 snprintf(timebuf, sizeof(timebuf), "%d", duration);
1607 pbx_builtin_setvar_helper(chan, "MVM_DURATION", timebuf);
1608 pbx_builtin_setvar_helper(chan, "MVM_FORMAT", fmt);
1611 global_stats.lastreceived = time(NULL);
1612 global_stats.receivedmessages++;
1613 // /* Go ahead and delete audio files from system, they're not needed any more */
1614 // if (ast_fileexists(tmptxtfile, NULL, NULL) <= 0) {
1615 // ast_filedelete(tmptxtfile, NULL);
1616 // /* Even not being used at the moment, it's better to convert ast_log to ast_debug anyway */
1617 // ast_debug(2, "-_-_- Deleted audio file after notification :: %s \n", tmptxtfile);
1623 if(ast_test_flag(vmu, MVM_ALLOCED))
1626 pbx_builtin_setvar_helper(chan, "MINIVM_RECORD_STATUS", "SUCCESS");
1630 /*! \brief Notify voicemail account owners - either generic template or user specific */
1631 static int minivm_notify_exec(struct ast_channel *chan, void *data)
1633 struct ast_module_user *u;
1640 struct minivm_account *vmu;
1641 char *username = argv[0];
1642 const char *template = "";
1643 const char *filename;
1645 const char *duration_string;
1647 u = ast_module_user_add(chan);
1650 if (ast_strlen_zero(data)) {
1651 ast_log(LOG_ERROR, "Minivm needs at least an account argument \n");
1652 ast_module_user_remove(u);
1655 tmpptr = ast_strdupa((char *)data);
1657 ast_log(LOG_ERROR, "Out of memory\n");
1658 ast_module_user_remove(u);
1661 argc = ast_app_separate_args(tmpptr, '|', argv, sizeof(argv) / sizeof(argv[0]));
1663 if (argc == 2 && !ast_strlen_zero(argv[1]))
1666 ast_copy_string(tmp, argv[0], sizeof(tmp));
1668 domain = strchr(tmp, '@');
1673 if (ast_strlen_zero(domain) || ast_strlen_zero(username)) {
1674 ast_log(LOG_ERROR, "Need username@domain as argument. Sorry. Argument 0 %s\n", argv[0]);
1675 ast_module_user_remove(u);
1679 if(!(vmu = find_account(domain, username, TRUE))) {
1680 /* We could not find user, let's exit */
1681 ast_log(LOG_WARNING, "Could not allocate temporary memory for '%s@%s'\n", username, domain);
1682 pbx_builtin_setvar_helper(chan, "MINIVM_NOTIFY_STATUS", "FAILED");
1683 ast_module_user_remove(u);
1687 filename = pbx_builtin_getvar_helper(chan, "MVM_FILENAME");
1688 format = pbx_builtin_getvar_helper(chan, "MVM_FORMAT");
1689 duration_string = pbx_builtin_getvar_helper(chan, "MVM_DURATION");
1690 /* Notify of new message to e-mail and pager */
1691 if (!ast_strlen_zero(filename)) {
1692 res = notify_new_message(chan, template, vmu, filename, atoi(duration_string), format, chan->cid.cid_num, chan->cid.cid_name);
1695 pbx_builtin_setvar_helper(chan, "MINIVM_NOTIFY_STATUS", res == 0 ? "SUCCESS" : "FAILED");
1698 if(ast_test_flag(vmu, MVM_ALLOCED))
1701 /* Ok, we're ready to rock and roll. Return to dialplan */
1702 ast_module_user_remove(u);
1708 /*! \brief Dialplan function to record voicemail */
1709 static int minivm_record_exec(struct ast_channel *chan, void *data)
1712 struct ast_module_user *u;
1714 struct leave_vm_options leave_options;
1717 struct ast_flags flags = { 0 };
1718 char *opts[OPT_ARG_ARRAY_SIZE];
1720 u = ast_module_user_add(chan);
1722 memset(&leave_options, 0, sizeof(leave_options));
1724 /* Answer channel if it's not already answered */
1725 if (chan->_state != AST_STATE_UP)
1728 if (ast_strlen_zero(data)) {
1729 ast_log(LOG_ERROR, "Minivm needs at least an account argument \n");
1730 ast_module_user_remove(u);
1733 tmp = ast_strdupa((char *)data);
1735 ast_log(LOG_ERROR, "Out of memory\n");
1736 ast_module_user_remove(u);
1739 argc = ast_app_separate_args(tmp, '|', argv, sizeof(argv) / sizeof(argv[0]));
1741 if (ast_app_parse_options(minivm_app_options, &flags, opts, argv[1])) {
1742 ast_module_user_remove(u);
1745 ast_copy_flags(&leave_options, &flags, OPT_SILENT | OPT_BUSY_GREETING | OPT_UNAVAIL_GREETING );
1746 if (ast_test_flag(&flags, OPT_RECORDGAIN)) {
1749 if (sscanf(opts[OPT_ARG_RECORDGAIN], "%d", &gain) != 1) {
1750 ast_log(LOG_WARNING, "Invalid value '%s' provided for record gain option\n", opts[OPT_ARG_RECORDGAIN]);
1751 ast_module_user_remove(u);
1754 leave_options.record_gain = (signed char) gain;
1758 /* Now run the appliation and good luck to you! */
1759 res = leave_voicemail(chan, argv[0], &leave_options);
1761 if (res == ERROR_LOCK_PATH) {
1762 ast_log(LOG_ERROR, "Could not leave voicemail. The path is already locked.\n");
1763 pbx_builtin_setvar_helper(chan, "MINIVM_RECORD_STATUS", "FAILED");
1766 pbx_builtin_setvar_helper(chan, "MINIVM_RECORD_STATUS", "SUCCESS");
1769 ast_module_user_remove(u);
1774 /*! \brief Play voicemail prompts - either generic or user specific */
1775 static int minivm_greet_exec(struct ast_channel *chan, void *data)
1777 struct ast_module_user *u;
1778 struct leave_vm_options leave_options = { 0, '\0'};
1781 struct ast_flags flags = { 0 };
1782 char *opts[OPT_ARG_ARRAY_SIZE];
1788 char dest[PATH_MAX];
1789 char prefile[PATH_MAX];
1790 char tempfile[PATH_MAX] = "";
1791 char ext_context[256] = "";
1793 char ecodes[16] = "#";
1795 struct minivm_account *vmu;
1796 char *username = argv[0];
1798 u = ast_module_user_add(chan);
1800 if (ast_strlen_zero(data)) {
1801 ast_log(LOG_ERROR, "Minivm needs at least an account argument \n");
1802 ast_module_user_remove(u);
1805 tmpptr = ast_strdupa((char *)data);
1807 ast_log(LOG_ERROR, "Out of memory\n");
1808 ast_module_user_remove(u);
1811 argc = ast_app_separate_args(tmpptr, '|', argv, sizeof(argv) / sizeof(argv[0]));
1814 if (ast_app_parse_options(minivm_app_options, &flags, opts, argv[1])) {
1815 ast_module_user_remove(u);
1818 ast_copy_flags(&leave_options, &flags, OPT_SILENT | OPT_BUSY_GREETING | OPT_UNAVAIL_GREETING );
1821 ast_copy_string(tmp, argv[0], sizeof(tmp));
1823 domain = strchr(tmp, '@');
1828 if (ast_strlen_zero(domain) || ast_strlen_zero(username)) {
1829 ast_log(LOG_ERROR, "Need username@domain as argument. Sorry. Argument: %s\n", argv[0]);
1830 ast_module_user_remove(u);
1833 ast_debug(1, "-_-_- Trying to find configuration for user %s in domain %s\n", username, domain);
1835 if (!(vmu = find_account(domain, username, TRUE))) {
1836 ast_log(LOG_ERROR, "Could not allocate memory. \n");
1837 ast_module_user_remove(u);
1841 /* Answer channel if it's not already answered */
1842 if (chan->_state != AST_STATE_UP)
1845 /* Setup pre-file if appropriate */
1846 if (strcmp(vmu->domain, "localhost"))
1847 snprintf(ext_context, sizeof(ext_context), "%s@%s", username, vmu->domain);
1849 ast_copy_string(ext_context, vmu->domain, sizeof(ext_context));
1851 if (ast_test_flag(&leave_options, OPT_BUSY_GREETING)) {
1852 res = check_dirpath(dest, sizeof(dest), vmu->domain, username, "busy");
1854 snprintf(prefile, sizeof(prefile), "%s%s/%s/busy", MVM_SPOOL_DIR, vmu->domain, username);
1855 } else if (ast_test_flag(&leave_options, OPT_UNAVAIL_GREETING)) {
1856 res = check_dirpath(dest, sizeof(dest), vmu->domain, username, "unavail");
1858 snprintf(prefile, sizeof(prefile), "%s%s/%s/unavail", MVM_SPOOL_DIR, vmu->domain, username);
1860 /* Check for temporary greeting - it overrides busy and unavail */
1861 snprintf(tempfile, sizeof(tempfile), "%s%s/%s/temp", MVM_SPOOL_DIR, vmu->domain, username);
1862 if (!(res = check_dirpath(dest, sizeof(dest), vmu->domain, username, "temp"))) {
1863 ast_debug(2, "Temporary message directory does not exist, using default (%s)\n", tempfile);
1864 ast_copy_string(prefile, tempfile, sizeof(prefile));
1866 ast_debug(2, "-_-_- Preparing to play message ...\n");
1868 /* Check current or macro-calling context for special extensions */
1869 if (ast_test_flag(vmu, MVM_OPERATOR)) {
1870 if (!ast_strlen_zero(vmu->exit)) {
1871 if (ast_exists_extension(chan, vmu->exit, "o", 1, chan->cid.cid_num)) {
1872 strncat(ecodes, "0", sizeof(ecodes) - strlen(ecodes) - 1);
1875 } else if (ast_exists_extension(chan, chan->context, "o", 1, chan->cid.cid_num)) {
1876 strncat(ecodes, "0", sizeof(ecodes) - strlen(ecodes) - 1);
1879 else if (!ast_strlen_zero(chan->macrocontext) && ast_exists_extension(chan, chan->macrocontext, "o", 1, chan->cid.cid_num)) {
1880 strncat(ecodes, "0", sizeof(ecodes) - strlen(ecodes) - 1);
1885 if (!ast_strlen_zero(vmu->exit)) {
1886 if (ast_exists_extension(chan, vmu->exit, "a", 1, chan->cid.cid_num))
1887 strncat(ecodes, "*", sizeof(ecodes) - strlen(ecodes) - 1);
1888 } else if (ast_exists_extension(chan, chan->context, "a", 1, chan->cid.cid_num))
1889 strncat(ecodes, "*", sizeof(ecodes) - strlen(ecodes) - 1);
1890 else if (!ast_strlen_zero(chan->macrocontext) && ast_exists_extension(chan, chan->macrocontext, "a", 1, chan->cid.cid_num)) {
1891 strncat(ecodes, "*", sizeof(ecodes) - strlen(ecodes) - 1);
1895 res = 0; /* Reset */
1896 /* Play the beginning intro if desired */
1897 if (!ast_strlen_zero(prefile)) {
1898 if (ast_streamfile(chan, prefile, chan->language) > -1)
1899 res = ast_waitstream(chan, ecodes);
1901 ast_debug(2, "%s doesn't exist, doing what we can\n", prefile);
1902 res = invent_message(chan, vmu->domain, username, ast_test_flag(&leave_options, OPT_BUSY_GREETING), ecodes);
1905 ast_debug(2, "Hang up during prefile playback\n");
1906 pbx_builtin_setvar_helper(chan, "MINIVM_GREET_STATUS", "FAILED");
1907 if(ast_test_flag(vmu, MVM_ALLOCED))
1909 ast_module_user_remove(u);
1913 /* On a '#' we skip the instructions */
1914 ast_set_flag(&leave_options, OPT_SILENT);
1917 if (!res && !ast_test_flag(&leave_options, OPT_SILENT)) {
1918 res = ast_streamfile(chan, SOUND_INTRO, chan->language);
1920 res = ast_waitstream(chan, ecodes);
1922 ast_set_flag(&leave_options, OPT_SILENT);
1927 ast_stopstream(chan);
1928 /* Check for a '*' here in case the caller wants to escape from voicemail to something
1929 other than the operator -- an automated attendant or mailbox login for example */
1931 chan->exten[0] = 'a';
1932 chan->exten[1] = '\0';
1933 if (!ast_strlen_zero(vmu->exit)) {
1934 ast_copy_string(chan->context, vmu->exit, sizeof(chan->context));
1935 } else if (ausemacro && !ast_strlen_zero(chan->macrocontext)) {
1936 ast_copy_string(chan->context, chan->macrocontext, sizeof(chan->context));
1939 pbx_builtin_setvar_helper(chan, "MINIVM_GREET_STATUS", "USEREXIT");
1941 } else if (res == '0') { /* Check for a '0' here */
1942 if(ouseexten || ousemacro) {
1943 chan->exten[0] = 'o';
1944 chan->exten[1] = '\0';
1945 if (!ast_strlen_zero(vmu->exit)) {
1946 ast_copy_string(chan->context, vmu->exit, sizeof(chan->context));
1947 } else if (ousemacro && !ast_strlen_zero(chan->macrocontext)) {
1948 ast_copy_string(chan->context, chan->macrocontext, sizeof(chan->context));
1950 ast_play_and_wait(chan, "transfer");
1952 pbx_builtin_setvar_helper(chan, "MINIVM_GREET_STATUS", "USEREXIT");
1955 } else if (res < 0) {
1956 pbx_builtin_setvar_helper(chan, "MINIVM_GREET_STATUS", "FAILED");
1959 pbx_builtin_setvar_helper(chan, "MINIVM_GREET_STATUS", "SUCCESS");
1961 if(ast_test_flag(vmu, MVM_ALLOCED))
1965 /* Ok, we're ready to rock and roll. Return to dialplan */
1966 ast_module_user_remove(u);
1972 /*! \brief Dialplan application to delete voicemail */
1973 static int minivm_delete_exec(struct ast_channel *chan, void *data)
1976 struct ast_module_user *u;
1977 char filename[BUFSIZ];
1979 u = ast_module_user_add(chan);
1981 if (!ast_strlen_zero(data))
1982 ast_copy_string(filename, (char *) data, sizeof(filename));
1984 ast_copy_string(filename, pbx_builtin_getvar_helper(chan, "MVM_FILENAME"), sizeof(filename));
1986 if (ast_strlen_zero(filename)) {
1987 ast_module_user_remove(u);
1988 ast_log(LOG_ERROR, "No filename given in application arguments or channel variable MVM_FILENAME\n");
1992 /* Go ahead and delete audio files from system, they're not needed any more */
1993 /* We should look for both audio and text files here */
1994 if (ast_fileexists(filename, NULL, NULL) > 0) {
1995 res = vm_delete(filename);
1997 ast_debug(2, "-_-_- Can't delete file: %s\n", filename);
1998 pbx_builtin_setvar_helper(chan, "MINIVM_DELETE_STATUS", "FAILED");
2000 ast_debug(2, "-_-_- Deleted voicemail file :: %s \n", filename);
2001 pbx_builtin_setvar_helper(chan, "MINIVM_DELETE_STATUS", "SUCCESS");
2004 ast_debug(2, "-_-_- Filename does not exist: %s\n", filename);
2005 pbx_builtin_setvar_helper(chan, "MINIVM_DELETE_STATUS", "FAILED");
2008 ast_module_user_remove(u);
2013 /*! \brief Record specific messages for voicemail account */
2014 static int minivm_accmess_exec(struct ast_channel *chan, void *data)
2016 struct ast_module_user *u;
2020 char filename[PATH_MAX];
2024 struct minivm_account *vmu;
2025 char *username = argv[0];
2026 struct ast_flags flags = { 0 };
2027 char *opts[OPT_ARG_ARRAY_SIZE];
2029 char *message = NULL;
2030 char *prompt = NULL;
2034 u = ast_module_user_add(chan);
2036 if (ast_strlen_zero(data)) {
2037 ast_log(LOG_ERROR, "MinivmAccmess needs at least two arguments: account and option\n");
2040 tmpptr = ast_strdupa((char *)data);
2043 ast_log(LOG_ERROR, "Out of memory\n");
2046 argc = ast_app_separate_args(tmpptr, '|', argv, sizeof(argv) / sizeof(argv[0]));
2050 ast_log(LOG_ERROR, "MinivmAccmess needs at least two arguments: account and option\n");
2053 if (!error && strlen(argv[1]) > 1) {
2054 ast_log(LOG_ERROR, "MinivmAccmess can only handle one option at a time. Bad option string: %s\n", argv[1]);
2058 if (!error && ast_app_parse_options(minivm_accmess_options, &flags, opts, argv[1])) {
2059 ast_log(LOG_ERROR, "Can't parse option %s\n", argv[1]);
2064 ast_module_user_remove(u);
2068 ast_copy_string(tmp, argv[0], sizeof(tmp));
2070 domain = strchr(tmp, '@');
2075 if (ast_strlen_zero(domain) || ast_strlen_zero(username)) {
2076 ast_log(LOG_ERROR, "Need username@domain as argument. Sorry. Argument 0 %s\n", argv[0]);
2077 ast_module_user_remove(u);
2081 if(!(vmu = find_account(domain, username, TRUE))) {
2082 /* We could not find user, let's exit */
2083 ast_log(LOG_WARNING, "Could not allocate temporary memory for '%s@%s'\n", username, domain);
2084 pbx_builtin_setvar_helper(chan, "MINIVM_NOTIFY_STATUS", "FAILED");
2085 ast_module_user_remove(u);
2089 /* Answer channel if it's not already answered */
2090 if (chan->_state != AST_STATE_UP)
2093 /* Here's where the action is */
2094 if (ast_test_flag(&flags, OPT_BUSY_GREETING)) {
2096 prompt = "vm-rec-busy";
2097 } else if (ast_test_flag(&flags, OPT_UNAVAIL_GREETING)) {
2098 message = "unavailable";
2099 prompt = "vm-rec-unavail";
2100 } else if (ast_test_flag(&flags, OPT_TEMP_GREETING)) {
2102 prompt = "vm-temp-greeting";
2103 } else if (ast_test_flag(&flags, OPT_NAME_GREETING)) {
2105 prompt = "vm-rec-name";
2107 snprintf(filename,sizeof(filename), "%s%s/%s/%s", MVM_SPOOL_DIR, vmu->domain, vmu->username, message);
2108 /* Maybe we should check the result of play_record_review ? */
2109 cmd = play_record_review(chan, prompt, filename, global_maxgreet, default_vmformat, 0, vmu, &duration, NULL, FALSE);
2111 ast_debug(1, "Recorded new %s message in %s (duration %d)\n", message, filename, duration);
2113 if(ast_test_flag(vmu, MVM_ALLOCED))
2117 /* Ok, we're ready to rock and roll. Return to dialplan */
2118 ast_module_user_remove(u);
2124 /*! \brief Append new mailbox to mailbox list from configuration file */
2125 static int create_vmaccount(char *name, struct ast_variable *var, int realtime)
2127 struct minivm_account *vmu;
2130 char accbuf[BUFSIZ];
2132 ast_debug(3, "Creating %s account for [%s]\n", realtime ? "realtime" : "static", name);
2134 ast_copy_string(accbuf, name, sizeof(accbuf));
2136 domain = strchr(accbuf, '@');
2141 if (ast_strlen_zero(domain)) {
2142 ast_log(LOG_ERROR, "No domain given for mini-voicemail account %s. Not configured.\n", name);
2146 ast_debug(3, "Creating static account for user %s domain %s\n", username, domain);
2148 /* Allocate user account */
2149 vmu = ast_calloc(1, sizeof(*vmu));
2153 ast_copy_string(vmu->domain, domain, sizeof(vmu->domain));
2154 ast_copy_string(vmu->username, username, sizeof(vmu->username));
2156 populate_defaults(vmu);
2158 ast_debug(3, "...Configuring account %s\n", name);
2161 ast_debug(3, "---- Configuring %s = \"%s\" for account %s\n", var->name, var->value, name);
2162 if (!strcasecmp(var->name, "serveremail")) {
2163 ast_copy_string(vmu->serveremail, var->value, sizeof(vmu->serveremail));
2164 } else if (!strcasecmp(var->name, "email")) {
2165 ast_copy_string(vmu->email, var->value, sizeof(vmu->email));
2166 } else if (!strcasecmp(var->name, "accountcode")) {
2167 ast_copy_string(vmu->accountcode, var->value, sizeof(vmu->accountcode));
2168 } else if (!strcasecmp(var->name, "pincode")) {
2169 ast_copy_string(vmu->pincode, var->value, sizeof(vmu->pincode));
2170 } else if (!strcasecmp(var->name, "domain")) {
2171 ast_copy_string(vmu->domain, var->value, sizeof(vmu->domain));
2172 } else if (!strcasecmp(var->name, "language")) {
2173 ast_copy_string(vmu->language, var->value, sizeof(vmu->language));
2174 } else if (!strcasecmp(var->name, "timezone")) {
2175 ast_copy_string(vmu->zonetag, var->value, sizeof(vmu->zonetag));
2176 } else if (!strcasecmp(var->name, "externnotify")) {
2177 ast_copy_string(vmu->externnotify, var->value, sizeof(vmu->externnotify));
2178 } else if (!strcasecmp(var->name, "etemplate")) {
2179 ast_copy_string(vmu->etemplate, var->value, sizeof(vmu->etemplate));
2180 } else if (!strcasecmp(var->name, "ptemplate")) {
2181 ast_copy_string(vmu->ptemplate, var->value, sizeof(vmu->ptemplate));
2182 } else if (!strcasecmp(var->name, "fullname")) {
2183 ast_copy_string(vmu->fullname, var->value, sizeof(vmu->fullname));
2184 } else if (!strcasecmp(var->name, "setvar")) {
2186 char *varname = ast_strdupa(var->value);
2187 struct ast_variable *tmpvar;
2189 if (varname && (varval = strchr(varname, '='))) {
2192 if ((tmpvar = ast_variable_new(varname, varval))) {
2193 tmpvar->next = vmu->chanvars;
2194 vmu->chanvars = tmpvar;
2197 } else if (!strcasecmp(var->name, "pager")) {
2198 ast_copy_string(vmu->pager, var->value, sizeof(vmu->pager));
2199 } else if (!strcasecmp(var->name, "volgain")) {
2200 sscanf(var->value, "%lf", &vmu->volgain);
2202 ast_log(LOG_ERROR, "Unknown configuration option for minivm account %s : %s\n", name, var->name);
2206 ast_debug(3, "...Linking account %s\n", name);
2208 AST_LIST_LOCK(&minivm_accounts);
2209 AST_LIST_INSERT_TAIL(&minivm_accounts, vmu, list);
2210 AST_LIST_UNLOCK(&minivm_accounts);
2212 global_stats.voicemailaccounts++;
2214 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)" : "");
2218 /*! \brief Free Mini Voicemail timezone */
2219 static void free_zone(struct minivm_zone *z)
2224 /*! \brief Clear list of timezones */
2225 static void timezone_destroy_list(void)
2227 struct minivm_zone *this;
2229 AST_LIST_LOCK(&minivm_zones);
2230 while ((this = AST_LIST_REMOVE_HEAD(&minivm_zones, list)))
2233 AST_LIST_UNLOCK(&minivm_zones);
2236 /*! \brief Add time zone to memory list */
2237 static int timezone_add(char *zonename, char *config)
2240 struct minivm_zone *newzone;
2241 char *msg_format, *timezone;
2243 newzone = ast_calloc(1, sizeof(*newzone));
2244 if (newzone == NULL)
2247 msg_format = ast_strdupa(config);
2248 if (msg_format == NULL) {
2249 ast_log(LOG_WARNING, "Out of memory.\n");
2254 timezone = strsep(&msg_format, "|");
2256 ast_log(LOG_WARNING, "Invalid timezone definition : %s\n", zonename);
2261 ast_copy_string(newzone->name, zonename, sizeof(newzone->name));
2262 ast_copy_string(newzone->timezone, timezone, sizeof(newzone->timezone));
2263 ast_copy_string(newzone->msg_format, msg_format, sizeof(newzone->msg_format));
2265 AST_LIST_LOCK(&minivm_zones);
2266 AST_LIST_INSERT_TAIL(&minivm_zones, newzone, list);
2267 AST_LIST_UNLOCK(&minivm_zones);
2269 global_stats.timezones++;
2274 /*! \brief Read message template from file */
2275 static char *message_template_parse_filebody(char *filename) {
2276 char buf[BUFSIZ * 6];
2277 char readbuf[BUFSIZ];
2278 char filenamebuf[BUFSIZ];
2284 if (ast_strlen_zero(filename))
2286 if (*filename == '/')
2287 ast_copy_string(filenamebuf, filename, sizeof(filenamebuf));
2289 snprintf(filenamebuf, sizeof(filenamebuf), "%s/%s", ast_config_AST_CONFIG_DIR, filename);
2291 if (!(fi = fopen(filenamebuf, "r"))) {
2292 ast_log(LOG_ERROR, "Can't read message template from file: %s\n", filenamebuf);
2296 while (fgets(readbuf, sizeof(readbuf), fi)) {
2298 if (writepos != buf) {
2299 *writepos = '\n'; /* Replace EOL with new line */
2302 ast_copy_string(writepos, readbuf, sizeof(buf) - (writepos - buf));
2303 writepos += strlen(readbuf) - 1;
2306 messagebody = ast_calloc(1, strlen(buf + 1));
2307 ast_copy_string(messagebody, buf, strlen(buf) + 1);
2308 ast_debug(4, "---> Size of allocation %d\n", (int) strlen(buf + 1) );
2309 ast_debug(4, "---> Done reading message template : \n%s\n---- END message template--- \n", messagebody);
2314 /*! \brief Parse emailbody template from configuration file */
2315 static char *message_template_parse_emailbody(const char *configuration)
2317 char *tmpread, *tmpwrite;
2318 char *emailbody = ast_strdup(configuration);
2320 /* substitute strings \t and \n into the apropriate characters */
2321 tmpread = tmpwrite = emailbody;
2322 while ((tmpwrite = strchr(tmpread,'\\'))) {
2323 int len = strlen("\n");
2324 switch (tmpwrite[1]) {
2326 strncpy(tmpwrite+len, tmpwrite+2, strlen(tmpwrite+2)+1);
2327 strncpy(tmpwrite, "\n", len);
2330 strncpy(tmpwrite+len, tmpwrite+2, strlen(tmpwrite+2)+1);
2331 strncpy(tmpwrite, "\t", len);
2334 ast_log(LOG_NOTICE, "Substitution routine does not support this character: %c\n", tmpwrite[1]);
2336 tmpread = tmpwrite + len;
2341 /*! \brief Apply general configuration options */
2342 static int apply_general_options(struct ast_variable *var)
2348 if (!strcmp(var->name, "mailcmd")) {
2349 ast_copy_string(global_mailcmd, var->value, sizeof(global_mailcmd)); /* User setting */
2350 } else if (!strcmp(var->name, "maxgreet")) {
2351 global_maxgreet = atoi(var->value);
2352 } else if (!strcmp(var->name, "maxsilence")) {
2353 global_maxsilence = atoi(var->value);
2354 if (global_maxsilence > 0)
2355 global_maxsilence *= 1000;
2356 } else if (!strcmp(var->name, "logfile")) {
2357 if (!ast_strlen_zero(var->value) ) {
2358 if(*(var->value) == '/')
2359 ast_copy_string(global_logfile, var->value, sizeof(global_logfile));
2361 snprintf(global_logfile, sizeof(global_logfile), "%s/%s", ast_config_AST_LOG_DIR, var->value);
2363 } else if (!strcmp(var->name, "externnotify")) {
2364 /* External voicemail notify application */
2365 ast_copy_string(global_externnotify, var->value, sizeof(global_externnotify));
2366 } else if (!strcmp(var->name, "silencetreshold")) {
2367 /* Silence treshold */
2368 global_silencethreshold = atoi(var->value);
2369 } else if (!strcmp(var->name, "maxmessage")) {
2371 if (sscanf(var->value, "%d", &x) == 1) {
2372 global_vmmaxmessage = x;
2375 ast_log(LOG_WARNING, "Invalid max message time length\n");
2377 } else if (!strcmp(var->name, "minmessage")) {
2379 if (sscanf(var->value, "%d", &x) == 1) {
2380 global_vmminmessage = x;
2381 if (global_maxsilence <= global_vmminmessage)
2382 ast_log(LOG_WARNING, "maxsilence should be less than minmessage or you may get empty messages\n");
2385 ast_log(LOG_WARNING, "Invalid min message time length\n");
2387 } else if (!strcmp(var->name, "format")) {
2388 ast_copy_string(default_vmformat, var->value, sizeof(default_vmformat));
2389 } else if (!strcmp(var->name, "review")) {
2390 ast_set2_flag((&globalflags), ast_true(var->value), MVM_REVIEW);
2391 } else if (!strcmp(var->name, "operator")) {
2392 ast_set2_flag((&globalflags), ast_true(var->value), MVM_OPERATOR);
2399 /*! \brief Load minivoicemail configuration */
2400 static int load_config(void)
2402 struct ast_config *cfg;
2403 struct ast_variable *var;
2405 const char *chanvar;
2408 cfg = ast_config_load(VOICEMAIL_CONFIG);
2409 ast_mutex_lock(&minivmlock);
2411 /* Destroy lists to reconfigure */
2412 message_destroy_list(); /* Destroy list of voicemail message templates */
2413 timezone_destroy_list(); /* Destroy list of timezones */
2414 vmaccounts_destroy_list(); /* Destroy list of voicemail accounts */
2415 ast_debug(2, "Destroyed memory objects...\n");
2417 /* First, set some default settings */
2418 global_externnotify[0] = '\0';
2419 global_logfile[0] = '\0';
2420 global_silencethreshold = 256;
2421 global_vmmaxmessage = 2000;
2422 global_maxgreet = 2000;
2423 global_vmminmessage = 0;
2424 strcpy(global_mailcmd, SENDMAIL);
2425 global_maxsilence = 0;
2426 global_saydurationminfo = 2;
2427 ast_copy_string(default_vmformat, "wav", sizeof(default_vmformat));
2428 ast_set2_flag((&globalflags), FALSE, MVM_REVIEW);
2429 ast_set2_flag((&globalflags), FALSE, MVM_OPERATOR);
2430 strcpy(global_charset, "ISO-8859-1");
2431 struct minivm_template *template;
2432 /* Reset statistics */
2433 memset(&global_stats, 0, sizeof(struct minivm_stats));
2434 global_stats.reset = time(NULL);
2436 /* Make sure we could load configuration file */
2438 ast_log(LOG_WARNING, "Failed to load configuration file. Module activated with default settings.\n");
2439 ast_mutex_unlock(&minivmlock);
2443 ast_debug(2, "-_-_- Loaded configuration file, now parsing\n");
2445 /* General settings */
2447 cat = ast_category_browse(cfg, NULL);
2449 ast_debug(3, "-_-_- Found configuration section [%s]\n", cat);
2450 if (!strcasecmp(cat, "general")) {
2451 /* Nothing right now */
2452 error += apply_general_options(ast_variable_browse(cfg, cat));
2453 } else if (!strncasecmp(cat, "template-", 9)) {
2455 char *name = cat + 9;
2457 /* Now build and link template to list */
2458 error += message_template_build(name, ast_variable_browse(cfg, cat));
2460 var = ast_variable_browse(cfg, cat);
2461 if (!strcasecmp(cat, "zonemessages")) {
2462 /* Timezones in this context */
2464 timezone_add(var->name, var->value);
2468 /* Create mailbox from this */
2469 error += create_vmaccount(cat, var, FALSE);
2472 /* Find next section in configuration file */
2473 cat = ast_category_browse(cfg, cat);
2476 /* Configure the default email template */
2477 message_template_build("email-default", NULL);
2478 template = message_template_find("email-default");
2480 /* Load date format config for voicemail mail */
2481 if ((chanvar = ast_variable_retrieve(cfg, "general", "emaildateformat")))
2482 ast_copy_string(template->dateformat, chanvar, sizeof(template->dateformat));
2483 if ((chanvar = ast_variable_retrieve(cfg, "general", "emailfromstring")))
2484 ast_copy_string(template->fromaddress, chanvar, sizeof(template->fromaddress));
2485 if ((chanvar = ast_variable_retrieve(cfg, "general", "emailaaddress")))
2486 ast_copy_string(template->serveremail, chanvar, sizeof(template->serveremail));
2487 if ((chanvar = ast_variable_retrieve(cfg, "general", "emailcharset")))
2488 ast_copy_string(template->charset, chanvar, sizeof(template->charset));
2489 if ((chanvar = ast_variable_retrieve(cfg, "general", "emailsubject")))
2490 ast_copy_string(template->subject, chanvar, sizeof(template->subject));
2491 if ((chanvar = ast_variable_retrieve(cfg, "general", "emailbody")))
2492 template->body = message_template_parse_emailbody(chanvar);
2493 template->attachment = TRUE;
2495 message_template_build("pager-default", NULL);
2496 template = message_template_find("pager-default");
2497 if ((chanvar = ast_variable_retrieve(cfg, "general", "pagerfromstring")))
2498 ast_copy_string(template->fromaddress, chanvar, sizeof(template->fromaddress));
2499 if ((chanvar = ast_variable_retrieve(cfg, "general", "pageraddress")))
2500 ast_copy_string(template->serveremail, chanvar, sizeof(template->serveremail));
2501 if ((chanvar = ast_variable_retrieve(cfg, "general", "pagercharset")))
2502 ast_copy_string(template->charset, chanvar, sizeof(template->charset));
2503 if ((chanvar = ast_variable_retrieve(cfg, "general", "pagersubject")))
2504 ast_copy_string(template->subject, chanvar,sizeof(template->subject));
2505 if ((chanvar = ast_variable_retrieve(cfg, "general", "pagerbody")))
2506 template->body = message_template_parse_emailbody(chanvar);
2507 template->attachment = FALSE;
2510 ast_log(LOG_ERROR, "--- A total of %d errors found in mini-voicemail configuration\n", error);
2512 ast_mutex_unlock(&minivmlock);
2513 ast_config_destroy(cfg);
2515 /* Close log file if it's open and disabled */
2517 fclose(minivmlogfile);
2519 /* Open log file if it's enabled */
2520 if(!ast_strlen_zero(global_logfile)) {
2521 minivmlogfile = fopen(global_logfile, "a");
2523 ast_log(LOG_ERROR, "Failed to open minivm log file %s : %s\n", global_logfile, strerror(errno));
2525 ast_debug(3, "-_-_- Opened log file %s \n", global_logfile);
2531 static const char minivm_show_users_help[] =
2532 "Usage: minivm list accounts\n"
2533 " Lists all mailboxes currently set up\n";
2535 static const char minivm_show_zones_help[] =
2536 "Usage: minivm list zones\n"
2537 " Lists zone message formats\n";
2539 static const char minivm_list_templates_help[] =
2540 "Usage: minivm list templates\n"
2541 " Lists message templates for e-mail, paging and IM\n";
2543 static const char minivm_show_stats_help[] =
2544 "Usage: minivm show stats\n"
2545 " Display Mini-Voicemail counters\n";
2547 static const char minivm_show_settings_help[] =
2548 "Usage: minivm show settings\n"
2549 " Display Mini-Voicemail general settings\n";
2551 static const char minivm_reload_help[] =
2552 "Usage: minivm reload\n"
2553 " Reload mini-voicemail configuration and reset statistics\n";
2555 /*! \brief CLI routine for listing templates */
2556 static int handle_minivm_list_templates(int fd, int argc, char *argv[])
2558 struct minivm_template *this;
2559 char *output_format = "%-15s %-10s %-10s %-15.15s %-50s\n";
2563 return RESULT_SHOWUSAGE;
2565 AST_LIST_LOCK(&message_templates);
2566 if (AST_LIST_EMPTY(&message_templates)) {
2567 ast_cli(fd, "There are no message templates defined\n");
2568 AST_LIST_UNLOCK(&message_templates);
2569 return RESULT_FAILURE;
2571 ast_cli(fd, output_format, "Template name", "Charset", "Locale", "Attach media", "Subject");
2572 ast_cli(fd, output_format, "-------------", "-------", "------", "------------", "-------");
2573 AST_LIST_TRAVERSE(&message_templates, this, list) {
2574 ast_cli(fd, output_format, this->name,
2575 this->charset ? this->charset : "-",
2576 this->locale ? this->locale : "-",
2577 this->attachment ? "Yes" : "No",
2578 this->subject ? this->subject : "-");
2581 AST_LIST_UNLOCK(&message_templates);
2582 ast_cli(fd, "\n * Total: %d minivoicemail message templates\n", count);
2583 return RESULT_SUCCESS;
2586 /*! \brief CLI command to list voicemail accounts */
2587 static int handle_minivm_show_users(int fd, int argc, char *argv[])
2589 struct minivm_account *vmu;
2590 char *output_format = "%-23s %-15s %-15s %-10s %-10s %-50s\n";
2593 if ((argc < 3) || (argc > 5) || (argc == 4))
2594 return RESULT_SHOWUSAGE;
2595 if ((argc == 5) && strcmp(argv[3],"for"))
2596 return RESULT_SHOWUSAGE;
2598 AST_LIST_LOCK(&minivm_accounts);
2599 if (AST_LIST_EMPTY(&minivm_accounts)) {
2600 ast_cli(fd, "There are no voicemail users currently defined\n");
2601 AST_LIST_UNLOCK(&minivm_accounts);
2602 return RESULT_FAILURE;
2604 ast_cli(fd, output_format, "User", "E-Template", "P-template", "Zone", "Format", "Full name");
2605 ast_cli(fd, output_format, "----", "----------", "----------", "----", "------", "---------");
2606 AST_LIST_TRAVERSE(&minivm_accounts, vmu, list) {
2610 if ((argc == 3) || ((argc == 5) && !strcmp(argv[4], vmu->domain))) {
2612 snprintf(tmp, sizeof(tmp), "%s@%s", vmu->username, vmu->domain);
2613 ast_cli(fd, output_format, tmp, vmu->etemplate ? vmu->etemplate : "-",
2614 vmu->ptemplate ? vmu->ptemplate : "-",
2615 vmu->zonetag ? vmu->zonetag : "-",
2616 vmu->attachfmt ? vmu->attachfmt : "-",
2620 AST_LIST_UNLOCK(&minivm_accounts);
2621 ast_cli(fd, "\n * Total: %d minivoicemail accounts\n", count);
2622 return RESULT_SUCCESS;
2625 /*! \brief Show a list of voicemail zones in the CLI */
2626 static int handle_minivm_show_zones(int fd, int argc, char *argv[])
2628 struct minivm_zone *zone;
2629 char *output_format = "%-15s %-20s %-45s\n";
2630 int res = RESULT_SUCCESS;
2633 return RESULT_SHOWUSAGE;
2635 AST_LIST_LOCK(&minivm_zones);
2636 if (!AST_LIST_EMPTY(&minivm_zones)) {
2637 ast_cli(fd, output_format, "Zone", "Timezone", "Message Format");
2638 ast_cli(fd, output_format, "----", "--------", "--------------");
2639 AST_LIST_TRAVERSE(&minivm_zones, zone, list) {
2640 ast_cli(fd, output_format, zone->name, zone->timezone, zone->msg_format);
2643 ast_cli(fd, "There are no voicemail zones currently defined\n");
2644 res = RESULT_FAILURE;
2646 AST_LIST_UNLOCK(&minivm_zones);
2652 static char *complete_minivm_show_users(const char *line, const char *word, int pos, int state)
2656 struct minivm_account *vmu;
2657 const char *domain = "";
2659 /* 0 - show; 1 - voicemail; 2 - users; 3 - for; 4 - <domain> */
2663 return (state == 0) ? ast_strdup("for") : NULL;
2664 wordlen = strlen(word);
2665 AST_LIST_TRAVERSE(&minivm_accounts, vmu, list) {
2666 if (!strncasecmp(word, vmu->domain, wordlen)) {
2667 if (domain && strcmp(domain, vmu->domain) && ++which > state)
2668 return ast_strdup(vmu->domain);
2669 /* ignore repeated domains ? */
2670 domain = vmu->domain;
2676 /*! \brief CLI Show settings */
2677 static int handle_minivm_show_settings(int fd, int argc, char *argv[])
2679 ast_cli(fd, "* Mini-Voicemail general settings\n");
2680 ast_cli(fd, " -------------------------------\n");
2682 ast_cli(fd, " Mail command (shell): %s\n", global_mailcmd);
2683 ast_cli(fd, " Max silence: %d\n", global_maxsilence);
2684 ast_cli(fd, " Silence treshold: %d\n", global_silencethreshold);
2685 ast_cli(fd, " Max message length (secs): %d\n", global_vmmaxmessage);
2686 ast_cli(fd, " Min message length (secs): %d\n", global_vmminmessage);
2687 ast_cli(fd, " Default format: %s\n", default_vmformat);
2688 ast_cli(fd, " Extern notify (shell): %s\n", global_externnotify);
2689 ast_cli(fd, " Logfile: %s\n", global_logfile[0] ? global_logfile : "<disabled>");
2690 ast_cli(fd, " Operator exit: %s\n", ast_test_flag(&globalflags, MVM_OPERATOR) ? "Yes" : "No");
2691 ast_cli(fd, " Message review: %s\n", ast_test_flag(&globalflags, MVM_REVIEW) ? "Yes" : "No");
2694 return RESULT_SUCCESS;
2697 /*! \brief Show stats */
2698 static int handle_minivm_show_stats(int fd, int argc, char *argv[])
2703 ast_cli(fd, "* Mini-Voicemail statistics\n");
2704 ast_cli(fd, " -------------------------\n");
2706 ast_cli(fd, " Voicemail accounts: %-5.5d\n", global_stats.voicemailaccounts);
2707 ast_cli(fd, " Templates: %-5.5d\n", global_stats.templates);
2708 ast_cli(fd, " Timezones: %-5.5d\n", global_stats.timezones);
2709 if (global_stats.receivedmessages == 0) {
2710 ast_cli(fd, " Received messages since last reset: <none>\n");
2712 ast_cli(fd, " Received messages since last reset: %d\n", global_stats.receivedmessages);
2713 ast_localtime(&global_stats.lastreceived, &time, NULL);
2714 strftime(buf, sizeof(buf), "%a %b %e %r %Z %Y", &time);
2715 ast_cli(fd, " Last received voicemail: %s\n", buf);
2717 ast_localtime(&global_stats.reset, &time, NULL);
2718 strftime(buf, sizeof(buf), "%a %b %e %r %Z %Y", &time);
2719 ast_cli(fd, " Last reset: %s\n", buf);
2722 return RESULT_SUCCESS;
2725 /*! \brief ${MINIVMACCOUNT()} Dialplan function - reads account data */
2726 static int minivm_account_func_read(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len)
2728 struct minivm_account *vmu;
2729 char *username, *domain, *colname;
2731 if (!(username = ast_strdupa(data))) {
2732 ast_log(LOG_ERROR, "Memory Error!\n");
2736 if ((colname = strchr(username, ':'))) {
2742 if ((domain = strchr(username, '@'))) {
2746 if (ast_strlen_zero(username) || ast_strlen_zero(domain)) {
2747 ast_log(LOG_ERROR, "This function needs a username and a domain: username@domain\n");
2751 if (!(vmu = find_account(domain, username, TRUE)))
2754 if (!strcasecmp(colname, "hasaccount")) {
2755 ast_copy_string(buf, (ast_test_flag(vmu, MVM_ALLOCED) ? "0" : "1"), len);
2756 } else if (!strcasecmp(colname, "fullname")) {
2757 ast_copy_string(buf, vmu->fullname, len);
2758 } else if (!strcasecmp(colname, "email")) {
2759 if (!ast_strlen_zero(vmu->email))
2760 ast_copy_string(buf, vmu->email, len);
2762 snprintf(buf, len, "%s@%s", vmu->username, vmu->domain);
2763 } else if (!strcasecmp(colname, "pager")) {
2764 ast_copy_string(buf, vmu->pager, len);
2765 } else if (!strcasecmp(colname, "etemplate")) {
2766 if (!ast_strlen_zero(vmu->etemplate))
2767 ast_copy_string(buf, vmu->etemplate, len);
2769 ast_copy_string(buf, "email-default", len);
2770 } else if (!strcasecmp(colname, "language")) {
2771 ast_copy_string(buf, vmu->language, len);
2772 } else if (!strcasecmp(colname, "timezone")) {
2773 ast_copy_string(buf, vmu->zonetag, len);
2774 } else if (!strcasecmp(colname, "ptemplate")) {
2775 if (!ast_strlen_zero(vmu->ptemplate))
2776 ast_copy_string(buf, vmu->ptemplate, len);
2778 ast_copy_string(buf, "email-default", len);
2779 } else if (!strcasecmp(colname, "accountcode")) {
2780 ast_copy_string(buf, vmu->accountcode, len);
2781 } else if (!strcasecmp(colname, "pincode")) {
2782 ast_copy_string(buf, vmu->pincode, len);
2783 } else if (!strcasecmp(colname, "path")) {
2784 check_dirpath(buf, len, vmu->domain, vmu->username, NULL);
2785 } else { /* Look in channel variables */
2786 struct ast_variable *var;
2789 for (var = vmu->chanvars ; var ; var = var->next)
2790 if (!strcmp(var->name, colname)) {
2791 ast_copy_string(buf, var->value, len);
2797 if(ast_test_flag(vmu, MVM_ALLOCED))
2803 /*! \brief lock directory
2805 only return failure if ast_lock_path returns 'timeout',
2806 not if the path does not exist or any other reason
2808 static int vm_lock_path(const char *path)
2810 switch (ast_lock_path(path)) {
2811 case AST_LOCK_TIMEOUT:
2818 /*! \brief Access counter file, lock directory, read and possibly write it again changed
2819 \param directory Directory to crate file in
2820 \param countername filename
2821 \param value If set to zero, we only read the variable
2822 \param operand 0 to read, 1 to set new value, 2 to change
2823 \return -1 on error, otherwise counter value
2825 static int access_counter_file(char *directory, char *countername, int value, int operand)
2827 char filename[BUFSIZ];
2828 char readbuf[BUFSIZ];
2830 int old = 0, counter = 0;
2832 /* Lock directory */
2833 if (vm_lock_path(directory)) {
2834 return -1; /* Could not lock directory */
2836 snprintf(filename, sizeof(filename), "%s/%s.counter", directory, countername);
2838 counterfile = fopen(filename, "r");
2840 if(fgets(readbuf, sizeof(readbuf), counterfile)) {
2841 ast_debug(3, "Read this string from counter file: %s\n", readbuf);
2842 old = counter = atoi(readbuf);
2844 fclose(counterfile);
2848 case 0: /* Read only */
2849 ast_unlock_path(directory);
2850 ast_debug(2, "MINIVM Counter %s/%s: Value %d\n", directory, countername, counter);
2853 case 1: /* Set new value */
2856 case 2: /* Change value */
2858 if (counter < 0) /* Don't allow counters to fall below zero */
2863 /* Now, write the new value to the file */
2864 counterfile = fopen(filename, "w");
2866 ast_log(LOG_ERROR, "Could not open counter file for writing : %s - %s\n", filename, strerror(errno));
2867 ast_unlock_path(directory);
2868 return -1; /* Could not open file for writing */
2870 fprintf(counterfile, "%d\n\n", counter);
2871 fclose(counterfile);
2872 ast_unlock_path(directory);
2873 ast_debug(2, "MINIVM Counter %s/%s: Old value %d New value %d\n", directory, countername, old, counter);
2877 /*! \brief ${MINIVMCOUNTER()} Dialplan function - read counters */
2878 static int minivm_counter_func_read(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len)
2880 char *username, *domain, *countername;
2881 struct minivm_account *vmu = NULL;
2882 char userpath[BUFSIZ];
2887 if (!(username = ast_strdupa(data))) { /* Copy indata to local buffer */
2888 ast_log(LOG_WARNING, "Memory error!\n");
2891 if ((countername = strchr(username, ':'))) {
2892 *countername = '\0';
2896 if ((domain = strchr(username, '@'))) {
2901 /* If we have neither username nor domain now, let's give up */
2902 if (ast_strlen_zero(username) && ast_strlen_zero(domain)) {
2903 ast_log(LOG_ERROR, "No account given\n");
2907 if (ast_strlen_zero(countername)) {
2908 ast_log(LOG_ERROR, "This function needs two arguments: Account:countername\n");
2912 /* We only have a domain, no username */
2913 if (!ast_strlen_zero(username) && ast_strlen_zero(domain)) {
2918 /* If we can't find account or if the account is temporary, return. */
2919 if (!ast_strlen_zero(username) && !(vmu = find_account(domain, username, FALSE))) {
2920 ast_log(LOG_ERROR, "Minivm account does not exist: %s@%s\n", username, domain);
2924 create_dirpath(userpath, sizeof(userpath), domain, username, NULL);
2926 /* We have the path, now read the counter file */
2927 res = access_counter_file(userpath, countername, 0, 0);
2929 snprintf(buf, len, "%d", res);
2933 /*! \brief ${MINIVMCOUNTER()} Dialplan function - changes counter data */
2934 static int minivm_counter_func_write(struct ast_channel *chan, const char *cmd, char *data, const char *value)
2936 char *username, *domain, *countername, *operand;
2937 char userpath[BUFSIZ];
2938 struct minivm_account *vmu;
2944 change = atoi(value);
2946 if (!(username = ast_strdupa(data))) { /* Copy indata to local buffer */
2947 ast_log(LOG_WARNING, "Memory error!\n");
2951 if ((countername = strchr(username, ':'))) {
2952 *countername = '\0';
2955 if ((operand = strchr(countername, ':'))) {
2960 if ((domain = strchr(username, '@'))) {
2965 /* If we have neither username nor domain now, let's give up */
2966 if (ast_strlen_zero(username) && ast_strlen_zero(domain)) {
2967 ast_log(LOG_ERROR, "No account given\n");
2971 /* We only have a domain, no username */
2972 if (!ast_strlen_zero(username) && ast_strlen_zero(domain)) {
2977 if (ast_strlen_zero(operand) || ast_strlen_zero(countername)) {
2978 ast_log(LOG_ERROR, "Writing to this function requires three arguments: Account:countername:operand\n");
2982 /* If we can't find account or if the account is temporary, return. */
2983 if (!ast_strlen_zero(username) && !(vmu = find_account(domain, username, FALSE))) {
2984 ast_log(LOG_ERROR, "Minivm account does not exist: %s@%s\n", username, domain);
2988 create_dirpath(userpath, sizeof(userpath), domain, username, NULL);
2989 /* Now, find out our operator */
2990 if (*operand == 'i') /* Increment */
2992 else if (*operand == 'd') {
2993 change = change * -1;
2995 } else if (*operand == 's')
2998 ast_log(LOG_ERROR, "Unknown operator: %s\n", operand);
3002 /* We have the path, now read the counter file */
3003 access_counter_file(userpath, countername, change, operation);
3008 /*! \brief CLI commands for Mini-voicemail */
3009 static struct ast_cli_entry cli_minivm[] = {
3010 { { "minivm", "list", "accounts", NULL },
3011 handle_minivm_show_users, "List defined mini-voicemail boxes",
3012 minivm_show_users_help, complete_minivm_show_users, NULL },
3014 { { "minivm", "list", "zones", NULL },
3015 handle_minivm_show_zones, "List zone message formats",
3016 minivm_show_zones_help, NULL, NULL },
3018 { { "minivm", "list", "templates", NULL },
3019 handle_minivm_list_templates, "List message templates",
3020 minivm_list_templates_help, NULL, NULL },
3022 { { "minivm", "reload", NULL, NULL },
3023 handle_minivm_reload, "Reload Mini-voicemail configuration",
3024 minivm_reload_help, NULL, NULL },
3026 { { "minivm", "show", "stats", NULL },
3027 handle_minivm_show_stats, "Show some mini-voicemail statistics",
3028 minivm_show_stats_help, NULL, NULL },
3030 { { "minivm", "show", "settings", NULL },
3031 handle_minivm_show_settings, "Show mini-voicemail general settings",
3032 minivm_show_settings_help, NULL, NULL },
3035 static struct ast_custom_function minivm_counter_function = {
3036 .name = "MINIVMCOUNTER",
3037 .synopsis = "Reads or sets counters for MiniVoicemail message",
3038 .syntax = "MINIVMCOUNTER(<account>:name[:operand])",
3039 .read = minivm_counter_func_read,
3040 .write = minivm_counter_func_write,
3041 .desc = "Valid operands for changing the value of a counter when assigning a value are:\n"
3042 "- i Increment by value\n"
3043 "- d Decrement by value\n"
3044 "- s Set to value\n"
3045 "\nThe counters never goes below zero.\n"
3046 "- The name of the counter is a string, up to 10 characters\n"
3047 "- If account is given and it exists, the counter is specific for the account\n"
3048 "- If account is a domain and the domain directory exists, counters are specific for a domain\n"
3049 "The operation is atomic and the counter is locked while changing the value\n"
3050 "\nThe counters are stored as text files in the minivm account directories. It might be better to use\n"
3051 "realtime functions if you are using a database to operate your Asterisk\n",
3054 static struct ast_custom_function minivm_account_function = {
3055 .name = "MINIVMACCOUNT",
3056 .synopsis = "Gets MiniVoicemail account information",
3057 .syntax = "MINIVMACCOUNT(<account>:item)",
3058 .read = minivm_account_func_read,
3059 .desc = "Valid items are:\n"
3060 "- path Path to account mailbox (if account exists, otherwise temporary mailbox)\n"
3061 "- hasaccount 1 if static Minivm account exists, 0 otherwise\n"
3062 "- fullname Full name of account owner\n"
3063 "- email Email address used for account\n"
3064 "- etemplate E-mail template for account (default template if none is configured)\n"
3065 "- ptemplate Pager template for account (default template if none is configured)\n"
3066 "- accountcode Account code for voicemail account\n"
3067 "- pincode Pin code for voicemail account\n"
3068 "- timezone Time zone for voicemail account\n"
3069 "- language Language for voicemail account\n"
3070 "- <channel variable name> Channel variable value (set in configuration for account)\n"
3074 /*! \brief Load mini voicemail module */
3075 static int load_module(void)
3079 res = ast_register_application(app_minivm_record, minivm_record_exec, synopsis_minivm_record, descrip_minivm_record);
3080 res = ast_register_application(app_minivm_greet, minivm_greet_exec, synopsis_minivm_greet, descrip_minivm_greet);
3081 res = ast_register_application(app_minivm_notify, minivm_notify_exec, synopsis_minivm_notify, descrip_minivm_notify);
3082 res = ast_register_application(app_minivm_delete, minivm_delete_exec, synopsis_minivm_delete, descrip_minivm_delete);
3083 res = ast_register_application(app_minivm_accmess, minivm_accmess_exec, synopsis_minivm_accmess, descrip_minivm_accmess);
3085 ast_custom_function_register(&minivm_account_function);
3086 ast_custom_function_register(&minivm_counter_function);
3090 if ((res = load_config()))
3093 ast_cli_register_multiple(cli_minivm, sizeof(cli_minivm)/sizeof(cli_minivm[0]));
3095 /* compute the location of the voicemail spool directory */
3096 snprintf(MVM_SPOOL_DIR, sizeof(MVM_SPOOL_DIR), "%s/voicemail/", ast_config_AST_SPOOL_DIR);
3101 /*! \brief Reload mini voicemail module */
3102 static int reload(void)
3104 return(load_config());
3107 /*! \brief Reload cofiguration */
3108 static int handle_minivm_reload(int fd, int argc, char *argv[])
3111 ast_cli(fd, "\n-- Mini voicemail re-configured \n");
3112 return RESULT_SUCCESS;
3115 /*! \brief Unload mini voicemail module */
3116 static int unload_module(void)
3120 res = ast_unregister_application(app_minivm_record);
3121 res |= ast_unregister_application(app_minivm_greet);
3122 res |= ast_unregister_application(app_minivm_notify);
3123 res |= ast_unregister_application(app_minivm_delete);
3124 res |= ast_unregister_application(app_minivm_accmess);
3125 ast_cli_unregister_multiple(cli_minivm, sizeof(cli_minivm)/sizeof(cli_minivm[0]));
3126 ast_custom_function_unregister(&minivm_account_function);
3127 ast_custom_function_unregister(&minivm_counter_function);
3129 message_destroy_list(); /* Destroy list of voicemail message templates */
3130 timezone_destroy_list(); /* Destroy list of timezones */
3131 vmaccounts_destroy_list(); /* Destroy list of voicemail accounts */
3134 ast_module_user_hangup_all();
3140 AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_DEFAULT, "Mini VoiceMail (A minimal Voicemail e-mail System)",
3141 .load = load_module,
3142 .unload = unload_module,