Mon Oct 8 12:38:56 2012

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

Generated on Mon Oct 8 12:38:56 2012 for Asterisk - The Open Source Telephony Project by  doxygen 1.4.7