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
144 #include "asterisk.h"
146 ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
149 #include <sys/time.h>
150 #include <sys/stat.h>
151 #include <sys/mman.h>
157 #include "asterisk/paths.h" /* use various paths */
158 #include "asterisk/lock.h"
159 #include "asterisk/file.h"
160 #include "asterisk/channel.h"
161 #include "asterisk/pbx.h"
162 #include "asterisk/config.h"
163 #include "asterisk/say.h"
164 #include "asterisk/module.h"
165 #include "asterisk/app.h"
166 #include "asterisk/manager.h"
167 #include "asterisk/dsp.h"
168 #include "asterisk/localtime.h"
169 #include "asterisk/cli.h"
170 #include "asterisk/utils.h"
171 #include "asterisk/linkedlists.h"
172 #include "asterisk/callerid.h"
173 #include "asterisk/event.h"
176 <application name="MinivmRecord" language="en_US">
178 Receive Mini-Voicemail and forward via e-mail.
181 <parameter name="mailbox" required="true" argsep="@">
182 <argument name="username" required="true">
183 <para>Voicemail username</para>
185 <argument name="domain" required="true">
186 <para>Voicemail domain</para>
189 <parameter name="options" required="false">
192 <para>Jump to the <literal>o</literal> extension in the current dialplan context.</para>
195 <para>Jump to the <literal>a</literal> extension in the current dialplan context.</para>
198 <argument name="gain">
199 <para>Amount of gain to use</para>
201 <para>Use the specified amount of gain when recording the voicemail message.
202 The units are whole-number decibels (dB).</para>
208 <para>This application is part of the Mini-Voicemail system, configured in <filename>minivm.conf</filename></para>
209 <para>MiniVM records audio file in configured format and forwards message to e-mail and pager.</para>
210 <para>If there's no user account for that address, a temporary account will be used with default options.</para>
211 <para>The recorded file name and path will be stored in <variable>MVM_FILENAME</variable> and the duration
212 of the message will be stored in <variable>MVM_DURATION</variable></para>
213 <note><para>If the caller hangs up after the recording, the only way to send the message and clean up is to
214 execute in the <literal>h</literal> extension. The application will exit if any of the following DTMF digits
215 are received and the requested extension exist in the current context.</para></note>
217 <variable name="MVM_RECORD_STATUS">
218 <para>This is the status of the record operation</para>
219 <value name="SUCCESS" />
220 <value name="USEREXIT" />
221 <value name="FAILED" />
226 <application name="MinivmGreet" language="en_US">
228 Play Mini-Voicemail prompts.
231 <parameter name="mailbox" required="true" argsep="@">
232 <argument name="username" required="true">
233 <para>Voicemail username</para>
235 <argument name="domain" required="true">
236 <para>Voicemail domain</para>
239 <parameter name="options" required="false">
242 <para>Play the <literal>busy</literal> greeting to the calling party.</para>
245 <para>Skip the playback of instructions for leaving a message to the calling party.</para>
248 <para>Play the <literal>unavailable</literal> greeting.</para>
254 <para>This application is part of the Mini-Voicemail system, configured in minivm.conf.</para>
255 <para>MinivmGreet() plays default prompts or user specific prompts for an account.</para>
256 <para>Busy and unavailable messages can be choosen, but will be overridden if a temporary
257 message exists for the account.</para>
259 <variable name="MVM_GREET_STATUS">
260 <para>This is the status of the greeting playback.</para>
261 <value name="SUCCESS" />
262 <value name="USEREXIT" />
263 <value name="FAILED" />
268 <application name="MinivmNotify" language="en_US">
270 Notify voicemail owner about new messages.
273 <parameter name="mailbox" required="true" argsep="@">
274 <argument name="username" required="true">
275 <para>Voicemail username</para>
277 <argument name="domain" required="true">
278 <para>Voicemail domain</para>
281 <parameter name="options" required="false">
283 <option name="template">
284 <para>E-mail template to use for voicemail notification</para>
290 <para>This application is part of the Mini-Voicemail system, configured in minivm.conf.</para>
291 <para>MiniVMnotify forwards messages about new voicemail to e-mail and pager. If there's no user
292 account for that address, a temporary account will be used with default options (set in
293 <filename>minivm.conf</filename>).</para>
294 <para>If the channel variable <variable>MVM_COUNTER</variable> is set, this will be used in the message
295 file name and available in the template for the message.</para>
296 <para>If no template is given, the default email template will be used to send email and default pager
297 template to send paging message (if the user account is configured with a paging address.</para>
299 <variable name="MVM_NOTIFY_STATUS">
300 <para>This is the status of the notification attempt</para>
301 <value name="SUCCESS" />
302 <value name="FAILED" />
307 <application name="MinivmDelete" language="en_US">
309 Delete Mini-Voicemail voicemail messages.
312 <parameter name="filename" required="true">
313 <para>File to delete</para>
317 <para>This application is part of the Mini-Voicemail system, configured in <filename>minivm.conf</filename>.</para>
318 <para>It deletes voicemail file set in MVM_FILENAME or given filename.</para>
320 <variable name="MVM_DELETE_STATUS">
321 <para>This is the status of the delete operation.</para>
322 <value name="SUCCESS" />
323 <value name="FAILED" />
329 <application name="MinivmAccMess" language="en_US">
331 Record account specific messages.
334 <parameter name="mailbox" required="true" argsep="@">
335 <argument name="username" required="true">
336 <para>Voicemail username</para>
338 <argument name="domain" required="true">
339 <para>Voicemail domain</para>
342 <parameter name="options" required="false">
345 <para>Record the <literal>unavailable</literal> greeting.</para>
348 <para>Record the <literal>busy</literal> greeting.</para>
351 <para>Record the temporary greeting.</para>
354 <para>Account name.</para>
360 <para>This application is part of the Mini-Voicemail system, configured in <filename>minivm.conf</filename>.</para>
361 <para>Use this application to record account specific audio/video messages for busy, unavailable
362 and temporary messages.</para>
363 <para>Account specific directories will be created if they do not exist.</para>
365 <variable name="MVM_ACCMESS_STATUS">
366 <para>This is the result of the attempt to record the specified greeting.</para>
367 <para><literal>FAILED</literal> is set if the file can't be created.</para>
368 <value name="SUCCESS" />
369 <value name="FAILED" />
374 <application name="MinivmMWI" language="en_US">
376 Send Message Waiting Notification to subscriber(s) of mailbox.
379 <parameter name="mailbox" required="true" argsep="@">
380 <argument name="username" required="true">
381 <para>Voicemail username</para>
383 <argument name="domain" required="true">
384 <para>Voicemail domain</para>
387 <parameter name="urgent" required="true">
388 <para>Number of urgent messages in mailbox.</para>
390 <parameter name="new" required="true">
391 <para>Number of new messages in mailbox.</para>
393 <parameter name="old" required="true">
394 <para>Number of old messages in mailbox.</para>
398 <para>This application is part of the Mini-Voicemail system, configured in <filename>minivm.conf</filename>.</para>
399 <para>MinivmMWI is used to send message waiting indication to any devices whose channels have
400 subscribed to the mailbox passed in the first parameter.</para>
403 <function name="MINIVMCOUNTER" language="en_US">
405 Reads or sets counters for MiniVoicemail message.
408 <parameter name="account" required="true">
409 <para>If account is given and it exists, the counter is specific for the account.</para>
410 <para>If account is a domain and the domain directory exists, counters are specific for a domain.</para>
412 <parameter name="name" required="true">
413 <para>The name of the counter is a string, up to 10 characters.</para>
415 <parameter name="operand">
416 <para>The counters never goes below zero. Valid operands for changing the value of a counter when assigning a value are:</para>
418 <enum name="i"><para>Increment by value.</para></enum>
419 <enum name="d"><para>Decrement by value.</para></enum>
420 <enum name="s"><para>Set to value.</para></enum>
425 <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>
428 <ref type="application">MinivmRecord</ref>
429 <ref type="application">MinivmGreet</ref>
430 <ref type="application">MinivmNotify</ref>
431 <ref type="application">MinivmDelete</ref>
432 <ref type="application">MinivmAccMess</ref>
433 <ref type="application">MinivmMWI</ref>
434 <ref type="function">MINIVMACCOUNT</ref>
437 <function name="MINIVMACCOUNT" language="en_US">
439 Gets MiniVoicemail account information.
442 <parameter name="account" required="true" />
443 <parameter name="item" required="true">
444 <para>Valid items are:</para>
447 <para>Path to account mailbox (if account exists, otherwise temporary mailbox).</para>
449 <enum name="hasaccount">
450 <para>1 is static Minivm account exists, 0 otherwise.</para>
452 <enum name="fullname">
453 <para>Full name of account owner.</para>
456 <para>Email address used for account.</para>
458 <enum name="etemplate">
459 <para>Email template for account (default template if none is configured).</para>
461 <enum name="ptemplate">
462 <para>Pager template for account (default template if none is configured).</para>
464 <enum name="accountcode">
465 <para>Account code for the voicemail account.</para>
467 <enum name="pincode">
468 <para>Pin code for voicemail account.</para>
470 <enum name="timezone">
471 <para>Time zone for voicemail account.</para>
473 <enum name="language">
474 <para>Language for voicemail account.</para>
476 <enum name="<channel variable name>">
477 <para>Channel variable value (set in configuration for account).</para>
486 <ref type="application">MinivmRecord</ref>
487 <ref type="application">MinivmGreet</ref>
488 <ref type="application">MinivmNotify</ref>
489 <ref type="application">MinivmDelete</ref>
490 <ref type="application">MinivmAccMess</ref>
491 <ref type="application">MinivmMWI</ref>
492 <ref type="function">MINIVMCOUNTER</ref>
506 #define MVM_REVIEW (1 << 0) /*!< Review message */
507 #define MVM_OPERATOR (1 << 1) /*!< Operator exit during voicemail recording */
508 #define MVM_REALTIME (1 << 2) /*!< This user is a realtime account */
509 #define MVM_SVMAIL (1 << 3)
510 #define MVM_ENVELOPE (1 << 4)
511 #define MVM_PBXSKIP (1 << 9)
512 #define MVM_ALLOCED (1 << 13)
514 /*! \brief Default mail command to mail voicemail. Change it with the
515 mailcmd= command in voicemail.conf */
516 #define SENDMAIL "/usr/sbin/sendmail -t"
518 #define SOUND_INTRO "vm-intro"
519 #define B64_BASEMAXINLINE 256 /*!< Buffer size for Base 64 attachment encoding */
520 #define B64_BASELINELEN 72 /*!< Line length for Base 64 endoded messages */
523 #define MAX_DATETIME_FORMAT 512
524 #define MAX_NUM_CID_CONTEXTS 10
526 #define ERROR_LOCK_PATH -100
527 #define VOICEMAIL_DIR_MODE 0700
529 #define VOICEMAIL_CONFIG "minivm.conf"
530 #define ASTERISK_USERNAME "asterisk" /*!< Default username for sending mail is asterisk\@localhost */
532 /*! \brief Message types for notification */
533 enum mvm_messagetype {
536 /* For trunk: MVM_MESSAGE_JABBER, */
539 static char MVM_SPOOL_DIR[PATH_MAX];
541 /* Module declarations */
542 static char *app_minivm_record = "MinivmRecord"; /* Leave a message */
543 static char *app_minivm_greet = "MinivmGreet"; /* Play voicemail prompts */
544 static char *app_minivm_notify = "MinivmNotify"; /* Notify about voicemail by using one of several methods */
545 static char *app_minivm_delete = "MinivmDelete"; /* Notify about voicemail by using one of several methods */
546 static char *app_minivm_accmess = "MinivmAccMess"; /* Record personal voicemail messages */
547 static char *app_minivm_mwi = "MinivmMWI";
551 enum minivm_option_flags {
552 OPT_SILENT = (1 << 0),
553 OPT_BUSY_GREETING = (1 << 1),
554 OPT_UNAVAIL_GREETING = (1 << 2),
555 OPT_TEMP_GREETING = (1 << 3),
556 OPT_NAME_GREETING = (1 << 4),
557 OPT_RECORDGAIN = (1 << 5),
560 enum minivm_option_args {
561 OPT_ARG_RECORDGAIN = 0,
562 OPT_ARG_ARRAY_SIZE = 1,
565 AST_APP_OPTIONS(minivm_app_options, {
566 AST_APP_OPTION('s', OPT_SILENT),
567 AST_APP_OPTION('b', OPT_BUSY_GREETING),
568 AST_APP_OPTION('u', OPT_UNAVAIL_GREETING),
569 AST_APP_OPTION_ARG('g', OPT_RECORDGAIN, OPT_ARG_RECORDGAIN),
572 AST_APP_OPTIONS(minivm_accmess_options, {
573 AST_APP_OPTION('b', OPT_BUSY_GREETING),
574 AST_APP_OPTION('u', OPT_UNAVAIL_GREETING),
575 AST_APP_OPTION('t', OPT_TEMP_GREETING),
576 AST_APP_OPTION('n', OPT_NAME_GREETING),
580 * \brief Structure for linked list of Mini-Voicemail users: \ref minivm_accounts */
581 struct minivm_account {
582 char username[AST_MAX_CONTEXT]; /*!< Mailbox username */
583 char domain[AST_MAX_CONTEXT]; /*!< Voicemail domain */
585 char pincode[10]; /*!< Secret pin code, numbers only */
586 char fullname[120]; /*!< Full name, for directory app */
587 char email[80]; /*!< E-mail address - override */
588 char pager[80]; /*!< E-mail address to pager (no attachment) */
589 char accountcode[AST_MAX_ACCOUNT_CODE]; /*!< Voicemail account account code */
590 char serveremail[80]; /*!< From: Mail address */
591 char externnotify[160]; /*!< Configurable notification command */
592 char language[MAX_LANGUAGE]; /*!< Config: Language setting */
593 char zonetag[80]; /*!< Time zone */
594 char uniqueid[20]; /*!< Unique integer identifier */
595 char exit[80]; /*!< Options for exiting from voicemail() */
596 char attachfmt[80]; /*!< Format for voicemail audio file attachment */
597 char etemplate[80]; /*!< Pager template */
598 char ptemplate[80]; /*!< Voicemail format */
599 unsigned int flags; /*!< MVM_ flags */
600 struct ast_variable *chanvars; /*!< Variables for e-mail template */
601 double volgain; /*!< Volume gain for voicemails sent via e-mail */
602 AST_LIST_ENTRY(minivm_account) list;
606 * \brief The list of e-mail accounts */
607 static AST_LIST_HEAD_STATIC(minivm_accounts, minivm_account);
610 * \brief Linked list of e-mail templates in various languages
611 * These are used as templates for e-mails, pager messages and jabber messages
612 * \ref message_templates
614 struct minivm_template {
615 char name[80]; /*!< Template name */
616 char *body; /*!< Body of this template */
617 char fromaddress[100]; /*!< Who's sending the e-mail? */
618 char serveremail[80]; /*!< From: Mail address */
619 char subject[100]; /*!< Subject line */
620 char charset[32]; /*!< Default character set for this template */
621 char locale[20]; /*!< Locale for setlocale() */
622 char dateformat[80]; /*!< Date format to use in this attachment */
623 int attachment; /*!< Attachment of media yes/no - no for pager messages */
624 AST_LIST_ENTRY(minivm_template) list; /*!< List mechanics */
627 /*! \brief The list of e-mail templates */
628 static AST_LIST_HEAD_STATIC(message_templates, minivm_template);
630 /*! \brief Options for leaving voicemail with the voicemail() application */
631 struct leave_vm_options {
633 signed char record_gain;
636 /*! \brief Structure for base64 encoding */
642 unsigned char iobuf[B64_BASEMAXINLINE];
645 /*! \brief Voicemail time zones */
647 char name[80]; /*!< Name of this time zone */
648 char timezone[80]; /*!< Timezone definition */
649 char msg_format[BUFSIZ]; /*!< Not used in minivm ...yet */
650 AST_LIST_ENTRY(minivm_zone) list; /*!< List mechanics */
653 /*! \brief The list of e-mail time zones */
654 static AST_LIST_HEAD_STATIC(minivm_zones, minivm_zone);
656 /*! \brief Structure for gathering statistics */
657 struct minivm_stats {
658 int voicemailaccounts; /*!< Number of static accounts */
659 int timezones; /*!< Number of time zones */
660 int templates; /*!< Number of templates */
662 struct timeval reset; /*!< Time for last reset */
663 int receivedmessages; /*!< Number of received messages since reset */
664 struct timeval lastreceived; /*!< Time for last voicemail sent */
667 /*! \brief Statistics for voicemail */
668 static struct minivm_stats global_stats;
670 AST_MUTEX_DEFINE_STATIC(minivmlock); /*!< Lock to protect voicemail system */
671 AST_MUTEX_DEFINE_STATIC(minivmloglock); /*!< Lock to protect voicemail system log file */
673 static FILE *minivmlogfile; /*!< The minivm log file */
675 static int global_vmminmessage; /*!< Minimum duration of messages */
676 static int global_vmmaxmessage; /*!< Maximum duration of message */
677 static int global_maxsilence; /*!< Maximum silence during recording */
678 static int global_maxgreet; /*!< Maximum length of prompts */
679 static int global_silencethreshold = 128;
680 static char global_mailcmd[160]; /*!< Configurable mail cmd */
681 static char global_externnotify[160]; /*!< External notification application */
682 static char global_logfile[PATH_MAX]; /*!< Global log file for messages */
683 static char default_vmformat[80];
685 static struct ast_flags globalflags = {0}; /*!< Global voicemail flags */
686 static int global_saydurationminfo;
688 static double global_volgain; /*!< Volume gain for voicmemail via e-mail */
691 * \brief Default dateformat, can be overridden in configuration file */
692 #define DEFAULT_DATEFORMAT "%A, %B %d, %Y at %r"
693 #define DEFAULT_CHARSET "ISO-8859-1"
695 /* Forward declarations */
696 static char *message_template_parse_filebody(const char *filename);
697 static char *message_template_parse_emailbody(const char *body);
698 static int create_vmaccount(char *name, struct ast_variable *var, int realtime);
699 static struct minivm_account *find_user_realtime(const char *domain, const char *username);
700 static char *handle_minivm_reload(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a);
703 * \brief Create message template */
704 static struct minivm_template *message_template_create(const char *name)
706 struct minivm_template *template;
708 template = ast_calloc(1, sizeof(*template));
712 /* Set some defaults for templates */
713 ast_copy_string(template->name, name, sizeof(template->name));
714 ast_copy_string(template->dateformat, DEFAULT_DATEFORMAT, sizeof(template->dateformat));
715 ast_copy_string(template->charset, DEFAULT_CHARSET, sizeof(template->charset));
716 ast_copy_string(template->subject, "New message in mailbox ${MVM_USERNAME}@${MVM_DOMAIN}", sizeof(template->subject));
717 template->attachment = TRUE;
723 * \brief Release memory allocated by message template */
724 static void message_template_free(struct minivm_template *template)
727 ast_free(template->body);
733 * \brief Build message template from configuration */
734 static int message_template_build(const char *name, struct ast_variable *var)
736 struct minivm_template *template;
739 template = message_template_create(name);
741 ast_log(LOG_ERROR, "Out of memory, can't allocate message template object %s.\n", name);
746 ast_debug(3, "Configuring template option %s = \"%s\" for template %s\n", var->name, var->value, name);
747 if (!strcasecmp(var->name, "fromaddress")) {
748 ast_copy_string(template->fromaddress, var->value, sizeof(template->fromaddress));
749 } else if (!strcasecmp(var->name, "fromemail")) {
750 ast_copy_string(template->serveremail, var->value, sizeof(template->serveremail));
751 } else if (!strcasecmp(var->name, "subject")) {
752 ast_copy_string(template->subject, var->value, sizeof(template->subject));
753 } else if (!strcasecmp(var->name, "locale")) {
754 ast_copy_string(template->locale, var->value, sizeof(template->locale));
755 } else if (!strcasecmp(var->name, "attachmedia")) {
756 template->attachment = ast_true(var->value);
757 } else if (!strcasecmp(var->name, "dateformat")) {
758 ast_copy_string(template->dateformat, var->value, sizeof(template->dateformat));
759 } else if (!strcasecmp(var->name, "charset")) {
760 ast_copy_string(template->charset, var->value, sizeof(template->charset));
761 } else if (!strcasecmp(var->name, "templatefile")) {
763 ast_free(template->body);
764 template->body = message_template_parse_filebody(var->value);
765 if (!template->body) {
766 ast_log(LOG_ERROR, "Error reading message body definition file %s\n", var->value);
769 } else if (!strcasecmp(var->name, "messagebody")) {
771 ast_free(template->body);
772 template->body = message_template_parse_emailbody(var->value);
773 if (!template->body) {
774 ast_log(LOG_ERROR, "Error parsing message body definition:\n %s\n", var->value);
778 ast_log(LOG_ERROR, "Unknown message template configuration option \"%s=%s\"\n", var->name, var->value);
784 ast_log(LOG_ERROR, "-- %d errors found parsing message template definition %s\n", error, name);
786 AST_LIST_LOCK(&message_templates);
787 AST_LIST_INSERT_TAIL(&message_templates, template, list);
788 AST_LIST_UNLOCK(&message_templates);
790 global_stats.templates++;
796 * \brief Find named template */
797 static struct minivm_template *message_template_find(const char *name)
799 struct minivm_template *this, *res = NULL;
801 if (ast_strlen_zero(name))
804 AST_LIST_LOCK(&message_templates);
805 AST_LIST_TRAVERSE(&message_templates, this, list) {
806 if (!strcasecmp(this->name, name)) {
811 AST_LIST_UNLOCK(&message_templates);
818 * \brief Clear list of templates */
819 static void message_destroy_list(void)
821 struct minivm_template *this;
822 AST_LIST_LOCK(&message_templates);
823 while ((this = AST_LIST_REMOVE_HEAD(&message_templates, list))) {
824 message_template_free(this);
827 AST_LIST_UNLOCK(&message_templates);
831 * \brief read buffer from file (base64 conversion) */
832 static int b64_inbuf(struct b64_baseio *bio, FILE *fi)
839 if ((l = fread(bio->iobuf, 1, B64_BASEMAXINLINE,fi)) <= 0) {
854 * \brief read character from file to buffer (base64 conversion) */
855 static int b64_inchar(struct b64_baseio *bio, FILE *fi)
857 if (bio->iocp >= bio->iolen) {
858 if (!b64_inbuf(bio, fi))
862 return bio->iobuf[bio->iocp++];
866 * \brief write buffer to file (base64 conversion) */
867 static int b64_ochar(struct b64_baseio *bio, int c, FILE *so)
869 if (bio->linelength >= B64_BASELINELEN) {
870 if (fputs(EOL,so) == EOF)
876 if (putc(((unsigned char) c), so) == EOF)
885 * \brief Encode file to base64 encoding for email attachment (base64 conversion) */
886 static int base_encode(char *filename, FILE *so)
888 unsigned char dtable[B64_BASEMAXINLINE];
891 struct b64_baseio bio;
893 memset(&bio, 0, sizeof(bio));
894 bio.iocp = B64_BASEMAXINLINE;
896 if (!(fi = fopen(filename, "rb"))) {
897 ast_log(LOG_WARNING, "Failed to open file: %s: %s\n", filename, strerror(errno));
901 for (i= 0; i<9; i++) {
905 dtable[26+i+9]= 'j'+i;
907 for (i= 0; i < 8; i++) {
909 dtable[26+i+18]= 's'+i;
911 for (i= 0; i < 10; i++) {
918 unsigned char igroup[3], ogroup[4];
921 igroup[0]= igroup[1]= igroup[2]= 0;
923 for (n= 0; n < 3; n++) {
924 if ((c = b64_inchar(&bio, fi)) == EOF) {
928 igroup[n]= (unsigned char)c;
932 ogroup[0]= dtable[igroup[0]>>2];
933 ogroup[1]= dtable[((igroup[0]&3)<<4) | (igroup[1]>>4)];
934 ogroup[2]= dtable[((igroup[1]&0xF)<<2) | (igroup[2]>>6)];
935 ogroup[3]= dtable[igroup[2]&0x3F];
945 b64_ochar(&bio, ogroup[i], so);
949 /* Put end of line - line feed */
950 if (fputs(EOL, so) == EOF)
958 static int get_date(char *s, int len)
961 struct timeval now = ast_tvnow();
963 ast_localtime(&now, &tm, NULL);
964 return ast_strftime(s, len, "%a %b %e %r %Z %Y", &tm);
969 * \brief Free user structure - if it's allocated */
970 static void free_user(struct minivm_account *vmu)
973 ast_variables_destroy(vmu->chanvars);
980 * \brief Prepare for voicemail template by adding channel variables
983 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)
986 struct ast_variable *var;
989 ast_log(LOG_ERROR, "No allocated channel, giving up...\n");
993 for (var = vmu->chanvars ; var ; var = var->next) {
994 pbx_builtin_setvar_helper(channel, var->name, var->value);
997 /* Prepare variables for substition in email body and subject */
998 pbx_builtin_setvar_helper(channel, "MVM_NAME", vmu->fullname);
999 pbx_builtin_setvar_helper(channel, "MVM_DUR", dur);
1000 pbx_builtin_setvar_helper(channel, "MVM_DOMAIN", vmu->domain);
1001 pbx_builtin_setvar_helper(channel, "MVM_USERNAME", vmu->username);
1002 pbx_builtin_setvar_helper(channel, "MVM_CALLERID", ast_callerid_merge(callerid, sizeof(callerid), cidname, cidnum, "Unknown Caller"));
1003 pbx_builtin_setvar_helper(channel, "MVM_CIDNAME", (cidname ? cidname : "an unknown caller"));
1004 pbx_builtin_setvar_helper(channel, "MVM_CIDNUM", (cidnum ? cidnum : "an unknown caller"));
1005 pbx_builtin_setvar_helper(channel, "MVM_DATE", date);
1006 if (!ast_strlen_zero(counter))
1007 pbx_builtin_setvar_helper(channel, "MVM_COUNTER", counter);
1011 * \brief Set default values for Mini-Voicemail users */
1012 static void populate_defaults(struct minivm_account *vmu)
1014 ast_copy_flags(vmu, (&globalflags), AST_FLAGS_ALL);
1015 ast_copy_string(vmu->attachfmt, default_vmformat, sizeof(vmu->attachfmt));
1016 vmu->volgain = global_volgain;
1020 * \brief Allocate new vm user and set default values */
1021 static struct minivm_account *mvm_user_alloc(void)
1023 struct minivm_account *new;
1025 new = ast_calloc(1, sizeof(*new));
1028 populate_defaults(new);
1035 * \brief Clear list of users */
1036 static void vmaccounts_destroy_list(void)
1038 struct minivm_account *this;
1039 AST_LIST_LOCK(&minivm_accounts);
1040 while ((this = AST_LIST_REMOVE_HEAD(&minivm_accounts, list)))
1042 AST_LIST_UNLOCK(&minivm_accounts);
1047 * \brief Find user from static memory object list */
1048 static struct minivm_account *find_account(const char *domain, const char *username, int createtemp)
1050 struct minivm_account *vmu = NULL, *cur;
1053 if (ast_strlen_zero(domain) || ast_strlen_zero(username)) {
1054 ast_log(LOG_NOTICE, "No username or domain? \n");
1057 ast_debug(3, "Looking for voicemail user %s in domain %s\n", username, domain);
1059 AST_LIST_LOCK(&minivm_accounts);
1060 AST_LIST_TRAVERSE(&minivm_accounts, cur, list) {
1061 /* Is this the voicemail account we're looking for? */
1062 if (!strcasecmp(domain, cur->domain) && !strcasecmp(username, cur->username))
1065 AST_LIST_UNLOCK(&minivm_accounts);
1068 ast_debug(3, "Found account for %s@%s\n", username, domain);
1072 vmu = find_user_realtime(domain, username);
1074 if (createtemp && !vmu) {
1075 /* Create a temporary user, send e-mail and be gone */
1076 vmu = mvm_user_alloc();
1077 ast_set2_flag(vmu, TRUE, MVM_ALLOCED);
1079 ast_copy_string(vmu->username, username, sizeof(vmu->username));
1080 ast_copy_string(vmu->domain, domain, sizeof(vmu->domain));
1081 ast_debug(1, "Created temporary account\n");
1089 * \brief Find user in realtime storage
1090 * \return pointer to minivm_account structure
1092 static struct minivm_account *find_user_realtime(const char *domain, const char *username)
1094 struct ast_variable *var;
1095 struct minivm_account *retval;
1096 char name[MAXHOSTNAMELEN];
1098 retval = mvm_user_alloc();
1103 ast_copy_string(retval->username, username, sizeof(retval->username));
1105 populate_defaults(retval);
1106 var = ast_load_realtime("minivm", "username", username, "domain", domain, SENTINEL);
1113 snprintf(name, sizeof(name), "%s@%s", username, domain);
1114 create_vmaccount(name, var, TRUE);
1116 ast_variables_destroy(var);
1121 * \brief Check if the string would need encoding within the MIME standard, to
1122 * avoid confusing certain mail software that expects messages to be 7-bit
1125 static int check_mime(const char *str)
1127 for (; *str; str++) {
1128 if (*str > 126 || *str < 32 || strchr("()<>@,:;/\"[]?.=", *str)) {
1136 * \brief Encode a string according to the MIME rules for encoding strings
1137 * that are not 7-bit clean or contain control characters.
1139 * Additionally, if the encoded string would exceed the MIME limit of 76
1140 * characters per line, then the encoding will be broken up into multiple
1141 * sections, separated by a space character, in order to facilitate
1142 * breaking up the associated header across multiple lines.
1144 * \param end An expandable buffer for holding the result
1145 * \param maxlen \see ast_str
1146 * \param charset Character set in which the result should be encoded
1147 * \param start A string to be encoded
1148 * \param preamble The length of the first line already used for this string,
1149 * to ensure that each line maintains a maximum length of 76 chars.
1150 * \param postamble the length of any additional characters appended to the
1151 * line, used to ensure proper field wrapping.
1152 * \return The encoded string.
1154 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)
1156 struct ast_str *tmp = ast_str_alloca(80);
1157 int first_section = 1;
1160 ast_str_reset(*end);
1161 ast_str_set(&tmp, -1, "=?%s?Q?", charset);
1162 for (; *start; start++) {
1163 int need_encoding = 0;
1164 if (*start < 33 || *start > 126 || strchr("()<>@,:;/\"[]?.=_", *start)) {
1167 if ((first_section && need_encoding && preamble + ast_str_strlen(tmp) > 70) ||
1168 (first_section && !need_encoding && preamble + ast_str_strlen(tmp) > 72) ||
1169 (!first_section && need_encoding && ast_str_strlen(tmp) > 70) ||
1170 (!first_section && !need_encoding && ast_str_strlen(tmp) > 72)) {
1171 /* Start new line */
1172 ast_str_append(end, maxlen, "%s%s?=", first_section ? "" : " ", ast_str_buffer(tmp));
1173 ast_str_set(&tmp, -1, "=?%s?Q?", charset);
1176 if (need_encoding && *start == ' ') {
1177 ast_str_append(&tmp, -1, "_");
1178 } else if (need_encoding) {
1179 ast_str_append(&tmp, -1, "=%hhX", *start);
1181 ast_str_append(&tmp, -1, "%c", *start);
1184 ast_str_append(end, maxlen, "%s%s?=%s", first_section ? "" : " ", ast_str_buffer(tmp), ast_str_strlen(tmp) + postamble > 74 ? " " : "");
1185 return ast_str_buffer(*end);
1189 * \brief Wraps a character sequence in double quotes, escaping occurences of quotes within the string.
1190 * \param from The string to work with.
1191 * \param buf The destination buffer to write the modified quoted string.
1192 * \param maxlen Always zero. \see ast_str
1194 * \return The destination string with quotes wrapped on it (the to field).
1196 static const char *ast_str_quote(struct ast_str **buf, ssize_t maxlen, const char *from)
1200 /* We're only ever passing 0 to maxlen, so short output isn't possible */
1201 ast_str_set(buf, maxlen, "\"");
1202 for (ptr = from; *ptr; ptr++) {
1203 if (*ptr == '"' || *ptr == '\\') {
1204 ast_str_append(buf, maxlen, "\\%c", *ptr);
1206 ast_str_append(buf, maxlen, "%c", *ptr);
1209 ast_str_append(buf, maxlen, "\"");
1211 return ast_str_buffer(*buf);
1215 * \brief Send voicemail with audio file as an attachment */
1216 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)
1220 char email[256] = "";
1224 char fname[PATH_MAX];
1226 char tmp[80] = "/tmp/astmail-XXXXXX";
1227 char tmp2[PATH_MAX];
1230 struct minivm_zone *the_zone = NULL;
1231 struct ast_channel *ast;
1232 char *finalfilename;
1233 struct ast_str *str1 = ast_str_create(16), *str2 = ast_str_create(16);
1237 if (!str1 || !str2) {
1243 if (type == MVM_MESSAGE_EMAIL) {
1244 if (vmu && !ast_strlen_zero(vmu->email)) {
1245 ast_copy_string(email, vmu->email, sizeof(email));
1246 } else if (!ast_strlen_zero(vmu->username) && !ast_strlen_zero(vmu->domain))
1247 snprintf(email, sizeof(email), "%s@%s", vmu->username, vmu->domain);
1248 } else if (type == MVM_MESSAGE_PAGE) {
1249 ast_copy_string(email, vmu->pager, sizeof(email));
1252 if (ast_strlen_zero(email)) {
1253 ast_log(LOG_WARNING, "No address to send message to.\n");
1257 ast_debug(3, "Sending mail to %s@%s - Using template %s\n", vmu->username, vmu->domain, template->name);
1259 if (!strcmp(format, "wav49"))
1263 /* If we have a gain option, process it now with sox */
1264 if (type == MVM_MESSAGE_EMAIL && (vmu->volgain < -.001 || vmu->volgain > .001) ) {
1265 char newtmp[PATH_MAX];
1266 char tmpcmd[PATH_MAX];
1269 ast_copy_string(newtmp, "/tmp/XXXXXX", sizeof(newtmp));
1270 ast_debug(3, "newtmp: %s\n", newtmp);
1271 tmpfd = mkstemp(newtmp);
1272 snprintf(tmpcmd, sizeof(tmpcmd), "sox -v %.4f %s.%s %s.%s", vmu->volgain, filename, format, newtmp, format);
1273 ast_safe_system(tmpcmd);
1274 finalfilename = newtmp;
1275 ast_debug(3, "VOLGAIN: Stored at: %s.%s - Level: %.4f - Mailbox: %s\n", filename, format, vmu->volgain, vmu->username);
1277 finalfilename = ast_strdupa(filename);
1280 /* Create file name */
1281 snprintf(fname, sizeof(fname), "%s.%s", finalfilename, format);
1283 if (template->attachment)
1284 ast_debug(1, "Attaching file '%s', format '%s', uservm is '%d'\n", finalfilename, format, attach_user_voicemail);
1286 /* Make a temporary file instead of piping directly to sendmail, in case the mail
1290 p = fdopen(pfd, "w");
1295 ast_debug(1, "Opening temp file for e-mail: %s\n", tmp);
1298 ast_log(LOG_WARNING, "Unable to open temporary file '%s'\n", tmp);
1301 /* Allocate channel used for chanvar substitution */
1302 ast = ast_channel_alloc(0, AST_STATE_DOWN, 0, 0, "", "", "", 0, "%s", "");
1305 snprintf(dur, sizeof(dur), "%d:%02d", duration / 60, duration % 60);
1307 /* Does this user have a timezone specified? */
1308 if (!ast_strlen_zero(vmu->zonetag)) {
1309 /* Find the zone in the list */
1310 struct minivm_zone *z;
1311 AST_LIST_LOCK(&minivm_zones);
1312 AST_LIST_TRAVERSE(&minivm_zones, z, list) {
1313 if (strcmp(z->name, vmu->zonetag))
1317 AST_LIST_UNLOCK(&minivm_zones);
1321 ast_localtime(&now, &tm, the_zone ? the_zone->timezone : NULL);
1322 ast_strftime(date, sizeof(date), "%a, %d %b %Y %H:%M:%S %z", &tm);
1324 /* Start printing the email to the temporary file */
1325 fprintf(p, "Date: %s\n", date);
1327 /* Set date format for voicemail mail */
1328 ast_strftime(date, sizeof(date), template->dateformat, &tm);
1331 /* Populate channel with channel variables for substitution */
1332 prep_email_sub_vars(ast, vmu, cidnum, cidname, dur, date, counter);
1334 /* Find email address to use */
1335 /* If there's a server e-mail adress in the account, user that, othterwise template */
1336 fromemail = ast_strlen_zero(vmu->serveremail) ? template->serveremail : vmu->serveremail;
1338 /* Find name to user for server e-mail */
1339 fromaddress = ast_strlen_zero(template->fromaddress) ? "" : template->fromaddress;
1341 /* If needed, add hostname as domain */
1342 if (ast_strlen_zero(fromemail))
1343 fromemail = "asterisk";
1345 if (strchr(fromemail, '@'))
1346 ast_copy_string(who, fromemail, sizeof(who));
1348 char host[MAXHOSTNAMELEN];
1349 gethostname(host, sizeof(host)-1);
1350 snprintf(who, sizeof(who), "%s@%s", fromemail, host);
1353 if (ast_strlen_zero(fromaddress)) {
1354 fprintf(p, "From: Asterisk PBX <%s>\n", who);
1356 ast_debug(4, "Fromaddress template: %s\n", fromaddress);
1357 ast_str_substitute_variables(&str1, 0, ast, fromaddress);
1358 if (check_mime(ast_str_buffer(str1))) {
1361 ast_str_encode_mime(&str2, 0, template->charset, ast_str_buffer(str1), strlen("From: "), strlen(who) + 3);
1362 while ((ptr = strchr(ast_str_buffer(str2), ' '))) {
1364 fprintf(p, "%s %s\n", first_line ? "From:" : "", ast_str_buffer(str2));
1366 /* Substring is smaller, so this will never grow */
1367 ast_str_set(&str2, 0, "%s", ptr + 1);
1369 fprintf(p, "%s %s <%s>\n", first_line ? "From:" : "", ast_str_buffer(str2), who);
1371 fprintf(p, "From: %s <%s>\n", ast_str_quote(&str2, 0, ast_str_buffer(str1)), who);
1375 fprintf(p, "Message-ID: <Asterisk-%d-%s-%d-%s>\n", (unsigned int)ast_random(), vmu->username, (int)getpid(), who);
1377 if (ast_strlen_zero(vmu->email)) {
1378 snprintf(email, sizeof(email), "%s@%s", vmu->username, vmu->domain);
1380 ast_copy_string(email, vmu->email, sizeof(email));
1383 if (check_mime(vmu->fullname)) {
1386 ast_str_encode_mime(&str2, 0, template->charset, vmu->fullname, strlen("To: "), strlen(email) + 3);
1387 while ((ptr = strchr(ast_str_buffer(str2), ' '))) {
1389 fprintf(p, "%s %s\n", first_line ? "To:" : "", ast_str_buffer(str2));
1391 /* Substring is smaller, so this will never grow */
1392 ast_str_set(&str2, 0, "%s", ptr + 1);
1394 fprintf(p, "%s %s <%s>\n", first_line ? "To:" : "", ast_str_buffer(str2), email);
1396 fprintf(p, "To: %s <%s>\n", ast_str_quote(&str2, 0, vmu->fullname), email);
1399 if (!ast_strlen_zero(template->subject)) {
1400 ast_str_substitute_variables(&str1, 0, ast, template->subject);
1401 if (check_mime(ast_str_buffer(str1))) {
1404 ast_str_encode_mime(&str2, 0, template->charset, ast_str_buffer(str1), strlen("Subject: "), 0);
1405 while ((ptr = strchr(ast_str_buffer(str2), ' '))) {
1407 fprintf(p, "%s %s\n", first_line ? "Subject:" : "", ast_str_buffer(str2));
1409 /* Substring is smaller, so this will never grow */
1410 ast_str_set(&str2, 0, "%s", ptr + 1);
1412 fprintf(p, "%s %s\n", first_line ? "Subject:" : "", ast_str_buffer(str2));
1414 fprintf(p, "Subject: %s\n", ast_str_buffer(str1));
1417 fprintf(p, "Subject: New message in mailbox %s@%s\n", vmu->username, vmu->domain);
1418 ast_debug(1, "Using default subject for this email \n");
1421 if (option_debug > 2)
1422 fprintf(p, "X-Asterisk-debug: template %s user account %s@%s\n", template->name, vmu->username, vmu->domain);
1423 fprintf(p, "MIME-Version: 1.0\n");
1425 /* Something unique. */
1426 snprintf(bound, sizeof(bound), "voicemail_%s%d%d", vmu->username, (int)getpid(), (unsigned int)ast_random());
1428 fprintf(p, "Content-Type: multipart/mixed; boundary=\"%s\"\n\n\n", bound);
1430 fprintf(p, "--%s\n", bound);
1431 fprintf(p, "Content-Type: text/plain; charset=%s\nContent-Transfer-Encoding: 8bit\n\n", template->charset);
1432 if (!ast_strlen_zero(template->body)) {
1433 ast_str_substitute_variables(&str1, 0, ast, template->body);
1434 ast_debug(3, "Message now: %s\n-----\n", ast_str_buffer(str1));
1435 fprintf(p, "%s\n", ast_str_buffer(str1));
1437 fprintf(p, "Dear %s:\n\n\tJust wanted to let you know you were just left a %s long message \n"
1438 "in mailbox %s from %s, on %s so you might\n"
1439 "want to check it when you get a chance. Thanks!\n\n\t\t\t\t--Asterisk\n\n", vmu->fullname,
1440 dur, vmu->username, (cidname ? cidname : (cidnum ? cidnum : "an unknown caller")), date);
1441 ast_debug(3, "Using default message body (no template)\n-----\n");
1443 /* Eww. We want formats to tell us their own MIME type */
1444 if (template->attachment) {
1445 char *ctype = "audio/x-";
1446 ast_debug(3, "Attaching file to message: %s\n", fname);
1447 if (!strcasecmp(format, "ogg"))
1448 ctype = "application/";
1450 fprintf(p, "--%s\n", bound);
1451 fprintf(p, "Content-Type: %s%s; name=\"voicemailmsg.%s\"\n", ctype, format, format);
1452 fprintf(p, "Content-Transfer-Encoding: base64\n");
1453 fprintf(p, "Content-Description: Voicemail sound attachment.\n");
1454 fprintf(p, "Content-Disposition: attachment; filename=\"voicemail%s.%s\"\n\n", counter ? counter : "", format);
1456 base_encode(fname, p);
1457 fprintf(p, "\n\n--%s--\n.\n", bound);
1460 snprintf(tmp2, sizeof(tmp2), "( %s < %s ; rm -f %s ) &", global_mailcmd, tmp, tmp);
1461 ast_safe_system(tmp2);
1462 ast_debug(1, "Sent message to %s with command '%s' - %s\n", vmu->email, global_mailcmd, template->attachment ? "(media attachment)" : "");
1463 ast_debug(3, "Actual command used: %s\n", tmp2);
1465 ast = ast_channel_release(ast);
1473 * \brief Create directory based on components */
1474 static int make_dir(char *dest, int len, const char *domain, const char *username, const char *folder)
1476 return snprintf(dest, len, "%s%s/%s%s%s", MVM_SPOOL_DIR, domain, username, ast_strlen_zero(folder) ? "" : "/", folder ? folder : "");
1480 * \brief Checks if directory exists. Does not create directory, but builds string in dest
1481 * \param dest String. base directory.
1482 * \param len Int. Length base directory string.
1483 * \param domain String. Ignored if is null or empty string.
1484 * \param username String. Ignored if is null or empty string.
1485 * \param folder String. Ignored if is null or empty string.
1486 * \return 0 on failure, 1 on success.
1488 static int check_dirpath(char *dest, int len, char *domain, char *username, char *folder)
1490 struct stat filestat;
1491 make_dir(dest, len, domain, username, folder ? folder : "");
1492 if (stat(dest, &filestat)== -1)
1499 * \brief basically mkdir -p $dest/$domain/$username/$folder
1500 * \param dest String. base directory.
1501 * \param len Length of directory string
1502 * \param domain String. Ignored if is null or empty string.
1503 * \param folder String. Ignored if is null or empty string.
1504 * \param username String. Ignored if is null or empty string.
1505 * \return -1 on failure, 0 on success.
1507 static int create_dirpath(char *dest, int len, char *domain, char *username, char *folder)
1510 make_dir(dest, len, domain, username, folder);
1511 if ((res = ast_mkdir(dest, 0777))) {
1512 ast_log(LOG_WARNING, "ast_mkdir '%s' failed: %s\n", dest, strerror(res));
1515 ast_debug(2, "Creating directory for %s@%s folder %s : %s\n", username, domain, folder, dest);
1521 * \brief Play intro message before recording voicemail
1523 static int invent_message(struct ast_channel *chan, char *domain, char *username, int busy, char *ecodes)
1528 ast_debug(2, "Still preparing to play message ...\n");
1530 snprintf(fn, sizeof(fn), "%s%s/%s/greet", MVM_SPOOL_DIR, domain, username);
1532 if (ast_fileexists(fn, NULL, NULL) > 0) {
1533 res = ast_streamfile(chan, fn, chan->language);
1536 res = ast_waitstream(chan, ecodes);
1540 int numericusername = 1;
1543 ast_debug(2, "No personal prompts. Using default prompt set for language\n");
1546 ast_debug(2, "Numeric? Checking %c\n", *i);
1548 numericusername = FALSE;
1554 if (numericusername) {
1555 if (ast_streamfile(chan, "vm-theperson", chan->language))
1557 if ((res = ast_waitstream(chan, ecodes)))
1560 res = ast_say_digit_str(chan, username, ecodes, chan->language);
1564 if (ast_streamfile(chan, "vm-theextensionis", chan->language))
1566 if ((res = ast_waitstream(chan, ecodes)))
1571 res = ast_streamfile(chan, busy ? "vm-isonphone" : "vm-isunavail", chan->language);
1574 res = ast_waitstream(chan, ecodes);
1579 * \brief Delete media files and attribute file */
1580 static int vm_delete(char *file)
1584 ast_debug(1, "Deleting voicemail file %s\n", file);
1586 res = unlink(file); /* Remove the meta data file */
1587 res |= ast_filedelete(file, NULL); /* remove the media file */
1593 * \brief Record voicemail message & let caller review or re-record it, or set options if applicable */
1594 static int play_record_review(struct ast_channel *chan, char *playfile, char *recordfile, int maxtime, char *fmt,
1595 int outsidecaller, struct minivm_account *vmu, int *duration, const char *unlockdir,
1596 signed char record_gain)
1599 int max_attempts = 3;
1602 int message_exists = 0;
1603 signed char zero_gain = 0;
1604 char *acceptdtmf = "#";
1605 char *canceldtmf = "";
1607 /* Note that urgent and private are for flagging messages as such in the future */
1609 /* barf if no pointer passed to store duration in */
1610 if (duration == NULL) {
1611 ast_log(LOG_WARNING, "Error play_record_review called without duration pointer\n");
1615 cmd = '3'; /* Want to start by recording */
1617 while ((cmd >= 0) && (cmd != 't')) {
1620 ast_verb(3, "Saving message as is\n");
1621 ast_stream_and_wait(chan, "vm-msgsaved", "");
1626 ast_verb(3, "Reviewing the message\n");
1627 ast_streamfile(chan, recordfile, chan->language);
1628 cmd = ast_waitstream(chan, AST_DIGIT_ANY);
1634 ast_verb(3, "Re-recording the message\n");
1636 ast_verb(3, "Recording the message\n");
1637 if (recorded && outsidecaller)
1638 cmd = ast_play_and_wait(chan, "beep");
1640 /* 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 */
1642 ast_channel_setoption(chan, AST_OPTION_RXGAIN, &record_gain, sizeof(record_gain), 0);
1643 if (ast_test_flag(vmu, MVM_OPERATOR))
1645 cmd = ast_play_and_record_full(chan, playfile, recordfile, maxtime, fmt, duration, global_silencethreshold, global_maxsilence, unlockdir, acceptdtmf, canceldtmf);
1647 ast_channel_setoption(chan, AST_OPTION_RXGAIN, &zero_gain, sizeof(zero_gain), 0);
1648 if (cmd == -1) /* User has hung up, no options to give */
1652 else if (cmd == '*')
1655 /* If all is well, a message exists */
1668 cmd = ast_play_and_wait(chan, "vm-sorry");
1671 if(!ast_test_flag(vmu, MVM_OPERATOR)) {
1672 cmd = ast_play_and_wait(chan, "vm-sorry");
1675 if (message_exists || recorded) {
1676 cmd = ast_play_and_wait(chan, "vm-saveoper");
1678 cmd = ast_waitfordigit(chan, 3000);
1680 ast_play_and_wait(chan, "vm-msgsaved");
1683 ast_play_and_wait(chan, "vm-deleted");
1684 vm_delete(recordfile);
1690 /* If the caller is an ouside caller, and the review option is enabled,
1691 allow them to review the message, but let the owner of the box review
1693 if (outsidecaller && !ast_test_flag(vmu, MVM_REVIEW))
1695 if (message_exists) {
1696 cmd = ast_play_and_wait(chan, "vm-review");
1698 cmd = ast_play_and_wait(chan, "vm-torerecord");
1700 cmd = ast_waitfordigit(chan, 600);
1703 if (!cmd && outsidecaller && ast_test_flag(vmu, MVM_OPERATOR)) {
1704 cmd = ast_play_and_wait(chan, "vm-reachoper");
1706 cmd = ast_waitfordigit(chan, 600);
1709 cmd = ast_waitfordigit(chan, 6000);
1713 if (attempts > max_attempts) {
1719 ast_play_and_wait(chan, "vm-goodbye");
1725 /*! \brief Run external notification for voicemail message */
1726 static void run_externnotify(struct ast_channel *chan, struct minivm_account *vmu)
1728 char arguments[BUFSIZ];
1730 if (ast_strlen_zero(vmu->externnotify) && ast_strlen_zero(global_externnotify))
1733 snprintf(arguments, sizeof(arguments), "%s %s@%s %s %s&",
1734 ast_strlen_zero(vmu->externnotify) ? global_externnotify : vmu->externnotify,
1735 vmu->username, vmu->domain,
1736 chan->cid.cid_name, chan->cid.cid_num);
1738 ast_debug(1, "Executing: %s\n", arguments);
1739 ast_safe_system(arguments);
1743 * \brief Send message to voicemail account owner */
1744 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)
1747 struct minivm_template *etemplate;
1748 char *messageformat;
1750 char oldlocale[100];
1751 const char *counter;
1753 if (!ast_strlen_zero(vmu->attachfmt)) {
1754 if (strstr(format, vmu->attachfmt)) {
1755 format = vmu->attachfmt;
1757 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);
1761 etemplate = message_template_find(vmu->etemplate);
1763 etemplate = message_template_find(templatename);
1765 etemplate = message_template_find("email-default");
1767 /* Attach only the first format */
1768 stringp = messageformat = ast_strdupa(format);
1769 strsep(&stringp, "|");
1771 if (!ast_strlen_zero(etemplate->locale)) {
1773 ast_copy_string(oldlocale, setlocale(LC_TIME, NULL), sizeof(oldlocale));
1774 ast_debug(2, "Changing locale from %s to %s\n", oldlocale, etemplate->locale);
1775 new_locale = setlocale(LC_TIME, etemplate->locale);
1776 if (new_locale == NULL) {
1777 ast_log(LOG_WARNING, "-_-_- Changing to new locale did not work. Locale: %s\n", etemplate->locale);
1783 /* Read counter if available */
1784 ast_channel_lock(chan);
1785 if ((counter = pbx_builtin_getvar_helper(chan, "MVM_COUNTER"))) {
1786 counter = ast_strdupa(counter);
1788 ast_channel_unlock(chan);
1790 if (ast_strlen_zero(counter)) {
1791 ast_debug(2, "MVM_COUNTER not found\n");
1793 ast_debug(2, "MVM_COUNTER found - will use it with value %s\n", counter);
1796 res = sendmail(etemplate, vmu, cidnum, cidname, filename, messageformat, duration, etemplate->attachment, MVM_MESSAGE_EMAIL, counter);
1798 if (res == 0 && !ast_strlen_zero(vmu->pager)) {
1799 /* Find template for paging */
1800 etemplate = message_template_find(vmu->ptemplate);
1802 etemplate = message_template_find("pager-default");
1803 if (etemplate->locale) {
1804 ast_copy_string(oldlocale, setlocale(LC_TIME, ""), sizeof(oldlocale));
1805 setlocale(LC_TIME, etemplate->locale);
1808 res = sendmail(etemplate, vmu, cidnum, cidname, filename, messageformat, duration, etemplate->attachment, MVM_MESSAGE_PAGE, counter);
1811 manager_event(EVENT_FLAG_CALL, "MiniVoiceMail", "Action: SentNotification\rn\nMailbox: %s@%s\r\nCounter: %s\r\n", vmu->username, vmu->domain, counter);
1813 run_externnotify(chan, vmu); /* Run external notification */
1815 if (etemplate->locale) {
1816 setlocale(LC_TIME, oldlocale); /* Rest to old locale */
1823 * \brief Record voicemail message, store into file prepared for sending e-mail */
1824 static int leave_voicemail(struct ast_channel *chan, char *username, struct leave_vm_options *options)
1826 char tmptxtfile[PATH_MAX];
1829 int res = 0, txtdes;
1833 char tmpdir[PATH_MAX];
1834 char ext_context[256] = "";
1838 struct minivm_account *vmu;
1841 ast_copy_string(tmp, username, sizeof(tmp));
1843 domain = strchr(tmp, '@');
1849 if (!(vmu = find_account(domain, username, TRUE))) {
1850 /* We could not find user, let's exit */
1851 ast_log(LOG_ERROR, "Can't allocate temporary account for '%s@%s'\n", username, domain);
1852 pbx_builtin_setvar_helper(chan, "MVM_RECORD_STATUS", "FAILED");
1856 /* Setup pre-file if appropriate */
1857 if (strcmp(vmu->domain, "localhost"))
1858 snprintf(ext_context, sizeof(ext_context), "%s@%s", username, vmu->domain);
1860 ast_copy_string(ext_context, vmu->domain, sizeof(ext_context));
1862 /* The meat of recording the message... All the announcements and beeps have been played*/
1863 if (ast_strlen_zero(vmu->attachfmt))
1864 ast_copy_string(fmt, default_vmformat, sizeof(fmt));
1866 ast_copy_string(fmt, vmu->attachfmt, sizeof(fmt));
1868 if (ast_strlen_zero(fmt)) {
1869 ast_log(LOG_WARNING, "No format for saving voicemail? Default %s\n", default_vmformat);
1870 pbx_builtin_setvar_helper(chan, "MVM_RECORD_STATUS", "FAILED");
1875 userdir = check_dirpath(tmpdir, sizeof(tmpdir), vmu->domain, username, "tmp");
1877 /* If we have no user directory, use generic temporary directory */
1879 create_dirpath(tmpdir, sizeof(tmpdir), "0000_minivm_temp", "mediafiles", "");
1880 ast_debug(3, "Creating temporary directory %s\n", tmpdir);
1884 snprintf(tmptxtfile, sizeof(tmptxtfile), "%s/XXXXXX", tmpdir);
1886 /* XXX This file needs to be in temp directory */
1887 txtdes = mkstemp(tmptxtfile);
1889 ast_log(LOG_ERROR, "Unable to create message file %s: %s\n", tmptxtfile, strerror(errno));
1890 res = ast_streamfile(chan, "vm-mailboxfull", chan->language);
1892 res = ast_waitstream(chan, "");
1893 pbx_builtin_setvar_helper(chan, "MVM_RECORD_STATUS", "FAILED");
1898 /* Unless we're *really* silent, try to send the beep */
1899 res = ast_streamfile(chan, "beep", chan->language);
1901 res = ast_waitstream(chan, "");
1904 /* OEJ XXX Maybe this can be turned into a log file? Hmm. */
1905 /* Store information */
1906 ast_debug(2, "Open file for metadata: %s\n", tmptxtfile);
1908 res = play_record_review(chan, NULL, tmptxtfile, global_vmmaxmessage, fmt, 1, vmu, &duration, NULL, options->record_gain);
1910 txt = fdopen(txtdes, "w+");
1912 ast_log(LOG_WARNING, "Error opening text file for output\n");
1915 struct timeval now = ast_tvnow();
1917 char logbuf[BUFSIZ];
1918 get_date(date, sizeof(date));
1919 ast_localtime(&now, &tm, NULL);
1920 ast_strftime(timebuf, sizeof(timebuf), "%H:%M:%S", &tm);
1922 snprintf(logbuf, sizeof(logbuf),
1923 /* "Mailbox:domain:macrocontext:exten:priority:callerchan:callerid:origdate:origtime:duration:durationstatus:accountcode" */
1924 "%s:%s:%s:%s:%d:%s:%s:%s:%s:%d:%s:%s\n",
1931 ast_callerid_merge(callerid, sizeof(callerid), chan->cid.cid_name, chan->cid.cid_num, "Unknown"),
1935 duration < global_vmminmessage ? "IGNORED" : "OK",
1938 fprintf(txt, "%s", logbuf);
1939 if (minivmlogfile) {
1940 ast_mutex_lock(&minivmloglock);
1941 fprintf(minivmlogfile, "%s", logbuf);
1942 ast_mutex_unlock(&minivmloglock);
1945 if (duration < global_vmminmessage) {
1946 ast_verb(3, "Recording was %d seconds long but needs to be at least %d - abandoning\n", duration, global_vmminmessage);
1948 ast_filedelete(tmptxtfile, NULL);
1950 pbx_builtin_setvar_helper(chan, "MVM_RECORD_STATUS", "FAILED");
1953 fclose(txt); /* Close log file */
1954 if (ast_fileexists(tmptxtfile, NULL, NULL) <= 0) {
1955 ast_debug(1, "The recorded media file is gone, so we should remove the .txt file too!\n");
1957 pbx_builtin_setvar_helper(chan, "MVM_RECORD_STATUS", "FAILED");
1958 if(ast_test_flag(vmu, MVM_ALLOCED))
1963 /* Set channel variables for the notify application */
1964 pbx_builtin_setvar_helper(chan, "MVM_FILENAME", tmptxtfile);
1965 snprintf(timebuf, sizeof(timebuf), "%d", duration);
1966 pbx_builtin_setvar_helper(chan, "MVM_DURATION", timebuf);
1967 pbx_builtin_setvar_helper(chan, "MVM_FORMAT", fmt);
1970 global_stats.lastreceived = ast_tvnow();
1971 global_stats.receivedmessages++;
1973 /* Go ahead and delete audio files from system, they're not needed any more */
1974 if (ast_fileexists(tmptxtfile, NULL, NULL) <= 0) {
1975 ast_filedelete(tmptxtfile, NULL);
1976 /* Even not being used at the moment, it's better to convert ast_log to ast_debug anyway */
1977 ast_debug(2, "-_-_- Deleted audio file after notification :: %s \n", tmptxtfile);
1984 if(ast_test_flag(vmu, MVM_ALLOCED))
1987 pbx_builtin_setvar_helper(chan, "MVM_RECORD_STATUS", "SUCCESS");
1992 * \brief Queue a message waiting event */
1993 static void queue_mwi_event(const char *mbx, const char *ctx, int urgent, int new, int old)
1995 struct ast_event *event;
1996 char *mailbox, *context;
1998 mailbox = ast_strdupa(mbx);
1999 context = ast_strdupa(ctx);
2000 if (ast_strlen_zero(context)) {
2001 context = "default";
2004 if (!(event = ast_event_new(AST_EVENT_MWI,
2005 AST_EVENT_IE_MAILBOX, AST_EVENT_IE_PLTYPE_STR, mailbox,
2006 AST_EVENT_IE_CONTEXT, AST_EVENT_IE_PLTYPE_STR, context,
2007 AST_EVENT_IE_NEWMSGS, AST_EVENT_IE_PLTYPE_UINT, (new+urgent),
2008 AST_EVENT_IE_OLDMSGS, AST_EVENT_IE_PLTYPE_UINT, old,
2009 AST_EVENT_IE_END))) {
2013 ast_event_queue_and_cache(event);
2017 * \brief Send MWI using interal Asterisk event subsystem */
2018 static int minivm_mwi_exec(struct ast_channel *chan, const char *data)
2027 if (ast_strlen_zero(data)) {
2028 ast_log(LOG_ERROR, "Minivm needs at least an account argument \n");
2031 tmpptr = ast_strdupa((char *)data);
2033 ast_log(LOG_ERROR, "Out of memory\n");
2036 argc = ast_app_separate_args(tmpptr, ',', argv, ARRAY_LEN(argv));
2038 ast_log(LOG_ERROR, "%d arguments passed to MiniVM_MWI, need 4.\n", argc);
2041 ast_copy_string(tmp, argv[0], sizeof(tmp));
2043 domain = strchr(tmp, '@');
2048 if (ast_strlen_zero(domain) || ast_strlen_zero(mailbox)) {
2049 ast_log(LOG_ERROR, "Need mailbox@context as argument. Sorry. Argument 0 %s\n", argv[0]);
2052 queue_mwi_event(mailbox, domain, atoi(argv[1]), atoi(argv[2]), atoi(argv[3]));
2059 * \brief Notify voicemail account owners - either generic template or user specific */
2060 static int minivm_notify_exec(struct ast_channel *chan, const char *data)
2068 struct minivm_account *vmu;
2069 char *username = argv[0];
2070 const char *template = "";
2071 const char *filename;
2073 const char *duration_string;
2075 if (ast_strlen_zero(data)) {
2076 ast_log(LOG_ERROR, "Minivm needs at least an account argument \n");
2079 tmpptr = ast_strdupa((char *)data);
2081 ast_log(LOG_ERROR, "Out of memory\n");
2084 argc = ast_app_separate_args(tmpptr, ',', argv, ARRAY_LEN(argv));
2086 if (argc == 2 && !ast_strlen_zero(argv[1]))
2089 ast_copy_string(tmp, argv[0], sizeof(tmp));
2091 domain = strchr(tmp, '@');
2096 if (ast_strlen_zero(domain) || ast_strlen_zero(username)) {
2097 ast_log(LOG_ERROR, "Need username@domain as argument. Sorry. Argument 0 %s\n", argv[0]);
2101 if(!(vmu = find_account(domain, username, TRUE))) {
2102 /* We could not find user, let's exit */
2103 ast_log(LOG_WARNING, "Could not allocate temporary memory for '%s@%s'\n", username, domain);
2104 pbx_builtin_setvar_helper(chan, "MVM_NOTIFY_STATUS", "FAILED");
2108 ast_channel_lock(chan);
2109 if ((filename = pbx_builtin_getvar_helper(chan, "MVM_FILENAME"))) {
2110 filename = ast_strdupa(filename);
2112 ast_channel_unlock(chan);
2113 /* Notify of new message to e-mail and pager */
2114 if (!ast_strlen_zero(filename)) {
2115 ast_channel_lock(chan);
2116 if ((format = pbx_builtin_getvar_helper(chan, "MVM_FORMAT"))) {
2117 format = ast_strdupa(format);
2119 if ((duration_string = pbx_builtin_getvar_helper(chan, "MVM_DURATION"))) {
2120 duration_string = ast_strdupa(duration_string);
2122 ast_channel_unlock(chan);
2123 res = notify_new_message(chan, template, vmu, filename, atoi(duration_string), format, chan->cid.cid_num, chan->cid.cid_name);
2126 pbx_builtin_setvar_helper(chan, "MVM_NOTIFY_STATUS", res == 0 ? "SUCCESS" : "FAILED");
2129 if(ast_test_flag(vmu, MVM_ALLOCED))
2132 /* Ok, we're ready to rock and roll. Return to dialplan */
2139 * \brief Dialplan function to record voicemail */
2140 static int minivm_record_exec(struct ast_channel *chan, const char *data)
2144 struct leave_vm_options leave_options;
2147 struct ast_flags flags = { 0 };
2148 char *opts[OPT_ARG_ARRAY_SIZE];
2150 memset(&leave_options, 0, sizeof(leave_options));
2152 /* Answer channel if it's not already answered */
2153 if (chan->_state != AST_STATE_UP)
2156 if (ast_strlen_zero(data)) {
2157 ast_log(LOG_ERROR, "Minivm needs at least an account argument \n");
2160 tmp = ast_strdupa((char *)data);
2162 ast_log(LOG_ERROR, "Out of memory\n");
2165 argc = ast_app_separate_args(tmp, ',', argv, ARRAY_LEN(argv));
2167 if (ast_app_parse_options(minivm_app_options, &flags, opts, argv[1])) {
2170 ast_copy_flags(&leave_options, &flags, OPT_SILENT | OPT_BUSY_GREETING | OPT_UNAVAIL_GREETING );
2171 if (ast_test_flag(&flags, OPT_RECORDGAIN)) {
2174 if (sscanf(opts[OPT_ARG_RECORDGAIN], "%d", &gain) != 1) {
2175 ast_log(LOG_WARNING, "Invalid value '%s' provided for record gain option\n", opts[OPT_ARG_RECORDGAIN]);
2178 leave_options.record_gain = (signed char) gain;
2182 /* Now run the appliation and good luck to you! */
2183 res = leave_voicemail(chan, argv[0], &leave_options);
2185 if (res == ERROR_LOCK_PATH) {
2186 ast_log(LOG_ERROR, "Could not leave voicemail. The path is already locked.\n");
2187 pbx_builtin_setvar_helper(chan, "MVM_RECORD_STATUS", "FAILED");
2190 pbx_builtin_setvar_helper(chan, "MVM_RECORD_STATUS", "SUCCESS");
2196 * \brief Play voicemail prompts - either generic or user specific */
2197 static int minivm_greet_exec(struct ast_channel *chan, const char *data)
2199 struct leave_vm_options leave_options = { 0, '\0'};
2202 struct ast_flags flags = { 0 };
2203 char *opts[OPT_ARG_ARRAY_SIZE];
2209 char dest[PATH_MAX];
2210 char prefile[PATH_MAX] = "";
2211 char tempfile[PATH_MAX] = "";
2212 char ext_context[256] = "";
2214 char ecodes[16] = "#";
2216 struct minivm_account *vmu;
2217 char *username = argv[0];
2219 if (ast_strlen_zero(data)) {
2220 ast_log(LOG_ERROR, "Minivm needs at least an account argument \n");
2223 tmpptr = ast_strdupa((char *)data);
2225 ast_log(LOG_ERROR, "Out of memory\n");
2228 argc = ast_app_separate_args(tmpptr, ',', argv, ARRAY_LEN(argv));
2231 if (ast_app_parse_options(minivm_app_options, &flags, opts, argv[1]))
2233 ast_copy_flags(&leave_options, &flags, OPT_SILENT | OPT_BUSY_GREETING | OPT_UNAVAIL_GREETING );
2236 ast_copy_string(tmp, argv[0], sizeof(tmp));
2238 domain = strchr(tmp, '@');
2243 if (ast_strlen_zero(domain) || ast_strlen_zero(username)) {
2244 ast_log(LOG_ERROR, "Need username@domain as argument. Sorry. Argument: %s\n", argv[0]);
2247 ast_debug(1, "Trying to find configuration for user %s in domain %s\n", username, domain);
2249 if (!(vmu = find_account(domain, username, TRUE))) {
2250 ast_log(LOG_ERROR, "Could not allocate memory. \n");
2254 /* Answer channel if it's not already answered */
2255 if (chan->_state != AST_STATE_UP)
2258 /* Setup pre-file if appropriate */
2259 if (strcmp(vmu->domain, "localhost"))
2260 snprintf(ext_context, sizeof(ext_context), "%s@%s", username, vmu->domain);
2262 ast_copy_string(ext_context, vmu->domain, sizeof(ext_context));
2264 if (ast_test_flag(&leave_options, OPT_BUSY_GREETING)) {
2265 res = check_dirpath(dest, sizeof(dest), vmu->domain, username, "busy");
2267 snprintf(prefile, sizeof(prefile), "%s%s/%s/busy", MVM_SPOOL_DIR, vmu->domain, username);
2268 } else if (ast_test_flag(&leave_options, OPT_UNAVAIL_GREETING)) {
2269 res = check_dirpath(dest, sizeof(dest), vmu->domain, username, "unavail");
2271 snprintf(prefile, sizeof(prefile), "%s%s/%s/unavail", MVM_SPOOL_DIR, vmu->domain, username);
2273 /* Check for temporary greeting - it overrides busy and unavail */
2274 snprintf(tempfile, sizeof(tempfile), "%s%s/%s/temp", MVM_SPOOL_DIR, vmu->domain, username);
2275 if (!(res = check_dirpath(dest, sizeof(dest), vmu->domain, username, "temp"))) {
2276 ast_debug(2, "Temporary message directory does not exist, using default (%s)\n", tempfile);
2277 ast_copy_string(prefile, tempfile, sizeof(prefile));
2279 ast_debug(2, "Preparing to play message ...\n");
2281 /* Check current or macro-calling context for special extensions */
2282 if (ast_test_flag(vmu, MVM_OPERATOR)) {
2283 if (!ast_strlen_zero(vmu->exit)) {
2284 if (ast_exists_extension(chan, vmu->exit, "o", 1, chan->cid.cid_num)) {
2285 strncat(ecodes, "0", sizeof(ecodes) - strlen(ecodes) - 1);
2288 } else if (ast_exists_extension(chan, chan->context, "o", 1, chan->cid.cid_num)) {
2289 strncat(ecodes, "0", sizeof(ecodes) - strlen(ecodes) - 1);
2292 else if (!ast_strlen_zero(chan->macrocontext) && ast_exists_extension(chan, chan->macrocontext, "o", 1, chan->cid.cid_num)) {
2293 strncat(ecodes, "0", sizeof(ecodes) - strlen(ecodes) - 1);
2298 if (!ast_strlen_zero(vmu->exit)) {
2299 if (ast_exists_extension(chan, vmu->exit, "a", 1, chan->cid.cid_num))
2300 strncat(ecodes, "*", sizeof(ecodes) - strlen(ecodes) - 1);
2301 } else if (ast_exists_extension(chan, chan->context, "a", 1, chan->cid.cid_num))
2302 strncat(ecodes, "*", sizeof(ecodes) - strlen(ecodes) - 1);
2303 else if (!ast_strlen_zero(chan->macrocontext) && ast_exists_extension(chan, chan->macrocontext, "a", 1, chan->cid.cid_num)) {
2304 strncat(ecodes, "*", sizeof(ecodes) - strlen(ecodes) - 1);
2308 res = 0; /* Reset */
2309 /* Play the beginning intro if desired */
2310 if (!ast_strlen_zero(prefile)) {
2311 if (ast_streamfile(chan, prefile, chan->language) > -1)
2312 res = ast_waitstream(chan, ecodes);
2314 ast_debug(2, "%s doesn't exist, doing what we can\n", prefile);
2315 res = invent_message(chan, vmu->domain, username, ast_test_flag(&leave_options, OPT_BUSY_GREETING), ecodes);
2318 ast_debug(2, "Hang up during prefile playback\n");
2319 pbx_builtin_setvar_helper(chan, "MVM_GREET_STATUS", "FAILED");
2320 if(ast_test_flag(vmu, MVM_ALLOCED))
2325 /* On a '#' we skip the instructions */
2326 ast_set_flag(&leave_options, OPT_SILENT);
2329 if (!res && !ast_test_flag(&leave_options, OPT_SILENT)) {
2330 res = ast_streamfile(chan, SOUND_INTRO, chan->language);
2332 res = ast_waitstream(chan, ecodes);
2334 ast_set_flag(&leave_options, OPT_SILENT);
2339 ast_stopstream(chan);
2340 /* Check for a '*' here in case the caller wants to escape from voicemail to something
2341 other than the operator -- an automated attendant or mailbox login for example */
2343 chan->exten[0] = 'a';
2344 chan->exten[1] = '\0';
2345 if (!ast_strlen_zero(vmu->exit)) {
2346 ast_copy_string(chan->context, vmu->exit, sizeof(chan->context));
2347 } else if (ausemacro && !ast_strlen_zero(chan->macrocontext)) {
2348 ast_copy_string(chan->context, chan->macrocontext, sizeof(chan->context));
2351 pbx_builtin_setvar_helper(chan, "MVM_GREET_STATUS", "USEREXIT");
2353 } else if (res == '0') { /* Check for a '0' here */
2354 if(ouseexten || ousemacro) {
2355 chan->exten[0] = 'o';
2356 chan->exten[1] = '\0';
2357 if (!ast_strlen_zero(vmu->exit)) {
2358 ast_copy_string(chan->context, vmu->exit, sizeof(chan->context));
2359 } else if (ousemacro && !ast_strlen_zero(chan->macrocontext)) {
2360 ast_copy_string(chan->context, chan->macrocontext, sizeof(chan->context));
2362 ast_play_and_wait(chan, "transfer");
2364 pbx_builtin_setvar_helper(chan, "MVM_GREET_STATUS", "USEREXIT");
2367 } else if (res < 0) {
2368 pbx_builtin_setvar_helper(chan, "MVM_GREET_STATUS", "FAILED");
2371 pbx_builtin_setvar_helper(chan, "MVM_GREET_STATUS", "SUCCESS");
2373 if(ast_test_flag(vmu, MVM_ALLOCED))
2377 /* Ok, we're ready to rock and roll. Return to dialplan */
2383 * \brief Dialplan application to delete voicemail */
2384 static int minivm_delete_exec(struct ast_channel *chan, const char *data)
2387 char filename[BUFSIZ];
2389 if (!ast_strlen_zero(data)) {
2390 ast_copy_string(filename, (char *) data, sizeof(filename));
2392 ast_channel_lock(chan);
2393 ast_copy_string(filename, pbx_builtin_getvar_helper(chan, "MVM_FILENAME"), sizeof(filename));
2394 ast_channel_unlock(chan);
2397 if (ast_strlen_zero(filename)) {
2398 ast_log(LOG_ERROR, "No filename given in application arguments or channel variable MVM_FILENAME\n");
2402 /* Go ahead and delete audio files from system, they're not needed any more */
2403 /* We should look for both audio and text files here */
2404 if (ast_fileexists(filename, NULL, NULL) > 0) {
2405 res = vm_delete(filename);
2407 ast_debug(2, "Can't delete file: %s\n", filename);
2408 pbx_builtin_setvar_helper(chan, "MVM_DELETE_STATUS", "FAILED");
2410 ast_debug(2, "Deleted voicemail file :: %s \n", filename);
2411 pbx_builtin_setvar_helper(chan, "MVM_DELETE_STATUS", "SUCCESS");
2414 ast_debug(2, "Filename does not exist: %s\n", filename);
2415 pbx_builtin_setvar_helper(chan, "MVM_DELETE_STATUS", "FAILED");
2421 /*! \brief Record specific messages for voicemail account */
2422 static int minivm_accmess_exec(struct ast_channel *chan, const char *data)
2426 char filename[PATH_MAX];
2429 char *tmpptr = NULL;
2430 struct minivm_account *vmu;
2431 char *username = argv[0];
2432 struct ast_flags flags = { 0 };
2433 char *opts[OPT_ARG_ARRAY_SIZE];
2435 char *message = NULL;
2436 char *prompt = NULL;
2440 if (ast_strlen_zero(data)) {
2441 ast_log(LOG_ERROR, "MinivmAccmess needs at least two arguments: account and option\n");
2444 tmpptr = ast_strdupa((char *)data);
2447 ast_log(LOG_ERROR, "Out of memory\n");
2450 argc = ast_app_separate_args(tmpptr, ',', argv, ARRAY_LEN(argv));
2454 ast_log(LOG_ERROR, "MinivmAccmess needs at least two arguments: account and option\n");
2457 if (!error && strlen(argv[1]) > 1) {
2458 ast_log(LOG_ERROR, "MinivmAccmess can only handle one option at a time. Bad option string: %s\n", argv[1]);
2462 if (!error && ast_app_parse_options(minivm_accmess_options, &flags, opts, argv[1])) {
2463 ast_log(LOG_ERROR, "Can't parse option %s\n", argv[1]);
2468 pbx_builtin_setvar_helper(chan, "MVM_ACCMESS_STATUS", "FAILED");
2472 ast_copy_string(tmp, argv[0], sizeof(tmp));
2474 domain = strchr(tmp, '@');
2479 if (ast_strlen_zero(domain) || ast_strlen_zero(username)) {
2480 ast_log(LOG_ERROR, "Need username@domain as argument. Sorry. Argument 0 %s\n", argv[0]);
2481 pbx_builtin_setvar_helper(chan, "MVM_ACCMESS_STATUS", "FAILED");
2485 if(!(vmu = find_account(domain, username, TRUE))) {
2486 /* We could not find user, let's exit */
2487 ast_log(LOG_WARNING, "Could not allocate temporary memory for '%s@%s'\n", username, domain);
2488 pbx_builtin_setvar_helper(chan, "MVM_ACCMESS_STATUS", "FAILED");
2492 /* Answer channel if it's not already answered */
2493 if (chan->_state != AST_STATE_UP)
2496 /* Here's where the action is */
2497 if (ast_test_flag(&flags, OPT_BUSY_GREETING)) {
2499 prompt = "vm-rec-busy";
2500 } else if (ast_test_flag(&flags, OPT_UNAVAIL_GREETING)) {
2501 message = "unavailable";
2502 prompt = "vm-rec-unv";
2503 } else if (ast_test_flag(&flags, OPT_TEMP_GREETING)) {
2505 prompt = "vm-rec-temp";
2506 } else if (ast_test_flag(&flags, OPT_NAME_GREETING)) {
2508 prompt = "vm-rec-name";
2510 snprintf(filename,sizeof(filename), "%s%s/%s/%s", MVM_SPOOL_DIR, vmu->domain, vmu->username, message);
2511 /* Maybe we should check the result of play_record_review ? */
2512 cmd = play_record_review(chan, prompt, filename, global_maxgreet, default_vmformat, 0, vmu, &duration, NULL, FALSE);
2514 ast_debug(1, "Recorded new %s message in %s (duration %d)\n", message, filename, duration);
2516 if(ast_test_flag(vmu, MVM_ALLOCED))
2519 pbx_builtin_setvar_helper(chan, "MVM_ACCMESS_STATUS", "SUCCESS");
2521 /* Ok, we're ready to rock and roll. Return to dialplan */
2525 /*! \brief Append new mailbox to mailbox list from configuration file */
2526 static int create_vmaccount(char *name, struct ast_variable *var, int realtime)
2528 struct minivm_account *vmu;
2531 char accbuf[BUFSIZ];
2533 ast_debug(3, "Creating %s account for [%s]\n", realtime ? "realtime" : "static", name);
2535 ast_copy_string(accbuf, name, sizeof(accbuf));
2537 domain = strchr(accbuf, '@');
2542 if (ast_strlen_zero(domain)) {
2543 ast_log(LOG_ERROR, "No domain given for mini-voicemail account %s. Not configured.\n", name);
2547 ast_debug(3, "Creating static account for user %s domain %s\n", username, domain);
2549 /* Allocate user account */
2550 vmu = ast_calloc(1, sizeof(*vmu));
2554 ast_copy_string(vmu->domain, domain, sizeof(vmu->domain));
2555 ast_copy_string(vmu->username, username, sizeof(vmu->username));
2557 populate_defaults(vmu);
2559 ast_debug(3, "...Configuring account %s\n", name);
2562 ast_debug(3, "Configuring %s = \"%s\" for account %s\n", var->name, var->value, name);
2563 if (!strcasecmp(var->name, "serveremail")) {
2564 ast_copy_string(vmu->serveremail, var->value, sizeof(vmu->serveremail));
2565 } else if (!strcasecmp(var->name, "email")) {
2566 ast_copy_string(vmu->email, var->value, sizeof(vmu->email));
2567 } else if (!strcasecmp(var->name, "accountcode")) {
2568 ast_copy_string(vmu->accountcode, var->value, sizeof(vmu->accountcode));
2569 } else if (!strcasecmp(var->name, "pincode")) {
2570 ast_copy_string(vmu->pincode, var->value, sizeof(vmu->pincode));
2571 } else if (!strcasecmp(var->name, "domain")) {
2572 ast_copy_string(vmu->domain, var->value, sizeof(vmu->domain));
2573 } else if (!strcasecmp(var->name, "language")) {
2574 ast_copy_string(vmu->language, var->value, sizeof(vmu->language));
2575 } else if (!strcasecmp(var->name, "timezone")) {
2576 ast_copy_string(vmu->zonetag, var->value, sizeof(vmu->zonetag));
2577 } else if (!strcasecmp(var->name, "externnotify")) {
2578 ast_copy_string(vmu->externnotify, var->value, sizeof(vmu->externnotify));
2579 } else if (!strcasecmp(var->name, "etemplate")) {
2580 ast_copy_string(vmu->etemplate, var->value, sizeof(vmu->etemplate));
2581 } else if (!strcasecmp(var->name, "ptemplate")) {
2582 ast_copy_string(vmu->ptemplate, var->value, sizeof(vmu->ptemplate));
2583 } else if (!strcasecmp(var->name, "fullname")) {
2584 ast_copy_string(vmu->fullname, var->value, sizeof(vmu->fullname));
2585 } else if (!strcasecmp(var->name, "setvar")) {
2587 char *varname = ast_strdupa(var->value);
2588 struct ast_variable *tmpvar;
2590 if (varname && (varval = strchr(varname, '='))) {
2593 if ((tmpvar = ast_variable_new(varname, varval, ""))) {
2594 tmpvar->next = vmu->chanvars;
2595 vmu->chanvars = tmpvar;
2598 } else if (!strcasecmp(var->name, "pager")) {
2599 ast_copy_string(vmu->pager, var->value, sizeof(vmu->pager));
2600 } else if (!strcasecmp(var->name, "volgain")) {
2601 sscanf(var->value, "%lf", &vmu->volgain);
2603 ast_log(LOG_ERROR, "Unknown configuration option for minivm account %s : %s\n", name, var->name);
2607 ast_debug(3, "...Linking account %s\n", name);
2609 AST_LIST_LOCK(&minivm_accounts);
2610 AST_LIST_INSERT_TAIL(&minivm_accounts, vmu, list);
2611 AST_LIST_UNLOCK(&minivm_accounts);
2613 global_stats.voicemailaccounts++;
2615 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)" : "");
2619 /*! \brief Free Mini Voicemail timezone */
2620 static void free_zone(struct minivm_zone *z)
2625 /*! \brief Clear list of timezones */
2626 static void timezone_destroy_list(void)
2628 struct minivm_zone *this;
2630 AST_LIST_LOCK(&minivm_zones);
2631 while ((this = AST_LIST_REMOVE_HEAD(&minivm_zones, list)))
2634 AST_LIST_UNLOCK(&minivm_zones);
2637 /*! \brief Add time zone to memory list */
2638 static int timezone_add(const char *zonename, const char *config)
2640 struct minivm_zone *newzone;
2641 char *msg_format, *timezone_str;
2643 newzone = ast_calloc(1, sizeof(*newzone));
2644 if (newzone == NULL)
2647 msg_format = ast_strdupa(config);
2648 if (msg_format == NULL) {
2649 ast_log(LOG_WARNING, "Out of memory.\n");
2654 timezone_str = strsep(&msg_format, "|");
2656 ast_log(LOG_WARNING, "Invalid timezone definition : %s\n", zonename);
2661 ast_copy_string(newzone->name, zonename, sizeof(newzone->name));
2662 ast_copy_string(newzone->timezone, timezone_str, sizeof(newzone->timezone));
2663 ast_copy_string(newzone->msg_format, msg_format, sizeof(newzone->msg_format));
2665 AST_LIST_LOCK(&minivm_zones);
2666 AST_LIST_INSERT_TAIL(&minivm_zones, newzone, list);
2667 AST_LIST_UNLOCK(&minivm_zones);
2669 global_stats.timezones++;
2674 /*! \brief Read message template from file */
2675 static char *message_template_parse_filebody(const char *filename) {
2676 char buf[BUFSIZ * 6];
2677 char readbuf[BUFSIZ];
2678 char filenamebuf[BUFSIZ];
2684 if (ast_strlen_zero(filename))
2686 if (*filename == '/')
2687 ast_copy_string(filenamebuf, filename, sizeof(filenamebuf));
2689 snprintf(filenamebuf, sizeof(filenamebuf), "%s/%s", ast_config_AST_CONFIG_DIR, filename);
2691 if (!(fi = fopen(filenamebuf, "r"))) {
2692 ast_log(LOG_ERROR, "Can't read message template from file: %s\n", filenamebuf);
2696 while (fgets(readbuf, sizeof(readbuf), fi)) {
2698 if (writepos != buf) {
2699 *writepos = '\n'; /* Replace EOL with new line */
2702 ast_copy_string(writepos, readbuf, sizeof(buf) - (writepos - buf));
2703 writepos += strlen(readbuf) - 1;
2706 messagebody = ast_calloc(1, strlen(buf + 1));
2707 ast_copy_string(messagebody, buf, strlen(buf) + 1);
2708 ast_debug(4, "---> Size of allocation %d\n", (int) strlen(buf + 1) );
2709 ast_debug(4, "---> Done reading message template : \n%s\n---- END message template--- \n", messagebody);
2714 /*! \brief Parse emailbody template from configuration file */
2715 static char *message_template_parse_emailbody(const char *configuration)
2717 char *tmpread, *tmpwrite;
2718 char *emailbody = ast_strdup(configuration);
2720 /* substitute strings \t and \n into the apropriate characters */
2721 tmpread = tmpwrite = emailbody;
2722 while ((tmpwrite = strchr(tmpread,'\\'))) {
2723 int len = strlen("\n");
2724 switch (tmpwrite[1]) {
2726 memmove(tmpwrite + len, tmpwrite + 2, strlen(tmpwrite + 2) + 1);
2727 strncpy(tmpwrite, "\n", len);
2730 memmove(tmpwrite + len, tmpwrite + 2, strlen(tmpwrite + 2) + 1);
2731 strncpy(tmpwrite, "\t", len);
2734 ast_log(LOG_NOTICE, "Substitution routine does not support this character: %c\n", tmpwrite[1]);
2736 tmpread = tmpwrite + len;
2741 /*! \brief Apply general configuration options */
2742 static int apply_general_options(struct ast_variable *var)
2748 if (!strcmp(var->name, "mailcmd")) {
2749 ast_copy_string(global_mailcmd, var->value, sizeof(global_mailcmd)); /* User setting */
2750 } else if (!strcmp(var->name, "maxgreet")) {
2751 global_maxgreet = atoi(var->value);
2752 } else if (!strcmp(var->name, "maxsilence")) {
2753 global_maxsilence = atoi(var->value);
2754 if (global_maxsilence > 0)
2755 global_maxsilence *= 1000;
2756 } else if (!strcmp(var->name, "logfile")) {
2757 if (!ast_strlen_zero(var->value) ) {
2758 if(*(var->value) == '/')
2759 ast_copy_string(global_logfile, var->value, sizeof(global_logfile));
2761 snprintf(global_logfile, sizeof(global_logfile), "%s/%s", ast_config_AST_LOG_DIR, var->value);
2763 } else if (!strcmp(var->name, "externnotify")) {
2764 /* External voicemail notify application */
2765 ast_copy_string(global_externnotify, var->value, sizeof(global_externnotify));
2766 } else if (!strcmp(var->name, "silencetreshold")) {
2767 /* Silence treshold */
2768 global_silencethreshold = atoi(var->value);
2769 } else if (!strcmp(var->name, "maxmessage")) {
2771 if (sscanf(var->value, "%d", &x) == 1) {
2772 global_vmmaxmessage = x;
2775 ast_log(LOG_WARNING, "Invalid max message time length\n");
2777 } else if (!strcmp(var->name, "minmessage")) {
2779 if (sscanf(var->value, "%d", &x) == 1) {
2780 global_vmminmessage = x;
2781 if (global_maxsilence <= global_vmminmessage)
2782 ast_log(LOG_WARNING, "maxsilence should be less than minmessage or you may get empty messages\n");
2785 ast_log(LOG_WARNING, "Invalid min message time length\n");
2787 } else if (!strcmp(var->name, "format")) {
2788 ast_copy_string(default_vmformat, var->value, sizeof(default_vmformat));
2789 } else if (!strcmp(var->name, "review")) {
2790 ast_set2_flag((&globalflags), ast_true(var->value), MVM_REVIEW);
2791 } else if (!strcmp(var->name, "operator")) {
2792 ast_set2_flag((&globalflags), ast_true(var->value), MVM_OPERATOR);
2799 /*! \brief Load minivoicemail configuration */
2800 static int load_config(int reload)
2802 struct ast_config *cfg;
2803 struct ast_variable *var;
2805 const char *chanvar;
2807 struct minivm_template *template;
2808 struct ast_flags config_flags = { reload ? CONFIG_FLAG_FILEUNCHANGED : 0 };
2810 cfg = ast_config_load(VOICEMAIL_CONFIG, config_flags);
2811 if (cfg == CONFIG_STATUS_FILEUNCHANGED) {
2813 } else if (cfg == CONFIG_STATUS_FILEINVALID) {
2814 ast_log(LOG_ERROR, "Config file " VOICEMAIL_CONFIG " is in an invalid format. Aborting.\n");
2818 ast_mutex_lock(&minivmlock);
2820 /* Destroy lists to reconfigure */
2821 message_destroy_list(); /* Destroy list of voicemail message templates */
2822 timezone_destroy_list(); /* Destroy list of timezones */
2823 vmaccounts_destroy_list(); /* Destroy list of voicemail accounts */
2824 ast_debug(2, "Destroyed memory objects...\n");
2826 /* First, set some default settings */
2827 global_externnotify[0] = '\0';
2828 global_logfile[0] = '\0';
2829 global_vmmaxmessage = 2000;
2830 global_maxgreet = 2000;
2831 global_vmminmessage = 0;
2832 strcpy(global_mailcmd, SENDMAIL);
2833 global_maxsilence = 0;
2834 global_saydurationminfo = 2;
2835 ast_copy_string(default_vmformat, "wav", sizeof(default_vmformat));
2836 ast_set2_flag((&globalflags), FALSE, MVM_REVIEW);
2837 ast_set2_flag((&globalflags), FALSE, MVM_OPERATOR);
2838 /* Reset statistics */
2839 memset(&global_stats, 0, sizeof(global_stats));
2840 global_stats.reset = ast_tvnow();
2842 global_silencethreshold = ast_dsp_get_threshold_from_settings(THRESHOLD_SILENCE);
2844 /* Make sure we could load configuration file */
2846 ast_log(LOG_WARNING, "Failed to load configuration file. Module activated with default settings.\n");
2847 ast_mutex_unlock(&minivmlock);
2851 ast_debug(2, "Loaded configuration file, now parsing\n");
2853 /* General settings */
2855 cat = ast_category_browse(cfg, NULL);
2857 ast_debug(3, "Found configuration section [%s]\n", cat);
2858 if (!strcasecmp(cat, "general")) {
2859 /* Nothing right now */
2860 error += apply_general_options(ast_variable_browse(cfg, cat));
2861 } else if (!strncasecmp(cat, "template-", 9)) {
2863 char *name = cat + 9;
2865 /* Now build and link template to list */
2866 error += message_template_build(name, ast_variable_browse(cfg, cat));
2868 var = ast_variable_browse(cfg, cat);
2869 if (!strcasecmp(cat, "zonemessages")) {
2870 /* Timezones in this context */
2872 timezone_add(var->name, var->value);
2876 /* Create mailbox from this */
2877 error += create_vmaccount(cat, var, FALSE);
2880 /* Find next section in configuration file */
2881 cat = ast_category_browse(cfg, cat);
2884 /* Configure the default email template */
2885 message_template_build("email-default", NULL);
2886 template = message_template_find("email-default");
2888 /* Load date format config for voicemail mail */
2889 if ((chanvar = ast_variable_retrieve(cfg, "general", "emaildateformat")))
2890 ast_copy_string(template->dateformat, chanvar, sizeof(template->dateformat));
2891 if ((chanvar = ast_variable_retrieve(cfg, "general", "emailfromstring")))
2892 ast_copy_string(template->fromaddress, chanvar, sizeof(template->fromaddress));
2893 if ((chanvar = ast_variable_retrieve(cfg, "general", "emailaaddress")))
2894 ast_copy_string(template->serveremail, chanvar, sizeof(template->serveremail));
2895 if ((chanvar = ast_variable_retrieve(cfg, "general", "emailcharset")))
2896 ast_copy_string(template->charset, chanvar, sizeof(template->charset));
2897 if ((chanvar = ast_variable_retrieve(cfg, "general", "emailsubject")))
2898 ast_copy_string(template->subject, chanvar, sizeof(template->subject));
2899 if ((chanvar = ast_variable_retrieve(cfg, "general", "emailbody")))
2900 template->body = message_template_parse_emailbody(chanvar);
2901 template->attachment = TRUE;
2903 message_template_build("pager-default", NULL);
2904 template = message_template_find("pager-default");
2905 if ((chanvar = ast_variable_retrieve(cfg, "general", "pagerfromstring")))
2906 ast_copy_string(template->fromaddress, chanvar, sizeof(template->fromaddress));
2907 if ((chanvar = ast_variable_retrieve(cfg, "general", "pageraddress")))
2908 ast_copy_string(template->serveremail, chanvar, sizeof(template->serveremail));
2909 if ((chanvar = ast_variable_retrieve(cfg, "general", "pagercharset")))
2910 ast_copy_string(template->charset, chanvar, sizeof(template->charset));
2911 if ((chanvar = ast_variable_retrieve(cfg, "general", "pagersubject")))
2912 ast_copy_string(template->subject, chanvar,sizeof(template->subject));
2913 if ((chanvar = ast_variable_retrieve(cfg, "general", "pagerbody")))
2914 template->body = message_template_parse_emailbody(chanvar);
2915 template->attachment = FALSE;
2918 ast_log(LOG_ERROR, "--- A total of %d errors found in mini-voicemail configuration\n", error);
2920 ast_mutex_unlock(&minivmlock);
2921 ast_config_destroy(cfg);
2923 /* Close log file if it's open and disabled */
2925 fclose(minivmlogfile);
2927 /* Open log file if it's enabled */
2928 if(!ast_strlen_zero(global_logfile)) {
2929 minivmlogfile = fopen(global_logfile, "a");
2931 ast_log(LOG_ERROR, "Failed to open minivm log file %s : %s\n", global_logfile, strerror(errno));
2933 ast_debug(3, "Opened log file %s \n", global_logfile);
2939 /*! \brief CLI routine for listing templates */
2940 static char *handle_minivm_list_templates(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
2942 struct minivm_template *this;
2943 #define HVLT_OUTPUT_FORMAT "%-15s %-10s %-10s %-15.15s %-50s\n"
2948 e->command = "minivm list templates";
2950 "Usage: minivm list templates\n"
2951 " Lists message templates for e-mail, paging and IM\n";
2958 return CLI_SHOWUSAGE;
2960 AST_LIST_LOCK(&message_templates);
2961 if (AST_LIST_EMPTY(&message_templates)) {
2962 ast_cli(a->fd, "There are no message templates defined\n");
2963 AST_LIST_UNLOCK(&message_templates);
2966 ast_cli(a->fd, HVLT_OUTPUT_FORMAT, "Template name", "Charset", "Locale", "Attach media", "Subject");
2967 ast_cli(a->fd, HVLT_OUTPUT_FORMAT, "-------------", "-------", "------", "------------", "-------");
2968 AST_LIST_TRAVERSE(&message_templates, this, list) {
2969 ast_cli(a->fd, HVLT_OUTPUT_FORMAT, this->name,
2970 this->charset ? this->charset : "-",
2971 this->locale ? this->locale : "-",
2972 this->attachment ? "Yes" : "No",
2973 this->subject ? this->subject : "-");
2976 AST_LIST_UNLOCK(&message_templates);
2977 ast_cli(a->fd, "\n * Total: %d minivoicemail message templates\n", count);
2981 static char *complete_minivm_show_users(const char *line, const char *word, int pos, int state)
2985 struct minivm_account *vmu;
2986 const char *domain = "";
2988 /* 0 - voicemail; 1 - list; 2 - accounts; 3 - for; 4 - <domain> */
2992 return (state == 0) ? ast_strdup("for") : NULL;
2993 wordlen = strlen(word);
2994 AST_LIST_TRAVERSE(&minivm_accounts, vmu, list) {
2995 if (!strncasecmp(word, vmu->domain, wordlen)) {
2996 if (domain && strcmp(domain, vmu->domain) && ++which > state)
2997 return ast_strdup(vmu->domain);
2998 /* ignore repeated domains ? */
2999 domain = vmu->domain;
3005 /*! \brief CLI command to list voicemail accounts */
3006 static char *handle_minivm_show_users(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
3008 struct minivm_account *vmu;
3009 #define HMSU_OUTPUT_FORMAT "%-23s %-15s %-15s %-10s %-10s %-50s\n"
3014 e->command = "minivm list accounts";
3016 "Usage: minivm list accounts\n"
3017 " Lists all mailboxes currently set up\n";
3020 return complete_minivm_show_users(a->line, a->word, a->pos, a->n);
3023 if ((a->argc < 3) || (a->argc > 5) || (a->argc == 4))
3024 return CLI_SHOWUSAGE;
3025 if ((a->argc == 5) && strcmp(a->argv[3],"for"))
3026 return CLI_SHOWUSAGE;
3028 AST_LIST_LOCK(&minivm_accounts);
3029 if (AST_LIST_EMPTY(&minivm_accounts)) {
3030 ast_cli(a->fd, "There are no voicemail users currently defined\n");
3031 AST_LIST_UNLOCK(&minivm_accounts);
3034 ast_cli(a->fd, HMSU_OUTPUT_FORMAT, "User", "E-Template", "P-template", "Zone", "Format", "Full name");
3035 ast_cli(a->fd, HMSU_OUTPUT_FORMAT, "----", "----------", "----------", "----", "------", "---------");
3036 AST_LIST_TRAVERSE(&minivm_accounts, vmu, list) {
3038 if ((a->argc == 3) || ((a->argc == 5) && !strcmp(a->argv[4], vmu->domain))) {
3040 snprintf(tmp, sizeof(tmp), "%s@%s", vmu->username, vmu->domain);
3041 ast_cli(a->fd, HMSU_OUTPUT_FORMAT, tmp, vmu->etemplate ? vmu->etemplate : "-",
3042 vmu->ptemplate ? vmu->ptemplate : "-",
3043 vmu->zonetag ? vmu->zonetag : "-",
3044 vmu->attachfmt ? vmu->attachfmt : "-",
3048 AST_LIST_UNLOCK(&minivm_accounts);
3049 ast_cli(a->fd, "\n * Total: %d minivoicemail accounts\n", count);
3053 /*! \brief Show a list of voicemail zones in the CLI */
3054 static char *handle_minivm_show_zones(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
3056 struct minivm_zone *zone;
3057 #define HMSZ_OUTPUT_FORMAT "%-15s %-20s %-45s\n"
3058 char *res = CLI_SUCCESS;
3062 e->command = "minivm list zones";
3064 "Usage: minivm list zones\n"
3065 " Lists zone message formats\n";
3071 if (a->argc != e->args)
3072 return CLI_SHOWUSAGE;
3074 AST_LIST_LOCK(&minivm_zones);
3075 if (!AST_LIST_EMPTY(&minivm_zones)) {
3076 ast_cli(a->fd, HMSZ_OUTPUT_FORMAT, "Zone", "Timezone", "Message Format");
3077 ast_cli(a->fd, HMSZ_OUTPUT_FORMAT, "----", "--------", "--------------");
3078 AST_LIST_TRAVERSE(&minivm_zones, zone, list) {
3079 ast_cli(a->fd, HMSZ_OUTPUT_FORMAT, zone->name, zone->timezone, zone->msg_format);
3082 ast_cli(a->fd, "There are no voicemail zones currently defined\n");
3085 AST_LIST_UNLOCK(&minivm_zones);