2 * Asterisk -- An open source telephony toolkit.
4 * Copyright (C) 1999 - 2005, Digium, Inc.
5 * and Edvina AB, Sollentuna, Sweden
7 * Mark Spencer <markster@digium.com> (Comedian Mail)
8 * and Olle E. Johansson, Edvina.net <oej@edvina.net> (Mini-Voicemail changes)
10 * See http://www.asterisk.org for more information about
11 * the Asterisk project. Please do not directly contact
12 * any of the maintainers of this project for assistance;
13 * the project provides a web site, mailing lists and IRC
14 * channels for your use.
16 * This program is free software, distributed under the terms of
17 * the GNU General Public License Version 2. See the LICENSE file
18 * at the top of the source tree.
23 * \brief MiniVoiceMail - A Minimal Voicemail System for Asterisk
25 * A voicemail system in small building blocks, working together
26 * based on the Comedian Mail voicemail system (app_voicemail.c).
29 * \arg \ref Config_minivm
30 * \arg \ref Config_minivm_examples
31 * \arg \ref App_minivm
33 * \ingroup applications
35 * \page App_minivm Asterisk Mini-voicemail - A minimal voicemail system
37 * This is a minimal voicemail system, building blocks for something
38 * else. It is built for multi-language systems.
39 * The current version is focused on accounts where voicemail is
40 * forwarded to users in e-mail. It's work in progress, with loosed ends hanging
41 * around from the old voicemail system and it's configuration.
43 * Hopefully, we can expand this to be a full replacement of voicemail() and voicemailmain()
46 * Dialplan applications
47 * - minivmRecord - record voicemail and send as e-mail ( \ref minivm_record_exec() )
48 * - minivmGreet - Play user's greeting or default greeting ( \ref minivm_greet_exec() )
49 * - minivmNotify - Notify user of message ( \ref minivm_notify_exec() )
50 * - minivmDelete - Delete voicemail message ( \ref minivm_delete_exec() )
51 * - minivmAccMess - Record personal messages (busy | unavailable | temporary)
54 * - MINIVMACCOUNT() - A dialplan function
55 * - MINIVMCOUNTER() - Manage voicemail-related counters for accounts or domains
58 * - minivm list accounts
60 * - minivm list templates
62 * - minivm show settings
65 * - General configuration in minivm.conf
66 * - Users in realtime or configuration file
67 * - Or configured on the command line with just the e-mail address
69 * Voicemail accounts are identified by userid and domain
71 * Language codes are like setlocale - langcode_countrycode
72 * \note Don't use language codes like the rest of Asterisk, two letter countrycode. Use
73 * language_country like setlocale().
76 * - Swedish, Sweden sv_se
77 * - Swedish, Finland sv_fi
78 * - English, USA en_us
82 * \arg \ref Config_minivm
83 * \arg \ref Config_minivm_examples
84 * \arg \ref Minivm_directories
85 * \arg \ref app_minivm.c
86 * \arg Comedian mail: app_voicemail.c
87 * \arg \ref descrip_minivm_accmess
88 * \arg \ref descrip_minivm_greet
89 * \arg \ref descrip_minivm_record
90 * \arg \ref descrip_minivm_delete
91 * \arg \ref descrip_minivm_notify
93 * \arg \ref App_minivm_todo
95 /*! \page Minivm_directories Asterisk Mini-Voicemail Directory structure
97 * The directory structure for storing voicemail
98 * - AST_SPOOL_DIR - usually /var/spool/asterisk (configurable in asterisk.conf)
99 * - MVM_SPOOL_DIR - should be configurable, usually AST_SPOOL_DIR/voicemail
100 * - Domain MVM_SPOOL_DIR/domain
101 * - Username MVM_SPOOL_DIR/domain/username
102 * - /greet : Recording of account owner's name
103 * - /busy : Busy message
104 * - /unavailable : Unavailable message
105 * - /temp : Temporary message
107 * For account anita@localdomain.xx the account directory would as a default be
108 * \b /var/spool/asterisk/voicemail/localdomain.xx/anita
110 * To avoid transcoding, these sound files should be converted into several formats
111 * They are recorded in the format closest to the incoming streams
114 * Back: \ref App_minivm
117 /*! \page Config_minivm_examples Example dialplan for Mini-Voicemail
118 * \section Example dialplan scripts for Mini-Voicemail
119 * \verbinclude extensions_minivm.conf.sample
121 * Back: \ref App_minivm
124 /*! \page App_minivm_todo Asterisk Mini-Voicemail - todo
125 * - configure accounts from AMI?
126 * - test, test, test, test
127 * - fix "vm-theextensionis.gsm" voiceprompt from Allison in various formats
128 * "The extension you are calling"
129 * - For trunk, consider using channel storage for information passing between small applications
130 * - Set default directory for voicemail
131 * - New app for creating directory for account if it does not exist
132 * - Re-insert code for IMAP storage at some point
133 * - Jabber integration for notifications
134 * - Figure out how to handle video in voicemail
135 * - Integration with the HTTP server
136 * - New app for moving messages between mailboxes, and optionally mark it as "new"
138 * For Asterisk 1.4/trunk
139 * - Use string fields for minivm_account
141 * Back: \ref App_minivm
145 <support_level>extended</support_level>
148 #include "asterisk.h"
150 ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
153 #include <sys/time.h>
154 #include <sys/stat.h>
155 #include <sys/mman.h>
161 #include "asterisk/paths.h" /* use various paths */
162 #include "asterisk/lock.h"
163 #include "asterisk/file.h"
164 #include "asterisk/channel.h"
165 #include "asterisk/pbx.h"
166 #include "asterisk/config.h"
167 #include "asterisk/say.h"
168 #include "asterisk/module.h"
169 #include "asterisk/app.h"
170 #include "asterisk/manager.h"
171 #include "asterisk/dsp.h"
172 #include "asterisk/localtime.h"
173 #include "asterisk/cli.h"
174 #include "asterisk/utils.h"
175 #include "asterisk/linkedlists.h"
176 #include "asterisk/callerid.h"
177 #include "asterisk/event.h"
180 <application name="MinivmRecord" language="en_US">
182 Receive Mini-Voicemail and forward via e-mail.
185 <parameter name="mailbox" required="true" argsep="@">
186 <argument name="username" required="true">
187 <para>Voicemail username</para>
189 <argument name="domain" required="true">
190 <para>Voicemail domain</para>
193 <parameter name="options" required="false">
196 <para>Jump to the <literal>o</literal> extension in the current dialplan context.</para>
199 <para>Jump to the <literal>a</literal> extension in the current dialplan context.</para>
202 <argument name="gain">
203 <para>Amount of gain to use</para>
205 <para>Use the specified amount of gain when recording the voicemail message.
206 The units are whole-number decibels (dB).</para>
212 <para>This application is part of the Mini-Voicemail system, configured in <filename>minivm.conf</filename></para>
213 <para>MiniVM records audio file in configured format and forwards message to e-mail and pager.</para>
214 <para>If there's no user account for that address, a temporary account will be used with default options.</para>
215 <para>The recorded file name and path will be stored in <variable>MVM_FILENAME</variable> and the duration
216 of the message will be stored in <variable>MVM_DURATION</variable></para>
217 <note><para>If the caller hangs up after the recording, the only way to send the message and clean up is to
218 execute in the <literal>h</literal> extension. The application will exit if any of the following DTMF digits
219 are received and the requested extension exist in the current context.</para></note>
221 <variable name="MVM_RECORD_STATUS">
222 <para>This is the status of the record operation</para>
223 <value name="SUCCESS" />
224 <value name="USEREXIT" />
225 <value name="FAILED" />
230 <application name="MinivmGreet" language="en_US">
232 Play Mini-Voicemail prompts.
235 <parameter name="mailbox" required="true" argsep="@">
236 <argument name="username" required="true">
237 <para>Voicemail username</para>
239 <argument name="domain" required="true">
240 <para>Voicemail domain</para>
243 <parameter name="options" required="false">
246 <para>Play the <literal>busy</literal> greeting to the calling party.</para>
249 <para>Skip the playback of instructions for leaving a message to the calling party.</para>
252 <para>Play the <literal>unavailable</literal> greeting.</para>
258 <para>This application is part of the Mini-Voicemail system, configured in minivm.conf.</para>
259 <para>MinivmGreet() plays default prompts or user specific prompts for an account.</para>
260 <para>Busy and unavailable messages can be choosen, but will be overridden if a temporary
261 message exists for the account.</para>
263 <variable name="MVM_GREET_STATUS">
264 <para>This is the status of the greeting playback.</para>
265 <value name="SUCCESS" />
266 <value name="USEREXIT" />
267 <value name="FAILED" />
272 <application name="MinivmNotify" language="en_US">
274 Notify voicemail owner about new messages.
277 <parameter name="mailbox" required="true" argsep="@">
278 <argument name="username" required="true">
279 <para>Voicemail username</para>
281 <argument name="domain" required="true">
282 <para>Voicemail domain</para>
285 <parameter name="options" required="false">
287 <option name="template">
288 <para>E-mail template to use for voicemail notification</para>
294 <para>This application is part of the Mini-Voicemail system, configured in minivm.conf.</para>
295 <para>MiniVMnotify forwards messages about new voicemail to e-mail and pager. If there's no user
296 account for that address, a temporary account will be used with default options (set in
297 <filename>minivm.conf</filename>).</para>
298 <para>If the channel variable <variable>MVM_COUNTER</variable> is set, this will be used in the message
299 file name and available in the template for the message.</para>
300 <para>If no template is given, the default email template will be used to send email and default pager
301 template to send paging message (if the user account is configured with a paging address.</para>
303 <variable name="MVM_NOTIFY_STATUS">
304 <para>This is the status of the notification attempt</para>
305 <value name="SUCCESS" />
306 <value name="FAILED" />
311 <application name="MinivmDelete" language="en_US">
313 Delete Mini-Voicemail voicemail messages.
316 <parameter name="filename" required="true">
317 <para>File to delete</para>
321 <para>This application is part of the Mini-Voicemail system, configured in <filename>minivm.conf</filename>.</para>
322 <para>It deletes voicemail file set in MVM_FILENAME or given filename.</para>
324 <variable name="MVM_DELETE_STATUS">
325 <para>This is the status of the delete operation.</para>
326 <value name="SUCCESS" />
327 <value name="FAILED" />
333 <application name="MinivmAccMess" language="en_US">
335 Record account specific messages.
338 <parameter name="mailbox" required="true" argsep="@">
339 <argument name="username" required="true">
340 <para>Voicemail username</para>
342 <argument name="domain" required="true">
343 <para>Voicemail domain</para>
346 <parameter name="options" required="false">
349 <para>Record the <literal>unavailable</literal> greeting.</para>
352 <para>Record the <literal>busy</literal> greeting.</para>
355 <para>Record the temporary greeting.</para>
358 <para>Account name.</para>
364 <para>This application is part of the Mini-Voicemail system, configured in <filename>minivm.conf</filename>.</para>
365 <para>Use this application to record account specific audio/video messages for busy, unavailable
366 and temporary messages.</para>
367 <para>Account specific directories will be created if they do not exist.</para>
369 <variable name="MVM_ACCMESS_STATUS">
370 <para>This is the result of the attempt to record the specified greeting.</para>
371 <para><literal>FAILED</literal> is set if the file can't be created.</para>
372 <value name="SUCCESS" />
373 <value name="FAILED" />
378 <application name="MinivmMWI" language="en_US">
380 Send Message Waiting Notification to subscriber(s) of mailbox.
383 <parameter name="mailbox" required="true" argsep="@">
384 <argument name="username" required="true">
385 <para>Voicemail username</para>
387 <argument name="domain" required="true">
388 <para>Voicemail domain</para>
391 <parameter name="urgent" required="true">
392 <para>Number of urgent messages in mailbox.</para>
394 <parameter name="new" required="true">
395 <para>Number of new messages in mailbox.</para>
397 <parameter name="old" required="true">
398 <para>Number of old messages in mailbox.</para>
402 <para>This application is part of the Mini-Voicemail system, configured in <filename>minivm.conf</filename>.</para>
403 <para>MinivmMWI is used to send message waiting indication to any devices whose channels have
404 subscribed to the mailbox passed in the first parameter.</para>
407 <function name="MINIVMCOUNTER" language="en_US">
409 Reads or sets counters for MiniVoicemail message.
412 <parameter name="account" required="true">
413 <para>If account is given and it exists, the counter is specific for the account.</para>
414 <para>If account is a domain and the domain directory exists, counters are specific for a domain.</para>
416 <parameter name="name" required="true">
417 <para>The name of the counter is a string, up to 10 characters.</para>
419 <parameter name="operand">
420 <para>The counters never goes below zero. Valid operands for changing the value of a counter when assigning a value are:</para>
422 <enum name="i"><para>Increment by value.</para></enum>
423 <enum name="d"><para>Decrement by value.</para></enum>
424 <enum name="s"><para>Set to value.</para></enum>
429 <para>The operation is atomic and the counter is locked while changing the value. The counters are stored as text files in the minivm account directories. It might be better to use realtime functions if you are using a database to operate your Asterisk.</para>
432 <ref type="application">MinivmRecord</ref>
433 <ref type="application">MinivmGreet</ref>
434 <ref type="application">MinivmNotify</ref>
435 <ref type="application">MinivmDelete</ref>
436 <ref type="application">MinivmAccMess</ref>
437 <ref type="application">MinivmMWI</ref>
438 <ref type="function">MINIVMACCOUNT</ref>
441 <function name="MINIVMACCOUNT" language="en_US">
443 Gets MiniVoicemail account information.
446 <parameter name="account" required="true" />
447 <parameter name="item" required="true">
448 <para>Valid items are:</para>
451 <para>Path to account mailbox (if account exists, otherwise temporary mailbox).</para>
453 <enum name="hasaccount">
454 <para>1 is static Minivm account exists, 0 otherwise.</para>
456 <enum name="fullname">
457 <para>Full name of account owner.</para>
460 <para>Email address used for account.</para>
462 <enum name="etemplate">
463 <para>Email template for account (default template if none is configured).</para>
465 <enum name="ptemplate">
466 <para>Pager template for account (default template if none is configured).</para>
468 <enum name="accountcode">
469 <para>Account code for the voicemail account.</para>
471 <enum name="pincode">
472 <para>Pin code for voicemail account.</para>
474 <enum name="timezone">
475 <para>Time zone for voicemail account.</para>
477 <enum name="language">
478 <para>Language for voicemail account.</para>
480 <enum name="<channel variable name>">
481 <para>Channel variable value (set in configuration for account).</para>
490 <ref type="application">MinivmRecord</ref>
491 <ref type="application">MinivmGreet</ref>
492 <ref type="application">MinivmNotify</ref>
493 <ref type="application">MinivmDelete</ref>
494 <ref type="application">MinivmAccMess</ref>
495 <ref type="application">MinivmMWI</ref>
496 <ref type="function">MINIVMCOUNTER</ref>
510 #define MVM_REVIEW (1 << 0) /*!< Review message */
511 #define MVM_OPERATOR (1 << 1) /*!< Operator exit during voicemail recording */
512 #define MVM_REALTIME (1 << 2) /*!< This user is a realtime account */
513 #define MVM_SVMAIL (1 << 3)
514 #define MVM_ENVELOPE (1 << 4)
515 #define MVM_PBXSKIP (1 << 9)
516 #define MVM_ALLOCED (1 << 13)
518 /*! \brief Default mail command to mail voicemail. Change it with the
519 mailcmd= command in voicemail.conf */
520 #define SENDMAIL "/usr/sbin/sendmail -t"
522 #define SOUND_INTRO "vm-intro"
523 #define B64_BASEMAXINLINE 256 /*!< Buffer size for Base 64 attachment encoding */
524 #define B64_BASELINELEN 72 /*!< Line length for Base 64 endoded messages */
527 #define MAX_DATETIME_FORMAT 512
528 #define MAX_NUM_CID_CONTEXTS 10
530 #define ERROR_LOCK_PATH -100
531 #define VOICEMAIL_DIR_MODE 0700
533 #define VOICEMAIL_CONFIG "minivm.conf"
534 #define ASTERISK_USERNAME "asterisk" /*!< Default username for sending mail is asterisk\@localhost */
536 /*! \brief Message types for notification */
537 enum mvm_messagetype {
540 /* For trunk: MVM_MESSAGE_JABBER, */
543 static char MVM_SPOOL_DIR[PATH_MAX];
545 /* Module declarations */
546 static char *app_minivm_record = "MinivmRecord"; /* Leave a message */
547 static char *app_minivm_greet = "MinivmGreet"; /* Play voicemail prompts */
548 static char *app_minivm_notify = "MinivmNotify"; /* Notify about voicemail by using one of several methods */
549 static char *app_minivm_delete = "MinivmDelete"; /* Notify about voicemail by using one of several methods */
550 static char *app_minivm_accmess = "MinivmAccMess"; /* Record personal voicemail messages */
551 static char *app_minivm_mwi = "MinivmMWI";
555 enum minivm_option_flags {
556 OPT_SILENT = (1 << 0),
557 OPT_BUSY_GREETING = (1 << 1),
558 OPT_UNAVAIL_GREETING = (1 << 2),
559 OPT_TEMP_GREETING = (1 << 3),
560 OPT_NAME_GREETING = (1 << 4),
561 OPT_RECORDGAIN = (1 << 5),
564 enum minivm_option_args {
565 OPT_ARG_RECORDGAIN = 0,
566 OPT_ARG_ARRAY_SIZE = 1,
569 AST_APP_OPTIONS(minivm_app_options, {
570 AST_APP_OPTION('s', OPT_SILENT),
571 AST_APP_OPTION('b', OPT_BUSY_GREETING),
572 AST_APP_OPTION('u', OPT_UNAVAIL_GREETING),
573 AST_APP_OPTION_ARG('g', OPT_RECORDGAIN, OPT_ARG_RECORDGAIN),
576 AST_APP_OPTIONS(minivm_accmess_options, {
577 AST_APP_OPTION('b', OPT_BUSY_GREETING),
578 AST_APP_OPTION('u', OPT_UNAVAIL_GREETING),
579 AST_APP_OPTION('t', OPT_TEMP_GREETING),
580 AST_APP_OPTION('n', OPT_NAME_GREETING),
584 * \brief Structure for linked list of Mini-Voicemail users: \ref minivm_accounts */
585 struct minivm_account {
586 char username[AST_MAX_CONTEXT]; /*!< Mailbox username */
587 char domain[AST_MAX_CONTEXT]; /*!< Voicemail domain */
589 char pincode[10]; /*!< Secret pin code, numbers only */
590 char fullname[120]; /*!< Full name, for directory app */
591 char email[80]; /*!< E-mail address - override */
592 char pager[80]; /*!< E-mail address to pager (no attachment) */
593 char accountcode[AST_MAX_ACCOUNT_CODE]; /*!< Voicemail account account code */
594 char serveremail[80]; /*!< From: Mail address */
595 char externnotify[160]; /*!< Configurable notification command */
596 char language[MAX_LANGUAGE]; /*!< Config: Language setting */
597 char zonetag[80]; /*!< Time zone */
598 char uniqueid[20]; /*!< Unique integer identifier */
599 char exit[80]; /*!< Options for exiting from voicemail() */
600 char attachfmt[80]; /*!< Format for voicemail audio file attachment */
601 char etemplate[80]; /*!< Pager template */
602 char ptemplate[80]; /*!< Voicemail format */
603 unsigned int flags; /*!< MVM_ flags */
604 struct ast_variable *chanvars; /*!< Variables for e-mail template */
605 double volgain; /*!< Volume gain for voicemails sent via e-mail */
606 AST_LIST_ENTRY(minivm_account) list;
610 * \brief The list of e-mail accounts */
611 static AST_LIST_HEAD_STATIC(minivm_accounts, minivm_account);
614 * \brief Linked list of e-mail templates in various languages
615 * These are used as templates for e-mails, pager messages and jabber messages
616 * \ref message_templates
618 struct minivm_template {
619 char name[80]; /*!< Template name */
620 char *body; /*!< Body of this template */
621 char fromaddress[100]; /*!< Who's sending the e-mail? */
622 char serveremail[80]; /*!< From: Mail address */
623 char subject[100]; /*!< Subject line */
624 char charset[32]; /*!< Default character set for this template */
625 char locale[20]; /*!< Locale for setlocale() */
626 char dateformat[80]; /*!< Date format to use in this attachment */
627 int attachment; /*!< Attachment of media yes/no - no for pager messages */
628 AST_LIST_ENTRY(minivm_template) list; /*!< List mechanics */
631 /*! \brief The list of e-mail templates */
632 static AST_LIST_HEAD_STATIC(message_templates, minivm_template);
634 /*! \brief Options for leaving voicemail with the voicemail() application */
635 struct leave_vm_options {
637 signed char record_gain;
640 /*! \brief Structure for base64 encoding */
646 unsigned char iobuf[B64_BASEMAXINLINE];
649 /*! \brief Voicemail time zones */
651 char name[80]; /*!< Name of this time zone */
652 char timezone[80]; /*!< Timezone definition */
653 char msg_format[BUFSIZ]; /*!< Not used in minivm ...yet */
654 AST_LIST_ENTRY(minivm_zone) list; /*!< List mechanics */
657 /*! \brief The list of e-mail time zones */
658 static AST_LIST_HEAD_STATIC(minivm_zones, minivm_zone);
660 /*! \brief Structure for gathering statistics */
661 struct minivm_stats {
662 int voicemailaccounts; /*!< Number of static accounts */
663 int timezones; /*!< Number of time zones */
664 int templates; /*!< Number of templates */
666 struct timeval reset; /*!< Time for last reset */
667 int receivedmessages; /*!< Number of received messages since reset */
668 struct timeval lastreceived; /*!< Time for last voicemail sent */
671 /*! \brief Statistics for voicemail */
672 static struct minivm_stats global_stats;
674 AST_MUTEX_DEFINE_STATIC(minivmlock); /*!< Lock to protect voicemail system */
675 AST_MUTEX_DEFINE_STATIC(minivmloglock); /*!< Lock to protect voicemail system log file */
677 static FILE *minivmlogfile; /*!< The minivm log file */
679 static int global_vmminmessage; /*!< Minimum duration of messages */
680 static int global_vmmaxmessage; /*!< Maximum duration of message */
681 static int global_maxsilence; /*!< Maximum silence during recording */
682 static int global_maxgreet; /*!< Maximum length of prompts */
683 static int global_silencethreshold = 128;
684 static char global_mailcmd[160]; /*!< Configurable mail cmd */
685 static char global_externnotify[160]; /*!< External notification application */
686 static char global_logfile[PATH_MAX]; /*!< Global log file for messages */
687 static char default_vmformat[80];
689 static struct ast_flags globalflags = {0}; /*!< Global voicemail flags */
690 static int global_saydurationminfo;
692 static double global_volgain; /*!< Volume gain for voicmemail via e-mail */
695 * \brief Default dateformat, can be overridden in configuration file */
696 #define DEFAULT_DATEFORMAT "%A, %B %d, %Y at %r"
697 #define DEFAULT_CHARSET "ISO-8859-1"
699 /* Forward declarations */
700 static char *message_template_parse_filebody(const char *filename);
701 static char *message_template_parse_emailbody(const char *body);
702 static int create_vmaccount(char *name, struct ast_variable *var, int realtime);
703 static struct minivm_account *find_user_realtime(const char *domain, const char *username);
704 static char *handle_minivm_reload(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a);
707 * \brief Create message template */
708 static struct minivm_template *message_template_create(const char *name)
710 struct minivm_template *template;
712 template = ast_calloc(1, sizeof(*template));
716 /* Set some defaults for templates */
717 ast_copy_string(template->name, name, sizeof(template->name));
718 ast_copy_string(template->dateformat, DEFAULT_DATEFORMAT, sizeof(template->dateformat));
719 ast_copy_string(template->charset, DEFAULT_CHARSET, sizeof(template->charset));
720 ast_copy_string(template->subject, "New message in mailbox ${MVM_USERNAME}@${MVM_DOMAIN}", sizeof(template->subject));
721 template->attachment = TRUE;
727 * \brief Release memory allocated by message template */
728 static void message_template_free(struct minivm_template *template)
731 ast_free(template->body);
737 * \brief Build message template from configuration */
738 static int message_template_build(const char *name, struct ast_variable *var)
740 struct minivm_template *template;
743 template = message_template_create(name);
745 ast_log(LOG_ERROR, "Out of memory, can't allocate message template object %s.\n", name);
750 ast_debug(3, "Configuring template option %s = \"%s\" for template %s\n", var->name, var->value, name);
751 if (!strcasecmp(var->name, "fromaddress")) {
752 ast_copy_string(template->fromaddress, var->value, sizeof(template->fromaddress));
753 } else if (!strcasecmp(var->name, "fromemail")) {
754 ast_copy_string(template->serveremail, var->value, sizeof(template->serveremail));
755 } else if (!strcasecmp(var->name, "subject")) {
756 ast_copy_string(template->subject, var->value, sizeof(template->subject));
757 } else if (!strcasecmp(var->name, "locale")) {
758 ast_copy_string(template->locale, var->value, sizeof(template->locale));
759 } else if (!strcasecmp(var->name, "attachmedia")) {
760 template->attachment = ast_true(var->value);
761 } else if (!strcasecmp(var->name, "dateformat")) {
762 ast_copy_string(template->dateformat, var->value, sizeof(template->dateformat));
763 } else if (!strcasecmp(var->name, "charset")) {
764 ast_copy_string(template->charset, var->value, sizeof(template->charset));
765 } else if (!strcasecmp(var->name, "templatefile")) {
767 ast_free(template->body);
768 template->body = message_template_parse_filebody(var->value);
769 if (!template->body) {
770 ast_log(LOG_ERROR, "Error reading message body definition file %s\n", var->value);
773 } else if (!strcasecmp(var->name, "messagebody")) {
775 ast_free(template->body);
776 template->body = message_template_parse_emailbody(var->value);
777 if (!template->body) {
778 ast_log(LOG_ERROR, "Error parsing message body definition:\n %s\n", var->value);
782 ast_log(LOG_ERROR, "Unknown message template configuration option \"%s=%s\"\n", var->name, var->value);
788 ast_log(LOG_ERROR, "-- %d errors found parsing message template definition %s\n", error, name);
790 AST_LIST_LOCK(&message_templates);
791 AST_LIST_INSERT_TAIL(&message_templates, template, list);
792 AST_LIST_UNLOCK(&message_templates);
794 global_stats.templates++;
800 * \brief Find named template */
801 static struct minivm_template *message_template_find(const char *name)
803 struct minivm_template *this, *res = NULL;
805 if (ast_strlen_zero(name))
808 AST_LIST_LOCK(&message_templates);
809 AST_LIST_TRAVERSE(&message_templates, this, list) {
810 if (!strcasecmp(this->name, name)) {
815 AST_LIST_UNLOCK(&message_templates);
822 * \brief Clear list of templates */
823 static void message_destroy_list(void)
825 struct minivm_template *this;
826 AST_LIST_LOCK(&message_templates);
827 while ((this = AST_LIST_REMOVE_HEAD(&message_templates, list))) {
828 message_template_free(this);
831 AST_LIST_UNLOCK(&message_templates);
835 * \brief read buffer from file (base64 conversion) */
836 static int b64_inbuf(struct b64_baseio *bio, FILE *fi)
843 if ((l = fread(bio->iobuf, 1, B64_BASEMAXINLINE,fi)) <= 0) {
858 * \brief read character from file to buffer (base64 conversion) */
859 static int b64_inchar(struct b64_baseio *bio, FILE *fi)
861 if (bio->iocp >= bio->iolen) {
862 if (!b64_inbuf(bio, fi))
866 return bio->iobuf[bio->iocp++];
870 * \brief write buffer to file (base64 conversion) */
871 static int b64_ochar(struct b64_baseio *bio, int c, FILE *so)
873 if (bio->linelength >= B64_BASELINELEN) {
874 if (fputs(EOL,so) == EOF)
880 if (putc(((unsigned char) c), so) == EOF)
889 * \brief Encode file to base64 encoding for email attachment (base64 conversion) */
890 static int base_encode(char *filename, FILE *so)
892 unsigned char dtable[B64_BASEMAXINLINE];
895 struct b64_baseio bio;
897 memset(&bio, 0, sizeof(bio));
898 bio.iocp = B64_BASEMAXINLINE;
900 if (!(fi = fopen(filename, "rb"))) {
901 ast_log(LOG_WARNING, "Failed to open file: %s: %s\n", filename, strerror(errno));
905 for (i= 0; i<9; i++) {
909 dtable[26+i+9]= 'j'+i;
911 for (i= 0; i < 8; i++) {
913 dtable[26+i+18]= 's'+i;
915 for (i= 0; i < 10; i++) {
922 unsigned char igroup[3], ogroup[4];
925 igroup[0]= igroup[1]= igroup[2]= 0;
927 for (n= 0; n < 3; n++) {
928 if ((c = b64_inchar(&bio, fi)) == EOF) {
932 igroup[n]= (unsigned char)c;
936 ogroup[0]= dtable[igroup[0]>>2];
937 ogroup[1]= dtable[((igroup[0]&3)<<4) | (igroup[1]>>4)];
938 ogroup[2]= dtable[((igroup[1]&0xF)<<2) | (igroup[2]>>6)];
939 ogroup[3]= dtable[igroup[2]&0x3F];
949 b64_ochar(&bio, ogroup[i], so);
953 /* Put end of line - line feed */
954 if (fputs(EOL, so) == EOF)
962 static int get_date(char *s, int len)
965 struct timeval now = ast_tvnow();
967 ast_localtime(&now, &tm, NULL);
968 return ast_strftime(s, len, "%a %b %e %r %Z %Y", &tm);
973 * \brief Free user structure - if it's allocated */
974 static void free_user(struct minivm_account *vmu)
977 ast_variables_destroy(vmu->chanvars);
984 * \brief Prepare for voicemail template by adding channel variables
987 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)
990 struct ast_variable *var;
993 ast_log(LOG_ERROR, "No allocated channel, giving up...\n");
997 for (var = vmu->chanvars ; var ; var = var->next) {
998 pbx_builtin_setvar_helper(channel, var->name, var->value);
1001 /* Prepare variables for substition in email body and subject */
1002 pbx_builtin_setvar_helper(channel, "MVM_NAME", vmu->fullname);
1003 pbx_builtin_setvar_helper(channel, "MVM_DUR", dur);
1004 pbx_builtin_setvar_helper(channel, "MVM_DOMAIN", vmu->domain);
1005 pbx_builtin_setvar_helper(channel, "MVM_USERNAME", vmu->username);
1006 pbx_builtin_setvar_helper(channel, "MVM_CALLERID", ast_callerid_merge(callerid, sizeof(callerid), cidname, cidnum, "Unknown Caller"));
1007 pbx_builtin_setvar_helper(channel, "MVM_CIDNAME", (cidname ? cidname : "an unknown caller"));
1008 pbx_builtin_setvar_helper(channel, "MVM_CIDNUM", (cidnum ? cidnum : "an unknown caller"));
1009 pbx_builtin_setvar_helper(channel, "MVM_DATE", date);
1010 if (!ast_strlen_zero(counter))
1011 pbx_builtin_setvar_helper(channel, "MVM_COUNTER", counter);
1015 * \brief Set default values for Mini-Voicemail users */
1016 static void populate_defaults(struct minivm_account *vmu)
1018 ast_copy_flags(vmu, (&globalflags), AST_FLAGS_ALL);
1019 ast_copy_string(vmu->attachfmt, default_vmformat, sizeof(vmu->attachfmt));
1020 vmu->volgain = global_volgain;
1024 * \brief Allocate new vm user and set default values */
1025 static struct minivm_account *mvm_user_alloc(void)
1027 struct minivm_account *new;
1029 new = ast_calloc(1, sizeof(*new));
1032 populate_defaults(new);
1039 * \brief Clear list of users */
1040 static void vmaccounts_destroy_list(void)
1042 struct minivm_account *this;
1043 AST_LIST_LOCK(&minivm_accounts);
1044 while ((this = AST_LIST_REMOVE_HEAD(&minivm_accounts, list)))
1046 AST_LIST_UNLOCK(&minivm_accounts);
1051 * \brief Find user from static memory object list */
1052 static struct minivm_account *find_account(const char *domain, const char *username, int createtemp)
1054 struct minivm_account *vmu = NULL, *cur;
1057 if (ast_strlen_zero(domain) || ast_strlen_zero(username)) {
1058 ast_log(LOG_NOTICE, "No username or domain? \n");
1061 ast_debug(3, "Looking for voicemail user %s in domain %s\n", username, domain);
1063 AST_LIST_LOCK(&minivm_accounts);
1064 AST_LIST_TRAVERSE(&minivm_accounts, cur, list) {
1065 /* Is this the voicemail account we're looking for? */
1066 if (!strcasecmp(domain, cur->domain) && !strcasecmp(username, cur->username))
1069 AST_LIST_UNLOCK(&minivm_accounts);
1072 ast_debug(3, "Found account for %s@%s\n", username, domain);
1076 vmu = find_user_realtime(domain, username);
1078 if (createtemp && !vmu) {
1079 /* Create a temporary user, send e-mail and be gone */
1080 vmu = mvm_user_alloc();
1081 ast_set2_flag(vmu, TRUE, MVM_ALLOCED);
1083 ast_copy_string(vmu->username, username, sizeof(vmu->username));
1084 ast_copy_string(vmu->domain, domain, sizeof(vmu->domain));
1085 ast_debug(1, "Created temporary account\n");
1093 * \brief Find user in realtime storage
1094 * \return pointer to minivm_account structure
1096 static struct minivm_account *find_user_realtime(const char *domain, const char *username)
1098 struct ast_variable *var;
1099 struct minivm_account *retval;
1100 char name[MAXHOSTNAMELEN];
1102 retval = mvm_user_alloc();
1107 ast_copy_string(retval->username, username, sizeof(retval->username));
1109 populate_defaults(retval);
1110 var = ast_load_realtime("minivm", "username", username, "domain", domain, SENTINEL);
1117 snprintf(name, sizeof(name), "%s@%s", username, domain);
1118 create_vmaccount(name, var, TRUE);
1120 ast_variables_destroy(var);
1125 * \brief Check if the string would need encoding within the MIME standard, to
1126 * avoid confusing certain mail software that expects messages to be 7-bit
1129 static int check_mime(const char *str)
1131 for (; *str; str++) {
1132 if (*str > 126 || *str < 32 || strchr("()<>@,:;/\"[]?.=", *str)) {
1140 * \brief Encode a string according to the MIME rules for encoding strings
1141 * that are not 7-bit clean or contain control characters.
1143 * Additionally, if the encoded string would exceed the MIME limit of 76
1144 * characters per line, then the encoding will be broken up into multiple
1145 * sections, separated by a space character, in order to facilitate
1146 * breaking up the associated header across multiple lines.
1148 * \param end An expandable buffer for holding the result
1149 * \param maxlen \see ast_str
1150 * \param charset Character set in which the result should be encoded
1151 * \param start A string to be encoded
1152 * \param preamble The length of the first line already used for this string,
1153 * to ensure that each line maintains a maximum length of 76 chars.
1154 * \param postamble the length of any additional characters appended to the
1155 * line, used to ensure proper field wrapping.
1156 * \return The encoded string.
1158 static const char *ast_str_encode_mime(struct ast_str **end, ssize_t maxlen, const char *charset, const char *start, size_t preamble, size_t postamble)
1160 struct ast_str *tmp = ast_str_alloca(80);
1161 int first_section = 1;
1164 ast_str_reset(*end);
1165 ast_str_set(&tmp, -1, "=?%s?Q?", charset);
1166 for (; *start; start++) {
1167 int need_encoding = 0;
1168 if (*start < 33 || *start > 126 || strchr("()<>@,:;/\"[]?.=_", *start)) {
1171 if ((first_section && need_encoding && preamble + ast_str_strlen(tmp) > 70) ||
1172 (first_section && !need_encoding && preamble + ast_str_strlen(tmp) > 72) ||
1173 (!first_section && need_encoding && ast_str_strlen(tmp) > 70) ||
1174 (!first_section && !need_encoding && ast_str_strlen(tmp) > 72)) {
1175 /* Start new line */
1176 ast_str_append(end, maxlen, "%s%s?=", first_section ? "" : " ", ast_str_buffer(tmp));
1177 ast_str_set(&tmp, -1, "=?%s?Q?", charset);
1180 if (need_encoding && *start == ' ') {
1181 ast_str_append(&tmp, -1, "_");
1182 } else if (need_encoding) {
1183 ast_str_append(&tmp, -1, "=%hhX", *start);
1185 ast_str_append(&tmp, -1, "%c", *start);
1188 ast_str_append(end, maxlen, "%s%s?=%s", first_section ? "" : " ", ast_str_buffer(tmp), ast_str_strlen(tmp) + postamble > 74 ? " " : "");
1189 return ast_str_buffer(*end);
1193 * \brief Wraps a character sequence in double quotes, escaping occurences of quotes within the string.
1194 * \param from The string to work with.
1195 * \param buf The destination buffer to write the modified quoted string.
1196 * \param maxlen Always zero. \see ast_str
1198 * \return The destination string with quotes wrapped on it (the to field).
1200 static const char *ast_str_quote(struct ast_str **buf, ssize_t maxlen, const char *from)
1204 /* We're only ever passing 0 to maxlen, so short output isn't possible */
1205 ast_str_set(buf, maxlen, "\"");
1206 for (ptr = from; *ptr; ptr++) {
1207 if (*ptr == '"' || *ptr == '\\') {
1208 ast_str_append(buf, maxlen, "\\%c", *ptr);
1210 ast_str_append(buf, maxlen, "%c", *ptr);
1213 ast_str_append(buf, maxlen, "\"");
1215 return ast_str_buffer(*buf);
1219 * \brief Send voicemail with audio file as an attachment */
1220 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)
1224 char email[256] = "";
1228 char fname[PATH_MAX];
1230 char tmp[80] = "/tmp/astmail-XXXXXX";
1231 char tmp2[PATH_MAX];
1234 struct minivm_zone *the_zone = NULL;
1235 struct ast_channel *ast;
1236 char *finalfilename = "";
1237 struct ast_str *str1 = ast_str_create(16), *str2 = ast_str_create(16);
1241 if (!str1 || !str2) {
1247 if (type == MVM_MESSAGE_EMAIL) {
1248 if (vmu && !ast_strlen_zero(vmu->email)) {
1249 ast_copy_string(email, vmu->email, sizeof(email));
1250 } else if (!ast_strlen_zero(vmu->username) && !ast_strlen_zero(vmu->domain))
1251 snprintf(email, sizeof(email), "%s@%s", vmu->username, vmu->domain);
1252 } else if (type == MVM_MESSAGE_PAGE) {
1253 ast_copy_string(email, vmu->pager, sizeof(email));
1256 if (ast_strlen_zero(email)) {
1257 ast_log(LOG_WARNING, "No address to send message to.\n");
1261 ast_debug(3, "Sending mail to %s@%s - Using template %s\n", vmu->username, vmu->domain, template->name);
1263 if (!strcmp(format, "wav49"))
1267 /* If we have a gain option, process it now with sox */
1268 if (type == MVM_MESSAGE_EMAIL && (vmu->volgain < -.001 || vmu->volgain > .001) ) {
1269 char newtmp[PATH_MAX];
1270 char tmpcmd[PATH_MAX];
1275 * /bug tmpfd is a leaked fd. The file is also never unlinked.
1276 * See app_voicemail.c for how the code works there that
1277 * doesn't have this bug.
1280 ast_copy_string(newtmp, "/tmp/XXXXXX", sizeof(newtmp));
1281 ast_debug(3, "newtmp: %s\n", newtmp);
1282 tmpfd = mkstemp(newtmp);
1284 snprintf(tmpcmd, sizeof(tmpcmd), "sox -v %.4f %s.%s %s.%s", vmu->volgain, filename, format, newtmp, format);
1285 ast_safe_system(tmpcmd);
1286 finalfilename = newtmp;
1287 ast_debug(3, "VOLGAIN: Stored at: %s.%s - Level: %.4f - Mailbox: %s\n", filename, format, vmu->volgain, vmu->username);
1290 finalfilename = ast_strdupa(filename);
1293 /* Create file name */
1294 snprintf(fname, sizeof(fname), "%s.%s", finalfilename, format);
1296 if (template->attachment)
1297 ast_debug(1, "Attaching file '%s', format '%s', uservm is '%d'\n", finalfilename, format, attach_user_voicemail);
1299 /* Make a temporary file instead of piping directly to sendmail, in case the mail
1303 p = fdopen(pfd, "w");
1308 ast_debug(1, "Opening temp file for e-mail: %s\n", tmp);
1311 ast_log(LOG_WARNING, "Unable to open temporary file '%s'\n", tmp);
1314 /* Allocate channel used for chanvar substitution */
1315 ast = ast_dummy_channel_alloc();
1320 snprintf(dur, sizeof(dur), "%d:%02d", duration / 60, duration % 60);
1322 /* Does this user have a timezone specified? */
1323 if (!ast_strlen_zero(vmu->zonetag)) {
1324 /* Find the zone in the list */
1325 struct minivm_zone *z;
1326 AST_LIST_LOCK(&minivm_zones);
1327 AST_LIST_TRAVERSE(&minivm_zones, z, list) {
1328 if (strcmp(z->name, vmu->zonetag))
1332 AST_LIST_UNLOCK(&minivm_zones);
1336 ast_localtime(&now, &tm, the_zone ? the_zone->timezone : NULL);
1337 ast_strftime(date, sizeof(date), "%a, %d %b %Y %H:%M:%S %z", &tm);
1339 /* Start printing the email to the temporary file */
1340 fprintf(p, "Date: %s\n", date);
1342 /* Set date format for voicemail mail */
1343 ast_strftime(date, sizeof(date), template->dateformat, &tm);
1346 /* Populate channel with channel variables for substitution */
1347 prep_email_sub_vars(ast, vmu, cidnum, cidname, dur, date, counter);
1349 /* Find email address to use */
1350 /* If there's a server e-mail adress in the account, user that, othterwise template */
1351 fromemail = ast_strlen_zero(vmu->serveremail) ? template->serveremail : vmu->serveremail;
1353 /* Find name to user for server e-mail */
1354 fromaddress = ast_strlen_zero(template->fromaddress) ? "" : template->fromaddress;
1356 /* If needed, add hostname as domain */
1357 if (ast_strlen_zero(fromemail))
1358 fromemail = "asterisk";
1360 if (strchr(fromemail, '@'))
1361 ast_copy_string(who, fromemail, sizeof(who));
1363 char host[MAXHOSTNAMELEN];
1364 gethostname(host, sizeof(host)-1);
1365 snprintf(who, sizeof(who), "%s@%s", fromemail, host);
1368 if (ast_strlen_zero(fromaddress)) {
1369 fprintf(p, "From: Asterisk PBX <%s>\n", who);
1371 ast_debug(4, "Fromaddress template: %s\n", fromaddress);
1372 ast_str_substitute_variables(&str1, 0, ast, fromaddress);
1373 if (check_mime(ast_str_buffer(str1))) {
1376 ast_str_encode_mime(&str2, 0, template->charset, ast_str_buffer(str1), strlen("From: "), strlen(who) + 3);
1377 while ((ptr = strchr(ast_str_buffer(str2), ' '))) {
1379 fprintf(p, "%s %s\n", first_line ? "From:" : "", ast_str_buffer(str2));
1381 /* Substring is smaller, so this will never grow */
1382 ast_str_set(&str2, 0, "%s", ptr + 1);
1384 fprintf(p, "%s %s <%s>\n", first_line ? "From:" : "", ast_str_buffer(str2), who);
1386 fprintf(p, "From: %s <%s>\n", ast_str_quote(&str2, 0, ast_str_buffer(str1)), who);
1390 fprintf(p, "Message-ID: <Asterisk-%d-%s-%d-%s>\n", (unsigned int)ast_random(), vmu->username, (int)getpid(), who);
1392 if (ast_strlen_zero(vmu->email)) {
1393 snprintf(email, sizeof(email), "%s@%s", vmu->username, vmu->domain);
1395 ast_copy_string(email, vmu->email, sizeof(email));
1398 if (check_mime(vmu->fullname)) {
1401 ast_str_encode_mime(&str2, 0, template->charset, vmu->fullname, strlen("To: "), strlen(email) + 3);
1402 while ((ptr = strchr(ast_str_buffer(str2), ' '))) {
1404 fprintf(p, "%s %s\n", first_line ? "To:" : "", ast_str_buffer(str2));
1406 /* Substring is smaller, so this will never grow */
1407 ast_str_set(&str2, 0, "%s", ptr + 1);
1409 fprintf(p, "%s %s <%s>\n", first_line ? "To:" : "", ast_str_buffer(str2), email);
1411 fprintf(p, "To: %s <%s>\n", ast_str_quote(&str2, 0, vmu->fullname), email);
1414 if (!ast_strlen_zero(template->subject)) {
1415 ast_str_substitute_variables(&str1, 0, ast, template->subject);
1416 if (check_mime(ast_str_buffer(str1))) {
1419 ast_str_encode_mime(&str2, 0, template->charset, ast_str_buffer(str1), strlen("Subject: "), 0);
1420 while ((ptr = strchr(ast_str_buffer(str2), ' '))) {
1422 fprintf(p, "%s %s\n", first_line ? "Subject:" : "", ast_str_buffer(str2));
1424 /* Substring is smaller, so this will never grow */
1425 ast_str_set(&str2, 0, "%s", ptr + 1);
1427 fprintf(p, "%s %s\n", first_line ? "Subject:" : "", ast_str_buffer(str2));
1429 fprintf(p, "Subject: %s\n", ast_str_buffer(str1));
1432 fprintf(p, "Subject: New message in mailbox %s@%s\n", vmu->username, vmu->domain);
1433 ast_debug(1, "Using default subject for this email \n");
1436 if (option_debug > 2)
1437 fprintf(p, "X-Asterisk-debug: template %s user account %s@%s\n", template->name, vmu->username, vmu->domain);
1438 fprintf(p, "MIME-Version: 1.0\n");
1440 /* Something unique. */
1441 snprintf(bound, sizeof(bound), "voicemail_%s%d%d", vmu->username, (int)getpid(), (unsigned int)ast_random());
1443 fprintf(p, "Content-Type: multipart/mixed; boundary=\"%s\"\n\n\n", bound);
1445 fprintf(p, "--%s\n", bound);
1446 fprintf(p, "Content-Type: text/plain; charset=%s\nContent-Transfer-Encoding: 8bit\n\n", template->charset);
1447 if (!ast_strlen_zero(template->body)) {
1448 ast_str_substitute_variables(&str1, 0, ast, template->body);
1449 ast_debug(3, "Message now: %s\n-----\n", ast_str_buffer(str1));
1450 fprintf(p, "%s\n", ast_str_buffer(str1));
1452 fprintf(p, "Dear %s:\n\n\tJust wanted to let you know you were just left a %s long message \n"
1453 "in mailbox %s from %s, on %s so you might\n"
1454 "want to check it when you get a chance. Thanks!\n\n\t\t\t\t--Asterisk\n\n", vmu->fullname,
1455 dur, vmu->username, (cidname ? cidname : (cidnum ? cidnum : "an unknown caller")), date);
1456 ast_debug(3, "Using default message body (no template)\n-----\n");
1458 /* Eww. We want formats to tell us their own MIME type */
1459 if (template->attachment) {
1460 char *ctype = "audio/x-";
1461 ast_debug(3, "Attaching file to message: %s\n", fname);
1462 if (!strcasecmp(format, "ogg"))
1463 ctype = "application/";
1465 fprintf(p, "--%s\n", bound);
1466 fprintf(p, "Content-Type: %s%s; name=\"voicemailmsg.%s\"\n", ctype, format, format);
1467 fprintf(p, "Content-Transfer-Encoding: base64\n");
1468 fprintf(p, "Content-Description: Voicemail sound attachment.\n");
1469 fprintf(p, "Content-Disposition: attachment; filename=\"voicemail%s.%s\"\n\n", counter ? counter : "", format);
1471 base_encode(fname, p);
1472 fprintf(p, "\n\n--%s--\n.\n", bound);
1475 snprintf(tmp2, sizeof(tmp2), "( %s < %s ; rm -f %s ) &", global_mailcmd, tmp, tmp);
1476 ast_safe_system(tmp2);
1477 ast_debug(1, "Sent message to %s with command '%s' - %s\n", vmu->email, global_mailcmd, template->attachment ? "(media attachment)" : "");
1478 ast_debug(3, "Actual command used: %s\n", tmp2);
1479 ast = ast_channel_unref(ast);
1486 * \brief Create directory based on components */
1487 static int make_dir(char *dest, int len, const char *domain, const char *username, const char *folder)
1489 return snprintf(dest, len, "%s%s/%s%s%s", MVM_SPOOL_DIR, domain, username, ast_strlen_zero(folder) ? "" : "/", folder ? folder : "");
1493 * \brief Checks if directory exists. Does not create directory, but builds string in dest
1494 * \param dest String. base directory.
1495 * \param len Int. Length base directory string.
1496 * \param domain String. Ignored if is null or empty string.
1497 * \param username String. Ignored if is null or empty string.
1498 * \param folder String. Ignored if is null or empty string.
1499 * \return 0 on failure, 1 on success.
1501 static int check_dirpath(char *dest, int len, char *domain, char *username, char *folder)
1503 struct stat filestat;
1504 make_dir(dest, len, domain, username, folder ? folder : "");
1505 if (stat(dest, &filestat)== -1)
1512 * \brief basically mkdir -p $dest/$domain/$username/$folder
1513 * \param dest String. base directory.
1514 * \param len Length of directory string
1515 * \param domain String. Ignored if is null or empty string.
1516 * \param folder String. Ignored if is null or empty string.
1517 * \param username String. Ignored if is null or empty string.
1518 * \return -1 on failure, 0 on success.
1520 static int create_dirpath(char *dest, int len, char *domain, char *username, char *folder)
1523 make_dir(dest, len, domain, username, folder);
1524 if ((res = ast_mkdir(dest, 0777))) {
1525 ast_log(LOG_WARNING, "ast_mkdir '%s' failed: %s\n", dest, strerror(res));
1528 ast_debug(2, "Creating directory for %s@%s folder %s : %s\n", username, domain, folder, dest);
1534 * \brief Play intro message before recording voicemail
1536 static int invent_message(struct ast_channel *chan, char *domain, char *username, int busy, char *ecodes)
1541 ast_debug(2, "Still preparing to play message ...\n");
1543 snprintf(fn, sizeof(fn), "%s%s/%s/greet", MVM_SPOOL_DIR, domain, username);
1545 if (ast_fileexists(fn, NULL, NULL) > 0) {
1546 res = ast_streamfile(chan, fn, ast_channel_language(chan));
1549 res = ast_waitstream(chan, ecodes);
1553 int numericusername = 1;
1556 ast_debug(2, "No personal prompts. Using default prompt set for language\n");
1559 ast_debug(2, "Numeric? Checking %c\n", *i);
1561 numericusername = FALSE;
1567 if (numericusername) {
1568 if (ast_streamfile(chan, "vm-theperson", ast_channel_language(chan)))
1570 if ((res = ast_waitstream(chan, ecodes)))
1573 res = ast_say_digit_str(chan, username, ecodes, ast_channel_language(chan));
1577 if (ast_streamfile(chan, "vm-theextensionis", ast_channel_language(chan)))
1579 if ((res = ast_waitstream(chan, ecodes)))
1584 res = ast_streamfile(chan, busy ? "vm-isonphone" : "vm-isunavail", ast_channel_language(chan));
1587 res = ast_waitstream(chan, ecodes);
1592 * \brief Delete media files and attribute file */
1593 static int vm_delete(char *file)
1597 ast_debug(1, "Deleting voicemail file %s\n", file);
1599 res = unlink(file); /* Remove the meta data file */
1600 res |= ast_filedelete(file, NULL); /* remove the media file */
1606 * \brief Record voicemail message & let caller review or re-record it, or set options if applicable */
1607 static int play_record_review(struct ast_channel *chan, char *playfile, char *recordfile, int maxtime, char *fmt,
1608 int outsidecaller, struct minivm_account *vmu, int *duration, int *sound_duration, const char *unlockdir,
1609 signed char record_gain)
1612 int max_attempts = 3;
1615 int message_exists = 0;
1616 signed char zero_gain = 0;
1617 char *acceptdtmf = "#";
1618 char *canceldtmf = "";
1620 /* Note that urgent and private are for flagging messages as such in the future */
1622 /* barf if no pointer passed to store duration in */
1623 if (duration == NULL) {
1624 ast_log(LOG_WARNING, "Error play_record_review called without duration pointer\n");
1628 cmd = '3'; /* Want to start by recording */
1630 while ((cmd >= 0) && (cmd != 't')) {
1633 ast_verb(3, "Saving message as is\n");
1634 ast_stream_and_wait(chan, "vm-msgsaved", "");
1639 ast_verb(3, "Reviewing the message\n");
1640 ast_streamfile(chan, recordfile, ast_channel_language(chan));
1641 cmd = ast_waitstream(chan, AST_DIGIT_ANY);
1647 ast_verb(3, "Re-recording the message\n");
1649 ast_verb(3, "Recording the message\n");
1650 if (recorded && outsidecaller)
1651 cmd = ast_play_and_wait(chan, "beep");
1653 /* 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 */
1655 ast_channel_setoption(chan, AST_OPTION_RXGAIN, &record_gain, sizeof(record_gain), 0);
1656 if (ast_test_flag(vmu, MVM_OPERATOR))
1658 cmd = ast_play_and_record_full(chan, playfile, recordfile, maxtime, fmt, duration, sound_duration, global_silencethreshold, global_maxsilence, unlockdir, acceptdtmf, canceldtmf);
1660 ast_channel_setoption(chan, AST_OPTION_RXGAIN, &zero_gain, sizeof(zero_gain), 0);
1661 if (cmd == -1) /* User has hung up, no options to give */
1665 else if (cmd == '*')
1668 /* If all is well, a message exists */
1681 cmd = ast_play_and_wait(chan, "vm-sorry");
1684 if(!ast_test_flag(vmu, MVM_OPERATOR)) {
1685 cmd = ast_play_and_wait(chan, "vm-sorry");
1688 if (message_exists || recorded) {
1689 cmd = ast_play_and_wait(chan, "vm-saveoper");
1691 cmd = ast_waitfordigit(chan, 3000);
1693 ast_play_and_wait(chan, "vm-msgsaved");
1696 ast_play_and_wait(chan, "vm-deleted");
1697 vm_delete(recordfile);
1703 /* If the caller is an ouside caller, and the review option is enabled,
1704 allow them to review the message, but let the owner of the box review
1706 if (outsidecaller && !ast_test_flag(vmu, MVM_REVIEW))
1708 if (message_exists) {
1709 cmd = ast_play_and_wait(chan, "vm-review");
1711 cmd = ast_play_and_wait(chan, "vm-torerecord");
1713 cmd = ast_waitfordigit(chan, 600);
1716 if (!cmd && outsidecaller && ast_test_flag(vmu, MVM_OPERATOR)) {
1717 cmd = ast_play_and_wait(chan, "vm-reachoper");
1719 cmd = ast_waitfordigit(chan, 600);
1722 cmd = ast_waitfordigit(chan, 6000);
1726 if (attempts > max_attempts) {
1732 ast_play_and_wait(chan, "vm-goodbye");
1738 /*! \brief Run external notification for voicemail message */
1739 static void run_externnotify(struct ast_channel *chan, struct minivm_account *vmu)
1741 char arguments[BUFSIZ];
1743 if (ast_strlen_zero(vmu->externnotify) && ast_strlen_zero(global_externnotify))
1746 snprintf(arguments, sizeof(arguments), "%s %s@%s %s %s&",
1747 ast_strlen_zero(vmu->externnotify) ? global_externnotify : vmu->externnotify,
1748 vmu->username, vmu->domain,
1749 (ast_channel_caller(chan)->id.name.valid && ast_channel_caller(chan)->id.name.str)
1750 ? ast_channel_caller(chan)->id.name.str : "",
1751 (ast_channel_caller(chan)->id.number.valid && ast_channel_caller(chan)->id.number.str)
1752 ? ast_channel_caller(chan)->id.number.str : "");
1754 ast_debug(1, "Executing: %s\n", arguments);
1755 ast_safe_system(arguments);
1759 * \brief Send message to voicemail account owner */
1760 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)
1763 struct minivm_template *etemplate;
1764 char *messageformat;
1766 char oldlocale[100];
1767 const char *counter;
1769 if (!ast_strlen_zero(vmu->attachfmt)) {
1770 if (strstr(format, vmu->attachfmt)) {
1771 format = vmu->attachfmt;
1773 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);
1777 etemplate = message_template_find(vmu->etemplate);
1779 etemplate = message_template_find(templatename);
1781 etemplate = message_template_find("email-default");
1783 /* Attach only the first format */
1784 stringp = messageformat = ast_strdupa(format);
1785 strsep(&stringp, "|");
1787 if (!ast_strlen_zero(etemplate->locale)) {
1789 ast_copy_string(oldlocale, setlocale(LC_TIME, NULL), sizeof(oldlocale));
1790 ast_debug(2, "Changing locale from %s to %s\n", oldlocale, etemplate->locale);
1791 new_locale = setlocale(LC_TIME, etemplate->locale);
1792 if (new_locale == NULL) {
1793 ast_log(LOG_WARNING, "-_-_- Changing to new locale did not work. Locale: %s\n", etemplate->locale);
1799 /* Read counter if available */
1800 ast_channel_lock(chan);
1801 if ((counter = pbx_builtin_getvar_helper(chan, "MVM_COUNTER"))) {
1802 counter = ast_strdupa(counter);
1804 ast_channel_unlock(chan);
1806 if (ast_strlen_zero(counter)) {
1807 ast_debug(2, "MVM_COUNTER not found\n");
1809 ast_debug(2, "MVM_COUNTER found - will use it with value %s\n", counter);
1812 res = sendmail(etemplate, vmu, cidnum, cidname, filename, messageformat, duration, etemplate->attachment, MVM_MESSAGE_EMAIL, counter);
1814 if (res == 0 && !ast_strlen_zero(vmu->pager)) {
1815 /* Find template for paging */
1816 etemplate = message_template_find(vmu->ptemplate);
1818 etemplate = message_template_find("pager-default");
1819 if (etemplate->locale) {
1820 ast_copy_string(oldlocale, setlocale(LC_TIME, ""), sizeof(oldlocale));
1821 setlocale(LC_TIME, etemplate->locale);
1824 res = sendmail(etemplate, vmu, cidnum, cidname, filename, messageformat, duration, etemplate->attachment, MVM_MESSAGE_PAGE, counter);
1827 ast_manager_event(chan, EVENT_FLAG_CALL, "MiniVoiceMail", "Action: SentNotification\rn\nMailbox: %s@%s\r\nCounter: %s\r\n", vmu->username, vmu->domain, counter);
1829 run_externnotify(chan, vmu); /* Run external notification */
1831 if (etemplate->locale) {
1832 setlocale(LC_TIME, oldlocale); /* Rest to old locale */
1839 * \brief Record voicemail message, store into file prepared for sending e-mail */
1840 static int leave_voicemail(struct ast_channel *chan, char *username, struct leave_vm_options *options)
1842 char tmptxtfile[PATH_MAX];
1845 int res = 0, txtdes;
1847 int sound_duration = 0;
1849 char tmpdir[PATH_MAX];
1850 char ext_context[256] = "";
1854 struct minivm_account *vmu;
1857 ast_copy_string(tmp, username, sizeof(tmp));
1859 domain = strchr(tmp, '@');
1865 if (!(vmu = find_account(domain, username, TRUE))) {
1866 /* We could not find user, let's exit */
1867 ast_log(LOG_ERROR, "Can't allocate temporary account for '%s@%s'\n", username, domain);
1868 pbx_builtin_setvar_helper(chan, "MVM_RECORD_STATUS", "FAILED");
1872 /* Setup pre-file if appropriate */
1873 if (strcmp(vmu->domain, "localhost"))
1874 snprintf(ext_context, sizeof(ext_context), "%s@%s", username, vmu->domain);
1876 ast_copy_string(ext_context, vmu->domain, sizeof(ext_context));
1878 /* The meat of recording the message... All the announcements and beeps have been played*/
1879 if (ast_strlen_zero(vmu->attachfmt))
1880 ast_copy_string(fmt, default_vmformat, sizeof(fmt));
1882 ast_copy_string(fmt, vmu->attachfmt, sizeof(fmt));
1884 if (ast_strlen_zero(fmt)) {
1885 ast_log(LOG_WARNING, "No format for saving voicemail? Default %s\n", default_vmformat);
1886 pbx_builtin_setvar_helper(chan, "MVM_RECORD_STATUS", "FAILED");
1890 userdir = check_dirpath(tmpdir, sizeof(tmpdir), vmu->domain, username, "tmp");
1892 /* If we have no user directory, use generic temporary directory */
1894 create_dirpath(tmpdir, sizeof(tmpdir), "0000_minivm_temp", "mediafiles", "");
1895 ast_debug(3, "Creating temporary directory %s\n", tmpdir);
1899 snprintf(tmptxtfile, sizeof(tmptxtfile), "%s/XXXXXX", tmpdir);
1901 /* XXX This file needs to be in temp directory */
1902 txtdes = mkstemp(tmptxtfile);
1904 ast_log(LOG_ERROR, "Unable to create message file %s: %s\n", tmptxtfile, strerror(errno));
1905 res = ast_streamfile(chan, "vm-mailboxfull", ast_channel_language(chan));
1907 res = ast_waitstream(chan, "");
1908 pbx_builtin_setvar_helper(chan, "MVM_RECORD_STATUS", "FAILED");
1913 /* Unless we're *really* silent, try to send the beep */
1914 res = ast_streamfile(chan, "beep", ast_channel_language(chan));
1916 res = ast_waitstream(chan, "");
1919 /* OEJ XXX Maybe this can be turned into a log file? Hmm. */
1920 /* Store information */
1921 ast_debug(2, "Open file for metadata: %s\n", tmptxtfile);
1923 res = play_record_review(chan, NULL, tmptxtfile, global_vmmaxmessage, fmt, 1, vmu, &duration, &sound_duration, NULL, options->record_gain);
1925 txt = fdopen(txtdes, "w+");
1927 ast_log(LOG_WARNING, "Error opening text file for output\n");
1930 struct timeval now = ast_tvnow();
1932 char logbuf[BUFSIZ];
1933 get_date(date, sizeof(date));
1934 ast_localtime(&now, &tm, NULL);
1935 ast_strftime(timebuf, sizeof(timebuf), "%H:%M:%S", &tm);
1937 ast_callerid_merge(callerid, sizeof(callerid),
1938 S_COR(ast_channel_caller(chan)->id.name.valid, ast_channel_caller(chan)->id.name.str, NULL),
1939 S_COR(ast_channel_caller(chan)->id.number.valid, ast_channel_caller(chan)->id.number.str, NULL),
1941 snprintf(logbuf, sizeof(logbuf),
1942 /* "Mailbox:domain:macrocontext:exten:priority:callerchan:callerid:origdate:origtime:duration:durationstatus:accountcode" */
1943 "%s:%s:%s:%s:%d:%s:%s:%s:%s:%d:%s:%s\n",
1945 ast_channel_context(chan),
1946 ast_channel_macrocontext(chan),
1947 ast_channel_exten(chan),
1948 ast_channel_priority(chan),
1949 ast_channel_name(chan),
1954 duration < global_vmminmessage ? "IGNORED" : "OK",
1957 fprintf(txt, "%s", logbuf);
1958 if (minivmlogfile) {
1959 ast_mutex_lock(&minivmloglock);
1960 fprintf(minivmlogfile, "%s", logbuf);
1961 ast_mutex_unlock(&minivmloglock);
1964 if (sound_duration < global_vmminmessage) {
1965 ast_verb(3, "Recording was %d seconds long but needs to be at least %d - abandoning\n", sound_duration, global_vmminmessage);
1967 ast_filedelete(tmptxtfile, NULL);
1969 pbx_builtin_setvar_helper(chan, "MVM_RECORD_STATUS", "FAILED");
1972 fclose(txt); /* Close log file */
1973 if (ast_fileexists(tmptxtfile, NULL, NULL) <= 0) {
1974 ast_debug(1, "The recorded media file is gone, so we should remove the .txt file too!\n");
1976 pbx_builtin_setvar_helper(chan, "MVM_RECORD_STATUS", "FAILED");
1977 if(ast_test_flag(vmu, MVM_ALLOCED))
1982 /* Set channel variables for the notify application */
1983 pbx_builtin_setvar_helper(chan, "MVM_FILENAME", tmptxtfile);
1984 snprintf(timebuf, sizeof(timebuf), "%d", duration);
1985 pbx_builtin_setvar_helper(chan, "MVM_DURATION", timebuf);
1986 pbx_builtin_setvar_helper(chan, "MVM_FORMAT", fmt);
1989 global_stats.lastreceived = ast_tvnow();
1990 global_stats.receivedmessages++;
1992 /* Go ahead and delete audio files from system, they're not needed any more */
1993 if (ast_fileexists(tmptxtfile, NULL, NULL) <= 0) {
1994 ast_filedelete(tmptxtfile, NULL);
1995 /* Even not being used at the moment, it's better to convert ast_log to ast_debug anyway */
1996 ast_debug(2, "-_-_- Deleted audio file after notification :: %s \n", tmptxtfile);
2003 if(ast_test_flag(vmu, MVM_ALLOCED))
2006 pbx_builtin_setvar_helper(chan, "MVM_RECORD_STATUS", "SUCCESS");
2011 * \brief Queue a message waiting event */
2012 static void queue_mwi_event(const char *mbx, const char *ctx, int urgent, int new, int old)
2014 struct ast_event *event;
2015 char *mailbox, *context;
2017 mailbox = ast_strdupa(mbx);
2018 context = ast_strdupa(ctx);
2019 if (ast_strlen_zero(context)) {
2020 context = "default";
2023 if (!(event = ast_event_new(AST_EVENT_MWI,
2024 AST_EVENT_IE_MAILBOX, AST_EVENT_IE_PLTYPE_STR, mailbox,
2025 AST_EVENT_IE_CONTEXT, AST_EVENT_IE_PLTYPE_STR, context,
2026 AST_EVENT_IE_NEWMSGS, AST_EVENT_IE_PLTYPE_UINT, (new+urgent),
2027 AST_EVENT_IE_OLDMSGS, AST_EVENT_IE_PLTYPE_UINT, old,
2028 AST_EVENT_IE_END))) {
2032 ast_event_queue_and_cache(event);
2036 * \brief Send MWI using interal Asterisk event subsystem */
2037 static int minivm_mwi_exec(struct ast_channel *chan, const char *data)
2046 if (ast_strlen_zero(data)) {
2047 ast_log(LOG_ERROR, "Minivm needs at least an account argument \n");
2050 tmpptr = ast_strdupa((char *)data);
2052 ast_log(LOG_ERROR, "Out of memory\n");
2055 argc = ast_app_separate_args(tmpptr, ',', argv, ARRAY_LEN(argv));
2057 ast_log(LOG_ERROR, "%d arguments passed to MiniVM_MWI, need 4.\n", argc);
2060 ast_copy_string(tmp, argv[0], sizeof(tmp));
2062 domain = strchr(tmp, '@');
2067 if (ast_strlen_zero(domain) || ast_strlen_zero(mailbox)) {
2068 ast_log(LOG_ERROR, "Need mailbox@context as argument. Sorry. Argument 0 %s\n", argv[0]);
2071 queue_mwi_event(mailbox, domain, atoi(argv[1]), atoi(argv[2]), atoi(argv[3]));
2078 * \brief Notify voicemail account owners - either generic template or user specific */
2079 static int minivm_notify_exec(struct ast_channel *chan, const char *data)
2087 struct minivm_account *vmu;
2089 const char *template = "";
2090 const char *filename;
2092 const char *duration_string;
2094 if (ast_strlen_zero(data)) {
2095 ast_log(LOG_ERROR, "Minivm needs at least an account argument \n");
2098 tmpptr = ast_strdupa((char *)data);
2100 ast_log(LOG_ERROR, "Out of memory\n");
2103 argc = ast_app_separate_args(tmpptr, ',', argv, ARRAY_LEN(argv));
2105 if (argc == 2 && !ast_strlen_zero(argv[1]))
2108 ast_copy_string(tmp, argv[0], sizeof(tmp));
2110 domain = strchr(tmp, '@');
2115 if (ast_strlen_zero(domain) || ast_strlen_zero(username)) {
2116 ast_log(LOG_ERROR, "Need username@domain as argument. Sorry. Argument 0 %s\n", argv[0]);
2120 if(!(vmu = find_account(domain, username, TRUE))) {
2121 /* We could not find user, let's exit */
2122 ast_log(LOG_WARNING, "Could not allocate temporary memory for '%s@%s'\n", username, domain);
2123 pbx_builtin_setvar_helper(chan, "MVM_NOTIFY_STATUS", "FAILED");
2127 ast_channel_lock(chan);
2128 if ((filename = pbx_builtin_getvar_helper(chan, "MVM_FILENAME"))) {
2129 filename = ast_strdupa(filename);
2131 ast_channel_unlock(chan);
2132 /* Notify of new message to e-mail and pager */
2133 if (!ast_strlen_zero(filename)) {
2134 ast_channel_lock(chan);
2135 if ((format = pbx_builtin_getvar_helper(chan, "MVM_FORMAT"))) {
2136 format = ast_strdupa(format);
2138 if ((duration_string = pbx_builtin_getvar_helper(chan, "MVM_DURATION"))) {
2139 duration_string = ast_strdupa(duration_string);
2141 ast_channel_unlock(chan);
2142 res = notify_new_message(chan, template, vmu, filename, atoi(duration_string),
2144 S_COR(ast_channel_caller(chan)->id.number.valid, ast_channel_caller(chan)->id.number.str, NULL),
2145 S_COR(ast_channel_caller(chan)->id.name.valid, ast_channel_caller(chan)->id.name.str, NULL));
2148 pbx_builtin_setvar_helper(chan, "MVM_NOTIFY_STATUS", res == 0 ? "SUCCESS" : "FAILED");
2151 if(ast_test_flag(vmu, MVM_ALLOCED))
2154 /* Ok, we're ready to rock and roll. Return to dialplan */
2161 * \brief Dialplan function to record voicemail */
2162 static int minivm_record_exec(struct ast_channel *chan, const char *data)
2166 struct leave_vm_options leave_options;
2169 struct ast_flags flags = { 0 };
2170 char *opts[OPT_ARG_ARRAY_SIZE];
2172 memset(&leave_options, 0, sizeof(leave_options));
2174 /* Answer channel if it's not already answered */
2175 if (ast_channel_state(chan) != AST_STATE_UP)
2178 if (ast_strlen_zero(data)) {
2179 ast_log(LOG_ERROR, "Minivm needs at least an account argument \n");
2182 tmp = ast_strdupa((char *)data);
2184 ast_log(LOG_ERROR, "Out of memory\n");
2187 argc = ast_app_separate_args(tmp, ',', argv, ARRAY_LEN(argv));
2189 if (ast_app_parse_options(minivm_app_options, &flags, opts, argv[1])) {
2192 ast_copy_flags(&leave_options, &flags, OPT_SILENT | OPT_BUSY_GREETING | OPT_UNAVAIL_GREETING );
2193 if (ast_test_flag(&flags, OPT_RECORDGAIN)) {
2196 if (sscanf(opts[OPT_ARG_RECORDGAIN], "%30d", &gain) != 1) {
2197 ast_log(LOG_WARNING, "Invalid value '%s' provided for record gain option\n", opts[OPT_ARG_RECORDGAIN]);
2200 leave_options.record_gain = (signed char) gain;
2204 /* Now run the appliation and good luck to you! */
2205 res = leave_voicemail(chan, argv[0], &leave_options);
2207 if (res == ERROR_LOCK_PATH) {
2208 ast_log(LOG_ERROR, "Could not leave voicemail. The path is already locked.\n");
2209 pbx_builtin_setvar_helper(chan, "MVM_RECORD_STATUS", "FAILED");
2212 pbx_builtin_setvar_helper(chan, "MVM_RECORD_STATUS", "SUCCESS");
2218 * \brief Play voicemail prompts - either generic or user specific */
2219 static int minivm_greet_exec(struct ast_channel *chan, const char *data)
2221 struct leave_vm_options leave_options = { 0, '\0'};
2224 struct ast_flags flags = { 0 };
2225 char *opts[OPT_ARG_ARRAY_SIZE];
2231 char dest[PATH_MAX];
2232 char prefile[PATH_MAX] = "";
2233 char tempfile[PATH_MAX] = "";
2234 char ext_context[256] = "";
2236 char ecodes[16] = "#";
2238 struct minivm_account *vmu;
2239 char *username = argv[0];
2241 if (ast_strlen_zero(data)) {
2242 ast_log(LOG_ERROR, "Minivm needs at least an account argument \n");
2245 tmpptr = ast_strdupa((char *)data);
2247 ast_log(LOG_ERROR, "Out of memory\n");
2250 argc = ast_app_separate_args(tmpptr, ',', argv, ARRAY_LEN(argv));
2253 if (ast_app_parse_options(minivm_app_options, &flags, opts, argv[1]))
2255 ast_copy_flags(&leave_options, &flags, OPT_SILENT | OPT_BUSY_GREETING | OPT_UNAVAIL_GREETING );
2258 ast_copy_string(tmp, argv[0], sizeof(tmp));
2260 domain = strchr(tmp, '@');
2265 if (ast_strlen_zero(domain) || ast_strlen_zero(username)) {
2266 ast_log(LOG_ERROR, "Need username@domain as argument. Sorry. Argument: %s\n", argv[0]);
2269 ast_debug(1, "Trying to find configuration for user %s in domain %s\n", username, domain);
2271 if (!(vmu = find_account(domain, username, TRUE))) {
2272 ast_log(LOG_ERROR, "Could not allocate memory. \n");
2276 /* Answer channel if it's not already answered */
2277 if (ast_channel_state(chan) != AST_STATE_UP)
2280 /* Setup pre-file if appropriate */
2281 if (strcmp(vmu->domain, "localhost"))
2282 snprintf(ext_context, sizeof(ext_context), "%s@%s", username, vmu->domain);
2284 ast_copy_string(ext_context, vmu->domain, sizeof(ext_context));
2286 if (ast_test_flag(&leave_options, OPT_BUSY_GREETING)) {
2287 res = check_dirpath(dest, sizeof(dest), vmu->domain, username, "busy");
2289 snprintf(prefile, sizeof(prefile), "%s%s/%s/busy", MVM_SPOOL_DIR, vmu->domain, username);
2290 } else if (ast_test_flag(&leave_options, OPT_UNAVAIL_GREETING)) {
2291 res = check_dirpath(dest, sizeof(dest), vmu->domain, username, "unavail");
2293 snprintf(prefile, sizeof(prefile), "%s%s/%s/unavail", MVM_SPOOL_DIR, vmu->domain, username);
2295 /* Check for temporary greeting - it overrides busy and unavail */
2296 snprintf(tempfile, sizeof(tempfile), "%s%s/%s/temp", MVM_SPOOL_DIR, vmu->domain, username);
2297 if (!(res = check_dirpath(dest, sizeof(dest), vmu->domain, username, "temp"))) {
2298 ast_debug(2, "Temporary message directory does not exist, using default (%s)\n", tempfile);
2299 ast_copy_string(prefile, tempfile, sizeof(prefile));
2301 ast_debug(2, "Preparing to play message ...\n");
2303 /* Check current or macro-calling context for special extensions */
2304 if (ast_test_flag(vmu, MVM_OPERATOR)) {
2305 if (!ast_strlen_zero(vmu->exit)) {
2306 if (ast_exists_extension(chan, vmu->exit, "o", 1,
2307 S_COR(ast_channel_caller(chan)->id.number.valid, ast_channel_caller(chan)->id.number.str, NULL))) {
2308 strncat(ecodes, "0", sizeof(ecodes) - strlen(ecodes) - 1);
2311 } else if (ast_exists_extension(chan, ast_channel_context(chan), "o", 1,
2312 S_COR(ast_channel_caller(chan)->id.number.valid, ast_channel_caller(chan)->id.number.str, NULL))) {
2313 strncat(ecodes, "0", sizeof(ecodes) - strlen(ecodes) - 1);
2316 else if (!ast_strlen_zero(ast_channel_macrocontext(chan))
2317 && ast_exists_extension(chan, ast_channel_macrocontext(chan), "o", 1,
2318 S_COR(ast_channel_caller(chan)->id.number.valid, ast_channel_caller(chan)->id.number.str, NULL))) {
2319 strncat(ecodes, "0", sizeof(ecodes) - strlen(ecodes) - 1);
2324 if (!ast_strlen_zero(vmu->exit)) {
2325 if (ast_exists_extension(chan, vmu->exit, "a", 1,
2326 S_COR(ast_channel_caller(chan)->id.number.valid, ast_channel_caller(chan)->id.number.str, NULL))) {
2327 strncat(ecodes, "*", sizeof(ecodes) - strlen(ecodes) - 1);
2329 } else if (ast_exists_extension(chan, ast_channel_context(chan), "a", 1,
2330 S_COR(ast_channel_caller(chan)->id.number.valid, ast_channel_caller(chan)->id.number.str, NULL))) {
2331 strncat(ecodes, "*", sizeof(ecodes) - strlen(ecodes) - 1);
2332 } else if (!ast_strlen_zero(ast_channel_macrocontext(chan))
2333 && ast_exists_extension(chan, ast_channel_macrocontext(chan), "a", 1,
2334 S_COR(ast_channel_caller(chan)->id.number.valid, ast_channel_caller(chan)->id.number.str, NULL))) {
2335 strncat(ecodes, "*", sizeof(ecodes) - strlen(ecodes) - 1);
2339 res = 0; /* Reset */
2340 /* Play the beginning intro if desired */
2341 if (!ast_strlen_zero(prefile)) {
2342 if (ast_streamfile(chan, prefile, ast_channel_language(chan)) > -1)
2343 res = ast_waitstream(chan, ecodes);
2345 ast_debug(2, "%s doesn't exist, doing what we can\n", prefile);
2346 res = invent_message(chan, vmu->domain, username, ast_test_flag(&leave_options, OPT_BUSY_GREETING), ecodes);
2349 ast_debug(2, "Hang up during prefile playback\n");
2350 pbx_builtin_setvar_helper(chan, "MVM_GREET_STATUS", "FAILED");
2351 if(ast_test_flag(vmu, MVM_ALLOCED))
2356 /* On a '#' we skip the instructions */
2357 ast_set_flag(&leave_options, OPT_SILENT);
2360 if (!res && !ast_test_flag(&leave_options, OPT_SILENT)) {
2361 res = ast_streamfile(chan, SOUND_INTRO, ast_channel_language(chan));
2363 res = ast_waitstream(chan, ecodes);
2365 ast_set_flag(&leave_options, OPT_SILENT);
2370 ast_stopstream(chan);
2371 /* Check for a '*' here in case the caller wants to escape from voicemail to something
2372 other than the operator -- an automated attendant or mailbox login for example */
2374 ast_channel_exten_set(chan, "a");
2375 if (!ast_strlen_zero(vmu->exit)) {
2376 ast_channel_context_set(chan, vmu->exit);
2377 } else if (ausemacro && !ast_strlen_zero(ast_channel_macrocontext(chan))) {
2378 ast_channel_context_set(chan, ast_channel_macrocontext(chan));
2380 ast_channel_priority_set(chan, 0);
2381 pbx_builtin_setvar_helper(chan, "MVM_GREET_STATUS", "USEREXIT");
2383 } else if (res == '0') { /* Check for a '0' here */
2384 if(ouseexten || ousemacro) {
2385 ast_channel_exten_set(chan, "o");
2386 if (!ast_strlen_zero(vmu->exit)) {
2387 ast_channel_context_set(chan, vmu->exit);
2388 } else if (ousemacro && !ast_strlen_zero(ast_channel_macrocontext(chan))) {
2389 ast_channel_context_set(chan, ast_channel_macrocontext(chan));
2391 ast_play_and_wait(chan, "transfer");
2392 ast_channel_priority_set(chan, 0);
2393 pbx_builtin_setvar_helper(chan, "MVM_GREET_STATUS", "USEREXIT");
2396 } else if (res < 0) {
2397 pbx_builtin_setvar_helper(chan, "MVM_GREET_STATUS", "FAILED");
2400 pbx_builtin_setvar_helper(chan, "MVM_GREET_STATUS", "SUCCESS");
2402 if(ast_test_flag(vmu, MVM_ALLOCED))
2406 /* Ok, we're ready to rock and roll. Return to dialplan */
2412 * \brief Dialplan application to delete voicemail */
2413 static int minivm_delete_exec(struct ast_channel *chan, const char *data)
2416 char filename[BUFSIZ];
2418 if (!ast_strlen_zero(data)) {
2419 ast_copy_string(filename, (char *) data, sizeof(filename));
2421 ast_channel_lock(chan);
2422 ast_copy_string(filename, pbx_builtin_getvar_helper(chan, "MVM_FILENAME"), sizeof(filename));
2423 ast_channel_unlock(chan);
2426 if (ast_strlen_zero(filename)) {
2427 ast_log(LOG_ERROR, "No filename given in application arguments or channel variable MVM_FILENAME\n");
2431 /* Go ahead and delete audio files from system, they're not needed any more */
2432 /* We should look for both audio and text files here */
2433 if (ast_fileexists(filename, NULL, NULL) > 0) {
2434 res = vm_delete(filename);
2436 ast_debug(2, "Can't delete file: %s\n", filename);
2437 pbx_builtin_setvar_helper(chan, "MVM_DELETE_STATUS", "FAILED");
2439 ast_debug(2, "Deleted voicemail file :: %s \n", filename);
2440 pbx_builtin_setvar_helper(chan, "MVM_DELETE_STATUS", "SUCCESS");
2443 ast_debug(2, "Filename does not exist: %s\n", filename);
2444 pbx_builtin_setvar_helper(chan, "MVM_DELETE_STATUS", "FAILED");
2450 /*! \brief Record specific messages for voicemail account */
2451 static int minivm_accmess_exec(struct ast_channel *chan, const char *data)
2455 char filename[PATH_MAX];
2458 char *tmpptr = NULL;
2459 struct minivm_account *vmu;
2461 struct ast_flags flags = { 0 };
2462 char *opts[OPT_ARG_ARRAY_SIZE];
2464 char *message = NULL;
2465 char *prompt = NULL;
2468 if (ast_strlen_zero(data)) {
2469 ast_log(LOG_ERROR, "MinivmAccmess needs at least two arguments: account and option\n");
2472 tmpptr = ast_strdupa((char *)data);
2475 ast_log(LOG_ERROR, "Out of memory\n");
2478 argc = ast_app_separate_args(tmpptr, ',', argv, ARRAY_LEN(argv));
2482 ast_log(LOG_ERROR, "MinivmAccmess needs at least two arguments: account and option\n");
2485 if (!error && strlen(argv[1]) > 1) {
2486 ast_log(LOG_ERROR, "MinivmAccmess can only handle one option at a time. Bad option string: %s\n", argv[1]);
2490 if (!error && ast_app_parse_options(minivm_accmess_options, &flags, opts, argv[1])) {
2491 ast_log(LOG_ERROR, "Can't parse option %s\n", argv[1]);
2496 pbx_builtin_setvar_helper(chan, "MVM_ACCMESS_STATUS", "FAILED");
2500 ast_copy_string(tmp, argv[0], sizeof(tmp));
2502 domain = strchr(tmp, '@');
2507 if (ast_strlen_zero(domain) || ast_strlen_zero(username)) {
2508 ast_log(LOG_ERROR, "Need username@domain as argument. Sorry. Argument 0 %s\n", argv[0]);
2509 pbx_builtin_setvar_helper(chan, "MVM_ACCMESS_STATUS", "FAILED");
2513 if(!(vmu = find_account(domain, username, TRUE))) {
2514 /* We could not find user, let's exit */
2515 ast_log(LOG_WARNING, "Could not allocate temporary memory for '%s@%s'\n", username, domain);
2516 pbx_builtin_setvar_helper(chan, "MVM_ACCMESS_STATUS", "FAILED");
2520 /* Answer channel if it's not already answered */
2521 if (ast_channel_state(chan) != AST_STATE_UP)
2524 /* Here's where the action is */
2525 if (ast_test_flag(&flags, OPT_BUSY_GREETING)) {
2527 prompt = "vm-rec-busy";
2528 } else if (ast_test_flag(&flags, OPT_UNAVAIL_GREETING)) {
2529 message = "unavailable";
2530 prompt = "vm-rec-unv";
2531 } else if (ast_test_flag(&flags, OPT_TEMP_GREETING)) {
2533 prompt = "vm-rec-temp";
2534 } else if (ast_test_flag(&flags, OPT_NAME_GREETING)) {
2536 prompt = "vm-rec-name";
2538 snprintf(filename,sizeof(filename), "%s%s/%s/%s", MVM_SPOOL_DIR, vmu->domain, vmu->username, message);
2539 /* Maybe we should check the result of play_record_review ? */
2540 play_record_review(chan, prompt, filename, global_maxgreet, default_vmformat, 0, vmu, &duration, NULL, NULL, FALSE);
2542 ast_debug(1, "Recorded new %s message in %s (duration %d)\n", message, filename, duration);
2544 if(ast_test_flag(vmu, MVM_ALLOCED))
2547 pbx_builtin_setvar_helper(chan, "MVM_ACCMESS_STATUS", "SUCCESS");
2549 /* Ok, we're ready to rock and roll. Return to dialplan */
2553 /*! \brief Append new mailbox to mailbox list from configuration file */
2554 static int create_vmaccount(char *name, struct ast_variable *var, int realtime)
2556 struct minivm_account *vmu;
2559 char accbuf[BUFSIZ];
2561 ast_debug(3, "Creating %s account for [%s]\n", realtime ? "realtime" : "static", name);
2563 ast_copy_string(accbuf, name, sizeof(accbuf));
2565 domain = strchr(accbuf, '@');
2570 if (ast_strlen_zero(domain)) {
2571 ast_log(LOG_ERROR, "No domain given for mini-voicemail account %s. Not configured.\n", name);
2575 ast_debug(3, "Creating static account for user %s domain %s\n", username, domain);
2577 /* Allocate user account */
2578 vmu = ast_calloc(1, sizeof(*vmu));
2582 ast_copy_string(vmu->domain, domain, sizeof(vmu->domain));
2583 ast_copy_string(vmu->username, username, sizeof(vmu->username));
2585 populate_defaults(vmu);
2587 ast_debug(3, "...Configuring account %s\n", name);
2590 ast_debug(3, "Configuring %s = \"%s\" for account %s\n", var->name, var->value, name);
2591 if (!strcasecmp(var->name, "serveremail")) {
2592 ast_copy_string(vmu->serveremail, var->value, sizeof(vmu->serveremail));
2593 } else if (!strcasecmp(var->name, "email")) {
2594 ast_copy_string(vmu->email, var->value, sizeof(vmu->email));
2595 } else if (!strcasecmp(var->name, "accountcode")) {
2596 ast_copy_string(vmu->accountcode, var->value, sizeof(vmu->accountcode));
2597 } else if (!strcasecmp(var->name, "pincode")) {
2598 ast_copy_string(vmu->pincode, var->value, sizeof(vmu->pincode));
2599 } else if (!strcasecmp(var->name, "domain")) {
2600 ast_copy_string(vmu->domain, var->value, sizeof(vmu->domain));
2601 } else if (!strcasecmp(var->name, "language")) {
2602 ast_copy_string(vmu->language, var->value, sizeof(vmu->language));
2603 } else if (!strcasecmp(var->name, "timezone")) {
2604 ast_copy_string(vmu->zonetag, var->value, sizeof(vmu->zonetag));
2605 } else if (!strcasecmp(var->name, "externnotify")) {
2606 ast_copy_string(vmu->externnotify, var->value, sizeof(vmu->externnotify));
2607 } else if (!strcasecmp(var->name, "etemplate")) {
2608 ast_copy_string(vmu->etemplate, var->value, sizeof(vmu->etemplate));
2609 } else if (!strcasecmp(var->name, "ptemplate")) {
2610 ast_copy_string(vmu->ptemplate, var->value, sizeof(vmu->ptemplate));
2611 } else if (!strcasecmp(var->name, "fullname")) {
2612 ast_copy_string(vmu->fullname, var->value, sizeof(vmu->fullname));
2613 } else if (!strcasecmp(var->name, "setvar")) {
2615 char *varname = ast_strdupa(var->value);
2616 struct ast_variable *tmpvar;
2618 if (varname && (varval = strchr(varname, '='))) {
2621 if ((tmpvar = ast_variable_new(varname, varval, ""))) {
2622 tmpvar->next = vmu->chanvars;
2623 vmu->chanvars = tmpvar;
2626 } else if (!strcasecmp(var->name, "pager")) {
2627 ast_copy_string(vmu->pager, var->value, sizeof(vmu->pager));
2628 } else if (!strcasecmp(var->name, "volgain")) {
2629 sscanf(var->value, "%30lf", &vmu->volgain);
2631 ast_log(LOG_ERROR, "Unknown configuration option for minivm account %s : %s\n", name, var->name);
2635 ast_debug(3, "...Linking account %s\n", name);
2637 AST_LIST_LOCK(&minivm_accounts);
2638 AST_LIST_INSERT_TAIL(&minivm_accounts, vmu, list);
2639 AST_LIST_UNLOCK(&minivm_accounts);
2641 global_stats.voicemailaccounts++;
2643 ast_debug(2, "MVM :: 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)" : "");
2647 /*! \brief Free Mini Voicemail timezone */
2648 static void free_zone(struct minivm_zone *z)
2653 /*! \brief Clear list of timezones */
2654 static void timezone_destroy_list(void)
2656 struct minivm_zone *this;
2658 AST_LIST_LOCK(&minivm_zones);
2659 while ((this = AST_LIST_REMOVE_HEAD(&minivm_zones, list)))
2662 AST_LIST_UNLOCK(&minivm_zones);
2665 /*! \brief Add time zone to memory list */
2666 static int timezone_add(const char *zonename, const char *config)
2668 struct minivm_zone *newzone;
2669 char *msg_format, *timezone_str;
2671 newzone = ast_calloc(1, sizeof(*newzone));
2672 if (newzone == NULL)
2675 msg_format = ast_strdupa(config);
2676 if (msg_format == NULL) {
2677 ast_log(LOG_WARNING, "Out of memory.\n");
2682 timezone_str = strsep(&msg_format, "|");
2684 ast_log(LOG_WARNING, "Invalid timezone definition : %s\n", zonename);
2689 ast_copy_string(newzone->name, zonename, sizeof(newzone->name));
2690 ast_copy_string(newzone->timezone, timezone_str, sizeof(newzone->timezone));
2691 ast_copy_string(newzone->msg_format, msg_format, sizeof(newzone->msg_format));
2693 AST_LIST_LOCK(&minivm_zones);
2694 AST_LIST_INSERT_TAIL(&minivm_zones, newzone, list);
2695 AST_LIST_UNLOCK(&minivm_zones);
2697 global_stats.timezones++;
2702 /*! \brief Read message template from file */
2703 static char *message_template_parse_filebody(const char *filename) {
2704 char buf[BUFSIZ * 6];
2705 char readbuf[BUFSIZ];
2706 char filenamebuf[BUFSIZ];
2712 if (ast_strlen_zero(filename))
2714 if (*filename == '/')
2715 ast_copy_string(filenamebuf, filename, sizeof(filenamebuf));
2717 snprintf(filenamebuf, sizeof(filenamebuf), "%s/%s", ast_config_AST_CONFIG_DIR, filename);
2719 if (!(fi = fopen(filenamebuf, "r"))) {
2720 ast_log(LOG_ERROR, "Can't read message template from file: %s\n", filenamebuf);
2724 while (fgets(readbuf, sizeof(readbuf), fi)) {
2726 if (writepos != buf) {
2727 *writepos = '\n'; /* Replace EOL with new line */
2730 ast_copy_string(writepos, readbuf, sizeof(buf) - (writepos - buf));
2731 writepos += strlen(readbuf) - 1;
2734 messagebody = ast_calloc(1, strlen(buf + 1));
2735 ast_copy_string(messagebody, buf, strlen(buf) + 1);
2736 ast_debug(4, "---> Size of allocation %d\n", (int) strlen(buf + 1) );
2737 ast_debug(4, "---> Done reading message template : \n%s\n---- END message template--- \n", messagebody);
2742 /*! \brief Parse emailbody template from configuration file */
2743 static char *message_template_parse_emailbody(const char *configuration)
2745 char *tmpread, *tmpwrite;
2746 char *emailbody = ast_strdup(configuration);
2748 /* substitute strings \t and \n into the apropriate characters */
2749 tmpread = tmpwrite = emailbody;
2750 while ((tmpwrite = strchr(tmpread,'\\'))) {
2751 int len = strlen("\n");
2752 switch (tmpwrite[1]) {
2754 memmove(tmpwrite + len, tmpwrite + 2, strlen(tmpwrite + 2) + 1);
2755 strncpy(tmpwrite, "\n", len);
2758 memmove(tmpwrite + len, tmpwrite + 2, strlen(tmpwrite + 2) + 1);
2759 strncpy(tmpwrite, "\t", len);
2762 ast_log(LOG_NOTICE, "Substitution routine does not support this character: %c\n", tmpwrite[1]);
2764 tmpread = tmpwrite + len;
2769 /*! \brief Apply general configuration options */
2770 static int apply_general_options(struct ast_variable *var)
2776 if (!strcmp(var->name, "mailcmd")) {
2777 ast_copy_string(global_mailcmd, var->value, sizeof(global_mailcmd)); /* User setting */
2778 } else if (!strcmp(var->name, "maxgreet")) {
2779 global_maxgreet = atoi(var->value);
2780 } else if (!strcmp(var->name, "maxsilence")) {
2781 global_maxsilence = atoi(var->value);
2782 if (global_maxsilence > 0)
2783 global_maxsilence *= 1000;
2784 } else if (!strcmp(var->name, "logfile")) {
2785 if (!ast_strlen_zero(var->value) ) {
2786 if(*(var->value) == '/')
2787 ast_copy_string(global_logfile, var->value, sizeof(global_logfile));
2789 snprintf(global_logfile, sizeof(global_logfile), "%s/%s", ast_config_AST_LOG_DIR, var->value);
2791 } else if (!strcmp(var->name, "externnotify")) {
2792 /* External voicemail notify application */
2793 ast_copy_string(global_externnotify, var->value, sizeof(global_externnotify));
2794 } else if (!strcmp(var->name, "silencetreshold")) {
2795 /* Silence treshold */
2796 global_silencethreshold = atoi(var->value);
2797 } else if (!strcmp(var->name, "maxmessage")) {
2799 if (sscanf(var->value, "%30d", &x) == 1) {
2800 global_vmmaxmessage = x;
2803 ast_log(LOG_WARNING, "Invalid max message time length\n");
2805 } else if (!strcmp(var->name, "minmessage")) {
2807 if (sscanf(var->value, "%30d", &x) == 1) {
2808 global_vmminmessage = x;
2809 if (global_maxsilence <= global_vmminmessage)
2810 ast_log(LOG_WARNING, "maxsilence should be less than minmessage or you may get empty messages\n");
2813 ast_log(LOG_WARNING, "Invalid min message time length\n");
2815 } else if (!strcmp(var->name, "format")) {
2816 ast_copy_string(default_vmformat, var->value, sizeof(default_vmformat));
2817 } else if (!strcmp(var->name, "review")) {
2818 ast_set2_flag((&globalflags), ast_true(var->value), MVM_REVIEW);
2819 } else if (!strcmp(var->name, "operator")) {
2820 ast_set2_flag((&globalflags), ast_true(var->value), MVM_OPERATOR);
2827 /*! \brief Load minivoicemail configuration */
2828 static int load_config(int reload)
2830 struct ast_config *cfg;
2831 struct ast_variable *var;
2833 const char *chanvar;
2835 struct minivm_template *template;
2836 struct ast_flags config_flags = { reload ? CONFIG_FLAG_FILEUNCHANGED : 0 };
2838 cfg = ast_config_load(VOICEMAIL_CONFIG, config_flags);
2839 if (cfg == CONFIG_STATUS_FILEUNCHANGED) {
2841 } else if (cfg == CONFIG_STATUS_FILEINVALID) {
2842 ast_log(LOG_ERROR, "Config file " VOICEMAIL_CONFIG " is in an invalid format. Aborting.\n");
2846 ast_mutex_lock(&minivmlock);
2848 /* Destroy lists to reconfigure */
2849 message_destroy_list(); /* Destroy list of voicemail message templates */
2850 timezone_destroy_list(); /* Destroy list of timezones */
2851 vmaccounts_destroy_list(); /* Destroy list of voicemail accounts */
2852 ast_debug(2, "Destroyed memory objects...\n");
2854 /* First, set some default settings */
2855 global_externnotify[0] = '\0';
2856 global_logfile[0] = '\0';
2857 global_vmmaxmessage = 2000;
2858 global_maxgreet = 2000;
2859 global_vmminmessage = 0;
2860 strcpy(global_mailcmd, SENDMAIL);
2861 global_maxsilence = 0;
2862 global_saydurationminfo = 2;
2863 ast_copy_string(default_vmformat, "wav", sizeof(default_vmformat));
2864 ast_set2_flag((&globalflags), FALSE, MVM_REVIEW);
2865 ast_set2_flag((&globalflags), FALSE, MVM_OPERATOR);
2866 /* Reset statistics */
2867 memset(&global_stats, 0, sizeof(global_stats));
2868 global_stats.reset = ast_tvnow();
2870 global_silencethreshold = ast_dsp_get_threshold_from_settings(THRESHOLD_SILENCE);
2872 /* Make sure we could load configuration file */
2874 ast_log(LOG_WARNING, "Failed to load configuration file. Module activated with default settings.\n");
2875 ast_mutex_unlock(&minivmlock);
2879 ast_debug(2, "Loaded configuration file, now parsing\n");
2881 /* General settings */
2883 cat = ast_category_browse(cfg, NULL);
2885 ast_debug(3, "Found configuration section [%s]\n", cat);
2886 if (!strcasecmp(cat, "general")) {
2887 /* Nothing right now */
2888 error += apply_general_options(ast_variable_browse(cfg, cat));
2889 } else if (!strncasecmp(cat, "template-", 9)) {
2891 char *name = cat + 9;
2893 /* Now build and link template to list */
2894 error += message_template_build(name, ast_variable_browse(cfg, cat));
2896 var = ast_variable_browse(cfg, cat);
2897 if (!strcasecmp(cat, "zonemessages")) {
2898 /* Timezones in this context */
2900 timezone_add(var->name, var->value);
2904 /* Create mailbox from this */
2905 error += create_vmaccount(cat, var, FALSE);
2908 /* Find next section in configuration file */
2909 cat = ast_category_browse(cfg, cat);
2912 /* Configure the default email template */
2913 message_template_build("email-default", NULL);
2914 template = message_template_find("email-default");
2916 /* Load date format config for voicemail mail */
2917 if ((chanvar = ast_variable_retrieve(cfg, "general", "emaildateformat")))
2918 ast_copy_string(template->dateformat, chanvar, sizeof(template->dateformat));
2919 if ((chanvar = ast_variable_retrieve(cfg, "general", "emailfromstring")))
2920 ast_copy_string(template->fromaddress, chanvar, sizeof(template->fromaddress));
2921 if ((chanvar = ast_variable_retrieve(cfg, "general", "emailaaddress")))
2922 ast_copy_string(template->serveremail, chanvar, sizeof(template->serveremail));
2923 if ((chanvar = ast_variable_retrieve(cfg, "general", "emailcharset")))
2924 ast_copy_string(template->charset, chanvar, sizeof(template->charset));
2925 if ((chanvar = ast_variable_retrieve(cfg, "general", "emailsubject")))
2926 ast_copy_string(template->subject, chanvar, sizeof(template->subject));
2927 if ((chanvar = ast_variable_retrieve(cfg, "general", "emailbody")))
2928 template->body = message_template_parse_emailbody(chanvar);
2929 template->attachment = TRUE;
2931 message_template_build("pager-default", NULL);
2932 template = message_template_find("pager-default");
2933 if ((chanvar = ast_variable_retrieve(cfg, "general", "pagerfromstring")))
2934 ast_copy_string(template->fromaddress, chanvar, sizeof(template->fromaddress));
2935 if ((chanvar = ast_variable_retrieve(cfg, "general", "pageraddress")))
2936 ast_copy_string(template->serveremail, chanvar, sizeof(template->serveremail));
2937 if ((chanvar = ast_variable_retrieve(cfg, "general", "pagercharset")))
2938 ast_copy_string(template->charset, chanvar, sizeof(template->charset));
2939 if ((chanvar = ast_variable_retrieve(cfg, "general", "pagersubject")))
2940 ast_copy_string(template->subject, chanvar,sizeof(template->subject));
2941 if ((chanvar = ast_variable_retrieve(cfg, "general", "pagerbody")))
2942 template->body = message_template_parse_emailbody(chanvar);
2943 template->attachment = FALSE;
2946 ast_log(LOG_ERROR, "--- A total of %d errors found in mini-voicemail configuration\n", error);
2948 ast_mutex_unlock(&minivmlock);
2949 ast_config_destroy(cfg);
2951 /* Close log file if it's open and disabled */
2953 fclose(minivmlogfile);
2955 /* Open log file if it's enabled */
2956 if(!ast_strlen_zero(global_logfile)) {
2957 minivmlogfile = fopen(global_logfile, "a");
2959 ast_log(LOG_ERROR, "Failed to open minivm log file %s : %s\n", global_logfile, strerror(errno));
2961 ast_debug(3, "Opened log file %s \n", global_logfile);
2967 /*! \brief CLI routine for listing templates */
2968 static char *handle_minivm_list_templates(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
2970 struct minivm_template *this;
2971 #define HVLT_OUTPUT_FORMAT "%-15s %-10s %-10s %-15.15s %-50s\n"
2976 e->command = "minivm list templates";
2978 "Usage: minivm list templates\n"
2979 " Lists message templates for e-mail, paging and IM\n";
2986 return CLI_SHOWUSAGE;
2988 AST_LIST_LOCK(&message_templates);
2989 if (AST_LIST_EMPTY(&message_templates)) {
2990 ast_cli(a->fd, "There are no message templates defined\n");
2991 AST_LIST_UNLOCK(&message_templates);
2994 ast_cli(a->fd, HVLT_OUTPUT_FORMAT, "Template name", "Charset", "Locale", "Attach media", "Subject");
2995 ast_cli(a->fd, HVLT_OUTPUT_FORMAT, "-------------", "-------", "------", "------------", "-------");
2996 AST_LIST_TRAVERSE(&message_templates, this, list) {
2997 ast_cli(a->fd, HVLT_OUTPUT_FORMAT, this->name,
2998 this->charset ? this->charset : "-",
2999 this->locale ? this->locale : "-",
3000 this->attachment ? "Yes" : "No",
3001 this->subject ? this->subject : "-");
3004 AST_LIST_UNLOCK(&message_templates);
3005 ast_cli(a->fd, "\n * Total: %d minivoicemail message templates\n", count);
3009 static char *complete_minivm_show_users(const char *line, const char *word, int pos, int state)
3013 struct minivm_account *vmu;
3014 const char *domain = "";
3016 /* 0 - voicemail; 1 - list; 2 - accounts; 3 - for; 4 - <domain> */
3020 return (state == 0) ? ast_strdup("for") : NULL;
3021 wordlen = strlen(word);
3022 AST_LIST_TRAVERSE(&minivm_accounts, vmu, list) {
3023 if (!strncasecmp(word, vmu->domain, wordlen)) {
3024 if (domain && strcmp(domain, vmu->domain) && ++which > state)
3025 return ast_strdup(vmu->domain);
3026 /* ignore repeated domains ? */
3027 domain = vmu->domain;
3033 /*! \brief CLI command to list voicemail accounts */
3034 static char *handle_minivm_show_users(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
3036 struct minivm_account *vmu;
3037 #define HMSU_OUTPUT_FORMAT "%-23s %-15s %-15s %-10s %-10s %-50s\n"
3042 e->command = "minivm list accounts";
3044 "Usage: minivm list accounts\n"
3045 " Lists all mailboxes currently set up\n";
3048 return complete_minivm_show_users(a->line, a->word, a->pos, a->n);
3051 if ((a->argc < 3) || (a->argc > 5) || (a->argc == 4))
3052 return CLI_SHOWUSAGE;
3053 if ((a->argc == 5) && strcmp(a->argv[3],"for"))
3054 return CLI_SHOWUSAGE;
3056 AST_LIST_LOCK(&minivm_accounts);
3057 if (AST_LIST_EMPTY(&minivm_accounts)) {
3058 ast_cli(a->fd, "There are no voicemail users currently defined\n");
3059 AST_LIST_UNLOCK(&minivm_accounts);
3062 ast_cli(a->fd, HMSU_OUTPUT_FORMAT, "User", "E-Template", "P-template", "Zone", "Format", "Full name");
3063 ast_cli(a->fd, HMSU_OUTPUT_FORMAT, "----", "----------", "----------", "----", "------", "---------");
3064 AST_LIST_TRAVERSE(&minivm_accounts, vmu, list) {
3066 if ((a->argc == 3) || ((a->argc == 5) && !strcmp(a->argv[4], vmu->domain))) {
3068 snprintf(tmp, sizeof(tmp), "%s@%s", vmu->username, vmu->domain);
3069 ast_cli(a->fd, HMSU_OUTPUT_FORMAT, tmp, vmu->etemplate ? vmu->etemplate : "-",
3070 vmu->ptemplate ? vmu->ptemplate : "-",
3071 vmu->zonetag ? vmu->zonetag : "-",
3072 vmu->attachfmt ? vmu->attachfmt : "-",
3076 AST_LIST_UNLOCK(&minivm_accounts);
3077 ast_cli(a->fd, "\n * Total: %d minivoicemail accounts\n", count);
3081 /*! \brief Show a list of voicemail zones in the CLI */
3082 static char *handle_minivm_show_zones(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)