Wed Nov 28 2012 04:18:01

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