[svn-commits] oej: trunk r61671 - in /trunk: apps/ configs/

svn-commits at lists.digium.com svn-commits at lists.digium.com
Wed Apr 18 00:57:19 MST 2007


Author: oej
Date: Wed Apr 18 02:57:18 2007
New Revision: 61671

URL: http://svn.digium.com/view/asterisk?view=rev&rev=61671
Log:
Mini-voicemail - an embryo for a new voicemail system based on building
blocks instead of one large monolithic app. Supports multiple templates
and is designed mostly for voicemail delivery over e-mail.

There's a todo with a list of ideas in the source code if you want
to contribute. Feedback is appreciated!

Added:
    trunk/apps/app_minivm.c   (with props)
    trunk/configs/extensions_minivm.conf.sample   (with props)
    trunk/configs/minivm.conf.sample   (with props)

Added: trunk/apps/app_minivm.c
URL: http://svn.digium.com/view/asterisk/trunk/apps/app_minivm.c?view=auto&rev=61671
==============================================================================
--- trunk/apps/app_minivm.c (added)
+++ trunk/apps/app_minivm.c Wed Apr 18 02:57:18 2007
@@ -1,0 +1,3229 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 1999 - 2005, Digium, Inc.
+ * and Edvina AB, Sollentuna, Sweden
+ *
+ * Mark Spencer <markster at digium.com> (Comedian Mail)
+ * and Olle E. Johansson, Edvina.net <oej at 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 at 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 <stdlib.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 at 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"
+	"\nNote: 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"
+	"\nThe 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 at 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 at 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 at 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"
+	"\nThe 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 */
+	unsigned int flags;		/*!< MVM_ flags */	
+	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 {
+	unsigned int flags;
+	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 */
+
+	time_t reset;			/*!< Time for last reset */
+	int receivedmessages;		/*!< Number of received messages since reset */
+	time_t lastreceived;		/*!< Time for last voicemail sent */
+};
+
+/*! \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);
+static int handle_minivm_reload(int fd, int argc, char *argv[]);
+
+/*! \brief Create message template */
+static struct minivm_template *message_template_create(const char *name)
+{
+	struct minivm_template *template;
+
+	template = ast_calloc(1, sizeof(struct minivm_template));
+	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)
+		free(template->body);
+
+	free (template);
+}
+
+/*! \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) {
+		if (option_debug > 2)
+			ast_log(LOG_DEBUG, "-_-_- Configuring template option %s = \"%s\" for template %s\n", var->name, var->value, name);
+		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) 
+				free(template->body);
+			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) 
+				free(template->body);
+			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];
+			ogroup[1]= dtable[((igroup[0]&3)<<4)|(igroup[1]>>4)];
+			ogroup[2]= dtable[((igroup[1]&0xF)<<2)|(igroup[2]>>6)];
+			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)
+{
+	struct tm tm;
+	time_t t;
+	t = time(0);
+	localtime_r(&t,&tm);
+	return strftime(s, len, "%a %b %e %r %Z %Y", &tm);
+}
+
+
+/*! \brief Free user structure - if it's allocated */
+static void free_user(struct minivm_account *vmu)
+{
+	if (vmu->chanvars)
+		ast_variables_destroy(vmu->chanvars);
+	free(vmu);
+}
+
+
+
+/*! \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;
+
+	new = calloc(1, sizeof(struct minivm_account));
+	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))) 
+		free(this);
+	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;
+	}
+	if (option_debug > 2)
+		ast_log(LOG_DEBUG, "-_-_-_- Looking for voicemail user %s in domain %s\n", username, domain);
+
+	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) {
+		if (option_debug > 2)
+			ast_log(LOG_DEBUG, "-_-_- Found account for %s@%s\n", username, domain);
+		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));
+			if (option_debug)
+				ast_log(LOG_DEBUG, "--- Created temporary account\n");
+		}
+
+	}
+	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) {
+		free(retval);
+		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];
+	time_t now;
+	struct tm tm;
+	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;	
+	}
+
+	if (option_debug > 2)
+		ast_log(LOG_DEBUG, "-_-_- Sending mail to %s@%s - Using template %s\n", vmu->username, vmu->domain, template->name);
+
+	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;
+
+		snprintf(newtmp, sizeof(newtmp), "/tmp/XXXXXX");
+		if (option_debug > 2)
+			ast_log(LOG_DEBUG, "newtmp: %s\n", newtmp);
+		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;
+		if (option_debug > 2)
+			ast_log	(LOG_DEBUG, "-- VOLGAIN: Stored at: %s.%s - Level: %.4f - Mailbox: %s\n", filename, format, vmu->volgain, vmu->username);
+	} else {
+		finalfilename = ast_strdupa(filename);
+	}
+
+	/* Create file name */
+	snprintf(fname, sizeof(fname), "%s.%s", finalfilename, format);
+
+	if (option_debug && template->attachment)
+		ast_log(LOG_DEBUG, "-- Attaching file '%s', format '%s', uservm is '%d'\n", finalfilename, format, attach_user_voicemail);
+
+	/* 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;
+		}
+		if (option_debug)
+			ast_log(LOG_DEBUG, "-_-_- Opening temp file for e-mail: %s\n", tmp);
+	}
+	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);
+	}
+
+	time(&now);
+	ast_localtime(&now, &tm, the_zone ? the_zone->timezone : NULL);
+	strftime(date, sizeof(date), "%a, %d %b %Y %H:%M:%S %z", &tm);
+
+	/* Start printing the email to the temporary file */
+	fprintf(p, "Date: %s\n", date);
+
+	/* Set date format for voicemail mail */
+	strftime(date, sizeof(date), template->dateformat, &tm);
+
+
+	/* 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 {
+		if (option_debug > 3)
+			ast_log(LOG_DEBUG, "-_-_- Fromaddress template: %s\n", fromaddress);
+		/* Allocate a buffer big enough for variable substitution */
+		int vmlen = strlen(fromaddress) * 3 + 200;
+
+		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;	
+		}
+	} 
+	if (option_debug > 3)
+		ast_log(LOG_DEBUG, "-_-_- Fromstring now: %s\n", ast_strlen_zero(passdata) ? "-default-" : passdata);
+
+	fprintf(p, "Message-ID: <Asterisk-%d-%s-%d-%s>\n", (unsigned int)rand(), vmu->username, getpid(), who);
+	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;	
+		}
+		if (option_debug > 3)
+			ast_log(LOG_DEBUG, "-_-_- Subject now: %s\n", passdata);
+	} else  {
+		fprintf(p, "Subject: New message in mailbox %s@%s\n", vmu->username, vmu->domain);
+		ast_log(LOG_DEBUG, "-_-_- Using default subject for this email \n");
+	}
+
+
+	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. */
+	snprintf(bound, sizeof(bound), "voicemail_%s%d%d", vmu->username, getpid(), (unsigned int)rand());
+
+	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\nContent-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);
+			if (option_debug > 2)
+				ast_log(LOG_DEBUG, "Message now: %s\n-----\n", passdata);
+			fprintf(p, "%s\n", passdata);
+		} else
+			ast_log(LOG_WARNING, "Cannot allocate workspace for variable substitution\n");
+	} else {
+		fprintf(p, "Dear %s:\n\n\tJust 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);
+		if (option_debug > 2)
+			ast_log(LOG_DEBUG, "Using default message body (no template)\n-----\n");
+	}
+	/* Eww. We want formats to tell us their own MIME type */
+	if (template->attachment) {
+		if (option_debug > 2)
+			ast_log(LOG_DEBUG, "-_-_- Attaching file to message: %s\n", fname);
+		char *ctype = "audio/x-";
+		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);

[... 2529 lines stripped ...]


More information about the svn-commits mailing list