2007-04-18 07:57:18 +00:00
/*
* Asterisk -- An open source telephony toolkit.
*
* Copyright (C) 1999 - 2005, Digium, Inc.
* and Edvina AB, Sollentuna, Sweden
*
* Mark Spencer <markster@digium.com> (Comedian Mail)
* and Olle E. Johansson, Edvina.net <oej@edvina.net> (Mini-Voicemail changes)
*
* See http://www.asterisk.org for more information about
* the Asterisk project. Please do not directly contact
* any of the maintainers of this project for assistance;
* the project provides a web site, mailing lists and IRC
* channels for your use.
*
* This program is free software, distributed under the terms of
* the GNU General Public License Version 2. See the LICENSE file
* at the top of the source tree.
*/
/*! \file
*
* \brief MiniVoiceMail - A Minimal Voicemail System for Asterisk
*
* A voicemail system in small building blocks, working together
* based on the Comedian Mail voicemail system (app_voicemail.c).
*
* \par See also
* \arg \ref Config_minivm
* \arg \ref Config_minivm_examples
* \arg \ref App_minivm
*
* \ingroup applications
*
* \page App_minivm Asterisk Mini-voicemail - A minimal voicemail system
*
* This is a minimal voicemail system, building blocks for something
* else. It is built for multi-language systems.
* The current version is focused on accounts where voicemail is
* forwarded to users in e-mail. It's work in progress, with loosed ends hanging
* around from the old voicemail system and it's configuration.
*
* Hopefully, we can expand this to be a full replacement of voicemail() and voicemailmain()
* in the future.
*
* Dialplan applications
* - minivmRecord - record voicemail and send as e-mail ( \ref minivm_record_exec() )
* - minivmGreet - Play user's greeting or default greeting ( \ref minivm_greet_exec() )
* - minivmNotify - Notify user of message ( \ref minivm_notify_exec() )
* - minivmDelete - Delete voicemail message ( \ref minivm_delete_exec() )
* - minivmAccMess - Record personal messages (busy | unavailable | temporary)
*
* Dialplan functions
* - MINIVMACCOUNT() - A dialplan function
* - MINIVMCOUNTER() - Manage voicemail-related counters for accounts or domains
*
* CLI Commands
* - minivm list accounts
* - minivm list zones
* - minivm list templates
* - minivm show stats
* - minivm show settings
*
* Some notes
* - General configuration in minivm.conf
* - Users in realtime or configuration file
* - Or configured on the command line with just the e-mail address
*
* Voicemail accounts are identified by userid and domain
*
* Language codes are like setlocale - langcode_countrycode
* \note Don't use language codes like the rest of Asterisk, two letter countrycode. Use
* language_country like setlocale().
*
* Examples:
* - Swedish, Sweden sv_se
* - Swedish, Finland sv_fi
* - English, USA en_us
* - English, GB en_gb
*
* \par See also
* \arg \ref Config_minivm
* \arg \ref Config_minivm_examples
* \arg \ref Minivm_directories
* \arg \ref app_minivm.c
* \arg Comedian mail: app_voicemail.c
* \arg \ref descrip_minivm_accmess
* \arg \ref descrip_minivm_greet
* \arg \ref descrip_minivm_record
* \arg \ref descrip_minivm_delete
* \arg \ref descrip_minivm_notify
*
* \arg \ref App_minivm_todo
*/
/*! \page Minivm_directories Asterisk Mini-Voicemail Directory structure
*
* The directory structure for storing voicemail
* - AST_SPOOL_DIR - usually /var/spool/asterisk (configurable in asterisk.conf)
* - MVM_SPOOL_DIR - should be configurable, usually AST_SPOOL_DIR/voicemail
* - Domain MVM_SPOOL_DIR/domain
* - Username MVM_SPOOL_DIR/domain/username
* - /greet : Recording of account owner's name
* - /busy : Busy message
* - /unavailable : Unavailable message
* - /temp : Temporary message
*
* For account anita@localdomain.xx the account directory would as a default be
* \b /var/spool/asterisk/voicemail/localdomain.xx/anita
*
* To avoid transcoding, these sound files should be converted into several formats
* They are recorded in the format closest to the incoming streams
*
*
* Back: \ref App_minivm
*/
/*! \page Config_minivm_examples Example dialplan for Mini-Voicemail
* \section Example dialplan scripts for Mini-Voicemail
* \verbinclude extensions_minivm.conf.sample
*
* Back: \ref App_minivm
*/
/*! \page App_minivm_todo Asterisk Mini-Voicemail - todo
* - configure accounts from AMI?
* - test, test, test, test
* - fix "vm-theextensionis.gsm" voiceprompt from Allison in various formats
* "The extension you are calling"
* - For trunk, consider using channel storage for information passing between small applications
* - Set default directory for voicemail
* - New app for creating directory for account if it does not exist
* - Re-insert code for IMAP storage at some point
* - Jabber integration for notifications
* - Figure out how to handle video in voicemail
* - Integration with the HTTP server
* - New app for moving messages between mailboxes, and optionally mark it as "new"
*
* For Asterisk 1.4/trunk
* - Use string fields for minivm_account
*
* Back: \ref App_minivm
*/
#include <stdlib.h>
#include <errno.h>
#include <unistd.h>
#include <string.h>
#include <stdio.h>
#include <ctype.h>
#include <sys/time.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/mman.h>
#include <time.h>
#include <dirent.h>
#include <locale.h>
#include "asterisk.h"
ASTERISK_FILE_VERSION ( __FILE__ , "$Revision$" )
#include "asterisk/astobj.h"
#include "asterisk/lock.h"
#include "asterisk/file.h"
#include "asterisk/logger.h"
#include "asterisk/channel.h"
#include "asterisk/pbx.h"
#include "asterisk/options.h"
#include "asterisk/config.h"
#include "asterisk/say.h"
#include "asterisk/module.h"
#include "asterisk/app.h"
#include "asterisk/manager.h"
#include "asterisk/dsp.h"
#include "asterisk/localtime.h"
#include "asterisk/cli.h"
#include "asterisk/utils.h"
#include "asterisk/linkedlists.h"
#include "asterisk/callerid.h"
#ifndef TRUE
#define TRUE 1
#endif
#ifndef FALSE
#define FALSE 0
#endif
#define MVM_REVIEW (1 << 0) /*!< Review message */
#define MVM_OPERATOR (1 << 1) /*!< Operator exit during voicemail recording */
#define MVM_REALTIME (1 << 2) /*!< This user is a realtime account */
#define MVM_SVMAIL (1 << 3)
#define MVM_ENVELOPE (1 << 4)
#define MVM_PBXSKIP (1 << 9)
#define MVM_ALLOCED (1 << 13)
/*! \brief Default mail command to mail voicemail. Change it with the
mailcmd= command in voicemail.conf */
#define SENDMAIL "/usr/sbin/sendmail -t"
#define SOUND_INTRO "vm-intro"
#define B64_BASEMAXINLINE 256 /*!< Buffer size for Base 64 attachment encoding */
#define B64_BASELINELEN 72 /*!< Line length for Base 64 endoded messages */
#define EOL "\r\n"
#define MAX_DATETIME_FORMAT 512
#define MAX_NUM_CID_CONTEXTS 10
#define ERROR_LOCK_PATH -100
#define VOICEMAIL_DIR_MODE 0700
#define VOICEMAIL_CONFIG "minivm.conf"
#define ASTERISK_USERNAME "asterisk" /*!< Default username for sending mail is asterisk\@localhost */
/*! \brief Message types for notification */
enum mvm_messagetype {
MVM_MESSAGE_EMAIL ,
MVM_MESSAGE_PAGE
/* For trunk: MVM_MESSAGE_JABBER, */
};
static char MVM_SPOOL_DIR [ PATH_MAX ];
/* Module declarations */
static char * app_minivm_record = "MinivmRecord" ; /* Leave a message */
static char * app_minivm_greet = "MinivmGreet" ; /* Play voicemail prompts */
static char * app_minivm_notify = "MinivmNotify" ; /* Notify about voicemail by using one of several methods */
static char * app_minivm_delete = "MinivmDelete" ; /* Notify about voicemail by using one of several methods */
static char * app_minivm_accmess = "MinivmAccMess" ; /* Record personal voicemail messages */
static char * synopsis_minivm_record = "Receive Mini-Voicemail and forward via e-mail" ;
static char * descrip_minivm_record =
"Syntax: MinivmRecord(username@domain[,options]) \n "
"This application is part of the Mini-Voicemail system, configured in minivm.conf. \n "
"MiniVM records audio file in configured format and forwards message to e-mail and pager. \n "
"If there's no user account for that address, a temporary account will \n "
"be used with default options. \n "
"The recorded file name and path will be stored in MINIVM_FILENAME and the \n "
"duration of the message will be stored in MINIVM_DURATION \n "
" \n Note: If the caller hangs up after the recording, the only way to send \n "
"the message and clean up is to execute in the \" h \" extension. \n "
" \n The application will exit if any of the following DTMF digits are \n "
"received and the requested extension exist in the current context. \n "
" 0 - Jump to the 'o' extension in the current dialplan context. \n "
" * - Jump to the 'a' extension in the current dialplan context. \n "
" \n "
"Result is given in channel variable MINIVM_RECORD_STATUS \n "
" The possible values are: SUCCESS | USEREXIT | FAILED \n\n "
" Options: \n "
" g(#) - Use the specified amount of gain when recording the voicemail \n "
" message. The units are whole-number decibels (dB). \n "
" \n " ;
static char * synopsis_minivm_greet = "Play Mini-Voicemail prompts" ;
static char * descrip_minivm_greet =
"Syntax: MinivmGreet(username@domain[,options]) \n "
"This application is part of the Mini-Voicemail system, configured in minivm.conf. \n "
"MinivmGreet() plays default prompts or user specific prompts for an account. \n "
"Busy and unavailable messages can be choosen, but will be overridden if a temporary \n "
"message exists for the account. \n "
" \n "
"Result is given in channel variable MINIVM_GREET_STATUS \n "
" The possible values are: SUCCESS | USEREXIT | FAILED \n\n "
" Options: \n "
" b - Play the 'busy' greeting to the calling party. \n "
" s - Skip the playback of instructions for leaving a message to the \n "
" calling party. \n "
" u - Play the 'unavailable greeting. \n "
" \n " ;
static char * synopsis_minivm_notify = "Notify voicemail owner about new messages." ;
static char * descrip_minivm_notify =
"Syntax: MinivmNotify(username@domain[,template]) \n "
"This application is part of the Mini-Voicemail system, configured in minivm.conf. \n "
"MiniVMnotify forwards messages about new voicemail to e-mail and pager. \n "
"If there's no user account for that address, a temporary account will \n "
"be used with default options (set in minivm.conf). \n "
"The recorded file name and path will be read from MVM_FILENAME and the \n "
"duration of the message will be accessed from MVM_DURATION (set by MinivmRecord() ) \n "
"If the channel variable MVM_COUNTER is set, this will be used in the \n "
"message file name and available in the template for the message. \n "
"If not template is given, the default email template will be used to send email and \n "
"default pager template to send paging message (if the user account is configured with \n "
"a paging address. \n "
" \n "
"Result is given in channel variable MINIVM_NOTIFY_STATUS \n "
" The possible values are: SUCCESS | FAILED \n "
" \n " ;
static char * synopsis_minivm_delete = "Delete Mini-Voicemail voicemail messages" ;
static char * descrip_minivm_delete =
"Syntax: MinivmDelete(filename) \n "
"This application is part of the Mini-Voicemail system, configured in minivm.conf. \n "
"It deletes voicemail file set in MVM_FILENAME or given filename. \n "
" \n "
"Result is given in channel variable MINIVM_DELETE_STATUS \n "
" The possible values are: SUCCESS | FAILED \n "
" FAILED is set if the file does not exist or can't be deleted. \n "
" \n " ;
static char * synopsis_minivm_accmess = "Record account specific messages" ;
static char * descrip_minivm_accmess =
"Syntax: MinivmAccmess(username@domain,option) \n "
"This application is part of the Mini-Voicemail system, configured in minivm.conf. \n "
"Use this application to record account specific audio/video messages for \n "
"busy, unavailable and temporary messages. \n "
"Account specific directories will be created if they do not exist. \n "
" \n The option selects message to be recorded: \n "
" u Unavailable \n "
" b Busy \n "
" t Temporary (overrides busy and unavailable) \n "
" n Account name \n "
" \n "
"Result is given in channel variable MINIVM_ACCMESS_STATUS \n "
" The possible values are: SUCCESS | FAILED \n "
" FAILED is set if the file can't be created. \n "
" \n " ;
enum {
OPT_SILENT = ( 1 << 0 ),
OPT_BUSY_GREETING = ( 1 << 1 ),
OPT_UNAVAIL_GREETING = ( 1 << 2 ),
OPT_TEMP_GREETING = ( 1 << 3 ),
OPT_NAME_GREETING = ( 1 << 4 ),
OPT_RECORDGAIN = ( 1 << 5 ),
} minivm_option_flags ;
enum {
OPT_ARG_RECORDGAIN = 0 ,
OPT_ARG_ARRAY_SIZE = 1 ,
} minivm_option_args ;
AST_APP_OPTIONS ( minivm_app_options , {
AST_APP_OPTION ( 's' , OPT_SILENT ),
AST_APP_OPTION ( 'b' , OPT_BUSY_GREETING ),
AST_APP_OPTION ( 'u' , OPT_UNAVAIL_GREETING ),
AST_APP_OPTION_ARG ( 'g' , OPT_RECORDGAIN , OPT_ARG_RECORDGAIN ),
});
AST_APP_OPTIONS ( minivm_accmess_options , {
AST_APP_OPTION ( 'b' , OPT_BUSY_GREETING ),
AST_APP_OPTION ( 'u' , OPT_UNAVAIL_GREETING ),
AST_APP_OPTION ( 't' , OPT_TEMP_GREETING ),
AST_APP_OPTION ( 'n' , OPT_NAME_GREETING ),
});
/*! \brief Structure for linked list of Mini-Voicemail users: \ref minivm_accounts */
struct minivm_account {
char username [ AST_MAX_CONTEXT ]; /*!< Mailbox username */
char domain [ AST_MAX_CONTEXT ]; /*!< Voicemail domain */
char pincode [ 10 ]; /*!< Secret pin code, numbers only */
char fullname [ 120 ]; /*!< Full name, for directory app */
char email [ 80 ]; /*!< E-mail address - override */
char pager [ 80 ]; /*!< E-mail address to pager (no attachment) */
char accountcode [ AST_MAX_ACCOUNT_CODE ]; /*!< Voicemail account account code */
char serveremail [ 80 ]; /*!< From: Mail address */
char externnotify [ 160 ]; /*!< Configurable notification command */
char language [ MAX_LANGUAGE ]; /*!< Config: Language setting */
char zonetag [ 80 ]; /*!< Time zone */
char uniqueid [ 20 ]; /*!< Unique integer identifier */
char exit [ 80 ]; /*!< Options for exiting from voicemail() */
char attachfmt [ 80 ]; /*!< Format for voicemail audio file attachment */
char etemplate [ 80 ]; /*!< Pager template */
char ptemplate [ 80 ]; /*!< Voicemail format */
After some study, thought, comparing, etc. I've backed out the previous universal mod to make ast_flags a 64 bit thing. Instead, I added a 64-bit version of ast_flags (ast_flags64), and 64-bit versions of the test-flag, set-flag, etc. macros, and an app_parse_options64 routine, and I use these in app_dial alone, to eliminate the 30-option limit it had grown to meet. There is room now for 32 more options and flags. I was heavily tempted to implement some of the other ideas that were presented, but this solution does not intro any new versions of dial, doesn't have a different API, has a minimal/zero impact on code outside of dial, and doesn't seriously (I hope) affect the code structure of dial. It's the best I can think of right now. My goal was NOT to rewrite dial. I leave that to a future, coordinated effort.
2007-07-19 23:24:27 +00:00
unsigned int flags ; /*!< MVM_ flags */
2007-04-18 07:57:18 +00:00
struct ast_variable * chanvars ; /*!< Variables for e-mail template */
double volgain ; /*!< Volume gain for voicemails sent via e-mail */
AST_LIST_ENTRY ( minivm_account ) list ;
};
/*! \brief The list of e-mail accounts */
static AST_LIST_HEAD_STATIC ( minivm_accounts , minivm_account );
/*! \brief Linked list of e-mail templates in various languages
These are used as templates for e-mails, pager messages and jabber messages
\ref message_templates
*/
struct minivm_template {
char name [ 80 ]; /*!< Template name */
char * body ; /*!< Body of this template */
char fromaddress [ 100 ]; /*!< Who's sending the e-mail? */
char serveremail [ 80 ]; /*!< From: Mail address */
char subject [ 100 ]; /*!< Subject line */
char charset [ 32 ]; /*!< Default character set for this template */
char locale [ 20 ]; /*!< Locale for setlocale() */
char dateformat [ 80 ]; /*!< Date format to use in this attachment */
int attachment ; /*!< Attachment of media yes/no - no for pager messages */
AST_LIST_ENTRY ( minivm_template ) list ; /*!< List mechanics */
};
/*! \brief The list of e-mail templates */
static AST_LIST_HEAD_STATIC ( message_templates , minivm_template );
/*! \brief Options for leaving voicemail with the voicemail() application */
struct leave_vm_options {
After some study, thought, comparing, etc. I've backed out the previous universal mod to make ast_flags a 64 bit thing. Instead, I added a 64-bit version of ast_flags (ast_flags64), and 64-bit versions of the test-flag, set-flag, etc. macros, and an app_parse_options64 routine, and I use these in app_dial alone, to eliminate the 30-option limit it had grown to meet. There is room now for 32 more options and flags. I was heavily tempted to implement some of the other ideas that were presented, but this solution does not intro any new versions of dial, doesn't have a different API, has a minimal/zero impact on code outside of dial, and doesn't seriously (I hope) affect the code structure of dial. It's the best I can think of right now. My goal was NOT to rewrite dial. I leave that to a future, coordinated effort.
2007-07-19 23:24:27 +00:00
unsigned int flags ;
2007-04-18 07:57:18 +00:00
signed char record_gain ;
};
/*! \brief Structure for base64 encoding */
struct b64_baseio {
int iocp ;
int iolen ;
int linelength ;
int ateof ;
unsigned char iobuf [ B64_BASEMAXINLINE ];
};
/*! \brief Voicemail time zones */
struct minivm_zone {
char name [ 80 ]; /*!< Name of this time zone */
char timezone [ 80 ]; /*!< Timezone definition */
char msg_format [ BUFSIZ ]; /*!< Not used in minivm ...yet */
AST_LIST_ENTRY ( minivm_zone ) list ; /*!< List mechanics */
};
/*! \brief The list of e-mail time zones */
static AST_LIST_HEAD_STATIC ( minivm_zones , minivm_zone );
/*! \brief Structure for gathering statistics */
struct minivm_stats {
int voicemailaccounts ; /*!< Number of static accounts */
int timezones ; /*!< Number of time zones */
int templates ; /*!< Number of templates */
2007-07-18 19:47:20 +00:00
struct timeval reset ; /*!< Time for last reset */
2007-04-18 07:57:18 +00:00
int receivedmessages ; /*!< Number of received messages since reset */
2007-07-18 19:47:20 +00:00
struct timeval lastreceived ; /*!< Time for last voicemail sent */
2007-04-18 07:57:18 +00:00
};
/*! \brief Statistics for voicemail */
static struct minivm_stats global_stats ;
AST_MUTEX_DEFINE_STATIC ( minivmlock ); /*!< Lock to protect voicemail system */
AST_MUTEX_DEFINE_STATIC ( minivmloglock ); /*!< Lock to protect voicemail system log file */
FILE * minivmlogfile ; /*!< The minivm log file */
static int global_vmminmessage ; /*!< Minimum duration of messages */
static int global_vmmaxmessage ; /*!< Maximum duration of message */
static int global_maxsilence ; /*!< Maximum silence during recording */
static int global_maxgreet ; /*!< Maximum length of prompts */
static int global_silencethreshold = 128 ;
static char global_mailcmd [ 160 ]; /*!< Configurable mail cmd */
static char global_externnotify [ 160 ]; /*!< External notification application */
static char global_logfile [ PATH_MAX ]; /*!< Global log file for messages */
static char default_vmformat [ 80 ];
static struct ast_flags globalflags = { 0 }; /*!< Global voicemail flags */
static int global_saydurationminfo ;
static char global_charset [ 32 ]; /*!< Global charset in messages */
static double global_volgain ; /*!< Volume gain for voicmemail via e-mail */
/*! \brief Default dateformat, can be overridden in configuration file */
#define DEFAULT_DATEFORMAT "%A, %B %d, %Y at %r"
#define DEFAULT_CHARSET "ISO-8859-1"
/* Forward declarations */
static char * message_template_parse_filebody ( char * filename );
static char * message_template_parse_emailbody ( const char * body );
static int create_vmaccount ( char * name , struct ast_variable * var , int realtime );
static struct minivm_account * find_user_realtime ( const char * domain , const char * username );
2007-09-20 23:14:30 +00:00
static char * handle_minivm_reload ( struct ast_cli_entry * e , int cmd , struct ast_cli_args * a );
2007-04-18 07:57:18 +00:00
/*! \brief Create message template */
static struct minivm_template * message_template_create ( const char * name )
{
struct minivm_template * template ;
2007-06-06 21:20:11 +00:00
template = ast_calloc ( 1 , sizeof ( * template ));
2007-04-18 07:57:18 +00:00
if ( ! template )
return NULL ;
/* Set some defaults for templates */
ast_copy_string ( template -> name , name , sizeof ( template -> name ));
ast_copy_string ( template -> dateformat , DEFAULT_DATEFORMAT , sizeof ( template -> dateformat ));
ast_copy_string ( template -> charset , DEFAULT_CHARSET , sizeof ( template -> charset ));
ast_copy_string ( template -> subject , "New message in mailbox ${MVM_USERNAME}@${MVM_DOMAIN}" , sizeof ( template -> subject ));
template -> attachment = TRUE ;
return template ;
}
/*! \brief Release memory allocated by message template */
static void message_template_free ( struct minivm_template * template )
{
if ( template -> body )
2007-06-06 21:20:11 +00:00
ast_free ( template -> body );
2007-04-18 07:57:18 +00:00
2007-06-06 21:20:11 +00:00
ast_free ( template );
2007-04-18 07:57:18 +00:00
}
/*! \brief Build message template from configuration */
static int message_template_build ( const char * name , struct ast_variable * var )
{
struct minivm_template * template ;
int error = 0 ;
template = message_template_create ( name );
if ( ! template ) {
ast_log ( LOG_ERROR , "Out of memory, can't allocate message template object %s. \n " , name );
return - 1 ;
}
while ( var ) {
2007-06-14 19:39:12 +00:00
ast_debug ( 3 , "-_-_- Configuring template option %s = \" %s \" for template %s \n " , var -> name , var -> value , name );
2007-04-18 07:57:18 +00:00
if ( ! strcasecmp ( var -> name , "fromaddress" )) {
ast_copy_string ( template -> fromaddress , var -> value , sizeof ( template -> fromaddress ));
} else if ( ! strcasecmp ( var -> name , "fromemail" )) {
ast_copy_string ( template -> serveremail , var -> value , sizeof ( template -> serveremail ));
} else if ( ! strcasecmp ( var -> name , "subject" )) {
ast_copy_string ( template -> subject , var -> value , sizeof ( template -> subject ));
} else if ( ! strcasecmp ( var -> name , "locale" )) {
ast_copy_string ( template -> locale , var -> value , sizeof ( template -> locale ));
} else if ( ! strcasecmp ( var -> name , "attachmedia" )) {
template -> attachment = ast_true ( var -> value );
} else if ( ! strcasecmp ( var -> name , "dateformat" )) {
ast_copy_string ( template -> dateformat , var -> value , sizeof ( template -> dateformat ));
} else if ( ! strcasecmp ( var -> name , "charset" )) {
ast_copy_string ( template -> charset , var -> value , sizeof ( template -> charset ));
} else if ( ! strcasecmp ( var -> name , "templatefile" )) {
if ( template -> body )
2007-06-06 21:20:11 +00:00
ast_free ( template -> body );
2007-04-18 07:57:18 +00:00
template -> body = message_template_parse_filebody ( var -> value );
if ( ! template -> body ) {
ast_log ( LOG_ERROR , "Error reading message body definition file %s \n " , var -> value );
error ++ ;
}
} else if ( ! strcasecmp ( var -> name , "messagebody" )) {
if ( template -> body )
2007-06-06 21:20:11 +00:00
ast_free ( template -> body );
2007-04-18 07:57:18 +00:00
template -> body = message_template_parse_emailbody ( var -> value );
if ( ! template -> body ) {
ast_log ( LOG_ERROR , "Error parsing message body definition: \n %s \n " , var -> value );
error ++ ;
}
} else {
ast_log ( LOG_ERROR , "Unknown message template configuration option \" %s=%s \"\n " , var -> name , var -> value );
error ++ ;
}
var = var -> next ;
}
if ( error )
ast_log ( LOG_ERROR , "-- %d errors found parsing message template definition %s \n " , error , name );
AST_LIST_LOCK ( & message_templates );
AST_LIST_INSERT_TAIL ( & message_templates , template , list );
AST_LIST_UNLOCK ( & message_templates );
global_stats . templates ++ ;
return error ;
}
/*! \brief Find named template */
static struct minivm_template * message_template_find ( const char * name )
{
struct minivm_template * this , * res = NULL ;
if ( ast_strlen_zero ( name ))
return NULL ;
AST_LIST_LOCK ( & message_templates );
AST_LIST_TRAVERSE ( & message_templates , this , list ) {
if ( ! strcasecmp ( this -> name , name )) {
res = this ;
break ;
}
}
AST_LIST_UNLOCK ( & message_templates );
return res ;
}
/*! \brief Clear list of templates */
static void message_destroy_list ( void )
{
struct minivm_template * this ;
AST_LIST_LOCK ( & message_templates );
while (( this = AST_LIST_REMOVE_HEAD ( & message_templates , list )))
message_template_free ( this );
AST_LIST_UNLOCK ( & message_templates );
}
/*! \brief read buffer from file (base64 conversion) */
static int b64_inbuf ( struct b64_baseio * bio , FILE * fi )
{
int l ;
if ( bio -> ateof )
return 0 ;
if (( l = fread ( bio -> iobuf , 1 , B64_BASEMAXINLINE , fi )) <= 0 ) {
if ( ferror ( fi ))
return - 1 ;
bio -> ateof = 1 ;
return 0 ;
}
bio -> iolen = l ;
bio -> iocp = 0 ;
return 1 ;
}
/*! \brief read character from file to buffer (base64 conversion) */
static int b64_inchar ( struct b64_baseio * bio , FILE * fi )
{
if ( bio -> iocp >= bio -> iolen ) {
if ( ! b64_inbuf ( bio , fi ))
return EOF ;
}
return bio -> iobuf [ bio -> iocp ++ ];
}
/*! \brief write buffer to file (base64 conversion) */
static int b64_ochar ( struct b64_baseio * bio , int c , FILE * so )
{
if ( bio -> linelength >= B64_BASELINELEN ) {
if ( fputs ( EOL , so ) == EOF )
return - 1 ;
bio -> linelength = 0 ;
}
if ( putc ((( unsigned char ) c ), so ) == EOF )
return - 1 ;
bio -> linelength ++ ;
return 1 ;
}
/*! \brief Encode file to base64 encoding for email attachment (base64 conversion) */
static int base_encode ( char * filename , FILE * so )
{
unsigned char dtable [ B64_BASEMAXINLINE ];
int i , hiteof = 0 ;
FILE * fi ;
struct b64_baseio bio ;
memset ( & bio , 0 , sizeof ( bio ));
bio . iocp = B64_BASEMAXINLINE ;
if ( ! ( fi = fopen ( filename , "rb" ))) {
ast_log ( LOG_WARNING , "Failed to open file: %s: %s \n " , filename , strerror ( errno ));
return - 1 ;
}
for ( i = 0 ; i < 9 ; i ++ ) {
dtable [ i ] = 'A' + i ;
dtable [ i + 9 ] = 'J' + i ;
dtable [ 26 + i ] = 'a' + i ;
dtable [ 26 + i + 9 ] = 'j' + i ;
}
for ( i = 0 ; i < 8 ; i ++ ) {
dtable [ i + 18 ] = 'S' + i ;
dtable [ 26 + i + 18 ] = 's' + i ;
}
for ( i = 0 ; i < 10 ; i ++ ) {
dtable [ 52 + i ] = '0' + i ;
}
dtable [ 62 ] = '+' ;
dtable [ 63 ] = '/' ;
while ( ! hiteof ){
unsigned char igroup [ 3 ], ogroup [ 4 ];
int c , n ;
igroup [ 0 ] = igroup [ 1 ] = igroup [ 2 ] = 0 ;
for ( n = 0 ; n < 3 ; n ++ ) {
if (( c = b64_inchar ( & bio , fi )) == EOF ) {
hiteof = 1 ;
break ;
}
igroup [ n ] = ( unsigned char ) c ;
}
if ( n > 0 ) {
ogroup [ 0 ] = dtable [ igroup [ 0 ] >> 2 ];
2007-07-31 01:10:47 +00:00
ogroup [ 1 ] = dtable [(( igroup [ 0 ] & 3 ) << 4 ) | ( igroup [ 1 ] >> 4 )];
ogroup [ 2 ] = dtable [(( igroup [ 1 ] & 0xF ) << 2 ) | ( igroup [ 2 ] >> 6 )];
2007-04-18 07:57:18 +00:00
ogroup [ 3 ] = dtable [ igroup [ 2 ] & 0x3F ];
if ( n < 3 ) {
ogroup [ 3 ] = '=' ;
if ( n < 2 )
ogroup [ 2 ] = '=' ;
}
for ( i = 0 ; i < 4 ; i ++ )
b64_ochar ( & bio , ogroup [ i ], so );
}
}
/* Put end of line - line feed */
if ( fputs ( EOL , so ) == EOF )
return 0 ;
fclose ( fi );
return 1 ;
}
static int get_date ( char * s , int len )
{
2007-07-18 19:47:20 +00:00
struct ast_tm tm ;
struct timeval tv = ast_tvnow ();
2007-06-14 22:09:20 +00:00
2007-07-18 19:47:20 +00:00
ast_localtime ( & tv , & tm , NULL );
return ast_strftime ( s , len , "%a %b %e %r %Z %Y" , & tm );
2007-04-18 07:57:18 +00:00
}
/*! \brief Free user structure - if it's allocated */
static void free_user ( struct minivm_account * vmu )
{
if ( vmu -> chanvars )
ast_variables_destroy ( vmu -> chanvars );
2007-06-06 21:20:11 +00:00
ast_free ( vmu );
2007-04-18 07:57:18 +00:00
}
/*! \brief Prepare for voicemail template by adding channel variables
to the channel
*/
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 )
{
char callerid [ 256 ];
struct ast_variable * var ;
if ( ! channel ) {
ast_log ( LOG_ERROR , "No allocated channel, giving up... \n " );
return ;
}
for ( var = vmu -> chanvars ; var ; var = var -> next )
pbx_builtin_setvar_helper ( channel , var -> name , var -> value );
/* Prepare variables for substition in email body and subject */
pbx_builtin_setvar_helper ( channel , "MVM_NAME" , vmu -> fullname );
pbx_builtin_setvar_helper ( channel , "MVM_DUR" , dur );
pbx_builtin_setvar_helper ( channel , "MVM_DOMAIN" , vmu -> domain );
pbx_builtin_setvar_helper ( channel , "MVM_USERNAME" , vmu -> username );
pbx_builtin_setvar_helper ( channel , "MVM_CALLERID" , ast_callerid_merge ( callerid , sizeof ( callerid ), cidname , cidnum , "Unknown Caller" ));
pbx_builtin_setvar_helper ( channel , "MVM_CIDNAME" , ( cidname ? cidname : "an unknown caller" ));
pbx_builtin_setvar_helper ( channel , "MVM_CIDNUM" , ( cidnum ? cidnum : "an unknown caller" ));
pbx_builtin_setvar_helper ( channel , "MVM_DATE" , date );
if ( ! ast_strlen_zero ( counter ))
pbx_builtin_setvar_helper ( channel , "MVM_COUNTER" , counter );
}
/*! \brief Set default values for Mini-Voicemail users */
static void populate_defaults ( struct minivm_account * vmu )
{
ast_copy_flags ( vmu , ( & globalflags ), AST_FLAGS_ALL );
ast_copy_string ( vmu -> attachfmt , default_vmformat , sizeof ( vmu -> attachfmt ));
vmu -> volgain = global_volgain ;
}
/*! \brief Fix quote of mail headers for non-ascii characters */
static char * mailheader_quote ( const char * from , char * to , size_t len )
{
char * ptr = to ;
* ptr ++ = '"' ;
for (; ptr < to + len - 1 ; from ++ ) {
if ( * from == '"' )
* ptr ++ = '\\' ;
else if ( * from == '\0' )
break ;
* ptr ++ = * from ;
}
if ( ptr < to + len - 1 )
* ptr ++ = '"' ;
* ptr = '\0' ;
return to ;
}
/*! \brief Allocate new vm user and set default values */
static struct minivm_account * mvm_user_alloc ( void )
{
struct minivm_account * new ;
2007-06-06 21:20:11 +00:00
new = ast_calloc ( 1 , sizeof ( * new ));
2007-04-18 07:57:18 +00:00
if ( ! new )
return NULL ;
populate_defaults ( new );
return new ;
}
/*! \brief Clear list of users */
static void vmaccounts_destroy_list ( void )
{
struct minivm_account * this ;
AST_LIST_LOCK ( & minivm_accounts );
while (( this = AST_LIST_REMOVE_HEAD ( & minivm_accounts , list )))
2007-06-06 21:20:11 +00:00
ast_free ( this );
2007-04-18 07:57:18 +00:00
AST_LIST_UNLOCK ( & minivm_accounts );
}
/*! \brief Find user from static memory object list */
static struct minivm_account * find_account ( const char * domain , const char * username , int createtemp )
{
struct minivm_account * vmu = NULL , * cur ;
if ( ast_strlen_zero ( domain ) || ast_strlen_zero ( username )) {
ast_log ( LOG_NOTICE , "No username or domain? \n " );
return NULL ;
}
2007-06-14 19:39:12 +00:00
ast_debug ( 3 , "-_-_-_- Looking for voicemail user %s in domain %s \n " , username , domain );
2007-04-18 07:57:18 +00:00
AST_LIST_LOCK ( & minivm_accounts );
AST_LIST_TRAVERSE ( & minivm_accounts , cur , list ) {
/* Is this the voicemail account we're looking for? */
if ( ! strcasecmp ( domain , cur -> domain ) && ! strcasecmp ( username , cur -> username ))
break ;
}
AST_LIST_UNLOCK ( & minivm_accounts );
if ( cur ) {
2007-06-14 19:39:12 +00:00
ast_debug ( 3 , "-_-_- Found account for %s@%s \n " , username , domain );
2007-04-18 07:57:18 +00:00
vmu = cur ;
} else
vmu = find_user_realtime ( domain , username );
if ( createtemp && ! vmu ) {
/* Create a temporary user, send e-mail and be gone */
vmu = mvm_user_alloc ();
ast_set2_flag ( vmu , TRUE , MVM_ALLOCED );
if ( vmu ) {
ast_copy_string ( vmu -> username , username , sizeof ( vmu -> username ));
ast_copy_string ( vmu -> domain , domain , sizeof ( vmu -> domain ));
2007-06-14 19:39:12 +00:00
ast_debug ( 1 , "--- Created temporary account \n " );
2007-04-18 07:57:18 +00:00
}
}
return vmu ;
}
/*! \brief Find user in realtime storage
Returns pointer to minivm_account structure
*/
static struct minivm_account * find_user_realtime ( const char * domain , const char * username )
{
struct ast_variable * var ;
struct minivm_account * retval ;
char name [ MAXHOSTNAMELEN ];
retval = mvm_user_alloc ();
if ( ! retval )
return NULL ;
if ( username )
ast_copy_string ( retval -> username , username , sizeof ( retval -> username ));
populate_defaults ( retval );
var = ast_load_realtime ( "minivm" , "username" , username , "domain" , domain , NULL );
if ( ! var ) {
2007-06-06 21:20:11 +00:00
ast_free ( retval );
2007-04-18 07:57:18 +00:00
return NULL ;
}
snprintf ( name , sizeof ( name ), "%s@%s" , username , domain );
create_vmaccount ( name , var , TRUE );
ast_variables_destroy ( var );
return retval ;
}
/*! \brief Send voicemail with audio file as an attachment */
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 )
{
FILE * p = NULL ;
int pfd ;
char email [ 256 ] = "" ;
char who [ 256 ] = "" ;
char date [ 256 ];
char bound [ 256 ];
char fname [ PATH_MAX ];
char dur [ PATH_MAX ];
char tmp [ 80 ] = "/tmp/astmail-XXXXXX" ;
char tmp2 [ PATH_MAX ];
2007-07-18 19:47:20 +00:00
struct timeval now ;
struct ast_tm tm ;
2007-04-18 07:57:18 +00:00
struct minivm_zone * the_zone = NULL ;
int len_passdata ;
struct ast_channel * ast ;
char * finalfilename ;
char * passdata = NULL ;
char * passdata2 = NULL ;
char * fromaddress ;
char * fromemail ;
if ( type == MVM_MESSAGE_EMAIL ) {
if ( vmu && ! ast_strlen_zero ( vmu -> email )) {
ast_copy_string ( email , vmu -> email , sizeof ( email ));
} else if ( ! ast_strlen_zero ( vmu -> username ) && ! ast_strlen_zero ( vmu -> domain ))
snprintf ( email , sizeof ( email ), "%s@%s" , vmu -> username , vmu -> domain );
} else if ( type == MVM_MESSAGE_PAGE ) {
ast_copy_string ( email , vmu -> pager , sizeof ( email ));
}
if ( ast_strlen_zero ( email )) {
ast_log ( LOG_WARNING , "No address to send message to. \n " );
return - 1 ;
}
2007-06-14 19:39:12 +00:00
ast_debug ( 3 , "-_-_- Sending mail to %s@%s - Using template %s \n " , vmu -> username , vmu -> domain , template -> name );
2007-04-18 07:57:18 +00:00
if ( ! strcmp ( format , "wav49" ))
format = "WAV" ;
/* If we have a gain option, process it now with sox */
if ( type == MVM_MESSAGE_EMAIL && ( vmu -> volgain < - .001 || vmu -> volgain > .001 ) ) {
char newtmp [ PATH_MAX ];
char tmpcmd [ PATH_MAX ];
int tmpfd ;
2007-10-01 15:23:19 +00:00
ast_copy_string ( newtmp , "/tmp/XXXXXX" , sizeof ( newtmp ));
2007-06-14 19:39:12 +00:00
ast_debug ( 3 , "newtmp: %s \n " , newtmp );
2007-04-18 07:57:18 +00:00
tmpfd = mkstemp ( newtmp );
snprintf ( tmpcmd , sizeof ( tmpcmd ), "sox -v %.4f %s.%s %s.%s" , vmu -> volgain , filename , format , newtmp , format );
ast_safe_system ( tmpcmd );
finalfilename = newtmp ;
2007-06-24 18:51:41 +00:00
ast_debug ( 3 , "-- VOLGAIN: Stored at: %s.%s - Level: %.4f - Mailbox: %s \n " , filename , format , vmu -> volgain , vmu -> username );
2007-04-18 07:57:18 +00:00
} else {
finalfilename = ast_strdupa ( filename );
}
/* Create file name */
snprintf ( fname , sizeof ( fname ), "%s.%s" , finalfilename , format );
2007-06-14 19:39:12 +00:00
if ( template -> attachment )
ast_debug ( 1 , "-- Attaching file '%s', format '%s', uservm is '%d' \n " , finalfilename , format , attach_user_voicemail );
2007-04-18 07:57:18 +00:00
/* Make a temporary file instead of piping directly to sendmail, in case the mail
command hangs */
pfd = mkstemp ( tmp );
if ( pfd > - 1 ) {
p = fdopen ( pfd , "w" );
if ( ! p ) {
close ( pfd );
pfd = - 1 ;
}
2007-06-14 19:39:12 +00:00
ast_debug ( 1 , "-_-_- Opening temp file for e-mail: %s \n " , tmp );
2007-04-18 07:57:18 +00:00
}
if ( ! p ) {
ast_log ( LOG_WARNING , "Unable to open temporary file '%s' \n " , tmp );
return - 1 ;
}
/* Allocate channel used for chanvar substitution */
ast = ast_channel_alloc ( 0 , AST_STATE_DOWN , 0 , 0 , "" , "" , "" , 0 , 0 );
snprintf ( dur , sizeof ( dur ), "%d:%02d" , duration / 60 , duration % 60 );
/* Does this user have a timezone specified? */
if ( ! ast_strlen_zero ( vmu -> zonetag )) {
/* Find the zone in the list */
struct minivm_zone * z ;
AST_LIST_LOCK ( & minivm_zones );
AST_LIST_TRAVERSE ( & minivm_zones , z , list ) {
if ( strcmp ( z -> name , vmu -> zonetag ))
continue ;
the_zone = z ;
}
AST_LIST_UNLOCK ( & minivm_zones );
}
2007-07-18 19:47:20 +00:00
now = ast_tvnow ();
2007-04-18 07:57:18 +00:00
ast_localtime ( & now , & tm , the_zone ? the_zone -> timezone : NULL );
2007-07-18 19:47:20 +00:00
ast_strftime ( date , sizeof ( date ), "%a, %d %b %Y %H:%M:%S %z" , & tm );
2007-04-18 07:57:18 +00:00
/* Start printing the email to the temporary file */
fprintf ( p , "Date: %s \n " , date );
/* Set date format for voicemail mail */
2007-07-18 19:47:20 +00:00
ast_strftime ( date , sizeof ( date ), template -> dateformat , & tm );
2007-04-18 07:57:18 +00:00
/* Populate channel with channel variables for substitution */
prep_email_sub_vars ( ast , vmu , cidnum , cidname , dur , date , counter );
/* Find email address to use */
/* If there's a server e-mail adress in the account, user that, othterwise template */
fromemail = ast_strlen_zero ( vmu -> serveremail ) ? template -> serveremail : vmu -> serveremail ;
/* Find name to user for server e-mail */
fromaddress = ast_strlen_zero ( template -> fromaddress ) ? "" : template -> fromaddress ;
/* If needed, add hostname as domain */
if ( ast_strlen_zero ( fromemail ))
fromemail = "asterisk" ;
if ( strchr ( fromemail , '@' ))
ast_copy_string ( who , fromemail , sizeof ( who ));
else {
char host [ MAXHOSTNAMELEN ];
gethostname ( host , sizeof ( host ) - 1 );
snprintf ( who , sizeof ( who ), "%s@%s" , fromemail , host );
}
if ( ast_strlen_zero ( fromaddress )) {
fprintf ( p , "From: Asterisk PBX <%s> \n " , who );
} else {
/* Allocate a buffer big enough for variable substitution */
int vmlen = strlen ( fromaddress ) * 3 + 200 ;
2007-06-28 18:59:19 +00:00
ast_debug ( 4 , "-_-_- Fromaddress template: %s \n " , fromaddress );
2007-04-18 07:57:18 +00:00
if (( passdata = alloca ( vmlen ))) {
memset ( passdata , 0 , vmlen );
pbx_substitute_variables_helper ( ast , fromaddress , passdata , vmlen );
len_passdata = strlen ( passdata ) * 2 + 3 ;
passdata2 = alloca ( len_passdata );
fprintf ( p , "From: %s <%s> \n " , mailheader_quote ( passdata , passdata2 , len_passdata ), who );
} else {
ast_log ( LOG_WARNING , "Cannot allocate workspace for variable substitution \n " );
fclose ( p );
return - 1 ;
}
}
2007-06-14 19:39:12 +00:00
ast_debug ( 4 , "-_-_- Fromstring now: %s \n " , ast_strlen_zero ( passdata ) ? "-default-" : passdata );
2007-04-18 07:57:18 +00:00
2007-08-22 23:17:04 +00:00
fprintf ( p , "Message-ID: <Asterisk-%d-%s-%d-%s> \n " , ( unsigned int ) rand (), vmu -> username , ( int ) getpid (), who );
2007-04-18 07:57:18 +00:00
len_passdata = strlen ( vmu -> fullname ) * 2 + 3 ;
passdata2 = alloca ( len_passdata );
if ( ! ast_strlen_zero ( vmu -> email ))
fprintf ( p , "To: %s <%s> \n " , mailheader_quote ( vmu -> fullname , passdata2 , len_passdata ), vmu -> email );
else
fprintf ( p , "To: %s <%s@%s> \n " , mailheader_quote ( vmu -> fullname , passdata2 , len_passdata ), vmu -> username , vmu -> domain );
if ( ! ast_strlen_zero ( template -> subject )) {
char * passdata ;
int vmlen = strlen ( template -> subject ) * 3 + 200 ;
if (( passdata = alloca ( vmlen ))) {
memset ( passdata , 0 , vmlen );
pbx_substitute_variables_helper ( ast , template -> subject , passdata , vmlen );
fprintf ( p , "Subject: %s \n " , passdata );
} else {
ast_log ( LOG_WARNING , "Cannot allocate workspace for variable substitution \n " );
fclose ( p );
return - 1 ;
}
2007-06-14 19:39:12 +00:00
ast_debug ( 4 , "-_-_- Subject now: %s \n " , passdata );
2007-04-18 07:57:18 +00:00
} else {
fprintf ( p , "Subject: New message in mailbox %s@%s \n " , vmu -> username , vmu -> domain );
2007-06-14 19:39:12 +00:00
ast_debug ( 1 , "-_-_- Using default subject for this email \n " );
2007-04-18 07:57:18 +00:00
}
if ( option_debug > 2 )
fprintf ( p , "X-Asterisk-debug: template %s user account %s@%s \n " , template -> name , vmu -> username , vmu -> domain );
fprintf ( p , "MIME-Version: 1.0 \n " );
/* Something unique. */
2007-08-22 23:17:04 +00:00
snprintf ( bound , sizeof ( bound ), "voicemail_%s%d%d" , vmu -> username , ( int ) getpid (), ( unsigned int ) rand ());
2007-04-18 07:57:18 +00:00
fprintf ( p , "Content-Type: multipart/mixed; boundary= \" %s \"\n\n\n " , bound );
fprintf ( p , "--%s \n " , bound );
fprintf ( p , "Content-Type: text/plain; charset=%s \n Content-Transfer-Encoding: 8bit \n\n " , global_charset );
if ( ! ast_strlen_zero ( template -> body )) {
char * passdata ;
int vmlen = strlen ( template -> body ) * 3 + 200 ;
if (( passdata = alloca ( vmlen ))) {
memset ( passdata , 0 , vmlen );
pbx_substitute_variables_helper ( ast , template -> body , passdata , vmlen );
2007-06-14 19:39:12 +00:00
ast_debug ( 3 , "Message now: %s \n ----- \n " , passdata );
2007-04-18 07:57:18 +00:00
fprintf ( p , "%s \n " , passdata );
} else
ast_log ( LOG_WARNING , "Cannot allocate workspace for variable substitution \n " );
} else {
fprintf ( p , "Dear %s: \n\n\t Just wanted to let you know you were just left a %s long message \n "
"in mailbox %s from %s, on %s so you might \n "
"want to check it when you get a chance. Thanks! \n\n\t\t\t\t --Asterisk \n\n " , vmu -> fullname ,
dur , vmu -> username , ( cidname ? cidname : ( cidnum ? cidnum : "an unknown caller" )), date );
2007-06-14 19:39:12 +00:00
ast_debug ( 3 , "Using default message body (no template) \n ----- \n " );
2007-04-18 07:57:18 +00:00
}
/* Eww. We want formats to tell us their own MIME type */
if ( template -> attachment ) {
char * ctype = "audio/x-" ;
2007-06-28 18:59:19 +00:00
ast_debug ( 3 , "-_-_- Attaching file to message: %s \n " , fname );
2007-04-18 07:57:18 +00:00
if ( ! strcasecmp ( format , "ogg" ))
ctype = "application/" ;
fprintf ( p , "--%s \n " , bound );
fprintf ( p , "Content-Type: %s%s; name= \" voicemailmsg.%s \"\n " , ctype , format , format );
fprintf ( p , "Content-Transfer-Encoding: base64 \n " );
fprintf ( p , "Content-Description: Voicemail sound attachment. \n " );
fprintf ( p , "Content-Disposition: attachment; filename= \" voicemail%s.%s \"\n\n " , counter ? counter : "" , format );
base_encode ( fname , p );
fprintf ( p , " \n\n --%s-- \n . \n " , bound );
}
fclose ( p );
snprintf ( tmp2 , sizeof ( tmp2 ), "( %s < %s ; rm -f %s ) &" , global_mailcmd , tmp , tmp );
ast_safe_system ( tmp2 );
2007-06-14 19:39:12 +00:00
ast_debug ( 1 , "Sent message to %s with command '%s' - %s \n " , vmu -> email , global_mailcmd , template -> attachment ? "(media attachment)" : "" );
ast_debug ( 3 , "-_-_- Actual command used: %s \n " , tmp2 );
2007-04-18 07:57:18 +00:00
if ( ast )
ast_channel_free ( ast );
return 0 ;
}
/*! \brief Create directory based on components */
static int make_dir ( char * dest , int len , const char * domain , const char * username , const char * folder )
{
return snprintf ( dest , len , "%s%s/%s%s%s" , MVM_SPOOL_DIR , domain , username , ast_strlen_zero ( folder ) ? "" : "/" , folder ? folder : "" );
}
/*! \brief Checks if directory exists. Does not create directory, but builds string in dest
* \param dest String. base directory.
2007-05-31 10:26:55 +00:00
* \param len Int. Length base directory string.
2007-04-18 07:57:18 +00:00
* \param domain String. Ignored if is null or empty string.
* \param username String. Ignored if is null or empty string.
* \param folder String. Ignored if is null or empty string.
* \return 0 on failure, 1 on success.
*/
static int check_dirpath ( char * dest , int len , char * domain , char * username , char * folder )
{
struct stat filestat ;
make_dir ( dest , len , domain , username , folder ? folder : "" );
if ( stat ( dest , & filestat ) == - 1 )
return FALSE ;
else
return TRUE ;
}
/*! \brief basically mkdir -p $dest/$domain/$username/$folder
* \param dest String. base directory.
* \param len Length of directory string
* \param domain String. Ignored if is null or empty string.
* \param folder String. Ignored if is null or empty string.
2007-05-31 10:26:55 +00:00
* \param username String. Ignored if is null or empty string.
2007-04-18 07:57:18 +00:00
* \return -1 on failure, 0 on success.
*/
static int create_dirpath ( char * dest , int len , char * domain , char * username , char * folder )
{
2007-06-22 04:35:12 +00:00
int res ;
make_dir ( dest , len , domain , username , folder );
if (( res = ast_mkdir ( dest , 0777 ))) {
ast_log ( LOG_WARNING , "ast_mkdir '%s' failed: %s \n " , dest , strerror ( res ));
return - 1 ;
2007-04-18 07:57:18 +00:00
}
2007-06-14 19:39:12 +00:00
ast_debug ( 2 , "Creating directory for %s@%s folder %s : %s \n " , username , domain , folder , dest );
2007-04-18 07:57:18 +00:00
return 0 ;
}
/*! \brief Play intro message before recording voicemail
*/
static int invent_message ( struct ast_channel * chan , char * domain , char * username , int busy , char * ecodes )
{
int res ;
char fn [ PATH_MAX ];
2007-06-14 19:39:12 +00:00
ast_debug ( 2 , "-_-_- Still preparing to play message ... \n " );
2007-04-18 07:57:18 +00:00
snprintf ( fn , sizeof ( fn ), "%s%s/%s/greet" , MVM_SPOOL_DIR , domain , username );
if ( ast_fileexists ( fn , NULL , NULL ) > 0 ) {
res = ast_streamfile ( chan , fn , chan -> language );
if ( res )
return - 1 ;
res = ast_waitstream ( chan , ecodes );
if ( res )
return res ;
} else {
int numericusername = 1 ;
char * i = username ;
2007-06-14 19:39:12 +00:00
ast_debug ( 2 , "-_-_- No personal prompts. Using default prompt set for language \n " );
2007-04-18 07:57:18 +00:00
while ( * i ) {
2007-06-14 19:39:12 +00:00
ast_debug ( 2 , "-_-_- Numeric? Checking %c \n " , * i );
2007-04-18 07:57:18 +00:00
if ( ! isdigit ( * i )) {
numericusername = FALSE ;
break ;
}
i ++ ;
}
if ( numericusername ) {
if ( ast_streamfile ( chan , "vm-theperson" , chan -> language ))
return - 1 ;
if (( res = ast_waitstream ( chan , ecodes )))
return res ;
res = ast_say_digit_str ( chan , username , ecodes , chan -> language );
if ( res )
return res ;
} else {
if ( ast_streamfile ( chan , "vm-theextensionis" , chan -> language ))
return - 1 ;
if (( res = ast_waitstream ( chan , ecodes )))
return res ;
}
}
res = ast_streamfile ( chan , busy ? "vm-isonphone" : "vm-isunavail" , chan -> language );
if ( res )
return - 1 ;
res = ast_waitstream ( chan , ecodes );
return res ;
}
/*! \brief Delete media files and attribute file */
static int vm_delete ( char * file )
{
int res ;
2007-06-14 19:39:12 +00:00
ast_debug ( 1 , "-_-_- Deleting voicemail file %s \n " , file );
2007-04-18 07:57:18 +00:00
res = unlink ( file ); /* Remove the meta data file */
res |= ast_filedelete ( file , NULL ); /* remove the media file */
return res ;
}
/*! \brief Record voicemail message & let caller review or re-record it, or set options if applicable */
static int play_record_review ( struct ast_channel * chan , char * playfile , char * recordfile , int maxtime , char * fmt ,
int outsidecaller , struct minivm_account * vmu , int * duration , const char * unlockdir ,
signed char record_gain )
{
int cmd = 0 ;
int max_attempts = 3 ;
int attempts = 0 ;
int recorded = 0 ;
int message_exists = 0 ;
signed char zero_gain = 0 ;
char * acceptdtmf = "#" ;
char * canceldtmf = "" ;
/* Note that urgent and private are for flagging messages as such in the future */
/* barf if no pointer passed to store duration in */
if ( duration == NULL ) {
ast_log ( LOG_WARNING , "Error play_record_review called without duration pointer \n " );
return - 1 ;
}
cmd = '3' ; /* Want to start by recording */
while (( cmd >= 0 ) && ( cmd != 't' )) {
switch ( cmd ) {
case '2' :
/* Review */
if ( option_verbose > 2 )
ast_verbose ( VERBOSE_PREFIX_3 "Reviewing the message \n " );
ast_streamfile ( chan , recordfile , chan -> language );
cmd = ast_waitstream ( chan , AST_DIGIT_ANY );
break ;
case '3' :
message_exists = 0 ;
/* Record */
if ( option_verbose > 2 ) {
if ( recorded == 1 )
ast_verbose ( VERBOSE_PREFIX_3 "Re-recording the message \n " );
else
ast_verbose ( VERBOSE_PREFIX_3 "Recording the message \n " );
}
if ( recorded && outsidecaller )
cmd = ast_play_and_wait ( chan , "beep" );
recorded = 1 ;
/* 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 */
if ( record_gain )
ast_channel_setoption ( chan , AST_OPTION_RXGAIN , & record_gain , sizeof ( record_gain ), 0 );
if ( ast_test_flag ( vmu , MVM_OPERATOR ))
canceldtmf = "0" ;
cmd = ast_play_and_record_full ( chan , playfile , recordfile , maxtime , fmt , duration , global_silencethreshold , global_maxsilence , unlockdir , acceptdtmf , canceldtmf );
if ( record_gain )
ast_channel_setoption ( chan , AST_OPTION_RXGAIN , & zero_gain , sizeof ( zero_gain ), 0 );
if ( cmd == - 1 ) /* User has hung up, no options to give */
return cmd ;
if ( cmd == '0' )
break ;
else if ( cmd == '*' )
break ;
else {
/* If all is well, a message exists */
message_exists = 1 ;
cmd = 0 ;
}
break ;
case '4' :
case '5' :
case '6' :
case '7' :
case '8' :
case '9' :
case '*' :
case '#' :
cmd = ast_play_and_wait ( chan , "vm-sorry" );
break ;
case '0' :
if ( ! ast_test_flag ( vmu , MVM_OPERATOR )) {
cmd = ast_play_and_wait ( chan , "vm-sorry" );
break ;
}
if ( message_exists || recorded ) {
cmd = ast_play_and_wait ( chan , "vm-saveoper" );
if ( ! cmd )
cmd = ast_waitfordigit ( chan , 3000 );
if ( cmd == '1' ) {
ast_play_and_wait ( chan , "vm-msgsaved" );
cmd = '0' ;
} else {
ast_play_and_wait ( chan , "vm-deleted" );
vm_delete ( recordfile );
cmd = '0' ;
}
}
return cmd ;
default :
/* If the caller is an ouside caller, and the review option is enabled,
allow them to review the message, but let the owner of the box review
their OGM's */
if ( outsidecaller && ! ast_test_flag ( vmu , MVM_REVIEW ))
return cmd ;
if ( message_exists ) {
cmd = ast_play_and_wait ( chan , "vm-review" );
} else {
cmd = ast_play_and_wait ( chan , "vm-torerecord" );
if ( ! cmd )
cmd = ast_waitfordigit ( chan , 600 );
}
if ( ! cmd && outsidecaller && ast_test_flag ( vmu , MVM_OPERATOR )) {
cmd = ast_play_and_wait ( chan , "vm-reachoper" );
if ( ! cmd )
cmd = ast_waitfordigit ( chan , 600 );
}
if ( ! cmd )
cmd = ast_waitfordigit ( chan , 6000 );
if ( ! cmd ) {
attempts ++ ;
}
if ( attempts > max_attempts ) {
cmd = 't' ;
}
}
}
if ( outsidecaller )
ast_play_and_wait ( chan , "vm-goodbye" );
if ( cmd == 't' )
cmd = 0 ;
return cmd ;
}
/*! \brief Run external notification for voicemail message */
static void run_externnotify ( struct ast_channel * chan , struct minivm_account * vmu )
{
char arguments [ BUFSIZ ];
if ( ast_strlen_zero ( vmu -> externnotify ) && ast_strlen_zero ( global_externnotify ))
return ;
snprintf ( arguments , sizeof ( arguments ), "%s %s@%s %s %s&" ,
ast_strlen_zero ( vmu -> externnotify ) ? global_externnotify : vmu -> externnotify ,
vmu -> username , vmu -> domain ,
chan -> cid . cid_name , chan -> cid . cid_num );
2007-06-14 19:39:12 +00:00
ast_debug ( 1 , "Executing: %s \n " , arguments );
2007-04-18 07:57:18 +00:00
ast_safe_system ( arguments );
}
/*! \brief Send message to voicemail account owner */
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 )
{
char * stringp ;
struct minivm_template * etemplate ;
char * messageformat ;
int res = 0 ;
char oldlocale [ 100 ];
const char * counter ;
if ( ! ast_strlen_zero ( vmu -> attachfmt )) {
if ( strstr ( format , vmu -> attachfmt )) {
format = vmu -> attachfmt ;
} else
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 );
}
etemplate = message_template_find ( vmu -> etemplate );
if ( ! etemplate )
etemplate = message_template_find ( templatename );
if ( ! etemplate )
etemplate = message_template_find ( "email-default" );
/* Attach only the first format */
stringp = messageformat = ast_strdupa ( format );
strsep ( & stringp , "|" );
if ( ! ast_strlen_zero ( etemplate -> locale )) {
char * newlocale ;
ast_copy_string ( oldlocale , setlocale ( LC_TIME , NULL ), sizeof ( oldlocale ));
2007-06-14 19:39:12 +00:00
ast_debug ( 2 , "-_-_- Changing locale from %s to %s \n " , oldlocale , etemplate -> locale );
2007-04-18 07:57:18 +00:00
newlocale = setlocale ( LC_TIME , etemplate -> locale );
if ( newlocale == NULL ) {
ast_log ( LOG_WARNING , "-_-_- Changing to new locale did not work. Locale: %s \n " , etemplate -> locale );
}
}
/* Read counter if available */
counter = pbx_builtin_getvar_helper ( chan , "MVM_COUNTER" );
2007-06-14 19:39:12 +00:00
if ( ast_strlen_zero ( counter )) {
ast_debug ( 2 , "-_-_- MVM_COUNTER not found \n " );
} else {
ast_debug ( 2 , "-_-_- MVM_COUNTER found - will use it with value %s \n " , counter );
2007-04-18 07:57:18 +00:00
}
res = sendmail ( etemplate , vmu , cidnum , cidname , filename , messageformat , duration , etemplate -> attachment , MVM_MESSAGE_EMAIL , counter );
if ( res == 0 && ! ast_strlen_zero ( vmu -> pager )) {
/* Find template for paging */
etemplate = message_template_find ( vmu -> ptemplate );
if ( ! etemplate )
etemplate = message_template_find ( "pager-default" );
if ( etemplate -> locale ) {
ast_copy_string ( oldlocale , setlocale ( LC_TIME , "" ), sizeof ( oldlocale ));
setlocale ( LC_TIME , etemplate -> locale );
}
res = sendmail ( etemplate , vmu , cidnum , cidname , filename , messageformat , duration , etemplate -> attachment , MVM_MESSAGE_PAGE , counter );
}
manager_event ( EVENT_FLAG_CALL , "MiniVoiceMail" , "Action: SentNotification \r n \n Mailbox: %s@%s \r\n Counter: %s \r\n " , vmu -> username , vmu -> domain , counter );
run_externnotify ( chan , vmu ); /* Run external notification */
if ( etemplate -> locale )
setlocale ( LC_TIME , oldlocale ); /* Rest to old locale */
return res ;
}
/*! \brief Record voicemail message, store into file prepared for sending e-mail */
static int leave_voicemail ( struct ast_channel * chan , char * username , struct leave_vm_options * options )
{
char tmptxtfile [ PATH_MAX ];
char callerid [ 256 ];
FILE * txt ;
int res = 0 , txtdes ;
int msgnum ;
int duration = 0 ;
char date [ 256 ];
char tmpdir [ PATH_MAX ];
char ext_context [ 256 ] = "" ;
char fmt [ 80 ];
char * domain ;
char tmp [ 256 ] = "" ;
struct minivm_account * vmu ;
int userdir ;
ast_copy_string ( tmp , username , sizeof ( tmp ));
username = tmp ;
domain = strchr ( tmp , '@' );
if ( domain ) {
* domain = '\0' ;
domain ++ ;
}
if ( ! ( vmu = find_account ( domain , username , TRUE ))) {
/* We could not find user, let's exit */
ast_log ( LOG_ERROR , "Can't allocate temporary account for '%s@%s' \n " , username , domain );
pbx_builtin_setvar_helper ( chan , "MINIVM_RECORD_STATUS" , "FAILED" );
return 0 ;
}
/* Setup pre-file if appropriate */
if ( strcmp ( vmu -> domain , "localhost" ))
snprintf ( ext_context , sizeof ( ext_context ), "%s@%s" , username , vmu -> domain );
else
ast_copy_string ( ext_context , vmu -> domain , sizeof ( ext_context ));
/* The meat of recording the message... All the announcements and beeps have been played*/
if ( ast_strlen_zero ( vmu -> attachfmt ))
ast_copy_string ( fmt , default_vmformat , sizeof ( fmt ));
else
ast_copy_string ( fmt , vmu -> attachfmt , sizeof ( fmt ));
if ( ast_strlen_zero ( fmt )) {
ast_log ( LOG_WARNING , "No format for saving voicemail? Default %s \n " , default_vmformat );
pbx_builtin_setvar_helper ( chan , "MINIVM_RECORD_STATUS" , "FAILED" );
return res ;
}
msgnum = 0 ;
userdir = check_dirpath ( tmpdir , sizeof ( tmpdir ), vmu -> domain , username , "tmp" );
/* If we have no user directory, use generic temporary directory */
if ( ! userdir ) {
create_dirpath ( tmpdir , sizeof ( tmpdir ), "0000_minivm_temp" , "mediafiles" , "" );
2007-06-14 19:39:12 +00:00
ast_debug ( 3 , "Creating temporary directory %s \n " , tmpdir );
2007-04-18 07:57:18 +00:00
}
snprintf ( tmptxtfile , sizeof ( tmptxtfile ), "%s/XXXXXX" , tmpdir );
/* XXX This file needs to be in temp directory */
txtdes = mkstemp ( tmptxtfile );
if ( txtdes < 0 ) {
ast_log ( LOG_ERROR , "Unable to create message file %s: %s \n " , tmptxtfile , strerror ( errno ));
res = ast_streamfile ( chan , "vm-mailboxfull" , chan -> language );
if ( ! res )
res = ast_waitstream ( chan , "" );
pbx_builtin_setvar_helper ( chan , "MINIVM_RECORD_STATUS" , "FAILED" );
return res ;
}
if ( res >= 0 ) {
/* Unless we're *really* silent, try to send the beep */
res = ast_streamfile ( chan , "beep" , chan -> language );
if ( ! res )
res = ast_waitstream ( chan , "" );
}
/* OEJ XXX Maybe this can be turned into a log file? Hmm. */
/* Store information */
2007-06-14 19:39:12 +00:00
ast_debug ( 2 , "Open file for metadata: %s \n " , tmptxtfile );
2007-04-18 07:57:18 +00:00
res = play_record_review ( chan , NULL , tmptxtfile , global_vmmaxmessage , fmt , 1 , vmu , & duration , NULL , options -> record_gain );
txt = fdopen ( txtdes , "w+" );
if ( ! txt ) {
ast_log ( LOG_WARNING , "Error opening text file for output \n " );
} else {
2007-07-18 19:47:20 +00:00
struct ast_tm tm ;
struct timeval now = ast_tvnow ();
2007-04-18 07:57:18 +00:00
char timebuf [ 30 ];
char logbuf [ BUFSIZ ];
get_date ( date , sizeof ( date ));
ast_localtime ( & now , & tm , NULL );
2007-07-18 19:47:20 +00:00
ast_strftime ( timebuf , sizeof ( timebuf ), "%H:%M:%S" , & tm );
2007-04-18 07:57:18 +00:00
snprintf ( logbuf , sizeof ( logbuf ),
/* "Mailbox:domain:macrocontext:exten:priority:callerchan:callerid:origdate:origtime:duration:durationstatus:accountcode" */
"%s:%s:%s:%s:%d:%s:%s:%s:%s:%d:%s:%s \n " ,
username ,
chan -> context ,
chan -> macrocontext ,
chan -> exten ,
chan -> priority ,
chan -> name ,
ast_callerid_merge ( callerid , sizeof ( callerid ), chan -> cid . cid_name , chan -> cid . cid_num , "Unknown" ),
date ,
timebuf ,
duration ,
duration < global_vmminmessage ? "IGNORED" : "OK" ,
vmu -> accountcode
);
fprintf ( txt , logbuf );
if ( minivmlogfile ) {
ast_mutex_lock ( & minivmloglock );
fprintf ( minivmlogfile , logbuf );
ast_mutex_unlock ( & minivmloglock );
}
if ( duration < global_vmminmessage ) {
if ( option_verbose > 2 )
ast_verbose ( VERBOSE_PREFIX_3 "Recording was %d seconds long but needs to be at least %d - abandoning \n " , duration , global_vmminmessage );
fclose ( txt );
ast_filedelete ( tmptxtfile , NULL );
unlink ( tmptxtfile );
pbx_builtin_setvar_helper ( chan , "MINIVM_RECORD_STATUS" , "FAILED" );
return 0 ;
}
fclose ( txt ); /* Close log file */
if ( ast_fileexists ( tmptxtfile , NULL , NULL ) <= 0 ) {
2007-06-14 19:39:12 +00:00
ast_debug ( 1 , "The recorded media file is gone, so we should remove the .txt file too! \n " );
2007-04-18 07:57:18 +00:00
unlink ( tmptxtfile );
pbx_builtin_setvar_helper ( chan , "MINIVM_RECORD_STATUS" , "FAILED" );
if ( ast_test_flag ( vmu , MVM_ALLOCED ))
free_user ( vmu );
return 0 ;
}
/* Set channel variables for the notify application */
pbx_builtin_setvar_helper ( chan , "MVM_FILENAME" , tmptxtfile );
snprintf ( timebuf , sizeof ( timebuf ), "%d" , duration );
pbx_builtin_setvar_helper ( chan , "MVM_DURATION" , timebuf );
pbx_builtin_setvar_helper ( chan , "MVM_FORMAT" , fmt );
}
2007-07-18 19:47:20 +00:00
global_stats . lastreceived = ast_tvnow ();
2007-04-18 07:57:18 +00:00
global_stats . receivedmessages ++ ;
// /* Go ahead and delete audio files from system, they're not needed any more */
// if (ast_fileexists(tmptxtfile, NULL, NULL) <= 0) {
// ast_filedelete(tmptxtfile, NULL);
2007-06-14 19:39:12 +00:00
// /* Even not being used at the moment, it's better to convert ast_log to ast_debug anyway */
// ast_debug(2, "-_-_- Deleted audio file after notification :: %s \n", tmptxtfile);
2007-04-18 07:57:18 +00:00
// }
if ( res > 0 )
res = 0 ;
if ( ast_test_flag ( vmu , MVM_ALLOCED ))
free_user ( vmu );
pbx_builtin_setvar_helper ( chan , "MINIVM_RECORD_STATUS" , "SUCCESS" );
return res ;
}
/*! \brief Notify voicemail account owners - either generic template or user specific */
static int minivm_notify_exec ( struct ast_channel * chan , void * data )
{
int argc ;
char * argv [ 2 ];
int res = 0 ;
char tmp [ PATH_MAX ];
char * domain ;
char * tmpptr ;
struct minivm_account * vmu ;
char * username = argv [ 0 ];
const char * template = "" ;
const char * filename ;
const char * format ;
const char * duration_string ;
if ( ast_strlen_zero ( data )) {
ast_log ( LOG_ERROR , "Minivm needs at least an account argument \n " );
return - 1 ;
}
tmpptr = ast_strdupa (( char * ) data );
if ( ! tmpptr ) {
ast_log ( LOG_ERROR , "Out of memory \n " );
return - 1 ;
}
2007-07-31 01:10:47 +00:00
argc = ast_app_separate_args ( tmpptr , ',' , argv , sizeof ( argv ) / sizeof ( argv [ 0 ]));
2007-04-18 07:57:18 +00:00
if ( argc == 2 && ! ast_strlen_zero ( argv [ 1 ]))
template = argv [ 1 ];
ast_copy_string ( tmp , argv [ 0 ], sizeof ( tmp ));
username = tmp ;
domain = strchr ( tmp , '@' );
if ( domain ) {
* domain = '\0' ;
domain ++ ;
}
if ( ast_strlen_zero ( domain ) || ast_strlen_zero ( username )) {
ast_log ( LOG_ERROR , "Need username@domain as argument. Sorry. Argument 0 %s \n " , argv [ 0 ]);
return - 1 ;
}
if ( ! ( vmu = find_account ( domain , username , TRUE ))) {
/* We could not find user, let's exit */
ast_log ( LOG_WARNING , "Could not allocate temporary memory for '%s@%s' \n " , username , domain );
pbx_builtin_setvar_helper ( chan , "MINIVM_NOTIFY_STATUS" , "FAILED" );
return - 1 ;
}
filename = pbx_builtin_getvar_helper ( chan , "MVM_FILENAME" );
format = pbx_builtin_getvar_helper ( chan , "MVM_FORMAT" );
duration_string = pbx_builtin_getvar_helper ( chan , "MVM_DURATION" );
/* Notify of new message to e-mail and pager */
if ( ! ast_strlen_zero ( filename )) {
res = notify_new_message ( chan , template , vmu , filename , atoi ( duration_string ), format , chan -> cid . cid_num , chan -> cid . cid_name );
};
pbx_builtin_setvar_helper ( chan , "MINIVM_NOTIFY_STATUS" , res == 0 ? "SUCCESS" : "FAILED" );
if ( ast_test_flag ( vmu , MVM_ALLOCED ))
free_user ( vmu );
/* Ok, we're ready to rock and roll. Return to dialplan */
return res ;
}
/*! \brief Dialplan function to record voicemail */
static int minivm_record_exec ( struct ast_channel * chan , void * data )
{
int res = 0 ;
char * tmp ;
struct leave_vm_options leave_options ;
int argc ;
char * argv [ 2 ];
struct ast_flags flags = { 0 };
char * opts [ OPT_ARG_ARRAY_SIZE ];
2007-07-16 14:39:29 +00:00
2007-04-18 07:57:18 +00:00
memset ( & leave_options , 0 , sizeof ( leave_options ));
/* Answer channel if it's not already answered */
if ( chan -> _state != AST_STATE_UP )
ast_answer ( chan );
if ( ast_strlen_zero ( data )) {
ast_log ( LOG_ERROR , "Minivm needs at least an account argument \n " );
return - 1 ;
}
tmp = ast_strdupa (( char * ) data );
if ( ! tmp ) {
ast_log ( LOG_ERROR , "Out of memory \n " );
return - 1 ;
}
2007-07-31 01:10:47 +00:00
argc = ast_app_separate_args ( tmp , ',' , argv , sizeof ( argv ) / sizeof ( argv [ 0 ]));
2007-04-18 07:57:18 +00:00
if ( argc == 2 ) {
if ( ast_app_parse_options ( minivm_app_options , & flags , opts , argv [ 1 ])) {
return - 1 ;
}
ast_copy_flags ( & leave_options , & flags , OPT_SILENT | OPT_BUSY_GREETING | OPT_UNAVAIL_GREETING );
if ( ast_test_flag ( & flags , OPT_RECORDGAIN )) {
int gain ;
if ( sscanf ( opts [ OPT_ARG_RECORDGAIN ], "%d" , & gain ) != 1 ) {
ast_log ( LOG_WARNING , "Invalid value '%s' provided for record gain option \n " , opts [ OPT_ARG_RECORDGAIN ]);
return - 1 ;
} else
leave_options . record_gain = ( signed char ) gain ;
}
}
/* Now run the appliation and good luck to you! */
res = leave_voicemail ( chan , argv [ 0 ], & leave_options );
if ( res == ERROR_LOCK_PATH ) {
ast_log ( LOG_ERROR , "Could not leave voicemail. The path is already locked. \n " );
pbx_builtin_setvar_helper ( chan , "MINIVM_RECORD_STATUS" , "FAILED" );
res = 0 ;
}
pbx_builtin_setvar_helper ( chan , "MINIVM_RECORD_STATUS" , "SUCCESS" );
return res ;
}
/*! \brief Play voicemail prompts - either generic or user specific */
static int minivm_greet_exec ( struct ast_channel * chan , void * data )
{
struct leave_vm_options leave_options = { 0 , '\0' };
int argc ;
char * argv [ 2 ];
struct ast_flags flags = { 0 };
char * opts [ OPT_ARG_ARRAY_SIZE ];
int res = 0 ;
int ausemacro = 0 ;
int ousemacro = 0 ;
int ouseexten = 0 ;
char tmp [ PATH_MAX ];
char dest [ PATH_MAX ];
char prefile [ PATH_MAX ];
char tempfile [ PATH_MAX ] = "" ;
char ext_context [ 256 ] = "" ;
char * domain ;
char ecodes [ 16 ] = "#" ;
char * tmpptr ;
struct minivm_account * vmu ;
char * username = argv [ 0 ];
if ( ast_strlen_zero ( data )) {
ast_log ( LOG_ERROR , "Minivm needs at least an account argument \n " );
return - 1 ;
}
tmpptr = ast_strdupa (( char * ) data );
if ( ! tmpptr ) {
ast_log ( LOG_ERROR , "Out of memory \n " );
return - 1 ;
}
2007-07-31 01:10:47 +00:00
argc = ast_app_separate_args ( tmpptr , ',' , argv , sizeof ( argv ) / sizeof ( argv [ 0 ]));
2007-04-18 07:57:18 +00:00
if ( argc == 2 ) {
2007-07-16 14:39:29 +00:00
if ( ast_app_parse_options ( minivm_app_options , & flags , opts , argv [ 1 ]))
2007-04-18 07:57:18 +00:00
return - 1 ;
ast_copy_flags ( & leave_options , & flags , OPT_SILENT | OPT_BUSY_GREETING | OPT_UNAVAIL_GREETING );
}
ast_copy_string ( tmp , argv [ 0 ], sizeof ( tmp ));
username = tmp ;
domain = strchr ( tmp , '@' );
if ( domain ) {
* domain = '\0' ;
domain ++ ;
}
if ( ast_strlen_zero ( domain ) || ast_strlen_zero ( username )) {
ast_log ( LOG_ERROR , "Need username@domain as argument. Sorry. Argument: %s \n " , argv [ 0 ]);
return - 1 ;
}
2007-06-14 19:39:12 +00:00
ast_debug ( 1 , "-_-_- Trying to find configuration for user %s in domain %s \n " , username , domain );
2007-04-18 07:57:18 +00:00
if ( ! ( vmu = find_account ( domain , username , TRUE ))) {
ast_log ( LOG_ERROR , "Could not allocate memory. \n " );
return - 1 ;
}
/* Answer channel if it's not already answered */
if ( chan -> _state != AST_STATE_UP )
ast_answer ( chan );
/* Setup pre-file if appropriate */
if ( strcmp ( vmu -> domain , "localhost" ))
snprintf ( ext_context , sizeof ( ext_context ), "%s@%s" , username , vmu -> domain );
else
ast_copy_string ( ext_context , vmu -> domain , sizeof ( ext_context ));
if ( ast_test_flag ( & leave_options , OPT_BUSY_GREETING )) {
res = check_dirpath ( dest , sizeof ( dest ), vmu -> domain , username , "busy" );
if ( res )
snprintf ( prefile , sizeof ( prefile ), "%s%s/%s/busy" , MVM_SPOOL_DIR , vmu -> domain , username );
} else if ( ast_test_flag ( & leave_options , OPT_UNAVAIL_GREETING )) {
res = check_dirpath ( dest , sizeof ( dest ), vmu -> domain , username , "unavail" );
if ( res )
snprintf ( prefile , sizeof ( prefile ), "%s%s/%s/unavail" , MVM_SPOOL_DIR , vmu -> domain , username );
}
/* Check for temporary greeting - it overrides busy and unavail */
snprintf ( tempfile , sizeof ( tempfile ), "%s%s/%s/temp" , MVM_SPOOL_DIR , vmu -> domain , username );
if ( ! ( res = check_dirpath ( dest , sizeof ( dest ), vmu -> domain , username , "temp" ))) {
2007-06-14 19:39:12 +00:00
ast_debug ( 2 , "Temporary message directory does not exist, using default (%s) \n " , tempfile );
2007-04-18 07:57:18 +00:00
ast_copy_string ( prefile , tempfile , sizeof ( prefile ));
}
2007-06-14 19:39:12 +00:00
ast_debug ( 2 , "-_-_- Preparing to play message ... \n " );
2007-04-18 07:57:18 +00:00
/* Check current or macro-calling context for special extensions */
if ( ast_test_flag ( vmu , MVM_OPERATOR )) {
if ( ! ast_strlen_zero ( vmu -> exit )) {
if ( ast_exists_extension ( chan , vmu -> exit , "o" , 1 , chan -> cid . cid_num )) {
strncat ( ecodes , "0" , sizeof ( ecodes ) - strlen ( ecodes ) - 1 );
ouseexten = 1 ;
}
} else if ( ast_exists_extension ( chan , chan -> context , "o" , 1 , chan -> cid . cid_num )) {
strncat ( ecodes , "0" , sizeof ( ecodes ) - strlen ( ecodes ) - 1 );
ouseexten = 1 ;
}
else if ( ! ast_strlen_zero ( chan -> macrocontext ) && ast_exists_extension ( chan , chan -> macrocontext , "o" , 1 , chan -> cid . cid_num )) {
strncat ( ecodes , "0" , sizeof ( ecodes ) - strlen ( ecodes ) - 1 );
ousemacro = 1 ;
}
}
if ( ! ast_strlen_zero ( vmu -> exit )) {
if ( ast_exists_extension ( chan , vmu -> exit , "a" , 1 , chan -> cid . cid_num ))
strncat ( ecodes , "*" , sizeof ( ecodes ) - strlen ( ecodes ) - 1 );
} else if ( ast_exists_extension ( chan , chan -> context , "a" , 1 , chan -> cid . cid_num ))
strncat ( ecodes , "*" , sizeof ( ecodes ) - strlen ( ecodes ) - 1 );
else if ( ! ast_strlen_zero ( chan -> macrocontext ) && ast_exists_extension ( chan , chan -> macrocontext , "a" , 1 , chan -> cid . cid_num )) {
strncat ( ecodes , "*" , sizeof ( ecodes ) - strlen ( ecodes ) - 1 );
ausemacro = 1 ;
}
res = 0 ; /* Reset */
/* Play the beginning intro if desired */
if ( ! ast_strlen_zero ( prefile )) {
if ( ast_streamfile ( chan , prefile , chan -> language ) > - 1 )
res = ast_waitstream ( chan , ecodes );
} else {
2007-06-14 19:39:12 +00:00
ast_debug ( 2 , "%s doesn't exist, doing what we can \n " , prefile );
2007-04-18 07:57:18 +00:00
res = invent_message ( chan , vmu -> domain , username , ast_test_flag ( & leave_options , OPT_BUSY_GREETING ), ecodes );
}
if ( res < 0 ) {
2007-06-14 19:39:12 +00:00
ast_debug ( 2 , "Hang up during prefile playback \n " );
2007-04-18 07:57:18 +00:00
pbx_builtin_setvar_helper ( chan , "MINIVM_GREET_STATUS" , "FAILED" );
if ( ast_test_flag ( vmu , MVM_ALLOCED ))
free_user ( vmu );
return - 1 ;
}
if ( res == '#' ) {
/* On a '#' we skip the instructions */
ast_set_flag ( & leave_options , OPT_SILENT );
res = 0 ;
}
if ( ! res && ! ast_test_flag ( & leave_options , OPT_SILENT )) {
res = ast_streamfile ( chan , SOUND_INTRO , chan -> language );
if ( ! res )
res = ast_waitstream ( chan , ecodes );
if ( res == '#' ) {
ast_set_flag ( & leave_options , OPT_SILENT );
res = 0 ;
}
}
if ( res > 0 )
ast_stopstream ( chan );
/* Check for a '*' here in case the caller wants to escape from voicemail to something
other than the operator -- an automated attendant or mailbox login for example */
if ( res == '*' ) {
chan -> exten [ 0 ] = 'a' ;
chan -> exten [ 1 ] = '\0' ;
if ( ! ast_strlen_zero ( vmu -> exit )) {
ast_copy_string ( chan -> context , vmu -> exit , sizeof ( chan -> context ));
} else if ( ausemacro && ! ast_strlen_zero ( chan -> macrocontext )) {
ast_copy_string ( chan -> context , chan -> macrocontext , sizeof ( chan -> context ));
}
chan -> priority = 0 ;
pbx_builtin_setvar_helper ( chan , "MINIVM_GREET_STATUS" , "USEREXIT" );
res = 0 ;
} else if ( res == '0' ) { /* Check for a '0' here */
if ( ouseexten || ousemacro ) {
chan -> exten [ 0 ] = 'o' ;
chan -> exten [ 1 ] = '\0' ;
if ( ! ast_strlen_zero ( vmu -> exit )) {
ast_copy_string ( chan -> context , vmu -> exit , sizeof ( chan -> context ));
} else if ( ousemacro && ! ast_strlen_zero ( chan -> macrocontext )) {
ast_copy_string ( chan -> context , chan -> macrocontext , sizeof ( chan -> context ));
}
ast_play_and_wait ( chan , "transfer" );
chan -> priority = 0 ;
pbx_builtin_setvar_helper ( chan , "MINIVM_GREET_STATUS" , "USEREXIT" );
}
res = 0 ;
} else if ( res < 0 ) {
pbx_builtin_setvar_helper ( chan , "MINIVM_GREET_STATUS" , "FAILED" );
res = - 1 ;
} else
pbx_builtin_setvar_helper ( chan , "MINIVM_GREET_STATUS" , "SUCCESS" );
if ( ast_test_flag ( vmu , MVM_ALLOCED ))
free_user ( vmu );
/* Ok, we're ready to rock and roll. Return to dialplan */
return res ;
}
/*! \brief Dialplan application to delete voicemail */
static int minivm_delete_exec ( struct ast_channel * chan , void * data )
{
int res = 0 ;
char filename [ BUFSIZ ];
2007-07-16 14:39:29 +00:00
2007-04-18 07:57:18 +00:00
if ( ! ast_strlen_zero ( data ))
ast_copy_string ( filename , ( char * ) data , sizeof ( filename ));
else
ast_copy_string ( filename , pbx_builtin_getvar_helper ( chan , "MVM_FILENAME" ), sizeof ( filename ));
if ( ast_strlen_zero ( filename )) {
ast_log ( LOG_ERROR , "No filename given in application arguments or channel variable MVM_FILENAME \n " );
return res ;
}
/* Go ahead and delete audio files from system, they're not needed any more */
/* We should look for both audio and text files here */
if ( ast_fileexists ( filename , NULL , NULL ) > 0 ) {
res = vm_delete ( filename );
if ( res ) {
2007-06-14 19:39:12 +00:00
ast_debug ( 2 , "-_-_- Can't delete file: %s \n " , filename );
2007-04-18 07:57:18 +00:00
pbx_builtin_setvar_helper ( chan , "MINIVM_DELETE_STATUS" , "FAILED" );
} else {
2007-06-14 19:39:12 +00:00
ast_debug ( 2 , "-_-_- Deleted voicemail file :: %s \n " , filename );
2007-04-18 07:57:18 +00:00
pbx_builtin_setvar_helper ( chan , "MINIVM_DELETE_STATUS" , "SUCCESS" );
}
} else {
2007-06-14 19:39:12 +00:00
ast_debug ( 2 , "-_-_- Filename does not exist: %s \n " , filename );
2007-04-18 07:57:18 +00:00
pbx_builtin_setvar_helper ( chan , "MINIVM_DELETE_STATUS" , "FAILED" );
}
return res ;
}
/*! \brief Record specific messages for voicemail account */
static int minivm_accmess_exec ( struct ast_channel * chan , void * data )
{
int argc = 0 ;
char * argv [ 2 ];
int res = 0 ;
char filename [ PATH_MAX ];
char tmp [ PATH_MAX ];
char * domain ;
2007-07-17 12:46:25 +00:00
char * tmpptr = NULL ;
2007-04-18 07:57:18 +00:00
struct minivm_account * vmu ;
char * username = argv [ 0 ];
struct ast_flags flags = { 0 };
char * opts [ OPT_ARG_ARRAY_SIZE ];
int error = FALSE ;
char * message = NULL ;
char * prompt = NULL ;
int duration ;
int cmd ;
if ( ast_strlen_zero ( data )) {
ast_log ( LOG_ERROR , "MinivmAccmess needs at least two arguments: account and option \n " );
error = TRUE ;
} else
tmpptr = ast_strdupa (( char * ) data );
if ( ! error ) {
if ( ! tmpptr ) {
ast_log ( LOG_ERROR , "Out of memory \n " );
error = TRUE ;
} else
2007-07-31 01:10:47 +00:00
argc = ast_app_separate_args ( tmpptr , ',' , argv , sizeof ( argv ) / sizeof ( argv [ 0 ]));
2007-04-18 07:57:18 +00:00
}
if ( argc <= 1 ) {
ast_log ( LOG_ERROR , "MinivmAccmess needs at least two arguments: account and option \n " );
error = TRUE ;
}
if ( ! error && strlen ( argv [ 1 ]) > 1 ) {
ast_log ( LOG_ERROR , "MinivmAccmess can only handle one option at a time. Bad option string: %s \n " , argv [ 1 ]);
error = TRUE ;
}
if ( ! error && ast_app_parse_options ( minivm_accmess_options , & flags , opts , argv [ 1 ])) {
ast_log ( LOG_ERROR , "Can't parse option %s \n " , argv [ 1 ]);
error = TRUE ;
}
2007-07-16 14:39:29 +00:00
if ( error )
2007-04-18 07:57:18 +00:00
return - 1 ;
ast_copy_string ( tmp , argv [ 0 ], sizeof ( tmp ));
username = tmp ;
domain = strchr ( tmp , '@' );
if ( domain ) {
* domain = '\0' ;
domain ++ ;
}
if ( ast_strlen_zero ( domain ) || ast_strlen_zero ( username )) {
ast_log ( LOG_ERROR , "Need username@domain as argument. Sorry. Argument 0 %s \n " , argv [ 0 ]);
return - 1 ;
}
if ( ! ( vmu = find_account ( domain , username , TRUE ))) {
/* We could not find user, let's exit */
ast_log ( LOG_WARNING , "Could not allocate temporary memory for '%s@%s' \n " , username , domain );
pbx_builtin_setvar_helper ( chan , "MINIVM_NOTIFY_STATUS" , "FAILED" );
return - 1 ;
}
/* Answer channel if it's not already answered */
if ( chan -> _state != AST_STATE_UP )
ast_answer ( chan );
/* Here's where the action is */
if ( ast_test_flag ( & flags , OPT_BUSY_GREETING )) {
message = "busy" ;
prompt = "vm-rec-busy" ;
} else if ( ast_test_flag ( & flags , OPT_UNAVAIL_GREETING )) {
message = "unavailable" ;
prompt = "vm-rec-unavail" ;
} else if ( ast_test_flag ( & flags , OPT_TEMP_GREETING )) {
message = "temp" ;
prompt = "vm-temp-greeting" ;
} else if ( ast_test_flag ( & flags , OPT_NAME_GREETING )) {
message = "greet" ;
prompt = "vm-rec-name" ;
}
snprintf ( filename , sizeof ( filename ), "%s%s/%s/%s" , MVM_SPOOL_DIR , vmu -> domain , vmu -> username , message );
/* Maybe we should check the result of play_record_review ? */
cmd = play_record_review ( chan , prompt , filename , global_maxgreet , default_vmformat , 0 , vmu , & duration , NULL , FALSE );
2007-06-14 19:39:12 +00:00
ast_debug ( 1 , "Recorded new %s message in %s (duration %d) \n " , message , filename , duration );
2007-04-18 07:57:18 +00:00
if ( ast_test_flag ( vmu , MVM_ALLOCED ))
free_user ( vmu );
/* Ok, we're ready to rock and roll. Return to dialplan */
return res ;
}
/*! \brief Append new mailbox to mailbox list from configuration file */
static int create_vmaccount ( char * name , struct ast_variable * var , int realtime )
{
struct minivm_account * vmu ;
char * domain ;
char * username ;
char accbuf [ BUFSIZ ];
2007-06-14 19:39:12 +00:00
ast_debug ( 3 , "Creating %s account for [%s] \n " , realtime ? "realtime" : "static" , name );
2007-04-18 07:57:18 +00:00
ast_copy_string ( accbuf , name , sizeof ( accbuf ));
username = accbuf ;
domain = strchr ( accbuf , '@' );
if ( domain ) {
* domain = '\0' ;
domain ++ ;
}
if ( ast_strlen_zero ( domain )) {
ast_log ( LOG_ERROR , "No domain given for mini-voicemail account %s. Not configured. \n " , name );
return 0 ;
}
2007-06-14 19:39:12 +00:00
ast_debug ( 3 , "Creating static account for user %s domain %s \n " , username , domain );
2007-04-18 07:57:18 +00:00
/* Allocate user account */
2007-06-06 21:20:11 +00:00
vmu = ast_calloc ( 1 , sizeof ( * vmu ));
2007-04-18 07:57:18 +00:00
if ( ! vmu )
return 0 ;
ast_copy_string ( vmu -> domain , domain , sizeof ( vmu -> domain ));
ast_copy_string ( vmu -> username , username , sizeof ( vmu -> username ));
populate_defaults ( vmu );
2007-06-14 19:39:12 +00:00
ast_debug ( 3 , "...Configuring account %s \n " , name );
2007-04-18 07:57:18 +00:00
while ( var ) {
2007-06-14 19:39:12 +00:00
ast_debug ( 3 , "---- Configuring %s = \" %s \" for account %s \n " , var -> name , var -> value , name );
2007-04-18 07:57:18 +00:00
if ( ! strcasecmp ( var -> name , "serveremail" )) {
ast_copy_string ( vmu -> serveremail , var -> value , sizeof ( vmu -> serveremail ));
} else if ( ! strcasecmp ( var -> name , "email" )) {
ast_copy_string ( vmu -> email , var -> value , sizeof ( vmu -> email ));
} else if ( ! strcasecmp ( var -> name , "accountcode" )) {
ast_copy_string ( vmu -> accountcode , var -> value , sizeof ( vmu -> accountcode ));
} else if ( ! strcasecmp ( var -> name , "pincode" )) {
ast_copy_string ( vmu -> pincode , var -> value , sizeof ( vmu -> pincode ));
} else if ( ! strcasecmp ( var -> name , "domain" )) {
ast_copy_string ( vmu -> domain , var -> value , sizeof ( vmu -> domain ));
} else if ( ! strcasecmp ( var -> name , "language" )) {
ast_copy_string ( vmu -> language , var -> value , sizeof ( vmu -> language ));
} else if ( ! strcasecmp ( var -> name , "timezone" )) {
ast_copy_string ( vmu -> zonetag , var -> value , sizeof ( vmu -> zonetag ));
} else if ( ! strcasecmp ( var -> name , "externnotify" )) {
ast_copy_string ( vmu -> externnotify , var -> value , sizeof ( vmu -> externnotify ));
} else if ( ! strcasecmp ( var -> name , "etemplate" )) {
ast_copy_string ( vmu -> etemplate , var -> value , sizeof ( vmu -> etemplate ));
} else if ( ! strcasecmp ( var -> name , "ptemplate" )) {
ast_copy_string ( vmu -> ptemplate , var -> value , sizeof ( vmu -> ptemplate ));
} else if ( ! strcasecmp ( var -> name , "fullname" )) {
ast_copy_string ( vmu -> fullname , var -> value , sizeof ( vmu -> fullname ));
} else if ( ! strcasecmp ( var -> name , "setvar" )) {
char * varval ;
char * varname = ast_strdupa ( var -> value );
struct ast_variable * tmpvar ;
if ( varname && ( varval = strchr ( varname , '=' ))) {
* varval = '\0' ;
varval ++ ;
2007-08-29 20:55:40 +00:00
if (( tmpvar = ast_variable_new ( varname , varval , "" ))) {
2007-04-18 07:57:18 +00:00
tmpvar -> next = vmu -> chanvars ;
vmu -> chanvars = tmpvar ;
}
}
} else if ( ! strcasecmp ( var -> name , "pager" )) {
ast_copy_string ( vmu -> pager , var -> value , sizeof ( vmu -> pager ));
} else if ( ! strcasecmp ( var -> name , "volgain" )) {
sscanf ( var -> value , "%lf" , & vmu -> volgain );
} else {
ast_log ( LOG_ERROR , "Unknown configuration option for minivm account %s : %s \n " , name , var -> name );
}
var = var -> next ;
}
2007-06-14 19:39:12 +00:00
ast_debug ( 3 , "...Linking account %s \n " , name );
2007-04-18 07:57:18 +00:00
AST_LIST_LOCK ( & minivm_accounts );
AST_LIST_INSERT_TAIL ( & minivm_accounts , vmu , list );
AST_LIST_UNLOCK ( & minivm_accounts );
global_stats . voicemailaccounts ++ ;
2007-06-14 19:39:12 +00:00
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)" : "" );
2007-04-18 07:57:18 +00:00
return 0 ;
}
/*! \brief Free Mini Voicemail timezone */
static void free_zone ( struct minivm_zone * z )
{
2007-06-06 21:20:11 +00:00
ast_free ( z );
2007-04-18 07:57:18 +00:00
}
/*! \brief Clear list of timezones */
static void timezone_destroy_list ( void )
{
struct minivm_zone * this ;
2007-05-10 15:19:57 +00:00
2007-04-18 07:57:18 +00:00
AST_LIST_LOCK ( & minivm_zones );
while (( this = AST_LIST_REMOVE_HEAD ( & minivm_zones , list )))
2007-05-31 17:56:27 +00:00
free_zone ( this );
2007-04-18 07:57:18 +00:00
AST_LIST_UNLOCK ( & minivm_zones );
}
/*! \brief Add time zone to memory list */
static int timezone_add ( char * zonename , char * config )
{
struct minivm_zone * newzone ;
char * msg_format , * timezone ;
2007-06-06 21:20:11 +00:00
newzone = ast_calloc ( 1 , sizeof ( * newzone ));
2007-04-18 07:57:18 +00:00
if ( newzone == NULL )
return 0 ;
msg_format = ast_strdupa ( config );
if ( msg_format == NULL ) {
ast_log ( LOG_WARNING , "Out of memory. \n " );
2007-06-06 21:20:11 +00:00
ast_free ( newzone );
2007-04-18 07:57:18 +00:00
return 0 ;
}
timezone = strsep ( & msg_format , "|" );
if ( ! msg_format ) {
ast_log ( LOG_WARNING , "Invalid timezone definition : %s \n " , zonename );
2007-06-06 21:20:11 +00:00
ast_free ( newzone );
2007-04-18 07:57:18 +00:00
return 0 ;
}
ast_copy_string ( newzone -> name , zonename , sizeof ( newzone -> name ));
ast_copy_string ( newzone -> timezone , timezone , sizeof ( newzone -> timezone ));
ast_copy_string ( newzone -> msg_format , msg_format , sizeof ( newzone -> msg_format ));
AST_LIST_LOCK ( & minivm_zones );
AST_LIST_INSERT_TAIL ( & minivm_zones , newzone , list );
AST_LIST_UNLOCK ( & minivm_zones );
global_stats . timezones ++ ;
return 0 ;
}
/*! \brief Read message template from file */
static char * message_template_parse_filebody ( char * filename ) {
char buf [ BUFSIZ * 6 ];
char readbuf [ BUFSIZ ];
char filenamebuf [ BUFSIZ ];
char * writepos ;
char * messagebody ;
FILE * fi ;
int lines = 0 ;
if ( ast_strlen_zero ( filename ))
return NULL ;
if ( * filename == '/' )
ast_copy_string ( filenamebuf , filename , sizeof ( filenamebuf ));
else
snprintf ( filenamebuf , sizeof ( filenamebuf ), "%s/%s" , ast_config_AST_CONFIG_DIR , filename );
if ( ! ( fi = fopen ( filenamebuf , "r" ))) {
ast_log ( LOG_ERROR , "Can't read message template from file: %s \n " , filenamebuf );
return NULL ;
}
writepos = buf ;
while ( fgets ( readbuf , sizeof ( readbuf ), fi )) {
lines ++ ;
if ( writepos != buf ) {
* writepos = '\n' ; /* Replace EOL with new line */
writepos ++ ;
}
ast_copy_string ( writepos , readbuf , sizeof ( buf ) - ( writepos - buf ));
writepos += strlen ( readbuf ) - 1 ;
}
fclose ( fi );
messagebody = ast_calloc ( 1 , strlen ( buf + 1 ));
ast_copy_string ( messagebody , buf , strlen ( buf ) + 1 );
2007-06-14 19:39:12 +00:00
ast_debug ( 4 , "---> Size of allocation %d \n " , ( int ) strlen ( buf + 1 ) );
ast_debug ( 4 , "---> Done reading message template : \n %s \n ---- END message template--- \n " , messagebody );
2007-04-18 07:57:18 +00:00
return messagebody ;
}
/*! \brief Parse emailbody template from configuration file */
static char * message_template_parse_emailbody ( const char * configuration )
{
char * tmpread , * tmpwrite ;
2007-06-14 23:01:01 +00:00
char * emailbody = ast_strdup ( configuration );
2007-04-18 07:57:18 +00:00
/* substitute strings \t and \n into the apropriate characters */
tmpread = tmpwrite = emailbody ;
while (( tmpwrite = strchr ( tmpread , '\\' ))) {
int len = strlen ( " \n " );
switch ( tmpwrite [ 1 ]) {
case 'n' :
2007-08-09 20:07:59 +00:00
memmove ( tmpwrite + len , tmpwrite + 2 , strlen ( tmpwrite + 2 ) + 1 );
2007-04-18 07:57:18 +00:00
strncpy ( tmpwrite , " \n " , len );
break ;
case 't' :
2007-08-09 20:07:59 +00:00
memmove ( tmpwrite + len , tmpwrite + 2 , strlen ( tmpwrite + 2 ) + 1 );
2007-04-18 07:57:18 +00:00
strncpy ( tmpwrite , " \t " , len );
break ;
default :
ast_log ( LOG_NOTICE , "Substitution routine does not support this character: %c \n " , tmpwrite [ 1 ]);
}
tmpread = tmpwrite + len ;
}
return emailbody ;
}
/*! \brief Apply general configuration options */
static int apply_general_options ( struct ast_variable * var )
{
int error = 0 ;
while ( var ) {
/* Mail command */
if ( ! strcmp ( var -> name , "mailcmd" )) {
ast_copy_string ( global_mailcmd , var -> value , sizeof ( global_mailcmd )); /* User setting */
} else if ( ! strcmp ( var -> name , "maxgreet" )) {
global_maxgreet = atoi ( var -> value );
} else if ( ! strcmp ( var -> name , "maxsilence" )) {
global_maxsilence = atoi ( var -> value );
if ( global_maxsilence > 0 )
global_maxsilence *= 1000 ;
} else if ( ! strcmp ( var -> name , "logfile" )) {
if ( ! ast_strlen_zero ( var -> value ) ) {
if ( * ( var -> value ) == '/' )
ast_copy_string ( global_logfile , var -> value , sizeof ( global_logfile ));
else
snprintf ( global_logfile , sizeof ( global_logfile ), "%s/%s" , ast_config_AST_LOG_DIR , var -> value );
}
} else if ( ! strcmp ( var -> name , "externnotify" )) {
/* External voicemail notify application */
ast_copy_string ( global_externnotify , var -> value , sizeof ( global_externnotify ));
} else if ( ! strcmp ( var -> name , "silencetreshold" )) {
/* Silence treshold */
global_silencethreshold = atoi ( var -> value );
} else if ( ! strcmp ( var -> name , "maxmessage" )) {
int x ;
if ( sscanf ( var -> value , "%d" , & x ) == 1 ) {
global_vmmaxmessage = x ;
} else {
error ++ ;
ast_log ( LOG_WARNING , "Invalid max message time length \n " );
}
} else if ( ! strcmp ( var -> name , "minmessage" )) {
int x ;
if ( sscanf ( var -> value , "%d" , & x ) == 1 ) {
global_vmminmessage = x ;
if ( global_maxsilence <= global_vmminmessage )
ast_log ( LOG_WARNING , "maxsilence should be less than minmessage or you may get empty messages \n " );
} else {
error ++ ;
ast_log ( LOG_WARNING , "Invalid min message time length \n " );
}
} else if ( ! strcmp ( var -> name , "format" )) {
ast_copy_string ( default_vmformat , var -> value , sizeof ( default_vmformat ));
} else if ( ! strcmp ( var -> name , "review" )) {
ast_set2_flag (( & globalflags ), ast_true ( var -> value ), MVM_REVIEW );
} else if ( ! strcmp ( var -> name , "operator" )) {
ast_set2_flag (( & globalflags ), ast_true ( var -> value ), MVM_OPERATOR );
}
var = var -> next ;
}
return error ;
}
/*! \brief Load minivoicemail configuration */
2007-08-16 21:09:46 +00:00
static int load_config ( int reload )
2007-04-18 07:57:18 +00:00
{
struct ast_config * cfg ;
struct ast_variable * var ;
char * cat ;
const char * chanvar ;
int error = 0 ;
2007-06-28 18:59:19 +00:00
struct minivm_template * template ;
2007-08-16 21:09:46 +00:00
struct ast_flags config_flags = { reload ? CONFIG_FLAG_FILEUNCHANGED : 0 };
cfg = ast_config_load ( VOICEMAIL_CONFIG , config_flags );
if ( cfg == CONFIG_STATUS_FILEUNCHANGED )
return 0 ;
2007-04-18 07:57:18 +00:00
ast_mutex_lock ( & minivmlock );
2007-05-31 17:56:27 +00:00
/* Destroy lists to reconfigure */
message_destroy_list (); /* Destroy list of voicemail message templates */
timezone_destroy_list (); /* Destroy list of timezones */
vmaccounts_destroy_list (); /* Destroy list of voicemail accounts */
2007-06-14 19:39:12 +00:00
ast_debug ( 2 , "Destroyed memory objects... \n " );
2007-04-18 07:57:18 +00:00
/* First, set some default settings */
global_externnotify [ 0 ] = '\0' ;
global_logfile [ 0 ] = '\0' ;
global_silencethreshold = 256 ;
global_vmmaxmessage = 2000 ;
global_maxgreet = 2000 ;
global_vmminmessage = 0 ;
strcpy ( global_mailcmd , SENDMAIL );
global_maxsilence = 0 ;
global_saydurationminfo = 2 ;
ast_copy_string ( default_vmformat , "wav" , sizeof ( default_vmformat ));
ast_set2_flag (( & globalflags ), FALSE , MVM_REVIEW );
ast_set2_flag (( & globalflags ), FALSE , MVM_OPERATOR );
strcpy ( global_charset , "ISO-8859-1" );
/* Reset statistics */
2007-07-18 19:47:20 +00:00
memset ( & global_stats , 0 , sizeof ( global_stats ));
global_stats . reset = ast_tvnow ();
2007-04-18 07:57:18 +00:00
/* Make sure we could load configuration file */
if ( ! cfg ) {
ast_log ( LOG_WARNING , "Failed to load configuration file. Module activated with default settings. \n " );
2007-05-26 19:35:33 +00:00
ast_mutex_unlock ( & minivmlock );
2007-04-18 07:57:18 +00:00
return 0 ;
}
2007-06-14 19:39:12 +00:00
ast_debug ( 2 , "-_-_- Loaded configuration file, now parsing \n " );
2007-04-18 07:57:18 +00:00
/* General settings */
cat = ast_category_browse ( cfg , NULL );
while ( cat ) {
2007-06-14 19:39:12 +00:00
ast_debug ( 3 , "-_-_- Found configuration section [%s] \n " , cat );
2007-04-18 07:57:18 +00:00
if ( ! strcasecmp ( cat , "general" )) {
/* Nothing right now */
error += apply_general_options ( ast_variable_browse ( cfg , cat ));
} else if ( ! strncasecmp ( cat , "template-" , 9 )) {
/* Template */
char * name = cat + 9 ;
/* Now build and link template to list */
error += message_template_build ( name , ast_variable_browse ( cfg , cat ));
} else {
var = ast_variable_browse ( cfg , cat );
if ( ! strcasecmp ( cat , "zonemessages" )) {
/* Timezones in this context */
while ( var ) {
timezone_add ( var -> name , var -> value );
var = var -> next ;
}
} else {
/* Create mailbox from this */
error += create_vmaccount ( cat , var , FALSE );
}
}
/* Find next section in configuration file */
cat = ast_category_browse ( cfg , cat );
}
/* Configure the default email template */
message_template_build ( "email-default" , NULL );
template = message_template_find ( "email-default" );
/* Load date format config for voicemail mail */
if (( chanvar = ast_variable_retrieve ( cfg , "general" , "emaildateformat" )))
ast_copy_string ( template -> dateformat , chanvar , sizeof ( template -> dateformat ));
if (( chanvar = ast_variable_retrieve ( cfg , "general" , "emailfromstring" )))
ast_copy_string ( template -> fromaddress , chanvar , sizeof ( template -> fromaddress ));
if (( chanvar = ast_variable_retrieve ( cfg , "general" , "emailaaddress" )))
ast_copy_string ( template -> serveremail , chanvar , sizeof ( template -> serveremail ));
if (( chanvar = ast_variable_retrieve ( cfg , "general" , "emailcharset" )))
ast_copy_string ( template -> charset , chanvar , sizeof ( template -> charset ));
if (( chanvar = ast_variable_retrieve ( cfg , "general" , "emailsubject" )))
ast_copy_string ( template -> subject , chanvar , sizeof ( template -> subject ));
if (( chanvar = ast_variable_retrieve ( cfg , "general" , "emailbody" )))
template -> body = message_template_parse_emailbody ( chanvar );
template -> attachment = TRUE ;
message_template_build ( "pager-default" , NULL );
template = message_template_find ( "pager-default" );
if (( chanvar = ast_variable_retrieve ( cfg , "general" , "pagerfromstring" )))
ast_copy_string ( template -> fromaddress , chanvar , sizeof ( template -> fromaddress ));
if (( chanvar = ast_variable_retrieve ( cfg , "general" , "pageraddress" )))
ast_copy_string ( template -> serveremail , chanvar , sizeof ( template -> serveremail ));
if (( chanvar = ast_variable_retrieve ( cfg , "general" , "pagercharset" )))
ast_copy_string ( template -> charset , chanvar , sizeof ( template -> charset ));
if (( chanvar = ast_variable_retrieve ( cfg , "general" , "pagersubject" )))
ast_copy_string ( template -> subject , chanvar , sizeof ( template -> subject ));
if (( chanvar = ast_variable_retrieve ( cfg , "general" , "pagerbody" )))
template -> body = message_template_parse_emailbody ( chanvar );
template -> attachment = FALSE ;
if ( error )
ast_log ( LOG_ERROR , "--- A total of %d errors found in mini-voicemail configuration \n " , error );
ast_mutex_unlock ( & minivmlock );
ast_config_destroy ( cfg );
/* Close log file if it's open and disabled */
if ( minivmlogfile )
fclose ( minivmlogfile );
/* Open log file if it's enabled */
if ( ! ast_strlen_zero ( global_logfile )) {
minivmlogfile = fopen ( global_logfile , "a" );
if ( ! minivmlogfile )
ast_log ( LOG_ERROR , "Failed to open minivm log file %s : %s \n " , global_logfile , strerror ( errno ));
2007-06-14 19:39:12 +00:00
if ( minivmlogfile )
ast_debug ( 3 , "-_-_- Opened log file %s \n " , global_logfile );
2007-04-18 07:57:18 +00:00
}
return 0 ;
}
/*! \brief CLI routine for listing templates */
2007-09-20 23:14:30 +00:00
static char * handle_minivm_list_templates ( struct ast_cli_entry * e , int cmd , struct ast_cli_args * a )
2007-04-18 07:57:18 +00:00
{
struct minivm_template * this ;
char * output_format = "%-15s %-10s %-10s %-15.15s %-50s \n " ;
int count = 0 ;
2007-09-20 23:14:30 +00:00
switch ( cmd ) {
case CLI_INIT :
e -> command = "minivm list templates" ;
e -> usage =
"Usage: minivm list templates \n "
" Lists message templates for e-mail, paging and IM \n " ;
return NULL ;
case CLI_GENERATE :
return NULL ;
}
if ( a -> argc > 3 )
return CLI_SHOWUSAGE ;
2007-04-18 07:57:18 +00:00
AST_LIST_LOCK ( & message_templates );
if ( AST_LIST_EMPTY ( & message_templates )) {
2007-09-20 23:14:30 +00:00
ast_cli ( a -> fd , "There are no message templates defined \n " );
2007-04-18 07:57:18 +00:00
AST_LIST_UNLOCK ( & message_templates );
2007-09-20 23:14:30 +00:00
return CLI_FAILURE ;
2007-04-18 07:57:18 +00:00
}
2007-09-20 23:14:30 +00:00
ast_cli ( a -> fd , output_format , "Template name" , "Charset" , "Locale" , "Attach media" , "Subject" );
ast_cli ( a -> fd , output_format , "-------------" , "-------" , "------" , "------------" , "-------" );
2007-04-18 07:57:18 +00:00
AST_LIST_TRAVERSE ( & message_templates , this , list ) {
2007-09-20 23:14:30 +00:00
ast_cli ( a -> fd , output_format , this -> name ,
2007-04-18 07:57:18 +00:00
this -> charset ? this -> charset : "-" ,
this -> locale ? this -> locale : "-" ,
this -> attachment ? "Yes" : "No" ,
this -> subject ? this -> subject : "-" );
count ++ ;
}
AST_LIST_UNLOCK ( & message_templates );
2007-09-20 23:14:30 +00:00
ast_cli ( a -> fd , " \n * Total: %d minivoicemail message templates \n " , count );
return CLI_SUCCESS ;
}
static char * complete_minivm_show_users ( const char * line , const char * word , int pos , int state )
{
int which = 0 ;
int wordlen ;
struct minivm_account * vmu ;
const char * domain = "" ;
/* 0 - voicemail; 1 - list; 2 - accounts; 3 - for; 4 - <domain> */
if ( pos > 4 )
return NULL ;
if ( pos == 3 )
return ( state == 0 ) ? ast_strdup ( "for" ) : NULL ;
wordlen = strlen ( word );
AST_LIST_TRAVERSE ( & minivm_accounts , vmu , list ) {
if ( ! strncasecmp ( word , vmu -> domain , wordlen )) {
if ( domain && strcmp ( domain , vmu -> domain ) && ++ which > state )
return ast_strdup ( vmu -> domain );
/* ignore repeated domains ? */
domain = vmu -> domain ;
}
}
return NULL ;
2007-04-18 07:57:18 +00:00
}
/*! \brief CLI command to list voicemail accounts */
2007-09-20 23:14:30 +00:00
static char * handle_minivm_show_users ( struct ast_cli_entry * e , int cmd , struct ast_cli_args * a )
2007-04-18 07:57:18 +00:00
{
struct minivm_account * vmu ;
char * output_format = "%-23s %-15s %-15s %-10s %-10s %-50s \n " ;
int count = 0 ;
2007-09-20 23:14:30 +00:00
switch ( cmd ) {
case CLI_INIT :
e -> command = "minivm list accounts" ;
e -> usage =
"Usage: minivm list accounts \n "
" Lists all mailboxes currently set up \n " ;
return NULL ;
case CLI_GENERATE :
return complete_minivm_show_users ( a -> line , a -> word , a -> pos , a -> n );
}
if (( a -> argc < 3 ) || ( a -> argc > 5 ) || ( a -> argc == 4 ))
return CLI_SHOWUSAGE ;
if (( a -> argc == 5 ) && strcmp ( a -> argv [ 3 ], "for" ))
return CLI_SHOWUSAGE ;
2007-04-18 07:57:18 +00:00
AST_LIST_LOCK ( & minivm_accounts );
if ( AST_LIST_EMPTY ( & minivm_accounts )) {
2007-09-20 23:14:30 +00:00
ast_cli ( a -> fd , "There are no voicemail users currently defined \n " );
2007-04-18 07:57:18 +00:00
AST_LIST_UNLOCK ( & minivm_accounts );
2007-09-20 23:14:30 +00:00
return CLI_FAILURE ;
2007-04-18 07:57:18 +00:00
}
2007-09-20 23:14:30 +00:00
ast_cli ( a -> fd , output_format , "User" , "E-Template" , "P-template" , "Zone" , "Format" , "Full name" );
ast_cli ( a -> fd , output_format , "----" , "----------" , "----------" , "----" , "------" , "---------" );
2007-04-18 07:57:18 +00:00
AST_LIST_TRAVERSE ( & minivm_accounts , vmu , list ) {
char tmp [ 256 ] = "" ;
2007-09-20 23:14:30 +00:00
if (( a -> argc == 3 ) || (( a -> argc == 5 ) && ! strcmp ( a -> argv [ 4 ], vmu -> domain ))) {
2007-04-18 07:57:18 +00:00
count ++ ;
snprintf ( tmp , sizeof ( tmp ), "%s@%s" , vmu -> username , vmu -> domain );
2007-09-20 23:14:30 +00:00
ast_cli ( a -> fd , output_format , tmp , vmu -> etemplate ? vmu -> etemplate : "-" ,
2007-04-18 07:57:18 +00:00
vmu -> ptemplate ? vmu -> ptemplate : "-" ,
vmu -> zonetag ? vmu -> zonetag : "-" ,
vmu -> attachfmt ? vmu -> attachfmt : "-" ,
vmu -> fullname );
}
}
AST_LIST_UNLOCK ( & minivm_accounts );
2007-09-20 23:14:30 +00:00
ast_cli ( a -> fd , " \n * Total: %d minivoicemail accounts \n " , count );
return CLI_SUCCESS ;
2007-04-18 07:57:18 +00:00
}
/*! \brief Show a list of voicemail zones in the CLI */
2007-09-20 23:14:30 +00:00
static char * handle_minivm_show_zones ( struct ast_cli_entry * e , int cmd , struct ast_cli_args * a )
2007-04-18 07:57:18 +00:00
{
struct minivm_zone * zone ;
char * output_format = "%-15s %-20s %-45s \n " ;
2007-09-20 23:14:30 +00:00
char * res = CLI_SUCCESS ;
switch ( cmd ) {
case CLI_INIT :
e -> command = "minivm list zones" ;
e -> usage =
"Usage: minivm list zones \n "
" Lists zone message formats \n " ;
return NULL ;
case CLI_GENERATE :
return NULL ;
}
2007-04-18 07:57:18 +00:00
2007-09-20 23:14:30 +00:00
if ( a -> argc != 3 )
return CLI_SHOWUSAGE ;
2007-04-18 07:57:18 +00:00
AST_LIST_LOCK ( & minivm_zones );
if ( ! AST_LIST_EMPTY ( & minivm_zones )) {
2007-09-20 23:14:30 +00:00
ast_cli ( a -> fd , output_format , "Zone" , "Timezone" , "Message Format" );
ast_cli ( a -> fd , output_format , "----" , "--------" , "--------------" );
2007-04-18 07:57:18 +00:00
AST_LIST_TRAVERSE ( & minivm_zones , zone , list ) {
2007-09-20 23:14:30 +00:00
ast_cli ( a -> fd , output_format , zone -> name , zone -> timezone , zone -> msg_format );
2007-04-18 07:57:18 +00:00
}
} else {
2007-09-20 23:14:30 +00:00
ast_cli ( a -> fd , "There are no voicemail zones currently defined \n " );
res = CLI_FAILURE ;
2007-04-18 07:57:18 +00:00
}
AST_LIST_UNLOCK ( & minivm_zones );
return res ;
}
2007-09-20 23:14:30 +00:00
/*! \brief CLI Show settings */
static char * handle_minivm_show_settings ( struct ast_cli_entry * e , int cmd , struct ast_cli_args * a )
2007-04-18 07:57:18 +00:00
{
2007-09-20 23:14:30 +00:00
switch ( cmd ) {
case CLI_INIT :
e -> command = "minivm show settings" ;
e -> usage =
"Usage: minivm show settings \n "
" Display Mini-Voicemail general settings \n " ;
return NULL ;
case CLI_GENERATE :
2007-04-18 07:57:18 +00:00
return NULL ;
}
2007-09-20 23:14:30 +00:00
ast_cli ( a -> fd , "* Mini-Voicemail general settings \n " );
ast_cli ( a -> fd , " ------------------------------- \n " );
ast_cli ( a -> fd , " \n " );
ast_cli ( a -> fd , " Mail command (shell): %s \n " , global_mailcmd );
ast_cli ( a -> fd , " Max silence: %d \n " , global_maxsilence );
ast_cli ( a -> fd , " Silence treshold: %d \n " , global_silencethreshold );
ast_cli ( a -> fd , " Max message length (secs): %d \n " , global_vmmaxmessage );
ast_cli ( a -> fd , " Min message length (secs): %d \n " , global_vmminmessage );
ast_cli ( a -> fd , " Default format: %s \n " , default_vmformat );
ast_cli ( a -> fd , " Extern notify (shell): %s \n " , global_externnotify );
ast_cli ( a -> fd , " Logfile: %s \n " , global_logfile [ 0 ] ? global_logfile : "<disabled>" );
ast_cli ( a -> fd , " Operator exit: %s \n " , ast_test_flag ( & globalflags , MVM_OPERATOR ) ? "Yes" : "No" );
ast_cli ( a -> fd , " Message review: %s \n " , ast_test_flag ( & globalflags , MVM_REVIEW ) ? "Yes" : "No" );
ast_cli ( a -> fd , " \n " );
return CLI_SUCCESS ;
2007-04-18 07:57:18 +00:00
}
/*! \brief Show stats */
2007-09-20 23:14:30 +00:00
static char * handle_minivm_show_stats ( struct ast_cli_entry * e , int cmd , struct ast_cli_args * a )
2007-04-18 07:57:18 +00:00
{
2007-07-18 19:47:20 +00:00
struct ast_tm time ;
2007-04-18 07:57:18 +00:00
char buf [ BUFSIZ ];
2007-09-20 23:14:30 +00:00
switch ( cmd ) {
case CLI_INIT :
e -> command = "minivm show stats" ;
e -> usage =
"Usage: minivm show stats \n "
" Display Mini-Voicemail counters \n " ;
return NULL ;
case CLI_GENERATE :
return NULL ;
}
ast_cli ( a -> fd , "* Mini-Voicemail statistics \n " );
ast_cli ( a -> fd , " ------------------------- \n " );
ast_cli ( a -> fd , " \n " );
ast_cli ( a -> fd , " Voicemail accounts: %5d \n " , global_stats . voicemailaccounts );
ast_cli ( a -> fd , " Templates: %5d \n " , global_stats . templates );
ast_cli ( a -> fd , " Timezones: %5d \n " , global_stats . timezones );
2007-04-18 07:57:18 +00:00
if ( global_stats . receivedmessages == 0 ) {
2007-09-20 23:14:30 +00:00
ast_cli ( a -> fd , " Received messages since last reset: <none> \n " );
2007-04-18 07:57:18 +00:00
} else {
2007-09-20 23:14:30 +00:00
ast_cli ( a -> fd , " Received messages since last reset: %d \n " , global_stats . receivedmessages );
2007-04-18 07:57:18 +00:00
ast_localtime ( & global_stats . lastreceived , & time , NULL );
2007-07-18 19:47:20 +00:00
ast_strftime ( buf , sizeof ( buf ), "%a %b %e %r %Z %Y" , & time );
2007-09-20 23:14:30 +00:00
ast_cli ( a -> fd , " Last received voicemail: %s \n " , buf );
2007-04-18 07:57:18 +00:00
}
ast_localtime ( & global_stats . reset , & time , NULL );
2007-07-18 19:47:20 +00:00
ast_strftime ( buf , sizeof ( buf ), "%a %b %e %r %Z %Y" , & time );
2007-09-20 23:14:30 +00:00
ast_cli ( a -> fd , " Last reset: %s \n " , buf );
2007-04-18 07:57:18 +00:00
2007-09-20 23:14:30 +00:00
ast_cli ( a -> fd , " \n " );
return CLI_SUCCESS ;
2007-04-18 07:57:18 +00:00
}
/*! \brief ${MINIVMACCOUNT()} Dialplan function - reads account data */
static int minivm_account_func_read ( struct ast_channel * chan , const char * cmd , char * data , char * buf , size_t len )
{
struct minivm_account * vmu ;
char * username , * domain , * colname ;
if ( ! ( username = ast_strdupa ( data ))) {
ast_log ( LOG_ERROR , "Memory Error! \n " );
return - 1 ;
}
if (( colname = strchr ( username , ':' ))) {
* colname = '\0' ;
colname ++ ;
} else {
colname = "path" ;
}
if (( domain = strchr ( username , '@' ))) {
* domain = '\0' ;
domain ++ ;
}
if ( ast_strlen_zero ( username ) || ast_strlen_zero ( domain )) {
ast_log ( LOG_ERROR , "This function needs a username and a domain: username@domain \n " );
return 0 ;
}
if ( ! ( vmu = find_account ( domain , username , TRUE )))
return 0 ;
if ( ! strcasecmp ( colname , "hasaccount" )) {
ast_copy_string ( buf , ( ast_test_flag ( vmu , MVM_ALLOCED ) ? "0" : "1" ), len );
} else if ( ! strcasecmp ( colname , "fullname" )) {
ast_copy_string ( buf , vmu -> fullname , len );
} else if ( ! strcasecmp ( colname , "email" )) {
if ( ! ast_strlen_zero ( vmu -> email ))
ast_copy_string ( buf , vmu -> email , len );
else
snprintf ( buf , len , "%s@%s" , vmu -> username , vmu -> domain );
} else if ( ! strcasecmp ( colname , "pager" )) {
ast_copy_string ( buf , vmu -> pager , len );
} else if ( ! strcasecmp ( colname , "etemplate" )) {
if ( ! ast_strlen_zero ( vmu -> etemplate ))
ast_copy_string ( buf , vmu -> etemplate , len );
else
ast_copy_string ( buf , "email-default" , len );
} else if ( ! strcasecmp ( colname , "language" )) {
ast_copy_string ( buf , vmu -> language , len );
} else if ( ! strcasecmp ( colname , "timezone" )) {
ast_copy_string ( buf , vmu -> zonetag , len );
} else if ( ! strcasecmp ( colname , "ptemplate" )) {
if ( ! ast_strlen_zero ( vmu -> ptemplate ))
ast_copy_string ( buf , vmu -> ptemplate , len );
else
ast_copy_string ( buf , "email-default" , len );
} else if ( ! strcasecmp ( colname , "accountcode" )) {
ast_copy_string ( buf , vmu -> accountcode , len );
} else if ( ! strcasecmp ( colname , "pincode" )) {
ast_copy_string ( buf , vmu -> pincode , len );
} else if ( ! strcasecmp ( colname , "path" )) {
check_dirpath ( buf , len , vmu -> domain , vmu -> username , NULL );
} else { /* Look in channel variables */
struct ast_variable * var ;
int found = 0 ;
for ( var = vmu -> chanvars ; var ; var = var -> next )
if ( ! strcmp ( var -> name , colname )) {
ast_copy_string ( buf , var -> value , len );
found = 1 ;
break ;
}
}
if ( ast_test_flag ( vmu , MVM_ALLOCED ))
free_user ( vmu );
return 0 ;
}
/*! \brief lock directory
only return failure if ast_lock_path returns 'timeout',
not if the path does not exist or any other reason
*/
static int vm_lock_path ( const char * path )
{
switch ( ast_lock_path ( path )) {
case AST_LOCK_TIMEOUT :
return - 1 ;
default :
return 0 ;
}
}
/*! \brief Access counter file, lock directory, read and possibly write it again changed
\param directory Directory to crate file in
2007-05-31 10:26:55 +00:00
\param countername filename
2007-04-18 07:57:18 +00:00
\param value If set to zero, we only read the variable
\param operand 0 to read, 1 to set new value, 2 to change
\return -1 on error, otherwise counter value
*/
static int access_counter_file ( char * directory , char * countername , int value , int operand )
{
char filename [ BUFSIZ ];
char readbuf [ BUFSIZ ];
FILE * counterfile ;
int old = 0 , counter = 0 ;
/* Lock directory */
if ( vm_lock_path ( directory )) {
return - 1 ; /* Could not lock directory */
}
snprintf ( filename , sizeof ( filename ), "%s/%s.counter" , directory , countername );
if ( operand != 1 ) {
counterfile = fopen ( filename , "r" );
if ( counterfile ) {
if ( fgets ( readbuf , sizeof ( readbuf ), counterfile )) {
2007-06-14 19:39:12 +00:00
ast_debug ( 3 , "Read this string from counter file: %s \n " , readbuf );
2007-04-18 07:57:18 +00:00
old = counter = atoi ( readbuf );
}
fclose ( counterfile );
}
}
switch ( operand ) {
case 0 : /* Read only */
ast_unlock_path ( directory );
2007-06-14 19:39:12 +00:00
ast_debug ( 2 , "MINIVM Counter %s/%s: Value %d \n " , directory , countername , counter );
2007-04-18 07:57:18 +00:00
return counter ;
break ;
case 1 : /* Set new value */
counter = value ;
break ;
case 2 : /* Change value */
counter += value ;
if ( counter < 0 ) /* Don't allow counters to fall below zero */
counter = 0 ;
break ;
}
/* Now, write the new value to the file */
counterfile = fopen ( filename , "w" );
if ( ! counterfile ) {
ast_log ( LOG_ERROR , "Could not open counter file for writing : %s - %s \n " , filename , strerror ( errno ));
ast_unlock_path ( directory );
return - 1 ; /* Could not open file for writing */
}
fprintf ( counterfile , "%d \n\n " , counter );
fclose ( counterfile );
ast_unlock_path ( directory );
2007-06-14 19:39:12 +00:00
ast_debug ( 2 , "MINIVM Counter %s/%s: Old value %d New value %d \n " , directory , countername , old , counter );
2007-04-18 07:57:18 +00:00
return counter ;
}
/*! \brief ${MINIVMCOUNTER()} Dialplan function - read counters */
static int minivm_counter_func_read ( struct ast_channel * chan , const char * cmd , char * data , char * buf , size_t len )
{
char * username , * domain , * countername ;
struct minivm_account * vmu = NULL ;
char userpath [ BUFSIZ ];
int res ;
* buf = '\0' ;
if ( ! ( username = ast_strdupa ( data ))) { /* Copy indata to local buffer */
ast_log ( LOG_WARNING , "Memory error! \n " );
return - 1 ;
}
if (( countername = strchr ( username , ':' ))) {
* countername = '\0' ;
countername ++ ;
}
if (( domain = strchr ( username , '@' ))) {
* domain = '\0' ;
domain ++ ;
}
/* If we have neither username nor domain now, let's give up */
if ( ast_strlen_zero ( username ) && ast_strlen_zero ( domain )) {
ast_log ( LOG_ERROR , "No account given \n " );
return - 1 ;
}
if ( ast_strlen_zero ( countername )) {
ast_log ( LOG_ERROR , "This function needs two arguments: Account:countername \n " );
return - 1 ;
}
/* We only have a domain, no username */
if ( ! ast_strlen_zero ( username ) && ast_strlen_zero ( domain )) {
domain = username ;
username = NULL ;
}
/* If we can't find account or if the account is temporary, return. */
if ( ! ast_strlen_zero ( username ) && ! ( vmu = find_account ( domain , username , FALSE ))) {
ast_log ( LOG_ERROR , "Minivm account does not exist: %s@%s \n " , username , domain );
return 0 ;
}
create_dirpath ( userpath , sizeof ( userpath ), domain , username , NULL );
/* We have the path, now read the counter file */
res = access_counter_file ( userpath , countername , 0 , 0 );
if ( res >= 0 )
snprintf ( buf , len , "%d" , res );
return 0 ;
}
/*! \brief ${MINIVMCOUNTER()} Dialplan function - changes counter data */
static int minivm_counter_func_write ( struct ast_channel * chan , const char * cmd , char * data , const char * value )
{
char * username , * domain , * countername , * operand ;
char userpath [ BUFSIZ ];
struct minivm_account * vmu ;
int change = 0 ;
int operation = 0 ;
if ( ! value )
return - 1 ;
change = atoi ( value );
if ( ! ( username = ast_strdupa ( data ))) { /* Copy indata to local buffer */
ast_log ( LOG_WARNING , "Memory error! \n " );
return - 1 ;
}
if (( countername = strchr ( username , ':' ))) {
* countername = '\0' ;
countername ++ ;
}
if (( operand = strchr ( countername , ':' ))) {
* operand = '\0' ;
operand ++ ;
}
if (( domain = strchr ( username , '@' ))) {
* domain = '\0' ;
domain ++ ;
}
/* If we have neither username nor domain now, let's give up */
if ( ast_strlen_zero ( username ) && ast_strlen_zero ( domain )) {
ast_log ( LOG_ERROR , "No account given \n " );
return - 1 ;
}
/* We only have a domain, no username */
if ( ! ast_strlen_zero ( username ) && ast_strlen_zero ( domain )) {
domain = username ;
username = NULL ;
}
if ( ast_strlen_zero ( operand ) || ast_strlen_zero ( countername )) {
ast_log ( LOG_ERROR , "Writing to this function requires three arguments: Account:countername:operand \n " );
return - 1 ;
}
/* If we can't find account or if the account is temporary, return. */
if ( ! ast_strlen_zero ( username ) && ! ( vmu = find_account ( domain , username , FALSE ))) {
ast_log ( LOG_ERROR , "Minivm account does not exist: %s@%s \n " , username , domain );
return 0 ;
}
create_dirpath ( userpath , sizeof ( userpath ), domain , username , NULL );
/* Now, find out our operator */
if ( * operand == 'i' ) /* Increment */
operation = 2 ;
else if ( * operand == 'd' ) {
change = change * - 1 ;
operation = 2 ;
} else if ( * operand == 's' )
operation = 1 ;
else {
ast_log ( LOG_ERROR , "Unknown operator: %s \n " , operand );
return - 1 ;
}
/* We have the path, now read the counter file */
access_counter_file ( userpath , countername , change , operation );
return 0 ;
}
/*! \brief CLI commands for Mini-voicemail */
static struct ast_cli_entry cli_minivm [] = {
2007-10-19 18:29:40 +00:00
AST_CLI ( handle_minivm_show_users , "List defined mini-voicemail boxes" ),
AST_CLI ( handle_minivm_show_zones , "List zone message formats" ),
AST_CLI ( handle_minivm_list_templates , "List message templates" ),
AST_CLI ( handle_minivm_reload , "Reload Mini-voicemail configuration" ),
AST_CLI ( handle_minivm_show_stats , "Show some mini-voicemail statistics" ),
AST_CLI ( handle_minivm_show_settings , "Show mini-voicemail general settings" ),
2007-04-18 07:57:18 +00:00
};
static struct ast_custom_function minivm_counter_function = {
. name = "MINIVMCOUNTER" ,
. synopsis = "Reads or sets counters for MiniVoicemail message" ,
. syntax = "MINIVMCOUNTER(<account>:name[:operand])" ,
. read = minivm_counter_func_read ,
. write = minivm_counter_func_write ,
. desc = "Valid operands for changing the value of a counter when assigning a value are: \n "
"- i Increment by value \n "
"- d Decrement by value \n "
"- s Set to value \n "
" \n The counters never goes below zero. \n "
"- The name of the counter is a string, up to 10 characters \n "
"- If account is given and it exists, the counter is specific for the account \n "
"- If account is a domain and the domain directory exists, counters are specific for a domain \n "
"The operation is atomic and the counter is locked while changing the value \n "
" \n The counters are stored as text files in the minivm account directories. It might be better to use \n "
"realtime functions if you are using a database to operate your Asterisk \n " ,
};
static struct ast_custom_function minivm_account_function = {
. name = "MINIVMACCOUNT" ,
. synopsis = "Gets MiniVoicemail account information" ,
. syntax = "MINIVMACCOUNT(<account>:item)" ,
. read = minivm_account_func_read ,
. desc = "Valid items are: \n "
"- path Path to account mailbox (if account exists, otherwise temporary mailbox) \n "
"- hasaccount 1 if static Minivm account exists, 0 otherwise \n "
"- fullname Full name of account owner \n "
"- email Email address used for account \n "
"- etemplate E-mail template for account (default template if none is configured) \n "
"- ptemplate Pager template for account (default template if none is configured) \n "
"- accountcode Account code for voicemail account \n "
"- pincode Pin code for voicemail account \n "
"- timezone Time zone for voicemail account \n "
"- language Language for voicemail account \n "
"- <channel variable name> Channel variable value (set in configuration for account) \n "
" \n " ,
};
/*! \brief Load mini voicemail module */
static int load_module ( void )
{
int res ;
res = ast_register_application ( app_minivm_record , minivm_record_exec , synopsis_minivm_record , descrip_minivm_record );
res = ast_register_application ( app_minivm_greet , minivm_greet_exec , synopsis_minivm_greet , descrip_minivm_greet );
res = ast_register_application ( app_minivm_notify , minivm_notify_exec , synopsis_minivm_notify , descrip_minivm_notify );
res = ast_register_application ( app_minivm_delete , minivm_delete_exec , synopsis_minivm_delete , descrip_minivm_delete );
res = ast_register_application ( app_minivm_accmess , minivm_accmess_exec , synopsis_minivm_accmess , descrip_minivm_accmess );
ast_custom_function_register ( & minivm_account_function );
ast_custom_function_register ( & minivm_counter_function );
if ( res )
return ( res );
2007-08-16 21:09:46 +00:00
if (( res = load_config ( 0 )))
2007-04-18 07:57:18 +00:00
return ( res );
ast_cli_register_multiple ( cli_minivm , sizeof ( cli_minivm ) / sizeof ( cli_minivm [ 0 ]));
/* compute the location of the voicemail spool directory */
snprintf ( MVM_SPOOL_DIR , sizeof ( MVM_SPOOL_DIR ), "%s/voicemail/" , ast_config_AST_SPOOL_DIR );
return res ;
}
/*! \brief Reload mini voicemail module */
static int reload ( void )
{
2007-08-16 21:09:46 +00:00
return ( load_config ( 1 ));
2007-04-18 07:57:18 +00:00
}
/*! \brief Reload cofiguration */
2007-09-20 23:14:30 +00:00
static char * handle_minivm_reload ( struct ast_cli_entry * e , int cmd , struct ast_cli_args * a )
2007-04-18 07:57:18 +00:00
{
2007-09-20 23:14:30 +00:00
switch ( cmd ) {
case CLI_INIT :
e -> command = "minivm reload" ;
e -> usage =
"Usage: minivm reload \n "
" Reload mini-voicemail configuration and reset statistics \n " ;
return NULL ;
case CLI_GENERATE :
return NULL ;
}
2007-04-18 07:57:18 +00:00
reload ();
2007-09-20 23:14:30 +00:00
ast_cli ( a -> fd , " \n -- Mini voicemail re-configured \n " );
return CLI_SUCCESS ;
2007-04-18 07:57:18 +00:00
}
/*! \brief Unload mini voicemail module */
static int unload_module ( void )
{
int res ;
res = ast_unregister_application ( app_minivm_record );
res |= ast_unregister_application ( app_minivm_greet );
res |= ast_unregister_application ( app_minivm_notify );
res |= ast_unregister_application ( app_minivm_delete );
res |= ast_unregister_application ( app_minivm_accmess );
ast_cli_unregister_multiple ( cli_minivm , sizeof ( cli_minivm ) / sizeof ( cli_minivm [ 0 ]));
ast_custom_function_unregister ( & minivm_account_function );
ast_custom_function_unregister ( & minivm_counter_function );
message_destroy_list (); /* Destroy list of voicemail message templates */
timezone_destroy_list (); /* Destroy list of timezones */
vmaccounts_destroy_list (); /* Destroy list of voicemail accounts */
return res ;
}
AST_MODULE_INFO ( ASTERISK_GPL_KEY , AST_MODFLAG_DEFAULT , "Mini VoiceMail (A minimal Voicemail e-mail System)" ,
. load = load_module ,
. unload = unload_module ,
. reload = reload ,
);