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

Generated on Sat Mar 10 01:53:58 2012 for Asterisk - The Open Source Telephony Project by  doxygen 1.4.7