app_minivm.c

Go to the documentation of this file.
00001 /*
00002  * Asterisk -- An open source telephony toolkit.
00003  *
00004  * Copyright (C) 1999 - 2005, Digium, Inc.
00005  * and Edvina AB, Sollentuna, Sweden
00006  *
00007  * Mark Spencer <markster@digium.com> (Comedian Mail)
00008  * and Olle E. Johansson, Edvina.net <oej@edvina.net> (Mini-Voicemail changes)
00009  *
00010  * See http://www.asterisk.org for more information about
00011  * the Asterisk project. Please do not directly contact
00012  * any of the maintainers of this project for assistance;
00013  * the project provides a web site, mailing lists and IRC
00014  * channels for your use.
00015  *
00016  * This program is free software, distributed under the terms of
00017  * the GNU General Public License Version 2. See the LICENSE file
00018  * at the top of the source tree.
00019  */
00020 
00021 /*! \file
00022  *
00023  * \brief MiniVoiceMail - A Minimal Voicemail System for Asterisk
00024  *
00025  * A voicemail system in small building blocks, working together
00026  * based on the Comedian Mail voicemail system (app_voicemail.c).
00027  * 
00028  * \par See also
00029  * \arg \ref Config_minivm_examples
00030  * \arg \ref App_minivm
00031  *
00032  * \ingroup applications
00033  *
00034  * \page App_minivm  Asterisk Mini-voicemail - A minimal voicemail system
00035  * 
00036  * This is a minimal voicemail system, building blocks for something
00037  * else. It is built for multi-language systems.
00038  * The current version is focused on accounts where voicemail is 
00039  * forwarded to users in e-mail. It's work in progress, with loosed ends hanging
00040  * around from the old voicemail system and it's configuration.
00041  *
00042  * Hopefully, we can expand this to be a full replacement of voicemail() and voicemailmain()
00043  * in the future.
00044  *
00045  * Dialplan applications
00046  * - minivmRecord - record voicemail and send as e-mail ( \ref minivm_record_exec() )
00047  * - minivmGreet - Play user's greeting or default greeting ( \ref minivm_greet_exec() )
00048  * - minivmNotify - Notify user of message ( \ref minivm_notify_exec() )
00049  *    - minivmDelete - Delete voicemail message ( \ref minivm_delete_exec() )
00050  * - minivmAccMess - Record personal messages (busy | unavailable | temporary)
00051  *
00052  * Dialplan functions
00053  * - MINIVMACCOUNT() - A dialplan function
00054  * - MINIVMCOUNTER() - Manage voicemail-related counters for accounts or domains
00055  *
00056  * CLI Commands
00057  * - minivm list accounts
00058  * - minivm list zones
00059  * - minivm list templates
00060  * - minivm show stats
00061  * - minivm show settings
00062  *
00063  * Some notes
00064  * - General configuration in minivm.conf
00065  * - Users in realtime or configuration file
00066  * - Or configured on the command line with just the e-mail address
00067  *    
00068  * Voicemail accounts are identified by userid and domain
00069  *
00070  * Language codes are like setlocale - langcode_countrycode
00071  * \note Don't use language codes like the rest of Asterisk, two letter countrycode. Use
00072  * language_country like setlocale(). 
00073  * 
00074  * Examples:
00075  *    - Swedish, Sweden sv_se
00076  *    - Swedish, Finland   sv_fi
00077  *    - English, USA    en_us
00078  *    - English, GB     en_gb
00079  * 
00080  * \par See also
00081  * \arg \ref Config_minivm
00082  * \arg \ref Config_minivm_examples
00083  * \arg \ref Minivm_directories
00084  * \arg \ref app_minivm.c
00085  * \arg Comedian mail: app_voicemail.c
00086  * \arg \ref descrip_minivm_accmess
00087  * \arg \ref descrip_minivm_greet
00088  * \arg \ref descrip_minivm_record
00089  * \arg \ref descrip_minivm_delete
00090  * \arg \ref descrip_minivm_notify
00091  *
00092  * \arg \ref App_minivm_todo
00093  */
00094 /*! \page Minivm_directories Asterisk Mini-Voicemail Directory structure
00095  *
00096  * The directory structure for storing voicemail
00097  *    - AST_SPOOL_DIR - usually /var/spool/asterisk (configurable in asterisk.conf)
00098  *    - MVM_SPOOL_DIR - should be configurable, usually AST_SPOOL_DIR/voicemail
00099  *    - Domain MVM_SPOOL_DIR/domain
00100  *    - Username  MVM_SPOOL_DIR/domain/username
00101  *       - /greet : Recording of account owner's name
00102  *       - /busy     : Busy message
00103  *       - /unavailable  : Unavailable message
00104  *       - /temp     : Temporary message
00105  *
00106  * For account anita@localdomain.xx the account directory would as a default be
00107  *    \b /var/spool/asterisk/voicemail/localdomain.xx/anita
00108  *
00109  * To avoid transcoding, these sound files should be converted into several formats
00110  * They are recorded in the format closest to the incoming streams
00111  *
00112  *
00113  * Back: \ref App_minivm
00114  */
00115 
00116 /*! \page Config_minivm_examples Example dialplan for Mini-Voicemail
00117  * \section Example dialplan scripts for Mini-Voicemail
00118  *  \verbinclude extensions_minivm.conf.sample
00119  *
00120  * Back: \ref App_minivm
00121  */
00122 
00123 /*! \page App_minivm_todo Asterisk Mini-Voicemail - todo
00124  * - configure accounts from AMI?
00125  * - test, test, test, test
00126  * - fix "vm-theextensionis.gsm" voiceprompt from Allison in various formats
00127  *    "The extension you are calling"
00128  * - For trunk, consider using channel storage for information passing between small applications
00129  * - Set default directory for voicemail
00130  * - New app for creating directory for account if it does not exist
00131  * - Re-insert code for IMAP storage at some point
00132  * - Jabber integration for notifications
00133  * - Figure out how to handle video in voicemail
00134  * - Integration with the HTTP server
00135  * - New app for moving messages between mailboxes, and optionally mark it as "new"
00136  *
00137  * For Asterisk 1.4/trunk
00138  * - Use string fields for minivm_account
00139  *
00140  * Back: \ref App_minivm
00141  */
00142 
00143 /*** MODULEINFO
00144    <support_level>extended</support_level>
00145  ***/
00146 
00147 #include "asterisk.h"
00148 
00149 ASTERISK_FILE_VERSION(__FILE__, "$Revision: 434287 $")
00150 
00151 #include <ctype.h>
00152 #include <sys/time.h>
00153 #include <sys/stat.h>
00154 #include <sys/mman.h>
00155 #include <time.h>
00156 #include <dirent.h>
00157 #include <locale.h>
00158 
00159 
00160 #include "asterisk/paths.h"   /* use various paths */
00161 #include "asterisk/lock.h"
00162 #include "asterisk/file.h"
00163 #include "asterisk/channel.h"
00164 #include "asterisk/pbx.h"
00165 #include "asterisk/config.h"
00166 #include "asterisk/say.h"
00167 #include "asterisk/module.h"
00168 #include "asterisk/app.h"
00169 #include "asterisk/dsp.h"
00170 #include "asterisk/localtime.h"
00171 #include "asterisk/cli.h"
00172 #include "asterisk/utils.h"
00173 #include "asterisk/linkedlists.h"
00174 #include "asterisk/callerid.h"
00175 #include "asterisk/stasis.h"
00176 #include "asterisk/stasis_channels.h"
00177 #include "asterisk/json.h"
00178 
00179 /*** DOCUMENTATION
00180 <application name="MinivmRecord" language="en_US">
00181    <synopsis>
00182       Receive Mini-Voicemail and forward via e-mail.
00183    </synopsis>
00184    <syntax>
00185       <parameter name="mailbox" required="true" argsep="@">
00186          <argument name="username" required="true">
00187             <para>Voicemail username</para>
00188          </argument>
00189          <argument name="domain" required="true">
00190             <para>Voicemail domain</para>
00191          </argument>
00192       </parameter>
00193       <parameter name="options" required="false">
00194          <optionlist>
00195             <option name="0">
00196                <para>Jump to the <literal>o</literal> extension in the current dialplan context.</para>
00197             </option>
00198             <option name="*">
00199                <para>Jump to the <literal>a</literal> extension in the current dialplan context.</para>
00200             </option>
00201             <option name="g">
00202                <argument name="gain">
00203                   <para>Amount of gain to use</para>
00204                </argument>
00205                <para>Use the specified amount of gain when recording the voicemail message.
00206                The units are whole-number decibels (dB).</para>
00207             </option>
00208          </optionlist>
00209       </parameter>
00210    </syntax>
00211    <description>
00212       <para>This application is part of the Mini-Voicemail system, configured in <filename>minivm.conf</filename></para>
00213       <para>MiniVM records audio file in configured format and forwards message to e-mail and pager.</para>
00214       <para>If there's no user account for that address, a temporary account will be used with default options.</para>
00215       <para>The recorded file name and path will be stored in <variable>MVM_FILENAME</variable> and the duration
00216       of the message will be stored in <variable>MVM_DURATION</variable></para>
00217       <note><para>If the caller hangs up after the recording, the only way to send the message and clean up is to
00218       execute in the <literal>h</literal> extension. The application will exit if any of the following DTMF digits
00219       are received and the requested extension exist in the current context.</para></note>
00220       <variablelist>
00221          <variable name="MVM_RECORD_STATUS">
00222             <para>This is the status of the record operation</para>
00223             <value name="SUCCESS" />
00224             <value name="USEREXIT" />
00225             <value name="FAILED" />
00226          </variable>
00227       </variablelist>
00228    </description>
00229 </application>
00230 <application name="MinivmGreet" language="en_US">
00231    <synopsis>
00232       Play Mini-Voicemail prompts.
00233    </synopsis>
00234    <syntax>
00235       <parameter name="mailbox" required="true" argsep="@">
00236          <argument name="username" required="true">
00237             <para>Voicemail username</para>
00238          </argument>
00239          <argument name="domain" required="true">
00240             <para>Voicemail domain</para>
00241          </argument>
00242       </parameter>
00243       <parameter name="options" required="false">
00244          <optionlist>
00245             <option name="b">
00246                <para>Play the <literal>busy</literal> greeting to the calling party.</para>
00247             </option>
00248             <option name="s">
00249                <para>Skip the playback of instructions for leaving a message to the calling party.</para>
00250             </option>
00251             <option name="u">
00252                <para>Play the <literal>unavailable</literal> greeting.</para>
00253             </option>
00254          </optionlist>
00255       </parameter>
00256    </syntax>
00257    <description>
00258       <para>This application is part of the Mini-Voicemail system, configured in minivm.conf.</para>
00259       <para>MinivmGreet() plays default prompts or user specific prompts for an account.</para>
00260       <para>Busy and unavailable messages can be choosen, but will be overridden if a temporary
00261       message exists for the account.</para>
00262       <variablelist>
00263          <variable name="MVM_GREET_STATUS">
00264             <para>This is the status of the greeting playback.</para>
00265             <value name="SUCCESS" />
00266             <value name="USEREXIT" />
00267             <value name="FAILED" />
00268          </variable>
00269       </variablelist>
00270    </description>
00271 </application>
00272 <application name="MinivmNotify" language="en_US">
00273    <synopsis>
00274       Notify voicemail owner about new messages.
00275    </synopsis>
00276    <syntax>
00277       <parameter name="mailbox" required="true" argsep="@">
00278          <argument name="username" required="true">
00279             <para>Voicemail username</para>
00280          </argument>
00281          <argument name="domain" required="true">
00282             <para>Voicemail domain</para>
00283          </argument>
00284       </parameter>
00285       <parameter name="options" required="false">
00286          <optionlist>
00287             <option name="template">
00288                <para>E-mail template to use for voicemail notification</para>
00289             </option>
00290          </optionlist>
00291       </parameter>
00292    </syntax>
00293    <description>
00294       <para>This application is part of the Mini-Voicemail system, configured in minivm.conf.</para>
00295       <para>MiniVMnotify forwards messages about new voicemail to e-mail and pager. If there's no user
00296       account for that address, a temporary account will be used with default options (set in
00297       <filename>minivm.conf</filename>).</para>
00298       <para>If the channel variable <variable>MVM_COUNTER</variable> is set, this will be used in the message
00299       file name and available in the template for the message.</para>
00300       <para>If no template is given, the default email template will be used to send email and default pager
00301       template to send paging message (if the user account is configured with a paging address.</para>
00302       <variablelist>
00303          <variable name="MVM_NOTIFY_STATUS">
00304             <para>This is the status of the notification attempt</para>
00305             <value name="SUCCESS" />
00306             <value name="FAILED" />
00307          </variable>
00308       </variablelist>
00309    </description>
00310 </application>
00311 <application name="MinivmDelete" language="en_US">
00312    <synopsis>
00313       Delete Mini-Voicemail voicemail messages.
00314    </synopsis>
00315    <syntax>
00316       <parameter name="filename" required="true">
00317          <para>File to delete</para>
00318       </parameter>
00319    </syntax>
00320    <description>
00321       <para>This application is part of the Mini-Voicemail system, configured in <filename>minivm.conf</filename>.</para>
00322       <para>It deletes voicemail file set in MVM_FILENAME or given filename.</para>
00323       <variablelist>
00324          <variable name="MVM_DELETE_STATUS">
00325             <para>This is the status of the delete operation.</para>
00326             <value name="SUCCESS" />
00327             <value name="FAILED" />
00328          </variable>
00329       </variablelist>
00330    </description>
00331 </application>
00332 
00333 <application name="MinivmAccMess" language="en_US">
00334    <synopsis>
00335       Record account specific messages.
00336    </synopsis>
00337    <syntax>
00338       <parameter name="mailbox" required="true" argsep="@">
00339          <argument name="username" required="true">
00340             <para>Voicemail username</para>
00341          </argument>
00342          <argument name="domain" required="true">
00343             <para>Voicemail domain</para>
00344          </argument>
00345       </parameter>
00346       <parameter name="options" required="false">
00347          <optionlist>
00348             <option name="u">
00349                <para>Record the <literal>unavailable</literal> greeting.</para>
00350             </option>
00351             <option name="b">
00352                <para>Record the <literal>busy</literal> greeting.</para>
00353             </option>
00354             <option name="t">
00355                <para>Record the temporary greeting.</para>
00356             </option>
00357             <option name="n">
00358                <para>Account name.</para>
00359             </option>
00360          </optionlist>
00361       </parameter>
00362    </syntax>
00363    <description>
00364       <para>This application is part of the Mini-Voicemail system, configured in <filename>minivm.conf</filename>.</para>
00365       <para>Use this application to record account specific audio/video messages for busy, unavailable
00366       and temporary messages.</para>
00367       <para>Account specific directories will be created if they do not exist.</para>
00368       <variablelist>
00369          <variable name="MVM_ACCMESS_STATUS">
00370             <para>This is the result of the attempt to record the specified greeting.</para>
00371             <para><literal>FAILED</literal> is set if the file can't be created.</para>
00372             <value name="SUCCESS" />
00373             <value name="FAILED" />
00374          </variable>
00375       </variablelist>
00376    </description>
00377 </application>
00378 <application name="MinivmMWI" language="en_US">
00379    <synopsis>
00380       Send Message Waiting Notification to subscriber(s) of mailbox.
00381    </synopsis>
00382    <syntax>
00383       <parameter name="mailbox" required="true" argsep="@">
00384          <argument name="username" required="true">
00385             <para>Voicemail username</para>
00386          </argument>
00387          <argument name="domain" required="true">
00388             <para>Voicemail domain</para>
00389          </argument>
00390       </parameter>
00391       <parameter name="urgent" required="true">
00392          <para>Number of urgent messages in mailbox.</para>
00393       </parameter>
00394       <parameter name="new" required="true">
00395          <para>Number of new messages in mailbox.</para>
00396       </parameter>
00397       <parameter name="old" required="true">
00398          <para>Number of old messages in mailbox.</para>
00399       </parameter>
00400    </syntax>
00401    <description>
00402       <para>This application is part of the Mini-Voicemail system, configured in <filename>minivm.conf</filename>.</para>
00403       <para>MinivmMWI is used to send message waiting indication to any devices whose channels have
00404       subscribed to the mailbox passed in the first parameter.</para>
00405    </description>
00406 </application>
00407 <function name="MINIVMCOUNTER" language="en_US">
00408    <synopsis>
00409       Reads or sets counters for MiniVoicemail message.
00410    </synopsis>
00411    <syntax argsep=":">
00412       <parameter name="account" required="true">
00413          <para>If account is given and it exists, the counter is specific for the account.</para>
00414          <para>If account is a domain and the domain directory exists, counters are specific for a domain.</para>
00415       </parameter>
00416       <parameter name="name" required="true">
00417          <para>The name of the counter is a string, up to 10 characters.</para>
00418       </parameter>
00419       <parameter name="operand">
00420          <para>The counters never goes below zero. Valid operands for changing the value of a counter when assigning a value are:</para>
00421          <enumlist>
00422             <enum name="i"><para>Increment by value.</para></enum>
00423             <enum name="d"><para>Decrement by value.</para></enum>
00424             <enum name="s"><para>Set to value.</para></enum>
00425          </enumlist>
00426       </parameter>
00427    </syntax>
00428    <description>
00429       <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>
00430    </description>
00431    <see-also>
00432       <ref type="application">MinivmRecord</ref>
00433       <ref type="application">MinivmGreet</ref>
00434       <ref type="application">MinivmNotify</ref>
00435       <ref type="application">MinivmDelete</ref>
00436       <ref type="application">MinivmAccMess</ref>
00437       <ref type="application">MinivmMWI</ref>
00438       <ref type="function">MINIVMACCOUNT</ref>
00439    </see-also>
00440 </function>
00441 <function name="MINIVMACCOUNT" language="en_US">
00442    <synopsis>
00443       Gets MiniVoicemail account information.
00444    </synopsis>
00445    <syntax argsep=":">
00446       <parameter name="account" required="true" />
00447       <parameter name="item" required="true">
00448          <para>Valid items are:</para>
00449          <enumlist>
00450             <enum name="path">
00451                <para>Path to account mailbox (if account exists, otherwise temporary mailbox).</para>
00452             </enum>
00453             <enum name="hasaccount">
00454                <para>1 is static Minivm account exists, 0 otherwise.</para>
00455             </enum>
00456             <enum name="fullname">
00457                <para>Full name of account owner.</para>
00458             </enum>
00459             <enum name="email">
00460                <para>Email address used for account.</para>
00461             </enum>
00462             <enum name="etemplate">
00463                <para>Email template for account (default template if none is configured).</para>
00464             </enum>
00465             <enum name="ptemplate">
00466                <para>Pager template for account (default template if none is configured).</para>
00467             </enum>
00468             <enum name="accountcode">
00469                <para>Account code for the voicemail account.</para>
00470             </enum>
00471             <enum name="pincode">
00472                <para>Pin code for voicemail account.</para>
00473             </enum>
00474             <enum name="timezone">
00475                <para>Time zone for voicemail account.</para>
00476             </enum>
00477             <enum name="language">
00478                <para>Language for voicemail account.</para>
00479             </enum>
00480             <enum name="&lt;channel variable name&gt;">
00481                <para>Channel variable value (set in configuration for account).</para>
00482             </enum>
00483          </enumlist>
00484       </parameter>
00485    </syntax>
00486    <description>
00487       <para />
00488    </description>
00489    <see-also>
00490       <ref type="application">MinivmRecord</ref>
00491       <ref type="application">MinivmGreet</ref>
00492       <ref type="application">MinivmNotify</ref>
00493       <ref type="application">MinivmDelete</ref>
00494       <ref type="application">MinivmAccMess</ref>
00495       <ref type="application">MinivmMWI</ref>
00496       <ref type="function">MINIVMCOUNTER</ref>
00497    </see-also>
00498 </function>
00499    <managerEvent language="en_US" name="MiniVoiceMail">
00500       <managerEventInstance class="EVENT_FLAG_CALL">
00501          <synopsis>Raised when a notification is sent out by a MiniVoiceMail application</synopsis>
00502          <syntax>
00503             <channel_snapshot/>
00504             <parameter name="Action">
00505                <para>What action was taken. Currently, this will always be <literal>SentNotification</literal></para>
00506             </parameter>
00507             <parameter name="Mailbox">
00508                <para>The mailbox that the notification was about, specified as <literal>mailbox</literal>@<literal>context</literal></para>
00509             </parameter>
00510             <parameter name="Counter">
00511                <para>A message counter derived from the <literal>MVM_COUNTER</literal> channel variable.</para>
00512             </parameter>
00513          </syntax>
00514       </managerEventInstance>
00515    </managerEvent>
00516 ***/
00517 
00518 #ifndef TRUE
00519 #define TRUE 1
00520 #endif
00521 #ifndef FALSE
00522 #define FALSE 0
00523 #endif
00524 
00525 
00526 #define MVM_REVIEW      (1 << 0) /*!< Review message */
00527 #define MVM_OPERATOR    (1 << 1) /*!< Operator exit during voicemail recording */
00528 #define MVM_REALTIME    (1 << 2) /*!< This user is a realtime account */
00529 #define MVM_SVMAIL      (1 << 3)
00530 #define MVM_ENVELOPE    (1 << 4)
00531 #define MVM_PBXSKIP     (1 << 9)
00532 #define MVM_ALLOCED     (1 << 13)
00533 
00534 /*! \brief Default mail command to mail voicemail. Change it with the
00535     mailcmd= command in voicemail.conf */
00536 #define SENDMAIL "/usr/sbin/sendmail -t"
00537 
00538 #define SOUND_INTRO     "vm-intro"
00539 #define B64_BASEMAXINLINE  256   /*!< Buffer size for Base 64 attachment encoding */
00540 #define B64_BASELINELEN    72 /*!< Line length for Base 64 endoded messages */
00541 #define EOL       "\r\n"
00542 
00543 #define MAX_DATETIME_FORMAT   512
00544 #define MAX_NUM_CID_CONTEXTS  10
00545 
00546 #define ERROR_LOCK_PATH    -100
00547 #define  VOICEMAIL_DIR_MODE   0700
00548 
00549 #define VOICEMAIL_CONFIG "minivm.conf"
00550 #define ASTERISK_USERNAME "asterisk"   /*!< Default username for sending mail is asterisk\@localhost */
00551 
00552 /*! \brief Message types for notification */
00553 enum mvm_messagetype {
00554    MVM_MESSAGE_EMAIL,
00555    MVM_MESSAGE_PAGE
00556    /* For trunk: MVM_MESSAGE_JABBER, */
00557 };
00558 
00559 static char MVM_SPOOL_DIR[PATH_MAX];
00560 
00561 /* Module declarations */
00562 static char *app_minivm_record = "MinivmRecord";   /* Leave a message */
00563 static char *app_minivm_greet = "MinivmGreet";     /* Play voicemail prompts */
00564 static char *app_minivm_notify = "MinivmNotify";   /* Notify about voicemail by using one of several methods */
00565 static char *app_minivm_delete = "MinivmDelete";   /* Notify about voicemail by using one of several methods */
00566 static char *app_minivm_accmess = "MinivmAccMess"; /* Record personal voicemail messages */
00567 static char *app_minivm_mwi = "MinivmMWI";
00568 
00569 
00570 
00571 enum minivm_option_flags {
00572    OPT_SILENT =      (1 << 0),
00573    OPT_BUSY_GREETING =    (1 << 1),
00574    OPT_UNAVAIL_GREETING = (1 << 2),
00575    OPT_TEMP_GREETING = (1 << 3),
00576    OPT_NAME_GREETING = (1 << 4),
00577    OPT_RECORDGAIN =  (1 << 5),
00578 };
00579 
00580 enum minivm_option_args {
00581    OPT_ARG_RECORDGAIN = 0,
00582    OPT_ARG_ARRAY_SIZE = 1,
00583 };
00584 
00585 AST_APP_OPTIONS(minivm_app_options, {
00586    AST_APP_OPTION('s', OPT_SILENT),
00587    AST_APP_OPTION('b', OPT_BUSY_GREETING),
00588    AST_APP_OPTION('u', OPT_UNAVAIL_GREETING),
00589    AST_APP_OPTION_ARG('g', OPT_RECORDGAIN, OPT_ARG_RECORDGAIN),
00590 });
00591 
00592 AST_APP_OPTIONS(minivm_accmess_options, {
00593    AST_APP_OPTION('b', OPT_BUSY_GREETING),
00594    AST_APP_OPTION('u', OPT_UNAVAIL_GREETING),
00595    AST_APP_OPTION('t', OPT_TEMP_GREETING),
00596    AST_APP_OPTION('n', OPT_NAME_GREETING),
00597 });
00598 
00599 /*!\internal
00600  * \brief Structure for linked list of Mini-Voicemail users: \ref minivm_accounts */
00601 struct minivm_account {
00602    char username[AST_MAX_CONTEXT];  /*!< Mailbox username */
00603    char domain[AST_MAX_CONTEXT]; /*!< Voicemail domain */
00604 
00605    char pincode[10];    /*!< Secret pin code, numbers only */
00606    char fullname[120];     /*!< Full name, for directory app */
00607    char email[80];         /*!< E-mail address - override */
00608    char pager[80];         /*!< E-mail address to pager (no attachment) */
00609    char accountcode[AST_MAX_ACCOUNT_CODE];   /*!< Voicemail account account code */
00610    char serveremail[80];      /*!< From: Mail address */
00611    char externnotify[160];    /*!< Configurable notification command */
00612    char language[MAX_LANGUAGE];    /*!< Config: Language setting */
00613    char zonetag[80];    /*!< Time zone */
00614    char uniqueid[20];      /*!< Unique integer identifier */
00615    char exit[80];       /*!< Options for exiting from voicemail() */
00616    char attachfmt[80];     /*!< Format for voicemail audio file attachment */
00617    char etemplate[80];     /*!< Pager template */
00618    char ptemplate[80];     /*!< Voicemail format */
00619    unsigned int flags;     /*!< MVM_ flags */
00620    struct ast_variable *chanvars;   /*!< Variables for e-mail template */
00621    double volgain;         /*!< Volume gain for voicemails sent via e-mail */
00622    AST_LIST_ENTRY(minivm_account) list;
00623 };
00624 
00625 /*!\internal
00626  * \brief The list of e-mail accounts */
00627 static AST_LIST_HEAD_STATIC(minivm_accounts, minivm_account);
00628 
00629 /*!\internal
00630  * \brief Linked list of e-mail templates in various languages
00631  * These are used as templates for e-mails, pager messages and jabber messages
00632  * \ref message_templates
00633 */
00634 struct minivm_template {
00635    char  name[80];      /*!< Template name */
00636    char  *body;         /*!< Body of this template */
00637    char  fromaddress[100]; /*!< Who's sending the e-mail? */
00638    char  serveremail[80];  /*!< From: Mail address */
00639    char  subject[100];     /*!< Subject line */
00640    char  charset[32];      /*!< Default character set for this template */
00641    char  locale[20];    /*!< Locale for setlocale() */
00642    char  dateformat[80];      /*!< Date format to use in this attachment */
00643    int   attachment;    /*!< Attachment of media yes/no - no for pager messages */
00644    AST_LIST_ENTRY(minivm_template) list;  /*!< List mechanics */
00645 };
00646 
00647 /*! \brief The list of e-mail templates */
00648 static AST_LIST_HEAD_STATIC(message_templates, minivm_template);
00649 
00650 /*! \brief Options for leaving voicemail with the voicemail() application */
00651 struct leave_vm_options {
00652    unsigned int flags;
00653    signed char record_gain;
00654 };
00655 
00656 /*! \brief Structure for base64 encoding */
00657 struct b64_baseio {
00658    int iocp;
00659    int iolen;
00660    int linelength;
00661    int ateof;
00662    unsigned char iobuf[B64_BASEMAXINLINE];
00663 };
00664 
00665 /*! \brief Voicemail time zones */
00666 struct minivm_zone {
00667    char name[80];          /*!< Name of this time zone */
00668    char timezone[80];         /*!< Timezone definition */
00669    char msg_format[BUFSIZ];      /*!< Not used in minivm ...yet */
00670    AST_LIST_ENTRY(minivm_zone) list;   /*!< List mechanics */
00671 };
00672 
00673 /*! \brief The list of e-mail time zones */
00674 static AST_LIST_HEAD_STATIC(minivm_zones, minivm_zone);
00675 
00676 /*! \brief Structure for gathering statistics */
00677 struct minivm_stats {
00678    int voicemailaccounts;     /*!< Number of static accounts */
00679    int timezones;       /*!< Number of time zones */
00680    int templates;       /*!< Number of templates */
00681 
00682    struct timeval reset;         /*!< Time for last reset */
00683    int receivedmessages;      /*!< Number of received messages since reset */
00684    struct timeval lastreceived;     /*!< Time for last voicemail sent */
00685 };
00686 
00687 /*! \brief Statistics for voicemail */
00688 static struct minivm_stats global_stats;
00689 
00690 AST_MUTEX_DEFINE_STATIC(minivmlock);   /*!< Lock to protect voicemail system */
00691 AST_MUTEX_DEFINE_STATIC(minivmloglock);   /*!< Lock to protect voicemail system log file */
00692 
00693 static FILE *minivmlogfile;      /*!< The minivm log file */
00694 
00695 static int global_vmminmessage;     /*!< Minimum duration of messages */
00696 static int global_vmmaxmessage;     /*!< Maximum duration of message */
00697 static int global_maxsilence;    /*!< Maximum silence during recording */
00698 static int global_maxgreet;      /*!< Maximum length of prompts  */
00699 static int global_silencethreshold = 128;
00700 static char global_mailcmd[160]; /*!< Configurable mail cmd */
00701 static char global_externnotify[160];  /*!< External notification application */
00702 static char global_logfile[PATH_MAX];  /*!< Global log file for messages */
00703 static char default_vmformat[80];
00704 
00705 static struct ast_flags globalflags = {0};   /*!< Global voicemail flags */
00706 static int global_saydurationminfo;
00707 
00708 static double global_volgain; /*!< Volume gain for voicmemail via e-mail */
00709 
00710 /*!\internal
00711  * \brief Default dateformat, can be overridden in configuration file */
00712 #define DEFAULT_DATEFORMAT    "%A, %B %d, %Y at %r"
00713 #define DEFAULT_CHARSET    "ISO-8859-1"
00714 
00715 /* Forward declarations */
00716 static char *message_template_parse_filebody(const char *filename);
00717 static char *message_template_parse_emailbody(const char *body);
00718 static int create_vmaccount(char *name, struct ast_variable *var, int realtime);
00719 static struct minivm_account *find_user_realtime(const char *domain, const char *username);
00720 static char *handle_minivm_reload(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a);
00721 
00722 /*!\internal
00723  * \brief Create message template */
00724 static struct minivm_template *message_template_create(const char *name)
00725 {
00726    struct minivm_template *template;
00727 
00728    template = ast_calloc(1, sizeof(*template));
00729    if (!template)
00730       return NULL;
00731 
00732    /* Set some defaults for templates */
00733    ast_copy_string(template->name, name, sizeof(template->name));
00734    ast_copy_string(template->dateformat, DEFAULT_DATEFORMAT, sizeof(template->dateformat));
00735    ast_copy_string(template->charset, DEFAULT_CHARSET, sizeof(template->charset));
00736    ast_copy_string(template->subject, "New message in mailbox ${MVM_USERNAME}@${MVM_DOMAIN}", sizeof(template->subject));
00737    template->attachment = TRUE;
00738 
00739    return template;
00740 }
00741 
00742 /*!\internal
00743  * \brief Release memory allocated by message template */
00744 static void message_template_free(struct minivm_template *template)
00745 {
00746    if (template->body)
00747       ast_free(template->body);
00748 
00749    ast_free (template);
00750 }
00751 
00752 /*!\internal
00753  * \brief Build message template from configuration */
00754 static int message_template_build(const char *name, struct ast_variable *var)
00755 {
00756    struct minivm_template *template;
00757    int error = 0;
00758 
00759    template = message_template_create(name);
00760    if (!template) {
00761       ast_log(LOG_ERROR, "Out of memory, can't allocate message template object %s.\n", name);
00762       return -1;
00763    }
00764 
00765    while (var) {
00766       ast_debug(3, "Configuring template option %s = \"%s\" for template %s\n", var->name, var->value, name);
00767       if (!strcasecmp(var->name, "fromaddress")) {
00768          ast_copy_string(template->fromaddress, var->value, sizeof(template->fromaddress));
00769       } else if (!strcasecmp(var->name, "fromemail")) {
00770          ast_copy_string(template->serveremail, var->value, sizeof(template->serveremail));
00771       } else if (!strcasecmp(var->name, "subject")) {
00772          ast_copy_string(template->subject, var->value, sizeof(template->subject));
00773       } else if (!strcasecmp(var->name, "locale")) {
00774          ast_copy_string(template->locale, var->value, sizeof(template->locale));
00775       } else if (!strcasecmp(var->name, "attachmedia")) {
00776          template->attachment = ast_true(var->value);
00777       } else if (!strcasecmp(var->name, "dateformat")) {
00778          ast_copy_string(template->dateformat, var->value, sizeof(template->dateformat));
00779       } else if (!strcasecmp(var->name, "charset")) {
00780          ast_copy_string(template->charset, var->value, sizeof(template->charset));
00781       } else if (!strcasecmp(var->name, "templatefile")) {
00782          if (template->body) 
00783             ast_free(template->body);
00784          template->body = message_template_parse_filebody(var->value);
00785          if (!template->body) {
00786             ast_log(LOG_ERROR, "Error reading message body definition file %s\n", var->value);
00787             error++;
00788          }
00789       } else if (!strcasecmp(var->name, "messagebody")) {
00790          if (template->body) 
00791             ast_free(template->body);
00792          template->body = message_template_parse_emailbody(var->value);
00793          if (!template->body) {
00794             ast_log(LOG_ERROR, "Error parsing message body definition:\n          %s\n", var->value);
00795             error++;
00796          }
00797       } else {
00798          ast_log(LOG_ERROR, "Unknown message template configuration option \"%s=%s\"\n", var->name, var->value);
00799          error++;
00800       }
00801       var = var->next;
00802    }
00803    if (error)
00804       ast_log(LOG_ERROR, "-- %d errors found parsing message template definition %s\n", error, name);
00805 
00806    AST_LIST_LOCK(&message_templates);
00807    AST_LIST_INSERT_TAIL(&message_templates, template, list);
00808    AST_LIST_UNLOCK(&message_templates);
00809 
00810    global_stats.templates++;
00811 
00812    return error;
00813 }
00814 
00815 /*!\internal
00816  * \brief Find named template */
00817 static struct minivm_template *message_template_find(const char *name)
00818 {
00819    struct minivm_template *this, *res = NULL;
00820 
00821    if (ast_strlen_zero(name))
00822       return NULL;
00823 
00824    AST_LIST_LOCK(&message_templates);
00825    AST_LIST_TRAVERSE(&message_templates, this, list) {
00826       if (!strcasecmp(this->name, name)) {
00827          res = this;
00828          break;
00829       }
00830    }
00831    AST_LIST_UNLOCK(&message_templates);
00832 
00833    return res;
00834 }
00835 
00836 
00837 /*!\internal
00838  * \brief Clear list of templates */
00839 static void message_destroy_list(void)
00840 {
00841    struct minivm_template *this;
00842    AST_LIST_LOCK(&message_templates);
00843    while ((this = AST_LIST_REMOVE_HEAD(&message_templates, list))) {
00844       message_template_free(this);
00845    }
00846 
00847    AST_LIST_UNLOCK(&message_templates);
00848 }
00849 
00850 /*!\internal
00851  * \brief read buffer from file (base64 conversion) */
00852 static int b64_inbuf(struct b64_baseio *bio, FILE *fi)
00853 {
00854    int l;
00855 
00856    if (bio->ateof)
00857       return 0;
00858 
00859    if ((l = fread(bio->iobuf, 1, B64_BASEMAXINLINE,fi)) <= 0) {
00860       if (ferror(fi))
00861          return -1;
00862 
00863       bio->ateof = 1;
00864       return 0;
00865    }
00866 
00867    bio->iolen= l;
00868    bio->iocp= 0;
00869 
00870    return 1;
00871 }
00872 
00873 /*!\internal
00874  * \brief read character from file to buffer (base64 conversion) */
00875 static int b64_inchar(struct b64_baseio *bio, FILE *fi)
00876 {
00877    if (bio->iocp >= bio->iolen) {
00878       if (!b64_inbuf(bio, fi))
00879          return EOF;
00880    }
00881 
00882    return bio->iobuf[bio->iocp++];
00883 }
00884 
00885 /*!\internal
00886  * \brief write buffer to file (base64 conversion) */
00887 static int b64_ochar(struct b64_baseio *bio, int c, FILE *so)
00888 {
00889    if (bio->linelength >= B64_BASELINELEN) {
00890       if (fputs(EOL,so) == EOF)
00891          return -1;
00892 
00893       bio->linelength= 0;
00894    }
00895 
00896    if (putc(((unsigned char) c), so) == EOF)
00897       return -1;
00898 
00899    bio->linelength++;
00900 
00901    return 1;
00902 }
00903 
00904 /*!\internal
00905  * \brief Encode file to base64 encoding for email attachment (base64 conversion) */
00906 static int base_encode(char *filename, FILE *so)
00907 {
00908    unsigned char dtable[B64_BASEMAXINLINE];
00909    int i,hiteof= 0;
00910    FILE *fi;
00911    struct b64_baseio bio;
00912 
00913    memset(&bio, 0, sizeof(bio));
00914    bio.iocp = B64_BASEMAXINLINE;
00915 
00916    if (!(fi = fopen(filename, "rb"))) {
00917       ast_log(LOG_WARNING, "Failed to open file: %s: %s\n", filename, strerror(errno));
00918       return -1;
00919    }
00920 
00921    for (i= 0; i<9; i++) {
00922       dtable[i]= 'A'+i;
00923       dtable[i+9]= 'J'+i;
00924       dtable[26+i]= 'a'+i;
00925       dtable[26+i+9]= 'j'+i;
00926    }
00927    for (i= 0; i < 8; i++) {
00928       dtable[i+18]= 'S'+i;
00929       dtable[26+i+18]= 's'+i;
00930    }
00931    for (i= 0; i < 10; i++) {
00932       dtable[52+i]= '0'+i;
00933    }
00934    dtable[62]= '+';
00935    dtable[63]= '/';
00936 
00937    while (!hiteof){
00938       unsigned char igroup[3], ogroup[4];
00939       int c,n;
00940 
00941       igroup[0]= igroup[1]= igroup[2]= 0;
00942 
00943       for (n= 0; n < 3; n++) {
00944          if ((c = b64_inchar(&bio, fi)) == EOF) {
00945             hiteof= 1;
00946             break;
00947          }
00948          igroup[n]= (unsigned char)c;
00949       }
00950 
00951       if (n> 0) {
00952          ogroup[0]= dtable[igroup[0]>>2];
00953          ogroup[1]= dtable[((igroup[0]&3)<<4) | (igroup[1]>>4)];
00954          ogroup[2]= dtable[((igroup[1]&0xF)<<2) | (igroup[2]>>6)];
00955          ogroup[3]= dtable[igroup[2]&0x3F];
00956 
00957          if (n<3) {
00958             ogroup[3]= '=';
00959 
00960             if (n<2)
00961                ogroup[2]= '=';
00962          }
00963 
00964          for (i= 0;i<4;i++)
00965             b64_ochar(&bio, ogroup[i], so);
00966       }
00967    }
00968 
00969    /* Put end of line - line feed */
00970    if (fputs(EOL, so) == EOF)
00971       return 0;
00972 
00973    fclose(fi);
00974 
00975    return 1;
00976 }
00977 
00978 static int get_date(char *s, int len)
00979 {
00980    struct ast_tm tm;
00981    struct timeval now = ast_tvnow();
00982 
00983    ast_localtime(&now, &tm, NULL);
00984    return ast_strftime(s, len, "%a %b %e %r %Z %Y", &tm);
00985 }
00986 
00987 
00988 /*!\internal
00989  * \brief Free user structure - if it's allocated */
00990 static void free_user(struct minivm_account *vmu)
00991 {
00992    if (vmu->chanvars)
00993       ast_variables_destroy(vmu->chanvars);
00994    ast_free(vmu);
00995 }
00996 
00997 
00998 
00999 /*!\internal
01000  * \brief Prepare for voicemail template by adding channel variables
01001  * to the channel
01002 */
01003 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)
01004 {
01005    char callerid[256];
01006    struct ast_variable *var;
01007    
01008    if (!channel) {
01009       ast_log(LOG_ERROR, "No allocated channel, giving up...\n");
01010       return;
01011    }
01012 
01013    for (var = vmu->chanvars ; var ; var = var->next) {
01014       pbx_builtin_setvar_helper(channel, var->name, var->value);
01015    }
01016 
01017    /* Prepare variables for substition in email body and subject */
01018    pbx_builtin_setvar_helper(channel, "MVM_NAME", vmu->fullname);
01019    pbx_builtin_setvar_helper(channel, "MVM_DUR", dur);
01020    pbx_builtin_setvar_helper(channel, "MVM_DOMAIN", vmu->domain);
01021    pbx_builtin_setvar_helper(channel, "MVM_USERNAME", vmu->username);
01022    pbx_builtin_setvar_helper(channel, "MVM_CALLERID", ast_callerid_merge(callerid, sizeof(callerid), cidname, cidnum, "Unknown Caller"));
01023    pbx_builtin_setvar_helper(channel, "MVM_CIDNAME", (cidname ? cidname : "an unknown caller"));
01024    pbx_builtin_setvar_helper(channel, "MVM_CIDNUM", (cidnum ? cidnum : "an unknown caller"));
01025    pbx_builtin_setvar_helper(channel, "MVM_DATE", date);
01026    if (!ast_strlen_zero(counter))
01027       pbx_builtin_setvar_helper(channel, "MVM_COUNTER", counter);
01028 }
01029 
01030 /*!\internal
01031  * \brief Set default values for Mini-Voicemail users */
01032 static void populate_defaults(struct minivm_account *vmu)
01033 {
01034    ast_copy_flags(vmu, (&globalflags), AST_FLAGS_ALL);   
01035    ast_copy_string(vmu->attachfmt, default_vmformat, sizeof(vmu->attachfmt));
01036    vmu->volgain = global_volgain;
01037 }
01038 
01039 /*!\internal
01040  * \brief Allocate new vm user and set default values */
01041 static struct minivm_account *mvm_user_alloc(void)
01042 {
01043    struct minivm_account *new;
01044 
01045    new = ast_calloc(1, sizeof(*new));
01046    if (!new)
01047       return NULL;
01048    populate_defaults(new);
01049 
01050    return new;
01051 }
01052 
01053 
01054 /*!\internal
01055  * \brief Clear list of users */
01056 static void vmaccounts_destroy_list(void)
01057 {
01058    struct minivm_account *this;
01059    AST_LIST_LOCK(&minivm_accounts);
01060    while ((this = AST_LIST_REMOVE_HEAD(&minivm_accounts, list))) 
01061       ast_free(this);
01062    AST_LIST_UNLOCK(&minivm_accounts);
01063 }
01064 
01065 
01066 /*!\internal
01067  * \brief Find user from static memory object list */
01068 static struct minivm_account *find_account(const char *domain, const char *username, int createtemp)
01069 {
01070    struct minivm_account *vmu = NULL, *cur;
01071 
01072 
01073    if (ast_strlen_zero(domain) || ast_strlen_zero(username)) {
01074       ast_log(LOG_NOTICE, "No username or domain? \n");
01075       return NULL;
01076    }
01077    ast_debug(3, "Looking for voicemail user %s in domain %s\n", username, domain);
01078 
01079    AST_LIST_LOCK(&minivm_accounts);
01080    AST_LIST_TRAVERSE(&minivm_accounts, cur, list) {
01081       /* Is this the voicemail account we're looking for? */
01082       if (!strcasecmp(domain, cur->domain) && !strcasecmp(username, cur->username))
01083          break;
01084    }
01085    AST_LIST_UNLOCK(&minivm_accounts);
01086 
01087    if (cur) {
01088       ast_debug(3, "Found account for %s@%s\n", username, domain);
01089       vmu = cur;
01090 
01091    } else
01092       vmu = find_user_realtime(domain, username);
01093 
01094    if (createtemp && !vmu) {
01095       /* Create a temporary user, send e-mail and be gone */
01096       vmu = mvm_user_alloc();
01097       ast_set2_flag(vmu, TRUE, MVM_ALLOCED); 
01098       if (vmu) {
01099          ast_copy_string(vmu->username, username, sizeof(vmu->username));
01100          ast_copy_string(vmu->domain, domain, sizeof(vmu->domain));
01101          ast_debug(1, "Created temporary account\n");
01102       }
01103 
01104    }
01105    return vmu;
01106 }
01107 
01108 /*!\internal
01109  * \brief Find user in realtime storage
01110  * \return pointer to minivm_account structure
01111 */
01112 static struct minivm_account *find_user_realtime(const char *domain, const char *username)
01113 {
01114    struct ast_variable *var;
01115    struct minivm_account *retval;
01116    char name[MAXHOSTNAMELEN];
01117 
01118    retval = mvm_user_alloc();
01119    if (!retval)
01120       return NULL;
01121 
01122    if (username) 
01123       ast_copy_string(retval->username, username, sizeof(retval->username));
01124 
01125    populate_defaults(retval);
01126    var = ast_load_realtime("minivm", "username", username, "domain", domain, SENTINEL);
01127 
01128    if (!var) {
01129       ast_free(retval);
01130       return NULL;
01131    }
01132 
01133    snprintf(name, sizeof(name), "%s@%s", username, domain);
01134    create_vmaccount(name, var, TRUE);
01135 
01136    ast_variables_destroy(var);
01137    return retval;
01138 }
01139 
01140 /*!\internal
01141  * \brief Check if the string would need encoding within the MIME standard, to
01142  * avoid confusing certain mail software that expects messages to be 7-bit
01143  * clean.
01144  */
01145 static int check_mime(const char *str)
01146 {
01147    for (; *str; str++) {
01148       if (*str > 126 || *str < 32 || strchr("()<>@,:;/\"[]?.=", *str)) {
01149          return 1;
01150       }
01151    }
01152    return 0;
01153 }
01154 
01155 /*!\internal
01156  * \brief Encode a string according to the MIME rules for encoding strings
01157  * that are not 7-bit clean or contain control characters.
01158  *
01159  * Additionally, if the encoded string would exceed the MIME limit of 76
01160  * characters per line, then the encoding will be broken up into multiple
01161  * sections, separated by a space character, in order to facilitate
01162  * breaking up the associated header across multiple lines.
01163  *
01164  * \param end An expandable buffer for holding the result
01165  * \param maxlen \see ast_str
01166  * \param charset Character set in which the result should be encoded
01167  * \param start A string to be encoded
01168  * \param preamble The length of the first line already used for this string,
01169  * to ensure that each line maintains a maximum length of 76 chars.
01170  * \param postamble the length of any additional characters appended to the
01171  * line, used to ensure proper field wrapping.
01172  * \return The encoded string.
01173  */
01174 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)
01175 {
01176    struct ast_str *tmp = ast_str_alloca(80);
01177    int first_section = 1;
01178 
01179    ast_str_reset(*end);
01180    ast_str_set(&tmp, -1, "=?%s?Q?", charset);
01181    for (; *start; start++) {
01182       int need_encoding = 0;
01183       if (*start < 33 || *start > 126 || strchr("()<>@,:;/\"[]?.=_", *start)) {
01184          need_encoding = 1;
01185       }
01186       if ((first_section && need_encoding && preamble + ast_str_strlen(tmp) > 70) ||
01187          (first_section && !need_encoding && preamble + ast_str_strlen(tmp) > 72) ||
01188          (!first_section && need_encoding && ast_str_strlen(tmp) > 70) ||
01189          (!first_section && !need_encoding && ast_str_strlen(tmp) > 72)) {
01190          /* Start new line */
01191          ast_str_append(end, maxlen, "%s%s?=", first_section ? "" : " ", ast_str_buffer(tmp));
01192          ast_str_set(&tmp, -1, "=?%s?Q?", charset);
01193          first_section = 0;
01194       }
01195       if (need_encoding && *start == ' ') {
01196          ast_str_append(&tmp, -1, "_");
01197       } else if (need_encoding) {
01198          ast_str_append(&tmp, -1, "=%hhX", *start);
01199       } else {
01200          ast_str_append(&tmp, -1, "%c", *start);
01201       }
01202    }
01203    ast_str_append(end, maxlen, "%s%s?=%s", first_section ? "" : " ", ast_str_buffer(tmp), ast_str_strlen(tmp) + postamble > 74 ? " " : "");
01204    return ast_str_buffer(*end);
01205 }
01206 
01207 /*!\internal
01208  * \brief Wraps a character sequence in double quotes, escaping occurences of quotes within the string.
01209  * \param from The string to work with.
01210  * \param buf The destination buffer to write the modified quoted string.
01211  * \param maxlen Always zero.  \see ast_str
01212  *
01213  * \return The destination string with quotes wrapped on it (the to field).
01214  */
01215 static const char *ast_str_quote(struct ast_str **buf, ssize_t maxlen, const char *from)
01216 {
01217    const char *ptr;
01218 
01219    /* We're only ever passing 0 to maxlen, so short output isn't possible */
01220    ast_str_set(buf, maxlen, "\"");
01221    for (ptr = from; *ptr; ptr++) {
01222       if (*ptr == '"' || *ptr == '\\') {
01223          ast_str_append(buf, maxlen, "\\%c", *ptr);
01224       } else {
01225          ast_str_append(buf, maxlen, "%c", *ptr);
01226       }
01227    }
01228    ast_str_append(buf, maxlen, "\"");
01229 
01230    return ast_str_buffer(*buf);
01231 }
01232 
01233 /*!\internal
01234  * \brief Send voicemail with audio file as an attachment */
01235 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)
01236 {
01237    FILE *p = NULL;
01238    int pfd;
01239    char email[256] = "";
01240    char who[256] = "";
01241    char date[256];
01242    char bound[256];
01243    char fname[PATH_MAX];
01244    char dur[PATH_MAX];
01245    char tmp[80] = "/tmp/astmail-XXXXXX";
01246    char tmp2[PATH_MAX];
01247    char newtmp[PATH_MAX]; /* Only used with volgain */
01248    struct timeval now;
01249    struct ast_tm tm;
01250    struct minivm_zone *the_zone = NULL;
01251    struct ast_channel *ast;
01252    char *finalfilename = "";
01253    struct ast_str *str1 = ast_str_create(16), *str2 = ast_str_create(16);
01254    char *fromaddress;
01255    char *fromemail;
01256 
01257    if (!str1 || !str2) {
01258       ast_free(str1);
01259       ast_free(str2);
01260       return -1;
01261    }
01262 
01263    if (type == MVM_MESSAGE_EMAIL) {
01264       if (vmu && !ast_strlen_zero(vmu->email)) {
01265          ast_copy_string(email, vmu->email, sizeof(email)); 
01266       } else if (!ast_strlen_zero(vmu->username) && !ast_strlen_zero(vmu->domain))
01267          snprintf(email, sizeof(email), "%s@%s", vmu->username, vmu->domain);
01268    } else if (type == MVM_MESSAGE_PAGE) {
01269       ast_copy_string(email, vmu->pager, sizeof(email));
01270    }
01271 
01272    if (ast_strlen_zero(email)) {
01273       ast_log(LOG_WARNING, "No address to send message to.\n");
01274       ast_free(str1);
01275       ast_free(str2);
01276       return -1;  
01277    }
01278 
01279    ast_debug(3, "Sending mail to %s@%s - Using template %s\n", vmu->username, vmu->domain, template->name);
01280 
01281    if (!strcmp(format, "wav49"))
01282       format = "WAV";
01283 
01284 
01285    /* If we have a gain option, process it now with sox */
01286    if (type == MVM_MESSAGE_EMAIL && (vmu->volgain < -.001 || vmu->volgain > .001) ) {
01287       char tmpcmd[PATH_MAX];
01288       int tmpfd;
01289 
01290       ast_copy_string(newtmp, "/tmp/XXXXXX", sizeof(newtmp));
01291       ast_debug(3, "newtmp: %s\n", newtmp);
01292       tmpfd = mkstemp(newtmp);
01293       if (tmpfd < 0) {
01294          ast_log(LOG_WARNING, "Failed to create temporary file for volgain: %d\n", errno);
01295          ast_free(str1);
01296          ast_free(str2);
01297          return -1;
01298       }
01299       snprintf(tmpcmd, sizeof(tmpcmd), "sox -v %.4f %s.%s %s.%s", vmu->volgain, filename, format, newtmp, format);
01300       ast_safe_system(tmpcmd);
01301       close(tmpfd);
01302       finalfilename = newtmp;
01303       ast_debug(3, "VOLGAIN: Stored at: %s.%s - Level: %.4f - Mailbox: %s\n", filename, format, vmu->volgain, vmu->username);
01304    } else {
01305       finalfilename = ast_strdupa(filename);
01306    }
01307 
01308    /* Create file name */
01309    snprintf(fname, sizeof(fname), "%s.%s", finalfilename, format);
01310 
01311    if (template->attachment)
01312       ast_debug(1, "Attaching file '%s', format '%s', uservm is '%d'\n", finalfilename, format, attach_user_voicemail);
01313 
01314    /* Make a temporary file instead of piping directly to sendmail, in case the mail
01315       command hangs */
01316    pfd = mkstemp(tmp);
01317    if (pfd > -1) {
01318       p = fdopen(pfd, "w");
01319       if (!p) {
01320          close(pfd);
01321          pfd = -1;
01322       }
01323       ast_debug(1, "Opening temp file for e-mail: %s\n", tmp);
01324    }
01325    if (!p) {
01326       ast_log(LOG_WARNING, "Unable to open temporary file '%s'\n", tmp);
01327       ast_free(str1);
01328       ast_free(str2);
01329       return -1;
01330    }
01331    /* Allocate channel used for chanvar substitution */
01332    ast = ast_dummy_channel_alloc();
01333    if (!ast) {
01334       ast_free(str1);
01335       ast_free(str2);
01336       return -1;
01337    }
01338 
01339    snprintf(dur, sizeof(dur), "%d:%02d", duration / 60, duration % 60);
01340 
01341    /* Does this user have a timezone specified? */
01342    if (!ast_strlen_zero(vmu->zonetag)) {
01343       /* Find the zone in the list */
01344       struct minivm_zone *z;
01345       AST_LIST_LOCK(&minivm_zones);
01346       AST_LIST_TRAVERSE(&minivm_zones, z, list) {
01347          if (strcmp(z->name, vmu->zonetag)) 
01348             continue;
01349          the_zone = z;
01350       }
01351       AST_LIST_UNLOCK(&minivm_zones);
01352    }
01353 
01354    now = ast_tvnow();
01355    ast_localtime(&now, &tm, the_zone ? the_zone->timezone : NULL);
01356    ast_strftime(date, sizeof(date), "%a, %d %b %Y %H:%M:%S %z", &tm);
01357 
01358    /* Start printing the email to the temporary file */
01359    fprintf(p, "Date: %s\n", date);
01360 
01361    /* Set date format for voicemail mail */
01362    ast_strftime(date, sizeof(date), template->dateformat, &tm);
01363 
01364 
01365    /* Populate channel with channel variables for substitution */
01366    prep_email_sub_vars(ast, vmu, cidnum, cidname, dur, date, counter);
01367 
01368    /* Find email address to use */
01369    /* If there's a server e-mail address in the account, use that, othterwise template */
01370    fromemail = ast_strlen_zero(vmu->serveremail) ?  template->serveremail : vmu->serveremail;
01371 
01372    /* Find name to user for server e-mail */
01373    fromaddress = ast_strlen_zero(template->fromaddress) ? "" : template->fromaddress;
01374 
01375    /* If needed, add hostname as domain */
01376    if (ast_strlen_zero(fromemail))
01377       fromemail = "asterisk";
01378 
01379    if (strchr(fromemail, '@'))
01380       ast_copy_string(who, fromemail, sizeof(who));
01381    else  {
01382       char host[MAXHOSTNAMELEN];
01383       gethostname(host, sizeof(host)-1);
01384       snprintf(who, sizeof(who), "%s@%s", fromemail, host);
01385    }
01386 
01387    if (ast_strlen_zero(fromaddress)) {
01388       fprintf(p, "From: Asterisk PBX <%s>\n", who);
01389    } else {
01390       ast_debug(4, "Fromaddress template: %s\n", fromaddress);
01391       ast_str_substitute_variables(&str1, 0, ast, fromaddress);
01392       if (check_mime(ast_str_buffer(str1))) {
01393          int first_line = 1;
01394          char *ptr;
01395          ast_str_encode_mime(&str2, 0, template->charset, ast_str_buffer(str1), strlen("From: "), strlen(who) + 3);
01396          while ((ptr = strchr(ast_str_buffer(str2), ' '))) {
01397             *ptr = '\0';
01398             fprintf(p, "%s %s\n", first_line ? "From:" : "", ast_str_buffer(str2));
01399             first_line = 0;
01400             /* Substring is smaller, so this will never grow */
01401             ast_str_set(&str2, 0, "%s", ptr + 1);
01402          }
01403          fprintf(p, "%s %s <%s>\n", first_line ? "From:" : "", ast_str_buffer(str2), who);
01404       } else {
01405          fprintf(p, "From: %s <%s>\n", ast_str_quote(&str2, 0, ast_str_buffer(str1)), who);
01406       }
01407    } 
01408 
01409    fprintf(p, "Message-ID: <Asterisk-%u-%s-%d-%s>\n", (unsigned int)ast_random(), vmu->username, (int)getpid(), who);
01410 
01411    if (ast_strlen_zero(vmu->email)) {
01412       snprintf(email, sizeof(email), "%s@%s", vmu->username, vmu->domain);
01413    } else {
01414       ast_copy_string(email, vmu->email, sizeof(email));
01415    }
01416 
01417    if (check_mime(vmu->fullname)) {
01418       int first_line = 1;
01419       char *ptr;
01420       ast_str_encode_mime(&str2, 0, template->charset, vmu->fullname, strlen("To: "), strlen(email) + 3);
01421       while ((ptr = strchr(ast_str_buffer(str2), ' '))) {
01422          *ptr = '\0';
01423          fprintf(p, "%s %s\n", first_line ? "To:" : "", ast_str_buffer(str2));
01424          first_line = 0;
01425          /* Substring is smaller, so this will never grow */
01426          ast_str_set(&str2, 0, "%s", ptr + 1);
01427       }
01428       fprintf(p, "%s %s <%s>\n", first_line ? "To:" : "", ast_str_buffer(str2), email);
01429    } else {
01430       fprintf(p, "To: %s <%s>\n", ast_str_quote(&str2, 0, vmu->fullname), email);
01431    }
01432 
01433    if (!ast_strlen_zero(template->subject)) {
01434       ast_str_substitute_variables(&str1, 0, ast, template->subject);
01435       if (check_mime(ast_str_buffer(str1))) {
01436          int first_line = 1;
01437          char *ptr;
01438          ast_str_encode_mime(&str2, 0, template->charset, ast_str_buffer(str1), strlen("Subject: "), 0);
01439          while ((ptr = strchr(ast_str_buffer(str2), ' '))) {
01440             *ptr = '\0';
01441             fprintf(p, "%s %s\n", first_line ? "Subject:" : "", ast_str_buffer(str2));
01442             first_line = 0;
01443             /* Substring is smaller, so this will never grow */
01444             ast_str_set(&str2, 0, "%s", ptr + 1);
01445          }
01446          fprintf(p, "%s %s\n", first_line ? "Subject:" : "", ast_str_buffer(str2));
01447       } else {
01448          fprintf(p, "Subject: %s\n", ast_str_buffer(str1));
01449       }
01450    } else {
01451       fprintf(p, "Subject: New message in mailbox %s@%s\n", vmu->username, vmu->domain);
01452       ast_debug(1, "Using default subject for this email \n");
01453    }
01454 
01455    if (option_debug > 2)
01456       fprintf(p, "X-Asterisk-debug: template %s user account %s@%s\n", template->name, vmu->username, vmu->domain);
01457    fprintf(p, "MIME-Version: 1.0\n");
01458 
01459    /* Something unique. */
01460    snprintf(bound, sizeof(bound), "voicemail_%s%d%u", vmu->username, (int)getpid(), (unsigned int)ast_random());
01461 
01462    fprintf(p, "Content-Type: multipart/mixed; boundary=\"%s\"\n\n\n", bound);
01463 
01464    fprintf(p, "--%s\n", bound);
01465    fprintf(p, "Content-Type: text/plain; charset=%s\nContent-Transfer-Encoding: 8bit\n\n", template->charset);
01466    if (!ast_strlen_zero(template->body)) {
01467       ast_str_substitute_variables(&str1, 0, ast, template->body);
01468       ast_debug(3, "Message now: %s\n-----\n", ast_str_buffer(str1));
01469       fprintf(p, "%s\n", ast_str_buffer(str1));
01470    } else {
01471       fprintf(p, "Dear %s:\n\n\tJust wanted to let you know you were just left a %s long message \n"
01472          "in mailbox %s from %s, on %s so you might\n"
01473          "want to check it when you get a chance.  Thanks!\n\n\t\t\t\t--Asterisk\n\n", vmu->fullname, 
01474          dur,  vmu->username, (cidname ? cidname : (cidnum ? cidnum : "an unknown caller")), date);
01475       ast_debug(3, "Using default message body (no template)\n-----\n");
01476    }
01477    /* Eww. We want formats to tell us their own MIME type */
01478    if (template->attachment) {
01479       char *ctype = "audio/x-";
01480       ast_debug(3, "Attaching file to message: %s\n", fname);
01481       if (!strcasecmp(format, "ogg"))
01482          ctype = "application/";
01483 
01484       fprintf(p, "--%s\n", bound);
01485       fprintf(p, "Content-Type: %s%s; name=\"voicemailmsg.%s\"\n", ctype, format, format);
01486       fprintf(p, "Content-Transfer-Encoding: base64\n");
01487       fprintf(p, "Content-Description: Voicemail sound attachment.\n");
01488       fprintf(p, "Content-Disposition: attachment; filename=\"voicemail%s.%s\"\n\n", counter ? counter : "", format);
01489 
01490       base_encode(fname, p);
01491       fprintf(p, "\n\n--%s--\n.\n", bound);
01492    }
01493    fclose(p);
01494    snprintf(tmp2, sizeof(tmp2), "( %s < %s ; rm -f %s ) &", global_mailcmd, tmp, tmp);
01495    ast_safe_system(tmp2);
01496    ast_debug(1, "Sent message to %s with command '%s' - %s\n", vmu->email, global_mailcmd, template->attachment ? "(media attachment)" : "");
01497    ast_debug(3, "Actual command used: %s\n", tmp2);
01498    ast = ast_channel_unref(ast);
01499    ast_free(str1);
01500    ast_free(str2);
01501    return 0;
01502 }
01503 
01504 /*!\internal
01505  * \brief Create directory based on components */
01506 static int make_dir(char *dest, int len, const char *domain, const char *username, const char *folder)
01507 {
01508    return snprintf(dest, len, "%s%s/%s%s%s", MVM_SPOOL_DIR, domain, username, ast_strlen_zero(folder) ? "" : "/", folder ? folder : "");
01509 }
01510 
01511 /*!\internal
01512  * \brief Checks if directory exists. Does not create directory, but builds string in dest
01513  * \param dest    String. base directory.
01514  * \param len    Int. Length base directory string.
01515  * \param domain String. Ignored if is null or empty string.
01516  * \param username String. Ignored if is null or empty string. 
01517  * \param folder  String. Ignored if is null or empty string.
01518  * \return 0 on failure, 1 on success.
01519  */
01520 static int check_dirpath(char *dest, int len, char *domain, char *username, char *folder)
01521 {
01522    struct stat filestat;
01523    make_dir(dest, len, domain, username, folder ? folder : "");
01524    if (stat(dest, &filestat)== -1)
01525       return FALSE;
01526    else
01527       return TRUE;
01528 }
01529 
01530 /*!\internal
01531  * \brief basically mkdir -p $dest/$domain/$username/$folder
01532  * \param dest    String. base directory.
01533  * \param len     Length of directory string
01534  * \param domain  String. Ignored if is null or empty string.
01535  * \param folder  String. Ignored if is null or empty string.
01536  * \param username  String. Ignored if is null or empty string.
01537  * \return -1 on failure, 0 on success.
01538  */
01539 static int create_dirpath(char *dest, int len, char *domain, char *username, char *folder)
01540 {
01541    int res;
01542    make_dir(dest, len, domain, username, folder);
01543    if ((res = ast_mkdir(dest, 0777))) {
01544       ast_log(LOG_WARNING, "ast_mkdir '%s' failed: %s\n", dest, strerror(res));
01545       return -1;
01546    }
01547    ast_debug(2, "Creating directory for %s@%s folder %s : %s\n", username, domain, folder, dest);
01548    return 0;
01549 }
01550 
01551 
01552 /*!\internal
01553  * \brief Play intro message before recording voicemail
01554  */
01555 static int invent_message(struct ast_channel *chan, char *domain, char *username, int busy, char *ecodes)
01556 {
01557    int res;
01558    char fn[PATH_MAX];
01559 
01560    ast_debug(2, "Still preparing to play message ...\n");
01561 
01562    snprintf(fn, sizeof(fn), "%s%s/%s/greet", MVM_SPOOL_DIR, domain, username);
01563 
01564    if (ast_fileexists(fn, NULL, NULL) > 0) {
01565       res = ast_streamfile(chan, fn, ast_channel_language(chan));
01566       if (res) 
01567          return -1;
01568       res = ast_waitstream(chan, ecodes);
01569       if (res) 
01570          return res;
01571    } else {
01572       int numericusername = 1;
01573       char *i = username;
01574 
01575       ast_debug(2, "No personal prompts. Using default prompt set for language\n");
01576 
01577       while (*i)  {
01578          ast_debug(2, "Numeric? Checking %c\n", *i);
01579          if (!isdigit(*i)) {
01580             numericusername = FALSE;
01581             break;
01582          }
01583          i++;
01584       }
01585 
01586       if (numericusername) {
01587          if (ast_streamfile(chan, "vm-theperson", ast_channel_language(chan)))
01588             return -1;
01589          if ((res = ast_waitstream(chan, ecodes)))
01590             return res;
01591 
01592          res = ast_say_digit_str(chan, username, ecodes, ast_channel_language(chan));
01593          if (res)
01594             return res;
01595       } else {
01596          if (ast_streamfile(chan, "vm-theextensionis", ast_channel_language(chan)))
01597             return -1;
01598          if ((res = ast_waitstream(chan, ecodes)))
01599             return res;
01600       }
01601    }
01602 
01603    res = ast_streamfile(chan, busy ? "vm-isonphone" : "vm-isunavail", ast_channel_language(chan));
01604    if (res)
01605       return -1;
01606    res = ast_waitstream(chan, ecodes);
01607    return res;
01608 }
01609 
01610 /*!\internal
01611  * \brief Delete media files and attribute file */
01612 static int vm_delete(char *file)
01613 {
01614    int res;
01615 
01616    ast_debug(1, "Deleting voicemail file %s\n", file);
01617 
01618    res = unlink(file);  /* Remove the meta data file */
01619    res |=  ast_filedelete(file, NULL); /* remove the media file */
01620    return res;
01621 }
01622 
01623 
01624 /*!\internal
01625  * \brief Record voicemail message & let caller review or re-record it, or set options if applicable */
01626 static int play_record_review(struct ast_channel *chan, char *playfile, char *recordfile, int maxtime, char *fmt,
01627                int outsidecaller, struct minivm_account *vmu, int *duration, int *sound_duration, const char *unlockdir,
01628                signed char record_gain)
01629 {
01630    int cmd = 0;
01631    int max_attempts = 3;
01632    int attempts = 0;
01633    int recorded = 0;
01634    int message_exists = 0;
01635    signed char zero_gain = 0;
01636    char *acceptdtmf = "#";
01637    char *canceldtmf = "";
01638 
01639    /* Note that urgent and private are for flagging messages as such in the future */
01640 
01641    /* barf if no pointer passed to store duration in */
01642    if (duration == NULL) {
01643       ast_log(LOG_WARNING, "Error play_record_review called without duration pointer\n");
01644       return -1;
01645    }
01646 
01647    cmd = '3';   /* Want to start by recording */
01648 
01649    while ((cmd >= 0) && (cmd != 't')) {
01650       switch (cmd) {
01651       case '1':
01652          ast_verb(3, "Saving message as is\n");
01653          ast_stream_and_wait(chan, "vm-msgsaved", "");
01654          cmd = 't';
01655          break;
01656       case '2':
01657          /* Review */
01658          ast_verb(3, "Reviewing the message\n");
01659          ast_streamfile(chan, recordfile, ast_channel_language(chan));
01660          cmd = ast_waitstream(chan, AST_DIGIT_ANY);
01661          break;
01662       case '3':
01663          message_exists = 0;
01664          /* Record */
01665          if (recorded == 1) 
01666             ast_verb(3, "Re-recording the message\n");
01667          else
01668             ast_verb(3, "Recording the message\n");
01669          if (recorded && outsidecaller) 
01670             cmd = ast_play_and_wait(chan, "beep");
01671          recorded = 1;
01672          /* 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 */
01673          if (record_gain)
01674             ast_channel_setoption(chan, AST_OPTION_RXGAIN, &record_gain, sizeof(record_gain), 0);
01675          if (ast_test_flag(vmu, MVM_OPERATOR))
01676             canceldtmf = "0";
01677          cmd = ast_play_and_record_full(chan, playfile, recordfile, maxtime, fmt, duration, sound_duration, 0, global_silencethreshold, global_maxsilence, unlockdir, acceptdtmf, canceldtmf, 0, AST_RECORD_IF_EXISTS_OVERWRITE);
01678          if (record_gain)
01679             ast_channel_setoption(chan, AST_OPTION_RXGAIN, &zero_gain, sizeof(zero_gain), 0);
01680          if (cmd == -1) /* User has hung up, no options to give */
01681             return cmd;
01682          if (cmd == '0')
01683             break;
01684          else if (cmd == '*')
01685             break;
01686          else {
01687             /* If all is well, a message exists */
01688             message_exists = 1;
01689             cmd = 0;
01690          }
01691          break;
01692       case '4':
01693       case '5':
01694       case '6':
01695       case '7':
01696       case '8':
01697       case '9':
01698       case '*':
01699       case '#':
01700          cmd = ast_play_and_wait(chan, "vm-sorry");
01701          break;
01702       case '0':
01703          if(!ast_test_flag(vmu, MVM_OPERATOR)) {
01704             cmd = ast_play_and_wait(chan, "vm-sorry");
01705             break;
01706          }
01707          if (message_exists || recorded) {
01708             cmd = ast_play_and_wait(chan, "vm-saveoper");
01709             if (!cmd)
01710                cmd = ast_waitfordigit(chan, 3000);
01711             if (cmd == '1') {
01712                ast_play_and_wait(chan, "vm-msgsaved");
01713                cmd = '0';
01714             } else {
01715                ast_play_and_wait(chan, "vm-deleted");
01716                vm_delete(recordfile);
01717                cmd = '0';
01718             }
01719          }
01720          return cmd;
01721       default:
01722          /* If the caller is an ouside caller, and the review option is enabled,
01723             allow them to review the message, but let the owner of the box review
01724             their OGM's */
01725          if (outsidecaller && !ast_test_flag(vmu, MVM_REVIEW))
01726             return cmd;
01727          if (message_exists) {
01728             cmd = ast_play_and_wait(chan, "vm-review");
01729          } else {
01730             cmd = ast_play_and_wait(chan, "vm-torerecord");
01731             if (!cmd)
01732                cmd = ast_waitfordigit(chan, 600);
01733          }
01734 
01735          if (!cmd && outsidecaller && ast_test_flag(vmu, MVM_OPERATOR)) {
01736             cmd = ast_play_and_wait(chan, "vm-reachoper");
01737             if (!cmd)
01738                cmd = ast_waitfordigit(chan, 600);
01739          }
01740          if (!cmd)
01741             cmd = ast_waitfordigit(chan, 6000);
01742          if (!cmd) {
01743             attempts++;
01744          }
01745          if (attempts > max_attempts) {
01746             cmd = 't';
01747          }
01748       }
01749    }
01750    if (outsidecaller)
01751       ast_play_and_wait(chan, "vm-goodbye");
01752    if (cmd == 't')
01753       cmd = 0;
01754    return cmd;
01755 }
01756 
01757 /*! \brief Run external notification for voicemail message */
01758 static void run_externnotify(struct ast_channel *chan, struct minivm_account *vmu)
01759 {
01760    char arguments[BUFSIZ];
01761 
01762    if (ast_strlen_zero(vmu->externnotify) && ast_strlen_zero(global_externnotify))
01763       return;
01764 
01765    snprintf(arguments, sizeof(arguments), "%s %s@%s %s %s&", 
01766       ast_strlen_zero(vmu->externnotify) ? global_externnotify : vmu->externnotify, 
01767       vmu->username, vmu->domain,
01768       (ast_channel_caller(chan)->id.name.valid && ast_channel_caller(chan)->id.name.str)
01769          ? ast_channel_caller(chan)->id.name.str : "",
01770       (ast_channel_caller(chan)->id.number.valid && ast_channel_caller(chan)->id.number.str)
01771          ? ast_channel_caller(chan)->id.number.str : "");
01772 
01773    ast_debug(1, "Executing: %s\n", arguments);
01774    ast_safe_system(arguments);
01775 }
01776 
01777 /*!\internal
01778  * \brief Send message to voicemail account owner */
01779 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)
01780 {
01781    RAII_VAR(struct ast_json *, json_object, NULL, ast_json_unref);
01782    RAII_VAR(struct stasis_message *, message, NULL, ao2_cleanup);
01783    RAII_VAR(struct ast_mwi_state *, mwi_state, NULL, ao2_cleanup);
01784    char *stringp;
01785    struct minivm_template *etemplate;
01786    char *messageformat;
01787    int res = 0;
01788    char oldlocale[100];
01789    const char *counter;
01790 
01791    if (!ast_strlen_zero(vmu->attachfmt)) {
01792       if (strstr(format, vmu->attachfmt)) {
01793          format = vmu->attachfmt;
01794       } else {
01795          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);
01796       }
01797    }
01798 
01799    etemplate = message_template_find(vmu->etemplate);
01800    if (!etemplate)
01801       etemplate = message_template_find(templatename);
01802    if (!etemplate)
01803       etemplate = message_template_find("email-default");
01804 
01805    /* Attach only the first format */
01806    stringp = messageformat = ast_strdupa(format);
01807    strsep(&stringp, "|");
01808 
01809    if (!ast_strlen_zero(etemplate->locale)) {
01810       char *new_locale;
01811       ast_copy_string(oldlocale, setlocale(LC_TIME, NULL), sizeof(oldlocale));
01812       ast_debug(2, "Changing locale from %s to %s\n", oldlocale, etemplate->locale);
01813       new_locale = setlocale(LC_TIME, etemplate->locale);
01814       if (new_locale == NULL) {
01815          ast_log(LOG_WARNING, "-_-_- Changing to new locale did not work. Locale: %s\n", etemplate->locale);
01816       }
01817    }
01818 
01819 
01820 
01821    /* Read counter if available */
01822    ast_channel_lock(chan);
01823    if ((counter = pbx_builtin_getvar_helper(chan, "MVM_COUNTER"))) {
01824       counter = ast_strdupa(counter);
01825    }
01826    ast_channel_unlock(chan);
01827 
01828    if (ast_strlen_zero(counter)) {
01829       ast_debug(2, "MVM_COUNTER not found\n");
01830    } else {
01831       ast_debug(2, "MVM_COUNTER found - will use it with value %s\n", counter);
01832    }
01833 
01834    res = sendmail(etemplate, vmu, cidnum, cidname, filename, messageformat, duration, etemplate->attachment, MVM_MESSAGE_EMAIL, counter);
01835 
01836    if (res == 0 && !ast_strlen_zero(vmu->pager))  {
01837       /* Find template for paging */
01838       etemplate = message_template_find(vmu->ptemplate);
01839       if (!etemplate)
01840          etemplate = message_template_find("pager-default");
01841 
01842       if (!ast_strlen_zero(etemplate->locale)) {
01843          ast_copy_string(oldlocale, setlocale(LC_TIME, ""), sizeof(oldlocale));
01844          setlocale(LC_TIME, etemplate->locale);
01845       }
01846 
01847       res = sendmail(etemplate, vmu, cidnum, cidname, filename, messageformat, duration, etemplate->attachment, MVM_MESSAGE_PAGE, counter);
01848    }
01849 
01850    mwi_state = ast_mwi_create(vmu->username, vmu->domain);
01851    if (!mwi_state) {
01852       goto notify_cleanup;
01853    }
01854    mwi_state->snapshot = ast_channel_snapshot_get_latest(ast_channel_uniqueid(chan));
01855 
01856    json_object = ast_json_pack("{s: s, s: s}",
01857          "Event", "MiniVoiceMail"
01858          "Action", "SentNotification",
01859          "Counter", counter);
01860    if (!json_object) {
01861       goto notify_cleanup;
01862    }
01863    message = ast_mwi_blob_create(mwi_state, ast_mwi_vm_app_type(), json_object);
01864    if (!message) {
01865       goto notify_cleanup;
01866    }
01867    stasis_publish(ast_mwi_topic(mwi_state->uniqueid), message);
01868 
01869 notify_cleanup:
01870    run_externnotify(chan, vmu);     /* Run external notification */
01871    if (!ast_strlen_zero(etemplate->locale)) {
01872       setlocale(LC_TIME, oldlocale);   /* Reset to old locale */
01873    }
01874    return res;
01875 }
01876 
01877  
01878 /*!\internal
01879  * \brief Record voicemail message, store into file prepared for sending e-mail */
01880 static int leave_voicemail(struct ast_channel *chan, char *username, struct leave_vm_options *options)
01881 {
01882    char tmptxtfile[PATH_MAX];
01883    char callerid[256];
01884    FILE *txt;
01885    int res = 0, txtdes;
01886    int duration = 0;
01887    int sound_duration = 0;
01888    char date[256];
01889    char tmpdir[PATH_MAX];
01890    char ext_context[256] = "";
01891    char fmt[80];
01892    char *domain;
01893    char tmp[256] = "";
01894    struct minivm_account *vmu;
01895    int userdir;
01896 
01897    ast_copy_string(tmp, username, sizeof(tmp));
01898    username = tmp;
01899    domain = strchr(tmp, '@');
01900    if (domain) {
01901       *domain = '\0';
01902       domain++;
01903    }
01904 
01905    if (!(vmu = find_account(domain, username, TRUE))) {
01906       /* We could not find user, let's exit */
01907       ast_log(LOG_ERROR, "Can't allocate temporary account for '%s@%s'\n", username, domain);
01908       pbx_builtin_setvar_helper(chan, "MVM_RECORD_STATUS", "FAILED");
01909       return 0;
01910    }
01911 
01912    /* Setup pre-file if appropriate */
01913    if (strcmp(vmu->domain, "localhost"))
01914       snprintf(ext_context, sizeof(ext_context), "%s@%s", username, vmu->domain);
01915    else
01916       ast_copy_string(ext_context, vmu->domain, sizeof(ext_context));
01917 
01918    /* The meat of recording the message...  All the announcements and beeps have been played*/
01919    if (ast_strlen_zero(vmu->attachfmt))
01920       ast_copy_string(fmt, default_vmformat, sizeof(fmt));
01921    else
01922       ast_copy_string(fmt, vmu->attachfmt, sizeof(fmt));
01923 
01924    if (ast_strlen_zero(fmt)) {
01925       ast_log(LOG_WARNING, "No format for saving voicemail? Default %s\n", default_vmformat);
01926       pbx_builtin_setvar_helper(chan, "MVM_RECORD_STATUS", "FAILED");
01927       return res;
01928    }
01929 
01930    userdir = check_dirpath(tmpdir, sizeof(tmpdir), vmu->domain, username, "tmp");
01931 
01932    /* If we have no user directory, use generic temporary directory */
01933    if (!userdir) {
01934       create_dirpath(tmpdir, sizeof(tmpdir), "0000_minivm_temp", "mediafiles", "");
01935       ast_debug(3, "Creating temporary directory %s\n", tmpdir);
01936    }
01937 
01938 
01939    snprintf(tmptxtfile, sizeof(tmptxtfile), "%s/XXXXXX", tmpdir);
01940 
01941    /* XXX This file needs to be in temp directory */
01942    txtdes = mkstemp(tmptxtfile);
01943    if (txtdes < 0) {
01944       ast_log(LOG_ERROR, "Unable to create message file %s: %s\n", tmptxtfile, strerror(errno));
01945       res = ast_streamfile(chan, "vm-mailboxfull", ast_channel_language(chan));
01946       if (!res)
01947          res = ast_waitstream(chan, "");
01948       pbx_builtin_setvar_helper(chan, "MVM_RECORD_STATUS", "FAILED");
01949       return res;
01950    }
01951 
01952    if (res >= 0) {
01953       /* Unless we're *really* silent, try to send the beep */
01954       res = ast_streamfile(chan, "beep", ast_channel_language(chan));
01955       if (!res)
01956          res = ast_waitstream(chan, "");
01957    }
01958 
01959    /* OEJ XXX Maybe this can be turned into a log file? Hmm. */
01960    /* Store information */
01961    ast_debug(2, "Open file for metadata: %s\n", tmptxtfile);
01962 
01963    res = play_record_review(chan, NULL, tmptxtfile, global_vmmaxmessage, fmt, 1, vmu, &duration, &sound_duration, NULL, options->record_gain);
01964 
01965    txt = fdopen(txtdes, "w+");
01966    if (!txt) {
01967       ast_log(LOG_WARNING, "Error opening text file for output\n");
01968    } else {
01969       struct ast_tm tm;
01970       struct timeval now = ast_tvnow();
01971       char timebuf[30];
01972       char logbuf[BUFSIZ];
01973       get_date(date, sizeof(date));
01974       ast_localtime(&now, &tm, NULL);
01975       ast_strftime(timebuf, sizeof(timebuf), "%H:%M:%S", &tm);
01976 
01977       ast_callerid_merge(callerid, sizeof(callerid),
01978          S_COR(ast_channel_caller(chan)->id.name.valid, ast_channel_caller(chan)->id.name.str, NULL),
01979          S_COR(ast_channel_caller(chan)->id.number.valid, ast_channel_caller(chan)->id.number.str, NULL),
01980          "Unknown");
01981       snprintf(logbuf, sizeof(logbuf),
01982          /* "Mailbox:domain:macrocontext:exten:priority:callerchan:callerid:origdate:origtime:duration:durationstatus:accountcode" */
01983          "%s:%s:%s:%s:%d:%s:%s:%s:%s:%d:%s:%s\n",
01984          username,
01985          ast_channel_context(chan),
01986          ast_channel_macrocontext(chan), 
01987          ast_channel_exten(chan),
01988          ast_channel_priority(chan),
01989          ast_channel_name(chan),
01990          callerid,
01991          date, 
01992          timebuf,
01993          duration,
01994          duration < global_vmminmessage ? "IGNORED" : "OK",
01995          vmu->accountcode
01996       ); 
01997       fprintf(txt, "%s", logbuf);
01998       if (minivmlogfile) {
01999          ast_mutex_lock(&minivmloglock);
02000          fprintf(minivmlogfile, "%s", logbuf);
02001          ast_mutex_unlock(&minivmloglock);
02002       }
02003 
02004       if (sound_duration < global_vmminmessage) {
02005          ast_verb(3, "Recording was %d seconds long but needs to be at least %d - abandoning\n", sound_duration, global_vmminmessage);
02006          fclose(txt);
02007          ast_filedelete(tmptxtfile, NULL);
02008          unlink(tmptxtfile);
02009          pbx_builtin_setvar_helper(chan, "MVM_RECORD_STATUS", "FAILED");
02010          return 0;
02011       } 
02012       fclose(txt); /* Close log file */
02013       if (ast_fileexists(tmptxtfile, NULL, NULL) <= 0) {
02014          ast_debug(1, "The recorded media file is gone, so we should remove the .txt file too!\n");
02015          unlink(tmptxtfile);
02016          pbx_builtin_setvar_helper(chan, "MVM_RECORD_STATUS", "FAILED");
02017          if(ast_test_flag(vmu, MVM_ALLOCED))
02018             free_user(vmu);
02019          return 0;
02020       }
02021 
02022       /* Set channel variables for the notify application */
02023       pbx_builtin_setvar_helper(chan, "MVM_FILENAME", tmptxtfile);
02024       snprintf(timebuf, sizeof(timebuf), "%d", duration);
02025       pbx_builtin_setvar_helper(chan, "MVM_DURATION", timebuf);
02026       pbx_builtin_setvar_helper(chan, "MVM_FORMAT", fmt);
02027 
02028    }
02029    global_stats.lastreceived = ast_tvnow();
02030    global_stats.receivedmessages++;
02031 #if 0
02032    /* Go ahead and delete audio files from system, they're not needed any more */
02033    if (ast_fileexists(tmptxtfile, NULL, NULL) <= 0) {
02034       ast_filedelete(tmptxtfile, NULL);
02035        /* Even not being used at the moment, it's better to convert ast_log to ast_debug anyway */
02036       ast_debug(2, "-_-_- Deleted audio file after notification :: %s \n", tmptxtfile);
02037    }
02038 #endif
02039 
02040    if (res > 0)
02041       res = 0;
02042 
02043    if(ast_test_flag(vmu, MVM_ALLOCED))
02044       free_user(vmu);
02045 
02046    pbx_builtin_setvar_helper(chan, "MVM_RECORD_STATUS", "SUCCESS");
02047    return res;
02048 }
02049 
02050 /*!\internal
02051  * \brief Queue a message waiting event */
02052 static void queue_mwi_event(const char *channel_id, const char *mbx, const char *ctx, int urgent, int new, int old)
02053 {
02054    char *mailbox, *context;
02055 
02056    mailbox = ast_strdupa(mbx);
02057    context = ast_strdupa(ctx);
02058    if (ast_strlen_zero(context)) {
02059       context = "default";
02060    }
02061 
02062    ast_publish_mwi_state_channel(mailbox, context, new + urgent, old, channel_id);
02063 }
02064 
02065 /*!\internal
02066  * \brief Send MWI using interal Asterisk event subsystem */
02067 static int minivm_mwi_exec(struct ast_channel *chan, const char *data)
02068 {
02069    int argc;
02070    char *argv[4];
02071    int res = 0;
02072    char *tmpptr;
02073    char tmp[PATH_MAX];
02074    char *mailbox;
02075    char *domain;
02076    if (ast_strlen_zero(data))  {
02077       ast_log(LOG_ERROR, "Minivm needs at least an account argument \n");
02078       return -1;
02079    }
02080    tmpptr = ast_strdupa((char *)data);
02081    argc = ast_app_separate_args(tmpptr, ',', argv, ARRAY_LEN(argv));
02082    if (argc < 4) {
02083       ast_log(LOG_ERROR, "%d arguments passed to MiniVM_MWI, need 4.\n", argc);
02084       return -1;
02085    }
02086    ast_copy_string(tmp, argv[0], sizeof(tmp));
02087    mailbox = tmp;
02088    domain = strchr(tmp, '@');
02089    if (domain) {
02090       *domain = '\0';
02091       domain++;
02092    }
02093    if (ast_strlen_zero(domain) || ast_strlen_zero(mailbox)) {
02094       ast_log(LOG_ERROR, "Need mailbox@context as argument. Sorry. Argument 0 %s\n", argv[0]);
02095       return -1;
02096    }
02097    queue_mwi_event(ast_channel_uniqueid(chan), mailbox, domain, atoi(argv[1]), atoi(argv[2]), atoi(argv[3]));
02098 
02099    return res;
02100 }
02101 
02102 
02103 /*!\internal
02104  * \brief Notify voicemail account owners - either generic template or user specific */
02105 static int minivm_notify_exec(struct ast_channel *chan, const char *data)
02106 {
02107    int argc;
02108    char *argv[2];
02109    int res = 0;
02110    char tmp[PATH_MAX];
02111    char *domain;
02112    char *tmpptr;
02113    struct minivm_account *vmu;
02114    char *username;
02115    const char *template = "";
02116    const char *filename;
02117    const char *format;
02118    const char *duration_string;
02119    if (ast_strlen_zero(data))  {
02120       ast_log(LOG_ERROR, "Minivm needs at least an account argument \n");
02121       return -1;
02122    }
02123    tmpptr = ast_strdupa((char *)data);
02124    argc = ast_app_separate_args(tmpptr, ',', argv, ARRAY_LEN(argv));
02125 
02126    if (argc == 2 && !ast_strlen_zero(argv[1]))
02127       template = argv[1];
02128 
02129    ast_copy_string(tmp, argv[0], sizeof(tmp));
02130    username = tmp;
02131    domain = strchr(tmp, '@');
02132    if (domain) {
02133       *domain = '\0';
02134       domain++;
02135    } 
02136    if (ast_strlen_zero(domain) || ast_strlen_zero(username)) {
02137       ast_log(LOG_ERROR, "Need username@domain as argument. Sorry. Argument 0 %s\n", argv[0]);
02138       return -1;
02139    }
02140 
02141    if(!(vmu = find_account(domain, username, TRUE))) {
02142       /* We could not find user, let's exit */
02143       ast_log(LOG_WARNING, "Could not allocate temporary memory for '%s@%s'\n", username, domain);
02144       pbx_builtin_setvar_helper(chan, "MVM_NOTIFY_STATUS", "FAILED");
02145       return -1;
02146    }
02147 
02148    ast_channel_lock(chan);
02149    if ((filename = pbx_builtin_getvar_helper(chan, "MVM_FILENAME"))) {
02150       filename = ast_strdupa(filename);
02151    }
02152    ast_channel_unlock(chan);
02153    /* Notify of new message to e-mail and pager */
02154    if (!ast_strlen_zero(filename)) {
02155       ast_channel_lock(chan); 
02156       if ((format = pbx_builtin_getvar_helper(chan, "MVM_FORMAT"))) {
02157          format = ast_strdupa(format);
02158       }
02159       if ((duration_string = pbx_builtin_getvar_helper(chan, "MVM_DURATION"))) {
02160          duration_string = ast_strdupa(duration_string);
02161       }
02162       ast_channel_unlock(chan);
02163       res = notify_new_message(chan, template, vmu, filename, atoi(duration_string),
02164          format,
02165          S_COR(ast_channel_caller(chan)->id.number.valid, ast_channel_caller(chan)->id.number.str, NULL),
02166          S_COR(ast_channel_caller(chan)->id.name.valid, ast_channel_caller(chan)->id.name.str, NULL));
02167    }
02168 
02169    pbx_builtin_setvar_helper(chan, "MVM_NOTIFY_STATUS", res == 0 ? "SUCCESS" : "FAILED");
02170 
02171 
02172    if(ast_test_flag(vmu, MVM_ALLOCED))
02173       free_user(vmu);
02174 
02175    /* Ok, we're ready to rock and roll. Return to dialplan */
02176 
02177    return res;
02178 
02179 }
02180 
02181 /*!\internal
02182  * \brief Dialplan function to record voicemail */
02183 static int minivm_record_exec(struct ast_channel *chan, const char *data)
02184 {
02185    int res = 0;
02186    char *tmp;
02187    struct leave_vm_options leave_options;
02188    int argc;
02189    char *argv[2];
02190    struct ast_flags flags = { 0 };
02191    char *opts[OPT_ARG_ARRAY_SIZE];
02192 
02193    memset(&leave_options, 0, sizeof(leave_options));
02194 
02195    /* Answer channel if it's not already answered */
02196    if (ast_channel_state(chan) != AST_STATE_UP)
02197       ast_answer(chan);
02198 
02199    if (ast_strlen_zero(data))  {
02200       ast_log(LOG_ERROR, "Minivm needs at least an account argument \n");
02201       return -1;
02202    }
02203    tmp = ast_strdupa((char *)data);
02204    argc = ast_app_separate_args(tmp, ',', argv, ARRAY_LEN(argv));
02205    if (argc == 2) {
02206       if (ast_app_parse_options(minivm_app_options, &flags, opts, argv[1])) {
02207          return -1;
02208       }
02209       ast_copy_flags(&leave_options, &flags, OPT_SILENT | OPT_BUSY_GREETING | OPT_UNAVAIL_GREETING );
02210       if (ast_test_flag(&flags, OPT_RECORDGAIN)) {
02211          int gain;
02212 
02213          if (sscanf(opts[OPT_ARG_RECORDGAIN], "%30d", &gain) != 1) {
02214             ast_log(LOG_WARNING, "Invalid value '%s' provided for record gain option\n", opts[OPT_ARG_RECORDGAIN]);
02215             return -1;
02216          } else 
02217             leave_options.record_gain = (signed char) gain;
02218       }
02219    } 
02220 
02221    /* Now run the appliation and good luck to you! */
02222    res = leave_voicemail(chan, argv[0], &leave_options);
02223 
02224    if (res == ERROR_LOCK_PATH) {
02225       ast_log(LOG_ERROR, "Could not leave voicemail. The path is already locked.\n");
02226       pbx_builtin_setvar_helper(chan, "MVM_RECORD_STATUS", "FAILED");
02227       res = 0;
02228    }
02229    pbx_builtin_setvar_helper(chan, "MVM_RECORD_STATUS", "SUCCESS");
02230 
02231    return res;
02232 }
02233 
02234 /*!\internal
02235  * \brief Play voicemail prompts - either generic or user specific */
02236 static int minivm_greet_exec(struct ast_channel *chan, const char *data)
02237 {
02238    struct leave_vm_options leave_options = { 0, '\0'};
02239    int argc;
02240    char *argv[2];
02241    struct ast_flags flags = { 0 };
02242    char *opts[OPT_ARG_ARRAY_SIZE];
02243    int res = 0;
02244    int ausemacro = 0;
02245    int ousemacro = 0;
02246    int ouseexten = 0;
02247    char tmp[PATH_MAX];
02248    char dest[PATH_MAX];
02249    char prefile[PATH_MAX] = "";
02250    char tempfile[PATH_MAX] = "";
02251    char ext_context[256] = "";
02252    char *domain;
02253    char ecodes[16] = "#";
02254    char *tmpptr;
02255    struct minivm_account *vmu;
02256    char *username = argv[0];
02257 
02258    if (ast_strlen_zero(data))  {
02259       ast_log(LOG_ERROR, "Minivm needs at least an account argument \n");
02260       return -1;
02261    }
02262    tmpptr = ast_strdupa((char *)data);
02263    argc = ast_app_separate_args(tmpptr, ',', argv, ARRAY_LEN(argv));
02264 
02265    if (argc == 2) {
02266       if (ast_app_parse_options(minivm_app_options, &flags, opts, argv[1]))
02267          return -1;
02268       ast_copy_flags(&leave_options, &flags, OPT_SILENT | OPT_BUSY_GREETING | OPT_UNAVAIL_GREETING );
02269    }
02270 
02271    ast_copy_string(tmp, argv[0], sizeof(tmp));
02272    username = tmp;
02273    domain = strchr(tmp, '@');
02274    if (domain) {
02275       *domain = '\0';
02276       domain++;
02277    } 
02278    if (ast_strlen_zero(domain) || ast_strlen_zero(username)) {
02279       ast_log(LOG_ERROR, "Need username@domain as argument. Sorry. Argument:  %s\n", argv[0]);
02280       return -1;
02281    }
02282    ast_debug(1, "Trying to find configuration for user %s in domain %s\n", username, domain);
02283 
02284    if (!(vmu = find_account(domain, username, TRUE))) {
02285       ast_log(LOG_ERROR, "Could not allocate memory. \n");
02286       return -1;
02287    }
02288 
02289    /* Answer channel if it's not already answered */
02290    if (ast_channel_state(chan) != AST_STATE_UP)
02291       ast_answer(chan);
02292 
02293    /* Setup pre-file if appropriate */
02294    if (strcmp(vmu->domain, "localhost"))
02295       snprintf(ext_context, sizeof(ext_context), "%s@%s", username, vmu->domain);
02296    else
02297       ast_copy_string(ext_context, vmu->domain, sizeof(ext_context));
02298 
02299    if (ast_test_flag(&leave_options, OPT_BUSY_GREETING)) {
02300       res = check_dirpath(dest, sizeof(dest), vmu->domain, username, "busy");
02301       if (res)
02302          snprintf(prefile, sizeof(prefile), "%s%s/%s/busy", MVM_SPOOL_DIR, vmu->domain, username);
02303    } else if (ast_test_flag(&leave_options, OPT_UNAVAIL_GREETING)) {
02304       res = check_dirpath(dest, sizeof(dest), vmu->domain, username, "unavail");
02305       if (res)
02306          snprintf(prefile, sizeof(prefile), "%s%s/%s/unavail", MVM_SPOOL_DIR, vmu->domain, username);
02307    }
02308    /* Check for temporary greeting - it overrides busy and unavail */
02309    snprintf(tempfile, sizeof(tempfile), "%s%s/%s/temp", MVM_SPOOL_DIR, vmu->domain, username);
02310    if (!(res = check_dirpath(dest, sizeof(dest), vmu->domain, username, "temp"))) {
02311       ast_debug(2, "Temporary message directory does not exist, using default (%s)\n", tempfile);
02312       ast_copy_string(prefile, tempfile, sizeof(prefile));
02313    }
02314    ast_debug(2, "Preparing to play message ...\n");
02315 
02316    /* Check current or macro-calling context for special extensions */
02317    if (ast_test_flag(vmu, MVM_OPERATOR)) {
02318       if (!ast_strlen_zero(vmu->exit)) {
02319          if (ast_exists_extension(chan, vmu->exit, "o", 1,
02320             S_COR(ast_channel_caller(chan)->id.number.valid, ast_channel_caller(chan)->id.number.str, NULL))) {
02321             strncat(ecodes, "0", sizeof(ecodes) - strlen(ecodes) - 1);
02322             ouseexten = 1;
02323          }
02324       } else if (ast_exists_extension(chan, ast_channel_context(chan), "o", 1,
02325          S_COR(ast_channel_caller(chan)->id.number.valid, ast_channel_caller(chan)->id.number.str, NULL))) {
02326          strncat(ecodes, "0", sizeof(ecodes) - strlen(ecodes) - 1);
02327          ouseexten = 1;
02328       }
02329       else if (!ast_strlen_zero(ast_channel_macrocontext(chan))
02330          && ast_exists_extension(chan, ast_channel_macrocontext(chan), "o", 1,
02331             S_COR(ast_channel_caller(chan)->id.number.valid, ast_channel_caller(chan)->id.number.str, NULL))) {
02332          strncat(ecodes, "0", sizeof(ecodes) - strlen(ecodes) - 1);
02333          ousemacro = 1;
02334       }
02335    }
02336 
02337    if (!ast_strlen_zero(vmu->exit)) {
02338       if (ast_exists_extension(chan, vmu->exit, "a", 1,
02339          S_COR(ast_channel_caller(chan)->id.number.valid, ast_channel_caller(chan)->id.number.str, NULL))) {
02340          strncat(ecodes, "*", sizeof(ecodes) -  strlen(ecodes) - 1);
02341       }
02342    } else if (ast_exists_extension(chan, ast_channel_context(chan), "a", 1,
02343       S_COR(ast_channel_caller(chan)->id.number.valid, ast_channel_caller(chan)->id.number.str, NULL))) {
02344       strncat(ecodes, "*", sizeof(ecodes) -  strlen(ecodes) - 1);
02345    } else if (!ast_strlen_zero(ast_channel_macrocontext(chan))
02346       && ast_exists_extension(chan, ast_channel_macrocontext(chan), "a", 1,
02347          S_COR(ast_channel_caller(chan)->id.number.valid, ast_channel_caller(chan)->id.number.str, NULL))) {
02348       strncat(ecodes, "*", sizeof(ecodes) -  strlen(ecodes) - 1);
02349       ausemacro = 1;
02350    }
02351 
02352    res = 0; /* Reset */
02353    /* Play the beginning intro if desired */
02354    if (!ast_strlen_zero(prefile)) {
02355       if (ast_streamfile(chan, prefile, ast_channel_language(chan)) > -1) 
02356          res = ast_waitstream(chan, ecodes);
02357    } else {
02358       ast_debug(2, "%s doesn't exist, doing what we can\n", prefile);
02359       res = invent_message(chan, vmu->domain, username, ast_test_flag(&leave_options, OPT_BUSY_GREETING), ecodes);
02360    }
02361    if (res < 0) {
02362       ast_debug(2, "Hang up during prefile playback\n");
02363       pbx_builtin_setvar_helper(chan, "MVM_GREET_STATUS", "FAILED");
02364       if(ast_test_flag(vmu, MVM_ALLOCED))
02365          free_user(vmu);
02366       return -1;
02367    }
02368    if (res == '#') {
02369       /* On a '#' we skip the instructions */
02370       ast_set_flag(&leave_options, OPT_SILENT);
02371       res = 0;
02372    }
02373    if (!res && !ast_test_flag(&leave_options, OPT_SILENT)) {
02374       res = ast_streamfile(chan, SOUND_INTRO, ast_channel_language(chan));
02375       if (!res)
02376          res = ast_waitstream(chan, ecodes);
02377       if (res == '#') {
02378          ast_set_flag(&leave_options, OPT_SILENT);
02379          res = 0;
02380       }
02381    }
02382    if (res > 0)
02383       ast_stopstream(chan);
02384    /* Check for a '*' here in case the caller wants to escape from voicemail to something
02385       other than the operator -- an automated attendant or mailbox login for example */
02386    if (res == '*') {
02387       ast_channel_exten_set(chan, "a");
02388       if (!ast_strlen_zero(vmu->exit)) {
02389          ast_channel_context_set(chan, vmu->exit);
02390       } else if (ausemacro && !ast_strlen_zero(ast_channel_macrocontext(chan))) {
02391          ast_channel_context_set(chan, ast_channel_macrocontext(chan));
02392       }
02393       ast_channel_priority_set(chan, 0);
02394       pbx_builtin_setvar_helper(chan, "MVM_GREET_STATUS", "USEREXIT");
02395       res = 0;
02396    } else if (res == '0') { /* Check for a '0' here */
02397       if(ouseexten || ousemacro) {
02398          ast_channel_exten_set(chan, "o");
02399          if (!ast_strlen_zero(vmu->exit)) {
02400             ast_channel_context_set(chan, vmu->exit);
02401          } else if (ousemacro && !ast_strlen_zero(ast_channel_macrocontext(chan))) {
02402             ast_channel_context_set(chan, ast_channel_macrocontext(chan));
02403          }
02404          ast_play_and_wait(chan, "transfer");
02405          ast_channel_priority_set(chan, 0);
02406          pbx_builtin_setvar_helper(chan, "MVM_GREET_STATUS", "USEREXIT");
02407       }
02408       res =  0;
02409    } else if (res < 0) {
02410       pbx_builtin_setvar_helper(chan, "MVM_GREET_STATUS", "FAILED");
02411       res = -1;
02412    } else
02413       pbx_builtin_setvar_helper(chan, "MVM_GREET_STATUS", "SUCCESS");
02414 
02415    if(ast_test_flag(vmu, MVM_ALLOCED))
02416       free_user(vmu);
02417 
02418 
02419    /* Ok, we're ready to rock and roll. Return to dialplan */
02420    return res;
02421 
02422 }
02423 
02424 /*!\internal
02425  * \brief Dialplan application to delete voicemail */
02426 static int minivm_delete_exec(struct ast_channel *chan, const char *data)
02427 {
02428    int res = 0;
02429    char filename[BUFSIZ];
02430 
02431    if (!ast_strlen_zero(data)) {
02432       ast_copy_string(filename, (char *) data, sizeof(filename));
02433    } else {
02434       ast_channel_lock(chan);
02435       ast_copy_string(filename, pbx_builtin_getvar_helper(chan, "MVM_FILENAME"), sizeof(filename));
02436       ast_channel_unlock(chan);
02437    }
02438 
02439    if (ast_strlen_zero(filename)) {
02440       ast_log(LOG_ERROR, "No filename given in application arguments or channel variable MVM_FILENAME\n");
02441       return res;
02442    } 
02443 
02444    /* Go ahead and delete audio files from system, they're not needed any more */
02445    /* We should look for both audio and text files here */
02446    if (ast_fileexists(filename, NULL, NULL) > 0) {
02447       res = vm_delete(filename);
02448       if (res) {
02449          ast_debug(2, "Can't delete file: %s\n", filename);
02450          pbx_builtin_setvar_helper(chan, "MVM_DELETE_STATUS", "FAILED");
02451       } else {
02452          ast_debug(2, "Deleted voicemail file :: %s \n", filename);
02453          pbx_builtin_setvar_helper(chan, "MVM_DELETE_STATUS", "SUCCESS");
02454       }
02455    } else {
02456       ast_debug(2, "Filename does not exist: %s\n", filename);
02457       pbx_builtin_setvar_helper(chan, "MVM_DELETE_STATUS", "FAILED");
02458    }
02459 
02460    return res;
02461 }
02462 
02463 /*! \brief Record specific messages for voicemail account */
02464 static int minivm_accmess_exec(struct ast_channel *chan, const char *data)
02465 {
02466    int argc = 0;
02467    char *argv[2];
02468    char filename[PATH_MAX];
02469    char tmp[PATH_MAX];
02470    char *domain;
02471    char *tmpptr = NULL;
02472    struct minivm_account *vmu;
02473    char *username;
02474    struct ast_flags flags = { 0 };
02475    char *opts[OPT_ARG_ARRAY_SIZE];
02476    int error = FALSE;
02477    char *message = NULL;
02478    char *prompt = NULL;
02479    int duration;
02480 
02481    if (ast_strlen_zero(data))  {
02482       ast_log(LOG_ERROR, "MinivmAccmess needs at least two arguments: account and option\n");
02483       error = TRUE;
02484    } else {
02485       tmpptr = ast_strdupa((char *)data);
02486       argc = ast_app_separate_args(tmpptr, ',', argv, ARRAY_LEN(argv));
02487    }
02488 
02489    if (argc <=1) {
02490       ast_log(LOG_ERROR, "MinivmAccmess needs at least two arguments: account and option\n");
02491       error = TRUE;
02492    }
02493    if (!error && strlen(argv[1]) > 1) {
02494       ast_log(LOG_ERROR, "MinivmAccmess can only handle one option at a time. Bad option string: %s\n", argv[1]);
02495       error = TRUE;
02496    }
02497 
02498    if (!error && ast_app_parse_options(minivm_accmess_options, &flags, opts, argv[1])) {
02499       ast_log(LOG_ERROR, "Can't parse option %s\n", argv[1]);
02500       error = TRUE;
02501    }
02502 
02503    if (error) {
02504       pbx_builtin_setvar_helper(chan, "MVM_ACCMESS_STATUS", "FAILED");
02505       return -1;
02506    }
02507 
02508    ast_copy_string(tmp, argv[0], sizeof(tmp));
02509    username = tmp;
02510    domain = strchr(tmp, '@');
02511    if (domain) {
02512       *domain = '\0';
02513       domain++;
02514    } 
02515    if (ast_strlen_zero(domain) || ast_strlen_zero(username)) {
02516       ast_log(LOG_ERROR, "Need username@domain as argument. Sorry. Argument 0 %s\n", argv[0]);
02517       pbx_builtin_setvar_helper(chan, "MVM_ACCMESS_STATUS", "FAILED");
02518       return -1;
02519    }
02520 
02521    if(!(vmu = find_account(domain, username, TRUE))) {
02522       /* We could not find user, let's exit */
02523       ast_log(LOG_WARNING, "Could not allocate temporary memory for '%s@%s'\n", username, domain);
02524       pbx_builtin_setvar_helper(chan, "MVM_ACCMESS_STATUS", "FAILED");
02525       return -1;
02526    }
02527 
02528    /* Answer channel if it's not already answered */
02529    if (ast_channel_state(chan) != AST_STATE_UP)
02530       ast_answer(chan);
02531    
02532    /* Here's where the action is */
02533    if (ast_test_flag(&flags, OPT_BUSY_GREETING)) {
02534       message = "busy";
02535       prompt = "vm-rec-busy";
02536    } else if (ast_test_flag(&flags, OPT_UNAVAIL_GREETING)) {
02537       message = "unavailable";
02538       prompt = "vm-rec-unv";
02539    } else if (ast_test_flag(&flags, OPT_TEMP_GREETING)) {
02540       message = "temp";
02541       prompt = "vm-rec-temp";
02542    } else if (ast_test_flag(&flags, OPT_NAME_GREETING)) {
02543       message = "greet";
02544       prompt = "vm-rec-name";
02545    }
02546    snprintf(filename,sizeof(filename), "%s%s/%s/%s", MVM_SPOOL_DIR, vmu->domain, vmu->username, message);
02547    /* Maybe we should check the result of play_record_review ? */
02548    play_record_review(chan, prompt, filename, global_maxgreet, default_vmformat, 0, vmu, &duration, NULL, NULL, FALSE);
02549 
02550    ast_debug(1, "Recorded new %s message in %s (duration %d)\n", message, filename, duration);
02551 
02552    if(ast_test_flag(vmu, MVM_ALLOCED))
02553       free_user(vmu);
02554 
02555    pbx_builtin_setvar_helper(chan, "MVM_ACCMESS_STATUS", "SUCCESS");
02556 
02557    /* Ok, we're ready to rock and roll. Return to dialplan */
02558    return 0;
02559 }
02560 
02561 /*! \brief Append new mailbox to mailbox list from configuration file */
02562 static int create_vmaccount(char *name, struct ast_variable *var, int realtime)
02563 {
02564    struct minivm_account *vmu;
02565    char *domain;
02566    char *username;
02567    char accbuf[BUFSIZ];
02568 
02569    ast_debug(3, "Creating %s account for [%s]\n", realtime ? "realtime" : "static", name);
02570 
02571    ast_copy_string(accbuf, name, sizeof(accbuf));
02572    username = accbuf;
02573    domain = strchr(accbuf, '@');
02574    if (domain) {
02575       *domain = '\0';
02576       domain++;
02577    }
02578    if (ast_strlen_zero(domain)) {
02579       ast_log(LOG_ERROR, "No domain given for mini-voicemail account %s. Not configured.\n", name);
02580       return 0;
02581    }
02582 
02583    ast_debug(3, "Creating static account for user %s domain %s\n", username, domain);
02584 
02585    /* Allocate user account */
02586    vmu = ast_calloc(1, sizeof(*vmu));
02587    if (!vmu)
02588       return 0;
02589    
02590    ast_copy_string(vmu->domain, domain, sizeof(vmu->domain));
02591    ast_copy_string(vmu->username, username, sizeof(vmu->username));
02592 
02593    populate_defaults(vmu);
02594 
02595    ast_debug(3, "...Configuring account %s\n", name);
02596 
02597    while (var) {
02598       ast_debug(3, "Configuring %s = \"%s\" for account %s\n", var->name, var->value, name);
02599       if (!strcasecmp(var->name, "serveremail")) {
02600          ast_copy_string(vmu->serveremail, var->value, sizeof(vmu->serveremail));
02601       } else if (!strcasecmp(var->name, "email")) {
02602          ast_copy_string(vmu->email, var->value, sizeof(vmu->email));
02603       } else if (!strcasecmp(var->name, "accountcode")) {
02604          ast_copy_string(vmu->accountcode, var->value, sizeof(vmu->accountcode));
02605       } else if (!strcasecmp(var->name, "pincode")) {
02606          ast_copy_string(vmu->pincode, var->value, sizeof(vmu->pincode));
02607       } else if (!strcasecmp(var->name, "domain")) {
02608          ast_copy_string(vmu->domain, var->value, sizeof(vmu->domain));
02609       } else if (!strcasecmp(var->name, "language")) {
02610          ast_copy_string(vmu->language, var->value, sizeof(vmu->language));
02611       } else if (!strcasecmp(var->name, "timezone")) {
02612          ast_copy_string(vmu->zonetag, var->value, sizeof(vmu->zonetag));
02613       } else if (!strcasecmp(var->name, "externnotify")) {
02614          ast_copy_string(vmu->externnotify, var->value, sizeof(vmu->externnotify));
02615       } else if (!strcasecmp(var->name, "etemplate")) {
02616          ast_copy_string(vmu->etemplate, var->value, sizeof(vmu->etemplate));
02617       } else if (!strcasecmp(var->name, "ptemplate")) {
02618          ast_copy_string(vmu->ptemplate, var->value, sizeof(vmu->ptemplate));
02619       } else if (!strcasecmp(var->name, "fullname")) {
02620          ast_copy_string(vmu->fullname, var->value, sizeof(vmu->fullname));
02621       } else if (!strcasecmp(var->name, "setvar")) {
02622          char *varval;
02623          char *varname = ast_strdupa(var->value);
02624          struct ast_variable *tmpvar;
02625 
02626          if ((varval = strchr(varname, '='))) {
02627             *varval = '\0';
02628             varval++;
02629             if ((tmpvar = ast_variable_new(varname, varval, ""))) {
02630                tmpvar->next = vmu->chanvars;
02631                vmu->chanvars = tmpvar;
02632             }
02633          }
02634       } else if (!strcasecmp(var->name, "pager")) {
02635          ast_copy_string(vmu->pager, var->value, sizeof(vmu->pager));
02636       } else if (!strcasecmp(var->name, "volgain")) {
02637          sscanf(var->value, "%30lf", &vmu->volgain);
02638       } else {
02639          ast_log(LOG_ERROR, "Unknown configuration option for minivm account %s : %s\n", name, var->name);
02640       }
02641       var = var->next;
02642    }
02643    ast_debug(3, "...Linking account %s\n", name);
02644    
02645    AST_LIST_LOCK(&minivm_accounts);
02646    AST_LIST_INSERT_TAIL(&minivm_accounts, vmu, list);
02647    AST_LIST_UNLOCK(&minivm_accounts);
02648 
02649    global_stats.voicemailaccounts++;
02650 
02651    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)" : "");
02652    return 0;
02653 }
02654 
02655 /*! \brief Free Mini Voicemail timezone */
02656 static void free_zone(struct minivm_zone *z)
02657 {
02658    ast_free(z);
02659 }
02660 
02661 /*! \brief Clear list of timezones */
02662 static void timezone_destroy_list(void)
02663 {
02664    struct minivm_zone *this;
02665 
02666    AST_LIST_LOCK(&minivm_zones);
02667    while ((this = AST_LIST_REMOVE_HEAD(&minivm_zones, list))) 
02668       free_zone(this);
02669       
02670    AST_LIST_UNLOCK(&minivm_zones);
02671 }
02672 
02673 /*! \brief Add time zone to memory list */
02674 static int timezone_add(const char *zonename, const char *config)
02675 {
02676    struct minivm_zone *newzone;
02677    char *msg_format, *timezone_str;
02678 
02679    newzone = ast_calloc(1, sizeof(*newzone));
02680    if (newzone == NULL)
02681       return 0;
02682 
02683    msg_format = ast_strdupa(config);
02684 
02685    timezone_str = strsep(&msg_format, "|");
02686    if (!msg_format) {
02687       ast_log(LOG_WARNING, "Invalid timezone definition : %s\n", zonename);
02688       ast_free(newzone);
02689       return 0;
02690    }
02691          
02692    ast_copy_string(newzone->name, zonename, sizeof(newzone->name));
02693    ast_copy_string(newzone->timezone, timezone_str, sizeof(newzone->timezone));
02694    ast_copy_string(newzone->msg_format, msg_format, sizeof(newzone->msg_format));
02695 
02696    AST_LIST_LOCK(&minivm_zones);
02697    AST_LIST_INSERT_TAIL(&minivm_zones, newzone, list);
02698    AST_LIST_UNLOCK(&minivm_zones);
02699 
02700    global_stats.timezones++;
02701 
02702    return 0;
02703 }
02704 
02705 /*! \brief Read message template from file */
02706 static char *message_template_parse_filebody(const char *filename) {
02707    char buf[BUFSIZ * 6];
02708    char readbuf[BUFSIZ];
02709    char filenamebuf[BUFSIZ];
02710    char *writepos;
02711    char *messagebody;
02712    FILE *fi;
02713    int lines = 0;
02714 
02715    if (ast_strlen_zero(filename))
02716       return NULL;
02717    if (*filename == '/') 
02718       ast_copy_string(filenamebuf, filename, sizeof(filenamebuf));
02719    else 
02720       snprintf(filenamebuf, sizeof(filenamebuf), "%s/%s", ast_config_AST_CONFIG_DIR, filename);
02721 
02722    if (!(fi = fopen(filenamebuf, "r"))) {
02723       ast_log(LOG_ERROR, "Can't read message template from file: %s\n", filenamebuf);
02724       return NULL;
02725    }
02726    writepos = buf;
02727    while (fgets(readbuf, sizeof(readbuf), fi)) {
02728       lines ++;
02729       if (writepos != buf) {
02730          *writepos = '\n';    /* Replace EOL with new line */
02731          writepos++;
02732       }
02733       ast_copy_string(writepos, readbuf, sizeof(buf) - (writepos - buf));
02734       writepos += strlen(readbuf) - 1;
02735    }
02736    fclose(fi);
02737    messagebody = ast_calloc(1, strlen(buf + 1));
02738    ast_copy_string(messagebody, buf, strlen(buf) + 1);
02739    ast_debug(4, "---> Size of allocation %d\n", (int) strlen(buf + 1) );
02740    ast_debug(4, "---> Done reading message template : \n%s\n---- END message template--- \n", messagebody);
02741 
02742    return messagebody;
02743 }
02744 
02745 /*! \brief Parse emailbody template from configuration file */
02746 static char *message_template_parse_emailbody(const char *configuration)
02747 {
02748    char *tmpread, *tmpwrite;
02749    char *emailbody = ast_strdup(configuration);
02750 
02751    /* substitute strings \t and \n into the apropriate characters */
02752    tmpread = tmpwrite = emailbody;
02753    while ((tmpwrite = strchr(tmpread,'\\'))) {
02754           int len = strlen("\n");
02755           switch (tmpwrite[1]) {
02756           case 'n':
02757             memmove(tmpwrite + len, tmpwrite + 2, strlen(tmpwrite + 2) + 1);
02758             strncpy(tmpwrite, "\n", len);
02759             break;
02760           case 't':
02761             memmove(tmpwrite + len, tmpwrite + 2, strlen(tmpwrite + 2) + 1);
02762             strncpy(tmpwrite, "\t", len);
02763             break;
02764           default:
02765             ast_log(LOG_NOTICE, "Substitution routine does not support this character: %c\n", tmpwrite[1]);
02766           }
02767           tmpread = tmpwrite + len;
02768    }
02769    return emailbody; 
02770 }
02771 
02772 /*! \brief Apply general configuration options */
02773 static int apply_general_options(struct ast_variable *var)
02774 {
02775    int error = 0;
02776 
02777    while (var) {
02778       /* Mail command */
02779       if (!strcmp(var->name, "mailcmd")) {
02780          ast_copy_string(global_mailcmd, var->value, sizeof(global_mailcmd)); /* User setting */
02781       } else if (!strcmp(var->name, "maxgreet")) {
02782          global_maxgreet = atoi(var->value);
02783       } else if (!strcmp(var->name, "maxsilence")) {
02784          global_maxsilence = atoi(var->value);
02785          if (global_maxsilence > 0)
02786             global_maxsilence *= 1000;
02787       } else if (!strcmp(var->name, "logfile")) {
02788          if (!ast_strlen_zero(var->value) ) {
02789             if(*(var->value) == '/')
02790                ast_copy_string(global_logfile, var->value, sizeof(global_logfile));
02791             else
02792                snprintf(global_logfile, sizeof(global_logfile), "%s/%s", ast_config_AST_LOG_DIR, var->value);
02793          }
02794       } else if (!strcmp(var->name, "externnotify")) {
02795          /* External voicemail notify application */
02796          ast_copy_string(global_externnotify, var->value, sizeof(global_externnotify));
02797       } else if (!strcmp(var->name, "silencetreshold")) {
02798          /* Silence treshold */
02799          global_silencethreshold = atoi(var->value);
02800       } else if (!strcmp(var->name, "maxmessage")) {
02801          int x;
02802          if (sscanf(var->value, "%30d", &x) == 1) {
02803             global_vmmaxmessage = x;
02804          } else {
02805             error ++;
02806             ast_log(LOG_WARNING, "Invalid max message time length\n");
02807          }
02808       } else if (!strcmp(var->name, "minmessage")) {
02809          int x;
02810          if (sscanf(var->value, "%30d", &x) == 1) {
02811             global_vmminmessage = x;
02812             if (global_maxsilence <= global_vmminmessage)
02813                ast_log(LOG_WARNING, "maxsilence should be less than minmessage or you may get empty messages\n");
02814          } else {
02815             error ++;
02816             ast_log(LOG_WARNING, "Invalid min message time length\n");
02817          }
02818       } else if (!strcmp(var->name, "format")) {
02819          ast_copy_string(default_vmformat, var->value, sizeof(default_vmformat));
02820       } else if (!strcmp(var->name, "review")) {
02821          ast_set2_flag((&globalflags), ast_true(var->value), MVM_REVIEW);  
02822       } else if (!strcmp(var->name, "operator")) {
02823          ast_set2_flag((&globalflags), ast_true(var->value), MVM_OPERATOR);   
02824       }
02825       var = var->next;
02826    }
02827    return error;
02828 }
02829 
02830 /*! \brief Load minivoicemail configuration */
02831 static int load_config(int reload)
02832 {
02833    struct ast_config *cfg;
02834    struct ast_variable *var;
02835    char *cat;
02836    const char *chanvar;
02837    int error = 0;
02838    struct minivm_template *template;
02839    struct ast_flags config_flags = { reload ? CONFIG_FLAG_FILEUNCHANGED : 0 };
02840 
02841    cfg = ast_config_load(VOICEMAIL_CONFIG, config_flags);
02842    if (cfg == CONFIG_STATUS_FILEUNCHANGED) {
02843       return 0;
02844    } else if (cfg == CONFIG_STATUS_FILEINVALID) {
02845       ast_log(LOG_ERROR, "Config file " VOICEMAIL_CONFIG " is in an invalid format.  Aborting.\n");
02846       return 0;
02847    }
02848 
02849    ast_mutex_lock(&minivmlock);
02850 
02851    /* Destroy lists to reconfigure */
02852    message_destroy_list();    /* Destroy list of voicemail message templates */
02853    timezone_destroy_list();   /* Destroy list of timezones */
02854    vmaccounts_destroy_list(); /* Destroy list of voicemail accounts */
02855    ast_debug(2, "Destroyed memory objects...\n");
02856 
02857    /* First, set some default settings */
02858    global_externnotify[0] = '\0';
02859    global_logfile[0] = '\0';
02860    global_vmmaxmessage = 2000;
02861    global_maxgreet = 2000;
02862    global_vmminmessage = 0;
02863    strcpy(global_mailcmd, SENDMAIL);
02864    global_maxsilence = 0;
02865    global_saydurationminfo = 2;
02866    ast_copy_string(default_vmformat, "wav", sizeof(default_vmformat));
02867    ast_set2_flag((&globalflags), FALSE, MVM_REVIEW);  
02868    ast_set2_flag((&globalflags), FALSE, MVM_OPERATOR);   
02869    /* Reset statistics */
02870    memset(&global_stats, 0, sizeof(global_stats));
02871    global_stats.reset = ast_tvnow();
02872 
02873    global_silencethreshold = ast_dsp_get_threshold_from_settings(THRESHOLD_SILENCE);
02874 
02875    /* Make sure we could load configuration file */
02876    if (!cfg) {
02877       ast_log(LOG_WARNING, "Failed to load configuration file. Module activated with default settings.\n");
02878       ast_mutex_unlock(&minivmlock);
02879       return 0;
02880    }
02881 
02882    ast_debug(2, "Loaded configuration file, now parsing\n");
02883 
02884    /* General settings */
02885 
02886    cat = ast_category_browse(cfg, NULL);
02887    while (cat) {
02888       ast_debug(3, "Found configuration section [%s]\n", cat);
02889       if (!strcasecmp(cat, "general")) {
02890          /* Nothing right now */
02891          error += apply_general_options(ast_variable_browse(cfg, cat));
02892       } else if (!strncasecmp(cat, "template-", 9))  {
02893          /* Template */
02894          char *name = cat + 9;
02895 
02896          /* Now build and link template to list */
02897          error += message_template_build(name, ast_variable_browse(cfg, cat));
02898       } else {
02899          var = ast_variable_browse(cfg, cat);
02900          if (!strcasecmp(cat, "zonemessages")) {
02901             /* Timezones in this context */
02902             while (var) {
02903                timezone_add(var->name, var->value);
02904                var = var->next;
02905             }
02906          } else {
02907             /* Create mailbox from this */
02908             error += create_vmaccount(cat, var, FALSE);
02909          }
02910       }
02911       /* Find next section in configuration file */
02912       cat = ast_category_browse(cfg, cat);
02913    }
02914 
02915    /* Configure the default email template */
02916    message_template_build("email-default", NULL);
02917    template = message_template_find("email-default");
02918 
02919    /* Load date format config for voicemail mail */
02920    if ((chanvar = ast_variable_retrieve(cfg, "general", "emaildateformat"))) 
02921       ast_copy_string(template->dateformat, chanvar, sizeof(template->dateformat));
02922    if ((chanvar = ast_variable_retrieve(cfg, "general", "emailfromstring")))
02923       ast_copy_string(template->fromaddress, chanvar, sizeof(template->fromaddress));
02924    if ((chanvar = ast_variable_retrieve(cfg, "general", "emailaaddress")))
02925       ast_copy_string(template->serveremail, chanvar, sizeof(template->serveremail));
02926    if ((chanvar = ast_variable_retrieve(cfg, "general", "emailcharset")))
02927       ast_copy_string(template->charset, chanvar, sizeof(template->charset));
02928    if ((chanvar = ast_variable_retrieve(cfg, "general", "emailsubject"))) 
02929       ast_copy_string(template->subject, chanvar, sizeof(template->subject));
02930    if ((chanvar = ast_variable_retrieve(cfg, "general", "emailbody"))) 
02931       template->body = message_template_parse_emailbody(chanvar);
02932    template->attachment = TRUE;
02933 
02934    message_template_build("pager-default", NULL);
02935    template = message_template_find("pager-default");
02936    if ((chanvar = ast_variable_retrieve(cfg, "general", "pagerfromstring")))
02937       ast_copy_string(template->fromaddress, chanvar, sizeof(template->fromaddress));
02938    if ((chanvar = ast_variable_retrieve(cfg, "general", "pageraddress")))
02939       ast_copy_string(template->serveremail, chanvar, sizeof(template->serveremail));
02940    if ((chanvar = ast_variable_retrieve(cfg, "general", "pagercharset")))
02941       ast_copy_string(template->charset, chanvar, sizeof(template->charset));
02942    if ((chanvar = ast_variable_retrieve(cfg, "general", "pagersubject")))
02943       ast_copy_string(template->subject, chanvar,sizeof(template->subject));
02944    if ((chanvar = ast_variable_retrieve(cfg, "general", "pagerbody"))) 
02945       template->body = message_template_parse_emailbody(chanvar);
02946    template->attachment = FALSE;
02947 
02948    if (error)
02949       ast_log(LOG_ERROR, "--- A total of %d errors found in mini-voicemail configuration\n", error);
02950 
02951    ast_mutex_unlock(&minivmlock);
02952    ast_config_destroy(cfg);
02953 
02954    /* Close log file if it's open and disabled */
02955    if(minivmlogfile)
02956       fclose(minivmlogfile);
02957 
02958    /* Open log file if it's enabled */
02959    if(!ast_strlen_zero(global_logfile)) {
02960       minivmlogfile = fopen(global_logfile, "a");
02961       if(!minivmlogfile)
02962          ast_log(LOG_ERROR, "Failed to open minivm log file %s : %s\n", global_logfile, strerror(errno));
02963       if (minivmlogfile)
02964          ast_debug(3, "Opened log file %s \n", global_logfile);
02965    }
02966 
02967    return 0;
02968 }
02969 
02970 /*! \brief CLI routine for listing templates */
02971 static char *handle_minivm_list_templates(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
02972 {
02973    struct minivm_template *this;
02974 #define HVLT_OUTPUT_FORMAT "%-15s %-10s %-10s %-15.15s %-50s\n"
02975    int count = 0;
02976 
02977    switch (cmd) {
02978    case CLI_INIT:
02979       e->command = "minivm list templates";
02980       e->usage =
02981          "Usage: minivm list templates\n"
02982          "       Lists message templates for e-mail, paging and IM\n";
02983       return NULL;
02984    case CLI_GENERATE:
02985       return NULL;
02986    }
02987 
02988    if (a->argc > 3)
02989       return CLI_SHOWUSAGE;
02990 
02991    AST_LIST_LOCK(&message_templates);
02992    if (AST_LIST_EMPTY(&message_templates)) {
02993       ast_cli(a->fd, "There are no message templates defined\n");
02994       AST_LIST_UNLOCK(&message_templates);
02995       return CLI_FAILURE;
02996    }
02997    ast_cli(a->fd, HVLT_OUTPUT_FORMAT, "Template name", "Charset", "Locale", "Attach media", "Subject");
02998    ast_cli(a->fd, HVLT_OUTPUT_FORMAT, "-------------", "-------", "------", "------------", "-------");
02999    AST_LIST_TRAVERSE(&message_templates, this, list) {
03000       ast_cli(a->fd, HVLT_OUTPUT_FORMAT, this->name, 
03001          this->charset ? this->charset : "-", 
03002          this->locale ? this->locale : "-",
03003          this->attachment ? "Yes" : "No",
03004          this->subject ? this->subject : "-");
03005       count++;
03006    }
03007    AST_LIST_UNLOCK(&message_templates);
03008    ast_cli(a->fd, "\n * Total: %d minivoicemail message templates\n", count);
03009    return CLI_SUCCESS;
03010 }
03011 
03012 static char *complete_minivm_show_users(const char *line, const char *word, int pos, int state)
03013 {
03014    int which = 0;
03015    int wordlen;
03016    struct minivm_account *vmu;
03017    const char *domain = "";
03018 
03019    /* 0 - voicemail; 1 - list; 2 - accounts; 3 - for; 4 - <domain> */
03020    if (pos > 4)
03021       return NULL;
03022    if (pos == 3)
03023       return (state == 0) ? ast_strdup("for") : NULL;
03024    wordlen = strlen(word);
03025    AST_LIST_TRAVERSE(&minivm_accounts, vmu, list) {
03026       if (!strncasecmp(word, vmu->domain, wordlen)) {
03027          if (domain && strcmp(domain, vmu->domain) && ++which > state)
03028             return ast_strdup(vmu->domain);
03029          /* ignore repeated domains ? */
03030          domain = vmu->domain;
03031       }
03032    }
03033    return NULL;
03034 }
03035 
03036 /*! \brief CLI command to list voicemail accounts */
03037 static char *handle_minivm_show_users(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
03038 {
03039    struct minivm_account *vmu;
03040 #define HMSU_OUTPUT_FORMAT "%-23s %-15s %-15s %-10s %-10s %-50s\n"
03041    int count = 0;
03042 
03043    switch (cmd) {
03044    case CLI_INIT:
03045       e->command = "minivm list accounts";
03046       e->usage =
03047          "Usage: minivm list accounts\n"
03048          "       Lists all mailboxes currently set up\n";
03049       return NULL;
03050    case CLI_GENERATE:
03051       return complete_minivm_show_users(a->line, a->word, a->pos, a->n);
03052    }
03053 
03054    if ((a->argc < 3) || (a->argc > 5) || (a->argc == 4))
03055       return CLI_SHOWUSAGE;
03056    if ((a->argc == 5) && strcmp(a->argv[3],"for"))
03057       return CLI_SHOWUSAGE;
03058 
03059    AST_LIST_LOCK(&minivm_accounts);
03060    if (AST_LIST_EMPTY(&minivm_accounts)) {
03061       ast_cli(a->fd, "There are no voicemail users currently defined\n");
03062       AST_LIST_UNLOCK(&minivm_accounts);
03063       return CLI_FAILURE;
03064    }
03065    ast_cli(a->fd, HMSU_OUTPUT_FORMAT, "User", "E-Template", "P-template", "Zone", "Format", "Full name");
03066    ast_cli(a->fd, HMSU_OUTPUT_FORMAT, "----", "----------", "----------", "----", "------", "---------");
03067    AST_LIST_TRAVERSE(&minivm_accounts, vmu, list) {
03068       char tmp[256] = "";
03069       if ((a->argc == 3) || ((a->argc == 5) && !strcmp(a->argv[4], vmu->domain))) {
03070          count++;
03071          snprintf(tmp, sizeof(tmp), "%s@%s", vmu->username, vmu->domain);
03072          ast_cli(a->fd, HMSU_OUTPUT_FORMAT, tmp, vmu->etemplate ? vmu->etemplate : "-", 
03073             vmu->ptemplate ? vmu->ptemplate : "-",
03074             vmu->zonetag ? vmu->zonetag : "-", 
03075             vmu->attachfmt ? vmu->attachfmt : "-",
03076             vmu->fullname);
03077       }
03078    }
03079    AST_LIST_UNLOCK(&minivm_accounts);
03080    ast_cli(a->fd, "\n * Total: %d minivoicemail accounts\n", count);
03081    return CLI_SUCCESS;
03082 }
03083 
03084 /*! \brief Show a list of voicemail zones in the CLI */
03085 static char *handle_minivm_show_zones(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
03086 {
03087    struct minivm_zone *zone;
03088 #define HMSZ_OUTPUT_FORMAT "%-15s %-20s %-45s\n"
03089    char *res = CLI_SUCCESS;
03090 
03091    switch (cmd) {
03092    case CLI_INIT:
03093       e->command = "minivm list zones";
03094       e->usage =
03095          "Usage: minivm list zones\n"
03096          "       Lists zone message formats\n";
03097       return NULL;
03098    case CLI_GENERATE:
03099       return NULL;
03100    }
03101 
03102    if (a->argc != e->args)
03103       return CLI_SHOWUSAGE;
03104 
03105    AST_LIST_LOCK(&minivm_zones);
03106    if (!AST_LIST_EMPTY(&minivm_zones)) {
03107       ast_cli(a->fd, HMSZ_OUTPUT_FORMAT, "Zone", "Timezone", "Message Format");
03108       ast_cli(a->fd, HMSZ_OUTPUT_FORMAT, "----", "--------", "--------------");
03109       AST_LIST_TRAVERSE(&minivm_zones, zone, list) {
03110          ast_cli(a->fd, HMSZ_OUTPUT_FORMAT, zone->name, zone->timezone, zone->msg_format);
03111       }
03112    } else {
03113       ast_cli(a->fd, "There are no voicemail zones currently defined\n");
03114       res = CLI_FAILURE;
03115    }
03116    AST_LIST_UNLOCK(&minivm_zones);
03117 
03118    return res;
03119 }
03120 
03121 /*! \brief CLI Show settings */
03122 static char *handle_minivm_show_settings(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
03123 {
03124    switch (cmd) {
03125    case CLI_INIT:
03126       e->command = "minivm show settings";
03127       e->usage =
03128          "Usage: minivm show settings\n"
03129          "       Display Mini-Voicemail general settings\n";
03130       return NULL;
03131    case CLI_GENERATE:
03132       return NULL;
03133    }
03134 
03135    ast_cli(a->fd, "* Mini-Voicemail general settings\n");
03136    ast_cli(a->fd, "  -------------------------------\n");
03137    ast_cli(a->fd, "\n");
03138    ast_cli(a->fd, "  Mail command (shell):               %s\n", global_mailcmd);
03139    ast_cli(a->fd, "  Max silence:                        %d\n", global_maxsilence);
03140    ast_cli(a->fd, "  Silence threshold:                  %d\n", global_silencethreshold);
03141    ast_cli(a->fd, "  Max message length (secs):          %d\n", global_vmmaxmessage);
03142    ast_cli(a->fd, "  Min message length (secs):          %d\n", global_vmminmessage);
03143    ast_cli(a->fd, "  Default format:                     %s\n", default_vmformat);
03144    ast_cli(a->fd, "  Extern notify (shell):              %s\n", global_externnotify);
03145    ast_cli(a->fd, "  Logfile:                            %s\n", global_logfile[0] ? global_logfile : "<disabled>");
03146    ast_cli(a->fd, "  Operator exit:                      %s\n", ast_test_flag(&globalflags, MVM_OPERATOR) ? "Yes" : "No");
03147    ast_cli(a->fd, "  Message review:                     %s\n", ast_test_flag(&globalflags, MVM_REVIEW) ? "Yes" : "No");
03148 
03149    ast_cli(a->fd, "\n");
03150    return CLI_SUCCESS;
03151 }
03152 
03153 /*! \brief Show stats */
03154 static char *handle_minivm_show_stats(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
03155 {
03156    struct ast_tm timebuf;
03157    char buf[BUFSIZ];
03158 
03159    switch (cmd) {
03160    
03161    case CLI_INIT:
03162       e->command = "minivm show stats";
03163       e->usage =
03164          "Usage: minivm show stats\n"
03165          "       Display Mini-Voicemail counters\n";
03166       return NULL;
03167    case CLI_GENERATE:
03168       return NULL;
03169    }
03170 
03171    ast_cli(a->fd, "* Mini-Voicemail statistics\n");
03172    ast_cli(a->fd, "  -------------------------\n");
03173    ast_cli(a->fd, "\n");
03174    ast_cli(a->fd, "  Voicemail accounts:                  %5d\n", global_stats.voicemailaccounts);
03175    ast_cli(a->fd, "  Templates:                           %5d\n", global_stats.templates);
03176    ast_cli(a->fd, "  Timezones:                           %5d\n", global_stats.timezones);
03177    if (global_stats.receivedmessages == 0) {
03178       ast_cli(a->fd, "  Received messages since last reset:  <none>\n");
03179    } else {
03180       ast_cli(a->fd, "  Received messages since last reset:  %d\n", global_stats.receivedmessages);
03181       ast_localtime(&global_stats.lastreceived, &timebuf, NULL);
03182       ast_strftime(buf, sizeof(buf), "%a %b %e %r %Z %Y", &timebuf);
03183       ast_cli(a->fd, "  Last received voicemail:             %s\n", buf);
03184    }
03185    ast_localtime(&global_stats.reset, &timebuf, NULL);
03186    ast_strftime(buf, sizeof(buf), "%a %b %e %r %Z %Y", &timebuf);
03187    ast_cli(a->fd, "  Last reset:                          %s\n", buf);
03188 
03189    ast_cli(a->fd, "\n");
03190    return CLI_SUCCESS;
03191 }
03192 
03193 /*! \brief  ${MINIVMACCOUNT()} Dialplan function - reads account data */
03194 static int minivm_account_func_read(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len)
03195 {
03196    struct minivm_account *vmu;
03197    char *username, *domain, *colname;
03198 
03199    username = ast_strdupa(data);
03200 
03201    if ((colname = strchr(username, ':'))) {
03202       *colname = '\0';
03203       colname++;
03204    } else {
03205       colname = "path";
03206    }
03207    if ((domain = strchr(username, '@'))) {
03208       *domain = '\0';
03209       domain++;
03210    }
03211    if (ast_strlen_zero(username) || ast_strlen_zero(domain)) {
03212       ast_log(LOG_ERROR, "This function needs a username and a domain: username@domain\n");
03213       return 0;
03214    }
03215 
03216    if (!(vmu = find_account(domain, username, TRUE)))
03217       return 0;
03218 
03219    if (!strcasecmp(colname, "hasaccount")) {
03220       ast_copy_string(buf, (ast_test_flag(vmu, MVM_ALLOCED) ? "0" : "1"), len);
03221    } else  if (!strcasecmp(colname, "fullname")) { 
03222       ast_copy_string(buf, vmu->fullname, len);
03223    } else  if (!strcasecmp(colname, "email")) { 
03224       if (!ast_strlen_zero(vmu->email))
03225          ast_copy_string(buf, vmu->email, len);
03226       else
03227          snprintf(buf, len, "%s@%s", vmu->username, vmu->domain);
03228    } else  if (!strcasecmp(colname, "pager")) { 
03229       ast_copy_string(buf, vmu->pager, len);
03230    } else  if (!strcasecmp(colname, "etemplate")) { 
03231       if (!ast_strlen_zero(vmu->etemplate))
03232          ast_copy_string(buf, vmu->etemplate, len);
03233       else
03234          ast_copy_string(buf, "email-default", len);
03235    } else  if (!strcasecmp(colname, "language")) { 
03236       ast_copy_string(buf, vmu->language, len);
03237    } else  if (!strcasecmp(colname, "timezone")) { 
03238       ast_copy_string(buf, vmu->zonetag, len);
03239    } else  if (!strcasecmp(colname, "ptemplate")) { 
03240       if (!ast_strlen_zero(vmu->ptemplate))
03241          ast_copy_string(buf, vmu->ptemplate, len);
03242       else
03243          ast_copy_string(buf, "email-default", len);
03244    } else  if (!strcasecmp(colname, "accountcode")) {
03245       ast_copy_string(buf, vmu->accountcode, len);
03246    } else  if (!strcasecmp(colname, "pincode")) {
03247       ast_copy_string(buf, vmu->pincode, len);
03248    } else  if (!strcasecmp(colname, "path")) {
03249       check_dirpath(buf, len, vmu->domain, vmu->username, NULL);
03250    } else { /* Look in channel variables */
03251       struct ast_variable *var;
03252 
03253       for (var = vmu->chanvars ; var ; var = var->next)
03254          if (!strcmp(var->name, colname)) {
03255             ast_copy_string(buf, var->value, len);
03256             break;
03257          }
03258    }
03259 
03260    if(ast_test_flag(vmu, MVM_ALLOCED))
03261       free_user(vmu);
03262 
03263    return 0;
03264 }
03265 
03266 /*! \brief lock directory
03267 
03268    only return failure if ast_lock_path returns 'timeout',
03269    not if the path does not exist or any other reason
03270 */
03271 static int vm_lock_path(const char *path)
03272 {
03273    switch (ast_lock_path(path)) {
03274    case AST_LOCK_TIMEOUT:
03275       return -1;
03276    default:
03277       return 0;
03278    }
03279 }
03280 
03281 /*! \brief Access counter file, lock directory, read and possibly write it again changed 
03282    \param directory  Directory to crate file in
03283    \param countername   filename 
03284    \param value      If set to zero, we only read the variable
03285    \param operand    0 to read, 1 to set new value, 2 to change 
03286    \return -1 on error, otherwise counter value
03287 */
03288 static int access_counter_file(char *directory, char *countername, int value, int operand)
03289 {
03290    char filename[BUFSIZ];
03291    char readbuf[BUFSIZ];
03292    FILE *counterfile;
03293    int old = 0, counter = 0;
03294 
03295    /* Lock directory */
03296    if (vm_lock_path(directory)) {
03297       return -1;  /* Could not lock directory */
03298    }
03299    snprintf(filename, sizeof(filename), "%s/%s.counter", directory, countername);
03300    if (operand != 1) {
03301       counterfile = fopen(filename, "r");
03302       if (counterfile) {
03303          if(fgets(readbuf, sizeof(readbuf), counterfile)) {
03304             ast_debug(3, "Read this string from counter file: %s\n", readbuf);
03305             old = counter = atoi(readbuf);
03306          }
03307          fclose(counterfile);
03308       }
03309    }
03310    switch (operand) {
03311    case 0:  /* Read only */
03312       ast_unlock_path(directory);
03313       ast_debug(2, "MINIVM Counter %s/%s: Value %d\n", directory, countername, counter);
03314       return counter;
03315       break;
03316    case 1: /* Set new value */
03317       counter = value;
03318       break;
03319    case 2: /* Change value */
03320       counter += value;
03321       if (counter < 0)  /* Don't allow counters to fall below zero */
03322          counter = 0;
03323       break;
03324    }
03325    
03326    /* Now, write the new value to the file */
03327    counterfile = fopen(filename, "w");
03328    if (!counterfile) {
03329       ast_log(LOG_ERROR, "Could not open counter file for writing : %s - %s\n", filename, strerror(errno));
03330       ast_unlock_path(directory);
03331       return -1;  /* Could not open file for writing */
03332    }
03333    fprintf(counterfile, "%d\n\n", counter);
03334    fclose(counterfile);
03335    ast_unlock_path(directory);
03336    ast_debug(2, "MINIVM Counter %s/%s: Old value %d New value %d\n", directory, countername, old, counter);
03337    return counter;
03338 }
03339 
03340 /*! \brief  ${MINIVMCOUNTER()} Dialplan function - read counters */
03341 static int minivm_counter_func_read(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len)
03342 {
03343    char *username, *domain, *countername;
03344    char userpath[BUFSIZ];
03345    int res;
03346 
03347    *buf = '\0';
03348 
03349    username = ast_strdupa(data);
03350 
03351    if ((countername = strchr(username, ':'))) {
03352       *countername = '\0';
03353       countername++;
03354    } 
03355 
03356    if ((domain = strchr(username, '@'))) {
03357       *domain = '\0';
03358       domain++;
03359    }
03360 
03361    /* If we have neither username nor domain now, let's give up */
03362    if (ast_strlen_zero(username) && ast_strlen_zero(domain)) {
03363       ast_log(LOG_ERROR, "No account given\n");
03364       return -1;
03365    }
03366 
03367    if (ast_strlen_zero(countername)) {
03368       ast_log(LOG_ERROR, "This function needs two arguments: Account:countername\n");
03369       return -1;
03370    }
03371 
03372    /* We only have a domain, no username */
03373    if (!ast_strlen_zero(username) && ast_strlen_zero(domain)) {
03374       domain = username;
03375       username = NULL;
03376    }
03377 
03378    /* If we can't find account or if the account is temporary, return. */
03379    if (!ast_strlen_zero(username) && !find_account(domain, username, FALSE)) {
03380       ast_log(LOG_ERROR, "Minivm account does not exist: %s@%s\n", username, domain);
03381       return 0;
03382    }
03383 
03384    create_dirpath(userpath, sizeof(userpath), domain, username, NULL);
03385 
03386    /* We have the path, now read the counter file */
03387    res = access_counter_file(userpath, countername, 0, 0);
03388    if (res >= 0)
03389       snprintf(buf, len, "%d", res);
03390    return 0;
03391 }
03392 
03393 /*! \brief  ${MINIVMCOUNTER()} Dialplan function - changes counter data */
03394 static int minivm_counter_func_write(struct ast_channel *chan, const char *cmd, char *data, const char *value)
03395 {
03396    char *username, *domain, *countername, *operand;
03397    char userpath[BUFSIZ];
03398    int change = 0;
03399    int operation = 0;
03400 
03401    if(!value)
03402       return -1;
03403    change = atoi(value);
03404 
03405    username = ast_strdupa(data);
03406 
03407    if ((countername = strchr(username, ':'))) {
03408       *countername = '\0';
03409       countername++;
03410    } 
03411    if ((operand = strchr(countername, ':'))) {
03412       *operand = '\0';
03413       operand++;
03414    } 
03415 
03416    if ((domain = strchr(username, '@'))) {
03417       *domain = '\0';
03418       domain++;
03419    }
03420 
03421    /* If we have neither username nor domain now, let's give up */
03422    if (ast_strlen_zero(username) && ast_strlen_zero(domain)) {
03423       ast_log(LOG_ERROR, "No account given\n");
03424       return -1;
03425    }
03426 
03427    /* We only have a domain, no username */
03428    if (!ast_strlen_zero(username) && ast_strlen_zero(domain)) {
03429       domain = username;
03430       username = NULL;
03431    }
03432 
03433    if (ast_strlen_zero(operand) || ast_strlen_zero(countername)) {
03434       ast_log(LOG_ERROR, "Writing to this function requires three arguments: Account:countername:operand\n");
03435       return -1;
03436    }
03437 
03438    /* If we can't find account or if the account is temporary, return. */
03439    if (!ast_strlen_zero(username) && !find_account(domain, username, FALSE)) {
03440       ast_log(LOG_ERROR, "Minivm account does not exist: %s@%s\n", username, domain);
03441       return 0;
03442    }
03443 
03444    create_dirpath(userpath, sizeof(userpath), domain, username, NULL);
03445    /* Now, find out our operator */
03446    if (*operand == 'i') /* Increment */
03447       operation = 2;
03448    else if (*operand == 'd') {
03449       change = change * -1;
03450       operation = 2;
03451    } else if (*operand == 's')
03452       operation = 1;
03453    else {
03454       ast_log(LOG_ERROR, "Unknown operator: %s\n", operand);
03455       return -1;
03456    }
03457 
03458    /* We have the path, now read the counter file */
03459    access_counter_file(userpath, countername, change, operation);
03460    return 0;
03461 }
03462 
03463 
03464 /*! \brief CLI commands for Mini-voicemail */
03465 static struct ast_cli_entry cli_minivm[] = {
03466    AST_CLI_DEFINE(handle_minivm_show_users, "List defined mini-voicemail boxes"),
03467    AST_CLI_DEFINE(handle_minivm_show_zones, "List zone message formats"),
03468    AST_CLI_DEFINE(handle_minivm_list_templates, "List message templates"), 
03469    AST_CLI_DEFINE(handle_minivm_reload, "Reload Mini-voicemail configuration"),
03470    AST_CLI_DEFINE(handle_minivm_show_stats, "Show some mini-voicemail statistics"),
03471    AST_CLI_DEFINE(handle_minivm_show_settings, "Show mini-voicemail general settings"),
03472 };
03473 
03474 static struct ast_custom_function minivm_counter_function = {
03475    .name = "MINIVMCOUNTER",
03476    .read = minivm_counter_func_read,
03477    .write = minivm_counter_func_write,
03478 };
03479 
03480 static struct ast_custom_function minivm_account_function = {
03481    .name = "MINIVMACCOUNT",
03482    .read = minivm_account_func_read,
03483 };
03484 
03485 /*! \brief Load mini voicemail module */
03486 static int load_module(void)
03487 {
03488    int res;
03489 
03490    res = ast_register_application_xml(app_minivm_record, minivm_record_exec);
03491    res = ast_register_application_xml(app_minivm_greet, minivm_greet_exec);
03492    res = ast_register_application_xml(app_minivm_notify, minivm_notify_exec);
03493    res = ast_register_application_xml(app_minivm_delete, minivm_delete_exec);
03494    res = ast_register_application_xml(app_minivm_accmess, minivm_accmess_exec);
03495    res = ast_register_application_xml(app_minivm_mwi, minivm_mwi_exec);
03496 
03497    ast_custom_function_register(&minivm_account_function);
03498    ast_custom_function_register(&minivm_counter_function);
03499    if (res)
03500       return(res);
03501 
03502    if ((res = load_config(0)))
03503       return(res);
03504 
03505    ast_cli_register_multiple(cli_minivm, ARRAY_LEN(cli_minivm));
03506 
03507    /* compute the location of the voicemail spool directory */
03508    snprintf(MVM_SPOOL_DIR, sizeof(MVM_SPOOL_DIR), "%s/voicemail/", ast_config_AST_SPOOL_DIR);
03509 
03510    return res;
03511 }
03512 
03513 /*! \brief Reload mini voicemail module */
03514 static int reload(void)
03515 {
03516    return(load_config(1));
03517 }
03518 
03519 /*! \brief Reload cofiguration */
03520 static char *handle_minivm_reload(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
03521 {
03522    
03523    switch (cmd) {
03524    case CLI_INIT:
03525       e->command = "minivm reload";
03526       e->usage =
03527          "Usage: minivm reload\n"
03528          "       Reload mini-voicemail configuration and reset statistics\n";
03529       return NULL;
03530    case CLI_GENERATE:
03531       return NULL;
03532    }
03533    
03534    reload();
03535    ast_cli(a->fd, "\n-- Mini voicemail re-configured \n");
03536    return CLI_SUCCESS;
03537 }
03538 
03539 /*! \brief Unload mini voicemail module */
03540 static int unload_module(void)
03541 {
03542    int res;
03543    
03544    res = ast_unregister_application(app_minivm_record);
03545    res |= ast_unregister_application(app_minivm_greet);
03546    res |= ast_unregister_application(app_minivm_notify);
03547    res |= ast_unregister_application(app_minivm_delete);
03548    res |= ast_unregister_application(app_minivm_accmess);
03549    res |= ast_unregister_application(app_minivm_mwi);
03550 
03551    ast_cli_unregister_multiple(cli_minivm, ARRAY_LEN(cli_minivm));
03552    ast_custom_function_unregister(&minivm_account_function);
03553    ast_custom_function_unregister(&minivm_counter_function);
03554 
03555    message_destroy_list();    /* Destroy list of voicemail message templates */
03556    timezone_destroy_list();   /* Destroy list of timezones */
03557    vmaccounts_destroy_list(); /* Destroy list of voicemail accounts */
03558 
03559    return res;
03560 }
03561 
03562 
03563 AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_DEFAULT, "Mini VoiceMail (A minimal Voicemail e-mail System)",
03564       .support_level = AST_MODULE_SUPPORT_EXTENDED,
03565       .load = load_module,
03566       .unload = unload_module,
03567       .reload = reload,
03568       );

Generated on Thu Apr 16 06:27:10 2015 for Asterisk - The Open Source Telephony Project by  doxygen 1.5.6