Sun Oct 16 2011 08:41:28

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: 328209 $")
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    *end = '\0';
01163 
01164    ast_str_reset(*end);
01165    ast_str_set(&tmp, -1, "=?%s?Q?", charset);
01166    for (; *start; start++) {
01167       int need_encoding = 0;
01168       if (*start < 33 || *start > 126 || strchr("()<>@,:;/\"[]?.=_", *start)) {
01169          need_encoding = 1;
01170       }
01171       if ((first_section && need_encoding && preamble + ast_str_strlen(tmp) > 70) ||
01172          (first_section && !need_encoding && preamble + ast_str_strlen(tmp) > 72) ||
01173          (!first_section && need_encoding && ast_str_strlen(tmp) > 70) ||
01174          (!first_section && !need_encoding && ast_str_strlen(tmp) > 72)) {
01175          /* Start new line */
01176          ast_str_append(end, maxlen, "%s%s?=", first_section ? "" : " ", ast_str_buffer(tmp));
01177          ast_str_set(&tmp, -1, "=?%s?Q?", charset);
01178          first_section = 0;
01179       }
01180       if (need_encoding && *start == ' ') {
01181          ast_str_append(&tmp, -1, "_");
01182       } else if (need_encoding) {
01183          ast_str_append(&tmp, -1, "=%hhX", *start);
01184       } else {
01185          ast_str_append(&tmp, -1, "%c", *start);
01186       }
01187    }
01188    ast_str_append(end, maxlen, "%s%s?=%s", first_section ? "" : " ", ast_str_buffer(tmp), ast_str_strlen(tmp) + postamble > 74 ? " " : "");
01189    return ast_str_buffer(*end);
01190 }
01191 
01192 /*!\internal
01193  * \brief Wraps a character sequence in double quotes, escaping occurences of quotes within the string.
01194  * \param from The string to work with.
01195  * \param buf The destination buffer to write the modified quoted string.
01196  * \param maxlen Always zero.  \see ast_str
01197  *
01198  * \return The destination string with quotes wrapped on it (the to field).
01199  */
01200 static const char *ast_str_quote(struct ast_str **buf, ssize_t maxlen, const char *from)
01201 {
01202    const char *ptr;
01203 
01204    /* We're only ever passing 0 to maxlen, so short output isn't possible */
01205    ast_str_set(buf, maxlen, "\"");
01206    for (ptr = from; *ptr; ptr++) {
01207       if (*ptr == '"' || *ptr == '\\') {
01208          ast_str_append(buf, maxlen, "\\%c", *ptr);
01209       } else {
01210          ast_str_append(buf, maxlen, "%c", *ptr);
01211       }
01212    }
01213    ast_str_append(buf, maxlen, "\"");
01214 
01215    return ast_str_buffer(*buf);
01216 }
01217 
01218 /*!\internal
01219  * \brief Send voicemail with audio file as an attachment */
01220 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)
01221 {
01222    FILE *p = NULL;
01223    int pfd;
01224    char email[256] = "";
01225    char who[256] = "";
01226    char date[256];
01227    char bound[256];
01228    char fname[PATH_MAX];
01229    char dur[PATH_MAX];
01230    char tmp[80] = "/tmp/astmail-XXXXXX";
01231    char tmp2[PATH_MAX];
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       return -1;  
01259    }
01260 
01261    ast_debug(3, "Sending mail to %s@%s - Using template %s\n", vmu->username, vmu->domain, template->name);
01262 
01263    if (!strcmp(format, "wav49"))
01264       format = "WAV";
01265 
01266 
01267    /* If we have a gain option, process it now with sox */
01268    if (type == MVM_MESSAGE_EMAIL && (vmu->volgain < -.001 || vmu->volgain > .001) ) {
01269       char newtmp[PATH_MAX];
01270       char tmpcmd[PATH_MAX];
01271       int tmpfd;
01272 
01273       /**
01274        * XXX
01275        * /bug tmpfd is a leaked fd.  The file is also never unlinked.
01276        *      See app_voicemail.c for how the code works there that
01277        *      doesn't have this bug.
01278        */
01279 
01280       ast_copy_string(newtmp, "/tmp/XXXXXX", sizeof(newtmp));
01281       ast_debug(3, "newtmp: %s\n", newtmp);
01282       tmpfd = mkstemp(newtmp);
01283       if (tmpfd > -1) {
01284          snprintf(tmpcmd, sizeof(tmpcmd), "sox -v %.4f %s.%s %s.%s", vmu->volgain, filename, format, newtmp, format);
01285          ast_safe_system(tmpcmd);
01286          finalfilename = newtmp;
01287          ast_debug(3, "VOLGAIN: Stored at: %s.%s - Level: %.4f - Mailbox: %s\n", filename, format, vmu->volgain, vmu->username);
01288       }
01289    } else {
01290       finalfilename = ast_strdupa(filename);
01291    }
01292 
01293    /* Create file name */
01294    snprintf(fname, sizeof(fname), "%s.%s", finalfilename, format);
01295 
01296    if (template->attachment)
01297       ast_debug(1, "Attaching file '%s', format '%s', uservm is '%d'\n", finalfilename, format, attach_user_voicemail);
01298 
01299    /* Make a temporary file instead of piping directly to sendmail, in case the mail
01300       command hangs */
01301    pfd = mkstemp(tmp);
01302    if (pfd > -1) {
01303       p = fdopen(pfd, "w");
01304       if (!p) {
01305          close(pfd);
01306          pfd = -1;
01307       }
01308       ast_debug(1, "Opening temp file for e-mail: %s\n", tmp);
01309    }
01310    if (!p) {
01311       ast_log(LOG_WARNING, "Unable to open temporary file '%s'\n", tmp);
01312       return -1;
01313    }
01314    /* Allocate channel used for chanvar substitution */
01315    ast = ast_dummy_channel_alloc();
01316 
01317    snprintf(dur, sizeof(dur), "%d:%02d", duration / 60, duration % 60);
01318 
01319    /* Does this user have a timezone specified? */
01320    if (!ast_strlen_zero(vmu->zonetag)) {
01321       /* Find the zone in the list */
01322       struct minivm_zone *z;
01323       AST_LIST_LOCK(&minivm_zones);
01324       AST_LIST_TRAVERSE(&minivm_zones, z, list) {
01325          if (strcmp(z->name, vmu->zonetag)) 
01326             continue;
01327          the_zone = z;
01328       }
01329       AST_LIST_UNLOCK(&minivm_zones);
01330    }
01331 
01332    now = ast_tvnow();
01333    ast_localtime(&now, &tm, the_zone ? the_zone->timezone : NULL);
01334    ast_strftime(date, sizeof(date), "%a, %d %b %Y %H:%M:%S %z", &tm);
01335 
01336    /* Start printing the email to the temporary file */
01337    fprintf(p, "Date: %s\n", date);
01338 
01339    /* Set date format for voicemail mail */
01340    ast_strftime(date, sizeof(date), template->dateformat, &tm);
01341 
01342 
01343    /* Populate channel with channel variables for substitution */
01344    prep_email_sub_vars(ast, vmu, cidnum, cidname, dur, date, counter);
01345 
01346    /* Find email address to use */
01347    /* If there's a server e-mail adress in the account, user that, othterwise template */
01348    fromemail = ast_strlen_zero(vmu->serveremail) ?  template->serveremail : vmu->serveremail;
01349 
01350    /* Find name to user for server e-mail */
01351    fromaddress = ast_strlen_zero(template->fromaddress) ? "" : template->fromaddress;
01352 
01353    /* If needed, add hostname as domain */
01354    if (ast_strlen_zero(fromemail))
01355       fromemail = "asterisk";
01356 
01357    if (strchr(fromemail, '@'))
01358       ast_copy_string(who, fromemail, sizeof(who));
01359    else  {
01360       char host[MAXHOSTNAMELEN];
01361       gethostname(host, sizeof(host)-1);
01362       snprintf(who, sizeof(who), "%s@%s", fromemail, host);
01363    }
01364 
01365    if (ast_strlen_zero(fromaddress)) {
01366       fprintf(p, "From: Asterisk PBX <%s>\n", who);
01367    } else {
01368       ast_debug(4, "Fromaddress template: %s\n", fromaddress);
01369       ast_str_substitute_variables(&str1, 0, ast, fromaddress);
01370       if (check_mime(ast_str_buffer(str1))) {
01371          int first_line = 1;
01372          char *ptr;
01373          ast_str_encode_mime(&str2, 0, template->charset, ast_str_buffer(str1), strlen("From: "), strlen(who) + 3);
01374          while ((ptr = strchr(ast_str_buffer(str2), ' '))) {
01375             *ptr = '\0';
01376             fprintf(p, "%s %s\n", first_line ? "From:" : "", ast_str_buffer(str2));
01377             first_line = 0;
01378             /* Substring is smaller, so this will never grow */
01379             ast_str_set(&str2, 0, "%s", ptr + 1);
01380          }
01381          fprintf(p, "%s %s <%s>\n", first_line ? "From:" : "", ast_str_buffer(str2), who);
01382       } else {
01383          fprintf(p, "From: %s <%s>\n", ast_str_quote(&str2, 0, ast_str_buffer(str1)), who);
01384       }
01385    } 
01386 
01387    fprintf(p, "Message-ID: <Asterisk-%d-%s-%d-%s>\n", (unsigned int)ast_random(), vmu->username, (int)getpid(), who);
01388 
01389    if (ast_strlen_zero(vmu->email)) {
01390       snprintf(email, sizeof(email), "%s@%s", vmu->username, vmu->domain);
01391    } else {
01392       ast_copy_string(email, vmu->email, sizeof(email));
01393    }
01394 
01395    if (check_mime(vmu->fullname)) {
01396       int first_line = 1;
01397       char *ptr;
01398       ast_str_encode_mime(&str2, 0, template->charset, vmu->fullname, strlen("To: "), strlen(email) + 3);
01399       while ((ptr = strchr(ast_str_buffer(str2), ' '))) {
01400          *ptr = '\0';
01401          fprintf(p, "%s %s\n", first_line ? "To:" : "", ast_str_buffer(str2));
01402          first_line = 0;
01403          /* Substring is smaller, so this will never grow */
01404          ast_str_set(&str2, 0, "%s", ptr + 1);
01405       }
01406       fprintf(p, "%s %s <%s>\n", first_line ? "To:" : "", ast_str_buffer(str2), email);
01407    } else {
01408       fprintf(p, "To: %s <%s>\n", ast_str_quote(&str2, 0, vmu->fullname), email);
01409    }
01410 
01411    if (!ast_strlen_zero(template->subject)) {
01412       ast_str_substitute_variables(&str1, 0, ast, template->subject);
01413       if (check_mime(ast_str_buffer(str1))) {
01414          int first_line = 1;
01415          char *ptr;
01416          ast_str_encode_mime(&str2, 0, template->charset, ast_str_buffer(str1), strlen("Subject: "), 0);
01417          while ((ptr = strchr(ast_str_buffer(str2), ' '))) {
01418             *ptr = '\0';
01419             fprintf(p, "%s %s\n", first_line ? "Subject:" : "", ast_str_buffer(str2));
01420             first_line = 0;
01421             /* Substring is smaller, so this will never grow */
01422             ast_str_set(&str2, 0, "%s", ptr + 1);
01423          }
01424          fprintf(p, "%s %s\n", first_line ? "Subject:" : "", ast_str_buffer(str2));
01425       } else {
01426          fprintf(p, "Subject: %s\n", ast_str_buffer(str1));
01427       }
01428    } else {
01429       fprintf(p, "Subject: New message in mailbox %s@%s\n", vmu->username, vmu->domain);
01430       ast_debug(1, "Using default subject for this email \n");
01431    }
01432 
01433    if (option_debug > 2)
01434       fprintf(p, "X-Asterisk-debug: template %s user account %s@%s\n", template->name, vmu->username, vmu->domain);
01435    fprintf(p, "MIME-Version: 1.0\n");
01436 
01437    /* Something unique. */
01438    snprintf(bound, sizeof(bound), "voicemail_%s%d%d", vmu->username, (int)getpid(), (unsigned int)ast_random());
01439 
01440    fprintf(p, "Content-Type: multipart/mixed; boundary=\"%s\"\n\n\n", bound);
01441 
01442    fprintf(p, "--%s\n", bound);
01443    fprintf(p, "Content-Type: text/plain; charset=%s\nContent-Transfer-Encoding: 8bit\n\n", template->charset);
01444    if (!ast_strlen_zero(template->body)) {
01445       ast_str_substitute_variables(&str1, 0, ast, template->body);
01446       ast_debug(3, "Message now: %s\n-----\n", ast_str_buffer(str1));
01447       fprintf(p, "%s\n", ast_str_buffer(str1));
01448    } else {
01449       fprintf(p, "Dear %s:\n\n\tJust wanted to let you know you were just left a %s long message \n"
01450          "in mailbox %s from %s, on %s so you might\n"
01451          "want to check it when you get a chance.  Thanks!\n\n\t\t\t\t--Asterisk\n\n", vmu->fullname, 
01452          dur,  vmu->username, (cidname ? cidname : (cidnum ? cidnum : "an unknown caller")), date);
01453       ast_debug(3, "Using default message body (no template)\n-----\n");
01454    }
01455    /* Eww. We want formats to tell us their own MIME type */
01456    if (template->attachment) {
01457       char *ctype = "audio/x-";
01458       ast_debug(3, "Attaching file to message: %s\n", fname);
01459       if (!strcasecmp(format, "ogg"))
01460          ctype = "application/";
01461 
01462       fprintf(p, "--%s\n", bound);
01463       fprintf(p, "Content-Type: %s%s; name=\"voicemailmsg.%s\"\n", ctype, format, format);
01464       fprintf(p, "Content-Transfer-Encoding: base64\n");
01465       fprintf(p, "Content-Description: Voicemail sound attachment.\n");
01466       fprintf(p, "Content-Disposition: attachment; filename=\"voicemail%s.%s\"\n\n", counter ? counter : "", format);
01467 
01468       base_encode(fname, p);
01469       fprintf(p, "\n\n--%s--\n.\n", bound);
01470    }
01471    fclose(p);
01472    snprintf(tmp2, sizeof(tmp2), "( %s < %s ; rm -f %s ) &", global_mailcmd, tmp, tmp);
01473    ast_safe_system(tmp2);
01474    ast_debug(1, "Sent message to %s with command '%s' - %s\n", vmu->email, global_mailcmd, template->attachment ? "(media attachment)" : "");
01475    ast_debug(3, "Actual command used: %s\n", tmp2);
01476    if (ast)
01477       ast = ast_channel_release(ast);
01478    ast_free(str1);
01479    ast_free(str2);
01480    return 0;
01481 }
01482 
01483 /*!\internal
01484  * \brief Create directory based on components */
01485 static int make_dir(char *dest, int len, const char *domain, const char *username, const char *folder)
01486 {
01487    return snprintf(dest, len, "%s%s/%s%s%s", MVM_SPOOL_DIR, domain, username, ast_strlen_zero(folder) ? "" : "/", folder ? folder : "");
01488 }
01489 
01490 /*!\internal
01491  * \brief Checks if directory exists. Does not create directory, but builds string in dest
01492  * \param dest    String. base directory.
01493  * \param len    Int. Length base directory string.
01494  * \param domain String. Ignored if is null or empty string.
01495  * \param username String. Ignored if is null or empty string. 
01496  * \param folder  String. Ignored if is null or empty string.
01497  * \return 0 on failure, 1 on success.
01498  */
01499 static int check_dirpath(char *dest, int len, char *domain, char *username, char *folder)
01500 {
01501    struct stat filestat;
01502    make_dir(dest, len, domain, username, folder ? folder : "");
01503    if (stat(dest, &filestat)== -1)
01504       return FALSE;
01505    else
01506       return TRUE;
01507 }
01508 
01509 /*!\internal
01510  * \brief basically mkdir -p $dest/$domain/$username/$folder
01511  * \param dest    String. base directory.
01512  * \param len     Length of directory string
01513  * \param domain  String. Ignored if is null or empty string.
01514  * \param folder  String. Ignored if is null or empty string.
01515  * \param username  String. Ignored if is null or empty string.
01516  * \return -1 on failure, 0 on success.
01517  */
01518 static int create_dirpath(char *dest, int len, char *domain, char *username, char *folder)
01519 {
01520    int res;
01521    make_dir(dest, len, domain, username, folder);
01522    if ((res = ast_mkdir(dest, 0777))) {
01523       ast_log(LOG_WARNING, "ast_mkdir '%s' failed: %s\n", dest, strerror(res));
01524       return -1;
01525    }
01526    ast_debug(2, "Creating directory for %s@%s folder %s : %s\n", username, domain, folder, dest);
01527    return 0;
01528 }
01529 
01530 
01531 /*!\internal
01532  * \brief Play intro message before recording voicemail
01533  */
01534 static int invent_message(struct ast_channel *chan, char *domain, char *username, int busy, char *ecodes)
01535 {
01536    int res;
01537    char fn[PATH_MAX];
01538 
01539    ast_debug(2, "Still preparing to play message ...\n");
01540 
01541    snprintf(fn, sizeof(fn), "%s%s/%s/greet", MVM_SPOOL_DIR, domain, username);
01542 
01543    if (ast_fileexists(fn, NULL, NULL) > 0) {
01544       res = ast_streamfile(chan, fn, chan->language);
01545       if (res) 
01546          return -1;
01547       res = ast_waitstream(chan, ecodes);
01548       if (res) 
01549          return res;
01550    } else {
01551       int numericusername = 1;
01552       char *i = username;
01553 
01554       ast_debug(2, "No personal prompts. Using default prompt set for language\n");
01555 
01556       while (*i)  {
01557          ast_debug(2, "Numeric? Checking %c\n", *i);
01558          if (!isdigit(*i)) {
01559             numericusername = FALSE;
01560             break;
01561          }
01562          i++;
01563       }
01564 
01565       if (numericusername) {
01566          if (ast_streamfile(chan, "vm-theperson", chan->language))
01567             return -1;
01568          if ((res = ast_waitstream(chan, ecodes)))
01569             return res;
01570 
01571          res = ast_say_digit_str(chan, username, ecodes, chan->language);
01572          if (res)
01573             return res;
01574       } else {
01575          if (ast_streamfile(chan, "vm-theextensionis", chan->language))
01576             return -1;
01577          if ((res = ast_waitstream(chan, ecodes)))
01578             return res;
01579       }
01580    }
01581 
01582    res = ast_streamfile(chan, busy ? "vm-isonphone" : "vm-isunavail", chan->language);
01583    if (res)
01584       return -1;
01585    res = ast_waitstream(chan, ecodes);
01586    return res;
01587 }
01588 
01589 /*!\internal
01590  * \brief Delete media files and attribute file */
01591 static int vm_delete(char *file)
01592 {
01593    int res;
01594 
01595    ast_debug(1, "Deleting voicemail file %s\n", file);
01596 
01597    res = unlink(file);  /* Remove the meta data file */
01598    res |=  ast_filedelete(file, NULL); /* remove the media file */
01599    return res;
01600 }
01601 
01602 
01603 /*!\internal
01604  * \brief Record voicemail message & let caller review or re-record it, or set options if applicable */
01605 static int play_record_review(struct ast_channel *chan, char *playfile, char *recordfile, int maxtime, char *fmt,
01606                int outsidecaller, struct minivm_account *vmu, int *duration, const char *unlockdir,
01607                signed char record_gain)
01608 {
01609    int cmd = 0;
01610    int max_attempts = 3;
01611    int attempts = 0;
01612    int recorded = 0;
01613    int message_exists = 0;
01614    signed char zero_gain = 0;
01615    char *acceptdtmf = "#";
01616    char *canceldtmf = "";
01617 
01618    /* Note that urgent and private are for flagging messages as such in the future */
01619 
01620    /* barf if no pointer passed to store duration in */
01621    if (duration == NULL) {
01622       ast_log(LOG_WARNING, "Error play_record_review called without duration pointer\n");
01623       return -1;
01624    }
01625 
01626    cmd = '3';   /* Want to start by recording */
01627 
01628    while ((cmd >= 0) && (cmd != 't')) {
01629       switch (cmd) {
01630       case '1':
01631          ast_verb(3, "Saving message as is\n");
01632          ast_stream_and_wait(chan, "vm-msgsaved", "");
01633          cmd = 't';
01634          break;
01635       case '2':
01636          /* Review */
01637          ast_verb(3, "Reviewing the message\n");
01638          ast_streamfile(chan, recordfile, chan->language);
01639          cmd = ast_waitstream(chan, AST_DIGIT_ANY);
01640          break;
01641       case '3':
01642          message_exists = 0;
01643          /* Record */
01644          if (recorded == 1) 
01645             ast_verb(3, "Re-recording the message\n");
01646          else
01647             ast_verb(3, "Recording the message\n");
01648          if (recorded && outsidecaller) 
01649             cmd = ast_play_and_wait(chan, "beep");
01650          recorded = 1;
01651          /* 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 */
01652          if (record_gain)
01653             ast_channel_setoption(chan, AST_OPTION_RXGAIN, &record_gain, sizeof(record_gain), 0);
01654          if (ast_test_flag(vmu, MVM_OPERATOR))
01655             canceldtmf = "0";
01656          cmd = ast_play_and_record_full(chan, playfile, recordfile, maxtime, fmt, duration, global_silencethreshold, global_maxsilence, unlockdir, acceptdtmf, canceldtmf);
01657          if (record_gain)
01658             ast_channel_setoption(chan, AST_OPTION_RXGAIN, &zero_gain, sizeof(zero_gain), 0);
01659          if (cmd == -1) /* User has hung up, no options to give */
01660             return cmd;
01661          if (cmd == '0')
01662             break;
01663          else if (cmd == '*')
01664             break;
01665          else {
01666             /* If all is well, a message exists */
01667             message_exists = 1;
01668             cmd = 0;
01669          }
01670          break;
01671       case '4':
01672       case '5':
01673       case '6':
01674       case '7':
01675       case '8':
01676       case '9':
01677       case '*':
01678       case '#':
01679          cmd = ast_play_and_wait(chan, "vm-sorry");
01680          break;
01681       case '0':
01682          if(!ast_test_flag(vmu, MVM_OPERATOR)) {
01683             cmd = ast_play_and_wait(chan, "vm-sorry");
01684             break;
01685          }
01686          if (message_exists || recorded) {
01687             cmd = ast_play_and_wait(chan, "vm-saveoper");
01688             if (!cmd)
01689                cmd = ast_waitfordigit(chan, 3000);
01690             if (cmd == '1') {
01691                ast_play_and_wait(chan, "vm-msgsaved");
01692                cmd = '0';
01693             } else {
01694                ast_play_and_wait(chan, "vm-deleted");
01695                vm_delete(recordfile);
01696                cmd = '0';
01697             }
01698          }
01699          return cmd;
01700       default:
01701          /* If the caller is an ouside caller, and the review option is enabled,
01702             allow them to review the message, but let the owner of the box review
01703             their OGM's */
01704          if (outsidecaller && !ast_test_flag(vmu, MVM_REVIEW))
01705             return cmd;
01706          if (message_exists) {
01707             cmd = ast_play_and_wait(chan, "vm-review");
01708          } else {
01709             cmd = ast_play_and_wait(chan, "vm-torerecord");
01710             if (!cmd)
01711                cmd = ast_waitfordigit(chan, 600);
01712          }
01713 
01714          if (!cmd && outsidecaller && ast_test_flag(vmu, MVM_OPERATOR)) {
01715             cmd = ast_play_and_wait(chan, "vm-reachoper");
01716             if (!cmd)
01717                cmd = ast_waitfordigit(chan, 600);
01718          }
01719          if (!cmd)
01720             cmd = ast_waitfordigit(chan, 6000);
01721          if (!cmd) {
01722             attempts++;
01723          }
01724          if (attempts > max_attempts) {
01725             cmd = 't';
01726          }
01727       }
01728    }
01729    if (outsidecaller)
01730       ast_play_and_wait(chan, "vm-goodbye");
01731    if (cmd == 't')
01732       cmd = 0;
01733    return cmd;
01734 }
01735 
01736 /*! \brief Run external notification for voicemail message */
01737 static void run_externnotify(struct ast_channel *chan, struct minivm_account *vmu)
01738 {
01739    char arguments[BUFSIZ];
01740 
01741    if (ast_strlen_zero(vmu->externnotify) && ast_strlen_zero(global_externnotify))
01742       return;
01743 
01744    snprintf(arguments, sizeof(arguments), "%s %s@%s %s %s&", 
01745       ast_strlen_zero(vmu->externnotify) ? global_externnotify : vmu->externnotify, 
01746       vmu->username, vmu->domain,
01747       (chan->caller.id.name.valid && chan->caller.id.name.str)
01748          ? chan->caller.id.name.str : "",
01749       (chan->caller.id.number.valid && chan->caller.id.number.str)
01750          ? chan->caller.id.number.str : "");
01751 
01752    ast_debug(1, "Executing: %s\n", arguments);
01753    ast_safe_system(arguments);
01754 }
01755 
01756 /*!\internal
01757  * \brief Send message to voicemail account owner */
01758 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)
01759 {
01760    char *stringp;
01761    struct minivm_template *etemplate;
01762    char *messageformat;
01763    int res = 0;
01764    char oldlocale[100];
01765    const char *counter;
01766 
01767    if (!ast_strlen_zero(vmu->attachfmt)) {
01768       if (strstr(format, vmu->attachfmt)) {
01769          format = vmu->attachfmt;
01770       } else {
01771          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);
01772       }
01773    }
01774 
01775    etemplate = message_template_find(vmu->etemplate);
01776    if (!etemplate)
01777       etemplate = message_template_find(templatename);
01778    if (!etemplate)
01779       etemplate = message_template_find("email-default");
01780 
01781    /* Attach only the first format */
01782    stringp = messageformat = ast_strdupa(format);
01783    strsep(&stringp, "|");
01784 
01785    if (!ast_strlen_zero(etemplate->locale)) {
01786       char *new_locale;
01787       ast_copy_string(oldlocale, setlocale(LC_TIME, NULL), sizeof(oldlocale));
01788       ast_debug(2, "Changing locale from %s to %s\n", oldlocale, etemplate->locale);
01789       new_locale = setlocale(LC_TIME, etemplate->locale);
01790       if (new_locale == NULL) {
01791          ast_log(LOG_WARNING, "-_-_- Changing to new locale did not work. Locale: %s\n", etemplate->locale);
01792       }
01793    }
01794 
01795 
01796 
01797    /* Read counter if available */
01798    ast_channel_lock(chan);
01799    if ((counter = pbx_builtin_getvar_helper(chan, "MVM_COUNTER"))) {
01800       counter = ast_strdupa(counter);
01801    }
01802    ast_channel_unlock(chan);
01803 
01804    if (ast_strlen_zero(counter)) {
01805       ast_debug(2, "MVM_COUNTER not found\n");
01806    } else {
01807       ast_debug(2, "MVM_COUNTER found - will use it with value %s\n", counter);
01808    }
01809 
01810    res = sendmail(etemplate, vmu, cidnum, cidname, filename, messageformat, duration, etemplate->attachment, MVM_MESSAGE_EMAIL, counter);
01811 
01812    if (res == 0 && !ast_strlen_zero(vmu->pager))  {
01813       /* Find template for paging */
01814       etemplate = message_template_find(vmu->ptemplate);
01815       if (!etemplate)
01816          etemplate = message_template_find("pager-default");
01817       if (etemplate->locale) {
01818          ast_copy_string(oldlocale, setlocale(LC_TIME, ""), sizeof(oldlocale));
01819          setlocale(LC_TIME, etemplate->locale);
01820       }
01821 
01822       res = sendmail(etemplate, vmu, cidnum, cidname, filename, messageformat, duration, etemplate->attachment, MVM_MESSAGE_PAGE, counter);
01823    }
01824 
01825    ast_manager_event(chan, EVENT_FLAG_CALL, "MiniVoiceMail", "Action: SentNotification\rn\nMailbox: %s@%s\r\nCounter: %s\r\n", vmu->username, vmu->domain, counter);
01826 
01827    run_externnotify(chan, vmu);     /* Run external notification */
01828 
01829    if (etemplate->locale) {
01830       setlocale(LC_TIME, oldlocale); /* Rest to old locale */
01831    }
01832    return res;
01833 }
01834 
01835  
01836 /*!\internal
01837  * \brief Record voicemail message, store into file prepared for sending e-mail */
01838 static int leave_voicemail(struct ast_channel *chan, char *username, struct leave_vm_options *options)
01839 {
01840    char tmptxtfile[PATH_MAX];
01841    char callerid[256];
01842    FILE *txt;
01843    int res = 0, txtdes;
01844    int duration = 0;
01845    char date[256];
01846    char tmpdir[PATH_MAX];
01847    char ext_context[256] = "";
01848    char fmt[80];
01849    char *domain;
01850    char tmp[256] = "";
01851    struct minivm_account *vmu;
01852    int userdir;
01853 
01854    ast_copy_string(tmp, username, sizeof(tmp));
01855    username = tmp;
01856    domain = strchr(tmp, '@');
01857    if (domain) {
01858       *domain = '\0';
01859       domain++;
01860    }
01861 
01862    if (!(vmu = find_account(domain, username, TRUE))) {
01863       /* We could not find user, let's exit */
01864       ast_log(LOG_ERROR, "Can't allocate temporary account for '%s@%s'\n", username, domain);
01865       pbx_builtin_setvar_helper(chan, "MVM_RECORD_STATUS", "FAILED");
01866       return 0;
01867    }
01868 
01869    /* Setup pre-file if appropriate */
01870    if (strcmp(vmu->domain, "localhost"))
01871       snprintf(ext_context, sizeof(ext_context), "%s@%s", username, vmu->domain);
01872    else
01873       ast_copy_string(ext_context, vmu->domain, sizeof(ext_context));
01874 
01875    /* The meat of recording the message...  All the announcements and beeps have been played*/
01876    if (ast_strlen_zero(vmu->attachfmt))
01877       ast_copy_string(fmt, default_vmformat, sizeof(fmt));
01878    else
01879       ast_copy_string(fmt, vmu->attachfmt, sizeof(fmt));
01880 
01881    if (ast_strlen_zero(fmt)) {
01882       ast_log(LOG_WARNING, "No format for saving voicemail? Default %s\n", default_vmformat);
01883       pbx_builtin_setvar_helper(chan, "MVM_RECORD_STATUS", "FAILED");
01884       return res;
01885    }
01886 
01887    userdir = check_dirpath(tmpdir, sizeof(tmpdir), vmu->domain, username, "tmp");
01888 
01889    /* If we have no user directory, use generic temporary directory */
01890    if (!userdir) {
01891       create_dirpath(tmpdir, sizeof(tmpdir), "0000_minivm_temp", "mediafiles", "");
01892       ast_debug(3, "Creating temporary directory %s\n", tmpdir);
01893    }
01894 
01895 
01896    snprintf(tmptxtfile, sizeof(tmptxtfile), "%s/XXXXXX", tmpdir);
01897 
01898    /* XXX This file needs to be in temp directory */
01899    txtdes = mkstemp(tmptxtfile);
01900    if (txtdes < 0) {
01901       ast_log(LOG_ERROR, "Unable to create message file %s: %s\n", tmptxtfile, strerror(errno));
01902       res = ast_streamfile(chan, "vm-mailboxfull", chan->language);
01903       if (!res)
01904          res = ast_waitstream(chan, "");
01905       pbx_builtin_setvar_helper(chan, "MVM_RECORD_STATUS", "FAILED");
01906       return res;
01907    }
01908 
01909    if (res >= 0) {
01910       /* Unless we're *really* silent, try to send the beep */
01911       res = ast_streamfile(chan, "beep", chan->language);
01912       if (!res)
01913          res = ast_waitstream(chan, "");
01914    }
01915 
01916    /* OEJ XXX Maybe this can be turned into a log file? Hmm. */
01917    /* Store information */
01918    ast_debug(2, "Open file for metadata: %s\n", tmptxtfile);
01919 
01920    res = play_record_review(chan, NULL, tmptxtfile, global_vmmaxmessage, fmt, 1, vmu, &duration, NULL, options->record_gain);
01921 
01922    txt = fdopen(txtdes, "w+");
01923    if (!txt) {
01924       ast_log(LOG_WARNING, "Error opening text file for output\n");
01925    } else {
01926       struct ast_tm tm;
01927       struct timeval now = ast_tvnow();
01928       char timebuf[30];
01929       char logbuf[BUFSIZ];
01930       get_date(date, sizeof(date));
01931       ast_localtime(&now, &tm, NULL);
01932       ast_strftime(timebuf, sizeof(timebuf), "%H:%M:%S", &tm);
01933 
01934       ast_callerid_merge(callerid, sizeof(callerid),
01935          S_COR(chan->caller.id.name.valid, chan->caller.id.name.str, NULL),
01936          S_COR(chan->caller.id.number.valid, chan->caller.id.number.str, NULL),
01937          "Unknown");
01938       snprintf(logbuf, sizeof(logbuf),
01939          /* "Mailbox:domain:macrocontext:exten:priority:callerchan:callerid:origdate:origtime:duration:durationstatus:accountcode" */
01940          "%s:%s:%s:%s:%d:%s:%s:%s:%s:%d:%s:%s\n",
01941          username,
01942          chan->context,
01943          chan->macrocontext, 
01944          chan->exten,
01945          chan->priority,
01946          chan->name,
01947          callerid,
01948          date, 
01949          timebuf,
01950          duration,
01951          duration < global_vmminmessage ? "IGNORED" : "OK",
01952          vmu->accountcode
01953       ); 
01954       fprintf(txt, "%s", logbuf);
01955       if (minivmlogfile) {
01956          ast_mutex_lock(&minivmloglock);
01957          fprintf(minivmlogfile, "%s", logbuf);
01958          ast_mutex_unlock(&minivmloglock);
01959       }
01960 
01961       if (duration < global_vmminmessage) {
01962          ast_verb(3, "Recording was %d seconds long but needs to be at least %d - abandoning\n", duration, global_vmminmessage);
01963          fclose(txt);
01964          ast_filedelete(tmptxtfile, NULL);
01965          unlink(tmptxtfile);
01966          pbx_builtin_setvar_helper(chan, "MVM_RECORD_STATUS", "FAILED");
01967          return 0;
01968       } 
01969       fclose(txt); /* Close log file */
01970       if (ast_fileexists(tmptxtfile, NULL, NULL) <= 0) {
01971          ast_debug(1, "The recorded media file is gone, so we should remove the .txt file too!\n");
01972          unlink(tmptxtfile);
01973          pbx_builtin_setvar_helper(chan, "MVM_RECORD_STATUS", "FAILED");
01974          if(ast_test_flag(vmu, MVM_ALLOCED))
01975             free_user(vmu);
01976          return 0;
01977       }
01978 
01979       /* Set channel variables for the notify application */
01980       pbx_builtin_setvar_helper(chan, "MVM_FILENAME", tmptxtfile);
01981       snprintf(timebuf, sizeof(timebuf), "%d", duration);
01982       pbx_builtin_setvar_helper(chan, "MVM_DURATION", timebuf);
01983       pbx_builtin_setvar_helper(chan, "MVM_FORMAT", fmt);
01984 
01985    }
01986    global_stats.lastreceived = ast_tvnow();
01987    global_stats.receivedmessages++;
01988 #if 0
01989    /* Go ahead and delete audio files from system, they're not needed any more */
01990    if (ast_fileexists(tmptxtfile, NULL, NULL) <= 0) {
01991       ast_filedelete(tmptxtfile, NULL);
01992        /* Even not being used at the moment, it's better to convert ast_log to ast_debug anyway */
01993       ast_debug(2, "-_-_- Deleted audio file after notification :: %s \n", tmptxtfile);
01994    }
01995 #endif
01996 
01997    if (res > 0)
01998       res = 0;
01999 
02000    if(ast_test_flag(vmu, MVM_ALLOCED))
02001       free_user(vmu);
02002 
02003    pbx_builtin_setvar_helper(chan, "MVM_RECORD_STATUS", "SUCCESS");
02004    return res;
02005 }
02006 
02007 /*!\internal
02008  * \brief Queue a message waiting event */
02009 static void queue_mwi_event(const char *mbx, const char *ctx, int urgent, int new, int old)
02010 {
02011    struct ast_event *event;
02012    char *mailbox, *context;
02013 
02014    mailbox = ast_strdupa(mbx);
02015    context = ast_strdupa(ctx);
02016    if (ast_strlen_zero(context)) {
02017       context = "default";
02018    }
02019 
02020    if (!(event = ast_event_new(AST_EVENT_MWI,
02021          AST_EVENT_IE_MAILBOX, AST_EVENT_IE_PLTYPE_STR, mailbox,
02022          AST_EVENT_IE_CONTEXT, AST_EVENT_IE_PLTYPE_STR, context,
02023          AST_EVENT_IE_NEWMSGS, AST_EVENT_IE_PLTYPE_UINT, (new+urgent),
02024          AST_EVENT_IE_OLDMSGS, AST_EVENT_IE_PLTYPE_UINT, old,
02025          AST_EVENT_IE_END))) {
02026       return;
02027    }
02028 
02029    ast_event_queue_and_cache(event);
02030 }
02031 
02032 /*!\internal
02033  * \brief Send MWI using interal Asterisk event subsystem */
02034 static int minivm_mwi_exec(struct ast_channel *chan, const char *data)
02035 {
02036    int argc;
02037    char *argv[4];
02038    int res = 0;
02039    char *tmpptr;
02040    char tmp[PATH_MAX];
02041    char *mailbox;
02042    char *domain;
02043    if (ast_strlen_zero(data))  {
02044       ast_log(LOG_ERROR, "Minivm needs at least an account argument \n");
02045       return -1;
02046    }
02047    tmpptr = ast_strdupa((char *)data);
02048    if (!tmpptr) {
02049       ast_log(LOG_ERROR, "Out of memory\n");
02050       return -1;
02051    }
02052    argc = ast_app_separate_args(tmpptr, ',', argv, ARRAY_LEN(argv));
02053    if (argc < 4) {
02054       ast_log(LOG_ERROR, "%d arguments passed to MiniVM_MWI, need 4.\n", argc);
02055       return -1;
02056    }
02057    ast_copy_string(tmp, argv[0], sizeof(tmp));
02058    mailbox = tmp;
02059    domain = strchr(tmp, '@');
02060    if (domain) {
02061       *domain = '\0';
02062       domain++;
02063    }
02064    if (ast_strlen_zero(domain) || ast_strlen_zero(mailbox)) {
02065       ast_log(LOG_ERROR, "Need mailbox@context as argument. Sorry. Argument 0 %s\n", argv[0]);
02066       return -1;
02067    }
02068    queue_mwi_event(mailbox, domain, atoi(argv[1]), atoi(argv[2]), atoi(argv[3]));
02069 
02070    return res;
02071 }
02072 
02073 
02074 /*!\internal
02075  * \brief Notify voicemail account owners - either generic template or user specific */
02076 static int minivm_notify_exec(struct ast_channel *chan, const char *data)
02077 {
02078    int argc;
02079    char *argv[2];
02080    int res = 0;
02081    char tmp[PATH_MAX];
02082    char *domain;
02083    char *tmpptr;
02084    struct minivm_account *vmu;
02085    char *username = argv[0];
02086    const char *template = "";
02087    const char *filename;
02088    const char *format;
02089    const char *duration_string;
02090 
02091    if (ast_strlen_zero(data))  {
02092       ast_log(LOG_ERROR, "Minivm needs at least an account argument \n");
02093       return -1;
02094    }
02095    tmpptr = ast_strdupa((char *)data);
02096    if (!tmpptr) {
02097       ast_log(LOG_ERROR, "Out of memory\n");
02098       return -1;
02099    }
02100    argc = ast_app_separate_args(tmpptr, ',', argv, ARRAY_LEN(argv));
02101 
02102    if (argc == 2 && !ast_strlen_zero(argv[1]))
02103       template = argv[1];
02104 
02105    ast_copy_string(tmp, argv[0], sizeof(tmp));
02106    username = tmp;
02107    domain = strchr(tmp, '@');
02108    if (domain) {
02109       *domain = '\0';
02110       domain++;
02111    } 
02112    if (ast_strlen_zero(domain) || ast_strlen_zero(username)) {
02113       ast_log(LOG_ERROR, "Need username@domain as argument. Sorry. Argument 0 %s\n", argv[0]);
02114       return -1;
02115    }
02116 
02117    if(!(vmu = find_account(domain, username, TRUE))) {
02118       /* We could not find user, let's exit */
02119       ast_log(LOG_WARNING, "Could not allocate temporary memory for '%s@%s'\n", username, domain);
02120       pbx_builtin_setvar_helper(chan, "MVM_NOTIFY_STATUS", "FAILED");
02121       return -1;
02122    }
02123 
02124    ast_channel_lock(chan);
02125    if ((filename = pbx_builtin_getvar_helper(chan, "MVM_FILENAME"))) {
02126       filename = ast_strdupa(filename);
02127    }
02128    ast_channel_unlock(chan);
02129    /* Notify of new message to e-mail and pager */
02130    if (!ast_strlen_zero(filename)) {
02131       ast_channel_lock(chan); 
02132       if ((format = pbx_builtin_getvar_helper(chan, "MVM_FORMAT"))) {
02133          format = ast_strdupa(format);
02134       }
02135       if ((duration_string = pbx_builtin_getvar_helper(chan, "MVM_DURATION"))) {
02136          duration_string = ast_strdupa(duration_string);
02137       }
02138       ast_channel_unlock(chan);
02139       res = notify_new_message(chan, template, vmu, filename, atoi(duration_string),
02140          format,
02141          S_COR(chan->caller.id.number.valid, chan->caller.id.number.str, NULL),
02142          S_COR(chan->caller.id.name.valid, chan->caller.id.name.str, NULL));
02143    }
02144 
02145    pbx_builtin_setvar_helper(chan, "MVM_NOTIFY_STATUS", res == 0 ? "SUCCESS" : "FAILED");
02146 
02147 
02148    if(ast_test_flag(vmu, MVM_ALLOCED))
02149       free_user(vmu);
02150 
02151    /* Ok, we're ready to rock and roll. Return to dialplan */
02152 
02153    return res;
02154 
02155 }
02156 
02157 /*!\internal
02158  * \brief Dialplan function to record voicemail */
02159 static int minivm_record_exec(struct ast_channel *chan, const char *data)
02160 {
02161    int res = 0;
02162    char *tmp;
02163    struct leave_vm_options leave_options;
02164    int argc;
02165    char *argv[2];
02166    struct ast_flags flags = { 0 };
02167    char *opts[OPT_ARG_ARRAY_SIZE];
02168 
02169    memset(&leave_options, 0, sizeof(leave_options));
02170 
02171    /* Answer channel if it's not already answered */
02172    if (chan->_state != AST_STATE_UP)
02173       ast_answer(chan);
02174 
02175    if (ast_strlen_zero(data))  {
02176       ast_log(LOG_ERROR, "Minivm needs at least an account argument \n");
02177       return -1;
02178    }
02179    tmp = ast_strdupa((char *)data);
02180    if (!tmp) {
02181       ast_log(LOG_ERROR, "Out of memory\n");
02182       return -1;
02183    }
02184    argc = ast_app_separate_args(tmp, ',', argv, ARRAY_LEN(argv));
02185    if (argc == 2) {
02186       if (ast_app_parse_options(minivm_app_options, &flags, opts, argv[1])) {
02187          return -1;
02188       }
02189       ast_copy_flags(&leave_options, &flags, OPT_SILENT | OPT_BUSY_GREETING | OPT_UNAVAIL_GREETING );
02190       if (ast_test_flag(&flags, OPT_RECORDGAIN)) {
02191          int gain;
02192 
02193          if (sscanf(opts[OPT_ARG_RECORDGAIN], "%30d", &gain) != 1) {
02194             ast_log(LOG_WARNING, "Invalid value '%s' provided for record gain option\n", opts[OPT_ARG_RECORDGAIN]);
02195             return -1;
02196          } else 
02197             leave_options.record_gain = (signed char) gain;
02198       }
02199    } 
02200 
02201    /* Now run the appliation and good luck to you! */
02202    res = leave_voicemail(chan, argv[0], &leave_options);
02203 
02204    if (res == ERROR_LOCK_PATH) {
02205       ast_log(LOG_ERROR, "Could not leave voicemail. The path is already locked.\n");
02206       pbx_builtin_setvar_helper(chan, "MVM_RECORD_STATUS", "FAILED");
02207       res = 0;
02208    }
02209    pbx_builtin_setvar_helper(chan, "MVM_RECORD_STATUS", "SUCCESS");
02210 
02211    return res;
02212 }
02213 
02214 /*!\internal
02215  * \brief Play voicemail prompts - either generic or user specific */
02216 static int minivm_greet_exec(struct ast_channel *chan, const char *data)
02217 {
02218    struct leave_vm_options leave_options = { 0, '\0'};
02219    int argc;
02220    char *argv[2];
02221    struct ast_flags flags = { 0 };
02222    char *opts[OPT_ARG_ARRAY_SIZE];
02223    int res = 0;
02224    int ausemacro = 0;
02225    int ousemacro = 0;
02226    int ouseexten = 0;
02227    char tmp[PATH_MAX];
02228    char dest[PATH_MAX];
02229    char prefile[PATH_MAX] = "";
02230    char tempfile[PATH_MAX] = "";
02231    char ext_context[256] = "";
02232    char *domain;
02233    char ecodes[16] = "#";
02234    char *tmpptr;
02235    struct minivm_account *vmu;
02236    char *username = argv[0];
02237 
02238    if (ast_strlen_zero(data))  {
02239       ast_log(LOG_ERROR, "Minivm needs at least an account argument \n");
02240       return -1;
02241    }
02242    tmpptr = ast_strdupa((char *)data);
02243    if (!tmpptr) {
02244       ast_log(LOG_ERROR, "Out of memory\n");
02245       return -1;
02246    }
02247    argc = ast_app_separate_args(tmpptr, ',', argv, ARRAY_LEN(argv));
02248 
02249    if (argc == 2) {
02250       if (ast_app_parse_options(minivm_app_options, &flags, opts, argv[1]))
02251          return -1;
02252       ast_copy_flags(&leave_options, &flags, OPT_SILENT | OPT_BUSY_GREETING | OPT_UNAVAIL_GREETING );
02253    }
02254 
02255    ast_copy_string(tmp, argv[0], sizeof(tmp));
02256    username = tmp;
02257    domain = strchr(tmp, '@');
02258    if (domain) {
02259       *domain = '\0';
02260       domain++;
02261    } 
02262    if (ast_strlen_zero(domain) || ast_strlen_zero(username)) {
02263       ast_log(LOG_ERROR, "Need username@domain as argument. Sorry. Argument:  %s\n", argv[0]);
02264       return -1;
02265    }
02266    ast_debug(1, "Trying to find configuration for user %s in domain %s\n", username, domain);
02267 
02268    if (!(vmu = find_account(domain, username, TRUE))) {
02269       ast_log(LOG_ERROR, "Could not allocate memory. \n");
02270       return -1;
02271    }
02272 
02273    /* Answer channel if it's not already answered */
02274    if (chan->_state != AST_STATE_UP)
02275       ast_answer(chan);
02276 
02277    /* Setup pre-file if appropriate */
02278    if (strcmp(vmu->domain, "localhost"))
02279       snprintf(ext_context, sizeof(ext_context), "%s@%s", username, vmu->domain);
02280    else
02281       ast_copy_string(ext_context, vmu->domain, sizeof(ext_context));
02282 
02283    if (ast_test_flag(&leave_options, OPT_BUSY_GREETING)) {
02284       res = check_dirpath(dest, sizeof(dest), vmu->domain, username, "busy");
02285       if (res)
02286          snprintf(prefile, sizeof(prefile), "%s%s/%s/busy", MVM_SPOOL_DIR, vmu->domain, username);
02287    } else if (ast_test_flag(&leave_options, OPT_UNAVAIL_GREETING)) {
02288       res = check_dirpath(dest, sizeof(dest), vmu->domain, username, "unavail");
02289       if (res)
02290          snprintf(prefile, sizeof(prefile), "%s%s/%s/unavail", MVM_SPOOL_DIR, vmu->domain, username);
02291    }
02292    /* Check for temporary greeting - it overrides busy and unavail */
02293    snprintf(tempfile, sizeof(tempfile), "%s%s/%s/temp", MVM_SPOOL_DIR, vmu->domain, username);
02294    if (!(res = check_dirpath(dest, sizeof(dest), vmu->domain, username, "temp"))) {
02295       ast_debug(2, "Temporary message directory does not exist, using default (%s)\n", tempfile);
02296       ast_copy_string(prefile, tempfile, sizeof(prefile));
02297    }
02298    ast_debug(2, "Preparing to play message ...\n");
02299 
02300    /* Check current or macro-calling context for special extensions */
02301    if (ast_test_flag(vmu, MVM_OPERATOR)) {
02302       if (!ast_strlen_zero(vmu->exit)) {
02303          if (ast_exists_extension(chan, vmu->exit, "o", 1,
02304             S_COR(chan->caller.id.number.valid, chan->caller.id.number.str, NULL))) {
02305             strncat(ecodes, "0", sizeof(ecodes) - strlen(ecodes) - 1);
02306             ouseexten = 1;
02307          }
02308       } else if (ast_exists_extension(chan, chan->context, "o", 1,
02309          S_COR(chan->caller.id.number.valid, chan->caller.id.number.str, NULL))) {
02310          strncat(ecodes, "0", sizeof(ecodes) - strlen(ecodes) - 1);
02311          ouseexten = 1;
02312       }
02313       else if (!ast_strlen_zero(chan->macrocontext)
02314          && ast_exists_extension(chan, chan->macrocontext, "o", 1,
02315             S_COR(chan->caller.id.number.valid, chan->caller.id.number.str, NULL))) {
02316          strncat(ecodes, "0", sizeof(ecodes) - strlen(ecodes) - 1);
02317          ousemacro = 1;
02318       }
02319    }
02320 
02321    if (!ast_strlen_zero(vmu->exit)) {
02322       if (ast_exists_extension(chan, vmu->exit, "a", 1,
02323          S_COR(chan->caller.id.number.valid, chan->caller.id.number.str, NULL))) {
02324          strncat(ecodes, "*", sizeof(ecodes) -  strlen(ecodes) - 1);
02325       }
02326    } else if (ast_exists_extension(chan, chan->context, "a", 1,
02327       S_COR(chan->caller.id.number.valid, chan->caller.id.number.str, NULL))) {
02328       strncat(ecodes, "*", sizeof(ecodes) -  strlen(ecodes) - 1);
02329    } else if (!ast_strlen_zero(chan->macrocontext)
02330       && ast_exists_extension(chan, chan->macrocontext, "a", 1,
02331          S_COR(chan->caller.id.number.valid, chan->caller.id.number.str, NULL))) {
02332       strncat(ecodes, "*", sizeof(ecodes) -  strlen(ecodes) - 1);
02333       ausemacro = 1;
02334    }
02335 
02336    res = 0; /* Reset */
02337    /* Play the beginning intro if desired */
02338    if (!ast_strlen_zero(prefile)) {
02339       if (ast_streamfile(chan, prefile, chan->language) > -1) 
02340          res = ast_waitstream(chan, ecodes);
02341    } else {
02342       ast_debug(2, "%s doesn't exist, doing what we can\n", prefile);
02343       res = invent_message(chan, vmu->domain, username, ast_test_flag(&leave_options, OPT_BUSY_GREETING), ecodes);
02344    }
02345    if (res < 0) {
02346       ast_debug(2, "Hang up during prefile playback\n");
02347       pbx_builtin_setvar_helper(chan, "MVM_GREET_STATUS", "FAILED");
02348       if(ast_test_flag(vmu, MVM_ALLOCED))
02349          free_user(vmu);
02350       return -1;
02351    }
02352    if (res == '#') {
02353       /* On a '#' we skip the instructions */
02354       ast_set_flag(&leave_options, OPT_SILENT);
02355       res = 0;
02356    }
02357    if (!res && !ast_test_flag(&leave_options, OPT_SILENT)) {
02358       res = ast_streamfile(chan, SOUND_INTRO, chan->language);
02359       if (!res)
02360          res = ast_waitstream(chan, ecodes);
02361       if (res == '#') {
02362          ast_set_flag(&leave_options, OPT_SILENT);
02363          res = 0;
02364       }
02365    }
02366    if (res > 0)
02367       ast_stopstream(chan);
02368    /* Check for a '*' here in case the caller wants to escape from voicemail to something
02369       other than the operator -- an automated attendant or mailbox login for example */
02370    if (res == '*') {
02371       chan->exten[0] = 'a';
02372       chan->exten[1] = '\0';
02373       if (!ast_strlen_zero(vmu->exit)) {
02374          ast_copy_string(chan->context, vmu->exit, sizeof(chan->context));
02375       } else if (ausemacro && !ast_strlen_zero(chan->macrocontext)) {
02376          ast_copy_string(chan->context, chan->macrocontext, sizeof(chan->context));
02377       }
02378       chan->priority = 0;
02379       pbx_builtin_setvar_helper(chan, "MVM_GREET_STATUS", "USEREXIT");
02380       res = 0;
02381    } else if (res == '0') { /* Check for a '0' here */
02382       if(ouseexten || ousemacro) {
02383          chan->exten[0] = 'o';
02384          chan->exten[1] = '\0';
02385          if (!ast_strlen_zero(vmu->exit)) {
02386             ast_copy_string(chan->context, vmu->exit, sizeof(chan->context));
02387          } else if (ousemacro && !ast_strlen_zero(chan->macrocontext)) {
02388             ast_copy_string(chan->context, chan->macrocontext, sizeof(chan->context));
02389          }
02390          ast_play_and_wait(chan, "transfer");
02391          chan->priority = 0;
02392          pbx_builtin_setvar_helper(chan, "MVM_GREET_STATUS", "USEREXIT");
02393       }
02394       res =  0;
02395    } else if (res < 0) {
02396       pbx_builtin_setvar_helper(chan, "MVM_GREET_STATUS", "FAILED");
02397       res = -1;
02398    } else
02399       pbx_builtin_setvar_helper(chan, "MVM_GREET_STATUS", "SUCCESS");
02400 
02401    if(ast_test_flag(vmu, MVM_ALLOCED))
02402       free_user(vmu);
02403 
02404 
02405    /* Ok, we're ready to rock and roll. Return to dialplan */
02406    return res;
02407 
02408 }
02409 
02410 /*!\internal
02411  * \brief Dialplan application to delete voicemail */
02412 static int minivm_delete_exec(struct ast_channel *chan, const char *data)
02413 {
02414    int res = 0;
02415    char filename[BUFSIZ];
02416 
02417    if (!ast_strlen_zero(data)) {
02418       ast_copy_string(filename, (char *) data, sizeof(filename));
02419    } else {
02420       ast_channel_lock(chan);
02421       ast_copy_string(filename, pbx_builtin_getvar_helper(chan, "MVM_FILENAME"), sizeof(filename));
02422       ast_channel_unlock(chan);
02423    }
02424 
02425    if (ast_strlen_zero(filename)) {
02426       ast_log(LOG_ERROR, "No filename given in application arguments or channel variable MVM_FILENAME\n");
02427       return res;
02428    } 
02429 
02430    /* Go ahead and delete audio files from system, they're not needed any more */
02431    /* We should look for both audio and text files here */
02432    if (ast_fileexists(filename, NULL, NULL) > 0) {
02433       res = vm_delete(filename);
02434       if (res) {
02435          ast_debug(2, "Can't delete file: %s\n", filename);
02436          pbx_builtin_setvar_helper(chan, "MVM_DELETE_STATUS", "FAILED");
02437       } else {
02438          ast_debug(2, "Deleted voicemail file :: %s \n", filename);
02439          pbx_builtin_setvar_helper(chan, "MVM_DELETE_STATUS", "SUCCESS");
02440       }
02441    } else {
02442       ast_debug(2, "Filename does not exist: %s\n", filename);
02443       pbx_builtin_setvar_helper(chan, "MVM_DELETE_STATUS", "FAILED");
02444    }
02445 
02446    return res;
02447 }
02448 
02449 /*! \brief Record specific messages for voicemail account */
02450 static int minivm_accmess_exec(struct ast_channel *chan, const char *data)
02451 {
02452    int argc = 0;
02453    char *argv[2];
02454    char filename[PATH_MAX];
02455    char tmp[PATH_MAX];
02456    char *domain;
02457    char *tmpptr = NULL;
02458    struct minivm_account *vmu;
02459    char *username = argv[0];
02460    struct ast_flags flags = { 0 };
02461    char *opts[OPT_ARG_ARRAY_SIZE];
02462    int error = FALSE;
02463    char *message = NULL;
02464    char *prompt = NULL;
02465    int duration;
02466 
02467    if (ast_strlen_zero(data))  {
02468       ast_log(LOG_ERROR, "MinivmAccmess needs at least two arguments: account and option\n");
02469       error = TRUE;
02470    } else 
02471       tmpptr = ast_strdupa((char *)data);
02472    if (!error) {
02473       if (!tmpptr) {
02474          ast_log(LOG_ERROR, "Out of memory\n");
02475          error = TRUE;
02476       } else
02477          argc = ast_app_separate_args(tmpptr, ',', argv, ARRAY_LEN(argv));
02478    }
02479 
02480    if (argc <=1) {
02481       ast_log(LOG_ERROR, "MinivmAccmess needs at least two arguments: account and option\n");
02482       error = TRUE;
02483    }
02484    if (!error && strlen(argv[1]) > 1) {
02485       ast_log(LOG_ERROR, "MinivmAccmess can only handle one option at a time. Bad option string: %s\n", argv[1]);
02486       error = TRUE;
02487    }
02488 
02489    if (!error && ast_app_parse_options(minivm_accmess_options, &flags, opts, argv[1])) {
02490       ast_log(LOG_ERROR, "Can't parse option %s\n", argv[1]);
02491       error = TRUE;
02492    }
02493 
02494    if (error) {
02495       pbx_builtin_setvar_helper(chan, "MVM_ACCMESS_STATUS", "FAILED");
02496       return -1;
02497    }
02498 
02499    ast_copy_string(tmp, argv[0], sizeof(tmp));
02500    username = tmp;
02501    domain = strchr(tmp, '@');
02502    if (domain) {
02503       *domain = '\0';
02504       domain++;
02505    } 
02506    if (ast_strlen_zero(domain) || ast_strlen_zero(username)) {
02507       ast_log(LOG_ERROR, "Need username@domain as argument. Sorry. Argument 0 %s\n", argv[0]);
02508       pbx_builtin_setvar_helper(chan, "MVM_ACCMESS_STATUS", "FAILED");
02509       return -1;
02510    }
02511 
02512    if(!(vmu = find_account(domain, username, TRUE))) {
02513       /* We could not find user, let's exit */
02514       ast_log(LOG_WARNING, "Could not allocate temporary memory for '%s@%s'\n", username, domain);
02515       pbx_builtin_setvar_helper(chan, "MVM_ACCMESS_STATUS", "FAILED");
02516       return -1;
02517    }
02518 
02519    /* Answer channel if it's not already answered */
02520    if (chan->_state != AST_STATE_UP)
02521       ast_answer(chan);
02522    
02523    /* Here's where the action is */
02524    if (ast_test_flag(&flags, OPT_BUSY_GREETING)) {
02525       message = "busy";
02526       prompt = "vm-rec-busy";
02527    } else if (ast_test_flag(&flags, OPT_UNAVAIL_GREETING)) {
02528       message = "unavailable";
02529       prompt = "vm-rec-unv";
02530    } else if (ast_test_flag(&flags, OPT_TEMP_GREETING)) {
02531       message = "temp";
02532       prompt = "vm-rec-temp";
02533    } else if (ast_test_flag(&flags, OPT_NAME_GREETING)) {
02534       message = "greet";
02535       prompt = "vm-rec-name";
02536    }
02537    snprintf(filename,sizeof(filename), "%s%s/%s/%s", MVM_SPOOL_DIR, vmu->domain, vmu->username, message);
02538    /* Maybe we should check the result of play_record_review ? */
02539    play_record_review(chan, prompt, filename, global_maxgreet, default_vmformat, 0, vmu, &duration, NULL, FALSE);
02540 
02541    ast_debug(1, "Recorded new %s message in %s (duration %d)\n", message, filename, duration);
02542 
02543    if(ast_test_flag(vmu, MVM_ALLOCED))
02544       free_user(vmu);
02545 
02546    pbx_builtin_setvar_helper(chan, "MVM_ACCMESS_STATUS", "SUCCESS");
02547 
02548    /* Ok, we're ready to rock and roll. Return to dialplan */
02549    return 0;
02550 }
02551 
02552 /*! \brief Append new mailbox to mailbox list from configuration file */
02553 static int create_vmaccount(char *name, struct ast_variable *var, int realtime)
02554 {
02555    struct minivm_account *vmu;
02556    char *domain;
02557    char *username;
02558    char accbuf[BUFSIZ];
02559 
02560    ast_debug(3, "Creating %s account for [%s]\n", realtime ? "realtime" : "static", name);
02561 
02562    ast_copy_string(accbuf, name, sizeof(accbuf));
02563    username = accbuf;
02564    domain = strchr(accbuf, '@');
02565    if (domain) {
02566       *domain = '\0';
02567       domain++;
02568    }
02569    if (ast_strlen_zero(domain)) {
02570       ast_log(LOG_ERROR, "No domain given for mini-voicemail account %s. Not configured.\n", name);
02571       return 0;
02572    }
02573 
02574    ast_debug(3, "Creating static account for user %s domain %s\n", username, domain);
02575 
02576    /* Allocate user account */
02577    vmu = ast_calloc(1, sizeof(*vmu));
02578    if (!vmu)
02579       return 0;
02580    
02581    ast_copy_string(vmu->domain, domain, sizeof(vmu->domain));
02582    ast_copy_string(vmu->username, username, sizeof(vmu->username));
02583 
02584    populate_defaults(vmu);
02585 
02586    ast_debug(3, "...Configuring account %s\n", name);
02587 
02588    while (var) {
02589       ast_debug(3, "Configuring %s = \"%s\" for account %s\n", var->name, var->value, name);
02590       if (!strcasecmp(var->name, "serveremail")) {
02591          ast_copy_string(vmu->serveremail, var->value, sizeof(vmu->serveremail));
02592       } else if (!strcasecmp(var->name, "email")) {
02593          ast_copy_string(vmu->email, var->value, sizeof(vmu->email));
02594       } else if (!strcasecmp(var->name, "accountcode")) {
02595          ast_copy_string(vmu->accountcode, var->value, sizeof(vmu->accountcode));
02596       } else if (!strcasecmp(var->name, "pincode")) {
02597          ast_copy_string(vmu->pincode, var->value, sizeof(vmu->pincode));
02598       } else if (!strcasecmp(var->name, "domain")) {
02599          ast_copy_string(vmu->domain, var->value, sizeof(vmu->domain));
02600       } else if (!strcasecmp(var->name, "language")) {
02601          ast_copy_string(vmu->language, var->value, sizeof(vmu->language));
02602       } else if (!strcasecmp(var->name, "timezone")) {
02603          ast_copy_string(vmu->zonetag, var->value, sizeof(vmu->zonetag));
02604       } else if (!strcasecmp(var->name, "externnotify")) {
02605          ast_copy_string(vmu->externnotify, var->value, sizeof(vmu->externnotify));
02606       } else if (!strcasecmp(var->name, "etemplate")) {
02607          ast_copy_string(vmu->etemplate, var->value, sizeof(vmu->etemplate));
02608       } else if (!strcasecmp(var->name, "ptemplate")) {
02609          ast_copy_string(vmu->ptemplate, var->value, sizeof(vmu->ptemplate));
02610       } else if (!strcasecmp(var->name, "fullname")) {
02611          ast_copy_string(vmu->fullname, var->value, sizeof(vmu->fullname));
02612       } else if (!strcasecmp(var->name, "setvar")) {
02613          char *varval;
02614          char *varname = ast_strdupa(var->value);
02615          struct ast_variable *tmpvar;
02616 
02617          if (varname && (varval = strchr(varname, '='))) {
02618             *varval = '\0';
02619             varval++;
02620             if ((tmpvar = ast_variable_new(varname, varval, ""))) {
02621                tmpvar->next = vmu->chanvars;
02622                vmu->chanvars = tmpvar;
02623             }
02624          }
02625       } else if (!strcasecmp(var->name, "pager")) {
02626          ast_copy_string(vmu->pager, var->value, sizeof(vmu->pager));
02627       } else if (!strcasecmp(var->name, "volgain")) {
02628          sscanf(var->value, "%30lf", &vmu->volgain);
02629       } else {
02630          ast_log(LOG_ERROR, "Unknown configuration option for minivm account %s : %s\n", name, var->name);
02631       }
02632       var = var->next;
02633    }
02634    ast_debug(3, "...Linking account %s\n", name);
02635    
02636    AST_LIST_LOCK(&minivm_accounts);
02637    AST_LIST_INSERT_TAIL(&minivm_accounts, vmu, list);
02638    AST_LIST_UNLOCK(&minivm_accounts);
02639 
02640    global_stats.voicemailaccounts++;
02641 
02642    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)" : "");
02643    return 0;
02644 }
02645 
02646 /*! \brief Free Mini Voicemail timezone */
02647 static void free_zone(struct minivm_zone *z)
02648 {
02649    ast_free(z);
02650 }
02651 
02652 /*! \brief Clear list of timezones */
02653 static void timezone_destroy_list(void)
02654 {
02655    struct minivm_zone *this;
02656 
02657    AST_LIST_LOCK(&minivm_zones);
02658    while ((this = AST_LIST_REMOVE_HEAD(&minivm_zones, list))) 
02659       free_zone(this);
02660       
02661    AST_LIST_UNLOCK(&minivm_zones);
02662 }
02663 
02664 /*! \brief Add time zone to memory list */
02665 static int timezone_add(const char *zonename, const char *config)
02666 {
02667    struct minivm_zone *newzone;
02668    char *msg_format, *timezone_str;
02669 
02670    newzone = ast_calloc(1, sizeof(*newzone));
02671    if (newzone == NULL)
02672       return 0;
02673 
02674    msg_format = ast_strdupa(config);
02675    if (msg_format == NULL) {
02676       ast_log(LOG_WARNING, "Out of memory.\n");
02677       ast_free(newzone);
02678       return 0;
02679    }
02680 
02681    timezone_str = strsep(&msg_format, "|");
02682    if (!msg_format) {
02683       ast_log(LOG_WARNING, "Invalid timezone definition : %s\n", zonename);
02684       ast_free(newzone);
02685       return 0;
02686    }
02687          
02688    ast_copy_string(newzone->name, zonename, sizeof(newzone->name));
02689    ast_copy_string(newzone->timezone, timezone_str, sizeof(newzone->timezone));
02690    ast_copy_string(newzone->msg_format, msg_format, sizeof(newzone->msg_format));
02691 
02692    AST_LIST_LOCK(&minivm_zones);
02693    AST_LIST_INSERT_TAIL(&minivm_zones, newzone, list);
02694    AST_LIST_UNLOCK(&minivm_zones);
02695 
02696    global_stats.timezones++;
02697 
02698    return 0;
02699 }
02700 
02701 /*! \brief Read message template from file */
02702 static char *message_template_parse_filebody(const char *filename) {
02703    char buf[BUFSIZ * 6];
02704    char readbuf[BUFSIZ];
02705    char filenamebuf[BUFSIZ];
02706    char *writepos;
02707    char *messagebody;
02708    FILE *fi;
02709    int lines = 0;
02710 
02711    if (ast_strlen_zero(filename))
02712       return NULL;
02713    if (*filename == '/') 
02714       ast_copy_string(filenamebuf, filename, sizeof(filenamebuf));
02715    else 
02716       snprintf(filenamebuf, sizeof(filenamebuf), "%s/%s", ast_config_AST_CONFIG_DIR, filename);
02717 
02718    if (!(fi = fopen(filenamebuf, "r"))) {
02719       ast_log(LOG_ERROR, "Can't read message template from file: %s\n", filenamebuf);
02720       return NULL;
02721    }
02722    writepos = buf;
02723    while (fgets(readbuf, sizeof(readbuf), fi)) {
02724       lines ++;
02725       if (writepos != buf) {
02726          *writepos = '\n';    /* Replace EOL with new line */
02727          writepos++;
02728       }
02729       ast_copy_string(writepos, readbuf, sizeof(buf) - (writepos - buf));
02730       writepos += strlen(readbuf) - 1;
02731    }
02732    fclose(fi);
02733    messagebody = ast_calloc(1, strlen(buf + 1));
02734    ast_copy_string(messagebody, buf, strlen(buf) + 1);
02735    ast_debug(4, "---> Size of allocation %d\n", (int) strlen(buf + 1) );
02736    ast_debug(4, "---> Done reading message template : \n%s\n---- END message template--- \n", messagebody);
02737 
02738    return messagebody;
02739 }
02740 
02741 /*! \brief Parse emailbody template from configuration file */
02742 static char *message_template_parse_emailbody(const char *configuration)
02743 {
02744    char *tmpread, *tmpwrite;
02745    char *emailbody = ast_strdup(configuration);
02746 
02747    /* substitute strings \t and \n into the apropriate characters */
02748    tmpread = tmpwrite = emailbody;
02749    while ((tmpwrite = strchr(tmpread,'\\'))) {
02750           int len = strlen("\n");
02751           switch (tmpwrite[1]) {
02752           case 'n':
02753             memmove(tmpwrite + len, tmpwrite + 2, strlen(tmpwrite + 2) + 1);
02754             strncpy(tmpwrite, "\n", len);
02755             break;
02756           case 't':
02757             memmove(tmpwrite + len, tmpwrite + 2, strlen(tmpwrite + 2) + 1);
02758             strncpy(tmpwrite, "\t", len);
02759             break;
02760           default:
02761             ast_log(LOG_NOTICE, "Substitution routine does not support this character: %c\n", tmpwrite[1]);
02762           }
02763           tmpread = tmpwrite + len;
02764    }
02765    return emailbody; 
02766 }
02767 
02768 /*! \brief Apply general configuration options */
02769 static int apply_general_options(struct ast_variable *var)
02770 {
02771    int error = 0;
02772 
02773    while (var) {
02774       /* Mail command */
02775       if (!strcmp(var->name, "mailcmd")) {
02776          ast_copy_string(global_mailcmd, var->value, sizeof(global_mailcmd)); /* User setting */
02777       } else if (!strcmp(var->name, "maxgreet")) {
02778          global_maxgreet = atoi(var->value);
02779       } else if (!strcmp(var->name, "maxsilence")) {
02780          global_maxsilence = atoi(var->value);
02781          if (global_maxsilence > 0)
02782             global_maxsilence *= 1000;
02783       } else if (!strcmp(var->name, "logfile")) {
02784          if (!ast_strlen_zero(var->value) ) {
02785             if(*(var->value) == '/')
02786                ast_copy_string(global_logfile, var->value, sizeof(global_logfile));
02787             else
02788                snprintf(global_logfile, sizeof(global_logfile), "%s/%s", ast_config_AST_LOG_DIR, var->value);
02789          }
02790       } else if (!strcmp(var->name, "externnotify")) {
02791          /* External voicemail notify application */
02792          ast_copy_string(global_externnotify, var->value, sizeof(global_externnotify));
02793       } else if (!strcmp(var->name, "silencetreshold")) {
02794          /* Silence treshold */
02795          global_silencethreshold = atoi(var->value);
02796       } else if (!strcmp(var->name, "maxmessage")) {
02797          int x;
02798          if (sscanf(var->value, "%30d", &x) == 1) {
02799             global_vmmaxmessage = x;
02800          } else {
02801             error ++;
02802             ast_log(LOG_WARNING, "Invalid max message time length\n");
02803          }
02804       } else if (!strcmp(var->name, "minmessage")) {
02805          int x;
02806          if (sscanf(var->value, "%30d", &x) == 1) {
02807             global_vmminmessage = x;
02808             if (global_maxsilence <= global_vmminmessage)
02809                ast_log(LOG_WARNING, "maxsilence should be less than minmessage or you may get empty messages\n");
02810          } else {
02811             error ++;
02812             ast_log(LOG_WARNING, "Invalid min message time length\n");
02813          }
02814       } else if (!strcmp(var->name, "format")) {
02815          ast_copy_string(default_vmformat, var->value, sizeof(default_vmformat));
02816       } else if (!strcmp(var->name, "review")) {
02817          ast_set2_flag((&globalflags), ast_true(var->value), MVM_REVIEW);  
02818       } else if (!strcmp(var->name, "operator")) {
02819          ast_set2_flag((&globalflags), ast_true(var->value), MVM_OPERATOR);   
02820       }
02821       var = var->next;
02822    }
02823    return error;
02824 }
02825 
02826 /*! \brief Load minivoicemail configuration */
02827 static int load_config(int reload)
02828 {
02829    struct ast_config *cfg;
02830    struct ast_variable *var;
02831    char *cat;
02832    const char *chanvar;
02833    int error = 0;
02834    struct minivm_template *template;
02835    struct ast_flags config_flags = { reload ? CONFIG_FLAG_FILEUNCHANGED : 0 };
02836 
02837    cfg = ast_config_load(VOICEMAIL_CONFIG, config_flags);
02838    if (cfg == CONFIG_STATUS_FILEUNCHANGED) {
02839       return 0;
02840    } else if (cfg == CONFIG_STATUS_FILEINVALID) {
02841       ast_log(LOG_ERROR, "Config file " VOICEMAIL_CONFIG " is in an invalid format.  Aborting.\n");
02842       return 0;
02843    }
02844 
02845    ast_mutex_lock(&minivmlock);
02846 
02847    /* Destroy lists to reconfigure */
02848    message_destroy_list();    /* Destroy list of voicemail message templates */
02849    timezone_destroy_list();   /* Destroy list of timezones */
02850    vmaccounts_destroy_list(); /* Destroy list of voicemail accounts */
02851    ast_debug(2, "Destroyed memory objects...\n");
02852 
02853    /* First, set some default settings */
02854    global_externnotify[0] = '\0';
02855    global_logfile[0] = '\0';
02856    global_vmmaxmessage = 2000;
02857    global_maxgreet = 2000;
02858    global_vmminmessage = 0;
02859    strcpy(global_mailcmd, SENDMAIL);
02860    global_maxsilence = 0;
02861    global_saydurationminfo = 2;
02862    ast_copy_string(default_vmformat, "wav", sizeof(default_vmformat));
02863    ast_set2_flag((&globalflags), FALSE, MVM_REVIEW);  
02864    ast_set2_flag((&globalflags), FALSE, MVM_OPERATOR);   
02865    /* Reset statistics */
02866    memset(&global_stats, 0, sizeof(global_stats));
02867    global_stats.reset = ast_tvnow();
02868 
02869    global_silencethreshold = ast_dsp_get_threshold_from_settings(THRESHOLD_SILENCE);
02870 
02871    /* Make sure we could load configuration file */
02872    if (!cfg) {
02873       ast_log(LOG_WARNING, "Failed to load configuration file. Module activated with default settings.\n");
02874       ast_mutex_unlock(&minivmlock);
02875       return 0;
02876    }
02877 
02878    ast_debug(2, "Loaded configuration file, now parsing\n");
02879 
02880    /* General settings */
02881 
02882    cat = ast_category_browse(cfg, NULL);
02883    while (cat) {
02884       ast_debug(3, "Found configuration section [%s]\n", cat);
02885       if (!strcasecmp(cat, "general")) {
02886          /* Nothing right now */
02887          error += apply_general_options(ast_variable_browse(cfg, cat));
02888       } else if (!strncasecmp(cat, "template-", 9))  {
02889          /* Template */
02890          char *name = cat + 9;
02891 
02892          /* Now build and link template to list */
02893          error += message_template_build(name, ast_variable_browse(cfg, cat));
02894       } else {
02895          var = ast_variable_browse(cfg, cat);
02896          if (!strcasecmp(cat, "zonemessages")) {
02897             /* Timezones in this context */
02898             while (var) {
02899                timezone_add(var->name, var->value);
02900                var = var->next;
02901             }
02902          } else {
02903             /* Create mailbox from this */
02904             error += create_vmaccount(cat, var, FALSE);
02905          }
02906       }
02907       /* Find next section in configuration file */
02908       cat = ast_category_browse(cfg, cat);
02909    }
02910 
02911    /* Configure the default email template */
02912    message_template_build("email-default", NULL);
02913    template = message_template_find("email-default");
02914 
02915    /* Load date format config for voicemail mail */
02916    if ((chanvar = ast_variable_retrieve(cfg, "general", "emaildateformat"))) 
02917       ast_copy_string(template->dateformat, chanvar, sizeof(template->dateformat));
02918    if ((chanvar = ast_variable_retrieve(cfg, "general", "emailfromstring")))
02919       ast_copy_string(template->fromaddress, chanvar, sizeof(template->fromaddress));
02920    if ((chanvar = ast_variable_retrieve(cfg, "general", "emailaaddress")))
02921       ast_copy_string(template->serveremail, chanvar, sizeof(template->serveremail));
02922    if ((chanvar = ast_variable_retrieve(cfg, "general", "emailcharset")))
02923       ast_copy_string(template->charset, chanvar, sizeof(template->charset));
02924    if ((chanvar = ast_variable_retrieve(cfg, "general", "emailsubject"))) 
02925       ast_copy_string(template->subject, chanvar, sizeof(template->subject));
02926    if ((chanvar = ast_variable_retrieve(cfg, "general", "emailbody"))) 
02927       template->body = message_template_parse_emailbody(chanvar);
02928    template->attachment = TRUE;
02929 
02930    message_template_build("pager-default", NULL);
02931    template = message_template_find("pager-default");
02932    if ((chanvar = ast_variable_retrieve(cfg, "general", "pagerfromstring")))
02933       ast_copy_string(template->fromaddress, chanvar, sizeof(template->fromaddress));
02934    if ((chanvar = ast_variable_retrieve(cfg, "general", "pageraddress")))
02935       ast_copy_string(template->serveremail, chanvar, sizeof(template->serveremail));
02936    if ((chanvar = ast_variable_retrieve(cfg, "general", "pagercharset")))
02937       ast_copy_string(template->charset, chanvar, sizeof(template->charset));
02938    if ((chanvar = ast_variable_retrieve(cfg, "general", "pagersubject")))
02939       ast_copy_string(template->subject, chanvar,sizeof(template->subject));
02940    if ((chanvar = ast_variable_retrieve(cfg, "general", "pagerbody"))) 
02941       template->body = message_template_parse_emailbody(chanvar);
02942    template->attachment = FALSE;
02943 
02944    if (error)
02945       ast_log(LOG_ERROR, "--- A total of %d errors found in mini-voicemail configuration\n", error);
02946 
02947    ast_mutex_unlock(&minivmlock);
02948    ast_config_destroy(cfg);
02949 
02950    /* Close log file if it's open and disabled */
02951    if(minivmlogfile)
02952       fclose(minivmlogfile);
02953 
02954    /* Open log file if it's enabled */
02955    if(!ast_strlen_zero(global_logfile)) {
02956       minivmlogfile = fopen(global_logfile, "a");
02957       if(!minivmlogfile)
02958          ast_log(LOG_ERROR, "Failed to open minivm log file %s : %s\n", global_logfile, strerror(errno));
02959       if (minivmlogfile)
02960          ast_debug(3, "Opened log file %s \n", global_logfile);
02961    }
02962 
02963    return 0;
02964 }
02965 
02966 /*! \brief CLI routine for listing templates */
02967 static char *handle_minivm_list_templates(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
02968 {
02969    struct minivm_template *this;
02970 #define HVLT_OUTPUT_FORMAT "%-15s %-10s %-10s %-15.15s %-50s\n"
02971    int count = 0;
02972 
02973    switch (cmd) {
02974    case CLI_INIT:
02975       e->command = "minivm list templates";
02976       e->usage =
02977          "Usage: minivm list templates\n"
02978          "       Lists message templates for e-mail, paging and IM\n";
02979       return NULL;
02980    case CLI_GENERATE:
02981       return NULL;
02982    }
02983 
02984    if (a->argc > 3)
02985       return CLI_SHOWUSAGE;
02986 
02987    AST_LIST_LOCK(&message_templates);
02988    if (AST_LIST_EMPTY(&message_templates)) {
02989       ast_cli(a->fd, "There are no message templates defined\n");
02990       AST_LIST_UNLOCK(&message_templates);
02991       return CLI_FAILURE;
02992    }
02993    ast_cli(a->fd, HVLT_OUTPUT_FORMAT, "Template name", "Charset", "Locale", "Attach media", "Subject");
02994    ast_cli(a->fd, HVLT_OUTPUT_FORMAT, "-------------", "-------", "------", "------------", "-------");
02995    AST_LIST_TRAVERSE(&message_templates, this, list) {
02996       ast_cli(a->fd, HVLT_OUTPUT_FORMAT, this->name, 
02997          this->charset ? this->charset : "-", 
02998          this->locale ? this->locale : "-",
02999          this->attachment ? "Yes" : "No",
03000          this->subject ? this->subject : "-");
03001       count++;
03002    }
03003    AST_LIST_UNLOCK(&message_templates);
03004    ast_cli(a->fd, "\n * Total: %d minivoicemail message templates\n", count);
03005    return CLI_SUCCESS;
03006 }
03007 
03008 static char *complete_minivm_show_users(const char *line, const char *word, int pos, int state)
03009 {
03010    int which = 0;
03011    int wordlen;
03012    struct minivm_account *vmu;
03013    const char *domain = "";
03014 
03015    /* 0 - voicemail; 1 - list; 2 - accounts; 3 - for; 4 - <domain> */
03016    if (pos > 4)
03017       return NULL;
03018    if (pos == 3)
03019       return (state == 0) ? ast_strdup("for") : NULL;
03020    wordlen = strlen(word);
03021    AST_LIST_TRAVERSE(&minivm_accounts, vmu, list) {
03022       if (!strncasecmp(word, vmu->domain, wordlen)) {
03023          if (domain && strcmp(domain, vmu->domain) && ++which > state)
03024             return ast_strdup(vmu->domain);
03025          /* ignore repeated domains ? */
03026          domain = vmu->domain;
03027       }
03028    }
03029    return NULL;
03030 }
03031 
03032 /*! \brief CLI command to list voicemail accounts */
03033 static char *handle_minivm_show_users(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
03034 {
03035    struct minivm_account *vmu;
03036 #define HMSU_OUTPUT_FORMAT "%-23s %-15s %-15s %-10s %-10s %-50s\n"
03037    int count = 0;
03038 
03039    switch (cmd) {
03040    case CLI_INIT:
03041       e->command = "minivm list accounts";
03042       e->usage =
03043          "Usage: minivm list accounts\n"
03044          "       Lists all mailboxes currently set up\n";
03045       return NULL;
03046    case CLI_GENERATE:
03047       return complete_minivm_show_users(a->line, a->word, a->pos, a->n);
03048    }
03049 
03050    if ((a->argc < 3) || (a->argc > 5) || (a->argc == 4))
03051       return CLI_SHOWUSAGE;
03052    if ((a->argc == 5) && strcmp(a->argv[3],"for"))
03053       return CLI_SHOWUSAGE;
03054 
03055    AST_LIST_LOCK(&minivm_accounts);
03056    if (AST_LIST_EMPTY(&minivm_accounts)) {
03057       ast_cli(a->fd, "There are no voicemail users currently defined\n");
03058       AST_LIST_UNLOCK(&minivm_accounts);
03059       return CLI_FAILURE;
03060    }
03061    ast_cli(a->fd, HMSU_OUTPUT_FORMAT, "User", "E-Template", "P-template", "Zone", "Format", "Full name");
03062    ast_cli(a->fd, HMSU_OUTPUT_FORMAT, "----", "----------", "----------", "----", "------", "---------");
03063    AST_LIST_TRAVERSE(&minivm_accounts, vmu, list) {
03064       char tmp[256] = "";
03065       if ((a->argc == 3) || ((a->argc == 5) && !strcmp(a->argv[4], vmu->domain))) {
03066          count++;
03067          snprintf(tmp, sizeof(tmp), "%s@%s", vmu->username, vmu->domain);
03068          ast_cli(a->fd, HMSU_OUTPUT_FORMAT, tmp, vmu->etemplate ? vmu->etemplate : "-", 
03069             vmu->ptemplate ? vmu->ptemplate : "-",
03070             vmu->zonetag ? vmu->zonetag : "-", 
03071             vmu->attachfmt ? vmu->attachfmt : "-",
03072             vmu->fullname);
03073       }
03074    }
03075    AST_LIST_UNLOCK(&minivm_accounts);
03076    ast_cli(a->fd, "\n * Total: %d minivoicemail accounts\n", count);
03077    return CLI_SUCCESS;
03078 }
03079 
03080 /*! \brief Show a list of voicemail zones in the CLI */
03081 static char *handle_minivm_show_zones(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
03082 {
03083    struct minivm_zone *zone;
03084 #define HMSZ_OUTPUT_FORMAT "%-15s %-20s %-45s\n"
03085    char *res = CLI_SUCCESS;
03086 
03087    switch (cmd) {
03088    case CLI_INIT:
03089       e->command = "minivm list zones";
03090       e->usage =
03091          "Usage: minivm list zones\n"
03092          "       Lists zone message formats\n";
03093       return NULL;
03094    case CLI_GENERATE:
03095       return NULL;
03096    }
03097 
03098    if (a->argc != e->args)
03099       return CLI_SHOWUSAGE;
03100 
03101    AST_LIST_LOCK(&minivm_zones);
03102    if (!AST_LIST_EMPTY(&minivm_zones)) {
03103       ast_cli(a->fd, HMSZ_OUTPUT_FORMAT, "Zone", "Timezone", "Message Format");
03104       ast_cli(a->fd, HMSZ_OUTPUT_FORMAT, "----", "--------", "--------------");
03105       AST_LIST_TRAVERSE(&minivm_zones, zone, list) {
03106          ast_cli(a->fd, HMSZ_OUTPUT_FORMAT, zone->name, zone->timezone, zone->msg_format);
03107       }
03108    } else {
03109       ast_cli(a->fd, "There are no voicemail zones currently defined\n");
03110       res = CLI_FAILURE;
03111    }
03112    AST_LIST_UNLOCK(&minivm_zones);
03113 
03114    return res;
03115 }
03116 
03117 /*! \brief CLI Show settings */
03118 static char *handle_minivm_show_settings(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
03119 {
03120    switch (cmd) {
03121    case CLI_INIT:
03122       e->command = "minivm show settings";
03123       e->usage =
03124          "Usage: minivm show settings\n"
03125          "       Display Mini-Voicemail general settings\n";
03126       return NULL;
03127    case CLI_GENERATE:
03128       return NULL;
03129    }
03130 
03131    ast_cli(a->fd, "* Mini-Voicemail general settings\n");
03132    ast_cli(a->fd, "  -------------------------------\n");
03133    ast_cli(a->fd, "\n");
03134    ast_cli(a->fd, "  Mail command (shell):               %s\n", global_mailcmd);
03135    ast_cli(a->fd, "  Max silence:                        %d\n", global_maxsilence);
03136    ast_cli(a->fd, "  Silence threshold:                  %d\n", global_silencethreshold);
03137    ast_cli(a->fd, "  Max message length (secs):          %d\n", global_vmmaxmessage);
03138    ast_cli(a->fd, "  Min message length (secs):          %d\n", global_vmminmessage);
03139    ast_cli(a->fd, "  Default format:                     %s\n", default_vmformat);
03140    ast_cli(a->fd, "  Extern notify (shell):              %s\n", global_externnotify);
03141    ast_cli(a->fd, "  Logfile:                            %s\n", global_logfile[0] ? global_logfile : "<disabled>");
03142    ast_cli(a->fd, "  Operator exit:                      %s\n", ast_test_flag(&globalflags, MVM_OPERATOR) ? "Yes" : "No");
03143    ast_cli(a->fd, "  Message review:                     %s\n", ast_test_flag(&globalflags, MVM_REVIEW) ? "Yes" : "No");
03144 
03145    ast_cli(a->fd, "\n");
03146    return CLI_SUCCESS;
03147 }
03148 
03149 /*! \brief Show stats */
03150 static char *handle_minivm_show_stats(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
03151 {
03152    struct ast_tm timebuf;
03153    char buf[BUFSIZ];
03154 
03155    switch (cmd) {
03156    
03157    case CLI_INIT:
03158       e->command = "minivm show stats";
03159       e->usage =
03160          "Usage: minivm show stats\n"
03161          "       Display Mini-Voicemail counters\n";
03162       return NULL;
03163    case CLI_GENERATE:
03164       return NULL;
03165    }
03166 
03167    ast_cli(a->fd, "* Mini-Voicemail statistics\n");
03168    ast_cli(a->fd, "  -------------------------\n");
03169    ast_cli(a->fd, "\n");
03170    ast_cli(a->fd, "  Voicemail accounts:                  %5d\n", global_stats.voicemailaccounts);
03171    ast_cli(a->fd, "  Templates:                           %5d\n", global_stats.templates);
03172    ast_cli(a->fd, "  Timezones:                           %5d\n", global_stats.timezones);
03173    if (global_stats.receivedmessages == 0) {
03174       ast_cli(a->fd, "  Received messages since last reset:  <none>\n");
03175    } else {
03176       ast_cli(a->fd, "  Received messages since last reset:  %d\n", global_stats.receivedmessages);
03177       ast_localtime(&global_stats.lastreceived, &timebuf, NULL);
03178       ast_strftime(buf, sizeof(buf), "%a %b %e %r %Z %Y", &timebuf);
03179       ast_cli(a->fd, "  Last received voicemail:             %s\n", buf);
03180    }
03181    ast_localtime(&global_stats.reset, &timebuf, NULL);
03182    ast_strftime(buf, sizeof(buf), "%a %b %e %r %Z %Y", &timebuf);
03183    ast_cli(a->fd, "  Last reset:                          %s\n", buf);
03184 
03185    ast_cli(a->fd, "\n");
03186    return CLI_SUCCESS;
03187 }
03188 
03189 /*! \brief  ${MINIVMACCOUNT()} Dialplan function - reads account data */
03190 static int minivm_account_func_read(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len)
03191 {
03192    struct minivm_account *vmu;
03193    char *username, *domain, *colname;
03194 
03195    if (!(username = ast_strdupa(data))) {
03196       ast_log(LOG_ERROR, "Memory Error!\n");
03197       return -1;
03198    }
03199 
03200    if ((colname = strchr(username, ':'))) {
03201       *colname = '\0';
03202       colname++;
03203    } else {
03204       colname = "path";
03205    }
03206    if ((domain = strchr(username, '@'))) {
03207       *domain = '\0';
03208       domain++;
03209    }
03210    if (ast_strlen_zero(username) || ast_strlen_zero(domain)) {
03211       ast_log(LOG_ERROR, "This function needs a username and a domain: username@domain\n");
03212       return 0;
03213    }
03214 
03215    if (!(vmu = find_account(domain, username, TRUE)))
03216       return 0;
03217 
03218    if (!strcasecmp(colname, "hasaccount")) {
03219       ast_copy_string(buf, (ast_test_flag(vmu, MVM_ALLOCED) ? "0" : "1"), len);
03220    } else  if (!strcasecmp(colname, "fullname")) { 
03221       ast_copy_string(buf, vmu->fullname, len);
03222    } else  if (!strcasecmp(colname, "email")) { 
03223       if (!ast_strlen_zero(vmu->email))
03224          ast_copy_string(buf, vmu->email, len);
03225       else
03226          snprintf(buf, len, "%s@%s", vmu->username, vmu->domain);
03227    } else  if (!strcasecmp(colname, "pager")) { 
03228       ast_copy_string(buf, vmu->pager, len);
03229    } else  if (!strcasecmp(colname, "etemplate")) { 
03230       if (!ast_strlen_zero(vmu->etemplate))
03231          ast_copy_string(buf, vmu->etemplate, len);
03232       else
03233          ast_copy_string(buf, "email-default", len);
03234    } else  if (!strcasecmp(colname, "language")) { 
03235       ast_copy_string(buf, vmu->language, len);
03236    } else  if (!strcasecmp(colname, "timezone")) { 
03237       ast_copy_string(buf, vmu->zonetag, len);
03238    } else  if (!strcasecmp(colname, "ptemplate")) { 
03239       if (!ast_strlen_zero(vmu->ptemplate))
03240          ast_copy_string(buf, vmu->ptemplate, len);
03241       else
03242          ast_copy_string(buf, "email-default", len);
03243    } else  if (!strcasecmp(colname, "accountcode")) {
03244       ast_copy_string(buf, vmu->accountcode, len);
03245    } else  if (!strcasecmp(colname, "pincode")) {
03246       ast_copy_string(buf, vmu->pincode, len);
03247    } else  if (!strcasecmp(colname, "path")) {
03248       check_dirpath(buf, len, vmu->domain, vmu->username, NULL);
03249    } else { /* Look in channel variables */
03250       struct ast_variable *var;
03251 
03252       for (var = vmu->chanvars ; var ; var = var->next)
03253          if (!strcmp(var->name, colname)) {
03254             ast_copy_string(buf, var->value, len);
03255             break;
03256          }
03257    }
03258 
03259    if(ast_test_flag(vmu, MVM_ALLOCED))
03260       free_user(vmu);
03261 
03262    return 0;
03263 }
03264 
03265 /*! \brief lock directory
03266 
03267    only return failure if ast_lock_path returns 'timeout',
03268    not if the path does not exist or any other reason
03269 */
03270 static int vm_lock_path(const char *path)
03271 {
03272    switch (ast_lock_path(path)) {
03273    case AST_LOCK_TIMEOUT:
03274       return -1;
03275    default:
03276       return 0;
03277    }
03278 }
03279 
03280 /*! \brief Access counter file, lock directory, read and possibly write it again changed 
03281    \param directory  Directory to crate file in
03282    \param countername   filename 
03283    \param value      If set to zero, we only read the variable
03284    \param operand    0 to read, 1 to set new value, 2 to change 
03285    \return -1 on error, otherwise counter value
03286 */
03287 static int access_counter_file(char *directory, char *countername, int value, int operand)
03288 {
03289    char filename[BUFSIZ];
03290    char readbuf[BUFSIZ];
03291    FILE *counterfile;
03292    int old = 0, counter = 0;
03293 
03294    /* Lock directory */
03295    if (vm_lock_path(directory)) {
03296       return -1;  /* Could not lock directory */
03297    }
03298    snprintf(filename, sizeof(filename), "%s/%s.counter", directory, countername);
03299    if (operand != 1) {
03300       counterfile = fopen(filename, "r");
03301       if (counterfile) {
03302          if(fgets(readbuf, sizeof(readbuf), counterfile)) {
03303             ast_debug(3, "Read this string from counter file: %s\n", readbuf);
03304             old = counter = atoi(readbuf);
03305          }
03306          fclose(counterfile);
03307       }
03308    }
03309    switch (operand) {
03310    case 0:  /* Read only */
03311       ast_unlock_path(directory);
03312       ast_debug(2, "MINIVM Counter %s/%s: Value %d\n", directory, countername, counter);
03313       return counter;
03314       break;
03315    case 1: /* Set new value */
03316       counter = value;
03317       break;
03318    case 2: /* Change value */
03319       counter += value;
03320       if (counter < 0)  /* Don't allow counters to fall below zero */
03321          counter = 0;
03322       break;
03323    }
03324    
03325    /* Now, write the new value to the file */
03326    counterfile = fopen(filename, "w");
03327    if (!counterfile) {
03328       ast_log(LOG_ERROR, "Could not open counter file for writing : %s - %s\n", filename, strerror(errno));
03329       ast_unlock_path(directory);
03330       return -1;  /* Could not open file for writing */
03331    }
03332    fprintf(counterfile, "%d\n\n", counter);
03333    fclose(counterfile);
03334    ast_unlock_path(directory);
03335    ast_debug(2, "MINIVM Counter %s/%s: Old value %d New value %d\n", directory, countername, old, counter);
03336    return counter;
03337 }
03338 
03339 /*! \brief  ${MINIVMCOUNTER()} Dialplan function - read counters */
03340 static int minivm_counter_func_read(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len)
03341 {
03342    char *username, *domain, *countername;
03343    struct minivm_account *vmu = NULL;
03344    char userpath[BUFSIZ];
03345    int res;
03346 
03347    *buf = '\0';
03348 
03349    if (!(username = ast_strdupa(data))) { /* Copy indata to local buffer */
03350       ast_log(LOG_WARNING, "Memory error!\n");
03351       return -1;
03352    }
03353    if ((countername = strchr(username, ':'))) {
03354       *countername = '\0';
03355       countername++;
03356    } 
03357 
03358    if ((domain = strchr(username, '@'))) {
03359       *domain = '\0';
03360       domain++;
03361    }
03362 
03363    /* If we have neither username nor domain now, let's give up */
03364    if (ast_strlen_zero(username) && ast_strlen_zero(domain)) {
03365       ast_log(LOG_ERROR, "No account given\n");
03366       return -1;
03367    }
03368 
03369    if (ast_strlen_zero(countername)) {
03370       ast_log(LOG_ERROR, "This function needs two arguments: Account:countername\n");
03371       return -1;
03372    }
03373 
03374    /* We only have a domain, no username */
03375    if (!ast_strlen_zero(username) && ast_strlen_zero(domain)) {
03376       domain = username;
03377       username = NULL;
03378    }
03379 
03380    /* If we can't find account or if the account is temporary, return. */
03381    if (!ast_strlen_zero(username) && !(vmu = find_account(domain, username, FALSE))) {
03382       ast_log(LOG_ERROR, "Minivm account does not exist: %s@%s\n", username, domain);
03383       return 0;
03384    }
03385 
03386    create_dirpath(userpath, sizeof(userpath), domain, username, NULL);
03387 
03388    /* We have the path, now read the counter file */
03389    res = access_counter_file(userpath, countername, 0, 0);
03390    if (res >= 0)
03391       snprintf(buf, len, "%d", res);
03392    return 0;
03393 }
03394 
03395 /*! \brief  ${MINIVMCOUNTER()} Dialplan function - changes counter data */
03396 static int minivm_counter_func_write(struct ast_channel *chan, const char *cmd, char *data, const char *value)
03397 {
03398    char *username, *domain, *countername, *operand;
03399    char userpath[BUFSIZ];
03400    struct minivm_account *vmu;
03401    int change = 0;
03402    int operation = 0;
03403 
03404    if(!value)
03405       return -1;
03406    change = atoi(value);
03407 
03408    if (!(username = ast_strdupa(data))) { /* Copy indata to local buffer */
03409       ast_log(LOG_WARNING, "Memory error!\n");
03410       return -1;
03411    }
03412 
03413    if ((countername = strchr(username, ':'))) {
03414       *countername = '\0';
03415       countername++;
03416    } 
03417    if ((operand = strchr(countername, ':'))) {
03418       *operand = '\0';
03419       operand++;
03420    } 
03421 
03422    if ((domain = strchr(username, '@'))) {
03423       *domain = '\0';
03424       domain++;
03425    }
03426 
03427    /* If we have neither username nor domain now, let's give up */
03428    if (ast_strlen_zero(username) && ast_strlen_zero(domain)) {
03429       ast_log(LOG_ERROR, "No account given\n");
03430       return -1;
03431    }
03432 
03433    /* We only have a domain, no username */
03434    if (!ast_strlen_zero(username) && ast_strlen_zero(domain)) {
03435       domain = username;
03436       username = NULL;
03437    }
03438 
03439    if (ast_strlen_zero(operand) || ast_strlen_zero(countername)) {
03440       ast_log(LOG_ERROR, "Writing to this function requires three arguments: Account:countername:operand\n");
03441       return -1;
03442    }
03443 
03444    /* If we can't find account or if the account is temporary, return. */
03445    if (!ast_strlen_zero(username) && !(vmu = find_account(domain, username, FALSE))) {
03446       ast_log(LOG_ERROR, "Minivm account does not exist: %s@%s\n", username, domain);
03447       return 0;
03448    }
03449 
03450    create_dirpath(userpath, sizeof(userpath), domain, username, NULL);
03451    /* Now, find out our operator */
03452    if (*operand == 'i') /* Increment */
03453       operation = 2;
03454    else if (*operand == 'd') {
03455       change = change * -1;
03456       operation = 2;
03457    } else if (*operand == 's')
03458       operation = 1;
03459    else {
03460       ast_log(LOG_ERROR, "Unknown operator: %s\n", operand);
03461       return -1;
03462    }
03463 
03464    /* We have the path, now read the counter file */
03465    access_counter_file(userpath, countername, change, operation);
03466    return 0;
03467 }
03468 
03469 
03470 /*! \brief CLI commands for Mini-voicemail */
03471 static struct ast_cli_entry cli_minivm[] = {
03472    AST_CLI_DEFINE(handle_minivm_show_users, "List defined mini-voicemail boxes"),
03473    AST_CLI_DEFINE(handle_minivm_show_zones, "List zone message formats"),
03474    AST_CLI_DEFINE(handle_minivm_list_templates, "List message templates"), 
03475    AST_CLI_DEFINE(handle_minivm_reload, "Reload Mini-voicemail configuration"),
03476    AST_CLI_DEFINE(handle_minivm_show_stats, "Show some mini-voicemail statistics"),
03477    AST_CLI_DEFINE(handle_minivm_show_settings, "Show mini-voicemail general settings"),
03478 };
03479 
03480 static struct ast_custom_function minivm_counter_function = {
03481    .name = "MINIVMCOUNTER",
03482    .read = minivm_counter_func_read,
03483    .write = minivm_counter_func_write,
03484 };
03485 
03486 static struct ast_custom_function minivm_account_function = {
03487    .name = "MINIVMACCOUNT",
03488    .read = minivm_account_func_read,
03489 };
03490 
03491 /*! \brief Load mini voicemail module */
03492 static int load_module(void)
03493 {
03494    int res;
03495 
03496    res = ast_register_application_xml(app_minivm_record, minivm_record_exec);
03497    res = ast_register_application_xml(app_minivm_greet, minivm_greet_exec);
03498    res = ast_register_application_xml(app_minivm_notify, minivm_notify_exec);
03499    res = ast_register_application_xml(app_minivm_delete, minivm_delete_exec);
03500    res = ast_register_application_xml(app_minivm_accmess, minivm_accmess_exec);
03501    res = ast_register_application_xml(app_minivm_mwi, minivm_mwi_exec);
03502 
03503    ast_custom_function_register(&minivm_account_function);
03504    ast_custom_function_register(&minivm_counter_function);
03505    if (res)
03506       return(res);
03507 
03508    if ((res = load_config(0)))
03509       return(res);
03510 
03511    ast_cli_register_multiple(cli_minivm, ARRAY_LEN(cli_minivm));
03512 
03513    /* compute the location of the voicemail spool directory */
03514    snprintf(MVM_SPOOL_DIR, sizeof(MVM_SPOOL_DIR), "%s/voicemail/", ast_config_AST_SPOOL_DIR);
03515 
03516    return res;
03517 }
03518 
03519 /*! \brief Reload mini voicemail module */
03520 static int reload(void)
03521 {
03522    return(load_config(1));
03523 }
03524 
03525 /*! \brief Reload cofiguration */
03526 static char *handle_minivm_reload(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
03527 {
03528    
03529    switch (cmd) {
03530    case CLI_INIT:
03531       e->command = "minivm reload";
03532       e->usage =
03533          "Usage: minivm reload\n"
03534          "       Reload mini-voicemail configuration and reset statistics\n";
03535       return NULL;
03536    case CLI_GENERATE:
03537       return NULL;
03538    }
03539    
03540    reload();
03541    ast_cli(a->fd, "\n-- Mini voicemail re-configured \n");
03542    return CLI_SUCCESS;
03543 }
03544 
03545 /*! \brief Unload mini voicemail module */
03546 static int unload_module(void)
03547 {
03548    int res;
03549    
03550    res = ast_unregister_application(app_minivm_record);
03551    res |= ast_unregister_application(app_minivm_greet);
03552    res |= ast_unregister_application(app_minivm_notify);
03553    res |= ast_unregister_application(app_minivm_delete);
03554    res |= ast_unregister_application(app_minivm_accmess);
03555    res |= ast_unregister_application(app_minivm_mwi);
03556 
03557    ast_cli_unregister_multiple(cli_minivm, ARRAY_LEN(cli_minivm));
03558    ast_custom_function_unregister(&minivm_account_function);
03559    ast_custom_function_unregister(&minivm_counter_function);
03560 
03561    message_destroy_list();    /* Destroy list of voicemail message templates */
03562    timezone_destroy_list();   /* Destroy list of timezones */
03563    vmaccounts_destroy_list(); /* Destroy list of voicemail accounts */
03564 
03565    return res;
03566 }
03567 
03568 
03569 AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_DEFAULT, "Mini VoiceMail (A minimal Voicemail e-mail System)",
03570       .load = load_module,
03571       .unload = unload_module,
03572       .reload = reload,
03573       );