Sat Apr 26 2014 22:01:27

Asterisk developer's documentation


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