Fri Aug 17 00:17:09 2018

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

Generated on 17 Aug 2018 for Asterisk - The Open Source Telephony Project by  doxygen 1.6.1