Wed Apr 6 11:29:38 2011

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

Generated on Wed Apr 6 11:29:38 2011 for Asterisk - The Open Source Telephony Project by  doxygen 1.4.7